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 --- sw/source/core/SwNumberTree/SwNodeNum.cxx | 369 + sw/source/core/SwNumberTree/SwNumberTree.cxx | 1182 +++ sw/source/core/access/AccessibilityCheck.cxx | 811 +++ sw/source/core/access/AccessibilityIssue.cxx | 82 + sw/source/core/access/acccell.cxx | 467 ++ sw/source/core/access/acccell.hxx | 135 + sw/source/core/access/acccontext.cxx | 1526 ++++ sw/source/core/access/acccontext.hxx | 360 + sw/source/core/access/accdoc.cxx | 719 ++ sw/source/core/access/accdoc.hxx | 172 + sw/source/core/access/accembedded.cxx | 121 + sw/source/core/access/accembedded.hxx | 72 + sw/source/core/access/accfootnote.cxx | 118 + sw/source/core/access/accfootnote.hxx | 65 + sw/source/core/access/accframe.cxx | 479 ++ sw/source/core/access/accframe.hxx | 163 + sw/source/core/access/accframebase.cxx | 374 + sw/source/core/access/accframebase.hxx | 65 + sw/source/core/access/accfrmobj.cxx | 408 ++ sw/source/core/access/accfrmobj.hxx | 89 + sw/source/core/access/accfrmobjmap.cxx | 149 + sw/source/core/access/accfrmobjmap.hxx | 126 + sw/source/core/access/accfrmobjslist.cxx | 163 + sw/source/core/access/accfrmobjslist.hxx | 131 + sw/source/core/access/accgraphic.cxx | 75 + sw/source/core/access/accgraphic.hxx | 57 + sw/source/core/access/accheaderfooter.cxx | 110 + sw/source/core/access/accheaderfooter.hxx | 68 + sw/source/core/access/acchyperlink.cxx | 237 + sw/source/core/access/acchyperlink.hxx | 75 + sw/source/core/access/acchypertextdata.cxx | 47 + sw/source/core/access/acchypertextdata.hxx | 54 + sw/source/core/access/accmap.cxx | 3527 +++++++++ sw/source/core/access/accnotextframe.cxx | 316 + sw/source/core/access/accnotextframe.hxx | 125 + sw/source/core/access/accnotexthyperlink.cxx | 234 + sw/source/core/access/accnotexthyperlink.hxx | 65 + sw/source/core/access/accpage.cxx | 160 + sw/source/core/access/accpage.hxx | 77 + sw/source/core/access/accpara.cxx | 3558 ++++++++++ sw/source/core/access/accpara.hxx | 399 ++ sw/source/core/access/accportions.cxx | 761 ++ sw/source/core/access/accportions.hxx | 171 + sw/source/core/access/accpreview.cxx | 75 + sw/source/core/access/accpreview.hxx | 68 + sw/source/core/access/accselectionhelper.cxx | 348 + sw/source/core/access/accselectionhelper.hxx | 74 + sw/source/core/access/acctable.cxx | 1739 +++++ sw/source/core/access/acctable.hxx | 280 + sw/source/core/access/acctextframe.cxx | 320 + sw/source/core/access/acctextframe.hxx | 116 + sw/source/core/access/parachangetrackinginfo.cxx | 204 + sw/source/core/access/parachangetrackinginfo.hxx | 51 + sw/source/core/access/textmarkuphelper.cxx | 225 + sw/source/core/access/textmarkuphelper.hxx | 70 + sw/source/core/attr/calbck.cxx | 381 + sw/source/core/attr/cellatr.cxx | 226 + sw/source/core/attr/fmtfollowtextflow.cxx | 30 + sw/source/core/attr/fmtwrapinfluenceonobjpos.cxx | 174 + sw/source/core/attr/format.cxx | 803 +++ sw/source/core/attr/hints.cxx | 270 + sw/source/core/attr/swatrset.cxx | 481 ++ sw/source/core/bastyp/SwSmartTagMgr.cxx | 70 + sw/source/core/bastyp/bparr.cxx | 505 ++ sw/source/core/bastyp/breakit.cxx | 183 + sw/source/core/bastyp/calc.cxx | 1498 ++++ sw/source/core/bastyp/checkit.cxx | 33 + sw/source/core/bastyp/index.cxx | 391 + sw/source/core/bastyp/init.cxx | 797 +++ sw/source/core/bastyp/proofreadingiterator.cxx | 68 + sw/source/core/bastyp/swcache.cxx | 512 ++ sw/source/core/bastyp/swrect.cxx | 243 + sw/source/core/bastyp/swregion.cxx | 193 + sw/source/core/bastyp/swtypes.cxx | 72 + sw/source/core/bastyp/tabcol.cxx | 92 + sw/source/core/crsr/BlockCursor.cxx | 32 + sw/source/core/crsr/BlockCursor.hxx | 91 + sw/source/core/crsr/DateFormFieldButton.cxx | 97 + sw/source/core/crsr/DropDownFormFieldButton.cxx | 167 + sw/source/core/crsr/FormFieldButton.cxx | 151 + sw/source/core/crsr/annotationmark.cxx | 97 + sw/source/core/crsr/bookmrk.cxx | 1068 +++ sw/source/core/crsr/callnk.cxx | 247 + sw/source/core/crsr/callnk.hxx | 48 + sw/source/core/crsr/crbm.cxx | 318 + sw/source/core/crsr/crossrefbookmark.cxx | 93 + sw/source/core/crsr/crsrsh.cxx | 3814 ++++++++++ sw/source/core/crsr/crstrvl.cxx | 2592 +++++++ sw/source/core/crsr/crstrvl1.cxx | 126 + sw/source/core/crsr/findattr.cxx | 1444 ++++ sw/source/core/crsr/findcoll.cxx | 113 + sw/source/core/crsr/findfmt.cxx | 97 + sw/source/core/crsr/findtxt.cxx | 1183 +++ sw/source/core/crsr/overlayrangesoutline.cxx | 104 + sw/source/core/crsr/overlayrangesoutline.hxx | 60 + sw/source/core/crsr/pam.cxx | 1171 +++ sw/source/core/crsr/paminit.cxx | 61 + sw/source/core/crsr/swcrsr.cxx | 2569 +++++++ sw/source/core/crsr/trvlcol.cxx | 103 + sw/source/core/crsr/trvlfnfl.cxx | 378 + sw/source/core/crsr/trvlreg.cxx | 280 + sw/source/core/crsr/trvltbl.cxx | 927 +++ sw/source/core/crsr/viscrs.cxx | 941 +++ sw/source/core/doc/CntntIdxStore.cxx | 477 ++ .../core/doc/DocumentChartDataProviderManager.cxx | 106 + .../core/doc/DocumentContentOperationsManager.cxx | 5138 ++++++++++++++ sw/source/core/doc/DocumentDeviceManager.cxx | 376 + sw/source/core/doc/DocumentDrawModelManager.cxx | 356 + sw/source/core/doc/DocumentExternalDataManager.cxx | 34 + sw/source/core/doc/DocumentFieldsManager.cxx | 1860 +++++ sw/source/core/doc/DocumentLayoutManager.cxx | 517 ++ .../doc/DocumentLinksAdministrationManager.cxx | 583 ++ sw/source/core/doc/DocumentListItemsManager.cxx | 105 + sw/source/core/doc/DocumentListsManager.cxx | 214 + sw/source/core/doc/DocumentOutlineNodesManager.cxx | 131 + sw/source/core/doc/DocumentRedlineManager.cxx | 3233 +++++++++ sw/source/core/doc/DocumentSettingManager.cxx | 964 +++ sw/source/core/doc/DocumentStateManager.cxx | 118 + sw/source/core/doc/DocumentStatisticsManager.cxx | 218 + sw/source/core/doc/DocumentStylePoolManager.cxx | 2655 +++++++ sw/source/core/doc/DocumentTimerManager.cxx | 236 + sw/source/core/doc/SwDocIdle.cxx | 62 + sw/source/core/doc/SwStyleNameMapper.cxx | 772 ++ sw/source/core/doc/acmplwrd.cxx | 402 ++ sw/source/core/doc/dbgoutsw.cxx | 846 +++ sw/source/core/doc/doc.cxx | 1830 +++++ sw/source/core/doc/docbasic.cxx | 234 + sw/source/core/doc/docbm.cxx | 1862 +++++ sw/source/core/doc/docchart.cxx | 179 + sw/source/core/doc/doccomp.cxx | 2698 +++++++ sw/source/core/doc/doccorr.cxx | 364 + sw/source/core/doc/docdesc.cxx | 922 +++ sw/source/core/doc/docdraw.cxx | 634 ++ sw/source/core/doc/docedt.cxx | 886 +++ sw/source/core/doc/docfld.cxx | 1168 +++ sw/source/core/doc/docfly.cxx | 1159 +++ sw/source/core/doc/docfmt.cxx | 2151 ++++++ sw/source/core/doc/docftn.cxx | 547 ++ sw/source/core/doc/docglbl.cxx | 523 ++ sw/source/core/doc/docglos.cxx | 212 + sw/source/core/doc/doclay.cxx | 1682 +++++ sw/source/core/doc/docnew.cxx | 1282 ++++ sw/source/core/doc/docnum.cxx | 2622 +++++++ sw/source/core/doc/docredln.cxx | 1906 +++++ sw/source/core/doc/docruby.cxx | 327 + sw/source/core/doc/docsort.cxx | 937 +++ sw/source/core/doc/docstat.cxx | 51 + sw/source/core/doc/doctxm.cxx | 2076 ++++++ sw/source/core/doc/docxforms.cxx | 129 + sw/source/core/doc/extinput.cxx | 304 + sw/source/core/doc/fmtcol.cxx | 627 ++ sw/source/core/doc/ftnidx.cxx | 520 ++ sw/source/core/doc/gctable.cxx | 450 ++ sw/source/core/doc/htmltbl.cxx | 1768 +++++ sw/source/core/doc/lineinfo.cxx | 129 + sw/source/core/doc/list.cxx | 274 + sw/source/core/doc/notxtfrm.cxx | 1549 ++++ sw/source/core/doc/number.cxx | 1475 ++++ sw/source/core/doc/poolfmt.cxx | 301 + sw/source/core/doc/rdfhelper.cxx | 264 + sw/source/core/doc/sortopt.cxx | 64 + sw/source/core/doc/swserv.cxx | 320 + sw/source/core/doc/swstylemanager.cxx | 158 + sw/source/core/doc/swstylemanager.hxx | 32 + sw/source/core/doc/tblafmt.cxx | 1251 ++++ sw/source/core/doc/tblcpy.cxx | 1042 +++ sw/source/core/doc/tblrwcl.cxx | 3403 +++++++++ sw/source/core/doc/textboxhelper.cxx | 774 ++ sw/source/core/doc/visiturl.cxx | 125 + sw/source/core/docnode/cancellablejob.cxx | 33 + sw/source/core/docnode/cancellablejob.hxx | 47 + sw/source/core/docnode/finalthreadmanager.cxx | 429 ++ sw/source/core/docnode/ndcopy.cxx | 361 + sw/source/core/docnode/ndnotxt.cxx | 293 + sw/source/core/docnode/ndnum.cxx | 96 + sw/source/core/docnode/ndsect.cxx | 1422 ++++ sw/source/core/docnode/ndsect.hxx | 32 + sw/source/core/docnode/ndtbl.cxx | 4645 ++++++++++++ sw/source/core/docnode/ndtbl1.cxx | 1631 +++++ sw/source/core/docnode/node.cxx | 2160 ++++++ sw/source/core/docnode/node2lay.cxx | 467 ++ sw/source/core/docnode/nodes.cxx | 2335 ++++++ sw/source/core/docnode/observablethread.cxx | 64 + sw/source/core/docnode/pausethreadstarting.cxx | 48 + .../core/docnode/retrievedinputstreamdata.cxx | 153 + sw/source/core/docnode/retrieveinputstream.cxx | 83 + .../core/docnode/retrieveinputstreamconsumer.cxx | 63 + sw/source/core/docnode/section.cxx | 1594 +++++ sw/source/core/docnode/swbaslnk.cxx | 359 + sw/source/core/docnode/swthreadjoiner.cxx | 54 + sw/source/core/docnode/swthreadmanager.cxx | 83 + sw/source/core/docnode/threadlistener.cxx | 48 + sw/source/core/docnode/threadmanager.cxx | 253 + sw/source/core/docnode/threadmanager.hxx | 148 + sw/source/core/draw/dcontact.cxx | 2530 +++++++ sw/source/core/draw/dflyobj.cxx | 1295 ++++ sw/source/core/draw/dobjfac.cxx | 38 + sw/source/core/draw/dpage.cxx | 254 + sw/source/core/draw/drawdoc.cxx | 138 + sw/source/core/draw/dview.cxx | 998 +++ sw/source/core/edit/acorrect.cxx | 693 ++ sw/source/core/edit/autofmt.cxx | 2834 ++++++++ sw/source/core/edit/edatmisc.cxx | 190 + sw/source/core/edit/edattr.cxx | 853 +++ sw/source/core/edit/eddel.cxx | 363 + sw/source/core/edit/edfcol.cxx | 2287 ++++++ sw/source/core/edit/edfld.cxx | 417 ++ sw/source/core/edit/edfldexp.cxx | 58 + sw/source/core/edit/edfmt.cxx | 159 + sw/source/core/edit/edglbldc.cxx | 384 + sw/source/core/edit/edglss.cxx | 337 + sw/source/core/edit/editsh.cxx | 1056 +++ sw/source/core/edit/edlingu.cxx | 1712 +++++ sw/source/core/edit/ednumber.cxx | 912 +++ sw/source/core/edit/edredln.cxx | 153 + sw/source/core/edit/edsect.cxx | 421 ++ sw/source/core/edit/edtab.cxx | 531 ++ sw/source/core/edit/edtox.cxx | 388 + sw/source/core/edit/edundo.cxx | 246 + sw/source/core/edit/edws.cxx | 321 + sw/source/core/fields/authfld.cxx | 668 ++ sw/source/core/fields/cellfml.cxx | 1218 ++++ sw/source/core/fields/chpfld.cxx | 301 + sw/source/core/fields/dbfld.cxx | 870 +++ sw/source/core/fields/ddefld.cxx | 377 + sw/source/core/fields/ddetbl.cxx | 207 + sw/source/core/fields/docufld.cxx | 2646 +++++++ sw/source/core/fields/expfld.cxx | 1432 ++++ sw/source/core/fields/fldbas.cxx | 824 +++ sw/source/core/fields/flddat.cxx | 232 + sw/source/core/fields/flddropdown.cxx | 217 + sw/source/core/fields/fldlst.cxx | 153 + sw/source/core/fields/macrofld.cxx | 222 + sw/source/core/fields/postithelper.cxx | 253 + sw/source/core/fields/reffld.cxx | 1462 ++++ sw/source/core/fields/scrptfld.cxx | 119 + sw/source/core/fields/tblcalc.cxx | 212 + sw/source/core/fields/textapi.cxx | 193 + sw/source/core/fields/usrfld.cxx | 368 + sw/source/core/frmedt/fecopy.cxx | 1626 +++++ sw/source/core/frmedt/fedesc.cxx | 244 + sw/source/core/frmedt/fefly1.cxx | 2110 ++++++ sw/source/core/frmedt/feflyole.cxx | 127 + sw/source/core/frmedt/feshview.cxx | 3262 +++++++++ sw/source/core/frmedt/fetab.cxx | 2324 ++++++ sw/source/core/frmedt/fews.cxx | 1328 ++++ sw/source/core/frmedt/tblsel.cxx | 2601 +++++++ sw/source/core/graphic/grfatr.cxx | 338 + sw/source/core/graphic/ndgrf.cxx | 874 +++ sw/source/core/inc/AccessibilityCheck.hxx | 38 + sw/source/core/inc/AccessibilityIssue.hxx | 67 + sw/source/core/inc/DateFormFieldButton.hxx | 45 + .../core/inc/DocumentChartDataProviderManager.hxx | 66 + .../core/inc/DocumentContentOperationsManager.hxx | 192 + sw/source/core/inc/DocumentDeviceManager.hxx | 84 + sw/source/core/inc/DocumentDrawModelManager.hxx | 90 + sw/source/core/inc/DocumentExternalDataManager.hxx | 46 + sw/source/core/inc/DocumentFieldsManager.hxx | 111 + sw/source/core/inc/DocumentLayoutManager.hxx | 76 + .../inc/DocumentLinksAdministrationManager.hxx | 84 + sw/source/core/inc/DocumentListItemsManager.hxx | 71 + sw/source/core/inc/DocumentListsManager.hxx | 73 + sw/source/core/inc/DocumentOutlineNodesManager.hxx | 65 + sw/source/core/inc/DocumentRedlineManager.hxx | 157 + sw/source/core/inc/DocumentSettingManager.hxx | 205 + sw/source/core/inc/DocumentStateManager.hxx | 65 + sw/source/core/inc/DocumentStatisticsManager.hxx | 72 + sw/source/core/inc/DocumentStylePoolManager.hxx | 61 + sw/source/core/inc/DocumentTimerManager.hxx | 89 + sw/source/core/inc/DropDownFormFieldButton.hxx | 40 + sw/source/core/inc/FormFieldButton.hxx | 56 + sw/source/core/inc/GetMetricVal.hxx | 42 + sw/source/core/inc/MarkManager.hxx | 150 + sw/source/core/inc/SwGrammarMarkUp.hxx | 68 + sw/source/core/inc/SwPortionHandler.hxx | 105 + sw/source/core/inc/SwUndoFmt.hxx | 256 + sw/source/core/inc/SwUndoPageDesc.hxx | 86 + sw/source/core/inc/SwUndoTOXChange.hxx | 48 + sw/source/core/inc/SwXMLBlockExport.hxx | 66 + sw/source/core/inc/SwXMLBlockImport.hxx | 131 + sw/source/core/inc/SwXMLTextBlocks.hxx | 96 + sw/source/core/inc/SwXTextDefaults.hxx | 66 + sw/source/core/inc/TextFrameIndex.hxx | 26 + sw/source/core/inc/UndoAttribute.hxx | 257 + sw/source/core/inc/UndoBookmark.hxx | 157 + sw/source/core/inc/UndoCore.hxx | 273 + sw/source/core/inc/UndoDelete.hxx | 106 + sw/source/core/inc/UndoDraw.hxx | 134 + sw/source/core/inc/UndoInsert.hxx | 223 + sw/source/core/inc/UndoManager.hxx | 131 + sw/source/core/inc/UndoNumbering.hxx | 142 + sw/source/core/inc/UndoOverwrite.hxx | 93 + sw/source/core/inc/UndoRedline.hxx | 137 + sw/source/core/inc/UndoSection.hxx | 97 + sw/source/core/inc/UndoSort.hxx | 84 + sw/source/core/inc/UndoSplitMove.hxx | 85 + sw/source/core/inc/UndoTable.hxx | 418 ++ sw/source/core/inc/acorrect.hxx | 119 + sw/source/core/inc/anchoredobjectposition.hxx | 447 ++ sw/source/core/inc/annotationmark.hxx | 48 + .../core/inc/ascharanchoredobjectposition.hxx | 160 + sw/source/core/inc/attrhint.hxx | 31 + sw/source/core/inc/bodyfrm.hxx | 41 + sw/source/core/inc/bookmrk.hxx | 356 + sw/source/core/inc/cellfrm.hxx | 70 + sw/source/core/inc/cntfrm.hxx | 125 + sw/source/core/inc/colfrm.hxx | 40 + sw/source/core/inc/crossrefbookmark.hxx | 86 + sw/source/core/inc/dbg_lay.hxx | 105 + sw/source/core/inc/dflyobj.hxx | 148 + sw/source/core/inc/dialoghelp.hxx | 30 + sw/source/core/inc/docedt.hxx | 31 + sw/source/core/inc/docfld.hxx | 179 + sw/source/core/inc/docredln.hxx | 35 + sw/source/core/inc/docsort.hxx | 160 + sw/source/core/inc/doctxm.hxx | 108 + sw/source/core/inc/drawfont.hxx | 597 ++ sw/source/core/inc/dumpfilter.hxx | 59 + sw/source/core/inc/dview.hxx | 126 + sw/source/core/inc/environmentofanchoredobject.hxx | 98 + sw/source/core/inc/fefly.hxx | 31 + sw/source/core/inc/fieldhint.hxx | 43 + sw/source/core/inc/flowfrm.hxx | 279 + sw/source/core/inc/flyfrm.hxx | 284 + sw/source/core/inc/flyfrms.hxx | 241 + sw/source/core/inc/fntcache.hxx | 164 + sw/source/core/inc/fntcap.hxx | 37 + sw/source/core/inc/frame.hxx | 1417 ++++ sw/source/core/inc/frminf.hxx | 75 + sw/source/core/inc/frmtool.hxx | 610 ++ sw/source/core/inc/ftnboss.hxx | 130 + sw/source/core/inc/ftnfrm.hxx | 165 + sw/source/core/inc/hffrm.hxx | 58 + sw/source/core/inc/ifinishedthreadlistener.hxx | 46 + sw/source/core/inc/layact.hxx | 213 + sw/source/core/inc/laycache.hxx | 69 + sw/source/core/inc/layfrm.hxx | 226 + sw/source/core/inc/layouter.hxx | 146 + sw/source/core/inc/movedfwdfrmsbyobjpos.hxx | 58 + sw/source/core/inc/mvsave.hxx | 199 + sw/source/core/inc/node2lay.hxx | 81 + sw/source/core/inc/noteurl.hxx | 32 + sw/source/core/inc/notxtfrm.hxx | 103 + sw/source/core/inc/objectformatter.hxx | 174 + sw/source/core/inc/observablethread.hxx | 87 + sw/source/core/inc/pagedeschint.hxx | 40 + sw/source/core/inc/pagefrm.hxx | 443 ++ sw/source/core/inc/paintfrm.hxx | 36 + sw/source/core/inc/pamtyp.hxx | 115 + sw/source/core/inc/prevwpage.hxx | 53 + sw/source/core/inc/ptqueue.hxx | 57 + sw/source/core/inc/retrievedinputstreamdata.hxx | 89 + sw/source/core/inc/retrieveinputstream.hxx | 57 + sw/source/core/inc/retrieveinputstreamconsumer.hxx | 59 + sw/source/core/inc/rolbck.hxx | 435 ++ sw/source/core/inc/rootfrm.hxx | 462 ++ sw/source/core/inc/rowfrm.hxx | 127 + sw/source/core/inc/scriptinfo.hxx | 399 ++ sw/source/core/inc/sectfrm.hxx | 175 + sw/source/core/inc/sortedobjs.hxx | 100 + sw/source/core/inc/swblocks.hxx | 128 + sw/source/core/inc/swcache.hxx | 265 + sw/source/core/inc/swfntcch.hxx | 76 + sw/source/core/inc/swfont.hxx | 1000 +++ sw/source/core/inc/swselectionlist.hxx | 91 + sw/source/core/inc/swthreadjoiner.hxx | 36 + sw/source/core/inc/swthreadmanager.hxx | 78 + sw/source/core/inc/tabfrm.hxx | 254 + sw/source/core/inc/tblrwcl.hxx | 198 + sw/source/core/inc/textapi.hxx | 65 + sw/source/core/inc/threadlistener.hxx | 55 + .../core/inc/tocntntanchoredobjectposition.hxx | 93 + .../core/inc/tolayoutanchoredobjectposition.hxx | 52 + sw/source/core/inc/txmsrt.hxx | 301 + sw/source/core/inc/txtfly.hxx | 378 + sw/source/core/inc/txtfrm.hxx | 1024 +++ sw/source/core/inc/txttypes.hxx | 93 + sw/source/core/inc/undoflystrattr.hxx | 49 + sw/source/core/inc/unobookmark.hxx | 210 + sw/source/core/inc/unoevent.hxx | 92 + sw/source/core/inc/unofield.hxx | 240 + sw/source/core/inc/unoflatpara.hxx | 146 + sw/source/core/inc/unofldmid.h | 52 + sw/source/core/inc/unofootnote.hxx | 149 + sw/source/core/inc/unofreg.hxx | 38 + sw/source/core/inc/unoidx.hxx | 229 + sw/source/core/inc/unometa.hxx | 270 + sw/source/core/inc/unoparaframeenum.hxx | 77 + sw/source/core/inc/unoport.hxx | 307 + sw/source/core/inc/unorefmark.hxx | 116 + sw/source/core/inc/unosection.hxx | 163 + sw/source/core/inc/unotextmarkup.hxx | 103 + sw/source/core/inc/viewimp.hxx | 307 + sw/source/core/inc/visiturl.hxx | 39 + sw/source/core/inc/wrong.hxx | 417 ++ sw/source/core/layout/anchoreddrawobject.cxx | 918 +++ sw/source/core/layout/anchoredobject.cxx | 900 +++ sw/source/core/layout/atrfrm.cxx | 3656 ++++++++++ sw/source/core/layout/calcmove.cxx | 2232 ++++++ sw/source/core/layout/colfrm.cxx | 445 ++ sw/source/core/layout/dbg_lay.cxx | 923 +++ sw/source/core/layout/dumpfilter.cxx | 163 + sw/source/core/layout/findfrm.cxx | 1827 +++++ sw/source/core/layout/flowfrm.cxx | 2665 +++++++ sw/source/core/layout/fly.cxx | 2917 ++++++++ sw/source/core/layout/flycnt.cxx | 1463 ++++ sw/source/core/layout/flyincnt.cxx | 278 + sw/source/core/layout/flylay.cxx | 1489 ++++ sw/source/core/layout/flypos.cxx | 63 + sw/source/core/layout/frmtool.cxx | 3897 ++++++++++ sw/source/core/layout/ftnfrm.cxx | 2968 ++++++++ sw/source/core/layout/hffrm.cxx | 767 ++ sw/source/core/layout/layact.cxx | 2344 ++++++ sw/source/core/layout/laycache.cxx | 1205 ++++ sw/source/core/layout/layhelp.hxx | 217 + sw/source/core/layout/layouter.cxx | 482 ++ sw/source/core/layout/legacyitem.cxx | 79 + sw/source/core/layout/movedfwdfrmsbyobjpos.cxx | 90 + sw/source/core/layout/newfrm.cxx | 613 ++ sw/source/core/layout/objectformatter.cxx | 478 ++ sw/source/core/layout/objectformatterlayfrm.cxx | 187 + sw/source/core/layout/objectformatterlayfrm.hxx | 70 + sw/source/core/layout/objectformattertxtfrm.cxx | 807 +++ sw/source/core/layout/objectformattertxtfrm.hxx | 185 + sw/source/core/layout/objstmpconsiderwrapinfl.cxx | 60 + sw/source/core/layout/objstmpconsiderwrapinfl.hxx | 42 + sw/source/core/layout/pagechg.cxx | 2546 +++++++ sw/source/core/layout/pagedesc.cxx | 633 ++ sw/source/core/layout/paintfrm.cxx | 7495 ++++++++++++++++++++ sw/source/core/layout/sectfrm.cxx | 2916 ++++++++ sw/source/core/layout/softpagebreak.cxx | 154 + sw/source/core/layout/sortedobjs.cxx | 292 + sw/source/core/layout/ssfrm.cxx | 739 ++ sw/source/core/layout/swselectionlist.cxx | 83 + sw/source/core/layout/tabfrm.cxx | 5851 +++++++++++++++ sw/source/core/layout/trvlfrm.cxx | 2638 +++++++ sw/source/core/layout/unusedf.cxx | 78 + sw/source/core/layout/virtoutp.cxx | 188 + sw/source/core/layout/virtoutp.hxx | 61 + sw/source/core/layout/wsfrm.cxx | 4602 ++++++++++++ .../objectpositioning/anchoredobjectposition.cxx | 1132 +++ .../ascharanchoredobjectposition.cxx | 395 ++ .../environmentofanchoredobject.cxx | 98 + .../tocntntanchoredobjectposition.cxx | 1202 ++++ .../tolayoutanchoredobjectposition.cxx | 226 + sw/source/core/ole/ndole.cxx | 1246 ++++ sw/source/core/para/paratr.cxx | 236 + sw/source/core/sw3io/swacorr.cxx | 99 + sw/source/core/swg/BlockListTokens.txt | 7 + sw/source/core/swg/SwXMLBlockExport.cxx | 136 + sw/source/core/swg/SwXMLBlockImport.cxx | 359 + sw/source/core/swg/SwXMLSectionList.cxx | 137 + sw/source/core/swg/SwXMLTextBlocks.cxx | 517 ++ sw/source/core/swg/SwXMLTextBlocks1.cxx | 566 ++ sw/source/core/swg/TextBlockTokens.txt | 5 + sw/source/core/swg/swblocks.cxx | 569 ++ sw/source/core/table/swnewtable.cxx | 2199 ++++++ sw/source/core/table/swtable.cxx | 2771 ++++++++ sw/source/core/text/EnhancedPDFExportHelper.cxx | 2280 ++++++ sw/source/core/text/SwGrammarMarkUp.cxx | 145 + sw/source/core/text/atrhndl.hxx | 121 + sw/source/core/text/atrstck.cxx | 846 +++ sw/source/core/text/frmcrsr.cxx | 1685 +++++ sw/source/core/text/frmform.cxx | 2061 ++++++ sw/source/core/text/frminf.cxx | 293 + sw/source/core/text/frmpaint.cxx | 714 ++ sw/source/core/text/guess.cxx | 591 ++ sw/source/core/text/guess.hxx | 64 + sw/source/core/text/inftxt.cxx | 1991 ++++++ sw/source/core/text/inftxt.hxx | 788 ++ sw/source/core/text/itradj.cxx | 841 +++ sw/source/core/text/itratr.cxx | 1481 ++++ sw/source/core/text/itratr.hxx | 114 + sw/source/core/text/itrcrsr.cxx | 1913 +++++ sw/source/core/text/itrform2.cxx | 2889 ++++++++ sw/source/core/text/itrform2.hxx | 245 + sw/source/core/text/itrpaint.cxx | 693 ++ sw/source/core/text/itrpaint.hxx | 65 + sw/source/core/text/itrtxt.cxx | 421 ++ sw/source/core/text/itrtxt.hxx | 340 + sw/source/core/text/noteurl.cxx | 25 + sw/source/core/text/pordrop.hxx | 106 + sw/source/core/text/porexp.cxx | 250 + sw/source/core/text/porexp.hxx | 74 + sw/source/core/text/porfld.cxx | 1350 ++++ sw/source/core/text/porfld.hxx | 254 + sw/source/core/text/porfly.cxx | 405 ++ sw/source/core/text/porfly.hxx | 91 + sw/source/core/text/porftn.hxx | 102 + sw/source/core/text/porglue.cxx | 260 + sw/source/core/text/porglue.hxx | 88 + sw/source/core/text/porhyph.hxx | 84 + sw/source/core/text/porlay.cxx | 2645 +++++++ sw/source/core/text/porlay.hxx | 327 + sw/source/core/text/porlin.cxx | 320 + sw/source/core/text/porlin.hxx | 204 + sw/source/core/text/pormulti.cxx | 2569 +++++++ sw/source/core/text/pormulti.hxx | 256 + sw/source/core/text/porref.cxx | 75 + sw/source/core/text/porref.hxx | 48 + sw/source/core/text/porrst.cxx | 633 ++ sw/source/core/text/porrst.hxx | 174 + sw/source/core/text/portab.hxx | 114 + sw/source/core/text/portox.cxx | 77 + sw/source/core/text/portox.hxx | 49 + sw/source/core/text/portxt.cxx | 845 +++ sw/source/core/text/portxt.hxx | 103 + sw/source/core/text/possiz.hxx | 69 + sw/source/core/text/redlnitr.cxx | 926 +++ sw/source/core/text/redlnitr.hxx | 136 + sw/source/core/text/txtcache.cxx | 190 + sw/source/core/text/txtcache.hxx | 65 + sw/source/core/text/txtdrop.cxx | 1079 +++ sw/source/core/text/txtfld.cxx | 737 ++ sw/source/core/text/txtfly.cxx | 1407 ++++ sw/source/core/text/txtfrm.cxx | 4024 +++++++++++ sw/source/core/text/txtftn.cxx | 1553 ++++ sw/source/core/text/txthyph.cxx | 572 ++ sw/source/core/text/txtinit.cxx | 60 + sw/source/core/text/txtpaint.cxx | 113 + sw/source/core/text/txtpaint.hxx | 127 + sw/source/core/text/txttab.cxx | 600 ++ sw/source/core/text/widorp.cxx | 544 ++ sw/source/core/text/widorp.hxx | 84 + sw/source/core/text/wrong.cxx | 937 +++ sw/source/core/text/xmldump.cxx | 567 ++ sw/source/core/tox/ToxLinkProcessor.cxx | 75 + sw/source/core/tox/ToxTabStopTokenHandler.cxx | 128 + sw/source/core/tox/ToxTextGenerator.cxx | 440 ++ sw/source/core/tox/ToxWhitespaceStripper.cxx | 63 + sw/source/core/tox/tox.cxx | 940 +++ sw/source/core/tox/toxhlp.cxx | 119 + sw/source/core/tox/txmsrt.cxx | 864 +++ sw/source/core/txtnode/SwGrammarContact.cxx | 190 + sw/source/core/txtnode/atrfld.cxx | 732 ++ sw/source/core/txtnode/atrflyin.cxx | 297 + sw/source/core/txtnode/atrftn.cxx | 576 ++ sw/source/core/txtnode/atrref.cxx | 109 + sw/source/core/txtnode/atrtox.cxx | 90 + sw/source/core/txtnode/chrfmt.cxx | 42 + sw/source/core/txtnode/fmtatr2.cxx | 843 +++ sw/source/core/txtnode/fntcache.cxx | 2712 +++++++ sw/source/core/txtnode/fntcap.cxx | 778 ++ sw/source/core/txtnode/modeltoviewhelper.cxx | 337 + sw/source/core/txtnode/ndhints.cxx | 475 ++ sw/source/core/txtnode/ndtxt.cxx | 5326 ++++++++++++++ sw/source/core/txtnode/swfntcch.cxx | 75 + sw/source/core/txtnode/swfont.cxx | 1541 ++++ sw/source/core/txtnode/thints.cxx | 3512 +++++++++ sw/source/core/txtnode/txatbase.cxx | 163 + sw/source/core/txtnode/txatritr.cxx | 218 + sw/source/core/txtnode/txtatr2.cxx | 314 + sw/source/core/txtnode/txtedt.cxx | 2288 ++++++ sw/source/core/undo/SwRewriter.cxx | 71 + sw/source/core/undo/SwUndoField.cxx | 150 + sw/source/core/undo/SwUndoFmt.cxx | 466 ++ sw/source/core/undo/SwUndoPageDesc.cxx | 341 + sw/source/core/undo/SwUndoTOXChange.cxx | 85 + sw/source/core/undo/docundo.cxx | 735 ++ sw/source/core/undo/rolbck.cxx | 1485 ++++ sw/source/core/undo/unattr.cxx | 1055 +++ sw/source/core/undo/unbkmk.cxx | 218 + sw/source/core/undo/undel.cxx | 1317 ++++ sw/source/core/undo/undobj.cxx | 1682 +++++ sw/source/core/undo/undobj1.cxx | 714 ++ sw/source/core/undo/undoflystrattr.cxx | 90 + sw/source/core/undo/undraw.cxx | 559 ++ sw/source/core/undo/unfmco.cxx | 87 + sw/source/core/undo/unins.cxx | 1045 +++ sw/source/core/undo/unmove.cxx | 308 + sw/source/core/undo/unnum.cxx | 395 ++ sw/source/core/undo/unoutl.cxx | 49 + sw/source/core/undo/unovwr.cxx | 474 ++ sw/source/core/undo/unredln.cxx | 527 ++ sw/source/core/undo/unsect.cxx | 601 ++ sw/source/core/undo/unsort.cxx | 253 + sw/source/core/undo/unspnd.cxx | 197 + sw/source/core/undo/untbl.cxx | 3155 ++++++++ sw/source/core/undo/untblk.cxx | 451 ++ sw/source/core/unocore/SwXTextDefaults.cxx | 235 + sw/source/core/unocore/TextCursorHelper.cxx | 46 + sw/source/core/unocore/XMLRangeHelper.cxx | 387 + sw/source/core/unocore/XMLRangeHelper.hxx | 68 + sw/source/core/unocore/swunohelper.cxx | 336 + sw/source/core/unocore/unobkm.cxx | 734 ++ sw/source/core/unocore/unochart.cxx | 2717 +++++++ sw/source/core/unocore/unocoll.cxx | 1941 +++++ sw/source/core/unocore/unocrsr.cxx | 214 + sw/source/core/unocore/unocrsrhelper.cxx | 1507 ++++ sw/source/core/unocore/unodraw.cxx | 2854 ++++++++ sw/source/core/unocore/unoevent.cxx | 233 + sw/source/core/unocore/unofield.cxx | 3034 ++++++++ sw/source/core/unocore/unoflatpara.cxx | 593 ++ sw/source/core/unocore/unoframe.cxx | 3656 ++++++++++ sw/source/core/unocore/unoftn.cxx | 575 ++ sw/source/core/unocore/unoidx.cxx | 3118 ++++++++ sw/source/core/unocore/unomap.cxx | 1569 ++++ sw/source/core/unocore/unomap1.cxx | 1665 +++++ sw/source/core/unocore/unomapproperties.hxx | 529 ++ sw/source/core/unocore/unoobj.cxx | 2948 ++++++++ sw/source/core/unocore/unoobj2.cxx | 1692 +++++ sw/source/core/unocore/unoparagraph.cxx | 1417 ++++ sw/source/core/unocore/unoport.cxx | 825 +++ sw/source/core/unocore/unoportenum.cxx | 1489 ++++ sw/source/core/unocore/unoredline.cxx | 587 ++ sw/source/core/unocore/unoredlines.cxx | 168 + sw/source/core/unocore/unorefmk.cxx | 1522 ++++ sw/source/core/unocore/unosect.cxx | 1735 +++++ sw/source/core/unocore/unosett.cxx | 2446 +++++++ sw/source/core/unocore/unosrch.cxx | 691 ++ sw/source/core/unocore/unostyle.cxx | 5596 +++++++++++++++ sw/source/core/unocore/unotbl.cxx | 4194 +++++++++++ sw/source/core/unocore/unotext.cxx | 2717 +++++++ sw/source/core/unocore/unotextmarkup.cxx | 531 ++ sw/source/core/view/dialoghelp.cxx | 48 + sw/source/core/view/pagepreviewlayout.cxx | 1461 ++++ sw/source/core/view/printdata.cxx | 459 ++ sw/source/core/view/vdraw.cxx | 279 + sw/source/core/view/viewimp.cxx | 457 ++ sw/source/core/view/viewpg.cxx | 209 + sw/source/core/view/viewsh.cxx | 2619 +++++++ sw/source/core/view/vnew.cxx | 387 + sw/source/core/view/vprint.cxx | 688 ++ sw/source/core/view/vprint.hxx | 33 + 624 files changed, 416123 insertions(+) create mode 100644 sw/source/core/SwNumberTree/SwNodeNum.cxx create mode 100644 sw/source/core/SwNumberTree/SwNumberTree.cxx create mode 100644 sw/source/core/access/AccessibilityCheck.cxx create mode 100644 sw/source/core/access/AccessibilityIssue.cxx create mode 100644 sw/source/core/access/acccell.cxx create mode 100644 sw/source/core/access/acccell.hxx create mode 100644 sw/source/core/access/acccontext.cxx create mode 100644 sw/source/core/access/acccontext.hxx create mode 100644 sw/source/core/access/accdoc.cxx create mode 100644 sw/source/core/access/accdoc.hxx create mode 100644 sw/source/core/access/accembedded.cxx create mode 100644 sw/source/core/access/accembedded.hxx create mode 100644 sw/source/core/access/accfootnote.cxx create mode 100644 sw/source/core/access/accfootnote.hxx create mode 100644 sw/source/core/access/accframe.cxx create mode 100644 sw/source/core/access/accframe.hxx create mode 100644 sw/source/core/access/accframebase.cxx create mode 100644 sw/source/core/access/accframebase.hxx create mode 100644 sw/source/core/access/accfrmobj.cxx create mode 100644 sw/source/core/access/accfrmobj.hxx create mode 100644 sw/source/core/access/accfrmobjmap.cxx create mode 100644 sw/source/core/access/accfrmobjmap.hxx create mode 100644 sw/source/core/access/accfrmobjslist.cxx create mode 100644 sw/source/core/access/accfrmobjslist.hxx create mode 100644 sw/source/core/access/accgraphic.cxx create mode 100644 sw/source/core/access/accgraphic.hxx create mode 100644 sw/source/core/access/accheaderfooter.cxx create mode 100644 sw/source/core/access/accheaderfooter.hxx create mode 100644 sw/source/core/access/acchyperlink.cxx create mode 100644 sw/source/core/access/acchyperlink.hxx create mode 100644 sw/source/core/access/acchypertextdata.cxx create mode 100644 sw/source/core/access/acchypertextdata.hxx create mode 100644 sw/source/core/access/accmap.cxx create mode 100644 sw/source/core/access/accnotextframe.cxx create mode 100644 sw/source/core/access/accnotextframe.hxx create mode 100644 sw/source/core/access/accnotexthyperlink.cxx create mode 100644 sw/source/core/access/accnotexthyperlink.hxx create mode 100644 sw/source/core/access/accpage.cxx create mode 100644 sw/source/core/access/accpage.hxx create mode 100644 sw/source/core/access/accpara.cxx create mode 100644 sw/source/core/access/accpara.hxx create mode 100644 sw/source/core/access/accportions.cxx create mode 100644 sw/source/core/access/accportions.hxx create mode 100644 sw/source/core/access/accpreview.cxx create mode 100644 sw/source/core/access/accpreview.hxx create mode 100644 sw/source/core/access/accselectionhelper.cxx create mode 100644 sw/source/core/access/accselectionhelper.hxx create mode 100644 sw/source/core/access/acctable.cxx create mode 100644 sw/source/core/access/acctable.hxx create mode 100644 sw/source/core/access/acctextframe.cxx create mode 100644 sw/source/core/access/acctextframe.hxx create mode 100644 sw/source/core/access/parachangetrackinginfo.cxx create mode 100644 sw/source/core/access/parachangetrackinginfo.hxx create mode 100644 sw/source/core/access/textmarkuphelper.cxx create mode 100644 sw/source/core/access/textmarkuphelper.hxx create mode 100644 sw/source/core/attr/calbck.cxx create mode 100644 sw/source/core/attr/cellatr.cxx create mode 100644 sw/source/core/attr/fmtfollowtextflow.cxx create mode 100644 sw/source/core/attr/fmtwrapinfluenceonobjpos.cxx create mode 100644 sw/source/core/attr/format.cxx create mode 100644 sw/source/core/attr/hints.cxx create mode 100644 sw/source/core/attr/swatrset.cxx create mode 100644 sw/source/core/bastyp/SwSmartTagMgr.cxx create mode 100644 sw/source/core/bastyp/bparr.cxx create mode 100644 sw/source/core/bastyp/breakit.cxx create mode 100644 sw/source/core/bastyp/calc.cxx create mode 100644 sw/source/core/bastyp/checkit.cxx create mode 100644 sw/source/core/bastyp/index.cxx create mode 100644 sw/source/core/bastyp/init.cxx create mode 100644 sw/source/core/bastyp/proofreadingiterator.cxx create mode 100644 sw/source/core/bastyp/swcache.cxx create mode 100644 sw/source/core/bastyp/swrect.cxx create mode 100644 sw/source/core/bastyp/swregion.cxx create mode 100644 sw/source/core/bastyp/swtypes.cxx create mode 100644 sw/source/core/bastyp/tabcol.cxx create mode 100644 sw/source/core/crsr/BlockCursor.cxx create mode 100644 sw/source/core/crsr/BlockCursor.hxx create mode 100644 sw/source/core/crsr/DateFormFieldButton.cxx create mode 100644 sw/source/core/crsr/DropDownFormFieldButton.cxx create mode 100644 sw/source/core/crsr/FormFieldButton.cxx create mode 100644 sw/source/core/crsr/annotationmark.cxx create mode 100644 sw/source/core/crsr/bookmrk.cxx create mode 100644 sw/source/core/crsr/callnk.cxx create mode 100644 sw/source/core/crsr/callnk.hxx create mode 100644 sw/source/core/crsr/crbm.cxx create mode 100644 sw/source/core/crsr/crossrefbookmark.cxx create mode 100644 sw/source/core/crsr/crsrsh.cxx create mode 100644 sw/source/core/crsr/crstrvl.cxx create mode 100644 sw/source/core/crsr/crstrvl1.cxx create mode 100644 sw/source/core/crsr/findattr.cxx create mode 100644 sw/source/core/crsr/findcoll.cxx create mode 100644 sw/source/core/crsr/findfmt.cxx create mode 100644 sw/source/core/crsr/findtxt.cxx create mode 100644 sw/source/core/crsr/overlayrangesoutline.cxx create mode 100644 sw/source/core/crsr/overlayrangesoutline.hxx create mode 100644 sw/source/core/crsr/pam.cxx create mode 100644 sw/source/core/crsr/paminit.cxx create mode 100644 sw/source/core/crsr/swcrsr.cxx create mode 100644 sw/source/core/crsr/trvlcol.cxx create mode 100644 sw/source/core/crsr/trvlfnfl.cxx create mode 100644 sw/source/core/crsr/trvlreg.cxx create mode 100644 sw/source/core/crsr/trvltbl.cxx create mode 100644 sw/source/core/crsr/viscrs.cxx create mode 100644 sw/source/core/doc/CntntIdxStore.cxx create mode 100644 sw/source/core/doc/DocumentChartDataProviderManager.cxx create mode 100644 sw/source/core/doc/DocumentContentOperationsManager.cxx create mode 100644 sw/source/core/doc/DocumentDeviceManager.cxx create mode 100644 sw/source/core/doc/DocumentDrawModelManager.cxx create mode 100644 sw/source/core/doc/DocumentExternalDataManager.cxx create mode 100644 sw/source/core/doc/DocumentFieldsManager.cxx create mode 100644 sw/source/core/doc/DocumentLayoutManager.cxx create mode 100644 sw/source/core/doc/DocumentLinksAdministrationManager.cxx create mode 100644 sw/source/core/doc/DocumentListItemsManager.cxx create mode 100644 sw/source/core/doc/DocumentListsManager.cxx create mode 100644 sw/source/core/doc/DocumentOutlineNodesManager.cxx create mode 100644 sw/source/core/doc/DocumentRedlineManager.cxx create mode 100644 sw/source/core/doc/DocumentSettingManager.cxx create mode 100644 sw/source/core/doc/DocumentStateManager.cxx create mode 100644 sw/source/core/doc/DocumentStatisticsManager.cxx create mode 100644 sw/source/core/doc/DocumentStylePoolManager.cxx create mode 100644 sw/source/core/doc/DocumentTimerManager.cxx create mode 100644 sw/source/core/doc/SwDocIdle.cxx create mode 100644 sw/source/core/doc/SwStyleNameMapper.cxx create mode 100644 sw/source/core/doc/acmplwrd.cxx create mode 100644 sw/source/core/doc/dbgoutsw.cxx create mode 100644 sw/source/core/doc/doc.cxx create mode 100644 sw/source/core/doc/docbasic.cxx create mode 100644 sw/source/core/doc/docbm.cxx create mode 100644 sw/source/core/doc/docchart.cxx create mode 100644 sw/source/core/doc/doccomp.cxx create mode 100644 sw/source/core/doc/doccorr.cxx create mode 100644 sw/source/core/doc/docdesc.cxx create mode 100644 sw/source/core/doc/docdraw.cxx create mode 100644 sw/source/core/doc/docedt.cxx create mode 100644 sw/source/core/doc/docfld.cxx create mode 100644 sw/source/core/doc/docfly.cxx create mode 100644 sw/source/core/doc/docfmt.cxx create mode 100644 sw/source/core/doc/docftn.cxx create mode 100644 sw/source/core/doc/docglbl.cxx create mode 100644 sw/source/core/doc/docglos.cxx create mode 100644 sw/source/core/doc/doclay.cxx create mode 100644 sw/source/core/doc/docnew.cxx create mode 100644 sw/source/core/doc/docnum.cxx create mode 100644 sw/source/core/doc/docredln.cxx create mode 100644 sw/source/core/doc/docruby.cxx create mode 100644 sw/source/core/doc/docsort.cxx create mode 100644 sw/source/core/doc/docstat.cxx create mode 100644 sw/source/core/doc/doctxm.cxx create mode 100644 sw/source/core/doc/docxforms.cxx create mode 100644 sw/source/core/doc/extinput.cxx create mode 100644 sw/source/core/doc/fmtcol.cxx create mode 100644 sw/source/core/doc/ftnidx.cxx create mode 100644 sw/source/core/doc/gctable.cxx create mode 100644 sw/source/core/doc/htmltbl.cxx create mode 100644 sw/source/core/doc/lineinfo.cxx create mode 100644 sw/source/core/doc/list.cxx create mode 100644 sw/source/core/doc/notxtfrm.cxx create mode 100644 sw/source/core/doc/number.cxx create mode 100644 sw/source/core/doc/poolfmt.cxx create mode 100644 sw/source/core/doc/rdfhelper.cxx create mode 100644 sw/source/core/doc/sortopt.cxx create mode 100644 sw/source/core/doc/swserv.cxx create mode 100644 sw/source/core/doc/swstylemanager.cxx create mode 100644 sw/source/core/doc/swstylemanager.hxx create mode 100644 sw/source/core/doc/tblafmt.cxx create mode 100644 sw/source/core/doc/tblcpy.cxx create mode 100644 sw/source/core/doc/tblrwcl.cxx create mode 100644 sw/source/core/doc/textboxhelper.cxx create mode 100644 sw/source/core/doc/visiturl.cxx create mode 100644 sw/source/core/docnode/cancellablejob.cxx create mode 100644 sw/source/core/docnode/cancellablejob.hxx create mode 100644 sw/source/core/docnode/finalthreadmanager.cxx create mode 100644 sw/source/core/docnode/ndcopy.cxx create mode 100644 sw/source/core/docnode/ndnotxt.cxx create mode 100644 sw/source/core/docnode/ndnum.cxx create mode 100644 sw/source/core/docnode/ndsect.cxx create mode 100644 sw/source/core/docnode/ndsect.hxx create mode 100644 sw/source/core/docnode/ndtbl.cxx create mode 100644 sw/source/core/docnode/ndtbl1.cxx create mode 100644 sw/source/core/docnode/node.cxx create mode 100644 sw/source/core/docnode/node2lay.cxx create mode 100644 sw/source/core/docnode/nodes.cxx create mode 100644 sw/source/core/docnode/observablethread.cxx create mode 100644 sw/source/core/docnode/pausethreadstarting.cxx create mode 100644 sw/source/core/docnode/retrievedinputstreamdata.cxx create mode 100644 sw/source/core/docnode/retrieveinputstream.cxx create mode 100644 sw/source/core/docnode/retrieveinputstreamconsumer.cxx create mode 100644 sw/source/core/docnode/section.cxx create mode 100644 sw/source/core/docnode/swbaslnk.cxx create mode 100644 sw/source/core/docnode/swthreadjoiner.cxx create mode 100644 sw/source/core/docnode/swthreadmanager.cxx create mode 100644 sw/source/core/docnode/threadlistener.cxx create mode 100644 sw/source/core/docnode/threadmanager.cxx create mode 100644 sw/source/core/docnode/threadmanager.hxx create mode 100644 sw/source/core/draw/dcontact.cxx create mode 100644 sw/source/core/draw/dflyobj.cxx create mode 100644 sw/source/core/draw/dobjfac.cxx create mode 100644 sw/source/core/draw/dpage.cxx create mode 100644 sw/source/core/draw/drawdoc.cxx create mode 100644 sw/source/core/draw/dview.cxx create mode 100644 sw/source/core/edit/acorrect.cxx create mode 100644 sw/source/core/edit/autofmt.cxx create mode 100644 sw/source/core/edit/edatmisc.cxx create mode 100644 sw/source/core/edit/edattr.cxx create mode 100644 sw/source/core/edit/eddel.cxx create mode 100644 sw/source/core/edit/edfcol.cxx create mode 100644 sw/source/core/edit/edfld.cxx create mode 100644 sw/source/core/edit/edfldexp.cxx create mode 100644 sw/source/core/edit/edfmt.cxx create mode 100644 sw/source/core/edit/edglbldc.cxx create mode 100644 sw/source/core/edit/edglss.cxx create mode 100644 sw/source/core/edit/editsh.cxx create mode 100644 sw/source/core/edit/edlingu.cxx create mode 100644 sw/source/core/edit/ednumber.cxx create mode 100644 sw/source/core/edit/edredln.cxx create mode 100644 sw/source/core/edit/edsect.cxx create mode 100644 sw/source/core/edit/edtab.cxx create mode 100644 sw/source/core/edit/edtox.cxx create mode 100644 sw/source/core/edit/edundo.cxx create mode 100644 sw/source/core/edit/edws.cxx create mode 100644 sw/source/core/fields/authfld.cxx create mode 100644 sw/source/core/fields/cellfml.cxx create mode 100644 sw/source/core/fields/chpfld.cxx create mode 100644 sw/source/core/fields/dbfld.cxx create mode 100644 sw/source/core/fields/ddefld.cxx create mode 100644 sw/source/core/fields/ddetbl.cxx create mode 100644 sw/source/core/fields/docufld.cxx create mode 100644 sw/source/core/fields/expfld.cxx create mode 100644 sw/source/core/fields/fldbas.cxx create mode 100644 sw/source/core/fields/flddat.cxx create mode 100644 sw/source/core/fields/flddropdown.cxx create mode 100644 sw/source/core/fields/fldlst.cxx create mode 100644 sw/source/core/fields/macrofld.cxx create mode 100644 sw/source/core/fields/postithelper.cxx create mode 100644 sw/source/core/fields/reffld.cxx create mode 100644 sw/source/core/fields/scrptfld.cxx create mode 100644 sw/source/core/fields/tblcalc.cxx create mode 100644 sw/source/core/fields/textapi.cxx create mode 100644 sw/source/core/fields/usrfld.cxx create mode 100644 sw/source/core/frmedt/fecopy.cxx create mode 100644 sw/source/core/frmedt/fedesc.cxx create mode 100644 sw/source/core/frmedt/fefly1.cxx create mode 100644 sw/source/core/frmedt/feflyole.cxx create mode 100644 sw/source/core/frmedt/feshview.cxx create mode 100644 sw/source/core/frmedt/fetab.cxx create mode 100644 sw/source/core/frmedt/fews.cxx create mode 100644 sw/source/core/frmedt/tblsel.cxx create mode 100644 sw/source/core/graphic/grfatr.cxx create mode 100644 sw/source/core/graphic/ndgrf.cxx create mode 100644 sw/source/core/inc/AccessibilityCheck.hxx create mode 100644 sw/source/core/inc/AccessibilityIssue.hxx create mode 100644 sw/source/core/inc/DateFormFieldButton.hxx create mode 100644 sw/source/core/inc/DocumentChartDataProviderManager.hxx create mode 100644 sw/source/core/inc/DocumentContentOperationsManager.hxx create mode 100644 sw/source/core/inc/DocumentDeviceManager.hxx create mode 100644 sw/source/core/inc/DocumentDrawModelManager.hxx create mode 100644 sw/source/core/inc/DocumentExternalDataManager.hxx create mode 100644 sw/source/core/inc/DocumentFieldsManager.hxx create mode 100644 sw/source/core/inc/DocumentLayoutManager.hxx create mode 100644 sw/source/core/inc/DocumentLinksAdministrationManager.hxx create mode 100644 sw/source/core/inc/DocumentListItemsManager.hxx create mode 100644 sw/source/core/inc/DocumentListsManager.hxx create mode 100644 sw/source/core/inc/DocumentOutlineNodesManager.hxx create mode 100644 sw/source/core/inc/DocumentRedlineManager.hxx create mode 100644 sw/source/core/inc/DocumentSettingManager.hxx create mode 100644 sw/source/core/inc/DocumentStateManager.hxx create mode 100644 sw/source/core/inc/DocumentStatisticsManager.hxx create mode 100644 sw/source/core/inc/DocumentStylePoolManager.hxx create mode 100644 sw/source/core/inc/DocumentTimerManager.hxx create mode 100644 sw/source/core/inc/DropDownFormFieldButton.hxx create mode 100644 sw/source/core/inc/FormFieldButton.hxx create mode 100644 sw/source/core/inc/GetMetricVal.hxx create mode 100644 sw/source/core/inc/MarkManager.hxx create mode 100644 sw/source/core/inc/SwGrammarMarkUp.hxx create mode 100644 sw/source/core/inc/SwPortionHandler.hxx create mode 100644 sw/source/core/inc/SwUndoFmt.hxx create mode 100644 sw/source/core/inc/SwUndoPageDesc.hxx create mode 100644 sw/source/core/inc/SwUndoTOXChange.hxx create mode 100644 sw/source/core/inc/SwXMLBlockExport.hxx create mode 100644 sw/source/core/inc/SwXMLBlockImport.hxx create mode 100644 sw/source/core/inc/SwXMLTextBlocks.hxx create mode 100644 sw/source/core/inc/SwXTextDefaults.hxx create mode 100644 sw/source/core/inc/TextFrameIndex.hxx create mode 100644 sw/source/core/inc/UndoAttribute.hxx create mode 100644 sw/source/core/inc/UndoBookmark.hxx create mode 100644 sw/source/core/inc/UndoCore.hxx create mode 100644 sw/source/core/inc/UndoDelete.hxx create mode 100644 sw/source/core/inc/UndoDraw.hxx create mode 100644 sw/source/core/inc/UndoInsert.hxx create mode 100644 sw/source/core/inc/UndoManager.hxx create mode 100644 sw/source/core/inc/UndoNumbering.hxx create mode 100644 sw/source/core/inc/UndoOverwrite.hxx create mode 100644 sw/source/core/inc/UndoRedline.hxx create mode 100644 sw/source/core/inc/UndoSection.hxx create mode 100644 sw/source/core/inc/UndoSort.hxx create mode 100644 sw/source/core/inc/UndoSplitMove.hxx create mode 100644 sw/source/core/inc/UndoTable.hxx create mode 100644 sw/source/core/inc/acorrect.hxx create mode 100644 sw/source/core/inc/anchoredobjectposition.hxx create mode 100644 sw/source/core/inc/annotationmark.hxx create mode 100644 sw/source/core/inc/ascharanchoredobjectposition.hxx create mode 100644 sw/source/core/inc/attrhint.hxx create mode 100644 sw/source/core/inc/bodyfrm.hxx create mode 100644 sw/source/core/inc/bookmrk.hxx create mode 100644 sw/source/core/inc/cellfrm.hxx create mode 100644 sw/source/core/inc/cntfrm.hxx create mode 100644 sw/source/core/inc/colfrm.hxx create mode 100644 sw/source/core/inc/crossrefbookmark.hxx create mode 100644 sw/source/core/inc/dbg_lay.hxx create mode 100644 sw/source/core/inc/dflyobj.hxx create mode 100644 sw/source/core/inc/dialoghelp.hxx create mode 100644 sw/source/core/inc/docedt.hxx create mode 100644 sw/source/core/inc/docfld.hxx create mode 100644 sw/source/core/inc/docredln.hxx create mode 100644 sw/source/core/inc/docsort.hxx create mode 100644 sw/source/core/inc/doctxm.hxx create mode 100644 sw/source/core/inc/drawfont.hxx create mode 100644 sw/source/core/inc/dumpfilter.hxx create mode 100644 sw/source/core/inc/dview.hxx create mode 100644 sw/source/core/inc/environmentofanchoredobject.hxx create mode 100644 sw/source/core/inc/fefly.hxx create mode 100644 sw/source/core/inc/fieldhint.hxx create mode 100644 sw/source/core/inc/flowfrm.hxx create mode 100644 sw/source/core/inc/flyfrm.hxx create mode 100644 sw/source/core/inc/flyfrms.hxx create mode 100644 sw/source/core/inc/fntcache.hxx create mode 100644 sw/source/core/inc/fntcap.hxx create mode 100644 sw/source/core/inc/frame.hxx create mode 100644 sw/source/core/inc/frminf.hxx create mode 100644 sw/source/core/inc/frmtool.hxx create mode 100644 sw/source/core/inc/ftnboss.hxx create mode 100644 sw/source/core/inc/ftnfrm.hxx create mode 100644 sw/source/core/inc/hffrm.hxx create mode 100644 sw/source/core/inc/ifinishedthreadlistener.hxx create mode 100644 sw/source/core/inc/layact.hxx create mode 100644 sw/source/core/inc/laycache.hxx create mode 100644 sw/source/core/inc/layfrm.hxx create mode 100644 sw/source/core/inc/layouter.hxx create mode 100644 sw/source/core/inc/movedfwdfrmsbyobjpos.hxx create mode 100644 sw/source/core/inc/mvsave.hxx create mode 100644 sw/source/core/inc/node2lay.hxx create mode 100644 sw/source/core/inc/noteurl.hxx create mode 100644 sw/source/core/inc/notxtfrm.hxx create mode 100644 sw/source/core/inc/objectformatter.hxx create mode 100644 sw/source/core/inc/observablethread.hxx create mode 100644 sw/source/core/inc/pagedeschint.hxx create mode 100644 sw/source/core/inc/pagefrm.hxx create mode 100644 sw/source/core/inc/paintfrm.hxx create mode 100644 sw/source/core/inc/pamtyp.hxx create mode 100644 sw/source/core/inc/prevwpage.hxx create mode 100644 sw/source/core/inc/ptqueue.hxx create mode 100644 sw/source/core/inc/retrievedinputstreamdata.hxx create mode 100644 sw/source/core/inc/retrieveinputstream.hxx create mode 100644 sw/source/core/inc/retrieveinputstreamconsumer.hxx create mode 100644 sw/source/core/inc/rolbck.hxx create mode 100644 sw/source/core/inc/rootfrm.hxx create mode 100644 sw/source/core/inc/rowfrm.hxx create mode 100644 sw/source/core/inc/scriptinfo.hxx create mode 100644 sw/source/core/inc/sectfrm.hxx create mode 100644 sw/source/core/inc/sortedobjs.hxx create mode 100644 sw/source/core/inc/swblocks.hxx create mode 100644 sw/source/core/inc/swcache.hxx create mode 100644 sw/source/core/inc/swfntcch.hxx create mode 100644 sw/source/core/inc/swfont.hxx create mode 100644 sw/source/core/inc/swselectionlist.hxx create mode 100644 sw/source/core/inc/swthreadjoiner.hxx create mode 100644 sw/source/core/inc/swthreadmanager.hxx create mode 100644 sw/source/core/inc/tabfrm.hxx create mode 100644 sw/source/core/inc/tblrwcl.hxx create mode 100644 sw/source/core/inc/textapi.hxx create mode 100644 sw/source/core/inc/threadlistener.hxx create mode 100644 sw/source/core/inc/tocntntanchoredobjectposition.hxx create mode 100644 sw/source/core/inc/tolayoutanchoredobjectposition.hxx create mode 100644 sw/source/core/inc/txmsrt.hxx create mode 100644 sw/source/core/inc/txtfly.hxx create mode 100644 sw/source/core/inc/txtfrm.hxx create mode 100644 sw/source/core/inc/txttypes.hxx create mode 100644 sw/source/core/inc/undoflystrattr.hxx create mode 100644 sw/source/core/inc/unobookmark.hxx create mode 100644 sw/source/core/inc/unoevent.hxx create mode 100644 sw/source/core/inc/unofield.hxx create mode 100644 sw/source/core/inc/unoflatpara.hxx create mode 100644 sw/source/core/inc/unofldmid.h create mode 100644 sw/source/core/inc/unofootnote.hxx create mode 100644 sw/source/core/inc/unofreg.hxx create mode 100644 sw/source/core/inc/unoidx.hxx create mode 100644 sw/source/core/inc/unometa.hxx create mode 100644 sw/source/core/inc/unoparaframeenum.hxx create mode 100644 sw/source/core/inc/unoport.hxx create mode 100644 sw/source/core/inc/unorefmark.hxx create mode 100644 sw/source/core/inc/unosection.hxx create mode 100644 sw/source/core/inc/unotextmarkup.hxx create mode 100644 sw/source/core/inc/viewimp.hxx create mode 100644 sw/source/core/inc/visiturl.hxx create mode 100644 sw/source/core/inc/wrong.hxx create mode 100644 sw/source/core/layout/anchoreddrawobject.cxx create mode 100644 sw/source/core/layout/anchoredobject.cxx create mode 100644 sw/source/core/layout/atrfrm.cxx create mode 100644 sw/source/core/layout/calcmove.cxx create mode 100644 sw/source/core/layout/colfrm.cxx create mode 100644 sw/source/core/layout/dbg_lay.cxx create mode 100644 sw/source/core/layout/dumpfilter.cxx create mode 100644 sw/source/core/layout/findfrm.cxx create mode 100644 sw/source/core/layout/flowfrm.cxx create mode 100644 sw/source/core/layout/fly.cxx create mode 100644 sw/source/core/layout/flycnt.cxx create mode 100644 sw/source/core/layout/flyincnt.cxx create mode 100644 sw/source/core/layout/flylay.cxx create mode 100644 sw/source/core/layout/flypos.cxx create mode 100644 sw/source/core/layout/frmtool.cxx create mode 100644 sw/source/core/layout/ftnfrm.cxx create mode 100644 sw/source/core/layout/hffrm.cxx create mode 100644 sw/source/core/layout/layact.cxx create mode 100644 sw/source/core/layout/laycache.cxx create mode 100644 sw/source/core/layout/layhelp.hxx create mode 100644 sw/source/core/layout/layouter.cxx create mode 100644 sw/source/core/layout/legacyitem.cxx create mode 100644 sw/source/core/layout/movedfwdfrmsbyobjpos.cxx create mode 100644 sw/source/core/layout/newfrm.cxx create mode 100644 sw/source/core/layout/objectformatter.cxx create mode 100644 sw/source/core/layout/objectformatterlayfrm.cxx create mode 100644 sw/source/core/layout/objectformatterlayfrm.hxx create mode 100644 sw/source/core/layout/objectformattertxtfrm.cxx create mode 100644 sw/source/core/layout/objectformattertxtfrm.hxx create mode 100644 sw/source/core/layout/objstmpconsiderwrapinfl.cxx create mode 100644 sw/source/core/layout/objstmpconsiderwrapinfl.hxx create mode 100644 sw/source/core/layout/pagechg.cxx create mode 100644 sw/source/core/layout/pagedesc.cxx create mode 100644 sw/source/core/layout/paintfrm.cxx create mode 100644 sw/source/core/layout/sectfrm.cxx create mode 100644 sw/source/core/layout/softpagebreak.cxx create mode 100644 sw/source/core/layout/sortedobjs.cxx create mode 100644 sw/source/core/layout/ssfrm.cxx create mode 100644 sw/source/core/layout/swselectionlist.cxx create mode 100644 sw/source/core/layout/tabfrm.cxx create mode 100644 sw/source/core/layout/trvlfrm.cxx create mode 100644 sw/source/core/layout/unusedf.cxx create mode 100644 sw/source/core/layout/virtoutp.cxx create mode 100644 sw/source/core/layout/virtoutp.hxx create mode 100644 sw/source/core/layout/wsfrm.cxx create mode 100644 sw/source/core/objectpositioning/anchoredobjectposition.cxx create mode 100644 sw/source/core/objectpositioning/ascharanchoredobjectposition.cxx create mode 100644 sw/source/core/objectpositioning/environmentofanchoredobject.cxx create mode 100644 sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx create mode 100644 sw/source/core/objectpositioning/tolayoutanchoredobjectposition.cxx create mode 100644 sw/source/core/ole/ndole.cxx create mode 100644 sw/source/core/para/paratr.cxx create mode 100644 sw/source/core/sw3io/swacorr.cxx create mode 100644 sw/source/core/swg/BlockListTokens.txt create mode 100644 sw/source/core/swg/SwXMLBlockExport.cxx create mode 100644 sw/source/core/swg/SwXMLBlockImport.cxx create mode 100644 sw/source/core/swg/SwXMLSectionList.cxx create mode 100644 sw/source/core/swg/SwXMLTextBlocks.cxx create mode 100644 sw/source/core/swg/SwXMLTextBlocks1.cxx create mode 100644 sw/source/core/swg/TextBlockTokens.txt create mode 100644 sw/source/core/swg/swblocks.cxx create mode 100644 sw/source/core/table/swnewtable.cxx create mode 100644 sw/source/core/table/swtable.cxx create mode 100644 sw/source/core/text/EnhancedPDFExportHelper.cxx create mode 100644 sw/source/core/text/SwGrammarMarkUp.cxx create mode 100644 sw/source/core/text/atrhndl.hxx create mode 100644 sw/source/core/text/atrstck.cxx create mode 100644 sw/source/core/text/frmcrsr.cxx create mode 100644 sw/source/core/text/frmform.cxx create mode 100644 sw/source/core/text/frminf.cxx create mode 100644 sw/source/core/text/frmpaint.cxx create mode 100644 sw/source/core/text/guess.cxx create mode 100644 sw/source/core/text/guess.hxx create mode 100644 sw/source/core/text/inftxt.cxx create mode 100644 sw/source/core/text/inftxt.hxx create mode 100644 sw/source/core/text/itradj.cxx create mode 100644 sw/source/core/text/itratr.cxx create mode 100644 sw/source/core/text/itratr.hxx create mode 100644 sw/source/core/text/itrcrsr.cxx create mode 100644 sw/source/core/text/itrform2.cxx create mode 100644 sw/source/core/text/itrform2.hxx create mode 100644 sw/source/core/text/itrpaint.cxx create mode 100644 sw/source/core/text/itrpaint.hxx create mode 100644 sw/source/core/text/itrtxt.cxx create mode 100644 sw/source/core/text/itrtxt.hxx create mode 100644 sw/source/core/text/noteurl.cxx create mode 100644 sw/source/core/text/pordrop.hxx create mode 100644 sw/source/core/text/porexp.cxx create mode 100644 sw/source/core/text/porexp.hxx create mode 100644 sw/source/core/text/porfld.cxx create mode 100644 sw/source/core/text/porfld.hxx create mode 100644 sw/source/core/text/porfly.cxx create mode 100644 sw/source/core/text/porfly.hxx create mode 100644 sw/source/core/text/porftn.hxx create mode 100644 sw/source/core/text/porglue.cxx create mode 100644 sw/source/core/text/porglue.hxx create mode 100644 sw/source/core/text/porhyph.hxx create mode 100644 sw/source/core/text/porlay.cxx create mode 100644 sw/source/core/text/porlay.hxx create mode 100644 sw/source/core/text/porlin.cxx create mode 100644 sw/source/core/text/porlin.hxx create mode 100644 sw/source/core/text/pormulti.cxx create mode 100644 sw/source/core/text/pormulti.hxx create mode 100644 sw/source/core/text/porref.cxx create mode 100644 sw/source/core/text/porref.hxx create mode 100644 sw/source/core/text/porrst.cxx create mode 100644 sw/source/core/text/porrst.hxx create mode 100644 sw/source/core/text/portab.hxx create mode 100644 sw/source/core/text/portox.cxx create mode 100644 sw/source/core/text/portox.hxx create mode 100644 sw/source/core/text/portxt.cxx create mode 100644 sw/source/core/text/portxt.hxx create mode 100644 sw/source/core/text/possiz.hxx create mode 100644 sw/source/core/text/redlnitr.cxx create mode 100644 sw/source/core/text/redlnitr.hxx create mode 100644 sw/source/core/text/txtcache.cxx create mode 100644 sw/source/core/text/txtcache.hxx create mode 100644 sw/source/core/text/txtdrop.cxx create mode 100644 sw/source/core/text/txtfld.cxx create mode 100644 sw/source/core/text/txtfly.cxx create mode 100644 sw/source/core/text/txtfrm.cxx create mode 100644 sw/source/core/text/txtftn.cxx create mode 100644 sw/source/core/text/txthyph.cxx create mode 100644 sw/source/core/text/txtinit.cxx create mode 100644 sw/source/core/text/txtpaint.cxx create mode 100644 sw/source/core/text/txtpaint.hxx create mode 100644 sw/source/core/text/txttab.cxx create mode 100644 sw/source/core/text/widorp.cxx create mode 100644 sw/source/core/text/widorp.hxx create mode 100644 sw/source/core/text/wrong.cxx create mode 100644 sw/source/core/text/xmldump.cxx create mode 100644 sw/source/core/tox/ToxLinkProcessor.cxx create mode 100644 sw/source/core/tox/ToxTabStopTokenHandler.cxx create mode 100644 sw/source/core/tox/ToxTextGenerator.cxx create mode 100644 sw/source/core/tox/ToxWhitespaceStripper.cxx create mode 100644 sw/source/core/tox/tox.cxx create mode 100644 sw/source/core/tox/toxhlp.cxx create mode 100644 sw/source/core/tox/txmsrt.cxx create mode 100644 sw/source/core/txtnode/SwGrammarContact.cxx create mode 100644 sw/source/core/txtnode/atrfld.cxx create mode 100644 sw/source/core/txtnode/atrflyin.cxx create mode 100644 sw/source/core/txtnode/atrftn.cxx create mode 100644 sw/source/core/txtnode/atrref.cxx create mode 100644 sw/source/core/txtnode/atrtox.cxx create mode 100644 sw/source/core/txtnode/chrfmt.cxx create mode 100644 sw/source/core/txtnode/fmtatr2.cxx create mode 100644 sw/source/core/txtnode/fntcache.cxx create mode 100644 sw/source/core/txtnode/fntcap.cxx create mode 100644 sw/source/core/txtnode/modeltoviewhelper.cxx create mode 100644 sw/source/core/txtnode/ndhints.cxx create mode 100644 sw/source/core/txtnode/ndtxt.cxx create mode 100644 sw/source/core/txtnode/swfntcch.cxx create mode 100644 sw/source/core/txtnode/swfont.cxx create mode 100644 sw/source/core/txtnode/thints.cxx create mode 100644 sw/source/core/txtnode/txatbase.cxx create mode 100644 sw/source/core/txtnode/txatritr.cxx create mode 100644 sw/source/core/txtnode/txtatr2.cxx create mode 100644 sw/source/core/txtnode/txtedt.cxx create mode 100644 sw/source/core/undo/SwRewriter.cxx create mode 100644 sw/source/core/undo/SwUndoField.cxx create mode 100644 sw/source/core/undo/SwUndoFmt.cxx create mode 100644 sw/source/core/undo/SwUndoPageDesc.cxx create mode 100644 sw/source/core/undo/SwUndoTOXChange.cxx create mode 100644 sw/source/core/undo/docundo.cxx create mode 100644 sw/source/core/undo/rolbck.cxx create mode 100644 sw/source/core/undo/unattr.cxx create mode 100644 sw/source/core/undo/unbkmk.cxx create mode 100644 sw/source/core/undo/undel.cxx create mode 100644 sw/source/core/undo/undobj.cxx create mode 100644 sw/source/core/undo/undobj1.cxx create mode 100644 sw/source/core/undo/undoflystrattr.cxx create mode 100644 sw/source/core/undo/undraw.cxx create mode 100644 sw/source/core/undo/unfmco.cxx create mode 100644 sw/source/core/undo/unins.cxx create mode 100644 sw/source/core/undo/unmove.cxx create mode 100644 sw/source/core/undo/unnum.cxx create mode 100644 sw/source/core/undo/unoutl.cxx create mode 100644 sw/source/core/undo/unovwr.cxx create mode 100644 sw/source/core/undo/unredln.cxx create mode 100644 sw/source/core/undo/unsect.cxx create mode 100644 sw/source/core/undo/unsort.cxx create mode 100644 sw/source/core/undo/unspnd.cxx create mode 100644 sw/source/core/undo/untbl.cxx create mode 100644 sw/source/core/undo/untblk.cxx create mode 100644 sw/source/core/unocore/SwXTextDefaults.cxx create mode 100644 sw/source/core/unocore/TextCursorHelper.cxx create mode 100644 sw/source/core/unocore/XMLRangeHelper.cxx create mode 100644 sw/source/core/unocore/XMLRangeHelper.hxx create mode 100644 sw/source/core/unocore/swunohelper.cxx create mode 100644 sw/source/core/unocore/unobkm.cxx create mode 100644 sw/source/core/unocore/unochart.cxx create mode 100644 sw/source/core/unocore/unocoll.cxx create mode 100644 sw/source/core/unocore/unocrsr.cxx create mode 100644 sw/source/core/unocore/unocrsrhelper.cxx create mode 100644 sw/source/core/unocore/unodraw.cxx create mode 100644 sw/source/core/unocore/unoevent.cxx create mode 100644 sw/source/core/unocore/unofield.cxx create mode 100644 sw/source/core/unocore/unoflatpara.cxx create mode 100644 sw/source/core/unocore/unoframe.cxx create mode 100644 sw/source/core/unocore/unoftn.cxx create mode 100644 sw/source/core/unocore/unoidx.cxx create mode 100644 sw/source/core/unocore/unomap.cxx create mode 100644 sw/source/core/unocore/unomap1.cxx create mode 100644 sw/source/core/unocore/unomapproperties.hxx create mode 100644 sw/source/core/unocore/unoobj.cxx create mode 100644 sw/source/core/unocore/unoobj2.cxx create mode 100644 sw/source/core/unocore/unoparagraph.cxx create mode 100644 sw/source/core/unocore/unoport.cxx create mode 100644 sw/source/core/unocore/unoportenum.cxx create mode 100644 sw/source/core/unocore/unoredline.cxx create mode 100644 sw/source/core/unocore/unoredlines.cxx create mode 100644 sw/source/core/unocore/unorefmk.cxx create mode 100644 sw/source/core/unocore/unosect.cxx create mode 100644 sw/source/core/unocore/unosett.cxx create mode 100644 sw/source/core/unocore/unosrch.cxx create mode 100644 sw/source/core/unocore/unostyle.cxx create mode 100644 sw/source/core/unocore/unotbl.cxx create mode 100644 sw/source/core/unocore/unotext.cxx create mode 100644 sw/source/core/unocore/unotextmarkup.cxx create mode 100644 sw/source/core/view/dialoghelp.cxx create mode 100644 sw/source/core/view/pagepreviewlayout.cxx create mode 100644 sw/source/core/view/printdata.cxx create mode 100644 sw/source/core/view/vdraw.cxx create mode 100644 sw/source/core/view/viewimp.cxx create mode 100644 sw/source/core/view/viewpg.cxx create mode 100644 sw/source/core/view/viewsh.cxx create mode 100644 sw/source/core/view/vnew.cxx create mode 100644 sw/source/core/view/vprint.cxx create mode 100644 sw/source/core/view/vprint.hxx (limited to 'sw/source/core') diff --git a/sw/source/core/SwNumberTree/SwNodeNum.cxx b/sw/source/core/SwNumberTree/SwNodeNum.cxx new file mode 100644 index 000000000..f77b002a6 --- /dev/null +++ b/sw/source/core/SwNumberTree/SwNodeNum.cxx @@ -0,0 +1,369 @@ +/* -*- 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 + +SwNodeNum::SwNodeNum(SwTextNode* pTextNode, bool const isHiddenRedlines) + : mpTextNode( pTextNode ) + , mpNumRule( nullptr ) + , m_isHiddenRedlines(isHiddenRedlines) +{ +} + +SwNodeNum::SwNodeNum( SwNumRule* pNumRule ) + : mpTextNode( nullptr ) + , mpNumRule( pNumRule ) + , m_isHiddenRedlines(false) +{ +} + +SwNodeNum::~SwNodeNum() +{ +} + + +void SwNodeNum::ChangeNumRule( SwNumRule& rNumRule ) +{ + OSL_ENSURE( GetNumRule() && GetTextNode(), + " - missing list style and/or text node. Serious defect -> please inform OD." ); + if ( GetNumRule() && GetTextNode() ) + { + GetNumRule()->RemoveTextNode( *(GetTextNode()) ); + } + + mpNumRule = &rNumRule; + + if ( GetNumRule() && GetTextNode() ) + { + GetNumRule()->AddTextNode( *(GetTextNode()) ); + } +} + +SwPosition SwNodeNum::GetPosition() const +{ + OSL_ENSURE( GetTextNode(), + " - no text node set at instance" ); + return SwPosition(*mpTextNode); +} + +SwNumberTreeNode * SwNodeNum::Create() const +{ + SwNodeNum * pResult = new SwNodeNum( GetNumRule() ); + + return pResult; +} + +void SwNodeNum::PreAdd() +{ + OSL_ENSURE( GetTextNode(), + " - no text node set at instance" ); + if ( !GetNumRule() && GetTextNode() ) + { + mpNumRule = GetTextNode()->GetNumRule(); + } + OSL_ENSURE( GetNumRule(), + " - no list style set at instance" ); + if (!m_isHiddenRedlines && GetNumRule() && GetTextNode()) + { + GetNumRule()->AddTextNode( *(GetTextNode()) ); + } + + if (!m_isHiddenRedlines) + { + if ( GetTextNode() && + GetTextNode()->GetNodes().IsDocNodes() ) + { + GetTextNode()->getIDocumentListItems().addListItem( *this ); + } + } +} + +void SwNodeNum::PostRemove() +{ + OSL_ENSURE( GetTextNode(), + " - no text node set at instance" ); + OSL_ENSURE( GetNumRule(), + " - no list style set at instance" ); + + if (!m_isHiddenRedlines && GetTextNode()) + { + GetTextNode()->getIDocumentListItems().removeListItem( *this ); + } + + if ( GetNumRule() ) + { + if (!m_isHiddenRedlines && GetTextNode()) + { + GetNumRule()->RemoveTextNode( *(GetTextNode()) ); + } + mpNumRule = nullptr; + } +} + +bool SwNodeNum::IsNotifiable() const +{ + bool aResult = true; + + if ( GetTextNode() ) + aResult = GetTextNode()->IsNotifiable(); + + return aResult; +} + +bool SwNodeNum::IsNotificationEnabled() const +{ + bool aResult = true; + + if ( GetTextNode() ) + aResult = GetTextNode()->IsNotificationEnabled(); + + return aResult; +} + +bool SwNodeNum::IsContinuous() const +{ + bool aResult = false; + + // #i64311# + if ( GetNumRule() ) + { + aResult = mpNumRule->IsContinusNum(); + } + else if ( GetParent() ) + { + aResult = GetParent()->IsContinuous(); + } + else + { + OSL_FAIL( " - OD debug" ); + } + + return aResult; +} + +bool SwNodeNum::IsCounted() const +{ + bool aResult = false; + + if ( GetTextNode() ) + { + // #i59559# + // determines, if a text node is counted for numbering + aResult = GetTextNode()->IsCountedInList(); + } + else + aResult = SwNumberTreeNode::IsCounted(); + + return aResult; +} + +// #i64010# +bool SwNodeNum::HasCountedChildren() const +{ + return std::any_of(mChildren.begin(), mChildren.end(), + [](SwNumberTreeNode* pNode) { + SwNodeNum* pChild( dynamic_cast(pNode) ); + OSL_ENSURE( pChild, " - unexpected type of child" ); + return pChild && (pChild->IsCountedForNumbering() || pChild->HasCountedChildren()); + }); +} +// #i64010# +bool SwNodeNum::IsCountedForNumbering() const +{ + return IsCounted() && + ( IsPhantom() || // phantoms + !GetTextNode() || // root node + GetTextNode()->HasNumber() || // text node + GetTextNode()->HasBullet() ); // text node +} + +void SwNodeNum::NotifyNode() +{ + ValidateMe(); + + if (mpTextNode) + { + mpTextNode->NumRuleChgd(); + } +} + +bool SwNodeNum::LessThan(const SwNumberTreeNode & rNode) const +{ + bool bResult = false; + const SwNodeNum & rTmpNode = static_cast(rNode); + + if (mpTextNode == nullptr && rTmpNode.mpTextNode != nullptr) + bResult = true; + else if (mpTextNode != nullptr && rTmpNode.mpTextNode != nullptr) + { + // #i83479# - refactoring + // simplify comparison by comparing the indexes of the text nodes + bResult = ( mpTextNode->GetIndex() < rTmpNode.mpTextNode->GetIndex() ); + } + + return bResult; +} + +bool SwNodeNum::IsRestart() const +{ + bool bIsRestart = false; + + if ( GetTextNode() ) + { + bIsRestart = GetTextNode()->IsListRestart(); + } + + return bIsRestart; +} + +bool SwNodeNum::IsCountPhantoms() const +{ + bool bResult = true; + + // #i64311# + // phantoms aren't counted in consecutive numbering rules + if ( mpNumRule ) + bResult = !mpNumRule->IsContinusNum() && + mpNumRule->IsCountPhantoms(); + else + { + OSL_FAIL( "GetActualListStartValue(); + } + else + { + SwNumRule * pRule = GetNumRule(); + + if (pRule) + { + int nLevel = GetParent() ? GetLevelInListTree() : 0; + + if (nLevel >= 0 && nLevel < MAXLEVEL) + { + const SwNumFormat * pFormat = pRule->GetNumFormat( static_cast(nLevel)); + + if (pFormat) + aResult = pFormat->GetStart(); + } + } + } + + return aResult; +} + +void SwNodeNum::HandleNumberTreeRootNodeDelete( SwNodeNum& rNodeNum ) +{ + SwNodeNum* pRootNode = rNodeNum.GetParent() + ? dynamic_cast(rNodeNum.GetRoot()) + : &rNodeNum; + if ( !pRootNode ) + { + // no root node -> nothing do. + return; + } + + // unregister all number tree node entries, which correspond to a text node, + // about the deletion of the number tree root node. + UnregisterMeAndChildrenDueToRootDelete( *pRootNode ); +} + +void SwNodeNum::UnregisterMeAndChildrenDueToRootDelete( SwNodeNum& rNodeNum ) +{ + const bool bIsPhantom( rNodeNum.IsPhantom() ); + tSwNumberTreeChildren::size_type nAllowedChildCount( 0 ); + bool bDone( false ); + while ( !bDone && + rNodeNum.GetChildCount() > nAllowedChildCount ) + { + SwNodeNum* pChildNode( dynamic_cast((*rNodeNum.mChildren.begin())) ); + if ( !pChildNode ) + { + OSL_FAIL( " - unknown number tree node child" ); + ++nAllowedChildCount; + continue; + } + + // Unregistering the last child of a phantom will destroy the phantom. + // Thus will be destroyed and access on has to + // be suppressed. + if ( bIsPhantom && rNodeNum.GetChildCount() == 1 ) + { + bDone = true; + } + + UnregisterMeAndChildrenDueToRootDelete( *pChildNode ); + } + + if ( !bIsPhantom ) + { + SwTextNode* pTextNode( rNodeNum.GetTextNode() ); + if ( pTextNode ) + { + pTextNode->RemoveFromList(); + // --> clear all list attributes and the list style + std::set aResetAttrsArray; + aResetAttrsArray.insert( aResetAttrsArray.end(), RES_PARATR_LIST_ID ); + aResetAttrsArray.insert( aResetAttrsArray.end(), RES_PARATR_LIST_LEVEL ); + aResetAttrsArray.insert( aResetAttrsArray.end(), RES_PARATR_LIST_ISRESTART ); + aResetAttrsArray.insert( aResetAttrsArray.end(), RES_PARATR_LIST_RESTARTVALUE ); + aResetAttrsArray.insert( aResetAttrsArray.end(), RES_PARATR_LIST_ISCOUNTED ); + aResetAttrsArray.insert( aResetAttrsArray.end(), RES_PARATR_NUMRULE ); + SwPaM aPam( *pTextNode ); + pTextNode->GetDoc()->ResetAttrs( aPam, false, + aResetAttrsArray, + false ); + } + } +} + +// #i81002# +const SwNodeNum* SwNodeNum::GetPrecedingNodeNumOf( const SwTextNode& rTextNode ) const +{ + const SwNodeNum* pPrecedingNodeNum( nullptr ); + + // #i83479# + SwNodeNum aNodeNumForTextNode( const_cast(&rTextNode), false/*doesn't matter*/ ); + + pPrecedingNodeNum = dynamic_cast( + GetRoot() + ? GetRoot()->GetPrecedingNodeOf( aNodeNumForTextNode ) + : GetPrecedingNodeOf( aNodeNumForTextNode ) ); + + return pPrecedingNodeNum; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/SwNumberTree/SwNumberTree.cxx b/sw/source/core/SwNumberTree/SwNumberTree.cxx new file mode 100644 index 000000000..2b2ab634b --- /dev/null +++ b/sw/source/core/SwNumberTree/SwNumberTree.cxx @@ -0,0 +1,1182 @@ +/* -*- 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 + +using std::vector; +using std::find; + +SwNumberTreeNode::SwNumberTreeNode() + : mChildren(), + mpParent( nullptr ), + mnNumber( 0 ), + mbContinueingPreviousSubTree( false ), + mbPhantom( false ), + mItLastValid() +{ + mItLastValid = mChildren.end(); +} + +SwNumberTreeNode::~SwNumberTreeNode() +{ + if (GetChildCount() > 0) + { + if (HasOnlyPhantoms()) + { + delete *mChildren.begin(); + + mChildren.clear(); + mItLastValid = mChildren.end(); + } + else + { + OSL_FAIL("lost children!"); + } + } + + OSL_ENSURE( IsPhantom() || mpParent == nullptr, ": I'm not supposed to have a parent."); + + mpParent = reinterpret_cast(0xdeadbeef); + + OSL_ENSURE(mChildren.empty(), "children left!"); +} + +SwNumberTreeNode * SwNumberTreeNode::CreatePhantom() +{ + SwNumberTreeNode * pNew = nullptr; + + if (! mChildren.empty() && + (*mChildren.begin())->IsPhantom()) + { + OSL_FAIL("phantom already present"); + } + else + { + pNew = Create(); + pNew->mbPhantom = true; + pNew->mpParent = this; + + std::pair aInsert = + mChildren.insert(pNew); + + if (! aInsert.second) + { + OSL_FAIL("insert of phantom failed!"); + + delete pNew; + pNew = nullptr; + } + } + + return pNew; +} + +SwNumberTreeNode * SwNumberTreeNode::GetRoot() const +{ + SwNumberTreeNode * pResult = mpParent; + + if (pResult) + while (pResult->mpParent) + pResult = pResult->mpParent; + + return pResult; +} + +void SwNumberTreeNode::ClearObsoletePhantoms() +{ + tSwNumberTreeChildren::iterator aIt = mChildren.begin(); + + if (aIt != mChildren.end() && (*aIt)->IsPhantom()) + { + (*aIt)->ClearObsoletePhantoms(); + + if ((*aIt)->mChildren.empty()) + { + // #i60652# + // Because could destroy the element, which + // is referenced by , it's needed to adjust + // before erasing . + SetLastValid(mChildren.end()); + + delete *aIt; + mChildren.erase(aIt); + } + } +} + +void SwNumberTreeNode::ValidateHierarchical(const SwNumberTreeNode * pNode) const +{ + tSwNumberTreeChildren::const_iterator aValidateIt = + GetIterator(pNode); + + if (aValidateIt != mChildren.end()) + { + OSL_ENSURE((*aValidateIt)->mpParent == this, "wrong parent"); + + tSwNumberTreeChildren::const_iterator aIt = mItLastValid; + + // --> + // improvement: + // - Only one time checked for . + // - Less checks for each loop run. + // correction: + // - consider case that current node isn't counted and isn't the first + // child of its parent. In this case the number of last counted child + // of the previous node determines the start value for the following + // children loop, if all children have to be validated and the first + // one doesn't restart the counting. + SwNumberTree::tSwNumTreeNumber nTmpNumber( 0 ); + if (aIt != mChildren.end()) + nTmpNumber = (*aIt)->mnNumber; + else + { + aIt = mChildren.begin(); + (*aIt)->mbContinueingPreviousSubTree = false; + + // determine default start value + // consider the case that the first child isn't counted. + nTmpNumber = (*aIt)->GetStartValue(); + if ( !(*aIt)->IsCounted() && + ( !(*aIt)->HasCountedChildren() || (*aIt)->IsPhantom() ) ) + { + --nTmpNumber; + } + + // determine special start value for the case that first child + // doesn't restart the numbering and the parent node isn't counted + // and isn't the first child. + const bool bParentCounted( IsCounted() && + ( !IsPhantom() || + HasPhantomCountedParent() ) ); + if ( !(*aIt)->IsRestart() && + GetParent() && !bParentCounted ) + { + tSwNumberTreeChildren::const_iterator aParentChildIt = + GetParent()->GetIterator( this ); + while ( aParentChildIt != GetParent()->mChildren.begin() ) + { + --aParentChildIt; + SwNumberTreeNode* pPrevNode( *aParentChildIt ); + if ( pPrevNode->GetChildCount() > 0 ) + { + (*aIt)->mbContinueingPreviousSubTree = true; + nTmpNumber = (*(pPrevNode->mChildren.rbegin()))->GetNumber(); + if ( (*aIt)->IsCounted() && + ( !(*aIt)->IsPhantom() || + (*aIt)->HasPhantomCountedParent() ) ) + { + ++nTmpNumber; + } + break; + } + else if ( pPrevNode->IsCounted() ) + { + break; + } + else + { + // Previous node has no children and is not counted. + // Thus, next turn and check for the previous node. + } + } + } + + (*aIt)->mnNumber = nTmpNumber; + } + + while (aIt != aValidateIt) + { + ++aIt; + (*aIt)->mbContinueingPreviousSubTree = false; + + // --> only for counted nodes the number + // has to be adjusted, compared to the previous node. + // this condition is hold also for nodes, which restart the numbering. + if ( (*aIt)->IsCounted() ) + { + if ((*aIt)->IsRestart()) + nTmpNumber = (*aIt)->GetStartValue(); + else + ++nTmpNumber; + } + + (*aIt)->mnNumber = nTmpNumber; + } + + SetLastValid(aIt, true); + } +} + +void SwNumberTreeNode::ValidateContinuous(const SwNumberTreeNode * pNode) const +{ + tSwNumberTreeChildren::const_iterator aIt = mItLastValid; + + do + { + if (aIt == mChildren.end()) + { + aIt = mChildren.begin(); + } + else + ++aIt; + + if (aIt != mChildren.end()) + { + SwNumberTree::tSwNumTreeNumber nTmpNumber = 0; + SwNumberTreeNode * pPred = (*aIt)->GetPred(); + + // #i64311# + // correct consideration of phantoms + // correct consideration of restart at a number tree node + if ( pPred ) + { + if ( !(*aIt)->IsCounted() ) + // #i65284# + nTmpNumber = pPred->GetNumber( pPred->GetParent() != (*aIt)->GetParent() ); + else + { + if ( (*aIt)->IsRestart() ) + nTmpNumber = (*aIt)->GetStartValue(); + else + nTmpNumber = pPred->GetNumber( pPred->GetParent() != (*aIt)->GetParent() ) + 1; + } + } + else + { + if ( !(*aIt)->IsCounted() ) + nTmpNumber = GetStartValue() - 1; + else + { + if ( (*aIt)->IsRestart() ) + nTmpNumber = (*aIt)->GetStartValue(); + else + nTmpNumber = GetStartValue(); + } + } + + (*aIt)->mnNumber = nTmpNumber; + } + } + while (aIt != mChildren.end() && *aIt != pNode); + + // #i74748# - applied patch from garnier_romain + // number tree node has to be validated. + SetLastValid( aIt, true ); +} + +void SwNumberTreeNode::Validate(const SwNumberTreeNode * pNode) const +{ + if (! IsValid(pNode)) + { + if (IsContinuous()) + ValidateContinuous(pNode); + else + ValidateHierarchical(pNode); + } +} + +void SwNumberTreeNode::GetNumberVector_(SwNumberTree::tNumberVector & rVector, + bool bValidate) const +{ + if (mpParent) + { + mpParent->GetNumberVector_(rVector, bValidate); + rVector.push_back(GetNumber(bValidate)); + } +} + +SwNumberTreeNode * SwNumberTreeNode::GetFirstNonPhantomChild() +{ + if (IsPhantom()) + return (*mChildren.begin())->GetFirstNonPhantomChild(); + + return this; +} + +/** Moves all children of this node that are greater than a given node + to the destination node. +*/ +void SwNumberTreeNode::MoveGreaterChildren( SwNumberTreeNode& _rCompareNode, + SwNumberTreeNode& _rDestNode ) +{ + if ( mChildren.empty() ) + return; + + // determine first child, which has to move to <_rDestNode> + tSwNumberTreeChildren::iterator aItUpper( mChildren.end() ); + if ((*mChildren.begin())->IsPhantom() && + _rCompareNode.LessThan(*(*mChildren.begin())->GetFirstNonPhantomChild())) + { + aItUpper = mChildren.begin(); + } + else + { + aItUpper = mChildren.upper_bound(&_rCompareNode); + } + + // move children + if (aItUpper != mChildren.end()) + { + tSwNumberTreeChildren::iterator aIt; + for (aIt = aItUpper; aIt != mChildren.end(); ++aIt) + (*aIt)->mpParent = &_rDestNode; + + _rDestNode.mChildren.insert(aItUpper, mChildren.end()); + + // #i60652# + // Because could destroy + // the element, which is referenced by , it's needed to + // adjust before erasing . + SetLastValid( mChildren.end() ); + + mChildren.erase(aItUpper, mChildren.end()); + + // #i60652# + if ( !mChildren.empty() ) + { + SetLastValid( --(mChildren.end()) ); + } + } + +#ifdef DBG_UTIL + IsSane(false); + _rDestNode.IsSane(true); +#endif +} + +void SwNumberTreeNode::MoveChildren(SwNumberTreeNode * pDest) +{ + if (! mChildren.empty()) + { + tSwNumberTreeChildren::iterator aItBegin = mChildren.begin(); + SwNumberTreeNode * pMyFirst = *mChildren.begin(); + + // #i60652# + // Because could destroy the element, + // which is referenced by , it's needed to adjust + // before erasing . + SetLastValid(mChildren.end()); + + if (pMyFirst->IsPhantom()) + { + SwNumberTreeNode * pDestLast = nullptr; + + if (pDest->mChildren.empty()) + pDestLast = pDest->CreatePhantom(); + else + pDestLast = *pDest->mChildren.rbegin(); + + pMyFirst->MoveChildren(pDestLast); + + delete pMyFirst; + mChildren.erase(aItBegin); + + aItBegin = mChildren.begin(); + } + + for (auto& rpChild : mChildren) + rpChild->mpParent = pDest; + + pDest->mChildren.insert(mChildren.begin(), mChildren.end()); + mChildren.clear(); + // destroys all existing iterators. + // Thus, is also destroyed and reset becomes necessary + mItLastValid = mChildren.end(); + } + + OSL_ENSURE(mChildren.empty(), "MoveChildren failed!"); + +#ifdef DBG_UTIL + IsSane(false); + pDest->IsSane(false); +#endif +} + +void SwNumberTreeNode::AddChild( SwNumberTreeNode * pChild, + const int nDepth ) +{ + /* + Algorithm: + + Search first child A that is greater than pChild, + A may be the end of children. + If nDepth > 0 then + { + if A is first child then + create new phantom child B at beginning of child list + else + B is A + + Add child to B with depth nDepth - 1. + } + else + { + Insert pNode before A. + + if A has predecessor B then + remove children of B that are greater as A and insert them as + children of A. + } + +*/ + + if ( nDepth < 0 ) + { + OSL_FAIL( " - parameter out of valid range. Serious defect." ); + return; + } + + if ( pChild->GetParent() != nullptr || pChild->GetChildCount() > 0 ) + { + OSL_FAIL("only orphans allowed."); + return; + } + + if (nDepth > 0) + { + tSwNumberTreeChildren::iterator aInsertDeepIt = + mChildren.upper_bound(pChild); + + OSL_ENSURE(! (aInsertDeepIt != mChildren.end() && + (*aInsertDeepIt)->IsPhantom()), " unexpected phantom"); + + if (aInsertDeepIt == mChildren.begin()) + { + SwNumberTreeNode * pNew = CreatePhantom(); + + SetLastValid(mChildren.end()); + + if (pNew) + pNew->AddChild(pChild, nDepth - 1); + } + else + { + --aInsertDeepIt; + (*aInsertDeepIt)->AddChild(pChild, nDepth - 1); + } + + } + else + { + pChild->PreAdd(); + std::pair aResult = + mChildren.insert(pChild); + + if (aResult.second) + { + pChild->mpParent = this; + bool bNotification = pChild->IsNotificationEnabled(); + tSwNumberTreeChildren::iterator aInsertedIt = aResult.first; + + if (aInsertedIt != mChildren.begin()) + { + tSwNumberTreeChildren::iterator aPredIt = aInsertedIt; + --aPredIt; + + // --> + // Move greater children of previous node to new child. + // This has to be done recursively on the children levels. + // Initialize loop variables and + // for loop on children levels. + SwNumberTreeNode* pPrevChildNode( *aPredIt ); + SwNumberTreeNode* pDestNode( pChild ); + while ( pDestNode && pPrevChildNode && + pPrevChildNode->GetChildCount() > 0 ) + { + // move children + pPrevChildNode->MoveGreaterChildren( *pChild, *pDestNode ); + + // prepare next loop: + // - search of last child of GetChildCount() > 0 ) + { + tSwNumberTreeChildren::reverse_iterator aIt = + pPrevChildNode->mChildren.rbegin(); + pPrevChildNode = *aIt; + // determine new destination node + if ( pDestNode->GetChildCount() > 0 ) + { + pDestNode = *(pDestNode->mChildren.begin()); + if ( !pDestNode->IsPhantom() ) + { + pDestNode = pDestNode->mpParent->CreatePhantom(); + } + } + else + { + pDestNode = pDestNode->CreatePhantom(); + } + } + else + { + // ready -> break loop. + break; + } + } + // assure that unnessary created phantoms at are deleted. + pChild->ClearObsoletePhantoms(); + + if ((*aPredIt)->IsValid()) + SetLastValid(aPredIt); + } + else + SetLastValid(mChildren.end()); + + ClearObsoletePhantoms(); + + if( bNotification ) + { + // invalidation of not counted parent + // and notification of its siblings. + if ( !IsCounted() ) + { + InvalidateMe(); + NotifyInvalidSiblings(); + } + NotifyInvalidChildren(); + } + } + } + +#ifdef DBG_UTIL + IsSane(false); +#endif +} + +void SwNumberTreeNode::RemoveChild(SwNumberTreeNode * pChild) +{ + /* + Algorithm: + + if pChild has predecessor A then + B is A + else + create phantom child B at beginning of child list + + Move children of pChild to B. + */ + + if (pChild->IsPhantom()) + { + OSL_FAIL("not applicable to phantoms!"); + + return; + } + + tSwNumberTreeChildren::const_iterator aRemoveIt = GetIterator(pChild); + + if (aRemoveIt != mChildren.end()) + { + SwNumberTreeNode * pRemove = *aRemoveIt; + + pRemove->mpParent = nullptr; + + tSwNumberTreeChildren::const_iterator aItPred = mChildren.end(); + + if (aRemoveIt == mChildren.begin()) + { + if (! pRemove->mChildren.empty()) + { + CreatePhantom(); + + aItPred = mChildren.begin(); + } + } + else + { + aItPred = aRemoveIt; + --aItPred; + } + + if (! pRemove->mChildren.empty()) + { + pRemove->MoveChildren(*aItPred); + (*aItPred)->InvalidateTree(); + (*aItPred)->NotifyInvalidChildren(); + } + + // #i60652# + // Because could destroy the element, + // which is referenced by , it's needed to adjust + // before erasing . + if (aItPred != mChildren.end() && (*aItPred)->IsPhantom()) + SetLastValid(mChildren.end()); + else + SetLastValid(aItPred); + + mChildren.erase(aRemoveIt); + + NotifyInvalidChildren(); + } + else + { + OSL_FAIL("RemoveChild: failed!"); + } + + pChild->PostRemove(); +} + +void SwNumberTreeNode::RemoveMe() +{ + if (mpParent) + { + SwNumberTreeNode * pSavedParent = mpParent; + + pSavedParent->RemoveChild(this); + + while (pSavedParent && pSavedParent->IsPhantom() && + pSavedParent->HasOnlyPhantoms()) + pSavedParent = pSavedParent->GetParent(); + + if (pSavedParent) + pSavedParent->ClearObsoletePhantoms(); + +#ifdef DBG_UTIL + IsSane(false); +#endif + } +} + +bool SwNumberTreeNode::IsValid() const +{ + return mpParent && mpParent->IsValid(this); +} + +SwNumberTree::tSwNumTreeNumber SwNumberTreeNode::GetNumber(bool bValidate) + const +{ + if (bValidate && mpParent) + mpParent->Validate(this); + + return mnNumber; +} + + +SwNumberTree::tNumberVector SwNumberTreeNode::GetNumberVector() const +{ + vector aResult; + + GetNumberVector_(aResult); + + return aResult; +} + +bool SwNumberTreeNode::IsValid(const SwNumberTreeNode * pChild) const +{ + bool bResult = false; + + if (mItLastValid != mChildren.end()) + { + if (pChild && pChild->mpParent == this) + { + bResult = ! (*mItLastValid)->LessThan(*pChild); + } + } + + return bResult; +} + + +bool SwNumberTreeNode::HasOnlyPhantoms() const +{ + bool bResult = false; + + if (GetChildCount() == 1) + { + tSwNumberTreeChildren::const_iterator aIt = mChildren.begin(); + + bResult = (*aIt)->IsPhantom() && (*aIt)->HasOnlyPhantoms(); + } + else if (GetChildCount() == 0) + bResult = true; + + return bResult; +} + +bool SwNumberTreeNode::IsCounted() const +{ + return !IsPhantom() || + ( IsCountPhantoms() && HasCountedChildren() ); +} + +bool SwNumberTreeNode::HasPhantomCountedParent() const +{ + bool bRet( false ); + + OSL_ENSURE( IsPhantom(), + " - wrong usage of method - it's only for phantoms" ); + if ( IsPhantom() && mpParent ) + { + if ( mpParent == GetRoot() ) + { + bRet = true; + } + else if ( !mpParent->IsPhantom() ) + { + bRet = mpParent->IsCounted(); + } + else + { + bRet = mpParent->IsCounted() && mpParent->HasPhantomCountedParent(); + } + } + + return bRet; +} + +bool SwNumberTreeNode::IsFirst(const SwNumberTreeNode * pNode) const +{ + tSwNumberTreeChildren::const_iterator aIt = mChildren.begin(); + + if ((*aIt)->IsPhantom()) + ++aIt; + + return *aIt == pNode; +} + +bool SwNumberTreeNode::IsFirst() const +{ + bool bResult = true; + + if (GetParent()) + { + if (GetParent()->IsFirst(this)) + { + SwNumberTreeNode * pNode = GetParent(); + + while (pNode) + { + if (!pNode->IsPhantom() && pNode->GetParent()) + { + bResult = false; + break; + } + + pNode = pNode->GetParent(); + } + + // If node isn't the first child, it is the second child and the + // first child is a phantom. In this case check, if the first phantom + // child have only phantom children + if ( bResult && + this != *(GetParent()->mChildren.begin()) && + !(*(GetParent()->mChildren.begin()))->HasOnlyPhantoms() ) + { + bResult = false; + } + } + else + bResult = false; + } + + return bResult; +} + +void SwNumberTreeNode::SetLevelInListTree( const int nLevel ) +{ + if ( nLevel < 0 ) + { + OSL_FAIL( " - parameter out of valid range. Serious defect." ); + return; + } + + OSL_ENSURE( GetParent(), + " - can only be called for number tree nodes in a list tree" ); + if ( GetParent() ) + { + if ( nLevel != GetLevelInListTree() ) + { + SwNumberTreeNode* pRootTreeNode = GetRoot(); + OSL_ENSURE( pRootTreeNode, + " - no root tree node found. Serious defect." ); + + RemoveMe(); + pRootTreeNode->AddChild( this, nLevel ); + } + } +} + +int SwNumberTreeNode::GetLevelInListTree() const +{ + if (mpParent) + return mpParent->GetLevelInListTree() + 1; + + return -1; +} + +SwNumberTreeNode::tSwNumberTreeChildren::size_type +SwNumberTreeNode::GetChildCount() const +{ + return mChildren.size(); +} + +#ifdef DBG_UTIL +void SwNumberTreeNode::IsSane(bool bRecursive) const +{ + vector aParents; + + return IsSane(bRecursive, aParents); +} + +void SwNumberTreeNode::IsSane(bool bRecursive, + vector rParents) + const +{ + assert(find(rParents.begin(), rParents.end(), this) == rParents.end()); + + assert(rParents.empty() || rParents.back() == mpParent); + + rParents.push_back(this); + + bool bFirst = true; + for (const auto& rpChild : mChildren) + { + if (rpChild) + { + if (rpChild->IsPhantom()) + { + SAL_WARN_IF(rpChild->HasOnlyPhantoms(), "sw.core", + "HasOnlyPhantoms: is this an error?"); + + assert(bFirst && "found phantom not at first position."); + } + + assert(rpChild->mpParent == this); + + if (mpParent) + { + assert(rpChild->IsPhantom() || !rpChild->LessThan(*this)); + } + } + else + { + assert(!"found child that is NULL"); + } + + if (bRecursive) + { + rpChild->IsSane(bRecursive, rParents); + } + + bFirst = false; + } + + rParents.pop_back(); +} +#endif // DBG_UTIL + +SwNumberTreeNode::tSwNumberTreeChildren::const_iterator +SwNumberTreeNode::GetIterator(const SwNumberTreeNode * pChild) const +{ + tSwNumberTreeChildren::const_iterator aItResult = + mChildren.find(const_cast(pChild)); + + OSL_ENSURE( aItResult != mChildren.end(), + "something went wrong getting the iterator for a child"); + + return aItResult; +} + +bool SwNumberTreeNodeLessThan(const SwNumberTreeNode * pA, + const SwNumberTreeNode * pB) +{ + bool bResult = false; + + if (pA == nullptr && pB != nullptr) + bResult = true; + else if (pA != nullptr && pB != nullptr) + bResult = pA->LessThan(*pB); + + return bResult; +} + +SwNumberTreeNode * SwNumberTreeNode::GetLastDescendant() const +{ + SwNumberTreeNode * pResult = nullptr; + tSwNumberTreeChildren::const_reverse_iterator aIt = mChildren.rbegin(); + + if (aIt != mChildren.rend()) + { + pResult = (*aIt)->GetLastDescendant(); + + if (! pResult) + pResult = *aIt; + } + + return pResult; +} + +bool SwNumberTreeNode::LessThan(const SwNumberTreeNode & rTreeNode) const +{ + return this < &rTreeNode; +} + +SwNumberTreeNode * SwNumberTreeNode::GetPred(bool bSibling) const +{ + SwNumberTreeNode * pResult = nullptr; + + if (mpParent) + { + tSwNumberTreeChildren::const_iterator aIt = + mpParent->GetIterator(this); + + if ( aIt == mpParent->mChildren.begin() ) + { + // #i64311# + // root node is no valid predecessor + pResult = mpParent->GetParent() ? mpParent : nullptr; + } + else + { + --aIt; + + if ( !bSibling ) + pResult = (*aIt)->GetLastDescendant(); + else + pResult = (*aIt); + + if (! pResult) + pResult = (*aIt); + } + } + + return pResult; +} + +void SwNumberTreeNode::SetLastValid + ( const SwNumberTreeNode::tSwNumberTreeChildren::const_iterator& aItValid, + bool bValidating ) const +{ + OSL_ENSURE( (aItValid == mChildren.end() || GetIterator(*aItValid) != mChildren.end()), + "last-valid iterator"); + + if ( + bValidating || + aItValid == mChildren.end() || + (mItLastValid != mChildren.end() && + (*aItValid)->LessThan(**mItLastValid)) + ) + { + mItLastValid = aItValid; + // invalidation of children of next not counted is needed + if ( GetParent() ) + { + tSwNumberTreeChildren::const_iterator aParentChildIt = + GetParent()->GetIterator( this ); + ++aParentChildIt; + if ( aParentChildIt != GetParent()->mChildren.end() ) + { + SwNumberTreeNode* pNextNode( *aParentChildIt ); + if ( !pNextNode->IsCounted() ) + { + pNextNode->InvalidateChildren(); + } + } + } + } + + { + if (IsContinuous()) + { + tSwNumberTreeChildren::const_iterator aIt = mItLastValid; + + if (aIt != mChildren.end()) + ++aIt; + else + aIt = mChildren.begin(); + + while (aIt != mChildren.end()) + { + (*aIt)->InvalidateTree(); + + ++aIt; + } + + if (mpParent) + { + mpParent->SetLastValid(mpParent->GetIterator(this), bValidating); + } + } + } +} + +void SwNumberTreeNode::InvalidateTree() const +{ + // do not call SetInvalid, would cause loop !!! + mItLastValid = mChildren.end(); + + for (const auto& rpChild : mChildren) + rpChild->InvalidateTree(); +} + +void SwNumberTreeNode::Invalidate(SwNumberTreeNode const * pChild) +{ + if (pChild->IsValid()) + { + tSwNumberTreeChildren::const_iterator aIt = GetIterator(pChild); + + if (aIt != mChildren.begin()) + --aIt; + else + aIt = mChildren.end(); + + SetLastValid(aIt); + + } +} + +void SwNumberTreeNode::InvalidateMe() +{ + if (mpParent) + mpParent->Invalidate(this); +} + +void SwNumberTreeNode::ValidateMe() +{ + if (mpParent) + mpParent->Validate(this); +} + +void SwNumberTreeNode::Notify() +{ + if (IsNotifiable()) + { + if (! IsPhantom()) + NotifyNode(); + + for (auto& rpChild : mChildren) + rpChild->Notify(); + } +} + +void SwNumberTreeNode::NotifyInvalidChildren() +{ + if (IsNotifiable()) + { + tSwNumberTreeChildren::const_iterator aIt = mItLastValid; + + if (aIt == mChildren.end()) + aIt = mChildren.begin(); + else + ++aIt; + + while (aIt != mChildren.end()) + { + (*aIt)->Notify(); + + ++aIt; + } + // notification of next not counted node is also needed. + if ( GetParent() ) + { + tSwNumberTreeChildren::const_iterator aParentChildIt = + GetParent()->GetIterator( this ); + ++aParentChildIt; + if ( aParentChildIt != GetParent()->mChildren.end() ) + { + SwNumberTreeNode* pNextNode( *aParentChildIt ); + if ( !pNextNode->IsCounted() ) + { + pNextNode->NotifyInvalidChildren(); + } + } + } + + } + + if (IsContinuous() && mpParent) + mpParent->NotifyInvalidChildren(); +} + +void SwNumberTreeNode::NotifyInvalidSiblings() +{ + if (mpParent != nullptr) + mpParent->NotifyInvalidChildren(); +} + +// #i81002# +const SwNumberTreeNode* SwNumberTreeNode::GetPrecedingNodeOf( + const SwNumberTreeNode& rNode ) const +{ + const SwNumberTreeNode* pPrecedingNode( nullptr ); + + if ( GetChildCount() > 0 ) + { + tSwNumberTreeChildren::const_iterator aUpperBoundIt = + mChildren.upper_bound( const_cast(&rNode) ); + if ( aUpperBoundIt != mChildren.begin() ) + { + --aUpperBoundIt; + pPrecedingNode = (*aUpperBoundIt)->GetPrecedingNodeOf( rNode ); + } + } + + if ( pPrecedingNode == nullptr && GetRoot() ) + { + // node has no children or the given node precedes all its children + // and the node isn't the root node. + // Thus, compare the given node with the node in order to check, + // if the node precedes the given node. + if ( !(rNode.LessThan( *this )) ) + { + pPrecedingNode = this; + } + } + + return pPrecedingNode; +} + +void SwNumberTreeNode::NotifyNodesOnListLevel( const int nListLevel ) +{ + if ( nListLevel < 0 ) + { + OSL_FAIL( " - invalid list level provided" ); + return; + } + + SwNumberTreeNode* pRootNode = GetParent() ? GetRoot() : this; + + pRootNode->NotifyChildrenOnDepth( nListLevel ); +} + +void SwNumberTreeNode::NotifyChildrenOnDepth( const int nDepth ) +{ + OSL_ENSURE( nDepth >= 0, + " - misusage" ); + + for ( const auto& rpChild : mChildren ) + { + if ( nDepth == 0 ) + { + rpChild->NotifyNode(); + } + else + { + rpChild->NotifyChildrenOnDepth( nDepth - 1 ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/AccessibilityCheck.cxx b/sw/source/core/access/AccessibilityCheck.cxx new file mode 100644 index 000000000..b59596da9 --- /dev/null +++ b/sw/source/core/access/AccessibilityCheck.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 +#include +#include +#include +#include + +namespace sw +{ +namespace +{ +std::shared_ptr +lclAddIssue(sfx::AccessibilityIssueCollection& rIssueCollection, OUString const& rText, + sfx::AccessibilityIssueID eIssue = sfx::AccessibilityIssueID::UNSPECIFIED) +{ + auto pIssue = std::make_shared(eIssue); + pIssue->m_aIssueText = rText; + rIssueCollection.getIssues().push_back(pIssue); + return pIssue; +} + +class BaseCheck +{ +protected: + sfx::AccessibilityIssueCollection& m_rIssueCollection; + +public: + BaseCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : m_rIssueCollection(rIssueCollection) + { + } + virtual ~BaseCheck() {} +}; + +class NodeCheck : public BaseCheck +{ +public: + NodeCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : BaseCheck(rIssueCollection) + { + } + + virtual void check(SwNode* pCurrent) = 0; +}; + +// Check NoTextNodes: Graphic, OLE for alt (title) text +class NoTextNodeAltTextCheck : public NodeCheck +{ + void checkNoTextNode(SwNoTextNode* pNoTextNode) + { + if (!pNoTextNode) + return; + + OUString sAlternative = pNoTextNode->GetTitle(); + if (sAlternative.isEmpty()) + { + OUString sName = pNoTextNode->GetFlyFormat()->GetName(); + + OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName); + + if (pNoTextNode->IsOLENode()) + { + auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::NO_ALT_OLE); + pIssue->setDoc(pNoTextNode->GetDoc()); + pIssue->setIssueObject(IssueObject::OLE); + pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName()); + } + else if (pNoTextNode->IsGrfNode()) + { + auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::NO_ALT_GRAPHIC); + pIssue->setDoc(pNoTextNode->GetDoc()); + pIssue->setIssueObject(IssueObject::GRAPHIC); + pIssue->setObjectID(pNoTextNode->GetFlyFormat()->GetName()); + } + } + } + +public: + NoTextNodeAltTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->GetNodeType() & SwNodeType::NoTextMask) + { + SwNoTextNode* pNoTextNode = pCurrent->GetNoTextNode(); + if (pNoTextNode) + checkNoTextNode(pNoTextNode); + } + } +}; + +// Check Table node if the table is merged and split. +class TableNodeMergeSplitCheck : public NodeCheck +{ +private: + void addTableIssue(SwTable const& rTable, SwDoc* pDoc) + { + const SwTableFormat* pFormat = rTable.GetFrameFormat(); + OUString sName = pFormat->GetName(); + OUString sIssueText = SwResId(STR_TABLE_MERGE_SPLIT).replaceAll("%OBJECT_NAME%", sName); + auto pIssue = lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::TABLE_MERGE_SPLIT); + pIssue->setDoc(pDoc); + pIssue->setIssueObject(IssueObject::TABLE); + pIssue->setObjectID(sName); + } + + void checkTableNode(SwTableNode* pTableNode) + { + if (!pTableNode) + return; + + SwTable const& rTable = pTableNode->GetTable(); + SwDoc* pDoc = pTableNode->GetDoc(); + if (rTable.IsTableComplex()) + { + addTableIssue(rTable, pDoc); + } + else + { + if (rTable.GetTabLines().size() > 1) + { + int i = 0; + size_t nFirstLineSize = 0; + bool bAllColumnsSameSize = true; + bool bCellSpansOverMoreRows = false; + + for (SwTableLine const* pTableLine : rTable.GetTabLines()) + { + if (i == 0) + { + nFirstLineSize = pTableLine->GetTabBoxes().size(); + } + else + { + size_t nLineSize = pTableLine->GetTabBoxes().size(); + if (nFirstLineSize != nLineSize) + { + bAllColumnsSameSize = false; + } + } + i++; + + // Check for row span in each table box (cell) + for (SwTableBox const* pBox : pTableLine->GetTabBoxes()) + { + if (pBox->getRowSpan() > 1) + bCellSpansOverMoreRows = true; + } + } + if (!bAllColumnsSameSize || bCellSpansOverMoreRows) + { + addTableIssue(rTable, pDoc); + } + } + } + } + +public: + TableNodeMergeSplitCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->GetNodeType() & SwNodeType::Table) + { + SwTableNode* pTableNode = pCurrent->GetTableNode(); + if (pTableNode) + checkTableNode(pTableNode); + } + } +}; + +class NumberingCheck : public NodeCheck +{ +private: + SwTextNode* m_pPreviousTextNode; + + const std::vector> m_aNumberingCombinations{ + { "1.", "2." }, { "(1)", "(2)" }, { "1)", "2)" }, { "a.", "b." }, { "(a)", "(b)" }, + { "a)", "b)" }, { "A.", "B." }, { "(A)", "(B)" }, { "A)", "B)" } + }; + +public: + NumberingCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + , m_pPreviousTextNode(nullptr) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->IsTextNode()) + { + if (m_pPreviousTextNode) + { + for (auto& rPair : m_aNumberingCombinations) + { + if (pCurrent->GetTextNode()->GetText().startsWith(rPair.second) + && m_pPreviousTextNode->GetText().startsWith(rPair.first)) + { + OUString sNumbering = rPair.first + " " + rPair.second + "..."; + OUString sIssueText + = SwResId(STR_FAKE_NUMBERING).replaceAll("%NUMBERING%", sNumbering); + lclAddIssue(m_rIssueCollection, sIssueText); + } + } + } + m_pPreviousTextNode = pCurrent->GetTextNode(); + } + } +}; + +class HyperlinkCheck : public NodeCheck +{ +private: + void checkTextRange(uno::Reference const& xTextRange) + { + uno::Reference xProperties(xTextRange, uno::UNO_QUERY); + if (xProperties->getPropertySetInfo()->hasPropertyByName("HyperLinkURL")) + { + OUString sHyperlink; + xProperties->getPropertyValue("HyperLinkURL") >>= sHyperlink; + if (!sHyperlink.isEmpty()) + { + OUString sText = xTextRange->getString(); + if (INetURLObject(sText) == INetURLObject(sHyperlink)) + { + OUString sIssueText + = SwResId(STR_HYPERLINK_TEXT_IS_LINK).replaceFirst("%LINK%", sHyperlink); + lclAddIssue(m_rIssueCollection, sIssueText); + } + } + } + } + +public: + HyperlinkCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->IsTextNode()) + { + SwTextNode* pTextNode = pCurrent->GetTextNode(); + uno::Reference xParagraph + = SwXParagraph::CreateXParagraph(*pTextNode->GetDoc(), pTextNode); + if (xParagraph.is()) + { + uno::Reference xRunEnumAccess(xParagraph, + uno::UNO_QUERY); + uno::Reference xRunEnum + = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + if (xRun.is()) + { + checkTextRange(xRun); + } + } + } + } + } +}; + +// Based on https://www.w3.org/TR/WCAG21/#dfn-relative-luminance +double calculateRelativeLuminance(Color const& rColor) +{ + // Convert to BColor which has R, G, B colors components + // represented by a floating point number from [0.0, 1.0] + const basegfx::BColor aBColor = rColor.getBColor(); + + double r = aBColor.getRed(); + double g = aBColor.getGreen(); + double b = aBColor.getBlue(); + + // Calculate the values according to the described algorithm + r = (r <= 0.03928) ? r / 12.92 : std::pow((r + 0.055) / 1.055, 2.4); + g = (g <= 0.03928) ? g / 12.92 : std::pow((g + 0.055) / 1.055, 2.4); + b = (b <= 0.03928) ? b / 12.92 : std::pow((b + 0.055) / 1.055, 2.4); + + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +// TODO move to common color tools (BColorTools maybe) +// Based on https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio +double calculateContrastRatio(Color const& rColor1, Color const& rColor2) +{ + const double fLuminance1 = calculateRelativeLuminance(rColor1); + const double fLuminance2 = calculateRelativeLuminance(rColor2); + const std::pair aMinMax = std::minmax(fLuminance1, fLuminance2); + + // (L1 + 0.05) / (L2 + 0.05) + // L1 is the lighter color (greater luminance value) + // L2 is the darker color (smaller luminance value) + return (aMinMax.second + 0.05) / (aMinMax.first + 0.05); +} + +class TextContrastCheck : public NodeCheck +{ +private: + void checkTextRange(uno::Reference const& xTextRange, + uno::Reference const& xParagraph, SwTextNode* pTextNode) + { + sal_Int32 nParaBackColor = {}; // spurious -Werror=maybe-uninitialized + uno::Reference xParagraphProperties(xParagraph, uno::UNO_QUERY); + if (!(xParagraphProperties->getPropertyValue("ParaBackColor") >>= nParaBackColor)) + { + SAL_WARN("sw.a11y", "ParaBackColor void"); + return; + } + + uno::Reference xProperties(xTextRange, uno::UNO_QUERY); + if (xProperties.is()) + { + // Foreground color + sal_Int32 nCharColor = {}; // spurious -Werror=maybe-uninitialized + if (!(xProperties->getPropertyValue("CharColor") >>= nCharColor)) + { // not sure this is impossible, can the default be void? + SAL_WARN("sw.a11y", "CharColor void"); + return; + } + Color aForegroundColor(nCharColor); + if (aForegroundColor == COL_AUTO) + return; + + const SwPageDesc* pPageDescription = pTextNode->FindPageDesc(); + const SwFrameFormat& rPageFormat = pPageDescription->GetMaster(); + const SwAttrSet& rPageSet = rPageFormat.GetAttrSet(); + + const XFillStyleItem* pXFillStyleItem( + rPageSet.GetItem(XATTR_FILLSTYLE, false)); + Color aPageBackground; + + if (pXFillStyleItem && pXFillStyleItem->GetValue() == css::drawing::FillStyle_SOLID) + { + const XFillColorItem* rXFillColorItem + = rPageSet.GetItem(XATTR_FILLCOLOR, false); + aPageBackground = rXFillColorItem->GetColorValue(); + } + + sal_Int32 nCharBackColor = {}; // spurious -Werror=maybe-uninitialized + + if (!(xProperties->getPropertyValue("CharBackColor") >>= nCharBackColor)) + { + SAL_WARN("sw.a11y", "CharBackColor void"); + return; + } + // Determine the background color + // Try Character background (highlight) + Color aBackgroundColor(nCharBackColor); + + // If not character background color, try paragraph background color + if (aBackgroundColor == COL_AUTO) + aBackgroundColor = Color(nParaBackColor); + + // If not paragraph background color, try page color + if (aBackgroundColor == COL_AUTO) + aBackgroundColor = aPageBackground; + + // If not page color, assume white background color + if (aBackgroundColor == COL_AUTO) + aBackgroundColor = COL_WHITE; + + double fContrastRatio = calculateContrastRatio(aForegroundColor, aBackgroundColor); + if (fContrastRatio < 4.5) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_CONTRAST)); + } + } + } + +public: + TextContrastCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->IsTextNode()) + { + SwTextNode* pTextNode = pCurrent->GetTextNode(); + uno::Reference xParagraph; + xParagraph = SwXParagraph::CreateXParagraph(*pTextNode->GetDoc(), pTextNode); + if (xParagraph.is()) + { + uno::Reference xRunEnumAccess(xParagraph, + uno::UNO_QUERY); + uno::Reference xRunEnum + = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + if (xRun.is()) + checkTextRange(xRun, xParagraph, pTextNode); + } + } + } + } +}; + +class TextFormattingCheck : public NodeCheck +{ +private: +public: + TextFormattingCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void checkAutoFormat(SwTextNode* pTextNode, const SwTextAttr* pTextAttr) + { + const SwFormatAutoFormat& rAutoFormat = pTextAttr->GetAutoFormat(); + SfxItemIter aItemIter(*rAutoFormat.GetStyleHandle()); + const SfxPoolItem* pItem = aItemIter.GetCurItem(); + std::vector aFormattings; + while (pItem) + { + OUString sFormattingType; + switch (pItem->Which()) + { + case RES_CHRATR_WEIGHT: + case RES_CHRATR_CJK_WEIGHT: + case RES_CHRATR_CTL_WEIGHT: + sFormattingType = "Weight"; + break; + case RES_CHRATR_POSTURE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CTL_POSTURE: + sFormattingType = "Posture"; + break; + + case RES_CHRATR_SHADOWED: + sFormattingType = "Shadowed"; + break; + + case RES_CHRATR_COLOR: + sFormattingType = "Font Color"; + break; + + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CTL_FONTSIZE: + sFormattingType = "Font Size"; + break; + + case RES_CHRATR_FONT: + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CTL_FONT: + sFormattingType = "Font"; + break; + + case RES_CHRATR_EMPHASIS_MARK: + sFormattingType = "Emphasis Mark"; + break; + + case RES_CHRATR_UNDERLINE: + sFormattingType = "Underline"; + break; + + case RES_CHRATR_OVERLINE: + sFormattingType = "Overline"; + break; + + case RES_CHRATR_CROSSEDOUT: + sFormattingType = "Strikethrough"; + break; + + case RES_CHRATR_RELIEF: + sFormattingType = "Relief"; + break; + + case RES_CHRATR_CONTOUR: + sFormattingType = "Outline"; + break; + default: + break; + } + if (!sFormattingType.isEmpty()) + aFormattings.push_back(sFormattingType); + pItem = aItemIter.NextItem(); + } + if (!aFormattings.empty()) + { + o3tl::remove_duplicates(aFormattings); + auto pIssue + = lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_FORMATTING_CONVEYS_MEANING), + sfx::AccessibilityIssueID::TEXT_FORMATTING); + pIssue->setIssueObject(IssueObject::TEXT); + pIssue->setNode(pTextNode); + SwDoc* pDocument = pTextNode->GetDoc(); + pIssue->setDoc(pDocument); + pIssue->setStart(pTextAttr->GetStart()); + pIssue->setEnd(pTextAttr->GetAnyEnd()); + } + } + void check(SwNode* pCurrent) override + { + if (pCurrent->IsTextNode()) + { + SwTextNode* pTextNode = pCurrent->GetTextNode(); + if (pTextNode->HasHints()) + { + SwpHints& rHints = pTextNode->GetSwpHints(); + for (size_t i = 0; i < rHints.Count(); ++i) + { + const SwTextAttr* pTextAttr = rHints.Get(i); + if (pTextAttr->Which() == RES_TXTATR_AUTOFMT) + { + checkAutoFormat(pTextNode, pTextAttr); + } + } + } + } + } +}; + +class BlinkingTextCheck : public NodeCheck +{ +private: + void checkTextRange(uno::Reference const& xTextRange) + { + uno::Reference xProperties(xTextRange, uno::UNO_QUERY); + if (xProperties.is() && xProperties->getPropertySetInfo()->hasPropertyByName("CharFlash")) + { + bool bBlinking = false; + xProperties->getPropertyValue("CharFlash") >>= bBlinking; + + if (bBlinking) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_TEXT_BLINKING)); + } + } + } + +public: + BlinkingTextCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->IsTextNode()) + { + SwTextNode* pTextNode = pCurrent->GetTextNode(); + uno::Reference xParagraph; + xParagraph = SwXParagraph::CreateXParagraph(*pTextNode->GetDoc(), pTextNode); + if (xParagraph.is()) + { + uno::Reference xRunEnumAccess(xParagraph, + uno::UNO_QUERY); + uno::Reference xRunEnum + = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + if (xRun.is()) + checkTextRange(xRun); + } + } + } + } +}; + +class HeaderCheck : public NodeCheck +{ +private: + int m_nPreviousLevel; + +public: + HeaderCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : NodeCheck(rIssueCollection) + , m_nPreviousLevel(0) + { + } + + void check(SwNode* pCurrent) override + { + if (pCurrent->IsTextNode()) + { + SwTextNode* pTextNode = pCurrent->GetTextNode(); + SwTextFormatColl* pCollection = pTextNode->GetTextColl(); + int nLevel = pCollection->GetAssignedOutlineStyleLevel(); + if (nLevel < 0) + return; + + if (nLevel > m_nPreviousLevel && std::abs(nLevel - m_nPreviousLevel) > 1) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_HEADINGS_NOT_IN_ORDER)); + } + m_nPreviousLevel = nLevel; + } + } +}; + +class DocumentCheck : public BaseCheck +{ +public: + DocumentCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : BaseCheck(rIssueCollection) + { + } + + virtual void check(SwDoc* pDoc) = 0; +}; + +// Check default language +class DocumentDefaultLanguageCheck : public DocumentCheck +{ +public: + DocumentDefaultLanguageCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : DocumentCheck(rIssueCollection) + { + } + + void check(SwDoc* pDoc) override + { + // TODO maybe - also check RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE if CJK or CTL are enabled + const SvxLanguageItem& rLang = pDoc->GetDefault(RES_CHRATR_LANGUAGE); + LanguageType eLanguage = rLang.GetLanguage(); + if (eLanguage == LANGUAGE_NONE) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_DEFAULT_LANGUAGE), + sfx::AccessibilityIssueID::DOCUMENT_LANGUAGE); + } + else + { + for (SwTextFormatColl* pTextFormatCollection : *pDoc->GetTextFormatColls()) + { + const SwAttrSet& rAttrSet = pTextFormatCollection->GetAttrSet(); + if (rAttrSet.GetLanguage(false).GetLanguage() == LANGUAGE_NONE) + { + OUString sName = pTextFormatCollection->GetName(); + OUString sIssueText + = SwResId(STR_STYLE_NO_LANGUAGE).replaceAll("%STYLE_NAME%", sName); + lclAddIssue(m_rIssueCollection, sIssueText, + sfx::AccessibilityIssueID::STYLE_LANGUAGE); + } + } + } + } +}; + +class DocumentTitleCheck : public DocumentCheck +{ +public: + DocumentTitleCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : DocumentCheck(rIssueCollection) + { + } + + void check(SwDoc* pDoc) override + { + SwDocShell* pShell = pDoc->GetDocShell(); + if (pShell) + { + const uno::Reference xDPS(pShell->GetModel(), + uno::UNO_QUERY_THROW); + const uno::Reference xDocumentProperties( + xDPS->getDocumentProperties()); + OUString sTitle = xDocumentProperties->getTitle(); + if (sTitle.isEmpty()) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_DOCUMENT_TITLE), + sfx::AccessibilityIssueID::DOCUMENT_TITLE); + } + } + } +}; + +class FootnoteEndnoteCheck : public DocumentCheck +{ +public: + FootnoteEndnoteCheck(sfx::AccessibilityIssueCollection& rIssueCollection) + : DocumentCheck(rIssueCollection) + { + } + + void check(SwDoc* pDoc) override + { + for (SwTextFootnote const* pTextFootnote : pDoc->GetFootnoteIdxs()) + { + SwFormatFootnote const& rFootnote = pTextFootnote->GetFootnote(); + if (rFootnote.IsEndNote()) + { + lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_ENDNOTES)); + } + else + { + lclAddIssue(m_rIssueCollection, SwResId(STR_AVOID_FOOTNOTES)); + } + } + } +}; + +} // end anonymous namespace + +// Check Shapes, TextBox +void AccessibilityCheck::checkObject(SdrObject* pObject) +{ + if (!pObject) + return; + + if (pObject->GetObjIdentifier() == OBJ_CUSTOMSHAPE || pObject->GetObjIdentifier() == OBJ_TEXT) + { + OUString sAlternative = pObject->GetTitle(); + if (sAlternative.isEmpty()) + { + OUString sName = pObject->GetName(); + OUString sIssueText = SwResId(STR_NO_ALT).replaceAll("%OBJECT_NAME%", sName); + lclAddIssue(m_aIssueCollection, sIssueText, sfx::AccessibilityIssueID::NO_ALT_SHAPE); + } + } +} + +void AccessibilityCheck::check() +{ + if (m_pDoc == nullptr) + return; + + std::vector> aDocumentChecks; + aDocumentChecks.push_back(std::make_unique(m_aIssueCollection)); + aDocumentChecks.push_back(std::make_unique(m_aIssueCollection)); + aDocumentChecks.push_back(std::make_unique(m_aIssueCollection)); + + for (std::unique_ptr& rpDocumentCheck : aDocumentChecks) + { + rpDocumentCheck->check(m_pDoc); + } + + std::vector> aNodeChecks; + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + aNodeChecks.push_back(std::make_unique(m_aIssueCollection)); + + auto const& pNodes = m_pDoc->GetNodes(); + SwNode* pNode = nullptr; + for (sal_uLong n = 0; n < pNodes.Count(); ++n) + { + pNode = pNodes[n]; + if (pNode) + { + for (std::unique_ptr& rpNodeCheck : aNodeChecks) + { + rpNodeCheck->check(pNode); + } + } + } + + IDocumentDrawModelAccess& rDrawModelAccess = m_pDoc->getIDocumentDrawModelAccess(); + auto* pModel = rDrawModelAccess.GetDrawModel(); + for (sal_uInt16 nPage = 0; nPage < pModel->GetPageCount(); ++nPage) + { + SdrPage* pPage = pModel->GetPage(nPage); + for (size_t nObject = 0; nObject < pPage->GetObjCount(); ++nObject) + { + SdrObject* pObject = pPage->GetObj(nObject); + if (pObject) + checkObject(pObject); + } + } +} + +} // end sw namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/AccessibilityIssue.cxx b/sw/source/core/access/AccessibilityIssue.cxx new file mode 100644 index 000000000..991ec866a --- /dev/null +++ b/sw/source/core/access/AccessibilityIssue.cxx @@ -0,0 +1,82 @@ +/* -*- 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 + +namespace sw +{ +AccessibilityIssue::AccessibilityIssue(sfx::AccessibilityIssueID eIssueID) + : sfx::AccessibilityIssue(eIssueID) + , m_eIssueObject(IssueObject::UNKNOWN) + , m_pDoc(nullptr) + , m_pNode(nullptr) + , m_nStart(0) + , m_nEnd(0) +{ +} + +void AccessibilityIssue::setIssueObject(IssueObject eIssueObject) { m_eIssueObject = eIssueObject; } + +void AccessibilityIssue::setDoc(SwDoc* pDoc) { m_pDoc = pDoc; } + +void AccessibilityIssue::setObjectID(OUString const& rID) { m_sObjectID = rID; } + +bool AccessibilityIssue::canGotoIssue() const +{ + if (m_eIssueObject != IssueObject::UNKNOWN) + return true; + return false; +} + +void AccessibilityIssue::gotoIssue() const +{ + if (!m_pDoc) + return; + + switch (m_eIssueObject) + { + case IssueObject::GRAPHIC: + case IssueObject::OLE: + { + SwWrtShell* pWrtShell = m_pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoFly(m_sObjectID, FLYCNTTYPE_ALL, true); + } + break; + case IssueObject::TABLE: + { + SwWrtShell* pWrtShell = m_pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoTable(m_sObjectID); + } + break; + case IssueObject::TEXT: + { + SwWrtShell* pWrtShell = m_pDoc->GetDocShell()->GetWrtShell(); + SwContentNode* pContentNode = m_pNode->GetContentNode(); + SwPosition aPoint(*pContentNode, m_nStart); + SwPosition aMark(*pContentNode, m_nEnd); + pWrtShell->EnterStdMode(); + pWrtShell->StartAllAction(); + SwPaM* pPaM = pWrtShell->GetCursor(); + *pPaM->GetPoint() = aPoint; + pPaM->SetMark(); + *pPaM->GetMark() = aMark; + pWrtShell->EndAllAction(); + } + break; + default: + break; + } +} + +} // end sw namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccell.cxx b/sw/source/core/access/acccell.cxx new file mode 100644 index 000000000..ee533636e --- /dev/null +++ b/sw/source/core/access/acccell.cxx @@ -0,0 +1,467 @@ +/* -*- 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 "accfrmobj.hxx" +#include "accfrmobjslist.hxx" +#include +#include +#include +#include "acccell.hxx" + +#include + +#include +#include +#include +#include "acctable.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace sw::access; + +const char sImplementationName[] = "com.sun.star.comp.Writer.SwAccessibleCellView"; + +bool SwAccessibleCell::IsSelected() +{ + bool bRet = false; + + assert(GetMap()); + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + if( auto pCSh = dynamic_cast(pVSh) ) + { + if( pCSh->IsTableMode() ) + { + const SwCellFrame *pCFrame = + static_cast< const SwCellFrame * >( GetFrame() ); + SwTableBox *pBox = + const_cast< SwTableBox *>( pCFrame->GetTabBox() ); + SwSelBoxes const& rBoxes(pCSh->GetTableCursor()->GetSelectedBoxes()); + bRet = rBoxes.find(pBox) != rBoxes.end(); + } + } + + return bRet; +} + +void SwAccessibleCell::GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // SELECTABLE + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + if( dynamic_cast( pVSh) != nullptr ) + rStateSet.AddState( AccessibleStateType::SELECTABLE ); + //Add resizable state to table cell. + rStateSet.AddState( AccessibleStateType::RESIZABLE ); + + if (IsDisposing()) // tdf#135098 + return; + + // SELECTED + if( IsSelected() ) + { + rStateSet.AddState( AccessibleStateType::SELECTED ); + SAL_WARN_IF(!m_bIsSelected, "sw.a11y", "bSelected out of sync"); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } +} + +SwAccessibleCell::SwAccessibleCell(std::shared_ptr const& pInitMap, + const SwCellFrame *pCellFrame ) + : SwAccessibleContext( pInitMap, AccessibleRole::TABLE_CELL, pCellFrame ) + , m_aSelectionHelper( *this ) + , m_bIsSelected( false ) +{ + OUString sBoxName( pCellFrame->GetTabBox()->GetName() ); + SetName( sBoxName ); + + m_bIsSelected = IsSelected(); + + css::uno::Reference xTableReference( + getAccessibleParentImpl()); + css::uno::Reference xContextTable( + xTableReference, css::uno::UNO_QUERY); + SAL_WARN_IF( + (!xContextTable.is() + || xContextTable->getAccessibleRole() != AccessibleRole::TABLE), + "sw.a11y", "bad accessible context"); + m_pAccTable = static_cast(xTableReference.get()); +} + +bool SwAccessibleCell::InvalidateMyCursorPos() +{ + bool bNew = IsSelected(); + bool bOld; + { + osl::MutexGuard aGuard( m_Mutex ); + bOld = m_bIsSelected; + m_bIsSelected = bNew; + } + if( bNew ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + bool bChanged = bOld != bNew; + if( bChanged ) + { + FireStateChangedEvent( AccessibleStateType::SELECTED, bNew ); + if (m_pAccTable.is()) + { + m_pAccTable->AddSelectionCell(this,bNew); + } + } + return bChanged; +} + +bool SwAccessibleCell::InvalidateChildrenCursorPos( const SwFrame *pFrame ) +{ + bool bChanged = false; + + const SwAccessibleChildSList aVisList( GetVisArea(), *pFrame, *GetMap() ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + if( pLower ) + { + if( rLower.IsAccessible( GetMap()->GetShell()->IsPreview() ) ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl( pLower, false ) ); + if( xAccImpl.is() ) + { + assert(xAccImpl->GetFrame()->IsCellFrame()); + bChanged = static_cast< SwAccessibleCell *>( + xAccImpl.get() )->InvalidateMyCursorPos(); + } + else + bChanged = true; // If the context is not know we + // don't know whether the selection + // changed or not. + } + else + { + // This is a box with sub rows. + bChanged |= InvalidateChildrenCursorPos( pLower ); + } + } + ++aIter; + } + + return bChanged; +} + +void SwAccessibleCell::InvalidateCursorPos_() +{ + if (IsSelected()) + { + const SwAccessibleChild aChild( GetChild( *(GetMap()), 0 ) ); + if( aChild.IsValid() && aChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( GetMap()->GetContextImpl( aChild.GetSwFrame()) ); + if (xChildImpl.is()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + aEvent.NewValue <<= AccessibleStateType::FOCUSED; + xChildImpl->FireAccessibleEvent( aEvent ); + } + } + } + + const SwFrame *pParent = GetParent( SwAccessibleChild(GetFrame()), IsInPagePreview() ); + assert(pParent->IsTabFrame()); + const SwTabFrame *pTabFrame = static_cast< const SwTabFrame * >( pParent ); + if( pTabFrame->IsFollow() ) + pTabFrame = pTabFrame->FindMaster(); + + while( pTabFrame ) + { + InvalidateChildrenCursorPos( pTabFrame ); + pTabFrame = pTabFrame->GetFollow(); + } + if (m_pAccTable.is()) + { + m_pAccTable->FireSelectionEvent(); + } +} + +bool SwAccessibleCell::HasCursor() +{ + osl::MutexGuard aGuard( m_Mutex ); + return m_bIsSelected; +} + +SwAccessibleCell::~SwAccessibleCell() +{ +} + +OUString SAL_CALL SwAccessibleCell::getAccessibleDescription() +{ + return GetName(); +} + +OUString SAL_CALL SwAccessibleCell::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleCell::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleCell::getSupportedServiceNames() +{ + return { "com.sun.star.table.AccessibleCellView", sAccessibleServiceName }; +} + +void SwAccessibleCell::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + const SwFrame *pParent = GetParent( SwAccessibleChild(GetFrame()), IsInPagePreview() ); + ::rtl::Reference< SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl( pParent, false ) ); + if( xAccImpl.is() ) + xAccImpl->DisposeChild(SwAccessibleChild(GetFrame()), bRecursive, bCanSkipInvisible); + SwAccessibleContext::Dispose( bRecursive ); +} + +void SwAccessibleCell::InvalidatePosOrSize( const SwRect& rOldBox ) +{ + const SwFrame *pParent = GetParent( SwAccessibleChild(GetFrame()), IsInPagePreview() ); + ::rtl::Reference< SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl( pParent, false ) ); + if( xAccImpl.is() ) + xAccImpl->InvalidateChildPosOrSize( SwAccessibleChild(GetFrame()), rOldBox ); + SwAccessibleContext::InvalidatePosOrSize( rOldBox ); +} + +// XAccessibleInterface + +uno::Any SwAccessibleCell::queryInterface( const uno::Type& rType ) +{ + if (rType == cppu::UnoType::get()) + { + uno::Any aR; + aR <<= uno::Reference(this); + return aR; + } + + if (rType == cppu::UnoType::get()) + { + uno::Any aR; + aR <<= uno::Reference(this); + return aR; + } + if ( rType == ::cppu::UnoType::get() ) + { + uno::Reference xValue = this; + uno::Any aRet; + aRet <<= xValue; + return aRet; + } + else + { + return SwAccessibleContext::queryInterface( rType ); + } +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleCell::getTypes() +{ + return cppu::OTypeCollection( + ::cppu::UnoType::get(), + SwAccessibleContext::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleCell::getImplementationId() +{ + return css::uno::Sequence(); +} + +// XAccessibleValue + +SwFrameFormat* SwAccessibleCell::GetTableBoxFormat() const +{ + assert(GetFrame()); + assert(GetFrame()->IsCellFrame()); + + const SwCellFrame* pCellFrame = static_cast( GetFrame() ); + return pCellFrame->GetTabBox()->GetFrameFormat(); +} + +//Implement TableCell currentValue +uno::Any SwAccessibleCell::getCurrentValue( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return uno::Any( GetTableBoxFormat()->GetTableBoxValue().GetValue() ); +} + +sal_Bool SwAccessibleCell::setCurrentValue( const uno::Any& aNumber ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + double fValue = 0; + bool bValid = (aNumber >>= fValue); + if( bValid ) + { + SwTableBoxValue aValue( fValue ); + GetTableBoxFormat()->SetFormatAttr( aValue ); + } + return bValid; +} + +uno::Any SwAccessibleCell::getMaximumValue( ) +{ + return uno::Any(DBL_MAX); +} + +uno::Any SwAccessibleCell::getMinimumValue( ) +{ + return uno::Any(-DBL_MAX); +} + +static OUString ReplaceOneChar(const OUString& oldOUString, const OUString& replacedChar, const OUString& replaceStr) +{ + int iReplace = oldOUString.lastIndexOf(replacedChar); + OUString aRet = oldOUString; + while(iReplace > -1) + { + aRet = aRet.replaceAt(iReplace,1, replaceStr); + iReplace = aRet.lastIndexOf(replacedChar,iReplace); + } + return aRet; +} + +static OUString ReplaceFourChar(const OUString& oldOUString) +{ + OUString aRet = ReplaceOneChar(oldOUString,"\\","\\\\"); + aRet = ReplaceOneChar(aRet,";","\\;"); + aRet = ReplaceOneChar(aRet,"=","\\="); + aRet = ReplaceOneChar(aRet,",","\\,"); + aRet = ReplaceOneChar(aRet,":","\\:"); + return aRet; +} + +css::uno::Any SAL_CALL SwAccessibleCell::getExtendedAttributes() +{ + SolarMutexGuard g; + + css::uno::Any strRet; + SwFrameFormat *pFrameFormat = GetTableBoxFormat(); + assert(pFrameFormat); + + const SwTableBoxFormula& tbl_formula = pFrameFormat->GetTableBoxFormula(); + + OUString strFormula = ReplaceFourChar(tbl_formula.GetFormula()); + OUString strFor = "Formula:" + strFormula + ";"; + strRet <<= strFor; + + return strRet; +} + +sal_Int32 SAL_CALL SwAccessibleCell::getBackground() +{ + SolarMutexGuard g; + + const SvxBrushItem &rBack = GetFrame()->GetAttrSet()->GetBackground(); + Color crBack = rBack.GetColor(); + + if (COL_AUTO == crBack) + { + uno::Reference xAccDoc = getAccessibleParent(); + if (xAccDoc.is()) + { + uno::Reference xComponentDoc(xAccDoc, uno::UNO_QUERY); + if (xComponentDoc.is()) + { + crBack = Color(xComponentDoc->getBackground()); + } + } + } + return sal_Int32(crBack); +} + +// XAccessibleSelection +void SwAccessibleCell::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + m_aSelectionHelper.selectAccessibleChild(nChildIndex); +} + +sal_Bool SwAccessibleCell::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + return m_aSelectionHelper.isAccessibleChildSelected(nChildIndex); +} + +void SwAccessibleCell::clearAccessibleSelection( ) +{ +} + +void SwAccessibleCell::selectAllAccessibleChildren( ) +{ + m_aSelectionHelper.selectAllAccessibleChildren(); +} + +sal_Int32 SwAccessibleCell::getSelectedAccessibleChildCount( ) +{ + return m_aSelectionHelper.getSelectedAccessibleChildCount(); +} + +uno::Reference SwAccessibleCell::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + return m_aSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex); +} + +void SwAccessibleCell::deselectAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + m_aSelectionHelper.deselectAccessibleChild(nSelectedChildIndex); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccell.hxx b/sw/source/core/access/acccell.hxx new file mode 100644 index 000000000..4caa25cad --- /dev/null +++ b/sw/source/core/access/acccell.hxx @@ -0,0 +1,135 @@ +/* -*- 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 . + */ + +#pragma once + +#include "acccontext.hxx" +#include +#include "accselectionhelper.hxx" + +class SwCellFrame; +class SwAccessibleTable; +class SwFrameFormat; + +class SwAccessibleCell : public SwAccessibleContext, + public css::accessibility::XAccessibleValue, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleExtendedAttributes +{ + // Implementation for XAccessibleSelection interface + SwAccessibleSelectionHelper m_aSelectionHelper; + bool m_bIsSelected; // protected by base class mutex + + bool IsSelected(); + + bool InvalidateMyCursorPos(); + bool InvalidateChildrenCursorPos( const SwFrame *pFrame ); + + rtl::Reference m_pAccTable; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets SELECTABLE(1) and SELECTED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual void InvalidateCursorPos_() override; + + virtual ~SwAccessibleCell() override; + +public: + SwAccessibleCell(std::shared_ptr const& pInitMap, + const SwCellFrame *pCellFrame); + + virtual bool HasCursor() override; // required by map to remember that object + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + virtual void InvalidatePosOrSize( const SwRect& rFrame ) override; + + // XInterface + + // (XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleValue). + + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) throw () override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) throw () override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleValue + + // XAccessibleExtendedAttributes + css::uno::Any SAL_CALL getExtendedAttributes() override ; +private: + SwFrameFormat* GetTableBoxFormat() const; + +public: + virtual css::uno::Any SAL_CALL getCurrentValue( ) override; + + virtual sal_Bool SAL_CALL setCurrentValue( const css::uno::Any& aNumber ) override; + + virtual css::uno::Any SAL_CALL getMaximumValue( ) override; + + virtual css::uno::Any SAL_CALL getMinimumValue( ) override; + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + virtual void SAL_CALL deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccontext.cxx b/sw/source/core/access/acccontext.cxx new file mode 100644 index 000000000..3b9f0f1e6 --- /dev/null +++ b/sw/source/core/access/acccontext.cxx @@ -0,0 +1,1526 @@ +/* -*- 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 "accfrmobjslist.hxx" +#include "acccontext.hxx" +#include +#include +#include +#include + +using namespace sw::access; +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +void SwAccessibleContext::InitStates() +{ + m_isShowingState = GetMap() && IsShowing( *(GetMap()) ); + + SwViewShell *pVSh = GetMap()->GetShell(); + m_isEditableState = pVSh && IsEditable( pVSh ); + m_isOpaqueState = pVSh && IsOpaque( pVSh ); + m_isDefuncState = false; +} + +void SwAccessibleContext::SetParent( SwAccessibleContext *pParent ) +{ + osl::MutexGuard aGuard( m_Mutex ); + + uno::Reference < XAccessible > xParent( pParent ); + m_xWeakParent = xParent; +} + +uno::Reference< XAccessible > SwAccessibleContext::GetWeakParent() const +{ + osl::MutexGuard aGuard( m_Mutex ); + + uno::Reference< XAccessible > xParent( m_xWeakParent ); + return xParent; +} + +vcl::Window *SwAccessibleContext::GetWindow() +{ + vcl::Window *pWin = nullptr; + + if( GetMap() ) + { + const SwViewShell *pVSh = GetMap()->GetShell(); + OSL_ENSURE( pVSh, "no view shell" ); + if( pVSh ) + pWin = pVSh->GetWin(); + + OSL_ENSURE( pWin, "no window" ); + } + + return pWin; +} + +// get SwViewShell from accessibility map, and cast to cursor shell +SwCursorShell* SwAccessibleContext::GetCursorShell() +{ + SwViewShell* pViewShell = GetMap() ? GetMap()->GetShell() : nullptr; + OSL_ENSURE( pViewShell, "no view shell" ); + return dynamic_cast( pViewShell); +} + +const SwCursorShell* SwAccessibleContext::GetCursorShell() const +{ + // just like non-const GetCursorShell + const SwViewShell* pViewShell = GetMap() ? GetMap()->GetShell() : nullptr; + OSL_ENSURE( pViewShell, "no view shell" ); + return dynamic_cast( pViewShell); +} + +namespace { + +enum class Action { NONE, SCROLLED, SCROLLED_WITHIN, + SCROLLED_IN, SCROLLED_OUT }; + +} + +void SwAccessibleContext::ChildrenScrolled( const SwFrame *pFrame, + const SwRect& rOldVisArea ) +{ + const SwRect& rNewVisArea = GetVisArea(); + const bool bVisibleChildrenOnly = SwAccessibleChild( pFrame ).IsVisibleChildrenOnly(); + + const SwAccessibleChildSList aList( *pFrame, *(GetMap()) ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + while( aIter != aList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwRect aBox( rLower.GetBox( *(GetMap()) ) ); + if( rLower.IsAccessible( GetShell()->IsPreview() ) ) + { + Action eAction = Action::NONE; + if( aBox.IsOver( rNewVisArea ) ) + { + if( aBox.IsOver( rOldVisArea ) ) + { + eAction = Action::SCROLLED_WITHIN; + } + else + { + if ( bVisibleChildrenOnly && + !rLower.AlwaysIncludeAsChild() ) + { + eAction = Action::SCROLLED_IN; + } + else + { + eAction = Action::SCROLLED; + } + } + } + else if( aBox.IsOver( rOldVisArea ) ) + { + if ( bVisibleChildrenOnly && + !rLower.AlwaysIncludeAsChild() ) + { + eAction = Action::SCROLLED_OUT; + } + else + { + eAction = Action::SCROLLED; + } + } + else if( !bVisibleChildrenOnly || + rLower.AlwaysIncludeAsChild() ) + { + // This wouldn't be required if the SwAccessibleFrame, + // wouldn't know about the visible area. + eAction = Action::SCROLLED; + } + if( Action::NONE != eAction ) + { + if ( rLower.GetSwFrame() ) + { + OSL_ENSURE( !rLower.AlwaysIncludeAsChild(), + " - always included child not considered!" ); + const SwFrame* pLower( rLower.GetSwFrame() ); + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( pLower ); + if( xAccImpl.is() ) + { + switch( eAction ) + { + case Action::SCROLLED: + xAccImpl->Scrolled( rOldVisArea ); + break; + case Action::SCROLLED_WITHIN: + xAccImpl->ScrolledWithin( rOldVisArea ); + break; + case Action::SCROLLED_IN: + xAccImpl->ScrolledIn(); + break; + case Action::SCROLLED_OUT: + xAccImpl->ScrolledOut( rOldVisArea ); + break; + case Action::NONE: + break; + } + } + else + { + ChildrenScrolled( pLower, rOldVisArea ); + } + } + else if ( rLower.GetDrawObject() ) + { + OSL_ENSURE( !rLower.AlwaysIncludeAsChild(), + " - always included child not considered!" ); + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rLower.GetDrawObject(), + this ); + if( xAccImpl.is() ) + { + switch( eAction ) + { + case Action::SCROLLED: + case Action::SCROLLED_WITHIN: + xAccImpl->ViewForwarderChanged(); + break; + case Action::SCROLLED_IN: + ScrolledInShape( xAccImpl.get() ); + break; + case Action::SCROLLED_OUT: + { + xAccImpl->ViewForwarderChanged(); + // this DisposeShape call was removed by + // IAccessibility2 implementation + // without giving any reason why + DisposeShape( rLower.GetDrawObject(), + xAccImpl.get() ); + } + break; + // coverity[dead_error_begin] - following conditions exist to avoid compiler warning + case Action::NONE: + break; + } + } + } + else if ( rLower.GetWindow() ) + { + // nothing to do - as such children are always included as children. + OSL_ENSURE( rLower.AlwaysIncludeAsChild(), + " - not always included child not considered!" ); + } + } + } + else if ( rLower.GetSwFrame() && + ( !bVisibleChildrenOnly || + aBox.IsOver( rOldVisArea ) || + aBox.IsOver( rNewVisArea ) ) ) + { + // There are no unaccessible SdrObjects that need to be notified + ChildrenScrolled( rLower.GetSwFrame(), rOldVisArea ); + } + ++aIter; + } +} + +void SwAccessibleContext::Scrolled( const SwRect& rOldVisArea ) +{ + SetVisArea( GetMap()->GetVisArea() ); + + ChildrenScrolled( GetFrame(), rOldVisArea ); + + bool bIsOldShowingState; + bool bIsNewShowingState = IsShowing( *(GetMap()) ); + { + osl::MutexGuard aGuard( m_Mutex ); + bIsOldShowingState = m_isShowingState; + m_isShowingState = bIsNewShowingState; + } + + if( bIsOldShowingState != bIsNewShowingState ) + FireStateChangedEvent( AccessibleStateType::SHOWING, + bIsNewShowingState ); +} + +void SwAccessibleContext::ScrolledWithin( const SwRect& rOldVisArea ) +{ + SetVisArea( GetMap()->GetVisArea() ); + + ChildrenScrolled( GetFrame(), rOldVisArea ); + + FireVisibleDataEvent(); +} + +void SwAccessibleContext::ScrolledIn() +{ + // This accessible should be freshly created, because it + // was not visible before. Therefore, its visible area must already + // reflect the scrolling. + OSL_ENSURE( GetVisArea() == GetMap()->GetVisArea(), + "Visible area of child is wrong. Did it exist already?" ); + + // Send child event at parent. That's all we have to do here. + const SwFrame* pParent = GetParent(); + ::rtl::Reference< SwAccessibleContext > xParentImpl( + GetMap()->GetContextImpl( pParent, false ) ); + uno::Reference < XAccessibleContext > xThis( this ); + if( xParentImpl.is() ) + { + SetParent( xParentImpl.get() ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xThis; + + xParentImpl->FireAccessibleEvent( aEvent ); + + if( HasCursor() ) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + { + FireStateChangedEvent( AccessibleStateType::FOCUSED, true ); + } + } + + } +} + +void SwAccessibleContext::ScrolledOut( const SwRect& rOldVisArea ) +{ + SetVisArea( GetMap()->GetVisArea() ); + + // First of all, update the children. That's required to dispose + // all children that are existing only if they are visible. They + // are not disposed by the recursive Dispose call that follows later on, + // because this call will only dispose children that are in the + // new visible area. The children we want to dispose however are in the + // old visible area all. + ChildrenScrolled( GetFrame(), rOldVisArea ); + + // Broadcast a state changed event for the showing state. + // It might be that the child is freshly created just to send + // the child event. In this case no listener will exist. + FireStateChangedEvent( AccessibleStateType::SHOWING, false ); + + // this Dispose call was removed by IAccessibility2 implementation + // without giving any reason why - without it we get stale + // entries in SwAccessibleMap::mpFrameMap. + Dispose(true); +} + +// #i27301# - use new type definition for <_nStates> +void SwAccessibleContext::InvalidateChildrenStates( const SwFrame* _pFrame, + AccessibleStates _nStates ) +{ + const SwAccessibleChildSList aVisList( GetVisArea(), *_pFrame, *(GetMap()) ); + + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame* pLower = rLower.GetSwFrame(); + if( pLower ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl; + if( rLower.IsAccessible( GetShell()->IsPreview() ) ) + xAccImpl = GetMap()->GetContextImpl( pLower, false ); + if( xAccImpl.is() ) + xAccImpl->InvalidateStates( _nStates ); + else + InvalidateChildrenStates( pLower, _nStates ); + } + else if ( rLower.GetDrawObject() ) + { + // TODO: SdrObjects + } + else if ( rLower.GetWindow() ) + { + // nothing to do ? + } + + ++aIter; + } +} + +void SwAccessibleContext::DisposeChildren(const SwFrame *pFrame, + bool bRecursive, + bool bCanSkipInvisible) +{ + const SwAccessibleChildSList aVisList( GetVisArea(), *pFrame, *(GetMap()) ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame* pLower = rLower.GetSwFrame(); + if( pLower ) + { + // tdf#117601 dispose the darn thing if it ever was accessible + ::rtl::Reference xAccImpl = GetMap()->GetContextImpl(pLower, false); + if( xAccImpl.is() ) + xAccImpl->Dispose( bRecursive ); + else + { + // it's possible that the xAccImpl *does* exist with a + // ref-count of 0 and blocked in its dtor in another thread - + // this call here could be from SwAccessibleMap dtor so + // remove it from any maps now! + GetMap()->RemoveContext(pLower); + // in this case the context will check with a weak_ptr + // that the map is still alive so it's not necessary + // to clear its m_pMap here. + if (bRecursive) + { + DisposeChildren(pLower, bRecursive, bCanSkipInvisible); + } + } + } + else if ( rLower.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl( + GetMap()->GetContextImpl( rLower.GetDrawObject(), + this, false ) ); + if( xAccImpl.is() ) + DisposeShape( rLower.GetDrawObject(), xAccImpl.get() ); + } + else if ( rLower.GetWindow() ) + { + DisposeChild(rLower, false, bCanSkipInvisible); + } + ++aIter; + } +} + +void SwAccessibleContext::InvalidateContent_( bool ) +{ +} + +void SwAccessibleContext::InvalidateCursorPos_() +{ +} + +void SwAccessibleContext::InvalidateFocus_() +{ +} + +void SwAccessibleContext::FireAccessibleEvent( AccessibleEventObject& rEvent ) +{ + OSL_ENSURE( GetFrame(), "fire event for disposed frame?" ); + if( !GetFrame() ) + return; + + if( !rEvent.Source.is() ) + { + uno::Reference < XAccessibleContext > xThis( this ); + rEvent.Source = xThis; + } + + if (m_nClientId) + comphelper::AccessibleEventNotifier::addEvent( m_nClientId, rEvent ); +} + +void SwAccessibleContext::FireVisibleDataEvent() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::VISIBLE_DATA_CHANGED; + + FireAccessibleEvent( aEvent ); +} + +void SwAccessibleContext::FireStateChangedEvent( sal_Int16 nState, + bool bNewState ) +{ + AccessibleEventObject aEvent; + + aEvent.EventId = AccessibleEventId::STATE_CHANGED; + if( bNewState ) + aEvent.NewValue <<= nState; + else + aEvent.OldValue <<= nState; + + FireAccessibleEvent( aEvent ); +} + +void SwAccessibleContext::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SolarMutexGuard aGuard; + + // SHOWING + if (m_isShowingState) + rStateSet.AddState( AccessibleStateType::SHOWING ); + + // EDITABLE + if (m_isEditableState) + //Set editable state to graphic and other object when the document is editable + { + rStateSet.AddState( AccessibleStateType::EDITABLE ); + rStateSet.AddState( AccessibleStateType::RESIZABLE ); + rStateSet.AddState( AccessibleStateType::MOVEABLE ); + } + // ENABLED + rStateSet.AddState( AccessibleStateType::ENABLED ); + + // OPAQUE + if (m_isOpaqueState) + rStateSet.AddState( AccessibleStateType::OPAQUE ); + + // VISIBLE + rStateSet.AddState( AccessibleStateType::VISIBLE ); + + if (m_isDefuncState) + rStateSet.AddState( AccessibleStateType::DEFUNC ); +} + +bool SwAccessibleContext::IsEditableState() +{ + bool bRet; + { + osl::MutexGuard aGuard( m_Mutex ); + bRet = m_isEditableState; + } + + return bRet; +} + +void SwAccessibleContext::ThrowIfDisposed() +{ + if (!(GetFrame() && GetMap())) + { + throw lang::DisposedException("object is nonfunctional", + static_cast(this)); + } +} + +SwAccessibleContext::SwAccessibleContext(std::shared_ptr const& pMap, + sal_Int16 const nRole, + const SwFrame *pF ) + : SwAccessibleFrame( pMap->GetVisArea().SVRect(), pF, + pMap->GetShell()->IsPreview() ) + , m_pMap(pMap.get()) + , m_wMap(pMap) + , m_nClientId(0) + , m_nRole(nRole) + , m_isDisposing( false ) + , m_isRegisteredAtAccessibleMap( true ) + , m_isSelectedInDoc(false) +{ + InitStates(); +} + +SwAccessibleContext::~SwAccessibleContext() +{ + // must have for 2 reasons: 2. as long as this thread has SolarMutex + // another thread cannot destroy the SwAccessibleMap so our temporary + // taking a hard ref to SwAccessibleMap won't delay its destruction + SolarMutexGuard aGuard; + // must check with weak_ptr that m_pMap is still alive + std::shared_ptr pMap(m_wMap.lock()); + if (m_isRegisteredAtAccessibleMap && GetFrame() && pMap) + { + pMap->RemoveContext( GetFrame() ); + } +} + +uno::Reference< XAccessibleContext > SAL_CALL + SwAccessibleContext::getAccessibleContext() +{ + uno::Reference < XAccessibleContext > xRet( this ); + return xRet; +} + +sal_Int32 SAL_CALL SwAccessibleContext::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return m_isDisposing ? 0 : GetChildCount( *(GetMap()) ); +} + +uno::Reference< XAccessible> SAL_CALL + SwAccessibleContext::getAccessibleChild( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwAccessibleChild aChild( GetChild( *(GetMap()), nIndex ) ); + if( !aChild.IsValid() ) + { + uno::Reference < XAccessibleContext > xThis( this ); + lang::IndexOutOfBoundsException aExcept( + "index out of bounds", + xThis ); + throw aExcept; + } + + uno::Reference< XAccessible > xChild; + if( aChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( + GetMap()->GetContextImpl( aChild.GetSwFrame(), !m_isDisposing ) ); + if( xChildImpl.is() ) + { + xChildImpl->SetParent( this ); + xChild = xChildImpl.get(); + } + } + else if ( aChild.GetDrawObject() ) + { + ::rtl::Reference < ::accessibility::AccessibleShape > xChildImpl( + GetMap()->GetContextImpl( aChild.GetDrawObject(), + this, !m_isDisposing) ); + if( xChildImpl.is() ) + xChild = xChildImpl.get(); + } + else if ( aChild.GetWindow() ) + { + xChild = aChild.GetWindow()->GetAccessible(); + } + + return xChild; +} + +css::uno::Sequence> SAL_CALL + SwAccessibleContext::getAccessibleChildren() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + std::list< sw::access::SwAccessibleChild > aChildren; + GetChildren( *GetMap(), aChildren ); + + std::vector> aRet; + aRet.reserve(aChildren.size()); + for (const auto & rSwChild : aChildren) + { + uno::Reference< XAccessible > xChild; + if( rSwChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( + GetMap()->GetContextImpl( rSwChild.GetSwFrame(), !m_isDisposing ) ); + if( xChildImpl.is() ) + { + xChildImpl->SetParent( this ); + xChild = xChildImpl.get(); + } + } + else if ( rSwChild.GetDrawObject() ) + { + ::rtl::Reference < ::accessibility::AccessibleShape > xChildImpl( + GetMap()->GetContextImpl( rSwChild.GetDrawObject(), + this, !m_isDisposing) ); + if( xChildImpl.is() ) + xChild = xChildImpl.get(); + } + else if ( rSwChild.GetWindow() ) + { + xChild = rSwChild.GetWindow()->GetAccessible(); + } + aRet.push_back(xChild); + } + return comphelper::containerToSequence(aRet); +} + +uno::Reference< XAccessible> SwAccessibleContext::getAccessibleParentImpl() +{ + SolarMutexGuard aGuard; + + const SwFrame *pUpper = GetParent(); + OSL_ENSURE( pUpper != nullptr || m_isDisposing, "no upper found" ); + + uno::Reference< XAccessible > xAcc; + if( pUpper ) + xAcc = GetMap()->GetContext( pUpper, !m_isDisposing ); + + OSL_ENSURE( xAcc.is() || m_isDisposing, "no parent found" ); + + // Remember the parent as weak ref. + { + osl::MutexGuard aWeakParentGuard( m_Mutex ); + m_xWeakParent = xAcc; + } + + return xAcc; +} + +uno::Reference< XAccessible> SAL_CALL SwAccessibleContext::getAccessibleParent() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return getAccessibleParentImpl(); +} + +sal_Int32 SAL_CALL SwAccessibleContext::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pUpper = GetParent(); + OSL_ENSURE( pUpper != nullptr || m_isDisposing, "no upper found" ); + + sal_Int32 nIndex = -1; + if( pUpper ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl( + GetMap()->GetContextImpl(pUpper, !m_isDisposing) ); + OSL_ENSURE( xAccImpl.is() || m_isDisposing, "no parent found" ); + if( xAccImpl.is() ) + nIndex = xAccImpl->GetChildIndex( *(GetMap()), SwAccessibleChild(GetFrame()) ); + } + + return nIndex; +} + +sal_Int16 SAL_CALL SwAccessibleContext::getAccessibleRole() +{ + return m_nRole; +} + +OUString SAL_CALL SwAccessibleContext::getAccessibleName() +{ + return m_sName; +} + +uno::Reference< XAccessibleRelationSet> SAL_CALL + SwAccessibleContext::getAccessibleRelationSet() +{ + // by default there are no relations + uno::Reference< XAccessibleRelationSet> xRet( new utl::AccessibleRelationSetHelper() ); + return xRet; +} + +uno::Reference SAL_CALL + SwAccessibleContext::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + ::utl::AccessibleStateSetHelper *pStateSet = + new ::utl::AccessibleStateSetHelper; + + if (m_isSelectedInDoc) + pStateSet->AddState( AccessibleStateType::SELECTED ); + + uno::Reference xStateSet( pStateSet ); + GetStates( *pStateSet ); + + return xStateSet; +} + +lang::Locale SAL_CALL SwAccessibleContext::getLocale() +{ + SolarMutexGuard aGuard; + + lang::Locale aLoc( Application::GetSettings().GetLanguageTag().getLocale() ); + return aLoc; +} + +void SAL_CALL SwAccessibleContext::addAccessibleEventListener( + const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + SolarMutexGuard aGuard; + if (!m_nClientId) + m_nClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( m_nClientId, xListener ); + } +} + +void SAL_CALL SwAccessibleContext::removeAccessibleEventListener( + const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is() && m_nClientId) + { + SolarMutexGuard aGuard; + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( m_nClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( m_nClientId ); + m_nClientId = 0; + } + } +} + +static bool lcl_PointInRectangle(const awt::Point & aPoint, + const awt::Rectangle & aRect) +{ + long nDiffX = aPoint.X - aRect.X; + long nDiffY = aPoint.Y - aRect.Y; + + return + nDiffX >= 0 && nDiffX < aRect.Width && nDiffY >= 0 && + nDiffY < aRect.Height; + +} + +sal_Bool SAL_CALL SwAccessibleContext::containsPoint( + const awt::Point& aPoint ) +{ + awt::Rectangle aPixBounds = getBoundsImpl(true); + aPixBounds.X = 0; + aPixBounds.Y = 0; + + return lcl_PointInRectangle(aPoint, aPixBounds); +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleContext::getAccessibleAtPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + uno::Reference< XAccessible > xAcc; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + Point aPixPoint( aPoint.X, aPoint.Y ); // px rel to parent + if( !GetFrame()->IsRootFrame() ) + { + SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip rel to doc root + Point aPixPos( GetMap()->CoreToPixel( aLogBounds.SVRect() ).TopLeft() ); + aPixPoint.setX(aPixPoint.getX() + aPixPos.getX()); + aPixPoint.setY(aPixPoint.getY() + aPixPos.getY()); + } + + const SwAccessibleChild aChild( GetChildAtPixel( aPixPoint, *(GetMap()) ) ); + if( aChild.GetSwFrame() ) + { + xAcc = GetMap()->GetContext( aChild.GetSwFrame() ); + } + else if( aChild.GetDrawObject() ) + { + xAcc = GetMap()->GetContext( aChild.GetDrawObject(), this ); + } + else if ( aChild.GetWindow() ) + { + xAcc = aChild.GetWindow()->GetAccessible(); + } + + return xAcc; +} + +/** + Get bounding box. + + There are two modes. + + - relative + + Return bounding box relative to parent if parent is no root + frame. Otherwise return the absolute bounding box. + + - absolute + + Return the absolute bounding box. + + @param bRelative + true: Use relative mode. + false: Use absolute mode. +*/ +awt::Rectangle SwAccessibleContext::getBoundsImpl(bool bRelative) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pParent = GetParent(); + OSL_ENSURE( pParent, "no Parent found" ); + vcl::Window *pWin = GetWindow(); + + if (!pParent) + { + throw uno::RuntimeException("no Parent", static_cast(this)); + } + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip relative to document root + tools::Rectangle aPixBounds( 0, 0, 0, 0 ); + if( GetFrame()->IsPageFrame() && + static_cast < const SwPageFrame * >( GetFrame() )->IsEmptyPage() ) + { + OSL_ENSURE( GetShell()->IsPreview(), "empty page accessible?" ); + if( GetShell()->IsPreview() ) + { + // adjust method call GetPreviewPageSize()> + sal_uInt16 nPageNum = + static_cast < const SwPageFrame * >( GetFrame() )->GetPhyPageNum(); + aLogBounds.SSize( GetMap()->GetPreviewPageSize( nPageNum ) ); + } + } + if( !aLogBounds.IsEmpty() ) + { + aPixBounds = GetMap()->CoreToPixel( aLogBounds.SVRect() ); + if( !pParent->IsRootFrame() && bRelative) + { + SwRect aParentLogBounds( GetBounds( *(GetMap()), pParent ) ); // twip rel to doc root + Point aParentPixPos( GetMap()->CoreToPixel( aParentLogBounds.SVRect() ).TopLeft() ); + aPixBounds.Move( -aParentPixPos.getX(), -aParentPixPos.getY() ); + } + } + + awt::Rectangle aBox( aPixBounds.Left(), aPixBounds.Top(), + aPixBounds.GetWidth(), aPixBounds.GetHeight() ); + + return aBox; +} + +awt::Rectangle SAL_CALL SwAccessibleContext::getBounds() +{ + return getBoundsImpl(true); +} + +awt::Point SAL_CALL SwAccessibleContext::getLocation() +{ + awt::Rectangle aRect = getBoundsImpl(true); + awt::Point aPoint(aRect.X, aRect.Y); + + return aPoint; +} + +awt::Point SAL_CALL SwAccessibleContext::getLocationOnScreen() +{ + awt::Rectangle aRect = getBoundsImpl(false); + + Point aPixPos(aRect.X, aRect.Y); + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + aPixPos = pWin->OutputToAbsoluteScreenPixel(aPixPos); + awt::Point aPoint(aPixPos.getX(), aPixPos.getY()); + + return aPoint; +} + +awt::Size SAL_CALL SwAccessibleContext::getSize() +{ + awt::Rectangle aRect = getBoundsImpl(false); + awt::Size aSize( aRect.Width, aRect.Height ); + + return aSize; +} + +void SAL_CALL SwAccessibleContext::grabFocus() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( GetFrame()->IsFlyFrame() ) + { + const SdrObject *pObj = + static_cast < const SwFlyFrame * >( GetFrame() )->GetVirtDrawObj(); + if( pObj ) + Select( const_cast < SdrObject * >( pObj ), false ); + } + else + { + const SwContentFrame *pCFrame = nullptr; + if( GetFrame()->IsContentFrame() ) + pCFrame = static_cast< const SwContentFrame * >( GetFrame() ); + else if( GetFrame()->IsLayoutFrame() ) + pCFrame = static_cast< const SwLayoutFrame * >( GetFrame() )->ContainsContent(); + + if( pCFrame && pCFrame->IsTextFrame() ) + { + const SwTextFrame *pTextFrame = static_cast< const SwTextFrame * >( pCFrame ); + const SwTextNode *pTextNd = pTextFrame->GetTextNodeFirst(); + assert(pTextNd); // can it actually be null? probably not=>simplify + if( pTextNd ) + { + // create pam for selection + SwPosition const aStartPos(pTextFrame->MapViewToModelPos(pTextFrame->GetOffset())); + SwPaM aPaM( aStartPos ); + + // set PaM at cursor shell + Select( aPaM ); + } + } + } +} + +sal_Int32 SAL_CALL SwAccessibleContext::getForeground() +{ + return sal_Int32(COL_BLACK); +} + +sal_Int32 SAL_CALL SwAccessibleContext::getBackground() +{ + return sal_Int32(COL_WHITE); +} + +sal_Bool SAL_CALL SwAccessibleContext::supportsService (const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +void SwAccessibleContext::DisposeShape( const SdrObject *pObj, + ::accessibility::AccessibleShape *pAccImpl ) +{ + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl( pAccImpl ); + if( !xAccImpl.is() ) + xAccImpl = GetMap()->GetContextImpl( pObj, this ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + uno::Reference< XAccessible > xAcc( xAccImpl.get() ); + aEvent.OldValue <<= xAcc; + FireAccessibleEvent( aEvent ); + + GetMap()->RemoveContext( pObj ); + xAccImpl->dispose(); +} + +void SwAccessibleContext::ScrolledInShape( ::accessibility::AccessibleShape *pAccImpl ) +{ + if(nullptr == pAccImpl) + { + return ; + } + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + uno::Reference< XAccessible > xAcc( pAccImpl ); + aEvent.NewValue <<= xAcc; + FireAccessibleEvent( aEvent ); + + if( pAccImpl->GetState( AccessibleStateType::FOCUSED ) ) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + { + AccessibleEventObject aStateChangedEvent; + aStateChangedEvent.EventId = AccessibleEventId::STATE_CHANGED; + aStateChangedEvent.NewValue <<= AccessibleStateType::FOCUSED; + aStateChangedEvent.Source = xAcc; + + FireAccessibleEvent( aStateChangedEvent ); + } + } +} + +void SwAccessibleContext::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( GetFrame() && GetMap(), "already disposed" ); + OSL_ENSURE( GetMap()->GetVisArea() == GetVisArea(), + "invalid visible area for dispose" ); + + m_isDisposing = true; + + // dispose children + if( bRecursive ) + DisposeChildren(GetFrame(), bRecursive, bCanSkipInvisible); + + // get parent + uno::Reference< XAccessible > xParent( GetWeakParent() ); + uno::Reference < XAccessibleContext > xThis( this ); + + // send child event at parent + if( xParent.is() ) + { + SwAccessibleContext *pAcc = static_cast(xParent.get()); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= xThis; + pAcc->FireAccessibleEvent( aEvent ); + } + + // set defunc state (it's not required to broadcast a state changed + // event if the object is disposed afterwards) + { + osl::MutexGuard aDefuncStateGuard( m_Mutex ); + m_isDefuncState = true; + } + + // broadcast dispose event + if (m_nClientId) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( m_nClientId, *this ); + m_nClientId = 0; + } + + RemoveFrameFromAccessibleMap(); + ClearFrame(); + m_pMap = nullptr; + m_wMap.reset(); + + m_isDisposing = false; +} + +void SwAccessibleContext::DisposeChild( const SwAccessibleChild& rChildFrameOrObj, + bool bRecursive, bool bCanSkipInvisible ) +{ + SolarMutexGuard aGuard; + + if ( !bCanSkipInvisible || + rChildFrameOrObj.AlwaysIncludeAsChild() || + IsShowing( *(GetMap()), rChildFrameOrObj ) || + !SwAccessibleChild( GetFrame() ).IsVisibleChildrenOnly() ) + { + // If the object could have existed before, then there is nothing to do, + // because no wrapper exists now and therefore no one is interested to + // get notified of the movement. + if( rChildFrameOrObj.GetSwFrame() ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetSwFrame(), false ); + if (xAccImpl) + xAccImpl->Dispose( bRecursive ); + } + else if ( rChildFrameOrObj.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetDrawObject(), + this, false ); + if (xAccImpl) + DisposeShape( rChildFrameOrObj.GetDrawObject(), + xAccImpl.get() ); + } + else if ( rChildFrameOrObj.GetWindow() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + uno::Reference< XAccessible > xAcc = + rChildFrameOrObj.GetWindow()->GetAccessible(); + aEvent.OldValue <<= xAcc; + FireAccessibleEvent( aEvent ); + } + } + else if( bRecursive && rChildFrameOrObj.GetSwFrame() ) + DisposeChildren(rChildFrameOrObj.GetSwFrame(), bRecursive, bCanSkipInvisible); +} + +void SwAccessibleContext::InvalidatePosOrSize( const SwRect& ) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( GetFrame() && !GetFrame()->getFrameArea().IsEmpty(), "context should have a size" ); + + bool bIsOldShowingState; + bool bIsNewShowingState = IsShowing( *(GetMap()) ); + { + osl::MutexGuard aShowingStateGuard( m_Mutex ); + bIsOldShowingState = m_isShowingState; + m_isShowingState = bIsNewShowingState; + } + + if( bIsOldShowingState != bIsNewShowingState ) + { + FireStateChangedEvent( AccessibleStateType::SHOWING, + bIsNewShowingState ); + } + else if( bIsNewShowingState ) + { + // The frame stays visible -> broadcast event + FireVisibleDataEvent(); + } + + // note: InvalidatePosOrSize must call InvalidateContent_ so that + // SwAccessibleParagraph updates its portions, or dispose it + // (see accmap.cxx: INVALID_CONTENT is contained in POS_CHANGED) + if( !bIsNewShowingState && + SwAccessibleChild( GetParent() ).IsVisibleChildrenOnly() ) + { + // this Dispose call was removed by IAccessibility2 implementation + // without giving any reason why - without it we get stale + // entries in SwAccessibleMap::mpFrameMap. + Dispose(true); + } + else + { + InvalidateContent_( true ); + } +} + +void SwAccessibleContext::InvalidateChildPosOrSize( + const SwAccessibleChild& rChildFrameOrObj, + const SwRect& rOldFrame ) +{ + SolarMutexGuard aGuard; + + // this happens during layout, e.g. when a page is deleted and next page's + // header/footer moves backward such an event is generated + SAL_INFO_IF(rChildFrameOrObj.GetSwFrame() && + rChildFrameOrObj.GetSwFrame()->getFrameArea().IsEmpty(), + "sw.a11y", "child context should have a size"); + + if ( rChildFrameOrObj.AlwaysIncludeAsChild() ) + { + // nothing to do; + return; + } + + const bool bVisibleChildrenOnly = SwAccessibleChild( GetFrame() ).IsVisibleChildrenOnly(); + const bool bNew = rOldFrame.IsEmpty() || + ( rOldFrame.Left() == 0 && rOldFrame.Top() == 0 ); + if( IsShowing( *(GetMap()), rChildFrameOrObj ) ) + { + // If the object could have existed before, then there is nothing to do, + // because no wrapper exists now and therefore no one is interested to + // get notified of the movement. + if( bNew || (bVisibleChildrenOnly && !IsShowing( rOldFrame )) ) + { + if( rChildFrameOrObj.GetSwFrame() ) + { + // The frame becomes visible. A child event must be send. + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetSwFrame() ); + xAccImpl->ScrolledIn(); + } + else if ( rChildFrameOrObj.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetDrawObject(), + this ); + // #i37790# + if ( xAccImpl.is() ) + { + ScrolledInShape( xAccImpl.get() ); + } + else + { + OSL_FAIL( " - no accessible shape found." ); + } + } + else if ( rChildFrameOrObj.GetWindow() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= rChildFrameOrObj.GetWindow()->GetAccessible(); + FireAccessibleEvent( aEvent ); + } + } + } + else + { + // If the frame was visible before, then a child event for the parent + // needs to be send. However, there is no wrapper existing, and so + // no notifications for grandchildren are required. If the are + // grandgrandchildren, they would be notified by the layout. + if( bVisibleChildrenOnly && + !bNew && IsShowing( rOldFrame ) ) + { + if( rChildFrameOrObj.GetSwFrame() ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetSwFrame() ); + xAccImpl->SetParent( this ); + xAccImpl->Dispose( true ); + } + else if ( rChildFrameOrObj.GetDrawObject() ) + { + ::rtl::Reference< ::accessibility::AccessibleShape > xAccImpl = + GetMap()->GetContextImpl( rChildFrameOrObj.GetDrawObject(), + this ); + DisposeShape( rChildFrameOrObj.GetDrawObject(), + xAccImpl.get() ); + } + else if ( rChildFrameOrObj.GetWindow() ) + { + OSL_FAIL( " - not expected to handle dispose of child of type ." ); + } + } + } +} + +void SwAccessibleContext::InvalidateContent() +{ + SolarMutexGuard aGuard; + + InvalidateContent_( false ); +} + +void SwAccessibleContext::InvalidateCursorPos() +{ + SolarMutexGuard aGuard; + + InvalidateCursorPos_(); +} + +void SwAccessibleContext::InvalidateFocus() +{ + SolarMutexGuard aGuard; + + InvalidateFocus_(); +} + +// #i27301# - use new type definition for <_nStates> +void SwAccessibleContext::InvalidateStates( AccessibleStates _nStates ) +{ + if( GetMap() ) + { + SwViewShell *pVSh = GetMap()->GetShell(); + if( pVSh ) + { + if( _nStates & AccessibleStates::EDITABLE ) + { + bool bIsOldEditableState; + bool bIsNewEditableState = IsEditable( pVSh ); + { + osl::MutexGuard aGuard( m_Mutex ); + bIsOldEditableState = m_isEditableState; + m_isEditableState = bIsNewEditableState; + } + + if( bIsOldEditableState != bIsNewEditableState ) + FireStateChangedEvent( AccessibleStateType::EDITABLE, + bIsNewEditableState ); + } + if( _nStates & AccessibleStates::OPAQUE ) + { + bool bIsOldOpaqueState; + bool bIsNewOpaqueState = IsOpaque( pVSh ); + { + osl::MutexGuard aGuard( m_Mutex ); + bIsOldOpaqueState = m_isOpaqueState; + m_isOpaqueState = bIsNewOpaqueState; + } + + if( bIsOldOpaqueState != bIsNewOpaqueState ) + FireStateChangedEvent( AccessibleStateType::OPAQUE, + bIsNewOpaqueState ); + } + } + + InvalidateChildrenStates( GetFrame(), _nStates ); + } +} + +void SwAccessibleContext::InvalidateRelation( sal_uInt16 nType ) +{ + AccessibleEventObject aEvent; + aEvent.EventId = nType; + + FireAccessibleEvent( aEvent ); +} + +/** #i27301# - text selection has changed */ +void SwAccessibleContext::InvalidateTextSelection() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TEXT_SELECTION_CHANGED; + + FireAccessibleEvent( aEvent ); +} + +/** #i88069# - attributes has changed */ +void SwAccessibleContext::InvalidateAttr() +{ + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TEXT_ATTRIBUTE_CHANGED; + + FireAccessibleEvent( aEvent ); +} + +bool SwAccessibleContext::HasCursor() +{ + return false; +} + +bool SwAccessibleContext::Select( SwPaM *pPaM, SdrObject *pObj, + bool bAdd ) +{ + SwCursorShell* pCursorShell = GetCursorShell(); + if( !pCursorShell ) + return false; + + SwFEShell* pFEShell = dynamic_cast( pCursorShell) != nullptr + ? static_cast( pCursorShell ) + : nullptr; + // Get rid of activated OLE object + if( pFEShell ) + pFEShell->FinishOLEObj(); + + SwWrtShell* pWrtShell = dynamic_cast( pCursorShell) != nullptr + ? static_cast( pCursorShell ) + : nullptr; + + bool bRet = false; + if( pObj ) + { + if( pFEShell ) + { + sal_uInt8 nFlags = bAdd ? SW_ADD_SELECT : 0; + pFEShell->SelectObj( Point(), nFlags, pObj ); + bRet = true; + } + } + else if( pPaM ) + { + // Get rid of frame selection. If there is one, make text cursor + // visible again. + bool bCallShowCursor = false; + if( pFEShell && (pFEShell->IsFrameSelected() || + pFEShell->IsObjSelected()) ) + { + Point aPt( LONG_MIN, LONG_MIN ); + pFEShell->SelectObj( aPt ); + bCallShowCursor = true; + } + pCursorShell->KillPams(); + if( pWrtShell && pPaM->HasMark() ) + // We have to do this or SwWrtShell can't figure out that it needs + // to kill the selection later, when the user moves the cursor. + pWrtShell->SttSelect(); + pCursorShell->SetSelection( *pPaM ); + if( pPaM->HasMark() && *pPaM->GetPoint() == *pPaM->GetMark()) + // Setting a "Selection" that starts and ends at the same spot + // should remove the selection rather than create an empty one, so + // that we get defined behavior if accessibility sets the cursor + // later. + pCursorShell->ClearMark(); + if( bCallShowCursor ) + pCursorShell->ShowCursor(); + bRet = true; + } + + return bRet; +} + +OUString SwAccessibleContext::GetResource(const char* pResId, + const OUString *pArg1, + const OUString *pArg2) +{ + OUString sStr = SwResId(pResId); + + if( pArg1 ) + { + sStr = sStr.replaceFirst( "$(ARG1)", *pArg1 ); + } + if( pArg2 ) + { + sStr = sStr.replaceFirst( "$(ARG2)", *pArg2 ); + } + + return sStr; +} + +void SwAccessibleContext::RemoveFrameFromAccessibleMap() +{ + assert(m_refCount > 0); // must be alive to do this without using m_wMap + if (m_isRegisteredAtAccessibleMap && GetFrame() && GetMap()) + GetMap()->RemoveContext( GetFrame() ); +} + +bool SwAccessibleContext::HasAdditionalAccessibleChildren() +{ + bool bRet( false ); + + if ( GetFrame()->IsTextFrame() ) + { + SwPostItMgr* pPostItMgr = GetMap()->GetShell()->GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + bRet = pPostItMgr->HasFrameConnectedSidebarWins( *(GetFrame()) ); + } + } + + return bRet; +} + +/** #i88070# - get additional accessible child by index */ +vcl::Window* SwAccessibleContext::GetAdditionalAccessibleChild( const sal_Int32 nIndex ) +{ + vcl::Window* pAdditionalAccessibleChild( nullptr ); + + if ( GetFrame()->IsTextFrame() ) + { + SwPostItMgr* pPostItMgr = GetMap()->GetShell()->GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + pAdditionalAccessibleChild = + pPostItMgr->GetSidebarWinForFrameByIndex( *(GetFrame()), nIndex ); + } + } + + return pAdditionalAccessibleChild; +} + +/** #i88070# - get all additional accessible children */ +void SwAccessibleContext::GetAdditionalAccessibleChildren( std::vector< vcl::Window* >* pChildren ) +{ + if ( GetFrame()->IsTextFrame() ) + { + SwPostItMgr* pPostItMgr = GetMap()->GetShell()->GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + pPostItMgr->GetAllSidebarWinForFrame( *(GetFrame()), pChildren ); + } + } +} + +bool SwAccessibleContext::SetSelectedState(bool const bSelected) +{ + if (m_isSelectedInDoc != bSelected) + { + m_isSelectedInDoc = bSelected; + FireStateChangedEvent( AccessibleStateType::SELECTED, bSelected ); + return true; + } + return false; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acccontext.hxx b/sw/source/core/access/acccontext.hxx new file mode 100644 index 000000000..491530558 --- /dev/null +++ b/sw/source/core/access/acccontext.hxx @@ -0,0 +1,360 @@ +/* -*- 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 . + */ + +#pragma once + +#include "accframe.hxx" +#include +#include +#include +#include +#include +#include + +#include + +namespace vcl { class Window; } +class SwCursorShell; +class SdrObject; +class SwPaM; +namespace utl { + class AccessibleStateSetHelper; +} +namespace accessibility { + class AccessibleShape; +} + +const char sAccessibleServiceName[] = "com.sun.star.accessibility.Accessible"; + +class SwAccessibleContext : + public ::cppu::WeakImplHelper< + css::accessibility::XAccessible, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleContext3, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleEventBroadcaster, + css::lang::XServiceInfo + >, + public SwAccessibleFrame +{ + // The implements for the XAccessibleSelection interface has been + // 'externalized' and wants access to the protected members like + // GetMap, GetChild, GetParent, and GetFrame. + friend class SwAccessibleSelectionHelper; +#if OSL_DEBUG_LEVEL > 0 + friend class SwAccessibleMap; +#endif + +protected: + mutable ::osl::Mutex m_Mutex; + +private: + OUString m_sName; // immutable outside constructor + + // The parent if it has been retrieved. This is always an + // SwAccessibleContext. (protected by Mutex) + css::uno::WeakReference < + css::accessibility::XAccessible > m_xWeakParent; + + SwAccessibleMap *m_pMap; // must be protected by solar mutex + /// note: the m_pMap is guaranteed to be valid until we hit the + /// dtor ~SwAccessibleContext, then m_wMap must be checked if it's still + /// alive, after locking SolarMutex (alternatively, Dispose clears m_pMap) + std::weak_ptr m_wMap; + + sal_uInt32 m_nClientId; // client id in the AccessibleEventNotifier queue + sal_Int16 m_nRole; // immutable outside constructor + + // The current states (protected by mutex) + bool m_isShowingState : 1; + bool m_isEditableState : 1; + bool m_isOpaqueState : 1; + bool m_isDefuncState : 1; + + // Are we currently disposing that object (protected by solar mutex)? + bool m_isDisposing : 1; + + // #i85634# - boolean, indicating if the accessible context is + // in general registered at the accessible map. + bool m_isRegisteredAtAccessibleMap; + + void InitStates(); + +protected: + void SetName( const OUString& rName ) { m_sName = rName; } + sal_Int16 GetRole() const + { + return m_nRole; + } + //This flag is used to mark the object's selected state. + bool m_isSelectedInDoc; + void SetParent( SwAccessibleContext *pParent ); + css::uno::Reference< css::accessibility::XAccessible> GetWeakParent() const; + + bool IsDisposing() const { return m_isDisposing; } + + vcl::Window *GetWindow(); + SwAccessibleMap *GetMap() { return m_pMap; } + const SwAccessibleMap *GetMap() const { return m_pMap; } + + /** convenience method to get the SwViewShell through accessibility map */ + SwViewShell* GetShell() + { + return GetMap()->GetShell(); + } + const SwViewShell* GetShell() const + { + return GetMap()->GetShell(); + } + + /** convenience method to get SwCursorShell through accessibility map + * @returns SwCursorShell, or NULL if none is found */ + SwCursorShell* GetCursorShell(); + const SwCursorShell* GetCursorShell() const; + + // Notify all children that the visible area has changed. + // The SwFrame might belong to the current object or to any other child or + // grandchild. + void ChildrenScrolled( const SwFrame *pFrame, const SwRect& rOldVisArea ); + + // The context's showing state changed. May only be called for context that + // exist even if they aren't visible. + void Scrolled( const SwRect& rOldVisArea ); + + // A child has been moved while setting the visible area + void ScrolledWithin( const SwRect& rOldVisArea ); + + // The has been added while setting the visible area + void ScrolledIn(); + + // The context has to be removed while setting the visible area + void ScrolledOut( const SwRect& rOldVisArea ); + + // Invalidate the states of all children of the specified SwFrame. The + // SwFrame might belong the current object or to any child or grandchild! + // #i27301# - use new type definition for <_nStates> + void InvalidateChildrenStates( const SwFrame* _pFrame, + AccessibleStates _nStates ); + + // Dispose children of the specified SwFrame. The SwFrame might belong to + // the current object or to any other child or grandchild. + void DisposeChildren(const SwFrame *pFrame, + bool bRecursive, bool bCanSkipInvisible); + + void DisposeShape( const SdrObject *pObj, + ::accessibility::AccessibleShape *pAccImpl ); + void ScrolledInShape( ::accessibility::AccessibleShape *pAccImpl ); + + virtual void InvalidateContent_( bool bVisibleDataFired ); + + virtual void InvalidateCursorPos_(); + virtual void InvalidateFocus_(); + +public: + void FireAccessibleEvent( css::accessibility::AccessibleEventObject& rEvent ); + +protected: + // broadcast visual data event + void FireVisibleDataEvent(); + + // broadcast state change event + void FireStateChangedEvent( sal_Int16 nState, bool bNewState ); + + // Set states for getAccessibleStateSet. + // This base class sets DEFUNC(0/1), EDITABLE(0/1), ENABLED(1), + // SHOWING(0/1), OPAQUE(0/1) and VISIBLE(1). + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ); + + bool IsEditableState(); + + /// @throws css::uno::RuntimeException + css::awt::Rectangle + getBoundsImpl(bool bRelative); + + // #i85634# + void NotRegisteredAtAccessibleMap() + { + m_isRegisteredAtAccessibleMap = false; + } + void RemoveFrameFromAccessibleMap(); + + void ThrowIfDisposed(); + + virtual ~SwAccessibleContext() override; + + // Return a reference to the parent. + css::uno::Reference< css::accessibility::XAccessible> + getAccessibleParentImpl(); + +public: + SwAccessibleContext( std::shared_ptr const& pMap, + sal_Int16 nRole, const SwFrame *pFrame ); + + // XAccessible + + // Return the XAccessibleContext. + virtual css::uno::Reference< css::accessibility::XAccessibleContext> SAL_CALL + getAccessibleContext() override; + + // XAccessibleContext + + // Return the number of currently visible children. + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + // Return the specified child or NULL if index is invalid. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + virtual css::uno::Sequence> SAL_CALL + getAccessibleChildren() override; + + // Return a reference to the parent. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleParent() override; + + // Return this objects index among the parents children. + virtual sal_Int32 SAL_CALL + getAccessibleIndexInParent() override; + + // Return this object's role. + virtual sal_Int16 SAL_CALL + getAccessibleRole() override; + + // getAccessibleDescription() is abstract + + // Return the object's current name. + virtual OUString SAL_CALL + getAccessibleName() override; + + // Return NULL to indicate that an empty relation set. + virtual css::uno::Reference< + css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; + + // Return the set of current states. + virtual css::uno::Reference< + css::accessibility::XAccessibleStateSet> SAL_CALL + getAccessibleStateSet() override; + + /** Return the parents locale or throw exception if this object has no + parent yet/anymore. */ + virtual css::lang::Locale SAL_CALL + getLocale() override; + + // XAccessibleEventBroadcaster + + virtual void SAL_CALL addAccessibleEventListener( + const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( + const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( + const css::awt::Point& aPoint ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( + const css::awt::Point& aPoint ) override; + + virtual css::awt::Rectangle SAL_CALL getBounds() override; + + virtual css::awt::Point SAL_CALL getLocation() override; + + virtual css::awt::Point SAL_CALL getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL getSize() override; + + virtual void SAL_CALL grabFocus() override; + + virtual sal_Int32 SAL_CALL getForeground() override; + virtual sal_Int32 SAL_CALL getBackground() override; + + // XServiceInfo + + // getImplementationName() and getSupportedServiceNames are abstract + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // thread safe C++ interface + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true); + + // The child object is not visible any longer and should be destroyed + virtual void DisposeChild(const sw::access::SwAccessibleChild& rFrameOrObj, bool bRecursive, bool bCanSkipInvisible); + + // The object has been moved by the layout + virtual void InvalidatePosOrSize( const SwRect& rFrame ); + + // The child object has been moved by the layout + virtual void InvalidateChildPosOrSize( const sw::access::SwAccessibleChild& rFrameOrObj, + const SwRect& rFrame ); + + // The content may have changed (but it hasn't to have changed) + void InvalidateContent(); + + // The caretPos has changed + void InvalidateCursorPos(); + + // The Focus state has changed + void InvalidateFocus(); + + // Check states + // #i27301# - use new type definition for <_nStates> + void InvalidateStates( AccessibleStates _nStates ); + + // the XAccessibleRelationSet may have changed + void InvalidateRelation( sal_uInt16 nType ); + + void InvalidateTextSelection(); // #i27301# - text selection has changed + void InvalidateAttr(); // #i88069# - attributes has changed + + bool HasAdditionalAccessibleChildren(); + + // #i88070# - get additional child by index + vcl::Window* GetAdditionalAccessibleChild( const sal_Int32 nIndex ); + + // #i88070# - get all additional accessible children + void GetAdditionalAccessibleChildren( std::vector< vcl::Window* >* pChildren ); + + const OUString& GetName() const { return m_sName; } + + virtual bool HasCursor(); // required by map to remember that object + + bool Select( SwPaM *pPaM, SdrObject *pObj, bool bAdd ); + bool Select( SwPaM& rPaM ) + { + return Select( &rPaM, nullptr, false ); + } + bool Select( SdrObject *pObj, bool bAdd ) + { + return Select( nullptr, pObj, bAdd ); + } + + //This method is used to update the selected state and fire the selected state changed event. + virtual bool SetSelectedState(bool bSeleted); + bool IsSeletedInDoc() const { return m_isSelectedInDoc; } + + static OUString GetResource(const char* pResId, + const OUString *pArg1 = nullptr, + const OUString *pArg2 = nullptr); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accdoc.cxx b/sw/source/core/access/accdoc.cxx new file mode 100644 index 000000000..bf2c0e844 --- /dev/null +++ b/sw/source/core/access/accdoc.cxx @@ -0,0 +1,719 @@ +/* -*- 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 "accdoc.hxx" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +const char sServiceName[] = "com.sun.star.text.AccessibleTextDocumentView"; +const char sImplementationName[] = "com.sun.star.comp.Writer.SwAccessibleDocumentView"; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +using lang::IndexOutOfBoundsException; + +// SwAccessibleDocumentBase: base class for SwAccessibleDocument and +// SwAccessiblePreview + +SwAccessibleDocumentBase::SwAccessibleDocumentBase( + std::shared_ptr const& pMap) + : SwAccessibleContext(pMap, AccessibleRole::DOCUMENT_TEXT, + pMap->GetShell()->GetLayout()) + , mxParent(pMap->GetShell()->GetWin()->GetAccessibleParentWindow()->GetAccessible()) + , mpChildWin(nullptr) +{ +} + +SwAccessibleDocumentBase::~SwAccessibleDocumentBase() +{ +} + +void SwAccessibleDocumentBase::SetVisArea() +{ + SolarMutexGuard aGuard; + + SwRect aOldVisArea( GetVisArea() ); + const SwRect& rNewVisArea = GetMap()->GetVisArea(); + if( aOldVisArea != rNewVisArea ) + { + SwAccessibleFrame::SetVisArea( GetMap()->GetVisArea() ); + // #i58139# - showing state of document view needs also be updated. + // Thus, call method instead of + // ChildrenScrolled( GetFrame(), aOldVisArea ); + Scrolled( aOldVisArea ); + } +} + +void SwAccessibleDocumentBase::AddChild( vcl::Window *pWin, bool bFireEvent ) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( !mpChildWin, "only one child window is supported" ); + if( !mpChildWin ) + { + mpChildWin = pWin; + + if( bFireEvent ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= mpChildWin->GetAccessible(); + FireAccessibleEvent( aEvent ); + } + } +} + +void SwAccessibleDocumentBase::RemoveChild( vcl::Window *pWin ) +{ + SolarMutexGuard aGuard; + + OSL_ENSURE( !mpChildWin || pWin == mpChildWin, "invalid child window to remove" ); + if( mpChildWin && pWin == mpChildWin ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.OldValue <<= mpChildWin->GetAccessible(); + FireAccessibleEvent( aEvent ); + + mpChildWin = nullptr; + } +} + +sal_Int32 SAL_CALL SwAccessibleDocumentBase::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + + // ThrowIfDisposed is called by parent + + sal_Int32 nChildren = SwAccessibleContext::getAccessibleChildCount(); + if( !IsDisposing() && mpChildWin ) + nChildren++; + + return nChildren; +} + +uno::Reference< XAccessible> SAL_CALL + SwAccessibleDocumentBase::getAccessibleChild( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + if( mpChildWin ) + { + ThrowIfDisposed(); + + if ( nIndex == GetChildCount( *(GetMap()) ) ) + { + return mpChildWin->GetAccessible(); + } + } + + return SwAccessibleContext::getAccessibleChild( nIndex ); +} + +uno::Reference< XAccessible> SAL_CALL SwAccessibleDocumentBase::getAccessibleParent() +{ + return mxParent; +} + +sal_Int32 SAL_CALL SwAccessibleDocumentBase::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + + uno::Reference < XAccessibleContext > xAcc( mxParent->getAccessibleContext() ); + uno::Reference < XAccessible > xThis( this ); + sal_Int32 nCount = xAcc->getAccessibleChildCount(); + + for( sal_Int32 i=0; i < nCount; i++ ) + { + try + { + if( xAcc->getAccessibleChild( i ) == xThis ) + return i; + } + catch(const css::lang::IndexOutOfBoundsException &) + { + return -1; + } + } + return -1; +} + +OUString SAL_CALL SwAccessibleDocumentBase::getAccessibleDescription() +{ + return GetResource( STR_ACCESS_DOC_DESC ); +} + +OUString SAL_CALL SwAccessibleDocumentBase::getAccessibleName() +{ + SolarMutexGuard g; + + OUString sAccName = GetResource( STR_ACCESS_DOC_WORDPROCESSING ); + SwDoc *pDoc = GetMap() ? GetShell()->GetDoc() : nullptr; + if ( pDoc ) + { + OUString sFileName = pDoc->getDocAccTitle(); + if ( sFileName.isEmpty() ) + { + SwDocShell* pDocSh = pDoc->GetDocShell(); + if ( pDocSh ) + { + sFileName = pDocSh->GetTitle( SFX_TITLE_APINAME ); + } + } + + if ( !sFileName.isEmpty() ) + { + sAccName = sFileName + " - " + sAccName; + } + } + + return sAccName; +} + +awt::Rectangle SAL_CALL SwAccessibleDocumentBase::getBounds() +{ + try + { + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + tools::Rectangle aPixBounds( pWin->GetWindowExtentsRelative( pWin->GetAccessibleParentWindow() ) ); + awt::Rectangle aBox( aPixBounds.Left(), aPixBounds.Top(), + aPixBounds.GetWidth(), aPixBounds.GetHeight() ); + + return aBox; + } + catch(const css::lang::IndexOutOfBoundsException &) + { + return awt::Rectangle(); + } +} + +awt::Point SAL_CALL SwAccessibleDocumentBase::getLocation() +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + Point aPixPos( pWin->GetWindowExtentsRelative( pWin->GetAccessibleParentWindow() ).TopLeft() ); + awt::Point aLoc( aPixPos.getX(), aPixPos.getY() ); + + return aLoc; +} + +css::awt::Point SAL_CALL SwAccessibleDocumentBase::getLocationOnScreen() +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + Point aPixPos( pWin->GetWindowExtentsRelative( nullptr ).TopLeft() ); + awt::Point aLoc( aPixPos.getX(), aPixPos.getY() ); + + return aLoc; +} + +css::awt::Size SAL_CALL SwAccessibleDocumentBase::getSize() +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + Size aPixSize( pWin->GetWindowExtentsRelative( nullptr ).GetSize() ); + awt::Size aSize( aPixSize.Width(), aPixSize.Height() ); + + return aSize; +} + +sal_Bool SAL_CALL SwAccessibleDocumentBase::containsPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + tools::Rectangle aPixBounds( pWin->GetWindowExtentsRelative( nullptr ) ); + aPixBounds.Move(-aPixBounds.Left(), -aPixBounds.Top()); + + Point aPixPoint( aPoint.X, aPoint.Y ); + return aPixBounds.IsInside( aPixPoint ); +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleDocumentBase::getAccessibleAtPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + if( mpChildWin ) + { + ThrowIfDisposed(); + + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + Point aPixPoint( aPoint.X, aPoint.Y ); // px rel to window + if( mpChildWin->GetWindowExtentsRelative( pWin ).IsInside( aPixPoint ) ) + return mpChildWin->GetAccessible(); + } + + return SwAccessibleContext::getAccessibleAtPoint( aPoint ); +} + +// SwAccessibleDocument + +void SwAccessibleDocument::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // MULTISELECTABLE + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); +} + +SwAccessibleDocument::SwAccessibleDocument( + std::shared_ptr const& pInitMap) + : SwAccessibleDocumentBase(pInitMap) + , maSelectionHelper(*this) +{ + SetName(pInitMap->GetDocName()); + vcl::Window *pWin = pInitMap->GetShell()->GetWin(); + if( pWin ) + { + pWin->AddChildEventListener( LINK( this, SwAccessibleDocument, WindowChildEventListener )); + sal_uInt16 nCount = pWin->GetChildCount(); + for( sal_uInt16 i=0; i < nCount; i++ ) + { + vcl::Window* pChildWin = pWin->GetChild( i ); + if( pChildWin && + AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + AddChild( pChildWin, false ); + } + } +} + +SwAccessibleDocument::~SwAccessibleDocument() +{ + vcl::Window *pWin = GetMap() ? GetMap()->GetShell()->GetWin() : nullptr; + if( pWin ) + pWin->RemoveChildEventListener( LINK( this, SwAccessibleDocument, WindowChildEventListener )); +} + +void SwAccessibleDocument::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + OSL_ENSURE( GetFrame() && GetMap(), "already disposed" ); + + vcl::Window *pWin = GetMap() ? GetMap()->GetShell()->GetWin() : nullptr; + if( pWin ) + pWin->RemoveChildEventListener( LINK( this, SwAccessibleDocument, WindowChildEventListener )); + SwAccessibleContext::Dispose(bRecursive, bCanSkipInvisible); +} + +IMPL_LINK( SwAccessibleDocument, WindowChildEventListener, VclWindowEvent&, rEvent, void ) +{ + OSL_ENSURE( rEvent.GetWindow(), "Window???" ); + switch ( rEvent.GetId() ) + { + case VclEventId::WindowShow: // send create on show for direct accessible children + { + vcl::Window* pChildWin = static_cast< vcl::Window* >( rEvent.GetData() ); + if( pChildWin && AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + { + AddChild( pChildWin ); + } + } + break; + case VclEventId::WindowHide: // send destroy on hide for direct accessible children + { + vcl::Window* pChildWin = static_cast< vcl::Window* >( rEvent.GetData() ); + if( pChildWin && AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + { + RemoveChild( pChildWin ); + } + } + break; + case VclEventId::ObjectDying: // send destroy on hide for direct accessible children + { + vcl::Window* pChildWin = rEvent.GetWindow(); + if( pChildWin && AccessibleRole::EMBEDDED_OBJECT == pChildWin->GetAccessibleRole() ) + { + RemoveChild( pChildWin ); + } + } + break; + default: break; + } +} + +OUString SAL_CALL SwAccessibleDocument::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleDocument::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleDocument::getSupportedServiceNames() +{ + return { sServiceName, sAccessibleServiceName }; +} + +// XInterface + +uno::Any SwAccessibleDocument::queryInterface( + const uno::Type& rType ) +{ + uno::Any aRet; + if ( rType == cppu::UnoType::get() ) + { + uno::Reference aSelect = this; + aRet <<= aSelect; + } + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAttribute = this; + aRet <<= aAttribute; + } + else + aRet = SwAccessibleContext::queryInterface( rType ); + return aRet; +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleDocument::getTypes() +{ + return cppu::OTypeCollection( + cppu::UnoType::get(), + SwAccessibleDocumentBase::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleDocument::getImplementationId() +{ + return css::uno::Sequence(); +} + +// XAccessibleSelection + +void SwAccessibleDocument::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + maSelectionHelper.selectAccessibleChild(nChildIndex); +} + +sal_Bool SwAccessibleDocument::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + return maSelectionHelper.isAccessibleChildSelected(nChildIndex); +} + +void SwAccessibleDocument::clearAccessibleSelection( ) +{ +} + +void SwAccessibleDocument::selectAllAccessibleChildren( ) +{ + maSelectionHelper.selectAllAccessibleChildren(); +} + +sal_Int32 SwAccessibleDocument::getSelectedAccessibleChildCount( ) +{ + return maSelectionHelper.getSelectedAccessibleChildCount(); +} + +uno::Reference SwAccessibleDocument::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + return maSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex); +} + +// index has to be treated as global child index. +void SwAccessibleDocument::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + maSelectionHelper.deselectAccessibleChild( nChildIndex ); +} + +uno::Any SAL_CALL SwAccessibleDocument::getExtendedAttributes() +{ + SolarMutexGuard g; + + uno::Any anyAtrribute; + SwDoc *pDoc = GetMap() ? GetShell()->GetDoc() : nullptr; + + if (!pDoc) + return anyAtrribute; + SwCursorShell* pCursorShell = GetCursorShell(); + if( !pCursorShell ) + return anyAtrribute; + + SwFEShell* pFEShell = dynamic_cast( pCursorShell) != nullptr + ? static_cast( pCursorShell ) + : nullptr; + OUString sValue; + sal_uInt16 nPage, nLogPage; + OUString sDisplay; + + if( pFEShell ) + { + pFEShell->GetPageNumber(-1,true,nPage,nLogPage,sDisplay); + + sValue = "page-name:" + sDisplay + + ";page-number:" + + OUString::number( nPage ) + + ";total-pages:" + + OUString::number( pCursorShell->GetPageCnt() ) + ";"; + + SwContentFrame* pCurrFrame = pCursorShell->GetCurrFrame(); + SwPageFrame* pCurrPage=static_cast(pCurrFrame)->FindPageFrame(); + sal_uLong nLineNum = 0; + SwTextFrame* pTextFrame = nullptr; + SwTextFrame* pCurrTextFrame = nullptr; + pTextFrame = static_cast< SwTextFrame* >(pCurrPage->ContainsContent()); + if (pCurrFrame->IsInFly())//such as, graphic,chart + { + SwFlyFrame *pFlyFrame = pCurrFrame->FindFlyFrame(); + const SwFormatAnchor& rAnchor = pFlyFrame->GetFormat()->GetAnchor(); + RndStdIds eAnchorId = rAnchor.GetAnchorId(); + if(eAnchorId == RndStdIds::FLY_AS_CHAR) + { + const SwFrame *pSwFrame = pFlyFrame->GetAnchorFrame(); + if(pSwFrame->IsTextFrame()) + pCurrTextFrame = const_cast(static_cast(pSwFrame)); + } + } + else + { + assert(dynamic_cast(pCurrFrame)); + pCurrTextFrame = static_cast(pCurrFrame); + } + //check whether the text frame where the Graph/OLE/Frame anchored is in the Header/Footer + SwFrame* pFrame = pCurrTextFrame; + while ( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) + pFrame = pFrame->GetUpper(); + if ( pFrame ) + pCurrTextFrame = nullptr; + //check shape + if(pCursorShell->Imp()->GetDrawView()) + { + const SdrMarkList &rMrkList = pCursorShell->Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark(i)->GetMarkedSdrObj(); + SwFrameFormat* pFormat = static_cast(pObj->GetUserCall())->GetFormat(); + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if( RndStdIds::FLY_AS_CHAR != rAnchor.GetAnchorId() ) + pCurrTextFrame = nullptr; + } + } + //calculate line number + if (pCurrTextFrame && pTextFrame) + { + if (!(pCurrTextFrame->IsInTab() || pCurrTextFrame->IsInFootnote())) + { + while( pTextFrame != pCurrTextFrame ) + { + //check header/footer + pFrame = pTextFrame; + while ( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) + pFrame = pFrame->GetUpper(); + if ( pFrame ) + { + pTextFrame = static_cast< SwTextFrame*>(pTextFrame->GetNextContentFrame()); + continue; + } + if (!(pTextFrame->IsInTab() || pTextFrame->IsInFootnote() || pTextFrame->IsInFly())) + nLineNum += pTextFrame->GetThisLines(); + pTextFrame = static_cast< SwTextFrame* >(pTextFrame ->GetNextContentFrame()); + } + SwPaM* pCaret = pCursorShell->GetCursor(); + if (!pCurrTextFrame->IsEmpty() && pCaret) + { + assert(pCurrTextFrame->IsTextFrame()); + const SwPosition* pPoint = nullptr; + if (pCurrTextFrame->IsInFly()) + { + SwFlyFrame *pFlyFrame = pCurrTextFrame->FindFlyFrame(); + const SwFormatAnchor& rAnchor = pFlyFrame->GetFormat()->GetAnchor(); + pPoint = rAnchor.GetContentAnchor(); + SwContentNode *const pNode(pPoint->nNode.GetNode().GetContentNode()); + pCurrTextFrame = pNode + ? static_cast(pNode->getLayoutFrame( + pCurrTextFrame->getRootFrame(), pPoint)) + : nullptr; + } + else + pPoint = pCaret->GetPoint(); + if (pCurrTextFrame) + { + TextFrameIndex const nActPos(pCurrTextFrame->MapModelToViewPos(*pPoint)); + nLineNum += pCurrTextFrame->GetLineCount( nActPos ); + } + } + else + ++nLineNum; + } + } + + sValue += "line-number:" + OUString::number( nLineNum ) + ";"; + + SwFrame* pCurrCol=static_cast(pCurrFrame)->FindColFrame(); + + sValue += "column-number:"; + + int nCurrCol = 1; + if(pCurrCol!=nullptr) + { + //SwLayoutFrame* pParent = pCurrCol->GetUpper(); + SwFrame* pCurrPageCol=static_cast(pCurrFrame)->FindColFrame(); + while(pCurrPageCol && pCurrPageCol->GetUpper() && pCurrPageCol->GetUpper()->IsPageFrame()) + { + pCurrPageCol = pCurrPageCol->GetUpper(); + } + + SwLayoutFrame* pParent = pCurrPageCol->GetUpper(); + + if(pParent!=nullptr) + { + SwFrame* pCol = pParent->Lower(); + while(pCol&&(pCol!=pCurrPageCol)) + { + pCol = pCol->GetNext(); + ++nCurrCol; + } + } + } + sValue += OUString::number( nCurrCol ) + ";"; + + const SwFormatCol &rFormatCol=pCurrPage->GetAttrSet()->GetCol(); + sal_uInt16 nColCount=rFormatCol.GetNumCols(); + nColCount = nColCount>0?nColCount:1; + sValue += "total-columns:" + OUString::number( nColCount ) + ";"; + + SwSectionFrame* pCurrSctFrame=static_cast(pCurrFrame)->FindSctFrame(); + if(pCurrSctFrame!=nullptr && pCurrSctFrame->GetSection()!=nullptr ) + { + OUString sectionName = pCurrSctFrame->GetSection()->GetSectionName(); + + sectionName = sectionName.replaceFirst( "\\" , "\\\\" ); + sectionName = sectionName.replaceFirst( "=" , "\\=" ); + sectionName = sectionName.replaceFirst( ";" , "\\;" ); + sectionName = sectionName.replaceFirst( "," , "\\," ); + sectionName = sectionName.replaceFirst( ":" , "\\:" ); + + sValue += "section-name:" + sectionName + ";"; + + //section-columns-number + + nCurrCol = 1; + + if(pCurrCol!=nullptr) + { + SwLayoutFrame* pParent = pCurrCol->GetUpper(); + if(pParent!=nullptr) + { + SwFrame* pCol = pParent->Lower(); + while(pCol&&(pCol!=pCurrCol)) + { + pCol = pCol->GetNext(); + nCurrCol +=1; + } + } + } + sValue += "section-columns-number:" + + OUString::number( nCurrCol ) + ";"; + + //section-total-columns + const SwFormatCol &rFormatSctCol=pCurrSctFrame->GetAttrSet()->GetCol(); + sal_uInt16 nSctColCount=rFormatSctCol.GetNumCols(); + nSctColCount = nSctColCount>0?nSctColCount:1; + sValue += "section-total-columns:" + + OUString::number( nSctColCount ) + ";"; + } + + anyAtrribute <<= sValue; + } + return anyAtrribute; +} + +sal_Int32 SAL_CALL SwAccessibleDocument::getBackground() +{ + SolarMutexGuard aGuard; + return sal_Int32(SW_MOD()->GetColorConfig().GetColorValue( ::svtools::DOCCOLOR ).nColor); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accdoc.hxx b/sw/source/core/access/accdoc.hxx new file mode 100644 index 000000000..452a62a33 --- /dev/null +++ b/sw/source/core/access/accdoc.hxx @@ -0,0 +1,172 @@ +/* -*- 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 . + */ + +#pragma once + +#include "acccontext.hxx" +#include +#include +#include "accselectionhelper.hxx" + +// base class for SwAccessibleDocument (in this same header file) and +// SwAccessiblePreview +class SwAccessibleDocumentBase : public SwAccessibleContext +{ + css::uno::Reference< css::accessibility::XAccessible> mxParent; + + VclPtr mpChildWin; // protected by solar mutex + + using SwAccessibleFrame::SetVisArea; + +protected: + virtual ~SwAccessibleDocumentBase() override; + +public: + SwAccessibleDocumentBase(std::shared_ptr const& pInitMap); + + void SetVisArea(); + + void AddChild( vcl::Window *pWin, bool bFireEvent = true ); + void RemoveChild( vcl::Window *pWin ); + + // XAccessibleContext + + // Return the number of currently visible children. + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + // Return the specified child or NULL if index is invalid. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + // Return a reference to the parent. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleParent() override; + + // Return this objects index among the parents children. + virtual sal_Int32 SAL_CALL + getAccessibleIndexInParent() override; + + // Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + virtual OUString SAL_CALL getAccessibleName() override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( + const css::awt::Point& aPoint ) override; + + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( + const css::awt::Point& aPoint ) override; + + virtual css::awt::Rectangle SAL_CALL getBounds() override; + + virtual css::awt::Point SAL_CALL getLocation() override; + + virtual css::awt::Point SAL_CALL getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL getSize() override; +}; + +/** + * access to an accessible Writer document + */ +class SwAccessibleDocument : public SwAccessibleDocumentBase, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleExtendedAttributes +{ + // Implementation for XAccessibleSelection interface + SwAccessibleSelectionHelper maSelectionHelper; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets MULTISELECTABLE(1) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual ~SwAccessibleDocument() override; + +public: + SwAccessibleDocument(std::shared_ptr const& pInitMap); + + DECL_LINK( WindowChildEventListener, VclWindowEvent&, void ); + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XInterface + + // XInterface is inherited through SwAccessibleContext and + // XAccessibleSelection. These methods are needed to avoid + // ambiguities. + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) throw () override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) throw () override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleSelection + + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // index has to be treated as global child index. + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual css::uno::Any SAL_CALL getExtendedAttributes() override; + + // thread safe C++ interface + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accembedded.cxx b/sw/source/core/access/accembedded.cxx new file mode 100644 index 000000000..58d9a7672 --- /dev/null +++ b/sw/source/core/access/accembedded.cxx @@ -0,0 +1,121 @@ +/* -*- 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 "accembedded.hxx" +#include +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +const char sImplementationName[] = "com.sun.star.comp.Writer.SwAccessibleEmbeddedObject"; + +SwAccessibleEmbeddedObject::SwAccessibleEmbeddedObject( + std::shared_ptr const& pInitMap, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleNoTextFrame( pInitMap, AccessibleRole::EMBEDDED_OBJECT, pFlyFrame ) +{ +} + +SwAccessibleEmbeddedObject::~SwAccessibleEmbeddedObject() +{ +} + +// XInterface +css::uno::Any SAL_CALL + SwAccessibleEmbeddedObject::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = SwAccessibleNoTextFrame::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast< css::accessibility::XAccessibleExtendedAttributes* >(this) ); + return aReturn; +} + +void SAL_CALL + SwAccessibleEmbeddedObject::acquire() + throw () +{ + SwAccessibleNoTextFrame::acquire (); +} + +void SAL_CALL + SwAccessibleEmbeddedObject::release() + throw () +{ + SwAccessibleNoTextFrame::release (); +} + +OUString SAL_CALL SwAccessibleEmbeddedObject::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleEmbeddedObject::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleEmbeddedObject::getSupportedServiceNames() +{ + return { "com.sun.star.text.AccessibleTextEmbeddedObject", sAccessibleServiceName }; +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleEmbeddedObject::getImplementationId() +{ + return css::uno::Sequence(); +} + +// XAccessibleExtendedAttributes +css::uno::Any SAL_CALL SwAccessibleEmbeddedObject::getExtendedAttributes() +{ + SolarMutexGuard g; + + css::uno::Any strRet; + OUString style; + SwFlyFrame* pFFrame = getFlyFrame(); + + if( pFFrame ) + { + style = "style:"; + SwContentFrame* pCFrame; + pCFrame = pFFrame->ContainsContent(); + if( pCFrame ) + { + assert(pCFrame->IsNoTextFrame()); + SwContentNode *const pCNode = static_cast(pCFrame)->GetNode(); + if( pCNode ) + { + style += static_cast(pCNode)->GetOLEObj().GetStyleString(); + } + } + style += ";"; + } + strRet <<= style; + return strRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accembedded.hxx b/sw/source/core/access/accembedded.hxx new file mode 100644 index 000000000..86fdcc9cd --- /dev/null +++ b/sw/source/core/access/accembedded.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ + +#pragma once + +#include "accnotextframe.hxx" + +#include + +class SwAccessibleEmbeddedObject : public SwAccessibleNoTextFrame + , public css::accessibility::XAccessibleExtendedAttributes + +{ +protected: + virtual ~SwAccessibleEmbeddedObject() override; + +public: + SwAccessibleEmbeddedObject(std::shared_ptr const& pInitMap, + const SwFlyFrame* pFlyFrame ); + + // XInterface + + virtual css::uno::Any SAL_CALL + queryInterface (const css::uno::Type & rType) override; + + virtual void SAL_CALL + acquire() + throw () override; + + virtual void SAL_CALL + release() + throw () override; + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleExtendedAttributes + virtual css::uno::Any SAL_CALL getExtendedAttributes() override ; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfootnote.cxx b/sw/source/core/access/accfootnote.cxx new file mode 100644 index 000000000..eb843ca36 --- /dev/null +++ b/sw/source/core/access/accfootnote.cxx @@ -0,0 +1,118 @@ +/* -*- 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 "accfootnote.hxx" +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +const char sImplementationNameFootnote[] = "com.sun.star.comp.Writer.SwAccessibleFootnoteView"; +const char sImplementationNameEndnote[] = "com.sun.star.comp.Writer.SwAccessibleEndnoteView"; + +SwAccessibleFootnote::SwAccessibleFootnote( + std::shared_ptr const& pInitMap, + bool bIsEndnote, + const SwFootnoteFrame *pFootnoteFrame ) : + SwAccessibleContext( pInitMap, + bIsEndnote ? AccessibleRole::END_NOTE : AccessibleRole::FOOTNOTE, + pFootnoteFrame ) +{ + const char* pResId = bIsEndnote ? STR_ACCESS_ENDNOTE_NAME + : STR_ACCESS_FOOTNOTE_NAME; + + OUString sArg; + const SwTextFootnote *pTextFootnote = + static_cast< const SwFootnoteFrame *>( GetFrame() )->GetAttr(); + if( pTextFootnote ) + { + const SwDoc *pDoc = GetShell()->GetDoc(); + sArg = pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, pFootnoteFrame->getRootFrame()); + } + + SetName(GetResource(pResId, &sArg)); +} + +SwAccessibleFootnote::~SwAccessibleFootnote() +{ +} + +OUString SAL_CALL SwAccessibleFootnote::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const char* pResId = AccessibleRole::END_NOTE == GetRole() + ? STR_ACCESS_ENDNOTE_DESC + : STR_ACCESS_FOOTNOTE_DESC ; + + OUString sArg; + const SwTextFootnote *pTextFootnote = + static_cast< const SwFootnoteFrame *>( GetFrame() )->GetAttr(); + if( pTextFootnote ) + { + const SwDoc *pDoc = GetMap()->GetShell()->GetDoc(); + sArg = pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, GetFrame()->getRootFrame()); + } + + return GetResource(pResId, &sArg); +} + +OUString SAL_CALL SwAccessibleFootnote::getImplementationName() +{ + if( AccessibleRole::END_NOTE == GetRole() ) + return sImplementationNameEndnote; + else + return sImplementationNameFootnote; +} + +sal_Bool SAL_CALL SwAccessibleFootnote::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +Sequence< OUString > SAL_CALL SwAccessibleFootnote::getSupportedServiceNames() +{ + return { (AccessibleRole::END_NOTE == GetRole())?OUString("com.sun.star.text.AccessibleEndnoteView"):OUString("com.sun.star.text.AccessibleFootnoteView"), + sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessibleFootnote::getImplementationId() +{ + return css::uno::Sequence(); +} + +bool SwAccessibleFootnote::IsEndnote( const SwFootnoteFrame *pFootnoteFrame ) +{ + const SwTextFootnote *pTextFootnote = pFootnoteFrame ->GetAttr(); + return pTextFootnote && pTextFootnote->GetFootnote().IsEndNote() ; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfootnote.hxx b/sw/source/core/access/accfootnote.hxx new file mode 100644 index 000000000..ecc852cd5 --- /dev/null +++ b/sw/source/core/access/accfootnote.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include +#include "acccontext.hxx" + +class SwAccessibleMap; +class SwFootnoteFrame; + +class SwAccessibleFootnote : public SwAccessibleContext +{ +protected: + virtual ~SwAccessibleFootnote() override; + +public: + SwAccessibleFootnote( std::shared_ptr const& pInitMap, + bool bIsEndnote, + const SwFootnoteFrame *pFootnoteFrame ); + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + static bool IsEndnote( const SwFootnoteFrame *pFrame ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframe.cxx b/sw/source/core/access/accframe.cxx new file mode 100644 index 000000000..a3ece547a --- /dev/null +++ b/sw/source/core/access/accframe.cxx @@ -0,0 +1,479 @@ +/* -*- 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 "accfrmobjslist.hxx" +#include "accfrmobjmap.hxx" +#include "accframe.hxx" + +using namespace sw::access; + +// Regarding visibility (or in terms of accessibility: regarding the showing +// state): A frame is visible and therefore contained in the tree if its frame +// size overlaps with the visible area. The bounding box however is the +// frame's paint area. +sal_Int32 SwAccessibleFrame::GetChildCount( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame *pFrame, + bool bInPagePreview ) +{ + sal_Int32 nCount = 0; + + const SwAccessibleChildSList aVisList( rVisArea, *pFrame, rAccMap ); + + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + nCount++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + nCount += GetChildCount( rAccMap, + rVisArea, rLower.GetSwFrame(), + bInPagePreview ); + } + ++aIter; + } + + return nCount; +} + +SwAccessibleChild SwAccessibleFrame::GetChild( + SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + sal_Int32& rPos, + bool bInPagePreview ) +{ + SwAccessibleChild aRet; + + if( rPos >= 0 ) + { + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_iterator aIter( aVisMap.cbegin() ); + while( aIter != aVisMap.cend() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = (*aIter).second; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( 0 == rPos ) + aRet = rLower; + else + rPos--; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChild( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rPos, + bInPagePreview ); + } + ++aIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( 0 == rPos ) + aRet = rLower; + else + rPos--; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChild( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rPos, + bInPagePreview ); + } + ++aIter; + } + } + } + + return aRet; +} + +bool SwAccessibleFrame::GetChildIndex( + SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + const SwAccessibleChild& rChild, + sal_Int32& rPos, + bool bInPagePreview ) +{ + bool bFound = false; + + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_iterator aIter( aVisMap.cbegin() ); + while( aIter != aVisMap.cend() && !bFound ) + { + const SwAccessibleChild& rLower = (*aIter).second; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( rChild == rLower ) + bFound = true; + else + rPos++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + bFound = GetChildIndex( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rChild, + rPos, bInPagePreview ); + } + ++aIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. + + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() && !bFound ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + if( rChild == rLower ) + bFound = true; + else + rPos++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + bFound = GetChildIndex( rAccMap, + rVisArea, *(rLower.GetSwFrame()), rChild, + rPos, bInPagePreview ); + } + ++aIter; + } + } + + return bFound; +} + +SwAccessibleChild SwAccessibleFrame::GetChildAtPixel( const SwRect& rVisArea, + const SwFrame& rFrame, + const Point& rPixPos, + bool bInPagePreview, + SwAccessibleMap& rAccMap ) +{ + SwAccessibleChild aRet; + + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here, and we have to reverse iterate, + // because objects in front should be returned. + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_reverse_iterator aRIter( aVisMap.crbegin() ); + while( aRIter != aVisMap.crend() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = (*aRIter).second; + // A frame is returned if it's frame size is inside the visarea + // and the position is inside the frame's paint area. + if( rLower.IsAccessible( bInPagePreview ) ) + { + SwRect aLogBounds( rLower.GetBounds( rAccMap ) ); + if( !aLogBounds.IsEmpty() ) + { + tools::Rectangle aPixBounds( rAccMap.CoreToPixel( aLogBounds.SVRect() ) ); + if( aPixBounds.IsInside( rPixPos ) ) + aRet = rLower; + } + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChildAtPixel( rVisArea, *(rLower.GetSwFrame()), rPixPos, + bInPagePreview, rAccMap ); + } + ++aRIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. Moreover, we can iterate forward, + // because the lowers don't overlap! + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() && !aRet.IsValid() ) + { + const SwAccessibleChild& rLower = *aIter; + // A frame is returned if it's frame size is inside the visarea + // and the position is inside the frame's paint area. + if( rLower.IsAccessible( bInPagePreview ) ) + { + SwRect aLogBounds( rLower.GetBounds( rAccMap ) ); + if( !aLogBounds.IsEmpty() ) + { + tools::Rectangle aPixBounds( rAccMap.CoreToPixel( aLogBounds.SVRect() ) ); + if( aPixBounds.IsInside( rPixPos ) ) + aRet = rLower; + } + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + aRet = GetChildAtPixel( rVisArea, *(rLower.GetSwFrame()), rPixPos, + bInPagePreview, rAccMap ); + } + ++aIter; + } + } + + return aRet; +} + +void SwAccessibleFrame::GetChildren( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + std::list< SwAccessibleChild >& rChildren, + bool bInPagePreview ) +{ + if( SwAccessibleChildMap::IsSortingRequired( rFrame ) ) + { + // We need a sorted list here + const SwAccessibleChildMap aVisMap( rVisArea, rFrame, rAccMap ); + SwAccessibleChildMap::const_iterator aIter( aVisMap.cbegin() ); + while( aIter != aVisMap.cend() ) + { + const SwAccessibleChild& rLower = (*aIter).second; + if( rLower.IsAccessible( bInPagePreview ) ) + { + rChildren.push_back( rLower ); + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + GetChildren( rAccMap, rVisArea, *(rLower.GetSwFrame()), + rChildren, bInPagePreview ); + } + ++aIter; + } + } + else + { + // The unsorted list is sorted enough, because it returns lower + // frames in the correct order. + const SwAccessibleChildSList aVisList( rVisArea, rFrame, rAccMap ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( bInPagePreview ) ) + { + rChildren.push_back( rLower ); + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + GetChildren( rAccMap, rVisArea, *(rLower.GetSwFrame()), + rChildren, bInPagePreview ); + } + ++aIter; + } + } +} + +SwRect SwAccessibleFrame::GetBounds( const SwAccessibleMap& rAccMap, + const SwFrame *pFrame ) +{ + if( !pFrame ) + pFrame = GetFrame(); + + SwAccessibleChild aFrame( pFrame ); + SwRect aBounds( aFrame.GetBounds( rAccMap ).Intersection( maVisArea ) ); + return aBounds; +} + +bool SwAccessibleFrame::IsEditable( SwViewShell const *pVSh ) const +{ + const SwFrame *pFrame = GetFrame(); + if( !pFrame ) + return false; + + OSL_ENSURE( pVSh, "no view shell" ); + if( pVSh && (pVSh->GetViewOptions()->IsReadonly() || + pVSh->IsPreview()) ) + return false; + + if( !pFrame->IsRootFrame() && pFrame->IsProtected() ) + return false; + + return true; +} + +bool SwAccessibleFrame::IsOpaque( SwViewShell const *pVSh ) const +{ + SwAccessibleChild aFrame( GetFrame() ); + if( !aFrame.GetSwFrame() ) + return false; + + OSL_ENSURE( pVSh, "no view shell" ); + if( !pVSh ) + return false; + + const SwViewOption *pVOpt = pVSh->GetViewOptions(); + do + { + const SwFrame *pFrame = aFrame.GetSwFrame(); + if( pFrame->IsRootFrame() ) + return true; + + if( pFrame->IsPageFrame() && !pVOpt->IsPageBack() ) + return false; + + const SvxBrushItem &rBack = pFrame->GetAttrSet()->GetBackground(); + if( !rBack.GetColor().GetTransparency() || + rBack.GetGraphicPos() != GPOS_NONE ) + return true; + + // If a fly frame has a transparent background color, we have to consider the background. + // But a background color "no fill"/"auto fill" should *not* be considered. + if( pFrame->IsFlyFrame() && + (rBack.GetColor().GetTransparency() != 0) && + (rBack.GetColor() != COL_TRANSPARENT) + ) + return true; + + if( pFrame->IsSctFrame() ) + { + const SwSection* pSection = static_cast(pFrame)->GetSection(); + if( pSection && ( SectionType::ToxHeader == pSection->GetType() || + SectionType::ToxContent == pSection->GetType() ) && + !pVOpt->IsReadonly() && + SwViewOption::IsIndexShadings() ) + return true; + } + if( pFrame->IsFlyFrame() ) + aFrame = static_cast(pFrame)->GetAnchorFrame(); + else + aFrame = pFrame->GetUpper(); + } while( aFrame.GetSwFrame() && !aFrame.IsAccessible( IsInPagePreview() ) ); + + return false; +} + +SwAccessibleFrame::SwAccessibleFrame( const SwRect& rVisArea, + const SwFrame *pF, + bool bIsPagePreview ) : + maVisArea( rVisArea ), + mpFrame( pF ), + mbIsInPagePreview( bIsPagePreview ) +{ + assert(mpFrame); +} + +SwAccessibleFrame::~SwAccessibleFrame() +{ +} + +const SwFrame* SwAccessibleFrame::GetParent( const SwAccessibleChild& rFrameOrObj, + bool bInPagePreview ) +{ + return rFrameOrObj.GetParent( bInPagePreview ); +} + +OUString SwAccessibleFrame::GetFormattedPageNumber() const +{ + sal_uInt16 nPageNum = GetFrame()->GetVirtPageNum(); + SvxNumType nFormat = GetFrame()->FindPageFrame()->GetPageDesc() + ->GetNumType().GetNumberingType(); + if( SVX_NUM_NUMBER_NONE == nFormat ) + nFormat = SVX_NUM_ARABIC; + + OUString sRet( FormatNumber( nPageNum, nFormat ) ); + return sRet; +} + +sal_Int32 SwAccessibleFrame::GetChildCount( SwAccessibleMap& rAccMap ) const +{ + return GetChildCount( rAccMap, maVisArea, mpFrame, IsInPagePreview() ); +} + +sw::access::SwAccessibleChild SwAccessibleFrame::GetChild( + SwAccessibleMap& rAccMap, + sal_Int32 nPos ) const +{ + return SwAccessibleFrame::GetChild( rAccMap, maVisArea, *mpFrame, nPos, IsInPagePreview() ); +} + +sal_Int32 SwAccessibleFrame::GetChildIndex( SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rChild ) const +{ + sal_Int32 nPos = 0; + return GetChildIndex( rAccMap, maVisArea, *mpFrame, rChild, nPos, IsInPagePreview() ) + ? nPos + : -1; +} + +sw::access::SwAccessibleChild SwAccessibleFrame::GetChildAtPixel( + const Point& rPos, + SwAccessibleMap& rAccMap ) const +{ + return GetChildAtPixel( maVisArea, *mpFrame, rPos, IsInPagePreview(), rAccMap ); +} + +void SwAccessibleFrame::GetChildren( SwAccessibleMap& rAccMap, + std::list< sw::access::SwAccessibleChild >& rChildren ) const +{ + GetChildren( rAccMap, maVisArea, *mpFrame, rChildren, IsInPagePreview() ); +} + +bool SwAccessibleFrame::IsShowing( const SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rFrameOrObj ) const +{ + return IsShowing( rFrameOrObj.GetBox( rAccMap ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframe.hxx b/sw/source/core/access/accframe.hxx new file mode 100644 index 000000000..c3ebd59d4 --- /dev/null +++ b/sw/source/core/access/accframe.hxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAME_HXX + +#include + +#include +#include + +#include +#include "accfrmobj.hxx" + +class SwAccessibleMap; +class SwFrame; +class SwViewShell; + +// Any method of this class must be called with an acquired solar mutex! + +class SwAccessibleFrame +{ + SwRect maVisArea; + const SwFrame* mpFrame; + const bool mbIsInPagePreview; + +protected: + // #i77106# - method needs to be called by new class + static sal_Int32 GetChildCount( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame *pFrame, + bool bInPagePreviewr ); + +// private: + static sw::access::SwAccessibleChild GetChild( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + sal_Int32& rPos, + bool bInPagePreview); + + static bool GetChildIndex( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + const sw::access::SwAccessibleChild& rChild, + sal_Int32& rPos, + bool bInPagePreview ); + + static sw::access::SwAccessibleChild GetChildAtPixel( const SwRect& rVisArea, + const SwFrame& rFrame, + const Point& rPos, + bool bInPagePreview, + SwAccessibleMap& rAccMap ); + + static void GetChildren( SwAccessibleMap& rAccMap, + const SwRect& rVisArea, + const SwFrame& rFrame, + std::list< sw::access::SwAccessibleChild >& rChildren, + bool bInPagePreview ); + + bool IsEditable( SwViewShell const *pVSh ) const; + + bool IsOpaque( SwViewShell const *pVSh ) const; + +public: + bool IsShowing( const SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rFrameOrObj ) const; + inline bool IsShowing( const SwRect& rFrame ) const; + inline bool IsShowing( const SwAccessibleMap& rAccMap ) const; + +protected: + bool IsInPagePreview() const + { + return mbIsInPagePreview; + } + + void ClearFrame() + { + mpFrame = nullptr; + } + + SwAccessibleFrame( const SwRect& rVisArea, + const SwFrame *pFrame, + bool bIsPagePreview ); + virtual ~SwAccessibleFrame(); +public: + // Return the SwFrame this context is attached to. + const SwFrame* GetFrame() const { return mpFrame; }; + + static const SwFrame* GetParent( const sw::access::SwAccessibleChild& rFrameOrObj, + bool bInPagePreview ); + + sal_Int32 GetChildIndex( SwAccessibleMap& rAccMap, + const sw::access::SwAccessibleChild& rChild ) const; + +protected: + // Return the bounding box of the frame clipped to the visible area. + // If no frame is specified, use this' frame. + SwRect GetBounds( const SwAccessibleMap& rAccMap, + const SwFrame *pFrame = nullptr ); + + // Return the upper that has a context attached. This might be + // another one than the immediate upper. + inline const SwFrame *GetParent() const; + + // Return the lower count or the nth lower, there the lowers have a + // not be same one as the SwFrame's lowers + sal_Int32 GetChildCount( SwAccessibleMap& rAccMap ) const; + sw::access::SwAccessibleChild GetChild( SwAccessibleMap& rAccMap, + sal_Int32 nPos ) const; + sw::access::SwAccessibleChild GetChildAtPixel( const Point& rPos, + SwAccessibleMap& rAccMap ) const; + void GetChildren( SwAccessibleMap& rAccMap, + std::list< sw::access::SwAccessibleChild >& rChildren ) const; + + void SetVisArea( const SwRect& rNewVisArea ) + { + maVisArea = rNewVisArea; + } + + const SwRect& GetVisArea() const + { + return maVisArea; + } + + OUString GetFormattedPageNumber() const; +}; + +inline bool SwAccessibleFrame::IsShowing( const SwRect& rFrame ) const +{ + return !rFrame.IsEmpty() && rFrame.IsOver( maVisArea ); +} + +inline bool SwAccessibleFrame::IsShowing( const SwAccessibleMap& rAccMap ) const +{ + sw::access::SwAccessibleChild aFrameOrObj( GetFrame() ); + return IsShowing( rAccMap, aFrameOrObj ); +} + +inline const SwFrame *SwAccessibleFrame::GetParent() const +{ + sw::access::SwAccessibleChild aFrameOrObj( GetFrame() ); + return GetParent( aFrameOrObj, IsInPagePreview() ); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframebase.cxx b/sw/source/core/access/accframebase.cxx new file mode 100644 index 000000000..cfbfe9ecf --- /dev/null +++ b/sw/source/core/access/accframebase.cxx @@ -0,0 +1,374 @@ +/* -*- 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 "accframebase.hxx" + +#include +#include +#include +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +bool SwAccessibleFrameBase::IsSelected() +{ + bool bRet = false; + + assert(GetMap()); + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + if( auto pFESh = dynamic_cast(pVSh) ) + { + const SwFrame *pFlyFrame = pFESh->GetSelectedFlyFrame(); + if( pFlyFrame == GetFrame() ) + bRet = true; + } + + return bRet; +} + +void SwAccessibleFrameBase::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + const SwViewShell *pVSh = GetMap()->GetShell(); + assert(pVSh); + + if (dynamic_cast(pVSh)) + { + // SELECTABLE + rStateSet.AddState(AccessibleStateType::SELECTABLE); + // FOCUSABLE + rStateSet.AddState(AccessibleStateType::FOCUSABLE); + } + + // SELECTED and FOCUSED + if( IsSelected() ) + { + rStateSet.AddState( AccessibleStateType::SELECTED ); + SAL_WARN_IF(!m_bIsSelected, "sw.a11y", "bSelected out of sync"); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + } + if( GetSelectedState() ) + rStateSet.AddState( AccessibleStateType::SELECTED ); +} + +SwNodeType SwAccessibleFrameBase::GetNodeType( const SwFlyFrame *pFlyFrame ) +{ + SwNodeType nType = SwNodeType::Text; + if( pFlyFrame->Lower() ) + { + if( pFlyFrame->Lower()->IsNoTextFrame() ) + { + const SwNoTextFrame *const pContentFrame = + static_cast(pFlyFrame->Lower()); + nType = pContentFrame->GetNode()->GetNodeType(); + } + } + else + { + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + const SwFormatContent& rContent = pFrameFormat->GetContent(); + const SwNodeIndex *pNdIdx = rContent.GetContentIdx(); + if( pNdIdx ) + { + const SwContentNode *pCNd = + (pNdIdx->GetNodes())[pNdIdx->GetIndex()+1]->GetContentNode(); + if( pCNd ) + nType = pCNd->GetNodeType(); + } + } + + return nType; +} + +SwAccessibleFrameBase::SwAccessibleFrameBase( + std::shared_ptr const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleContext( pInitMap, nInitRole, pFlyFrame ), + m_bIsSelected( false ) +{ + const SwFrameFormat* pFrameFormat = pFlyFrame->GetFormat(); + if(pFrameFormat) + StartListening(const_cast(pFrameFormat)->GetNotifier()); + + SetName( pFrameFormat->GetName() ); + + m_bIsSelected = IsSelected(); +} + +void SwAccessibleFrameBase::InvalidateCursorPos_() +{ + bool bNewSelected = IsSelected(); + bool bOldSelected; + + { + osl::MutexGuard aGuard( m_Mutex ); + bOldSelected = m_bIsSelected; + m_bIsSelected = bNewSelected; + } + + if( bNewSelected ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + if( bOldSelected != bNewSelected ) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() && bNewSelected ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, bNewSelected ); + if( pWin && pWin->HasFocus() && !bNewSelected ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, bNewSelected ); + if(bNewSelected) + { + uno::Reference< XAccessible > xParent( GetWeakParent() ); + if( xParent.is() ) + { + SwAccessibleContext *pAcc = + static_cast ( xParent.get() ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + uno::Reference< XAccessible > xChild(this); + aEvent.NewValue <<= xChild; + pAcc->FireAccessibleEvent( aEvent ); + } + } + } +} + +void SwAccessibleFrameBase::InvalidateFocus_() +{ + vcl::Window *pWin = GetWindow(); + if( pWin ) + { + bool bSelected; + + { + osl::MutexGuard aGuard( m_Mutex ); + bSelected = m_bIsSelected; + } + assert(bSelected && "focus object should be selected"); + + FireStateChangedEvent( AccessibleStateType::FOCUSED, + pWin->HasFocus() && bSelected ); + } +} + +bool SwAccessibleFrameBase::HasCursor() +{ + osl::MutexGuard aGuard( m_Mutex ); + return m_bIsSelected; +} + +SwAccessibleFrameBase::~SwAccessibleFrameBase() +{ +} + +void SwAccessibleFrameBase::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + EndListeningAll(); + } + else if(auto pLegacyModifyHint = dynamic_cast(&rHint)) + { + sal_uInt16 nWhich = pLegacyModifyHint->m_pOld ? pLegacyModifyHint->m_pOld->Which() : pLegacyModifyHint->m_pNew ? pLegacyModifyHint->m_pNew->Which() : 0; + const SwFlyFrame* pFlyFrame = static_cast(GetFrame()); + if(nWhich == RES_NAME_CHANGED && pFlyFrame) + { + const SwFrameFormat* pFrameFormat = pFlyFrame->GetFormat(); + + const OUString sOldName( GetName() ); + assert( !pLegacyModifyHint->m_pOld || + static_cast(pLegacyModifyHint->m_pOld)->GetString() == GetName()); + + SetName( pFrameFormat->GetName() ); + assert( !pLegacyModifyHint->m_pNew || + static_cast(pLegacyModifyHint->m_pNew)->GetString() == GetName()); + + if( sOldName != GetName() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldName; + aEvent.NewValue <<= GetName(); + FireAccessibleEvent( aEvent ); + } + } + } +} + +void SwAccessibleFrameBase::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + EndListeningAll(); + SwAccessibleContext::Dispose(bRecursive, bCanSkipInvisible); +} + +//Get the selection cursor of the document. +SwPaM* SwAccessibleFrameBase::GetCursor() +{ + // get the cursor shell; if we don't have any, we don't have a + // cursor/selection either + SwPaM* pCursor = nullptr; + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr && !pCursorShell->IsTableMode() ) + { + SwFEShell *pFESh = dynamic_cast( pCursorShell) != nullptr + ? static_cast< SwFEShell * >( pCursorShell ) : nullptr; + if( !pFESh || + !(pFESh->IsFrameSelected() || pFESh->IsObjSelected() > 0) ) + { + // get the selection, and test whether it affects our text node + pCursor = pCursorShell->GetCursor( false /* ??? */ ); + } + } + + return pCursor; +} + +//Return the selected state of the object. +//when the object's anchor are in the selection cursor, we should return true. +bool SwAccessibleFrameBase::GetSelectedState( ) +{ + SolarMutexGuard aGuard; + + if(GetMap()->IsDocumentSelAll()) + { + return true; + } + + // SELECTED. + SwFlyFrame* pFlyFrame = getFlyFrame(); + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + const SwPosition *pPos = rAnchor.GetContentAnchor(); + if( !pPos ) + return false; + int nIndex = pPos->nContent.GetIndex(); + if( pPos->nNode.GetNode().GetTextNode() ) + { + SwPaM* pCursor = GetCursor(); + if( pCursor != nullptr ) + { + const SwTextNode* pNode = pPos->nNode.GetNode().GetTextNode(); + sal_uLong nHere = pNode->GetIndex(); + + // iterate over ring + SwPaM* pRingStart = pCursor; + do + { + // ignore, if no mark + if( pCursor->HasMark() ) + { + // check whether nHere is 'inside' pCursor + SwPosition* pStart = pCursor->Start(); + sal_uLong nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = pCursor->End(); + sal_uLong nEndIndex = pEnd->nNode.GetIndex(); + if( ( nHere >= nStartIndex ) && (nHere <= nEndIndex) ) + { + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + if( ((nHere == nStartIndex) && (nIndex >= pStart->nContent.GetIndex())) || (nHere > nStartIndex) ) + if( ((nHere == nEndIndex) && (nIndex < pEnd->nContent.GetIndex())) || (nHere < nEndIndex) ) + return true; + } + else if( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA ) + { + if (IsSelectFrameAnchoredAtPara(*pPos, *pStart, *pEnd)) + return true; + } + else if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + if (IsDestroyFrameAnchoredAtChar(*pPos, *pStart, *pEnd)) + { + return true; + } + } + break; + } + // else: this PaM doesn't point to this paragraph + } + // else: this PaM is collapsed and doesn't select anything + + // next PaM in ring + pCursor = pCursor->GetNext(); + } + while( pCursor != pRingStart ); + } + } + return false; +} + +SwFlyFrame* SwAccessibleFrameBase::getFlyFrame() const +{ + SwFlyFrame* pFlyFrame = nullptr; + + const SwFrame* pFrame = GetFrame(); + assert(pFrame); + if( pFrame->IsFlyFrame() ) + { + pFlyFrame = static_cast( const_cast( pFrame ) ); + } + + return pFlyFrame; +} + +bool SwAccessibleFrameBase::SetSelectedState( bool ) +{ + bool bParaSelected = GetSelectedState() || IsSelected(); + + if (m_isSelectedInDoc != bParaSelected) + { + m_isSelectedInDoc = bParaSelected; + FireStateChangedEvent( AccessibleStateType::SELECTED, bParaSelected ); + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accframebase.hxx b/sw/source/core/access/accframebase.hxx new file mode 100644 index 000000000..c38424a3b --- /dev/null +++ b/sw/source/core/access/accframebase.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAMEBASE_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRAMEBASE_HXX + +#include "acccontext.hxx" +#include +#include + +class SwFlyFrame; + +class SwAccessibleFrameBase : public SwAccessibleContext, public SvtListener +{ + bool m_bIsSelected; // protected by base class mutex + bool IsSelected(); + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets SELECTABLE(1), SELECTED(+), + // FOCUSABLE(1) and FOCUSED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + SwFlyFrame* getFlyFrame() const; + bool GetSelectedState( ); + SwPaM* GetCursor(); + + virtual void InvalidateCursorPos_() override; + virtual void InvalidateFocus_() override; + + virtual ~SwAccessibleFrameBase() override; + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleFrameBase(std::shared_ptr const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame *pFlyFrame ); + + virtual bool HasCursor() override; // required by map to remember that object + + static SwNodeType GetNodeType( const SwFlyFrame *pFlyFrame ); + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + virtual bool SetSelectedState( bool bSeleted ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobj.cxx b/sw/source/core/access/accfrmobj.cxx new file mode 100644 index 000000000..c09c0215e --- /dev/null +++ b/sw/source/core/access/accfrmobj.cxx @@ -0,0 +1,408 @@ +/* -*- 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 "accfrmobj.hxx" + +#include +#include "acccontext.hxx" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace sw::access { + +SwAccessibleChild::SwAccessibleChild() + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{} + +SwAccessibleChild::SwAccessibleChild( const SdrObject* pDrawObj ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + Init( pDrawObj ); +} + +SwAccessibleChild::SwAccessibleChild( const SwFrame* pFrame ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + Init( pFrame ); +} + +SwAccessibleChild::SwAccessibleChild( vcl::Window* pWindow ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + Init( pWindow ); +} + +SwAccessibleChild::SwAccessibleChild( const SwFrame* pFrame, + const SdrObject* pDrawObj, + vcl::Window* pWindow ) + : mpFrame( nullptr ) + , mpDrawObj( nullptr ) + , mpWindow( nullptr ) +{ + if ( pFrame ) + { + Init( pFrame ); + } + else if ( pDrawObj ) + { + Init( pDrawObj ); + } + else if ( pWindow ) + { + Init( pWindow ); + } + OSL_ENSURE( (!pFrame || pFrame == mpFrame) && + (!pDrawObj || pDrawObj == mpDrawObj) && + (!pWindow || pWindow == mpWindow), + "invalid frame/object/window combination" ); + +} + +SwAccessibleChild::~SwAccessibleChild() = default; + +void SwAccessibleChild::Init( const SdrObject* pDrawObj ) +{ + mpDrawObj = pDrawObj; + const SwVirtFlyDrawObj* pFlyDrawObj = dynamic_cast(mpDrawObj); + mpFrame = pFlyDrawObj ? pFlyDrawObj->GetFlyFrame() : nullptr; + mpWindow = nullptr; +} + +void SwAccessibleChild::Init( const SwFrame* pFrame ) +{ + mpFrame = pFrame; + mpDrawObj = mpFrame && mpFrame->IsFlyFrame() + ? static_cast < const SwFlyFrame * >( mpFrame )->GetVirtDrawObj() + : nullptr; + mpWindow = nullptr; +} + +void SwAccessibleChild::Init( vcl::Window* pWindow ) +{ + mpWindow = pWindow; + mpFrame = nullptr; + mpDrawObj = nullptr; +} + +bool SwAccessibleChild::IsAccessible( bool bPagePreview ) const +{ + bool bRet( false ); + + if ( mpFrame ) + { + bRet = mpFrame->IsAccessibleFrame() && + ( !mpFrame->IsCellFrame() || + static_cast( mpFrame )->GetTabBox()->GetSttNd() != nullptr ) && + !mpFrame->IsInCoveredCell() && + ( bPagePreview || + !mpFrame->IsPageFrame() ); + } + else if ( mpDrawObj ) + { + bRet = true; + } + else if ( mpWindow ) + { + bRet = true; + } + + return bRet; +} + +bool SwAccessibleChild::IsBoundAsChar() const +{ + bool bRet( false ); + + if ( mpFrame ) + { + bRet = mpFrame->IsFlyFrame() && + static_cast< const SwFlyFrame *>(mpFrame)->IsFlyInContentFrame(); + } + else if ( mpDrawObj ) + { + const SwFrameFormat* pFrameFormat = ::FindFrameFormat( mpDrawObj ); + bRet = pFrameFormat + && (RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId()); + } + else if ( mpWindow ) + { + bRet = false; + } + + return bRet; +} + +SwAccessibleChild& SwAccessibleChild::operator=( const SdrObject* pDrawObj ) +{ + Init( pDrawObj ); + return *this; +} + +SwAccessibleChild& SwAccessibleChild::operator=( const SwFrame* pFrame ) +{ + Init( pFrame ); + return *this; +} + +SwAccessibleChild& SwAccessibleChild::operator=( vcl::Window* pWindow ) +{ + Init( pWindow ); + return *this; +} + +bool SwAccessibleChild::operator==( const SwAccessibleChild& r ) const +{ + return mpFrame == r.mpFrame && + mpDrawObj == r.mpDrawObj && + mpWindow == r.mpWindow; +} + +bool SwAccessibleChild::IsValid() const +{ + return mpFrame != nullptr || + mpDrawObj != nullptr || + mpWindow != nullptr; +} + +bool SwAccessibleChild::IsVisibleChildrenOnly() const +{ + bool bRet( false ); + + if ( !mpFrame ) + { + bRet = true; + } + else + { + bRet = mpFrame->IsRootFrame() || + !( mpFrame->IsTabFrame() || + mpFrame->IsInTab() || + ( IsBoundAsChar() && + static_cast(mpFrame)->GetAnchorFrame()->IsInTab() ) ); + } + + return bRet; +} + +SwRect SwAccessibleChild::GetBox( const SwAccessibleMap& rAccMap ) const +{ + SwRect aBox; + + if ( mpFrame ) + { + if ( mpFrame->IsPageFrame() && + static_cast< const SwPageFrame * >( mpFrame )->IsEmptyPage() ) + { + aBox = SwRect( mpFrame->getFrameArea().Left(), mpFrame->getFrameArea().Top()-1, 1, 1 ); + } + else if ( mpFrame->IsTabFrame() ) + { + aBox = mpFrame->getFrameArea(); + aBox.Intersection( mpFrame->GetUpper()->getFrameArea() ); + } + else + { + aBox = mpFrame->getFrameArea(); + } + } + else if( mpDrawObj ) + { + const SwContact* const pContact = ::GetUserCall(mpDrawObj); + // assume that a) the SwVirt* objects that don't have this are handled + // by the mpFrame case above b) for genuine SdrObject this must be set + // if it's connected to layout + assert(dynamic_cast(pContact)); + SwPageFrame const*const pPage(const_cast( + pContact->GetAnchoredObj(mpDrawObj))->FindPageFrameOfAnchor()); + if (pPage) // may end up here with partial layout -> not visible + { + aBox = SwRect( mpDrawObj->GetCurrentBoundRect() ); + // tdf#91260 drawing object may be partially off-page + aBox.Intersection(pPage->getFrameArea()); + } + } + else if ( mpWindow ) + { + vcl::Window *pWin = rAccMap.GetShell()->GetWin(); + if (pWin) + { + aBox = SwRect( pWin->PixelToLogic( + tools::Rectangle( mpWindow->GetPosPixel(), + mpWindow->GetSizePixel() ) ) ); + } + } + + return aBox; +} + +SwRect SwAccessibleChild::GetBounds( const SwAccessibleMap& rAccMap ) const +{ + SwRect aBound; + + if( mpFrame ) + { + if( mpFrame->IsPageFrame() && + static_cast< const SwPageFrame * >( mpFrame )->IsEmptyPage() ) + { + aBound = SwRect( mpFrame->getFrameArea().Left(), mpFrame->getFrameArea().Top()-1, 0, 0 ); + } + else + aBound = mpFrame->GetPaintArea(); + } + else if( mpDrawObj ) + { + aBound = GetBox( rAccMap ); + } + else if ( mpWindow ) + { + aBound = GetBox( rAccMap ); + } + + return aBound; +} + +bool SwAccessibleChild::AlwaysIncludeAsChild() const +{ + bool bAlwaysIncludedAsChild( false ); + + if ( mpWindow ) + { + bAlwaysIncludedAsChild = true; + } + + return bAlwaysIncludedAsChild; +} + +const SwFrame* SwAccessibleChild::GetParent( const bool bInPagePreview ) const +{ + const SwFrame* pParent( nullptr ); + + if ( mpFrame ) + { + if( mpFrame->IsFlyFrame() ) + { + const SwFlyFrame* pFly = static_cast< const SwFlyFrame *>( mpFrame ); + if( pFly->IsFlyInContentFrame() ) + { + // For RndStdIds::FLY_AS_CHAR the parent is the anchor + pParent = pFly->GetAnchorFrame(); + OSL_ENSURE( SwAccessibleChild( pParent ).IsAccessible( bInPagePreview ), + "parent is not accessible" ); + } + else + { + // In any other case the parent is the root frm + // (in page preview, the page frame) + if( bInPagePreview ) + pParent = pFly->FindPageFrame(); + else + pParent = pFly->getRootFrame(); + } + } + else + { + SwAccessibleChild aUpper( mpFrame->GetUpper() ); + while( aUpper.GetSwFrame() && !aUpper.IsAccessible(bInPagePreview) ) + { + aUpper = aUpper.GetSwFrame()->GetUpper(); + } + pParent = aUpper.GetSwFrame(); + } + } + else if( mpDrawObj ) + { + const SwDrawContact *pContact = + static_cast< const SwDrawContact* >( GetUserCall( mpDrawObj ) ); + OSL_ENSURE( pContact, "sdr contact is missing" ); + if( pContact ) + { + const SwFrameFormat *pFrameFormat = pContact->GetFormat(); + OSL_ENSURE( pFrameFormat, "frame format is missing" ); + if( pFrameFormat && RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId() ) + { + // For RndStdIds::FLY_AS_CHAR the parent is the anchor + pParent = pContact->GetAnchorFrame(); + OSL_ENSURE( SwAccessibleChild( pParent ).IsAccessible( bInPagePreview ), + "parent is not accessible" ); + + } + else + { + // In any other case the parent is the root frm + SwFrame const*const pAnchor(pContact->GetAnchorFrame()); + if (pAnchor) // null if object removed from layout + { + if (bInPagePreview) + pParent = pAnchor->FindPageFrame(); + else + pParent = pAnchor->getRootFrame(); + } + } + } + } + else if ( mpWindow ) + { + css::uno::Reference < css::accessibility::XAccessible > xAcc = + mpWindow->GetAccessible(); + if ( xAcc.is() ) + { + css::uno::Reference < css::accessibility::XAccessibleContext > xAccContext = + xAcc->getAccessibleContext(); + if ( xAccContext.is() ) + { + css::uno::Reference < css::accessibility::XAccessible > xAccParent = + xAccContext->getAccessibleParent(); + if ( xAccParent.is() ) + { + SwAccessibleContext* pAccParentImpl = + dynamic_cast< SwAccessibleContext *>( xAccParent.get() ); + if ( pAccParentImpl ) + { + pParent = pAccParentImpl->GetFrame(); + } + } + } + } + } + + return pParent; +} + +} // namespace sw::access + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobj.hxx b/sw/source/core/access/accfrmobj.hxx new file mode 100644 index 000000000..cb5fdfb7b --- /dev/null +++ b/sw/source/core/access/accfrmobj.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJ_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJ_HXX + +#include + +class SwAccessibleMap; +class SwFrame; +class SdrObject; +namespace vcl { class Window; } +class SwRect; + +namespace sw::access { + +class SwAccessibleChild +{ + public: + SwAccessibleChild(); + ~SwAccessibleChild(); + explicit SwAccessibleChild( const SdrObject* pDrawObj ); + explicit SwAccessibleChild( const SwFrame* pFrame ); + explicit SwAccessibleChild( vcl::Window* pWindow ); + SwAccessibleChild( const SwFrame* pFrame, + const SdrObject* pDrawObj, + vcl::Window* pWindow ); + + SwAccessibleChild(SwAccessibleChild const &) = default; + SwAccessibleChild(SwAccessibleChild &&) = default; + SwAccessibleChild & operator =(SwAccessibleChild const &) = default; + SwAccessibleChild & operator =(SwAccessibleChild &&) = default; + + SwAccessibleChild& operator=( const SdrObject* pDrawObj ); + SwAccessibleChild& operator=( const SwFrame* pFrame ); + SwAccessibleChild& operator=( vcl::Window* pWindow ); + + bool operator==( const SwAccessibleChild& r ) const; + + bool IsValid() const; + + const SwFrame* GetSwFrame() const { return mpFrame; } + const SdrObject* GetDrawObject() const { return mpDrawObj; } + vcl::Window* GetWindow() const { return mpWindow; } + + const SwFrame* GetParent( const bool bInPagePreview ) const; + + bool IsAccessible( bool bPagePreview ) const; + bool IsBoundAsChar() const; + + bool IsVisibleChildrenOnly() const; + SwRect GetBox( const SwAccessibleMap& rAccMap ) const; + SwRect GetBounds( const SwAccessibleMap& rAccMap ) const; + + /** indicating, if accessible child is included even, if the corresponding + object is not visible. */ + bool AlwaysIncludeAsChild() const; + + private: + const SwFrame* mpFrame; + const SdrObject* mpDrawObj; + VclPtr mpWindow; + + void Init( const SdrObject* pDrawObj ); + void Init( const SwFrame* pFrame ); + void Init( vcl::Window* pWindow ); +}; + +} // eof of namespace sw::access + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjmap.cxx b/sw/source/core/access/accfrmobjmap.cxx new file mode 100644 index 000000000..f088ff8fd --- /dev/null +++ b/sw/source/core/access/accfrmobjmap.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 "accfrmobjmap.hxx" +#include +#include "acccontext.hxx" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace sw::access; + +SwAccessibleChildMap::SwAccessibleChildMap( const SwRect& rVisArea, + const SwFrame& rFrame, + SwAccessibleMap& rAccMap ) + : mnHellId( rAccMap.GetShell()->GetDoc()->getIDocumentDrawModelAccess().GetHellId() ) + , mnControlsId( rAccMap.GetShell()->GetDoc()->getIDocumentDrawModelAccess().GetControlsId() ) +{ + const bool bVisibleChildrenOnly = SwAccessibleChild( &rFrame ).IsVisibleChildrenOnly(); + + sal_uInt32 nPos = 0; + SwAccessibleChild aLower( rFrame.GetLower() ); + while( aLower.GetSwFrame() ) + { + if ( !bVisibleChildrenOnly || + aLower.AlwaysIncludeAsChild() || + aLower.GetBox( rAccMap ).IsOver( rVisArea ) ) + { + insert( nPos++, SwAccessibleChildMapKey::TEXT, aLower ); + } + + aLower = aLower.GetSwFrame()->GetNext(); + } + + if ( rFrame.IsPageFrame() ) + { + OSL_ENSURE( bVisibleChildrenOnly, "page frame within tab frame???" ); + const SwPageFrame *pPgFrame = + static_cast< const SwPageFrame * >( &rFrame ); + const SwSortedObjs *pObjs = pPgFrame->GetSortedObjs(); + if ( pObjs ) + { + for(const SwAnchoredObject* pObj : *pObjs) + { + aLower = pObj->GetDrawObj(); + if ( aLower.GetBox( rAccMap ).IsOver( rVisArea ) ) + { + insert( aLower.GetDrawObject(), aLower ); + } + } + } + } + else if( rFrame.IsTextFrame() ) + { + const SwSortedObjs *pObjs = rFrame.GetDrawObjs(); + if ( pObjs ) + { + for(const SwAnchoredObject* pObj : *pObjs) + { + aLower = pObj->GetDrawObj(); + if ( aLower.IsBoundAsChar() && + ( !bVisibleChildrenOnly || + aLower.AlwaysIncludeAsChild() || + aLower.GetBox( rAccMap ).IsOver( rVisArea ) ) ) + { + insert( aLower.GetDrawObject(), aLower ); + } + } + } + + { + ::rtl::Reference < SwAccessibleContext > xAccImpl = + rAccMap.GetContextImpl( &rFrame, false ); + if( xAccImpl.is() ) + { + SwAccessibleContext* pAccImpl = xAccImpl.get(); + if ( pAccImpl && + pAccImpl->HasAdditionalAccessibleChildren() ) + { + std::vector< vcl::Window* > aAdditionalChildren; + pAccImpl->GetAdditionalAccessibleChildren( &aAdditionalChildren ); + + sal_Int32 nCounter( 0 ); + for ( const auto& rpChild : aAdditionalChildren ) + { + aLower = rpChild; + insert( ++nCounter, SwAccessibleChildMapKey::XWINDOW, aLower ); + } + } + } + } + } +} + +std::pair< SwAccessibleChildMap::iterator, bool > SwAccessibleChildMap::insert( + const sal_uInt32 nPos, + const SwAccessibleChildMapKey::LayerId eLayerId, + const SwAccessibleChild& rLower ) +{ + SwAccessibleChildMapKey aKey( eLayerId, nPos ); + return emplace( aKey, rLower ); +} + +std::pair< SwAccessibleChildMap::iterator, bool > SwAccessibleChildMap::insert( + const SdrObject *pObj, + const SwAccessibleChild& rLower ) +{ + const SdrLayerID nLayer = pObj->GetLayer(); + SwAccessibleChildMapKey::LayerId eLayerId = + (mnHellId == nLayer) + ? SwAccessibleChildMapKey::HELL + : ( (mnControlsId == nLayer) + ? SwAccessibleChildMapKey::CONTROLS + : SwAccessibleChildMapKey::HEAVEN ); + SwAccessibleChildMapKey aKey( eLayerId, pObj->GetOrdNum() ); + return emplace( aKey, rLower ); +} + +bool SwAccessibleChildMap::IsSortingRequired( const SwFrame& rFrame ) +{ + return ( rFrame.IsPageFrame() && + static_cast< const SwPageFrame& >( rFrame ).GetSortedObjs() ) || + ( rFrame.IsTextFrame() && + rFrame.GetDrawObjs() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjmap.hxx b/sw/source/core/access/accfrmobjmap.hxx new file mode 100644 index 000000000..ed4ecafec --- /dev/null +++ b/sw/source/core/access/accfrmobjmap.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJMAP_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJMAP_HXX + +#include +#include +#include "accfrmobj.hxx" +#include + +class SwAccessibleMap; +class SwRect; +class SwFrame; +class SdrObject; + +class SwAccessibleChildMapKey +{ +public: + enum LayerId { INVALID, HELL, TEXT, HEAVEN, CONTROLS, XWINDOW }; + + SwAccessibleChildMapKey() + : m_eLayerId( INVALID ) + , m_nOrdNum( 0 ) + , m_nPosNum( 0, 0 ) + {} + + SwAccessibleChildMapKey( LayerId eId, sal_uInt32 nOrd ) + : m_eLayerId( eId ) + , m_nOrdNum( nOrd ) + , m_nPosNum( 0, 0 ) + {} + + bool operator()( const SwAccessibleChildMapKey& r1, + const SwAccessibleChildMapKey& r2 ) const + { + if(r1.m_eLayerId == r2.m_eLayerId) + { + if(r1.m_nPosNum == r2.m_nPosNum) + return r1.m_nOrdNum < r2.m_nOrdNum; + else + { + if(r1.m_nPosNum.getY() == r2.m_nPosNum.getY()) + return r1.m_nPosNum.getX() < r2.m_nPosNum.getX(); + else + return r1.m_nPosNum.getY() < r2.m_nPosNum.getY(); + } + } + else + return r1.m_eLayerId < r2.m_eLayerId; + } + + /* MT: Need to get this position parameter stuff in dev300 somehow... + //This methods are used to insert an object to the map, adding a position parameter. + std::pair< iterator, bool > insert( sal_uInt32 nOrd, Point nPos, + const SwFrameOrObj& rLower ); + std::pair< iterator, bool > insert( const SdrObject *pObj, + const SwFrameOrObj& rLower, + const SwDoc *pDoc, + Point nPos); + */ + +private: + LayerId m_eLayerId; + sal_uInt32 m_nOrdNum; + Point m_nPosNum; +}; + + +class SwAccessibleChildMap +{ +public: + typedef SwAccessibleChildMapKey key_type; + typedef sw::access::SwAccessibleChild mapped_type; + typedef std::pair value_type; + typedef SwAccessibleChildMapKey key_compare; + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; + typedef std::map::const_reverse_iterator const_reverse_iterator; + +private: + const SdrLayerID mnHellId; + const SdrLayerID mnControlsId; + std::map maMap; + + std::pair< iterator, bool > insert( const sal_uInt32 nPos, + const SwAccessibleChildMapKey::LayerId eLayerId, + const sw::access::SwAccessibleChild& rLower ); + std::pair< iterator, bool > insert( const SdrObject* pObj, + const sw::access::SwAccessibleChild& rLower ); + +public: + SwAccessibleChildMap( const SwRect& rVisArea, + const SwFrame& rFrame, + SwAccessibleMap& rAccMap ); + + static bool IsSortingRequired( const SwFrame& rFrame ); + + const_iterator cbegin() const { return maMap.cbegin(); } + const_iterator cend() const { return maMap.cend(); } + const_reverse_iterator crbegin() const { return maMap.crbegin(); } + const_reverse_iterator crend() const { return maMap.crend(); } + + template + std::pair emplace(Args&&... args) { return maMap.emplace(std::forward(args)...); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjslist.cxx b/sw/source/core/access/accfrmobjslist.cxx new file mode 100644 index 000000000..7eca43a7b --- /dev/null +++ b/sw/source/core/access/accfrmobjslist.cxx @@ -0,0 +1,163 @@ +/* -*- 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 "accfrmobjslist.hxx" +#include +#include "acccontext.hxx" + +#include +#include +#include + +using namespace ::sw::access; + +SwAccessibleChildSList_const_iterator::SwAccessibleChildSList_const_iterator( + const SwAccessibleChildSList& rLst, + SwAccessibleMap& rAccMap ) + : m_rList( rLst ), + m_aCurr( m_rList.GetFrame().GetLower() ), + m_nNextObj( 0 ) +{ + if( !m_aCurr.GetSwFrame() ) + { + const SwFrame& rFrame = m_rList.GetFrame(); + if( rFrame.IsPageFrame() ) + { + const SwPageFrame& rPgFrame = static_cast< const SwPageFrame& >( rFrame ); + const SwSortedObjs *pObjs = rPgFrame.GetSortedObjs(); + if( pObjs && pObjs->size() ) + { + m_aCurr = (*pObjs)[m_nNextObj++]->GetDrawObj(); + } + } + else if( rFrame.IsTextFrame() ) + { + const SwSortedObjs *pObjs = rFrame.GetDrawObjs(); + if ( pObjs && pObjs->size() ) + { + m_aCurr = (*pObjs)[m_nNextObj++]->GetDrawObj(); + while( m_aCurr.IsValid() && !m_aCurr.IsBoundAsChar() ) + { + m_aCurr = (m_nNextObj < pObjs->size()) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + } + } + if ( !m_aCurr.IsValid() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl = + rAccMap.GetContextImpl( &rFrame, false ); + if( xAccImpl.is() ) + { + SwAccessibleContext* pAccImpl = xAccImpl.get(); + m_aCurr = SwAccessibleChild( pAccImpl->GetAdditionalAccessibleChild( 0 ) ); + ++m_nNextObj; + } + } + } + } + + if( m_rList.IsVisibleChildrenOnly() ) + { + // Find the first visible + while( m_aCurr.IsValid() && + !m_aCurr.AlwaysIncludeAsChild() && + !m_aCurr.GetBox( rAccMap ).IsOver( m_rList.GetVisArea() ) ) + { + next(); + } + } +} + +SwAccessibleChildSList_const_iterator& SwAccessibleChildSList_const_iterator::next() +{ + bool bNextTaken( true ); + if( m_aCurr.GetDrawObject() || m_aCurr.GetWindow() ) + { + bNextTaken = false; + } + else if( m_aCurr.GetSwFrame() ) + { + m_aCurr = m_aCurr.GetSwFrame()->GetNext(); + if( !m_aCurr.GetSwFrame() ) + { + bNextTaken = false; + } + } + + if( !bNextTaken ) + { + const SwFrame& rFrame = m_rList.GetFrame(); + if( rFrame.IsPageFrame() ) + { + const SwPageFrame& rPgFrame = static_cast< const SwPageFrame& >( rFrame ); + const SwSortedObjs *pObjs = rPgFrame.GetSortedObjs(); + m_aCurr = ( pObjs && m_nNextObj < pObjs->size() ) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + } + else if( rFrame.IsTextFrame() ) + { + const SwSortedObjs* pObjs = rFrame.GetDrawObjs(); + const size_t nObjsCount = pObjs ? pObjs->size() : 0; + m_aCurr = ( pObjs && m_nNextObj < nObjsCount ) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + while( m_aCurr.IsValid() && !m_aCurr.IsBoundAsChar() ) + { + m_aCurr = ( m_nNextObj < nObjsCount ) + ? (*pObjs)[m_nNextObj++]->GetDrawObj() + : static_cast< const SdrObject *>( nullptr ); + } + if ( !m_aCurr.IsValid() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl = + m_rList.GetAccMap().GetContextImpl( &rFrame, false ); + if( xAccImpl.is() ) + { + SwAccessibleContext* pAccImpl = xAccImpl.get(); + m_aCurr = SwAccessibleChild( pAccImpl->GetAdditionalAccessibleChild( m_nNextObj - nObjsCount ) ); + ++m_nNextObj; + } + } + } + } + + return *this; +} + +SwAccessibleChildSList_const_iterator& SwAccessibleChildSList_const_iterator::next_visible() +{ + next(); + while( m_aCurr.IsValid() && + !m_aCurr.AlwaysIncludeAsChild() && + !m_aCurr.GetBox( m_rList.GetAccMap() ).IsOver( m_rList.GetVisArea() ) ) + { + next(); + } + + return *this; +} + +SwAccessibleChildSList_const_iterator& SwAccessibleChildSList_const_iterator::operator++() +{ + return m_rList.IsVisibleChildrenOnly() ? next_visible() : next(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accfrmobjslist.hxx b/sw/source/core/access/accfrmobjslist.hxx new file mode 100644 index 000000000..9df293d71 --- /dev/null +++ b/sw/source/core/access/accfrmobjslist.hxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJSLIST_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCFRMOBJSLIST_HXX + +#include "accfrmobj.hxx" +#include + +class SwAccessibleMap; +class SwAccessibleChildSList; + +class SwAccessibleChildSList_const_iterator +{ +private: + friend class SwAccessibleChildSList; + + const SwAccessibleChildSList& m_rList; // The frame we are iterating over + sw::access::SwAccessibleChild m_aCurr; // The current object + size_t m_nNextObj; // The index of the current sdr object + + SwAccessibleChildSList_const_iterator( const SwAccessibleChildSList& rLst ) + : m_rList( rLst ) + , m_nNextObj( 0 ) + {} + + SwAccessibleChildSList_const_iterator( const SwAccessibleChildSList& rLst, + SwAccessibleMap& rAccMap ); + + SwAccessibleChildSList_const_iterator& next(); + SwAccessibleChildSList_const_iterator& next_visible(); + +public: + bool operator==( const SwAccessibleChildSList_const_iterator& r ) const + { + return m_aCurr == r.m_aCurr; + } + + bool operator!=( + const SwAccessibleChildSList_const_iterator& r ) const + { + return !(*this == r); + } + + SwAccessibleChildSList_const_iterator& operator++(); + + const sw::access::SwAccessibleChild& operator*() const + { + return m_aCurr; + } +}; + +// An iterator to iterate over a frame's child in any order +class SwAccessibleChildSList +{ + const SwRect maVisArea; + const SwFrame& mrFrame; + const bool mbVisibleChildrenOnly; + SwAccessibleMap& mrAccMap; + +public: + typedef SwAccessibleChildSList_const_iterator const_iterator; + + SwAccessibleChildSList( const SwFrame& rFrame, + SwAccessibleMap& rAccMap ) + : maVisArea() + , mrFrame( rFrame ) + , mbVisibleChildrenOnly( false ) + , mrAccMap( rAccMap ) + {} + + SwAccessibleChildSList( const SwRect& rVisArea, + const SwFrame& rFrame, + SwAccessibleMap& rAccMap ) + : maVisArea( rVisArea ) + , mrFrame( rFrame ) + , mbVisibleChildrenOnly( sw::access::SwAccessibleChild( &rFrame ).IsVisibleChildrenOnly() ) + , mrAccMap( rAccMap ) + { + } + + const_iterator begin() const + { + return SwAccessibleChildSList_const_iterator( *this, mrAccMap ); + } + + const_iterator end() const + { + return SwAccessibleChildSList_const_iterator( *this ); + } + + const SwFrame& GetFrame() const + { + return mrFrame; + } + + bool IsVisibleChildrenOnly() const + { + return mbVisibleChildrenOnly; + } + + const SwRect& GetVisArea() const + { + return maVisArea; + } + + SwAccessibleMap& GetAccMap() const + { + return mrAccMap; + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accgraphic.cxx b/sw/source/core/access/accgraphic.cxx new file mode 100644 index 000000000..d6d115d78 --- /dev/null +++ b/sw/source/core/access/accgraphic.cxx @@ -0,0 +1,75 @@ +/* -*- 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 "accgraphic.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +SwAccessibleGraphic::SwAccessibleGraphic( + std::shared_ptr const& pInitMap, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleNoTextFrame( pInitMap, AccessibleRole::GRAPHIC, pFlyFrame ) +{ +} + +SwAccessibleGraphic::~SwAccessibleGraphic() +{ +} + +OUString SAL_CALL SwAccessibleGraphic::getImplementationName() +{ + return "com.sun.star.comp.Writer.SwAccessibleGraphic"; +} + +sal_Bool SAL_CALL SwAccessibleGraphic::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +Sequence< OUString > SAL_CALL SwAccessibleGraphic::getSupportedServiceNames() +{ + return { "com.sun.star.text.AccessibleTextGraphicObject", sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessibleGraphic::getImplementationId() +{ + return css::uno::Sequence(); +} + +// Return this object's role. +sal_Int16 SAL_CALL SwAccessibleGraphic::getAccessibleRole() +{ + SolarMutexGuard g; + + SwFormatURL aURL( static_cast(GetFrame())->GetFormat()->GetURL() ); + + if (aURL.GetMap()) + return AccessibleRole::IMAGE_MAP; + return AccessibleRole::GRAPHIC; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accgraphic.hxx b/sw/source/core/access/accgraphic.hxx new file mode 100644 index 000000000..dc21201bc --- /dev/null +++ b/sw/source/core/access/accgraphic.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCGRAPHIC_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCGRAPHIC_HXX + +#include "accnotextframe.hxx" + +class SwAccessibleGraphic : public SwAccessibleNoTextFrame +{ +protected: + virtual ~SwAccessibleGraphic() override; + +public: + SwAccessibleGraphic(std::shared_ptr const& pInitMap, + const SwFlyFrame *pFlyFrame ); + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + /// Return this object's role. + virtual sal_Int16 SAL_CALL getAccessibleRole() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accheaderfooter.cxx b/sw/source/core/access/accheaderfooter.cxx new file mode 100644 index 000000000..b40bcf58d --- /dev/null +++ b/sw/source/core/access/accheaderfooter.cxx @@ -0,0 +1,110 @@ +/* -*- 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 "accheaderfooter.hxx" +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +const char sImplementationNameHeader[] = "com.sun.star.comp.Writer.SwAccessibleHeaderView"; +const char sImplementationNameFooter[] = "com.sun.star.comp.Writer.SwAccessibleFooterView"; + +SwAccessibleHeaderFooter::SwAccessibleHeaderFooter( + std::shared_ptr const& pInitMap, + const SwHeaderFrame* pHdFrame ) : + SwAccessibleContext( pInitMap, AccessibleRole::HEADER, pHdFrame ) +{ + OUString sArg( OUString::number( pHdFrame->GetPhyPageNum() ) ); + SetName( GetResource( STR_ACCESS_HEADER_NAME, &sArg ) ); +} + +SwAccessibleHeaderFooter::SwAccessibleHeaderFooter( + std::shared_ptr const& pInitMap, + const SwFooterFrame* pFtFrame ) : + SwAccessibleContext( pInitMap, AccessibleRole::FOOTER, pFtFrame ) +{ + OUString sArg( OUString::number( pFtFrame->GetPhyPageNum() ) ); + SetName( GetResource( STR_ACCESS_FOOTER_NAME, &sArg ) ); +} + +SwAccessibleHeaderFooter::~SwAccessibleHeaderFooter() +{ +} + +OUString SAL_CALL SwAccessibleHeaderFooter::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const char* pResId = AccessibleRole::HEADER == GetRole() + ? STR_ACCESS_HEADER_DESC + : STR_ACCESS_FOOTER_DESC ; + + OUString sArg( GetFormattedPageNumber() ); + + return GetResource(pResId, &sArg); +} + +OUString SAL_CALL SwAccessibleHeaderFooter::getImplementationName() +{ + if( AccessibleRole::HEADER == GetRole() ) + return sImplementationNameHeader; + else + return sImplementationNameFooter; +} + +sal_Bool SAL_CALL SwAccessibleHeaderFooter::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +Sequence< OUString > SAL_CALL SwAccessibleHeaderFooter::getSupportedServiceNames() +{ + return { (AccessibleRole::HEADER == GetRole())?OUString("com.sun.star.text.AccessibleHeaderView"):OUString("com.sun.star.text.AccessibleFooterView"), + sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessibleHeaderFooter::getImplementationId() +{ + return css::uno::Sequence(); +} + +sal_Int32 SAL_CALL SwAccessibleHeaderFooter::getBackground() +{ + Reference< XAccessible > xParent = getAccessibleParent(); + if (xParent.is()) + { + Reference< XAccessibleComponent > xAccContext (xParent,UNO_QUERY); + if(xAccContext.is()) + { + return xAccContext->getBackground(); + } + } + return SwAccessibleContext::getBackground(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accheaderfooter.hxx b/sw/source/core/access/accheaderfooter.hxx new file mode 100644 index 000000000..f948311c7 --- /dev/null +++ b/sw/source/core/access/accheaderfooter.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHEADERFOOTER_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHEADERFOOTER_HXX + +#include "acccontext.hxx" + +class SwHeaderFrame; +class SwFooterFrame; + +class SwAccessibleHeaderFooter : public SwAccessibleContext +{ +protected: + virtual ~SwAccessibleHeaderFooter() override; + +public: + SwAccessibleHeaderFooter( std::shared_ptr const& pInitMap, + const SwHeaderFrame* pHdFrame ); + SwAccessibleHeaderFooter( std::shared_ptr const& pInitMap, + const SwFooterFrame* pFtFrame ); + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchyperlink.cxx b/sw/source/core/access/acchyperlink.cxx new file mode 100644 index 000000000..74f9ac443 --- /dev/null +++ b/sw/source/core/access/acchyperlink.cxx @@ -0,0 +1,237 @@ +/* -*- 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 "accpara.hxx" +#include "acchyperlink.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using ::com::sun::star::lang::IndexOutOfBoundsException; + +SwAccessibleHyperlink::SwAccessibleHyperlink(const SwTextAttr & rTextAttr, + SwAccessibleParagraph & rAccPara, + sal_Int32 const nStt, sal_Int32 const nEnd) + : m_pHyperlink(const_cast(&rTextAttr.GetINetFormat())) + , m_xParagraph(&rAccPara) + , m_nStartIndex( nStt ) + , m_nEndIndex( nEnd ) +{ + StartListening(m_pHyperlink->GetNotifier()); +} + +SwAccessibleHyperlink::~SwAccessibleHyperlink() +{ + Invalidate(); // with SolarMutex! +} + +// when the pool item dies, invalidate! this is the only reason for Listener... +void SwAccessibleHyperlink::Notify(SfxHint const& rHint) +{ + if (rHint.GetId() == SfxHintId::Dying) + { + Invalidate(); + } +} + +// both the parent SwAccessibleParagraph and the pool-item must be valid +const SwFormatINetFormat *SwAccessibleHyperlink::GetTextAttr() const +{ + return (m_xParagraph.is() && m_xParagraph->GetMap()) + ? m_pHyperlink + : nullptr; +} + +// XAccessibleAction +sal_Int32 SAL_CALL SwAccessibleHyperlink::getAccessibleActionCount() +{ + return isValid() ? 1 : 0; +} + +sal_Bool SAL_CALL SwAccessibleHyperlink::doAccessibleAction( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + bool bRet = false; + + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + SwFormatINetFormat const*const pINetFormat = GetTextAttr(); + if (pINetFormat && !pINetFormat->GetValue().isEmpty()) + { + SwViewShell *pVSh = m_xParagraph->GetShell(); + if (pVSh) + { + LoadURL(*pVSh, pINetFormat->GetValue(), LoadUrlFlags::NONE, + pINetFormat->GetTargetFrame()); + const SwTextINetFormat *const pTextAttr = pINetFormat->GetTextINetFormat(); + if (pTextAttr) + { + const_cast(pTextAttr)->SetVisited(true); + const_cast(pTextAttr)->SetVisitedValid(true); + } + bRet = true; + } + } + + return bRet; +} + +OUString SAL_CALL SwAccessibleHyperlink::getAccessibleActionDescription( + sal_Int32 nIndex ) +{ + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + + SolarMutexGuard g; + if (SwFormatINetFormat const*const pINetFormat = GetTextAttr()) + { + return pINetFormat->GetValue(); + } + + return OUString(); +} + +uno::Reference< XAccessibleKeyBinding > SAL_CALL + SwAccessibleHyperlink::getAccessibleActionKeyBinding( sal_Int32 ) +{ + uno::Reference< XAccessibleKeyBinding > xKeyBinding; + + if( isValid() ) + { + ::comphelper::OAccessibleKeyBindingHelper* pKeyBindingHelper = + new ::comphelper::OAccessibleKeyBindingHelper(); + xKeyBinding = pKeyBindingHelper; + + awt::KeyStroke aKeyStroke; + aKeyStroke.Modifiers = 0; + aKeyStroke.KeyCode = KEY_RETURN; + aKeyStroke.KeyChar = 0; + aKeyStroke.KeyFunc = 0; + pKeyBindingHelper->AddKeyBinding( aKeyStroke ); + } + + return xKeyBinding; +} + +// XAccessibleHyperlink +uno::Any SAL_CALL SwAccessibleHyperlink::getAccessibleActionAnchor( + sal_Int32 nIndex) +{ + SolarMutexGuard g; + + uno::Any aRet; + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + OUString text( m_xParagraph->GetString() ); + OUString retText = text.copy(m_nStartIndex, m_nEndIndex - m_nStartIndex); + aRet <<= retText; + return aRet; +} + +uno::Any SAL_CALL SwAccessibleHyperlink::getAccessibleActionObject( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if(nIndex != 0) + throw lang::IndexOutOfBoundsException(); + OUString retText; + if (SwFormatINetFormat const*const pINetFormat = GetTextAttr()) + { + retText = pINetFormat->GetValue(); + } + uno::Any aRet; + aRet <<= retText; + return aRet; +} + +sal_Int32 SAL_CALL SwAccessibleHyperlink::getStartIndex() +{ + return m_nStartIndex; +} + +sal_Int32 SAL_CALL SwAccessibleHyperlink::getEndIndex() +{ + return m_nEndIndex; +} + +sal_Bool SAL_CALL SwAccessibleHyperlink::isValid( ) +{ + SolarMutexGuard aGuard; + if (m_xParagraph.is()) + { + if (SwFormatINetFormat const*const pINetFormat = GetTextAttr()) + { + OUString const sText(pINetFormat->GetValue()); + OUString sToken = "#"; + sal_Int32 nPos = sText.indexOf(sToken); + if (nPos==0)//document link + { + uno::Reference< lang::XMultiServiceFactory > xFactory( ::comphelper::getProcessServiceFactory() ); + if( ! xFactory.is() ) + return false; + uno::Reference< css::frame::XDesktop > xDesktop( xFactory->createInstance( "com.sun.star.frame.Desktop" ), + uno::UNO_QUERY ); + if( !xDesktop.is() ) + return false; + uno::Reference< lang::XComponent > xComp = xDesktop->getCurrentComponent(); + if( !xComp.is() ) + return false; + uno::Reference< css::document::XLinkTargetSupplier > xLTS(xComp, uno::UNO_QUERY); + if ( !xLTS.is()) + return false; + + uno::Reference< css::container::XNameAccess > xLinks = xLTS->getLinks(); + uno::Reference< css::container::XNameAccess > xSubLinks; + const uno::Sequence< OUString > aNames( xLinks->getElementNames() ); + + for( const OUString& aLink : aNames ) + { + uno::Any aAny = xLinks->getByName( aLink ); + aAny >>= xSubLinks; + if (xSubLinks->hasByName(sText.copy(1)) ) + return true; + } + } + else//internet + return true; + } + }//xpara valid + return false; +} + +void SwAccessibleHyperlink::Invalidate() +{ + SolarMutexGuard aGuard; + m_xParagraph = nullptr; + m_pHyperlink = nullptr; + EndListeningAll(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchyperlink.hxx b/sw/source/core/access/acchyperlink.hxx new file mode 100644 index 000000000..68116d99d --- /dev/null +++ b/sw/source/core/access/acchyperlink.hxx @@ -0,0 +1,75 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERLINK_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERLINK_HXX + +#include +#include +#include +#include + +class SwFormatINetFormat; +class SwAccessibleParagraph; +class SwTextAttr; + +class SwAccessibleHyperlink + : public ::cppu::WeakImplHelper + , public SvtListener +{ + friend class SwAccessibleParagraph; + friend class SwAccessibleHyperTextData; + SwFormatINetFormat * m_pHyperlink; + ::rtl::Reference< SwAccessibleParagraph > m_xParagraph; + sal_Int32 m_nStartIndex; + sal_Int32 m_nEndIndex; + + SwAccessibleHyperlink(const SwTextAttr &, + SwAccessibleParagraph &, + sal_Int32 nStt, sal_Int32 nEnd ); + virtual ~SwAccessibleHyperlink() override; + + const SwFormatINetFormat* GetTextAttr() const; + void Invalidate(); + + virtual void Notify(SfxHint const& rHint) override; + +public: + // XAccessibleAction + virtual sal_Int32 SAL_CALL getAccessibleActionCount() override; + virtual sal_Bool SAL_CALL doAccessibleAction( sal_Int32 nIndex ) override; + virtual OUString SAL_CALL getAccessibleActionDescription( + sal_Int32 nIndex ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleKeyBinding > SAL_CALL + getAccessibleActionKeyBinding( sal_Int32 nIndex ) override; + + // XAccessibleHyperlink + virtual css::uno::Any SAL_CALL getAccessibleActionAnchor( + sal_Int32 nIndex ) override; + virtual css::uno::Any SAL_CALL getAccessibleActionObject( + sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getStartIndex() override; + virtual sal_Int32 SAL_CALL getEndIndex() override; + virtual sal_Bool SAL_CALL isValid( ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchypertextdata.cxx b/sw/source/core/access/acchypertextdata.cxx new file mode 100644 index 000000000..c62465d12 --- /dev/null +++ b/sw/source/core/access/acchypertextdata.cxx @@ -0,0 +1,47 @@ +/* -*- 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 "acchyperlink.hxx" +#include "acchypertextdata.hxx" + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +SwAccessibleHyperTextData::SwAccessibleHyperTextData() : + maMap() +{ +} + +SwAccessibleHyperTextData::~SwAccessibleHyperTextData() +{ + iterator aIter = begin(); + while( aIter != end() ) + { + Reference < XAccessibleHyperlink > xTmp = (*aIter).second; + if( xTmp.is() ) + { + SwAccessibleHyperlink *pTmp = + static_cast< SwAccessibleHyperlink * >( xTmp.get() ); + pTmp->Invalidate(); + } + ++aIter; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acchypertextdata.hxx b/sw/source/core/access/acchypertextdata.hxx new file mode 100644 index 000000000..01833885a --- /dev/null +++ b/sw/source/core/access/acchypertextdata.hxx @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERTEXTDATA_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCHYPERTEXTDATA_HXX + +#include +#include + +class SwTextAttr; +namespace com::sun::star::accessibility { + class XAccessibleHyperlink; +} + +class SwAccessibleHyperTextData +{ +public: + typedef const SwTextAttr * key_type; + typedef css::uno::WeakReference< css::accessibility::XAccessibleHyperlink > mapped_type; + typedef std::pair value_type; + typedef std::less< const SwTextAttr * > key_compare; + typedef std::map::iterator iterator; +private: + std::map maMap; +public: + SwAccessibleHyperTextData(); + ~SwAccessibleHyperTextData(); + + iterator begin() { return maMap.begin(); } + iterator end() { return maMap.end(); } + iterator find(const key_type& key) { return maMap.find(key); } + template + std::pair emplace(Args&&... args) { return maMap.emplace(std::forward(args)...); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accmap.cxx b/sw/source/core/access/accmap.cxx new file mode 100644 index 000000000..dd63845d4 --- /dev/null +++ b/sw/source/core/access/accmap.cxx @@ -0,0 +1,3527 @@ +/* -*- 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 "acccontext.hxx" +#include "accdoc.hxx" +#include +#include "accpreview.hxx" +#include "accpage.hxx" +#include "accpara.hxx" +#include "accheaderfooter.hxx" +#include "accfootnote.hxx" +#include "acctextframe.hxx" +#include "accgraphic.hxx" +#include "accembedded.hxx" +#include "acccell.hxx" +#include "acctable.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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::accessibility; +using namespace ::sw::access; + +namespace { + +struct SwFrameFunc +{ + bool operator()( const SwFrame * p1, const SwFrame * p2) const + { + return p1 < p2; + } +}; + +} + +class SwAccessibleContextMap_Impl +{ +public: + typedef const SwFrame * key_type; + typedef uno::WeakReference < XAccessible > mapped_type; + typedef std::pair value_type; + typedef SwFrameFunc key_compare; + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; +private: + std::map maMap; +public: + +#if OSL_DEBUG_LEVEL > 0 + bool mbLocked; +#endif + + SwAccessibleContextMap_Impl() +#if OSL_DEBUG_LEVEL > 0 + : mbLocked( false ) +#endif + {} + + iterator begin() { return maMap.begin(); } + iterator end() { return maMap.end(); } + bool empty() const { return maMap.empty(); } + void clear() { maMap.clear(); } + iterator find(const key_type& key) { return maMap.find(key); } + template + std::pair emplace(Args&&... args) { return maMap.emplace(std::forward(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +namespace { + +class SwDrawModellListener_Impl : public SfxListener, + public ::cppu::WeakImplHelper< document::XShapeEventBroadcaster > +{ + mutable ::osl::Mutex maListenerMutex; + ::comphelper::OInterfaceContainerHelper2 maEventListeners; + std::unordered_multimap, css::uno::Reference< css::document::XShapeEventListener >> maShapeListeners; + SdrModel *mpDrawModel; +protected: + virtual ~SwDrawModellListener_Impl() override; + +public: + explicit SwDrawModellListener_Impl( SdrModel *pDrawModel ); + + // css::document::XEventBroadcaster + virtual void SAL_CALL addEventListener( const uno::Reference< document::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const uno::Reference< document::XEventListener >& xListener ) override; + // css::document::XShapeEventBroadcaster + virtual void SAL_CALL addShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::document::XShapeEventListener >& xListener ) override; + virtual void SAL_CALL removeShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::document::XShapeEventListener >& xListener ) override; + + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + void Dispose(); +}; + +} + +SwDrawModellListener_Impl::SwDrawModellListener_Impl( SdrModel *pDrawModel ) : + maEventListeners( maListenerMutex ), + mpDrawModel( pDrawModel ) +{ + StartListening( *mpDrawModel ); +} + +SwDrawModellListener_Impl::~SwDrawModellListener_Impl() +{ + Dispose(); +} + +void SAL_CALL SwDrawModellListener_Impl::addEventListener( const uno::Reference< document::XEventListener >& xListener ) +{ + maEventListeners.addInterface( xListener ); +} + +void SAL_CALL SwDrawModellListener_Impl::removeEventListener( const uno::Reference< document::XEventListener >& xListener ) +{ + maEventListeners.removeInterface( xListener ); +} + +void SAL_CALL SwDrawModellListener_Impl::addShapeEventListener( + const css::uno::Reference< css::drawing::XShape >& xShape, + const uno::Reference< document::XShapeEventListener >& xListener ) +{ + assert(xShape.is() && "no shape?"); + osl::MutexGuard aGuard(maListenerMutex); + maShapeListeners.emplace(xShape, xListener); +} + +void SAL_CALL SwDrawModellListener_Impl::removeShapeEventListener( + const css::uno::Reference< css::drawing::XShape >& xShape, + const uno::Reference< document::XShapeEventListener >& xListener ) +{ + osl::MutexGuard aGuard(maListenerMutex); + auto [itBegin, itEnd] = maShapeListeners.equal_range(xShape); + for (auto it = itBegin; it != itEnd; ++it) + if (it->second == xListener) + { + maShapeListeners.erase(it); + return; + } +} + +void SwDrawModellListener_Impl::Notify( SfxBroadcaster& /*rBC*/, + const SfxHint& rHint ) +{ + // do not broadcast notifications for writer fly frames, because there + // are no shapes that need to know about them. + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint *pSdrHint = static_cast( &rHint ); + if (pSdrHint->GetObject() && + ( dynamic_cast< const SwFlyDrawObj* >(pSdrHint->GetObject()) != nullptr || + dynamic_cast< const SwVirtFlyDrawObj* >(pSdrHint->GetObject()) != nullptr || + typeid(SdrObject) == typeid(pSdrHint->GetObject()) ) ) + { + return; + } + + OSL_ENSURE( mpDrawModel, "draw model listener is disposed" ); + if( !mpDrawModel ) + return; + + document::EventObject aEvent; + if( !SvxUnoDrawMSFactory::createEvent( mpDrawModel, pSdrHint, aEvent ) ) + return; + + ::comphelper::OInterfaceIteratorHelper2 aIter( maEventListeners ); + while( aIter.hasMoreElements() ) + { + uno::Reference < document::XEventListener > xListener( aIter.next(), + uno::UNO_QUERY ); + try + { + xListener->notifyEvent( aEvent ); + } + catch( uno::RuntimeException const & ) + { + TOOLS_WARN_EXCEPTION("sw.a11y", "Runtime exception caught while notifying shape"); + } + } + + // right now, we're only handling the specific event necessary to fix this performance problem + if (pSdrHint->GetKind() == SdrHintKind::ObjectChange) + { + auto pSdrObject = const_cast(pSdrHint->GetObject()); + uno::Reference xShape(pSdrObject->getUnoShape(), uno::UNO_QUERY); + osl::MutexGuard aGuard(maListenerMutex); + auto [itBegin, itEnd] = maShapeListeners.equal_range(xShape); + for (auto it = itBegin; it != itEnd; ++it) + it->second->notifyShapeEvent(aEvent); + } +} + +void SwDrawModellListener_Impl::Dispose() +{ + if (mpDrawModel != nullptr) { + EndListening( *mpDrawModel ); + } + mpDrawModel = nullptr; +} + +namespace { + +struct SwShapeFunc +{ + bool operator()( const SdrObject * p1, const SdrObject * p2) const + { + return p1 < p2; + } +}; + +} + +typedef std::pair < const SdrObject *, ::rtl::Reference < ::accessibility::AccessibleShape > > SwAccessibleObjShape_Impl; + +class SwAccessibleShapeMap_Impl +{ +public: + + typedef const SdrObject * key_type; + typedef uno::WeakReference mapped_type; + typedef std::pair value_type; + typedef SwShapeFunc key_compare; + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; + +private: + + ::accessibility::AccessibleShapeTreeInfo maInfo; + std::map maMap; + +public: + + explicit SwAccessibleShapeMap_Impl( SwAccessibleMap const *pMap ) + : maMap() + { + maInfo.SetSdrView( pMap->GetShell()->GetDrawView() ); + maInfo.SetDevice( pMap->GetShell()->GetWin() ); + maInfo.SetViewForwarder( pMap ); + uno::Reference < document::XShapeEventBroadcaster > xModelBroadcaster = + new SwDrawModellListener_Impl( + pMap->GetShell()->getIDocumentDrawModelAccess().GetOrCreateDrawModel() ); + maInfo.SetModelBroadcaster( xModelBroadcaster ); + } + + ~SwAccessibleShapeMap_Impl(); + + const ::accessibility::AccessibleShapeTreeInfo& GetInfo() const { return maInfo; } + + std::unique_ptr Copy( size_t& rSize, + const SwFEShell *pFESh, + SwAccessibleObjShape_Impl **pSelShape ) const; + + iterator end() { return maMap.end(); } + const_iterator cbegin() const { return maMap.cbegin(); } + const_iterator cend() const { return maMap.cend(); } + bool empty() const { return maMap.empty(); } + iterator find(const key_type& key) { return maMap.find(key); } + template + std::pair emplace(Args&&... args) { return maMap.emplace(std::forward(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +SwAccessibleShapeMap_Impl::~SwAccessibleShapeMap_Impl() +{ + uno::Reference < document::XEventBroadcaster > xBrd( maInfo.GetModelBroadcaster() ); + if( xBrd.is() ) + static_cast < SwDrawModellListener_Impl * >( xBrd.get() )->Dispose(); +} + +std::unique_ptr + SwAccessibleShapeMap_Impl::Copy( + size_t& rSize, const SwFEShell *pFESh, + SwAccessibleObjShape_Impl **pSelStart ) const +{ + std::unique_ptr pShapes; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + + size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + rSize = maMap.size(); + + if( rSize > 0 ) + { + pShapes.reset(new SwAccessibleObjShape_Impl[rSize]); + + SwAccessibleObjShape_Impl *pShape = pShapes.get(); + pSelShape = &(pShapes[rSize]); + for( const auto& rEntry : maMap ) + { + const SdrObject *pObj = rEntry.first; + uno::Reference < XAccessible > xAcc( rEntry.second ); + if( nSelShapes && pFESh && pFESh->IsObjSelected( *pObj ) ) + { + // selected objects are inserted from the back + --pSelShape; + pSelShape->first = pObj; + pSelShape->second = + static_cast < ::accessibility::AccessibleShape* >( + xAcc.get() ); + --nSelShapes; + } + else + { + pShape->first = pObj; + pShape->second = + static_cast < ::accessibility::AccessibleShape* >( + xAcc.get() ); + ++pShape; + } + } + assert(pSelShape == pShape); + } + + if( pSelStart ) + *pSelStart = pSelShape; + + return pShapes; +} + +struct SwAccessibleEvent_Impl +{ +public: + enum EventType { CARET_OR_STATES, + INVALID_CONTENT, + POS_CHANGED, + CHILD_POS_CHANGED, + SHAPE_SELECTION, + DISPOSE, + INVALID_ATTR }; + +private: + SwRect maOldBox; // the old bounds for CHILD_POS_CHANGED + // and POS_CHANGED + uno::WeakReference < XAccessible > mxAcc; // The object that fires the event + SwAccessibleChild maFrameOrObj; // the child for CHILD_POS_CHANGED and + // the same as xAcc for any other + // event type + EventType meType; // The event type + AccessibleStates mnStates; // check states or update caret pos + +public: + const SwFrame* mpParentFrame; // The object that fires the event + bool IsNoXaccParentFrame() const + { + return CHILD_POS_CHANGED == meType && mpParentFrame != nullptr; + } + +public: + SwAccessibleEvent_Impl( EventType eT, + SwAccessibleContext *pA, + const SwAccessibleChild& rFrameOrObj ) + : mxAcc( pA ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + {} + + SwAccessibleEvent_Impl( EventType eT, + const SwAccessibleChild& rFrameOrObj ) + : maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + { + assert(SwAccessibleEvent_Impl::DISPOSE == meType && + "wrong event constructor, DISPOSE only"); + } + + explicit SwAccessibleEvent_Impl( EventType eT ) + : meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + { + assert(SwAccessibleEvent_Impl::SHAPE_SELECTION == meType && + "wrong event constructor, SHAPE_SELECTION only" ); + } + + SwAccessibleEvent_Impl( EventType eT, + SwAccessibleContext *pA, + const SwAccessibleChild& rFrameOrObj, + const SwRect& rR ) + : maOldBox( rR ), + mxAcc( pA ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( nullptr ) + { + assert((SwAccessibleEvent_Impl::CHILD_POS_CHANGED == meType || + SwAccessibleEvent_Impl::POS_CHANGED == meType) && + "wrong event constructor, (CHILD_)POS_CHANGED only" ); + } + + SwAccessibleEvent_Impl( EventType eT, + SwAccessibleContext *pA, + const SwAccessibleChild& rFrameOrObj, + const AccessibleStates _nStates ) + : mxAcc( pA ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( _nStates ), + mpParentFrame( nullptr ) + { + assert( SwAccessibleEvent_Impl::CARET_OR_STATES == meType && + "wrong event constructor, CARET_OR_STATES only" ); + } + + SwAccessibleEvent_Impl( EventType eT, const SwFrame *pParentFrame, + const SwAccessibleChild& rFrameOrObj, const SwRect& rR ) : + maOldBox( rR ), + maFrameOrObj( rFrameOrObj ), + meType( eT ), + mnStates( AccessibleStates::NONE ), + mpParentFrame( pParentFrame ) + { + assert( SwAccessibleEvent_Impl::CHILD_POS_CHANGED == meType && + "wrong event constructor, CHILD_POS_CHANGED only" ); + } + + // only used in method + void SetType( EventType eT ) + { + meType = eT; + } + EventType GetType() const + { + return meType; + } + + ::rtl::Reference < SwAccessibleContext > GetContext() const + { + uno::Reference < XAccessible > xTmp( mxAcc ); + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast( xTmp.get() ) ); + + return xAccImpl; + } + + const SwRect& GetOldBox() const + { + return maOldBox; + } + // only used in method + void SetOldBox( const SwRect& rOldBox ) + { + maOldBox = rOldBox; + } + + const SwAccessibleChild& GetFrameOrObj() const + { + return maFrameOrObj; + } + + // only used in method + void SetStates( AccessibleStates _nStates ) + { + mnStates |= _nStates; + } + + bool IsUpdateCursorPos() const + { + return bool(mnStates & AccessibleStates::CARET); + } + bool IsInvalidateStates() const + { + return bool(mnStates & (AccessibleStates::EDITABLE | AccessibleStates::OPAQUE)); + } + bool IsInvalidateRelation() const + { + return bool(mnStates & (AccessibleStates::RELATION_FROM | AccessibleStates::RELATION_TO)); + } + bool IsInvalidateTextSelection() const + { + return bool( mnStates & AccessibleStates::TEXT_SELECTION_CHANGED ); + } + + bool IsInvalidateTextAttrs() const + { + return bool( mnStates & AccessibleStates::TEXT_ATTRIBUTE_CHANGED ); + } + + AccessibleStates GetStates() const + { + return mnStates; + } + + AccessibleStates GetAllStates() const + { + return mnStates; + } +}; + +class SwAccessibleEventList_Impl +{ + std::list maEvents; + bool mbFiring; + +public: + SwAccessibleEventList_Impl() + : mbFiring( false ) + {} + + void SetFiring() + { + mbFiring = true; + } + bool IsFiring() const + { + return mbFiring; + } + + void MoveMissingXAccToEnd(); + + size_t size() const { return maEvents.size(); } + std::list::iterator begin() { return maEvents.begin(); } + std::list::iterator end() { return maEvents.end(); } + std::list::iterator insert( const std::list::iterator& aIter, + const SwAccessibleEvent_Impl& rEvent ) + { + return maEvents.insert( aIter, rEvent ); + } + std::list::iterator erase( const std::list::iterator& aPos ) + { + return maEvents.erase( aPos ); + } +}; + +// see comment in SwAccessibleMap::InvalidatePosOrSize() +// last case "else if(pParent)" for why this surprising hack exists +void SwAccessibleEventList_Impl::MoveMissingXAccToEnd() +{ + size_t nSize = size(); + if (nSize < 2 ) + { + return; + } + SwAccessibleEventList_Impl lstEvent; + for (auto li = begin(); li != end(); ) + { + if (li->IsNoXaccParentFrame()) + { + lstEvent.insert(lstEvent.end(), *li); + li = erase(li); + } + else + ++li; + } + assert(size() + lstEvent.size() == nSize); + maEvents.insert(end(),lstEvent.begin(),lstEvent.end()); + assert(size() == nSize); +} + +namespace { + +struct SwAccessibleChildFunc +{ + bool operator()( const SwAccessibleChild& r1, + const SwAccessibleChild& r2 ) const + { + const void *p1 = r1.GetSwFrame() + ? static_cast < const void * >( r1.GetSwFrame()) + : ( r1.GetDrawObject() + ? static_cast < const void * >( r1.GetDrawObject() ) + : static_cast < const void * >( r1.GetWindow() ) ); + const void *p2 = r2.GetSwFrame() + ? static_cast < const void * >( r2.GetSwFrame()) + : ( r2.GetDrawObject() + ? static_cast < const void * >( r2.GetDrawObject() ) + : static_cast < const void * >( r2.GetWindow() ) ); + return p1 < p2; + } +}; + +} + +class SwAccessibleEventMap_Impl +{ +public: + typedef SwAccessibleChild key_type; + typedef std::list::iterator mapped_type; + typedef std::pair value_type; + typedef SwAccessibleChildFunc key_compare; + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; +private: + std::map maMap; +public: + iterator end() { return maMap.end(); } + iterator find(const key_type& key) { return maMap.find(key); } + template + std::pair emplace(Args&&... args) { return maMap.emplace(std::forward(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +namespace { + +struct SwAccessibleParaSelection +{ + TextFrameIndex nStartOfSelection; + TextFrameIndex nEndOfSelection; + + SwAccessibleParaSelection(const TextFrameIndex nStartOfSelection_, + const TextFrameIndex nEndOfSelection_) + : nStartOfSelection(nStartOfSelection_) + , nEndOfSelection(nEndOfSelection_) + {} +}; + +struct SwXAccWeakRefComp +{ + bool operator()( const uno::WeakReference& _rXAccWeakRef1, + const uno::WeakReference& _rXAccWeakRef2 ) const + { + return _rXAccWeakRef1.get() < _rXAccWeakRef2.get(); + } +}; + +} + +class SwAccessibleSelectedParas_Impl +{ +public: + typedef uno::WeakReference < XAccessible > key_type; + typedef SwAccessibleParaSelection mapped_type; + typedef std::pair value_type; + typedef SwXAccWeakRefComp key_compare; + typedef std::map::iterator iterator; + typedef std::map::const_iterator const_iterator; +private: + std::map maMap; +public: + iterator begin() { return maMap.begin(); } + iterator end() { return maMap.end(); } + iterator find(const key_type& key) { return maMap.find(key); } + template + std::pair emplace(Args&&... args) { return maMap.emplace(std::forward(args)...); } + iterator erase(const_iterator const & pos) { return maMap.erase(pos); } +}; + +// helper class that stores preview data +class SwAccPreviewData +{ + typedef std::vector Rectangles; + Rectangles maPreviewRects; + Rectangles maLogicRects; + + SwRect maVisArea; + Fraction maScale; + + const SwPageFrame *mpSelPage; + + /** adjust logic page rectangle to its visible part + + @param _iorLogicPgSwRect + input/output parameter - reference to the logic page rectangle, which + has to be adjusted. + + @param _rPreviewPgSwRect + input parameter - constant reference to the corresponding preview page + rectangle; needed to determine the visible part of the logic page rectangle. + + @param _rPreviewWinSize + input parameter - constant reference to the preview window size in TWIP; + needed to determine the visible part of the logic page rectangle + */ + static void AdjustLogicPgRectToVisibleArea( SwRect& _iorLogicPgSwRect, + const SwRect& _rPreviewPgSwRect, + const Size& _rPreviewWinSize ); + +public: + SwAccPreviewData(); + + void Update( const SwAccessibleMap& rAccMap, + const std::vector>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ); + + void InvalidateSelection( const SwPageFrame* _pSelectedPageFrame ); + + const SwRect& GetVisArea() const { return maVisArea;} + + /** Adjust the MapMode so that the preview page appears at the + * proper position. rPoint identifies the page for which the + * MapMode should be adjusted. If bFromPreview is true, rPoint is + * a preview coordinate; else it's a document coordinate. */ + void AdjustMapMode( MapMode& rMapMode, + const Point& rPoint ) const; + + const SwPageFrame *GetSelPage() const { return mpSelPage; } + + void DisposePage(const SwPageFrame *pPageFrame ); +}; + +SwAccPreviewData::SwAccPreviewData() : + mpSelPage( nullptr ) +{ +} + +void SwAccPreviewData::Update( const SwAccessibleMap& rAccMap, + const std::vector>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + // store preview scaling, maximal preview page size and selected page + maScale = _rScale; + mpSelPage = _pSelectedPageFrame; + + // prepare loop on preview pages + maPreviewRects.clear(); + maLogicRects.clear(); + SwAccessibleChild aPage; + maVisArea.Clear(); + + // loop on preview pages to calculate , and + // + for ( auto & rpPreviewPage : _rPreviewPages ) + { + aPage = rpPreviewPage->pPage; + + // add preview page rectangle to + tools::Rectangle aPreviewPgRect( rpPreviewPage->aPreviewWinPos, rpPreviewPage->aPageSize ); + maPreviewRects.push_back( aPreviewPgRect ); + + // add logic page rectangle to + SwRect aLogicPgSwRect( aPage.GetBox( rAccMap ) ); + tools::Rectangle aLogicPgRect( aLogicPgSwRect.SVRect() ); + maLogicRects.push_back( aLogicPgRect ); + // union visible area with visible part of logic page rectangle + if ( rpPreviewPage->bVisible ) + { + if ( !rpPreviewPage->pPage->IsEmptyPage() ) + { + AdjustLogicPgRectToVisibleArea( aLogicPgSwRect, + SwRect( aPreviewPgRect ), + _rPreviewWinSize ); + } + if ( maVisArea.IsEmpty() ) + maVisArea = aLogicPgSwRect; + else + maVisArea.Union( aLogicPgSwRect ); + } + } +} + +void SwAccPreviewData::InvalidateSelection( const SwPageFrame* _pSelectedPageFrame ) +{ + mpSelPage = _pSelectedPageFrame; + assert(mpSelPage); +} + +namespace { + +struct ContainsPredicate +{ + const Point& mrPoint; + explicit ContainsPredicate( const Point& rPoint ) : mrPoint(rPoint) {} + bool operator() ( const tools::Rectangle& rRect ) const + { + return rRect.IsInside( mrPoint ); + } +}; + +} + +void SwAccPreviewData::AdjustMapMode( MapMode& rMapMode, + const Point& rPoint ) const +{ + // adjust scale + rMapMode.SetScaleX( maScale ); + rMapMode.SetScaleY( maScale ); + + // find proper rectangle + Rectangles::const_iterator aBegin = maLogicRects.begin(); + Rectangles::const_iterator aEnd = maLogicRects.end(); + Rectangles::const_iterator aFound = std::find_if( aBegin, aEnd, + ContainsPredicate( rPoint ) ); + + if( aFound != aEnd ) + { + // found! set new origin + Point aPoint = (maPreviewRects.begin() + (aFound - aBegin))->TopLeft(); + aPoint -= (maLogicRects.begin() + (aFound-aBegin))->TopLeft(); + rMapMode.SetOrigin( aPoint ); + } + // else: don't adjust MapMode +} + +void SwAccPreviewData::DisposePage(const SwPageFrame *pPageFrame ) +{ + if( mpSelPage == pPageFrame ) + mpSelPage = nullptr; +} + +// adjust logic page rectangle to its visible part +void SwAccPreviewData::AdjustLogicPgRectToVisibleArea( + SwRect& _iorLogicPgSwRect, + const SwRect& _rPreviewPgSwRect, + const Size& _rPreviewWinSize ) +{ + // determine preview window rectangle + const SwRect aPreviewWinSwRect( Point( 0, 0 ), _rPreviewWinSize ); + // calculate visible preview page rectangle + SwRect aVisPreviewPgSwRect( _rPreviewPgSwRect ); + aVisPreviewPgSwRect.Intersection( aPreviewWinSwRect ); + // adjust logic page rectangle + SwTwips nTmpDiff; + // left + nTmpDiff = aVisPreviewPgSwRect.Left() - _rPreviewPgSwRect.Left(); + _iorLogicPgSwRect.AddLeft( nTmpDiff ); + // top + nTmpDiff = aVisPreviewPgSwRect.Top() - _rPreviewPgSwRect.Top(); + _iorLogicPgSwRect.AddTop( nTmpDiff ); + // right + nTmpDiff = _rPreviewPgSwRect.Right() - aVisPreviewPgSwRect.Right(); + _iorLogicPgSwRect.AddRight( - nTmpDiff ); + // bottom + nTmpDiff = _rPreviewPgSwRect.Bottom() - aVisPreviewPgSwRect.Bottom(); + _iorLogicPgSwRect.AddBottom( - nTmpDiff ); +} + +static bool AreInSameTable( const uno::Reference< XAccessible >& rAcc, + const SwFrame *pFrame ) +{ + bool bRet = false; + + if( pFrame && pFrame->IsCellFrame() && rAcc.is() ) + { + // Is it in the same table? We check that + // by comparing the last table frame in the + // follow chain, because that's cheaper than + // searching the first one. + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( rAcc.get() ); + if( pAccImpl->GetFrame()->IsCellFrame() ) + { + const SwTabFrame *pTabFrame1 = pAccImpl->GetFrame()->FindTabFrame(); + if (pTabFrame1) + { + while (pTabFrame1->GetFollow()) + pTabFrame1 = pTabFrame1->GetFollow(); + } + + const SwTabFrame *pTabFrame2 = pFrame->FindTabFrame(); + if (pTabFrame2) + { + while (pTabFrame2->GetFollow()) + pTabFrame2 = pTabFrame2->GetFollow(); + } + + bRet = (pTabFrame1 == pTabFrame2); + } + } + + return bRet; +} + +void SwAccessibleMap::FireEvent( const SwAccessibleEvent_Impl& rEvent ) +{ + ::rtl::Reference < SwAccessibleContext > xAccImpl( rEvent.GetContext() ); + if (!xAccImpl.is() && rEvent.mpParentFrame != nullptr) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( rEvent.mpParentFrame ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if (xAcc.is()) + { + uno::Reference < XAccessibleContext > xContext(xAcc,uno::UNO_QUERY); + if (xContext.is() && xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + xAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + } + } + if( SwAccessibleEvent_Impl::SHAPE_SELECTION == rEvent.GetType() ) + { + DoInvalidateShapeSelection(); + } + else if( xAccImpl.is() && xAccImpl->GetFrame() ) + { + if ( rEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE && + rEvent.IsInvalidateTextAttrs() ) + { + xAccImpl->InvalidateAttr(); + } + switch( rEvent.GetType() ) + { + case SwAccessibleEvent_Impl::INVALID_CONTENT: + xAccImpl->InvalidateContent(); + break; + case SwAccessibleEvent_Impl::POS_CHANGED: + xAccImpl->InvalidatePosOrSize( rEvent.GetOldBox() ); + break; + case SwAccessibleEvent_Impl::CHILD_POS_CHANGED: + xAccImpl->InvalidateChildPosOrSize( rEvent.GetFrameOrObj(), + rEvent.GetOldBox() ); + break; + case SwAccessibleEvent_Impl::DISPOSE: + assert(!"dispose event has been stored"); + break; + case SwAccessibleEvent_Impl::INVALID_ATTR: + // nothing to do here - handled above + break; + default: + break; + } + if( SwAccessibleEvent_Impl::DISPOSE != rEvent.GetType() ) + { + if( rEvent.IsUpdateCursorPos() ) + xAccImpl->InvalidateCursorPos(); + if( rEvent.IsInvalidateStates() ) + xAccImpl->InvalidateStates( rEvent.GetStates() ); + if( rEvent.IsInvalidateRelation() ) + { + // both events CONTENT_FLOWS_FROM_RELATION_CHANGED and + // CONTENT_FLOWS_TO_RELATION_CHANGED are possible + if ( rEvent.GetAllStates() & AccessibleStates::RELATION_FROM ) + { + xAccImpl->InvalidateRelation( + AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED ); + } + if ( rEvent.GetAllStates() & AccessibleStates::RELATION_TO ) + { + xAccImpl->InvalidateRelation( + AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED ); + } + } + + if ( rEvent.IsInvalidateTextSelection() ) + { + xAccImpl->InvalidateTextSelection(); + } + } + } +} + +void SwAccessibleMap::AppendEvent( const SwAccessibleEvent_Impl& rEvent ) +{ + osl::MutexGuard aGuard( maEventMutex ); + + if( !mpEvents ) + mpEvents.reset(new SwAccessibleEventList_Impl); + if( !mpEventMap ) + mpEventMap.reset(new SwAccessibleEventMap_Impl); + + if( mpEvents->IsFiring() ) + { + // While events are fired new ones are generated. They have to be fired + // now. This does not work for DISPOSE events! + OSL_ENSURE( rEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE, + "dispose event while firing events" ); + FireEvent( rEvent ); + } + else + { + + SwAccessibleEventMap_Impl::iterator aIter = + mpEventMap->find( rEvent.GetFrameOrObj() ); + if( aIter != mpEventMap->end() ) + { + SwAccessibleEvent_Impl aEvent( *(*aIter).second ); + assert( aEvent.GetType() != SwAccessibleEvent_Impl::DISPOSE && + "dispose events should not be stored" ); + bool bAppendEvent = true; + switch( rEvent.GetType() ) + { + case SwAccessibleEvent_Impl::CARET_OR_STATES: + // A CARET_OR_STATES event is added to any other + // event only. It is broadcasted after any other event, so the + // event should be put to the back. + OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + aEvent.SetStates( rEvent.GetAllStates() ); + break; + case SwAccessibleEvent_Impl::INVALID_CONTENT: + // An INVALID_CONTENT event overwrites a CARET_OR_STATES + // event (but keeps its flags) and it is contained in a + // POS_CHANGED event. + // Therefore, the event's type has to be adapted and the event + // has to be put at the end. + // + // fdo#56031 An INVALID_CONTENT event overwrites a INVALID_ATTR + // event and overwrites its flags + OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + if( aEvent.GetType() == SwAccessibleEvent_Impl::CARET_OR_STATES ) + aEvent.SetType( SwAccessibleEvent_Impl::INVALID_CONTENT ); + else if ( aEvent.GetType() == SwAccessibleEvent_Impl::INVALID_ATTR ) + { + aEvent.SetType( SwAccessibleEvent_Impl::INVALID_CONTENT ); + aEvent.SetStates( rEvent.GetAllStates() ); + } + + break; + case SwAccessibleEvent_Impl::POS_CHANGED: + // A pos changed event overwrites CARET_STATES (keeping its + // flags) as well as INVALID_CONTENT. The old box position + // has to be stored however if the old event is not a + // POS_CHANGED itself. + OSL_ENSURE( aEvent.GetType() != SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + if( aEvent.GetType() != SwAccessibleEvent_Impl::POS_CHANGED ) + aEvent.SetOldBox( rEvent.GetOldBox() ); + aEvent.SetType( SwAccessibleEvent_Impl::POS_CHANGED ); + break; + case SwAccessibleEvent_Impl::CHILD_POS_CHANGED: + // CHILD_POS_CHANGED events can only follow CHILD_POS_CHANGED + // events. The only action that needs to be done again is + // to put the old event to the back. The new one cannot be used, + // because we are interested in the old frame bounds. + OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + "invalid event combination" ); + break; + case SwAccessibleEvent_Impl::SHAPE_SELECTION: + OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::SHAPE_SELECTION, + "invalid event combination" ); + break; + case SwAccessibleEvent_Impl::DISPOSE: + // DISPOSE events overwrite all others. They are not stored + // but executed immediately to avoid broadcasting of + // nonfunctional objects. So what needs to be done here is to + // remove all events for the frame in question. + bAppendEvent = false; + break; + case SwAccessibleEvent_Impl::INVALID_ATTR: + OSL_ENSURE( aEvent.GetType() == SwAccessibleEvent_Impl::INVALID_ATTR, + "invalid event combination" ); + break; + } + if( bAppendEvent ) + { + mpEvents->erase( (*aIter).second ); + (*aIter).second = mpEvents->insert( mpEvents->end(), aEvent ); + } + else + { + mpEvents->erase( (*aIter).second ); + mpEventMap->erase( aIter ); + } + } + else if( SwAccessibleEvent_Impl::DISPOSE != rEvent.GetType() ) + { + mpEventMap->emplace( rEvent.GetFrameOrObj(), + mpEvents->insert( mpEvents->end(), rEvent ) ); + } + } +} + +void SwAccessibleMap::InvalidateCursorPosition( + const uno::Reference< XAccessible >& rAcc ) +{ + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( rAcc.get() ); + assert(pAccImpl); + assert(pAccImpl->GetFrame()); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, + SwAccessibleChild(pAccImpl->GetFrame()), + AccessibleStates::CARET ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + // While firing events the current frame might have + // been disposed because it moved out of the visible area. + // Setting the cursor for such frames is useless and even + // causes asserts. + if( pAccImpl->GetFrame() ) + pAccImpl->InvalidateCursorPos(); + } +} + +void SwAccessibleMap::InvalidateShapeSelection() +{ + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::SHAPE_SELECTION ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + DoInvalidateShapeSelection(); + } +} + +//This method should implement the following functions: +//1.find the shape objects and set the selected state. +//2.find the Swframe objects and set the selected state. +//3.find the paragraph objects and set the selected state. +void SwAccessibleMap::InvalidateShapeInParaSelection() +{ + std::unique_ptr pShapes; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + size_t nShapes = 0; + + const SwViewShell *pVSh = GetShell(); + const SwFEShell *pFESh = dynamic_cast( pVSh) != nullptr ? + static_cast< const SwFEShell * >( pVSh ) : nullptr; + SwPaM* pCursor = pFESh ? pFESh->GetCursor( false /* ??? */ ) : nullptr; + + //const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape ); + } + + bool bIsSelAll =IsDocumentSelAll(); + + if( mpShapeMap ) + { + //Checked for shapes. + SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin(); + SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend(); + + if( bIsSelAll) + { + while( aIter != aEndIter ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if( xAcc.is() ) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + + ++aIter; + } + } + else + { + while( aIter != aEndIter ) + { + const SwFrameFormat *pFrameFormat = (*aIter).first ? ::FindFrameFormat( (*aIter).first ) : nullptr; + if( !pFrameFormat ) + { + ++aIter; + continue; + } + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + const SwPosition *pPos = rAnchor.GetContentAnchor(); + + if(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if(xAcc.is()) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + + ++aIter; + continue; + } + + if( !pPos ) + { + ++aIter; + continue; + } + if( pPos->nNode.GetNode().GetTextNode() ) + { + int nIndex = pPos->nContent.GetIndex(); + bool bMarked = false; + if( pCursor != nullptr ) + { + const SwTextNode* pNode = pPos->nNode.GetNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast(pNode->getLayoutFrame(pVSh->GetLayout()))); + sal_uLong nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + sal_uLong nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + sal_uLong nHere = pNode->GetIndex(); + + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + // ignore, if no mark + if( rTmpCursor.HasMark() ) + { + bMarked = true; + // check whether nHere is 'inside' pCursor + SwPosition* pStart = rTmpCursor.Start(); + sal_uLong nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = rTmpCursor.End(); + sal_uLong nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + if( ( ((nHere == nStartIndex) && (nIndex >= pStart->nContent.GetIndex())) || (nHere > nStartIndex) ) + &&( ((nHere == nEndIndex) && (nIndex < pEnd->nContent.GetIndex())) || (nHere < nEndIndex) ) ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if( xAcc.is() ) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + } + else + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if( xAcc.is() ) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + } + else if( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA ) + { + uno::Reference const xAcc((*aIter).second); + if (xAcc.is()) + { + if (IsSelectFrameAnchoredAtPara(*pPos, *pStart, *pEnd)) + { + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + } + else + { + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + } + } + else if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + uno::Reference const xAcc((*aIter).second); + if (xAcc.is()) + { + if (IsDestroyFrameAnchoredAtChar(*pPos, *pStart, *pEnd)) + { + static_cast<::accessibility::AccessibleShape*>(xAcc.get())->SetState( AccessibleStateType::SELECTED ); + } + else + { + static_cast<::accessibility::AccessibleShape*>(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + } + } + } + } + } + } + if( !bMarked ) + { + SwAccessibleObjShape_Impl *pShape = pShapes.get(); + size_t nNumShapes = nShapes; + while( nNumShapes ) + { + if( pShape < pSelShape && (pShape->first==(*aIter).first) ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + if(xAcc.is()) + static_cast < ::accessibility::AccessibleShape* >(xAcc.get())->ResetState( AccessibleStateType::SELECTED ); + } + --nNumShapes; + ++pShape; + } + } + } + + ++aIter; + }//while( aIter != aEndIter ) + }//else + } + + pShapes.reset(); + + //Checked for FlyFrame + if (mpFrameMap) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->begin(); + while( aIter != mpFrameMap->end() ) + { + const SwFrame *pFrame = (*aIter).first; + if(pFrame->IsFlyFrame()) + { + uno::Reference < XAccessible > xAcc = (*aIter).second; + + if(xAcc.is()) + { + SwAccessibleFrameBase *pAccFrame = static_cast< SwAccessibleFrameBase * >(xAcc.get()); + bool bFrameChanged = pAccFrame->SetSelectedState( true ); + if (bFrameChanged) + { + const SwFlyFrame *pFlyFrame = static_cast< const SwFlyFrame * >( pFrame ); + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + if (pFrameFormat) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + uno::Reference< XAccessible > xAccParent = pAccFrame->getAccessibleParent(); + if (xAccParent.is()) + { + uno::Reference< XAccessibleContext > xAccContext = xAccParent->getAccessibleContext(); + if(xAccContext.is() && xAccContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xAccContext.get()); + if(pAccFrame->IsSeletedInDoc()) + { + m_setParaAdd.insert(pAccPara); + } + else if(m_setParaAdd.count(pAccPara) == 0) + { + m_setParaRemove.insert(pAccPara); + } + } + } + } + } + } + } + } + ++aIter; + } + } + + typedef std::vector< SwAccessibleContext* > VEC_PARA; + VEC_PARA vecAdd; + VEC_PARA vecRemove; + //Checked for Paras. + bool bMarkChanged = false; + SwAccessibleContextMap_Impl mapTemp; + if( pCursor != nullptr ) + { + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + if( rTmpCursor.HasMark() ) + { + SwNodeIndex nStartIndex( rTmpCursor.Start()->nNode ); + SwNodeIndex nEndIndex( rTmpCursor.End()->nNode ); + for (; nStartIndex <= nEndIndex; ++nStartIndex) + { + SwFrame *pFrame = nullptr; + if(nStartIndex.GetNode().IsContentNode()) + { + SwContentNode* pCNd = static_cast(&(nStartIndex.GetNode())); + pFrame = SwIterator(*pCNd).First(); + if (mapTemp.find(pFrame) != mapTemp.end()) + { + continue; // sw_redlinehide: once is enough + } + } + else if( nStartIndex.GetNode().IsTableNode() ) + { + SwTableNode * pTable = static_cast(&(nStartIndex.GetNode())); + SwTableFormat* pFormat = pTable->GetTable().GetFrameFormat(); + pFrame = SwIterator(*pFormat).First(); + } + + if( pFrame && mpFrameMap) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pFrame ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc = (*aIter).second; + bool isChanged = false; + if( xAcc.is() ) + { + isChanged = static_cast< SwAccessibleContext * >(xAcc.get())->SetSelectedState( true ); + } + if(!isChanged) + { + SwAccessibleContextMap_Impl::iterator aEraseIter = mpSeletedFrameMap->find( pFrame ); + if(aEraseIter != mpSeletedFrameMap->end()) + mpSeletedFrameMap->erase(aEraseIter); + } + else + { + bMarkChanged = true; + vecAdd.push_back(static_cast< SwAccessibleContext * >(xAcc.get())); + } + + mapTemp.emplace( pFrame, xAcc ); + } + } + } + } + } + } + if( !mpSeletedFrameMap ) + mpSeletedFrameMap.reset( new SwAccessibleContextMap_Impl ); + if( !mpSeletedFrameMap->empty() ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpSeletedFrameMap->begin(); + while( aIter != mpSeletedFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc = (*aIter).second; + if(xAcc.is()) + static_cast< SwAccessibleContext * >(xAcc.get())->SetSelectedState( false ); + ++aIter; + vecRemove.push_back(static_cast< SwAccessibleContext * >(xAcc.get())); + } + bMarkChanged = true; + mpSeletedFrameMap->clear(); + } + + SwAccessibleContextMap_Impl::iterator aIter = mapTemp.begin(); + while( aIter != mapTemp.end() ) + { + mpSeletedFrameMap->emplace( (*aIter).first, (*aIter).second ); + ++aIter; + } + mapTemp.clear(); + + if( bMarkChanged && mpFrameMap) + { + for (SwAccessibleContext* pAccPara : vecAdd) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + if (pAccPara) + { + pAccPara->FireAccessibleEvent( aEvent ); + } + } + for (SwAccessibleContext* pAccPara : vecRemove) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + if (pAccPara) + { + pAccPara->FireAccessibleEvent( aEvent ); + } + } + } +} + +//Merge with DoInvalidateShapeFocus +void SwAccessibleMap::DoInvalidateShapeSelection(bool bInvalidateFocusMode /*=false*/) +{ + std::unique_ptr pShapes; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + size_t nShapes = 0; + + const SwViewShell *pVSh = GetShell(); + const SwFEShell *pFESh = dynamic_cast( pVSh) != nullptr ? + static_cast< const SwFEShell * >( pVSh ) : nullptr; + const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + + //when InvalidateFocus Call this function ,and the current selected shape count is not 1 , + //return + if (bInvalidateFocusMode && nSelShapes != 1) + { + return; + } + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape ); + } + + if( !pShapes ) + return; + + typedef std::vector< ::rtl::Reference < ::accessibility::AccessibleShape > > VEC_SHAPE; + VEC_SHAPE vecxShapeAdd; + VEC_SHAPE vecxShapeRemove; + int nCountSelectedShape=0; + + vcl::Window *pWin = GetShell()->GetWin(); + bool bFocused = pWin && pWin->HasFocus(); + SwAccessibleObjShape_Impl *pShape = pShapes.get(); + int nShapeCount = nShapes; + while( nShapeCount ) + { + if (pShape->second.is() && IsInSameLevel(pShape->first, pFESh)) + { + if( pShape < pSelShape ) + { + if(pShape->second->ResetState( AccessibleStateType::SELECTED )) + { + vecxShapeRemove.push_back(pShape->second); + } + pShape->second->ResetState( AccessibleStateType::FOCUSED ); + } + } + --nShapeCount; + ++pShape; + } + + for (const auto& rpShape : vecxShapeRemove) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape) + { + pAccShape->CommitChange(AccessibleEventId::SELECTION_CHANGED_REMOVE, uno::Any(), uno::Any()); + } + } + + pShape = pShapes.get(); + + while( nShapes ) + { + if (pShape->second.is() && IsInSameLevel(pShape->first, pFESh)) + { + if( pShape >= pSelShape ) + { + //first fire focus event + if( bFocused && 1 == nSelShapes ) + pShape->second->SetState( AccessibleStateType::FOCUSED ); + else + pShape->second->ResetState( AccessibleStateType::FOCUSED ); + + if(pShape->second->SetState( AccessibleStateType::SELECTED )) + { + vecxShapeAdd.push_back(pShape->second); + } + ++nCountSelectedShape; + } + } + + --nShapes; + ++pShape; + } + + const unsigned int SELECTION_WITH_NUM = 10; + if (vecxShapeAdd.size() > SELECTION_WITH_NUM ) + { + uno::Reference< XAccessible > xDoc = GetDocumentView( ); + SwAccessibleContext * pCont = static_cast(xDoc.get()); + if (pCont) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + pCont->FireAccessibleEvent(aEvent); + } + } + else + { + short nEventID = AccessibleEventId::SELECTION_CHANGED_ADD; + if (nCountSelectedShape <= 1 && vecxShapeAdd.size() == 1 ) + { + nEventID = AccessibleEventId::SELECTION_CHANGED; + } + for (const auto& rpShape : vecxShapeAdd) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape) + { + pAccShape->CommitChange(nEventID, uno::Any(), uno::Any()); + } + } + } + + for (const auto& rpShape : vecxShapeAdd) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape) + { + SdrObject *pObj = GetSdrObjectFromXShape(pAccShape->GetXShape()); + SwFrameFormat *pFrameFormat = pObj ? FindFrameFormat( pObj ) : nullptr; + if (pFrameFormat) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent(); + if (xPara.is()) + { + uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext(); + if (xParaContext.is() && xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get()); + if (pAccPara) + { + m_setParaAdd.insert(pAccPara); + } + } + } + } + } + } + } + for (const auto& rpShape : vecxShapeRemove) + { + ::accessibility::AccessibleShape *pAccShape = rpShape.get(); + if (pAccShape && !pAccShape->IsDisposed()) + { + uno::Reference< XAccessible > xPara = pAccShape->getAccessibleParent(); + uno::Reference< XAccessibleContext > xParaContext = xPara->getAccessibleContext(); + if (xParaContext.is() && xParaContext->getAccessibleRole() == AccessibleRole::PARAGRAPH) + { + SwAccessibleParagraph* pAccPara = static_cast< SwAccessibleParagraph *>(xPara.get()); + if (m_setParaAdd.count(pAccPara) == 0 ) + { + m_setParaRemove.insert(pAccPara); + } + } + } + } +} + +//Merge with DoInvalidateShapeSelection +/* +void SwAccessibleMap::DoInvalidateShapeFocus() +{ + const SwViewShell *pVSh = GetShell(); + const SwFEShell *pFESh = dynamic_cast( pVSh) != nullptr ? + static_cast< const SwFEShell * >( pVSh ) : nullptr; + const size_t nSelShapes = pFESh ? pFESh->IsObjSelected() : 0; + + if( nSelShapes != 1 ) + return; + + SwAccessibleObjShape_Impl *pShapes = nullptr; + SwAccessibleObjShape_Impl *pSelShape = nullptr; + size_t nShapes = 0; + + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + pShapes = mpShapeMap->Copy( nShapes, pFESh, &pSelShape ); + } + + if( pShapes ) + { + vcl::Window *pWin = GetShell()->GetWin(); + bool bFocused = pWin && pWin->HasFocus(); + SwAccessibleObjShape_Impl *pShape = pShapes; + while( nShapes ) + { + if( pShape->second.is() ) + { + if( bFocused && pShape >= pSelShape ) + pShape->second->SetState( AccessibleStateType::FOCUSED ); + else + pShape->second->ResetState( AccessibleStateType::FOCUSED ); + } + + --nShapes; + ++pShape; + } + + delete[] pShapes; + } +} + +*/ + +SwAccessibleMap::SwAccessibleMap( SwViewShell *pSh ) : + mpVSh( pSh ), + mbShapeSelected( false ), + maDocName(SwAccessibleContext::GetResource(STR_ACCESS_DOC_NAME)) +{ + pSh->GetLayout()->AddAccessibleShell(); +} + +SwAccessibleMap::~SwAccessibleMap() +{ + DBG_TESTSOLARMUTEX(); + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + if( mpFrameMap ) + { + const SwRootFrame *pRootFrame = GetShell()->GetLayout(); + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pRootFrame ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + if( !xAcc.is() ) + assert(false); // let's hope this can't happen? the vcl::Window apparently owns the top-level + //xAcc = new SwAccessibleDocument(shared_from_this()); + } + } + + if(xAcc.is()) + { + SwAccessibleDocumentBase *const pAcc = + static_cast(xAcc.get()); + pAcc->Dispose( true ); + } +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->begin(); + while( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xTmp = (*aIter).second; + if( xTmp.is() ) + { + SwAccessibleContext *pTmp = static_cast< SwAccessibleContext * >( xTmp.get() ); + assert(pTmp->GetMap() == nullptr); // must be disposed + } + ++aIter; + } + } +#endif + { + osl::MutexGuard aGuard( maMutex ); + assert((!mpFrameMap || mpFrameMap->empty()) && + "Frame map should be empty after disposing the root frame"); + assert((!mpShapeMap || mpShapeMap->empty()) && + "Object map should be empty after disposing the root frame"); + mpFrameMap.reset(); + mpShapeMap.reset(); + mvShapes.clear(); + mpSelectedParas.reset(); + } + + mpPreview.reset(); + + { + osl::MutexGuard aGuard( maEventMutex ); + assert(!mpEvents); + assert(!mpEventMap); + mpEventMap.reset(); + mpEvents.reset(); + } + mpVSh->GetLayout()->RemoveAccessibleShell(); +} + +uno::Reference< XAccessible > SwAccessibleMap::GetDocumentView_( + bool bPagePreview ) +{ + uno::Reference < XAccessible > xAcc; + bool bSetVisArea = false; + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpFrameMap ) + { + mpFrameMap.reset(new SwAccessibleContextMap_Impl); +#if OSL_DEBUG_LEVEL > 0 + mpFrameMap->mbLocked = false; +#endif + } + +#if OSL_DEBUG_LEVEL > 0 + assert(!mpFrameMap->mbLocked); + mpFrameMap->mbLocked = true; +#endif + + const SwRootFrame *pRootFrame = GetShell()->GetLayout(); + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pRootFrame ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + if( xAcc.is() ) + { + bSetVisArea = true; // Set VisArea when map mutex is not locked + } + else + { + if( bPagePreview ) + xAcc = new SwAccessiblePreview(shared_from_this()); + else + xAcc = new SwAccessibleDocument(shared_from_this()); + + if( aIter != mpFrameMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpFrameMap->emplace( pRootFrame, xAcc ); + } + } + +#if OSL_DEBUG_LEVEL > 0 + mpFrameMap->mbLocked = false; +#endif + } + + if( bSetVisArea ) + { + SwAccessibleDocumentBase *pAcc = + static_cast< SwAccessibleDocumentBase * >( xAcc.get() ); + pAcc->SetVisArea(); + } + + return xAcc; +} + +uno::Reference< XAccessible > SwAccessibleMap::GetDocumentView( ) +{ + return GetDocumentView_( false ); +} + +uno::Reference SwAccessibleMap::GetDocumentPreview( + const std::vector>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + // create & update preview data object + if( mpPreview == nullptr ) + mpPreview.reset( new SwAccPreviewData() ); + mpPreview->Update( *this, _rPreviewPages, _rScale, _pSelectedPageFrame, _rPreviewWinSize ); + + uno::Reference xAcc = GetDocumentView_( true ); + return xAcc; +} + +uno::Reference< XAccessible> SwAccessibleMap::GetContext( const SwFrame *pFrame, + bool bCreate ) +{ + DBG_TESTSOLARMUTEX(); + uno::Reference < XAccessible > xAcc; + uno::Reference < XAccessible > xOldCursorAcc; + bool bOldShapeSelected = false; + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpFrameMap && bCreate ) + mpFrameMap.reset(new SwAccessibleContextMap_Impl); + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pFrame ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + + if( !xAcc.is() && bCreate ) + { + SwAccessibleContext *pAcc = nullptr; + switch( pFrame->GetType() ) + { + case SwFrameType::Txt: + pAcc = new SwAccessibleParagraph(shared_from_this(), + static_cast< const SwTextFrame& >( *pFrame ) ); + break; + case SwFrameType::Header: + pAcc = new SwAccessibleHeaderFooter(shared_from_this(), + static_cast< const SwHeaderFrame *>( pFrame ) ); + break; + case SwFrameType::Footer: + pAcc = new SwAccessibleHeaderFooter(shared_from_this(), + static_cast< const SwFooterFrame *>( pFrame ) ); + break; + case SwFrameType::Ftn: + { + const SwFootnoteFrame *pFootnoteFrame = + static_cast < const SwFootnoteFrame * >( pFrame ); + bool bIsEndnote = + SwAccessibleFootnote::IsEndnote( pFootnoteFrame ); + pAcc = new SwAccessibleFootnote(shared_from_this(), bIsEndnote, + /*(bIsEndnote ? mnEndnote++ : mnFootnote++),*/ + pFootnoteFrame ); + } + break; + case SwFrameType::Fly: + { + const SwFlyFrame *pFlyFrame = + static_cast < const SwFlyFrame * >( pFrame ); + switch( SwAccessibleFrameBase::GetNodeType( pFlyFrame ) ) + { + case SwNodeType::Grf: + pAcc = new SwAccessibleGraphic(shared_from_this(), pFlyFrame ); + break; + case SwNodeType::Ole: + pAcc = new SwAccessibleEmbeddedObject(shared_from_this(), pFlyFrame ); + break; + default: + pAcc = new SwAccessibleTextFrame(shared_from_this(), *pFlyFrame ); + break; + } + } + break; + case SwFrameType::Cell: + pAcc = new SwAccessibleCell(shared_from_this(), + static_cast< const SwCellFrame *>( pFrame ) ); + break; + case SwFrameType::Tab: + pAcc = new SwAccessibleTable(shared_from_this(), + static_cast< const SwTabFrame *>( pFrame ) ); + break; + case SwFrameType::Page: + OSL_ENSURE( GetShell()->IsPreview(), + "accessible page frames only in PagePreview" ); + pAcc = new SwAccessiblePage(shared_from_this(), pFrame); + break; + default: break; + } + xAcc = pAcc; + assert(xAcc.is()); + + if( aIter != mpFrameMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpFrameMap->emplace( pFrame, xAcc ); + } + + if( pAcc->HasCursor() && + !AreInSameTable( mxCursorContext, pFrame ) ) + { + // If the new context has the focus, and if we know + // another context that had the focus, then the focus + // just moves from the old context to the new one. We + // then have to send a focus event and a caret event for + // the old context. We have to do that now, + // because after we have left this method, anyone might + // call getStates for the new context and will get a + // focused state then. Sending the focus changes event + // after that seems to be strange. However, we cannot + // send a focus event for the new context now, because + // no one except us knows it. In any case, we remember + // the new context as the one that has the focus + // currently. + + xOldCursorAcc = mxCursorContext; + mxCursorContext = xAcc; + + bOldShapeSelected = mbShapeSelected; + mbShapeSelected = false; + } + } + } + } + + // Invalidate focus for old object when map is not locked + if( xOldCursorAcc.is() ) + InvalidateCursorPosition( xOldCursorAcc ); + if( bOldShapeSelected ) + InvalidateShapeSelection(); + + return xAcc; +} + +::rtl::Reference < SwAccessibleContext > SwAccessibleMap::GetContextImpl( + const SwFrame *pFrame, + bool bCreate ) +{ + uno::Reference < XAccessible > xAcc( GetContext( pFrame, bCreate ) ); + + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast< SwAccessibleContext * >( xAcc.get() ) ); + + return xAccImpl; +} + +uno::Reference< XAccessible> SwAccessibleMap::GetContext( + const SdrObject *pObj, + SwAccessibleContext *pParentImpl, + bool bCreate ) +{ + uno::Reference < XAccessible > xAcc; + uno::Reference < XAccessible > xOldCursorAcc; + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpShapeMap && bCreate ) + mpShapeMap.reset(new SwAccessibleShapeMap_Impl( this )); + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj ); + if( aIter != mpShapeMap->end() ) + xAcc = (*aIter).second; + + if( !xAcc.is() && bCreate ) + { + rtl::Reference< ::accessibility::AccessibleShape> pAcc; + uno::Reference < drawing::XShape > xShape( + const_cast< SdrObject * >( pObj )->getUnoShape(), + uno::UNO_QUERY ); + if( xShape.is() ) + { + ::accessibility::ShapeTypeHandler& rShapeTypeHandler = + ::accessibility::ShapeTypeHandler::Instance(); + uno::Reference < XAccessible > xParent( pParentImpl ); + ::accessibility::AccessibleShapeInfo aShapeInfo( + xShape, xParent, this ); + + pAcc = rShapeTypeHandler.CreateAccessibleObject( + aShapeInfo, mpShapeMap->GetInfo() ); + } + xAcc = pAcc.get(); + assert(xAcc.is()); + pAcc->Init(); + if( aIter != mpShapeMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpShapeMap->emplace( pObj, xAcc ); + } + // TODO: focus!!! + AddGroupContext(pObj, xAcc); + } + } + } + + // Invalidate focus for old object when map is not locked + if( xOldCursorAcc.is() ) + InvalidateCursorPosition( xOldCursorAcc ); + + return xAcc; +} + +bool SwAccessibleMap::IsInSameLevel(const SdrObject* pObj, const SwFEShell* pFESh) +{ + if (pFESh) + return pFESh->IsObjSameLevelWithMarked(pObj); + return false; +} + +void SwAccessibleMap::AddShapeContext(const SdrObject *pObj, uno::Reference < XAccessible > const & xAccShape) +{ + osl::MutexGuard aGuard( maMutex ); + + if( mpShapeMap ) + { + mpShapeMap->emplace( pObj, xAccShape ); + } + +} + +//Added by yanjun for sym2_6407 +void SwAccessibleMap::RemoveGroupContext(const SdrObject *pParentObj) +{ + osl::MutexGuard aGuard( maMutex ); + // TODO: Why are sub-shapes of group shapes even added to our map? + // Doesn't the AccessibleShape of the top-level shape create them + // on demand anyway? Why does SwAccessibleMap need to know them? + // We cannot rely on getAccessibleChild here to remove the sub-shapes + // from mpShapes because the top-level shape may not only be disposed here + // but also by visibility checks in svx, then it doesn't return children. + if (mpShapeMap && pParentObj && pParentObj->IsGroupObject()) + { + SdrObjList *const pChildren(pParentObj->GetSubList()); + for (size_t i = 0; pChildren && i < pChildren->GetObjCount(); ++i) + { + SdrObject *const pChild(pChildren->GetObj(i)); + assert(pChild); + RemoveContext(pChild); + } + } +} +//End + +void SwAccessibleMap::AddGroupContext(const SdrObject *pParentObj, uno::Reference < XAccessible > const & xAccParent) +{ + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + { + //here get all the sub list. + if (pParentObj->IsGroupObject()) + { + if (xAccParent.is()) + { + uno::Reference < XAccessibleContext > xContext = xAccParent->getAccessibleContext(); + if (xContext.is()) + { + sal_Int32 nChildren = xContext->getAccessibleChildCount(); + for(sal_Int32 i = 0; i xChild = xContext->getAccessibleChild(i); + if (xChild.is()) + { + uno::Reference < XAccessibleContext > xChildContext = xChild->getAccessibleContext(); + if (xChildContext.is()) + { + short nRole = xChildContext->getAccessibleRole(); + if (nRole == AccessibleRole::SHAPE) + { + ::accessibility::AccessibleShape* pAccShape = static_cast < ::accessibility::AccessibleShape* >( xChild.get()); + uno::Reference < drawing::XShape > xShape = pAccShape->GetXShape(); + if (xShape.is()) + { + SdrObject* pObj = GetSdrObjectFromXShape(xShape); + AddShapeContext(pObj, xChild); + AddGroupContext(pObj,xChild); + } + } + } + } + } + } + } + } + } +} + +::rtl::Reference < ::accessibility::AccessibleShape > SwAccessibleMap::GetContextImpl( + const SdrObject *pObj, + SwAccessibleContext *pParentImpl, + bool bCreate ) +{ + uno::Reference < XAccessible > xAcc( GetContext( pObj, pParentImpl, bCreate ) ); + + ::rtl::Reference < ::accessibility::AccessibleShape > xAccImpl( + static_cast< ::accessibility::AccessibleShape* >( xAcc.get() ) ); + + return xAccImpl; +} + +void SwAccessibleMap::RemoveContext( const SwFrame *pFrame ) +{ + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pFrame ); + if( aIter != mpFrameMap->end() ) + { + mpFrameMap->erase( aIter ); + + // Remove reference to old caret object. Though mxCursorContext + // is a weak reference and cleared automatically, clearing it + // directly makes sure to not keep a non-functional object. + uno::Reference < XAccessible > xOldAcc( mxCursorContext ); + if( xOldAcc.is() ) + { + SwAccessibleContext *pOldAccImpl = + static_cast< SwAccessibleContext *>( xOldAcc.get() ); + OSL_ENSURE( pOldAccImpl->GetFrame(), "old caret context is disposed" ); + if( pOldAccImpl->GetFrame() == pFrame ) + { + xOldAcc.clear(); // get an empty ref + mxCursorContext = xOldAcc; + } + } + + if( mpFrameMap->empty() ) + { + mpFrameMap.reset(); + } + } + } +} + +void SwAccessibleMap::RemoveContext( const SdrObject *pObj ) +{ + osl::MutexGuard aGuard( maMutex ); + + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj ); + if( aIter != mpShapeMap->end() ) + { + uno::Reference < XAccessible > xTempHold( (*aIter).second ); + mpShapeMap->erase( aIter ); + RemoveGroupContext(pObj); + // The shape selection flag is not cleared, but one might do + // so but has to make sure that the removed context is the one + // that is selected. + + if( mpShapeMap && mpShapeMap->empty() ) + { + mpShapeMap.reset(); + } + } + } +} + +bool SwAccessibleMap::Contains(const SwFrame *pFrame) const +{ + return (pFrame && mpFrameMap && mpFrameMap->find(pFrame) != mpFrameMap->end()); +} + +void SwAccessibleMap::A11yDispose( const SwFrame *pFrame, + const SdrObject *pObj, + vcl::Window* pWindow, + bool bRecursive, + bool bCanSkipInvisible ) +{ + SwAccessibleChild aFrameOrObj( pFrame, pObj, pWindow ); + + // Indeed, the following assert checks the frame's accessible flag, + // because that's the one that is evaluated in the layout. The frame + // might not be accessible anyway. That's the case for cell frames that + // contain further cells. + OSL_ENSURE( !aFrameOrObj.GetSwFrame() || aFrameOrObj.GetSwFrame()->IsAccessibleFrame(), + "non accessible frame should be disposed" ); + + if (aFrameOrObj.IsAccessible(GetShell()->IsPreview()) + // fdo#87199 dispose the darn thing if it ever was accessible + || Contains(pFrame)) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl; + ::rtl::Reference< SwAccessibleContext > xParentAccImpl; + ::rtl::Reference< ::accessibility::AccessibleShape > xShapeAccImpl; + // get accessible context for frame + { + osl::MutexGuard aGuard( maMutex ); + + // First of all look for an accessible context for a frame + if( aFrameOrObj.GetSwFrame() && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + if( !xAccImpl.is() && mpFrameMap ) + { + // If there is none, look if the parent is accessible. + const SwFrame *pParent = + SwAccessibleFrame::GetParent( aFrameOrObj, + GetShell()->IsPreview()); + + if( pParent ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pParent ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xParentAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + } + if( !xParentAccImpl.is() && !aFrameOrObj.GetSwFrame() && mpShapeMap ) + { + SwAccessibleShapeMap_Impl::iterator aIter = + mpShapeMap->find( aFrameOrObj.GetDrawObject() ); + if( aIter != mpShapeMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xShapeAccImpl = + static_cast< ::accessibility::AccessibleShape *>( xAcc.get() ); + } + } + if( pObj && GetShell()->ActionPend() && + (xParentAccImpl.is() || xShapeAccImpl.is()) ) + { + // Keep a reference to the XShape to avoid that it + // is deleted with a SwFrameFormat::Modify. + uno::Reference < drawing::XShape > xShape( + const_cast< SdrObject * >( pObj )->getUnoShape(), + uno::UNO_QUERY ); + if( xShape.is() ) + { + mvShapes.push_back( xShape ); + } + } + } + + // remove events stored for the frame + { + osl::MutexGuard aGuard( maEventMutex ); + if( mpEvents ) + { + SwAccessibleEventMap_Impl::iterator aIter = + mpEventMap->find( aFrameOrObj ); + if( aIter != mpEventMap->end() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::DISPOSE, aFrameOrObj ); + AppendEvent( aEvent ); + } + } + } + + // If the frame is accessible and there is a context for it, dispose + // the frame. If the frame is no context for it but disposing should + // take place recursive, the frame's children have to be disposed + // anyway, so we have to create the context then. + if( xAccImpl.is() ) + { + xAccImpl->Dispose( bRecursive ); + } + else if( xParentAccImpl.is() ) + { + // If the frame is a cell frame, the table must be notified. + // If we are in an action, a table model change event will + // be broadcasted at the end of the action to give the table + // a chance to generate a single table change event. + + xParentAccImpl->DisposeChild( aFrameOrObj, bRecursive, bCanSkipInvisible ); + } + else if( xShapeAccImpl.is() ) + { + RemoveContext( aFrameOrObj.GetDrawObject() ); + xShapeAccImpl->dispose(); + } + + if( mpPreview && pFrame && pFrame->IsPageFrame() ) + mpPreview->DisposePage( static_cast< const SwPageFrame *>( pFrame ) ); + } +} + +void SwAccessibleMap::InvalidatePosOrSize( const SwFrame *pFrame, + const SdrObject *pObj, + vcl::Window* pWindow, + const SwRect& rOldBox ) +{ + SwAccessibleChild aFrameOrObj( pFrame, pObj, pWindow ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + ::rtl::Reference< SwAccessibleContext > xAccImpl; + ::rtl::Reference< SwAccessibleContext > xParentAccImpl; + const SwFrame *pParent =nullptr; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + if( aFrameOrObj.GetSwFrame() ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + // If there is an accessible object already it is + // notified directly. + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + if( !xAccImpl.is() ) + { + // Otherwise we look if the parent is accessible. + // If not, there is nothing to do. + pParent = + SwAccessibleFrame::GetParent( aFrameOrObj, + GetShell()->IsPreview()); + + if( pParent ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pParent ); + if( aIter != mpFrameMap->end() ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + xParentAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + } + } + } + } + } + + if( xAccImpl.is() ) + { + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::POS_CHANGED, xAccImpl.get(), + aFrameOrObj, rOldBox ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + if (xAccImpl->GetFrame()) // not if disposed by FireEvents() + { + xAccImpl->InvalidatePosOrSize(rOldBox); + } + } + } + else if( xParentAccImpl.is() ) + { + if( GetShell()->ActionPend() ) + { + assert(pParent); + // tdf#99722 faster not to buffer events that won't be sent + if (!SwAccessibleChild(pParent).IsVisibleChildrenOnly() + || xParentAccImpl->IsShowing(rOldBox) + || xParentAccImpl->IsShowing(*this, aFrameOrObj)) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + xParentAccImpl.get(), aFrameOrObj, rOldBox ); + AppendEvent( aEvent ); + } + } + else + { + FireEvents(); + xParentAccImpl->InvalidateChildPosOrSize( aFrameOrObj, + rOldBox ); + } + } + else if(pParent) + { +/* +For child graphic and its parent paragraph,if split 2 graphic to 2 paragraph, +will delete one graphic swfrm and new create 1 graphic swfrm , +then the new paragraph and the new graphic SwFrame will add . +but when add graphic SwFrame ,the accessible of the new Paragraph is not created yet. +so the new graphic accessible 'parent is NULL, +so run here: save the parent's SwFrame not the accessible object parent, +*/ + bool bIsValidFrame = false; + bool bIsTextParent = false; + if (aFrameOrObj.GetSwFrame()) + { + if (SwFrameType::Fly == pFrame->GetType()) + { + bIsValidFrame =true; + } + } + else if(pObj) + { + if (SwFrameType::Txt == pParent->GetType()) + { + bIsTextParent =true; + } + } + if( bIsValidFrame || bIsTextParent ) + { + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::CHILD_POS_CHANGED, + pParent, aFrameOrObj, rOldBox ); + AppendEvent( aEvent ); + } + else + { + OSL_ENSURE(false,""); + } + } + } + } +} + +void SwAccessibleMap::InvalidateContent( const SwFrame *pFrame ) +{ + SwAccessibleChild aFrameOrObj( pFrame ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::INVALID_CONTENT, pAccImpl, + aFrameOrObj ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateContent(); + } + } + } +} + +void SwAccessibleMap::InvalidateAttr( const SwTextFrame& rTextFrame ) +{ + SwAccessibleChild aFrameOrObj( &rTextFrame ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::INVALID_ATTR, + pAccImpl, aFrameOrObj ); + aEvent.SetStates( AccessibleStates::TEXT_ATTRIBUTE_CHANGED ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateAttr(); + } + } + } +} + +void SwAccessibleMap::InvalidateCursorPosition( const SwFrame *pFrame ) +{ + SwAccessibleChild aFrameOrObj( pFrame ); + bool bShapeSelected = false; + const SwViewShell *pVSh = GetShell(); + if( auto pCSh = dynamic_cast(pVSh) ) + { + if( pCSh->IsTableMode() ) + { + while( aFrameOrObj.GetSwFrame() && !aFrameOrObj.GetSwFrame()->IsCellFrame() ) + aFrameOrObj = aFrameOrObj.GetSwFrame()->GetUpper(); + } + else if( auto pFESh = dynamic_cast(pVSh) ) + { + const SwFrame *pFlyFrame = pFESh->GetSelectedFlyFrame(); + if( pFlyFrame ) + { + OSL_ENSURE( !pFrame || pFrame->FindFlyFrame() == pFlyFrame, + "cursor is not contained in fly frame" ); + aFrameOrObj = pFlyFrame; + } + else if( pFESh->IsObjSelected() > 0 ) + { + bShapeSelected = true; + aFrameOrObj = static_cast( nullptr ); + } + } + } + + OSL_ENSURE( bShapeSelected || aFrameOrObj.IsAccessible(GetShell()->IsPreview()), + "frame is not accessible" ); + + uno::Reference < XAccessible > xOldAcc; + uno::Reference < XAccessible > xAcc; + bool bOldShapeSelected = false; + + { + osl::MutexGuard aGuard( maMutex ); + + xOldAcc = mxCursorContext; + mxCursorContext = xAcc; // clear reference + + bOldShapeSelected = mbShapeSelected; + mbShapeSelected = bShapeSelected; + + if( aFrameOrObj.GetSwFrame() && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + else + { + SwRect rcEmpty; + const SwTabFrame* pTabFrame = aFrameOrObj.GetSwFrame()->FindTabFrame(); + if (pTabFrame) + { + InvalidatePosOrSize(pTabFrame, nullptr, nullptr, rcEmpty); + } + else + { + InvalidatePosOrSize(aFrameOrObj.GetSwFrame(), nullptr, nullptr, rcEmpty); + } + + aIter = mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + + // For cells, some extra thoughts are necessary, + // because invalidating the cursor for one cell + // invalidates the cursor for all cells of the same + // table. For this reason, we don't want to + // invalidate the cursor for the old cursor object + // and the new one if they are within the same table, + // because this would result in doing the work twice. + // Moreover, we have to make sure to invalidate the + // cursor even if the current cell has no accessible object. + // If the old cursor objects exists and is in the same + // table, it's the best choice, because using it avoids + // an unnecessary cursor invalidation cycle when creating + // a new object for the current cell. + if( aFrameOrObj.GetSwFrame()->IsCellFrame() ) + { + if( xOldAcc.is() && + AreInSameTable( xOldAcc, aFrameOrObj.GetSwFrame() ) ) + { + if( xAcc.is() ) + xOldAcc = xAcc; // avoid extra invalidation + else + xAcc = xOldAcc; // make sure at least one + } + if( !xAcc.is() ) + xAcc = GetContext( aFrameOrObj.GetSwFrame() ); + } + } + else if (bShapeSelected) + { + const SwFEShell *pFESh = static_cast< const SwFEShell * >( pVSh ); + const SdrMarkList *pMarkList = pFESh->GetMarkList(); + if (pMarkList != nullptr && pMarkList->GetMarkCount() == 1) + { + SdrObject *pObj = pMarkList->GetMark( 0 )->GetMarkedSdrObj(); + ::rtl::Reference < ::accessibility::AccessibleShape > pAccShapeImpl = GetContextImpl(pObj,nullptr,false); + if (!pAccShapeImpl.is()) + { + while (pObj && pObj->getParentSdrObjectFromSdrObject()) + { + pObj = pObj->getParentSdrObjectFromSdrObject(); + } + if (pObj != nullptr) + { + const SwFrame *pParent = SwAccessibleFrame::GetParent( SwAccessibleChild(pObj), GetShell()->IsPreview() ); + if( pParent ) + { + ::rtl::Reference< SwAccessibleContext > xParentAccImpl = GetContextImpl(pParent,false); + if (!xParentAccImpl.is()) + { + const SwTabFrame* pTabFrame = pParent->FindTabFrame(); + if (pTabFrame) + { + //The Table should not add in acc.because the "pParent" is not add to acc . + uno::Reference< XAccessible> xAccParentTab = GetContext(pTabFrame);//Should Create. + + const SwFrame *pParentRoot = SwAccessibleFrame::GetParent( SwAccessibleChild(pTabFrame), GetShell()->IsPreview() ); + if (pParentRoot) + { + ::rtl::Reference< SwAccessibleContext > xParentAccImplRoot = GetContextImpl(pParentRoot,false); + if(xParentAccImplRoot.is()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xAccParentTab; + xParentAccImplRoot->FireAccessibleEvent( aEvent ); + } + } + + //Get "pParent" acc again. + xParentAccImpl = GetContextImpl(pParent,false); + } + else + { + //directly create this acc para . + xParentAccImpl = GetContextImpl(pParent);//Should Create. + + const SwFrame *pParentRoot = SwAccessibleFrame::GetParent( SwAccessibleChild(pParent), GetShell()->IsPreview() ); + + ::rtl::Reference< SwAccessibleContext > xParentAccImplRoot = GetContextImpl(pParentRoot,false); + if(xParentAccImplRoot.is()) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= uno::Reference< XAccessible>(xParentAccImpl.get()); + xParentAccImplRoot->FireAccessibleEvent( aEvent ); + } + } + } + if (xParentAccImpl.is()) + { + uno::Reference< XAccessible> xAccShape = + GetContext(pObj,xParentAccImpl.get()); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CHILD; + aEvent.NewValue <<= xAccShape; + xParentAccImpl->FireAccessibleEvent( aEvent ); + } + } + } + } + } + } + } + + m_setParaAdd.clear(); + m_setParaRemove.clear(); + if( xOldAcc.is() && xOldAcc != xAcc ) + InvalidateCursorPosition( xOldAcc ); + if( bOldShapeSelected || bShapeSelected ) + InvalidateShapeSelection(); + if( xAcc.is() ) + InvalidateCursorPosition( xAcc ); + + InvalidateShapeInParaSelection(); + + for (SwAccessibleParagraph* pAccPara : m_setParaRemove) + { + if(pAccPara && pAccPara->getSelectedAccessibleChildCount() == 0 && pAccPara->getSelectedText().getLength() == 0) + { + if(pAccPara->SetSelectedState(false)) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + pAccPara->FireAccessibleEvent( aEvent ); + } + } + } + for (SwAccessibleParagraph* pAccPara : m_setParaAdd) + { + if(pAccPara && pAccPara->SetSelectedState(true)) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED; + pAccPara->FireAccessibleEvent( aEvent ); + } + } +} + +void SwAccessibleMap::InvalidateFocus() +{ + if(GetShell()->IsPreview()) + { + uno::Reference xAcc = GetDocumentView_( true ); + if (xAcc) + { + SwAccessiblePreview *pAccPreview = static_cast(xAcc.get()); + if (pAccPreview) + { + pAccPreview->InvalidateFocus(); + return ; + } + } + } + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + xAcc = mxCursorContext; + } + + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + pAccImpl->InvalidateFocus(); + } + else + { + DoInvalidateShapeSelection(true); + } +} + +void SwAccessibleMap::SetCursorContext( + const ::rtl::Reference < SwAccessibleContext >& rCursorContext ) +{ + osl::MutexGuard aGuard( maMutex ); + uno::Reference < XAccessible > xAcc( rCursorContext.get() ); + mxCursorContext = xAcc; +} + +void SwAccessibleMap::InvalidateEditableStates( const SwFrame* _pFrame ) +{ + // Start with the frame or the first upper that is accessible + SwAccessibleChild aFrameOrObj( _pFrame ); + while( aFrameOrObj.GetSwFrame() && + !aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + aFrameOrObj = aFrameOrObj.GetSwFrame()->GetUpper(); + if( !aFrameOrObj.GetSwFrame() ) + aFrameOrObj = GetShell()->GetLayout(); + + uno::Reference< XAccessible > xAcc( GetContext( aFrameOrObj.GetSwFrame() ) ); + SwAccessibleContext *pAccImpl = static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, + SwAccessibleChild(pAccImpl->GetFrame()), + AccessibleStates::EDITABLE ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateStates( AccessibleStates::EDITABLE ); + } +} + +void SwAccessibleMap::InvalidateRelationSet_( const SwFrame* pFrame, + bool bFrom ) +{ + // first, see if this frame is accessible, and if so, get the respective + SwAccessibleChild aFrameOrObj( pFrame ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + } + + // deliver event directly, or queue event + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, SwAccessibleChild(pFrame), + ( bFrom + ? AccessibleStates::RELATION_FROM + : AccessibleStates::RELATION_TO ) ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateRelation( bFrom + ? AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED + : AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED ); + } + } + } +} + +void SwAccessibleMap::InvalidateRelationSet( const SwFrame* pMaster, + const SwFrame* pFollow ) +{ + InvalidateRelationSet_( pMaster, false ); + InvalidateRelationSet_( pFollow, true ); +} + +// invalidation of CONTENT_FLOW_FROM/_TO relation of a paragraph +void SwAccessibleMap::InvalidateParaFlowRelation( const SwTextFrame& _rTextFrame, + const bool _bFrom ) +{ + InvalidateRelationSet_( &_rTextFrame, _bFrom ); +} + +// invalidation of text selection of a paragraph +void SwAccessibleMap::InvalidateParaTextSelection( const SwTextFrame& _rTextFrame ) +{ + // first, see if this frame is accessible, and if so, get the respective + SwAccessibleChild aFrameOrObj( &_rTextFrame ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + } + + // deliver event directly, or queue event + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + if( GetShell()->ActionPend() ) + { + SwAccessibleEvent_Impl aEvent( + SwAccessibleEvent_Impl::CARET_OR_STATES, + pAccImpl, + SwAccessibleChild( &_rTextFrame ), + AccessibleStates::TEXT_SELECTION_CHANGED ); + AppendEvent( aEvent ); + } + else + { + FireEvents(); + pAccImpl->InvalidateTextSelection(); + } + } + } +} + +sal_Int32 SwAccessibleMap::GetChildIndex( const SwFrame& rParentFrame, + vcl::Window& rChild ) const +{ + sal_Int32 nIndex( -1 ); + + SwAccessibleChild aFrameOrObj( &rParentFrame ); + if( aFrameOrObj.IsAccessible( GetShell()->IsPreview() ) ) + { + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + if( mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( aFrameOrObj.GetSwFrame() ); + if( aIter != mpFrameMap->end() ) + { + xAcc = (*aIter).second; + } + } + } + + if( xAcc.is() ) + { + SwAccessibleContext *pAccImpl = + static_cast< SwAccessibleContext *>( xAcc.get() ); + + nIndex = pAccImpl->GetChildIndex( const_cast(*this), + SwAccessibleChild( &rChild ) ); + } + } + + return nIndex; +} + +void SwAccessibleMap::UpdatePreview( const std::vector>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + assert(GetShell()->IsPreview() && "no preview?"); + assert(mpPreview != nullptr && "no preview data?"); + + mpPreview->Update( *this, _rPreviewPages, _rScale, _pSelectedPageFrame, _rPreviewWinSize ); + + // propagate change of VisArea through the document's + // accessibility tree; this will also send appropriate scroll + // events + SwAccessibleContext* pDoc = + GetContextImpl( GetShell()->GetLayout() ).get(); + static_cast( pDoc )->SetVisArea(); + + uno::Reference < XAccessible > xOldAcc; + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + xOldAcc = mxCursorContext; + + const SwPageFrame *pSelPage = mpPreview->GetSelPage(); + if( pSelPage && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = + mpFrameMap->find( pSelPage ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( xOldAcc.is() && xOldAcc != xAcc ) + InvalidateCursorPosition( xOldAcc ); + if( xAcc.is() ) + InvalidateCursorPosition( xAcc ); +} + +void SwAccessibleMap::InvalidatePreviewSelection( sal_uInt16 nSelPage ) +{ + assert(GetShell()->IsPreview()); + assert(mpPreview != nullptr); + + mpPreview->InvalidateSelection( GetShell()->GetLayout()->GetPageByPageNum( nSelPage ) ); + + uno::Reference < XAccessible > xOldAcc; + uno::Reference < XAccessible > xAcc; + { + osl::MutexGuard aGuard( maMutex ); + + xOldAcc = mxCursorContext; + + const SwPageFrame *pSelPage = mpPreview->GetSelPage(); + if( pSelPage && mpFrameMap ) + { + SwAccessibleContextMap_Impl::iterator aIter = mpFrameMap->find( pSelPage ); + if( aIter != mpFrameMap->end() ) + xAcc = (*aIter).second; + } + } + + if( xOldAcc.is() && xOldAcc != xAcc ) + InvalidateCursorPosition( xOldAcc ); + if( xAcc.is() ) + InvalidateCursorPosition( xAcc ); +} + +bool SwAccessibleMap::IsPageSelected( const SwPageFrame *pPageFrame ) const +{ + return mpPreview && mpPreview->GetSelPage() == pPageFrame; +} + +void SwAccessibleMap::FireEvents() +{ + { + osl::MutexGuard aGuard( maEventMutex ); + if( mpEvents ) + { + if (mpEvents->IsFiring()) + { + return; // prevent recursive FireEvents() + } + + mpEvents->SetFiring(); + mpEvents->MoveMissingXAccToEnd(); + for( auto const& aEvent : *mpEvents ) + FireEvent(aEvent); + + mpEventMap.reset(); + mpEvents.reset(); + } + } + { + osl::MutexGuard aGuard( maMutex ); + mvShapes.clear(); + } + +} + +tools::Rectangle SwAccessibleMap::GetVisibleArea() const +{ + MapMode aSrc( MapUnit::MapTwip ); + MapMode aDest( MapUnit::Map100thMM ); + return OutputDevice::LogicToLogic( GetVisArea().SVRect(), aSrc, aDest ); +} + +// Convert a MM100 value relative to the document root into a pixel value +// relative to the screen! +Point SwAccessibleMap::LogicToPixel( const Point& rPoint ) const +{ + MapMode aSrc( MapUnit::Map100thMM ); + MapMode aDest( MapUnit::MapTwip ); + + Point aPoint = OutputDevice::LogicToLogic( rPoint, aSrc, aDest ); + if (const vcl::Window* pWin = GetShell()->GetWin()) + { + MapMode aMapMode; + GetMapMode( aPoint, aMapMode ); + aPoint = pWin->LogicToPixel( aPoint, aMapMode ); + aPoint = pWin->OutputToAbsoluteScreenPixel( aPoint ); + } + + return aPoint; +} + +Size SwAccessibleMap::LogicToPixel( const Size& rSize ) const +{ + MapMode aSrc( MapUnit::Map100thMM ); + MapMode aDest( MapUnit::MapTwip ); + Size aSize( OutputDevice::LogicToLogic( rSize, aSrc, aDest ) ); + if (const OutputDevice* pWin = GetShell()->GetWin()) + { + MapMode aMapMode; + GetMapMode( Point(0,0), aMapMode ); + aSize = pWin->LogicToPixel( aSize, aMapMode ); + } + + return aSize; +} + +bool SwAccessibleMap::ReplaceChild ( + ::accessibility::AccessibleShape* pCurrentChild, + const uno::Reference< drawing::XShape >& _rxShape, + const long /*_nIndex*/, + const ::accessibility::AccessibleShapeTreeInfo& /*_rShapeTreeInfo*/ + ) +{ + const SdrObject *pObj = nullptr; + { + osl::MutexGuard aGuard( maMutex ); + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin(); + SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend(); + while( aIter != aEndIter && !pObj ) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + ::accessibility::AccessibleShape *pAccShape = + static_cast < ::accessibility::AccessibleShape* >( xAcc.get() ); + if( pAccShape == pCurrentChild ) + { + pObj = (*aIter).first; + } + ++aIter; + } + } + } + if( !pObj ) + return false; + + uno::Reference < drawing::XShape > xShape( _rxShape ); // keep reference to shape, because + // we might be the only one that + // holds it. + // Also get keep parent. + uno::Reference < XAccessible > xParent( pCurrentChild->getAccessibleParent() ); + pCurrentChild = nullptr; // will be released by dispose + A11yDispose( nullptr, pObj, nullptr ); + + { + osl::MutexGuard aGuard( maMutex ); + + if( !mpShapeMap ) + mpShapeMap.reset(new SwAccessibleShapeMap_Impl( this )); + + // create the new child + ::accessibility::ShapeTypeHandler& rShapeTypeHandler = + ::accessibility::ShapeTypeHandler::Instance(); + ::accessibility::AccessibleShapeInfo aShapeInfo( + xShape, xParent, this ); + rtl::Reference< ::accessibility::AccessibleShape> pReplacement( + rShapeTypeHandler.CreateAccessibleObject ( + aShapeInfo, mpShapeMap->GetInfo() )); + + uno::Reference < XAccessible > xAcc( pReplacement.get() ); + if( xAcc.is() ) + { + pReplacement->Init(); + + SwAccessibleShapeMap_Impl::iterator aIter = mpShapeMap->find( pObj ); + if( aIter != mpShapeMap->end() ) + { + (*aIter).second = xAcc; + } + else + { + mpShapeMap->emplace( pObj, xAcc ); + } + } + } + + SwRect aEmptyRect; + InvalidatePosOrSize( nullptr, pObj, nullptr, aEmptyRect ); + + return true; +} + +//Get the accessible control shape from the model object, here model object is with XPropertySet type +::accessibility::AccessibleControlShape * SwAccessibleMap::GetAccControlShapeFromModel(css::beans::XPropertySet* pSet) +{ + if( mpShapeMap ) + { + SwAccessibleShapeMap_Impl::const_iterator aIter = mpShapeMap->cbegin(); + SwAccessibleShapeMap_Impl::const_iterator aEndIter = mpShapeMap->cend(); + while( aIter != aEndIter) + { + uno::Reference < XAccessible > xAcc( (*aIter).second ); + ::accessibility::AccessibleShape *pAccShape = + static_cast < ::accessibility::AccessibleShape* >( xAcc.get() ); + if(pAccShape && ::accessibility::ShapeTypeHandler::Instance().GetTypeId (pAccShape->GetXShape()) == ::accessibility::DRAWING_CONTROL) + { + ::accessibility::AccessibleControlShape *pCtlAccShape = static_cast < ::accessibility::AccessibleControlShape* >(pAccShape); + if (pCtlAccShape->GetControlModel() == pSet) + return pCtlAccShape; + } + ++aIter; + } + } + return nullptr; +} + +css::uno::Reference< XAccessible > + SwAccessibleMap::GetAccessibleCaption (const css::uno::Reference< css::drawing::XShape >&) +{ + return nullptr; +} + +Point SwAccessibleMap::PixelToCore( const Point& rPoint ) const +{ + Point aPoint; + if (const OutputDevice* pWin = GetShell()->GetWin()) + { + MapMode aMapMode; + GetMapMode( rPoint, aMapMode ); + aPoint = pWin->PixelToLogic( rPoint, aMapMode ); + } + return aPoint; +} + +static long lcl_CorrectCoarseValue(long aCoarseValue, long aFineValue, + long aRefValue, bool bToLower) +{ + long aResult = aCoarseValue; + + if (bToLower) + { + if (aFineValue < aRefValue) + aResult -= 1; + } + else + { + if (aFineValue > aRefValue) + aResult += 1; + } + + return aResult; +} + +static void lcl_CorrectRectangle(tools::Rectangle & rRect, + const tools::Rectangle & rSource, + const tools::Rectangle & rInGrid) +{ + rRect.SetLeft( lcl_CorrectCoarseValue(rRect.Left(), rSource.Left(), + rInGrid.Left(), false) ); + rRect.SetTop( lcl_CorrectCoarseValue(rRect.Top(), rSource.Top(), + rInGrid.Top(), false) ); + rRect.SetRight( lcl_CorrectCoarseValue(rRect.Right(), rSource.Right(), + rInGrid.Right(), true) ); + rRect.SetBottom( lcl_CorrectCoarseValue(rRect.Bottom(), rSource.Bottom(), + rInGrid.Bottom(), true) ); +} + +tools::Rectangle SwAccessibleMap::CoreToPixel( const tools::Rectangle& rRect ) const +{ + tools::Rectangle aRect; + if (const OutputDevice* pWin = GetShell()->GetWin()) + { + MapMode aMapMode; + GetMapMode( rRect.TopLeft(), aMapMode ); + aRect = pWin->LogicToPixel( rRect, aMapMode ); + + tools::Rectangle aTmpRect = pWin->PixelToLogic( aRect, aMapMode ); + lcl_CorrectRectangle(aRect, rRect, aTmpRect); + } + + return aRect; +} + +/** get mapping mode for LogicToPixel and PixelToLogic conversions + + Method returns mapping mode of current output device and adjusts it, + if the shell is in page/print preview. + Necessary, because changes mapping mode at current + output device for mapping logic document positions to page preview window + positions and vice versa and doesn't take care to recover its changes. +*/ +void SwAccessibleMap::GetMapMode( const Point& _rPoint, + MapMode& _orMapMode ) const +{ + MapMode aMapMode = GetShell()->GetWin()->GetMapMode(); + if( GetShell()->IsPreview() ) + { + assert(mpPreview != nullptr); + mpPreview->AdjustMapMode( aMapMode, _rPoint ); + } + _orMapMode = aMapMode; +} + +Size SwAccessibleMap::GetPreviewPageSize(sal_uInt16 const nPreviewPageNum) const +{ + assert(mpVSh->IsPreview()); + assert(mpPreview != nullptr); + return mpVSh->PagePreviewLayout()->GetPreviewPageSizeByPageNum(nPreviewPageNum); +} + +/** method to build up a new data structure of the accessible paragraphs, + which have a selection + Important note: method has to be used inside a mutual exclusive section +*/ +std::unique_ptr SwAccessibleMap::BuildSelectedParas() +{ + // no accessible contexts, no selection + if ( !mpFrameMap ) + { + return nullptr; + } + + // get cursor as an instance of its base class + SwPaM* pCursor( nullptr ); + { + SwCursorShell* pCursorShell = dynamic_cast(GetShell()); + if ( pCursorShell ) + { + SwFEShell* pFEShell = dynamic_cast(pCursorShell); + if ( !pFEShell || + ( !pFEShell->IsFrameSelected() && + pFEShell->IsObjSelected() == 0 ) ) + { + // get cursor without updating an existing table cursor. + pCursor = pCursorShell->GetCursor( false ); + } + } + } + // no cursor, no selection + if ( !pCursor ) + { + return nullptr; + } + + std::unique_ptr pRetSelectedParas; + + // loop on all cursors + SwPaM* pRingStart = pCursor; + do { + + // for a selection the cursor has to have a mark. + // for safety reasons assure that point and mark are in text nodes + if ( pCursor->HasMark() && + pCursor->GetPoint()->nNode.GetNode().IsTextNode() && + pCursor->GetMark()->nNode.GetNode().IsTextNode() ) + { + SwPosition* pStartPos = pCursor->Start(); + SwPosition* pEndPos = pCursor->End(); + // loop on all text nodes inside the selection + SwNodeIndex aIdx( pStartPos->nNode ); + for ( ; aIdx.GetIndex() <= pEndPos->nNode.GetIndex(); ++aIdx ) + { + SwTextNode* pTextNode( aIdx.GetNode().GetTextNode() ); + if ( pTextNode ) + { + // loop on all text frames registered at the text node. + SwIterator aIter(*pTextNode); + for( SwTextFrame* pTextFrame = aIter.First(); pTextFrame; pTextFrame = aIter.Next() ) + { + uno::WeakReference < XAccessible > xWeakAcc; + SwAccessibleContextMap_Impl::iterator aMapIter = + mpFrameMap->find( pTextFrame ); + if( aMapIter != mpFrameMap->end() ) + { + xWeakAcc = (*aMapIter).second; + SwAccessibleParaSelection aDataEntry( + sw::FrameContainsNode(*pTextFrame, pStartPos->nNode.GetIndex()) + ? pTextFrame->MapModelToViewPos(*pStartPos) + : TextFrameIndex(0), + + sw::FrameContainsNode(*pTextFrame, pEndPos->nNode.GetIndex()) + ? pTextFrame->MapModelToViewPos(*pEndPos) + : TextFrameIndex(COMPLETE_STRING)); + if ( !pRetSelectedParas ) + { + pRetSelectedParas.reset( + new SwAccessibleSelectedParas_Impl); + } + // sw_redlinehide: should be idempotent for multiple nodes in a merged para + pRetSelectedParas->emplace( xWeakAcc, aDataEntry ); + } + } + } + } + } + + // prepare next turn: get next cursor in ring + pCursor = pCursor->GetNext(); + } while ( pCursor != pRingStart ); + + return pRetSelectedParas; +} + +void SwAccessibleMap::InvalidateTextSelectionOfAllParas() +{ + osl::MutexGuard aGuard( maMutex ); + + // keep previously known selected paragraphs + std::unique_ptr pPrevSelectedParas( std::move(mpSelectedParas) ); + + // determine currently selected paragraphs + mpSelectedParas = BuildSelectedParas(); + + // compare currently selected paragraphs with the previously selected + // paragraphs and submit corresponding TEXT_SELECTION_CHANGED events. + // first, search for new and changed selections. + // on the run remove selections from previously known ones, if they are + // also in the current ones. + if ( mpSelectedParas ) + { + SwAccessibleSelectedParas_Impl::iterator aIter = mpSelectedParas->begin(); + for ( ; aIter != mpSelectedParas->end(); ++aIter ) + { + bool bSubmitEvent( false ); + if ( !pPrevSelectedParas ) + { + // new selection + bSubmitEvent = true; + } + else + { + SwAccessibleSelectedParas_Impl::iterator aPrevSelected = + pPrevSelectedParas->find( (*aIter).first ); + if ( aPrevSelected != pPrevSelectedParas->end() ) + { + // check, if selection has changed + if ( (*aIter).second.nStartOfSelection != + (*aPrevSelected).second.nStartOfSelection || + (*aIter).second.nEndOfSelection != + (*aPrevSelected).second.nEndOfSelection ) + { + // changed selection + bSubmitEvent = true; + } + pPrevSelectedParas->erase( aPrevSelected ); + } + else + { + // new selection + bSubmitEvent = true; + } + } + + if ( bSubmitEvent ) + { + uno::Reference < XAccessible > xAcc( (*aIter).first ); + if ( xAcc.is() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast( xAcc.get() ) ); + if ( xAccImpl.is() && xAccImpl->GetFrame() ) + { + const SwTextFrame* pTextFrame( + dynamic_cast(xAccImpl->GetFrame()) ); + OSL_ENSURE( pTextFrame, + " - unexpected type of frame" ); + if ( pTextFrame ) + { + InvalidateParaTextSelection( *pTextFrame ); + } + } + } + } + } + } + + // second, handle previous selections - after the first step the data + // structure of the previously known only contains the 'old' selections + if ( pPrevSelectedParas ) + { + SwAccessibleSelectedParas_Impl::iterator aIter = pPrevSelectedParas->begin(); + for ( ; aIter != pPrevSelectedParas->end(); ++aIter ) + { + uno::Reference < XAccessible > xAcc( (*aIter).first ); + if ( xAcc.is() ) + { + ::rtl::Reference < SwAccessibleContext > xAccImpl( + static_cast( xAcc.get() ) ); + if ( xAccImpl.is() && xAccImpl->GetFrame() ) + { + const SwTextFrame* pTextFrame( + dynamic_cast(xAccImpl->GetFrame()) ); + OSL_ENSURE( pTextFrame, + " - unexpected type of frame" ); + if ( pTextFrame ) + { + InvalidateParaTextSelection( *pTextFrame ); + } + } + } + } + } +} + +const SwRect& SwAccessibleMap::GetVisArea() const +{ + assert(!GetShell()->IsPreview() || (mpPreview != nullptr)); + + return GetShell()->IsPreview() + ? mpPreview->GetVisArea() + : GetShell()->VisArea(); +} + +bool SwAccessibleMap::IsDocumentSelAll() +{ + return GetShell()->GetDoc()->IsPrepareSelAll(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotextframe.cxx b/sw/source/core/access/accnotextframe.cxx new file mode 100644 index 000000000..acc4f0ce3 --- /dev/null +++ b/sw/source/core/access/accnotextframe.cxx @@ -0,0 +1,316 @@ +/* -*- 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 "accnotextframe.hxx" +#include +#include "accnotexthyperlink.hxx" +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using utl::AccessibleRelationSetHelper; + +const SwNoTextNode *SwAccessibleNoTextFrame::GetNoTextNode() const +{ + const SwNoTextNode *pNd = nullptr; + const SwFlyFrame *pFlyFrame = static_cast< const SwFlyFrame *>( GetFrame() ); + if( pFlyFrame->Lower() && pFlyFrame->Lower()->IsNoTextFrame() ) + { + const SwNoTextFrame *pContentFrame = + static_cast(pFlyFrame->Lower()); + const SwContentNode* pSwContentNode = pContentFrame->GetNode(); + if(pSwContentNode != nullptr) + { + pNd = pSwContentNode->GetNoTextNode(); + } + } + + return pNd; +} + +SwAccessibleNoTextFrame::SwAccessibleNoTextFrame( + std::shared_ptr const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame* pFlyFrame ) : + SwAccessibleFrameBase( pInitMap, nInitRole, pFlyFrame ), + msTitle(), + msDesc() +{ + const SwNoTextNode* pNd = GetNoTextNode(); + StartListening(const_cast(pNd)->GetNotifier()); + // #i73249# + // consider new attributes Title and Description + if( pNd ) + { + msTitle = pNd->GetTitle(); + + msDesc = pNd->GetDescription(); + if ( msDesc.isEmpty() && + msTitle != GetName() ) + { + msDesc = msTitle; + } + } +} + +SwAccessibleNoTextFrame::~SwAccessibleNoTextFrame() +{ +} + +void SwAccessibleNoTextFrame::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + EndListeningAll(); + else if(auto pLegacyModifyHint = dynamic_cast(&rHint)) + { + const sal_uInt16 nWhich = pLegacyModifyHint->m_pOld ? pLegacyModifyHint->m_pOld->Which() : pLegacyModifyHint->m_pNew ? pLegacyModifyHint->m_pNew->Which() : 0; + if (nWhich != RES_TITLE_CHANGED && nWhich != RES_DESCRIPTION_CHANGED) + return; + const SwNoTextNode* pNd = GetNoTextNode(); + switch(nWhich) + { + // #i73249# + case RES_TITLE_CHANGED: + { + OUString sOldTitle, sNewTitle; + const SwStringMsgPoolItem* pOldItem = dynamic_cast(pLegacyModifyHint->m_pOld); + if(pOldItem) + sOldTitle = pOldItem->GetString(); + const SwStringMsgPoolItem* pNewItem = dynamic_cast(pLegacyModifyHint->m_pNew); + if(pNewItem) + sNewTitle = pNewItem->GetString(); + if(sOldTitle == sNewTitle) + break; + msTitle = sNewTitle; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldTitle; + aEvent.NewValue <<= msTitle; + FireAccessibleEvent(aEvent); + + if(!pNd->GetDescription().isEmpty()) + break; + [[fallthrough]]; + } + case RES_DESCRIPTION_CHANGED: + { + if(pNd && GetFrame()) + { + const OUString sOldDesc(msDesc); + + const OUString& rDesc = pNd->GetDescription(); + msDesc = rDesc; + if(msDesc.isEmpty() && msTitle != GetName()) + msDesc = msTitle; + + if(msDesc != sOldDesc) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= msDesc; + FireAccessibleEvent(aEvent); + } + } + } + } + } +} + +void SwAccessibleNoTextFrame::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + EndListeningAll(); + SwAccessibleFrameBase::Dispose(bRecursive, bCanSkipInvisible); +} + +// #i73249# +OUString SAL_CALL SwAccessibleNoTextFrame::getAccessibleName() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if ( !msTitle.isEmpty() ) + { + return msTitle; + } + + return SwAccessibleFrameBase::getAccessibleName(); +} + +OUString SAL_CALL SwAccessibleNoTextFrame::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return msDesc; +} + +// XInterface + +uno::Any SAL_CALL SwAccessibleNoTextFrame::queryInterface( const uno::Type& aType ) +{ + if( aType == + ::cppu::UnoType::get() ) + { + uno::Reference xImage = this; + return uno::Any(xImage); + } + else if ( aType == cppu::UnoType::get()) + { + uno::Reference aAccHypertext = this; + return uno::Any( aAccHypertext ); + } + else + return SwAccessibleContext::queryInterface( aType ); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SwAccessibleNoTextFrame::getTypes() +{ + return cppu::OTypeCollection( + ::cppu::UnoType::get(), + SwAccessibleFrameBase::getTypes() ).getTypes(); +} + +/// XAccessibleImage +/** implementation of the XAccessibleImage methods is a no-brainer, as + all relevant information is already accessible through other + methods. So we just delegate to those. */ + +OUString SAL_CALL SwAccessibleNoTextFrame::getAccessibleImageDescription() +{ + return getAccessibleDescription(); +} + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getAccessibleImageHeight( ) +{ + return getSize().Height; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getAccessibleImageWidth( ) +{ + return getSize().Width; +} + +// XAccessibleText +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getCaretPosition( ){return 0;} +sal_Bool SAL_CALL SwAccessibleNoTextFrame::setCaretPosition( sal_Int32 ){return false;} +sal_Unicode SAL_CALL SwAccessibleNoTextFrame::getCharacter( sal_Int32 ){return 0;} +css::uno::Sequence< css::beans::PropertyValue > SAL_CALL SwAccessibleNoTextFrame::getCharacterAttributes( sal_Int32 , const css::uno::Sequence< OUString >& ) +{ + return uno::Sequence(); +} +css::awt::Rectangle SAL_CALL SwAccessibleNoTextFrame::getCharacterBounds( sal_Int32 ) +{ + return css::awt::Rectangle(0, 0, 0, 0 ); +} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getCharacterCount( ){return 0;} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getIndexAtPoint( const css::awt::Point& ){return 0;} +OUString SAL_CALL SwAccessibleNoTextFrame::getSelectedText( ){return OUString();} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getSelectionStart( ){return 0;} +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getSelectionEnd( ){return 0;} +sal_Bool SAL_CALL SwAccessibleNoTextFrame::setSelection( sal_Int32 , sal_Int32 ){return true;} +OUString SAL_CALL SwAccessibleNoTextFrame::getText( ){return OUString();} +OUString SAL_CALL SwAccessibleNoTextFrame::getTextRange( sal_Int32 , sal_Int32 ){return OUString();} +css::accessibility::TextSegment SAL_CALL SwAccessibleNoTextFrame::getTextAtIndex( sal_Int32 , sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +css::accessibility::TextSegment SAL_CALL SwAccessibleNoTextFrame::getTextBeforeIndex( sal_Int32, sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +css::accessibility::TextSegment SAL_CALL SwAccessibleNoTextFrame::getTextBehindIndex( sal_Int32 , sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} + +sal_Bool SAL_CALL SwAccessibleNoTextFrame::copyText( sal_Int32, sal_Int32 ){return true;} +sal_Bool SAL_CALL SwAccessibleNoTextFrame::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ){return false;} + +// XAccessibleHyperText + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getHyperLinkCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nCount = 0; + SwFormatURL aURL( static_cast(GetFrame())->GetFormat()->GetURL() ); + + if(aURL.GetMap() || !aURL.GetURL().isEmpty()) + nCount = 1; + + return nCount; +} + +uno::Reference< XAccessibleHyperlink > SAL_CALL + SwAccessibleNoTextFrame::getHyperLink( sal_Int32 nLinkIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwFormatURL aURL( static_cast(GetFrame())->GetFormat()->GetURL() ); + + if( nLinkIndex > 0 ) + throw lang::IndexOutOfBoundsException(); + + if( aURL.GetMap() || !aURL.GetURL().isEmpty() ) + { + if ( !m_xHyperlink.is() ) + { + m_xHyperlink = new SwAccessibleNoTextHyperlink( this, GetFrame() ); + } + + return m_xHyperlink; + } + + return nullptr; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextFrame::getHyperLinkIndex( sal_Int32 ) +{ + return 0; +} + +uno::Reference SAL_CALL SwAccessibleNoTextFrame::getAccessibleRelationSet( ) +{ + return new AccessibleRelationSetHelper(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotextframe.hxx b/sw/source/core/access/accnotextframe.hxx new file mode 100644 index 000000000..21d3bcbe0 --- /dev/null +++ b/sw/source/core/access/accnotextframe.hxx @@ -0,0 +1,125 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTFRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTFRAME_HXX + +#include "accframebase.hxx" +#include +#include +#include + +class SwFlyFrame; +class SwNoTextNode; + +class SwAccessibleNoTextFrame : public SwAccessibleFrameBase, + public css::accessibility::XAccessibleImage, + public css::accessibility::XAccessibleHypertext//Added by yangzhh for HyperLink +{ + friend class SwAccessibleNoTextHyperlink; + css::uno::Reference< css::accessibility::XAccessibleHyperlink > m_xHyperlink; + OUString msTitle; + OUString msDesc; + +protected: + virtual ~SwAccessibleNoTextFrame() override; + + const SwNoTextNode *GetNoTextNode() const; + + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleNoTextFrame( std::shared_ptr const& pInitMap, + sal_Int16 nInitRole, + const SwFlyFrame *pFlyFrame ); + + // XAccessibleContext + + // #i73249# - Return the object's current name. + virtual OUString SAL_CALL + getAccessibleName() override; + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleImage. + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) throw () override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) throw () override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // XAccessibleImage + virtual OUString SAL_CALL + getAccessibleImageDescription( ) override; + + virtual sal_Int32 SAL_CALL + getAccessibleImageHeight( ) override; + + virtual sal_Int32 SAL_CALL + getAccessibleImageWidth( ) override; + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + virtual sal_Int32 SAL_CALL getCaretPosition( ) override; + virtual sal_Bool SAL_CALL setCaretPosition( sal_Int32 nIndex ) override; + virtual sal_Unicode SAL_CALL getCharacter( sal_Int32 nIndex ) override;//Shen Zhen Jie changed sal_Unicode to sal_uInt32 + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::awt::Rectangle SAL_CALL getCharacterBounds( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getCharacterCount( ) override; + virtual sal_Int32 SAL_CALL getIndexAtPoint( const css::awt::Point& aPoint ) override; + virtual OUString SAL_CALL getSelectedText( ) override; + virtual sal_Int32 SAL_CALL getSelectionStart( ) override; + virtual sal_Int32 SAL_CALL getSelectionEnd( ) override; + virtual sal_Bool SAL_CALL setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual OUString SAL_CALL getText( ) override; + virtual OUString SAL_CALL getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual sal_Bool SAL_CALL copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL scrollSubstringTo( sal_Int32 nStartIndex, sal_Int32 nEndIndex, css::accessibility::AccessibleScrollType aScrollType) override; + + // XAccessibleHypertext + virtual sal_Int32 SAL_CALL getHyperLinkCount() override; + virtual css::uno::Reference< css::accessibility::XAccessibleHyperlink > + SAL_CALL getHyperLink( sal_Int32 nLinkIndex ) override; + virtual sal_Int32 SAL_CALL getHyperLinkIndex( sal_Int32 nCharIndex ) override; + + SwAccessibleMap *GetAccessibleMap(){ return GetMap();} + +public: + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotexthyperlink.cxx b/sw/source/core/access/accnotexthyperlink.cxx new file mode 100644 index 000000000..5fbe826c9 --- /dev/null +++ b/sw/source/core/access/accnotexthyperlink.cxx @@ -0,0 +1,234 @@ +/* -*- 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 "accnotexthyperlink.hxx" + +#include + +#include +#include +#include + +#include + +using namespace css; +using namespace css::lang; +using namespace css::uno; +using namespace css::accessibility; + +SwAccessibleNoTextHyperlink::SwAccessibleNoTextHyperlink( SwAccessibleNoTextFrame *p, const SwFrame *aFrame ) : + mxFrame( p ), + mpFrame( aFrame ) +{ +} + +// XAccessibleAction +sal_Int32 SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionCount() +{ + SolarMutexGuard g; + + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + return pMap->GetIMapObjectCount(); + } + else if( !aURL.GetURL().isEmpty() ) + { + return 1; + } + + return 0; +} + +sal_Bool SAL_CALL SwAccessibleNoTextHyperlink::doAccessibleAction( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + bool bRet = false; + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetURL().isEmpty()) + { + SwViewShell *pVSh = mxFrame->GetShell(); + if( pVSh ) + { + LoadURL( *pVSh, pMapObj->GetURL(), LoadUrlFlags::NONE, + pMapObj->GetTarget() ); + bRet = true; + } + } + } + else if (!aURL.GetURL().isEmpty()) + { + SwViewShell *pVSh = mxFrame->GetShell(); + if( pVSh ) + { + LoadURL( *pVSh, aURL.GetURL(), LoadUrlFlags::NONE, + aURL.GetTargetFrameName() ); + bRet = true; + } + } + + return bRet; +} + +OUString SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionDescription( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + OUString sDesc; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetDesc().isEmpty()) + sDesc = pMapObj->GetDesc(); + else if (!pMapObj->GetURL().isEmpty()) + sDesc = pMapObj->GetURL(); + } + else if( !aURL.GetURL().isEmpty() ) + sDesc = aURL.GetName(); + + return sDesc; +} + +Reference< XAccessibleKeyBinding > SAL_CALL + SwAccessibleNoTextHyperlink::getAccessibleActionKeyBinding( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + Reference< XAccessibleKeyBinding > xKeyBinding; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + bool bIsValid = false; + SwFormatURL aURL( GetFormat()->GetURL() ); + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetURL().isEmpty()) + bIsValid = true; + } + else if (!aURL.GetURL().isEmpty()) + bIsValid = true; + + if(bIsValid) + { + ::comphelper::OAccessibleKeyBindingHelper* pKeyBindingHelper = + new ::comphelper::OAccessibleKeyBindingHelper(); + xKeyBinding = pKeyBindingHelper; + + css::awt::KeyStroke aKeyStroke; + aKeyStroke.Modifiers = 0; + aKeyStroke.KeyCode = KEY_RETURN; + aKeyStroke.KeyChar = 0; + aKeyStroke.KeyFunc = 0; + pKeyBindingHelper->AddKeyBinding( aKeyStroke ); + } + + return xKeyBinding; +} + +// XAccessibleHyperlink +Any SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionAnchor( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + Any aRet; + //SwFrame* pAnchor = static_cast(mpFrame)->GetAnchor(); + Reference< XAccessible > xAnchor = mxFrame->GetAccessibleMap()->GetContext(mpFrame); + //SwAccessibleNoTextFrame* pFrame = xFrame.get(); + //Reference< XAccessible > xAnchor = (XAccessible*)pFrame; + aRet <<= xAnchor; + return aRet; +} + +Any SAL_CALL SwAccessibleNoTextHyperlink::getAccessibleActionObject( + sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if(nIndex < 0 || nIndex >= getAccessibleActionCount()) + throw lang::IndexOutOfBoundsException(); + + SwFormatURL aURL( GetFormat()->GetURL() ); + OUString retText; + ImageMap* pMap = aURL.GetMap(); + if( pMap != nullptr ) + { + IMapObject* pMapObj = pMap->GetIMapObject(nIndex); + if (!pMapObj->GetURL().isEmpty()) + retText = pMapObj->GetURL(); + } + else if ( !aURL.GetURL().isEmpty() ) + retText = aURL.GetURL(); + + Any aRet; + aRet <<= retText; + return aRet; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextHyperlink::getStartIndex() +{ + return 0; +} + +sal_Int32 SAL_CALL SwAccessibleNoTextHyperlink::getEndIndex() +{ + return 0; +} + +sal_Bool SAL_CALL SwAccessibleNoTextHyperlink::isValid( ) +{ + SolarMutexGuard g; + + SwFormatURL aURL( GetFormat()->GetURL() ); + + if( aURL.GetMap() || !aURL.GetURL().isEmpty() ) + return true; + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accnotexthyperlink.hxx b/sw/source/core/access/accnotexthyperlink.hxx new file mode 100644 index 000000000..b986abc87 --- /dev/null +++ b/sw/source/core/access/accnotexthyperlink.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTHYPERLINK_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCNOTEXTHYPERLINK_HXX + +#include +#include +#include + +#include "accnotextframe.hxx" + +class SwAccessibleNoTextHyperlink : + public ::cppu::WeakImplHelper< + css::accessibility::XAccessibleHyperlink > +{ + friend class SwAccessibleNoTextFrame; + + ::rtl::Reference< SwAccessibleNoTextFrame > mxFrame; + const SwFrame *mpFrame; + + SwFrameFormat *GetFormat() + { + return const_cast(static_cast(mpFrame))->GetFormat(); + } +public: + + SwAccessibleNoTextHyperlink( SwAccessibleNoTextFrame *p, const SwFrame* aFrame ); + + // XAccessibleAction + virtual sal_Int32 SAL_CALL getAccessibleActionCount() override; + virtual sal_Bool SAL_CALL doAccessibleAction( sal_Int32 nIndex ) override; + virtual OUString SAL_CALL getAccessibleActionDescription( + sal_Int32 nIndex ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleKeyBinding > SAL_CALL + getAccessibleActionKeyBinding( sal_Int32 nIndex ) override; + + // XAccessibleHyperlink + virtual css::uno::Any SAL_CALL getAccessibleActionAnchor( + sal_Int32 nIndex ) override; + virtual css::uno::Any SAL_CALL getAccessibleActionObject( + sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getStartIndex() override; + virtual sal_Int32 SAL_CALL getEndIndex() override; + virtual sal_Bool SAL_CALL isValid( ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpage.cxx b/sw/source/core/access/accpage.cxx new file mode 100644 index 000000000..39cf02b4b --- /dev/null +++ b/sw/source/core/access/accpage.cxx @@ -0,0 +1,160 @@ +/* -*- 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 "accpage.hxx" + +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +using uno::Sequence; + +const char sImplementationName[] = "com.sun.star.comp.Writer.SwAccessiblePageView"; + +bool SwAccessiblePage::IsSelected() +{ + return GetMap()->IsPageSelected( static_cast < const SwPageFrame * >( GetFrame() ) ); +} + +void SwAccessiblePage::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // FOCUSABLE + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + + // FOCUSED + if( IsSelected() ) + { + OSL_ENSURE( m_bIsSelected, "bSelected out of sync" ); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + } +} + +void SwAccessiblePage::InvalidateCursorPos_() +{ + bool bNewSelected = IsSelected(); + bool bOldSelected; + + { + osl::MutexGuard aGuard( m_Mutex ); + bOldSelected = m_bIsSelected; + m_bIsSelected = bNewSelected; + } + + if( bNewSelected ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + if( bOldSelected != bNewSelected ) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, bNewSelected ); + } +} + +void SwAccessiblePage::InvalidateFocus_() +{ + vcl::Window *pWin = GetWindow(); + if( pWin ) + { + bool bSelected; + + { + osl::MutexGuard aGuard( m_Mutex ); + bSelected = m_bIsSelected; + } + OSL_ENSURE( bSelected, "focus object should be selected" ); + + FireStateChangedEvent( AccessibleStateType::FOCUSED, + pWin->HasFocus() && bSelected ); + } +} + +SwAccessiblePage::SwAccessiblePage(std::shared_ptr const& pInitMap, + const SwFrame* pFrame ) + : SwAccessibleContext( pInitMap, AccessibleRole::PANEL, pFrame ) + , m_bIsSelected( false ) +{ + assert(pFrame != nullptr); + assert(pInitMap != nullptr); + assert(pFrame->IsPageFrame()); + + OUString sPage = OUString::number( + static_cast( GetFrame() )->GetPhyPageNum() ); + SetName( GetResource( STR_ACCESS_PAGE_NAME, &sPage ) ); +} + +SwAccessiblePage::~SwAccessiblePage() +{ +} + +bool SwAccessiblePage::HasCursor() +{ + osl::MutexGuard aGuard( m_Mutex ); + return m_bIsSelected; +} + +OUString SwAccessiblePage::getImplementationName( ) +{ + return sImplementationName; +} + +sal_Bool SwAccessiblePage::supportsService( const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence SwAccessiblePage::getSupportedServiceNames( ) +{ + return { "com.sun.star.text.AccessiblePageView", sAccessibleServiceName }; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessiblePage::getImplementationId() +{ + return css::uno::Sequence(); +} + +OUString SwAccessiblePage::getAccessibleDescription( ) +{ + ThrowIfDisposed(); + + OUString sArg( GetFormattedPageNumber() ); + return GetResource( STR_ACCESS_PAGE_DESC, &sArg ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpage.hxx b/sw/source/core/access/accpage.hxx new file mode 100644 index 000000000..006eda746 --- /dev/null +++ b/sw/source/core/access/accpage.hxx @@ -0,0 +1,77 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPAGE_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPAGE_HXX + +#include "acccontext.hxx" + +/** + * accessibility implementation for the page (SwPageFrame) + * The page is _only_ visible in the page preview. For the regular + * document view, it doesn't make sense to add this additional element + * into the hierarchy. For the page preview, however, the page is the + * important. + */ +class SwAccessiblePage : public SwAccessibleContext +{ + bool m_bIsSelected; // protected by base class mutex + + bool IsSelected(); + + using SwAccessibleFrame::GetBounds; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets + // FOCUSABLE(1) and FOCUSED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual void InvalidateCursorPos_() override; + virtual void InvalidateFocus_() override; + + virtual ~SwAccessiblePage() override; + +public: + // convenience constructor to avoid typecast; + // may only be called with SwPageFrame argument + SwAccessiblePage(std::shared_ptr const& pInitMap, + const SwFrame* pFrame); + + // XAccessibleContext methods that need to be overridden + + virtual OUString SAL_CALL getAccessibleDescription() override; + + // XServiceInfo + + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService ( + const OUString& sServiceName) override; + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + virtual bool HasCursor() override; // required by map to remember that object +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpara.cxx b/sw/source/core/access/accpara.cxx new file mode 100644 index 000000000..4119275a7 --- /dev/null +++ b/sw/source/core/access/accpara.cxx @@ -0,0 +1,3558 @@ +/* -*- 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 "accpara.hxx" +#include "accportions.hxx" +#include +#include +#include +#include +#include +#include +#include +#include "acchyperlink.hxx" +#include "acchypertextdata.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "textmarkuphelper.hxx" +#include "parachangetrackinginfo.hxx" +#include +#include +#include +#include +#include + +#include +#include +#include "../../uibase/inc/fldmgr.hxx" +#include // SwField + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::container; + +using beans::PropertyValue; +using beans::XMultiPropertySet; +using beans::UnknownPropertyException; +using beans::PropertyState_DIRECT_VALUE; + +using std::max; +using std::min; +using std::sort; + +namespace com::sun::star::text { + class XText; +} + +const char sServiceName[] = "com.sun.star.text.AccessibleParagraphView"; +const char sImplementationName[] = "com.sun.star.comp.Writer.SwAccessibleParagraphView"; + +OUString const & SwAccessibleParagraph::GetString() +{ + return GetPortionData().GetAccessibleString(); +} + +OUString SwAccessibleParagraph::GetDescription() +{ + return OUString(); // provide empty description for paragraphs +} + +sal_Int32 SwAccessibleParagraph::GetCaretPos() +{ + sal_Int32 nRet = -1; + + // get the selection's point, and test whether it's in our node + // #i27301# - consider adjusted method signature + SwPaM* pCaret = GetCursor( false ); // caret is first PaM in PaM-ring + + if( pCaret != nullptr ) + { + SwTextFrame const*const pTextFrame(static_cast(GetFrame())); + assert(pTextFrame); + + // check whether the point points into 'our' node + SwPosition* pPoint = pCaret->GetPoint(); + if (sw::FrameContainsNode(*pTextFrame, pPoint->nNode.GetIndex())) + { + // same node? Then check whether it's also within 'our' part + // of the paragraph + const TextFrameIndex nIndex = pTextFrame->MapModelToViewPos(*pPoint); + if(!GetPortionData().IsValidCorePosition( nIndex ) || + (GetPortionData().IsZeroCorePositionData() + && nIndex == TextFrameIndex(0))) + { + bool bFormat = pTextFrame->HasPara(); + if(bFormat) + { + ClearPortionData(); + UpdatePortionData(); + } + } + if( GetPortionData().IsValidCorePosition( nIndex ) ) + { + // Yes, it's us! + // consider that cursor/caret is in front of the list label + if ( pCaret->IsInFrontOfLabel() ) + { + nRet = 0; + } + else + { + nRet = GetPortionData().GetAccessiblePosition( nIndex ); + } + + OSL_ENSURE( nRet >= 0, "invalid cursor?" ); + OSL_ENSURE( nRet <= GetPortionData().GetAccessibleString(). + getLength(), "invalid cursor?" ); + } + // else: in this paragraph, but in different frame + } + // else: not in this paragraph + } + // else: no cursor -> no caret + + return nRet; +} + +// #i27301# - new parameter <_bForSelection> +SwPaM* SwAccessibleParagraph::GetCursor( const bool _bForSelection ) +{ + // get the cursor shell; if we don't have any, we don't have a + // cursor/selection either + SwPaM* pCursor = nullptr; + SwCursorShell* pCursorShell = SwAccessibleParagraph::GetCursorShell(); + // #i27301# - if cursor is retrieved for selection, the cursors for + // a table selection has to be returned. + if ( pCursorShell != nullptr && + ( _bForSelection || !pCursorShell->IsTableMode() ) ) + { + SwFEShell *pFESh = dynamic_cast( pCursorShell) != nullptr + ? static_cast< SwFEShell * >( pCursorShell ) : nullptr; + if( !pFESh || + !(pFESh->IsFrameSelected() || pFESh->IsObjSelected() > 0) ) + { + // get the selection, and test whether it affects our text node + pCursor = pCursorShell->GetCursor( false /* ??? */ ); + } + } + + return pCursor; +} + +bool SwAccessibleParagraph::IsHeading() const +{ + SwTextFrame const*const pFrame(static_cast(GetFrame())); + const SwTextNode *pTextNd = pFrame->GetTextNodeForParaProps(); + return pTextNd->IsOutline(); +} + +void SwAccessibleParagraph::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + + // MULTILINE + rStateSet.AddState( AccessibleStateType::MULTI_LINE ); + + if (GetCursorShell()) + { + // MULTISELECTABLE + rStateSet.AddState(AccessibleStateType::MULTI_SELECTABLE); + // FOCUSABLE + rStateSet.AddState(AccessibleStateType::FOCUSABLE); + } + + // FOCUSED (simulates node index of cursor) + SwPaM* pCaret = GetCursor( false ); // #i27301# - consider adjusted method signature + SwTextFrame const*const pFrame(static_cast(GetFrame())); + assert(pFrame); + if (pCaret != nullptr && + sw::FrameContainsNode(*pFrame, pCaret->GetPoint()->nNode.GetIndex()) && + m_nOldCaretPos != -1) + { + vcl::Window *pWin = GetWindow(); + if( pWin && pWin->HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } +} + +void SwAccessibleParagraph::InvalidateContent_( bool bVisibleDataFired ) +{ + OUString sOldText( GetString() ); + + ClearPortionData(); + + const OUString& rText = GetString(); + + if( rText != sOldText ) + { + // The text is changed + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TEXT_CHANGED; + + // determine exact changes between sOldText and rText + (void)comphelper::OCommonAccessibleText::implInitTextChangedEvent(sOldText, rText, + aEvent.OldValue, + aEvent.NewValue); + + FireAccessibleEvent( aEvent ); + uno::Reference< XAccessible > xparent = getAccessibleParent(); + uno::Reference< XAccessibleContext > xAccContext(xparent,uno::UNO_QUERY); + if (xAccContext.is() && xAccContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + SwAccessibleContext* pPara = static_cast< SwAccessibleContext* >(xparent.get()); + if(pPara) + { + AccessibleEventObject aParaEvent; + aParaEvent.EventId = AccessibleEventId::VALUE_CHANGED; + pPara->FireAccessibleEvent(aParaEvent); + } + } + } + else if( !bVisibleDataFired ) + { + FireVisibleDataEvent(); + } + + bool bNewIsHeading = IsHeading(); + //Get the real heading level, Heading1 ~ Heading10 + m_nHeadingLevel = GetRealHeadingLevel(); + bool bOldIsHeading; + { + osl::MutexGuard aGuard( m_Mutex ); + bOldIsHeading = m_bIsHeading; + if( m_bIsHeading != bNewIsHeading ) + m_bIsHeading = bNewIsHeading; + } + + if( bNewIsHeading != bOldIsHeading ) + { + // The role has changed + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::ROLE_CHANGED; + + FireAccessibleEvent( aEvent ); + } + + if( rText != sOldText ) + { + OUString sNewDesc( GetDescription() ); + OUString sOldDesc; + { + osl::MutexGuard aGuard( m_Mutex ); + sOldDesc = m_sDesc; + if( m_sDesc != sNewDesc ) + m_sDesc = sNewDesc; + } + + if( sNewDesc != sOldDesc ) + { + // The text is changed + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= sNewDesc; + + FireAccessibleEvent( aEvent ); + } + } +} + +void SwAccessibleParagraph::InvalidateCursorPos_() +{ + // The text is changed + sal_Int32 nNew = GetCaretPos(); + sal_Int32 nOld; + { + osl::MutexGuard aGuard( m_Mutex ); + nOld = m_nOldCaretPos; + m_nOldCaretPos = nNew; + } + if( -1 != nNew ) + { + // remember that object as the one that has the caret. This is + // necessary to notify that object if the cursor leaves it. + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + vcl::Window *pWin = GetWindow(); + if( nOld == nNew ) + return; + + // The cursor's node position is simulated by the focus! + if( pWin && pWin->HasFocus() && -1 == nOld ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, true ); + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::CARET_CHANGED; + aEvent.OldValue <<= nOld; + aEvent.NewValue <<= nNew; + + FireAccessibleEvent( aEvent ); + + if( pWin && pWin->HasFocus() && -1 == nNew ) + FireStateChangedEvent( AccessibleStateType::FOCUSED, false ); + //To send TEXT_SELECTION_CHANGED event + sal_Int32 nStart=0; + sal_Int32 nEnd =0; + bool bCurSelection = GetSelection(nStart,nEnd); + if(m_bLastHasSelection || bCurSelection ) + { + aEvent.EventId = AccessibleEventId::TEXT_SELECTION_CHANGED; + aEvent.OldValue.clear(); + aEvent.NewValue.clear(); + FireAccessibleEvent(aEvent); + } + m_bLastHasSelection =bCurSelection; + +} + +void SwAccessibleParagraph::InvalidateFocus_() +{ + vcl::Window *pWin = GetWindow(); + if( pWin ) + { + sal_Int32 nPos; + { + osl::MutexGuard aGuard( m_Mutex ); + nPos = m_nOldCaretPos; + } + OSL_ENSURE( nPos != -1, "focus object should be selected" ); + + FireStateChangedEvent( AccessibleStateType::FOCUSED, + pWin->HasFocus() && nPos != -1 ); + } +} + +SwAccessibleParagraph::SwAccessibleParagraph( + std::shared_ptr const& pInitMap, + const SwTextFrame& rTextFrame ) + : SwAccessibleContext( pInitMap, AccessibleRole::PARAGRAPH, &rTextFrame ) + , m_sDesc() + , m_nOldCaretPos( -1 ) + , m_bIsHeading( false ) + //Get the real heading level, Heading1 ~ Heading10 + , m_nHeadingLevel (-1) + , m_aSelectionHelper( *this ) + , mpParaChangeTrackInfo( new SwParaChangeTrackingInfo( rTextFrame ) ) // #i108125# + , m_bLastHasSelection(false) //To add TEXT_SELECTION_CHANGED event +{ + StartListening(const_cast(rTextFrame)); + m_bIsHeading = IsHeading(); + //Get the real heading level, Heading1 ~ Heading10 + m_nHeadingLevel = GetRealHeadingLevel(); + SetName( OUString() ); // set an empty accessibility name for paragraphs + + // If this object has the focus, then it is remembered by the map itself. + m_nOldCaretPos = GetCaretPos(); +} + +SwAccessibleParagraph::~SwAccessibleParagraph() +{ + SolarMutexGuard aGuard; + + m_pPortionData.reset(); + m_pHyperTextData.reset(); + mpParaChangeTrackInfo.reset(); // #i108125# + EndListeningAll(); +} + +bool SwAccessibleParagraph::HasCursor() +{ + osl::MutexGuard aGuard( m_Mutex ); + return m_nOldCaretPos != -1; +} + +void SwAccessibleParagraph::UpdatePortionData() +{ + // obtain the text frame + OSL_ENSURE( GetFrame() != nullptr, "The text frame has vanished!" ); + OSL_ENSURE( GetFrame()->IsTextFrame(), "The text frame has mutated!" ); + const SwTextFrame* pFrame = static_cast( GetFrame() ); + + // build new portion data + m_pPortionData.reset( new SwAccessiblePortionData( + pFrame, GetMap()->GetShell()->GetViewOptions()) ); + pFrame->VisitPortions( *m_pPortionData ); + + OSL_ENSURE( m_pPortionData != nullptr, "UpdatePortionData() failed" ); +} + +void SwAccessibleParagraph::ClearPortionData() +{ + m_pPortionData.reset(); + m_pHyperTextData.reset(); +} + +void SwAccessibleParagraph::ExecuteAtViewShell( sal_uInt16 nSlot ) +{ + OSL_ENSURE( GetMap() != nullptr, "no map?" ); + SwViewShell* pViewShell = GetMap()->GetShell(); + + OSL_ENSURE( pViewShell != nullptr, "View shell expected!" ); + SfxViewShell* pSfxShell = pViewShell->GetSfxViewShell(); + + OSL_ENSURE( pSfxShell != nullptr, "SfxViewShell shell expected!" ); + if( !pSfxShell ) + return; + + SfxViewFrame *pFrame = pSfxShell->GetViewFrame(); + OSL_ENSURE( pFrame != nullptr, "View frame expected!" ); + if( !pFrame ) + return; + + SfxDispatcher *pDispatcher = pFrame->GetDispatcher(); + OSL_ENSURE( pDispatcher != nullptr, "Dispatcher expected!" ); + if( !pDispatcher ) + return; + + pDispatcher->Execute( nSlot ); +} + +SwXTextPortion* SwAccessibleParagraph::CreateUnoPortion( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + OSL_ENSURE( (IsValidChar(nStartIndex, GetString().getLength()) && + (nEndIndex == -1)) || + IsValidRange(nStartIndex, nEndIndex, GetString().getLength()), + "please check parameters before calling this method" ); + + const TextFrameIndex nStart = GetPortionData().GetCoreViewPosition(nStartIndex); + const TextFrameIndex nEnd = (nEndIndex == -1) + ? (nStart + TextFrameIndex(1)) + : GetPortionData().GetCoreViewPosition(nEndIndex); + + // create UNO cursor + SwTextFrame const*const pFrame(static_cast(GetFrame())); + SwPosition aStartPos(pFrame->MapViewToModelPos(nStart)); + auto pUnoCursor(const_cast(pFrame->GetDoc()).CreateUnoCursor(aStartPos)); + pUnoCursor->SetMark(); + *pUnoCursor->GetMark() = pFrame->MapViewToModelPos(nEnd); + + // create a (dummy) text portion to be returned + uno::Reference aEmpty; + SwXTextPortion* pPortion = + new SwXTextPortion ( pUnoCursor.get(), aEmpty, PORTION_TEXT); + + return pPortion; +} + +// range checking for parameter + +bool SwAccessibleParagraph::IsValidChar( + sal_Int32 nPos, sal_Int32 nLength) +{ + return (nPos >= 0) && (nPos < nLength); +} + +bool SwAccessibleParagraph::IsValidPosition( + sal_Int32 nPos, sal_Int32 nLength) +{ + return (nPos >= 0) && (nPos <= nLength); +} + +bool SwAccessibleParagraph::IsValidRange( + sal_Int32 nBegin, sal_Int32 nEnd, sal_Int32 nLength) +{ + return IsValidPosition(nBegin, nLength) && IsValidPosition(nEnd, nLength); +} + +//the function is to check whether the position is in a redline range. +const SwRangeRedline* SwAccessibleParagraph::GetRedlineAtIndex() +{ + const SwRangeRedline* pRedline = nullptr; + SwPaM* pCrSr = GetCursor( true ); + if ( pCrSr ) + { + SwPosition* pStart = pCrSr->Start(); + pRedline = pStart->GetDoc()->getIDocumentRedlineAccess().GetRedline(*pStart, nullptr); + } + + return pRedline; +} + +// text boundaries + +bool SwAccessibleParagraph::GetCharBoundary( + i18n::Boundary& rBound, + sal_Int32 nPos ) +{ + if( GetPortionData().FillBoundaryIFDateField( rBound, nPos) ) + return true; + + rBound.startPos = nPos; + rBound.endPos = nPos+1; + return true; +} + +bool SwAccessibleParagraph::GetWordBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + // now ask the Break-Iterator for the word + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + // get locale for this position + SwTextFrame const*const pFrame(static_cast(GetFrame())); + const TextFrameIndex nCorePos = GetPortionData().GetCoreViewPosition(nPos); + lang::Locale aLocale = g_pBreakIt->GetLocale(pFrame->GetLangOfChar(nCorePos, 0, true)); + + // which type of word are we interested in? + // (DICTIONARY_WORD includes punctuation, ANY_WORD doesn't.) + const sal_Int16 nWordType = i18n::WordType::ANY_WORD; + + // get word boundary, as the Break-Iterator sees fit. + rBound = g_pBreakIt->GetBreakIter()->getWordBoundary( + rText, nPos, aLocale, nWordType, true ); + + return true; +} + +bool SwAccessibleParagraph::GetSentenceBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + const sal_Unicode* pStr = rText.getStr(); + while( nPos < rText.getLength() && pStr[nPos] == u' ' ) + nPos++; + + GetPortionData().GetSentenceBoundary( rBound, nPos ); + return true; +} + +bool SwAccessibleParagraph::GetLineBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + if( rText.getLength() == nPos ) + GetPortionData().GetLastLineBoundary( rBound ); + else + GetPortionData().GetLineBoundary( rBound, nPos ); + return true; +} + +bool SwAccessibleParagraph::GetParagraphBoundary( + i18n::Boundary& rBound, + const OUString& rText ) +{ + rBound.startPos = 0; + rBound.endPos = rText.getLength(); + return true; +} + +bool SwAccessibleParagraph::GetAttributeBoundary( + i18n::Boundary& rBound, + sal_Int32 nPos ) +{ + GetPortionData().GetAttributeBoundary( rBound, nPos ); + return true; +} + +bool SwAccessibleParagraph::GetGlyphBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ) +{ + // ask the Break-Iterator for the glyph by moving one cell + // forward, and then one cell back + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + // get locale for this position + SwTextFrame const*const pFrame(static_cast(GetFrame())); + const TextFrameIndex nCorePos = GetPortionData().GetCoreViewPosition(nPos); + lang::Locale aLocale = g_pBreakIt->GetLocale(pFrame->GetLangOfChar(nCorePos, 0, true)); + + // get word boundary, as the Break-Iterator sees fit. + const sal_Int16 nIterMode = i18n::CharacterIteratorMode::SKIPCELL; + sal_Int32 nDone = 0; + rBound.endPos = g_pBreakIt->GetBreakIter()->nextCharacters( + rText, nPos, aLocale, nIterMode, 1, nDone ); + rBound.startPos = g_pBreakIt->GetBreakIter()->previousCharacters( + rText, rBound.endPos, aLocale, nIterMode, 1, nDone ); + bool bRet = ((rBound.startPos <= nPos) && (nPos <= rBound.endPos)); + OSL_ENSURE( rBound.startPos <= nPos, "start pos too high" ); + OSL_ENSURE( rBound.endPos >= nPos, "end pos too low" ); + + return bRet; +} + +bool SwAccessibleParagraph::GetTextBoundary( + i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos, + sal_Int16 nTextType ) +{ + // error checking + if( !( AccessibleTextType::LINE == nTextType + ? IsValidPosition( nPos, rText.getLength() ) + : IsValidChar( nPos, rText.getLength() ) ) ) + throw lang::IndexOutOfBoundsException(); + + bool bRet; + + switch( nTextType ) + { + case AccessibleTextType::WORD: + bRet = GetWordBoundary(rBound, rText, nPos); + break; + + case AccessibleTextType::SENTENCE: + bRet = GetSentenceBoundary( rBound, rText, nPos ); + break; + + case AccessibleTextType::PARAGRAPH: + bRet = GetParagraphBoundary( rBound, rText ); + break; + + case AccessibleTextType::CHARACTER: + bRet = GetCharBoundary( rBound, nPos ); + break; + + case AccessibleTextType::LINE: + //Solve the problem of returning wrong LINE and PARAGRAPH + if((nPos == rText.getLength()) && nPos > 0) + bRet = GetLineBoundary( rBound, rText, nPos - 1); + else + bRet = GetLineBoundary( rBound, rText, nPos ); + break; + + case AccessibleTextType::ATTRIBUTE_RUN: + bRet = GetAttributeBoundary( rBound, nPos ); + break; + + case AccessibleTextType::GLYPH: + bRet = GetGlyphBoundary( rBound, rText, nPos ); + break; + + default: + throw lang::IllegalArgumentException( ); + } + + return bRet; +} + +OUString SAL_CALL SwAccessibleParagraph::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + osl::MutexGuard aGuard2( m_Mutex ); + if( m_sDesc.isEmpty() ) + m_sDesc = GetDescription(); + + return m_sDesc; +} + +lang::Locale SAL_CALL SwAccessibleParagraph::getLocale() +{ + SolarMutexGuard aGuard; + + const SwTextFrame *pTextFrame = dynamic_cast( GetFrame() ); + if( !pTextFrame ) + { + throw uno::RuntimeException("no SwTextFrame", static_cast(this)); + } + + lang::Locale aLoc(g_pBreakIt->GetLocale(pTextFrame->GetLangOfChar(TextFrameIndex(0), 0, true))); + + return aLoc; +} + +// #i27138# - paragraphs are in relation CONTENT_FLOWS_FROM and/or CONTENT_FLOWS_TO +uno::Reference SAL_CALL SwAccessibleParagraph::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + utl::AccessibleRelationSetHelper* pHelper = new utl::AccessibleRelationSetHelper(); + + const SwTextFrame* pTextFrame = dynamic_cast(GetFrame()); + OSL_ENSURE( pTextFrame, + " - missing text frame"); + if ( pTextFrame ) + { + const SwContentFrame* pPrevContentFrame( pTextFrame->FindPrevCnt() ); + if ( pPrevContentFrame ) + { + uno::Sequence< uno::Reference > aSequence { GetMap()->GetContext( pPrevContentFrame ) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_FROM, + aSequence ); + pHelper->AddRelation( aAccRel ); + } + + const SwContentFrame* pNextContentFrame( pTextFrame->FindNextCnt( true ) ); + if ( pNextContentFrame ) + { + uno::Sequence< uno::Reference > aSequence { GetMap()->GetContext( pNextContentFrame ) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_TO, + aSequence ); + pHelper->AddRelation( aAccRel ); + } + } + + return pHelper; +} + +void SAL_CALL SwAccessibleParagraph::grabFocus() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // get cursor shell + SwCursorShell *pCursorSh = GetCursorShell(); + SwPaM *pCursor = GetCursor( false ); // #i27301# - consider new method signature + const SwTextFrame *pTextFrame = static_cast( GetFrame() ); + + if (pCursorSh != nullptr && + ( pCursor == nullptr || + !sw::FrameContainsNode(*pTextFrame, pCursor->GetPoint()->nNode.GetIndex()) || + !pTextFrame->IsInside(pTextFrame->MapModelToViewPos(*pCursor->GetPoint())))) + { + // create pam for selection + SwPosition const aStartPos(pTextFrame->MapViewToModelPos(pTextFrame->GetOffset())); + SwPaM aPaM( aStartPos ); + + // set PaM at cursor shell + Select( aPaM ); + + } + + // ->#i13955# + vcl::Window * pWindow = GetWindow(); + + if (pWindow != nullptr) + pWindow->GrabFocus(); + // <-#i13955# +} + +// #i71385# +static bool lcl_GetBackgroundColor( Color & rColor, + const SwFrame* pFrame, + SwCursorShell* pCursorSh ) +{ + const SvxBrushItem* pBackgrdBrush = nullptr; + const Color* pSectionTOXColor = nullptr; + SwRect aDummyRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( pFrame && + pFrame->GetBackgroundBrush( aFillAttributes, pBackgrdBrush, pSectionTOXColor, aDummyRect, false, /*bConsiderTextBox=*/false ) ) + { + if ( pSectionTOXColor ) + { + rColor = *pSectionTOXColor; + return true; + } + else + { + rColor = pBackgrdBrush->GetColor(); + return true; + } + } + else if ( pCursorSh ) + { + rColor = pCursorSh->Imp()->GetRetoucheColor(); + return true; + } + + return false; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getForeground() +{ + SolarMutexGuard g; + + Color aBackgroundCol; + + if ( lcl_GetBackgroundColor( aBackgroundCol, GetFrame(), GetCursorShell() ) ) + { + if ( aBackgroundCol.IsDark() ) + { + return sal_Int32(COL_WHITE); + } + else + { + return sal_Int32(COL_BLACK); + } + } + + return SwAccessibleContext::getForeground(); +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getBackground() +{ + SolarMutexGuard g; + + Color aBackgroundCol; + + if ( lcl_GetBackgroundColor( aBackgroundCol, GetFrame(), GetCursorShell() ) ) + { + return sal_Int32(aBackgroundCol); + } + + return SwAccessibleContext::getBackground(); +} + +OUString SAL_CALL SwAccessibleParagraph::getImplementationName() +{ + return sImplementationName; +} + +sal_Bool SAL_CALL SwAccessibleParagraph::supportsService( + const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleParagraph::getSupportedServiceNames() +{ + return { sServiceName, sAccessibleServiceName }; +} + +static uno::Sequence< OUString > const & getAttributeNames() +{ + static uno::Sequence< OUString > const aNames + { + // Add the font name to attribute list + // sorted list of strings + UNO_NAME_CHAR_BACK_COLOR, + UNO_NAME_CHAR_COLOR, + UNO_NAME_CHAR_CONTOURED, + UNO_NAME_CHAR_EMPHASIS, + UNO_NAME_CHAR_ESCAPEMENT, + UNO_NAME_CHAR_FONT_NAME, + UNO_NAME_CHAR_HEIGHT, + UNO_NAME_CHAR_POSTURE, + UNO_NAME_CHAR_SHADOWED, + UNO_NAME_CHAR_STRIKEOUT, + UNO_NAME_CHAR_UNDERLINE, + UNO_NAME_CHAR_UNDERLINE_COLOR, + UNO_NAME_CHAR_WEIGHT, + }; + return aNames; +} + +static uno::Sequence< OUString > const & getSupplementalAttributeNames() +{ + static uno::Sequence< OUString > const aNames + { + // sorted list of strings + UNO_NAME_NUMBERING_LEVEL, + UNO_NAME_NUMBERING_RULES, + UNO_NAME_PARA_ADJUST, + UNO_NAME_PARA_BOTTOM_MARGIN, + UNO_NAME_PARA_FIRST_LINE_INDENT, + UNO_NAME_PARA_LEFT_MARGIN, + UNO_NAME_PARA_LINE_SPACING, + UNO_NAME_PARA_RIGHT_MARGIN, + UNO_NAME_TABSTOPS, + }; + return aNames; +} + +// XInterface + +uno::Any SwAccessibleParagraph::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet; + if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccText = static_cast(*this); // resolve ambiguity + aRet <<= aAccText; + } + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccEditText = this; + aRet <<= aAccEditText; + } + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccSel = this; + aRet <<= aAccSel; + } + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccHyp = this; + aRet <<= aAccHyp; + } + // #i63870# + // add interface com::sun:star:accessibility::XAccessibleTextAttributes + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccTextAttr = this; + aRet <<= aAccTextAttr; + } + // #i89175# + // add interface com::sun:star:accessibility::XAccessibleTextMarkup + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccTextMarkup = this; + aRet <<= aAccTextMarkup; + } + // add interface com::sun:star:accessibility::XAccessibleMultiLineText + else if ( rType == cppu::UnoType::get()) + { + uno::Reference aAccMultiLineText = this; + aRet <<= aAccMultiLineText; + } + else if ( rType == cppu::UnoType::get()) + { + uno::Reference< css::accessibility::XAccessibleTextSelection > aTextExtension = this; + aRet <<= aTextExtension; + } + else if ( rType == cppu::UnoType::get()) + { + uno::Reference xAttr = this; + aRet <<= xAttr; + } + else + { + aRet = SwAccessibleContext::queryInterface(rType); + } + + return aRet; +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleParagraph::getTypes() +{ + // #i63870# - add type accessibility::XAccessibleTextAttributes + // #i89175# - add type accessibility::XAccessibleTextMarkup and + return cppu::OTypeCollection( + cppu::UnoType::get(), + cppu::UnoType::get(), + ::cppu::UnoType::get(), + cppu::UnoType::get(), + cppu::UnoType::get(), + cppu::UnoType::get(), + SwAccessibleContext::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleParagraph::getImplementationId() +{ + return css::uno::Sequence(); +} + +// XAccessibleText + +sal_Int32 SwAccessibleParagraph::getCaretPosition() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nRet = GetCaretPos(); + { + osl::MutexGuard aOldCaretPosGuard( m_Mutex ); + OSL_ENSURE( nRet == m_nOldCaretPos, "caret pos out of sync" ); + m_nOldCaretPos = nRet; + } + if( -1 != nRet ) + { + ::rtl::Reference < SwAccessibleContext > xThis( this ); + GetMap()->SetCursorContext( xThis ); + } + + return nRet; +} + +sal_Bool SAL_CALL SwAccessibleParagraph::setCaretPosition( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + bool bRet = false; + + // get cursor shell + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + // create pam for selection + SwTextFrame const*const pFrame(static_cast(GetFrame())); + TextFrameIndex const nFrameIndex(GetPortionData().GetCoreViewPosition(nIndex)); + SwPosition aStartPos(pFrame->MapViewToModelPos(nFrameIndex)); + SwPaM aPaM( aStartPos ); + + // set PaM at cursor shell + bRet = Select( aPaM ); + } + + return bRet; +} + +sal_Unicode SwAccessibleParagraph::getCharacter( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + OUString sText( GetString() ); + + // return character (if valid) + if( !IsValidChar(nIndex, sText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + return sText[nIndex]; +} + +css::uno::Sequence< css::style::TabStop > SwAccessibleParagraph::GetCurrentTabStop( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + /* #i12332# The position after the string needs special treatment. + IsValidChar -> IsValidPosition + */ + if( ! (IsValidPosition( nIndex, GetString().getLength() ) ) ) + throw lang::IndexOutOfBoundsException(); + + /* #i12332# */ + bool bBehindText = false; + if ( nIndex == GetString().getLength() ) + bBehindText = true; + + // get model position & prepare GetCharRect() arguments + SwCursorMoveState aMoveState; + aMoveState.m_bRealHeight = true; + aMoveState.m_bRealWidth = true; + SwSpecialPos aSpecialPos; + SwTextFrame const*const pFrame(static_cast(GetFrame())); + + /* #i12332# FillSpecialPos does not accept nIndex == + GetString().getLength(). In that case nPos is set to the + length of the string in the core. This way GetCharRect + returns the rectangle for a cursor at the end of the + paragraph. */ + const TextFrameIndex nPos = bBehindText + ? TextFrameIndex(pFrame->GetText().getLength()) + : GetPortionData().FillSpecialPos(nIndex, aSpecialPos, aMoveState.m_pSpecialPos ); + + // call GetCharRect + SwRect aCoreRect; + SwPosition aPosition(pFrame->MapViewToModelPos(nPos)); + GetFrame()->GetCharRect( aCoreRect, aPosition, &aMoveState ); + + // already get the caret position + css::uno::Sequence< css::style::TabStop > tabs; + const sal_Int32 nStrLen = pFrame->GetText().getLength(); + if( nStrLen > 0 ) + { + SwFrame* pTFrame = const_cast(GetFrame()); + tabs = pTFrame->GetTabStopInfo(aCoreRect.Left()); + } + + if( tabs.hasElements() ) + { + // translate core coordinates into accessibility coordinates + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + SwRect aTmpRect(0, 0, tabs[0].Position, 0); + + tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aTmpRect.SVRect() )); + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds.SVRect() ).TopLeft() ); + aScreenRect.Move( -aFramePixPos.X(), -aFramePixPos.Y() ); + + tabs[0].Position = aScreenRect.GetWidth(); + } + + return tabs; +} + +namespace { + +struct IndexCompare +{ + const PropertyValue* pValues; + explicit IndexCompare( const PropertyValue* pVals ) : pValues(pVals) {} + bool operator() ( sal_Int32 a, sal_Int32 b ) const + { + return (pValues[a].Name < pValues[b].Name); + } +}; + +} + +OUString SwAccessibleParagraph::GetFieldTypeNameAtIndex(sal_Int32 nIndex) +{ + OUString strTypeName; + SwFieldMgr aMgr; + SwTextField* pTextField = nullptr; + sal_Int32 nFieldIndex = GetPortionData().GetFieldIndex(nIndex); + if (nFieldIndex >= 0) + { + SwTextFrame const*const pFrame(static_cast(GetFrame())); + sw::MergedAttrIter iter(*pFrame); + while (SwTextAttr const*const pHt = iter.NextAttr()) + { + if ((pHt->Which() == RES_TXTATR_FIELD + || pHt->Which() == RES_TXTATR_ANNOTATION + || pHt->Which() == RES_TXTATR_INPUTFIELD) + && (nFieldIndex-- == 0)) + { + pTextField = const_cast( + static_txtattr_cast(pHt)); + break; + } + else if (pHt->Which() == RES_TXTATR_REFMARK + && (nFieldIndex-- == 0)) + { + strTypeName = "set reference"; + } + } + } + if (pTextField) + { + const SwField* pField = pTextField->GetFormatField().GetField(); + if (pField) + { + strTypeName = SwFieldType::GetTypeStr(pField->GetTypeId()); + const SwFieldIds nWhich = pField->GetTyp()->Which(); + OUString sEntry; + sal_uInt32 subType = 0; + switch (nWhich) + { + case SwFieldIds::DocStat: + subType = static_cast(pField)->GetSubType(); + break; + case SwFieldIds::GetRef: + { + switch( pField->GetSubType() ) + { + case REF_BOOKMARK: + { + const SwGetRefField* pRefField = dynamic_cast(pField); + if ( pRefField && pRefField->IsRefToHeadingCrossRefBookmark() ) + sEntry = "Headings"; + else if ( pRefField && pRefField->IsRefToNumItemCrossRefBookmark() ) + sEntry = "Numbered Paragraphs"; + else + sEntry = "Bookmarks"; + } + break; + case REF_FOOTNOTE: + sEntry = "Footnotes"; + break; + case REF_ENDNOTE: + sEntry = "Endnotes"; + break; + case REF_SETREFATTR: + sEntry = "Insert Reference"; + break; + case REF_SEQUENCEFLD: + sEntry = static_cast(pField)->GetSetRefName(); + break; + } + //Get format string + strTypeName = sEntry; + // GetFormat() >= 0> is always true as GetFormat()> is unsigned +// if (pField->GetFormat() >= 0) + { + sEntry = aMgr.GetFormatStr( pField->GetTypeId(), pField->GetFormat() ); + if (sEntry.getLength() > 0) + { + strTypeName += "-" + sEntry; + } + } + } + break; + case SwFieldIds::DateTime: + subType = static_cast(pField)->GetSubType(); + break; + case SwFieldIds::JumpEdit: + { + const sal_uInt32 nFormat= pField->GetFormat(); + const sal_uInt16 nSize = aMgr.GetFormatCount(pField->GetTypeId(), false); + if (nFormat < nSize) + { + sEntry = aMgr.GetFormatStr(pField->GetTypeId(), nFormat); + if (sEntry.getLength() > 0) + { + strTypeName += "-" + sEntry; + } + } + } + break; + case SwFieldIds::ExtUser: + subType = static_cast(pField)->GetSubType(); + break; + case SwFieldIds::HiddenText: + case SwFieldIds::SetExp: + { + sEntry = pField->GetTyp()->GetName(); + if (sEntry.getLength() > 0) + { + strTypeName += "-" + sEntry; + } + } + break; + case SwFieldIds::DocInfo: + subType = pField->GetSubType(); + subType &= 0x00ff; + break; + case SwFieldIds::RefPageSet: + { + const SwRefPageSetField* pRPld = static_cast(pField); + bool bOn = pRPld->IsOn(); + strTypeName += "-"; + if (bOn) + strTypeName += "on"; + else + strTypeName += "off"; + } + break; + case SwFieldIds::Author: + { + strTypeName += "-" + aMgr.GetFormatStr(pField->GetTypeId(), pField->GetFormat() & 0xff); + } + break; + default: break; + } + if (subType > 0 || nWhich == SwFieldIds::DocInfo || nWhich == SwFieldIds::ExtUser || nWhich == SwFieldIds::DocStat) + { + std::vector aLst; + aMgr.GetSubTypes(pField->GetTypeId(), aLst); + if (subType < aLst.size()) + sEntry = aLst[subType]; + if (sEntry.getLength() > 0) + { + if (nWhich == SwFieldIds::DocInfo) + { + strTypeName = sEntry; + sal_uInt16 nSize = aMgr.GetFormatCount(pField->GetTypeId(), false); + const sal_uInt16 nExSub = pField->GetSubType() & 0xff00; + if (nSize > 0 && nExSub > 0) + { + //Get extra subtype string + strTypeName += "-"; + sEntry = aMgr.GetFormatStr(pField->GetTypeId(), nExSub/0x0100-1); + strTypeName += sEntry; + } + } + else + { + strTypeName += "-" + sEntry; + } + } + } + } + } + return strTypeName; +} + +// #i63870# - re-implement method on behalf of methods +// <_getDefaultAttributesImpl(..)> and <_getRunAttributesImpl(..)> +uno::Sequence SwAccessibleParagraph::getCharacterAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString >& aRequestedAttributes ) +{ + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString& rText = GetString(); + + if (!IsValidPosition(nIndex, rText.getLength())) + throw lang::IndexOutOfBoundsException(); + + bool bSupplementalMode = false; + uno::Sequence< OUString > aNames = aRequestedAttributes; + if (!aNames.hasElements()) + { + bSupplementalMode = true; + aNames = getAttributeNames(); + } + // retrieve default character attributes + tAccParaPropValMap aDefAttrSeq; + _getDefaultAttributesImpl( aNames, aDefAttrSeq, true ); + + // retrieved run character attributes + tAccParaPropValMap aRunAttrSeq; + _getRunAttributesImpl( nIndex, aNames, aRunAttrSeq ); + + // merge default and run attributes + std::vector< PropertyValue > aValues( aDefAttrSeq.size() ); + sal_Int32 i = 0; + for ( const auto& rDefEntry : aDefAttrSeq ) + { + tAccParaPropValMap::const_iterator aRunIter = + aRunAttrSeq.find( rDefEntry.first ); + if ( aRunIter != aRunAttrSeq.end() ) + { + aValues[i] = aRunIter->second; + } + else + { + aValues[i] = rDefEntry.second; + } + ++i; + } + if( bSupplementalMode ) + { + uno::Sequence< OUString > aSupplementalNames = aRequestedAttributes; + if (!aSupplementalNames.hasElements()) + aSupplementalNames = getSupplementalAttributeNames(); + + tAccParaPropValMap aSupplementalAttrSeq; + _getSupplementalAttributesImpl( aSupplementalNames, aSupplementalAttrSeq ); + + aValues.resize( aValues.size() + aSupplementalAttrSeq.size() ); + + for ( const auto& rSupplementalEntry : aSupplementalAttrSeq ) + { + aValues[i] = rSupplementalEntry.second; + ++i; + } + + _correctValues( nIndex, aValues ); + + aValues.emplace_back(); + + OUString strTypeName = GetFieldTypeNameAtIndex(nIndex); + if (!strTypeName.isEmpty()) + { + aValues.emplace_back(); + PropertyValue& rValueFT = aValues.back(); + rValueFT.Name = "FieldType"; + rValueFT.Value <<= strTypeName.toAsciiLowerCase(); + rValueFT.Handle = -1; + rValueFT.State = PropertyState_DIRECT_VALUE; + } + + //sort property values + // build sorted index array + sal_Int32 nLength = aValues.size(); + std::vector aIndices; + aIndices.reserve(nLength); + for (i = 0; i < nLength; ++i) + aIndices.push_back(i); + std::sort(aIndices.begin(), aIndices.end(), IndexCompare(aValues.data())); + // create sorted sequences according to index array + uno::Sequence aNewValues( nLength ); + PropertyValue* pNewValues = aNewValues.getArray(); + for (i = 0; i < nLength; ++i) + { + pNewValues[i] = aValues[aIndices[i]]; + } + return aNewValues; + } + + return comphelper::containerToSequence(aValues); +} + +static void SetPutRecursive(SfxItemSet &targetSet, const SfxItemSet &sourceSet) +{ + const SfxItemSet *const pParentSet = sourceSet.GetParent(); + if (pParentSet) + SetPutRecursive(targetSet, *pParentSet); + targetSet.Put(sourceSet); +} + +// #i63870# +void SwAccessibleParagraph::_getDefaultAttributesImpl( + const uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rDefAttrSeq, + const bool bOnlyCharAttrs ) +{ + // retrieve default attributes + SwTextFrame const*const pFrame(static_cast(GetFrame())); + const SwTextNode *const pTextNode(pFrame->GetTextNodeForParaProps()); + std::unique_ptr pSet; + if ( !bOnlyCharAttrs ) + { + pSet.reset( new SfxItemSet( const_cast(pTextNode->GetDoc()->GetAttrPool()), + svl::Items{} ) ); + } + else + { + pSet.reset( new SfxItemSet( const_cast(pTextNode->GetDoc()->GetAttrPool()), + svl::Items{} ) ); + } + // #i82637# - From the perspective of the a11y API the default character + // attributes are the character attributes, which are set at the paragraph style + // of the paragraph. The character attributes set at the automatic paragraph + // style of the paragraph are treated as run attributes. + // pTextNode->SwContentNode::GetAttr( *pSet ); + // get default paragraph attributes, if needed, and merge these into + if ( !bOnlyCharAttrs ) + { + SfxItemSet aParaSet( const_cast(pTextNode->GetDoc()->GetAttrPool()), + svl::Items{} ); + pTextNode->SwContentNode::GetAttr( aParaSet ); + pSet->Put( aParaSet ); + } + // get default character attributes and merge these into + OSL_ENSURE( pTextNode->GetTextColl(), + " - missing paragraph style. Serious defect!" ); + if ( pTextNode->GetTextColl() ) + { + SfxItemSet aCharSet( const_cast(pTextNode->GetDoc()->GetAttrPool()), + svl::Items{} ); + SetPutRecursive( aCharSet, pTextNode->GetTextColl()->GetAttrSet() ); + pSet->Put( aCharSet ); + } + + // build-up sequence containing the run attributes + tAccParaPropValMap aDefAttrSeq; + { + const SfxItemPropertyMap& rPropMap = + aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_CURSOR )->getPropertyMap(); + PropertyEntryVector_t aPropertyEntries = rPropMap.getPropertyEntries(); + for ( const auto& rProp : aPropertyEntries ) + { + const SfxPoolItem* pItem = pSet->GetItem( rProp.nWID ); + if ( pItem ) + { + uno::Any aVal; + pItem->QueryValue( aVal, rProp.nMemberId ); + + PropertyValue rPropVal; + rPropVal.Name = rProp.sName; + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + + aDefAttrSeq[rPropVal.Name] = rPropVal; + } + } + + // #i72800# + // add property value entry for the paragraph style + if ( !bOnlyCharAttrs && pTextNode->GetTextColl() ) + { + if ( aDefAttrSeq.find( UNO_NAME_PARA_STYLE_NAME ) == aDefAttrSeq.end() ) + { + PropertyValue rPropVal; + rPropVal.Name = UNO_NAME_PARA_STYLE_NAME; + uno::Any aVal( uno::makeAny( pTextNode->GetTextColl()->GetName() ) ); + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + + aDefAttrSeq[rPropVal.Name] = rPropVal; + } + } + + // #i73371# + // resolve value text::WritingMode2::PAGE of property value entry WritingMode + if ( !bOnlyCharAttrs && GetFrame() ) + { + tAccParaPropValMap::iterator aIter = aDefAttrSeq.find( UNO_NAME_WRITING_MODE ); + if ( aIter != aDefAttrSeq.end() ) + { + PropertyValue rPropVal( aIter->second ); + sal_Int16 nVal = rPropVal.Value.get(); + if ( nVal == text::WritingMode2::PAGE ) + { + const SwFrame* pUpperFrame( GetFrame()->GetUpper() ); + while ( pUpperFrame ) + { + if ( pUpperFrame->GetType() & + ( SwFrameType::Page | SwFrameType::Fly | SwFrameType::Section | SwFrameType::Tab | SwFrameType::Cell ) ) + { + if ( pUpperFrame->IsVertical() ) + { + nVal = text::WritingMode2::TB_RL; + } + else if ( pUpperFrame->IsRightToLeft() ) + { + nVal = text::WritingMode2::RL_TB; + } + else + { + nVal = text::WritingMode2::LR_TB; + } + rPropVal.Value <<= nVal; + aDefAttrSeq[rPropVal.Name] = rPropVal; + break; + } + + if ( const SwFlyFrame* pFlyFrame = dynamic_cast(pUpperFrame) ) + { + pUpperFrame = pFlyFrame->GetAnchorFrame(); + } + else + { + pUpperFrame = pUpperFrame->GetUpper(); + } + } + } + } + } + } + + if ( !aRequestedAttributes.hasElements() ) + { + rDefAttrSeq = aDefAttrSeq; + } + else + { + for( const OUString& rReqAttr : aRequestedAttributes ) + { + tAccParaPropValMap::const_iterator const aIter = aDefAttrSeq.find( rReqAttr ); + if ( aIter != aDefAttrSeq.end() ) + { + rDefAttrSeq[ aIter->first ] = aIter->second; + } + } + } +} + +uno::Sequence< PropertyValue > SwAccessibleParagraph::getDefaultAttributes( + const uno::Sequence< OUString >& aRequestedAttributes ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + tAccParaPropValMap aDefAttrSeq; + _getDefaultAttributesImpl( aRequestedAttributes, aDefAttrSeq ); + + // #i92233# + static const char sMMToPixelRatio[] = "MMToPixelRatio"; + bool bProvideMMToPixelRatio( !aRequestedAttributes.hasElements() || + (comphelper::findValue(aRequestedAttributes, sMMToPixelRatio) != -1) ); + + uno::Sequence< PropertyValue > aValues( aDefAttrSeq.size() + + ( bProvideMMToPixelRatio ? 1 : 0 ) ); + std::transform(aDefAttrSeq.begin(), aDefAttrSeq.end(), aValues.begin(), + [](const auto& rEntry) -> PropertyValue { return rEntry.second; }); + + // #i92233# + if ( bProvideMMToPixelRatio ) + { + PropertyValue rPropVal; + rPropVal.Name = sMMToPixelRatio; + const Size a100thMMSize( 1000, 1000 ); + const Size aPixelSize = GetMap()->LogicToPixel( a100thMMSize ); + const float fRatio = (static_cast(a100thMMSize.Width())/100)/aPixelSize.Width(); + rPropVal.Value <<= fRatio; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + aValues[ aValues.getLength() - 1 ] = rPropVal; + } + + return aValues; +} + +void SwAccessibleParagraph::_getRunAttributesImpl( + const sal_Int32 nIndex, + const uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rRunAttrSeq ) +{ + // create PaM for character at position + std::unique_ptr pPaM; + const TextFrameIndex nCorePos(GetPortionData().GetCoreViewPosition(nIndex)); + SwTextFrame const*const pFrame(static_cast(GetFrame())); + SwPosition const aModelPos(pFrame->MapViewToModelPos(nCorePos)); + SwTextNode *const pTextNode(aModelPos.nNode.GetNode().GetTextNode()); + { + SwPosition const aEndPos(*pTextNode, + aModelPos.nContent.GetIndex() == pTextNode->Len() + ? pTextNode->Len() // ??? + : aModelPos.nContent.GetIndex() + 1); + pPaM.reset(new SwPaM(aModelPos, aEndPos)); + } + + // retrieve character attributes for the created PaM + SfxItemSet aSet( pPaM->GetDoc()->GetAttrPool(), + svl::Items{} ); + // #i82637# + // From the perspective of the a11y API the character attributes, which + // are set at the automatic paragraph style of the paragraph, are treated + // as run attributes. + // SwXTextCursor::GetCursorAttr( *pPaM, aSet, sal_True, sal_True ); + // get character attributes from automatic paragraph style and merge these into + { + if ( pTextNode->HasSwAttrSet() ) + { + SfxItemSet aAutomaticParaStyleCharAttrs( pPaM->GetDoc()->GetAttrPool(), + svl::Items{} ); + aAutomaticParaStyleCharAttrs.Put( *(pTextNode->GetpSwAttrSet()), false ); + aSet.Put( aAutomaticParaStyleCharAttrs ); + } + } + // get character attributes at and merge these into + { + SfxItemSet aCharAttrsAtPaM( pPaM->GetDoc()->GetAttrPool(), + svl::Items{} ); + SwUnoCursorHelper::GetCursorAttr(*pPaM, aCharAttrsAtPaM, true); + aSet.Put( aCharAttrsAtPaM ); + } + + // build-up sequence containing the run attributes + { + tAccParaPropValMap aRunAttrSeq; + { + tAccParaPropValMap aDefAttrSeq; + uno::Sequence< OUString > aDummy; + _getDefaultAttributesImpl( aDummy, aDefAttrSeq, true ); // #i82637# + + const SfxItemPropertyMap& rPropMap = + aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_CURSOR )->getPropertyMap(); + PropertyEntryVector_t aPropertyEntries = rPropMap.getPropertyEntries(); + for ( const auto& rProp : aPropertyEntries ) + { + const SfxPoolItem* pItem( nullptr ); + // #i82637# - Found character attributes, whose value equals the value of + // the corresponding default character attributes, are excluded. + if ( aSet.GetItemState( rProp.nWID, true, &pItem ) == SfxItemState::SET ) + { + uno::Any aVal; + pItem->QueryValue( aVal, rProp.nMemberId ); + + PropertyValue rPropVal; + rPropVal.Name = rProp.sName; + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = PropertyState_DIRECT_VALUE; + + tAccParaPropValMap::const_iterator aDefIter = + aDefAttrSeq.find( rPropVal.Name ); + if ( aDefIter == aDefAttrSeq.end() || + rPropVal.Value != aDefIter->second.Value ) + { + aRunAttrSeq[rPropVal.Name] = rPropVal; + } + } + } + } + + if ( !aRequestedAttributes.hasElements() ) + { + rRunAttrSeq = aRunAttrSeq; + } + else + { + for( const OUString& rReqAttr : aRequestedAttributes ) + { + tAccParaPropValMap::iterator aIter = aRunAttrSeq.find( rReqAttr ); + if ( aIter != aRunAttrSeq.end() ) + { + rRunAttrSeq[ (*aIter).first ] = (*aIter).second; + } + } + } + } +} + +uno::Sequence< PropertyValue > SwAccessibleParagraph::getRunAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString >& aRequestedAttributes ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + { + const OUString& rText = GetString(); + if (!IsValidPosition(nIndex, rText.getLength())) + { + throw lang::IndexOutOfBoundsException(); + } + } + + tAccParaPropValMap aRunAttrSeq; + _getRunAttributesImpl( nIndex, aRequestedAttributes, aRunAttrSeq ); + + return comphelper::mapValuesToSequence( aRunAttrSeq ); +} + +void SwAccessibleParagraph::_getSupplementalAttributesImpl( + const uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rSupplementalAttrSeq ) +{ + SwTextFrame const*const pFrame(static_cast(GetFrame())); + const SwTextNode *const pTextNode(pFrame->GetTextNodeForParaProps()); + std::unique_ptr pSet; + pSet.reset( + new SfxItemSet( + const_cast(pTextNode->GetDoc()->GetAttrPool()), + svl::Items< + RES_PARATR_LINESPACING, RES_PARATR_ADJUST, + RES_PARATR_TABSTOP, RES_PARATR_TABSTOP, + RES_PARATR_NUMRULE, RES_PARATR_NUMRULE, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END - 1, + RES_LR_SPACE, RES_UL_SPACE>{})); + + if ( pTextNode->HasBullet() || pTextNode->HasNumber() ) + { + pSet->Put( pTextNode->GetAttr(RES_PARATR_LIST_LEVEL) ); + } + pSet->Put( pTextNode->SwContentNode::GetAttr(RES_UL_SPACE) ); + pSet->Put( pTextNode->SwContentNode::GetAttr(RES_LR_SPACE) ); + pSet->Put( pTextNode->SwContentNode::GetAttr(RES_PARATR_ADJUST) ); + + tAccParaPropValMap aSupplementalAttrSeq; + { + const SfxItemPropertyMapEntry* pPropMap( + aSwMapProvider.GetPropertyMapEntries( PROPERTY_MAP_ACCESSIBILITY_TEXT_ATTRIBUTE ) ); + while ( !pPropMap->aName.isEmpty() ) + { + const SfxPoolItem* pItem = pSet->GetItem( pPropMap->nWID ); + if ( pItem ) + { + uno::Any aVal; + pItem->QueryValue( aVal, pPropMap->nMemberId ); + + PropertyValue rPropVal; + rPropVal.Name = pPropMap->aName; + rPropVal.Value = aVal; + rPropVal.Handle = -1; + rPropVal.State = beans::PropertyState_DEFAULT_VALUE; + + aSupplementalAttrSeq[rPropVal.Name] = rPropVal; + } + + ++pPropMap; + } + } + + for( const OUString& rSupplementalAttr : aRequestedAttributes ) + { + tAccParaPropValMap::const_iterator const aIter = aSupplementalAttrSeq.find( rSupplementalAttr ); + if ( aIter != aSupplementalAttrSeq.end() ) + { + rSupplementalAttrSeq[ aIter->first ] = aIter->second; + } + } +} + +void SwAccessibleParagraph::_correctValues( const sal_Int32 nIndex, + std::vector< PropertyValue >& rValues) +{ + PropertyValue ChangeAttr, ChangeAttrColor; + + const SwRangeRedline* pRedline = GetRedlineAtIndex(); + if ( pRedline ) + { + + const SwModuleOptions *pOpt = SW_MOD()->GetModuleConfig(); + AuthorCharAttr aChangeAttr; + if ( pOpt ) + { + switch( pRedline->GetType()) + { + case RedlineType::Insert: + aChangeAttr = pOpt->GetInsertAuthorAttr(); + break; + case RedlineType::Delete: + aChangeAttr = pOpt->GetDeletedAuthorAttr(); + break; + case RedlineType::Format: + aChangeAttr = pOpt->GetFormatAuthorAttr(); + break; + default: break; + } + } + switch( aChangeAttr.m_nItemId ) + { + case SID_ATTR_CHAR_WEIGHT: + ChangeAttr.Name = UNO_NAME_CHAR_WEIGHT; + ChangeAttr.Value <<= awt::FontWeight::BOLD; + break; + case SID_ATTR_CHAR_POSTURE: + ChangeAttr.Name = UNO_NAME_CHAR_POSTURE; + ChangeAttr.Value <<= awt::FontSlant_ITALIC; //char posture + break; + case SID_ATTR_CHAR_STRIKEOUT: + ChangeAttr.Name = UNO_NAME_CHAR_STRIKEOUT; + ChangeAttr.Value <<= awt::FontStrikeout::SINGLE; //char strikeout + break; + case SID_ATTR_CHAR_UNDERLINE: + ChangeAttr.Name = UNO_NAME_CHAR_UNDERLINE; + ChangeAttr.Value <<= aChangeAttr.m_nAttr; //underline line + break; + } + if( aChangeAttr.m_nColor != COL_NONE_COLOR ) + { + if( aChangeAttr.m_nItemId == SID_ATTR_BRUSH ) + { + ChangeAttrColor.Name = UNO_NAME_CHAR_BACK_COLOR; + if( aChangeAttr.m_nColor == COL_TRANSPARENT )//char backcolor + ChangeAttrColor.Value <<= COL_BLUE; + else + ChangeAttrColor.Value <<= aChangeAttr.m_nColor; + } + else + { + ChangeAttrColor.Name = UNO_NAME_CHAR_COLOR; + if( aChangeAttr.m_nColor == COL_TRANSPARENT )//char color + ChangeAttrColor.Value <<= COL_BLUE; + else + ChangeAttrColor.Value <<= aChangeAttr.m_nColor; + } + } + } + + // sw_redlinehide: this function only needs SwWrongList for 1 character, + // and the end is excluded by InWrongWord(), + // so it ought to work to just pick the wrong-list/node that contains + // the character following the given nIndex + SwTextFrame const*const pFrame(static_cast(GetFrame())); + TextFrameIndex const nCorePos(GetPortionData().GetCoreViewPosition(nIndex)); + std::pair pos(pFrame->MapViewToModel(nCorePos)); + if (pos.first->Len() == pos.second + && nCorePos != TextFrameIndex(pFrame->GetText().getLength())) + { + pos = pFrame->MapViewToModel(nCorePos + TextFrameIndex(1)); // try this one instead + assert(pos.first->Len() != pos.second); + } + const SwTextNode *const pTextNode(pos.first); + + sal_Int32 nValues = rValues.size(); + for (sal_Int32 i = 0; i < nValues; ++i) + { + PropertyValue& rValue = rValues[i]; + + if (rValue.Name == ChangeAttr.Name ) + { + rValue.Value = ChangeAttr.Value; + continue; + } + + if (rValue.Name == ChangeAttrColor.Name ) + { + rValue.Value = ChangeAttrColor.Value; + continue; + } + + //back color + if (rValue.Name == UNO_NAME_CHAR_BACK_COLOR) + { + uno::Any &anyChar = rValue.Value; + sal_uInt32 crBack = static_cast( reinterpret_cast(anyChar.pReserved)); + if (COL_AUTO == Color(crBack)) + { + uno::Reference xComponent(this); + if (xComponent.is()) + { + crBack = static_cast(xComponent->getBackground()); + } + rValue.Value <<= crBack; + } + continue; + } + + //char color + if (rValue.Name == UNO_NAME_CHAR_COLOR) + { + if( GetPortionData().IsInGrayPortion( nIndex ) ) + rValue.Value <<= SwViewOption::GetFieldShadingsColor(); + uno::Any &anyChar = rValue.Value; + sal_uInt32 crChar = static_cast( reinterpret_cast(anyChar.pReserved)); + + if( COL_AUTO == Color(crChar) ) + { + uno::Reference xComponent(this); + if (xComponent.is()) + { + Color cr(xComponent->getBackground()); + crChar = sal_uInt32(cr.IsDark() ? COL_WHITE : COL_BLACK); + rValue.Value <<= crChar; + } + } + continue; + } + + // UnderLine + if (rValue.Name == UNO_NAME_CHAR_UNDERLINE) + { + //misspelled word + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr && pCursorShell->GetViewOptions() && pCursorShell->GetViewOptions()->IsOnlineSpell()) + { + const SwWrongList* pWrongList = pTextNode->GetWrong(); + if( nullptr != pWrongList ) + { + sal_Int32 nBegin = pos.second; + sal_Int32 nLen = 1; + if (pWrongList->InWrongWord(nBegin, nLen) && !pTextNode->IsSymbolAt(nBegin)) + { + rValue.Value <<= sal_uInt16(LINESTYLE_WAVE); + } + } + } + continue; + } + + // UnderLineColor + if (rValue.Name == UNO_NAME_CHAR_UNDERLINE_COLOR) + { + //misspelled word + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr && pCursorShell->GetViewOptions() && pCursorShell->GetViewOptions()->IsOnlineSpell()) + { + const SwWrongList* pWrongList = pTextNode->GetWrong(); + if( nullptr != pWrongList ) + { + sal_Int32 nBegin = pos.second; + sal_Int32 nLen = 1; + if (pWrongList->InWrongWord(nBegin, nLen) && !pTextNode->IsSymbolAt(nBegin)) + { + rValue.Value <<= sal_Int32(0x00ff0000); + continue; + } + } + } + + uno::Any &anyChar = rValue.Value; + sal_uInt32 crUnderline = static_cast( reinterpret_cast(anyChar.pReserved)); + if ( COL_AUTO == Color(crUnderline) ) + { + uno::Reference xComponent(this); + if (xComponent.is()) + { + Color cr(xComponent->getBackground()); + crUnderline = sal_uInt32(cr.IsDark() ? COL_WHITE : COL_BLACK); + rValue.Value <<= crUnderline; + } + } + + continue; + } + + //tab stop + if (rValue.Name == UNO_NAME_TABSTOPS) + { + css::uno::Sequence< css::style::TabStop > tabs = GetCurrentTabStop( nIndex ); + if( !tabs.hasElements() ) + { + tabs.realloc(1); + css::style::TabStop ts; + css::awt::Rectangle rc0 = getCharacterBounds(0); + css::awt::Rectangle rc1 = getCharacterBounds(nIndex); + if( rc1.X - rc0.X >= 48 ) + ts.Position = (rc1.X - rc0.X) - (rc1.X - rc0.X - 48)% 47 + 47; + else + ts.Position = 48; + ts.DecimalChar = ' '; + ts.FillChar = ' '; + ts.Alignment = css::style::TabAlign_LEFT; + tabs[0] = ts; + } + rValue.Value <<= tabs; + continue; + } + + //footnote & endnote + if (rValue.Name == UNO_NAME_CHAR_ESCAPEMENT) + { + if ( GetPortionData().IsIndexInFootnode(nIndex) ) + { + rValue.Value <<= sal_Int32(101); + } + continue; + } + } +} + +awt::Rectangle SwAccessibleParagraph::getCharacterBounds( + sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // #i12332# The position after the string needs special treatment. + // IsValidChar -> IsValidPosition + if( ! (IsValidPosition( nIndex, GetString().getLength() ) ) ) + throw lang::IndexOutOfBoundsException(); + + // #i12332# + bool bBehindText = false; + if ( nIndex == GetString().getLength() ) + bBehindText = true; + + // get model position & prepare GetCharRect() arguments + SwCursorMoveState aMoveState; + aMoveState.m_bRealHeight = true; + aMoveState.m_bRealWidth = true; + SwSpecialPos aSpecialPos; + SwTextFrame const*const pFrame(static_cast(GetFrame())); + + /** #i12332# FillSpecialPos does not accept nIndex == + GetString().getLength(). In that case nPos is set to the + length of the string in the core. This way GetCharRect + returns the rectangle for a cursor at the end of the + paragraph. */ + const TextFrameIndex nPos = bBehindText + ? TextFrameIndex(pFrame->GetText().getLength()) + : GetPortionData().FillSpecialPos(nIndex, aSpecialPos, aMoveState.m_pSpecialPos ); + + // call GetCharRect + SwRect aCoreRect; + SwPosition aPosition(pFrame->MapViewToModelPos(nPos)); + GetFrame()->GetCharRect( aCoreRect, aPosition, &aMoveState ); + + // translate core coordinates into accessibility coordinates + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aCoreRect.SVRect() )); + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds.SVRect() ).TopLeft() ); + aScreenRect.Move( -aFramePixPos.getX(), -aFramePixPos.getY() ); + + // convert into AWT Rectangle + return awt::Rectangle( + aScreenRect.Left(), aScreenRect.Top(), + aScreenRect.GetWidth(), aScreenRect.GetHeight() ); +} + +sal_Int32 SwAccessibleParagraph::getCharacterCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetString().getLength(); +} + +sal_Int32 SwAccessibleParagraph::getIndexAtPoint( const awt::Point& rPoint ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // construct Point (translate into layout coordinates) + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + Point aPoint( rPoint.X, rPoint.Y ); + SwRect aLogBounds( GetBounds( *(GetMap()), GetFrame() ) ); // twip rel to doc root + Point aPixPos( GetMap()->CoreToPixel( aLogBounds.SVRect() ).TopLeft() ); + aPoint.setX(aPoint.getX() + aPixPos.getX()); + aPoint.setY(aPoint.getY() + aPixPos.getY()); + Point aCorePoint( GetMap()->PixelToCore( aPoint ) ); + if( !aLogBounds.IsInside( aCorePoint ) ) + { + // #i12332# rPoint is may also be in rectangle returned by + // getCharacterBounds(getCharacterCount() + + awt::Rectangle aRectEndPos = + getCharacterBounds(getCharacterCount()); + + if (rPoint.X - aRectEndPos.X >= 0 && + rPoint.X - aRectEndPos.X < aRectEndPos.Width && + rPoint.Y - aRectEndPos.Y >= 0 && + rPoint.Y - aRectEndPos.Y < aRectEndPos.Height) + return getCharacterCount(); + + return -1; + } + + // ask core for position + OSL_ENSURE( GetFrame() != nullptr, "The text frame has vanished!" ); + OSL_ENSURE( GetFrame()->IsTextFrame(), "The text frame has mutated!" ); + const SwTextFrame* pFrame = static_cast( GetFrame() ); + // construct SwPosition (where GetModelPositionForViewPoint() will put the result into) + SwTextNode* pNode = const_cast(pFrame->GetTextNodeFirst()); + SwPosition aPos(*pNode, 0); + SwCursorMoveState aMoveState; + aMoveState.m_bPosMatchesBounds = true; + const bool bSuccess = pFrame->GetModelPositionForViewPoint( &aPos, aCorePoint, &aMoveState ); + + TextFrameIndex nIndex = pFrame->MapModelToViewPos(aPos); + if (TextFrameIndex(0) < nIndex) + { + assert(bSuccess); + SwRect aResultRect; + pFrame->GetCharRect( aResultRect, aPos ); + bool bVert = pFrame->IsVertical(); + bool bR2L = pFrame->IsRightToLeft(); + + if ( (!bVert && aResultRect.Pos().getX() > aCorePoint.getX()) || + ( bVert && aResultRect.Pos().getY() > aCorePoint.getY()) || + ( bR2L && aResultRect.Right() < aCorePoint.getX()) ) + { + SwPosition aPosPrev(pFrame->MapViewToModelPos(nIndex - TextFrameIndex(1))); + SwRect aResultRectPrev; + pFrame->GetCharRect( aResultRectPrev, aPosPrev ); + if ( (!bVert && aResultRectPrev.Pos().getX() < aCorePoint.getX() && aResultRect.Pos().getY() == aResultRectPrev.Pos().getY()) || + ( bVert && aResultRectPrev.Pos().getY() < aCorePoint.getY() && aResultRect.Pos().getX() == aResultRectPrev.Pos().getX()) || + ( bR2L && aResultRectPrev.Right() > aCorePoint.getX() && aResultRect.Pos().getY() == aResultRectPrev.Pos().getY()) ) + { + --nIndex; + } + } + } + + return bSuccess + ? GetPortionData().GetAccessiblePosition(nIndex) + : -1; +} + +OUString SwAccessibleParagraph::getSelectedText() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + bool bSelected = GetSelection( nStart, nEnd ); + return bSelected + ? GetString().copy( nStart, nEnd - nStart ) + : OUString(); +} + +sal_Int32 SwAccessibleParagraph::getSelectionStart() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + GetSelection( nStart, nEnd ); + return nStart; +} + +sal_Int32 SwAccessibleParagraph::getSelectionEnd() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + GetSelection( nStart, nEnd ); + return nEnd; +} + +sal_Bool SwAccessibleParagraph::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidRange( nStartIndex, nEndIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + bool bRet = false; + + // get cursor shell + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + // create pam for selection + SwTextFrame const*const pFrame(static_cast(GetFrame())); + TextFrameIndex const nStart(GetPortionData().GetCoreViewPosition(nStartIndex)); + TextFrameIndex const nEnd(GetPortionData().GetCoreViewPosition(nEndIndex)); + SwPaM aPaM(pFrame->MapViewToModelPos(nStart)); + aPaM.SetMark(); + *aPaM.GetPoint() = pFrame->MapViewToModelPos(nEnd); + + // set PaM at cursor shell + bRet = Select( aPaM ); + } + + return bRet; +} + +OUString SwAccessibleParagraph::getText() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetString(); +} + +OUString SwAccessibleParagraph::getTextRange( + sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + OUString sText( GetString() ); + + if ( !IsValidRange( nStartIndex, nEndIndex, sText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + OrderRange( nStartIndex, nEndIndex ); + return sText.copy(nStartIndex, nEndIndex-nStartIndex ); +} + +/*accessibility::*/TextSegment SwAccessibleParagraph::getTextAtIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + /*accessibility::*/TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + const OUString rText = GetString(); + // implement the silly specification that first position after + // text must return an empty string, rather than throwing an + // IndexOutOfBoundsException, except for LINE, where the last + // line is returned + if( nIndex == rText.getLength() && AccessibleTextType::LINE != nTextType ) + return aResult; + + // with error checking + i18n::Boundary aBound; + bool bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + + OSL_ENSURE( aBound.startPos >= 0, "illegal boundary" ); + OSL_ENSURE( aBound.startPos <= aBound.endPos, "illegal boundary" ); + + // return word (if present) + if ( bWord ) + { + aResult.SegmentText = rText.copy( aBound.startPos, aBound.endPos - aBound.startPos ); + aResult.SegmentStart = aBound.startPos; + aResult.SegmentEnd = aBound.endPos; + } + + return aResult; +} + +/*accessibility::*/TextSegment SwAccessibleParagraph::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 nTextType ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString rText = GetString(); + + /*accessibility::*/TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + //If nIndex = 0, then nobefore text so return -1 directly. + if( nIndex == 0 ) + return aResult; + //Tab will be return when call WORDTYPE + + // get starting pos + i18n::Boundary aBound; + if (nIndex == rText.getLength()) + aBound.startPos = aBound.endPos = nIndex; + else + { + bool bTmp = GetTextBoundary( aBound, rText, nIndex, nTextType ); + + if ( ! bTmp ) + aBound.startPos = aBound.endPos = nIndex; + } + + // now skip to previous word + if (nTextType==2 || nTextType == 3) + { + i18n::Boundary preBound = aBound; + while(preBound.startPos==aBound.startPos && nIndex > 0) + { + nIndex = min( nIndex, preBound.startPos ) - 1; + if( nIndex < 0 ) break; + GetTextBoundary( preBound, rText, nIndex, nTextType ); + } + //if (nIndex>0) + if (nIndex>=0) + //Tab will be return when call WORDTYPE + { + aResult.SegmentText = rText.copy( preBound.startPos, preBound.endPos - preBound.startPos ); + aResult.SegmentStart = preBound.startPos; + aResult.SegmentEnd = preBound.endPos; + } + } + else + { + bool bWord = false; + while( !bWord ) + { + nIndex = min( nIndex, aBound.startPos ) - 1; + if( nIndex >= 0 ) + { + bWord = GetTextBoundary( aBound, rText, nIndex, nTextType ); + } + else + break; // exit if beginning of string is reached + } + + if (bWord && nIndex= aBound.startPos ) + { + while(nexBound.endPos==aBound.endPos&&nIndex(this)); + + /* Start and end character bounds, in pixels, relative to the paragraph */ + awt::Rectangle startR, endR; + startR = getCharacterBounds(nStartIndex); + endR = getCharacterBounds(nEndIndex); + + /* Adjust points to fit the bounding box of both bounds. */ + Point sP(std::min(startR.X, endR.X), startR.Y); + Point eP(std::max(startR.X + startR.Width, endR.X + endR.Width), endR.Y + endR.Height); + + /* Offset the values relative to the view shell frame */ + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds.SVRect() ).TopLeft() ); + sP += aFramePixPos; + eP += aFramePixPos; + + Point startPoint(GetMap()->PixelToCore(sP)); + Point endPoint(GetMap()->PixelToCore(eP)); + + switch (aScrollType) + { +#ifdef notyet + case AccessibleScrollType_SCROLL_TOP_LEFT: + break; + case AccessibleScrollType_SCROLL_BOTTOM_RIGHT: + break; + case AccessibleScrollType_SCROLL_TOP_EDGE: + break; + case AccessibleScrollType_SCROLL_BOTTOM_EDGE: + break; + case AccessibleScrollType_SCROLL_LEFT_EDGE: + break; + case AccessibleScrollType_SCROLL_RIGHT_EDGE: + break; +#endif + case AccessibleScrollType_SCROLL_ANYWHERE: + break; + default: + return false; + } + + const SwRect aRect(startPoint, endPoint); + SwViewShell* pViewShell = GetMap()->GetShell(); + OSL_ENSURE( pViewShell != nullptr, "View shell expected!" ); + + ScrollMDI(pViewShell, aRect, USHRT_MAX, USHRT_MAX); + + return true; +} + +// XAccessibleEditableText + +sal_Bool SwAccessibleParagraph::cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( !IsEditableState() ) + return false; + + // select and cut (through dispatch mechanism) + setSelection( nStartIndex, nEndIndex ); + ExecuteAtViewShell( SID_CUT ); + return true; +} + +sal_Bool SwAccessibleParagraph::pasteText( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( !IsEditableState() ) + return false; + + // select and paste (through dispatch mechanism) + setSelection( nIndex, nIndex ); + ExecuteAtViewShell( SID_PASTE ); + return true; +} + +sal_Bool SwAccessibleParagraph::deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) +{ + return replaceText( nStartIndex, nEndIndex, OUString() ); +} + +sal_Bool SwAccessibleParagraph::insertText( const OUString& sText, sal_Int32 nIndex ) +{ + return replaceText( nIndex, nIndex, sText ); +} + +sal_Bool SwAccessibleParagraph::replaceText( + sal_Int32 nStartIndex, sal_Int32 nEndIndex, + const OUString& sReplacement ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString& rText = GetString(); + + if( !IsValidRange( nStartIndex, nEndIndex, rText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + if( !IsEditableState() ) + return false; + + // translate positions + TextFrameIndex nStart; + TextFrameIndex nEnd; + bool bSuccess = GetPortionData().GetEditableRange( + nStartIndex, nEndIndex, nStart, nEnd ); + + // edit only if the range is editable + if( bSuccess ) + { + SwTextFrame const*const pFrame(static_cast(GetFrame())); + // create SwPosition for nStartIndex + SwPosition aStartPos(pFrame->MapViewToModelPos(nStart)); + + // create SwPosition for nEndIndex + SwPosition aEndPos(pFrame->MapViewToModelPos(nEnd)); + + // now create XTextRange as helper and set string + const uno::Reference xRange( + SwXTextRange::CreateXTextRange( + const_cast(pFrame->GetDoc()), aStartPos, &aEndPos)); + xRange->setString(sReplacement); + + // delete portion data + ClearPortionData(); + } + + return bSuccess; + +} + +sal_Bool SwAccessibleParagraph::setAttributes( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex, + const uno::Sequence& rAttributeSet ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const OUString& rText = GetString(); + + if( ! IsValidRange( nStartIndex, nEndIndex, rText.getLength() ) ) + throw lang::IndexOutOfBoundsException(); + + if( !IsEditableState() ) + return false; + + // create a (dummy) text portion for the sole purpose of calling + // setPropertyValue on it + uno::Reference xPortion = CreateUnoPortion( nStartIndex, + nEndIndex ); + + // build sorted index array + sal_Int32 nLength = rAttributeSet.getLength(); + const PropertyValue* pPairs = rAttributeSet.getConstArray(); + std::vector aIndices(nLength); + std::iota(aIndices.begin(), aIndices.end(), 0); + std::sort(aIndices.begin(), aIndices.end(), IndexCompare(pPairs)); + + // create sorted sequences according to index array + uno::Sequence< OUString > aNames( nLength ); + OUString* pNames = aNames.getArray(); + uno::Sequence< uno::Any > aValues( nLength ); + uno::Any* pValues = aValues.getArray(); + for (sal_Int32 i = 0; i < nLength; ++i) + { + const PropertyValue& rVal = pPairs[aIndices[i]]; + pNames[i] = rVal.Name; + pValues[i] = rVal.Value; + } + aIndices.clear(); + + // now set the values + bool bRet = true; + try + { + xPortion->setPropertyValues( aNames, aValues ); + } + catch (const UnknownPropertyException&) + { + // error handling through return code! + bRet = false; + } + + return bRet; +} + +sal_Bool SwAccessibleParagraph::setText( const OUString& sText ) +{ + return replaceText(0, GetString().getLength(), sText); +} + +// XAccessibleSelection + +void SwAccessibleParagraph::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + + m_aSelectionHelper.selectAccessibleChild(nChildIndex); +} + +sal_Bool SwAccessibleParagraph::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + + return m_aSelectionHelper.isAccessibleChildSelected(nChildIndex); +} + +void SwAccessibleParagraph::clearAccessibleSelection( ) +{ + ThrowIfDisposed(); +} + +void SwAccessibleParagraph::selectAllAccessibleChildren( ) +{ + ThrowIfDisposed(); + + m_aSelectionHelper.selectAllAccessibleChildren(); +} + +sal_Int32 SwAccessibleParagraph::getSelectedAccessibleChildCount( ) +{ + ThrowIfDisposed(); + + return m_aSelectionHelper.getSelectedAccessibleChildCount(); +} + +uno::Reference SwAccessibleParagraph::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + ThrowIfDisposed(); + + return m_aSelectionHelper.getSelectedAccessibleChild(nSelectedChildIndex); +} + +// index has to be treated as global child index. +void SwAccessibleParagraph::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + + m_aSelectionHelper.deselectAccessibleChild( nChildIndex ); +} + +// XAccessibleHypertext + +namespace { + +class SwHyperlinkIter_Impl +{ + SwTextFrame const& m_rFrame; + sw::MergedAttrIter m_Iter; + TextFrameIndex m_nStt; + TextFrameIndex m_nEnd; + +public: + explicit SwHyperlinkIter_Impl(const SwTextFrame & rTextFrame); + const SwTextAttr *next(SwTextNode const** ppNode = nullptr); + + TextFrameIndex startIdx() const { return m_nStt; } + TextFrameIndex endIdx() const { return m_nEnd; } +}; + +} + +SwHyperlinkIter_Impl::SwHyperlinkIter_Impl(const SwTextFrame & rTextFrame) + : m_rFrame(rTextFrame) + , m_Iter(rTextFrame) + , m_nStt(rTextFrame.GetOffset()) +{ + const SwTextFrame *const pFollFrame = rTextFrame.GetFollow(); + m_nEnd = pFollFrame ? pFollFrame->GetOffset() : TextFrameIndex(rTextFrame.GetText().getLength()); +} + +const SwTextAttr *SwHyperlinkIter_Impl::next(SwTextNode const** ppNode) +{ + const SwTextAttr *pAttr = nullptr; + if (ppNode) + { + *ppNode = nullptr; + } + + SwTextNode const* pNode(nullptr); + while (SwTextAttr const*const pHt = m_Iter.NextAttr(&pNode)) + { + if (RES_TXTATR_INETFMT == pHt->Which()) + { + const TextFrameIndex nHtStt(m_rFrame.MapModelToView(pNode, pHt->GetStart())); + const TextFrameIndex nHtEnd(m_rFrame.MapModelToView(pNode, pHt->GetAnyEnd())); + if (nHtEnd > nHtStt && + ((nHtStt >= m_nStt && nHtStt < m_nEnd) || + (nHtEnd > m_nStt && nHtEnd <= m_nEnd))) + { + pAttr = pHt; + if (ppNode) + { + *ppNode = pNode; + } + break; + } + } + } + + return pAttr; +}; + +sal_Int32 SAL_CALL SwAccessibleParagraph::getHyperLinkCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nCount = 0; + // #i77108# - provide hyperlinks also in editable documents. + + const SwTextFrame *pTextFrame = static_cast( GetFrame() ); + SwHyperlinkIter_Impl aIter(*pTextFrame); + while( aIter.next() ) + nCount++; + + return nCount; +} + +uno::Reference< XAccessibleHyperlink > SAL_CALL + SwAccessibleParagraph::getHyperLink( sal_Int32 nLinkIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + uno::Reference< XAccessibleHyperlink > xRet; + + const SwTextFrame *pTextFrame = static_cast( GetFrame() ); + SwHyperlinkIter_Impl aHIter(*pTextFrame); + SwTextNode const* pNode(nullptr); + SwTextAttr* pHt = const_cast(aHIter.next(&pNode)); + for (sal_Int32 nTIndex = 0; pHt && nTIndex <= nLinkIndex; ++nTIndex) + { + if( nTIndex == nLinkIndex ) + { // found + if (!m_pHyperTextData) + m_pHyperTextData.reset( new SwAccessibleHyperTextData ); + SwAccessibleHyperTextData::iterator aIter = + m_pHyperTextData ->find( pHt ); + if (aIter != m_pHyperTextData->end()) + { + xRet = (*aIter).second; + } + if (!xRet.is()) + { + TextFrameIndex const nHintStart(pTextFrame->MapModelToView(pNode, pHt->GetStart())); + TextFrameIndex const nHintEnd(pTextFrame->MapModelToView(pNode, pHt->GetAnyEnd())); + const sal_Int32 nTmpHStt = GetPortionData().GetAccessiblePosition( + max(aHIter.startIdx(), nHintStart)); + const sal_Int32 nTmpHEnd = GetPortionData().GetAccessiblePosition( + min(aHIter.endIdx(), nHintEnd)); + xRet = new SwAccessibleHyperlink(*pHt, + *this, nTmpHStt, nTmpHEnd ); + if (aIter != m_pHyperTextData->end()) + { + (*aIter).second = xRet; + } + else + { + m_pHyperTextData->emplace( pHt, xRet ); + } + } + break; + } + + // iterate next hyperlink + pHt = const_cast(aHIter.next(&pNode)); + } + if( !xRet.is() ) + throw lang::IndexOutOfBoundsException(); + + return xRet; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getHyperLinkIndex( sal_Int32 nCharIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nCharIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + sal_Int32 nRet = -1; + // #i77108# + { + const SwTextFrame *pTextFrame = static_cast( GetFrame() ); + SwHyperlinkIter_Impl aHIter(*pTextFrame); + + const TextFrameIndex nIdx = GetPortionData().GetCoreViewPosition(nCharIndex); + sal_Int32 nPos = 0; + SwTextNode const* pNode(nullptr); + const SwTextAttr *pHt = aHIter.next(&pNode); + while (pHt && !(nIdx >= pTextFrame->MapModelToView(pNode, pHt->GetStart()) + && nIdx < pTextFrame->MapModelToView(pNode, pHt->GetAnyEnd()))) + { + pHt = aHIter.next(&pNode); + nPos++; + } + + if( pHt ) + nRet = nPos; + } + + if (nRet == -1) + throw lang::IndexOutOfBoundsException(); + return nRet; +} + +// #i71360#, #i108125# - adjustments for change tracking text markup +sal_Int32 SAL_CALL SwAccessibleParagraph::getTextMarkupCount( sal_Int32 nTextMarkupType ) +{ + SolarMutexGuard g; + + std::unique_ptr pTextMarkupHelper; + switch ( nTextMarkupType ) + { + case text::TextMarkupType::TRACK_CHANGE_INSERTION: + case text::TextMarkupType::TRACK_CHANGE_DELETION: + case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pTextMarkupHelper.reset( new SwTextMarkupHelper( + GetPortionData(), + *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) ); + } + break; + default: + { + SwTextFrame const*const pFrame(static_cast(GetFrame())); + pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame)); + } + } + + return pTextMarkupHelper->getTextMarkupCount( nTextMarkupType ); +} + +//MSAA Extension Implementation in app module +sal_Bool SAL_CALL SwAccessibleParagraph::scrollToPosition( const css::awt::Point&, sal_Bool ) +{ + return false; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getSelectedPortionCount( ) +{ + SolarMutexGuard g; + + sal_Int32 nSelected = 0; + SwPaM* pCursor = GetCursor( true ); + if( pCursor != nullptr ) + { + // get SwPosition for my node + SwTextFrame const*const pFrame(static_cast(GetFrame())); + sal_uLong nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + sal_uLong nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + // iterate over ring + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + // ignore, if no mark + if( rTmpCursor.HasMark() ) + { + // check whether frame's node(s) are 'inside' pCursor + SwPosition* pStart = rTmpCursor.Start(); + sal_uLong nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = rTmpCursor.End(); + sal_uLong nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + nSelected++; + } + // else: this PaM doesn't point to this paragraph + } + // else: this PaM is collapsed and doesn't select anything + } + } + return nSelected; + +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getSeletedPositionStart( sal_Int32 nSelectedPortionIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + /*sal_Bool bSelected = */GetSelectionAtIndex(&nSelectedPortionIndex, nStart, nEnd ); + return nStart; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getSeletedPositionEnd( sal_Int32 nSelectedPortionIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nStart, nEnd; + /*sal_Bool bSelected = */GetSelectionAtIndex(&nSelectedPortionIndex, nStart, nEnd ); + return nEnd; +} + +sal_Bool SAL_CALL SwAccessibleParagraph::removeSelection( sal_Int32 selectionIndex ) +{ + SolarMutexGuard g; + + if(selectionIndex < 0) return false; + + sal_Int32 nSelected = selectionIndex; + + // get the selection, and test whether it affects our text node + SwPaM* pCursor = GetCursor( true ); + + if( pCursor != nullptr ) + { + bool bRet = false; + + // get SwPosition for my node + SwTextFrame const*const pFrame(static_cast(GetFrame())); + sal_uLong nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + sal_uLong nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + // iterate over ring + SwPaM* pRingStart = pCursor; + do + { + // ignore, if no mark + if( pCursor->HasMark() ) + { + // check whether frame's node(s) are 'inside' pCursor + SwPosition* pStart = pCursor->Start(); + sal_uLong nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = pCursor->End(); + sal_uLong nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + if( nSelected == 0 ) + { + pCursor->MoveTo(nullptr); + delete pCursor; + bRet = true; + } + else + { + nSelected--; + } + } + } + // else: this PaM is collapsed and doesn't select anything + if(!bRet) + pCursor = pCursor->GetNext(); + } + while( !bRet && (pCursor != pRingStart) ); + } + return true; +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::addSelection( sal_Int32, sal_Int32 startOffset, sal_Int32 endOffset) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking + sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidRange( startOffset, endOffset, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + sal_Int32 nSelectedCount = getSelectedPortionCount(); + for ( sal_Int32 i = nSelectedCount ; i >= 0 ; i--) + { + sal_Int32 nStart, nEnd; + bool bSelected = GetSelectionAtIndex(&i, nStart, nEnd ); + if(bSelected) + { + if(nStart <= nEnd ) + { + if (( startOffset>=nStart && startOffset <=nEnd ) || //startOffset in a selection + ( endOffset>=nStart && endOffset <=nEnd ) || //endOffset in a selection + ( startOffset <= nStart && endOffset >=nEnd) || //start and end include the old selection + ( startOffset >= nStart && endOffset <=nEnd) ) + { + removeSelection(i); + } + + } + else + { + if (( startOffset>=nEnd && startOffset <=nStart ) || //startOffset in a selection + ( endOffset>=nEnd && endOffset <=nStart ) || //endOffset in a selection + ( startOffset <= nStart && endOffset >=nEnd) || //start and end include the old selection + ( startOffset >= nStart && endOffset <=nEnd) ) + + { + removeSelection(i); + } + } + } + + } + + // get cursor shell + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + // create pam for selection + pCursorShell->StartAction(); + SwTextFrame const*const pFrame(static_cast(GetFrame())); + SwPaM* aPaM = pCursorShell->CreateCursor(); + aPaM->SetMark(); + *aPaM->GetPoint() = pFrame->MapViewToModelPos(GetPortionData().GetCoreViewPosition(startOffset)); + *aPaM->GetMark() = pFrame->MapViewToModelPos(GetPortionData().GetCoreViewPosition(endOffset)); + pCursorShell->EndAction(); + } + + return 0; +} + +/*accessibility::*/TextSegment SAL_CALL + SwAccessibleParagraph::getTextMarkup( sal_Int32 nTextMarkupIndex, + sal_Int32 nTextMarkupType ) +{ + SolarMutexGuard g; + + std::unique_ptr pTextMarkupHelper; + switch ( nTextMarkupType ) + { + case text::TextMarkupType::TRACK_CHANGE_INSERTION: + case text::TextMarkupType::TRACK_CHANGE_DELETION: + case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pTextMarkupHelper.reset( new SwTextMarkupHelper( + GetPortionData(), + *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) ); + } + break; + default: + { + SwTextFrame const*const pFrame(static_cast(GetFrame())); + pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame)); + } + } + + return pTextMarkupHelper->getTextMarkup( nTextMarkupIndex, nTextMarkupType ); +} + +uno::Sequence< /*accessibility::*/TextSegment > SAL_CALL + SwAccessibleParagraph::getTextMarkupAtIndex( sal_Int32 nCharIndex, + sal_Int32 nTextMarkupType ) +{ + SolarMutexGuard g; + + // parameter checking + const sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nCharIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + std::unique_ptr pTextMarkupHelper; + switch ( nTextMarkupType ) + { + case text::TextMarkupType::TRACK_CHANGE_INSERTION: + case text::TextMarkupType::TRACK_CHANGE_DELETION: + case text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pTextMarkupHelper.reset( new SwTextMarkupHelper( + GetPortionData(), + *(mpParaChangeTrackInfo->getChangeTrackingTextMarkupList( nTextMarkupType ) )) ); + } + break; + default: + { + SwTextFrame const*const pFrame(static_cast(GetFrame())); + pTextMarkupHelper.reset(new SwTextMarkupHelper(GetPortionData(), *pFrame)); + } + } + + return pTextMarkupHelper->getTextMarkupAtIndex( nCharIndex, nTextMarkupType ); +} + +// #i89175# +sal_Int32 SAL_CALL SwAccessibleParagraph::getLineNumberAtIndex( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + // parameter checking + const sal_Int32 nLength = GetString().getLength(); + if ( ! IsValidPosition( nIndex, nLength ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + const sal_Int32 nLineNo = GetPortionData().GetLineNo( nIndex ); + return nLineNo; +} + +/*accessibility::*/TextSegment SAL_CALL + SwAccessibleParagraph::getTextAtLineNumber( sal_Int32 nLineNo ) +{ + SolarMutexGuard g; + + // parameter checking + if ( nLineNo < 0 || + nLineNo >= GetPortionData().GetLineCount() ) + { + throw lang::IndexOutOfBoundsException(); + } + + i18n::Boundary aLineBound; + GetPortionData().GetBoundaryOfLine( nLineNo, aLineBound ); + + /*accessibility::*/TextSegment aTextAtLine; + const OUString rText = GetString(); + aTextAtLine.SegmentText = rText.copy( aLineBound.startPos, + aLineBound.endPos - aLineBound.startPos ); + aTextAtLine.SegmentStart = aLineBound.startPos; + aTextAtLine.SegmentEnd = aLineBound.endPos; + + return aTextAtLine; +} + +/*accessibility::*/TextSegment SAL_CALL SwAccessibleParagraph::getTextAtLineWithCaret() +{ + SolarMutexGuard g; + + const sal_Int32 nLineNoOfCaret = getNumberOfLineWithCaret(); + + if ( nLineNoOfCaret >= 0 && + nLineNoOfCaret < GetPortionData().GetLineCount() ) + { + return getTextAtLineNumber( nLineNoOfCaret ); + } + + return /*accessibility::*/TextSegment(); +} + +sal_Int32 SAL_CALL SwAccessibleParagraph::getNumberOfLineWithCaret() +{ + SolarMutexGuard g; + + const sal_Int32 nCaretPos = getCaretPosition(); + const sal_Int32 nLength = GetString().getLength(); + if ( !IsValidPosition( nCaretPos, nLength ) ) + { + return -1; + } + + sal_Int32 nLineNo = GetPortionData().GetLineNo( nCaretPos ); + + // special handling for cursor positioned at end of text line via End key + if ( nCaretPos != 0 ) + { + i18n::Boundary aLineBound; + GetPortionData().GetBoundaryOfLine( nLineNo, aLineBound ); + if ( nCaretPos == aLineBound.startPos ) + { + SwCursorShell* pCursorShell = SwAccessibleParagraph::GetCursorShell(); + if ( pCursorShell != nullptr ) + { + const awt::Rectangle aCharRect = getCharacterBounds( nCaretPos ); + + const SwRect& aCursorCoreRect = pCursorShell->GetCharRect(); + // translate core coordinates into accessibility coordinates + vcl::Window *pWin = GetWindow(); + if (!pWin) + { + throw uno::RuntimeException("no Window", static_cast(this)); + } + + tools::Rectangle aScreenRect( GetMap()->CoreToPixel( aCursorCoreRect.SVRect() )); + + SwRect aFrameLogBounds( GetBounds( *(GetMap()) ) ); // twip rel to doc root + Point aFramePixPos( GetMap()->CoreToPixel( aFrameLogBounds.SVRect() ).TopLeft() ); + aScreenRect.Move( -aFramePixPos.getX(), -aFramePixPos.getY() ); + + // convert into AWT Rectangle + const awt::Rectangle aCursorRect( aScreenRect.Left(), + aScreenRect.Top(), + aScreenRect.GetWidth(), + aScreenRect.GetHeight() ); + + if ( aCharRect.X != aCursorRect.X || + aCharRect.Y != aCursorRect.Y ) + { + --nLineNo; + } + } + } + } + + return nLineNo; +} + +// #i108125# +void SwAccessibleParagraph::Notify(SfxBroadcaster&, const SfxHint&) +{ + mpParaChangeTrackInfo->reset(); +} + +bool SwAccessibleParagraph::GetSelectionAtIndex( + sal_Int32 * pSelection, sal_Int32& nStart, sal_Int32& nEnd) +{ + if (pSelection && *pSelection < 0) return false; + + bool bRet = false; + nStart = -1; + nEnd = -1; + + // get the selection, and test whether it affects our text node + SwPaM* pCursor = GetCursor( true ); + if( pCursor != nullptr ) + { + // get SwPosition for my node + SwTextFrame const*const pFrame(static_cast(GetFrame())); + sal_uLong nFirstNode(pFrame->GetTextNodeFirst()->GetIndex()); + sal_uLong nLastNode; + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + nLastNode = pMerged->pLastNode->GetIndex(); + } + else + { + nLastNode = nFirstNode; + } + + // iterate over ring + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + // ignore, if no mark + if( rTmpCursor.HasMark() ) + { + // check whether frame's node(s) are 'inside' pCursor + SwPosition* pStart = rTmpCursor.Start(); + sal_uLong nStartIndex = pStart->nNode.GetIndex(); + SwPosition* pEnd = rTmpCursor.End(); + sal_uLong nEndIndex = pEnd->nNode.GetIndex(); + if ((nStartIndex <= nLastNode) && (nFirstNode <= nEndIndex)) + { + if (!pSelection || *pSelection == 0) + { + // translate start and end positions + + // start position + sal_Int32 nLocalStart = -1; + if (nStartIndex < nFirstNode) + { + // selection starts in previous node: + // then our local selection starts with the paragraph + nLocalStart = 0; + } + else + { + assert(FrameContainsNode(*pFrame, nStartIndex)); + + // selection starts in this node: + // then check whether it's before or inside our part of + // the paragraph, and if so, get the proper position + const TextFrameIndex nCoreStart = + pFrame->MapModelToViewPos(*pStart); + if( nCoreStart < + GetPortionData().GetFirstValidCorePosition() ) + { + nLocalStart = 0; + } + else if( nCoreStart <= + GetPortionData().GetLastValidCorePosition() ) + { + SAL_WARN_IF( + !GetPortionData().IsValidCorePosition( + nCoreStart), + "sw.a11y", + "problem determining valid core position"); + + nLocalStart = + GetPortionData().GetAccessiblePosition( + nCoreStart ); + } + } + + // end position + sal_Int32 nLocalEnd = -1; + if (nLastNode < nEndIndex) + { + // selection ends in following node: + // then our local selection extends to the end + nLocalEnd = GetPortionData().GetAccessibleString(). + getLength(); + } + else + { + assert(FrameContainsNode(*pFrame, nEndIndex)); + + // selection ends in this node: then select everything + // before our part of the node + const TextFrameIndex nCoreEnd = + pFrame->MapModelToViewPos(*pEnd); + if( nCoreEnd > + GetPortionData().GetLastValidCorePosition() ) + { + // selection extends beyond out part of this para + nLocalEnd = GetPortionData().GetAccessibleString(). + getLength(); + } + else if( nCoreEnd >= + GetPortionData().GetFirstValidCorePosition() ) + { + // selection is inside our part of this para + SAL_WARN_IF( + !GetPortionData().IsValidCorePosition( + nCoreEnd), + "sw.a11y", + "problem determining valid core position"); + + nLocalEnd = GetPortionData().GetAccessiblePosition( + nCoreEnd ); + } + } + + if( ( nLocalStart != -1 ) && ( nLocalEnd != -1 ) ) + { + nStart = nLocalStart; + nEnd = nLocalEnd; + bRet = true; + } + } // if hit the index + else + { + --*pSelection; + } + } + // else: this PaM doesn't point to this paragraph + } + // else: this PaM is collapsed and doesn't select anything + if(bRet) + break; + } + } + // else: nocursor -> no selection + + if (pSelection && bRet) + { + sal_Int32 nCaretPos = GetCaretPos(); + if( nStart == nCaretPos ) + { + sal_Int32 tmp = nStart; + nStart = nEnd; + nEnd = tmp; + } + } + return bRet; +} + +sal_Int16 SAL_CALL SwAccessibleParagraph::getAccessibleRole() +{ + SolarMutexGuard g; + + //Get the real heading level, Heading1 ~ Heading10 + if (m_nHeadingLevel > 0) + { + return AccessibleRole::HEADING; + } + else + { + return AccessibleRole::PARAGRAPH; + } +} + +//Get the real heading level, Heading1 ~ Heading10 +sal_Int32 SwAccessibleParagraph::GetRealHeadingLevel() +{ + uno::Reference< css::beans::XPropertySet > xPortion = CreateUnoPortion( 0, 0 ); + uno::Any styleAny = xPortion->getPropertyValue( "ParaStyleName" ); + OUString sValue; + if (styleAny >>= sValue) + { + sal_Int32 length = sValue.getLength(); + if (length == 9 || length == 10) + { + OUString headStr = sValue.copy(0, 7); + if (headStr == "Heading") + { + OUString intStr = sValue.copy(8); + sal_Int32 headingLevel = intStr.toInt32(); + return headingLevel; + } + } + } + return -1; +} + +uno::Any SAL_CALL SwAccessibleParagraph::getExtendedAttributes() +{ + SolarMutexGuard g; + + uno::Any Ret; + OUString strHeading("heading-level:"); + if( m_nHeadingLevel >= 0 ) + strHeading += OUString::number(m_nHeadingLevel); + // tdf#84102: expose the same attribute with the name "level" + strHeading += ";level:"; + if( m_nHeadingLevel >= 0 ) + strHeading += OUString::number(m_nHeadingLevel); + strHeading += ";"; + + Ret <<= strHeading; + + return Ret; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpara.hxx b/sw/source/core/access/accpara.hxx new file mode 100644 index 000000000..ee5288cd2 --- /dev/null +++ b/sw/source/core/access/accpara.hxx @@ -0,0 +1,399 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPARA_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPARA_HXX + +#include "acccontext.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "accselectionhelper.hxx" +#include +#include + +class SwTextFrame; +class SwPaM; +class SwAccessiblePortionData; +class SwAccessibleHyperTextData; +class SwRangeRedline; +class SwXTextPortion; +class SwParaChangeTrackingInfo; //#i108125# + +namespace com::sun::star { + namespace i18n { struct Boundary; } + namespace accessibility { class XAccessibleHyperlink; } + namespace style { struct TabStop; } +} + +typedef std::unordered_map< OUString, + css::beans::PropertyValue > tAccParaPropValMap; + +class SwAccessibleParagraph : + public SfxListener, + public SwAccessibleContext, + public css::accessibility::XAccessibleEditableText, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleHypertext, + public css::accessibility::XAccessibleTextMarkup, + public css::accessibility::XAccessibleMultiLineText, + public css::accessibility::XAccessibleTextAttributes, + public css::accessibility::XAccessibleTextSelection, + public css::accessibility::XAccessibleExtendedAttributes +{ + friend class SwAccessibleHyperlink; + + OUString m_sDesc; // protected by base classes mutex + + // data for this paragraph's text portions; this contains the + // mapping from the core 'model string' to the accessible text + // string. + // pPortionData may be NULL; it should only be accessed through the + // Get/Clear/Has/UpdatePortionData() methods + std::unique_ptr m_pPortionData; + std::unique_ptr m_pHyperTextData; + + sal_Int32 m_nOldCaretPos; // The 'old' caret pos. It's only valid as long + // as the cursor is inside this object (protected by + // mutex) + + bool m_bIsHeading; // protected by base classes mutex + sal_Int32 m_nHeadingLevel; + + // implementation for XAccessibleSelection + SwAccessibleSelectionHelper m_aSelectionHelper; + + std::unique_ptr mpParaChangeTrackInfo; // #i108125# + + // XAccessibleComponent + bool m_bLastHasSelection; + + /// get the (accessible) text string (requires frame; check before) + OUString const & GetString(); + + static OUString GetDescription(); + + // get the current care position + sal_Int32 GetCaretPos(); + + // determine the current selection. Fill the values with + // -1 if there is no selection in the this paragraph + // @param pSelection (optional) check only Nth selection in ring + bool GetSelectionAtIndex(sal_Int32 * pSelection, sal_Int32& nStart, sal_Int32& nEnd); + bool GetSelection(sal_Int32& nStart, sal_Int32& nEnd) { + return GetSelectionAtIndex(nullptr, nStart, nEnd); + } + + // helper for GetSelection and getCaretPosition + // #i27301# - add parameter <_bForSelection>, which indicates, + // if the cursor is retrieved for selection or for caret position. + SwPaM* GetCursor( const bool _bForSelection ); + + // for cut/copy/paste: execute a particular slot at the view shell + void ExecuteAtViewShell( sal_uInt16 nSlot ); + + // helper method for get/setAttributes + // (for the special case of (nEndIndex==-1) a single character will + // be selected) + SwXTextPortion* CreateUnoPortion( sal_Int32 nStart, sal_Int32 nEnd ); + + // methods for checking the parameter range: + + // does nPos point to a char? + static bool IsValidChar(sal_Int32 nPos, sal_Int32 nLength); + + // does nPos point to a position? (may be behind the last character) + static bool IsValidPosition(sal_Int32 nPos, sal_Int32 nLength); + + // is nBegin...nEnd a valid range? (nEnd points past the last character) + static bool IsValidRange(sal_Int32 nBegin, sal_Int32 nEnd, sal_Int32 nLength); + + // Ensure ordered range (i.e. nBegin is smaller then nEnd) + static void OrderRange(sal_Int32& nBegin, sal_Int32& nEnd) + { + if( nBegin > nEnd ) + { + sal_Int32 nTmp = nBegin; nBegin = nEnd; nEnd = nTmp; + } + } + + const SwRangeRedline* GetRedlineAtIndex(); + OUString GetFieldTypeNameAtIndex(sal_Int32 nIndex); + + // #i63870# + void _getDefaultAttributesImpl( + const css::uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rDefAttrSeq, + const bool bOnlyCharAttrs = false ); + void _getRunAttributesImpl( + const sal_Int32 nIndex, + const css::uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rRunAttrSeq ); + + void _getSupplementalAttributesImpl( + const css::uno::Sequence< OUString >& aRequestedAttributes, + tAccParaPropValMap& rSupplementalAttrSeq ); + + void _correctValues( + const sal_Int32 nIndex, + std::vector< css::beans::PropertyValue >& rValues ); + +public: + bool IsHeading() const; + +protected: + + // Set states for getAccessibleStateSet. + // This derived class additionally sets MULTILINE(1), MULTISELECTABLE(+), + // FOCUSABLE(+) and FOCUSED(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual void InvalidateContent_( bool bVisibleDataFired ) override; + + virtual void InvalidateCursorPos_() override; + virtual void InvalidateFocus_() override; + + virtual ~SwAccessibleParagraph() override; + + // handling of data for the text portions + + // force update of new portion data + /// @throws css::uno::RuntimeException + void UpdatePortionData(); + + // remove the current portion data + void ClearPortionData(); + + // get portion data; update if necessary + /// @throws css::uno::RuntimeException + SwAccessiblePortionData& GetPortionData() + { + if( m_pPortionData == nullptr ) + UpdatePortionData(); + return *m_pPortionData; + } + + //helpers for word boundaries + + bool GetCharBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ); + bool GetWordBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + bool GetSentenceBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + bool GetLineBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + static bool GetParagraphBoundary( css::i18n::Boundary& rBound, + const OUString& rText ); + bool GetAttributeBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ); + bool GetGlyphBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos ); + + // get boundaries of word/sentence/etc. for specified text type + // Does all argument checking, and then delegates to helper methods above. + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + bool GetTextBoundary( css::i18n::Boundary& rBound, + const OUString& rText, + sal_Int32 nPos, + sal_Int16 aTextType ); + + virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override; + +public: + + SwAccessibleParagraph( std::shared_ptr const& pInitMap, + const SwTextFrame& rTextFrame ); + + inline operator css::accessibility::XAccessibleText *(); + + virtual bool HasCursor() override; // required by map to remember that object + + css::uno::Sequence< css::style::TabStop > GetCurrentTabStop( sal_Int32 nIndex ); + virtual sal_Int16 SAL_CALL getAccessibleRole() override; + + // XAccessibleContext + + // Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // Return the parents locale or throw exception if this object has no + // parent yet/anymore. + virtual css::lang::Locale SAL_CALL + getLocale() override; + + // #i27138# - paragraphs are in relation CONTENT_FLOWS_FROM and/or CONTENT_FLOWS_TO + virtual css::uno::Reference< + css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; + + // XAccessibleComponent + + virtual void SAL_CALL grabFocus() override; + // #i71385# + virtual sal_Int32 SAL_CALL getForeground() override; + virtual sal_Int32 SAL_CALL getBackground() override; + + // XServiceInfo + + // Returns an identifier for the implementation of this object. + virtual OUString SAL_CALL + getImplementationName() override; + + // Return whether the specified service is supported by this class. + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + // Returns a list of all supported services. In this case that is just + // the AccessibleContext service. + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XInterface + + // (XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleEditableText). + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) throw () override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) throw () override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleText + virtual sal_Int32 SAL_CALL getCaretPosition() override; + virtual sal_Bool SAL_CALL setCaretPosition( sal_Int32 nIndex ) override; + virtual sal_Unicode SAL_CALL getCharacter( sal_Int32 nIndex ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::awt::Rectangle SAL_CALL getCharacterBounds( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getCharacterCount( ) override; + virtual sal_Int32 SAL_CALL getIndexAtPoint( const css::awt::Point& aPoint ) override; + virtual OUString SAL_CALL getSelectedText( ) override; + virtual sal_Int32 SAL_CALL getSelectionStart() override; + virtual sal_Int32 SAL_CALL getSelectionEnd() override; + virtual sal_Bool SAL_CALL setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual OUString SAL_CALL getText( ) override; + virtual OUString SAL_CALL getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual sal_Bool SAL_CALL copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL scrollSubstringTo( sal_Int32 nStartIndex, sal_Int32 nEndIndex, css::accessibility::AccessibleScrollType aScrollType) override; + + // XAccessibleEditableText + virtual sal_Bool SAL_CALL cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL pasteText( sal_Int32 nIndex ) override; + virtual sal_Bool SAL_CALL deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL insertText( const OUString& sText, sal_Int32 nIndex ) override; + virtual sal_Bool SAL_CALL replaceText( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const OUString& sReplacement ) override; + virtual sal_Bool SAL_CALL setAttributes( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const css::uno::Sequence< css::beans::PropertyValue >& aAttributeSet ) override; + virtual sal_Bool SAL_CALL setText( const OUString& sText ) override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // index has to be treated as global child index. + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nChildIndex ) override; + + // XAccessibleHypertext + virtual sal_Int32 SAL_CALL getHyperLinkCount() override; + virtual css::uno::Reference< + css::accessibility::XAccessibleHyperlink > + SAL_CALL getHyperLink( sal_Int32 nLinkIndex ) override; + virtual sal_Int32 SAL_CALL getHyperLinkIndex( sal_Int32 nCharIndex ) override; + + // #i71360# + // XAccessibleTextMarkup + virtual sal_Int32 SAL_CALL getTextMarkupCount( sal_Int32 nTextMarkupType ) override; + + virtual css::accessibility::TextSegment SAL_CALL + getTextMarkup( sal_Int32 nTextMarkupIndex, + sal_Int32 nTextMarkupType ) override; + + virtual css::uno::Sequence< css::accessibility::TextSegment > SAL_CALL + getTextMarkupAtIndex( sal_Int32 nCharIndex, + sal_Int32 nTextMarkupType ) override; + + // XAccessibleTextSelection + virtual sal_Bool SAL_CALL scrollToPosition( const css::awt::Point& aPoint, sal_Bool isLeftTop ) override; + virtual sal_Int32 SAL_CALL getSelectedPortionCount( ) override; + virtual sal_Int32 SAL_CALL getSeletedPositionStart( sal_Int32 nSelectedPortionIndex ) override; + virtual sal_Int32 SAL_CALL getSeletedPositionEnd( sal_Int32 nSelectedPortionIndex ) override; + virtual sal_Bool SAL_CALL removeSelection( sal_Int32 selectionIndex ) override; + virtual sal_Int32 SAL_CALL addSelection( sal_Int32 selectionIndex, sal_Int32 startOffset, sal_Int32 endOffset) override; + // XAccessibleExtendedAttributes + virtual css::uno::Any SAL_CALL getExtendedAttributes() override ; + sal_Int32 GetRealHeadingLevel(); + + // #i89175# + // XAccessibleMultiLineText + virtual sal_Int32 SAL_CALL getLineNumberAtIndex( sal_Int32 nIndex ) override; + + virtual css::accessibility::TextSegment SAL_CALL + getTextAtLineNumber( sal_Int32 nLineNo ) override; + + virtual css::accessibility::TextSegment SAL_CALL + getTextAtLineWithCaret() override; + + virtual sal_Int32 SAL_CALL getNumberOfLineWithCaret() override; + + // #i63870# + // XAccessibleTextAttributes + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getDefaultAttributes( const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getRunAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; +}; + +inline SwAccessibleParagraph::operator css::accessibility::XAccessibleText *() +{ + return static_cast< css::accessibility::XAccessibleEditableText * >( this ); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accportions.cxx b/sw/source/core/access/accportions.cxx new file mode 100644 index 000000000..b929bc54b --- /dev/null +++ b/sw/source/core/access/accportions.cxx @@ -0,0 +1,761 @@ +/* -*- 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 "accportions.hxx" +#include +#include +#include +#include + +// for portion replacement in Special() +#include + +// for GetWordBoundary(...), GetSentenceBoundary(...): +#include +#include + +// for FillSpecialPos(...) +#include + +using namespace ::com::sun::star; + +using i18n::Boundary; + +// 'portion type' for terminating portions +#define POR_TERMINATE PortionType::NONE + +// portion attributes +#define PORATTR_SPECIAL 1 +#define PORATTR_READONLY 2 +#define PORATTR_GRAY 4 +#define PORATTR_TERM 128 + +/// returns the index of the first position whose value is smaller +/// or equal, and whose following value is equal or larger +template +static size_t FindBreak(const std::vector& rPositions, T nValue); + +/// like FindBreak, but finds the last equal or larger position +template +static size_t FindLastBreak(const std::vector& rPositions, T nValue); + + +SwAccessiblePortionData::SwAccessiblePortionData( + const SwTextFrame *const pTextFrame, + const SwViewOption* pViewOpt ) : + SwPortionHandler(), + m_pTextFrame(pTextFrame), + m_aBuffer(), + m_nViewPosition( 0 ), + m_pViewOptions( pViewOpt ), + m_sAccessibleString(), + m_aLineBreaks(), + m_aAccessiblePositions(), + m_aFieldPosition(), + m_aPortionAttrs(), + m_nBeforePortions( 0 ), + m_bFinished( false ) +{ + OSL_ENSURE( m_pTextFrame != nullptr, "Need SwTextFrame!" ); + + // reserve some space to reduce memory allocations + m_aLineBreaks.reserve( 5 ); + m_ViewPositions.reserve( 10 ); + m_aAccessiblePositions.reserve( 10 ); + + // always include 'first' line-break position + m_aLineBreaks.push_back( 0 ); +} + +SwAccessiblePortionData::~SwAccessiblePortionData() +{ +} + +void SwAccessiblePortionData::Text(TextFrameIndex const nLength, + PortionType nType, sal_Int32 /*nHeight*/, sal_Int32 /*nWidth*/) +{ + OSL_ENSURE((m_nViewPosition + nLength) <= TextFrameIndex(m_pTextFrame->GetText().getLength()), + "portion exceeds model string!" ); + + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + // ignore zero-length portions + if (nLength == TextFrameIndex(0)) + return; + + // store 'old' positions + m_ViewPositions.push_back( m_nViewPosition ); + m_aAccessiblePositions.push_back( m_aBuffer.getLength() ); + + // store portion attributes + sal_uInt8 nAttr = IsGrayPortionType(nType) ? PORATTR_GRAY : 0; + m_aPortionAttrs.push_back( nAttr ); + + // update buffer + nViewPosition + m_aBuffer.append(std::u16string_view(m_pTextFrame->GetText()).substr(sal_Int32(m_nViewPosition), sal_Int32(nLength))); + m_nViewPosition += nLength; +} + +void SwAccessiblePortionData::Special( + TextFrameIndex const nLength, const OUString& rText, PortionType nType, + sal_Int32 /*nHeight*/, sal_Int32 /*nWidth*/, const SwFont* /*pFont*/) +{ + OSL_ENSURE(m_nViewPosition >= TextFrameIndex(0), "illegal position"); + OSL_ENSURE((m_nViewPosition + nLength) <= TextFrameIndex(m_pTextFrame->GetText().getLength()), + "portion exceeds model string!" ); + + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + // construct string with representation; either directly from + // rText, or use resources for special case portions + OUString sDisplay; + switch( nType ) + { + case PortionType::PostIts: + case PortionType::FlyCnt: + sDisplay = OUString(u'\xfffc'); + break; + case PortionType::Field: + case PortionType::Hidden: + case PortionType::Combined: + case PortionType::IsoRef: + // When the field content is empty, input a special character. + if (rText.isEmpty()) + sDisplay = OUString(u'\xfffc'); + else + sDisplay = rText; + m_aFieldPosition.push_back(m_aBuffer.getLength()); + m_aFieldPosition.push_back(m_aBuffer.getLength() + rText.getLength()); + break; + case PortionType::FootnoteNum: + break; + case PortionType::Footnote: + { + sDisplay = rText; + sal_Int32 nStart=m_aBuffer.getLength(); + sal_Int32 nEnd=nStart + rText.getLength(); + m_vecPairPos.emplace_back(nStart,nEnd); + break; + } + break; + case PortionType::Number: + case PortionType::Bullet: + sDisplay = rText + " "; + break; + // There should probably be some special treatment to graphical bullets + case PortionType::GrfNum: + break; + // #i111768# - apply patch from kstribley: + // Include the control characters. + case PortionType::ControlChar: + sDisplay = rText + OUStringChar(m_pTextFrame->GetText()[sal_Int32(m_nViewPosition)]); + break; + case PortionType::Bookmark: + // TODO + break; + default: + sDisplay = rText; + break; + } + + // ignore zero/zero portions (except for terminators) + if ((nLength == TextFrameIndex(0)) && (sDisplay.getLength() == 0) && (nType != POR_TERMINATE)) + return; + + // special treatment for zero length portion at the beginning: + // count as 'before' portion + if ((nLength == TextFrameIndex(0)) && (m_nViewPosition == TextFrameIndex(0))) + m_nBeforePortions++; + + // store the 'old' positions + m_ViewPositions.push_back( m_nViewPosition ); + m_aAccessiblePositions.push_back( m_aBuffer.getLength() ); + + // store portion attributes + sal_uInt8 nAttr = PORATTR_SPECIAL; + if( IsGrayPortionType(nType) ) nAttr |= PORATTR_GRAY; + if (nLength == TextFrameIndex(0)) nAttr |= PORATTR_READONLY; + if( nType == POR_TERMINATE ) nAttr |= PORATTR_TERM; + m_aPortionAttrs.push_back( nAttr ); + + // update buffer + nViewPosition + m_aBuffer.append( sDisplay ); + m_nViewPosition += nLength; +} + +void SwAccessiblePortionData::LineBreak(sal_Int32 /*nWidth*/) +{ + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + m_aLineBreaks.push_back( m_aBuffer.getLength() ); +} + +void SwAccessiblePortionData::Skip(TextFrameIndex const nLength) +{ + OSL_ENSURE( !m_bFinished, "We are already done!" ); + OSL_ENSURE( m_ViewPositions.empty(), "Never Skip() after portions" ); + OSL_ENSURE(nLength <= TextFrameIndex(m_pTextFrame->GetText().getLength()), + "skip exceeds model string!" ); + + m_nViewPosition += nLength; +} + +void SwAccessiblePortionData::Finish() +{ + OSL_ENSURE( !m_bFinished, "We are already done!" ); + + // include terminator values: always include two 'last character' + // markers in the position arrays to make sure we always find one + // position before the end + Special( TextFrameIndex(0), OUString(), POR_TERMINATE ); + Special( TextFrameIndex(0), OUString(), POR_TERMINATE ); + LineBreak(0); + LineBreak(0); + + m_sAccessibleString = m_aBuffer.makeStringAndClear(); + m_bFinished = true; +} + +bool SwAccessiblePortionData::IsPortionAttrSet( + size_t nPortionNo, sal_uInt8 nAttr ) const +{ + OSL_ENSURE( nPortionNo < m_aPortionAttrs.size(), + "Illegal portion number" ); + return (m_aPortionAttrs[nPortionNo] & nAttr) != 0; +} + +bool SwAccessiblePortionData::IsSpecialPortion( size_t nPortionNo ) const +{ + return IsPortionAttrSet(nPortionNo, PORATTR_SPECIAL); +} + +bool SwAccessiblePortionData::IsGrayPortionType( PortionType nType ) const +{ + // gray portions? + // Compare with: inftxt.cxx, SwTextPaintInfo::DrawViewOpt(...) + bool bGray = false; + switch( nType ) + { + case PortionType::Footnote: + case PortionType::IsoRef: + case PortionType::Ref: + case PortionType::QuoVadis: + case PortionType::Number: + case PortionType::Field: + case PortionType::InputField: + case PortionType::IsoTox: + case PortionType::Tox: + case PortionType::Hidden: + bGray = !m_pViewOptions->IsPagePreview() && + !m_pViewOptions->IsReadonly() && SwViewOption::IsFieldShadings(); + break; + case PortionType::Table: bGray = m_pViewOptions->IsTab(); break; + case PortionType::SoftHyphen: bGray = m_pViewOptions->IsSoftHyph(); break; + case PortionType::Blank: bGray = m_pViewOptions->IsHardBlank(); break; + default: + break; // bGray is false + } + return bGray; +} + +const OUString& SwAccessiblePortionData::GetAccessibleString() const +{ + OSL_ENSURE( m_bFinished, "Shouldn't call this before we are done!" ); + + return m_sAccessibleString; +} + +void SwAccessiblePortionData::GetLineBoundary( + Boundary& rBound, + sal_Int32 nPos ) const +{ + FillBoundary( rBound, m_aLineBreaks, + FindBreak( m_aLineBreaks, nPos ) ); +} + +// #i89175# +sal_Int32 SwAccessiblePortionData::GetLineCount() const +{ + size_t nBreaks = m_aLineBreaks.size(); + // A non-empty paragraph has at least 4 breaks: one for each line3 and + // 3 additional ones. + // An empty paragraph has 3 breaks. + // Less than 3 breaks is an error case. + sal_Int32 nLineCount = ( nBreaks > 3 ) + ? nBreaks - 3 + : ( ( nBreaks == 3 ) ? 1 : 0 ); + return nLineCount; +} + +sal_Int32 SwAccessiblePortionData::GetLineNo( const sal_Int32 nPos ) const +{ + sal_Int32 nLineNo = FindBreak( m_aLineBreaks, nPos ); + + // handling of position after last character + const sal_Int32 nLineCount( GetLineCount() ); + if ( nLineNo >= nLineCount ) + { + nLineNo = nLineCount - 1; + } + + return nLineNo; +} + +void SwAccessiblePortionData::GetBoundaryOfLine( const sal_Int32 nLineNo, + i18n::Boundary& rLineBound ) +{ + FillBoundary( rLineBound, m_aLineBreaks, nLineNo ); +} + +void SwAccessiblePortionData::GetLastLineBoundary( + Boundary& rBound ) const +{ + OSL_ENSURE( m_aLineBreaks.size() >= 2, "need min + max value" ); + + // The last two positions except the two delimiters are the ones + // we are looking for, except for empty paragraphs (nBreaks==3) + size_t nBreaks = m_aLineBreaks.size(); + FillBoundary( rBound, m_aLineBreaks, nBreaks <= 3 ? 0 : nBreaks-4 ); +} + +TextFrameIndex SwAccessiblePortionData::GetCoreViewPosition(sal_Int32 const nPos) const +{ + OSL_ENSURE( nPos >= 0, "illegal position" ); + OSL_ENSURE( nPos <= m_sAccessibleString.getLength(), "illegal position" ); + + // find the portion number + size_t nPortionNo = FindBreak( m_aAccessiblePositions, nPos ); + + // get core view portion size + TextFrameIndex nStartPos = m_ViewPositions[nPortionNo]; + + // if it's a non-special portion, move into the portion, else + // return the portion start + if( ! IsSpecialPortion( nPortionNo ) ) + { + // 'wide' portions have to be of the same width + OSL_ENSURE( sal_Int32(m_ViewPositions[nPortionNo+1] - nStartPos) == + ( m_aAccessiblePositions[nPortionNo+1] - + m_aAccessiblePositions[nPortionNo] ), + "accessibility portion disagrees with text model" ); + + nStartPos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]); + } + // else: return nStartPos unmodified + + OSL_ENSURE(nStartPos >= TextFrameIndex(0), "There's something weird in number of characters of SwTextFrame"); + return nStartPos; +} + +void SwAccessiblePortionData::FillBoundary( + Boundary& rBound, + const AccessiblePositions& rPositions, + size_t nPos ) +{ + rBound.startPos = rPositions[nPos]; + rBound.endPos = rPositions[nPos+1]; +} + +template +static size_t FindBreak(const std::vector& rPositions, T const nValue) +{ + OSL_ENSURE( rPositions.size() >= 2, "need min + max value" ); + OSL_ENSURE( rPositions[0] <= nValue, "need min value" ); + OSL_ENSURE( rPositions[rPositions.size()-1] >= nValue, + "need first terminator value" ); + OSL_ENSURE( rPositions[rPositions.size()-2] >= nValue, + "need second terminator value" ); + + size_t nMin = 0; + size_t nMax = rPositions.size()-2; + + // loop until no more than two candidates are left + while( nMin+1 < nMax ) + { + // check loop invariants + OSL_ENSURE( ( (nMin == 0) && (rPositions[nMin] <= nValue) ) || + ( (nMin != 0) && (rPositions[nMin] < nValue) ), + "minvalue not minimal" ); + OSL_ENSURE( nValue <= rPositions[nMax], "max value not maximal" ); + + // get middle (and ensure progress) + size_t nMiddle = (nMin + nMax)/2; + OSL_ENSURE( nMin < nMiddle, "progress?" ); + OSL_ENSURE( nMiddle < nMax, "progress?" ); + + // check array + OSL_ENSURE( rPositions[nMin] <= rPositions[nMiddle], + "garbled positions array" ); + OSL_ENSURE( rPositions[nMiddle] <= rPositions[nMax], + "garbled positions array" ); + + if( nValue > rPositions[nMiddle] ) + nMin = nMiddle; + else + nMax = nMiddle; + } + + // only two are left; we only need to check which one is the winner + OSL_ENSURE( (nMax == nMin) || (nMax == nMin+1), "only two left" ); + if( (rPositions[nMin] < nValue) && (rPositions[nMin+1] <= nValue) ) + nMin = nMin+1; + + // finally, check to see whether the returned value is the 'right' position + OSL_ENSURE( rPositions[nMin] <= nValue, "not smaller or equal" ); + OSL_ENSURE( nValue <= rPositions[nMin+1], "not equal or larger" ); + OSL_ENSURE( (nMin == 0) || (rPositions[nMin-1] <= nValue), + "earlier value should have been returned" ); + + OSL_ENSURE( nMin < rPositions.size()-1, + "shouldn't return last position (due to terminator values)" ); + + return nMin; +} + +template +static size_t FindLastBreak(const std::vector& rPositions, T const nValue) +{ + size_t nResult = FindBreak( rPositions, nValue ); + + // skip 'zero-length' portions + // #i70538# consider size of and ignore last entry + while ( nResult < rPositions.size() - 2 && + rPositions[nResult+1] <= nValue ) + { + nResult++; + } + + return nResult; +} + +void SwAccessiblePortionData::GetSentenceBoundary( + Boundary& rBound, + sal_Int32 nPos ) +{ + OSL_ENSURE( nPos >= 0, "illegal position; check before" ); + OSL_ENSURE( nPos < m_sAccessibleString.getLength(), "illegal position" ); + + if( m_pSentences == nullptr ) + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + m_pSentences.reset(new AccessiblePositions); + m_pSentences->reserve(10); + + // use xBreak->endOfSentence to iterate over all words; store + // positions in pSentences + sal_Int32 nCurrent = 0; + sal_Int32 nLength = m_sAccessibleString.getLength(); + do + { + m_pSentences->push_back( nCurrent ); + + const TextFrameIndex nFramePos = GetCoreViewPosition(nCurrent); + + sal_Int32 nNew = g_pBreakIt->GetBreakIter()->endOfSentence( + m_sAccessibleString, nCurrent, + g_pBreakIt->GetLocale(m_pTextFrame->GetLangOfChar(nFramePos, 0, true))) + 1; + + if( (nNew < 0) && (nNew > nLength) ) + nNew = nLength; + else if (nNew <= nCurrent) + nNew = nCurrent + 1; // ensure forward progress + + nCurrent = nNew; + } + while (nCurrent < nLength); + + // finish with two terminators + m_pSentences->push_back( nLength ); + m_pSentences->push_back( nLength ); + } + + FillBoundary( rBound, *m_pSentences, FindBreak( *m_pSentences, nPos ) ); +} + +void SwAccessiblePortionData::GetAttributeBoundary( + Boundary& rBound, + sal_Int32 nPos) const +{ + OSL_ENSURE( m_pTextFrame != nullptr, "Need SwTextNode!" ); + + // attribute boundaries can only occur on portion boundaries + FillBoundary( rBound, m_aAccessiblePositions, + FindBreak( m_aAccessiblePositions, nPos ) ); +} + +sal_Int32 SwAccessiblePortionData::GetAccessiblePosition(TextFrameIndex const nPos) const +{ + OSL_ENSURE(nPos <= TextFrameIndex(m_pTextFrame->GetText().getLength()), "illegal position"); + + // find the portion number + // #i70538# - consider "empty" model portions - e.g. number portion + size_t nPortionNo = FindLastBreak( m_ViewPositions, nPos ); + + sal_Int32 nRet = m_aAccessiblePositions[nPortionNo]; + + // if the view portion has more than one position, go into it; + // else return that position + TextFrameIndex nStartPos = m_ViewPositions[nPortionNo]; + TextFrameIndex nEndPos = m_ViewPositions[nPortionNo+1]; + if ((nEndPos - nStartPos) > TextFrameIndex(1)) + { + // 'wide' portions have to be of the same width + OSL_ENSURE( sal_Int32(nEndPos - nStartPos) == + ( m_aAccessiblePositions[nPortionNo+1] - + m_aAccessiblePositions[nPortionNo] ), + "accessibility portion disagrees with text model" ); + + TextFrameIndex nWithinPortion = nPos - m_ViewPositions[nPortionNo]; + nRet += sal_Int32(nWithinPortion); + } + // else: return nRet unmodified + + OSL_ENSURE( (nRet >= 0) && (nRet <= m_sAccessibleString.getLength()), + "too long!" ); + return nRet; +} + +TextFrameIndex SwAccessiblePortionData::FillSpecialPos( + sal_Int32 nPos, + SwSpecialPos& rPos, + SwSpecialPos*& rpPos ) const +{ + size_t nPortionNo = FindLastBreak( m_aAccessiblePositions, nPos ); + + SwSPExtendRange nExtend(SwSPExtendRange::NONE); + sal_Int32 nRefPos(0); + TextFrameIndex nCorePos(0); + + if( nPortionNo < m_nBeforePortions ) + { + nExtend = SwSPExtendRange::BEFORE; + rpPos = &rPos; + } + else + { + TextFrameIndex nCoreEndPos = m_ViewPositions[nPortionNo+1]; + nCorePos = m_ViewPositions[nPortionNo]; + + // skip backwards over zero-length portions, since GetCharRect() + // counts all model-zero-length portions as belonging to the + // previous portion + size_t nCorePortionNo = nPortionNo; + while (nCorePos == nCoreEndPos) + { + nCorePortionNo--; + nCoreEndPos = nCorePos; + nCorePos = m_ViewPositions[nCorePortionNo]; + + OSL_ENSURE( nCorePos >= TextFrameIndex(0), "Can't happen." ); + OSL_ENSURE( nCorePortionNo >= m_nBeforePortions, "Can't happen." ); + } + OSL_ENSURE( nCorePos != nCoreEndPos, + "portion with core-representation expected" ); + + // if we have anything except plain text, compute nExtend + nRefPos + if ((nCoreEndPos - nCorePos == TextFrameIndex(1)) && + (m_pTextFrame->GetText()[sal_Int32(nCorePos)] != m_sAccessibleString[nPos])) + { + // case 1: a one-character, non-text portion + // reference position is the first accessibility for our + // core portion + nRefPos = m_aAccessiblePositions[ nCorePortionNo ]; + nExtend = SwSPExtendRange::NONE; + rpPos = &rPos; + } + else if(nPortionNo != nCorePortionNo) + { + // case 2: a multi-character (text!) portion, followed by + // zero-length portions + // reference position is the first character of the next + // portion, and we are 'behind' + nRefPos = m_aAccessiblePositions[ nCorePortionNo+1 ]; + nExtend = SwSPExtendRange::BEHIND; + rpPos = &rPos; + } + else + { + // case 3: regular text portion + OSL_ENSURE( sal_Int32(nCoreEndPos - nCorePos) == + ( m_aAccessiblePositions[nPortionNo+1] - + m_aAccessiblePositions[nPortionNo] ), + "text portion expected" ); + + nCorePos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]); + rpPos = nullptr; + } + } + if( rpPos != nullptr ) + { + OSL_ENSURE( rpPos == &rPos, "Yes!" ); + OSL_ENSURE( nRefPos <= nPos, "wrong reference" ); + + // get the line number, and adjust nRefPos for the line + // (if necessary) + size_t nRefLine = FindBreak( m_aLineBreaks, nRefPos ); + size_t nMyLine = FindBreak( m_aLineBreaks, nPos ); + sal_uInt16 nLineOffset = static_cast( nMyLine - nRefLine ); + if( nLineOffset != 0 ) + nRefPos = m_aLineBreaks[ nMyLine ]; + + // fill char offset and 'special position' + rPos.nCharOfst = nPos - nRefPos; + rPos.nExtendRange = nExtend; + rPos.nLineOfst = nLineOffset; + } + + return nCorePos; +} + +bool SwAccessiblePortionData::FillBoundaryIFDateField( css::i18n::Boundary& rBound, const sal_Int32 nPos ) +{ + if( m_aFieldPosition.size() < 2 ) + return false; + for( size_t i = 0; i < m_aFieldPosition.size() - 1; i += 2 ) + { + if( nPos < m_aFieldPosition[ i + 1 ] && nPos >= m_aFieldPosition[ i ] ) + { + rBound.startPos = m_aFieldPosition[i]; + rBound.endPos = m_aFieldPosition[i + 1]; + return true; + } + } + return false; +} + +void SwAccessiblePortionData::AdjustAndCheck( + sal_Int32 nPos, + size_t& nPortionNo, + TextFrameIndex& rCorePos, + bool& bEdit) const +{ + // find portion and get mode position + nPortionNo = FindBreak( m_aAccessiblePositions, nPos ); + rCorePos = m_ViewPositions[ nPortionNo ]; + + // for special portions, make sure we're on a portion boundary + // for text portions, add the in-portion offset + if( IsSpecialPortion( nPortionNo ) ) + bEdit &= nPos == m_aAccessiblePositions[nPortionNo]; + else + rCorePos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]); +} + +bool SwAccessiblePortionData::GetEditableRange( + sal_Int32 nStart, sal_Int32 nEnd, + TextFrameIndex& rCoreStart, TextFrameIndex& rCoreEnd) const +{ + bool bIsEditable = true; + + // get start and end portions + size_t nStartPortion, nEndPortion; + AdjustAndCheck( nStart, nStartPortion, rCoreStart, bIsEditable ); + AdjustAndCheck( nEnd, nEndPortion, rCoreEnd, bIsEditable ); + + // iterate over portions, and make sure there is no read-only portion + // in-between + size_t nLastPortion = nEndPortion; + + // don't count last portion if we're in front of a special portion + if( IsSpecialPortion(nLastPortion) ) + { + if (nLastPortion > 0) + nLastPortion--; + else + // special case: because size_t is usually unsigned, we can't just + // decrease nLastPortion to -1 (which would normally do the job, so + // this whole if wouldn't be needed). Instead, we'll do this + // special case and just increase the start portion beyond the last + // portion to make sure the loop below will have zero iteration. + nStartPortion = nLastPortion + 1; + } + + for( size_t nPor = nStartPortion; nPor <= nLastPortion; nPor++ ) + { + bIsEditable &= ! IsPortionAttrSet(nPor, PORATTR_READONLY); + } + + return bIsEditable; +} + +bool SwAccessiblePortionData::IsValidCorePosition(TextFrameIndex const nPos) const +{ + // a position is valid if it's within the core view positions that we know + return (m_ViewPositions[0] <= nPos) && (nPos <= m_ViewPositions.back()); +} + +bool SwAccessiblePortionData::IsZeroCorePositionData() +{ + if (m_ViewPositions.empty()) return true; + return m_ViewPositions[0] == TextFrameIndex(0) + && m_ViewPositions.back() == TextFrameIndex(0); +} + +bool SwAccessiblePortionData::IsIndexInFootnode(sal_Int32 nIndex) +{ + for (const auto & pairPos : m_vecPairPos) + { + if(nIndex >= pairPos.first && nIndex < pairPos.second ) + { + return true; + } + } + return false; +} + +bool SwAccessiblePortionData::IsInGrayPortion( sal_Int32 nPos ) +{ +// return IsGrayPortion( FindBreak( aAccessiblePositions, nPos ) ); + return IsPortionAttrSet( FindBreak( m_aAccessiblePositions, nPos ), + PORATTR_GRAY ); +} + +sal_Int32 SwAccessiblePortionData::GetFieldIndex(sal_Int32 nPos) +{ + sal_Int32 nIndex = -1; + if( m_aFieldPosition.size() >= 2 ) + { + for( size_t i = 0; i < m_aFieldPosition.size() - 1; i += 2 ) + { + if( nPos <= m_aFieldPosition[ i + 1 ] && nPos >= m_aFieldPosition[ i ] ) + { + nIndex = i/2; + break; + } + } + } + return nIndex; +} + +TextFrameIndex SwAccessiblePortionData::GetFirstValidCorePosition() const +{ + return m_ViewPositions[0]; +} + +TextFrameIndex SwAccessiblePortionData::GetLastValidCorePosition() const +{ + return m_ViewPositions.back(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accportions.hxx b/sw/source/core/access/accportions.hxx new file mode 100644 index 000000000..246ed8622 --- /dev/null +++ b/sw/source/core/access/accportions.hxx @@ -0,0 +1,171 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPORTIONS_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPORTIONS_HXX + +#include +#include +#include +#include +#include + +class SwTextFrame; +struct SwSpecialPos; +class SwViewOption; +namespace com::sun::star { + namespace i18n { struct Boundary; } +} + +/** + * collect text portion data from the layout through SwPortionHandler interface + */ +class SwAccessiblePortionData : public SwPortionHandler +{ + // the frame this portion is referring to + SwTextFrame const* m_pTextFrame; + + // variables used while collecting the data + OUStringBuffer m_aBuffer; + TextFrameIndex m_nViewPosition; + const SwViewOption* m_pViewOptions; + + /// the accessible string + /// note that the content is different both from the string in the text + /// node(s) as well as the string in the text frame, so there are 3 + /// different index spaces involved. + OUString m_sAccessibleString; + + // positions array + // instances of Position_t must always include the minimum and + // maximum positions as first/last elements (to simplify the + // algorithms) + typedef std::vector AccessiblePositions; + typedef std::vector FramePositions; + + AccessiblePositions m_aLineBreaks; /// position of line breaks + FramePositions m_ViewPositions; /// position of portion breaks in the core view + AccessiblePositions m_aAccessiblePositions; /// portion breaks in m_sAccessibleString + AccessiblePositions m_aFieldPosition; + + std::vector m_aPortionAttrs; /// additional portion attributes + + std::unique_ptr m_pSentences; /// positions of sentence breaks + + size_t m_nBeforePortions; /// # of portions before first core character + bool m_bFinished; + + /// fill the boundary with the values from rPositions[nPos] + static void FillBoundary(css::i18n::Boundary& rBound, + const AccessiblePositions& rPositions, + size_t nPos ); + + /// Access to portion attributes + bool IsPortionAttrSet( size_t nPortionNo, sal_uInt8 nAttr ) const; + bool IsSpecialPortion( size_t nPortionNo ) const; + bool IsGrayPortionType( PortionType nType ) const; + + // helper method for GetEditableRange(...): + void AdjustAndCheck( sal_Int32 nPos, size_t& nPortionNo, + TextFrameIndex& rCorePos, bool& bEdit) const; + +public: + SwAccessiblePortionData( const SwTextFrame* pTextFrame, + const SwViewOption* pViewOpt ); + virtual ~SwAccessiblePortionData() override; + + // SwPortionHandler methods + virtual void Text(TextFrameIndex nLength, PortionType nType, sal_Int32 nHeight = 0, sal_Int32 nWidth = 0) override; + virtual void Special(TextFrameIndex nLength, const OUString& rText, PortionType nType, sal_Int32 nHeight = 0, sal_Int32 nWidth = 0, const SwFont* pFont = nullptr) override; + virtual void LineBreak(sal_Int32 nWidth) override; + virtual void Skip(TextFrameIndex nLength) override; + virtual void Finish() override; + + bool FillBoundaryIFDateField( css::i18n::Boundary& rBound, const sal_Int32 nPos ); + bool IsIndexInFootnode(sal_Int32 nIndex); + bool IsInGrayPortion( sal_Int32 nPos ); + sal_Int32 GetFieldIndex(sal_Int32 nPos); + + bool IsZeroCorePositionData(); + + // access to the portion data + + /// get the text string, as presented by the layout + const OUString& GetAccessibleString() const; + + /// get the start & end positions of the sentence + void GetLineBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ) const; + + // get start and end position of the last line + void GetLastLineBoundary( css::i18n::Boundary& rBound ) const; + + /// Determine whether this core position is valid for these portions. + /// (A paragraph may be split into several frames, e.g. at page + /// boundaries. In this case, only part of a paragraph is represented + /// through this object. This method determines whether one particular + /// position is valid for this object or not.) + bool IsValidCorePosition(TextFrameIndex nPos) const; + TextFrameIndex GetFirstValidCorePosition() const; + TextFrameIndex GetLastValidCorePosition() const; + + /// get the position in the accessibility string for a given view position + sal_Int32 GetAccessiblePosition(TextFrameIndex nPos) const; + + // #i89175# + sal_Int32 GetLineCount() const; + sal_Int32 GetLineNo( const sal_Int32 nPos ) const; + void GetBoundaryOfLine( const sal_Int32 nLineNo, + css::i18n::Boundary& rLineBound ); + + /// get the position in the core view string for a given + /// (accessibility) position + TextFrameIndex GetCoreViewPosition(sal_Int32 nPos) const; + + /// fill a SwSpecialPos structure, suitable for calling + /// SwTextFrame->GetCharRect + /// Returns the core position, and fills rpPos either with NULL or + /// with the &rPos, after putting the appropriate data into it. + TextFrameIndex FillSpecialPos(sal_Int32 nPos, + SwSpecialPos& rPos, + SwSpecialPos*& rpPos ) const; + + // get boundaries of words/sentences. The data structures are + // created on-demand. + void GetSentenceBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ); + + // get (a) boundary for attribute change + void GetAttributeBoundary( css::i18n::Boundary& rBound, + sal_Int32 nPos ) const; + + /// Convert start and end positions into core positions. + /// @returns true if 'special' portions are included either completely + /// or not at all. This can be used to test whether editing + /// that range would be legal + bool GetEditableRange( sal_Int32 nStart, sal_Int32 nEnd, + TextFrameIndex& rCoreStart, TextFrameIndex& rCoreEnd) const; + +private: + std::vector< std::pair > m_vecPairPos; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpreview.cxx b/sw/source/core/access/accpreview.cxx new file mode 100644 index 000000000..3465aae96 --- /dev/null +++ b/sw/source/core/access/accpreview.cxx @@ -0,0 +1,75 @@ +/* -*- 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 "accpreview.hxx" + +const char sImplementationName[] = "com.sun.star.comp.Writer.SwAccessibleDocumentPageView"; + +using ::com::sun::star::uno::Sequence; + +SwAccessiblePreview::SwAccessiblePreview(std::shared_ptr const& pMap) + : SwAccessibleDocumentBase(pMap) +{ + SetName( GetResource( STR_ACCESS_PREVIEW_DOC_NAME ) ); +} + +SwAccessiblePreview::~SwAccessiblePreview() +{ +} + +OUString SwAccessiblePreview::getImplementationName( ) +{ + return sImplementationName; +} + +sal_Bool SwAccessiblePreview::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence SwAccessiblePreview::getSupportedServiceNames( ) +{ + return {"com.sun.star.text.AccessibleTextDocumentPageView", + sAccessibleServiceName}; +} + +Sequence< sal_Int8 > SAL_CALL SwAccessiblePreview::getImplementationId() +{ + return css::uno::Sequence(); +} + +OUString SAL_CALL SwAccessiblePreview::getAccessibleDescription() +{ + return GetResource( STR_ACCESS_PREVIEW_DOC_NAME ); +} + +OUString SAL_CALL SwAccessiblePreview::getAccessibleName() +{ + return SwAccessibleDocumentBase::getAccessibleName() + " " + GetResource( STR_ACCESS_PREVIEW_DOC_SUFFIX ); +} + +void SwAccessiblePreview::InvalidateFocus_() +{ + FireStateChangedEvent( css::accessibility::AccessibleStateType::FOCUSED, true ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accpreview.hxx b/sw/source/core/access/accpreview.hxx new file mode 100644 index 000000000..a60254f6a --- /dev/null +++ b/sw/source/core/access/accpreview.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPREVIEW_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCPREVIEW_HXX + +#include "accdoc.hxx" + +/** + * accessibility implementation for the page preview. + * The children of the page preview are the pages that are visible in the + * preview. + * + * The vast majority of the implementation logic is inherited from + * SwAccessibleDocumentBase. + */ +class SwAccessiblePreview : public SwAccessibleDocumentBase +{ + virtual ~SwAccessiblePreview() override; + +public: + SwAccessiblePreview(std::shared_ptr const& pMap); + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. + */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. + */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + OUString SAL_CALL getAccessibleDescription() override; + OUString SAL_CALL getAccessibleName() override; + virtual void InvalidateFocus_() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accselectionhelper.cxx b/sw/source/core/access/accselectionhelper.cxx new file mode 100644 index 000000000..be92d4a2c --- /dev/null +++ b/sw/source/core/access/accselectionhelper.cxx @@ -0,0 +1,348 @@ +/* -*- 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 "accselectionhelper.hxx" + +#include "acccontext.hxx" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +using ::com::sun::star::accessibility::XAccessible; +using ::com::sun::star::accessibility::XAccessibleContext; +using ::com::sun::star::accessibility::XAccessibleSelection; + +using namespace ::sw::access; + +SwAccessibleSelectionHelper::SwAccessibleSelectionHelper( + SwAccessibleContext& rContext ) : + m_rContext( rContext ) +{ +} + +SwAccessibleSelectionHelper::~SwAccessibleSelectionHelper() +{ +} + +SwFEShell* SwAccessibleSelectionHelper::GetFEShell() +{ + OSL_ENSURE( m_rContext.GetMap() != nullptr, "no map?" ); + SwViewShell* pViewShell = m_rContext.GetMap()->GetShell(); + OSL_ENSURE( pViewShell != nullptr, + "No view shell? Then what are you looking at?" ); + + SwFEShell* pFEShell = dynamic_cast( pViewShell ); + + return pFEShell; +} + +void SwAccessibleSelectionHelper::throwIndexOutOfBoundsException() +{ + Reference < XAccessibleContext > xThis( &m_rContext ); + Reference < XAccessibleSelection >xSelThis( xThis, UNO_QUERY ); + lang::IndexOutOfBoundsException aExcept( + "index out of bounds", + xSelThis ); + throw aExcept; +} + +// XAccessibleSelection +void SwAccessibleSelectionHelper::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + // Get the respective child as SwFrame (also do index checking), ... + const SwAccessibleChild aChild = m_rContext.GetChild( *(m_rContext.GetMap()), + nChildIndex ); + if( !aChild.IsValid() ) + throwIndexOutOfBoundsException(); + + // we can only select fly frames, so we ignore (should: return + // false) all other attempts at child selection + SwFEShell* pFEShell = GetFEShell(); + if( pFEShell != nullptr ) + { + const SdrObject *pObj = aChild.GetDrawObject(); + if( pObj ) + m_rContext.Select( const_cast< SdrObject *>( pObj ), nullptr==aChild.GetSwFrame()); + } + // no frame shell, or no frame, or no fly frame -> can't select +} + +//When the selected state of the SwFrameOrObj is set, return true. +static bool lcl_getSelectedState(const SwAccessibleChild& aChild, + SwAccessibleContext* pContext, + SwAccessibleMap* pMap) +{ + Reference< XAccessible > xAcc; + if ( aChild.GetSwFrame() ) + { + xAcc = pMap->GetContext( aChild.GetSwFrame(), false ); + } + else if ( aChild.GetDrawObject() ) + { + xAcc = pMap->GetContext( aChild.GetDrawObject(), pContext, false ); + } + + if( xAcc.is() ) + { + Reference< XAccessibleContext > pRContext = xAcc->getAccessibleContext(); + if(!pRContext.is()) + return false; + Reference pRStateSet = pRContext->getAccessibleStateSet(); + if( pRStateSet.is() ) + { + Sequence aStates = pRStateSet->getStates(); + if (std::find(aStates.begin(), aStates.end(), AccessibleStateType::SELECTED) != aStates.end()) + return true; + } + } + return false; +} + +bool SwAccessibleSelectionHelper::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + // Get the respective child as SwFrame (also do index checking), ... + const SwAccessibleChild aChild = m_rContext.GetChild( *(m_rContext.GetMap()), + nChildIndex ); + if( !aChild.IsValid() ) + throwIndexOutOfBoundsException(); + + // ... and compare to the currently selected frame + bool bRet = false; + const SwFEShell* pFEShell = GetFEShell(); + if( pFEShell ) + { + if ( aChild.GetSwFrame() != nullptr ) + { + bRet = (pFEShell->GetSelectedFlyFrame() == aChild.GetSwFrame()); + } + else if ( aChild.GetDrawObject() ) + { + bRet = pFEShell->IsObjSelected( *aChild.GetDrawObject() ); + } + //If the SwFrameOrObj is not selected directly in the UI, we should check whether it is selected in the selection cursor. + if( !bRet ) + { + if( lcl_getSelectedState( aChild, &m_rContext, m_rContext.GetMap() ) ) + bRet = true; + } + } + + return bRet; +} + +void SwAccessibleSelectionHelper::selectAllAccessibleChildren( ) +{ + SolarMutexGuard aGuard; + + // We can select only one. So iterate over the children to find + // the first we can select, and select it. + + SwFEShell* pFEShell = GetFEShell(); + if( pFEShell ) + { + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + + for( const SwAccessibleChild& rChild : aChildren ) + { + const SdrObject* pObj = rChild.GetDrawObject(); + const SwFrame* pFrame = rChild.GetSwFrame(); + if( pObj && !(pFrame != nullptr && pFEShell->IsObjSelected()) ) + { + m_rContext.Select( const_cast< SdrObject *>( pObj ), nullptr==pFrame ); + if( pFrame ) + break; + } + } + } +} + +sal_Int32 SwAccessibleSelectionHelper::getSelectedAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nCount = 0; + // Only one frame can be selected at a time, and we only frames + // for selectable children. + const SwFEShell* pFEShell = GetFEShell(); + if( pFEShell != nullptr ) + { + const SwFlyFrame* pFlyFrame = pFEShell->GetSelectedFlyFrame(); + if( pFlyFrame ) + { + nCount = 1; + } + else + { + const size_t nSelObjs = pFEShell->IsObjSelected(); + if( nSelObjs > 0 ) + { + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + + for( const SwAccessibleChild& rChild : aChildren ) + { + if( rChild.GetDrawObject() && !rChild.GetSwFrame() && + SwAccessibleFrame::GetParent(rChild, m_rContext.IsInPagePreview()) + == m_rContext.GetFrame() && + pFEShell->IsObjSelected( *rChild.GetDrawObject() ) ) + { + nCount++; + } + if (o3tl::make_unsigned(nCount) >= nSelObjs) + break; + } + } + } + //If the SwFrameOrObj is not selected directly in the UI, + //we should check whether it is selected in the selection cursor. + if( nCount == 0 ) + { + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + nCount = static_cast(std::count_if(aChildren.begin(), aChildren.end(), + [this](const SwAccessibleChild& aChild) { return lcl_getSelectedState(aChild, &m_rContext, m_rContext.GetMap()); })); + } + } + return nCount; +} + +Reference SwAccessibleSelectionHelper::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + + // Since the index is relative to the selected children, and since + // there can be at most one selected frame child, the index must + // be 0, and a selection must exist, otherwise we have to throw an + // lang::IndexOutOfBoundsException + SwFEShell* pFEShell = GetFEShell(); + if( nullptr == pFEShell ) + throwIndexOutOfBoundsException(); + + SwAccessibleChild aChild; + const SwFlyFrame *pFlyFrame = pFEShell->GetSelectedFlyFrame(); + if( pFlyFrame ) + { + if( 0 == nSelectedChildIndex ) + { + if(SwAccessibleFrame::GetParent( SwAccessibleChild(pFlyFrame), m_rContext.IsInPagePreview()) == m_rContext.GetFrame() ) + { + aChild = pFlyFrame; + } + else + { + const SwFrameFormat *pFrameFormat = pFlyFrame->GetFormat(); + if (pFrameFormat) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if( rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + const SwFrame *pParaFrame = SwAccessibleFrame::GetParent( SwAccessibleChild(pFlyFrame), m_rContext.IsInPagePreview() ); + aChild = pParaFrame; + } + } + } + } + } + else + { + const size_t nSelObjs = pFEShell->IsObjSelected(); + if( 0 == nSelObjs || o3tl::make_unsigned(nSelectedChildIndex) >= nSelObjs ) + throwIndexOutOfBoundsException(); + + std::list< SwAccessibleChild > aChildren; + m_rContext.GetChildren( *(m_rContext.GetMap()), aChildren ); + + for( const SwAccessibleChild& rChild : aChildren ) + { + if( rChild.GetDrawObject() && !rChild.GetSwFrame() && + SwAccessibleFrame::GetParent(rChild, m_rContext.IsInPagePreview()) == + m_rContext.GetFrame() && + pFEShell->IsObjSelected( *rChild.GetDrawObject() ) ) + { + if( 0 == nSelectedChildIndex ) + aChild = rChild; + else + --nSelectedChildIndex; + } + if (aChild.IsValid()) + break; + } + } + + if( !aChild.IsValid() ) + throwIndexOutOfBoundsException(); + + OSL_ENSURE( m_rContext.GetMap() != nullptr, "We need the map." ); + Reference< XAccessible > xChild; + if( aChild.GetSwFrame() ) + { + ::rtl::Reference < SwAccessibleContext > xChildImpl( + m_rContext.GetMap()->GetContextImpl( aChild.GetSwFrame() ) ); + if( xChildImpl.is() ) + { + xChildImpl->SetParent( &m_rContext ); + xChild = xChildImpl.get(); + } + } + else if ( aChild.GetDrawObject() ) + { + ::rtl::Reference < ::accessibility::AccessibleShape > xChildImpl( + m_rContext.GetMap()->GetContextImpl( aChild.GetDrawObject(), + &m_rContext ) ); + if( xChildImpl.is() ) + xChild = xChildImpl.get(); + } + return xChild; +} + +// index has to be treated as global child index. +void SwAccessibleSelectionHelper::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard g; + + if( nChildIndex < 0 || + nChildIndex >= m_rContext.GetChildCount( *(m_rContext.GetMap()) ) ) + throwIndexOutOfBoundsException(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/accselectionhelper.hxx b/sw/source/core/access/accselectionhelper.hxx new file mode 100644 index 000000000..10d78d996 --- /dev/null +++ b/sw/source/core/access/accselectionhelper.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCSELECTIONHELPER_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCSELECTIONHELPER_HXX + +#include +#include + +namespace com::sun::star::accessibility { class XAccessible; } + +class SwAccessibleContext; +class SwFEShell; + +class SwAccessibleSelectionHelper +{ + /// the context on which this helper works + SwAccessibleContext& m_rContext; + + /// get FE-Shell + SwFEShell* GetFEShell(); + + /// @throws css::lang::IndexOutOfBoundsException + void throwIndexOutOfBoundsException(); + +public: + SwAccessibleSelectionHelper( SwAccessibleContext& rContext ); + ~SwAccessibleSelectionHelper(); + + // XAccessibleSelection + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + void selectAccessibleChild( + sal_Int32 nChildIndex ); + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + bool isAccessibleChildSelected( + sal_Int32 nChildIndex ); + /// @throws css::uno::RuntimeException + void selectAllAccessibleChildren( ); + /// @throws css::uno::RuntimeException + sal_Int32 getSelectedAccessibleChildCount( ); + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::accessibility::XAccessible > getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ); + // index has to be treated as global child index. + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::uno::RuntimeException + void deselectAccessibleChild( + sal_Int32 nChildIndex ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctable.cxx b/sw/source/core/access/acctable.cxx new file mode 100644 index 000000000..ccdf75eb8 --- /dev/null +++ b/sw/source/core/access/acctable.cxx @@ -0,0 +1,1739 @@ +/* -*- 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 "accfrmobjslist.hxx" +#include +#include +#include "acctable.hxx" + +#include + +#include +#include +#include + +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::sw::access; + +typedef o3tl::sorted_vector< sal_Int32 > Int32Set_Impl; + +const unsigned int SELECTION_WITH_NUM = 10; + +namespace { + +class SwAccTableSelHander_Impl +{ +public: + virtual void Unselect( sal_Int32 nRowOrCol, sal_Int32 nExt ) = 0; + +protected: + ~SwAccTableSelHander_Impl() {} +}; + +} + +class SwAccessibleTableData_Impl +{ + SwAccessibleMap& mrAccMap; + Int32Set_Impl maRows; + Int32Set_Impl maColumns; + Point maTabFramePos; + const SwTabFrame *mpTabFrame; + bool mbIsInPagePreview; + bool mbOnlyTableColumnHeader; + + void CollectData( const SwFrame *pFrame ); + + bool FindCell( const Point& rPos, const SwFrame *pFrame , + bool bExact, const SwFrame *& rFrame ) const; + + void GetSelection( const Point& rTabPos, const SwRect& rArea, + const SwSelBoxes& rSelBoxes, const SwFrame *pFrame, + SwAccTableSelHander_Impl& rSelHdl, + bool bColumns ) const; + + // #i77106# + bool IncludeRow( const SwFrame& rFrame ) const + { + return !mbOnlyTableColumnHeader || + mpTabFrame->IsInHeadline( rFrame ); + } +public: + // #i77106# - add third optional parameter , default value + SwAccessibleTableData_Impl( SwAccessibleMap& rAccMap, + const SwTabFrame *pTabFrame, + bool bIsInPagePreview, + bool bOnlyTableColumnHeader = false ); + + const Int32Set_Impl& GetRows() const { return maRows; } + const Int32Set_Impl& GetColumns() const { return maColumns; } + + inline Int32Set_Impl::const_iterator GetRowIter( sal_Int32 nRow ) const; + inline Int32Set_Impl::const_iterator GetColumnIter( sal_Int32 nCol ) const; + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + const SwFrame *GetCell( sal_Int32 nRow, sal_Int32 nColumn, SwAccessibleTable *pThis ) const; + const SwFrame *GetCellAtPos( sal_Int32 nLeft, sal_Int32 nTop ) const; + inline sal_Int32 GetRowCount() const; + inline sal_Int32 GetColumnCount() const; + bool CompareExtents( const SwAccessibleTableData_Impl& r ) const; + + void GetSelection( sal_Int32 nStart, sal_Int32 nEnd, + const SwSelBoxes& rSelBoxes, + SwAccTableSelHander_Impl& rSelHdl, + bool bColumns ) const; + + /// @throws lang::IndexOutOfBoundsException + void CheckRowAndCol( sal_Int32 nRow, sal_Int32 nCol, + SwAccessibleTable *pThis ) const; + + const Point& GetTablePos() const { return maTabFramePos; } + void SetTablePos( const Point& rPos ) { maTabFramePos = rPos; } +}; + +void SwAccessibleTableData_Impl::CollectData( const SwFrame *pFrame ) +{ + const SwAccessibleChildSList aList( *pFrame, mrAccMap ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + SwAccessibleChildSList::const_iterator aEndIter( aList.end() ); + while( aIter != aEndIter ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + if( pLower ) + { + if( pLower->IsRowFrame() ) + { + // #i77106# + if ( IncludeRow( *pLower ) ) + { + maRows.insert( pLower->getFrameArea().Top() - maTabFramePos.getY() ); + CollectData( pLower ); + } + } + else if( pLower->IsCellFrame() && + rLower.IsAccessible( mbIsInPagePreview ) ) + { + maColumns.insert( pLower->getFrameArea().Left() - maTabFramePos.getX() ); + } + else + { + CollectData( pLower ); + } + } + ++aIter; + } +} + +bool SwAccessibleTableData_Impl::FindCell( + const Point& rPos, const SwFrame *pFrame, bool bExact, + const SwFrame *& rRet ) const +{ + bool bFound = false; + + const SwAccessibleChildSList aList( *pFrame, mrAccMap ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + SwAccessibleChildSList::const_iterator aEndIter( aList.end() ); + while( !bFound && aIter != aEndIter ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + OSL_ENSURE( pLower, "child should be a frame" ); + if( pLower ) + { + if( rLower.IsAccessible( mbIsInPagePreview ) ) + { + OSL_ENSURE( pLower->IsCellFrame(), "lower is not a cell frame" ); + const SwRect& rFrame = pLower->getFrameArea(); + if( rFrame.Right() >= rPos.X() && rFrame.Bottom() >= rPos.Y() ) + { + // We have found the cell + OSL_ENSURE( rFrame.Left() <= rPos.X() && rFrame.Top() <= rPos.Y(), + "find frame moved to far!" ); + bFound = true; + if( !bExact || + (rFrame.Top() == rPos.Y() && rFrame.Left() == rPos.Y() ) ) + { + rRet = pLower; + } + } + } + else + { + // #i77106# + if ( !pLower->IsRowFrame() || + IncludeRow( *pLower ) ) + { + bFound = FindCell( rPos, pLower, bExact, rRet ); + } + } + } + ++aIter; + } + + return bFound; +} + +void SwAccessibleTableData_Impl::GetSelection( + const Point& rTabPos, + const SwRect& rArea, + const SwSelBoxes& rSelBoxes, + const SwFrame *pFrame, + SwAccTableSelHander_Impl& rSelHdl, + bool bColumns ) const +{ + const SwAccessibleChildSList aList( *pFrame, mrAccMap ); + SwAccessibleChildSList::const_iterator aIter( aList.begin() ); + SwAccessibleChildSList::const_iterator aEndIter( aList.end() ); + while( aIter != aEndIter ) + { + const SwAccessibleChild& rLower = *aIter; + const SwFrame *pLower = rLower.GetSwFrame(); + OSL_ENSURE( pLower, "child should be a frame" ); + const SwRect& rBox = rLower.GetBox( mrAccMap ); + if( pLower && rBox.IsOver( rArea ) ) + { + if( rLower.IsAccessible( mbIsInPagePreview ) ) + { + OSL_ENSURE( pLower->IsCellFrame(), "lower is not a cell frame" ); + const SwCellFrame *pCFrame = + static_cast < const SwCellFrame * >( pLower ); + SwTableBox *pBox = + const_cast< SwTableBox *>( pCFrame->GetTabBox() ); + if( rSelBoxes.find( pBox ) == rSelBoxes.end() ) + { + const Int32Set_Impl rRowsOrCols = + bColumns ? maColumns : maRows; + + sal_Int32 nPos = bColumns ? (rBox.Left() - rTabPos.X()) + : (rBox.Top() - rTabPos.Y()); + Int32Set_Impl::const_iterator aSttRowOrCol( + rRowsOrCols.lower_bound( nPos ) ); + sal_Int32 nRowOrCol = + static_cast< sal_Int32 >( std::distance( + rRowsOrCols.begin(), aSttRowOrCol ) ); + + nPos = bColumns ? (rBox.Right() - rTabPos.X()) + : (rBox.Bottom() - rTabPos.Y()); + Int32Set_Impl::const_iterator aEndRowOrCol( + rRowsOrCols.upper_bound( nPos ) ); + sal_Int32 nExt = + static_cast< sal_Int32 >( std::distance( + aSttRowOrCol, aEndRowOrCol ) ); + + rSelHdl.Unselect( nRowOrCol, nExt ); + } + } + else + { + // #i77106# + if ( !pLower->IsRowFrame() || + IncludeRow( *pLower ) ) + { + GetSelection( rTabPos, rArea, rSelBoxes, pLower, rSelHdl, + bColumns ); + } + } + } + ++aIter; + } +} + +const SwFrame *SwAccessibleTableData_Impl::GetCell( + sal_Int32 nRow, sal_Int32 nColumn, + SwAccessibleTable *pThis ) const +{ + CheckRowAndCol( nRow, nColumn, pThis ); + + Int32Set_Impl::const_iterator aSttCol( GetColumnIter( nColumn ) ); + Int32Set_Impl::const_iterator aSttRow( GetRowIter( nRow ) ); + const SwFrame *pCellFrame = GetCellAtPos( *aSttCol, *aSttRow ); + + return pCellFrame; +} + +void SwAccessibleTableData_Impl::GetSelection( + sal_Int32 nStart, sal_Int32 nEnd, + const SwSelBoxes& rSelBoxes, + SwAccTableSelHander_Impl& rSelHdl, + bool bColumns ) const +{ + SwRect aArea( mpTabFrame->getFrameArea() ); + Point aPos( aArea.Pos() ); + + const Int32Set_Impl& rRowsOrColumns = bColumns ? maColumns : maRows; + if( nStart > 0 ) + { + Int32Set_Impl::const_iterator aStt( rRowsOrColumns.begin() ); + std::advance( aStt, + static_cast< Int32Set_Impl::difference_type >( nStart ) ); + if( bColumns ) + aArea.Left( *aStt + aPos.getX() ); + else + aArea.Top( *aStt + aPos.getY() ); + } + if( nEnd < static_cast< sal_Int32 >( rRowsOrColumns.size() ) ) + { + Int32Set_Impl::const_iterator aEnd( rRowsOrColumns.begin() ); + std::advance( aEnd, + static_cast< Int32Set_Impl::difference_type >( nEnd ) ); + if( bColumns ) + aArea.Right( *aEnd + aPos.getX() - 1 ); + else + aArea.Bottom( *aEnd + aPos.getY() - 1 ); + } + + GetSelection( aPos, aArea, rSelBoxes, mpTabFrame, rSelHdl, bColumns ); +} + +const SwFrame *SwAccessibleTableData_Impl::GetCellAtPos( + sal_Int32 nLeft, sal_Int32 nTop ) const +{ + Point aPos( mpTabFrame->getFrameArea().Pos() ); + aPos.Move( nLeft, nTop ); + const SwFrame *pRet = nullptr; + FindCell( aPos, mpTabFrame, false/*bExact*/, pRet ); + + return pRet; +} + +inline sal_Int32 SwAccessibleTableData_Impl::GetRowCount() const +{ + sal_Int32 count = static_cast< sal_Int32 >( maRows.size() ) ; + count = (count <=0)? 1:count; + return count; +} + +inline sal_Int32 SwAccessibleTableData_Impl::GetColumnCount() const +{ + return static_cast< sal_Int32 >( maColumns.size() ); +} + +bool SwAccessibleTableData_Impl::CompareExtents( + const SwAccessibleTableData_Impl& rCmp ) const +{ + return maRows == rCmp.maRows + && maColumns == rCmp.maColumns; +} + +SwAccessibleTableData_Impl::SwAccessibleTableData_Impl( SwAccessibleMap& rAccMap, + const SwTabFrame *pTabFrame, + bool bIsInPagePreview, + bool bOnlyTableColumnHeader ) + : mrAccMap( rAccMap ) + , maTabFramePos( pTabFrame->getFrameArea().Pos() ) + , mpTabFrame( pTabFrame ) + , mbIsInPagePreview( bIsInPagePreview ) + , mbOnlyTableColumnHeader( bOnlyTableColumnHeader ) +{ + CollectData( mpTabFrame ); +} + +inline Int32Set_Impl::const_iterator SwAccessibleTableData_Impl::GetRowIter( + sal_Int32 nRow ) const +{ + Int32Set_Impl::const_iterator aCol( GetRows().begin() ); + if( nRow > 0 ) + { + std::advance( aCol, + static_cast< Int32Set_Impl::difference_type >( nRow ) ); + } + return aCol; +} + +inline Int32Set_Impl::const_iterator SwAccessibleTableData_Impl::GetColumnIter( + sal_Int32 nColumn ) const +{ + Int32Set_Impl::const_iterator aCol = GetColumns().begin(); + if( nColumn > 0 ) + { + std::advance( aCol, + static_cast< Int32Set_Impl::difference_type >( nColumn ) ); + } + return aCol; +} + +void SwAccessibleTableData_Impl::CheckRowAndCol( + sal_Int32 nRow, sal_Int32 nCol, SwAccessibleTable *pThis ) const +{ + if( ( nRow < 0 || nRow >= static_cast< sal_Int32 >( maRows.size() ) ) || + ( nCol < 0 || nCol >= static_cast< sal_Int32 >( maColumns.size() ) ) ) + { + uno::Reference < XAccessibleTable > xThis( pThis ); + lang::IndexOutOfBoundsException aExcept( + "row or column index out of range", + xThis ); + throw aExcept; + } +} + +namespace { + +class SwAccSingleTableSelHander_Impl : public SwAccTableSelHander_Impl +{ + bool m_bSelected; + +public: + + inline SwAccSingleTableSelHander_Impl(); + + virtual ~SwAccSingleTableSelHander_Impl() {} + + bool IsSelected() const { return m_bSelected; } + + virtual void Unselect( sal_Int32, sal_Int32 ) override; +}; + +} + +inline SwAccSingleTableSelHander_Impl::SwAccSingleTableSelHander_Impl() : + m_bSelected( true ) +{ +} + +void SwAccSingleTableSelHander_Impl::Unselect( sal_Int32, sal_Int32 ) +{ + m_bSelected = false; +} + +namespace { + +class SwAccAllTableSelHander_Impl : public SwAccTableSelHander_Impl + +{ + std::vector< bool > m_aSelected; + sal_Int32 m_nCount; + +public: + explicit SwAccAllTableSelHander_Impl(sal_Int32 nSize) + : m_aSelected(nSize, true) + , m_nCount(nSize) + { + } + + uno::Sequence < sal_Int32 > GetSelSequence(); + + virtual void Unselect( sal_Int32 nRowOrCol, sal_Int32 nExt ) override; + virtual ~SwAccAllTableSelHander_Impl(); +}; + +} + +SwAccAllTableSelHander_Impl::~SwAccAllTableSelHander_Impl() +{ +} + +uno::Sequence < sal_Int32 > SwAccAllTableSelHander_Impl::GetSelSequence() +{ + OSL_ENSURE( m_nCount >= 0, "underflow" ); + uno::Sequence < sal_Int32 > aRet( m_nCount ); + sal_Int32 *pRet = aRet.getArray(); + sal_Int32 nPos = 0; + size_t nSize = m_aSelected.size(); + for( size_t i=0; i < nSize && nPos < m_nCount; i++ ) + { + if( m_aSelected[i] ) + { + *pRet++ = i; + nPos++; + } + } + + OSL_ENSURE( nPos == m_nCount, "count is wrong" ); + + return aRet; +} + +void SwAccAllTableSelHander_Impl::Unselect( sal_Int32 nRowOrCol, + sal_Int32 nExt ) +{ + OSL_ENSURE( o3tl::make_unsigned( nRowOrCol ) < m_aSelected.size(), + "index too large" ); + OSL_ENSURE( o3tl::make_unsigned( nRowOrCol+nExt ) <= m_aSelected.size(), + "extent too large" ); + while( nExt ) + { + if( m_aSelected[static_cast< size_t >( nRowOrCol )] ) + { + m_aSelected[static_cast< size_t >( nRowOrCol )] = false; + m_nCount--; + } + nExt--; + nRowOrCol++; + } +} + +const SwSelBoxes *SwAccessibleTable::GetSelBoxes() const +{ + const SwSelBoxes *pSelBoxes = nullptr; + const SwCursorShell *pCSh = GetCursorShell(); + if( (pCSh != nullptr) && pCSh->IsTableMode() ) + { + pSelBoxes = &pCSh->GetTableCursor()->GetSelectedBoxes(); + } + + return pSelBoxes; +} + +void SwAccessibleTable::FireTableChangeEvent( + const SwAccessibleTableData_Impl& rTableData ) +{ + AccessibleTableModelChange aModelChange; + aModelChange.Type = AccessibleTableModelChangeType::UPDATE; + aModelChange.FirstRow = 0; + aModelChange.LastRow = rTableData.GetRowCount() - 1; + aModelChange.FirstColumn = 0; + aModelChange.LastColumn = rTableData.GetColumnCount() - 1; + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TABLE_MODEL_CHANGED; + aEvent.NewValue <<= aModelChange; + + FireAccessibleEvent( aEvent ); +} + +const SwTableBox* SwAccessibleTable::GetTableBox( sal_Int32 nChildIndex ) const +{ + OSL_ENSURE( nChildIndex >= 0, "Illegal child index." ); + OSL_ENSURE( nChildIndex < const_cast(this)->getAccessibleChildCount(), "Illegal child index." ); // #i77106# + + const SwTableBox* pBox = nullptr; + + // get table box for 'our' table cell + SwAccessibleChild aCell( GetChild( *const_cast(GetMap()), nChildIndex ) ); + if( aCell.GetSwFrame() ) + { + const SwFrame* pChildFrame = aCell.GetSwFrame(); + if( (pChildFrame != nullptr) && pChildFrame->IsCellFrame() ) + { + const SwCellFrame* pCellFrame = + static_cast( pChildFrame ); + pBox = pCellFrame->GetTabBox(); + } + } + + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + return pBox; +} + +bool SwAccessibleTable::IsChildSelected( sal_Int32 nChildIndex ) const +{ + bool bRet = false; + const SwSelBoxes* pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + const SwTableBox* pBox = GetTableBox( nChildIndex ); + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + bRet = pSelBoxes->find( const_cast( pBox ) ) != pSelBoxes->end(); + } + + return bRet; +} + +sal_Int32 SwAccessibleTable::GetIndexOfSelectedChild( + sal_Int32 nSelectedChildIndex ) const +{ + // iterate over all children to n-th isAccessibleChildSelected() + sal_Int32 nChildren = const_cast(this)->getAccessibleChildCount(); // #i77106# + if( nSelectedChildIndex >= nChildren ) + return -1; + + sal_Int32 n = 0; + while( n < nChildren ) + { + if( IsChildSelected( n ) ) + { + if( 0 == nSelectedChildIndex ) + break; + else + --nSelectedChildIndex; + } + ++n; + } + + return n < nChildren ? n : -1; +} + +void SwAccessibleTable::GetStates( + ::utl::AccessibleStateSetHelper& rStateSet ) +{ + SwAccessibleContext::GetStates( rStateSet ); + //Add resizable state to table + rStateSet.AddState( AccessibleStateType::RESIZABLE ); + // MULTISELECTABLE + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell ) + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); +} + +SwAccessibleTable::SwAccessibleTable( + std::shared_ptr const& pInitMap, + const SwTabFrame* pTabFrame ) : + SwAccessibleContext( pInitMap, AccessibleRole::TABLE, pTabFrame ) +{ + const SwFrameFormat* pFrameFormat = pTabFrame->GetFormat(); + if(pFrameFormat) + StartListening(const_cast(pFrameFormat)->GetNotifier()); + + SetName( pFrameFormat->GetName() + "-" + OUString::number( pTabFrame->GetPhyPageNum() ) ); + + const OUString sArg1( static_cast< const SwTabFrame * >( GetFrame() )->GetFormat()->GetName() ); + const OUString sArg2( GetFormattedPageNumber() ); + + m_sDesc = GetResource( STR_ACCESS_TABLE_DESC, &sArg1, &sArg2 ); + UpdateTableData(); +} + +SwAccessibleTable::~SwAccessibleTable() +{ + SolarMutexGuard aGuard; + + mpTableData.reset(); +} + +void SwAccessibleTable::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + EndListeningAll(); + } + else if(auto pLegacyHint = dynamic_cast(&rHint)) + { + sal_uInt16 nWhich = pLegacyHint->m_pOld ? pLegacyHint->m_pOld->Which() : pLegacyHint->m_pNew ? pLegacyHint->m_pNew->Which() : 0; + const SwTabFrame* pTabFrame = static_cast(GetFrame()); + if(nWhich == RES_NAME_CHANGED && pTabFrame) + { + const SwFrameFormat *pFrameFormat = pTabFrame->GetFormat(); + + const OUString sOldName( GetName() ); + const OUString sNewTabName = pFrameFormat->GetName(); + + SetName( sNewTabName + "-" + OUString::number( pTabFrame->GetPhyPageNum() ) ); + + if( sOldName != GetName() ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldName; + aEvent.NewValue <<= GetName(); + FireAccessibleEvent( aEvent ); + } + + const OUString sOldDesc( m_sDesc ); + const OUString sArg2( GetFormattedPageNumber() ); + + m_sDesc = GetResource( STR_ACCESS_TABLE_DESC, &sNewTabName, &sArg2 ); + if( m_sDesc != sOldDesc ) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= m_sDesc; + FireAccessibleEvent( aEvent ); + } + } + } +} + +uno::Any SwAccessibleTable::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet; + if ( rType == cppu::UnoType::get() ) + { + uno::Reference xThis( this ); + aRet <<= xThis; + } + else if ( rType == cppu::UnoType::get() ) + { + uno::Reference xSelection( this ); + aRet <<= xSelection; + } + else if ( rType == cppu::UnoType::get() ) + { + uno::Reference xTableExtent( this ); + aRet <<= xTableExtent; + } + else + { + aRet = SwAccessibleContext::queryInterface(rType); + } + + return aRet; +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SwAccessibleTable::getTypes() +{ + return cppu::OTypeCollection( + cppu::UnoType::get(), + cppu::UnoType::get(), + SwAccessibleContext::getTypes() ).getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleTable::getImplementationId() +{ + return css::uno::Sequence(); +} + +// #i77106# +std::unique_ptr SwAccessibleTable::CreateNewTableData() +{ + const SwTabFrame* pTabFrame = static_cast( GetFrame() ); + return std::unique_ptr(new SwAccessibleTableData_Impl( *GetMap(), pTabFrame, IsInPagePreview() )); +} + +void SwAccessibleTable::UpdateTableData() +{ + // #i77106# - usage of new method + mpTableData = CreateNewTableData(); +} + +void SwAccessibleTable::ClearTableData() +{ + mpTableData.reset(); +} + +OUString SAL_CALL SwAccessibleTable::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return m_sDesc; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleRowCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetTableData().GetRowCount(); +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleColumnCount( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return GetTableData().GetColumnCount(); +} + +OUString SAL_CALL SwAccessibleTable::getAccessibleRowDescription( + sal_Int32 nRow ) +{ + // #i87532# - determine table cell in th row and + // in first column of row header table and return its text content. + OUString sRowDesc; + + GetTableData().CheckRowAndCol(nRow, 0, this); + + uno::Reference< XAccessibleTable > xTableRowHeader = getAccessibleRowHeaders(); + if ( xTableRowHeader.is() ) + { + uno::Reference< XAccessible > xRowHeaderCell = + xTableRowHeader->getAccessibleCellAt( nRow, 0 ); + OSL_ENSURE( xRowHeaderCell.is(), + " - missing row header cell -> serious issue." ); + uno::Reference< XAccessibleContext > xRowHeaderCellContext = + xRowHeaderCell->getAccessibleContext(); + const sal_Int32 nCellChildCount( xRowHeaderCellContext->getAccessibleChildCount() ); + for ( sal_Int32 nChildIndex = 0; nChildIndex < nCellChildCount; ++nChildIndex ) + { + uno::Reference< XAccessible > xChild = xRowHeaderCellContext->getAccessibleChild( nChildIndex ); + uno::Reference< XAccessibleText > xChildText( xChild, uno::UNO_QUERY ); + if ( xChildText.is() ) + { + sRowDesc += xChildText->getText(); + } + } + } + + return sRowDesc; +} + +OUString SAL_CALL SwAccessibleTable::getAccessibleColumnDescription( + sal_Int32 nColumn ) +{ + // #i87532# - determine table cell in first row and + // in th column of column header table and return its text content. + OUString sColumnDesc; + + GetTableData().CheckRowAndCol(0, nColumn, this); + + uno::Reference< XAccessibleTable > xTableColumnHeader = getAccessibleColumnHeaders(); + if ( xTableColumnHeader.is() ) + { + uno::Reference< XAccessible > xColumnHeaderCell = + xTableColumnHeader->getAccessibleCellAt( 0, nColumn ); + OSL_ENSURE( xColumnHeaderCell.is(), + " - missing column header cell -> serious issue." ); + uno::Reference< XAccessibleContext > xColumnHeaderCellContext = + xColumnHeaderCell->getAccessibleContext(); + const sal_Int32 nCellChildCount( xColumnHeaderCellContext->getAccessibleChildCount() ); + for ( sal_Int32 nChildIndex = 0; nChildIndex < nCellChildCount; ++nChildIndex ) + { + uno::Reference< XAccessible > xChild = xColumnHeaderCellContext->getAccessibleChild( nChildIndex ); + uno::Reference< XAccessibleText > xChildText( xChild, uno::UNO_QUERY ); + if ( xChildText.is() ) + { + sColumnDesc += xChildText->getText(); + } + } + } + + return sColumnDesc; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleRowExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_Int32 nExtend = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + UpdateTableData(); + GetTableData().CheckRowAndCol( nRow, nColumn, this ); + + Int32Set_Impl::const_iterator aSttCol( + GetTableData().GetColumnIter( nColumn ) ); + Int32Set_Impl::const_iterator aSttRow( + GetTableData().GetRowIter( nRow ) ); + const SwFrame *pCellFrame = GetTableData().GetCellAtPos( *aSttCol, *aSttRow ); + if( pCellFrame ) + { + sal_Int32 nBottom = pCellFrame->getFrameArea().Bottom(); + nBottom -= GetFrame()->getFrameArea().Top(); + Int32Set_Impl::const_iterator aEndRow( + GetTableData().GetRows().upper_bound( nBottom ) ); + nExtend = + static_cast< sal_Int32 >( std::distance( aSttRow, aEndRow ) ); + } + + return nExtend; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleColumnExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_Int32 nExtend = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + UpdateTableData(); + + GetTableData().CheckRowAndCol( nRow, nColumn, this ); + + Int32Set_Impl::const_iterator aSttCol( + GetTableData().GetColumnIter( nColumn ) ); + Int32Set_Impl::const_iterator aSttRow( + GetTableData().GetRowIter( nRow ) ); + const SwFrame *pCellFrame = GetTableData().GetCellAtPos( *aSttCol, *aSttRow ); + if( pCellFrame ) + { + sal_Int32 nRight = pCellFrame->getFrameArea().Right(); + nRight -= GetFrame()->getFrameArea().Left(); + Int32Set_Impl::const_iterator aEndCol( + GetTableData().GetColumns().upper_bound( nRight ) ); + nExtend = + static_cast< sal_Int32 >( std::distance( aSttCol, aEndCol ) ); + } + + return nExtend; +} + +uno::Reference< XAccessibleTable > SAL_CALL + SwAccessibleTable::getAccessibleRowHeaders( ) +{ + // Row headers aren't supported + return uno::Reference< XAccessibleTable >(); +} + +uno::Reference< XAccessibleTable > SAL_CALL + SwAccessibleTable::getAccessibleColumnHeaders( ) +{ + SolarMutexGuard aGuard; + + // #i87532# - assure that return accessible object is empty, + // if no column header exists. + SwAccessibleTableColHeaders* pTableColHeaders = + new SwAccessibleTableColHeaders(GetMap()->shared_from_this(), + static_cast(GetFrame())); + uno::Reference< XAccessibleTable > xTableColumnHeaders( pTableColHeaders ); + if ( pTableColHeaders->getAccessibleChildCount() <= 0 ) + { + return uno::Reference< XAccessibleTable >(); + } + + return xTableColumnHeaders; +} + +uno::Sequence< sal_Int32 > SAL_CALL SwAccessibleTable::getSelectedAccessibleRows() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + sal_Int32 nRows = GetTableData().GetRowCount(); + SwAccAllTableSelHander_Impl aSelRows( nRows ); + + GetTableData().GetSelection( 0, nRows, *pSelBoxes, aSelRows, + false ); + + return aSelRows.GetSelSequence(); + } + else + { + return uno::Sequence< sal_Int32 >( 0 ); + } +} + +uno::Sequence< sal_Int32 > SAL_CALL SwAccessibleTable::getSelectedAccessibleColumns() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + sal_Int32 nCols = GetTableData().GetColumnCount(); + SwAccAllTableSelHander_Impl aSelCols( nCols ); + + GetTableData().GetSelection( 0, nCols, *pSelBoxes, aSelCols, true ); + + return aSelCols.GetSelSequence(); + } + else + { + return uno::Sequence< sal_Int32 >( 0 ); + } +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleRowSelected( sal_Int32 nRow ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + GetTableData().CheckRowAndCol( nRow, 0, this ); + + bool bRet; + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + SwAccSingleTableSelHander_Impl aSelRow; + GetTableData().GetSelection( nRow, nRow+1, *pSelBoxes, aSelRow, + false ); + bRet = aSelRow.IsSelected(); + } + else + { + bRet = false; + } + + return bRet; +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleColumnSelected( + sal_Int32 nColumn ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + GetTableData().CheckRowAndCol( 0, nColumn, this ); + + bool bRet; + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + SwAccSingleTableSelHander_Impl aSelCol; + + GetTableData().GetSelection( nColumn, nColumn+1, *pSelBoxes, aSelCol, + true ); + bRet = aSelCol.IsSelected(); + } + else + { + bRet = false; + } + + return bRet; +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleTable::getAccessibleCellAt( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + uno::Reference< XAccessible > xRet; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pCellFrame = + GetTableData().GetCell( nRow, nColumn, this ); + if( pCellFrame ) + xRet = GetMap()->GetContext( pCellFrame ); + + return xRet; +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleTable::getAccessibleCaption() +{ + // captions aren't supported + return uno::Reference< XAccessible >(); +} + +uno::Reference< XAccessible > SAL_CALL SwAccessibleTable::getAccessibleSummary() +{ + // summaries aren't supported + return uno::Reference< XAccessible >(); +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleSelected( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + bool bRet = false; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + const SwFrame *pFrame = + GetTableData().GetCell( nRow, nColumn, this ); + if( pFrame && pFrame->IsCellFrame() ) + { + const SwSelBoxes *pSelBoxes = GetSelBoxes(); + if( pSelBoxes ) + { + const SwCellFrame *pCFrame = static_cast < const SwCellFrame * >( pFrame ); + SwTableBox *pBox = + const_cast< SwTableBox *>( pCFrame->GetTabBox() ); + bRet = pSelBoxes->find( pBox ) != pSelBoxes->end(); + } + } + + return bRet; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleIndex( + sal_Int32 nRow, sal_Int32 nColumn ) +{ + sal_Int32 nRet = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwAccessibleChild aCell( GetTableData().GetCell( nRow, nColumn, this )); + if ( aCell.IsValid() ) + { + nRet = GetChildIndex( *(GetMap()), aCell ); + } + + return nRet; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleRow( sal_Int32 nChildIndex ) +{ + sal_Int32 nRet = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // #i77106# + if ( ( nChildIndex < 0 ) || + ( nChildIndex >= getAccessibleChildCount() ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + SwAccessibleChild aCell( GetChild( *(GetMap()), nChildIndex ) ); + if ( aCell.GetSwFrame() ) + { + sal_Int32 nTop = aCell.GetSwFrame()->getFrameArea().Top(); + nTop -= GetFrame()->getFrameArea().Top(); + Int32Set_Impl::const_iterator aRow( + GetTableData().GetRows().lower_bound( nTop ) ); + nRet = static_cast< sal_Int32 >( std::distance( + GetTableData().GetRows().begin(), aRow ) ); + } + else + { + OSL_ENSURE( !aCell.IsValid(), "SwAccessibleTable::getAccessibleColumn:" + "aCell not expected to be valid."); + + throw lang::IndexOutOfBoundsException(); + } + + return nRet; +} + +sal_Int32 SAL_CALL SwAccessibleTable::getAccessibleColumn( + sal_Int32 nChildIndex ) +{ + sal_Int32 nRet = -1; + + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // #i77106# + if ( ( nChildIndex < 0 ) || + ( nChildIndex >= getAccessibleChildCount() ) ) + { + throw lang::IndexOutOfBoundsException(); + } + + SwAccessibleChild aCell( GetChild( *(GetMap()), nChildIndex ) ); + if ( aCell.GetSwFrame() ) + { + sal_Int32 nLeft = aCell.GetSwFrame()->getFrameArea().Left(); + nLeft -= GetFrame()->getFrameArea().Left(); + Int32Set_Impl::const_iterator aCol( + GetTableData().GetColumns().lower_bound( nLeft ) ); + nRet = static_cast< sal_Int32 >( std::distance( + GetTableData().GetColumns().begin(), aCol ) ); + } + else + { + OSL_ENSURE( !aCell.IsValid(), "SwAccessibleTable::getAccessibleColumn:" + "aCell not expected to be valid."); + + throw lang::IndexOutOfBoundsException(); + } + + return nRet; +} + +OUString SAL_CALL SwAccessibleTable::getImplementationName() +{ + return "com.sun.star.comp.Writer.SwAccessibleTableView"; +} + +sal_Bool SAL_CALL SwAccessibleTable::supportsService( + const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleTable::getSupportedServiceNames() +{ + return { "com.sun.star.table.AccessibleTableView", sAccessibleServiceName }; +} + +void SwAccessibleTable::InvalidatePosOrSize( const SwRect& rOldBox ) +{ + SolarMutexGuard aGuard; + + //need to update children + std::unique_ptr pNewTableData = CreateNewTableData(); + if( !pNewTableData->CompareExtents( GetTableData() ) ) + { + mpTableData = std::move(pNewTableData); + FireTableChangeEvent(*mpTableData); + } + if( HasTableData() ) + GetTableData().SetTablePos( GetFrame()->getFrameArea().Pos() ); + + SwAccessibleContext::InvalidatePosOrSize( rOldBox ); +} + +void SwAccessibleTable::Dispose(bool bRecursive, bool bCanSkipInvisible) +{ + SolarMutexGuard aGuard; + EndListeningAll(); + SwAccessibleContext::Dispose(bRecursive, bCanSkipInvisible); +} + +void SwAccessibleTable::DisposeChild( const SwAccessibleChild& rChildFrameOrObj, + bool bRecursive, bool bCanSkipInvisible ) +{ + SolarMutexGuard aGuard; + + const SwFrame *pFrame = rChildFrameOrObj.GetSwFrame(); + OSL_ENSURE( pFrame, "frame expected" ); + if( HasTableData() ) + { + FireTableChangeEvent( GetTableData() ); + ClearTableData(); + } + + // There are two reason why this method has been called. The first one + // is there is no context for pFrame. The method is then called by + // the map, and we have to call our superclass. + // The other situation is that we have been call by a call to get notified + // about its change. We then must not call the superclass + uno::Reference< XAccessible > xAcc( GetMap()->GetContext( pFrame, false ) ); + if( !xAcc.is() ) + SwAccessibleContext::DisposeChild( rChildFrameOrObj, bRecursive, bCanSkipInvisible ); +} + +void SwAccessibleTable::InvalidateChildPosOrSize( const SwAccessibleChild& rChildFrameOrObj, + const SwRect& rOldBox ) +{ + SolarMutexGuard aGuard; + + if( HasTableData() ) + { + SAL_WARN_IF( HasTableData() && + GetFrame()->getFrameArea().Pos() != GetTableData().GetTablePos(), + "sw.a11y", "table has invalid position" ); + if( HasTableData() ) + { + std::unique_ptr pNewTableData = CreateNewTableData(); // #i77106# + if( !pNewTableData->CompareExtents( GetTableData() ) ) + { + if (pNewTableData->GetRowCount() != mpTableData->GetRowCount() + && 1 < GetTableData().GetRowCount()) + { + Int32Set_Impl::const_iterator aSttCol( GetTableData().GetColumnIter( 0 ) ); + Int32Set_Impl::const_iterator aSttRow( GetTableData().GetRowIter( 1 ) ); + const SwFrame *pCellFrame = GetTableData().GetCellAtPos( *aSttCol, *aSttRow ); + Int32Set_Impl::const_iterator aSttCol2( pNewTableData->GetColumnIter( 0 ) ); + Int32Set_Impl::const_iterator aSttRow2( pNewTableData->GetRowIter( 0 ) ); + const SwFrame *pCellFrame2 = pNewTableData->GetCellAtPos( *aSttCol2, *aSttRow2 ); + + if(pCellFrame == pCellFrame2) + { + AccessibleTableModelChange aModelChange; + aModelChange.Type = AccessibleTableModelChangeType::UPDATE; + aModelChange.FirstRow = 0; + aModelChange.LastRow = mpTableData->GetRowCount() - 1; + aModelChange.FirstColumn = 0; + aModelChange.LastColumn = mpTableData->GetColumnCount() - 1; + + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED; + aEvent.NewValue <<= aModelChange; + + FireAccessibleEvent( aEvent ); + } + } + else + FireTableChangeEvent( GetTableData() ); + ClearTableData(); + mpTableData = std::move(pNewTableData); + } + } + } + + // #i013961# - always call super class method + SwAccessibleContext::InvalidateChildPosOrSize( rChildFrameOrObj, rOldBox ); +} + +// XAccessibleSelection + +void SAL_CALL SwAccessibleTable::selectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( (nChildIndex < 0) || (nChildIndex >= getAccessibleChildCount()) ) // #i77106# + throw lang::IndexOutOfBoundsException(); + + // preliminaries: get 'our' table box, and get the cursor shell + const SwTableBox* pBox = GetTableBox( nChildIndex ); + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell == nullptr ) + return; + + // assure, that child, identified by the given index, isn't already selected. + if ( IsChildSelected( nChildIndex ) ) + { + return; + } + + // now we can start to do the work: check whether we already have + // a table selection (in 'our' table). If so, extend the + // selection, else select the current cell. + + // if we have a selection in a table, check if it's in the + // same table that we're trying to select in + const SwTableNode* pSelectedTable = pCursorShell->IsCursorInTable(); + if( pSelectedTable != nullptr ) + { + // get top-most table line + const SwTableLine* pUpper = pBox->GetUpper(); + while( pUpper->GetUpper() != nullptr ) + pUpper = pUpper->GetUpper()->GetUpper(); + sal_uInt16 nPos = + pSelectedTable->GetTable().GetTabLines().GetPos( pUpper ); + if( nPos == USHRT_MAX ) + pSelectedTable = nullptr; + } + + // create the new selection + const SwStartNode* pStartNode = pBox->GetSttNd(); + if( pSelectedTable == nullptr || !pCursorShell->GetTableCrs() ) + { + pCursorShell->StartAction(); + // Set cursor into current cell. This deletes any table cursor. + SwPaM aPaM( *pStartNode ); + aPaM.Move( fnMoveForward, GoInNode ); + Select( aPaM ); + // Move cursor to the end of the table creating a selection and a table + // cursor. + pCursorShell->SetMark(); + pCursorShell->MoveTable( GotoCurrTable, fnTableEnd ); + // now set the cursor into the cell again. + SwPaM *pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + *pPaM->GetPoint() = *pPaM->GetMark(); + pCursorShell->EndAction(); + // we now have one cell selected! + } + else + { + // if the cursor is already in this table, + // expand the current selection (i.e., set + // point to new position; keep mark) + SwPaM aPaM( *pStartNode ); + aPaM.Move( fnMoveForward, GoInNode ); + aPaM.SetMark(); + const SwPaM *pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + *(aPaM.GetMark()) = *pPaM->GetMark(); + Select( aPaM ); + + } +} + +sal_Bool SAL_CALL SwAccessibleTable::isAccessibleChildSelected( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if( (nChildIndex < 0) || (nChildIndex >= getAccessibleChildCount()) ) // #i77106# + throw lang::IndexOutOfBoundsException(); + + return IsChildSelected( nChildIndex ); +} + +void SAL_CALL SwAccessibleTable::clearAccessibleSelection( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + pCursorShell->StartAction(); + pCursorShell->ClearMark(); + pCursorShell->EndAction(); + } +} + +void SAL_CALL SwAccessibleTable::selectAllAccessibleChildren( ) +{ + // first clear selection, then select first and last child + clearAccessibleSelection(); + selectAccessibleChild( 0 ); + selectAccessibleChild( getAccessibleChildCount()-1 ); // #i77106# +} + +sal_Int32 SAL_CALL SwAccessibleTable::getSelectedAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // iterate over all children and count isAccessibleChildSelected() + sal_Int32 nCount = 0; + + sal_Int32 nChildren = getAccessibleChildCount(); // #i71106# + for( sal_Int32 n = 0; n < nChildren; n++ ) + if( IsChildSelected( n ) ) + nCount++; + + return nCount; +} + +uno::Reference SAL_CALL SwAccessibleTable::getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // parameter checking (part 1): index lower 0 + if( nSelectedChildIndex < 0 ) + throw lang::IndexOutOfBoundsException(); + + sal_Int32 nChildIndex = GetIndexOfSelectedChild( nSelectedChildIndex ); + + // parameter checking (part 2): index higher than selected children? + if( nChildIndex < 0 ) + throw lang::IndexOutOfBoundsException(); + + // #i77106# + if ( nChildIndex >= getAccessibleChildCount() ) + { + throw lang::IndexOutOfBoundsException(); + } + + return getAccessibleChild( nChildIndex ); +} + +// index has to be treated as global child index. +void SAL_CALL SwAccessibleTable::deselectAccessibleChild( + sal_Int32 nChildIndex ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + SwCursorShell* pCursorShell = GetCursorShell(); + + // index has to be treated as global child index + if ( !pCursorShell ) + throw lang::IndexOutOfBoundsException(); + + // assure, that given child index is in bounds. + if ( nChildIndex < 0 || nChildIndex >= getAccessibleChildCount() ) // #i77106# + throw lang::IndexOutOfBoundsException(); + + // assure, that child, identified by the given index, is selected. + if ( !IsChildSelected( nChildIndex ) ) + return; + + const SwTableBox* pBox = GetTableBox( nChildIndex ); + OSL_ENSURE( pBox != nullptr, "We need the table box." ); + + // If we unselect point, then set cursor to mark. If we clear another + // selected box, then set cursor to point. + // reduce selection to mark. + SwPaM *pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + bool bDeselectPoint = + pBox->GetSttNd() == + pPaM->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + + SwPaM aPaM( bDeselectPoint ? *pPaM->GetMark() : *pPaM->GetPoint() ); + + pCursorShell->StartAction(); + + // Set cursor into either point or mark + Select( aPaM ); + // Move cursor to the end of the table creating a selection and a table + // cursor. + pCursorShell->SetMark(); + pCursorShell->MoveTable( GotoCurrTable, fnTableEnd ); + // now set the cursor into the cell again. + pPaM = pCursorShell->GetTableCrs() ? pCursorShell->GetTableCrs() + : pCursorShell->GetCursor(); + *pPaM->GetPoint() = *pPaM->GetMark(); + pCursorShell->EndAction(); +} + +sal_Int32 SAL_CALL SwAccessibleTable::getBackground() +{ + const SvxBrushItem &rBack = GetFrame()->GetAttrSet()->GetBackground(); + Color crBack = rBack.GetColor(); + + if (COL_AUTO == crBack) + { + uno::Reference xAccDoc = getAccessibleParent(); + if (xAccDoc.is()) + { + uno::Reference xComponentDoc(xAccDoc,uno::UNO_QUERY); + if (xComponentDoc.is()) + { + crBack = Color(xComponentDoc->getBackground()); + } + } + } + return sal_Int32(crBack); +} + +void SwAccessibleTable::FireSelectionEvent( ) +{ + AccessibleEventObject aEvent; + + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_REMOVE; + + for (const auto& rCell : m_vecCellRemove) + { + // fdo#57197: check if the object is still alive + uno::Reference const xAcc(rCell.second); + if (xAcc.is()) + { + SwAccessibleContext *const pAccCell(rCell.first); + assert(pAccCell); + pAccCell->FireAccessibleEvent(aEvent); + } + } + + if (m_vecCellAdd.size() <= SELECTION_WITH_NUM) + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_ADD; + for (const auto& rCell : m_vecCellAdd) + { + // fdo#57197: check if the object is still alive + uno::Reference const xAcc(rCell.second); + if (xAcc.is()) + { + SwAccessibleContext *const pAccCell(rCell.first); + assert(pAccCell); + pAccCell->FireAccessibleEvent(aEvent); + } + } + return ; + } + else + { + aEvent.EventId = AccessibleEventId::SELECTION_CHANGED_WITHIN; + FireAccessibleEvent(aEvent); + } +} + +void SwAccessibleTable::AddSelectionCell( + SwAccessibleContext *const pAccCell, bool const bAddOrRemove) +{ + uno::Reference const xTmp(pAccCell); + if (bAddOrRemove) + { + m_vecCellAdd.emplace_back(pAccCell, xTmp); + } + else + { + m_vecCellRemove.emplace_back(pAccCell, xTmp); + } +} + +// XAccessibleTableSelection +sal_Bool SAL_CALL SwAccessibleTable::selectRow( sal_Int32 row ) +{ + SolarMutexGuard g; + + if( isAccessibleColumnSelected( row ) ) + return true; + + long lColumnCount = getAccessibleColumnCount(); + for(long lCol = 0; lCol < lColumnCount; lCol ++) + { + long lChildIndex = getAccessibleIndex(row, lCol); + selectAccessibleChild(lChildIndex); + } + + return true; +} +sal_Bool SAL_CALL SwAccessibleTable::selectColumn( sal_Int32 column ) +{ + SolarMutexGuard g; + + if( isAccessibleColumnSelected( column ) ) + return true; + + long lRowCount = getAccessibleRowCount(); + + for(long lRow = 0; lRow < lRowCount; lRow ++) + { + long lChildIndex = getAccessibleIndex(lRow, column); + selectAccessibleChild(lChildIndex); + } + return true; +} + +sal_Bool SAL_CALL SwAccessibleTable::unselectRow( sal_Int32 row ) +{ + SolarMutexGuard g; + + if( isAccessibleSelected( row , 0 ) && isAccessibleSelected( row , getAccessibleColumnCount()-1 ) ) + { + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + pCursorShell->StartAction(); + pCursorShell->ClearMark(); + pCursorShell->EndAction(); + return true; + } + } + return true; +} + +sal_Bool SAL_CALL SwAccessibleTable::unselectColumn( sal_Int32 column ) +{ + SolarMutexGuard g; + + if( isAccessibleSelected( 0 , column ) && isAccessibleSelected( getAccessibleRowCount()-1,column)) + { + SwCursorShell* pCursorShell = GetCursorShell(); + if( pCursorShell != nullptr ) + { + pCursorShell->StartAction(); + pCursorShell->ClearMark(); + pCursorShell->EndAction(); + return true; + } + } + return true; +} + +// #i77106# - implementation of class +SwAccessibleTableColHeaders::SwAccessibleTableColHeaders( + std::shared_ptr const& pMap, + const SwTabFrame *const pTabFrame) + : SwAccessibleTable(pMap, pTabFrame) +{ + SolarMutexGuard aGuard; + + const SwFrameFormat* pFrameFormat = pTabFrame->GetFormat(); + if(pFrameFormat) + StartListening(const_cast(pFrameFormat)->GetNotifier()); + const OUString aName = pFrameFormat->GetName() + "-ColumnHeaders"; + + SetName( aName + "-" + OUString::number( pTabFrame->GetPhyPageNum() ) ); + + const OUString sArg2( GetFormattedPageNumber() ); + + SetDesc( GetResource( STR_ACCESS_TABLE_DESC, &aName, &sArg2 ) ); + + NotRegisteredAtAccessibleMap(); // #i85634# +} + +std::unique_ptr SwAccessibleTableColHeaders::CreateNewTableData() +{ + const SwTabFrame* pTabFrame = static_cast( GetFrame() ); + return std::unique_ptr(new SwAccessibleTableData_Impl( *(GetMap()), pTabFrame, IsInPagePreview(), true )); +} + +void SwAccessibleTableColHeaders::Notify(const SfxHint& ) +{ +} + +// XInterface +uno::Any SAL_CALL SwAccessibleTableColHeaders::queryInterface( const uno::Type& aType ) +{ + return SwAccessibleTable::queryInterface( aType ); +} + +// XAccessibleContext +sal_Int32 SAL_CALL SwAccessibleTableColHeaders::getAccessibleChildCount() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + sal_Int32 nCount = 0; + + const SwTabFrame* pTabFrame = static_cast( GetFrame() ); + const SwAccessibleChildSList aVisList( GetVisArea(), *pTabFrame, *(GetMap()) ); + SwAccessibleChildSList::const_iterator aIter( aVisList.begin() ); + while( aIter != aVisList.end() ) + { + const SwAccessibleChild& rLower = *aIter; + if( rLower.IsAccessible( IsInPagePreview() ) ) + { + nCount++; + } + else if( rLower.GetSwFrame() ) + { + // There are no unaccessible SdrObjects that count + if ( !rLower.GetSwFrame()->IsRowFrame() || + pTabFrame->IsInHeadline( *(rLower.GetSwFrame()) ) ) + { + nCount += SwAccessibleFrame::GetChildCount( *(GetMap()), + GetVisArea(), + rLower.GetSwFrame(), + IsInPagePreview() ); + } + } + ++aIter; + } + + return nCount; +} + +uno::Reference< XAccessible> SAL_CALL + SwAccessibleTableColHeaders::getAccessibleChild (sal_Int32 nIndex) +{ + if ( nIndex < 0 || nIndex >= getAccessibleChildCount() ) + { + throw lang::IndexOutOfBoundsException(); + } + + return SwAccessibleTable::getAccessibleChild( nIndex ); +} + +// XAccessibleTable +uno::Reference< XAccessibleTable > + SAL_CALL SwAccessibleTableColHeaders::getAccessibleRowHeaders() +{ + return uno::Reference< XAccessibleTable >(); +} + +uno::Reference< XAccessibleTable > + SAL_CALL SwAccessibleTableColHeaders::getAccessibleColumnHeaders() +{ + return uno::Reference< XAccessibleTable >(); +} + +// XServiceInfo + +OUString SAL_CALL SwAccessibleTableColHeaders::getImplementationName() +{ + static const char sImplName[] = "com.sun.star.comp.Writer.SwAccessibleTableColumnHeadersView"; + return sImplName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctable.hxx b/sw/source/core/access/acctable.hxx new file mode 100644 index 000000000..4da4944ba --- /dev/null +++ b/sw/source/core/access/acctable.hxx @@ -0,0 +1,280 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCTABLE_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCTABLE_HXX + +#include +#include +#include +#include + +#include +#include + +#include "acccontext.hxx" + +class SwTabFrame; +class SwAccessibleTableData_Impl; +class SwTableBox; +class SwSelBoxes; + +namespace sw::access { + class SwAccessibleChild; +} + +class SwAccessibleTable : + public SwAccessibleContext, + public css::accessibility::XAccessibleTable, + public css::accessibility::XAccessibleSelection, + public css::accessibility::XAccessibleTableSelection, + public SvtListener +{ + std::unique_ptr mpTableData; // the table's data, protected by SolarMutex + OUString m_sDesc; + const SwSelBoxes *GetSelBoxes() const; + + void FireTableChangeEvent( const SwAccessibleTableData_Impl& rTableData ); + + /** get the SwTableBox* for the given child */ + const SwTableBox* GetTableBox( sal_Int32 ) const; + + bool IsChildSelected( sal_Int32 nChildIndex ) const; + + sal_Int32 GetIndexOfSelectedChild( sal_Int32 nSelectedChildIndex ) const; + +protected: + // Set states for getAccessibleStateSet. + // This derived class additionally sets MULTISELECTABLE(+) + virtual void GetStates( ::utl::AccessibleStateSetHelper& rStateSet ) override; + + virtual ~SwAccessibleTable() override; + + // #i77106# + void SetDesc( const OUString& sNewDesc ) + { + m_sDesc = sNewDesc; + } + + virtual std::unique_ptr CreateNewTableData(); // #i77106# + + // force update of table data + void UpdateTableData(); + + // remove the current table data + void ClearTableData(); + + // get table data, update if necessary + inline SwAccessibleTableData_Impl& GetTableData(); + + // Is table data evailable? + bool HasTableData() const { return (mpTableData != nullptr); } + + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleTable(std::shared_ptr const& pInitMap, + const SwTabFrame* pTableFrame); + + // XInterface + + // (XInterface methods need to be implemented to disambiguate + // between those inherited through SwAccessibleContext and + // XAccessibleTable). + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + virtual void SAL_CALL acquire( ) throw () override + { SwAccessibleContext::acquire(); }; + + virtual void SAL_CALL release( ) throw () override + { SwAccessibleContext::release(); }; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleContext + + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XAccessibleTable + + virtual sal_Int32 SAL_CALL getAccessibleRowCount() override; + virtual sal_Int32 SAL_CALL getAccessibleColumnCount( ) override; + virtual OUString SAL_CALL getAccessibleRowDescription( + sal_Int32 nRow ) override; + virtual OUString SAL_CALL getAccessibleColumnDescription( + sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleRowExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleColumnExtentAt( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleRowHeaders( ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleColumnHeaders( ) override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL + getSelectedAccessibleRows( ) override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL + getSelectedAccessibleColumns( ) override; + virtual sal_Bool SAL_CALL isAccessibleRowSelected( sal_Int32 nRow ) override; + virtual sal_Bool SAL_CALL isAccessibleColumnSelected( sal_Int32 nColumn ) override; + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL + getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL + getAccessibleCaption( ) override; + virtual css::uno::Reference< + css::accessibility::XAccessible > SAL_CALL + getAccessibleSummary( ) override; + virtual sal_Bool SAL_CALL isAccessibleSelected( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndex( + sal_Int32 nRow, sal_Int32 nColumn ) override; + virtual sal_Int32 SAL_CALL getAccessibleRow( sal_Int32 nChildIndex ) override; + virtual sal_Int32 SAL_CALL getAccessibleColumn( sal_Int32 nChildIndex ) override; + // XAccessibleTableSelection + virtual sal_Bool SAL_CALL selectRow( sal_Int32 row ) override ; + virtual sal_Bool SAL_CALL selectColumn( sal_Int32 column ) override ; + virtual sal_Bool SAL_CALL unselectRow( sal_Int32 row ) override; + virtual sal_Bool SAL_CALL unselectColumn( sal_Int32 column ) override; + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. + */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. + */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // C++ interface + + // The object has been moved by the layout + virtual void InvalidatePosOrSize( const SwRect& rOldBox ) override; + + // The object is not visible any longer and should be destroyed + virtual void Dispose(bool bRecursive, bool bCanSkipInvisible = true) override; + + virtual void DisposeChild( const sw::access::SwAccessibleChild& rFrameOrObj, + bool bRecursive, bool bCanSkipInvisible ) override; + virtual void InvalidateChildPosOrSize( const sw::access::SwAccessibleChild& rFrameOrObj, + const SwRect& rFrame ) override; + + // XAccessibleSelection + + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + + virtual void SAL_CALL clearAccessibleSelection( ) override; + + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // index has to be treated as global child index. + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nChildIndex ) override; + + // XAccessibleComponent + sal_Int32 SAL_CALL getBackground() override; + typedef std::vector< std::pair > > Cells_t; + Cells_t m_vecCellAdd; + Cells_t m_vecCellRemove; + void FireSelectionEvent( ); + void AddSelectionCell(SwAccessibleContext*, bool bAddOrRemove); +}; + +inline SwAccessibleTableData_Impl& SwAccessibleTable::GetTableData() +{ + if( !mpTableData ) + UpdateTableData(); + return *mpTableData; +} + +// #i77106# - subclass to represent table column headers +class SwAccessibleTableColHeaders : public SwAccessibleTable +{ +protected: + virtual ~SwAccessibleTableColHeaders() override + {} + + virtual std::unique_ptr CreateNewTableData() override; + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleTableColHeaders(std::shared_ptr const& pMap, + const SwTabFrame *pTabFrame); + + // XInterface + + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& aType ) override; + + // XAccessibleContext + + /// Return the number of currently visible children. + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + /// Return the specified child or NULL if index is invalid. + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + // XAccessibleTable + + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleRowHeaders( ) override; + virtual css::uno::Reference< + css::accessibility::XAccessibleTable > + SAL_CALL getAccessibleColumnHeaders( ) override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctextframe.cxx b/sw/source/core/access/acctextframe.cxx new file mode 100644 index 000000000..928a68677 --- /dev/null +++ b/sw/source/core/access/acctextframe.cxx @@ -0,0 +1,320 @@ +/* -*- 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 "acctextframe.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +using utl::AccessibleRelationSetHelper; +using ::com::sun::star::accessibility::XAccessibleContext; + +SwAccessibleTextFrame::SwAccessibleTextFrame( + std::shared_ptr const& pInitMap, + const SwFlyFrame& rFlyFrame ) : + SwAccessibleFrameBase( pInitMap, AccessibleRole::TEXT_FRAME, &rFlyFrame ), + msTitle(), + msDesc() +{ + const SwFlyFrameFormat* pFlyFrameFormat = rFlyFrame.GetFormat(); + msTitle = pFlyFrameFormat->GetObjTitle(); + + msDesc = pFlyFrameFormat->GetObjDescription(); + if ( msDesc.isEmpty() && + msTitle != GetName() ) + { + msDesc = msTitle; + } +} + +SwAccessibleTextFrame::~SwAccessibleTextFrame() +{ +} + +void SwAccessibleTextFrame::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + EndListeningAll(); + else if(auto pLegacyModifyHint = dynamic_cast(&rHint)) + { + const sal_uInt16 nWhich = pLegacyModifyHint->m_pOld ? pLegacyModifyHint->m_pOld->Which() : pLegacyModifyHint->m_pNew ? pLegacyModifyHint->m_pNew->Which() : 0; + const SwFlyFrame* pFlyFrame = static_cast(GetFrame()); + switch(nWhich) + { + // #i73249# + case RES_TITLE_CHANGED: + { + OUString sOldTitle, sNewTitle; + const SwStringMsgPoolItem *pOldItem = dynamic_cast(pLegacyModifyHint->m_pOld); + if(pOldItem) + sOldTitle = pOldItem->GetString(); + const SwStringMsgPoolItem *pNewItem = dynamic_cast(pLegacyModifyHint->m_pNew); + if(pNewItem) + sNewTitle = pNewItem->GetString(); + if(sOldTitle == sNewTitle) + break; + msTitle = sNewTitle; + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::NAME_CHANGED; + aEvent.OldValue <<= sOldTitle; + aEvent.NewValue <<= msTitle; + FireAccessibleEvent( aEvent ); + + const SwFlyFrameFormat* pFlyFrameFormat = pFlyFrame->GetFormat(); + if(!pFlyFrameFormat || !pFlyFrameFormat->GetObjDescription().isEmpty()) + break; + [[fallthrough]]; + } + case RES_DESCRIPTION_CHANGED: + { + if(pFlyFrame) + { + const OUString sOldDesc(msDesc); + + const SwFlyFrameFormat* pFlyFrameFormat = pFlyFrame->GetFormat(); + const OUString& rDesc = pFlyFrameFormat->GetObjDescription(); + msDesc = rDesc; + if(msDesc.isEmpty() && msTitle != GetName()) + msDesc = msTitle; + + if(msDesc != sOldDesc) + { + AccessibleEventObject aEvent; + aEvent.EventId = AccessibleEventId::DESCRIPTION_CHANGED; + aEvent.OldValue <<= sOldDesc; + aEvent.NewValue <<= msDesc; + FireAccessibleEvent(aEvent); + } + } + } + } + } +} + +// XInterface + +css::uno::Any SAL_CALL + SwAccessibleTextFrame::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = SwAccessibleContext::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast< css::accessibility::XAccessibleSelection* >(this) + ); + return aReturn; +} + +void SAL_CALL + SwAccessibleTextFrame::acquire() + throw () +{ + SwAccessibleContext::acquire (); +} + +void SAL_CALL + SwAccessibleTextFrame::release() + throw () +{ + SwAccessibleContext::release (); +} + +// XAccessibleSelection + +void SAL_CALL SwAccessibleTextFrame::selectAccessibleChild( sal_Int32 ) +{ + SAL_WARN("sw.a11y", " - missing implementation"); +} + +sal_Bool SAL_CALL SwAccessibleTextFrame::isAccessibleChildSelected( sal_Int32 nChildIndex ) +{ + SolarMutexGuard g; + + uno::Reference xAcc = getAccessibleChild( nChildIndex ); + uno::Reference xContext; + if( xAcc.is() ) + xContext = xAcc->getAccessibleContext(); + + if( xContext.is() ) + { + if( xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH ) + { + uno::Reference< css::accessibility::XAccessibleText > + xText(xAcc, uno::UNO_QUERY); + if( xText.is() ) + { + if( xText->getSelectionStart() >= 0 ) return true; + } + } + } + + return false; +} + +void SAL_CALL SwAccessibleTextFrame::clearAccessibleSelection( ) +{ + SAL_WARN("sw.a11y", " - missing implementation"); +} + +void SAL_CALL SwAccessibleTextFrame::selectAllAccessibleChildren( ) +{ + SAL_WARN("sw.a11y", " - missing implementation"); +} + +sal_Int32 SAL_CALL SwAccessibleTextFrame::getSelectedAccessibleChildCount() +{ + sal_Int32 nCount = 0; + sal_Int32 TotalCount = getAccessibleChildCount(); + for( sal_Int32 i = 0; i < TotalCount; i++ ) + if( isAccessibleChildSelected(i) ) nCount++; + + return nCount; +} + +uno::Reference SAL_CALL SwAccessibleTextFrame::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard g; + + if ( nSelectedChildIndex > getSelectedAccessibleChildCount() ) + throw lang::IndexOutOfBoundsException(); + sal_Int32 i1, i2; + for( i1 = 0, i2 = 0; i1 < getAccessibleChildCount(); i1++ ) + if( isAccessibleChildSelected(i1) ) + { + if( i2 == nSelectedChildIndex ) + return getAccessibleChild( i1 ); + i2++; + } + return uno::Reference(); +} + +void SAL_CALL SwAccessibleTextFrame::deselectAccessibleChild( sal_Int32 ) +{ + SAL_WARN("sw.a11y", " - missing implementation"); +} + +// #i73249# +OUString SAL_CALL SwAccessibleTextFrame::getAccessibleName() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + if ( !msTitle.isEmpty() ) + { + return msTitle; + } + + return SwAccessibleFrameBase::getAccessibleName(); +} + +OUString SAL_CALL SwAccessibleTextFrame::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + return msDesc; + +} + +OUString SAL_CALL SwAccessibleTextFrame::getImplementationName() +{ + return "com.sun.star.comp.Writer.SwAccessibleTextFrameView"; +} + +sal_Bool SAL_CALL SwAccessibleTextFrame::supportsService(const OUString& sTestServiceName) +{ + return cppu::supportsService(this, sTestServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwAccessibleTextFrame::getSupportedServiceNames() +{ + return { "com.sun.star.text.AccessibleTextFrameView", sAccessibleServiceName }; +} + +uno::Sequence< sal_Int8 > SAL_CALL SwAccessibleTextFrame::getImplementationId() +{ + return css::uno::Sequence(); +} + +// XAccessibleRelationSet + +SwFlyFrame* SwAccessibleTextFrame::getFlyFrame() const +{ + SwFlyFrame* pFlyFrame = nullptr; + + const SwFrame* pFrame = GetFrame(); + assert(pFrame); + if( pFrame->IsFlyFrame() ) + { + pFlyFrame = static_cast( const_cast( pFrame ) ); + } + + return pFlyFrame; +} + +AccessibleRelation SwAccessibleTextFrame::makeRelation( sal_Int16 nType, const SwFlyFrame* pFrame ) +{ + uno::Sequence > aSequence { GetMap()->GetContext( pFrame ) }; + return AccessibleRelation( nType, aSequence ); +} + +uno::Reference SAL_CALL SwAccessibleTextFrame::getAccessibleRelationSet( ) +{ + SolarMutexGuard aGuard; + + ThrowIfDisposed(); + + // get the frame, and insert prev/next relations into helper + + AccessibleRelationSetHelper* pHelper = new AccessibleRelationSetHelper(); + + SwFlyFrame* pFlyFrame = getFlyFrame(); + assert(pFlyFrame); + + const SwFlyFrame* pPrevFrame = pFlyFrame->GetPrevLink(); + if( pPrevFrame != nullptr ) + pHelper->AddRelation( makeRelation( + AccessibleRelationType::CONTENT_FLOWS_FROM, pPrevFrame ) ); + + const SwFlyFrame* pNextFrame = pFlyFrame->GetNextLink(); + if( pNextFrame != nullptr ) + pHelper->AddRelation( makeRelation( + AccessibleRelationType::CONTENT_FLOWS_TO, pNextFrame ) ); + + return pHelper; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/acctextframe.hxx b/sw/source/core/access/acctextframe.hxx new file mode 100644 index 000000000..a4ee77ad8 --- /dev/null +++ b/sw/source/core/access/acctextframe.hxx @@ -0,0 +1,116 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_ACCTEXTFRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_ACCTEXTFRAME_HXX + +#include "accframebase.hxx" + +#include + +class SwFlyFrame; + +class SwAccessibleTextFrame : public SwAccessibleFrameBase, + public css::accessibility::XAccessibleSelection +{ +private: + // #i73249# + OUString msTitle; + OUString msDesc; + +protected: + virtual ~SwAccessibleTextFrame() override; + virtual void Notify(const SfxHint&) override; + +public: + SwAccessibleTextFrame(std::shared_ptr const& pInitMap, + const SwFlyFrame& rFlyFrame); + + virtual css::uno::Any SAL_CALL queryInterface( + css::uno::Type const & rType ) override; + virtual void SAL_CALL acquire() throw () override; + virtual void SAL_CALL release() throw () override; + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( + sal_Int32 nChildIndex ) override; + + virtual sal_Bool SAL_CALL isAccessibleChildSelected( + sal_Int32 nChildIndex ) override; + + virtual void SAL_CALL clearAccessibleSelection( ) override; + + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + virtual void SAL_CALL deselectAccessibleChild( + sal_Int32 nSelectedChildIndex ) override; + + // XAccessibleContext + + // #i73249# - Return the object's current name. + virtual OUString SAL_CALL + getAccessibleName() override; + /// Return this object's description. + virtual OUString SAL_CALL + getAccessibleDescription() override; + + // XServiceInfo + + /** Returns an identifier for the implementation of this object. + */ + virtual OUString SAL_CALL + getImplementationName() override; + + /** Return whether the specified service is supported by this class. + */ + virtual sal_Bool SAL_CALL + supportsService (const OUString& sServiceName) override; + + /** Returns a list of all supported services. In this case that is just + the AccessibleContext service. + */ + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XAccessibleContext::getAccessibleRelationSet + + // text frame may have accessible relations to their + // predecessor/successor frames + +private: + // helper methods for getAccessibleRelationSet: + SwFlyFrame* getFlyFrame() const; + + css::accessibility::AccessibleRelation makeRelation( + sal_Int16 nType, const SwFlyFrame* pFrame ); + +public: + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet> SAL_CALL getAccessibleRelationSet() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/parachangetrackinginfo.cxx b/sw/source/core/access/parachangetrackinginfo.cxx new file mode 100644 index 000000000..5fcc6f41c --- /dev/null +++ b/sw/source/core/access/parachangetrackinginfo.cxx @@ -0,0 +1,204 @@ +/* -*- 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 "parachangetrackinginfo.hxx" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + void initChangeTrackTextMarkupLists( const SwTextFrame& rTextFrame, + std::unique_ptr& opChangeTrackInsertionTextMarkupList, + std::unique_ptr& opChangeTrackDeletionTextMarkupList, + std::unique_ptr& opChangeTrackFormatChangeTextMarkupList ) + { + opChangeTrackInsertionTextMarkupList.reset( new SwWrongList( WRONGLIST_CHANGETRACKING ) ); + opChangeTrackDeletionTextMarkupList.reset( new SwWrongList( WRONGLIST_CHANGETRACKING ) ); + opChangeTrackFormatChangeTextMarkupList.reset( new SwWrongList( WRONGLIST_CHANGETRACKING ) ); + + if (!rTextFrame.GetTextNodeFirst()) + { + OSL_FAIL( " instance!" ); + return; + } + // sw_redlinehide: the first node is sufficient - there are only + // multiple ones in Hide case and the code below returns early then + const SwTextNode& rTextNode(*(rTextFrame.GetTextNodeFirst())); + + const IDocumentRedlineAccess& rIDocChangeTrack( rTextNode.getIDocumentRedlineAccess() ); + + if (!IDocumentRedlineAccess::IsShowChanges(rIDocChangeTrack.GetRedlineFlags()) + || rTextFrame.getRootFrame()->IsHideRedlines() + || rIDocChangeTrack.GetRedlineTable().empty()) + { + // nothing to do --> empty change track text markup lists. + return; + } + + const SwRedlineTable::size_type nIdxOfFirstRedlineForTextNode = + rIDocChangeTrack.GetRedlinePos( rTextNode, RedlineType::Any ); + if ( nIdxOfFirstRedlineForTextNode == SwRedlineTable::npos ) + { + // nothing to do --> empty change track text markup lists. + return; + } + + // sw_redlinehide: rely on the Hide early return above & cast + // TextFrameIndex to SwIndex directly + const sal_Int32 nTextFrameTextStartPos = rTextFrame.IsFollow() + ? sal_Int32(rTextFrame.GetOffset()) + : 0; + const sal_Int32 nTextFrameTextEndPos = rTextFrame.HasFollow() + ? sal_Int32(rTextFrame.GetFollow()->GetOffset()) + : rTextFrame.GetText().getLength(); + + // iteration over the redlines which overlap with the text node. + const SwRedlineTable& rRedlineTable = rIDocChangeTrack.GetRedlineTable(); + const SwRedlineTable::size_type nRedlineCount( rRedlineTable.size() ); + for ( SwRedlineTable::size_type nActRedline = nIdxOfFirstRedlineForTextNode; + nActRedline < nRedlineCount; + ++nActRedline) + { + const SwRangeRedline* pActRedline = rRedlineTable[ nActRedline ]; + if ( pActRedline->Start()->nNode > rTextNode.GetIndex() ) + { + break; + } + + sal_Int32 nTextNodeChangeTrackStart(COMPLETE_STRING); + sal_Int32 nTextNodeChangeTrackEnd(COMPLETE_STRING); + pActRedline->CalcStartEnd( rTextNode.GetIndex(), + nTextNodeChangeTrackStart, + nTextNodeChangeTrackEnd ); + if ( nTextNodeChangeTrackStart > nTextFrameTextEndPos || + nTextNodeChangeTrackEnd < nTextFrameTextStartPos ) + { + // Consider only redlines which overlap with the text frame's text. + continue; + } + + SwWrongList* pMarkupList( nullptr ); + switch ( pActRedline->GetType() ) + { + case RedlineType::Insert: + { + pMarkupList = opChangeTrackInsertionTextMarkupList.get(); + } + break; + case RedlineType::Delete: + { + pMarkupList = opChangeTrackDeletionTextMarkupList.get(); + } + break; + case RedlineType::Format: + { + pMarkupList = opChangeTrackFormatChangeTextMarkupList.get(); + } + break; + default: + { + // other types are not considered + } + } + if ( pMarkupList ) + { + const sal_Int32 nTextFrameChangeTrackStart = + std::max(nTextNodeChangeTrackStart, nTextFrameTextStartPos); + + const sal_Int32 nTextFrameChangeTrackEnd = + std::min(nTextNodeChangeTrackEnd, nTextFrameTextEndPos); + + pMarkupList->Insert( OUString(), nullptr, + nTextFrameChangeTrackStart, + nTextFrameChangeTrackEnd - nTextFrameChangeTrackStart, + pMarkupList->Count() ); + } + } // eof iteration over the redlines which overlap with the text node + } +} // eof anonymous namespace + +SwParaChangeTrackingInfo::SwParaChangeTrackingInfo( const SwTextFrame& rTextFrame ) + : mrTextFrame( rTextFrame ) +{ +} + +SwParaChangeTrackingInfo::~SwParaChangeTrackingInfo() +{ + reset(); +} + +void SwParaChangeTrackingInfo::reset() +{ + mpChangeTrackInsertionTextMarkupList.reset(); + mpChangeTrackDeletionTextMarkupList.reset(); + mpChangeTrackFormatChangeTextMarkupList.reset(); +} + +const SwWrongList* SwParaChangeTrackingInfo::getChangeTrackingTextMarkupList( const sal_Int32 nTextMarkupType ) +{ + SwWrongList* pChangeTrackingTextMarkupList = nullptr; + + if ( mpChangeTrackInsertionTextMarkupList == nullptr ) + { + OSL_ENSURE( mpChangeTrackDeletionTextMarkupList == nullptr, + " expected to be NULL." ); + OSL_ENSURE( mpChangeTrackFormatChangeTextMarkupList == nullptr, + " expected to be NULL." ); + initChangeTrackTextMarkupLists( mrTextFrame, + mpChangeTrackInsertionTextMarkupList, + mpChangeTrackDeletionTextMarkupList, + mpChangeTrackFormatChangeTextMarkupList ); + } + + switch ( nTextMarkupType ) + { + case css::text::TextMarkupType::TRACK_CHANGE_INSERTION: + { + pChangeTrackingTextMarkupList = mpChangeTrackInsertionTextMarkupList.get(); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_DELETION: + { + pChangeTrackingTextMarkupList = mpChangeTrackDeletionTextMarkupList.get(); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pChangeTrackingTextMarkupList = mpChangeTrackFormatChangeTextMarkupList.get(); + } + break; + default: + { + OSL_FAIL( " - misusage - unexpected text markup type for change tracking." ); + } + } + + return pChangeTrackingTextMarkupList; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/parachangetrackinginfo.hxx b/sw/source/core/access/parachangetrackinginfo.hxx new file mode 100644 index 000000000..30f020125 --- /dev/null +++ b/sw/source/core/access/parachangetrackinginfo.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_PARACHANGETRACKINGINFO_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_PARACHANGETRACKINGINFO_HXX + +#include +#include + +class SwTextFrame; +class SwWrongList; + +class SwParaChangeTrackingInfo +{ + public: + explicit SwParaChangeTrackingInfo( const SwTextFrame& rTextFrame ); + ~SwParaChangeTrackingInfo(); + + void reset(); + + const SwWrongList* getChangeTrackingTextMarkupList( const sal_Int32 nTextMarkupType ); + + private: + SwParaChangeTrackingInfo( const SwParaChangeTrackingInfo& ) = delete; + SwParaChangeTrackingInfo& operator=( const SwParaChangeTrackingInfo& ) = delete; + + const SwTextFrame& mrTextFrame; + + std::unique_ptr mpChangeTrackInsertionTextMarkupList; + std::unique_ptr mpChangeTrackDeletionTextMarkupList; + std::unique_ptr mpChangeTrackFormatChangeTextMarkupList; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/textmarkuphelper.cxx b/sw/source/core/access/textmarkuphelper.cxx new file mode 100644 index 000000000..19bd4d576 --- /dev/null +++ b/sw/source/core/access/textmarkuphelper.cxx @@ -0,0 +1,225 @@ +/* -*- 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 "textmarkuphelper.hxx" +#include "accportions.hxx" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace com::sun::star; + +// helper functions +namespace { + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + SwWrongList const* (SwTextNode::* + getTextMarkupFunc(const sal_Int32 nTextMarkupType))() const + { + switch ( nTextMarkupType ) + { + case text::TextMarkupType::SPELLCHECK: + { + return &SwTextNode::GetWrong; + } + break; + case text::TextMarkupType::PROOFREADING: + { + // support not implemented yet + return nullptr; + } + break; + case text::TextMarkupType::SMARTTAG: + { + // support not implemented yet + return nullptr; + } + break; + default: + { + throw lang::IllegalArgumentException(); + } + } + } +} + +// implementation of class +SwTextMarkupHelper::SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwTextFrame& rTextFrame) + : mrPortionData( rPortionData ) + , m_pTextFrame(&rTextFrame) + , mpTextMarkupList( nullptr ) +{ +} + +// #i108125# +SwTextMarkupHelper::SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwWrongList& rTextMarkupList ) + : mrPortionData( rPortionData ) + , m_pTextFrame( nullptr ) + , mpTextMarkupList( &rTextMarkupList ) +{ +} + +sal_Int32 SwTextMarkupHelper::getTextMarkupCount( const sal_Int32 nTextMarkupType ) +{ + sal_Int32 nTextMarkupCount( 0 ); + + if (mpTextMarkupList) + { + nTextMarkupCount = mpTextMarkupList->Count(); + } + else + { + assert(m_pTextFrame); + SwWrongList const* (SwTextNode::*const pGetWrongList)() const = getTextMarkupFunc(nTextMarkupType); + if (pGetWrongList) + { + sw::WrongListIteratorCounter iter(*m_pTextFrame, pGetWrongList); + nTextMarkupCount = iter.GetElementCount(); + } + } + + return nTextMarkupCount; +} + +css::accessibility::TextSegment + SwTextMarkupHelper::getTextMarkup( const sal_Int32 nTextMarkupIndex, + const sal_Int32 nTextMarkupType ) +{ + if ( nTextMarkupIndex >= getTextMarkupCount( nTextMarkupType ) || + nTextMarkupIndex < 0 ) + { + throw lang::IndexOutOfBoundsException(); + } + + css::accessibility::TextSegment aTextMarkupSegment; + aTextMarkupSegment.SegmentStart = -1; + aTextMarkupSegment.SegmentEnd = -1; + + std::unique_ptr pIter; + if (mpTextMarkupList) + { + pIter.reset(new sw::WrongListIteratorCounter(*mpTextMarkupList)); + } + else + { + assert(m_pTextFrame); + SwWrongList const* (SwTextNode::*const pGetWrongList)() const = getTextMarkupFunc(nTextMarkupType); + if (pGetWrongList) + { + pIter.reset(new sw::WrongListIteratorCounter(*m_pTextFrame, pGetWrongList)); + } + } + + if (pIter) + { + auto const oElement(pIter->GetElementAt(nTextMarkupIndex)); + if (oElement) + { + const OUString& rText = mrPortionData.GetAccessibleString(); + const sal_Int32 nStartPos = + mrPortionData.GetAccessiblePosition(oElement->first); + const sal_Int32 nEndPos = + mrPortionData.GetAccessiblePosition(oElement->second); + aTextMarkupSegment.SegmentText = rText.copy( nStartPos, nEndPos - nStartPos ); + aTextMarkupSegment.SegmentStart = nStartPos; + aTextMarkupSegment.SegmentEnd = nEndPos; + } + else + { + OSL_FAIL( " - missing instance" ); + } + } + + return aTextMarkupSegment; +} + +css::uno::Sequence< css::accessibility::TextSegment > + SwTextMarkupHelper::getTextMarkupAtIndex( const sal_Int32 nCharIndex, + const sal_Int32 nTextMarkupType ) +{ + // assumption: + // value of is in range [0..length of accessible text) + + const TextFrameIndex nCoreCharIndex = mrPortionData.GetCoreViewPosition(nCharIndex); + // Handling of portions with core length == 0 at the beginning of the + // paragraph - e.g. numbering portion. + if ( mrPortionData.GetAccessiblePosition( nCoreCharIndex ) > nCharIndex ) + { + return uno::Sequence< css::accessibility::TextSegment >(); + } + + std::unique_ptr pIter; + if (mpTextMarkupList) + { + pIter.reset(new sw::WrongListIteratorCounter(*mpTextMarkupList)); + } + else + { + assert(m_pTextFrame); + SwWrongList const* (SwTextNode::*const pGetWrongList)() const = getTextMarkupFunc(nTextMarkupType); + if (pGetWrongList) + { + pIter.reset(new sw::WrongListIteratorCounter(*m_pTextFrame, pGetWrongList)); + } + } + + std::vector< css::accessibility::TextSegment > aTmpTextMarkups; + if (pIter) + { + const OUString& rText = mrPortionData.GetAccessibleString(); + sal_uInt16 count(pIter->GetElementCount()); + for (sal_uInt16 i = 0; i < count; ++i) + { + auto const oElement(pIter->GetElementAt(i)); + if (oElement && + oElement->first <= nCoreCharIndex && + nCoreCharIndex < oElement->second) + { + const sal_Int32 nStartPos = + mrPortionData.GetAccessiblePosition(oElement->first); + const sal_Int32 nEndPos = + mrPortionData.GetAccessiblePosition(oElement->second); + css::accessibility::TextSegment aTextMarkupSegment; + aTextMarkupSegment.SegmentText = rText.copy( nStartPos, nEndPos - nStartPos ); + aTextMarkupSegment.SegmentStart = nStartPos; + aTextMarkupSegment.SegmentEnd = nEndPos; + aTmpTextMarkups.push_back( aTextMarkupSegment ); + } + } + } + + uno::Sequence< css::accessibility::TextSegment > aTextMarkups( + aTmpTextMarkups.size() ); + std::copy( aTmpTextMarkups.begin(), aTmpTextMarkups.end(), aTextMarkups.begin() ); + + return aTextMarkups; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/access/textmarkuphelper.hxx b/sw/source/core/access/textmarkuphelper.hxx new file mode 100644 index 000000000..6db3f9bc0 --- /dev/null +++ b/sw/source/core/access/textmarkuphelper.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESS_TEXTMARKUPHELPER_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESS_TEXTMARKUPHELPER_HXX + +#include +#include + +namespace com::sun::star::accessibility { + struct TextSegment; +} + +class SwAccessiblePortionData; +class SwTextFrame; +class SwWrongList; // #i108125# + +class SwTextMarkupHelper +{ + public: + SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwTextFrame& rTextFrame); + SwTextMarkupHelper( const SwAccessiblePortionData& rPortionData, + const SwWrongList& rTextMarkupList ); // #i108125# + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + sal_Int32 getTextMarkupCount( const sal_Int32 nTextMarkupType ); + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::accessibility::TextSegment getTextMarkup( + const sal_Int32 nTextMarkupIndex, + const sal_Int32 nTextMarkupType ); + + /// @throws css::lang::IndexOutOfBoundsException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::uno::Sequence< css::accessibility::TextSegment > + getTextMarkupAtIndex( const sal_Int32 nCharIndex, + const sal_Int32 nTextMarkupType ); + + private: + SwTextMarkupHelper( const SwTextMarkupHelper& ) = delete; + SwTextMarkupHelper& operator=( const SwTextMarkupHelper& ) = delete; + + const SwAccessiblePortionData& mrPortionData; + + SwTextFrame const* m_pTextFrame; + const SwWrongList* mpTextMarkupList; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/calbck.cxx b/sw/source/core/attr/calbck.cxx new file mode 100644 index 000000000..cef9b02b0 --- /dev/null +++ b/sw/source/core/attr/calbck.cxx @@ -0,0 +1,381 @@ +/* -*- 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 + +namespace sw +{ + bool ListenerEntry::GetInfo(SfxPoolItem& rInfo) const + { return m_pToTell == nullptr || m_pToTell->GetInfo( rInfo ); } + void ListenerEntry::Modify(const SfxPoolItem *const pOldValue, + const SfxPoolItem *const pNewValue) + { + SwClientNotify(*GetRegisteredIn(), sw::LegacyModifyHint(pOldValue, pNewValue)); + } + void ListenerEntry::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) + { + if (auto pLegacyHint = dynamic_cast(&rHint)) + { + if (pLegacyHint->m_pNew && pLegacyHint->m_pNew->Which() == RES_OBJECTDYING) + { + auto pModifyChanged = CheckRegistration(pLegacyHint->m_pOld); + if (pModifyChanged) + m_pToTell->SwClientNotify(rModify, *pModifyChanged); + } + else if (m_pToTell) + m_pToTell->SwClientNotifyCall(rModify, rHint); + } + else if (m_pToTell) + m_pToTell->SwClientNotifyCall(rModify, rHint); + } +} + +sw::LegacyModifyHint::~LegacyModifyHint() {} +sw::ModifyChangedHint::~ModifyChangedHint() {} + +SwClient::SwClient(SwClient&& o) noexcept + : m_pRegisteredIn(nullptr) +{ + if(o.m_pRegisteredIn) + { + o.m_pRegisteredIn->Add(this); + o.EndListeningAll(); + } +} + +SwClient::~SwClient() +{ + if(GetRegisteredIn()) + DBG_TESTSOLARMUTEX(); + OSL_ENSURE( !m_pRegisteredIn || m_pRegisteredIn->HasWriterListeners(), "SwModify still known, but Client already disconnected!" ); + if( m_pRegisteredIn && m_pRegisteredIn->HasWriterListeners() ) + m_pRegisteredIn->Remove( this ); +} + +std::unique_ptr SwClient::CheckRegistration( const SfxPoolItem* pOld ) +{ + DBG_TESTSOLARMUTEX(); + // this method only handles notification about dying SwModify objects + if( !pOld || pOld->Which() != RES_OBJECTDYING ) + return nullptr; + + assert(dynamic_cast(pOld)); + const SwPtrMsgPoolItem* pDead = static_cast(pOld); + if(pDead->pObject != m_pRegisteredIn) + { + // we should only care received death notes from objects we are following + return nullptr; + } + // I've got a notification from the object I know + SwModify* pAbove = m_pRegisteredIn->GetRegisteredIn(); + if(pAbove) + { + // if the dying object itself was listening at an SwModify, I take over + // adding myself to pAbove will automatically remove me from my current pRegisteredIn + pAbove->Add(this); + } + else + { + // destroy connection + EndListeningAll(); + } + return std::unique_ptr(new sw::ModifyChangedHint(pAbove)); +} + +void SwClient::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (auto pLegacyHint = dynamic_cast(&rHint)) + { + Modify(pLegacyHint->m_pOld, pLegacyHint->m_pNew); + } +}; + +void SwClient::StartListeningToSameModifyAs(const SwClient& other) +{ + if(other.m_pRegisteredIn) + other.m_pRegisteredIn->Add(this); + else + EndListeningAll(); +} + +void SwClient::EndListeningAll() +{ + if(m_pRegisteredIn) + m_pRegisteredIn->Remove(this); +} + +void SwClient::Modify(SfxPoolItem const*const pOldValue, SfxPoolItem const*const /*pNewValue*/) +{ + CheckRegistration( pOldValue ); +} + +void SwModify::SetInDocDTOR() +{ + // If the document gets destroyed anyway, just tell clients to + // forget me so that they don't try to get removed from my list + // later when they also get destroyed + SwIterator aIter(*this); + for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next()) + pClient->m_pRegisteredIn = nullptr; + m_pWriterListeners = nullptr; +} + +SwModify::~SwModify() +{ + DBG_TESTSOLARMUTEX(); + OSL_ENSURE( !IsModifyLocked(), "Modify destroyed but locked." ); + + if ( IsInCache() ) + SwFrame::GetCache().Delete( this ); + + if ( IsInSwFntCache() ) + pSwFontCache->Delete( this ); + + // notify all clients that they shall remove themselves + SwPtrMsgPoolItem aDyObject( RES_OBJECTDYING, this ); + NotifyClients( &aDyObject, &aDyObject ); + + // remove all clients that have not done themselves + // mba: possibly a hotfix for forgotten base class calls?! + while( m_pWriterListeners ) + static_cast(m_pWriterListeners)->CheckRegistration( &aDyObject ); +} + +void SwModify::NotifyClients( const SfxPoolItem* pOldValue, const SfxPoolItem* pNewValue ) +{ + DBG_TESTSOLARMUTEX(); + if ( IsInCache() || IsInSwFntCache() ) + { + const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() : + pNewValue ? pNewValue->Which() : 0; + CheckCaching( nWhich ); + } + + if ( !m_pWriterListeners || IsModifyLocked() ) + return; + + LockModify(); + + // mba: WTF?! + if( !pOldValue ) + { + m_bLockClientList = true; + } + else + { + switch( pOldValue->Which() ) + { + case RES_OBJECTDYING: + case RES_REMOVE_UNO_OBJECT: + m_bLockClientList = static_cast(pOldValue)->pObject != this; + break; + + default: + m_bLockClientList = true; + } + } + + ModifyBroadcast( pOldValue, pNewValue ); + m_bLockClientList = false; + UnlockModify(); +} + +bool SwModify::GetInfo( SfxPoolItem& rInfo ) const +{ + if(!m_pWriterListeners) + return true; + SwIterator aIter(*this); + for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next()) + if(!pClient->GetInfo( rInfo )) + return false; + return true; +} + +void SwModify::Add( SwClient* pDepend ) +{ + DBG_TESTSOLARMUTEX(); + OSL_ENSURE( !m_bLockClientList, "Client inserted while in Modify" ); + + if(pDepend->m_pRegisteredIn != this ) + { +#if OSL_DEBUG_LEVEL > 0 + if(sw::ClientIteratorBase::s_pClientIters) + { + for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer()) + { + SAL_WARN_IF(&rIter.m_rRoot == m_pWriterListeners, "sw.core", "a " << typeid(*pDepend).name() << " client added as listener to a " << typeid(*this).name() << " during client iteration."); + } + } +#endif + // deregister new client in case it is already registered elsewhere + if( pDepend->m_pRegisteredIn != nullptr ) + pDepend->m_pRegisteredIn->Remove( pDepend ); + + if( !m_pWriterListeners ) + { + // first client added + m_pWriterListeners = pDepend; + m_pWriterListeners->m_pLeft = nullptr; + m_pWriterListeners->m_pRight = nullptr; + } + else + { + // append client + pDepend->m_pRight = m_pWriterListeners->m_pRight; + m_pWriterListeners->m_pRight = pDepend; + pDepend->m_pLeft = m_pWriterListeners; + if( pDepend->m_pRight ) + pDepend->m_pRight->m_pLeft = pDepend; + } + + // connect client to me + pDepend->m_pRegisteredIn = this; + } +} + +SwClient* SwModify::Remove( SwClient* pDepend ) +{ + DBG_TESTSOLARMUTEX(); + assert(pDepend->m_pRegisteredIn == this); + + // SwClient is my listener + // remove it from my list + ::sw::WriterListener* pR = pDepend->m_pRight; + ::sw::WriterListener* pL = pDepend->m_pLeft; + if( m_pWriterListeners == pDepend ) + m_pWriterListeners = pL ? pL : pR; + + if( pL ) + pL->m_pRight = pR; + if( pR ) + pR->m_pLeft = pL; + + // update ClientIterators + if(sw::ClientIteratorBase::s_pClientIters) + { + for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer()) + { + if (&rIter.m_rRoot == this && + (rIter.m_pCurrent == pDepend || rIter.m_pPosition == pDepend)) + { + // if object being removed is the current or next object in an + // iterator, advance this iterator + rIter.m_pPosition = pR; + } + } + } + pDepend->m_pLeft = nullptr; + pDepend->m_pRight = nullptr; + pDepend->m_pRegisteredIn = nullptr; + return pDepend; +} + +void SwModify::CheckCaching( const sal_uInt16 nWhich ) +{ + if( isCHRATR( nWhich ) ) + { + SetInSwFntCache( false ); + } + else + { + switch( nWhich ) + { + case RES_OBJECTDYING: + case RES_FMT_CHG: + case RES_ATTRSET_CHG: + SetInSwFntCache( false ); + [[fallthrough]]; + case RES_UL_SPACE: + case RES_LR_SPACE: + case RES_BOX: + case RES_SHADOW: + case RES_FRM_SIZE: + case RES_KEEP: + case RES_BREAK: + if( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + break; + } + } +} + +sw::WriterMultiListener::WriterMultiListener(SwClient& rToTell) + : m_rToTell(rToTell) +{} + +sw::WriterMultiListener::~WriterMultiListener() +{} + +void sw::WriterMultiListener::StartListening(SwModify* pDepend) +{ + EndListening(nullptr); + m_vDepends.emplace_back(ListenerEntry(&m_rToTell, pDepend)); +} + + +bool sw::WriterMultiListener::IsListeningTo(const SwModify* const pBroadcaster) const +{ + return std::any_of(m_vDepends.begin(), m_vDepends.end(), + [&pBroadcaster](const ListenerEntry& aListener) + { + return aListener.GetRegisteredIn() == pBroadcaster; + }); +} + +void sw::WriterMultiListener::EndListening(SwModify* pBroadcaster) +{ + m_vDepends.erase( + std::remove_if( m_vDepends.begin(), m_vDepends.end(), + [&pBroadcaster](const ListenerEntry& aListener) + { + return aListener.GetRegisteredIn() == nullptr || aListener.GetRegisteredIn() == pBroadcaster; + }), + m_vDepends.end()); +} + +void sw::WriterMultiListener::EndListeningAll() +{ + m_vDepends.clear(); +} + +sw::ClientIteratorBase* sw::ClientIteratorBase::s_pClientIters = nullptr; + +void SwModify::CallSwClientNotify( const SfxHint& rHint ) const +{ + SwIterator aIter(*this); + for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next()) + pClient->SwClientNotify( *this, rHint ); +} + +void sw::BroadcastingModify::CallSwClientNotify(const SfxHint& rHint) const +{ + SwModify::CallSwClientNotify(rHint); + const_cast(this)->GetNotifier().Broadcast(rHint); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/cellatr.cxx b/sw/source/core/attr/cellatr.cxx new file mode 100644 index 000000000..a0a459ed8 --- /dev/null +++ b/sw/source/core/attr/cellatr.cxx @@ -0,0 +1,226 @@ +/* -*- 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 + +// The % SV_COUNTRY_LANGUAGE_OFFSET result checks if nFormat is a mere built-in +// @ Text format of *any* locale and if so uses the default text format. Text +// is text, the locale doesn't matter for Writer's number formatting purposes. +// The advantage is that this is the pool's default item value and some places +// benefit from this special treatment in that they don't have to handle/store +// attribute specifics, especially when writing a document. +SwTableBoxNumFormat::SwTableBoxNumFormat( sal_uInt32 nFormat ) + : SfxUInt32Item( RES_BOXATR_FORMAT, + (((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == getSwDefaultTextFormat()) ? + getSwDefaultTextFormat() : nFormat)) +{ +} + +bool SwTableBoxNumFormat::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return GetValue() == static_cast(rAttr).GetValue(); +} + +SwTableBoxNumFormat* SwTableBoxNumFormat::Clone( SfxItemPool* ) const +{ + return new SwTableBoxNumFormat( GetValue() ); +} + +SwTableBoxFormula::SwTableBoxFormula( const OUString& rFormula ) + : SfxPoolItem( RES_BOXATR_FORMULA ), + SwTableFormula( rFormula ), + m_pDefinedIn( nullptr ) +{ +} + +bool SwTableBoxFormula::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return GetFormula() == static_cast(rAttr).GetFormula() && + m_pDefinedIn == static_cast(rAttr).m_pDefinedIn; +} + +SwTableBoxFormula* SwTableBoxFormula::Clone( SfxItemPool* ) const +{ + // switch to external rendering + SwTableBoxFormula* pNew = new SwTableBoxFormula( GetFormula() ); + pNew->SwTableFormula::operator=( *this ); + return pNew; +} + +/** Get node type of the node containing this formula + + E.g. TextField -> TextNode, or + BoxAttribute -> BoxStartNode + + Caution: Must override when inheriting. +*/ +const SwNode* SwTableBoxFormula::GetNodeOfFormula() const +{ + const SwNode* pRet = nullptr; + if( m_pDefinedIn ) + { + SwTableBox* pBox = SwIterator( *m_pDefinedIn ).First(); + if( pBox ) + pRet = pBox->GetSttNd(); + } + return pRet; +} + +SwTableBox* SwTableBoxFormula::GetTableBox() +{ + SwTableBox* pBox = nullptr; + if( m_pDefinedIn ) + pBox = SwIterator( *m_pDefinedIn ).First(); + return pBox; +} + +void SwTableBoxFormula::ChangeState( const SfxPoolItem* pItem ) +{ + if( !m_pDefinedIn ) + return ; + + SwTableFormulaUpdate* pUpdateField; + if( !pItem || RES_TABLEFML_UPDATE != pItem->Which() ) + { + // reset value flag + ChgValid( false ); + return ; + } + + pUpdateField = const_cast(static_cast(pItem)); + + // detect table that contains this attribute + const SwTableNode* pTableNd; + const SwNode* pNd = GetNodeOfFormula(); + if (!pNd || &pNd->GetNodes() != &pNd->GetDoc()->GetNodes()) + return; + pTableNd = pNd->FindTableNode(); + if( pTableNd != nullptr ) + { + switch( pUpdateField->m_eFlags ) + { + case TBL_CALC: + // reset value flag + ChgValid( false ); + break; + case TBL_BOXNAME: + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + // use external rendering + PtrToBoxNm( pUpdateField->m_pTable ); + break; + case TBL_BOXPTR: + // internal rendering + BoxNmToPtr( &pTableNd->GetTable() ); + break; + case TBL_RELBOXNAME: + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + // relative rendering + ToRelBoxNm( pUpdateField->m_pTable ); + break; + + case TBL_SPLITTBL: + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + { + sal_uInt16 nLnPos = SwTableFormula::GetLnPosInTable( + pTableNd->GetTable(), GetTableBox() ); + pUpdateField->m_bBehindSplitLine = USHRT_MAX != nLnPos && + pUpdateField->m_nSplitLine <= nLnPos; + } + else + pUpdateField->m_bBehindSplitLine = false; + [[fallthrough]]; + case TBL_MERGETBL: + if( pUpdateField->m_pHistory ) + { + // for a history record the unchanged formula is needed + SwTableBoxFormula aCopy( *this ); + pUpdateField->m_bModified = false; + ToSplitMergeBoxNm( *pUpdateField ); + + if( pUpdateField->m_bModified ) + { + // external rendering + aCopy.PtrToBoxNm( &pTableNd->GetTable() ); + pUpdateField->m_pHistory->Add( + &aCopy, + &aCopy, + pNd->FindTableBoxStartNode()->GetIndex()); + } + } + else + ToSplitMergeBoxNm( *pUpdateField ); + break; + } + } +} + +void SwTableBoxFormula::Calc( SwTableCalcPara& rCalcPara, double& rValue ) +{ + if( !rCalcPara.m_rCalc.IsCalcError() ) + { + // create pointers from box names + BoxNmToPtr( rCalcPara.m_pTable ); + const OUString sFormula( MakeFormula( rCalcPara )); + if( !rCalcPara.m_rCalc.IsCalcError() ) + rValue = rCalcPara.m_rCalc.Calculate( sFormula ).GetDouble(); + else + rValue = DBL_MAX; + ChgValid( !rCalcPara.IsStackOverflow() ); // value is now valid again + } +} + +SwTableBoxValue::SwTableBoxValue() + : SfxPoolItem( RES_BOXATR_VALUE ), m_nValue( 0 ) +{ +} + +SwTableBoxValue::SwTableBoxValue( const double nVal ) + : SfxPoolItem( RES_BOXATR_VALUE ), m_nValue( nVal ) +{ +} + +bool SwTableBoxValue::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + SwTableBoxValue const& rOther( static_cast(rAttr) ); + // items with NaN should be equal to enable pooling + return std::isnan( m_nValue ) + ? std::isnan( rOther.m_nValue ) + : ( m_nValue == rOther.m_nValue ); +} + +SwTableBoxValue* SwTableBoxValue::Clone( SfxItemPool* ) const +{ + return new SwTableBoxValue( m_nValue ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/fmtfollowtextflow.cxx b/sw/source/core/attr/fmtfollowtextflow.cxx new file mode 100644 index 000000000..8b04869af --- /dev/null +++ b/sw/source/core/attr/fmtfollowtextflow.cxx @@ -0,0 +1,30 @@ +/* -*- 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 + +SwFormatFollowTextFlow* SwFormatFollowTextFlow::Clone( SfxItemPool * ) const +{ + return new SwFormatFollowTextFlow(*this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/fmtwrapinfluenceonobjpos.cxx b/sw/source/core/attr/fmtwrapinfluenceonobjpos.cxx new file mode 100644 index 000000000..21ceec2f0 --- /dev/null +++ b/sw/source/core/attr/fmtwrapinfluenceonobjpos.cxx @@ -0,0 +1,174 @@ +/* -*- 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 + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +SwFormatWrapInfluenceOnObjPos::SwFormatWrapInfluenceOnObjPos( sal_Int16 _nWrapInfluenceOnPosition ) + : SfxPoolItem( RES_WRAP_INFLUENCE_ON_OBJPOS ), + mnWrapInfluenceOnPosition( _nWrapInfluenceOnPosition ) +{ +} + +SwFormatWrapInfluenceOnObjPos::~SwFormatWrapInfluenceOnObjPos() +{ +} + +bool SwFormatWrapInfluenceOnObjPos::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SwFormatWrapInfluenceOnObjPos& rAttribute + = static_cast(rAttr); + return (mnWrapInfluenceOnPosition == rAttribute.GetWrapInfluenceOnObjPos() + && mbAllowOverlap == rAttribute.mbAllowOverlap + && mnOverlapVertOffset == rAttribute.mnOverlapVertOffset); +} + +SwFormatWrapInfluenceOnObjPos* SwFormatWrapInfluenceOnObjPos::Clone( SfxItemPool * ) const +{ + return new SwFormatWrapInfluenceOnObjPos(*this); +} + +bool SwFormatWrapInfluenceOnObjPos::QueryValue( Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + if( nMemberId == MID_WRAP_INFLUENCE ) + { + rVal <<= GetWrapInfluenceOnObjPos(); + } + else if( nMemberId == MID_ALLOW_OVERLAP ) + { + rVal <<= GetAllowOverlap(); + } + else + { + OSL_FAIL( " - unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatWrapInfluenceOnObjPos::PutValue( const Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = false; + + if( nMemberId == MID_WRAP_INFLUENCE ) + { + sal_Int16 nNewWrapInfluence = 0; + rVal >>= nNewWrapInfluence; + // #i35017# - constant names have changed and has been added + if ( nNewWrapInfluence == text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE || + nNewWrapInfluence == text::WrapInfluenceOnPosition::ONCE_CONCURRENT || + nNewWrapInfluence == text::WrapInfluenceOnPosition::ITERATIVE ) + { + SetWrapInfluenceOnObjPos( nNewWrapInfluence ); + bRet = true; + } + else + { + OSL_FAIL( " - invalid attribute value" ); + } + } + else if( nMemberId == MID_ALLOW_OVERLAP ) + { + bool bAllowOverlap = true; + if (rVal >>= bAllowOverlap) + { + SetAllowOverlap(bAllowOverlap); + bRet = true; + } + else + { + SAL_WARN("sw.core", "SwFormatWrapInfluenceOnObjPos::PutValue: invalid AllowOverlap type"); + } + } + else + { + OSL_FAIL( " - unknown MemberId" ); + } + return bRet; +} + +void SwFormatWrapInfluenceOnObjPos::SetWrapInfluenceOnObjPos( sal_Int16 _nWrapInfluenceOnPosition ) +{ + // #i35017# - constant names have changed and consider new value + if ( _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE || + _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ONCE_CONCURRENT || + _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ITERATIVE ) + { + mnWrapInfluenceOnPosition = _nWrapInfluenceOnPosition; + } + else + { + OSL_FAIL( " - invalid attribute value" ); + } +} + +// #i35017# - add parameter <_bIterativeAsOnceConcurrent> to control, if +// value has to be treated as +sal_Int16 SwFormatWrapInfluenceOnObjPos::GetWrapInfluenceOnObjPos( + const bool _bIterativeAsOnceConcurrent ) const +{ + sal_Int16 nWrapInfluenceOnPosition( mnWrapInfluenceOnPosition ); + + if ( _bIterativeAsOnceConcurrent && + nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ITERATIVE ) + { + nWrapInfluenceOnPosition = text::WrapInfluenceOnPosition::ONCE_CONCURRENT; + } + + return nWrapInfluenceOnPosition; +} + +void SwFormatWrapInfluenceOnObjPos::SetAllowOverlap(bool bAllowOverlap) +{ + mbAllowOverlap = bAllowOverlap; +} + +bool SwFormatWrapInfluenceOnObjPos::GetAllowOverlap() const +{ + return mbAllowOverlap; +} + +void SwFormatWrapInfluenceOnObjPos::SetOverlapVertOffset(SwTwips nOverlapVertOffset) +{ + mnOverlapVertOffset = nOverlapVertOffset; +} + +SwTwips SwFormatWrapInfluenceOnObjPos::GetOverlapVertOffset() const { return mnOverlapVertOffset; } + +void SwFormatWrapInfluenceOnObjPos::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatWrapInfluenceOnObjPos")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWrapInfluenceOnPosition"), BAD_CAST(OString::number(mnWrapInfluenceOnPosition).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mbAllowOverlap"), BAD_CAST(OString::boolean(mbAllowOverlap).getStr())); + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/format.cxx b/sw/source/core/attr/format.cxx new file mode 100644 index 000000000..a69dbf125 --- /dev/null +++ b/sw/source/core/attr/format.cxx @@ -0,0 +1,803 @@ +/* -*- 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 //For SwFmt::getIDocumentSettingAccess() +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + + +SwFormat::SwFormat( SwAttrPool& rPool, const char* pFormatNm, + const sal_uInt16* pWhichRanges, SwFormat *pDrvdFrame, + sal_uInt16 nFormatWhich ) : + m_aFormatName( OUString::createFromAscii(pFormatNm) ), + m_aSet( rPool, pWhichRanges ), + m_nWhichId( nFormatWhich ), + m_nPoolFormatId( USHRT_MAX ), + m_nPoolHelpId( USHRT_MAX ), + m_nPoolHlpFileId( UCHAR_MAX ) +{ + m_bAutoUpdateFormat = false; // LAYER_IMPL + m_bAutoFormat = true; + m_bFormatInDTOR = m_bHidden = false; + + if( pDrvdFrame ) + { + pDrvdFrame->Add(this); + m_aSet.SetParent( &pDrvdFrame->m_aSet ); + } +} + +SwFormat::SwFormat( SwAttrPool& rPool, const OUString& rFormatNm, + const sal_uInt16* pWhichRanges, SwFormat* pDrvdFrame, + sal_uInt16 nFormatWhich ) : + m_aFormatName( rFormatNm ), + m_aSet( rPool, pWhichRanges ), + m_nWhichId( nFormatWhich ), + m_nPoolFormatId( USHRT_MAX ), + m_nPoolHelpId( USHRT_MAX ), + m_nPoolHlpFileId( UCHAR_MAX ) +{ + m_bAutoUpdateFormat = false; // LAYER_IMPL + m_bAutoFormat = true; + m_bFormatInDTOR = m_bHidden = false; + + if( pDrvdFrame ) + { + pDrvdFrame->Add(this); + m_aSet.SetParent( &pDrvdFrame->m_aSet ); + } +} + +SwFormat::SwFormat( const SwFormat& rFormat ) : + m_aFormatName( rFormat.m_aFormatName ), + m_aSet( rFormat.m_aSet ), + m_nWhichId( rFormat.m_nWhichId ), + m_nPoolFormatId( rFormat.GetPoolFormatId() ), + m_nPoolHelpId( rFormat.GetPoolHelpId() ), + m_nPoolHlpFileId( rFormat.GetPoolHlpFileId() ) +{ + m_bFormatInDTOR = false; // LAYER_IMPL + m_bAutoFormat = rFormat.m_bAutoFormat; + m_bHidden = rFormat.m_bHidden; + m_bAutoUpdateFormat = rFormat.m_bAutoUpdateFormat; + + if( auto pDerived = rFormat.DerivedFrom() ) + { + pDerived->Add(this); + m_aSet.SetParent( &pDerived->m_aSet ); + } + // a few special treatments for attributes + m_aSet.SetModifyAtAttr( this ); +} + +SwFormat &SwFormat::operator=(const SwFormat& rFormat) +{ + if(this == &rFormat) + return *this; + + m_nWhichId = rFormat.m_nWhichId; + m_nPoolFormatId = rFormat.GetPoolFormatId(); + m_nPoolHelpId = rFormat.GetPoolHelpId(); + m_nPoolHlpFileId = rFormat.GetPoolHlpFileId(); + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + // copy only array with attributes delta + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + m_aSet.Intersect_BC( rFormat.m_aSet, &aOld, &aNew ); + (void)m_aSet.Put_BC( rFormat.m_aSet, &aOld, &aNew ); + + // a few special treatments for attributes + m_aSet.SetModifyAtAttr( this ); + + // create PoolItem attribute for Modify + if( aOld.Count() ) + { + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } + + if(GetRegisteredIn() != rFormat.GetRegisteredIn()) + { + StartListeningToSameModifyAs(rFormat); + m_aSet.SetParent( GetRegisteredIn() + ? &rFormat.m_aSet + : nullptr); + } + + m_bAutoFormat = rFormat.m_bAutoFormat; + m_bHidden = rFormat.m_bHidden; + m_bAutoUpdateFormat = rFormat.m_bAutoUpdateFormat; + return *this; +} + +void SwFormat::SetName( const OUString& rNewName, bool bBroadcast ) +{ + OSL_ENSURE( !IsDefault(), "SetName: Defaultformat" ); + if( bBroadcast ) + { + SwStringMsgPoolItem aOld( RES_NAME_CHANGED, m_aFormatName ); + SwStringMsgPoolItem aNew( RES_NAME_CHANGED, rNewName ); + m_aFormatName = rNewName; + ModifyNotification( &aOld, &aNew ); + } + else + { + m_aFormatName = rNewName; + } +} + +/** Copy attributes + + This function is called in every Copy-Ctor for copying the attributes. + The latter can be only copied as soon as the derived class exists since + for setting them the Which() function is called and that has the default + value of 0 in the base class and is then overridden by the derived class. + + If we copy over multiple documents then the new document has to be provided + in which is defined. Currently this is important for DropCaps + because that contains data that needs to be copied deeply. +*/ +void SwFormat::CopyAttrs( const SwFormat& rFormat ) +{ + // copy only array with attributes delta + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + // special treatments for some attributes + SwAttrSet* pChgSet = const_cast(&rFormat.m_aSet); + + // copy only array with attributes delta + if( pChgSet->GetPool() != m_aSet.GetPool() ) + pChgSet->CopyToModify( *this ); + else + { + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + + if ( m_aSet.Put_BC( *pChgSet, &aOld, &aNew ) ) + { + // a few special treatments for attributes + m_aSet.SetModifyAtAttr( this ); + + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } + } + + if( pChgSet != &rFormat.m_aSet ) // was a Set created? + delete pChgSet; +} + +SwFormat::~SwFormat() +{ + // This happens at an ObjectDying message. Thus put all dependent + // ones on DerivedFrom. + if( HasWriterListeners() ) + { + m_bFormatInDTOR = true; + + SwFormat* pParentFormat = DerivedFrom(); + if( !pParentFormat ) + { + SAL_WARN( + "sw.core", + "~SwFormat: parent format missing from: " << GetName() ); + } + else + { + SwFormatChg aOldFormat( this ); + SwFormatChg aNewFormat( pParentFormat ); + SwIterator aIter(*this); + for(SwClient* pClient = aIter.First(); pClient && pParentFormat; pClient = aIter.Next()) + { + SAL_INFO("sw.core", "reparenting " << typeid(*pClient).name() << " at " << pClient << " from " << typeid(*this).name() << " at " << this << " to " << typeid(*pParentFormat).name() << " at " << pParentFormat); + pParentFormat->Add( pClient ); + pClient->ModifyNotification( &aOldFormat, &aNewFormat ); + } + } + } +} + +void SwFormat::Modify( const SfxPoolItem* pOldValue, const SfxPoolItem* pNewValue ) +{ + bool bContinue = true; // true = pass on to dependent ones + + sal_uInt16 nWhich = pOldValue ? pOldValue->Which() : + pNewValue ? pNewValue->Which() : 0 ; + switch( nWhich ) + { + case 0: break; // Which-Id of 0? + + case RES_OBJECTDYING: + if (pNewValue) + { + // If the dying object is the parent format of this format so + // attach this to the parent of the parent + SwFormat* pFormat = static_cast(static_cast(pNewValue)->pObject); + + // do not move if this is the topmost format + if( GetRegisteredIn() && GetRegisteredIn() == pFormat ) + { + if( pFormat->GetRegisteredIn() ) + { + // if parent so register in new parent + pFormat->DerivedFrom()->Add( this ); + m_aSet.SetParent( &DerivedFrom()->m_aSet ); + } + else + { + // otherwise de-register at least from dying one + EndListeningAll(); + m_aSet.SetParent( nullptr ); + } + } + } + break; + case RES_ATTRSET_CHG: + if (pOldValue && pNewValue && static_cast(pOldValue)->GetTheChgdSet() != &m_aSet) + { + // pass only those that are not set + SwAttrSetChg aOld( *static_cast(pOldValue) ); + SwAttrSetChg aNew( *static_cast(pNewValue) ); + + aOld.GetChgSet()->Differentiate( m_aSet ); + aNew.GetChgSet()->Differentiate( m_aSet ); + + if( aNew.Count() ) + NotifyClients( &aOld, &aNew ); + bContinue = false; + } + break; + case RES_FMT_CHG: + // if the format parent will be moved so register my attribute set at + // the new one + + // skip my own Modify + if ( pOldValue && pNewValue && + static_cast(pOldValue)->pChangedFormat != this && + static_cast(pNewValue)->pChangedFormat == GetRegisteredIn() ) + { + // attach Set to new parent + m_aSet.SetParent( DerivedFrom() ? &DerivedFrom()->m_aSet : nullptr ); + } + break; + default: + { + // attribute is defined in this format + if( SfxItemState::SET == m_aSet.GetItemState( nWhich, false )) + { + // DropCaps might come into this block + OSL_ENSURE( RES_PARATR_DROP == nWhich, "Modify was sent without sender" ); + bContinue = false; + } + } + } + + if( bContinue ) + { + // walk over all dependent formats + NotifyClients( pOldValue, pNewValue ); + } +} + +bool SwFormat::SetDerivedFrom(SwFormat *pDerFrom) +{ + if ( pDerFrom ) + { + const SwFormat* pFormat = pDerFrom; + while ( pFormat != nullptr ) + { + if ( pFormat == this ) + return false; + + pFormat=pFormat->DerivedFrom(); + } + } + else + { + // nothing provided, search for Dflt format + pDerFrom = this; + while ( pDerFrom->DerivedFrom() ) + pDerFrom = pDerFrom->DerivedFrom(); + } + if ( (pDerFrom == DerivedFrom()) || (pDerFrom == this) ) + return false; + + assert( Which()==pDerFrom->Which() + || (Which()==RES_CONDTXTFMTCOLL && pDerFrom->Which()==RES_TXTFMTCOLL) + || (Which()==RES_TXTFMTCOLL && pDerFrom->Which()==RES_CONDTXTFMTCOLL) + || (Which()==RES_FLYFRMFMT && pDerFrom->Which()==RES_FRMFMT) + ); + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + pDerFrom->Add( this ); + m_aSet.SetParent( &pDerFrom->m_aSet ); + + SwFormatChg aOldFormat( this ); + SwFormatChg aNewFormat( this ); + ModifyNotification( &aOldFormat, &aNewFormat ); + + return true; +} + +bool SwFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +const SfxPoolItem& SwFormat::GetFormatAttr( sal_uInt16 nWhich, bool bInParents ) const +{ + if (RES_BACKGROUND == nWhich && supportsFullDrawingLayerFillAttributeSet()) + { + // FALLBACKBREAKHERE should not be used; instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] + SAL_INFO("sw.core", "Do no longer use SvxBrushItem, instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] FillAttributes or makeBackgroundBrushItem (simple fallback is in place and used)"); + static std::unique_ptr aSvxBrushItem; //(std::make_shared(RES_BACKGROUND)); + + // fill the local static SvxBrushItem from the current ItemSet so that + // the fill attributes [XATTR_FILL_FIRST .. XATTR_FILL_LAST] are used + // as good as possible to create a fallback representation and return that + aSvxBrushItem = getSvxBrushItemFromSourceSet(m_aSet, RES_BACKGROUND, bInParents); + + return *aSvxBrushItem; + } + + return m_aSet.Get( nWhich, bInParents ); +} + +SfxItemState SwFormat::GetItemState( sal_uInt16 nWhich, bool bSrchInParent, const SfxPoolItem **ppItem ) const +{ + if (RES_BACKGROUND == nWhich && supportsFullDrawingLayerFillAttributeSet()) + { + // FALLBACKBREAKHERE should not be used; instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] + SAL_INFO("sw.core", "Do no longer use SvxBrushItem, instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] FillAttributes or SwFormat::GetBackgroundStat (simple fallback is in place and used)"); + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFill = getSdrAllFillAttributesHelper(); + + // check if the new fill attributes are used + if(aFill && aFill->isUsed()) + { + // if yes, fill the local SvxBrushItem using the new fill attributes + // as good as possible to have an instance for the pointer to point + // to and return as state that it is set + static std::unique_ptr aSvxBrushItem; //(RES_BACKGROUND); + + aSvxBrushItem = getSvxBrushItemFromSourceSet(m_aSet, RES_BACKGROUND, bSrchInParent); + if( ppItem ) + *ppItem = aSvxBrushItem.get(); + + return SfxItemState::SET; + } + + // if not, reset pointer and return SfxItemState::DEFAULT to signal that + // the item is not set + if( ppItem ) + *ppItem = nullptr; + + return SfxItemState::DEFAULT; + } + + return m_aSet.GetItemState( nWhich, bSrchInParent, ppItem ); +} + +SfxItemState SwFormat::GetBackgroundState(std::unique_ptr& rItem) const +{ + if (supportsFullDrawingLayerFillAttributeSet()) + { + // FALLBACKBREAKHERE should not be used; instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFill = getSdrAllFillAttributesHelper(); + + // check if the new fill attributes are used + if(aFill && aFill->isUsed()) + { + // if yes, fill the local SvxBrushItem using the new fill attributes + // as good as possible to have an instance for the pointer to point + // to and return as state that it is set + rItem = getSvxBrushItemFromSourceSet(m_aSet, RES_BACKGROUND); + return SfxItemState::SET; + } + + // if not return SfxItemState::DEFAULT to signal that the item is not set + return SfxItemState::DEFAULT; + } + + const SfxPoolItem* pItem = nullptr; + SfxItemState eRet = m_aSet.GetItemState(RES_BACKGROUND, true, &pItem); + if (pItem) + rItem.reset(static_cast(pItem->Clone())); + return eRet; +} + +bool SwFormat::SetFormatAttr( const SfxPoolItem& rAttr ) +{ + if ( IsInCache() || IsInSwFntCache() ) + { + const sal_uInt16 nWhich = rAttr.Which(); + CheckCaching( nWhich ); + } + + bool bRet = false; + + if (RES_BACKGROUND == rAttr.Which() && supportsFullDrawingLayerFillAttributeSet()) + { + // FALLBACKBREAKHERE should not be used; instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] + SAL_INFO("sw.core", "Do no longer use SvxBrushItem, instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] FillAttributes (simple fallback is in place and used)"); + SfxItemSet aTempSet(*m_aSet.GetPool(), svl::Items{}); + const SvxBrushItem& rSource = static_cast< const SvxBrushItem& >(rAttr); + + // fill a local ItemSet with the attributes corresponding as good as possible + // to the new fill properties [XATTR_FILL_FIRST .. XATTR_FILL_LAST] and set these + // as ItemSet + setSvxBrushItemAsFillAttributesToTargetSet(rSource, aTempSet); + + if(IsModifyLocked()) + { + bRet = m_aSet.Put( aTempSet ); + if( bRet ) + { + m_aSet.SetModifyAtAttr( this ); + } + } + else + { + SwAttrSet aOld(*m_aSet.GetPool(), m_aSet.GetRanges()), aNew(*m_aSet.GetPool(), m_aSet.GetRanges()); + + bRet = m_aSet.Put_BC(aTempSet, &aOld, &aNew); + + if(bRet) + { + m_aSet.SetModifyAtAttr(this); + + SwAttrSetChg aChgOld(m_aSet, aOld); + SwAttrSetChg aChgNew(m_aSet, aNew); + + ModifyNotification(&aChgOld, &aChgNew); + } + } + + return bRet; + } + + // if Modify is locked then no modifications will be sent; + // but call Modify always for FrameFormats + const sal_uInt16 nFormatWhich = Which(); + if( IsModifyLocked() || + ( !HasWriterListeners() && + (RES_GRFFMTCOLL == nFormatWhich || + RES_TXTFMTCOLL == nFormatWhich ) ) ) + { + bRet = nullptr != m_aSet.Put( rAttr ); + if( bRet ) + m_aSet.SetModifyAtAttr( this ); + // #i71574# + if ( nFormatWhich == RES_TXTFMTCOLL && rAttr.Which() == RES_PARATR_NUMRULE ) + { + TextFormatCollFunc::CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( this ); + } + } + else + { + // copy only array with attributes delta + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + + bRet = m_aSet.Put_BC( rAttr, &aOld, &aNew ); + if( bRet ) + { + // some special treatments for attributes + m_aSet.SetModifyAtAttr( this ); + + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } + } + return bRet; +} + +bool SwFormat::SetFormatAttr( const SfxItemSet& rSet ) +{ + if( !rSet.Count() ) + return false; + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + bool bRet = false; + + // Use local copy to be able to apply needed changes, e.g. call + // CheckForUniqueItemForLineFillNameOrIndex which is needed for NameOrIndex stuff + SfxItemSet aTempSet(rSet); + + // Need to check for unique item for DrawingLayer items of type NameOrIndex + // and evtl. correct that item to ensure unique names for that type. This call may + // modify/correct entries inside of the given SfxItemSet + if(GetDoc()) + { + GetDoc()->CheckForUniqueItemForLineFillNameOrIndex(aTempSet); + } + + if (supportsFullDrawingLayerFillAttributeSet()) + { + const SfxPoolItem* pSource = nullptr; + + if(SfxItemState::SET == aTempSet.GetItemState(RES_BACKGROUND, false, &pSource)) + { + // FALLBACKBREAKHERE should not be used; instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] + SAL_INFO("sw.core", "Do no longer use SvxBrushItem, instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] FillAttributes (simple fallback is in place and used)"); + + // copy all items to be set anyways to a local ItemSet with is also prepared for the new + // fill attribute ranges [XATTR_FILL_FIRST .. XATTR_FILL_LAST]. Add the attributes + // corresponding as good as possible to the new fill properties and set the whole ItemSet + const SvxBrushItem& rSource(static_cast< const SvxBrushItem& >(*pSource)); + setSvxBrushItemAsFillAttributesToTargetSet(rSource, aTempSet); + + if(IsModifyLocked()) + { + bRet = m_aSet.Put( aTempSet ); + if( bRet ) + { + m_aSet.SetModifyAtAttr( this ); + } + } + else + { + SwAttrSet aOld(*m_aSet.GetPool(), m_aSet.GetRanges()), aNew(*m_aSet.GetPool(), m_aSet.GetRanges()); + + bRet = m_aSet.Put_BC(aTempSet, &aOld, &aNew); + + if(bRet) + { + m_aSet.SetModifyAtAttr(this); + + SwAttrSetChg aChgOld(m_aSet, aOld); + SwAttrSetChg aChgNew(m_aSet, aNew); + + ModifyNotification(&aChgOld, &aChgNew); + } + } + + return bRet; + } + } + + // if Modify is locked then no modifications will be sent; + // but call Modify always for FrameFormats + const sal_uInt16 nFormatWhich = Which(); + if ( IsModifyLocked() || + ( !HasWriterListeners() && + ( RES_GRFFMTCOLL == nFormatWhich || + RES_TXTFMTCOLL == nFormatWhich ) ) ) + { + bRet = m_aSet.Put( aTempSet ); + if( bRet ) + m_aSet.SetModifyAtAttr( this ); + // #i71574# + if ( nFormatWhich == RES_TXTFMTCOLL ) + { + TextFormatCollFunc::CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( this ); + } + } + else + { + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + bRet = m_aSet.Put_BC( aTempSet, &aOld, &aNew ); + if( bRet ) + { + // some special treatments for attributes + m_aSet.SetModifyAtAttr( this ); + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } + } + return bRet; +} + +// remove Hint using nWhich from array with delta +bool SwFormat::ResetFormatAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 ) +{ + if( !m_aSet.Count() ) + return false; + + if( !nWhich2 || nWhich2 < nWhich1 ) + nWhich2 = nWhich1; // then set to 1st ID, only this item + + if ( IsInCache() || IsInSwFntCache() ) + { + for( sal_uInt16 n = nWhich1; n < nWhich2; ++n ) + CheckCaching( n ); + } + + // if Modify is locked then no modifications will be sent + if( IsModifyLocked() ) + return 0 != (( nWhich2 == nWhich1 ) + ? m_aSet.ClearItem( nWhich1 ) + : m_aSet.ClearItem_BC( nWhich1, nWhich2 )); + + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + bool bRet = 0 != m_aSet.ClearItem_BC( nWhich1, nWhich2, &aOld, &aNew ); + if( bRet ) + { + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } + return bRet; +} + +// #i73790# +sal_uInt16 SwFormat::ResetAllFormatAttr() +{ + if( !m_aSet.Count() ) + return 0; + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + // if Modify is locked then no modifications will be sent + if( IsModifyLocked() ) + return m_aSet.ClearItem(); + + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + bool bRet = 0 != m_aSet.ClearItem_BC( 0, &aOld, &aNew ); + if( bRet ) + { + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } + return aNew.Count(); +} + +void SwFormat::DelDiffs( const SfxItemSet& rSet ) +{ + if( !m_aSet.Count() ) + return; + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + // if Modify is locked then no modifications will be sent + if( IsModifyLocked() ) + { + m_aSet.Intersect( rSet ); + return; + } + + SwAttrSet aOld( *m_aSet.GetPool(), m_aSet.GetRanges() ), + aNew( *m_aSet.GetPool(), m_aSet.GetRanges() ); + bool bRet = 0 != m_aSet.Intersect_BC( rSet, &aOld, &aNew ); + if( bRet ) + { + SwAttrSetChg aChgOld( m_aSet, aOld ); + SwAttrSetChg aChgNew( m_aSet, aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // send all modified ones + } +} + +/** SwFormat::IsBackgroundTransparent + + Virtual method to determine, if background of format is transparent. + Default implementation returns false. Thus, subclasses have to override + method, if the specific subclass can have a transparent background. + + @return false, default implementation +*/ +bool SwFormat::IsBackgroundTransparent() const +{ + return false; +} + +/* + * Document Interface Access + */ +const IDocumentSettingAccess& SwFormat::getIDocumentSettingAccess() const { return GetDoc()->GetDocumentSettingManager(); } +const IDocumentDrawModelAccess& SwFormat::getIDocumentDrawModelAccess() const { return GetDoc()->getIDocumentDrawModelAccess(); } +IDocumentDrawModelAccess& SwFormat::getIDocumentDrawModelAccess() { return GetDoc()->getIDocumentDrawModelAccess(); } +const IDocumentLayoutAccess& SwFormat::getIDocumentLayoutAccess() const { return GetDoc()->getIDocumentLayoutAccess(); } +IDocumentLayoutAccess& SwFormat::getIDocumentLayoutAccess() { return GetDoc()->getIDocumentLayoutAccess(); } +IDocumentTimerAccess& SwFormat::getIDocumentTimerAccess() { return GetDoc()->getIDocumentTimerAccess(); } +IDocumentFieldsAccess& SwFormat::getIDocumentFieldsAccess() { return GetDoc()->getIDocumentFieldsAccess(); } +IDocumentChartDataProviderAccess& SwFormat::getIDocumentChartDataProviderAccess() { return GetDoc()->getIDocumentChartDataProviderAccess(); } + +void SwFormat::GetGrabBagItem(uno::Any& rVal) const +{ + if (m_pGrabBagItem) + m_pGrabBagItem->QueryValue(rVal); + else + rVal <<= uno::Sequence(); +} + +void SwFormat::SetGrabBagItem(const uno::Any& rVal) +{ + if (!m_pGrabBagItem) + m_pGrabBagItem = std::make_shared(); + + m_pGrabBagItem->PutValue(rVal, 0); +} + +std::unique_ptr SwFormat::makeBackgroundBrushItem(bool bInP) const +{ + if (supportsFullDrawingLayerFillAttributeSet()) + { + // FALLBACKBREAKHERE should not be used; instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] + SAL_INFO("sw.core", "Do no longer use SvxBrushItem, instead use [XATTR_FILL_FIRST .. XATTR_FILL_LAST] FillAttributes (simple fallback is in place and used)"); + + // fill the local static SvxBrushItem from the current ItemSet so that + // the fill attributes [XATTR_FILL_FIRST .. XATTR_FILL_LAST] are used + // as good as possible to create a fallback representation and return that + return getSvxBrushItemFromSourceSet(m_aSet, RES_BACKGROUND, bInP); + } + + return std::unique_ptr(m_aSet.GetBackground(bInP).Clone()); +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwFormat::getSdrAllFillAttributesHelper() const +{ + return drawinglayer::attribute::SdrAllFillAttributesHelperPtr(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/hints.cxx b/sw/source/core/attr/hints.cxx new file mode 100644 index 000000000..b8d626e1b --- /dev/null +++ b/sw/source/core/attr/hints.cxx @@ -0,0 +1,270 @@ +/* -*- 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 + +SwFormatChg::SwFormatChg( SwFormat* pFormat ) + : SwMsgPoolItem( RES_FMT_CHG ), pChangedFormat( pFormat ) +{ +} + +SwInsText::SwInsText( sal_Int32 nP, sal_Int32 nL ) + : SwMsgPoolItem( RES_INS_TXT ), nPos( nP ), nLen( nL ) +{ +} + +SwDelChr::SwDelChr( sal_Int32 nP ) + : SwMsgPoolItem( RES_DEL_CHR ), nPos( nP ) +{ +} + +SwDelText::SwDelText( sal_Int32 nS, sal_Int32 nL ) + : SwMsgPoolItem( RES_DEL_TXT ), nStart( nS ), nLen( nL ) +{ +} + +namespace sw { + +MoveText::MoveText(SwTextNode *const pD, sal_Int32 const nD, sal_Int32 const nS, sal_Int32 const nL) + : pDestNode(pD), nDestStart(nD), nSourceStart(nS), nLen(nL) +{ +} + +RedlineDelText::RedlineDelText(sal_Int32 const nS, sal_Int32 const nL) + : nStart(nS), nLen(nL) +{ +} + +RedlineUnDelText::RedlineUnDelText(sal_Int32 const nS, sal_Int32 const nL) + : nStart(nS), nLen(nL) +{ +} + +} // namespace sw + +SwUpdateAttr::SwUpdateAttr( sal_Int32 nS, sal_Int32 nE, sal_uInt16 nW ) + : SwMsgPoolItem( RES_UPDATE_ATTR ), m_nStart( nS ), m_nEnd( nE ), m_nWhichAttr( nW ) +{ +} + +SwUpdateAttr::SwUpdateAttr( sal_Int32 nS, sal_Int32 nE, sal_uInt16 nW, std::vector aW ) + : SwMsgPoolItem( RES_UPDATE_ATTR ), m_nStart( nS ), m_nEnd( nE ), m_nWhichAttr( nW ), m_aWhichFmtAttrs( aW ) +{ +} + +SwRefMarkFieldUpdate::SwRefMarkFieldUpdate( OutputDevice* pOutput ) + : SwMsgPoolItem( RES_REFMARKFLD_UPDATE ), + pOut( pOutput ) +{ + OSL_ENSURE( pOut, "No OutputDevice pointer" ); +} + +SwDocPosUpdate::SwDocPosUpdate( const SwTwips nDcPos ) + : SwMsgPoolItem( RES_DOCPOS_UPDATE ), nDocPos(nDcPos) +{ +} + +SwTableFormulaUpdate::SwTableFormulaUpdate( const SwTable* pNewTable ) + : SwMsgPoolItem( RES_TABLEFML_UPDATE ), + m_pTable( pNewTable ), m_pHistory( nullptr ), m_nSplitLine( USHRT_MAX ), + m_eFlags( TBL_CALC ) +{ + m_aData.pDelTable = nullptr; + m_bModified = m_bBehindSplitLine = false; + OSL_ENSURE( m_pTable, "No Table pointer" ); +} + +SwAutoFormatGetDocNode::SwAutoFormatGetDocNode( const SwNodes* pNds ) + : SwMsgPoolItem( RES_AUTOFMT_DOCNODE ), pNodes( pNds ) +{ +} + +SwAttrSetChg::SwAttrSetChg( const SwAttrSet& rTheSet, SwAttrSet& rSet ) + : SwMsgPoolItem( RES_ATTRSET_CHG ), + m_bDelSet( false ), + m_pChgSet( &rSet ), + m_pTheChgdSet( &rTheSet ) +{ +} + +SwAttrSetChg::SwAttrSetChg( const SwAttrSetChg& rChgSet ) + : SwMsgPoolItem( RES_ATTRSET_CHG ), + m_bDelSet( true ), + m_pTheChgdSet( rChgSet.m_pTheChgdSet ) +{ + m_pChgSet = new SwAttrSet( *rChgSet.m_pChgSet ); +} + +SwAttrSetChg::~SwAttrSetChg() +{ + if( m_bDelSet ) + delete m_pChgSet; +} + +#ifdef DBG_UTIL +void SwAttrSetChg::ClearItem( sal_uInt16 nWhch ) +{ + OSL_ENSURE( m_bDelSet, "The Set may not be changed!" ); + m_pChgSet->ClearItem( nWhch ); +} +#endif + +SwMsgPoolItem::SwMsgPoolItem( sal_uInt16 nWhch ) + : SfxPoolItem( nWhch ) +{ +} + +bool SwMsgPoolItem::operator==( const SfxPoolItem& ) const +{ + assert( false && "SwMsgPoolItem knows no ==" ); + return false; +} + +SwMsgPoolItem* SwMsgPoolItem::Clone( SfxItemPool* ) const +{ + OSL_FAIL( "SwMsgPoolItem knows no Clone" ); + return nullptr; +} + +#if OSL_DEBUG_LEVEL > 0 +const SfxPoolItem* GetDfltAttr( sal_uInt16 nWhich ) +{ + OSL_ASSERT( nWhich < POOLATTR_END && nWhich >= POOLATTR_BEGIN ); + + SfxPoolItem *pHt = aAttrTab[ nWhich - POOLATTR_BEGIN ]; + OSL_ENSURE( pHt, "GetDfltFormatAttr(): Dflt == 0" ); + return pHt; +} +#else +const SfxPoolItem* GetDfltAttr( sal_uInt16 nWhich ) +{ + return aAttrTab[ nWhich - POOLATTR_BEGIN ]; +} +#endif + +SwCondCollCondChg::SwCondCollCondChg( SwFormat *pFormat ) + : SwMsgPoolItem( RES_CONDCOLL_CONDCHG ), pChangedFormat( pFormat ) +{ +} + +SwVirtPageNumInfo::SwVirtPageNumInfo( const SwPageFrame *pPg ) : + SwMsgPoolItem( RES_VIRTPAGENUM_INFO ), m_pPage( nullptr ), m_pOrigPage( pPg ), m_pFrame( nullptr ) +{ +} + +SwFindNearestNode::SwFindNearestNode( const SwNode& rNd ) + : SwMsgPoolItem( RES_FINDNEARESTNODE ), m_pNode( &rNd ), m_pFound( nullptr ) +{ +} + +void SwFindNearestNode::CheckNode( const SwNode& rNd ) +{ + if( &m_pNode->GetNodes() == &rNd.GetNodes() ) + { + sal_uLong nIdx = rNd.GetIndex(); + if( nIdx < m_pNode->GetIndex() && + ( !m_pFound || nIdx > m_pFound->GetIndex() ) && + nIdx > rNd.GetNodes().GetEndOfExtras().GetIndex() ) + m_pFound = &rNd; + } +} + +sal_uInt16 GetWhichOfScript( sal_uInt16 nWhich, sal_uInt16 nScript ) +{ + static const sal_uInt16 aLangMap[3] = + { RES_CHRATR_LANGUAGE, RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE }; + static const sal_uInt16 aFontMap[3] = + { RES_CHRATR_FONT, RES_CHRATR_CJK_FONT, RES_CHRATR_CTL_FONT}; + static const sal_uInt16 aFontSizeMap[3] = + { RES_CHRATR_FONTSIZE, RES_CHRATR_CJK_FONTSIZE, RES_CHRATR_CTL_FONTSIZE }; + static const sal_uInt16 aWeightMap[3] = + { RES_CHRATR_WEIGHT, RES_CHRATR_CJK_WEIGHT, RES_CHRATR_CTL_WEIGHT}; + static const sal_uInt16 aPostureMap[3] = + { RES_CHRATR_POSTURE, RES_CHRATR_CJK_POSTURE, RES_CHRATR_CTL_POSTURE}; + + const sal_uInt16* pM; + switch( nWhich ) + { + case RES_CHRATR_LANGUAGE: + case RES_CHRATR_CJK_LANGUAGE: + case RES_CHRATR_CTL_LANGUAGE: + pM = aLangMap; + break; + + case RES_CHRATR_FONT: + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CTL_FONT: + pM = aFontMap; + break; + + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CTL_FONTSIZE: + pM = aFontSizeMap; + break; + + case RES_CHRATR_WEIGHT: + case RES_CHRATR_CJK_WEIGHT: + case RES_CHRATR_CTL_WEIGHT: + pM = aWeightMap; + break; + + case RES_CHRATR_POSTURE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CTL_POSTURE: + pM = aPostureMap; + break; + + default: + pM = nullptr; + } + + sal_uInt16 nRet; + if( pM ) + { + using namespace ::com::sun::star; + { + if( i18n::ScriptType::WEAK == nScript ) + nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + switch( nScript) + { + case i18n::ScriptType::COMPLEX: + ++pM; + [[fallthrough]]; + case i18n::ScriptType::ASIAN: + ++pM; + [[fallthrough]]; + default: + nRet = *pM; + } + } + } + else + nRet = nWhich; + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/swatrset.cxx b/sw/source/core/attr/swatrset.cxx new file mode 100644 index 000000000..aa6d9f1ad --- /dev/null +++ b/sw/source/core/attr/swatrset.cxx @@ -0,0 +1,481 @@ +/* -*- 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 + +SwAttrPool::SwAttrPool( SwDoc* pD ) + : SfxItemPool( "SWG", + POOLATTR_BEGIN, POOLATTR_END-1, + aSlotTab, &aAttrTab ), + m_pDoc( pD ) +{ + // create secondary pools immediately + createAndAddSecondaryPools(); +} + +SwAttrPool::~SwAttrPool() +{ + // cleanup secondary pools first + removeAndDeleteSecondaryPools(); +} + +void SwAttrPool::createAndAddSecondaryPools() +{ + const SfxItemPool* pCheckAlreadySet = GetSecondaryPool(); + + if(pCheckAlreadySet) + { + OSL_ENSURE(false, "SwAttrPool already has a secondary pool (!)"); + return; + } + + // create SfxItemPool and EditEngine pool and add these in a chain. These + // belong us and will be removed/destroyed in removeAndDeleteSecondaryPools() used from + // the destructor + SfxItemPool *pSdrPool = new SdrItemPool(this); + + // #75371# change DefaultItems for the SdrEdgeObj distance items + // to TWIPS. + // 1/100th mm in twips + const long nDefEdgeDist = (500 * 72) / 127; + + pSdrPool->SetPoolDefaultItem(SdrEdgeNode1HorzDistItem(nDefEdgeDist)); + pSdrPool->SetPoolDefaultItem(SdrEdgeNode1VertDistItem(nDefEdgeDist)); + pSdrPool->SetPoolDefaultItem(SdrEdgeNode2HorzDistItem(nDefEdgeDist)); + pSdrPool->SetPoolDefaultItem(SdrEdgeNode2VertDistItem(nDefEdgeDist)); + + // #i33700# // Set shadow distance defaults as PoolDefaultItems + pSdrPool->SetPoolDefaultItem(makeSdrShadowXDistItem((300 * 72) / 127)); + pSdrPool->SetPoolDefaultItem(makeSdrShadowYDistItem((300 * 72) / 127)); + + SfxItemPool *pEEgPool = EditEngine::CreatePool(); + + pSdrPool->SetSecondaryPool(pEEgPool); + + if(!GetFrozenIdRanges()) + { + FreezeIdRanges(); + } + else + { + pSdrPool->FreezeIdRanges(); + } +} + +void SwAttrPool::removeAndDeleteSecondaryPools() +{ + SfxItemPool *pSdrPool = GetSecondaryPool(); + + if(!pSdrPool) + { + OSL_ENSURE(false, "SwAttrPool has no secondary pool, it's missing (!)"); + return; + } + + SfxItemPool *pEEgPool = pSdrPool->GetSecondaryPool(); + + if(!pEEgPool) + { + OSL_ENSURE(false, "i don't accept additional pools"); + return; + } + + // first delete the items, then break the linking + pSdrPool->Delete(); + + SetSecondaryPool(nullptr); + pSdrPool->SetSecondaryPool(nullptr); + + // final cleanup of secondary pool(s) + SfxItemPool::Free(pSdrPool); + SfxItemPool::Free(pEEgPool); +} + +SwAttrSet::SwAttrSet( SwAttrPool& rPool, sal_uInt16 nWh1, sal_uInt16 nWh2 ) + : SfxItemSet( rPool, {{nWh1, nWh2}} ), m_pOldSet( nullptr ), m_pNewSet( nullptr ) +{ +} + +SwAttrSet::SwAttrSet( SwAttrPool& rPool, const sal_uInt16* nWhichPairTable ) + : SfxItemSet( rPool, nWhichPairTable ), m_pOldSet( nullptr ), m_pNewSet( nullptr ) +{ +} + +SwAttrSet::SwAttrSet( const SwAttrSet& rSet ) + : SfxItemSet( rSet ), m_pOldSet( nullptr ), m_pNewSet( nullptr ) +{ +} + +std::unique_ptr SwAttrSet::Clone( bool bItems, SfxItemPool *pToPool ) const +{ + if ( pToPool && pToPool != GetPool() ) + { + SwAttrPool* pAttrPool = dynamic_cast< SwAttrPool* >(pToPool); + std::unique_ptr pTmpSet; + if ( !pAttrPool ) + pTmpSet = SfxItemSet::Clone( bItems, pToPool ); + else + { + pTmpSet.reset(new SwAttrSet( *pAttrPool, GetRanges() )); + if ( bItems ) + { + SfxWhichIter aIter(*pTmpSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + const SfxPoolItem* pItem; + if ( SfxItemState::SET == GetItemState( nWhich, false, &pItem ) ) + pTmpSet->Put( *pItem ); + nWhich = aIter.NextWhich(); + } + } + } + return pTmpSet; + } + else + return std::unique_ptr( + bItems + ? new SwAttrSet( *this ) + : new SwAttrSet( *GetPool(), GetRanges() )); +} + +bool SwAttrSet::Put_BC( const SfxPoolItem& rAttr, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + m_pNewSet = pNew; + m_pOldSet = pOld; + bool bRet = nullptr != SfxItemSet::Put( rAttr ); + m_pOldSet = m_pNewSet = nullptr; + return bRet; +} + +bool SwAttrSet::Put_BC( const SfxItemSet& rSet, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + m_pNewSet = pNew; + m_pOldSet = pOld; + bool bRet = SfxItemSet::Put( rSet ); + m_pOldSet = m_pNewSet = nullptr; + return bRet; +} + +sal_uInt16 SwAttrSet::ClearItem_BC( sal_uInt16 nWhich, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + m_pNewSet = pNew; + m_pOldSet = pOld; + sal_uInt16 nRet = SfxItemSet::ClearItem( nWhich ); + m_pOldSet = m_pNewSet = nullptr; + return nRet; +} + +sal_uInt16 SwAttrSet::ClearItem_BC( sal_uInt16 nWhich1, sal_uInt16 nWhich2, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + OSL_ENSURE( nWhich1 <= nWhich2, "no valid range" ); + m_pNewSet = pNew; + m_pOldSet = pOld; + sal_uInt16 nRet = 0; + for( ; nWhich1 <= nWhich2; ++nWhich1 ) + nRet = nRet + SfxItemSet::ClearItem( nWhich1 ); + m_pOldSet = m_pNewSet = nullptr; + return nRet; +} + +int SwAttrSet::Intersect_BC( const SfxItemSet& rSet, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + m_pNewSet = pNew; + m_pOldSet = pOld; + SfxItemSet::Intersect( rSet ); + m_pOldSet = m_pNewSet = nullptr; + return pNew ? pNew->Count() : ( pOld ? pOld->Count() : 0 ); +} + +/// Notification callback +void SwAttrSet::Changed( const SfxPoolItem& rOld, const SfxPoolItem& rNew ) +{ + if( m_pOldSet ) + m_pOldSet->PutChgd( rOld ); + if( m_pNewSet ) + m_pNewSet->PutChgd( rNew ); +} + +/** special treatment for some attributes + + Set the Modify pointer (old pDefinedIn) for the following attributes: + - SwFormatDropCaps + - SwFormatPageDesc + + (Is called at inserts into formats/nodes) +*/ +bool SwAttrSet::SetModifyAtAttr( const SwModify* pModify ) +{ + bool bSet = false; + + const SfxPoolItem* pItem; + if( SfxItemState::SET == GetItemState( RES_PAGEDESC, false, &pItem ) && + static_cast(pItem)->GetDefinedIn() != pModify ) + { + const_cast(static_cast(pItem))->ChgDefinedIn( pModify ); + bSet = true; + } + + if( SfxItemState::SET == GetItemState( RES_PARATR_DROP, false, &pItem ) && + static_cast(pItem)->GetDefinedIn() != pModify ) + { + // If CharFormat is set and it is set in different attribute pools then + // the CharFormat has to be copied. + SwCharFormat* pCharFormat = const_cast(static_cast(pItem))->GetCharFormat(); + if( pCharFormat && GetPool() != pCharFormat->GetAttrSet().GetPool() ) + { + pCharFormat = GetDoc()->CopyCharFormat( *pCharFormat ); + const_cast(static_cast(pItem))->SetCharFormat( pCharFormat ); + } + const_cast(static_cast(pItem))->ChgDefinedIn( pModify ); + bSet = true; + } + + if( SfxItemState::SET == GetItemState( RES_BOXATR_FORMULA, false, &pItem ) && + static_cast(pItem)->GetDefinedIn() != pModify ) + { + const_cast(static_cast(pItem))->ChgDefinedIn( pModify ); + bSet = true; + } + + return bSet; +} + +void SwAttrSet::CopyToModify( SwModify& rMod ) const +{ + // copy attributes across multiple documents if needed + SwContentNode* pCNd = dynamic_cast( &rMod ); + SwFormat* pFormat = dynamic_cast( &rMod ); + + if( pCNd || pFormat ) + { + if( Count() ) + { + // #i92811# + std::unique_ptr pNewListIdItem; + + const SfxPoolItem* pItem; + const SwDoc *pSrcDoc = GetDoc(); + SwDoc *pDstDoc = pCNd ? pCNd->GetDoc() : pFormat->GetDoc(); + + // Does the NumRule has to be copied? + if( pSrcDoc != pDstDoc && + SfxItemState::SET == GetItemState( RES_PARATR_NUMRULE, false, &pItem ) ) + { + const OUString& rNm = static_cast(pItem)->GetValue(); + if( !rNm.isEmpty() ) + { + SwNumRule* pDestRule = pDstDoc->FindNumRulePtr( rNm ); + if( pDestRule ) + pDestRule->SetInvalidRule( true ); + else + pDstDoc->MakeNumRule( rNm, pSrcDoc->FindNumRulePtr( rNm ) ); + } + } + + // copy list and if needed also the corresponding list style + // for text nodes + if ( pSrcDoc != pDstDoc && + pCNd && pCNd->IsTextNode() && + GetItemState( RES_PARATR_LIST_ID, false, &pItem ) == SfxItemState::SET ) + { + auto pStrItem = dynamic_cast(pItem); + assert(pStrItem); + const OUString& sListId = pStrItem->GetValue(); + if ( !sListId.isEmpty() && + !pDstDoc->getIDocumentListsAccess().getListByName( sListId ) ) + { + const SwList* pList = pSrcDoc->getIDocumentListsAccess().getListByName( sListId ); + // copy list style, if needed + const OUString& sDefaultListStyleName = + pList->GetDefaultListStyleName(); + // #i92811# + const SwNumRule* pDstDocNumRule = + pDstDoc->FindNumRulePtr( sDefaultListStyleName ); + if ( !pDstDocNumRule ) + { + pDstDoc->MakeNumRule( sDefaultListStyleName, + pSrcDoc->FindNumRulePtr( sDefaultListStyleName ) ); + } + else + { + const SwNumRule* pSrcDocNumRule = + pSrcDoc->FindNumRulePtr( sDefaultListStyleName ); + // If list id of text node equals the list style's + // default list id in the source document, the same + // should be hold in the destination document. + // Thus, create new list id item. + if (pSrcDocNumRule && sListId == pSrcDocNumRule->GetDefaultListId()) + { + pNewListIdItem.reset(new SfxStringItem ( + RES_PARATR_LIST_ID, + pDstDocNumRule->GetDefaultListId() )); + } + } + // check again, if list exist, because + // could have also created it. + if ( pNewListIdItem == nullptr && + !pDstDoc->getIDocumentListsAccess().getListByName( sListId ) ) + { + // copy list + pDstDoc->getIDocumentListsAccess().createList( sListId, sDefaultListStyleName ); + } + } + } + + std::unique_ptr< SfxItemSet > tmpSet; + + const SwPageDesc* pPgDesc; + if( pSrcDoc != pDstDoc && SfxItemState::SET == GetItemState( + RES_PAGEDESC, false, &pItem )) + { + pPgDesc = static_cast(pItem)->GetPageDesc(); + if( pPgDesc ) + { + tmpSet.reset(new SfxItemSet(*this)); + + SwPageDesc* pDstPgDesc = pDstDoc->FindPageDesc(pPgDesc->GetName()); + if( !pDstPgDesc ) + { + pDstPgDesc = pDstDoc->MakePageDesc(pPgDesc->GetName()); + pDstDoc->CopyPageDesc( *pPgDesc, *pDstPgDesc ); + } + SwFormatPageDesc aDesc( pDstPgDesc ); + aDesc.SetNumOffset( static_cast(pItem)->GetNumOffset() ); + tmpSet->Put( aDesc ); + } + } + + if( pSrcDoc != pDstDoc && SfxItemState::SET == GetItemState( RES_ANCHOR, false, &pItem ) + && static_cast< const SwFormatAnchor* >( pItem )->GetContentAnchor() != nullptr ) + { + if( !tmpSet ) + tmpSet.reset( new SfxItemSet( *this )); + // Anchors at any node position cannot be copied to another document, because the SwPosition + // would still point to the old document. It needs to be fixed up explicitly. + tmpSet->ClearItem( RES_ANCHOR ); + } + + if (pSrcDoc != pDstDoc && + SfxItemState::SET == GetItemState(RES_PARATR_LIST_AUTOFMT, false, &pItem)) + { + SfxItemSet const& rAutoStyle(*static_cast(*pItem).GetStyleHandle()); + std::shared_ptr const pNewSet( + rAutoStyle.SfxItemSet::Clone(true, &pDstDoc->GetAttrPool())); + + // fix up character style, it contains pointers to pSrcDoc + if (SfxItemState::SET == pNewSet->GetItemState(RES_TXTATR_CHARFMT, false, &pItem)) + { + auto const* pChar(static_cast(pItem)); + SwCharFormat *const pCopy(pDstDoc->CopyCharFormat(*pChar->GetCharFormat())); + const_cast(pChar)->SetCharFormat(pCopy); + } + + SwFormatAutoFormat item(RES_PARATR_LIST_AUTOFMT); + // TODO: for ODF export we'd need to add it to the autostyle pool + item.SetStyleHandle(pNewSet); + if (!tmpSet) + { + tmpSet.reset(new SfxItemSet(*this)); + } + tmpSet->Put(item); + } + + if( tmpSet ) + { + if( pCNd ) + { + // #i92811# + if ( pNewListIdItem != nullptr ) + { + tmpSet->Put( *pNewListIdItem ); + } + pCNd->SetAttr( *tmpSet ); + } + else + { + pFormat->SetFormatAttr( *tmpSet ); + } + } + else if( pCNd ) + { + // #i92811# + if ( pNewListIdItem != nullptr ) + { + SfxItemSet aTmpSet( *this ); + aTmpSet.Put( *pNewListIdItem ); + pCNd->SetAttr( aTmpSet ); + } + else + { + pCNd->SetAttr( *this ); + } + } + else + { + pFormat->SetFormatAttr( *this ); + } + } + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_FAIL("neither Format nor ContentNode - no Attributes copied"); +#endif +} + +/// check if ID is in range of attribute set IDs +bool IsInRange( const sal_uInt16* pRange, const sal_uInt16 nId ) +{ + while( *pRange ) + { + if( *pRange <= nId && nId <= *(pRange+1) ) + return true; + pRange += 2; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/SwSmartTagMgr.cxx b/sw/source/core/bastyp/SwSmartTagMgr.cxx new file mode 100644 index 000000000..096705e8b --- /dev/null +++ b/sw/source/core/bastyp/SwSmartTagMgr.cxx @@ -0,0 +1,70 @@ +/* -*- 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 + +using namespace com::sun::star; +using namespace com::sun::star::uno; + +SwSmartTagMgr* SwSmartTagMgr::spTheSwSmartTagMgr = nullptr; + +SwSmartTagMgr& SwSmartTagMgr::Get() +{ + if ( !spTheSwSmartTagMgr ) + { + spTheSwSmartTagMgr = new SwSmartTagMgr( SwDocShell::Factory().GetModuleName() ); + spTheSwSmartTagMgr->Init("Writer"); + } + return *spTheSwSmartTagMgr; +} + +SwSmartTagMgr::SwSmartTagMgr( const OUString& rModuleName ) : + SmartTagMgr( rModuleName ) +{ +} + +SwSmartTagMgr::~SwSmartTagMgr() +{ +} + +void SwSmartTagMgr::modified( const lang::EventObject& rEO ) +{ + SolarMutexGuard aGuard; + + // Installed recognizers have changed. We remove all existing smart tags: + SwModule::CheckSpellChanges( false, true, true, true ); + + SmartTagMgr::modified( rEO ); +} + +void SwSmartTagMgr::changesOccurred( const util::ChangesEvent& rEvent ) +{ + SolarMutexGuard aGuard; + + // Configuration has changed. We remove all existing smart tags: + SwModule::CheckSpellChanges( false, true, true, true ); + + SmartTagMgr::changesOccurred( rEvent ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/bparr.cxx b/sw/source/core/bastyp/bparr.cxx new file mode 100644 index 000000000..8158a485a --- /dev/null +++ b/sw/source/core/bastyp/bparr.cxx @@ -0,0 +1,505 @@ +/* -*- 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 + +/** Resize block management by this constant. + As a result there are approx. 20 * MAXENTRY == 20000 entries available */ +static const sal_uInt16 nBlockGrowSize = 20; + +#if OSL_DEBUG_LEVEL > 2 +#define CHECKIDX( p, n, i, c ) CheckIdx( p, n, i, c ); +void CheckIdx( BlockInfo** ppInf, sal_uInt16 nBlock, sal_uLong nSize, sal_uInt16 nCur ) +{ + assert( !nSize || nCur < nBlock ); // BigPtrArray: CurIndex invalid + + sal_uLong nIdx = 0; + for( sal_uInt16 nCnt = 0; nCnt < nBlock; ++nCnt, ++ppInf ) + { + nIdx += (*ppInf)->nElem; + // Array with holes is not allowed + assert( !nCnt || (*(ppInf-1))->nEnd + 1 == (*ppInf)->nStart ); + } + assert(nIdx == nSize); // invalid count in nSize +} +#else +#define CHECKIDX( p, n, i, c ) +#endif + +BigPtrArray::BigPtrArray() +{ + m_nBlock = m_nCur = 0; + m_nSize = 0; + m_nMaxBlock = nBlockGrowSize; + m_ppInf.reset( new BlockInfo* [ m_nMaxBlock ] ); +} + +BigPtrArray::~BigPtrArray() +{ + if( m_nBlock ) + { + BlockInfo** pp = m_ppInf.get(); + for( sal_uInt16 n = 0; n < m_nBlock; ++n, ++pp ) + { + delete *pp; + } + } +} + +// Also moving is done simply here. Optimization is useless because of the +// division of this field into multiple parts. +void BigPtrArray::Move( sal_uLong from, sal_uLong to ) +{ + if (from != to) + { + sal_uInt16 cur = Index2Block( from ); + BlockInfo* p = m_ppInf[ cur ]; + BigPtrEntry* pElem = p->mvData[ from - p->nStart ]; + Insert( pElem, to ); // insert first, then delete! + Remove( ( to < from ) ? ( from + 1 ) : from ); + } +} + +BigPtrEntry* BigPtrArray::operator[]( sal_uLong idx ) const +{ + assert(idx < m_nSize); // operator[]: Index out of bounds + m_nCur = Index2Block( idx ); + BlockInfo* p = m_ppInf[ m_nCur ]; + return p->mvData[ idx - p->nStart ]; +} + +/** Search a block at a given position */ +sal_uInt16 BigPtrArray::Index2Block( sal_uLong pos ) const +{ + // last used block? + BlockInfo* p = m_ppInf[ m_nCur ]; + if( p->nStart <= pos && p->nEnd >= pos ) + return m_nCur; + // Index = 0? + if( !pos ) + return 0; + + // following one? + if( m_nCur < ( m_nBlock - 1 ) ) + { + p = m_ppInf[ m_nCur+1 ]; + if( p->nStart <= pos && p->nEnd >= pos ) + return m_nCur+1; + } + // previous one? + else if( pos < p->nStart && m_nCur > 0 ) + { + p = m_ppInf[ m_nCur-1 ]; + if( p->nStart <= pos && p->nEnd >= pos ) + return m_nCur-1; + } + + // binary search: always successful + sal_uInt16 lower = 0, upper = m_nBlock - 1; + sal_uInt16 cur = 0; + for(;;) + { + sal_uInt16 n = lower + ( upper - lower ) / 2; + cur = ( n == cur ) ? n+1 : n; + p = m_ppInf[ cur ]; + if( p->nStart <= pos && p->nEnd >= pos ) + return cur; + + if( p->nStart > pos ) + upper = cur; + else + lower = cur; + } +} + +/** Update all index areas + + @param pos last correct block (starting point) +*/ +void BigPtrArray::UpdIndex( sal_uInt16 pos ) +{ + BlockInfo** pp = m_ppInf.get() + pos; + sal_uLong idx = (*pp)->nEnd + 1; + while( ++pos < m_nBlock ) + { + BlockInfo* p = *++pp; + p->nStart = idx; + idx += p->nElem; + p->nEnd = idx - 1; + } +} + +/** Create and insert new block + + Existing blocks will be moved rearward. + + @param pos Position at which the new block should be created. +*/ +BlockInfo* BigPtrArray::InsBlock( sal_uInt16 pos ) +{ + if( m_nBlock == m_nMaxBlock ) + { + // than extend the array first + BlockInfo** ppNew = new BlockInfo* [ m_nMaxBlock + nBlockGrowSize ]; + memcpy( ppNew, m_ppInf.get(), m_nMaxBlock * sizeof( BlockInfo* )); + m_nMaxBlock += nBlockGrowSize; + m_ppInf.reset( ppNew ); + } + if( pos != m_nBlock ) + { + memmove( m_ppInf.get() + pos+1, m_ppInf.get() + pos, + ( m_nBlock - pos ) * sizeof( BlockInfo* )); + } + ++m_nBlock; + BlockInfo* p = new BlockInfo; + m_ppInf[ pos ] = p; + + if( pos ) + p->nStart = p->nEnd = m_ppInf[ pos-1 ]->nEnd + 1; + else + p->nStart = p->nEnd = 0; + + p->nEnd--; // no elements + p->nElem = 0; + p->pBigArr = this; + return p; +} + +void BigPtrArray::BlockDel( sal_uInt16 nDel ) +{ + m_nBlock = m_nBlock - nDel; + if( m_nMaxBlock - m_nBlock > nBlockGrowSize ) + { + // than shrink array + nDel = (( m_nBlock / nBlockGrowSize ) + 1 ) * nBlockGrowSize; + BlockInfo** ppNew = new BlockInfo* [ nDel ]; + memcpy( ppNew, m_ppInf.get(), m_nBlock * sizeof( BlockInfo* )); + m_ppInf.reset( ppNew ); + m_nMaxBlock = nDel; + } +} + +void BigPtrArray::Insert( BigPtrEntry* pElem, sal_uLong pos ) +{ + CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); + + BlockInfo* p; + sal_uInt16 cur; + if( !m_nSize ) + { + // special case: insert first element + cur = 0; + p = InsBlock( cur ); + } + else if( pos == m_nSize ) + { + // special case: insert at end + cur = m_nBlock - 1; + p = m_ppInf[ cur ]; + if( p->nElem == MAXENTRY ) + // the last block is full, create a new one + p = InsBlock( ++cur ); + } + else + { + // standard case: + cur = Index2Block( pos ); + p = m_ppInf[ cur ]; + } + + if( p->nElem == MAXENTRY ) + { + // does the last entry fit into the next block? + BlockInfo* q; + if( cur < ( m_nBlock - 1 ) && m_ppInf[ cur+1 ]->nElem < MAXENTRY ) + { + q = m_ppInf[ cur+1 ]; + if( q->nElem ) + { + int nCount = q->nElem; + auto pFrom = q->mvData.begin() + nCount; + auto pTo = pFrom + 1; + while( nCount-- ) + { + *--pTo = *--pFrom; + ++((*pTo)->m_nOffset); + } + } + q->nStart--; + q->nEnd--; + } + else + { + // If it does not fit, then insert a new block. But if there is more + // than 50% space in the array then compress first. + if( /*nBlock == nMaxBlock &&*/ + m_nBlock > ( m_nSize / ( MAXENTRY / 2 ) ) && + cur >= Compress() ) + { + // Something was moved before the current position and all + // pointer might be invalid. Thus restart Insert. + Insert( pElem, pos ); + return ; + } + + q = InsBlock( cur+1 ); + } + + // entry does not fit anymore - clear space + BigPtrEntry* pLast = p->mvData[ MAXENTRY-1 ]; + pLast->m_nOffset = 0; + pLast->m_pBlock = q; + + q->mvData[ 0 ] = pLast; + q->nElem++; + q->nEnd++; + + p->nEnd--; + p->nElem--; + } + // now we have free space - insert + pos -= p->nStart; + assert(pos < MAXENTRY); + if( pos != p->nElem ) + { + int nCount = p->nElem - sal_uInt16(pos); + auto pFrom = p->mvData.begin() + p->nElem; + auto pTo = pFrom + 1; + while( nCount-- ) + { + *--pTo = *--pFrom; + ++( *pTo )->m_nOffset; + } + } + // insert element and update indices + pElem->m_nOffset = sal_uInt16(pos); + pElem->m_pBlock = p; + p->mvData[ pos ] = pElem; + p->nEnd++; + p->nElem++; + m_nSize++; + if( cur != ( m_nBlock - 1 ) ) UpdIndex( cur ); + m_nCur = cur; + + CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); +} + +void BigPtrArray::Remove( sal_uLong pos, sal_uLong n ) +{ + CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); + + sal_uInt16 nBlkdel = 0; // deleted blocks + sal_uInt16 cur = Index2Block( pos ); // current block number + sal_uInt16 nBlk1 = cur; // 1st treated block + sal_uInt16 nBlk1del = USHRT_MAX; // 1st deleted block + BlockInfo* p = m_ppInf[ cur ]; + pos -= p->nStart; + + sal_uLong nElem = n; + while( nElem ) + { + sal_uInt16 nel = p->nElem - sal_uInt16(pos); + if( sal_uLong(nel) > nElem ) + nel = sal_uInt16(nElem); + // move elements if needed + if( ( pos + nel ) < sal_uLong(p->nElem) ) + { + auto pTo = p->mvData.begin() + pos; + auto pFrom = pTo + nel; + int nCount = p->nElem - nel - sal_uInt16(pos); + while( nCount-- ) + { + *pTo = *pFrom++; + (*pTo)->m_nOffset = (*pTo)->m_nOffset - nel; + ++pTo; + } + } + p->nEnd -= nel; + p->nElem = p->nElem - nel; + // possibly delete block completely + if( !p->nElem ) + { + nBlkdel++; + if( USHRT_MAX == nBlk1del ) + nBlk1del = cur; + } + nElem -= nel; + if( !nElem ) + break; + p = m_ppInf[ ++cur ]; + pos = 0; + } + + // update table if blocks were removed + if( nBlkdel ) + { + for( sal_uInt16 i = nBlk1del; i < ( nBlk1del + nBlkdel ); i++ ) + delete m_ppInf[ i ]; + + if( ( nBlk1del + nBlkdel ) < m_nBlock ) + { + memmove( m_ppInf.get() + nBlk1del, m_ppInf.get() + nBlk1del + nBlkdel, + ( m_nBlock - nBlkdel - nBlk1del ) * sizeof( BlockInfo* ) ); + + // UpdateIdx updates the successor thus start before first elem + if( !nBlk1 ) + { + p = m_ppInf[ 0 ]; + p->nStart = 0; + p->nEnd = p->nElem-1; + } + else + { + --nBlk1; + } + } + BlockDel( nBlkdel ); // blocks were deleted + } + + m_nSize -= n; + if( nBlk1 != ( m_nBlock - 1 ) && m_nSize ) + UpdIndex( nBlk1 ); + m_nCur = nBlk1; + + // call Compress() if there is more than 50% space in the array + if( m_nBlock > ( m_nSize / ( MAXENTRY / 2 ) ) ) + Compress(); + + CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); +} + +void BigPtrArray::Replace( sal_uLong idx, BigPtrEntry* pElem) +{ + assert(idx < m_nSize); // Index out of bounds + m_nCur = Index2Block( idx ); + BlockInfo* p = m_ppInf[ m_nCur ]; + pElem->m_nOffset = sal_uInt16(idx - p->nStart); + pElem->m_pBlock = p; + p->mvData[ idx - p->nStart ] = pElem; +} + +/** Compress the array */ +sal_uInt16 BigPtrArray::Compress() +{ + CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); + + // Iterate over InfoBlock array from beginning to end. If there is a deleted + // block in between so move all following ones accordingly. The pointer + // represents the "old" and the "new" array. + BlockInfo** pp = m_ppInf.get(), **qq = pp; + BlockInfo* p; + BlockInfo* pLast(nullptr); // last empty block + sal_uInt16 nLast = 0; // missing elements + sal_uInt16 nBlkdel = 0; // number of deleted blocks + sal_uInt16 nFirstChgPos = USHRT_MAX; // at which position was the 1st change? + + // convert fill percentage into number of remaining elements + short const nMax = MAXENTRY - long(MAXENTRY) * COMPRESSLVL / 100; + + for( sal_uInt16 cur = 0; cur < m_nBlock; ++cur ) + { + p = *pp++; + sal_uInt16 n = p->nElem; + // Check if a not completely full block will be ignored. This happens if + // the current block would have to be split but the filling of the + // inspected block is already over its threshold value. In this case we + // do not fill more (it's expensive because of a double memmove() call) + if( nLast && ( n > nLast ) && ( nLast < nMax ) ) + nLast = 0; + if( nLast ) + { + if( USHRT_MAX == nFirstChgPos ) + nFirstChgPos = cur; + + // Not full yet? Then fill up. + if( n > nLast ) + n = nLast; + + // move elements from current to last block + auto pElem = pLast->mvData.begin() + pLast->nElem; + auto pFrom = p->mvData.begin(); + for( sal_uInt16 nCount = n, nOff = pLast->nElem; + nCount; --nCount, ++pElem ) + { + *pElem = *pFrom++; + (*pElem)->m_pBlock = pLast; + (*pElem)->m_nOffset = nOff++; + } + + // adjustment + pLast->nElem = pLast->nElem + n; + nLast = nLast - n; + p->nElem = p->nElem - n; + + // Is the current block now empty as a result? + if( !p->nElem ) + { + // then remove + delete p; + p = nullptr; + ++nBlkdel; + } + else + { + pElem = p->mvData.begin(); + pFrom = pElem + n; + int nCount = p->nElem; + while( nCount-- ) + { + *pElem = *pFrom++; + (*pElem)->m_nOffset = (*pElem)->m_nOffset - n; + ++pElem; + } + } + } + + if( p ) // BlockInfo was not deleted + { + *qq++ = p; // adjust to correct position + + // keep the potentially existing last half-full block + if( !nLast && p->nElem < MAXENTRY ) + { + pLast = p; + nLast = MAXENTRY - p->nElem; + } + } + } + + // if blocks were deleted shrink BlockInfo array if needed + if( nBlkdel ) + BlockDel( nBlkdel ); + + // and re-index + p = m_ppInf[ 0 ]; + p->nEnd = p->nElem - 1; + UpdIndex( 0 ); + + if( m_nCur >= nFirstChgPos ) + m_nCur = 0; + + CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); + + return nFirstChgPos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/breakit.cxx b/sw/source/core/bastyp/breakit.cxx new file mode 100644 index 000000000..af77672f7 --- /dev/null +++ b/sw/source/core/bastyp/breakit.cxx @@ -0,0 +1,183 @@ +/* -*- 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 namespace com::sun::star; + +SwBreakIt* g_pBreakIt = nullptr; + +void SwBreakIt::Create_( const uno::Reference & rxContext ) +{ + delete g_pBreakIt; + g_pBreakIt = new SwBreakIt( rxContext ); +} + +void SwBreakIt::Delete_() +{ + delete g_pBreakIt; + g_pBreakIt = nullptr; +} + +SwBreakIt * SwBreakIt::Get() +{ + return g_pBreakIt; +} + +SwBreakIt::SwBreakIt( const uno::Reference & rxContext ) + : m_xContext(rxContext) + , m_xBreak(i18n::BreakIterator::create(m_xContext)) + , m_aForbiddenLang(LANGUAGE_DONTKNOW) +{ +} + +void SwBreakIt::GetLocale_( const LanguageType aLang ) +{ + if (m_xLanguageTag) + m_xLanguageTag->reset(aLang); + else + m_xLanguageTag.reset(new LanguageTag(aLang)); +} + +void SwBreakIt::GetLocale_( const LanguageTag& rLanguageTag ) +{ + if (m_xLanguageTag) + *m_xLanguageTag = rLanguageTag; + else + m_xLanguageTag.reset(new LanguageTag(rLanguageTag)); +} + +void SwBreakIt::GetForbidden_( const LanguageType aLang ) +{ + LocaleDataWrapper aWrap(m_xContext, GetLanguageTag(aLang)); + + m_aForbiddenLang = aLang; + m_xForbidden.reset(new i18n::ForbiddenCharacters(aWrap.getForbiddenCharacters())); +} + +sal_uInt16 SwBreakIt::GetRealScriptOfText( const OUString& rText, sal_Int32 nPos ) const +{ + sal_uInt16 nScript = i18n::ScriptType::WEAK; + if (!rText.isEmpty()) + { + if( nPos && nPos == rText.getLength() ) + --nPos; + else if( nPos < 0) + nPos = 0; + + nScript = m_xBreak->getScriptType(rText, nPos); + sal_Int32 nChgPos = 0; + if (i18n::ScriptType::WEAK == nScript && nPos >= 0 && nPos + 1 < rText.getLength()) + { + // A weak character followed by a mark may be meant to combine with + // the mark, so prefer the following character's script + switch (u_charType(rText[nPos + 1])) + { + case U_NON_SPACING_MARK: + case U_ENCLOSING_MARK: + case U_COMBINING_SPACING_MARK: + nScript = m_xBreak->getScriptType(rText, nPos+1); + break; + } + } + if( i18n::ScriptType::WEAK == nScript && nPos ) + { + nChgPos = m_xBreak->beginOfScript(rText, nPos, nScript); + if( 0 < nChgPos ) + nScript = m_xBreak->getScriptType(rText, nChgPos-1); + } + + if( i18n::ScriptType::WEAK == nScript ) + { + nChgPos = m_xBreak->endOfScript(rText, nPos, nScript); + if( rText.getLength() > nChgPos && 0 <= nChgPos ) + nScript = m_xBreak->getScriptType(rText, nChgPos); + } + } + if( i18n::ScriptType::WEAK == nScript ) + nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + return nScript; +} + +SvtScriptType SwBreakIt::GetAllScriptsOfText( const OUString& rText ) const +{ + const SvtScriptType coAllScripts = SvtScriptType::LATIN | + SvtScriptType::ASIAN | + SvtScriptType::COMPLEX; + SvtScriptType nRet = SvtScriptType::NONE; + sal_uInt16 nScript = 0; + if (!rText.isEmpty()) + { + for( sal_Int32 n = 0, nEnd = rText.getLength(); n < nEnd; + n = m_xBreak->endOfScript(rText, n, nScript) ) + { + nScript = m_xBreak->getScriptType(rText, n); + switch( nScript ) + { + case i18n::ScriptType::LATIN: nRet |= SvtScriptType::LATIN; break; + case i18n::ScriptType::ASIAN: nRet |= SvtScriptType::ASIAN; break; + case i18n::ScriptType::COMPLEX: nRet |= SvtScriptType::COMPLEX; break; + case i18n::ScriptType::WEAK: + if( nRet == SvtScriptType::NONE ) + nRet |= coAllScripts; + break; + } + if( coAllScripts == nRet ) + break; + } + } + return nRet; +} + +sal_Int32 SwBreakIt::getGraphemeCount(const OUString& rText, + sal_Int32 nStart, sal_Int32 nEnd) const +{ + sal_Int32 nGraphemeCount = 0; + + sal_Int32 nCurPos = std::max(static_cast(0), nStart); + while (nCurPos < nEnd) + { + // fdo#49208 cheat and assume that nothing can combine with a space + // to form a single grapheme + if (rText[nCurPos] == ' ') + { + ++nCurPos; + } + else + { + sal_Int32 nCount2 = 1; + nCurPos = m_xBreak->nextCharacters(rText, nCurPos, lang::Locale(), + i18n::CharacterIteratorMode::SKIPCELL, nCount2, nCount2); + } + ++nGraphemeCount; + } + + return nGraphemeCount; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/calc.cxx b/sw/source/core/bastyp/calc.cxx new file mode 100644 index 000000000..ffe3d9d9c --- /dev/null +++ b/sw/source/core/bastyp/calc.cxx @@ -0,0 +1,1498 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +const char sCalc_Add[] = "add"; +const char sCalc_Sub[] = "sub"; +const char sCalc_Mul[] = "mul"; +const char sCalc_Div[] = "div"; +const char sCalc_Phd[] = "phd"; +const char sCalc_Sqrt[] = "sqrt"; +const char sCalc_Pow[] = "pow"; +const char sCalc_Or[] = "or"; +const char sCalc_Xor[] = "xor"; +const char sCalc_And[] = "and"; +const char sCalc_Not[] = "not"; +const char sCalc_Eq[] = "eq"; +const char sCalc_Neq[] = "neq"; +const char sCalc_Leq[] = "leq"; +const char sCalc_Geq[] = "geq"; +const char sCalc_L[] = "l"; +const char sCalc_G[] = "g"; +const char sCalc_Sum[] = "sum"; +const char sCalc_Mean[] = "mean"; +const char sCalc_Min[] = "min"; +const char sCalc_Max[] = "max"; +const char sCalc_Sin[] = "sin"; +const char sCalc_Cos[] = "cos"; +const char sCalc_Tan[] = "tan"; +const char sCalc_Asin[] = "asin"; +const char sCalc_Acos[] = "acos"; +const char sCalc_Atan[] = "atan"; +const char sCalc_Round[]= "round"; +const char sCalc_Date[] = "date"; + +// ATTENTION: sorted list of all operators +struct CalcOp +{ + union{ + const char* pName; + const OUString* pUName; + }; + SwCalcOper eOp; +}; + +CalcOp const aOpTable[] = { +/* ACOS */ {{sCalc_Acos}, CALC_ACOS}, // Arc cosine +/* ADD */ {{sCalc_Add}, CALC_PLUS}, // Addition +/* AND */ {{sCalc_And}, CALC_AND}, // log. AND +/* ASIN */ {{sCalc_Asin}, CALC_ASIN}, // Arc sine +/* ATAN */ {{sCalc_Atan}, CALC_ATAN}, // Arc tangent +/* COS */ {{sCalc_Cos}, CALC_COS}, // Cosine +/* DATE */ {{sCalc_Date}, CALC_DATE}, // Date +/* DIV */ {{sCalc_Div}, CALC_DIV}, // Division +/* EQ */ {{sCalc_Eq}, CALC_EQ}, // Equality +/* G */ {{sCalc_G}, CALC_GRE}, // Greater than +/* GEQ */ {{sCalc_Geq}, CALC_GEQ}, // Greater or equal +/* L */ {{sCalc_L}, CALC_LES}, // Less than +/* LEQ */ {{sCalc_Leq}, CALC_LEQ}, // Less or equal +/* MAX */ {{sCalc_Max}, CALC_MAX}, // Maximum value +/* MEAN */ {{sCalc_Mean}, CALC_MEAN}, // Mean +/* MIN */ {{sCalc_Min}, CALC_MIN}, // Minimum value +/* MUL */ {{sCalc_Mul}, CALC_MUL}, // Multiplication +/* NEQ */ {{sCalc_Neq}, CALC_NEQ}, // Not equal +/* NOT */ {{sCalc_Not}, CALC_NOT}, // log. NOT +/* OR */ {{sCalc_Or}, CALC_OR}, // log. OR +/* PHD */ {{sCalc_Phd}, CALC_PHD}, // Percentage +/* POW */ {{sCalc_Pow}, CALC_POW}, // Exponentiation +/* ROUND */ {{sCalc_Round}, CALC_ROUND}, // Rounding +/* SIN */ {{sCalc_Sin}, CALC_SIN}, // Sine +/* SQRT */ {{sCalc_Sqrt}, CALC_SQRT}, // Square root +/* SUB */ {{sCalc_Sub}, CALC_MINUS}, // Subtraction +/* SUM */ {{sCalc_Sum}, CALC_SUM}, // Sum +/* TAN */ {{sCalc_Tan}, CALC_TAN}, // Tangent +/* XOR */ {{sCalc_Xor}, CALC_XOR} // log. XOR +}; + +double const nRoundVal[] = { + 5.0e+0, 0.5e+0, 0.5e-1, 0.5e-2, 0.5e-3, 0.5e-4, 0.5e-5, 0.5e-6, + 0.5e-7, 0.5e-8, 0.5e-9, 0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14, + 0.5e-15,0.5e-16 +}; + +// First character may be any alphabetic or underscore. +const sal_Int32 coStartFlags = + i18n::KParseTokens::ANY_LETTER_OR_NUMBER | + i18n::KParseTokens::ASC_UNDERSCORE | + i18n::KParseTokens::IGNORE_LEADING_WS; + +// Continuing characters may be any alphanumeric, underscore, or dot. +const sal_Int32 coContFlags = + ( coStartFlags | i18n::KParseTokens::ASC_DOT ) + & ~i18n::KParseTokens::IGNORE_LEADING_WS; + +extern "C" { +static int OperatorCompare( const void *pFirst, const void *pSecond) +{ + int nRet = 0; + if( CALC_NAME == static_cast(pFirst)->eOp ) + { + if( CALC_NAME == static_cast(pSecond)->eOp ) + nRet = static_cast(pFirst)->pUName->compareTo( + *static_cast(pSecond)->pUName ); + else + nRet = static_cast(pFirst)->pUName->compareToAscii( + static_cast(pSecond)->pName ); + } + else + { + if( CALC_NAME == static_cast(pSecond)->eOp ) + nRet = -1 * static_cast(pSecond)->pUName->compareToAscii( + static_cast(pFirst)->pName ); + else + nRet = strcmp( static_cast(pFirst)->pName, + static_cast(pSecond)->pName ); + } + return nRet; +} +}// extern "C" + +CalcOp* FindOperator( const OUString& rSrch ) +{ + CalcOp aSrch; + aSrch.pUName = &rSrch; + aSrch.eOp = CALC_NAME; + + return static_cast(bsearch( static_cast(&aSrch), + static_cast(aOpTable), + SAL_N_ELEMENTS( aOpTable ), + sizeof( CalcOp ), + OperatorCompare )); +} + +static LanguageType GetDocAppScriptLang( SwDoc const & rDoc ) +{ + return static_cast(rDoc.GetDefault( + GetWhichOfScript( RES_CHRATR_LANGUAGE, + SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() )) + )).GetLanguage(); +} + +static double lcl_ConvertToDateValue( SwDoc& rDoc, sal_Int32 nDate ) +{ + double nRet = 0; + SvNumberFormatter* pFormatter = rDoc.GetNumberFormatter(); + if( pFormatter ) + { + const Date& rNull = pFormatter->GetNullDate(); + Date aDate( nDate >> 24, (nDate& 0x00FF0000) >> 16, nDate& 0x0000FFFF ); + nRet = aDate - rNull; + } + return nRet; +} + +SwCalc::SwCalc( SwDoc& rD ) + : m_aVarTable(TBLSZ) + , m_aErrExpr( OUString(), SwSbxValue(), nullptr ) + , m_nCommandPos(0) + , m_rDoc( rD ) + , m_pLocaleDataWrapper( m_aSysLocale.GetLocaleDataPtr() ) + , m_pCharClass( &GetAppCharClass() ) + , m_nListPor( 0 ) + , m_eCurrOper( CALC_NAME ) + , m_eCurrListOper( CALC_NAME ) + , m_eError( SwCalcError::NONE ) +{ + m_aErrExpr.aStr = "~C_ERR~"; + LanguageType eLang = GetDocAppScriptLang( m_rDoc ); + + if( eLang != m_pLocaleDataWrapper->getLanguageTag().getLanguageType() || + eLang != m_pCharClass->getLanguageTag().getLanguageType() ) + { + LanguageTag aLanguageTag( eLang ); + m_pCharClass = new CharClass( ::comphelper::getProcessComponentContext(), aLanguageTag ); + m_pLocaleDataWrapper = new LocaleDataWrapper( aLanguageTag ); + } + + m_sCurrSym = comphelper::string::strip(m_pLocaleDataWrapper->getCurrSymbol(), ' '); + m_sCurrSym = m_pCharClass->lowercase( m_sCurrSym ); + + static char const + sNType0[] = "false", + sNType1[] = "true", + sNType2[] = "pi", + sNType3[] = "e", + sNType4[] = "tables", + sNType5[] = "graf", + sNType6[] = "ole", + sNType7[] = "page", + sNType8[] = "para", + sNType9[] = "word", + sNType10[]= "char", + + sNType11[] = "user_firstname" , + sNType12[] = "user_lastname" , + sNType13[] = "user_initials" , + sNType14[] = "user_company" , + sNType15[] = "user_street" , + sNType16[] = "user_country" , + sNType17[] = "user_zipcode" , + sNType18[] = "user_city" , + sNType19[] = "user_title" , + sNType20[] = "user_position" , + sNType21[] = "user_tel_work" , + sNType22[] = "user_tel_home" , + sNType23[] = "user_fax" , + sNType24[] = "user_email" , + sNType25[] = "user_state" , + sNType26[] = "graph" + ; + static const char* const sNTypeTab[ 27 ] = + { + sNType0, sNType1, sNType2, sNType3, sNType4, sNType5, + sNType6, sNType7, sNType8, sNType9, sNType10, sNType11, + sNType12, sNType13, sNType14, sNType15, sNType16, sNType17, + sNType18, sNType19, sNType20, sNType21, sNType22, sNType23, + sNType24, + + // those have two HashIds + sNType25, sNType26 + }; + static sal_uInt16 const aHashValue[ 27 ] = + { + 34, 38, 43, 7, 18, 32, 22, 29, 30, 33, 3, + 28, 24, 40, 9, 11, 26, 45, 4, 23, 36, 44, 19, 5, 1, + // those have two HashIds + 11, 38 + }; + static UserOptToken const aAdrToken[ 12 ] = + { + UserOptToken::Company, UserOptToken::Street, UserOptToken::Country, UserOptToken::Zip, + UserOptToken::City, UserOptToken::Title, UserOptToken::Position, UserOptToken::TelephoneWork, + UserOptToken::TelephoneHome, UserOptToken::Fax, UserOptToken::Email, UserOptToken::State + }; + + static sal_uInt16 SwDocStat::* const aDocStat1[ 3 ] = + { + &SwDocStat::nTable, &SwDocStat::nGrf, &SwDocStat::nOLE + }; + static sal_uLong SwDocStat::* const aDocStat2[ 4 ] = + { + &SwDocStat::nPage, &SwDocStat::nPara, + &SwDocStat::nWord, &SwDocStat::nChar + }; + +#if TBLSZ != 47 +#error Did you adjust all hash values? +#endif + + const SwDocStat& rDocStat = m_rDoc.getIDocumentStatistics().GetDocStat(); + + SwSbxValue nVal; + OUString sTmpStr; + sal_uInt16 n; + + for( n = 0; n < 25; ++n ) + { + sTmpStr = OUString::createFromAscii(sNTypeTab[n]); + m_aVarTable[ aHashValue[ n ] ].reset( new SwCalcExp( sTmpStr, nVal, nullptr ) ); + } + + m_aVarTable[ aHashValue[ 0 ] ]->nValue.PutBool( false ); + m_aVarTable[ aHashValue[ 1 ] ]->nValue.PutBool( true ); + m_aVarTable[ aHashValue[ 2 ] ]->nValue.PutDouble( F_PI ); + m_aVarTable[ aHashValue[ 3 ] ]->nValue.PutDouble( 2.7182818284590452354 ); + + for( n = 0; n < 3; ++n ) + m_aVarTable[ aHashValue[ n + 4 ] ]->nValue.PutLong( rDocStat.*aDocStat1[ n ] ); + for( n = 0; n < 4; ++n ) + m_aVarTable[ aHashValue[ n + 7 ] ]->nValue.PutLong( rDocStat.*aDocStat2[ n ] ); + + SvtUserOptions& rUserOptions = SW_MOD()->GetUserOptions(); + + m_aVarTable[ aHashValue[ 11 ] ]->nValue.PutString( rUserOptions.GetFirstName() ); + m_aVarTable[ aHashValue[ 12 ] ]->nValue.PutString( rUserOptions.GetLastName() ); + m_aVarTable[ aHashValue[ 13 ] ]->nValue.PutString( rUserOptions.GetID() ); + + for( n = 0; n < 11; ++n ) + m_aVarTable[ aHashValue[ n + 14 ] ]->nValue.PutString( + rUserOptions.GetToken( aAdrToken[ n ] )); + + nVal.PutString( rUserOptions.GetToken( aAdrToken[ 11 ] )); + sTmpStr = OUString::createFromAscii(sNTypeTab[25]); + m_aVarTable[ aHashValue[ 25 ] ]->pNext.reset( new SwCalcExp( sTmpStr, nVal, nullptr ) ); + +} // SwCalc::SwCalc + +SwCalc::~SwCalc() COVERITY_NOEXCEPT_FALSE +{ + if( m_pLocaleDataWrapper != m_aSysLocale.GetLocaleDataPtr() ) + delete m_pLocaleDataWrapper; + if( m_pCharClass != &GetAppCharClass() ) + delete m_pCharClass; +} + +SwSbxValue SwCalc::Calculate( const OUString& rStr ) +{ + m_eError = SwCalcError::NONE; + SwSbxValue nResult; + + if( rStr.isEmpty() ) + return nResult; + + m_nListPor = 0; + m_eCurrListOper = CALC_PLUS; // default: sum + + m_sCommand = rStr; + m_nCommandPos = 0; + + for (;;) + { + m_eCurrOper = GetToken(); + if (m_eCurrOper == CALC_ENDCALC || m_eError != SwCalcError::NONE ) + break; + nResult = Expr(); + } + + if( m_eError != SwCalcError::NONE) + nResult.PutDouble( DBL_MAX ); + + return nResult; +} + +OUString SwCalc::GetStrResult( const SwSbxValue& rVal ) +{ + if( !rVal.IsDouble() ) + { + return rVal.GetOUString(); + } + return GetStrResult( rVal.GetDouble() ); +} + +OUString SwCalc::GetStrResult( double nValue ) +{ + if( nValue >= DBL_MAX ) + switch( m_eError ) + { + case SwCalcError::Syntax : return SwViewShell::GetShellRes()->aCalc_Syntax; + case SwCalcError::DivByZero : return SwViewShell::GetShellRes()->aCalc_ZeroDiv; + case SwCalcError::FaultyBrackets : return SwViewShell::GetShellRes()->aCalc_Brack; + case SwCalcError::OverflowInPower : return SwViewShell::GetShellRes()->aCalc_Pow; + case SwCalcError::Overflow : return SwViewShell::GetShellRes()->aCalc_Overflow; + default : return SwViewShell::GetShellRes()->aCalc_Default; + } + + const sal_Int32 nDecPlaces = 15; + OUString aRetStr( ::rtl::math::doubleToUString( + nValue, + rtl_math_StringFormat_Automatic, + nDecPlaces, + m_pLocaleDataWrapper->getNumDecimalSep()[0], + true )); + return aRetStr; +} + +SwCalcExp* SwCalc::VarInsert( const OUString &rStr ) +{ + OUString aStr = m_pCharClass->lowercase( rStr ); + return VarLook( aStr, true ); +} + +SwCalcExp* SwCalc::VarLook( const OUString& rStr, bool bIns ) +{ + m_aErrExpr.nValue.SetVoidValue(false); + + sal_uInt16 ii = 0; + OUString aStr = m_pCharClass->lowercase( rStr ); + + SwCalcExp* pFnd = m_aVarTable.Find(aStr, &ii); + + if( !pFnd ) + { + // then check doc + SwHashTable const & rDocTable = m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().GetFieldTypeTable(); + for( SwHash* pEntry = rDocTable[ii].get(); pEntry; pEntry = pEntry->pNext.get() ) + { + if( aStr == pEntry->aStr ) + { + // then insert here + pFnd = new SwCalcExp( aStr, SwSbxValue(), + static_cast(pEntry)->pFieldType ); + pFnd->pNext = std::move( m_aVarTable[ii] ); + m_aVarTable[ii].reset(pFnd); + break; + } + } + } + + if( pFnd ) + { + if( pFnd->pFieldType && pFnd->pFieldType->Which() == SwFieldIds::User ) + { + SwUserFieldType* pUField = const_cast(static_cast(pFnd->pFieldType)); + if( nsSwGetSetExpType::GSE_STRING & pUField->GetType() ) + { + pFnd->nValue.PutString( pUField->GetContent() ); + } + else if( !pUField->IsValid() ) + { + // Save the current values... + sal_uInt16 nListPor = m_nListPor; + SwSbxValue nLastLeft = m_nLastLeft; + SwSbxValue nNumberValue = m_nNumberValue; + sal_Int32 nCommandPos = m_nCommandPos; + SwCalcOper eCurrOper = m_eCurrOper; + SwCalcOper eCurrListOper = m_eCurrListOper; + OUString sCurrCommand = m_sCommand; + + pFnd->nValue.PutDouble( pUField->GetValue( *this ) ); + + // ...and write them back. + m_nListPor = nListPor; + m_nLastLeft = nLastLeft; + m_nNumberValue = nNumberValue; + m_nCommandPos = nCommandPos; + m_eCurrOper = eCurrOper; + m_eCurrListOper = eCurrListOper; + m_sCommand = sCurrCommand; + } + else + { + pFnd->nValue.PutDouble( pUField->GetValue() ); + } + } + else if ( !pFnd->pFieldType && pFnd->nValue.IsDBvalue() ) + { + if ( pFnd->nValue.IsString() ) + m_aErrExpr.nValue.PutString( pFnd->nValue.GetOUString() ); + else if ( pFnd->nValue.IsDouble() ) + m_aErrExpr.nValue.PutDouble( pFnd->nValue.GetDouble() ); + pFnd = &m_aErrExpr; + } + return pFnd; + } + + // At this point the "real" case variable has to be used + OUString const sTmpName( ::ReplacePoint(rStr) ); + + if( !bIns ) + { +#if HAVE_FEATURE_DBCONNECTIVITY + SwDBManager *pMgr = m_rDoc.GetDBManager(); + + OUString sDBName(GetDBName( sTmpName )); + OUString sSourceName(sDBName.getToken(0, DB_DELIM)); + OUString sTableName(sDBName.getToken(0, ';').getToken(1, DB_DELIM)); + if( pMgr && !sSourceName.isEmpty() && !sTableName.isEmpty() && + pMgr->OpenDataSource(sSourceName, sTableName)) + { + OUString sColumnName( GetColumnName( sTmpName )); + OSL_ENSURE(!sColumnName.isEmpty(), "Missing DB column name"); + + OUString sDBNum( SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber) ); + sDBNum = m_pCharClass->lowercase(sDBNum); + + // Initialize again because this doesn't happen in docfld anymore for + // elements != SwFieldIds::Database. E.g. if there is an expression field before + // a DB_Field in a document. + const sal_uInt32 nTmpRec = pMgr->GetSelectedRecordId(sSourceName, sTableName); + VarChange(sDBNum, nTmpRec); + + if( sDBNum.equalsIgnoreAsciiCase(sColumnName) ) + { + m_aErrExpr.nValue.PutULong(nTmpRec); + return &m_aErrExpr; + } + + OUString sResult; + double nNumber = DBL_MAX; + + LanguageType nLang = m_pLocaleDataWrapper->getLanguageTag().getLanguageType(); + if(pMgr->GetColumnCnt( sSourceName, sTableName, sColumnName, + nTmpRec, nLang, sResult, &nNumber )) + { + if (nNumber != DBL_MAX) + m_aErrExpr.nValue.PutDouble( nNumber ); + else + m_aErrExpr.nValue.PutString( sResult ); + + return &m_aErrExpr; + } + } + else +#endif + { + //data source was not available - set return to "NoValue" + m_aErrExpr.nValue.SetVoidValue(true); + } + // NEVER save! + return &m_aErrExpr; + } + + SwCalcExp* pNewExp = new SwCalcExp( aStr, SwSbxValue(), nullptr ); + pNewExp->pNext = std::move( m_aVarTable[ ii ] ); + m_aVarTable[ ii ].reset( pNewExp ); + + OUString sColumnName( GetColumnName( sTmpName )); + OSL_ENSURE( !sColumnName.isEmpty(), "Missing DB column name" ); + if( sColumnName.equalsIgnoreAsciiCase( + SwFieldType::GetTypeStr( SwFieldTypesEnum::DatabaseSetNumber ) )) + { +#if HAVE_FEATURE_DBCONNECTIVITY + SwDBManager *pMgr = m_rDoc.GetDBManager(); + OUString sDBName(GetDBName( sTmpName )); + OUString sSourceName(sDBName.getToken(0, DB_DELIM)); + OUString sTableName(sDBName.getToken(0, ';').getToken(1, DB_DELIM)); + if( pMgr && !sSourceName.isEmpty() && !sTableName.isEmpty() && + pMgr->OpenDataSource(sSourceName, sTableName) && + !pMgr->IsInMerge()) + { + pNewExp->nValue.PutULong( pMgr->GetSelectedRecordId(sSourceName, sTableName)); + } + else +#endif + { + pNewExp->nValue.SetVoidValue(true); + } + } + + return pNewExp; +} + +void SwCalc::VarChange( const OUString& rStr, double nValue ) +{ + SwSbxValue aVal( nValue ); + VarChange( rStr, aVal ); +} + +void SwCalc::VarChange( const OUString& rStr, const SwSbxValue& rValue ) +{ + OUString aStr = m_pCharClass->lowercase( rStr ); + + sal_uInt16 nPos = 0; + SwCalcExp* pFnd = m_aVarTable.Find( aStr, &nPos ); + + if( !pFnd ) + { + pFnd = new SwCalcExp( aStr, rValue, nullptr ); + pFnd->pNext = std::move( m_aVarTable[ nPos ] ); + m_aVarTable[ nPos ].reset( pFnd ); + } + else + { + pFnd->nValue = rValue; + } +} + +bool SwCalc::Push( const SwUserFieldType* pUserFieldType ) +{ + if( m_aRekurStack.end() != std::find(m_aRekurStack.begin(), m_aRekurStack.end(), pUserFieldType ) ) + return false; + + m_aRekurStack.push_back( pUserFieldType ); + return true; +} + +void SwCalc::Pop() +{ + OSL_ENSURE( m_aRekurStack.size(), "SwCalc: Pop on an invalid pointer" ); + + m_aRekurStack.pop_back(); +} + +CharClass* SwCalc::GetCharClass() +{ + return m_pCharClass; +} + +SwCalcOper SwCalc::GetToken() +{ + if( m_nCommandPos >= m_sCommand.getLength() ) + { + m_eCurrOper = CALC_ENDCALC; + return m_eCurrOper; + } + + using namespace ::com::sun::star::i18n; + { + // Parse any token. + ParseResult aRes = m_pCharClass->parseAnyToken( m_sCommand, m_nCommandPos, + coStartFlags, OUString(), + coContFlags, OUString()); + + bool bSetError = true; + sal_Int32 nRealStt = m_nCommandPos + aRes.LeadingWhiteSpace; + if( aRes.TokenType & (KParseType::ASC_NUMBER | KParseType::UNI_NUMBER) ) + { + m_nNumberValue.PutDouble( aRes.Value ); + m_eCurrOper = CALC_NUMBER; + bSetError = false; + } + else if( aRes.TokenType & KParseType::IDENTNAME ) + { + OUString aName( m_sCommand.copy( nRealStt, + aRes.EndPos - nRealStt ) ); + //#101436#: The variable may contain a database name. It must not be + // converted to lower case! Instead all further comparisons must be + // done case-insensitive + OUString sLowerCaseName = m_pCharClass->lowercase( aName ); + // catch currency symbol + if( sLowerCaseName == m_sCurrSym ) + { + m_nCommandPos = aRes.EndPos; + return GetToken(); // call again + } + + // catch operators + CalcOp* pFnd = ::FindOperator( sLowerCaseName ); + if( pFnd ) + { + m_eCurrOper = pFnd->eOp; + switch( m_eCurrOper ) + { + case CALC_SUM: + case CALC_MEAN: + m_eCurrListOper = CALC_PLUS; + break; + case CALC_MIN: + m_eCurrListOper = CALC_MIN_IN; + break; + case CALC_MAX: + m_eCurrListOper = CALC_MAX_IN; + break; + case CALC_DATE: + m_eCurrListOper = CALC_MONTH; + break; + default: + break; + } + m_nCommandPos = aRes.EndPos; + return m_eCurrOper; + } + m_aVarName = aName; + m_eCurrOper = CALC_NAME; + bSetError = false; + } + else if ( aRes.TokenType & KParseType::DOUBLE_QUOTE_STRING ) + { + m_nNumberValue.PutString( aRes.DequotedNameOrString ); + m_eCurrOper = CALC_NUMBER; + bSetError = false; + } + else if( aRes.TokenType & KParseType::ONE_SINGLE_CHAR ) + { + OUString aName( m_sCommand.copy( nRealStt, + aRes.EndPos - nRealStt )); + if( 1 == aName.getLength() ) + { + bSetError = false; + sal_Unicode ch = aName[0]; + switch( ch ) + { + case ';': + if( CALC_MONTH == m_eCurrListOper || CALC_DAY == m_eCurrListOper ) + { + m_eCurrOper = m_eCurrListOper; + break; + } + [[fallthrough]]; + case '\n': + m_eCurrOper = CALC_PRINT; + break; + + case '%': + case '^': + case '*': + case '/': + case '+': + case '-': + case '(': + case ')': + m_eCurrOper = SwCalcOper(ch); + break; + + case '=': + case '!': + { + SwCalcOper eTmp2; + if( '=' == ch ) + { + m_eCurrOper = SwCalcOper('='); + eTmp2 = CALC_EQ; + } + else + { + m_eCurrOper = CALC_NOT; + eTmp2 = CALC_NEQ; + } + + if( aRes.EndPos < m_sCommand.getLength() && + '=' == m_sCommand[aRes.EndPos] ) + { + m_eCurrOper = eTmp2; + ++aRes.EndPos; + } + } + break; + + case cListDelim: + m_eCurrOper = m_eCurrListOper; + break; + + case '[': + if( aRes.EndPos < m_sCommand.getLength() ) + { + m_aVarName.setLength(0); + sal_Int32 nFndPos = aRes.EndPos, + nSttPos = nFndPos; + + do { + if( -1 != ( nFndPos = + m_sCommand.indexOf( ']', nFndPos )) ) + { + // ignore the ] + if ('\\' == m_sCommand[nFndPos-1]) + { + m_aVarName.append(std::u16string_view(m_sCommand).substr(nSttPos, + nFndPos - nSttPos - 1) ); + nSttPos = ++nFndPos; + } + else + break; + } + } while( nFndPos != -1 ); + + if( nFndPos != -1 ) + { + if( nSttPos != nFndPos ) + m_aVarName.append(std::u16string_view(m_sCommand).substr(nSttPos, + nFndPos - nSttPos) ); + aRes.EndPos = nFndPos + 1; + m_eCurrOper = CALC_NAME; + } + else + bSetError = true; + } + else + { + bSetError = true; + } + break; + + default: + bSetError = true; + break; + } + } + } + else if( aRes.TokenType & KParseType::BOOLEAN ) + { + OUString aName( m_sCommand.copy( nRealStt, + aRes.EndPos - nRealStt )); + if( !aName.isEmpty() ) + { + sal_Unicode ch = aName[0]; + + bSetError = true; + if ('<' == ch || '>' == ch) + { + bSetError = false; + + SwCalcOper eTmp2 = ('<' == ch) ? CALC_LEQ : CALC_GEQ; + m_eCurrOper = ('<' == ch) ? CALC_LES : CALC_GRE; + + if( 2 == aName.getLength() && '=' == aName[1] ) + m_eCurrOper = eTmp2; + else if( 1 != aName.getLength() ) + bSetError = true; + } + } + } + else if( nRealStt == m_sCommand.getLength() ) + { + m_eCurrOper = CALC_ENDCALC; + bSetError = false; + } + + if( bSetError ) + { + m_eError = SwCalcError::Syntax; + m_eCurrOper = CALC_PRINT; + } + m_nCommandPos = aRes.EndPos; + }; + + return m_eCurrOper; +} + +SwSbxValue SwCalc::Term() +{ + SwSbxValue left( Prim() ); + m_nLastLeft = left; + for(;;) + { + sal_uInt16 nSbxOper = USHRT_MAX; + + switch( m_eCurrOper ) + { + case CALC_AND: + { + GetToken(); + bool bB = Prim().GetBool(); + left.PutBool( left.GetBool() && bB ); + } + break; + case CALC_OR: + { + GetToken(); + bool bB = Prim().GetBool(); + left.PutBool( left.GetBool() || bB ); + } + break; + case CALC_XOR: + { + GetToken(); + bool bR = Prim().GetBool(); + bool bL = left.GetBool(); + left.PutBool(bL != bR); + } + break; + + case CALC_EQ: nSbxOper = SbxEQ; break; + case CALC_NEQ: nSbxOper = SbxNE; break; + case CALC_LEQ: nSbxOper = SbxLE; break; + case CALC_GEQ: nSbxOper = SbxGE; break; + case CALC_GRE: nSbxOper = SbxGT; break; + case CALC_LES: nSbxOper = SbxLT; break; + + case CALC_MUL: nSbxOper = SbxMUL; break; + case CALC_DIV: nSbxOper = SbxDIV; break; + + case CALC_MIN_IN: + { + GetToken(); + SwSbxValue e = Prim(); + left = left.GetDouble() < e.GetDouble() ? left : e; + } + break; + case CALC_MAX_IN: + { + GetToken(); + SwSbxValue e = Prim(); + left = left.GetDouble() > e.GetDouble() ? left : e; + } + break; + case CALC_MONTH: + { + GetToken(); + SwSbxValue e = Prim(); + sal_Int32 nYear = static_cast(floor( left.GetDouble() )); + nYear = nYear & 0x0000FFFF; + sal_Int32 nMonth = static_cast(floor( e.GetDouble() )); + nMonth = ( nMonth & 0x000000FF ) << 16; + left.PutLong( nMonth + nYear ); + m_eCurrOper = CALC_DAY; + } + break; + case CALC_DAY: + { + GetToken(); + SwSbxValue e = Prim(); + sal_Int32 nYearMonth = static_cast(floor( left.GetDouble() )); + nYearMonth = nYearMonth & 0x00FFFFFF; + sal_Int32 nDay = static_cast(floor( e.GetDouble() )); + nDay = ( nDay & 0x000000FF ) << 24; + left = lcl_ConvertToDateValue( m_rDoc, nDay + nYearMonth ); + } + break; + case CALC_ROUND: + { + GetToken(); + SwSbxValue e = Prim(); + + double fVal = 0; + double fFac = 1; + sal_Int32 nDec = static_cast(floor( e.GetDouble() )); + if( nDec < -20 || nDec > 20 ) + { + m_eError = SwCalcError::Overflow; + left.Clear(); + return left; + } + fVal = left.GetDouble(); + if( nDec >= 0) + { + for (sal_Int32 i = 0; i < nDec; ++i ) + fFac *= 10.0; + } + else + { + for (sal_Int32 i = 0; i < -nDec; ++i ) + fFac /= 10.0; + } + + fVal *= fFac; + bool bSign; + if (fVal < 0.0) + { + fVal *= -1.0; + bSign = true; + } + else + { + bSign = false; + } + + // rounding + double fNum = fVal; // find the exponent + int nExp = 0; + if( fNum > 0 ) + { + while( fNum < 1.0 ) + { + fNum *= 10.0; + --nExp; + } + while( fNum >= 10.0 ) + { + fNum /= 10.0; + ++nExp; + } + } + nExp = 15 - nExp; + if( nExp > 15 ) + nExp = 15; + else if( nExp <= 1 ) + nExp = 0; + fVal = floor( fVal+ 0.5 + nRoundVal[ nExp ] ); + + if (bSign) + fVal *= -1.0; + + fVal /= fFac; + + left.PutDouble( fVal ); + } + break; + +//#77448# (=2*3^2 != 18) + + default: + return left; + } + + if( USHRT_MAX != nSbxOper ) + { + // #i47706: cast to SbxOperator AFTER comparing to USHRT_MAX + SbxOperator eSbxOper = static_cast(nSbxOper); + + GetToken(); + if( SbxEQ <= eSbxOper && eSbxOper <= SbxGE ) + { + left.PutBool( left.Compare( eSbxOper, Prim() )); + } + else + { + SwSbxValue aRight( Prim() ); + aRight.MakeDouble(); + left.MakeDouble(); + + if( SbxDIV == eSbxOper && !aRight.GetDouble() ) + m_eError = SwCalcError::DivByZero; + else + left.Compute( eSbxOper, aRight ); + } + } + } +} + +SwSbxValue SwCalc::StdFunc(pfCalc pFnc, bool bChkTrig) +{ + SwSbxValue nErg; + GetToken(); + double nVal = Prim().GetDouble(); + if( !bChkTrig || ( nVal > -1 && nVal < 1 ) ) + nErg.PutDouble( (*pFnc)( nVal ) ); + else + m_eError = SwCalcError::Overflow; + return nErg; +} + +SwSbxValue SwCalc::PrimFunc(bool &rChkPow) +{ + rChkPow = false; + + switch (m_eCurrOper) + { + case CALC_SIN: + SAL_INFO("sw.calc", "sin"); + return StdFunc(&sin, false); + break; + case CALC_COS: + SAL_INFO("sw.calc", "cos"); + return StdFunc(&cos, false); + break; + case CALC_TAN: + SAL_INFO("sw.calc", "tan"); + return StdFunc(&tan, false); + break; + case CALC_ATAN: + SAL_INFO("sw.calc", "atan"); + return StdFunc(&atan, false); + break; + case CALC_ASIN: + SAL_INFO("sw.calc", "asin"); + return StdFunc(&asin, true); + break; + case CALC_ACOS: + SAL_INFO("sw.calc", "acos"); + return StdFunc(&acos, true); + break; + case CALC_NOT: + { + SAL_INFO("sw.calc", "not"); + GetToken(); + SwSbxValue nErg = Prim(); + if( SbxSTRING == nErg.GetType() ) + { + nErg.PutBool( nErg.GetOUString().isEmpty() ); + } + else if(SbxBOOL == nErg.GetType() ) + { + nErg.PutBool(!nErg.GetBool()); + } + // Evaluate arguments manually so that the binary NOT below does not + // get called. We want a BOOLEAN NOT. + else if (nErg.IsNumeric()) + { + nErg.PutLong( nErg.GetDouble() == 0.0 ? 1 : 0 ); + } + else + { + OSL_FAIL( "unexpected case. computing binary NOT" ); + //!! computes a binary NOT + nErg.Compute( SbxNOT, nErg ); + } + return nErg; + break; + } + case CALC_NUMBER: + { + SAL_INFO("sw.calc", "number: " << m_nNumberValue.GetDouble()); + SwSbxValue nErg; + if( GetToken() == CALC_PHD ) + { + double aTmp = m_nNumberValue.GetDouble(); + aTmp *= 0.01; + nErg.PutDouble( aTmp ); + GetToken(); + } + else if( m_eCurrOper == CALC_NAME ) + { + m_eError = SwCalcError::Syntax; + } + else + { + nErg = m_nNumberValue; + rChkPow = true; + } + return nErg; + break; + } + case CALC_NAME: + { + SAL_INFO("sw.calc", "name"); + SwSbxValue nErg; + switch(SwCalcOper eOper = GetToken()) + { + case CALC_ASSIGN: + { + SwCalcExp* n = VarInsert(m_aVarName.toString()); + GetToken(); + nErg = n->nValue = Expr(); + break; + } + default: + nErg = VarLook(m_aVarName.toString())->nValue; + // Explicitly disallow unknown function names (followed by "("), + // allow unknown variable names (equal to zero) + if (nErg.IsVoidValue() && (eOper == CALC_LP)) + m_eError = SwCalcError::Syntax; + else + rChkPow = true; + break; + } + return nErg; + break; + } + case CALC_MINUS: + { + SAL_INFO("sw.calc", "-"); + SwSbxValue nErg; + GetToken(); + nErg.PutDouble( -(Prim().GetDouble()) ); + return nErg; + break; + } + case CALC_LP: + { + SAL_INFO("sw.calc", "("); + GetToken(); + SwSbxValue nErg = Expr(); + if( m_eCurrOper != CALC_RP ) + { + m_eError = SwCalcError::FaultyBrackets; + } + else + { + GetToken(); + rChkPow = true; // in order for =(7)^2 to work + } + return nErg; + break; + } + case CALC_RP: + // ignore, see tdf#121962 + SAL_INFO("sw.calc", ")"); + break; + case CALC_MEAN: + { + SAL_INFO("sw.calc", "mean"); + m_nListPor = 1; + GetToken(); + SwSbxValue nErg = Expr(); + double aTmp = nErg.GetDouble(); + aTmp /= m_nListPor; + nErg.PutDouble( aTmp ); + return nErg; + break; + } + case CALC_SQRT: + { + SAL_INFO("sw.calc", "sqrt"); + GetToken(); + SwSbxValue nErg = Prim(); + if( nErg.GetDouble() < 0 ) + m_eError = SwCalcError::Overflow; + else + nErg.PutDouble( sqrt( nErg.GetDouble() )); + return nErg; + break; + } + case CALC_SUM: + case CALC_DATE: + case CALC_MIN: + case CALC_MAX: + { + SAL_INFO("sw.calc", "sum/date/min/max"); + GetToken(); + SwSbxValue nErg = Expr(); + return nErg; + break; + } + case CALC_ENDCALC: + { + SAL_INFO("sw.calc", "endcalc"); + SwSbxValue nErg; + nErg.Clear(); + return nErg; + break; + } + default: + SAL_INFO("sw.calc", "syntax error"); + m_eError = SwCalcError::Syntax; + break; + } + + return SwSbxValue(); +} + +SwSbxValue SwCalc::Prim() +{ + bool bChkPow; + SwSbxValue nErg = PrimFunc(bChkPow); + + if (bChkPow && m_eCurrOper == CALC_POW) + { + double dleft = nErg.GetDouble(); + GetToken(); + double right = Prim().GetDouble(); + + double fraction; + fraction = modf( right, &o3tl::temporary(double()) ); + if( ( dleft < 0.0 && 0.0 != fraction ) || + ( 0.0 == dleft && right < 0.0 ) ) + { + m_eError = SwCalcError::Overflow; + nErg.Clear(); + } + else + { + dleft = pow(dleft, right ); + if( dleft == HUGE_VAL ) + { + m_eError = SwCalcError::OverflowInPower; + nErg.Clear(); + } + else + { + nErg.PutDouble( dleft ); + } + } + } + + return nErg; +} + +SwSbxValue SwCalc::Expr() +{ + SwSbxValue left = Term(); + m_nLastLeft = left; + for(;;) + { + switch(m_eCurrOper) + { + case CALC_PLUS: + { + GetToken(); + left.MakeDouble(); + SwSbxValue right(Term()); + right.MakeDouble(); + left.Compute(SbxPLUS, right); + m_nListPor++; + break; + } + case CALC_MINUS: + { + GetToken(); + left.MakeDouble(); + SwSbxValue right(Term()); + right.MakeDouble(); + left.Compute(SbxMINUS, right); + break; + } + default: + { + return left; + } + } + } +} + +OUString SwCalc::GetColumnName(const OUString& rName) +{ + sal_Int32 nPos = rName.indexOf(DB_DELIM); + if( -1 != nPos ) + { + nPos = rName.indexOf(DB_DELIM, nPos + 1); + + if( -1 != nPos ) + return rName.copy(nPos + 1); + } + return rName; +} + +OUString SwCalc::GetDBName(const OUString& rName) +{ + sal_Int32 nPos = rName.indexOf(DB_DELIM); + if( -1 != nPos ) + { + nPos = rName.indexOf(DB_DELIM, nPos + 1); + + if( -1 != nPos ) + return rName.copy( 0, nPos ); + } + SwDBData aData = m_rDoc.GetDBData(); + return aData.sDataSource + OUStringChar(DB_DELIM) + aData.sCommand; +} + +namespace +{ + bool lcl_Str2Double( const OUString& rCommand, sal_Int32& rCommandPos, + double& rVal, + const LocaleDataWrapper* const pLclData ) + { + assert(pLclData); + const sal_Unicode nCurrCmdPos = rCommandPos; + rtl_math_ConversionStatus eStatus; + const sal_Unicode* pEnd; + rVal = pLclData->stringToDouble( rCommand.getStr() + rCommandPos, + rCommand.getStr() + rCommand.getLength(), + true, + &eStatus, + &pEnd ); + rCommandPos = static_cast(pEnd - rCommand.getStr()); + + return rtl_math_ConversionStatus_Ok == eStatus && + nCurrCmdPos != rCommandPos; + } +} + +bool SwCalc::Str2Double( const OUString& rCommand, sal_Int32& rCommandPos, + double& rVal ) +{ + const SvtSysLocale aSysLocale; + return lcl_Str2Double( rCommand, rCommandPos, rVal, aSysLocale.GetLocaleDataPtr() ); +} + +bool SwCalc::Str2Double( const OUString& rCommand, sal_Int32& rCommandPos, + double& rVal, SwDoc const * const pDoc ) +{ + const SvtSysLocale aSysLocale; + std::unique_ptr pLclD; + if( pDoc ) + { + LanguageType eLang = GetDocAppScriptLang( *pDoc ); + if (eLang != aSysLocale.GetLanguageTag().getLanguageType()) + { + pLclD.reset( new LocaleDataWrapper( LanguageTag( eLang )) ); + } + } + + bool const bRet = lcl_Str2Double(rCommand, rCommandPos, rVal, + pLclD ? pLclD.get() : aSysLocale.GetLocaleDataPtr()); + + return bRet; +} + +bool SwCalc::IsValidVarName( const OUString& rStr, OUString* pValidName ) +{ + bool bRet = false; + using namespace ::com::sun::star::i18n; + { + // Parse any token. + ParseResult aRes = GetAppCharClass().parseAnyToken( rStr, 0, + coStartFlags, OUString(), + coContFlags, OUString() ); + + if( aRes.TokenType & KParseType::IDENTNAME ) + { + bRet = aRes.EndPos == rStr.getLength(); + if( pValidName ) + { + *pValidName = rStr.copy( aRes.LeadingWhiteSpace, + aRes.EndPos - aRes.LeadingWhiteSpace ); + } + } + else if( pValidName ) + pValidName->clear(); + } + return bRet; +} + +SwHash::SwHash(const OUString& rStr) + : aStr(rStr) +{ +} + +SwHash::~SwHash() +{ +} + +SwCalcExp::SwCalcExp(const OUString& rStr, const SwSbxValue& rVal, + const SwFieldType* pType) + : SwHash(rStr) + , nValue(rVal) + , pFieldType(pType) +{ +} + +bool SwSbxValue::GetBool() const +{ + return SbxSTRING == GetType() ? !GetOUString().isEmpty() + : SbxValue::GetBool(); +} + +double SwSbxValue::GetDouble() const +{ + double nRet; + if( SbxSTRING == GetType() ) + { + sal_Int32 nStt = 0; + SwCalc::Str2Double( GetOUString(), nStt, nRet ); + } + else if (IsBool()) + { + nRet = GetBool() ? 1.0 : 0.0; + } + else + { + nRet = SbxValue::GetDouble(); + } + return nRet; +} + +SwSbxValue& SwSbxValue::MakeDouble() +{ + if( GetType() == SbxSTRING || GetType() == SbxBOOL ) + PutDouble( GetDouble() ); + return *this; +} + +#ifdef STANDALONE_HASHCALC + +// this is example code how to create hash values in the CTOR: + +#include +void main() +{ + static char + sNType0[] = "false", sNType1[] = "true", sNType2[] = "pi", + sNType3[] = "e", sNType4[] = "tables", sNType5[] = "graf", + sNType6[] = "ole", sNType7[] = "page", sNType8[] = "para", + sNType9[] = "word", sNType10[]= "char", + sNType11[] = "user_company" , sNType12[] = "user_firstname" , + sNType13[] = "user_lastname" , sNType14[] = "user_initials", + sNType15[] = "user_street" , sNType16[] = "user_country" , + sNType17[] = "user_zipcode" , sNType18[] = "user_city" , + sNType19[] = "user_title" , sNType20[] = "user_position" , + sNType21[] = "user_tel_home", sNType22[] = "user_tel_work", + sNType23[] = "user_fax" , sNType24[] = "user_email" , + sNType25[] = "user_state", sNType26[] = "graph" + ; + + static const char* sNTypeTab[ 27 ] = + { + sNType0, sNType1, sNType2, sNType3, sNType4, sNType5, + sNType6, sNType7, sNType8, sNType9, sNType10, sNType11, + sNType12, sNType13, sNType14, sNType15, sNType16, sNType17, + sNType18, sNType19, sNType20, sNType21, sNType22, sNType23, + sNType24, sNType25, sNType26 + }; + + const unsigned short nTableSize = 47; + int aArr[ nTableSize ] = { 0 }; + char ch; + + for( int n = 0; n < 27; ++n ) + { + unsigned int ii = 0; + const char* pp = sNTypeTab[ n ]; + + while( *pp ) + { + ii = ii << 1 ^ *pp++; + } + ii %= nTableSize; + + ch = aArr[ ii ] ? 'X' : ' '; + aArr[ ii ] = 1; + printf( "%-20s -> %3u [%c]\n", sNTypeTab[ n ], ii, ch ); + } +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/checkit.cxx b/sw/source/core/bastyp/checkit.cxx new file mode 100644 index 000000000..69066ff5c --- /dev/null +++ b/sw/source/core/bastyp/checkit.cxx @@ -0,0 +1,33 @@ +/* -*- 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 namespace ::com::sun::star::uno; +using namespace ::com::sun::star::i18n; + +SwCheckIt::SwCheckIt() +{ + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + xCheck = InputSequenceChecker::create(xContext); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/index.cxx b/sw/source/core/bastyp/index.cxx new file mode 100644 index 000000000..073b66563 --- /dev/null +++ b/sw/source/core/bastyp/index.cxx @@ -0,0 +1,391 @@ +/* -*- 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 + +SwIndex::SwIndex(SwIndexReg *const pReg, sal_Int32 const nIdx) + : m_nIndex( nIdx ) + , m_pIndexReg( pReg ) + , m_pNext( nullptr ) + , m_pPrev( nullptr ) + , m_pMark( nullptr ) +{ + Init(m_nIndex); +} + +SwIndex::SwIndex( const SwIndex& rIdx, short nDiff ) + : m_pIndexReg( rIdx.m_pIndexReg ) + , m_pNext( nullptr ) + , m_pPrev( nullptr ) + , m_pMark( nullptr ) +{ + ChgValue( rIdx, rIdx.m_nIndex + nDiff ); +} + +SwIndex::SwIndex( const SwIndex& rIdx ) + : m_nIndex( rIdx.m_nIndex ) + , m_pIndexReg( rIdx.m_pIndexReg ) + , m_pNext( nullptr ) + , m_pPrev( nullptr ) + , m_pMark( nullptr ) +{ + ChgValue( rIdx, rIdx.m_nIndex ); +} + +void SwIndex::Init(sal_Int32 const nIdx) +{ + if (!m_pIndexReg) + { + m_nIndex = 0; // always 0 if no IndexReg + } + else if (!m_pIndexReg->m_pFirst) // first Index? + { + assert(!m_pIndexReg->m_pLast); + m_pIndexReg->m_pFirst = m_pIndexReg->m_pLast = this; + m_nIndex = nIdx; + } + else if (nIdx > ((m_pIndexReg->m_pLast->m_nIndex + - m_pIndexReg->m_pFirst->m_nIndex) / 2)) + { + ChgValue( *m_pIndexReg->m_pLast, nIdx ); + } + else + { + ChgValue( *m_pIndexReg->m_pFirst, nIdx ); + } +} + +SwIndex& SwIndex::ChgValue( const SwIndex& rIdx, sal_Int32 nNewValue ) +{ + assert(m_pIndexReg == rIdx.m_pIndexReg); + if (!m_pIndexReg) + { + m_nIndex = 0; + return *this; // no IndexReg => no list to sort into; m_nIndex is 0 + } + SwIndex* pFnd = const_cast(&rIdx); + if (rIdx.m_nIndex > nNewValue) // move forwards + { + for (;;) + { + SwIndex* pPrv = pFnd->m_pPrev; + if (!pPrv || pPrv->m_nIndex <= nNewValue) + break; + pFnd = pPrv; + } + + if( pFnd != this ) + { + // remove from list at old position + Remove(); + + m_pNext = pFnd; + m_pPrev = pFnd->m_pPrev; + if (m_pPrev) + m_pPrev->m_pNext = this; + else + m_pIndexReg->m_pFirst = this; + pFnd->m_pPrev = this; + } + } + else if (rIdx.m_nIndex < nNewValue) + { + for (;;) + { + SwIndex* pNxt = pFnd->m_pNext; + if (!pNxt || pNxt->m_nIndex >= nNewValue) + break; + pFnd = pNxt; + } + + if( pFnd != this ) + { + // remove from list at old position + Remove(); + + m_pPrev = pFnd; + m_pNext = pFnd->m_pNext; + if (m_pNext) + m_pNext->m_pPrev = this; + else + m_pIndexReg->m_pLast = this; + pFnd->m_pNext = this; + } + } + else if( pFnd != this ) + { + // remove from list at old position + Remove(); + + m_pPrev = pFnd; // == &rIdx here + m_pNext = rIdx.m_pNext; + m_pPrev->m_pNext = this; + + if (!m_pNext) // last in the list + m_pIndexReg->m_pLast = this; + else + m_pNext->m_pPrev = this; + } + + if (m_pIndexReg->m_pFirst == m_pNext) + m_pIndexReg->m_pFirst = this; + if (m_pIndexReg->m_pLast == m_pPrev) + m_pIndexReg->m_pLast = this; + + m_nIndex = nNewValue; + + return *this; +} + +void SwIndex::Remove() +{ + if (!m_pIndexReg) + { + assert(!m_pPrev && !m_pNext); + return; + } + + if (m_pPrev) + { + m_pPrev->m_pNext = m_pNext; + } + else if (m_pIndexReg->m_pFirst == this) + { + m_pIndexReg->m_pFirst = m_pNext; + } + + if (m_pNext) + { + m_pNext->m_pPrev = m_pPrev; + } + else if (m_pIndexReg->m_pLast == this) + { + m_pIndexReg->m_pLast = m_pPrev; + } +} + +SwIndex& SwIndex::operator=( const SwIndex& rIdx ) +{ + bool bEqual; + if (rIdx.m_pIndexReg != m_pIndexReg) // unregister! + { + Remove(); + m_pIndexReg = rIdx.m_pIndexReg; + m_pNext = m_pPrev = nullptr; + bEqual = false; + } + else + bEqual = rIdx.m_nIndex == m_nIndex; + + if( !bEqual ) + ChgValue( rIdx, rIdx.m_nIndex ); + return *this; +} + +SwIndex& SwIndex::Assign( SwIndexReg* pArr, sal_Int32 nIdx ) +{ + if (pArr != m_pIndexReg) // unregister! + { + Remove(); + m_pIndexReg = pArr; + m_pNext = m_pPrev = nullptr; + Init(nIdx); + } + else if (m_nIndex != nIdx) + { + ChgValue( *this, nIdx ); + } + return *this; +} + +void SwIndex::SetMark(const sw::mark::IMark* pMark) +{ + m_pMark = pMark; +} + +SwIndexReg::SwIndexReg() + : m_pFirst( nullptr ), m_pLast( nullptr ) +{ +} + +SwIndexReg::~SwIndexReg() +{ + assert(!m_pFirst && !m_pLast && "There are still indices registered"); +} + +void SwIndexReg::Update( + SwIndex const & rIdx, + const sal_Int32 nDiff, + const bool bNeg, + const bool /* argument is only used in derived class*/ ) +{ + SwIndex* pStt = const_cast(&rIdx); + const sal_Int32 nNewVal = rIdx.m_nIndex; + if( bNeg ) + { + const sal_Int32 nLast = rIdx.GetIndex() + nDiff; + while (pStt && pStt->m_nIndex == nNewVal) + { + pStt->m_nIndex = nNewVal; + pStt = pStt->m_pPrev; + } + pStt = rIdx.m_pNext; + while (pStt && pStt->m_nIndex >= nNewVal + && pStt->m_nIndex <= nLast) + { + pStt->m_nIndex = nNewVal; + pStt = pStt->m_pNext; + } + while( pStt ) + { + pStt->m_nIndex = pStt->m_nIndex - nDiff; + pStt = pStt->m_pNext; + } + } + else + { + while (pStt && pStt->m_nIndex == nNewVal) + { + pStt->m_nIndex = pStt->m_nIndex + nDiff; + pStt = pStt->m_pPrev; + } + pStt = rIdx.m_pNext; + while( pStt ) + { + // HACK: avoid updating position of cross-ref bookmarks + if (!pStt->m_pMark || nullptr == dynamic_cast< + ::sw::mark::CrossRefBookmark const*>(pStt->m_pMark)) + { + pStt->m_nIndex = pStt->m_nIndex + nDiff; + } + pStt = pStt->m_pNext; + } + } +} + +void SwIndexReg::MoveTo( SwIndexReg& rArr ) +{ + if (this != &rArr && m_pFirst) + { + SwIndex * pIdx = const_cast(m_pFirst); + SwIndex * pNext; + while( pIdx ) + { + pNext = pIdx->m_pNext; + pIdx->Assign( &rArr, pIdx->GetIndex() ); + pIdx = pNext; + } + m_pFirst = nullptr; + m_pLast = nullptr; + } +} + +#ifdef DBG_UTIL + +// SwIndex + +sal_Int32 SwIndex::operator++() +{ + SAL_WARN_IF( !(m_nIndex < SAL_MAX_INT32), "sw.core", + "SwIndex::operator++() wraps around" ); + + ChgValue( *this, m_nIndex+1 ); + return m_nIndex; +} + +sal_Int32 SwIndex::operator--(int) +{ + SAL_WARN_IF( !(m_nIndex > 0), "sw.core", + "SwIndex::operator--(int) wraps around" ); + + const sal_Int32 nOldIndex = m_nIndex; + ChgValue( *this, m_nIndex-1 ); + return nOldIndex; +} + +sal_Int32 SwIndex::operator--() +{ + SAL_WARN_IF( !( m_nIndex > 0), "sw.core", + "SwIndex::operator--() wraps around" ); + return ChgValue( *this, m_nIndex-1 ).m_nIndex; +} + +sal_Int32 SwIndex::operator+=( sal_Int32 const nVal ) +{ + SAL_WARN_IF( !(m_nIndex <= SAL_MAX_INT32 - nVal), "sw.core", + "SwIndex SwIndex::operator+=(sal_Int32) wraps around" ); + return ChgValue( *this, m_nIndex + nVal ).m_nIndex; +} + +sal_Int32 SwIndex::operator-=( sal_Int32 const nVal ) +{ + SAL_WARN_IF( !(m_nIndex >= nVal), "sw.core", + "SwIndex::operator-=(sal_Int32) wraps around" ); + return ChgValue( *this, m_nIndex - nVal ).m_nIndex; +} + +bool SwIndex::operator< ( const SwIndex & rIndex ) const +{ + // Attempt to compare indices into different arrays + assert(m_pIndexReg == rIndex.m_pIndexReg); + return m_nIndex < rIndex.m_nIndex; +} + +bool SwIndex::operator<=( const SwIndex & rIndex ) const +{ + // Attempt to compare indices into different arrays + assert(m_pIndexReg == rIndex.m_pIndexReg); + return m_nIndex <= rIndex.m_nIndex; +} + +bool SwIndex::operator> ( const SwIndex & rIndex ) const +{ + // Attempt to compare indices into different arrays + assert(m_pIndexReg == rIndex.m_pIndexReg); + return m_nIndex > rIndex.m_nIndex; +} + +bool SwIndex::operator>=( const SwIndex & rIndex ) const +{ + // Attempt to compare indices into different arrays + assert(m_pIndexReg == rIndex.m_pIndexReg); + return m_nIndex >= rIndex.m_nIndex; +} + +SwIndex& SwIndex::operator= ( sal_Int32 const nVal ) +{ + if (m_nIndex != nVal) + ChgValue( *this, nVal ); + + return *this; +} + +#endif + +std::ostream& operator <<(std::ostream& s, const SwIndex& index) +{ + return s << "SwIndex offset (" << index.GetIndex() << ")"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx new file mode 100644 index 000000000..c3a8d48b0 --- /dev/null +++ b/sw/source/core/bastyp/init.cxx @@ -0,0 +1,797 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; + +// some ranges for sets in collections/ nodes + +// AttrSet range for the 2 break attributes +sal_uInt16 const aBreakSetRange[] = { + RES_PAGEDESC, RES_BREAK, + 0 +}; + +// AttrSet range for TextFormatColl +// list attributes ( RES_PARATR_LIST_BEGIN - RES_PARATR_LIST_END ) are not +// included in the paragraph style's itemset. +sal_uInt16 const aTextFormatCollSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_PARATR_BEGIN, RES_PARATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + + // FillAttribute support + XATTR_FILL_FIRST, XATTR_FILL_LAST, + + 0 +}; + +// AttrSet range for GrfFormatColl +sal_uInt16 const aGrfFormatCollSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_GRFATR_BEGIN, RES_GRFATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + 0 +}; + +// AttrSet range for TextNode +sal_uInt16 const aTextNodeSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_PARATR_BEGIN, RES_PARATR_END-1, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + + // FillAttribute support (paragraph FillStyle) + XATTR_FILL_FIRST, XATTR_FILL_LAST, + + 0 +}; + +// AttrSet range for NoTextNode +sal_uInt16 const aNoTextNodeSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_GRFATR_BEGIN, RES_GRFATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + 0 +}; + +sal_uInt16 const aTableSetRange[] = { + RES_FILL_ORDER, RES_FRM_SIZE, + RES_LR_SPACE, RES_BREAK, + RES_BACKGROUND, RES_SHADOW, + RES_HORI_ORIENT, RES_HORI_ORIENT, + RES_KEEP, RES_KEEP, + RES_LAYOUT_SPLIT, RES_LAYOUT_SPLIT, + RES_FRAMEDIR, RES_FRAMEDIR, + // #i29550# + RES_COLLAPSING_BORDERS, RES_COLLAPSING_BORDERS, + // <-- collapsing + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + RES_FRMATR_GRABBAG, RES_FRMATR_GRABBAG, + 0 +}; + +sal_uInt16 const aTableLineSetRange[] = { + RES_FILL_ORDER, RES_FRM_SIZE, + RES_LR_SPACE, RES_UL_SPACE, + RES_BACKGROUND, RES_SHADOW, + RES_ROW_SPLIT, RES_ROW_SPLIT, + RES_PROTECT, RES_PROTECT, + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + RES_FRMATR_GRABBAG, RES_FRMATR_GRABBAG, + 0 +}; + +sal_uInt16 const aTableBoxSetRange[] = { + RES_FILL_ORDER, RES_FRM_SIZE, + RES_LR_SPACE, RES_UL_SPACE, + RES_BACKGROUND, RES_SHADOW, + RES_PROTECT, RES_PROTECT, + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_FRAMEDIR, RES_FRAMEDIR, + RES_BOXATR_BEGIN, RES_BOXATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + RES_FRMATR_GRABBAG, RES_FRMATR_GRABBAG, + 0 +}; + +// AttrSet range for SwFrameFormat +sal_uInt16 const aFrameFormatSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + + // FillAttribute support (TextFrame, OLE, Writer GraphicObject) + XATTR_FILL_FIRST, XATTR_FILL_LAST, + + 0 +}; + +// AttrSet range for SwCharFormat +sal_uInt16 const aCharFormatSetRange[] = { + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + 0 +}; + +// AttrSet range for character autostyles +sal_uInt16 const aCharAutoFormatSetRange[] = { + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_TXTATR_UNKNOWN_CONTAINER, RES_TXTATR_UNKNOWN_CONTAINER, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + 0 +}; + +// AttrSet range for SwPageDescFormat +sal_uInt16 const aPgFrameFormatSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + 0 +}; + +// create table for accessing default format attributes +SwDfltAttrTab aAttrTab( POOLATTR_END - POOLATTR_BEGIN, nullptr ); + +SfxItemInfo aSlotTab[] = +{ + { SID_ATTR_CHAR_CASEMAP, true }, // RES_CHRATR_CASEMAP + { SID_ATTR_CHAR_CHARSETCOLOR, true }, // RES_CHRATR_CHARSETCOLOR + { SID_ATTR_CHAR_COLOR, true }, // RES_CHRATR_COLOR + { SID_ATTR_CHAR_CONTOUR, true }, // RES_CHRATR_CONTOUR + { SID_ATTR_CHAR_STRIKEOUT, true }, // RES_CHRATR_CROSSEDOUT + { SID_ATTR_CHAR_ESCAPEMENT, true }, // RES_CHRATR_ESCAPEMENT + { SID_ATTR_CHAR_FONT, true }, // RES_CHRATR_FONT + { SID_ATTR_CHAR_FONTHEIGHT, true }, // RES_CHRATR_FONTSIZE + { SID_ATTR_CHAR_KERNING, true }, // RES_CHRATR_KERNING + { SID_ATTR_CHAR_LANGUAGE, true }, // RES_CHRATR_LANGUAGE + { SID_ATTR_CHAR_POSTURE, true }, // RES_CHRATR_POSTURE + { 0, true }, // RES_CHRATR_UNUSED1 + { SID_ATTR_CHAR_SHADOWED, true }, // RES_CHRATR_SHADOWED + { SID_ATTR_CHAR_UNDERLINE, true }, // RES_CHRATR_UNDERLINE + { SID_ATTR_CHAR_WEIGHT, true }, // RES_CHRATR_WEIGHT + { SID_ATTR_CHAR_WORDLINEMODE, true }, // RES_CHRATR_WORDLINEMODE + { SID_ATTR_CHAR_AUTOKERN, true }, // RES_CHRATR_AUTOKERN + { SID_ATTR_FLASH, true }, // RES_CHRATR_BLINK + { 0, true }, // RES_CHRATR_UNUSED2 + { 0, true }, // RES_CHRATR_NOHYPHEN + { SID_ATTR_BRUSH_CHAR, true }, // RES_CHRATR_BACKGROUND + { SID_ATTR_CHAR_CJK_FONT, true }, // RES_CHRATR_CJK_FONT + { SID_ATTR_CHAR_CJK_FONTHEIGHT, true },// RES_CHRATR_CJK_FONTSIZE + { SID_ATTR_CHAR_CJK_LANGUAGE, true }, // RES_CHRATR_CJK_LANGUAGE + { SID_ATTR_CHAR_CJK_POSTURE, true }, // RES_CHRATR_CJK_POSTURE + { SID_ATTR_CHAR_CJK_WEIGHT, true }, // RES_CHRATR_CJK_WEIGHT + { SID_ATTR_CHAR_CTL_FONT, true }, // RES_CHRATR_CTL_FONT + { SID_ATTR_CHAR_CTL_FONTHEIGHT, true },// RES_CHRATR_CTL_FONTSIZE + { SID_ATTR_CHAR_CTL_LANGUAGE, true }, // RES_CHRATR_CTL_LANGUAGE + { SID_ATTR_CHAR_CTL_POSTURE, true }, // RES_CHRATR_CTL_POSTURE + { SID_ATTR_CHAR_CTL_WEIGHT, true }, // RES_CHRATR_CTL_WEIGHT + { SID_ATTR_CHAR_ROTATED, true }, // RES_CHRATR_ROTATE + { SID_ATTR_CHAR_EMPHASISMARK, true }, // RES_CHRATR_EMPHASIS_MARK + { SID_ATTR_CHAR_TWO_LINES, true }, // RES_CHRATR_TWO_LINES + { SID_ATTR_CHAR_SCALEWIDTH, true }, // RES_CHRATR_SCALEW + { SID_ATTR_CHAR_RELIEF, true }, // RES_CHRATR_RELIEF + { SID_ATTR_CHAR_HIDDEN, true }, // RES_CHRATR_HIDDEN + { SID_ATTR_CHAR_OVERLINE, true }, // RES_CHRATR_OVERLINE + { 0, true }, // RES_CHRATR_RSID + { SID_ATTR_CHAR_BOX, true }, // RES_CHRATR_BOX + { SID_ATTR_CHAR_SHADOW, true }, // RES_CHRATR_SHADOW + { 0, true }, // RES_CHRATR_HIGHLIGHT + { SID_ATTR_CHAR_GRABBAG, true }, // RES_CHRATR_GRABBAG + { 0, true }, // RES_CHRATR_BIDIRTL + { 0, true }, // RES_CHRATR_IDCTHINT + + { 0, false }, // RES_TXTATR_REFMARK + { 0, false }, // RES_TXTATR_TOXMARK + { 0, false }, // RES_TXTATR_META + { 0, false }, // RES_TXTATR_METAFIELD + { 0, true }, // RES_TXTATR_AUTOFMT + { FN_TXTATR_INET, false }, // RES_TXTATR_INETFMT + { 0, false }, // RES_TXTATR_CHARFMT + { SID_ATTR_CHAR_CJK_RUBY, false }, // RES_TXTATR_CJK_RUBY + { 0, true }, // RES_TXTATR_UNKNOWN_CONTAINER + { 0, false }, // RES_TXTATR_INPUTFIELD + + { 0, false }, // RES_TXTATR_FIELD + { 0, false }, // RES_TXTATR_FLYCNT + { 0, false }, // RES_TXTATR_FTN + { 0, false }, // RES_TXTATR_ANNOTATION + { 0, true }, // RES_TXTATR_DUMMY3 + { 0, true }, // RES_TXTATR_DUMMY1 + { 0, true }, // RES_TXTATR_DUMMY2 + + { SID_ATTR_PARA_LINESPACE, true }, // RES_PARATR_LINESPACING + { SID_ATTR_PARA_ADJUST, true }, // RES_PARATR_ADJUST + { SID_ATTR_PARA_SPLIT, true }, // RES_PARATR_SPLIT + { SID_ATTR_PARA_ORPHANS, true }, // RES_PARATR_ORPHANS + { SID_ATTR_PARA_WIDOWS, true }, // RES_PARATR_WIDOWS + { SID_ATTR_TABSTOP, true }, // RES_PARATR_TABSTOP + { SID_ATTR_PARA_HYPHENZONE, true }, // RES_PARATR_HYPHENZONE + { FN_FORMAT_DROPCAPS, false }, // RES_PARATR_DROP + { SID_ATTR_PARA_REGISTER, true }, // RES_PARATR_REGISTER + { SID_ATTR_PARA_NUMRULE, true }, // RES_PARATR_NUMRULE + { SID_ATTR_PARA_SCRIPTSPACE, true }, // RES_PARATR_SCRIPTSPACE + { SID_ATTR_PARA_HANGPUNCTUATION, true },// RES_PARATR_HANGINGPUNCTUATION + + { SID_ATTR_PARA_FORBIDDEN_RULES, true },// RES_PARATR_FORBIDDEN_RULES + { SID_PARA_VERTALIGN, true }, // RES_PARATR_VERTALIGN + { SID_ATTR_PARA_SNAPTOGRID, true }, // RES_PARATR_SNAPTOGRID + { SID_ATTR_BORDER_CONNECT, true }, // RES_PARATR_CONNECT_BORDER + + { SID_ATTR_PARA_OUTLINE_LEVEL, true }, // RES_PARATR_OUTLINELEVEL //#outline level + { 0, true }, // RES_PARATR_RSID + { SID_ATTR_PARA_GRABBAG, true }, // RES_PARATR_GRABBAG + { 0, true }, // RES_PARATR_LIST_ID + { 0, true }, // RES_PARATR_LIST_LEVEL + { 0, true }, // RES_PARATR_LIST_ISRESTART + { 0, true }, // RES_PARATR_LIST_RESTARTVALUE + { 0, true }, // RES_PARATR_LIST_ISCOUNTED + { 0, true }, // RES_PARATR_LIST_AUTOFMT + + { 0, true }, // RES_FILL_ORDER + { 0, true }, // RES_FRM_SIZE + { SID_ATTR_PAGE_PAPERBIN, true }, // RES_PAPER_BIN + { SID_ATTR_LRSPACE, true }, // RES_LR_SPACE + { SID_ATTR_ULSPACE, true }, // RES_UL_SPACE + { 0, false }, // RES_PAGEDESC + { SID_ATTR_PARA_PAGEBREAK, true }, // RES_BREAK + { 0, false }, // RES_CNTNT + { 0, true }, // RES_HEADER + { 0, true }, // RES_FOOTER + { 0, true }, // RES_PRINT + { FN_OPAQUE, true }, // RES_OPAQUE + { FN_SET_PROTECT, true }, // RES_PROTECT + { FN_SURROUND, true }, // RES_SURROUND + { FN_VERT_ORIENT, true }, // RES_VERT_ORIENT + { FN_HORI_ORIENT, true }, // RES_HORI_ORIENT + { 0, false }, // RES_ANCHOR + { SID_ATTR_BRUSH, true }, // RES_BACKGROUND + { SID_ATTR_BORDER_OUTER, true }, // RES_BOX + { SID_ATTR_BORDER_SHADOW, true }, // RES_SHADOW + { SID_ATTR_MACROITEM, true }, // RES_FRMMACRO + { FN_ATTR_COLUMNS, true }, // RES_COL + { SID_ATTR_PARA_KEEP, true }, // RES_KEEP + { 0, true }, // RES_URL + { 0, true }, // RES_EDIT_IN_READONLY + + { 0, true }, // RES_LAYOUT_SPLIT + { 0, false }, // RES_CHAIN + { 0, true }, // RES_TEXTGRID + { FN_FORMAT_LINENUMBER, true }, // RES_LINENUMBER + { 0, true }, // RES_FTN_AT_TXTEND + { 0, true }, // RES_END_AT_TXTEND + { 0, true }, // RES_COLUMNBALANCE + + { SID_ATTR_FRAMEDIRECTION, true }, // RES_FRAMEDIR + + { SID_ATTR_HDFT_DYNAMIC_SPACING, true },// RES_HEADER_FOOTER_EAT_SPACING + { FN_TABLE_ROW_SPLIT, true }, // RES_ROW_SPLIT + // #i18732# - use slot-id define in svx + { SID_SW_FOLLOW_TEXT_FLOW, true }, // RES_FOLLOW_TEXT_FLOW + // #i29550# + { SID_SW_COLLAPSING_BORDERS, true }, // RES_COLLAPSING_BORDERS + // #i28701# + { SID_SW_WRAP_INFLUENCE_ON_OBJPOS, true },// RES_WRAP_INFLUENCE_ON_OBJPOS + { 0, false }, // RES_AUTO_STYLE + { 0, true }, // RES_FRMATR_STYLE_NAME + { 0, true }, // RES_FRMATR_CONDITIONAL_STYLE_NAME + { 0, true }, // RES_FRMATR_GRABBAG + { 0, true }, // RES_TEXT_VERT_ADJUST + + { 0, true }, // RES_GRFATR_MIRRORGRF + { SID_ATTR_GRAF_CROP, true }, // RES_GRFATR_CROPGRF + { 0, true }, // RES_GRFATR_ROTATION, + { 0, true }, // RES_GRFATR_LUMINANCE, + { 0, true }, // RES_GRFATR_CONTRAST, + { 0, true }, // RES_GRFATR_CHANNELR, + { 0, true }, // RES_GRFATR_CHANNELG, + { 0, true }, // RES_GRFATR_CHANNELB, + { 0, true }, // RES_GRFATR_GAMMA, + { 0, true }, // RES_GRFATR_INVERT, + { 0, true }, // RES_GRFATR_TRANSPARENCY, + { 0, true }, // RES_GRFATR_DUMMY1, + { 0, true }, // RES_GRFATR_DUMMY2, + { 0, true }, // RES_GRFATR_DUMMY3, + { 0, true }, // RES_GRFATR_DUMMY4, + { 0, true }, // RES_GRFATR_DUMMY5, + { 0, true }, // RES_GRFATR_DUMMY6, + + { 0, true }, // RES_BOXATR_FORMAT + { 0, false }, // RES_BOXATR_FORMULA, + { 0, true }, // RES_BOXATR_VALUE + + { 0, true } // RES_UNKNOWNATR_CONTAINER +}; + +std::vector *pGlobalOLEExcludeList = nullptr; + +SwAutoCompleteWord* SwDoc::s_pAutoCompleteWords = nullptr; + +SwCheckIt* pCheckIt = nullptr; +static CharClass* pAppCharClass = nullptr; + +static CollatorWrapper* pCollator = nullptr, + *pCaseCollator = nullptr; + +salhelper::SingletonRef* s_getCalendarWrapper() +{ + static salhelper::SingletonRef aCalendarWrapper; + return &aCalendarWrapper; +} + +void InitCore() +{ + SfxPoolItem* pItem; + + aAttrTab[ RES_CHRATR_CASEMAP- POOLATTR_BEGIN ] = new SvxCaseMapItem( SvxCaseMap::NotMapped, RES_CHRATR_CASEMAP); + aAttrTab[ RES_CHRATR_CHARSETCOLOR- POOLATTR_BEGIN ] = new SvxColorItem(RES_CHRATR_CHARSETCOLOR); + aAttrTab[ RES_CHRATR_COLOR- POOLATTR_BEGIN ] = new SvxColorItem(RES_CHRATR_COLOR); + aAttrTab[ RES_CHRATR_CONTOUR- POOLATTR_BEGIN ] = new SvxContourItem( false, RES_CHRATR_CONTOUR ); + aAttrTab[ RES_CHRATR_CROSSEDOUT- POOLATTR_BEGIN ] = new SvxCrossedOutItem( STRIKEOUT_NONE, RES_CHRATR_CROSSEDOUT ); + aAttrTab[ RES_CHRATR_ESCAPEMENT- POOLATTR_BEGIN ] = new SvxEscapementItem( RES_CHRATR_ESCAPEMENT ); + aAttrTab[ RES_CHRATR_FONT- POOLATTR_BEGIN ] = new SvxFontItem( RES_CHRATR_FONT ); + + aAttrTab[ RES_CHRATR_FONTSIZE- POOLATTR_BEGIN ] = new SvxFontHeightItem( 240, 100, RES_CHRATR_FONTSIZE ); + aAttrTab[ RES_CHRATR_KERNING- POOLATTR_BEGIN ] = new SvxKerningItem( 0, RES_CHRATR_KERNING ); + aAttrTab[ RES_CHRATR_LANGUAGE- POOLATTR_BEGIN ] = new SvxLanguageItem(LANGUAGE_DONTKNOW, RES_CHRATR_LANGUAGE ); + aAttrTab[ RES_CHRATR_POSTURE- POOLATTR_BEGIN ] = new SvxPostureItem( ITALIC_NONE, RES_CHRATR_POSTURE ); + aAttrTab[ RES_CHRATR_UNUSED1- POOLATTR_BEGIN ] = new SfxVoidItem( RES_CHRATR_UNUSED1 ); + aAttrTab[ RES_CHRATR_SHADOWED- POOLATTR_BEGIN ] = new SvxShadowedItem( false, RES_CHRATR_SHADOWED ); + aAttrTab[ RES_CHRATR_UNDERLINE- POOLATTR_BEGIN ] = new SvxUnderlineItem( LINESTYLE_NONE, RES_CHRATR_UNDERLINE ); + aAttrTab[ RES_CHRATR_WEIGHT- POOLATTR_BEGIN ] = new SvxWeightItem( WEIGHT_NORMAL, RES_CHRATR_WEIGHT ); + aAttrTab[ RES_CHRATR_RSID - POOLATTR_BEGIN ] = new SvxRsidItem( 0, RES_CHRATR_RSID ); + aAttrTab[ RES_CHRATR_WORDLINEMODE- POOLATTR_BEGIN ] = new SvxWordLineModeItem( false, RES_CHRATR_WORDLINEMODE ); + aAttrTab[ RES_CHRATR_AUTOKERN- POOLATTR_BEGIN ] = new SvxAutoKernItem( false, RES_CHRATR_AUTOKERN ); + aAttrTab[ RES_CHRATR_BLINK - POOLATTR_BEGIN ] = new SvxBlinkItem( false, RES_CHRATR_BLINK ); + aAttrTab[ RES_CHRATR_NOHYPHEN - POOLATTR_BEGIN ] = new SvxNoHyphenItem( RES_CHRATR_NOHYPHEN ); + aAttrTab[ RES_CHRATR_UNUSED2- POOLATTR_BEGIN ] = new SfxVoidItem( RES_CHRATR_UNUSED2 ); + aAttrTab[ RES_CHRATR_BACKGROUND - POOLATTR_BEGIN ] = new SvxBrushItem( RES_CHRATR_BACKGROUND ); + + // CJK-Attributes + aAttrTab[ RES_CHRATR_CJK_FONT - POOLATTR_BEGIN ] = new SvxFontItem( RES_CHRATR_CJK_FONT ); + aAttrTab[ RES_CHRATR_CJK_FONTSIZE - POOLATTR_BEGIN ] = new SvxFontHeightItem( 240, 100, RES_CHRATR_CJK_FONTSIZE ); + aAttrTab[ RES_CHRATR_CJK_LANGUAGE - POOLATTR_BEGIN ] = new SvxLanguageItem(LANGUAGE_DONTKNOW, RES_CHRATR_CJK_LANGUAGE); + aAttrTab[ RES_CHRATR_CJK_POSTURE - POOLATTR_BEGIN ] = new SvxPostureItem(ITALIC_NONE, RES_CHRATR_CJK_POSTURE ); + aAttrTab[ RES_CHRATR_CJK_WEIGHT - POOLATTR_BEGIN ] = new SvxWeightItem( WEIGHT_NORMAL, RES_CHRATR_CJK_WEIGHT ); + + // CTL-Attributes + aAttrTab[ RES_CHRATR_CTL_FONT - POOLATTR_BEGIN ] = new SvxFontItem( RES_CHRATR_CTL_FONT ); + aAttrTab[ RES_CHRATR_CTL_FONTSIZE - POOLATTR_BEGIN ] = new SvxFontHeightItem( 240, 100, RES_CHRATR_CTL_FONTSIZE ); + aAttrTab[ RES_CHRATR_CTL_LANGUAGE - POOLATTR_BEGIN ] = new SvxLanguageItem(LANGUAGE_DONTKNOW, RES_CHRATR_CTL_LANGUAGE); + aAttrTab[ RES_CHRATR_CTL_POSTURE - POOLATTR_BEGIN ] = new SvxPostureItem(ITALIC_NONE, RES_CHRATR_CTL_POSTURE ); + aAttrTab[ RES_CHRATR_CTL_WEIGHT - POOLATTR_BEGIN ] = new SvxWeightItem( WEIGHT_NORMAL, RES_CHRATR_CTL_WEIGHT ); + + aAttrTab[ RES_CHRATR_ROTATE - POOLATTR_BEGIN ] = new SvxCharRotateItem( 0, false, RES_CHRATR_ROTATE ); + aAttrTab[ RES_CHRATR_EMPHASIS_MARK - POOLATTR_BEGIN ] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, RES_CHRATR_EMPHASIS_MARK ); + aAttrTab[ RES_CHRATR_TWO_LINES - POOLATTR_BEGIN ] = new SvxTwoLinesItem( false, 0, 0, RES_CHRATR_TWO_LINES ); + aAttrTab[ RES_CHRATR_SCALEW - POOLATTR_BEGIN ] = new SvxCharScaleWidthItem( 100, RES_CHRATR_SCALEW ); + aAttrTab[ RES_CHRATR_RELIEF - POOLATTR_BEGIN ] = new SvxCharReliefItem( FontRelief::NONE, RES_CHRATR_RELIEF ); + aAttrTab[ RES_CHRATR_HIDDEN - POOLATTR_BEGIN ] = new SvxCharHiddenItem( false, RES_CHRATR_HIDDEN ); + aAttrTab[ RES_CHRATR_OVERLINE- POOLATTR_BEGIN ] = new SvxOverlineItem( LINESTYLE_NONE, RES_CHRATR_OVERLINE ); + aAttrTab[ RES_CHRATR_BOX - POOLATTR_BEGIN ] = new SvxBoxItem( RES_CHRATR_BOX ); + aAttrTab[ RES_CHRATR_SHADOW - POOLATTR_BEGIN ] = new SvxShadowItem( RES_CHRATR_SHADOW ); + aAttrTab[ RES_CHRATR_HIGHLIGHT - POOLATTR_BEGIN ] = new SvxBrushItem( RES_CHRATR_HIGHLIGHT ); + aAttrTab[ RES_CHRATR_GRABBAG - POOLATTR_BEGIN ] = new SfxGrabBagItem( RES_CHRATR_GRABBAG ); + +// CharacterAttr - MSWord weak char direction/script override emulation + aAttrTab[ RES_CHRATR_BIDIRTL - POOLATTR_BEGIN ] = new SfxInt16Item( RES_CHRATR_BIDIRTL, sal_Int16(-1) ); + aAttrTab[ RES_CHRATR_IDCTHINT - POOLATTR_BEGIN ] = new SfxInt16Item( RES_CHRATR_IDCTHINT, sal_Int16(-1) ); + + aAttrTab[ RES_TXTATR_REFMARK - POOLATTR_BEGIN ] = new SwFormatRefMark( OUString() ); + aAttrTab[ RES_TXTATR_TOXMARK - POOLATTR_BEGIN ] = new SwTOXMark; + aAttrTab[ RES_TXTATR_META - POOLATTR_BEGIN ] = SwFormatMeta::CreatePoolDefault(RES_TXTATR_META); + aAttrTab[ RES_TXTATR_METAFIELD - POOLATTR_BEGIN ] = SwFormatMeta::CreatePoolDefault(RES_TXTATR_METAFIELD); + aAttrTab[ RES_TXTATR_AUTOFMT- POOLATTR_BEGIN ] = new SwFormatAutoFormat; + aAttrTab[ RES_TXTATR_INETFMT - POOLATTR_BEGIN ] = new SwFormatINetFormat( OUString(), OUString() ); + aAttrTab[ RES_TXTATR_CHARFMT- POOLATTR_BEGIN ] = new SwFormatCharFormat( nullptr ); + aAttrTab[ RES_TXTATR_CJK_RUBY - POOLATTR_BEGIN ] = new SwFormatRuby( OUString() ); + aAttrTab[ RES_TXTATR_UNKNOWN_CONTAINER - POOLATTR_BEGIN ] = new SvXMLAttrContainerItem( RES_TXTATR_UNKNOWN_CONTAINER ); + aAttrTab[ RES_TXTATR_INPUTFIELD - POOLATTR_BEGIN ] = new SwFormatField( RES_TXTATR_INPUTFIELD ); + + aAttrTab[ RES_TXTATR_FIELD- POOLATTR_BEGIN ] = new SwFormatField( RES_TXTATR_FIELD ); + aAttrTab[ RES_TXTATR_FLYCNT - POOLATTR_BEGIN ] = new SwFormatFlyCnt( nullptr ); + aAttrTab[ RES_TXTATR_FTN - POOLATTR_BEGIN ] = new SwFormatFootnote; + aAttrTab[ RES_TXTATR_ANNOTATION - POOLATTR_BEGIN ] = new SwFormatField( RES_TXTATR_ANNOTATION ); + +// TextAttr - Dummies + aAttrTab[ RES_TXTATR_DUMMY1 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_TXTATR_DUMMY1 ); + aAttrTab[ RES_TXTATR_DUMMY2 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_TXTATR_DUMMY2 ); + aAttrTab[ RES_TXTATR_DUMMY3 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_TXTATR_DUMMY3 ); + + aAttrTab[ RES_PARATR_LINESPACING- POOLATTR_BEGIN ] = new SvxLineSpacingItem( LINE_SPACE_DEFAULT_HEIGHT, RES_PARATR_LINESPACING ); + aAttrTab[ RES_PARATR_ADJUST- POOLATTR_BEGIN ] = new SvxAdjustItem( SvxAdjust::Left, RES_PARATR_ADJUST ); + aAttrTab[ RES_PARATR_SPLIT- POOLATTR_BEGIN ] = new SvxFormatSplitItem( true, RES_PARATR_SPLIT ); + aAttrTab[ RES_PARATR_WIDOWS- POOLATTR_BEGIN ] = new SvxWidowsItem( 0, RES_PARATR_WIDOWS ); + aAttrTab[ RES_PARATR_ORPHANS- POOLATTR_BEGIN ] = new SvxOrphansItem( 0, RES_PARATR_ORPHANS ); + aAttrTab[ RES_PARATR_TABSTOP- POOLATTR_BEGIN ] = new SvxTabStopItem( 1, SVX_TAB_DEFDIST, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + + pItem = new SvxHyphenZoneItem( false, RES_PARATR_HYPHENZONE ); + static_cast(pItem)->GetMaxHyphens() = 0; // Default: 0 + aAttrTab[ RES_PARATR_HYPHENZONE- POOLATTR_BEGIN ] = pItem; + + aAttrTab[ RES_PARATR_DROP- POOLATTR_BEGIN ] = new SwFormatDrop; + aAttrTab[ RES_PARATR_REGISTER - POOLATTR_BEGIN ] = new SwRegisterItem( false ); + aAttrTab[ RES_PARATR_NUMRULE - POOLATTR_BEGIN ] = new SwNumRuleItem( OUString() ); + + aAttrTab[ RES_PARATR_SCRIPTSPACE - POOLATTR_BEGIN ] = new SvxScriptSpaceItem( true, RES_PARATR_SCRIPTSPACE ); + aAttrTab[ RES_PARATR_HANGINGPUNCTUATION - POOLATTR_BEGIN ] = new SvxHangingPunctuationItem( true, RES_PARATR_HANGINGPUNCTUATION ); + aAttrTab[ RES_PARATR_FORBIDDEN_RULES - POOLATTR_BEGIN ] = new SvxForbiddenRuleItem( true, RES_PARATR_FORBIDDEN_RULES ); + aAttrTab[ RES_PARATR_VERTALIGN - POOLATTR_BEGIN ] = new SvxParaVertAlignItem( SvxParaVertAlignItem::Align::Automatic, RES_PARATR_VERTALIGN ); + aAttrTab[ RES_PARATR_SNAPTOGRID - POOLATTR_BEGIN ] = new SvxParaGridItem( true, RES_PARATR_SNAPTOGRID ); + aAttrTab[ RES_PARATR_CONNECT_BORDER - POOLATTR_BEGIN ] = new SwParaConnectBorderItem; + + aAttrTab[ RES_PARATR_OUTLINELEVEL - POOLATTR_BEGIN ] = new SfxUInt16Item( RES_PARATR_OUTLINELEVEL, 0 ); + aAttrTab[ RES_PARATR_RSID - POOLATTR_BEGIN ] = new SvxRsidItem( 0, RES_PARATR_RSID ); + aAttrTab[ RES_PARATR_GRABBAG - POOLATTR_BEGIN ] = new SfxGrabBagItem( RES_PARATR_GRABBAG ); + + aAttrTab[ RES_PARATR_LIST_ID - POOLATTR_BEGIN ] = new SfxStringItem( RES_PARATR_LIST_ID, OUString() ); + aAttrTab[ RES_PARATR_LIST_LEVEL - POOLATTR_BEGIN ] = new SfxInt16Item( RES_PARATR_LIST_LEVEL, 0 ); + aAttrTab[ RES_PARATR_LIST_ISRESTART - POOLATTR_BEGIN ] = new SfxBoolItem( RES_PARATR_LIST_ISRESTART, false ); + aAttrTab[ RES_PARATR_LIST_RESTARTVALUE - POOLATTR_BEGIN ] = new SfxInt16Item( RES_PARATR_LIST_RESTARTVALUE, 1 ); + aAttrTab[ RES_PARATR_LIST_ISCOUNTED - POOLATTR_BEGIN ] = new SfxBoolItem( RES_PARATR_LIST_ISCOUNTED, true ); + aAttrTab[ RES_PARATR_LIST_AUTOFMT - POOLATTR_BEGIN ] = new SwFormatAutoFormat(RES_PARATR_LIST_AUTOFMT);//new SfxSetItem(RES_PARATR_LIST_AUTOFMT, std::make_unique(aCharAutoFormatSetRange)); + + aAttrTab[ RES_FILL_ORDER- POOLATTR_BEGIN ] = new SwFormatFillOrder; + aAttrTab[ RES_FRM_SIZE- POOLATTR_BEGIN ] = new SwFormatFrameSize; + aAttrTab[ RES_PAPER_BIN- POOLATTR_BEGIN ] = new SvxPaperBinItem( RES_PAPER_BIN ); + aAttrTab[ RES_LR_SPACE- POOLATTR_BEGIN ] = new SvxLRSpaceItem( RES_LR_SPACE ); + aAttrTab[ RES_UL_SPACE- POOLATTR_BEGIN ] = new SvxULSpaceItem( RES_UL_SPACE ); + aAttrTab[ RES_PAGEDESC- POOLATTR_BEGIN ] = new SwFormatPageDesc; + aAttrTab[ RES_BREAK- POOLATTR_BEGIN ] = new SvxFormatBreakItem( SvxBreak::NONE, RES_BREAK); + aAttrTab[ RES_CNTNT- POOLATTR_BEGIN ] = new SwFormatContent; + aAttrTab[ RES_HEADER- POOLATTR_BEGIN ] = new SwFormatHeader; + aAttrTab[ RES_FOOTER- POOLATTR_BEGIN ] = new SwFormatFooter; + aAttrTab[ RES_PRINT- POOLATTR_BEGIN ] = new SvxPrintItem( RES_PRINT ); + aAttrTab[ RES_OPAQUE- POOLATTR_BEGIN ] = new SvxOpaqueItem( RES_OPAQUE ); + aAttrTab[ RES_PROTECT- POOLATTR_BEGIN ] = new SvxProtectItem( RES_PROTECT ); + aAttrTab[ RES_SURROUND- POOLATTR_BEGIN ] = new SwFormatSurround; + aAttrTab[ RES_VERT_ORIENT- POOLATTR_BEGIN ] = new SwFormatVertOrient; + aAttrTab[ RES_HORI_ORIENT- POOLATTR_BEGIN ] = new SwFormatHoriOrient; + aAttrTab[ RES_ANCHOR- POOLATTR_BEGIN ] = new SwFormatAnchor; + aAttrTab[ RES_BACKGROUND- POOLATTR_BEGIN ] = new SvxBrushItem( RES_BACKGROUND ); + aAttrTab[ RES_BOX- POOLATTR_BEGIN ] = new SvxBoxItem( RES_BOX ); + aAttrTab[ RES_SHADOW- POOLATTR_BEGIN ] = new SvxShadowItem( RES_SHADOW ); + aAttrTab[ RES_FRMMACRO- POOLATTR_BEGIN ] = new SvxMacroItem( RES_FRMMACRO ); + aAttrTab[ RES_COL- POOLATTR_BEGIN ] = new SwFormatCol; + aAttrTab[ RES_KEEP - POOLATTR_BEGIN ] = new SvxFormatKeepItem( false, RES_KEEP ); + aAttrTab[ RES_URL - POOLATTR_BEGIN ] = new SwFormatURL(); + aAttrTab[ RES_EDIT_IN_READONLY - POOLATTR_BEGIN ] = new SwFormatEditInReadonly; + aAttrTab[ RES_LAYOUT_SPLIT - POOLATTR_BEGIN ] = new SwFormatLayoutSplit; + aAttrTab[ RES_CHAIN - POOLATTR_BEGIN ] = new SwFormatChain; + aAttrTab[ RES_TEXTGRID - POOLATTR_BEGIN ] = new SwTextGridItem; + aAttrTab[ RES_HEADER_FOOTER_EAT_SPACING - POOLATTR_BEGIN ] = new SwHeaderAndFooterEatSpacingItem; + aAttrTab[ RES_LINENUMBER - POOLATTR_BEGIN ] = new SwFormatLineNumber; + aAttrTab[ RES_FTN_AT_TXTEND - POOLATTR_BEGIN ] = new SwFormatFootnoteAtTextEnd; + aAttrTab[ RES_END_AT_TXTEND - POOLATTR_BEGIN ] = new SwFormatEndAtTextEnd; + aAttrTab[ RES_COLUMNBALANCE - POOLATTR_BEGIN ] = new SwFormatNoBalancedColumns; + aAttrTab[ RES_FRAMEDIR - POOLATTR_BEGIN ] = new SvxFrameDirectionItem( SvxFrameDirection::Environment, RES_FRAMEDIR ); + aAttrTab[ RES_ROW_SPLIT - POOLATTR_BEGIN ] = new SwFormatRowSplit; + + // #i18732# + aAttrTab[ RES_FOLLOW_TEXT_FLOW - POOLATTR_BEGIN ] = new SwFormatFollowTextFlow(false); + // collapsing borders #i29550# + aAttrTab[ RES_COLLAPSING_BORDERS - POOLATTR_BEGIN ] = new SfxBoolItem( RES_COLLAPSING_BORDERS, false ); + // #i28701# + // #i35017# - constant name has changed + aAttrTab[ RES_WRAP_INFLUENCE_ON_OBJPOS - POOLATTR_BEGIN ] = new SwFormatWrapInfluenceOnObjPos( text::WrapInfluenceOnPosition::ONCE_CONCURRENT ); + + aAttrTab[ RES_AUTO_STYLE - POOLATTR_BEGIN ] = new SwFormatAutoFormat( RES_AUTO_STYLE ); + aAttrTab[ RES_FRMATR_STYLE_NAME - POOLATTR_BEGIN ] = new SfxStringItem( RES_FRMATR_STYLE_NAME, OUString()); + aAttrTab[ RES_FRMATR_CONDITIONAL_STYLE_NAME - POOLATTR_BEGIN ] = new SfxStringItem( RES_FRMATR_CONDITIONAL_STYLE_NAME, OUString() ); + aAttrTab[ RES_FRMATR_GRABBAG - POOLATTR_BEGIN ] = new SfxGrabBagItem(RES_FRMATR_GRABBAG); + aAttrTab[ RES_TEXT_VERT_ADJUST - POOLATTR_BEGIN ] = new SdrTextVertAdjustItem(SDRTEXTVERTADJUST_TOP,RES_TEXT_VERT_ADJUST); + + aAttrTab[ RES_GRFATR_MIRRORGRF- POOLATTR_BEGIN ] = new SwMirrorGrf; + aAttrTab[ RES_GRFATR_CROPGRF- POOLATTR_BEGIN ] = new SwCropGrf; + aAttrTab[ RES_GRFATR_ROTATION - POOLATTR_BEGIN ] = new SwRotationGrf; + aAttrTab[ RES_GRFATR_LUMINANCE - POOLATTR_BEGIN ] = new SwLuminanceGrf; + aAttrTab[ RES_GRFATR_CONTRAST - POOLATTR_BEGIN ] = new SwContrastGrf; + aAttrTab[ RES_GRFATR_CHANNELR - POOLATTR_BEGIN ] = new SwChannelRGrf; + aAttrTab[ RES_GRFATR_CHANNELG - POOLATTR_BEGIN ] = new SwChannelGGrf; + aAttrTab[ RES_GRFATR_CHANNELB - POOLATTR_BEGIN ] = new SwChannelBGrf; + aAttrTab[ RES_GRFATR_GAMMA - POOLATTR_BEGIN ] = new SwGammaGrf; + aAttrTab[ RES_GRFATR_INVERT - POOLATTR_BEGIN ] = new SwInvertGrf; + aAttrTab[ RES_GRFATR_TRANSPARENCY - POOLATTR_BEGIN ] = new SwTransparencyGrf; + aAttrTab[ RES_GRFATR_DRAWMODE - POOLATTR_BEGIN ] = new SwDrawModeGrf; + +// GraphicAttr - Dummies + aAttrTab[ RES_GRFATR_DUMMY1 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_GRFATR_DUMMY1 ); + aAttrTab[ RES_GRFATR_DUMMY2 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_GRFATR_DUMMY2 ); + aAttrTab[ RES_GRFATR_DUMMY3 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_GRFATR_DUMMY3 ); + aAttrTab[ RES_GRFATR_DUMMY4 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_GRFATR_DUMMY4 ); + aAttrTab[ RES_GRFATR_DUMMY5 - POOLATTR_BEGIN ] = new SfxBoolItem( RES_GRFATR_DUMMY5 ); + + aAttrTab[ RES_BOXATR_FORMAT- POOLATTR_BEGIN ] = new SwTableBoxNumFormat; + aAttrTab[ RES_BOXATR_FORMULA- POOLATTR_BEGIN ] = new SwTableBoxFormula( OUString() ); + aAttrTab[ RES_BOXATR_VALUE- POOLATTR_BEGIN ] = new SwTableBoxValue; + + aAttrTab[ RES_UNKNOWNATR_CONTAINER- POOLATTR_BEGIN ] = + new SvXMLAttrContainerItem( RES_UNKNOWNATR_CONTAINER ); + + // get the correct fonts: + ::GetDefaultFonts( *static_cast(aAttrTab[ RES_CHRATR_FONT- POOLATTR_BEGIN ]), + *static_cast(aAttrTab[ RES_CHRATR_CJK_FONT - POOLATTR_BEGIN ]), + *static_cast(aAttrTab[ RES_CHRATR_CTL_FONT - POOLATTR_BEGIN ]) ); + + SwBreakIt::Create_( ::comphelper::getProcessComponentContext() ); + pCheckIt = nullptr; + + FrameInit(); + TextInit_(); + + SwSelPaintRects::s_pMapMode = new MapMode; + SwFntObj::pPixMap = new MapMode; + + pGlobalOLEExcludeList = new std::vector; + + if (!utl::ConfigManager::IsFuzzing()) + { + const SvxSwAutoFormatFlags& rAFlags = SvxAutoCorrCfg::Get().GetAutoCorrect()->GetSwFlags(); + SwDoc::s_pAutoCompleteWords = new SwAutoCompleteWord( rAFlags.nAutoCmpltListLen, + rAFlags.nAutoCmpltWordLen ); + } + else + { + SwDoc::s_pAutoCompleteWords = new SwAutoCompleteWord( 0, 0 ); + } +} + +void FinitCore() +{ + FrameFinit(); + TextFinit(); + + sw::proofreadingiterator::dispose(); + SwBreakIt::Delete_(); + delete pCheckIt; + delete pAppCharClass; + delete pCollator; + delete pCaseCollator; + + // destroy default TableAutoFormat + delete SwTableAutoFormat::pDfltBoxAutoFormat; + + delete SwSelPaintRects::s_pMapMode; + delete SwFntObj::pPixMap; + + delete SwEditShell::s_pAutoFormatFlags; + +#if OSL_DEBUG_LEVEL > 0 + // free defaults to prevent assertions + if ( aAttrTab[0]->GetRefCount() ) + SfxItemPool::ReleaseDefaults( &aAttrTab ); +#endif + delete SwDoc::s_pAutoCompleteWords; + + // delete all default attributes + for(SfxPoolItem* pHt : aAttrTab) + { + delete pHt; + } + + delete pGlobalOLEExcludeList; +} + +// returns the APP - CharClass instance - used for all ToUpper/ToLower/... +CharClass& GetAppCharClass() +{ + if ( !pAppCharClass ) + { + pAppCharClass = new CharClass( + ::comphelper::getProcessComponentContext(), + SwBreakIt::Get()->GetLanguageTag( GetAppLanguageTag() )); + } + return *pAppCharClass; +} + +void SwCalendarWrapper::LoadDefaultCalendar( LanguageType eLang ) +{ + if( eLang != m_nLang ) + { + m_nLang = eLang; + loadDefaultCalendar( LanguageTag::convertToLocale( m_nLang )); + } +} + +LanguageType GetAppLanguage() +{ + if (!utl::ConfigManager::IsFuzzing()) + return Application::GetSettings().GetLanguageTag().getLanguageType(); + return LANGUAGE_ENGLISH_US; +} + +const LanguageTag& GetAppLanguageTag() +{ + return Application::GetSettings().GetLanguageTag(); +} + +CollatorWrapper& GetAppCollator() +{ + if( !pCollator ) + { + const lang::Locale& rLcl = g_pBreakIt->GetLocale( GetAppLanguage() ); + + pCollator = new CollatorWrapper( ::comphelper::getProcessComponentContext() ); + pCollator->loadDefaultCollator( rLcl, SW_COLLATOR_IGNORES ); + } + return *pCollator; +} + +CollatorWrapper& GetAppCaseCollator() +{ + if( !pCaseCollator ) + { + const lang::Locale& rLcl = g_pBreakIt->GetLocale( GetAppLanguage() ); + + pCaseCollator = new CollatorWrapper( ::comphelper::getProcessComponentContext() ); + pCaseCollator->loadDefaultCollator( rLcl, 0 ); + } + return *pCaseCollator; +} + +namespace +{ + class TransWrp + { + private: + std::unique_ptr m_xTransWrp; + public: + TransWrp() + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + m_xTransWrp.reset(new ::utl::TransliterationWrapper( xContext, + TransliterationFlags::IGNORE_CASE | + TransliterationFlags::IGNORE_KANA | + TransliterationFlags::IGNORE_WIDTH )); + + m_xTransWrp->loadModuleIfNeeded( GetAppLanguage() ); + } + const ::utl::TransliterationWrapper& getTransliterationWrapper() const + { + return *m_xTransWrp; + } + }; + + class theTransWrp : public rtl::Static {}; +} + +const ::utl::TransliterationWrapper& GetAppCmpStrIgnore() +{ + return theTransWrp::get().getTransliterationWrapper(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/proofreadingiterator.cxx b/sw/source/core/bastyp/proofreadingiterator.cxx new file mode 100644 index 000000000..7d09b1676 --- /dev/null +++ b/sw/source/core/bastyp/proofreadingiterator.cxx @@ -0,0 +1,68 @@ +/* -*- 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 + +namespace { + +css::uno::Reference instance; +bool disposed = false; + +void doDispose( + css::uno::Reference const & + inst) +{ + css::uno::Reference comp(inst, css::uno::UNO_QUERY); + if (comp.is()) { + SolarMutexReleaser r; + comp->dispose(); + } +} + +} + +css::uno::Reference +sw::proofreadingiterator::get( + css::uno::Reference const & context) +{ + css::uno::Reference inst( + css::linguistic2::ProofreadingIterator::create(context)); + bool disp; + { + SolarMutexGuard g; + instance = inst; + disp = disposed; + } + if (disp) { + doDispose(inst); + } + return inst; +} + +void sw::proofreadingiterator::dispose() { + css::uno::Reference inst; + { + SolarMutexGuard g; + inst = instance; + instance.clear(); + disposed = true; + } + doDispose(inst); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/swcache.cxx b/sw/source/core/bastyp/swcache.cxx new file mode 100644 index 000000000..222700005 --- /dev/null +++ b/sw/source/core/bastyp/swcache.cxx @@ -0,0 +1,512 @@ +/* -*- 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 + +#ifdef DBG_UTIL +void SwCache::Check() +{ + if ( !m_pRealFirst ) + { + assert(m_pFirst == nullptr && m_pLast == nullptr); + return; + } + + // consistency check + assert(m_pLast->GetNext() == nullptr); + assert(m_pRealFirst->GetPrev() == nullptr); + sal_uInt16 nCnt = 0; + bool bFirstFound = false; + SwCacheObj *pObj = m_pRealFirst; + SwCacheObj *const pOldRealFirst = m_pRealFirst; + while ( pObj ) + { + // the object must be found also when moving backwards + SwCacheObj *pTmp = m_pLast; + while ( pTmp && pTmp != pObj ) + pTmp = pTmp->GetPrev(); + assert(pTmp && "Object not found."); + + ++nCnt; + if ( pObj == m_pFirst ) + bFirstFound = true; + if ( !pObj->GetNext() ) + { + assert(pObj == m_pLast); + } + pObj = pObj->GetNext(); + assert(pObj != pOldRealFirst); (void) pOldRealFirst; + } + assert(bFirstFound); + SAL_WARN_IF( nCnt + m_aFreePositions.size() != size(), "sw.core", "Lost Chain." ); + SAL_WARN_IF( + size() == m_nCurMax && m_nCurMax != m_aFreePositions.size() + nCnt, "sw.core", + "Lost FreePositions." ); +} + +#define INCREMENT( nVar ) ++nVar +#define CHECK Check(); + +#else +#define INCREMENT( nVar ) +#define CHECK +#endif + +SwCache::SwCache( const sal_uInt16 nInitSize +#ifdef DBG_UTIL + , const OString &rNm +#endif + ) : + m_aCacheObjects(), + m_pRealFirst( nullptr ), + m_pFirst( nullptr ), + m_pLast( nullptr ), + m_nCurMax( nInitSize ) +#ifdef DBG_UTIL + , m_aName( rNm ) + , m_nAppend( 0 ) + , m_nInsertFree( 0 ) + , m_nReplace( 0 ) + , m_nGetSuccess( 0 ) + , m_nGetFail( 0 ) + , m_nToTop( 0 ) + , m_nDelete( 0 ) + , m_nGetSeek( 0 ) + , m_nAverageSeekCnt( 0 ) + , m_nFlushCnt( 0 ) + , m_nFlushedObjects( 0 ) + , m_nIncreaseMax( 0 ) + , m_nDecreaseMax( 0 ) +#endif +{ + m_aCacheObjects.reserve( nInitSize ); +} + +SwCache::~SwCache() +{ +#ifdef DBG_UTIL + SAL_INFO( + "sw.core", + m_aName << "; number of new entries: " << m_nAppend + << "; number of insert on free places: " << m_nInsertFree + << "; number of replacements: " << m_nReplace + << "; number of successful Gets: " << m_nGetSuccess + << "; number of failed Gets: " << m_nGetFail + << "; number or reordering (LRU): " << m_nToTop + << "; number of suppressions: " << m_nDelete + << "; number of Gets without Index: " << m_nGetSeek + << "; number of Seek for Get without Index: " << m_nAverageSeekCnt + << "; number of Flush calls: " << m_nFlushCnt + << "; number of flushed objects: " << m_nFlushedObjects + << "; number of Cache expansions: " << m_nIncreaseMax + << "; number of Cache reductions: " << m_nDecreaseMax); + Check(); +#endif +} + +void SwCache::IncreaseMax( const sal_uInt16 nAdd ) +{ + if (o3tl::checked_add(m_nCurMax, nAdd, m_nCurMax)) + { + std::abort(); + } +#ifdef DBG_UTIL + ++m_nIncreaseMax; +#endif +} + +void SwCache::DecreaseMax( const sal_uInt16 nSub ) +{ + if ( m_nCurMax > nSub ) + m_nCurMax = m_nCurMax - sal::static_int_cast< sal_uInt16 >(nSub); +#ifdef DBG_UTIL + ++m_nDecreaseMax; +#endif +} + +void SwCache::Flush() +{ + INCREMENT( m_nFlushCnt ); + SwCacheObj *pObj = m_pRealFirst; + m_pRealFirst = m_pFirst = m_pLast = nullptr; + SwCacheObj *pTmp; + while ( pObj ) + { + assert(!pObj->IsLocked()); + { + pTmp = pObj; + pObj = pTmp->GetNext(); + m_aFreePositions.push_back( pTmp->GetCachePos() ); + m_aCacheObjects[pTmp->GetCachePos()].reset(); // deletes pTmp + INCREMENT( m_nFlushedObjects ); + } + } +} + +void SwCache::ToTop( SwCacheObj *pObj ) +{ + INCREMENT( m_nToTop ); + + // cut object out of chain and insert at beginning + if ( m_pRealFirst == pObj ) // pFirst was checked by caller + { + CHECK; + return; + } + + if ( !m_pRealFirst ) + { + // the first will be inserted + assert(!m_pFirst && !m_pLast); + m_pRealFirst = m_pFirst = m_pLast = pObj; + CHECK; + return; + } + + assert(m_pFirst && m_pLast); + + // cut + if ( pObj == m_pLast ) + { + assert(pObj->GetPrev()); + m_pLast = pObj->GetPrev(); + m_pLast->SetNext( nullptr ); + } + else + { + if ( pObj->GetNext() ) + pObj->GetNext()->SetPrev( pObj->GetPrev() ); + if ( pObj->GetPrev() ) + pObj->GetPrev()->SetNext( pObj->GetNext() ); + } + + // paste at the (virtual) beginning + if ( m_pRealFirst == m_pFirst ) + { + m_pRealFirst->SetPrev( pObj ); + pObj->SetNext( m_pRealFirst ); + pObj->SetPrev( nullptr ); + m_pRealFirst = m_pFirst = pObj; + CHECK; + } + else + { + if ( m_pFirst->GetPrev() ) + { + m_pFirst->GetPrev()->SetNext( pObj ); + pObj->SetPrev( m_pFirst->GetPrev() ); + } + else + pObj->SetPrev( nullptr ); + m_pFirst->SetPrev( pObj ); + pObj->SetNext( m_pFirst ); + m_pFirst = pObj; + CHECK; + } +} + +SwCacheObj *SwCache::Get( const void *pOwner, const sal_uInt16 nIndex, + const bool bToTop ) +{ + SwCacheObj *pRet; + if ( nullptr != (pRet = (nIndex < m_aCacheObjects.size()) ? m_aCacheObjects[ nIndex ].get() : nullptr) ) + { + if ( !pRet->IsOwner( pOwner ) ) + pRet = nullptr; + else if ( bToTop && pRet != m_pFirst ) + ToTop( pRet ); + } + +#ifdef DBG_UTIL + if ( pRet ) + ++m_nGetSuccess; + else + ++m_nGetFail; +#endif + + return pRet; +} + +SwCacheObj *SwCache::Get( const void *pOwner, const bool bToTop ) +{ + SwCacheObj *pRet = m_pRealFirst; + while ( pRet && !pRet->IsOwner( pOwner ) ) + { + INCREMENT( m_nAverageSeekCnt ); + pRet = pRet->GetNext(); + } + + if ( bToTop && pRet && pRet != m_pFirst ) + ToTop( pRet ); + +#ifdef DBG_UTIL + if ( pRet ) + ++m_nGetSuccess; + else + ++m_nGetFail; + ++m_nGetSeek; +#endif + return pRet; +} + +void SwCache::DeleteObj( SwCacheObj *pObj ) +{ + CHECK; + OSL_ENSURE( !pObj->IsLocked(), "SwCache::Delete: object is locked." ); + if ( pObj->IsLocked() ) + return; + + if ( m_pFirst == pObj ) + { + if ( m_pFirst->GetNext() ) + m_pFirst = m_pFirst->GetNext(); + else + m_pFirst = m_pFirst->GetPrev(); + } + if ( m_pRealFirst == pObj ) + m_pRealFirst = m_pRealFirst->GetNext(); + if ( m_pLast == pObj ) + m_pLast = m_pLast->GetPrev(); + if ( pObj->GetPrev() ) + pObj->GetPrev()->SetNext( pObj->GetNext() ); + if ( pObj->GetNext() ) + pObj->GetNext()->SetPrev( pObj->GetPrev() ); + + m_aFreePositions.push_back( pObj->GetCachePos() ); + assert(m_aCacheObjects[pObj->GetCachePos()].get() == pObj); + m_aCacheObjects[pObj->GetCachePos()] = nullptr; // deletes pObj + + CHECK; + if ( m_aCacheObjects.size() > m_nCurMax && + (m_nCurMax <= (m_aCacheObjects.size() - m_aFreePositions.size())) ) + { + // Shrink if possible.To do so we need enough free positions. + // Unpleasant side effect: positions will be moved and the owner of + // these might not find them afterwards + for ( size_t i = 0; i < m_aCacheObjects.size(); ++i ) + { + SwCacheObj *pTmpObj = m_aCacheObjects[i].get(); + if ( !pTmpObj ) + { + m_aCacheObjects.erase( m_aCacheObjects.begin() + i ); + --i; + } + else + { + pTmpObj->SetCachePos( i ); + } + } + m_aFreePositions.clear(); + } + CHECK; +} + +void SwCache::Delete(void const*const pOwner, sal_uInt16 const nIndex) +{ + INCREMENT( m_nDelete ); + if (SwCacheObj *const pObj = Get(pOwner, nIndex, false)) + { + DeleteObj(pObj); + } +} + +void SwCache::Delete( const void *pOwner ) +{ + INCREMENT( m_nDelete ); + SwCacheObj *pObj; + if ( nullptr != (pObj = Get( pOwner, false )) ) + DeleteObj( pObj ); +} + +bool SwCache::Insert(SwCacheObj *const pNew, bool const isDuplicateOwnerAllowed) +{ + CHECK; + OSL_ENSURE( !pNew->GetPrev() && !pNew->GetNext(), "New but not new." ); + if (!isDuplicateOwnerAllowed) + { + for (auto const & rpObj : m_aCacheObjects) + { // check owner doesn't have a cache object yet; required for SwTextLine + assert(!rpObj || rpObj->GetOwner() != pNew->GetOwner()); + (void) rpObj; + } + } + + sal_uInt16 nPos; + if ( m_aCacheObjects.size() < m_nCurMax ) + { + // there is still space; insert directly + INCREMENT( m_nAppend ); + nPos = m_aCacheObjects.size(); + m_aCacheObjects.emplace_back(pNew); + } + else if ( !m_aFreePositions.empty() ) + { + // there are placeholders; use the last of those + INCREMENT( m_nInsertFree ); + const sal_uInt16 nFreePos = m_aFreePositions.size() - 1; + nPos = m_aFreePositions[ nFreePos ]; + m_aCacheObjects[nPos].reset(pNew); + m_aFreePositions.erase( m_aFreePositions.begin() + nFreePos ); + } + else + { + INCREMENT( m_nReplace ); + // the last of the LRU has to go + SwCacheObj *pObj = m_pLast; + + while ( pObj && pObj->IsLocked() ) + pObj = pObj->GetPrev(); + if ( !pObj ) + { + SAL_WARN("sw.core", "SwCache overflow."); + IncreaseMax(100); // embiggen & try again + return Insert(pNew, isDuplicateOwnerAllowed); + } + + nPos = pObj->GetCachePos(); + if ( pObj == m_pLast ) + { + m_pLast = pObj->GetPrev(); + assert(m_pLast); // must have capacity > 1 + } + if (pObj == m_pFirst) + { + if (pObj->GetNext()) + { + m_pFirst = pObj->GetNext(); + } + else + { + m_pFirst = pObj->GetPrev(); + } + assert(m_pFirst); // must have capacity > 1 + } + if (pObj == m_pRealFirst) + { + m_pRealFirst = pObj->GetNext(); + assert(m_pRealFirst); // must have capacity > 1 + } + if (pObj->GetPrev()) + { + pObj->GetPrev()->SetNext( pObj->GetNext() ); + } + if (pObj->GetNext()) + { + pObj->GetNext()->SetPrev( pObj->GetPrev() ); + } + m_aCacheObjects[nPos].reset(pNew); + } + pNew->SetCachePos( nPos ); + + if ( m_pFirst ) + { + if ( m_pFirst->GetPrev() ) + { m_pFirst->GetPrev()->SetNext( pNew ); + pNew->SetPrev( m_pFirst->GetPrev() ); + } + m_pFirst->SetPrev( pNew ); + pNew->SetNext( m_pFirst ); + } + else + { + assert(!m_pLast); + m_pLast = pNew; + } + if ( m_pFirst == m_pRealFirst ) + m_pRealFirst = pNew; + m_pFirst = pNew; + + CHECK; + return true; +} + +void SwCache::SetLRUOfst( const sal_uInt16 nOfst ) +{ + assert(nOfst < m_nCurMax); + if ( !m_pRealFirst || ((m_aCacheObjects.size() - m_aFreePositions.size()) < nOfst) ) + return; + + CHECK; + m_pFirst = m_pRealFirst; + for ( sal_uInt16 i = 0; i < m_aCacheObjects.size() && i < nOfst; ++i ) + { + if ( m_pFirst->GetNext() && m_pFirst->GetNext()->GetNext() ) + m_pFirst = m_pFirst->GetNext(); + else + break; + } + CHECK; +} + +SwCacheObj::SwCacheObj( const void *pOwn ) : + m_pNext( nullptr ), + m_pPrev( nullptr ), + m_nCachePos( USHRT_MAX ), + m_nLock( 0 ), + m_pOwner( pOwn ) +{ +} + +SwCacheObj::~SwCacheObj() +{ +} + +#ifdef DBG_UTIL +void SwCacheObj::Lock() +{ + OSL_ENSURE( m_nLock < UCHAR_MAX, "Too many Locks for CacheObject." ); + ++m_nLock; +} + +void SwCacheObj::Unlock() +{ + OSL_ENSURE( m_nLock, "No more Locks available." ); + --m_nLock; +} +#endif + +SwCacheAccess::~SwCacheAccess() +{ + if ( m_pObj ) + m_pObj->Unlock(); +} + +void SwCacheAccess::Get_(bool const isDuplicateOwnerAllowed) +{ + OSL_ENSURE( !m_pObj, "SwCacheAcces Obj already available." ); + + m_pObj = NewObj(); + if (!m_rCache.Insert(m_pObj, isDuplicateOwnerAllowed)) + { + delete m_pObj; + m_pObj = nullptr; + } + else + { + m_pObj->Lock(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/swrect.cxx b/sw/source/core/bastyp/swrect.cxx new file mode 100644 index 000000000..d443c85d9 --- /dev/null +++ b/sw/source/core/bastyp/swrect.cxx @@ -0,0 +1,243 @@ +/* -*- 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 + +#ifdef DBG_UTIL +#include +#endif + +SwRect::SwRect( const tools::Rectangle &rRect ) : + m_Point( rRect.Left(), rRect.Top() ) +{ + m_Size.setWidth( rRect.IsWidthEmpty() ? 0 : rRect.Right() - rRect.Left() + 1); + m_Size.setHeight(rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top() + 1); +} + +Point SwRect::Center() const +{ + return Point( Left() + Width() / 2, + Top() + Height() / 2 ); +} + +SwRect& SwRect::Union( const SwRect& rRect ) +{ + if ( Top() > rRect.Top() ) + Top( rRect.Top() ); + if ( Left() > rRect.Left() ) + Left( rRect.Left() ); + long n = rRect.Right(); + if ( Right() < n ) + Right( n ); + n = rRect.Bottom(); + if ( Bottom() < n ) + Bottom( n ); + return *this; +} + +SwRect& SwRect::Intersection( const SwRect& rRect ) +{ + // any similarity between me and given element? + if ( IsOver( rRect ) ) + { + // get smaller right and lower, and greater left and upper edge + if ( Left() < rRect.Left() ) + Left( rRect.Left() ); + if ( Top() < rRect.Top() ) + Top( rRect.Top() ); + long n = rRect.Right(); + if ( Right() > n ) + Right( n ); + n = rRect.Bottom(); + if ( Bottom() > n ) + Bottom( n ); + } + else + // Def.: if intersection is empty, set only SSize to 0 + SSize(0, 0); + + return *this; +} + +SwRect& SwRect::Intersection_( const SwRect& rOther ) +{ + // get smaller right and lower, and greater left and upper edge + auto left = std::max( m_Point.X(), rOther.m_Point.X() ); + auto top = std::max( m_Point.Y(), rOther.m_Point.Y() ); + long right = std::min( m_Point.X() + m_Size.Width(), rOther.m_Point.X() + rOther.m_Size.Width() ); + auto bottom = std::min( m_Point.Y() + m_Size.Height(), rOther.m_Point.Y() + rOther.m_Size.Height() ); + + *this = SwRect( left, top, right - left, bottom - top ); + + return *this; +} + +bool SwRect::IsInside( const SwRect& rRect ) const +{ + const long nRight = Right(); + const long nBottom = Bottom(); + const long nrRight = rRect.Right(); + const long nrBottom= rRect.Bottom(); + return (Left() <= rRect.Left()) && (rRect.Left()<= nRight) && + (Left() <= nrRight) && (nrRight <= nRight) && + (Top() <= rRect.Top()) && (rRect.Top() <= nBottom) && + (Top() <= nrBottom) && (nrBottom <= nBottom); +} + +bool SwRect::IsInside( const Point& rPoint ) const +{ + return (Left() <= rPoint.X()) && + (Top() <= rPoint.Y()) && + (Right() >= rPoint.X()) && + (Bottom()>= rPoint.Y()); +} + +// mouse moving of table borders +bool SwRect::IsNear( const Point& rPoint, long nTolerance ) const +{ + bool bIsNearby = (((Left() - nTolerance) <= rPoint.X()) && + ((Top() - nTolerance) <= rPoint.Y()) && + ((Right() + nTolerance) >= rPoint.X()) && + ((Bottom() + nTolerance) >= rPoint.Y())); + return IsInside(rPoint) || bIsNearby; +} + +bool SwRect::IsOver( const SwRect& rRect ) const +{ + return (Top() <= rRect.Bottom()) && + (Left() <= rRect.Right()) && + (Right() >= rRect.Left()) && + (Bottom()>= rRect.Top()); +} + +void SwRect::Justify() +{ + if ( m_Size.getHeight() < 0 ) + { + m_Point.setY(m_Point.getY() + m_Size.getHeight() + 1); + m_Size.setHeight(-m_Size.getHeight()); + } + if ( m_Size.getWidth() < 0 ) + { + m_Point.setX(m_Point.getX() + m_Size.getWidth() + 1); + m_Size.setWidth(-m_Size.getWidth()); + } +} + +// Similar to the inline methods, but we need the function pointers +void SwRect::Width_( const long nNew ) { m_Size.setWidth(nNew); } +void SwRect::Height_( const long nNew ) { m_Size.setHeight(nNew); } +void SwRect::Left_( const long nLeft ){ m_Size.AdjustWidth(m_Point.getX() - nLeft ); m_Point.setX(nLeft); } +void SwRect::Right_( const long nRight ){ m_Size.setWidth(nRight - m_Point.getX()); } +void SwRect::Top_( const long nTop ){ m_Size.AdjustHeight(m_Point.getY() - nTop ); m_Point.setY(nTop); } +void SwRect::Bottom_( const long nBottom ){ m_Size.setHeight(nBottom - m_Point.getY()); } + +long SwRect::Width_() const{ return m_Size.getWidth(); } +long SwRect::Height_() const{ return m_Size.getHeight(); } +long SwRect::Left_() const{ return m_Point.getX(); } +long SwRect::Right_() const{ return m_Point.getX() + m_Size.getWidth(); } +long SwRect::Top_() const{ return m_Point.getY(); } +long SwRect::Bottom_() const{ return m_Point.getY() + m_Size.getHeight(); } + +void SwRect::AddWidth( const long nAdd ) { m_Size.AdjustWidth(nAdd ); } +void SwRect::AddHeight( const long nAdd ) { m_Size.AdjustHeight(nAdd ); } +void SwRect::AddLeft( const long nAdd ){ m_Size.AdjustWidth(-nAdd ); m_Point.setX(m_Point.getX() + nAdd); } +void SwRect::SubLeft( const long nSub ){ m_Size.AdjustWidth(nSub ); m_Point.setX(m_Point.getX() - nSub); } +void SwRect::AddRight( const long nAdd ){ m_Size.AdjustWidth(nAdd ); } +void SwRect::AddTop( const long nAdd ){ m_Size.AdjustHeight(-nAdd ); m_Point.setY(m_Point.getY() + nAdd); } +void SwRect::SubTop( const long nSub ){ m_Size.AdjustHeight(nSub ); m_Point.setY(m_Point.getY() - nSub); } +void SwRect::AddBottom( const long nAdd ){ m_Size.AdjustHeight(nAdd ); } +void SwRect::SetPosX( const long nNew ){ m_Point.setX(nNew); } +void SwRect::SetPosY( const long nNew ){ m_Point.setY(nNew); } + +Size SwRect::Size_() const { return SSize(); } +Size SwRect::SwappedSize() const { return Size( m_Size.getHeight(), m_Size.getWidth() ); } + +Point SwRect::TopLeft() const { return Pos(); } +Point SwRect::TopRight() const { return Point( m_Point.getX() + m_Size.getWidth(), m_Point.getY() ); } +Point SwRect::BottomLeft() const { return Point( m_Point.getX(), m_Point.getY() + m_Size.getHeight() ); } +Point SwRect::BottomRight() const + { return Point( m_Point.getX() + m_Size.getWidth(), m_Point.getY() + m_Size.getHeight() ); } + +long SwRect::GetLeftDistance( long nLimit ) const { return m_Point.getX() - nLimit; } +long SwRect::GetBottomDistance( long nLim ) const { return nLim - m_Point.getY() - m_Size.getHeight();} +long SwRect::GetTopDistance( long nLimit ) const { return m_Point.getY() - nLimit; } +long SwRect::GetRightDistance( long nLim ) const { return nLim - m_Point.getX() - m_Size.getWidth(); } + +bool SwRect::OverStepLeft( long nLimit ) const + { return nLimit > m_Point.getX() && m_Point.getX() + m_Size.getWidth() > nLimit; } +bool SwRect::OverStepBottom( long nLimit ) const + { return nLimit > m_Point.getY() && m_Point.getY() + m_Size.getHeight() > nLimit; } +bool SwRect::OverStepTop( long nLimit ) const + { return nLimit > m_Point.getY() && m_Point.getY() + m_Size.getHeight() > nLimit; } +bool SwRect::OverStepRight( long nLimit ) const + { return nLimit > m_Point.getX() && m_Point.getX() + m_Size.getWidth() > nLimit; } + +void SwRect::SetLeftAndWidth( long nLeft, long nNew ) +{ + m_Point.setX(nLeft); + m_Size.setWidth(nNew); +} +void SwRect::SetTopAndHeight( long nTop, long nNew ) +{ + m_Point.setY(nTop); + m_Size.setHeight(nNew); +} +void SwRect::SetRightAndWidth( long nRight, long nNew ) +{ + m_Point.setX(nRight - nNew); + m_Size.setWidth(nNew); +} +void SwRect::SetBottomAndHeight( long nBottom, long nNew ) +{ + m_Point.setY(nBottom - nNew); + m_Size.setHeight(nNew); +} +void SwRect::SetUpperLeftCorner( const Point& rNew ) + { m_Point = rNew; } +void SwRect::SetUpperRightCorner( const Point& rNew ) + { m_Point = Point(rNew.X() - m_Size.getWidth(), rNew.Y()); } +void SwRect::SetLowerLeftCorner( const Point& rNew ) + { m_Point = Point(rNew.X(), rNew.Y() - m_Size.getHeight()); } + +void SwRect::dumpAsXmlAttributes(xmlTextWriterPtr writer) const +{ + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("left"), "%li", Left()); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("top"), "%li", Top()); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("width"), "%li", Width()); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("height"), "%li", Height()); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("bottom"), "%li", Bottom()); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("right"), "%li", Right()); +} + +#ifdef DBG_UTIL +SvStream& WriteSwRect(SvStream &rStream, const SwRect &rRect) +{ + rStream.WriteChar('[').WriteInt32(rRect.Top()). + WriteChar('/').WriteInt32(rRect.Left()). + WriteChar(',').WriteInt32(rRect.Width()). + WriteChar('x').WriteInt32(rRect.Height()). + WriteCharPtr("] "); + return rStream; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/swregion.cxx b/sw/source/core/bastyp/swregion.cxx new file mode 100644 index 000000000..e67dac264 --- /dev/null +++ b/sw/source/core/bastyp/swregion.cxx @@ -0,0 +1,193 @@ +/* -*- 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 + +SwRegionRects::SwRegionRects( const SwRect &rStartRect, sal_uInt16 nInit ) : + SwRects(), + m_aOrigin( rStartRect ) +{ + reserve(nInit); + push_back( m_aOrigin ); +} + +// If is true then this Rect will be overwritten by at +// position . Otherwise is attached at the end. +inline void SwRegionRects::InsertRect( const SwRect &rRect, + const sal_uInt16 nPos, bool &rDel ) +{ + if( rDel ) + { + (*this)[nPos] = rRect; + rDel = false; + } + else + { + push_back( rRect ); + } +} + +void SwRegionRects::operator+=( const SwRect &rRect ) +{ + bool f = false; + InsertRect( rRect, 0, f ); +} + +/** Delete all overlaps of the Rects in array with the given + + To do so, all existing rectangles have to be either split or deleted. + + @param rRect rectangle with the area that should be deleted +*/ +void SwRegionRects::operator-=( const SwRect &rRect ) +{ + sal_uInt16 nMax = size(); + for ( sal_uInt16 i = 0; i < nMax; ++i ) + { + if ( rRect.IsOver( (*this)[i] ) ) + { + SwRect aTmp( (*this)[i] ); + SwRect aInter( aTmp ); + aInter.Intersection_( rRect ); + + // The first Rect that should be inserted takes position of i. + // This avoids one Delete() call. + bool bDel = true; + + // now split; only those rectangles should be left over that are in + // the "old" but not in the "new" area; hence, not in intersection. + long nTmp; + if ( 0 < (nTmp = aInter.Top() - aTmp.Top()) ) + { + const long nOldVal = aTmp.Height(); + aTmp.Height(nTmp); + InsertRect( aTmp, i, bDel ); + aTmp.Height( nOldVal ); + } + + aTmp.Top( aInter.Top() + aInter.Height() ); + if ( aTmp.Height() > 0 ) + InsertRect( aTmp, i, bDel ); + + aTmp.Top( aInter.Top() ); + aTmp.Bottom( aInter.Bottom() ); + if ( 0 < (nTmp = aInter.Left() - aTmp.Left()) ) + { + const long nOldVal = aTmp.Width(); + aTmp.Width( nTmp ); + InsertRect( aTmp, i, bDel ); + aTmp.Width( nOldVal ); + } + + aTmp.Left( aInter.Left() + aInter.Width() ); //+1? + if ( aTmp.Width() > 0 ) + InsertRect( aTmp, i, bDel ); + + if( bDel ) + { + erase( begin() + i ); + --i; // so that we don't forget any + --nMax; // so that we don't check too much + } + } + } +} + +/** invert current rectangle + + Change the shape, such that holes with be areas and areas are holes now. + + Note: If no rects were removed, then the shape is identical to the original + shape. As a result, it will be a NULL-SRectangle after inverting. +*/ +void SwRegionRects::Invert() +{ + // not very elegant and fast, but efficient: + // Create a new region and remove all areas that are left over. Afterwards + // copy all values. + + // To avoid unnecessary memory requirements, create a "useful" initial size: + // Number of rectangles in this area * 2 + 2 for the special case of a + // single hole (so four Rects in the inverse case). + SwRegionRects aInvRegion( m_aOrigin, size()*2+2 ); + for( const_iterator it = begin(); it != end(); ++it ) + aInvRegion -= *it; + + // overwrite all existing + swap( aInvRegion ); +} + +static SwTwips CalcArea( const SwRect &rRect ) +{ + return rRect.Width() * rRect.Height(); +} + +// combine all adjacent rectangles +void SwRegionRects::Compress() +{ + for (size_type i = 0; i < size(); ) + { + bool bRestart(false); + for ( size_type j = i+1; j < size(); ++j ) + { + // If one rectangle contains a second completely than the latter + // does not need to be stored and can be deleted + if ( (*this)[i].IsInside( (*this)[j] ) ) + { + erase( begin() + j ); + --j; + } + else if ( (*this)[j].IsInside( (*this)[i] ) ) + { + (*this)[i] = (*this)[j]; + erase( begin() + j ); + bRestart = true; + break; + } + else + { + // If two rectangles have the same area of their union minus the + // intersection then one of them can be deleted. + // For combining as much as possible (and for having less single + // paints), the area of the union can be a little bit larger: + // ( 9622 * 141.5 = 1361513 ~= a quarter (1/4) centimeter wider + // than the width of an A4 page + const long nFuzzy = 1361513; + SwRect aUnion( (*this)[i] ); + aUnion.Union( (*this)[j] ); + SwRect aInter( (*this)[i] ); + aInter.Intersection( (*this)[j] ); + if ( (::CalcArea( (*this)[i] ) + + ::CalcArea( (*this)[j] ) + nFuzzy) >= + (::CalcArea( aUnion ) - CalcArea( aInter )) ) + { + (*this)[i] = aUnion; + erase( begin() + j ); + bRestart = true; + break; + } + } + } + i = bRestart ? 0 : i+1; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/swtypes.cxx b/sw/source/core/bastyp/swtypes.cxx new file mode 100644 index 000000000..59c01abb5 --- /dev/null +++ b/sw/source/core/bastyp/swtypes.cxx @@ -0,0 +1,72 @@ +/* -*- 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 + +using namespace com::sun::star; + +Size GetGraphicSizeTwip(const Graphic& rGraphic, vcl::RenderContext* pOutDev) +{ + const MapMode aMapTwip(MapUnit::MapTwip); + Size aSize(rGraphic.GetPrefSize()); + + if (!aSize.getWidth() && !aSize.getHeight()) + { + const_cast(rGraphic).makeAvailable(); + aSize = rGraphic.GetPrefSize(); + } + + if (MapUnit::MapPixel == rGraphic.GetPrefMapMode().GetMapUnit()) + { + if (!pOutDev) + pOutDev = Application::GetDefaultDevice(); + aSize = pOutDev->PixelToLogic(aSize, aMapTwip); + } + else + { + aSize = OutputDevice::LogicToLogic(aSize, rGraphic.GetPrefMapMode(), aMapTwip); + } + return aSize; +} + +uno::Reference< linguistic2::XSpellChecker1 > GetSpellChecker() +{ + return LinguMgr::GetSpellChecker(); +} + +uno::Reference< linguistic2::XHyphenator > GetHyphenator() +{ + return LinguMgr::GetHyphenator(); +} + +uno::Reference< linguistic2::XThesaurus > GetThesaurus() +{ + return LinguMgr::GetThesaurus(); +} + +uno::Reference< linguistic2::XLinguProperties > GetLinguPropertySet() +{ + return LinguMgr::GetLinguPropertySet(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/bastyp/tabcol.cxx b/sw/source/core/bastyp/tabcol.cxx new file mode 100644 index 000000000..0ecbb1069 --- /dev/null +++ b/sw/source/core/bastyp/tabcol.cxx @@ -0,0 +1,92 @@ +/* -*- 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 + +SwTabCols::SwTabCols( sal_uInt16 nSize ) : + m_nLeftMin( 0 ), + m_nLeft( 0 ), + m_nRight( 0 ), + m_nRightMax( 0 ), + m_bLastRowAllowedToChange( true ) +{ + if ( nSize ) + m_aData.reserve( nSize ); +} + +SwTabCols::SwTabCols( const SwTabCols& rCpy ) : + m_nLeftMin( rCpy.GetLeftMin() ), + m_nLeft( rCpy.GetLeft() ), + m_nRight( rCpy.GetRight() ), + m_nRightMax( rCpy.GetRightMax() ), + m_bLastRowAllowedToChange( rCpy.IsLastRowAllowedToChange() ), + m_aData( rCpy.GetData() ) +{ +} + +SwTabCols &SwTabCols::operator=( const SwTabCols& rCpy ) +{ + m_nLeftMin = rCpy.GetLeftMin(); + m_nLeft = rCpy.GetLeft(); + m_nRight = rCpy.GetRight(); + m_nRightMax= rCpy.GetRightMax(); + m_bLastRowAllowedToChange = rCpy.IsLastRowAllowedToChange(); + + Remove( 0, Count() ); + m_aData = rCpy.GetData(); + + return *this; +} + +void SwTabCols::Insert( long nValue, long nMin, long nMax, + bool bValue, size_t nPos ) +{ + SwTabColsEntry aEntry; + aEntry.nPos = nValue; + aEntry.nMin = nMin; + aEntry.nMax = nMax; + aEntry.bHidden = bValue; + m_aData.insert( m_aData.begin() + nPos, aEntry ); +} + +void SwTabCols::Insert( long nValue, bool bValue, size_t nPos ) +{ + SwTabColsEntry aEntry; + aEntry.nPos = nValue; + aEntry.nMin = 0; + aEntry.nMax = LONG_MAX; + aEntry.bHidden = bValue; + m_aData.insert( m_aData.begin() + nPos, aEntry ); + +#if OSL_DEBUG_LEVEL > 1 + for ( const auto& rPos : m_aData ) + { + aEntry = rPos; + } +#endif +} + +void SwTabCols::Remove( size_t nPos, size_t nCount ) +{ + SwTabColsEntries::iterator aStart = m_aData.begin() + nPos; + m_aData.erase( aStart, aStart + nCount ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/BlockCursor.cxx b/sw/source/core/crsr/BlockCursor.cxx new file mode 100644 index 000000000..5c986ab86 --- /dev/null +++ b/sw/source/core/crsr/BlockCursor.cxx @@ -0,0 +1,32 @@ +/* -*- 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 "BlockCursor.hxx" + +SwBlockCursor::~SwBlockCursor() +{ +} + +SwShellCursor& SwBlockCursor::getShellCursor() +{ + return maCursor; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/BlockCursor.hxx b/sw/source/core/crsr/BlockCursor.hxx new file mode 100644 index 000000000..28b09e0e8 --- /dev/null +++ b/sw/source/core/crsr/BlockCursor.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_CRSR_BLOCKCURSOR_HXX +#define INCLUDED_SW_SOURCE_CORE_CRSR_BLOCKCURSOR_HXX + +#include +#include + +#include + +class SwCursorShell; +struct SwPosition; + + /** Access to the block cursor + + A block cursor contains a SwShellCursor and additional information about + the rectangle which has been created by pressing the mouse button and + moving the mouse. + + It's simply an aggregation of a SwShellCursor and a rectangle defined by + a start and an end point. +*/ +class SwBlockCursor +{ + SwShellCursor maCursor; + std::optional maStartPt; + std::optional maEndPt; + +public: + SwBlockCursor( const SwCursorShell& rCursorSh, const SwPosition &rPos ) : + maCursor( rCursorSh, rPos ) {} + /** Access to the shell cursor + + @return SwShellCursor& which represents the start and end position of the + current block selection + */ + SwShellCursor& getShellCursor(); + /** Defines the starting vertex of the block selection + + @param rPt + rPt should contain the document coordinates of the mouse cursor when + the block selection starts (MouseButtonDown) + */ + void setStartPoint( const Point &rPt ) { maStartPt = rPt; } + /** Defines the ending vertex of the block selection + + @param rPt + rPt should contain the document coordinates of the mouse cursor when + the block selection has started and the mouse has been moved (MouseMove) + */ + void setEndPoint( const Point &rPt ) { maEndPt = rPt; } + /** The document coordinates where the block selection has been started + + @return 0, if no start point has been set + */ + std::optional const & getStartPoint() const { return maStartPt; } + /** The document coordinates where the block selection ends (at the moment) + + @return 0, if no end point has been set + */ + std::optional const & getEndPoint() const { return maEndPt; } + /** Deletion of the mouse created rectangle + + When start and end points exist, the block cursor depends on this. If the + cursor is moved by cursor keys (e.g. up/down, home/end) the mouse rectangle + is obsolete and has to be deleted. + */ + void clearPoints() { maStartPt.reset(); maEndPt.reset(); } + ~SwBlockCursor(); +}; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/DateFormFieldButton.cxx b/sw/source/core/crsr/DateFormFieldButton.cxx new file mode 100644 index 000000000..d5c44f121 --- /dev/null +++ b/sw/source/core/crsr/DateFormFieldButton.cxx @@ -0,0 +1,97 @@ +/* -*- 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/. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +class SwDatePickerDialog : public FloatingWindow +{ +private: + VclPtr m_pCalendar; + sw::mark::DateFieldmark* m_pFieldmark; + SvNumberFormatter* m_pNumberFormatter; + + DECL_LINK(ImplSelectHdl, Calendar*, void); + +public: + SwDatePickerDialog(SwEditWin* parent, sw::mark::DateFieldmark* pFieldmark, + SvNumberFormatter* pNumberFormatter); + virtual ~SwDatePickerDialog() override; + virtual void dispose() override; +}; +} + +SwDatePickerDialog::SwDatePickerDialog(SwEditWin* parent, sw::mark::DateFieldmark* pFieldmark, + SvNumberFormatter* pNumberFormatter) + : FloatingWindow(parent, WB_BORDER | WB_SYSTEMWINDOW | WB_NOSHADOW) + , m_pCalendar(VclPtr::Create(this, WB_TABSTOP)) + , m_pFieldmark(pFieldmark) + , m_pNumberFormatter(pNumberFormatter) +{ + if (m_pFieldmark != nullptr) + { + std::pair aResult = m_pFieldmark->GetCurrentDate(); + if (aResult.first) + { + const Date& rNullDate = m_pNumberFormatter->GetNullDate(); + m_pCalendar->SetCurDate(rNullDate + sal_Int32(aResult.second)); + } + } + m_pCalendar->SetSelectHdl(LINK(this, SwDatePickerDialog, ImplSelectHdl)); + m_pCalendar->SetOutputSizePixel(m_pCalendar->CalcWindowSizePixel()); + m_pCalendar->Show(); + SetOutputSizePixel(m_pCalendar->GetSizePixel()); +} + +SwDatePickerDialog::~SwDatePickerDialog() { disposeOnce(); } + +void SwDatePickerDialog::dispose() +{ + m_pCalendar.clear(); + FloatingWindow::dispose(); +} + +IMPL_LINK(SwDatePickerDialog, ImplSelectHdl, Calendar*, pCalendar, void) +{ + if (!pCalendar->IsTravelSelect()) + { + if (m_pFieldmark != nullptr) + { + const Date& rNullDate = m_pNumberFormatter->GetNullDate(); + double dDate = pCalendar->GetFirstSelectedDate() - rNullDate; + m_pFieldmark->SetCurrentDate(dDate); + } + EndPopupMode(); + } +} + +DateFormFieldButton::DateFormFieldButton(SwEditWin* pEditWin, sw::mark::DateFieldmark& rFieldmark, + SvNumberFormatter* pNumberFormatter) + : FormFieldButton(pEditWin, rFieldmark) + , m_pNumberFormatter(pNumberFormatter) +{ +} + +DateFormFieldButton::~DateFormFieldButton() { disposeOnce(); } + +void DateFormFieldButton::InitPopup() +{ + sw::mark::DateFieldmark* pDateFieldmark = dynamic_cast(&m_rFieldmark); + m_pFieldPopup = VclPtr::Create(static_cast(GetParent()), + pDateFieldmark, m_pNumberFormatter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/crsr/DropDownFormFieldButton.cxx b/sw/source/core/crsr/DropDownFormFieldButton.cxx new file mode 100644 index 000000000..a110ac3f0 --- /dev/null +++ b/sw/source/core/crsr/DropDownFormFieldButton.cxx @@ -0,0 +1,167 @@ +/* -*- 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/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +class SwFieldListBox final : public InterimItemWindow +{ +private: + std::unique_ptr m_xTreeView; + +public: + SwFieldListBox(vcl::Window* pParent) + : InterimItemWindow(pParent, "modules/swriter/ui/formdropdown.ui", "FormDropDown") + , m_xTreeView(m_xBuilder->weld_tree_view("list")) + { + } + weld::TreeView& get_widget() { return *m_xTreeView; } + virtual ~SwFieldListBox() override { disposeOnce(); } + virtual void dispose() override + { + m_xTreeView.reset(); + InterimItemWindow::dispose(); + } +}; + +/** + * Popup dialog for drop-down form field showing the list items of the field. + * The user can select the item using this popup while filling in a form. + */ +class SwFieldDialog : public FloatingWindow +{ +private: + VclPtr m_xListBox; + sw::mark::IFieldmark* m_pFieldmark; + + DECL_LINK(MyListBoxHandler, weld::TreeView&, bool); + +public: + SwFieldDialog(SwEditWin* parent, sw::mark::IFieldmark* fieldBM, long nMinListWidth); + virtual ~SwFieldDialog() override; + virtual void dispose() override; +}; +} + +SwFieldDialog::SwFieldDialog(SwEditWin* parent, sw::mark::IFieldmark* fieldBM, long nMinListWidth) + : FloatingWindow(parent, WB_BORDER | WB_SYSTEMWINDOW) + , m_xListBox(VclPtr::Create(this)) + , m_pFieldmark(fieldBM) +{ + weld::TreeView& rTreeView = m_xListBox->get_widget(); + + if (fieldBM != nullptr) + { + const sw::mark::IFieldmark::parameter_map_t* const pParameters = fieldBM->GetParameters(); + + OUString sListKey = ODF_FORMDROPDOWN_LISTENTRY; + sw::mark::IFieldmark::parameter_map_t::const_iterator pListEntries + = pParameters->find(sListKey); + css::uno::Sequence vListEntries; + if (pListEntries != pParameters->end()) + { + pListEntries->second >>= vListEntries; + for (OUString const& i : std::as_const(vListEntries)) + rTreeView.append_text(i); + } + + if (!vListEntries.hasElements()) + { + rTreeView.append_text(SwResId(STR_DROP_DOWN_EMPTY_LIST)); + } + + // Select the current one + OUString sResultKey = ODF_FORMDROPDOWN_RESULT; + sw::mark::IFieldmark::parameter_map_t::const_iterator pResult + = pParameters->find(sResultKey); + if (pResult != pParameters->end()) + { + sal_Int32 nSelection = -1; + pResult->second >>= nSelection; + rTreeView.set_cursor(nSelection); + rTreeView.select(nSelection); + } + } + + auto nHeight = rTreeView.get_height_rows( + std::min(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount(), + rTreeView.n_children())); + rTreeView.set_size_request(-1, nHeight); + Size lbSize(rTreeView.get_preferred_size()); + lbSize.AdjustWidth(4); + lbSize.AdjustHeight(4); + lbSize.setWidth(std::max(lbSize.Width(), nMinListWidth)); + m_xListBox->SetSizePixel(lbSize); + rTreeView.connect_row_activated(LINK(this, SwFieldDialog, MyListBoxHandler)); + m_xListBox->Show(); + + rTreeView.grab_focus(); + + SetSizePixel(lbSize); +} + +SwFieldDialog::~SwFieldDialog() { disposeOnce(); } + +void SwFieldDialog::dispose() +{ + m_xListBox.disposeAndClear(); + FloatingWindow::dispose(); +} + +IMPL_LINK(SwFieldDialog, MyListBoxHandler, weld::TreeView&, rBox, bool) +{ + OUString sSelection = rBox.get_selected_text(); + if (sSelection == SwResId(STR_DROP_DOWN_EMPTY_LIST)) + { + EndPopupMode(); + return true; + } + + sal_Int32 nSelection = rBox.get_selected_index(); + if (nSelection >= 0) + { + OUString sKey = ODF_FORMDROPDOWN_RESULT; + (*m_pFieldmark->GetParameters())[sKey] <<= nSelection; + m_pFieldmark->Invalidate(); + SwView& rView = static_cast(GetParent())->GetView(); + rView.GetDocShell()->SetModified(); + } + + EndPopupMode(); + + return true; +} + +DropDownFormFieldButton::DropDownFormFieldButton(SwEditWin* pEditWin, + sw::mark::DropDownFieldmark& rFieldmark) + : FormFieldButton(pEditWin, rFieldmark) +{ +} + +DropDownFormFieldButton::~DropDownFormFieldButton() { disposeOnce(); } + +void DropDownFormFieldButton::InitPopup() +{ + m_pFieldPopup = VclPtr::Create(static_cast(GetParent()), + &m_rFieldmark, GetSizePixel().Width()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/crsr/FormFieldButton.cxx b/sw/source/core/crsr/FormFieldButton.cxx new file mode 100644 index 000000000..43d8ff6e0 --- /dev/null +++ b/sw/source/core/crsr/FormFieldButton.cxx @@ -0,0 +1,151 @@ +/* -*- 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/. + */ + +#include +#include +#include +#include +#include +#include + +FormFieldButton::FormFieldButton(SwEditWin* pEditWin, sw::mark::Fieldmark& rFieldmark) + : MenuButton(pEditWin, WB_DIALOGCONTROL) + , m_rFieldmark(rFieldmark) +{ + assert(GetParent()); + assert(dynamic_cast(GetParent())); +} + +FormFieldButton::~FormFieldButton() { disposeOnce(); } + +void FormFieldButton::dispose() +{ + m_pFieldPopup.disposeAndClear(); + MenuButton::dispose(); +} + +void FormFieldButton::CalcPosAndSize(const SwRect& rPortionPaintArea) +{ + assert(GetParent()); + + Point aBoxPos = GetParent()->LogicToPixel(rPortionPaintArea.Pos()); + Size aBoxSize = GetParent()->LogicToPixel(rPortionPaintArea.SSize()); + + // First calculate the size of the frame around the field + int nPadding = aBoxSize.Height() / 4; + aBoxPos.AdjustX(-nPadding); + aBoxPos.AdjustY(-nPadding); + aBoxSize.AdjustWidth(2 * nPadding); + aBoxSize.AdjustHeight(2 * nPadding); + + m_aFieldFramePixel = tools::Rectangle(aBoxPos, aBoxSize); + + // Then extend the size with the button area + aBoxSize.AdjustWidth(GetParent()->LogicToPixel(rPortionPaintArea.SSize()).Height()); + + if (aBoxPos != GetPosPixel() || aBoxSize != GetSizePixel()) + { + SetPosSizePixel(aBoxPos, aBoxSize); + Invalidate(); + } +} + +void FormFieldButton::MouseButtonUp(const MouseEvent&) +{ + assert(GetParent()); + + Point aPixPos = GetPosPixel(); + aPixPos.AdjustY(GetSizePixel().Height()); + + // sets m_pFieldPopup + InitPopup(); + + m_pFieldPopup->SetPopupModeEndHdl(LINK(this, DropDownFormFieldButton, FieldPopupModeEndHdl)); + + tools::Rectangle aRect(GetParent()->OutputToScreenPixel(aPixPos), Size(0, 0)); + m_pFieldPopup->StartPopupMode(aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus); + Invalidate(); +} + +IMPL_LINK_NOARG(FormFieldButton, FieldPopupModeEndHdl, FloatingWindow*, void) +{ + m_pFieldPopup.disposeAndClear(); + m_rFieldmark.Invalidate(); + // Hide the button here and make it visible later, to make transparent background work with SAL_USE_VCLPLUGIN=gen + Show(false); + Invalidate(); +} + +static basegfx::BColor lcl_GetFillColor(const basegfx::BColor& rLineColor, double aLuminance) +{ + basegfx::BColor aHslLine = basegfx::utils::rgb2hsl(rLineColor); + aHslLine.setZ(aLuminance); + return basegfx::utils::hsl2rgb(aHslLine); +} + +void FormFieldButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + SetMapMode(MapMode(MapUnit::MapPixel)); + + //const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color aLineColor = COL_BLACK; + Color aFillColor(lcl_GetFillColor(aLineColor.getBColor(), (m_pFieldPopup ? 0.5 : 0.75))); + + // Draw the frame around the field + // GTK3 backend cuts down the frame's top and left border, to avoid that add a padding around the frame + int nPadding = 1; + Point aPos(nPadding, nPadding); + Size aSize(m_aFieldFramePixel.GetSize().Width() - nPadding, + m_aFieldFramePixel.GetSize().Height() - nPadding); + const tools::Rectangle aFrameRect(tools::Rectangle(aPos, aSize)); + rRenderContext.SetLineColor(aLineColor); + rRenderContext.SetFillColor(COL_TRANSPARENT); + rRenderContext.DrawRect(aFrameRect); + + // Draw the button next to the frame + Point aButtonPos(aFrameRect.TopLeft()); + aButtonPos.AdjustX(aFrameRect.GetSize().getWidth() - 1); + Size aButtonSize(aFrameRect.GetSize()); + aButtonSize.setWidth(GetSizePixel().getWidth() - aFrameRect.getWidth() - nPadding); + const tools::Rectangle aButtonRect(tools::Rectangle(aButtonPos, aButtonSize)); + + // Background & border + rRenderContext.SetLineColor(aLineColor); + rRenderContext.SetFillColor(aFillColor); + rRenderContext.DrawRect(aButtonRect); + + // the arrowhead + rRenderContext.SetLineColor(aLineColor); + rRenderContext.SetFillColor(aLineColor); + + Point aCenter(aButtonPos.X() + (aButtonSize.Width() / 2), + aButtonPos.Y() + (aButtonSize.Height() / 2)); + Size aArrowSize(aButtonSize.Width() / 4, aButtonSize.Height() / 10); + + tools::Polygon aPoly(3); + aPoly.SetPoint(Point(aCenter.X() - aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 0); + aPoly.SetPoint(Point(aCenter.X() + aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 1); + aPoly.SetPoint(Point(aCenter.X(), aCenter.Y() + aArrowSize.Height()), 2); + rRenderContext.DrawPolygon(aPoly); +} + +WindowHitTest FormFieldButton::ImplHitTest(const Point& rFramePos) +{ + // We need to check whether the position hits the button (the frame should be mouse transparent) + WindowHitTest aResult = MenuButton::ImplHitTest(rFramePos); + if (aResult != WindowHitTest::Inside) + return aResult; + else + { + return rFramePos.X() >= m_aFieldFramePixel.Right() ? WindowHitTest::Inside + : WindowHitTest::Transparent; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/crsr/annotationmark.cxx b/sw/source/core/crsr/annotationmark.cxx new file mode 100644 index 000000000..0fc6d16da --- /dev/null +++ b/sw/source/core/crsr/annotationmark.cxx @@ -0,0 +1,97 @@ +/* -*- 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 + +namespace sw::mark +{ + AnnotationMark::AnnotationMark( + const SwPaM& rPaM, + const OUString& rName ) + : MarkBase( rPaM, rName ) + { + if ( rName.getLength() == 0 ) + { + SetName( MarkBase::GenerateNewName("__Annotation__") ); + } + } + + AnnotationMark::~AnnotationMark() + { + } + + void AnnotationMark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const, SwPosition const*const) + { + SwTextNode *pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode(); + assert(pTextNode); + SwTextField *const pTextField = pTextNode->GetFieldTextAttrAt( + GetMarkEnd().nContent.GetIndex()-1, true); + assert(pTextField != nullptr); + auto pPostItField + = dynamic_cast(pTextField->GetFormatField().GetField()); + assert(pPostItField); + // use the annotation mark's name as the annotation name, if + // - the annotation field has an empty annotation name or + // - the annotation mark's name differs (on mark creation a name clash had been detected) + if ( pPostItField->GetName().isEmpty() + || pPostItField->GetName() != GetName() ) + { + const_cast(pPostItField)->SetName( GetName() ); + } + + if (io_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + io_pDoc->GetIDocumentUndoRedo().AppendUndo( std::make_unique(*this) ); + } + io_pDoc->getIDocumentState().SetModified(); + } + + const SwFormatField* AnnotationMark::GetAnnotationFormatField() const + { + SwDoc* pDoc = GetMarkPos().GetDoc(); + assert(pDoc != nullptr); + + const auto sName = GetName(); + SwFieldType* pType = pDoc->getIDocumentFieldsAccess().GetFieldType( SwFieldIds::Postit, OUString(), false ); + std::vector vFields; + pType->GatherFields(vFields); + auto ppFound = std::find_if(vFields.begin(), vFields.end(), [&sName](SwFormatField* pF) + { + auto pPF = dynamic_cast(pF->GetField()); + return pPF && pPF->GetName() == sName; + }); + return ppFound != vFields.end() ? *ppFound : nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/bookmrk.cxx b/sw/source/core/crsr/bookmrk.cxx new file mode 100644 index 000000000..bd7e5d48f --- /dev/null +++ b/sw/source/core/crsr/bookmrk.cxx @@ -0,0 +1,1068 @@ +/* -*- 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 + +using namespace ::sw::mark; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sw::mark +{ + + SwPosition FindFieldSep(IFieldmark const& rMark) + { + SwPosition const& rStartPos(rMark.GetMarkStart()); + SwPosition const& rEndPos(rMark.GetMarkEnd()); + SwNodes const& rNodes(rStartPos.nNode.GetNodes()); + sal_uLong const nStartNode(rStartPos.nNode.GetIndex()); + sal_uLong const nEndNode(rEndPos.nNode.GetIndex()); + int nFields(0); + std::optional ret; + for (sal_uLong n = nEndNode; nStartNode <= n; --n) + { + SwNode *const pNode(rNodes[n]); + if (pNode->IsTextNode()) + { + SwTextNode & rTextNode(*pNode->GetTextNode()); + sal_Int32 const nStart(n == nStartNode + ? rStartPos.nContent.GetIndex() + 1 + : 0); + sal_Int32 const nEnd(n == nEndNode + // subtract 1 to ignore the end char + ? rEndPos.nContent.GetIndex() - 1 + : rTextNode.Len()); + for (sal_Int32 i = nEnd; nStart < i; --i) + { + const sal_Unicode c(rTextNode.GetText()[i - 1]); + switch (c) + { + case CH_TXT_ATR_FIELDSTART: + --nFields; + assert(0 <= nFields); + break; + case CH_TXT_ATR_FIELDEND: + ++nFields; + // fields in field result could happen by manual + // editing, although the field update deletes them + break; + case CH_TXT_ATR_FIELDSEP: + if (nFields == 0) + { + assert(!ret); // one per field + ret = SwPosition(rTextNode, i - 1); +#ifndef DBG_UTIL + return *ret; +#endif + } + break; + } + } + } + else if (pNode->IsEndNode()) + { + assert(nStartNode <= pNode->StartOfSectionIndex()); + // fieldmark cannot overlap node section + n = pNode->StartOfSectionIndex(); + } + else + { + assert(pNode->IsNoTextNode()); + } + } + assert(ret); // must have found it + return *ret; + } +} // namespace sw::mark + +namespace +{ + void lcl_FixPosition(SwPosition& rPos) + { + // make sure the position has 1) the proper node, and 2) a proper index + SwTextNode* pTextNode = rPos.nNode.GetNode().GetTextNode(); + if(pTextNode == nullptr && rPos.nContent.GetIndex() > 0) + { + SAL_INFO( + "sw.core", + "illegal position: " << rPos.nContent.GetIndex() + << " without proper TextNode"); + rPos.nContent.Assign(nullptr, 0); + } + else if(pTextNode != nullptr && rPos.nContent.GetIndex() > pTextNode->Len()) + { + SAL_INFO( + "sw.core", + "illegal position: " << rPos.nContent.GetIndex() + << " is beyond " << pTextNode->Len()); + rPos.nContent.Assign(pTextNode, pTextNode->Len()); + } + } + + void lcl_AssertFieldMarksSet(Fieldmark const * const pField, + const sal_Unicode aStartMark, + const sal_Unicode aEndMark) + { + if (aEndMark != CH_TXT_ATR_FORMELEMENT) + { + SwPosition const& rStart(pField->GetMarkStart()); + assert(rStart.nNode.GetNode().GetTextNode()->GetText()[rStart.nContent.GetIndex()] == aStartMark); (void) rStart; (void) aStartMark; + SwPosition const sepPos(sw::mark::FindFieldSep(*pField)); + assert(sepPos.nNode.GetNode().GetTextNode()->GetText()[sepPos.nContent.GetIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos; + } + SwPosition const& rEnd(pField->GetMarkEnd()); + assert(rEnd.nNode.GetNode().GetTextNode()->GetText()[rEnd.nContent.GetIndex() - 1] == aEndMark); (void) rEnd; + } + + void lcl_SetFieldMarks(Fieldmark* const pField, + SwDoc* const io_pDoc, + const sal_Unicode aStartMark, + const sal_Unicode aEndMark, + SwPosition const*const pSepPos) + { + io_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); + OUString startChar(aStartMark); + if (aEndMark != CH_TXT_ATR_FORMELEMENT + && pField->GetMarkStart() == pField->GetMarkEnd()) + { + // do only 1 InsertString call - to expand existing bookmarks at the + // position over the whole field instead of just aStartMark + startChar += OUStringChar(CH_TXT_ATR_FIELDSEP) + OUStringChar(aEndMark); + } + + SwPosition start = pField->GetMarkStart(); + if (aEndMark != CH_TXT_ATR_FORMELEMENT) + { + SwPaM aStartPaM(start); + io_pDoc->getIDocumentContentOperations().InsertString(aStartPaM, startChar); + start.nContent -= startChar.getLength(); // restore, it was moved by InsertString + // do not manipulate via reference directly but call SetMarkStartPos + // which works even if start and end pos were the same + pField->SetMarkStartPos( start ); + SwPosition& rEnd = pField->GetMarkEnd(); // note: retrieve after + // setting start, because if start==end it can go stale, see SetMarkPos() + assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd)); + if (startChar.getLength() == 1) + { + *aStartPaM.GetPoint() = pSepPos ? *pSepPos : rEnd; + io_pDoc->getIDocumentContentOperations().InsertString(aStartPaM, OUString(CH_TXT_ATR_FIELDSEP)); + if (!pSepPos || rEnd < *pSepPos) + { // rEnd is not moved automatically if it's same as insert pos + ++rEnd.nContent; + } + } + assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd)); + } + else + { + assert(pSepPos == nullptr); + } + + SwPosition& rEnd = pField->GetMarkEnd(); + if (aEndMark && startChar.getLength() == 1) + { + SwPaM aEndPaM(rEnd); + io_pDoc->getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark)); + ++rEnd.nContent; + } + lcl_AssertFieldMarksSet(pField, aStartMark, aEndMark); + + io_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); + }; + + void lcl_RemoveFieldMarks(Fieldmark const * const pField, + SwDoc* const io_pDoc, + const sal_Unicode aStartMark, + const sal_Unicode aEndMark) + { + io_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); + + const SwPosition& rStart = pField->GetMarkStart(); + SwTextNode const*const pStartTextNode = rStart.nNode.GetNode().GetTextNode(); + assert(pStartTextNode); + if (aEndMark != CH_TXT_ATR_FORMELEMENT) + { + (void) pStartTextNode; + // check this before start / end because of the +1 / -1 ... + SwPosition const sepPos(sw::mark::FindFieldSep(*pField)); + io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark); + io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP); + } + + const SwPosition& rEnd = pField->GetMarkEnd(); + SwTextNode *const pEndTextNode = rEnd.nNode.GetNode().GetTextNode(); + assert(pEndTextNode); + const sal_Int32 nEndPos = (rEnd == rStart) + ? rEnd.nContent.GetIndex() + : rEnd.nContent.GetIndex() - 1; + assert(pEndTextNode->GetText()[nEndPos] == aEndMark); + SwPosition const aEnd(*pEndTextNode, nEndPos); + io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark); + + io_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); + }; + + auto InvalidatePosition(SwPosition const& rPos) -> void + { + SwUpdateAttr const hint(rPos.nContent.GetIndex(), rPos.nContent.GetIndex(), 0); + rPos.nNode.GetNode().GetTextNode()->NotifyClients(nullptr, &hint); + } +} + +namespace sw::mark +{ + MarkBase::MarkBase(const SwPaM& aPaM, + const OUString& rName) + : m_pPos1(new SwPosition(*(aPaM.GetPoint()))) + , m_aName(rName) + { + m_pPos1->nContent.SetMark(this); + lcl_FixPosition(*m_pPos1); + if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint())) + { + MarkBase::SetOtherMarkPos(*(aPaM.GetMark())); + lcl_FixPosition(*m_pPos2); + } + } + + // For fieldmarks, the CH_TXT_ATR_FIELDSTART and CH_TXT_ATR_FIELDEND + // themselves are part of the covered range. This is guaranteed by + // TextFieldmark::InitDoc/lcl_AssureFieldMarksSet. + bool MarkBase::IsCoveringPosition(const SwPosition& rPos) const + { + return GetMarkStart() <= rPos && rPos < GetMarkEnd(); + } + + void MarkBase::SetMarkPos(const SwPosition& rNewPos) + { + std::make_unique(rNewPos).swap(m_pPos1); + m_pPos1->nContent.SetMark(this); + } + + void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos) + { + std::make_unique(rNewPos).swap(m_pPos2); + m_pPos2->nContent.SetMark(this); + } + + OUString MarkBase::ToString( ) const + { + return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ " + + OUString::number( GetMarkPos().nNode.GetIndex( ) ) + ", " + + OUString::number( GetMarkPos().nContent.GetIndex( ) ) + " ] )"; + } + + void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const + { + xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr())); + xmlTextWriterStartElement(pWriter, BAD_CAST("markPos")); + GetMarkPos().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + if (IsExpanded()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos")); + GetOtherMarkPos().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); + } + + MarkBase::~MarkBase() + { } + + OUString MarkBase::GenerateNewName(const OUString& rPrefix) + { + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + if (bHack) + { + static sal_Int64 nIdCounter = SAL_CONST_INT64(6000000000); + return rPrefix + OUString::number(nIdCounter++); + } + else + { + static OUString sUniquePostfix; + static sal_Int32 nCount = SAL_MAX_INT32; + if(nCount == SAL_MAX_INT32) + { + unsigned int const n(comphelper::rng::uniform_uint_distribution(0, + std::numeric_limits::max())); + sUniquePostfix = "_" + OUString::number(n); + nCount = 0; + } + // putting the counter in front of the random parts will speed up string comparisons + return rPrefix + OUString::number(nCount++) + sUniquePostfix; + } + } + + void MarkBase::Modify( const SfxPoolItem *pOld, const SfxPoolItem *pNew ) + { + NotifyClients(pOld, pNew); + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXBookmark(uno::Reference(nullptr)); + } + } + + auto MarkBase::InvalidateFrames() -> void + { + } + + NavigatorReminder::NavigatorReminder(const SwPaM& rPaM) + : MarkBase(rPaM, MarkBase::GenerateNewName("__NavigatorReminder__")) + { } + + UnoMark::UnoMark(const SwPaM& aPaM) + : MarkBase(aPaM, MarkBase::GenerateNewName("__UnoMark__")) + { } + + DdeBookmark::DdeBookmark(const SwPaM& aPaM) + : MarkBase(aPaM, MarkBase::GenerateNewName("__DdeLink__")) + { } + + void DdeBookmark::SetRefObject(SwServerObject* pObj) + { + m_aRefObj = pObj; + } + + void DdeBookmark::DeregisterFromDoc(SwDoc* const pDoc) + { + if(m_aRefObj.is()) + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer(m_aRefObj.get()); + } + + DdeBookmark::~DdeBookmark() + { + if( m_aRefObj.is() ) + { + if(m_aRefObj->HasDataLinks()) + { + ::sfx2::SvLinkSource* p = m_aRefObj.get(); + p->SendDataChanged(); + } + m_aRefObj->SetNoServer(); + } + } + + Bookmark::Bookmark(const SwPaM& aPaM, + const vcl::KeyCode& rCode, + const OUString& rName) + : DdeBookmark(aPaM) + , ::sfx2::Metadatable() + , m_aCode(rCode) + , m_bHidden(false) + { + m_aName = rName; + } + + void Bookmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const, SwPosition const*const) + { + if (io_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + io_pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique(*this)); + } + io_pDoc->getIDocumentState().SetModified(); + InvalidateFrames(); + } + + void Bookmark::DeregisterFromDoc(SwDoc* const io_pDoc) + { + DdeBookmark::DeregisterFromDoc(io_pDoc); + + if (io_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + io_pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique(*this)); + } + io_pDoc->getIDocumentState().SetModified(); + InvalidateFrames(); + } + + // invalidate text frames in case it's hidden or Formatting Marks enabled + auto Bookmark::InvalidateFrames() -> void + { + InvalidatePosition(GetMarkPos()); + if (IsExpanded()) + { + InvalidatePosition(GetOtherMarkPos()); + } + } + + void Bookmark::Hide(bool const isHide) + { + if (isHide != m_bHidden) + { + m_bHidden = isHide; + InvalidateFrames(); + } + } + + void Bookmark::SetHideCondition(OUString const& rHideCondition) + { + if (m_sHideCondition != rHideCondition) + { + m_sHideCondition = rHideCondition; + InvalidateFrames(); + } + } + + ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry() + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + return pDoc->GetXmlIdRegistry(); + } + + bool Bookmark::IsInClipboard() const + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + return pDoc->IsClipBoard(); + } + + bool Bookmark::IsInUndo() const + { + return false; + } + + bool Bookmark::IsInContent() const + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + return !pDoc->IsInHeaderFooter( GetMarkPos().nNode ); + } + + uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject() + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + const uno::Reference< rdf::XMetadatable> xMeta( + SwXBookmark::CreateXBookmark(*pDoc, this), uno::UNO_QUERY); + return xMeta; + } + + Fieldmark::Fieldmark(const SwPaM& rPaM) + : MarkBase(rPaM, MarkBase::GenerateNewName("__Fieldmark__")) + { + if(!IsExpanded()) + SetOtherMarkPos(GetMarkPos()); + } + + void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos ) + { + if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) + return SetMarkPos( rNewStartPos ); + else + return SetOtherMarkPos( rNewStartPos ); + } + + void Fieldmark::SetMarkEndPos( const SwPosition& rNewEndPos ) + { + if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) + return SetOtherMarkPos( rNewEndPos ); + else + return SetMarkPos( rNewEndPos ); + } + + OUString Fieldmark::ToString( ) const + { + return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", " + + m_aFieldname + ", [ " + OUString::number( GetMarkPos().nNode.GetIndex( ) ) + + ", " + OUString::number( GetMarkPos( ).nContent.GetIndex( ) ) + " ], [" + + OUString::number( GetOtherMarkPos().nNode.GetIndex( ) ) + ", " + + OUString::number( GetOtherMarkPos( ).nContent.GetIndex( ) ) + " ] ) "; + } + + void Fieldmark::Invalidate( ) + { + // TODO: Does exist a better solution to trigger a format of the + // fieldmark portion? If yes, please use it. + SwPaM aPaM( GetMarkPos(), GetOtherMarkPos() ); + aPaM.InvalidatePaM(); + } + + void Fieldmark::dumpAsXml(xmlTextWriterPtr pWriter) const + { + xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr())); + MarkBase::dumpAsXml(pWriter); + xmlTextWriterStartElement(pWriter, BAD_CAST("parameters")); + for (auto& rParam : m_vParams) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("parameter")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr())); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); + xmlTextWriterEndElement(pWriter); + } + + TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName) + : Fieldmark(rPaM) + { + if ( !rName.isEmpty() ) + m_aName = rName; + } + + void TextFieldmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) + { + if (eMode == sw::mark::InsertMode::New) + { + lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos); + // no need to invalidate text frames here, the insertion of the + // CH_TXT_ATR already invalidates + } + else + { + lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + } + + void TextFieldmark::ReleaseDoc(SwDoc* const pDoc) + { + IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo()); + if (rIDUR.DoesUndo()) + { + rIDUR.AppendUndo(std::make_unique(*this)); + } + ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes + lcl_RemoveFieldMarks(this, pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + + NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM) + : Fieldmark(rPaM) + { } + + void NonTextFieldmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) + { + assert(pSepPos == nullptr); + if (eMode == sw::mark::InsertMode::New) + { + lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos); + + // For some reason the end mark is moved from 1 by the Insert: + // we don't want this for checkboxes + SwPosition aNewEndPos = GetMarkEnd(); + aNewEndPos.nContent--; + SetMarkEndPos( aNewEndPos ); + } + else + { + lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); + } + } + + void NonTextFieldmark::ReleaseDoc(SwDoc* const pDoc) + { + IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo()); + if (rIDUR.DoesUndo()) + { + rIDUR.AppendUndo(std::make_unique(*this)); + } + ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes + lcl_RemoveFieldMarks(this, pDoc, + CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); + } + + + CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM) + : NonTextFieldmark(rPaM) + { } + + void CheckboxFieldmark::SetChecked(bool checked) + { + if ( IsChecked() != checked ) + { + (*GetParameters())[OUString(ODF_FORMCHECKBOX_RESULT)] <<= checked; + // mark document as modified + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + if ( pDoc ) + pDoc->getIDocumentState().SetModified(); + } + } + + bool CheckboxFieldmark::IsChecked() const + { + bool bResult = false; + parameter_map_t::const_iterator pResult = GetParameters()->find(OUString(ODF_FORMCHECKBOX_RESULT)); + if(pResult != GetParameters()->end()) + pResult->second >>= bResult; + return bResult; + } + + FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM) + : NonTextFieldmark(rPaM) + , m_pButton(nullptr) + { + } + + FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton() + { + m_pButton.disposeAndClear(); + } + + void FieldmarkWithDropDownButton::HideButton() + { + if(m_pButton) + m_pButton->Show(false); + } + + void FieldmarkWithDropDownButton::RemoveButton() + { + if(m_pButton) + m_pButton.disposeAndClear(); + } + + DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM) + : FieldmarkWithDropDownButton(rPaM) + { + } + + DropDownFieldmark::~DropDownFieldmark() + { + SendLOKMessage("hide"); + } + + void DropDownFieldmark::ShowButton(SwEditWin* pEditWin) + { + if(pEditWin) + { + if(!m_pButton) + m_pButton = VclPtr::Create(pEditWin, *this); + m_pButton->CalcPosAndSize(m_aPortionPaintArea); + m_pButton->Show(); + SendLOKMessage("show"); + } + } + + void DropDownFieldmark::HideButton() + { + SendLOKMessage("hide"); + FieldmarkWithDropDownButton::HideButton(); + } + + void DropDownFieldmark::RemoveButton() + { + SendLOKMessage("hide"); + FieldmarkWithDropDownButton::RemoveButton(); + } + + void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea) + { + m_aPortionPaintArea = rPortionPaintArea; + if(m_pButton) + { + m_pButton->Show(); + m_pButton->CalcPosAndSize(m_aPortionPaintArea); + SendLOKMessage("show"); + } + } + + void DropDownFieldmark::SendLOKMessage(const OString& sAction) + { + const SfxViewShell* pViewShell = SfxViewShell::Current(); + if (pViewShell && pViewShell->isLOKMobilePhone()) + { + return; + } + + if (comphelper::LibreOfficeKit::isActive()) + { + if (!m_pButton) + return; + + SwEditWin* pEditWin = dynamic_cast(m_pButton->GetParent()); + if (!pEditWin) + return; + + OStringBuffer sPayload; + if (sAction == "show") + { + if(m_aPortionPaintArea.IsEmpty()) + return; + + sPayload = OStringLiteral("{\"action\": \"show\"," + " \"type\": \"drop-down\", \"textArea\": \"") + + m_aPortionPaintArea.SVRect().toString() + "\","; + // Add field params to the message + sPayload.append(" \"params\": { \"items\": ["); + + // List items + auto pParameters = this->GetParameters(); + auto pListEntriesIter = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY); + css::uno::Sequence vListEntries; + if (pListEntriesIter != pParameters->end()) + { + pListEntriesIter->second >>= vListEntries; + for (const OUString& sItem : std::as_const(vListEntries)) + sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", "); + sPayload.setLength(sPayload.getLength() - 2); + } + sPayload.append("], "); + + // Selected item + OUString sResultKey = ODF_FORMDROPDOWN_RESULT; + auto pSelectedItemIter = pParameters->find(sResultKey); + sal_Int32 nSelection = -1; + if (pSelectedItemIter != pParameters->end()) + { + pSelectedItemIter->second >>= nSelection; + } + sPayload.append("\"selected\": \"" + OString::number(nSelection) + "\", "); + + // Placeholder text + sPayload.append("\"placeholderText\": \"" + OUStringToOString(SwResId(STR_DROP_DOWN_EMPTY_LIST), RTL_TEXTENCODING_UTF8) + "\"}}"); + } + else + { + sPayload = "{\"action\": \"hide\", \"type\": \"drop-down\"}"; + } + if (sPayload.toString() != m_sLastSentLOKMsg) { + m_sLastSentLOKMsg = sPayload.toString(); + pEditWin->GetView().GetWrtShell().GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, m_sLastSentLOKMsg.getStr()); + } + } + } + + DateFieldmark::DateFieldmark(const SwPaM& rPaM) + : FieldmarkWithDropDownButton(rPaM) + , m_pNumberFormatter(nullptr) + , m_pDocumentContentOperationsManager(nullptr) + { + } + + DateFieldmark::~DateFieldmark() + { + } + + void DateFieldmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode eMode, SwPosition const*const pSepPos) + { + m_pNumberFormatter = io_pDoc->GetNumberFormatter(); + m_pDocumentContentOperationsManager = &io_pDoc->GetDocumentContentOperationsManager(); + if (eMode == sw::mark::InsertMode::New) + { + lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos); + } + else + { + lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + } + + void DateFieldmark::ReleaseDoc(SwDoc* const pDoc) + { + IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo()); + if (rIDUR.DoesUndo()) + { + // TODO does this need a 3rd Undo class? + rIDUR.AppendUndo(std::make_unique(*this)); + } + ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes + lcl_RemoveFieldMarks(this, pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + + void DateFieldmark::ShowButton(SwEditWin* pEditWin) + { + if(pEditWin) + { + if(!m_pButton) + m_pButton = VclPtr::Create(pEditWin, *this, m_pNumberFormatter); + SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight()); + m_pButton->CalcPosAndSize(aPaintArea); + m_pButton->Show(); + } + } + + void DateFieldmark::SetPortionPaintAreaStart(const SwRect& rPortionPaintArea) + { + if (rPortionPaintArea.IsEmpty()) + return; + + m_aPaintAreaStart = rPortionPaintArea; + InvalidateCurrentDateParam(); + } + + void DateFieldmark::SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea) + { + if (rPortionPaintArea.IsEmpty()) + return; + + if(m_aPaintAreaEnd == rPortionPaintArea && + m_pButton && m_pButton->IsVisible()) + return; + + m_aPaintAreaEnd = rPortionPaintArea; + if(m_pButton) + { + m_pButton->Show(); + SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight()); + m_pButton->CalcPosAndSize(aPaintArea); + m_pButton->Invalidate(); + } + InvalidateCurrentDateParam(); + } + + OUString DateFieldmark::GetContent() const + { + const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode(); + SwPosition const sepPos(sw::mark::FindFieldSep(*this)); + const sal_Int32 nStart(sepPos.nContent.GetIndex()); + const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex()); + + OUString sContent; + if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() && + nEnd > nStart + 2) + sContent = pTextNode->GetText().copy(nStart + 1, nEnd - nStart - 2); + return sContent; + } + + void DateFieldmark::ReplaceContent(const OUString& sNewContent) + { + if(!m_pDocumentContentOperationsManager) + return; + + const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode(); + SwPosition const sepPos(sw::mark::FindFieldSep(*this)); + const sal_Int32 nStart(sepPos.nContent.GetIndex()); + const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex()); + + if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() && + nEnd > nStart + 2) + { + SwPaM aFieldPam(GetMarkStart().nNode, nStart + 1, + GetMarkStart().nNode, nEnd - 1); + m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false); + } + else + { + SwPaM aFieldStartPam(GetMarkStart().nNode, nStart + 1); + m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent); + } + + } + + std::pair DateFieldmark::GetCurrentDate() const + { + // Check current date param first + std::pair aResult = ParseCurrentDateParam(); + if(aResult.first) + return aResult; + + const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); + bool bFoundValidDate = false; + double dCurrentDate = 0; + OUString sDateFormat; + auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT); + if (pResult != pParameters->end()) + { + pResult->second >>= sDateFormat; + } + + OUString sLang; + pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE); + if (pResult != pParameters->end()) + { + pResult->second >>= sLang; + } + + // Get current content of the field + OUString sContent = GetContent(); + + sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType()); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + m_pNumberFormatter->PutEntry(sDateFormat, + nCheckPos, + nType, + nFormat, + LanguageTag(sLang).getLanguageType()); + } + + if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sContent, nFormat, dCurrentDate); + } + return std::pair(bFoundValidDate, dCurrentDate); + } + + void DateFieldmark::SetCurrentDate(double fDate) + { + // Replace current content with the selected date + ReplaceContent(GetDateInCurrentDateFormat(fDate)); + + // Also save the current date in a standard format + sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); + (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= GetDateInStandardDateFormat(fDate); + } + + OUString DateFieldmark::GetDateInStandardDateFormat(double fDate) const + { + OUString sCurrentDate; + sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT; + m_pNumberFormatter->PutEntry(sFormat, + nCheckPos, + nType, + nFormat, + ODF_FORMDATE_CURRENTDATE_LANGUAGE); + } + + if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + Color* pCol = nullptr; + m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false); + } + return sCurrentDate; + } + + std::pair DateFieldmark::ParseCurrentDateParam() const + { + bool bFoundValidDate = false; + double dCurrentDate = 0; + + const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); + auto pResult = pParameters->find(ODF_FORMDATE_CURRENTDATE); + OUString sCurrentDate; + if (pResult != pParameters->end()) + { + pResult->second >>= sCurrentDate; + } + if(!sCurrentDate.isEmpty()) + { + sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT; + m_pNumberFormatter->PutEntry(sFormat, + nCheckPos, + nType, + nFormat, + ODF_FORMDATE_CURRENTDATE_LANGUAGE); + } + + if(nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sCurrentDate, nFormat, dCurrentDate); + } + } + return std::pair(bFoundValidDate, dCurrentDate); + } + + + OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const + { + // Get current date format and language + OUString sDateFormat; + const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); + auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT); + if (pResult != pParameters->end()) + { + pResult->second >>= sDateFormat; + } + + OUString sLang; + pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE); + if (pResult != pParameters->end()) + { + pResult->second >>= sLang; + } + + // Fill the content with the specified format + OUString sCurrentContent; + sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType()); + if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nCheckPos = 0; + SvNumFormatType nType; + OUString sFormat = sDateFormat; + m_pNumberFormatter->PutEntry(sFormat, + nCheckPos, + nType, + nFormat, + LanguageTag(sLang).getLanguageType()); + } + + if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + Color* pCol = nullptr; + m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false); + } + return sCurrentContent; + } + + void DateFieldmark::InvalidateCurrentDateParam() + { + std::pair aResult = ParseCurrentDateParam(); + if(!aResult.first) + return; + + // Current date became invalid + if(GetDateInCurrentDateFormat(aResult.second) != GetContent()) + { + sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); + (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/callnk.cxx b/sw/source/core/crsr/callnk.cxx new file mode 100644 index 000000000..f6c277036 --- /dev/null +++ b/sw/source/core/crsr/callnk.cxx @@ -0,0 +1,247 @@ +/* -*- 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 "callnk.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SwCallLink::SwCallLink( SwCursorShell & rSh ) + : m_rShell( rSh ) +{ + // remember SPoint-values of current cursor + SwPaM* pCursor = m_rShell.IsTableMode() ? m_rShell.GetTableCrs() : m_rShell.GetCursor(); + SwNode& rNd = pCursor->GetPoint()->nNode.GetNode(); + m_nNode = rNd.GetIndex(); + m_nContent = pCursor->GetPoint()->nContent.GetIndex(); + m_nNodeType = rNd.GetNodeType(); + m_bHasSelection = ( *pCursor->GetPoint() != *pCursor->GetMark() ); + + if( rNd.IsTextNode() ) + m_nLeftFramePos = SwCallLink::getLayoutFrame( m_rShell.GetLayout(), *rNd.GetTextNode(), m_nContent, + !m_rShell.ActionPend() ); + else + { + m_nLeftFramePos = 0; + + // A special treatment for SwFeShell: + // When deleting the header/footer, footnotes SwFeShell sets the + // Cursor to NULL (Node + Content). + // If the Cursor is not on a ContentNode (ContentNode) this fact gets + // saved in nNdType. + if( SwNodeType::ContentMask & m_nNodeType ) + m_nNodeType = SwNodeType::NONE; + } +} + +static void lcl_notifyRow(const SwContentNode* pNode, SwCursorShell & rShell) +{ + if ( !pNode ) + return; + + SwFrame *const pMyFrame = pNode->getLayoutFrame( rShell.GetLayout() ); + if ( !pMyFrame ) + return; + + // We need to emulated a change of the row height in order + // to have the complete row redrawn + SwRowFrame *const pRow = pMyFrame->FindRowFrame(); + if ( !pRow ) + return; + + const SwTableLine* pLine = pRow->GetTabLine( ); + + if (rShell.IsTableMode() || (rShell.StartsWithTable() && rShell.ExtendedSelectedAll())) + { + // If we have a table selection, then avoid the notification: it's not necessary (the text + // cursor needs no updating) and the notification may kill the selection overlay, leading to + // flicker. + // Same for whole-document selection when it starts with a table. + return; + } + + SwFormatFrameSize aSize = pLine->GetFrameFormat()->GetFrameSize(); + pRow->ModifyNotification(nullptr, &aSize); +} + +SwCallLink::~SwCallLink() COVERITY_NOEXCEPT_FALSE +{ + if( m_nNodeType == SwNodeType::NONE || !m_rShell.m_bCallChgLnk ) // see ctor + return ; + + // If travelling over Nodes check formats and register them anew at the + // new Node. + SwPaM* pCurrentCursor = m_rShell.IsTableMode() ? m_rShell.GetTableCrs() : m_rShell.GetCursor(); + SwContentNode * pCNd = pCurrentCursor->GetContentNode(); + if( !pCNd ) + return; + + lcl_notifyRow(pCNd, m_rShell); + + const SwDoc *pDoc=m_rShell.GetDoc(); + const SwContentNode *pNode = nullptr; + if ( pDoc && m_nNode < pDoc->GetNodes( ).Count( ) ) + { + pNode = pDoc->GetNodes()[m_nNode]->GetContentNode(); + } + lcl_notifyRow(pNode, m_rShell); + + sal_Int32 nCmp, nCurrentContent = pCurrentCursor->GetPoint()->nContent.GetIndex(); + SwNodeType nNdWhich = pCNd->GetNodeType(); + sal_uLong nCurrentNode = pCurrentCursor->GetPoint()->nNode.GetIndex(); + + // Register the Shell as dependent at the current Node. By doing this all + // attribute changes can be signaled over the link. + pCNd->Add( &m_rShell ); + + const bool bCurrentHasSelection = (*pCurrentCursor->GetPoint() != *pCurrentCursor->GetMark()); + + if( m_nNodeType != nNdWhich || m_nNode != nCurrentNode ) + { + // Every time a switch between nodes occurs, there is a chance that + // new attributes do apply - meaning text-attributes. + // So the currently applying attributes would have to be determined. + // That can be done in one go by the handler. + m_rShell.CallChgLnk(); + } + else if (m_bHasSelection != bCurrentHasSelection) + { + // always call change link when selection changes + m_rShell.CallChgLnk(); + } + else if( m_rShell.m_aChgLnk.IsSet() && SwNodeType::Text == nNdWhich && + m_nContent != nCurrentContent ) + { + // If travelling with left/right only and the frame is + // unchanged (columns!) then check text hints. + if( m_nLeftFramePos == SwCallLink::getLayoutFrame( m_rShell.GetLayout(), *pCNd->GetTextNode(), nCurrentContent, + !m_rShell.ActionPend() ) && + (( nCmp = m_nContent ) + 1 == nCurrentContent || // Right + m_nContent -1 == ( nCmp = nCurrentContent )) ) // Left + { + if( nCmp == nCurrentContent && pCurrentCursor->HasMark() ) // left & select + ++nCmp; + + if ( pCNd->GetTextNode()->HasHints() ) + { + const SwpHints &rHts = pCNd->GetTextNode()->GetSwpHints(); + + for( size_t n = 0; n < rHts.Count(); ++n ) + { + const SwTextAttr* pHt = rHts.Get( n ); + const sal_Int32 *pEnd = pHt->End(); + const sal_Int32 nStart = pHt->GetStart(); + + // If "only start" or "start and end equal" then call on + // every overflow of start. + if( ( !pEnd || ( nStart == *pEnd ) ) && + ( nStart == m_nContent || nStart == nCurrentContent) ) + { + m_rShell.CallChgLnk(); + return; + } + + // If the attribute has an area and that area is not empty ... + else if( pEnd && nStart < *pEnd && + // ... then test if travelling occurred via start/end. + ( nStart == nCmp || + ( pHt->DontExpand() ? nCmp == *pEnd-1 + : nCmp == *pEnd ) )) + { + m_rShell.CallChgLnk(); + return; + } + } + } + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + const OUString rText = pCNd->GetTextNode()->GetText(); + if( !nCmp || + g_pBreakIt->GetBreakIter()->getScriptType( rText, m_nContent ) + != g_pBreakIt->GetBreakIter()->getScriptType(rText, nCurrentContent)) + { + m_rShell.CallChgLnk(); + return; + } + } + else + // If travelling more than one character with home/end/.. then + // always call ChgLnk, because it can not be determined here what + // has changed. Something may have changed. + m_rShell.CallChgLnk(); + } + + const SwFrame* pFrame; + const SwFlyFrame *pFlyFrame; + if (m_rShell.ActionPend()) + return; + pFrame = pCNd->getLayoutFrame(m_rShell.GetLayout(), nullptr, nullptr); + if (!pFrame) + return; + pFlyFrame = pFrame->FindFlyFrame(); + if ( pFlyFrame && !m_rShell.IsTableMode() ) + { + const SwNodeIndex* pIndex = pFlyFrame->GetFormat()->GetContent().GetContentIdx(); + OSL_ENSURE( pIndex, "Fly without Content" ); + + if (!pIndex) + return; + + const SwNode& rStNd = pIndex->GetNode(); + + if( rStNd.EndOfSectionNode()->StartOfSectionIndex() > m_nNode || + m_nNode > rStNd.EndOfSectionIndex() ) + m_rShell.GetFlyMacroLnk().Call( pFlyFrame->GetFormat() ); + } +} + +long SwCallLink::getLayoutFrame(const SwRootFrame* pRoot, + SwTextNode const & rNd, sal_Int32 nCntPos, bool /*bCalcFrame*/) +{ + SwTextFrame* pFrame = static_cast(rNd.getLayoutFrame(pRoot, nullptr, nullptr)); + SwTextFrame* pNext; + if ( pFrame && !pFrame->IsHiddenNow() ) + { + if( pFrame->HasFollow() ) + { + TextFrameIndex const nPos(pFrame->MapModelToView(&rNd, nCntPos)); + for (;;) + { + pNext = pFrame->GetFollow(); + if(!pNext || nPos < pNext->GetOffset()) + break; + pFrame = pNext; + } + } + + return pFrame->getFrameArea().Left(); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/callnk.hxx b/sw/source/core/crsr/callnk.hxx new file mode 100644 index 000000000..241491917 --- /dev/null +++ b/sw/source/core/crsr/callnk.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX +#define INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX + +#include +#include + +class SwCursorShell; +class SwTextNode; +class SwRootFrame; + +class SwCallLink +{ +public: + SwCursorShell & m_rShell; + sal_uLong m_nNode; + sal_Int32 m_nContent; + SwNodeType m_nNodeType; + long m_nLeftFramePos; + bool m_bHasSelection; + + explicit SwCallLink( SwCursorShell & rSh ); + ~SwCallLink() COVERITY_NOEXCEPT_FALSE; + + static long getLayoutFrame( const SwRootFrame*, SwTextNode const & rNd, sal_Int32 nCntPos, bool bCalcFrame ); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/crbm.cxx b/sw/source/core/crsr/crbm.cxx new file mode 100644 index 000000000..2311cf8d9 --- /dev/null +++ b/sw/source/core/crsr/crbm.cxx @@ -0,0 +1,318 @@ +/* -*- 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 + +using namespace std; + +namespace +{ + struct CursorStateHelper + { + explicit CursorStateHelper(SwCursorShell const & rShell) + : m_pCursor(rShell.GetSwCursor()) + , m_aSaveState(*m_pCursor) + { } + + void SetCursorToMark(::sw::mark::IMark const * const pMark) + { + *(m_pCursor->GetPoint()) = pMark->GetMarkStart(); + if(pMark->IsExpanded()) + { + m_pCursor->SetMark(); + *(m_pCursor->GetMark()) = pMark->GetMarkEnd(); + } + } + + /// returns true if the Cursor had been rolled back + bool RollbackIfIllegal() + { + if(m_pCursor->IsSelOvr(SwCursorSelOverFlags::CheckNodeSection + | SwCursorSelOverFlags::Toggle)) + { + m_pCursor->DeleteMark(); + m_pCursor->RestoreSavePos(); + return true; + } + return false; + } + + SwCursor* m_pCursor; + SwCursorSaveState m_aSaveState; + }; + + bool lcl_ReverseMarkOrderingByEnd(const ::sw::mark::IMark* pFirst, + const ::sw::mark::IMark* pSecond) + { + return pFirst->GetMarkEnd() > pSecond->GetMarkEnd(); + } + + bool lcl_IsInvisibleBookmark(const ::sw::mark::IMark* pMark) + { + return IDocumentMarkAccess::GetType(*pMark) != IDocumentMarkAccess::MarkType::BOOKMARK; + } +} + +// at CurrentCursor.SPoint +::sw::mark::IMark* SwCursorShell::SetBookmark( + const vcl::KeyCode& rCode, + const OUString& rName, + IDocumentMarkAccess::MarkType eMark) +{ + StartAction(); + ::sw::mark::IMark* pMark = getIDocumentMarkAccess()->makeMark( + *GetCursor(), + rName, + eMark, sw::mark::InsertMode::New); + ::sw::mark::IBookmark* pBookmark = dynamic_cast< ::sw::mark::IBookmark* >(pMark); + if(pBookmark) + { + pBookmark->SetKeyCode(rCode); + pBookmark->SetShortName(OUString()); + } + EndAction(); + return pMark; +} +// set CurrentCursor.SPoint + +// at CurrentCursor.SPoint +::sw::mark::IMark* SwCursorShell::SetBookmark2( + const vcl::KeyCode& rCode, + const OUString& rName, + bool bHide, + const OUString& rCondition) +{ + StartAction(); + ::sw::mark::IMark* pMark = getIDocumentMarkAccess()->makeMark( + *GetCursor(), + rName, + IDocumentMarkAccess::MarkType::BOOKMARK, sw::mark::InsertMode::New); + ::sw::mark::IBookmark* pBookmark = dynamic_cast< ::sw::mark::IBookmark* >(pMark); + if (pBookmark) + { + pBookmark->SetKeyCode(rCode); + pBookmark->SetShortName(OUString()); + pBookmark->Hide(bHide); + pBookmark->SetHideCondition(rCondition); + } + EndAction(); + return pMark; +} + +namespace sw { + +bool IsMarkHidden(SwRootFrame const& rLayout, ::sw::mark::IMark const& rMark) +{ + if (!rLayout.IsHideRedlines()) + { + return false; + } + SwTextNode const& rNode(*rMark.GetMarkPos().nNode.GetNode().GetTextNode()); + SwTextFrame const*const pFrame(static_cast( + rNode.getLayoutFrame(&rLayout))); + if (!pFrame) + { + return true; + } + if (rMark.IsExpanded()) + { + SwTextFrame const*const pOtherFrame(static_cast( + rMark.GetOtherMarkPos().nNode.GetNode().GetTextNode()->getLayoutFrame(&rLayout))); + return pFrame == pOtherFrame + && pFrame->MapModelToViewPos(rMark.GetMarkPos()) + == pFrame->MapModelToViewPos(rMark.GetOtherMarkPos()); + } + else + { + if (rMark.GetMarkPos().nContent.GetIndex() == rNode.Len()) + { // at end of node: never deleted (except if node deleted) + return rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden; + } + else + { // check character following mark pos + return pFrame->MapModelToViewPos(rMark.GetMarkPos()) + == pFrame->MapModelToView(&rNode, rMark.GetMarkPos().nContent.GetIndex() + 1); + } + } +} + +} // namespace sw + +// set CurrentCursor.SPoint +bool SwCursorShell::GotoMark(const ::sw::mark::IMark* const pMark, bool bAtStart) +{ + if (sw::IsMarkHidden(*GetLayout(), *pMark)) + { + return false; + } + // watch Cursor-Moves + CursorStateHelper aCursorSt(*this); + if ( bAtStart ) + *aCursorSt.m_pCursor->GetPoint() = pMark->GetMarkStart(); + else + *aCursorSt.m_pCursor->GetPoint() = pMark->GetMarkEnd(); + + if(aCursorSt.RollbackIfIllegal()) return false; + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; +} + +bool SwCursorShell::GotoMark(const ::sw::mark::IMark* const pMark) +{ + if (sw::IsMarkHidden(*GetLayout(), *pMark)) + { + return false; + } + // watch Cursor-Moves + CursorStateHelper aCursorSt(*this); + aCursorSt.SetCursorToMark(pMark); + + if(aCursorSt.RollbackIfIllegal()) return false; + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; +} + +bool SwCursorShell::GoNextBookmark() +{ + IDocumentMarkAccess* pMarkAccess = getIDocumentMarkAccess(); + std::vector<::sw::mark::IMark*> vCandidates; + remove_copy_if( + pMarkAccess->findFirstBookmarkStartsAfter(*GetCursor()->GetPoint()), + pMarkAccess->getBookmarksEnd(), + back_inserter(vCandidates), + &lcl_IsInvisibleBookmark); + + // watch Cursor-Moves + CursorStateHelper aCursorSt(*this); + auto ppMark = vCandidates.begin(); + for(; ppMark!=vCandidates.end(); ++ppMark) + { + if (sw::IsMarkHidden(*GetLayout(), **ppMark)) + { + continue; + } + aCursorSt.SetCursorToMark(*ppMark); + if(!aCursorSt.RollbackIfIllegal()) + break; // found legal move + } + if(ppMark==vCandidates.end()) + { + SttEndDoc(false); + return false; + } + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; +} + +bool SwCursorShell::GoPrevBookmark() +{ + IDocumentMarkAccess* pMarkAccess = getIDocumentMarkAccess(); + // candidates from which to choose the mark before + // no need to consider marks starting after rPos + std::vector<::sw::mark::IMark*> vCandidates; + remove_copy_if( + pMarkAccess->getBookmarksBegin(), + pMarkAccess->findFirstBookmarkStartsAfter(*GetCursor()->GetPoint()), + back_inserter(vCandidates), + &lcl_IsInvisibleBookmark); + sort( + vCandidates.begin(), + vCandidates.end(), + &lcl_ReverseMarkOrderingByEnd); + + // watch Cursor-Moves + CursorStateHelper aCursorSt(*this); + auto ppMark = vCandidates.begin(); + for(; ppMark!=vCandidates.end(); ++ppMark) + { + // ignoring those not ending before the Cursor + // (we were only able to eliminate those starting + // behind the Cursor by the upper_bound(..) + // above) + if(!((**ppMark).GetMarkEnd() < *GetCursor()->GetPoint())) + continue; + if (sw::IsMarkHidden(*GetLayout(), **ppMark)) + { + continue; + } + aCursorSt.SetCursorToMark(*ppMark); + if(!aCursorSt.RollbackIfIllegal()) + break; // found legal move + } + if(ppMark==vCandidates.end()) + { + SttEndDoc(true); + return false; + } + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; +} + +bool SwCursorShell::IsFormProtected() +{ + return getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FORM); +} + +::sw::mark::IFieldmark* SwCursorShell::GetCurrentFieldmark() +{ + // TODO: Refactor + SwPosition pos(*GetCursor()->GetPoint()); + return getIDocumentMarkAccess()->getFieldmarkFor(pos); +} + +::sw::mark::IFieldmark* SwCursorShell::GetFieldmarkAfter() +{ + SwPosition pos(*GetCursor()->GetPoint()); + return getIDocumentMarkAccess()->getFieldmarkAfter(pos); +} + +::sw::mark::IFieldmark* SwCursorShell::GetFieldmarkBefore() +{ + SwPosition pos(*GetCursor()->GetPoint()); + return getIDocumentMarkAccess()->getFieldmarkBefore(pos); +} + +bool SwCursorShell::GotoFieldmark(::sw::mark::IFieldmark const * const pMark) +{ + if(pMark==nullptr) return false; + + // watch Cursor-Moves + CursorStateHelper aCursorSt(*this); + aCursorSt.SetCursorToMark(pMark); + ++aCursorSt.m_pCursor->GetPoint()->nContent; + --aCursorSt.m_pCursor->GetMark()->nContent; + + if(aCursorSt.RollbackIfIllegal()) return false; + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/crossrefbookmark.cxx b/sw/source/core/crsr/crossrefbookmark.cxx new file mode 100644 index 000000000..9fbba6de0 --- /dev/null +++ b/sw/source/core/crsr/crossrefbookmark.cxx @@ -0,0 +1,93 @@ +/* -*- 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 + +namespace +{ + const char CrossRefNumItemBookmark_NamePrefix[] = "__RefNumPara__"; +} + +namespace sw::mark +{ + CrossRefBookmark::CrossRefBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName, + const OUString& rPrefix) + : Bookmark( + // ensure that m_pPos2 is null by only passing start to super + SwPaM(*rPaM.Start()), rCode, rName) + { + assert( IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark(rPaM) + && "" + "- creation of cross-reference bookmark with an illegal PaM that does not expand over exactly one whole paragraph."); + if(rName.isEmpty()) + m_aName = MarkBase::GenerateNewName(rPrefix); + assert(!m_pPos2); + } + + void CrossRefBookmark::SetMarkPos(const SwPosition& rNewPos) + { + assert(rNewPos.nNode.GetNode().GetTextNode() && + "" + " - new bookmark position for cross-reference bookmark doesn't mark text node"); + assert(rNewPos.nContent.GetIndex() == 0 && + "" + " - new bookmark position for cross-reference bookmark doesn't mark start of text node"); + MarkBase::SetMarkPos(rNewPos); + } + + SwPosition& CrossRefBookmark::GetOtherMarkPos() const + { + assert(false && + "" + " - this should never be called!"); + for (;;) { std::abort(); } // avoid "must return a value" warnings + } + + CrossRefHeadingBookmark::CrossRefHeadingBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName) + : CrossRefBookmark(rPaM, rCode, rName, IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()+"_Toc") + { } + + bool CrossRefHeadingBookmark::IsLegalName(const OUString& rName) + { + return rName.match(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()); + } + + CrossRefNumItemBookmark::CrossRefNumItemBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName) + : CrossRefBookmark(rPaM, rCode, rName, CrossRefNumItemBookmark_NamePrefix) + { } + + bool CrossRefNumItemBookmark::IsLegalName(const OUString& rName) + { + return rName.match(CrossRefNumItemBookmark_NamePrefix); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx new file mode 100644 index 000000000..7a0c3b881 --- /dev/null +++ b/sw/source/core/crsr/crsrsh.cxx @@ -0,0 +1,3814 @@ +/* -*- 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 "BlockCursor.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "callnk.hxx" +#include +#include +#include +#include +#include +#include +#include +#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 util; + +/** + * Check if pCurrentCursor points into already existing ranges and delete those. + * @param Pointer to SwCursor object + */ +static void CheckRange( SwCursor* pCurrentCursor ) +{ + const SwPosition *pStt = pCurrentCursor->Start(), + *pEnd = pCurrentCursor->GetPoint() == pStt ? pCurrentCursor->GetMark() : pCurrentCursor->GetPoint(); + + SwPaM *pTmpDel = nullptr, + *pTmp = pCurrentCursor->GetNext(); + + // Search the complete ring + while( pTmp != pCurrentCursor ) + { + const SwPosition *pTmpStt = pTmp->Start(), + *pTmpEnd = pTmp->GetPoint() == pTmpStt ? + pTmp->GetMark() : pTmp->GetPoint(); + if( *pStt <= *pTmpStt ) + { + if( *pEnd > *pTmpStt || + ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd )) + pTmpDel = pTmp; + } + else + if( *pStt < *pTmpEnd ) + pTmpDel = pTmp; + + // If Point or Mark is within the Cursor range, we need to remove the old + // range. Take note that Point does not belong to the range anymore. + pTmp = pTmp->GetNext(); + delete pTmpDel; // Remove old range + pTmpDel = nullptr; + } +} + +// SwCursorShell + +/** + * Add a copy of current cursor, append it after current, and collapse current cursor. + * @return - Returns a newly created copy of current cursor. + */ +SwPaM * SwCursorShell::CreateCursor() +{ + // don't create new Cursor with active table Selection + assert(!IsTableMode()); + + // New cursor as copy of current one. Add to the ring. + // Links point to previously created one, ie forward. + SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); + + // Hide PaM logically, to avoid undoing the inverting from + // copied PaM (#i75172#) + pNew->swapContent(*m_pCurrentCursor); + + m_pCurrentCursor->DeleteMark(); + + UpdateCursor( SwCursorShell::SCROLLWIN ); + return pNew; +} + +/** + * Delete current Cursor, making the following one the current. + * Note, this function does not delete anything if there is no other cursor. + * @return - returns true if there was another cursor and we deleted one. + */ +void SwCursorShell::DestroyCursor() +{ + // don't delete Cursor with active table Selection + assert(!IsTableMode()); + + // Is there a next one? Don't do anything if not. + if(!m_pCurrentCursor->IsMultiSelection()) + return; + + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursor* pNextCursor = static_cast(m_pCurrentCursor->GetNext()); + delete m_pCurrentCursor; + m_pCurrentCursor = dynamic_cast(pNextCursor); + UpdateCursor(); +} + +/** + * Create and return a new shell cursor. + * Simply returns the current shell cursor if there is no selection + * (HasSelection()). + */ +SwPaM & SwCursorShell::CreateNewShellCursor() +{ + if (HasSelection()) + { + (void) CreateCursor(); // n.b. returns old cursor + } + return *GetCursor(); +} + +/** + * Return the current shell cursor + * @return - returns current `SwPaM` shell cursor + */ +SwPaM & SwCursorShell::GetCurrentShellCursor() +{ + return *GetCursor(); +} + +/** + * Return pointer to the current shell cursor + * @return - returns pointer to current `SwPaM` shell cursor + */ +SwPaM* SwCursorShell::GetCursor( bool bMakeTableCursor ) const +{ + if( m_pTableCursor ) + { + if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() ) + { + //don't re-create 'parked' cursors + const SwContentNode* pCNd; + if( m_pTableCursor->GetPoint()->nNode.GetIndex() && + m_pTableCursor->GetMark()->nNode.GetIndex() ) + { + pCNd = m_pTableCursor->GetContentNode(); + if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) + { + pCNd = m_pTableCursor->GetContentNode(false); + if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) + { + SwShellTableCursor* pTC = m_pTableCursor; + GetLayout()->MakeTableCursors( *pTC ); + } + } + } + } + + if( m_pTableCursor->IsChgd() ) + { + const_cast(this)->m_pCurrentCursor = + dynamic_cast(m_pTableCursor->MakeBoxSels( m_pCurrentCursor )); + } + } + return m_pCurrentCursor; +} + +void SwCursorShell::StartAction() +{ + if( !ActionPend() ) + { + // save for update of the ribbon bar + const SwNode& rNd = m_pCurrentCursor->GetPoint()->nNode.GetNode(); + m_nCurrentNode = rNd.GetIndex(); + m_nCurrentContent = m_pCurrentCursor->GetPoint()->nContent.GetIndex(); + m_nCurrentNdTyp = rNd.GetNodeType(); + if( rNd.IsTextNode() ) + m_nLeftFramePos = SwCallLink::getLayoutFrame( GetLayout(), *rNd.GetTextNode(), m_nCurrentContent, true ); + else + m_nLeftFramePos = 0; + } + SwViewShell::StartAction(); // to the SwViewShell +} + +void SwCursorShell::EndAction( const bool bIdleEnd, const bool DoSetPosX ) +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + bool bVis = m_bSVCursorVis; + + sal_uInt16 eFlags = SwCursorShell::CHKRANGE; + if ( !DoSetPosX ) + eFlags |= SwCursorShell::UPDOWN; + + + // Idle-formatting? + if( bIdleEnd && Imp()->GetRegion() ) + { + m_pCurrentCursor->Hide(); + } + + // Update all invalid numberings before the last action + if( 1 == mnStartAction ) + GetDoc()->UpdateNumRule(); + + // #i76923#: Don't show the cursor in the SwViewShell::EndAction() - call. + // Only the UpdateCursor shows the cursor. + bool bSavSVCursorVis = m_bSVCursorVis; + m_bSVCursorVis = false; + + SwViewShell::EndAction( bIdleEnd ); // have SwViewShell go first + + m_bSVCursorVis = bSavSVCursorVis; + + if( ActionPend() ) + { + if( bVis ) // display SV-Cursor again + m_pVisibleCursor->Show(); + + return; + } + + if ( !bIdleEnd ) + eFlags |= SwCursorShell::SCROLLWIN; + + UpdateCursor( eFlags, bIdleEnd ); // Show Cursor changes + + { + SwCallLink aLk( *this ); // Watch cursor moves, + aLk.m_nNode = m_nCurrentNode; // possibly call the link + aLk.m_nNodeType = m_nCurrentNdTyp; + aLk.m_nContent = m_nCurrentContent; + aLk.m_nLeftFramePos = m_nLeftFramePos; + + if( !m_nCursorMove || + ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) ) + // display Cursor & Selections again + ShowCursors( m_bSVCursorVis ); + } + // call ChgCall if there is still one + if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() ) + { + m_aChgLnk.Call(nullptr); + m_bChgCallFlag = false; // reset flag + } +} + +void SwCursorShell::SttCursorMove() +{ +#ifdef DBG_UTIL + OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." ); +#endif + ++m_nCursorMove; + StartAction(); +} + +void SwCursorShell::EndCursorMove( const bool bIdleEnd ) +{ +#ifdef DBG_UTIL + OSL_ENSURE( m_nCursorMove, "EndCursorMove() without SttCursorMove()." ); +#endif + EndAction( bIdleEnd, true ); + --m_nCursorMove; +#ifdef DBG_UTIL + if( !m_nCursorMove ) + m_bInCMvVisportChgd = false; +#endif +} + +bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, sal_uInt16 nMode, + bool bVisualAllowed ) +{ + if( IsTableMode() ) + return bLeft ? GoPrevCell() : GoNextCell(); + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + bool bRet = false; + + // #i27615# Handle cursor in front of label. + const SwTextNode* pTextNd = nullptr; + + if( m_pBlockCursor ) + m_pBlockCursor->clearPoints(); + + // 1. CASE: Cursor is in front of label. A move to the right + // will simply reset the bInFrontOfLabel flag: + SwShellCursor* pShellCursor = getShellCursor( true ); + if ( !bLeft && pShellCursor->IsInFrontOfLabel() ) + { + SetInFrontOfLabel( false ); + bRet = true; + } + // 2. CASE: Cursor is at beginning of numbered paragraph. A move + // to the left will simply set the bInFrontOfLabel flag: + else if (bLeft + && pShellCursor->GetPoint()->nNode.GetNode().IsTextNode() + && static_cast( + pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->getLayoutFrame(GetLayout()) + )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0) + && !pShellCursor->IsInFrontOfLabel() + && !pShellCursor->HasMark() + && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->nNode)) + && pTextNd->HasVisibleNumberingOrBullet()) + { + SetInFrontOfLabel( true ); + bRet = true; + } + // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag: + else + { + const bool bSkipHidden = !GetViewOptions()->IsShowHiddenChar(); + // #i107447# + // To avoid loop the reset of flag is no longer + // reflected in the return value . + const bool bResetOfInFrontOfLabel = SetInFrontOfLabel( false ); + bRet = pShellCursor->LeftRight( bLeft, nCnt, nMode, bVisualAllowed, + bSkipHidden, !IsOverwriteCursor(), + GetLayout()); + if ( !bRet && bLeft && bResetOfInFrontOfLabel ) + { + // undo reset of flag + SetInFrontOfLabel( true ); + } + } + + if( bRet ) + { + UpdateCursor(); + } + + return bRet; +} + +void SwCursorShell::MarkListLevel( const OUString& sListId, + const int nListLevel ) +{ + if ( sListId != m_sMarkedListId || + nListLevel != m_nMarkedListLevel) + { + if ( !m_sMarkedListId.isEmpty() ) + mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false ); + + if ( !sListId.isEmpty() ) + { + mxDoc->MarkListLevel( sListId, nListLevel, true ); + } + + m_sMarkedListId = sListId; + m_nMarkedListLevel = nListLevel; + } +} + +void SwCursorShell::UpdateMarkedListLevel() +{ + SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), + GetCursor_()->GetPoint()->nNode); + + if ( pTextNd ) + { + if (!pTextNd->IsNumbered(GetLayout())) + { + m_pCurrentCursor->SetInFrontOfLabel_( false ); + MarkListLevel( OUString(), 0 ); + } + else if ( m_pCurrentCursor->IsInFrontOfLabel() ) + { + if ( pTextNd->IsInList() ) + { + assert(pTextNd->GetActualListLevel() >= 0 && + pTextNd->GetActualListLevel() < MAXLEVEL); + MarkListLevel( pTextNd->GetListId(), + pTextNd->GetActualListLevel() ); + } + } + else + { + MarkListLevel( OUString(), 0 ); + } + } +} + +void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage) +{ +#ifdef ACCESSIBLE_LAYOUT + if( Imp()->IsAccessible() ) + Imp()->FirePageChangeEvent( nOldPage, nNewPage ); +#else + (void)nOldPage; + (void)nNewPage; +#endif +} + +void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn) +{ +#ifdef ACCESSIBLE_LAYOUT + if( Imp()->IsAccessible() ) + Imp()->FireColumnChangeEvent( nOldColumn, nNewColumn); +#else + (void)nOldColumn; + (void)nNewColumn; +#endif +} + +void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection) +{ +#ifdef ACCESSIBLE_LAYOUT + if( Imp()->IsAccessible() ) + Imp()->FireSectionChangeEvent( nOldSection, nNewSection ); +#else + (void)nOldSection; + (void)nNewSection; +#endif +} + +bool SwCursorShell::bColumnChange() +{ + SwFrame* pCurrFrame = GetCurrFrame(false); + + if (pCurrFrame == nullptr) + { + return false; + } + + SwFrame* pCurrCol=pCurrFrame->FindColFrame(); + + while(pCurrCol== nullptr && pCurrFrame!=nullptr ) + { + SwLayoutFrame* pParent = pCurrFrame->GetUpper(); + if(pParent!=nullptr) + { + pCurrCol=static_cast(pParent)->FindColFrame(); + pCurrFrame = pParent; + } + else + { + break; + } + } + + if(m_oldColFrame == pCurrCol) + return false; + else + { + m_oldColFrame = pCurrCol; + return true; + } +} + +bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt ) +{ + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + bool bTableMode = IsTableMode(); + SwShellCursor* pTmpCursor = getShellCursor( true ); + + bool bRet = pTmpCursor->UpDown( bUp, nCnt ); + // #i40019# UpDown should always reset the bInFrontOfLabel flag: + bRet |= SetInFrontOfLabel(false); + + if( m_pBlockCursor ) + m_pBlockCursor->clearPoints(); + + if( bRet ) + { + m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint + if( !ActionPend() ) + { + CursorFlag eUpdateMode = SwCursorShell::SCROLLWIN; + if( !bTableMode ) + eUpdateMode = static_cast(eUpdateMode + | SwCursorShell::UPDOWN | SwCursorShell::CHKRANGE); + UpdateCursor( static_cast(eUpdateMode) ); + } + } + return bRet; +} + +bool SwCursorShell::LRMargin( bool bLeft, bool bAPI) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SET_CURR_SHELL( this ); + m_eMvState = CursorMoveState::LeftMargin; // status for Cursor travelling - GetModelPositionForViewPoint + + const bool bTableMode = IsTableMode(); + SwShellCursor* pTmpCursor = getShellCursor( true ); + + if( m_pBlockCursor ) + m_pBlockCursor->clearPoints(); + + const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI); + + bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI); + + if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() ) + { + const SwTextNode * pTextNd = GetCursor_()->GetNode().GetTextNode(); + assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->nNode) == pTextNd); + if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() ) + SetInFrontOfLabel( true ); + } + else if ( !bLeft ) + { + bRet = SetInFrontOfLabel( false ) || bRet; + } + + if( bRet ) + { + UpdateCursor(); + } + return bRet; +} + +bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const +{ + const SwShellCursor* pTmpCursor = getShellCursor( true ); + return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI); +} + +bool SwCursorShell::SttEndDoc( bool bStt ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor; + bool bRet = pTmpCursor->SttEndDoc( bStt ); + if( bRet ) + { + if( bStt ) + pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header) + if( m_pBlockCursor ) + { + m_pBlockCursor->clearPoints(); + RefreshBlockCursor(); + } + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +void SwCursorShell::ExtendedSelectAll(bool bFootnotes) +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwPosition* pPos = m_pCurrentCursor->GetPoint(); + pPos->nNode = bFootnotes ? rNodes.GetEndOfPostIts() : rNodes.GetEndOfAutotext(); + pPos->nContent.Assign( rNodes.GoNext( &pPos->nNode ), 0 ); + pPos = m_pCurrentCursor->GetMark(); + pPos->nNode = rNodes.GetEndOfContent(); + SwContentNode* pCNd = SwNodes::GoPrevious( &pPos->nNode ); + pPos->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); +} + +bool SwCursorShell::ExtendedSelectedAll() +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwNodeIndex nNode = rNodes.GetEndOfAutotext(); + SwContentNode* pStart = rNodes.GoNext(&nNode); + + nNode = rNodes.GetEndOfContent(); + SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); + + if (!pStart || !pEnd) + return false; + + SwPosition aStart(*pStart, 0); + SwPosition aEnd(*pEnd, pEnd->Len()); + SwShellCursor* pShellCursor = getShellCursor(false); + return aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End(); +} + +bool SwCursorShell::StartsWithTable() +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwNodeIndex nNode(rNodes.GetEndOfExtras()); + SwContentNode* pContentNode = rNodes.GoNext(&nNode); + return pContentNode->FindTableNode(); +} + +bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage ) +{ + bool bRet = false; + + // never jump of section borders at selection + if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SET_CURR_SHELL( this ); + + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + Point& rPt = m_pCurrentCursor->GetPtPos(); + std::pair tmp(rPt, false); + SwContentFrame * pFrame = m_pCurrentCursor->GetContentNode()-> + getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos )) + { + UpdateCursor(); + bRet = true; + } + } + return bRet; +} + +bool SwCursorShell::isInHiddenTextFrame(SwShellCursor* pShellCursor) +{ + SwContentNode *pCNode = pShellCursor->GetContentNode(); + std::pair tmp(pShellCursor->GetPtPos(), false); + SwContentFrame *const pFrame = pCNode + ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp) + : nullptr; + return !pFrame || (pFrame->IsTextFrame() && static_cast(pFrame)->IsHiddenNow()); +} + +// sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara +static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell, + SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara) +{ + SwContentNode *const pCNode = pShellCursor->GetContentNode(); + assert(pCNode); // surely can't have moved otherwise? + std::pair tmp(pShellCursor->GetPtPos(), false); + SwContentFrame const*const pFrame = pCNode->getLayoutFrame( + pShell->GetLayout(), pShellCursor->GetPoint(), &tmp); + if (!pFrame || !pFrame->IsTextFrame()) + { + return false; + } + SwTextFrame const& rTextFrame(static_cast(*pFrame)); + TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint())); + if (&fnParaStart == &fnPosPara) + { + return ix == TextFrameIndex(0); + } + else + { + assert(&fnParaEnd == &fnPosPara); + return ix == TextFrameIndex(rTextFrame.GetText().getLength()); + } +} + +bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwShellCursor* pTmpCursor = getShellCursor( true ); + bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara ); + if( bRet ) + { + //keep going until we get something visible, i.e. skip + //over hidden paragraphs, don't get stuck at the start + //which is what SwCursorShell::UpdateCursorPos will reset + //the position to if we pass it a position in an + //invisible hidden paragraph field + while (isInHiddenTextFrame(pTmpCursor) + || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara)) + { + if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara)) + break; + } + + UpdateCursor(); + } + return bRet; +} + +bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect, + SwMoveFnCollection const & fnPosSect) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursor* pTmpCursor = getShellCursor( true ); + bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect ); + if( bRet ) + UpdateCursor(); + return bRet; + +} + +// position cursor + +static SwFrame* lcl_IsInHeaderFooter( const SwNodeIndex& rIdx, Point& rPt ) +{ + SwFrame* pFrame = nullptr; + SwContentNode* pCNd = rIdx.GetNode().GetContentNode(); + if( pCNd ) + { + std::pair tmp(rPt, false); + SwContentFrame *pContentFrame = pCNd->getLayoutFrame( + pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr; + while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() ) + pFrame = pFrame->IsFlyFrame() ? static_cast(pFrame)->AnchorFrame() + : pFrame->GetUpper(); + } + return pFrame; +} + +bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const +{ + Point aPt; + SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->nNode, aPt ); + if( pFrame && pbInHeader ) + *pbInHeader = pFrame->IsHeaderFrame(); + return nullptr != pFrame; +} + +int SwCursorShell::SetCursor( const Point &rLPt, bool bOnlyText, bool bBlock ) +{ + SET_CURR_SHELL( this ); + + SwShellCursor* pCursor = getShellCursor( bBlock ); + SwPosition aPos( *pCursor->GetPoint() ); + Point aPt( rLPt ); + Point & rCurrentCursorPt = pCursor->GetPtPos(); + SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel : + bOnlyText ? CursorMoveState::SetOnlyText : CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + + SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + + if ( pTextNd && !IsTableMode() && + // #i37515# No bInFrontOfLabel during selection + !pCursor->HasMark() && + pTextNd->HasVisibleNumberingOrBullet() ) + { + aTmpState.m_bInFrontOfLabel = true; // #i27615# + } + else + { + aTmpState.m_bInFrontOfLabel = false; + } + + int bRet = CRSR_POSOLD | + ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) + ? 0 : CRSR_POSCHG ); + + const bool bOldInFrontOfLabel = IsInFrontOfLabel(); + const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel; + + pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel ); + + if( CursorMoveState::RightMargin == aTmpState.m_eState ) + m_eMvState = CursorMoveState::RightMargin; + // is the new position in header or footer? + SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.nNode, aPt ); + if( IsTableMode() && !pFrame && aPos.nNode.GetNode().StartOfSectionNode() == + pCursor->GetPoint()->nNode.GetNode().StartOfSectionNode() ) + // same table column and not in header/footer -> back + return bRet; + + if( m_pBlockCursor && bBlock ) + { + m_pBlockCursor->setEndPoint( rLPt ); + if( !pCursor->HasMark() ) + m_pBlockCursor->setStartPoint( rLPt ); + else if( !m_pBlockCursor->getStartPoint() ) + m_pBlockCursor->setStartPoint( pCursor->GetMkPos() ); + } + if( !pCursor->HasMark() ) + { + // is at the same position and if in header/footer -> in the same + if( aPos == *pCursor->GetPoint() && + bOldInFrontOfLabel == bNewInFrontOfLabel ) + { + if( pFrame ) + { + if( pFrame->getFrameArea().IsInside( rCurrentCursorPt )) + return bRet; + } + else if( aPos.nNode.GetNode().IsContentNode() ) + { + // in the same frame? + std::pair tmp(m_aCharRect.Pos(), false); + SwFrame* pOld = static_cast(aPos.nNode.GetNode()).getLayoutFrame( + GetLayout(), nullptr, &tmp); + tmp.first = aPt; + SwFrame* pNew = static_cast(aPos.nNode.GetNode()).getLayoutFrame( + GetLayout(), nullptr, &tmp); + if( pNew == pOld ) + return bRet; + } + } + } + else + { + // SSelection over not allowed sections or if in header/footer -> different + if( !CheckNodesRange( aPos.nNode, pCursor->GetMark()->nNode, true ) + || ( pFrame && !pFrame->getFrameArea().IsInside( pCursor->GetMkPos() ) )) + return bRet; + + // is at same position but not in header/footer + if( aPos == *pCursor->GetPoint() ) + return bRet; + } + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursorSaveState aSaveState( *pCursor ); + + *pCursor->GetPoint() = aPos; + rCurrentCursorPt = aPt; + + // #i41424# Only update the marked number levels if necessary + // Force update of marked number levels if necessary. + if ( bNewInFrontOfLabel || bOldInFrontOfLabel ) + m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel ); + SetInFrontOfLabel( bNewInFrontOfLabel ); + + if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) ) + { + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE ); + bRet &= ~CRSR_POSOLD; + } + else if( bOnlyText && !m_pCurrentCursor->HasMark() ) + { + if( FindValidContentNode( bOnlyText ) ) + { + // position cursor in a valid content + if( aPos == *pCursor->GetPoint() ) + bRet = CRSR_POSOLD; + else + { + UpdateCursor(); + bRet &= ~CRSR_POSOLD; + } + } + else + { + // there is no valid content -> hide cursor + m_pVisibleCursor->Hide(); // always hide visible cursor + m_eMvState = CursorMoveState::NONE; // status for Cursor travelling + m_bAllProtect = true; + if( GetDoc()->GetDocShell() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI(); + CallChgLnk(); // notify UI + } + } + } + return bRet; +} + +void SwCursorShell::TableCursorToCursor() +{ + assert(m_pTableCursor); + delete m_pTableCursor; + m_pTableCursor = nullptr; +} + +void SwCursorShell::BlockCursorToCursor() +{ + assert(m_pBlockCursor); + if( m_pBlockCursor && !HasSelection() ) + { + SwPaM& rPam = m_pBlockCursor->getShellCursor(); + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetPoint() = *rPam.GetPoint(); + if( rPam.HasMark() ) + *m_pCurrentCursor->GetMark() = *rPam.GetMark(); + else + m_pCurrentCursor->DeleteMark(); + } + delete m_pBlockCursor; + m_pBlockCursor = nullptr; +} + +void SwCursorShell::CursorToBlockCursor() +{ + if( !m_pBlockCursor ) + { + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + m_pBlockCursor = new SwBlockCursor( *this, aPos ); + SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); + rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos(); + if( m_pCurrentCursor->HasMark() ) + { + rBlock.SetMark(); + *rBlock.GetMark() = *m_pCurrentCursor->GetMark(); + rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos(); + } + } + m_pBlockCursor->clearPoints(); + RefreshBlockCursor(); +} + +void SwCursorShell::ClearMark() +{ + // is there any GetMark? + if( m_pTableCursor ) + { + std::vector vCursors; + for(auto& rCursor : m_pCurrentCursor->GetRingContainer()) + if(&rCursor != m_pCurrentCursor) + vCursors.push_back(&rCursor); + for(auto pCursor : vCursors) + delete pCursor; + m_pTableCursor->DeleteMark(); + + m_pCurrentCursor->DeleteMark(); + + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + m_pCurrentCursor->SwSelPaintRects::Show(); + } + else + { + if( !m_pCurrentCursor->HasMark() ) + return; + m_pCurrentCursor->DeleteMark(); + if( !m_nCursorMove ) + m_pCurrentCursor->SwSelPaintRects::Show(); + } +} + +void SwCursorShell::NormalizePam(bool bPointFirst) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor->Normalize(bPointFirst); +} + +void SwCursorShell::SwapPam() +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor->Exchange(); +} + +//TODO: provide documentation +/** Search in the selected area for a Selection that covers the given point. + + It checks if a Selection exists but does + not move the current cursor. + + @param rPt The point to search at. + @param bTstHit ??? +*/ +bool SwCursorShell::TestCurrPam( + const Point & rPt, + bool bTstHit ) +{ + SET_CURR_SHELL( this ); + + // check if the SPoint is in a table selection + if( m_pTableCursor ) + return m_pTableCursor->IsInside( rPt ); + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + // search position in document + SwPosition aPtPos( *m_pCurrentCursor->GetPoint() ); + Point aPt( rPt ); + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit ) + return false; + + // search in all selections for this position + SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor + do + { + if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos) + return true; // return without update + pCmp = pCmp->GetNext(); + } while (m_pCurrentCursor != pCmp); + return false; +} + +void SwCursorShell::KillPams() +{ + // Does any exist for deletion? + if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() ) + return; + + while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) + delete m_pCurrentCursor->GetNext(); + m_pCurrentCursor->SetColumnSelection( false ); + + if( m_pTableCursor ) + { + // delete the ring of cursors + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + } + else if( m_pBlockCursor ) + { + // delete the ring of cursors + m_pCurrentCursor->DeleteMark(); + SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); + *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint(); + m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos(); + rBlock.DeleteMark(); + m_pBlockCursor->clearPoints(); + } + UpdateCursor( SwCursorShell::SCROLLWIN ); +} + +int SwCursorShell::CompareCursorStackMkCurrPt() const +{ + int nRet = 0; + const SwPosition *pFirst = nullptr, *pSecond = nullptr; + const SwPaM *pCur = GetCursor(), *pStack = m_pStackCursor; + // cursor on stack is needed if we compare against stack + if( pStack ) + { + pFirst = pStack->GetMark(); + pSecond = pCur->GetPoint(); + } + if( !pFirst || !pSecond ) + nRet = INT_MAX; + else if( *pFirst < *pSecond ) + nRet = -1; + else if( *pFirst == *pSecond ) + nRet = 0; + else + nRet = 1; + return nRet; +} + +bool SwCursorShell::IsSelOnePara() const +{ + if (m_pCurrentCursor->IsMultiSelection()) + { + return false; + } + if (m_pCurrentCursor->GetPoint()->nNode == m_pCurrentCursor->GetMark()->nNode) + { + return true; + } + if (GetLayout()->IsHideRedlines()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + auto const n(m_pCurrentCursor->GetMark()->nNode.GetIndex()); + return FrameContainsNode(*pFrame, n); + } + return false; +} + +bool SwCursorShell::IsSttPara() const +{ + if (GetLayout()->IsHideRedlines()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->nNode.GetNode().GetTextNode()); + if (pNode) + { + SwTextFrame const*const pFrame(static_cast( + pNode->getLayoutFrame(GetLayout()))); + if (pFrame) + { + return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint()) + == TextFrameIndex(0); + } + } + } + return m_pCurrentCursor->GetPoint()->nContent == 0; +} + +bool SwCursorShell::IsEndPara() const +{ + if (GetLayout()->IsHideRedlines()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->nNode.GetNode().GetTextNode()); + if (pNode) + { + SwTextFrame const*const pFrame(static_cast( + pNode->getLayoutFrame(GetLayout()))); + if (pFrame) + { + return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint()) + == TextFrameIndex(pFrame->GetText().getLength()); + } + } + } + return m_pCurrentCursor->GetPoint()->nContent == m_pCurrentCursor->GetContentNode()->Len(); +} + +bool SwCursorShell::IsEndOfTable() const +{ + if (IsTableMode() || IsBlockMode() || !IsEndPara()) + { + return false; + } + SwTableNode const*const pTableNode( IsCursorInTable() ); + if (!pTableNode) + { + return false; + } + SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode()); + SwNodeIndex const lastNode(*pEndTableNode, -2); + SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core", + "text node expected"); + return (lastNode == m_pCurrentCursor->GetPoint()->nNode); +} + +bool SwCursorShell::IsCursorInFootnote() const +{ + SwStartNodeType aStartNodeType = m_pCurrentCursor->GetNode().StartOfSectionNode()->GetStartNodeType(); + return aStartNodeType == SwStartNodeType::SwFootnoteStartNode; +} + +bool SwCursorShell::IsInFrontOfLabel() const +{ + return m_pCurrentCursor->IsInFrontOfLabel(); +} + +bool SwCursorShell::SetInFrontOfLabel( bool bNew ) +{ + if ( bNew != IsInFrontOfLabel() ) + { + m_pCurrentCursor->SetInFrontOfLabel_( bNew ); + UpdateMarkedListLevel(); + return true; + } + return false; +} + +namespace { + +void collectUIInformation(const OUString& aPage) +{ + EventDescription aDescription; + aDescription.aAction = "GOTO"; + aDescription.aParameters = {{"PAGE", aPage}}; + aDescription.aID = "writer_edit"; + aDescription.aKeyWord = "SwEditWinUIObject"; + aDescription.aParent = "MainWindow"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +bool SwCursorShell::GotoPage( sal_uInt16 nPage ) +{ + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + + collectUIInformation(OUString::number(nPage)); + return bRet; +} + +void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos) +{ + SwContentFrame* pFrame = GetCurrFrame(); + pFrame->GetCharRect( rRect, *pPos ); +} + +void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum, + bool bAtCursorPos, const bool bCalcFrame ) +{ + SET_CURR_SHELL( this ); + // page number: first visible page or the one at the cursor + const SwContentFrame* pCFrame; + const SwPageFrame *pPg = nullptr; + + if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) || + nullptr == (pPg = pCFrame->FindPageFrame()) ) + { + pPg = Imp()->GetFirstVisPage(GetOut()); + while( pPg && pPg->IsEmptyPage() ) + pPg = static_cast(pPg->GetNext()); + } + // pPg has to exist with a default of 1 for the special case "Writerstart" + rnPhyNum = pPg? pPg->GetPhyPageNum() : 1; + rnVirtNum = pPg? pPg->GetVirtPageNum() : 1; +} + +sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty() +{ + SET_CURR_SHELL(this); + // page number: first visible page or the one at the cursor + const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true); + const SwPageFrame* pPg = nullptr; + + if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame())) + { + pPg = Imp()->GetFirstVisPage(GetOut()); + while (pPg && pPg->IsEmptyPage()) + pPg = static_cast(pPg->GetNext()); + } + + sal_uInt16 nPageNo = 0; + while (pPg) + { + if (!pPg->IsEmptyPage()) + ++nPageNo; + pPg = static_cast(pPg->GetPrev()); + } + return nPageNo; +} + +sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext ) +{ + SET_CURR_SHELL( this ); + // page number: first visible page or the one at the cursor + const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut()); + if( pPg ) + { + const SwTwips nPageTop = pPg->getFrameArea().Top(); + + if( bNext ) + { + // go to next view layout row: + do + { + pPg = static_cast(pPg->GetNext()); + } + while( pPg && pPg->getFrameArea().Top() == nPageTop ); + + while( pPg && pPg->IsEmptyPage() ) + pPg = static_cast(pPg->GetNext()); + } + else + { + // go to previous view layout row: + do + { + pPg = static_cast(pPg->GetPrev()); + } + while( pPg && pPg->getFrameArea().Top() == nPageTop ); + + while( pPg && pPg->IsEmptyPage() ) + pPg = static_cast(pPg->GetPrev()); + } + } + // pPg has to exist with a default of 1 for the special case "Writerstart" + return pPg ? pPg->GetPhyPageNum() : USHRT_MAX; +} + +sal_uInt16 SwCursorShell::GetPageCnt() +{ + SET_CURR_SHELL( this ); + // return number of pages + return GetLayout()->GetPageNum(); +} + +OUString SwCursorShell::getPageRectangles() +{ + CurrShell aCurr(this); + SwRootFrame* pLayout = GetLayout(); + OUStringBuffer aBuf; + for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext()) + { + aBuf.append(pFrame->getFrameArea().Left()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Top()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Width()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Height()); + aBuf.append("; "); + } + if (!aBuf.isEmpty()) + aBuf.setLength( aBuf.getLength() - 2); // remove the last "; " + return aBuf.makeStringAndClear(); +} + +void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const +{ + auto pView = const_cast(GetDrawView()); + if (pView->GetTextEditObject()) + { + // Blinking cursor. + EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); + rEditView.RegisterOtherShell(pOtherShell); + rEditView.ShowCursor(); + rEditView.RegisterOtherShell(nullptr); + // Text selection, if any. + rEditView.DrawSelectionXOR(pOtherShell); + + // Shape text lock. + if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView()) + { + OString sRect = pOutlinerView->GetOutputArea().toString(); + SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect); + } + } + else + { + // Cursor position. + m_pVisibleCursor->SetPosAndShow(pOtherShell); + // Cursor visibility. + if (GetSfxViewShell() != pOtherShell) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + // Text selection. + m_pCurrentCursor->Show(pOtherShell); + // Graphic selection. + pView->AdjustMarkHdl(pOtherShell); + } +} + +/// go to the next SSelection +bool SwCursorShell::GoNextCursor() +{ + if( !m_pCurrentCursor->IsMultiSelection() ) + return false; + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor = m_pCurrentCursor->GetNext(); + + // #i24086#: show also all others + if( !ActionPend() ) + { + UpdateCursor(); + m_pCurrentCursor->Show(nullptr); + } + return true; +} + +/// go to the previous SSelection +bool SwCursorShell::GoPrevCursor() +{ + if( !m_pCurrentCursor->IsMultiSelection() ) + return false; + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + m_pCurrentCursor = m_pCurrentCursor->GetPrev(); + + // #i24086#: show also all others + if( !ActionPend() ) + { + UpdateCursor(); + m_pCurrentCursor->Show(nullptr); + } + return true; +} + +bool SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + if( !m_pCurrentCursor->IsMultiSelection() ) + { + if( !m_pCurrentCursor->HasMark() ) + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + return bNext ? GoNextCursor() : GoPrevCursor(); +} + +void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect) +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + SET_CURR_SHELL( this ); + + // always switch off all cursors when painting + SwRect aRect( rRect ); + + bool bVis = false; + // if a cursor is visible then hide the SV cursor + if( m_pVisibleCursor->IsVisible() && !aRect.IsOver( m_aCharRect ) ) + { + bVis = true; + m_pVisibleCursor->Hide(); + } + + // re-paint area + SwViewShell::Paint(rRenderContext, rRect); + + if( m_bHasFocus && !m_bBasicHideCursor ) + { + SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + + if( !ActionPend() ) + { + // so that right/bottom borders will not be cropped + pCurrentCursor->Invalidate( VisArea() ); + pCurrentCursor->Show(nullptr); + } + else + pCurrentCursor->Invalidate( aRect ); + + } + + if (SwPostItMgr* pPostItMgr = GetPostItMgr()) + { + // No point in showing the cursor for Writer text when there is an + // active annotation edit. + if (bVis) + bVis = !pPostItMgr->HasActiveSidebarWin(); + } + + if( m_bSVCursorVis && bVis ) // also show SV cursor again + m_pVisibleCursor->Show(); +} + +void SwCursorShell::VisPortChgd( const SwRect & rRect ) +{ + SET_CURR_SHELL( this ); + bool bVis; // switch off all cursors when scrolling + + // if a cursor is visible then hide the SV cursor + bVis = m_pVisibleCursor->IsVisible(); + if( bVis ) + m_pVisibleCursor->Hide(); + + m_bVisPortChgd = true; + m_aOldRBPos.setX(VisArea().Right()); + m_aOldRBPos.setY(VisArea().Bottom()); + + // For not having problems with the SV cursor, Update() is called for the + // Window in SwViewShell::VisPo... + // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact? + SwViewShell::VisPortChgd( rRect ); // move area + + if( m_bSVCursorVis && bVis ) // show SV cursor again + m_pVisibleCursor->Show(); + + if( m_nCursorMove ) + m_bInCMvVisportChgd = true; + + m_bVisPortChgd = false; +} + +/** Set the cursor back into content. + + This should only be called if the cursor was move somewhere else (e.g. when + deleting a border). The new position is calculated from its current position + in the layout. +*/ +void SwCursorShell::UpdateCursorPos() +{ + SET_CURR_SHELL( this ); + ++mnStartAction; + SwShellCursor* pShellCursor = getShellCursor( true ); + Size aOldSz( GetDocSize() ); + + if( isInHiddenTextFrame(pShellCursor) ) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(), + &aTmpState ); + pShellCursor->DeleteMark(); + } + IGrammarContact *pGrammarContact = GetDoc() ? GetDoc()->getGrammarContact() : nullptr; + if( pGrammarContact ) + pGrammarContact->updateCursorPosition( *m_pCurrentCursor->GetPoint() ); + --mnStartAction; + if( aOldSz != GetDocSize() ) + SizeChgNotify(); +} + +// #i65475# - if Point/Mark in hidden sections, move them out +static bool lcl_CheckHiddenSection( SwNodeIndex& rIdx ) +{ + bool bOk = true; + const SwSectionNode* pSectNd = rIdx.GetNode().FindSectionNode(); + if( pSectNd && pSectNd->GetSection().IsHiddenFlag() ) + { + SwNodeIndex aTmp( *pSectNd ); + const SwNode* pFrameNd = + rIdx.GetNodes().FindPrvNxtFrameNode( aTmp, pSectNd->EndOfSectionNode() ); + bOk = pFrameNd != nullptr; + SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames"); + rIdx = aTmp; + } + return bOk; +} + +/// Try to set the cursor to the next visible content node. +static void lcl_CheckHiddenPara( SwPosition& rPos ) +{ + SwNodeIndex aTmp( rPos.nNode ); + SwTextNode* pTextNd = aTmp.GetNode().GetTextNode(); + while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) ) + { + SwContentNode* pContent = aTmp.GetNodes().GoNext( &aTmp ); + if ( pContent && pContent->IsTextNode() ) + pTextNd = pContent->GetTextNode(); + else + pTextNd = nullptr; + } + + if ( pTextNd ) + rPos = SwPosition( aTmp, SwIndex( pTextNd, 0 ) ); +} + +namespace { + +// #i27301# - helper class that notifies the accessibility about invalid text +// selections in its destructor +class SwNotifyAccAboutInvalidTextSelections +{ + private: + SwCursorShell& mrCursorSh; + + public: + explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh ) + : mrCursorSh( _rCursorSh ) + {} + + ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE + { + mrCursorSh.InvalidateAccessibleParaTextSelection(); + } +}; + +} + +void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd ) +{ + SET_CURR_SHELL( this ); + ClearUpCursors(); + + if (ActionPend()) + { + if ( eFlags & SwCursorShell::READONLY ) + m_bIgnoreReadonly = true; + return; // if not then no update + } + + SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this ); + + if ( m_bIgnoreReadonly ) + { + m_bIgnoreReadonly = false; + eFlags |= SwCursorShell::READONLY; + } + + if( eFlags & SwCursorShell::CHKRANGE ) // check all cursor moves for + CheckRange( m_pCurrentCursor ); // overlapping ranges + + if( !bIdleEnd ) + CheckTableBoxContent(); + + // If the current cursor is in a table and point/mark in different boxes, + // then the table mode is active (also if it is already active: m_pTableCursor) + SwPaM* pTstCursor = getShellCursor( true ); + if( pTstCursor->HasMark() && !m_pBlockCursor && + mxDoc->IsIdxInTable( pTstCursor->GetPoint()->nNode ) && + ( m_pTableCursor || + pTstCursor->GetNode().StartOfSectionNode() != + pTstCursor->GetNode( false ).StartOfSectionNode() ) && !mbSelectAll) + { + SwShellCursor* pITmpCursor = getShellCursor( true ); + Point aTmpPt( pITmpCursor->GetPtPos() ); + Point aTmpMk( pITmpCursor->GetMkPos() ); + SwPosition* pPos = pITmpCursor->GetPoint(); + + // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out + lcl_CheckHiddenSection( pPos->nNode ); + lcl_CheckHiddenSection( pITmpCursor->GetMark()->nNode ); + + // Move cursor out of hidden paragraphs + if ( !GetViewOptions()->IsShowHiddenChar() ) + { + lcl_CheckHiddenPara( *pPos ); + lcl_CheckHiddenPara( *pITmpCursor->GetMark() ); + } + + std::pair const tmp(aTmpPt, false); + SwContentFrame *pTableFrame = pPos->nNode.GetNode().GetContentNode()-> + getLayoutFrame( GetLayout(), pPos, &tmp); + + OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" ); + + // --> Make code robust. The table cursor may point + // to a table in a currently inactive header. + SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr; + + if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 ) + { + // First check if point is in repeated headline: + bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame ); + + // Second check if mark is in repeated headline: + if ( !bInRepeatedHeadline ) + { + std::pair const tmp1(aTmpMk, false); + SwContentFrame* pMarkTableFrame = pITmpCursor->GetContentNode( false )-> + getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1); + OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" ); + + if ( pMarkTableFrame ) + { + SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame(); + OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" ); + + // Make code robust: + if ( pMarkTab ) + { + bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame ); + } + } + } + + // No table cursor in repeated headlines: + if ( bInRepeatedHeadline ) + { + pTableFrame = nullptr; + + SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark() + ? fnSectionStart + : fnSectionEnd; + + // then only select inside the Box + if( m_pTableCursor ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark(); + m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos(); + m_pTableCursor->DeleteMark(); + m_pTableCursor->SwSelPaintRects::Hide(); + } + + *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark(); + GoCurrSection( *m_pCurrentCursor, fnPosSect ); + } + } + + // we really want a table selection + if( pTab && pTableFrame ) + { + if( !m_pTableCursor ) + { + m_pTableCursor = new SwShellTableCursor( *this, + *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(), + *pPos, aTmpPt ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SwSelPaintRects::Hide(); + + CheckTableBoxContent(); + if(!m_pTableCursor) + { + SAL_WARN("sw.core", "fdo#74854: " + "this should not happen, but better lose the selection " + "rather than crashing"); + return; + } + } + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bRealHeight = true; + { + DisableCallbackAction a(*GetLayout()); + if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState)) + { + Point aCentrPt( m_aCharRect.Center() ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState); + bool const bResult = + pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint()); + OSL_ENSURE( bResult, "GetCharRect failed." ); + } + } + + m_pVisibleCursor->Hide(); // always hide visible Cursor + // scroll Cursor to visible area + if( eFlags & SwCursorShell::SCROLLWIN && + (HasSelection() || eFlags & SwCursorShell::READONLY || + !IsCursorReadonly()) ) + { + SwFrame* pBoxFrame = pTableFrame; + while( pBoxFrame && !pBoxFrame->IsCellFrame() ) + pBoxFrame = pBoxFrame->GetUpper(); + if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() ) + MakeVisible( pBoxFrame->getFrameArea() ); + else + MakeVisible( m_aCharRect ); + } + + // let Layout create the Cursors in the Boxes + if( m_pTableCursor->IsCursorMovedUpdate() ) + GetLayout()->MakeTableCursors( *m_pTableCursor ); + if( m_bHasFocus && !m_bBasicHideCursor ) + m_pTableCursor->Show(nullptr); + + // set Cursor-Points to the new Positions + m_pTableCursor->GetPtPos().setX(m_aCharRect.Left()); + m_pTableCursor->GetPtPos().setY(m_aCharRect.Top()); + + if( m_bSVCursorVis ) + { + m_aCursorHeight.setX(0); + m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? + -m_aCharRect.Width() : m_aCharRect.Height()); + m_pVisibleCursor->Show(); // show again + } + m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint + if (Imp()->IsAccessible()) + Imp()->InvalidateAccessibleCursorPosition( pTableFrame ); + return; + } + } + + if( m_pTableCursor ) + { + // delete Ring + while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) + delete m_pCurrentCursor->GetNext(); + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + } + + m_pVisibleCursor->Hide(); // always hide visible Cursor + + // are we perhaps in a protected / hidden Section ? + { + SwShellCursor* pShellCursor = getShellCursor( true ); + bool bChgState = true; + const SwSectionNode* pSectNd = pShellCursor->GetNode().FindSectionNode(); + if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() || + ( !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag() && + ( !mxDoc->GetDocShell() || + !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) ) + { + if( !FindValidContentNode( !HasDrawView() || + 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount())) + { + // everything protected/hidden -> special mode + if( m_bAllProtect && !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag() ) + bChgState = false; + else + { + m_eMvState = CursorMoveState::NONE; // state for cursor travelling + m_bAllProtect = true; + if( GetDoc()->GetDocShell() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI(); + CallChgLnk(); // notify UI! + } + return; + } + } + } + if( bChgState ) + { + bool bWasAllProtect = m_bAllProtect; + m_bAllProtect = false; + if( bWasAllProtect && GetDoc()->GetDocShell() && + GetDoc()->GetDocShell()->IsReadOnlyUI() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI( false ); + CallChgLnk(); // notify UI! + } + } + } + + UpdateCursorPos(); + + // The cursor must always point into content; there's some code + // that relies on this. (E.g. in SwEditShell::GetScriptType, which always + // loops _behind_ the last node in the selection, which always works if you + // are in content.) To achieve this, we'll force cursor(s) to point into + // content, if UpdateCursorPos() hasn't already done so. + for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer()) + { + // start will move forwards, end will move backwards + bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() ); + + // move point; forward if it's the start, backwards if it's the end + if( ! rCmp.GetPoint()->nNode.GetNode().IsContentNode() ) + rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward, + GoInContent ); + + // move mark (if exists); forward if it's the start, else backwards + if( rCmp.HasMark() ) + { + if( ! rCmp.GetMark()->nNode.GetNode().IsContentNode() ) + { + rCmp.Exchange(); + rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward, + GoInContent ); + rCmp.Exchange(); + } + } + } + + SwRect aOld( m_aCharRect ); + bool bFirst = true; + SwContentFrame *pFrame; + int nLoopCnt = 100; + SwShellCursor* pShellCursor = getShellCursor( true ); + + do { + bool bAgainst; + do { + bAgainst = false; + std::pair const tmp1(pShellCursor->GetPtPos(), false); + pFrame = pShellCursor->GetContentNode()->getLayoutFrame(GetLayout(), + pShellCursor->GetPoint(), &tmp1); + // if the Frame doesn't exist anymore, the complete Layout has to be + // created, because there used to be a Frame here! + if ( !pFrame ) + { + do + { + CalcLayout(); + std::pair const tmp(pShellCursor->GetPtPos(), false); + pFrame = pShellCursor->GetContentNode()->getLayoutFrame( + GetLayout(), pShellCursor->GetPoint(), &tmp); + } while( !pFrame ); + } + else if ( Imp()->IsIdleAction() ) + // Guarantee everything's properly formatted + pFrame->PrepareCursor(); + + // In protected Fly? but ignore in case of frame selection + if( !IsReadOnlyAvailable() && pFrame->IsProtected() && + ( !Imp()->GetDrawView() || + !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) && + (!mxDoc->GetDocShell() || + !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) ) + { + // look for a valid position + bool bChgState = true; + if( !FindValidContentNode(!HasDrawView() || + 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount())) + { + // everything is protected / hidden -> special Mode + if( m_bAllProtect ) + bChgState = false; + else + { + m_eMvState = CursorMoveState::NONE; // state for cursor travelling + m_bAllProtect = true; + if( GetDoc()->GetDocShell() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI(); + CallChgLnk(); // notify UI! + } + return; + } + } + + if( bChgState ) + { + bool bWasAllProtect = m_bAllProtect; + m_bAllProtect = false; + if( bWasAllProtect && GetDoc()->GetDocShell() && + GetDoc()->GetDocShell()->IsReadOnlyUI() ) + { + GetDoc()->GetDocShell()->SetReadOnlyUI( false ); + CallChgLnk(); // notify UI! + } + m_bAllProtect = false; + bAgainst = true; // look for the right Frame again + } + } + } while( bAgainst ); + + SwCursorMoveState aTmpState( m_eMvState ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + aTmpState.m_bRealHeight = true; + aTmpState.m_bRealWidth = IsOverwriteCursor(); + aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel(); + + // #i27615#,#i30453# + SwSpecialPos aSpecialPos; + aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE; + if (pShellCursor->IsInFrontOfLabel()) + { + aTmpState.m_pSpecialPos = &aSpecialPos; + } + + { + DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action + if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState)) + { + Point& rPt = pShellCursor->GetPtPos(); + rPt = m_aCharRect.Center(); + pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState ); + } + } + UISizeNotify(); // tdf#96256 update view size + + if( !pShellCursor->HasMark() ) + m_aCursorHeight = aTmpState.m_aRealHeight; + else + { + m_aCursorHeight.setX(0); + m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? + -m_aCharRect.Width() : m_aCharRect.Height()); + } + + if( !bFirst && aOld == m_aCharRect ) + break; + + // if the layout says that we are after the 100th iteration still in + // flow then we should always take the current position for granted. + // (see bug: 29658) + if( !--nLoopCnt ) + { + OSL_ENSURE( false, "endless loop? CharRect != OldCharRect "); + break; + } + aOld = m_aCharRect; + bFirst = false; + + // update cursor Points to the new Positions + pShellCursor->GetPtPos().setX(m_aCharRect.Left()); + pShellCursor->GetPtPos().setY(m_aCharRect.Top()); + + if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down + { + DisableCallbackAction a(*GetLayout()); + pFrame->Calc(GetOut()); + m_nUpDownX = pFrame->IsVertical() ? + m_aCharRect.Top() - pFrame->getFrameArea().Top() : + m_aCharRect.Left() - pFrame->getFrameArea().Left(); + } + + // scroll Cursor to visible area + if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN && + (HasSelection() || eFlags & SwCursorShell::READONLY || + !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) ) + { + // in case of scrolling this EndAction doesn't show the SV cursor + // again, thus save and reset the flag here + bool bSav = m_bSVCursorVis; + m_bSVCursorVis = false; + MakeSelVisible(); + m_bSVCursorVis = bSav; + } + + } while( eFlags & SwCursorShell::SCROLLWIN ); + + assert(pFrame); + + if( m_pBlockCursor ) + RefreshBlockCursor(); + + // We should not restrict cursor update to the active view when using LOK + bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive(); + + if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor ) + { + if( m_pTableCursor ) + m_pTableCursor->SwSelPaintRects::Show(); + else + { + m_pCurrentCursor->SwSelPaintRects::Show(); + if( m_pBlockCursor ) + { + SwShellCursor* pNxt = m_pCurrentCursor->GetNext(); + while( pNxt && pNxt != m_pCurrentCursor ) + { + pNxt->SwSelPaintRects::Show(); + pNxt = pNxt->GetNext(); + } + } + } + } + + m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint + + if (Imp()->IsAccessible()) + Imp()->InvalidateAccessibleCursorPosition( pFrame ); + + // switch from blinking cursor to read-only-text-selection cursor + const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings(). + GetCursorBlinkTime(); + + if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) == + ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) ) + { + // non blinking cursor in read only - text selection mode + AllSettings aSettings = GetOut()->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ? + Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() : + STYLE_CURSOR_NOBLINKTIME; + aStyleSettings.SetCursorBlinkTime( nNewBlinkTime ); + aSettings.SetStyleSettings( aStyleSettings ); + GetOut()->SetSettings( aSettings ); + } + + if( m_bSVCursorVis ) + m_pVisibleCursor->Show(); // show again + + if (comphelper::LibreOfficeKit::isActive()) + sendLOKCursorUpdates(); + + getIDocumentMarkAccess()->NotifyCursorUpdate(*this); +} + +void SwCursorShell::sendLOKCursorUpdates() +{ + SwWrtShell* pShell = GetDoc()->GetDocShell()->GetWrtShell(); + if (!pShell) + return; + + SwFrame* pCurrentFrame = GetCurrFrame(); + SelectionType eType = pShell->GetSelectionType(); + + boost::property_tree::ptree aRootTree; + + if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab()) + { + const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr); + + boost::property_tree::ptree aTableColumns; + { + SwTabCols aTabCols; + pShell->GetTabCols(aTabCols); + + const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left(); + + aTableColumns.put("left", aTabCols.GetLeft()); + aTableColumns.put("right", aTabCols.GetRight()); + aTableColumns.put("tableOffset", nColumnOffset); + + boost::property_tree::ptree aEntries; + for (size_t i = 0; i < aTabCols.Count(); ++i) + { + auto const & rEntry = aTabCols.GetEntry(i); + boost::property_tree::ptree aTableColumnEntry; + aTableColumnEntry.put("position", rEntry.nPos); + aTableColumnEntry.put("min", rEntry.nMin); + aTableColumnEntry.put("max", rEntry.nMax); + aTableColumnEntry.put("hidden", rEntry.bHidden); + aEntries.push_back(std::make_pair("", aTableColumnEntry)); + } + aTableColumns.push_back(std::make_pair("entries", aEntries)); + } + + boost::property_tree::ptree aTableRows; + { + SwTabCols aTabRows; + pShell->GetTabRows(aTabRows); + + const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top(); + + aTableRows.put("left", aTabRows.GetLeft()); + aTableRows.put("right", aTabRows.GetRight()); + aTableRows.put("tableOffset", nRowOffset); + + boost::property_tree::ptree aEntries; + for (size_t i = 0; i < aTabRows.Count(); ++i) + { + auto const & rEntry = aTabRows.GetEntry(i); + boost::property_tree::ptree aTableRowEntry; + aTableRowEntry.put("position", rEntry.nPos); + aTableRowEntry.put("min", rEntry.nMin); + aTableRowEntry.put("max", rEntry.nMax); + aTableRowEntry.put("hidden", rEntry.bHidden); + aEntries.push_back(std::make_pair("", aTableRowEntry)); + } + aTableRows.push_back(std::make_pair("entries", aEntries)); + } + + aRootTree.add_child("columns", aTableColumns); + aRootTree.add_child("rows", aTableRows); + } + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aRootTree); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, aStream.str().c_str()); +} + +void SwCursorShell::RefreshBlockCursor() +{ + assert(m_pBlockCursor); + SwShellCursor &rBlock = m_pBlockCursor->getShellCursor(); + Point aPt = rBlock.GetPtPos(); + std::pair const tmp(aPt, false); + SwContentFrame* pFrame = rBlock.GetContentNode()->getLayoutFrame( + GetLayout(), rBlock.GetPoint(), &tmp); + Point aMk; + if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() ) + { + aPt = *m_pBlockCursor->getStartPoint(); + aMk = *m_pBlockCursor->getEndPoint(); + } + else + { + aPt = rBlock.GetPtPos(); + if( pFrame ) + { + if( pFrame->IsVertical() ) + aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX()); + else + aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX()); + } + aMk = rBlock.GetMkPos(); + } + SwRect aRect( aMk, aPt ); + aRect.Justify(); + SwSelectionList aSelList( pFrame ); + + if( GetLayout()->FillSelection( aSelList, aRect ) ) + { + SwCursor* pNxt = static_cast(m_pCurrentCursor->GetNext()); + while( pNxt != m_pCurrentCursor ) + { + delete pNxt; + pNxt = static_cast(m_pCurrentCursor->GetNext()); + } + + std::list::iterator pStart = aSelList.getStart(); + std::list::iterator pPam = aSelList.getEnd(); + OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" ); + m_pCurrentCursor->SetMark(); + --pPam; + // If there is only one text portion inside the rectangle, a simple + // selection is created + if( pPam == pStart ) + { + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); + if( (*pPam)->HasMark() ) + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + else + m_pCurrentCursor->DeleteMark(); + delete *pPam; + m_pCurrentCursor->SetColumnSelection( false ); + } + else + { + // The order of the SwSelectionList has to be preserved but + // the order inside the ring created by CreateCursor() is not like + // expected => First create the selections before the last one + // downto the first selection. + // At least create the cursor for the last selection + --pPam; + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections) + if( (*pPam)->HasMark() ) + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + else + m_pCurrentCursor->DeleteMark(); + delete *pPam; + m_pCurrentCursor->SetColumnSelection( true ); + while( pPam != pStart ) + { + --pPam; + + SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); + pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end()); + m_pCurrentCursor->clear(); + m_pCurrentCursor->DeleteMark(); + + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1 + if( (*pPam)->HasMark() ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + } + else + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SetColumnSelection( true ); + delete *pPam; + } + { + SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor ); + pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() ); + m_pCurrentCursor->clear(); + m_pCurrentCursor->DeleteMark(); + } + pPam = aSelList.getEnd(); + --pPam; + *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection + if( (*pPam)->HasMark() ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark(); + } + else + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SetColumnSelection( true ); + delete *pPam; + } + } +} + +/// create a copy of the cursor and save it in the stack +void SwCursorShell::Push() +{ + // fdo#60513: if we have a table cursor, copy that; else copy current. + // This seems to work because UpdateCursor() will fix this up on Pop(), + // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring. + SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor); + m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(), + pCurrent->GetPtPos(), m_pStackCursor ); + + if (pCurrent->HasMark()) + { + m_pStackCursor->SetMark(); + *m_pStackCursor->GetMark() = *pCurrent->GetMark(); + } +} + +/** delete cursor + + @param eDelete delete from stack, or delete current + and assign the one from stack as the new current cursor. + @return if there was one on the stack, otherwise +*/ +bool SwCursorShell::Pop(PopMode const eDelete) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + // are there any left? + if (nullptr == m_pStackCursor) + return false; + + SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor; + + // the successor becomes the current one + if (m_pStackCursor->GetNext() != m_pStackCursor) + { + pTmp = m_pStackCursor->GetNext(); + } + + if (PopMode::DeleteStack == eDelete) + delete m_pStackCursor; + + m_pStackCursor = pTmp; // assign new one + + if (PopMode::DeleteCurrent == eDelete) + { + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // If the visible SSelection was not changed + const Point& rPoint = pOldStack->GetPtPos(); + if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos()) + { + // move "Selections Rectangles" + m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() ); + pOldStack->clear(); + } + + if( pOldStack->HasMark() ) + { + m_pCurrentCursor->SetMark(); + *m_pCurrentCursor->GetMark() = *pOldStack->GetMark(); + m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos(); + } + else + // no selection so revoke old one and set to old position + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint(); + m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos(); + delete pOldStack; + + if( !m_pCurrentCursor->IsInProtectTable( true ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ) ) + { + UpdateCursor(); // update current cursor + if (m_pTableCursor) + { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table + m_pTableCursor->SetChgd(); + } + } + } + return true; +} + +/** Combine two cursors + + Delete topmost from stack and use its GetMark in the current. +*/ +void SwCursorShell::Combine() +{ + // any others left? + if (nullptr == m_pStackCursor) + return; + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + // rhbz#689053: IsSelOvr must restore the saved stack position, not the + // current one, because current point + stack mark may be invalid PaM + SwCursorSaveState aSaveState(*m_pStackCursor); + // stack cursor & current cursor in same Section? + assert(!m_pStackCursor->HasMark() || + CheckNodesRange(m_pStackCursor->GetMark()->nNode, + m_pCurrentCursor->GetPoint()->nNode, true)); + *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint(); + m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos(); + + SwShellCursor * pTmp = nullptr; + if (m_pStackCursor->GetNext() != m_pStackCursor) + { + pTmp = m_pStackCursor->GetNext(); + } + delete m_pCurrentCursor; + m_pCurrentCursor = m_pStackCursor; + m_pStackCursor->MoveTo(nullptr); // remove from ring + m_pStackCursor = pTmp; + if( !m_pCurrentCursor->IsInProtectTable( true ) && + !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ) ) + { + UpdateCursor(); // update current cursor + } +} + +void SwCursorShell::HideCursors() +{ + if( !m_bHasFocus || m_bBasicHideCursor ) + return; + + // if cursor is visible then hide SV cursor + if( m_pVisibleCursor->IsVisible() ) + { + SET_CURR_SHELL( this ); + m_pVisibleCursor->Hide(); + } + // revoke inversion of SSelection + SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + pCurrentCursor->Hide(); +} + +void SwCursorShell::ShowCursors( bool bCursorVis ) +{ + if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor ) + return; + + SET_CURR_SHELL( this ); + SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + pCurrentCursor->Show(nullptr); + + if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again + m_pVisibleCursor->Show(); +} + +void SwCursorShell::ShowCursor() +{ + if( !m_bBasicHideCursor ) + { + m_bSVCursorVis = true; + m_pCurrentCursor->SetShowTextInputFieldOverlay( true ); + + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + + UpdateCursor(); + } +} + +void SwCursorShell::HideCursor() +{ + if( !m_bBasicHideCursor ) + { + m_bSVCursorVis = false; + // possibly reverse selected areas!! + SET_CURR_SHELL( this ); + m_pCurrentCursor->SetShowTextInputFieldOverlay( false ); + m_pVisibleCursor->Hide(); + + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + } +} + +void SwCursorShell::ShellLoseFocus() +{ + if( !m_bBasicHideCursor ) + HideCursors(); + m_bHasFocus = false; +} + +void SwCursorShell::ShellGetFocus() +{ + m_bHasFocus = true; + if( !m_bBasicHideCursor && VisArea().Width() ) + { + UpdateCursor( static_cast( SwCursorShell::CHKRANGE ) ); + ShowCursors( m_bSVCursorVis ); + } +} + +/** Get current frame in which the cursor is positioned. */ +SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const +{ + SET_CURR_SHELL( const_cast(this) ); + SwContentFrame *pRet = nullptr; + SwContentNode *pNd = m_pCurrentCursor->GetContentNode(); + if ( pNd ) + { + if ( bCalcFrame ) + { + sal_uInt16* pST = const_cast(&mnStartAction); + ++(*pST); + const Size aOldSz( GetDocSize() ); + std::pair const tmp(m_pCurrentCursor->GetPtPos(), true); + pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + --(*pST); + if( aOldSz != GetDocSize() ) + const_cast(this)->SizeChgNotify(); + } + else + { + std::pair const tmp(m_pCurrentCursor->GetPtPos(), false); + pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + } + } + return pRet; +} + +//TODO: provide documentation +/** forward all attribute/format changes at the current node to the Link + + @param pOld ??? + @param pNew ??? +*/ +void SwCursorShell::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + const sal_uInt16 nWhich = pOld ? + pOld->Which() : + pNew ? + pNew->Which() : + sal::static_int_cast(RES_MSG_BEGIN); + + if( m_bCallChgLnk && + ( nWhich < RES_MSG_BEGIN || nWhich >= RES_MSG_END || + nWhich == RES_FMT_CHG || nWhich == RES_UPDATE_ATTR || + nWhich == RES_ATTRSET_CHG )) + // messages are not forwarded + // #i6681#: RES_UPDATE_ATTR is implicitly unset in + // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do + // not need to send the expensive RES_FMT_CHG in Insert. + CallChgLnk(); + + if( m_aGrfArrivedLnk.IsSet() && + ( RES_GRAPHIC_ARRIVED == nWhich || RES_GRAPHIC_SWAPIN == nWhich )) + m_aGrfArrivedLnk.Call( *this ); +} + +/** Does the current cursor create a selection? + + This means checking if GetMark is set and if SPoint and GetMark differ. +*/ +bool SwCursorShell::HasSelection() const +{ + const SwPaM* pCursor = getShellCursor( true ); + return IsTableMode() + || (pCursor->HasMark() && + (*pCursor->GetPoint() != *pCursor->GetMark() + || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End()))); +} + +void SwCursorShell::CallChgLnk() +{ + // Do not make any call in StartAction/EndAction but just set the flag. + // This will be handled in EndAction. + if (ActionPend()) + m_bChgCallFlag = true; // remember change + else if( m_aChgLnk.IsSet() ) + { + if( m_bCallChgLnk ) + m_aChgLnk.Call(nullptr); + m_bChgCallFlag = false; // reset flag + } +} + +/// get selected text of a node at current cursor +OUString SwCursorShell::GetSelText() const +{ + OUString aText; + if (GetLayout()->IsHideRedlines()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + if (FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->nNode.GetIndex())) + { + OUStringBuffer buf; + SwPosition const*const pStart(m_pCurrentCursor->Start()); + SwPosition const*const pEnd(m_pCurrentCursor->End()); + for (sal_uLong i = pStart->nNode.GetIndex(); i <= pEnd->nNode.GetIndex(); ++i) + { + SwNode const& rNode(*pStart->nNode.GetNodes()[i]); + assert(!rNode.IsEndNode()); + if (rNode.IsStartNode()) + { + i = rNode.EndOfSectionIndex(); + } + else if (rNode.IsTextNode()) + { + sal_Int32 const nStart(i == pStart->nNode.GetIndex() + ? pStart->nContent.GetIndex() + : 0); + sal_Int32 const nEnd(i == pEnd->nNode.GetIndex() + ? pEnd->nContent.GetIndex() + : rNode.GetTextNode()->Len()); + buf.append(rNode.GetTextNode()->GetExpandText( + GetLayout(), + nStart, nEnd - nStart, false, false, false, + ExpandMode::HideDeletions)); + + } + } + aText = buf.makeStringAndClear(); + } + } + else if( m_pCurrentCursor->GetPoint()->nNode.GetIndex() == + m_pCurrentCursor->GetMark()->nNode.GetIndex() ) + { + SwTextNode* pTextNd = m_pCurrentCursor->GetNode().GetTextNode(); + if( pTextNd ) + { + const sal_Int32 nStt = m_pCurrentCursor->Start()->nContent.GetIndex(); + aText = pTextNd->GetExpandText(GetLayout(), nStt, + m_pCurrentCursor->End()->nContent.GetIndex() - nStt ); + } + } + return aText; +} + +/** get the nth character of the current SSelection + + @param bEnd Start counting from the end? From start otherwise. + @param nOffset position of the character +*/ +sal_Unicode SwCursorShell::GetChar( bool bEnd, long nOffset ) +{ + if( IsTableMode() ) // not possible in table mode + return 0; + + const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint() + : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start(); + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return 0; + + const sal_Int32 nPos = pPos->nContent.GetIndex(); + const OUString& rStr = pTextNd->GetText(); + sal_Unicode cCh = 0; + + if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength()) + cCh = rStr[nPos + nOffset]; + + return cCh; +} + +/** extend current SSelection by n characters + + @param bEnd Start counting from the end? From start otherwise. + @param nCount Number of characters. +*/ +bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount ) +{ + if( !m_pCurrentCursor->HasMark() || IsTableMode() ) + return false; // no selection + + SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start(); + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + assert(pTextNd); + + sal_Int32 nPos = pPos->nContent.GetIndex(); + if( bEnd ) + { + if ((nPos + nCount) <= pTextNd->GetText().getLength()) + nPos = nPos + nCount; + else + return false; // not possible + } + else if( nPos >= nCount ) + nPos = nPos - nCount; + else + return false; // not possible anymore + + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + pPos->nContent = nPos; + UpdateCursor(); + + return true; +} + +/** Move visible cursor to given position in document. + + @param rPt The position to move the visible cursor to. + @return if SPoint was corrected by the layout. +*/ +bool SwCursorShell::SetVisibleCursor( const Point &rPt ) +{ + SET_CURR_SHELL( this ); + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + aTmpState.m_bRealHeight = true; + + const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ ); + + SetInFrontOfLabel( false ); // #i27615# + + // show only in TextNodes + SwTextNode* pTextNd = aPos.nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return false; + + const SwSectionNode* pSectNd = pTextNd->FindSectionNode(); + if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() || + ( !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag())) ) + return false; + + std::pair const tmp(aPt, true); + SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp); + if ( Imp()->IsIdleAction() ) + pFrame->PrepareCursor(); + SwRect aTmp( m_aCharRect ); + + pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState ); + + // #i10137# + if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() ) + return true; + + m_pVisibleCursor->Hide(); // always hide visible cursor + if( IsScrollMDI( this, m_aCharRect )) + { + MakeVisible( m_aCharRect ); + m_pCurrentCursor->Show(nullptr); + } + + { + if( aTmpState.m_bRealHeight ) + m_aCursorHeight = aTmpState.m_aRealHeight; + else + { + m_aCursorHeight.setX(0); + m_aCursorHeight.setY(m_aCharRect.Height()); + } + + m_pVisibleCursor->SetDragCursor(); + m_pVisibleCursor->Show(); // show again + } + return bRet; +} + +SwVisibleCursor* SwCursorShell::GetVisibleCursor() const +{ + return m_pVisibleCursor; +} + +bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const +{ + Point aPt( rPt ); + SwPaM aPam( *m_pCurrentCursor->GetPoint() ); + GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt ); + // form view + return aPam.HasReadonlySel( GetViewOptions()->IsFormView() ); +} + +/** Get the number of elements in the ring of cursors + + @param bAll If get only spanned ones (= with selections) (Basic). +*/ +sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const +{ + SwPaM* pTmp = GetCursor()->GetNext(); + sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() && + *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0; + while( pTmp != m_pCurrentCursor ) + { + if( bAll || ( pTmp->HasMark() && + *pTmp->GetPoint() != *pTmp->GetMark())) + ++n; + pTmp = pTmp->GetNext(); + } + return n; +} + +bool SwCursorShell::IsStartOfDoc() const +{ + if( m_pCurrentCursor->GetPoint()->nContent.GetIndex() ) + return false; + + // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd) + SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 ); + if( !aIdx.GetNode().IsContentNode() ) + GetDoc()->GetNodes().GoNext( &aIdx ); + return aIdx == m_pCurrentCursor->GetPoint()->nNode; +} + +bool SwCursorShell::IsEndOfDoc() const +{ + SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = SwNodes::GoPrevious( &aIdx ); + + return aIdx == m_pCurrentCursor->GetPoint()->nNode && + pCNd->Len() == m_pCurrentCursor->GetPoint()->nContent.GetIndex(); +} + +/** Invalidate cursors + + Delete all created cursors, set table crsr and last crsr to their TextNode + (or StartNode?). They will then all re-created at the next ::GetCursor() call. + + This is needed for Drag&Drop/ Clipboard-paste in tables. +*/ +bool SwCursorShell::ParkTableCursor() +{ + if( !m_pTableCursor ) + return false; + + m_pTableCursor->ParkCursor(); + + while( m_pCurrentCursor->GetNext() != m_pCurrentCursor ) + delete m_pCurrentCursor->GetNext(); + + // *always* move cursor's Point and Mark + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint(); + + return true; +} + +void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing ) +{ + const SwPosition *pStt = pDelRg->Start(), + *pEnd = pDelRg->GetPoint() == pStt ? pDelRg->GetMark() : pDelRg->GetPoint(); + + SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing; + + // search over the whole ring + bool bGoNext; + do { + + if (!pTmp) + break; + + const SwPosition *pTmpStt = pTmp->Start(), + *pTmpEnd = pTmp->GetPoint() == pTmpStt ? + pTmp->GetMark() : pTmp->GetPoint(); + // If a SPoint or GetMark are in a cursor area then cancel the old area. + // During comparison keep in mind that End() is outside the area. + if( *pStt <= *pTmpStt ) + { + if( *pEnd > *pTmpStt || + ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd )) + pTmpDel = pTmp; + } + else + if( *pStt < *pTmpEnd ) + pTmpDel = pTmp; + + bGoNext = true; + if (pTmpDel) // is the pam in the range -> delete + { + bool bDelete = true; + if( *ppDelRing == pTmpDel ) + { + if( *ppDelRing == m_pCurrentCursor ) + { + bDelete = GoNextCursor(); + if( bDelete ) + { + bGoNext = false; + pTmp = pTmp->GetNext(); + } + } + else + bDelete = false; // never delete the StackCursor + } + + if( bDelete ) + { + if (pTmp == pTmpDel) + pTmp = nullptr; + delete pTmpDel; // invalidate old area + } + else + { + pTmpDel->GetPoint()->nContent.Assign(nullptr, 0); + pTmpDel->GetPoint()->nNode = 0; + pTmpDel->DeleteMark(); + } + pTmpDel = nullptr; + } + if( bGoNext && pTmp ) + pTmp = pTmp->GetNext(); + + } while( !bGoNext || *ppDelRing != pTmp ); +} + +//TODO: provide documentation +/** Remove selections and additional cursors of all shells. + + The remaining cursor of the shell is parked. + + @param rIdx ??? +*/ +void SwCursorShell::ParkCursor( const SwNodeIndex &rIdx ) +{ + SwNode *pNode = &rIdx.GetNode(); + + // create a new PaM + std::unique_ptr pNew( new SwPaM( *GetCursor()->GetPoint() ) ); + if( pNode->GetStartNode() ) + { + pNode = pNode->StartOfSectionNode(); + if( pNode->IsTableNode() ) + { + // the given node is in a table, thus park cursor to table node + // (outside of the table) + pNew->GetPoint()->nNode = *pNode->StartOfSectionNode(); + } + else + // Also on the start node itself. Then we need to request the start + // node always via its end node! (StartOfSelection of StartNode is + // the parent) + pNew->GetPoint()->nNode = *pNode->EndOfSectionNode()->StartOfSectionNode(); + } + else + pNew->GetPoint()->nNode = *pNode->StartOfSectionNode(); + pNew->SetMark(); + pNew->GetPoint()->nNode = *pNode->EndOfSectionNode(); + + // take care of all shells + for(SwViewShell& rTmp : GetRingContainer()) + { + if( dynamic_cast(&rTmp) != nullptr) + { + SwCursorShell* pSh = static_cast(&rTmp); + if (pSh->m_pStackCursor) + pSh->ParkPams(pNew.get(), &pSh->m_pStackCursor); + + pSh->ParkPams( pNew.get(), &pSh->m_pCurrentCursor ); + if( pSh->m_pTableCursor ) + { + // set table cursor always to 0 and the current one always to + // the beginning of the table + SwPaM* pTCursor = pSh->GetTableCrs(); + SwNode* pTableNd = pTCursor->GetPoint()->nNode.GetNode().FindTableNode(); + if ( pTableNd ) + { + pTCursor->GetPoint()->nContent.Assign(nullptr, 0); + pTCursor->GetPoint()->nNode = 0; + pTCursor->DeleteMark(); + pSh->m_pCurrentCursor->GetPoint()->nNode = *pTableNd; + } + } + } + } +} + +/** Copy constructor + + Copy cursor position and add it to the ring. + All views of a document are in the ring of the shell. +*/ +SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin ) + : SwViewShell( rShell, pInitWin ) + , SwModify() + , m_pStackCursor( nullptr ) + , m_pBlockCursor( nullptr ) + , m_pTableCursor( nullptr ) + , m_pBoxIdx( nullptr ) + , m_pBoxPtr( nullptr ) + , m_nUpDownX(0) + , m_nLeftFramePos(0) + , m_nCurrentNode(0) + , m_nCurrentContent(0) + , m_nCurrentNdTyp(SwNodeType::NONE) + , m_nCursorMove( 0 ) + , m_eMvState( CursorMoveState::NONE ) + , m_eEnhancedTableSel(SwTable::SEARCH_NONE) + , m_sMarkedListId() + , m_nMarkedListLevel( 0 ) + , m_oldColFrame(nullptr) +{ + SET_CURR_SHELL( this ); + // only keep the position of the current cursor of the copy shell + m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) ); + m_pCurrentCursor->GetContentNode()->Add( this ); + + m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = + m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = + m_bOverwriteCursor = false; + m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; + m_bSVCursorVis = true; + m_bSetCursorInReadOnly = true; + m_pVisibleCursor = new SwVisibleCursor( this ); + m_bMacroExecAllowed = rShell.IsMacroExecAllowed(); +} + +/// default constructor +SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin, + const SwViewOption *pInitOpt ) + : SwViewShell( rDoc, pInitWin, pInitOpt ) + , SwModify() + , m_pStackCursor( nullptr ) + , m_pBlockCursor( nullptr ) + , m_pTableCursor( nullptr ) + , m_pBoxIdx( nullptr ) + , m_pBoxPtr( nullptr ) + , m_nUpDownX(0) + , m_nLeftFramePos(0) + , m_nCurrentNode(0) + , m_nCurrentContent(0) + , m_nCurrentNdTyp(SwNodeType::NONE) + , m_nCursorMove( 0 ) + , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint + , m_eEnhancedTableSel(SwTable::SEARCH_NONE) + , m_sMarkedListId() + , m_nMarkedListLevel( 0 ) + , m_oldColFrame(nullptr) +{ + SET_CURR_SHELL( this ); + // create initial cursor and set it to first content position + SwNodes& rNds = rDoc.GetNodes(); + + SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() ); + SwContentNode* pCNd = rNds.GoNext( &aNodeIdx ); // go to the first ContentNode + + m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, SwIndex( pCNd, 0 ))); + + // Register shell as dependent at current node. As a result all attribute + // changes can be forwarded via the Link. + pCNd->Add( this ); + + m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = + m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = + m_bOverwriteCursor = false; + m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; + m_bSVCursorVis = true; + m_bSetCursorInReadOnly = true; + + m_pVisibleCursor = new SwVisibleCursor( this ); + m_bMacroExecAllowed = true; +} + +SwCursorShell::~SwCursorShell() +{ + // if it is not the last view then at least the field should be updated + if( !unique() ) + CheckTableBoxContent( m_pCurrentCursor->GetPoint() ); + else + ClearTableBoxContent(); + + delete m_pVisibleCursor; + delete m_pBlockCursor; + delete m_pTableCursor; + + // release cursors + while(m_pCurrentCursor->GetNext() != m_pCurrentCursor) + delete m_pCurrentCursor->GetNext(); + delete m_pCurrentCursor; + + // free stack + if (m_pStackCursor) + { + while (m_pStackCursor->GetNext() != m_pStackCursor) + delete m_pStackCursor->GetNext(); + delete m_pStackCursor; + } + + // #i54025# - do not give a HTML parser that might potentially hang as + // a client at the cursor shell the chance to hang itself on a TextNode + EndListeningAll(); +} + +SwShellCursor* SwCursorShell::getShellCursor( bool bBlock ) +{ + if( m_pTableCursor ) + return m_pTableCursor; + if( m_pBlockCursor && bBlock ) + return &m_pBlockCursor->getShellCursor(); + return m_pCurrentCursor; +} + +/** Should WaitPtr be switched on for the clipboard? + + Wait for TableMode, multiple selections and more than x selected paragraphs. +*/ +bool SwCursorShell::ShouldWait() const +{ + if ( IsTableMode() || GetCursorCnt() > 1 ) + return true; + + if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() ) + return true; + + SwPaM* pPam = GetCursor(); + return pPam->Start()->nNode.GetIndex() + 10 < + pPam->End()->nNode.GetIndex(); +} + +size_t SwCursorShell::UpdateTableSelBoxes() +{ + if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount())) + { + GetLayout()->MakeTableCursors( *m_pTableCursor ); + } + return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0; +} + +/// show the current selected "object" +void SwCursorShell::MakeSelVisible() +{ + OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" ); + if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() ) + { + SwRect aTmp( m_aCharRect ); + long nDiff = m_aCharRect.Height() - VisArea().Height(); + if( nDiff < m_aCursorHeight.getX() ) + aTmp.Top( nDiff + m_aCharRect.Top() ); + else + { + aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() ); + aTmp.Height( m_aCursorHeight.getY() ); + } + if( !aTmp.HasArea() ) + { + aTmp.AddHeight(1 ); + aTmp.AddWidth(1 ); + } + MakeVisible( aTmp ); + } + else + { + if( m_aCharRect.HasArea() ) + MakeVisible( m_aCharRect ); + else + { + SwRect aTmp( m_aCharRect ); + aTmp.AddHeight(1 ); + aTmp.AddWidth(1 ); + MakeVisible( aTmp ); + } + } +} + +/// search a valid content position (not protected/hidden) +bool SwCursorShell::FindValidContentNode( bool bOnlyText ) +{ + if( m_pTableCursor ) + { + assert(!"Did not remove table selection!"); + return false; + } + + // #i45129# - everything is allowed in UI-readonly + if( !m_bAllProtect && GetDoc()->GetDocShell() && + GetDoc()->GetDocShell()->IsReadOnlyUI() ) + return true; + + if( m_pCurrentCursor->HasMark() ) + ClearMark(); + + // first check for frames + SwNodeIndex& rNdIdx = m_pCurrentCursor->GetPoint()->nNode; + sal_uLong nNdIdx = rNdIdx.GetIndex(); // keep backup + SwNodes& rNds = mxDoc->GetNodes(); + SwContentNode* pCNd = rNdIdx.GetNode().GetContentNode(); + const SwContentFrame * pFrame; + + if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) && + !IsReadOnlyAvailable() && pFrame->IsProtected() && + nNdIdx < rNds.GetEndOfExtras().GetIndex() ) + { + // skip protected frame + SwPaM aPam( *m_pCurrentCursor->GetPoint() ); + aPam.SetMark(); + aPam.GetMark()->nNode = rNds.GetEndOfContent(); + aPam.GetPoint()->nNode = *pCNd->EndOfSectionNode(); + + bool bFirst = false; + if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward ))) + { + aPam.GetMark()->nNode = *rNds.GetEndOfPostIts().StartOfSectionNode(); + pCNd = ::GetNode( aPam, bFirst, fnMoveBackward ); + } + + if( !pCNd ) // should *never* happen + { + rNdIdx = nNdIdx; // back to old node + return false; + } + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + else if( bOnlyText && pCNd && pCNd->IsNoTextNode() ) + { + // set to beginning of document + rNdIdx = mxDoc->GetNodes().GetEndOfExtras(); + m_pCurrentCursor->GetPoint()->nContent.Assign( mxDoc->GetNodes().GoNext( + &rNdIdx ), 0 ); + nNdIdx = rNdIdx.GetIndex(); + } + + bool bOk = true; + + // #i9059# cursor may not stand in protected cells + // (unless cursor in protected areas is OK.) + const SwTableNode* pTableNode = rNdIdx.GetNode().FindTableNode(); + if( !IsReadOnlyAvailable() && + pTableNode != nullptr && rNdIdx.GetNode().IsProtect() ) + { + // we're in a table, and we're in a protected area, so we're + // probably in a protected cell. + + // move forward into non-protected area. + SwPaM aPam( rNdIdx.GetNode(), 0 ); + while( aPam.GetNode().IsProtect() && + aPam.Move( fnMoveForward, GoInContent ) ) + ; // nothing to do in the loop; the aPam.Move does the moving! + + // didn't work? then go backwards! + if( aPam.GetNode().IsProtect() ) + { + SwPaM aTmpPaM( rNdIdx.GetNode(), 0 ); + aPam = aTmpPaM; + while( aPam.GetNode().IsProtect() && + aPam.Move( fnMoveBackward, GoInContent ) ) + ; // nothing to do in the loop; the aPam.Move does the moving! + } + + // if we're successful, set the new position + if( ! aPam.GetNode().IsProtect() ) + { + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + } + + // in a protected frame + const SwSectionNode* pSectNd = rNdIdx.GetNode().FindSectionNode(); + if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() || + ( !IsReadOnlyAvailable() && + pSectNd->GetSection().IsProtectFlag() )) ) + { + bOk = false; + bool bGoNextSection = true; + for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt ) + { + bool bContinue; + do { + bContinue = false; + for (;;) + { + if (bGoNextSection) + pCNd = rNds.GoNextSection( &rNdIdx, + true, !IsReadOnlyAvailable() ); + else + pCNd = SwNodes::GoPrevSection( &rNdIdx, + true, !IsReadOnlyAvailable() ); + if ( pCNd == nullptr) break; + // moved inside a table -> check if it is protected + if( pCNd->FindTableNode() ) + { + SwCallLink aTmp( *this ); + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR + if( !m_pCurrentCursor->IsInProtectTable( true ) ) + { + const SwSectionNode* pSNd = pCNd->FindSectionNode(); + if( !pSNd || !pSNd->GetSection().IsHiddenFlag() + || (!IsReadOnlyAvailable() && + pSNd->GetSection().IsProtectFlag() )) + { + bOk = true; + break; // found non-protected cell + } + continue; // continue search + } + } + else + { + bOk = true; + break; // found non-protected cell + } + } + + if( bOk && rNdIdx.GetIndex() < rNds.GetEndOfExtras().GetIndex() ) + { + // also check for Fly - might be protected as well + pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr); + if (nullptr == pFrame || + ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) || + ( bOnlyText && pCNd->IsNoTextNode() ) ) + { + // continue search + bOk = false; + bContinue = true; + } + } + } while( bContinue ); + + if( !bOk ) + { + if( !nLoopCnt ) + bGoNextSection = false; + rNdIdx = nNdIdx; + } + } + } + if( bOk ) + { + pCNd = rNdIdx.GetNode().GetContentNode(); + const sal_Int32 nContent = rNdIdx.GetIndex() < nNdIdx ? pCNd->Len() : 0; + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, nContent ); + } + else + { + pCNd = rNdIdx.GetNode().GetContentNode(); + // if cursor in hidden frame, always move it + if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr)) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(), + &aTmpState ); + } + } + return bOk; +} + +bool SwCursorShell::IsCursorReadonly() const +{ + if ( GetViewOptions()->IsReadonly() || + GetViewOptions()->IsFormView() /* Formula view */ ) + { + SwFrame *pFrame = GetCurrFrame( false ); + const SwFlyFrame* pFly; + const SwSection* pSection; + + if( pFrame && pFrame->IsInFly() && + (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() && + pFly->Lower() && + !pFly->Lower()->IsNoTextFrame() && + !GetDrawView()->GetMarkedObjectList().GetMarkCount() ) + { + return false; + } + // edit in readonly sections + else if ( pFrame && pFrame->IsInSct() && + nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) && + pSection->IsEditInReadonlyFlag() ) + { + return false; + } + else if ( !IsMultiSelection() && CursorInsideInputField() ) + { + return false; + } + + return true; + } + return false; +} + +/// is the cursor allowed to enter ReadOnly sections? +void SwCursorShell::SetReadOnlyAvailable( bool bFlag ) +{ + // *never* switch in GlobalDoc + if( (!GetDoc()->GetDocShell() || + dynamic_cast(GetDoc()->GetDocShell()) == nullptr ) && + bFlag != m_bSetCursorInReadOnly ) + { + // If the flag is switched off then all selections need to be + // invalidated. Otherwise we would trust that nothing protected is selected. + if( !bFlag ) + { + ClearMark(); + } + m_bSetCursorInReadOnly = bFlag; + UpdateCursor(); + } +} + +bool SwCursorShell::HasReadonlySel() const +{ + bool bRet = false; + // If protected area is to be ignored, then selections are never read-only. + if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() || + GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) && + !SwViewOption::IsIgnoreProtectedArea()) + { + if ( m_pTableCursor != nullptr ) + { + bRet = m_pTableCursor->HasReadOnlyBoxSel() + || m_pTableCursor->HasReadonlySel( GetViewOptions()->IsFormView() ); + } + else + { + for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) + { + if( rCursor.HasReadonlySel( GetViewOptions()->IsFormView() ) ) + { + bRet = true; + break; + } + } + } + } + return bRet; +} + +bool SwCursorShell::IsSelFullPara() const +{ + bool bRet = false; + + if( m_pCurrentCursor->GetPoint()->nNode.GetIndex() == + m_pCurrentCursor->GetMark()->nNode.GetIndex() && !m_pCurrentCursor->IsMultiSelection() ) + { + sal_Int32 nStt = m_pCurrentCursor->GetPoint()->nContent.GetIndex(); + sal_Int32 nEnd = m_pCurrentCursor->GetMark()->nContent.GetIndex(); + if( nStt > nEnd ) + { + sal_Int32 nTmp = nStt; + nStt = nEnd; + nEnd = nTmp; + } + const SwContentNode* pCNd = m_pCurrentCursor->GetContentNode(); + bRet = pCNd && !nStt && nEnd == pCNd->Len(); + } + return bRet; +} + +SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const +{ + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() ); + if( pPt ) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ); + } + + return mxDoc->GetTextDirection( aPos, &aPt ); +} + +bool SwCursorShell::IsInVerticalText( const Point* pPt ) const +{ + const SvxFrameDirection nDir = GetTextDirection( pPt ); + return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir + || nDir == SvxFrameDirection::Vertical_LR_BT; +} + +bool SwCursorShell::IsInRightToLeftText() const +{ + const SvxFrameDirection nDir = GetTextDirection(); + // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in + // vertical environment + return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir; +} + +/// If the current cursor position is inside a hidden range, the hidden range +/// is selected. +bool SwCursorShell::SelectHiddenRange() +{ + bool bRet = false; + if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() ) + { + SwPosition& rPt = *m_pCurrentCursor->GetPoint(); + const SwTextNode* pNode = rPt.nNode.GetNode().GetTextNode(); + if ( pNode ) + { + const sal_Int32 nPos = rPt.nContent.GetIndex(); + + // check if nPos is in hidden range + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd ); + if ( COMPLETE_STRING != nHiddenStart ) + { + // make selection: + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetMark()->nContent = nHiddenEnd; + bRet = true; + } + } + } + + return bRet; +} + +sal_uLong SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt, + bool bSearchInNotes, + SwDocPositions eStart, SwDocPositions eEnd, + bool& bCancel, + FindRanges eRng, + bool bReplace ) +{ + if( m_pTableCursor ) + GetCursor(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + sal_uLong nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd, + bCancel, eRng, bReplace, GetLayout()); + if( nRet || bCancel ) + UpdateCursor(); + return nRet; +} + +sal_uLong SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl, + SwDocPositions eStart, SwDocPositions eEnd, + bool& bCancel, + FindRanges eRng, + const SwTextFormatColl* pReplFormat ) +{ + if( m_pTableCursor ) + GetCursor(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + sal_uLong nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng, + pReplFormat ); + if( nRet ) + UpdateCursor(); + return nRet; +} + +sal_uLong SwCursorShell::FindAttrs( const SfxItemSet& rSet, + bool bNoCollections, + SwDocPositions eStart, SwDocPositions eEnd, + bool& bCancel, + FindRanges eRng, + const i18nutil::SearchOptions2* pSearchOpt, + const SfxItemSet* rReplSet ) +{ + if( m_pTableCursor ) + GetCursor(); + delete m_pTableCursor; + m_pTableCursor = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + sal_uLong nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd, + bCancel, eRng, pSearchOpt, rReplSet, GetLayout()); + if( nRet ) + UpdateCursor(); + return nRet; +} + +void SwCursorShell::SetSelection( const SwPaM& rCursor ) +{ + StartAction(); + SwPaM* pCursor = GetCursor(); + *pCursor->GetPoint() = *rCursor.GetPoint(); + if(rCursor.GetNext() != &rCursor) + { + const SwPaM *_pStartCursor = rCursor.GetNext(); + do + { + SwPaM* pCurrentCursor = CreateCursor(); + *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint(); + if(_pStartCursor->HasMark()) + { + pCurrentCursor->SetMark(); + *pCurrentCursor->GetMark() = *_pStartCursor->GetMark(); + } + } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor ); + } + // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current + // cursor; therefore set current cursor's mark only after creating all other cursors + if (rCursor.HasMark()) + { + pCursor->SetMark(); + *pCursor->GetMark() = *rCursor.GetMark(); + } + EndAction(); +} + +static const SwStartNode* lcl_NodeContext( const SwNode& rNode ) +{ + const SwStartNode *pRet = rNode.StartOfSectionNode(); + while( pRet->IsSectionNode() || pRet->IsTableNode() || + pRet->GetStartNodeType() == SwTableBoxStartNode ) + { + pRet = pRet->StartOfSectionNode(); + } + return pRet; +} + +/** + Checks if a position is valid. To be valid the position's node must + be a content node and the content must not be unregistered. + + @param aPos the position to check. +*/ +bool sw_PosOk(const SwPosition & aPos) +{ + return nullptr != aPos.nNode.GetNode().GetContentNode() && + aPos.nContent.GetIdxReg(); +} + +/** + Checks if a PaM is valid. For a PaM to be valid its point must be + valid. Additionally if the PaM has a mark this has to be valid, too. + + @param aPam the PaM to check +*/ +static bool lcl_CursorOk(SwPaM & aPam) +{ + return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark() + || sw_PosOk(*aPam.GetMark())); +} + +void SwCursorShell::ClearUpCursors() +{ + // start of the ring + SwPaM * pStartCursor = GetCursor(); + // start loop with second entry of the ring + SwPaM * pCursor = pStartCursor->GetNext(); + SwPaM * pTmpCursor; + bool bChanged = false; + + // For all entries in the ring except the start entry delete the entry if + // it is invalid. + while (pCursor != pStartCursor) + { + pTmpCursor = pCursor->GetNext(); + if ( ! lcl_CursorOk(*pCursor)) + { + delete pCursor; + bChanged = true; + } + pCursor = pTmpCursor; + } + + if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) ) + { + pStartCursor->DeleteMark(); + bChanged = true; + } + if( !sw_PosOk( *pStartCursor->GetPoint() ) ) + { + SwNodes & aNodes = GetDoc()->GetNodes(); + const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->nNode.GetNode() ); + SwNodeIndex aIdx( pStartCursor->GetPoint()->nNode ); + SwNode * pNode = SwNodes::GoPrevious(&aIdx); + if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) + aNodes.GoNext( &aIdx ); + if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) + { + // If the start entry of the ring is invalid replace it with a + // cursor pointing to the beginning of the first content node in the + // document. + aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode()); + pNode = aNodes.GoNext( &aIdx ); + } + bool bFound = (pNode != nullptr); + + assert(bFound); + + if (bFound) + { + SwPaM aTmpPam(*pNode); + *pStartCursor = aTmpPam; + } + + bChanged = true; + } + + // If at least one of the cursors in the ring have been deleted or replaced, + // remove the table cursor. + if (m_pTableCursor != nullptr && bChanged) + TableCursorToCursor(); +} + +OUString SwCursorShell::GetCursorDescr() const +{ + OUString aResult; + + if (IsMultiSelection()) + aResult += SwResId(STR_MULTISEL); + else + aResult = SwDoc::GetPaMDescr(*GetCursor()); + + return aResult; +} + +void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell")); + + SwViewShell::dumpAsXml(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor")); + for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer()) + rPaM.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes, + uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps, + const SwWrongList& rSmartTagList, sal_Int32 nCurrent ) +{ + // Insert smart tag information + std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps; + + for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i ) + { + const sal_Int32 nSTPos = rSmartTagList.Pos( i ); + const sal_Int32 nSTLen = rSmartTagList.Len( i ); + + if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen ) + { + const SwWrongArea* pArea = rSmartTagList.GetElement( i ); + if ( pArea ) + { + rSmartTagTypes.push_back( pArea->maType ); + } + } + } + + if ( !rSmartTagTypes.empty() ) + { + rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps); + } +} + +static void lcl_FillTextRange( uno::Reference& rRange, + SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen ) +{ + // create SwPosition for nStartIndex + SwIndex aIndex( &rNode, nBegin ); + SwPosition aStartPos( rNode, aIndex ); + + // create SwPosition for nEndIndex + SwPosition aEndPos( aStartPos ); + aEndPos.nContent = nBegin + nLen; + + const uno::Reference xRange = + SwXTextRange::CreateXTextRange(*rNode.GetDoc(), aStartPos, &aEndPos); + + rRange = xRange; +} + +void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes, + uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps, + uno::Reference< text::XTextRange>& rRange ) const +{ + if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() ) + return; + + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + SwTextNode *pNode = aPos.nNode.GetNode().GetTextNode(); + if ( pNode && !pNode->IsInProtectSect() ) + { + const SwWrongList *pSmartTagList = pNode->GetSmartTags(); + if ( pSmartTagList ) + { + sal_Int32 nCurrent = aPos.nContent.GetIndex(); + sal_Int32 nBegin = nCurrent; + sal_Int32 nLen = 1; + + if (pSmartTagList->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin)) + { + const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin ); + const SwWrongList* pSubList = pSmartTagList->SubList( nIndex ); + if ( pSubList ) + { + pSmartTagList = pSubList; + nCurrent = 0; + } + + lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent ); + lcl_FillTextRange( rRange, *pNode, nBegin, nLen ); + } + } + } +} + +// see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect ) +void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect ) +{ + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + Point aPt( rPt ); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + SwSpecialPos aSpecialPos; + eTmpState.m_pSpecialPos = &aSpecialPos; + SwTextNode *pNode; + const SwWrongList *pSmartTagList; + + if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) ) + return; + pNode = aPos.nNode.GetNode().GetTextNode(); + if( !pNode ) + return; + pSmartTagList = pNode->GetSmartTags(); + if( !pSmartTagList ) + return; + if( !pNode->IsInProtectSect() ) + { + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + + if (pSmartTagList->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin)) + { + // get smarttag word + OUString aText( pNode->GetText().copy(nBegin, nLen) ); + + //save the start and end positions of the line and the starting point + Push(); + LeftMargin(); + const sal_Int32 nLineStart = GetCursor()->GetPoint()->nContent.GetIndex(); + RightMargin(); + const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex(); + Pop(PopMode::DeleteCurrent); + + // make sure the selection build later from the data below does not + // include "in word" character to the left and right in order to + // preserve those. Therefore count those "in words" in order to + // modify the selection accordingly. + const sal_Unicode* pChar = aText.getStr(); + sal_Int32 nLeft = 0; + while (*pChar++ == CH_TXTATR_INWORD) + ++nLeft; + pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr; + sal_Int32 nRight = 0; + while (pChar && *pChar-- == CH_TXTATR_INWORD) + ++nRight; + + aPos.nContent = nBegin + nLeft; + pCursor = GetCursor(); + *pCursor->GetPoint() = aPos; + pCursor->SetMark(); + ExtendSelection( true, nLen - nLeft - nRight ); + // do not determine the rectangle in the current line + const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft; + // take one less than the line end - otherwise the next line would + // be calculated + const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd); + Push(); + pCursor->DeleteMark(); + SwIndex& rContent = GetCursor()->GetPoint()->nContent; + rContent = nWordStart; + SwRect aStartRect; + SwCursorMoveState aState; + aState.m_bRealWidth = true; + SwContentNode* pContentNode = pCursor->GetContentNode(); + std::pair const tmp(rPt, false); + SwContentFrame *pContentFrame = pContentNode->getLayoutFrame( + GetLayout(), pCursor->GetPoint(), &tmp); + + pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); + rContent = nWordEnd - 1; + SwRect aEndRect; + pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState ); + rSelectRect = aStartRect.Union( aEndRect ); + Pop(PopMode::DeleteCurrent); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/crstrvl.cxx b/sw/source/core/crsr/crstrvl.cxx new file mode 100644 index 000000000..4dbb5e732 --- /dev/null +++ b/sw/source/core/crsr/crstrvl.cxx @@ -0,0 +1,2592 @@ +/* -*- 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 "callnk.hxx" +#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; + +void SwCursorShell::MoveCursorToNum() +{ + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + if( ActionPend() ) + return; + SET_CURR_SHELL( this ); + // try to set cursor onto this position, at half of the char- + // SRectangle's height + Point aPt( m_pCurrentCursor->GetPtPos() ); + std::pair const tmp(aPt, true); + SwContentFrame * pFrame = m_pCurrentCursor->GetContentNode()->getLayoutFrame( + GetLayout(), m_pCurrentCursor->GetPoint(), &tmp); + pFrame->GetCharRect( m_aCharRect, *m_pCurrentCursor->GetPoint() ); + pFrame->Calc(GetOut()); + if( pFrame->IsVertical() ) + { + aPt.setX(m_aCharRect.Center().getX()); + aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX()); + } + else + { + aPt.setY(m_aCharRect.Center().getY()); + aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX()); + } + pFrame->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt ); + if ( !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos )) + { + UpdateCursor(SwCursorShell::UPDOWN | + SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } +} + +/// go to next/previous point on the same level +void SwCursorShell::GotoNextNum() +{ + if (!SwDoc::GotoNextNum(*m_pCurrentCursor->GetPoint(), GetLayout())) + return; + MoveCursorToNum(); +} + +void SwCursorShell::GotoPrevNum() +{ + if (!SwDoc::GotoPrevNum(*m_pCurrentCursor->GetPoint(), GetLayout())) + return; + MoveCursorToNum(); +} + +/// jump from content to header +bool SwCursorShell::GotoHeaderText() +{ + const SwFrame* pFrame = GetCurrFrame()->FindPageFrame(); + while( pFrame && !pFrame->IsHeaderFrame() ) + pFrame = pFrame->GetLower(); + // found header, search 1. content frame + while( pFrame && !pFrame->IsContentFrame() ) + pFrame = pFrame->GetLower(); + + if( pFrame ) + { + SET_CURR_SHELL( this ); + // get header frame + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursor *pTmpCursor = getShellCursor( true ); + SwCursorSaveState aSaveState( *pTmpCursor ); + pFrame->Calc(GetOut()); + Point aPt( pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos() ); + pFrame->GetModelPositionForViewPoint( pTmpCursor->GetPoint(), aPt ); + if( !pTmpCursor->IsSelOvr() ) + UpdateCursor(); + else + pFrame = nullptr; + } + return nullptr != pFrame; +} + +/// jump from content to footer +bool SwCursorShell::GotoFooterText() +{ + const SwPageFrame* pFrame = GetCurrFrame()->FindPageFrame(); + if( pFrame ) + { + const SwFrame* pLower = pFrame->GetLastLower(); + + while( pLower && !pLower->IsFooterFrame() ) + pLower = pLower->GetLower(); + // found footer, search 1. content frame + while( pLower && !pLower->IsContentFrame() ) + pLower = pLower->GetLower(); + + if( pLower ) + { + SwCursor *pTmpCursor = getShellCursor( true ); + SET_CURR_SHELL( this ); + // get position in footer + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pTmpCursor ); + pLower->Calc(GetOut()); + Point aPt( pLower->getFrameArea().Pos() + pLower->getFramePrintArea().Pos() ); + pLower->GetModelPositionForViewPoint( pTmpCursor->GetPoint(), aPt ); + if( !pTmpCursor->IsSelOvr() ) + UpdateCursor(); + else + pFrame = nullptr; + } + else + pFrame = nullptr; + } + else + pFrame = nullptr; + return nullptr != pFrame; +} + +bool SwCursorShell::SetCursorInHdFt( size_t nDescNo, bool bInHeader ) +{ + bool bRet = false; + SwDoc *pMyDoc = GetDoc(); + const SwPageDesc* pDesc = nullptr; + + SET_CURR_SHELL( this ); + + if( SIZE_MAX == nDescNo ) + { + // take the current one + const SwContentFrame *pCurrFrame = GetCurrFrame(); + const SwPageFrame* pPage = (pCurrFrame == nullptr) ? nullptr : pCurrFrame->FindPageFrame(); + if( pPage && pMyDoc->ContainsPageDesc( + pPage->GetPageDesc(), &nDescNo) ) + pDesc = pPage->GetPageDesc(); + } + else + if (nDescNo < pMyDoc->GetPageDescCnt()) + pDesc = &pMyDoc->GetPageDesc( nDescNo ); + + if( pDesc ) + { + // check if the attribute exists + const SwFormatContent* pCnt = nullptr; + if( bInHeader ) + { + // mirrored pages? ignore for now + const SwFormatHeader& rHd = pDesc->GetMaster().GetHeader(); + if( rHd.GetHeaderFormat() ) + pCnt = &rHd.GetHeaderFormat()->GetContent(); + } + else + { + const SwFormatFooter& rFt = pDesc->GetMaster().GetFooter(); + if( rFt.GetFooterFormat() ) + pCnt = &rFt.GetFooterFormat()->GetContent(); + } + + if( pCnt && pCnt->GetContentIdx() ) + { + SwNodeIndex aIdx( *pCnt->GetContentIdx(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pMyDoc->GetNodes().GoNext( &aIdx ); + + Point aPt( m_pCurrentCursor->GetPtPos() ); + + std::pair const tmp(aPt, false); + if (pCNd && nullptr != pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp)) + { + // then we can set the cursor in here + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + ClearMark(); + + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + rPos.nNode = *pCNd; + rPos.nContent.Assign( pCNd, 0 ); + + bRet = !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } + } + } + return bRet; +} + +/// jump to the next index +bool SwCursorShell::GotoNextTOXBase( const OUString* pName ) +{ + bool bRet = false; + + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + SwContentNode* pFnd = nullptr; + for( SwSectionFormats::size_type n = rFormats.size(); n; ) + { + const SwSection* pSect = rFormats[ --n ]->GetSection(); + if (SectionType::ToxContent == pSect->GetType()) + { + SwSectionNode const*const pSectNd( + pSect->GetFormat()->GetSectionNode()); + if ( pSectNd + && m_pCurrentCursor->GetPoint()->nNode < pSectNd->GetIndex() + && (!pFnd || pFnd->GetIndex() > pSectNd->GetIndex()) + && (!pName || *pName == + static_cast(pSect)->GetTOXName())) + { + SwNodeIndex aIdx(*pSectNd, 1); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if (!pCNd) + pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + if (pCNd && + pCNd->EndOfSectionIndex() <= pSectNd->EndOfSectionIndex()) + { + SwContentFrame const*const pCFrame( + pCNd->getLayoutFrame(GetLayout())); + if (pCFrame && + (IsReadOnlyAvailable() || !pCFrame->IsProtected())) + { + pFnd = pCNd; + } + } + } + } + } + if( pFnd ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->GetPoint()->nNode = *pFnd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pFnd, 0 ); + bRet = !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +/// jump to previous index +bool SwCursorShell::GotoPrevTOXBase( const OUString* pName ) +{ + bool bRet = false; + + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + SwContentNode* pFnd = nullptr; + for( SwSectionFormats::size_type n = rFormats.size(); n; ) + { + const SwSection* pSect = rFormats[ --n ]->GetSection(); + if (SectionType::ToxContent == pSect->GetType()) + { + SwSectionNode const*const pSectNd( + pSect->GetFormat()->GetSectionNode()); + if ( pSectNd + && m_pCurrentCursor->GetPoint()->nNode > pSectNd->EndOfSectionIndex() + && (!pFnd || pFnd->GetIndex() < pSectNd->GetIndex()) + && (!pName || *pName == + static_cast(pSect)->GetTOXName())) + { + SwNodeIndex aIdx(*pSectNd, 1); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if (!pCNd) + pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + if (pCNd && + pCNd->EndOfSectionIndex() <= pSectNd->EndOfSectionIndex()) + { + SwContentFrame const*const pCFrame( + pCNd->getLayoutFrame(GetLayout())); + if (pCFrame && + (IsReadOnlyAvailable() || !pCFrame->IsProtected())) + { + pFnd = pCNd; + } + } + } + } + } + + if( pFnd ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->GetPoint()->nNode = *pFnd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pFnd, 0 ); + bRet = !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +/// jump to index of TOXMark +void SwCursorShell::GotoTOXMarkBase() +{ + bool bRet = false; + + SwTOXMarks aMarks; + sal_uInt16 nCnt = SwDoc::GetCurTOXMark( *m_pCurrentCursor->GetPoint(), aMarks ); + if( nCnt ) + { + // Take the 1. and get the index type. Search in its dependency list + // for the actual index + const SwTOXType* pType = aMarks[0]->GetTOXType(); + SwIterator aIter( *pType ); + + for( SwTOXBase* pTOX = aIter.First(); pTOX; pTOX = aIter.Next() ) + { + auto pTOXBaseSection = dynamic_cast( pTOX); + if( !pTOXBaseSection ) + continue; + auto pSectFormat = pTOXBaseSection->GetFormat(); + if( !pSectFormat ) + continue; + const SwSectionNode* pSectNd = pSectFormat->GetSectionNode(); + if (!pSectNd) + continue; + SwNodeIndex aIdx( *pSectNd, 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + if( !pCNd ) + continue; + if( pCNd->EndOfSectionIndex() >= pSectNd->EndOfSectionIndex() ) + continue; + const SwContentFrame* pCFrame = pCNd->getLayoutFrame( GetLayout() ); + if( pCFrame && ( IsReadOnlyAvailable() || !pCFrame->IsProtected() ) ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + assert(pCFrame->IsTextFrame()); + *m_pCurrentCursor->GetPoint() = + static_cast(pCFrame) + ->MapViewToModelPos(TextFrameIndex(0)); + bRet = !m_pCurrentCursor->IsInProtectTable() && + !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + break; + } + } + } +} + +/// Jump to next/previous table formula +/// Optionally it is possible to also jump to broken formulas +bool SwCursorShell::GotoNxtPrvTableFormula( bool bNext, bool bOnlyErrors ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + if( IsTableMode() ) + return false; + + bool bFnd = false; + SwPosition aOldPos = *m_pCurrentCursor->GetPoint(); + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + + Point aPt; + SwPosition aFndPos( GetDoc()->GetNodes().GetEndOfContent() ); + if( !bNext ) + aFndPos.nNode = 0; + SetGetExpField aFndGEF( aFndPos ), aCurGEF( rPos ); + + { + const SwNode* pSttNd = rPos.nNode.GetNode().FindTableBoxStartNode(); + if( pSttNd ) + { + const SwTableBox* pTBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + if( pTBox ) + aCurGEF = SetGetExpField( *pTBox ); + } + } + + if( rPos.nNode < GetDoc()->GetNodes().GetEndOfExtras() ) + { + // also at collection use only the first frame + std::pair const tmp(aPt, false); + aCurGEF.SetBodyPos( *rPos.nNode.GetNode().GetContentNode()->getLayoutFrame( GetLayout(), + &rPos, &tmp) ); + } + { + sal_uInt32 nMaxItems = GetDoc()->GetAttrPool().GetItemCount2( RES_BOXATR_FORMULA ); + + if( nMaxItems > 0 ) + { + sal_uInt8 nMaxDo = 2; + do { + for (const SfxPoolItem* pItem : GetDoc()->GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + const SwTableBox* pTBox; + auto pFormulaItem = dynamic_cast(pItem); + if( !pFormulaItem ) + continue; + pTBox = pFormulaItem->GetTableBox(); + if( pTBox && + pTBox->GetSttNd() && + pTBox->GetSttNd()->GetNodes().IsDocNodes() && + ( !bOnlyErrors || + !pFormulaItem->HasValidBoxes() ) ) + { + const SwContentFrame* pCFrame; + SwNodeIndex aIdx( *pTBox->GetSttNd() ); + const SwContentNode* pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + std::pair const tmp(aPt, false); + if (pCNd) + { + pCFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pCFrame && (IsReadOnlyAvailable() || !pCFrame->IsProtected() )) + { + SetGetExpField aCmp( *pTBox ); + aCmp.SetBodyPos( *pCFrame ); + + if( bNext ? ( aCurGEF < aCmp && aCmp < aFndGEF ) + : ( aCmp < aCurGEF && aFndGEF < aCmp )) + { + aFndGEF = aCmp; + bFnd = true; + } + } + } + } + } + if( !bFnd ) + { + if( bNext ) + { + rPos.nNode = 0; + rPos.nContent = 0; + aCurGEF = SetGetExpField( rPos ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + } + else + { + aCurGEF = SetGetExpField( SwPosition( GetDoc()->GetNodes().GetEndOfContent() ) ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + } + } while( !bFnd && --nMaxDo ); + } + } + + if( bFnd ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + aFndGEF.GetPosOfContent( rPos ); + m_pCurrentCursor->DeleteMark(); + + bFnd = !m_pCurrentCursor->IsSelOvr(); + if( bFnd ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } + else + { + rPos = aOldPos; + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + } + + return bFnd; +} + +/// jump to next/previous index marker +bool SwCursorShell::GotoNxtPrvTOXMark( bool bNext ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + if( IsTableMode() ) + return false; + + bool bFnd = false; + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + + Point aPt; + SwPosition aFndPos( GetDoc()->GetNodes().GetEndOfContent() ); + if( !bNext ) + aFndPos.nNode = 0; + SetGetExpField aFndGEF( aFndPos ), aCurGEF( rPos ); + + if( rPos.nNode.GetIndex() < GetDoc()->GetNodes().GetEndOfExtras().GetIndex() ) + { + // also at collection use only the first frame + std::pair const tmp(aPt, false); + aCurGEF.SetBodyPos( *rPos.nNode.GetNode(). + GetContentNode()->getLayoutFrame(GetLayout(), &rPos, &tmp)); + } + + { + const SwTextNode* pTextNd; + const SwTextTOXMark* pTextTOX; + sal_uInt32 nMaxItems = GetDoc()->GetAttrPool().GetItemCount2( RES_TXTATR_TOXMARK ); + + if( nMaxItems > 0 ) + { + do { + for (const SfxPoolItem* pItem : GetDoc()->GetAttrPool().GetItemSurrogates(RES_TXTATR_TOXMARK)) + { + auto pToxMarkItem = dynamic_cast(pItem); + if( !pToxMarkItem ) + continue; + pTextTOX = pToxMarkItem->GetTextTOXMark(); + if( !pTextTOX ) + continue; + pTextNd = &pTextTOX->GetTextNode(); + if( !pTextNd->GetNodes().IsDocNodes() ) + continue; + std::pair const tmp(aPt, false); + const SwContentFrame* pCFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if( pCFrame && ( IsReadOnlyAvailable() || !pCFrame->IsProtected() )) + { + SwNodeIndex aNdIndex( *pTextNd ); // UNIX needs this object + SetGetExpField aCmp( aNdIndex, *pTextTOX ); + aCmp.SetBodyPos( *pCFrame ); + + if( bNext ? ( aCurGEF < aCmp && aCmp < aFndGEF ) + : ( aCmp < aCurGEF && aFndGEF < aCmp )) + { + aFndGEF = aCmp; + bFnd = true; + } + } + } + if( !bFnd ) + { + if ( bNext ) + { + rPos.nNode = 0; + rPos.nContent = 0; + aCurGEF = SetGetExpField( rPos ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + } + else + { + aCurGEF = SetGetExpField( SwPosition( GetDoc()->GetNodes().GetEndOfContent() ) ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + } + } while ( !bFnd ); + } + else + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + } + + if( bFnd ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + aFndGEF.GetPosOfContent( rPos ); + + bFnd = !m_pCurrentCursor->IsSelOvr(); + if( bFnd ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } + return bFnd; +} + +/// traveling between marks +const SwTOXMark& SwCursorShell::GotoTOXMark( const SwTOXMark& rStart, + SwTOXSearch eDir ) +{ + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + const SwTOXMark& rNewMark = GetDoc()->GotoTOXMark( rStart, eDir, + IsReadOnlyAvailable() ); + // set position + SwPosition& rPos = *GetCursor()->GetPoint(); + rPos.nNode = rNewMark.GetTextTOXMark()->GetTextNode(); + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + rNewMark.GetTextTOXMark()->GetStart() ); + + if( !m_pCurrentCursor->IsSelOvr() ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + + return rNewMark; +} + +/// jump to next/previous field type +static void lcl_MakeFieldLst( + SetGetExpFields& rLst, + const SwFieldType& rFieldType, + const bool bInReadOnly, + const bool bChkInpFlag = false ) +{ + // always search the 1. frame + Point aPt; + std::vector vFields; + rFieldType.GatherFields(vFields, false); + for(SwFormatField* pFormatField: vFields) + { + SwTextField* pTextField = pFormatField->GetTextField(); + if ( pTextField != nullptr + && ( !bChkInpFlag + || static_cast(pTextField->GetFormatField().GetField())->GetInputFlag() ) ) + { + const SwTextNode& rTextNode = pTextField->GetTextNode(); + std::pair const tmp(aPt, false); + const SwContentFrame* pCFrame = + rTextNode.getLayoutFrame( + rTextNode.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + if ( pCFrame != nullptr + && ( bInReadOnly || !pCFrame->IsProtected() ) ) + { + std::unique_ptr pNew(new SetGetExpField( SwNodeIndex( rTextNode ), pTextField )); + pNew->SetBodyPos( *pCFrame ); + rLst.insert( std::move(pNew) ); + } + } + } +} + +static SetGetExpFields::const_iterator +lcl_FindField(bool & o_rFound, SetGetExpFields const& rSrtLst, + SwRootFrame const *const pLayout, SwTextNode *const pTextNode, + SwTextField const *const pTextField, SwPosition const& rPos, + sal_Int32 const nContentOffset) +{ + std::unique_ptr pSrch; + std::unique_ptr pIndex; + if (-1 == nContentOffset) + { + pSrch.reset(new SetGetExpField(rPos.nNode, pTextField, &rPos.nContent)); + } + else + { + pIndex.reset(new SwIndex(rPos.nNode.GetNode().GetContentNode(), nContentOffset)); + pSrch.reset(new SetGetExpField(rPos.nNode, pTextField, pIndex.get())); + } + + if (rPos.nNode.GetIndex() < pTextNode->GetNodes().GetEndOfExtras().GetIndex()) + { + // also at collection use only the first frame + Point aPt; + std::pair const tmp(aPt, false); + pSrch->SetBodyPos(*pTextNode->getLayoutFrame(pLayout, &rPos, &tmp)); + } + + SetGetExpFields::const_iterator it = rSrtLst.lower_bound(pSrch.get()); + + o_rFound = (it != rSrtLst.end()) && (**it == *pSrch); + return it; +} + +bool SwCursorShell::MoveFieldType( + const SwFieldType* pFieldType, + const bool bNext, + const SwFieldIds nResType, + const bool bAddSetExpressionFieldsToInputFields ) +{ + // sorted list of all fields + SetGetExpFields aSrtLst; + + if ( pFieldType ) + { + if( SwFieldIds::Input != pFieldType->Which() && !pFieldType->HasWriterListeners() ) + { + return false; + } + + // found Modify object, add all fields to array + ::lcl_MakeFieldLst( aSrtLst, *pFieldType, IsReadOnlyAvailable() ); + + if( SwFieldIds::Input == pFieldType->Which() && bAddSetExpressionFieldsToInputFields ) + { + // there are hidden input fields in the set exp. fields + const SwFieldTypes& rFieldTypes = *mxDoc->getIDocumentFieldsAccess().GetFieldTypes(); + const size_t nSize = rFieldTypes.size(); + for( size_t i=0; i < nSize; ++i ) + { + pFieldType = rFieldTypes[ i ].get(); + if ( SwFieldIds::SetExp == pFieldType->Which() ) + { + ::lcl_MakeFieldLst( aSrtLst, *pFieldType, IsReadOnlyAvailable(), true ); + } + } + } + } + else + { + const SwFieldTypes& rFieldTypes = *mxDoc->getIDocumentFieldsAccess().GetFieldTypes(); + const size_t nSize = rFieldTypes.size(); + for( size_t i=0; i < nSize; ++i ) + { + pFieldType = rFieldTypes[ i ].get(); + if( nResType == pFieldType->Which() ) + { + ::lcl_MakeFieldLst( aSrtLst, *pFieldType, IsReadOnlyAvailable() ); + } + } + } + + // found no fields? + if( aSrtLst.empty() ) + return false; + + SetGetExpFields::const_iterator it; + SwCursor* pCursor = getShellCursor( true ); + { + // (1998): Always use field for search so that the right one is found as + // well some are in frames that are anchored to a paragraph that has a + // field + const SwPosition& rPos = *pCursor->GetPoint(); + + SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTNd, "No ContentNode" ); + + SwTextField * pTextField = pTNd->GetFieldTextAttrAt( rPos.nContent.GetIndex(), true ); + const bool bDelField = ( pTextField == nullptr ); + sal_Int32 nContentOffset = -1; + + if( bDelField ) + { + // create dummy for the search + SwFormatField* pFormatField = new SwFormatField( SwDateTimeField( + static_cast(mxDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::DateTime ) ) ) ); + + pTextField = new SwTextField( *pFormatField, rPos.nContent.GetIndex(), + mxDoc->IsClipBoard() ); + pTextField->ChgTextNode( pTNd ); + } + else + { + // the cursor might be anywhere inside the input field, + // but we will be searching for the field start + if (pTextField->Which() == RES_TXTATR_INPUTFIELD + && rPos.nContent.GetIndex() != pTextField->GetStart()) + nContentOffset = pTextField->GetStart(); + } + bool isSrch; + it = lcl_FindField(isSrch, aSrtLst, + GetLayout(), pTNd, pTextField, rPos, nContentOffset); + + if( bDelField ) + { + auto const pFormat(static_cast(&pTextField->GetAttr())); + delete pTextField; + delete pFormat; + } + + if( it != aSrtLst.end() && isSrch ) // found + { + if( bNext ) + { + if( ++it == aSrtLst.end() ) + return false; // already at the end + } + else + { + if( it == aSrtLst.begin() ) + return false; // no more steps backward possible + --it; + } + } + else // not found + { + if( bNext ) + { + if( it == aSrtLst.end() ) + return false; + } + else + { + if( it == aSrtLst.begin() ) + return false; // no more steps backward possible + --it; + } + } + } + const SetGetExpField& rFnd = **it; + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + + rFnd.GetPosOfContent( *pCursor->GetPoint() ); + bool bRet = !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle ); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return bRet; +} + +bool SwCursorShell::GotoFormatField( const SwFormatField& rField ) +{ + bool bRet = false; + SwTextField const*const pTextField(rField.GetTextField()); + if (pTextField + && (!GetLayout()->IsHideRedlines() + || !sw::IsFieldDeletedInModel( + GetDoc()->getIDocumentRedlineAccess(), *pTextField))) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + + SwCursor* pCursor = getShellCursor( true ); + SwCursorSaveState aSaveState( *pCursor ); + + SwTextNode* pTNd = pTextField->GetpTextNode(); + pCursor->GetPoint()->nNode = *pTNd; + pCursor->GetPoint()->nContent.Assign( pTNd, pTextField->GetStart() ); + + bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +SwTextField * SwCursorShell::GetTextFieldAtPos( + const SwPosition* pPos, + const bool bIncludeInputFieldAtStart ) +{ + SwTextField* pTextField = nullptr; + + SwTextNode * const pNode = pPos->nNode.GetNode().GetTextNode(); + if ( pNode != nullptr ) + { + pTextField = pNode->GetFieldTextAttrAt( pPos->nContent.GetIndex(), bIncludeInputFieldAtStart ); + } + + return pTextField; +} + +SwTextField* SwCursorShell::GetTextFieldAtCursor( + const SwPaM* pCursor, + const bool bIncludeInputFieldAtStart ) +{ + SwTextField* pFieldAtCursor = nullptr; + + SwTextField* pTextField = GetTextFieldAtPos( pCursor->Start(), bIncludeInputFieldAtStart ); + if ( pTextField != nullptr + && pCursor->Start()->nNode == pCursor->End()->nNode ) + { + const sal_Int32 nTextFieldLength = + pTextField->End() != nullptr + ? *(pTextField->End()) - pTextField->GetStart() + : 1; + if ( ( pCursor->End()->nContent.GetIndex() - pCursor->Start()->nContent.GetIndex() ) <= nTextFieldLength ) + { + pFieldAtCursor = pTextField; + } + } + + return pFieldAtCursor; +} + +SwField* SwCursorShell::GetFieldAtCursor( + const SwPaM *const pCursor, + const bool bIncludeInputFieldAtStart) +{ + SwTextField *const pField(GetTextFieldAtCursor(pCursor, bIncludeInputFieldAtStart)); + return pField + ? const_cast(pField->GetFormatField().GetField()) + : nullptr; +} + +SwField* SwCursorShell::GetCurField( const bool bIncludeInputFieldAtStart ) const +{ + SwPaM* pCursor = GetCursor(); + if ( pCursor->IsMultiSelection() ) + { + // multi selection not handled. + return nullptr; + } + + SwField* pCurField = GetFieldAtCursor( pCursor, bIncludeInputFieldAtStart ); + if ( pCurField != nullptr + && SwFieldIds::Table == pCurField->GetTyp()->Which() ) + { + // table formula? convert internal name into external + const SwTableNode* pTableNd = IsCursorInTable(); + static_cast(pCurField)->PtrToBoxNm( pTableNd ? &pTableNd->GetTable() : nullptr ); + } + + return pCurField; +} + +bool SwCursorShell::CursorInsideInputField() const +{ + for(SwPaM& rCursor : GetCursor()->GetRingContainer()) + { + if (dynamic_cast(GetTextFieldAtCursor(&rCursor, true))) + return true; + } + return false; +} + +bool SwCursorShell::PosInsideInputField( const SwPosition& rPos ) +{ + return dynamic_cast(GetTextFieldAtPos( &rPos, false )) != nullptr; +} + +bool SwCursorShell::DocPtInsideInputField( const Point& rDocPt ) const +{ + SwPosition aPos( *(GetCursor()->Start()) ); + Point aDocPt( rDocPt ); + if ( GetLayout()->GetModelPositionForViewPoint( &aPos, aDocPt ) ) + { + return PosInsideInputField( aPos ); + } + return false; +} + +sal_Int32 SwCursorShell::StartOfInputFieldAtPos( const SwPosition& rPos ) +{ + const SwTextInputField* pTextInputField = dynamic_cast(GetTextFieldAtPos( &rPos, true )); + assert(pTextInputField != nullptr + && " - no Input Field at given position"); + return pTextInputField->GetStart(); +} + +sal_Int32 SwCursorShell::EndOfInputFieldAtPos( const SwPosition& rPos ) +{ + const SwTextInputField* pTextInputField = dynamic_cast(GetTextFieldAtPos( &rPos, true )); + assert(pTextInputField != nullptr + && " - no Input Field at given position"); + return *(pTextInputField->End()); +} + +void SwCursorShell::GotoOutline( SwOutlineNodes::size_type nIdx ) +{ + SwCursor* pCursor = getShellCursor( true ); + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + + const SwNodes& rNds = GetDoc()->GetNodes(); + SwTextNode* pTextNd = rNds.GetOutLineNds()[ nIdx ]->GetTextNode(); + pCursor->GetPoint()->nNode = *pTextNd; + pCursor->GetPoint()->nContent.Assign( pTextNd, 0 ); + + if( !pCursor->IsSelOvr() ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); +} + +bool SwCursorShell::GotoOutline( const OUString& rName ) +{ + SwCursor* pCursor = getShellCursor( true ); + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + + bool bRet = false; + if (mxDoc->GotoOutline(*pCursor->GetPoint(), rName, GetLayout()) + && !pCursor->IsSelOvr()) + { + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + bRet = true; + } + return bRet; +} + +/// jump to next node with outline num. +bool SwCursorShell::GotoNextOutline() +{ + const SwNodes& rNds = GetDoc()->GetNodes(); + + if ( rNds.GetOutLineNds().empty() ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + SwCursor* pCursor = getShellCursor( true ); + SwNode* pNd = &(pCursor->GetNode()); + SwOutlineNodes::size_type nPos; + bool bUseFirst = !rNds.GetOutLineNds().Seek_Entry( pNd, &nPos ); + SwOutlineNodes::size_type const nStartPos(nPos); + + do + { + if (!bUseFirst) + { + ++nPos; + } + if (rNds.GetOutLineNds().size() <= nPos) + { + nPos = 0; + } + + if (bUseFirst) + { + bUseFirst = false; + } + else + { + if (nPos == nStartPos) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + } + + pNd = rNds.GetOutLineNds()[ nPos ]; + } + while (!sw::IsParaPropsNode(*GetLayout(), *pNd->GetTextNode())); + + if (nPos < nStartPos) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + } + else + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + } + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + pCursor->GetPoint()->nNode = *pNd; + pCursor->GetPoint()->nContent.Assign( pNd->GetTextNode(), 0 ); + + bool bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return bRet; +} + +/// jump to previous node with outline num. +bool SwCursorShell::GotoPrevOutline() +{ + const SwNodes& rNds = GetDoc()->GetNodes(); + + if ( rNds.GetOutLineNds().empty() ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + SwCursor* pCursor = getShellCursor( true ); + SwNode* pNd = &(pCursor->GetNode()); + SwOutlineNodes::size_type nPos; + bool bRet = false; + (void)rNds.GetOutLineNds().Seek_Entry(pNd, &nPos); + SwOutlineNodes::size_type const nStartPos(nPos); + + do + { + if (nPos == 0) + { + nPos = rNds.GetOutLineNds().size() - 1; + } + else + { + --nPos; // before + } + if (nPos == nStartPos) + { + pNd = nullptr; + break; + } + + pNd = rNds.GetOutLineNds()[ nPos ]; + } + while (!sw::IsParaPropsNode(*GetLayout(), *pNd->GetTextNode())); + + if (pNd) + { + if (nStartPos < nPos) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + else + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + } + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + pCursor->GetPoint()->nNode = *pNd; + pCursor->GetPoint()->nContent.Assign( pNd->GetTextNode(), 0 ); + + bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + else + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + } + return bRet; +} + +/// search "outline position" before previous outline node at given level +SwOutlineNodes::size_type SwCursorShell::GetOutlinePos( sal_uInt8 nLevel ) +{ + SwPaM* pCursor = getShellCursor( true ); + const SwNodes& rNds = GetDoc()->GetNodes(); + + SwNode* pNd = &(pCursor->GetNode()); + SwOutlineNodes::size_type nPos; + if( rNds.GetOutLineNds().Seek_Entry( pNd, &nPos )) + nPos++; // is at correct position; take next for while + + while( nPos-- ) // check the one in front of the current + { + pNd = rNds.GetOutLineNds()[ nPos ]; + + if (sw::IsParaPropsNode(*GetLayout(), *pNd->GetTextNode()) + && pNd->GetTextNode()->GetAttrOutlineLevel()-1 <= nLevel) + { + return nPos; + } + } + return SwOutlineNodes::npos; // no more left +} + +bool SwCursorShell::MakeOutlineSel(SwOutlineNodes::size_type nSttPos, SwOutlineNodes::size_type nEndPos, + bool bWithChildren , bool bKillPams) +{ + const SwNodes& rNds = GetDoc()->GetNodes(); + const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds(); + if( rOutlNds.empty() ) + return false; + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + + if( nSttPos > nEndPos ) // parameters switched? + { + OSL_ENSURE( false, "Start > End for array access" ); + std::swap(nSttPos, nEndPos); + } + + SwNode* pSttNd = rOutlNds[ nSttPos ]; + SwNode* pEndNd = rOutlNds[ nEndPos ]; + + if( bWithChildren ) + { + const int nLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel()-1; + for( ++nEndPos; nEndPos < rOutlNds.size(); ++nEndPos ) + { + pEndNd = rOutlNds[ nEndPos ]; + const int nNxtLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel()-1; + if( nNxtLevel <= nLevel ) + break; // EndPos is now on the next one + } + } + // if without children then set onto next one + else if( ++nEndPos < rOutlNds.size() ) + pEndNd = rOutlNds[ nEndPos ]; + + if( nEndPos == rOutlNds.size() ) // no end found + pEndNd = &rNds.GetEndOfContent(); + + if( bKillPams ) + KillPams(); + + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // set end to the end of the previous content node + m_pCurrentCursor->GetPoint()->nNode = *pSttNd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pSttNd->GetContentNode(), 0 ); + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetPoint()->nNode = *pEndNd; + m_pCurrentCursor->Move( fnMoveBackward, GoInNode ); // end of predecessor + + // and everything is already selected + bool bRet = !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return bRet; +} + +/// jump to reference marker +bool SwCursorShell::GotoRefMark( const OUString& rRefMark, sal_uInt16 nSubType, + sal_uInt16 nSeqNo ) +{ + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + sal_Int32 nPos = -1; + SwTextNode* pTextNd = SwGetRefFieldType::FindAnchor( GetDoc(), rRefMark, + nSubType, nSeqNo, &nPos, nullptr, GetLayout()); + if( pTextNd && pTextNd->GetNodes().IsDocNodes() ) + { + m_pCurrentCursor->GetPoint()->nNode = *pTextNd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pTextNd, nPos ); + + if( !m_pCurrentCursor->IsSelOvr() ) + { + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; + } + } + return false; +} + +bool SwCursorShell::IsPageAtPos( const Point &rPt ) const +{ + if( GetLayout() ) + return nullptr != GetLayout()->GetPageAtPos( rPt ); + return false; +} + +bool SwCursorShell::GetContentAtPos( const Point& rPt, + SwContentAtPos& rContentAtPos, + bool bSetCursor, + SwRect* pFieldRect ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if( !IsTableMode() ) + { + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + + SwTextNode* pTextNd; + SwContentFrame *pFrame(nullptr); + SwTextAttr* pTextAttr; + SwCursorMoveState aTmpState; + aTmpState.m_bFieldInfo = true; + aTmpState.m_bExactOnly = !( IsAttrAtPos::Outline & rContentAtPos.eContentAtPos ); + aTmpState.m_bContentCheck = bool(IsAttrAtPos::ContentCheck & rContentAtPos.eContentAtPos); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + + SwSpecialPos aSpecialPos; + aTmpState.m_pSpecialPos = ( IsAttrAtPos::SmartTag & rContentAtPos.eContentAtPos ) ? + &aSpecialPos : nullptr; + + const bool bCursorFoundExact = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ); + pTextNd = aPos.nNode.GetNode().GetTextNode(); + + const SwNodes& rNds = GetDoc()->GetNodes(); + if( pTextNd + && IsAttrAtPos::Outline & rContentAtPos.eContentAtPos + && !rNds.GetOutLineNds().empty() ) + { + const SwTextNode* pONd = pTextNd->FindOutlineNodeOfLevel(MAXLEVEL-1, GetLayout()); + if( pONd ) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::Outline; + rContentAtPos.sStr = sw::GetExpandTextMerged(GetLayout(), *pONd, true, false, ExpandMode::ExpandFootnote); + bRet = true; + } + } + else if ( IsAttrAtPos::ContentCheck & rContentAtPos.eContentAtPos + && bCursorFoundExact ) + { + bRet = true; + } + else if( pTextNd + && IsAttrAtPos::NumLabel & rContentAtPos.eContentAtPos) + { + bRet = aTmpState.m_bInNumPortion; + rContentAtPos.aFnd.pNode = sw::GetParaPropsNode(*GetLayout(), aPos.nNode); + + Size aSizeLogic(aTmpState.m_nInNumPortionOffset, 0); + Size aSizePixel = GetWin()->LogicToPixel(aSizeLogic); + rContentAtPos.nDist = aSizePixel.Width(); + } + else if( bCursorFoundExact && pTextNd ) + { + if( !aTmpState.m_bPosCorr ) + { + if ( IsAttrAtPos::SmartTag & rContentAtPos.eContentAtPos + && !aTmpState.m_bFootnoteNoInfo ) + { + const SwWrongList* pSmartTagList = pTextNd->GetSmartTags(); + sal_Int32 nCurrent = aPos.nContent.GetIndex(); + const sal_Int32 nBegin = nCurrent; + sal_Int32 nLen = 1; + + if (pSmartTagList && pSmartTagList->InWrongWord(nCurrent, nLen) && !pTextNd->IsSymbolAt(nBegin)) + { + const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin ); + const SwWrongList* pSubList = pSmartTagList->SubList( nIndex ); + if ( pSubList ) + { + nCurrent = aTmpState.m_pSpecialPos->nCharOfst; + + if ( pSubList->InWrongWord( nCurrent, nLen ) ) + bRet = true; + } + else + bRet = true; + + if( bRet && bSetCursor ) + { + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + SwCallLink aLk( *this ); // watch Cursor-Moves + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = aPos; + if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | SwCursorSelOverFlags::Toggle) ) + bRet = false; + else + UpdateCursor(); + } + if( bRet ) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::SmartTag; + + std::pair tmp(aPt, true); + if (pFieldRect) + { + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pFrame) + pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState ); + } + } + } + } + + if ( !bRet + && ( IsAttrAtPos::Field | IsAttrAtPos::ClickField ) & rContentAtPos.eContentAtPos + && !aTmpState.m_bFootnoteNoInfo ) + { + pTextAttr = pTextNd->GetFieldTextAttrAt( aPos.nContent.GetIndex() ); + const SwField* pField = pTextAttr != nullptr + ? pTextAttr->GetFormatField().GetField() + : nullptr; + if ( IsAttrAtPos::ClickField & rContentAtPos.eContentAtPos + && pField && !pField->HasClickHdl() ) + { + pField = nullptr; + } + + if ( pField ) + { + if (pFieldRect) + { + std::pair tmp(aPt, true); + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pFrame) + { + //tdf#116397 now that we looking for the bounds of the field drop the SmartTag + //index within field setting so we don't the bounds of the char within the field + SwSpecialPos* pSpecialPos = aTmpState.m_pSpecialPos; + aTmpState.m_pSpecialPos = nullptr; + pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState ); + aTmpState.m_pSpecialPos = pSpecialPos; + } + } + + if( bSetCursor ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = aPos; + if( m_pCurrentCursor->IsSelOvr() ) + { + // allow click fields in protected sections + // only placeholder is not possible + if( IsAttrAtPos::Field & rContentAtPos.eContentAtPos + || SwFieldIds::JumpEdit == pField->Which() ) + pField = nullptr; + } + else + UpdateCursor(); + } + else if( SwFieldIds::Table == pField->Which() && + static_cast(pField)->IsIntrnlName() ) + { + // create from internal (for CORE) the external + // (for UI) formula + const SwTableNode* pTableNd = pTextNd->FindTableNode(); + if( pTableNd ) // is in a table + const_cast(static_cast(pField))->PtrToBoxNm( &pTableNd->GetTable() ); + } + } + + if( pField ) + { + rContentAtPos.aFnd.pField = pField; + rContentAtPos.pFndTextAttr = pTextAttr; + rContentAtPos.eContentAtPos = IsAttrAtPos::Field; + bRet = true; + } + } + + if( !bRet && IsAttrAtPos::FormControl & rContentAtPos.eContentAtPos ) + { + IDocumentMarkAccess* pMarksAccess = GetDoc()->getIDocumentMarkAccess( ); + sw::mark::IFieldmark* pFieldBookmark = pMarksAccess->getFieldmarkFor( aPos ); + if (bCursorFoundExact && pFieldBookmark) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::FormControl; + rContentAtPos.aFnd.pFieldmark = pFieldBookmark; + bRet=true; + } + } + + if( !bRet && IsAttrAtPos::Ftn & rContentAtPos.eContentAtPos ) + { + if( aTmpState.m_bFootnoteNoInfo ) + { + // over the footnote's char + bRet = true; + if( bSetCursor ) + { + *m_pCurrentCursor->GetPoint() = aPos; + if( !GotoFootnoteAnchor() ) + bRet = false; + } + if( bRet ) + rContentAtPos.eContentAtPos = IsAttrAtPos::Ftn; + } + else if ( nullptr != ( pTextAttr = pTextNd->GetTextAttrForCharAt( + aPos.nContent.GetIndex(), RES_TXTATR_FTN )) ) + { + bRet = true; + if( bSetCursor ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->GetPoint()->nNode = *static_cast(pTextAttr)->GetStartNode(); + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( + &m_pCurrentCursor->GetPoint()->nNode, + true, !IsReadOnlyAvailable() ); + + if( pCNd ) + { + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, 0 ); + if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle )) + bRet = false; + else + UpdateCursor(); + } + else + bRet = false; + } + + if( bRet ) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::Ftn; + rContentAtPos.pFndTextAttr = pTextAttr; + rContentAtPos.aFnd.pAttr = &pTextAttr->GetAttr(); + + if (pFieldRect) + { + std::pair tmp(aPt, true); + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pFrame) + pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState ); + } + } + } + } + + if( !bRet + && ( IsAttrAtPos::ToxMark | IsAttrAtPos::RefMark ) & rContentAtPos.eContentAtPos + && !aTmpState.m_bFootnoteNoInfo ) + { + pTextAttr = nullptr; + if( IsAttrAtPos::ToxMark & rContentAtPos.eContentAtPos ) + { + std::vector const marks( + pTextNd->GetTextAttrsAt( + aPos.nContent.GetIndex(), RES_TXTATR_TOXMARK)); + if (!marks.empty()) + { // hmm... can only return 1 here + pTextAttr = *marks.begin(); + } + } + + if( !pTextAttr && + IsAttrAtPos::RefMark & rContentAtPos.eContentAtPos ) + { + std::vector const marks( + pTextNd->GetTextAttrsAt( + aPos.nContent.GetIndex(), RES_TXTATR_REFMARK)); + if (!marks.empty()) + { // hmm... can only return 1 here + pTextAttr = *marks.begin(); + } + } + + if( pTextAttr ) + { + bRet = true; + if( bSetCursor ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = aPos; + if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | SwCursorSelOverFlags::Toggle ) ) + bRet = false; + else + UpdateCursor(); + } + + if( bRet ) + { + const sal_Int32* pEnd = pTextAttr->GetEnd(); + if( pEnd ) + rContentAtPos.sStr = + pTextNd->GetExpandText(GetLayout(), pTextAttr->GetStart(), *pEnd - pTextAttr->GetStart()); + else if( RES_TXTATR_TOXMARK == pTextAttr->Which()) + rContentAtPos.sStr = + pTextAttr->GetTOXMark().GetAlternativeText(); + + rContentAtPos.eContentAtPos = + RES_TXTATR_TOXMARK == pTextAttr->Which() + ? IsAttrAtPos::ToxMark + : IsAttrAtPos::RefMark; + rContentAtPos.pFndTextAttr = pTextAttr; + rContentAtPos.aFnd.pAttr = &pTextAttr->GetAttr(); + + std::pair tmp(aPt, true); + if (pFieldRect) + { + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pFrame) + pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState ); + } + } + } + } + + if ( !bRet + && IsAttrAtPos::InetAttr & rContentAtPos.eContentAtPos + && !aTmpState.m_bFootnoteNoInfo ) + { + pTextAttr = pTextNd->GetTextAttrAt( + aPos.nContent.GetIndex(), RES_TXTATR_INETFMT); + // "detect" only INetAttrs with URLs + if( pTextAttr && !pTextAttr->GetINetFormat().GetValue().isEmpty() ) + { + bRet = true; + if( bSetCursor ) + { + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + SwCallLink aLk( *this ); // watch Cursor-Moves + m_pCurrentCursor->DeleteMark(); + *m_pCurrentCursor->GetPoint() = aPos; + if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle) ) + bRet = false; + else + UpdateCursor(); + } + if( bRet ) + { + const sal_Int32 nSt = pTextAttr->GetStart(); + const sal_Int32 nEnd = *pTextAttr->End(); + + rContentAtPos.sStr = pTextNd->GetExpandText(GetLayout(), nSt, nEnd-nSt); + + rContentAtPos.aFnd.pAttr = &pTextAttr->GetAttr(); + rContentAtPos.eContentAtPos = IsAttrAtPos::InetAttr; + rContentAtPos.pFndTextAttr = pTextAttr; + + if (pFieldRect) + { + std::pair tmp(aPt, true); + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pFrame) + { + //get bounding box of range + SwRect aStart; + SwPosition aStartPos(*pTextNd, nSt); + pFrame->GetCharRect(aStart, aStartPos, &aTmpState); + SwRect aEnd; + SwPosition aEndPos(*pTextNd, nEnd); + pFrame->GetCharRect(aEnd, aEndPos, &aTmpState); + if (aStart.Top() != aEnd.Top() || aStart.Bottom() != aEnd.Bottom()) + { + aStart.Left(pFrame->getFrameArea().Left()); + aEnd.Right(pFrame->getFrameArea().Right()); + } + *pFieldRect = aStart.Union(aEnd); + } + } + } + } + } + + if( !bRet && IsAttrAtPos::Redline & rContentAtPos.eContentAtPos ) + { + const SwRangeRedline* pRedl = GetDoc()->getIDocumentRedlineAccess().GetRedline(aPos, nullptr); + + if( pRedl ) + { + rContentAtPos.aFnd.pRedl = pRedl; + rContentAtPos.eContentAtPos = IsAttrAtPos::Redline; + rContentAtPos.pFndTextAttr = nullptr; + bRet = true; + + if (pFieldRect) + { + std::pair tmp(aPt, true); + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if( pFrame ) + { + // not sure if this should be limited to one + // paragraph, or mark the entire redline; let's + // leave it limited to one for now... + sal_Int32 nStart; + sal_Int32 nEnd; + pRedl->CalcStartEnd(pTextNd->GetIndex(), nStart, nEnd); + if (nStart == COMPLETE_STRING) + { + // consistency: found pRedl, so there must be + // something in pTextNd + assert(nEnd != COMPLETE_STRING); + nStart = 0; + } + if (nEnd == COMPLETE_STRING) + { + nEnd = pTextNd->Len(); + } + //get bounding box of range + SwRect aStart; + pFrame->GetCharRect(aStart, SwPosition(*pTextNd, nStart), &aTmpState); + SwRect aEnd; + pFrame->GetCharRect(aEnd, SwPosition(*pTextNd, nEnd), &aTmpState); + if (aStart.Top() != aEnd.Top() || aStart.Bottom() != aEnd.Bottom()) + { + aStart.Left(pFrame->getFrameArea().Left()); + aEnd.Right(pFrame->getFrameArea().Right()); + } + *pFieldRect = aStart.Union(aEnd); + } + } + } + } + } + + if( !bRet + && ( IsAttrAtPos::TableBoxFml & rContentAtPos.eContentAtPos +#ifdef DBG_UTIL + || IsAttrAtPos::TableBoxValue & rContentAtPos.eContentAtPos +#endif + ) ) + { + const SwTableNode* pTableNd; + const SwTableBox* pBox; + const SwStartNode* pSttNd = pTextNd->FindTableBoxStartNode(); + const SfxPoolItem* pItem; + if( pSttNd && nullptr != ( pTableNd = pTextNd->FindTableNode()) && + nullptr != ( pBox = pTableNd->GetTable().GetTableBox( + pSttNd->GetIndex() )) && +#ifdef DBG_UTIL + ( SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_FORMULA, false, &pItem ) || + SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_VALUE, false, &pItem )) +#else + SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_FORMULA, false, &pItem ) +#endif + ) + { + std::pair tmp(aPt, true); + SwFrame* pF = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if( pF ) + { + // then the CellFrame + pFrame = static_cast(pF); + while( pF && !pF->IsCellFrame() ) + pF = pF->GetUpper(); + } + + if( aTmpState.m_bPosCorr ) + { + if( pF && !pF->getFrameArea().IsInside( aPt )) + pF = nullptr; + } + else if( !pF ) + pF = pFrame; + + if( pF ) // only then it is valid + { + // create from internal (for CORE) the external + // (for UI) formula + rContentAtPos.eContentAtPos = IsAttrAtPos::TableBoxFml; +#ifdef DBG_UTIL + if( RES_BOXATR_VALUE == pItem->Which() ) + rContentAtPos.eContentAtPos = IsAttrAtPos::TableBoxValue; + else +#endif + const_cast(static_cast(pItem))->PtrToBoxNm( &pTableNd->GetTable() ); + + bRet = true; + if( bSetCursor ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + *m_pCurrentCursor->GetPoint() = aPos; + if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle) ) + bRet = false; + else + UpdateCursor(); + } + + if( bRet ) + { + if( pFieldRect ) + { + *pFieldRect = pF->getFramePrintArea(); + *pFieldRect += pF->getFrameArea().Pos(); + } + rContentAtPos.pFndTextAttr = nullptr; + rContentAtPos.aFnd.pAttr = pItem; + } + } + } + } + +#ifdef DBG_UTIL + if( !bRet && IsAttrAtPos::CurrAttrs & rContentAtPos.eContentAtPos ) + { + const sal_Int32 n = aPos.nContent.GetIndex(); + SfxItemSet aSet( GetDoc()->GetAttrPool(), svl::Items{} ); + if( pTextNd->GetpSwpHints() ) + { + for( size_t i = 0; i < pTextNd->GetSwpHints().Count(); ++i ) + { + const SwTextAttr* pHt = pTextNd->GetSwpHints().Get(i); + const sal_Int32 nAttrStart = pHt->GetStart(); + if( nAttrStart > n ) // over the section + break; + + if( nullptr != pHt->End() && ( + ( nAttrStart < n && + ( pHt->DontExpand() ? n < *pHt->End() + : n <= *pHt->End() )) || + ( n == nAttrStart && + ( nAttrStart == *pHt->End() || !n ))) ) + { + aSet.Put( pHt->GetAttr() ); + } + } + if( pTextNd->HasSwAttrSet() && + pTextNd->GetpSwAttrSet()->Count() ) + { + SfxItemSet aFormatSet( pTextNd->GetSwAttrSet() ); + // remove all from format set that are also in TextSet + aFormatSet.Differentiate( aSet ); + // now merge all together + aSet.Put( aFormatSet ); + } + } + else + pTextNd->SwContentNode::GetAttr( aSet ); + + rContentAtPos.sStr = "Pos: ("; + rContentAtPos.sStr += OUString::number( aPos.nNode.GetIndex()); + rContentAtPos.sStr += ":"; + rContentAtPos.sStr += OUString::number( aPos.nContent.GetIndex()); + rContentAtPos.sStr += ")"; + rContentAtPos.sStr += "\nParagraph Style: "; + rContentAtPos.sStr += pTextNd->GetFormatColl()->GetName(); + if( pTextNd->GetCondFormatColl() ) + { + rContentAtPos.sStr += "\nConditional Style: " + pTextNd->GetCondFormatColl()->GetName(); + } + + if( aSet.Count() ) + { + OUStringBuffer sAttrs; + SfxItemIter aIter( aSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + const IntlWrapper aInt(SvtSysLocale().GetUILanguageTag()); + do + { + if( !IsInvalidItem( pItem )) + { + OUString aStr; + GetDoc()->GetAttrPool().GetPresentation(*pItem, + MapUnit::MapCM, aStr, aInt); + if (!sAttrs.isEmpty()) + sAttrs.append(", "); + sAttrs.append(aStr); + } + pItem = aIter.NextItem(); + } while (pItem); + if (!sAttrs.isEmpty()) + { + if( !rContentAtPos.sStr.isEmpty() ) + rContentAtPos.sStr += "\n"; + rContentAtPos.sStr += "Attr: " + sAttrs.toString(); + } + } + bRet = true; + rContentAtPos.eContentAtPos = IsAttrAtPos::CurrAttrs; + } +#endif + } + } + + if( !bRet ) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::NONE; + rContentAtPos.aFnd.pField = nullptr; + } + return bRet; +} + +// #i90516# +const SwPostItField* SwCursorShell::GetPostItFieldAtCursor() const +{ + const SwPostItField* pPostItField = nullptr; + + if ( !IsTableMode() ) + { + const SwPosition* pCursorPos = GetCursor_()->GetPoint(); + const SwTextNode* pTextNd = pCursorPos->nNode.GetNode().GetTextNode(); + if ( pTextNd ) + { + SwTextAttr* pTextAttr = pTextNd->GetFieldTextAttrAt( pCursorPos->nContent.GetIndex() ); + const SwField* pField = pTextAttr != nullptr ? pTextAttr->GetFormatField().GetField() : nullptr; + if ( pField && pField->Which()== SwFieldIds::Postit ) + { + pPostItField = static_cast(pField); + } + } + } + + return pPostItField; +} + +/// is the node in a protected section? +bool SwContentAtPos::IsInProtectSect() const +{ + const SwTextNode* pNd = nullptr; + if( pFndTextAttr ) + { + switch( eContentAtPos ) + { + case IsAttrAtPos::Field: + case IsAttrAtPos::ClickField: + pNd = static_txtattr_cast(pFndTextAttr)->GetpTextNode(); + break; + + case IsAttrAtPos::Ftn: + pNd = &static_cast(pFndTextAttr)->GetTextNode(); + break; + + case IsAttrAtPos::InetAttr: + pNd = static_txtattr_cast(pFndTextAttr)->GetpTextNode(); + break; + + default: + break; + } + } + + if( !pNd ) + return false; + if( pNd->IsInProtectSect() ) + return true; + + const SwContentFrame* pFrame = pNd->getLayoutFrame(pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr); + return pFrame && pFrame->IsProtected() ; +} + +bool SwContentAtPos::IsInRTLText()const +{ + bool bRet = false; + const SwTextNode* pNd = nullptr; + if (pFndTextAttr && (eContentAtPos == IsAttrAtPos::Ftn)) + { + const SwTextFootnote* pTextFootnote = static_cast(pFndTextAttr); + if(pTextFootnote->GetStartNode()) + { + SwStartNode* pSttNd = pTextFootnote->GetStartNode()->GetNode().GetStartNode(); + SwPaM aTemp( *pSttNd ); + aTemp.Move(fnMoveForward, GoInNode); + SwContentNode* pContentNode = aTemp.GetContentNode(); + if(pContentNode && pContentNode->IsTextNode()) + pNd = pContentNode->GetTextNode(); + } + } + if(pNd) + { + SwIterator aIter(*pNd); + SwTextFrame* pTmpFrame = aIter.First(); + while( pTmpFrame ) + { + if ( !pTmpFrame->IsFollow()) + { + bRet = pTmpFrame->IsRightToLeft(); + break; + } + pTmpFrame = aIter.Next(); + } + } + return bRet; +} + +bool SwCursorShell::SelectText( const sal_Int32 nStart, + const sal_Int32 nEnd ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + SwCallLink aLk( *this ); + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + m_pCurrentCursor->DeleteMark(); + rPos.nContent = nStart; + m_pCurrentCursor->SetMark(); + rPos.nContent = nEnd; + + if( !m_pCurrentCursor->IsSelOvr() ) + { + UpdateCursor(); + bRet = true; + } + + return bRet; +} + +bool SwCursorShell::SelectTextAttr( sal_uInt16 nWhich, + bool bExpand, + const SwTextAttr* pTextAttr ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if( !IsTableMode() ) + { + if( !pTextAttr ) + { + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + pTextAttr = pTextNd + ? pTextNd->GetTextAttrAt(rPos.nContent.GetIndex(), + nWhich, + bExpand ? SwTextNode::EXPAND : SwTextNode::DEFAULT) + : nullptr; + } + + if( pTextAttr ) + { + const sal_Int32* pEnd = pTextAttr->End(); + bRet = SelectText( pTextAttr->GetStart(), ( pEnd ? *pEnd : pTextAttr->GetStart() + 1 ) ); + } + } + return bRet; +} + +bool SwCursorShell::GotoINetAttr( const SwTextINetFormat& rAttr ) +{ + bool bRet = false; + if( rAttr.GetpTextNode() ) + { + SwCursor* pCursor = getShellCursor( true ); + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + + pCursor->GetPoint()->nNode = *rAttr.GetpTextNode(); + pCursor->GetPoint()->nContent.Assign( const_cast(rAttr.GetpTextNode()), + rAttr.GetStart() ); + bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +const SwFormatINetFormat* SwCursorShell::FindINetAttr( const OUString& rName ) const +{ + return mxDoc->FindINetAttr( rName ); +} + +bool SwCursorShell::GetShadowCursorPos( const Point& rPt, SwFillMode eFillMode, + SwRect& rRect, sal_Int16& rOrient ) +{ + + SET_CURR_SHELL( this ); + bool bRet = false; + + if (!IsTableMode() && !HasSelection() + && GetDoc()->GetIDocumentUndoRedo().DoesUndo()) + { + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + + SwFillCursorPos aFPos( eFillMode ); + SwCursorMoveState aTmpState( &aFPos ); + + if( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) && + !aPos.nNode.GetNode().IsProtect()) + { + // start position in protected section? + rRect = aFPos.aCursor; + rOrient = aFPos.eOrient; + bRet = true; + } + } + return bRet; +} + +bool SwCursorShell::SetShadowCursorPos( const Point& rPt, SwFillMode eFillMode ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if (!IsTableMode() && !HasSelection() + && GetDoc()->GetIDocumentUndoRedo().DoesUndo()) + { + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + + SwFillCursorPos aFPos( eFillMode ); + SwCursorMoveState aTmpState( &aFPos ); + + if( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + StartAction(); + + SwContentNode* pCNd = aPos.nNode.GetNode().GetContentNode(); + SwUndoId nUndoId = SwUndoId::INS_FROM_SHADOWCRSR; + // If only the paragraph attributes "Adjust" or "LRSpace" are set, + // then the following should not delete those again. + if( 0 == aFPos.nParaCnt + aFPos.nColumnCnt && + ( SwFillMode::Indent == aFPos.eMode || + ( text::HoriOrientation::NONE != aFPos.eOrient && + 0 == aFPos.nTabCnt + aFPos.nSpaceCnt )) && + pCNd && pCNd->Len() ) + nUndoId = SwUndoId::EMPTY; + + GetDoc()->GetIDocumentUndoRedo().StartUndo( nUndoId, nullptr ); + + SwTextFormatColl* pNextFormat = nullptr; + SwTextNode* pTNd = pCNd ? pCNd->GetTextNode() : nullptr; + if( pTNd ) + pNextFormat = &pTNd->GetTextColl()->GetNextTextFormatColl(); + + const SwSectionNode* pSectNd = pCNd ? pCNd->FindSectionNode() : nullptr; + if( pSectNd && aFPos.nParaCnt ) + { + SwNodeIndex aEnd( aPos.nNode, 1 ); + while( aEnd.GetNode().IsEndNode() && + &aEnd.GetNode() != + pSectNd->EndOfSectionNode() ) + ++aEnd; + + if( aEnd.GetNode().IsEndNode() && + pCNd->Len() == aPos.nContent.GetIndex() ) + aPos.nNode = *pSectNd->EndOfSectionNode(); + } + + for( sal_uInt16 n = 0; n < aFPos.nParaCnt + aFPos.nColumnCnt; ++n ) + { + GetDoc()->getIDocumentContentOperations().AppendTextNode( aPos ); + if( !n && pNextFormat ) + { + *m_pCurrentCursor->GetPoint() = aPos; + GetDoc()->SetTextFormatColl( *m_pCurrentCursor, pNextFormat, false ); + } + if( n < aFPos.nColumnCnt ) + { + *m_pCurrentCursor->GetPoint() = aPos; + GetDoc()->getIDocumentContentOperations().InsertPoolItem( *m_pCurrentCursor, + SvxFormatBreakItem( SvxBreak::ColumnBefore, RES_BREAK ) ); + } + } + + *m_pCurrentCursor->GetPoint() = aPos; + switch( aFPos.eMode ) + { + case SwFillMode::Indent: + if( nullptr != (pCNd = aPos.nNode.GetNode().GetContentNode() )) + { + SfxItemSet aSet( + GetDoc()->GetAttrPool(), + svl::Items< + RES_PARATR_ADJUST, RES_PARATR_ADJUST, + RES_LR_SPACE, RES_LR_SPACE>{}); + SvxLRSpaceItem aLR( static_cast( + pCNd->GetAttr( RES_LR_SPACE ) ) ); + aLR.SetTextLeft( aFPos.nTabCnt ); + aLR.SetTextFirstLineOffset( 0 ); + aSet.Put( aLR ); + + const SvxAdjustItem& rAdj = static_cast(pCNd-> + GetAttr( RES_PARATR_ADJUST )); + if( SvxAdjust::Left != rAdj.GetAdjust() ) + aSet.Put( SvxAdjustItem( SvxAdjust::Left, RES_PARATR_ADJUST ) ); + + GetDoc()->getIDocumentContentOperations().InsertItemSet( *m_pCurrentCursor, aSet ); + } + else { + OSL_ENSURE( false, "No ContentNode" ); + } + break; + + case SwFillMode::Tab: + case SwFillMode::TabSpace: + case SwFillMode::Space: + { + OUStringBuffer sInsert; + if (aFPos.eMode == SwFillMode::Space) + { + comphelper::string::padToLength(sInsert, sInsert.getLength() + aFPos.nSpaceOnlyCnt, ' '); + } + else + { + if (aFPos.nTabCnt) + comphelper::string::padToLength(sInsert, aFPos.nTabCnt, '\t'); + if (aFPos.nSpaceCnt) + comphelper::string::padToLength(sInsert, sInsert.getLength() + aFPos.nSpaceCnt, ' '); + } + if (!sInsert.isEmpty()) + GetDoc()->getIDocumentContentOperations().InsertString( *m_pCurrentCursor, sInsert.makeStringAndClear()); + } + [[fallthrough]]; // still need to set orientation + case SwFillMode::Margin: + if( text::HoriOrientation::NONE != aFPos.eOrient ) + { + SvxAdjustItem aAdj( SvxAdjust::Left, RES_PARATR_ADJUST ); + switch( aFPos.eOrient ) + { + case text::HoriOrientation::CENTER: + aAdj.SetAdjust( SvxAdjust::Center ); + break; + case text::HoriOrientation::RIGHT: + aAdj.SetAdjust( SvxAdjust::Right ); + break; + default: + break; + } + GetDoc()->getIDocumentContentOperations().InsertPoolItem( *m_pCurrentCursor, aAdj ); + } + break; + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo( nUndoId, nullptr ); + EndAction(); + + bRet = true; + } + } + return bRet; +} + +const SwRangeRedline* SwCursorShell::SelNextRedline() +{ + const SwRangeRedline* pFnd = nullptr; + if( !IsTableMode() ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // ensure point is at the end so alternating SelNext/SelPrev works + NormalizePam(false); + pFnd = GetDoc()->getIDocumentRedlineAccess().SelNextRedline( *m_pCurrentCursor ); + if( pFnd && !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() ) + UpdateCursor( SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + else + pFnd = nullptr; + } + return pFnd; +} + +const SwRangeRedline* SwCursorShell::SelPrevRedline() +{ + const SwRangeRedline* pFnd = nullptr; + if( !IsTableMode() ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // ensure point is at the start so alternating SelNext/SelPrev works + NormalizePam(true); + pFnd = GetDoc()->getIDocumentRedlineAccess().SelPrevRedline( *m_pCurrentCursor ); + if( pFnd && !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() ) + UpdateCursor( SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + else + pFnd = nullptr; + } + return pFnd; +} + +const SwRangeRedline* SwCursorShell::GotoRedline_( SwRedlineTable::size_type nArrPos, bool bSelect ) +{ + const SwRangeRedline* pFnd = nullptr; + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + pFnd = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable()[ nArrPos ]; + if( pFnd ) + { + *m_pCurrentCursor->GetPoint() = *pFnd->Start(); + + SwNodeIndex* pIdx = &m_pCurrentCursor->GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( pIdx, + true, IsReadOnlyAvailable() ); + if( pCNd ) + { + if( *pIdx <= pFnd->End()->nNode ) + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, 0 ); + else + pFnd = nullptr; + } + } + + if( pFnd && bSelect ) + { + m_pCurrentCursor->SetMark(); + if( RedlineType::FmtColl == pFnd->GetType() ) + { + SwContentNode* pCNd = pIdx->GetNode().GetContentNode(); + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + m_pCurrentCursor->GetMark()->nContent.Assign( pCNd, 0 ); + } + else + *m_pCurrentCursor->GetPoint() = *pFnd->End(); + + pIdx = &m_pCurrentCursor->GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + SwContentNode* pCNd = SwNodes::GoPrevSection( pIdx, + true, IsReadOnlyAvailable() ); + if( pCNd ) + { + if( *pIdx >= m_pCurrentCursor->GetMark()->nNode ) + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + } + + if( !pFnd ) + { + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->RestoreSavePos(); + } + else if( bSelect && *m_pCurrentCursor->GetMark() == *m_pCurrentCursor->GetPoint() ) + m_pCurrentCursor->DeleteMark(); + + if( pFnd && !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE + | SwCursorShell::READONLY ); + else + { + pFnd = nullptr; + if( bSelect ) + m_pCurrentCursor->DeleteMark(); + } + } + return pFnd; +} + +const SwRangeRedline* SwCursorShell::GotoRedline( SwRedlineTable::size_type nArrPos, bool bSelect ) +{ + const SwRangeRedline* pFnd = nullptr; + if( !IsTableMode() ) + { + SET_CURR_SHELL( this ); + + const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + const SwRangeRedline* pTmp = rTable[ nArrPos ]; + sal_uInt16 nSeqNo = pTmp->GetSeqNo(); + if( nSeqNo && bSelect ) + { + bool bCheck = false; + int nLoopCnt = 2; + SwRedlineTable::size_type nArrSavPos = nArrPos; + + do { + pTmp = GotoRedline_( nArrPos, true ); + + if( !pFnd ) + pFnd = pTmp; + + if( pTmp && bCheck ) + { + // Check for overlaps. These can happen when FormatColl- + // Redlines were stretched over a whole paragraph + SwPaM* pCur = m_pCurrentCursor; + SwPaM* pNextPam = pCur->GetNext(); + SwPosition* pCStt = pCur->Start(), *pCEnd = pCur->End(); + while( pCur != pNextPam ) + { + const SwPosition *pNStt = pNextPam->Start(), + *pNEnd = pNextPam->End(); + + bool bDel = true; + switch( ::ComparePosition( *pCStt, *pCEnd, + *pNStt, *pNEnd )) + { + case SwComparePosition::Inside: // Pos1 is completely in Pos2 + if( !pCur->HasMark() ) + { + pCur->SetMark(); + *pCur->GetMark() = *pNStt; + } + else + *pCStt = *pNStt; + *pCEnd = *pNEnd; + break; + + case SwComparePosition::Outside: // Pos2 is completely in Pos1 + case SwComparePosition::Equal: // Pos1 has same size as Pos2 + break; + + case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 at beginning + if( !pCur->HasMark() ) + pCur->SetMark(); + *pCEnd = *pNEnd; + break; + case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at end + if( !pCur->HasMark() ) + { + pCur->SetMark(); + *pCur->GetMark() = *pNStt; + } + else + *pCStt = *pNStt; + break; + + default: + bDel = false; + } + + if( bDel ) + { + // not needed anymore + SwPaM* pPrevPam = pNextPam->GetPrev(); + delete pNextPam; + pNextPam = pPrevPam; + } + pNextPam = pNextPam->GetNext(); + } + } + + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? rTable.FindNextOfSeqNo( nArrPos ) + : rTable.FindPrevOfSeqNo( nArrPos ); + if( SwRedlineTable::npos != nFndPos || + ( 0 != ( --nLoopCnt ) && SwRedlineTable::npos != ( + nFndPos = rTable.FindPrevOfSeqNo( nArrSavPos ))) ) + { + if( pTmp ) + { + // create new cursor + CreateCursor(); + bCheck = true; + } + nArrPos = nFndPos; + } + else + nLoopCnt = 0; + + } while( nLoopCnt ); + } + else + pFnd = GotoRedline_( nArrPos, bSelect ); + } + return pFnd; +} + +bool SwCursorShell::SelectNxtPrvHyperlink( bool bNext ) +{ + SwNodes& rNds = GetDoc()->GetNodes(); + const SwNode* pBodyEndNd = &rNds.GetEndOfContent(); + const SwNode* pBodySttNd = pBodyEndNd->StartOfSectionNode(); + sal_uLong nBodySttNdIdx = pBodySttNd->GetIndex(); + Point aPt; + + SetGetExpField aCmpPos( SwPosition( bNext ? *pBodyEndNd : *pBodySttNd ) ); + SetGetExpField aCurPos( bNext ? *m_pCurrentCursor->End() : *m_pCurrentCursor->Start() ); + if( aCurPos.GetNode() < nBodySttNdIdx ) + { + const SwContentNode* pCNd = aCurPos.GetNodeFromContent()->GetContentNode(); + SwContentFrame* pFrame; + std::pair tmp(aPt, true); + if (pCNd) + { + pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if( pFrame ) + aCurPos.SetBodyPos( *pFrame ); + } + } + + // check first all the hyperlink fields + { + const SwTextNode* pTextNd; + const SwCharFormats* pFormats = GetDoc()->GetCharFormats(); + for( SwCharFormats::size_type n = pFormats->size(); 1 < n; ) + { + SwIterator aIter(*(*pFormats)[--n]); + + for( SwTextINetFormat* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + pTextNd = pFnd->GetpTextNode(); + if( pTextNd && pTextNd->GetNodes().IsDocNodes() ) + { + SwTextINetFormat& rAttr = *pFnd; + SwPosition aTmpPos( *pTextNd ); + SetGetExpField aPos( aTmpPos.nNode, rAttr ); + SwContentFrame* pFrame; + if (pTextNd->GetIndex() < nBodySttNdIdx) + { + std::pair tmp(aPt, true); + pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + if (pFrame) + { + aPos.SetBodyPos( *pFrame ); + } + } + + if( bNext + ? ( aPos < aCmpPos && aCurPos < aPos ) + : ( aCmpPos < aPos && aPos < aCurPos )) + { + OUString sText(pTextNd->GetExpandText(GetLayout(), + rAttr.GetStart(), + *rAttr.GetEnd() - rAttr.GetStart() ) ); + + sText = sText.replaceAll("\x0a", ""); + sText = comphelper::string::strip(sText, ' '); + + if( !sText.isEmpty() ) + aCmpPos = aPos; + } + } + } + } + } + + // then check all the Flys with a URL or image map + { + const SwFrameFormats* pFormats = GetDoc()->GetSpzFrameFormats(); + for( SwFrameFormats::size_type n = 0, nEnd = pFormats->size(); n < nEnd; ++n ) + { + SwFlyFrameFormat* pFormat = static_cast((*pFormats)[ n ]); + const SwFormatURL& rURLItem = pFormat->GetURL(); + if( rURLItem.GetMap() || !rURLItem.GetURL().isEmpty() ) + { + SwFlyFrame* pFly = pFormat->GetFrame( &aPt ); + SwPosition aTmpPos( *pBodySttNd ); + if( pFly && + GetBodyTextNode( *GetDoc(), aTmpPos, *pFly->GetLower() ) ) + { + SetGetExpField aPos( *pFormat, &aTmpPos ); + + if( bNext + ? ( aPos < aCmpPos && aCurPos < aPos ) + : ( aCmpPos < aPos && aPos < aCurPos )) + aCmpPos = aPos; + } + } + } + } + + // found any URL ? + bool bRet = false; + const SwTextINetFormat* pFndAttr = aCmpPos.GetINetFormat(); + const SwFlyFrameFormat* pFndFormat = aCmpPos.GetFlyFormat(); + if( pFndAttr || pFndFormat ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); + + // found a text attribute ? + if( pFndAttr ) + { + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + aCmpPos.GetPosOfContent( *m_pCurrentCursor->GetPoint() ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetPoint()->nContent = *pFndAttr->End(); + + if( !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() ) + { + UpdateCursor( SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE| + SwCursorShell::READONLY ); + bRet = true; + } + } + // found a draw object ? + else if( RES_DRAWFRMFMT == pFndFormat->Which() ) + { + const SdrObject* pSObj = pFndFormat->FindSdrObject(); + if (pSObj) + { + static_cast(this)->SelectObj( pSObj->GetCurrentBoundRect().Center() ); + MakeSelVisible(); + bRet = true; + } + } + else // then is it a fly + { + SwFlyFrame* pFly = pFndFormat->GetFrame(&aPt); + if( pFly ) + { + static_cast(this)->SelectFlyFrame( *pFly ); + MakeSelVisible(); + bRet = true; + } + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/crstrvl1.cxx b/sw/source/core/crsr/crstrvl1.cxx new file mode 100644 index 000000000..565456676 --- /dev/null +++ b/sw/source/core/crsr/crstrvl1.cxx @@ -0,0 +1,126 @@ +/* -*- 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 namespace ::com::sun::star::i18n; + +bool SwCursorShell::IsStartWord( sal_Int16 nWordType ) const +{ + return m_pCurrentCursor->IsStartWordWT(nWordType, GetLayout()); +} +bool SwCursorShell::IsEndWord( sal_Int16 nWordType ) const +{ + return m_pCurrentCursor->IsEndWordWT(nWordType, GetLayout()); +} + +bool SwCursorShell::IsInWord( sal_Int16 nWordType ) const +{ + return m_pCurrentCursor->IsInWordWT(nWordType, GetLayout()); +} + +bool SwCursorShell::IsStartSentence() const +{ + return m_pCurrentCursor->IsStartEndSentence(false, GetLayout()); +} +bool SwCursorShell::IsEndSentence() const +{ + return m_pCurrentCursor->IsStartEndSentence(true, GetLayout()); +} + +bool SwCursorShell::GoStartWord() +{ + return CallCursorShellFN( &SwCursorShell::GoStartWordImpl ); +} +bool SwCursorShell::GoEndWord() +{ + return CallCursorShellFN( &SwCursorShell::GoEndWordImpl ); +} + +bool SwCursorShell::GoNextWord() +{ + return CallCursorShellFN( &SwCursorShell::GoNextWordImpl ); +} +bool SwCursorShell::GoPrevWord() +{ + return CallCursorShellFN( &SwCursorShell::GoPrevWordImpl ); +} + +bool SwCursorShell::GoNextSentence() +{ + return CallCursorShellFN( &SwCursorShell::GoNextSentenceImpl ); +} + +bool SwCursorShell::GoEndSentence() +{ + return CallCursorShellFN( &SwCursorShell::GoEndSentenceImpl ); +} + +bool SwCursorShell::GoStartSentence() +{ + return CallCursorShellFN( &SwCursorShell::GoStartSentenceImpl ); +} + +bool SwCursorShell::SelectWord( const Point* pPt ) +{ + return m_pCurrentCursor->SelectWord( this, pPt ); +} + +void SwCursorShell::ExpandToSentenceBorders() +{ + m_pCurrentCursor->ExpandToSentenceBorders(GetLayout()); +} + +bool SwCursorShell::GoStartWordImpl() +{ + return getShellCursor(true)->GoStartWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout()); +} + +bool SwCursorShell::GoEndWordImpl() +{ + return getShellCursor(true)->GoEndWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout()); +} + +bool SwCursorShell::GoNextWordImpl() +{ + return getShellCursor(true)->GoNextWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout()); +} + +bool SwCursorShell::GoPrevWordImpl() +{ + return getShellCursor(true)->GoPrevWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout()); +} + +bool SwCursorShell::GoNextSentenceImpl() +{ + return getShellCursor(true)->GoSentence(SwCursor::NEXT_SENT, GetLayout()); +} +bool SwCursorShell::GoEndSentenceImpl() +{ + return getShellCursor(true)->GoSentence(SwCursor::END_SENT, GetLayout()); +} +bool SwCursorShell::GoStartSentenceImpl() +{ + return getShellCursor(true)->GoSentence(SwCursor::START_SENT, GetLayout()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/findattr.cxx b/sw/source/core/crsr/findattr.cxx new file mode 100644 index 000000000..a2486ab29 --- /dev/null +++ b/sw/source/core/crsr/findattr.cxx @@ -0,0 +1,1444 @@ +/* -*- 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 namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::util; + +// Special case for SvxFontItem: only compare the name +static bool CmpAttr( const SfxPoolItem& rItem1, const SfxPoolItem& rItem2 ) +{ + switch( rItem1.Which() ) + { + case RES_CHRATR_FONT: + return static_cast(rItem1).GetFamilyName() == + static_cast(rItem2).GetFamilyName(); + + case RES_CHRATR_COLOR: + return static_cast(rItem1).GetValue().IsRGBEqual( + static_cast(rItem2).GetValue() ); + case RES_PAGEDESC: + ::std::optional const oNumOffset1 = + static_cast(rItem1).GetNumOffset(); + ::std::optional const oNumOffset2 = + static_cast(rItem2).GetNumOffset(); + + if (oNumOffset1 != oNumOffset2) + return false; + + return static_cast(rItem1).GetPageDesc() == static_cast(rItem2).GetPageDesc(); + } + return rItem1 == rItem2; +} + +const SwTextAttr* GetFrwrdTextHint( const SwpHints& rHtsArr, size_t& rPos, + sal_Int32 nContentPos ) +{ + while( rPos < rHtsArr.Count() ) + { + const SwTextAttr *pTextHt = rHtsArr.Get( rPos++ ); + // the start of an attribute has to be in the section + if( pTextHt->GetStart() >= nContentPos ) + return pTextHt; // valid text attribute + } + return nullptr; // invalid text attribute +} + +const SwTextAttr* GetBkwrdTextHint( const SwpHints& rHtsArr, size_t& rPos, + sal_Int32 nContentPos ) +{ + while( rPos > 0 ) + { + const SwTextAttr *pTextHt = rHtsArr.Get( --rPos ); + // the start of an attribute has to be in the section + if( pTextHt->GetStart() < nContentPos ) + return pTextHt; // valid text attribute + } + return nullptr; // invalid text attribute +} + +static void lcl_SetAttrPam( SwPaM& rPam, sal_Int32 nStart, const sal_Int32* pEnd, + const bool bSaveMark ) +{ + sal_Int32 nContentPos; + if( bSaveMark ) + nContentPos = rPam.GetMark()->nContent.GetIndex(); + else + nContentPos = rPam.GetPoint()->nContent.GetIndex(); + bool bTstEnd = rPam.GetPoint()->nNode == rPam.GetMark()->nNode; + + SwContentNode* pCNd = rPam.GetContentNode(); + rPam.GetPoint()->nContent.Assign( pCNd, nStart ); + rPam.SetMark(); // Point == GetMark + + // Point points to end of search area or end of attribute + if( pEnd ) + { + if( bTstEnd && *pEnd > nContentPos ) + rPam.GetPoint()->nContent = nContentPos; + else + rPam.GetPoint()->nContent = *pEnd; + } +} + +// TODO: provide documentation +/** search for a text attribute + + This function searches in a text node for a given attribute. + If that is found then the SwPaM contains the section that surrounds the + attribute (w.r.t. the search area). + + @param rTextNd Text node to search in. + @param rPam ??? + @param rCmpItem ??? + @param fnMove ??? + @return Returns if found, otherwise. +*/ +static bool lcl_SearchAttr( const SwTextNode& rTextNd, SwPaM& rPam, + const SfxPoolItem& rCmpItem, + SwMoveFnCollection const & fnMove) +{ + if ( !rTextNd.HasHints() ) + return false; + + const SwTextAttr *pTextHt = nullptr; + bool bForward = &fnMove == &fnMoveForward; + size_t nPos = bForward ? 0 : rTextNd.GetSwpHints().Count(); + sal_Int32 nContentPos = rPam.GetPoint()->nContent.GetIndex(); + + while( nullptr != ( pTextHt=(*fnMove.fnGetHint)(rTextNd.GetSwpHints(),nPos,nContentPos))) + if (pTextHt->Which() == rCmpItem.Which()) + { + lcl_SetAttrPam( rPam, pTextHt->GetStart(), pTextHt->End(), bForward ); + return true; + } + return false; +} + +namespace { + +/// search for multiple text attributes +struct SwSrchChrAttr +{ + sal_uInt16 nWhich; + sal_Int32 nStt; + sal_Int32 nEnd; + + SwSrchChrAttr(): nWhich(0), nStt(0), nEnd(0) {} + + SwSrchChrAttr( const SfxPoolItem& rItem, + sal_Int32 nStart, sal_Int32 nAnyEnd ) + : nWhich( rItem.Which() ), nStt( nStart ), nEnd( nAnyEnd ) + {} +}; + +class SwAttrCheckArr +{ + SwSrchChrAttr *m_pFindArr, *m_pStackArr; + sal_Int32 m_nNodeStart; + sal_Int32 m_nNodeEnd; + sal_uInt16 m_nArrStart, m_nArrLen; + sal_uInt16 m_nFound, m_nStackCount; + SfxItemSet m_aComapeSet; + bool m_bNoColls; + bool m_bForward; + +public: + SwAttrCheckArr( const SfxItemSet& rSet, bool bForward, bool bNoCollections ); + ~SwAttrCheckArr(); + + void SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam ); + + /// how many attributes are there in total? + sal_uInt16 Count() const { return m_aComapeSet.Count(); } + bool Found() const { return m_nFound == m_aComapeSet.Count(); } + bool CheckStack(); + + sal_Int32 Start() const; + sal_Int32 End() const; + + sal_Int32 GetNdStt() const { return m_nNodeStart; } + sal_Int32 GetNdEnd() const { return m_nNodeEnd; } + + bool SetAttrFwd( const SwTextAttr& rAttr ); + bool SetAttrBwd( const SwTextAttr& rAttr ); +}; + +} + +SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet& rSet, bool bFwd, + bool bNoCollections ) + : m_nNodeStart(0) + , m_nNodeEnd(0) + , m_nFound(0) + , m_nStackCount(0) + , m_aComapeSet( *rSet.GetPool(), svl::Items{} ) + , m_bNoColls(bNoCollections) + , m_bForward(bFwd) +{ + m_aComapeSet.Put( rSet, false ); + + // determine area of Fnd/Stack array (Min/Max) + SfxItemIter aIter( m_aComapeSet ); + m_nArrStart = m_aComapeSet.GetWhichByPos( aIter.GetFirstPos() ); + m_nArrLen = m_aComapeSet.GetWhichByPos( aIter.GetLastPos() ) - m_nArrStart+1; + + char* pFndChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ]; + char* pStackChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ]; + + m_pFindArr = reinterpret_cast(pFndChar); + m_pStackArr = reinterpret_cast(pStackChar); +} + +SwAttrCheckArr::~SwAttrCheckArr() +{ + delete[] reinterpret_cast(m_pFindArr); + delete[] reinterpret_cast(m_pStackArr); +} + +void SwAttrCheckArr::SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam ) +{ + std::fill(m_pFindArr, m_pFindArr + m_nArrLen, SwSrchChrAttr()); + std::fill(m_pStackArr, m_pStackArr + m_nArrLen, SwSrchChrAttr()); + m_nFound = 0; + m_nStackCount = 0; + + if( m_bForward ) + { + m_nNodeStart = rPam.GetPoint()->nContent.GetIndex(); + m_nNodeEnd = rPam.GetPoint()->nNode == rPam.GetMark()->nNode + ? rPam.GetMark()->nContent.GetIndex() + : rTextNd.GetText().getLength(); + } + else + { + m_nNodeEnd = rPam.GetPoint()->nContent.GetIndex(); + m_nNodeStart = rPam.GetPoint()->nNode == rPam.GetMark()->nNode + ? rPam.GetMark()->nContent.GetIndex() + : 0; + } + + if( m_bNoColls && !rTextNd.HasSwAttrSet() ) + return ; + + const SfxItemSet& rSet = rTextNd.GetSwAttrSet(); + + SfxItemIter aIter( m_aComapeSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + const SfxPoolItem* pFndItem; + sal_uInt16 nWhich; + + do + { + if( IsInvalidItem( pItem ) ) + { + nWhich = m_aComapeSet.GetWhichByPos( aIter.GetCurPos() ); + if( RES_TXTATR_END <= nWhich ) + break; // end of text attributes + + if( SfxItemState::SET == rSet.GetItemState( nWhich, !m_bNoColls, &pFndItem ) + && !CmpAttr( *pFndItem, rSet.GetPool()->GetDefaultItem( nWhich ) )) + { + m_pFindArr[ nWhich - m_nArrStart ] = + SwSrchChrAttr( *pFndItem, m_nNodeStart, m_nNodeEnd ); + m_nFound++; + } + } + else + { + if( RES_TXTATR_END <= (nWhich = pItem->Which() )) + break; // end of text attributes + + if( CmpAttr( rSet.Get( nWhich, !m_bNoColls ), *pItem ) ) + { + m_pFindArr[ nWhich - m_nArrStart ] = + SwSrchChrAttr( *pItem, m_nNodeStart, m_nNodeEnd ); + m_nFound++; + } + } + + pItem = aIter.NextItem(); + } while (pItem); +} + +static bool +lcl_IsAttributeIgnorable(sal_Int32 const nNdStart, sal_Int32 const nNdEnd, + SwSrchChrAttr const& rTmp) +{ + // #i115528#: if there is a paragraph attribute, it has been added by the + // SwAttrCheckArr ctor, and nFound is 1. + // if the paragraph is entirely covered by hints that override the paragraph + // attribute, then this function must find an attribute to decrement nFound! + // so check for an empty search range, let attributes that start/end there + // cover it, and hope for the best... + return ((nNdEnd == nNdStart) + ? ((rTmp.nEnd < nNdStart) || (nNdEnd < rTmp.nStt)) + : ((rTmp.nEnd <= nNdStart) || (nNdEnd <= rTmp.nStt))); +} + +bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr& rAttr ) +{ + SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() ); + + // ignore all attributes not in search range + if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp)) + { + return Found(); + } + + const SfxPoolItem* pItem; + // here we explicitly also search in character templates + sal_uInt16 nWhch = rAttr.Which(); + std::unique_ptr pIter; + const SfxPoolItem* pTmpItem = nullptr; + const SfxItemSet* pSet = nullptr; + if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch ) + { + if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch ) + return Found(); + pTmpItem = nullptr; + pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( pSet ) + { + pIter.reset(new SfxWhichIter( *pSet )); + nWhch = pIter->FirstWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->NextWhich(); + if( !nWhch ) + pTmpItem = nullptr; + } + } + else + pTmpItem = &rAttr.GetAttr(); + + while( pTmpItem ) + { + SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem ); + if( SfxItemState::DONTCARE == eState || SfxItemState::SET == eState ) + { + sal_uInt16 n; + SwSrchChrAttr* pCmp; + + // first delete all up to start position that are already invalid + SwSrchChrAttr* pArrPtr; + if( m_nFound ) + for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; + ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nEnd <= aTmp.nStt ) + { + pArrPtr->nWhich = 0; // deleted + m_nFound--; + } + + // delete all up to start position that are already invalid and + // move all "open" ones (= stick out over start position) from stack + // into FndSet + if( m_nStackCount ) + for( pArrPtr = m_pStackArr, n=0; n < m_nArrLen; ++n, ++pArrPtr ) + { + if( !pArrPtr->nWhich ) + continue; + + if( pArrPtr->nEnd <= aTmp.nStt ) + { + pArrPtr->nWhich = 0; // deleted + if( !--m_nStackCount ) + break; + } + else if( pArrPtr->nStt <= aTmp.nStt ) + { + pCmp = &m_pFindArr[ n ]; + if( pCmp->nWhich ) + { + if( pCmp->nEnd < pArrPtr->nEnd ) // extend + pCmp->nEnd = pArrPtr->nEnd; + } + else + { + *pCmp = *pArrPtr; + m_nFound++; + } + pArrPtr->nWhich = 0; + if( !--m_nStackCount ) + break; + } + } + + bool bContinue = false; + + if( SfxItemState::DONTCARE == eState ) + { + // Will the attribute become valid? + if( !CmpAttr( m_aComapeSet.GetPool()->GetDefaultItem( nWhch ), + *pTmpItem )) + { + // search attribute and extend if needed + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if( !pCmp->nWhich ) + { + *pCmp = aTmp; // not found, insert + m_nFound++; + } + else if( pCmp->nEnd < aTmp.nEnd ) // extend? + pCmp->nEnd = aTmp.nEnd; + + bContinue = true; + } + } + // Will the attribute become valid? + else if( CmpAttr( *pItem, *pTmpItem ) ) + { + m_pFindArr[ nWhch - m_nArrStart ] = aTmp; + ++m_nFound; + bContinue = true; + } + + // then is has to go on the stack + if( !bContinue ) + { + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if (pCmp->nWhich ) + { + // exists on stack, only if it is even bigger + if( pCmp->nEnd > aTmp.nEnd ) + { + OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich, + "slot on stack is still in use" ); + + if( aTmp.nStt <= pCmp->nStt ) + pCmp->nStt = aTmp.nEnd; + else + pCmp->nEnd = aTmp.nStt; + + m_pStackArr[ nWhch - m_nArrStart ] = *pCmp; + m_nStackCount++; + } + pCmp->nWhich = 0; + m_nFound--; + } + } + } + if( pIter ) + { + assert(pSet && "otherwise no pIter"); + nWhch = pIter->NextWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->NextWhich(); + if( !nWhch ) + break; + } + else + break; + } + pIter.reset(); + return Found(); +} + +bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr& rAttr ) +{ + SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() ); + + // ignore all attributes not in search range + if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp)) + { + return Found(); + } + + const SfxPoolItem* pItem; + // here we explicitly also search in character templates + sal_uInt16 nWhch = rAttr.Which(); + std::unique_ptr pIter; + const SfxPoolItem* pTmpItem = nullptr; + const SfxItemSet* pSet = nullptr; + if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch ) + { + if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch ) + return Found(); + + pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( pSet ) + { + pIter.reset( new SfxWhichIter( *pSet ) ); + nWhch = pIter->FirstWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->NextWhich(); + if( !nWhch ) + pTmpItem = nullptr; + } + } + else + pTmpItem = &rAttr.GetAttr(); + + while( pTmpItem ) + { + SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem ); + if( SfxItemState::DONTCARE == eState || SfxItemState::SET == eState ) + { + sal_uInt16 n; + SwSrchChrAttr* pCmp; + + // first delete all up to start position that are already invalid + SwSrchChrAttr* pArrPtr; + if( m_nFound ) + for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nStt >= aTmp.nEnd ) + { + pArrPtr->nWhich = 0; // deleted + m_nFound--; + } + + // delete all up to start position that are already invalid and + // move all "open" ones (= stick out over start position) from stack + // into FndSet + if( m_nStackCount ) + for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + { + if( !pArrPtr->nWhich ) + continue; + + if( pArrPtr->nStt >= aTmp.nEnd ) + { + pArrPtr->nWhich = 0; // deleted + if( !--m_nStackCount ) + break; + } + else if( pArrPtr->nEnd >= aTmp.nEnd ) + { + pCmp = &m_pFindArr[ n ]; + if( pCmp->nWhich ) + { + if( pCmp->nStt > pArrPtr->nStt ) // extend + pCmp->nStt = pArrPtr->nStt; + } + else + { + *pCmp = *pArrPtr; + m_nFound++; + } + pArrPtr->nWhich = 0; + if( !--m_nStackCount ) + break; + } + } + + bool bContinue = false; + if( SfxItemState::DONTCARE == eState ) + { + // Will the attribute become valid? + if( !CmpAttr( m_aComapeSet.GetPool()->GetDefaultItem( nWhch ), + *pTmpItem ) ) + { + // search attribute and extend if needed + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if( !pCmp->nWhich ) + { + *pCmp = aTmp; // not found, insert + m_nFound++; + } + else if( pCmp->nStt > aTmp.nStt ) // extend? + pCmp->nStt = aTmp.nStt; + + bContinue = true; + } + } + // Will the attribute become valid? + else if( CmpAttr( *pItem, *pTmpItem )) + { + m_pFindArr[ nWhch - m_nArrStart ] = aTmp; + ++m_nFound; + bContinue = true; + } + + // then is has to go on the stack + if( !bContinue ) + { + pCmp = &m_pFindArr[ nWhch - m_nArrStart ]; + if( pCmp->nWhich ) + { + // exists on stack, only if it is even bigger + if( pCmp->nStt < aTmp.nStt ) + { + OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich, + "slot on stack is still in use" ); + + if( aTmp.nEnd <= pCmp->nEnd ) + pCmp->nEnd = aTmp.nStt; + else + pCmp->nStt = aTmp.nEnd; + + m_pStackArr[ nWhch - m_nArrStart ] = *pCmp; + m_nStackCount++; + } + pCmp->nWhich = 0; + m_nFound--; + } + } + } + if( pIter ) + { + assert(pSet && "otherwise no pIter"); + nWhch = pIter->NextWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->NextWhich(); + if( !nWhch ) + break; + } + else + break; + } + pIter.reset(); + return Found(); +} + +sal_Int32 SwAttrCheckArr::Start() const +{ + sal_Int32 nStart = m_nNodeStart; + SwSrchChrAttr* pArrPtr = m_pFindArr; + for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nStt > nStart ) + nStart = pArrPtr->nStt; + + return nStart; +} + +sal_Int32 SwAttrCheckArr::End() const +{ + SwSrchChrAttr* pArrPtr = m_pFindArr; + sal_Int32 nEnd = m_nNodeEnd; + for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + if( pArrPtr->nWhich && pArrPtr->nEnd < nEnd ) + nEnd = pArrPtr->nEnd; + + return nEnd; +} + +bool SwAttrCheckArr::CheckStack() +{ + if( !m_nStackCount ) + return false; + + sal_uInt16 n; + const sal_Int32 nSttPos = Start(); + const sal_Int32 nEndPos = End(); + SwSrchChrAttr* pArrPtr; + for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr ) + { + if( !pArrPtr->nWhich ) + continue; + + if( m_bForward ? pArrPtr->nEnd <= nSttPos : pArrPtr->nStt >= nEndPos ) + { + pArrPtr->nWhich = 0; // deleted + if( !--m_nStackCount ) + return m_nFound == m_aComapeSet.Count(); + } + else if( m_bForward ? pArrPtr->nStt < nEndPos : pArrPtr->nEnd > nSttPos ) + { + // move all "open" ones (= stick out over start position) into FndSet + OSL_ENSURE( !m_pFindArr[ n ].nWhich, "slot in array is already in use" ); + m_pFindArr[ n ] = *pArrPtr; + pArrPtr->nWhich = 0; + m_nFound++; + if( !--m_nStackCount ) + return m_nFound == m_aComapeSet.Count(); + } + } + return m_nFound == m_aComapeSet.Count(); +} + +static bool lcl_SearchForward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr, + SwPaM& rPam ) +{ + sal_Int32 nEndPos; + rCmpArr.SetNewSet( rTextNd, rPam ); + if( !rTextNd.HasHints() ) + { + if( !rCmpArr.Found() ) + return false; + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true ); + return true; + } + + const SwpHints& rHtArr = rTextNd.GetSwpHints(); + const SwTextAttr* pAttr; + size_t nPos = 0; + + // if everything is already there then check with which it will be ended + if( rCmpArr.Found() ) + { + for( ; nPos < rHtArr.Count(); ++nPos ) + { + pAttr = rHtArr.Get( nPos ); + if( !rCmpArr.SetAttrFwd( *pAttr ) ) + { + if( rCmpArr.GetNdStt() < pAttr->GetStart() ) + { + // found end + auto nTmpStart = pAttr->GetStart(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), + &nTmpStart, true ); + return true; + } + // continue search + break; + } + } + + if( nPos == rHtArr.Count() && rCmpArr.Found() ) + { + // found + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true ); + return true; + } + } + + sal_Int32 nSttPos; + for( ; nPos < rHtArr.Count(); ++nPos ) + { + pAttr = rHtArr.Get( nPos ); + if( rCmpArr.SetAttrFwd( *pAttr ) ) + { + // Do multiple start at that position? Do also check those: + nSttPos = pAttr->GetStart(); + while( ++nPos < rHtArr.Count() ) + { + pAttr = rHtArr.Get( nPos ); + if( nSttPos != pAttr->GetStart() || !rCmpArr.SetAttrFwd( *pAttr ) ) + break; + } + + if( !rCmpArr.Found() ) + continue; + + // then we have our search area + if( (nSttPos = rCmpArr.Start()) > (nEndPos = rCmpArr.End()) ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true ); + return true; + } + } + + if( !rCmpArr.CheckStack() ) + return false; + nSttPos = rCmpArr.Start(); + nEndPos = rCmpArr.End(); + if( nSttPos > nEndPos ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true ); + return true; +} + +static bool lcl_SearchBackward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr, + SwPaM& rPam ) +{ + sal_Int32 nEndPos; + rCmpArr.SetNewSet( rTextNd, rPam ); + if( !rTextNd.HasHints() ) + { + if( !rCmpArr.Found() ) + return false; + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false ); + return true; + } + + const SwpHints& rHtArr = rTextNd.GetSwpHints(); + const SwTextAttr* pAttr; + size_t nPos = rHtArr.Count(); + sal_Int32 nSttPos; + + // if everything is already there then check with which it will be ended + if( rCmpArr.Found() ) + { + while( nPos ) + { + pAttr = rHtArr.GetSortedByEnd( --nPos ); + if( !rCmpArr.SetAttrBwd( *pAttr ) ) + { + nSttPos = pAttr->GetAnyEnd(); + if( nSttPos < rCmpArr.GetNdEnd() ) + { + // found end + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false ); + return true; + } + + // continue search + break; + } + } + + if( !nPos && rCmpArr.Found() ) + { + // found + nEndPos = rCmpArr.GetNdEnd(); + lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false ); + return true; + } + } + + while( nPos ) + { + pAttr = rHtArr.GetSortedByEnd( --nPos ); + if( rCmpArr.SetAttrBwd( *pAttr ) ) + { + // Do multiple start at that position? Do also check those: + if( nPos ) + { + nEndPos = pAttr->GetAnyEnd(); + while( --nPos ) + { + pAttr = rHtArr.GetSortedByEnd( nPos ); + if( nEndPos != pAttr->GetAnyEnd() || !rCmpArr.SetAttrBwd( *pAttr ) ) + break; + } + } + if( !rCmpArr.Found() ) + continue; + + // then we have our search area + if( (nSttPos = rCmpArr.Start()) > (nEndPos = rCmpArr.End()) ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false ); + return true; + } + } + + if( !rCmpArr.CheckStack() ) + return false; + nSttPos = rCmpArr.Start(); + nEndPos = rCmpArr.End(); + if( nSttPos > nEndPos ) + return false; + + lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false ); + return true; +} + +static bool lcl_Search( const SwContentNode& rCNd, const SfxItemSet& rCmpSet, bool bNoColls ) +{ + // search only hard attribution? + if( bNoColls && !rCNd.HasSwAttrSet() ) + return false; + + const SfxItemSet& rNdSet = rCNd.GetSwAttrSet(); + SfxItemIter aIter( rCmpSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + const SfxPoolItem* pNdItem; + sal_uInt16 nWhich; + + do + { + if( IsInvalidItem( pItem )) + { + nWhich = rCmpSet.GetWhichByPos( aIter.GetCurPos() ); + if( SfxItemState::SET != rNdSet.GetItemState( nWhich, !bNoColls, &pNdItem ) + || CmpAttr( *pNdItem, rNdSet.GetPool()->GetDefaultItem( nWhich ) )) + return false; + } + else + { + nWhich = pItem->Which(); + + if( !CmpAttr( rNdSet.Get( nWhich, !bNoColls ), *pItem )) + return false; + } + + pItem = aIter.NextItem(); + } while (pItem); + return true; // found +} + +namespace sw { + +bool FindAttrImpl(SwPaM & rSearchPam, + const SfxPoolItem& rAttr, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + SwRootFrame const*const pLayout) +{ + // determine which attribute is searched: + const sal_uInt16 nWhich = rAttr.Which(); + bool bCharAttr = isCHRATR(nWhich) || isTXTATR(nWhich); + assert(isTXTATR(nWhich)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others + + std::unique_ptr pPam(sw::MakeRegion(fnMove, rRegion)); + + bool bFound = false; + bool bFirst = true; + const bool bSrchForward = &fnMove == &fnMoveForward; + SwContentNode * pNode; + + // if at beginning/end then move it out of the node + if( bSrchForward + ? pPam->GetPoint()->nContent.GetIndex() == pPam->GetContentNode()->Len() + : !pPam->GetPoint()->nContent.GetIndex() ) + { + if( !(*fnMove.fnNds)( &pPam->GetPoint()->nNode, false )) + { + return false; + } + SwContentNode *pNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pNd, bSrchForward ? 0 : pNd->Len() ); + } + + while (nullptr != (pNode = ::GetNode(*pPam, bFirst, fnMove, bInReadOnly, pLayout))) + { + if( bCharAttr ) + { + if( !pNode->IsTextNode() ) // CharAttr are only in text nodes + continue; + + SwTextFrame const*const pFrame(pLayout + ? static_cast(pNode->getLayoutFrame(pLayout)) + : nullptr); + if (pFrame) + { + SwTextNode const* pAttrNode(nullptr); + SwTextAttr const* pAttr(nullptr); + if (bSrchForward) + { + sw::MergedAttrIter iter(*pFrame); + do + { + pAttr = iter.NextAttr(&pAttrNode); + } + while (pAttr + && (pAttrNode->GetIndex() < pPam->GetPoint()->nNode.GetIndex() + || (pAttrNode->GetIndex() == pPam->GetPoint()->nNode.GetIndex() + && pAttr->GetStart() < pPam->GetPoint()->nContent.GetIndex()) + || pAttr->Which() != nWhich)); + } + else + { + sw::MergedAttrIterReverse iter(*pFrame); + do + { + pAttr = iter.PrevAttr(&pAttrNode); + } + while (pAttr + && (pPam->GetPoint()->nNode.GetIndex() < pAttrNode->GetIndex() + || (pPam->GetPoint()->nNode.GetIndex() == pAttrNode->GetIndex() + && pPam->GetPoint()->nContent.GetIndex() <= pAttr->GetStart()) + || pAttr->Which() != nWhich)); + } + if (pAttr) + { + assert(pAttrNode); + pPam->GetPoint()->nNode = *pAttrNode; + lcl_SetAttrPam(*pPam, pAttr->GetStart(), pAttr->End(), bSrchForward); + bFound = true; + break; + } + } + else if (!pLayout && pNode->GetTextNode()->HasHints() && + lcl_SearchAttr(*pNode->GetTextNode(), *pPam, rAttr, fnMove)) + { + bFound = true; + } + if (bFound) + { + // set to the values of the attribute + rSearchPam.SetMark(); + *rSearchPam.GetPoint() = *pPam->GetPoint(); + *rSearchPam.GetMark() = *pPam->GetMark(); + break; + } + else if (isTXTATR(nWhich)) + continue; + } + +#if 0 + // no hard attribution, so check if node was asked for this attr before + if( !pNode->HasSwAttrSet() ) + { + SwFormat* pTmpFormat = pNode->GetFormatColl(); + if( !aFormatArr.insert( pTmpFormat ).second ) + continue; // collection was requested earlier + } + + if( SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( nWhich, + true, &pItem )) + { + // FORWARD: SPoint at the end, GetMark at the beginning of the node + // BACKWARD: SPoint at the beginning, GetMark at the end of the node + // always: incl. start and incl. end + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + pNode->MakeEndIndex( &rSearchPam.GetPoint()->nContent ); + bFound = true; + break; + } +#endif + } + + // if backward search, switch point and mark + if( bFound && !bSrchForward ) + rSearchPam.Exchange(); + + return bFound; +} + +} // namespace sw + +typedef bool (*FnSearchAttr)( const SwTextNode&, SwAttrCheckArr&, SwPaM& ); + +static bool FindAttrsImpl(SwPaM & rSearchPam, + const SfxItemSet& rSet, bool bNoColls, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, bool bMoveFirst, + SwRootFrame const*const pLayout) +{ + std::unique_ptr pPam(sw::MakeRegion(fnMove, rRegion)); + + bool bFound = false; + bool bFirst = true; + const bool bSrchForward = &fnMove == &fnMoveForward; + SwContentNode * pNode; + o3tl::sorted_vector aFormatArr; + + // check which text/char attributes are searched + SwAttrCheckArr aCmpArr( rSet, bSrchForward, bNoColls ); + SfxItemSet aOtherSet( rSearchPam.GetDoc()->GetAttrPool(), + svl::Items{} ); + aOtherSet.Put( rSet, false ); // got all invalid items + + FnSearchAttr fnSearch = bSrchForward + ? (&::lcl_SearchForward) + : (&::lcl_SearchBackward); + + // if at beginning/end then move it out of the node + if( bMoveFirst && + ( bSrchForward + ? pPam->GetPoint()->nContent.GetIndex() == pPam->GetContentNode()->Len() + : !pPam->GetPoint()->nContent.GetIndex() ) ) + { + if( !(*fnMove.fnNds)( &pPam->GetPoint()->nNode, false )) + { + return false; + } + SwContentNode *pNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pNd, bSrchForward ? 0 : pNd->Len() ); + } + + while (nullptr != (pNode = ::GetNode(*pPam, bFirst, fnMove, bInReadOnly, pLayout))) + { + SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode() + ? static_cast(pNode->getLayoutFrame(pLayout)) + : nullptr); + assert(!pLayout || !pNode->IsTextNode() || pFrame); + // sw_redlinehide: it's apparently not possible to find break items + // with the UI, so checking one node is enough + SwContentNode const& rPropsNode(*(pFrame + ? pFrame->GetTextNodeForParaProps() + : pNode)); + + if( aCmpArr.Count() ) + { + if( !pNode->IsTextNode() ) // CharAttr are only in text nodes + continue; + + if (aOtherSet.Count() && + !lcl_Search(rPropsNode, aOtherSet, bNoColls)) + { + continue; + } + sw::MergedPara const*const pMergedPara(pFrame ? pFrame->GetMergedPara() : nullptr); + if (pMergedPara) + { + SwPosition const& rStart(*pPam->Start()); + SwPosition const& rEnd(*pPam->End()); + // no extents? fall back to searching index 0 of propsnode + // to find its node items + if (pMergedPara->extents.empty()) + { + if (rStart.nNode.GetIndex() <= rPropsNode.GetIndex() + && rPropsNode.GetIndex() <= rEnd.nNode.GetIndex()) + { + SwPaM tmp(rPropsNode, 0, rPropsNode, 0); + bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, tmp); + if (bFound) + { + *pPam = tmp; + } + } + } + else + { + // iterate the extents, and intersect with input pPam: + // the found ranges should never include delete redlines + // so that subsequent Replace will not affect them + for (size_t i = 0; i < pMergedPara->extents.size(); ++i) + { + auto const rExtent(pMergedPara->extents[bSrchForward + ? i + : pMergedPara->extents.size() - i - 1]); + if (rExtent.pNode->GetIndex() < rStart.nNode.GetIndex() + || rEnd.nNode.GetIndex() < rExtent.pNode->GetIndex()) + { + continue; + } + sal_Int32 const nStart(rExtent.pNode == &rStart.nNode.GetNode() + ? rStart.nContent.GetIndex() + : 0); + if (rExtent.nEnd <= nStart) + { + continue; + } + sal_Int32 const nEnd(rExtent.pNode == &rEnd.nNode.GetNode() + ? rEnd.nContent.GetIndex() + : rExtent.pNode->Len()); + if (nEnd < rExtent.nStart + || (nStart != nEnd && nEnd == rExtent.nStart)) + { + continue; + } + SwPaM tmp(*rExtent.pNode, std::max(nStart, rExtent.nStart), + *rExtent.pNode, std::min(nEnd, rExtent.nEnd)); + tmp.Normalize(bSrchForward); + bFound = (*fnSearch)(*rExtent.pNode, aCmpArr, tmp); + if (bFound) + { + *pPam = tmp; + break; + } + } + } + } + else + { + bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, *pPam); + } + if (bFound) + { + // set to the values of the attribute + rSearchPam.SetMark(); + *rSearchPam.GetPoint() = *pPam->GetPoint(); + *rSearchPam.GetMark() = *pPam->GetMark(); + break; + } + continue; // text attribute + } + + if( !aOtherSet.Count() ) + continue; + + // no hard attribution, so check if node was asked for this attr before + // (as an optimisation) + if (!rPropsNode.HasSwAttrSet()) + { + SwFormat* pTmpFormat = rPropsNode.GetFormatColl(); + if( !aFormatArr.insert( pTmpFormat ).second ) + continue; // collection was requested earlier + } + + if (lcl_Search(rPropsNode, aOtherSet, bNoColls)) + { + // FORWARD: SPoint at the end, GetMark at the beginning of the node + // BACKWARD: SPoint at the beginning, GetMark at the end of the node + if (pFrame) + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + *rSearchPam.GetMark() = pFrame->MapViewToModelPos( + TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0)); + } + else + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + if (bSrchForward) + { + pNode->MakeEndIndex( &rSearchPam.GetPoint()->nContent ); + } + else + { + pNode->MakeStartIndex( &rSearchPam.GetPoint()->nContent ); + } + } + bFound = true; + break; + } + } + + // in search direction, mark precedes point, because the next iteration + // starts at point + if (bFound) + { + rSearchPam.Normalize(!bSrchForward); + } + + return bFound; +} + +namespace { + +/// parameters for search for attributes +struct SwFindParaAttr : public SwFindParas +{ + bool m_bNoCollection; + const SfxItemSet *pSet, *pReplSet; + const i18nutil::SearchOptions2 *pSearchOpt; + SwCursor& m_rCursor; + SwRootFrame const* m_pLayout; + std::unique_ptr pSText; + + SwFindParaAttr( const SfxItemSet& rSet, bool bNoCollection, + const i18nutil::SearchOptions2* pOpt, const SfxItemSet* pRSet, + SwCursor& rCursor, SwRootFrame const*const pLayout) + : m_bNoCollection(bNoCollection) + , pSet( &rSet ) + , pReplSet( pRSet ) + , pSearchOpt( pOpt ) + , m_rCursor(rCursor) + , m_pLayout(pLayout) + {} + + virtual ~SwFindParaAttr() {} + + virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, + std::unique_ptr& xSearchItem) override; + virtual bool IsReplaceMode() const override; +}; + +} + +int SwFindParaAttr::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + std::unique_ptr& xSearchItem) +{ + // replace string (only if text given and search is not parameterized)? + bool bReplaceText = pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() || + !pSet->Count() ); + bool bReplaceAttr = pReplSet && pReplSet->Count(); + bool bMoveFirst = !bReplaceAttr; + if( bInReadOnly && (bReplaceAttr || bReplaceText )) + bInReadOnly = false; + + // We search for attributes, should we search for text as well? + { + SwPaM aRegion( *rRegion.GetMark(), *rRegion.GetPoint() ); + SwPaM* pTextRegion = &aRegion; + SwPaM aSrchPam( *rCursor.GetPoint() ); + + while( true ) + { + if( pSet->Count() ) // any attributes? + { + // first attributes + if (!FindAttrsImpl(aSrchPam, *pSet, m_bNoCollection, fnMove, aRegion, bInReadOnly, bMoveFirst, m_pLayout)) + return FIND_NOT_FOUND; + bMoveFirst = true; + + if( !pSearchOpt ) + break; // ok, only attributes, so found + + pTextRegion = &aSrchPam; + } + else if( !pSearchOpt ) + return FIND_NOT_FOUND; + + // then search in text of it + if( !pSText ) + { + i18nutil::SearchOptions2 aTmp( *pSearchOpt ); + + // search in selection + aTmp.searchFlag |= (SearchFlags::REG_NOT_BEGINOFLINE | + SearchFlags::REG_NOT_ENDOFLINE); + + aTmp.Locale = SvtSysLocale().GetLanguageTag().getLocale(); + + pSText.reset( new utl::TextSearch( aTmp ) ); + } + + // TODO: searching for attributes in Outliner text?! + + // continue search in correct section (pTextRegion) + if (sw::FindTextImpl(aSrchPam, *pSearchOpt, false/*bSearchInNotes*/, *pSText, fnMove, *pTextRegion, bInReadOnly, m_pLayout, xSearchItem) && + *aSrchPam.GetMark() != *aSrchPam.GetPoint() ) + break; // found + else if( !pSet->Count() ) + return FIND_NOT_FOUND; // only text and nothing found + + *aRegion.GetMark() = *aSrchPam.GetPoint(); + } + + *rCursor.GetPoint() = *aSrchPam.GetPoint(); + rCursor.SetMark(); + *rCursor.GetMark() = *aSrchPam.GetMark(); + } + + if( bReplaceText ) + { + const bool bRegExp( + SearchAlgorithms2::REGEXP == pSearchOpt->AlgorithmType2); + SwIndex& rSttCntIdx = rCursor.Start()->nContent; + const sal_Int32 nSttCnt = rSttCntIdx.GetIndex(); + + // add to shell-cursor-ring so that the regions will be moved eventually + SwPaM* pPrevRing(nullptr); + if( bRegExp ) + { + pPrevRing = const_cast(rRegion).GetPrev(); + const_cast(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() ); + } + + std::optional xRepl; + if (bRegExp) + xRepl = sw::ReplaceBackReferences(*pSearchOpt, &rCursor, m_pLayout); + sw::ReplaceImpl(rCursor, + xRepl ? *xRepl : pSearchOpt->replaceString, bRegExp, + *m_rCursor.GetDoc(), m_pLayout); + + m_rCursor.SaveTableBoxContent( rCursor.GetPoint() ); + + if( bRegExp ) + { + // and remove region again + SwPaM* p; + SwPaM* pNext = const_cast(&rRegion); + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo(const_cast(&rRegion)); + } while( p != pPrevRing ); + } + rSttCntIdx = nSttCnt; + } + + if( bReplaceAttr ) + { + // is the selection still existent? + // all searched attributes are reset to default if + // they are not in ReplaceSet + if( !pSet->Count() ) + { + rCursor.GetDoc()->getIDocumentContentOperations().InsertItemSet( + rCursor, *pReplSet, SetAttrMode::DEFAULT, m_pLayout); + } + else + { + SfxItemPool* pPool = pReplSet->GetPool(); + SfxItemSet aSet( *pPool, pReplSet->GetRanges() ); + + SfxItemIter aIter( *pSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + // reset all that are not set with pool defaults + if( !IsInvalidItem( pItem ) && SfxItemState::SET != + pReplSet->GetItemState( pItem->Which(), false )) + aSet.Put( pPool->GetDefaultItem( pItem->Which() )); + + pItem = aIter.NextItem(); + } while (pItem); + aSet.Put( *pReplSet ); + rCursor.GetDoc()->getIDocumentContentOperations().InsertItemSet( + rCursor, aSet, SetAttrMode::DEFAULT, m_pLayout); + } + + return FIND_NO_RING; + } + else + return FIND_FOUND; +} + +bool SwFindParaAttr::IsReplaceMode() const +{ + return ( pSearchOpt && !pSearchOpt->replaceString.isEmpty() ) || + ( pReplSet && pReplSet->Count() ); +} + +/// search for attributes +sal_uLong SwCursor::FindAttrs( const SfxItemSet& rSet, bool bNoCollections, + SwDocPositions nStart, SwDocPositions nEnd, + bool& bCancel, FindRanges eFndRngs, + const i18nutil::SearchOptions2* pSearchOpt, + const SfxItemSet* pReplSet, + SwRootFrame const*const pLayout) +{ + // switch off OLE-notifications + SwDoc* pDoc = GetDoc(); + Link aLnk( pDoc->GetOle2Link() ); + pDoc->SetOle2Link( Link() ); + + bool bReplace = ( pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() || + !rSet.Count() ) ) || + (pReplSet && pReplSet->Count()); + bool const bStartUndo = pDoc->GetIDocumentUndoRedo().DoesUndo() && bReplace; + if (bStartUndo) + { + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr ); + } + + SwFindParaAttr aSwFindParaAttr( rSet, bNoCollections, pSearchOpt, + pReplSet, *this, pLayout ); + + sal_uLong nRet = FindAll( aSwFindParaAttr, nStart, nEnd, eFndRngs, bCancel ); + pDoc->SetOle2Link( aLnk ); + if( nRet && bReplace ) + pDoc->getIDocumentState().SetModified(); + + if (bStartUndo) + { + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, nullptr ); + } + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/findcoll.cxx b/sw/source/core/crsr/findcoll.cxx new file mode 100644 index 000000000..8385e0a4c --- /dev/null +++ b/sw/source/core/crsr/findcoll.cxx @@ -0,0 +1,113 @@ +/* -*- 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 + +namespace { + +/// parameters for a search for FormatCollections +struct SwFindParaFormatColl : public SwFindParas +{ + const SwTextFormatColl *pFormatColl, *pReplColl; + SwRootFrame const* m_pLayout; + SwFindParaFormatColl(const SwTextFormatColl& rFormatColl, + const SwTextFormatColl *const pRpColl, + SwRootFrame const*const pLayout) + : pFormatColl( &rFormatColl ) + , pReplColl( pRpColl ) + , m_pLayout(pLayout) + {} + virtual ~SwFindParaFormatColl() {} + virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, std::unique_ptr& xSearchItem) override; + virtual bool IsReplaceMode() const override; +}; + +} + +int SwFindParaFormatColl::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + std::unique_ptr& /*xSearchItem*/) +{ + int nRet = FIND_FOUND; + if( bInReadOnly && pReplColl ) + bInReadOnly = false; + + if (!sw::FindFormatImpl(rCursor, *pFormatColl, fnMove, rRegion, bInReadOnly, m_pLayout)) + nRet = FIND_NOT_FOUND; + else if( pReplColl ) + { + rCursor.GetDoc()->SetTextFormatColl(rCursor, + const_cast(pReplColl), true, false, m_pLayout); + nRet = FIND_NO_RING; + } + return nRet; +} + +bool SwFindParaFormatColl::IsReplaceMode() const +{ + return nullptr != pReplColl; +} + +/// search for Format-Collections +sal_uLong SwCursor::FindFormat( const SwTextFormatColl& rFormatColl, SwDocPositions nStart, + SwDocPositions nEnd, bool& bCancel, + FindRanges eFndRngs, const SwTextFormatColl* pReplFormatColl, + SwRootFrame const*const pLayout) +{ + // switch off OLE-notifications + SwDoc* pDoc = GetDoc(); + Link aLnk( pDoc->GetOle2Link() ); + pDoc->SetOle2Link( Link() ); + + bool const bStartUndo = + pDoc->GetIDocumentUndoRedo().DoesUndo() && pReplFormatColl; + if (bStartUndo) + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, rFormatColl.GetName()); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, pReplFormatColl->GetName()); + + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE_STYLE, + &aRewriter ); + } + + SwFindParaFormatColl aSwFindParaFormatColl(rFormatColl, pReplFormatColl, pLayout); + + sal_uLong nRet = FindAll( aSwFindParaFormatColl, nStart, nEnd, eFndRngs, bCancel ); + pDoc->SetOle2Link( aLnk ); + + if( nRet && pReplFormatColl ) + pDoc->getIDocumentState().SetModified(); + + if (bStartUndo) + { + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/findfmt.cxx b/sw/source/core/crsr/findfmt.cxx new file mode 100644 index 000000000..a04850284 --- /dev/null +++ b/sw/source/core/crsr/findfmt.cxx @@ -0,0 +1,97 @@ +/* -*- 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 + +namespace sw { + +bool FindFormatImpl(SwPaM & rSearchPam, + const SwFormat& rFormat, SwMoveFnCollection const & fnMove, + const SwPaM &rRegion, bool bInReadOnly, + SwRootFrame const*const pLayout) +{ + bool bFound = false; + const bool bSrchForward = &fnMove == &fnMoveForward; + std::unique_ptr pPam(MakeRegion( fnMove, rRegion )); + + // if at beginning/end then move it out of the node + if( bSrchForward + ? pPam->GetPoint()->nContent.GetIndex() == pPam->GetContentNode()->Len() + : !pPam->GetPoint()->nContent.GetIndex() ) + { + if( !(*fnMove.fnNds)( &pPam->GetPoint()->nNode, false )) + { + return false; + } + SwContentNode *pNd = pPam->GetPoint()->nNode.GetNode().GetContentNode(); + pPam->GetPoint()->nContent.Assign( pNd, bSrchForward ? 0 : pNd->Len() ); + } + + bool bFirst = true; + SwContentNode* pNode; + while (nullptr != (pNode = ::GetNode(*pPam, bFirst, fnMove, bInReadOnly, pLayout))) + { + SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode() + ? static_cast(pNode->getLayoutFrame(pLayout)) + : nullptr); + assert(!pLayout || !pNode->IsTextNode() || pFrame); + SwContentNode const& rPropsNode(*(pFrame + ? pFrame->GetTextNodeForParaProps() + : pNode)); + + if (rPropsNode.GetFormatColl() == &rFormat) + { + // if a FormatCollection is found then it is definitely a SwContentNode + + // FORWARD: SPoint at the end, GetMark at the beginning of the node + // BACKWARD: SPoint at the beginning, GetMark at the end of the node + // always: incl. start and incl. end + if (pFrame) + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + *rSearchPam.GetMark() = pFrame->MapViewToModelPos( + TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0)); + } + else + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + pNode->MakeEndIndex( &rSearchPam.GetPoint()->nContent ); + rSearchPam.GetMark()->nContent = 0; + } + + // if backward search, switch point and mark + if( !bSrchForward ) + rSearchPam.Exchange(); + + bFound = true; + break; + } + } + return bFound; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/findtxt.cxx b/sw/source/core/crsr/findtxt.cxx new file mode 100644 index 000000000..1e9ecef2c --- /dev/null +++ b/sw/source/core/crsr/findtxt.cxx @@ -0,0 +1,1183 @@ +/* -*- 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 + +using namespace ::com::sun::star; +using namespace util; + +namespace { + +/// because the Find may be called on the View or the Model, we need an index +/// afflicted by multiple personality disorder +struct AmbiguousIndex +{ +private: + sal_Int32 m_value; + +#ifndef NDEBUG + enum class tags : char { Any, Frame, Model }; + tags m_tag; +#endif + +public: + AmbiguousIndex() : m_value(-1) +#ifndef NDEBUG + , m_tag(tags::Any) +#endif + {} + explicit AmbiguousIndex(sal_Int32 const value +#ifndef NDEBUG + , tags const tag +#endif + ) : m_value(value) +#ifndef NDEBUG + , m_tag(tag) +#endif + {} + + sal_Int32 & GetAnyIndex() { return m_value; } ///< for arithmetic + sal_Int32 const& GetAnyIndex() const { return m_value; } ///< for arithmetic + TextFrameIndex GetFrameIndex() const + { + assert(m_tag != tags::Model); + return TextFrameIndex(m_value); + } + sal_Int32 GetModelIndex() const + { + assert(m_tag != tags::Frame); + return m_value; + } + void SetFrameIndex(TextFrameIndex const value) + { +#ifndef NDEBUG + m_tag = tags::Frame; +#endif + m_value = sal_Int32(value); + } + void SetModelIndex(sal_Int32 const value) + { +#ifndef NDEBUG + m_tag = tags::Model; +#endif + m_value = value; + } + + bool operator ==(AmbiguousIndex const& rOther) const + { + assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag); + return m_value == rOther.m_value; + } + bool operator <=(AmbiguousIndex const& rOther) const + { + assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag); + return m_value <= rOther.m_value; + } + bool operator < (AmbiguousIndex const& rOther) const + { + assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag); + return m_value < rOther.m_value; + } + AmbiguousIndex operator - (AmbiguousIndex const& rOther) const + { + assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag); + return AmbiguousIndex(m_value - rOther.m_value +#ifndef NDEBUG + , std::max(m_tag, rOther.m_tag) +#endif + ); + } +}; + +class MaybeMergedIter +{ + std::optional m_oMergedIter; + SwTextNode const*const m_pNode; + size_t m_HintIndex; + +public: + MaybeMergedIter(SwTextFrame const*const pFrame, SwTextNode const*const pNode) + : m_pNode(pNode) + , m_HintIndex(0) + { + if (pFrame) + { +#if BOOST_VERSION < 105600 + m_oMergedIter.reset(*pFrame); +#else + m_oMergedIter.emplace(*pFrame); +#endif + } + } + + SwTextAttr const* NextAttr(SwTextNode const*& rpNode) + { + if (m_oMergedIter) + { + return m_oMergedIter->NextAttr(&rpNode); + } + if (SwpHints const*const pHints = m_pNode->GetpSwpHints()) + { + if (m_HintIndex < pHints->Count()) + { + rpNode = m_pNode; + return pHints->Get(m_HintIndex++); + } + } + return nullptr; + } +}; + +} + +static OUString +lcl_CleanStr(const SwTextNode& rNd, + SwTextFrame const*const pFrame, + SwRootFrame const*const pLayout, + AmbiguousIndex const nStart, AmbiguousIndex & rEnd, + std::vector &rArr, + bool const bRemoveSoftHyphen, bool const bRemoveCommentAnchors) +{ + OUStringBuffer buf(pLayout ? pFrame->GetText() : rNd.GetText()); + rArr.clear(); + + MaybeMergedIter iter(pLayout ? pFrame : nullptr, pLayout ? nullptr : &rNd); + + AmbiguousIndex nSoftHyphen = nStart; + AmbiguousIndex nHintStart; + bool bNewHint = true; + bool bNewSoftHyphen = true; + const AmbiguousIndex nEnd = rEnd; + std::vector aReplaced; + SwTextNode const* pNextHintNode(nullptr); + SwTextAttr const* pNextHint(iter.NextAttr(pNextHintNode)); + + do + { + if ( bNewHint ) + { + if (pLayout) + { + nHintStart.SetFrameIndex(pNextHint + ? pFrame->MapModelToView(pNextHintNode, pNextHint->GetStart()) + : TextFrameIndex(-1)); + } + else + { + nHintStart.SetModelIndex(pNextHint ? pNextHint->GetStart() : -1); + } + } + + if ( bNewSoftHyphen ) + { + if (pLayout) + { + nSoftHyphen.SetFrameIndex(TextFrameIndex(bRemoveSoftHyphen + ? pFrame->GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex()) + : -1)); + } + else + { + nSoftHyphen.SetModelIndex(bRemoveSoftHyphen + ? rNd.GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex()) + : -1); + } + } + + bNewHint = false; + bNewSoftHyphen = false; + AmbiguousIndex nStt; + + // Check if next stop is a hint. + if (0 <= nHintStart.GetAnyIndex() + && (-1 == nSoftHyphen.GetAnyIndex() || nHintStart < nSoftHyphen) + && nHintStart < nEnd ) + { + nStt = nHintStart; + bNewHint = true; + } + // Check if next stop is a soft hyphen. + else if ( -1 != nSoftHyphen.GetAnyIndex() + && (-1 == nHintStart.GetAnyIndex() || nSoftHyphen < nHintStart) + && nSoftHyphen < nEnd) + { + nStt = nSoftHyphen; + bNewSoftHyphen = true; + } + // If nSoftHyphen == nHintStart, the current hint *must* be a hint with an end. + else if (-1 != nSoftHyphen.GetAnyIndex() && nSoftHyphen == nHintStart) + { + nStt = nSoftHyphen; + bNewHint = true; + bNewSoftHyphen = true; + } + else + break; + + AmbiguousIndex nCurrent(nStt); + nCurrent.GetAnyIndex() -= rArr.size(); + + if ( bNewHint ) + { + if (pNextHint && pNextHint->HasDummyChar() && (nStart <= nStt)) + { + switch (pNextHint->Which()) + { + case RES_TXTATR_FLYCNT: + case RES_TXTATR_FTN: + case RES_TXTATR_FIELD: + case RES_TXTATR_REFMARK: + case RES_TXTATR_TOXMARK: + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + // (1998) they are desired as separators and + // belong not any longer to a word. + // they should also be ignored at a + // beginning/end of a sentence if blank. Those are + // simply removed if first. If at the end, we keep the + // replacement and remove afterwards all at a string's + // end (might be normal 0x7f). + const bool bEmpty = pNextHint->Which() != RES_TXTATR_FIELD + || (static_txtattr_cast(pNextHint)->GetFormatField().GetField()->ExpandField(true, pLayout).isEmpty()); + if ( bEmpty && nStart == nCurrent ) + { + rArr.push_back( nCurrent ); + if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex()) + { + --rEnd.GetAnyIndex(); + } + buf.remove(nCurrent.GetAnyIndex(), 1); + } + else + { + if ( bEmpty ) + aReplaced.push_back( nCurrent ); + buf[nCurrent.GetAnyIndex()] = '\x7f'; + } + } + break; + case RES_TXTATR_ANNOTATION: + { + if( bRemoveCommentAnchors ) + { + rArr.push_back( nCurrent ); + if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex()) + { + --rEnd.GetAnyIndex(); + } + buf.remove( nCurrent.GetAnyIndex(), 1 ); + } + } + break; + default: + OSL_FAIL( "unknown case in lcl_CleanStr" ); + break; + } + } + pNextHint = iter.NextAttr(pNextHintNode); + } + + if ( bNewSoftHyphen ) + { + rArr.push_back( nCurrent ); + + // If the soft hyphen to be removed is past the end of the range we're searching in, + // don't adjust the end. + if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex()) + { + --rEnd.GetAnyIndex(); + } + + buf.remove(nCurrent.GetAnyIndex(), 1); + ++nSoftHyphen.GetAnyIndex(); + } + } + while ( true ); + + for (auto i = aReplaced.size(); i; ) + { + const AmbiguousIndex nTmp = aReplaced[ --i ]; + if (nTmp.GetAnyIndex() == buf.getLength() - 1) + { + buf.truncate(nTmp.GetAnyIndex()); + rArr.push_back( nTmp ); + --rEnd.GetAnyIndex(); + } + } + + return buf.makeStringAndClear(); +} + +static bool DoSearch(SwPaM & rSearchPam, + const i18nutil::SearchOptions2& rSearchOpt, utl::TextSearch& rSText, + SwMoveFnCollection const & fnMove, + bool bSrchForward, bool bRegSearch, bool bChkEmptyPara, bool bChkParaEnd, + AmbiguousIndex & nStart, AmbiguousIndex & nEnd, AmbiguousIndex nTextLen, + SwTextNode const* pNode, SwTextFrame const* pTextFrame, + SwRootFrame const* pLayout, SwPaM* pPam); + +namespace sw { + +// @param xSearchItem allocate in parent so we can do so outside the calling loop +bool FindTextImpl(SwPaM & rSearchPam, + const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes, + utl::TextSearch& rSText, + SwMoveFnCollection const & fnMove, const SwPaM & rRegion, + bool bInReadOnly, SwRootFrame const*const pLayout, + std::unique_ptr& xSearchItem) +{ + if( rSearchOpt.searchString.isEmpty() ) + return false; + + std::unique_ptr pPam = sw::MakeRegion(fnMove, rRegion); + const bool bSrchForward = &fnMove == &fnMoveForward; + SwNodeIndex& rNdIdx = pPam->GetPoint()->nNode; + SwIndex& rContentIdx = pPam->GetPoint()->nContent; + + // If bFound is true then the string was found and is between nStart and nEnd + bool bFound = false; + // start position in text or initial position + bool bFirst = true; + SwContentNode * pNode; + + const bool bRegSearch = SearchAlgorithms2::REGEXP == rSearchOpt.AlgorithmType2; + const bool bChkEmptyPara = bRegSearch && 2 == rSearchOpt.searchString.getLength() && + ( rSearchOpt.searchString == "^$" || + rSearchOpt.searchString == "$^" ); + const bool bChkParaEnd = bRegSearch && rSearchOpt.searchString == "$"; + + if (!xSearchItem) + { + xSearchItem.reset(new SvxSearchItem(SID_SEARCH_ITEM)); // this is a very expensive operation (calling configmgr etc.) + xSearchItem->SetSearchOptions(rSearchOpt); + xSearchItem->SetBackward(!bSrchForward); + } + + // LanguageType eLastLang = 0; + while (nullptr != (pNode = ::GetNode(*pPam, bFirst, fnMove, bInReadOnly, pLayout))) + { + if( pNode->IsTextNode() ) + { + SwTextNode& rTextNode = *pNode->GetTextNode(); + SwTextFrame const*const pFrame(pLayout + ? static_cast(rTextNode.getLayoutFrame(pLayout)) + : nullptr); + assert(!pLayout || pFrame); + AmbiguousIndex nTextLen; + if (pLayout) + { + nTextLen.SetFrameIndex(TextFrameIndex(pFrame->GetText().getLength())); + } + else + { + nTextLen.SetModelIndex(rTextNode.GetText().getLength()); + } + AmbiguousIndex nEnd; + if (pLayout + ? FrameContainsNode(*pFrame, pPam->GetMark()->nNode.GetIndex()) + : rNdIdx == pPam->GetMark()->nNode) + { + if (pLayout) + { + nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->GetMark())); + } + else + { + nEnd.SetModelIndex(pPam->GetMark()->nContent.GetIndex()); + } + } + else + { + if (bSrchForward) + { + nEnd = nTextLen; + } + else + { + if (pLayout) + { + nEnd.SetFrameIndex(TextFrameIndex(0)); + } + else + { + nEnd.SetModelIndex(0); + } + } + } + AmbiguousIndex nStart; + if (pLayout) + { + nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->GetPoint())); + } + else + { + nStart.SetModelIndex(rContentIdx.GetIndex()); + } + + /* #i80135# */ + // if there are SwPostItFields inside our current node text, we + // split the text into separate pieces and search for text inside + // the pieces as well as inside the fields + MaybeMergedIter iter(pLayout ? pFrame : nullptr, pLayout ? nullptr : &rTextNode); + + // count PostItFields by looping over all fields + std::vector> postits; + if (bSearchInNotes) + { + if (!bSrchForward) + { + std::swap(nStart, nEnd); + } + + SwTextNode const* pTemp(nullptr); + while (SwTextAttr const*const pTextAttr = iter.NextAttr(pTemp)) + { + if ( pTextAttr->Which()==RES_TXTATR_ANNOTATION ) + { + AmbiguousIndex aPos; + aPos.SetModelIndex(pTextAttr->GetStart()); + if (pLayout) + { + aPos.SetFrameIndex(pFrame->MapModelToView(pTemp, aPos.GetModelIndex())); + } + if ((nStart <= aPos) && (aPos <= nEnd)) + { + postits.emplace_back(pTextAttr, aPos); + } + } + } + + if (!bSrchForward) + { + std::swap(nStart, nEnd); + } + + } + + SwDocShell *const pDocShell = pNode->GetDoc()->GetDocShell(); + SwWrtShell *const pWrtShell = pDocShell ? pDocShell->GetWrtShell() : nullptr; + SwPostItMgr *const pPostItMgr = pWrtShell ? pWrtShell->GetPostItMgr() : nullptr; + + // If there is an active text edit, then search there. + bool bEndedTextEdit = false; + SdrView* pSdrView = pWrtShell ? pWrtShell->GetDrawView() : nullptr; + if (pSdrView) + { + // If the edited object is not anchored to this node, then ignore it. + SdrObject* pObject = pSdrView->GetTextEditObject(); + if (pObject) + { + if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject)) + { + const SwPosition* pPosition = pFrameFormat->GetAnchor().GetContentAnchor(); + if (!pPosition || (pLayout + ? !FrameContainsNode(*pFrame, pPosition->nNode.GetIndex()) + : pPosition->nNode.GetIndex() != pNode->GetIndex())) + pObject = nullptr; + } + } + + if (pObject) + { + sal_uInt16 nResult = pSdrView->GetTextEditOutlinerView()->StartSearchAndReplace(*xSearchItem); + if (!nResult) + { + // If not found, end the text edit. + pSdrView->SdrEndTextEdit(); + const Point aPoint(pSdrView->GetAllMarkedRect().TopLeft()); + pSdrView->UnmarkAll(); + pWrtShell->CallSetCursor(&aPoint, true); + pWrtShell->Edit(); + bEndedTextEdit = true; + } + else + { + bFound = true; + break; + } + } + } + + if (comphelper::LibreOfficeKit::isActive()) + { + // Writer and editeng selections are not supported in parallel. + SvxSearchItem* pSearchItem = SwView::GetSearchItem(); + // If we just finished search in shape text, don't attempt to do that again. + if (!bEndedTextEdit && !(pSearchItem && pSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL)) + { + // If there are any shapes anchored to this node, search there. + SwPaM aPaM(pNode->GetDoc()->GetNodes().GetEndOfContent()); + if (pLayout) + { + *aPaM.GetPoint() = pFrame->MapViewToModelPos(nStart.GetFrameIndex()); + } + else + { + aPaM.GetPoint()->nNode = rTextNode; + aPaM.GetPoint()->nContent.Assign( + aPaM.GetPoint()->nNode.GetNode().GetTextNode(), + nStart.GetModelIndex()); + } + aPaM.SetMark(); + if (pLayout) + { + aPaM.GetMark()->nNode = (pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pLastNode + : rTextNode) + .GetIndex() + 1; + } + else + { + aPaM.GetMark()->nNode = rTextNode.GetIndex() + 1; + } + aPaM.GetMark()->nContent.Assign(aPaM.GetMark()->nNode.GetNode().GetTextNode(), 0); + if (pNode->GetDoc()->getIDocumentDrawModelAccess().Search(aPaM, *xSearchItem) && pSdrView) + { + if (SdrObject* pObject = pSdrView->GetTextEditObject()) + { + if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject)) + { + const SwPosition* pPosition = pFrameFormat->GetAnchor().GetContentAnchor(); + if (pPosition) + { + // Set search position to the shape's anchor point. + *rSearchPam.GetPoint() = *pPosition; + rSearchPam.GetPoint()->nContent.Assign(pPosition->nNode.GetNode().GetContentNode(), 0); + rSearchPam.SetMark(); + bFound = true; + break; + } + } + } + } + } + } + + // do we need to finish a note? + if (pPostItMgr && pPostItMgr->HasActiveSidebarWin()) + { + if (bSearchInNotes) + { + if (!postits.empty()) + { + if (bSrchForward) + { + postits.erase(postits.begin()); + } + else + { + postits.pop_back(); // hope that's the right one? + } + } + //search inside, finish and put focus back into the doc + if (pPostItMgr->FinishSearchReplace(rSearchOpt,bSrchForward)) + { + bFound = true ; + break; + } + } + else + { + pPostItMgr->SetActiveSidebarWin(nullptr); + } + } + + if (!postits.empty()) + { + // now we have to split + AmbiguousIndex nStartInside; + AmbiguousIndex nEndInside; + sal_Int32 aLoop = bSrchForward ? 0 : postits.size(); + + while ((0 <= aLoop) && (o3tl::make_unsigned(aLoop) <= postits.size())) + { + if (bSrchForward) + { + if (aLoop == 0) + { + nStartInside = nStart; + } + else if (pLayout) + { + nStartInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex() + TextFrameIndex(1)); + } + else + { + nStartInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1); + } + nEndInside = static_cast(aLoop) == postits.size() + ? nEnd + : postits[aLoop].second; + nTextLen = nEndInside - nStartInside; + } + else + { + nStartInside = static_cast(aLoop) == postits.size() + ? nStart + : postits[aLoop].second; + if (aLoop == 0) + { + nEndInside = nEnd; + } + else if (pLayout) + { + nEndInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex() + TextFrameIndex(1)); + } + else + { + nEndInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1); + } + nTextLen = nStartInside - nEndInside; + } + // search inside the text between a note + bFound = DoSearch( rSearchPam, + rSearchOpt, rSText, fnMove, bSrchForward, + bRegSearch, bChkEmptyPara, bChkParaEnd, + nStartInside, nEndInside, nTextLen, + pNode->GetTextNode(), pFrame, pLayout, + pPam.get() ); + if ( bFound ) + break; + else + { + // we should now be right in front of a note, search inside + if (bSrchForward + ? (static_cast(aLoop) != postits.size()) + : (aLoop != 0)) + { + const SwTextAttr *const pTextAttr = bSrchForward + ? postits[aLoop].first + : postits[aLoop - 1].first; + if (pPostItMgr && pPostItMgr->SearchReplace( + static_txtattr_cast(pTextAttr)->GetFormatField(),rSearchOpt,bSrchForward)) + { + bFound = true ; + break; + } + } + } + aLoop = bSrchForward ? aLoop+1 : aLoop-1; + } + } + else + { + // if there is no SwPostItField inside or searching inside notes + // is disabled, we search the whole length just like before + bFound = DoSearch( rSearchPam, + rSearchOpt, rSText, fnMove, bSrchForward, + bRegSearch, bChkEmptyPara, bChkParaEnd, + nStart, nEnd, nTextLen, + pNode->GetTextNode(), pFrame, pLayout, + pPam.get() ); + } + if (bFound) + break; + } + } + return bFound; +} + +} // namespace sw + +bool DoSearch(SwPaM & rSearchPam, + const i18nutil::SearchOptions2& rSearchOpt, utl::TextSearch& rSText, + SwMoveFnCollection const & fnMove, bool bSrchForward, bool bRegSearch, + bool bChkEmptyPara, bool bChkParaEnd, + AmbiguousIndex & nStart, AmbiguousIndex & nEnd, AmbiguousIndex const nTextLen, + SwTextNode const*const pNode, SwTextFrame const*const pFrame, + SwRootFrame const*const pLayout, SwPaM* pPam) +{ + bool bFound = false; + SwNodeIndex& rNdIdx = pPam->GetPoint()->nNode; + OUString sCleanStr; + std::vector aFltArr; + LanguageType eLastLang = LANGUAGE_SYSTEM; + // if the search string contains a soft hyphen, + // we don't strip them from the text: + bool bRemoveSoftHyphens = true; + // if the search string contains a comment, we don't strip them from the text + const bool bRemoveCommentAnchors = rSearchOpt.searchString.indexOf( CH_TXTATR_INWORD ) == -1; + + if ( bRegSearch ) + { + if ( -1 != rSearchOpt.searchString.indexOf("\\xAD") + || -1 != rSearchOpt.searchString.indexOf("\\x{00AD}") + || -1 != rSearchOpt.searchString.indexOf("\\u00AD") + || -1 != rSearchOpt.searchString.indexOf("\\U000000AD") + || -1 != rSearchOpt.searchString.indexOf("\\N{SOFT HYPHEN}")) + { + bRemoveSoftHyphens = false; + } + } + else + { + if ( 1 == rSearchOpt.searchString.getLength() && + CHAR_SOFTHYPHEN == rSearchOpt.searchString.toChar() ) + bRemoveSoftHyphens = false; + } + + if( bSrchForward ) + sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nStart, nEnd, + aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors); + else + sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nEnd, nStart, + aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors); + + std::unique_ptr pScriptIter; + sal_uInt16 nSearchScript = 0; + sal_uInt16 nCurrScript = 0; + + if (SearchAlgorithms2::APPROXIMATE == rSearchOpt.AlgorithmType2) + { + pScriptIter.reset(new SwScriptIterator(sCleanStr, nStart.GetAnyIndex(), bSrchForward)); + nSearchScript = g_pBreakIt->GetRealScriptOfText( rSearchOpt.searchString, 0 ); + } + + const AmbiguousIndex nStringEnd = nEnd; + bool bZeroMatch = false; // zero-length match, i.e. only $ anchor as regex + while ( ((bSrchForward && nStart < nStringEnd) || + (!bSrchForward && nStringEnd < nStart)) && !bZeroMatch ) + { + // SearchAlgorithms_APPROXIMATE works on a per word base so we have to + // provide the text searcher with the correct locale, because it uses + // the break-iterator + if ( pScriptIter ) + { + nEnd.GetAnyIndex() = pScriptIter->GetScriptChgPos(); + nCurrScript = pScriptIter->GetCurrScript(); + if ( nSearchScript == nCurrScript ) + { + const LanguageType eCurrLang = pLayout + ? pFrame->GetLangOfChar(bSrchForward + ? nStart.GetFrameIndex() + : nEnd.GetFrameIndex(), + 0, true) + : pNode->GetLang(bSrchForward + ? nStart.GetModelIndex() + : nEnd.GetModelIndex()); + + if ( eCurrLang != eLastLang ) + { + const lang::Locale aLocale( + g_pBreakIt->GetLocale( eCurrLang ) ); + rSText.SetLocale( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt), aLocale ); + eLastLang = eCurrLang; + } + } + pScriptIter->Next(); + } + AmbiguousIndex nProxyStart = nStart; + AmbiguousIndex nProxyEnd = nEnd; + if( nSearchScript == nCurrScript && + (rSText.*fnMove.fnSearch)( sCleanStr, &nProxyStart.GetAnyIndex(), &nProxyEnd.GetAnyIndex(), nullptr) && + !(bZeroMatch = (nProxyStart == nProxyEnd))) + { + nStart = nProxyStart; + nEnd = nProxyEnd; + // set section correctly + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + + // adjust start and end + if( !aFltArr.empty() ) + { + // if backward search, switch positions temporarily + if (!bSrchForward) { std::swap(nStart, nEnd); } + + AmbiguousIndex nNew = nStart; + for (size_t n = 0; n < aFltArr.size() && aFltArr[ n ] <= nStart; ++n ) + { + ++nNew.GetAnyIndex(); + } + + nStart = nNew; + nNew = nEnd; + for( size_t n = 0; n < aFltArr.size() && aFltArr[ n ] < nEnd; ++n ) + { + ++nNew.GetAnyIndex(); + } + + nEnd = nNew; + // if backward search, switch positions temporarily + if( !bSrchForward ) { std::swap(nStart, nEnd); } + } + if (pLayout) + { + *rSearchPam.GetMark() = pFrame->MapViewToModelPos(nStart.GetFrameIndex()); + *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(nEnd.GetFrameIndex()); + } + else + { + rSearchPam.GetMark()->nContent = nStart.GetModelIndex(); + rSearchPam.GetPoint()->nContent = nEnd.GetModelIndex(); + } + + // if backward search, switch point and mark + if( !bSrchForward ) + rSearchPam.Exchange(); + bFound = true; + break; + } + else + { + nEnd = nProxyEnd; + } + nStart = nEnd; + } + + pScriptIter.reset(); + + if ( bFound ) + return true; + else if ((bChkEmptyPara && !nStart.GetAnyIndex() && !nTextLen.GetAnyIndex()) + || bChkParaEnd) + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + if (pLayout) + { + *rSearchPam.GetPoint() = pFrame->MapViewToModelPos( + bChkParaEnd ? nTextLen.GetFrameIndex() : TextFrameIndex(0)); + } + else + { + rSearchPam.GetPoint()->nContent = bChkParaEnd ? nTextLen.GetModelIndex() : 0; + } + rSearchPam.SetMark(); + const SwNode *const pSttNd = bSrchForward + ? &rSearchPam.GetPoint()->nNode.GetNode() // end of the frame + : &rNdIdx.GetNode(); // keep the bug as-is for now... + /* FIXME: this condition does not work for !bSrchForward backward + * search, it probably never did. (pSttNd != &rNdIdx.GetNode()) + * is never true in this case. */ + if( (bSrchForward || pSttNd != &rNdIdx.GetNode()) && + rSearchPam.Move(fnMoveForward, GoInContent) && + (!bSrchForward || pSttNd != &rSearchPam.GetPoint()->nNode.GetNode()) && + 1 == std::abs(static_cast(rSearchPam.GetPoint()->nNode.GetIndex() - + rSearchPam.GetMark()->nNode.GetIndex()))) + { + // if backward search, switch point and mark + if( !bSrchForward ) + rSearchPam.Exchange(); + return true; + } + } + return bFound; +} + +namespace { + +/// parameters for search and replace in text +struct SwFindParaText : public SwFindParas +{ + const i18nutil::SearchOptions2& m_rSearchOpt; + SwCursor& m_rCursor; + SwRootFrame const* m_pLayout; + utl::TextSearch m_aSText; + bool m_bReplace; + bool m_bSearchInNotes; + + SwFindParaText(const i18nutil::SearchOptions2& rOpt, bool bSearchInNotes, + bool bRepl, SwCursor& rCursor, SwRootFrame const*const pLayout) + : m_rSearchOpt( rOpt ) + , m_rCursor( rCursor ) + , m_pLayout(pLayout) + , m_aSText( utl::TextSearch::UpgradeToSearchOptions2(rOpt) ) + , m_bReplace( bRepl ) + , m_bSearchInNotes( bSearchInNotes ) + {} + virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, std::unique_ptr& xSearchItem) override; + virtual bool IsReplaceMode() const override; + virtual ~SwFindParaText(); +}; + +} + +SwFindParaText::~SwFindParaText() +{ +} + +int SwFindParaText::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + std::unique_ptr& xSearchItem) +{ + if( bInReadOnly && m_bReplace ) + bInReadOnly = false; + + const bool bFnd = sw::FindTextImpl(rCursor, m_rSearchOpt, m_bSearchInNotes, + m_aSText, fnMove, rRegion, bInReadOnly, m_pLayout, xSearchItem); + + if( bFnd && m_bReplace ) // replace string + { + // use replace method in SwDoc + const bool bRegExp(SearchAlgorithms2::REGEXP == m_rSearchOpt.AlgorithmType2); + SwIndex& rSttCntIdx = rCursor.Start()->nContent; + const sal_Int32 nSttCnt = rSttCntIdx.GetIndex(); + // add to shell-cursor-ring so that the regions will be moved eventually + SwPaM* pPrev(nullptr); + if( bRegExp ) + { + pPrev = const_cast(rRegion).GetPrev(); + const_cast(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() ); + } + + std::optional xRepl; + if (bRegExp) + xRepl = sw::ReplaceBackReferences(m_rSearchOpt, &rCursor, m_pLayout); + bool const bReplaced = sw::ReplaceImpl(rCursor, + xRepl ? *xRepl : m_rSearchOpt.replaceString, + bRegExp, *m_rCursor.GetDoc(), m_pLayout); + + m_rCursor.SaveTableBoxContent( rCursor.GetPoint() ); + + if( bRegExp ) + { + // and remove region again + SwPaM* p; + SwPaM* pNext(const_cast(&rRegion)); + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo(const_cast(&rRegion)); + } while( p != pPrev ); + } + if (bRegExp && !bReplaced) + { // fdo#80715 avoid infinite loop if join failed + bool bRet = ((&fnMoveForward == &fnMove) ? &GoNextPara : &GoPrevPara) + (rCursor, fnMove); + (void) bRet; + assert(bRet); // if join failed, next node must be SwTextNode + } + else + rCursor.Start()->nContent = nSttCnt; + return FIND_NO_RING; + } + return bFnd ? FIND_FOUND : FIND_NOT_FOUND; +} + +bool SwFindParaText::IsReplaceMode() const +{ + return m_bReplace; +} + +sal_uLong SwCursor::Find_Text( const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes, + SwDocPositions nStart, SwDocPositions nEnd, + bool& bCancel, FindRanges eFndRngs, bool bReplace, + SwRootFrame const*const pLayout) +{ + // switch off OLE-notifications + SwDoc* pDoc = GetDoc(); + Link aLnk( pDoc->GetOle2Link() ); + pDoc->SetOle2Link( Link() ); + + bool const bStartUndo = pDoc->GetIDocumentUndoRedo().DoesUndo() && bReplace; + if (bStartUndo) + { + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr ); + } + + bool bSearchSel = 0 != (rSearchOpt.searchFlag & SearchFlags::REG_NOT_BEGINOFLINE); + if( bSearchSel ) + eFndRngs = static_cast(eFndRngs | FindRanges::InSel); + SwFindParaText aSwFindParaText(rSearchOpt, bSearchInNotes, bReplace, *this, pLayout); + sal_uLong nRet = FindAll( aSwFindParaText, nStart, nEnd, eFndRngs, bCancel ); + pDoc->SetOle2Link( aLnk ); + if( nRet && bReplace ) + pDoc->getIDocumentState().SetModified(); + + if (bStartUndo) + { + SwRewriter rewriter(MakeUndoReplaceRewriter( + nRet, rSearchOpt.searchString, rSearchOpt.replaceString)); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, & rewriter ); + } + return nRet; +} + +namespace sw { + +bool ReplaceImpl( + SwPaM & rCursor, + OUString const& rReplacement, + bool const bRegExp, + SwDoc & rDoc, + SwRootFrame const*const pLayout) +{ + bool bReplaced(true); + IDocumentContentOperations & rIDCO(rDoc.getIDocumentContentOperations()); +#if 0 + // FIXME there's some problem with multiple redlines here on Undo + std::vector> ranges; + if (rDoc.getIDocumentRedlineAccess().IsRedlineOn() + || !pLayout + || !pLayout->IsHideRedlines() + || sw::GetRanges(ranges, rDoc, rCursor)) + { + bReplaced = rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp); + } + else + { + assert(!ranges.empty()); + assert(ranges.front()->GetPoint()->nNode == ranges.front()->GetMark()->nNode); + bReplaced = rIDCO.ReplaceRange(*ranges.front(), rReplacement, bRegExp); + for (auto it = ranges.begin() + 1; it != ranges.end(); ++it) + { + bReplaced &= rIDCO.DeleteAndJoin(**it); + } + } +#else + IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess()); + if (pLayout && pLayout->IsHideRedlines() + && !rIDRA.IsRedlineOn() // otherwise: ReplaceRange will handle it + && (rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete)) // otherwise: ReplaceRange will DeleteRedline() + { + SwRedlineTable::size_type tmp; + rIDRA.GetRedline(*rCursor.Start(), &tmp); + while (tmp < rIDRA.GetRedlineTable().size()) + { + SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]); + if (*rCursor.End() <= *pRedline->Start()) + { + break; + } + if (*pRedline->End() <= *rCursor.Start()) + { + ++tmp; + continue; + } + if (pRedline->GetType() == RedlineType::Delete) + { + assert(*pRedline->Start() != *pRedline->End()); + // search in hidden layout can't overlap redlines + assert(*rCursor.Start() <= *pRedline->Start() && *pRedline->End() <= *rCursor.End()); + SwPaM pam(*pRedline, nullptr); + bReplaced &= rIDCO.DeleteAndJoin(pam); + } + else + { + ++tmp; + } + } + } + bReplaced &= rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp); +#endif + return bReplaced; +} + +std::optional ReplaceBackReferences(const i18nutil::SearchOptions2& rSearchOpt, + SwPaM *const pPam, SwRootFrame const*const pLayout) +{ + std::optional xRet; + if( pPam && pPam->HasMark() && + SearchAlgorithms2::REGEXP == rSearchOpt.AlgorithmType2 ) + { + SwContentNode const*const pTextNode = pPam->GetContentNode(); + SwContentNode const*const pMarkTextNode = pPam->GetContentNode(false); + if (!pTextNode || !pTextNode->IsTextNode() + || !pMarkTextNode || !pMarkTextNode->IsTextNode()) + { + return xRet; + } + SwTextFrame const*const pFrame(pLayout + ? static_cast(pTextNode->getLayoutFrame(pLayout)) + : nullptr); + const bool bParaEnd = rSearchOpt.searchString == "$" || rSearchOpt.searchString == "^$" || rSearchOpt.searchString == "$^"; + if (bParaEnd || (pLayout + ? sw::FrameContainsNode(*pFrame, pPam->GetMark()->nNode.GetIndex()) + : pTextNode == pMarkTextNode)) + { + utl::TextSearch aSText( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt) ); + SearchResult aResult; + OUString aReplaceStr( rSearchOpt.replaceString ); + if (bParaEnd) + { + OUString const aStr("\\n"); + aResult.subRegExpressions = 1; + aResult.startOffset.realloc(1); + aResult.endOffset.realloc(1); + aResult.startOffset[0] = 0; + aResult.endOffset[0] = aStr.getLength(); + aSText.ReplaceBackReferences( aReplaceStr, aStr, aResult ); + xRet = aReplaceStr; + } + else + { + OUString const aStr(pLayout + ? pFrame->GetText() + : pTextNode->GetTextNode()->GetText()); + AmbiguousIndex nStart; + AmbiguousIndex nEnd; + if (pLayout) + { + nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->Start())); + nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->End())); + } + else + { + nStart.SetModelIndex(pPam->Start()->nContent.GetIndex()); + nEnd.SetModelIndex(pPam->End()->nContent.GetIndex()); + } + if (aSText.SearchForward(aStr, &nStart.GetAnyIndex(), &nEnd.GetAnyIndex(), &aResult)) + { + aSText.ReplaceBackReferences( aReplaceStr, aStr, aResult ); + xRet = aReplaceStr; + } + } + } + } + return xRet; +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/overlayrangesoutline.cxx b/sw/source/core/crsr/overlayrangesoutline.cxx new file mode 100644 index 000000000..1ec83370e --- /dev/null +++ b/sw/source/core/crsr/overlayrangesoutline.cxx @@ -0,0 +1,104 @@ +/* -*- 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 "overlayrangesoutline.hxx" +#include +#include +#include +#include +#include +#include + +namespace +{ + // combine ranges geometrically to a single, ORed polygon + basegfx::B2DPolyPolygon impCombineRangesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges) + { + const sal_uInt32 nCount(rRanges.size()); + basegfx::B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0); a < nCount; a++) + { + const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(rRanges[a])); + + if(0 == a) + { + aRetval.append(aDiscretePolygon); + } + else + { + aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon)); + } + } + + return aRetval; + } +} + +namespace sw::overlay +{ + drawinglayer::primitive2d::Primitive2DContainer OverlayRangesOutline::createOverlayObjectPrimitive2DSequence() + { + drawinglayer::primitive2d::Primitive2DContainer aRetval; + const sal_uInt32 nCount(getRanges().size()); + + if( nCount ) + { + const basegfx::BColor aRGBColor(getBaseColor().getBColor()); + const basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges())); + const drawinglayer::primitive2d::Primitive2DReference aOutline( + new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D( + aPolyPolygon, + aRGBColor)); + + aRetval.resize(1); + aRetval[0] = aOutline; + } + + return aRetval; + } + + OverlayRangesOutline::OverlayRangesOutline( + const Color& rColor, + const std::vector< basegfx::B2DRange >& rRanges ) + : sdr::overlay::OverlayObject(rColor) + , maRanges(rRanges) + { + // no AA for highlight overlays + allowAntiAliase(false); + } + + OverlayRangesOutline::~OverlayRangesOutline() + { + if( getOverlayManager() ) + { + getOverlayManager()->remove(*this); + } + } + + void OverlayRangesOutline::setRanges(const std::vector< basegfx::B2DRange >& rNew) + { + if(rNew != maRanges) + { + maRanges = rNew; + objectChange(); + } + } +} // end of namespace sw::overlay + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/overlayrangesoutline.hxx b/sw/source/core/crsr/overlayrangesoutline.hxx new file mode 100644 index 000000000..3621f7fe6 --- /dev/null +++ b/sw/source/core/crsr/overlayrangesoutline.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_CRSR_OVERLAYRANGESOUTLINE_HXX +#define INCLUDED_SW_SOURCE_CORE_CRSR_OVERLAYRANGESOUTLINE_HXX + +#include +#include + +#include + +namespace sw +{ + namespace overlay + { + class OverlayRangesOutline : public sdr::overlay::OverlayObject + { + // geometry of overlay + std::vector< basegfx::B2DRange > maRanges; + + // geometry creation for OverlayObject + virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override; + + public: + OverlayRangesOutline( + const Color& rColor, + const std::vector< basegfx::B2DRange >& rRanges ); + + virtual ~OverlayRangesOutline() override; + + // data read access + const std::vector< basegfx::B2DRange >& getRanges() const + { + return maRanges; + } + + // data write access + void setRanges(const std::vector< basegfx::B2DRange >& rNew); + }; + } // end of namespace overlay +} // end of namespace sw + +#endif // INCLUDED_SW_SOURCE_CORE_CRSR_OVERLAYRANGESOUTLINE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/pam.cxx b/sw/source/core/crsr/pam.cxx new file mode 100644 index 000000000..d1fccd357 --- /dev/null +++ b/sw/source/core/crsr/pam.cxx @@ -0,0 +1,1171 @@ +/* -*- 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 + +// for the dump "MSC-" compiler +static sal_Int32 GetSttOrEnd( bool bCondition, const SwContentNode& rNd ) +{ + return bCondition ? 0 : rNd.Len(); +} + +SwPosition::SwPosition( const SwNodeIndex & rNodeIndex, const SwIndex & rContent ) + : nNode( rNodeIndex ), nContent( rContent ) +{ +} + +SwPosition::SwPosition( const SwNodeIndex & rNodeIndex ) + : nNode( rNodeIndex ), nContent( nNode.GetNode().GetContentNode() ) +{ +} + +SwPosition::SwPosition( const SwNode& rNode ) + : nNode( rNode ), nContent( nNode.GetNode().GetContentNode() ) +{ +} + +SwPosition::SwPosition( SwContentNode & rNode, const sal_Int32 nOffset ) + : nNode( rNode ), nContent( &rNode, nOffset ) +{ +} + +bool SwPosition::operator<(const SwPosition &rPos) const +{ + if( nNode < rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent < rPos.nContent); + } + else // by convention position with no index is smaller + { + return pOtherReg != nullptr; + } + } + return false; +} + +bool SwPosition::operator>(const SwPosition &rPos) const +{ + if(nNode > rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent > rPos.nContent); + } + else // by convention position with no index is smaller + { + return pThisReg != nullptr; + } + } + return false; +} + +bool SwPosition::operator<=(const SwPosition &rPos) const +{ + if(nNode < rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent <= rPos.nContent); + } + else // by convention position with no index is smaller + { + return pThisReg == nullptr; + } + } + return false; +} + +bool SwPosition::operator>=(const SwPosition &rPos) const +{ + if(nNode > rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent >= rPos.nContent); + } + else // by convention position with no index is smaller + { + return pOtherReg == nullptr; + } + } + return false; +} + +bool SwPosition::operator==(const SwPosition &rPos) const +{ + return (nNode == rPos.nNode) + && (nContent == rPos.nContent); +} + +bool SwPosition::operator!=(const SwPosition &rPos) const +{ + return (nNode != rPos.nNode) + || (nContent != rPos.nContent); +} + +SwDoc * SwPosition::GetDoc() const +{ + return nNode.GetNode().GetDoc(); +} + +void SwPosition::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPosition")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nNode"), BAD_CAST(OString::number(nNode.GetIndex()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nContent"), BAD_CAST(OString::number(nContent.GetIndex()).getStr())); + xmlTextWriterEndElement(pWriter); +} + +std::ostream &operator <<(std::ostream& s, const SwPosition& position) +{ + return s << "SwPosition (node " << position.nNode.GetIndex() << ", offset " << position.nContent.GetIndex() << ")"; +} + +namespace { + +enum CHKSECTION { Chk_Both, Chk_One, Chk_None }; + +} + +static CHKSECTION lcl_TstIdx( sal_uLong nSttIdx, sal_uLong nEndIdx, const SwNode& rEndNd ) +{ + sal_uLong nStt = rEndNd.StartOfSectionIndex(), nEnd = rEndNd.GetIndex(); + CHKSECTION eSec = nStt < nSttIdx && nEnd >= nSttIdx ? Chk_One : Chk_None; + if( nStt < nEndIdx && nEnd >= nEndIdx ) + return( eSec == Chk_One ? Chk_Both : Chk_One ); + return eSec; +} + +static bool lcl_ChkOneRange( CHKSECTION eSec, bool bChkSections, + const SwNode& rBaseEnd, sal_uLong nStt, sal_uLong nEnd ) +{ + if( eSec != Chk_Both ) + return false; + + if( !bChkSections ) + return true; + + // search the surrounding section + const SwNodes& rNds = rBaseEnd.GetNodes(); + const SwNode *pTmp, *pNd = rNds[ nStt ]; + if( !pNd->IsStartNode() ) + pNd = pNd->StartOfSectionNode(); + + if( pNd == rNds[ nEnd ]->StartOfSectionNode() ) + return true; // same StartNode, same section + + // already on a base node => error + if( !pNd->StartOfSectionIndex() ) + return false; + + for (;;) + { + pTmp = pNd->StartOfSectionNode(); + if (pTmp->EndOfSectionNode() == &rBaseEnd ) + break; + pNd = pTmp; + } + + sal_uLong nSttIdx = pNd->GetIndex(), nEndIdx = pNd->EndOfSectionIndex(); + return nSttIdx <= nStt && nStt <= nEndIdx && + nSttIdx <= nEnd && nEnd <= nEndIdx; +} + +/** Check if the given range is inside one of the defined top-level sections. + * + * The top-level sections are Content, AutoText, PostIts, Inserts, and Redlines. + * + * @param bChkSection if true, also check that the given range is inside + * a single second-level section inside any of the + * top-level sections, except for the Content section. + * + * @return if valid range + */ +bool CheckNodesRange( const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, bool bChkSection ) +{ + const SwNodes& rNds = rStt.GetNodes(); + sal_uLong nStt = rStt.GetIndex(), nEnd = rEnd.GetIndex(); + CHKSECTION eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfContent() ); + if( Chk_None != eSec ) + return eSec == Chk_Both; + + eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfAutotext() ); + if( Chk_None != eSec ) + return lcl_ChkOneRange( eSec, bChkSection, + rNds.GetEndOfAutotext(), nStt, nEnd ); + + eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfPostIts() ); + if( Chk_None != eSec ) + return lcl_ChkOneRange( eSec, bChkSection, + rNds.GetEndOfPostIts(), nStt, nEnd ); + + eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfInserts() ); + if( Chk_None != eSec ) + return lcl_ChkOneRange( eSec, bChkSection, + rNds.GetEndOfInserts(), nStt, nEnd ); + + eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfRedlines() ); + if( Chk_None != eSec ) + return lcl_ChkOneRange( eSec, bChkSection, + rNds.GetEndOfRedlines(), nStt, nEnd ); + + return false; // somewhere in between => error +} + +bool GoNext(SwNode* pNd, SwIndex * pIdx, sal_uInt16 nMode ) +{ + if( pNd->IsContentNode() ) + return static_cast(pNd)->GoNext( pIdx, nMode ); + return false; +} + +bool GoPrevious( SwNode* pNd, SwIndex * pIdx, sal_uInt16 nMode ) +{ + if( pNd->IsContentNode() ) + return static_cast(pNd)->GoPrevious( pIdx, nMode ); + return false; +} + +SwContentNode* GoNextNds( SwNodeIndex* pIdx, bool bChk ) +{ + SwNodeIndex aIdx( *pIdx ); + SwContentNode* pNd = aIdx.GetNodes().GoNext( &aIdx ); + if( pNd ) + { + if( bChk && 1 != aIdx.GetIndex() - pIdx->GetIndex() && + !CheckNodesRange( *pIdx, aIdx, true ) ) + pNd = nullptr; + else + *pIdx = aIdx; + } + return pNd; +} + +SwContentNode* GoPreviousNds( SwNodeIndex * pIdx, bool bChk ) +{ + SwNodeIndex aIdx( *pIdx ); + SwContentNode* pNd = SwNodes::GoPrevious( &aIdx ); + if( pNd ) + { + if( bChk && 1 != pIdx->GetIndex() - aIdx.GetIndex() && + !CheckNodesRange( *pIdx, aIdx, true ) ) + pNd = nullptr; + else + *pIdx = aIdx; + } + return pNd; +} + +SwPaM::SwPaM( const SwPosition& rPos, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rPos ) + , m_Bound2( rPos.nNode.GetNode().GetNodes() ) // default initialize + , m_pPoint( &m_Bound1 ) + , m_pMark( m_pPoint ) + , m_bIsInFrontOfLabel( false ) +{ +} + +SwPaM::SwPaM( const SwPosition& rMark, const SwPosition& rPoint, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rMark ) + , m_Bound2( rPoint ) + , m_pPoint( &m_Bound2 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ +} + +SwPaM::SwPaM( const SwNodeIndex& rMark, const SwNodeIndex& rPoint, + long nMarkOffset, long nPointOffset, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rMark ) + , m_Bound2( rPoint ) + , m_pPoint( &m_Bound2 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + if ( nMarkOffset ) + { + m_pMark->nNode += nMarkOffset; + } + if ( nPointOffset ) + { + m_pPoint->nNode += nPointOffset; + } + m_Bound1.nContent.Assign( m_Bound1.nNode.GetNode().GetContentNode(), 0 ); + m_Bound2.nContent.Assign( m_Bound2.nNode.GetNode().GetContentNode(), 0 ); +} + +SwPaM::SwPaM( const SwNode& rMark, const SwNode& rPoint, + long nMarkOffset, long nPointOffset, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rMark ) + , m_Bound2( rPoint ) + , m_pPoint( &m_Bound2 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + if ( nMarkOffset ) + { + m_pMark->nNode += nMarkOffset; + } + if ( nPointOffset ) + { + m_pPoint->nNode += nPointOffset; + } + m_Bound1.nContent.Assign( m_Bound1.nNode.GetNode().GetContentNode(), 0 ); + m_Bound2.nContent.Assign( m_Bound2.nNode.GetNode().GetContentNode(), 0 ); +} + +SwPaM::SwPaM( const SwNodeIndex& rMark, sal_Int32 nMarkContent, + const SwNodeIndex& rPoint, sal_Int32 nPointContent, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rMark ) + , m_Bound2( rPoint ) + , m_pPoint( &m_Bound2 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + m_pPoint->nContent.Assign( rPoint.GetNode().GetContentNode(), nPointContent); + m_pMark ->nContent.Assign( rMark .GetNode().GetContentNode(), nMarkContent ); +} + +SwPaM::SwPaM( const SwNode& rMark, sal_Int32 nMarkContent, + const SwNode& rPoint, sal_Int32 nPointContent, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rMark ) + , m_Bound2( rPoint ) + , m_pPoint( &m_Bound2 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + m_pPoint->nContent.Assign( m_pPoint->nNode.GetNode().GetContentNode(), + nPointContent); + m_pMark ->nContent.Assign( m_pMark ->nNode.GetNode().GetContentNode(), + nMarkContent ); +} + +SwPaM::SwPaM( const SwNode& rNode, sal_Int32 nContent, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rNode ) + , m_Bound2( m_Bound1.nNode.GetNode().GetNodes() ) // default initialize + , m_pPoint( &m_Bound1 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + m_pPoint->nContent.Assign( m_pPoint->nNode.GetNode().GetContentNode(), + nContent ); +} + +SwPaM::SwPaM( const SwNodeIndex& rNodeIdx, sal_Int32 nContent, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rNodeIdx ) + , m_Bound2( rNodeIdx.GetNode().GetNodes() ) // default initialize + , m_pPoint( &m_Bound1 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + m_pPoint->nContent.Assign( rNodeIdx.GetNode().GetContentNode(), nContent ); +} + +SwPaM::~SwPaM() {} + +SwPaM::SwPaM(SwPaM const& rPam, SwPaM *const pRing) + : Ring(pRing) + , m_Bound1( *(rPam.m_pPoint) ) + , m_Bound2( *(rPam.m_pMark) ) + , m_pPoint( &m_Bound1 ), m_pMark( rPam.HasMark() ? &m_Bound2 : m_pPoint ) + , m_bIsInFrontOfLabel( false ) +{ +} + +// @@@ semantic: no copy assignment for super class Ring. +SwPaM &SwPaM::operator=( const SwPaM &rPam ) +{ + if(this == &rPam) + return *this; + + *m_pPoint = *( rPam.m_pPoint ); + if ( rPam.HasMark() ) + { + SetMark(); + *m_pMark = *( rPam.m_pMark ); + } + else + { + DeleteMark(); + } + return *this; +} + +void SwPaM::SetMark() +{ + if (m_pPoint == &m_Bound1) + { + m_pMark = &m_Bound2; + } + else + { + m_pMark = &m_Bound1; + } + (*m_pMark) = *m_pPoint; +} + +#ifdef DBG_UTIL +void SwPaM::Exchange() +{ + if (m_pPoint != m_pMark) + { + SwPosition *pTmp = m_pPoint; + m_pPoint = m_pMark; + m_pMark = pTmp; + } +} +#endif + +/// movement of cursor +bool SwPaM::Move( SwMoveFnCollection const & fnMove, SwGoInDoc fnGo ) +{ + const bool bRet = (*fnGo)( *this, fnMove ); + + m_bIsInFrontOfLabel = false; + return bRet; +} + +namespace sw { + +/** make a new region + + Sets the first SwPaM onto the given SwPaM, or to the beginning or end of a + document. SPoint stays at its position, GetMark will be changed respectively. + + @param fnMove Contains information if beginning or end of document. + @param pOrigRg The given region. + + @return Newly created range, in Ring with parameter pOrigRg. +*/ +std::unique_ptr MakeRegion(SwMoveFnCollection const & fnMove, + const SwPaM & rOrigRg) +{ + std::unique_ptr pPam; + { + pPam.reset(new SwPaM(rOrigRg, const_cast(&rOrigRg))); // given search range + // make sure that SPoint is on the "real" start position + // FORWARD: SPoint always smaller than GetMark + // BACKWARD: SPoint always bigger than GetMark + if( (pPam->GetMark()->*fnMove.fnCmpOp)( *pPam->GetPoint() ) ) + pPam->Exchange(); + } + return pPam; +} + +} // namespace sw + +void SwPaM::Normalize(bool bPointFirst) +{ + if (HasMark()) + if ( ( bPointFirst && *m_pPoint > *m_pMark) || + (!bPointFirst && *m_pPoint < *m_pMark) ) + { + Exchange(); + } +} + +/// return page number at cursor (for reader and page bound frames) +sal_uInt16 SwPaM::GetPageNum( bool bAtPoint, const Point* pLayPos ) +{ + const SwContentFrame* pCFrame; + const SwPageFrame *pPg; + const SwContentNode *pNd ; + const SwPosition* pPos = bAtPoint ? m_pPoint : m_pMark; + + std::pair tmp; + if (pLayPos) + { + tmp.first = *pLayPos; + tmp.second = false; + } + if( nullptr != ( pNd = pPos->nNode.GetNode().GetContentNode() ) && + nullptr != (pCFrame = pNd->getLayoutFrame(pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), pPos, pLayPos ? &tmp : nullptr)) && + nullptr != ( pPg = pCFrame->FindPageFrame() )) + return pPg->GetPhyPageNum(); + return 0; +} + +// form view - see also SwCursorShell::IsCursorReadonly() +static const SwFrame* lcl_FindEditInReadonlyFrame( const SwFrame& rFrame ) +{ + const SwFrame* pRet = nullptr; + + const SwFlyFrame* pFly; + const SwSectionFrame* pSectionFrame; + + if( rFrame.IsInFly() && + (pFly = rFrame.FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() && + pFly->Lower() && + !pFly->Lower()->IsNoTextFrame() ) + { + pRet = pFly; + } + else if ( rFrame.IsInSct() && + nullptr != ( pSectionFrame = rFrame.FindSctFrame() )->GetSection() && + pSectionFrame->GetSection()->IsEditInReadonlyFlag() ) + { + pRet = pSectionFrame; + } + + return pRet; +} + +/// is in protected section or selection surrounds something protected +bool SwPaM::HasReadonlySel( bool bFormView ) const +{ + bool bRet = false; + + const SwContentNode* pNd = GetPoint()->nNode.GetNode().GetContentNode(); + const SwContentFrame *pFrame = nullptr; + if ( pNd != nullptr ) + { + Point aTmpPt; + std::pair const tmp(aTmpPt, false); + pFrame = pNd->getLayoutFrame( + pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + GetPoint(), &tmp); + } + + // Will be set if point are inside edit-in-readonly environment + const SwFrame* pPointEditInReadonlyFrame = nullptr; + if ( pFrame != nullptr + && ( pFrame->IsProtected() + || ( bFormView + && nullptr == ( pPointEditInReadonlyFrame = lcl_FindEditInReadonlyFrame( *pFrame ) ) ) ) ) + { + bRet = true; + } + else if( pNd != nullptr ) + { + const SwSectionNode* pSNd = pNd->GetSectionNode(); + if ( pSNd != nullptr + && ( pSNd->GetSection().IsProtectFlag() + || ( bFormView + && !pSNd->GetSection().IsEditInReadonlyFlag()) ) ) + { + bRet = true; + } + else + { + const SwSectionNode* pParentSectionNd = pNd->FindSectionNode(); + if ( pParentSectionNd != nullptr + && ( pParentSectionNd->GetSection().IsProtectFlag() + || ( bFormView && !pParentSectionNd->GetSection().IsEditInReadonlyFlag()) ) ) + { + bRet = true; + } + } + } + + if ( !bRet + && HasMark() + && GetPoint()->nNode != GetMark()->nNode ) + { + pNd = GetMark()->nNode.GetNode().GetContentNode(); + pFrame = nullptr; + if ( pNd != nullptr ) + { + Point aTmpPt; + std::pair const tmp(aTmpPt, false); + pFrame = pNd->getLayoutFrame( + pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + GetMark(), &tmp); + } + + const SwFrame* pMarkEditInReadonlyFrame = nullptr; + if ( pFrame != nullptr + && ( pFrame->IsProtected() + || ( bFormView + && nullptr == ( pMarkEditInReadonlyFrame = lcl_FindEditInReadonlyFrame( *pFrame ) ) ) ) ) + { + bRet = true; + } + else if( pNd != nullptr ) + { + const SwSectionNode* pSNd = pNd->GetSectionNode(); + if ( pSNd != nullptr + && ( pSNd->GetSection().IsProtectFlag() + || ( bFormView + && !pSNd->GetSection().IsEditInReadonlyFlag()) ) ) + { + bRet = true; + } + } + + if ( !bRet && bFormView ) + { + // Check if start and end frame are inside the _same_ + // edit-in-readonly-environment. Otherwise we better return 'true' + if ( pPointEditInReadonlyFrame != pMarkEditInReadonlyFrame ) + bRet = true; + } + + // check for protected section inside the selection + if( !bRet ) + { + sal_uLong nSttIdx = GetMark()->nNode.GetIndex(), + nEndIdx = GetPoint()->nNode.GetIndex(); + if( nEndIdx <= nSttIdx ) + { + sal_uLong nTmp = nSttIdx; + nSttIdx = nEndIdx; + nEndIdx = nTmp; + } + + // If a protected section should be between nodes, then the + // selection needs to contain already x nodes. + // (TextNd, SectNd, TextNd, EndNd, TextNd ) + if( nSttIdx + 3 < nEndIdx ) + { + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + for( SwSectionFormats::size_type n = rFormats.size(); n; ) + { + const SwSectionFormat* pFormat = rFormats[ --n ]; + if( pFormat->GetProtect().IsContentProtected() ) + { + const SwFormatContent& rContent = pFormat->GetContent(false); + OSL_ENSURE( rContent.GetContentIdx(), "where is the SectionNode?" ); + sal_uLong nIdx = rContent.GetContentIdx()->GetIndex(); + if( nSttIdx <= nIdx && nEndIdx >= nIdx && + rContent.GetContentIdx()->GetNode().GetNodes().IsDocNodes() ) + { + bRet = true; + break; + } + } + } + } + } + } + + const SwDoc *pDoc = GetDoc(); + // Legacy text/combo/checkbox: never return read-only when inside these form fields. + const IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess(); + sw::mark::IFieldmark* pA = GetPoint() ? pMarksAccess->getFieldmarkFor( *GetPoint( ) ) : nullptr; + sw::mark::IFieldmark* pB = GetMark() ? pMarksAccess->getFieldmarkFor( *GetMark( ) ) : pA; + // prevent the user from accidentally deleting the field itself when modifying the text. + const bool bAtStartA = (pA != nullptr) && (pA->GetMarkStart() == *GetPoint()); + const bool bAtStartB = (pB != nullptr) && (pB->GetMarkStart() == *GetMark()); + + if (!bRet) + { + bool bUnhandledMark = pA && pA->GetFieldname( ) == ODF_UNHANDLED; + // Unhandled fieldmarks case shouldn't be edited manually to avoid breaking anything + if ( ( pA == pB ) && bUnhandledMark ) + bRet = true; + else + { + if ((pA == pB) && (bAtStartA != bAtStartB)) + bRet = true; + else if (pA != pB) + { + // If both points are either outside or at marks edges (i.e. selection either + // touches fields, or fully encloses it), then don't disable editing + bRet = !( ( !pA || bAtStartA ) && ( !pB || bAtStartB ) ); + } + if( !bRet && pDoc->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM ) && (pA || pB) ) + { + // Form protection case + bRet = ( pA == nullptr ) || ( pB == nullptr ) || bAtStartA || bAtStartB; + } + } + } + else + { + // Allow editing when the cursor/selection is fully inside of a legacy form field. + bRet = !( pA != nullptr && !bAtStartA && !bAtStartB && pA == pB ); + } + + if (!bRet) + { + // Paragraph Signatures and Classification fields are read-only. + if (pDoc && pDoc->GetEditShell()) + bRet = pDoc->GetEditShell()->IsCursorInParagraphMetadataField(); + } + + if (!bRet && + pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS)) + { + if (pDoc->getIDocumentMarkAccess()->isBookmarkDeleted(*this)) + { + return true; + } + } + if (!bRet && + pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FIELDS)) + { + SwPosition const& rStart(*Start()); + SwPosition const& rEnd(*End()); + for (SwNodeIndex n = rStart.nNode; n <= rEnd.nNode; ++n) + { + if (SwTextNode const*const pNode = n.GetNode().GetTextNode()) + { + if (SwpHints const*const pHints = pNode->GetpSwpHints()) + { + for (size_t i = 0; i < pHints->Count(); ++i) + { + SwTextAttr const*const pHint(pHints->Get(i)); + if (n == rStart.nNode && pHint->GetStart() < rStart.nContent.GetIndex()) + { + continue; // before selection + } + if (n == rEnd.nNode && rEnd.nContent.GetIndex() <= pHint->GetStart()) + { + break; // after selection + } + if (pHint->Which() == RES_TXTATR_FIELD + // placeholders don't work if you can't delete them + && pHint->GetFormatField().GetField()->GetTyp()->Which() != SwFieldIds::JumpEdit) + { + return true; + } + } + } + } + } + } + + return bRet; +} + +/// This function returns the next node in direction of search. If there is no +/// left or the next is out of the area, then a null-pointer is returned. +/// @param rbFirst If then first time request. If so than the position of +/// the PaM must not be changed! +SwContentNode* GetNode( SwPaM & rPam, bool& rbFirst, SwMoveFnCollection const & fnMove, + bool const bInReadOnly, SwRootFrame const*const i_pLayout) +{ + SwRootFrame const*const pLayout(i_pLayout ? i_pLayout : + rPam.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()); + SwContentNode * pNd = nullptr; + if( ((*rPam.GetPoint()).*fnMove.fnCmpOp)( *rPam.GetMark() ) || + ( *rPam.GetPoint() == *rPam.GetMark() && rbFirst ) ) + { + if( rbFirst ) + { + rbFirst = false; + pNd = rPam.GetContentNode(); + if( pNd ) + { + SwContentFrame const*const pFrame(pNd->getLayoutFrame(pLayout)); + if( + ( + nullptr == pFrame || + ( !bInReadOnly && pFrame->IsProtected() ) || + (pFrame->IsTextFrame() && static_cast(pFrame)->IsHiddenNow()) + ) || + ( !bInReadOnly && pNd->FindSectionNode() && + pNd->FindSectionNode()->GetSection().IsProtect() + ) + ) + { + pNd = nullptr; + } + } + } + + if( !pNd ) // is the cursor not on a ContentNode? + { + SwPosition aPos( *rPam.GetPoint() ); + bool bSrchForward = &fnMove == &fnMoveForward; + SwNodes& rNodes = aPos.nNode.GetNodes(); + + // go to next/previous ContentNode + while( true ) + { + if (i_pLayout && aPos.nNode.GetNode().IsTextNode()) + { + auto const fal(sw::GetFirstAndLastNode(*pLayout, aPos.nNode)); + aPos.nNode = bSrchForward ? *fal.second : *fal.first; + } + + pNd = bSrchForward + ? rNodes.GoNextSection( &aPos.nNode, true, !bInReadOnly ) + : SwNodes::GoPrevSection( &aPos.nNode, true, !bInReadOnly ); + if( pNd ) + { + aPos.nContent.Assign( pNd, ::GetSttOrEnd( bSrchForward,*pNd )); + // is the position still in the area + if( (aPos.*fnMove.fnCmpOp)( *rPam.GetMark() ) ) + { + // only in AutoTextSection can be nodes that are hidden + SwContentFrame const*const pFrame(pNd->getLayoutFrame(pLayout)); + if (nullptr == pFrame || + ( !bInReadOnly && pFrame->IsProtected() ) || + ( pFrame->IsTextFrame() && + static_cast(pFrame)->IsHiddenNow())) + { + pNd = nullptr; + continue; + } + *rPam.GetPoint() = aPos; + } + else + pNd = nullptr; // no valid node + break; + } + break; + } + } + } + return pNd; +} + +void GoStartDoc( SwPosition * pPos ) +{ + SwNodes& rNodes = pPos->nNode.GetNodes(); + pPos->nNode = *rNodes.GetEndOfContent().StartOfSectionNode(); + // we always need to find a ContentNode! + SwContentNode* pCNd = rNodes.GoNext( &pPos->nNode ); + if( pCNd ) + pCNd->MakeStartIndex( &pPos->nContent ); +} + +void GoEndDoc( SwPosition * pPos ) +{ + SwNodes& rNodes = pPos->nNode.GetNodes(); + pPos->nNode = rNodes.GetEndOfContent(); + SwContentNode* pCNd = GoPreviousNds( &pPos->nNode, true ); + if( pCNd ) + pCNd->MakeEndIndex( &pPos->nContent ); +} + +void GoStartSection( SwPosition * pPos ) +{ + // jump to section's beginning + SwNodes& rNodes = pPos->nNode.GetNodes(); + sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->nNode ); + if( pPos->nNode < rNodes.GetEndOfContent().StartOfSectionIndex() ) + nLevel--; + do { SwNodes::GoStartOfSection( &pPos->nNode ); } while( nLevel-- ); + + // already on a ContentNode + pPos->nNode.GetNode().GetContentNode()->MakeStartIndex( &pPos->nContent ); +} + +/// go to the end of the current base section +void GoEndSection( SwPosition * pPos ) +{ + // jump to section's beginning/end + SwNodes& rNodes = pPos->nNode.GetNodes(); + sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->nNode ); + if( pPos->nNode < rNodes.GetEndOfContent().StartOfSectionIndex() ) + nLevel--; + do { SwNodes::GoEndOfSection( &pPos->nNode ); } while( nLevel-- ); + + // now on an EndNode, thus to the previous ContentNode + if( GoPreviousNds( &pPos->nNode, true ) ) + pPos->nNode.GetNode().GetContentNode()->MakeEndIndex( &pPos->nContent ); +} + +bool GoInDoc( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + (*fnMove.fnDoc)( rPam.GetPoint() ); + return true; +} + +bool GoInSection( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + (*fnMove.fnSections)( rPam.GetPoint() ); + return true; +} + +bool GoInNode( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + SwContentNode *pNd = (*fnMove.fnNds)( &rPam.GetPoint()->nNode, true ); + if( pNd ) + rPam.GetPoint()->nContent.Assign( pNd, + ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) ); + return pNd; +} + +bool GoInContent( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CHARS )) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoInContentCells( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CELLS )) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoInContentSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CHARS | CRSR_SKIP_HIDDEN ) ) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoInContentCellsSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CELLS | CRSR_SKIP_HIDDEN ) ) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoPrevPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara ) +{ + if( rPam.Move( fnMoveBackward, GoInNode ) ) + { + // always on a ContentNode + SwPosition& rPos = *rPam.GetPoint(); + SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode(); + rPos.nContent.Assign( pNd, + ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) ); + return true; + } + return false; +} + +bool GoCurrPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara ) +{ + SwPosition& rPos = *rPam.GetPoint(); + SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode(); + if( pNd ) + { + const sal_Int32 nOld = rPos.nContent.GetIndex(); + const sal_Int32 nNew = &aPosPara == &fnMoveForward ? 0 : pNd->Len(); + // if already at beginning/end then to the next/previous + if( nOld != nNew ) + { + rPos.nContent.Assign( pNd, nNew ); + return true; + } + } + // move node to next/previous ContentNode + if( ( &aPosPara==&fnParaStart && nullptr != ( pNd = + GoPreviousNds( &rPos.nNode, true ))) || + ( &aPosPara==&fnParaEnd && nullptr != ( pNd = + GoNextNds( &rPos.nNode, true ))) ) + { + rPos.nContent.Assign( pNd, + ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd )); + return true; + } + return false; +} + +bool GoNextPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara ) +{ + if( rPam.Move( fnMoveForward, GoInNode ) ) + { + // always on a ContentNode + SwPosition& rPos = *rPam.GetPoint(); + SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode(); + rPos.nContent.Assign( pNd, + ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) ); + return true; + } + return false; +} + +bool GoCurrSection( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + SwPosition& rPos = *rPam.GetPoint(); + SwPosition aSavePos( rPos ); // position for comparison + (fnMove.fnSection)( &rPos.nNode ); + SwContentNode *pNd; + if( nullptr == ( pNd = rPos.nNode.GetNode().GetContentNode()) && + nullptr == ( pNd = (*fnMove.fnNds)( &rPos.nNode, true )) ) + { + rPos = aSavePos; // do not change cursor + return false; + } + + rPos.nContent.Assign( pNd, + ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) ); + return aSavePos != rPos; +} + +OUString SwPaM::GetText() const +{ + OUStringBuffer aResult; + + SwNodeIndex aNodeIndex = Start()->nNode; + + // The first node can be already the end node. + // Use a "forever" loop with an exit condition in the middle + // of its body, in order to correctly handle all cases. + bool bIsStartNode = true; + for (;;) + { + const bool bIsEndNode = aNodeIndex == End()->nNode; + SwTextNode * pTextNode = aNodeIndex.GetNode().GetTextNode(); + + if (pTextNode != nullptr) + { + if (!bIsStartNode) + { + aResult.append(CH_TXTATR_NEWLINE); // use newline for para break + } + const OUString& aTmpStr = pTextNode->GetText(); + + if (bIsStartNode || bIsEndNode) + { + // Handle corner cases of start/end node(s) + const sal_Int32 nStart = bIsStartNode + ? Start()->nContent.GetIndex() + : 0; + const sal_Int32 nEnd = bIsEndNode + ? End()->nContent.GetIndex() + : aTmpStr.getLength(); + + aResult.append(std::u16string_view(aTmpStr).substr(nStart, nEnd-nStart)); + } + else + { + aResult.append(aTmpStr); + } + } + + if (bIsEndNode) + { + break; + } + + ++aNodeIndex; + bIsStartNode = false; + } + + return aResult.makeStringAndClear(); +} + +void SwPaM::InvalidatePaM() +{ + for (SwNodeIndex index = Start()->nNode; index <= End()->nNode; ++index) + { + if (SwTextNode *const pTextNode = index.GetNode().GetTextNode()) + { + // pretend that the PaM marks changed formatting to reformat... + sal_Int32 const nStart( + index == Start()->nNode ? Start()->nContent.GetIndex() : 0); + // this should work even for length of 0 + SwUpdateAttr const aHint( + nStart, + index == End()->nNode + ? End()->nContent.GetIndex() - nStart + : pTextNode->Len() - nStart, + 0); + pTextNode->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint)); + } + // other node types not invalidated + } +} + +void SwPaM::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPaM")); + + xmlTextWriterStartElement(pWriter, BAD_CAST("point")); + GetPoint()->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + if (HasMark()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("mark")); + GetMark()->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + + xmlTextWriterEndElement(pWriter); +} + +std::ostream &operator <<(std::ostream& s, const SwPaM& pam) +{ + if( pam.HasMark()) + return s << "SwPaM (point " << *pam.GetPoint() << ", mark " << *pam.GetMark() << ")"; + else + return s << "SwPaM (point " << *pam.GetPoint() << ")"; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/paminit.cxx b/sw/source/core/crsr/paminit.cxx new file mode 100644 index 000000000..bbc975acf --- /dev/null +++ b/sw/source/core/crsr/paminit.cxx @@ -0,0 +1,61 @@ +/* -*- 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 + +static const SwMoveFnCollection aFwrd = { + /* fnNd */ &GoNext, + /* fnNds */ &GoNextNds, + /* fnDoc */ &GoEndDoc, + /* fnSections */ &GoEndSection, + /* fnCmpOp */ &SwPosition::operator<, + /* fnGetHint */ &GetFrwrdTextHint, + /* fnSearch */ &utl::TextSearch::SearchForward, + /* fnSection */ &SwNodes::GoStartOfSection +}; + +static const SwMoveFnCollection aBwrd = { + /* fnNd */ &GoPrevious, + /* fnNds */ &GoPreviousNds, + /* fnDoc */ &GoStartDoc, + /* fnSections */ &GoStartSection, + /* fnCmpOp */ &SwPosition::operator>, + /* fnGetHint */ &GetBkwrdTextHint, + /* fnSearch */ &utl::TextSearch::SearchBackward, + /* fnSection */ &SwNodes::GoEndOfSection +}; + +SwMoveFnCollection const & fnParaStart = aFwrd; +SwMoveFnCollection const & fnParaEnd = aBwrd; + +SwMoveFnCollection const & fnSectionStart = aFwrd; +SwMoveFnCollection const & fnSectionEnd = aBwrd; + +SwMoveFnCollection const & fnTableStart = aFwrd; +SwMoveFnCollection const & fnTableEnd = aBwrd; + +SwMoveFnCollection const & fnRegionStart = aFwrd; +SwMoveFnCollection const & fnRegionEnd = aBwrd; + +SwMoveFnCollection const & fnMoveBackward = aBwrd; +SwMoveFnCollection const & fnMoveForward = aFwrd; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/swcrsr.cxx b/sw/source/core/crsr/swcrsr.cxx new file mode 100644 index 000000000..e1237d8cf --- /dev/null +++ b/sw/source/core/crsr/swcrsr.cxx @@ -0,0 +1,2569 @@ +/* -*- 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::i18n; + +static const sal_uInt16 coSrchRplcThreshold = 60000; + +namespace { + +struct PercentHdl +{ + SwDocShell* pDSh; + sal_uLong nActPos; + bool bBack, bNodeIdx; + + PercentHdl( sal_uLong nStt, sal_uLong nEnd, SwDocShell* pSh ) + : pDSh(pSh), nActPos(nStt), bBack(false), bNodeIdx(false) + { + if( ( bBack = (nStt > nEnd )) ) + { + sal_uLong n = nStt; nStt = nEnd; nEnd = n; + } + ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd ); + } + + explicit PercentHdl( const SwPaM& rPam ) + : pDSh( rPam.GetDoc()->GetDocShell() ) + { + sal_uLong nStt, nEnd; + if( rPam.GetPoint()->nNode == rPam.GetMark()->nNode ) + { + bNodeIdx = false; + nStt = rPam.GetMark()->nContent.GetIndex(); + nEnd = rPam.GetPoint()->nContent.GetIndex(); + } + else + { + bNodeIdx = true; + nStt = rPam.GetMark()->nNode.GetIndex(); + nEnd = rPam.GetPoint()->nNode.GetIndex(); + } + nActPos = nStt; + if( ( bBack = (nStt > nEnd )) ) + { + sal_uLong n = nStt; nStt = nEnd; nEnd = n; + } + ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd, pDSh ); + } + + ~PercentHdl() { ::EndProgress( pDSh ); } + + void NextPos( sal_uLong nPos ) const + { ::SetProgressState( bBack ? nActPos - nPos : nPos, pDSh ); } + + void NextPos( SwPosition const & rPos ) const + { + sal_uLong nPos; + if( bNodeIdx ) + nPos = rPos.nNode.GetIndex(); + else + nPos = rPos.nContent.GetIndex(); + ::SetProgressState( bBack ? nActPos - nPos : nPos, pDSh ); + } +}; + +} + +SwCursor::SwCursor( const SwPosition &rPos, SwPaM* pRing ) + : SwPaM( rPos, pRing ) + , m_nRowSpanOffset(0) + , m_nCursorBidiLevel(0) + , m_bColumnSelection(false) +{ +} + +// @@@ semantic: no copy ctor. +SwCursor::SwCursor(SwCursor const& rCpy, SwPaM *const pRing) + : SwPaM( rCpy, pRing ) + , m_nRowSpanOffset(rCpy.m_nRowSpanOffset) + , m_nCursorBidiLevel(rCpy.m_nCursorBidiLevel) + , m_bColumnSelection(rCpy.m_bColumnSelection) +{ +} + +SwCursor::~SwCursor() +{ +} + +SwCursor* SwCursor::Create( SwPaM* pRing ) const +{ + return new SwCursor( *GetPoint(), pRing ); +} + +bool SwCursor::IsReadOnlyAvailable() const +{ + return false; +} + +bool SwCursor::IsSkipOverHiddenSections() const +{ + return true; +} + +bool SwCursor::IsSkipOverProtectSections() const +{ + return !IsReadOnlyAvailable(); +} + +// CreateNewSavePos is virtual so that derived classes of cursor can implement +// own SaveObjects if needed and validate them in the virtual check routines. +void SwCursor::SaveState() +{ + m_vSavePos.emplace_back( *this ); +} + +void SwCursor::RestoreState() +{ + if (!m_vSavePos.empty()) // Robust + { + m_vSavePos.pop_back(); + } +} + +/// determine if point is outside of the node-array's content area +bool SwCursor::IsNoContent() const +{ + return GetPoint()->nNode.GetIndex() < + GetDoc()->GetNodes().GetEndOfExtras().GetIndex(); +} + +bool SwCursor::IsSelOvrCheck(SwCursorSelOverFlags) +{ + return false; +} + +// extracted from IsSelOvr() +bool SwTableCursor::IsSelOvrCheck(SwCursorSelOverFlags eFlags) +{ + SwNodes& rNds = GetDoc()->GetNodes(); + // check sections of nodes array + if( (SwCursorSelOverFlags::CheckNodeSection & eFlags) + && HasMark() ) + { + SwNodeIndex aOldPos( rNds, GetSavePos()->nNode ); + if( !CheckNodesRange( aOldPos, GetPoint()->nNode, true )) + { + GetPoint()->nNode = aOldPos; + GetPoint()->nContent.Assign( GetContentNode(), GetSavePos()->nContent ); + return true; + } + } + return SwCursor::IsSelOvrCheck(eFlags); +} + +namespace +{ + const SwTextAttr* InputFieldAtPos(SwPosition const *pPos) + { + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + if (!pTextNd) + return nullptr; + return pTextNd->GetTextAttrAt(pPos->nContent.GetIndex(), RES_TXTATR_INPUTFIELD, SwTextNode::PARENT); + } +} + +bool SwCursor::IsSelOvr( SwCursorSelOverFlags eFlags ) +{ + SwDoc* pDoc = GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + + bool bSkipOverHiddenSections = IsSkipOverHiddenSections(); + bool bSkipOverProtectSections = IsSkipOverProtectSections(); + + if ( IsSelOvrCheck( eFlags ) ) + { + return true; + } + + if (m_vSavePos.back().nNode != GetPoint()->nNode.GetIndex() && + // (1997) in UI-ReadOnly everything is allowed + ( !pDoc->GetDocShell() || !pDoc->GetDocShell()->IsReadOnlyUI() )) + { + // check new sections + SwNodeIndex& rPtIdx = GetPoint()->nNode; + const SwSectionNode* pSectNd = rPtIdx.GetNode().FindSectionNode(); + if( pSectNd && + ((bSkipOverHiddenSections && pSectNd->GetSection().IsHiddenFlag() ) || + (bSkipOverProtectSections && pSectNd->GetSection().IsProtectFlag() ))) + { + if( !( SwCursorSelOverFlags::ChangePos & eFlags ) ) + { + // then we're already done + RestoreSavePos(); + return true; + } + + // set cursor to new position: + SwNodeIndex aIdx( rPtIdx ); + sal_Int32 nContentPos = m_vSavePos.back().nContent; + bool bGoNxt = m_vSavePos.back().nNode < rPtIdx.GetIndex(); + SwContentNode* pCNd = bGoNxt + ? rNds.GoNextSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections) + : SwNodes::GoPrevSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections); + if( !pCNd && ( SwCursorSelOverFlags::EnableRevDirection & eFlags )) + { + bGoNxt = !bGoNxt; + pCNd = bGoNxt ? rNds.GoNextSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections) + : SwNodes::GoPrevSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections); + } + + bool bIsValidPos = nullptr != pCNd; + const bool bValidNodesRange = bIsValidPos && + ::CheckNodesRange( rPtIdx, aIdx, true ); + if( !bValidNodesRange ) + { + rPtIdx = m_vSavePos.back().nNode; + if( nullptr == ( pCNd = rPtIdx.GetNode().GetContentNode() ) ) + { + bIsValidPos = false; + nContentPos = 0; + rPtIdx = aIdx; + if( nullptr == ( pCNd = rPtIdx.GetNode().GetContentNode() ) ) + { + // then to the beginning of the document + rPtIdx = rNds.GetEndOfExtras(); + pCNd = rNds.GoNext( &rPtIdx ); + } + } + } + + // register ContentIndex: + const sal_Int32 nTmpPos = bIsValidPos ? (bGoNxt ? 0 : pCNd->Len()) : nContentPos; + GetPoint()->nContent.Assign( pCNd, nTmpPos ); + if( !bIsValidPos || !bValidNodesRange || + IsInProtectTable( true ) ) + return true; + } + + // is there a protected section in the section? + if( HasMark() && bSkipOverProtectSections) + { + sal_uLong nSttIdx = GetMark()->nNode.GetIndex(), + nEndIdx = GetPoint()->nNode.GetIndex(); + if( nEndIdx <= nSttIdx ) + { + sal_uLong nTmp = nSttIdx; + nSttIdx = nEndIdx; + nEndIdx = nTmp; + } + + const SwSectionFormats& rFormats = pDoc->GetSections(); + for( SwSectionFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + const SwSectionFormat* pFormat = rFormats[n]; + const SvxProtectItem& rProtect = pFormat->GetProtect(); + if( rProtect.IsContentProtected() ) + { + const SwFormatContent& rContent = pFormat->GetContent(false); + OSL_ENSURE( rContent.GetContentIdx(), "No SectionNode?" ); + sal_uLong nIdx = rContent.GetContentIdx()->GetIndex(); + if( nSttIdx <= nIdx && nEndIdx >= nIdx ) + { + // if it is no linked section then we cannot select it + const SwSection& rSect = *pFormat->GetSection(); + if( SectionType::Content == rSect.GetType() ) + { + RestoreSavePos(); + return true; + } + } + } + } + } + } + + const SwNode* pNd = &GetPoint()->nNode.GetNode(); + if( pNd->IsContentNode() && !dynamic_cast(this) ) + { + const SwContentFrame* pFrame = static_cast(pNd)->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + if ( (SwCursorSelOverFlags::ChangePos & eFlags) //allowed to change position if it's a bad one + && pFrame && pFrame->isFrameAreaDefinitionValid() + && !pFrame->getFrameArea().Height() //a bad zero height position + && !InputFieldAtPos(GetPoint()) ) //unless it's a (vertical) input field + { + // skip to the next/prev valid paragraph with a layout + SwNodeIndex& rPtIdx = GetPoint()->nNode; + bool bGoNxt = m_vSavePos.back().nNode < rPtIdx.GetIndex(); + for (;;) + { + pFrame = bGoNxt ? pFrame->GetNextContentFrame() : pFrame->GetPrevContentFrame(); + if (!pFrame || 0 != pFrame->getFrameArea().Height() ) + break; + } + + // #i72394# skip to prev/next valid paragraph with a layout in case + // the first search did not succeed: + if( !pFrame ) + { + bGoNxt = !bGoNxt; + pFrame = static_cast(pNd)->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + while ( pFrame && 0 == pFrame->getFrameArea().Height() ) + { + pFrame = bGoNxt ? pFrame->GetNextContentFrame() + : pFrame->GetPrevContentFrame(); + } + } + + if (pFrame != nullptr) + { + if (pFrame->IsTextFrame()) + { + SwTextFrame const*const pTextFrame(static_cast(pFrame)); + *GetPoint() = pTextFrame->MapViewToModelPos(TextFrameIndex( + bGoNxt ? 0 : pTextFrame->GetText().getLength())); + } + else + { + assert(pFrame->IsNoTextFrame()); + SwContentNode *const pCNd = const_cast( + static_cast(pFrame)->GetNode()); + assert(pCNd); + + // set this ContentNode as new position + rPtIdx = *pCNd; + // assign corresponding ContentIndex + const sal_Int32 nTmpPos = bGoNxt ? 0 : pCNd->Len(); + GetPoint()->nContent.Assign( pCNd, nTmpPos ); + } + + + if (rPtIdx.GetIndex() == m_vSavePos.back().nNode + && GetPoint()->nContent.GetIndex() == m_vSavePos.back().nContent) + { + // new position equals saved one + // --> trigger restore of saved pos by setting to NULL - see below + pFrame = nullptr; + } + + if ( IsInProtectTable( true ) ) + { + // new position in protected table + // --> trigger restore of saved pos by setting to NULL - see below + pFrame = nullptr; + } + } + } + + if( !pFrame ) + { + DeleteMark(); + RestoreSavePos(); + return true; // we need a frame + } + } + + // is the cursor allowed to be in a protected node? + if( !( SwCursorSelOverFlags::ChangePos & eFlags ) && !IsAtValidPos() ) + { + DeleteMark(); + RestoreSavePos(); + return true; + } + + if( !HasMark() ) + return false; + + // check for invalid sections + if( !::CheckNodesRange( GetMark()->nNode, GetPoint()->nNode, true )) + { + DeleteMark(); + RestoreSavePos(); + return true; // we need a frame + } + + pNd = &GetMark()->nNode.GetNode(); + if( pNd->IsContentNode() + && !static_cast(pNd)->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) + && !dynamic_cast(this) ) + { + DeleteMark(); + RestoreSavePos(); + return true; // we need a frame + } + + // ensure that selection is only inside an InputField or contains the InputField completely + { + const SwTextAttr* pInputFieldTextAttrAtPoint = InputFieldAtPos(GetPoint()); + const SwTextAttr* pInputFieldTextAttrAtMark = InputFieldAtPos(GetMark()); + + if ( pInputFieldTextAttrAtPoint != pInputFieldTextAttrAtMark ) + { + const sal_uLong nRefNodeIdx = + ( SwCursorSelOverFlags::Toggle & eFlags ) + ? m_vSavePos.back().nNode + : GetMark()->nNode.GetIndex(); + const sal_Int32 nRefContentIdx = + ( SwCursorSelOverFlags::Toggle & eFlags ) + ? m_vSavePos.back().nContent + : GetMark()->nContent.GetIndex(); + const bool bIsForwardSelection = + nRefNodeIdx < GetPoint()->nNode.GetIndex() + || ( nRefNodeIdx == GetPoint()->nNode.GetIndex() + && nRefContentIdx < GetPoint()->nContent.GetIndex() ); + + if ( pInputFieldTextAttrAtPoint != nullptr ) + { + const sal_Int32 nNewPointPos = + bIsForwardSelection ? *(pInputFieldTextAttrAtPoint->End()) : pInputFieldTextAttrAtPoint->GetStart(); + SwTextNode* pTextNdAtPoint = GetPoint()->nNode.GetNode().GetTextNode(); + GetPoint()->nContent.Assign( pTextNdAtPoint, nNewPointPos ); + } + + if ( pInputFieldTextAttrAtMark != nullptr ) + { + const sal_Int32 nNewMarkPos = + bIsForwardSelection ? pInputFieldTextAttrAtMark->GetStart() : *(pInputFieldTextAttrAtMark->End()); + SwTextNode* pTextNdAtMark = GetMark()->nNode.GetNode().GetTextNode(); + GetMark()->nContent.Assign( pTextNdAtMark, nNewMarkPos ); + } + } + } + + const SwTableNode* pPtNd = GetPoint()->nNode.GetNode().FindTableNode(); + const SwTableNode* pMrkNd = GetMark()->nNode.GetNode().FindTableNode(); + // both in no or in same table node + if( ( !pMrkNd && !pPtNd ) || pPtNd == pMrkNd ) + return false; + + // in different tables or only mark in table + if( pMrkNd ) + { + // not allowed, so go back to old position + RestoreSavePos(); + // Cursor stays at old position + return true; + } + + // Note: this cannot happen in TableMode + // Only Point in Table then go behind/in front of table + if (SwCursorSelOverFlags::ChangePos & eFlags) + { + bool bSelTop = GetPoint()->nNode.GetIndex() < + ((SwCursorSelOverFlags::Toggle & eFlags) + ? m_vSavePos.back().nNode : GetMark()->nNode.GetIndex()); + + do { // loop for table after table + sal_uLong nSEIdx = pPtNd->EndOfSectionIndex(); + sal_uLong nSttEndTable = nSEIdx + 1; + + if( bSelTop ) + nSttEndTable = rNds[ nSEIdx ]->StartOfSectionIndex() - 1; + + GetPoint()->nNode = nSttEndTable; + const SwNode* pMyNd = &(GetNode()); + + if( pMyNd->IsSectionNode() || ( pMyNd->IsEndNode() && + pMyNd->StartOfSectionNode()->IsSectionNode() ) ) + { + pMyNd = bSelTop + ? SwNodes::GoPrevSection( &GetPoint()->nNode,true,false ) + : rNds.GoNextSection( &GetPoint()->nNode,true,false ); + + /* #i12312# Handle failure of Go{Prev|Next}Section */ + if ( nullptr == pMyNd) + break; + + if( nullptr != ( pPtNd = pMyNd->FindTableNode() )) + continue; + } + + // we permit these + if( pMyNd->IsContentNode() && + ::CheckNodesRange( GetMark()->nNode, + GetPoint()->nNode, true )) + { + // table in table + const SwTableNode* pOuterTableNd = pMyNd->FindTableNode(); + if ( pOuterTableNd ) + pMyNd = pOuterTableNd; + else + { + SwContentNode* pCNd = const_cast(static_cast(pMyNd)); + GetPoint()->nContent.Assign( pCNd, bSelTop ? pCNd->Len() : 0 ); + return false; + } + } + if( bSelTop ) + { + if ( !pMyNd->IsEndNode() ) + break; + pPtNd = pMyNd->FindTableNode(); + } + else + pPtNd = pMyNd->GetTableNode(); + if (!pPtNd) + break; + } while( true ); + } + + // stay on old position + RestoreSavePos(); + return true; +} + +bool SwCursor::IsInProtectTable( bool bMove, bool bChgCursor ) +{ + SwContentNode* pCNd = GetContentNode(); + if( !pCNd ) + return false; + + // No table, no protected cell: + const SwTableNode* pTableNode = pCNd->FindTableNode(); + if ( !pTableNode ) + return false; + + // Current position == last save position? + if (m_vSavePos.back().nNode == GetPoint()->nNode.GetIndex()) + return false; + + // Check for covered cell: + bool bInCoveredCell = false; + const SwStartNode* pTmpSttNode = pCNd->FindTableBoxStartNode(); + OSL_ENSURE( pTmpSttNode, "In table, therefore I expect to get a SwTableBoxStartNode" ); + const SwTableBox* pBox = pTmpSttNode ? pTableNode->GetTable().GetTableBox( pTmpSttNode->GetIndex() ) : nullptr; //Robust #151355 + if ( pBox && pBox->getRowSpan() < 1 ) // Robust #151270 + bInCoveredCell = true; + + // Positions of covered cells are not acceptable: + if ( !bInCoveredCell ) + { + // Position not protected? + if ( !pCNd->IsProtect() ) + return false; + + // Cursor in protected cells allowed? + if ( IsReadOnlyAvailable() ) + return false; + } + + // If we reach this point, we are in a protected or covered table cell! + + if( !bMove ) + { + if( bChgCursor ) + // restore the last save position + RestoreSavePos(); + + return true; // Cursor stays at old position + } + + // We are in a protected table cell. Traverse top to bottom? + if (m_vSavePos.back().nNode < GetPoint()->nNode.GetIndex()) + { + // search next valid box + // if there is another StartNode after the EndNode of a cell then + // there is another cell + SwNodeIndex aCellStt( *GetNode().FindTableBoxStartNode()->EndOfSectionNode(), 1 ); + bool bProt = true; +GoNextCell: + for (;;) { + if( !aCellStt.GetNode().IsStartNode() ) + break; + ++aCellStt; + if( nullptr == ( pCNd = aCellStt.GetNode().GetContentNode() )) + pCNd = aCellStt.GetNodes().GoNext( &aCellStt ); + bProt = pCNd->IsProtect(); + if( !bProt ) + break; + aCellStt.Assign( *pCNd->FindTableBoxStartNode()->EndOfSectionNode(), 1 ); + } + +SetNextCursor: + if( !bProt ) // found free cell + { + GetPoint()->nNode = aCellStt; + SwContentNode* pTmpCNd = GetContentNode(); + if( pTmpCNd ) + { + GetPoint()->nContent.Assign( pTmpCNd, 0 ); + return false; + } + return IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); + } + // end of table, so go to next node + ++aCellStt; + SwNode* pNd = &aCellStt.GetNode(); + if( pNd->IsEndNode() || HasMark()) + { + // if only table in FlyFrame or SSelection then stay on old position + if( bChgCursor ) + RestoreSavePos(); + return true; + } + else if( pNd->IsTableNode() && aCellStt++ ) + goto GoNextCell; + + bProt = false; // index is now on a content node + goto SetNextCursor; + } + + // search for the previous valid box + { + // if there is another EndNode in front of the StartNode than there + // exists a previous cell + SwNodeIndex aCellStt( *GetNode().FindTableBoxStartNode(), -1 ); + SwNode* pNd; + bool bProt = true; +GoPrevCell: + for (;;) { + pNd = &aCellStt.GetNode(); + if( !pNd->IsEndNode() ) + break; + aCellStt.Assign( *pNd->StartOfSectionNode(), +1 ); + if( nullptr == ( pCNd = aCellStt.GetNode().GetContentNode() )) + pCNd = pNd->GetNodes().GoNext( &aCellStt ); + bProt = pCNd->IsProtect(); + if( !bProt ) + break; + aCellStt.Assign( *pNd->FindTableBoxStartNode(), -1 ); + } + +SetPrevCursor: + if( !bProt ) // found free cell + { + GetPoint()->nNode = aCellStt; + SwContentNode* pTmpCNd = GetContentNode(); + if( pTmpCNd ) + { + GetPoint()->nContent.Assign( pTmpCNd, 0 ); + return false; + } + return IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); + } + // at the beginning of a table, so go to next node + --aCellStt; + pNd = &aCellStt.GetNode(); + if( pNd->IsStartNode() || HasMark() ) + { + // if only table in FlyFrame or SSelection then stay on old position + if( bChgCursor ) + RestoreSavePos(); + return true; + } + else if( pNd->StartOfSectionNode()->IsTableNode() && aCellStt-- ) + goto GoPrevCell; + + bProt = false; // index is now on a content node + goto SetPrevCursor; + } +} + +/// Return if cursor can be set to this position +bool SwCursor::IsAtValidPos( bool bPoint ) const +{ + const SwDoc* pDoc = GetDoc(); + const SwPosition* pPos = bPoint ? GetPoint() : GetMark(); + const SwNode* pNd = &pPos->nNode.GetNode(); + + if( pNd->IsContentNode() && !static_cast(pNd)->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) && + !dynamic_cast(this) ) + { + return false; + } + + // #i45129# - in UI-ReadOnly everything is allowed + if( !pDoc->GetDocShell() || !pDoc->GetDocShell()->IsReadOnlyUI() ) + return true; + + const bool bCursorInReadOnly = IsReadOnlyAvailable(); + if( !bCursorInReadOnly && pNd->IsProtect() ) + return false; + + const SwSectionNode* pSectNd = pNd->FindSectionNode(); + return !pSectNd + || !(pSectNd->GetSection().IsHiddenFlag() || + ( !bCursorInReadOnly && pSectNd->GetSection().IsProtectFlag() )); +} + +void SwCursor::SaveTableBoxContent( const SwPosition* ) {} + +/// set range for search in document +SwMoveFnCollection const & SwCursor::MakeFindRange( SwDocPositions nStart, + SwDocPositions nEnd, SwPaM* pRange ) const +{ + pRange->SetMark(); + FillFindPos( nStart, *pRange->GetMark() ); + FillFindPos( nEnd, *pRange->GetPoint() ); + + // determine direction of search + return ( SwDocPositions::Start == nStart || SwDocPositions::OtherStart == nStart || + (SwDocPositions::Curr == nStart && + (SwDocPositions::End == nEnd || SwDocPositions::OtherEnd == nEnd ) )) + ? fnMoveForward : fnMoveBackward; +} + +static sal_uLong lcl_FindSelection( SwFindParas& rParas, SwCursor* pCurrentCursor, + SwMoveFnCollection const & fnMove, SwCursor*& pFndRing, + SwPaM& aRegion, FindRanges eFndRngs, + bool bInReadOnly, bool& bCancel ) +{ + SwDoc* pDoc = pCurrentCursor->GetDoc(); + bool const bDoesUndo = pDoc->GetIDocumentUndoRedo().DoesUndo(); + int nFndRet = 0; + sal_uLong nFound = 0; + const bool bSrchBkwrd = &fnMove == &fnMoveBackward; + SwPaM *pTmpCursor = pCurrentCursor, *pSaveCursor = pCurrentCursor; + std::unique_ptr xSearchItem; + + // only create progress bar for ShellCursor + bool bIsUnoCursor = dynamic_cast(pCurrentCursor) != nullptr; + std::unique_ptr pPHdl; + sal_uInt16 nCursorCnt = 0; + if( FindRanges::InSel & eFndRngs ) + { + while( pCurrentCursor != ( pTmpCursor = pTmpCursor->GetNext() )) + ++nCursorCnt; + if( nCursorCnt && !bIsUnoCursor ) + pPHdl.reset(new PercentHdl( 0, nCursorCnt, pDoc->GetDocShell() )); + } + else + pSaveCursor = pSaveCursor->GetPrev(); + + bool bEnd = false; + do { + aRegion.SetMark(); + // independent from search direction: SPoint is always bigger than mark + // if the search area is valid + SwPosition *pSttPos = aRegion.GetMark(), + *pEndPos = aRegion.GetPoint(); + *pSttPos = *pTmpCursor->Start(); + *pEndPos = *pTmpCursor->End(); + if( bSrchBkwrd ) + aRegion.Exchange(); + + if( !nCursorCnt && !pPHdl && !bIsUnoCursor ) + pPHdl.reset(new PercentHdl( aRegion )); + + // as long as found and not at same position + while( *pSttPos <= *pEndPos ) + { + nFndRet = rParas.DoFind(*pCurrentCursor, fnMove, aRegion, bInReadOnly, xSearchItem); + if( 0 == nFndRet || + ( pFndRing && + *pFndRing->GetPoint() == *pCurrentCursor->GetPoint() && + *pFndRing->GetMark() == *pCurrentCursor->GetMark() )) + break; + if( !( FIND_NO_RING & nFndRet )) + { + // #i24084# - create ring similar to the one in CreateCursor + SwCursor* pNew = pCurrentCursor->Create( pFndRing ); + if( !pFndRing ) + pFndRing = pNew; + + pNew->SetMark(); + *pNew->GetMark() = *pCurrentCursor->GetMark(); + } + + ++nFound; + + if( !( eFndRngs & FindRanges::InSelAll) ) + { + bEnd = true; + break; + } + + if ((coSrchRplcThreshold == nFound) + && pDoc->GetIDocumentUndoRedo().DoesUndo() + && rParas.IsReplaceMode()) + { + short nRet = pCurrentCursor->MaxReplaceArived(); + if( RET_YES == nRet ) + { + pDoc->GetIDocumentUndoRedo().DelAllUndoObj(); + pDoc->GetIDocumentUndoRedo().DoUndo(false); + } + else + { + bEnd = true; + if(RET_CANCEL == nRet) + { + bCancel = true; + } + break; + } + } + + if( bSrchBkwrd ) + // move pEndPos in front of the found area + *pEndPos = *pCurrentCursor->Start(); + else + // move pSttPos behind the found area + *pSttPos = *pCurrentCursor->End(); + + if( *pSttPos == *pEndPos ) + // in area but at the end => done + break; + + if( !nCursorCnt && pPHdl ) + { + pPHdl->NextPos( *aRegion.GetMark() ); + } + } + + if( bEnd || !( eFndRngs & ( FindRanges::InSelAll | FindRanges::InSel )) ) + break; + + pTmpCursor = pTmpCursor->GetNext(); + if( nCursorCnt && pPHdl ) + { + pPHdl->NextPos( ++pPHdl->nActPos ); + } + + } while( pTmpCursor != pSaveCursor && pTmpCursor->GetNext() != pTmpCursor); + + if( nFound && !pFndRing ) // if no ring should be created + pFndRing = pCurrentCursor->Create(); + + pDoc->GetIDocumentUndoRedo().DoUndo(bDoesUndo); + return nFound; +} + +static bool lcl_MakeSelFwrd( const SwNode& rSttNd, const SwNode& rEndNd, + SwPaM& rPam, bool bFirst ) +{ + if( rSttNd.GetIndex() + 1 == rEndNd.GetIndex() ) + return false; + + SwNodes& rNds = rPam.GetDoc()->GetNodes(); + rPam.DeleteMark(); + SwContentNode* pCNd; + if( !bFirst ) + { + rPam.GetPoint()->nNode = rSttNd; + pCNd = rNds.GoNext( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeStartIndex( &rPam.GetPoint()->nContent ); + } + else if( rSttNd.GetIndex() > rPam.GetPoint()->nNode.GetIndex() || + rPam.GetPoint()->nNode.GetIndex() >= rEndNd.GetIndex() ) + // not in this section + return false; + + rPam.SetMark(); + rPam.GetPoint()->nNode = rEndNd; + pCNd = SwNodes::GoPrevious( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeEndIndex( &rPam.GetPoint()->nContent ); + + return *rPam.GetMark() < *rPam.GetPoint(); +} + +static bool lcl_MakeSelBkwrd( const SwNode& rSttNd, const SwNode& rEndNd, + SwPaM& rPam, bool bFirst ) +{ + if( rEndNd.GetIndex() + 1 == rSttNd.GetIndex() ) + return false; + + SwNodes& rNds = rPam.GetDoc()->GetNodes(); + rPam.DeleteMark(); + SwContentNode* pCNd; + if( !bFirst ) + { + rPam.GetPoint()->nNode = rSttNd; + pCNd = SwNodes::GoPrevious( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeEndIndex( &rPam.GetPoint()->nContent ); + } + else if( rEndNd.GetIndex() > rPam.GetPoint()->nNode.GetIndex() || + rPam.GetPoint()->nNode.GetIndex() >= rSttNd.GetIndex() ) + return false; // not in this section + + rPam.SetMark(); + rPam.GetPoint()->nNode = rEndNd; + pCNd = rNds.GoNext( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeStartIndex( &rPam.GetPoint()->nContent ); + + return *rPam.GetPoint() < *rPam.GetMark(); +} + +// this method "searches" for all use cases because in SwFindParas is always the +// correct parameters and respective search method +sal_uLong SwCursor::FindAll( SwFindParas& rParas, + SwDocPositions nStart, SwDocPositions nEnd, + FindRanges eFndRngs, bool& bCancel ) +{ + bCancel = false; + SwCursorSaveState aSaveState( *this ); + + // create region without adding it to the ring + SwPaM aRegion( *GetPoint() ); + SwMoveFnCollection const & fnMove = MakeFindRange( nStart, nEnd, &aRegion ); + + sal_uLong nFound = 0; + const bool bMvBkwrd = &fnMove == &fnMoveBackward; + bool bInReadOnly = IsReadOnlyAvailable(); + std::unique_ptr xSearchItem; + + SwCursor* pFndRing = nullptr; + SwNodes& rNds = GetDoc()->GetNodes(); + + // search in sections? + if( FindRanges::InSel & eFndRngs ) + { + // if string was not found in region then get all sections (cursors + // stays unchanged) + if( 0 == ( nFound = lcl_FindSelection( rParas, this, fnMove, + pFndRing, aRegion, eFndRngs, + bInReadOnly, bCancel ) )) + return nFound; + + // found string at least once; it's all in new Cursor ring thus delete old one + while( GetNext() != this ) + delete GetNext(); + + *GetPoint() = *pFndRing->GetPoint(); + SetMark(); + *GetMark() = *pFndRing->GetMark(); + pFndRing->GetRingContainer().merge( GetRingContainer() ); + delete pFndRing; + } + else if( FindRanges::InOther & eFndRngs ) + { + // put cursor as copy of current into ring + // chaining points always to first created, so forward + SwCursor* pSav = Create( this ); // save the current cursor + + // if already outside of body text search from this position or start at + // 1. base section + if( bMvBkwrd + ? lcl_MakeSelBkwrd( rNds.GetEndOfExtras(), + *rNds.GetEndOfPostIts().StartOfSectionNode(), + *this, rNds.GetEndOfExtras().GetIndex() >= + GetPoint()->nNode.GetIndex() ) + : lcl_MakeSelFwrd( *rNds.GetEndOfPostIts().StartOfSectionNode(), + rNds.GetEndOfExtras(), *this, + rNds.GetEndOfExtras().GetIndex() >= + GetPoint()->nNode.GetIndex() )) + { + nFound = lcl_FindSelection( rParas, this, fnMove, pFndRing, + aRegion, eFndRngs, bInReadOnly, bCancel ); + } + + if( !nFound ) + { + // put back the old one + *GetPoint() = *pSav->GetPoint(); + if( pSav->HasMark() ) + { + SetMark(); + *GetMark() = *pSav->GetMark(); + } + else + DeleteMark(); + return 0; + } + + if( !( FindRanges::InSelAll & eFndRngs )) + { + // there should only be a single one, thus add it + // independent from search direction: SPoint is always bigger than + // mark if the search area is valid + *GetPoint() = *pFndRing->GetPoint(); + SetMark(); + *GetMark() = *pFndRing->GetMark(); + } + else + { + // found string at least once; it's all in new Cursor ring thus delete old one + while( GetNext() != this ) + delete GetNext(); + + *GetPoint() = *pFndRing->GetPoint(); + SetMark(); + *GetMark() = *pFndRing->GetMark(); + pFndRing->GetRingContainer().merge( GetRingContainer() ); + } + delete pFndRing; + } + else if( FindRanges::InSelAll & eFndRngs ) + { + SwCursor* pSav = Create( this ); // save the current cursor + + const SwNode* pSttNd = ( FindRanges::InBodyOnly & eFndRngs ) + ? rNds.GetEndOfContent().StartOfSectionNode() + : rNds.GetEndOfPostIts().StartOfSectionNode(); + + if( bMvBkwrd + ? lcl_MakeSelBkwrd( rNds.GetEndOfContent(), *pSttNd, *this, false ) + : lcl_MakeSelFwrd( *pSttNd, rNds.GetEndOfContent(), *this, false )) + { + nFound = lcl_FindSelection( rParas, this, fnMove, pFndRing, + aRegion, eFndRngs, bInReadOnly, bCancel ); + } + + if( !nFound ) + { + // put back the old one + *GetPoint() = *pSav->GetPoint(); + if( pSav->HasMark() ) + { + SetMark(); + *GetMark() = *pSav->GetMark(); + } + else + DeleteMark(); + return 0; + } + while( GetNext() != this ) + delete GetNext(); + + *GetPoint() = *pFndRing->GetPoint(); + SetMark(); + *GetMark() = *pFndRing->GetMark(); + pFndRing->GetRingContainer().merge( GetRingContainer() ); + delete pFndRing; + } + else + { + // if a GetMark is set then keep the GetMark of the found object + // This allows spanning an area with this search. + SwPosition aMarkPos( *GetMark() ); + const bool bMarkPos = HasMark() && (eFndRngs == FindRanges::InBody); + + nFound = rParas.DoFind(*this, fnMove, aRegion, bInReadOnly, xSearchItem) ? 1 : 0; + if (0 != nFound && bMarkPos) + *GetMark() = aMarkPos; + } + + if( nFound && SwCursor::IsSelOvr( SwCursorSelOverFlags::Toggle ) ) + nFound = 0; + return nFound; +} + +void SwCursor::FillFindPos( SwDocPositions ePos, SwPosition& rPos ) const +{ + bool bIsStart = true; + SwContentNode* pCNd = nullptr; + SwNodes& rNds = GetDoc()->GetNodes(); + + switch( ePos ) + { + case SwDocPositions::Start: + rPos.nNode = *rNds.GetEndOfContent().StartOfSectionNode(); + pCNd = rNds.GoNext( &rPos.nNode ); + break; + case SwDocPositions::End: + rPos.nNode = rNds.GetEndOfContent(); + pCNd = SwNodes::GoPrevious( &rPos.nNode ); + bIsStart = false; + break; + case SwDocPositions::OtherStart: + rPos.nNode = *rNds[ sal_uLong(0) ]; + pCNd = rNds.GoNext( &rPos.nNode ); + break; + case SwDocPositions::OtherEnd: + rPos.nNode = *rNds.GetEndOfContent().StartOfSectionNode(); + pCNd = SwNodes::GoPrevious( &rPos.nNode ); + bIsStart = false; + break; + default: + rPos = *GetPoint(); + } + + if( pCNd ) + { + rPos.nContent.Assign( pCNd, bIsStart ? 0 : pCNd->Len() ); + } +} + +short SwCursor::MaxReplaceArived() +{ + return RET_YES; +} + +namespace { + +struct HideWrapper +{ + // either the frame's text or the node's text (possibly pre-filtered) + OUString const* m_pText; + // this is actually a TextFrameIndex but all of the i18n code uses sal_Int32 + sal_Int32 m_nPtIndex; + // if mapping is needed, use this frame + SwTextFrame * m_pFrame; + // input in the constructor, output (via mapping) in the destructor + SwTextNode *& m_rpTextNode; + sal_Int32 & m_rPtPos; + + HideWrapper(SwRootFrame const*const pLayout, + SwTextNode *& rpTextNode, sal_Int32 & rPtPos, + OUString const*const pFilteredNodeText = nullptr) + : m_pText(pFilteredNodeText) + , m_pFrame(nullptr) + , m_rpTextNode(rpTextNode) + , m_rPtPos(rPtPos) + { + if (pLayout && pLayout->IsHideRedlines()) + { + m_pFrame = static_cast(rpTextNode->getLayoutFrame(pLayout)); + m_pText = &m_pFrame->GetText(); + m_nPtIndex = sal_Int32(m_pFrame->MapModelToView(rpTextNode, rPtPos)); + } + else + { + if (!m_pText) + { + m_pText = &rpTextNode->GetText(); + } + m_nPtIndex = rPtPos; + } + } + ~HideWrapper() + { + AssignBack(m_rpTextNode, m_rPtPos); + } + void AssignBack(SwTextNode *& rpTextNode, sal_Int32 & rPtPos) + { + if (0 <= m_nPtIndex && m_pFrame) + { + std::pair const pos( + m_pFrame->MapViewToModel(TextFrameIndex(m_nPtIndex))); + rpTextNode = pos.first; + rPtPos = pos.second; + } + else + { + rPtPos = m_nPtIndex; + } + } +}; + +} // namespace + +bool SwCursor::SelectWord( SwViewShell const * pViewShell, const Point* pPt ) +{ + return SelectWordWT( pViewShell, WordType::ANYWORD_IGNOREWHITESPACES, pPt ); +} + +bool SwCursor::IsStartWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + HideWrapper w(pLayout, pTextNd, nPtPos); + + bRet = g_pBreakIt->GetBreakIter()->isBeginWord( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos )), + nWordType ); + } + return bRet; +} + +bool SwCursor::IsEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + HideWrapper w(pLayout, pTextNd, nPtPos); + + bRet = g_pBreakIt->GetBreakIter()->isEndWord( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + nWordType ); + + } + return bRet; +} + +bool SwCursor::IsInWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pTextNd, nPtPos); + + Boundary aBoundary = g_pBreakIt->GetBreakIter()->getWordBoundary( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + nWordType, + true ); + + bRet = aBoundary.startPos != aBoundary.endPos && + aBoundary.startPos <= w.m_nPtIndex && + w.m_nPtIndex <= aBoundary.endPos; + w.m_nPtIndex = aBoundary.startPos; // hack: convert startPos back... + } + if(bRet) + { + const CharClass& rCC = GetAppCharClass(); + bRet = rCC.isLetterNumeric(pTextNd->GetText(), nPtPos); + } + } + return bRet; +} + +bool SwCursor::IsStartEndSentence(bool bEnd, SwRootFrame const*const pLayout) const +{ + bool bRet = bEnd ? + GetContentNode() && GetPoint()->nContent == GetContentNode()->Len() : + GetPoint()->nContent.GetIndex() == 0; + + if ((pLayout != nullptr && pLayout->IsHideRedlines()) || !bRet) + { + SwCursor aCursor(*GetPoint(), nullptr); + SwPosition aOrigPos = *aCursor.GetPoint(); + aCursor.GoSentence(bEnd ? SwCursor::END_SENT : SwCursor::START_SENT, pLayout); + bRet = aOrigPos == *aCursor.GetPoint(); + } + return bRet; +} + +bool SwCursor::GoStartWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pTextNd, nPtPos); + + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->getWordBoundary( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + nWordType, + false ).startPos; + } + + if (nPtPos < pTextNd->GetText().getLength() && nPtPos >= 0) + { + *GetPoint() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::GoEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pTextNd, nPtPos); + + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->getWordBoundary( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + nWordType, + true ).endPos; + } + + if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0 && + GetPoint()->nContent.GetIndex() != nPtPos ) + { + *GetPoint() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::GoNextWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pTextNd, nPtPos); + + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->nextWord( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang(nPtPos, 1) ), + nWordType ).startPos; + } + + if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0) + { + *GetPoint() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::GoPrevWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pTextNd, nPtPos); + + const sal_Int32 nPtStart = w.m_nPtIndex; + if (w.m_nPtIndex) + { + --w.m_nPtIndex; + w.AssignBack(pTextNd, nPtPos); + } + + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->previousWord( + *w.m_pText, nPtStart, + g_pBreakIt->GetLocale( pTextNd->GetLang(nPtPos, 1) ), + nWordType ).startPos; + } + + if (nPtPos < pTextNd->GetText().getLength() && nPtPos >= 0) + { + *GetPoint() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::SelectWordWT( SwViewShell const * pViewShell, sal_Int16 nWordType, const Point* pPt ) +{ + SwCursorSaveState aSave( *this ); + + bool bRet = false; + DeleteMark(); + const SwRootFrame* pLayout = pViewShell->GetLayout(); + if( pPt && nullptr != pLayout ) + { + // set the cursor to the layout position + Point aPt( *pPt ); + pLayout->GetModelPositionForViewPoint( GetPoint(), aPt ); + } + + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + // Should we select the whole fieldmark? + const IDocumentMarkAccess* pMarksAccess = GetDoc()->getIDocumentMarkAccess( ); + sw::mark::IFieldmark const*const pMark(pMarksAccess->getFieldmarkFor(*GetPoint())); + if (pMark && (IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + { + *GetPoint() = sw::mark::FindFieldSep(*pMark); + ++GetPoint()->nContent; // Don't select the separator + + const SwPosition& rEnd = pMark->GetMarkEnd(); + + assert(pMark->GetMarkEnd() != *GetPoint()); + SetMark(); + GetMark()->nNode = rEnd.nNode; + GetMark()->nContent = rEnd.nContent; + --GetMark()->nContent; // Don't select the end delimiter + + bRet = true; + } + else + { + bool bForward = true; + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + HideWrapper w(pViewShell->GetLayout(), pTextNd, nPtPos); + + Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + nWordType, + bForward )); + + if (comphelper::LibreOfficeKit::isActive() && aBndry.startPos == aBndry.endPos && w.m_nPtIndex > 0) + { + // nPtPos is the end of the paragraph, select the last word then. + --w.m_nPtIndex; + w.AssignBack(pTextNd, nPtPos); + + aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + nWordType, + bForward ); + + } + + SwTextNode * pStartNode(pTextNd); + sal_Int32 nStartIndex; + w.m_nPtIndex = aBndry.startPos; + w.AssignBack(pStartNode, nStartIndex); + + SwTextNode * pEndNode(pTextNd); + sal_Int32 nEndIndex; + w.m_nPtIndex = aBndry.endPos; + w.AssignBack(pEndNode, nEndIndex); + + if( aBndry.startPos != aBndry.endPos ) + { + *GetPoint() = SwPosition(*pEndNode, nEndIndex); + if( !IsSelOvr() ) + { + SetMark(); + *GetMark() = SwPosition(*pStartNode, nStartIndex); + if (sw::mark::IMark* pAnnotationMark = pMarksAccess->getAnnotationMarkFor(*GetPoint())) + { + // An annotation mark covers the selected word. Check + // if it covers only the word: in that case we select + // the comment anchor as well. + bool bStartMatch = GetMark()->nNode == pAnnotationMark->GetMarkStart().nNode && + GetMark()->nContent == pAnnotationMark->GetMarkStart().nContent; + bool bEndMatch = GetPoint()->nNode == pAnnotationMark->GetMarkEnd().nNode && + GetPoint()->nContent.GetIndex() + 1 == pAnnotationMark->GetMarkEnd().nContent.GetIndex(); + if (bStartMatch && bEndMatch) + ++GetPoint()->nContent; + } + if( !IsSelOvr() ) + bRet = true; + } + } + } + } + + if( !bRet ) + { + DeleteMark(); + RestoreSavePos(); + } + return bRet; +} + +static OUString lcl_MaskDeletedRedlines( const SwTextNode* pTextNd ) +{ + OUString aRes; + if (pTextNd) + { + //mask deleted redlines + OUString sNodeText(pTextNd->GetText()); + const SwDoc& rDoc = *pTextNd->GetDoc(); + const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + if ( bShowChg ) + { + SwRedlineTable::size_type nAct = rDoc.getIDocumentRedlineAccess().GetRedlinePos( *pTextNd, RedlineType::Any ); + for ( ; nAct < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ ) + { + const SwRangeRedline* pRed = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nAct ]; + if ( pRed->Start()->nNode > pTextNd->GetIndex() ) + break; + + if( RedlineType::Delete == pRed->GetType() ) + { + sal_Int32 nStart, nEnd; + pRed->CalcStartEnd( pTextNd->GetIndex(), nStart, nEnd ); + + while ( nStart < nEnd && nStart < sNodeText.getLength() ) + sNodeText = sNodeText.replaceAt( nStart++, 1, OUString(CH_TXTATR_INWORD) ); + } + } + } + aRes = sNodeText; + } + return aRes; +} + +bool SwCursor::GoSentence(SentenceMoveType eMoveType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + OUString const sNodeText(lcl_MaskDeletedRedlines(pTextNd)); + + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pTextNd, nPtPos, &sNodeText); + + switch ( eMoveType ) + { + case START_SENT: /* when modifying: see also ExpandToSentenceBorders below! */ + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos))); + break; + case END_SENT: /* when modifying: see also ExpandToSentenceBorders below! */ + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos))); + break; + case NEXT_SENT: + { + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos))); + if (w.m_nPtIndex >= 0 && w.m_nPtIndex < w.m_pText->getLength()) + { + do + { + ++w.m_nPtIndex; + } + while (w.m_nPtIndex < w.m_pText->getLength() + && (*w.m_pText)[w.m_nPtIndex] == ' '); + } + break; + } + case PREV_SENT: + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos))); + + if (w.m_nPtIndex == 0) + return false; // the previous sentence is not in this paragraph + if (w.m_nPtIndex > 0) + { + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence( + *w.m_pText, w.m_nPtIndex - 1, + g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos))); + } + break; + } + } + + // it is allowed to place the PaM just behind the last + // character in the text thus <= ...Len + if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0) + { + *GetPoint() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::ExpandToSentenceBorders(SwRootFrame const*const pLayout) +{ + bool bRes = false; + SwTextNode* pStartNd = Start()->nNode.GetNode().GetTextNode(); + SwTextNode* pEndNd = End()->nNode.GetNode().GetTextNode(); + if (pStartNd && pEndNd) + { + if (!HasMark()) + SetMark(); + + OUString sStartText( lcl_MaskDeletedRedlines( pStartNd ) ); + OUString sEndText( pStartNd == pEndNd? sStartText : lcl_MaskDeletedRedlines( pEndNd ) ); + + SwCursorSaveState aSave( *this ); + sal_Int32 nStartPos = Start()->nContent.GetIndex(); + sal_Int32 nEndPos = End()->nContent.GetIndex(); + + { + HideWrapper w(pLayout, pStartNd, nStartPos, &sStartText); + + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pStartNd->GetLang( nStartPos ) ) ); + } + { + HideWrapper w(pLayout, pEndNd, nEndPos, &sEndText); + + w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence( + *w.m_pText, w.m_nPtIndex, + g_pBreakIt->GetLocale( pEndNd->GetLang( nEndPos ) ) ); + } + + // it is allowed to place the PaM just behind the last + // character in the text thus <= ...Len + bool bChanged = false; + if (nStartPos <= pStartNd->GetText().getLength() && nStartPos >= 0) + { + *GetMark() = SwPosition(*pStartNd, nStartPos); + bChanged = true; + } + if (nEndPos <= pEndNd->GetText().getLength() && nEndPos >= 0) + { + *GetPoint() = SwPosition(*pEndNd, nEndPos); + bChanged = true; + } + if (bChanged && !IsSelOvr()) + bRes = true; + } + return bRes; +} + +bool SwTableCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, sal_uInt16 /*nMode*/, + bool /*bVisualAllowed*/, bool /*bSkipHidden*/, bool /*bInsertCursor*/, + SwRootFrame const*) +{ + return bLeft ? GoPrevCell( nCnt ) + : GoNextCell( nCnt ); +} + +// calculate cursor bidi level: extracted from LeftRight() +const SwContentFrame* +SwCursor::DoSetBidiLevelLeftRight( + bool & io_rbLeft, bool bVisualAllowed, bool bInsertCursor) +{ + // calculate cursor bidi level + const SwContentFrame* pSttFrame = nullptr; + SwNode& rNode = GetPoint()->nNode.GetNode(); + + if( rNode.IsTextNode() ) + { + const SwTextNode& rTNd = *rNode.GetTextNode(); + SwIndex& rIdx = GetPoint()->nContent; + sal_Int32 nPos = rIdx.GetIndex(); + + const SvtCTLOptions& rCTLOptions = SW_MOD()->GetCTLOptions(); + if ( bVisualAllowed && rCTLOptions.IsCTLFontEnabled() && + SvtCTLOptions::MOVEMENT_VISUAL == + rCTLOptions.GetCTLCursorMovement() ) + { + // for visual cursor travelling (used in bidi layout) + // we first have to convert the logic to a visual position + Point aPt; + std::pair const tmp(aPt, true); + pSttFrame = rTNd.getLayoutFrame( + GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + GetPoint(), &tmp); + if( pSttFrame ) + { + sal_uInt8 nCursorLevel = GetCursorBidiLevel(); + bool bForward = ! io_rbLeft; + SwTextFrame *const pTF(const_cast( + static_cast(pSttFrame))); + TextFrameIndex nTFIndex(pTF->MapModelToViewPos(*GetPoint())); + pTF->PrepareVisualMove( nTFIndex, nCursorLevel, + bForward, bInsertCursor ); + *GetPoint() = pTF->MapViewToModelPos(nTFIndex); + SetCursorBidiLevel( nCursorLevel ); + io_rbLeft = ! bForward; + } + } + else + { + SwTextFrame const* pFrame; + const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo(rTNd, &pFrame); + if ( pSI ) + { + const sal_Int32 nMoveOverPos = io_rbLeft ? + ( nPos ? nPos - 1 : 0 ) : + nPos; + TextFrameIndex nIndex(pFrame->MapModelToView(&rTNd, nMoveOverPos)); + SetCursorBidiLevel( pSI->DirType(nIndex) ); + } + } + } + return pSttFrame; +} + +bool SwCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, sal_uInt16 nMode, + bool bVisualAllowed,bool bSkipHidden, bool bInsertCursor, + SwRootFrame const*const pLayout) +{ + // calculate cursor bidi level + SwNode& rNode = GetPoint()->nNode.GetNode(); + const SwContentFrame* pSttFrame = // may side-effect bLeft! + DoSetBidiLevelLeftRight(bLeft, bVisualAllowed, bInsertCursor); + + // can the cursor be moved n times? + SwCursorSaveState aSave( *this ); + SwMoveFnCollection const & fnMove = bLeft ? fnMoveBackward : fnMoveForward; + + SwGoInDoc fnGo; + if ( bSkipHidden ) + fnGo = CRSR_SKIP_CELLS == nMode ? GoInContentCellsSkipHidden : GoInContentSkipHidden; + else + fnGo = CRSR_SKIP_CELLS == nMode ? GoInContentCells : GoInContent; + + SwTextFrame const* pFrame(nullptr); + if (pLayout) + { + pFrame = static_cast(rNode.GetContentNode()->getLayoutFrame(pLayout)); + if (pFrame) + { + while (pFrame->GetPrecede()) + { + pFrame = static_cast(pFrame->GetPrecede()); + } + } + } + + while( nCnt ) + { + SwNodeIndex aOldNodeIdx( GetPoint()->nNode ); + + TextFrameIndex beforeIndex(-1); + if (pFrame) + { + beforeIndex = pFrame->MapModelToViewPos(*GetPoint()); + } + + if ( !Move( fnMove, fnGo ) ) + break; + + if (pFrame) + { + SwTextFrame const* pNewFrame(static_cast( + GetPoint()->nNode.GetNode().GetContentNode()->getLayoutFrame(pLayout))); + if (pNewFrame) + { + while (pNewFrame->GetPrecede()) + { + pNewFrame = static_cast(pNewFrame->GetPrecede()); + } + } + // sw_redlinehide: fully redline-deleted nodes don't have frames... + if (pFrame == pNewFrame || !pNewFrame) + { + if (!pNewFrame || beforeIndex == pFrame->MapModelToViewPos(*GetPoint())) + { + continue; // moving inside delete redline, doesn't count... + } + } + else + { + // assume iteration is stable & returns the same frame + assert(!pFrame->IsAnFollow(pNewFrame) && !pNewFrame->IsAnFollow(pFrame)); + pFrame = pNewFrame; + } + } + + // If we were located inside a covered cell but our position has been + // corrected, we check if the last move has moved the cursor to a + // different table cell. In this case we set the cursor to the stored + // covered position and redo the move: + if (m_nRowSpanOffset) + { + const SwNode* pOldTabBoxSttNode = aOldNodeIdx.GetNode().FindTableBoxStartNode(); + const SwTableNode* pOldTabSttNode = pOldTabBoxSttNode ? pOldTabBoxSttNode->FindTableNode() : nullptr; + const SwNode* pNewTabBoxSttNode = GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + const SwTableNode* pNewTabSttNode = pNewTabBoxSttNode ? pNewTabBoxSttNode->FindTableNode() : nullptr; + + const bool bCellChanged = pOldTabSttNode && pNewTabSttNode && + pOldTabSttNode == pNewTabSttNode && + pOldTabBoxSttNode && pNewTabBoxSttNode && + pOldTabBoxSttNode != pNewTabBoxSttNode; + + if ( bCellChanged ) + { + // Set cursor to start/end of covered cell: + SwTableBox* pTableBox = pOldTabBoxSttNode->GetTableBox(); + if ( pTableBox && pTableBox->getRowSpan() > 1 ) + { + pTableBox = & pTableBox->FindEndOfRowSpan( + pOldTabSttNode->GetTable(), + static_cast(pTableBox->getRowSpan() + m_nRowSpanOffset)); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + + GetDoc()->GetNodes().GoNextSection( &rPtIdx, false, false ); + SwContentNode* pContentNode = GetContentNode(); + if ( pContentNode ) + { + GetPoint()->nContent.Assign( pContentNode, bLeft ? pContentNode->Len() : 0 ); + + // Redo the move: + if ( !Move( fnMove, fnGo ) ) + break; + } + } + m_nRowSpanOffset = 0; + } + } + + // Check if I'm inside a covered cell. Correct cursor if necessary and + // store covered cell: + const SwNode* pTableBoxStartNode = GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + if ( pTableBoxStartNode ) + { + const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox(); + if ( pTableBox && pTableBox->getRowSpan() < 1 ) + { + // Store the row span offset: + m_nRowSpanOffset = pTableBox->getRowSpan(); + + // Move cursor to non-covered cell: + const SwTableNode* pTableNd = pTableBoxStartNode->FindTableNode(); + pTableBox = & pTableBox->FindStartOfRowSpan( pTableNd->GetTable() ); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + + GetDoc()->GetNodes().GoNextSection( &rPtIdx, false, false ); + SwContentNode* pContentNode = GetContentNode(); + if ( pContentNode ) + { + GetPoint()->nContent.Assign( pContentNode, bLeft ? pContentNode->Len() : 0 ); + } + } + } + --nCnt; + } + + // here come some special rules for visual cursor travelling + if ( pSttFrame ) + { + SwNode& rTmpNode = GetPoint()->nNode.GetNode(); + if ( &rTmpNode != &rNode && rTmpNode.IsTextNode() ) + { + Point aPt; + std::pair const tmp(aPt, true); + const SwContentFrame* pEndFrame = rTmpNode.GetTextNode()->getLayoutFrame( + GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + GetPoint(), &tmp); + if ( pEndFrame ) + { + if ( ! pEndFrame->IsRightToLeft() != ! pSttFrame->IsRightToLeft() ) + { + if ( ! bLeft ) + pEndFrame->RightMargin( this ); + else + pEndFrame->LeftMargin( this ); + } + } + } + } + + return 0 == nCnt && !IsInProtectTable( true ) && + !IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); +} + +// calculate cursor bidi level: extracted from UpDown() +void SwCursor::DoSetBidiLevelUpDown() +{ + SwNode& rNode = GetPoint()->nNode.GetNode(); + if ( rNode.IsTextNode() ) + { + SwTextFrame const* pFrame; + const SwScriptInfo* pSI = + SwScriptInfo::GetScriptInfo( *rNode.GetTextNode(), &pFrame ); + if ( pSI ) + { + SwIndex& rIdx = GetPoint()->nContent; + const sal_Int32 nPos = rIdx.GetIndex(); + + if (nPos && nPos < rNode.GetTextNode()->GetText().getLength()) + { + TextFrameIndex const nIndex(pFrame->MapModelToView(rNode.GetTextNode(), nPos)); + const sal_uInt8 nCurrLevel = pSI->DirType( nIndex ); + const sal_uInt8 nPrevLevel = pSI->DirType( nIndex - TextFrameIndex(1) ); + + if ( nCurrLevel % 2 != nPrevLevel % 2 ) + { + // set cursor level to the lower of the two levels + SetCursorBidiLevel( std::min( nCurrLevel, nPrevLevel ) ); + } + else + SetCursorBidiLevel( nCurrLevel ); + } + } + } +} + +bool SwCursor::UpDown( bool bUp, sal_uInt16 nCnt, + Point const * pPt, long nUpDownX, + SwRootFrame & rLayout) +{ + SwTableCursor* pTableCursor = dynamic_cast(this); + bool bAdjustTableCursor = false; + + // If the point/mark of the table cursor in the same box then set cursor to + // beginning of the box + if( pTableCursor && GetNode().StartOfSectionNode() == + GetNode( false ).StartOfSectionNode() ) + { + if ( End() != GetPoint() ) + Exchange(); + bAdjustTableCursor = true; + } + + bool bRet = false; + Point aPt; + if( pPt ) + aPt = *pPt; + std::pair const temp(aPt, true); + SwContentFrame* pFrame = GetContentNode()->getLayoutFrame(&rLayout, GetPoint(), &temp); + + if( pFrame ) + { + SwCursorSaveState aSave( *this ); + + if( !pPt ) + { + SwRect aTmpRect; + pFrame->GetCharRect( aTmpRect, *GetPoint() ); + aPt = aTmpRect.Pos(); + + nUpDownX = pFrame->IsVertical() ? + aPt.getY() - pFrame->getFrameArea().Top() : + aPt.getX() - pFrame->getFrameArea().Left(); + } + + // It is allowed to move footnotes in other footnotes but not sections + const bool bChkRange = !pFrame->IsInFootnote() || HasMark(); + const SwPosition aOldPos( *GetPoint() ); + const bool bInReadOnly = IsReadOnlyAvailable(); + + if ( bAdjustTableCursor && !bUp ) + { + // Special case: We have a table cursor but the start box has more + // than one paragraph. If we want to go down, we have to set the + // point to the last frame in the table box. This is only necessary + // if we do not already have a table selection + const SwStartNode* pTableNd = GetNode().FindTableBoxStartNode(); + OSL_ENSURE( pTableNd, "pTableCursor without SwTableNode?" ); + + if ( pTableNd ) // safety first + { + const SwNode* pEndNd = pTableNd->EndOfSectionNode(); + GetPoint()->nNode = *pEndNd; + pTableCursor->Move( fnMoveBackward, GoInNode ); + std::pair const tmp(aPt, true); + pFrame = GetContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp); + } + } + + while( nCnt && + (bUp ? pFrame->UnitUp( this, nUpDownX, bInReadOnly ) + : pFrame->UnitDown( this, nUpDownX, bInReadOnly ) ) && + CheckNodesRange( aOldPos.nNode, GetPoint()->nNode, bChkRange )) + { + std::pair const tmp(aPt, true); + pFrame = GetContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp); + --nCnt; + } + + // iterate over whole number of items? + if( !nCnt && !IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ) ) + { + if( !pTableCursor ) + { + // try to position the cursor at half of the char-rect's height + DisableCallbackAction a(rLayout); + std::pair const tmp(aPt, true); + pFrame = GetContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp); + SwCursorMoveState eTmpState( CursorMoveState::UpDown ); + eTmpState.m_bSetInReadOnly = bInReadOnly; + SwRect aTmpRect; + pFrame->GetCharRect( aTmpRect, *GetPoint(), &eTmpState ); + if ( pFrame->IsVertical() ) + { + aPt.setX(aTmpRect.Center().getX()); + pFrame->Calc(rLayout.GetCurrShell()->GetOut()); + aPt.setY(pFrame->getFrameArea().Top() + nUpDownX); + } + else + { + aPt.setY(aTmpRect.Center().getY()); + pFrame->Calc(rLayout.GetCurrShell()->GetOut()); + aPt.setX(pFrame->getFrameArea().Left() + nUpDownX); + } + pFrame->GetModelPositionForViewPoint( GetPoint(), aPt, &eTmpState ); + } + bRet = !IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos ); + } + else + { + // Jump to beginning or end of line when the cursor at first or last line. + SwNode& rNode = GetPoint()->nNode.GetNode(); + const sal_Int32 nOffset = bUp ? 0 : rNode.GetTextNode()->GetText().getLength(); + const SwPosition aPos(*GetContentNode(), nOffset); + + //if cursor has already been at start or end of file, + //Update cursor to change nUpDownX. + if ( aOldPos.nContent.GetIndex() == nOffset ) + { + GetDoc()->GetEditShell()->UpdateCursor(); + bRet = false; + } + else{ + *GetPoint() = aPos; // just give a new position + bRet = true; + } + + } + + DoSetBidiLevelUpDown(); // calculate cursor bidi level + } + return bRet; +} + +bool SwCursor::LeftRightMargin(SwRootFrame const& rLayout, bool bLeft, bool bAPI) +{ + Point aPt; + std::pair const tmp(aPt, true); + SwContentFrame const*const pFrame = GetContentNode()->getLayoutFrame( + &rLayout, GetPoint(), &tmp); + + // calculate cursor bidi level + if ( pFrame ) + SetCursorBidiLevel( pFrame->IsRightToLeft() ? 1 : 0 ); + + SwCursorSaveState aSave( *this ); + return pFrame + && (bLeft ? pFrame->LeftMargin( this ) : pFrame->RightMargin( this, bAPI ) ) + && !IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos ); +} + +bool SwCursor::IsAtLeftRightMargin(SwRootFrame const& rLayout, bool bLeft, bool bAPI) const +{ + bool bRet = false; + Point aPt; + std::pair const tmp(aPt, true); + SwContentFrame const*const pFrame = GetContentNode()->getLayoutFrame( + &rLayout, GetPoint(), &tmp); + if( pFrame ) + { + SwPaM aPam( *GetPoint() ); + if( !bLeft && aPam.GetPoint()->nContent.GetIndex() ) + --aPam.GetPoint()->nContent; + bRet = (bLeft ? pFrame->LeftMargin( &aPam ) + : pFrame->RightMargin( &aPam, bAPI )) + && (!pFrame->IsTextFrame() + || static_cast(pFrame)->MapModelToViewPos(*aPam.GetPoint()) + == static_cast(pFrame)->MapModelToViewPos(*GetPoint())); + } + return bRet; +} + +bool SwCursor::SttEndDoc( bool bStt ) +{ + SwCursorSaveState aSave( *this ); + // Never jump over section boundaries during selection! + // Can the cursor still moved on? + SwMoveFnCollection const & fnMove = bStt ? fnMoveBackward : fnMoveForward; + bool bRet = (!HasMark() || !IsNoContent() ) && + Move( fnMove, GoInDoc ) && + !IsInProtectTable( true ) && + !IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos | + SwCursorSelOverFlags::EnableRevDirection ); + return bRet; +} + +bool SwCursor::GoPrevNextCell( bool bNext, sal_uInt16 nCnt ) +{ + const SwTableNode* pTableNd = GetPoint()->nNode.GetNode().FindTableNode(); + if( !pTableNd ) + return false; + + // If there is another EndNode in front of the cell's StartNode then there + // exists a previous cell + SwCursorSaveState aSave( *this ); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + + while( nCnt-- ) + { + const SwNode* pTableBoxStartNode = rPtIdx.GetNode().FindTableBoxStartNode(); + const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox(); + + // Check if we have to move the cursor to a covered cell before + // proceeding: + if (m_nRowSpanOffset) + { + if ( pTableBox && pTableBox->getRowSpan() > 1 ) + { + pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(), + static_cast(pTableBox->getRowSpan() + m_nRowSpanOffset)); + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + pTableBoxStartNode = rPtIdx.GetNode().FindTableBoxStartNode(); + } + m_nRowSpanOffset = 0; + } + + const SwNode* pTmpNode = bNext ? + pTableBoxStartNode->EndOfSectionNode() : + pTableBoxStartNode; + + SwNodeIndex aCellIdx( *pTmpNode, bNext ? 1 : -1 ); + if( (bNext && !aCellIdx.GetNode().IsStartNode()) || + (!bNext && !aCellIdx.GetNode().IsEndNode()) ) + return false; + + if (bNext) + rPtIdx = aCellIdx; + else + rPtIdx.Assign(*aCellIdx.GetNode().StartOfSectionNode()); + + pTableBoxStartNode = rPtIdx.GetNode().FindTableBoxStartNode(); + pTableBox = pTableBoxStartNode->GetTableBox(); + if ( pTableBox && pTableBox->getRowSpan() < 1 ) + { + m_nRowSpanOffset = pTableBox->getRowSpan(); + // move cursor to non-covered cell: + pTableBox = & pTableBox->FindStartOfRowSpan( pTableNd->GetTable() ); + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + } + } + + ++rPtIdx; + if( !rPtIdx.GetNode().IsContentNode() ) + GetDoc()->GetNodes().GoNextSection( &rPtIdx, true, false ); + GetPoint()->nContent.Assign( GetContentNode(), 0 ); + + return !IsInProtectTable( true ); +} + +bool SwTableCursor::GotoTable( const OUString& ) +{ + return false; // invalid action +} + +bool SwCursor::GotoTable( const OUString& rName ) +{ + bool bRet = false; + if ( !HasMark() ) + { + SwTable* pTmpTable = SwTable::FindTable( GetDoc()->FindTableFormatByName( rName ) ); + if( pTmpTable ) + { + // a table in a normal nodes array + SwCursorSaveState aSave( *this ); + GetPoint()->nNode = *pTmpTable->GetTabSortBoxes()[ 0 ]-> + GetSttNd()->FindTableNode(); + Move( fnMoveForward, GoInContent ); + bRet = !IsSelOvr(); + } + } + return bRet; +} + +bool SwCursor::GotoTableBox( const OUString& rName ) +{ + bool bRet = false; + const SwTableNode* pTableNd = GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + // retrieve box by name + const SwTableBox* pTableBox = pTableNd->GetTable().GetTableBox( rName ); + if( pTableBox && pTableBox->GetSttNd() && + ( !pTableBox->GetFrameFormat()->GetProtect().IsContentProtected() || + IsReadOnlyAvailable() ) ) + { + SwCursorSaveState aSave( *this ); + GetPoint()->nNode = *pTableBox->GetSttNd(); + Move( fnMoveForward, GoInContent ); + bRet = !IsSelOvr(); + } + } + return bRet; +} + +bool SwCursor::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara ) +{ + // for optimization test something before + const SwNode* pNd = &GetPoint()->nNode.GetNode(); + bool bShortCut = false; + if ( fnWhichPara == GoCurrPara ) + { + // #i41048# + // If fnWhichPara == GoCurrPara then (*fnWhichPara)( *this, fnPosPara ) + // can already move the cursor to a different text node. In this case + // we better check if IsSelOvr(). + const SwContentNode* pContentNd = pNd->GetContentNode(); + if ( pContentNd ) + { + const sal_Int32 nSttEnd = &fnPosPara == &fnMoveForward ? 0 : pContentNd->Len(); + if ( GetPoint()->nContent.GetIndex() != nSttEnd ) + bShortCut = true; + } + } + else + { + if ( pNd->IsTextNode() && + pNd->GetNodes()[ pNd->GetIndex() + + (fnWhichPara == GoNextPara ? 1 : -1 ) ]->IsTextNode() ) + bShortCut = true; + } + + if ( bShortCut ) + return (*fnWhichPara)( *this, fnPosPara ); + + // else we must use the SaveStructure, because the next/prev is not + // a same node type. + SwCursorSaveState aSave( *this ); + return (*fnWhichPara)( *this, fnPosPara ) && + !IsInProtectTable( true ) && + !IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); +} + +bool SwCursor::MoveSection( SwWhichSection fnWhichSect, + SwMoveFnCollection const & fnPosSect) +{ + SwCursorSaveState aSave( *this ); + return (*fnWhichSect)( *this, fnPosSect ) && + !IsInProtectTable( true ) && + !IsSelOvr( SwCursorSelOverFlags::Toggle | + SwCursorSelOverFlags::ChangePos ); +} + +void SwCursor::RestoreSavePos() +{ + // This method is not supposed to be used in cases when nodes may be + // deleted; detect such cases, but do not crash (example: fdo#40831). + sal_uLong uNodeCount = GetPoint()->nNode.GetNodes().Count(); + OSL_ENSURE(m_vSavePos.empty() || m_vSavePos.back().nNode < uNodeCount, + "SwCursor::RestoreSavePos: invalid node: " + "probably something was deleted; consider using SwUnoCursor instead"); + if (!m_vSavePos.empty() && m_vSavePos.back().nNode < uNodeCount) + { + GetPoint()->nNode = m_vSavePos.back().nNode; + + sal_Int32 nIdx = 0; + if ( GetContentNode() ) + { + if (m_vSavePos.back().nContent <= GetContentNode()->Len()) + nIdx = m_vSavePos.back().nContent; + else + { + nIdx = GetContentNode()->Len(); + OSL_FAIL("SwCursor::RestoreSavePos: invalid content index"); + } + } + GetPoint()->nContent.Assign( GetContentNode(), nIdx ); + } +} + +SwTableCursor::SwTableCursor( const SwPosition &rPos ) + : SwCursor( rPos, nullptr ) +{ + m_bParked = false; + m_bChanged = false; + m_nTablePtNd = 0; + m_nTableMkNd = 0; + m_nTablePtCnt = 0; + m_nTableMkCnt = 0; +} + +SwTableCursor::~SwTableCursor() {} + +static bool +lcl_SeekEntry(const SwSelBoxes& rTmp, SwStartNode const*const pSrch, + size_t & o_rFndPos) +{ + sal_uLong nIdx = pSrch->GetIndex(); + + size_t nO = rTmp.size(); + if( nO > 0 ) + { + nO--; + size_t nU = 0; + while( nU <= nO ) + { + size_t nM = nU + ( nO - nU ) / 2; + if( rTmp[ nM ]->GetSttNd() == pSrch ) + { + o_rFndPos = nM; + return true; + } + else if( rTmp[ nM ]->GetSttIdx() < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + return false; + else + nO = nM - 1; + } + } + return false; +} + +SwCursor* SwTableCursor::MakeBoxSels( SwCursor* pCurrentCursor ) +{ + if (m_bChanged) + { + if (m_bParked) + { + // move back into content + Exchange(); + Move( fnMoveForward ); + Exchange(); + Move( fnMoveForward ); + m_bParked = false; + } + + m_bChanged = false; + + // create temporary copies so that all boxes that + // have already cursors can be removed + SwSelBoxes aTmp(m_SelectedBoxes); + + // compare old and new ones + SwNodes& rNds = pCurrentCursor->GetDoc()->GetNodes(); + const SwStartNode* pSttNd; + SwPaM* pCur = pCurrentCursor; + do { + size_t nPos; + bool bDel = false; + pSttNd = pCur->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + if( !pCur->HasMark() || !pSttNd || + pSttNd != pCur->GetMark()->nNode.GetNode().FindTableBoxStartNode() ) + bDel = true; + + else if( lcl_SeekEntry( aTmp, pSttNd, nPos )) + { + SwNodeIndex aIdx( *pSttNd, 1 ); + const SwNode* pNd = &aIdx.GetNode(); + if( !pNd->IsContentNode() ) + pNd = rNds.GoNextSection( &aIdx, true, false ); + + SwPosition* pPos = pCur->GetMark(); + if( pNd != &pPos->nNode.GetNode() ) + pPos->nNode = *pNd; + pPos->nContent.Assign( const_cast(static_cast(pNd)), 0 ); + + aIdx.Assign( *pSttNd->EndOfSectionNode(), - 1 ); + pNd = &aIdx.GetNode(); + if( !pNd->IsContentNode() ) + pNd = SwNodes::GoPrevSection( &aIdx, true, false ); + + pPos = pCur->GetPoint(); + if (pNd && pNd != &pPos->nNode.GetNode()) + pPos->nNode = *pNd; + pPos->nContent.Assign(const_cast(static_cast(pNd)), pNd ? static_cast(pNd)->Len() : 0); + + aTmp.erase( aTmp.begin() + nPos ); + } + else + bDel = true; + + pCur = pCur->GetNext(); + if( bDel ) + { + SwPaM* pDel = pCur->GetPrev(); + + if( pDel == pCurrentCursor ) + pCurrentCursor->DeleteMark(); + else + delete pDel; + } + } while ( pCurrentCursor != pCur ); + + for (size_t nPos = 0; nPos < aTmp.size(); ++nPos) + { + pSttNd = aTmp[ nPos ]->GetSttNd(); + + SwNodeIndex aIdx( *pSttNd, 1 ); + if( &aIdx.GetNodes() != &rNds ) + break; + SwNode* pNd = &aIdx.GetNode(); + if( !pNd->IsContentNode() ) + pNd = rNds.GoNextSection( &aIdx, true, false ); + + SwPaM *const pNew = (!pCurrentCursor->IsMultiSelection() && !pCurrentCursor->HasMark()) + ? pCurrentCursor + : pCurrentCursor->Create( pCurrentCursor ); + pNew->GetPoint()->nNode = *pNd; + pNew->GetPoint()->nContent.Assign( static_cast(pNd), 0 ); + pNew->SetMark(); + + SwPosition* pPos = pNew->GetPoint(); + pPos->nNode.Assign( *pSttNd->EndOfSectionNode(), - 1 ); + pNd = &pPos->nNode.GetNode(); + if( !pNd->IsContentNode() ) + pNd = SwNodes::GoPrevSection( &pPos->nNode, true, false ); + + pPos->nContent.Assign(static_cast(pNd), pNd ? static_cast(pNd)->Len() : 0); + } + } + return pCurrentCursor; +} + +void SwTableCursor::InsertBox( const SwTableBox& rTableBox ) +{ + SwTableBox* pBox = const_cast(&rTableBox); + m_SelectedBoxes.insert(pBox); + m_bChanged = true; +} + +void SwTableCursor::DeleteBox(size_t const nPos) +{ + m_SelectedBoxes.erase(m_SelectedBoxes.begin() + nPos); + m_bChanged = true; +} + +bool SwTableCursor::NewTableSelection() +{ + bool bRet = false; + const SwNode *pStart = GetNode().FindTableBoxStartNode(); + const SwNode *pEnd = GetNode(false).FindTableBoxStartNode(); + if( pStart && pEnd ) + { + const SwTableNode *pTableNode = pStart->FindTableNode(); + if( pTableNode == pEnd->FindTableNode() && + pTableNode->GetTable().IsNewModel() ) + { + bRet = true; + SwSelBoxes aNew(m_SelectedBoxes); + pTableNode->GetTable().CreateSelection( pStart, pEnd, aNew, + SwTable::SEARCH_NONE, false ); + ActualizeSelection( aNew ); + } + } + return bRet; +} + +void SwTableCursor::ActualizeSelection( const SwSelBoxes &rNew ) +{ + size_t nOld = 0, nNew = 0; + while (nOld < m_SelectedBoxes.size() && nNew < rNew.size()) + { + SwTableBox const*const pPOld = m_SelectedBoxes[ nOld ]; + const SwTableBox* pPNew = rNew[ nNew ]; + if( pPOld == pPNew ) + { // this box will stay + ++nOld; + ++nNew; + } + else if( pPOld->GetSttIdx() < pPNew->GetSttIdx() ) + { + DeleteBox( nOld ); // this box has to go + } + else + { + InsertBox( *pPNew ); // this is a new one + ++nOld; + ++nNew; + } + } + + while (nOld < m_SelectedBoxes.size()) + { + DeleteBox( nOld ); // some more to delete + } + + for ( ; nNew < rNew.size(); ++nNew ) // some more to insert + { + InsertBox( *rNew[ nNew ] ); + } +} + +bool SwTableCursor::IsCursorMovedUpdate() +{ + if( !IsCursorMoved() ) + return false; + + m_nTableMkNd = GetMark()->nNode.GetIndex(); + m_nTablePtNd = GetPoint()->nNode.GetIndex(); + m_nTableMkCnt = GetMark()->nContent.GetIndex(); + m_nTablePtCnt = GetPoint()->nContent.GetIndex(); + return true; +} + +/// park table cursor on the boxes' start node +void SwTableCursor::ParkCursor() +{ + // de-register index from text node + SwNode* pNd = &GetPoint()->nNode.GetNode(); + if( !pNd->IsStartNode() ) + pNd = pNd->StartOfSectionNode(); + GetPoint()->nNode = *pNd; + GetPoint()->nContent.Assign( nullptr, 0 ); + + pNd = &GetMark()->nNode.GetNode(); + if( !pNd->IsStartNode() ) + pNd = pNd->StartOfSectionNode(); + GetMark()->nNode = *pNd; + GetMark()->nContent.Assign( nullptr, 0 ); + + m_bChanged = true; + m_bParked = true; +} + +bool SwTableCursor::HasReadOnlyBoxSel() const +{ + bool bRet = false; + for (size_t n = m_SelectedBoxes.size(); n; ) + { + if (m_SelectedBoxes[--n]->GetFrameFormat()->GetProtect().IsContentProtected()) + { + bRet = true; + break; + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/trvlcol.cxx b/sw/source/core/crsr/trvlcol.cxx new file mode 100644 index 000000000..8e1de5ba8 --- /dev/null +++ b/sw/source/core/crsr/trvlcol.cxx @@ -0,0 +1,103 @@ +/* -*- 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 "callnk.hxx" + +SwLayoutFrame* GetCurrColumn( const SwLayoutFrame* pLayFrame ) +{ + while( pLayFrame && !pLayFrame->IsColumnFrame() ) + pLayFrame = pLayFrame->GetUpper(); + return const_cast(pLayFrame); +} + +SwLayoutFrame* GetNextColumn( const SwLayoutFrame* pLayFrame ) +{ + SwLayoutFrame* pActCol = GetCurrColumn( pLayFrame ); + return pActCol ? static_cast(pActCol->GetNext()) : nullptr; +} + +SwLayoutFrame* GetPrevColumn( const SwLayoutFrame* pLayFrame ) +{ + SwLayoutFrame* pActCol = GetCurrColumn( pLayFrame ); + return pActCol ? static_cast(pActCol->GetPrev()) : nullptr; +} + +SwContentFrame* GetColumnStt( const SwLayoutFrame* pColFrame ) +{ + return pColFrame ? const_cast(pColFrame->ContainsContent()) : nullptr; +} + +SwContentFrame* GetColumnEnd( const SwLayoutFrame* pColFrame ) +{ + SwContentFrame *pRet = GetColumnStt( pColFrame ); + if( !pRet ) + return nullptr; + + SwContentFrame *pNxt = pRet->GetNextContentFrame(); + while( pNxt && pColFrame->IsAnLower( pNxt ) ) + { + pRet = pNxt; + pNxt = pNxt->GetNextContentFrame(); + } + return pRet; +} + +void SwCursorShell::MoveColumn( SwWhichColumn fnWhichCol, SwPosColumn fnPosCol ) +{ + if( m_pTableCursor ) + return; + SwLayoutFrame* pLayFrame = GetCurrFrame()->GetUpper(); + if( !pLayFrame ) + return; + pLayFrame = (*fnWhichCol)( pLayFrame ); + if( pLayFrame ) + { + SwContentFrame* pCnt = (*fnPosCol)( pLayFrame ); + if( pCnt ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + pCnt->Calc(GetOut()); + + Point aPt( pCnt->getFrameArea().Pos() + pCnt->getFramePrintArea().Pos() ); + if( fnPosCol == GetColumnEnd ) + { + aPt.setX(aPt.getX() + pCnt->getFramePrintArea().Width()); + aPt.setY(aPt.getY() + pCnt->getFramePrintArea().Height()); + } + + pCnt->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt ); + + if( !m_pCurrentCursor->IsInProtectTable( true ) && + !m_pCurrentCursor->IsSelOvr() ) + { + UpdateCursor(); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/trvlfnfl.cxx b/sw/source/core/crsr/trvlfnfl.cxx new file mode 100644 index 000000000..e0eb9ca48 --- /dev/null +++ b/sw/source/core/crsr/trvlfnfl.cxx @@ -0,0 +1,378 @@ +/* -*- 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 "callnk.hxx" +#include + +bool SwCursorShell::CallCursorShellFN( FNCursorShell fnCursor ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves + bool bRet = (this->*fnCursor)(); + if( bRet ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + return bRet; +} + +bool SwCursorShell::CallCursorFN( FNCursor fnCursor ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursor* pCursor = getShellCursor( true ); + bool bRet = (pCursor->*fnCursor)(); + if( bRet ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + return bRet; +} + +bool SwCursor::GotoFootnoteText() +{ + // jump from content to footnote + bool bRet = false; + SwTextNode* pTextNd = GetPoint()->nNode.GetNode().GetTextNode(); + + SwTextAttr *const pFootnote( pTextNd + ? pTextNd->GetTextAttrForCharAt( + GetPoint()->nContent.GetIndex(), RES_TXTATR_FTN) + : nullptr); + if (pFootnote) + { + SwCursorSaveState aSaveState( *this ); + GetPoint()->nNode = *static_cast(pFootnote)->GetStartNode(); + + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( + &GetPoint()->nNode, + true, !IsReadOnlyAvailable() ); + if( pCNd ) + { + GetPoint()->nContent.Assign( pCNd, 0 ); + bRet = !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle ); + } + } + return bRet; +} + +bool SwCursorShell::GotoFootnoteText() +{ + bool bRet = CallCursorFN( &SwCursor::GotoFootnoteText ); + if( !bRet ) + { + SwTextNode* pTextNd = GetCursor_() ? + GetCursor_()->GetPoint()->nNode.GetNode().GetTextNode() : nullptr; + if( pTextNd ) + { + std::pair const tmp(GetCursor_()->GetSttPos(), true); + const SwFrame *pFrame = pTextNd->getLayoutFrame( GetLayout(), + GetCursor_()->Start(), &tmp); + const SwFootnoteBossFrame* pFootnoteBoss; + bool bSkip = pFrame && pFrame->IsInFootnote(); + while( pFrame ) + { + pFootnoteBoss = pFrame->FindFootnoteBossFrame(); + if (!pFootnoteBoss) + break; + pFrame = pFootnoteBoss->FindFootnoteCont(); + if( pFrame ) + { + if( bSkip ) + bSkip = false; + else + { + const SwContentFrame* pCnt = static_cast + (pFrame)->ContainsContent(); + if( pCnt ) + { + SwTextFrame const*const pTF( + static_cast(pCnt)); + *GetCursor_()->GetPoint() = + pTF->MapViewToModelPos(pTF->GetOffset()); + UpdateCursor( SwCursorShell::SCROLLWIN | + SwCursorShell::CHKRANGE | SwCursorShell::READONLY ); + bRet = true; + break; + } + } + } + if( pFootnoteBoss->GetNext() && !pFootnoteBoss->IsPageFrame() ) + pFrame = pFootnoteBoss->GetNext(); + else + pFrame = pFootnoteBoss->GetUpper(); + } + } + } + return bRet; +} + +bool SwCursor::GotoFootnoteAnchor() +{ + // jump from footnote to anchor + const SwNode* pSttNd = GetNode().FindFootnoteStartNode(); + if( pSttNd ) + { + // search in all footnotes in document for this StartIndex + const SwFootnoteIdxs& rFootnoteArr = pSttNd->GetDoc()->GetFootnoteIdxs(); + for( size_t n = 0; n < rFootnoteArr.size(); ++n ) + { + const SwTextFootnote* pTextFootnote = rFootnoteArr[ n ]; + if( nullptr != pTextFootnote->GetStartNode() && + pSttNd == &pTextFootnote->GetStartNode()->GetNode() ) + { + SwCursorSaveState aSaveState( *this ); + + SwTextNode& rTNd = const_cast(pTextFootnote->GetTextNode()); + GetPoint()->nNode = rTNd; + GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() ); + + return !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle ); + } + } + } + return false; +} + +bool SwCursorShell::GotoFootnoteAnchor() +{ + // jump from footnote to anchor + SwCallLink aLk( *this ); // watch Cursor-Moves + bool bRet = m_pCurrentCursor->GotoFootnoteAnchor(); + if( bRet ) + { + // special treatment for table header row + m_pCurrentCursor->GetPtPos() = Point(); + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } + return bRet; +} + +static bool CmpLE( const SwTextFootnote& rFootnote, sal_uLong nNd, sal_Int32 nCnt ) +{ + const sal_uLong nTNd = rFootnote.GetTextNode().GetIndex(); + return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() <= nCnt ); +} + +static bool CmpL( const SwTextFootnote& rFootnote, sal_uLong nNd, sal_Int32 nCnt ) +{ + const sal_uLong nTNd = rFootnote.GetTextNode().GetIndex(); + return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() < nCnt ); +} + +bool SwCursor::GotoNextFootnoteAnchor() +{ + const SwFootnoteIdxs& rFootnoteArr = GetDoc()->GetFootnoteIdxs(); + const SwTextFootnote* pTextFootnote = nullptr; + size_t nPos = 0; + + if( rFootnoteArr.empty() ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + if( rFootnoteArr.SeekEntry( GetPoint()->nNode, &nPos )) + { + // there is a footnote with this index, so search also for the next one + if( nPos < rFootnoteArr.size() ) + { + sal_uLong nNdPos = GetPoint()->nNode.GetIndex(); + const sal_Int32 nCntPos = GetPoint()->nContent.GetIndex(); + + pTextFootnote = rFootnoteArr[ nPos ]; + // search forwards + if( CmpLE( *pTextFootnote, nNdPos, nCntPos ) ) + { + pTextFootnote = nullptr; + for( ++nPos; nPos < rFootnoteArr.size(); ++nPos ) + { + pTextFootnote = rFootnoteArr[ nPos ]; + if( !CmpLE( *pTextFootnote, nNdPos, nCntPos ) ) + break; // found + pTextFootnote = nullptr; + } + } + else if( nPos ) + { + // search backwards + pTextFootnote = nullptr; + while( nPos ) + { + pTextFootnote = rFootnoteArr[ --nPos ]; + if( CmpLE( *pTextFootnote, nNdPos, nCntPos ) ) + { + pTextFootnote = rFootnoteArr[ ++nPos ]; + break; // found + } + } + } + } + } + else if( nPos < rFootnoteArr.size() ) + pTextFootnote = rFootnoteArr[ nPos ]; + + if (pTextFootnote == nullptr) + { + pTextFootnote = rFootnoteArr[ 0 ]; + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + } + else + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + bool bRet = nullptr != pTextFootnote; + if( bRet ) + { + SwCursorSaveState aSaveState( *this ); + + SwTextNode& rTNd = const_cast(pTextFootnote->GetTextNode()); + GetPoint()->nNode = rTNd; + GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() ); + bRet = !IsSelOvr(); + } + return bRet; +} + +bool SwCursor::GotoPrevFootnoteAnchor() +{ + const SwFootnoteIdxs& rFootnoteArr = GetDoc()->GetFootnoteIdxs(); + const SwTextFootnote* pTextFootnote = nullptr; + size_t nPos = 0; + + if( rFootnoteArr.empty() ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + if( rFootnoteArr.SeekEntry( GetPoint()->nNode, &nPos ) ) + { + // there is a footnote with this index, so search also for the next one + sal_uLong nNdPos = GetPoint()->nNode.GetIndex(); + const sal_Int32 nCntPos = GetPoint()->nContent.GetIndex(); + + pTextFootnote = rFootnoteArr[ nPos ]; + // search forwards + if( CmpL( *pTextFootnote, nNdPos, nCntPos )) + { + for( ++nPos; nPos < rFootnoteArr.size(); ++nPos ) + { + pTextFootnote = rFootnoteArr[ nPos ]; + if( !CmpL( *pTextFootnote, nNdPos, nCntPos ) ) + { + pTextFootnote = rFootnoteArr[ nPos-1 ]; + break; + } + } + } + else if( nPos ) + { + // search backwards + pTextFootnote = nullptr; + while( nPos ) + { + pTextFootnote = rFootnoteArr[ --nPos ]; + if( CmpL( *pTextFootnote, nNdPos, nCntPos )) + break; // found + pTextFootnote = nullptr; + } + } + else + pTextFootnote = nullptr; + } + else if( nPos ) + pTextFootnote = rFootnoteArr[ nPos-1 ]; + + if( pTextFootnote == nullptr ) + { + pTextFootnote = rFootnoteArr[ rFootnoteArr.size() - 1 ]; + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + else + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + bool bRet = nullptr != pTextFootnote; + if( bRet ) + { + SwCursorSaveState aSaveState( *this ); + + SwTextNode& rTNd = const_cast(pTextFootnote->GetTextNode()); + GetPoint()->nNode = rTNd; + GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() ); + bRet = !IsSelOvr(); + } + return bRet; +} + +bool SwCursorShell::GotoNextFootnoteAnchor() +{ + return CallCursorFN( &SwCursor::GotoNextFootnoteAnchor ); +} + +bool SwCursorShell::GotoPrevFootnoteAnchor() +{ + return CallCursorFN( &SwCursor::GotoPrevFootnoteAnchor ); +} + +/// jump from border to anchor +void SwCursorShell::GotoFlyAnchor() +{ + SET_CURR_SHELL( this ); + const SwFrame* pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while( pFrame && !pFrame->IsFlyFrame() ); + + if( !pFrame ) // no FlyFrame + return; + + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // jump in BodyFrame closest to FlyFrame + SwRect aTmpRect( m_aCharRect ); + if( !pFrame->getFrameArea().IsInside( aTmpRect )) + aTmpRect = pFrame->getFrameArea(); + Point aPt( aTmpRect.Left(), aTmpRect.Top() + + ( aTmpRect.Bottom() - aTmpRect.Top() ) / 2 ); + aPt.setX(aPt.getX() > (pFrame->getFrameArea().Left() + (pFrame->getFrameArea().SSize().Width() / 2 )) + ? pFrame->getFrameArea().Right() + : pFrame->getFrameArea().Left()); + + const SwPageFrame* pPageFrame = pFrame->FindPageFrame(); + const SwContentFrame* pFndFrame = pPageFrame->GetContentPos( aPt, false, true ); + pFndFrame->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt ); + + bool bRet = !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/trvlreg.cxx b/sw/source/core/crsr/trvlreg.cxx new file mode 100644 index 000000000..1648caf8f --- /dev/null +++ b/sw/source/core/crsr/trvlreg.cxx @@ -0,0 +1,280 @@ +/* -*- 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 "callnk.hxx" +#include +#include +#include + +bool GotoPrevRegion( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosRegion, + bool bInReadOnly ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + SwNodeIndex aIdx( rCurrentCursor.GetPoint()->nNode ); + SwSectionNode* pNd = aIdx.GetNode().FindSectionNode(); + if( pNd ) + aIdx.Assign( *pNd, - 1 ); + + SwNodeIndex aOldIdx = aIdx; + sal_uLong nLastNd = rCurrentCursor.GetDoc()->GetNodes().Count() - 1; + do { + while( aIdx.GetIndex() ) + { + pNd = aIdx.GetNode().StartOfSectionNode()->GetSectionNode(); + if (pNd) + break; + --aIdx; + if ( aIdx == aOldIdx ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + } + + if ( !aIdx.GetIndex() ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + aIdx = nLastNd; + continue; + } + + assert( pNd ); // coverity, should never be nullptr + { + if( pNd->GetSection().IsHiddenFlag() || + ( !bInReadOnly && + pNd->GetSection().IsProtectFlag() )) + { + // skip protected or hidden ones + aIdx.Assign( *pNd, - 1 ); + continue; + } + else if( &fnPosRegion == &fnMoveForward ) + { + aIdx = *pNd; + SwContentNode* pCNd = pNd->GetNodes().GoNextSection( &aIdx, + true, !bInReadOnly ); + if( !pCNd ) + { + --aIdx; + continue; + } + rCurrentCursor.GetPoint()->nContent.Assign( pCNd, 0 ); + } + else + { + aIdx = *pNd->EndOfSectionNode(); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aIdx, + true, !bInReadOnly ); + if( !pCNd ) + { + aIdx.Assign( *pNd, - 1 ); + continue; + } + rCurrentCursor.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + } + rCurrentCursor.GetPoint()->nNode = aIdx; + return true; + } + } while( true ); + + // the flow is such that it is not possible to get here + return false; +} + +bool GotoNextRegion( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosRegion, + bool bInReadOnly ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + SwNodeIndex aIdx( rCurrentCursor.GetPoint()->nNode ); + SwSectionNode* pNd = aIdx.GetNode().FindSectionNode(); + if( pNd ) + aIdx.Assign( *pNd->EndOfSectionNode(), - 1 ); + + SwNodeIndex aOldIdx = aIdx; + sal_uLong nEndCount = aIdx.GetNode().GetNodes().Count()-1; + do { + while( aIdx.GetIndex() < nEndCount ) + { + pNd = aIdx.GetNode().GetSectionNode(); + if (pNd) + break; + ++aIdx; + if ( aIdx == aOldIdx ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + } + + if ( aIdx.GetIndex() == nEndCount ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + aIdx = 0; + continue; + } + + assert( pNd ); // coverity, should never be nullptr + { + if( pNd->GetSection().IsHiddenFlag() || + ( !bInReadOnly && + pNd->GetSection().IsProtectFlag() )) + { + // skip protected or hidden ones + aIdx.Assign( *pNd->EndOfSectionNode(), +1 ); + continue; + } + else if( &fnPosRegion == &fnMoveForward ) + { + aIdx = *pNd; + SwContentNode* pCNd = pNd->GetNodes().GoNextSection( &aIdx, + true, !bInReadOnly ); + if( !pCNd ) + { + aIdx.Assign( *pNd->EndOfSectionNode(), +1 ); + continue; + } + rCurrentCursor.GetPoint()->nContent.Assign( pCNd, 0 ); + } + else + { + aIdx = *pNd->EndOfSectionNode(); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aIdx, + true, !bInReadOnly ); + if( !pCNd ) + { + ++aIdx; + continue; + } + rCurrentCursor.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + } + rCurrentCursor.GetPoint()->nNode = aIdx; + return true; + } + } while( true ); + + // the flow is such that it is not possible to get here + return false; +} + +bool GotoCurrRegionAndSkip( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosRegion, + bool bInReadOnly ) +{ + SwNode& rCurrNd = rCurrentCursor.GetNode(); + SwSectionNode* pNd = rCurrNd.FindSectionNode(); + if( !pNd ) + return false; + + SwPosition* pPos = rCurrentCursor.GetPoint(); + const sal_Int32 nCurrCnt = pPos->nContent.GetIndex(); + bool bMoveBackward = &fnPosRegion == &fnMoveBackward; + + do { + SwContentNode* pCNd; + if( bMoveBackward ) // to the end of the section + { + SwNodeIndex aIdx( *pNd->EndOfSectionNode() ); + pCNd = SwNodes::GoPrevSection( &aIdx, true, !bInReadOnly ); + if( !pCNd ) + return false; + pPos->nNode = aIdx; + } + else + { + SwNodeIndex aIdx( *pNd ); + pCNd = pNd->GetNodes().GoNextSection( &aIdx, true, !bInReadOnly ); + if( !pCNd ) + return false; + pPos->nNode = aIdx; + } + + pPos->nContent.Assign( pCNd, bMoveBackward ? pCNd->Len() : 0 ); + + if( &pPos->nNode.GetNode() != &rCurrNd || + pPos->nContent.GetIndex() != nCurrCnt ) + // there was a change + return true; + + // try also the parent of this section + SwSection* pParent = pNd->GetSection().GetParent(); + pNd = pParent ? pParent->GetFormat()->GetSectionNode() : nullptr; + } while( pNd ); + return false; +} + +bool SwCursor::MoveRegion( SwWhichRegion fnWhichRegion, SwMoveFnCollection const & fnPosRegion ) +{ + SwCursorSaveState aSaveState( *this ); + return !dynamic_cast(this) && + (*fnWhichRegion)( *this, fnPosRegion, IsReadOnlyAvailable() ) && + !IsSelOvr() && + (GetPoint()->nNode.GetIndex() != m_vSavePos.back().nNode || + GetPoint()->nContent.GetIndex() != m_vSavePos.back().nContent); +} + +bool SwCursorShell::MoveRegion( SwWhichRegion fnWhichRegion, SwMoveFnCollection const & fnPosRegion ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves;call Link if needed + bool bRet = !m_pTableCursor && m_pCurrentCursor->MoveRegion( fnWhichRegion, fnPosRegion ); + if( bRet ) + UpdateCursor(); + return bRet; +} + +bool SwCursor::GotoRegion( const OUString& rName ) +{ + bool bRet = false; + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + for( SwSectionFormats::size_type n = rFormats.size(); n; ) + { + const SwSectionFormat* pFormat = rFormats[ --n ]; + const SwSection* pSect = pFormat->GetSection(); + if( pSect && pSect->GetSectionName() == rName ) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + // area in normal nodes array + SwCursorSaveState aSaveState( *this ); + + GetPoint()->nNode = *pIdx; + Move( fnMoveForward, GoInContent ); + bRet = !IsSelOvr(); + } + } + } + return bRet; +} + +bool SwCursorShell::GotoRegion( const OUString& rName ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves;call Link if needed + bool bRet = !m_pTableCursor && m_pCurrentCursor->GotoRegion( rName ); + if( bRet ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/trvltbl.cxx b/sw/source/core/crsr/trvltbl.cxx new file mode 100644 index 000000000..cdee8e9c5 --- /dev/null +++ b/sw/source/core/crsr/trvltbl.cxx @@ -0,0 +1,927 @@ +/* -*- 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 "callnk.hxx" +#include +#include +#include +#include +#include +#include + +/// set cursor into next/previous cell +bool SwCursorShell::GoNextCell( bool bAppendLine ) +{ + bool bRet = false; + const SwTableNode* pTableNd = nullptr; + + if( IsTableMode() || nullptr != ( pTableNd = IsCursorInTable() )) + { + SwCursor* pCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + SwCallLink aLk( *this ); // watch Cursor-Moves + bRet = true; + + // Check if we have to move the cursor to a covered cell before + // proceeding: + const SwNode* pTableBoxStartNode = pCursor->GetNode().FindTableBoxStartNode(); + const SwTableBox* pTableBox = nullptr; + + if ( pCursor->GetCursorRowSpanOffset() ) + { + pTableBox = pTableBoxStartNode->GetTableBox(); + if ( pTableBox->getRowSpan() > 1 ) + { + if ( !pTableNd ) + pTableNd = IsCursorInTable(); + assert (pTableNd); + pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(), + static_cast(pTableBox->getRowSpan() + pCursor->GetCursorRowSpanOffset() ) ); + pTableBoxStartNode = pTableBox->GetSttNd(); + } + } + + SwNodeIndex aCellStt( *pTableBoxStartNode->EndOfSectionNode(), 1 ); + + // if there is another StartNode after the EndNode of a cell then + // there is another cell + if( !aCellStt.GetNode().IsStartNode() ) + { + if( pCursor->HasMark() || !bAppendLine ) + bRet = false; + else if (pTableNd) + { + // if there is no list anymore then create new one + if ( !pTableBox ) + pTableBox = pTableNd->GetTable().GetTableBox( + pCursor->GetPoint()->nNode.GetNode(). + StartOfSectionIndex() ); + + OSL_ENSURE( pTableBox, "Box is not in this table" ); + SwSelBoxes aBoxes; + + // the document might change; w/o Action views would not be notified + static_cast(this)->StartAllAction(); + bRet = mxDoc->InsertRow( SwTable::SelLineFromBox( pTableBox, aBoxes, false )); + static_cast(this)->EndAllAction(); + } + } + bRet = bRet && pCursor->GoNextCell(); + if( bRet ) + UpdateCursor(); + } + return bRet; +} + +bool SwCursorShell::GoPrevCell() +{ + bool bRet = false; + if( IsTableMode() || IsCursorInTable() ) + { + SwCursor* pCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + SwCallLink aLk( *this ); // watch Cursor-Moves + bRet = pCursor->GoPrevCell(); + if( bRet ) + UpdateCursor(); // update current cursor + } + return bRet; +} + +static const SwFrame* lcl_FindMostUpperCellFrame( const SwFrame* pFrame ) +{ + while ( pFrame && + ( !pFrame->IsCellFrame() || + !pFrame->GetUpper()->GetUpper()->IsTabFrame() || + pFrame->GetUpper()->GetUpper()->GetUpper()->IsInTab() ) ) + { + pFrame = pFrame->GetUpper(); + } + return pFrame; +} + +bool SwCursorShell::SelTableRowOrCol( bool bRow, bool bRowSimple ) +{ + // check if the current cursor's SPoint/Mark are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame->IsInTab() ) + return false; + + const SwTabFrame* pTabFrame = pFrame->FindTabFrame(); + const SwTabFrame* pMasterTabFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame; + const SwTable* pTable = pTabFrame->GetTable(); + + SET_CURR_SHELL( this ); + + const SwTableBox* pStt = nullptr; + const SwTableBox* pEnd = nullptr; + + // search box based on layout + SwSelBoxes aBoxes; + SwTableSearchType eType = bRow ? SwTableSearchType::Row : SwTableSearchType::Col; + const bool bCheckProtected = !IsReadOnlyAvailable(); + + if( bCheckProtected ) + eType = static_cast(eType | SwTableSearchType::Protect); + + if ( !bRowSimple ) + { + GetTableSel( *this, aBoxes, eType ); + + if( aBoxes.empty() ) + return false; + + pStt = aBoxes[0]; + pEnd = aBoxes.back(); + } + // #i32329# Enhanced table selection + else if ( pTable->IsNewModel() ) + { + const SwShellCursor *pCursor = GetCursor_(); + SwTable::SearchType eSearchType = bRow ? SwTable::SEARCH_ROW : SwTable::SEARCH_COL; + pTable->CreateSelection( *pCursor, aBoxes, eSearchType, bCheckProtected ); + if( aBoxes.empty() ) + return false; + + pStt = aBoxes[0]; + pEnd = aBoxes.back(); + + m_eEnhancedTableSel = eSearchType; + } + else + { + const SwShellCursor *pCursor = GetCursor_(); + const SwFrame* pStartFrame = pFrame; + const SwContentNode *pCNd = pCursor->GetContentNode( false ); + std::pair const tmp(pCursor->GetMkPos(), true); + const SwFrame* pEndFrame = pCNd + ? pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp) + : nullptr; + + if ( bRow ) + { + pStartFrame = lcl_FindMostUpperCellFrame( pStartFrame ); + pEndFrame = lcl_FindMostUpperCellFrame( pEndFrame ); + } + + if ( !pStartFrame || !pEndFrame ) + return false; + + const bool bVert = pFrame->ImplFindTabFrame()->IsVertical(); + + // If we select upwards it is sufficient to set pStt and pEnd + // to the first resp. last box of the selection obtained from + // GetTableSel. However, selecting downwards requires the frames + // located at the corners of the selection. This does not work + // for column selections in vertical tables: + const bool bSelectUp = ( bVert && !bRow ) || + *pCursor->GetPoint() <= *pCursor->GetMark(); + SwCellFrames aCells; + GetTableSel( static_cast(pStartFrame), + static_cast(pEndFrame), + aBoxes, bSelectUp ? nullptr : &aCells, eType ); + + if( aBoxes.empty() || ( !bSelectUp && 4 != aCells.size() ) ) + return false; + + if ( bSelectUp ) + { + pStt = aBoxes[0]; + pEnd = aBoxes.back(); + } + else + { + // will become point of table cursor + pStt = aCells[bVert ? 0 : (bRow ? 2 : 1)]->GetTabBox(); + // will become mark of table cursor + pEnd = aCells[bVert ? 3 : (bRow ? 1 : 2)]->GetTabBox(); + } + } + + // if no table cursor exists, create one + if( !m_pTableCursor ) + { + m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SwSelPaintRects::Hide(); + } + + m_pTableCursor->DeleteMark(); + + // set start and end of a column + m_pTableCursor->GetPoint()->nNode = *pEnd->GetSttNd(); + m_pTableCursor->Move( fnMoveForward, GoInContent ); + m_pTableCursor->SetMark(); + m_pTableCursor->GetPoint()->nNode = *pStt->GetSttNd()->EndOfSectionNode(); + m_pTableCursor->Move( fnMoveBackward, GoInContent ); + + // set PtPos 'close' to the reference table, otherwise we might get problems + // with the repeated headlines check in UpdateCursor(): + if ( !bRow ) + m_pTableCursor->GetPtPos() = pMasterTabFrame->IsVertical() + ? pMasterTabFrame->getFrameArea().TopRight() + : pMasterTabFrame->getFrameArea().TopLeft(); + + UpdateCursor(); + return true; +} + +bool SwCursorShell::SelTable() +{ + // check if the current cursor's SPoint/Mark are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame->IsInTab() ) + return false; + + const SwTabFrame *pTableFrame = pFrame->ImplFindTabFrame(); + const SwTabFrame* pMasterTabFrame = pTableFrame->IsFollow() ? pTableFrame->FindMaster( true ) : pTableFrame; + const SwTableNode* pTableNd = pTableFrame->GetTable()->GetTableNode(); + + SET_CURR_SHELL( this ); + + if( !m_pTableCursor ) + { + m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SwSelPaintRects::Hide(); + } + + m_pTableCursor->DeleteMark(); + m_pTableCursor->GetPoint()->nNode = *pTableNd; + m_pTableCursor->Move( fnMoveForward, GoInContent ); + m_pTableCursor->SetMark(); + // set MkPos 'close' to the master table, otherwise we might get problems + // with the repeated headlines check in UpdateCursor(): + m_pTableCursor->GetMkPos() = pMasterTabFrame->IsVertical() ? pMasterTabFrame->getFrameArea().TopRight() : pMasterTabFrame->getFrameArea().TopLeft(); + m_pTableCursor->GetPoint()->nNode = *pTableNd->EndOfSectionNode(); + m_pTableCursor->Move( fnMoveBackward, GoInContent ); + UpdateCursor(); + return true; +} + +bool SwCursorShell::SelTableBox() +{ + // if we're in a table, create a table cursor, and select the cell + // that the current cursor's point resides in + + // search for start node of our table box. If not found, exit really + const SwStartNode* pStartNode = + m_pCurrentCursor->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + +#if OSL_DEBUG_LEVEL > 0 + // the old code checks whether we're in a table by asking the + // frame. This should yield the same result as searching for the + // table box start node, right? + SwFrame *pFrame = GetCurrFrame(); + OSL_ENSURE( !pFrame->IsInTab() == !(pStartNode != nullptr), + "Schroedinger's table: We're in a box, and also we aren't." ); +#endif + if( pStartNode == nullptr ) + return false; + + SET_CURR_SHELL( this ); + + // create a table cursor, if there isn't one already + if( !m_pTableCursor ) + { + m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SwSelPaintRects::Hide(); + } + + // select the complete box with our shiny new m_pTableCursor + // 1. delete mark, and move point to first content node in box + m_pTableCursor->DeleteMark(); + *(m_pTableCursor->GetPoint()) = SwPosition( *pStartNode ); + m_pTableCursor->Move( fnMoveForward, GoInNode ); + + // 2. set mark, and move point to last content node in box + m_pTableCursor->SetMark(); + *(m_pTableCursor->GetPoint()) = SwPosition( *(pStartNode->EndOfSectionNode()) ); + m_pTableCursor->Move( fnMoveBackward, GoInNode ); + + // 3. exchange + m_pTableCursor->Exchange(); + + // with some luck, UpdateCursor() will now update everything that + // needs updating + UpdateCursor(); + + return true; +} + +// TODO: provide documentation +/** get the next non-protected cell inside a table + + @param[in,out] rIdx is on a table node + @param bInReadOnly ??? + + @return if no suitable cell could be found, otherwise points + to content in a suitable cell and is returned. +*/ +static bool lcl_FindNextCell( SwNodeIndex& rIdx, bool bInReadOnly ) +{ + // check protected cells + SwNodeIndex aTmp( rIdx, 2 ); // TableNode + StartNode + + // the resulting cell should be in that table: + const SwTableNode* pTableNd = rIdx.GetNode().GetTableNode(); + + if ( !pTableNd ) + { + OSL_FAIL( "lcl_FindNextCell not celled with table start node!" ); + return false; + } + + const SwNode* pTableEndNode = pTableNd->EndOfSectionNode(); + + SwNodes& rNds = aTmp.GetNode().GetNodes(); + SwContentNode* pCNd = aTmp.GetNode().GetContentNode(); + + // no content node => go to next content node + if( !pCNd ) + pCNd = rNds.GoNext( &aTmp ); + + // robust + if ( !pCNd ) + return false; + + SwContentFrame* pFrame = pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + + if ( nullptr == pFrame || pCNd->FindTableNode() != pTableNd || + (!bInReadOnly && pFrame->IsProtected() ) ) + { + // we are not located inside a 'valid' cell. We have to continue searching... + + // skip behind current section. This might be the end of the table cell + // or behind an inner section or... + aTmp.Assign( *pCNd->EndOfSectionNode(), 1 ); + + // loop to find a suitable cell... + for( ;; ) + { + SwNode* pNd = &aTmp.GetNode(); + + // we break this loop if we reached the end of the table. + // to make this code even more robust, we also break if we are + // already behind the table end node: + if( pNd == pTableEndNode || /*robust: */ pNd->GetIndex() > pTableEndNode->GetIndex() ) + return false; + + // ok, get the next content node: + pCNd = aTmp.GetNode().GetContentNode(); + if( nullptr == pCNd ) + pCNd = rNds.GoNext( &aTmp ); + + // robust: + if ( !pCNd ) + return false; + + // check if we have found a suitable table cell: + pFrame = pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + + if ( nullptr != pFrame && pCNd->FindTableNode() == pTableNd && + (bInReadOnly || !pFrame->IsProtected() ) ) + { + // finally, we have found a suitable table cell => set index and return + rIdx = *pCNd; + return true; + } + + // continue behind the current section: + aTmp.Assign( *pCNd->EndOfSectionNode(), +1 ); + } + } + rIdx = *pCNd; + return true; +} + +/// see lcl_FindNextCell() +static bool lcl_FindPrevCell( SwNodeIndex& rIdx, bool bInReadOnly ) +{ + SwNodeIndex aTmp( rIdx, -2 ); // TableNode + EndNode + + const SwNode* pTableEndNode = &rIdx.GetNode(); + const SwTableNode* pTableNd = pTableEndNode->StartOfSectionNode()->GetTableNode(); + + if ( !pTableNd ) + { + OSL_FAIL( "lcl_FindPrevCell not celled with table start node!" ); + return false; + } + + SwContentNode* pCNd = aTmp.GetNode().GetContentNode(); + + if( !pCNd ) + pCNd = SwNodes::GoPrevious( &aTmp ); + + if ( !pCNd ) + return false; + + SwContentFrame* pFrame = pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + + if( nullptr == pFrame || pCNd->FindTableNode() != pTableNd || + (!bInReadOnly && pFrame->IsProtected() )) + { + // skip before current section + aTmp.Assign( *pCNd->StartOfSectionNode(), -1 ); + for( ;; ) + { + SwNode* pNd = &aTmp.GetNode(); + + if( pNd == pTableNd || pNd->GetIndex() < pTableNd->GetIndex() ) + return false; + + pCNd = aTmp.GetNode().GetContentNode(); + if( nullptr == pCNd ) + pCNd = SwNodes::GoPrevious( &aTmp ); + + if ( !pCNd ) + return false; + + pFrame = pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + + if( nullptr != pFrame && pCNd->FindTableNode() == pTableNd && + (bInReadOnly || !pFrame->IsProtected() ) ) + { + rIdx = *pCNd; + return true; // ok, not protected + } + aTmp.Assign( *pCNd->StartOfSectionNode(), - 1 ); + } + } + rIdx = *pCNd; + return true; +} + +bool GotoPrevTable( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosTable, + bool bInReadOnly ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + SwNodeIndex aIdx( rCurrentCursor.GetPoint()->nNode ); + + SwTableNode* pTableNd = aIdx.GetNode().FindTableNode(); + if( pTableNd ) + { + // #i26532#: If we are inside a table, we may not go backward to the + // table start node, because we would miss any tables inside this table. + SwTableNode* pInnerTableNd = nullptr; + SwNodeIndex aTmpIdx( aIdx ); + while( aTmpIdx.GetIndex() && + nullptr == ( pInnerTableNd = aTmpIdx.GetNode().StartOfSectionNode()->GetTableNode()) ) + --aTmpIdx; + + if( pInnerTableNd == pTableNd ) + aIdx.Assign( *pTableNd, - 1 ); + } + + SwNodeIndex aOldIdx = aIdx; + sal_uLong nLastNd = rCurrentCursor.GetDoc()->GetNodes().Count() - 1; + do { + while( aIdx.GetIndex() && + nullptr == ( pTableNd = aIdx.GetNode().StartOfSectionNode()->GetTableNode()) ) + { + --aIdx; + if ( aIdx == aOldIdx ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + } + + if ( !aIdx.GetIndex() ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + aIdx = nLastNd; + continue; + } + + { + if( &fnPosTable == &fnMoveForward ) // at the beginning? + { + aIdx = *aIdx.GetNode().StartOfSectionNode(); + if( !lcl_FindNextCell( aIdx, bInReadOnly )) + { + // skip table + aIdx.Assign( *pTableNd, -1 ); + continue; + } + } + else + { + // check protected cells + if( !lcl_FindNextCell( aIdx, bInReadOnly )) + { + // skip table + aIdx.Assign( *pTableNd, -1 ); + continue; + } + } + + SwTextNode* pTextNode = aIdx.GetNode().GetTextNode(); + if ( pTextNode ) + { + rCurrentCursor.GetPoint()->nNode = *pTextNode; + rCurrentCursor.GetPoint()->nContent.Assign( pTextNode, &fnPosTable == &fnMoveBackward ? + pTextNode->Len() : + 0 ); + } + return true; + } + } while( true ); + + return false; +} + +bool GotoNextTable( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosTable, + bool bInReadOnly ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + SwNodeIndex aIdx( rCurrentCursor.GetPoint()->nNode ); + SwTableNode* pTableNd = aIdx.GetNode().FindTableNode(); + + if( pTableNd ) + aIdx.Assign( *pTableNd->EndOfSectionNode(), 1 ); + + SwNodeIndex aOldIdx = aIdx; + sal_uLong nLastNd = rCurrentCursor.GetDoc()->GetNodes().Count() - 1; + do { + while( aIdx.GetIndex() < nLastNd && + nullptr == ( pTableNd = aIdx.GetNode().GetTableNode()) ) + { + ++aIdx; + if ( aIdx == aOldIdx ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + } + + if ( aIdx.GetIndex() == nLastNd ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + aIdx = 0; + continue; + } + + assert( pTableNd ); // coverity, should never be nullptr + + if( &fnPosTable == &fnMoveForward ) // at the beginning? + { + if( !lcl_FindNextCell( aIdx, bInReadOnly )) + { + // skip table + aIdx.Assign( *pTableNd->EndOfSectionNode(), + 1 ); + continue; + } + } + else + { + aIdx = *aIdx.GetNode().EndOfSectionNode(); + // check protected cells + if( !lcl_FindNextCell( aIdx, bInReadOnly )) + { + // skip table + aIdx.Assign( *pTableNd->EndOfSectionNode(), + 1 ); + continue; + } + } + + SwTextNode* pTextNode = aIdx.GetNode().GetTextNode(); + if ( pTextNode ) + { + rCurrentCursor.GetPoint()->nNode = *pTextNode; + rCurrentCursor.GetPoint()->nContent.Assign( pTextNode, &fnPosTable == &fnMoveBackward ? + pTextNode->Len() : + 0 ); + } + return true; + + } while( true ); + + // the flow is such that it is not possible to get there + + return false; +} + +bool GotoCurrTable( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosTable, + bool bInReadOnly ) +{ + SwTableNode* pTableNd = rCurrentCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( !pTableNd ) + return false; + + SwTextNode* pTextNode = nullptr; + if( &fnPosTable == &fnMoveBackward ) // to the end of the table + { + SwNodeIndex aIdx( *pTableNd->EndOfSectionNode() ); + if( !lcl_FindPrevCell( aIdx, bInReadOnly )) + return false; + pTextNode = aIdx.GetNode().GetTextNode(); + } + else + { + SwNodeIndex aIdx( *pTableNd ); + if( !lcl_FindNextCell( aIdx, bInReadOnly )) + return false; + pTextNode = aIdx.GetNode().GetTextNode(); + } + + if ( pTextNode ) + { + rCurrentCursor.GetPoint()->nNode = *pTextNode; + rCurrentCursor.GetPoint()->nContent.Assign( pTextNode, &fnPosTable == &fnMoveBackward ? + pTextNode->Len() : + 0 ); + } + + return true; +} + +bool SwCursor::MoveTable( SwWhichTable fnWhichTable, SwMoveFnCollection const & fnPosTable ) +{ + bool bRet = false; + SwTableCursor* pTableCursor = dynamic_cast(this); + + if( pTableCursor || !HasMark() ) + { + SwCursorSaveState aSaveState( *this ); + bRet = (*fnWhichTable)( *this, fnPosTable, IsReadOnlyAvailable() ) && + !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle ); + } + return bRet; +} + +bool SwCursorShell::MoveTable( SwWhichTable fnWhichTable, SwMoveFnCollection const & fnPosTable ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + SwShellCursor* pCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor; + bool bCheckPos; + bool bRet; + sal_uLong nPtNd = 0; + sal_Int32 nPtCnt = 0; + + if ( !m_pTableCursor && m_pCurrentCursor->HasMark() ) + { + // switch to table mode + m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SwSelPaintRects::Hide(); + m_pTableCursor->SetMark(); + pCursor = m_pTableCursor; + bCheckPos = false; + } + else + { + bCheckPos = true; + nPtNd = pCursor->GetPoint()->nNode.GetIndex(); + nPtCnt = pCursor->GetPoint()->nContent.GetIndex(); + } + + bRet = pCursor->MoveTable( fnWhichTable, fnPosTable ); + + if( bRet ) + { + // #i45028# - set "top" position for repeated headline rows + pCursor->GetPtPos() = Point(); + + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + + if( bCheckPos && + pCursor->GetPoint()->nNode.GetIndex() == nPtNd && + pCursor->GetPoint()->nContent.GetIndex() == nPtCnt ) + bRet = false; + } + return bRet; +} + +bool SwCursorShell::IsTableComplexForChart() +{ + bool bRet = false; + + // Here we may trigger table formatting so we better do that inside an action + StartAction(); + const SwTableNode* pTNd = m_pCurrentCursor->GetPoint()->nNode.GetNode().FindTableNode(); + if( pTNd ) + { + // in a table; check if table or section is balanced + OUString sSel; + if( m_pTableCursor ) + sSel = GetBoxNms(); + bRet = pTNd->GetTable().IsTableComplexForChart( sSel ); + } + EndAction(); + + return bRet; +} + +OUString SwCursorShell::GetBoxNms() const +{ + OUString sNm; + const SwPosition* pPos; + SwFrame* pFrame; + + if( IsTableMode() ) + { + SwContentNode *pCNd = m_pTableCursor->Start()->nNode.GetNode().GetContentNode(); + pFrame = pCNd ? pCNd->getLayoutFrame( GetLayout() ) : nullptr; + if( !pFrame ) + return sNm; + + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + + OSL_ENSURE( pFrame, "no frame for this box" ); + + if( !pFrame ) + return sNm; + + sNm = static_cast(pFrame)->GetTabBox()->GetName() + ":"; + pPos = m_pTableCursor->End(); + } + else + { + const SwTableNode* pTableNd = IsCursorInTable(); + if( !pTableNd ) + return sNm; + pPos = GetCursor()->GetPoint(); + } + + SwContentNode* pCNd = pPos->nNode.GetNode().GetContentNode(); + pFrame = pCNd ? pCNd->getLayoutFrame( GetLayout() ) : nullptr; + + if( pFrame ) + { + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + + if( pFrame ) + sNm += static_cast(pFrame)->GetTabBox()->GetName(); + } + return sNm; +} + +bool SwCursorShell::GotoTable( const OUString& rName ) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves + bool bRet = !m_pTableCursor && m_pCurrentCursor->GotoTable( rName ); + if( bRet ) + { + m_pCurrentCursor->GetPtPos() = Point(); + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } + return bRet; +} + +bool SwCursorShell::CheckTableBoxContent( const SwPosition* pPos ) +{ + if( !m_pBoxIdx || !m_pBoxPtr || IsSelTableCells() || !IsAutoUpdateCells() ) + return false; + + // check if box content is consistent with given box format, reset if not + SwTableBox* pChkBox = nullptr; + SwStartNode* pSttNd = nullptr; + if( !pPos ) + { + // get stored position + if (nullptr != (pSttNd = m_pBoxIdx->GetNode().GetStartNode()) && + SwTableBoxStartNode == pSttNd->GetStartNodeType() && + m_pBoxPtr == pSttNd->FindTableNode()->GetTable(). + GetTableBox( m_pBoxIdx->GetIndex() ) ) + pChkBox = m_pBoxPtr; + } + else if( nullptr != ( pSttNd = pPos->nNode.GetNode(). + FindSttNodeByType( SwTableBoxStartNode )) ) + { + pChkBox = pSttNd->FindTableNode()->GetTable().GetTableBox( pSttNd->GetIndex() ); + } + + // box has more than one paragraph + if( pChkBox && pSttNd->GetIndex() + 2 != pSttNd->EndOfSectionIndex() ) + pChkBox = nullptr; + + // destroy pointer before next action starts + if( !pPos && !pChkBox ) + ClearTableBoxContent(); + + // cursor not anymore in this section? + if( pChkBox && !pPos && + ( m_pCurrentCursor->HasMark() || m_pCurrentCursor->GetNext() != m_pCurrentCursor || + pSttNd->GetIndex() + 1 == m_pCurrentCursor->GetPoint()->nNode.GetIndex() )) + pChkBox = nullptr; + + // Did the content of a box change at all? This is important if e.g. Undo + // could not restore the content properly. + if( pChkBox ) + { + const SwTextNode* pNd = GetDoc()->GetNodes()[ + pSttNd->GetIndex() + 1 ]->GetTextNode(); + if( !pNd || + ( pNd->GetText() == SwViewShell::GetShellRes()->aCalc_Error && + SfxItemState::SET == pChkBox->GetFrameFormat()-> + GetItemState( RES_BOXATR_FORMULA )) ) + pChkBox = nullptr; + } + + if( pChkBox ) + { + // destroy pointer before next action starts + ClearTableBoxContent(); + StartAction(); + GetDoc()->ChkBoxNumFormat( *pChkBox, true ); + EndAction(); + } + + return nullptr != pChkBox; +} + +void SwCursorShell::SaveTableBoxContent( const SwPosition* pPos ) +{ + if( IsSelTableCells() || !IsAutoUpdateCells() ) + return ; + + if( !pPos ) + pPos = m_pCurrentCursor->GetPoint(); + + SwStartNode* pSttNd = pPos->nNode.GetNode().FindSttNodeByType( SwTableBoxStartNode ); + + bool bCheckBox = false; + if( pSttNd && m_pBoxIdx ) + { + if( pSttNd == &m_pBoxIdx->GetNode() ) + pSttNd = nullptr; + else + bCheckBox = true; + } + else + bCheckBox = nullptr != m_pBoxIdx; + + if( bCheckBox ) + { + // check m_pBoxIdx + SwPosition aPos( *m_pBoxIdx ); + CheckTableBoxContent( &aPos ); + } + + if( pSttNd ) + { + m_pBoxPtr = pSttNd->FindTableNode()->GetTable().GetTableBox( pSttNd->GetIndex() ); + + if( m_pBoxIdx ) + *m_pBoxIdx = *pSttNd; + else + m_pBoxIdx = new SwNodeIndex( *pSttNd ); + } +} + +void SwCursorShell::ClearTableBoxContent() +{ + delete m_pBoxIdx; + m_pBoxIdx = nullptr; + m_pBoxPtr = nullptr; +} + +bool SwCursorShell::EndAllTableBoxEdit() +{ + bool bRet = false; + for(SwViewShell& rSh : GetRingContainer()) + { + if( dynamic_cast(&rSh) != nullptr ) + bRet |= static_cast(&rSh)->CheckTableBoxContent( + static_cast(&rSh)->m_pCurrentCursor->GetPoint() ); + + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx new file mode 100644 index 000000000..6f3a1e3b6 --- /dev/null +++ b/sw/source/core/crsr/viscrs.cxx @@ -0,0 +1,941 @@ +/* -*- 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 "overlayrangesoutline.hxx" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Here static members are defined. They will get changed on alteration of the +// MapMode. This is done so that on ShowCursor the same size does not have to be +// expensively determined again and again. + +long SwSelPaintRects::s_nPixPtX = 0; +long SwSelPaintRects::s_nPixPtY = 0; +MapMode* SwSelPaintRects::s_pMapMode = nullptr; + +// Starting from here: classes / methods for the non-text-cursor +SwVisibleCursor::SwVisibleCursor( const SwCursorShell * pCShell ) + : m_pCursorShell( pCShell ) + , m_nPageLastTime(0) +{ + pCShell->GetWin()->SetCursor( &m_aTextCursor ); + m_bIsVisible = m_aTextCursor.IsVisible(); + m_bIsDragCursor = false; + m_aTextCursor.SetWidth( 0 ); +} + +SwVisibleCursor::~SwVisibleCursor() +{ + if( m_bIsVisible && m_aTextCursor.IsVisible() ) + m_aTextCursor.Hide(); + + m_pCursorShell->GetWin()->SetCursor( nullptr ); +} + +void SwVisibleCursor::Show() +{ + if( !m_bIsVisible ) + { + m_bIsVisible = true; + + // display at all? + if( m_pCursorShell->VisArea().IsOver( m_pCursorShell->m_aCharRect ) || comphelper::LibreOfficeKit::isActive() ) + SetPosAndShow(nullptr); + } +} + +void SwVisibleCursor::Hide() +{ + if( m_bIsVisible ) + { + m_bIsVisible = false; + + if( m_aTextCursor.IsVisible() ) // Shouldn't the flags be in effect? + m_aTextCursor.Hide(); + } +} + +namespace +{ + +// Build JSON message to be sent to Online +OString buildHyperlinkJSON(const OUString& sText, const OUString& sLink) +{ + boost::property_tree::ptree aTree; + aTree.put("text", sText); + aTree.put("link", sLink); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree, false); + + return OString(aStream.str().c_str()).trim(); +} + +} + +void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) +{ + SwRect aRect; + long nTmpY = m_pCursorShell->m_aCursorHeight.getY(); + if( 0 > nTmpY ) + { + nTmpY = -nTmpY; + m_aTextCursor.SetOrientation( 900 ); + aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(), + Size( m_pCursorShell->m_aCharRect.Height(), nTmpY ) ); + aRect.Pos().setX(aRect.Pos().getX() + m_pCursorShell->m_aCursorHeight.getX()); + if( m_pCursorShell->IsOverwriteCursor() ) + aRect.Pos().setY(aRect.Pos().getY() + aRect.Width()); + } + else + { + m_aTextCursor.SetOrientation(); + aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(), + Size( m_pCursorShell->m_aCharRect.Width(), nTmpY ) ); + aRect.Pos().setY(aRect.Pos().getY() + m_pCursorShell->m_aCursorHeight.getX()); + } + + // check if cursor should show the current cursor bidi level + m_aTextCursor.SetDirection(); + const SwCursor* pTmpCursor = m_pCursorShell->GetCursor_(); + + if ( pTmpCursor && !m_pCursorShell->IsOverwriteCursor() ) + { + SwNode& rNode = pTmpCursor->GetPoint()->nNode.GetNode(); + if( rNode.IsTextNode() ) + { + const SwTextNode& rTNd = *rNode.GetTextNode(); + const SwFrame* pFrame = rTNd.getLayoutFrame(m_pCursorShell->GetLayout(), nullptr, nullptr); + if ( pFrame ) + { + const SwScriptInfo* pSI = static_cast(pFrame)->GetScriptInfo(); + // cursor level has to be shown + if ( pSI && pSI->CountDirChg() > 1 ) + { + m_aTextCursor.SetDirection( + ( pTmpCursor->GetCursorBidiLevel() % 2 ) ? + CursorDirection::RTL : + CursorDirection::LTR ); + } + if ( pFrame->IsRightToLeft() ) + { + const OutputDevice *pOut = m_pCursorShell->GetOut(); + if ( pOut ) + { + long nSize = pOut->GetSettings().GetStyleSettings().GetCursorSize(); + Size aSize( nSize, nSize ); + aSize = pOut->PixelToLogic( aSize ); + aRect.Left( aRect.Left() - aSize.Width() ); + } + } + } + } + } + + if( aRect.Height()) + { + ::SwCalcPixStatics( m_pCursorShell->GetOut() ); + + // Disable pixel alignment when tiled rendering, so that twip values of + // the cursor don't depend on statics. + if (!comphelper::LibreOfficeKit::isActive()) + ::SwAlignRect( aRect, static_cast(m_pCursorShell), m_pCursorShell->GetOut() ); + } + if( !m_pCursorShell->IsOverwriteCursor() || m_bIsDragCursor || + m_pCursorShell->IsSelection() ) + aRect.Width( 0 ); + + m_aTextCursor.SetSize( aRect.SSize() ); + + m_aTextCursor.SetPos( aRect.Pos() ); + + bool bPostItActive = false; + SwView* pView = dynamic_cast(m_pCursorShell->GetSfxViewShell()); + if (pView) + { + if (SwPostItMgr* pPostItMgr = pView->GetPostItMgr()) + bPostItActive = pPostItMgr->GetActiveSidebarWin() != nullptr; + } + + if (comphelper::LibreOfficeKit::isActive() && !bPostItActive) + { + // notify about page number change (if that happened) + sal_uInt16 nPage, nVirtPage; + // bCalcFrame=false is important to avoid calculating the layout when + // we're in the middle of doing that already. + const_cast(m_pCursorShell)->GetPageNum(nPage, nVirtPage, /*bAtCursorPos=*/true, /*bCalcFrame=*/false); + if (nPage != m_nPageLastTime) + { + m_nPageLastTime = nPage; + OString aPayload = OString::number(nPage - 1); + m_pCursorShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr()); + } + + // notify about the cursor position & size + tools::Rectangle aSVRect(aRect.Pos().getX(), aRect.Pos().getY(), aRect.Pos().getX() + aRect.SSize().Width(), aRect.Pos().getY() + aRect.SSize().Height()); + OString sRect = aSVRect.toString(); + + // is cursor at a misspelled word ? + bool bIsWrong = false; + if (pView && pView->GetWrtShellPtr()) + { + const SwViewOption* pVOpt = pView->GetWrtShell().GetViewOptions(); + if(pVOpt && pVOpt->IsOnlineSpell()) + { + SwPaM* pCursor = m_pCursorShell->GetCursor(); + SwPosition aPos(*pCursor->GetPoint()); + Point aPt = aRect.Pos(); + SwCursorMoveState eTmpState(CursorMoveState::SetOnlyText); + SwTextNode *pNode = nullptr; + if (m_pCursorShell->GetLayout()->GetModelPositionForViewPoint(&aPos, aPt, &eTmpState)) + pNode = aPos.nNode.GetNode().GetTextNode(); + if (pNode && !pNode->IsInProtectSect()) + { + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + + SwWrongList *pWrong = nullptr; + pWrong = pNode->GetWrong(); + if (!pWrong) + pWrong = pNode->GetGrammarCheck(); + if (pWrong) + bIsWrong = pWrong->InWrongWord(nBegin,nLen) && !pNode->IsSymbolAt(nBegin); + } + } + } + + OString sHyperlink; + SwContentAtPos aContentAtPos(IsAttrAtPos::InetAttr); + bool bIsSelection = m_pCursorShell->IsSelection(); + + if (const_cast(m_pCursorShell)->GetContentAtPos(aRect.Pos(), aContentAtPos)) + { + const SwFormatINetFormat* pItem = static_cast(aContentAtPos.aFnd.pAttr); + sHyperlink = buildHyperlinkJSON(aContentAtPos.sStr, pItem->GetValue()); + } + else if (bIsSelection) + { + SwWrtShell* pShell = m_pCursorShell->GetDoc()->GetDocShell()->GetWrtShell(); + + if (pShell) + { + SfxItemSet aSet(m_pCursorShell->GetSfxViewShell()->GetPool(), + svl::Items{}); + pShell->GetCurAttr(aSet); + if(SfxItemState::SET <= aSet.GetItemState( RES_TXTATR_INETFMT )) + { + sHyperlink = buildHyperlinkJSON(m_pCursorShell->GetSelText(), + aSet.GetItem(RES_TXTATR_INETFMT)->GetValue()); + } + } + } + + if (pViewShell) + { + if (pViewShell == m_pCursorShell->GetSfxViewShell()) + { + SfxLokHelper::notifyVisCursorInvalidation(pViewShell, sRect, bIsWrong, sHyperlink); + } + else + SfxLokHelper::notifyOtherView(m_pCursorShell->GetSfxViewShell(), pViewShell, LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); + } + else + { + SfxLokHelper::notifyVisCursorInvalidation(m_pCursorShell->GetSfxViewShell(), sRect, bIsWrong, sHyperlink); + SfxLokHelper::notifyOtherViews(m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); + } + } + + if ( !m_pCursorShell->IsCursorReadonly() || m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() ) + { + if ( m_pCursorShell->GetDrawView() ) + const_cast(static_cast(m_pCursorShell->GetDrawView()))->SetAnimationEnabled( + !m_pCursorShell->IsSelection() ); + + sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0; + if( nStyle != m_aTextCursor.GetStyle() ) + { + m_aTextCursor.SetStyle( nStyle ); + m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : nullptr ); + } + + m_aTextCursor.Show(); + } +} + +const vcl::Cursor& SwVisibleCursor::GetTextCursor() const +{ + return m_aTextCursor; +} + +SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh ) + : SwRects() + , m_pCursorShell( &rCSh ) +#if HAVE_FEATURE_DESKTOP + , m_bShowTextInputFieldOverlay(true) +#endif +{ +} + +SwSelPaintRects::~SwSelPaintRects() +{ + Hide(); +} + +void SwSelPaintRects::swapContent(SwSelPaintRects& rSwap) +{ + SwRects::swap(rSwap); + +#if HAVE_FEATURE_DESKTOP + // #i75172# also swap m_pCursorOverlay + std::swap(m_pCursorOverlay, rSwap.m_pCursorOverlay); + std::swap(m_bShowTextInputFieldOverlay, rSwap.m_bShowTextInputFieldOverlay); + std::swap(m_pTextInputFieldOverlay, rSwap.m_pTextInputFieldOverlay); +#endif +} + +void SwSelPaintRects::Hide() +{ +#if HAVE_FEATURE_DESKTOP + m_pCursorOverlay.reset(); + m_pTextInputFieldOverlay.reset(); +#endif + + SwRects::clear(); +} + +/** + * Return a layout rectangle (typically with minimal width) that represents a + * cursor at rPosition. + * + * @param rPoint layout position as a hint about what layout frame contains + * rPosition (there might be multiple frames for a single node) + * @param rPosition the doc model position (paragraph / character index) + */ +static SwRect lcl_getLayoutRect(const Point& rPoint, const SwPosition& rPosition) +{ + const SwContentNode* pNode = rPosition.nNode.GetNode().GetContentNode(); + std::pair const tmp(rPoint, true); + const SwContentFrame* pFrame = pNode->getLayoutFrame( + pNode->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + &rPosition, &tmp); + SwRect aRect; + pFrame->GetCharRect(aRect, rPosition); + return aRect; +} + +void SwShellCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const +{ + const SwShellCursor* pCursor = GetShell()->getShellCursor(false); + rStart = lcl_getLayoutRect(pCursor->GetSttPos(), *pCursor->Start()); + rEnd = lcl_getLayoutRect(pCursor->GetEndPos(), *pCursor->End()); +} + +void SwSelPaintRects::Show(std::vector* pSelectionRectangles) +{ + SdrView *const pView = const_cast(m_pCursorShell->GetDrawView()); + + if(pView && pView->PaintWindowCount()) + { + // reset rects + SwRects::clear(); + FillRects(); + +#if HAVE_FEATURE_DESKTOP + // get new rects + std::vector< basegfx::B2DRange > aNewRanges; + aNewRanges.reserve(size()); + for(size_type a = 0; a < size(); ++a) + { + const SwRect aNextRect((*this)[a]); + const tools::Rectangle aPntRect(aNextRect.SVRect()); + + aNewRanges.emplace_back( + aPntRect.Left(), aPntRect.Top(), + aPntRect.Right() + 1, aPntRect.Bottom() + 1); + } + + if (m_pCursorOverlay) + { + if(!aNewRanges.empty()) + { + static_cast(m_pCursorOverlay.get())->setRanges(aNewRanges); + } + else + { + m_pCursorOverlay.reset(); + } + } + else if(!empty()) + { + SdrPaintWindow* pCandidate = pView->GetPaintWindow(0); + const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + // get the system's highlight color + const SvtOptionsDrawinglayer aSvtOptionsDrawinglayer; + const Color aHighlight(aSvtOptionsDrawinglayer.getHilightColor()); + + // create correct selection + m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, + aHighlight, + aNewRanges, + true) ); + + xTargetOverlay->add(*m_pCursorOverlay); + } + } + + HighlightInputField(); +#endif + + // Tiled editing does not expose the draw and writer cursor, it just + // talks about "the" cursor at the moment. As long as that's true, + // don't say anything about the Writer cursor till a draw object is + // being edited. + if (comphelper::LibreOfficeKit::isActive() && !pView->GetTextEditObject()) + { + // If pSelectionRectangles is set, we're just collecting the text selections -> don't emit start/end. + if (!empty() && !pSelectionRectangles) + { + // The selection may be a complex polygon, emit the logical + // start/end cursor rectangle of the selection as separate + // events, if there is a real selection. + // This can be used to easily show selection handles on the + // client side. + SwRect aStartRect; + SwRect aEndRect; + FillStartEnd(aStartRect, aEndRect); + + if (aStartRect.HasArea()) + { + OString sRect = aStartRect.SVRect().toString(); + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, sRect.getStr()); + } + if (aEndRect.HasArea()) + { + OString sRect = aEndRect.SVRect().toString(); + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, sRect.getStr()); + } + } + + std::vector aRect; + aRect.reserve(size()); + for (size_type i = 0; i < size(); ++i) + { + const SwRect& rRect = (*this)[i]; + aRect.push_back(rRect.SVRect().toString()); + } + OString sRect = comphelper::string::join("; ", aRect); + if (!pSelectionRectangles) + { + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRect.getStr()); + SfxLokHelper::notifyOtherViews(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect); + } + else + pSelectionRectangles->push_back(sRect); + } + } +} + +void SwSelPaintRects::HighlightInputField() +{ + std::vector< basegfx::B2DRange > aInputFieldRanges; + + if (m_bShowTextInputFieldOverlay) + { + SwTextInputField* pCurTextInputFieldAtCursor = + dynamic_cast(SwCursorShell::GetTextFieldAtPos( GetShell()->GetCursor()->Start(), false )); + if ( pCurTextInputFieldAtCursor != nullptr ) + { + SwTextNode* pTextNode = pCurTextInputFieldAtCursor->GetpTextNode(); + std::unique_ptr pCursorForInputTextField( + new SwShellCursor( *GetShell(), SwPosition( *pTextNode, pCurTextInputFieldAtCursor->GetStart() ) ) ); + pCursorForInputTextField->SetMark(); + pCursorForInputTextField->GetMark()->nNode = *pTextNode; + pCursorForInputTextField->GetMark()->nContent.Assign( pTextNode, *(pCurTextInputFieldAtCursor->End()) ); + + pCursorForInputTextField->FillRects(); + SwRects* pRects = static_cast(pCursorForInputTextField.get()); + for (const SwRect & rNextRect : *pRects) + { + const tools::Rectangle aPntRect(rNextRect.SVRect()); + + aInputFieldRanges.emplace_back( + aPntRect.Left(), aPntRect.Top(), + aPntRect.Right() + 1, aPntRect.Bottom() + 1); + } + } + } + + if ( !aInputFieldRanges.empty() ) + { + if (m_pTextInputFieldOverlay != nullptr) + { + m_pTextInputFieldOverlay->setRanges( aInputFieldRanges ); + } + else + { + SdrView* pView = const_cast(GetShell()->GetDrawView()); + SdrPaintWindow* pCandidate = pView->GetPaintWindow(0); + const rtl::Reference& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + // use system's highlight color with decreased luminance as highlight color + const SvtOptionsDrawinglayer aSvtOptionsDrawinglayer; + Color aHighlight(aSvtOptionsDrawinglayer.getHilightColor()); + aHighlight.DecreaseLuminance( 128 ); + + m_pTextInputFieldOverlay.reset( new sw::overlay::OverlayRangesOutline( + aHighlight, aInputFieldRanges ) ); + xTargetOverlay->add( *m_pTextInputFieldOverlay ); + } + } + } + else + { + m_pTextInputFieldOverlay.reset(); + } +} + +void SwSelPaintRects::Invalidate( const SwRect& rRect ) +{ + size_type nSz = size(); + if( !nSz ) + return; + + SwRegionRects aReg( GetShell()->VisArea() ); + aReg.assign( begin(), end() ); + aReg -= rRect; + SwRects::erase( begin(), begin() + nSz ); + SwRects::insert( begin(), aReg.begin(), aReg.end() ); + + // If the selection is to the right or at the bottom, outside the + // visible area, it is never aligned on one pixel at the right/bottom. + // This has to be determined here and if that is the case the + // rectangle has to be expanded. + if( GetShell()->m_bVisPortChgd && 0 != ( nSz = size()) ) + { + SwSelPaintRects::Get1PixelInLogic( *GetShell() ); + iterator it = begin(); + for( ; nSz--; ++it ) + { + SwRect& rRectIt = *it; + if( rRectIt.Right() == GetShell()->m_aOldRBPos.X() ) + rRectIt.AddRight( s_nPixPtX ); + if( rRectIt.Bottom() == GetShell()->m_aOldRBPos.Y() ) + rRectIt.AddBottom( s_nPixPtY ); + } + } +} + +// check current MapMode of the shell and set possibly the static members. +// Optional set the parameters pX, pY +void SwSelPaintRects::Get1PixelInLogic( const SwViewShell& rSh, + long* pX, long* pY ) +{ + const OutputDevice* pOut = rSh.GetWin(); + if ( ! pOut ) + pOut = rSh.GetOut(); + + const MapMode& rMM = pOut->GetMapMode(); + if (s_pMapMode->GetMapUnit() != rMM.GetMapUnit() || + s_pMapMode->GetScaleX() != rMM.GetScaleX() || + s_pMapMode->GetScaleY() != rMM.GetScaleY()) + { + *s_pMapMode = rMM; + Size aTmp( 1, 1 ); + aTmp = pOut->PixelToLogic( aTmp ); + s_nPixPtX = aTmp.Width(); + s_nPixPtY = aTmp.Height(); + } + if( pX ) + *pX = s_nPixPtX; + if( pY ) + *pY = s_nPixPtY; +} + +SwShellCursor::SwShellCursor( + const SwCursorShell& rCShell, + const SwPosition &rPos ) + : SwCursor(rPos,nullptr) + , SwSelPaintRects(rCShell) + , m_pInitialPoint(SwPaM::GetPoint()) +{} + +SwShellCursor::SwShellCursor( + const SwCursorShell& rCShell, + const SwPosition &rPos, + const Point& rPtPos, + SwPaM* pRing ) + : SwCursor(rPos, pRing) + , SwSelPaintRects(rCShell) + , m_MarkPt(rPtPos) + , m_PointPt(rPtPos) + , m_pInitialPoint(SwPaM::GetPoint()) +{} + +SwShellCursor::SwShellCursor( SwShellCursor& rICursor ) + : SwCursor(rICursor, &rICursor) + , SwSelPaintRects(*rICursor.GetShell()) + , m_MarkPt(rICursor.GetMkPos()) + , m_PointPt(rICursor.GetPtPos()) + , m_pInitialPoint(SwPaM::GetPoint()) +{} + +SwShellCursor::~SwShellCursor() +{} + +bool SwShellCursor::IsReadOnlyAvailable() const +{ + return GetShell()->IsReadOnlyAvailable(); +} + +void SwShellCursor::SetMark() +{ + if (SwPaM::GetPoint() == m_pInitialPoint) + m_MarkPt = m_PointPt; + else + m_PointPt = m_MarkPt; + SwPaM::SetMark(); +} + +void SwShellCursor::FillRects() +{ + // calculate the new rectangles + if( HasMark() && + GetPoint()->nNode.GetNode().IsContentNode() && + GetPoint()->nNode.GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) && + (GetMark()->nNode == GetPoint()->nNode || + (GetMark()->nNode.GetNode().IsContentNode() && + GetMark()->nNode.GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) ) )) + GetShell()->GetLayout()->CalcFrameRects( *this ); +} + +void SwShellCursor::Show(SfxViewShell const * pViewShell) +{ + std::vector aSelectionRectangles; + for(SwPaM& rPaM : GetRingContainer()) + { + SwShellCursor* pShCursor = dynamic_cast(&rPaM); + if(pShCursor) + pShCursor->SwSelPaintRects::Show(&aSelectionRectangles); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + std::vector aRect; + for (const OString & rSelectionRectangle : aSelectionRectangles) + { + if (rSelectionRectangle.isEmpty()) + continue; + aRect.push_back(rSelectionRectangle); + } + OString sRect = comphelper::string::join("; ", aRect); + if (pViewShell) + { + // Just notify pViewShell about our existing selection. + if (pViewShell != GetShell()->GetSfxViewShell()) + SfxLokHelper::notifyOtherView(GetShell()->GetSfxViewShell(), pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect); + } + else + { + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRect.getStr()); + SfxLokHelper::notifyOtherViews(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect); + } + } +} + +// This rectangle gets painted anew, therefore the SSelection in this +// area is invalid. +void SwShellCursor::Invalidate( const SwRect& rRect ) +{ + for(SwPaM& rPaM : GetRingContainer()) + { + SwShellCursor* pShCursor = dynamic_cast(&rPaM); + // skip any non SwShellCursor objects in the ring + // see also: SwAutoFormat::DeleteSel() + if(pShCursor) + pShCursor->SwSelPaintRects::Invalidate(rRect); + } +} + +void SwShellCursor::Hide() +{ + for(SwPaM& rPaM : GetRingContainer()) + { + SwShellCursor* pShCursor = dynamic_cast(&rPaM); + if(pShCursor) + pShCursor->SwSelPaintRects::Hide(); + } +} + +SwCursor* SwShellCursor::Create( SwPaM* pRing ) const +{ + return new SwShellCursor( *GetShell(), *GetPoint(), GetPtPos(), pRing ); +} + +short SwShellCursor::MaxReplaceArived() +{ + short nRet = RET_YES; + SvxSearchDialog* pDlg = SwView::GetSearchDialog(); + if( pDlg ) + { + // Terminate old actions. The table-frames get constructed and + // a SSelection can be created. + std::vector vActionCounts; + for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer()) + { + sal_uInt16 nActCnt = 0; + while(rShell.ActionPend()) + { + rShell.EndAction(); + ++nActCnt; + } + vActionCounts.push_back(nActCnt); + } + std::unique_ptr xBuilder(Application::CreateBuilder(pDlg->getDialog(), "modules/swriter/ui/asksearchdialog.ui")); + std::unique_ptr xDialog(xBuilder->weld_message_dialog("AskSearchDialog")); + nRet = xDialog->run(); + auto pActionCount = vActionCounts.begin(); + for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer()) + { + while(*pActionCount) + { + rShell.StartAction(); + --(*pActionCount); + } + ++pActionCount; + } + } + else + // otherwise from the Basic, and then switch to RET_YES + nRet = RET_YES; + + return nRet; +} + +void SwShellCursor::SaveTableBoxContent( const SwPosition* pPos ) +{ + const_cast(GetShell())->SaveTableBoxContent( pPos ); +} + +bool SwShellCursor::UpDown( bool bUp, sal_uInt16 nCnt ) +{ + return SwCursor::UpDown( bUp, nCnt, + &GetPtPos(), GetShell()->GetUpDownX(), + *GetShell()->GetLayout()); +} + +// if than the cursor can be set to the position. +bool SwShellCursor::IsAtValidPos( bool bPoint ) const +{ + if( GetShell() && ( GetShell()->IsAllProtect() || + GetShell()->GetViewOptions()->IsReadonly() || + ( GetShell()->Imp()->GetDrawView() && + GetShell()->Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ))) + return true; + + return SwCursor::IsAtValidPos( bPoint ); +} + +SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh, + const SwPosition& rPos ) + : SwCursor(rPos,nullptr), SwShellCursor(rCursorSh, rPos), SwTableCursor(rPos) +{ +} + +SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh, + const SwPosition& rMkPos, const Point& rMkPt, + const SwPosition& rPtPos, const Point& rPtPt ) + : SwCursor(rPtPos,nullptr), SwShellCursor(rCursorSh, rPtPos), SwTableCursor(rPtPos) +{ + SetMark(); + *GetMark() = rMkPos; + GetMkPos() = rMkPt; + GetPtPos() = rPtPt; +} + +SwShellTableCursor::~SwShellTableCursor() {} + +void SwShellTableCursor::SetMark() { SwShellCursor::SetMark(); } + +SwCursor* SwShellTableCursor::Create( SwPaM* pRing ) const +{ + return SwShellCursor::Create( pRing ); +} + +short SwShellTableCursor::MaxReplaceArived() +{ + return SwShellCursor::MaxReplaceArived(); +} + +void SwShellTableCursor::SaveTableBoxContent( const SwPosition* pPos ) +{ + SwShellCursor::SaveTableBoxContent( pPos ); +} + +void SwShellTableCursor::FillRects() +{ + // Calculate the new rectangles. If the cursor is still "parked" do nothing + if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->nNode.GetIndex()) + return; + + bool bStart = true; + SwRegionRects aReg( GetShell()->VisArea() ); + if (comphelper::LibreOfficeKit::isActive()) + aReg = GetShell()->getIDocumentLayoutAccess().GetCurrentLayout()->getFrameArea(); + SwNodes& rNds = GetDoc()->GetNodes(); + SwFrame* pEndFrame = nullptr; + for (size_t n = 0; n < m_SelectedBoxes.size(); ++n) + { + const SwStartNode* pSttNd = m_SelectedBoxes[n]->GetSttNd(); + const SwTableNode* pSelTableNd = pSttNd->FindTableNode(); + + SwNodeIndex aIdx( *pSttNd ); + SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false ); + + // table in table + // (see also lcl_FindTopLevelTable in unoobj2.cxx for a different + // version to do this) + const SwTableNode* pCurTableNd = pCNd ? pCNd->FindTableNode() : nullptr; + while ( pSelTableNd != pCurTableNd && pCurTableNd ) + { + aIdx = pCurTableNd->EndOfSectionIndex(); + pCNd = rNds.GoNextSection( &aIdx, true, false ); + pCurTableNd = pCNd->FindTableNode(); + } + + if( !pCNd ) + continue; + + std::pair const tmp(GetSttPos(), false); + SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp); + while( pFrame && !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + + OSL_ENSURE( pFrame, "Node not in a table" ); + + while ( pFrame ) + { + if( aReg.GetOrigin().IsOver( pFrame->getFrameArea() ) ) + { + aReg -= pFrame->getFrameArea(); + if (bStart) + { + bStart = false; + m_aStart = SwRect(pFrame->getFrameArea().Left(), pFrame->getFrameArea().Top(), 1, pFrame->getFrameArea().Height()); + } + } + + pEndFrame = pFrame; + pFrame = pFrame->GetNextCellLeaf(); + } + } + if (pEndFrame) + m_aEnd = SwRect(pEndFrame->getFrameArea().Right(), pEndFrame->getFrameArea().Top(), 1, pEndFrame->getFrameArea().Height()); + aReg.Invert(); + insert( begin(), aReg.begin(), aReg.end() ); +} + +void SwShellTableCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const +{ + rStart = m_aStart; + rEnd = m_aEnd; +} + +// Check if the SPoint is within the Table-SSelection. +bool SwShellTableCursor::IsInside( const Point& rPt ) const +{ + // Calculate the new rectangles. If the cursor is still "parked" do nothing + if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->nNode.GetIndex()) + return false; + + SwNodes& rNds = GetDoc()->GetNodes(); + for (size_t n = 0; n < m_SelectedBoxes.size(); ++n) + { + SwNodeIndex aIdx( *m_SelectedBoxes[n]->GetSttNd() ); + SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false ); + if( !pCNd ) + continue; + + std::pair const tmp(GetPtPos(), true); + SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp); + while( pFrame && !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + OSL_ENSURE( pFrame, "Node not in a table" ); + if( pFrame && pFrame->getFrameArea().IsInside( rPt ) ) + return true; + + for ( SwCellFrame* pCellFrame = static_cast(pFrame); pCellFrame; pCellFrame = pCellFrame->GetFollowCell() ) + { + if( pCellFrame->getFrameArea().IsInside( rPt ) ) + return true; + } + } + return false; +} + +bool SwShellTableCursor::IsAtValidPos( bool bPoint ) const +{ + return SwShellCursor::IsAtValidPos( bPoint ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/CntntIdxStore.cxx b/sw/source/core/doc/CntntIdxStore.cxx new file mode 100644 index 000000000..1fe119f2e --- /dev/null +++ b/sw/source/core/doc/CntntIdxStore.cxx @@ -0,0 +1,477 @@ +/* -*- 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 ::boost; +using namespace ::sw::mark; + +namespace +{ + // #i59534: If a paragraph will be split we have to restore some redline positions + // This help function checks a position compared with a node and a content index + + static const int BEFORE_NODE = 0; // Position before the given node index + static const int BEFORE_SAME_NODE = 1; // Same node index but content index before given content index + static const int SAME_POSITION = 2; // Same node index and samecontent index + static const int BEHIND_SAME_NODE = 3; // Same node index but content index behind given content index + static const int BEHIND_NODE = 4; // Position behind the given node index + + int lcl_RelativePosition( const SwPosition& rPos, sal_uLong nNode, sal_Int32 nContent ) + { + sal_uLong nIndex = rPos.nNode.GetIndex(); + int nReturn = BEFORE_NODE; + if( nIndex == nNode ) + { + const sal_Int32 nCntIdx = rPos.nContent.GetIndex(); + if( nCntIdx < nContent ) + nReturn = BEFORE_SAME_NODE; + else if( nCntIdx == nContent ) + nReturn = SAME_POSITION; + else + nReturn = BEHIND_SAME_NODE; + } + else if( nIndex > nNode ) + nReturn = BEHIND_NODE; + return nReturn; + } + struct MarkEntry + { + long int m_nIdx; + bool m_bOther; + sal_Int32 m_nContent; +#if 0 +#include + void Dump() + { + SAL_INFO("sw.core", "Index: " << m_nIdx << "\tOther: " << m_bOther << "\tContent: " << m_nContent); + } +#endif + }; + struct PaMEntry + { + SwPaM* m_pPaM; + bool m_isMark; + sal_Int32 m_nContent; + }; + struct OffsetUpdater + { + const SwContentNode* m_pNewContentNode; + const sal_Int32 m_nOffset; + OffsetUpdater(SwContentNode const * pNewContentNode, sal_Int32 nOffset) + : m_pNewContentNode(pNewContentNode), m_nOffset(nOffset) {}; + void operator()(SwPosition& rPos, sal_Int32 nContent) const + { + rPos.nNode = *m_pNewContentNode; + rPos.nContent.Assign(const_cast(m_pNewContentNode), nContent + m_nOffset); + }; + }; + struct LimitUpdater + { + const SwContentNode* m_pNewContentNode; + const sal_uLong m_nLen; + const sal_Int32 m_nCorrLen; + LimitUpdater(SwContentNode const * pNewContentNode, sal_uLong nLen, sal_Int32 nCorrLen) + : m_pNewContentNode(pNewContentNode), m_nLen(nLen), m_nCorrLen(nCorrLen) {}; + void operator()(SwPosition& rPos, sal_Int32 nContent) const + { + rPos.nNode = *m_pNewContentNode; + if( nContent < m_nCorrLen ) + { + rPos.nContent.Assign(const_cast(m_pNewContentNode), std::min( nContent, static_cast(m_nLen) ) ); + } + else + { + rPos.nContent -= m_nCorrLen; + } + }; + }; + struct ContentIdxStoreImpl : sw::mark::ContentIdxStore + { + std::vector m_aBkmkEntries; + std::vector m_aRedlineEntries; + std::vector m_aFlyEntries; + std::vector m_aUnoCursorEntries; + std::vector m_aShellCursorEntries; + typedef std::function updater_t; + virtual void Clear() override + { + m_aBkmkEntries.clear(); + m_aRedlineEntries.clear(); + m_aFlyEntries.clear(); + m_aUnoCursorEntries.clear(); + m_aShellCursorEntries.clear(); + } + virtual bool Empty() override + { + return m_aBkmkEntries.empty() && m_aRedlineEntries.empty() && m_aFlyEntries.empty() && m_aUnoCursorEntries.empty() && m_aShellCursorEntries.empty(); + } + virtual void Save(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit=false) override + { + SaveBkmks(pDoc, nNode, nContent); + SaveRedlines(pDoc, nNode, nContent); + SaveFlys(pDoc, nNode, nContent, bSaveFlySplit); + SaveUnoCursors(pDoc, nNode, nContent); + SaveShellCursors(pDoc, nNode, nContent); + } + virtual void Restore(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nOffset=0, bool bAuto = false, RestoreMode eMode = RestoreMode::All) override + { + SwContentNode* pCNd = pDoc->GetNodes()[ nNode ]->GetContentNode(); + updater_t aUpdater = OffsetUpdater(pCNd, nOffset); + if (eMode & RestoreMode::NonFlys) + { + RestoreBkmks(pDoc, aUpdater); + RestoreRedlines(pDoc, aUpdater); + RestoreUnoCursors(aUpdater); + RestoreShellCursors(aUpdater); + } + if (eMode & RestoreMode::Flys) + { + RestoreFlys(pDoc, aUpdater, bAuto); + } + } + virtual void Restore(SwNode& rNd, sal_Int32 nLen, sal_Int32 nCorrLen, RestoreMode eMode = RestoreMode::All) override + { + SwContentNode* pCNd = rNd.GetContentNode(); + SwDoc* pDoc = rNd.GetDoc(); + updater_t aUpdater = LimitUpdater(pCNd, nLen, nCorrLen); + if (eMode & RestoreMode::NonFlys) + { + RestoreBkmks(pDoc, aUpdater); + RestoreRedlines(pDoc, aUpdater); + RestoreUnoCursors(aUpdater); + RestoreShellCursors(aUpdater); + } + if (eMode & RestoreMode::Flys) + { + RestoreFlys(pDoc, aUpdater, false); + } + } + + private: + void SaveBkmks(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreBkmks(SwDoc* pDoc, updater_t const & rUpdater); + void SaveRedlines(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreRedlines(SwDoc* pDoc, updater_t const & rUpdater); + void SaveFlys(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit); + void RestoreFlys(SwDoc* pDoc, updater_t const & rUpdater, bool bAuto); + void SaveUnoCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreUnoCursors(updater_t const & rUpdater); + void SaveShellCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreShellCursors(updater_t const & rUpdater); + static const SwPosition& GetRightMarkPos(::sw::mark::IMark const * pMark, bool bOther) + { return bOther ? pMark->GetOtherMarkPos() : pMark->GetMarkPos(); }; + static void SetRightMarkPos(MarkBase* pMark, bool bOther, const SwPosition* const pPos) + { bOther ? pMark->SetOtherMarkPos(*pPos) : pMark->SetMarkPos(*pPos); }; + }; + void lcl_ChkPaM( std::vector& rPaMEntries, const sal_uLong nNode, const sal_Int32 nContent, SwPaM& rPaM, const bool bGetPoint, bool bSetMark) + { + const SwPosition* pPos = &rPaM.GetBound(bGetPoint); + if( pPos->nNode.GetIndex() == nNode && pPos->nContent.GetIndex() < nContent ) + { + const PaMEntry aEntry = { &rPaM, bSetMark, pPos->nContent.GetIndex() }; + rPaMEntries.push_back(aEntry); + } + } + void lcl_ChkPaMBoth( std::vector& rPaMEntries, const sal_uLong nNode, const sal_Int32 nContent, SwPaM& rPaM) + { + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, true, true); + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, false, false); + } + void lcl_ChkUnoCrsrPaMBoth(std::vector& rPaMEntries, const sal_uLong nNode, const sal_Int32 nContent, SwPaM& rPaM) + { + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, true, false); + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, false, true); + } + +#if 0 + static void DumpEntries(std::vector* pEntries) + { + for (MarkEntry& aEntry : *pEntries) + aEntry.Dump(); + } +#endif +} + +void ContentIdxStoreImpl::SaveBkmks(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess(); + const IDocumentMarkAccess::const_iterator_t ppBkmkEnd = pMarkAccess->getAllMarksEnd(); + for( + IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->getAllMarksBegin(); + ppBkmk != ppBkmkEnd; + ++ppBkmk) + { + const ::sw::mark::IMark* pBkmk = *ppBkmk; + bool bMarkPosEqual = false; + if(pBkmk->GetMarkPos().nNode.GetIndex() == nNode + && pBkmk->GetMarkPos().nContent.GetIndex() <= nContent) + { + if(pBkmk->GetMarkPos().nContent.GetIndex() < nContent) + { + const MarkEntry aEntry = { static_cast(ppBkmk - pMarkAccess->getAllMarksBegin()), false, pBkmk->GetMarkPos().nContent.GetIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + else // if a bookmark position is equal nContent, the other position + bMarkPosEqual = true; // has to decide if it is added to the array + } + if(pBkmk->IsExpanded() + && pBkmk->GetOtherMarkPos().nNode.GetIndex() == nNode + && pBkmk->GetOtherMarkPos().nContent.GetIndex() <= nContent) + { + if(bMarkPosEqual) + { // the other position is before, the (main) position is equal + const MarkEntry aEntry = { static_cast(ppBkmk - pMarkAccess->getAllMarksBegin()), false, pBkmk->GetMarkPos().nContent.GetIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + const MarkEntry aEntry = { static_cast(ppBkmk - pMarkAccess->getAllMarksBegin()), true, pBkmk->GetOtherMarkPos().nContent.GetIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + } +} + +void ContentIdxStoreImpl::RestoreBkmks(SwDoc* pDoc, updater_t const & rUpdater) +{ + IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess(); + for (const MarkEntry& aEntry : m_aBkmkEntries) + { + if (MarkBase *const pMark = pMarkAccess->getAllMarksBegin().get()[aEntry.m_nIdx]) + { + SwPosition aNewPos(GetRightMarkPos(pMark, aEntry.m_bOther)); + rUpdater(aNewPos, aEntry.m_nContent); + SetRightMarkPos(pMark, aEntry.m_bOther, &aNewPos); + } + } + if (!m_aBkmkEntries.empty()) + { // tdf#105705 sort bookmarks because SaveBkmks special handling of + // "bMarkPosEqual" may destroy sort order + pMarkAccess->assureSortedMarkContainers(); + } +} + +void ContentIdxStoreImpl::SaveRedlines(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + SwRedlineTable const & rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + long int nIdx = 0; + for (const SwRangeRedline* pRdl : rRedlineTable) + { + int nPointPos = lcl_RelativePosition( *pRdl->GetPoint(), nNode, nContent ); + int nMarkPos = pRdl->HasMark() ? lcl_RelativePosition( *pRdl->GetMark(), nNode, nContent ) : + nPointPos; + // #i59534: We have to store the positions inside the same node before the insert position + // and the one at the insert position if the corresponding Point/Mark position is before + // the insert position. + if( nPointPos == BEFORE_SAME_NODE || + ( nPointPos == SAME_POSITION && nMarkPos < SAME_POSITION ) ) + { + const MarkEntry aEntry = { nIdx, false, pRdl->GetPoint()->nContent.GetIndex() }; + m_aRedlineEntries.push_back(aEntry); + } + if( pRdl->HasMark() && ( nMarkPos == BEFORE_SAME_NODE || + ( nMarkPos == SAME_POSITION && nPointPos < SAME_POSITION ) ) ) + { + const MarkEntry aEntry = { nIdx, true, pRdl->GetMark()->nContent.GetIndex() }; + m_aRedlineEntries.push_back(aEntry); + } + ++nIdx; + } +} + +void ContentIdxStoreImpl::RestoreRedlines(SwDoc* pDoc, updater_t const & rUpdater) +{ + const SwRedlineTable& rRedlTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for (const MarkEntry& aEntry : m_aRedlineEntries) + { + SwPosition* const pPos = aEntry.m_bOther + ? rRedlTable[ aEntry.m_nIdx ]->GetMark() + : rRedlTable[ aEntry.m_nIdx ]->GetPoint(); + rUpdater(*pPos, aEntry.m_nContent); + } +} + +void ContentIdxStoreImpl::SaveFlys(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit) +{ + SwContentNode *pNode = pDoc->GetNodes()[nNode]->GetContentNode(); + if( !pNode ) + return; + SwFrame* pFrame = pNode->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + if( pFrame ) + { + // sw_redlinehide: this looks like an invalid optimisation if merged, + // assuming that flys in deleted redlines should be saved here too. + if ((!pFrame->IsTextFrame() || !static_cast(pFrame)->GetMergedPara()) + && !pFrame->GetDrawObjs()) + return; // if we have a layout and no DrawObjs, we can skip this + } + MarkEntry aSave = { 0, false, 0 }; + for (const SwFrameFormat* pFrameFormat : *pDoc->GetSpzFrameFormats()) + { + if ( RES_FLYFRMFMT == pFrameFormat->Which() || RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if ( pAPos && ( nNode == pAPos->nNode.GetIndex() ) && + ( RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId() || + RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() ) ) + { + bool bSkip = false; + aSave.m_bOther = false; + aSave.m_nContent = pAPos->nContent.GetIndex(); + if ( RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() ) + { + if( nContent <= aSave.m_nContent ) + { + if( bSaveFlySplit ) + aSave.m_bOther = true; + else + bSkip = true; + } + } + if(!bSkip) + m_aFlyEntries.push_back(aSave); + } + } + ++aSave.m_nIdx; + } +} + +void ContentIdxStoreImpl::RestoreFlys(SwDoc* pDoc, updater_t const & rUpdater, bool bAuto) +{ + SwFrameFormats* pSpz = pDoc->GetSpzFrameFormats(); + for (const MarkEntry& aEntry : m_aFlyEntries) + { + if(!aEntry.m_bOther) + { + SwFrameFormat *pFrameFormat = (*pSpz)[ aEntry.m_nIdx ]; + const SwFormatAnchor& rFlyAnchor = pFrameFormat->GetAnchor(); + if( rFlyAnchor.GetContentAnchor() ) + { + SwFormatAnchor aNew( rFlyAnchor ); + SwPosition aNewPos( *rFlyAnchor.GetContentAnchor() ); + rUpdater(aNewPos, aEntry.m_nContent); + if ( RndStdIds::FLY_AT_CHAR != rFlyAnchor.GetAnchorId() ) + { + aNewPos.nContent.Assign( nullptr, 0 ); + } + aNew.SetAnchor( &aNewPos ); + pFrameFormat->SetFormatAttr( aNew ); + } + } + else if( bAuto ) + { + SwFrameFormat *pFrameFormat = (*pSpz)[ aEntry.m_nIdx ]; + SfxPoolItem const *pAnchor = &pFrameFormat->GetAnchor(); + pFrameFormat->NotifyClients( pAnchor, pAnchor ); + } + } +} + +void ContentIdxStoreImpl::SaveUnoCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + pDoc->cleanupUnoCursorTable(); + for (const auto& pWeakUnoCursor : pDoc->mvUnoCursorTable) + { + auto pUnoCursor(pWeakUnoCursor.lock()); + if(!pUnoCursor) + continue; + for(SwPaM& rPaM : pUnoCursor->GetRingContainer()) + { + lcl_ChkUnoCrsrPaMBoth(m_aUnoCursorEntries, nNode, nContent, rPaM); + } + const SwUnoTableCursor* pUnoTableCursor = dynamic_cast(pUnoCursor.get()); + if( pUnoTableCursor ) + { + for(SwPaM& rPaM : const_cast(pUnoTableCursor)->GetSelRing().GetRingContainer()) + { + lcl_ChkUnoCrsrPaMBoth(m_aUnoCursorEntries, nNode, nContent, rPaM); + } + } + } +} + +void ContentIdxStoreImpl::RestoreUnoCursors(updater_t const & rUpdater) +{ + for (const PaMEntry& aEntry : m_aUnoCursorEntries) + { + rUpdater(aEntry.m_pPaM->GetBound(!aEntry.m_isMark), aEntry.m_nContent); + } +} + +void ContentIdxStoreImpl::SaveShellCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + SwCursorShell* pShell = pDoc->GetEditShell(); + if( !pShell ) + return; + for(SwViewShell& rCurShell : pShell->GetRingContainer()) + { + if( dynamic_cast(&rCurShell) != nullptr ) + { + SwPaM *_pStackCursor = static_cast(&rCurShell)->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_ChkPaMBoth( m_aShellCursorEntries, nNode, nContent, *_pStackCursor); + if (!_pStackCursor) + break; + _pStackCursor = _pStackCursor->GetNext(); + if (_pStackCursor == static_cast(&rCurShell)->GetStackCursor()) + break; + } + + for(SwPaM& rPaM : static_cast(&rCurShell)->GetCursor_()->GetRingContainer()) + { + lcl_ChkPaMBoth( m_aShellCursorEntries, nNode, nContent, rPaM); + } + } + } +} + +void ContentIdxStoreImpl::RestoreShellCursors(updater_t const & rUpdater) +{ + for (const PaMEntry& aEntry : m_aShellCursorEntries) + { + rUpdater(aEntry.m_pPaM->GetBound(aEntry.m_isMark), aEntry.m_nContent); + } +} + +namespace sw::mark { + std::shared_ptr ContentIdxStore::Create() + { + return std::make_shared(); + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentChartDataProviderManager.cxx b/sw/source/core/doc/DocumentChartDataProviderManager.cxx new file mode 100644 index 000000000..90785725e --- /dev/null +++ b/sw/source/core/doc/DocumentChartDataProviderManager.cxx @@ -0,0 +1,106 @@ +/* -*- 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 namespace com::sun::star; +using namespace com::sun::star::uno; + +namespace sw { + +DocumentChartDataProviderManager::DocumentChartDataProviderManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), + maChartDataProviderImplRef() +{ + +} + +SwChartDataProvider * DocumentChartDataProviderManager::GetChartDataProvider( bool bCreate ) const +{ + // since there must be only one instance of this object per document + // we need a mutex here + SolarMutexGuard aGuard; + + if (bCreate && !maChartDataProviderImplRef.is()) + { + maChartDataProviderImplRef = new SwChartDataProvider( & m_rDoc ); + } + return maChartDataProviderImplRef.get(); +} + +void DocumentChartDataProviderManager::CreateChartInternalDataProviders( const SwTable *pTable ) +{ + if (pTable) + { + OUString aName( pTable->GetFrameFormat()->GetName() ); + SwOLENode *pONd; + SwStartNode *pStNd; + SwNodeIndex aIdx( *m_rDoc.GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while (nullptr != (pStNd = aIdx.GetNode().GetStartNode())) + { + ++aIdx; + pONd = aIdx.GetNode().GetOLENode(); + if( pONd && + aName == pONd->GetChartTableName() /* OLE node is chart? */ && + nullptr != (pONd->getLayoutFrame( m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout() )) /* chart frame is not hidden */ ) + { + uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + uno::Reference< chart2::XChartDocument > xChart( xIP->getComponent(), UNO_QUERY ); + if (xChart.is()) + xChart->createInternalDataProvider( true ); + + // there may be more than one chart for each table thus we need to continue the loop... + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + } +} + +SwChartLockController_Helper & DocumentChartDataProviderManager::GetChartControllerHelper() +{ + if (!mpChartControllerHelper) + { + mpChartControllerHelper.reset(new SwChartLockController_Helper( & m_rDoc )); + } + return *mpChartControllerHelper; +} + +DocumentChartDataProviderManager::~DocumentChartDataProviderManager() +{ + // clean up chart related structures... + // Note: the chart data provider gets already disposed in ~SwDocShell + // since all UNO API related functionality requires an existing SwDocShell + // this assures that dispose gets called if there is need for it. + maChartDataProviderImplRef.clear(); +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx new file mode 100644 index 000000000..17bbd2b56 --- /dev/null +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -0,0 +1,5138 @@ +/* -*- 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 + + +using namespace ::com::sun::star::i18n; + +namespace +{ + // Copy method from SwDoc + // Prevent copying into Flys that are anchored in the range + bool lcl_ChkFlyFly( SwDoc* pDoc, sal_uLong nSttNd, sal_uLong nEndNd, + sal_uLong nInsNd ) + { + const SwFrameFormats& rFrameFormatTable = *pDoc->GetSpzFrameFormats(); + + for( size_t n = 0; n < rFrameFormatTable.size(); ++n ) + { + SwFrameFormat const*const pFormat = rFrameFormatTable[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) && + nSttNd <= pAPos->nNode.GetIndex() && + pAPos->nNode.GetIndex() < nEndNd ) + { + const SwFormatContent& rContent = pFormat->GetContent(); + SwStartNode* pSNd; + if( !rContent.GetContentIdx() || + nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() )) + continue; + + if( pSNd->GetIndex() < nInsNd && + nInsNd < pSNd->EndOfSectionIndex() ) + // Do not copy ! + return true; + + if( lcl_ChkFlyFly( pDoc, pSNd->GetIndex(), + pSNd->EndOfSectionIndex(), nInsNd ) ) + // Do not copy ! + return true; + } + } + + return false; + } + + SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, sal_uLong & rDelCount) + { + SwNodeIndex const& rStart(rSourcePaM.Start()->nNode); + // Special handling for SwDoc::AppendDoc + if (rSourcePaM.GetDoc()->GetNodes().GetEndOfExtras().GetIndex() + 1 + == rStart.GetIndex()) + { + rDelCount = 1; + return SwNodeIndex(rStart, +1); + } + else + { + rDelCount = 0; + return rStart; + } + } + + /* + The CopyBookmarks function has to copy bookmarks from the source to the destination nodes + array. It is called after a call of the CopyNodes(..) function. But this function does not copy + every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied + if the corresponding end/start node is outside the copied pam. + The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node + index inside the pam. + rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number + of "non-copy" nodes between rPam.Start() and rLastIdx. + nNewIdx is the new position of interest. + */ + void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const sal_uLong nNewIdx, sal_uLong& rDelCount ) + { + sal_uLong nStart = rPam.Start()->nNode.GetIndex(); + sal_uLong nEnd = rPam.End()->nNode.GetIndex(); + if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward? + { + // We never copy the StartOfContent node + do // count "non-copy" nodes + { + SwNode& rNode = rLastIdx.GetNode(); + if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd ) + || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) ) + { + ++rDelCount; + } + ++rLastIdx; + } + while( rLastIdx.GetIndex() < nNewIdx ); + } + else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now, + // no move backward needed + { + while( rLastIdx.GetIndex() > nNewIdx ) + { + SwNode& rNode = rLastIdx.GetNode(); + if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd ) + || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) ) + { + --rDelCount; + } + rLastIdx--; + } + } + } + + void lcl_SetCpyPos( const SwPosition& rOrigPos, + const SwPosition& rOrigStt, + const SwPosition& rCpyStt, + SwPosition& rChgPos, + sal_uLong nDelCount ) + { + sal_uLong nNdOff = rOrigPos.nNode.GetIndex(); + nNdOff -= rOrigStt.nNode.GetIndex(); + nNdOff -= nDelCount; + sal_Int32 nContentPos = rOrigPos.nContent.GetIndex(); + + // Always adjust at to be changed instance + rChgPos.nNode = nNdOff + rCpyStt.nNode.GetIndex(); + if( !nNdOff ) + { + // just adapt the content index + if( nContentPos > rOrigStt.nContent.GetIndex() ) + nContentPos -= rOrigStt.nContent.GetIndex(); + else + nContentPos = 0; + nContentPos += rCpyStt.nContent.GetIndex(); + } + rChgPos.nContent.Assign( rChgPos.nNode.GetNode().GetContentNode(), nContentPos ); + } + +} + +namespace sw +{ + // TODO: use SaveBookmark (from DelBookmarks) + void CopyBookmarks(const SwPaM& rPam, SwPosition& rCpyPam) + { + const SwDoc* pSrcDoc = rPam.GetDoc(); + SwDoc* pDestDoc = rCpyPam.GetDoc(); + const IDocumentMarkAccess* const pSrcMarkAccess = pSrcDoc->getIDocumentMarkAccess(); + ::sw::UndoGuard const undoGuard(pDestDoc->GetIDocumentUndoRedo()); + + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + SwPosition const*const pCpyStt = &rCpyPam; + + std::vector< const ::sw::mark::IMark* > vMarksToCopy; + for ( IDocumentMarkAccess::const_iterator_t ppMark = pSrcMarkAccess->getAllMarksBegin(); + ppMark != pSrcMarkAccess->getAllMarksEnd(); + ++ppMark ) + { + const ::sw::mark::IMark* const pMark = *ppMark; + + const SwPosition& rMarkStart = pMark->GetMarkStart(); + const SwPosition& rMarkEnd = pMark->GetMarkEnd(); + // only include marks that are in the range and not touching both start and end + // - not for annotation or checkbox marks. + const bool bIsNotOnBoundary = + pMark->IsExpanded() + ? (rMarkStart != rStt || rMarkEnd != rEnd) // rMarkStart != rMarkEnd + : (rMarkStart != rStt && rMarkEnd != rEnd); // rMarkStart == rMarkEnd + const IDocumentMarkAccess::MarkType aMarkType = IDocumentMarkAccess::GetType(*pMark); + if ( rMarkStart >= rStt && rMarkEnd <= rEnd + && ( bIsNotOnBoundary + || aMarkType == IDocumentMarkAccess::MarkType::ANNOTATIONMARK + || aMarkType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + { + vMarksToCopy.push_back(pMark); + } + } + // We have to count the "non-copied" nodes... + sal_uLong nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + for(const sw::mark::IMark* const pMark : vMarksToCopy) + { + SwPaM aTmpPam(*pCpyStt); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().nNode.GetIndex(), nDelCount); + lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount); + if(pMark->IsExpanded()) + { + aTmpPam.SetMark(); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().nNode.GetIndex(), nDelCount); + lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount); + } + + ::sw::mark::IMark* const pNewMark = pDestDoc->getIDocumentMarkAccess()->makeMark( + aTmpPam, + pMark->GetName(), + IDocumentMarkAccess::GetType(*pMark), + ::sw::mark::InsertMode::CopyText); + // Explicitly try to get exactly the same name as in the source + // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name + pDestDoc->getIDocumentMarkAccess()->renameMark(pNewMark, pMark->GetName()); + + // copying additional attributes for bookmarks or fieldmarks + ::sw::mark::IBookmark* const pNewBookmark = + dynamic_cast< ::sw::mark::IBookmark* const >(pNewMark); + const ::sw::mark::IBookmark* const pOldBookmark = + dynamic_cast< const ::sw::mark::IBookmark* >(pMark); + if (pNewBookmark && pOldBookmark) + { + pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode()); + pNewBookmark->SetShortName(pOldBookmark->GetShortName()); + } + ::sw::mark::IFieldmark* const pNewFieldmark = + dynamic_cast< ::sw::mark::IFieldmark* const >(pNewMark); + const ::sw::mark::IFieldmark* const pOldFieldmark = + dynamic_cast< const ::sw::mark::IFieldmark* >(pMark); + if (pNewFieldmark && pOldFieldmark) + { + pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname()); + pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext()); + ::sw::mark::IFieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters(); + const ::sw::mark::IFieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters(); + for (const auto& rEntry : *pOldParams ) + { + pNewParams->insert( rEntry ); + } + } + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pMark)); + ::sfx2::Metadatable *const pNewMetadatable( + dynamic_cast< ::sfx2::Metadatable * >(pNewMark)); + if (pMetadatable && pNewMetadatable) + { + pNewMetadatable->RegisterAsCopyOf(*pMetadatable); + } + } + } +} // namespace sw + +namespace +{ + void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam ) + { + const SwDoc* pSrcDoc = rPam.GetDoc(); + const SwRedlineTable& rTable = pSrcDoc->getIDocumentRedlineAccess().GetRedlineTable(); + if( !rTable.empty() ) + { + SwDoc* pDestDoc = rCpyPam.GetDoc(); + SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End(); + std::unique_ptr pDelPam; + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + // We have to count the "non-copied" nodes + sal_uLong nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + + SwRedlineTable::size_type n = 0; + pSrcDoc->getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for( ; n < rTable.size(); ++n ) + { + const SwRangeRedline* pRedl = rTable[ n ]; + if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() ) + { + const SwPosition *pRStt = pRedl->Start(), *pREnd = pRedl->End(); + + SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); + switch( eCmpPos ) + { + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + // Pos1 is before Pos2 + break; + + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: + // Pos1 is after Pos2 + n = rTable.size(); + break; + + default: + { + pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() )); + if( *pStt < *pRStt ) + { + lcl_NonCopyCount( rPam, aCorrIdx, pRStt->nNode.GetIndex(), nDelCount ); + lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + pDelPam->SetMark(); + + if( *pEnd < *pREnd ) + *pDelPam->GetPoint() = *pCpyEnd; + else + { + lcl_NonCopyCount( rPam, aCorrIdx, pREnd->nNode.GetIndex(), nDelCount ); + lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + + if (pDelPam->GetNext() && *pDelPam->GetNext()->End() == *pDelPam->Start()) + { + *pDelPam->GetNext()->End() = *pDelPam->End(); + pDelPam.reset(pDelPam->GetNext()); + } + } + } + } + } + + if( pDelPam ) + { + RedlineFlags eOld = pDestDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDestDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore ); + + ::sw::UndoGuard const undoGuard(pDestDoc->GetIDocumentUndoRedo()); + + do { + pDestDoc->getIDocumentContentOperations().DeleteAndJoin( *pDelPam->GetNext() ); + if( !pDelPam->IsMultiSelection() ) + break; + delete pDelPam->GetNext(); + } while( true ); + + pDestDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + } + } + + void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg ) + { + SwDoc* pSrcDoc = rRg.aStart.GetNode().GetDoc(); + if( !pSrcDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd ); + lcl_DeleteRedlines( aRgTmp, aCpyTmp ); + } + } + + void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest ) + { + SwFormatChain aSrc( pSrc->GetChain() ); + if ( !aSrc.GetNext() ) + { + aSrc.SetNext( pDest ); + pSrc->SetFormatAttr( aSrc ); + } + SwFormatChain aDest( pDest->GetChain() ); + if ( !aDest.GetPrev() ) + { + aDest.SetPrev( pSrc ); + pDest->SetFormatAttr( aDest ); + } + } + + // #i86492# + bool lcl_ContainsOnlyParagraphsInList( const SwPaM& rPam ) + { + bool bRet = false; + + const SwTextNode* pTextNd = rPam.Start()->nNode.GetNode().GetTextNode(); + const SwTextNode* pEndTextNd = rPam.End()->nNode.GetNode().GetTextNode(); + if ( pTextNd && pTextNd->IsInList() && + pEndTextNd && pEndTextNd->IsInList() ) + { + bRet = true; + SwNodeIndex aIdx(rPam.Start()->nNode); + + do + { + ++aIdx; + pTextNd = aIdx.GetNode().GetTextNode(); + + if ( !pTextNd || !pTextNd->IsInList() ) + { + bRet = false; + break; + } + } while (pTextNd != pEndTextNd); + } + + return bRet; + } + + bool lcl_MarksWholeNode(const SwPaM & rPam) + { + bool bResult = false; + const SwPosition* pStt = rPam.Start(); + const SwPosition* pEnd = rPam.End(); + + if (nullptr != pStt && nullptr != pEnd) + { + const SwTextNode* pSttNd = pStt->nNode.GetNode().GetTextNode(); + const SwTextNode* pEndNd = pEnd->nNode.GetNode().GetTextNode(); + + if (nullptr != pSttNd && nullptr != pEndNd && + pStt->nContent.GetIndex() == 0 && + pEnd->nContent.GetIndex() == pEndNd->Len()) + { + bResult = true; + } + } + + return bResult; + } +} + +//local functions originally from sw/source/core/doc/docedt.cxx +namespace sw +{ + void CalcBreaks(std::vector> & rBreaks, + SwPaM const & rPam, bool const isOnlyFieldmarks) + { + sal_uLong const nStartNode(rPam.Start()->nNode.GetIndex()); + sal_uLong const nEndNode(rPam.End()->nNode.GetIndex()); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + IDocumentMarkAccess const& rIDMA(*rPam.GetDoc()->getIDocumentMarkAccess()); + + std::stack> startedFields; + + for (sal_uLong n = nStartNode; n <= nEndNode; ++n) + { + SwNode *const pNode(rNodes[n]); + if (pNode->IsTextNode()) + { + SwTextNode & rTextNode(*pNode->GetTextNode()); + sal_Int32 const nStart(n == nStartNode + ? rPam.Start()->nContent.GetIndex() + : 0); + sal_Int32 const nEnd(n == nEndNode + ? rPam.End()->nContent.GetIndex() + : rTextNode.Len()); + for (sal_Int32 i = nStart; i < nEnd; ++i) + { + const sal_Unicode c(rTextNode.GetText()[i]); + switch (c) + { + // note: CH_TXT_ATR_FORMELEMENT does not need handling + // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled + case CH_TXTATR_INWORD: + case CH_TXTATR_BREAKWORD: + { + // META hints only have dummy char at the start, not + // at the end, so no need to check in nStartNode + if (n == nEndNode && !isOnlyFieldmarks) + { + SwTextAttr const*const pAttr(rTextNode.GetTextAttrForCharAt(i)); + if (pAttr && pAttr->End() && (nEnd < *pAttr->End())) + { + assert(pAttr->HasDummyChar()); + rBreaks.emplace_back(n, i); + } + } + break; + } + case CH_TXT_ATR_FIELDSTART: + { + auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i))); + startedFields.emplace(pFieldMark, false, 0, 0); + break; + } + case CH_TXT_ATR_FIELDSEP: + { + if (startedFields.empty()) + { + rBreaks.emplace_back(n, i); + } + else + { // no way to find the field via MarkManager... + assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i))); + std::get<1>(startedFields.top()) = true; + std::get<2>(startedFields.top()) = n; + std::get<3>(startedFields.top()) = i; + } + break; + } + case CH_TXT_ATR_FIELDEND: + { + if (startedFields.empty()) + { + rBreaks.emplace_back(n, i); + } + else + { // fieldmarks must not overlap => stack + assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i))); + startedFields.pop(); + } + break; + } + } + } + } + else if (pNode->IsStartNode()) + { + if (pNode->EndOfSectionIndex() <= nEndNode) + { // fieldmark cannot overlap node section + n = pNode->EndOfSectionIndex(); + } + } + else + { // EndNode can actually happen with sections :( + assert(pNode->IsEndNode() || pNode->IsNoTextNode()); + } + } + while (!startedFields.empty()) + { + SwPosition const& rStart(std::get<0>(startedFields.top())->GetMarkStart()); + std::pair const pos( + rStart.nNode.GetIndex(), rStart.nContent.GetIndex()); + auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos); + assert(it == rBreaks.end() || *it != pos); + rBreaks.insert(it, pos); + if (std::get<1>(startedFields.top())) + { + std::pair const posSep( + std::get<2>(startedFields.top()), + std::get<3>(startedFields.top())); + it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep); + assert(it == rBreaks.end() || *it != posSep); + rBreaks.insert(it, posSep); + } + startedFields.pop(); + } + } +} + +namespace +{ + + bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations, SwPaM & rPam, + bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, bool), const bool bForceJoinNext = false) + { + std::vector> Breaks; + + sw::CalcBreaks(Breaks, rPam); + + if (Breaks.empty()) + { + return (rDocumentContentOperations.*pFunc)(rPam, bForceJoinNext); + } + + // Deletion must be split into several parts if the text node + // contains a text attribute with end and with dummy character + // and the selection does not contain the text attribute completely, + // but overlaps its start (left), where the dummy character is. + + SwPosition const & rSelectionEnd( *rPam.End() ); + + bool bRet( true ); + // iterate from end to start, to avoid invalidating the offsets! + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + } + + return bRet; + } + + bool lcl_StrLenOverflow( const SwPaM& rPam ) + { + // If we try to merge two paragraphs we have to test if afterwards + // the string doesn't exceed the allowed string length + if( rPam.GetPoint()->nNode != rPam.GetMark()->nNode ) + { + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + const SwTextNode* pEndNd = pEnd->nNode.GetNode().GetTextNode(); + if( (nullptr != pEndNd) && pStt->nNode.GetNode().IsTextNode() ) + { + const sal_uInt64 nSum = pStt->nContent.GetIndex() + + pEndNd->GetText().getLength() - pEnd->nContent.GetIndex(); + return nSum > o3tl::make_unsigned(SAL_MAX_INT32); + } + } + return false; + } + + struct SaveRedline + { + SwRangeRedline* pRedl; + sal_uInt32 nStt, nEnd; + sal_Int32 nSttCnt; + sal_Int32 nEndCnt; + + SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + const SwPosition* pStt = pR->Start(), + * pEnd = pR->GetMark() == pStt ? pR->GetPoint() : pR->GetMark(); + sal_uInt32 nSttIdx = rSttIdx.GetIndex(); + nStt = pStt->nNode.GetIndex() - nSttIdx; + nSttCnt = pStt->nContent.GetIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->nNode.GetIndex() - nSttIdx; + nEndCnt = pEnd->nContent.GetIndex(); + } + + pRedl->GetPoint()->nNode = 0; + pRedl->GetPoint()->nContent.Assign( nullptr, 0 ); + pRedl->GetMark()->nNode = 0; + pRedl->GetMark()->nContent.Assign( nullptr, 0 ); + } + + SaveRedline( SwRangeRedline* pR, const SwPosition& rPos ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + const SwPosition* pStt = pR->Start(), + * pEnd = pR->GetMark() == pStt ? pR->GetPoint() : pR->GetMark(); + sal_uInt32 nSttIdx = rPos.nNode.GetIndex(); + nStt = pStt->nNode.GetIndex() - nSttIdx; + nSttCnt = pStt->nContent.GetIndex(); + if( nStt == 0 ) + nSttCnt = nSttCnt - rPos.nContent.GetIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->nNode.GetIndex() - nSttIdx; + nEndCnt = pEnd->nContent.GetIndex(); + if( nEnd == 0 ) + nEndCnt = nEndCnt - rPos.nContent.GetIndex(); + } + + pRedl->GetPoint()->nNode = 0; + pRedl->GetPoint()->nContent.Assign( nullptr, 0 ); + pRedl->GetMark()->nNode = 0; + pRedl->GetMark()->nContent.Assign( nullptr, 0 ); + } + + void SetPos( sal_uInt32 nInsPos ) + { + pRedl->GetPoint()->nNode = nInsPos + nStt; + pRedl->GetPoint()->nContent.Assign( pRedl->GetContentNode(), nSttCnt ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->nNode = nInsPos + nEnd; + pRedl->GetMark()->nContent.Assign( pRedl->GetContentNode(false), nEndCnt ); + } + } + + void SetPos( const SwPosition& aPos ) + { + pRedl->GetPoint()->nNode = aPos.nNode.GetIndex() + nStt; + pRedl->GetPoint()->nContent.Assign( pRedl->GetContentNode(), nSttCnt + ( nStt == 0 ? aPos.nContent.GetIndex() : 0 ) ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->nNode = aPos.nNode.GetIndex() + nEnd; + pRedl->GetMark()->nContent.Assign( pRedl->GetContentNode(false), nEndCnt + ( nEnd == 0 ? aPos.nContent.GetIndex() : 0 ) ); + } + } + }; + + typedef std::vector< SaveRedline > SaveRedlines_t; + + void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr) + { + SwDoc* pDoc = aPam.GetNode().GetDoc(); + + const SwPosition* pStart = aPam.Start(); + const SwPosition* pEnd = aPam.End(); + + // get first relevant redline + SwRedlineTable::size_type nCurrentRedline; + pDoc->getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline ); + if( nCurrentRedline > 0) + nCurrentRedline--; + + // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + // iterate over relevant redlines and decide for each whether it should + // be saved, or split + saved + SwRedlineTable& rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( ; nCurrentRedline < rRedlineTable.size(); nCurrentRedline++ ) + { + SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ]; + SwComparePosition eCompare = + ComparePosition( *pCurrent->Start(), *pCurrent->End(), + *pStart, *pEnd); + + // we must save this redline if it overlaps aPam + // (we may have to split it, too) + if( eCompare == SwComparePosition::OverlapBehind || + eCompare == SwComparePosition::OverlapBefore || + eCompare == SwComparePosition::Outside || + eCompare == SwComparePosition::Inside || + eCompare == SwComparePosition::Equal ) + { + rRedlineTable.Remove( nCurrentRedline-- ); + + // split beginning, if necessary + if( eCompare == SwComparePosition::OverlapBefore || + eCompare == SwComparePosition::Outside ) + { + SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent ); + *pNewRedline->End() = *pStart; + *pCurrent->Start() = *pStart; + pDoc->getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // split end, if necessary + if( eCompare == SwComparePosition::OverlapBehind || + eCompare == SwComparePosition::Outside ) + { + SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent ); + *pNewRedline->Start() = *pEnd; + *pCurrent->End() = *pEnd; + pDoc->getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // save the current redline + rArr.emplace_back( pCurrent, *pStart ); + } + } + + // restore old redline mode + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc* pDoc, const SwPosition& rPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( rPos ); + pDoc->getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr) + { + SwDoc* pDoc = rRg.aStart.GetNode().GetDoc(); + SwRedlineTable::size_type nRedlPos; + SwPosition aSrchPos( rRg.aStart ); aSrchPos.nNode--; + aSrchPos.nContent.Assign( aSrchPos.nNode.GetNode().GetContentNode(), 0 ); + if( pDoc->getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos ) + --nRedlPos; + else if( nRedlPos >= pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ) + return ; + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + SwRedlineTable& rRedlTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + + do { + SwRangeRedline* pTmp = rRedlTable[ nRedlPos ]; + + const SwPosition* pRStt = pTmp->Start(), + * pREnd = pTmp->GetMark() == pRStt + ? pTmp->GetPoint() : pTmp->GetMark(); + + if( pRStt->nNode < rRg.aStart ) + { + if( pREnd->nNode > rRg.aStart && pREnd->nNode < rRg.aEnd ) + { + // Create a copy and set the end of the original to the end of the MoveArea. + // The copy is moved too. + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->Start(); + pTmpPos->nNode = rRg.aStart; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + + rArr.emplace_back(pNewRedl, rRg.aStart); + + pTmpPos = pTmp->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + } + else if( pREnd->nNode == rRg.aStart ) + { + SwPosition* pTmpPos = pTmp->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + } + } + else if( pRStt->nNode < rRg.aEnd ) + { + rRedlTable.Remove( nRedlPos-- ); + if( pREnd->nNode < rRg.aEnd || + ( pREnd->nNode == rRg.aEnd && !pREnd->nContent.GetIndex()) ) + { + // move everything + rArr.emplace_back( pTmp, rRg.aStart ); + } + else + { + // split + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + + rArr.emplace_back( pNewRedl, rRg.aStart ); + + pTmpPos = pTmp->Start(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + pDoc->getIDocumentRedlineAccess().AppendRedline( pTmp, true ); + } + } + else + break; + + } while( ++nRedlPos < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc *const pDoc, sal_uInt32 const nInsPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( nInsPos ); + pDoc->getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + if (rSvRedLine.pRedl->GetType() == RedlineType::Delete) + { + UpdateFramesForAddDeleteRedline(*pDoc, *rSvRedLine.pRedl); + } + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + bool lcl_SaveFootnote( const SwNodeIndex& rSttNd, const SwNodeIndex& rEndNd, + const SwNodeIndex& rInsPos, + SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr, + const SwIndex* pSttCnt = nullptr, const SwIndex* pEndCnt = nullptr ) + { + bool bUpdateFootnote = false; + const SwNodes& rNds = rInsPos.GetNodes(); + const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() && + rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex(); + const bool bSaveFootnote = !bDelFootnote && + rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex(); + if( !rFootnoteArr.empty() ) + { + + size_t nPos = 0; + rFootnoteArr.SeekEntry( rSttNd, &nPos ); + SwTextFootnote* pSrch; + const SwNode* pFootnoteNd; + + // Delete/save all that come after it + while( nPos < rFootnoteArr.size() && ( pFootnoteNd = + &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex() + <= rEndNd.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( ( pEndCnt && pSttCnt ) + ? (( &rSttNd.GetNode() == pFootnoteNd && + pSttCnt->GetIndex() > nFootnoteSttIdx) || + ( &rEndNd.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEndCnt->GetIndex() )) + : ( &rEndNd.GetNode() == pFootnoteNd )) + { + ++nPos; // continue searching + } + else + { + // delete it + if( bDelFootnote ) + { + SwTextNode& rTextNd = const_cast(pSrch->GetTextNode()); + SwIndex aIdx( &rTextNd, nFootnoteSttIdx ); + rTextNd.EraseText( aIdx, 1 ); + } + else + { + pSrch->DelFrames(nullptr); + rFootnoteArr.erase( rFootnoteArr.begin() + nPos ); + if( bSaveFootnote ) + rSaveArr.insert( pSrch ); + } + bUpdateFootnote = true; + } + } + + while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )-> + GetTextNode())->GetIndex() >= rSttNd.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( !pEndCnt || !pSttCnt || + ! (( &rSttNd.GetNode() == pFootnoteNd && + pSttCnt->GetIndex() > nFootnoteSttIdx ) || + ( &rEndNd.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEndCnt->GetIndex() )) ) + { + if( bDelFootnote ) + { + // delete it + SwTextNode& rTextNd = const_cast(pSrch->GetTextNode()); + SwIndex aIdx( &rTextNd, nFootnoteSttIdx ); + rTextNd.EraseText( aIdx, 1 ); + } + else + { + pSrch->DelFrames(nullptr); + rFootnoteArr.erase( rFootnoteArr.begin() + nPos ); + if( bSaveFootnote ) + rSaveArr.insert( pSrch ); + } + bUpdateFootnote = true; + } + } + } + // When moving from redline section into document content section, e.g. + // after loading a document with (delete-)redlines, the footnote array + // has to be adjusted... (#i70572) + if( bSaveFootnote ) + { + SwNodeIndex aIdx( rSttNd ); + while( aIdx < rEndNd ) // Check the moved section + { + SwNode* pNode = &aIdx.GetNode(); + if( pNode->IsTextNode() ) // Looking for text nodes... + { + SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints(); + if( pHints && pHints->HasFootnote() ) //...with footnotes + { + bUpdateFootnote = true; // Heureka + const size_t nCount = pHints->Count(); + for( size_t i = 0; i < nCount; ++i ) + { + SwTextAttr *pAttr = pHints->Get( i ); + if ( pAttr->Which() == RES_TXTATR_FTN ) + { + rSaveArr.insert( static_cast(pAttr) ); + } + } + } + } + ++aIdx; + } + } + return bUpdateFootnote; + } + + bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos ) + { + sal_Unicode const cChr = pNode->GetText()[nPos]; + switch (cChr) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + return !pNode->GetTextAttrForCharAt(nPos);// how could there be none? + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + case CH_TXT_ATR_FORMELEMENT: + return false; + default: + return true; + } + } + + void lcl_SkipAttr( const SwTextNode *pNode, SwIndex &rIdx, sal_Int32 &rStart ) + { + if( !lcl_MayOverwrite( pNode, rStart ) ) + { + // skip all special attributes + do { + ++rIdx; + rStart = rIdx.GetIndex(); + } while (rStart < pNode->GetText().getLength() + && !lcl_MayOverwrite(pNode, rStart) ); + } + } + + bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc ) + { + if( bRegExpRplc ) + { + sal_Int32 nPos = 0; + const OUString sPara("\\n"); + for (;;) + { + nPos = rStr.indexOf( sPara, nPos ); + if (nPos<0) + { + break; + } + // Has this been escaped? + if( nPos && '\\' == rStr[nPos-1]) + { + ++nPos; + if( nPos >= rStr.getLength() ) + { + break; + } + } + else + { + rRet = rStr.copy( 0, nPos ); + rStr = rStr.copy( nPos + sPara.getLength() ); + return true; + } + } + } + rRet = rStr; + rStr.clear(); + return false; + } +} + +namespace //local functions originally from docfmt.cxx +{ + + bool lcl_ApplyOtherSet( + SwContentNode & rNode, + SwHistory *const pHistory, + SfxItemSet const& rOtherSet, + SfxItemSet const& rFirstSet, + SfxItemSet const& rPropsSet, + SwRootFrame const*const pLayout, + SwNodeIndex *const o_pIndex = nullptr) + { + assert(rOtherSet.Count()); + + bool ret(false); + SwTextNode *const pTNd = rNode.GetTextNode(); + sw::MergedPara const* pMerged(nullptr); + if (pLayout && pLayout->IsHideRedlines() && pTNd) + { + SwTextFrame const*const pTextFrame(static_cast( + pTNd->getLayoutFrame(pLayout))); + if (pTextFrame) + { + pMerged = pTextFrame->GetMergedPara(); + } + if (pMerged) + { + if (rFirstSet.Count()) + { + if (pHistory) + { + SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory); + ret = pMerged->pFirstNode->SetAttr(rFirstSet); + } + else + { + ret = pMerged->pFirstNode->SetAttr(rFirstSet); + } + } + if (rPropsSet.Count()) + { + if (pHistory) + { + SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory); + ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret; + } + else + { + ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret; + } + } + if (o_pIndex) + { + *o_pIndex = *pMerged->pLastNode; // skip hidden + } + } + } + + // input cursor can't be on hidden node, and iteration skips them + assert(!pLayout || !pLayout->IsHideRedlines() + || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden); + + if (!pMerged) + { + if (pHistory) + { + SwRegHistory aRegH(&rNode, rNode, pHistory); + ret = rNode.SetAttr( rOtherSet ); + } + else + { + ret = rNode.SetAttr( rOtherSet ); + } + } + return ret; + } + + #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; } + + /// Insert Hints according to content types; + // Is used in SwDoc::Insert(..., SwFormatHint &rHt) + + bool lcl_InsAttr( + SwDoc *const pDoc, + const SwPaM &rRg, + const SfxItemSet& rChgSet, + const SetAttrMode nFlags, + SwUndoAttr *const pUndo, + SwRootFrame const*const pLayout, + const bool bExpandCharToPara, + SwTextAttr **ppNewTextAttr) + { + // Divide the Sets (for selections in Nodes) + const SfxItemSet* pCharSet = nullptr; + const SfxItemSet* pOtherSet = nullptr; + bool bDelete = false; + bool bCharAttr = false; + bool bOtherAttr = false; + + // Check, if we can work with rChgSet or if we have to create additional SfxItemSets + if ( 1 == rChgSet.Count() ) + { + SfxItemIter aIter( rChgSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + if (pItem && !IsInvalidItem(pItem)) + { + const sal_uInt16 nWhich = pItem->Which(); + + if ( isCHRATR(nWhich) || + (RES_TXTATR_CHARFMT == nWhich) || + (RES_TXTATR_INETFMT == nWhich) || + (RES_TXTATR_AUTOFMT == nWhich) || + (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) ) + { + pCharSet = &rChgSet; + bCharAttr = true; + } + + if ( isPARATR(nWhich) + || isPARATR_LIST(nWhich) + || isFRMATR(nWhich) + || isGRFATR(nWhich) + || isUNKNOWNATR(nWhich) + || isDrawingLayerAttribute(nWhich) ) + { + pOtherSet = &rChgSet; + bOtherAttr = true; + } + } + } + + // Build new itemset if either + // - rChgSet.Count() > 1 or + // - The attribute in rChgSet does not belong to one of the above categories + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSet* pTmpCharItemSet = new SfxItemSet( + pDoc->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT, + RES_TXTATR_UNKNOWN_CONTAINER, + RES_TXTATR_UNKNOWN_CONTAINER>{}); + + SfxItemSet* pTmpOtherItemSet = new SfxItemSet( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_BEGIN, RES_GRFATR_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1, + // FillAttribute support: + XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + + pTmpCharItemSet->Put( rChgSet ); + pTmpOtherItemSet->Put( rChgSet ); + + pCharSet = pTmpCharItemSet; + pOtherSet = pTmpOtherItemSet; + + bDelete = true; + } + + SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr; + bool bRet = false; + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + SwContentNode* pNode = pStt->nNode.GetNode().GetContentNode(); + + if( pNode && pNode->IsTextNode() ) + { + // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol + if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()-> + get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING )) + { + SwContentNode* pEndNode = pEnd->nNode.GetNode().GetContentNode(); + SwContentNode* pCurrentNode = pEndNode; + auto nStartIndex = pNode->GetIndex(); + auto nEndIndex = pEndNode->GetIndex(); + SwNodeIndex aIdx( pEnd->nNode.GetNode() ); + while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() ) + { + if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) && + // remove character formatting only on wholly selected paragraphs + (nStartIndex < pCurrentNode->GetIndex() || pStt->nContent.GetIndex() == 0) && + (pCurrentNode->GetIndex() < nEndIndex || pEnd->nContent.GetIndex() == pEndNode->Len())) + { + pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT); + // reset also paragraph marker + SwIndex nIdx( pCurrentNode, pCurrentNode->Len() ); + pCurrentNode->GetTextNode()->RstTextAttr(nIdx, 1); + } + pCurrentNode = SwNodes::GoPrevious( &aIdx ); + } + } + // #i27615# + if (rRg.IsInFrontOfLabel()) + { + SwTextNode * pTextNd = pNode->GetTextNode(); + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd); + } + SwNumRule * pNumRule = pTextNd->GetNumRule(); + + if ( !pNumRule ) + { + OSL_FAIL( " - PaM in front of label, but text node has no numbering rule set. This is a serious defect." ); + DELETECHARSETS + return false; + } + + int nLevel = pTextNd->GetActualListLevel(); + + if (nLevel < 0) + nLevel = 0; + + if (nLevel >= MAXLEVEL) + nLevel = MAXLEVEL - 1; + + SwNumFormat aNumFormat = pNumRule->Get(static_cast(nLevel)); + SwCharFormat * pCharFormat = + pDoc->FindCharFormatByName(aNumFormat.GetCharFormatName()); + + if (pCharFormat) + { + if (pHistory) + pHistory->Add(pCharFormat->GetAttrSet(), *pCharFormat); + + if ( pCharSet ) + pCharFormat->SetFormatAttr(*pCharSet); + } + + DELETECHARSETS + return true; + } + + const SwIndex& rSt = pStt->nContent; + + // Attributes without an end do not have a range + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSet aTextSet( pDoc->GetAttrPool(), + svl::Items{} ); + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( + aTextSet, rSt.GetIndex(), rSt.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet; + + if (bRet && (pDoc->getIDocumentRedlineAccess().IsRedlineOn() || (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() + && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + SwPaM aPam( pStt->nNode, pStt->nContent.GetIndex()-1, + pStt->nNode, pStt->nContent.GetIndex() ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, true ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + pDoc->getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + } + + // TextAttributes with an end never expand their range + if ( !bCharAttr && !bOtherAttr ) + { + // CharFormat and URL attributes are treated separately! + // TEST_TEMP ToDo: AutoFormat! + SfxItemSet aTextSet( + pDoc->GetAttrPool(), + svl::Items< + RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD, + RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY, + RES_TXTATR_INPUTFIELD, RES_TXTATR_INPUTFIELD>{}); + + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + const sal_Int32 nInsCnt = rSt.GetIndex(); + const sal_Int32 nEnd = pStt->nNode == pEnd->nNode + ? pEnd->nContent.GetIndex() + : pNode->Len(); + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr ) + || bRet; + + if (bRet && (pDoc->getIDocumentRedlineAccess().IsRedlineOn() || (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() + && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + // Was text content inserted? (RefMark/TOXMarks without an end) + bool bTextIns = nInsCnt != rSt.GetIndex(); + // Was content inserted or set over the selection? + SwPaM aPam( pStt->nNode, bTextIns ? nInsCnt + 1 : nEnd, + pStt->nNode, nInsCnt ); + if( pUndo ) + pUndo->SaveRedlineData( aPam, bTextIns ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( + bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ), + true); + else if( bTextIns ) + pDoc->getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + } + } + + // We always have to set the auto flag for PageDescs that are set at the Node! + if( pOtherSet && pOtherSet->Count() ) + { + SwTableNode* pTableNd; + const SwFormatPageDesc* pDesc; + if( SfxItemState::SET == pOtherSet->GetItemState( RES_PAGEDESC, + false, reinterpret_cast(&pDesc) )) + { + if( pNode ) + { + // Set auto flag. Only in the template it's without auto! + SwFormatPageDesc aNew( *pDesc ); + + // Tables now also know line breaks + if( !(nFlags & SetAttrMode::APICALL) && + nullptr != ( pTableNd = pNode->FindTableNode() ) ) + { + SwTableNode* pCurTableNd = pTableNd; + while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) ) + pTableNd = pCurTableNd; + + // set the table format + SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat(); + SwRegHistory aRegH( pFormat, *pTableNd, pHistory ); + pFormat->SetFormatAttr( aNew ); + bRet = true; + } + else + { + SwContentNode * pFirstNode(pNode); + if (pLayout && pLayout->IsHideRedlines()) + { + pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->nNode).first; + } + SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory ); + bRet = pFirstNode->SetAttr( aNew ) || bRet; + } + } + + // bOtherAttr = true means that pOtherSet == rChgSet. In this case + // we know, that there is only one attribute in pOtherSet. We cannot + // perform the following operations, instead we return: + if ( bOtherAttr ) + return bRet; + + const_cast(pOtherSet)->ClearItem( RES_PAGEDESC ); + if( !pOtherSet->Count() ) + { + DELETECHARSETS + return bRet; + } + } + + // Tables now also know line breaks + const SvxFormatBreakItem* pBreak; + if( pNode && !(nFlags & SetAttrMode::APICALL) && + nullptr != (pTableNd = pNode->FindTableNode() ) && + SfxItemState::SET == pOtherSet->GetItemState( RES_BREAK, + false, reinterpret_cast(&pBreak) ) ) + { + SwTableNode* pCurTableNd = pTableNd; + while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) ) + pTableNd = pCurTableNd; + + // set the table format + SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat(); + SwRegHistory aRegH( pFormat, *pTableNd, pHistory ); + pFormat->SetFormatAttr( *pBreak ); + bRet = true; + + // bOtherAttr = true means that pOtherSet == rChgSet. In this case + // we know, that there is only one attribute in pOtherSet. We cannot + // perform the following operations, instead we return: + if ( bOtherAttr ) + return bRet; + + const_cast(pOtherSet)->ClearItem( RES_BREAK ); + if( !pOtherSet->Count() ) + { + DELETECHARSETS + return bRet; + } + } + + { + // If we have a PoolNumRule, create it if needed + const SwNumRuleItem* pRule; + sal_uInt16 nPoolId=0; + if( SfxItemState::SET == pOtherSet->GetItemState( RES_PARATR_NUMRULE, + false, reinterpret_cast(&pRule) ) && + !pDoc->FindNumRulePtr( pRule->GetValue() ) && + USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(), + SwGetPoolIdFromName::NumRule )) ) + pDoc->getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId ); + } + } + + SfxItemSet firstSet(pDoc->GetAttrPool(), + svl::Items{}); + if (pOtherSet && pOtherSet->Count()) + { // actually only RES_BREAK is possible here... + firstSet.Put(*pOtherSet); + } + SfxItemSet propsSet(pDoc->GetAttrPool(), + svl::Items{}); + if (pOtherSet && pOtherSet->Count()) + { + propsSet.Put(*pOtherSet); + } + + if( !rRg.HasMark() ) // no range + { + if( !pNode ) + { + DELETECHARSETS + return bRet; + } + + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwTextNode* pTextNd = pNode->GetTextNode(); + const SwIndex& rSt = pStt->nContent; + sal_Int32 nMkPos, nPtPos = rSt.GetIndex(); + const OUString& rStr = pTextNd->GetText(); + + // Special case: if the Cursor is located within a URL attribute, we take over it's area + SwTextAttr const*const pURLAttr( + pTextNd->GetTextAttrAt(rSt.GetIndex(), RES_TXTATR_INETFMT)); + if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty()) + { + nMkPos = pURLAttr->GetStart(); + nPtPos = *pURLAttr->End(); + } + else + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTextNd->GetText(), nPtPos, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos ) + { + nMkPos = aBndry.startPos; + nPtPos = aBndry.endPos; + } + else + nPtPos = nMkPos = rSt.GetIndex(); + } + + // Remove the overriding attributes from the SwpHintsArray, + // if the selection spans across the whole paragraph. + // These attributes are inserted as FormatAttributes and + // never override the TextAttributes! + if( !(nFlags & SetAttrMode::DONTREPLACE ) && + pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength()) + { + SwIndex aSt( pTextNd ); + if( pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNd, pHistory ); + pTextNd->GetpSwpHints()->Register( &aRHst ); + pTextNd->RstTextAttr( aSt, nPtPos, 0, pCharSet ); + if( pTextNd->GetpSwpHints() ) + pTextNd->GetpSwpHints()->DeRegister(); + } + else + pTextNd->RstTextAttr( aSt, nPtPos, 0, pCharSet ); + } + + // the SwRegHistory inserts the attribute into the TextNode! + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr ) + || bRet; + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, false ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Format, aPam ), true); + } + } + if( pOtherSet && pOtherSet->Count() ) + { + // Need to check for unique item for DrawingLayer items of type NameOrIndex + // and evtl. correct that item to ensure unique names for that type. This call may + // modify/correct entries inside of the given SfxItemSet + SfxItemSet aTempLocalCopy(*pOtherSet); + + pDoc->CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy); + bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet; + } + + DELETECHARSETS + return bRet; + } + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() ) + { + if( pUndo ) + pUndo->SaveRedlineData( rRg, false ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Format, rRg ), true); + } + + /* now if range */ + sal_uLong nNodes = 0; + + SwNodeIndex aSt( pDoc->GetNodes() ); + SwNodeIndex aEnd( pDoc->GetNodes() ); + SwIndex aCntEnd( pEnd->nContent ); + + if( pNode ) + { + const sal_Int32 nLen = pNode->Len(); + if( pStt->nNode != pEnd->nNode ) + aCntEnd.Assign( pNode, nLen ); + + if( pStt->nContent.GetIndex() != 0 || aCntEnd.GetIndex() != nLen ) + { + // the SwRegHistory inserts the attribute into the TextNode! + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems(*pCharSet, + pStt->nContent.GetIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr) + || bRet; + } + + if( pOtherSet && pOtherSet->Count() ) + { + bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet; + } + + // Only selection in a Node. + if( pStt->nNode == pEnd->nNode ) + { + //The data parameter flag: bExpandCharToPara, comes from the data member of SwDoc, + //which is set in SW MS Word Binary filter WW8ImplRreader. With this flag on, means that + //current setting attribute set is a character range properties set and comes from a MS Word + //binary file, and the setting range include a paragraph end position (0X0D); + //more specifications, as such property inside the character range properties set recorded in + //MS Word binary file are dealt and inserted into data model (SwDoc) one by one, so we + //only dealing the scenario that the char properties set with 1 item inside; + + if (bExpandCharToPara && pCharSet && pCharSet->Count() ==1 ) + { + SwTextNode* pCurrentNd = pStt->nNode.GetNode().GetTextNode(); + + if (pCurrentNd) + { + pCurrentNd->TryCharSetExpandToNum(*pCharSet); + + } + } + DELETECHARSETS + return bRet; + } + ++nNodes; + aSt.Assign( pStt->nNode.GetNode(), +1 ); + } + else + aSt = pStt->nNode; + aCntEnd = pEnd->nContent; // aEnd was changed! + } + else + aSt.Assign( pStt->nNode.GetNode(), +1 ); + + // aSt points to the first full Node now + + /* + * The selection spans more than one Node. + */ + if( pStt->nNode < pEnd->nNode ) + { + pNode = pEnd->nNode.GetNode().GetContentNode(); + if(pNode) + { + if( aCntEnd.GetIndex() != pNode->Len() ) + { + // the SwRegHistory inserts the attribute into the TextNode! + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + (void)history.InsertItems(*pCharSet, + 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr); + } + + if( pOtherSet && pOtherSet->Count() ) + { + lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout); + } + + ++nNodes; + aEnd = pEnd->nNode; + } + else + aEnd.Assign( pEnd->nNode.GetNode(), +1 ); + } + else + aEnd = pEnd->nNode; + } + else + aEnd.Assign( pEnd->nNode.GetNode(), +1 ); + + // aEnd points BEHIND the last full node now + + /* Edit the fully selected Nodes. */ + // Reset all attributes from the set! + if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) ) + { + ::sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHistory, pCharSet, pLayout); + pDoc->GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + } + + bool bCreateSwpHints = pCharSet && ( + SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) || + SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) ); + + for (SwNodeIndex current = aSt; current < aEnd; ++current) + { + SwTextNode *const pTNd = current.GetNode().GetTextNode(); + if (!pTNd) + continue; + + if (pLayout && pLayout->IsHideRedlines() + && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { // not really sure what to do here, but applying to hidden + continue; // nodes doesn't make sense... + } + + if( pHistory ) + { + SwRegHistory aRegH( pTNd, *pTNd, pHistory ); + + if (pCharSet && pCharSet->Count()) + { + SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints() + : pTNd->GetpSwpHints(); + if( pSwpHints ) + pSwpHints->Register( &aRegH ); + + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + if( pSwpHints ) + pSwpHints->DeRegister(); + } + } + else + { + if (pCharSet && pCharSet->Count()) + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + } + ++nNodes; + } + + if (pOtherSet && pOtherSet->Count()) + { + for (; aSt < aEnd; ++aSt) + { + pNode = aSt.GetNode().GetContentNode(); + if (!pNode) + continue; + + lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt); + ++nNodes; + } + } + + //The data parameter flag: bExpandCharToPara, comes from the data member of SwDoc, + //which is set in SW MS Word Binary filter WW8ImplRreader. With this flag on, means that + //current setting attribute set is a character range properties set and comes from a MS Word + //binary file, and the setting range include a paragraph end position (0X0D); + //more specifications, as such property inside the character range properties set recorded in + //MS Word binary file are dealt and inserted into data model (SwDoc) one by one, so we + //only dealing the scenario that the char properties set with 1 item inside; + if (bExpandCharToPara && pCharSet && pCharSet->Count() ==1) + { + SwPosition aStartPos (*rRg.Start()); + SwPosition aEndPos (*rRg.End()); + + if (aEndPos.nNode.GetNode().GetTextNode() && aEndPos.nContent != aEndPos.nNode.GetNode().GetTextNode()->Len()) + aEndPos.nNode--; + + sal_uLong nStart = aStartPos.nNode.GetIndex(); + sal_uLong nEnd = aEndPos.nNode.GetIndex(); + for(; nStart <= nEnd; ++nStart) + { + SwNode* pNd = pDoc->GetNodes()[ nStart ]; + if (!pNd || !pNd->IsTextNode()) + continue; + SwTextNode *pCurrentNd = pNd->GetTextNode(); + pCurrentNd->TryCharSetExpandToNum(*pCharSet); + } + } + + DELETECHARSETS + return (nNodes != 0) || bRet; + } +} + +namespace sw +{ + +namespace mark +{ + bool IsFieldmarkOverlap(SwPaM const& rPaM) + { + std::vector> Breaks; + sw::CalcBreaks(Breaks, rPaM); + return !Breaks.empty(); + } +} + +DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +/** + * Checks if rStart..rEnd mark a range that makes sense to copy. + * + * IsMoveToFly means the copy is a move to create a fly + * and so existing flys at the edge must not be copied. + */ +static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd, + SwCopyFlags const flags) +{ + if (rStart == rEnd) + { // check if a fly anchored there would be copied - then copy... + return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd, + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::WriterfilterHack|DelContentType::AllMask + : DelContentType::AllMask); + } + else + { + return rEnd < rStart; + } +} + +// Copy an area into this document or into another document +bool +DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags) const +{ + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); + bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); + + // Catch if there's no copy to do + if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel)) + return false; + + // Prevent copying into Flys that are anchored in the source range + if (pDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly)) + { + // Correct the Start-/EndNode + sal_uLong nStt = pStt->nNode.GetIndex(), + nEnd = pEnd->nNode.GetIndex(), + nDiff = nEnd - nStt +1; + SwNode* pNd = m_rDoc.GetNodes()[ nStt ]; + if( pNd->IsContentNode() && pStt->nContent.GetIndex() ) + { + ++nStt; + --nDiff; + } + if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() && + static_cast(pNd)->Len() != pEnd->nContent.GetIndex() ) + { + --nEnd; + --nDiff; + } + if( nDiff && + lcl_ChkFlyFly( pDoc, nStt, nEnd, rPos.nNode.GetIndex() ) ) + { + return false; + } + } + + SwPaM* pRedlineRange = nullptr; + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() || + (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) ) + pRedlineRange = new SwPaM( rPos ); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + + bool bRet = false; + + if( pDoc != &m_rDoc ) + { // ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else if( ! ( *pStt <= rPos && rPos < *pEnd && + ( pStt->nNode != pEnd->nNode || + !pStt->nNode.GetNode().IsTextNode() )) ) + { + // Copy to a position outside of the area, or copy a single TextNode + // Do an ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else + { + // Copy the range in itself + assert(!"mst: this is assumed to be dead code"); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pRedlineRange ) + { + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlineRange ), true); + else + pDoc->getIDocumentRedlineAccess().SplitRedline( *pRedlineRange ); + delete pRedlineRange; + } + + return bRet; +} + +/// Delete a full Section of the NodeArray. +/// The passed Node is located somewhere in the designated Section. +void DocumentContentOperationsManager::DeleteSection( SwNode *pNode ) +{ + assert(pNode && "Didn't pass a Node."); + + SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast(pNode) + : pNode->StartOfSectionNode(); + SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() ); + + // delete all Flys, Bookmarks, ... + DelFlyInRange( aSttIdx, aEndIdx ); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any ); + DelBookmarks(aSttIdx, aEndIdx); + + { + // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area + SwNodeIndex aMvStt( aSttIdx, 1 ); + SwDoc::CorrAbs( aMvStt, aEndIdx, SwPosition( aSttIdx ), true ); + } + + m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 ); +} + +void DocumentContentOperationsManager::DeleteDummyChar( + SwPosition const& rPos, sal_Unicode const cDummy) +{ + SwPaM aPam(rPos, rPos); + ++aPam.GetPoint()->nContent; + assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy); + (void) cDummy; + + DeleteRangeImpl(aPam); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam ) +{ + lcl_DoWithBreaks( *this, rPam, &DocumentContentOperationsManager::DeleteRangeImpl ); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam ) +{ + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + const SwNode* pNd = &rStt.nNode.GetNode(); + sal_uInt32 nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() - + pNd->StartOfSectionIndex(); + sal_uInt32 nNodeDiff = rEnd.nNode.GetIndex() - rStt.nNode.GetIndex(); + + if ( nSectDiff-2 <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + /* #i9185# Prevent getting the node after the end node (see below) */ + rEnd.nNode.GetIndex() + 1 == m_rDoc.GetNodes().Count() ) + { + return false; + } + + { + SwPaM temp(rPam, nullptr); + if (!temp.HasMark()) + { + temp.SetMark(); + } + if (SwTextNode *const pNode = temp.Start()->nNode.GetNode().GetTextNode()) + { // rPam may not have nContent set but IsFieldmarkOverlap requires it + pNode->MakeStartIndex(&temp.Start()->nContent); + } + if (SwTextNode *const pNode = temp.End()->nNode.GetNode().GetTextNode()) + { + pNode->MakeEndIndex(&temp.End()->nContent); + } + if (sw::mark::IsFieldmarkOverlap(temp)) + { // a bit of a problem: we want to completely remove the nodes + // but then how can the CH_TXT_ATR survive? + return false; + } + } + + // Move hard page brakes to the following Node. + bool bSavePageBreak = false, bSavePageDesc = false; + + /* #i9185# This would lead to a segmentation fault if not caught above. */ + sal_uLong nNextNd = rEnd.nNode.GetIndex() + 1; + SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode(); + + if( pTableNd && pNd->IsContentNode() ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + + { + const SfxPoolItem *pItem; + const SfxItemSet* pSet = static_cast(pNd)->GetpSwAttrSet(); + if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + bSavePageDesc = true; + } + + if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + bSavePageBreak = true; + } + } + } + + bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if( bDoesUndo ) + { + if( !rPam.HasMark() ) + rPam.SetMark(); + else if( rPam.GetPoint() == &rStt ) + rPam.Exchange(); + rPam.GetPoint()->nNode++; + + SwContentNode *pTmpNode = rPam.GetPoint()->nNode.GetNode().GetContentNode(); + rPam.GetPoint()->nContent.Assign( pTmpNode, 0 ); + bool bGoNext = (nullptr == pTmpNode); + pTmpNode = rPam.GetMark()->nNode.GetNode().GetContentNode(); + rPam.GetMark()->nContent.Assign( pTmpNode, 0 ); + + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + { + SwPosition aTmpPos( *aDelPam.GetPoint() ); + if( bGoNext ) + { + pTmpNode = m_rDoc.GetNodes().GoNext( &aTmpPos.nNode ); + aTmpPos.nContent.Assign( pTmpNode, 0 ); + } + ::PaMCorrAbs( aDelPam, aTmpPos ); + } + + std::unique_ptr pUndo(new SwUndoDelete( aDelPam, true )); + + *rPam.GetPoint() = *aDelPam.GetPoint(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + rPam.DeleteMark(); + } + else + { + SwNodeRange aRg( rStt.nNode, rEnd.nNode ); + rPam.Normalize(false); + + // Try to move past the End + if( !rPam.Move( fnMoveForward, GoInNode ) ) + { + // Fair enough, at the Beginning then + rPam.Exchange(); + if( !rPam.Move( fnMoveBackward, GoInNode )) + { + SAL_WARN("sw.core", "DelFullPara: no more Nodes"); + return false; + } + } + // move bookmarks, redlines etc. + if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this + { + m_rDoc.CorrAbs( aRg.aStart, *rPam.GetPoint(), 0, true ); + } + else + { + SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true ); + } + + // What's with Flys? + { + // If there are FlyFrames left, delete these too + for( size_t n = 0; n < m_rDoc.GetSpzFrameFormats()->size(); ++n ) + { + SwFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + // note: here use <= not < like in + // IsDestroyFrameAnchoredAtChar() because of the increment + // of rPam in the bDoesUndo path above! + aRg.aStart <= pAPos->nNode && pAPos->nNode <= aRg.aEnd ) + { + m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + --n; + } + } + } + + rPam.DeleteMark(); + m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 ); + } + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +// #i100466# Add handling of new optional parameter +bool DocumentContentOperationsManager::DeleteAndJoin( SwPaM & rPam, + const bool bForceJoinNext ) +{ + if ( lcl_StrLenOverflow( rPam ) ) + return false; + + bool const ret = lcl_DoWithBreaks( *this, rPam, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl + : &DocumentContentOperationsManager::DeleteAndJoinImpl, + bForceJoinNext ); + + return ret; +} + +// It seems that this is mostly used by SwDoc internals; the only +// way to call this from the outside seems to be the special case in +// SwDoc::CopyRange (but I have not managed to actually hit that case). +bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags ) +{ + // nothing moved: return + const SwPosition *pStt = rPaM.Start(), *pEnd = rPaM.End(); + if( !rPaM.HasMark() || *pStt >= *pEnd || (*pStt <= rPos && rPos < *pEnd)) + return false; + + assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created? + + // Save the paragraph anchored Flys, so that they can be moved. + SaveFlyArr aSaveFlyArr; + SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) ); + + // save redlines (if DOC_MOVEREDLINES is used) + SaveRedlines_t aSaveRedl; + if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + lcl_SaveRedlines( rPaM, aSaveRedl ); + + // #i17764# unfortunately, code below relies on undos being + // in a particular order, and presence of bookmarks + // will change this order. Hence, we delete bookmarks + // here without undo. + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + DelBookmarks( + pStt->nNode, + pEnd->nNode, + nullptr, + &pStt->nContent, + &pEnd->nContent); + } + + bool bUpdateFootnote = false; + SwFootnoteIdxs aTmpFntIdx; + + std::unique_ptr pUndoMove; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoMove.reset(new SwUndoMove( rPaM, rPos )); + pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES ); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( pStt->nNode, pEnd->nNode, rPos.nNode, + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx, + &pStt->nContent, &pEnd->nContent ); + } + + bool bSplit = false; + SwPaM aSavePam( rPos, rPos ); + + // Move the SPoint to the beginning of the range + if( rPaM.GetPoint() == pEnd ) + rPaM.Exchange(); + + // If there is a TextNode before and after the Move, create a JoinNext in the EditShell. + SwTextNode* pSrcNd = rPaM.GetPoint()->nNode.GetNode().GetTextNode(); + bool bCorrSavePam = pSrcNd && pStt->nNode != pEnd->nNode; + + // If one ore more TextNodes are moved, SwNodes::Move will do a SplitNode. + // However, this does not update the cursor. So we create a TextNode to keep + // updating the indices. After the Move the Node is optionally deleted. + SwTextNode * pTNd = rPos.nNode.GetNode().GetTextNode(); + if( pTNd && rPaM.GetPoint()->nNode != rPaM.GetMark()->nNode && + ( rPos.nContent.GetIndex() || ( pTNd->Len() && bCorrSavePam )) ) + { + bSplit = true; + const sal_Int32 nMkContent = rPaM.GetMark()->nContent.GetIndex(); + + const std::shared_ptr pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( &m_rDoc, rPos.nNode.GetIndex(), rPos.nContent.GetIndex(), true ); + + SwTextNode * pOrigNode = pTNd; + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->nContent.GetIdxReg() == pOrigNode); + assert(aSavePam.GetPoint()->nNode == rPos.nNode.GetIndex()); + assert(rPos.nNode.GetIndex() == pOrigNode->GetIndex()); + + std::function restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(&m_rDoc, pOrigNode->GetIndex()-1, 0, true, eMode); + } + }); + pTNd = pTNd->SplitContentNode(rPos, &restoreFunc)->GetTextNode(); + + //A new node was inserted before the orig pTNd and the content up to + //rPos moved into it. The old node is returned with the remainder + //of the content in it. + // + //aSavePam was created with rPos, it continues to point to the + //old node, but with the *original* content index into the node. + //Seeing as all the orignode content before that index has + //been removed, the new index into the original node should now be set + //to 0 and the content index of rPos should also be adapted to the + //truncated node + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->nContent.GetIdxReg() == pOrigNode); + assert(aSavePam.GetPoint()->nNode == rPos.nNode.GetIndex()); + assert(rPos.nNode.GetIndex() == pOrigNode->GetIndex()); + aSavePam.GetPoint()->nContent.Assign(pOrigNode, 0); + rPos = *aSavePam.GetMark() = *aSavePam.GetPoint(); + + // correct the PaM! + if( rPos.nNode == rPaM.GetMark()->nNode ) + { + rPaM.GetMark()->nNode = rPos.nNode.GetIndex()-1; + rPaM.GetMark()->nContent.Assign( pTNd, nMkContent ); + } + } + + // Put back the Pam by one "content"; so that it's always outside of + // the manipulated range. + // tdf#99692 don't Move() back if that would end up in another node + // because moving backward is not necessarily the inverse of forward then. + // (but do Move() back if we have split the node) + const bool bNullContent = !bSplit && aSavePam.GetPoint()->nContent == 0; + if( bNullContent ) + { + aSavePam.GetPoint()->nNode--; + aSavePam.GetPoint()->nContent.Assign(aSavePam.GetContentNode(), 0); + } + else + { + bool const success(aSavePam.Move(fnMoveBackward, GoInContent)); + assert(success); + (void) success; + } + + // Copy all Bookmarks that are within the Move range into an array, + // that saves the position as an offset. + std::vector< ::sw::mark::SaveBookmark> aSaveBkmks; + DelBookmarks( + pStt->nNode, + pEnd->nNode, + &aSaveBkmks, + &pStt->nContent, + &pEnd->nContent); + + // If there is no range anymore due to the above deletions (e.g. the + // footnotes got deleted), it's still a valid Move! + if( *rPaM.GetPoint() != *rPaM.GetMark() ) + { + // now do the actual move + m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() ); + + // after a MoveRange() the Mark is deleted + if ( rPaM.HasMark() ) // => no Move occurred! + { + return false; + } + } + else + rPaM.DeleteMark(); + + OSL_ENSURE( *aSavePam.GetMark() == rPos || + ( aSavePam.GetMark()->nNode.GetNode().GetContentNode() == nullptr ), + "PaM was not moved. Aren't there ContentNodes at the beginning/end?" ); + *aSavePam.GetMark() = rPos; + + rPaM.SetMark(); // create a Sel. around the new range + pTNd = aSavePam.GetNode().GetTextNode(); + assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo()); + bool bRemove = true; + // Do two Nodes have to be joined at the SavePam? + if (bSplit && pTNd) + { + if (pTNd->CanJoinNext()) + { + // Always join next, because has to stay as it is. + // A join previous from its next would more or less delete + pTNd->JoinNext(); + bRemove = false; + } + } + if (bNullContent) + { + aSavePam.GetPoint()->nNode++; + aSavePam.GetPoint()->nContent.Assign( aSavePam.GetContentNode(), 0 ); + } + else if (bRemove) // No move forward after joining with next paragraph + { + aSavePam.Move( fnMoveForward, GoInContent ); + } + + // Insert the Bookmarks back into the Document. + *rPaM.GetMark() = *aSavePam.Start(); + for(auto& rBkmk : aSaveBkmks) + rBkmk.SetInDoc( + &m_rDoc, + rPaM.GetMark()->nNode, + &rPaM.GetMark()->nContent); + *rPaM.GetPoint() = *aSavePam.End(); + + // Move the Flys to the new position. + // note: rPos is at the end here; can't really tell flys that used to be + // at the start of rPam from flys that used to be at the end of rPam + // unfortunately, so some of them are going to end up with wrong anchor... + RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &(rPos.nNode) ); + + // restore redlines (if DOC_MOVEREDLINES is used) + if( !aSaveRedl.empty() ) + { + lcl_RestoreRedlines( &m_rDoc, *aSavePam.Start(), aSaveRedl ); + } + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNodeIndex& rPos, + SwMoveFlags eMvFlags ) +{ + // Moves all Nodes to the new position. + // Bookmarks are moved too (currently without Undo support). + + // If footnotes are being moved to the special section, remove them now. + + // Or else delete the Frames for all footnotes that are being moved + // and have it rebuild after the Move (footnotes can change pages). + // Additionally we have to correct the FootnoteIdx array's sorting. + bool bUpdateFootnote = false; + SwFootnoteIdxs aTmpFntIdx; + + std::unique_ptr pUndo; + if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoMove( &m_rDoc, rRange, rPos )); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( rRange.aStart, rRange.aEnd, rPos, + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx ); + } + + SaveRedlines_t aSaveRedl; + std::vector aSavRedlInsPosArr; + if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + lcl_SaveRedlines( rRange, aSaveRedl ); + + // Find all RedLines that end at the InsPos. + // These have to be moved back to the "old" position after the Move. + SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rPos.GetNode(), RedlineType::Any ); + if( SwRedlineTable::npos != nRedlPos ) + { + const SwPosition *pRStt, *pREnd; + do { + SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + pRStt = pTmp->Start(); + pREnd = pTmp->End(); + if( pREnd->nNode == rPos && pRStt->nNode < rPos ) + { + aSavRedlInsPosArr.push_back( pTmp ); + } + } while( pRStt->nNode < rPos && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()); + } + } + + // Copy all Bookmarks that are within the Move range into an array + // that stores all references to positions as an offset. + // The final mapping happens after the Move. + std::vector< ::sw::mark::SaveBookmark> aSaveBkmks; + DelBookmarks(rRange.aStart, rRange.aEnd, &aSaveBkmks); + + // Save the paragraph-bound Flys, so that they can be moved. + SaveFlyArr aSaveFlyArr; + if( !m_rDoc.GetSpzFrameFormats()->empty() ) + SaveFlyInRange( rRange, aSaveFlyArr ); + + // Set it to before the Position, so that it cannot be moved further. + SwNodeIndex aIdx( rPos, -1 ); + + std::unique_ptr pSaveInsPos; + if( pUndo ) + pSaveInsPos.reset(new SwNodeIndex( rRange.aStart, -1 )); + + // move the Nodes + bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags); + if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rPos, !bNoDelFrames ) ) + { + ++aIdx; // again back to old position + if( pSaveInsPos ) + ++(*pSaveInsPos); + } + else + { + aIdx = rRange.aStart; + pUndo.reset(); + } + + // move the Flys to the new position + if( !aSaveFlyArr.empty() ) + { + SwPosition const tmp(aIdx); + RestFlyInRange(aSaveFlyArr, tmp, nullptr); + } + + // Add the Bookmarks back to the Document + for(auto& rBkmk : aSaveBkmks) + rBkmk.SetInDoc(&m_rDoc, aIdx); + + if( !aSavRedlInsPosArr.empty() ) + { + SwNode* pNewNd = &aIdx.GetNode(); + for(SwRangeRedline* pTmp : aSavRedlInsPosArr) + { + if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) ) + { + SwPosition* pEnd = pTmp->End(); + pEnd->nNode = aIdx; + pEnd->nContent.Assign( pNewNd->GetContentNode(), 0 ); + } + } + } + + if( !aSaveRedl.empty() ) + lcl_RestoreRedlines( &m_rDoc, aIdx.GetIndex(), aSaveRedl ); + + if( pUndo ) + { + pUndo->SetDestRange( aIdx, rPos, *pSaveInsPos ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + pSaveInsPos.reset(); + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos ) +{ + SwNodeIndex aIdx( rPaM.Start()->nNode ); + bool bJoinText = aIdx.GetNode().IsTextNode(); + bool bOneNode = rPaM.GetPoint()->nNode == rPaM.GetMark()->nNode; + aIdx--; // in front of the move area! + + bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT ); + if( bRet && !bOneNode ) + { + if( bJoinText ) + ++aIdx; + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aNxtIdx( aIdx ); + if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) ) + { + { // Block so SwIndex into node is deleted before Join + m_rDoc.CorrRel( aNxtIdx, SwPosition( aIdx, SwIndex(pTextNd, + pTextNd->GetText().getLength()) ), 0, true ); + } + pTextNd->JoinNext(); + } + } + return bRet; +} + +// Overwrite only uses the point of the PaM, the mark is ignored; characters +// are replaced from point until the end of the node; at the end of the node, +// characters are inserted. +bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr ) +{ + assert(rStr.getLength()); + SwPosition& rPt = *const_cast(rRg.GetPoint()); + if( m_rDoc.GetAutoCorrExceptWord() ) // Add to AutoCorrect + { + if( 1 == rStr.getLength() ) + m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] ); + m_rDoc.DeleteAutoCorrExceptWord(); + } + + SwTextNode *pNode = rPt.nNode.GetNode().GetTextNode(); + if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase + { + return false; + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called + } + + const size_t nOldAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + SwDataChanged aTmp( rRg ); + SwIndex& rIdx = rPt.nContent; + sal_Int32 const nActualStart(rIdx.GetIndex()); + sal_Int32 nStart = 0; + + bool bOldExpFlg = pNode->IsIgnoreDontExpand(); + pNode->SetIgnoreDontExpand( true ); + + for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt ) + { + // start behind the characters (to fix the attributes!) + nStart = rIdx.GetIndex(); + if (nStart < pNode->GetText().getLength()) + { + lcl_SkipAttr( pNode, rIdx, nStart ); + } + sal_Unicode c = rStr[ nCnt ]; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + bool bMerged(false); + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo(); + SwUndoOverwrite *const pUndoOW( + dynamic_cast(pUndo) ); + if (pUndoOW) + { + // if CanGrouping() returns true it's already merged + bMerged = pUndoOW->CanGrouping( &m_rDoc, rPt, c ); + } + } + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique(&m_rDoc, rPt, c) ); + } + } + else + { + // start behind the characters (to fix the attributes!) + if (nStart < pNode->GetText().getLength()) + ++rIdx; + pNode->InsertText( OUString(c), rIdx, SwInsertFlags::EMPTYEXPAND ); + if( nStart+1 < rIdx.GetIndex() ) + { + rIdx = nStart; + pNode->EraseText( rIdx, 1 ); + ++rIdx; + } + } + } + pNode->SetIgnoreDontExpand( bOldExpFlg ); + + const size_t nNewAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + if( nOldAttrCnt != nNewAttrCnt ) + { + SwUpdateAttr aHint(0,0,0); + pNode->ModifyBroadcast(nullptr, &aHint); + } + + if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + SwPaM aPam(rPt.nNode, nActualStart, rPt.nNode, rPt.nContent.GetIndex()); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any ); + } + else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + // FIXME: this redline is WRONG: there is no DELETE, and the skipped + // characters are also included in aPam + SwPaM aPam(rPt.nNode, nActualStart, rPt.nNode, rPt.nContent.GetIndex()); + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr, + const SwInsertFlags nInsertMode ) +{ + // tdf#119019 accept tracked paragraph formatting to do not hide new insertions + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg ); + if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags()) + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + // fetching DoesUndo is surprisingly expensive + bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if (bDoesUndo) + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called! + + const SwPosition& rPos = *rRg.GetPoint(); + + if( m_rDoc.GetAutoCorrExceptWord() ) // add to auto correction + { + if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ) + { + m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] ); + } + m_rDoc.DeleteAutoCorrExceptWord(); + } + + SwTextNode *const pNode = rPos.nNode.GetNode().GetTextNode(); + if(!pNode) + return false; + + SwDataChanged aTmp( rRg ); + + if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + OUString const ins(pNode->InsertText(rStr, rPos.nContent, nInsertMode)); + if (bDoesUndo) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique(rPos.nNode, + rPos.nContent.GetIndex(), ins.getLength(), nInsertMode)); + } + } + else + { // if Undo and grouping is enabled, everything changes! + SwUndoInsert * pUndo = nullptr; + + // don't group the start if hints at the start should be expanded + if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND)) + { + SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo(); + SwUndoInsert *const pUndoInsert( + dynamic_cast(pLastUndo) ); + if (pUndoInsert && pUndoInsert->CanGrouping(rPos)) + { + pUndo = pUndoInsert; + } + } + + CharClass const& rCC = GetAppCharClass(); + sal_Int32 nInsPos = rPos.nContent.GetIndex(); + + if (!pUndo) + { + pUndo = new SwUndoInsert( rPos.nNode, nInsPos, 0, nInsertMode, + !rCC.isLetterNumeric( rStr, 0 ) ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr(pUndo) ); + } + + OUString const ins(pNode->InsertText(rStr, rPos.nContent, nInsertMode)); + + for (sal_Int32 i = 0; i < ins.getLength(); ++i) + { + nInsPos++; + // if CanGrouping() returns true, everything has already been done + if (!pUndo->CanGrouping(ins[i])) + { + pUndo = new SwUndoInsert(rPos.nNode, nInsPos, 1, nInsertMode, + !rCC.isLetterNumeric(ins, i)); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr(pUndo) ); + } + } + } + + // To-Do - add 'SwExtraRedlineTable' also ? + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( rPos.nNode, aTmp.GetContent(), + rPos.nNode, rPos.nContent.GetIndex()); + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + else + { + m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +void DocumentContentOperationsManager::TransliterateText( + const SwPaM& rPaM, + utl::TransliterationWrapper& rTrans ) +{ + std::unique_ptr pUndo; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoTransliterate( rPaM, rTrans )); + + const SwPosition* pStt = rPaM.Start(), + * pEnd = rPaM.End(); + sal_uLong nSttNd = pStt->nNode.GetIndex(), + nEndNd = pEnd->nNode.GetIndex(); + sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pStt == pEnd && pTNd ) // no selection? + { + // set current word as 'area of effect' + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTNd->GetText(), nSttCnt, + g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos ) + { + nSttCnt = aBndry.startPos; + nEndCnt = aBndry.endPos; + } + } + + if( nSttNd != nEndNd ) // is more than one text node involved? + { + // iterate over all effected text nodes, the first and the last one + // may be incomplete because the selection starts and/or ends there + + SwNodeIndex aIdx( pStt->nNode ); + if( nSttCnt ) + { + ++aIdx; + if( pTNd ) + pTNd->TransliterateText( + rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get()); + } + + for( ; aIdx.GetIndex() < nEndNd; ++aIdx ) + { + pTNd = aIdx.GetNode().GetTextNode(); + if (pTNd) + { + pTNd->TransliterateText( + rTrans, 0, pTNd->GetText().getLength(), pUndo.get()); + } + } + + if( nEndCnt && nullptr != ( pTNd = pEnd->nNode.GetNode().GetTextNode() )) + pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get() ); + } + else if( pTNd && nSttCnt < nEndCnt ) + pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get() ); + + if( pUndo && pUndo->HasData() ) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + m_rDoc.getIDocumentState().SetModified(); +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic( + const SwPaM &rRg, + const OUString& rGrfName, + const OUString& rFltName, + const Graphic* pGraphic, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* pFrameFormat ) +{ + if( !pFrameFormat ) + pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC ); + SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rGrfName, rFltName, pGraphic, + m_rDoc.GetDfltGrfFormatColl() ); + SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode, + pFlyAttrSet, pGrfAttrSet, pFrameFormat ); + return pSwFlyFrameFormat; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphicObject( + const SwPaM &rRg, const GraphicObject& rGrfObj, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet ) +{ + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC ); + SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rGrfObj, m_rDoc.GetDfltGrfFormatColl() ); + SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode, + pFlyAttrSet, pGrfAttrSet, pFrameFormat ); + return pSwFlyFrameFormat; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject( + const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj, + SfxItemSet* pFlyAttrSet) +{ + sal_uInt16 nId = RES_POOLFRM_OLE; + if (xObj.is()) + { + SvGlobalName aClassName( xObj->getClassID() ); + if (SotExchange::IsMath(aClassName)) + { + nId = RES_POOLFRM_FORMEL; + } + } + + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId ); + + return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + xObj, + m_rDoc.GetDfltGrfFormatColl() ), + pFlyAttrSet, nullptr, + pFrameFormat ); +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName, + sal_Int64 nAspect, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet) +{ + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE ); + + return InsNoTextNode( *rRg.GetPoint(), + m_rDoc.GetNodes().MakeOLENode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rObjName, + nAspect, + m_rDoc.GetDfltGrfFormatColl(), + nullptr ), + pFlyAttrSet, pGrfAttrSet, + pFrameFormat ); +} + +void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName, + const OUString& rFltName, const Graphic* pGraphic ) +{ + SwGrfNode *pGrfNd; + if( ( !rPam.HasMark() + || rPam.GetPoint()->nNode.GetIndex() == rPam.GetMark()->nNode.GetIndex() ) + && nullptr != ( pGrfNd = rPam.GetPoint()->nNode.GetNode().GetGrfNode() ) ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique(rPam, *pGrfNd)); + } + + // Because we don't know if we can mirror the graphic, the mirror attribute is always reset + if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet(). + GetMirrorGrf().GetValue() ) + pGrfNd->SetAttr( SwMirrorGrf() ); + + pGrfNd->ReRead( rGrfName, rFltName, pGraphic ); + m_rDoc.getIDocumentState().SetModified(); + } +} + +// Insert drawing object, which has to be already inserted in the DrawModel +SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj( + const SwPaM &rRg, + SdrObject& rDrawObj, + const SfxItemSet& rFlyAttrSet ) +{ + SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() ); + + const SwFormatAnchor* pAnchor = nullptr; + rFlyAttrSet.GetItemState( RES_ANCHOR, false, reinterpret_cast(&pAnchor) ); + pFormat->SetFormatAttr( rFlyAttrSet ); + + // Didn't set the Anchor yet? + // DrawObjecte must never end up in the Header/Footer! + RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId(); + const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId); + + const SwNodeIndex* pChkIdx = nullptr; + if ( pAnchor == nullptr ) + { + pChkIdx = &rRg.GetPoint()->nNode; + } + else if ( bIsAtContent ) + { + pChkIdx = + pAnchor->GetContentAnchor() ? &pAnchor->GetContentAnchor()->nNode : &rRg.GetPoint()->nNode; + } + + // allow drawing objects in header/footer, but control objects aren't allowed in header/footer. + if( pChkIdx != nullptr + && ::CheckControlLayer( &rDrawObj ) + && m_rDoc.IsInHeaderFooter( *pChkIdx ) ) + { + // apply at-page anchor format + eAnchorId = RndStdIds::FLY_AT_PAGE; + pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) ); + } + else if( pAnchor == nullptr + || ( bIsAtContent + && pAnchor->GetContentAnchor() == nullptr ) ) + { + // apply anchor format + SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() ); + eAnchorId = aAnch.GetAnchorId(); + if ( eAnchorId == RndStdIds::FLY_AT_FLY ) + { + SwPosition aPos( *rRg.GetNode().FindFlyStartNode() ); + aAnch.SetAnchor( &aPos ); + } + else + { + aAnch.SetAnchor( rRg.GetPoint() ); + if ( eAnchorId == RndStdIds::FLY_AT_PAGE ) + { + eAnchorId = dynamic_cast( &rDrawObj) != nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA; + aAnch.SetType( eAnchorId ); + } + } + pFormat->SetFormatAttr( aAnch ); + } + + // insert text attribute for as-character anchored drawing object + if ( eAnchorId == RndStdIds::FLY_AS_CHAR ) + { + bool bAnchorAtPageAsFallback = true; + const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor(); + if ( rDrawObjAnchorFormat.GetContentAnchor() != nullptr ) + { + SwTextNode* pAnchorTextNode = + rDrawObjAnchorFormat.GetContentAnchor()->nNode.GetNode().GetTextNode(); + if ( pAnchorTextNode != nullptr ) + { + const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->nContent.GetIndex(); + SwFormatFlyCnt aFormat( pFormat ); + pAnchorTextNode->InsertItem( aFormat, nStt, nStt ); + bAnchorAtPageAsFallback = false; + } + } + + if ( bAnchorAtPageAsFallback ) + { + OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" ); + pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) ); + } + } + + SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj ); + + // Create Frames if necessary + if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // create layout representation + pFormat->MakeFrames(); + // #i42319# - follow-up of #i35635# + // move object to visible layer + // #i79391# + if ( pContact->GetAnchorFrame() ) + { + pContact->MoveObjToVisibleLayer( &rDrawObj ); + } + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(pFormat, 0, 0) ); + } + + m_rDoc.getIDocumentState().SetModified(); + return pFormat; +} + +bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart ) +{ + SwContentNode *pNode = rPos.nNode.GetNode().GetContentNode(); + if(nullptr == pNode) + return false; + + { + // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope. + // After that they can be before/after the position. + SwDataChanged aTmp( &m_rDoc, rPos ); + } + + SwUndoSplitNode* pUndo = nullptr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + // insert the Undo object (currently only for TextNode) + if( pNode->IsTextNode() ) + { + pUndo = new SwUndoSplitNode( &m_rDoc, rPos, bChkTableStart ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(pUndo)); + } + } + + // Update the rsid of the old and the new node unless + // the old node is split at the beginning or at the end + SwTextNode *pTextNode = rPos.nNode.GetNode().GetTextNode(); + const sal_Int32 nPos = rPos.nContent.GetIndex(); + if( pTextNode && nPos && nPos != pTextNode->Len() ) + { + m_rDoc.UpdateParRsid( pTextNode ); + } + + //JP 28.01.97: Special case for SplitNode at table start: + // If it is at the beginning of a Doc/Fly/Footer/... or right at after a table + // then insert a paragraph before it. + if( bChkTableStart && !rPos.nContent.GetIndex() && pNode->IsTextNode() ) + { + sal_uLong nPrevPos = rPos.nNode.GetIndex() - 1; + const SwTableNode* pTableNd; + const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ]; + if( pNd->IsStartNode() && + SwTableBoxStartNode == static_cast(pNd)->GetStartNodeType() && + nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) && + ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() && + SwTableBoxStartNode != static_cast(pNd)->GetStartNodeType() ) + || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() ) + || pNd->IsContentNode() )) + { + if( pNd->IsContentNode() ) + { + //JP 30.04.99 Bug 65660: + // There are no page breaks outside of the normal body area, + // so this is not a valid condition to insert a paragraph. + if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + pNd = nullptr; + else + { + // Only if the table has page breaks! + const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat(); + if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) && + SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) ) + pNd = nullptr; + } + } + + if( pNd ) + { + SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode( + SwNodeIndex( *pTableNd ), + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + if( pTextNd ) + { + const_cast(rPos).nNode = pTableNd->GetIndex()-1; + const_cast(rPos).nContent.Assign( pTextNd, 0 ); + + // only add page breaks/styles to the body area + if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pTextNd->SetAttr( *pItem ); + pFrameFormat->ResetFormatAttr( RES_PAGEDESC ); + } + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pTextNd->SetAttr( *pItem ); + pFrameFormat->ResetFormatAttr( RES_BREAK ); + } + } + + if( pUndo ) + pUndo->SetTableFlag(); + m_rDoc.getIDocumentState().SetModified(); + return true; + } + } + } + } + + const std::shared_ptr pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( &m_rDoc, rPos.nNode.GetIndex(), rPos.nContent.GetIndex(), true ); + assert(pNode->IsTextNode()); + std::function restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { // move all bookmarks, TOXMarks, FlyAtCnt + pContentStore->Restore(&m_rDoc, rPos.nNode.GetIndex()-1, 0, true, eMode); + } + if (eMode & sw::mark::RestoreMode::NonFlys) + { + // To-Do - add 'SwExtraRedlineTable' also ? + if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && + !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())) + { + SwPaM aPam( rPos ); + aPam.SetMark(); + aPam.Move( fnMoveBackward ); + if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + { + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(RedlineType::Insert, aPam), true); + } + else + { + m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam); + } + } + } + }); + pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc); + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos ) +{ + // create new node before EndOfContent + SwTextNode * pCurNode = rPos.nNode.GetNode().GetTextNode(); + if( !pCurNode ) + { + // so then one can be created! + SwNodeIndex aIdx( rPos.nNode, 1 ); + pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + } + else + pCurNode = pCurNode->AppendNode( rPos )->GetTextNode(); + + rPos.nNode++; + rPos.nContent.Assign( pCurNode, 0 ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique( rPos.nNode ) ); + } + + // To-Do - add 'SwExtraRedlineTable' also ? + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( rPos ); + aPam.SetMark(); + aPam.Move( fnMoveBackward ); + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr, + const bool bRegExReplace ) +{ + // unfortunately replace works slightly differently from delete, + // so we cannot use lcl_DoWithBreaks here... + + std::vector> Breaks; + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + aPam.Normalize(false); + if (aPam.GetPoint()->nNode != aPam.GetMark()->nNode) + { + aPam.Move(fnMoveBackward); + } + OSL_ENSURE((aPam.GetPoint()->nNode == aPam.GetMark()->nNode), "invalid pam?"); + + sw::CalcBreaks(Breaks, aPam); + + while (!Breaks.empty() // skip over prefix of dummy chars + && (aPam.GetMark()->nNode.GetIndex() == Breaks.begin()->first) + && (aPam.GetMark()->nContent.GetIndex() == Breaks.begin()->second)) + { + // skip! + ++aPam.GetMark()->nContent; // always in bounds if Breaks valid + Breaks.erase(Breaks.begin()); + } + *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix + + if (Breaks.empty()) + { + // park aPam somewhere so it does not point to node that is deleted + aPam.DeleteMark(); + *aPam.GetPoint() = SwPosition(m_rDoc.GetNodes().GetEndOfContent()); + return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam! + } + + // Deletion must be split into several parts if the text node + // contains a text attribute with end and with dummy character + // and the selection does not contain the text attribute completely, + // but overlaps its start (left), where the dummy character is. + + bool bRet( true ); + // iterate from end to start, to avoid invalidating the offsets! + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!"); + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + // set end of temp pam to original end (undo Move backward above) + rEnd = *rPam.End(); + // after first deletion, rEnd will point into the original text node again! + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? DeleteAndJoinWithRedlineImpl(aPam) + : DeleteAndJoinImpl(aPam, false); + nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + assert(rStart < rEnd && "replace part empty!"); + if (rStart < rEnd) // check if part is empty + { + bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace); + } + + rPam = aPam; // update original pam (is this required?) + + return bRet; +} + +///Add a para for the char attribute exp... +bool DocumentContentOperationsManager::InsertPoolItem( + const SwPaM &rRg, + const SfxPoolItem &rHt, + const SetAttrMode nFlags, + SwRootFrame const*const pLayout, + const bool bExpandCharToPara, + SwTextAttr **ppNewTextAttr) +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + + SwDataChanged aTmp( rRg ); + std::unique_ptr pUndoAttr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags )); + } + + SfxItemSet aSet( m_rDoc.GetAttrPool(), {{rHt.Which(), rHt.Which()}} ); + aSet.Put( rHt ); + const bool bRet = lcl_InsAttr(&m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, bExpandCharToPara, ppNewTextAttr); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) ); + } + + if( bRet ) + { + m_rDoc.getIDocumentState().SetModified(); + } + return bRet; +} + +void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet, + const SetAttrMode nFlags, SwRootFrame const*const pLayout) +{ + SwDataChanged aTmp( rRg ); + std::unique_ptr pUndoAttr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags )); + } + + bool bRet = lcl_InsAttr(&m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*bExpandCharToPara*/false, /*ppNewTextAttr*/nullptr ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) ); + } + + if( bRet ) + m_rDoc.getIDocumentState().SetModified(); +} + +void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos ) +{ + const SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + if ( pTNd ) + { + const OUString& rText = pTNd->GetText(); + sal_Int32 nIdx = 0; + while (nIdx < rText.getLength()) + { + sal_Unicode const cCh = rText[nIdx]; + if (('\t' != cCh) && (' ' != cCh)) + { + break; + } + ++nIdx; + } + + if ( nIdx > 0 ) + { + SwPaM aPam(rPos); + aPam.GetPoint()->nContent = 0; + aPam.SetMark(); + aPam.GetMark()->nContent = nIdx; + DeleteRange( aPam ); + } + } +} + +// Copy method from SwDoc - "copy Flys in Flys" +/// note: rRg/rInsPos *exclude* a partially selected start text node; +/// pCopiedPaM *includes* a partially selected start text node +void DocumentContentOperationsManager::CopyWithFlyInFly( + const SwNodeRange& rRg, + const SwNodeIndex& rInsPos, + const std::pair* pCopiedPaM /*and real insert pos*/, + const bool bMakeNewFrames, + const bool bDelRedlines, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->first.End()->nNode == rRg.aEnd); + assert(!pCopiedPaM || pCopiedPaM->second.nNode <= rInsPos); + + SwDoc* pDest = rInsPos.GetNode().GetDoc(); + SwNodeIndex aSavePos( rInsPos ); + + if (rRg.aStart != rRg.aEnd) + { + bool bEndIsEqualEndPos = rInsPos == rRg.aEnd; + bool isRecreateEndNode(false); + --aSavePos; + SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 ); + + // insert behind the already copied start node + m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true ); + aRedlRest.Restore(); + if (bMakeNewFrames) // tdf#130685 only after aRedlRest + { // recreate from previous node (could be merged now) + if (SwTextNode *const pNode = aSavePos.GetNode().GetTextNode()) + { + std::unordered_set frames; + SwTextNode *const pEndNode = rInsPos.GetNode().GetTextNode(); + if (pEndNode) + { + SwIterator aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.insert(pFrame); + } + } + } + sw::RecreateStartTextFrames(*pNode); + if (!frames.empty()) + { // tdf#132187 check if the end node needs new frames + SwIterator aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + auto const it = frames.find(pFrame); + if (it != frames.end()) + { + frames.erase(it); + } + } + } + if (!frames.empty()) // existing frame was deleted + { // all layouts because MakeFrames recreates all layouts + pEndNode->DelFrames(nullptr); + isRecreateEndNode = true; + } + } + } + } + bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode()); + ++aSavePos; + if (bMakeNewFrames) + { + // it's possible that CheckParaRedlineMerge() deleted frames + // on rInsPos so have to include it, but it must not be included + // if it was the first node in the document so that MakeFrames() + // will find the existing (wasn't deleted) frame on it + SwNodeIndex const end(rInsPos, + (!isRecreateEndNode || isAtStartOfSection) + ? 0 : +1); + ::MakeFrames(pDest, aSavePos, end); + } + if (bEndIsEqualEndPos) + { + const_cast(rRg.aEnd) = aSavePos; + } + } + +#if OSL_DEBUG_LEVEL > 0 + { + //JP 17.06.99: Bug 66973 - check count only if the selection is in + // the same section or there's no section, because sections that are + // not fully selected are not copied. + const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode(); + SwNodeIndex aTmpI( rRg.aEnd, -1 ); + const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode(); + if( pSSectNd == pESectNd && + !rRg.aStart.GetNode().IsSectionNode() && + !aTmpI.GetNode().IsEndNode() ) + { + // If the range starts with a SwStartNode, it isn't copied + sal_uInt16 offset = (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0; + OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() == + rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset, + "An insufficient number of nodes were copied!" ); + } + } +#endif + + { + ::sw::UndoGuard const undoGuard(pDest->GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr, + // see comment below regarding use of pCopiedPaM->second + (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + ? pCopiedPaM->second.nNode + : aSavePos, + bCopyFlyAtFly, + flags); + } + + SwNodeRange aCpyRange( aSavePos, rInsPos ); + + // Also copy all bookmarks + // guess this must be done before the DelDummyNodes below as that + // deletes nodes so would mess up the index arithmetic + if( m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPaM aCpyPaM(aCpyRange.aStart, aCpyRange.aEnd); + if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + { + // there is 1 (partially selected, maybe) paragraph before + assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->nNode); + // only use the passed in target SwPosition if the source PaM point + // is on a different node; if it was the same node then the target + // position was likely moved along by the copy operation and now + // points to the end of the range! + *aCpyPaM.GetPoint() = pCopiedPaM->second; + } + + sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, *aCpyPaM.Start()); + } + + if( bDelRedlines && ( RedlineFlags::DeleteRedlines & pDest->getIDocumentRedlineAccess().GetRedlineFlags() )) + lcl_DeleteRedlines( rRg, aCpyRange ); + + pDest->GetNodes().DelDummyNodes( aCpyRange ); +} + +// note: for the redline Show/Hide this must be in sync with +// SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection() +void DocumentContentOperationsManager::CopyFlyInFlyImpl( + const SwNodeRange& rRg, + SwPaM const*const pCopiedPaM, + const SwNodeIndex& rStartIdx, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->End()->nNode == rRg.aEnd); + + // First collect all Flys, sort them according to their ordering number, + // and then only copy them. This maintains the ordering numbers (which are only + // managed in the DrawModel). + SwDoc *const pDest = rStartIdx.GetNode().GetDoc(); + std::set< ZSortFly > aSet; + const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size(); + + SwTextBoxHelper::SavedLink aOldTextBoxes; + SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes); + + for ( size_t n = 0; n < nArrLen; ++n ) + { + SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if ( !pAPos ) + continue; + bool bAdd = false; + sal_uLong nSkipAfter = pAPos->nNode.GetIndex(); + sal_uLong nStart = rRg.aStart.GetIndex(); + switch ( pAnchor->GetAnchorId() ) + { + case RndStdIds::FLY_AT_FLY: + if(bCopyFlyAtFly) + ++nSkipAfter; + else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) + ++nStart; + break; + case RndStdIds::FLY_AT_PARA: + { + bAdd = IsSelectFrameAnchoredAtPara(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd), + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask); + } + break; + case RndStdIds::FLY_AT_CHAR: + { + bAdd = IsDestroyFrameAnchoredAtChar(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd), + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask); + } + break; + default: + continue; + } + if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) + { + if (nStart > nSkipAfter) + continue; + if (pAPos->nNode > rRg.aEnd) + continue; + //frames at the last source node are not always copied: + //- if the node is empty and is the last node of the document or a table cell + // or a text frame then they have to be copied + //- if the content index in this node is > 0 then paragraph and frame bound objects are copied + //- to-character bound objects are copied if their index is <= nEndContentIndex + if (pAPos->nNode < rRg.aEnd) + bAdd = true; + if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move + { + if (!bAdd) + { + // technically old code checked nContent of AT_FLY which is pointless + bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->nContent.GetIndex(); + } + } + } + if( bAdd ) + { + aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() )); + } + } + + // Store all copied (and also the newly created) frames in another array. + // They are stored as matching the originals, so that we will be later + // able to build the chains accordingly. + std::vector< SwFrameFormat* > aVecSwFrameFormat; + std::set< ZSortFly >::const_iterator it=aSet.begin(); + + while (it != aSet.end()) + { + // #i59964# + // correct determination of new anchor position + SwFormatAnchor aAnchor( *(*it).GetAnchor() ); + assert( aAnchor.GetContentAnchor() != nullptr ); + SwPosition newPos = *aAnchor.GetContentAnchor(); + // for at-paragraph and at-character anchored objects the new anchor + // position can *not* be determined by the difference of the current + // anchor position to the start of the copied range, because not + // complete selected sections in the copied range aren't copied - see + // method . + // Thus, the new anchor position in the destination document is found + // by counting the text nodes. + if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) || + (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) ) + { + // First, determine number of anchor text node in the copied range. + // Note: The anchor text node *have* to be inside the copied range. + sal_uLong nAnchorTextNdNumInRange( 0 ); + bool bAnchorTextNdFound( false ); + // start at the first node for which flys are copied + SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->nNode : rRg.aStart); + while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + ++nAnchorTextNdNumInRange; + bAnchorTextNdFound = aAnchor.GetContentAnchor()->nNode == aIdx; + } + + ++aIdx; + } + + if ( !bAnchorTextNdFound ) + { + // This case can *not* happen, but to be robust take the first + // text node in the destination document. + OSL_FAIL( " - anchor text node in copied range not found" ); + nAnchorTextNdNumInRange = 1; + } + // Second, search corresponding text node in destination document + // by counting forward from start insert position the + // determined number of text nodes. + aIdx = rStartIdx; + SwNodeIndex aAnchorNdIdx( rStartIdx ); + const SwNode& aEndOfContentNd = + aIdx.GetNode().GetNodes().GetEndOfContent(); + while ( nAnchorTextNdNumInRange > 0 && + &(aIdx.GetNode()) != &aEndOfContentNd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + --nAnchorTextNdNumInRange; + aAnchorNdIdx = aIdx; + } + + ++aIdx; + } + if ( !aAnchorNdIdx.GetNode().IsTextNode() ) + { + // This case can *not* happen, but to be robust take the first + // text node in the destination document. + OSL_FAIL( " - found anchor node index isn't a text node" ); + aAnchorNdIdx = rStartIdx; + while ( !aAnchorNdIdx.GetNode().IsTextNode() ) + { + ++aAnchorNdIdx; + } + } + // apply found anchor text node as new anchor position + newPos.nNode = aAnchorNdIdx; + } + else + { + long nOffset = newPos.nNode.GetIndex() - rRg.aStart.GetIndex(); + SwNodeIndex aIdx( rStartIdx, nOffset ); + newPos.nNode = aIdx; + } + // Set the character bound Flys back at the original character + if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) && + newPos.nNode.GetNode().IsTextNode() ) + { + // only if pCopiedPaM: care about partially selected start node + sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->nNode == aAnchor.GetContentAnchor()->nNode + ? newPos.nContent.GetIndex() - pCopiedPaM->Start()->nContent.GetIndex() + : newPos.nContent.GetIndex(); + newPos.nContent.Assign(newPos.nNode.GetNode().GetTextNode(), nContent); + } + else + { + newPos.nContent.Assign( nullptr, 0 ); + } + aAnchor.SetAnchor( &newPos ); + + // Check recursion: if copying content inside the same frame, then don't copy the format. + if( pDest == &m_rDoc ) + { + const SwFormatContent& rContent = (*it).GetFormat()->GetContent(); + const SwStartNode* pSNd; + if( rContent.GetContentIdx() && + nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) && + pSNd->GetIndex() < rStartIdx.GetIndex() && + rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() ) + { + it = aSet.erase(it); + continue; + } + } + + // Ignore TextBoxes, they are already handled in + // sw::DocumentLayoutManager::CopyLayoutFormat(). + if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT)) + { + it = aSet.erase(it); + continue; + } + + // Copy the format and set the new anchor + aVecSwFrameFormat.push_back( pDest->getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(), + aAnchor, false, true ) ); + ++it; + } + + // Rebuild as much as possible of all chains that are available in the original, + OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" ); + if ( aSet.size() == aVecSwFrameFormat.size() ) + { + size_t n = 0; + for (const auto& rFlyN : aSet) + { + const SwFrameFormat *pFormatN = rFlyN.GetFormat(); + const SwFormatChain &rChain = pFormatN->GetChain(); + int nCnt = int(nullptr != rChain.GetPrev()); + nCnt += rChain.GetNext() ? 1: 0; + size_t k = 0; + for (const auto& rFlyK : aSet) + { + const SwFrameFormat *pFormatK = rFlyK.GetFormat(); + if ( rChain.GetPrev() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) ); + --nCnt; + } + else if ( rChain.GetNext() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) ); + --nCnt; + } + ++k; + } + ++n; + } + + // Re-create content property of draw formats, knowing how old shapes + // were paired with old fly formats (aOldTextBoxes) and that aSet is + // parallel with aVecSwFrameFormat. + SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes); + } +} + +/* + * Reset the text's hard formatting + */ +/** @params pArgs contains the document's ChrFormatTable + * Is need for selections at the beginning/end and with no SSelection. + */ +bool DocumentContentOperationsManager::lcl_RstTextAttr( const SwNodePtr& rpNd, void* pArgs ) +{ + ParaRstFormat* pPara = static_cast(pArgs); + if (pPara->pLayout && pPara->pLayout->IsHideRedlines() + && rpNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; // skip hidden, since new items aren't applied + } + SwTextNode * pTextNode = rpNd->GetTextNode(); + if( pTextNode && pTextNode->GetpSwpHints() ) + { + SwIndex aSt( pTextNode, 0 ); + sal_Int32 nEnd = pTextNode->Len(); + + if( &pPara->pSttNd->nNode.GetNode() == pTextNode && + pPara->pSttNd->nContent.GetIndex() ) + aSt = pPara->pSttNd->nContent.GetIndex(); + + if( &pPara->pEndNd->nNode.GetNode() == rpNd ) + nEnd = pPara->pEndNd->nContent.GetIndex(); + + if( pPara->pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNode, pPara->pHistory ); + pTextNode->GetpSwpHints()->Register( &aRHst ); + pTextNode->RstTextAttr( aSt, nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + if( pTextNode->GetpSwpHints() ) + pTextNode->GetpSwpHints()->DeRegister(); + } + else + pTextNode->RstTextAttr( aSt, nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + } + return true; +} + +DocumentContentOperationsManager::~DocumentContentOperationsManager() +{ +} +//Private methods + +bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl( SwPaM & rPam, const bool ) +{ + assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()); + + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + + if (*rPam.GetPoint() == *rPam.GetMark()) + { + return false; // do not add empty redlines + } + + std::vector redlines; + { + auto pRedline(std::make_unique(RedlineType::Delete, rPam)); + if (pRedline->HasValidRange()) + { + redlines.push_back(pRedline.release()); + } + else // sigh ... why is such a selection even possible... + { // split it up so we get one SwUndoRedlineDelete per inserted RL + redlines = GetAllValidRanges(std::move(pRedline)); + } + } + + if (redlines.empty()) + { + return false; + } + + // tdf#54819 current redlining needs also modification of paragraph style and + // attributes added to the same grouped Undo + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + auto & rDMA(*m_rDoc.getIDocumentMarkAccess()); + std::vector> MarkUndos; + for (auto iter = rDMA.getAnnotationMarksBegin(); + iter != rDMA.getAnnotationMarksEnd(); ) + { + // tdf#111524 remove annotation marks that have their field + // characters deleted + SwPosition const& rEndPos((**iter).GetMarkEnd()); + if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End()) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + MarkUndos.emplace_back(std::make_unique(**iter)); + } + // iter is into annotation mark vector so must be dereferenced! + rDMA.deleteMark(&**iter); + // this invalidates iter, have to start over... + iter = rDMA.getAnnotationMarksBegin(); + } + else + { // marks are sorted by start + if (*rPam.End() < (**iter).GetMarkStart()) + { + break; + } + ++iter; + } + } + + // tdf#119019 accept tracked paragraph formatting to do not hide new deletions + if (*rPam.GetPoint() != *rPam.GetMark()) + m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam); + + std::vector> undos; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + // (randomTest and testTdf54819 triggers it) + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in DeleteAndJoin"); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + for (SwRangeRedline * pRedline : redlines) + { + assert(pRedline->HasValidRange()); + undos.emplace_back(std::make_unique( + *pRedline, SwUndoId::DELETE)); + } + const SwRewriter aRewriter = undos.front()->GetRewriter(); + // can only group a single undo action + if (MarkUndos.empty() && undos.size() == 1 + && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() ); + SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast(pLastUndo)); + bool const bMerged = pUndoRedlineDel + && pUndoRedlineDel->CanGrouping(*undos.front()); + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front())); + } + undos.clear(); // prevent unmatched EndUndo + } + else + { + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + for (auto& it : MarkUndos) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it)); + } + for (auto & it : undos) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it)); + } + } + } + + for (SwRangeRedline *const pRedline : redlines) + { + // note: 1. the pRedline can still be merged & deleted + // 2. the impl. can even DeleteAndJoin the range => no plain PaM + std::shared_ptr const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark())); + pCursor->SetMark(); + *pCursor->GetPoint() = *pRedline->GetPoint(); + m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline, true); + // sw_redlinehide: 2 reasons why this is needed: + // 1. it's the first redline in node => RedlineDelText was sent but ignored + // 2. redline spans multiple nodes => must merge text frames + sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor); + } + m_rDoc.getIDocumentState().SetModified(); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + if (!undos.empty()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return true; +} + +bool DocumentContentOperationsManager::DeleteAndJoinImpl( SwPaM & rPam, + const bool bForceJoinNext ) +{ + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + // #i100466# + if ( bForceJoinNext ) + { + bJoinPrev = false; + } + + { + bool const bSuccess( DeleteRangeImpl( rPam ) ); + if (!bSuccess) + return false; + } + + if( bJoinText ) + { + ::sw_JoinText( rPam, bJoinPrev ); + } + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } + + return true; +} + +bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, const bool) +{ + // Move all cursors out of the deleted range, but first copy the + // passed PaM, because it could be a cursor that would be moved! + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); + + bool const bSuccess( DeleteRangeImplImpl( aDelPam ) ); + if (bSuccess) + { // now copy position from temp copy to given PaM + *rPam.GetPoint() = *aDelPam.GetPoint(); + } + + return bSuccess; +} + +bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) +{ + SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + if (!rPam.HasMark() + || (*pStt == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStt, *pEnd))) + { + return false; + } + + if( m_rDoc.GetAutoCorrExceptWord() ) + { + // if necessary the saved Word for the exception + if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() || pStt->nNode != pEnd->nNode || + pStt->nContent.GetIndex() + 1 != pEnd->nContent.GetIndex() || + !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt )) + { m_rDoc.DeleteAutoCorrExceptWord(); } + } + + { + // Delete all empty TextHints at the Mark's position + SwTextNode* pTextNd = rPam.GetMark()->nNode.GetNode().GetTextNode(); + SwpHints* pHts; + if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() ) + { + const sal_Int32 nMkCntPos = rPam.GetMark()->nContent.GetIndex(); + for( size_t n = pHts->Count(); n; ) + { + const SwTextAttr* pAttr = pHts->Get( --n ); + if( nMkCntPos > pAttr->GetStart() ) + break; + + const sal_Int32 *pEndIdx; + if( nMkCntPos == pAttr->GetStart() && + nullptr != (pEndIdx = pAttr->End()) && + *pEndIdx == pAttr->GetStart() ) + pTextNd->DestroyAttr( pHts->Cut( n ) ); + } + } + } + + { + // Send DataChanged before deletion, so that we still know + // which objects are in the range. + // Afterwards they could be before/after the Position. + SwDataChanged aTmp( rPam ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + bool bMerged(false); + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() ); + SwUndoDelete *const pUndoDelete( + dynamic_cast(pLastUndo) ); + if (pUndoDelete) + { + bMerged = pUndoDelete->CanGrouping( &m_rDoc, rPam ); + // if CanGrouping() returns true it's already merged + } + } + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique( rPam ) ); + } + + m_rDoc.getIDocumentState().SetModified(); + + return true; + } + + if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); + + // Delete and move all "Flys at the paragraph", which are within the Selection + DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode, + &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent); + DelBookmarks( + pStt->nNode, + pEnd->nNode, + nullptr, + &pStt->nContent, + &pEnd->nContent); + + SwNodeIndex aSttIdx( pStt->nNode ); + SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode(); + + do { // middle checked loop! + if( pCNd ) + { + SwTextNode * pStartTextNode( pCNd->GetTextNode() ); + if ( pStartTextNode ) + { + // now move the Content to the new Node + bool bOneNd = pStt->nNode == pEnd->nNode; + const sal_Int32 nLen = ( bOneNd ? pEnd->nContent.GetIndex() + : pCNd->Len() ) + - pStt->nContent.GetIndex(); + + // Don't call again, if already empty + if( nLen ) + { + pStartTextNode->EraseText( pStt->nContent, nLen ); + + if( !pStartTextNode->Len() ) + { + // METADATA: remove reference if empty (consider node deleted) + pStartTextNode->RemoveMetadataReference(); + } + } + + if( bOneNd ) // that's it + break; + + ++aSttIdx; + } + else + { + // So that there are no indices left registered when deleted, + // we remove a SwPaM from the Content here. + pStt->nContent.Assign( nullptr, 0 ); + } + } + + pCNd = pEnd->nNode.GetNode().GetContentNode(); + if( pCNd ) + { + SwTextNode * pEndTextNode( pCNd->GetTextNode() ); + if( pEndTextNode ) + { + // if already empty, don't call again + if( pEnd->nContent.GetIndex() ) + { + SwIndex aIdx( pCNd, 0 ); + pEndTextNode->EraseText( aIdx, pEnd->nContent.GetIndex() ); + + if( !pEndTextNode->Len() ) + { + // METADATA: remove reference if empty (consider node deleted) + pEndTextNode->RemoveMetadataReference(); + } + } + } + else + { + // So that there are no indices left registered when deleted, + // we remove a SwPaM from the Content here. + pEnd->nContent.Assign( nullptr, 0 ); + } + } + + // if the end is not a content node, delete it as well + sal_uInt32 nEnd = pEnd->nNode.GetIndex(); + if( pCNd == nullptr ) + nEnd++; + + if( aSttIdx != nEnd ) + { + // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete + SwNode *pTmpNd; + while (pEnd == rPam.GetPoint() + && nEnd + 2 < m_rDoc.GetNodes().Count() + && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode() + && pTmpNd->StartOfSectionNode()->IsSectionNode() + && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex()) + { + SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd); + m_rDoc.GetNodes().SectionUp(&range); + --nEnd; // account for deleted start node + } + + // delete the Nodes from the NodesArary + m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() ); + } + + // If the Node that contained the Cursor has been deleted, + // the Content has to be assigned to the current Content. + pStt->nContent.Assign( pStt->nNode.GetNode().GetContentNode(), + pStt->nContent.GetIndex() ); + + // If we deleted across Node boundaries we have to correct the PaM, + // because they are in different Nodes now. + // Also, the Selection is revoked. + *pEnd = *pStt; + rPam.DeleteMark(); + + } while( false ); + + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +// It's possible to call Replace with a PaM that spans 2 paragraphs: +// search with regex for "$", then replace _all_ +bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr, + const bool bRegExReplace ) +{ + if (!rPam.HasMark()) + return false; + + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + + { + // Create a copy of the Cursor in order to move all Pams from + // the other views out of the deletion range. + // Except for itself! + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); + + SwPosition *pStt = aDelPam.Start(), + *pEnd = aDelPam.End(); + bool bOneNode = pStt->nNode == pEnd->nNode; + + // Own Undo? + OUString sRepl( rStr ); + SwTextNode* pTextNd = pStt->nNode.GetNode().GetTextNode(); + sal_Int32 nStt = pStt->nContent.GetIndex(); + sal_Int32 nEnd; + + SwDataChanged aTmp( aDelPam ); + + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in ReplaceRange"); + + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + // If any Redline will change (split!) the node + const ::sw::mark::IMark* pBkmk = + m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam, + OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete ); + + *aDelPam.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + *aDelPam.GetMark() = pBkmk->GetOtherMarkPos(); + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + pStt = aDelPam.Start(); + pTextNd = pStt->nNode.GetNode().GetTextNode(); + nStt = pStt->nContent.GetIndex(); + } + + if( !sRepl.isEmpty() ) + { + // Apply the first character's attributes to the ReplaceText + SfxItemSet aSet( m_rDoc.GetAttrPool(), + svl::Items{} ); + pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 ); + + aSet.ClearItem( RES_TXTATR_REFMARK ); + aSet.ClearItem( RES_TXTATR_TOXMARK ); + aSet.ClearItem( RES_TXTATR_CJK_RUBY ); + aSet.ClearItem( RES_TXTATR_INETFMT ); + aSet.ClearItem( RES_TXTATR_META ); + aSet.ClearItem( RES_TXTATR_METAFIELD ); + + if( aDelPam.GetPoint() != aDelPam.End() ) + aDelPam.Exchange(); + + // Remember the End + SwNodeIndex aPtNd( aDelPam.GetPoint()->nNode, -1 ); + const sal_Int32 nPtCnt = aDelPam.GetPoint()->nContent.GetIndex(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + InsertString( aDelPam, sIns ); + if( bFirst ) + { + SwNodeIndex aMkNd( aDelPam.GetMark()->nNode, -1 ); + const sal_Int32 nMkCnt = aDelPam.GetMark()->nContent.GetIndex(); + + SplitNode( *aDelPam.GetPoint(), false ); + + ++aMkNd; + aDelPam.GetMark()->nNode = aMkNd; + aDelPam.GetMark()->nContent.Assign( + aMkNd.GetNode().GetContentNode(), nMkCnt ); + bFirst = false; + } + else + SplitNode( *aDelPam.GetPoint(), false ); + } + if( !sIns.isEmpty() ) + { + InsertString( aDelPam, sIns ); + } + + SwPaM aTmpRange( *aDelPam.GetPoint() ); + aTmpRange.SetMark(); + + ++aPtNd; + aDelPam.GetPoint()->nNode = aPtNd; + aDelPam.GetPoint()->nContent.Assign( aPtNd.GetNode().GetContentNode(), + nPtCnt); + *aTmpRange.GetMark() = *aDelPam.GetPoint(); + + m_rDoc.RstTextAttrs( aTmpRange ); + InsertItemSet( aTmpRange, aSet ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique( aDelPam, SwUndoId::REPLACE )); + } + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true); + + *rPam.GetMark() = *aDelPam.GetMark(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + *aDelPam.GetPoint() = *rPam.GetPoint(); + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + // If any Redline will change (split!) the node + const ::sw::mark::IMark* pBkmk = + m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam, + OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + + SwIndex& rIdx = aDelPam.GetPoint()->nContent; + rIdx.Assign( nullptr, 0 ); + aDelPam.GetMark()->nContent = rIdx; + rPam.GetPoint()->nNode = 0; + rPam.GetPoint()->nContent = rIdx; + *rPam.GetMark() = *rPam.GetPoint(); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + + *rPam.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + *rPam.GetMark() = pBkmk->GetOtherMarkPos(); + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + } + bJoinText = false; + } + else + { + assert((pStt->nNode == pEnd->nNode || + ( pStt->nNode.GetIndex() + 1 == pEnd->nNode.GetIndex() && + !pEnd->nContent.GetIndex() )) && + "invalid range: Point and Mark on different nodes" ); + + if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any ); + + SwUndoReplace* pUndoRpl = nullptr; + bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if (bDoesUndo) + { + pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(pUndoRpl)); + } + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + + if( aDelPam.GetPoint() != pStt ) + aDelPam.Exchange(); + + SwNodeIndex aPtNd( pStt->nNode, -1 ); + const sal_Int32 nPtCnt = pStt->nContent.GetIndex(); + + // Set the values again, if Frames or footnotes on the Text have been removed. + nStt = nPtCnt; + nEnd = bOneNode ? pEnd->nContent.GetIndex() + : pTextNd->GetText().getLength(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + if (!bFirst || nStt == pTextNd->GetText().getLength()) + { + InsertString( aDelPam, sIns ); + } + else if( nStt < nEnd || !sIns.isEmpty() ) + { + pTextNd->ReplaceText( pStt->nContent, nEnd - nStt, sIns ); + } + SplitNode( *pStt, false); + bFirst = false; + } + + if( bFirst || !sIns.isEmpty() ) + { + if (!bFirst || nStt == pTextNd->GetText().getLength()) + { + InsertString( aDelPam, sIns ); + } + else if( nStt < nEnd || !sIns.isEmpty() ) + { + pTextNd->ReplaceText( pStt->nContent, nEnd - nStt, sIns ); + } + } + + *rPam.GetPoint() = *aDelPam.GetMark(); + ++aPtNd; + rPam.GetMark()->nNode = aPtNd; + rPam.GetMark()->nContent.Assign( aPtNd.GetNode().GetContentNode(), + nPtCnt ); + + if (bJoinText) + { + assert(rPam.GetPoint() == rPam.End()); + // move so that SetEnd remembers position after sw_JoinText + rPam.Move(fnMoveBackward); + } + else if (aDelPam.GetPoint() == pStt) // backward selection? + { + assert(*rPam.GetMark() <= *rPam.GetPoint()); + rPam.Exchange(); // swap so that rPam is backwards + } + + if( pUndoRpl ) + { + pUndoRpl->SetEnd(rPam); + } + } + } + + bool bRet(true); + if (bJoinText) + { + bRet = ::sw_JoinText(rPam, bJoinPrev); + } + + m_rDoc.getIDocumentState().SetModified(); + return bRet; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* pFrameFormat) +{ + SwFlyFrameFormat *pFormat = nullptr; + if( pNode ) + { + pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA, + pFlyAttrSet, pFrameFormat ); + if( pGrfAttrSet ) + pNode->SetAttr( *pGrfAttrSet ); + } + return pFormat; +} + +#define NUMRULE_STATE \ + SfxItemState aNumRuleState = SfxItemState::UNKNOWN; \ + std::shared_ptr aNumRuleItem; \ + SfxItemState aListIdState = SfxItemState::UNKNOWN; \ + std::shared_ptr aListIdItem; \ + +#define PUSH_NUMRULE_STATE \ + lcl_PushNumruleState( aNumRuleState, aNumRuleItem, aListIdState, aListIdItem, pDestTextNd ); + +#define POP_NUMRULE_STATE \ + lcl_PopNumruleState( aNumRuleState, aNumRuleItem, aListIdState, aListIdItem, pDestTextNd, rPam ); + +static void lcl_PushNumruleState( + SfxItemState &aNumRuleState, std::shared_ptr& aNumRuleItem, + SfxItemState &aListIdState, std::shared_ptr& aListIdItem, + const SwTextNode *pDestTextNd ) +{ + // Safe numrule item at destination. + // #i86492# - Safe also item of destination. + const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet(); + if (pAttrSet != nullptr) + { + const SfxPoolItem * pItem = nullptr; + aNumRuleState = pAttrSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem); + if (SfxItemState::SET == aNumRuleState) + { + aNumRuleItem.reset(static_cast(pItem->Clone())); + } + + aListIdState = pAttrSet->GetItemState(RES_PARATR_LIST_ID, false, &pItem); + if (SfxItemState::SET == aListIdState) + { + aListIdItem.reset(static_cast(pItem->Clone())); + } + } +} + +static void lcl_PopNumruleState( + SfxItemState aNumRuleState, const std::shared_ptr& aNumRuleItem, + SfxItemState aListIdState, const std::shared_ptr& aListIdItem, + SwTextNode *pDestTextNd, const SwPaM& rPam ) +{ + /* If only a part of one paragraph is copied + restore the numrule at the destination. */ + // #i86492# - restore also item + if ( !lcl_MarksWholeNode(rPam) ) + { + if (SfxItemState::SET == aNumRuleState) + { + pDestTextNd->SetAttr(*aNumRuleItem); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_NUMRULE); + } + if (SfxItemState::SET == aListIdState) + { + pDestTextNd->SetAttr(*aListIdItem); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_LIST_ID); + } + } +} + +bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + SwPaM *const pCopyRange) const +{ + std::vector> Breaks; + + sw::CalcBreaks(Breaks, rPam, true); + + if (Breaks.empty()) + { + return CopyImplImpl(rPam, rPos, flags, pCopyRange); + } + + SwPosition const & rSelectionEnd( *rPam.End() ); + + bool bRet(true); + bool bFirst(true); + // iterate from end to start, ... don't think it's necessary here? + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + SwPaM copyRange(rPos, rPos); + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + // pass in copyRange member as rPos; should work ... + bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range); + nOffset = iter->first - rStart.nNode.GetIndex(); // fly nodes... + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + bFirst = false; + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + if (rStart < rEnd) // check if part is empty + { + bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range); + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + } + + return bRet; +} + +bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + SwPaM *const pCpyRange) const +{ + SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); + const bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); + + SwPosition const*const pStt = rPam.Start(); + SwPosition *const pEnd = rPam.End(); + + // Catch when there's no copy to do. + if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel) || + //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end + //JP 15.11.2001: don't test inclusive the end, ever exclusive + ( pDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd )) + { + return false; + } + + const bool bEndEqualIns = pDoc == &m_rDoc && rPos == *pEnd; + + // If Undo is enabled, create the UndoCopy object + SwUndoCpyDoc* pUndo = nullptr; + // lcl_DeleteRedlines may delete the start or end node of the cursor when + // removing the redlines so use cursor that is corrected by PaMCorrAbs + std::shared_ptr const pCopyPam(pDoc->CreateUnoCursor(rPos)); + + SwTableNumFormatMerge aTNFM( m_rDoc, *pDoc ); + std::unique_ptr> pFlys; + std::vector const* pFlysAtInsPos; + + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoCpyDoc(*pCopyPam); + pDoc->GetIDocumentUndoRedo().AppendUndo( std::unique_ptr(pUndo) ); + pFlysAtInsPos = pUndo->GetFlysAnchoredAt(); + } + else + { + pFlys = sw::GetFlysAnchoredAt(*pDoc, rPos.nNode.GetIndex()); + pFlysAtInsPos = pFlys.get(); + } + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + // Move the PaM one node back from the insert position, so that + // the position doesn't get moved + pCopyPam->SetMark(); + bool bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent); + // If the position was shifted from more than one node, an end node has been skipped + bool bAfterTable = false; + if ((rPos.nNode.GetIndex() - pCopyPam->GetPoint()->nNode.GetIndex()) > 1) + { + // First go back to the original place + pCopyPam->GetPoint()->nNode = rPos.nNode; + pCopyPam->GetPoint()->nContent = rPos.nContent; + + bCanMoveBack = false; + bAfterTable = true; + } + if( !bCanMoveBack ) + { + pCopyPam->GetPoint()->nNode--; + assert(pCopyPam->GetPoint()->nContent.GetIndex() == 0); + } + + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + SwNodeIndex aInsPos( rPos.nNode ); + const bool bOneNode = pStt->nNode == pEnd->nNode; + SwTextNode* pSttTextNd = pStt->nNode.GetNode().GetTextNode(); + SwTextNode* pEndTextNd = pEnd->nNode.GetNode().GetTextNode(); + SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode(); + bool bCopyCollFormat = !pDoc->IsInsOnlyTextGlossary() && + ( (pDestTextNd && !pDestTextNd->GetText().getLength()) || + ( !bOneNode && !rPos.nContent.GetIndex() ) ); + bool bCopyBookmarks = true; + bool bCopyPageSource = false; + int nDeleteTextNodes = 0; + + // #i104585# copy outline num rule to clipboard (for ASCII filter) + if (pDoc->IsClipBoard() && m_rDoc.GetOutlineNumRule()) + { + pDoc->SetOutlineNumRule(*m_rDoc.GetOutlineNumRule()); + } + + // #i86492# + // Correct the search for a previous list: + // First search for non-outline numbering list. Then search for non-outline + // bullet list. + // Keep also the value for possible propagation. + OUString aListIdToPropagate; + const SwNumRule* pNumRuleToPropagate = + pDoc->SearchNumRule( rPos, false, true, false, 0, aListIdToPropagate, nullptr, true ); + if ( !pNumRuleToPropagate ) + { + pNumRuleToPropagate = + pDoc->SearchNumRule( rPos, false, false, false, 0, aListIdToPropagate, nullptr, true ); + } + // #i86492# + // Do not propagate previous found list, if + // - destination is an empty paragraph which is not in a list and + // - source contains at least one paragraph which is not in a list + if ( pNumRuleToPropagate && + pDestTextNd && !pDestTextNd->GetText().getLength() && + !pDestTextNd->IsInList() && + !lcl_ContainsOnlyParagraphsInList( rPam ) ) + { + pNumRuleToPropagate = nullptr; + } + + // This do/while block is only there so that we can break out of it! + do { + if( pSttTextNd ) + { + ++nDeleteTextNodes; // must be joined in Undo + // Don't copy the beginning completely? + if( !bCopyCollFormat || bColumnSel || pStt->nContent.GetIndex() ) + { + SwIndex aDestIdx( rPos.nContent ); + bool bCopyOk = false; + if( !pDestTextNd ) + { + if( pStt->nContent.GetIndex() || bOneNode ) + pDestTextNd = pDoc->GetNodes().MakeTextNode( aInsPos, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + else + { + pDestTextNd = pSttTextNd->MakeCopy(pDoc, aInsPos, true)->GetTextNode(); + bCopyOk = true; + } + aDestIdx.Assign( pDestTextNd, 0 ); + bCopyCollFormat = true; + } + else if( !bOneNode || bColumnSel ) + { + const sal_Int32 nContentEnd = pEnd->nContent.GetIndex(); + { + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + + if (bCanMoveBack && rPos == *pCopyPam->GetPoint()) + { + // after the SplitNode, span the CpyPam correctly again + pCopyPam->Move( fnMoveBackward, GoInContent ); + pCopyPam->Move( fnMoveBackward, GoInContent ); + } + + pDestTextNd = pDoc->GetNodes()[ aInsPos.GetIndex()-1 ]->GetTextNode(); + aDestIdx.Assign( + pDestTextNd, pDestTextNd->GetText().getLength()); + + // Correct the area again + if( bEndEqualIns ) + { + bool bChg = pEnd != rPam.GetPoint(); + if( bChg ) + rPam.Exchange(); + rPam.Move( fnMoveBackward, GoInContent ); + if( bChg ) + rPam.Exchange(); + } + else if( rPos == *pEnd ) + { + // The end was also moved + pEnd->nNode--; + pEnd->nContent.Assign( pDestTextNd, nContentEnd ); + } + // tdf#63022 always reset pEndTextNd after SplitNode + aRg.aEnd = pEnd->nNode; + pEndTextNd = pEnd->nNode.GetNode().GetTextNode(); + } + + NUMRULE_STATE + if( bCopyCollFormat && bOneNode ) + { + PUSH_NUMRULE_STATE + } + + if( !bCopyOk ) + { + const sal_Int32 nCpyLen = ( bOneNode + ? pEnd->nContent.GetIndex() + : pSttTextNd->GetText().getLength()) + - pStt->nContent.GetIndex(); + pSttTextNd->CopyText( pDestTextNd, aDestIdx, + pStt->nContent, nCpyLen ); + if( bEndEqualIns ) + pEnd->nContent -= nCpyLen; + } + + aRg.aStart++; + + if( bOneNode ) + { + if (bCopyCollFormat) + { + pSttTextNd->CopyCollFormat( *pDestTextNd ); + POP_NUMRULE_STATE + } + + // copy at-char flys in rPam + SwNodeIndex temp(*pDestTextNd); // update to new (start) node for flys + // tdf#126626 prevent duplicate Undos + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(aRg, &rPam, temp, false); + + break; + } + } + } + else if( pDestTextNd ) + { + // Problems with insertion of table selections into "normal" text solved. + // We have to set the correct PaM for Undo, if this PaM starts in a textnode, + // the undo operation will try to merge this node after removing the table. + // If we didn't split a textnode, the PaM should start at the inserted table node + if( rPos.nContent.GetIndex() == pDestTextNd->Len() ) + { // Insertion at the last position of a textnode (empty or not) + ++aInsPos; // The table will be inserted behind the text node + } + else if( rPos.nContent.GetIndex() ) + { // Insertion in the middle of a text node, it has to be split + // (and joined from undo) + ++nDeleteTextNodes; + + const sal_Int32 nContentEnd = pEnd->nContent.GetIndex(); + { + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + + if (bCanMoveBack && rPos == *pCopyPam->GetPoint()) + { + // after the SplitNode, span the CpyPam correctly again + pCopyPam->Move( fnMoveBackward, GoInContent ); + pCopyPam->Move( fnMoveBackward, GoInContent ); + } + + // Correct the area again + if( bEndEqualIns ) + aRg.aEnd--; + // The end would also be moved + else if( rPos == *pEnd ) + { + rPos.nNode-=2; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + nContentEnd ); + rPos.nNode++; + aRg.aEnd--; + } + } + else if( bCanMoveBack ) + { // Insertion at the first position of a text node. It will not be split, the table + // will be inserted before the text node. + // See below, before the SetInsertRange function of the undo object will be called, + // the CpyPam would be moved to the next content position. This has to be avoided + // We want to be moved to the table node itself thus we have to set bCanMoveBack + // and to manipulate pCopyPam. + bCanMoveBack = false; + pCopyPam->GetPoint()->nNode--; + } + } + + pDestTextNd = aInsPos.GetNode().GetTextNode(); + if (pEndTextNd) + { + SwIndex aDestIdx( rPos.nContent ); + if( !pDestTextNd ) + { + pDestTextNd = pDoc->GetNodes().MakeTextNode( aInsPos, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + aDestIdx.Assign( pDestTextNd, 0 ); + aInsPos--; + + // if we have to insert an extra text node + // at the destination, this node will be our new destination + // (text) node, and thus we increment nDeleteTextNodes. This + // will ensure that this node will be deleted during Undo. + ++nDeleteTextNodes; // must be deleted + } + + const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty(); + + NUMRULE_STATE + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + PUSH_NUMRULE_STATE + } + + pEndTextNd->CopyText( pDestTextNd, aDestIdx, SwIndex( pEndTextNd ), + pEnd->nContent.GetIndex() ); + + // Also copy all format templates + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + pEndTextNd->CopyCollFormat( *pDestTextNd ); + if ( bOneNode ) + { + POP_NUMRULE_STATE + } + } + } + + SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet()) + { + aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() ); + if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) ) + pDestTextNd->ResetAttr( RES_BREAK ); + if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) ) + pDestTextNd->ResetAttr( RES_PAGEDESC ); + } + } + + { + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + std::pair tmp(rPam, startPos); + if( aInsPos == pEnd->nNode ) + { + SwNodeIndex aSaveIdx( aInsPos, -1 ); + assert(pStt->nNode != pEnd->nNode); + pEnd->nContent = 0; // TODO why this? + CopyWithFlyInFly(aRg, aInsPos, &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + ++aSaveIdx; + pEnd->nNode = aSaveIdx; + pEnd->nContent.Assign( aSaveIdx.GetNode().GetTextNode(), 0 ); + } + else + CopyWithFlyInFly(aRg, aInsPos, &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + + bCopyBookmarks = false; + } + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + // ... also for at-para anchors; here start is preferable because + // it's consistent with SplitNode from SwUndoInserts::RedoImpl() + if (pFlysAtInsPos) + { + // init *again* - because CopyWithFlyInFly moved startPos + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + SwPosition startPosAtPara(startPos); + startPosAtPara.nContent.Assign(nullptr, 0); + + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPos ); + pFly->SetFormatAttr(anchor); + } + else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPosAtPara ); + pFly->SetFormatAttr(anchor); + } + } + } + + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + // Put the breaks back into the first node + if( aBrkSet.Count() && nullptr != ( pDestTextNd = pDoc->GetNodes()[ + pCopyPam->GetPoint()->nNode.GetIndex()+1 ]->GetTextNode())) + { + pDestTextNd->SetAttr( aBrkSet ); + bCopyPageSource = true; + } + } + } while( false ); + + + // it is not possible to make this test when copy from the clipBoard to document + // in this case the PageNum not exist anymore + // tdf#39400 and tdf#97526 + // when copy from document to ClipBoard, and it is from the first page + // and not the source has the page break + if (pDoc->IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource) + { + pDestTextNd->ResetAttr(RES_BREAK); // remove the page-break + pDestTextNd->ResetAttr(RES_PAGEDESC); + } + + + // Adjust position (in case it was moved / in another node) + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + rPos.nContent.GetIndex() ); + + if( rPos.nNode != aInsPos ) + { + pCopyPam->GetMark()->nNode = aInsPos; + if (aInsPos < rPos.nNode) + { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above + pCopyPam->GetContentNode(false)->MakeEndIndex(&pCopyPam->GetMark()->nContent); + } + else // incremented in (!pSttTextNd && pDestTextNd) above + { + pCopyPam->GetMark()->nContent.Assign(pCopyPam->GetContentNode(false), 0); + } + rPos = *pCopyPam->GetMark(); + } + else + *pCopyPam->GetMark() = rPos; + + if ( !bAfterTable ) + pCopyPam->Move( fnMoveForward, bCanMoveBack ? GoInContent : GoInNode ); + else + { + pCopyPam->GetPoint()->nNode++; + + // Reset the offset to 0 as it was before the insertion + pCopyPam->GetPoint()->nContent.Assign(pCopyPam->GetPoint()->nNode.GetNode().GetContentNode(), 0); + // If the next node is a start node, then step back: the start node + // has been copied and needs to be in the selection for the undo + if (pCopyPam->GetPoint()->nNode.GetNode().IsStartNode()) + pCopyPam->GetPoint()->nNode--; + + } + pCopyPam->Exchange(); + + // Also copy all bookmarks + if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) + { + sw::CopyBookmarks(rPam, *pCopyPam->Start()); + } + + if( RedlineFlags::DeleteRedlines & eOld ) + { + assert(*pCopyPam->GetPoint() == rPos); + // the Node rPos points to may be deleted so unregister ... + rPos.nContent.Assign(nullptr, 0); + lcl_DeleteRedlines(rPam, *pCopyPam); + rPos = *pCopyPam->GetPoint(); // ... and restore. + } + + // If Undo is enabled, store the inserted area + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes); + } + + if( pCpyRange ) + { + pCpyRange->SetMark(); + *pCpyRange->GetPoint() = *pCopyPam->GetPoint(); + *pCpyRange->GetMark() = *pCopyPam->GetMark(); + } + + if ( pNumRuleToPropagate != nullptr ) + { + // #i86492# - use , because it also handles the + // Don't reset indent attributes, that would mean loss of direct + // formatting. + pDoc->SetNumRule( *pCopyPam, *pNumRuleToPropagate, false, nullptr, + aListIdToPropagate, true, /*bResetIndentAttrs=*/false ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + pDoc->getIDocumentState().SetModified(); + + return true; +} + + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentDeviceManager.cxx b/sw/source/core/doc/DocumentDeviceManager.cxx new file mode 100644 index 000000000..18da12493 --- /dev/null +++ b/sw/source/core/doc/DocumentDeviceManager.cxx @@ -0,0 +1,376 @@ +/* -*- 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 + +class SwDocShell; +class SwWait; + +namespace sw { + +DocumentDeviceManager::DocumentDeviceManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), mpPrt(nullptr), mpVirDev(nullptr) {} + +SfxPrinter* DocumentDeviceManager::getPrinter(/*[in]*/ bool bCreate ) const +{ + SfxPrinter* pRet = nullptr; + if ( !bCreate || mpPrt ) + pRet = mpPrt; + else + pRet = &CreatePrinter_(); + + return pRet; +} + +void DocumentDeviceManager::setPrinter(/*[in]*/ SfxPrinter *pP,/*[in]*/ bool bDeleteOld,/*[in]*/ bool bCallPrtDataChanged ) +{ + assert ( !pP || !pP->isDisposed() ); + if ( pP != mpPrt ) + { + if ( bDeleteOld ) + mpPrt.disposeAndClear(); + mpPrt = pP; + + // our printer should always use TWIP. Don't rely on this being set in SwViewShell::InitPrt, there + // are situations where this isn't called. #i108712# + if ( mpPrt ) + { + MapMode aMapMode( mpPrt->GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + mpPrt->SetMapMode( aMapMode ); + } + + if ( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && !m_rDoc.GetDocumentSettingManager().get( DocumentSettingId::USE_VIRTUAL_DEVICE ) ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( mpPrt ); + } + + if ( bCallPrtDataChanged && + // #i41075# Do not call PrtDataChanged() if we do not + // use the printer for formatting: + !m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) ) + PrtDataChanged(); +} + +VirtualDevice* DocumentDeviceManager::getVirtualDevice(/*[in]*/ bool bCreate ) const +{ + VirtualDevice* pRet = nullptr; + if ( !bCreate || mpVirDev ) + pRet = mpVirDev; + else + pRet = &CreateVirtualDevice_(); + + assert ( !pRet || !pRet->isDisposed() ); + + return pRet; +} + +void DocumentDeviceManager::setVirtualDevice(/*[in]*/ VirtualDevice* pVd ) +{ + assert ( !pVd->isDisposed() ); + + if ( mpVirDev.get() != pVd ) + { + mpVirDev.disposeAndClear(); + mpVirDev = pVd; + + if ( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && m_rDoc.GetDocumentSettingManager().get( DocumentSettingId::USE_VIRTUAL_DEVICE ) ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( mpVirDev ); + } +} + +OutputDevice* DocumentDeviceManager::getReferenceDevice(/*[in]*/ bool bCreate ) const +{ + OutputDevice* pRet = nullptr; + if ( !m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) ) + { + pRet = getPrinter( bCreate ); + + if ( bCreate && !mpPrt->IsValid() ) + { + pRet = getVirtualDevice( true ); + } + } + else + { + pRet = getVirtualDevice( bCreate ); + } + + assert ( !pRet || !pRet->isDisposed() ); + + return pRet; +} + +void DocumentDeviceManager::setReferenceDeviceType(/*[in]*/ bool bNewVirtual, /*[in]*/ bool bNewHiRes ) +{ + if ( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) != bNewVirtual || + m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE) != bNewHiRes ) + { + if ( bNewVirtual ) + { + VirtualDevice* pMyVirDev = getVirtualDevice( true ); + if ( !bNewHiRes ) + pMyVirDev->SetReferenceDevice( VirtualDevice::RefDevMode::Dpi600 ); + else + pMyVirDev->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + + if( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( pMyVirDev ); + } + else + { + // #i41075# + // We have to take care that a printer exists before calling + // PrtDataChanged() in order to prevent that PrtDataChanged() + // triggers this funny situation: + // getReferenceDevice()->getPrinter()->CreatePrinter_() + // ->setPrinter()-> PrtDataChanged() + SfxPrinter* pPrinter = getPrinter( true ); + if( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( pPrinter ); + } + + m_rDoc.GetDocumentSettingManager().set(DocumentSettingId::USE_VIRTUAL_DEVICE, bNewVirtual ); + m_rDoc.GetDocumentSettingManager().set(DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE, bNewHiRes ); + PrtDataChanged(); + m_rDoc.getIDocumentState().SetModified(); + } +} + +const JobSetup* DocumentDeviceManager::getJobsetup() const +{ + return mpPrt ? &mpPrt->GetJobSetup() : nullptr; +} + +void DocumentDeviceManager::setJobsetup(/*[in]*/ const JobSetup &rJobSetup ) +{ + bool bCheckPageDescs = !mpPrt; + bool bDataChanged = false; + + if ( mpPrt ) + { + if ( mpPrt->GetName() == rJobSetup.GetPrinterName() ) + { + if ( mpPrt->GetJobSetup() != rJobSetup ) + { + mpPrt->SetJobSetup( rJobSetup ); + bDataChanged = true; + } + } + else + mpPrt.disposeAndClear(); + } + + if( !mpPrt ) + { + //The ItemSet is deleted by Sfx! + auto pSet = std::make_unique( + m_rDoc.GetAttrPool(), + svl::Items< + SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN, + SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC, + SID_HTML_MODE, SID_HTML_MODE, + FN_PARAM_ADDPRINTER, FN_PARAM_ADDPRINTER>{}); + VclPtr p = VclPtr::Create( std::move(pSet), rJobSetup ); + if ( bCheckPageDescs ) + setPrinter( p, true, true ); + else + { + mpPrt = p; + bDataChanged = true; + } + } + if ( bDataChanged && !m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) ) + PrtDataChanged(); +} + +const SwPrintData & DocumentDeviceManager::getPrintData() const +{ + if(!mpPrtData) + { + DocumentDeviceManager * pThis = const_cast< DocumentDeviceManager * >(this); + pThis->mpPrtData.reset(new SwPrintData); + + // SwPrintData should be initialized from the configuration, + // the respective config item is implemented by SwPrintOptions which + // is also derived from SwPrintData + const SwDocShell *pDocSh = m_rDoc.GetDocShell(); + OSL_ENSURE( pDocSh, "pDocSh is 0, can't determine if this is a WebDoc or not" ); + bool bWeb = dynamic_cast< const SwWebDocShell * >(pDocSh) != nullptr; + SwPrintOptions aPrintOptions( bWeb ); + *pThis->mpPrtData = aPrintOptions; + } + return *mpPrtData; +} + +void DocumentDeviceManager::setPrintData(/*[in]*/ const SwPrintData& rPrtData ) +{ + if(!mpPrtData) + mpPrtData.reset(new SwPrintData); + *mpPrtData = rPrtData; +} + +DocumentDeviceManager::~DocumentDeviceManager() +{ + mpPrtData.reset(); + mpVirDev.disposeAndClear(); + mpPrt.disposeAndClear(); +} + +VirtualDevice& DocumentDeviceManager::CreateVirtualDevice_() const +{ +#ifdef IOS + VclPtr pNewVir = VclPtr::Create(DeviceFormat::GRAYSCALE); +#else + VclPtr pNewVir = VclPtr::Create(DeviceFormat::BITMASK); +#endif + + pNewVir->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + + // #i60945# External leading compatibility for unix systems. + if ( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::UNIX_FORCE_ZERO_EXT_LEADING ) ) + pNewVir->Compat_ZeroExtleadBug(); + + MapMode aMapMode( pNewVir->GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + pNewVir->SetMapMode( aMapMode ); + + const_cast(this)->setVirtualDevice( pNewVir ); + return *mpVirDev; +} + +SfxPrinter& DocumentDeviceManager::CreatePrinter_() const +{ + OSL_ENSURE( ! mpPrt, "Do not call CreatePrinter_(), call getPrinter() instead" ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("sw", "Printer will be created!" ); +#endif + + // We create a default SfxPrinter. + // The ItemSet is deleted by Sfx! + auto pSet = std::make_unique( + m_rDoc.GetAttrPool(), + svl::Items< + SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN, + SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC, + SID_HTML_MODE, SID_HTML_MODE, + FN_PARAM_ADDPRINTER, FN_PARAM_ADDPRINTER>{}); + + VclPtr pNewPrt = VclPtr::Create( std::move(pSet) ); + + // assign PrintData to newly created printer + const SwPrintData& rPrtData = getPrintData(); + SwAddPrinterItem aAddPrinterItem(rPrtData); + SfxItemSet aOptions(pNewPrt->GetOptions()); + aOptions.Put(aAddPrinterItem); + pNewPrt->SetOptions(aOptions); + + const_cast(this)->setPrinter( pNewPrt, true, true ); + return *mpPrt; +} + +void DocumentDeviceManager::PrtDataChanged() +{ +// If you change this, also modify InJobSetup in Sw3io if appropriate. + + // #i41075# + OSL_ENSURE( m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::USE_VIRTUAL_DEVICE) || + nullptr != getPrinter( false ), "PrtDataChanged will be called recursively!" ); + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + std::unique_ptr pWait; + bool bEndAction = false; + + if( m_rDoc.GetDocShell() ) + m_rDoc.GetDocShell()->UpdateFontList(); + + bool bDraw = true; + if ( pTmpRoot ) + { + SwViewShell *pSh = m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh && + (!pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsPrtFormat()) ) + { + if ( m_rDoc.GetDocShell() ) + pWait.reset(new SwWait( *m_rDoc.GetDocShell(), true )); + + pTmpRoot->StartAllAction(); + bEndAction = true; + + bDraw = false; + if( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + { + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetAddExtLeading( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::ADD_EXT_LEADING) ); + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( getReferenceDevice( false ) ); + } + + pFntCache->Flush(); + + for(SwRootFrame* aLayout : m_rDoc.GetAllLayouts()) + aLayout->InvalidateAllContent(SwInvalidateFlags::Size); + + for(SwViewShell& rShell : pSh->GetRingContainer()) + rShell.InitPrt(getPrinter(false)); + } + } + if ( bDraw && m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + { + const bool bTmpAddExtLeading = m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::ADD_EXT_LEADING); + if ( bTmpAddExtLeading != m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->IsAddExtLeading() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetAddExtLeading( bTmpAddExtLeading ); + + OutputDevice* pOutDev = getReferenceDevice( false ); + if ( pOutDev != m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetRefDevice() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( pOutDev ); + } + + m_rDoc.PrtOLENotify( true ); + + if ( bEndAction ) + pTmpRoot->EndAllAction(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentDrawModelManager.cxx b/sw/source/core/doc/DocumentDrawModelManager.cxx new file mode 100644 index 000000000..ef1aa1a29 --- /dev/null +++ b/sw/source/core/doc/DocumentDrawModelManager.cxx @@ -0,0 +1,356 @@ +/* -*- 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 + +class SdrOutliner; + +namespace sw +{ + +DocumentDrawModelManager::DocumentDrawModelManager(SwDoc& i_rSwdoc) + : m_rDoc(i_rSwdoc) + , mnHeaven(0) + , mnHell(0) + , mnControls(0) + , mnInvisibleHeaven(0) + , mnInvisibleHell(0) + , mnInvisibleControls(0) +{ +} + +// Is also called by the Sw3 Reader, if there was an error when reading the +// drawing layer. If it is called by the Sw3 Reader the layer is rebuilt +// from scratch. +void DocumentDrawModelManager::InitDrawModel() +{ + // !! Attention: there is similar code in the Sw3 Reader (sw3imp.cxx) that + // also has to be maintained!! + if ( mpDrawModel ) + ReleaseDrawModel(); + + // set FontHeight pool defaults without changing static SdrEngineDefaults + m_rDoc.GetAttrPool().SetPoolDefaultItem(SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT )); + + SAL_INFO( "sw.doc", "before create DrawDocument" ); + // The document owns the SwDrawModel. We always have two layers and one page. + mpDrawModel.reset( new SwDrawModel( &m_rDoc ) ); + + mpDrawModel->EnableUndo( m_rDoc.GetIDocumentUndoRedo().DoesUndo() ); + + OUString sLayerNm; + sLayerNm = "Hell"; + mnHell = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "Heaven"; + mnHeaven = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "Controls"; + mnControls = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + mpDrawModel->GetLayerAdmin().SetControlLayerName(sLayerNm); + + // add invisible layers corresponding to the visible ones. + { + sLayerNm = "InvisibleHell"; + mnInvisibleHell = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "InvisibleHeaven"; + mnInvisibleHeaven = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "InvisibleControls"; + mnInvisibleControls = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + } + + SdrPage* pMasterPage = mpDrawModel->AllocPage( false ); + mpDrawModel->InsertPage( pMasterPage ); + SAL_INFO( "sw.doc", "after create DrawDocument" ); + SdrOutliner& rOutliner = mpDrawModel->GetDrawOutliner(); + if (!utl::ConfigManager::IsFuzzing()) + { + SAL_INFO( "sw.doc", "before create Spellchecker/Hyphenator" ); + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpell = ::GetSpellChecker(); + rOutliner.SetSpeller( xSpell ); + css::uno::Reference< css::linguistic2::XHyphenator > xHyphenator( ::GetHyphenator() ); + rOutliner.SetHyphenator( xHyphenator ); + SAL_INFO( "sw.doc", "after create Spellchecker/Hyphenator" ); + } + m_rDoc.SetCalcFieldValueHdl(&rOutliner); + m_rDoc.SetCalcFieldValueHdl(&mpDrawModel->GetHitTestOutliner()); + + // Set the LinkManager in the model so that linked graphics can be inserted. + // The WinWord import needs it too. + mpDrawModel->SetLinkManager( & m_rDoc.getIDocumentLinksAdministration().GetLinkManager() ); + mpDrawModel->SetAddExtLeading( m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::ADD_EXT_LEADING) ); + + OutputDevice* pRefDev = m_rDoc.getIDocumentDeviceAccess().getReferenceDevice( false ); + if ( pRefDev ) + mpDrawModel->SetRefDevice( pRefDev ); + + mpDrawModel->SetNotifyUndoActionHdl( std::bind( &SwDoc::AddDrawUndo, &m_rDoc, std::placeholders::_1 )); + SwViewShell* const pSh = m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(const SwViewShell& rViewSh : pSh->GetRingContainer()) + { + SwRootFrame* pRoot = rViewSh.GetLayout(); + if( pRoot && !pRoot->GetDrawPage() ) + { + // Disable "multiple layout" for the moment: + // use pMasterPage instead of a new created SdrPage + // mpDrawModel->AllocPage( FALSE ); + // mpDrawModel->InsertPage( pDrawPage ); + SdrPage* pDrawPage = pMasterPage; + pRoot->SetDrawPage( pDrawPage ); + pDrawPage->SetSize( pRoot->getFrameArea().SSize() ); + } + } + } +} + + +void DocumentDrawModelManager::ReleaseDrawModel() +{ + // !! Also maintain the code in the sw3io for inserting documents!! + mpDrawModel.reset(); +} + + +const SwDrawModel* DocumentDrawModelManager::GetDrawModel() const +{ + return mpDrawModel.get(); +} + +SwDrawModel* DocumentDrawModelManager::GetDrawModel() +{ + return mpDrawModel.get(); +} + +SwDrawModel* DocumentDrawModelManager::MakeDrawModel_() +{ + OSL_ENSURE( !mpDrawModel, "MakeDrawModel_: Why?" ); + InitDrawModel(); + SwViewShell* const pSh = m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(SwViewShell& rViewSh : pSh->GetRingContainer()) + rViewSh.MakeDrawView(); + + // Broadcast, so that the FormShell can be connected to the DrawView + if( m_rDoc.GetDocShell() ) + { + SfxHint aHint( SfxHintId::SwDrawViewsCreated ); + m_rDoc.GetDocShell()->Broadcast( aHint ); + } + } + return mpDrawModel.get(); +} + +SwDrawModel* DocumentDrawModelManager::GetOrCreateDrawModel() +{ + return GetDrawModel() ? GetDrawModel() : MakeDrawModel_(); +} + +SdrLayerID DocumentDrawModelManager::GetHeavenId() const +{ + return mnHeaven; +} + +SdrLayerID DocumentDrawModelManager::GetHellId() const +{ + return mnHell; +} + +SdrLayerID DocumentDrawModelManager::GetControlsId() const +{ + return mnControls; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleHeavenId() const +{ + return mnInvisibleHeaven; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleHellId() const +{ + return mnInvisibleHell; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleControlsId() const +{ + return mnInvisibleControls; +} + +void DocumentDrawModelManager::NotifyInvisibleLayers( SdrPageView& _rSdrPageView ) +{ + OUString sLayerNm; + sLayerNm = "InvisibleHell"; + _rSdrPageView.SetLayerVisible( sLayerNm, false ); + + sLayerNm = "InvisibleHeaven"; + _rSdrPageView.SetLayerVisible( sLayerNm, false ); + + sLayerNm = "InvisibleControls"; + _rSdrPageView.SetLayerVisible( sLayerNm, false ); +} + +bool DocumentDrawModelManager::IsVisibleLayerId( SdrLayerID _nLayerId ) const +{ + bool bRetVal; + + if ( _nLayerId == GetHeavenId() || + _nLayerId == GetHellId() || + _nLayerId == GetControlsId() ) + { + bRetVal = true; + } + else if ( _nLayerId == GetInvisibleHeavenId() || + _nLayerId == GetInvisibleHellId() || + _nLayerId == GetInvisibleControlsId() ) + { + bRetVal = false; + } + else + { + OSL_FAIL( " - unknown layer ID." ); + bRetVal = false; + } + + return bRetVal; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleLayerIdByVisibleOne( SdrLayerID _nVisibleLayerId ) +{ + SdrLayerID nInvisibleLayerId; + + if ( _nVisibleLayerId == GetHeavenId() ) + { + nInvisibleLayerId = GetInvisibleHeavenId(); + } + else if ( _nVisibleLayerId == GetHellId() ) + { + nInvisibleLayerId = GetInvisibleHellId(); + } + else if ( _nVisibleLayerId == GetControlsId() ) + { + nInvisibleLayerId = GetInvisibleControlsId(); + } + else if ( _nVisibleLayerId == GetInvisibleHeavenId() || + _nVisibleLayerId == GetInvisibleHellId() || + _nVisibleLayerId == GetInvisibleControlsId() ) + { + OSL_FAIL( " - given layer ID already an invisible one." ); + nInvisibleLayerId = _nVisibleLayerId; + } + else + { + OSL_FAIL( " - given layer ID is unknown." ); + nInvisibleLayerId = _nVisibleLayerId; + } + + return nInvisibleLayerId; +} + +bool DocumentDrawModelManager::Search(const SwPaM& rPaM, const SvxSearchItem& rSearchItem) +{ + SwPosFlyFrames aFrames = m_rDoc.GetAllFlyFormats(&rPaM, /*bDrawAlso=*/true); + + for (const SwPosFlyFramePtr& pPosFlyFrame : aFrames) + { + // Filter for at-paragraph anchored draw frames. + const SwFrameFormat& rFrameFormat = pPosFlyFrame->GetFormat(); + const SwFormatAnchor& rAnchor = rFrameFormat.GetAnchor(); + if (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PARA || rFrameFormat.Which() != RES_DRAWFRMFMT) + continue; + + // Does the shape have matching text? + SdrOutliner& rOutliner = GetDrawModel()->GetDrawOutliner(); + SdrObject* pObject = const_cast(rFrameFormat.FindSdrObject()); + SdrTextObj* pTextObj = dynamic_cast(pObject); + if (!pTextObj) + continue; + const OutlinerParaObject* pParaObj = pTextObj->GetOutlinerParaObject(); + if (!pParaObj) + continue; + rOutliner.SetText(*pParaObj); + SwDocShell* pDocShell = m_rDoc.GetDocShell(); + if (!pDocShell) + return false; + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + if (!pWrtShell) + return false; + if (!rOutliner.HasText(rSearchItem)) + continue; + + // If so, then select highlight the search result. + pWrtShell->SelectObj(Point(), 0, pObject); + SwView* pView = pDocShell->GetView(); + if (!pView) + return false; + if (!pView->EnterShapeDrawTextMode(pObject)) + continue; + SdrView* pSdrView = pWrtShell->GetDrawView(); + if (!pSdrView) + return false; + OutlinerView* pOutlinerView = pSdrView->GetTextEditOutlinerView(); + if (!rSearchItem.GetBackward()) + pOutlinerView->SetSelection(ESelection(0, 0, 0, 0)); + else + pOutlinerView->SetSelection(ESelection(EE_PARA_MAX_COUNT, EE_TEXTPOS_MAX_COUNT, EE_PARA_MAX_COUNT, EE_TEXTPOS_MAX_COUNT)); + pOutlinerView->StartSearchAndReplace(rSearchItem); + return true; + } + + return false; +} + +void DocumentDrawModelManager::DrawNotifyUndoHdl() +{ + mpDrawModel->SetNotifyUndoActionHdl( nullptr ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentExternalDataManager.cxx b/sw/source/core/doc/DocumentExternalDataManager.cxx new file mode 100644 index 000000000..3e751a316 --- /dev/null +++ b/sw/source/core/doc/DocumentExternalDataManager.cxx @@ -0,0 +1,34 @@ +/* -*- 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 + +namespace sw +{ + +void DocumentExternalDataManager::setExternalData(::sw::tExternalDataType eType, ::sw::tExternalDataPointer pPayload) +{ + m_externalData[eType] = pPayload; +} + +::sw::tExternalDataPointer DocumentExternalDataManager::getExternalData(::sw::tExternalDataType eType) +{ + return m_externalData[eType]; +} + +} diff --git a/sw/source/core/doc/DocumentFieldsManager.cxx b/sw/source/core/doc/DocumentFieldsManager.cxx new file mode 100644 index 000000000..800c90967 --- /dev/null +++ b/sw/source/core/doc/DocumentFieldsManager.cxx @@ -0,0 +1,1860 @@ +/* -*- 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 + +using namespace ::com::sun::star::uno; + +namespace sw +{ + bool IsFieldDeletedInModel(IDocumentRedlineAccess const& rIDRA, + SwTextField const& rTextField) + { + SwRedlineTable::size_type tmp; + SwPosition const pos(rTextField.GetTextNode(), + rTextField.GetStart()); + SwRangeRedline const*const pRedline(rIDRA.GetRedline(pos, &tmp)); + return (pRedline + && pRedline->GetType() == RedlineType::Delete + && *pRedline->GetPoint() != *pRedline->GetMark()); + } +} + +namespace +{ + #if HAVE_FEATURE_DBCONNECTIVITY + + OUString lcl_GetDBVarName( SwDoc& rDoc, SwDBNameInfField& rDBField ) + { + SwDBData aDBData( rDBField.GetDBData( &rDoc )); + OUString sDBNumNm; + SwDBData aDocData = rDoc.GetDBData(); + + if( aDBData != aDocData ) + { + sDBNumNm = aDBData.sDataSource + OUStringChar(DB_DELIM) + + aDBData.sCommand + OUStringChar(DB_DELIM); + } + sDBNumNm += SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber); + + return sDBNumNm; + } + + #endif + + bool IsFieldDeleted(IDocumentRedlineAccess const& rIDRA, + SwRootFrame const& rLayout, SwTextField const& rTextField) + { + SwTextNode const& rNode(rTextField.GetTextNode()); + bool const isInBody( + rNode.GetNodes().GetEndOfExtras().GetIndex() < rNode.GetIndex()); + if (!isInBody && nullptr == rNode.getLayoutFrame(&rLayout)) + { // see SwDocUpdateField::GetBodyNode() - fields in hidden sections + // don't have layout frames but must be updated, so use the same + // check as there, but do it again because GetBodyNode() checks + // for *any* layout... + return true; + } + return sw::IsFieldDeletedInModel(rIDRA, rTextField); + } + + void lcl_CalcField( SwDoc& rDoc, SwCalc& rCalc, const SetGetExpField& rSGEField, + SwDBManager* pMgr, SwRootFrame const*const pLayout) + { + const SwTextField* pTextField = rSGEField.GetTextField(); + if( !pTextField ) + return ; + + if (pLayout && pLayout->IsHideRedlines() + && IsFieldDeleted(rDoc.getIDocumentRedlineAccess(), *pLayout, *pTextField)) + { + return; + } + + const SwField* pField = pTextField->GetFormatField().GetField(); + const SwFieldIds nFieldWhich = pField->GetTyp()->Which(); + + if( SwFieldIds::SetExp == nFieldWhich ) + { + SwSbxValue aValue; + if( nsSwGetSetExpType::GSE_EXPR & pField->GetSubType() ) + aValue.PutDouble( static_cast(pField)->GetValue(pLayout) ); + else + // Extension to calculate with Strings + aValue.PutString( static_cast(pField)->GetExpStr(pLayout) ); + + // set the new value in Calculator + rCalc.VarChange( pField->GetTyp()->GetName(), aValue ); + } + else if( pMgr ) + { + #if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDoc; + #else + switch( nFieldWhich ) + { + case SwFieldIds::DbNumSet: + { + SwDBNumSetField* pDBField = const_cast(static_cast(pField)); + + SwDBData aDBData(pDBField->GetDBData(&rDoc)); + + if( pDBField->IsCondValid() && + pMgr->OpenDataSource( aDBData.sDataSource, aDBData.sCommand )) + rCalc.VarChange( lcl_GetDBVarName( rDoc, *pDBField), + pDBField->GetFormat() ); + } + break; + case SwFieldIds::DbNextSet: + { + SwDBNextSetField* pDBField = const_cast(static_cast(pField)); + SwDBData aDBData(pDBField->GetDBData(&rDoc)); + if( !pDBField->IsCondValid() || + !pMgr->OpenDataSource( aDBData.sDataSource, aDBData.sCommand )) + break; + + OUString sDBNumNm(lcl_GetDBVarName( rDoc, *pDBField)); + SwCalcExp* pExp = rCalc.VarLook( sDBNumNm ); + if( pExp ) + rCalc.VarChange( sDBNumNm, pExp->nValue.GetLong() + 1 ); + } + break; + + default: break; + } + #endif + } + } +} + +namespace sw +{ + +DocumentFieldsManager::DocumentFieldsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), + mbNewFieldLst(true), + mpUpdateFields(new SwDocUpdateField(m_rDoc)), + mpFieldTypes( new SwFieldTypes ), + mnLockExpField( 0 ) +{ +} + +const SwFieldTypes* DocumentFieldsManager::GetFieldTypes() const +{ + return mpFieldTypes.get(); +} + +/** Insert field types + * + * @param rFieldTyp ??? + * @return Always returns a pointer to the type, if it's new or already added. + */ +SwFieldType* DocumentFieldsManager::InsertFieldType(const SwFieldType &rFieldTyp) +{ + const SwFieldTypes::size_type nSize = mpFieldTypes->size(); + const SwFieldIds nFieldWhich = rFieldTyp.Which(); + + SwFieldTypes::size_type i = INIT_FLDTYPES; + + switch( nFieldWhich ) + { + case SwFieldIds::SetExp: + //JP 29.01.96: SequenceFields start at INIT_FLDTYPES - 3!! + // Or we get doubble number circles!! + //MIB 14.03.95: From now on also the SW3-Reader relies on this, when + //constructing string pools and when reading SetExp fields + if( nsSwGetSetExpType::GSE_SEQ & static_cast(rFieldTyp).GetType() ) + i -= INIT_SEQ_FLDTYPES; + [[fallthrough]]; + case SwFieldIds::Database: + case SwFieldIds::User: + case SwFieldIds::Dde: + { + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + OUString sFieldNm( rFieldTyp.GetName() ); + for( ; i < nSize; ++i ) + if( nFieldWhich == (*mpFieldTypes)[i]->Which() && + rSCmp.isEqual( sFieldNm, (*mpFieldTypes)[i]->GetName() )) + return (*mpFieldTypes)[i].get(); + } + break; + + case SwFieldIds::TableOfAuthorities: + for( ; i < nSize; ++i ) + if( nFieldWhich == (*mpFieldTypes)[i]->Which() ) + return (*mpFieldTypes)[i].get(); + break; + + default: + for( i = 0; i < nSize; ++i ) + if( nFieldWhich == (*mpFieldTypes)[i]->Which() ) + return (*mpFieldTypes)[i].get(); + } + + std::unique_ptr pNew = rFieldTyp.Copy(); + switch( nFieldWhich ) + { + case SwFieldIds::Dde: + static_cast(pNew.get())->SetDoc( &m_rDoc ); + break; + + case SwFieldIds::Database: + case SwFieldIds::Table: + case SwFieldIds::DateTime: + case SwFieldIds::GetExp: + static_cast(pNew.get())->SetDoc( &m_rDoc ); + break; + + case SwFieldIds::User: + case SwFieldIds::SetExp: + static_cast(pNew.get())->SetDoc( &m_rDoc ); + // JP 29.07.96: Optionally prepare FieldList for Calculator: + mpUpdateFields->InsertFieldType( *pNew ); + break; + case SwFieldIds::TableOfAuthorities : + static_cast(pNew.get())->SetDoc( &m_rDoc ); + break; + default: break; + } + + mpFieldTypes->insert( mpFieldTypes->begin() + nSize, std::move(pNew) ); + m_rDoc.getIDocumentState().SetModified(); + + return (*mpFieldTypes)[ nSize ].get(); +} + +/// @returns the field type of the Doc +SwFieldType *DocumentFieldsManager::GetSysFieldType( const SwFieldIds eWhich ) const +{ + for( SwFieldTypes::size_type i = 0; i < INIT_FLDTYPES; ++i ) + if( eWhich == (*mpFieldTypes)[i]->Which() ) + return (*mpFieldTypes)[i].get(); + return nullptr; +} + +/// Find first type with ResId and name +SwFieldType* DocumentFieldsManager::GetFieldType( + SwFieldIds nResId, + const OUString& rName, + bool bDbFieldMatching // used in some UNO calls for SwFieldIds::Database to use different string matching code #i51815# + ) const +{ + const SwFieldTypes::size_type nSize = mpFieldTypes->size(); + SwFieldTypes::size_type i {0}; + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + + switch( nResId ) + { + case SwFieldIds::SetExp: + //JP 29.01.96: SequenceFields start at INIT_FLDTYPES - 3!! + // Or we get doubble number circles!! + //MIB 14.03.95: From now on also the SW3-Reader relies on this, when + //constructing string pools and when reading SetExp fields + i = INIT_FLDTYPES - INIT_SEQ_FLDTYPES; + break; + + case SwFieldIds::Database: + case SwFieldIds::User: + case SwFieldIds::Dde: + case SwFieldIds::TableOfAuthorities: + i = INIT_FLDTYPES; + break; + default: break; + } + + SwFieldType* pRet = nullptr; + for( ; i < nSize; ++i ) + { + SwFieldType* pFieldType = (*mpFieldTypes)[i].get(); + + if (nResId == pFieldType->Which()) + { + OUString aFieldName( pFieldType->GetName() ); + if (bDbFieldMatching && nResId == SwFieldIds::Database) // #i51815# + aFieldName = aFieldName.replace(DB_DELIM, '.'); + + if (rSCmp.isEqual( rName, aFieldName )) + { + pRet = pFieldType; + break; + } + } + } + return pRet; +} + +/// Remove field type +void DocumentFieldsManager::RemoveFieldType(size_t nField) +{ + OSL_ENSURE( INIT_FLDTYPES <= nField, "don't remove InitFields" ); + /* + * Dependent fields present -> ErrRaise + */ + if(nField < mpFieldTypes->size()) + { + SwFieldType* pTmp = (*mpFieldTypes)[nField].get(); + + // JP 29.07.96: Optionally prepare FieldList for Calculator + SwFieldIds nWhich = pTmp->Which(); + switch( nWhich ) + { + case SwFieldIds::SetExp: + case SwFieldIds::User: + mpUpdateFields->RemoveFieldType( *pTmp ); + [[fallthrough]]; + case SwFieldIds::Dde: + if( pTmp->HasWriterListeners() && !m_rDoc.IsUsed( *pTmp ) ) + { + if( SwFieldIds::SetExp == nWhich ) + static_cast(pTmp)->SetDeleted( true ); + else if( SwFieldIds::User == nWhich ) + static_cast(pTmp)->SetDeleted( true ); + else + static_cast(pTmp)->SetDeleted( true ); + nWhich = SwFieldIds::Database; + } + break; + default: break; + } + + if( nWhich != SwFieldIds::Database ) + { + OSL_ENSURE( !pTmp->HasWriterListeners(), "Dependent fields present!" ); + } + else + (*mpFieldTypes)[nField].release(); // DB fields are ref-counted and delete themselves + + mpFieldTypes->erase( mpFieldTypes->begin() + nField ); + m_rDoc.getIDocumentState().SetModified(); + } +} + +// All have to be re-evaluated. +void DocumentFieldsManager::UpdateFields( bool bCloseDB ) +{ + // Call Modify() for every field type, + // dependent SwTextField get notified ... + + for( auto const & pFieldType : *mpFieldTypes ) + { + switch( pFieldType->Which() ) + { + // Update table fields second to last + // Update references last + case SwFieldIds::GetRef: + case SwFieldIds::Table: + case SwFieldIds::Database: + case SwFieldIds::JumpEdit: + case SwFieldIds::RefPageSet: // are never expanded! + break; + + case SwFieldIds::Dde: + { + SwMsgPoolItem aUpdateDDE( RES_UPDATEDDETBL ); + pFieldType->ModifyNotification( nullptr, &aUpdateDDE ); + break; + } + case SwFieldIds::GetExp: + case SwFieldIds::SetExp: + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + // Expression fields are treated separately + break; + default: + pFieldType->ModifyNotification ( nullptr, nullptr ); + } + } + + if( !IsExpFieldsLocked() ) + UpdateExpFields( nullptr, false ); // update expression fields + + // Tables + UpdateTableFields(nullptr); + + // References + UpdateRefFields(); + if( bCloseDB ) + { +#if HAVE_FEATURE_DBCONNECTIVITY + m_rDoc.GetDBManager()->CloseAll(); +#endif + } + // Only evaluate on full update + m_rDoc.getIDocumentState().SetModified(); +} + +void DocumentFieldsManager::InsDeletedFieldType( SwFieldType& rFieldTyp ) +{ + // The FieldType was marked as deleted and removed from the array. + // One has to look this up again, now. + // - If it's not present, it can be re-inserted. + // - If the same type is found, the deleted one has to be renamed. + + const SwFieldTypes::size_type nSize = mpFieldTypes->size(); + const SwFieldIds nFieldWhich = rFieldTyp.Which(); + + OSL_ENSURE( SwFieldIds::SetExp == nFieldWhich || + SwFieldIds::User == nFieldWhich || + SwFieldIds::Dde == nFieldWhich, "Wrong FieldType" ); + + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + const OUString& rFieldNm = rFieldTyp.GetName(); + + for( SwFieldTypes::size_type i = INIT_FLDTYPES; i < nSize; ++i ) + { + SwFieldType* pFnd = (*mpFieldTypes)[i].get(); + if( nFieldWhich == pFnd->Which() && + rSCmp.isEqual( rFieldNm, pFnd->GetName() ) ) + { + // find new name + SwFieldTypes::size_type nNum = 1; + do { + OUString sSrch = rFieldNm + OUString::number( nNum ); + for( i = INIT_FLDTYPES; i < nSize; ++i ) + { + pFnd = (*mpFieldTypes)[i].get(); + if( nFieldWhich == pFnd->Which() && + rSCmp.isEqual( sSrch, pFnd->GetName() ) ) + break; + } + if( i >= nSize ) // not found + { + const_cast(rFieldNm) = sSrch; + break; // exit while loop + } + ++nNum; + } while( true ); + break; + } + } + + // not found, so insert, and updated deleted flag + mpFieldTypes->insert( mpFieldTypes->begin() + nSize, std::unique_ptr(&rFieldTyp) ); + switch( nFieldWhich ) + { + case SwFieldIds::SetExp: + static_cast(rFieldTyp).SetDeleted( false ); + break; + case SwFieldIds::User: + static_cast(rFieldTyp).SetDeleted( false ); + break; + case SwFieldIds::Dde: + static_cast(rFieldTyp).SetDeleted( false ); + break; + default: break; + } +} + +void DocumentFieldsManager::PutValueToField(const SwPosition & rPos, + const Any& rVal, sal_uInt16 nWhich) +{ + Any aOldVal; + SwField * pField = GetFieldAtPos(rPos); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + pField->QueryValue(aOldVal, nWhich)) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique(rPos, aOldVal, rVal, nWhich)); + } + + pField->PutValue(rVal, nWhich); +} + +bool DocumentFieldsManager::UpdateField(SwTextField * pDstTextField, SwField & rSrcField, + SwMsgPoolItem * pMsgHint, + bool bUpdateFields) +{ + OSL_ENSURE(pDstTextField, "no field to update!"); + + bool bTableSelBreak = false; + + SwFormatField * pDstFormatField = const_cast(&pDstTextField->GetFormatField()); + SwField * pDstField = pDstFormatField->GetField(); + SwFieldIds nFieldWhich = rSrcField.GetTyp()->Which(); + SwNodeIndex aTableNdIdx(pDstTextField->GetTextNode()); + + if (pDstField->GetTyp()->Which() == + rSrcField.GetTyp()->Which()) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + SwPosition aPosition( pDstTextField->GetTextNode() ); + aPosition.nContent = pDstTextField->GetStart(); + + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique( aPosition, *pDstField, rSrcField, pMsgHint, bUpdateFields) ); + } + + pDstFormatField->SetField(rSrcField.CopyField()); + SwField* pNewField = pDstFormatField->GetField(); + + switch( nFieldWhich ) + { + case SwFieldIds::SetExp: + case SwFieldIds::GetExp: + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + UpdateExpFields( pDstTextField, true ); + break; + + case SwFieldIds::Table: + { + const SwTableNode* pTableNd = + m_rDoc.IsIdxInTable(aTableNdIdx); + if( pTableNd ) + { + SwTableFormulaUpdate aTableUpdate( &pTableNd-> + GetTable() ); + if (bUpdateFields) + UpdateTableFields( &aTableUpdate ); + else + pNewField->GetTyp()->ModifyNotification(nullptr, &aTableUpdate); + + if (! bUpdateFields) + bTableSelBreak = true; + } + } + break; + + case SwFieldIds::Macro: + if( bUpdateFields && pDstTextField->GetpTextNode() ) + pDstTextField->GetpTextNode()-> + ModifyNotification( nullptr, pDstFormatField ); + break; + + case SwFieldIds::DatabaseName: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + m_rDoc.ChgDBData(static_cast( pNewField)->GetRealDBData()); + pNewField->GetTyp()->UpdateFields(); + + break; + + case SwFieldIds::Database: +#if HAVE_FEATURE_DBCONNECTIVITY + { + // JP 10.02.96: call ChgValue, so that the style change sets the + // ContentString correctly + SwDBField* pDBField = static_cast(pNewField); + if (pDBField->IsInitialized()) + pDBField->ChgValue( pDBField->GetValue(), true ); + + pDBField->ClearInitialized(); + pDBField->InitContent(); + } +#endif + [[fallthrough]]; + + default: + pDstFormatField->UpdateTextNode(nullptr, pMsgHint); + } + + // The fields we can calculate here are being triggered for an update + // here explicitly. + if( nFieldWhich == SwFieldIds::User ) + UpdateUsrFields(); + } + + return bTableSelBreak; +} + +/// Update reference and table fields +void DocumentFieldsManager::UpdateRefFields() +{ + for( auto const & pFieldType : *mpFieldTypes ) + if( SwFieldIds::GetRef == pFieldType->Which() ) + pFieldType->ModifyNotification( nullptr, nullptr ); +} + +void DocumentFieldsManager::UpdateTableFields( SfxPoolItem* pHt ) +{ + OSL_ENSURE( !pHt || RES_TABLEFML_UPDATE == pHt->Which(), + "What MessageItem is this?" ); + + auto pFieldType = GetFieldType( SwFieldIds::Table, OUString(), false ); + if(pFieldType) + { + std::vector vFields; + pFieldType->GatherFields(vFields); + SwTableFormulaUpdate* pUpdateField = nullptr; + if( pHt && RES_TABLEFML_UPDATE == pHt->Which() ) + pUpdateField = static_cast(pHt); + for(auto pFormatField : vFields) + { + SwTableField* pField = static_cast(pFormatField->GetField()); + if( pUpdateField ) + { + // table where this field is located + const SwTableNode* pTableNd; + const SwTextNode& rTextNd = pFormatField->GetTextField()->GetTextNode(); + pTableNd = rTextNd.FindTableNode(); + if (pTableNd == nullptr) + continue; + + switch( pUpdateField->m_eFlags ) + { + case TBL_CALC: + // re-set the value flag + // JP 17.06.96: internal representation of all formulas + // (reference to other table!!!) + if( nsSwExtendedSubType::SUB_CMD & pField->GetSubType() ) + pField->PtrToBoxNm( pUpdateField->m_pTable ); + else + pField->ChgValid( false ); + break; + case TBL_BOXNAME: + // is this the wanted table? + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + // to the external representation + pField->PtrToBoxNm( pUpdateField->m_pTable ); + break; + case TBL_BOXPTR: + // to the internal representation + // JP 17.06.96: internal representation on all formulas + // (reference to other table!!!) + pField->BoxNmToPtr( pUpdateField->m_pTable ); + break; + case TBL_RELBOXNAME: + // is this the wanted table? + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + // to the relative representation + pField->ToRelBoxNm( pUpdateField->m_pTable ); + break; + default: + break; + } + } + else + // reset the value flag for all + pField->ChgValid( false ); + } + } + // process all table box formulas + for (const SfxPoolItem* pItem : m_rDoc.GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + auto pBoxFormula = dynamic_cast(pItem); + if( pBoxFormula && pBoxFormula->GetDefinedIn() ) + { + const_cast(pBoxFormula)->ChangeState( pHt ); + } + } + + SwRootFrame const* pLayout(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + assert(!pLayout || pLay->IsHideRedlines() == pLayout->IsHideRedlines()); // TODO + pLayout = pLay; + } + + // all fields/boxes are now invalid, so we can start to calculate + if( pHt && ( RES_TABLEFML_UPDATE != pHt->Which() || + TBL_CALC != static_cast(pHt)->m_eFlags )) + return ; + + std::unique_ptr> pCalc; + + if( pFieldType ) + { + std::vector vFields; + pFieldType->GatherFields(vFields); + for(SwFormatField* pFormatField: vFields) + { + // start calculation at the end + // new fields are inserted at the beginning of the modify chain + // that gives faster calculation on import + // mba: do we really need this "optimization"? Is it still valid? + SwTableField *const pField(static_cast(pFormatField->GetField())); + if (nsSwExtendedSubType::SUB_CMD & pField->GetSubType()) + continue; + + // needs to be recalculated + if( !pField->IsValid() ) + { + // table where this field is located + const SwTextNode& rTextNd = pFormatField->GetTextField()->GetTextNode(); + const SwTableNode* pTableNd = rTextNd.FindTableNode(); + if( !pTableNd ) + continue; + + // if this field is not in the to-be-updated table, skip it + if( pHt && &pTableNd->GetTable() != + static_cast(pHt)->m_pTable ) + continue; + + if( !pCalc ) + pCalc.reset(new SwCalc( m_rDoc )); + + // get the values of all SetExpression fields that are valid + // until the table + SwFrame* pFrame = nullptr; + if( pTableNd->GetIndex() < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + // is in the special section, that's expensive! + Point aPt; // return the first frame of the layout - Tab.Headline!! + std::pair const tmp(aPt, true); + pFrame = rTextNd.getLayoutFrame(pLayout, nullptr, &tmp); + if( pFrame ) + { + SwPosition aPos( *pTableNd ); + if( GetBodyTextNode( m_rDoc, aPos, *pFrame ) ) + FieldsToCalc( *pCalc, SetGetExpField( + aPos.nNode, pFormatField->GetTextField(), + &aPos.nContent), pLayout); + else + pFrame = nullptr; + } + } + if( !pFrame ) + { + // create index to determine the TextNode + SwNodeIndex aIdx( rTextNd ); + FieldsToCalc( *pCalc, + SetGetExpField(aIdx, pFormatField->GetTextField()), + pLayout); + } + + SwTableCalcPara aPara(*pCalc, pTableNd->GetTable(), pLayout); + pField->CalcField( aPara ); + if( aPara.IsStackOverflow() ) + { + bool const bResult = aPara.CalcWithStackOverflow(); + if (bResult) + { + pField->CalcField( aPara ); + } + OSL_ENSURE(bResult, + "the chained formula could no be calculated"); + } + pCalc->SetCalcError( SwCalcError::NONE ); + } + pFormatField->UpdateTextNode(nullptr, pHt); + } + } + + // calculate the formula at the boxes + for (const SfxPoolItem* pItem : m_rDoc.GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + auto pFormula = const_cast(dynamic_cast(pItem)); + if( pFormula && pFormula->GetDefinedIn() && !pFormula->IsValid() ) + { + SwTableBox* pBox = pFormula->GetTableBox(); + if( pBox && pBox->GetSttNd() && + pBox->GetSttNd()->GetNodes().IsDocNodes() ) + { + const SwTableNode* pTableNd = pBox->GetSttNd()->FindTableNode(); + if( !pHt || &pTableNd->GetTable() == + static_cast(pHt)->m_pTable ) + { + double nValue; + if( !pCalc ) + pCalc.reset(new SwCalc( m_rDoc )); + + // get the values of all SetExpression fields that are valid + // until the table + SwFrame* pFrame = nullptr; + if( pTableNd->GetIndex() < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + // is in the special section, that's expensive! + Point aPt; // return the first frame of the layout - Tab.Headline!! + SwNodeIndex aCNdIdx( *pTableNd, +2 ); + SwContentNode* pCNd = aCNdIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = m_rDoc.GetNodes().GoNext( &aCNdIdx ); + + if (pCNd) + { + std::pair const tmp(aPt, true); + pFrame = pCNd->getLayoutFrame(pLayout, nullptr, &tmp); + if( pFrame ) + { + SwPosition aPos( *pCNd ); + if( GetBodyTextNode( m_rDoc, aPos, *pFrame ) ) + FieldsToCalc(*pCalc, SetGetExpField(aPos.nNode), + pLayout); + else + pFrame = nullptr; + } + } + } + if( !pFrame ) + { + // create index to determine the TextNode + SwNodeIndex aIdx( *pTableNd ); + FieldsToCalc(*pCalc, SetGetExpField(aIdx), pLayout); + } + + SwTableCalcPara aPara(*pCalc, pTableNd->GetTable(), pLayout); + pFormula->Calc( aPara, nValue ); + + if( aPara.IsStackOverflow() ) + { + bool const bResult = aPara.CalcWithStackOverflow(); + if (bResult) + { + pFormula->Calc( aPara, nValue ); + } + OSL_ENSURE(bResult, + "the chained formula could no be calculated"); + } + + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + SfxItemSet aTmp( m_rDoc.GetAttrPool(), + svl::Items{} ); + + if( pCalc->IsCalcError() ) + nValue = DBL_MAX; + aTmp.Put( SwTableBoxValue( nValue )); + if( SfxItemState::SET != pFormat->GetItemState( RES_BOXATR_FORMAT )) + aTmp.Put( SwTableBoxNumFormat( 0 )); + pFormat->SetFormatAttr( aTmp ); + + pCalc->SetCalcError( SwCalcError::NONE ); + } + } + } + } +} + +void DocumentFieldsManager::UpdateExpFields( SwTextField* pUpdateField, bool bUpdRefFields ) +{ + if( IsExpFieldsLocked() || m_rDoc.IsInReading() ) + return; + + bool bOldInUpdateFields = mpUpdateFields->IsInUpdateFields(); + mpUpdateFields->SetInUpdateFields( true ); + + mpUpdateFields->MakeFieldList( m_rDoc, true, GETFLD_ALL ); + mbNewFieldLst = false; + + if (mpUpdateFields->GetSortList()->empty()) + { + if( bUpdRefFields ) + UpdateRefFields(); + + mpUpdateFields->SetInUpdateFields( bOldInUpdateFields ); + mpUpdateFields->SetFieldsDirty( false ); + return ; + } + + SwRootFrame const* pLayout(nullptr); + SwRootFrame const* pLayoutRLHidden(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayoutRLHidden = pLay; + } + else + { + pLayout = pLay; + } + } + if (pLayout || !pLayoutRLHidden) // always calc *something*... + { + UpdateExpFieldsImpl(pUpdateField, pLayout); + } + if (pLayoutRLHidden) + { + UpdateExpFieldsImpl(pUpdateField, pLayoutRLHidden); + } + + // update reference fields + if( bUpdRefFields ) + UpdateRefFields(); + + mpUpdateFields->SetInUpdateFields( bOldInUpdateFields ); + mpUpdateFields->SetFieldsDirty( false ); +} + +void DocumentFieldsManager::UpdateExpFieldsImpl( + SwTextField * pUpdateField, SwRootFrame const*const pLayout) +{ + SwFieldIds nWhich; + + // Hash table for all string replacements is filled on-the-fly. + // Try to fabricate an uneven number. + const SwFieldTypes::size_type nHashSize {(( mpFieldTypes->size() / 7 ) + 1 ) * 7}; + const sal_uInt16 nStrFormatCnt = static_cast(nHashSize); + OSL_ENSURE( nStrFormatCnt == nHashSize, "Downcasting to sal_uInt16 lost information!" ); + SwHashTable aHashStrTable(nStrFormatCnt); + + { + const SwFieldType* pFieldType; + // process separately: + for( auto n = mpFieldTypes->size(); n; ) + { + pFieldType = (*mpFieldTypes)[ --n ].get(); + switch( pFieldType->Which() ) + { + case SwFieldIds::User: + { + // Entry present? + sal_uInt16 nPos; + const OUString& rNm = pFieldType->GetName(); + OUString sExpand(const_cast(static_cast(pFieldType))->Expand(nsSwGetSetExpType::GSE_STRING, 0, LANGUAGE_SYSTEM)); + SwHash* pFnd = aHashStrTable.Find( rNm, &nPos ); + if( pFnd ) + // modify entry in the hash table + static_cast(pFnd)->aSetStr = sExpand; + else + // insert the new entry + aHashStrTable[nPos].reset( new HashStr( rNm, sExpand, + aHashStrTable[nPos].release() ) ); + } + break; + default: break; + } + } + } + + // The array is filled with all fields; start calculation. + SwCalc aCalc( m_rDoc ); + +#if HAVE_FEATURE_DBCONNECTIVITY + OUString sDBNumNm( SwFieldType::GetTypeStr( SwFieldTypesEnum::DatabaseSetNumber ) ); + + // already set the current record number + SwDBManager* pMgr = m_rDoc.GetDBManager(); + pMgr->CloseAll( false ); + + SvtSysLocale aSysLocale; + const LocaleDataWrapper* pLclData = aSysLocale.GetLocaleDataPtr(); + const LanguageType nLang = pLclData->getLanguageTag().getLanguageType(); + bool bCanFill = pMgr->FillCalcWithMergeData( m_rDoc.GetNumberFormatter(), nLang, aCalc ); +#endif + + // Make sure we don't hide all content, which would lead to a crash. First, count how many visible sections we have. + int nShownSections = 0; + sal_uLong nContentStart = m_rDoc.GetNodes().GetEndOfContent().StartOfSectionIndex() + 1; + sal_uLong nContentEnd = m_rDoc.GetNodes().GetEndOfContent().GetIndex(); + SwSectionFormats& rSectFormats = m_rDoc.GetSections(); + for( SwSectionFormats::size_type n = 0; nEndOfSectionNode(), 1 ); + if ( n == 0 && pSectionNode->GetIndex() != nContentStart ) + nShownSections++; //document does not start with a section + if ( n == rSectFormats.size() - 1 ) + { + if ( aNextIdx.GetIndex() != nContentEnd ) + nShownSections++; //document does not end in a section + } + else if ( !aNextIdx.GetNode().IsSectionNode() ) + nShownSections++; //section is not immediately followed by another section + } + + // count only visible sections + if ( pSect && !pSect->CalcHiddenFlag()) + nShownSections++; + } + + IDocumentRedlineAccess const& rIDRA(m_rDoc.getIDocumentRedlineAccess()); + std::unordered_map SetExpOutlineNodeMap; + + for (std::unique_ptr const& it : *mpUpdateFields->GetSortList()) + { + SwSection* pSect = const_cast(it->GetSection()); + if( pSect ) + { + SwSbxValue aValue = aCalc.Calculate( + pSect->GetCondition() ); + if(!aValue.IsVoidValue()) + { + // Do we want to hide this one? + bool bHide = aValue.GetBool(); + if (bHide && !pSect->IsCondHidden()) + { + // This section will be hidden, but it wasn't before + if (nShownSections == 1) + { + // This would be the last section, so set its condition to false, and avoid hiding it. + pSect->SetCondition("0"); + bHide = false; + } + nShownSections--; + } + pSect->SetCondHidden( bHide ); + } + continue; + } + + SwTextField* pTextField = const_cast(it->GetTextField()); + if( !pTextField ) + { + OSL_ENSURE( false, "what's wrong now'" ); + continue; + } + + if (pLayout && pLayout->IsHideRedlines() + && IsFieldDeleted(rIDRA, *pLayout, *pTextField)) + { + continue; + } + + SwFormatField* pFormatField = const_cast(&pTextField->GetFormatField()); + const SwField* pField = pFormatField->GetField(); + + nWhich = pField->GetTyp()->Which(); + switch( nWhich ) + { + case SwFieldIds::HiddenText: + { + SwHiddenTextField* pHField = const_cast(static_cast(pField)); + SwSbxValue aValue = aCalc.Calculate( pHField->GetPar1() ); + bool bValue = !aValue.GetBool(); + if(!aValue.IsVoidValue()) + { + pHField->SetValue( bValue ); + // evaluate field + pHField->Evaluate(&m_rDoc); + } + } + break; + case SwFieldIds::HiddenPara: + { + SwHiddenParaField* pHPField = const_cast(static_cast(pField)); + SwSbxValue aValue = aCalc.Calculate( pHPField->GetPar1() ); + bool bValue = aValue.GetBool(); + if(!aValue.IsVoidValue()) + pHPField->SetHidden( bValue ); + } + break; + case SwFieldIds::DbSetNumber: +#if HAVE_FEATURE_DBCONNECTIVITY + { + const_cast(static_cast(pField))->Evaluate(&m_rDoc); + aCalc.VarChange( sDBNumNm, static_cast(pField)->GetSetNumber()); + pField->ExpandField(m_rDoc.IsClipBoard(), nullptr); + } +#endif + break; + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: +#if HAVE_FEATURE_DBCONNECTIVITY + { + UpdateDBNumFields( *const_cast(static_cast(pField)), aCalc ); + if( bCanFill ) + bCanFill = pMgr->FillCalcWithMergeData( m_rDoc.GetNumberFormatter(), nLang, aCalc ); + } +#endif + break; + case SwFieldIds::Database: + { +#if HAVE_FEATURE_DBCONNECTIVITY + // evaluate field + const_cast(static_cast(pField))->Evaluate(); + + SwDBData aTmpDBData(static_cast(pField)->GetDBData()); + + if( pMgr->IsDataSourceOpen(aTmpDBData.sDataSource, aTmpDBData.sCommand, false)) + aCalc.VarChange( sDBNumNm, pMgr->GetSelectedRecordId(aTmpDBData.sDataSource, aTmpDBData.sCommand, aTmpDBData.nCommandType)); + + const OUString& rName = pField->GetTyp()->GetName(); + + // Add entry to hash table + // Entry present? + sal_uInt16 nPos; + HashStr* pFnd = aHashStrTable.Find( rName, &nPos ); + OUString const value(pField->ExpandField(m_rDoc.IsClipBoard(), nullptr)); + if( pFnd ) + { + // Modify entry in the hash table + pFnd->aSetStr = value; + } + else + { + // insert new entry + aHashStrTable[nPos].reset( new HashStr( rName, + value, aHashStrTable[nPos].release()) ); + } +#endif + } + break; + case SwFieldIds::GetExp: + case SwFieldIds::SetExp: + { + if( nsSwGetSetExpType::GSE_STRING & pField->GetSubType() ) // replace String + { + if( SwFieldIds::GetExp == nWhich ) + { + SwGetExpField* pGField = const_cast(static_cast(pField)); + + if( (!pUpdateField || pUpdateField == pTextField ) + && pGField->IsInBodyText() ) + { + OUString aNew = LookString( aHashStrTable, pGField->GetFormula() ); + pGField->ChgExpStr( aNew, pLayout ); + } + } + else + { + SwSetExpField* pSField = const_cast(static_cast(pField)); + // is the "formula" a field? + OUString aNew = LookString( aHashStrTable, pSField->GetFormula() ); + + if( aNew.isEmpty() ) // nothing found then the formula is the new value + aNew = pSField->GetFormula(); + + // only update one field + if( !pUpdateField || pUpdateField == pTextField ) + pSField->ChgExpStr( aNew, pLayout ); + + // lookup the field's name + aNew = static_cast(pSField->GetTyp())->GetSetRefName(); + // Entry present? + sal_uInt16 nPos; + HashStr* pFnd = aHashStrTable.Find( aNew, &nPos ); + if( pFnd ) + // Modify entry in the hash table + pFnd->aSetStr = pSField->GetExpStr(pLayout); + else + { + // insert new entry + aHashStrTable[nPos].reset( new HashStr( aNew, + pSField->GetExpStr(pLayout), + aHashStrTable[nPos].release() ) ); + pFnd = aHashStrTable[nPos].get(); + } + + // Extension for calculation with Strings + SwSbxValue aValue; + aValue.PutString( pFnd->aSetStr ); + aCalc.VarChange( aNew, aValue ); + } + } + else // recalculate formula + { + if( SwFieldIds::GetExp == nWhich ) + { + SwGetExpField* pGField = const_cast(static_cast(pField)); + + if( (!pUpdateField || pUpdateField == pTextField ) + && pGField->IsInBodyText() ) + { + SwSbxValue aValue = aCalc.Calculate( + pGField->GetFormula()); + if(!aValue.IsVoidValue()) + pGField->SetValue(aValue.GetDouble(), pLayout); + } + } + else + { + SwSetExpField* pSField = const_cast(static_cast(pField)); + SwSetExpFieldType* pSFieldTyp = static_cast(pField->GetTyp()); + OUString aNew = pSFieldTyp->GetName(); + + SwNode* pSeqNd = nullptr; + + if( pSField->IsSequenceField() ) + { + const sal_uInt8 nLvl = pSFieldTyp->GetOutlineLvl(); + if( MAXLEVEL > nLvl ) + { + // test if the Number needs to be updated + pSeqNd = m_rDoc.GetNodes()[ it->GetNode() ]; + + const SwTextNode* pOutlNd = pSeqNd-> + FindOutlineNodeOfLevel(nLvl, pLayout); + auto const iter(SetExpOutlineNodeMap.find(pSFieldTyp)); + if (iter == SetExpOutlineNodeMap.end() + || iter->second != pOutlNd) + { + SetExpOutlineNodeMap[pSFieldTyp] = pOutlNd; + aCalc.VarChange( aNew, 0 ); + } + } + } + + aNew += "=" + pSField->GetFormula(); + + SwSbxValue aValue = aCalc.Calculate( aNew ); + if (!aCalc.IsCalcError()) + { + double nErg = aValue.GetDouble(); + // only update one field + if( !aValue.IsVoidValue() && (!pUpdateField || pUpdateField == pTextField) ) + { + pSField->SetValue(nErg, pLayout); + + if( pSeqNd ) + pSFieldTyp->SetChapter(*pSField, *pSeqNd, pLayout); + } + } + } + } + } + break; + default: break; + } // switch + + { + // avoid calling ReplaceText() for input fields, it is pointless + // here and moves the cursor if it's inside the field ... + SwTextInputField *const pInputField( + pUpdateField == pTextField // ... except once, when the dialog + ? nullptr // is used to change content via UpdateOneField() + : dynamic_cast(pTextField)); + if (pInputField) + { + bool const tmp = pInputField->LockNotifyContentChange(); + (void) tmp; + assert(tmp && "should not be locked here?"); + } + ::comphelper::ScopeGuard g([pInputField]() + { + if (pInputField) + { + pInputField->UnlockNotifyContentChange(); + } + }); + pFormatField->UpdateTextNode(nullptr, nullptr); // trigger formatting + } + + if (pUpdateField == pTextField) // if only this one is updated + { + if( SwFieldIds::GetExp == nWhich || // only GetField or + SwFieldIds::HiddenText == nWhich || // HiddenText? + SwFieldIds::HiddenPara == nWhich) // HiddenParaField? + break; // quit + pUpdateField = nullptr; // update all from here on + } + } + +#if HAVE_FEATURE_DBCONNECTIVITY + pMgr->CloseAll(false); +#endif +} + +/// Insert field type that was marked as deleted +void DocumentFieldsManager::UpdateUsrFields() +{ + SwCalc* pCalc = nullptr; + for( SwFieldTypes::size_type i = INIT_FLDTYPES; i < mpFieldTypes->size(); ++i ) + { + const SwFieldType* pFieldType = (*mpFieldTypes)[i].get(); + if( SwFieldIds::User == pFieldType->Which() ) + { + if( !pCalc ) + pCalc = new SwCalc( m_rDoc ); + const_cast(static_cast(pFieldType))->GetValue( *pCalc ); + } + } + + if( pCalc ) + { + delete pCalc; + m_rDoc.getIDocumentState().SetModified(); + } +} + +sal_Int32 DocumentFieldsManager::GetRecordsPerDocument() const +{ + sal_Int32 nRecords = 1; + + mpUpdateFields->MakeFieldList( m_rDoc, true, GETFLD_ALL ); + if (mpUpdateFields->GetSortList()->empty()) + return nRecords; + + for (std::unique_ptr const& it : *mpUpdateFields->GetSortList()) + { + const SwTextField *pTextField = it->GetTextField(); + if( !pTextField ) + continue; + + const SwFormatField &pFormatField = pTextField->GetFormatField(); + const SwField* pField = pFormatField.GetField(); + + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + nRecords++; + break; + default: + break; + } + } + + return nRecords; +} + +void DocumentFieldsManager::UpdatePageFields( SfxPoolItem* pMsgHint ) +{ + for( SwFieldTypes::size_type i = 0; i < INIT_FLDTYPES; ++i ) + { + SwFieldType* pFieldType = (*mpFieldTypes)[ i ].get(); + switch( pFieldType->Which() ) + { + case SwFieldIds::PageNumber: + case SwFieldIds::Chapter: + case SwFieldIds::GetExp: + case SwFieldIds::RefPageGet: + pFieldType->ModifyNotification( nullptr, pMsgHint ); + break; + case SwFieldIds::DocStat: + pFieldType->ModifyNotification( nullptr, nullptr ); + break; + default: break; + } + } + SetNewFieldLst(true); +} + +void DocumentFieldsManager::LockExpFields() +{ + ++mnLockExpField; +} + +void DocumentFieldsManager::UnlockExpFields() +{ + assert(mnLockExpField != 0); + if( mnLockExpField ) + --mnLockExpField; +} + +bool DocumentFieldsManager::IsExpFieldsLocked() const +{ + return 0 != mnLockExpField; +} + +SwDocUpdateField& DocumentFieldsManager::GetUpdateFields() const +{ + return *mpUpdateFields; +} + +bool DocumentFieldsManager::SetFieldsDirty( bool b, const SwNode* pChk, sal_uLong nLen ) +{ + // See if the supplied nodes actually contain fields. + // If they don't, the flag doesn't need to be changed. + bool bFieldsFnd = false; + if( b && pChk && !GetUpdateFields().IsFieldsDirty() && !m_rDoc.IsInDtor() + // ?? what's up with Undo, this is also wanted there! + /*&& &pChk->GetNodes() == &GetNodes()*/ ) + { + b = false; + if( !nLen ) + ++nLen; + sal_uLong nStt = pChk->GetIndex(); + const SwNodes& rNds = pChk->GetNodes(); + while( nLen-- ) + { + const SwTextNode* pTNd = rNds[ nStt++ ]->GetTextNode(); + if( pTNd ) + { + if( pTNd->GetAttrOutlineLevel() != 0 ) + // update chapter fields + b = true; + else if( pTNd->GetpSwpHints() && pTNd->GetSwpHints().Count() ) + { + const size_t nEnd = pTNd->GetSwpHints().Count(); + for( size_t n = 0 ; n < nEnd; ++n ) + { + const SwTextAttr* pAttr = pTNd->GetSwpHints().Get(n); + if ( pAttr->Which() == RES_TXTATR_FIELD + || pAttr->Which() == RES_TXTATR_INPUTFIELD) + { + b = true; + break; + } + } + } + + if( b ) + break; + } + } + bFieldsFnd = b; + } + GetUpdateFields().SetFieldsDirty( b ); + return bFieldsFnd; +} + +void DocumentFieldsManager::SetFixFields( const DateTime* pNewDateTime ) +{ + bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + sal_Int32 nDate; + sal_Int64 nTime; + if( pNewDateTime ) + { + nDate = pNewDateTime->GetDate(); + nTime = pNewDateTime->GetTime(); + } + else + { + DateTime aDateTime( DateTime::SYSTEM ); + nDate = aDateTime.GetDate(); + nTime = aDateTime.GetTime(); + } + + SwFieldIds const aTypes[] { + /*0*/ SwFieldIds::DocInfo, + /*1*/ SwFieldIds::Author, + /*2*/ SwFieldIds::ExtUser, + /*3*/ SwFieldIds::Filename, + /*4*/ SwFieldIds::DateTime }; // MUST be at the end! + + for(SwFieldIds aType : aTypes) + { + std::vector vFields; + GetSysFieldType(aType)->GatherFields(vFields); + for(auto pFormatField: vFields) + { + if (pFormatField->GetTextField()) + { + bool bChgd = false; + switch( aType ) + { + case SwFieldIds::DocInfo: + if( static_cast(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwDocInfoField* pDocInfField = static_cast(pFormatField->GetField()); + pDocInfField->SetExpansion( static_cast( + pDocInfField->GetTyp())->Expand( + pDocInfField->GetSubType(), + pDocInfField->GetFormat(), + pDocInfField->GetLanguage(), + pDocInfField->GetName() ) ); + } + break; + + case SwFieldIds::Author: + if( static_cast(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwAuthorField* pAuthorField = static_cast(pFormatField->GetField()); + pAuthorField->SetExpansion( SwAuthorFieldType::Expand( pAuthorField->GetFormat() ) ); + } + break; + + case SwFieldIds::ExtUser: + if( static_cast(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwExtUserField* pExtUserField = static_cast(pFormatField->GetField()); + pExtUserField->SetExpansion( SwExtUserFieldType::Expand(pExtUserField->GetSubType()) ); + } + break; + + case SwFieldIds::DateTime: + if( static_cast(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + static_cast(pFormatField->GetField())->SetDateTime( + DateTime(Date(nDate), tools::Time(nTime)) ); + } + break; + + case SwFieldIds::Filename: + if( static_cast(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwFileNameField* pFileNameField = + static_cast(pFormatField->GetField()); + pFileNameField->SetExpansion( static_cast( + pFileNameField->GetTyp())->Expand( + pFileNameField->GetFormat() ) ); + } + break; + default: break; + } + + // Trigger formatting + if( bChgd ) + pFormatField->UpdateTextNode(nullptr, nullptr); + } + } + } + + if( !bIsModified ) + m_rDoc.getIDocumentState().ResetModified(); +} + +void DocumentFieldsManager::FieldsToCalc(SwCalc& rCalc, + const SetGetExpField& rToThisField, SwRootFrame const*const pLayout) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_CALC ); + mbNewFieldLst = false; + +#if !HAVE_FEATURE_DBCONNECTIVITY + SwDBManager* pMgr = NULL; +#else + SwDBManager* pMgr = m_rDoc.GetDBManager(); + pMgr->CloseAll(false); +#endif + + if (!mpUpdateFields->GetSortList()->empty()) + { + SetGetExpFields::const_iterator const itLast = + mpUpdateFields->GetSortList()->upper_bound( + &rToThisField); + for (auto it = mpUpdateFields->GetSortList()->begin(); it != itLast; ++it) + { + lcl_CalcField(m_rDoc, rCalc, **it, pMgr, pLayout); + } + } +#if HAVE_FEATURE_DBCONNECTIVITY + pMgr->CloseAll(false); +#endif +} + +void DocumentFieldsManager::FieldsToCalc( SwCalc& rCalc, sal_uLong nLastNd, sal_uInt16 nLastCnt ) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_CALC ); + mbNewFieldLst = false; + +#if !HAVE_FEATURE_DBCONNECTIVITY + SwDBManager* pMgr = NULL; +#else + SwDBManager* pMgr = m_rDoc.GetDBManager(); + pMgr->CloseAll(false); +#endif + + SwRootFrame const* pLayout(nullptr); + SwRootFrame const* pLayoutRLHidden(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayoutRLHidden = pLay; + } + else + { + pLayout = pLay; + } + } + + // note this is not duplicate of the other FieldsToCalc because there is + // (currently) no SetGetExpField that compares only a position + for(auto it = mpUpdateFields->GetSortList()->begin(); + it != mpUpdateFields->GetSortList()->end() && + ( (*it)->GetNode() < nLastNd || + ( (*it)->GetNode() == nLastNd && (*it)->GetContent() <= nLastCnt ) + ); + ++it ) + { + if (pLayout || !pLayoutRLHidden) // always calc *something*... + { + lcl_CalcField( m_rDoc, rCalc, **it, pMgr, pLayout ); + } + if (pLayoutRLHidden) + { + lcl_CalcField( m_rDoc, rCalc, **it, pMgr, pLayoutRLHidden ); + } + } + +#if HAVE_FEATURE_DBCONNECTIVITY + pMgr->CloseAll(false); +#endif +} + +void DocumentFieldsManager::FieldsToExpand( SwHashTable & rHashTable, + const SetGetExpField& rToThisField, SwRootFrame const& rLayout) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_EXPAND ); + mbNewFieldLst = false; + + IDocumentRedlineAccess const& rIDRA(m_rDoc.getIDocumentRedlineAccess()); + + // Hash table for all string replacements is filled on-the-fly. + // Try to fabricate an uneven number. + sal_uInt16 nTableSize = ((mpUpdateFields->GetSortList()->size() / 7) + 1) * 7; + rHashTable.resize(nTableSize); + + SetGetExpFields::const_iterator const itLast = + mpUpdateFields->GetSortList()->upper_bound(&rToThisField); + + for (auto it = mpUpdateFields->GetSortList()->begin(); it != itLast; ++it) + { + const SwTextField* pTextField = (*it)->GetTextField(); + if( !pTextField ) + continue; + + if (rLayout.IsHideRedlines() + && IsFieldDeleted(rIDRA, rLayout, *pTextField)) + { + continue; + } + + const SwField* pField = pTextField->GetFormatField().GetField(); + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::SetExp: + if( nsSwGetSetExpType::GSE_STRING & pField->GetSubType() ) + { + // set the new value in the hash table + // is the formula a field? + SwSetExpField* pSField = const_cast(static_cast(pField)); + OUString aNew = LookString( rHashTable, pSField->GetFormula() ); + + if( aNew.isEmpty() ) // nothing found, then the formula is + aNew = pSField->GetFormula(); // the new value + + // #i3141# - update expression of field as in method + // for string/text fields + pSField->ChgExpStr(aNew, &rLayout); + + // look up the field's name + aNew = static_cast(pSField->GetTyp())->GetSetRefName(); + // Entry present? + sal_uInt16 nPos; + SwHash* pFnd = rHashTable.Find( aNew, &nPos ); + if( pFnd ) + // modify entry in the hash table + static_cast(pFnd)->aSetStr = pSField->GetExpStr(&rLayout); + else + // insert the new entry + rHashTable[nPos].reset( new HashStr( aNew, + pSField->GetExpStr(&rLayout), rHashTable[nPos].release())); + } + break; + case SwFieldIds::Database: + { + const OUString& rName = pField->GetTyp()->GetName(); + + // Insert entry in the hash table + // Entry present? + sal_uInt16 nPos; + HashStr* pFnd = rHashTable.Find( rName, &nPos ); + OUString const value(pField->ExpandField(m_rDoc.IsClipBoard(), nullptr)); + if( pFnd ) + { + // modify entry in the hash table + pFnd->aSetStr = value; + } + else + { + // insert the new entry + rHashTable[nPos].reset( new HashStr( rName, + value, rHashTable[nPos].release()) ); + } + } + break; + default: break; + } + } +} + + +bool DocumentFieldsManager::IsNewFieldLst() const +{ + return mbNewFieldLst; +} + +void DocumentFieldsManager::SetNewFieldLst(bool bFlag) +{ + mbNewFieldLst = bFlag; +} + +void DocumentFieldsManager::InsDelFieldInFieldLst( bool bIns, const SwTextField& rField ) +{ + if (!mbNewFieldLst && !m_rDoc.IsInDtor()) + mpUpdateFields->InsDelFieldInFieldLst( bIns, rField ); +} + +SwField * DocumentFieldsManager::GetFieldAtPos(const SwPosition & rPos) +{ + SwTextField * const pAttr = GetTextFieldAtPos(rPos); + + return pAttr ? const_cast( pAttr->GetFormatField().GetField() ) : nullptr; +} + +SwTextField * DocumentFieldsManager::GetTextFieldAtPos(const SwPosition & rPos) +{ + SwTextNode * const pNode = rPos.nNode.GetNode().GetTextNode(); + + return (pNode != nullptr) + ? pNode->GetFieldTextAttrAt( rPos.nContent.GetIndex(), true ) + : nullptr; +} + +/// @note For simplicity assume that all field types have updatable contents so +/// optimization currently only available when no fields exist. +bool DocumentFieldsManager::containsUpdatableFields() +{ + std::vector vFields; + for (auto const& pFieldType: *mpFieldTypes) + { + pFieldType->GatherFields(vFields); + if(vFields.size()>0) + return true; + } + return false; +} + +/// Remove all unreferenced field types of a document +void DocumentFieldsManager::GCFieldTypes() +{ + for( auto n = mpFieldTypes->size(); n > INIT_FLDTYPES; ) + if( !(*mpFieldTypes)[ --n ]->HasWriterListeners() ) + RemoveFieldType( n ); +} + +void DocumentFieldsManager::InitFieldTypes() // is being called by the CTOR +{ + // Field types + mpFieldTypes->emplace_back( new SwDateTimeFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwChapterFieldType ); + mpFieldTypes->emplace_back( new SwPageNumberFieldType ); + mpFieldTypes->emplace_back( new SwAuthorFieldType ); + mpFieldTypes->emplace_back( new SwFileNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwDBNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwGetExpFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwGetRefFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwHiddenTextFieldType ); + mpFieldTypes->emplace_back( new SwPostItFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwDocStatFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwDocInfoFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwInputFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwTableFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwMacroFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwHiddenParaFieldType ); + mpFieldTypes->emplace_back( new SwDBNextSetFieldType ); + mpFieldTypes->emplace_back( new SwDBNumSetFieldType ); + mpFieldTypes->emplace_back( new SwDBSetNumberFieldType ); + mpFieldTypes->emplace_back( new SwTemplNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwTemplNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwExtUserFieldType ); + mpFieldTypes->emplace_back( new SwRefPageSetFieldType ); + mpFieldTypes->emplace_back( new SwRefPageGetFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwJumpEditFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwScriptFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwCombinedCharFieldType ); + mpFieldTypes->emplace_back( new SwDropDownFieldType ); + + // Types have to be at the end! + // We expect this in the InsertFieldType! + // MIB 14.04.95: In Sw3StringPool::Setup (sw3imp.cxx) and + // lcl_sw3io_InSetExpField (sw3field.cxx) now also + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_ABB), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_TABLE), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_FRAME), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_DRAWING), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_FIGURE), nsSwGetSetExpType::GSE_SEQ) ); + + assert( mpFieldTypes->size() == INIT_FLDTYPES ); +} + +void DocumentFieldsManager::ClearFieldTypes() +{ + mpFieldTypes->erase( mpFieldTypes->begin() + INIT_FLDTYPES, mpFieldTypes->end() ); +} + +void DocumentFieldsManager::UpdateDBNumFields( SwDBNameInfField& rDBField, SwCalc& rCalc ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDBField; + (void) rCalc; +#else + SwDBManager* pMgr = m_rDoc.GetDBManager(); + + SwFieldIds nFieldType = rDBField.Which(); + + bool bPar1 = rCalc.Calculate( rDBField.GetPar1() ).GetBool(); + + if( SwFieldIds::DbNextSet == nFieldType ) + static_cast(rDBField).SetCondValid( bPar1 ); + else + static_cast(rDBField).SetCondValid( bPar1 ); + + if( !rDBField.GetRealDBData().sDataSource.isEmpty() ) + { + // Edit a certain database + if( SwFieldIds::DbNextSet == nFieldType ) + static_cast(rDBField).Evaluate(&m_rDoc); + else + static_cast(rDBField).Evaluate(&m_rDoc); + + SwDBData aTmpDBData( rDBField.GetDBData(&m_rDoc) ); + + if( pMgr->OpenDataSource( aTmpDBData.sDataSource, aTmpDBData.sCommand )) + rCalc.VarChange( lcl_GetDBVarName( m_rDoc, rDBField), + pMgr->GetSelectedRecordId(aTmpDBData.sDataSource, aTmpDBData.sCommand, aTmpDBData.nCommandType) ); + } + else + { + OSL_FAIL("TODO: what should happen with unnamed DBFields?"); + } +#endif +} + +DocumentFieldsManager::~DocumentFieldsManager() +{ + mpUpdateFields.reset(); + mpFieldTypes.reset(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentLayoutManager.cxx b/sw/source/core/doc/DocumentLayoutManager.cxx new file mode 100644 index 000000000..8d5cc79dc --- /dev/null +++ b/sw/source/core/doc/DocumentLayoutManager.cxx @@ -0,0 +1,517 @@ +/* -*- 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; + +namespace sw +{ + +DocumentLayoutManager::DocumentLayoutManager( SwDoc& i_rSwdoc ) : + m_rDoc( i_rSwdoc ), + mpCurrentView( nullptr ) +{ +} + +const SwViewShell *DocumentLayoutManager::GetCurrentViewShell() const +{ + return mpCurrentView; +} + +SwViewShell *DocumentLayoutManager::GetCurrentViewShell() +{ + return mpCurrentView; +} + +void DocumentLayoutManager::SetCurrentViewShell( SwViewShell* pNew ) +{ + mpCurrentView = pNew; +} + +// It must be able to communicate to a SwViewShell. This is going to be removed later. +const SwRootFrame *DocumentLayoutManager::GetCurrentLayout() const +{ + if(GetCurrentViewShell()) + return GetCurrentViewShell()->GetLayout(); + return nullptr; +} + +SwRootFrame *DocumentLayoutManager::GetCurrentLayout() +{ + if(GetCurrentViewShell()) + return GetCurrentViewShell()->GetLayout(); + return nullptr; +} + +bool DocumentLayoutManager::HasLayout() const +{ + // if there is a view, there is always a layout + return (mpCurrentView != nullptr); +} + +SwLayouter* DocumentLayoutManager::GetLayouter() +{ + return mpLayouter.get(); +} + +const SwLayouter* DocumentLayoutManager::GetLayouter() const +{ + return mpLayouter.get(); +} + +void DocumentLayoutManager::SetLayouter( SwLayouter* pNew ) +{ + mpLayouter.reset( pNew ); +} + +/** Create a new format whose settings fit to the Request by default. + + The format is put into the respective format array. + If there already is a fitting format, it is returned instead. */ +SwFrameFormat *DocumentLayoutManager::MakeLayoutFormat( RndStdIds eRequest, const SfxItemSet* pSet ) +{ + SwFrameFormat *pFormat = nullptr; + const bool bMod = m_rDoc.getIDocumentState().IsModified(); + bool bHeader = false; + + switch ( eRequest ) + { + case RndStdIds::HEADER: + case RndStdIds::HEADERL: + case RndStdIds::HEADERR: + { + bHeader = true; + [[fallthrough]]; + } + case RndStdIds::FOOTER: + { + pFormat = new SwFrameFormat( m_rDoc.GetAttrPool(), + (bHeader ? "Right header" : "Right footer"), + m_rDoc.GetDfltFrameFormat() ); + + SwNodeIndex aTmpIdx( m_rDoc.GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = + m_rDoc.GetNodes().MakeTextSection + ( aTmpIdx, + bHeader ? SwHeaderStartNode : SwFooterStartNode, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast( bHeader + ? ( eRequest == RndStdIds::HEADERL + ? RES_POOLCOLL_HEADERL + : eRequest == RndStdIds::HEADERR + ? RES_POOLCOLL_HEADERR + : RES_POOLCOLL_HEADER ) + : RES_POOLCOLL_FOOTER + ) ) ); + pFormat->SetFormatAttr( SwFormatContent( pSttNd )); + + if( pSet ) // Set a few more attributes + pFormat->SetFormatAttr( *pSet ); + + // Why set it back? Doc has changed, or not? + // In any case, wrong for the FlyFrames! + if ( !bMod ) + m_rDoc.getIDocumentState().ResetModified(); + } + break; + + case RndStdIds::DRAW_OBJECT: + { + pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() ); + if( pSet ) // Set a few more attributes + pFormat->SetFormatAttr( *pSet ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pFormat, 0, 0)); + } + } + break; + +#if OSL_DEBUG_LEVEL > 0 + case RndStdIds::FLY_AT_PAGE: + case RndStdIds::FLY_AT_CHAR: + case RndStdIds::FLY_AT_FLY: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AS_CHAR: + OSL_FAIL( "use new interface instead: SwDoc::MakeFlySection!" ); + break; +#endif + + default: + OSL_ENSURE( false, + "LayoutFormat was requested with an invalid Request." ); + + } + return pFormat; +} + +/// Deletes the denoted format and its content. +void DocumentLayoutManager::DelLayoutFormat( SwFrameFormat *pFormat ) +{ + // A chain of frames needs to be merged, if necessary, + // so that the Frame's contents are adjusted accordingly before we destroy the Frames. + const SwFormatChain &rChain = pFormat->GetChain(); + if ( rChain.GetPrev() ) + { + SwFormatChain aChain( rChain.GetPrev()->GetChain() ); + aChain.SetNext( rChain.GetNext() ); + m_rDoc.SetAttr( aChain, *rChain.GetPrev() ); + } + if ( rChain.GetNext() ) + { + SwFormatChain aChain( rChain.GetNext()->GetChain() ); + aChain.SetPrev( rChain.GetPrev() ); + m_rDoc.SetAttr( aChain, *rChain.GetNext() ); + } + + const SwNodeIndex* pCntIdx = nullptr; + // The draw format doesn't own its content, it just has a pointer to it. + if (pFormat->Which() != RES_DRAWFRMFMT) + pCntIdx = pFormat->GetContent().GetContentIdx(); + if (pCntIdx && !m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // Disconnect if it's an OLE object + SwOLENode* pOLENd = m_rDoc.GetNodes()[ pCntIdx->GetIndex()+1 ]->GetOLENode(); + if( pOLENd && pOLENd->GetOLEObj().IsOleRef() ) + { + try + { + pOLENd->GetOLEObj().GetOleRef()->changeState( embed::EmbedStates::LOADED ); + } + catch ( uno::Exception& ) + { + } + } + } + + // Destroy Frames + pFormat->DelFrames(); + + // Only FlyFrames are undoable at first + const sal_uInt16 nWh = pFormat->Which(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + (RES_FLYFRMFMT == nWh || RES_DRAWFRMFMT == nWh)) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique( pFormat )); + } + else + { + // #i32089# - delete at-frame anchored objects + if ( nWh == RES_FLYFRMFMT ) + { + // determine frame formats of at-frame anchored objects + const SwNodeIndex* pContentIdx = nullptr; + if (pFormat->Which() != RES_DRAWFRMFMT) + pContentIdx = pFormat->GetContent().GetContentIdx(); + if (pContentIdx) + { + const SwFrameFormats* pTable = pFormat->GetDoc()->GetSpzFrameFormats(); + if ( pTable ) + { + std::vector aToDeleteFrameFormats; + const sal_uLong nNodeIdxOfFlyFormat( pContentIdx->GetIndex() ); + + for ( size_t i = 0; i < pTable->size(); ++i ) + { + SwFrameFormat* pTmpFormat = (*pTable)[i]; + const SwFormatAnchor &rAnch = pTmpFormat->GetAnchor(); + if ( rAnch.GetAnchorId() == RndStdIds::FLY_AT_FLY && + rAnch.GetContentAnchor()->nNode.GetIndex() == nNodeIdxOfFlyFormat ) + { + aToDeleteFrameFormats.push_back( pTmpFormat ); + } + } + + // delete found frame formats + while ( !aToDeleteFrameFormats.empty() ) + { + SwFrameFormat* pTmpFormat = aToDeleteFrameFormats.back(); + pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pTmpFormat ); + + aToDeleteFrameFormats.pop_back(); + } + } + } + } + + // Delete content + if( pCntIdx ) + { + SwNode *pNode = &pCntIdx->GetNode(); + const_cast(pFormat->GetFormatAttr( RES_CNTNT )).SetNewContentIdx( nullptr ); + m_rDoc.getIDocumentContentOperations().DeleteSection( pNode ); + } + + // Delete the character for FlyFrames anchored as char (if necessary) + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) && rAnchor.GetContentAnchor()) + { + const SwPosition* pPos = rAnchor.GetContentAnchor(); + SwTextNode *pTextNd = pPos->nNode.GetNode().GetTextNode(); + + // attribute is still in text node, delete it + if ( pTextNd ) + { + SwTextFlyCnt* const pAttr = static_cast( + pTextNd->GetTextAttrForCharAt( pPos->nContent.GetIndex(), + RES_TXTATR_FLYCNT )); + if ( pAttr && (pAttr->GetFlyCnt().GetFrameFormat() == pFormat) ) + { + // don't delete, set pointer to 0 + const_cast(pAttr->GetFlyCnt()).SetFlyFormat(); + SwIndex aIdx( pPos->nContent ); + pTextNd->EraseText( aIdx, 1 ); + } + } + } + + m_rDoc.DelFrameFormat( pFormat ); + } + m_rDoc.getIDocumentState().SetModified(); +} + +/** Copies the stated format (pSrc) to pDest and returns pDest. + + If there's no pDest, it is created. + If the source format is located in another document, also copy correctly + in this case. + The Anchor attribute's position is always set to 0! */ +SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat( + const SwFrameFormat& rSource, + const SwFormatAnchor& rNewAnchor, + bool bSetTextFlyAtt, + bool bMakeFrames ) +{ + const bool bFly = RES_FLYFRMFMT == rSource.Which(); + const bool bDraw = RES_DRAWFRMFMT == rSource.Which(); + OSL_ENSURE( bFly || bDraw, "this method only works for fly or draw" ); + + SwDoc* pSrcDoc = const_cast(rSource.GetDoc()); + + // May we copy this object? + // We may, unless it's 1) it's a control (and therefore a draw) + // 2) anchored in a header/footer + // 3) anchored (to paragraph?) + bool bMayNotCopy = false; + if(bDraw) + { + const auto pCAnchor = rNewAnchor.GetContentAnchor(); + bool bCheckControlLayer = false; + rSource.CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer)); + bMayNotCopy = + bCheckControlLayer && + ((RndStdIds::FLY_AT_PARA == rNewAnchor.GetAnchorId()) || (RndStdIds::FLY_AT_FLY == rNewAnchor.GetAnchorId()) || (RndStdIds::FLY_AT_CHAR == rNewAnchor.GetAnchorId())) && + pCAnchor && m_rDoc.IsInHeaderFooter(pCAnchor->nNode); + } + + // just return if we can't copy this + if( bMayNotCopy ) + return nullptr; + + SwFrameFormat* pDest = m_rDoc.GetDfltFrameFormat(); + if( rSource.GetRegisteredIn() != pSrcDoc->GetDfltFrameFormat() ) + pDest = m_rDoc.CopyFrameFormat( *static_cast(rSource.GetRegisteredIn()) ); + if( bFly ) + { + // #i11176# + // To do a correct cloning concerning the ZOrder for all objects + // it is necessary to actually create a draw object for fly frames, too. + // These are then added to the DrawingLayer (which needs to exist). + // Together with correct sorting of all drawinglayer based objects + // before cloning ZOrder transfer works correctly then. + SwFlyFrameFormat *pFormat = m_rDoc.MakeFlyFrameFormat( rSource.GetName(), pDest ); + pDest = pFormat; + + SwXFrame::GetOrCreateSdrObject(*pFormat); + } + else + pDest = m_rDoc.MakeDrawFrameFormat( OUString(), pDest ); + + // Copy all other or new attributes + pDest->CopyAttrs( rSource ); + + // Do not copy chains + pDest->ResetFormatAttr( RES_CHAIN ); + + if( bFly ) + { + // Duplicate the content. + const SwNode& rCSttNd = rSource.GetContent().GetContentIdx()->GetNode(); + SwNodeRange aRg( rCSttNd, 1, *rCSttNd.EndOfSectionNode() ); + + SwNodeIndex aIdx( m_rDoc.GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aIdx, SwFlyStartNode ); + + // Set the Anchor/ContentIndex first. + // Within the copying part, we can access the values (DrawFormat in Headers and Footers) + aIdx = *pSttNd; + SwFormatContent aAttr( rSource.GetContent() ); + aAttr.SetNewContentIdx( &aIdx ); + pDest->SetFormatAttr( aAttr ); + pDest->SetFormatAttr( rNewAnchor ); + + if( !m_rDoc.IsCopyIsMove() || &m_rDoc != pSrcDoc ) + { + if( m_rDoc.IsInReading() || m_rDoc.IsInMailMerge() ) + pDest->SetName( OUString() ); + else + { + // Test first if the name is already taken, if so generate a new one. + SwNodeType nNdTyp = aRg.aStart.GetNode().GetNodeType(); + + OUString sOld( pDest->GetName() ); + pDest->SetName( OUString() ); + if( m_rDoc.FindFlyByName( sOld, nNdTyp ) ) // found one + switch( nNdTyp ) + { + case SwNodeType::Grf: sOld = m_rDoc.GetUniqueGrfName(); break; + case SwNodeType::Ole: sOld = m_rDoc.GetUniqueOLEName(); break; + default: sOld = m_rDoc.GetUniqueFrameName(); break; + } + + pDest->SetName( sOld ); + } + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique(pDest,0,0)); + } + + // Make sure that FlyFrames in FlyFrames are copied + aIdx = *pSttNd->EndOfSectionNode(); + + //fdo#36631 disable (scoped) any undo operations associated with the + //contact object itself. They should be managed by SwUndoInsLayFormat. + const ::sw::DrawUndoGuard drawUndoGuard(m_rDoc.GetIDocumentUndoRedo()); + + pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aIdx, nullptr, false, true, true); + } + else + { + OSL_ENSURE( RES_DRAWFRMFMT == rSource.Which(), "Neither Fly nor Draw." ); + // #i52780# - Note: moving object to visible layer not needed. + rSource.CallSwClientNotify(sw::DrawFormatLayoutCopyHint(static_cast(*pDest), m_rDoc)); + + if(pDest->GetAnchor() == rNewAnchor) + { + // Do *not* connect to layout, if a will not be called. + if(bMakeFrames) + pDest->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::MAKE_FRAMES)); + + } + else + pDest->SetFormatAttr( rNewAnchor ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique(pDest,0,0)); + } + } + + if (bSetTextFlyAtt && (RndStdIds::FLY_AS_CHAR == rNewAnchor.GetAnchorId())) + { + const SwPosition* pPos = rNewAnchor.GetContentAnchor(); + SwFormatFlyCnt aFormat( pDest ); + pPos->nNode.GetNode().GetTextNode()->InsertItem( + aFormat, pPos->nContent.GetIndex(), 0 ); + } + + if( bMakeFrames ) + pDest->MakeFrames(); + + // If the draw format has a TextBox, then copy its fly format as well. + if (SwFrameFormat* pSourceTextBox = SwTextBoxHelper::getOtherTextBoxFormat(&rSource, RES_DRAWFRMFMT)) + { + SwFormatAnchor boxAnchor(rNewAnchor); + if (RndStdIds::FLY_AS_CHAR == boxAnchor.GetAnchorId()) + { + // AS_CHAR *must not* be set on textbox fly-frame + boxAnchor.SetType(RndStdIds::FLY_AT_CHAR); + } + // presumably these anchors are supported though not sure + assert(RndStdIds::FLY_AT_CHAR == boxAnchor.GetAnchorId() || RndStdIds::FLY_AT_PARA == boxAnchor.GetAnchorId()); + SwFrameFormat* pDestTextBox = CopyLayoutFormat(*pSourceTextBox, + boxAnchor, bSetTextFlyAtt, bMakeFrames); + SwAttrSet aSet(pDest->GetAttrSet()); + SwFormatContent aContent(pDestTextBox->GetContent().GetContentIdx()->GetNode().GetStartNode()); + aSet.Put(aContent); + pDest->SetFormatAttr(aSet); + + // Link FLY and DRAW formats, so it becomes a text box + pDest->SetOtherTextBoxFormat(pDestTextBox); + pDestTextBox->SetOtherTextBoxFormat(pDest); + } + + if (pDest->GetName().isEmpty()) + { + // Format name should have unique name. Let's use object name as a fallback + SdrObject *pObj = pDest->FindSdrObject(); + if (pObj) + pDest->SetName(pObj->GetName()); + } + + return pDest; +} + +//Load document from fdo#42534 under valgrind, drag the scrollbar down so full +//document layout is triggered. Close document before layout has completed, and +//SwAnchoredObject objects deleted by the deletion of layout remain referenced +//by the SwLayouter +void DocumentLayoutManager::ClearSwLayouterEntries() +{ + SwLayouter::ClearMovedFwdFrames( m_rDoc ); + SwLayouter::ClearObjsTmpConsiderWrapInfluence( m_rDoc ); + // #i65250# + SwLayouter::ClearMoveBwdLayoutInfo( m_rDoc ); +} + +DocumentLayoutManager::~DocumentLayoutManager() +{ +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/sw/source/core/doc/DocumentLinksAdministrationManager.cxx b/sw/source/core/doc/DocumentLinksAdministrationManager.cxx new file mode 100644 index 000000000..c5ca5b11e --- /dev/null +++ b/sw/source/core/doc/DocumentLinksAdministrationManager.cxx @@ -0,0 +1,583 @@ +/* -*- 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 namespace ::com::sun::star; + +//Helper functions for this file +namespace +{ + struct FindItem + { + const OUString m_Item; + SwTableNode* pTableNd; + SwSectionNode* pSectNd; + + explicit FindItem(const OUString& rS) + : m_Item(rS), pTableNd(nullptr), pSectNd(nullptr) + {} + }; + + ::sfx2::SvBaseLink* lcl_FindNextRemovableLink( const ::sfx2::SvBaseLinks& rLinks ) + { + for (const auto& rLinkIter : rLinks) + { + ::sfx2::SvBaseLink& rLnk = *rLinkIter; + if ((sfx2::SvBaseLinkObjectType::ClientGraphic == rLnk.GetObjType() || sfx2::SvBaseLinkObjectType::ClientFile == rLnk.GetObjType()) + && dynamic_cast(&rLnk) != nullptr) + { + tools::SvRef xLink(&rLnk); + + OUString sFName; + sfx2::LinkManager::GetDisplayNames( xLink.get(), nullptr, &sFName ); + + INetURLObject aURL( sFName ); + if( INetProtocol::File == aURL.GetProtocol() || + INetProtocol::Cid == aURL.GetProtocol() ) + return &rLnk; + } + } + return nullptr; + } + + + ::sw::mark::DdeBookmark* lcl_FindDdeBookmark( const IDocumentMarkAccess& rMarkAccess, const OUString& rName, const bool bCaseSensitive ) + { + //Iterating over all bookmarks, checking DdeBookmarks + const OUString sNameLc = bCaseSensitive ? rName : GetAppCharClass().lowercase(rName); + for(IDocumentMarkAccess::const_iterator_t ppMark = rMarkAccess.getAllMarksBegin(); + ppMark != rMarkAccess.getAllMarksEnd(); + ++ppMark) + { + if (::sw::mark::DdeBookmark* const pBkmk = dynamic_cast< ::sw::mark::DdeBookmark*>(*ppMark)) + { + if ( + (bCaseSensitive && (pBkmk->GetName() == sNameLc)) || + (!bCaseSensitive && GetAppCharClass().lowercase(pBkmk->GetName()) == sNameLc) + ) + { + return pBkmk; + } + } + } + return nullptr; + } + + + bool lcl_FindSection( const SwSectionFormat* pSectFormat, FindItem * const pItem, bool bCaseSensitive ) + { + SwSection* pSect = pSectFormat->GetSection(); + if( pSect ) + { + OUString sNm( bCaseSensitive + ? pSect->GetSectionName() + : GetAppCharClass().lowercase( pSect->GetSectionName() )); + OUString sCompare( bCaseSensitive + ? pItem->m_Item + : GetAppCharClass().lowercase( pItem->m_Item ) ); + if( sNm == sCompare ) + { + // found, so get the data + const SwNodeIndex* pIdx = pSectFormat->GetContent().GetContentIdx(); + if( pIdx && &pSectFormat->GetDoc()->GetNodes() == &pIdx->GetNodes() ) + { + // a table in the normal NodesArr + pItem->pSectNd = pIdx->GetNode().GetSectionNode(); + return false; + } + // If the name is already correct, but not the rest then we don't have them. + // The names are always unique. + } + } + return true; + } + + bool lcl_FindTable( const SwFrameFormat* pTableFormat, FindItem * const pItem ) + { + OUString sNm( GetAppCharClass().lowercase( pTableFormat->GetName() )); + if ( sNm == pItem->m_Item ) + { + SwTable* pTmpTable = SwTable::FindTable( pTableFormat ); + if( pTmpTable ) + { + SwTableBox* pFBox = pTmpTable->GetTabSortBoxes()[0]; + if( pFBox && pFBox->GetSttNd() && + &pTableFormat->GetDoc()->GetNodes() == &pFBox->GetSttNd()->GetNodes() ) + { + // a table in the normal NodesArr + pItem->pTableNd = const_cast( + pFBox->GetSttNd()->FindTableNode()); + return false; + } + } + // If the name is already correct, but not the rest then we don't have them. + // The names are always unique. + } + return true; + } + +} + + +namespace sw +{ + +DocumentLinksAdministrationManager::DocumentLinksAdministrationManager( SwDoc& i_rSwdoc ) + : mbVisibleLinks(true) + , mbLinksUpdated( false ) //#i38810# + , m_pLinkMgr( new sfx2::LinkManager(nullptr) ) + , m_rDoc( i_rSwdoc ) +{ +} + +bool DocumentLinksAdministrationManager::IsVisibleLinks() const +{ + return mbVisibleLinks; +} + +void DocumentLinksAdministrationManager::SetVisibleLinks(bool bFlag) +{ + mbVisibleLinks = bFlag; +} + +sfx2::LinkManager& DocumentLinksAdministrationManager::GetLinkManager() +{ + return *m_pLinkMgr; +} + +const sfx2::LinkManager& DocumentLinksAdministrationManager::GetLinkManager() const +{ + return *m_pLinkMgr; +} + +// #i42634# Moved common code of SwReader::Read() and SwDocShell::UpdateLinks() +// to new SwDoc::UpdateLinks(): +void DocumentLinksAdministrationManager::UpdateLinks() +{ + if (!m_rDoc.GetDocShell()) + return; + SfxObjectCreateMode eMode = m_rDoc.GetDocShell()->GetCreateMode(); + if (eMode == SfxObjectCreateMode::INTERNAL) + return; + if (eMode == SfxObjectCreateMode::ORGANIZER) + return; + if (m_rDoc.GetDocShell()->IsPreview()) + return; + if (GetLinkManager().GetLinks().empty()) + return; + sal_uInt16 nLinkMode = m_rDoc.GetDocumentSettingManager().getLinkUpdateMode(true); + sal_uInt16 nUpdateDocMode = m_rDoc.GetDocShell()->GetUpdateDocMode(); + if (nLinkMode == NEVER && nUpdateDocMode != document::UpdateDocMode::FULL_UPDATE) + return; + + bool bAskUpdate = nLinkMode == MANUAL; + bool bUpdate = true; + switch(nUpdateDocMode) + { + case document::UpdateDocMode::NO_UPDATE: bUpdate = false;break; + case document::UpdateDocMode::QUIET_UPDATE:bAskUpdate = false; break; + case document::UpdateDocMode::FULL_UPDATE: bAskUpdate = true; break; + } + if (nLinkMode == AUTOMATIC && !bAskUpdate) + { + SfxMedium * medium = m_rDoc.GetDocShell()->GetMedium(); + if (!SvtSecurityOptions().isTrustedLocationUriForUpdatingLinks( + medium == nullptr ? OUString() : medium->GetName())) + { + bAskUpdate = true; + } + } + comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = m_rDoc.GetDocShell()->getEmbeddedObjectContainer(); + if (bUpdate) + { + rEmbeddedObjectContainer.setUserAllowsLinkUpdate(true); + + weld::Window* pDlgParent = GetFrameWeld(m_rDoc.GetDocShell()); + GetLinkManager().UpdateAllLinks(bAskUpdate, false, pDlgParent); + } + else + { + rEmbeddedObjectContainer.setUserAllowsLinkUpdate(false); + } +} + +bool DocumentLinksAdministrationManager::GetData( const OUString& rItem, const OUString& rMimeType, + uno::Any & rValue ) const +{ + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk) + return SwServerObject(*pBkmk).GetData(rValue, rMimeType); + + // Do we already have the Item? + OUString sItem( bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem)); + FindItem aPara( sItem ); + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + if( aPara.pSectNd ) + { + // found, so get the data + return SwServerObject( *aPara.pSectNd ).GetData( rValue, rMimeType ); + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + FindItem aPara( GetAppCharClass().lowercase( rItem )); + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } + if( aPara.pTableNd ) + { + return SwServerObject( *aPara.pTableNd ).GetData( rValue, rMimeType ); + } + + return false; +} + +void DocumentLinksAdministrationManager::SetData( const OUString& rItem ) +{ + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk) + { + return; + } + + // Do we already have the Item? + OUString sItem( bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem)); + FindItem aPara( sItem ); + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + if( aPara.pSectNd ) + { + // found, so get the data + return; + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + OUString sItem(GetAppCharClass().lowercase(rItem)); + FindItem aPara( sItem ); + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } +} + +::sfx2::SvLinkSource* DocumentLinksAdministrationManager::CreateLinkSource(const OUString& rItem) +{ + SwServerObject* pObj = nullptr; + + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + // bookmarks + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk && pBkmk->IsExpanded()) + { + pObj = pBkmk->GetRefObject(); + if( !pObj ) + { + // mark found, but no link yet -> create hotlink + pObj = new SwServerObject(*pBkmk); + pBkmk->SetRefObject(pObj); + GetLinkManager().InsertServer(pObj); + } + } + if(pObj) + return pObj; + + FindItem aPara(bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem)); + // sections + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + + if(aPara.pSectNd) + { + pObj = aPara.pSectNd->GetSection().GetObject(); + if( !pObj ) + { + // section found, but no link yet -> create hotlink + pObj = new SwServerObject( *aPara.pSectNd ); + aPara.pSectNd->GetSection().SetRefObject( pObj ); + GetLinkManager().InsertServer(pObj); + } + } + if(pObj) + return pObj; + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + FindItem aPara( GetAppCharClass().lowercase(rItem) ); + // tables + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } + if(aPara.pTableNd) + { + pObj = aPara.pTableNd->GetTable().GetObject(); + if( !pObj ) + { + // table found, but no link yet -> create hotlink + pObj = new SwServerObject(*aPara.pTableNd); + aPara.pTableNd->GetTable().SetRefObject(pObj); + GetLinkManager().InsertServer(pObj); + } + } + return pObj; +} + +/// embedded all local links (Areas/Graphics) +bool DocumentLinksAdministrationManager::EmbedAllLinks() +{ + bool bRet = false; + sfx2::LinkManager& rLnkMgr = GetLinkManager(); + const ::sfx2::SvBaseLinks& rLinks = rLnkMgr.GetLinks(); + if( !rLinks.empty() ) + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + + ::sfx2::SvBaseLink* pLnk = nullptr; + while( nullptr != (pLnk = lcl_FindNextRemovableLink( rLinks ) ) ) + { + tools::SvRef xLink = pLnk; + // Tell the link that it's being destroyed! + xLink->Closed(); + + // if one forgot to remove itself + if( xLink.is() ) + rLnkMgr.Remove( xLink.get() ); + + bRet = true; + } + + m_rDoc.GetIDocumentUndoRedo().DelAllUndoObj(); + m_rDoc.getIDocumentState().SetModified(); + } + return bRet; +} + +void DocumentLinksAdministrationManager::SetLinksUpdated(const bool bNewLinksUpdated) +{ + mbLinksUpdated = bNewLinksUpdated; +} + +bool DocumentLinksAdministrationManager::LinksUpdated() const +{ + return mbLinksUpdated; +} + +DocumentLinksAdministrationManager::~DocumentLinksAdministrationManager() +{ +} + +bool DocumentLinksAdministrationManager::SelectServerObj( const OUString& rStr, SwPaM*& rpPam, std::unique_ptr& rpRange ) const +{ + // Do we actually have the Item? + rpPam = nullptr; + rpRange = nullptr; + + OUString sItem( INetURLObject::decode( rStr, + INetURLObject::DecodeMechanism::WithCharset )); + + sal_Int32 nPos = sItem.indexOf( cMarkSeparator ); + + const CharClass& rCC = GetAppCharClass(); + + // Extension for sections: not only link bookmarks/sections + // but also frames (text!), tables, outlines: + if( -1 != nPos ) + { + bool bContinue = false; + OUString sName( sItem.copy( 0, nPos ) ); + OUString sCmp( sItem.copy( nPos + 1 )); + sItem = rCC.lowercase( sItem ); + + FindItem aPara( sName ); + + if( sCmp == "table" ) + { + sName = rCC.lowercase( sName ); + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } + if( aPara.pTableNd ) + { + rpRange.reset(new SwNodeRange( *aPara.pTableNd, 0, + *aPara.pTableNd->EndOfSectionNode(), 1 )); + return true; + } + } + else if( sCmp == "frame" ) + { + SwNodeIndex* pIdx; + SwNode* pNd; + const SwFlyFrameFormat* pFlyFormat = m_rDoc.FindFlyByName( sName ); + if( pFlyFormat ) + { + pIdx = const_cast(pFlyFormat->GetContent().GetContentIdx()); + if( pIdx ) + { + pNd = &pIdx->GetNode(); + if( !pNd->IsNoTextNode() ) + { + rpRange.reset(new SwNodeRange( *pNd, 1, *pNd->EndOfSectionNode() )); + return true; + } + } + } + } + else if( sCmp == "region" ) + { + sItem = sName; // Is being dealt with further down! + bContinue = true; + } + else if( sCmp == "outline" ) + { + SwPosition aPos( SwNodeIndex( m_rDoc.GetNodes() )); + if (m_rDoc.GotoOutline(aPos, sName, nullptr)) + { + SwNode* pNd = &aPos.nNode.GetNode(); + const int nLvl = pNd->GetTextNode()->GetAttrOutlineLevel()-1; + + const SwOutlineNodes& rOutlNds = m_rDoc.GetNodes().GetOutLineNds(); + SwOutlineNodes::size_type nTmpPos; + (void)rOutlNds.Seek_Entry( pNd, &nTmpPos ); + rpRange.reset(new SwNodeRange( aPos.nNode, 0, aPos.nNode )); + + // look for the section's end, now + for( ++nTmpPos; + nTmpPos < rOutlNds.size() && + nLvl < rOutlNds[ nTmpPos ]->GetTextNode()-> + GetAttrOutlineLevel()-1; + ++nTmpPos ) + ; // there is no block + + if( nTmpPos < rOutlNds.size() ) + rpRange->aEnd = *rOutlNds[ nTmpPos ]; + else + rpRange->aEnd = m_rDoc.GetNodes().GetEndOfContent(); + return true; + } + } + + if( !bContinue ) + return false; + } + + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), sItem, bCaseSensitive); + if(pBkmk) + { + if(pBkmk->IsExpanded()) + rpPam = new SwPaM( + pBkmk->GetMarkPos(), + pBkmk->GetOtherMarkPos()); + return static_cast(rpPam); + } + + FindItem aPara( bCaseSensitive ? sItem : rCC.lowercase( sItem ) ); + + if( !m_rDoc.GetSections().empty() ) + { + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + if( aPara.pSectNd ) + { + rpRange.reset(new SwNodeRange( *aPara.pSectNd, 1, + *aPara.pSectNd->EndOfSectionNode() )); + return true; + + } + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + return false; +} + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentListItemsManager.cxx b/sw/source/core/doc/DocumentListItemsManager.cxx new file mode 100644 index 000000000..2a8f0691d --- /dev/null +++ b/sw/source/core/doc/DocumentListItemsManager.cxx @@ -0,0 +1,105 @@ +/* -*- 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 + +namespace sw +{ + +DocumentListItemsManager::DocumentListItemsManager() : mpListItemsList( new tImplSortedNodeNumList ) // #i83479# +{ +} + +bool DocumentListItemsManager::lessThanNodeNum::operator()( const SwNodeNum* pNodeNumOne, + const SwNodeNum* pNodeNumTwo ) const +{ + return pNodeNumOne->LessThan( *pNodeNumTwo ); +} + +void DocumentListItemsManager::addListItem( const SwNodeNum& rNodeNum ) +{ + if ( mpListItemsList == nullptr ) + { + return; + } + + const bool bAlreadyInserted( + mpListItemsList->insert( &rNodeNum ).second ); + OSL_ENSURE( bAlreadyInserted, + " - instance already registered as numbered item!" ); +} + +void DocumentListItemsManager::removeListItem( const SwNodeNum& rNodeNum ) +{ + if ( mpListItemsList == nullptr ) + { + return; + } + + const tImplSortedNodeNumList::size_type nDeleted = mpListItemsList->erase( &rNodeNum ); + if ( nDeleted > 1 ) + { + OSL_FAIL( " - was registered more than once as numbered item!" ); + } +} + +OUString DocumentListItemsManager::getListItemText(const SwNodeNum& rNodeNum, + SwRootFrame const& rLayout) const +{ + SwTextNode const*const pNode(rNodeNum.GetTextNode()); + assert(pNode); + return sw::GetExpandTextMerged(&rLayout, *pNode, true, true, ExpandMode::ExpandFootnote); +} + +bool DocumentListItemsManager::isNumberedInLayout( + SwNodeNum const& rNodeNum, // note: this is the non-hidden Num ... + SwRootFrame const& rLayout) const +{ + return sw::IsParaPropsNode(rLayout, *rNodeNum.GetTextNode()); +} + +void DocumentListItemsManager::getNumItems( tSortedNodeNumList& orNodeNumList ) const +{ + orNodeNumList.clear(); + orNodeNumList.reserve( mpListItemsList->size() ); + + for ( const SwNodeNum* pNodeNum : *mpListItemsList ) + { + if ( pNodeNum->IsCounted() && + pNodeNum->GetTextNode() && pNodeNum->GetTextNode()->HasNumber() ) + { + orNodeNumList.push_back( pNodeNum ); + } + } +} + +DocumentListItemsManager::~DocumentListItemsManager() +{ +} + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentListsManager.cxx b/sw/source/core/doc/DocumentListsManager.cxx new file mode 100644 index 000000000..d74e924fd --- /dev/null +++ b/sw/source/core/doc/DocumentListsManager.cxx @@ -0,0 +1,214 @@ +/* -*- 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 + + +namespace sw +{ + +DocumentListsManager::DocumentListsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), maLists(), maListStyleLists() +{ +} + +SwList* DocumentListsManager::createList( const OUString& rListId, + const OUString& sDefaultListStyleName ) +{ + OUString sListId = rListId; + if ( sListId.isEmpty() ) + { + sListId = CreateUniqueListId(); + } + + if ( getListByName( sListId ) ) + { + OSL_FAIL( " - provided list id already used. Serious defect." ); + return nullptr; + } + + SwNumRule* pDefaultNumRuleForNewList = m_rDoc.FindNumRulePtr( sDefaultListStyleName ); + if ( !pDefaultNumRuleForNewList ) + { + OSL_FAIL( " - for provided default list style name no list style is found. Serious defect." ); + return nullptr; + } + + SwList* pNewList = new SwList( sListId, *pDefaultNumRuleForNewList, m_rDoc.GetNodes() ); + maLists[sListId].reset(pNewList); + + return pNewList; +} + +SwList* DocumentListsManager::getListByName( const OUString& sListId ) const +{ + SwList* pList = nullptr; + + auto aListIter = maLists.find( sListId ); + if ( aListIter != maLists.end() ) + { + pList = (*aListIter).second.get(); + } + + return pList; +} + +void DocumentListsManager::createListForListStyle( const OUString& sListStyleName ) +{ + if ( sListStyleName.isEmpty() ) + { + OSL_FAIL( " - no list style name provided. Serious defect." ); + return; + } + + if ( getListForListStyle( sListStyleName ) ) + { + OSL_FAIL( " - a list for the provided list style name already exists. Serious defect." ); + return; + } + + SwNumRule* pNumRule = m_rDoc.FindNumRulePtr( sListStyleName ); + if ( !pNumRule ) + { + OSL_FAIL( " - for provided list style name no list style is found. Serious defect." ); + return; + } + + OUString sListId( pNumRule->GetDefaultListId() ); // can be empty String + if ( getListByName( sListId ) ) + { + sListId.clear(); + } + SwList* pNewList = createList( sListId, sListStyleName ); + maListStyleLists[sListStyleName] = pNewList; + pNumRule->SetDefaultListId( pNewList->GetListId() ); +} + +SwList* DocumentListsManager::getListForListStyle( const OUString& sListStyleName ) const +{ + SwList* pList = nullptr; + + std::unordered_map< OUString, SwList* >::const_iterator + aListIter = maListStyleLists.find( sListStyleName ); + if ( aListIter != maListStyleLists.end() ) + { + pList = (*aListIter).second; + } + + return pList; +} + +void DocumentListsManager::deleteListForListStyle( const OUString& sListStyleName ) +{ + OUString sListId; + { + SwList* pList = getListForListStyle( sListStyleName ); + OSL_ENSURE( pList, + " - misusage of method: no list found for given list style name" ); + if ( pList ) + { + sListId = pList->GetListId(); + } + } + if ( !sListId.isEmpty() ) + { + maListStyleLists.erase( sListStyleName ); + maLists.erase( sListId ); + } +} + +void DocumentListsManager::deleteListsByDefaultListStyle( const OUString& rListStyleName ) +{ + auto aListIter = maLists.begin(); + while ( aListIter != maLists.end() ) + { + if ( (*aListIter).second->GetDefaultListStyleName() == rListStyleName ) + { + aListIter = maLists.erase(aListIter); + } + else + ++aListIter; + } +} + +void DocumentListsManager::trackChangeOfListStyleName( const OUString& sListStyleName, + const OUString& sNewListStyleName ) +{ + SwList* pList = getListForListStyle( sListStyleName ); + OSL_ENSURE( pList, + " - misusage of method: no list found for given list style name" ); + + if ( pList != nullptr ) + { + maListStyleLists.erase( sListStyleName ); + maListStyleLists[sNewListStyleName] = pList; + } + for (auto & it : maLists) // tdf#91131 update these references too + { + if (it.second->GetDefaultListStyleName() == sListStyleName) + { + it.second->SetDefaultListStyleName(sNewListStyleName); + } + } +} + + +DocumentListsManager::~DocumentListsManager() +{ +} + + +OUString DocumentListsManager::MakeListIdUnique( const OUString& aSuggestedUniqueListId ) +{ + long nHitCount = 0; + OUString aTmpStr = aSuggestedUniqueListId; + while ( getListByName( aTmpStr ) ) + { + ++nHitCount; + aTmpStr = aSuggestedUniqueListId + OUString::number( nHitCount ); + } + + return aTmpStr; +} + +OUString DocumentListsManager::CreateUniqueListId() +{ + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + if (bHack) + { + static sal_Int64 nIdCounter = SAL_CONST_INT64(7000000000); + return MakeListIdUnique( OUString( "list" + OUString::number(nIdCounter++) ) ); + } + else + { + // #i92478# + unsigned int const n(comphelper::rng::uniform_uint_distribution(0, + std::numeric_limits::max())); + OUString const aNewListId = "list" + OUString::number(n); + return MakeListIdUnique( aNewListId ); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentOutlineNodesManager.cxx b/sw/source/core/doc/DocumentOutlineNodesManager.cxx new file mode 100644 index 000000000..25f381476 --- /dev/null +++ b/sw/source/core/doc/DocumentOutlineNodesManager.cxx @@ -0,0 +1,131 @@ +/* -*- 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 + +namespace sw +{ + +DocumentOutlineNodesManager::DocumentOutlineNodesManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +IDocumentOutlineNodes::tSortedOutlineNodeList::size_type DocumentOutlineNodesManager::getOutlineNodesCount() const +{ + return m_rDoc.GetNodes().GetOutLineNds().size(); +} + +int DocumentOutlineNodesManager::getOutlineLevel( const tSortedOutlineNodeList::size_type nIdx ) const +{ + return m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]-> + GetTextNode()->GetAttrOutlineLevel()-1; +} + +OUString GetExpandTextMerged(SwRootFrame const*const pLayout, + SwTextNode const& rNode, bool const bWithNumber, + bool const bWithSpacesForLevel, ExpandMode const i_mode) +{ + if (pLayout && pLayout->IsHideRedlines()) + { + SwTextFrame const*const pFrame(static_cast(rNode.getLayoutFrame(pLayout))); + if (pFrame) + { + sw::MergedPara const*const pMerged = pFrame->GetMergedPara(); + if (pMerged) + { + if (&rNode != pMerged->pParaPropsNode) + { + return OUString(); + } + else + { + ExpandMode const mode(ExpandMode::HideDeletions | i_mode); + OUStringBuffer ret(rNode.GetExpandText(pLayout, 0, -1, + bWithNumber, bWithNumber, bWithSpacesForLevel, mode)); + for (sal_uLong i = rNode.GetIndex() + 1; + i <= pMerged->pLastNode->GetIndex(); ++i) + { + SwNode *const pTmp(rNode.GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + ret.append(pTmp->GetTextNode()->GetExpandText( + pLayout, 0, -1, false, false, false, mode)); + } + } + return ret.makeStringAndClear(); + } + } + } + } + return rNode.GetExpandText(pLayout, 0, -1, bWithNumber, + bWithNumber, bWithSpacesForLevel, i_mode); +} + +OUString DocumentOutlineNodesManager::getOutlineText( + const tSortedOutlineNodeList::size_type nIdx, + SwRootFrame const*const pLayout, + const bool bWithNumber, + const bool bWithSpacesForLevel, + const bool bWithFootnote ) const +{ + SwTextNode const*const pNode(m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]->GetTextNode()); + return GetExpandTextMerged(pLayout, *pNode, + bWithNumber, bWithSpacesForLevel, + (bWithFootnote ? ExpandMode::ExpandFootnote : ExpandMode(0))); +} + +SwTextNode* DocumentOutlineNodesManager::getOutlineNode( const tSortedOutlineNodeList::size_type nIdx ) const +{ + return m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]->GetTextNode(); +} + +bool DocumentOutlineNodesManager::isOutlineInLayout( + const tSortedOutlineNodeList::size_type nIdx, + SwRootFrame const& rLayout) const +{ + auto const pNode(m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]->GetTextNode()); + return sw::IsParaPropsNode(rLayout, *pNode); +} + +void DocumentOutlineNodesManager::getOutlineNodes( IDocumentOutlineNodes::tSortedOutlineNodeList& orOutlineNodeList ) const +{ + orOutlineNodeList.clear(); + orOutlineNodeList.reserve( getOutlineNodesCount() ); + + const tSortedOutlineNodeList::size_type nOutlCount = getOutlineNodesCount(); + for ( tSortedOutlineNodeList::size_type i = 0; i < nOutlCount; ++i ) + { + orOutlineNodeList.push_back( + m_rDoc.GetNodes().GetOutLineNds()[i]->GetTextNode() ); + } +} + +DocumentOutlineNodesManager::~DocumentOutlineNodesManager() +{ +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx b/sw/source/core/doc/DocumentRedlineManager.cxx new file mode 100644 index 000000000..f3aaa13a6 --- /dev/null +++ b/sw/source/core/doc/DocumentRedlineManager.cxx @@ -0,0 +1,3233 @@ +/* -*- 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 + +using namespace com::sun::star; + +#ifdef DBG_UTIL + + #define ERROR_PREFIX "redline table corrupted: " + + namespace + { + // helper function for lcl_CheckRedline + // 1. make sure that pPos->nContent points into pPos->nNode + // 2. check that position is valid and doesn't point after text + void lcl_CheckPosition( const SwPosition* pPos ) + { + assert(dynamic_cast(&pPos->nNode.GetNode()) + == pPos->nContent.GetIdxReg()); + + SwTextNode* pTextNode = pPos->nNode.GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + assert(pPos->nContent == 0); + } + else + { + assert(pPos->nContent >= 0 && pPos->nContent <= pTextNode->Len()); + } + } + + void lcl_CheckPam( const SwPaM* pPam ) + { + assert(pPam); + lcl_CheckPosition( pPam->GetPoint() ); + lcl_CheckPosition( pPam->GetMark() ); + } + + // check validity of the redline table. Checks redline bounds, and make + // sure the redlines are sorted and non-overlapping. + void lcl_CheckRedline( IDocumentRedlineAccess& redlineAccess ) + { + const SwRedlineTable& rTable = redlineAccess.GetRedlineTable(); + + // verify valid redline positions + for(SwRangeRedline* i : rTable) + lcl_CheckPam( i ); + + for(SwRangeRedline* j : rTable) + { + // check for empty redlines + // note: these can destroy sorting in SwTextNode::Update() + // if there's another one without mark on the same pos. + OSL_ENSURE( ( *(j->GetPoint()) != *(j->GetMark()) ) || + ( j->GetContentIdx() != nullptr ), + ERROR_PREFIX "empty redline" ); + } + + // verify proper redline sorting + for( size_t n = 1; n < rTable.size(); ++n ) + { + const SwRangeRedline* pPrev = rTable[ n-1 ]; + const SwRangeRedline* pCurrent = rTable[ n ]; + + // check redline sorting + SAL_WARN_IF( *pPrev->Start() > *pCurrent->Start(), "sw", + ERROR_PREFIX "not sorted correctly" ); + + // check for overlapping redlines + SAL_WARN_IF( *pPrev->End() > *pCurrent->Start(), "sw", + ERROR_PREFIX "overlapping redlines" ); + } + + assert(std::is_sorted(rTable.begin(), rTable.end(), CompareSwRedlineTable())); + } + } + + #define CHECK_REDLINE( pDoc ) lcl_CheckRedline( pDoc ); + +#else + + #define CHECK_REDLINE( pDoc ) + +#endif + +namespace sw { + +static void UpdateFieldsForRedline(IDocumentFieldsAccess & rIDFA) +{ + auto const pAuthType(static_cast(rIDFA.GetFieldType( + SwFieldIds::TableOfAuthorities, OUString(), false))); + if (pAuthType) // created on demand... + { + pAuthType->DelSequenceArray(); + } + rIDFA.GetFieldType(SwFieldIds::RefPageGet, OUString(), false)->UpdateFields(); + rIDFA.GetSysFieldType(SwFieldIds::Chapter)->UpdateFields(); + rIDFA.UpdateExpFields(nullptr, false); + rIDFA.UpdateRefFields(); +} + +void UpdateFramesForAddDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) +{ + // no need to call UpdateFootnoteNums for FTNNUM_PAGE: + // the AppendFootnote/RemoveFootnote will do it by itself! + rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->nNode); + SwPosition currentStart(*rPam.Start()); + SwTextNode * pStartNode(rPam.Start()->nNode.GetNode().GetTextNode()); + while (!pStartNode) + { + SwStartNode *const pTableOrSectionNode( + currentStart.nNode.GetNode().IsTableNode() + ? static_cast(currentStart.nNode.GetNode().GetTableNode()) + : static_cast(currentStart.nNode.GetNode().GetSectionNode())); + assert(pTableOrSectionNode); // known pathology + for (sal_uLong j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) + { + pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + } + for (SwRootFrame const*const pLayout : rDoc.GetAllLayouts()) + { + if (pLayout->IsHideRedlines()) + { + if (pTableOrSectionNode->IsTableNode()) + { + static_cast(pTableOrSectionNode)->DelFrames(pLayout); + } + else + { + static_cast(pTableOrSectionNode)->DelFrames(pLayout); + } + } + } + currentStart.nNode = pTableOrSectionNode->EndOfSectionIndex() + 1; + currentStart.nContent.Assign(currentStart.nNode.GetNode().GetContentNode(), 0); + pStartNode = currentStart.nNode.GetNode().GetTextNode(); + } + if (currentStart < *rPam.End()) + { + SwTextNode * pNode(pStartNode); + do + { + std::vector frames; + SwIterator aIter(*pNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + if (frames.empty()) + { + auto const& layouts(rDoc.GetAllLayouts()); + assert(std::none_of(layouts.begin(), layouts.end(), + [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); + (void) layouts; + break; + } + auto eMode(sw::FrameMode::Existing); + SwTextNode * pLast(pNode); + for (SwTextFrame * pFrame : frames) + { + SwTextNode & rFirstNode(pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pFirstNode + : *pNode); + assert(pNode == pStartNode + ? rFirstNode.GetIndex() <= pNode->GetIndex() + : &rFirstNode == pNode); + // clear old one first to avoid DelFrames confusing updates & asserts... + pFrame->SetMergedPara(nullptr); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + eMode = sw::FrameMode::New; // Existing is not idempotent! + // the first node of the new redline is not necessarily the first + // node of the merged frame, there could be another redline nearby + sw::AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, *pNode, nullptr); + // if redline is split across table and table cell is empty, there's no redline in the cell and so no merged para + if (pFrame->GetMergedPara()) + { + pLast = const_cast(pFrame->GetMergedPara()->pLastNode); + } + } + SwNodeIndex tmp(*pLast); + // skip over hidden sections! + pNode = static_cast(pLast->GetNodes().GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false)); + } + while (pNode && pNode->GetIndex() <= rPam.End()->nNode.GetIndex()); + } + // fields last - SwGetRefField::UpdateField requires up-to-date frames + UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes + + // update SwPostItMgr / notes in the margin + rDoc.GetDocShell()->Broadcast( + SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::REMOVED) ); +} + +void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) +{ + bool isAppendObjsCalled(false); + rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->nNode); + SwPosition currentStart(*rPam.Start()); + SwTextNode * pStartNode(rPam.Start()->nNode.GetNode().GetTextNode()); + while (!pStartNode) + { + SwStartNode const*const pTableOrSectionNode( + currentStart.nNode.GetNode().IsTableNode() + ? static_cast(currentStart.nNode.GetNode().GetTableNode()) + : static_cast(currentStart.nNode.GetNode().GetSectionNode())); + assert(pTableOrSectionNode); // known pathology + for (sal_uLong j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) + { + pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::None); + } + if (rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->IsHideRedlines()) + { + // note: this will also create frames for all currently hidden flys + // because it calls AppendAllObjs + SwNodeIndex const end(*pTableOrSectionNode->EndOfSectionNode()); + ::MakeFrames(&rDoc, currentStart.nNode, end); + isAppendObjsCalled = true; + } + currentStart.nNode = pTableOrSectionNode->EndOfSectionIndex() + 1; + currentStart.nContent.Assign(currentStart.nNode.GetNode().GetContentNode(), 0); + pStartNode = currentStart.nNode.GetNode().GetTextNode(); + } + if (currentStart < *rPam.End()) + { + SwTextNode * pNode(pStartNode); + do + { + std::vector frames; + SwIterator aIter(*pNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + if (frames.empty()) + { + auto const& layouts(rDoc.GetAllLayouts()); + assert(std::none_of(layouts.begin(), layouts.end(), + [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); + (void) layouts; + break; + } + + // first, call CheckParaRedlineMerge on the first paragraph, + // to init flag on new merge range (if any) + 1st node post the merge + auto eMode(sw::FrameMode::Existing); + SwTextNode * pLast(pNode); + for (SwTextFrame * pFrame : frames) + { + if (auto const pMergedPara = pFrame->GetMergedPara()) + { + pLast = const_cast(pMergedPara->pLastNode); + assert(pNode == pStartNode + ? pMergedPara->pFirstNode->GetIndex() <= pNode->GetIndex() + : pMergedPara->pFirstNode == pNode); + // clear old one first to avoid DelFrames confusing updates & asserts... + SwTextNode & rFirstNode(*pMergedPara->pFirstNode); + pFrame->SetMergedPara(nullptr); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + eMode = sw::FrameMode::New; // Existing is not idempotent! + } + } + if (pLast != pNode) + { + // now start node until end of merge + 1 has proper flags; MakeFrames + // should pick up from the next node in need of frames by checking flags + SwNodeIndex const start(*pNode, +1); + SwNodeIndex const end(*pLast, +1); // end is exclusive + // note: this will also create frames for all currently hidden flys + // both on first and non-first nodes because it calls AppendAllObjs + ::MakeFrames(&rDoc, start, end); + isAppendObjsCalled = true; + // re-use this to move flys that are now on the wrong frame, with end + // of redline as "second" node; the nodes between start and end should + // be complete with MakeFrames already + sw::MoveMergedFlysAndFootnotes(frames, *pNode, *pLast, false); + } + SwNodeIndex tmp(*pLast); + // skip over hidden sections! + pNode = static_cast(pLast->GetNodes().GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false)); + } + while (pNode && pNode->GetIndex() <= rPam.End()->nNode.GetIndex()); + } + + if (!isAppendObjsCalled) + { // recreate flys in the one node the hard way... + for (auto const& pLayout : rDoc.GetAllLayouts()) + { + if (pLayout->IsHideRedlines()) + { + AppendAllObjs(rDoc.GetSpzFrameFormats(), pLayout); + break; + } + } + } + // fields last - SwGetRefField::UpdateField requires up-to-date frames + UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes + + // update SwPostItMgr / notes in the margin + rDoc.GetDocShell()->Broadcast( + SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::INSERTED) ); +} + +} // namespace sw + +namespace +{ + bool IsPrevPos( const SwPosition & rPos1, const SwPosition & rPos2 ) + { + const SwContentNode* pCNd; + if( 0 != rPos2.nContent.GetIndex() ) + return false; + if( rPos2.nNode.GetIndex() - 1 != rPos1.nNode.GetIndex() ) + return false; + pCNd = rPos1.nNode.GetNode().GetContentNode(); + return pCNd && rPos1.nContent.GetIndex() == pCNd->Len(); + } + + // copy style or return with SwRedlineExtra_FormatColl with reject data of the upcoming copy + SwRedlineExtraData_FormatColl* lcl_CopyStyle( const SwPosition & rFrom, const SwPosition & rTo, bool bCopy = true ) + { + SwTextNode* pToNode = rTo.nNode.GetNode().GetTextNode(); + SwTextNode* pFromNode = rFrom.nNode.GetNode().GetTextNode(); + if (pToNode != nullptr && pFromNode != nullptr && pToNode != pFromNode) + { + const SwPaM aPam(*pToNode); + SwDoc* pDoc = aPam.GetDoc(); + // using Undo, copy paragraph style + SwTextFormatColl* pFromColl = pFromNode->GetTextColl(); + SwTextFormatColl* pToColl = pToNode->GetTextColl(); + if (bCopy && pFromColl != pToColl) + pDoc->SetTextFormatColl(aPam, pFromColl); + + // using Undo, remove direct paragraph formatting of the "To" paragraph, + // and apply here direct paragraph formatting of the "From" paragraph + SfxItemSet aTmp( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_BEGIN, RES_PARATR_END - 3, // skip RSID and GRABBAG + RES_PARATR_LIST_BEGIN, RES_UL_SPACE, // skip PAGEDESC and BREAK + RES_CNTNT, RES_FRMATR_END - 1>{}); + SfxItemSet aTmp2(aTmp); + + pToNode->GetParaAttr(aTmp, 0, 0); + pFromNode->GetParaAttr(aTmp2, 0, 0); + + bool bSameSet = aTmp == aTmp2; + + if (!bSameSet) + { + for( sal_uInt16 nItem = 0; nItem < aTmp.TotalCount(); ++nItem) + { + sal_uInt16 nWhich = aTmp.GetWhichByPos(nItem); + if( SfxItemState::SET == aTmp.GetItemState( nWhich, false ) && + SfxItemState::SET != aTmp2.GetItemState( nWhich, false ) ) + aTmp2.Put( aTmp.GetPool()->GetDefaultItem(nWhich), nWhich ); + } + } + + if (bCopy && !bSameSet) + pDoc->getIDocumentContentOperations().InsertItemSet(aPam, aTmp2); + else if (!bCopy && (!bSameSet || pFromColl != pToColl)) + return new SwRedlineExtraData_FormatColl( pFromColl->GetName(), USHRT_MAX, &aTmp2 ); + } + return nullptr; + } + + bool lcl_AcceptRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + bool bCallDelete, + const SwPosition* pSttRng = nullptr, + const SwPosition* pEndRng = nullptr ) + { + bool bRet = true; + SwRangeRedline* pRedl = rArr[ rPos ]; + SwPosition *pRStt = nullptr, *pREnd = nullptr; + SwComparePosition eCmp = SwComparePosition::Outside; + if( pSttRng && pEndRng ) + { + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd ); + } + + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + case RedlineType::Format: + { + bool bCheck = false, bReplace = false; + switch( eCmp ) + { + case SwComparePosition::Inside: + if( *pSttRng == *pRStt ) + pRedl->SetStart( *pEndRng, pRStt ); + else + { + if( *pEndRng != *pREnd ) + { + // split up + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEndRng ); + rArr.Insert( pNew ); ++rPos; + } + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + } + break; + + case SwComparePosition::OverlapBefore: + pRedl->SetStart( *pEndRng, pRStt ); + bReplace = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + break; + + case SwComparePosition::Outside: + case SwComparePosition::Equal: + rArr.DeleteAndDestroy( rPos-- ); + break; + + default: + bRet = false; + } + + if( bReplace || ( bCheck && !pRedl->HasValidRange() )) + { + // re-insert + rArr.Remove( pRedl ); + rArr.Insert( pRedl ); + } + } + break; + case RedlineType::Delete: + { + SwDoc& rDoc = *pRedl->GetDoc(); + const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr; + bool bDelRedl = false; + switch( eCmp ) + { + case SwComparePosition::Inside: + if( bCallDelete ) + { + pDelStt = pSttRng; + pDelEnd = pEndRng; + } + break; + + case SwComparePosition::OverlapBefore: + if( bCallDelete ) + { + pDelStt = pRStt; + pDelEnd = pEndRng; + } + break; + case SwComparePosition::OverlapBehind: + if( bCallDelete ) + { + pDelStt = pREnd; + pDelEnd = pSttRng; + } + break; + + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + rArr.Remove( rPos-- ); + bDelRedl = true; + if( bCallDelete ) + { + pDelStt = pRedl->Start(); + pDelEnd = pRedl->End(); + } + } + break; + default: + bRet = false; + } + + if( pDelStt && pDelEnd ) + { + SwPaM aPam( *pDelStt, *pDelEnd ); + SwContentNode* pCSttNd = pDelStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pDelEnd->nNode.GetNode().GetContentNode(); + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + + // keep style of the empty paragraph after deletion of wholly paragraphs + if( pCSttNd && pCEndNd && pRStt && pREnd && pRStt->nContent == 0 ) + lcl_CopyStyle(*pREnd, *pRStt); + + if( bDelRedl ) + delete pRedl; + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + if( pCSttNd && pCEndNd ) + rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + else if (pCSttNd && !pCEndNd) + { + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + rDoc.getIDocumentContentOperations().DelFullPara( aPam ); + } + else + { + rDoc.getIDocumentContentOperations().DeleteRange(aPam); + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( bDelRedl ) + delete pRedl; + } + break; + + case RedlineType::FmtColl: + rArr.DeleteAndDestroy( rPos-- ); + break; + + case RedlineType::ParagraphFormat: + rArr.DeleteAndDestroy( rPos-- ); + break; + + default: + bRet = false; + } + return bRet; + } + + bool lcl_RejectRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + bool bCallDelete, + const SwPosition* pSttRng = nullptr, + const SwPosition* pEndRng = nullptr ) + { + bool bRet = true; + SwRangeRedline* pRedl = rArr[ rPos ]; + SwDoc& rDoc = *pRedl->GetDoc(); + SwPosition *pRStt = nullptr, *pREnd = nullptr; + SwComparePosition eCmp = SwComparePosition::Outside; + if( pSttRng && pEndRng ) + { + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd ); + } + + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + { + const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr; + bool bDelRedl = false; + switch( eCmp ) + { + case SwComparePosition::Inside: + if( bCallDelete ) + { + pDelStt = pSttRng; + pDelEnd = pEndRng; + } + break; + + case SwComparePosition::OverlapBefore: + if( bCallDelete ) + { + pDelStt = pRStt; + pDelEnd = pEndRng; + } + break; + case SwComparePosition::OverlapBehind: + if( bCallDelete ) + { + pDelStt = pREnd; + pDelEnd = pSttRng; + } + break; + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + // delete the range again + rArr.Remove( rPos-- ); + bDelRedl = true; + if( bCallDelete ) + { + pDelStt = pRedl->Start(); + pDelEnd = pRedl->End(); + } + } + break; + + default: + bRet = false; + } + if( pDelStt && pDelEnd ) + { + SwPaM aPam( *pDelStt, *pDelEnd ); + + SwContentNode* pCSttNd = pDelStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pDelEnd->nNode.GetNode().GetContentNode(); + + if( bDelRedl ) + delete pRedl; + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + if( pCSttNd && pCEndNd ) + rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + else if (pCSttNd && !pCEndNd) + { + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + if (aPam.End()->nNode.GetNode().IsStartNode()) + { // end node will be deleted too! see nNodeDiff+1 + --aPam.End()->nNode; + } + assert(!aPam.End()->nNode.GetNode().IsStartNode()); + rDoc.getIDocumentContentOperations().DelFullPara( aPam ); + } + else + { + rDoc.getIDocumentContentOperations().DeleteRange(aPam); + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( bDelRedl ) + delete pRedl; + } + break; + case RedlineType::Delete: + { + SwRangeRedline* pNew = nullptr; + bool bCheck = false, bReplace = false; + SwPaM const updatePaM(pSttRng ? *pSttRng : *pRedl->Start(), + pEndRng ? *pEndRng : *pRedl->End()); + + if( pRedl->GetExtraData() ) + pRedl->GetExtraData()->Reject( *pRedl ); + + switch( eCmp ) + { + case SwComparePosition::Inside: + { + if( 1 < pRedl->GetStackCount() ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PopData(); + } + if( *pSttRng == *pRStt ) + { + pRedl->SetStart( *pEndRng, pRStt ); + bReplace = true; + if( pNew ) + pNew->SetEnd( *pEndRng ); + } + else + { + if( *pEndRng != *pREnd ) + { + // split up + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEndRng ); + rArr.Insert( pCpy ); ++rPos; + if( pNew ) + pNew->SetEnd( *pEndRng ); + } + + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + if( pNew ) + pNew->SetStart( *pSttRng ); + } + } + break; + + case SwComparePosition::OverlapBefore: + if( 1 < pRedl->GetStackCount() ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PopData(); + } + pRedl->SetStart( *pEndRng, pRStt ); + bReplace = true; + if( pNew ) + pNew->SetEnd( *pEndRng ); + break; + + case SwComparePosition::OverlapBehind: + if( 1 < pRedl->GetStackCount() ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PopData(); + } + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + if( pNew ) + pNew->SetStart( *pSttRng ); + break; + + case SwComparePosition::Outside: + case SwComparePosition::Equal: + if( !pRedl->PopData() ) + // deleting the RedlineObject is enough + rArr.DeleteAndDestroy( rPos-- ); + break; + + default: + bRet = false; + } + + if( pNew ) + { + rArr.Insert( pNew ); ++rPos; + } + + if( bReplace || ( bCheck && !pRedl->HasValidRange() )) + { + // re-insert + rArr.Remove( pRedl ); + rArr.Insert( pRedl ); + } + + sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM); + } + break; + + case RedlineType::Format: + case RedlineType::FmtColl: + case RedlineType::ParagraphFormat: + { + // tdf#52391 instead of hidden acception at the requested + // rejection, remove direct text formatting to get the potential + // original state of the text (FIXME if the original text + // has already contained direct text formatting: unfortunately + // ODF 1.2 doesn't support rejection of format-only changes) + if ( pRedl->GetType() == RedlineType::Format ) + { + SwPaM aPam( *(pRedl->Start()), *(pRedl->End()) ); + rDoc.ResetAttrs(aPam); + } + else if ( pRedl->GetType() == RedlineType::ParagraphFormat ) + { + // handle paragraph formatting changes + // (range is only a full paragraph or a part of it) + const SwPosition* pStt = pRedl->Start(); + SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + // expand range to the whole paragraph + // and reset only the paragraph attributes + SwPaM aPam( *pTNd, pTNd->GetText().getLength() ); + std::set aResetAttrsArray; + + sal_uInt16 aResetableSetRange[] = { + RES_PARATR_BEGIN, RES_PARATR_END - 1, + RES_PARATR_LIST_BEGIN, RES_FRMATR_END - 1, + 0 + }; + + const sal_uInt16 *pUShorts = aResetableSetRange; + while (*pUShorts) + { + for (sal_uInt16 i = pUShorts[0]; i <= pUShorts[1]; ++i) + aResetAttrsArray.insert( aResetAttrsArray.end(), i ); + pUShorts += 2; + } + + rDoc.ResetAttrs(aPam, false, aResetAttrsArray); + + // remove numbering + if ( pTNd->GetNumRule() ) + rDoc.DelNumRules(aPam); + } + } + + if( pRedl->GetExtraData() ) + pRedl->GetExtraData()->Reject( *pRedl ); + + rArr.DeleteAndDestroy( rPos-- ); + } + break; + + default: + bRet = false; + } + return bRet; + } + + typedef bool (*Fn_AcceptReject)( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + bool bCallDelete, + const SwPosition* pSttRng, + const SwPosition* pEndRng); + + + int lcl_AcceptRejectRedl( Fn_AcceptReject fn_AcceptReject, + SwRedlineTable& rArr, bool bCallDelete, + const SwPaM& rPam) + { + SwRedlineTable::size_type n = 0; + int nCount = 0; + + const SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + const SwRangeRedline* pFnd = rArr.FindAtPosition( *pStt, n ); + if( pFnd && // Is new a part of it? + ( *pFnd->Start() != *pStt || *pFnd->End() > *pEnd )) + { + // Only revoke the partial selection + if( (*fn_AcceptReject)( rArr, n, bCallDelete, pStt, pEnd )) + nCount++; + ++n; + } + + // tdf#119824 first we will accept only overlapping paragraph format changes + // in the first loop to avoid potential content changes during Redo + bool bHasParagraphFormatChange = false; + for( int m = 0 ; m < 2 && !bHasParagraphFormatChange; ++m ) + { + for(SwRedlineTable::size_type o = n ; o < rArr.size(); ++o ) + { + SwRangeRedline* pTmp = rArr[ o ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + if( *pTmp->End() <= *pEnd ) + { + if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) && + (*fn_AcceptReject)( rArr, o, bCallDelete, nullptr, nullptr )) + { + bHasParagraphFormatChange = true; + nCount++; + } + } + else + { + if( *pTmp->Start() < *pEnd ) + { + // Only revoke the partial selection + if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) && + (*fn_AcceptReject)( rArr, o, bCallDelete, pStt, pEnd )) + { + bHasParagraphFormatChange = true; + nCount++; + } + } + break; + } + } + } + } + return nCount; + } + + void lcl_AdjustRedlineRange( SwPaM& rPam ) + { + // The Selection is only in the ContentSection. If there are Redlines + // to Non-ContentNodes before or after that, then the Selections + // expand to them. + SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + SwDoc* pDoc = rPam.GetDoc(); + if( !pStt->nContent.GetIndex() && + !pDoc->GetNodes()[ pStt->nNode.GetIndex() - 1 ]->IsContentNode() ) + { + const SwRangeRedline* pRedl = pDoc->getIDocumentRedlineAccess().GetRedline( *pStt, nullptr ); + if( pRedl ) + { + const SwPosition* pRStt = pRedl->Start(); + if( !pRStt->nContent.GetIndex() && pRStt->nNode.GetIndex() == + pStt->nNode.GetIndex() - 1 ) + *pStt = *pRStt; + } + } + if( pEnd->nNode.GetNode().IsContentNode() && + !pDoc->GetNodes()[ pEnd->nNode.GetIndex() + 1 ]->IsContentNode() && + pEnd->nContent.GetIndex() == pEnd->nNode.GetNode().GetContentNode()->Len() ) + { + const SwRangeRedline* pRedl = pDoc->getIDocumentRedlineAccess().GetRedline( *pEnd, nullptr ); + if( pRedl ) + { + const SwPosition* pREnd = pRedl->End(); + if( !pREnd->nContent.GetIndex() && pREnd->nNode.GetIndex() == + pEnd->nNode.GetIndex() + 1 ) + *pEnd = *pREnd; + } + } + } + + /// in case some text is deleted, ensure that the not-yet-inserted + /// SwRangeRedline has its positions corrected not to point to deleted node + class TemporaryRedlineUpdater + { + private: + SwRangeRedline & m_rRedline; + std::shared_ptr m_pCursor; + public: + TemporaryRedlineUpdater(SwDoc & rDoc, SwRangeRedline & rRedline) + : m_rRedline(rRedline) + , m_pCursor(rDoc.CreateUnoCursor(*rRedline.GetPoint(), false)) + { + if (m_rRedline.HasMark()) + { + m_pCursor->SetMark(); + *m_pCursor->GetMark() = *m_rRedline.GetMark(); + *m_rRedline.GetMark() = SwPosition(rDoc.GetNodes().GetEndOfContent()); + } + *m_rRedline.GetPoint() = SwPosition(rDoc.GetNodes().GetEndOfContent()); + } + ~TemporaryRedlineUpdater() + { + static_cast(m_rRedline) = *m_pCursor; + } + }; +} + +namespace sw +{ + +DocumentRedlineManager::DocumentRedlineManager(SwDoc& i_rSwdoc) + : m_rDoc(i_rSwdoc) + , meRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) + , mpRedlineTable(new SwRedlineTable) + , mpExtraRedlineTable(new SwExtraRedlineTable) + , mbIsRedlineMove(false) + , mnAutoFormatRedlnCommentNo(0) +{ +} + +RedlineFlags DocumentRedlineManager::GetRedlineFlags() const +{ + return meRedlineFlags; +} + +void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode ) +{ + if( meRedlineFlags != eMode ) + { + if( (RedlineFlags::ShowMask & meRedlineFlags) != (RedlineFlags::ShowMask & eMode) + || !(RedlineFlags::ShowMask & eMode) ) + { + bool bSaveInXMLImportFlag = m_rDoc.IsInXMLImport(); + m_rDoc.SetInXMLImport( false ); + // and then hide/display everything + void (SwRangeRedline::*pFnc)(sal_uInt16, size_t); // Allow compiler warn if use of + // uninitialized ptr is possible + + RedlineFlags eShowMode = RedlineFlags::ShowMask & eMode; + if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + pFnc = &SwRangeRedline::Show; + else if (eShowMode == RedlineFlags::ShowInsert) + pFnc = &SwRangeRedline::Hide; + else if (eShowMode == RedlineFlags::ShowDelete) + pFnc = &SwRangeRedline::ShowOriginal; + else + { + pFnc = &SwRangeRedline::Hide; + eMode |= RedlineFlags::ShowInsert; + } + + CheckAnchoredFlyConsistency(m_rDoc); + CHECK_REDLINE( *this ) + + o3tl::sorted_vector hiddenLayouts; + if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + { + // sw_redlinehide: the problem here is that MoveFromSection + // creates the frames wrongly (non-merged), because its own + // SwRangeRedline has wrong positions until after the nodes + // are all moved, so fix things up by force by re-creating + // all merged frames from scratch. + o3tl::sorted_vector const layouts(m_rDoc.GetAllLayouts()); + for (SwRootFrame *const pLayout : layouts) + { + if (pLayout->IsHideRedlines()) + { + pLayout->SetHideRedlines(false); + hiddenLayouts.insert(pLayout); + } + } + } + + for (sal_uInt16 nLoop = 1; nLoop <= 2; ++nLoop) + for (size_t i = 0; i < mpRedlineTable->size(); ++i) + { + SwRangeRedline *const pRedline((*mpRedlineTable)[i]); + (pRedline->*pFnc)(nLoop, i); + while (mpRedlineTable->size() <= i + || (*mpRedlineTable)[i] != pRedline) + { // ensure current position + --i; // a previous redline may have been deleted + } + } + + //SwRangeRedline::MoveFromSection routinely changes + //the keys that mpRedlineTable is sorted by + mpRedlineTable->Resort(); + + CheckAnchoredFlyConsistency(m_rDoc); + CHECK_REDLINE( *this ) + + for (SwRootFrame *const pLayout : hiddenLayouts) + { + pLayout->SetHideRedlines(true); + } + + m_rDoc.SetInXMLImport( bSaveInXMLImportFlag ); + } + meRedlineFlags = eMode; + m_rDoc.getIDocumentState().SetModified(); + } + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::IsRedlineOn() const +{ + return IDocumentRedlineAccess::IsRedlineOn(meRedlineFlags); +} + +bool DocumentRedlineManager::IsIgnoreRedline() const +{ + return bool(RedlineFlags::Ignore & meRedlineFlags); +} + +void DocumentRedlineManager::SetRedlineFlags_intern(RedlineFlags eMode) +{ + meRedlineFlags = eMode; +} + +const SwRedlineTable& DocumentRedlineManager::GetRedlineTable() const +{ + return *mpRedlineTable; +} + +SwRedlineTable& DocumentRedlineManager::GetRedlineTable() +{ + return *mpRedlineTable; +} + +const SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() const +{ + return *mpExtraRedlineTable; +} + +SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() +{ + return *mpExtraRedlineTable; +} + +bool DocumentRedlineManager::HasExtraRedlineTable() const +{ + return mpExtraRedlineTable != nullptr; +} + +bool DocumentRedlineManager::IsInRedlines(const SwNode & rNode) const +{ + SwPosition aPos(rNode); + SwNode & rEndOfRedlines = m_rDoc.GetNodes().GetEndOfRedlines(); + SwPaM aPam(SwPosition(*rEndOfRedlines.StartOfSectionNode()), + SwPosition(rEndOfRedlines)); + + return aPam.ContainsPosition(aPos); +} + +bool DocumentRedlineManager::IsRedlineMove() const +{ + return mbIsRedlineMove; +} + +void DocumentRedlineManager::SetRedlineMove(bool bFlag) +{ + mbIsRedlineMove = bFlag; +} + +/* +Text means Text not "polluted" by Redlines. + +Behaviour of Insert-Redline: + - in the Text - insert Redline Object + - in InsertRedline (own) - ignore, existing is extended + - in InsertRedline (others) - split up InsertRedline and + insert Redline Object + - in DeleteRedline - split up DeleteRedline or + move at the end/beginning + +Behaviour of Delete-Redline: + - in the Text - insert Redline Object + - in DeleteRedline (own/others) - ignore + - in InsertRedline (own) - ignore, but delete character + - in InsertRedline (others) - split up InsertRedline and + insert Redline Object + - Text and own Insert overlap - delete Text in the own Insert, + extend in the other Text + (up to the Insert!) + - Text and other Insert overlap - insert Redline Object, the + other Insert is overlapped by + the Delete +*/ +IDocumentRedlineAccess::AppendResult +DocumentRedlineManager::AppendRedline(SwRangeRedline* pNewRedl, bool const bCallDelete) +{ + bool bMerged = false; + CHECK_REDLINE( *this ) + + if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) + { + pNewRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + + if( m_rDoc.IsAutoFormatRedline() ) + { + pNewRedl->SetAutoFormat(); + if( mpAutoFormatRedlnComment && !mpAutoFormatRedlnComment->isEmpty() ) + { + pNewRedl->SetComment( *mpAutoFormatRedlnComment ); + pNewRedl->SetSeqNo( mnAutoFormatRedlnCommentNo ); + } + } + + SwPosition* pStt = pNewRedl->Start(), + * pEnd = pStt == pNewRedl->GetPoint() ? pNewRedl->GetMark() + : pNewRedl->GetPoint(); + { + SwTextNode* pTextNode = pStt->nNode.GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + if( pStt->nContent > 0 ) + { + OSL_ENSURE( false, "Redline start: non-text-node with content" ); + pStt->nContent = 0; + } + } + else + { + if( pStt->nContent > pTextNode->Len() ) + { + OSL_ENSURE( false, "Redline start: index after text" ); + pStt->nContent = pTextNode->Len(); + } + } + pTextNode = pEnd->nNode.GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + if( pEnd->nContent > 0 ) + { + OSL_ENSURE( false, "Redline end: non-text-node with content" ); + pEnd->nContent = 0; + } + } + else + { + if( pEnd->nContent > pTextNode->Len() ) + { + OSL_ENSURE( false, "Redline end: index after text" ); + pEnd->nContent = pTextNode->Len(); + } + } + } + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + { // Do not insert empty redlines + delete pNewRedl; + return AppendResult::IGNORED; + } + bool bCompress = false; + SwRedlineTable::size_type n = 0; + // look up the first Redline for the starting position + if( !GetRedline( *pStt, &n ) && n ) + --n; + bool bDec = false; + + for( ; pNewRedl && n < mpRedlineTable->size(); bDec ? n : ++n ) + { + bDec = false; + + SwRangeRedline* pRedl = (*mpRedlineTable)[ n ]; + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark() + : pRedl->GetPoint(); + + // #i8518# remove empty redlines while we're at it + if( ( *pRStt == *pREnd ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + mpRedlineTable->DeleteAndDestroy(n); + continue; + } + + SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); + + switch( pNewRedl->GetType() ) + { + case RedlineType::Insert: + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + if( pRedl->IsOwnRedline( *pNewRedl ) ) + { + bool bDelete = false; + + // Merge if applicable? + if( (( SwComparePosition::Behind == eCmpPos && + IsPrevPos( *pREnd, *pStt ) ) || + ( SwComparePosition::CollideStart == eCmpPos ) || + ( SwComparePosition::OverlapBehind == eCmpPos ) ) && + pRedl->CanCombine( *pNewRedl ) && + ( n+1 >= mpRedlineTable->size() || + ( *(*mpRedlineTable)[ n+1 ]->Start() >= *pEnd && + *(*mpRedlineTable)[ n+1 ]->Start() != *pREnd ) ) ) + { + pRedl->SetEnd( *pEnd, pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + } + + bMerged = true; + bDelete = true; + } + else if( (( SwComparePosition::Before == eCmpPos && + IsPrevPos( *pEnd, *pRStt ) ) || + ( SwComparePosition::CollideEnd == eCmpPos ) || + ( SwComparePosition::OverlapBefore == eCmpPos ) ) && + pRedl->CanCombine( *pNewRedl ) && + ( !n || + *(*mpRedlineTable)[ n-1 ]->End() != *pRStt )) + { + pRedl->SetStart( *pStt, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + + bMerged = true; + bDelete = true; + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // own insert-over-insert redlines: + // just scrap the inside ones + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + { + *pStt = *pREnd; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + bDelete = true; + } + else if( SwComparePosition::OverlapBefore == eCmpPos ) + { + *pEnd = *pRStt; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + bDelete = true; + } + else if( SwComparePosition::Inside == eCmpPos ) + { + bDelete = true; + bMerged = true; + } + else if( SwComparePosition::Equal == eCmpPos ) + bDelete = true; + + if( bDelete ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + else if( SwComparePosition::Inside == eCmpPos ) + { + // split up + if( *pEnd != *pREnd ) + { + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + mpRedlineTable->Insert( pCpy ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + } + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // handle overlapping redlines in broken documents + + // split up the new redline, since it covers the + // existing redline. Insert the first part, and + // progress with the remainder as usual + SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl ); + pSplit->SetEnd( *pRStt ); + pNewRedl->SetStart( *pREnd ); + mpRedlineTable->Insert( pSplit ); + if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + else if ( SwComparePosition::OverlapBehind == eCmpPos ) + { + // handle overlapping redlines in broken documents + pNewRedl->SetStart( *pREnd ); + } + else if ( SwComparePosition::OverlapBefore == eCmpPos ) + { + // handle overlapping redlines in broken documents + *pEnd = *pRStt; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + break; + case RedlineType::Delete: + if( SwComparePosition::Inside == eCmpPos ) + { + // split up + if( *pEnd != *pREnd ) + { + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + mpRedlineTable->Insert( pCpy ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + } + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // handle overlapping redlines in broken documents + + // split up the new redline, since it covers the + // existing redline. Insert the first part, and + // progress with the remainder as usual + SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl ); + pSplit->SetEnd( *pRStt ); + pNewRedl->SetStart( *pREnd ); + mpRedlineTable->Insert( pSplit ); + if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + else if ( SwComparePosition::Equal == eCmpPos ) + { + // handle identical redlines in broken documents + // delete old (delete) redline + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if ( SwComparePosition::OverlapBehind == eCmpPos ) + { // Another workaround for broken redlines + pNewRedl->SetStart( *pREnd ); + } + break; + case RedlineType::Format: + switch( eCmpPos ) + { + case SwComparePosition::OverlapBefore: + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pStt, pREnd ); + if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + + case SwComparePosition::Equal: + case SwComparePosition::Outside: + // Overlaps the current one completely or has the + // same dimension, delete the old one + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + break; + + case SwComparePosition::Inside: + // Overlaps the current one completely, + // split or shorten the new one + if( *pEnd != *pREnd ) + { + if( *pEnd != *pRStt ) + { + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + pRedl->SetEnd( *pStt, pREnd ); + if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) + mpRedlineTable->DeleteAndDestroy( n ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + } + else + pRedl->SetEnd( *pStt, pREnd ); + break; + default: + break; + } + break; + default: + break; + } + break; + + case RedlineType::Delete: + switch( pRedl->GetType() ) + { + case RedlineType::Delete: + switch( eCmpPos ) + { + case SwComparePosition::Outside: + { + // Overlaps the current one completely, + // split the new one + if (*pEnd == *pREnd) + { + pNewRedl->SetEnd(*pRStt, pEnd); + } + else if (*pStt == *pRStt) + { + pNewRedl->SetStart(*pREnd, pStt); + } + else + { + SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl ); + pNew->SetStart( *pREnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + } + break; + + case SwComparePosition::Inside: + case SwComparePosition::Equal: + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + break; + + case SwComparePosition::OverlapBefore: + case SwComparePosition::OverlapBehind: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl )) + { + // If that's the case we can merge it, meaning + // the new one covers this well + if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pRStt, pStt ); + else + pNewRedl->SetEnd( *pREnd, pEnd ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pREnd, pStt ); + else + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::CollideStart: + case SwComparePosition::CollideEnd: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) ) + { + if( IsHideChanges( meRedlineFlags )) + { + // Before we can merge, we make it visible! + // We insert temporarily so that pNew is + // also dealt with when moving the indices. + mpRedlineTable->Insert(pNewRedl); + pRedl->Show(0, mpRedlineTable->GetPos(pRedl)); + mpRedlineTable->Remove( pNewRedl ); + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + } + + // If that's the case we can merge it, meaning + // the new one covers this well + if( SwComparePosition::CollideStart == eCmpPos ) + pNewRedl->SetStart( *pRStt, pStt ); + else + pNewRedl->SetEnd( *pREnd, pEnd ); + + // delete current (below), and restart process with + // previous + SwRedlineTable::size_type nToBeDeleted = n; + bDec = true; + + if( *(pNewRedl->Start()) <= *pREnd ) + { + // Whoooah, we just extended the new 'redline' + // beyond previous redlines, so better start + // again. Of course this is not supposed to + // happen, and in an ideal world it doesn't, + // but unfortunately this code is buggy and + // totally rotten so it does happen and we + // better fix it. + n = 0; + bDec = true; + } + + mpRedlineTable->DeleteAndDestroy( nToBeDeleted ); + } + break; + default: + break; + } + break; + + case RedlineType::Insert: + { + // b62341295: Do not throw away redlines + // even if they are not allowed to be combined + RedlineFlags eOld = meRedlineFlags; + if( !( eOld & RedlineFlags::DontCombineRedlines ) && + pRedl->IsOwnRedline( *pNewRedl ) ) + { + + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore); + switch( eCmpPos ) + { + case SwComparePosition::Equal: + bCompress = true; + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + [[fallthrough]]; + + case SwComparePosition::Inside: + if( bCallDelete ) + { + // DeleteAndJoin does not yield the + // desired result if there is no paragraph to + // join with, i.e. at the end of the document. + // For this case, we completely delete the + // paragraphs (if, of course, we also start on + // a paragraph boundary). + if( (pStt->nContent == 0) && + pEnd->nNode.GetNode().IsEndNode() ) + { + pEnd->nNode--; + pEnd->nContent.Assign( + pEnd->nNode.GetNode().GetTextNode(), 0); + m_rDoc.getIDocumentContentOperations().DelFullPara( *pNewRedl ); + } + else + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + + bCompress = true; + } + if( !bCallDelete && !bDec && *pEnd == *pREnd ) + { + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + bCompress = true; + } + else if ( bCallDelete || !bDec ) + { + // delete new redline, except in some cases of fallthrough from previous + // case ::Equal (eg. same portion w:del in w:ins in OOXML import) + delete pNewRedl; + pNewRedl = nullptr; + } + break; + + case SwComparePosition::Outside: + { + mpRedlineTable->Remove( n ); + bDec = true; + if( bCallDelete ) + { + TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pRedl ); + n = 0; // re-initialize + } + delete pRedl; + } + break; + + case SwComparePosition::OverlapBefore: + { + SwPaM aPam( *pRStt, *pEnd ); + + if( *pEnd == *pREnd ) + mpRedlineTable->DeleteAndDestroy( n ); + else + { + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + } + + if( bCallDelete ) + { + TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + n = 0; // re-initialize + } + bDec = true; + } + break; + + case SwComparePosition::OverlapBehind: + { + SwPaM aPam( *pStt, *pREnd ); + + if( *pStt == *pRStt ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else + pRedl->SetEnd( *pStt, pREnd ); + + if( bCallDelete ) + { + TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + n = 0; // re-initialize + bDec = true; + } + } + break; + default: + break; + } + + meRedlineFlags = eOld; + } + else + { + // it may be necessary to split the existing redline in + // two. In this case, pRedl will be changed to cover + // only part of its former range, and pNew will cover + // the remainder. + SwRangeRedline* pNew = nullptr; + + switch( eCmpPos ) + { + case SwComparePosition::Equal: + { + pRedl->PushData( *pNewRedl ); + delete pNewRedl; + pNewRedl = nullptr; + if( IsHideChanges( meRedlineFlags )) + { + pRedl->Hide(0, mpRedlineTable->GetPos(pRedl)); + } + bCompress = true; + } + break; + + case SwComparePosition::Inside: + { + if( *pRStt == *pStt ) + { + // #i97421# + // redline w/out extent loops + if (*pStt != *pEnd) + { + pNewRedl->PushData( *pRedl, false ); + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + } + } + else + { + pNewRedl->PushData( *pRedl, false ); + if( *pREnd != *pEnd ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + } + } + } + break; + + case SwComparePosition::Outside: + { + pRedl->PushData( *pNewRedl ); + if( *pEnd == *pREnd ) + { + pNewRedl->SetEnd( *pRStt, pEnd ); + } + else if (*pStt == *pRStt) + { + pNewRedl->SetStart(*pREnd, pStt); + } + else + { + pNew = new SwRangeRedline( *pNewRedl ); + pNew->SetEnd( *pRStt ); + pNewRedl->SetStart( *pREnd, pStt ); + } + bCompress = true; + } + break; + + case SwComparePosition::OverlapBefore: + { + if( *pEnd == *pREnd ) + { + pRedl->PushData( *pNewRedl ); + pNewRedl->SetEnd( *pRStt, pEnd ); + if( IsHideChanges( meRedlineFlags )) + { + mpRedlineTable->Insert(pNewRedl); + pRedl->Hide(0, mpRedlineTable->GetPos(pRedl)); + mpRedlineTable->Remove( pNewRedl ); + } + } + else + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PushData( *pNewRedl ); + pNew->SetEnd( *pEnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + pRedl->SetStart( *pNew->End(), pRStt ) ; + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + bDec = true; + } + } + break; + + case SwComparePosition::OverlapBehind: + { + if( *pStt == *pRStt ) + { + pRedl->PushData( *pNewRedl ); + pNewRedl->SetStart( *pREnd, pStt ); + if( IsHideChanges( meRedlineFlags )) + { + mpRedlineTable->Insert( pNewRedl ); + pRedl->Hide(0, mpRedlineTable->GetPos(pRedl)); + mpRedlineTable->Remove( pNewRedl ); + } + } + else + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PushData( *pNewRedl ); + pNew->SetStart( *pStt ); + pNewRedl->SetStart( *pREnd, pStt ); + pRedl->SetEnd( *pNew->Start(), pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + } + } + } + break; + default: + break; + } + + // insert the pNew part (if it exists) + if( pNew ) + { + mpRedlineTable->Insert( pNew ); + + // pNew must be deleted if Insert() wasn't + // successful. But that can't happen, since pNew is + // part of the original pRedl redline. + // OSL_ENSURE( bRet, "Can't insert existing redline?" ); + + // restart (now with pRedl being split up) + n = 0; + bDec = true; + } + } + } + break; + + case RedlineType::Format: + switch( eCmpPos ) + { + case SwComparePosition::OverlapBefore: + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pStt, pREnd ); + break; + + case SwComparePosition::Equal: + case SwComparePosition::Outside: + // Overlaps the current one completely or has the + // same dimension, delete the old one + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + break; + + case SwComparePosition::Inside: + // Overlaps the current one completely, + // split or shorten the new one + if( *pEnd != *pREnd ) + { + if( *pEnd != *pRStt ) + { + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + mpRedlineTable->DeleteAndDestroy( n ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + } + else + pRedl->SetEnd( *pStt, pREnd ); + break; + default: + break; + } + break; + default: + break; + } + break; + + case RedlineType::Format: + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + case RedlineType::Delete: + switch( eCmpPos ) + { + case SwComparePosition::OverlapBefore: + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::OverlapBehind: + pNewRedl->SetStart( *pREnd, pStt ); + break; + + case SwComparePosition::Equal: + case SwComparePosition::Inside: + delete pNewRedl; + pNewRedl = nullptr; + break; + + case SwComparePosition::Outside: + // Overlaps the current one completely, + // split or shorten the new one + if (*pEnd == *pREnd) + { + pNewRedl->SetEnd(*pRStt, pEnd); + } + else if (*pStt == *pRStt) + { + pNewRedl->SetStart(*pREnd, pStt); + } + else + { + SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl ); + pNew->SetStart( *pREnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + break; + default: + break; + } + break; + case RedlineType::Format: + switch( eCmpPos ) + { + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + // Overlaps the current one completely or has the + // same dimension, delete the old one + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + + case SwComparePosition::Inside: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl )) + { + // own one can be ignored completely + delete pNewRedl; + pNewRedl = nullptr; + } + else if( *pREnd == *pEnd ) + // or else only shorten the current one + pRedl->SetEnd( *pStt, pREnd ); + else if( *pRStt == *pStt ) + { + // or else only shorten the current one + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + } + else + { + // If it lies completely within the current one + // we need to split it + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + pRedl->SetEnd( *pStt, pREnd ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + break; + + case SwComparePosition::OverlapBefore: + case SwComparePosition::OverlapBehind: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl )) + { + // If that's the case we can merge it, meaning + // the new one covers this well + if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pRStt, pStt ); + else + pNewRedl->SetEnd( *pREnd, pEnd ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = false; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pREnd, pStt ); + else + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::CollideEnd: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) && n && + *(*mpRedlineTable)[ n-1 ]->End() < *pStt ) + { + // If that's the case we can merge it, meaning + // the new one covers this well + pNewRedl->SetEnd( *pREnd, pEnd ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + case SwComparePosition::CollideStart: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) && + n+1 < mpRedlineTable->size() && + *(*mpRedlineTable)[ n+1 ]->Start() < *pEnd ) + { + // If that's the case we can merge it, meaning + // the new one covers this well + pNewRedl->SetStart( *pRStt, pStt ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + default: + break; + } + break; + default: + break; + } + break; + + case RedlineType::FmtColl: + // How should we behave here? + // insert as is + break; + default: + break; + } + } + + if( pNewRedl ) + { + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + { // Do not insert empty redlines + delete pNewRedl; + pNewRedl = nullptr; + } + else + { + if ( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + if ( pStt->nContent != 0 ) + { + // tdf#119571 update the style of the joined paragraph + // after a partially deleted paragraph to show its correct style + // in "Show changes" mode, too. All removed paragraphs + // get the style of the first (partially deleted) paragraph + // to avoid text insertion with bad style in the deleted + // area later. + + SwContentNode* pDelNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pTextNd = pEnd->nNode.GetNode().GetContentNode(); + SwTextNode* pDelNode = pStt->nNode.GetNode().GetTextNode(); + SwTextNode* pTextNode; + SwNodeIndex aIdx( pEnd->nNode.GetNode() ); + bool bFirst = true; + + while (pDelNode != nullptr && pTextNd != nullptr && pDelNd->GetIndex() < pTextNd->GetIndex()) + { + pTextNode = pTextNd->GetTextNode(); + if (pTextNode && pDelNode != pTextNode ) + { + SwPosition aPos(aIdx); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + bCompress = true; + + // split redline to store ExtraData per paragraphs + SwRangeRedline* pPar = new SwRangeRedline( *pNewRedl ); + pPar->SetStart( aPos ); + pNewRedl->SetEnd( aPos ); + + // get extradata for reset formatting of the modified paragraph + SwRedlineExtraData_FormatColl* pExtraData = lcl_CopyStyle(aPos, *pStt, false); + if (pExtraData) + { + std::unique_ptr xRedlineExtraData; + if (!bFirst) + pExtraData->SetFormatAll(false); + xRedlineExtraData.reset(pExtraData); + pPar->SetExtraData( xRedlineExtraData.get() ); + } + mpRedlineTable->Insert( pPar ); + } + + // modify paragraph formatting + lcl_CopyStyle(*pStt, aPos); + } + pTextNd = SwNodes::GoPrevious( &aIdx ); + + if (bFirst) + bFirst = false; + } + } + } + bool const ret = mpRedlineTable->Insert( pNewRedl ); + assert(ret || !pNewRedl); + if (ret && !pNewRedl) + { + bMerged = true; // treat InsertWithValidRanges as "merge" + } + } + } + + if( bCompress ) + CompressRedlines(); + } + else + { + if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + RedlineFlags eOld = meRedlineFlags; + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + meRedlineFlags = eOld; + } + delete pNewRedl; + pNewRedl = nullptr; + } + CHECK_REDLINE( *this ) + + return (nullptr != pNewRedl) + ? AppendResult::APPENDED + : (bMerged ? AppendResult::MERGED : AppendResult::IGNORED); +} + +bool DocumentRedlineManager::AppendTableRowRedline( SwTableRowRedline* pNewRedl ) +{ + // #TODO - equivalent for 'SwTableRowRedline' + /* + CHECK_REDLINE( this ) + */ + + if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) + { + // #TODO - equivalent for 'SwTableRowRedline' + /* + pNewRedl->InvalidateRange(); + */ + + // Make equivalent of 'AppendRedline' checks inside here too + + mpExtraRedlineTable->Insert( pNewRedl ); + } + else + { + // TO DO - equivalent for 'SwTableRowRedline' + /* + if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + RedlineFlags eOld = meRedlineFlags; + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore); + DeleteAndJoin( *pNewRedl ); + meRedlineFlags = eOld; + } + delete pNewRedl, pNewRedl = 0; + */ + } + // #TODO - equivalent for 'SwTableRowRedline' + /* + CHECK_REDLINE( this ) + */ + + return nullptr != pNewRedl; +} + +bool DocumentRedlineManager::AppendTableCellRedline( SwTableCellRedline* pNewRedl ) +{ + // #TODO - equivalent for 'SwTableCellRedline' + /* + CHECK_REDLINE( this ) + */ + + if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) + { + // #TODO - equivalent for 'SwTableCellRedline' + /* + pNewRedl->InvalidateRange(); + */ + + // Make equivalent of 'AppendRedline' checks inside here too + + mpExtraRedlineTable->Insert( pNewRedl ); + } + else + { + // TO DO - equivalent for 'SwTableCellRedline' + /* + if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + RedlineFlags eOld = meRedlineFlags; + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore); + DeleteAndJoin( *pNewRedl ); + meRedlineFlags = eOld; + } + delete pNewRedl, pNewRedl = 0; + */ + } + // #TODO - equivalent for 'SwTableCellRedline' + /* + CHECK_REDLINE( this ) + */ + + return nullptr != pNewRedl; +} + +void DocumentRedlineManager::CompressRedlines() +{ + CHECK_REDLINE( *this ) + + void (SwRangeRedline::*pFnc)(sal_uInt16, size_t) = nullptr; + RedlineFlags eShow = RedlineFlags::ShowMask & meRedlineFlags; + if( eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + pFnc = &SwRangeRedline::Show; + else if (eShow == RedlineFlags::ShowInsert) + pFnc = &SwRangeRedline::Hide; + + // Try to merge identical ones + for( SwRedlineTable::size_type n = 1; n < mpRedlineTable->size(); ++n ) + { + SwRangeRedline* pPrev = (*mpRedlineTable)[ n-1 ], + * pCur = (*mpRedlineTable)[ n ]; + const SwPosition* pPrevStt = pPrev->Start(), + * pPrevEnd = pPrevStt == pPrev->GetPoint() + ? pPrev->GetMark() : pPrev->GetPoint(); + const SwPosition* pCurStt = pCur->Start(), + * pCurEnd = pCurStt == pCur->GetPoint() + ? pCur->GetMark() : pCur->GetPoint(); + if( *pPrevEnd == *pCurStt && pPrev->CanCombine( *pCur ) && + pPrevStt->nNode.GetNode().StartOfSectionNode() == + pCurEnd->nNode.GetNode().StartOfSectionNode() && + !pCurEnd->nNode.GetNode().StartOfSectionNode()->IsTableNode() ) + { + // we then can merge them + SwRedlineTable::size_type nPrevIndex = n-1; + pPrev->Show(0, nPrevIndex); + pCur->Show(0, n); + + pPrev->SetEnd( *pCur->End() ); + mpRedlineTable->DeleteAndDestroy( n ); + --n; + if( pFnc ) + (pPrev->*pFnc)(0, nPrevIndex); + } + } + CHECK_REDLINE( *this ) + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::SplitRedline( const SwPaM& rRange ) +{ + bool bChg = false; + SwRedlineTable::size_type n = 0; + const SwPosition* pStt = rRange.Start(); + const SwPosition* pEnd = rRange.End(); + GetRedline( *pStt, &n ); + for ( ; n < mpRedlineTable->size(); ++n) + { + SwRangeRedline * pRedline = (*mpRedlineTable)[ n ]; + SwPosition *const pRedlineStart = pRedline->Start(); + SwPosition *const pRedlineEnd = pRedline->End(); + if (*pRedlineStart <= *pStt && *pStt <= *pRedlineEnd && + *pRedlineStart <= *pEnd && *pEnd <= *pRedlineEnd) + { + bChg = true; + int nn = 0; + if (*pStt == *pRedlineStart) + nn += 1; + if (*pEnd == *pRedlineEnd) + nn += 2; + + SwRangeRedline* pNew = nullptr; + switch( nn ) + { + case 0: + pNew = new SwRangeRedline( *pRedline ); + pRedline->SetEnd( *pStt, pRedlineEnd ); + pNew->SetStart( *pEnd ); + break; + + case 1: + *pRedlineStart = *pEnd; + break; + + case 2: + *pRedlineEnd = *pStt; + break; + + case 3: + pRedline->InvalidateRange(SwRangeRedline::Invalidation::Remove); + mpRedlineTable->DeleteAndDestroy( n-- ); + pRedline = nullptr; + break; + } + if (pRedline && !pRedline->HasValidRange()) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedline, n ); + } + if( pNew ) + mpRedlineTable->Insert( pNew, n ); + } + else if (*pEnd < *pRedlineStart) + break; + } + return bChg; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::DeleteRedline( const SwPaM& rRange, bool bSaveInUndo, + RedlineType nDelType ) +{ + if( !rRange.HasMark() || *rRange.GetMark() == *rRange.GetPoint() ) + return false; + + bool bChg = false; + + if (bSaveInUndo && m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo(new SwUndoRedline( SwUndoId::REDLINE, rRange )); + if( pUndo->GetRedlSaveCount() ) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + const SwPosition* pStt = rRange.Start(), + * pEnd = pStt == rRange.GetPoint() ? rRange.GetMark() + : rRange.GetPoint(); + SwRedlineTable::size_type n = 0; + GetRedline( *pStt, &n ); + for( ; n < mpRedlineTable->size() ; ++n ) + { + SwRangeRedline* pRedl = (*mpRedlineTable)[ n ]; + if( RedlineType::Any != nDelType && nDelType != pRedl->GetType() ) + continue; + + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark() + : pRedl->GetPoint(); + switch( ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ) ) + { + case SwComparePosition::Equal: + case SwComparePosition::Outside: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + mpRedlineTable->DeleteAndDestroy( n-- ); + bChg = true; + break; + + case SwComparePosition::OverlapBefore: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + pRedl->SetStart( *pEnd, pRStt ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + break; + + case SwComparePosition::OverlapBehind: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + pRedl->SetEnd( *pStt, pREnd ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + } + break; + + case SwComparePosition::Inside: + { + // this one needs to be split + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + if( *pRStt == *pStt ) + { + pRedl->SetStart( *pEnd, pRStt ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + } + else + { + SwRangeRedline* pCpy; + if( *pREnd != *pEnd ) + { + pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + pCpy->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + else + pCpy = nullptr; + pRedl->SetEnd( *pStt, pREnd ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + } + if( pCpy ) + mpRedlineTable->Insert( pCpy ); + } + } + break; + + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + n = mpRedlineTable->size(); + break; + default: + break; + } + } + + if( bChg ) + m_rDoc.getIDocumentState().SetModified(); + + return bChg; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::DeleteRedline( const SwStartNode& rNode, bool bSaveInUndo, + RedlineType nDelType ) +{ + SwPaM aTemp(*rNode.EndOfSectionNode(), rNode); + return DeleteRedline(aTemp, bSaveInUndo, nDelType); +} + +SwRedlineTable::size_type DocumentRedlineManager::GetRedlinePos( const SwNode& rNd, RedlineType nType ) const +{ + const sal_uLong nNdIdx = rNd.GetIndex(); + for( SwRedlineTable::size_type n = 0; n < mpRedlineTable->size() ; ++n ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + sal_uLong nPt = pTmp->GetPoint()->nNode.GetIndex(), + nMk = pTmp->GetMark()->nNode.GetIndex(); + if( nPt < nMk ) { long nTmp = nMk; nMk = nPt; nPt = nTmp; } + + if( ( RedlineType::Any == nType || nType == pTmp->GetType()) && + nMk <= nNdIdx && nNdIdx <= nPt ) + return n; + + if( nMk > nNdIdx ) + break; + } + return SwRedlineTable::npos; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +const SwRangeRedline* DocumentRedlineManager::GetRedline( const SwPosition& rPos, + SwRedlineTable::size_type* pFndPos ) const +{ + SwRedlineTable::size_type nO = mpRedlineTable->size(), nM, nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + nM = nU + ( nO - nU ) / 2; + const SwRangeRedline* pRedl = (*mpRedlineTable)[ nM ]; + const SwPosition* pStt = pRedl->Start(); + const SwPosition* pEnd = pStt == pRedl->GetPoint() + ? pRedl->GetMark() + : pRedl->GetPoint(); + if( pEnd == pStt + ? *pStt == rPos + : ( *pStt <= rPos && rPos < *pEnd ) ) + { + while( nM && rPos == *(*mpRedlineTable)[ nM - 1 ]->End() && + rPos == *(*mpRedlineTable)[ nM - 1 ]->Start() ) + { + --nM; + pRedl = (*mpRedlineTable)[ nM ]; + } + // if there are format and insert changes in the same position + // show insert change first. + // since the redlines are sorted by position, only check the redline + // before and after the current redline + if( RedlineType::Format == pRedl->GetType() ) + { + if( nM && rPos >= *(*mpRedlineTable)[ nM - 1 ]->Start() && + rPos <= *(*mpRedlineTable)[ nM - 1 ]->End() && + ( RedlineType::Insert == (*mpRedlineTable)[ nM - 1 ]->GetType() ) ) + { + --nM; + pRedl = (*mpRedlineTable)[ nM ]; + } + else if( ( nM + 1 ) <= nO && rPos >= *(*mpRedlineTable)[ nM + 1 ]->Start() && + rPos <= *(*mpRedlineTable)[ nM + 1 ]->End() && + ( RedlineType::Insert == (*mpRedlineTable)[ nM + 1 ]->GetType() ) ) + { + ++nM; + pRedl = (*mpRedlineTable)[ nM ]; + } + } + + if( pFndPos ) + *pFndPos = nM; + return pRedl; + } + else if( *pEnd <= rPos ) + nU = nM + 1; + else if( nM == 0 ) + { + if( pFndPos ) + *pFndPos = nU; + return nullptr; + } + else + nO = nM - 1; + } + } + if( pFndPos ) + *pFndPos = nU; + return nullptr; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::AcceptRedline( SwRedlineTable::size_type nPos, bool bCallDelete ) +{ + bool bRet = false; + + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + SwRangeRedline* pTmp = (*mpRedlineTable)[ nPos ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, pTmp->GetDescr()); + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::ACCEPT_REDLINE, &aRewriter); + } + + int nLoopCnt = 2; + sal_uInt16 nSeqNo = pTmp->GetSeqNo(); + + do { + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique(*pTmp) ); + } + + bRet |= lcl_AcceptRedline( *mpRedlineTable, nPos, bCallDelete ); + + if( nSeqNo ) + { + if( SwRedlineTable::npos == nPos ) + nPos = 0; + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? mpRedlineTable->FindNextSeqNo( nSeqNo, nPos ) + : mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ); + if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && + SwRedlineTable::npos != ( nFndPos = + mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ))) ) + { + nPos = nFndPos; + pTmp = (*mpRedlineTable)[ nPos ]; + } + else + nLoopCnt = 0; + } + else + nLoopCnt = 0; + + } while( nLoopCnt ); + + if( bRet ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + } + return bRet; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::AcceptRedline( const SwPaM& rPam, bool bCallDelete ) +{ + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + // The Selection is only in the ContentSection. If there are Redlines + // to Non-ContentNodes before or after that, then the Selections + // expand to them. + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + lcl_AdjustRedlineRange( aPam ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::ACCEPT_REDLINE, nullptr ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique( aPam )); + } + + int nRet = lcl_AcceptRejectRedl( lcl_AcceptRedline, *mpRedlineTable, + bCallDelete, aPam ); + if( nRet > 0 ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + OUString aTmpStr; + + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(nRet)); + aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::ACCEPT_REDLINE, &aRewriter ); + } + return nRet != 0; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +void DocumentRedlineManager::AcceptRedlineParagraphFormatting( const SwPaM &rPam ) +{ + const SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + + const sal_uLong nSttIdx = pStt->nNode.GetIndex(); + const sal_uLong nEndIdx = pEnd->nNode.GetIndex(); + + for( SwRedlineTable::size_type n = 0; n < mpRedlineTable->size() ; ++n ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + sal_uLong nPt = pTmp->GetPoint()->nNode.GetIndex(), + nMk = pTmp->GetMark()->nNode.GetIndex(); + if( nPt < nMk ) { long nTmp = nMk; nMk = nPt; nPt = nTmp; } + + if( RedlineType::ParagraphFormat == pTmp->GetType() && + ( (nSttIdx <= nMk && nMk <= nEndIdx) || (nSttIdx <= nPt && nPt <= nEndIdx) ) ) + AcceptRedline( n, false ); + + if( nMk > nEndIdx ) + break; + } +} + +bool DocumentRedlineManager::RejectRedline( SwRedlineTable::size_type nPos, bool bCallDelete ) +{ + bool bRet = false; + + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + SwRangeRedline* pTmp = (*mpRedlineTable)[ nPos ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, pTmp->GetDescr()); + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::REJECT_REDLINE, &aRewriter); + } + + int nLoopCnt = 2; + sal_uInt16 nSeqNo = pTmp->GetSeqNo(); + + do { + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique( *pTmp ) ); + } + + bRet |= lcl_RejectRedline( *mpRedlineTable, nPos, bCallDelete ); + + if( nSeqNo ) + { + if( SwRedlineTable::npos == nPos ) + nPos = 0; + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? mpRedlineTable->FindNextSeqNo( nSeqNo, nPos ) + : mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ); + if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && + SwRedlineTable::npos != ( nFndPos = + mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ))) ) + { + nPos = nFndPos; + pTmp = (*mpRedlineTable)[ nPos ]; + } + else + nLoopCnt = 0; + } + else + nLoopCnt = 0; + + } while( nLoopCnt ); + + if( bRet ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + } + return bRet; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::RejectRedline( const SwPaM& rPam, bool bCallDelete ) +{ + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + // The Selection is only in the ContentSection. If there are Redlines + // to Non-ContentNodes before or after that, then the Selections + // expand to them. + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + lcl_AdjustRedlineRange( aPam ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REJECT_REDLINE, nullptr ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(aPam) ); + } + + int nRet = lcl_AcceptRejectRedl( lcl_RejectRedline, *mpRedlineTable, + bCallDelete, aPam ); + if( nRet > 0 ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + OUString aTmpStr; + + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(nRet)); + aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REJECT_REDLINE, &aRewriter ); + } + + return nRet != 0; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +void DocumentRedlineManager::AcceptAllRedline(bool bAccept) +{ + bool bSuccess = true; + OUString sUndoStr; + IDocumentUndoRedo& rUndoMgr = m_rDoc.GetIDocumentUndoRedo(); + + if (mpRedlineTable->size() > 1) + { + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(mpRedlineTable->size())); + sUndoStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, sUndoStr); + rUndoMgr.StartUndo(bAccept ? SwUndoId::ACCEPT_REDLINE : SwUndoId::REJECT_REDLINE, &aRewriter); + } + + while (!mpRedlineTable->empty() && bSuccess) + { + if (bAccept) + bSuccess = AcceptRedline(mpRedlineTable->size() - 1, true); + else + bSuccess = RejectRedline(mpRedlineTable->size() - 1, true); + } + + if (!sUndoStr.isEmpty()) + { + rUndoMgr.EndUndo(SwUndoId::EMPTY, nullptr); + } +} + +const SwRangeRedline* DocumentRedlineManager::SelNextRedline( SwPaM& rPam ) const +{ + rPam.DeleteMark(); + rPam.SetMark(); + + SwPosition& rSttPos = *rPam.GetPoint(); + SwPosition aSavePos( rSttPos ); + bool bRestart; + + // If the starting position points to the last valid ContentNode, + // we take the next Redline in any case. + SwRedlineTable::size_type n = 0; + const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n ); + if( pFnd ) + { + const SwPosition* pEnd = pFnd->End(); + if( !pEnd->nNode.GetNode().IsContentNode() ) + { + SwNodeIndex aTmp( pEnd->nNode ); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aTmp ); + if( !pCNd || ( aTmp == rSttPos.nNode && + pCNd->Len() == rSttPos.nContent.GetIndex() )) + pFnd = nullptr; + } + if( pFnd ) + rSttPos = *pFnd->End(); + } + + do { + bRestart = false; + + for( ; !pFnd && n < mpRedlineTable->size(); ++n ) + { + pFnd = (*mpRedlineTable)[ n ]; + if( pFnd->HasMark() && pFnd->IsVisible() ) + { + *rPam.GetMark() = *pFnd->Start(); + rSttPos = *pFnd->End(); + break; + } + else + pFnd = nullptr; + } + + if( pFnd ) + { + // Merge all of the same type and author that are + // consecutive into one Selection. + const SwPosition* pPrevEnd = pFnd->End(); + while( ++n < mpRedlineTable->size() ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + const SwPosition *pRStt; + if( pFnd->GetType() != pTmp->GetType() || + pFnd->GetAuthor() != pTmp->GetAuthor() ) + break; + pRStt = pTmp->Start(); + if( *pPrevEnd == *pRStt || IsPrevPos( *pPrevEnd, *pRStt ) ) + { + pPrevEnd = pTmp->End(); + rSttPos = *pPrevEnd; + } + else + break; + } + } + } + + if( pFnd ) + { + const SwRangeRedline* pSaveFnd = pFnd; + + SwContentNode* pCNd; + SwNodeIndex* pIdx = &rPam.GetMark()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = m_rDoc.GetNodes().GoNextSection( pIdx ); + if( pCNd ) + { + if( *pIdx <= rPam.GetPoint()->nNode ) + rPam.GetMark()->nContent.Assign( pCNd, 0 ); + else + pFnd = nullptr; + } + } + + if( pFnd ) + { + pIdx = &rPam.GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = SwNodes::GoPrevSection( pIdx ); + if( pCNd ) + { + if( *pIdx >= rPam.GetMark()->nNode ) + rPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + } + + if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) + { + if( n < mpRedlineTable->size() ) + { + bRestart = true; + *rPam.GetPoint() = *pSaveFnd->End(); + } + else + { + rPam.DeleteMark(); + *rPam.GetPoint() = aSavePos; + } + pFnd = nullptr; + } + } + } while( bRestart ); + + return pFnd; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +const SwRangeRedline* DocumentRedlineManager::SelPrevRedline( SwPaM& rPam ) const +{ + rPam.DeleteMark(); + rPam.SetMark(); + + SwPosition& rSttPos = *rPam.GetPoint(); + SwPosition aSavePos( rSttPos ); + bool bRestart; + + // If the starting position points to the last valid ContentNode, + // we take the previous Redline in any case. + SwRedlineTable::size_type n = 0; + const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n, false ); + if( pFnd ) + { + const SwPosition* pStt = pFnd->Start(); + if( !pStt->nNode.GetNode().IsContentNode() ) + { + SwNodeIndex aTmp( pStt->nNode ); + SwContentNode* pCNd = m_rDoc.GetNodes().GoNextSection( &aTmp ); + if( !pCNd || ( aTmp == rSttPos.nNode && + !rSttPos.nContent.GetIndex() )) + pFnd = nullptr; + } + if( pFnd ) + rSttPos = *pFnd->Start(); + } + + do { + bRestart = false; + + while( !pFnd && 0 < n ) + { + pFnd = (*mpRedlineTable)[ --n ]; + if( pFnd->HasMark() && pFnd->IsVisible() ) + { + *rPam.GetMark() = *pFnd->End(); + rSttPos = *pFnd->Start(); + } + else + pFnd = nullptr; + } + + if( pFnd ) + { + // Merge all of the same type and author that are + // consecutive into one Selection. + const SwPosition* pNextStt = pFnd->Start(); + while( 0 < n ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ --n ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + const SwPosition *pREnd; + if( pFnd->GetType() == pTmp->GetType() && + pFnd->GetAuthor() == pTmp->GetAuthor() && + ( *pNextStt == *( pREnd = pTmp->End() ) || + IsPrevPos( *pREnd, *pNextStt )) ) + { + pNextStt = pTmp->Start(); + rSttPos = *pNextStt; + } + else + { + ++n; + break; + } + } + } + } + + if( pFnd ) + { + const SwRangeRedline* pSaveFnd = pFnd; + + SwContentNode* pCNd; + SwNodeIndex* pIdx = &rPam.GetMark()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = SwNodes::GoPrevSection( pIdx ); + if( pCNd ) + { + if( *pIdx >= rPam.GetPoint()->nNode ) + rPam.GetMark()->nContent.Assign( pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + + if( pFnd ) + { + pIdx = &rPam.GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = m_rDoc.GetNodes().GoNextSection( pIdx ); + if( pCNd ) + { + if( *pIdx <= rPam.GetMark()->nNode ) + rPam.GetPoint()->nContent.Assign( pCNd, 0 ); + else + pFnd = nullptr; + } + } + } + + if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) + { + if( n ) + { + bRestart = true; + *rPam.GetPoint() = *pSaveFnd->Start(); + } + else + { + rPam.DeleteMark(); + *rPam.GetPoint() = aSavePos; + } + pFnd = nullptr; + } + } + } while( bRestart ); + + return pFnd; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +// Set comment at the Redline +bool DocumentRedlineManager::SetRedlineComment( const SwPaM& rPaM, const OUString& rS ) +{ + bool bRet = false; + const SwPosition* pStt = rPaM.Start(), + * pEnd = pStt == rPaM.GetPoint() ? rPaM.GetMark() + : rPaM.GetPoint(); + SwRedlineTable::size_type n = 0; + if( GetRedlineTable().FindAtPosition( *pStt, n ) ) + { + for( ; n < mpRedlineTable->size(); ++n ) + { + bRet = true; + SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + if( pStt != pEnd && *pTmp->Start() > *pEnd ) + break; + + pTmp->SetComment( rS ); + if( *pTmp->End() >= *pEnd ) + break; + } + } + if( bRet ) + m_rDoc.getIDocumentState().SetModified(); + + return bRet; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +// Create a new author if necessary +std::size_t DocumentRedlineManager::GetRedlineAuthor() +{ + return SW_MOD()->GetRedlineAuthor(); +} + +/// Insert new author into the Table for the Readers etc. +std::size_t DocumentRedlineManager::InsertRedlineAuthor( const OUString& rNew ) +{ + return SW_MOD()->InsertRedlineAuthor(rNew); +} + +void DocumentRedlineManager::UpdateRedlineAttr() +{ + const SwRedlineTable& rTable = GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + if( pRedl->IsVisible() ) + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +const uno::Sequence & DocumentRedlineManager::GetRedlinePassword() const +{ + return maRedlinePasswd; +} + +void DocumentRedlineManager::SetRedlinePassword( + /*[in]*/const uno::Sequence & rNewPassword) +{ + maRedlinePasswd = rNewPassword; + m_rDoc.getIDocumentState().SetModified(); +} + +/// Set comment text for the Redline, which is inserted later on via +/// AppendRedline. Is used by Autoformat. +/// A null pointer resets the mode. The pointer is not copied, so it +/// needs to stay valid! +void DocumentRedlineManager::SetAutoFormatRedlineComment( const OUString* pText, sal_uInt16 nSeqNo ) +{ + m_rDoc.SetAutoFormatRedline( nullptr != pText ); + if( pText ) + { + mpAutoFormatRedlnComment.reset( new OUString( *pText ) ); + } + else + { + mpAutoFormatRedlnComment.reset(); + } + + mnAutoFormatRedlnCommentNo = nSeqNo; +} + +DocumentRedlineManager::~DocumentRedlineManager() +{ +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentSettingManager.cxx b/sw/source/core/doc/DocumentSettingManager.cxx new file mode 100644 index 000000000..ca68d3e30 --- /dev/null +++ b/sw/source/core/doc/DocumentSettingManager.cxx @@ -0,0 +1,964 @@ +/* -*- 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 + +/* IDocumentSettingAccess */ + +sw::DocumentSettingManager::DocumentSettingManager(SwDoc &rDoc) + :m_rDoc(rDoc), + mnLinkUpdMode( GLOBALSETTING ), + meFieldUpdMode( AUTOUPD_GLOBALSETTING ), + meChrCmprType( CharCompressType::NONE ), + mn32DummyCompatibilityOptions1(0), + mn32DummyCompatibilityOptions2(0), + mbHTMLMode(false), + mbIsGlobalDoc(false), + mbGlblDocSaveLinks(false), + mbIsLabelDoc(false), + mbPurgeOLE(true), + mbKernAsianPunctuation(false), + + // COMPATIBILITY FLAGS START + + mbAddFlyOffsets(false), + mbAddVerticalFlyOffsets(false), + mbUseHiResolutionVirtualDevice(true), + mbMathBaselineAlignment(false), // default for *old* documents is 'off' + mbStylesNoDefault(false), + mbFloattableNomargins(false), + mEmbedFonts(false), + mEmbedUsedFonts(false), + mEmbedLatinScriptFonts(true), + mEmbedAsianScriptFonts(true), + mEmbedComplexScriptFonts(true), + mEmbedSystemFonts(false), + mbOldNumbering(false), + mbIgnoreFirstLineIndentInNumbering(false), + mbDoNotResetParaAttrsForNumFont(false), + mbTableRowKeep(false), + mbIgnoreTabsAndBlanksForLineCalculation(false), + mbDoNotCaptureDrawObjsOnPage(false), + mbClipAsCharacterAnchoredWriterFlyFrames(false), + mbUnixForceZeroExtLeading(false), + mbTabRelativeToIndent(true), + mbProtectForm(false), // i#78591# + mbMsWordCompTrailingBlanks(false), // tdf#104349 tdf#104668 + mbMsWordCompMinLineHeightByFly(false), + mbInvertBorderSpacing (false), + mbCollapseEmptyCellPara(true), + mbTabAtLeftIndentForParagraphsInList(false), //#i89181# + mbSmallCapsPercentage66(false), + mbTabOverflow(true), + mbUnbreakableNumberings(false), + mbClippedPictures(false), + mbBackgroundParaOverDrawings(false), + mbTabOverMargin(false), + mbTreatSingleColumnBreakAsPageBreak(false), + mbSurroundTextWrapSmall(false), + mbPropLineSpacingShrinksFirstLine(true), + mbSubtractFlys(false), + mApplyParagraphMarkFormatToNumbering(false), + mbLastBrowseMode( false ), + mbDisableOffPagePositioning ( false ), + mbProtectBookmarks(false), + mbProtectFields(false), + mbHeaderSpacingBelowLastPara(false) + + // COMPATIBILITY FLAGS END +{ + // COMPATIBILITY FLAGS START + + // Note: Any non-hidden compatibility flag should obtain its default + // by asking SvtCompatibilityOptions, see below. + + if (!utl::ConfigManager::IsFuzzing()) + { + const SvtCompatibilityOptions aOptions; + mbParaSpaceMax = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddSpacing ); + mbParaSpaceMaxAtPages = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddSpacingAtPages ); + mbTabCompat = !aOptions.GetDefault( SvtCompatibilityEntry::Index::UseOurTabStops ); + mbUseVirtualDevice = !aOptions.GetDefault( SvtCompatibilityEntry::Index::UsePrtMetrics ); + mbAddExternalLeading = !aOptions.GetDefault( SvtCompatibilityEntry::Index::NoExtLeading ); + mbOldLineSpacing = aOptions.GetDefault( SvtCompatibilityEntry::Index::UseLineSpacing ); + mbAddParaSpacingToTableCells = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddTableSpacing ); + mbAddParaLineSpacingToTableCells = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddTableLineSpacing ); + mbUseFormerObjectPos = aOptions.GetDefault( SvtCompatibilityEntry::Index::UseObjectPositioning ); + mbUseFormerTextWrapping = aOptions.GetDefault( SvtCompatibilityEntry::Index::UseOurTextWrapping ); + mbConsiderWrapOnObjPos = aOptions.GetDefault( SvtCompatibilityEntry::Index::ConsiderWrappingStyle ); + mbDoNotJustifyLinesWithManualBreak = !aOptions.GetDefault( SvtCompatibilityEntry::Index::ExpandWordSpace ); + mbProtectForm = aOptions.GetDefault( SvtCompatibilityEntry::Index::ProtectForm ); + mbMsWordCompTrailingBlanks = aOptions.GetDefault( SvtCompatibilityEntry::Index::MsWordTrailingBlanks ); + mbSubtractFlys = aOptions.GetDefault( SvtCompatibilityEntry::Index::SubtractFlysAnchoredAtFlys ); + mbEmptyDbFieldHidesPara + = aOptions.GetDefault(SvtCompatibilityEntry::Index::EmptyDbFieldHidesPara); + } + else + { + mbParaSpaceMax = false; + mbParaSpaceMaxAtPages = false; + mbTabCompat = true; + mbUseVirtualDevice = true; + mbAddExternalLeading = true; + mbOldLineSpacing = false; + mbAddParaSpacingToTableCells = false; + mbAddParaLineSpacingToTableCells = false; + mbUseFormerObjectPos = false; + mbUseFormerTextWrapping = false; + mbConsiderWrapOnObjPos = false; + mbDoNotJustifyLinesWithManualBreak = true; + mbProtectForm = false; + mbMsWordCompTrailingBlanks = false; + mbSubtractFlys = false; + mbEmptyDbFieldHidesPara = true; + } + + // COMPATIBILITY FLAGS END + +} + + +sw::DocumentSettingManager::~DocumentSettingManager() +{ +} + +/* IDocumentSettingAccess */ +bool sw::DocumentSettingManager::get(/*[in]*/ DocumentSettingId id) const +{ + switch (id) + { + // COMPATIBILITY FLAGS START + case DocumentSettingId::PARA_SPACE_MAX: return mbParaSpaceMax; //(n8Dummy1 & DUMMY_PARASPACEMAX); + case DocumentSettingId::PARA_SPACE_MAX_AT_PAGES: return mbParaSpaceMaxAtPages; //(n8Dummy1 & DUMMY_PARASPACEMAX_AT_PAGES); + case DocumentSettingId::TAB_COMPAT: return mbTabCompat; //(n8Dummy1 & DUMMY_TAB_COMPAT); + case DocumentSettingId::ADD_FLY_OFFSETS: return mbAddFlyOffsets; //(n8Dummy2 & DUMMY_ADD_FLY_OFFSETS); + case DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS: return mbAddVerticalFlyOffsets; + case DocumentSettingId::ADD_EXT_LEADING: return mbAddExternalLeading; //(n8Dummy2 & DUMMY_ADD_EXTERNAL_LEADING); + case DocumentSettingId::USE_VIRTUAL_DEVICE: return mbUseVirtualDevice; //(n8Dummy1 & DUMMY_USE_VIRTUAL_DEVICE); + case DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE: return mbUseHiResolutionVirtualDevice; //(n8Dummy2 & DUMMY_USE_HIRES_VIR_DEV); + case DocumentSettingId::OLD_NUMBERING: return mbOldNumbering; + case DocumentSettingId::OLD_LINE_SPACING: return mbOldLineSpacing; + case DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS: return mbAddParaSpacingToTableCells; + case DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS: return mbAddParaLineSpacingToTableCells; + case DocumentSettingId::USE_FORMER_OBJECT_POS: return mbUseFormerObjectPos; + case DocumentSettingId::USE_FORMER_TEXT_WRAPPING: return mbUseFormerTextWrapping; + case DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION: return mbConsiderWrapOnObjPos; + case DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK: return mbDoNotJustifyLinesWithManualBreak; + case DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING: return mbIgnoreFirstLineIndentInNumbering; + case DocumentSettingId::TABLE_ROW_KEEP: return mbTableRowKeep; + case DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION: return mbIgnoreTabsAndBlanksForLineCalculation; + case DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE: return mbDoNotCaptureDrawObjsOnPage; + // #i68949# + case DocumentSettingId::CLIP_AS_CHARACTER_ANCHORED_WRITER_FLY_FRAME: return mbClipAsCharacterAnchoredWriterFlyFrames; + case DocumentSettingId::UNIX_FORCE_ZERO_EXT_LEADING: return mbUnixForceZeroExtLeading; + case DocumentSettingId::TABS_RELATIVE_TO_INDENT : return mbTabRelativeToIndent; + case DocumentSettingId::PROTECT_FORM: return mbProtectForm; + // tdf#104349 tdf#104668 + case DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS: return mbMsWordCompTrailingBlanks; + case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: return mbMsWordCompMinLineHeightByFly; + // #i89181# + case DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: return mbTabAtLeftIndentForParagraphsInList; + case DocumentSettingId::INVERT_BORDER_SPACING: return mbInvertBorderSpacing; + case DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA: return mbCollapseEmptyCellPara; + case DocumentSettingId::SMALL_CAPS_PERCENTAGE_66: return mbSmallCapsPercentage66; + case DocumentSettingId::TAB_OVERFLOW: return mbTabOverflow; + case DocumentSettingId::UNBREAKABLE_NUMBERINGS: return mbUnbreakableNumberings; + case DocumentSettingId::CLIPPED_PICTURES: return mbClippedPictures; + case DocumentSettingId::BACKGROUND_PARA_OVER_DRAWINGS: return mbBackgroundParaOverDrawings; + case DocumentSettingId::TAB_OVER_MARGIN: return mbTabOverMargin; + case DocumentSettingId::TREAT_SINGLE_COLUMN_BREAK_AS_PAGE_BREAK: return mbTreatSingleColumnBreakAsPageBreak; + case DocumentSettingId::SURROUND_TEXT_WRAP_SMALL: return mbSurroundTextWrapSmall; + case DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE: return mbPropLineSpacingShrinksFirstLine; + case DocumentSettingId::SUBTRACT_FLYS: return mbSubtractFlys; + + case DocumentSettingId::BROWSE_MODE: return mbLastBrowseMode; // Attention: normally the SwViewShell has to be asked! + case DocumentSettingId::HTML_MODE: return mbHTMLMode; + case DocumentSettingId::GLOBAL_DOCUMENT: return mbIsGlobalDoc; + case DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS: return mbGlblDocSaveLinks; + case DocumentSettingId::LABEL_DOCUMENT: return mbIsLabelDoc; + case DocumentSettingId::PURGE_OLE: return mbPurgeOLE; + case DocumentSettingId::KERN_ASIAN_PUNCTUATION: return mbKernAsianPunctuation; + case DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT: return mbDoNotResetParaAttrsForNumFont; + case DocumentSettingId::MATH_BASELINE_ALIGNMENT: return mbMathBaselineAlignment; + case DocumentSettingId::STYLES_NODEFAULT: return mbStylesNoDefault; + case DocumentSettingId::FLOATTABLE_NOMARGINS: return mbFloattableNomargins; + case DocumentSettingId::EMBED_FONTS: return mEmbedFonts; + case DocumentSettingId::EMBED_USED_FONTS: return mEmbedUsedFonts; + case DocumentSettingId::EMBED_LATIN_SCRIPT_FONTS: return mEmbedLatinScriptFonts; + case DocumentSettingId::EMBED_ASIAN_SCRIPT_FONTS: return mEmbedAsianScriptFonts; + case DocumentSettingId::EMBED_COMPLEX_SCRIPT_FONTS: return mEmbedComplexScriptFonts; + case DocumentSettingId::EMBED_SYSTEM_FONTS: return mEmbedSystemFonts; + case DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING: return mApplyParagraphMarkFormatToNumbering; + case DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING: return mbDisableOffPagePositioning; + case DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA: return mbEmptyDbFieldHidesPara; + case DocumentSettingId::CONTINUOUS_ENDNOTES: return mbContinuousEndnotes; + case DocumentSettingId::PROTECT_BOOKMARKS: return mbProtectBookmarks; + case DocumentSettingId::PROTECT_FIELDS: return mbProtectFields; + case DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA: return mbHeaderSpacingBelowLastPara; + default: + OSL_FAIL("Invalid setting id"); + } + return false; +} + +void sw::DocumentSettingManager::set(/*[in]*/ DocumentSettingId id, /*[in]*/ bool value) +{ + switch (id) + { + // COMPATIBILITY FLAGS START + case DocumentSettingId::PARA_SPACE_MAX: + mbParaSpaceMax = value; + break; + case DocumentSettingId::PARA_SPACE_MAX_AT_PAGES: + mbParaSpaceMaxAtPages = value; + break; + case DocumentSettingId::TAB_COMPAT: + mbTabCompat = value; + break; + case DocumentSettingId::ADD_FLY_OFFSETS: + mbAddFlyOffsets = value; + break; + case DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS: + mbAddVerticalFlyOffsets = value; + break; + case DocumentSettingId::ADD_EXT_LEADING: + mbAddExternalLeading = value; + break; + case DocumentSettingId::USE_VIRTUAL_DEVICE: + mbUseVirtualDevice = value; + break; + case DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE: + mbUseHiResolutionVirtualDevice = value; + break; + case DocumentSettingId::OLD_NUMBERING: + if (mbOldNumbering != value) + { + mbOldNumbering = value; + + const SwNumRuleTable& rNmTable = m_rDoc.GetNumRuleTable(); + for( SwNumRuleTable::size_type n = 0; n < rNmTable.size(); ++n ) + rNmTable[n]->SetInvalidRule(true); + + m_rDoc.UpdateNumRule(); + + SwNumRule *pOutlineRule = m_rDoc.GetOutlineNumRule(); + if (pOutlineRule) + { + pOutlineRule->Validate(); + // counting of phantoms depends on + pOutlineRule->SetCountPhantoms( !mbOldNumbering ); + } + } + break; + case DocumentSettingId::OLD_LINE_SPACING: + mbOldLineSpacing = value; + break; + case DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS: + mbAddParaSpacingToTableCells = value; + break; + case DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS: + mbAddParaLineSpacingToTableCells = value; + break; + case DocumentSettingId::USE_FORMER_OBJECT_POS: + mbUseFormerObjectPos = value; + break; + case DocumentSettingId::USE_FORMER_TEXT_WRAPPING: + mbUseFormerTextWrapping = value; + break; + case DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION: + mbConsiderWrapOnObjPos = value; + break; + case DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK: + mbDoNotJustifyLinesWithManualBreak = value; + break; + case DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING: + mbIgnoreFirstLineIndentInNumbering = value; + break; + + case DocumentSettingId::TABLE_ROW_KEEP: + mbTableRowKeep = value; + break; + + case DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION: + mbIgnoreTabsAndBlanksForLineCalculation = value; + break; + + case DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE: + mbDoNotCaptureDrawObjsOnPage = value; + break; + + // #i68949# + case DocumentSettingId::CLIP_AS_CHARACTER_ANCHORED_WRITER_FLY_FRAME: + mbClipAsCharacterAnchoredWriterFlyFrames = value; + break; + + case DocumentSettingId::UNIX_FORCE_ZERO_EXT_LEADING: + mbUnixForceZeroExtLeading = value; + break; + + case DocumentSettingId::PROTECT_FORM: + mbProtectForm = value; + break; + + // tdf#140349 + case DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS: + mbMsWordCompTrailingBlanks = value; + break; + + case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: + mbMsWordCompMinLineHeightByFly = value; + break; + + case DocumentSettingId::TABS_RELATIVE_TO_INDENT: + mbTabRelativeToIndent = value; + break; + // #i89181# + case DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: + mbTabAtLeftIndentForParagraphsInList = value; + break; + + case DocumentSettingId::INVERT_BORDER_SPACING: + mbInvertBorderSpacing = value; + break; + + case DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA: + mbCollapseEmptyCellPara = value; + break; + + case DocumentSettingId::SMALL_CAPS_PERCENTAGE_66: + mbSmallCapsPercentage66 = value; + break; + + case DocumentSettingId::TAB_OVERFLOW: + mbTabOverflow = value; + break; + + case DocumentSettingId::UNBREAKABLE_NUMBERINGS: + mbUnbreakableNumberings = value; + break; + + case DocumentSettingId::CLIPPED_PICTURES: + mbClippedPictures = value; + break; + + case DocumentSettingId::BACKGROUND_PARA_OVER_DRAWINGS: + mbBackgroundParaOverDrawings = value; + break; + + case DocumentSettingId::TAB_OVER_MARGIN: + mbTabOverMargin = value; + break; + + case DocumentSettingId::TREAT_SINGLE_COLUMN_BREAK_AS_PAGE_BREAK: + mbTreatSingleColumnBreakAsPageBreak = value; + break; + + case DocumentSettingId::SURROUND_TEXT_WRAP_SMALL: + mbSurroundTextWrapSmall = value; + break; + + case DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE: + mbPropLineSpacingShrinksFirstLine = value; + break; + + case DocumentSettingId::SUBTRACT_FLYS: + mbSubtractFlys = value; + break; + + // COMPATIBILITY FLAGS END + + case DocumentSettingId::BROWSE_MODE: //can be used temporary (load/save) when no SwViewShell is available + mbLastBrowseMode = value; + break; + + case DocumentSettingId::HTML_MODE: + mbHTMLMode = value; + break; + + case DocumentSettingId::GLOBAL_DOCUMENT: + mbIsGlobalDoc = value; + break; + + case DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS: + mbGlblDocSaveLinks = value; + break; + + case DocumentSettingId::LABEL_DOCUMENT: + mbIsLabelDoc = value; + break; + + case DocumentSettingId::PURGE_OLE: + mbPurgeOLE = value; + break; + + case DocumentSettingId::KERN_ASIAN_PUNCTUATION: + mbKernAsianPunctuation = value; + break; + + case DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT: + mbDoNotResetParaAttrsForNumFont = value; + break; + case DocumentSettingId::MATH_BASELINE_ALIGNMENT: + mbMathBaselineAlignment = value; + break; + case DocumentSettingId::STYLES_NODEFAULT: + mbStylesNoDefault = value; + break; + case DocumentSettingId::FLOATTABLE_NOMARGINS: + mbFloattableNomargins = value; + break; + case DocumentSettingId::EMBED_FONTS: + mEmbedFonts = value; + break; + case DocumentSettingId::EMBED_USED_FONTS: + mEmbedUsedFonts = value; + break; + case DocumentSettingId::EMBED_LATIN_SCRIPT_FONTS: + mEmbedLatinScriptFonts = value; + break; + case DocumentSettingId::EMBED_ASIAN_SCRIPT_FONTS: + mEmbedAsianScriptFonts = value; + break; + case DocumentSettingId::EMBED_COMPLEX_SCRIPT_FONTS: + mEmbedComplexScriptFonts = value; + break; + case DocumentSettingId::EMBED_SYSTEM_FONTS: + mEmbedSystemFonts = value; + break; + case DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING: + mApplyParagraphMarkFormatToNumbering = value; + break; + case DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING: + mbDisableOffPagePositioning = value; + break; + case DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA: + mbEmptyDbFieldHidesPara = value; + break; + case DocumentSettingId::CONTINUOUS_ENDNOTES: + mbContinuousEndnotes = value; + break; + case DocumentSettingId::PROTECT_BOOKMARKS: + mbProtectBookmarks = value; + break; + case DocumentSettingId::PROTECT_FIELDS: + mbProtectFields = value; + break; + case DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA: + mbHeaderSpacingBelowLastPara = value; + break; + default: + OSL_FAIL("Invalid setting id"); + } +} + +const css::i18n::ForbiddenCharacters* + sw::DocumentSettingManager::getForbiddenCharacters(/*[in]*/ LanguageType nLang, /*[in]*/ bool bLocaleData ) const +{ + const css::i18n::ForbiddenCharacters* pRet = nullptr; + if (mxForbiddenCharsTable) + pRet = mxForbiddenCharsTable->GetForbiddenCharacters( nLang, false ); + if( bLocaleData && !pRet && g_pBreakIt ) + pRet = &g_pBreakIt->GetForbidden( nLang ); + return pRet; +} + +void sw::DocumentSettingManager::setForbiddenCharacters(/*[in]*/ LanguageType nLang, + /*[in]*/ const css::i18n::ForbiddenCharacters& rFChars ) +{ + if (!mxForbiddenCharsTable) + mxForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext()); + mxForbiddenCharsTable->SetForbiddenCharacters( nLang, rFChars ); + + SdrModel *pDrawModel = m_rDoc.getIDocumentDrawModelAccess().GetDrawModel(); + if( pDrawModel ) + { + pDrawModel->SetForbiddenCharsTable(mxForbiddenCharsTable); + if( !m_rDoc.IsInReading() ) + pDrawModel->ReformatAllTextObjects(); + } + + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot && !m_rDoc.IsInReading() ) + { + pTmpRoot->StartAllAction(); + for(SwRootFrame* aLayout : m_rDoc.GetAllLayouts()) + aLayout->InvalidateAllContent(SwInvalidateFlags::Size); + pTmpRoot->EndAllAction(); + } + m_rDoc.getIDocumentState().SetModified(); +} + +std::shared_ptr& sw::DocumentSettingManager::getForbiddenCharacterTable() +{ + if (!mxForbiddenCharsTable) + mxForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext()); + return mxForbiddenCharsTable; +} + +const std::shared_ptr& sw::DocumentSettingManager::getForbiddenCharacterTable() const +{ + return mxForbiddenCharsTable; +} + +sal_uInt16 sw::DocumentSettingManager::getLinkUpdateMode( /*[in]*/bool bGlobalSettings ) const +{ + sal_uInt16 nRet = mnLinkUpdMode; + if( bGlobalSettings && GLOBALSETTING == nRet ) + nRet = SW_MOD()->GetLinkUpdMode(); + return nRet; +} + +void sw::DocumentSettingManager::setLinkUpdateMode( /*[in]*/sal_uInt16 eMode ) +{ + mnLinkUpdMode = eMode; +} + +SwFieldUpdateFlags sw::DocumentSettingManager::getFieldUpdateFlags( /*[in]*/bool bGlobalSettings ) const +{ + SwFieldUpdateFlags eRet = meFieldUpdMode; + if( bGlobalSettings && AUTOUPD_GLOBALSETTING == eRet ) + eRet = SW_MOD()->GetFieldUpdateFlags(); + return eRet; +} + +void sw::DocumentSettingManager::setFieldUpdateFlags(/*[in]*/SwFieldUpdateFlags eMode ) +{ + meFieldUpdMode = eMode; +} + +CharCompressType sw::DocumentSettingManager::getCharacterCompressionType() const +{ + return meChrCmprType; +} + +void sw::DocumentSettingManager::setCharacterCompressionType( /*[in]*/CharCompressType n ) +{ + if( meChrCmprType != n ) + { + meChrCmprType = n; + + SdrModel *pDrawModel = m_rDoc.getIDocumentDrawModelAccess().GetDrawModel(); + if( pDrawModel ) + { + pDrawModel->SetCharCompressType( n ); + if( !m_rDoc.IsInReading() ) + pDrawModel->ReformatAllTextObjects(); + } + + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot && !m_rDoc.IsInReading() ) + { + pTmpRoot->StartAllAction(); + for( auto aLayout : m_rDoc.GetAllLayouts() ) + aLayout->InvalidateAllContent(SwInvalidateFlags::Size); + pTmpRoot->EndAllAction(); + } + m_rDoc.getIDocumentState().SetModified(); + } +} + + +void sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSettingManager& rSource) +{ + Setn32DummyCompatibilityOptions1( rSource.Getn32DummyCompatibilityOptions1() ); + Setn32DummyCompatibilityOptions2( rSource.Getn32DummyCompatibilityOptions2() ); + + // No mbHTMLMode + // No mbIsGlobalDoc + // No mbGlblDocSaveLinks + // No mbIsLabelDoc + // No mbPurgeOLE + // No mbKernAsianPunctuation + mbParaSpaceMax = rSource.mbParaSpaceMax; + mbParaSpaceMaxAtPages = rSource.mbParaSpaceMaxAtPages; + mbTabCompat = rSource.mbTabCompat; + mbUseVirtualDevice = rSource.mbUseVirtualDevice; + mbAddFlyOffsets = rSource.mbAddFlyOffsets; + // No mbAddVerticalFlyOffsets + mbAddExternalLeading = rSource.mbAddExternalLeading; + mbUseHiResolutionVirtualDevice = rSource.mbUseHiResolutionVirtualDevice; + mbOldLineSpacing = rSource.mbOldLineSpacing; + mbAddParaSpacingToTableCells = rSource.mbAddParaSpacingToTableCells; + mbAddParaLineSpacingToTableCells = rSource.mbAddParaLineSpacingToTableCells; + mbUseFormerObjectPos = rSource.mbUseFormerObjectPos; + mbUseFormerTextWrapping = rSource.mbUseFormerTextWrapping; + mbConsiderWrapOnObjPos = rSource.mbConsiderWrapOnObjPos; + // No mbMathBaselineAlignment + // No mbStylesNoDefault + // No mbFloattableNomargins + mbOldNumbering = rSource.mbOldNumbering; + mbIgnoreFirstLineIndentInNumbering = rSource.mbIgnoreFirstLineIndentInNumbering; + mbDoNotJustifyLinesWithManualBreak = rSource.mbDoNotJustifyLinesWithManualBreak; + mbDoNotResetParaAttrsForNumFont = rSource.mbDoNotResetParaAttrsForNumFont; + mbTableRowKeep = rSource.mbTableRowKeep; + mbIgnoreTabsAndBlanksForLineCalculation = rSource.mbIgnoreTabsAndBlanksForLineCalculation; + mbDoNotCaptureDrawObjsOnPage = rSource.mbDoNotCaptureDrawObjsOnPage; + mbClipAsCharacterAnchoredWriterFlyFrames = rSource.mbClipAsCharacterAnchoredWriterFlyFrames; + mbUnixForceZeroExtLeading = rSource.mbUnixForceZeroExtLeading; + mbTabRelativeToIndent = rSource.mbTabRelativeToIndent; + // No mbProtectForm + mbMsWordCompTrailingBlanks = rSource.mbMsWordCompTrailingBlanks; + mbMsWordCompMinLineHeightByFly = rSource.mbMsWordCompMinLineHeightByFly; + // No mbInvertBorderSpacing + mbCollapseEmptyCellPara = rSource.mbCollapseEmptyCellPara; + mbTabAtLeftIndentForParagraphsInList = rSource.mbTabAtLeftIndentForParagraphsInList; + // No mbSmallCapsPercentage66 + // No mbTabOverflow + mbUnbreakableNumberings = rSource.mbUnbreakableNumberings; + // No mbClippedPictures + // No mbBackgroundParaOverDrawings + mbTabOverMargin = rSource.mbTabOverMargin; + // No mbTreatSingleColumnBreakAsPageBreak + // No mbSurroundTextWrapSmall + // No mbPropLineSpacingShrinksFirstLine + mbSubtractFlys = rSource.mbSubtractFlys; + // No mbLastBrowseMode + mbDisableOffPagePositioning = rSource.mbDisableOffPagePositioning; + // No mbEmptyDbFieldHidesPara + mbEmptyDbFieldHidesPara = rSource.mbEmptyDbFieldHidesPara; + mbContinuousEndnotes = rSource.mbContinuousEndnotes; + // No mbProtectBookmarks + // No mbProtectFields + mbHeaderSpacingBelowLastPara = rSource.mbHeaderSpacingBelowLastPara; +} + +sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions1() const +{ + return mn32DummyCompatibilityOptions1; +} + +void sw::DocumentSettingManager::Setn32DummyCompatibilityOptions1( const sal_uInt32 CompatibilityOptions1 ) +{ + mn32DummyCompatibilityOptions1 = CompatibilityOptions1; +} + +sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions2() const +{ + return mn32DummyCompatibilityOptions2; +} + +void sw::DocumentSettingManager::Setn32DummyCompatibilityOptions2( const sal_uInt32 CompatibilityOptions2 ) +{ + mn32DummyCompatibilityOptions2 = CompatibilityOptions2; +} + +void sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("DocumentSettingManager")); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbHTMLMode")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHTMLMode).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIsGlobalDoc")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIsGlobalDoc).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbGlblDocSaveLinks")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbGlblDocSaveLinks).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIsLabelDoc")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIsLabelDoc).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbPurgeOLE")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbPurgeOLE).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbKernAsianPunctuation")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbKernAsianPunctuation).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbParaSpaceMax")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbParaSpaceMax).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbParaSpaceMaxAtPages")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbParaSpaceMaxAtPages).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabCompat")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabCompat).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseVirtualDevice")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseVirtualDevice).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddFlyOffsets")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddFlyOffsets).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddVerticalFlyOffsets")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddVerticalFlyOffsets).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddExternalLeading")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddExternalLeading).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseHiResolutionVirtualDevice")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseHiResolutionVirtualDevice).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbOldLineSpacing")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbOldLineSpacing).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddParaSpacingToTableCells")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddParaSpacingToTableCells).getStr())); + xmlTextWriterEndElement(pWriter); + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddParaLineSpacingToTableCells")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddParaLineSpacingToTableCells).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseFormerObjectPos")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseFormerObjectPos).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseFormerTextWrapping")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseFormerTextWrapping).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbConsiderWrapOnObjPos")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbConsiderWrapOnObjPos).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbMathBaselineAlignment")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMathBaselineAlignment).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbStylesNoDefault")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbStylesNoDefault).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbFloattableNomargins")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbFloattableNomargins).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbOldNumbering")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbOldNumbering).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIgnoreFirstLineIndentInNumbering")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIgnoreFirstLineIndentInNumbering).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotJustifyLinesWithManualBreak")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotJustifyLinesWithManualBreak).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotResetParaAttrsForNumFont")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotResetParaAttrsForNumFont).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTableRowKeep")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTableRowKeep).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIgnoreTabsAndBlanksForLineCalculation")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIgnoreTabsAndBlanksForLineCalculation).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotCaptureDrawObjsOnPage")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotCaptureDrawObjsOnPage).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbClipAsCharacterAnchoredWriterFlyFrames")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbClipAsCharacterAnchoredWriterFlyFrames).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUnixForceZeroExtLeading")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUnixForceZeroExtLeading).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabRelativeToIndent")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabRelativeToIndent).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbProtectForm")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbProtectForm).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompTrailingBlanks")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompTrailingBlanks).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinLineHeightByFly")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompMinLineHeightByFly).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbInvertBorderSpacing")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbInvertBorderSpacing).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbCollapseEmptyCellPara")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbCollapseEmptyCellPara).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabAtLeftIndentForParagraphsInList")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabAtLeftIndentForParagraphsInList).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbSmallCapsPercentage66")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSmallCapsPercentage66).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverflow")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverflow).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUnbreakableNumberings")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUnbreakableNumberings).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbClippedPictures")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbClippedPictures).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbBackgroundParaOverDrawings")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbBackgroundParaOverDrawings).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverMargin")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverMargin).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTreatSingleColumnBreakAsPageBreak")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTreatSingleColumnBreakAsPageBreak).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbSurroundTextWrapSmall")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSurroundTextWrapSmall).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbPropLineSpacingShrinksFirstLine")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbPropLineSpacingShrinksFirstLine).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbSubtractFlys")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSubtractFlys).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbLastBrowseMode")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbLastBrowseMode).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDisableOffPagePositioning")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDisableOffPagePositioning).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbEmptyDbFieldHidesPara")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbEmptyDbFieldHidesPara).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbContinuousEndnotes")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbContinuousEndnotes).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbHeaderSpacingBelowLastPara")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHeaderSpacingBelowLastPara).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentStateManager.cxx b/sw/source/core/doc/DocumentStateManager.cxx new file mode 100644 index 000000000..bf965d54b --- /dev/null +++ b/sw/source/core/doc/DocumentStateManager.cxx @@ -0,0 +1,118 @@ +/* -*- 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 + +namespace sw +{ + +DocumentStateManager::DocumentStateManager( SwDoc& i_rSwdoc ) : + m_rDoc( i_rSwdoc ), + mbEnableSetModified(true), + mbModified(false), + mbUpdateExpField(false), + mbNewDoc(false), + mbInCallModified(false) +{ +} + +void DocumentStateManager::SetModified() +{ + if (!IsEnableSetModified()) + return; + + m_rDoc.GetDocumentLayoutManager().ClearSwLayouterEntries(); + mbModified = true; + m_rDoc.GetDocumentStatisticsManager().SetDocStatModified( true ); + if( m_rDoc.GetOle2Link().IsSet() ) + { + mbInCallModified = true; + m_rDoc.GetOle2Link().Call( true ); + mbInCallModified = false; + } + + if( m_rDoc.GetAutoCorrExceptWord() && !m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ) + m_rDoc.DeleteAutoCorrExceptWord(); +} + +void DocumentStateManager::ResetModified() +{ + // give the old and new modified state to the link + // Bit 0: -> old state + // Bit 1: -> new state + bool bOldModified = mbModified; + mbModified = false; + m_rDoc.GetDocumentStatisticsManager().SetDocStatModified( false ); + m_rDoc.GetIDocumentUndoRedo().SetUndoNoModifiedPosition(); + if( bOldModified && m_rDoc.GetOle2Link().IsSet() ) + { + mbInCallModified = true; + m_rDoc.GetOle2Link().Call( false ); + mbInCallModified = false; + } +} + +bool DocumentStateManager::IsModified() const +{ + return mbModified; +} + +bool DocumentStateManager::IsEnableSetModified() const +{ + return mbEnableSetModified; +} + +void DocumentStateManager::SetEnableSetModified(bool bEnableSetModified) +{ + mbEnableSetModified = bEnableSetModified; +} + +bool DocumentStateManager::IsInCallModified() const +{ + return mbInCallModified; +} + +bool DocumentStateManager::IsUpdateExpField() const +{ + return mbUpdateExpField; +} + +bool DocumentStateManager::IsNewDoc() const +{ + return mbNewDoc; +} + +void DocumentStateManager::SetNewDoc(bool b) +{ + mbNewDoc = b; +} + +void DocumentStateManager::SetUpdateExpFieldStat(bool b) +{ + mbUpdateExpField = b; +} + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentStatisticsManager.cxx b/sw/source/core/doc/DocumentStatisticsManager.cxx new file mode 100644 index 000000000..9508e6d72 --- /dev/null +++ b/sw/source/core/doc/DocumentStatisticsManager.cxx @@ -0,0 +1,218 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +namespace sw +{ + +DocumentStatisticsManager::DocumentStatisticsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), + mpDocStat( new SwDocStat ), + mbInitialized( false ), + maStatsUpdateIdle( i_rSwdoc ) + +{ + maStatsUpdateIdle.SetPriority( TaskPriority::LOWEST ); + maStatsUpdateIdle.SetInvokeHandler( LINK( this, DocumentStatisticsManager, DoIdleStatsUpdate ) ); + maStatsUpdateIdle.SetDebugName( "sw::DocumentStatisticsManager maStatsUpdateIdle" ); +} + +void DocumentStatisticsManager::DocInfoChgd(bool const isEnableSetModified) +{ + m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::DocInfo )->UpdateFields(); + m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::TemplateName )->UpdateFields(); + if (isEnableSetModified) + { + m_rDoc.getIDocumentState().SetModified(); + } +} + +const SwDocStat& DocumentStatisticsManager::GetDocStat() const +{ + return *mpDocStat; +} + +void DocumentStatisticsManager::SetDocStatModified(bool bSet) +{ + mpDocStat->bModified = bSet; +} + +const SwDocStat& DocumentStatisticsManager::GetUpdatedDocStat( bool bCompleteAsync, bool bFields ) +{ + if( mpDocStat->bModified || !mbInitialized) + { + UpdateDocStat( bCompleteAsync, bFields ); + } + return *mpDocStat; +} + +void DocumentStatisticsManager::SetDocStat( const SwDocStat& rStat ) +{ + *mpDocStat = rStat; + mbInitialized = true; +} + +void DocumentStatisticsManager::UpdateDocStat( bool bCompleteAsync, bool bFields ) +{ + if( mpDocStat->bModified || !mbInitialized) + { + if (!bCompleteAsync) + { + maStatsUpdateIdle.Stop(); + while (IncrementalDocStatCalculate( + std::numeric_limits::max(), bFields)) {} + } + else if (IncrementalDocStatCalculate(5000, bFields)) + maStatsUpdateIdle.Start(); + else + maStatsUpdateIdle.Stop(); + } +} + +// returns true while there is more to do +bool DocumentStatisticsManager::IncrementalDocStatCalculate(long nChars, bool bFields) +{ + mbInitialized = true; + mpDocStat->Reset(); + mpDocStat->nPara = 0; // default is 1! + + // This is the inner loop - at least while the paras are dirty. + for( sal_uLong i = m_rDoc.GetNodes().Count(); i > 0 && nChars > 0; ) + { + SwNode* pNd = m_rDoc.GetNodes()[ --i ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + { + long const nOldChars(mpDocStat->nChar); + SwTextNode *pText = static_cast< SwTextNode * >( pNd ); + if (pText->CountWords(*mpDocStat, 0, pText->GetText().getLength())) + { + nChars -= (mpDocStat->nChar - nOldChars); + } + break; + } + case SwNodeType::Table: ++mpDocStat->nTable; break; + case SwNodeType::Grf: ++mpDocStat->nGrf; break; + case SwNodeType::Ole: ++mpDocStat->nOLE; break; + case SwNodeType::Section: break; + default: break; + } + } + + // #i93174#: notes contain paragraphs that are not nodes + { + SwFieldType * const pPostits( m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Postit) ); + std::vector vFields; + pPostits->GatherFields(vFields); + for(auto pFormatField : vFields) + { + const auto pField = static_cast(pFormatField->GetField()); + mpDocStat->nAllPara += pField->GetNumberOfParagraphs(); + } + } + + mpDocStat->nPage = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ? m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->GetPageNum() : 0; + SetDocStatModified( false ); + + css::uno::Sequence < css::beans::NamedValue > aStat( mpDocStat->nPage ? 8 : 7); + sal_Int32 n=0; + aStat[n].Name = "TableCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nTable); + aStat[n].Name = "ImageCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nGrf); + aStat[n].Name = "ObjectCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nOLE); + if ( mpDocStat->nPage ) + { + aStat[n].Name = "PageCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nPage); + } + aStat[n].Name = "ParagraphCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nPara); + aStat[n].Name = "WordCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nWord); + aStat[n].Name = "CharacterCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nChar); + aStat[n].Name = "NonWhitespaceCharacterCount"; + aStat[n++].Value <<= static_cast(mpDocStat->nCharExcludingSpaces); + + // For e.g. autotext documents there is no pSwgInfo (#i79945) + SwDocShell* pObjShell(m_rDoc.GetDocShell()); + if (pObjShell) + { + const uno::Reference xDPS( + pObjShell->GetModel(), uno::UNO_QUERY_THROW); + const uno::Reference xDocProps( + xDPS->getDocumentProperties()); + // #i96786#: do not set modified flag when updating statistics + const bool bDocWasModified( m_rDoc.getIDocumentState().IsModified() ); + const ModifyBlocker_Impl b(pObjShell); + // rhbz#1081176: don't jump to cursor pos because of (temporary) + // activation of modified flag triggering move to input position + auto aViewGuard(pObjShell->LockAllViews()); + xDocProps->setDocumentStatistics(aStat); + if (!bDocWasModified) + { + m_rDoc.getIDocumentState().ResetModified(); + } + } + + // optionally update stat. fields + if (bFields) + { + SwFieldType *pType = m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DocStat); + pType->UpdateFields(); + } + + return nChars < 0; +} + +IMPL_LINK( DocumentStatisticsManager, DoIdleStatsUpdate, Timer *, pIdle, void ) +{ + if (IncrementalDocStatCalculate(32000)) + pIdle->Start(); + SwView* pView = m_rDoc.GetDocShell() ? m_rDoc.GetDocShell()->GetView() : nullptr; + if( pView ) + pView->UpdateDocStats(); +} + +DocumentStatisticsManager::~DocumentStatisticsManager() +{ + maStatsUpdateIdle.Stop(); +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentStylePoolManager.cxx b/sw/source/core/doc/DocumentStylePoolManager.cxx new file mode 100644 index 000000000..b3ccc8913 --- /dev/null +++ b/sw/source/core/doc/DocumentStylePoolManager.cxx @@ -0,0 +1,2655 @@ +/* -*- 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 + +using namespace ::editeng; +using namespace ::com::sun::star; + +bool IsConditionalByPoolId(sal_uInt16 nId) +{ + // TODO: why is this style conditional? + // If it is changed to no longer be conditional, then a style "Text Body" + // will be imported without its conditions from ODF. + return RES_POOLCOLL_TEXT == nId; +} + +namespace +{ + static const sal_uInt16 PT_3 = 3 * 20; // 3 pt + static const sal_uInt16 PT_6 = 6 * 20; // 6 pt + static const sal_uInt16 PT_7 = 7 * 20; // 7 pt + static const sal_uInt16 PT_10 = 10 * 20; // 10 pt + static const sal_uInt16 PT_12 = 12 * 20; // 12 pt + static const sal_uInt16 PT_14 = 14 * 20; // 14 pt + static const sal_uInt16 PT_16 = 16 * 20; // 16 pt + static const sal_uInt16 PT_18 = 18 * 20; // 18 pt + static const sal_uInt16 PT_24 = 24 * 20; // 24 pt + static const sal_uInt16 PT_28 = 28 * 20; // 28 pt + + #define HTML_PARSPACE GetMetricVal( CM_05 ) + + static const sal_uInt16 aHeadlineSizes[ 2 * MAXLEVEL ] = { + // we do everything percentual now: + 130, 115, 101, 95, 85, + 85, 80, 80, 75, 75, // normal + PT_24, PT_18, PT_14, PT_12, PT_10, + PT_7, PT_7, PT_7, PT_7, PT_7 // HTML mode + }; + + long lcl_GetRightMargin( SwDoc& rDoc ) + { + // Make sure that the printer settings are taken over to the standard + // page style + const SwFrameFormat& rPgDscFormat = rDoc.GetPageDesc( 0 ).GetMaster(); + const SvxLRSpaceItem& rLR = rPgDscFormat.GetLRSpace(); + const long nLeft = rLR.GetLeft(); + const long nRight = rLR.GetRight(); + const long nWidth = rPgDscFormat.GetFrameSize().GetWidth(); + return nWidth - nLeft - nRight; + } + + void lcl_SetDfltFont( DefaultFontType nFntType, SfxItemSet& rSet ) + { + static struct { + sal_uInt16 nResLngId; + sal_uInt16 nResFntId; + } aArr[ 3 ] = { + { RES_CHRATR_LANGUAGE, RES_CHRATR_FONT }, + { RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CJK_FONT }, + { RES_CHRATR_CTL_LANGUAGE, RES_CHRATR_CTL_FONT } + }; + for(const auto & n : aArr) + { + LanguageType nLng = static_cast(rSet.GetPool()->GetDefaultItem( + n.nResLngId )).GetLanguage(); + vcl::Font aFnt( OutputDevice::GetDefaultFont( nFntType, + nLng, GetDefaultFontFlags::OnlyOne ) ); + + rSet.Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), + OUString(), aFnt.GetPitch(), + aFnt.GetCharSet(), n.nResFntId )); + } + } + + void lcl_SetDfltFont( DefaultFontType nLatinFntType, DefaultFontType nCJKFntType, + DefaultFontType nCTLFntType, SfxItemSet& rSet ) + { + static struct { + sal_uInt16 nResLngId; + sal_uInt16 nResFntId; + DefaultFontType nFntType; + } aArr[ 3 ] = { + { RES_CHRATR_LANGUAGE, RES_CHRATR_FONT, static_cast(0) }, + { RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CJK_FONT, static_cast(0) }, + { RES_CHRATR_CTL_LANGUAGE, RES_CHRATR_CTL_FONT, static_cast(0) } + }; + aArr[0].nFntType = nLatinFntType; + aArr[1].nFntType = nCJKFntType; + aArr[2].nFntType = nCTLFntType; + + for(const auto & n : aArr) + { + LanguageType nLng = static_cast(rSet.GetPool()->GetDefaultItem( + n.nResLngId )).GetLanguage(); + vcl::Font aFnt( OutputDevice::GetDefaultFont( n.nFntType, + nLng, GetDefaultFontFlags::OnlyOne ) ); + + rSet.Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), + OUString(), aFnt.GetPitch(), + aFnt.GetCharSet(), n.nResFntId )); + } + } + + void lcl_SetHeadline( SwDoc* pDoc, SwTextFormatColl* pColl, + SfxItemSet& rSet, + sal_uInt16 nOutLvlBits, sal_uInt8 nLevel, bool bItalic ) + { + SetAllScriptItem( rSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SvxFontHeightItem aHItem(240, 100, RES_CHRATR_FONTSIZE); + const bool bHTMLMode = pDoc->GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE); + if( bHTMLMode ) + aHItem.SetHeight( aHeadlineSizes[ MAXLEVEL + nLevel ] ); + else + aHItem.SetHeight( PT_14, aHeadlineSizes[ nLevel ] ); + SetAllScriptItem( rSet, aHItem ); + + if( bItalic && !bHTMLMode ) + SetAllScriptItem( rSet, SvxPostureItem( ITALIC_NORMAL, RES_CHRATR_POSTURE ) ); + + if( bHTMLMode ) + { + lcl_SetDfltFont( DefaultFontType::LATIN_TEXT, DefaultFontType::CJK_TEXT, + DefaultFontType::CTL_TEXT, rSet ); + } + + if( !pColl ) + return; + + if( !( nOutLvlBits & ( 1 << nLevel )) ) + { + pColl->AssignToListLevelOfOutlineStyle(nLevel); + if( !bHTMLMode ) + { + SwNumRule * pOutlineRule = pDoc->GetOutlineNumRule(); + const SwNumFormat& rNFormat = pOutlineRule->Get( nLevel ); + + if ( rNFormat.GetPositionAndSpaceMode() == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION && + ( rNFormat.GetAbsLSpace() || rNFormat.GetFirstLineOffset() ) ) + { + SvxLRSpaceItem aLR( pColl->GetFormatAttr( RES_LR_SPACE ) ); + aLR.SetTextFirstLineOffsetValue( rNFormat.GetFirstLineOffset() ); + //TODO: overflow + aLR.SetTextLeft( rNFormat.GetAbsLSpace() ); + pColl->SetFormatAttr( aLR ); + } + + // All paragraph styles, which are assigned to a level of the + // outline style has to have the outline style set as its list style. + SwNumRuleItem aItem(pOutlineRule->GetName()); + pColl->SetFormatAttr(aItem); + } + } + pColl->SetNextTextFormatColl( *pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + + void lcl_SetRegister( SwDoc* pDoc, SfxItemSet& rSet, sal_uInt16 nFact, + bool bHeader, bool bTab ) + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + sal_uInt16 nLeft = nFact ? GetMetricVal( CM_05 ) * nFact : 0; + aLR.SetTextLeft( nLeft ); + + rSet.Put( aLR ); + if( bHeader ) + { + SetAllScriptItem( rSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( rSet, SvxFontHeightItem( PT_16, 100, RES_CHRATR_FONTSIZE ) ); + } + if( bTab ) + { + long nRightMargin = lcl_GetRightMargin( *pDoc ); + SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( nRightMargin - nLeft, + SvxTabAdjust::Right, + cDfltDecimalChar, '.' )); + rSet.Put( aTStops ); + } + } + + void lcl_SetNumBul( SwDoc* pDoc, SwTextFormatColl* pColl, + SfxItemSet& rSet, + sal_uInt16 nNxt, SwTwips nEZ, SwTwips nLeft, + SwTwips nUpper, SwTwips nLower ) + { + + SvxLRSpaceItem aLR( RES_LR_SPACE ); + SvxULSpaceItem aUL( RES_UL_SPACE ); + aLR.SetTextFirstLineOffset( sal_uInt16(nEZ) ); + aLR.SetTextLeft( sal_uInt16(nLeft) ); + aUL.SetUpper( sal_uInt16(nUpper) ); + aUL.SetLower( sal_uInt16(nLower) ); + rSet.Put( aLR ); + rSet.Put( aUL ); + + if( pColl ) + pColl->SetNextTextFormatColl( *pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nNxt )); + } + + void lcl_PutStdPageSizeIntoItemSet( SwDoc* pDoc, SfxItemSet& rSet ) + { + SwPageDesc* pStdPgDsc = pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + SwFormatFrameSize aFrameSz( pStdPgDsc->GetMaster().GetFrameSize() ); + if( pStdPgDsc->GetLandscape() ) + { + SwTwips nTmp = aFrameSz.GetHeight(); + aFrameSz.SetHeight( aFrameSz.GetWidth() ); + aFrameSz.SetWidth( nTmp ); + } + rSet.Put( aFrameSz ); + } +} + +static const char* STR_POOLCOLL_TEXT_ARY[] = +{ + // Category Text + STR_POOLCOLL_STANDARD, + STR_POOLCOLL_TEXT, + STR_POOLCOLL_TEXT_IDENT, + STR_POOLCOLL_TEXT_NEGIDENT, + STR_POOLCOLL_TEXT_MOVE, + STR_POOLCOLL_GREETING, + STR_POOLCOLL_SIGNATURE, + STR_POOLCOLL_CONFRONTATION, + STR_POOLCOLL_MARGINAL, + // Subcategory Headlines + STR_POOLCOLL_HEADLINE_BASE, + STR_POOLCOLL_HEADLINE1, + STR_POOLCOLL_HEADLINE2, + STR_POOLCOLL_HEADLINE3, + STR_POOLCOLL_HEADLINE4, + STR_POOLCOLL_HEADLINE5, + STR_POOLCOLL_HEADLINE6, + STR_POOLCOLL_HEADLINE7, + STR_POOLCOLL_HEADLINE8, + STR_POOLCOLL_HEADLINE9, + STR_POOLCOLL_HEADLINE10 +}; + +static const char* STR_POOLCOLL_LISTS_ARY[] +{ + // Category Lists + STR_POOLCOLL_NUMBER_BULLET_BASE, + // Subcategory Numbering + STR_POOLCOLL_NUM_LEVEL1S, + STR_POOLCOLL_NUM_LEVEL1, + STR_POOLCOLL_NUM_LEVEL1E, + STR_POOLCOLL_NUM_NONUM1, + STR_POOLCOLL_NUM_LEVEL2S, + STR_POOLCOLL_NUM_LEVEL2, + STR_POOLCOLL_NUM_LEVEL2E, + STR_POOLCOLL_NUM_NONUM2, + STR_POOLCOLL_NUM_LEVEL3S, + STR_POOLCOLL_NUM_LEVEL3, + STR_POOLCOLL_NUM_LEVEL3E, + STR_POOLCOLL_NUM_NONUM3, + STR_POOLCOLL_NUM_LEVEL4S, + STR_POOLCOLL_NUM_LEVEL4, + STR_POOLCOLL_NUM_LEVEL4E, + STR_POOLCOLL_NUM_NONUM4, + STR_POOLCOLL_NUM_LEVEL5S, + STR_POOLCOLL_NUM_LEVEL5, + STR_POOLCOLL_NUM_LEVEL5E, + STR_POOLCOLL_NUM_NONUM5, + + // Subcategory Enumeration + STR_POOLCOLL_BULLET_LEVEL1S, + STR_POOLCOLL_BULLET_LEVEL1, + STR_POOLCOLL_BULLET_LEVEL1E, + STR_POOLCOLL_BULLET_NONUM1, + STR_POOLCOLL_BULLET_LEVEL2S, + STR_POOLCOLL_BULLET_LEVEL2, + STR_POOLCOLL_BULLET_LEVEL2E, + STR_POOLCOLL_BULLET_NONUM2, + STR_POOLCOLL_BULLET_LEVEL3S, + STR_POOLCOLL_BULLET_LEVEL3, + STR_POOLCOLL_BULLET_LEVEL3E, + STR_POOLCOLL_BULLET_NONUM3, + STR_POOLCOLL_BULLET_LEVEL4S, + STR_POOLCOLL_BULLET_LEVEL4, + STR_POOLCOLL_BULLET_LEVEL4E, + STR_POOLCOLL_BULLET_NONUM4, + STR_POOLCOLL_BULLET_LEVEL5S, + STR_POOLCOLL_BULLET_LEVEL5, + STR_POOLCOLL_BULLET_LEVEL5E, + STR_POOLCOLL_BULLET_NONUM5 +}; + +// Special Areas +static const char* STR_POOLCOLL_EXTRA_ARY[] +{ + // Subcategory Header + STR_POOLCOLL_HEADERFOOTER, + STR_POOLCOLL_HEADER, + STR_POOLCOLL_HEADERL, + STR_POOLCOLL_HEADERR, + // Subcategory Footer + STR_POOLCOLL_FOOTER, + STR_POOLCOLL_FOOTERL, + STR_POOLCOLL_FOOTERR, + // Subcategory Table + STR_POOLCOLL_TABLE, + STR_POOLCOLL_TABLE_HDLN, + // Subcategory Labels + STR_POOLCOLL_LABEL, + STR_POOLCOLL_LABEL_ABB, + STR_POOLCOLL_LABEL_TABLE, + STR_POOLCOLL_LABEL_FRAME, + STR_POOLCOLL_LABEL_FIGURE, + // Miscellaneous + STR_POOLCOLL_FRAME, + STR_POOLCOLL_FOOTNOTE, + STR_POOLCOLL_JAKETADRESS, + STR_POOLCOLL_SENDADRESS, + STR_POOLCOLL_ENDNOTE, + STR_POOLCOLL_LABEL_DRAWING +}; + +static const char* STR_POOLCOLL_REGISTER_ARY[] = +{ + // Category Directories + STR_POOLCOLL_REGISTER_BASE, + // Subcategory Index-Directories + STR_POOLCOLL_TOX_IDXH, + STR_POOLCOLL_TOX_IDX1, + STR_POOLCOLL_TOX_IDX2, + STR_POOLCOLL_TOX_IDX3, + STR_POOLCOLL_TOX_IDXBREAK, + // Subcategory Tables of Contents + STR_POOLCOLL_TOX_CNTNTH, + STR_POOLCOLL_TOX_CNTNT1, + STR_POOLCOLL_TOX_CNTNT2, + STR_POOLCOLL_TOX_CNTNT3, + STR_POOLCOLL_TOX_CNTNT4, + STR_POOLCOLL_TOX_CNTNT5, + // Subcategory User-Directories: + STR_POOLCOLL_TOX_USERH, + STR_POOLCOLL_TOX_USER1, + STR_POOLCOLL_TOX_USER2, + STR_POOLCOLL_TOX_USER3, + STR_POOLCOLL_TOX_USER4, + STR_POOLCOLL_TOX_USER5, + // Subcategory Table of Contents more Levels 5 - 10 + STR_POOLCOLL_TOX_CNTNT6, + STR_POOLCOLL_TOX_CNTNT7, + STR_POOLCOLL_TOX_CNTNT8, + STR_POOLCOLL_TOX_CNTNT9, + STR_POOLCOLL_TOX_CNTNT10, + // Illustrations Index + STR_POOLCOLL_TOX_ILLUSH, + STR_POOLCOLL_TOX_ILLUS1, + // Object Index + STR_POOLCOLL_TOX_OBJECTH, + STR_POOLCOLL_TOX_OBJECT1, + // Tables Index + STR_POOLCOLL_TOX_TABLESH, + STR_POOLCOLL_TOX_TABLES1, + // Index of Authorities + STR_POOLCOLL_TOX_AUTHORITIESH, + STR_POOLCOLL_TOX_AUTHORITIES1, + // Subcategory User-Directories more Levels 5 - 10 + STR_POOLCOLL_TOX_USER6, + STR_POOLCOLL_TOX_USER7, + STR_POOLCOLL_TOX_USER8, + STR_POOLCOLL_TOX_USER9, + STR_POOLCOLL_TOX_USER10 +}; + +static const char* STR_POOLCOLL_DOC_ARY[] = +{ + // Category Chapter/Document + STR_POOLCOLL_DOC_TITLE, + STR_POOLCOLL_DOC_SUBTITLE, + STR_POOLCOLL_DOC_APPENDIX +}; + +static const char* STR_POOLCOLL_HTML_ARY[] = +{ + // Category HTML-Templates + STR_POOLCOLL_HTML_BLOCKQUOTE, + STR_POOLCOLL_HTML_PRE, + STR_POOLCOLL_HTML_HR, + STR_POOLCOLL_HTML_DD, + STR_POOLCOLL_HTML_DT +}; + +static const char* STR_POOLCHR_ARY[] = +{ + STR_POOLCHR_FOOTNOTE, + STR_POOLCHR_PAGENO, + STR_POOLCHR_LABEL, + STR_POOLCHR_DROPCAPS, + STR_POOLCHR_NUM_LEVEL, + STR_POOLCHR_BULLET_LEVEL, + STR_POOLCHR_INET_NORMAL, + STR_POOLCHR_INET_VISIT, + STR_POOLCHR_JUMPEDIT, + STR_POOLCHR_TOXJUMP, + STR_POOLCHR_ENDNOTE, + STR_POOLCHR_LINENUM, + STR_POOLCHR_IDX_MAIN_ENTRY, + STR_POOLCHR_FOOTNOTE_ANCHOR, + STR_POOLCHR_ENDNOTE_ANCHOR, + STR_POOLCHR_RUBYTEXT, + STR_POOLCHR_VERT_NUM +}; + +static const char* STR_POOLCHR_HTML_ARY[] = +{ + STR_POOLCHR_HTML_EMPHASIS, + STR_POOLCHR_HTML_CITIATION, + STR_POOLCHR_HTML_STRONG, + STR_POOLCHR_HTML_CODE, + STR_POOLCHR_HTML_SAMPLE, + STR_POOLCHR_HTML_KEYBOARD, + STR_POOLCHR_HTML_VARIABLE, + STR_POOLCHR_HTML_DEFINSTANCE, + STR_POOLCHR_HTML_TELETYPE +}; + +static const char* STR_POOLFRM_ARY[] = +{ + STR_POOLFRM_FRAME, + STR_POOLFRM_GRAPHIC, + STR_POOLFRM_OLE, + STR_POOLFRM_FORMEL, + STR_POOLFRM_MARGINAL, + STR_POOLFRM_WATERSIGN, + STR_POOLFRM_LABEL +}; + +static const char* STR_POOLPAGE_ARY[] = +{ + // Page styles + STR_POOLPAGE_STANDARD, + STR_POOLPAGE_FIRST, + STR_POOLPAGE_LEFT, + STR_POOLPAGE_RIGHT, + STR_POOLPAGE_JAKET, + STR_POOLPAGE_REGISTER, + STR_POOLPAGE_HTML, + STR_POOLPAGE_FOOTNOTE, + STR_POOLPAGE_ENDNOTE, + STR_POOLPAGE_LANDSCAPE +}; + +static const char* STR_POOLNUMRULE_NUM_ARY[] = +{ + // Numbering styles + STR_POOLNUMRULE_NUM1, + STR_POOLNUMRULE_NUM2, + STR_POOLNUMRULE_NUM3, + STR_POOLNUMRULE_NUM4, + STR_POOLNUMRULE_NUM5, + STR_POOLNUMRULE_BUL1, + STR_POOLNUMRULE_BUL2, + STR_POOLNUMRULE_BUL3, + STR_POOLNUMRULE_BUL4, + STR_POOLNUMRULE_BUL5 +}; + +// XXX MUST match the entries of TableStyleProgNameTable in +// sw/source/core/doc/SwStyleNameMapper.cxx and MUST match the order of +// RES_POOL_TABLESTYLE_TYPE in sw/inc/poolfmt.hxx +static const char* STR_TABSTYLE_ARY[] = +{ + // XXX MUST be in order, Writer first, then Svx old, then Svx new + // 1 Writer resource string + STR_TABSTYLE_DEFAULT, + // 16 old styles Svx resource strings + RID_SVXSTR_TBLAFMT_3D, + RID_SVXSTR_TBLAFMT_BLACK1, + RID_SVXSTR_TBLAFMT_BLACK2, + RID_SVXSTR_TBLAFMT_BLUE, + RID_SVXSTR_TBLAFMT_BROWN, + RID_SVXSTR_TBLAFMT_CURRENCY, + RID_SVXSTR_TBLAFMT_CURRENCY_3D, + RID_SVXSTR_TBLAFMT_CURRENCY_GRAY, + RID_SVXSTR_TBLAFMT_CURRENCY_LAVENDER, + RID_SVXSTR_TBLAFMT_CURRENCY_TURQUOISE, + RID_SVXSTR_TBLAFMT_GRAY, + RID_SVXSTR_TBLAFMT_GREEN, + RID_SVXSTR_TBLAFMT_LAVENDER, + RID_SVXSTR_TBLAFMT_RED, + RID_SVXSTR_TBLAFMT_TURQUOISE, + RID_SVXSTR_TBLAFMT_YELLOW, + // 10 new styles since LibreOffice 6.0 Svx resource strings + RID_SVXSTR_TBLAFMT_LO6_ACADEMIC, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_BLUE, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_GREEN, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_RED, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_YELLOW, + RID_SVXSTR_TBLAFMT_LO6_ELEGANT, + RID_SVXSTR_TBLAFMT_LO6_FINANCIAL, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_GRID_COLUMNS, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_GRID_ROWS, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_LIST_SHADED +}; + +namespace sw +{ + +DocumentStylePoolManager::DocumentStylePoolManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +SwTextFormatColl* DocumentStylePoolManager::GetTextCollFromPool( sal_uInt16 nId, bool bRegardLanguage ) +{ + OSL_ENSURE( + (RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END) || + (RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END) || + (RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END) || + (RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END) || + (RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END) || + (RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END), + "Wrong AutoFormat Id" ); + + SwTextFormatColl* pNewColl; + sal_uInt16 nOutLvlBits = 0; + for (size_t n = 0, nSize = m_rDoc.GetTextFormatColls()->size(); n < nSize; ++n) + { + pNewColl = (*m_rDoc.GetTextFormatColls())[ n ]; + if( nId == pNewColl->GetPoolFormatId() ) + { + return pNewColl; + } + + if( pNewColl->IsAssignedToListLevelOfOutlineStyle()) + nOutLvlBits |= ( 1 << pNewColl->GetAssignedOutlineStyleLevel() ); + } + + // Didn't find it until here -> create anew + const char* pResId = nullptr; + if (RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_TEXT_ARY) == RES_POOLCOLL_TEXT_END - RES_POOLCOLL_TEXT_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_TEXT_ARY[nId - RES_POOLCOLL_TEXT_BEGIN]; + } + else if (RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_LISTS_ARY) == RES_POOLCOLL_LISTS_END - RES_POOLCOLL_LISTS_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_LISTS_ARY[nId - RES_POOLCOLL_LISTS_BEGIN]; + } + else if (RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_EXTRA_ARY) == RES_POOLCOLL_EXTRA_END - RES_POOLCOLL_EXTRA_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_EXTRA_ARY[nId - RES_POOLCOLL_EXTRA_BEGIN]; + } + else if (RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_REGISTER_ARY) == RES_POOLCOLL_REGISTER_END - RES_POOLCOLL_REGISTER_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_REGISTER_ARY[nId - RES_POOLCOLL_REGISTER_BEGIN]; + } + else if (RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_DOC_ARY) == RES_POOLCOLL_DOC_END - RES_POOLCOLL_DOC_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_DOC_ARY[nId - RES_POOLCOLL_DOC_BEGIN]; + } + else if (RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_HTML_ARY) == RES_POOLCOLL_HTML_END - RES_POOLCOLL_HTML_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_HTML_ARY[nId - RES_POOLCOLL_HTML_BEGIN]; + } + + OSL_ENSURE(pResId, "Invalid Pool ID"); + if (!pResId) + return GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + OUString aNm(SwResId(pResId)); + + // A Set for all to-be-set Attributes + SwAttrSet aSet( m_rDoc.GetAttrPool(), aTextFormatCollSetRange ); + sal_uInt16 nParent = GetPoolParent( nId ); + + { + +//FEATURE::CONDCOLL + if(::IsConditionalByPoolId( nId )) + pNewColl = new SwConditionTextFormatColl( m_rDoc.GetAttrPool(), aNm, !nParent + ? m_rDoc.GetDfltTextFormatColl() + : GetTextCollFromPool( nParent )); + else +//FEATURE::CONDCOLL + pNewColl = new SwTextFormatColl( m_rDoc.GetAttrPool(), aNm, !nParent + ? m_rDoc.GetDfltTextFormatColl() + : GetTextCollFromPool( nParent )); + pNewColl->SetPoolFormatId( nId ); + m_rDoc.GetTextFormatColls()->push_back( pNewColl ); + } + + bool bNoDefault = m_rDoc.GetDocumentSettingManager().get( DocumentSettingId::STYLES_NODEFAULT ); + if ( !bNoDefault ) + { + switch( nId ) + { + // General content forms + case RES_POOLCOLL_STANDARD: + /* koreans do not like SvxScriptItem(TRUE) */ + if (bRegardLanguage) + { + LanguageType nAppLanguage = GetAppLanguage(); + if (GetDefaultFrameDirection(nAppLanguage) == + SvxFrameDirection::Horizontal_RL_TB) + { + SvxAdjustItem aAdjust(SvxAdjust::Right, RES_PARATR_ADJUST ); + aSet.Put(aAdjust); + } + if (nAppLanguage == LANGUAGE_KOREAN) + { + SvxScriptSpaceItem aScriptSpace(false, RES_PARATR_SCRIPTSPACE); + aSet.Put(aScriptSpace); + } + } + break; + + case RES_POOLCOLL_TEXT: // Text body + { + SvxLineSpacingItem aLSpc( LINE_SPACE_DEFAULT_HEIGHT, RES_PARATR_LINESPACING ); + SvxULSpaceItem aUL( 0, PT_7, RES_UL_SPACE ); + aLSpc.SetPropLineSpace( 115 ); + if( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) aUL.SetLower( HTML_PARSPACE ); + aSet.Put( aUL ); + aSet.Put( aLSpc ); + } + break; + case RES_POOLCOLL_TEXT_IDENT: // Text body indentation + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( GetMetricVal( CM_05 )); + aSet.Put( aLR ); + } + break; + case RES_POOLCOLL_TEXT_NEGIDENT: // Text body neg. indentation + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( -static_cast(GetMetricVal( CM_05 ))); + aLR.SetTextLeft( GetMetricVal( CM_1 )); + SvxTabStopItem aTStops(RES_PARATR_TABSTOP); + aTStops.Insert( SvxTabStop( 0 )); + + aSet.Put( aLR ); + aSet.Put( aTStops ); + } + break; + case RES_POOLCOLL_TEXT_MOVE: // Text body move + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextLeft( GetMetricVal( CM_05 )); + aSet.Put( aLR ); + } + break; + + case RES_POOLCOLL_CONFRONTATION: // Text body confrontation + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( - short( GetMetricVal( CM_1 ) * 4 + + GetMetricVal( CM_05)) ); + aLR.SetTextLeft( GetMetricVal( CM_1 ) * 5 ); + SvxTabStopItem aTStops( RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( 0 )); + + aSet.Put( aLR ); + aSet.Put( aTStops ); + } + break; + case RES_POOLCOLL_MARGINAL: // Text body marginal + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextLeft( GetMetricVal( CM_1 ) * 4 ); + aSet.Put( aLR ); + } + break; + + case RES_POOLCOLL_HEADLINE_BASE: // Base headline + { + static const sal_uInt16 aFontWhich[] = + { RES_CHRATR_FONT, + RES_CHRATR_CJK_FONT, + RES_CHRATR_CTL_FONT + }; + static const sal_uInt16 aLangTypes[] = + { + RES_CHRATR_LANGUAGE, + RES_CHRATR_CJK_LANGUAGE, + RES_CHRATR_CTL_LANGUAGE + }; + static const LanguageType aLangs[] = + { + LANGUAGE_ENGLISH_US, + LANGUAGE_ENGLISH_US, + LANGUAGE_ARABIC_SAUDI_ARABIA + }; + static const DefaultFontType nFontTypes[] = + { + DefaultFontType::LATIN_HEADING, + DefaultFontType::CJK_HEADING, + DefaultFontType::CTL_HEADING + }; + + for( int i = 0; i < 3; ++i ) + { + LanguageType nLng = static_cast(m_rDoc.GetDefault( aLangTypes[i] )).GetLanguage(); + if( LANGUAGE_DONTKNOW == nLng ) + nLng = aLangs[i]; + + vcl::Font aFnt( OutputDevice::GetDefaultFont( nFontTypes[i], + nLng, GetDefaultFontFlags::OnlyOne ) ); + + aSet.Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), + OUString(), aFnt.GetPitch(), + aFnt.GetCharSet(), aFontWhich[i] )); + } + + SvxFontHeightItem aFntSize( PT_14, 100, RES_CHRATR_FONTSIZE ); + SvxULSpaceItem aUL( PT_12, PT_6, RES_UL_SPACE ); + if( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) + aUL.SetLower( HTML_PARSPACE ); + aSet.Put( SvxFormatKeepItem( true, RES_KEEP )); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + + aSet.Put( aUL ); + SetAllScriptItem( aSet, aFntSize ); + } + break; + + case RES_POOLCOLL_NUMBER_BULLET_BASE: // Base Numbering + break; + + case RES_POOLCOLL_GREETING: // Greeting + case RES_POOLCOLL_REGISTER_BASE: // Base indexes + case RES_POOLCOLL_SIGNATURE: // Signatures + case RES_POOLCOLL_TABLE: // Tabele content + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + if (nId == RES_POOLCOLL_TABLE) + { + aSet.Put( SvxWidowsItem( 0, RES_PARATR_WIDOWS ) ); + aSet.Put( SvxOrphansItem( 0, RES_PARATR_ORPHANS ) ); + } + } + break; + + case RES_POOLCOLL_HEADLINE1: // Heading 1 + { + SvxULSpaceItem aUL( PT_12, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 0, false ); + } + break; + case RES_POOLCOLL_HEADLINE2: // Heading 2 + { + SvxULSpaceItem aUL( PT_10, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 1, false ); + } + break; + case RES_POOLCOLL_HEADLINE3: // Heading 3 + { + SvxULSpaceItem aUL( PT_7, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 2, false ); + } + break; + case RES_POOLCOLL_HEADLINE4: // Heading 4 + { + SvxULSpaceItem aUL( PT_6, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 3, true ); + } + break; + case RES_POOLCOLL_HEADLINE5: // Heading 5 + { + SvxULSpaceItem aUL( PT_6, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 4, false ); + } + break; + case RES_POOLCOLL_HEADLINE6: // Heading 6 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 5, true ); + } + break; + case RES_POOLCOLL_HEADLINE7: // Heading 7 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 6, false ); + } + break; + case RES_POOLCOLL_HEADLINE8: // Heading 8 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 7, true ); + } + break; + case RES_POOLCOLL_HEADLINE9: // Heading 9 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 8, false ); + } + break; + case RES_POOLCOLL_HEADLINE10: // Heading 10 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 9, false ); + } + break; + + // Special sections: + // Header + case RES_POOLCOLL_HEADERFOOTER: + case RES_POOLCOLL_HEADER: + case RES_POOLCOLL_HEADERL: + case RES_POOLCOLL_HEADERR: + // Footer + case RES_POOLCOLL_FOOTER: + case RES_POOLCOLL_FOOTERL: + case RES_POOLCOLL_FOOTERR: + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + + long nRightMargin = lcl_GetRightMargin( m_rDoc ); + + SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( nRightMargin / 2, SvxTabAdjust::Center ) ); + aTStops.Insert( SvxTabStop( nRightMargin, SvxTabAdjust::Right ) ); + + aSet.Put( aTStops ); + + if ( (nId==RES_POOLCOLL_HEADERR) || (nId==RES_POOLCOLL_FOOTERR) ) { + SvxAdjustItem aAdjust(SvxAdjust::Right, RES_PARATR_ADJUST ); + aSet.Put(aAdjust); + } + } + break; + + case RES_POOLCOLL_TABLE_HDLN: + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST ) ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_FOOTNOTE: // paragraph style Footnote + case RES_POOLCOLL_ENDNOTE: // paragraph style Endnote + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( -static_cast( GetMetricVal( CM_05 ) + GetMetricVal( CM_01 ) ) ); + aLR.SetTextLeft( GetMetricVal( CM_05 ) + GetMetricVal( CM_01 ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_10, 100, RES_CHRATR_FONTSIZE ) ); + aSet.Put( aLR ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_LABEL: // basic caption + { + SvxULSpaceItem aUL( RES_UL_SPACE ); + aUL.SetUpper( PT_6 ); + aUL.SetLower( PT_6 ); + aSet.Put( aUL ); + SetAllScriptItem( aSet, SvxPostureItem( ITALIC_NORMAL, RES_CHRATR_POSTURE ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_10, 100, RES_CHRATR_FONTSIZE ) ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_FRAME: // Frame content + case RES_POOLCOLL_LABEL_ABB: // caption image + case RES_POOLCOLL_LABEL_TABLE: // caption table + case RES_POOLCOLL_LABEL_FRAME: // caption frame + case RES_POOLCOLL_LABEL_DRAWING: // caption drawing + case RES_POOLCOLL_LABEL_FIGURE: + break; + + case RES_POOLCOLL_JAKETADRESS: // envelope address + { + SvxULSpaceItem aUL( RES_UL_SPACE ); + aUL.SetLower( PT_3 ); + aSet.Put( aUL ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_SENDADRESS: // Sender address + { + if( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) + SetAllScriptItem( aSet, SvxPostureItem(ITALIC_NORMAL, RES_CHRATR_POSTURE) ); + else + { + SvxULSpaceItem aUL( RES_UL_SPACE ); aUL.SetLower( PT_3 ); + aSet.Put( aUL ); + } + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + // User defined indexes: + case RES_POOLCOLL_TOX_USERH: // Header + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_USER1: // 1st level + lcl_SetRegister( &m_rDoc, aSet, 0, false, true ); + break; + case RES_POOLCOLL_TOX_USER2: // 2nd level + lcl_SetRegister( &m_rDoc, aSet, 1, false, true ); + break; + case RES_POOLCOLL_TOX_USER3: // 3rd level + lcl_SetRegister( &m_rDoc, aSet, 2, false, true ); + break; + case RES_POOLCOLL_TOX_USER4: // 4th level + lcl_SetRegister( &m_rDoc, aSet, 3, false, true ); + break; + case RES_POOLCOLL_TOX_USER5: // 5th level + lcl_SetRegister( &m_rDoc, aSet, 4, false, true ); + break; + case RES_POOLCOLL_TOX_USER6: // 6th level + lcl_SetRegister( &m_rDoc, aSet, 5, false, true ); + break; + case RES_POOLCOLL_TOX_USER7: // 7th level + lcl_SetRegister( &m_rDoc, aSet, 6, false, true ); + break; + case RES_POOLCOLL_TOX_USER8: // 8th level + lcl_SetRegister( &m_rDoc, aSet, 7, false, true ); + break; + case RES_POOLCOLL_TOX_USER9: // 9th level + lcl_SetRegister( &m_rDoc, aSet, 8, false, true ); + break; + case RES_POOLCOLL_TOX_USER10: // 10th level + lcl_SetRegister( &m_rDoc, aSet, 9, false, true ); + break; + + // Index + case RES_POOLCOLL_TOX_IDXH: // Header + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_IDX1: // 1st level + lcl_SetRegister( &m_rDoc, aSet, 0, false, false ); + break; + case RES_POOLCOLL_TOX_IDX2: // 2nd level + lcl_SetRegister( &m_rDoc, aSet, 1, false, false ); + break; + case RES_POOLCOLL_TOX_IDX3: // 3rd level + lcl_SetRegister( &m_rDoc, aSet, 2, false, false ); + break; + case RES_POOLCOLL_TOX_IDXBREAK: // Separator + lcl_SetRegister( &m_rDoc, aSet, 0, false, false ); + break; + + // Table of Content + case RES_POOLCOLL_TOX_CNTNTH: // Header + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_CNTNT1: // 1st level + lcl_SetRegister( &m_rDoc, aSet, 0, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT2: // 2nd level + lcl_SetRegister( &m_rDoc, aSet, 1, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT3: // 3rd level + lcl_SetRegister( &m_rDoc, aSet, 2, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT4: // 4th level + lcl_SetRegister( &m_rDoc, aSet, 3, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT5: // 5th level + lcl_SetRegister( &m_rDoc, aSet, 4, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT6: // 6th level + lcl_SetRegister( &m_rDoc, aSet, 5, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT7: // 7th level + lcl_SetRegister( &m_rDoc, aSet, 6, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT8: // 8th level + lcl_SetRegister( &m_rDoc, aSet, 7, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT9: // 9th level + lcl_SetRegister( &m_rDoc, aSet, 8, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT10: // 10th level + lcl_SetRegister( &m_rDoc, aSet, 9, false, true ); + break; + + case RES_POOLCOLL_TOX_ILLUSH: + case RES_POOLCOLL_TOX_OBJECTH: + case RES_POOLCOLL_TOX_TABLESH: + case RES_POOLCOLL_TOX_AUTHORITIESH: + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_ILLUS1: + case RES_POOLCOLL_TOX_OBJECT1: + case RES_POOLCOLL_TOX_TABLES1: + case RES_POOLCOLL_TOX_AUTHORITIES1: + lcl_SetRegister( &m_rDoc, aSet, 0, false, true ); + break; + + case RES_POOLCOLL_NUM_LEVEL1S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL1, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 0 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL1, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 0 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL1E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL1, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 0 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM1, + 0, SwNumRule::GetNumIndent( 0 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL2S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL2, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 1 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL2, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 1 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL2E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL2, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 1 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM2, + 0, SwNumRule::GetNumIndent( 1 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL3S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL3, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 2 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL3, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 2 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL3E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL3, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 2 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM3, + 0, SwNumRule::GetNumIndent( 2 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL4S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL4, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 3 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL4, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 3 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL4E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL4, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 3 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM4, + 0, SwNumRule::GetNumIndent( 3 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL5S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL5, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 4 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL5, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 4 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL5E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL5, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 4 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM5, + 0, SwNumRule::GetNumIndent( 4 ), 0, PT_6 ); + break; + + case RES_POOLCOLL_BULLET_LEVEL1S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL1, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 0 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL1, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 0 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL1E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL1, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 0 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM1, + 0, SwNumRule::GetBullIndent( 0 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL2S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL2, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 1 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL2, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 1 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL2E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL2, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 1 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM2, + 0, SwNumRule::GetBullIndent( 1 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL3S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL3, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 2 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL3, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 2 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL3E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL3, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 2 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM3, + 0, SwNumRule::GetBullIndent( 2 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL4S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL4, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 3 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL4, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 3 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL4E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL4, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 3 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM4, + 0, SwNumRule::GetBullIndent( 3 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL5S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL5, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 4 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL5, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 4 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL5E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL5, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 4 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM5, + 0, SwNumRule::GetBullIndent( 4 ), 0, PT_6 ); + break; + + case RES_POOLCOLL_DOC_TITLE: // Document Title + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_28, 100, RES_CHRATR_FONTSIZE ) ); + + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST ) ); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + break; + + case RES_POOLCOLL_DOC_SUBTITLE: // Document subtitle + { + SvxULSpaceItem aUL( PT_3, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_18, 100, RES_CHRATR_FONTSIZE )); + + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST )); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + break; + + case RES_POOLCOLL_DOC_APPENDIX: // Document Appendix tdf#114090 + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_16, 100, RES_CHRATR_FONTSIZE ) ); + + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST ) ); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + break; + + case RES_POOLCOLL_HTML_BLOCKQUOTE: + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetLeft( GetMetricVal( CM_1 )); + aLR.SetRight( GetMetricVal( CM_1 )); + aSet.Put( aLR ); + std::unique_ptr aUL(pNewColl->GetULSpace().Clone()); + aUL->SetLower( HTML_PARSPACE ); + aSet.Put(std::move(aUL)); + } + break; + + case RES_POOLCOLL_HTML_PRE: + { + ::lcl_SetDfltFont( DefaultFontType::FIXED, aSet ); + + // WORKAROUND: Set PRE to 10pt + SetAllScriptItem( aSet, SvxFontHeightItem(PT_10, 100, RES_CHRATR_FONTSIZE) ); + + // The lower paragraph distance is set explicitly (makes + // assigning hard attributes easier) + std::unique_ptr aULSpaceItem(pNewColl->GetULSpace().Clone()); + aULSpaceItem->SetLower( 0 ); + aSet.Put(std::move(aULSpaceItem)); + } + break; + + case RES_POOLCOLL_HTML_HR: + { + SvxBoxItem aBox( RES_BOX ); + Color aColor( COL_GRAY ); + SvxBorderLine aNew(&aColor, 3, SvxBorderLineStyle::DOUBLE); + aBox.SetLine( &aNew, SvxBoxItemLine::BOTTOM ); + + aSet.Put( aBox ); + aSet.Put( SwParaConnectBorderItem( false ) ); + SetAllScriptItem( aSet, SvxFontHeightItem(120, 100, RES_CHRATR_FONTSIZE) ); + + std::unique_ptr aUL; + { + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + aUL.reset(pNewColl->GetULSpace().Clone()); + } + aUL->SetLower( HTML_PARSPACE ); + aSet.Put(std::move(aUL)); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_HTML_DD: + { + std::unique_ptr aLR(pNewColl->GetLRSpace().Clone()); + // We indent by 1 cm. The IDs are always 2 away from each other! + aLR->SetLeft( GetMetricVal( CM_1 )); + aSet.Put(std::move(aLR)); + } + break; + case RES_POOLCOLL_HTML_DT: + { + std::unique_ptr aLR; + { + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_HTML_DD )); + aLR.reset(pNewColl->GetLRSpace().Clone()); + } + // We indent by 0 cm. The IDs are always 2 away from each other! + aLR->SetLeft( 0 ); + aSet.Put( std::move(aLR) ); + } + break; + } + } + + if( aSet.Count() ) + pNewColl->SetFormatAttr( aSet ); + return pNewColl; +} + +/// Return the AutomaticFormat with the supplied Id. If it doesn't +/// exist, create it. +SwFormat* DocumentStylePoolManager::GetFormatFromPool( sal_uInt16 nId ) +{ + SwFormat *pNewFormat = nullptr; + SwFormat *pDeriveFormat = nullptr; + + SwFormatsBase* pArray[ 2 ]; + sal_uInt16 nArrCnt = 1; + const char* pRCId = nullptr; + sal_uInt16 const * pWhichRange = nullptr; + + switch( nId & (COLL_GET_RANGE_BITS + POOLGRP_NOCOLLID) ) + { + case POOLGRP_CHARFMT: + { + pArray[0] = m_rDoc.GetCharFormats(); + pDeriveFormat = m_rDoc.GetDfltCharFormat(); + pWhichRange = aCharFormatSetRange; + + if (nId >= RES_POOLCHR_HTML_BEGIN && nId < RES_POOLCHR_HTML_END) + pRCId = STR_POOLCHR_HTML_ARY[nId - RES_POOLCHR_HTML_BEGIN]; + else if (nId >= RES_POOLCHR_NORMAL_BEGIN && nId < RES_POOLCHR_NORMAL_END) + pRCId = STR_POOLCHR_ARY[nId - RES_POOLCHR_BEGIN]; + else + { + // Fault: unknown Format, but a CharFormat -> return the first one + OSL_ENSURE( false, "invalid Id" ); + pRCId = STR_POOLCHR_ARY[0]; + } + } + break; + case POOLGRP_FRAMEFMT: + { + pArray[0] = m_rDoc.GetFrameFormats(); + pArray[1] = m_rDoc.GetSpzFrameFormats(); + pDeriveFormat = m_rDoc.GetDfltFrameFormat(); + nArrCnt = 2; + pWhichRange = aFrameFormatSetRange; + + // Fault: unknown Format, but a FrameFormat + // -> return the first one + if( RES_POOLFRM_BEGIN > nId || nId >= RES_POOLFRM_END ) + { + OSL_ENSURE( false, "invalid Id" ); + nId = RES_POOLFRM_BEGIN; + } + + pRCId = STR_POOLFRM_ARY[nId - RES_POOLFRM_BEGIN]; + } + break; + + default: + // Fault, unknown Format + OSL_ENSURE( nId, "invalid Id" ); + return nullptr; + } + OSL_ENSURE(pRCId, "invalid Id"); + + while( nArrCnt-- ) + for( size_t n = 0; n < (*pArray[nArrCnt]).GetFormatCount(); ++n ) + { + pNewFormat = (*pArray[ nArrCnt ] ).GetFormat( n ); + if( nId == pNewFormat->GetPoolFormatId() ) + { + return pNewFormat; + } + } + + OUString aNm(SwResId(pRCId)); + SwAttrSet aSet( m_rDoc.GetAttrPool(), pWhichRange ); + + { + bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + switch (nId & (COLL_GET_RANGE_BITS + POOLGRP_NOCOLLID) ) + { + case POOLGRP_CHARFMT: + pNewFormat = m_rDoc.MakeCharFormat_(aNm, pDeriveFormat, false, true); + break; + case POOLGRP_FRAMEFMT: + pNewFormat = m_rDoc.MakeFrameFormat_(aNm, pDeriveFormat, false, true); + break; + default: + break; + } + } + + if( !bIsModified ) + m_rDoc.getIDocumentState().ResetModified(); + pNewFormat->SetPoolFormatId( nId ); + pNewFormat->SetAuto(false); // no AutoFormat + } + + switch( nId ) + { + case RES_POOLCHR_FOOTNOTE: // Footnote + case RES_POOLCHR_PAGENO: // Page/Field + case RES_POOLCHR_LABEL: // Label + case RES_POOLCHR_DROPCAPS: // Dropcaps + case RES_POOLCHR_NUM_LEVEL: // Numbering level + case RES_POOLCHR_TOXJUMP: // Table of contents jump + case RES_POOLCHR_ENDNOTE: // Endnote + case RES_POOLCHR_LINENUM: // Line numbering + break; + + case RES_POOLCHR_ENDNOTE_ANCHOR: // Endnote anchor + case RES_POOLCHR_FOOTNOTE_ANCHOR: // Footnote anchor + { + aSet.Put( SvxEscapementItem( DFLT_ESC_AUTO_SUPER, DFLT_ESC_PROP, RES_CHRATR_ESCAPEMENT ) ); + } + break; + + case RES_POOLCHR_BULLET_LEVEL: // Bullet character + { + const vcl::Font& rBulletFont = numfunc::GetDefBulletFont(); + SetAllScriptItem( aSet, SvxFontItem( rBulletFont.GetFamilyType(), + rBulletFont.GetFamilyName(), rBulletFont.GetStyleName(), + rBulletFont.GetPitch(), rBulletFont.GetCharSet(), RES_CHRATR_FONT )); + } + break; + + case RES_POOLCHR_INET_NORMAL: + { + aSet.Put( SvxColorItem( COL_BLUE, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE ) ); + // i40133: patch submitted by rail: set language to 'none' to prevent spell checking: + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CJK_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CTL_LANGUAGE ) ); + } + break; + case RES_POOLCHR_INET_VISIT: + { + aSet.Put( SvxColorItem( COL_RED, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CJK_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CTL_LANGUAGE ) ); + } + break; + case RES_POOLCHR_JUMPEDIT: + { + aSet.Put( SvxColorItem( COL_CYAN, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_DOTTED, RES_CHRATR_UNDERLINE ) ); + aSet.Put( SvxCaseMapItem( SvxCaseMap::SmallCaps, RES_CHRATR_CASEMAP ) ); + } + break; + + case RES_POOLCHR_RUBYTEXT: + { + long nH = GetDfltAttr( RES_CHRATR_CJK_FONTSIZE )->GetHeight() / 2; + SetAllScriptItem( aSet, SvxFontHeightItem( nH, 100, RES_CHRATR_FONTSIZE)); + aSet.Put(SvxUnderlineItem( LINESTYLE_NONE, RES_CHRATR_UNDERLINE )); + aSet.Put(SvxEmphasisMarkItem( FontEmphasisMark::NONE, RES_CHRATR_EMPHASIS_MARK) ); + } + break; + + case RES_POOLCHR_HTML_EMPHASIS: + case RES_POOLCHR_HTML_CITIATION: + case RES_POOLCHR_HTML_VARIABLE: + { + SetAllScriptItem( aSet, SvxPostureItem( ITALIC_NORMAL, RES_CHRATR_POSTURE) ); + } + break; + + case RES_POOLCHR_IDX_MAIN_ENTRY: + case RES_POOLCHR_HTML_STRONG: + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT )); + } + break; + + case RES_POOLCHR_HTML_CODE: + case RES_POOLCHR_HTML_SAMPLE: + case RES_POOLCHR_HTML_KEYBOARD: + case RES_POOLCHR_HTML_TELETYPE: + { + ::lcl_SetDfltFont( DefaultFontType::FIXED, aSet ); + } + break; + case RES_POOLCHR_VERT_NUM: + aSet.Put( SvxCharRotateItem( 900, false, RES_CHRATR_ROTATE ) ); + break; + + case RES_POOLFRM_FRAME: + { + if ( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::LINE_CENTER, text::RelOrientation::PRINT_AREA ) ); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + } + else + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_PARALLEL ) ); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::PRINT_AREA ) ); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::PRINT_AREA ) ); + Color aCol( COL_BLACK ); + SvxBorderLine aLine( &aCol, DEF_LINE_WIDTH_0 ); + SvxBoxItem aBox( RES_BOX ); + aBox.SetLine( &aLine, SvxBoxItemLine::TOP ); + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + aBox.SetLine( &aLine, SvxBoxItemLine::LEFT ); + aBox.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + aBox.SetAllDistances( 85 ); + aSet.Put( aBox ); + aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) ); + aSet.Put( SvxULSpaceItem( 114, 114, RES_UL_SPACE ) ); + } + + // for styles of FlyFrames do not set the FillStyle to make it a derived attribute + aSet.ClearItem(XATTR_FILLSTYLE); + } + break; + case RES_POOLFRM_GRAPHIC: + case RES_POOLFRM_OLE: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::FRAME )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_DYNAMIC )); + } + break; + case RES_POOLFRM_FORMEL: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR ) ); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::CHAR_CENTER, text::RelOrientation::FRAME ) ); + aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) ); + } + break; + case RES_POOLFRM_MARGINAL: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::LEFT, text::RelOrientation::FRAME )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_PARALLEL )); + // Set the default width to 3.5 cm, use the minimum value for the height + aSet.Put( SwFormatFrameSize( SwFrameSize::Minimum, + GetMetricVal( CM_1 ) * 3 + GetMetricVal( CM_05 ), + MM50 )); + } + break; + case RES_POOLFRM_WATERSIGN: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PAGE )); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::FRAME )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::CENTER, text::RelOrientation::FRAME )); + aSet.Put( SvxOpaqueItem( RES_OPAQUE, false )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH )); + } + break; + case RES_POOLFRM_LABEL: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR ) ); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ) ); + aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) ); + + SvxProtectItem aProtect( RES_PROTECT ); + aProtect.SetSizeProtect( true ); + aProtect.SetPosProtect( true ); + aSet.Put( aProtect ); + + pNewFormat->SetAutoUpdateFormat(); + } + break; + } + if( aSet.Count() ) + { + pNewFormat->SetFormatAttr( aSet ); + } + return pNewFormat; +} + +SwFrameFormat* DocumentStylePoolManager::GetFrameFormatFromPool( sal_uInt16 nId ) +{ + return static_cast(GetFormatFromPool( nId )); +} + +SwCharFormat* DocumentStylePoolManager::GetCharFormatFromPool( sal_uInt16 nId ) +{ + return static_cast(GetFormatFromPool( nId )); +} + +SwPageDesc* DocumentStylePoolManager::GetPageDescFromPool( sal_uInt16 nId, bool bRegardLanguage ) +{ + OSL_ENSURE( RES_POOLPAGE_BEGIN <= nId && nId < RES_POOLPAGE_END, + "Wrong AutoFormat Id" ); + + for( size_t n = 0; n < m_rDoc.GetPageDescCnt(); ++n ) + { + if ( nId == m_rDoc.GetPageDesc(n).GetPoolFormatId() ) + { + return &m_rDoc.GetPageDesc(n); + } + } + + if( RES_POOLPAGE_BEGIN > nId || nId >= RES_POOLPAGE_END ) + { + // unknown page pool ID + OSL_ENSURE( false, " - unknown page pool ID" ); + nId = RES_POOLPAGE_BEGIN; + } + + SwPageDesc* pNewPgDsc = nullptr; + { + static_assert(SAL_N_ELEMENTS(STR_POOLPAGE_ARY) == RES_POOLPAGE_END - RES_POOLPAGE_BEGIN, "### unexpected size!"); + const OUString aNm(SwResId(STR_POOLPAGE_ARY[nId - RES_POOLPAGE_BEGIN])); + const bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + pNewPgDsc = m_rDoc.MakePageDesc(aNm, nullptr, bRegardLanguage); + } + + pNewPgDsc->SetPoolFormatId( nId ); + if ( !bIsModified ) + { + m_rDoc.getIDocumentState().ResetModified(); + } + } + + SvxLRSpaceItem aLR( RES_LR_SPACE ); + { + aLR.SetLeft( GetMetricVal( CM_1 ) * 2 ); + aLR.SetRight( aLR.GetLeft() ); + } + SvxULSpaceItem aUL( RES_UL_SPACE ); + { + aUL.SetUpper( static_cast(aLR.GetLeft()) ); + aUL.SetLower( static_cast(aLR.GetLeft()) ); + } + + SwAttrSet aSet( m_rDoc.GetAttrPool(), aPgFrameFormatSetRange ); + bool bSetLeft = true; + + switch( nId ) + { + case RES_POOLPAGE_STANDARD: // "Default" + { + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All | UseOnPage::FirstShare ); + } + break; + + case RES_POOLPAGE_FIRST: // "First Page" + case RES_POOLPAGE_REGISTER: // "Index" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All ); + if( RES_POOLPAGE_FIRST == nId ) + pNewPgDsc->SetFollow( GetPageDescFromPool( RES_POOLPAGE_STANDARD )); + } + break; + + case RES_POOLPAGE_LEFT: // "Left Page" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + bSetLeft = false; + pNewPgDsc->SetUseOn( UseOnPage::Left ); + // this relies on GetPageDescFromPool() not going into infinite recursion + // (by this point RES_POOLPAGE_LEFT will not reach this place again) + pNewPgDsc->SetFollow( GetPageDescFromPool( RES_POOLPAGE_RIGHT )); + } + break; + case RES_POOLPAGE_RIGHT: // "Right Page" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + bSetLeft = false; + pNewPgDsc->SetUseOn( UseOnPage::Right ); + pNewPgDsc->SetFollow( GetPageDescFromPool( RES_POOLPAGE_LEFT )); + } + break; + + case RES_POOLPAGE_JAKET: // "Envelope" + { + Size aPSize( SvxPaperInfo::GetPaperSize( PAPER_ENV_C65 ) ); + LandscapeSwap( aPSize ); + aSet.Put( SwFormatFrameSize( SwFrameSize::Fixed, aPSize.Width(), aPSize.Height() )); + aLR.SetLeft( 0 ); aLR.SetRight( 0 ); + aUL.SetUpper( 0 ); aUL.SetLower( 0 ); + aSet.Put( aLR ); + aSet.Put( aUL ); + + pNewPgDsc->SetUseOn( UseOnPage::All ); + pNewPgDsc->SetLandscape( true ); + } + break; + + case RES_POOLPAGE_HTML: // "HTML" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aLR.SetRight( GetMetricVal( CM_1 )); + aUL.SetUpper( static_cast(aLR.GetRight()) ); + aUL.SetLower( static_cast(aLR.GetRight()) ); + aSet.Put( aLR ); + aSet.Put( aUL ); + + pNewPgDsc->SetUseOn( UseOnPage::All ); + } + break; + + case RES_POOLPAGE_FOOTNOTE: // "Footnote" + case RES_POOLPAGE_ENDNOTE: // "Endnote" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All ); + SwPageFootnoteInfo aInf( pNewPgDsc->GetFootnoteInfo() ); + aInf.SetLineWidth( 0 ); + aInf.SetTopDist( 0 ); + aInf.SetBottomDist( 0 ); + pNewPgDsc->SetFootnoteInfo( aInf ); + } + break; + + case RES_POOLPAGE_LANDSCAPE: // "Landscape" + { + SwPageDesc* pStdPgDsc = GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + SwFormatFrameSize aFrameSz( pStdPgDsc->GetMaster().GetFrameSize() ); + if ( !pStdPgDsc->GetLandscape() ) + { + const SwTwips nTmp = aFrameSz.GetHeight(); + aFrameSz.SetHeight( aFrameSz.GetWidth() ); + aFrameSz.SetWidth( nTmp ); + } + aSet.Put( aFrameSz ); + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All ); + pNewPgDsc->SetLandscape( true ); + } + break; + + } + + if( aSet.Count() ) + { + if( bSetLeft ) + { + pNewPgDsc->GetLeft().SetFormatAttr( aSet ); + pNewPgDsc->GetFirstLeft().SetFormatAttr( aSet ); + } + pNewPgDsc->GetMaster().SetFormatAttr( aSet ); + pNewPgDsc->GetFirstMaster().SetFormatAttr( aSet ); + } + return pNewPgDsc; +} + +SwNumRule* DocumentStylePoolManager::GetNumRuleFromPool( sal_uInt16 nId ) +{ + OSL_ENSURE( RES_POOLNUMRULE_BEGIN <= nId && nId < RES_POOLNUMRULE_END, + "Wrong AutoFormat Id" ); + + SwNumRule* pNewRule; + + for (size_t n = 0; n < m_rDoc.GetNumRuleTable().size(); ++n ) + { + pNewRule = m_rDoc.GetNumRuleTable()[ n ]; + if (nId == pNewRule->GetPoolFormatId()) + { + return pNewRule; + } + } + + // error: unknown Pool style + if( RES_POOLNUMRULE_BEGIN > nId || nId >= RES_POOLNUMRULE_END ) + { + OSL_ENSURE( false, "invalid Id" ); + nId = RES_POOLNUMRULE_BEGIN; + } + + static_assert(SAL_N_ELEMENTS(STR_POOLNUMRULE_NUM_ARY) == RES_POOLNUMRULE_END - RES_POOLNUMRULE_BEGIN, "### unexpected size!"); + OUString aNm(SwResId(STR_POOLNUMRULE_NUM_ARY[nId - RES_POOLNUMRULE_BEGIN])); + + SwCharFormat *pNumCFormat = nullptr, *pBullCFormat = nullptr; + + const SvxNumberFormat::SvxNumPositionAndSpaceMode eNumberFormatPositionAndSpaceMode + = numfunc::GetDefaultPositionAndSpaceMode(); //#i89178# + { + bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + sal_uInt16 n = m_rDoc.MakeNumRule( aNm, nullptr, false, eNumberFormatPositionAndSpaceMode ); + + pNewRule = m_rDoc.GetNumRuleTable()[ n ]; + pNewRule->SetPoolFormatId( nId ); + pNewRule->SetAutoRule( false ); + + if( RES_POOLNUMRULE_NUM1 <= nId && nId <= RES_POOLNUMRULE_NUM5 ) + pNumCFormat = GetCharFormatFromPool( RES_POOLCHR_NUM_LEVEL ); + + if( ( RES_POOLNUMRULE_BUL1 <= nId && nId <= RES_POOLNUMRULE_BUL5 ) || + RES_POOLNUMRULE_NUM5 == nId ) + pBullCFormat = GetCharFormatFromPool( RES_POOLCHR_NUM_LEVEL ); + + if( !bIsModified ) + m_rDoc.getIDocumentState().ResetModified(); + } + + switch( nId ) + { + case RES_POOLNUMRULE_NUM1: + { + SwNumFormat aFormat; + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ARABIC); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0.7 cm intervals, with 1 cm = 567 + 397, 794, 1191, 1588, 1985, 2381, 2778, 3175, 3572, 3969 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr + 357 ); // 357 is indent of 0.63 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr + 357 ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + + case RES_POOLNUMRULE_NUM2: + { + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { + 397, 397, 397, 397, // 0.70 cm intervals + 397, 397, 397, 397, + 397, 397 + }; + + const sal_uInt16* pArr = aAbsSpace; + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHARS_UPPER_LETTER); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + } + + sal_uInt16 nSpace = 357; // indent of 0.63 cm + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + nSpace += pArr[ n ]; + aFormat.SetAbsLSpace( nSpace ); + aFormat.SetFirstLineOffset( - pArr[ n ] ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + nSpace += pArr[ n ]; + aFormat.SetListtabPos( nSpace ); + aFormat.SetIndentAt( nSpace ); + aFormat.SetFirstLineIndent( - pArr[ n ] ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_NUM3: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + + long const nOffs = 397; // 0.70 cm + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - nOffs ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( (n+1) * nOffs + 357 ); // 357 is indent of 0.63 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + long nPos = (n+1) * nOffs; + aFormat.SetListtabPos(nPos + 357); + aFormat.SetIndentAt(nPos + 357); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_NUM4: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ROMAN_UPPER); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + aFormat.SetNumAdjust( SvxAdjust::Right ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 1.33 cm intervals + 754, 1508, 1191, 2262, 3016, 3771, 4525, 5279, 6033, 6787 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( 580 - (*pArr) ); // 1 cm space + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( 580 - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_NUM5: + { + // [ First, LSpace ] + static const sal_uInt16 aAbsSpace0to2[] = + { + 174, 754, // 0.33, 1.33, + 567, 1151, // 1.03, 2.03, + 397, 1548 // 2.03, 2.73 + }; + + const sal_uInt16* pArr0to2 = aAbsSpace0to2; + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + aFormat.SetNumAdjust( SvxAdjust::Right ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + } + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( -pArr0to2[0] ); // == 0.33 cm + aFormat.SetAbsLSpace( pArr0to2[1] ); // == 1.33 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( -pArr0to2[0] ); + aFormat.SetListtabPos( pArr0to2[1] ); + aFormat.SetIndentAt( pArr0to2[1] ); + } + + aFormat.SetCharFormat( pNumCFormat ); + pNewRule->Set( 0, aFormat ); + + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetStart( 1 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( -pArr0to2[2] ); // == 1.03 cm + aFormat.SetAbsLSpace( pArr0to2[3] ); // == 2.03 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( -pArr0to2[2] ); + aFormat.SetListtabPos( pArr0to2[3] ); + aFormat.SetIndentAt( pArr0to2[3] ); + } + + pNewRule->Set( 1, aFormat ); + + aFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); + aFormat.SetSuffix(OUString(u')')); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetStart( 3 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - pArr0to2[4] ); // == 2.03 cm + aFormat.SetAbsLSpace( pArr0to2[5] ); // == 2.73 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( -pArr0to2[4] ); + aFormat.SetListtabPos( pArr0to2[5] ); + aFormat.SetIndentAt( pArr0to2[5] ); + } + + pNewRule->Set( 2, aFormat ); + + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + aFormat.SetBulletChar( cBulletChar ); + sal_Int16 nOffs = GetMetricVal( CM_01 ) * 4, + nOffs2 = GetMetricVal( CM_1 ) * 2; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( - nOffs ); + } + + aFormat.SetSuffix( OUString() ); + for (sal_uInt16 n = 3; n < MAXLEVEL; ++n) + { + aFormat.SetStart( n+1 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( nOffs2 + ((n-3) * nOffs) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + long nPos = nOffs2 + ((n-3) * static_cast(nOffs)); + aFormat.SetListtabPos(nPos); + aFormat.SetIndentAt(nPos); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + + case RES_POOLNUMRULE_BUL1: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + aFormat.SetBulletChar( cBulletChar ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,4 0,8 1,2 1,6 2,0 2,4 2,8 3,2 3,6 4,0 + 227, 454, 680, 907, 1134, 1361, 1587, 1814, 2041, 2268 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL2: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + aFormat.SetBulletChar( 0x2013 ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,3 0,6 0,9 1,2 1,5 1,8 2,1 2,4 2,7 3,0 + 170, 340, 510, 680, 850, 1020, 1191, 1361, 1531, 1701 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL3: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + + sal_uInt16 nOffs = GetMetricVal( CM_01 ) * 4; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - nOffs ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n) + { + aFormat.SetBulletChar( (n & 1) ? 0x25a1 : 0x2611 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( ((n & 1) +1) * nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + long nPos = ((n & 1) +1) * static_cast(nOffs); + aFormat.SetListtabPos(nPos); + aFormat.SetIndentAt(nPos); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL4: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,4 0,8 1,2 1,6 2,0 2,4 2,8 3,2 3,6 4,0 + 227, 454, 680, 907, 1134, 1361, 1587, 1814, 2041, 2268 + }; + + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::SPACE ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + switch( n ) + { + case 0: aFormat.SetBulletChar( 0x27a2 ); break; + case 1: aFormat.SetBulletChar( 0xE006 ); break; + default: aFormat.SetBulletChar( 0xE004 ); break; + } + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL5: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletChar( 0x2717 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,4 0,8 1,2 1,6 2,0 2,4 2,8 3,2 3,6 4,0 + 227, 454, 680, 907, 1134, 1361, 1587, 1814, 2041, 2268 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + } + + return pNewRule; +} + +/// Check if this AutoCollection is already/still in use in this Document +bool DocumentStylePoolManager::IsPoolTextCollUsed( sal_uInt16 nId ) const +{ + OSL_ENSURE( + (RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END) || + (RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END) || + (RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END) || + (RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END) || + (RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END) || + (RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END), + "Wrong AutoFormat Id" ); + + SwTextFormatColl* pNewColl = nullptr; + bool bFnd = false; + for( SwTextFormatColls::size_type n = 0; !bFnd && n < m_rDoc.GetTextFormatColls()->size(); ++n ) + { + pNewColl = (*m_rDoc.GetTextFormatColls())[ n ]; + if( nId == pNewColl->GetPoolFormatId() ) + bFnd = true; + } + + if( !bFnd || !pNewColl->HasWriterListeners() ) + return false; + + SwAutoFormatGetDocNode aGetHt( &m_rDoc.GetNodes() ); + return !pNewColl->GetInfo( aGetHt ); +} + +/// Check if this AutoCollection is already/still in use +bool DocumentStylePoolManager::IsPoolFormatUsed( sal_uInt16 nId ) const +{ + const SwFormat *pNewFormat = nullptr; + const SwFormatsBase* pArray[ 2 ]; + sal_uInt16 nArrCnt = 1; + bool bFnd = true; + + if (RES_POOLCHR_BEGIN <= nId && nId < RES_POOLCHR_END) + { + pArray[0] = m_rDoc.GetCharFormats(); + } + else if (RES_POOLFRM_BEGIN <= nId && nId < RES_POOLFRM_END) + { + pArray[0] = m_rDoc.GetFrameFormats(); + pArray[1] = m_rDoc.GetSpzFrameFormats(); + nArrCnt = 2; + } + else + { + SAL_WARN("sw.core", "Invalid Pool Id: " << nId << " should be within " + "[" << int(RES_POOLCHR_BEGIN) << "," << int(RES_POOLCHR_END) << ") or " + "[" << int(RES_POOLFRM_BEGIN) << "," << int(RES_POOLFRM_END) << ")"); + bFnd = false; + } + + if( bFnd ) + { + bFnd = false; + while( nArrCnt-- && !bFnd ) + for( size_t n = 0; !bFnd && n < (*pArray[nArrCnt]).GetFormatCount(); ++n ) + { + pNewFormat = (*pArray[ nArrCnt ] ).GetFormat( n ); + if( nId == pNewFormat->GetPoolFormatId() ) + bFnd = true; + } + } + + // Not found or no dependencies? + if( bFnd && pNewFormat->HasWriterListeners() ) + { + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + SwAutoFormatGetDocNode aGetHt( &m_rDoc.GetNodes() ); + bFnd = !pNewFormat->GetInfo( aGetHt ); + } + else + bFnd = false; + + return bFnd; +} + +/// Check if this AutoCollection is already/still in use in this Document +bool DocumentStylePoolManager::IsPoolPageDescUsed( sal_uInt16 nId ) const +{ + OSL_ENSURE( RES_POOLPAGE_BEGIN <= nId && nId < RES_POOLPAGE_END, + "Wrong AutoFormat Id" ); + SwPageDesc *pNewPgDsc = nullptr; + bool bFnd = false; + for( size_t n = 0; !bFnd && n < m_rDoc.GetPageDescCnt(); ++n ) + { + pNewPgDsc = &m_rDoc.GetPageDesc(n); + if( nId == pNewPgDsc->GetPoolFormatId() ) + bFnd = true; + } + + // Not found or no dependencies? + if( !bFnd || !pNewPgDsc->HasWriterListeners() ) // ?????? + return false; + + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + SwAutoFormatGetDocNode aGetHt( &m_rDoc.GetNodes() ); + return !pNewPgDsc->GetInfo( aGetHt ); +} + +DocumentStylePoolManager::~DocumentStylePoolManager() +{ +} + +} + +static std::vector +lcl_NewUINameArray(const char** pIds, const size_t nLen, const size_t nSvxIds = 0) +{ + assert(nSvxIds <= nLen); + const size_t nWriterIds = nLen - nSvxIds; + std::vector aNameArray; + aNameArray.reserve(nLen); + for (size_t i = 0; i < nWriterIds; ++i) + aNameArray.push_back(SwResId(pIds[i])); + for (size_t i = nWriterIds; i < nLen; ++i) + aNameArray.push_back(SvxResId(pIds[i])); + return aNameArray; +} + +const std::vector& SwStyleNameMapper::GetTextUINameArray() +{ + static const std::vector s_aTextUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_TEXT_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_TEXT_ARY))); + return s_aTextUINameArray; +} + +const std::vector& SwStyleNameMapper::GetListsUINameArray() +{ + static const std::vector s_aListsUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_LISTS_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_LISTS_ARY))); + return s_aListsUINameArray; +} + +const std::vector& SwStyleNameMapper::GetExtraUINameArray() +{ + static const std::vector s_aExtraUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_EXTRA_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_EXTRA_ARY))); + return s_aExtraUINameArray; +} + +const std::vector& SwStyleNameMapper::GetRegisterUINameArray() +{ + static const std::vector s_aRegisterUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_REGISTER_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_REGISTER_ARY))); + return s_aRegisterUINameArray; +} + +const std::vector& SwStyleNameMapper::GetDocUINameArray() +{ + static const std::vector s_aDocUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_DOC_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_DOC_ARY))); + return s_aDocUINameArray; +} + +const std::vector& SwStyleNameMapper::GetHTMLUINameArray() +{ + static const std::vector s_aHTMLUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_HTML_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_HTML_ARY))); + return s_aHTMLUINameArray; +} + +const std::vector& SwStyleNameMapper::GetFrameFormatUINameArray() +{ + static const std::vector s_aFrameFormatUINameArray( + lcl_NewUINameArray(STR_POOLFRM_ARY, SAL_N_ELEMENTS(STR_POOLFRM_ARY))); + return s_aFrameFormatUINameArray; +} + +const std::vector& SwStyleNameMapper::GetChrFormatUINameArray() +{ + static const std::vector s_aChrFormatUINameArray( + lcl_NewUINameArray(STR_POOLCHR_ARY, SAL_N_ELEMENTS(STR_POOLCHR_ARY))); + return s_aChrFormatUINameArray; +} + +const std::vector& SwStyleNameMapper::GetHTMLChrFormatUINameArray() +{ + static const std::vector s_aHTMLChrFormatUINameArray( + lcl_NewUINameArray(STR_POOLCHR_HTML_ARY, SAL_N_ELEMENTS(STR_POOLCHR_HTML_ARY))); + return s_aHTMLChrFormatUINameArray; +} + +const std::vector& SwStyleNameMapper::GetPageDescUINameArray() +{ + static const std::vector s_aPageDescUINameArray( + lcl_NewUINameArray(STR_POOLPAGE_ARY, SAL_N_ELEMENTS(STR_POOLPAGE_ARY))); + return s_aPageDescUINameArray; +} + +const std::vector& SwStyleNameMapper::GetNumRuleUINameArray() +{ + static const std::vector s_aNumRuleUINameArray( + lcl_NewUINameArray(STR_POOLNUMRULE_NUM_ARY, SAL_N_ELEMENTS(STR_POOLNUMRULE_NUM_ARY))); + return s_aNumRuleUINameArray; +} + +const std::vector& SwStyleNameMapper::GetTableStyleUINameArray() +{ + static const std::vector s_aTableStyleUINameArray( + // 1 Writer resource string (XXX if this ever changes rather use offset math) + lcl_NewUINameArray(STR_TABSTYLE_ARY, SAL_N_ELEMENTS(STR_TABSTYLE_ARY), + static_cast(SAL_N_ELEMENTS(STR_TABSTYLE_ARY) - 1))); + return s_aTableStyleUINameArray; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentTimerManager.cxx b/sw/source/core/doc/DocumentTimerManager.cxx new file mode 100644 index 000000000..842c1262d --- /dev/null +++ b/sw/source/core/doc/DocumentTimerManager.cxx @@ -0,0 +1,236 @@ +/* -*- 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 + +namespace sw +{ +DocumentTimerManager::DocumentTimerManager(SwDoc& i_rSwdoc) + : m_rDoc(i_rSwdoc) + , m_nIdleBlockCount(0) + , m_bStartOnUnblock(false) + , m_aDocIdle(i_rSwdoc) + , m_aFireIdleJobsTimer("sw::DocumentTimerManager m_aFireIdleJobsTimer") + , m_bWaitForLokInit(true) +{ + m_aDocIdle.SetPriority(TaskPriority::LOWEST); + m_aDocIdle.SetInvokeHandler(LINK(this, DocumentTimerManager, DoIdleJobs)); + m_aDocIdle.SetDebugName("sw::DocumentTimerManager m_aDocIdle"); + + m_aFireIdleJobsTimer.SetInvokeHandler(LINK(this, DocumentTimerManager, FireIdleJobsTimeout)); + m_aFireIdleJobsTimer.SetTimeout(1000); // Enough time for LOK to render the first tiles. +} + +void DocumentTimerManager::StartIdling() +{ + if (m_bWaitForLokInit && comphelper::LibreOfficeKit::isActive()) + { + // Start the idle jobs only after a certain delay. + m_bWaitForLokInit = false; + StopIdling(); + m_aFireIdleJobsTimer.Start(); + return; + } + + m_bWaitForLokInit = false; + m_bStartOnUnblock = true; + if (0 == m_nIdleBlockCount) + { + if (!m_aDocIdle.IsActive()) + m_aDocIdle.Start(); + else + Scheduler::Wakeup(); + } +} + +void DocumentTimerManager::StopIdling() +{ + m_bStartOnUnblock = false; + m_aDocIdle.Stop(); +} + +void DocumentTimerManager::BlockIdling() +{ + assert(SAL_MAX_UINT32 != m_nIdleBlockCount); + ++m_nIdleBlockCount; +} + +void DocumentTimerManager::UnblockIdling() +{ + assert(0 != m_nIdleBlockCount); + --m_nIdleBlockCount; + + if ((0 == m_nIdleBlockCount) && m_bStartOnUnblock) + { + if (!m_aDocIdle.IsActive()) + m_aDocIdle.Start(); + else + Scheduler::Wakeup(); + } +} + +IMPL_LINK(DocumentTimerManager, FireIdleJobsTimeout, Timer*, , void) +{ + // Now we can run the idle jobs, assuming we finished LOK initialization. + StartIdling(); +} + +DocumentTimerManager::IdleJob DocumentTimerManager::GetNextIdleJob() const +{ + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot && + !SfxProgress::GetActiveProgress( m_rDoc.GetDocShell() ) ) + { + SwViewShell* pShell(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()); + for(const SwViewShell& rSh : pShell->GetRingContainer()) + if( rSh.ActionPend() ) + return IdleJob::Busy; + + if( pTmpRoot->IsNeedGrammarCheck() ) + { + bool bIsOnlineSpell = pShell->GetViewOptions()->IsOnlineSpell(); + bool bIsAutoGrammar = false; + SvtLinguConfig().GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bIsAutoGrammar; + + if( bIsOnlineSpell && bIsAutoGrammar && m_rDoc.StartGrammarChecking( true ) ) + return IdleJob::Grammar; + } + + // If we're dragging re-layout doesn't occur so avoid a busy loop. + if (!pShell->HasDrawViewDrag()) + { + for ( auto pLayout : m_rDoc.GetAllLayouts() ) + { + if( pLayout->IsIdleFormat() ) + return IdleJob::Layout; + } + } + + SwFieldUpdateFlags nFieldUpdFlag = m_rDoc.GetDocumentSettingManager().getFieldUpdateFlags(true); + if( ( AUTOUPD_FIELD_ONLY == nFieldUpdFlag + || AUTOUPD_FIELD_AND_CHARTS == nFieldUpdFlag ) + && m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().IsFieldsDirty() ) + { + if( m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().IsInUpdateFields() + || m_rDoc.getIDocumentFieldsAccess().IsExpFieldsLocked() ) + return IdleJob::Busy; + return IdleJob::Fields; + } + } + + return IdleJob::None; +} + +IMPL_LINK_NOARG( DocumentTimerManager, DoIdleJobs, Timer*, void ) +{ +#ifdef TIMELOG + static ::rtl::Logfile* pModLogFile = new ::rtl::Logfile( "First DoIdleJobs" ); +#endif + BlockIdling(); + StopIdling(); + + IdleJob eJob = GetNextIdleJob(); + + switch ( eJob ) + { + case IdleJob::Grammar: + m_rDoc.StartGrammarChecking(); + break; + + case IdleJob::Layout: + for ( auto pLayout : m_rDoc.GetAllLayouts() ) + if( pLayout->IsIdleFormat() ) + { + pLayout->GetCurrShell()->LayoutIdle(); + break; + } + break; + + case IdleJob::Fields: + { + SwViewShell* pShell( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ); + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + + // Action brackets! + m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().SetInUpdateFields( true ); + + pTmpRoot->StartAllAction(); + + // no jump on update of fields #i85168# + const bool bOldLockView = pShell->IsViewLocked(); + pShell->LockView( true ); + + m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Chapter )->ModifyNotification( nullptr, nullptr ); // ChapterField + m_rDoc.getIDocumentFieldsAccess().UpdateExpFields( nullptr, false ); // Updates ExpressionFields + m_rDoc.getIDocumentFieldsAccess().UpdateTableFields(nullptr); // Tables + m_rDoc.getIDocumentFieldsAccess().UpdateRefFields(); // References + + // Validate and update the paragraph signatures. + if (m_rDoc.GetEditShell()) + m_rDoc.GetEditShell()->ValidateAllParagraphSignatures(true); + + pTmpRoot->EndAllAction(); + + pShell->LockView( bOldLockView ); + + m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().SetInUpdateFields( false ); + m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().SetFieldsDirty( false ); + break; + } + + case IdleJob::Busy: + break; + case IdleJob::None: + break; + } + + if ( IdleJob::None != eJob ) + StartIdling(); + UnblockIdling(); + +#ifdef TIMELOG + if( pModLogFile && 1 != (long)pModLogFile ) + delete pModLogFile, static_cast(pModLogFile) = 1; +#endif +} + +DocumentTimerManager::~DocumentTimerManager() {} + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/SwDocIdle.cxx b/sw/source/core/doc/SwDocIdle.cxx new file mode 100644 index 000000000..24f51c90e --- /dev/null +++ b/sw/source/core/doc/SwDocIdle.cxx @@ -0,0 +1,62 @@ +/* -*- 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 + +namespace sw +{ + +sal_uInt64 SwDocIdle::UpdateMinPeriod( sal_uInt64 /* nTimeNow */ ) const +{ + bool bReadyForSchedule = true; + + SwView* pView = m_rDoc.GetDocShell() ? m_rDoc.GetDocShell()->GetView() : nullptr; + if( pView ) + { + SwWrtShell& rWrtShell = pView->GetWrtShell(); + bReadyForSchedule = rWrtShell.GetViewOptions()->IsIdle(); + } + + if( bReadyForSchedule && !m_rDoc.getIDocumentTimerAccess().IsDocIdle() ) + bReadyForSchedule = false; + + return bReadyForSchedule + ? Scheduler::ImmediateTimeoutMs : Scheduler::InfiniteTimeoutMs; +} + +SwDocIdle::SwDocIdle( SwDoc &doc ) + : m_rDoc( doc ) +{ +} + +SwDocIdle::~SwDocIdle() +{ +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/SwStyleNameMapper.cxx b/sw/source/core/doc/SwStyleNameMapper.cxx new file mode 100644 index 000000000..9f2f88c6d --- /dev/null +++ b/sw/source/core/doc/SwStyleNameMapper.cxx @@ -0,0 +1,772 @@ +/* -*- 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 + +#ifdef _NEED_TO_DEBUG_MAPPING +#include +#endif + +namespace +{ + +const OUString & +lcl_GetSpecialExtraName(const OUString& rExtraName, const bool bIsUIName ) +{ + const std::vector& rExtraArr = bIsUIName + ? SwStyleNameMapper::GetExtraUINameArray() + : SwStyleNameMapper::GetExtraProgNameArray(); + static const sal_uInt16 nIds[] = + { + RES_POOLCOLL_LABEL_DRAWING - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_ABB - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_TABLE - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_FRAME- RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_FIGURE-RES_POOLCOLL_EXTRA_BEGIN, + 0 + }; + const sal_uInt16 * pIds; + for ( pIds = nIds; *pIds; ++pIds) + { + if (rExtraName == rExtraArr[ *pIds ]) + { + return bIsUIName + ? SwStyleNameMapper::GetExtraProgNameArray()[*pIds] + : SwStyleNameMapper::GetExtraUINameArray()[*pIds]; + } + } + return rExtraName; +} + +bool lcl_SuffixIsUser(const OUString & rString) +{ + const sal_Unicode *pChar = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + bool bRet = false; + if( nLen > 8 && + pChar[nLen-7] == ' ' && + pChar[nLen-6] == '(' && + pChar[nLen-5] == 'u' && + pChar[nLen-4] == 's' && + pChar[nLen-3] == 'e' && + pChar[nLen-2] == 'r' && + pChar[nLen-1] == ')' ) + bRet = true; + return bRet; +} + +void lcl_CheckSuffixAndDelete(OUString & rString) +{ + if (lcl_SuffixIsUser(rString)) + { + rString = rString.copy(0, rString.getLength() - 7); + } +} + +NameToIdHash HashFromRange(sal_uInt16 nAcc) { return NameToIdHash(nAcc); } +template +NameToIdHash HashFromRange(sal_uInt16 nAcc, sal_uInt16 nBegin, sal_uInt16 nEnd, + const std::vector& (*pFunc)(), Rest... rest) +{ + NameToIdHash hash(HashFromRange(nAcc + nEnd - nBegin, rest...)); + sal_uInt16 nIndex, nId; + const std::vector& rStrings = pFunc(); + for (nIndex = 0, nId = nBegin; nId < nEnd; nId++, nIndex++) + hash[rStrings[nIndex]] = nId; + return hash; +} + +template struct TablePair +{ + static const NameToIdHash& getMap(bool bProgName) + { + if (bProgName) + { + static const NameToIdHash s_aProgMap(initFunc(true)); + return s_aProgMap; + } + static const NameToIdHash s_aUIMap(initFunc(false)); + return s_aUIMap; + } +}; + +NameToIdHash GetParaMap (bool bProgName) +{ + return HashFromRange(0, + RES_POOLCOLL_TEXT_BEGIN, RES_POOLCOLL_TEXT_END, bProgName ? &SwStyleNameMapper::GetTextProgNameArray : &SwStyleNameMapper::GetTextUINameArray, + RES_POOLCOLL_LISTS_BEGIN, RES_POOLCOLL_LISTS_END, bProgName ? &SwStyleNameMapper::GetListsProgNameArray : &SwStyleNameMapper::GetListsUINameArray, + RES_POOLCOLL_EXTRA_BEGIN, RES_POOLCOLL_EXTRA_END, bProgName ? &SwStyleNameMapper::GetExtraProgNameArray : &SwStyleNameMapper::GetExtraUINameArray, + RES_POOLCOLL_REGISTER_BEGIN, RES_POOLCOLL_REGISTER_END, bProgName ? &SwStyleNameMapper::GetRegisterProgNameArray : &SwStyleNameMapper::GetRegisterUINameArray, + RES_POOLCOLL_DOC_BEGIN, RES_POOLCOLL_DOC_END, bProgName ? &SwStyleNameMapper::GetDocProgNameArray : &SwStyleNameMapper::GetDocUINameArray, + RES_POOLCOLL_HTML_BEGIN, RES_POOLCOLL_HTML_END, bProgName ? &SwStyleNameMapper::GetHTMLProgNameArray : &SwStyleNameMapper::GetHTMLUINameArray + ); +}; + +NameToIdHash GetCharMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLCHR_NORMAL_BEGIN, RES_POOLCHR_NORMAL_END, bProgName ? &SwStyleNameMapper::GetChrFormatProgNameArray : &SwStyleNameMapper::GetChrFormatUINameArray, + RES_POOLCHR_HTML_BEGIN, RES_POOLCHR_HTML_END, bProgName ? &SwStyleNameMapper::GetHTMLChrFormatProgNameArray : &SwStyleNameMapper::GetHTMLChrFormatUINameArray + ); +}; + +NameToIdHash GetFrameMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLFRM_BEGIN, RES_POOLFRM_END, bProgName ? &SwStyleNameMapper::GetFrameFormatProgNameArray : &SwStyleNameMapper::GetFrameFormatUINameArray + ); +}; + +NameToIdHash GetPageMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLPAGE_BEGIN, RES_POOLPAGE_END, bProgName ? &SwStyleNameMapper::GetPageDescProgNameArray : &SwStyleNameMapper::GetPageDescUINameArray + ); +}; + +NameToIdHash GetNumRuleMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLNUMRULE_BEGIN, RES_POOLNUMRULE_END, bProgName ? &SwStyleNameMapper::GetNumRuleProgNameArray : &SwStyleNameMapper::GetNumRuleUINameArray + ); +}; + +NameToIdHash GetTableStyleMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLTABLESTYLE_BEGIN, RES_POOLTABLESTYLE_END, bProgName ? &SwStyleNameMapper::GetTableStyleProgNameArray : &SwStyleNameMapper::GetTableStyleUINameArray + ); +}; + +NameToIdHash GetCellStyleMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLCELLSTYLE_BEGIN, RES_POOLCELLSTYLE_END, bProgName ? &SwStyleNameMapper::GetCellStyleProgNameArray : &SwStyleNameMapper::GetCellStyleUINameArray + ); +}; + +} // namespace + +#ifdef _NEED_TO_DEBUG_MAPPING +void SwStyleNameMapper::testNameTable( SwGetPoolIdFromName const nFamily, sal_uInt16 const nStartIndex, sal_uInt16 const nEndIndex ) +{ + sal_uInt16 nIndex; + sal_uInt16 nId; + + for ( nIndex = 0, nId = nStartIndex ; nId < nEndIndex ; nId++,nIndex++ ) + { + OUString aString, bString; + FillUIName ( nId, aString ); + bString = GetProgName ( nFamily, aString ); + sal_uInt16 nNewId = GetPoolIdFromProgName ( bString, nFamily ); + FillProgName ( nNewId, aString ); + bString = GetUIName ( aString, nFamily ); + nNewId = GetPoolIdFromUIName ( aString, nFamily ); + if ( nNewId != nId ) + abort(); + } +} +#endif + +const NameToIdHash & SwStyleNameMapper::getHashTable ( SwGetPoolIdFromName eFlags, bool bProgName ) +{ +#ifdef _NEED_TO_DEBUG_MAPPING + static bool bTested = false; + if ( !bTested ) + { + bTested = true; + + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_TEXT_BEGIN, RES_POOLCOLL_TEXT_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_LISTS_BEGIN, RES_POOLCOLL_LISTS_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_EXTRA_BEGIN, RES_POOLCOLL_EXTRA_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_REGISTER_BEGIN, RES_POOLCOLL_REGISTER_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_DOC_BEGIN, RES_POOLCOLL_DOC_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_HTML_BEGIN, RES_POOLCOLL_HTML_END ); + testNameTable( SwGetPoolIdFromName::ChrFmt, RES_POOLCHR_NORMAL_BEGIN, RES_POOLCHR_NORMAL_END ); + testNameTable( SwGetPoolIdFromName::ChrFmt, RES_POOLCHR_HTML_BEGIN, RES_POOLCHR_HTML_END ); + testNameTable( SwGetPoolIdFromName::FrmFmt, RES_POOLFRM_BEGIN, RES_POOLFRM_END ); + testNameTable( SwGetPoolIdFromName::PageDesc, RES_POOLPAGE_BEGIN, RES_POOLPAGE_END ); + testNameTable( SwGetPoolIdFromName::NumRule, RES_POOLNUMRULE_BEGIN, RES_POOLNUMRULE_END ); + } +#endif + + switch ( eFlags ) + { + case SwGetPoolIdFromName::TxtColl: + return TablePair::getMap(bProgName); + case SwGetPoolIdFromName::ChrFmt: + return TablePair::getMap(bProgName); + case SwGetPoolIdFromName::FrmFmt: + return TablePair::getMap(bProgName); + case SwGetPoolIdFromName::PageDesc: + return TablePair::getMap(bProgName); + case SwGetPoolIdFromName::NumRule: + return TablePair::getMap(bProgName); + case SwGetPoolIdFromName::TabStyle: + return TablePair::getMap(bProgName); + case SwGetPoolIdFromName::CellStyle: + return TablePair::getMap(bProgName); + } + + assert(false); // must not reach here + abort(); +} + +// This gets the UI name from the programmatic name +const OUString& SwStyleNameMapper::GetUIName(const OUString& rName, + SwGetPoolIdFromName const eFlags) +{ + sal_uInt16 nId = GetPoolIdFromProgName ( rName, eFlags ); + return nId != USHRT_MAX ? GetUIName( nId, rName ) : rName; +} + +// Get the programmatic name from the UI name +const OUString& SwStyleNameMapper::GetProgName( + const OUString& rName, SwGetPoolIdFromName const eFlags) +{ + sal_uInt16 nId = GetPoolIdFromUIName ( rName, eFlags ); + return nId != USHRT_MAX ? GetProgName( nId, rName ) : rName; +} + +// Get the programmatic name from the UI name in rName and put it into rFillName +void SwStyleNameMapper::FillProgName( + const OUString& rName, OUString& rFillName, + SwGetPoolIdFromName const eFlags) +{ + sal_uInt16 nId = GetPoolIdFromUIName ( rName, eFlags ); + if ( nId == USHRT_MAX ) + { + // rName isn't in our UI name table...check if it's in the programmatic one + nId = GetPoolIdFromProgName ( rName, eFlags ); + + rFillName = rName; + if (nId == USHRT_MAX ) + { + // It isn't ...make sure the suffix isn't already " (user)"...if it is, + // we need to add another one + if (lcl_SuffixIsUser(rFillName)) + rFillName += " (user)"; + } + else + { + // It's in the programmatic name table...append suffix + rFillName += " (user)"; + } + } + else + { + // If we aren't trying to disambiguate, then just do a normal fill + fillNameFromId(nId, rFillName, true); + } + + if (eFlags == SwGetPoolIdFromName::ChrFmt && rName == SwResId(STR_POOLCHR_STANDARD)) + rFillName = "Standard"; +} + +// Get the UI name from the programmatic name in rName and put it into rFillName +void SwStyleNameMapper::FillUIName( + const OUString& rName, OUString& rFillName, + SwGetPoolIdFromName const eFlags) +{ + OUString aName = rName; + if (eFlags == SwGetPoolIdFromName::ChrFmt && rName == "Standard") + aName = SwResId(STR_POOLCHR_STANDARD); + + sal_uInt16 nId = GetPoolIdFromProgName ( aName, eFlags ); + if ( nId == USHRT_MAX ) + { + rFillName = aName; + // aName isn't in our Prog name table...check if it has a " (user)" suffix, if so remove it + lcl_CheckSuffixAndDelete ( rFillName ); + } + else + { + // If we aren't trying to disambiguate, then just do a normal fill + fillNameFromId(nId, rFillName, false); + } +} + +const OUString& SwStyleNameMapper::getNameFromId( + sal_uInt16 const nId, const OUString& rFillName, bool const bProgName) +{ + sal_uInt16 nStt = 0; + const std::vector* pStrArr = nullptr; + + switch( (USER_FMT | COLL_GET_RANGE_BITS | POOLGRP_NOCOLLID) & nId ) + { + case COLL_TEXT_BITS: + if( RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END ) + { + pStrArr = bProgName ? &GetTextProgNameArray() : &GetTextUINameArray(); + nStt = RES_POOLCOLL_TEXT_BEGIN; + } + break; + case COLL_LISTS_BITS: + if( RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END ) + { + pStrArr = bProgName ? &GetListsProgNameArray() : &GetListsUINameArray(); + nStt = RES_POOLCOLL_LISTS_BEGIN; + } + break; + case COLL_EXTRA_BITS: + if( RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END ) + { + pStrArr = bProgName ? &GetExtraProgNameArray() : &GetExtraUINameArray(); + nStt = RES_POOLCOLL_EXTRA_BEGIN; + } + break; + case COLL_REGISTER_BITS: + if( RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END ) + { + pStrArr = bProgName ? &GetRegisterProgNameArray() : &GetRegisterUINameArray(); + nStt = RES_POOLCOLL_REGISTER_BEGIN; + } + break; + case COLL_DOC_BITS: + if( RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END ) + { + pStrArr = bProgName ? &GetDocProgNameArray() : &GetDocUINameArray(); + nStt = RES_POOLCOLL_DOC_BEGIN; + } + break; + case COLL_HTML_BITS: + if( RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END ) + { + pStrArr = bProgName ? &GetHTMLProgNameArray() : &GetHTMLUINameArray(); + nStt = RES_POOLCOLL_HTML_BEGIN; + } + break; + case POOLGRP_CHARFMT: + if( RES_POOLCHR_NORMAL_BEGIN <= nId && nId < RES_POOLCHR_NORMAL_END ) + { + pStrArr = bProgName ? &GetChrFormatProgNameArray() : &GetChrFormatUINameArray(); + nStt = RES_POOLCHR_NORMAL_BEGIN; + } + else if( RES_POOLCHR_HTML_BEGIN <= nId && nId < RES_POOLCHR_HTML_END ) + { + pStrArr = bProgName ? &GetHTMLChrFormatProgNameArray() : &GetHTMLChrFormatUINameArray(); + nStt = RES_POOLCHR_HTML_BEGIN; + } + break; + case POOLGRP_FRAMEFMT: + if( RES_POOLFRM_BEGIN <= nId && nId < RES_POOLFRM_END ) + { + pStrArr = bProgName ? &GetFrameFormatProgNameArray() : &GetFrameFormatUINameArray(); + nStt = RES_POOLFRM_BEGIN; + } + break; + case POOLGRP_PAGEDESC: + if( RES_POOLPAGE_BEGIN <= nId && nId < RES_POOLPAGE_END ) + { + pStrArr = bProgName ? &GetPageDescProgNameArray() : &GetPageDescUINameArray(); + nStt = RES_POOLPAGE_BEGIN; + } + break; + case POOLGRP_NUMRULE: + if( RES_POOLNUMRULE_BEGIN <= nId && nId < RES_POOLNUMRULE_END ) + { + pStrArr = bProgName ? &GetNumRuleProgNameArray() : &GetNumRuleUINameArray(); + nStt = RES_POOLNUMRULE_BEGIN; + } + break; + case POOLGRP_TABSTYLE: + if( RES_POOLTABLESTYLE_BEGIN <= nId && nId < RES_POOLTABLESTYLE_END ) + { + pStrArr = bProgName ? &GetTableStyleProgNameArray() : &GetTableStyleUINameArray(); + nStt = RES_POOLTABLESTYLE_BEGIN; + } + break; + } + return pStrArr ? pStrArr->operator[](nId - nStt) : rFillName; +} + +void SwStyleNameMapper::fillNameFromId( + sal_uInt16 const nId, OUString& rFillName, bool bProgName) +{ + rFillName = getNameFromId(nId, rFillName, bProgName); +} + +// Get the UI name from the pool ID +void SwStyleNameMapper::FillUIName(sal_uInt16 const nId, OUString& rFillName) +{ + fillNameFromId(nId, rFillName, false); +} + +// Get the UI name from the pool ID +const OUString& SwStyleNameMapper::GetUIName( + sal_uInt16 const nId, const OUString& rName) +{ + return getNameFromId(nId, rName, false); +} + +// Get the programmatic name from the pool ID +void SwStyleNameMapper::FillProgName(sal_uInt16 nId, OUString& rFillName) +{ + fillNameFromId(nId, rFillName, true); +} + +// Get the programmatic name from the pool ID +const OUString& +SwStyleNameMapper::GetProgName(sal_uInt16 const nId, const OUString& rName) +{ + return getNameFromId(nId, rName, true); +} + +// This gets the PoolId from the UI Name +sal_uInt16 SwStyleNameMapper::GetPoolIdFromUIName( + const OUString& rName, SwGetPoolIdFromName const eFlags) +{ + const NameToIdHash & rHashMap = getHashTable ( eFlags, false ); + NameToIdHash::const_iterator aIter = rHashMap.find(rName); + return aIter != rHashMap.end() ? (*aIter).second : USHRT_MAX; +} + +// Get the Pool ID from the programmatic name +sal_uInt16 SwStyleNameMapper::GetPoolIdFromProgName( + const OUString& rName, SwGetPoolIdFromName const eFlags) +{ + const NameToIdHash & rHashMap = getHashTable ( eFlags, true ); + NameToIdHash::const_iterator aIter = rHashMap.find(rName); + return aIter != rHashMap.end() ? (*aIter).second : USHRT_MAX; +} + +// Hard coded Programmatic Name tables + +/// returns an empty array because Cell Names aren't translated +const std::vector& SwStyleNameMapper::GetCellStyleUINameArray() +{ + static const std::vector s_aCellStyleUINameArray; + return s_aCellStyleUINameArray; +} + +const std::vector& SwStyleNameMapper::GetTextProgNameArray() +{ + static const std::vector s_aTextProgNameArray = { + "Standard", // RES_POOLCOLL_STANDARD + "Text body", + "First line indent", + "Hanging indent", + "Text body indent", + "Salutation", + "Signature", + "List Indent", // RES_POOLCOLL_CONFRONTATION + "Marginalia", + "Heading", + "Heading 1", + "Heading 2", + "Heading 3", + "Heading 4", + "Heading 5", + "Heading 6", + "Heading 7", + "Heading 8", + "Heading 9", + "Heading 10", // RES_POOLCOLL_TEXT_END + }; + return s_aTextProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetListsProgNameArray() +{ + static const std::vector s_aListsProgNameArray = { + "List", // STR_POCO_PRGM_NUMBER_BULLET_BASE + "Numbering 1 Start", // STR_POCO_PRGM_NUM_LEVEL1S + "Numbering 1", + "Numbering 1 End", + "Numbering 1 Cont.", + "Numbering 2 Start", + "Numbering 2", + "Numbering 2 End", + "Numbering 2 Cont.", + "Numbering 3 Start", + "Numbering 3", + "Numbering 3 End", + "Numbering 3 Cont.", + "Numbering 4 Start", + "Numbering 4", + "Numbering 4 End", + "Numbering 4 Cont.", + "Numbering 5 Start", + "Numbering 5", + "Numbering 5 End", + "Numbering 5 Cont.", + "List 1 Start", + "List 1", + "List 1 End", + "List 1 Cont.", + "List 2 Start", + "List 2", + "List 2 End", + "List 2 Cont.", + "List 3 Start", + "List 3", + "List 3 End", + "List 3 Cont.", + "List 4 Start", + "List 4", + "List 4 End", + "List 4 Cont.", + "List 5 Start", + "List 5", + "List 5 End", + "List 5 Cont.", // STR_POCO_PRGM_BULLET_NONUM5 + }; + return s_aListsProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetExtraProgNameArray() +{ + static const std::vector s_aExtraProgNameArray = { + "Header and Footer", // RES_POOLCOLL_EXTRA_BEGIN + "Header", + "Header left", + "Header right", + "Footer", + "Footer left", + "Footer right", + "Table Contents", + "Table Heading", + "Caption", + "Illustration", + "Table", + "Text", + "Figure", // RES_POOLCOLL_LABEL_FIGURE + "Frame contents", + "Footnote", + "Addressee", + "Sender", + "Endnote", + "Drawing", // RES_POOLCOLL_LABEL_DRAWING + }; + return s_aExtraProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetRegisterProgNameArray() +{ + static const std::vector s_aRegisterProgNameArray = { + "Index", // STR_POCO_PRGM_REGISTER_BASE + "Index Heading", // STR_POCO_PRGM_TOX_IDXH + "Index 1", + "Index 2", + "Index 3", + "Index Separator", + "Contents Heading", + "Contents 1", + "Contents 2", + "Contents 3", + "Contents 4", + "Contents 5", + "User Index Heading", + "User Index 1", + "User Index 2", + "User Index 3", + "User Index 4", + "User Index 5", + "Contents 6", + "Contents 7", + "Contents 8", + "Contents 9", + "Contents 10", + "Figure Index Heading", + "Figure Index 1", + "Object index heading", + "Object index 1", + "Table index heading", + "Table index 1", + "Bibliography Heading", + "Bibliography 1", + "User Index 6", + "User Index 7", + "User Index 8", + "User Index 9", + "User Index 10", // STR_POCO_PRGM_TOX_USER10 + }; + return s_aRegisterProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetDocProgNameArray() +{ + static const std::vector s_aDocProgNameArray = { + "Title", // STR_POCO_PRGM_DOC_TITLE + "Subtitle", + "Appendix", + }; + return s_aDocProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetHTMLProgNameArray() +{ + static const std::vector s_aHTMLProgNameArray = { + "Quotations", + "Preformatted Text", + "Horizontal Line", + "List Contents", + "List Heading", // STR_POCO_PRGM_HTML_DT + }; + return s_aHTMLProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetFrameFormatProgNameArray() +{ + static const std::vector s_aFrameFormatProgNameArray = { + "Frame", // RES_POOLFRM_FRAME + "Graphics", + "OLE", + "Formula", + "Marginalia", + "Watermark", + "Labels", // RES_POOLFRM_LABEL + }; + return s_aFrameFormatProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetChrFormatProgNameArray() +{ + static const std::vector s_aChrFormatProgNameArray = { + "Footnote Symbol", // RES_POOLCHR_FOOTNOTE + "Page Number", + "Caption characters", + "Drop Caps", + "Numbering Symbols", + "Bullet Symbols", + "Internet link", + "Visited Internet Link", + "Placeholder", + "Index Link", + "Endnote Symbol", + "Line numbering", + "Main index entry", + "Footnote anchor", + "Endnote anchor", + "Rubies", // RES_POOLCHR_RUBYTEXT + "Vertical Numbering Symbols", // RES_POOLCHR_VERT_NUMBER + }; + return s_aChrFormatProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetHTMLChrFormatProgNameArray() +{ + static const std::vector s_aHTMLChrFormatProgNameArray = { + "Emphasis", // RES_POOLCHR_HTML_EMPHASIS + "Citation", + "Strong Emphasis", + "Source Text", + "Example", + "User Entry", + "Variable", + "Definition", + "Teletype", // RES_POOLCHR_HTML_TELETYPE + }; + return s_aHTMLChrFormatProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetPageDescProgNameArray() +{ + static const std::vector s_aPageDescProgNameArray = { + "Standard", // STR_POOLPAGE_PRGM_STANDARD + "First Page", + "Left Page", + "Right Page", + "Envelope", + "Index", + "HTML", + "Footnote", + "Endnote", // STR_POOLPAGE_PRGM_ENDNOTE + "Landscape", + }; + return s_aPageDescProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetNumRuleProgNameArray() +{ + static const std::vector s_aNumRuleProgNameArray = { + "Numbering 123", // STR_POOLNUMRULE_PRGM_NUM1 + "Numbering ABC", + "Numbering abc", + "Numbering IVX", + "Numbering ivx", + "List 1", + "List 2", + "List 3", + "List 4", + "List 5", // STR_POOLNUMRULE_PRGM_BUL5 + }; + return s_aNumRuleProgNameArray; +} + +const std::vector& SwStyleNameMapper::GetTableStyleProgNameArray() +{ + // XXX MUST match the entries of STR_TABSTYLE_ARY in + // sw/source/core/doc/DocumentStylePoolManager.cxx and MUST match the order of + // RES_POOL_TABLESTYLE_TYPE in sw/inc/poolfmt.hxx + static const std::vector s_aTableStyleProgNameArray = { + "Default Style", // RES_POOLTABLESTYLE_DEFAULT + "3D", // RES_POOLTABLESTYLE_3D + "Black 1", // RES_POOLTABLESTYLE_BLACK1 + "Black 2", // RES_POOLTABLESTYLE_BLACK2 + "Blue", // RES_POOLTABLESTYLE_BLUE + "Brown", // RES_POOLTABLESTYLE_BROWN + "Currency", // RES_POOLTABLESTYLE_CURRENCY + "Currency 3D", // RES_POOLTABLESTYLE_CURRENCY_3D + "Currency Gray", // RES_POOLTABLESTYLE_CURRENCY_GRAY + "Currency Lavender", // RES_POOLTABLESTYLE_CURRENCY_LAVENDER + "Currency Turquoise", // RES_POOLTABLESTYLE_CURRENCY_TURQUOISE + "Gray", // RES_POOLTABLESTYLE_GRAY + "Green", // RES_POOLTABLESTYLE_GREEN + "Lavender", // RES_POOLTABLESTYLE_LAVENDER + "Red", // RES_POOLTABLESTYLE_RED + "Turquoise", // RES_POOLTABLESTYLE_TURQUOISE + "Yellow", // RES_POOLTABLESTYLE_YELLOW + "Academic", // RES_POOLTABLESTYLE_LO6_ACADEMIC + "Box List Blue", // RES_POOLTABLESTYLE_LO6_BOX_LIST_BLUE + "Box List Green", // RES_POOLTABLESTYLE_LO6_BOX_LIST_GREEN + "Box List Red", // RES_POOLTABLESTYLE_LO6_BOX_LIST_RED + "Box List Yellow", // RES_POOLTABLESTYLE_LO6_BOX_LIST_YELLOW + "Elegant", // RES_POOLTABLESTYLE_LO6_ELEGANT + "Financial", // RES_POOLTABLESTYLE_LO6_FINANCIAL + "Simple Grid Columns", // RES_POOLTABLESTYLE_LO6_SIMPLE_GRID_COLUMNS + "Simple Grid Rows", // RES_POOLTABLESTYLE_LO6_SIMPLE_GRID_ROWS + "Simple List Shaded", // RES_POOLTABLESTYLE_LO6_SIMPLE_LIST_SHADED + }; + return s_aTableStyleProgNameArray; +} + +/// returns an empty array because Cell Names aren't translated +const std::vector& SwStyleNameMapper::GetCellStyleProgNameArray() +{ + static const std::vector s_aCellStyleProgNameArray; + return s_aCellStyleProgNameArray; +} + +const OUString & +SwStyleNameMapper::GetSpecialExtraProgName(const OUString& rExtraUIName) +{ + return lcl_GetSpecialExtraName( rExtraUIName, true ); +} + +const OUString & +SwStyleNameMapper::GetSpecialExtraUIName(const OUString& rExtraProgName) +{ + return lcl_GetSpecialExtraName( rExtraProgName, false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/acmplwrd.cxx b/sw/source/core/doc/acmplwrd.cxx new file mode 100644 index 000000000..b256c5065 --- /dev/null +++ b/sw/source/core/doc/acmplwrd.cxx @@ -0,0 +1,402 @@ +/* -*- 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 + +class SwAutoCompleteClient : public SwClient +{ + SwAutoCompleteWord* m_pAutoCompleteWord; + SwDoc* m_pDoc; +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong s_nSwAutoCompleteClientCount; +#endif +public: + SwAutoCompleteClient(SwAutoCompleteWord& rToTell, SwDoc& rSwDoc); + SwAutoCompleteClient(const SwAutoCompleteClient& rClient); + virtual ~SwAutoCompleteClient() override; + + SwAutoCompleteClient& operator=(const SwAutoCompleteClient& rClient); + + const SwDoc& GetDoc() const {return *m_pDoc;} +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong GetElementCount() {return s_nSwAutoCompleteClientCount;} +#endif +protected: + virtual void Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew) override; +}; + +class SwAutoCompleteWord_Impl +{ + std::vector + m_aClientVector; + SwAutoCompleteWord& m_rAutoCompleteWord; +public: + explicit SwAutoCompleteWord_Impl(SwAutoCompleteWord& rParent) : + m_rAutoCompleteWord(rParent){} + void AddDocument(SwDoc& rDoc); + void RemoveDocument(const SwDoc& rDoc); +}; + +class SwAutoCompleteString + : public editeng::IAutoCompleteString +{ +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong s_nSwAutoCompleteStringCount; +#endif + std::vector m_aSourceDocs; + public: + SwAutoCompleteString(const OUString& rStr, sal_Int32 nLen); + + virtual ~SwAutoCompleteString() override; + void AddDocument(const SwDoc& rDoc); + //returns true if last document reference has been removed + bool RemoveDocument(const SwDoc& rDoc); +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong GetElementCount() {return s_nSwAutoCompleteStringCount;} +#endif +}; +#if OSL_DEBUG_LEVEL > 0 + sal_uLong SwAutoCompleteClient::s_nSwAutoCompleteClientCount = 0; + sal_uLong SwAutoCompleteString::s_nSwAutoCompleteStringCount = 0; +#endif + +SwAutoCompleteClient::SwAutoCompleteClient(SwAutoCompleteWord& rToTell, SwDoc& rSwDoc) : + m_pAutoCompleteWord(&rToTell), + m_pDoc(&rSwDoc) +{ + m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->Add(this); +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteClientCount; +#endif +} + +SwAutoCompleteClient::SwAutoCompleteClient(const SwAutoCompleteClient& rClient) : + SwClient(), + m_pAutoCompleteWord(rClient.m_pAutoCompleteWord), + m_pDoc(rClient.m_pDoc) +{ + m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->Add(this); +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteClientCount; +#endif +} + +SwAutoCompleteClient::~SwAutoCompleteClient() +{ +#if OSL_DEBUG_LEVEL > 0 + --s_nSwAutoCompleteClientCount; +#else + (void) this; +#endif +} + +SwAutoCompleteClient& SwAutoCompleteClient::operator=(const SwAutoCompleteClient& rClient) +{ + m_pAutoCompleteWord = rClient.m_pAutoCompleteWord; + m_pDoc = rClient.m_pDoc; + StartListeningToSameModifyAs(rClient); + return *this; +} + +void SwAutoCompleteClient::Modify( const SfxPoolItem* pOld, const SfxPoolItem *) +{ + switch( pOld ? pOld->Which() : 0 ) + { + case RES_REMOVE_UNO_OBJECT: + case RES_OBJECTDYING: + if( static_cast(GetRegisteredIn()) == static_cast(pOld)->pObject ) + EndListeningAll(); + m_pAutoCompleteWord->DocumentDying(*m_pDoc); + break; + } +} + +void SwAutoCompleteWord_Impl::AddDocument(SwDoc& rDoc) +{ + if (std::any_of(m_aClientVector.begin(), m_aClientVector.end(), + [&rDoc](SwAutoCompleteClient& rClient) { return &rClient.GetDoc() == &rDoc; })) + return; + m_aClientVector.emplace_back(m_rAutoCompleteWord, rDoc); +} + +void SwAutoCompleteWord_Impl::RemoveDocument(const SwDoc& rDoc) +{ + auto aIt = std::find_if(m_aClientVector.begin(), m_aClientVector.end(), + [&rDoc](SwAutoCompleteClient& rClient) { return &rClient.GetDoc() == &rDoc; }); + if (aIt != m_aClientVector.end()) + m_aClientVector.erase(aIt); +} + +SwAutoCompleteString::SwAutoCompleteString( + const OUString& rStr, sal_Int32 const nLen) + : editeng::IAutoCompleteString(rStr.copy(0, nLen)) +{ +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteStringCount; +#endif +} + +SwAutoCompleteString::~SwAutoCompleteString() +{ +#if OSL_DEBUG_LEVEL > 0 + --s_nSwAutoCompleteStringCount; +#else + (void) this; +#endif +} + +void SwAutoCompleteString::AddDocument(const SwDoc& rDoc) +{ + auto aIt = std::find(m_aSourceDocs.begin(), m_aSourceDocs.end(), &rDoc); + if (aIt != m_aSourceDocs.end()) + return; + m_aSourceDocs.push_back(&rDoc); +} + +bool SwAutoCompleteString::RemoveDocument(const SwDoc& rDoc) +{ + auto aIt = std::find(m_aSourceDocs.begin(), m_aSourceDocs.end(), &rDoc); + if (aIt != m_aSourceDocs.end()) + { + m_aSourceDocs.erase(aIt); + return m_aSourceDocs.empty(); + } + return false; +} + +SwAutoCompleteWord::SwAutoCompleteWord( + editeng::SortedAutoCompleteStrings::size_type nWords, sal_uInt16 nMWrdLen ): + m_pImpl(new SwAutoCompleteWord_Impl(*this)), + m_nMaxCount( nWords ), + m_nMinWordLen( nMWrdLen ), + m_bLockWordList( false ) +{ +} + +SwAutoCompleteWord::~SwAutoCompleteWord() +{ + m_WordList.DeleteAndDestroyAll(); // so the assertion below works +#if OSL_DEBUG_LEVEL > 0 + sal_uLong nStrings = SwAutoCompleteString::GetElementCount(); + sal_uLong nClients = SwAutoCompleteClient::GetElementCount(); + OSL_ENSURE(!nStrings && !nClients, "AutoComplete: clients or string count mismatch"); +#endif +} + +bool SwAutoCompleteWord::InsertWord( const OUString& rWord, SwDoc& rDoc ) +{ + SwDocShell* pDocShell = rDoc.GetDocShell(); + SfxMedium* pMedium = pDocShell ? pDocShell->GetMedium() : nullptr; + // strings from help module should not be added + if( pMedium ) + { + const INetURLObject& rURL = pMedium->GetURLObject(); + if ( rURL.GetProtocol() == INetProtocol::VndSunStarHelp ) + return false; + } + + OUString aNewWord = rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), "") + .replaceAll(OUStringChar(CH_TXTATR_BREAKWORD), ""); + + m_pImpl->AddDocument(rDoc); + bool bRet = false; + sal_Int32 nWrdLen = aNewWord.getLength(); + while( nWrdLen && '.' == aNewWord[ nWrdLen-1 ]) + --nWrdLen; + + if( !m_bLockWordList && nWrdLen >= m_nMinWordLen ) + { + SwAutoCompleteString* pNew = new SwAutoCompleteString( aNewWord, nWrdLen ); + pNew->AddDocument(rDoc); + std::pair + aInsPair = m_WordList.insert(pNew); + + m_LookupTree.insert( aNewWord.copy(0, nWrdLen) ); + + if (aInsPair.second) + { + bRet = true; + if (m_aLRUList.size() >= m_nMaxCount) + { + // the last one needs to be removed + // so that there is space for the first one + SwAutoCompleteString* pDel = m_aLRUList.back(); + m_aLRUList.pop_back(); + m_WordList.erase(pDel); + delete pDel; + } + m_aLRUList.push_front(pNew); + } + else + { + delete pNew; + // then move "up" + pNew = static_cast(*aInsPair.first); + + // add the document to the already inserted string + pNew->AddDocument(rDoc); + + // move pNew to the front of the LRU list + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pNew ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + if ( m_aLRUList.begin() != it && m_aLRUList.end() != it ) + { + m_aLRUList.erase( it ); + m_aLRUList.push_front( pNew ); + } + } + } + return bRet; +} + +void SwAutoCompleteWord::SetMaxCount( + editeng::SortedAutoCompleteStrings::size_type nNewMax ) +{ + if( nNewMax < m_nMaxCount && m_aLRUList.size() > nNewMax ) + { + // remove the trailing ones + SwAutoCompleteStringPtrDeque::size_type nLRUIndex = nNewMax-1; + while (nNewMax < m_WordList.size() && nLRUIndex < m_aLRUList.size()) + { + editeng::SortedAutoCompleteStrings::const_iterator it = + m_WordList.find(m_aLRUList[ nLRUIndex++ ]); + OSL_ENSURE( m_WordList.end() != it, "String not found" ); + editeng::IAutoCompleteString *const pDel = *it; + m_WordList.erase(it - m_WordList.begin()); + delete pDel; + } + m_aLRUList.erase( m_aLRUList.begin() + nNewMax - 1, m_aLRUList.end() ); + } + m_nMaxCount = nNewMax; +} + +void SwAutoCompleteWord::SetMinWordLen( sal_uInt16 n ) +{ + // Do you really want to remove all words that are less than the minWrdLen? + if( n < m_nMinWordLen ) + { + for (size_t nPos = 0; nPos < m_WordList.size(); ++nPos) + if (m_WordList[ nPos ]->GetAutoCompleteString().getLength() < n) + { + SwAutoCompleteString *const pDel = + dynamic_cast(m_WordList[nPos]); + m_WordList.erase(nPos); + + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pDel ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + m_aLRUList.erase( it ); + --nPos; + delete pDel; + } + } + + m_nMinWordLen = n; +} + +/** Return all words matching a given prefix + * + * @param aMatch the prefix to search for + * @param rWords the words found matching + */ +bool SwAutoCompleteWord::GetWordsMatching(const OUString& aMatch, std::vector& rWords) const +{ + assert(rWords.empty()); + m_LookupTree.findSuggestions(aMatch, rWords); + return !rWords.empty(); +} + +void SwAutoCompleteWord::CheckChangedList( + const editeng::SortedAutoCompleteStrings& rNewLst) +{ + size_t nMyLen = m_WordList.size(), nNewLen = rNewLst.size(); + size_t nMyPos = 0, nNewPos = 0; + + for( ; nMyPos < nMyLen && nNewPos < nNewLen; ++nMyPos, ++nNewPos ) + { + const editeng::IAutoCompleteString * pStr = rNewLst[ nNewPos ]; + while (m_WordList[nMyPos] != pStr) + { + SwAutoCompleteString *const pDel = + dynamic_cast(m_WordList[nMyPos]); + m_WordList.erase(nMyPos); + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pDel ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + m_aLRUList.erase( it ); + delete pDel; + if( nMyPos >= --nMyLen ) + break; + } + } + // remove the elements at the end of the array + if( nMyPos < nMyLen ) + { + // clear LRU array first then delete the string object + for( ; nNewPos < nMyLen; ++nNewPos ) + { + SwAutoCompleteString *const pDel = + dynamic_cast(m_WordList[nNewPos]); + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pDel ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + m_aLRUList.erase( it ); + delete pDel; + } + // remove from array + m_WordList.erase(m_WordList.begin() + nMyPos, + m_WordList.begin() + nMyLen); + } +} + +void SwAutoCompleteWord::DocumentDying(const SwDoc& rDoc) +{ + m_pImpl->RemoveDocument(rDoc); + + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + const bool bDelete = !pACorr->GetSwFlags().bAutoCmpltKeepList; + for (size_t nPos = m_WordList.size(); nPos; nPos--) + { + SwAutoCompleteString *const pCurrent = dynamic_cast(m_WordList[nPos - 1]); + if(pCurrent && pCurrent->RemoveDocument(rDoc) && bDelete) + { + m_WordList.erase(nPos - 1); + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pCurrent ); + OSL_ENSURE( m_aLRUList.end() != it, "word not found in LRU list" ); + m_aLRUList.erase( it ); + delete pCurrent; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/dbgoutsw.cxx b/sw/source/core/doc/dbgoutsw.cxx new file mode 100644 index 000000000..3fb9c460b --- /dev/null +++ b/sw/source/core/doc/dbgoutsw.cxx @@ -0,0 +1,846 @@ +/* -*- 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 . + */ + +#ifdef DBG_UTIL + +#include +#include +#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 std; + +static OString aDbgOutResult; +bool bDbgOutStdErr = false; +bool bDbgOutPrintAttrSet = false; + +template +static OUString lcl_dbg_out_SvPtrArr(const T & rArr) +{ + OUStringBuffer aStr("[ "); + + for (typename T::const_iterator i(rArr.begin()); i != rArr.end(); ++i) + { + if (i != rArr.begin()) + aStr.append(", "); + + if (*i) + aStr.append(lcl_dbg_out(**i)); + else + aStr.append("(null)"); + } + + aStr.append(" ]"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const void * pVoid) +{ + char sBuffer[1024]; + + sprintf(sBuffer, "%p", pVoid); + + OUString aTmpStr(sBuffer, strlen(sBuffer), RTL_TEXTENCODING_ASCII_US); + + return dbg_out(aTmpStr); +} + +const char * dbg_out(const OUString & aStr) +{ + aDbgOutResult = OUStringToOString(aStr, RTL_TEXTENCODING_ASCII_US); + + if (bDbgOutStdErr) + fprintf(stderr, "%s", aDbgOutResult.getStr()); + + return aDbgOutResult.getStr(); +} + +static map & GetItemWhichMap() +{ + static map aItemWhichMap + { + { RES_CHRATR_CASEMAP , "CHRATR_CASEMAP" }, + { RES_CHRATR_CHARSETCOLOR , "CHRATR_CHARSETCOLOR" }, + { RES_CHRATR_COLOR , "CHRATR_COLOR" }, + { RES_CHRATR_CONTOUR , "CHRATR_CONTOUR" }, + { RES_CHRATR_CROSSEDOUT , "CHRATR_CROSSEDOUT" }, + { RES_CHRATR_ESCAPEMENT , "CHRATR_ESCAPEMENT" }, + { RES_CHRATR_FONT , "CHRATR_FONT" }, + { RES_CHRATR_FONTSIZE , "CHRATR_FONTSIZE" }, + { RES_CHRATR_KERNING , "CHRATR_KERNING" }, + { RES_CHRATR_LANGUAGE , "CHRATR_LANGUAGE" }, + { RES_CHRATR_POSTURE , "CHRATR_POSTURE" }, + { RES_CHRATR_SHADOWED , "CHRATR_SHADOWED" }, + { RES_CHRATR_UNDERLINE , "CHRATR_UNDERLINE" }, + { RES_CHRATR_OVERLINE , "CHRATR_OVERLINE" }, + { RES_CHRATR_WEIGHT , "CHRATR_WEIGHT" }, + { RES_CHRATR_WORDLINEMODE , "CHRATR_WORDLINEMODE" }, + { RES_CHRATR_AUTOKERN , "CHRATR_AUTOKERN" }, + { RES_CHRATR_BLINK , "CHRATR_BLINK" }, + { RES_CHRATR_NOHYPHEN , "CHRATR_NOHYPHEN" }, + { RES_CHRATR_BACKGROUND , "CHRATR_BACKGROUND" }, + { RES_CHRATR_HIGHLIGHT , "CHRATR_HIGHLIGHT" }, + { RES_CHRATR_CJK_FONT , "CHRATR_CJK_FONT" }, + { RES_CHRATR_CJK_FONTSIZE , "CHRATR_CJK_FONTSIZE" }, + { RES_CHRATR_CJK_LANGUAGE , "CHRATR_CJK_LANGUAGE" }, + { RES_CHRATR_CJK_POSTURE , "CHRATR_CJK_POSTURE" }, + { RES_CHRATR_CJK_WEIGHT , "CHRATR_CJK_WEIGHT" }, + { RES_CHRATR_CTL_FONT , "CHRATR_CTL_FONT" }, + { RES_CHRATR_CTL_FONTSIZE , "CHRATR_CTL_FONTSIZE" }, + { RES_CHRATR_CTL_LANGUAGE , "CHRATR_CTL_LANGUAGE" }, + { RES_CHRATR_CTL_POSTURE , "CHRATR_CTL_POSTURE" }, + { RES_CHRATR_CTL_WEIGHT , "CHRATR_CTL_WEIGHT" }, + { RES_CHRATR_ROTATE , "CHRATR_ROTATE" }, + { RES_CHRATR_EMPHASIS_MARK , "CHRATR_EMPHASIS_MARK" }, + { RES_CHRATR_TWO_LINES , "CHRATR_TWO_LINES" }, + { RES_CHRATR_SCALEW , "CHRATR_SCALEW" }, + { RES_CHRATR_RELIEF , "CHRATR_RELIEF" }, + { RES_CHRATR_HIDDEN , "CHRATR_HIDDEN" }, + { RES_CHRATR_BOX , "CHRATR_BOX" }, + { RES_CHRATR_SHADOW , "CHRATR_SHADOW" }, + { RES_TXTATR_AUTOFMT , "TXTATR_AUTOFMT" }, + { RES_TXTATR_INETFMT , "TXTATR_INETFMT" }, + { RES_TXTATR_REFMARK , "TXTATR_REFMARK" }, + { RES_TXTATR_TOXMARK , "TXTATR_TOXMARK" }, + { RES_TXTATR_CHARFMT , "TXTATR_CHARFMT" }, + { RES_TXTATR_INPUTFIELD , "RES_TXTATR_INPUTFIELD" }, + { RES_TXTATR_CJK_RUBY , "TXTATR_CJK_RUBY" }, + { RES_TXTATR_UNKNOWN_CONTAINER , "TXTATR_UNKNOWN_CONTAINER" }, + { RES_TXTATR_META , "TXTATR_META" }, + { RES_TXTATR_METAFIELD , "TXTATR_METAFIELD" }, + { RES_TXTATR_FIELD , "TXTATR_FIELD" }, + { RES_TXTATR_FLYCNT , "TXTATR_FLYCNT" }, + { RES_TXTATR_FTN , "TXTATR_FTN" }, + { RES_TXTATR_ANNOTATION , "TXTATR_ANNOTATION" }, + { RES_TXTATR_DUMMY3 , "TXTATR_DUMMY3" }, + { RES_TXTATR_DUMMY1 , "TXTATR_DUMMY1" }, + { RES_TXTATR_DUMMY2 , "TXTATR_DUMMY2" }, + { RES_PARATR_LINESPACING , "PARATR_LINESPACING" }, + { RES_PARATR_ADJUST , "PARATR_ADJUST" }, + { RES_PARATR_SPLIT , "PARATR_SPLIT" }, + { RES_PARATR_ORPHANS , "PARATR_ORPHANS" }, + { RES_PARATR_WIDOWS , "PARATR_WIDOWS" }, + { RES_PARATR_TABSTOP , "PARATR_TABSTOP" }, + { RES_PARATR_HYPHENZONE , "PARATR_HYPHENZONE" }, + { RES_PARATR_DROP , "PARATR_DROP" }, + { RES_PARATR_REGISTER , "PARATR_REGISTER" }, + { RES_PARATR_NUMRULE , "PARATR_NUMRULE" }, + { RES_PARATR_SCRIPTSPACE , "PARATR_SCRIPTSPACE" }, + { RES_PARATR_HANGINGPUNCTUATION , "PARATR_HANGINGPUNCTUATION" }, + { RES_PARATR_FORBIDDEN_RULES , "PARATR_FORBIDDEN_RULES" }, + { RES_PARATR_VERTALIGN , "PARATR_VERTALIGN" }, + { RES_PARATR_SNAPTOGRID , "PARATR_SNAPTOGRID" }, + { RES_PARATR_CONNECT_BORDER , "PARATR_CONNECT_BORDER" }, + { RES_FILL_ORDER , "FILL_ORDER" }, + { RES_FRM_SIZE , "FRM_SIZE" }, + { RES_PAPER_BIN , "PAPER_BIN" }, + { RES_LR_SPACE , "LR_SPACE" }, + { RES_UL_SPACE , "UL_SPACE" }, + { RES_PAGEDESC , "PAGEDESC" }, + { RES_BREAK , "BREAK" }, + { RES_CNTNT , "CNTNT" }, + { RES_HEADER , "HEADER" }, + { RES_FOOTER , "FOOTER" }, + { RES_PRINT , "PRINT" }, + { RES_OPAQUE , "OPAQUE" }, + { RES_PROTECT , "PROTECT" }, + { RES_SURROUND , "SURROUND" }, + { RES_VERT_ORIENT , "VERT_ORIENT" }, + { RES_HORI_ORIENT , "HORI_ORIENT" }, + { RES_ANCHOR , "ANCHOR" }, + { RES_BACKGROUND , "BACKGROUND" }, + { RES_BOX , "BOX" }, + { RES_SHADOW , "SHADOW" }, + { RES_FRMMACRO , "FRMMACRO" }, + { RES_COL , "COL" }, + { RES_KEEP , "KEEP" }, + { RES_URL , "URL" }, + { RES_EDIT_IN_READONLY , "EDIT_IN_READONLY" }, + { RES_LAYOUT_SPLIT , "LAYOUT_SPLIT" }, + { RES_CHAIN , "CHAIN" }, + { RES_TEXTGRID , "TEXTGRID" }, + { RES_LINENUMBER , "LINENUMBER" }, + { RES_FTN_AT_TXTEND , "FTN_AT_TXTEND" }, + { RES_END_AT_TXTEND , "END_AT_TXTEND" }, + { RES_COLUMNBALANCE , "COLUMNBALANCE" }, + { RES_FRAMEDIR , "FRAMEDIR" }, + { RES_HEADER_FOOTER_EAT_SPACING , "HEADER_FOOTER_EAT_SPACING" }, + { RES_ROW_SPLIT , "ROW_SPLIT" }, + { RES_GRFATR_MIRRORGRF , "GRFATR_MIRRORGRF" }, + { RES_GRFATR_CROPGRF , "GRFATR_CROPGRF" }, + { RES_GRFATR_ROTATION , "GRFATR_ROTATION" }, + { RES_GRFATR_LUMINANCE , "GRFATR_LUMINANCE" }, + { RES_GRFATR_CONTRAST , "GRFATR_CONTRAST" }, + { RES_GRFATR_CHANNELR , "GRFATR_CHANNELR" }, + { RES_GRFATR_CHANNELG , "GRFATR_CHANNELG" }, + { RES_GRFATR_CHANNELB , "GRFATR_CHANNELB" }, + { RES_GRFATR_GAMMA , "GRFATR_GAMMA" }, + { RES_GRFATR_INVERT , "GRFATR_INVERT" }, + { RES_GRFATR_TRANSPARENCY , "GRFATR_TRANSPARENCY" }, + { RES_GRFATR_DRAWMODE , "GRFATR_DRAWMODE" }, + { RES_BOXATR_FORMAT , "BOXATR_FORMAT" }, + { RES_BOXATR_FORMULA , "BOXATR_FORMULA" }, + { RES_BOXATR_VALUE , "BOXATR_VALUE" }, + }; + + return aItemWhichMap; +} + +static OUString lcl_dbg_out(const SfxPoolItem & rItem) +{ + OUString aStr("[ "); + + if (GetItemWhichMap().find(rItem.Which()) != GetItemWhichMap().end()) + aStr += GetItemWhichMap()[rItem.Which()]; + else + aStr += OUString::number(rItem.Which()); + + aStr += " ]"; + + return aStr; +} + +const char * dbg_out(const SfxPoolItem & rItem) +{ + return dbg_out(lcl_dbg_out(rItem)); +} + +const char * dbg_out(const SfxPoolItem * pItem) +{ + return dbg_out(pItem ? lcl_dbg_out(*pItem) : OUString("(nil)")); +} + +static OUString lcl_dbg_out(const SfxItemSet & rSet) +{ + SfxItemIter aIter(rSet); + bool bFirst = true; + OUStringBuffer aStr = "[ "; + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if (!bFirst) + aStr.append(", "); + + if (reinterpret_cast(pItem) != SAL_MAX_SIZE) + aStr.append(lcl_dbg_out(*pItem)); + else + aStr.append("invalid"); + + bFirst = false; + } + + aStr.append(" ]"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const SfxItemSet & rSet) +{ + return dbg_out(lcl_dbg_out(rSet)); +} + +static OUString lcl_dbg_out(const SwTextAttr & rAttr) +{ + OUString aStr = + "[ " + + OUString::number(rAttr.GetStart()) + + "->" + + OUString::number(*rAttr.End()) + + " " + + lcl_dbg_out(rAttr.GetAttr()) + + " ]"; + + return aStr; +} + +const char * dbg_out(const SwTextAttr & rAttr) +{ + return dbg_out(lcl_dbg_out(rAttr)); +} + +static OUString lcl_dbg_out(const SwpHints & rHints) +{ + OUStringBuffer aStr("[ SwpHints\n"); + + for (size_t i = 0; i < rHints.Count(); ++i) + { + aStr.append(" "); + aStr.append(lcl_dbg_out(*rHints.Get(i))); + aStr.append("\n"); + } + + aStr.append("]\n"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const SwpHints &rHints) +{ + return dbg_out(lcl_dbg_out(rHints)); +} + +static OUString lcl_dbg_out(const SwPosition & rPos) +{ + OUString aStr = + "( " + + OUString::number(rPos.nNode.GetIndex()) + + ", " + + OUString::number(rPos.nContent.GetIndex()) + + ": " + + OUString::number(reinterpret_cast(rPos.nContent.GetIdxReg()), 16) + + " )"; + + return aStr; +} + +const char * dbg_out(const SwPosition & rPos) +{ + return dbg_out(lcl_dbg_out(rPos)); +} + +static OUString lcl_dbg_out(const SwPaM & rPam) +{ + OUString aStr = + "[ Pt: " + + lcl_dbg_out(*rPam.GetPoint()); + + if (rPam.HasMark()) + { + aStr += ", Mk: " + lcl_dbg_out(*rPam.GetMark()); + } + + aStr += " ]"; + + return aStr; +} + +const char * dbg_out(const SwPaM & rPam) +{ + return dbg_out(lcl_dbg_out(rPam)); +} + +static OUString lcl_dbg_out(const SwNodeNum & ) +{ + return OUString();/*rNum.ToString();*/ +} + +const char * dbg_out(const SwNodeNum & rNum) +{ + return dbg_out(lcl_dbg_out(rNum)); +} + +static OUString lcl_dbg_out(const SwRect & rRect) +{ + OUString aResult = + "[ [" + + OUString::number(rRect.Left()) + + ", " + + OUString::number(rRect.Top()) + + "], [" + + OUString::number(rRect.Right()) + + ", " + + OUString::number(rRect.Bottom()) + + "] ]"; + + return aResult; +} + +const char * dbg_out(const SwRect & rRect) +{ + return dbg_out(lcl_dbg_out(rRect)); +} + +static OUString lcl_dbg_out(const SwFrameFormat & rFrameFormat) +{ + char sBuffer[256]; + sprintf(sBuffer, "%p", &rFrameFormat); + + OUString aResult = "[ " + + OUString(sBuffer, strlen(sBuffer), RTL_TEXTENCODING_ASCII_US) + + "(" + + rFrameFormat.GetName() + ")"; + + if (rFrameFormat.IsAuto()) + aResult += "*"; + + aResult += " ," + lcl_dbg_out(rFrameFormat.FindLayoutRect()) + " ]"; + + return aResult; +} + +const char * dbg_out(const SwFrameFormat & rFrameFormat) +{ + return dbg_out(lcl_dbg_out(rFrameFormat)); +} + +static OUString lcl_AnchoredFrames(const SwNode & rNode) +{ + OUStringBuffer aResult("["); + + const SwDoc * pDoc = rNode.GetDoc(); + if (pDoc) + { + const SwFrameFormats * pFrameFormats = pDoc->GetSpzFrameFormats(); + + if (pFrameFormats) + { + bool bFirst = true; + for (SwFrameFormats::const_iterator i(pFrameFormats->begin()); + i != pFrameFormats->end(); ++i) + { + const SwFormatAnchor & rAnchor = (*i)->GetAnchor(); + const SwPosition * pPos = rAnchor.GetContentAnchor(); + + if (pPos && &pPos->nNode.GetNode() == &rNode) + { + if (! bFirst) + aResult.append(", "); + + if (*i) + aResult.append(lcl_dbg_out(**i)); + bFirst = false; + } + } + } + } + + aResult.append("]"); + + return aResult.makeStringAndClear(); +} + +static OUString lcl_dbg_out_NumType(sal_Int16 nType) +{ + OUString aTmpStr; + + switch (nType) + { + case SVX_NUM_NUMBER_NONE: + aTmpStr += " NONE"; + + break; + case SVX_NUM_CHARS_UPPER_LETTER: + aTmpStr += " CHARS_UPPER_LETTER"; + + break; + case SVX_NUM_CHARS_LOWER_LETTER: + aTmpStr += " CHARS_LOWER_LETTER"; + + break; + case SVX_NUM_ROMAN_UPPER: + aTmpStr += " ROMAN_UPPER"; + + break; + case SVX_NUM_ROMAN_LOWER: + aTmpStr += " ROMAN_LOWER"; + + break; + case SVX_NUM_ARABIC: + aTmpStr += " ARABIC"; + + break; + default: + aTmpStr += " ??"; + + break; + } + + return aTmpStr; +} + +static OUString lcl_dbg_out(const SwNode & rNode) +{ + char aBuffer[128]; + sprintf(aBuffer, "%p", &rNode); + + OUString aTmpStr = ""; + + const SwTextNode * pTextNode = rNode.GetTextNode(); + + if (rNode.IsTextNode()) + { + const SfxItemSet * pAttrSet = pTextNode->GetpSwAttrSet(); + + aTmpStr += "" + (pTextNode->GetText().getLength() > 10 ? pTextNode->GetText().copy(0, 10) : pTextNode->GetText()) + ""; + + if (rNode.IsTableNode()) + aTmpStr += ""; + + aTmpStr += "" + OUString::number(pTextNode->GetAttrOutlineLevel()-1) + ""; + + const SwNumRule * pNumRule = pTextNode->GetNumRule(); + + if (pNumRule != nullptr) + { + aTmpStr += ""; + if ( pTextNode->GetNum() ) + { + aTmpStr += lcl_dbg_out(*(pTextNode->GetNum())); + } + aTmpStr += "" + + pNumRule->GetName(); + + const SfxPoolItem * pItem = nullptr; + + if (pAttrSet && SfxItemState::SET == + pAttrSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem)) + { + aTmpStr += "(" + + static_cast(pItem)->GetValue() + ")*"; + } + + const SwNumFormat * pNumFormat = nullptr; + aTmpStr += ""; + + if (pTextNode->GetActualListLevel() > 0) + pNumFormat = pNumRule->GetNumFormat( static_cast< sal_uInt16 >(pTextNode->GetActualListLevel()) ); + + if (pNumFormat) + { + aTmpStr += "" + + lcl_dbg_out_NumType(pNumFormat->GetNumberingType()) + ""; + } + } + + if (pTextNode->IsCountedInList()) + aTmpStr += ""; + + SwFormatColl * pColl = pTextNode->GetFormatColl(); + + if (pColl) + { + aTmpStr += "" + pColl->GetName() + "("; + + SwTextFormatColl *pTextColl = static_cast(pColl); + if (pTextColl->IsAssignedToListLevelOfOutlineStyle()) + { + aTmpStr += OUString::number(pTextColl->GetAssignedOutlineStyleLevel()); + } + else + { + aTmpStr += OUString::number(-1); + } + + const SwNumRuleItem & rItem = + pColl->GetFormatAttr(RES_PARATR_NUMRULE); + const OUString& sNumruleName = rItem.GetValue(); + + if (!sNumruleName.isEmpty()) + { + aTmpStr += ", " + sNumruleName; + } + aTmpStr += ")" + ""; + } + + SwFormatColl * pCColl = pTextNode->GetCondFormatColl(); + + if (pCColl) + { + aTmpStr += "" + pCColl->GetName() + ""; + } + + aTmpStr += "" + lcl_AnchoredFrames(rNode) + ""; + + if (bDbgOutPrintAttrSet) + { + aTmpStr += "" + lcl_dbg_out(pTextNode->GetSwAttrSet()) + ""; + } + } + else if (rNode.IsStartNode()) + { + aTmpStr += " (&rNode); + if (pStartNode != nullptr) + aTmpStr += OUString::number(pStartNode->EndOfSectionNode()->GetIndex()); + + aTmpStr += "\"/>"; + } + else if (rNode.IsEndNode()) + aTmpStr += ""; + + aTmpStr += ""; + + return aTmpStr; +} + +const char * dbg_out(const SwNode & rNode) +{ + return dbg_out(lcl_dbg_out(rNode)); +} + +const char * dbg_out(const SwNode * pNode) +{ + if (nullptr != pNode) + return dbg_out(*pNode); + else + return nullptr; +} + +const char * dbg_out(const SwContentNode * pNode) +{ + if (nullptr != pNode) + return dbg_out(*pNode); + else + return nullptr; +} + +const char * dbg_out(const SwTextNode * pNode) +{ + if (nullptr != pNode) + return dbg_out(*pNode); + else + return nullptr; +} + +static OUString lcl_dbg_out(const SwUndo & rUndo) +{ + return "[ " + OUString::number(static_cast(rUndo.GetId())) + + ": " + rUndo.GetComment() + " ]"; +} + +const char * dbg_out(const SwUndo & rUndo) +{ + return dbg_out(lcl_dbg_out(rUndo)); +} + +static OUString lcl_dbg_out(SwOutlineNodes const & rNodes) +{ + OUStringBuffer aStr("[\n"); + + for (size_t i = 0; i < rNodes.size(); i++) + { + aStr.append(lcl_dbg_out(*rNodes[i])); + aStr.append("\n"); + } + + aStr.append("]\n"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out( SwOutlineNodes const & rNodes) +{ + return dbg_out(lcl_dbg_out(rNodes)); +} + +static OUString lcl_dbg_out(const SvxNumberFormat & rFormat) +{ + OUString aResult = lcl_dbg_out_NumType(rFormat.GetNumberingType()); + return aResult; +} + +static OUString lcl_dbg_out(const SwNumRule & rRule) +{ + OUStringBuffer aResult("[ "); + + aResult.append(rRule.GetName()); + aResult.append(" ["); + + for (sal_uInt8 n = 0; n < MAXLEVEL; n++) + { + if (n > 0) + aResult.append(", "); + + aResult.append(lcl_dbg_out(rRule.Get(n))); + } + + aResult.append("]"); + + aResult.append("]"); + + return aResult.makeStringAndClear(); +} + +const char * dbg_out(const SwNumRule & rRule) +{ + return dbg_out(lcl_dbg_out(rRule)); +} + +static OUString lcl_dbg_out(const SwTextFormatColl & rFormat) +{ + return rFormat.GetName() + "(" + + OUString::number(rFormat.GetAttrOutlineLevel()) + ")"; +} + +const char * dbg_out(const SwTextFormatColl & rFormat) +{ + return dbg_out(lcl_dbg_out(rFormat)); +} + +static OUString lcl_dbg_out(const SwFrameFormats & rFrameFormats) +{ + return lcl_dbg_out_SvPtrArr(rFrameFormats); +} + +const char * dbg_out(const SwFrameFormats & rFrameFormats) +{ + return dbg_out(lcl_dbg_out(rFrameFormats)); +} + +static OUString lcl_dbg_out(const SwNumRuleTable & rTable) +{ + OUStringBuffer aResult("["); + + for (size_t n = 0; n < rTable.size(); n++) + { + if (n > 0) + aResult.append(", "); + + aResult.append(rTable[n]->GetName()); + + char sBuffer[256]; + sprintf(sBuffer, "(%p)", rTable[n]); + aResult.appendAscii(sBuffer); + } + + aResult.append("]"); + + return aResult.makeStringAndClear(); +} + +const char * dbg_out(const SwNumRuleTable & rTable) +{ + return dbg_out(lcl_dbg_out(rTable)); +} + +static OUString lcl_TokenType2Str(FormTokenType nType) +{ + switch(nType) + { + case TOKEN_ENTRY_NO: + return "NO"; + case TOKEN_ENTRY_TEXT: + return "ENTRY_TEXT"; + case TOKEN_ENTRY: + return "ENTRY"; + case TOKEN_TAB_STOP: + return "TAB_STOP"; + case TOKEN_TEXT: + return "TOKEN_TEXT"; + case TOKEN_PAGE_NUMS: + return "NUMS"; + case TOKEN_CHAPTER_INFO: + return "CHAPTER_INFO"; + case TOKEN_LINK_START: + return "LINK_START"; + case TOKEN_LINK_END: + return "LINK_END"; + case TOKEN_AUTHORITY: + return "AUTHORITY"; + case TOKEN_END: + return "END"; + default: + OSL_FAIL("should not be reached"); + return "??"; + } +} + +static OUString lcl_dbg_out(const SwFormToken & rToken) +{ + return rToken.GetString(); +} + +const char * dbg_out(const SwFormToken & rToken) +{ + return dbg_out(lcl_dbg_out(rToken)); +} + +static OUString lcl_dbg_out(const SwFormTokens & rTokens) +{ + OUStringBuffer aStr("["); + + SwFormTokens::const_iterator aIt; + + for (aIt = rTokens.begin(); aIt != rTokens.end(); ++aIt) + { + if (aIt != rTokens.begin()) + aStr.append(", "); + + aStr.append(lcl_TokenType2Str(aIt->eTokenType)); + aStr.append(": "); + aStr.append(lcl_dbg_out(*aIt)); + } + + aStr.append("]"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const SwFormTokens & rTokens) +{ + return dbg_out(lcl_dbg_out(rTokens)); +} + +static OUString lcl_dbg_out(const SwNodeRange & rRange) +{ + OUString aStr = + "[" + + lcl_dbg_out(SwPosition(rRange.aStart)) + + ", " + + lcl_dbg_out(SwPosition(rRange.aEnd)) + + "]"; + + return aStr; +} + +const char * dbg_out(const SwNodeRange & rRange) +{ + return dbg_out(lcl_dbg_out(rRange)); +} + +#endif // DEBUG + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doc.cxx b/sw/source/core/doc/doc.cxx new file mode 100644 index 000000000..b850b2c8a --- /dev/null +++ b/sw/source/core/doc/doc.cxx @@ -0,0 +1,1830 @@ +/* -*- 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 + +/* @@@MAINTAINABILITY-HORROR@@@ + Probably unwanted dependency on SwDocShell +*/ +#include + +using namespace ::com::sun::star; + +sal_Int32 SwDoc::acquire() +{ + assert(mReferenceCount >= 0); + return osl_atomic_increment(&mReferenceCount); +} + +sal_Int32 SwDoc::release() +{ + assert(mReferenceCount >= 1); + auto x = osl_atomic_decrement(&mReferenceCount); + if (x == 0) + delete this; + return x; +} + +sal_Int32 SwDoc::getReferenceCount() const +{ + assert(mReferenceCount >= 0); + return mReferenceCount; +} + +::sw::MetaFieldManager & SwDoc::GetMetaFieldManager() +{ + return *m_pMetaFieldManager; +} + +::sw::UndoManager & SwDoc::GetUndoManager() +{ + return *m_pUndoManager; +} + +::sw::UndoManager const & SwDoc::GetUndoManager() const +{ + return *m_pUndoManager; +} + + +IDocumentUndoRedo & SwDoc::GetIDocumentUndoRedo() +{ + return *m_pUndoManager; +} + +IDocumentUndoRedo const & SwDoc::GetIDocumentUndoRedo() const +{ + return *m_pUndoManager; +} + +/* IDocumentDrawModelAccess */ +IDocumentDrawModelAccess const & SwDoc::getIDocumentDrawModelAccess() const +{ + return GetDocumentDrawModelManager(); +} + +IDocumentDrawModelAccess & SwDoc::getIDocumentDrawModelAccess() +{ + return GetDocumentDrawModelManager(); +} + +::sw::DocumentDrawModelManager const & SwDoc::GetDocumentDrawModelManager() const +{ + return *m_pDocumentDrawModelManager; +} + +::sw::DocumentDrawModelManager & SwDoc::GetDocumentDrawModelManager() +{ + return *m_pDocumentDrawModelManager; +} + +/* IDocumentSettingAccess */ +IDocumentSettingAccess const & SwDoc::getIDocumentSettingAccess() const +{ + return GetDocumentSettingManager(); +} + +IDocumentSettingAccess & SwDoc::getIDocumentSettingAccess() +{ + return GetDocumentSettingManager(); +} + +::sw::DocumentSettingManager & SwDoc::GetDocumentSettingManager() +{ + return *m_pDocumentSettingManager; +} + +::sw::DocumentSettingManager const & SwDoc::GetDocumentSettingManager() const +{ + return *m_pDocumentSettingManager; +} + +sal_uInt32 SwDoc::getRsid() const +{ + return mnRsid; +} + +void SwDoc::setRsid( sal_uInt32 nVal ) +{ + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + sal_uInt32 nIncrease = 0; + if (!bHack) + { + // Increase the rsid with a random number smaller than 2^17. This way we + // expect to be able to edit a document 2^12 times before rsid overflows. + // start from 1 to ensure the new rsid is not the same + nIncrease = comphelper::rng::uniform_uint_distribution(1, (1 << 17) - 1); + } + mnRsid = nVal + nIncrease; +} + +sal_uInt32 SwDoc::getRsidRoot() const +{ + return mnRsidRoot; +} + +void SwDoc::setRsidRoot( sal_uInt32 nVal ) +{ + mnRsidRoot = nVal; +} + +/* IDocumentChartDataProviderAccess */ +IDocumentChartDataProviderAccess const & SwDoc::getIDocumentChartDataProviderAccess() const +{ + return *m_pDocumentChartDataProviderManager; +} + +IDocumentChartDataProviderAccess & SwDoc::getIDocumentChartDataProviderAccess() +{ + return *m_pDocumentChartDataProviderManager; +} + +// IDocumentDeviceAccess +IDocumentDeviceAccess const & SwDoc::getIDocumentDeviceAccess() const +{ + return *m_pDeviceAccess; +} + +IDocumentDeviceAccess & SwDoc::getIDocumentDeviceAccess() +{ + return *m_pDeviceAccess; +} + +//IDocumentTimerAccess +IDocumentTimerAccess const & SwDoc::getIDocumentTimerAccess() const +{ + return *m_pDocumentTimerManager; +} + +IDocumentTimerAccess & SwDoc::getIDocumentTimerAccess() +{ + return *m_pDocumentTimerManager; +} + +// IDocumentLinksAdministration +IDocumentLinksAdministration const & SwDoc::getIDocumentLinksAdministration() const +{ + return *m_pDocumentLinksAdministrationManager; +} + +IDocumentLinksAdministration & SwDoc::getIDocumentLinksAdministration() +{ + return *m_pDocumentLinksAdministrationManager; +} + +::sw::DocumentLinksAdministrationManager const & SwDoc::GetDocumentLinksAdministrationManager() const +{ + return *m_pDocumentLinksAdministrationManager; +} + +::sw::DocumentLinksAdministrationManager & SwDoc::GetDocumentLinksAdministrationManager() +{ + return *m_pDocumentLinksAdministrationManager; +} + +//IDocumentListItems +IDocumentListItems const & SwDoc::getIDocumentListItems() const +{ + return *m_pDocumentListItemsManager; +} + +//IDocumentListItems +IDocumentListItems & SwDoc::getIDocumentListItems() +{ + return *m_pDocumentListItemsManager; +} + +//IDocumentListsAccess +IDocumentListsAccess const & SwDoc::getIDocumentListsAccess() const +{ + return *m_pDocumentListsManager; +} + +IDocumentListsAccess & SwDoc::getIDocumentListsAccess() +{ + return *m_pDocumentListsManager; +} + +//IDocumentOutlinesNodes +IDocumentOutlineNodes const & SwDoc::getIDocumentOutlineNodes() const +{ + return *m_pDocumentOutlineNodesManager; +} + +IDocumentOutlineNodes & SwDoc::getIDocumentOutlineNodes() +{ + return *m_pDocumentOutlineNodesManager; +} + +//IDocumentContentOperations +IDocumentContentOperations const & SwDoc::getIDocumentContentOperations() const +{ + return *m_pDocumentContentOperationsManager; +} + +IDocumentContentOperations & SwDoc::getIDocumentContentOperations() +{ + return *m_pDocumentContentOperationsManager; +} + +::sw::DocumentContentOperationsManager const & SwDoc::GetDocumentContentOperationsManager() const +{ + return *m_pDocumentContentOperationsManager; +} +::sw::DocumentContentOperationsManager & SwDoc::GetDocumentContentOperationsManager() +{ + return *m_pDocumentContentOperationsManager; +} + +//IDocumentRedlineAccess +IDocumentRedlineAccess const & SwDoc::getIDocumentRedlineAccess() const +{ + return *m_pDocumentRedlineManager; +} + +IDocumentRedlineAccess& SwDoc::getIDocumentRedlineAccess() +{ + return *m_pDocumentRedlineManager; +} + +::sw::DocumentRedlineManager const & SwDoc::GetDocumentRedlineManager() const +{ + return *m_pDocumentRedlineManager; +} + +::sw::DocumentRedlineManager& SwDoc::GetDocumentRedlineManager() +{ + return *m_pDocumentRedlineManager; +} + +//IDocumentFieldsAccess + +IDocumentFieldsAccess const & SwDoc::getIDocumentFieldsAccess() const +{ + return *m_pDocumentFieldsManager; +} + +IDocumentFieldsAccess & SwDoc::getIDocumentFieldsAccess() +{ + return *m_pDocumentFieldsManager; +} + +::sw::DocumentFieldsManager & SwDoc::GetDocumentFieldsManager() +{ + return *m_pDocumentFieldsManager; +} + +//IDocumentStatistics +IDocumentStatistics const & SwDoc::getIDocumentStatistics() const +{ + return *m_pDocumentStatisticsManager; +} + +IDocumentStatistics & SwDoc::getIDocumentStatistics() +{ + return *m_pDocumentStatisticsManager; +} + +::sw::DocumentStatisticsManager const & SwDoc::GetDocumentStatisticsManager() const +{ + return *m_pDocumentStatisticsManager; +} + +::sw::DocumentStatisticsManager & SwDoc::GetDocumentStatisticsManager() +{ + return *m_pDocumentStatisticsManager; +} + +//IDocumentState +IDocumentState const & SwDoc::getIDocumentState() const +{ + return *m_pDocumentStateManager; +} + +IDocumentState & SwDoc::getIDocumentState() +{ + return *m_pDocumentStateManager; +} + +//IDocumentLayoutAccess +IDocumentLayoutAccess const & SwDoc::getIDocumentLayoutAccess() const +{ + return *m_pDocumentLayoutManager; +} + +IDocumentLayoutAccess & SwDoc::getIDocumentLayoutAccess() +{ + return *m_pDocumentLayoutManager; +} + +::sw::DocumentLayoutManager const & SwDoc::GetDocumentLayoutManager() const +{ + return *m_pDocumentLayoutManager; +} + +::sw::DocumentLayoutManager & SwDoc::GetDocumentLayoutManager() +{ + return *m_pDocumentLayoutManager; +} + +//IDocumentStylePoolAccess +IDocumentStylePoolAccess const & SwDoc::getIDocumentStylePoolAccess() const +{ + return *m_pDocumentStylePoolManager; +} + +IDocumentStylePoolAccess & SwDoc::getIDocumentStylePoolAccess() +{ + return *m_pDocumentStylePoolManager; +} + +//IDocumentExternalData +IDocumentExternalData const & SwDoc::getIDocumentExternalData() const +{ + return *m_pDocumentExternalDataManager; +} + +IDocumentExternalData & SwDoc::getIDocumentExternalData() +{ + return *m_pDocumentExternalDataManager; +} + +/* Implementations the next Interface here */ + +/* + * Document editing (Doc-SS) to fill the document + * by the RTF parser and for the EditShell. + */ +void SwDoc::ChgDBData(const SwDBData& rNewData) +{ + if( rNewData != maDBData ) + { + maDBData = rNewData; + getIDocumentState().SetModified(); + if (m_pDBManager) + m_pDBManager->CommitLastRegistrations(); + } + getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DatabaseName)->UpdateFields(); +} + +namespace { + +struct PostItField_ : public SetGetExpField +{ + PostItField_( const SwNodeIndex& rNdIdx, const SwTextField* pField ) + : SetGetExpField( rNdIdx, pField, nullptr ) {} + + sal_uInt16 GetPageNo( const StringRangeEnumerator &rRangeEnum, + const std::set< sal_Int32 > &rPossiblePages, + sal_uInt16& rVirtPgNo, sal_uInt16& rLineNo ); + + const SwPostItField* GetPostIt() const + { + return static_cast( GetTextField()->GetFormatField().GetField() ); + } +}; + +} + +sal_uInt16 PostItField_::GetPageNo( + const StringRangeEnumerator &rRangeEnum, + const std::set< sal_Int32 > &rPossiblePages, + /* out */ sal_uInt16& rVirtPgNo, /* out */ sal_uInt16& rLineNo ) +{ + //Problem: If a PostItField is contained in a Node that is represented + //by more than one layout instance, + //we have to decide whether it should be printed once or n-times. + //Probably only once. For the page number we don't select a random one, + //but the PostIt's first occurrence in the selected area. + rVirtPgNo = 0; + SwIterator aIter(GetTextField()->GetTextNode()); + for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + TextFrameIndex const nPos = pFrame->MapModelToView( + &GetTextField()->GetTextNode(), GetContent()); + if( pFrame->GetOffset() > nPos || + (pFrame->HasFollow() && pFrame->GetFollow()->GetOffset() <= nPos) ) + continue; + sal_uInt16 nPgNo = pFrame->GetPhyPageNum(); + if( rRangeEnum.hasValue( nPgNo, &rPossiblePages )) + { + rLineNo = static_cast(pFrame->GetLineCount( nPos ) + + pFrame->GetAllLines() - pFrame->GetThisLines()); + rVirtPgNo = pFrame->GetVirtPageNum(); + return nPgNo; + } + } + return 0; +} + +bool sw_GetPostIts(IDocumentFieldsAccess const* pIDFA, SetGetExpFields* pSrtLst) +{ + SwFieldType* pFieldType = pIDFA->GetSysFieldType(SwFieldIds::Postit); + assert(pFieldType); + + std::vector vFields; + pFieldType->GatherFields(vFields); + if(pSrtLst) + for(auto pField: vFields) + { + auto pTextField = pField->GetTextField(); + SwNodeIndex aIdx(pTextField->GetTextNode()); + std::unique_ptr pNew(new PostItField_(aIdx, pTextField)); + pSrtLst->insert(std::move(pNew)); + + } + return vFields.size()>0; +} + +static void lcl_FormatPostIt( + IDocumentContentOperations* pIDCO, + SwPaM& aPam, + const SwPostItField* pField, + bool bNewPage, bool bIsFirstPostIt, + sal_uInt16 nPageNo, sal_uInt16 nLineNo ) +{ + static char const sTmp[] = " : "; + + assert(SwViewShell::GetShellRes()); + + if (bNewPage) + { + pIDCO->InsertPoolItem( aPam, SvxFormatBreakItem( SvxBreak::PageAfter, RES_BREAK ) ); + pIDCO->SplitNode( *aPam.GetPoint(), false ); + } + else if (!bIsFirstPostIt) + { + // add an empty line between different notes + pIDCO->SplitNode( *aPam.GetPoint(), false ); + pIDCO->SplitNode( *aPam.GetPoint(), false ); + } + + OUString aStr( SwViewShell::GetShellRes()->aPostItPage ); + aStr += sTmp + + OUString::number( nPageNo ) + + " "; + if( nLineNo ) + { + aStr += SwViewShell::GetShellRes()->aPostItLine; + aStr += sTmp + + OUString::number( nLineNo ) + + " "; + } + aStr += SwViewShell::GetShellRes()->aPostItAuthor; + aStr += sTmp; + aStr += pField->GetPar1() + " "; + SvtSysLocale aSysLocale; + aStr += /*(LocaleDataWrapper&)*/aSysLocale.GetLocaleData().getDate( pField->GetDate() ); + if(pField->GetResolved()) + aStr += " " + SwResId(STR_RESOLVED); + pIDCO->InsertString( aPam, aStr ); + + pIDCO->SplitNode( *aPam.GetPoint(), false ); + aStr = pField->GetPar2(); +#if defined(_WIN32) + // Throw out all CR in Windows + aStr = aStr.replaceAll("\r", ""); +#endif + pIDCO->InsertString( aPam, aStr ); +} + +/// provide the paper tray to use according to the page style in use, +/// but do that only if the respective item is NOT just the default item +static sal_Int32 lcl_GetPaperBin( const SwPageFrame *pStartFrame ) +{ + sal_Int32 nRes = -1; + + const SwFrameFormat &rFormat = pStartFrame->GetPageDesc()->GetMaster(); + const SfxPoolItem *pItem = nullptr; + SfxItemState eState = rFormat.GetItemState( RES_PAPER_BIN, false, &pItem ); + const SvxPaperBinItem *pPaperBinItem = dynamic_cast< const SvxPaperBinItem * >(pItem); + if (eState > SfxItemState::DEFAULT && pPaperBinItem) + nRes = pPaperBinItem->GetValue(); + + return nRes; +} + +namespace +{ +// tdf#:114663 Translates a range string from user input (with page numbering possibly not +// taking blank pages into account) to equivalent string which references physical page numbers. +// rUIPages2PhyPagesMap must contain a contiguous sequence of UI page numbers +OUString UIPages2PhyPages(const OUString& rUIPageRange, const std::map< sal_Int32, sal_Int32 >& rUIPages2PhyPagesMap) +{ + if (rUIPages2PhyPagesMap.empty()) + return OUString(); + auto iMin = rUIPages2PhyPagesMap.begin(); + const sal_Int32 nUIPageMin = iMin->first, nPhyPageMin = iMin->second; + auto iMax = rUIPages2PhyPagesMap.rbegin(); + const sal_Int32 nUIPageMax = iMax->first, nPhyPageMax = iMax->second; + OUStringBuffer aOut(rUIPageRange.getLength()); + OUStringBuffer aNumber(16); + const sal_Unicode* pInput = rUIPageRange.getStr(); + while (*pInput) + { + while (*pInput >= '0' && *pInput <= '9') + aNumber.append(*pInput++); + if (!aNumber.isEmpty()) + { + sal_Int32 nNumber = aNumber.makeStringAndClear().toInt32(); + if (nNumber < nUIPageMin) + nNumber = nPhyPageMin-1; + else if (nNumber > nUIPageMax) + nNumber = nPhyPageMax+1; + else + nNumber = rUIPages2PhyPagesMap.at(nNumber); + aOut.append(nNumber); + } + + while (*pInput && (*pInput < '0' || *pInput > '9')) + aOut.append(*pInput++); + } + + return aOut.makeStringAndClear(); +} +} + +// tdf#52316 remove blank pages from page count and actual page number +void SwDoc::CalculateNonBlankPages( + const SwRootFrame& rLayout, + sal_uInt16& nDocPageCount, + sal_uInt16& nActualPage) +{ + sal_uInt16 nDocPageCountWithBlank = nDocPageCount; + sal_uInt16 nActualPageWithBlank = nActualPage; + sal_uInt16 nPageNum = 1; + const SwPageFrame *pStPage = dynamic_cast( rLayout.Lower() ); + while (pStPage && nPageNum <= nDocPageCountWithBlank) + { + if ( pStPage->getFrameArea().Height() == 0 ) + { + --nDocPageCount; + if (nPageNum <= nActualPageWithBlank) + --nActualPage; + } + ++nPageNum; + pStPage = static_cast(pStPage->GetNext()); + } +} + +void SwDoc::CalculatePagesForPrinting( + const SwRootFrame& rLayout, + /* out */ SwRenderData &rData, + const SwPrintUIOptions &rOptions, + bool bIsPDFExport, + sal_Int32 nDocPageCount ) +{ + const sal_Int64 nContent = rOptions.getIntValue( "PrintContent", 0 ); + const bool bPrintSelection = nContent == 2; + + // properties to take into account when calculating the set of pages + // (PDF export UI does not allow for selecting left or right pages only) + bool bPrintLeftPages = bIsPDFExport || rOptions.IsPrintLeftPages(); + bool bPrintRightPages = bIsPDFExport || rOptions.IsPrintRightPages(); + // #i103700# printing selections should not allow for automatic inserting empty pages + bool bPrintEmptyPages = !bPrintSelection && rOptions.IsPrintEmptyPages( bIsPDFExport ); + + std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays(); + std::set< sal_Int32 > &rValidPages = rData.GetValidPagesSet(); + // Map page numbers from user input (possibly ignoring blanks) to physical page numbers + std::map< sal_Int32, sal_Int32 > aUIPages2PhyPagesMap; + rValidPages.clear(); + + sal_Int32 nPageNum = 1, nUIPageNum = 1; + const SwPageFrame *pStPage = dynamic_cast( rLayout.Lower() ); + while (pStPage && nPageNum <= nDocPageCount) + { + const bool bNonEmptyPage = pStPage->getFrameArea().Height() != 0; + const bool bPrintThisPage = + ( (bPrintRightPages && pStPage->OnRightPage()) || + (bPrintLeftPages && !pStPage->OnRightPage()) ) && + ( bPrintEmptyPages || bNonEmptyPage ); + + if (bPrintThisPage) + { + rValidPages.insert( nPageNum ); + rPrinterPaperTrays[ nPageNum ] = lcl_GetPaperBin( pStPage ); + } + + if ( bPrintEmptyPages || bNonEmptyPage ) + { + aUIPages2PhyPagesMap[nUIPageNum++] = nPageNum; + } + ++nPageNum; + pStPage = static_cast(pStPage->GetNext()); + } + + // now that we have identified the valid pages for printing according + // to the print settings we need to get the PageRange to use and + // use both results to get the actual pages to be printed + // (post-it settings need to be taken into account later on!) + + // get PageRange value to use + OUString aPageRange; + // #i116085# - adjusting fix for i113919 + if ( !bIsPDFExport ) + { + // PageContent : + // 0 -> print all pages (default if aPageRange is empty) + // 1 -> print range according to PageRange + // 2 -> print selection + if (1 == nContent) + aPageRange = rOptions.getStringValue( "PageRange" ); + + if (2 == nContent) + { + // note that printing selections is actually implemented by copying + // the selection to a new temporary document and printing all of that one. + // Thus for Writer "PrintContent" must never be 2. + // See SwXTextDocument::GetRenderDoc for evaluating if a selection is to be + // printed and for creating the temporary document. + } + + // please note + } + if (aPageRange.isEmpty()) // empty string -> print all + { + // set page range to print to 'all pages' + aPageRange = OUString::number( 1 ) + "-" + OUString::number( nDocPageCount ); + } + else + { + // Convert page numbers from user input to physical page numbers + aPageRange = UIPages2PhyPages(aPageRange, aUIPages2PhyPagesMap); + } + rData.SetPageRange( aPageRange ); + + // get vector of pages to print according to PageRange and valid pages set from above + // (result may be an empty vector, for example if the range string is not correct) + // If excluding empty pages, allow range to specify range of printable pages + StringRangeEnumerator::getRangesFromString( aPageRange, rData.GetPagesToPrint(), + 1, nDocPageCount, 0, &rData.GetValidPagesSet() ); +} + +void SwDoc::UpdatePagesForPrintingWithPostItData( + /* out */ SwRenderData &rData, + const SwPrintUIOptions &rOptions, + sal_Int32 nDocPageCount ) +{ + + SwPostItMode nPostItMode = static_cast( rOptions.getIntValue( "PrintAnnotationMode", 0 ) ); + assert((nPostItMode == SwPostItMode::NONE || rData.HasPostItData()) + && "print post-its without post-it data?"); + const SetGetExpFields::size_type nPostItCount = + rData.HasPostItData() ? rData.m_pPostItFields->size() : 0; + if (nPostItMode == SwPostItMode::NONE || nPostItCount <= 0) + return; + + SET_CURR_SHELL( rData.m_pPostItShell.get() ); + + // clear document and move to end of it + SwDoc & rPostItDoc(*rData.m_pPostItShell->GetDoc()); + SwPaM aPam(rPostItDoc.GetNodes().GetEndOfContent()); + aPam.Move( fnMoveBackward, GoInDoc ); + aPam.SetMark(); + aPam.Move( fnMoveForward, GoInDoc ); + rPostItDoc.getIDocumentContentOperations().DeleteRange( aPam ); + + const StringRangeEnumerator aRangeEnum( rData.GetPageRange(), 1, nDocPageCount, 0 ); + + // For mode SwPostItMode::EndPage: + // maps a physical page number to the page number in post-it document that holds + // the first post-it for that physical page . Needed to relate the correct start frames + // from the post-it doc to the physical page of the document + std::map< sal_Int32, sal_Int32 > aPostItLastStartPageNum; + + // add all post-its on valid pages within the page range to the + // temporary post-it document. + // Since the array of post-it fields is sorted by page and line number we will + // already get them in the correct order + sal_uInt16 nVirtPg = 0, nLineNo = 0, nLastPageNum = 0, nPhyPageNum = 0; + bool bIsFirstPostIt = true; + for (SetGetExpFields::size_type i = 0; i < nPostItCount; ++i) + { + PostItField_& rPostIt = static_cast(*(*rData.m_pPostItFields)[ i ]); + nLastPageNum = nPhyPageNum; + nPhyPageNum = rPostIt.GetPageNo( + aRangeEnum, rData.GetValidPagesSet(), nVirtPg, nLineNo ); + if (nPhyPageNum) + { + // need to insert a page break? + // In SwPostItMode::EndPage mode for each document page the following + // post-it page needs to start on a new page + const bool bNewPage = nPostItMode == SwPostItMode::EndPage && + !bIsFirstPostIt && nPhyPageNum != nLastPageNum; + + lcl_FormatPostIt( &rData.m_pPostItShell->GetDoc()->getIDocumentContentOperations(), aPam, + rPostIt.GetPostIt(), bNewPage, bIsFirstPostIt, nVirtPg, nLineNo ); + bIsFirstPostIt = false; + + if (nPostItMode == SwPostItMode::EndPage) + { + // get the correct number of current pages for the post-it document + rData.m_pPostItShell->CalcLayout(); + const sal_Int32 nPages = rData.m_pPostItShell->GetPageCount(); + aPostItLastStartPageNum[ nPhyPageNum ] = nPages; + } + } + } + + // format post-it doc to get correct number of pages + rData.m_pPostItShell->CalcLayout(); + + SwRootFrame* pPostItRoot = rData.m_pPostItShell->GetLayout(); + //tdf#103313 print dialog maxes out cpu as Idles never get to + //complete this postitshell's desire to complete formatting + pPostItRoot->ResetIdleFormat(); + + const sal_Int32 nPostItDocPageCount = rData.m_pPostItShell->GetPageCount(); + + if (nPostItMode == SwPostItMode::Only || nPostItMode == SwPostItMode::EndDoc) + { + // now add those post-it pages to the vector of pages to print + // or replace them if only post-its should be printed + + if (nPostItMode == SwPostItMode::Only) + { + // no document page to be printed + rData.GetPagesToPrint().clear(); + } + + // now we just need to add the post-it pages to be printed to the + // end of the vector of pages to print + sal_Int32 nPageNum = 0; + const SwPageFrame * pPageFrame = static_cast(pPostItRoot->Lower()); + while( pPageFrame && nPageNum < nPostItDocPageCount ) + { + ++nPageNum; + // negative page number indicates page is from the post-it doc + rData.GetPagesToPrint().push_back( -nPageNum ); + pPageFrame = static_cast(pPageFrame->GetNext()); + } + OSL_ENSURE( nPageNum == nPostItDocPageCount, "unexpected number of pages" ); + } + else if (nPostItMode == SwPostItMode::EndPage) + { + // the next step is to find all the pages from the post-it + // document that should be printed for a given physical page + // of the document + + std::vector< sal_Int32 > aTmpPagesToPrint; + sal_Int32 nLastPostItPage(0); + const size_t nNum = rData.GetPagesToPrint().size(); + for (size_t i = 0 ; i < nNum; ++i) + { + // add the physical page to print from the document + const sal_Int32 nPhysPage = rData.GetPagesToPrint()[i]; + aTmpPagesToPrint.push_back( nPhysPage ); + + // add the post-it document pages to print, i.e those + // post-it pages that have the data for the above physical page + std::map::const_iterator const iter( + aPostItLastStartPageNum.find(nPhysPage)); + if (iter != aPostItLastStartPageNum.end()) + { + for (sal_Int32 j = nLastPostItPage + 1; + j <= iter->second; ++j) + { + // negative page number indicates page is from the + aTmpPagesToPrint.push_back(-j); // post-it document + } + nLastPostItPage = iter->second; + } + } + + // finally we need to assign those vectors to the resulting ones. + // swapping the data should be more efficient than assigning since + // we won't need the temporary vectors anymore + rData.GetPagesToPrint().swap( aTmpPagesToPrint ); + } + +} + +void SwDoc::CalculatePagePairsForProspectPrinting( + const SwRootFrame& rLayout, + /* out */ SwRenderData &rData, + const SwPrintUIOptions &rOptions, + sal_Int32 nDocPageCount ) +{ + std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays(); + std::set< sal_Int32 > &rValidPagesSet = rData.GetValidPagesSet(); + std::vector< std::pair< sal_Int32, sal_Int32 > > &rPagePairs = rData.GetPagePairsForProspectPrinting(); + std::map< sal_Int32, const SwPageFrame * > validStartFrames; + + rPagePairs.clear(); + rValidPagesSet.clear(); + + OUString aPageRange; + // PageContent : + // 0 -> print all pages (default if aPageRange is empty) + // 1 -> print range according to PageRange + // 2 -> print selection + const sal_Int64 nContent = rOptions.getIntValue( "PrintContent", 0 ); + if (nContent == 1) + aPageRange = rOptions.getStringValue( "PageRange" ); + if (aPageRange.isEmpty()) // empty string -> print all + { + // set page range to print to 'all pages' + aPageRange = OUString::number( 1 ) + "-" + OUString::number( nDocPageCount ); + } + StringRangeEnumerator aRange( aPageRange, 1, nDocPageCount, 0 ); + + if ( aRange.size() <= 0) + return; + + const SwPageFrame *pStPage = dynamic_cast( rLayout.Lower() ); + for ( sal_Int32 i = 1; pStPage && i < nDocPageCount; ++i ) + pStPage = static_cast(pStPage->GetNext()); + if ( !pStPage ) // Then it was that + return; + + // currently for prospect printing all pages are valid to be printed + // thus we add them all to the respective map and set for later use + sal_Int32 nPageNum = 0; + const SwPageFrame *pPageFrame = dynamic_cast( rLayout.Lower() ); + while( pPageFrame && nPageNum < nDocPageCount ) + { + ++nPageNum; + rValidPagesSet.insert( nPageNum ); + validStartFrames[ nPageNum ] = pPageFrame; + pPageFrame = static_cast(pPageFrame->GetNext()); + + rPrinterPaperTrays[ nPageNum ] = lcl_GetPaperBin( pStPage ); + } + OSL_ENSURE( nPageNum == nDocPageCount, "unexpected number of pages" ); + + // properties to take into account when calculating the set of pages + // Note: here bPrintLeftPages and bPrintRightPages refer to the (virtual) resulting pages + // of the prospect! + bool bPrintLeftPages = rOptions.IsPrintLeftPages(); + bool bPrintRightPages = rOptions.IsPrintRightPages(); + bool bPrintProspectRTL = rOptions.getIntValue( "PrintProspectRTL", 0 ) != 0; + + // get pages for prospect printing according to the 'PageRange' + // (duplicates and any order allowed!) + std::vector< sal_Int32 > aPagesToPrint; + StringRangeEnumerator::getRangesFromString( + aPageRange, aPagesToPrint, 1, nDocPageCount, 0 ); + + if (aPagesToPrint.empty()) + return; + + // now fill the vector for calculating the page pairs with the start frames + // from the above obtained vector + std::vector< const SwPageFrame * > aVec; + for (sal_Int32 nPage : aPagesToPrint) + { + const SwPageFrame *pFrame = validStartFrames[ nPage ]; + aVec.push_back( pFrame ); + } + + // just one page is special ... + if ( 1 == aVec.size() ) + aVec.insert( aVec.begin() + 1, nullptr ); // insert a second empty page + else + { + // now extend the number of pages to fit a multiple of 4 + // (4 'normal' pages are needed for a single prospect paper + // with back and front) + while( aVec.size() & 3 ) + aVec.push_back( nullptr ); + } + + // make sure that all pages are in correct order + std::vector< const SwPageFrame * >::size_type nSPg = 0; + std::vector< const SwPageFrame * >::size_type nEPg = aVec.size(); + sal_Int32 nStep = 1; + if ( 0 == (nEPg & 1 )) // there are no uneven ones! + --nEPg; + + if ( !bPrintLeftPages ) + ++nStep; + else if ( !bPrintRightPages ) + { + ++nStep; + ++nSPg; + --nEPg; + } + + // the number of 'virtual' pages to be printed + sal_Int32 nCntPage = (( nEPg - nSPg ) / ( 2 * nStep )) + 1; + + for ( sal_Int32 nPrintCount = 0; nSPg < nEPg && + nPrintCount < nCntPage; ++nPrintCount ) + { + pStPage = aVec[ nSPg ]; + const SwPageFrame* pNxtPage = nEPg < aVec.size() ? aVec[ nEPg ] : nullptr; + + short nRtlOfs = bPrintProspectRTL ? 1 : 0; + if ( 0 == (( nSPg + nRtlOfs) & 1 ) ) // switch for odd number in LTR, even number in RTL + { + const SwPageFrame* pTmp = pStPage; + pStPage = pNxtPage; + pNxtPage = pTmp; + } + + sal_Int32 nFirst = -1, nSecond = -1; + for ( int nC = 0; nC < 2; ++nC ) + { + sal_Int32 nPage = -1; + if ( pStPage ) + nPage = pStPage->GetPhyPageNum(); + if (nC == 0) + nFirst = nPage; + else + nSecond = nPage; + + pStPage = pNxtPage; + } + rPagePairs.emplace_back(nFirst, nSecond ); + + nSPg = nSPg + nStep; + nEPg = nEPg - nStep; + } + OSL_ENSURE( size_t(nCntPage) == rPagePairs.size(), "size mismatch for number of page pairs" ); + + // luckily prospect printing does not make use of post-its so far, + // thus we are done here. +} + +/// @return the reference in the doc for the name +const SwFormatRefMark* SwDoc::GetRefMark( const OUString& rName ) const +{ + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)) + { + auto pFormatRef = dynamic_cast(pItem); + if(!pFormatRef) + continue; + + const SwTextRefMark* pTextRef = pFormatRef->GetTextRefMark(); + if( pTextRef && &pTextRef->GetTextNode().GetNodes() == &GetNodes() && + rName == pFormatRef->GetRefName() ) + return pFormatRef; + } + return nullptr; +} + +/// @return the RefMark per index - for Uno +const SwFormatRefMark* SwDoc::GetRefMark( sal_uInt16 nIndex ) const +{ + const SwFormatRefMark* pRet = nullptr; + + sal_uInt32 nCount = 0; + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)) + { + auto pRefMark = dynamic_cast(pItem); + if( !pRefMark ) + continue; + const SwTextRefMark* pTextRef = pRefMark->GetTextRefMark(); + if( pTextRef && &pTextRef->GetTextNode().GetNodes() == &GetNodes() ) + { + if(nCount == nIndex) + { + pRet = pRefMark; + break; + } + nCount++; + } + } + return pRet; +} + +/// @return the names of all set references in the Doc +//JP 24.06.96: If the array pointer is 0, then just return whether a RefMark is set in the Doc +// OS 25.06.96: From now on we always return the reference count +sal_uInt16 SwDoc::GetRefMarks( std::vector* pNames ) const +{ + sal_uInt16 nCount = 0; + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)) + { + auto pRefMark = dynamic_cast(pItem); + if( !pRefMark ) + continue; + const SwTextRefMark* pTextRef = pRefMark->GetTextRefMark(); + if( pTextRef && &pTextRef->GetTextNode().GetNodes() == &GetNodes() ) + { + if( pNames ) + { + OUString aTmp(pRefMark->GetRefName()); + pNames->insert(pNames->begin() + nCount, aTmp); + } + ++nCount; + } + } + + return nCount; +} + +static bool lcl_SpellAndGrammarAgain( const SwNodePtr& rpNd, void* pArgs ) +{ + SwTextNode *pTextNode = rpNd->GetTextNode(); + bool bOnlyWrong = *static_cast(pArgs); + if( pTextNode ) + { + if( bOnlyWrong ) + { + if( pTextNode->GetWrong() && + pTextNode->GetWrong()->InvalidateWrong() ) + pTextNode->SetWrongDirty(SwTextNode::WrongState::TODO); + if( pTextNode->GetGrammarCheck() && + pTextNode->GetGrammarCheck()->InvalidateWrong() ) + pTextNode->SetGrammarCheckDirty( true ); + } + else + { + pTextNode->SetWrongDirty(SwTextNode::WrongState::TODO); + if( pTextNode->GetWrong() ) + pTextNode->GetWrong()->SetInvalid( 0, COMPLETE_STRING ); + pTextNode->SetGrammarCheckDirty( true ); + if( pTextNode->GetGrammarCheck() ) + pTextNode->GetGrammarCheck()->SetInvalid( 0, COMPLETE_STRING ); + } + } + return true; +} + +static bool lcl_CheckSmartTagsAgain( const SwNodePtr& rpNd, void* ) +{ + SwTextNode *pTextNode = rpNd->GetTextNode(); + if( pTextNode ) + { + pTextNode->SetSmartTagDirty( true ); + if( pTextNode->GetSmartTags() ) + { + pTextNode->SetSmartTags( nullptr ); + } + } + return true; +} + +/** + * Re-trigger spelling in the idle handler. + * + * @param bInvalid if , the WrongLists in all nodes are invalidated + * and the SpellInvalid flag is set on all pages. + * @param bOnlyWrong controls whether only the areas with wrong words are + * checked or the whole area. + * @param bSmartTags ??? + */ +void SwDoc::SpellItAgainSam( bool bInvalid, bool bOnlyWrong, bool bSmartTags ) +{ + o3tl::sorted_vector aAllLayouts = GetAllLayouts(); + assert(getIDocumentLayoutAccess().GetCurrentLayout() && "SpellAgain: Where's my RootFrame?"); + if( bInvalid ) + { + for ( auto aLayout : aAllLayouts ) + { + aLayout->AllInvalidateSmartTagsOrSpelling(bSmartTags); + aLayout->SetNeedGrammarCheck(true); + } + if ( bSmartTags ) + GetNodes().ForEach( lcl_CheckSmartTagsAgain, &bOnlyWrong ); + GetNodes().ForEach( lcl_SpellAndGrammarAgain, &bOnlyWrong ); + } + + for ( auto aLayout : aAllLayouts ) + aLayout->SetIdleFlags(); +} + +void SwDoc::InvalidateAutoCompleteFlag() +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot ) + { + o3tl::sorted_vector aAllLayouts = GetAllLayouts(); + for( auto aLayout : aAllLayouts ) + aLayout->AllInvalidateAutoCompleteWords(); + for( sal_uLong nNd = 1, nCnt = GetNodes().Count(); nNd < nCnt; ++nNd ) + { + SwTextNode* pTextNode = GetNodes()[ nNd ]->GetTextNode(); + if ( pTextNode ) pTextNode->SetAutoCompleteWordDirty( true ); + } + + for( auto aLayout : aAllLayouts ) + aLayout->SetIdleFlags(); + } +} + +const SwFormatINetFormat* SwDoc::FindINetAttr( const OUString& rName ) const +{ + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + auto pFormatItem = dynamic_cast(pItem); + if( !pFormatItem || pFormatItem->GetName() != rName ) + continue; + const SwTextINetFormat* pTextAttr = pFormatItem->GetTextINetFormat(); + if( !pTextAttr ) + continue; + const SwTextNode* pTextNd = pTextAttr->GetpTextNode(); + if( pTextNd && &pTextNd->GetNodes() == &GetNodes() ) + { + return pFormatItem; + } + } + return nullptr; +} + +void SwDoc::Summary( SwDoc* pExtDoc, sal_uInt8 nLevel, sal_uInt8 nPara, bool bImpress ) +{ + const SwOutlineNodes& rOutNds = GetNodes().GetOutLineNds(); + if( pExtDoc && !rOutNds.empty() ) + { + ::StartProgress( STR_STATSTR_SUMMARY, 0, rOutNds.size(), GetDocShell() ); + SwNodeIndex aEndOfDoc( pExtDoc->GetNodes().GetEndOfContent(), -1 ); + for( SwOutlineNodes::size_type i = 0; i < rOutNds.size(); ++i ) + { + ::SetProgressState( static_cast(i), GetDocShell() ); + const sal_uLong nIndex = rOutNds[ i ]->GetIndex(); + + const int nLvl = GetNodes()[ nIndex ]->GetTextNode()->GetAttrOutlineLevel()-1; + if( nLvl > nLevel ) + continue; + long nEndOfs = 1; + sal_uInt8 nWish = nPara; + sal_uLong nNextOutNd = i + 1 < rOutNds.size() ? + rOutNds[ i + 1 ]->GetIndex() : GetNodes().Count(); + bool bKeep = false; + while( ( nWish || bKeep ) && nIndex + nEndOfs < nNextOutNd && + GetNodes()[ nIndex + nEndOfs ]->IsTextNode() ) + { + SwTextNode* pTextNode = GetNodes()[ nIndex+nEndOfs ]->GetTextNode(); + if (pTextNode->GetText().getLength() && nWish) + --nWish; + bKeep = pTextNode->GetSwAttrSet().GetKeep().GetValue(); + ++nEndOfs; + } + + SwNodeRange aRange( *rOutNds[ i ], 0, *rOutNds[ i ], nEndOfs ); + GetNodes().Copy_( aRange, aEndOfDoc ); + } + const SwTextFormatColls *pColl = pExtDoc->GetTextFormatColls(); + for( SwTextFormatColls::size_type i = 0; i < pColl->size(); ++i ) + (*pColl)[ i ]->ResetFormatAttr( RES_PAGEDESC, RES_BREAK ); + SwNodeIndex aIndx( pExtDoc->GetNodes().GetEndOfExtras() ); + ++aEndOfDoc; + while( aIndx < aEndOfDoc ) + { + bool bDelete = false; + SwNode *pNode = &aIndx.GetNode(); + if( pNode->IsTextNode() ) + { + SwTextNode *pNd = pNode->GetTextNode(); + if( pNd->HasSwAttrSet() ) + pNd->ResetAttr( RES_PAGEDESC, RES_BREAK ); + if( bImpress ) + { + SwTextFormatColl* pMyColl = pNd->GetTextColl(); + + const sal_uInt16 nHeadLine = static_cast( + !pMyColl->IsAssignedToListLevelOfOutlineStyle() + ? RES_POOLCOLL_HEADLINE2 + : RES_POOLCOLL_HEADLINE1 ); + pMyColl = pExtDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nHeadLine ); + pNd->ChgFormatColl( pMyColl ); + } + if( !pNd->Len() && + pNd->StartOfSectionIndex()+2 < pNd->EndOfSectionIndex() ) + { + bDelete = true; + pExtDoc->GetNodes().Delete( aIndx ); + } + } + if( !bDelete ) + ++aIndx; + } + ::EndProgress( GetDocShell() ); + } +} + +namespace +{ +void RemoveOrDeleteContents(SwTextNode* pTextNd, IDocumentContentOperations& xOperations) +{ + SwPaM aPam(*pTextNd, 0, *pTextNd, pTextNd->GetText().getLength()); + + // Remove hidden paragraph or delete contents: + // Delete contents if + // 1. removing the paragraph would result in an empty section or + // 2. if the paragraph is the last paragraph in the section and + // there is no paragraph in front of the paragraph: + if ((2 == pTextNd->EndOfSectionIndex() - pTextNd->StartOfSectionIndex()) + || (1 == pTextNd->EndOfSectionIndex() - pTextNd->GetIndex() + && !pTextNd->GetNodes()[pTextNd->GetIndex() - 1]->GetTextNode())) + { + xOperations.DeleteRange(aPam); + } + else + { + aPam.DeleteMark(); + xOperations.DelFullPara(aPam); + } +} +// Returns if the data was actually modified +bool HandleHidingField(SwFormatField& rFormatField, const SwNodes& rNodes, + IDocumentContentOperations& xOperations) +{ + if( !rFormatField.GetTextField() ) + return false; + SwTextNode* pTextNd = rFormatField.GetTextField()->GetpTextNode(); + if( pTextNd + && pTextNd->GetpSwpHints() && pTextNd->IsHiddenByParaField() + && &pTextNd->GetNodes() == &rNodes) + { + RemoveOrDeleteContents(pTextNd, xOperations); + return true; + } + return false; +} +} + +// The greater the returned value, the more weight has this field type on deciding the final +// paragraph state +int SwDoc::FieldCanHideParaWeight(SwFieldIds eFieldId) const +{ + switch (eFieldId) + { + case SwFieldIds::HiddenPara: + return 20; + case SwFieldIds::Database: + return GetDocumentSettingManager().get(DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA) + ? 10 + : 0; + default: + return 0; + } +} + +bool SwDoc::FieldHidesPara(const SwField& rField) const +{ + switch (rField.GetTyp()->Which()) + { + case SwFieldIds::HiddenPara: + return static_cast(rField).IsHidden(); + case SwFieldIds::Database: + return FieldCanHideParaWeight(SwFieldIds::Database) + && rField.ExpandField(true, nullptr).isEmpty(); + default: + return false; + } +} + +/// Remove the invisible content from the document e.g. hidden areas, hidden paragraphs +// Returns if the data was actually modified +bool SwDoc::RemoveInvisibleContent() +{ + bool bRet = false; + GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_DELETE_INVISIBLECNTNT, nullptr ); + + { + class FieldTypeGuard : public SwClient + { + public: + explicit FieldTypeGuard(SwFieldType* pType) + : SwClient(pType) + { + } + const SwFieldType* get() const + { + return static_cast(GetRegisteredIn()); + } + }; + // Removing some nodes for one SwFieldIds::Database type might remove the type from + // document's field types, invalidating iterators. So, we need to create own list of + // matching types prior to processing them. + std::vector> aHidingFieldTypes; + for (std::unique_ptr const & pType : *getIDocumentFieldsAccess().GetFieldTypes()) + { + if (FieldCanHideParaWeight(pType->Which())) + aHidingFieldTypes.push_back(std::make_unique(pType.get())); + } + for (const auto& pTypeGuard : aHidingFieldTypes) + { + if (const SwFieldType* pType = pTypeGuard->get()) + { + std::vector vFields; + pType->GatherFields(vFields); + for(auto pFormatField: vFields) + bRet |= HandleHidingField(*pFormatField, GetNodes(), getIDocumentContentOperations()); + } + } + } + + // Remove any hidden paragraph (hidden text attribute) + for( sal_uLong n = GetNodes().Count(); n; ) + { + SwTextNode* pTextNd = GetNodes()[ --n ]->GetTextNode(); + if ( pTextNd ) + { + bool bRemoved = false; + if ( pTextNd->HasHiddenCharAttribute( true ) ) + { + bRemoved = true; + bRet = true; + + if (2 == pTextNd->EndOfSectionIndex() - pTextNd->StartOfSectionIndex()) + { + SwFrameFormat *const pFormat = pTextNd->StartOfSectionNode()->GetFlyFormat(); + if (nullptr != pFormat) + { + // remove hidden text frame + getIDocumentLayoutAccess().DelLayoutFormat(pFormat); + } + else + { + // default, remove hidden paragraph + RemoveOrDeleteContents(pTextNd, getIDocumentContentOperations()); + } + } + else + { + // default, remove hidden paragraph + RemoveOrDeleteContents(pTextNd, getIDocumentContentOperations()); + } + } + else if ( pTextNd->HasHiddenCharAttribute( false ) ) + { + bRemoved = true; + bRet = true; + SwScriptInfo::DeleteHiddenRanges( *pTextNd ); + } + + // Footnotes/Frames may have been removed, therefore we have + // to reset n: + if ( bRemoved ) + { + // [n] has to be inside [0 .. GetNodes().Count()] range + if (n > GetNodes().Count()) + n = GetNodes().Count(); + } + } + } + + { + // Delete/empty all hidden areas + o3tl::sorted_vector aSectFormats; + SwSectionFormats& rSectFormats = GetSections(); + + for( SwSectionFormats::size_type n = rSectFormats.size(); n; ) + { + SwSectionFormat* pSectFormat = rSectFormats[ --n ]; + // don't add sections in Undo/Redo + if( !pSectFormat->IsInNodesArr()) + continue; + SwSection* pSect = pSectFormat->GetSection(); + if( pSect->CalcHiddenFlag() ) + { + SwSection* pParent = pSect, *pTmp; + while( nullptr != (pTmp = pParent->GetParent() )) + { + if( pTmp->IsHiddenFlag() ) + pSect = pTmp; + pParent = pTmp; + } + + aSectFormats.insert( pSect->GetFormat() ); + } + if( !pSect->GetCondition().isEmpty() ) + { + SwSectionData aSectionData( *pSect ); + aSectionData.SetCondition( OUString() ); + aSectionData.SetHidden( false ); + UpdateSection( n, aSectionData ); + } + } + + auto n = aSectFormats.size(); + + if( 0 != n ) + { + while( n ) + { + SwSectionFormat* pSectFormat = aSectFormats[ --n ]; + SwSectionNode* pSectNd = pSectFormat->GetSectionNode(); + if( pSectNd ) + { + bRet = true; + SwPaM aPam( *pSectNd ); + + if( pSectNd->StartOfSectionNode()->StartOfSectionIndex() == + pSectNd->GetIndex() - 1 && + pSectNd->StartOfSectionNode()->EndOfSectionIndex() == + pSectNd->EndOfSectionIndex() + 1 ) + { + // only delete the content + SwContentNode* pCNd = GetNodes().GoNext( + &aPam.GetPoint()->nNode ); + aPam.GetPoint()->nContent.Assign( pCNd, 0 ); + aPam.SetMark(); + aPam.GetPoint()->nNode = *pSectNd->EndOfSectionNode(); + pCNd = SwNodes::GoPrevious( + &aPam.GetPoint()->nNode ); + aPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + + getIDocumentContentOperations().DeleteRange( aPam ); + } + else + { + // delete the whole section + aPam.SetMark(); + aPam.GetPoint()->nNode = *pSectNd->EndOfSectionNode(); + getIDocumentContentOperations().DelFullPara( aPam ); + } + + } + } + } + } + + if( bRet ) + getIDocumentState().SetModified(); + GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_DELETE_INVISIBLECNTNT, nullptr ); + return bRet; +} + +bool SwDoc::HasInvisibleContent() const +{ + std::vector vFields; + getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::HiddenPara)->GatherFields(vFields); + if(vFields.size()) + return true; + + // Search for any hidden paragraph (hidden text attribute) + for( sal_uLong n = GetNodes().Count()-1; n; --n) + { + SwTextNode* pTextNd = GetNodes()[ n ]->GetTextNode(); + if ( pTextNd && + ( pTextNd->HasHiddenCharAttribute( true ) || pTextNd->HasHiddenCharAttribute( false ) ) ) + return true; + } + + for(auto pSectFormat : GetSections()) + { + // don't add sections in Undo/Redo + if( !pSectFormat->IsInNodesArr()) + continue; + SwSection* pSect = pSectFormat->GetSection(); + if( pSect->IsHidden() ) + return true; + } + return false; +} + +bool SwDoc::RestoreInvisibleContent() +{ + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, & nLastUndoId) + && (SwUndoId::UI_DELETE_INVISIBLECNTNT == nLastUndoId)) + { + GetIDocumentUndoRedo().Undo(); + GetIDocumentUndoRedo().ClearRedo(); + return true; + } + return false; +} + +bool SwDoc::ConvertFieldsToText(SwRootFrame const& rLayout) +{ + bool bRet = false; + getIDocumentFieldsAccess().LockExpFields(); + GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE, nullptr ); + + const SwFieldTypes* pMyFieldTypes = getIDocumentFieldsAccess().GetFieldTypes(); + const SwFieldTypes::size_type nCount = pMyFieldTypes->size(); + //go backward, field types are removed + for(SwFieldTypes::size_type nType = nCount; nType > 0; --nType) + { + const SwFieldType *pCurType = (*pMyFieldTypes)[nType - 1].get(); + + if ( SwFieldIds::Postit == pCurType->Which() ) + continue; + + std::vector vFieldFormats; + pCurType->GatherFields(vFieldFormats, false); + for(const auto& rpFieldFormat : vFieldFormats) + { + const SwTextField *pTextField = rpFieldFormat->GetTextField(); + // skip fields that are currently not in the document + // e.g. fields in undo or redo array + + bool bSkip = !pTextField || + !pTextField->GetpTextNode()->GetNodes().IsDocNodes(); + + if (!bSkip) + { + bool bInHeaderFooter = IsInHeaderFooter(SwNodeIndex(*pTextField->GetpTextNode())); + const SwFormatField& rFormatField = pTextField->GetFormatField(); + const SwField* pField = rFormatField.GetField(); + + //#i55595# some fields have to be excluded in headers/footers + SwFieldIds nWhich = pField->GetTyp()->Which(); + if(!bInHeaderFooter || + (nWhich != SwFieldIds::PageNumber && + nWhich != SwFieldIds::Chapter && + nWhich != SwFieldIds::GetExp&& + nWhich != SwFieldIds::SetExp&& + nWhich != SwFieldIds::Input&& + nWhich != SwFieldIds::RefPageGet&& + nWhich != SwFieldIds::RefPageSet)) + { + OUString sText = pField->ExpandField(true, &rLayout); + + // database fields should not convert their command into text + if( SwFieldIds::Database == pCurType->Which() && !static_cast(pField)->IsInitialized()) + sText.clear(); + + SwPaM aInsertPam(*pTextField->GetpTextNode(), pTextField->GetStart()); + aInsertPam.SetMark(); + + // go to the end of the field + const SwTextField *pFieldAtEnd = sw::DocumentFieldsManager::GetTextFieldAtPos(*aInsertPam.End()); + if (pFieldAtEnd && pFieldAtEnd->Which() == RES_TXTATR_INPUTFIELD) + { + SwPosition &rEndPos = *aInsertPam.GetPoint(); + rEndPos.nContent = SwCursorShell::EndOfInputFieldAtPos( *aInsertPam.End() ); + } + else + { + aInsertPam.Move(); + } + + // first insert the text after field to keep the field's attributes, + // then delete the field + if (!sText.isEmpty()) + { + // to keep the position after insert + SwPaM aDelPam( *aInsertPam.GetMark(), *aInsertPam.GetPoint() ); + aDelPam.Move( fnMoveBackward ); + aInsertPam.DeleteMark(); + + getIDocumentContentOperations().InsertString( aInsertPam, sText ); + + aDelPam.Move(); + // finally remove the field + getIDocumentContentOperations().DeleteAndJoin( aDelPam ); + } + else + { + getIDocumentContentOperations().DeleteAndJoin( aInsertPam ); + } + + bRet = true; + } + } + } + } + + if( bRet ) + getIDocumentState().SetModified(); + GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_REPLACE, nullptr ); + getIDocumentFieldsAccess().UnlockExpFields(); + return bRet; + +} + +bool SwDoc::IsInsTableFormatNum() const +{ + return SW_MOD()->IsInsTableFormatNum(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +bool SwDoc::IsInsTableChangeNumFormat() const +{ + return SW_MOD()->IsInsTableChangeNumFormat(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +bool SwDoc::IsInsTableAlignNum() const +{ + return SW_MOD()->IsInsTableAlignNum(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +bool SwDoc::IsSplitVerticalByDefault() const +{ + return SW_MOD()->IsSplitVerticalByDefault(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +void SwDoc::SetSplitVerticalByDefault(bool value) +{ + SW_MOD()->SetSplitVerticalByDefault(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE), value); +} + +/// Set up the InsertDB as Undo table +void SwDoc::AppendUndoForInsertFromDB( const SwPaM& rPam, bool bIsTable ) +{ + if( bIsTable ) + { + const SwTableNode* pTableNd = rPam.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::unique_ptr pUndo(new SwUndoCpyTable(this)); + pUndo->SetTableSttIdx( pTableNd->GetIndex() ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else if( rPam.HasMark() ) + { + std::unique_ptr pUndo(new SwUndoCpyDoc( rPam )); + pUndo->SetInsertRange( rPam, false ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } +} + +void SwDoc::ChangeTOX(SwTOXBase & rTOX, const SwTOXBase & rNew) +{ + assert(dynamic_cast(&rTOX)); + SwTOXBaseSection& rTOXSect(static_cast(rTOX)); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(this, rTOXSect, rNew)); + } + + rTOX = rNew; + + // note: do not Update the ToX here - the caller will do it, with a ViewShell! +} + +OUString SwDoc::GetPaMDescr(const SwPaM & rPam) +{ + if (&rPam.GetNode() == &rPam.GetNode(false)) + { + SwTextNode * pTextNode = rPam.GetNode().GetTextNode(); + + if (nullptr != pTextNode) + { + const sal_Int32 nStart = rPam.Start()->nContent.GetIndex(); + const sal_Int32 nEnd = rPam.End()->nContent.GetIndex(); + + return SwResId(STR_START_QUOTE) + + ShortenString(pTextNode->GetText().copy(nStart, nEnd - nStart), + nUndoStringLength, + SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + } + } + else + { + return SwResId(STR_PARAGRAPHS); + } + + return "??"; +} + +bool SwDoc::ContainsHiddenChars() const +{ + for( sal_uLong n = GetNodes().Count(); n; ) + { + SwNode* pNd = GetNodes()[ --n ]; + if ( pNd->IsTextNode() && pNd->GetTextNode()->HasHiddenCharAttribute( false ) ) + return true; + } + + return false; +} + +std::shared_ptr SwDoc::CreateUnoCursor( const SwPosition& rPos, bool bTableCursor ) +{ + std::shared_ptr pNew; + if( bTableCursor ) + pNew = std::make_shared(rPos); + else + pNew = std::make_shared(rPos); + + mvUnoCursorTable.push_back( pNew ); + return pNew; +} + +void SwDoc::ChkCondColls() +{ + for (SwTextFormatColls::size_type n = 0; n < mpTextFormatCollTable->size(); ++n) + { + SwTextFormatColl *pColl = (*mpTextFormatCollTable)[n]; + if (RES_CONDTXTFMTCOLL == pColl->Which()) + pColl->CallSwClientNotify( SwAttrHint() ); + } +} + +uno::Reference< script::vba::XVBAEventProcessor > const & +SwDoc::GetVbaEventProcessor() +{ +#if HAVE_FEATURE_SCRIPTING + if( !mxVbaEvents.is() && mpDocShell && ooo::vba::isAlienWordDoc( *mpDocShell ) ) + { + try + { + uno::Reference< frame::XModel > xModel( mpDocShell->GetModel(), uno::UNO_SET_THROW ); + uno::Sequence< uno::Any > aArgs(1); + aArgs[0] <<= xModel; + mxVbaEvents.set( ooo::vba::createVBAUnoAPIServiceWithArgs( mpDocShell, "com.sun.star.script.vba.VBATextEventProcessor" , aArgs ), uno::UNO_QUERY_THROW ); + } + catch( uno::Exception& ) + { + } + } +#endif + return mxVbaEvents; +} + +void SwDoc::SetMissingDictionaries( bool bIsMissing ) +{ + if (!bIsMissing) + meDictionaryMissing = MissingDictionary::False; + else if (meDictionaryMissing == MissingDictionary::Undefined) + meDictionaryMissing = MissingDictionary::True; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docbasic.cxx b/sw/source/core/doc/docbasic.cxx new file mode 100644 index 000000000..4fb0db964 --- /dev/null +++ b/sw/source/core/doc/docbasic.cxx @@ -0,0 +1,234 @@ +/* -*- 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 + +using namespace ::com::sun::star::uno; + +static Sequence *lcl_docbasic_convertArgs( SbxArray& rArgs ) +{ + Sequence *pRet = nullptr; + + sal_uInt32 nCount = rArgs.Count32(); + if( nCount > 1 ) + { + nCount--; + pRet = new Sequence( nCount ); + Any *pUnoArgs = pRet->getArray(); + for( sal_uInt32 i=0; iGetType() ) + { + case SbxSTRING: + pUnoArgs[i] <<= pVar->GetOUString(); + break; + case SbxCHAR: + pUnoArgs[i] <<= static_cast(pVar->GetChar()) ; + break; + case SbxUSHORT: + pUnoArgs[i] <<= static_cast(pVar->GetUShort()); + break; + case SbxLONG: + pUnoArgs[i] <<= pVar->GetLong(); + break; + default: + pUnoArgs[i].clear(); + break; + } + } + } + + return pRet; +} + +void SwDoc::ExecMacro( const SvxMacro& rMacro, OUString* pRet, SbxArray* pArgs ) +{ + switch( rMacro.GetScriptType() ) + { + case STARBASIC: + { + SbxBaseRef aRef; + SbxValue* pRetValue = new SbxValue; + aRef = pRetValue; + mpDocShell->CallBasic( rMacro.GetMacName(), + rMacro.GetLibName(), + pArgs, pRet ? pRetValue : nullptr ); + + if( pRet && SbxNULL < pRetValue->GetType() && + SbxVOID != pRetValue->GetType() ) + { + // valid value, so set it + *pRet = pRetValue->GetOUString(); + } + } + break; + case JAVASCRIPT: + // ignore JavaScript calls + break; + case EXTENDED_STYPE: + { + std::unique_ptr > pUnoArgs; + if( pArgs ) + { + // better to rename the local function to lcl_translateBasic2Uno and + // a much shorter routine can be found in sfx2/source/doc/objmisc.cxx + pUnoArgs.reset(lcl_docbasic_convertArgs( *pArgs )); + } + + if (!pUnoArgs) + { + pUnoArgs.reset(new Sequence< Any > (0)); + } + + // TODO - return value is not handled + Any aRet; + Sequence< sal_Int16 > aOutArgsIndex; + Sequence< Any > aOutArgs; + + SAL_INFO("sw", "SwDoc::ExecMacro URL is " << rMacro.GetMacName() ); + + mpDocShell->CallXScript( + rMacro.GetMacName(), *pUnoArgs, aRet, aOutArgsIndex, aOutArgs); + + break; + } + } +} + +sal_uInt16 SwDoc::CallEvent( SvMacroItemId nEvent, const SwCallMouseEvent& rCallEvent, + bool bCheckPtr ) +{ + if( !mpDocShell ) // we can't do that without a DocShell! + return 0; + + sal_uInt16 nRet = 0; + const SvxMacroTableDtor* pTable = nullptr; + switch( rCallEvent.eType ) + { + case EVENT_OBJECT_INETATTR: + if( bCheckPtr ) + { + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + auto pFormatItem = dynamic_cast(pItem); + if( pFormatItem && rCallEvent.PTR.pINetAttr == pFormatItem ) + { + bCheckPtr = false; // misuse as a flag + break; + } + } + } + if( !bCheckPtr ) + pTable = rCallEvent.PTR.pINetAttr->GetMacroTable(); + break; + + case EVENT_OBJECT_URLITEM: + case EVENT_OBJECT_IMAGE: + { + const SwFrameFormat* pFormat = rCallEvent.PTR.pFormat; + if( bCheckPtr ) + { + if (GetSpzFrameFormats()->IsAlive(pFormat)) + bCheckPtr = false; // misuse as a flag + else + // this shouldn't be possible now that SwCallMouseEvent + // listens for dying format? + assert(false); + } + if( !bCheckPtr ) + pTable = &pFormat->GetMacro().GetMacroTable(); + } + break; + + case EVENT_OBJECT_IMAGEMAP: + { + const IMapObject* pIMapObj = rCallEvent.PTR.IMAP.pIMapObj; + if( bCheckPtr ) + { + const SwFrameFormat* pFormat = rCallEvent.PTR.IMAP.pFormat; + if (GetSpzFrameFormats()->IsAlive(pFormat)) + { + const ImageMap* pIMap = pFormat->GetURL().GetMap(); + if (pIMap) + { + for( size_t nPos = pIMap->GetIMapObjectCount(); nPos; ) + if( pIMapObj == pIMap->GetIMapObject( --nPos )) + { + bCheckPtr = false; // misuse as a flag + break; + } + } + } + } + if( !bCheckPtr ) + pTable = &pIMapObj->GetMacroTable(); + } + break; + default: + break; + } + + if( pTable ) + { + nRet = 0x1; + if( pTable->IsKeyValid( nEvent ) ) + { + const SvxMacro& rMacro = *pTable->Get( nEvent ); + if( STARBASIC == rMacro.GetScriptType() ) + { + nRet += ERRCODE_NONE == mpDocShell->CallBasic( rMacro.GetMacName(), + rMacro.GetLibName(), nullptr ) ? 1 : 0; + } + else if( EXTENDED_STYPE == rMacro.GetScriptType() ) + { + std::unique_ptr > pUnoArgs(new Sequence()); + + Any aRet; + Sequence< sal_Int16 > aOutArgsIndex; + Sequence< Any > aOutArgs; + + SAL_INFO("sw", "SwDoc::CallEvent URL is " << rMacro.GetMacName() ); + + nRet += ERRCODE_NONE == mpDocShell->CallXScript( + rMacro.GetMacName(), *pUnoArgs,aRet, aOutArgsIndex, aOutArgs) ? 1 : 0; + } + // JavaScript calls are ignored + } + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docbm.cxx b/sw/source/core/doc/docbm.cxx new file mode 100644 index 000000000..c76e4417c --- /dev/null +++ b/sw/source/core/doc/docbm.cxx @@ -0,0 +1,1862 @@ +/* -*- 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 namespace ::sw::mark; + +std::vector<::sw::mark::MarkBase*>::const_iterator const& +IDocumentMarkAccess::iterator::get() const +{ + return *m_pIter; +} + +IDocumentMarkAccess::iterator::iterator(std::vector<::sw::mark::MarkBase*>::const_iterator const& rIter) + : m_pIter(new std::vector<::sw::mark::MarkBase*>::const_iterator(rIter)) +{ +} + +IDocumentMarkAccess::iterator::iterator(iterator const& rOther) + : m_pIter(new std::vector<::sw::mark::MarkBase*>::const_iterator(*rOther.m_pIter)) +{ +} + +auto IDocumentMarkAccess::iterator::operator=(iterator const& rOther) -> iterator& +{ + m_pIter.reset(new std::vector<::sw::mark::MarkBase*>::const_iterator(*rOther.m_pIter)); + return *this; +} + +IDocumentMarkAccess::iterator::iterator(iterator && rOther) noexcept + : m_pIter(std::move(rOther.m_pIter)) +{ +} + +auto IDocumentMarkAccess::iterator::operator=(iterator && rOther) noexcept -> iterator& +{ + m_pIter = std::move(rOther.m_pIter); + return *this; +} + +IDocumentMarkAccess::iterator::~iterator() +{ +} + +// ARGH why does it *need* to return const& ? +::sw::mark::IMark* /*const&*/ +IDocumentMarkAccess::iterator::operator*() const +{ + return static_cast(**m_pIter); +} + +auto IDocumentMarkAccess::iterator::operator++() -> iterator& +{ + ++(*m_pIter); + return *this; +} +auto IDocumentMarkAccess::iterator::operator++(int) -> iterator +{ + iterator tmp(*this); + ++(*m_pIter); + return tmp; +} + +bool IDocumentMarkAccess::iterator::operator==(iterator const& rOther) const +{ + return *m_pIter == *rOther.m_pIter; +} + +bool IDocumentMarkAccess::iterator::operator!=(iterator const& rOther) const +{ + return *m_pIter != *rOther.m_pIter; +} + +IDocumentMarkAccess::iterator::iterator() + : m_pIter(new std::vector<::sw::mark::MarkBase*>::const_iterator()) +{ +} + +auto IDocumentMarkAccess::iterator::operator--() -> iterator& +{ + --(*m_pIter); + return *this; +} + +auto IDocumentMarkAccess::iterator::operator--(int) -> iterator +{ + iterator tmp(*this); + --(*m_pIter); + return tmp; +} + +auto IDocumentMarkAccess::iterator::operator+=(difference_type const n) -> iterator& +{ + (*m_pIter) += n; + return *this; +} + +auto IDocumentMarkAccess::iterator::operator+(difference_type const n) const -> iterator +{ + return iterator(*m_pIter + n); +} + +auto IDocumentMarkAccess::iterator::operator-=(difference_type const n) -> iterator& +{ + (*m_pIter) -= n; + return *this; +} + +auto IDocumentMarkAccess::iterator::operator-(difference_type const n) const -> iterator +{ + return iterator(*m_pIter - n); +} + +auto IDocumentMarkAccess::iterator::operator-(iterator const& rOther) const -> difference_type +{ + return *m_pIter - *rOther.m_pIter; +} + +auto IDocumentMarkAccess::iterator::operator[](difference_type const n) const -> value_type +{ + return static_cast((*m_pIter)[n]); +} + +bool IDocumentMarkAccess::iterator::operator<(iterator const& rOther) const +{ + return *m_pIter < *rOther.m_pIter; +} +bool IDocumentMarkAccess::iterator::operator>(iterator const& rOther) const +{ + return *m_pIter > *rOther.m_pIter; +} +bool IDocumentMarkAccess::iterator::operator<=(iterator const& rOther) const +{ + return *m_pIter <= *rOther.m_pIter; +} +bool IDocumentMarkAccess::iterator::operator>=(iterator const& rOther) const +{ + return *m_pIter >= *rOther.m_pIter; +} + + +namespace +{ + bool lcl_GreaterThan( const SwPosition& rPos, const SwNodeIndex& rNdIdx, const SwIndex* pIdx ) + { + return pIdx != nullptr + ? ( rPos.nNode > rNdIdx + || ( rPos.nNode == rNdIdx + && rPos.nContent >= pIdx->GetIndex() ) ) + : rPos.nNode >= rNdIdx; + } + + bool lcl_Lower( const SwPosition& rPos, const SwNodeIndex& rNdIdx, const SwIndex* pIdx ) + { + return rPos.nNode < rNdIdx + || ( pIdx != nullptr + && rPos.nNode == rNdIdx + && rPos.nContent < pIdx->GetIndex() ); + } + + bool lcl_MarkOrderingByStart(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + auto const& rFirstStart(pFirst->GetMarkStart()); + auto const& rSecondStart(pSecond->GetMarkStart()); + if (rFirstStart.nNode != rSecondStart.nNode) + { + return rFirstStart.nNode < rSecondStart.nNode; + } + const sal_Int32 nFirstContent = rFirstStart.nContent.GetIndex(); + const sal_Int32 nSecondContent = rSecondStart.nContent.GetIndex(); + if (nFirstContent != 0 || nSecondContent != 0) + { + return nFirstContent < nSecondContent; + } + auto *const pCRFirst (dynamic_cast<::sw::mark::CrossRefBookmark const*>(pFirst)); + auto *const pCRSecond(dynamic_cast<::sw::mark::CrossRefBookmark const*>(pSecond)); + if ((pCRFirst == nullptr) == (pCRSecond == nullptr)) + { + return false; // equal + } + return pCRFirst != nullptr; // cross-ref sorts *before* + } + + bool lcl_MarkOrderingByEnd(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + return pFirst->GetMarkEnd() < pSecond->GetMarkEnd(); + } + + void lcl_InsertMarkSorted(MarkManager::container_t& io_vMarks, + ::sw::mark::MarkBase *const pMark) + { + io_vMarks.insert( + lower_bound( + io_vMarks.begin(), + io_vMarks.end(), + pMark, + &lcl_MarkOrderingByStart), + pMark); + } + + std::unique_ptr lcl_PositionFromContentNode( + SwContentNode * const pContentNode, + const bool bAtEnd) + { + std::unique_ptr pResult(new SwPosition(*pContentNode)); + pResult->nContent.Assign(pContentNode, bAtEnd ? pContentNode->Len() : 0); + return pResult; + } + + // return a position at the begin of rEnd, if it is a ContentNode + // else set it to the begin of the Node after rEnd, if there is one + // else set it to the end of the node before rStt + // else set it to the ContentNode of the Pos outside the Range + std::unique_ptr lcl_FindExpelPosition( + const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + const SwPosition& rOtherPosition) + { + SwContentNode * pNode = rEnd.GetNode().GetContentNode(); + bool bPosAtEndOfNode = false; + if ( pNode == nullptr) + { + SwNodeIndex aEnd = rEnd; + pNode = rEnd.GetNodes().GoNext( &aEnd ); + bPosAtEndOfNode = false; + } + if ( pNode == nullptr ) + { + SwNodeIndex aStt = rStt; + pNode = SwNodes::GoPrevious(&aStt); + bPosAtEndOfNode = true; + } + if ( pNode != nullptr ) + { + return lcl_PositionFromContentNode( pNode, bPosAtEndOfNode ); + } + + return std::make_unique(rOtherPosition); + } + + struct CompareIMarkStartsBefore + { + bool operator()(SwPosition const& rPos, + const sw::mark::IMark* pMark) + { + return rPos < pMark->GetMarkStart(); + } + bool operator()(const sw::mark::IMark* pMark, + SwPosition const& rPos) + { + return pMark->GetMarkStart() < rPos; + } + }; + + // Apple llvm-g++ 4.2.1 with _GLIBCXX_DEBUG won't eat boost::bind for this + // Neither will MSVC 2008 with _DEBUG + struct CompareIMarkStartsAfter + { + bool operator()(SwPosition const& rPos, + const sw::mark::IMark* pMark) + { + return pMark->GetMarkStart() > rPos; + } + }; + + + IMark* lcl_getMarkAfter(const MarkManager::container_t& rMarks, const SwPosition& rPos) + { + auto const pMarkAfter = upper_bound( + rMarks.begin(), + rMarks.end(), + rPos, + CompareIMarkStartsAfter()); + if(pMarkAfter == rMarks.end()) + return nullptr; + return *pMarkAfter; + }; + + IMark* lcl_getMarkBefore(const MarkManager::container_t& rMarks, const SwPosition& rPos) + { + // candidates from which to choose the mark before + MarkManager::container_t vCandidates; + // no need to consider marks starting after rPos + auto const pCandidatesEnd = upper_bound( + rMarks.begin(), + rMarks.end(), + rPos, + CompareIMarkStartsAfter()); + vCandidates.reserve(pCandidatesEnd - rMarks.begin()); + // only marks ending before are candidates + remove_copy_if( + rMarks.begin(), + pCandidatesEnd, + back_inserter(vCandidates), + [&rPos] (const ::sw::mark::MarkBase *const pMark) { return !(pMark->GetMarkEnd() < rPos); } ); + // no candidate left => we are in front of the first mark or there are none + if(vCandidates.empty()) return nullptr; + // return the highest (last) candidate using mark end ordering + return *max_element(vCandidates.begin(), vCandidates.end(), &lcl_MarkOrderingByEnd); + } + + bool lcl_FixCorrectedMark( + const bool bChangedPos, + const bool bChangedOPos, + MarkBase* io_pMark ) + { + if ( IDocumentMarkAccess::GetType(*io_pMark) == IDocumentMarkAccess::MarkType::ANNOTATIONMARK ) + { + // annotation marks are allowed to span a table cell range. + // but trigger sorting to be save + return true; + } + + if ( ( bChangedPos || bChangedOPos ) + && io_pMark->IsExpanded() + && io_pMark->GetOtherMarkPos().nNode.GetNode().FindTableBoxStartNode() != + io_pMark->GetMarkPos().nNode.GetNode().FindTableBoxStartNode() ) + { + if ( !bChangedOPos ) + { + io_pMark->SetMarkPos( io_pMark->GetOtherMarkPos() ); + } + io_pMark->ClearOtherMarkPos(); + DdeBookmark * const pDdeBkmk = dynamic_cast< DdeBookmark*>(io_pMark); + if ( pDdeBkmk != nullptr + && pDdeBkmk->IsServer() ) + { + pDdeBkmk->SetRefObject(nullptr); + } + return true; + } + return false; + } + + bool lcl_MarkEqualByStart(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + return !lcl_MarkOrderingByStart(pFirst, pSecond) && + !lcl_MarkOrderingByStart(pSecond, pFirst); + } + + MarkManager::container_t::const_iterator lcl_FindMark( + MarkManager::container_t& rMarks, + const ::sw::mark::MarkBase *const pMarkToFind) + { + auto ppCurrentMark = lower_bound( + rMarks.begin(), rMarks.end(), + pMarkToFind, &lcl_MarkOrderingByStart); + // since there are usually not too many marks on the same start + // position, we are not doing a bisect search for the upper bound + // but instead start to iterate from pMarkLow directly + while (ppCurrentMark != rMarks.end() && lcl_MarkEqualByStart(*ppCurrentMark, pMarkToFind)) + { + if(*ppCurrentMark == pMarkToFind) + { + return MarkManager::container_t::const_iterator(std::move(ppCurrentMark)); + } + ++ppCurrentMark; + } + // reached a mark starting on a later start pos or the end of the + // vector => not found + return rMarks.end(); + }; + + MarkManager::container_t::const_iterator lcl_FindMarkAtPos( + MarkManager::container_t& rMarks, + const SwPosition& rPos, + const IDocumentMarkAccess::MarkType eType) + { + for (auto ppCurrentMark = lower_bound( + rMarks.begin(), rMarks.end(), + rPos, + CompareIMarkStartsBefore()); + ppCurrentMark != rMarks.end(); + ++ppCurrentMark) + { + // Once we reach a mark starting after the target pos + // we do not need to continue + if((*ppCurrentMark)->GetMarkStart() > rPos) + break; + if(IDocumentMarkAccess::GetType(**ppCurrentMark) == eType) + { + return MarkManager::container_t::const_iterator(std::move(ppCurrentMark)); + } + } + // reached a mark starting on a later start pos or the end of the + // vector => not found + return rMarks.end(); + }; + + MarkManager::container_t::const_iterator lcl_FindMarkByName( + const OUString& rName, + const MarkManager::container_t::const_iterator& ppMarksBegin, + const MarkManager::container_t::const_iterator& ppMarksEnd) + { + return find_if( + ppMarksBegin, + ppMarksEnd, + [&rName] (::sw::mark::MarkBase const*const pMark) { return pMark->GetName() == rName; } ); + } + + void lcl_DebugMarks(MarkManager::container_t const& rMarks) + { +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("sw.core", rMarks.size() << " Marks"); + for (auto ppMark = rMarks.begin(); + ppMark != rMarks.end(); + ++ppMark) + { + IMark* pMark = *ppMark; + const SwPosition* const pStPos = &pMark->GetMarkStart(); + const SwPosition* const pEndPos = &pMark->GetMarkEnd(); + SAL_INFO("sw.core", + pStPos->nNode.GetIndex() << "," << + pStPos->nContent.GetIndex() << " " << + pEndPos->nNode.GetIndex() << "," << + pEndPos->nContent.GetIndex() << " " << + typeid(*pMark).name() << " " << + pMark->GetName()); + } +#else + (void) rMarks; +#endif + assert(std::is_sorted(rMarks.begin(), rMarks.end(), lcl_MarkOrderingByStart)); + }; +} + +IDocumentMarkAccess::MarkType IDocumentMarkAccess::GetType(const IMark& rBkmk) +{ + const std::type_info* const pMarkTypeInfo = &typeid(rBkmk); + // not using dynamic_cast<> here for performance + if(*pMarkTypeInfo == typeid(UnoMark)) + return MarkType::UNO_BOOKMARK; + else if(*pMarkTypeInfo == typeid(DdeBookmark)) + return MarkType::DDE_BOOKMARK; + else if(*pMarkTypeInfo == typeid(Bookmark)) + return MarkType::BOOKMARK; + else if(*pMarkTypeInfo == typeid(CrossRefHeadingBookmark)) + return MarkType::CROSSREF_HEADING_BOOKMARK; + else if(*pMarkTypeInfo == typeid(CrossRefNumItemBookmark)) + return MarkType::CROSSREF_NUMITEM_BOOKMARK; + else if(*pMarkTypeInfo == typeid(AnnotationMark)) + return MarkType::ANNOTATIONMARK; + else if(*pMarkTypeInfo == typeid(TextFieldmark)) + return MarkType::TEXT_FIELDMARK; + else if(*pMarkTypeInfo == typeid(CheckboxFieldmark)) + return MarkType::CHECKBOX_FIELDMARK; + else if(*pMarkTypeInfo == typeid(DropDownFieldmark)) + return MarkType::DROPDOWN_FIELDMARK; + else if(*pMarkTypeInfo == typeid(DateFieldmark)) + return MarkType::DATE_FIELDMARK; + else if(*pMarkTypeInfo == typeid(NavigatorReminder)) + return MarkType::NAVIGATOR_REMINDER; + else + { + assert(false && "IDocumentMarkAccess::GetType(..)" + " - unknown MarkType. This needs to be fixed!"); + return MarkType::UNO_BOOKMARK; + } +} + +OUString IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix() +{ + return "__RefHeading__"; +} + +bool IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark( const SwPaM& rPaM ) +{ + return rPaM.Start()->nNode.GetNode().IsTextNode() && + rPaM.Start()->nContent.GetIndex() == 0 && + ( !rPaM.HasMark() || + ( rPaM.GetMark()->nNode == rPaM.GetPoint()->nNode && + rPaM.End()->nContent.GetIndex() == rPaM.End()->nNode.GetNode().GetTextNode()->Len() ) ); +} + +void IDocumentMarkAccess::DeleteFieldmarkCommand(::sw::mark::IFieldmark const& rMark) +{ + if (GetType(rMark) != MarkType::TEXT_FIELDMARK) + { + return; // TODO FORMDATE has no command? + } + SwPaM pam(sw::mark::FindFieldSep(rMark), rMark.GetMarkStart()); + ++pam.GetPoint()->nContent; // skip CH_TXT_ATR_FIELDSTART + pam.GetDoc()->getIDocumentContentOperations().DeleteAndJoin(pam); +} + +namespace sw::mark +{ + MarkManager::MarkManager(SwDoc& rDoc) + : m_vAllMarks() + , m_vBookmarks() + , m_vFieldmarks() + , m_vAnnotationMarks() + , m_pDoc(&rDoc) + , m_pLastActiveFieldmark(nullptr) + { } + + ::sw::mark::IMark* MarkManager::makeMark(const SwPaM& rPaM, + const OUString& rName, + const IDocumentMarkAccess::MarkType eType, + sw::mark::InsertMode const eMode, + SwPosition const*const pSepPos) + { +#if OSL_DEBUG_LEVEL > 0 + { + const SwPosition* const pPos1 = rPaM.GetPoint(); + const SwPosition* pPos2 = pPos1; + if(rPaM.HasMark()) + pPos2 = rPaM.GetMark(); + SAL_INFO("sw.core", + rName << " " << + pPos1->nNode.GetIndex() << "," << + pPos1->nContent.GetIndex() << " " << + pPos2->nNode.GetIndex() << "," << + pPos2->nContent.GetIndex()); + } +#endif + if ( (!rPaM.GetPoint()->nNode.GetNode().IsTextNode() + && (eType != MarkType::UNO_BOOKMARK + // SwXTextRange can be on table node or plain start node (FLY_AT_FLY) + || !rPaM.GetPoint()->nNode.GetNode().IsStartNode())) + || (!rPaM.GetMark()->nNode.GetNode().IsTextNode() + && (eType != MarkType::UNO_BOOKMARK + || !rPaM.GetMark()->nNode.GetNode().IsStartNode()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - refusing to create mark on non-textnode"); + return nullptr; + } + // There should only be one CrossRefBookmark per Textnode per Type + if ((eType == MarkType::CROSSREF_NUMITEM_BOOKMARK || eType == MarkType::CROSSREF_HEADING_BOOKMARK) + && (lcl_FindMarkAtPos(m_vBookmarks, *rPaM.Start(), eType) != m_vBookmarks.end())) + { // this can happen via UNO API + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - refusing to create duplicate CrossRefBookmark"); + return nullptr; + } + + if ((eType == MarkType::CHECKBOX_FIELDMARK || eType == MarkType::DROPDOWN_FIELDMARK) + && (eMode == InsertMode::New + ? *rPaM.GetPoint() != *rPaM.GetMark() + // CopyText: pam covers CH_TXT_ATR_FORMELEMENT + : (rPaM.GetPoint()->nNode != rPaM.GetMark()->nNode + || rPaM.Start()->nContent.GetIndex() + 1 != rPaM.End()->nContent.GetIndex()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on point fieldmark"); + return nullptr; + } + + if ((eType == MarkType::TEXT_FIELDMARK || eType == MarkType::DATE_FIELDMARK) + && (rPaM.GetPoint()->nNode.GetNode().StartOfSectionNode() != rPaM.GetMark()->nNode.GetNode().StartOfSectionNode() + || (pSepPos && rPaM.GetPoint()->nNode.GetNode().StartOfSectionNode() != pSepPos->nNode.GetNode().StartOfSectionNode()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on fieldmark, different nodes array sections"); + return nullptr; + } + + if ((eType == MarkType::TEXT_FIELDMARK || eType == MarkType::DATE_FIELDMARK) + // can't check for Copy - it asserts - but it's also obviously unnecessary + && eMode == InsertMode::New + && sw::mark::IsFieldmarkOverlap(rPaM)) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on fieldmark, overlaps existing fieldmark or meta-field"); + return nullptr; + } + + // create mark + std::unique_ptr<::sw::mark::MarkBase> pMark; + switch(eType) + { + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + pMark = std::make_unique(rPaM, rName); + break; + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + pMark = std::make_unique(rPaM); + break; + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + pMark = std::make_unique(rPaM); + break; + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + pMark = std::make_unique(rPaM); + break; + case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: + pMark = std::make_unique(rPaM); + break; + case IDocumentMarkAccess::MarkType::BOOKMARK: + pMark = std::make_unique(rPaM, vcl::KeyCode(), rName); + break; + case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: + pMark = std::make_unique(rPaM); + break; + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + pMark = std::make_unique(rPaM, vcl::KeyCode(), rName); + break; + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + pMark = std::make_unique(rPaM, vcl::KeyCode(), rName); + break; + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + pMark = std::make_unique(rPaM); + break; + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + pMark = std::make_unique( rPaM, rName ); + break; + } + assert(pMark && "MarkManager::makeMark(..) - Mark was not created."); + + if(pMark->GetMarkPos() != pMark->GetMarkStart()) + pMark->Swap(); + + // for performance reasons, we trust UnoMarks to have a (generated) unique name + if ( eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK ) + pMark->SetName( getUniqueMarkName( pMark->GetName() ) ); + + // insert any dummy chars before inserting into sorted vectors + pMark->InitDoc(m_pDoc, eMode, pSepPos); + + // register mark + lcl_InsertMarkSorted(m_vAllMarks, pMark.get()); + switch(eType) + { + case IDocumentMarkAccess::MarkType::BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + lcl_InsertMarkSorted(m_vBookmarks, pMark.get()); + break; + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + lcl_InsertMarkSorted(m_vFieldmarks, pMark.get()); + break; + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + lcl_InsertMarkSorted( m_vAnnotationMarks, pMark.get() ); + break; + case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: + case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + // no special array for these + break; + } + SAL_INFO("sw.core", "--- makeType ---"); + SAL_INFO("sw.core", "Marks"); + lcl_DebugMarks(m_vAllMarks); + SAL_INFO("sw.core", "Bookmarks"); + lcl_DebugMarks(m_vBookmarks); + SAL_INFO("sw.core", "Fieldmarks"); + lcl_DebugMarks(m_vFieldmarks); + + return pMark.release(); + } + + ::sw::mark::IFieldmark* MarkManager::makeFieldBookmark( + const SwPaM& rPaM, + const OUString& rName, + const OUString& rType, + SwPosition const*const pSepPos) + { + + // Disable undo, because we handle it using SwUndoInsTextFieldmark + bool bUndoIsEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + m_pDoc->GetIDocumentUndoRedo().DoUndo(false); + + sw::mark::IMark* pMark = nullptr; + if(rType == ODF_FORMDATE) + { + pMark = makeMark(rPaM, rName, + IDocumentMarkAccess::MarkType::DATE_FIELDMARK, + sw::mark::InsertMode::New, + pSepPos); + } + else + { + pMark = makeMark(rPaM, rName, + IDocumentMarkAccess::MarkType::TEXT_FIELDMARK, + sw::mark::InsertMode::New, + pSepPos); + } + sw::mark::IFieldmark* pFieldMark = dynamic_cast( pMark ); + if (pFieldMark) + pFieldMark->SetFieldname( rType ); + + if (bUndoIsEnabled) + { + m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); + if (pFieldMark) + m_pDoc->GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pFieldMark)); + } + + return pFieldMark; + } + + ::sw::mark::IFieldmark* MarkManager::makeNoTextFieldBookmark( + const SwPaM& rPaM, + const OUString& rName, + const OUString& rType) + { + // Disable undo, because we handle it using SwUndoInsNoTextFieldmark + bool bUndoIsEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + m_pDoc->GetIDocumentUndoRedo().DoUndo(false); + + bool bEnableSetModified = m_pDoc->getIDocumentState().IsEnableSetModified(); + m_pDoc->getIDocumentState().SetEnableSetModified(false); + + sw::mark::IMark* pMark = nullptr; + if(rType == ODF_FORMCHECKBOX) + { + pMark = makeMark( rPaM, rName, + IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK, + sw::mark::InsertMode::New); + } + else if(rType == ODF_FORMDROPDOWN) + { + pMark = makeMark( rPaM, rName, + IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK, + sw::mark::InsertMode::New); + } + else if(rType == ODF_FORMDATE) + { + pMark = makeMark( rPaM, rName, + IDocumentMarkAccess::MarkType::DATE_FIELDMARK, + sw::mark::InsertMode::New); + } + + sw::mark::IFieldmark* pFieldMark = dynamic_cast( pMark ); + if (pFieldMark) + pFieldMark->SetFieldname( rType ); + + if (bUndoIsEnabled) + { + m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); + if (pFieldMark) + m_pDoc->GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pFieldMark)); + } + + m_pDoc->getIDocumentState().SetEnableSetModified(bEnableSetModified); + m_pDoc->getIDocumentState().SetModified(); + + return pFieldMark; + } + + ::sw::mark::IMark* MarkManager::getMarkForTextNode( + const SwTextNode& rTextNode, + const IDocumentMarkAccess::MarkType eType ) + { + SwPosition aPos(rTextNode); + aPos.nContent.Assign(&const_cast(rTextNode), 0); + auto const ppExistingMark = lcl_FindMarkAtPos(m_vBookmarks, aPos, eType); + if(ppExistingMark != m_vBookmarks.end()) + return *ppExistingMark; + const SwPaM aPaM(aPos); + return makeMark(aPaM, OUString(), eType, sw::mark::InsertMode::New); + } + + sw::mark::IMark* MarkManager::makeAnnotationMark( + const SwPaM& rPaM, + const OUString& rName ) + { + return makeMark(rPaM, rName, IDocumentMarkAccess::MarkType::ANNOTATIONMARK, + sw::mark::InsertMode::New); + } + + void MarkManager::repositionMark( + ::sw::mark::IMark* const io_pMark, + const SwPaM& rPaM) + { + assert(io_pMark->GetMarkPos().GetDoc() == m_pDoc && + "" + " - Mark is not in my doc."); + MarkBase* const pMarkBase = dynamic_cast< MarkBase* >(io_pMark); + if (!pMarkBase) + return; + + pMarkBase->InvalidateFrames(); + + pMarkBase->SetMarkPos(*(rPaM.GetPoint())); + if(rPaM.HasMark()) + pMarkBase->SetOtherMarkPos(*(rPaM.GetMark())); + else + pMarkBase->ClearOtherMarkPos(); + + if(pMarkBase->GetMarkPos() != pMarkBase->GetMarkStart()) + pMarkBase->Swap(); + + pMarkBase->InvalidateFrames(); + + sortMarks(); + } + + bool MarkManager::renameMark( + ::sw::mark::IMark* io_pMark, + const OUString& rNewName ) + { + assert(io_pMark->GetMarkPos().GetDoc() == m_pDoc && + "" + " - Mark is not in my doc."); + if ( io_pMark->GetName() == rNewName ) + return true; + if (lcl_FindMarkByName(rNewName, m_vAllMarks.begin(), m_vAllMarks.end()) != m_vAllMarks.end()) + return false; + if (::sw::mark::MarkBase* pMarkBase = dynamic_cast< ::sw::mark::MarkBase* >(io_pMark)) + { + const OUString sOldName(pMarkBase->GetName()); + pMarkBase->SetName(rNewName); + + if (dynamic_cast< ::sw::mark::Bookmark* >(io_pMark)) + { + if (m_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + m_pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique(sOldName, rNewName, m_pDoc)); + } + m_pDoc->getIDocumentState().SetModified(); + } + } + return true; + } + + void MarkManager::correctMarksAbsolute( + const SwNodeIndex& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset) + { + const SwNode* const pOldNode = &rOldNode.GetNode(); + SwPosition aNewPos(rNewPos); + aNewPos.nContent += nOffset; + bool isSortingNeeded = false; + + for (auto ppMark = m_vAllMarks.begin(); + ppMark != m_vAllMarks.end(); + ++ppMark) + { + ::sw::mark::MarkBase *const pMark = *ppMark; + // correction of non-existent non-MarkBase instances cannot be done + assert(pMark); + // is on position ?? + bool bChangedPos = false; + if(&pMark->GetMarkPos().nNode.GetNode() == pOldNode) + { + pMark->SetMarkPos(aNewPos); + bChangedPos = true; + isSortingNeeded = true; + } + bool bChangedOPos = false; + if (pMark->IsExpanded() && + &pMark->GetOtherMarkPos().nNode.GetNode() == pOldNode) + { + // shift the OtherMark to aNewPos + pMark->SetOtherMarkPos(aNewPos); + bChangedOPos= true; + isSortingNeeded = true; + } + // illegal selection? collapse the mark and restore sorting later + isSortingNeeded |= lcl_FixCorrectedMark(bChangedPos, bChangedOPos, pMark); + } + + // restore sorting if needed + if(isSortingNeeded) + sortMarks(); + + SAL_INFO("sw.core", "correctMarksAbsolute"); + lcl_DebugMarks(m_vAllMarks); + } + + void MarkManager::correctMarksRelative(const SwNodeIndex& rOldNode, const SwPosition& rNewPos, const sal_Int32 nOffset) + { + const SwNode* const pOldNode = &rOldNode.GetNode(); + SwPosition aNewPos(rNewPos); + aNewPos.nContent += nOffset; + bool isSortingNeeded = false; + + for (auto ppMark = m_vAllMarks.begin(); + ppMark != m_vAllMarks.end(); + ++ppMark) + { + // is on position ?? + bool bChangedPos = false, bChangedOPos = false; + ::sw::mark::MarkBase* const pMark = *ppMark; + // correction of non-existent non-MarkBase instances cannot be done + assert(pMark); + if(&pMark->GetMarkPos().nNode.GetNode() == pOldNode) + { + SwPosition aNewPosRel(aNewPos); + if (dynamic_cast< ::sw::mark::CrossRefBookmark *>(pMark)) + { + // ensure that cross ref bookmark always starts at 0 + aNewPosRel.nContent = 0; // HACK for WW8 import + isSortingNeeded = true; // and sort them to be safe... + } + aNewPosRel.nContent += pMark->GetMarkPos().nContent.GetIndex(); + pMark->SetMarkPos(aNewPosRel); + bChangedPos = true; + } + if(pMark->IsExpanded() && + &pMark->GetOtherMarkPos().nNode.GetNode() == pOldNode) + { + SwPosition aNewPosRel(aNewPos); + aNewPosRel.nContent += pMark->GetOtherMarkPos().nContent.GetIndex(); + pMark->SetOtherMarkPos(aNewPosRel); + bChangedOPos = true; + } + // illegal selection? collapse the mark and restore sorting later + isSortingNeeded |= lcl_FixCorrectedMark(bChangedPos, bChangedOPos, pMark); + } + + // restore sorting if needed + if(isSortingNeeded) + sortMarks(); + + SAL_INFO("sw.core", "correctMarksRelative"); + lcl_DebugMarks(m_vAllMarks); + } + + static bool isDeleteMark( + ::sw::mark::MarkBase const*const pMark, + SwNodeIndex const& rStt, + SwNodeIndex const& rEnd, + SwIndex const*const pSttIdx, + SwIndex const*const pEndIdx, + bool & rbIsPosInRange, + bool & rbIsOtherPosInRange) + { + assert(pMark); + // navigator marks should not be moved + // TODO: Check if this might make them invalid + if (IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER) + { + return false; + } + + // on position ?? + rbIsPosInRange = lcl_GreaterThan(pMark->GetMarkPos(), rStt, pSttIdx) + && lcl_Lower(pMark->GetMarkPos(), rEnd, pEndIdx); + rbIsOtherPosInRange = pMark->IsExpanded() + && lcl_GreaterThan(pMark->GetOtherMarkPos(), rStt, pSttIdx) + && lcl_Lower(pMark->GetOtherMarkPos(), rEnd, pEndIdx); + // special case: completely in range, touching the end? + if ( pEndIdx != nullptr + && ( ( rbIsOtherPosInRange + && pMark->GetMarkPos().nNode == rEnd + && pMark->GetMarkPos().nContent == *pEndIdx ) + || ( rbIsPosInRange + && pMark->IsExpanded() + && pMark->GetOtherMarkPos().nNode == rEnd + && pMark->GetOtherMarkPos().nContent == *pEndIdx ) ) ) + { + rbIsPosInRange = true; + rbIsOtherPosInRange = true; + } + + if (rbIsPosInRange + && (rbIsOtherPosInRange + || !pMark->IsExpanded())) + { + // completely in range + + bool bDeleteMark = true; + { + switch ( IDocumentMarkAccess::GetType( *pMark ) ) + { + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + // no delete of cross-reference bookmarks, if range is inside one paragraph + bDeleteMark = rStt != rEnd; + break; + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + // no delete of UNO mark, if it is not expanded and only touches the start of the range + bDeleteMark = rbIsOtherPosInRange + || pMark->IsExpanded() + || pSttIdx == nullptr + || !( pMark->GetMarkPos().nNode == rStt + && pMark->GetMarkPos().nContent == *pSttIdx ); + break; + default: + bDeleteMark = true; + break; + } + } + return bDeleteMark; + } + return false; + } + + bool MarkManager::isBookmarkDeleted(SwPaM const& rPaM) const + { + SwPosition const& rStart(*rPaM.Start()); + SwPosition const& rEnd(*rPaM.End()); + for (auto ppMark = m_vBookmarks.begin(); + ppMark != m_vBookmarks.end(); + ++ppMark) + { + bool bIsPosInRange(false); + bool bIsOtherPosInRange(false); + bool const bDeleteMark = isDeleteMark(*ppMark, + rStart.nNode, rEnd.nNode, &rStart.nContent, &rEnd.nContent, + bIsPosInRange, bIsOtherPosInRange); + if (bDeleteMark + && IDocumentMarkAccess::GetType(**ppMark) == MarkType::BOOKMARK) + { + return true; + } + } + return false; + } + + void MarkManager::deleteMarks( + const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + std::vector* pSaveBkmk, + const SwIndex* pSttIdx, + const SwIndex* pEndIdx ) + { + std::vector vMarksToDelete; + bool bIsSortingNeeded = false; + + // boolean indicating, if at least one mark has been moved while collecting marks for deletion + bool bMarksMoved = false; + // have marks in the range been skipped instead of deleted + bool bMarksSkipDeletion = false; + + // copy all bookmarks in the move area to a vector storing all position data as offset + // reassignment is performed after the move + for (auto ppMark = m_vAllMarks.begin(); + ppMark != m_vAllMarks.end(); + ++ppMark) + { + ::sw::mark::MarkBase *const pMark = *ppMark; + bool bIsPosInRange(false); + bool bIsOtherPosInRange(false); + bool const bDeleteMark = isDeleteMark(pMark, rStt, rEnd, pSttIdx, pEndIdx, bIsPosInRange, bIsOtherPosInRange); + + if ( bIsPosInRange + && ( bIsOtherPosInRange + || !pMark->IsExpanded() ) ) + { + if ( bDeleteMark ) + { + if ( pSaveBkmk ) + { + pSaveBkmk->push_back( SaveBookmark( *pMark, rStt, pSttIdx ) ); + } + vMarksToDelete.emplace_back(ppMark); + } + else + { + bMarksSkipDeletion = true; + } + } + else if ( bIsPosInRange != bIsOtherPosInRange ) + { + // the bookmark is partially in the range + // move position of that is in the range out of it + + std::unique_ptr< SwPosition > pNewPos; + { + if ( pEndIdx != nullptr ) + { + pNewPos = std::make_unique< SwPosition >( rEnd, *pEndIdx ); + } + else + { + pNewPos = + lcl_FindExpelPosition( rStt, rEnd, bIsPosInRange ? pMark->GetOtherMarkPos() : pMark->GetMarkPos() ); + } + } + + bool bMoveMark = true; + { + switch ( IDocumentMarkAccess::GetType( *pMark ) ) + { + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + // no move of cross-reference bookmarks, if move occurs inside a certain node + bMoveMark = pMark->GetMarkPos().nNode != pNewPos->nNode; + break; + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + // no move of annotation marks, if method is called to collect deleted marks + bMoveMark = pSaveBkmk == nullptr; + break; + default: + bMoveMark = true; + break; + } + } + if ( bMoveMark ) + { + if ( bIsPosInRange ) + pMark->SetMarkPos(*pNewPos); + else + pMark->SetOtherMarkPos(*pNewPos); + bMarksMoved = true; + + // illegal selection? collapse the mark and restore sorting later + bIsSortingNeeded |= lcl_FixCorrectedMark( bIsPosInRange, bIsOtherPosInRange, pMark ); + } + } + } + + { + // fdo#61016 delay the deletion of the fieldmark characters + // to prevent that from deleting the marks on that position + // which would invalidate the iterators in vMarksToDelete + std::vector< std::unique_ptr > vDelay; + vDelay.reserve(vMarksToDelete.size()); + + // If needed, sort mark containers containing subsets of the marks + // in order to assure sorting. The sorting is critical for the + // deletion of a mark as it is searched in these container for + // deletion. + if ( !vMarksToDelete.empty() && bMarksMoved ) + { + sortSubsetMarks(); + } + // we just remembered the iterators to delete, so we do not need to search + // for the shared_ptr<> (the entry in m_vAllMarks) again + // reverse iteration, since erasing an entry invalidates iterators + // behind it (the iterators in vMarksToDelete are sorted) + for ( std::vector< const_iterator_t >::reverse_iterator pppMark = vMarksToDelete.rbegin(); + pppMark != vMarksToDelete.rend(); + ++pppMark ) + { + vDelay.push_back(deleteMark(*pppMark)); + } + } // scope to kill vDelay + + // also need to sort if both marks were moved and not-deleted because + // the not-deleted marks could be in wrong order vs. the moved ones + if (bIsSortingNeeded || (bMarksMoved && bMarksSkipDeletion)) + { + sortMarks(); + } + + SAL_INFO("sw.core", "deleteMarks"); + lcl_DebugMarks(m_vAllMarks); + } + + namespace { + + struct LazyFieldmarkDeleter : public IDocumentMarkAccess::ILazyDeleter + { + std::unique_ptr m_pFieldmark; + SwDoc * m_pDoc; + LazyFieldmarkDeleter(Fieldmark* pMark, SwDoc *const pDoc) + : m_pFieldmark(pMark), m_pDoc(pDoc) + { + assert(m_pFieldmark); + } + virtual ~LazyFieldmarkDeleter() override + { + // note: because of the call chain from SwUndoDelete, the field + // command *cannot* be deleted here as it would create a separate + // SwUndoDelete that's interleaved with the SwHistory of the outer + // one - only delete the CH_TXT_ATR_FIELD*! + m_pFieldmark->ReleaseDoc(m_pDoc); + } + }; + + } + + std::unique_ptr + MarkManager::deleteMark(const const_iterator_t& ppMark) + { + std::unique_ptr ret; + if (ppMark.get() == m_vAllMarks.end()) + return ret; + IMark* pMark = *ppMark; + + switch(IDocumentMarkAccess::GetType(*pMark)) + { + case IDocumentMarkAccess::MarkType::BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + { + auto const ppBookmark = lcl_FindMark(m_vBookmarks, *ppMark.get()); + if ( ppBookmark != m_vBookmarks.end() ) + { + m_vBookmarks.erase(ppBookmark); + } + else + { + assert(false && + " - Bookmark not found in Bookmark container."); + } + } + break; + + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + { + auto const ppFieldmark = lcl_FindMark(m_vFieldmarks, *ppMark.get()); + if ( ppFieldmark != m_vFieldmarks.end() ) + { + if(m_pLastActiveFieldmark == *ppFieldmark) + ClearFieldActivation(); + + m_vFieldmarks.erase(ppFieldmark); + ret.reset(new LazyFieldmarkDeleter(dynamic_cast(pMark), m_pDoc)); + } + else + { + assert(false && + " - Fieldmark not found in Fieldmark container."); + } + } + break; + + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + { + auto const ppAnnotationMark = lcl_FindMark(m_vAnnotationMarks, *ppMark.get()); + assert(ppAnnotationMark != m_vAnnotationMarks.end() && + " - Annotation Mark not found in Annotation Mark container."); + m_vAnnotationMarks.erase(ppAnnotationMark); + } + break; + + case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: + case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + // no special marks container + break; + } + DdeBookmark* const pDdeBookmark = dynamic_cast(pMark); + if (pDdeBookmark) + pDdeBookmark->DeregisterFromDoc(m_pDoc); + //Effective STL Item 27, get a non-const iterator aI at the same + //position as const iterator ppMark was + auto aI = m_vAllMarks.begin(); + std::advance(aI, std::distance(aI, ppMark.get())); + + m_vAllMarks.erase(aI); + // If we don't have a lazy deleter + if (!ret) + // delete after we remove from the list, because the destructor can + // recursively call into this method. + delete pMark; + return ret; + } + + void MarkManager::deleteMark(const IMark* const pMark) + { + assert(pMark->GetMarkPos().GetDoc() == m_pDoc && + "" + " - Mark is not in my doc."); + // finds the last Mark that is starting before pMark + // (pMarkLow < pMark) + auto [it, endIt] = equal_range( + m_vAllMarks.begin(), + m_vAllMarks.end(), + pMark->GetMarkStart(), + CompareIMarkStartsBefore()); + for ( ; it != endIt; ++it) + if (*it == pMark) + { + deleteMark(iterator(it)); + break; + } + } + + void MarkManager::clearAllMarks() + { + ClearFieldActivation(); + m_vFieldmarks.clear(); + m_vBookmarks.clear(); + m_vAnnotationMarks.clear(); + for (const auto & p : m_vAllMarks) + delete p; + m_vAllMarks.clear(); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::findMark(const OUString& rName) const + { + auto const ret = lcl_FindMarkByName(rName, m_vAllMarks.begin(), m_vAllMarks.end()); + return IDocumentMarkAccess::iterator(ret); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::findBookmark(const OUString& rName) const + { + auto const ret = lcl_FindMarkByName(rName, m_vBookmarks.begin(), m_vBookmarks.end()); + return IDocumentMarkAccess::iterator(ret); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAllMarksBegin() const + { return m_vAllMarks.begin(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAllMarksEnd() const + { return m_vAllMarks.end(); } + + sal_Int32 MarkManager::getAllMarksCount() const + { return m_vAllMarks.size(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getBookmarksBegin() const + { return m_vBookmarks.begin(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getBookmarksEnd() const + { return m_vBookmarks.end(); } + + sal_Int32 MarkManager::getBookmarksCount() const + { return m_vBookmarks.size(); } + + // finds the first that is starting after + IDocumentMarkAccess::const_iterator_t MarkManager::findFirstBookmarkStartsAfter(const SwPosition& rPos) const + { + return std::upper_bound( + m_vBookmarks.begin(), + m_vBookmarks.end(), + rPos, + CompareIMarkStartsAfter()); + } + + IFieldmark* MarkManager::getFieldmarkAt(const SwPosition& rPos) const + { + auto const pFieldmark = find_if( + m_vFieldmarks.begin(), + m_vFieldmarks.end(), + [&rPos] (::sw::mark::MarkBase const*const pMark) { + return pMark->GetMarkStart() == rPos + // end position includes the CH_TXT_ATR_FIELDEND + || (pMark->GetMarkEnd().nContent.GetIndex() == rPos.nContent.GetIndex() + 1 + && pMark->GetMarkEnd().nNode == rPos.nNode); + } ); + return (pFieldmark == m_vFieldmarks.end()) + ? nullptr + : dynamic_cast(*pFieldmark); + } + + IFieldmark* MarkManager::getFieldmarkFor(const SwPosition& rPos) const + { + auto itFieldmark = find_if( + m_vFieldmarks.begin(), + m_vFieldmarks.end(), + [&rPos] (const ::sw::mark::MarkBase *const pMark) { return pMark->IsCoveringPosition(rPos); } ); + if (itFieldmark == m_vFieldmarks.end()) + return nullptr; + auto pFieldmark(*itFieldmark); + for ( ; itFieldmark != m_vFieldmarks.end() + && (**itFieldmark).IsCoveringPosition(rPos); ++itFieldmark) + { // find the innermost fieldmark + if (pFieldmark->GetMarkStart() < (**itFieldmark).GetMarkStart() + || (**itFieldmark).GetMarkEnd() < pFieldmark->GetMarkEnd()) + { + pFieldmark = *itFieldmark; + } + } + return dynamic_cast(pFieldmark); + } + + void MarkManager::deleteFieldmarkAt(const SwPosition& rPos) + { + auto const pFieldmark = dynamic_cast(getFieldmarkAt(rPos)); + if (!pFieldmark) + return; + + deleteMark(lcl_FindMark(m_vAllMarks, pFieldmark)); + } + + ::sw::mark::IFieldmark* MarkManager::changeFormFieldmarkType(::sw::mark::IFieldmark* pFieldmark, const OUString& rNewType) + { + bool bActualChange = false; + if(rNewType == ODF_FORMDROPDOWN) + { + if (!dynamic_cast<::sw::mark::DropDownFieldmark*>(pFieldmark)) + bActualChange = true; + if (!dynamic_cast<::sw::mark::CheckboxFieldmark*>(pFieldmark)) // only allowed converting between checkbox <-> dropdown + return nullptr; + } + else if(rNewType == ODF_FORMCHECKBOX) + { + if (!dynamic_cast<::sw::mark::CheckboxFieldmark*>(pFieldmark)) + bActualChange = true; + if (!dynamic_cast<::sw::mark::DropDownFieldmark*>(pFieldmark)) // only allowed converting between checkbox <-> dropdown + return nullptr; + } + else if(rNewType == ODF_FORMDATE) + { + if (!dynamic_cast<::sw::mark::DateFieldmark*>(pFieldmark)) + bActualChange = true; + if (!dynamic_cast<::sw::mark::TextFieldmark*>(pFieldmark)) // only allowed converting between date field <-> text field + return nullptr; + } + + if (!bActualChange) + return nullptr; + + // Store attributes needed to create the new fieldmark + OUString sName = pFieldmark->GetName(); + SwPaM aPaM(pFieldmark->GetMarkPos()); + + // Remove the old fieldmark and create a new one with the new type + if(aPaM.GetPoint()->nContent > 0 && (rNewType == ODF_FORMDROPDOWN || rNewType == ODF_FORMCHECKBOX)) + { + --aPaM.GetPoint()->nContent; + SwPosition aNewPos (aPaM.GetPoint()->nNode, aPaM.GetPoint()->nContent); + deleteFieldmarkAt(aNewPos); + return makeNoTextFieldBookmark(aPaM, sName, rNewType); + } + else if(rNewType == ODF_FORMDATE) + { + SwPosition aPos (aPaM.GetPoint()->nNode, aPaM.GetPoint()->nContent); + SwPaM aNewPaM(pFieldmark->GetMarkStart(), pFieldmark->GetMarkEnd()); + deleteFieldmarkAt(aPos); + // HACK: hard-code the separator position here at the start because + // writerfilter put it in the wrong place (at the end) on attach() + SwPosition const sepPos(*aNewPaM.Start()); + return makeFieldBookmark(aNewPaM, sName, rNewType, &sepPos); + } + return nullptr; + } + + void MarkManager::NotifyCursorUpdate(const SwCursorShell& rCursorShell) + { + SwView* pSwView = dynamic_cast(rCursorShell.GetSfxViewShell()); + if(!pSwView) + return; + + SwEditWin& rEditWin = pSwView->GetEditWin(); + SwPosition aPos(*rCursorShell.GetCursor()->GetPoint()); + IFieldmark* pFieldBM = getFieldmarkFor(aPos); + FieldmarkWithDropDownButton* pNewActiveFieldmark = nullptr; + if ((!pFieldBM || (pFieldBM->GetFieldname() != ODF_FORMDROPDOWN && pFieldBM->GetFieldname() != ODF_FORMDATE)) + && aPos.nContent.GetIndex() > 0 ) + { + --aPos.nContent; + pFieldBM = getFieldmarkFor(aPos); + } + + if ( pFieldBM && (pFieldBM->GetFieldname() == ODF_FORMDROPDOWN || + pFieldBM->GetFieldname() == ODF_FORMDATE)) + { + if (m_pLastActiveFieldmark != pFieldBM) + { + FieldmarkWithDropDownButton& rFormField = dynamic_cast(*pFieldBM); + pNewActiveFieldmark = &rFormField; + } + else + { + pNewActiveFieldmark = m_pLastActiveFieldmark; + } + } + + if(pNewActiveFieldmark != m_pLastActiveFieldmark) + { + ClearFieldActivation(); + m_pLastActiveFieldmark = pNewActiveFieldmark; + if(pNewActiveFieldmark) + pNewActiveFieldmark->ShowButton(&rEditWin); + } + } + + void MarkManager::ClearFieldActivation() + { + if(m_pLastActiveFieldmark) + m_pLastActiveFieldmark->RemoveButton(); + + m_pLastActiveFieldmark = nullptr; + } + + IFieldmark* MarkManager::getDropDownFor(const SwPosition& rPos) const + { + IFieldmark *pMark = getFieldmarkAt(rPos); + if (!pMark || pMark->GetFieldname() != ODF_FORMDROPDOWN) + return nullptr; + return pMark; + } + + std::vector MarkManager::getDropDownsFor(const SwPaM &rPaM) const + { + std::vector aRet; + + for (auto aI = m_vFieldmarks.begin(), + aEnd = m_vFieldmarks.end(); aI != aEnd; ++aI) + { + ::sw::mark::IMark* pI = *aI; + const SwPosition &rStart = pI->GetMarkPos(); + if (!rPaM.ContainsPosition(rStart)) + continue; + + IFieldmark *pMark = dynamic_cast(pI); + if (!pMark || pMark->GetFieldname() != ODF_FORMDROPDOWN) + continue; + + aRet.push_back(pMark); + } + + return aRet; + } + + IFieldmark* MarkManager::getFieldmarkAfter(const SwPosition& rPos) const + { return dynamic_cast(lcl_getMarkAfter(m_vFieldmarks, rPos)); } + + IFieldmark* MarkManager::getFieldmarkBefore(const SwPosition& rPos) const + { return dynamic_cast(lcl_getMarkBefore(m_vFieldmarks, rPos)); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAnnotationMarksBegin() const + { + return m_vAnnotationMarks.begin(); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAnnotationMarksEnd() const + { + return m_vAnnotationMarks.end(); + } + + sal_Int32 MarkManager::getAnnotationMarksCount() const + { + return m_vAnnotationMarks.size(); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::findAnnotationMark( const OUString& rName ) const + { + auto const ret = lcl_FindMarkByName( rName, m_vAnnotationMarks.begin(), m_vAnnotationMarks.end() ); + return IDocumentMarkAccess::iterator(ret); + } + + IMark* MarkManager::getAnnotationMarkFor(const SwPosition& rPos) const + { + auto const pAnnotationMark = find_if( + m_vAnnotationMarks.begin(), + m_vAnnotationMarks.end(), + [&rPos] (const ::sw::mark::MarkBase *const pMark) { return pMark->IsCoveringPosition(rPos); } ); + if (pAnnotationMark == m_vAnnotationMarks.end()) + return nullptr; + return *pAnnotationMark; + } + + // finds the first that is starting after + IDocumentMarkAccess::const_iterator_t MarkManager::findFirstAnnotationStartsAfter(const SwPosition& rPos) const + { + return std::upper_bound( + m_vAnnotationMarks.begin(), + m_vAnnotationMarks.end(), + rPos, + CompareIMarkStartsAfter()); + } + + OUString MarkManager::getUniqueMarkName(const OUString& rName) const + { + OSL_ENSURE(rName.getLength(), + " - a name should be proposed"); + if( m_pDoc->IsInMailMerge()) + { + OUString newName = rName + "MailMergeMark" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( m_vAllMarks.size() + 1 ); + return newName; + } + + if (lcl_FindMarkByName(rName, m_vAllMarks.begin(), m_vAllMarks.end()) == m_vAllMarks.end()) + { + return rName; + } + OUString sTmp; + + // try the name "XXX" (where XXX is a number starting from 1) unless there is + // an unused name. Due to performance-reasons (especially in mailmerge-scenarios) there + // is a map m_aMarkBasenameMapUniqueOffset which holds the next possible offset (XXX) for + // rName (so there is no need to test for nCnt-values smaller than the offset). + sal_Int32 nCnt = 1; + MarkBasenameMapUniqueOffset_t::const_iterator aIter = m_aMarkBasenameMapUniqueOffset.find(rName); + if(aIter != m_aMarkBasenameMapUniqueOffset.end()) nCnt = aIter->second; + while(nCnt < SAL_MAX_INT32) + { + sTmp = rName + OUString::number(nCnt); + nCnt++; + if (lcl_FindMarkByName(sTmp, m_vAllMarks.begin(), m_vAllMarks.end()) == m_vAllMarks.end()) + { + break; + } + } + m_aMarkBasenameMapUniqueOffset[rName] = nCnt; + + return sTmp; + } + + void MarkManager::assureSortedMarkContainers() const + { + const_cast< MarkManager* >(this)->sortMarks(); + } + + void MarkManager::sortSubsetMarks() + { + sort(m_vBookmarks.begin(), m_vBookmarks.end(), &lcl_MarkOrderingByStart); + sort(m_vFieldmarks.begin(), m_vFieldmarks.end(), &lcl_MarkOrderingByStart); + sort(m_vAnnotationMarks.begin(), m_vAnnotationMarks.end(), &lcl_MarkOrderingByStart); + } + + void MarkManager::sortMarks() + { + sort(m_vAllMarks.begin(), m_vAllMarks.end(), &lcl_MarkOrderingByStart); + sortSubsetMarks(); + } + +void MarkManager::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + struct + { + const char* pName; + const container_t* pContainer; + } aContainers[] = + { + // UNO marks are only part of all marks. + {"allmarks", &m_vAllMarks}, + {"bookmarks", &m_vBookmarks}, + {"fieldmarks", &m_vFieldmarks}, + {"annotationmarks", &m_vAnnotationMarks} + }; + + xmlTextWriterStartElement(pWriter, BAD_CAST("MarkManager")); + for (const auto & rContainer : aContainers) + { + if (!rContainer.pContainer->empty()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST(rContainer.pName)); + for (auto it = rContainer.pContainer->begin(); it != rContainer.pContainer->end(); ++it) + (*it)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + } + xmlTextWriterEndElement(pWriter); +} + +} // namespace ::sw::mark + +namespace +{ + bool lcl_Greater( const SwPosition& rPos, const SwNodeIndex& rNdIdx, const SwIndex* pIdx ) + { + return rPos.nNode > rNdIdx || ( pIdx && rPos.nNode == rNdIdx && rPos.nContent > pIdx->GetIndex() ); + } +} + +// IDocumentMarkAccess for SwDoc +IDocumentMarkAccess* SwDoc::getIDocumentMarkAccess() + { return static_cast< IDocumentMarkAccess* >(mpMarkManager.get()); } + +const IDocumentMarkAccess* SwDoc::getIDocumentMarkAccess() const + { return static_cast< IDocumentMarkAccess* >(mpMarkManager.get()); } + +SaveBookmark::SaveBookmark( + const IMark& rBkmk, + const SwNodeIndex & rMvPos, + const SwIndex* pIdx) + : m_aName(rBkmk.GetName()) + , m_aShortName() + , m_aCode() + , m_eOrigBkmType(IDocumentMarkAccess::GetType(rBkmk)) +{ + const IBookmark* const pBookmark = dynamic_cast< const IBookmark* >(&rBkmk); + if(pBookmark) + { + m_aShortName = pBookmark->GetShortName(); + m_aCode = pBookmark->GetKeyCode(); + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pBookmark)); + if (pMetadatable) + { + m_pMetadataUndo = pMetadatable->CreateUndo(); + } + } + m_nNode1 = rBkmk.GetMarkPos().nNode.GetIndex(); + m_nContent1 = rBkmk.GetMarkPos().nContent.GetIndex(); + + m_nNode1 -= rMvPos.GetIndex(); + if(pIdx && !m_nNode1) + m_nContent1 -= pIdx->GetIndex(); + + if(rBkmk.IsExpanded()) + { + m_nNode2 = rBkmk.GetOtherMarkPos().nNode.GetIndex(); + m_nContent2 = rBkmk.GetOtherMarkPos().nContent.GetIndex(); + + m_nNode2 -= rMvPos.GetIndex(); + if(pIdx && !m_nNode2) + m_nContent2 -= pIdx->GetIndex(); + } + else + { + m_nNode2 = ULONG_MAX; + m_nContent2 = -1; + } +} + +void SaveBookmark::SetInDoc( + SwDoc* pDoc, + const SwNodeIndex& rNewPos, + const SwIndex* pIdx) +{ + SwPaM aPam(rNewPos.GetNode()); + if(pIdx) + aPam.GetPoint()->nContent = *pIdx; + + if(ULONG_MAX != m_nNode2) + { + aPam.SetMark(); + + aPam.GetMark()->nNode += m_nNode2; + if(pIdx && !m_nNode2) + aPam.GetMark()->nContent += m_nContent2; + else + aPam.GetMark()->nContent.Assign(aPam.GetContentNode(false), m_nContent2); + } + + aPam.GetPoint()->nNode += m_nNode1; + + if(pIdx && !m_nNode1) + aPam.GetPoint()->nContent += m_nContent1; + else + aPam.GetPoint()->nContent.Assign(aPam.GetContentNode(), m_nContent1); + + if(!aPam.HasMark() + || CheckNodesRange(aPam.GetPoint()->nNode, aPam.GetMark()->nNode, true)) + { + ::sw::mark::IBookmark* const pBookmark = dynamic_cast<::sw::mark::IBookmark*>( + pDoc->getIDocumentMarkAccess()->makeMark(aPam, m_aName, + m_eOrigBkmType, sw::mark::InsertMode::New)); + if(pBookmark) + { + pBookmark->SetKeyCode(m_aCode); + pBookmark->SetShortName(m_aShortName); + if (m_pMetadataUndo) + { + ::sfx2::Metadatable * const pMeta( + dynamic_cast< ::sfx2::Metadatable* >(pBookmark)); + assert(pMeta && "metadata undo, but not metadatable?"); + if (pMeta) + { + pMeta->RestoreMetadata(m_pMetadataUndo); + } + } + } + } +} + +// DelBookmarks + +void DelBookmarks( + const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + std::vector * pSaveBkmk, + const SwIndex* pSttIdx, + const SwIndex* pEndIdx) +{ + // illegal range ?? + if(rStt.GetIndex() > rEnd.GetIndex() + || (rStt == rEnd && (!pSttIdx || !pEndIdx || pSttIdx->GetIndex() >= pEndIdx->GetIndex()))) + return; + SwDoc* const pDoc = rStt.GetNode().GetDoc(); + + pDoc->getIDocumentMarkAccess()->deleteMarks(rStt, rEnd, pSaveBkmk, pSttIdx, pEndIdx); + + // Copy all Redlines which are in the move area into an array + // which holds all position information as offset. + // Assignment happens after moving. + SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + // Is at position? + SwPosition *const pRStt = pRedl->Start(); + SwPosition *const pREnd = pRedl->End(); + + if( lcl_Greater( *pRStt, rStt, pSttIdx ) && lcl_Lower( *pRStt, rEnd, pEndIdx )) + { + pRStt->nNode = rEnd; + if( pEndIdx ) + pRStt->nContent = *pEndIdx; + else + { + bool bStt = true; + SwContentNode* pCNd = pRStt->nNode.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &pRStt->nNode ); + if (!pCNd) + { + bStt = false; + pRStt->nNode = rStt; + if( nullptr == ( pCNd = SwNodes::GoPrevious( &pRStt->nNode )) ) + { + pRStt->nNode = pREnd->nNode; + pCNd = pRStt->nNode.GetNode().GetContentNode(); + } + } + pRStt->nContent.Assign( pCNd, bStt ? 0 : pCNd->Len() ); + } + } + if( lcl_Greater( *pREnd, rStt, pSttIdx ) && lcl_Lower( *pREnd, rEnd, pEndIdx )) + { + pREnd->nNode = rStt; + if( pSttIdx ) + pREnd->nContent = *pSttIdx; + else + { + bool bStt = false; + SwContentNode* pCNd = pREnd->nNode.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = SwNodes::GoPrevious( &pREnd->nNode ); + if( !pCNd ) + { + bStt = true; + pREnd->nNode = rEnd; + if( nullptr == ( pCNd = pDoc->GetNodes().GoNext( &pREnd->nNode )) ) + { + pREnd->nNode = pRStt->nNode; + pCNd = pREnd->nNode.GetNode().GetContentNode(); + } + } + pREnd->nContent.Assign( pCNd, bStt ? 0 : pCNd->Len() ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docchart.cxx b/sw/source/core/doc/docchart.cxx new file mode 100644 index 000000000..4acfb5119 --- /dev/null +++ b/sw/source/core/doc/docchart.cxx @@ -0,0 +1,179 @@ +/* -*- 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 SwTable::UpdateCharts() const +{ + GetFrameFormat()->GetDoc()->UpdateCharts( GetFrameFormat()->GetName() ); +} + +bool SwTable::IsTableComplexForChart( const OUString& rSelection ) const +{ + const SwTableBox* pSttBox, *pEndBox; + if( 2 < rSelection.getLength() ) + { + const sal_Int32 nSeparator {rSelection.indexOf( ':' )}; + OSL_ENSURE( -1 != nSeparator, "no valid selection" ); + + // Remove brackets at the beginning and from the end + const sal_Int32 nOffset {'<' == rSelection[0] ? 1 : 0}; + const sal_Int32 nLength {'>' == rSelection[ rSelection.getLength()-1 ] + ? rSelection.getLength()-1 : rSelection.getLength()}; + + pSttBox = GetTableBox(rSelection.copy( nOffset, nSeparator - nOffset )); + pEndBox = GetTableBox(rSelection.copy( nSeparator+1, nLength - (nSeparator+1) )); + } + else + { + const SwTableLines* pLns = &GetTabLines(); + pSttBox = (*pLns)[ 0 ]->GetTabBoxes().front(); + while( !pSttBox->GetSttNd() ) + // Until the Content Box! + pSttBox = pSttBox->GetTabLines().front()->GetTabBoxes().front(); + + const SwTableBoxes* pBoxes = &pLns->back()->GetTabBoxes(); + pEndBox = pBoxes->back(); + while( !pEndBox->GetSttNd() ) + { + // Until the Content Box! + pLns = &pEndBox->GetTabLines(); + pBoxes = &pLns->back()->GetTabBoxes(); + pEndBox = pBoxes->back(); + } + } + + return !pSttBox || !pEndBox || !::ChkChartSel( *pSttBox->GetSttNd(), + *pEndBox->GetSttNd() ); +} + +void SwDoc::DoUpdateAllCharts() +{ + SwViewShell* pVSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pVSh ) + { + const SwFrameFormats& rTableFormats = *GetTableFrameFormats(); + for( size_t n = 0; n < rTableFormats.size(); ++n ) + { + const SwFrameFormat* pFormat = rTableFormats[ n ]; + if( SwTable* pTmpTable = SwTable::FindTable( pFormat ) ) + if( const SwTableNode* pTableNd = pTmpTable->GetTableNode() ) + if( pTableNd->GetNodes().IsDocNodes() ) + { + UpdateCharts_( *pTmpTable, *pVSh ); + } + } + } +} + +void SwDoc::UpdateCharts_( const SwTable& rTable, SwViewShell const & rVSh ) const +{ + OUString aName( rTable.GetFrameFormat()->GetName() ); + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwOLENode *pONd = aIdx.GetNode().GetOLENode(); + if( pONd && + aName == pONd->GetChartTableName() && + pONd->getLayoutFrame( rVSh.GetLayout() ) ) + { + SwChartDataProvider *pPCD = getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + pPCD->InvalidateTable( &rTable ); + // following this the framework will now take care of repainting + // the chart or it's replacement image... + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } +} + +void SwDoc::UpdateCharts( const OUString &rName ) const +{ + SwTable* pTmpTable = SwTable::FindTable( FindTableFormatByName( rName ) ); + if( pTmpTable ) + { + SwViewShell const * pVSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + + if( pVSh ) + UpdateCharts_( *pTmpTable, *pVSh ); + } +} + +void SwDoc::SetTableName( SwFrameFormat& rTableFormat, const OUString &rNewName ) +{ + const OUString aOldName( rTableFormat.GetName() ); + + bool bNameFound = rNewName.isEmpty(); + if( !bNameFound ) + { + const SwFrameFormats& rTable = *GetTableFrameFormats(); + for( size_t i = rTable.size(); i; ) + { + const SwFrameFormat* pFormat = rTable[ --i ]; + if( !pFormat->IsDefault() && + pFormat->GetName() == rNewName && IsUsed( *pFormat ) ) + { + bNameFound = true; + break; + } + } + } + + if( !bNameFound ) + rTableFormat.SetName( rNewName, true ); + else + rTableFormat.SetName( GetUniqueTableName(), true ); + + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwOLENode *pNd = aIdx.GetNode().GetOLENode(); + if( pNd && aOldName == pNd->GetChartTableName() ) + { + pNd->SetChartTableName( rNewName ); + + SwTable* pTable = SwTable::FindTable( &rTableFormat ); + SwChartDataProvider *pPCD = getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + pPCD->InvalidateTable( pTable ); + // following this the framework will now take care of repainting + // the chart or it's replacement image... + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + getIDocumentState().SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doccomp.cxx b/sw/source/core/doc/doccomp.cxx new file mode 100644 index 000000000..1fe455f50 --- /dev/null +++ b/sw/source/core/doc/doccomp.cxx @@ -0,0 +1,2698 @@ +/* -*- 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; + +namespace { + +class SwCompareLine +{ + const SwNode& m_rNode; +public: + explicit SwCompareLine( const SwNode& rNd ) : m_rNode( rNd ) {} + + sal_uLong GetHashValue() const; + bool Compare( const SwCompareLine& rLine ) const; + + static sal_uLong GetTextNodeHashValue( const SwTextNode& rNd, sal_uLong nVal ); + static bool CompareNode( const SwNode& rDstNd, const SwNode& rSrcNd ); + static bool CompareTextNd( const SwTextNode& rDstNd, + const SwTextNode& rSrcNd ); + + bool ChangesInLine( const SwCompareLine& rLine, + std::unique_ptr& rpInsRing, std::unique_ptr& rpDelRing ) const; + + const SwNode& GetNode() const { return m_rNode; } + + const SwNode& GetEndNode() const; + + // for debugging + OUString GetText() const; +}; + + +class CompareData +{ +protected: + SwDoc& m_rDoc; +private: + std::unique_ptr m_pIndex; + std::unique_ptr m_pChangedFlag; + + std::unique_ptr m_pInsertRing, m_pDelRing; + + static sal_uLong PrevIdx( const SwNode* pNd ); + static sal_uLong NextIdx( const SwNode* pNd ); + + vector< SwCompareLine* > m_aLines; + bool m_bRecordDiff; + + // Truncate beginning and end and add all others to the LinesArray + void CheckRanges( CompareData& ); + + virtual const SwNode& GetEndOfContent() = 0; + +public: + CompareData(SwDoc& rD, bool bRecordDiff) + : m_rDoc( rD ) + , m_bRecordDiff(bRecordDiff) + { + } + virtual ~CompareData(); + + // Are there differences? + bool HasDiffs( const CompareData& rData ) const; + + // Triggers the comparison and creation of two documents + void CompareLines( CompareData& rData ); + // Display the differences - calls the methods ShowInsert and ShowDelete. + // These are passed the start and end line number. + // Displaying the actually content is to be handled by the subclass! + sal_uLong ShowDiffs( const CompareData& rData ); + + void ShowInsert( sal_uLong nStt, sal_uLong nEnd ); + void ShowDelete( const CompareData& rData, sal_uLong nStt, + sal_uLong nEnd, sal_uLong nInsPos ); + void CheckForChangesInLine( const CompareData& rData, + sal_uLong nStt, sal_uLong nEnd, + sal_uLong nThisStt, sal_uLong nThisEnd ); + + // Set non-ambiguous index for a line. Same lines have the same index, even in the other CompareData! + void SetIndex( size_t nLine, size_t nIndex ); + size_t GetIndex( size_t nLine ) const + { return nLine < m_aLines.size() ? m_pIndex[ nLine ] : 0; } + + // Set/get of a line has changed + void SetChanged( size_t nLine, bool bFlag = true ); + bool GetChanged( size_t nLine ) const + { + return (m_pChangedFlag && nLine < m_aLines.size()) + && m_pChangedFlag[ nLine ]; + } + + size_t GetLineCount() const { return m_aLines.size(); } + const SwCompareLine* GetLine( size_t nLine ) const + { return m_aLines[ nLine ]; } + void InsertLine( SwCompareLine* pLine ) + { m_aLines.push_back( pLine ); } + + void SetRedlinesToDoc( bool bUseDocInfo ); +}; + +class CompareMainText : public CompareData +{ +public: + CompareMainText(SwDoc &rD, bool bRecordDiff) + : CompareData(rD, bRecordDiff) + { + } + + virtual const SwNode& GetEndOfContent() override + { + return m_rDoc.GetNodes().GetEndOfContent(); + } +}; + +class CompareFrameFormatText : public CompareData +{ + const SwNodeIndex &m_rIndex; +public: + CompareFrameFormatText(SwDoc &rD, const SwNodeIndex &rIndex) + : CompareData(rD, true/*bRecordDiff*/) + , m_rIndex(rIndex) + { + } + + virtual const SwNode& GetEndOfContent() override + { + return *m_rIndex.GetNode().EndOfSectionNode(); + } +}; + +class Hash +{ + struct HashData + { + sal_uLong nNext, nHash; + const SwCompareLine* pLine; + + HashData() + : nNext( 0 ), nHash( 0 ), pLine(nullptr) {} + }; + + std::unique_ptr m_pHashArr; + std::unique_ptr m_pDataArr; + sal_uLong m_nCount, m_nPrime; + +public: + explicit Hash( sal_uLong nSize ); + + void CalcHashValue( CompareData& rData ); + + sal_uLong GetCount() const { return m_nCount; } +}; + +class Compare +{ +public: + class MovedData + { + std::unique_ptr m_pIndex; + std::unique_ptr m_pLineNum; + sal_uLong m_nCount; + + public: + MovedData( CompareData& rData, const char* pDiscard ); + + sal_uLong GetIndex( sal_uLong n ) const { return m_pIndex[ n ]; } + sal_uLong GetLineNum( sal_uLong n ) const { return m_pLineNum[ n ]; } + sal_uLong GetCount() const { return m_nCount; } + }; + +private: + /// Look for the moved lines + class CompareSequence + { + CompareData &m_rData1, &m_rData2; + const MovedData &m_rMoved1, &m_rMoved2; + std::unique_ptr m_pMemory; + long *m_pFDiag, *m_pBDiag; + + void Compare( sal_uLong nStt1, sal_uLong nEnd1, sal_uLong nStt2, sal_uLong nEnd2 ); + sal_uLong CheckDiag( sal_uLong nStt1, sal_uLong nEnd1, + sal_uLong nStt2, sal_uLong nEnd2, sal_uLong* pCost ); + public: + CompareSequence( CompareData& rD1, CompareData& rD2, + const MovedData& rMD1, const MovedData& rMD2 ); + }; + + static void CountDifference( const CompareData& rData, sal_uLong* pCounts ); + static void SetDiscard( const CompareData& rData, + char* pDiscard, const sal_uLong* pCounts ); + static void CheckDiscard( sal_uLong nLen, char* pDiscard ); + static void ShiftBoundaries( CompareData& rData1, CompareData& rData2 ); + +public: + Compare( sal_uLong nDiff, CompareData& rData1, CompareData& rData2 ); +}; + +class ArrayComparator +{ +public: + virtual bool Compare( int nIdx1, int nIdx2 ) const = 0; + virtual int GetLen1() const = 0; + virtual int GetLen2() const = 0; + virtual ~ArrayComparator() {} +}; + +/// Consider two lines equal if similar enough (e.g. look like different +/// versions of the same paragraph) +class LineArrayComparator : public ArrayComparator +{ +private: + int m_nLen1, m_nLen2; + const CompareData &m_rData1, &m_rData2; + int m_nFirst1, m_nFirst2; + +public: + LineArrayComparator( const CompareData &rD1, const CompareData &rD2, + int nStt1, int nEnd1, int nStt2, int nEnd2 ); + + virtual bool Compare( int nIdx1, int nIdx2 ) const override; + virtual int GetLen1() const override { return m_nLen1; } + virtual int GetLen2() const override { return m_nLen2; } +}; + +class WordArrayComparator : public ArrayComparator +{ +private: + const SwTextNode *m_pTextNode1, *m_pTextNode2; + std::unique_ptr m_pPos1, m_pPos2; + int m_nCount1, m_nCount2; // number of words + + static void CalcPositions( int *pPos, const SwTextNode *pTextNd, int &nCnt ); + +public: + WordArrayComparator( const SwTextNode *pNode1, const SwTextNode *pNode2 ); + + virtual bool Compare( int nIdx1, int nIdx2 ) const override; + virtual int GetLen1() const override { return m_nCount1; } + virtual int GetLen2() const override { return m_nCount2; } + int GetCharSequence( const int *pWordLcs1, const int *pWordLcs2, + int *pSubseq1, int *pSubseq2, int nLcsLen ); +}; + +class CharArrayComparator : public ArrayComparator +{ +private: + const SwTextNode *m_pTextNode1, *m_pTextNode2; + +public: + CharArrayComparator( const SwTextNode *pNode1, const SwTextNode *pNode2 ) + : m_pTextNode1( pNode1 ), m_pTextNode2( pNode2 ) + { + } + + virtual bool Compare( int nIdx1, int nIdx2 ) const override; + virtual int GetLen1() const override { return m_pTextNode1->GetText().getLength(); } + virtual int GetLen2() const override { return m_pTextNode2->GetText().getLength(); } +}; + +/// Options set in Tools->Options->Writer->Comparison +struct CmpOptionsContainer +{ + SwCompareMode eCmpMode; + int nIgnoreLen; + bool bUseRsid; +}; + +} + +static CmpOptionsContainer CmpOptions; + +namespace { + +class CommonSubseq +{ +private: + std::unique_ptr m_pData; + +protected: + ArrayComparator &m_rComparator; + + CommonSubseq( ArrayComparator &rComparator, int nMaxSize ) + : m_rComparator( rComparator ) + { + m_pData.reset( new int[ nMaxSize ] ); + } + + int FindLCS( int *pLcs1, int *pLcs2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ); + +public: + static int IgnoreIsolatedPieces( int *pLcs1, int *pLcs2, int nLen1, int nLen2, + int nLcsLen, int nPieceLen ); +}; + +/// Use Hirschberg's algorithm to find LCS in linear space +class LgstCommonSubseq: public CommonSubseq +{ +private: + static const int CUTOFF = 1<<20; // Stop recursion at this value + + std::unique_ptr m_pL1, m_pL2; + std::unique_ptr m_pBuff1, m_pBuff2; + + void FindL( int *pL, int nStt1, int nEnd1, int nStt2, int nEnd2 ); + int HirschbergLCS( int *pLcs1, int *pLcs2, int nStt1, int nEnd1, + int nStt2, int nEnd2 ); + +public: + explicit LgstCommonSubseq( ArrayComparator &rComparator ); + + int Find( int *pSubseq1, int *pSubseq2 ); +}; + +/// Find a common subsequence in linear time +class FastCommonSubseq: private CommonSubseq +{ +private: + static const int CUTOFF = 2056; + + int FindFastCS( int *pSeq1, int *pSeq2, int nStt1, int nEnd1, + int nStt2, int nEnd2 ); + +public: + explicit FastCommonSubseq( ArrayComparator &rComparator ) + : CommonSubseq( rComparator, CUTOFF ) + { + } + + int Find( int *pSubseq1, int *pSubseq2 ) + { + return FindFastCS( pSubseq1, pSubseq2, 0, m_rComparator.GetLen1(), + 0, m_rComparator.GetLen2() ); + } +}; + +} + +CompareData::~CompareData() +{ + if( m_pDelRing ) + { + while( m_pDelRing->GetNext() != m_pDelRing.get() ) + delete m_pDelRing->GetNext(); + m_pDelRing.reset(); + } + if( m_pInsertRing ) + { + while( m_pInsertRing->GetNext() != m_pInsertRing.get() ) + delete m_pInsertRing->GetNext(); + m_pInsertRing.reset(); + } +} + +void CompareData::SetIndex( size_t nLine, size_t nIndex ) +{ + if( !m_pIndex ) + { + m_pIndex.reset( new size_t[ m_aLines.size() ] ); + memset( m_pIndex.get(), 0, m_aLines.size() * sizeof( size_t ) ); + } + if( nLine < m_aLines.size() ) + m_pIndex[ nLine ] = nIndex; +} + +void CompareData::SetChanged( size_t nLine, bool bFlag ) +{ + if( !m_pChangedFlag ) + { + m_pChangedFlag.reset( new bool[ m_aLines.size() +1 ] ); + memset( m_pChangedFlag.get(), 0, (m_aLines.size() +1) * sizeof( bool ) ); + } + if( nLine < m_aLines.size() ) + m_pChangedFlag[ nLine ] = bFlag; +} + +void CompareData::CompareLines( CompareData& rData ) +{ + CheckRanges( rData ); + + sal_uLong nDifferent; + { + Hash aH( GetLineCount() + rData.GetLineCount() + 1 ); + aH.CalcHashValue( *this ); + aH.CalcHashValue( rData ); + nDifferent = aH.GetCount(); + } + { + Compare aComp( nDifferent, *this, rData ); + } +} + +sal_uLong CompareData::ShowDiffs( const CompareData& rData ) +{ + sal_uLong nLen1 = rData.GetLineCount(), nLen2 = GetLineCount(); + sal_uLong nStt1 = 0, nStt2 = 0; + sal_uLong nCnt = 0; + + while( nStt1 < nLen1 || nStt2 < nLen2 ) + { + if( rData.GetChanged( nStt1 ) || GetChanged( nStt2 ) ) + { + // Find a region of different lines between two pairs of identical + // lines. + sal_uLong nSav1 = nStt1, nSav2 = nStt2; + while( nStt1 < nLen1 && rData.GetChanged( nStt1 )) ++nStt1; + while( nStt2 < nLen2 && GetChanged( nStt2 )) ++nStt2; + + if (m_bRecordDiff) + { + // Check if there are changed lines (only slightly different) and + // compare them in detail. + CheckForChangesInLine( rData, nSav1, nStt1, nSav2, nStt2 ); + } + + ++nCnt; + } + ++nStt1; + ++nStt2; + } + return nCnt; +} + +bool CompareData::HasDiffs( const CompareData& rData ) const +{ + bool bRet = false; + sal_uLong nLen1 = rData.GetLineCount(), nLen2 = GetLineCount(); + sal_uLong nStt1 = 0, nStt2 = 0; + + while( nStt1 < nLen1 || nStt2 < nLen2 ) + { + if( rData.GetChanged( nStt1 ) || GetChanged( nStt2 ) ) + { + bRet = true; + break; + } + ++nStt1; + ++nStt2; + } + return bRet; +} + +Hash::Hash( sal_uLong nSize ) + : m_nCount(1) +{ + + static const sal_uLong primes[] = + { + 509, + 1021, + 2039, + 4093, + 8191, + 16381, + 32749, + 65521, + 131071, + 262139, + 524287, + 1048573, + 2097143, + 4194301, + 8388593, + 16777213, + 33554393, + 67108859, /* Preposterously large . . . */ + 134217689, + 268435399, + 536870909, + 1073741789, + 2147483647, + 0 + }; + int i; + + m_pDataArr.reset( new HashData[ nSize ] ); + m_pDataArr[0].nNext = 0; + m_pDataArr[0].nHash = 0; + m_pDataArr[0].pLine = nullptr; + m_nPrime = primes[0]; + + for( i = 0; primes[i] < nSize / 3; i++) + if( !primes[i] ) + { + m_pHashArr = nullptr; + return; + } + m_nPrime = primes[ i ]; + m_pHashArr.reset( new sal_uLong[ m_nPrime ] ); + memset( m_pHashArr.get(), 0, m_nPrime * sizeof( sal_uLong ) ); +} + +void Hash::CalcHashValue( CompareData& rData ) +{ + if( m_pHashArr ) + { + for( size_t n = 0; n < rData.GetLineCount(); ++n ) + { + const SwCompareLine* pLine = rData.GetLine( n ); + OSL_ENSURE( pLine, "where is the line?" ); + sal_uLong nH = pLine->GetHashValue(); + + sal_uLong* pFound = &m_pHashArr[ nH % m_nPrime ]; + size_t i; + for( i = *pFound; ; i = m_pDataArr[i].nNext ) + if( !i ) + { + i = m_nCount++; + m_pDataArr[i].nNext = *pFound; + m_pDataArr[i].nHash = nH; + m_pDataArr[i].pLine = pLine; + *pFound = i; + break; + } + else if( m_pDataArr[i].nHash == nH && + m_pDataArr[i].pLine->Compare( *pLine )) + break; + + rData.SetIndex( n, i ); + } + } +} + +Compare::Compare( sal_uLong nDiff, CompareData& rData1, CompareData& rData2 ) +{ + std::unique_ptr pMD1, pMD2; + // Look for the differing lines + { + std::unique_ptr pDiscard1( new char[ rData1.GetLineCount() ] ); + std::unique_ptr pDiscard2( new char[ rData2.GetLineCount() ] ); + + std::unique_ptr pCount1(new sal_uLong[ nDiff ]); + std::unique_ptr pCount2(new sal_uLong[ nDiff ]); + memset( pCount1.get(), 0, nDiff * sizeof( sal_uLong )); + memset( pCount2.get(), 0, nDiff * sizeof( sal_uLong )); + + // find indices in CompareData which have been assigned multiple times + CountDifference( rData1, pCount1.get() ); + CountDifference( rData2, pCount2.get() ); + + // All which occur only once now have either been inserted or deleted. + // All which are also contained in the other one have been moved. + SetDiscard( rData1, pDiscard1.get(), pCount2.get() ); + SetDiscard( rData2, pDiscard2.get(), pCount1.get() ); + + CheckDiscard( rData1.GetLineCount(), pDiscard1.get() ); + CheckDiscard( rData2.GetLineCount(), pDiscard2.get() ); + + pMD1.reset(new MovedData( rData1, pDiscard1.get() )); + pMD2.reset(new MovedData( rData2, pDiscard2.get() )); + } + + { + CompareSequence aTmp( rData1, rData2, *pMD1, *pMD2 ); + } + + ShiftBoundaries( rData1, rData2 ); +} + +void Compare::CountDifference( const CompareData& rData, sal_uLong* pCounts ) +{ + sal_uLong nLen = rData.GetLineCount(); + for( sal_uLong n = 0; n < nLen; ++n ) + { + sal_uLong nIdx = rData.GetIndex( n ); + ++pCounts[ nIdx ]; + } +} + +void Compare::SetDiscard( const CompareData& rData, + char* pDiscard, const sal_uLong* pCounts ) +{ + const sal_uLong nLen = rData.GetLineCount(); + + // calculate Max with respect to the line count + sal_uLong nMax = 5; + + for( sal_uLong n = nLen / 64; ( n = n >> 2 ) > 0; ) + nMax <<= 1; + + for( sal_uLong n = 0; n < nLen; ++n ) + { + sal_uLong nIdx = rData.GetIndex( n ); + if( nIdx ) + { + nIdx = pCounts[ nIdx ]; + pDiscard[ n ] = !nIdx ? 1 : nIdx > nMax ? 2 : 0; + } + else + pDiscard[ n ] = 0; + } +} + +void Compare::CheckDiscard( sal_uLong nLen, char* pDiscard ) +{ + for( sal_uLong n = 0; n < nLen; ++n ) + { + if( 2 == pDiscard[ n ] ) + pDiscard[n] = 0; + else if( pDiscard[ n ] ) + { + sal_uLong j; + sal_uLong length; + sal_uLong provisional = 0; + + /* Find end of this run of discardable lines. + Count how many are provisionally discardable. */ + for (j = n; j < nLen; j++) + { + if( !pDiscard[j] ) + break; + if( 2 == pDiscard[j] ) + ++provisional; + } + + /* Cancel provisional discards at end, and shrink the run. */ + while( j > n && 2 == pDiscard[j - 1] ) + { + pDiscard[ --j ] = 0; + --provisional; + } + + /* Now we have the length of a run of discardable lines + whose first and last are not provisional. */ + length = j - n; + + /* If 1/4 of the lines in the run are provisional, + cancel discarding of all provisional lines in the run. */ + if (provisional * 4 > length) + { + while (j > n) + if (pDiscard[--j] == 2) + pDiscard[j] = 0; + } + else + { + sal_uLong consec; + sal_uLong minimum = 1; + sal_uLong tem = length / 4; + + /* MINIMUM is approximate square root of LENGTH/4. + A subrun of two or more provisionals can stand + when LENGTH is at least 16. + A subrun of 4 or more can stand when LENGTH >= 64. */ + while ((tem = tem >> 2) > 0) + minimum *= 2; + minimum++; + + /* Cancel any subrun of MINIMUM or more provisionals + within the larger run. */ + for (j = 0, consec = 0; j < length; j++) + if (pDiscard[n + j] != 2) + consec = 0; + else if (minimum == ++consec) + /* Back up to start of subrun, to cancel it all. */ + j -= consec; + else if (minimum < consec) + pDiscard[n + j] = 0; + + /* Scan from beginning of run + until we find 3 or more nonprovisionals in a row + or until the first nonprovisional at least 8 lines in. + Until that point, cancel any provisionals. */ + for (j = 0, consec = 0; j < length; j++) + { + if (j >= 8 && pDiscard[n + j] == 1) + break; + if (pDiscard[n + j] == 2) + { + consec = 0; + pDiscard[n + j] = 0; + } + else if (pDiscard[n + j] == 0) + consec = 0; + else + consec++; + if (consec == 3) + break; + } + + /* I advances to the last line of the run. */ + n += length - 1; + + /* Same thing, from end. */ + for (j = 0, consec = 0; j < length; j++) + { + if (j >= 8 && pDiscard[n - j] == 1) + break; + if (pDiscard[n - j] == 2) + { + consec = 0; + pDiscard[n - j] = 0; + } + else if (pDiscard[n - j] == 0) + consec = 0; + else + consec++; + if (consec == 3) + break; + } + } + } + } +} + +Compare::MovedData::MovedData( CompareData& rData, const char* pDiscard ) + : m_nCount( 0 ) +{ + sal_uLong nLen = rData.GetLineCount(); + sal_uLong n; + + for( n = 0; n < nLen; ++n ) + if( pDiscard[ n ] ) + rData.SetChanged( n ); + else + ++m_nCount; + + if( m_nCount ) + { + m_pIndex.reset( new sal_uLong[ m_nCount ] ); + m_pLineNum.reset( new sal_uLong[ m_nCount ] ); + + for( n = 0, m_nCount = 0; n < nLen; ++n ) + if( !pDiscard[ n ] ) + { + m_pIndex[ m_nCount ] = rData.GetIndex( n ); + m_pLineNum[ m_nCount++ ] = n; + } + } +} + +/// Find the differing lines +Compare::CompareSequence::CompareSequence( + CompareData& rD1, CompareData& rD2, + const MovedData& rMD1, const MovedData& rMD2 ) + : m_rData1( rD1 ), m_rData2( rD2 ), m_rMoved1( rMD1 ), m_rMoved2( rMD2 ) +{ + sal_uLong nSize = rMD1.GetCount() + rMD2.GetCount() + 3; + m_pMemory.reset( new long[ nSize * 2 ] ); + m_pFDiag = m_pMemory.get() + ( rMD2.GetCount() + 1 ); + m_pBDiag = m_pMemory.get() + ( nSize + rMD2.GetCount() + 1 ); + + Compare( 0, rMD1.GetCount(), 0, rMD2.GetCount() ); +} + +void Compare::CompareSequence::Compare( sal_uLong nStt1, sal_uLong nEnd1, + sal_uLong nStt2, sal_uLong nEnd2 ) +{ + /* Slide down the bottom initial diagonal. */ + while( nStt1 < nEnd1 && nStt2 < nEnd2 && + m_rMoved1.GetIndex( nStt1 ) == m_rMoved2.GetIndex( nStt2 )) + { + ++nStt1; + ++nStt2; + } + + /* Slide up the top initial diagonal. */ + while( nEnd1 > nStt1 && nEnd2 > nStt2 && + m_rMoved1.GetIndex( nEnd1 - 1 ) == m_rMoved2.GetIndex( nEnd2 - 1 )) + { + --nEnd1; + --nEnd2; + } + + /* Handle simple cases. */ + if( nStt1 == nEnd1 ) + while( nStt2 < nEnd2 ) + m_rData2.SetChanged( m_rMoved2.GetLineNum( nStt2++ )); + + else if (nStt2 == nEnd2) + while (nStt1 < nEnd1) + m_rData1.SetChanged( m_rMoved1.GetLineNum( nStt1++ )); + + else + { + sal_uLong c, d, b; + + /* Find a point of correspondence in the middle of the files. */ + + d = CheckDiag( nStt1, nEnd1, nStt2, nEnd2, &c ); + b = m_pBDiag[ d ]; + + if( 1 != c ) + { + /* Use that point to split this problem into two subproblems. */ + Compare( nStt1, b, nStt2, b - d ); + /* This used to use f instead of b, + but that is incorrect! + It is not necessarily the case that diagonal d + has a snake from b to f. */ + Compare( b, nEnd1, b - d, nEnd2 ); + } + } +} + +sal_uLong Compare::CompareSequence::CheckDiag( sal_uLong nStt1, sal_uLong nEnd1, + sal_uLong nStt2, sal_uLong nEnd2, sal_uLong* pCost ) +{ + const long dmin = nStt1 - nEnd2; /* Minimum valid diagonal. */ + const long dmax = nEnd1 - nStt2; /* Maximum valid diagonal. */ + const long fmid = nStt1 - nStt2; /* Center diagonal of top-down search. */ + const long bmid = nEnd1 - nEnd2; /* Center diagonal of bottom-up search. */ + + long fmin = fmid, fmax = fmid; /* Limits of top-down search. */ + long bmin = bmid, bmax = bmid; /* Limits of bottom-up search. */ + + long c; /* Cost. */ + long odd = (fmid - bmid) & 1; /* True if southeast corner is on an odd + diagonal with respect to the northwest. */ + + m_pFDiag[fmid] = nStt1; + m_pBDiag[bmid] = nEnd1; + + for (c = 1;; ++c) + { + long d; /* Active diagonal. */ + + /* Extend the top-down search by an edit step in each diagonal. */ + if (fmin > dmin) + m_pFDiag[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + m_pFDiag[++fmax + 1] = -1; + else + --fmax; + for (d = fmax; d >= fmin; d -= 2) + { + long x, y, tlo = m_pFDiag[d - 1], thi = m_pFDiag[d + 1]; + + if (tlo >= thi) + x = tlo + 1; + else + x = thi; + y = x - d; + while( o3tl::make_unsigned(x) < nEnd1 && o3tl::make_unsigned(y) < nEnd2 && + m_rMoved1.GetIndex( x ) == m_rMoved2.GetIndex( y )) + { + ++x; + ++y; + } + m_pFDiag[d] = x; + if( odd && bmin <= d && d <= bmax && m_pBDiag[d] <= m_pFDiag[d] ) + { + *pCost = 2 * c - 1; + return d; + } + } + + /* Similar extend the bottom-up search. */ + if (bmin > dmin) + m_pBDiag[--bmin - 1] = INT_MAX; + else + ++bmin; + if (bmax < dmax) + m_pBDiag[++bmax + 1] = INT_MAX; + else + --bmax; + for (d = bmax; d >= bmin; d -= 2) + { + long x, y, tlo = m_pBDiag[d - 1], thi = m_pBDiag[d + 1]; + + if (tlo < thi) + x = tlo; + else + x = thi - 1; + y = x - d; + while( o3tl::make_unsigned(x) > nStt1 && o3tl::make_unsigned(y) > nStt2 && + m_rMoved1.GetIndex( x - 1 ) == m_rMoved2.GetIndex( y - 1 )) + { + --x; + --y; + } + m_pBDiag[d] = x; + if (!odd && fmin <= d && d <= fmax && m_pBDiag[d] <= m_pFDiag[d]) + { + *pCost = 2 * c; + return d; + } + } + } +} + +namespace +{ + void lcl_ShiftBoundariesOneway( CompareData* const pData, CompareData const * const pOtherData) + { + sal_uLong i = 0; + sal_uLong j = 0; + sal_uLong i_end = pData->GetLineCount(); + sal_uLong preceding = ULONG_MAX; + sal_uLong other_preceding = ULONG_MAX; + + while (true) + { + sal_uLong start, other_start; + + /* Scan forwards to find beginning of another run of changes. + Also keep track of the corresponding point in the other file. */ + + while( i < i_end && !pData->GetChanged( i ) ) + { + while( pOtherData->GetChanged( j++ )) + /* Non-corresponding lines in the other file + will count as the preceding batch of changes. */ + other_preceding = j; + i++; + } + + if (i == i_end) + break; + + start = i; + other_start = j; + + while (true) + { + /* Now find the end of this run of changes. */ + + while( pData->GetChanged( ++i )) + ; + + /* If the first changed line matches the following unchanged one, + and this run does not follow right after a previous run, + and there are no lines deleted from the other file here, + then classify the first changed line as unchanged + and the following line as changed in its place. */ + + /* You might ask, how could this run follow right after another? + Only because the previous run was shifted here. */ + + if( i != i_end && + pData->GetIndex( start ) == pData->GetIndex( i ) && + !pOtherData->GetChanged( j ) && + !( start == preceding || other_start == other_preceding )) + { + pData->SetChanged( start++, false ); + pData->SetChanged( i ); + /* Since one line-that-matches is now before this run + instead of after, we must advance in the other file + to keep in sync. */ + ++j; + } + else + break; + } + + preceding = i; + other_preceding = j; + } + } +} + +void Compare::ShiftBoundaries( CompareData& rData1, CompareData& rData2 ) +{ + lcl_ShiftBoundariesOneway(&rData1, &rData2); + lcl_ShiftBoundariesOneway(&rData2, &rData1); +} + +sal_uLong SwCompareLine::GetHashValue() const +{ + sal_uLong nRet = 0; + switch( m_rNode.GetNodeType() ) + { + case SwNodeType::Text: + nRet = GetTextNodeHashValue( *m_rNode.GetTextNode(), nRet ); + break; + + case SwNodeType::Table: + { + const SwNode* pEndNd = m_rNode.EndOfSectionNode(); + SwNodeIndex aIdx( m_rNode ); + while( &aIdx.GetNode() != pEndNd ) + { + if( aIdx.GetNode().IsTextNode() ) + nRet = GetTextNodeHashValue( *aIdx.GetNode().GetTextNode(), nRet ); + ++aIdx; + } + } + break; + + case SwNodeType::Section: + { + OUString sStr( GetText() ); + for( sal_Int32 n = 0; n < sStr.getLength(); ++n ) + nRet = (nRet << 1) + sStr[ n ]; + } + break; + + case SwNodeType::Grf: + case SwNodeType::Ole: + // Fixed ID? Should never occur ... + break; + default: break; + } + return nRet; +} + +const SwNode& SwCompareLine::GetEndNode() const +{ + const SwNode* pNd = &m_rNode; + switch( m_rNode.GetNodeType() ) + { + case SwNodeType::Table: + pNd = m_rNode.EndOfSectionNode(); + break; + + case SwNodeType::Section: + { + const SwSectionNode& rSNd = static_cast(m_rNode); + const SwSection& rSect = rSNd.GetSection(); + if( SectionType::Content != rSect.GetType() || rSect.IsProtect() ) + pNd = m_rNode.EndOfSectionNode(); + } + break; + default: break; + } + return *pNd; +} + +bool SwCompareLine::Compare( const SwCompareLine& rLine ) const +{ + return CompareNode( m_rNode, rLine.m_rNode ); +} + +namespace +{ + OUString SimpleTableToText(const SwNode &rNode) + { + OUStringBuffer sRet; + const SwNode* pEndNd = rNode.EndOfSectionNode(); + SwNodeIndex aIdx( rNode ); + while (&aIdx.GetNode() != pEndNd) + { + if (aIdx.GetNode().IsTextNode()) + { + if (sRet.getLength()) + { + sRet.append( '\n' ); + } + sRet.append( aIdx.GetNode().GetTextNode()->GetExpandText(nullptr) ); + } + ++aIdx; + } + return sRet.makeStringAndClear(); + } +} + +bool SwCompareLine::CompareNode( const SwNode& rDstNd, const SwNode& rSrcNd ) +{ + if( rSrcNd.GetNodeType() != rDstNd.GetNodeType() ) + return false; + + bool bRet = false; + + switch( rDstNd.GetNodeType() ) + { + case SwNodeType::Text: + bRet = CompareTextNd( *rDstNd.GetTextNode(), *rSrcNd.GetTextNode() ) + && ( !CmpOptions.bUseRsid || rDstNd.GetTextNode()->CompareParRsid( *rSrcNd.GetTextNode() ) ); + break; + + case SwNodeType::Table: + { + const SwTableNode& rTSrcNd = static_cast(rSrcNd); + const SwTableNode& rTDstNd = static_cast(rDstNd); + + bRet = ( rTSrcNd.EndOfSectionIndex() - rTSrcNd.GetIndex() ) == + ( rTDstNd.EndOfSectionIndex() - rTDstNd.GetIndex() ); + + // --> #i107826#: compare actual table content + if (bRet) + { + bRet = (SimpleTableToText(rSrcNd) == SimpleTableToText(rDstNd)); + } + } + break; + + case SwNodeType::Section: + { + const SwSectionNode& rSSrcNd = static_cast(rSrcNd), + & rSDstNd = static_cast(rDstNd); + const SwSection& rSrcSect = rSSrcNd.GetSection(), + & rDstSect = rSDstNd.GetSection(); + SectionType eSrcSectType = rSrcSect.GetType(), + eDstSectType = rDstSect.GetType(); + switch( eSrcSectType ) + { + case SectionType::Content: + bRet = SectionType::Content == eDstSectType && + rSrcSect.IsProtect() == rDstSect.IsProtect(); + if( bRet && rSrcSect.IsProtect() ) + { + // the only have they both the same size + bRet = ( rSSrcNd.EndOfSectionIndex() - rSSrcNd.GetIndex() ) == + ( rSDstNd.EndOfSectionIndex() - rSDstNd.GetIndex() ); + } + break; + + case SectionType::ToxHeader: + case SectionType::ToxContent: + if( SectionType::ToxHeader == eDstSectType || + SectionType::ToxContent == eDstSectType ) + { + // the same type of TOX? + const SwTOXBase* pSrcTOX = rSrcSect.GetTOXBase(); + const SwTOXBase* pDstTOX = rDstSect.GetTOXBase(); + bRet = pSrcTOX && pDstTOX + && pSrcTOX->GetType() == pDstTOX->GetType() + && pSrcTOX->GetTitle() == pDstTOX->GetTitle() + && pSrcTOX->GetTypeName() == pDstTOX->GetTypeName() + ; + } + break; + + case SectionType::DdeLink: + case SectionType::FileLink: + bRet = eSrcSectType == eDstSectType && + rSrcSect.GetLinkFileName() == + rDstSect.GetLinkFileName(); + break; + } + } + break; + + case SwNodeType::End: + bRet = rSrcNd.StartOfSectionNode()->GetNodeType() == + rDstNd.StartOfSectionNode()->GetNodeType(); + + // --> #i107826#: compare actual table content + if (bRet && rSrcNd.StartOfSectionNode()->GetNodeType() == SwNodeType::Table) + { + bRet = CompareNode( + *rSrcNd.StartOfSectionNode(), *rDstNd.StartOfSectionNode()); + } + + break; + + default: break; + } + return bRet; +} + +OUString SwCompareLine::GetText() const +{ + OUString sRet; + switch( m_rNode.GetNodeType() ) + { + case SwNodeType::Text: + sRet = m_rNode.GetTextNode()->GetExpandText(nullptr); + break; + + case SwNodeType::Table: + { + sRet = "Tabelle: " + SimpleTableToText(m_rNode); + } + break; + + case SwNodeType::Section: + { + sRet = "Section - Node:"; + + const SwSectionNode& rSNd = static_cast(m_rNode); + const SwSection& rSect = rSNd.GetSection(); + switch( rSect.GetType() ) + { + case SectionType::Content: + if( rSect.IsProtect() ) + sRet += OUString::number( + rSNd.EndOfSectionIndex() - rSNd.GetIndex() ); + break; + + case SectionType::ToxHeader: + case SectionType::ToxContent: + { + const SwTOXBase* pTOX = rSect.GetTOXBase(); + if( pTOX ) + sRet += pTOX->GetTitle() + pTOX->GetTypeName() + + OUString::number(pTOX->GetType()); + } + break; + + case SectionType::DdeLink: + case SectionType::FileLink: + sRet += rSect.GetLinkFileName(); + break; + } + } + break; + + case SwNodeType::Grf: + sRet = "Grafik - Node:"; + break; + case SwNodeType::Ole: + sRet = "OLE - Node:"; + break; + default: break; + } + return sRet; +} + +sal_uLong SwCompareLine::GetTextNodeHashValue( const SwTextNode& rNd, sal_uLong nVal ) +{ + OUString sStr( rNd.GetExpandText(nullptr) ); + for( sal_Int32 n = 0; n < sStr.getLength(); ++n ) + nVal = (nVal << 1 ) + sStr[ n ]; + return nVal; +} + +bool SwCompareLine::CompareTextNd( const SwTextNode& rDstNd, + const SwTextNode& rSrcNd ) +{ + bool bRet = false; + // Very simple at first + if( rDstNd.GetText() == rSrcNd.GetText() ) + { + // The text is the same, but are the "special attributes" (0xFF) also the same? + bRet = true; + } + return bRet; +} + +bool SwCompareLine::ChangesInLine( const SwCompareLine& rLine, + std::unique_ptr& rpInsRing, std::unique_ptr& rpDelRing ) const +{ + bool bRet = false; + + // Only compare textnodes + if( SwNodeType::Text == m_rNode.GetNodeType() && + SwNodeType::Text == rLine.GetNode().GetNodeType() ) + { + SwTextNode& rDstNd = *const_cast(m_rNode.GetTextNode()); + const SwTextNode& rSrcNd = *rLine.GetNode().GetTextNode(); + SwDoc* pDstDoc = rDstNd.GetDoc(); + + int nLcsLen = 0; + + int nDstLen = rDstNd.GetText().getLength(); + int nSrcLen = rSrcNd.GetText().getLength(); + + int nMinLen = std::min( nDstLen , nSrcLen ); + int nAvgLen = ( nDstLen + nSrcLen )/2; + + std::vector aLcsDst( nMinLen + 1 ); + std::vector aLcsSrc( nMinLen + 1 ); + + if( CmpOptions.eCmpMode == SwCompareMode::ByWord ) + { + std::vector aTmpLcsDst( nMinLen + 1 ); + std::vector aTmpLcsSrc( nMinLen + 1 ); + + WordArrayComparator aCmp( &rDstNd, &rSrcNd ); + + LgstCommonSubseq aSeq( aCmp ); + + nLcsLen = aSeq.Find( aTmpLcsDst.data(), aTmpLcsSrc.data() ); + + if( CmpOptions.nIgnoreLen ) + { + nLcsLen = CommonSubseq::IgnoreIsolatedPieces( aTmpLcsDst.data(), aTmpLcsSrc.data(), + aCmp.GetLen1(), aCmp.GetLen2(), + nLcsLen, CmpOptions.nIgnoreLen ); + } + + nLcsLen = aCmp.GetCharSequence( aTmpLcsDst.data(), aTmpLcsSrc.data(), + aLcsDst.data(), aLcsSrc.data(), nLcsLen ); + } + else + { + CharArrayComparator aCmp( &rDstNd, &rSrcNd ); + LgstCommonSubseq aSeq( aCmp ); + + nLcsLen = aSeq.Find( aLcsDst.data(), aLcsSrc.data() ); + + if( CmpOptions.nIgnoreLen ) + { + nLcsLen = CommonSubseq::IgnoreIsolatedPieces( aLcsDst.data(), aLcsSrc.data(), nDstLen, + nSrcLen, nLcsLen, + CmpOptions.nIgnoreLen ); + } + } + + // find the sum of the squares of the continuous substrings + int nSqSum = 0; + int nCnt = 1; + for( int i = 0; i < nLcsLen; i++ ) + { + if( i != nLcsLen - 1 && aLcsDst[i] + 1 == aLcsDst[i + 1] + && aLcsSrc[i] + 1 == aLcsSrc[i + 1] ) + { + nCnt++; + } + else + { + nSqSum += nCnt*nCnt; + nCnt = 1; + } + } + + // Don't compare if there aren't enough similarities + if ( nAvgLen >= 8 && nSqSum*32 < nAvgLen*nAvgLen ) + { + return false; + } + + // Show the differences + int nSkip = 0; + for( int i = 0; i <= nLcsLen; i++ ) + { + int nDstFrom = i ? (aLcsDst[i - 1] + 1) : 0; + int nDstTo = ( i == nLcsLen ) ? nDstLen : aLcsDst[i]; + int nSrcFrom = i ? (aLcsSrc[i - 1] + 1) : 0; + int nSrcTo = ( i == nLcsLen ) ? nSrcLen : aLcsSrc[i]; + + SwPaM aPam( rDstNd, nDstTo + nSkip ); + + if ( nDstFrom < nDstTo ) + { + SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpInsRing.get() ); + if( !rpInsRing ) + rpInsRing.reset(pTmp); + pTmp->SetMark(); + pTmp->GetMark()->nContent = nDstFrom + nSkip; + } + + if ( nSrcFrom < nSrcTo ) + { + bool bUndo = pDstDoc->GetIDocumentUndoRedo().DoesUndo(); + pDstDoc->GetIDocumentUndoRedo().DoUndo( false ); + SwPaM aCpyPam( rSrcNd, nSrcFrom ); + aCpyPam.SetMark(); + aCpyPam.GetPoint()->nContent = nSrcTo; + aCpyPam.GetDoc()->getIDocumentContentOperations().CopyRange( aCpyPam, *aPam.GetPoint(), + SwCopyFlags::CheckPosInFly); + pDstDoc->GetIDocumentUndoRedo().DoUndo( bUndo ); + + SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpDelRing.get() ); + if( !rpDelRing ) + rpDelRing.reset(pTmp); + + pTmp->SetMark(); + pTmp->GetMark()->nContent = nDstTo + nSkip; + nSkip += nSrcTo - nSrcFrom; + + if( rpInsRing ) + { + SwPaM* pCorr = rpInsRing->GetPrev(); + if( *pCorr->GetPoint() == *pTmp->GetPoint() ) + *pCorr->GetPoint() = *pTmp->GetMark(); + } + } + } + + bRet = true; + } + + return bRet; +} + +sal_uLong CompareData::NextIdx( const SwNode* pNd ) +{ + if( pNd->IsStartNode() ) + { + if( pNd->IsTableNode() ) + pNd = pNd->EndOfSectionNode(); + else + { + const SwSectionNode* pSNd = pNd->GetSectionNode(); + if( pSNd && + ( SectionType::Content != pSNd->GetSection().GetType() || + pSNd->GetSection().IsProtect() ) ) + pNd = pNd->EndOfSectionNode(); + } + } + return pNd->GetIndex() + 1; +} + +sal_uLong CompareData::PrevIdx( const SwNode* pNd ) +{ + if( pNd->IsEndNode() ) + { + if( pNd->StartOfSectionNode()->IsTableNode() ) + pNd = pNd->StartOfSectionNode(); + else + { + const SwSectionNode* pSNd = pNd->StartOfSectionNode()->GetSectionNode(); + if( pSNd && + ( SectionType::Content != pSNd->GetSection().GetType() || + pSNd->GetSection().IsProtect() ) ) + pNd = pNd->StartOfSectionNode(); + } + } + return pNd->GetIndex() - 1; +} + +void CompareData::CheckRanges( CompareData& rData ) +{ + const SwNodes& rSrcNds = rData.m_rDoc.GetNodes(); + const SwNodes& rDstNds = m_rDoc.GetNodes(); + + const SwNode& rSrcEndNd = rData.GetEndOfContent(); + const SwNode& rDstEndNd = GetEndOfContent(); + + sal_uLong nSrcSttIdx = NextIdx( rSrcEndNd.StartOfSectionNode() ); + sal_uLong nSrcEndIdx = rSrcEndNd.GetIndex(); + + sal_uLong nDstSttIdx = NextIdx( rDstEndNd.StartOfSectionNode() ); + sal_uLong nDstEndIdx = rDstEndNd.GetIndex(); + + while( nSrcSttIdx < nSrcEndIdx && nDstSttIdx < nDstEndIdx ) + { + const SwNode* pSrcNd = rSrcNds[ nSrcSttIdx ]; + const SwNode* pDstNd = rDstNds[ nDstSttIdx ]; + if( !SwCompareLine::CompareNode( *pSrcNd, *pDstNd )) + break; + + nSrcSttIdx = NextIdx( pSrcNd ); + nDstSttIdx = NextIdx( pDstNd ); + } + + nSrcEndIdx = PrevIdx( &rSrcEndNd ); + nDstEndIdx = PrevIdx( &rDstEndNd ); + while( nSrcSttIdx < nSrcEndIdx && nDstSttIdx < nDstEndIdx ) + { + const SwNode* pSrcNd = rSrcNds[ nSrcEndIdx ]; + const SwNode* pDstNd = rDstNds[ nDstEndIdx ]; + if( !SwCompareLine::CompareNode( *pSrcNd, *pDstNd )) + break; + + nSrcEndIdx = PrevIdx( pSrcNd ); + nDstEndIdx = PrevIdx( pDstNd ); + } + + while( nSrcSttIdx <= nSrcEndIdx ) + { + const SwNode* pNd = rSrcNds[ nSrcSttIdx ]; + rData.InsertLine( new SwCompareLine( *pNd ) ); + nSrcSttIdx = NextIdx( pNd ); + } + + while( nDstSttIdx <= nDstEndIdx ) + { + const SwNode* pNd = rDstNds[ nDstSttIdx ]; + InsertLine( new SwCompareLine( *pNd ) ); + nDstSttIdx = NextIdx( pNd ); + } +} + +void CompareData::ShowInsert( sal_uLong nStt, sal_uLong nEnd ) +{ + SwPaM* pTmp = new SwPaM( GetLine( nStt )->GetNode(), 0, + GetLine( nEnd-1 )->GetEndNode(), 0, + m_pInsertRing.get() ); + if( !m_pInsertRing ) + m_pInsertRing.reset( pTmp ); + + // #i65201#: These SwPaMs are calculated smaller than needed, see comment below +} + +void CompareData::ShowDelete( + const CompareData& rData, + sal_uLong nStt, + sal_uLong nEnd, + sal_uLong nInsPos ) +{ + SwNodeRange aRg( + rData.GetLine( nStt )->GetNode(), 0, + rData.GetLine( nEnd-1 )->GetEndNode(), 1 ); + + sal_uInt16 nOffset = 0; + const SwCompareLine* pLine = nullptr; + if( nInsPos >= 1 ) + { + if( GetLineCount() == nInsPos ) + { + pLine = GetLine( nInsPos-1 ); + nOffset = 1; + } + else + pLine = GetLine( nInsPos ); + } + + const SwNode* pLineNd; + if( pLine ) + { + if( nOffset ) + pLineNd = &pLine->GetEndNode(); + else + pLineNd = &pLine->GetNode(); + } + else + { + pLineNd = &GetEndOfContent(); + nOffset = 0; + } + + SwNodeIndex aInsPos( *pLineNd, nOffset ); + SwNodeIndex aSavePos( aInsPos, -1 ); + + rData.m_rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos); + m_rDoc.getIDocumentState().SetModified(); + ++aSavePos; + + // #i65201#: These SwPaMs are calculated when the (old) delete-redlines are hidden, + // they will be inserted when the delete-redlines are shown again. + // To avoid unwanted insertions of delete-redlines into these new redlines, what happens + // especially at the end of the document, I reduce the SwPaM by one node. + // Before the new redlines are inserted, they have to expand again. + SwPaM* pTmp = new SwPaM( aSavePos.GetNode(), aInsPos.GetNode(), 0, -1, m_pDelRing.get() ); + if( !m_pDelRing ) + m_pDelRing.reset(pTmp); + + if( m_pInsertRing ) + { + SwPaM* pCorr = m_pInsertRing->GetPrev(); + if( *pCorr->GetPoint() == *pTmp->GetPoint() ) + { + SwNodeIndex aTmpPos( pTmp->GetMark()->nNode, -1 ); + *pCorr->GetPoint() = SwPosition( aTmpPos ); + } + } +} + +void CompareData::CheckForChangesInLine( const CompareData& rData, + sal_uLong nStt, sal_uLong nEnd, + sal_uLong nThisStt, sal_uLong nThisEnd ) +{ + LineArrayComparator aCmp( *this, rData, nThisStt, nThisEnd, + nStt, nEnd ); + + int nMinLen = std::min( aCmp.GetLen1(), aCmp.GetLen2() ); + std::unique_ptr pLcsDst(new int[ nMinLen ]); + std::unique_ptr pLcsSrc(new int[ nMinLen ]); + + FastCommonSubseq subseq( aCmp ); + int nLcsLen = subseq.Find( pLcsDst.get(), pLcsSrc.get() ); + for (int i = 0; i <= nLcsLen; i++) + { + // Beginning of inserted lines (inclusive) + int nDstFrom = i ? pLcsDst[i - 1] + 1 : 0; + // End of inserted lines (exclusive) + int nDstTo = ( i == nLcsLen ) ? aCmp.GetLen1() : pLcsDst[i]; + // Beginning of deleted lines (inclusive) + int nSrcFrom = i ? pLcsSrc[i - 1] + 1 : 0; + // End of deleted lines (exclusive) + int nSrcTo = ( i == nLcsLen ) ? aCmp.GetLen2() : pLcsSrc[i]; + + if( i ) + { + const SwCompareLine* pDstLn = GetLine( nThisStt + nDstFrom - 1 ); + const SwCompareLine* pSrcLn = rData.GetLine( nStt + nSrcFrom - 1 ); + + // Show differences in detail for lines that + // were matched as only slightly different + if( !pDstLn->ChangesInLine( *pSrcLn, m_pInsertRing, m_pDelRing ) ) + { + ShowInsert( nThisStt + nDstFrom - 1, nThisStt + nDstFrom ); + ShowDelete( rData, nStt + nSrcFrom - 1, nStt + nSrcFrom, + nThisStt + nDstFrom ); + } + } + + // Lines missing from source are inserted + if( nDstFrom != nDstTo ) + { + ShowInsert( nThisStt + nDstFrom, nThisStt + nDstTo ); + } + + // Lines missing from destination are deleted + if( nSrcFrom != nSrcTo ) + { + ShowDelete( rData, nStt + nSrcFrom, nStt + nSrcTo, nThisStt + nDstTo ); + } + } +} + +void CompareData::SetRedlinesToDoc( bool bUseDocInfo ) +{ + SwPaM* pTmp = m_pDelRing.get(); + + // get the Author / TimeStamp from the "other" document info + std::size_t nAuthor = m_rDoc.getIDocumentRedlineAccess().GetRedlineAuthor(); + DateTime aTimeStamp( DateTime::SYSTEM ); + SwDocShell *pDocShell(m_rDoc.GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) { + uno::Reference xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + + if( bUseDocInfo && xDocProps.is() ) { + OUString aTmp( 1 == xDocProps->getEditingCycles() + ? xDocProps->getAuthor() + : xDocProps->getModifiedBy() ); + util::DateTime uDT( 1 == xDocProps->getEditingCycles() + ? xDocProps->getCreationDate() + : xDocProps->getModificationDate() ); + + if( !aTmp.isEmpty() ) + { + nAuthor = m_rDoc.getIDocumentRedlineAccess().InsertRedlineAuthor( aTmp ); + aTimeStamp = DateTime(uDT); + } + } + } + + if( pTmp ) + { + SwRedlineData aRedlnData( RedlineType::Delete, nAuthor, aTimeStamp, + OUString(), nullptr ); + do { + // #i65201#: Expand again, see comment above. + if( pTmp->GetPoint()->nContent == 0 ) + { + ++pTmp->GetPoint()->nNode; + pTmp->GetPoint()->nContent.Assign( pTmp->GetContentNode(), 0 ); + } + // #i101009# + // prevent redlines that end on structural end node + if (& GetEndOfContent() == + & pTmp->GetPoint()->nNode.GetNode()) + { + --pTmp->GetPoint()->nNode; + SwContentNode *const pContentNode( pTmp->GetContentNode() ); + pTmp->GetPoint()->nContent.Assign( pContentNode, + pContentNode ? pContentNode->Len() : 0 ); + // tdf#106218 try to avoid losing a paragraph break here: + if (pTmp->GetMark()->nContent == 0) + { + SwNodeIndex const prev(pTmp->GetMark()->nNode, -1); + if (prev.GetNode().IsTextNode()) + { + *pTmp->GetMark() = SwPosition( + *prev.GetNode().GetTextNode(), + prev.GetNode().GetTextNode()->Len()); + } + } + } + + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pTmp, false, RedlineType::Any ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique( *pTmp, false )); + } + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( aRedlnData, *pTmp ), true ); + + } while( m_pDelRing.get() != ( pTmp = pTmp->GetNext()) ); + } + + pTmp = m_pInsertRing.get(); + if( pTmp ) + { + do { + if( pTmp->GetPoint()->nContent == 0 ) + { + ++pTmp->GetPoint()->nNode; + pTmp->GetPoint()->nContent.Assign( pTmp->GetContentNode(), 0 ); + } + // #i101009# + // prevent redlines that end on structural end node + if (& GetEndOfContent() == + & pTmp->GetPoint()->nNode.GetNode()) + { + --pTmp->GetPoint()->nNode; + SwContentNode *const pContentNode( pTmp->GetContentNode() ); + pTmp->GetPoint()->nContent.Assign( pContentNode, + pContentNode ? pContentNode->Len() : 0 ); + // tdf#106218 try to avoid losing a paragraph break here: + if (pTmp->GetMark()->nContent == 0) + { + SwNodeIndex const prev(pTmp->GetMark()->nNode, -1); + if (prev.GetNode().IsTextNode()) + { + *pTmp->GetMark() = SwPosition( + *prev.GetNode().GetTextNode(), + prev.GetNode().GetTextNode()->Len()); + } + } + } + } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) ); + SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp, + OUString(), nullptr ); + + // combine consecutive + if( pTmp->GetNext() != m_pInsertRing.get() ) + { + do { + SwPosition& rSttEnd = *pTmp->End(), + & rEndStt = *pTmp->GetNext()->Start(); + const SwContentNode* pCNd; + if( rSttEnd == rEndStt || + (!rEndStt.nContent.GetIndex() && + rEndStt.nNode.GetIndex() - 1 == rSttEnd.nNode.GetIndex() && + nullptr != ( pCNd = rSttEnd.nNode.GetNode().GetContentNode() ) && + rSttEnd.nContent.GetIndex() == pCNd->Len())) + { + if( pTmp->GetNext() == m_pInsertRing.get() ) + { + // are consecutive, so combine + rEndStt = *pTmp->Start(); + delete pTmp; + pTmp = m_pInsertRing.get(); + } + else + { + // are consecutive, so combine + rSttEnd = *pTmp->GetNext()->End(); + delete pTmp->GetNext(); + } + } + else + pTmp = pTmp->GetNext(); + } while( m_pInsertRing.get() != pTmp ); + } + + do { + if (IDocumentRedlineAccess::AppendResult::APPENDED == + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(aRedlnData, *pTmp), true) && + m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique( *pTmp, true )); + } + } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) ); + } +} + +typedef std::shared_ptr CompareDataPtr; +typedef std::pair CompareDataPtrPair; +typedef std::vector Comparators; + +namespace +{ + Comparators buildComparators(SwDoc &rSrcDoc, SwDoc &rDestDoc) + { + Comparators aComparisons; + //compare main text + aComparisons.emplace_back(std::make_shared(rSrcDoc, true), + std::make_shared(rDestDoc, true)); + + //if we have the same number of frames then try to compare within them + const SwFrameFormats *pSrcFrameFormats = rSrcDoc.GetSpzFrameFormats(); + const SwFrameFormats *pDestFrameFormats = rDestDoc.GetSpzFrameFormats(); + if (pSrcFrameFormats->size() == pDestFrameFormats->size()) + { + for (size_t i = 0; i < pSrcFrameFormats->size(); ++i) + { + const SwFrameFormat& rSrcFormat = *(*pSrcFrameFormats)[i]; + const SwFrameFormat& rDestFormat = *(*pDestFrameFormats)[i]; + const SwNodeIndex* pSrcIdx = rSrcFormat.GetContent().GetContentIdx(); + const SwNodeIndex* pDestIdx = rDestFormat.GetContent().GetContentIdx(); + if (!pSrcIdx && !pDestIdx) + continue; + if (!pSrcIdx || !pDestIdx) + break; + const SwNode* pSrcNode = pSrcIdx->GetNode().EndOfSectionNode(); + const SwNode* pDestNode = pDestIdx->GetNode().EndOfSectionNode(); + if (!pSrcNode && !pDestNode) + continue; + if (!pSrcNode || !pDestNode) + break; + if (pSrcIdx->GetNodes()[pSrcIdx->GetIndex() + 1]->IsNoTextNode() + || pDestIdx->GetNodes()[pDestIdx->GetIndex() + 1]->IsNoTextNode()) + { + continue; // tdf#125660 don't redline GrfNode/OLENode + } + aComparisons.emplace_back(std::make_shared(rSrcDoc, *pSrcIdx), + std::make_shared(rDestDoc, *pDestIdx)); + } + } + return aComparisons; + } +} + +// Returns (the difference count?) if something is different +long SwDoc::CompareDoc( const SwDoc& rDoc ) +{ + if( &rDoc == this ) + return 0; + + long nRet = 0; + + // Get comparison options + CmpOptions.eCmpMode = SW_MOD()->GetCompareMode(); + if( CmpOptions.eCmpMode == SwCompareMode::Auto ) + { + if( getRsidRoot() == rDoc.getRsidRoot() ) + { + CmpOptions.eCmpMode = SwCompareMode::ByChar; + CmpOptions.bUseRsid = true; + CmpOptions.nIgnoreLen = 2; + } + else + { + CmpOptions.eCmpMode = SwCompareMode::ByWord; + CmpOptions.bUseRsid = false; + CmpOptions.nIgnoreLen = 3; + } + } + else + { + CmpOptions.bUseRsid = getRsidRoot() == rDoc.getRsidRoot() && SW_MOD()->IsUseRsid(); + CmpOptions.nIgnoreLen = SW_MOD()->IsIgnorePieces() ? SW_MOD()->GetPieceLen() : 0; + } + + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + bool bDocWasModified = getIDocumentState().IsModified(); + SwDoc& rSrcDoc = const_cast(rDoc); + bool bSrcModified = rSrcDoc.getIDocumentState().IsModified(); + + RedlineFlags eSrcRedlMode = rSrcDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowInsert ); + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowInsert); + + Comparators aComparisons(buildComparators(rSrcDoc, *this)); + + for (auto& a : aComparisons) + { + CompareData& rD0 = *a.first; + CompareData& rD1 = *a.second; + rD1.CompareLines( rD0 ); + nRet |= rD1.ShowDiffs( rD0 ); + } + + if( nRet ) + { + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | + RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + for (auto& a : aComparisons) + { + CompareData& rD1 = *a.second; + rD1.SetRedlinesToDoc( !bDocWasModified ); + } + getIDocumentState().SetModified(); + } + + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( eSrcRedlMode ); + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + if( !bSrcModified ) + rSrcDoc.getIDocumentState().ResetModified(); + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return nRet; +} + +namespace +{ + struct SaveMergeRedline + { + const SwRangeRedline* pSrcRedl; + SwRangeRedline* pDestRedl; + SaveMergeRedline( const SwNode& rDstNd, const SwRangeRedline& rSrcRedl); + sal_uInt16 InsertRedline(SwPaM* pLastDestRedline); + }; +} + +SaveMergeRedline::SaveMergeRedline( const SwNode& rDstNd, + const SwRangeRedline& rSrcRedl) + : pSrcRedl( &rSrcRedl ) +{ + SwPosition aPos( rDstNd ); + + const SwPosition* pStt = rSrcRedl.Start(); + if( rDstNd.IsContentNode() ) + aPos.nContent.Assign( const_cast(static_cast(&rDstNd)), pStt->nContent.GetIndex() ); + pDestRedl = new SwRangeRedline( rSrcRedl.GetRedlineData(), aPos ); + + if( RedlineType::Delete == pDestRedl->GetType() ) + { + // mark the area as deleted + const SwPosition* pEnd = pStt == rSrcRedl.GetPoint() + ? rSrcRedl.GetMark() + : rSrcRedl.GetPoint(); + + pDestRedl->SetMark(); + pDestRedl->GetPoint()->nNode += pEnd->nNode.GetIndex() - + pStt->nNode.GetIndex(); + pDestRedl->GetPoint()->nContent.Assign( pDestRedl->GetContentNode(), + pEnd->nContent.GetIndex() ); + } +} + +sal_uInt16 SaveMergeRedline::InsertRedline(SwPaM* pLastDestRedline) +{ + sal_uInt16 nIns = 0; + SwDoc* pDoc = pDestRedl->GetDoc(); + + if( RedlineType::Insert == pDestRedl->GetType() ) + { + // the part was inserted so copy it from the SourceDoc + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodeIndex aSaveNd( pDestRedl->GetPoint()->nNode, -1 ); + const sal_Int32 nSaveCnt = pDestRedl->GetPoint()->nContent.GetIndex(); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + pSrcRedl->GetDoc()->getIDocumentContentOperations().CopyRange( + *const_cast(static_cast(pSrcRedl)), + *pDestRedl->GetPoint(), SwCopyFlags::CheckPosInFly); + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + pDestRedl->SetMark(); + ++aSaveNd; + pDestRedl->GetMark()->nNode = aSaveNd; + pDestRedl->GetMark()->nContent.Assign( aSaveNd.GetNode().GetContentNode(), + nSaveCnt ); + + if( pLastDestRedline && *pLastDestRedline->GetPoint() == *pDestRedl->GetPoint() ) + *pLastDestRedline->GetPoint() = *pDestRedl->GetMark(); + } + else + { + //JP 21.09.98: Bug 55909 + // If there already is a deleted or inserted one at the same position, we have to split it! + SwPosition* pDStt = pDestRedl->GetMark(), + * pDEnd = pDestRedl->GetPoint(); + SwRedlineTable::size_type n = 0; + + // find the first redline for StartPos + if( !pDoc->getIDocumentRedlineAccess().GetRedline( *pDStt, &n ) && n ) + --n; + + const SwRedlineTable& rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( ; n < rRedlineTable.size(); ++n ) + { + SwRangeRedline* pRedl = rRedlineTable[ n ]; + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark() + : pRedl->GetPoint(); + if( RedlineType::Delete == pRedl->GetType() || + RedlineType::Insert == pRedl->GetType() ) + { + SwComparePosition eCmpPos = ComparePosition( *pDStt, *pDEnd, *pRStt, *pREnd ); + switch( eCmpPos ) + { + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: + break; + + case SwComparePosition::Inside: + case SwComparePosition::Equal: + delete pDestRedl; + pDestRedl = nullptr; + [[fallthrough]]; + + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + n = rRedlineTable.size(); + break; + + case SwComparePosition::Outside: + assert(pDestRedl && "is this actually impossible"); + if (pDestRedl) + { + SwRangeRedline* pCpyRedl = new SwRangeRedline( + pDestRedl->GetRedlineData(), *pDStt ); + pCpyRedl->SetMark(); + *pCpyRedl->GetPoint() = *pRStt; + + std::unique_ptr pUndo; + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoCompDoc( *pCpyRedl )); + + // now modify doc: append redline, undo (and count) + pDoc->getIDocumentRedlineAccess().AppendRedline( pCpyRedl, true ); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + ++nIns; + + *pDStt = *pREnd; + + // we should start over now + n = SwRedlineTable::npos; + } + break; + + case SwComparePosition::OverlapBefore: + *pDEnd = *pRStt; + break; + + case SwComparePosition::OverlapBehind: + *pDStt = *pREnd; + break; + } + } + else if( *pDEnd <= *pRStt ) + break; + } + + } + + if( pDestRedl ) + { + std::unique_ptr pUndo; + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoCompDoc( *pDestRedl )); + + // now modify doc: append redline, undo (and count) + IDocumentRedlineAccess::AppendResult const result( + pDoc->getIDocumentRedlineAccess().AppendRedline(pDestRedl, true)); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + ++nIns; + + // if AppendRedline has deleted our redline, we may not keep a + // reference to it + if (IDocumentRedlineAccess::AppendResult::APPENDED != result) + pDestRedl = nullptr; + } + return nIns; +} + +/// Merge two documents +long SwDoc::MergeDoc( const SwDoc& rDoc ) +{ + if( &rDoc == this ) + return 0; + + long nRet = 0; + + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + SwDoc& rSrcDoc = const_cast(rDoc); + bool bSrcModified = rSrcDoc.getIDocumentState().IsModified(); + + RedlineFlags eSrcRedlMode = rSrcDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowDelete ); + getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowDelete ); + + CompareMainText aD0(rSrcDoc, false); + CompareMainText aD1(*this, false); + aD1.CompareLines( aD0 ); + if( !aD1.HasDiffs( aD0 ) ) + { + // we want to get all redlines from the SourceDoc + + // look for all insert redlines from the SourceDoc and determine their position in the DestDoc + std::vector vRedlines; + const SwRedlineTable& rSrcRedlTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable(); + sal_uLong nEndOfExtra = rSrcDoc.GetNodes().GetEndOfExtras().GetIndex(); + sal_uLong nMyEndOfExtra = GetNodes().GetEndOfExtras().GetIndex(); + for(const SwRangeRedline* pRedl : rSrcRedlTable) + { + sal_uLong nNd = pRedl->GetPoint()->nNode.GetIndex(); + RedlineType eType = pRedl->GetType(); + if( nEndOfExtra < nNd && + ( RedlineType::Insert == eType || RedlineType::Delete == eType )) + { + const SwNode* pDstNd = GetNodes()[ + nMyEndOfExtra + nNd - nEndOfExtra ]; + + // Found the position. + // Then we also have to insert the redline to the line in the DestDoc. + vRedlines.emplace_back(*pDstNd, *pRedl); + } + } + + if( !vRedlines.empty() ) + { + // Carry over all into DestDoc + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | + RedlineFlags::ShowInsert | + RedlineFlags::ShowDelete); + + SwPaM* pLastDestRedline(nullptr); + for(SaveMergeRedline& rRedline: vRedlines) + { + nRet += rRedline.InsertRedline(pLastDestRedline); + pLastDestRedline = rRedline.pDestRedl; + } + } + } + + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( eSrcRedlMode ); + if( !bSrcModified ) + rSrcDoc.getIDocumentState().ResetModified(); + + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return nRet; +} + +LineArrayComparator::LineArrayComparator( const CompareData &rD1, + const CompareData &rD2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ) + : m_rData1( rD1 ), m_rData2( rD2 ), m_nFirst1( nStt1 ), m_nFirst2( nStt2 ) +{ + m_nLen1 = nEnd1 - nStt1; + m_nLen2 = nEnd2 - nStt2; +} + +bool LineArrayComparator::Compare( int nIdx1, int nIdx2 ) const +{ + if( nIdx1 < 0 || nIdx2 < 0 || nIdx1 >= m_nLen1 || nIdx2 >= m_nLen2 ) + { + OSL_ENSURE( false, "Index out of range!" ); + return false; + } + + const SwTextNode *pTextNd1 = m_rData1.GetLine( m_nFirst1 + nIdx1 )->GetNode().GetTextNode(); + const SwTextNode *pTextNd2 = m_rData2.GetLine( m_nFirst2 + nIdx2 )->GetNode().GetTextNode(); + + if( !pTextNd1 || !pTextNd2 + || ( CmpOptions.bUseRsid && !pTextNd1->CompareParRsid( *pTextNd2 ) ) ) + { + return false; + } + + const sal_Int32 nPar1Len = pTextNd1->Len(); + const sal_Int32 nPar2Len = pTextNd2->Len(); + + if( std::min( nPar1Len, nPar2Len ) * 3 < std::max( nPar1Len, nPar2Len ) ) + { + return false; + } + + sal_Int32 nBorderLen = ( nPar1Len + nPar2Len )/16; + + if( nBorderLen < 3 ) + { + nBorderLen = std::min( 3, std::min( nPar1Len, nPar2Len ) ); + } + + std::set aHashes; + unsigned nHash = 0; + unsigned nMul = 251; + unsigned nPow = 1; + sal_Int32 i; + + for( i = 0; i < nBorderLen - 1; i++ ) + { + nPow *= nMul; + } + for( i = 0; i < nBorderLen; i++ ) + { + nHash = nHash*nMul + pTextNd1->GetText()[i]; + } + aHashes.insert( nHash ); + for( ; i < nPar1Len; i++ ) + { + nHash = nHash - nPow*pTextNd1->GetText()[ i - nBorderLen ]; + nHash = nHash*nMul + pTextNd1->GetText()[ i ]; + + aHashes.insert( nHash ); + } + + nHash = 0; + for( i = 0; i < nBorderLen; i++ ) + { + nHash = nHash*nMul + pTextNd2->GetText()[ i ]; + } + + if( aHashes.find( nHash ) != aHashes.end() ) + { + return true; + } + + for( ; i < nPar2Len; i++ ) + { + nHash = nHash - nPow*pTextNd2->GetText()[ i - nBorderLen ]; + nHash = nHash*nMul + pTextNd2->GetText()[ i ]; + if( aHashes.find( nHash ) != aHashes.end() ) + { + return true; + } + } + return false; +} + +bool CharArrayComparator::Compare( int nIdx1, int nIdx2 ) const +{ + if( nIdx1 < 0 || nIdx2 < 0 || nIdx1 >= GetLen1() || nIdx2 >= GetLen2() ) + { + OSL_ENSURE( false, "Index out of range!" ); + return false; + } + + return ( !CmpOptions.bUseRsid + || m_pTextNode1->CompareRsid( *m_pTextNode2, nIdx1 + 1, nIdx2 + 1 ) ) + && m_pTextNode1->GetText()[ nIdx1 ] == m_pTextNode2->GetText()[ nIdx2 ]; +} + +WordArrayComparator::WordArrayComparator( const SwTextNode *pNode1, + const SwTextNode *pNode2 ) + : m_pTextNode1( pNode1 ), m_pTextNode2( pNode2 ) +{ + m_pPos1.reset( new int[ m_pTextNode1->GetText().getLength() + 1 ] ); + m_pPos2.reset( new int[ m_pTextNode2->GetText().getLength() + 1 ] ); + + CalcPositions( m_pPos1.get(), m_pTextNode1, m_nCount1 ); + CalcPositions( m_pPos2.get(), m_pTextNode2, m_nCount2 ); +} + +bool WordArrayComparator::Compare( int nIdx1, int nIdx2 ) const +{ + int nLen = m_pPos1[ nIdx1 + 1 ] - m_pPos1[ nIdx1 ]; + if( nLen != m_pPos2[ nIdx2 + 1 ] - m_pPos2[ nIdx2 ] ) + { + return false; + } + for( int i = 0; i < nLen; i++) + { + if( m_pTextNode1->GetText()[ m_pPos1[ nIdx1 ] + i ] + != m_pTextNode2->GetText()[ m_pPos2[ nIdx2 ] + i ] + || ( CmpOptions.bUseRsid && !m_pTextNode1->CompareRsid( *m_pTextNode2, + m_pPos1[ nIdx1 ] + i, m_pPos2[ nIdx2 ] + i ) ) ) + { + return false; + } + } + return true; +} + +int WordArrayComparator::GetCharSequence( const int *pWordLcs1, + const int *pWordLcs2, int *pSubseq1, int *pSubseq2, int nLcsLen ) +{ + int nLen = 0; + for( int i = 0; i < nLcsLen; i++ ) + { + // Check for hash collisions + if( m_pPos1[ pWordLcs1[i] + 1 ] - m_pPos1[ pWordLcs1[i] ] + != m_pPos2[ pWordLcs2[i] + 1 ] - m_pPos2[ pWordLcs2[i] ] ) + { + continue; + } + for( int j = 0; j < m_pPos1[pWordLcs1[i]+1] - m_pPos1[pWordLcs1[i]]; j++) + { + pSubseq1[ nLen ] = m_pPos1[ pWordLcs1[i] ] + j; + pSubseq2[ nLen ] = m_pPos2[ pWordLcs2[i] ] + j; + + if( m_pTextNode1->GetText()[ m_pPos1[ pWordLcs1[i] ] + j ] + != m_pTextNode2->GetText()[ m_pPos2[ pWordLcs2[i] ] + j ] ) + { + nLen -= j; + break; + } + + nLen++; + } + } + return nLen; +} + +void WordArrayComparator::CalcPositions( int *pPos, const SwTextNode *pTextNd, + int &nCnt ) +{ + nCnt = -1; + for (int i = 0; i <= pTextNd->GetText().getLength(); ++i) + { + if (i == 0 || i == pTextNd->GetText().getLength() + || !rtl::isAsciiAlphanumeric( pTextNd->GetText()[ i - 1 ]) + || !rtl::isAsciiAlphanumeric( pTextNd->GetText()[ i ])) + { // Begin new word + nCnt++; + pPos[ nCnt ] = i; + } + } +} + +int CommonSubseq::FindLCS( int *pLcs1, int *pLcs2, int nStt1, int nEnd1, + int nStt2, int nEnd2 ) +{ + int nLen1 = nEnd1 ? nEnd1 - nStt1 : m_rComparator.GetLen1(); + int nLen2 = nEnd2 ? nEnd2 - nStt2 : m_rComparator.GetLen2(); + + assert( nLen1 >= 0 ); + assert( nLen2 >= 0 ); + + std::unique_ptr pLcs( new int*[ nLen1 + 1 ] ); + pLcs[ 0 ] = m_pData.get(); + + for( int i = 1; i < nLen1 + 1; i++ ) + pLcs[ i ] = pLcs[ i - 1 ] + nLen2 + 1; + + for( int i = 0; i <= nLen1; i++ ) + pLcs[i][0] = 0; + + for( int j = 0; j <= nLen2; j++ ) + pLcs[0][j] = 0; + + // Find lcs + for( int i = 1; i <= nLen1; i++ ) + { + for( int j = 1; j <= nLen2; j++ ) + { + if( m_rComparator.Compare( nStt1 + i - 1, nStt2 + j - 1 ) ) + pLcs[i][j] = pLcs[i - 1][j - 1] + 1; + else + pLcs[i][j] = std::max( pLcs[i][j - 1], pLcs[i - 1][j] ); + } + } + + int nLcsLen = pLcs[ nLen1 ][ nLen2 ]; + + // Recover the lcs in the two sequences + if( pLcs1 && pLcs2 ) + { + int nIdx1 = nLen1; + int nIdx2 = nLen2; + int nIdx = nLcsLen - 1; + + while( nIdx1 > 0 && nIdx2 > 0 ) + { + if( pLcs[ nIdx1 ][ nIdx2 ] == pLcs[ nIdx1 - 1 ][ nIdx2 ] ) + nIdx1--; + else if( pLcs[ nIdx1 ][ nIdx2 ] == pLcs[ nIdx1 ][ nIdx2 - 1 ] ) + nIdx2--; + else + { + nIdx1--; + nIdx2--; + pLcs1[ nIdx ] = nIdx1 + nStt1; + pLcs2[ nIdx ] = nIdx2 + nStt2; + nIdx--; + } + } + } + + return nLcsLen; +} + +int CommonSubseq::IgnoreIsolatedPieces( int *pLcs1, int *pLcs2, int nLen1, + int nLen2, int nLcsLen, int nPieceLen ) +{ + if( !nLcsLen ) + { + return 0; + } + + int nNext = 0; + + // Don't ignore text at the beginning of the paragraphs + if( pLcs1[ 0 ] == 0 && pLcs2[ 0 ] == 0 ) + { + while( nNext < nLcsLen - 1 && pLcs1[ nNext ] + 1 == pLcs1[ nNext + 1 ] + && pLcs2[ nNext ] + 1 == pLcs2[ nNext + 1 ] ) + { + nNext++; + } + nNext++; + } + + int nCnt = 1; + + for( int i = nNext; i < nLcsLen; i++ ) + { + if( i != nLcsLen - 1 && pLcs1[ i ] + 1 == pLcs1[ i + 1 ] + && pLcs2[ i ] + 1 == pLcs2[ i + 1 ] ) + { + nCnt++; + } + else + { + if( nCnt > nPieceLen + // Don't ignore text at the end of the paragraphs + || ( i == nLcsLen - 1 + && pLcs1[i] == nLen1 - 1 && pLcs2[i] == nLen2 - 1 )) + { + for( int j = i + 1 - nCnt; j <= i; j++ ) + { + pLcs2[ nNext ] = pLcs2[ j ]; + pLcs1[ nNext ] = pLcs1[ j ]; + nNext++; + } + } + nCnt = 1; + } + } + + return nNext; +} + +LgstCommonSubseq::LgstCommonSubseq( ArrayComparator &rComparator ) + : CommonSubseq( rComparator, CUTOFF ) +{ + m_pBuff1.reset( new int[ rComparator.GetLen2() + 1 ] ); + m_pBuff2.reset( new int[ rComparator.GetLen2() + 1 ] ); + + m_pL1.reset( new int[ rComparator.GetLen2() + 1 ] ); + m_pL2.reset( new int[ rComparator.GetLen2() + 1 ] ); +} + +void LgstCommonSubseq::FindL( int *pL, int nStt1, int nEnd1, + int nStt2, int nEnd2 ) +{ + int nLen1 = nEnd1 ? nEnd1 - nStt1 : m_rComparator.GetLen1(); + int nLen2 = nEnd2 ? nEnd2 - nStt2 : m_rComparator.GetLen2(); + + int *currL = m_pBuff1.get(); + int *prevL = m_pBuff2.get(); + + // Avoid memory corruption + if( nLen2 > m_rComparator.GetLen2() ) + { + assert( false ); + return; + } + + memset( m_pBuff1.get(), 0, sizeof( m_pBuff1[0] ) * ( nLen2 + 1 ) ); + memset( m_pBuff2.get(), 0, sizeof( m_pBuff2[0] ) * ( nLen2 + 1 ) ); + + // Find lcs + for( int i = 1; i <= nLen1; i++ ) + { + for( int j = 1; j <= nLen2; j++ ) + { + if( m_rComparator.Compare( nStt1 + i - 1, nStt2 + j - 1 ) ) + currL[j] = prevL[j - 1] + 1; + else + currL[j] = std::max( currL[j - 1], prevL[j] ); + } + int *tmp = currL; + currL = prevL; + prevL = tmp; + } + memcpy( pL, prevL, ( nLen2 + 1 ) * sizeof( *prevL ) ); +} + +int LgstCommonSubseq::HirschbergLCS( int *pLcs1, int *pLcs2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ) +{ + static int nLen1; + static int nLen2; + nLen1 = nEnd1 - nStt1; + nLen2 = nEnd2 - nStt2; + + if( ( nLen1 + 1 ) * ( nLen2 + 1 ) <= CUTOFF ) + { + if( !nLen1 || !nLen2 ) + { + return 0; + } + return FindLCS(pLcs1, pLcs2, nStt1, nEnd1, nStt2, nEnd2); + } + + int nMid = nLen1/2; + + FindL( m_pL1.get(), nStt1, nStt1 + nMid, nStt2, nEnd2 ); + FindL( m_pL2.get(), nStt1 + nMid, nEnd1, nStt2, nEnd2 ); + + int nMaxPos = 0; + static int nMaxVal; + nMaxVal = -1; + + static int i; + for( i = 0; i <= nLen2; i++ ) + { + if( m_pL1[i] + ( m_pL2[nLen2] - m_pL2[i] ) > nMaxVal ) + { + nMaxPos = i; + nMaxVal = m_pL1[i]+( m_pL2[nLen2] - m_pL2[i] ); + } + } + + int nRet = HirschbergLCS( pLcs1, pLcs2, nStt1, nStt1 + nMid, + nStt2, nStt2 + nMaxPos ); + nRet += HirschbergLCS( pLcs1 + nRet, pLcs2 + nRet, nStt1 + nMid, nEnd1, + nStt2 + nMaxPos, nEnd2 ); + + return nRet; +} + +int LgstCommonSubseq::Find( int *pSubseq1, int *pSubseq2 ) +{ + int nStt = 0; + int nCutEnd = 0; + int nEnd1 = m_rComparator.GetLen1(); + int nEnd2 = m_rComparator.GetLen2(); + + // Check for corresponding lines in the beginning of the sequences + while( nStt < nEnd1 && nStt < nEnd2 && m_rComparator.Compare( nStt, nStt ) ) + { + pSubseq1[ nStt ] = nStt; + pSubseq2[ nStt ] = nStt; + nStt++; + } + + pSubseq1 += nStt; + pSubseq2 += nStt; + + // Check for corresponding lines in the end of the sequences + while( nStt < nEnd1 && nStt < nEnd2 + && m_rComparator.Compare( nEnd1 - 1, nEnd2 - 1 ) ) + { + nCutEnd++; + nEnd1--; + nEnd2--; + } + + int nLen = HirschbergLCS( pSubseq1, pSubseq2, nStt, nEnd1, nStt, nEnd2 ); + + for( int i = 0; i < nCutEnd; i++ ) + { + pSubseq1[ nLen + i ] = nEnd1 + i; + pSubseq2[ nLen + i ] = nEnd2 + i; + } + + return nStt + nLen + nCutEnd; +} + +int FastCommonSubseq::FindFastCS( int *pSeq1, int *pSeq2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ) +{ + int nCutBeg = 0; + int nCutEnd = 0; + + // Check for corresponding lines in the beginning of the sequences + while( nStt1 < nEnd1 && nStt2 < nEnd2 && m_rComparator.Compare( nStt1, nStt2 ) ) + { + pSeq1[ nCutBeg ] = nStt1++; + pSeq2[ nCutBeg ] = nStt2++; + nCutBeg++; + } + + pSeq1 += nCutBeg; + pSeq2 += nCutBeg; + + // Check for corresponding lines in the end of the sequences + while( nStt1 < nEnd1 && nStt2 < nEnd2 + && m_rComparator.Compare( nEnd1 - 1, nEnd2 - 1 ) ) + { + nCutEnd++; + nEnd1--; + nEnd2--; + } + + int nLen1 = nEnd1 - nStt1; + int nLen2 = nEnd2 - nStt2; + + // Return if a sequence is empty + if( nLen1 <= 0 || nLen2 <= 0 ) + { + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ i ] = nEnd1 + i; + pSeq2[ i ] = nEnd2 + i; + } + return nCutBeg + nCutEnd; + } + + // Cut to LCS for small values + if( nLen1 < 3 || nLen2 < 3 || ( nLen1 + 1 ) * ( nLen2 + 1 ) <= CUTOFF ) + { + int nLcsLen = FindLCS( pSeq1, pSeq2, nStt1, nEnd1, nStt2, nEnd2); + + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ nLcsLen + i ] = nEnd1 + i; + pSeq2[ nLcsLen + i ] = nEnd2 + i; + } + return nCutBeg + nLcsLen + nCutEnd; + } + + int nMid1 = nLen1/2; + int nMid2 = nLen2/2; + + int nRad; + int nPos1 = -1, nPos2 = -1; + + // Find a point of correspondence in the middle of the sequences + for( nRad = 0; nRad*nRad < std::min( nMid1, nMid2 ); nRad++ ) + { + // Search to the left and to the right of the middle of the first sequence + for( int i = nMid1 - nRad; i <= nMid1 + nRad; i++ ) + { + if( m_rComparator.Compare( nStt1 + i, nStt2 + nMid2 - nRad ) ) + { + nPos1 = nStt1 + i; + nPos2 = nStt2 + nMid2 - nRad; + break; + } + if( m_rComparator.Compare( nStt1 + i, nStt2 + nMid2 + nRad ) ) + { + nPos1 = nStt1 + i; + nPos2 = nStt2 + nMid2 - nRad; + break; + } + } + // Search to the left and to the right of the middle of the second sequence + for( int i = nMid2 - nRad; i <= nMid2 + nRad; i++ ) + { + if( m_rComparator.Compare( nStt2 + nMid2 - nRad, nStt2 + i ) ) + { + nPos2 = nStt2 + i; + nPos1 = nStt1 + nMid1 - nRad; + break; + } + if( m_rComparator.Compare( nStt2 + nMid2 - nRad, nStt2 + i ) ) + { + nPos2 = nStt2 + i; + nPos1 = nStt1 + nMid1 - nRad; + break; + } + } + } + + // return if no point of correspondence found + if( nPos1 == -1 ) + { + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ i ] = nEnd1 + i; + pSeq2[ i ] = nEnd2 + i; + } + return nCutBeg + nCutEnd; + } + + // Run the same on the sequences to the left of the correspondence point + int nLen = FindFastCS( pSeq1, pSeq2, nStt1, nPos1, nStt2, nPos2 ); + + pSeq1[ nLen ] = nPos1; + pSeq2[ nLen ] = nPos2; + + // Run the same on the sequences to the right of the correspondence point + nLen += FindFastCS( pSeq1 + nLen + 1, pSeq2 + nLen + 1, + nPos1 + 1, nEnd1, nPos2 + 1, nEnd2 ) + 1; + + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ nLen + i ] = nEnd1 + i; + pSeq2[ nLen + i ] = nEnd2 + i; + } + + return nLen + nCutBeg + nCutEnd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doccorr.cxx b/sw/source/core/doc/doccorr.cxx new file mode 100644 index 000000000..41f7b673d --- /dev/null +++ b/sw/source/core/doc/doccorr.cxx @@ -0,0 +1,364 @@ +/* -*- 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 + +namespace +{ + /// find the relevant section in which the SwUnoCursor may wander. + /// returns NULL if no restrictions apply + const SwStartNode* lcl_FindUnoCursorSection( const SwNode& rNode ) + { + const SwStartNode* pStartNode = rNode.StartOfSectionNode(); + while( ( pStartNode != nullptr ) && + ( pStartNode->StartOfSectionNode() != pStartNode ) && + ( pStartNode->GetStartNodeType() == SwNormalStartNode ) ) + pStartNode = pStartNode->StartOfSectionNode(); + + return pStartNode; + } + + bool lcl_PosCorrAbs(SwPosition & rPos, + const SwPosition& rStart, + const SwPosition& rEnd, + const SwPosition& rNewPos) + { + if ((rStart <= rPos) && (rPos <= rEnd)) + { + rPos = rNewPos; + return true; + } + return false; + }; + + bool lcl_PaMCorrAbs(SwPaM & rPam, + const SwPosition& rStart, + const SwPosition& rEnd, + const SwPosition& rNewPos) + { + bool bRet = false; + bRet |= lcl_PosCorrAbs(rPam.GetBound(), rStart, rEnd, rNewPos); + bRet |= lcl_PosCorrAbs(rPam.GetBound(false), rStart, rEnd, rNewPos); + return bRet; + }; + + void lcl_PaMCorrRel1(SwPaM * pPam, + SwNode const * const pOldNode, + const SwPosition& rNewPos, + const sal_Int32 nCntIdx) + { + for(int nb = 0; nb < 2; ++nb) + if(&(pPam->GetBound(bool(nb)).nNode.GetNode()) == pOldNode) + { + pPam->GetBound(bool(nb)).nNode = rNewPos.nNode; + pPam->GetBound(bool(nb)).nContent.Assign( + const_cast(rNewPos.nContent.GetIdxReg()), + nCntIdx + pPam->GetBound(bool(nb)).nContent.GetIndex()); + } + } +} + +void PaMCorrAbs( const SwPaM& rRange, + const SwPosition& rNewPos ) +{ + SwPosition const aStart( *rRange.Start() ); + SwPosition const aEnd( *rRange.End() ); + SwPosition const aNewPos( rNewPos ); + SwDoc *const pDoc = aStart.nNode.GetNode().GetDoc(); + SwCursorShell *const pShell = pDoc->GetEditShell(); + + if( pShell ) + { + for(const SwViewShell& rShell : pShell->GetRingContainer()) + { + if(dynamic_cast(&rShell) == nullptr) + continue; + const SwCursorShell* pCursorShell = static_cast(&rShell); + SwPaM *_pStackCursor = pCursorShell->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_PaMCorrAbs( *_pStackCursor, aStart, aEnd, aNewPos ); + if( !_pStackCursor ) + break; + _pStackCursor = _pStackCursor->GetNext(); + if( _pStackCursor == pCursorShell->GetStackCursor() ) + break; + } + + for(SwPaM& rPaM : const_cast(pCursorShell->GetCursor_())->GetRingContainer()) + { + lcl_PaMCorrAbs( rPaM, aStart, aEnd, aNewPos ); + } + + if( pCursorShell->IsTableMode() ) + lcl_PaMCorrAbs( const_cast(*pCursorShell->GetTableCrs()), aStart, aEnd, aNewPos ); + } + } + + pDoc->cleanupUnoCursorTable(); + for(const auto& pWeakUnoCursor : pDoc->mvUnoCursorTable) + { + auto pUnoCursor(pWeakUnoCursor.lock()); + if(!pUnoCursor) + continue; + + bool bChange = false; // has the UNO cursor been corrected? + + // determine whether the UNO cursor will leave it's designated + // section + bool const bLeaveSection = + pUnoCursor->IsRemainInSection() && + ( lcl_FindUnoCursorSection( aNewPos.nNode.GetNode() ) != + lcl_FindUnoCursorSection( + pUnoCursor->GetPoint()->nNode.GetNode() ) ); + + for(SwPaM& rPaM : pUnoCursor->GetRingContainer()) + { + bChange |= lcl_PaMCorrAbs( rPaM, aStart, aEnd, aNewPos ); + } + + SwUnoTableCursor *const pUnoTableCursor = + dynamic_cast(pUnoCursor.get()); + if( pUnoTableCursor ) + { + for(SwPaM& rPaM : pUnoTableCursor->GetSelRing().GetRingContainer()) + { + bChange |= + lcl_PaMCorrAbs( rPaM, aStart, aEnd, aNewPos ); + } + } + + // if a UNO cursor leaves its designated section, we must inform + // (and invalidate) said cursor + if (bChange && bLeaveSection) + { + // the UNO cursor has left its section. We need to notify it! + sw::UnoCursorHint aHint; + pUnoCursor->m_aNotifier.Broadcast(aHint); + } + } +} + +void SwDoc::CorrAbs(const SwNodeIndex& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset, + bool bMoveCursor) +{ + SwContentNode *const pContentNode( rOldNode.GetNode().GetContentNode() ); + SwPaM const aPam(rOldNode, 0, + rOldNode, pContentNode ? pContentNode->Len() : 0); + SwPosition aNewPos(rNewPos); + aNewPos.nContent += nOffset; + + getIDocumentMarkAccess()->correctMarksAbsolute(rOldNode, rNewPos, nOffset); + // fix redlines + { + SwRedlineTable& rTable = getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type n = 0; n < rTable.size(); ) + { + // is on position ?? + SwRangeRedline *const pRedline( rTable[ n ] ); + bool const bChanged = + lcl_PaMCorrAbs(*pRedline, *aPam.Start(), *aPam.End(), aNewPos); + // clean up empty redlines: docredln.cxx asserts these as invalid + if (bChanged && (*pRedline->GetPoint() == *pRedline->GetMark()) + && (pRedline->GetContentIdx() == nullptr)) + { + rTable.DeleteAndDestroy(n); + } + else + { + ++n; + } + } + + // To-Do - need to add here 'SwExtraRedlineTable' also ? + } + + if(bMoveCursor) + { + ::PaMCorrAbs(aPam, aNewPos); + } +} + +void SwDoc::CorrAbs( + const SwPaM& rRange, + const SwPosition& rNewPos, + bool bMoveCursor ) +{ + SwPosition aStart(*rRange.Start()); + SwPosition aEnd(*rRange.End()); + + DelBookmarks( aStart.nNode, aEnd.nNode, nullptr, &aStart.nContent, &aEnd.nContent ); + + if(bMoveCursor) + ::PaMCorrAbs(rRange, rNewPos); +} + +void SwDoc::CorrAbs( + const SwNodeIndex& rStartNode, + const SwNodeIndex& rEndNode, + const SwPosition& rNewPos, + bool bMoveCursor ) +{ + DelBookmarks( rStartNode, rEndNode ); + + if(bMoveCursor) + { + SwContentNode *const pContentNode( rEndNode.GetNode().GetContentNode() ); + SwPaM const aPam(rStartNode, 0, + rEndNode, pContentNode ? pContentNode->Len() : 0); + ::PaMCorrAbs(aPam, rNewPos); + } +} + +void PaMCorrRel( const SwNodeIndex &rOldNode, + const SwPosition &rNewPos, + const sal_Int32 nOffset ) +{ + const SwNode* pOldNode = &rOldNode.GetNode(); + SwPosition aNewPos( rNewPos ); + const SwDoc* pDoc = pOldNode->GetDoc(); + + const sal_Int32 nCntIdx = rNewPos.nContent.GetIndex() + nOffset; + + SwCursorShell const* pShell = pDoc->GetEditShell(); + if( pShell ) + { + for(const SwViewShell& rShell : pShell->GetRingContainer()) + { + if(dynamic_cast(&rShell) == nullptr) + continue; + SwCursorShell* pCursorShell = const_cast(static_cast(&rShell)); + SwPaM *_pStackCursor = pCursorShell->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_PaMCorrRel1( _pStackCursor, pOldNode, aNewPos, nCntIdx ); + if( !_pStackCursor ) + break; + _pStackCursor = _pStackCursor->GetNext(); + if( _pStackCursor == pCursorShell->GetStackCursor() ) + break; + } + + SwPaM* pStartPaM = pCursorShell->GetCursor_(); + for(SwPaM& rPaM : pStartPaM->GetRingContainer()) + { + lcl_PaMCorrRel1( &rPaM, pOldNode, aNewPos, nCntIdx); + } + + if( pCursorShell->IsTableMode() ) + lcl_PaMCorrRel1( pCursorShell->GetTableCrs(), pOldNode, aNewPos, nCntIdx ); + } + } + + pDoc->cleanupUnoCursorTable(); + for(const auto& pWeakUnoCursor : pDoc->mvUnoCursorTable) + { + auto pUnoCursor(pWeakUnoCursor.lock()); + if(!pUnoCursor) + continue; + for(SwPaM& rPaM : pUnoCursor->GetRingContainer()) + { + lcl_PaMCorrRel1( &rPaM, pOldNode, aNewPos, nCntIdx ); + } + + SwUnoTableCursor* pUnoTableCursor = + dynamic_cast(pUnoCursor.get()); + if( pUnoTableCursor ) + { + for(SwPaM& rPaM : pUnoTableCursor->GetSelRing().GetRingContainer()) + { + lcl_PaMCorrRel1( &rPaM, pOldNode, aNewPos, nCntIdx ); + } + } + } +} + +void SwDoc::CorrRel(const SwNodeIndex& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset, + bool bMoveCursor) +{ + getIDocumentMarkAccess()->correctMarksRelative(rOldNode, rNewPos, nOffset); + + { // fix the Redlines + SwRedlineTable& rTable = getIDocumentRedlineAccess().GetRedlineTable(); + SwPosition aNewPos(rNewPos); + for(SwRangeRedline* p : rTable) + { + // lies on the position ?? + lcl_PaMCorrRel1( p, &rOldNode.GetNode(), aNewPos, aNewPos.nContent.GetIndex() + nOffset ); + } + + // To-Do - need to add here 'SwExtraRedlineTable' also ? + } + + if(bMoveCursor) + ::PaMCorrRel(rOldNode, rNewPos, nOffset); +} + +SwEditShell const * SwDoc::GetEditShell() const +{ + SwViewShell const *pCurrentView = getIDocumentLayoutAccess().GetCurrentViewShell(); + // Layout and OLE shells should be available + if( pCurrentView ) + { + for(const SwViewShell& rCurrentSh : pCurrentView->GetRingContainer()) + { + // look for an EditShell (if it exists) + if( dynamic_cast(&rCurrentSh) != nullptr ) + { + return static_cast(&rCurrentSh); + } + } + } + return nullptr; +} + +SwEditShell* SwDoc::GetEditShell() +{ + return const_cast( const_cast( this )->GetEditShell() ); +} + +::sw::IShellCursorSupplier * SwDoc::GetIShellCursorSupplier() +{ + return GetEditShell(); +} + +//bool foo() +//{ +// bool b1 = true ? true : false; +// bool b2 = (true ? true : false) ? true : false; +// bool b3 = true ? (true ? true : false) : false; +// bool b4 = true ? true : (true ? true : false); +// return false; +//} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docdesc.cxx b/sw/source/core/doc/docdesc.cxx new file mode 100644 index 000000000..51dc930f0 --- /dev/null +++ b/sw/source/core/doc/docdesc.cxx @@ -0,0 +1,922 @@ +/* -*- 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 + +using namespace com::sun::star; + +static void lcl_DefaultPageFormat( sal_uInt16 nPoolFormatId, + SwFrameFormat &rFormat1, + SwFrameFormat &rFormat2, + SwFrameFormat &rFormat3, + SwFrameFormat &rFormat4) +{ + // --> #i41075# Printer on demand + // This function does not require a printer anymore. + // The default page size is obtained from the application + //locale + + SwFormatFrameSize aFrameSize( SwFrameSize::Fixed ); + const Size aPhysSize = SvxPaperInfo::GetDefaultPaperSize(); + aFrameSize.SetSize( aPhysSize ); + + // Prepare for default margins. + // Margins have a default minimum size. + // If the printer forces a larger margins, that's ok too. + // The HTML page desc had A4 as page size always. + // This has been changed to take the page size from the printer. + // Unfortunately, the margins of the HTML page desc are smaller than + // the margins used here in general, so one extra case is required. + // In the long term, this needs to be changed to always keep the + // margins from the page desc. + sal_Int32 nMinTop, nMinBottom, nMinLeft, nMinRight; + if( RES_POOLPAGE_HTML == nPoolFormatId ) + { + nMinRight = nMinTop = nMinBottom = GetMetricVal( CM_1 ); + nMinLeft = nMinRight * 2; + } + else if (!utl::ConfigManager::IsFuzzing() && MeasurementSystem::Metric == SvtSysLocale().GetLocaleData().getMeasurementSystemEnum() ) + { + nMinTop = nMinBottom = nMinLeft = nMinRight = 1134; // 2 centimeters + } + else + { + nMinTop = nMinBottom = 1440; // as in MS Word: 1 Inch + nMinLeft = nMinRight = 1800; // 1,25 Inch + } + + // set margins + SvxLRSpaceItem aLR( RES_LR_SPACE ); + SvxULSpaceItem aUL( RES_UL_SPACE ); + + aUL.SetUpper( static_cast(nMinTop) ); + aUL.SetLower( static_cast(nMinBottom) ); + aLR.SetRight( nMinRight ); + aLR.SetLeft( nMinLeft ); + + rFormat1.SetFormatAttr( aFrameSize ); + rFormat1.SetFormatAttr( aLR ); + rFormat1.SetFormatAttr( aUL ); + + rFormat2.SetFormatAttr( aFrameSize ); + rFormat2.SetFormatAttr( aLR ); + rFormat2.SetFormatAttr( aUL ); + + rFormat3.SetFormatAttr( aFrameSize ); + rFormat3.SetFormatAttr( aLR ); + rFormat3.SetFormatAttr( aUL ); + + rFormat4.SetFormatAttr( aFrameSize ); + rFormat4.SetFormatAttr( aLR ); + rFormat4.SetFormatAttr( aUL ); +} + +static void lcl_DescSetAttr( const SwFrameFormat &rSource, SwFrameFormat &rDest, + const bool bPage = true ) +{ + // We should actually use ItemSet's Intersect here, but that doesn't work + // correctly if we have different WhichRanges. + + // Take over the attributes which are of interest. + sal_uInt16 const aIdArr[] = { + RES_FRM_SIZE, RES_UL_SPACE, // [83..86 + RES_BACKGROUND, RES_SHADOW, // [99..101 + RES_COL, RES_COL, // [103 + RES_TEXTGRID, RES_TEXTGRID, // [109 + RES_FRAMEDIR, RES_FRAMEDIR, // [114 + RES_HEADER_FOOTER_EAT_SPACING, RES_HEADER_FOOTER_EAT_SPACING, // [115 + RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER, // [143 + + // take over DrawingLayer FillStyles + XATTR_FILL_FIRST, XATTR_FILL_LAST, // [1014 + + 0}; + + const SfxPoolItem* pItem; + for( sal_uInt16 n = 0; aIdArr[ n ]; n += 2 ) + { + for( sal_uInt16 nId = aIdArr[ n ]; nId <= aIdArr[ n+1]; ++nId ) + { + // #i45539# + // bPage == true: + // All in aIdArr except from RES_HEADER_FOOTER_EAT_SPACING + // bPage == false: + // All in aIdArr except from RES_COL and RES_PAPER_BIN: + bool bExecuteId(true); + + if(bPage) + { + // When Page + switch(nId) + { + // All in aIdArr except from RES_HEADER_FOOTER_EAT_SPACING + case RES_HEADER_FOOTER_EAT_SPACING: + // take out SvxBrushItem; it's the result of the fallback + // at SwFormat::GetItemState and not really in state SfxItemState::SET + case RES_BACKGROUND: + bExecuteId = false; + break; + default: + break; + } + } + else + { + // When not Page + switch(nId) + { + // When not Page: All in aIdArr except from RES_COL and RES_PAPER_BIN: + case RES_COL: + case RES_PAPER_BIN: + bExecuteId = false; + break; + default: + break; + } + } + + if(bExecuteId) + { + if (SfxItemState::SET == rSource.GetItemState(nId, false, &pItem)) + { + rDest.SetFormatAttr(*pItem); + } + else + { + rDest.ResetFormatAttr(nId); + } + } + } + } + + // Transmit pool and help IDs too + rDest.SetPoolFormatId( rSource.GetPoolFormatId() ); + rDest.SetPoolHelpId( rSource.GetPoolHelpId() ); + rDest.SetPoolHlpFileId( rSource.GetPoolHlpFileId() ); +} + +namespace +{ + SwFrameFormat& getFrameFormat(SwPageDesc &rDesc, bool bLeft, bool bFirst) + { + if (bFirst) + { + if (bLeft) + return rDesc.GetFirstLeft(); + return rDesc.GetFirstMaster(); + } + return rDesc.GetLeft(); + } + + const SwFrameFormat& getConstFrameFormat(const SwPageDesc &rDesc, bool bLeft, bool bFirst) + { + return getFrameFormat(const_cast(rDesc), bLeft, bFirst); + } +} + +void SwDoc::CopyMasterHeader(const SwPageDesc &rChged, const SwFormatHeader &rHead, SwPageDesc &rDesc, bool bLeft, bool bFirst) +{ + assert(bLeft || bFirst); + SwFrameFormat& rDescFrameFormat = getFrameFormat(rDesc, bLeft, bFirst); + if (bFirst && bLeft) + { + // special case: always shared with something + rDescFrameFormat.SetFormatAttr( rChged.IsFirstShared() + ? rDesc.GetLeft().GetHeader() + : rDesc.GetFirstMaster().GetHeader()); + } + else if ((bFirst ? rChged.IsFirstShared() : rChged.IsHeaderShared()) + || !rHead.IsActive()) + { + // Left or first shares the header with the Master. + rDescFrameFormat.SetFormatAttr( rDesc.GetMaster().GetHeader() ); + } + else if ( rHead.IsActive() ) + { // Left or first gets its own header if the Format doesn't already have one. + // If it already has one and it points to the same Section as the + // Right one, it needs to get an own Header. + // The content is evidently copied. + const SwFormatHeader &rFormatHead = rDescFrameFormat.GetHeader(); + if ( !rFormatHead.IsActive() ) + { + SwFormatHeader aHead( getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::HEADERL, nullptr ) ); + rDescFrameFormat.SetFormatAttr( aHead ); + // take over additional attributes (margins, borders ...) + ::lcl_DescSetAttr( *rHead.GetHeaderFormat(), *aHead.GetHeaderFormat(), false); + } + else + { + const SwFrameFormat *pRight = rHead.GetHeaderFormat(); + const SwFormatContent &aRCnt = pRight->GetContent(); + const SwFormatContent &aCnt = rFormatHead.GetHeaderFormat()->GetContent(); + + if (!aCnt.GetContentIdx()) + { + const SwFrameFormat& rChgedFrameFormat = getConstFrameFormat(rChged, bLeft, bFirst); + rDescFrameFormat.SetFormatAttr( rChgedFrameFormat.GetHeader() ); + } + else if ((*aRCnt.GetContentIdx() == *aCnt.GetContentIdx()) || + // The ContentIdx is _always_ different when called from + // SwDocStyleSheet::SetItemSet, because it deep-copies the + // PageDesc. So check if it was previously shared. + (bFirst ? rDesc.IsFirstShared() : rDesc.IsHeaderShared())) + { + SwFrameFormat *pFormat = new SwFrameFormat( GetAttrPool(), + bFirst ? "First header" : "Left header", + GetDfltFrameFormat() ); + ::lcl_DescSetAttr( *pRight, *pFormat, false ); + // The section which the right header attribute is pointing + // is copied, and the Index to the StartNode is set to + // the left or first header attribute. + SwNodeIndex aTmp( GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aTmp, SwHeaderStartNode ); + SwNodeRange aRange( aRCnt.GetContentIdx()->GetNode(), 0, + *aRCnt.GetContentIdx()->GetNode().EndOfSectionNode() ); + aTmp = *pSttNd->EndOfSectionNode(); + GetNodes().Copy_( aRange, aTmp, false ); + aTmp = *pSttNd; + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp); + SwPaM const source(aRange.aStart, aRange.aEnd); + SwPosition dest(aTmp); + sw::CopyBookmarks(source, dest); + pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); + rDescFrameFormat.SetFormatAttr( SwFormatHeader( pFormat ) ); + } + else + ::lcl_DescSetAttr( *pRight, + *const_cast(rFormatHead.GetHeaderFormat()), false ); + } + } +} + +void SwDoc::CopyMasterFooter(const SwPageDesc &rChged, const SwFormatFooter &rFoot, SwPageDesc &rDesc, bool bLeft, bool bFirst) +{ + assert(bLeft || bFirst); + SwFrameFormat& rDescFrameFormat = getFrameFormat(rDesc, bLeft, bFirst); + if (bFirst && bLeft) + { + // special case: always shared with something + rDescFrameFormat.SetFormatAttr( rChged.IsFirstShared() + ? rDesc.GetLeft().GetFooter() + : rDesc.GetFirstMaster().GetFooter()); + } + else if ((bFirst ? rChged.IsFirstShared() : rChged.IsFooterShared()) + || !rFoot.IsActive()) + { + // Left or first shares the Header with the Master. + rDescFrameFormat.SetFormatAttr( rDesc.GetMaster().GetFooter() ); + } + else if ( rFoot.IsActive() ) + { // Left or first gets its own Footer if the Format does not already have one. + // If the Format already has a Footer and it points to the same section as the Right one, + // it needs to get an own one. + // The content is evidently copied. + const SwFormatFooter &rFormatFoot = rDescFrameFormat.GetFooter(); + if ( !rFormatFoot.IsActive() ) + { + SwFormatFooter aFoot( getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::FOOTER, nullptr ) ); + rDescFrameFormat.SetFormatAttr( aFoot ); + // Take over additional attributes (margins, borders ...). + ::lcl_DescSetAttr( *rFoot.GetFooterFormat(), *aFoot.GetFooterFormat(), false); + } + else + { + const SwFrameFormat *pRight = rFoot.GetFooterFormat(); + const SwFormatContent &aRCnt = pRight->GetContent(); + const SwFormatContent &aLCnt = rFormatFoot.GetFooterFormat()->GetContent(); + if( !aLCnt.GetContentIdx() ) + { + const SwFrameFormat& rChgedFrameFormat = getConstFrameFormat(rChged, bLeft, bFirst); + rDescFrameFormat.SetFormatAttr( rChgedFrameFormat.GetFooter() ); + } + else if ((*aRCnt.GetContentIdx() == *aLCnt.GetContentIdx()) || + // The ContentIdx is _always_ different when called from + // SwDocStyleSheet::SetItemSet, because it deep-copies the + // PageDesc. So check if it was previously shared. + (bFirst ? rDesc.IsFirstShared() : rDesc.IsFooterShared())) + { + SwFrameFormat *pFormat = new SwFrameFormat( GetAttrPool(), + bFirst ? "First footer" : "Left footer", + GetDfltFrameFormat() ); + ::lcl_DescSetAttr( *pRight, *pFormat, false ); + // The section to which the right footer attribute is pointing + // is copied, and the Index to the StartNode is set to + // the left footer attribute. + SwNodeIndex aTmp( GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aTmp, SwFooterStartNode ); + SwNodeRange aRange( aRCnt.GetContentIdx()->GetNode(), 0, + *aRCnt.GetContentIdx()->GetNode().EndOfSectionNode() ); + aTmp = *pSttNd->EndOfSectionNode(); + GetNodes().Copy_( aRange, aTmp, false ); + aTmp = *pSttNd; + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp); + SwPaM const source(aRange.aStart, aRange.aEnd); + SwPosition dest(aTmp); + sw::CopyBookmarks(source, dest); + pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); + rDescFrameFormat.SetFormatAttr( SwFormatFooter( pFormat ) ); + } + else + ::lcl_DescSetAttr( *pRight, + *const_cast(rFormatFoot.GetFooterFormat()), false ); + } + } +} + +void SwDoc::ChgPageDesc( size_t i, const SwPageDesc &rChged ) +{ + assert(i < m_PageDescs.size() && "PageDescs is out of range."); + + SwPageDesc& rDesc = *m_PageDescs[i]; + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(rDesc, rChged, this)); + } + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Mirror at first if needed. + if ( rChged.GetUseOn() == UseOnPage::Mirror ) + const_cast(rChged).Mirror(); + else + { + // Or else transfer values from Master to Left + ::lcl_DescSetAttr(rChged.GetMaster(), + const_cast(rChged).GetLeft()); + } + ::lcl_DescSetAttr(rChged.GetMaster(), + const_cast(rChged).GetFirstMaster()); + ::lcl_DescSetAttr(rChged.GetLeft(), + const_cast(rChged).GetFirstLeft()); + + // Take over NumType. + if( rChged.GetNumType().GetNumberingType() != rDesc.GetNumType().GetNumberingType() ) + { + rDesc.SetNumType( rChged.GetNumType() ); + // Notify page number fields that NumFormat has changed + getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::PageNumber )->UpdateFields(); + getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::RefPageGet )->UpdateFields(); + + // If the numbering scheme has changed we could have QuoVadis/ErgoSum texts + // that refer to a changed page, so we invalidate foot notes. + SwFootnoteIdxs& rFootnoteIdxs = GetFootnoteIdxs(); + for( SwFootnoteIdxs::size_type nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + + // Take over orientation + rDesc.SetLandscape( rChged.GetLandscape() ); + + // #i46909# no undo if header or footer changed + bool bHeaderFooterChanged = false; + + // Synch header. + const SwFormatHeader &rHead = rChged.GetMaster().GetHeader(); + if (undoGuard.UndoWasEnabled()) + { + // #i46909# no undo if header or footer changed + // Did something change in the nodes? + const SwFormatHeader &rOldHead = rDesc.GetMaster().GetHeader(); + bHeaderFooterChanged |= + ( rHead.IsActive() != rOldHead.IsActive() || + rChged.IsHeaderShared() != rDesc.IsHeaderShared() || + rChged.IsFirstShared() != rDesc.IsFirstShared() ); + } + rDesc.GetMaster().SetFormatAttr( rHead ); + CopyMasterHeader(rChged, rHead, rDesc, true, false); // Copy left header + CopyMasterHeader(rChged, rHead, rDesc, false, true); // Copy first master + CopyMasterHeader(rChged, rHead, rDesc, true, true); // Copy first left + rDesc.ChgHeaderShare( rChged.IsHeaderShared() ); + + // Synch Footer. + const SwFormatFooter &rFoot = rChged.GetMaster().GetFooter(); + if (undoGuard.UndoWasEnabled()) + { + // #i46909# no undo if header or footer changed + // Did something change in the Nodes? + const SwFormatFooter &rOldFoot = rDesc.GetMaster().GetFooter(); + bHeaderFooterChanged |= + ( rFoot.IsActive() != rOldFoot.IsActive() || + rChged.IsFooterShared() != rDesc.IsFooterShared() ); + } + rDesc.GetMaster().SetFormatAttr( rFoot ); + CopyMasterFooter(rChged, rFoot, rDesc, true, false); // Copy left footer + CopyMasterFooter(rChged, rFoot, rDesc, false, true); // Copy first master + CopyMasterFooter(rChged, rFoot, rDesc, true, true); // Copy first left + rDesc.ChgFooterShare( rChged.IsFooterShared() ); + // there is just one first shared flag for both header and footer? + rDesc.ChgFirstShare( rChged.IsFirstShared() ); + + if ( rDesc.GetName() != rChged.GetName() ) + rDesc.SetName( rChged.GetName() ); + + // A RegisterChange is triggered, if necessary + rDesc.SetRegisterFormatColl( rChged.GetRegisterFormatColl() ); + + // If UseOn or the Follow change, the paragraphs need to know about it. + bool bUseOn = false; + bool bFollow = false; + if (rDesc.GetUseOn() != rChged.GetUseOn()) + { + rDesc.SetUseOn( rChged.GetUseOn() ); + bUseOn = true; + } + if (rDesc.GetFollow() != rChged.GetFollow()) + { + if (rChged.GetFollow() == &rChged) + { + if (rDesc.GetFollow() != &rDesc) + { + rDesc.SetFollow( &rDesc ); + bFollow = true; + } + } + else + { + rDesc.SetFollow( rChged.m_pFollow ); + bFollow = true; + } + } + + if ( (bUseOn || bFollow) && pTmpRoot) + // Inform layout! + { + for( auto aLayout : GetAllLayouts() ) + aLayout->AllCheckPageDescs(); + } + + // Take over the page attributes. + ::lcl_DescSetAttr( rChged.GetMaster(), rDesc.GetMaster() ); + ::lcl_DescSetAttr( rChged.GetLeft(), rDesc.GetLeft() ); + ::lcl_DescSetAttr( rChged.GetFirstMaster(), rDesc.GetFirstMaster() ); + ::lcl_DescSetAttr( rChged.GetFirstLeft(), rDesc.GetFirstLeft() ); + + // If the FootnoteInfo changes, the pages are triggered. + if( !(rDesc.GetFootnoteInfo() == rChged.GetFootnoteInfo()) ) + { + rDesc.SetFootnoteInfo( rChged.GetFootnoteInfo() ); + sw::PageFootnoteHint aHint; + rDesc.GetMaster().CallSwClientNotify(aHint); + rDesc.GetLeft().CallSwClientNotify(aHint); + rDesc.GetFirstMaster().CallSwClientNotify(aHint); + rDesc.GetFirstLeft().CallSwClientNotify(aHint); + } + getIDocumentState().SetModified(); + + // #i46909# no undo if header or footer changed + if( bHeaderFooterChanged ) + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + SfxBindings* pBindings = + ( GetDocShell() && GetDocShell()->GetDispatcher() ) ? GetDocShell()->GetDispatcher()->GetBindings() : nullptr; + if ( pBindings ) + { + pBindings->Invalidate( SID_ATTR_PAGE_COLUMN ); + pBindings->Invalidate( SID_ATTR_PAGE ); + pBindings->Invalidate( SID_ATTR_PAGE_SIZE ); + pBindings->Invalidate( SID_ATTR_PAGE_ULSPACE ); + pBindings->Invalidate( SID_ATTR_PAGE_LRSPACE ); + } + + //h/f of first-left page must not be unique but same as first master or left + assert((rDesc.IsFirstShared()) + ? rDesc.GetFirstLeft().GetHeader().GetHeaderFormat() == rDesc.GetLeft().GetHeader().GetHeaderFormat() + : rDesc.GetFirstLeft().GetHeader().GetHeaderFormat() == rDesc.GetFirstMaster().GetHeader().GetHeaderFormat()); + assert((rDesc.IsFirstShared()) + ? rDesc.GetFirstLeft().GetFooter().GetFooterFormat() == rDesc.GetLeft().GetFooter().GetFooterFormat() + : rDesc.GetFirstLeft().GetFooter().GetFooterFormat() == rDesc.GetFirstMaster().GetFooter().GetFooterFormat()); +} + +/// All descriptors whose Follow point to the to-be-deleted have to be adapted. +// #i7983# +void SwDoc::PreDelPageDesc(SwPageDesc const * pDel) +{ + if (nullptr == pDel) + return; + + // mba: test iteration as clients are removed while iteration + SwPageDescHint aHint( m_PageDescs[0] ); + pDel->CallSwClientNotify( aHint ); + + bool bHasLayout = getIDocumentLayoutAccess().HasLayout(); + if ( mpFootnoteInfo->DependsOn( pDel ) ) + { + mpFootnoteInfo->ChgPageDesc( m_PageDescs[0] ); + if ( bHasLayout ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->CheckFootnotePageDescs(false); + } + } + else if ( mpEndNoteInfo->DependsOn( pDel ) ) + { + mpEndNoteInfo->ChgPageDesc( m_PageDescs[0] ); + if ( bHasLayout ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->CheckFootnotePageDescs(true); + } + } + + for (SwPageDesc* pPageDesc : m_PageDescs) + { + if (pPageDesc->GetFollow() == pDel) + { + pPageDesc->SetFollow(nullptr); + if( bHasLayout ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->AllCheckPageDescs(); + } + } + } +} + +void SwDoc::BroadcastStyleOperation(const OUString& rName, SfxStyleFamily eFamily, + SfxHintId nOp) +{ + if (mpDocShell) + { + SfxStyleSheetBasePool * pPool = mpDocShell->GetStyleSheetPool(); + + if (pPool) + { + SfxStyleSheetBase* pBase = pPool->Find(rName, eFamily); + + if (pBase != nullptr) + pPool->Broadcast(SfxStyleSheetHint( nOp, *pBase )); + } + } +} + +void SwDoc::DelPageDesc( size_t i, bool bBroadcast ) +{ + OSL_ENSURE(i < m_PageDescs.size(), "PageDescs is out of range."); + OSL_ENSURE( i != 0, "You cannot delete the default Pagedesc."); + if ( i == 0 ) + return; + + SwPageDesc &rDel = *m_PageDescs[i]; + + if (bBroadcast) + BroadcastStyleOperation(rDel.GetName(), SfxStyleFamily::Page, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(rDel, this)); + } + + PreDelPageDesc(&rDel); // #i7983# + + m_PageDescs.erase(m_PageDescs.begin() + i); + getIDocumentState().SetModified(); +} + +SwPageDesc* SwDoc::MakePageDesc(const OUString &rName, const SwPageDesc *pCpy, + bool bRegardLanguage, bool bBroadcast) +{ + SwPageDesc *pNew; + if( pCpy ) + { + pNew = new SwPageDesc( *pCpy ); + pNew->SetName( rName ); + if( rName != pCpy->GetName() ) + { + pNew->SetPoolFormatId( USHRT_MAX ); + pNew->SetPoolHelpId( USHRT_MAX ); + pNew->SetPoolHlpFileId( UCHAR_MAX ); + } + } + else + { + pNew = new SwPageDesc( rName, GetDfltFrameFormat(), this ); + // Set the default page format. + lcl_DefaultPageFormat( USHRT_MAX, pNew->GetMaster(), pNew->GetLeft(), pNew->GetFirstMaster(), pNew->GetFirstLeft() ); + + SvxFrameDirection aFrameDirection = bRegardLanguage ? + GetDefaultFrameDirection(GetAppLanguage()) + : SvxFrameDirection::Horizontal_LR_TB; + + pNew->GetMaster().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + pNew->GetLeft().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + pNew->GetFirstMaster().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + pNew->GetFirstLeft().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + } + + std::pair res = m_PageDescs.push_back( pNew ); + SAL_WARN_IF(!res.second, "sw", "MakePageDesc called with existing name" ); + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Page, + SfxHintId::StyleSheetCreated); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique(pNew, this)); + } + + getIDocumentState().SetModified(); + return pNew; +} + +void SwDoc::PrtOLENotify( bool bAll ) +{ + SwFEShell *pShell = nullptr; + { + SwViewShell *pSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(SwViewShell& rShell : pSh->GetRingContainer()) + { + if(dynamic_cast( &rShell) != nullptr) + { + pShell = static_cast(&rShell); + break; + } + } + } + } + if ( !pShell ) + { + // This doesn't make sense without a Shell and thus without a client, because + // the communication about size changes is implemented by these components. + // Because we don't have a Shell we remember this unfortunate situation + // in the document, + // which is made up for later on when creating the first Shell. + mbOLEPrtNotifyPending = true; + if ( bAll ) + mbAllOLENotify = true; + } + else + { + if ( mbAllOLENotify ) + bAll = true; + + mbOLEPrtNotifyPending = mbAllOLENotify = false; + + std::unique_ptr pNodes = SwContentNode::CreateOLENodesArray( *GetDfltGrfFormatColl(), !bAll ); + if ( pNodes ) + { + ::StartProgress( STR_STATSTR_SWGPRTOLENOTIFY, + 0, pNodes->size(), GetDocShell()); + getIDocumentLayoutAccess().GetCurrentLayout()->StartAllAction(); + + for( SwOLENodes::size_type i = 0; i < pNodes->size(); ++i ) + { + ::SetProgressState( i, GetDocShell() ); + + SwOLENode* pOLENd = (*pNodes)[i]; + pOLENd->SetOLESizeInvalid( false ); + + // At first load the Infos and see if it's not already in the exclude list. + SvGlobalName aName; + + svt::EmbeddedObjectRef& xObj = pOLENd->GetOLEObj().GetObject(); + if ( xObj.is() ) + aName = SvGlobalName( xObj->getClassID() ); + else // Not yet loaded + { + // TODO/LATER: retrieve ClassID of an unloaded object + // aName = ???? + } + + bool bFound = false; + for ( std::vector::size_type j = 0; + j < pGlobalOLEExcludeList->size() && !bFound; + ++j ) + { + bFound = (*pGlobalOLEExcludeList)[j] == aName; + } + if ( bFound ) + continue; + + // We don't know it, so the object has to be loaded. + // If it doesn't want to be informed + if ( xObj.is() ) + { + pGlobalOLEExcludeList->push_back( aName ); + } + } + pNodes.reset(); + getIDocumentLayoutAccess().GetCurrentLayout()->EndAllAction(); + ::EndProgress( GetDocShell() ); + } + } +} + +IMPL_LINK_NOARG( SwDoc, DoUpdateModifiedOLE, Timer *, void ) +{ + SwFEShell* pSh = static_cast(GetEditShell()); + if( pSh ) + { + mbOLEPrtNotifyPending = mbAllOLENotify = false; + + std::unique_ptr pNodes = SwContentNode::CreateOLENodesArray( *GetDfltGrfFormatColl(), true ); + if( pNodes ) + { + ::StartProgress( STR_STATSTR_SWGPRTOLENOTIFY, + 0, pNodes->size(), GetDocShell()); + getIDocumentLayoutAccess().GetCurrentLayout()->StartAllAction(); + SwMsgPoolItem aMsgHint( RES_UPDATE_ATTR ); + + for( SwOLENodes::size_type i = 0; i < pNodes->size(); ++i ) + { + ::SetProgressState( i, GetDocShell() ); + + SwOLENode* pOLENd = (*pNodes)[i]; + pOLENd->SetOLESizeInvalid( false ); + + // We don't know it, so the object has to be loaded. + // If it doesn't want to be informed + if( pOLENd->GetOLEObj().GetOleRef().is() ) // Broken? + { + pOLENd->ModifyNotification( &aMsgHint, &aMsgHint ); + } + } + getIDocumentLayoutAccess().GetCurrentLayout()->EndAllAction(); + ::EndProgress( GetDocShell() ); + } + } +} + +static SwPageDesc* lcl_FindPageDesc( const SwPageDescs *pPageDescs, + size_t *pPos, const OUString &rName ) +{ + SwPageDesc* res = nullptr; + SwPageDescs::const_iterator it = pPageDescs->find( rName ); + if( it != pPageDescs->end() ) + { + res = *it; + if( pPos ) + *pPos = std::distance( pPageDescs->begin(), it ); + } + else if( pPos ) + *pPos = SIZE_MAX; + return res; +} + +SwPageDesc* SwDoc::FindPageDesc( const OUString & rName, size_t* pPos ) const +{ + return lcl_FindPageDesc( &m_PageDescs, pPos, rName ); +} + +bool SwDoc::ContainsPageDesc( const SwPageDesc *pDesc, size_t* pPos ) const +{ + if( pDesc == nullptr ) + return false; + if( !m_PageDescs.contains( const_cast ( pDesc ) ) ) { + if( pPos ) + *pPos = SIZE_MAX; + return false; + } + if( ! pPos ) + return true; + + SwPageDesc* desc = lcl_FindPageDesc( + &m_PageDescs, pPos, pDesc->GetName() ); + SAL_WARN_IF( desc != pDesc, "sw", "SwPageDescs container is broken!" ); + return true; +} + +void SwDoc::DelPageDesc( const OUString & rName, bool bBroadcast ) +{ + size_t nI; + + if (FindPageDesc(rName, &nI)) + DelPageDesc(nI, bBroadcast); +} + +void SwDoc::ChgPageDesc( const OUString & rName, const SwPageDesc & rDesc) +{ + size_t nI; + + if (FindPageDesc(rName, &nI)) + ChgPageDesc(nI, rDesc); +} + +/* + * The HTML import cannot resist changing the page descriptions, I don't + * know why. This function is meant to check the page descriptors for invalid + * values. + */ +void SwDoc::CheckDefaultPageFormat() +{ + for ( size_t i = 0; i < GetPageDescCnt(); ++i ) + { + SwPageDesc& rDesc = GetPageDesc( i ); + + SwFrameFormat& rMaster = rDesc.GetMaster(); + SwFrameFormat& rLeft = rDesc.GetLeft(); + + const SwFormatFrameSize& rMasterSize = rMaster.GetFrameSize(); + const SwFormatFrameSize& rLeftSize = rLeft.GetFrameSize(); + + const bool bSetSize = INVALID_TWIPS == rMasterSize.GetWidth() || + INVALID_TWIPS == rMasterSize.GetHeight() || + INVALID_TWIPS == rLeftSize.GetWidth() || + INVALID_TWIPS == rLeftSize.GetHeight(); + + if ( bSetSize ) + lcl_DefaultPageFormat( rDesc.GetPoolFormatId(), rDesc.GetMaster(), rDesc.GetLeft(), rDesc.GetFirstMaster(), rDesc.GetFirstLeft() ); + } +} + +void SwDoc::SetDefaultPageMode(bool bSquaredPageMode) +{ + if( !bSquaredPageMode == !IsSquaredPageMode() ) + return; + + const SwTextGridItem& rGrid = GetDefault( RES_TEXTGRID ); + SwTextGridItem aNewGrid = rGrid; + aNewGrid.SetSquaredMode(bSquaredPageMode); + aNewGrid.Init(); + SetDefault(aNewGrid); + + for ( size_t i = 0; i < GetPageDescCnt(); ++i ) + { + SwPageDesc& rDesc = GetPageDesc( i ); + + SwFrameFormat& rMaster = rDesc.GetMaster(); + SwFrameFormat& rLeft = rDesc.GetLeft(); + + SwTextGridItem aGrid(rMaster.GetFormatAttr(RES_TEXTGRID)); + aGrid.SwitchPaperMode( bSquaredPageMode ); + rMaster.SetFormatAttr(aGrid); + rLeft.SetFormatAttr(aGrid); + } +} + +bool SwDoc::IsSquaredPageMode() const +{ + const SwTextGridItem& rGrid = GetDefault( RES_TEXTGRID ); + return rGrid.IsSquaredMode(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docdraw.cxx b/sw/source/core/doc/docdraw.cxx new file mode 100644 index 000000000..cdc449943 --- /dev/null +++ b/sw/source/core/doc/docdraw.cxx @@ -0,0 +1,634 @@ +/* -*- 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 + +using namespace ::com::sun::star; +using namespace ::com::sun::star::linguistic2; + +/** local method to determine positioning and alignment attributes for a drawing + * object, which is newly connected to the layout. + * + * Used for a newly formed group object + * and the members of a destroyed group + */ +static void lcl_AdjustPositioningAttr( SwDrawFrameFormat* _pFrameFormat, + const SdrObject& _rSdrObj ) +{ + const SwContact* pContact = GetUserCall( &_rSdrObj ); + OSL_ENSURE( pContact, " - missing contact object." ); + + // determine position of new group object relative to its anchor frame position + SwTwips nHoriRelPos = 0; + SwTwips nVertRelPos = 0; + { + const SwFrame* pAnchorFrame = pContact->GetAnchoredObj( &_rSdrObj )->GetAnchorFrame(); + OSL_ENSURE( !pAnchorFrame || + !pAnchorFrame->IsTextFrame() || + !static_cast(pAnchorFrame)->IsFollow(), + " - anchor frame is a follow." ); + bool bVert = false; + bool bR2L = false; + // #i45952# - use anchor position of anchor frame, if it exist. + Point aAnchorPos; + if ( pAnchorFrame ) + { + // #i45952# + aAnchorPos = pAnchorFrame->GetFrameAnchorPos( ::HasWrap( &_rSdrObj ) ); + bVert = pAnchorFrame->IsVertical(); + bR2L = pAnchorFrame->IsRightToLeft(); + } + else + { + // #i45952# + aAnchorPos = _rSdrObj.GetAnchorPos(); + // If no anchor frame exist - e.g. because no layout exists - the + // default layout direction is taken. + const SvxFrameDirectionItem& rDirItem = + _pFrameFormat->GetAttrSet().GetPool()->GetDefaultItem( RES_FRAMEDIR ); + switch ( rDirItem.GetValue() ) + { + case SvxFrameDirection::Vertical_LR_TB: + { + // vertical from left-to-right + bVert = true; + bR2L = true; + OSL_FAIL( " - vertical from left-to-right not supported." ); + } + break; + case SvxFrameDirection::Vertical_RL_TB: + { + // vertical from right-to-left + bVert = true; + bR2L = false; + } + break; + case SvxFrameDirection::Horizontal_RL_TB: + { + // horizontal from right-to-left + bVert = false; + bR2L = true; + } + break; + case SvxFrameDirection::Horizontal_LR_TB: + { + // horizontal from left-to-right + bVert = false; + bR2L = false; + } + break; + case SvxFrameDirection::Environment: + SAL_WARN("sw.core", "lcl_AdjustPositioningAttr(..) SvxFrameDirection::Environment not supported"); + break; + default: break; + } + + } + // use geometry of drawing object + const SwRect aObjRect = _rSdrObj.GetSnapRect(); + + if ( bVert ) + { + if ( bR2L ) { + //SvxFrameDirection::Vertical_LR_TB + nHoriRelPos = aObjRect.Left() - aAnchorPos.getX(); + nVertRelPos = aObjRect.Top() - aAnchorPos.getY(); + } else { + //SvxFrameDirection::Vertical_RL_TB + nHoriRelPos = aObjRect.Top() - aAnchorPos.getY(); + nVertRelPos = aAnchorPos.getX() - aObjRect.Right(); + } + } + else if ( bR2L ) + { + nHoriRelPos = aAnchorPos.getX() - aObjRect.Right(); + nVertRelPos = aObjRect.Top() - aAnchorPos.getY(); + } + else + { + nHoriRelPos = aObjRect.Left() - aAnchorPos.getX(); + nVertRelPos = aObjRect.Top() - aAnchorPos.getY(); + } + } + + _pFrameFormat->SetFormatAttr( SwFormatHoriOrient( nHoriRelPos, text::HoriOrientation::NONE, text::RelOrientation::FRAME ) ); + _pFrameFormat->SetFormatAttr( SwFormatVertOrient( nVertRelPos, text::VertOrientation::NONE, text::RelOrientation::FRAME ) ); + // #i44334#, #i44681# - positioning attributes already set + _pFrameFormat->PosAttrSet(); + // #i34750# - keep current object rectangle for drawing + // objects. The object rectangle is used on events from the drawing layer + // to adjust the positioning attributes - see . + { + const SwAnchoredObject* pAnchoredObj = pContact->GetAnchoredObj( &_rSdrObj ); + if ( auto pAnchoredDrawObj = dynamic_cast( pAnchoredObj) ) + { + const SwRect aObjRect = _rSdrObj.GetSnapRect(); + const_cast(pAnchoredDrawObj) + ->SetLastObjRect( aObjRect.SVRect() ); + } + } +} + +SwDrawContact* SwDoc::GroupSelection( SdrView& rDrawView ) +{ + // replace marked 'virtual' drawing objects by the corresponding 'master' + // drawing objects. + SwDrawView::ReplaceMarkedDrawVirtObjs( rDrawView ); + + const SdrMarkList &rMrkList = rDrawView.GetMarkedObjectList(); + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + bool bNoGroup = ( nullptr == pObj->getParentSdrObjectFromSdrObject() ); + SwDrawContact* pNewContact = nullptr; + if( bNoGroup ) + { + SwDrawFrameFormat *pFormat = nullptr; + + // Revoke anchor attribute. + SwDrawContact *pMyContact = static_cast(GetUserCall(pObj)); + const SwFormatAnchor aAnch( pMyContact->GetFormat()->GetAnchor() ); + + std::unique_ptr pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoDrawGroup( static_cast(rMrkList.GetMarkCount()) , this)); + + // #i53320# + bool bGroupMembersNotPositioned( false ); + { + SwAnchoredDrawObject* pAnchoredDrawObj = + static_cast(pMyContact->GetAnchoredObj( pObj )); + bGroupMembersNotPositioned = pAnchoredDrawObj->NotYetPositioned(); + } + // Destroy ContactObjects and formats. + for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + SwDrawContact *pContact = static_cast(GetUserCall(pObj)); + + // #i53320# +#if OSL_DEBUG_LEVEL > 0 + SwAnchoredDrawObject* pAnchoredDrawObj = + static_cast(pContact->GetAnchoredObj( pObj )); + OSL_ENSURE( bGroupMembersNotPositioned == pAnchoredDrawObj->NotYetPositioned(), + " - group members have different positioning status!" ); +#endif + + pFormat = static_cast(pContact->GetFormat()); + // Deletes itself! + pContact->Changed(*pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + if( pUndo ) + pUndo->AddObj( i, pFormat, pObj ); + else + DelFrameFormat( pFormat ); + + // #i45952# - re-introduce position normalization of group member + // objects, because its anchor position is cleared, when they are + // grouped. + Point aAnchorPos( pObj->GetAnchorPos() ); + pObj->NbcSetAnchorPos( Point( 0, 0 ) ); + pObj->NbcMove( Size( aAnchorPos.getX(), aAnchorPos.getY() ) ); + } + + pFormat = MakeDrawFrameFormat( GetUniqueDrawObjectName(), + GetDfltFrameFormat() ); + pFormat->SetFormatAttr( aAnch ); + // #i36010# - set layout direction of the position + pFormat->SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + + rDrawView.GroupMarked(); + OSL_ENSURE( rMrkList.GetMarkCount() == 1, "GroupMarked more or none groups." ); + + SdrObject* pNewGroupObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + pNewGroupObj->SetName(pFormat->GetName()); + pNewContact = new SwDrawContact( pFormat, pNewGroupObj ); + // #i35635# + pNewContact->MoveObjToVisibleLayer( pNewGroupObj ); + pNewContact->ConnectToLayout(); + // #i53320# - No adjustment of the positioning and alignment + // attributes, if group members aren't positioned yet. + if ( !bGroupMembersNotPositioned ) + { + // #i26791# - Adjust positioning and alignment attributes. + lcl_AdjustPositioningAttr( pFormat, *pNewGroupObj ); + } + + if( pUndo ) + { + pUndo->SetGroupFormat( pFormat ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + } + + rDrawView.GroupMarked(); + OSL_ENSURE( rMrkList.GetMarkCount() == 1, "GroupMarked more or none groups." ); + } + + return pNewContact; +} + +void SwDoc::UnGroupSelection( SdrView& rDrawView ) +{ + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if( bUndo ) + { + GetIDocumentUndoRedo().ClearRedo(); + } + + // replace marked 'virtual' drawing objects by the corresponding 'master' + // drawing objects. + SwDrawView::ReplaceMarkedDrawVirtObjs( rDrawView ); + + const SdrMarkList &rMrkList = rDrawView.GetMarkedObjectList(); + std::unique_ptr >[]> pFormatsAndObjs; + const size_t nMarkCount( rMrkList.GetMarkCount() ); + if ( nMarkCount ) + { + pFormatsAndObjs.reset( new std::vector< std::pair< SwDrawFrameFormat*, SdrObject* > >[nMarkCount] ); + SdrObject *pMyObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if( !pMyObj->getParentSdrObjectFromSdrObject() ) + { + for ( size_t i = 0; i < nMarkCount; ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( dynamic_cast(pObj) != nullptr ) + { + SwDrawContact *pContact = static_cast(GetUserCall(pObj)); + SwFormatAnchor aAnch( pContact->GetFormat()->GetAnchor() ); + SdrObjList *pLst = static_cast(pObj)->GetSubList(); + + SwUndoDrawUnGroup* pUndo = nullptr; + if( bUndo ) + { + pUndo = new SwUndoDrawUnGroup( static_cast(pObj), this ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(pUndo)); + } + + for ( size_t i2 = 0; i2 < pLst->GetObjCount(); ++i2 ) + { + SdrObject* pSubObj = pLst->GetObj( i2 ); + SwDrawFrameFormat *pFormat = MakeDrawFrameFormat( GetUniqueShapeName(), + GetDfltFrameFormat() ); + pFormat->SetFormatAttr( aAnch ); + // #i36010# - set layout direction of the position + pFormat->SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + pFormatsAndObjs[i].emplace_back( pFormat, pSubObj ); + + if( bUndo ) + pUndo->AddObj( static_cast(i2), pFormat ); + } + } + } + } + } + rDrawView.UnGroupMarked(); + // creation of instances for the former group members and + // its connection to the Writer layout. + for ( size_t i = 0; i < nMarkCount; ++i ) + { + SwUndoDrawUnGroupConnectToLayout* pUndo = nullptr; + if( bUndo ) + { + pUndo = new SwUndoDrawUnGroupConnectToLayout(this); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(pUndo)); + } + + while ( !pFormatsAndObjs[i].empty() ) + { + SwDrawFrameFormat* pFormat( pFormatsAndObjs[i].back().first ); + SdrObject* pObj( pFormatsAndObjs[i].back().second ); + pFormatsAndObjs[i].pop_back(); + + SwDrawContact* pContact = new SwDrawContact( pFormat, pObj ); + pContact->MoveObjToVisibleLayer( pObj ); + pContact->ConnectToLayout(); + lcl_AdjustPositioningAttr( pFormat, *pObj ); + + if ( bUndo ) + { + pUndo->AddFormatAndObj( pFormat, pObj ); + } + } + } +} + +bool SwDoc::DeleteSelection( SwDrawView& rDrawView ) +{ + bool bCallBase = false; + const SdrMarkList &rMrkList = rDrawView.GetMarkedObjectList(); + if( rMrkList.GetMarkCount() ) + { + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + bool bDelMarked = true; + + if( 1 == rMrkList.GetMarkCount() ) + { + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if( dynamic_cast( pObj) != nullptr ) + { + SwFlyFrameFormat* pFrameFormat = + static_cast(pObj)->GetFlyFrame()->GetFormat(); + if( pFrameFormat ) + { + getIDocumentLayoutAccess().DelLayoutFormat( pFrameFormat ); + bDelMarked = false; + } + } + } + + for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if( dynamic_cast( pObj) == nullptr ) + { + SwDrawContact *pC = static_cast(GetUserCall(pObj)); + SwDrawFrameFormat *pFrameFormat = static_cast(pC->GetFormat()); + if( pFrameFormat && + RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId() ) + { + rDrawView.MarkObj( pObj, rDrawView.Imp().GetPageView(), true ); + --i; + getIDocumentLayoutAccess().DelLayoutFormat( pFrameFormat ); + } + } + } + + if( rMrkList.GetMarkCount() && bDelMarked ) + { + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if( !pObj->getParentSdrObjectFromSdrObject() ) + { + std::unique_ptr pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoDrawDelete( static_cast(rMrkList.GetMarkCount()), this )); + + // Destroy ContactObjects, save formats. + for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + const SdrMark& rMark = *rMrkList.GetMark( i ); + pObj = rMark.GetMarkedSdrObj(); + SwDrawContact *pContact = static_cast(pObj->GetUserCall()); + if( pContact ) // of course not for grouped objects + { + SwDrawFrameFormat *pFormat = static_cast(pContact->GetFormat()); + // before delete of selection is performed, marked + // -objects have to be replaced by its + // reference objects. Thus, assert, if a + // -object is found in the mark list. + if ( dynamic_cast( pObj) != nullptr ) + { + OSL_FAIL( " is still marked for delete. application will crash!" ); + } + // Deletes itself! + pContact->Changed(*pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + if( pUndo ) + pUndo->AddObj( pFormat, rMark ); + else + DelFrameFormat( pFormat ); + } + } + + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + bCallBase = true; + } + getIDocumentState().SetModified(); + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + + return bCallBase; +} + +ZSortFly::ZSortFly(const SwFrameFormat* pFrameFormat, const SwFormatAnchor* pFlyAn, sal_uInt32 nArrOrdNum) + : m_pFormat(pFrameFormat) + , m_pAnchor(pFlyAn) + , m_nOrdNum(nArrOrdNum) +{ + SAL_WARN_IF(m_pFormat->Which() != RES_FLYFRMFMT && m_pFormat->Which() != RES_DRAWFRMFMT, "sw.core", "What kind of format is this?"); + m_pFormat->CallSwClientNotify(sw::GetZOrderHint(m_nOrdNum)); +} + +/// In the Outliner, set a link to the method for field display in edit objects. +void SwDoc::SetCalcFieldValueHdl(Outliner* pOutliner) +{ + pOutliner->SetCalcFieldValueHdl(LINK(this, SwDoc, CalcFieldValueHdl)); +} + +/// Recognise fields/URLs in the Outliner and set how they are displayed. +IMPL_LINK(SwDoc, CalcFieldValueHdl, EditFieldInfo*, pInfo, void) +{ + if (!pInfo) + return; + + const SvxFieldItem& rField = pInfo->GetField(); + const SvxFieldData* pField = rField.GetField(); + + if (auto pDateField = dynamic_cast( pField)) + { + // Date field + pInfo->SetRepresentation( + pDateField->GetFormatted( + *GetNumberFormatter(), LANGUAGE_SYSTEM) ); + } + else if (auto pURLField = dynamic_cast( pField)) + { + // URL field + switch ( pURLField->GetFormat() ) + { + case SvxURLFormat::AppDefault: //!!! Can be set in App??? + case SvxURLFormat::Repr: + pInfo->SetRepresentation(pURLField->GetRepresentation()); + break; + + case SvxURLFormat::Url: + pInfo->SetRepresentation(pURLField->GetURL()); + break; + } + + sal_uInt16 nChrFormat; + + if (IsVisitedURL(pURLField->GetURL())) + nChrFormat = RES_POOLCHR_INET_VISIT; + else + nChrFormat = RES_POOLCHR_INET_NORMAL; + + SwFormat *pFormat = getIDocumentStylePoolAccess().GetCharFormatFromPool(nChrFormat); + + Color aColor(COL_LIGHTBLUE); + if (pFormat) + aColor = pFormat->GetColor().GetValue(); + + pInfo->SetTextColor(aColor); + } + else if (dynamic_cast( pField)) + { + // Clear measure field + pInfo->SetFieldColor(std::optional()); + } + else if ( auto pTimeField = dynamic_cast( pField) ) + { + // Time field + pInfo->SetRepresentation( + pTimeField->GetFormatted(*GetNumberFormatter(), LANGUAGE_SYSTEM) ); + } + else + { + OSL_FAIL("unknown field command"); + pInfo->SetRepresentation( OUString( '?' ) ); + } +} + +// #i62875# +namespace docfunc +{ + bool ExistsDrawObjs( SwDoc& p_rDoc ) + { + bool bExistsDrawObjs( false ); + + if ( p_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && + p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 ) ) + { + const SdrPage& rSdrPage( *(p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 )) ); + + SdrObjListIter aIter( &rSdrPage, SdrIterMode::Flat ); + while( aIter.IsMore() ) + { + SdrObject* pObj( aIter.Next() ); + if ( !dynamic_cast(pObj) && + !dynamic_cast(pObj) ) + { + bExistsDrawObjs = true; + break; + } + } + } + + return bExistsDrawObjs; + } + + bool AllDrawObjsOnPage( SwDoc& p_rDoc ) + { + bool bAllDrawObjsOnPage( true ); + + if ( p_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && + p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 ) ) + { + const SdrPage& rSdrPage( *(p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 )) ); + + SdrObjListIter aIter( &rSdrPage, SdrIterMode::Flat ); + while( aIter.IsMore() ) + { + SdrObject* pObj( aIter.Next() ); + if ( !dynamic_cast(pObj) && + !dynamic_cast(pObj) ) + { + SwDrawContact* pDrawContact = + dynamic_cast(::GetUserCall( pObj )); + if ( pDrawContact ) + { + SwAnchoredDrawObject* pAnchoredDrawObj = + dynamic_cast(pDrawContact->GetAnchoredObj( pObj )); + + // error handling + { + if ( !pAnchoredDrawObj ) + { + OSL_FAIL( "NotYetPositioned() ) + { + // The drawing object isn't yet layouted. + // Thus, it isn't known, if all drawing objects are on page. + bAllDrawObjsOnPage = false; + break; + } + else if ( pAnchoredDrawObj->IsOutsidePage() ) + { + bAllDrawObjsOnPage = false; + break; + } + } + else + { + // contact object of drawing object doesn't exists. + // Thus, the drawing object isn't yet positioned. + // Thus, it isn't known, if all drawing objects are on page. + bAllDrawObjsOnPage = false; + break; + } + } + } + } + + return bAllDrawObjsOnPage; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docedt.cxx b/sw/source/core/doc/docedt.cxx new file mode 100644 index 000000000..07902efe3 --- /dev/null +++ b/sw/source/core/doc/docedt.cxx @@ -0,0 +1,886 @@ +/* -*- 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 ::com::sun::star; +using namespace ::com::sun::star::linguistic2; +using namespace ::com::sun::star::i18n; + + +void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos, + const SwNodeIndex* pInsertPos ) +{ + SwPosition aPos(rStartPos); + for(const SaveFly & rSave : rArr) + { + // create new anchor + SwFrameFormat* pFormat = rSave.pFrameFormat; + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + + if (rSave.isAtInsertNode) + { + if( pInsertPos != nullptr ) + { + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + aPos.nNode = *pInsertPos; + aPos.nContent.Assign(dynamic_cast(&aPos.nNode.GetNode()), + rSave.nContentIndex); + } + else + { + assert(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR); + aPos = rStartPos; + } + } + else + { + aPos.nNode = rStartPos.nNode; + aPos.nContent.Assign(dynamic_cast(&aPos.nNode.GetNode()), 0); + } + } + else + { + aPos.nNode = rStartPos.nNode.GetIndex() + rSave.nNdDiff; + aPos.nContent.Assign(dynamic_cast(&aPos.nNode.GetNode()), + rSave.nNdDiff == 0 + ? rStartPos.nContent.GetIndex() + rSave.nContentIndex + : rSave.nContentIndex); + } + + aAnchor.SetAnchor( &aPos ); + pFormat->GetDoc()->GetSpzFrameFormats()->push_back( pFormat ); + // SetFormatAttr should call Modify() and add it to the node + pFormat->SetFormatAttr( aAnchor ); + SwContentNode* pCNd = aPos.nNode.GetNode().GetContentNode(); + if (pCNd && pCNd->getLayoutFrame(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) + pFormat->MakeFrames(); + } + sw::CheckAnchoredFlyConsistency(*rStartPos.nNode.GetNode().GetDoc()); +} + +void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) +{ + SwFrameFormats& rFormats = *rRg.aStart.GetNode().GetDoc()->GetSpzFrameFormats(); + for( SwFrameFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + SwFrameFormat *const pFormat = rFormats[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + rRg.aStart <= pAPos->nNode && pAPos->nNode < rRg.aEnd ) + { + SaveFly aSave( pAPos->nNode.GetIndex() - rRg.aStart.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? pAPos->nContent.GetIndex() + : 0, + pFormat, false ); + rArr.push_back( aSave ); + pFormat->DelFrames(); + // set a dummy anchor position to maintain anchoring invariants + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + aAnchor.SetAnchor(nullptr); + pFormat->SetFormatAttr(aAnchor); + rFormats.erase( rFormats.begin() + n-- ); + } + } + sw::CheckAnchoredFlyConsistency(*rRg.aStart.GetNode().GetDoc()); +} + +void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, + SaveFlyArr& rArr, bool bMoveAllFlys ) +{ + SwFrameFormats& rFormats = *rPam.GetPoint()->nNode.GetNode().GetDoc()->GetSpzFrameFormats(); + SwFrameFormat* pFormat; + const SwFormatAnchor* pAnchor; + + const SwPosition* pPos = rPam.Start(); + const SwNodeIndex& rSttNdIdx = pPos->nNode; + + SwPosition atParaEnd(*rPam.End()); + if (bMoveAllFlys) + { + assert(!rPam.End()->nNode.GetNode().IsTextNode() // can be table end-node + || rPam.End()->nContent.GetIndex() == rPam.End()->nNode.GetNode().GetTextNode()->Len()); + ++atParaEnd.nNode; + atParaEnd.nContent.Assign(atParaEnd.nNode.GetNode().GetContentNode(), 0); + } + + for( SwFrameFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + pFormat = rFormats[n]; + pAnchor = &pFormat->GetAnchor(); + const SwPosition* pAPos = pAnchor->GetContentAnchor(); + const SwNodeIndex* pContentIdx; + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + // do not move if the InsPos is in the ContentArea of the Fly + ( nullptr == ( pContentIdx = pFormat->GetContent().GetContentIdx() ) || + !(*pContentIdx < rInsPos.nNode && + rInsPos.nNode < pContentIdx->GetNode().EndOfSectionIndex()))) + { + bool bInsPos = false; + + if ( (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() + && IsDestroyFrameAnchoredAtChar(*pAPos, *rPam.Start(), *rPam.End())) + || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId() + && IsSelectFrameAnchoredAtPara(*pAPos, *rPam.Start(), atParaEnd, + bMoveAllFlys + ? DelContentType::CheckNoCntnt|DelContentType::AllMask + : DelContentType::AllMask)) + || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos.nNode == pAPos->nNode))) + || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos == *pAPos)))) + { + SaveFly aSave( pAPos->nNode.GetIndex() - rSttNdIdx.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? (pAPos->nNode == rSttNdIdx) + ? pAPos->nContent.GetIndex() - rPam.Start()->nContent.GetIndex() + : pAPos->nContent.GetIndex() + : 0, + pFormat, bInsPos ); + rArr.push_back( aSave ); + pFormat->DelFrames(); + // set a dummy anchor position to maintain anchoring invariants + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + aAnchor.SetAnchor(nullptr); + pFormat->SetFormatAttr(aAnchor); + rFormats.erase( rFormats.begin() + n-- ); + } + } + } + sw::CheckAnchoredFlyConsistency(*rPam.GetPoint()->nNode.GetNode().GetDoc()); +} + +/// Delete and move all Flys at the paragraph, that are within the selection. +/// If there is a Fly at the SPoint, it is moved onto the Mark. +void DelFlyInRange( const SwNodeIndex& rMkNdIdx, + const SwNodeIndex& rPtNdIdx, + SwIndex const*const pMkIdx, SwIndex const*const pPtIdx) +{ + assert((pMkIdx == nullptr) == (pPtIdx == nullptr)); + SwPosition const point(pPtIdx + ? SwPosition(rPtNdIdx, *pPtIdx) + : SwPosition(rPtNdIdx)); + SwPosition const mark(pPtIdx + ? SwPosition(rMkNdIdx, *pMkIdx) + : SwPosition(rMkNdIdx)); + SwPosition const& rStart = mark <= point ? mark : point; + SwPosition const& rEnd = mark <= point ? point : mark; + + SwDoc* pDoc = rMkNdIdx.GetNode().GetDoc(); + SwFrameFormats& rTable = *pDoc->GetSpzFrameFormats(); + for ( auto i = rTable.size(); i; ) + { + SwFrameFormat *pFormat = rTable[--i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + SwPosition const*const pAPos = rAnch.GetContentAnchor(); + if (pAPos && + (((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) + && IsSelectFrameAnchoredAtPara(*pAPos, rStart, rEnd, pPtIdx + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt)) + || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, pPtIdx + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt)))) + { + // If the Fly is deleted, all Flys in its content have to be deleted too. + const SwFormatContent &rContent = pFormat->GetContent(); + // But only fly formats own their content, not draw formats. + if (rContent.GetContentIdx() && pFormat->Which() == RES_FLYFRMFMT) + { + DelFlyInRange( *rContent.GetContentIdx(), + SwNodeIndex( *rContent.GetContentIdx()-> + GetNode().EndOfSectionNode() )); + // Position could have been moved! + if (i > rTable.size()) + i = rTable.size(); + else if (pFormat != rTable[i]) + i = std::distance(rTable.begin(), rTable.find( pFormat )); + } + + pDoc->getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + + // DelLayoutFormat can also trigger the deletion of objects. + if (i > rTable.size()) + i = rTable.size(); + } + } +} + +// #i59534: Redo of insertion of multiple text nodes runs into trouble +// because of unnecessary expanded redlines +// From now on this class saves the redline positions of all redlines which ends exact at the +// insert position (node _and_ content index) +SaveRedlEndPosForRestore::SaveRedlEndPosForRestore( const SwNodeIndex& rInsIdx, sal_Int32 nCnt ) + : mnSaveContent( nCnt ) +{ + SwNode& rNd = rInsIdx.GetNode(); + SwDoc* pDest = rNd.GetDoc(); + if( !pDest->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwRedlineTable::size_type nFndPos; + const SwPosition* pEnd; + SwPosition aSrcPos( rInsIdx, SwIndex( rNd.GetContentNode(), nCnt )); + pDest->getIDocumentRedlineAccess().GetRedline( aSrcPos, &nFndPos ); + const SwRangeRedline* pRedl; + while( nFndPos-- + && *( pEnd = ( pRedl = pDest->getIDocumentRedlineAccess().GetRedlineTable()[ nFndPos ] )->End() ) == aSrcPos + && *pRedl->Start() < aSrcPos ) + { + if( !mpSaveIndex ) + { + mpSaveIndex.reset(new SwNodeIndex( rInsIdx, -1 )); + } + mvSavArr.push_back( const_cast(pEnd) ); + } + } +} + +SaveRedlEndPosForRestore::~SaveRedlEndPosForRestore() +{ + mpSaveIndex.reset(); +} + +void SaveRedlEndPosForRestore::Restore() +{ + if (mvSavArr.empty()) + return; + ++(*mpSaveIndex); + SwContentNode* pNode = mpSaveIndex->GetNode().GetContentNode(); + // If there's no content node at the remembered position, we will not restore the old position + // This may happen if a table (or section?) will be inserted. + if( pNode ) + { + SwPosition aPos( *mpSaveIndex, SwIndex( pNode, mnSaveContent )); + for( auto n = mvSavArr.size(); n; ) + *mvSavArr[ --n ] = aPos; + } +} + +/// Convert list of ranges of whichIds to a corresponding list of whichIds +static std::vector lcl_RangesToVector(const sal_uInt16 * pRanges) +{ + std::vector aResult; + + int i = 0; + while (pRanges[i] != 0) + { + OSL_ENSURE(pRanges[i+1] != 0, "malformed ranges"); + + for (sal_uInt16 j = pRanges[i]; j <= pRanges[i+1]; j++) + aResult.push_back(j); + + i += 2; + } + + return aResult; +} + +void sw_GetJoinFlags( SwPaM& rPam, bool& rJoinText, bool& rJoinPrev ) +{ + rJoinText = false; + rJoinPrev = false; + if( rPam.GetPoint()->nNode != rPam.GetMark()->nNode ) + { + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + SwTextNode *pSttNd = pStt->nNode.GetNode().GetTextNode(); + if( pSttNd ) + { + SwTextNode *pEndNd = pEnd->nNode.GetNode().GetTextNode(); + rJoinText = nullptr != pEndNd; + if( rJoinText ) + { + bool bExchange = pStt == rPam.GetPoint(); + if( !pStt->nContent.GetIndex() && + pEndNd->GetText().getLength() != pEnd->nContent.GetIndex()) + bExchange = !bExchange; + if( bExchange ) + rPam.Exchange(); + rJoinPrev = rPam.GetPoint() == pStt; + OSL_ENSURE( !pStt->nContent.GetIndex() && + pEndNd->GetText().getLength() != pEnd->nContent.GetIndex() + ? (rPam.GetPoint()->nNode < rPam.GetMark()->nNode) + : (rPam.GetPoint()->nNode > rPam.GetMark()->nNode), + "sw_GetJoinFlags"); + } + } + } +} + +bool sw_JoinText( SwPaM& rPam, bool bJoinPrev ) +{ + SwNodeIndex aIdx( rPam.GetPoint()->nNode ); + SwTextNode *pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aOldIdx( aIdx ); + SwTextNode *pOldTextNd = pTextNd; + + if( pTextNd && pTextNd->CanJoinNext( &aIdx ) ) + { + SwDoc* pDoc = rPam.GetDoc(); + if( bJoinPrev ) + { + // We do not need to handle xmlids in this case, because + // it is only invoked if one paragraph is/becomes completely empty + // (see sw_GetJoinFlags) + { + // If PageBreaks are deleted/set, it must not be added to the Undo history! + // Also, deleting the Node is not added to the Undo history! + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + /* PageBreaks, PageDesc, ColumnBreaks */ + // If we need to change something about the logic to copy the PageBreaks, + // PageDesc, etc. we also have to change SwUndoDelete. + // There, we copy the AUTO PageBreak from the GetMarkNode! + + /* The MarkNode */ + pTextNd = aIdx.GetNode().GetTextNode(); + if (pTextNd->HasSwAttrSet()) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( + RES_BREAK, false, &pItem ) ) + pTextNd->ResetAttr( RES_BREAK ); + if( pTextNd->HasSwAttrSet() && + SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( + RES_PAGEDESC, false, &pItem ) ) + pTextNd->ResetAttr( RES_PAGEDESC ); + } + + /* The PointNode */ + if( pOldTextNd->HasSwAttrSet() ) + { + const SfxPoolItem* pItem; + SfxItemSet aSet( pDoc->GetAttrPool(), aBreakSetRange ); + const SfxItemSet* pSet = pOldTextNd->GetpSwAttrSet(); + if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, + false, &pItem ) ) + aSet.Put( *pItem ); + if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + aSet.Put( *pItem ); + if( aSet.Count() ) + pTextNd->SetAttr( aSet ); + } + pOldTextNd->FormatToTextAttr( pTextNd ); + + const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save(pDoc, aOldIdx.GetIndex(), SAL_MAX_INT32); + + SwIndex aAlphaIdx(pTextNd); + pOldTextNd->CutText( pTextNd, aAlphaIdx, SwIndex(pOldTextNd), + pOldTextNd->Len() ); + SwPosition aAlphaPos( aIdx, aAlphaIdx ); + pDoc->CorrRel( rPam.GetPoint()->nNode, aAlphaPos, 0, true ); + + // move all Bookmarks/TOXMarks + if( !pContentStore->Empty() ) + pContentStore->Restore( pDoc, aIdx.GetIndex() ); + + // If the passed PaM is not in the Cursor ring, + // treat it separately (e.g. when it's being called from AutoFormat) + if( pOldTextNd == rPam.GetBound().nContent.GetIdxReg() ) + rPam.GetBound() = aAlphaPos; + if( pOldTextNd == rPam.GetBound( false ).nContent.GetIdxReg() ) + rPam.GetBound( false ) = aAlphaPos; + } + // delete the Node, at last! + SwNode::Merge const eOldMergeFlag(pOldTextNd->GetRedlineMergeFlag()); + if (eOldMergeFlag == SwNode::Merge::First + && !pTextNd->IsCreateFrameWhenHidingRedlines()) + { + sw::MoveDeletedPrevFrames(*pOldTextNd, *pTextNd); + } + pDoc->GetNodes().Delete( aOldIdx ); + sw::CheckResetRedlineMergeFlag(*pTextNd, + eOldMergeFlag == SwNode::Merge::NonFirst + ? sw::Recreate::Predecessor + : sw::Recreate::No); + } + else + { + SwTextNode* pDelNd = aIdx.GetNode().GetTextNode(); + if( pTextNd->Len() ) + pDelNd->FormatToTextAttr( pTextNd ); + else + { + /* This case was missed: + + <-- pTextNd + ccc <-- pDelNd + + and are paragraph + attributes. The attribute stayed if not + overwritten by an attribute in "ccc". Fixed by + first resetting all character attributes in first + paragraph (pTextNd). + */ + std::vector aShorts = + lcl_RangesToVector(aCharFormatSetRange); + pTextNd->ResetAttr(aShorts); + + if( pDelNd->HasSwAttrSet() ) + { + // only copy the character attributes + SfxItemSet aTmpSet( pDoc->GetAttrPool(), aCharFormatSetRange ); + aTmpSet.Put( *pDelNd->GetpSwAttrSet() ); + pTextNd->SetAttr( aTmpSet ); + } + } + + pDoc->CorrRel( aIdx, *rPam.GetPoint(), 0, true ); + // #i100466# adjust given , if it does not belong to the cursors + if ( pDelNd == rPam.GetBound().nContent.GetIdxReg() ) + { + rPam.GetBound() = SwPosition( SwNodeIndex( *pTextNd ), SwIndex( pTextNd ) ); + } + if( pDelNd == rPam.GetBound( false ).nContent.GetIdxReg() ) + { + rPam.GetBound( false ) = SwPosition( SwNodeIndex( *pTextNd ), SwIndex( pTextNd ) ); + } + pTextNd->JoinNext(); + } + return true; + } + else return false; +} + +static void lcl_syncGrammarError( SwTextNode &rTextNode, linguistic2::ProofreadingResult& rResult, + const ModelToViewHelper &rConversionMap ) +{ + if( rTextNode.IsGrammarCheckDirty() ) + return; + SwGrammarMarkUp* pWrong = rTextNode.GetGrammarCheck(); + linguistic2::SingleProofreadingError* pArray = rResult.aErrors.getArray(); + sal_uInt16 j = 0; + if( pWrong ) + { + for( sal_Int32 i = 0; i < rResult.aErrors.getLength(); ++i ) + { + const linguistic2::SingleProofreadingError &rError = rResult.aErrors[i]; + const sal_Int32 nStart = rConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos; + const sal_Int32 nEnd = rConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos; + if( i != j ) + pArray[j] = pArray[i]; + if( pWrong->LookForEntry( nStart, nEnd ) ) + ++j; + } + } + if( rResult.aErrors.getLength() > j ) + rResult.aErrors.realloc( j ); +} + +uno::Any SwDoc::Spell( SwPaM& rPaM, + uno::Reference< XSpellChecker1 > const &xSpeller, + sal_uInt16* pPageCnt, sal_uInt16* pPageSt, + bool bGrammarCheck, + SwRootFrame const*const pLayout, + SwConversionArgs *pConvArgs ) const +{ + SwPosition* pSttPos = rPaM.Start(), *pEndPos = rPaM.End(); + + std::unique_ptr pSpellArgs; + if (pConvArgs) + { + pConvArgs->SetStart(pSttPos->nNode.GetNode().GetTextNode(), pSttPos->nContent); + pConvArgs->SetEnd( pEndPos->nNode.GetNode().GetTextNode(), pEndPos->nContent ); + } + else + pSpellArgs.reset(new SwSpellArgs( xSpeller, + pSttPos->nNode.GetNode().GetTextNode(), pSttPos->nContent, + pEndPos->nNode.GetNode().GetTextNode(), pEndPos->nContent, + bGrammarCheck )); + + sal_uLong nCurrNd = pSttPos->nNode.GetIndex(); + sal_uLong nEndNd = pEndPos->nNode.GetIndex(); + + uno::Any aRet; + if( nCurrNd <= nEndNd ) + { + SwContentFrame* pContentFrame; + bool bGoOn = true; + while( bGoOn ) + { + SwNode* pNd = GetNodes()[ nCurrNd ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + if( nullptr != ( pContentFrame = pNd->GetTextNode()->getLayoutFrame( getIDocumentLayoutAccess().GetCurrentLayout() )) ) + { + // skip protected and hidden Cells and Flys + if( pContentFrame->IsProtected() ) + { + nCurrNd = pNd->EndOfSectionIndex(); + } + else if( !static_cast(pContentFrame)->IsHiddenNow() ) + { + if( pPageCnt && *pPageCnt && pPageSt ) + { + sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum(); + if( !*pPageSt ) + { + *pPageSt = nPageNr; + if( *pPageCnt < *pPageSt ) + *pPageCnt = *pPageSt; + } + long nStat; + if( nPageNr >= *pPageSt ) + nStat = nPageNr - *pPageSt + 1; + else + nStat = nPageNr + *pPageCnt - *pPageSt + 1; + ::SetProgressState( nStat, GetDocShell() ); + } + //Spell() changes the pSpellArgs in case an error is found + sal_Int32 nBeginGrammarCheck = 0; + sal_Int32 nEndGrammarCheck = 0; + if( pSpellArgs && pSpellArgs->bIsGrammarCheck) + { + nBeginGrammarCheck = pSpellArgs->pStartNode == pNd ? pSpellArgs->pStartIdx->GetIndex() : 0; + // if grammar checking starts inside of a sentence the start position has to be adjusted + if( nBeginGrammarCheck ) + { + SwIndex aStartIndex( dynamic_cast< SwTextNode* >( pNd ), nBeginGrammarCheck ); + SwPosition aStart( *pNd, aStartIndex ); + SwCursor aCursor(aStart, nullptr); + SwPosition aOrigPos = *aCursor.GetPoint(); + aCursor.GoSentence( SwCursor::START_SENT ); + if( aOrigPos != *aCursor.GetPoint() ) + { + nBeginGrammarCheck = aCursor.GetPoint()->nContent.GetIndex(); + } + } + nEndGrammarCheck = (pSpellArgs->pEndNode == pNd) + ? pSpellArgs->pEndIdx->GetIndex() + : pNd->GetTextNode() + ->GetText().getLength(); + } + + sal_Int32 nSpellErrorPosition = pNd->GetTextNode()->GetText().getLength(); + if( (!pConvArgs && pNd->GetTextNode()->Spell( pSpellArgs.get() )) || + ( pConvArgs && pNd->GetTextNode()->Convert( *pConvArgs ))) + { + // Cancel and remember position + pSttPos->nNode = nCurrNd; + pEndPos->nNode = nCurrNd; + nCurrNd = nEndNd; + if( pSpellArgs ) + nSpellErrorPosition = pSpellArgs->pStartIdx->GetIndex() > pSpellArgs->pEndIdx->GetIndex() ? + pSpellArgs->pEndIdx->GetIndex() : + pSpellArgs->pStartIdx->GetIndex(); + } + + if( pSpellArgs && pSpellArgs->bIsGrammarCheck ) + { + uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( GetGCIterator() ); + if (xGCIterator.is()) + { + uno::Reference< lang::XComponent > xDoc = GetDocShell()->GetBaseModel(); + // Expand the string: + const ModelToViewHelper aConversionMap(*pNd->GetTextNode(), pLayout); + const OUString& aExpandText = aConversionMap.getViewText(); + + // get XFlatParagraph to use... + uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNd->GetTextNode(), aExpandText, aConversionMap ); + + // get error position of cursor in XFlatParagraph + linguistic2::ProofreadingResult aResult; + bool bGrammarErrors; + do + { + aConversionMap.ConvertToViewPosition( nBeginGrammarCheck ); + aResult = xGCIterator->checkSentenceAtPosition( + xDoc, xFlatPara, aExpandText, lang::Locale(), nBeginGrammarCheck, -1, -1 ); + + lcl_syncGrammarError( *pNd->GetTextNode(), aResult, aConversionMap ); + + // get suggestions to use for the specific error position + bGrammarErrors = aResult.aErrors.hasElements(); + // if grammar checking doesn't have any progress then quit + if( aResult.nStartOfNextSentencePosition <= nBeginGrammarCheck ) + break; + // prepare next iteration + nBeginGrammarCheck = aResult.nStartOfNextSentencePosition; + } + while( nSpellErrorPosition > aResult.nBehindEndOfSentencePosition && !bGrammarErrors && aResult.nBehindEndOfSentencePosition < nEndGrammarCheck ); + + if( bGrammarErrors && nSpellErrorPosition >= aResult.nBehindEndOfSentencePosition ) + { + aRet <<= aResult; + //put the cursor to the current error + const linguistic2::SingleProofreadingError &rError = aResult.aErrors[0]; + nCurrNd = pNd->GetIndex(); + pSttPos->nNode = nCurrNd; + pEndPos->nNode = nCurrNd; + pSpellArgs->pStartNode = pNd->GetTextNode(); + pSpellArgs->pEndNode = pNd->GetTextNode(); + pSpellArgs->pStartIdx->Assign(pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos ); + pSpellArgs->pEndIdx->Assign(pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos ); + nCurrNd = nEndNd; + } + } + } + } + } + break; + case SwNodeType::Section: + if( static_cast(pNd)->GetSection().IsProtect() || + static_cast(pNd)->GetSection().IsHidden() ) + nCurrNd = pNd->EndOfSectionIndex(); + break; + case SwNodeType::End: + { + break; + } + default: break; + } + + bGoOn = nCurrNd < nEndNd; + ++nCurrNd; + } + } + + if( !aRet.hasValue() ) + { + if (pConvArgs) + aRet <<= pConvArgs->aConvText; + else + aRet <<= pSpellArgs->xSpellAlt; + } + + return aRet; +} + +namespace { + +class SwHyphArgs : public SwInterHyphInfo +{ + const SwNode *m_pStart; + const SwNode *m_pEnd; + SwNode *m_pNode; + sal_uInt16 *m_pPageCnt; + sal_uInt16 *m_pPageSt; + + sal_uInt32 m_nNode; + sal_Int32 m_nPamStart; + sal_Int32 m_nPamLen; + +public: + SwHyphArgs( const SwPaM *pPam, const Point &rPoint, + sal_uInt16* pPageCount, sal_uInt16* pPageStart ); + void SetPam( SwPaM *pPam ) const; + void SetNode( SwNode *pNew ) { m_pNode = pNew; } + inline void SetRange( const SwNode *pNew ); + void NextNode() { ++m_nNode; } + sal_uInt16 *GetPageCnt() { return m_pPageCnt; } + sal_uInt16 *GetPageSt() { return m_pPageSt; } +}; + +} + +SwHyphArgs::SwHyphArgs( const SwPaM *pPam, const Point &rCursorPos, + sal_uInt16* pPageCount, sal_uInt16* pPageStart ) + : SwInterHyphInfo( rCursorPos ), m_pNode(nullptr), + m_pPageCnt( pPageCount ), m_pPageSt( pPageStart ) +{ + // The following constraints have to be met: + // 1) there is at least one Selection + // 2) SPoint() == Start() + OSL_ENSURE( pPam->HasMark(), "SwDoc::Hyphenate: blowing in the wind"); + OSL_ENSURE( *pPam->GetPoint() <= *pPam->GetMark(), + "SwDoc::Hyphenate: New York, New York"); + + const SwPosition *pPoint = pPam->GetPoint(); + m_nNode = pPoint->nNode.GetIndex(); + + // Set start + m_pStart = pPoint->nNode.GetNode().GetTextNode(); + m_nPamStart = pPoint->nContent.GetIndex(); + + // Set End and Length + const SwPosition *pMark = pPam->GetMark(); + m_pEnd = pMark->nNode.GetNode().GetTextNode(); + m_nPamLen = pMark->nContent.GetIndex(); + if( pPoint->nNode == pMark->nNode ) + m_nPamLen = m_nPamLen - pPoint->nContent.GetIndex(); +} + +inline void SwHyphArgs::SetRange( const SwNode *pNew ) +{ + m_nStart = m_pStart == pNew ? m_nPamStart : 0; + m_nEnd = m_pEnd == pNew ? m_nPamStart + m_nPamLen : SAL_MAX_INT32; +} + +void SwHyphArgs::SetPam( SwPaM *pPam ) const +{ + if( !m_pNode ) + *pPam->GetPoint() = *pPam->GetMark(); + else + { + pPam->GetPoint()->nNode = m_nNode; + pPam->GetPoint()->nContent.Assign( m_pNode->GetContentNode(), m_nWordStart ); + pPam->GetMark()->nNode = m_nNode; + pPam->GetMark()->nContent.Assign( m_pNode->GetContentNode(), + m_nWordStart + m_nWordLen ); + OSL_ENSURE( m_nNode == m_pNode->GetIndex(), + "SwHyphArgs::SetPam: Pam disaster" ); + } +} + +// Returns true if we can proceed. +static bool lcl_HyphenateNode( const SwNodePtr& rpNd, void* pArgs ) +{ + // Hyphenate returns true if there is a hyphenation point and sets pPam + SwTextNode *pNode = rpNd->GetTextNode(); + SwHyphArgs *pHyphArgs = static_cast(pArgs); + if( pNode ) + { + // sw_redlinehide: this will be called once per node for merged nodes; + // the fully deleted ones won't have frames so are skipped. + SwContentFrame* pContentFrame = pNode->getLayoutFrame( pNode->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + if( pContentFrame && !static_cast(pContentFrame)->IsHiddenNow() ) + { + sal_uInt16 *pPageSt = pHyphArgs->GetPageSt(); + sal_uInt16 *pPageCnt = pHyphArgs->GetPageCnt(); + if( pPageCnt && *pPageCnt && pPageSt ) + { + sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum(); + if( !*pPageSt ) + { + *pPageSt = nPageNr; + if( *pPageCnt < *pPageSt ) + *pPageCnt = *pPageSt; + } + long nStat = nPageNr >= *pPageSt ? nPageNr - *pPageSt + 1 + : nPageNr + *pPageCnt - *pPageSt + 1; + ::SetProgressState( nStat, pNode->GetDoc()->GetDocShell() ); + } + pHyphArgs->SetRange( rpNd ); + if( pNode->Hyphenate( *pHyphArgs ) ) + { + pHyphArgs->SetNode( rpNd ); + return false; + } + } + } + pHyphArgs->NextNode(); + return true; +} + +uno::Reference< XHyphenatedWord > SwDoc::Hyphenate( + SwPaM *pPam, const Point &rCursorPos, + sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + OSL_ENSURE(this == pPam->GetDoc(), "SwDoc::Hyphenate: strangers in the night"); + + if( *pPam->GetPoint() > *pPam->GetMark() ) + pPam->Exchange(); + + SwHyphArgs aHyphArg( pPam, rCursorPos, pPageCnt, pPageSt ); + SwNodeIndex aTmpIdx( pPam->GetMark()->nNode, 1 ); + GetNodes().ForEach( pPam->GetPoint()->nNode, aTmpIdx, + lcl_HyphenateNode, &aHyphArg ); + aHyphArg.SetPam( pPam ); + return aHyphArg.GetHyphWord(); // will be set by lcl_HyphenateNode +} + +// Save the current values to add them as automatic entries to AutoCorrect. +void SwDoc::SetAutoCorrExceptWord( std::unique_ptr pNew ) +{ + mpACEWord = std::move(pNew); +} + +void SwDoc::DeleteAutoCorrExceptWord() +{ + mpACEWord.reset(); +} + +void SwDoc::CountWords( const SwPaM& rPaM, SwDocStat& rStat ) +{ + // This is a modified version of SwDoc::TransliterateText + const SwPosition* pStt = rPaM.Start(); + const SwPosition* pEnd = pStt == rPaM.GetPoint() ? rPaM.GetMark() + : rPaM.GetPoint(); + + const sal_uLong nSttNd = pStt->nNode.GetIndex(); + const sal_uLong nEndNd = pEnd->nNode.GetIndex(); + + const sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + const sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + const SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pStt == pEnd && pTNd ) // no region ? + { + // do nothing + return; + } + + if( nSttNd != nEndNd ) + { + SwNodeIndex aIdx( pStt->nNode ); + if( nSttCnt ) + { + ++aIdx; + if( pTNd ) + pTNd->CountWords( rStat, nSttCnt, pTNd->GetText().getLength() ); + } + + for( ; aIdx.GetIndex() < nEndNd; ++aIdx ) + if( nullptr != ( pTNd = aIdx.GetNode().GetTextNode() )) + pTNd->CountWords( rStat, 0, pTNd->GetText().getLength() ); + + if( nEndCnt && nullptr != ( pTNd = pEnd->nNode.GetNode().GetTextNode() )) + pTNd->CountWords( rStat, 0, nEndCnt ); + } + else if( pTNd && nSttCnt < nEndCnt ) + pTNd->CountWords( rStat, nSttCnt, nEndCnt ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docfld.cxx b/sw/source/core/doc/docfld.cxx new file mode 100644 index 000000000..7ea93bb4a --- /dev/null +++ b/sw/source/core/doc/docfld.cxx @@ -0,0 +1,1168 @@ +/* -*- 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 + +using namespace ::com::sun::star::uno; + +// the StartIndex can be supplied optionally (e.g. if it was queried before - is a virtual +// method otherwise!) +SetGetExpField::SetGetExpField( + const SwNodeIndex& rNdIdx, + const SwTextField* pField, + const SwIndex* pIdx ) +{ + m_eSetGetExpFieldType = TEXTFIELD; + m_CNTNT.pTextField = pField; + m_nNode = rNdIdx.GetIndex(); + if( pIdx ) + m_nContent = pIdx->GetIndex(); + else if( pField ) + m_nContent = pField->GetStart(); + else + m_nContent = 0; +} + +SetGetExpField::SetGetExpField( const SwNodeIndex& rNdIdx, + const SwTextINetFormat& rINet ) +{ + m_eSetGetExpFieldType = TEXTINET; + m_CNTNT.pTextINet = &rINet; + m_nNode = rNdIdx.GetIndex(); + m_nContent = rINet.GetStart(); +} + +// Extension for Sections: +// these always have content position 0xffffffff! +// There is never a field on this, only up to COMPLETE_STRING possible +SetGetExpField::SetGetExpField( const SwSectionNode& rSectNd, + const SwPosition* pPos ) +{ + m_eSetGetExpFieldType = SECTIONNODE; + m_CNTNT.pSection = &rSectNd.GetSection(); + + if( pPos ) + { + m_nNode = pPos->nNode.GetIndex(); + m_nContent = pPos->nContent.GetIndex(); + } + else + { + m_nNode = rSectNd.GetIndex(); + m_nContent = 0; + } +} + +SetGetExpField::SetGetExpField( const SwTableBox& rTBox ) +{ + m_eSetGetExpFieldType = TABLEBOX; + m_CNTNT.pTBox = &rTBox; + + m_nNode = 0; + m_nContent = 0; + if( rTBox.GetSttNd() ) + { + SwNodeIndex aIdx( *rTBox.GetSttNd() ); + const SwContentNode* pNd = aIdx.GetNode().GetNodes().GoNext( &aIdx ); + if( pNd ) + m_nNode = pNd->GetIndex(); + } +} + +SetGetExpField::SetGetExpField( const SwNodeIndex& rNdIdx, + const SwTextTOXMark& rTOX ) +{ + m_eSetGetExpFieldType = TEXTTOXMARK; + m_CNTNT.pTextTOX = &rTOX; + m_nNode = rNdIdx.GetIndex(); + m_nContent = rTOX.GetStart(); +} + +SetGetExpField::SetGetExpField( const SwPosition& rPos ) +{ + m_eSetGetExpFieldType = CRSRPOS; + m_CNTNT.pPos = &rPos; + m_nNode = rPos.nNode.GetIndex(); + m_nContent = rPos.nContent.GetIndex(); +} + +SetGetExpField::SetGetExpField( const SwFlyFrameFormat& rFlyFormat, + const SwPosition* pPos ) +{ + m_eSetGetExpFieldType = FLYFRAME; + m_CNTNT.pFlyFormat = &rFlyFormat; + if( pPos ) + { + m_nNode = pPos->nNode.GetIndex(); + m_nContent = pPos->nContent.GetIndex(); + } + else + { + const SwFormatContent& rContent = rFlyFormat.GetContent(); + m_nNode = rContent.GetContentIdx()->GetIndex() + 1; + m_nContent = 0; + } +} + +void SetGetExpField::GetPosOfContent( SwPosition& rPos ) const +{ + const SwNode* pNd = GetNodeFromContent(); + if( pNd ) + pNd = pNd->GetContentNode(); + + if( pNd ) + { + rPos.nNode = *pNd; + rPos.nContent.Assign( const_cast(static_cast(pNd)), GetCntPosFromContent() ); + } + else + { + rPos.nNode = m_nNode; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), m_nContent ); + } +} + +void SetGetExpField::SetBodyPos( const SwContentFrame& rFrame ) +{ + if( !rFrame.IsInDocBody() ) + { + SwNodeIndex aIdx( rFrame.IsTextFrame() + ? *static_cast(rFrame).GetTextNodeFirst() + : *static_cast(rFrame).GetNode() ); + SwDoc& rDoc = *aIdx.GetNodes().GetDoc(); + SwPosition aPos( aIdx ); + bool const bResult = ::GetBodyTextNode( rDoc, aPos, rFrame ); + OSL_ENSURE(bResult, "Where is the field?"); + m_nNode = aPos.nNode.GetIndex(); + m_nContent = aPos.nContent.GetIndex(); + } +} + +bool SetGetExpField::operator==( const SetGetExpField& rField ) const +{ + return m_nNode == rField.m_nNode + && m_nContent == rField.m_nContent + && ( !m_CNTNT.pTextField + || !rField.m_CNTNT.pTextField + || m_CNTNT.pTextField == rField.m_CNTNT.pTextField ); +} + +bool SetGetExpField::operator<( const SetGetExpField& rField ) const +{ + if( m_nNode < rField.m_nNode || ( m_nNode == rField.m_nNode && m_nContent < rField.m_nContent )) + return true; + else if( m_nNode != rField.m_nNode || m_nContent != rField.m_nContent ) + return false; + + const SwNode *pFirst = GetNodeFromContent(), + *pNext = rField.GetNodeFromContent(); + + // Position is the same: continue only if both field pointers are set! + if( !pFirst || !pNext ) + return false; + + // same Section? + if( pFirst->StartOfSectionNode() != pNext->StartOfSectionNode() ) + { + // is one in the table? + const SwNode *pFirstStt, *pNextStt; + const SwTableNode* pTableNd = pFirst->FindTableNode(); + if( pTableNd ) + pFirstStt = pTableNd->StartOfSectionNode(); + else + pFirstStt = pFirst->StartOfSectionNode(); + + if( nullptr != ( pTableNd = pNext->FindTableNode() ) ) + pNextStt = pTableNd->StartOfSectionNode(); + else + pNextStt = pNext->StartOfSectionNode(); + + if( pFirstStt != pNextStt ) + { + if( pFirst->IsTextNode() && pNext->IsTextNode() && + ( pFirst->FindFlyStartNode() || pNext->FindFlyStartNode() )) + { + // FIXME: in NewFieldPortion(), SwGetExpField are expanded via + // DocumentFieldsManager::FieldsToExpand() calling + // std::upper_bound binary search function - the sort order + // depends on the fly positions in the layout, but the fly + // positions depend on the expansion of the SwGetExpField! + // This circular dep will cause trouble, it would be better to + // use only model positions (anchor), but then how to compare + // at-page anchored flys which don't have a model anchor? + return ::IsFrameBehind( *pNext->GetTextNode(), m_nContent, *pFirst->GetTextNode(), m_nContent ); + } + return pFirstStt->GetIndex() < pNextStt->GetIndex(); + } + } + + // same Section: is the field in the same Node? + if( pFirst != pNext ) + return pFirst->GetIndex() < pNext->GetIndex(); + + // same Node in the Section, check Position in the Node + return GetCntPosFromContent() < rField.GetCntPosFromContent(); +} + +const SwNode* SetGetExpField::GetNodeFromContent() const +{ + const SwNode* pRet = nullptr; + if( m_CNTNT.pTextField ) + switch( m_eSetGetExpFieldType ) + { + case TEXTFIELD: + pRet = &m_CNTNT.pTextField->GetTextNode(); + break; + + case TEXTINET: + pRet = &m_CNTNT.pTextINet->GetTextNode(); + break; + + case SECTIONNODE: + pRet = m_CNTNT.pSection->GetFormat()->GetSectionNode(); + break; + + case CRSRPOS: + pRet = &m_CNTNT.pPos->nNode.GetNode(); + break; + + case TEXTTOXMARK: + pRet = &m_CNTNT.pTextTOX->GetTextNode(); + break; + + case TABLEBOX: + if( m_CNTNT.pTBox->GetSttNd() ) + { + SwNodeIndex aIdx( *m_CNTNT.pTBox->GetSttNd() ); + pRet = aIdx.GetNode().GetNodes().GoNext( &aIdx ); + } + break; + + case FLYFRAME: + { + SwNodeIndex aIdx( *m_CNTNT.pFlyFormat->GetContent().GetContentIdx() ); + pRet = aIdx.GetNode().GetNodes().GoNext( &aIdx ); + } + break; + } + return pRet; +} + +sal_Int32 SetGetExpField::GetCntPosFromContent() const +{ + sal_Int32 nRet = 0; + if( m_CNTNT.pTextField ) + switch( m_eSetGetExpFieldType ) + { + case TEXTFIELD: + nRet = m_CNTNT.pTextField->GetStart(); + break; + case TEXTINET: + nRet = m_CNTNT.pTextINet->GetStart(); + break; + case TEXTTOXMARK: + nRet = m_CNTNT.pTextTOX->GetStart(); + break; + case CRSRPOS: + nRet = m_CNTNT.pPos->nContent.GetIndex(); + break; + default: + break; + } + return nRet; +} + +HashStr::HashStr( const OUString& rName, const OUString& rText, + HashStr* pNxt ) + : SwHash( rName ), aSetStr( rText ) +{ + pNext.reset( pNxt ); +} + +/// Look up the Name, if it is present, return its String, otherwise return an empty String +OUString LookString( SwHashTable const & rTable, const OUString& rName ) +{ + HashStr* pFnd = rTable.Find( comphelper::string::strip(rName, ' ') ); + if( pFnd ) + return pFnd->aSetStr; + + return OUString(); +} + +SwDBData const & SwDoc::GetDBData() +{ + return GetDBDesc(); +} + +const SwDBData& SwDoc::GetDBDesc() +{ +#if HAVE_FEATURE_DBCONNECTIVITY + if(maDBData.sDataSource.isEmpty()) + { + const SwFieldTypes::size_type nSize = getIDocumentFieldsAccess().GetFieldTypes()->size(); + for(SwFieldTypes::size_type i = 0; i < nSize && maDBData.sDataSource.isEmpty(); ++i) + { + SwFieldType& rFieldType = *((*getIDocumentFieldsAccess().GetFieldTypes())[i]); + SwFieldIds nWhich = rFieldType.Which(); + if(IsUsed(rFieldType)) + { + switch(nWhich) + { + case SwFieldIds::Database: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + { + std::vector vFields; + rFieldType.GatherFields(vFields); + if(vFields.size()) + { + if(SwFieldIds::Database == nWhich) + maDBData = static_cast(vFields.front()->GetField()->GetTyp())->GetDBData(); + else + maDBData = static_cast (vFields.front()->GetField())->GetRealDBData(); + } + } + break; + default: break; + } + } + } + } + if(maDBData.sDataSource.isEmpty()) + maDBData = SwDBManager::GetAddressDBName(); +#endif + return maDBData; +} + +void SwDoc::SetInitDBFields( bool b ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) b; +#else + GetDBManager()->SetInitDBFields( b ); +#endif +} + +#if HAVE_FEATURE_DBCONNECTIVITY + +/// Get all databases that are used by fields +static OUString lcl_DBDataToString(const SwDBData& rData) +{ + return rData.sDataSource + OUStringChar(DB_DELIM) + + rData.sCommand + OUStringChar(DB_DELIM) + + OUString::number(rData.nCommandType); +} + +#endif + +void SwDoc::GetAllUsedDB( std::vector& rDBNameList, + const std::vector* pAllDBNames ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDBNameList; + (void) pAllDBNames; +#else + std::vector aUsedDBNames; + std::vector aAllDBNames; + + if( !pAllDBNames ) + { + GetAllDBNames( aAllDBNames ); + pAllDBNames = &aAllDBNames; + } + + SwSectionFormats& rArr = GetSections(); + for (auto n = rArr.size(); n; ) + { + SwSection* pSect = rArr[ --n ]->GetSection(); + + if( pSect ) + { + AddUsedDBToList( rDBNameList, FindUsedDBs( *pAllDBNames, + pSect->GetCondition(), aUsedDBNames ) ); + aUsedDBNames.clear(); + } + } + + for (sal_uInt16 const nWhichHint : { RES_TXTATR_FIELD, RES_TXTATR_INPUTFIELD }) + { + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(nWhichHint)) + { + const SwFormatField* pFormatField = static_cast(pItem); + const SwTextField* pTextField = pFormatField->GetTextField(); + if (!pTextField || !pTextField->GetTextNode().GetNodes().IsDocNodes()) + continue; + + const SwField* pField = pFormatField->GetField(); + switch (pField->GetTyp()->Which()) + { + case SwFieldIds::Database: + AddUsedDBToList( rDBNameList, + lcl_DBDataToString(static_cast(pField)->GetDBData() )); + break; + + case SwFieldIds::DbSetNumber: + case SwFieldIds::DatabaseName: + AddUsedDBToList( rDBNameList, + lcl_DBDataToString(static_cast(pField)->GetRealDBData() )); + break; + + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + AddUsedDBToList( rDBNameList, + lcl_DBDataToString(static_cast(pField)->GetRealDBData() )); + [[fallthrough]]; // JP: is that right like that? + + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + AddUsedDBToList(rDBNameList, FindUsedDBs( *pAllDBNames, + pField->GetPar1(), aUsedDBNames )); + aUsedDBNames.clear(); + break; + + case SwFieldIds::SetExp: + case SwFieldIds::GetExp: + case SwFieldIds::Table: + AddUsedDBToList(rDBNameList, FindUsedDBs( *pAllDBNames, + pField->GetFormula(), aUsedDBNames )); + aUsedDBNames.clear(); + break; + default: break; + } + } + } +#endif +} + +void SwDoc::GetAllDBNames( std::vector& rAllDBNames ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rAllDBNames; +#else + SwDBManager* pMgr = GetDBManager(); + + const SwDSParams_t& rArr = pMgr->GetDSParamArray(); + for (const auto& pParam : rArr) + { + rAllDBNames.emplace_back(pParam->sDataSource + OUStringChar(DB_DELIM) + pParam->sCommand); + } +#endif +} + +std::vector& SwDoc::FindUsedDBs( const std::vector& rAllDBNames, + const OUString& rFormula, + std::vector& rUsedDBNames ) +{ + const CharClass& rCC = GetAppCharClass(); +#ifndef UNX + const OUString sFormula(rCC.uppercase( rFormula )); +#else + const OUString sFormula(rFormula); +#endif + + for (const auto &sItem : rAllDBNames) + { + sal_Int32 nPos = sFormula.indexOf( sItem ); + if( nPos>=0 && + sFormula[ nPos + sItem.getLength() ] == '.' && + (!nPos || !rCC.isLetterNumeric( sFormula, nPos - 1 ))) + { + // Look up table name + nPos += sItem.getLength() + 1; + const sal_Int32 nEndPos = sFormula.indexOf('.', nPos); + if( nEndPos>=0 ) + { + rUsedDBNames.emplace_back(sItem + OUStringChar(DB_DELIM) + sFormula.copy( nPos, nEndPos - nPos )); + } + } + } + return rUsedDBNames; +} + +void SwDoc::AddUsedDBToList( std::vector& rDBNameList, + const std::vector& rUsedDBNames ) +{ + for ( const auto &sName : rUsedDBNames ) + AddUsedDBToList( rDBNameList, sName ); +} + +void SwDoc::AddUsedDBToList( std::vector& rDBNameList, const OUString& rDBName) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDBNameList; + (void) rDBName; +#else + if( rDBName.isEmpty() ) + return; + +#ifdef UNX + for( const auto &sName : rDBNameList ) + if( rDBName == sName.getToken(0, ';') ) + return; +#else + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + for( const auto &sName : rDBNameList ) + if( rSCmp.isEqual( rDBName, sName.getToken(0, ';') ) ) + return; +#endif + + SwDBData aData; + sal_Int32 nIdx{ 0 }; + aData.sDataSource = rDBName.getToken(0, DB_DELIM, nIdx); + aData.sCommand = rDBName.getToken(0, DB_DELIM, nIdx); + aData.nCommandType = -1; + GetDBManager()->CreateDSData(aData); + rDBNameList.push_back(rDBName); +#endif +} + +void SwDoc::ChangeDBFields( const std::vector& rOldNames, + const OUString& rNewName ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rOldNames; + (void) rNewName; +#else + SwDBData aNewDBData; + sal_Int32 nIdx{ 0 }; + aNewDBData.sDataSource = rNewName.getToken(0, DB_DELIM, nIdx); + aNewDBData.sCommand = rNewName.getToken(0, DB_DELIM, nIdx); + aNewDBData.nCommandType = static_cast(rNewName.getToken(0, DB_DELIM, nIdx).toInt32()); + + SwSectionFormats& rArr = GetSections(); + for (auto n = rArr.size(); n; ) + { + SwSection* pSect = rArr[ --n ]->GetSection(); + + if( pSect ) + { + pSect->SetCondition(ReplaceUsedDBs(rOldNames, rNewName, pSect->GetCondition())); + } + } + + for (sal_uInt16 const nWhichHint : { RES_TXTATR_FIELD, RES_TXTATR_INPUTFIELD }) + { + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(nWhichHint)) + { + SwFormatField* pFormatField = const_cast(static_cast(pItem)); + SwTextField* pTextField = pFormatField->GetTextField(); + if (!pTextField || !pTextField->GetTextNode().GetNodes().IsDocNodes()) + continue; + + SwField* pField = pFormatField->GetField(); + bool bExpand = false; + + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::Database: +#if HAVE_FEATURE_DBCONNECTIVITY + if (IsNameInArray(rOldNames, lcl_DBDataToString(static_cast(pField)->GetDBData()))) + { + SwDBFieldType* pOldTyp = static_cast(pField->GetTyp()); + + SwDBFieldType* pTyp = static_cast(getIDocumentFieldsAccess().InsertFieldType( + SwDBFieldType(this, pOldTyp->GetColumnName(), aNewDBData))); + + pFormatField->RegisterToFieldType( *pTyp ); + pField->ChgTyp(pTyp); + + static_cast(pField)->ClearInitialized(); + static_cast(pField)->InitContent(); + + bExpand = true; + } +#endif + break; + + case SwFieldIds::DbSetNumber: + case SwFieldIds::DatabaseName: + if (IsNameInArray(rOldNames, + lcl_DBDataToString(static_cast(pField)->GetRealDBData()))) + { + static_cast(pField)->SetDBData(aNewDBData); + bExpand = true; + } + break; + + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + if (IsNameInArray(rOldNames, + lcl_DBDataToString(static_cast(pField)->GetRealDBData()))) + { + static_cast(pField)->SetDBData(aNewDBData); + } + [[fallthrough]]; + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + pField->SetPar1( ReplaceUsedDBs(rOldNames, rNewName, pField->GetPar1()) ); + bExpand = true; + break; + + case SwFieldIds::SetExp: + case SwFieldIds::GetExp: + case SwFieldIds::Table: + pField->SetPar2( ReplaceUsedDBs(rOldNames, rNewName, pField->GetFormula()) ); + bExpand = true; + break; + default: break; + } + + if (bExpand) + pTextField->ExpandTextField( true ); + } + } + getIDocumentState().SetModified(); +#endif +} + +namespace +{ + +OUString lcl_CutOffDBCommandType(const OUString& rName) +{ + return rName.replaceFirst(OUStringChar(DB_DELIM), ".").getToken(0, DB_DELIM); +} + +} + +OUString SwDoc::ReplaceUsedDBs( const std::vector& rUsedDBNames, + const OUString& rNewName, const OUString& rFormula ) +{ + const CharClass& rCC = GetAppCharClass(); + const OUString sNewName( lcl_CutOffDBCommandType(rNewName) ); + OUString sFormula(rFormula); + + for(const auto & rUsedDBName : rUsedDBNames) + { + const OUString sDBName( lcl_CutOffDBCommandType(rUsedDBName) ); + + if (sDBName!=sNewName) + { + sal_Int32 nPos = 0; + for (;;) + { + nPos = sFormula.indexOf(sDBName, nPos); + if (nPos<0) + { + break; + } + + if( sFormula[nPos + sDBName.getLength()] == '.' && + (!nPos || !rCC.isLetterNumeric( sFormula, nPos - 1 ))) + { + sFormula = sFormula.replaceAt(nPos, sDBName.getLength(), sNewName); + //prevent re-searching - this is useless and provokes + //endless loops when names containing each other and numbers are exchanged + //e.g.: old ?12345.12345 new: i12345.12345 + nPos += sNewName.getLength(); + } + } + } + } + return sFormula; +} + +bool SwDoc::IsNameInArray( const std::vector& rArr, const OUString& rName ) +{ +#ifdef UNX + for( const auto &sName : rArr ) + if( rName == sName ) + return true; +#else + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + for( const auto &sName : rArr ) + if( rSCmp.isEqual( rName, sName )) + return true; +#endif + return false; +} + +void SwDoc::ChangeAuthorityData( const SwAuthEntry* pNewData ) +{ + const SwFieldTypes::size_type nSize = getIDocumentFieldsAccess().GetFieldTypes()->size(); + + for( SwFieldTypes::size_type i = INIT_FLDTYPES; i < nSize; ++i ) + { + SwFieldType* pFieldType = (*getIDocumentFieldsAccess().GetFieldTypes())[i].get(); + if( SwFieldIds::TableOfAuthorities == pFieldType->Which() ) + { + SwAuthorityFieldType* pAuthType = static_cast(pFieldType); + pAuthType->ChangeEntryContent(pNewData); + break; + } + } + +} + +void SwDocUpdateField::InsDelFieldInFieldLst( bool bIns, const SwTextField& rField ) +{ + const SwFieldIds nWhich = rField.GetFormatField().GetField()->GetTyp()->Which(); + switch( nWhich ) + { + case SwFieldIds::Database: + case SwFieldIds::SetExp: + case SwFieldIds::HiddenPara: + case SwFieldIds::HiddenText: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbSetNumber: + case SwFieldIds::GetExp: + break; // these have to be added/removed! + + default: + return; + } + + SetFieldsDirty( true ); + if (!m_pFieldSortList) + { + if( !bIns ) // if list is present and deleted + return; // don't do a thing + m_pFieldSortList.reset(new SetGetExpFields); + } + + if( bIns ) // insert anew: + GetBodyNode( rField, nWhich ); + else + { + // look up via the pTextField pointer. It is a sorted list, but it's sorted by node + // position. Until this is found, the search for the pointer is already done. + for (SetGetExpFields::size_type n = 0; n < m_pFieldSortList->size(); ++n) + { + if (&rField == (*m_pFieldSortList)[n]->GetPointer()) + { + m_pFieldSortList->erase(n); + n--; // one field can occur multiple times + } + } + } +} + +void SwDocUpdateField::MakeFieldList( SwDoc& rDoc, bool bAll, int eGetMode ) +{ + if (!m_pFieldSortList || bAll + || ((eGetMode & m_nFieldListGetMode) != eGetMode) + || rDoc.GetNodes().Count() != m_nNodes) + { + MakeFieldList_( rDoc, eGetMode ); + } +} + +void SwDocUpdateField::MakeFieldList_( SwDoc& rDoc, int eGetMode ) +{ + // new version: walk all fields of the attribute pool + m_pFieldSortList.reset(new SetGetExpFields); + + // consider and unhide sections + // with hide condition, only in mode GETFLD_ALL () + // notes by OD: + // eGetMode == GETFLD_CALC in call from methods SwDoc::FieldsToCalc + // eGetMode == GETFLD_EXPAND in call from method SwDoc::FieldsToExpand + // eGetMode == GETFLD_ALL in call from method SwDoc::UpdateExpFields + // I figured out that hidden section only have to be shown, + // if fields have updated (call by SwDoc::UpdateExpFields) and thus + // the hide conditions of section have to be updated. + // For correct updating the hide condition of a section, its position + // have to be known in order to insert the hide condition as a new + // expression field into the sorted field list (). + if ( eGetMode == GETFLD_ALL ) + // Collect the sections first. Supply sections that are hidden by condition + // with frames so that the contained fields are sorted properly. + { + // In order for the frames to be created the right way, they have to be expanded + // from top to bottom + std::vector aTmpArr; + std::vector::size_type nArrStt = 0; + SwSectionFormats& rArr = rDoc.GetSections(); + SwSectionNode* pSectNd = nullptr; + sal_uLong nSttContent = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + + for (SwSectionFormats::size_type n = rArr.size(); n; ) + { + SwSection* pSect = rArr[ --n ]->GetSection(); + if( !pSect || !pSect->IsHidden() || pSect->GetCondition().isEmpty() ) + continue; + pSectNd = pSect->GetFormat()->GetSectionNode(); + if( pSectNd ) + { + sal_uLong nIdx = pSectNd->GetIndex(); + aTmpArr.push_back( nIdx ); + if( nIdx < nSttContent ) + ++nArrStt; + } + } + std::sort(aTmpArr.begin(), aTmpArr.end()); + + // Display all first so that we have frames. The BodyAnchor is defined by that. + // First the ContentArea, then the special areas! + for (std::vector::size_type n = nArrStt; n < aTmpArr.size(); ++n) + { + pSectNd = rDoc.GetNodes()[ aTmpArr[ n ] ]->GetSectionNode(); + OSL_ENSURE( pSectNd, "Where is my SectionNode" ); + pSectNd->GetSection().SetCondHidden( false ); + } + for (std::vector::size_type n = 0; n < nArrStt; ++n) + { + pSectNd = rDoc.GetNodes()[ aTmpArr[ n ] ]->GetSectionNode(); + OSL_ENSURE( pSectNd, "Where is my SectionNode" ); + pSectNd->GetSection().SetCondHidden( false ); + } + + // add all to the list so that they are sorted + for (const auto &nId : aTmpArr) + { + GetBodyNode( *rDoc.GetNodes()[ nId ]->GetSectionNode() ); + } + } + + const OUString sTrue("TRUE"); + const OUString sFalse("FALSE"); + +#if HAVE_FEATURE_DBCONNECTIVITY + bool bIsDBManager = nullptr != rDoc.GetDBManager(); +#endif + + for (sal_uInt16 const nWhichHint : { RES_TXTATR_FIELD, RES_TXTATR_INPUTFIELD }) + { + for (const SfxPoolItem* pItem : rDoc.GetAttrPool().GetItemSurrogates(nWhichHint)) + { + const SwFormatField* pFormatField = static_cast(pItem); + const SwTextField* pTextField = pFormatField->GetTextField(); + if (!pTextField || !pTextField->GetTextNode().GetNodes().IsDocNodes()) + continue; + + OUString sFormula; + const SwField* pField = pFormatField->GetField(); + const SwFieldIds nWhich = pField->GetTyp()->Which(); + switch (nWhich) + { + case SwFieldIds::DbSetNumber: + case SwFieldIds::GetExp: + if (GETFLD_ALL == eGetMode) + sFormula = sTrue; + break; + + case SwFieldIds::Database: + if (GETFLD_EXPAND & eGetMode) + sFormula = sTrue; + break; + + case SwFieldIds::SetExp: + if ((eGetMode != GETFLD_EXPAND) || + (nsSwGetSetExpType::GSE_STRING & pField->GetSubType())) + { + sFormula = sTrue; + } + break; + + case SwFieldIds::HiddenPara: + if (GETFLD_ALL == eGetMode) + { + sFormula = pField->GetPar1(); + if (sFormula.isEmpty() || sFormula==sFalse) + const_cast(static_cast(pField))->SetHidden( false ); + else if (sFormula==sTrue) + const_cast(static_cast(pField))->SetHidden( true ); + else + break; + + sFormula.clear(); + // trigger formatting + const_cast(pFormatField)->ModifyNotification( nullptr, nullptr ); + } + break; + + case SwFieldIds::HiddenText: + if (GETFLD_ALL == eGetMode) + { + sFormula = pField->GetPar1(); + if (sFormula.isEmpty() || sFormula==sFalse) + const_cast(static_cast(pField))->SetValue( true ); + else if (sFormula==sTrue) + const_cast(static_cast(pField))->SetValue( false ); + else + break; + + sFormula.clear(); + + // evaluate field + const_cast(static_cast(pField))->Evaluate(&rDoc); + // trigger formatting + const_cast(pFormatField)->UpdateTextNode(nullptr, nullptr); + } + break; + +#if HAVE_FEATURE_DBCONNECTIVITY + case SwFieldIds::DbNumSet: + { + SwDBData aDBData(const_cast(static_cast(pField))->GetDBData(&rDoc)); + + if ( (bIsDBManager && rDoc.GetDBManager()->OpenDataSource(aDBData.sDataSource, aDBData.sCommand)) + && (GETFLD_ALL == eGetMode + || (GETFLD_CALC & eGetMode + && static_cast(pField)->IsCondValid())) + ) + { + sFormula = pField->GetPar1(); + } + } + break; + case SwFieldIds::DbNextSet: + { + SwDBData aDBData(const_cast(static_cast(pField))->GetDBData(&rDoc)); + + if ( (bIsDBManager && rDoc.GetDBManager()->OpenDataSource(aDBData.sDataSource, aDBData.sCommand)) + && (GETFLD_ALL == eGetMode + || (GETFLD_CALC & eGetMode + && static_cast(pField)->IsCondValid())) + ) + { + sFormula = pField->GetPar1(); + } + } + break; +#endif + default: break; + } + + if (!sFormula.isEmpty()) + { + GetBodyNode( *pTextField, nWhich ); + } + } + } + m_nFieldListGetMode = eGetMode; + m_nNodes = rDoc.GetNodes().Count(); +} + +void SwDocUpdateField::GetBodyNode( const SwTextField& rTField, SwFieldIds nFieldWhich ) +{ + const SwTextNode& rTextNd = rTField.GetTextNode(); + const SwDoc& rDoc = *rTextNd.GetDoc(); + + // always the first! (in tab headline, header-/footer) + Point aPt; + std::pair const tmp(aPt, false); + const SwContentFrame* pFrame = rTextNd.getLayoutFrame( + rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); + + std::unique_ptr pNew; + bool bIsInBody = false; + + if( !pFrame || pFrame->IsInDocBody() ) + { + // create index to determine the TextNode + SwNodeIndex aIdx( rTextNd ); + bIsInBody = rDoc.GetNodes().GetEndOfExtras().GetIndex() < aIdx.GetIndex(); + + // We don't want to update fields in redlines, or those + // in frames whose anchor is in redline. However, we do want to update + // fields in hidden sections. So: In order to be updated, a field 1) + // must have a frame, or 2) it must be in the document body. + if( (pFrame != nullptr) || bIsInBody ) + pNew.reset(new SetGetExpField( aIdx, &rTField )); + } + else + { + // create index to determine the TextNode + SwPosition aPos( rDoc.GetNodes().GetEndOfPostIts() ); + bool const bResult = GetBodyTextNode( rDoc, aPos, *pFrame ); + OSL_ENSURE(bResult, "where is the Field"); + pNew.reset(new SetGetExpField( aPos.nNode, &rTField, &aPos.nContent )); + } + + // always set the BodyTextFlag in GetExp or DB fields + if( SwFieldIds::GetExp == nFieldWhich ) + { + SwGetExpField* pGetField = const_cast(static_cast(rTField.GetFormatField().GetField())); + pGetField->ChgBodyTextFlag( bIsInBody ); + } +#if HAVE_FEATURE_DBCONNECTIVITY + else if( SwFieldIds::Database == nFieldWhich ) + { + SwDBField* pDBField = const_cast(static_cast(rTField.GetFormatField().GetField())); + pDBField->ChgBodyTextFlag( bIsInBody ); + } +#endif + if( pNew != nullptr ) + m_pFieldSortList->insert( std::move(pNew) ); +} + +void SwDocUpdateField::GetBodyNode( const SwSectionNode& rSectNd ) +{ + const SwDoc& rDoc = *rSectNd.GetDoc(); + std::unique_ptr pNew; + + if( rSectNd.GetIndex() < rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + do { // middle check loop + + // we need to get the anchor first + // create index to determine the TextNode + SwPosition aPos( rSectNd ); + SwContentNode* pCNd = rDoc.GetNodes().GoNext( &aPos.nNode ); // to the next ContentNode + + if( !pCNd || !pCNd->IsTextNode() ) + break; + + // always the first! (in tab headline, header-/footer) + Point aPt; + std::pair const tmp(aPt, false); + const SwContentFrame* pFrame = pCNd->getLayoutFrame( + rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + if( !pFrame ) + break; + + bool const bResult = GetBodyTextNode( rDoc, aPos, *pFrame ); + OSL_ENSURE(bResult, "where is the Field"); + pNew.reset(new SetGetExpField( rSectNd, &aPos )); + + } while( false ); + } + + if( !pNew ) + pNew.reset(new SetGetExpField( rSectNd )); + + m_pFieldSortList->insert( std::move(pNew) ); +} + +void SwDocUpdateField::InsertFieldType( const SwFieldType& rType ) +{ + OUString sFieldName; + switch( rType.Which() ) + { + case SwFieldIds::User : + sFieldName = static_cast(rType).GetName(); + break; + case SwFieldIds::SetExp: + sFieldName = static_cast(rType).GetName(); + break; + default: + OSL_ENSURE( false, "No valid field type" ); + } + + if( !sFieldName.isEmpty() ) + { + SetFieldsDirty( true ); + // look up and remove from the hash table + sFieldName = GetAppCharClass().lowercase( sFieldName ); + sal_uInt16 n; + + SwCalcFieldType* pFnd = GetFieldTypeTable().Find( sFieldName, &n ); + + if( !pFnd ) + { + SwCalcFieldType* pNew = new SwCalcFieldType( sFieldName, &rType ); + pNew->pNext.reset( m_FieldTypeTable[n].release() ); + m_FieldTypeTable[n].reset(pNew); + } + } +} + +void SwDocUpdateField::RemoveFieldType( const SwFieldType& rType ) +{ + OUString sFieldName; + switch( rType.Which() ) + { + case SwFieldIds::User : + sFieldName = static_cast(rType).GetName(); + break; + case SwFieldIds::SetExp: + sFieldName = static_cast(rType).GetName(); + break; + default: break; + } + + if( !sFieldName.isEmpty() ) + { + SetFieldsDirty( true ); + // look up and remove from the hash table + sFieldName = GetAppCharClass().lowercase( sFieldName ); + sal_uInt16 n; + + SwCalcFieldType* pFnd = GetFieldTypeTable().Find( sFieldName, &n ); + if( pFnd ) + { + if (m_FieldTypeTable[n].get() == pFnd) + { + m_FieldTypeTable[n].reset(static_cast(pFnd->pNext.release())); + } + else + { + SwHash* pPrev = m_FieldTypeTable[n].get(); + while( pPrev->pNext.get() != pFnd ) + pPrev = pPrev->pNext.get(); + pPrev->pNext = std::move(pFnd->pNext); + // no need to explicitly delete here, the embedded linked list uses unique_ptr + } + } + } +} + +SwDocUpdateField::SwDocUpdateField(SwDoc& rDoc) + : m_FieldTypeTable(TBLSZ) + , m_nNodes(0) + , m_nFieldListGetMode(0) + , m_rDoc(rDoc) + , m_bInUpdateFields(false) + , m_bFieldsDirty(false) +{ +} + +SwDocUpdateField::~SwDocUpdateField() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docfly.cxx b/sw/source/core/doc/docfly.cxx new file mode 100644 index 000000000..988e59c76 --- /dev/null +++ b/sw/source/core/doc/docfly.cxx @@ -0,0 +1,1159 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +size_t SwDoc::GetFlyCount( FlyCntType eType, bool bIgnoreTextBoxes ) const +{ + const SwFrameFormats& rFormats = *GetSpzFrameFormats(); + const size_t nSize = rFormats.size(); + size_t nCount = 0; + const SwNodeIndex* pIdx; + + for ( size_t i = 0; i < nSize; ++i) + { + const SwFrameFormat* pFlyFormat = rFormats[ i ]; + + if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) + continue; + + if( RES_FLYFRMFMT != pFlyFormat->Which() ) + continue; + pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNodes().IsDocNodes() ) + { + const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; + + switch( eType ) + { + case FLYCNTTYPE_FRM: + if(!pNd->IsNoTextNode()) + nCount++; + break; + + case FLYCNTTYPE_GRF: + if( pNd->IsGrfNode() ) + nCount++; + break; + + case FLYCNTTYPE_OLE: + if(pNd->IsOLENode()) + nCount++; + break; + + default: + nCount++; + } + } + } + return nCount; +} + +/// @attention If you change this, also update SwXFrameEnumeration in unocoll. +SwFrameFormat* SwDoc::GetFlyNum( size_t nIdx, FlyCntType eType, bool bIgnoreTextBoxes ) +{ + SwFrameFormats& rFormats = *GetSpzFrameFormats(); + SwFrameFormat* pRetFormat = nullptr; + const size_t nSize = rFormats.size(); + const SwNodeIndex* pIdx; + size_t nCount = 0; + + for( size_t i = 0; !pRetFormat && i < nSize; ++i ) + { + SwFrameFormat* pFlyFormat = rFormats[ i ]; + + if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) + continue; + + if( RES_FLYFRMFMT != pFlyFormat->Which() ) + continue; + pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNodes().IsDocNodes() ) + { + const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; + switch( eType ) + { + case FLYCNTTYPE_FRM: + if( !pNd->IsNoTextNode() && nIdx == nCount++) + pRetFormat = pFlyFormat; + break; + case FLYCNTTYPE_GRF: + if(pNd->IsGrfNode() && nIdx == nCount++ ) + pRetFormat = pFlyFormat; + break; + case FLYCNTTYPE_OLE: + if(pNd->IsOLENode() && nIdx == nCount++) + pRetFormat = pFlyFormat; + break; + default: + if(nIdx == nCount++) + pRetFormat = pFlyFormat; + } + } + } + return pRetFormat; +} + +std::vector SwDoc::GetFlyFrameFormats( + FlyCntType const eType, bool const bIgnoreTextBoxes) +{ + SwFrameFormats& rFormats = *GetSpzFrameFormats(); + const size_t nSize = rFormats.size(); + + std::vector ret; + ret.reserve(nSize); + + for (size_t i = 0; i < nSize; ++i) + { + SwFrameFormat const*const pFlyFormat = rFormats[ i ]; + + if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) + { + continue; + } + + if (RES_FLYFRMFMT != pFlyFormat->Which()) + { + continue; + } + + SwNodeIndex const*const pIdx(pFlyFormat->GetContent().GetContentIdx()); + if (pIdx && pIdx->GetNodes().IsDocNodes()) + { + SwNode const*const pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; + switch (eType) + { + case FLYCNTTYPE_FRM: + if (!pNd->IsNoTextNode()) + ret.push_back(pFlyFormat); + break; + case FLYCNTTYPE_GRF: + if (pNd->IsGrfNode()) + ret.push_back(pFlyFormat); + break; + case FLYCNTTYPE_OLE: + if (pNd->IsOLENode()) + ret.push_back(pFlyFormat); + break; + default: + ret.push_back(pFlyFormat); + } + } + } + + return ret; +} + +static Point lcl_FindAnchorLayPos( SwDoc& rDoc, const SwFormatAnchor& rAnch, + const SwFrameFormat* pFlyFormat ) +{ + Point aRet; + if( rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) + switch( rAnch.GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + if( pFlyFormat && rAnch.GetContentAnchor() ) + { + const SwFrame* pOld = static_cast(pFlyFormat)->GetFrame( &aRet ); + if( pOld ) + aRet = pOld->getFrameArea().Pos(); + } + break; + + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL + if( rAnch.GetContentAnchor() ) + { + const SwPosition *pPos = rAnch.GetContentAnchor(); + const SwContentNode* pNd = pPos->nNode.GetNode().GetContentNode(); + std::pair const tmp(aRet, false); + const SwFrame* pOld = pNd ? pNd->getLayoutFrame(rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + if( pOld ) + aRet = pOld->getFrameArea().Pos(); + } + break; + + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + if( rAnch.GetContentAnchor() ) + { + const SwFlyFrameFormat* pFormat = static_cast(rAnch.GetContentAnchor()-> + nNode.GetNode().GetFlyFormat()); + const SwFrame* pOld = pFormat ? pFormat->GetFrame( &aRet ) : nullptr; + if( pOld ) + aRet = pOld->getFrameArea().Pos(); + } + break; + + case RndStdIds::FLY_AT_PAGE: + { + sal_uInt16 nPgNum = rAnch.GetPageNum(); + const SwPageFrame *pPage = static_cast(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->Lower()); + for( sal_uInt16 i = 1; (i <= nPgNum) && pPage; ++i, + pPage =static_cast(pPage->GetNext()) ) + if( i == nPgNum ) + { + aRet = pPage->getFrameArea().Pos(); + break; + } + } + break; + default: + break; + } + return aRet; +} + +#define MAKEFRMS 0 +#define IGNOREANCHOR 1 +#define DONTMAKEFRMS 2 + +sal_Int8 SwDoc::SetFlyFrameAnchor( SwFrameFormat& rFormat, SfxItemSet& rSet, bool bNewFrames ) +{ + // Changing anchors is almost always allowed. + // Exception: Paragraph and character bound frames must not become + // page bound, if they are located in the header or footer. + const SwFormatAnchor &rOldAnch = rFormat.GetAnchor(); + const RndStdIds nOld = rOldAnch.GetAnchorId(); + + SwFormatAnchor aNewAnch( rSet.Get( RES_ANCHOR ) ); + RndStdIds nNew = aNewAnch.GetAnchorId(); + + // Is the new anchor valid? + if( !aNewAnch.GetContentAnchor() && (RndStdIds::FLY_AT_FLY == nNew || + (RndStdIds::FLY_AT_PARA == nNew) || (RndStdIds::FLY_AS_CHAR == nNew) || + (RndStdIds::FLY_AT_CHAR == nNew) )) + { + return IGNOREANCHOR; + } + + if( nOld == nNew ) + return DONTMAKEFRMS; + + Point aOldAnchorPos( ::lcl_FindAnchorLayPos( *this, rOldAnch, &rFormat )); + Point aNewAnchorPos( ::lcl_FindAnchorLayPos( *this, aNewAnch, nullptr )); + + // Destroy the old Frames. + // The Views are hidden implicitly, so hiding them another time would be + // kind of a show! + rFormat.DelFrames(); + + if ( RndStdIds::FLY_AS_CHAR == nOld ) + { + // We need to handle InContents in a special way: + // The TextAttribut needs to be destroyed which, unfortunately, also + // destroys the format. To avoid that, we disconnect the format from + // the attribute. + const SwPosition *pPos = rOldAnch.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); + OSL_ENSURE( pHint && pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint && pHint->GetFlyCnt().GetFrameFormat() == &rFormat, + "Wrong TextFlyCnt-Hint." ); + if (pHint) + const_cast(pHint->GetFlyCnt()).SetFlyFormat(); + + // They are disconnected. We now have to destroy the attribute. + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + + // We can finally set the attribute. It needs to be the first one! + // Undo depends on it! + rFormat.SetFormatAttr( aNewAnch ); + + // Correct the position + const SfxPoolItem* pItem; + switch( nNew ) + { + case RndStdIds::FLY_AS_CHAR: + // If no position attributes are received, we have to make sure + // that no forbidden automatic alignment is left. + { + const SwPosition *pPos = aNewAnch.GetContentAnchor(); + SwTextNode *pNd = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Cursor does not point to TextNode." ); + + SwFormatFlyCnt aFormat( static_cast(&rFormat) ); + pNd->InsertItem( aFormat, pPos->nContent.GetIndex(), 0 ); + } + + if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false, &pItem )) + { + SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); + bool bSet = true; + switch( aOldV.GetVertOrient() ) + { + case text::VertOrientation::LINE_TOP: aOldV.SetVertOrient( text::VertOrientation::TOP ); break; + case text::VertOrientation::LINE_CENTER: aOldV.SetVertOrient( text::VertOrientation::CENTER); break; + case text::VertOrientation::LINE_BOTTOM: aOldV.SetVertOrient( text::VertOrientation::BOTTOM); break; + case text::VertOrientation::NONE: aOldV.SetVertOrient( text::VertOrientation::CENTER); break; + default: + bSet = false; + } + if( bSet ) + rSet.Put( aOldV ); + } + break; + + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + case RndStdIds::FLY_AT_PAGE: + { + // If no position attributes are coming in, we correct the position in a way + // such that the fly's document coordinates are preserved. + // If only the alignment changes in the position attributes (text::RelOrientation::FRAME + // vs. text::RelOrientation::PRTAREA), we also correct the position. + if( SfxItemState::SET != rSet.GetItemState( RES_HORI_ORIENT, false, &pItem )) + pItem = nullptr; + + SwFormatHoriOrient aOldH( rFormat.GetHoriOrient() ); + bool bPutOldH(false); + + if( text::HoriOrientation::NONE == aOldH.GetHoriOrient() && ( !pItem || + aOldH.GetPos() == static_cast(pItem)->GetPos() )) + { + SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldH.GetPos(); + nPos += aOldAnchorPos.getX() - aNewAnchorPos.getX(); + + if( pItem ) + { + SwFormatHoriOrient* pH = const_cast(static_cast(pItem)); + aOldH.SetHoriOrient( pH->GetHoriOrient() ); + aOldH.SetRelationOrient( pH->GetRelationOrient() ); + } + aOldH.SetPos( nPos ); + bPutOldH = true; + } + if (nNew == RndStdIds::FLY_AT_PAGE) + { + sal_Int16 nRelOrient(pItem + ? static_cast(pItem)->GetRelationOrient() + : aOldH.GetRelationOrient()); + if (sw::GetAtPageRelOrientation(nRelOrient, false)) + { + SAL_INFO("sw.ui", "fixing horizontal RelOrientation for at-page anchor"); + aOldH.SetRelationOrient(nRelOrient); + bPutOldH = true; + } + } + if (bPutOldH) + { + rSet.Put( aOldH ); + } + + if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false, &pItem )) + pItem = nullptr; + SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); + + // #i28922# - correction: compare + if( text::VertOrientation::NONE == aOldV.GetVertOrient() && (!pItem || + aOldV.GetPos() == static_cast(pItem)->GetPos() ) ) + { + SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldV.GetPos(); + nPos += aOldAnchorPos.getY() - aNewAnchorPos.getY(); + if( pItem ) + { + SwFormatVertOrient* pV = const_cast(static_cast(pItem)); + aOldV.SetVertOrient( pV->GetVertOrient() ); + aOldV.SetRelationOrient( pV->GetRelationOrient() ); + } + aOldV.SetPos( nPos ); + rSet.Put( aOldV ); + } + } + break; + default: + break; + } + + if( bNewFrames ) + rFormat.MakeFrames(); + + return MAKEFRMS; +} + +static bool +lcl_SetFlyFrameAttr(SwDoc & rDoc, + sal_Int8 (SwDoc::*pSetFlyFrameAnchor)(SwFrameFormat &, SfxItemSet &, bool), + SwFrameFormat & rFlyFormat, SfxItemSet & rSet) +{ + // #i32968# Inserting columns in the frame causes MakeFrameFormat to put two + // objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + // Is the anchor attribute included? + // If so, we pass it to a special method, which returns true + // if the Fly needs to be created anew, because we e.g change the FlyType. + sal_Int8 const nMakeFrames = + (SfxItemState::SET == rSet.GetItemState( RES_ANCHOR, false )) + ? (rDoc.*pSetFlyFrameAnchor)( rFlyFormat, rSet, false ) + : DONTMAKEFRMS; + + const SfxPoolItem* pItem; + SfxItemIter aIter( rSet ); + SfxItemSet aTmpSet( rDoc.GetAttrPool(), aFrameFormatSetRange ); + const SfxPoolItem* pItemIter = aIter.GetCurItem(); + do { + switch(pItemIter->Which()) + { + case RES_FILL_ORDER: + case RES_BREAK: + case RES_PAGEDESC: + case RES_CNTNT: + case RES_FOOTER: + OSL_FAIL( "Unknown Fly attribute." ); + [[fallthrough]]; + case RES_CHAIN: + rSet.ClearItem(pItemIter->Which()); + break; + case RES_ANCHOR: + if( DONTMAKEFRMS != nMakeFrames ) + break; + [[fallthrough]]; + default: + if( !IsInvalidItem(pItemIter) && ( SfxItemState::SET != + rFlyFormat.GetAttrSet().GetItemState(pItemIter->Which(), true, &pItem ) || + *pItem != *pItemIter)) + aTmpSet.Put(*pItemIter); + break; + } + + pItemIter = aIter.NextItem(); + + } while (pItemIter && (0 != pItemIter->Which())); + + if( aTmpSet.Count() ) + rFlyFormat.SetFormatAttr( aTmpSet ); + + if( MAKEFRMS == nMakeFrames ) + rFlyFormat.MakeFrames(); + + return aTmpSet.Count() || MAKEFRMS == nMakeFrames; +} + +void SwDoc::CheckForUniqueItemForLineFillNameOrIndex(SfxItemSet& rSet) +{ + SwDrawModel* pDrawModel = getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + SfxItemIter aIter(rSet); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if (IsInvalidItem(pItem)) + continue; + std::unique_ptr pResult; + + switch(pItem->Which()) + { + case XATTR_FILLBITMAP: + { + pResult = static_cast< const XFillBitmapItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINEDASH: + { + pResult = static_cast< const XLineDashItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINESTART: + { + pResult = static_cast< const XLineStartItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINEEND: + { + pResult = static_cast< const XLineEndItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLGRADIENT: + { + pResult = static_cast< const XFillGradientItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLFLOATTRANSPARENCE: + { + pResult = static_cast< const XFillFloatTransparenceItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLHATCH: + { + pResult = static_cast< const XFillHatchItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + } + + if(pResult) + { + rSet.Put(*pResult); + } + } +} + +bool SwDoc::SetFlyFrameAttr( SwFrameFormat& rFlyFormat, SfxItemSet& rSet ) +{ + if( !rSet.Count() ) + return false; + + std::unique_ptr pSaveUndo; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); // AppendUndo far below, so leave it + pSaveUndo.reset( new SwUndoFormatAttrHelper( rFlyFormat ) ); + } + + bool const bRet = lcl_SetFlyFrameAttr(*this, &SwDoc::SetFlyFrameAnchor, rFlyFormat, rSet); + + if (pSaveUndo && pSaveUndo->GetUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( pSaveUndo->ReleaseUndo() ); + } + + getIDocumentState().SetModified(); + + SwTextBoxHelper::syncFlyFrameAttr(rFlyFormat, rSet); + + return bRet; +} + +// #i73249# +void SwDoc::SetFlyFrameTitle( SwFlyFrameFormat& rFlyFrameFormat, + const OUString& sNewTitle ) +{ + if ( rFlyFrameFormat.GetObjTitle() == sNewTitle ) + { + return; + } + + ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique( rFlyFrameFormat, + SwUndoId::FLYFRMFMT_TITLE, + rFlyFrameFormat.GetObjTitle(), + sNewTitle ) ); + } + + rFlyFrameFormat.SetObjTitle( sNewTitle, true ); + + getIDocumentState().SetModified(); +} + +void SwDoc::SetFlyFrameDescription( SwFlyFrameFormat& rFlyFrameFormat, + const OUString& sNewDescription ) +{ + if ( rFlyFrameFormat.GetObjDescription() == sNewDescription ) + { + return; + } + + ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique( rFlyFrameFormat, + SwUndoId::FLYFRMFMT_DESCRIPTION, + rFlyFrameFormat.GetObjDescription(), + sNewDescription ) ); + } + + rFlyFrameFormat.SetObjDescription( sNewDescription, true ); + + getIDocumentState().SetModified(); +} + +bool SwDoc::SetFrameFormatToFly( SwFrameFormat& rFormat, SwFrameFormat& rNewFormat, + SfxItemSet* pSet, bool bKeepOrient ) +{ + bool bChgAnchor = false, bFrameSz = false; + + const SwFormatFrameSize aFrameSz( rFormat.GetFrameSize() ); + + SwUndoSetFlyFormat* pUndo = nullptr; + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if (bUndo) + { + pUndo = new SwUndoSetFlyFormat( rFormat, rNewFormat ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(pUndo)); + } + + // #i32968# Inserting columns in the section causes MakeFrameFormat to put + // 2 objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Set the column first, or we'll have trouble with + //Set/Reset/Synch. and so on + const SfxPoolItem* pItem; + if( SfxItemState::SET != rNewFormat.GetAttrSet().GetItemState( RES_COL )) + rFormat.ResetFormatAttr( RES_COL ); + + if( rFormat.DerivedFrom() != &rNewFormat ) + { + rFormat.SetDerivedFrom( &rNewFormat ); + + // 1. If not automatic = ignore; else = dispose + // 2. Dispose of it! + if( SfxItemState::SET == rNewFormat.GetAttrSet().GetItemState( RES_FRM_SIZE, false )) + { + rFormat.ResetFormatAttr( RES_FRM_SIZE ); + bFrameSz = true; + } + + const SfxItemSet* pAsk = pSet; + if( !pAsk ) pAsk = &rNewFormat.GetAttrSet(); + if( SfxItemState::SET == pAsk->GetItemState( RES_ANCHOR, false, &pItem ) + && static_cast(pItem)->GetAnchorId() != + rFormat.GetAnchor().GetAnchorId() ) + { + if( pSet ) + bChgAnchor = MAKEFRMS == SetFlyFrameAnchor( rFormat, *pSet, false ); + else + { + // Needs to have the FlyFormat range, because we set attributes in it, + // in SetFlyFrameAnchor. + SfxItemSet aFlySet( *rNewFormat.GetAttrSet().GetPool(), + rNewFormat.GetAttrSet().GetRanges() ); + aFlySet.Put( *pItem ); + bChgAnchor = MAKEFRMS == SetFlyFrameAnchor( rFormat, aFlySet, false); + } + } + } + + // Only reset vertical and horizontal orientation, if we have automatic alignment + // set in the template. Otherwise use the old value. + // If we update the frame template the Fly should NOT lose its orientation (which + // is not being updated!). + // text::HoriOrientation::NONE and text::VertOrientation::NONE are allowed now + if (!bKeepOrient) + { + rFormat.ResetFormatAttr(RES_VERT_ORIENT); + rFormat.ResetFormatAttr(RES_HORI_ORIENT); + } + + rFormat.ResetFormatAttr( RES_PRINT, RES_SURROUND ); + rFormat.ResetFormatAttr( RES_LR_SPACE, RES_UL_SPACE ); + rFormat.ResetFormatAttr( RES_BACKGROUND, RES_COL ); + rFormat.ResetFormatAttr( RES_URL, RES_EDIT_IN_READONLY ); + + if( !bFrameSz ) + rFormat.SetFormatAttr( aFrameSz ); + + if( bChgAnchor ) + rFormat.MakeFrames(); + + if( pUndo ) + pUndo->EndListeningAll(); + + getIDocumentState().SetModified(); + + return bChgAnchor; +} + +void SwDoc::GetGrfNms( const SwFlyFrameFormat& rFormat, OUString* pGrfName, + OUString* pFltName ) +{ + SwNodeIndex aIdx( *rFormat.GetContent().GetContentIdx(), 1 ); + const SwGrfNode* pGrfNd = aIdx.GetNode().GetGrfNode(); + if( pGrfNd && pGrfNd->IsLinkedFile() ) + pGrfNd->GetFileFilterNms( pGrfName, pFltName ); +} + +bool SwDoc::ChgAnchor( const SdrMarkList& _rMrkList, + RndStdIds _eAnchorType, + const bool _bSameOnly, + const bool _bPosCorr ) +{ + OSL_ENSURE( getIDocumentLayoutAccess().GetCurrentLayout(), "No layout!" ); + + if ( !_rMrkList.GetMarkCount() || + _rMrkList.GetMark( 0 )->GetMarkedSdrObj()->getParentSdrObjectFromSdrObject() ) + { + return false; + } + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSATTR, nullptr ); + + bool bUnmark = false; + for ( size_t i = 0; i < _rMrkList.GetMarkCount(); ++i ) + { + SdrObject* pObj = _rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( dynamic_cast( pObj) == nullptr ) + { + SwDrawContact* pContact = static_cast(GetUserCall(pObj)); + + // consider, that drawing object has + // no user call. E.g.: a 'virtual' drawing object is disconnected by + // the anchor type change of the 'master' drawing object. + // Continue with next selected object and assert, if this isn't excepted. + if ( !pContact ) + { +#if OSL_DEBUG_LEVEL > 0 + bool bNoUserCallExcepted = + dynamic_cast( pObj) != nullptr && + !static_cast(pObj)->IsConnected(); + OSL_ENSURE( bNoUserCallExcepted, "SwDoc::ChgAnchor(..) - no contact at selected drawing object" ); +#endif + continue; + } + + // #i26791# + const SwFrame* pOldAnchorFrame = pContact->GetAnchorFrame( pObj ); + const SwFrame* pNewAnchorFrame = pOldAnchorFrame; + + // #i54336# + // Instead of only keeping the index position for an as-character + // anchored object the complete is kept, because the + // anchor index position could be moved, if the object again is + // anchored as character. + std::unique_ptr xOldAsCharAnchorPos; + const RndStdIds eOldAnchorType = pContact->GetAnchorId(); + if ( !_bSameOnly && eOldAnchorType == RndStdIds::FLY_AS_CHAR ) + { + xOldAsCharAnchorPos.reset(new SwPosition(pContact->GetContentAnchor())); + } + + if ( _bSameOnly ) + _eAnchorType = eOldAnchorType; + + SwFormatAnchor aNewAnch( _eAnchorType ); + SwAnchoredObject *pAnchoredObj = pContact->GetAnchoredObj(pObj); + tools::Rectangle aObjRect(pAnchoredObj->GetObjRect().SVRect()); + const Point aPt( aObjRect.TopLeft() ); + + switch ( _eAnchorType ) + { + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + { + const Point aNewPoint = ( pOldAnchorFrame->IsVertical() || + pOldAnchorFrame->IsRightToLeft() ) + ? aObjRect.TopRight() + : aPt; + + // allow drawing objects in header/footer + pNewAnchorFrame = ::FindAnchor( pOldAnchorFrame, aNewPoint ); + if ( pNewAnchorFrame->IsTextFrame() && static_cast(pNewAnchorFrame)->IsFollow() ) + { + pNewAnchorFrame = static_cast(pNewAnchorFrame)->FindMaster(); + } + if ( pNewAnchorFrame->IsProtected() ) + { + pNewAnchorFrame = nullptr; + } + else + { + SwPosition aPos( pNewAnchorFrame->IsTextFrame() + ? *static_cast(pNewAnchorFrame)->GetTextNodeForParaProps() + : *static_cast(pNewAnchorFrame)->GetNode() ); + + aNewAnch.SetType( _eAnchorType ); + aNewAnch.SetAnchor( &aPos ); + } + } + break; + + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + { + // Search the closest SwFlyFrame starting from the upper left corner. + SwFrame *pTextFrame; + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + SwPosition aPos( GetNodes() ); + Point aPoint( aPt ); + aPoint.setX(aPoint.getX() - 1); + getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + // consider that drawing objects can be in + // header/footer. Thus, by left-top-corner + std::pair const tmp(aPt, false); + pTextFrame = aPos.nNode.GetNode(). + GetContentNode()->getLayoutFrame( + getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + } + const SwFrame *pTmp = ::FindAnchor( pTextFrame, aPt ); + pNewAnchorFrame = pTmp->FindFlyFrame(); + if( pNewAnchorFrame && !pNewAnchorFrame->IsProtected() ) + { + const SwFrameFormat *pTmpFormat = static_cast(pNewAnchorFrame)->GetFormat(); + const SwFormatContent& rContent = pTmpFormat->GetContent(); + SwPosition aPos( *rContent.GetContentIdx() ); + aNewAnch.SetAnchor( &aPos ); + break; + } + + aNewAnch.SetType( RndStdIds::FLY_AT_PAGE ); + [[fallthrough]]; + } + case RndStdIds::FLY_AT_PAGE: + { + pNewAnchorFrame = getIDocumentLayoutAccess().GetCurrentLayout()->Lower(); + while ( pNewAnchorFrame && !pNewAnchorFrame->getFrameArea().IsInside( aPt ) ) + pNewAnchorFrame = pNewAnchorFrame->GetNext(); + if ( !pNewAnchorFrame ) + continue; + + aNewAnch.SetPageNum( static_cast(pNewAnchorFrame)->GetPhyPageNum()); + } + break; + case RndStdIds::FLY_AS_CHAR: + if( _bSameOnly ) // Change of position/size + { + if( !pOldAnchorFrame ) + { + pContact->ConnectToLayout(); + pOldAnchorFrame = pContact->GetAnchorFrame(); + } + const_cast(static_cast(pOldAnchorFrame))->Prepare(); + } + else // Change of anchors + { + // allow drawing objects in header/footer + pNewAnchorFrame = ::FindAnchor( pOldAnchorFrame, aPt ); + if( pNewAnchorFrame->IsProtected() ) + { + pNewAnchorFrame = nullptr; + break; + } + + bUnmark = ( 0 != i ); + Point aPoint( aPt ); + aPoint.setX(aPoint.getX() - 1); // Do not load in the DrawObj! + aNewAnch.SetType( RndStdIds::FLY_AS_CHAR ); + assert(pNewAnchorFrame->IsTextFrame()); // because AS_CHAR + SwTextFrame const*const pFrame( + static_cast(pNewAnchorFrame)); + SwPosition aPos( *pFrame->GetTextNodeForParaProps() ); + if ( pNewAnchorFrame->getFrameArea().IsInside( aPoint ) ) + { + // We need to find a TextNode, because only there we can anchor a + // content-bound DrawObject. + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + } + else + { + if ( pNewAnchorFrame->getFrameArea().Bottom() < aPt.Y() ) + { + aPos = pFrame->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + aPos = pFrame->MapViewToModelPos( + TextFrameIndex(pFrame->GetText().getLength())); + } + } + aNewAnch.SetAnchor( &aPos ); + SetAttr( aNewAnch, *pContact->GetFormat() ); + // #i26791# - adjust vertical positioning to 'center to + // baseline' + SetAttr( SwFormatVertOrient( 0, text::VertOrientation::CENTER, text::RelOrientation::FRAME ), *pContact->GetFormat() ); + SwTextNode *pNd = aPos.nNode.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Cursor not positioned at TextNode." ); + + SwFormatFlyCnt aFormat( pContact->GetFormat() ); + pNd->InsertItem( aFormat, aPos.nContent.GetIndex(), 0 ); + } + break; + default: + OSL_ENSURE( false, "unexpected AnchorId." ); + } + + if ( (RndStdIds::FLY_AS_CHAR != _eAnchorType) && + pNewAnchorFrame && + ( !_bSameOnly || pNewAnchorFrame != pOldAnchorFrame ) ) + { + // #i26791# - Direct object positioning no longer needed. Apply + // of attributes (method call ) takes care of the + // invalidation of the object position. + if ( _bPosCorr ) + { + // #i33313# - consider not connected 'virtual' drawing + // objects + if ( dynamic_cast( pObj) != nullptr && + !static_cast(pObj)->IsConnected() ) + { + SwRect aNewObjRect( aObjRect ); + static_cast(pContact->GetAnchoredObj( nullptr )) + ->AdjustPositioningAttr( pNewAnchorFrame, + &aNewObjRect ); + } + else + { + static_cast(pContact->GetAnchoredObj( pObj )) + ->AdjustPositioningAttr( pNewAnchorFrame ); + } + } + if (aNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + SwFormatHoriOrient item(pContact->GetFormat()->GetHoriOrient()); + sal_Int16 nRelOrient(item.GetRelationOrient()); + if (sw::GetAtPageRelOrientation(nRelOrient, false)) + { + SAL_INFO("sw.ui", "fixing horizontal RelOrientation for at-page anchor"); + item.SetRelationOrient(nRelOrient); + SetAttr(item, *pContact->GetFormat()); + } + } + // tdf#136385 set the anchor last - otherwise it messes up the + // position in SwDrawContact::Changed_() callback + SetAttr(aNewAnch, *pContact->GetFormat()); + } + + // we have changed the anchoring attributes, and those are used to + // order the object in its sorted list, so update its position + pAnchoredObj->UpdateObjInSortedList(); + + // #i54336# + if (xOldAsCharAnchorPos) + { + if ( pNewAnchorFrame) + { + // We need to handle InContents in a special way: + // The TextAttribut needs to be destroyed which, unfortunately, also + // destroys the format. To avoid that, we disconnect the format from + // the attribute. + const sal_Int32 nIndx( xOldAsCharAnchorPos->nContent.GetIndex() ); + SwTextNode* pTextNode( xOldAsCharAnchorPos->nNode.GetNode().GetTextNode() ); + assert(pTextNode && " - missing previous anchor text node for as-character anchored object"); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIndx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing FlyInCnt-Hint."); + const_cast(pHint->GetFlyCnt()).SetFlyFormat(); + + // They are disconnected. We now have to destroy the attribute. + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIndx, nIndx ); + } + } + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + getIDocumentState().SetModified(); + + return bUnmark; +} + +SwChainRet SwDoc::Chainable( const SwFrameFormat &rSource, const SwFrameFormat &rDest ) +{ + // The Source must not yet have a Follow. + const SwFormatChain &rOldChain = rSource.GetChain(); + if ( rOldChain.GetNext() ) + return SwChainRet::SOURCE_CHAINED; + + // Target must not be equal to Source and we also must not have a closed chain. + const SwFrameFormat *pFormat = &rDest; + do { + if( pFormat == &rSource ) + return SwChainRet::SELF; + pFormat = pFormat->GetChain().GetNext(); + } while ( pFormat ); + + // There must not be a chaining from outside to inside or the other way around. + if( rDest.IsLowerOf( rSource ) || rSource .IsLowerOf( rDest ) ) + return SwChainRet::SELF; + + // The Target must not yet have a Master. + const SwFormatChain &rChain = rDest.GetChain(); + if( rChain.GetPrev() ) + return SwChainRet::IS_IN_CHAIN; + + // Target must be empty. + const SwNodeIndex* pCntIdx = rDest.GetContent().GetContentIdx(); + if( !pCntIdx ) + return SwChainRet::NOT_FOUND; + + SwNodeIndex aNxtIdx( *pCntIdx, 1 ); + const SwTextNode* pTextNd = aNxtIdx.GetNode().GetTextNode(); + if( !pTextNd ) + return SwChainRet::NOT_FOUND; + + const sal_uLong nFlySttNd = pCntIdx->GetIndex(); + if( 2 != ( pCntIdx->GetNode().EndOfSectionIndex() - nFlySttNd ) || + pTextNd->GetText().getLength() ) + { + return SwChainRet::NOT_EMPTY; + } + + for( auto pSpzFrameFm : *GetSpzFrameFormats() ) + { + const SwFormatAnchor& rAnchor = pSpzFrameFm->GetAnchor(); + // #i20622# - to-frame anchored objects are allowed. + if ( (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PARA) && + (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_CHAR) ) + continue; + if ( nullptr == rAnchor.GetContentAnchor() ) + continue; + sal_uLong nTstSttNd = rAnchor.GetContentAnchor()->nNode.GetIndex(); + if( nFlySttNd <= nTstSttNd && nTstSttNd < nFlySttNd + 2 ) + { + return SwChainRet::NOT_EMPTY; + } + } + + // We also need to consider the right area. + // Both Flys need to be located in the same area (Body, Header/Footer, Fly). + // If the Source is not the selected frame, it's enough to find a suitable + // one. e.g. if it's requested by the API. + + // both in the same fly, header, footer or on the page? + const SwFormatAnchor &rSrcAnchor = rSource.GetAnchor(), + &rDstAnchor = rDest.GetAnchor(); + sal_uLong nEndOfExtras = GetNodes().GetEndOfExtras().GetIndex(); + bool bAllowed = false; + if ( RndStdIds::FLY_AT_PAGE == rSrcAnchor.GetAnchorId() ) + { + if ( (RndStdIds::FLY_AT_PAGE == rDstAnchor.GetAnchorId()) || + ( rDstAnchor.GetContentAnchor() && + rDstAnchor.GetContentAnchor()->nNode.GetIndex() > nEndOfExtras )) + bAllowed = true; + } + else if( rSrcAnchor.GetContentAnchor() && rDstAnchor.GetContentAnchor() ) + { + const SwNodeIndex &rSrcIdx = rSrcAnchor.GetContentAnchor()->nNode, + &rDstIdx = rDstAnchor.GetContentAnchor()->nNode; + const SwStartNode* pSttNd = nullptr; + if( rSrcIdx == rDstIdx || + ( !pSttNd && + nullptr != ( pSttNd = rSrcIdx.GetNode().FindFlyStartNode() ) && + pSttNd == rDstIdx.GetNode().FindFlyStartNode() ) || + ( !pSttNd && + nullptr != ( pSttNd = rSrcIdx.GetNode().FindFooterStartNode() ) && + pSttNd == rDstIdx.GetNode().FindFooterStartNode() ) || + ( !pSttNd && + nullptr != ( pSttNd = rSrcIdx.GetNode().FindHeaderStartNode() ) && + pSttNd == rDstIdx.GetNode().FindHeaderStartNode() ) || + ( !pSttNd && rDstIdx.GetIndex() > nEndOfExtras && + rSrcIdx.GetIndex() > nEndOfExtras )) + bAllowed = true; + } + + return bAllowed ? SwChainRet::OK : SwChainRet::WRONG_AREA; +} + +SwChainRet SwDoc::Chain( SwFrameFormat &rSource, const SwFrameFormat &rDest ) +{ + SwChainRet nErr = Chainable( rSource, rDest ); + if ( nErr == SwChainRet::OK ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::CHAINE, nullptr ); + + SwFlyFrameFormat& rDestFormat = const_cast(static_cast(rDest)); + + // Attach Follow to the Master. + SwFormatChain aChain = rDestFormat.GetChain(); + aChain.SetPrev( &static_cast(rSource) ); + SetAttr( aChain, rDestFormat ); + + SfxItemSet aSet( GetAttrPool(), svl::Items{} ); + + // Attach Follow to the Master. + aChain.SetPrev( &static_cast(rSource) ); + SetAttr( aChain, rDestFormat ); + + // Attach Master to the Follow. + // Make sure that the Master has a fixed height. + aChain = rSource.GetChain(); + aChain.SetNext( &rDestFormat ); + aSet.Put( aChain ); + + SwFormatFrameSize aSize( rSource.GetFrameSize() ); + if ( aSize.GetHeightSizeType() != SwFrameSize::Fixed ) + { + SwFlyFrame *pFly = SwIterator( rSource ).First(); + if ( pFly ) + aSize.SetHeight( pFly->getFrameArea().Height() ); + aSize.SetHeightSizeType( SwFrameSize::Fixed ); + aSet.Put( aSize ); + } + SetAttr( aSet, rSource ); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::CHAINE, nullptr ); + } + return nErr; +} + +void SwDoc::Unchain( SwFrameFormat &rFormat ) +{ + SwFormatChain aChain( rFormat.GetChain() ); + if ( aChain.GetNext() ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::UNCHAIN, nullptr ); + SwFrameFormat *pFollow = aChain.GetNext(); + aChain.SetNext( nullptr ); + SetAttr( aChain, rFormat ); + aChain = pFollow->GetChain(); + aChain.SetPrev( nullptr ); + SetAttr( aChain, *pFollow ); + GetIDocumentUndoRedo().EndUndo( SwUndoId::UNCHAIN, nullptr ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docfmt.cxx b/sw/source/core/doc/docfmt.cxx new file mode 100644 index 000000000..7b877a7cd --- /dev/null +++ b/sw/source/core/doc/docfmt.cxx @@ -0,0 +1,2151 @@ +/* -*- 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 + +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; + +/* + * Internal functions + */ + +static void SetTextFormatCollNext( SwTextFormatColl* pTextColl, const SwTextFormatColl* pDel ) +{ + if ( &pTextColl->GetNextTextFormatColl() == pDel ) + { + pTextColl->SetNextTextFormatColl( *pTextColl ); + } +} + +static bool lcl_RstAttr( const SwNodePtr& rpNd, void* pArgs ) +{ + const sw::DocumentContentOperationsManager::ParaRstFormat* pPara = static_cast(pArgs); + SwContentNode* pNode = rpNd->GetContentNode(); + if (pPara && pPara->pLayout && pPara->pLayout->IsHideRedlines() + && pNode && pNode->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; + } + if( pNode && pNode->HasSwAttrSet() ) + { + const bool bLocked = pNode->IsModifyLocked(); + pNode->LockModify(); + + SwDoc* pDoc = pNode->GetDoc(); + + // remove unused attribute RES_LR_SPACE + // add list attributes + SfxItemSet aSavedAttrsSet( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_NUMRULE, RES_PARATR_NUMRULE, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END - 1, + RES_PAGEDESC, RES_BREAK>{}); + const SfxItemSet* pAttrSetOfNode = pNode->GetpSwAttrSet(); + + std::vector aClearWhichIds; + // restoring all paragraph list attributes + { + SfxItemSet aListAttrSet( pDoc->GetAttrPool(), svl::Items{} ); + aListAttrSet.Set(*pAttrSetOfNode); + if ( aListAttrSet.Count() ) + { + aSavedAttrsSet.Put(aListAttrSet); + SfxItemIter aIter( aListAttrSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + while( pItem ) + { + aClearWhichIds.push_back( pItem->Which() ); + pItem = aIter.NextItem(); + } + } + } + + const SfxPoolItem* pItem; + + sal_uInt16 const aSavIds[3] = { RES_PAGEDESC, RES_BREAK, RES_PARATR_NUMRULE }; + for (sal_uInt16 aSavId : aSavIds) + { + if (SfxItemState::SET == pAttrSetOfNode->GetItemState(aSavId, false, &pItem)) + { + bool bSave = false; + switch( aSavId ) + { + case RES_PAGEDESC: + bSave = nullptr != static_cast(pItem)->GetPageDesc(); + break; + case RES_BREAK: + bSave = SvxBreak::NONE != static_cast(pItem)->GetBreak(); + break; + case RES_PARATR_NUMRULE: + bSave = !static_cast(pItem)->GetValue().isEmpty(); + break; + } + if( bSave ) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(aSavId); + } + } + } + + // do not clear items directly from item set and only clear to be kept + // attributes, if no deletion item set is found. + const bool bKeepAttributes = + !pPara || !pPara->pDelSet || pPara->pDelSet->Count() == 0; + if ( bKeepAttributes ) + { + pNode->ResetAttr( aClearWhichIds ); + } + + if( !bLocked ) + pNode->UnlockModify(); + + if( pPara ) + { + SwRegHistory aRegH( pNode, *pNode, pPara->pHistory ); + + if( pPara->pDelSet && pPara->pDelSet->Count() ) + { + OSL_ENSURE( !bKeepAttributes, + " - certain attributes are kept, but not needed." ); + SfxItemIter aIter( *pPara->pDelSet ); + for (pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if ( ( pItem->Which() != RES_PAGEDESC && + pItem->Which() != RES_BREAK && + pItem->Which() != RES_PARATR_NUMRULE ) || + ( aSavedAttrsSet.GetItemState( pItem->Which(), false ) != SfxItemState::SET ) ) + { + pNode->ResetAttr( pItem->Which() ); + } + } + } + else if( pPara->bResetAll ) + pNode->ResetAllAttr(); + else + pNode->ResetAttr( RES_PARATR_BEGIN, POOLATTR_END - 1 ); + } + else + pNode->ResetAllAttr(); + + // only restore saved attributes, if needed + if (bKeepAttributes && aSavedAttrsSet.Count()) + { + pNode->LockModify(); + + pNode->SetAttr(aSavedAttrsSet); + + if( !bLocked ) + pNode->UnlockModify(); + } + } + return true; +} + +void SwDoc::RstTextAttrs(const SwPaM &rRg, bool bInclRefToxMark, + bool bExactRange, SwRootFrame const*const pLayout) +{ + SwHistory* pHst = nullptr; + SwDataChanged aTmp( rRg ); + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo(new SwUndoResetAttr( rRg, RES_CHRFMT )); + pHst = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout ); + aPara.bInclRefToxMark = bInclRefToxMark; + aPara.bExactRange = bExactRange; + GetNodes().ForEach( pStt->nNode.GetIndex(), pEnd->nNode.GetIndex()+1, + sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + getIDocumentState().SetModified(); +} + +void SwDoc::ResetAttrs( const SwPaM &rRg, + bool bTextAttr, + const std::set &rAttrs, + const bool bSendDataChangedEvents, + SwRootFrame const*const pLayout) +{ + SwPaM* pPam = const_cast(&rRg); + if( !bTextAttr && !rAttrs.empty() && RES_TXTATR_END > *(rAttrs.begin()) ) + bTextAttr = true; + + if( !rRg.HasMark() ) + { + SwTextNode* pTextNd = rRg.GetPoint()->nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return ; + + pPam = new SwPaM( *rRg.GetPoint() ); + + SwIndex& rSt = pPam->GetPoint()->nContent; + sal_Int32 nMkPos, nPtPos = rSt.GetIndex(); + + // Special case: if the Cursor is located within a URL attribute, we take over it's area + SwTextAttr const*const pURLAttr( + pTextNd->GetTextAttrAt(rSt.GetIndex(), RES_TXTATR_INETFMT)); + if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty()) + { + nMkPos = pURLAttr->GetStart(); + nPtPos = *pURLAttr->End(); + } + else + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTextNd->GetText(), nPtPos, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos ) + { + nMkPos = aBndry.startPos; + nPtPos = aBndry.endPos; + } + else + { + nPtPos = nMkPos = rSt.GetIndex(); + if( bTextAttr ) + pTextNd->DontExpandFormat( rSt ); + } + } + + rSt = nMkPos; + pPam->SetMark(); + pPam->GetPoint()->nContent = nPtPos; + } + + // #i96644# + std::unique_ptr< SwDataChanged > xDataChanged; + if ( bSendDataChangedEvents ) + { + xDataChanged.reset( new SwDataChanged( *pPam ) ); + } + SwHistory* pHst = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo(new SwUndoResetAttr( rRg, + bTextAttr ? sal_uInt16(RES_CONDTXTFMTCOLL) : sal_uInt16(RES_TXTFMTCOLL) )); + if( !rAttrs.empty() ) + { + pUndo->SetAttrs( rAttrs ); + } + pHst = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + const SwPosition *pStt = pPam->Start(), *pEnd = pPam->End(); + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout); + + // mst: not including META here; it seems attrs with CH_TXTATR are omitted + SfxItemSet aDelSet(GetAttrPool(), svl::Items{}); + for( std::set::const_reverse_iterator it = rAttrs.rbegin(); it != rAttrs.rend(); ++it ) + { + if( POOLATTR_END > *it ) + aDelSet.Put( *GetDfltAttr( *it )); + } + if( aDelSet.Count() ) + aPara.pDelSet = &aDelSet; + + bool bAdd = true; + SwNodeIndex aTmpStt( pStt->nNode ); + SwNodeIndex aTmpEnd( pEnd->nNode ); + if( pStt->nContent.GetIndex() ) // just one part + { + // set up a later, and all CharFormatAttr -> TextFormatAttr + SwTextNode* pTNd = aTmpStt.GetNode().GetTextNode(); + if( pTNd && pTNd->HasSwAttrSet() && pTNd->GetpSwAttrSet()->Count() ) + { + if (pHst) + { + SwRegHistory history(pTNd, *pTNd, pHst); + pTNd->FormatToTextAttr(pTNd); + } + else + { + pTNd->FormatToTextAttr(pTNd); + } + } + + ++aTmpStt; + } + if( pEnd->nContent.GetIndex() == pEnd->nNode.GetNode().GetContentNode()->Len() ) + { + // set up a later, and all CharFormatAttr -> TextFormatAttr + ++aTmpEnd; + bAdd = false; + } + else if( pStt->nNode != pEnd->nNode || !pStt->nContent.GetIndex() ) + { + SwTextNode* pTNd = aTmpEnd.GetNode().GetTextNode(); + if( pTNd && pTNd->HasSwAttrSet() && pTNd->GetpSwAttrSet()->Count() ) + { + if (pHst) + { + SwRegHistory history(pTNd, *pTNd, pHst); + pTNd->FormatToTextAttr(pTNd); + } + else + { + pTNd->FormatToTextAttr(pTNd); + } + } + } + + if( aTmpStt < aTmpEnd ) + GetNodes().ForEach( pStt->nNode, aTmpEnd, lcl_RstAttr, &aPara ); + else if( !rRg.HasMark() ) + { + aPara.bResetAll = false ; + ::lcl_RstAttr( &pStt->nNode.GetNode(), &aPara ); + aPara.bResetAll = true ; + } + + if( bTextAttr ) + { + if( bAdd ) + ++aTmpEnd; + GetNodes().ForEach( pStt->nNode, aTmpEnd, sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + } + + getIDocumentState().SetModified(); + + xDataChanged.reset(); //before delete pPam + + if( pPam != &rRg ) + delete pPam; +} + +/// Set the rsid of the next nLen symbols of rRg to the current session number +void SwDoc::UpdateRsid( const SwPaM &rRg, const sal_Int32 nLen ) +{ + if (!SW_MOD()->GetModuleConfig()->IsStoreRsid()) + return; + + SwTextNode *pTextNode = rRg.GetPoint()->nNode.GetNode().GetTextNode(); + if (!pTextNode) + { + return; + } + const sal_Int32 nStart(rRg.GetPoint()->nContent.GetIndex() - nLen); + SvxRsidItem aRsid( mnRsid, RES_CHRATR_RSID ); + + SfxItemSet aSet(GetAttrPool(), svl::Items{}); + aSet.Put(aRsid); + bool const bRet(pTextNode->SetAttr(aSet, nStart, + rRg.GetPoint()->nContent.GetIndex())); + + if (bRet && GetIDocumentUndoRedo().DoesUndo()) + { + SwUndo *const pLastUndo = GetUndoManager().GetLastUndo(); + SwUndoInsert *const pUndoInsert(dynamic_cast(pLastUndo)); + // this function is called after Insert so expects to find SwUndoInsert + assert(pUndoInsert); + if (pUndoInsert) + { + pUndoInsert->SetWithRsid(); + } + } +} + +bool SwDoc::UpdateParRsid( SwTextNode *pTextNode, sal_uInt32 nVal ) +{ + if (!SW_MOD()->GetModuleConfig()->IsStoreRsid()) + return false; + + if (!pTextNode) + { + return false; + } + + SvxRsidItem aRsid( nVal ? nVal : mnRsid, RES_PARATR_RSID ); + return pTextNode->SetAttr( aRsid ); +} + +/// Set the attribute according to the stated format. +/// If Undo is enabled, the old values is added to the Undo history. +void SwDoc::SetAttr( const SfxPoolItem& rAttr, SwFormat& rFormat ) +{ + SfxItemSet aSet( GetAttrPool(), {{rAttr.Which(), rAttr.Which()}} ); + aSet.Put( rAttr ); + SetAttr( aSet, rFormat ); +} + +/// Set the attribute according to the stated format. +/// If Undo is enabled, the old values is added to the Undo history. +void SwDoc::SetAttr( const SfxItemSet& rSet, SwFormat& rFormat ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + SwUndoFormatAttrHelper aTmp( rFormat ); + rFormat.SetFormatAttr( rSet ); + if ( aTmp.GetUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( aTmp.ReleaseUndo() ); + } + else + { + GetIDocumentUndoRedo().ClearRedo(); + } + } + else + { + rFormat.SetFormatAttr( rSet ); + } + getIDocumentState().SetModified(); +} + +void SwDoc::ResetAttrAtFormat( const sal_uInt16 nWhichId, + SwFormat& rChangedFormat ) +{ + std::unique_ptr pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoFormatResetAttr( rChangedFormat, nWhichId )); + + const bool bAttrReset = rChangedFormat.ResetFormatAttr( nWhichId ); + + if ( bAttrReset ) + { + if ( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + getIDocumentState().SetModified(); + } +} + +static bool lcl_SetNewDefTabStops( SwTwips nOldWidth, SwTwips nNewWidth, + SvxTabStopItem& rChgTabStop ) +{ + // Set the default values of all TabStops to the new value. + // Attention: we always work with the PoolAttribute here, so that + // we don't calculate the same value on the same TabStop (pooled!) for all sets. + // We send a FormatChg to modify. + + sal_uInt16 nOldCnt = rChgTabStop.Count(); + if( !nOldCnt || nOldWidth == nNewWidth ) + return false; + + // Find the default's beginning + sal_uInt16 n; + for( n = nOldCnt; n ; --n ) + if( SvxTabAdjust::Default != rChgTabStop[n - 1].GetAdjustment() ) + break; + ++n; + if( n < nOldCnt ) // delete the DefTabStops + rChgTabStop.Remove( n, nOldCnt - n ); + return true; +} + +/// Set the attribute as new default attribute in this document. +/// If Undo is enabled, the old value is added to the Undo history. +void SwDoc::SetDefault( const SfxPoolItem& rAttr ) +{ + SfxItemSet aSet( GetAttrPool(), {{rAttr.Which(), rAttr.Which()}} ); + aSet.Put( rAttr ); + SetDefault( aSet ); +} + +void SwDoc::SetDefault( const SfxItemSet& rSet ) +{ + if( !rSet.Count() ) + return; + + SwModify aCallMod; + SwAttrSet aOld( GetAttrPool(), rSet.GetRanges() ), + aNew( GetAttrPool(), rSet.GetRanges() ); + SfxItemIter aIter( rSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + SfxItemPool* pSdrPool = GetAttrPool().GetSecondaryPool(); + do + { + bool bCheckSdrDflt = false; + const sal_uInt16 nWhich = pItem->Which(); + aOld.Put( GetAttrPool().GetDefaultItem( nWhich ) ); + GetAttrPool().SetPoolDefaultItem( *pItem ); + aNew.Put( GetAttrPool().GetDefaultItem( nWhich ) ); + + if (isCHRATR(nWhich) || isTXTATR(nWhich)) + { + aCallMod.Add( mpDfltTextFormatColl.get() ); + aCallMod.Add( mpDfltCharFormat.get() ); + bCheckSdrDflt = nullptr != pSdrPool; + } + else if ( isPARATR(nWhich) || + isPARATR_LIST(nWhich) ) + { + aCallMod.Add( mpDfltTextFormatColl.get() ); + bCheckSdrDflt = nullptr != pSdrPool; + } + else if (isGRFATR(nWhich)) + { + aCallMod.Add( mpDfltGrfFormatColl.get() ); + } + else if (isFRMATR(nWhich) || isDrawingLayerAttribute(nWhich) ) + { + aCallMod.Add( mpDfltGrfFormatColl.get() ); + aCallMod.Add( mpDfltTextFormatColl.get() ); + aCallMod.Add( mpDfltFrameFormat.get() ); + } + else if (isBOXATR(nWhich)) + { + aCallMod.Add( mpDfltFrameFormat.get() ); + } + + // also copy the defaults + if( bCheckSdrDflt ) + { + sal_uInt16 nSlotId = GetAttrPool().GetSlotId( nWhich ); + if( 0 != nSlotId && nSlotId != nWhich ) + { + sal_uInt16 nEdtWhich = pSdrPool->GetWhich( nSlotId ); + if( 0 != nEdtWhich && nSlotId != nEdtWhich ) + { + std::unique_ptr pCpy(pItem->Clone()); + pCpy->SetWhich( nEdtWhich ); + pSdrPool->SetPoolDefaultItem( *pCpy ); + } + } + } + + pItem = aIter.NextItem(); + } while (pItem); + + if( aNew.Count() && aCallMod.HasWriterListeners() ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique( aOld, this ) ); + } + + const SfxPoolItem* pTmpItem; + if( ( SfxItemState::SET == + aNew.GetItemState( RES_PARATR_TABSTOP, false, &pTmpItem ) ) && + static_cast(pTmpItem)->Count() ) + { + // Set the default values of all TabStops to the new value. + // Attention: we always work with the PoolAttribute here, so that + // we don't calculate the same value on the same TabStop (pooled!) for all sets. + // We send a FormatChg to modify. + SwTwips nNewWidth = (*static_cast(pTmpItem))[ 0 ].GetTabPos(), + nOldWidth = aOld.Get(RES_PARATR_TABSTOP)[ 0 ].GetTabPos(); + + bool bChg = false; + for (const SfxPoolItem* pItem2 : GetAttrPool().GetItemSurrogates(RES_PARATR_TABSTOP)) + { + auto pTabStopItem = dynamic_cast(pItem2); + if(pTabStopItem) + bChg |= lcl_SetNewDefTabStops( nOldWidth, nNewWidth, + *const_cast(pTabStopItem) ); + } + + aNew.ClearItem( RES_PARATR_TABSTOP ); + aOld.ClearItem( RES_PARATR_TABSTOP ); + if( bChg ) + { + SwFormatChg aChgFormat( mpDfltCharFormat.get() ); + // notify the frames + aCallMod.ModifyNotification( &aChgFormat, &aChgFormat ); + } + } + } + + if( aNew.Count() && aCallMod.HasWriterListeners() ) + { + SwAttrSetChg aChgOld( aOld, aOld ); + SwAttrSetChg aChgNew( aNew, aNew ); + aCallMod.ModifyNotification( &aChgOld, &aChgNew ); // all changed are sent + } + + // remove the default formats from the object again + SwIterator aClientIter(aCallMod); + for(SwClient* pClient = aClientIter.First(); pClient; pClient = aClientIter.Next()) + aCallMod.Remove( pClient ); + + getIDocumentState().SetModified(); +} + +/// Get the default attribute in this document +const SfxPoolItem& SwDoc::GetDefault( sal_uInt16 nFormatHint ) const +{ + return GetAttrPool().GetDefaultItem( nFormatHint ); +} + +/// Delete the formats +void SwDoc::DelCharFormat(size_t nFormat, bool bBroadcast) +{ + SwCharFormat * pDel = (*mpCharFormatTable)[nFormat]; + + if (bBroadcast) + BroadcastStyleOperation(pDel->GetName(), SfxStyleFamily::Char, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pDel, this)); + } + + delete (*mpCharFormatTable)[nFormat]; + mpCharFormatTable->erase(mpCharFormatTable->begin() + nFormat); + + getIDocumentState().SetModified(); +} + +void SwDoc::DelCharFormat( SwCharFormat const *pFormat, bool bBroadcast ) +{ + size_t nFormat = mpCharFormatTable->GetPos( pFormat ); + OSL_ENSURE( SIZE_MAX != nFormat, "Format not found," ); + DelCharFormat( nFormat, bBroadcast ); +} + +void SwDoc::DelFrameFormat( SwFrameFormat *pFormat, bool bBroadcast ) +{ + if( dynamic_cast( pFormat) != nullptr || dynamic_cast( pFormat) != nullptr ) + { + OSL_ENSURE( false, "Format is not in the DocArray any more, " + "so it can be deleted with delete" ); + delete pFormat; + } + else + { + // The format has to be in the one or the other, we'll see in which one. + if (mpFrameFormatTable->ContainsFormat(*pFormat)) + { + if (bBroadcast) + BroadcastStyleOperation(pFormat->GetName(), + SfxStyleFamily::Frame, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pFormat, this)); + } + + mpFrameFormatTable->erase( pFormat ); + delete pFormat; + } + else + { + bool contains = GetSpzFrameFormats()->ContainsFormat(*pFormat); + OSL_ENSURE( contains, "FrameFormat not found." ); + if( contains ) + { + GetSpzFrameFormats()->erase( pFormat ); + delete pFormat; + } + } + } +} + +void SwDoc::DelTableFrameFormat( SwTableFormat *pFormat ) +{ + SwFrameFormats::const_iterator it = mpTableFrameFormatTable->find( pFormat ); + OSL_ENSURE( it != mpTableFrameFormatTable->end(), "Format not found," ); + mpTableFrameFormatTable->erase( it ); + delete pFormat; +} + +SwFrameFormat* SwDoc::FindFrameFormatByName( const OUString& rName ) const +{ + return static_cast(FindFormatByName( static_cast(*mpFrameFormatTable), rName )); +} + +/// Create the formats +SwFlyFrameFormat *SwDoc::MakeFlyFrameFormat( const OUString &rFormatName, + SwFrameFormat *pDerivedFrom ) +{ + SwFlyFrameFormat *pFormat = new SwFlyFrameFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + GetSpzFrameFormats()->push_back(pFormat); + getIDocumentState().SetModified(); + return pFormat; +} + +SwDrawFrameFormat *SwDoc::MakeDrawFrameFormat( const OUString &rFormatName, + SwFrameFormat *pDerivedFrom ) +{ + SwDrawFrameFormat *pFormat = new SwDrawFrameFormat( GetAttrPool(), rFormatName, pDerivedFrom); + GetSpzFrameFormats()->push_back(pFormat); + getIDocumentState().SetModified(); + return pFormat; +} + +size_t SwDoc::GetTableFrameFormatCount(bool bUsed) const +{ + if (!bUsed) + { + return mpTableFrameFormatTable->size(); + } + + SwAutoFormatGetDocNode aGetHt(&GetNodes()); + size_t nCount = 0; + for (SwFrameFormat* const & pFormat : *mpTableFrameFormatTable) + { + if (!pFormat->GetInfo(aGetHt)) + nCount++; + } + return nCount; +} + +SwFrameFormat& SwDoc::GetTableFrameFormat(size_t nFormat, bool bUsed) const +{ + if (!bUsed) + { + return *((*mpTableFrameFormatTable)[nFormat]); + } + + SwAutoFormatGetDocNode aGetHt(&GetNodes()); + + size_t index = 0; + + for (SwFrameFormat* const & pFormat : *mpTableFrameFormatTable) + { + if (!pFormat->GetInfo(aGetHt)) + { + if (index == nFormat) + return *pFormat; + else + index++; + } + } + throw std::out_of_range("Format index out of range."); +} + +SwTableFormat* SwDoc::MakeTableFrameFormat( const OUString &rFormatName, + SwFrameFormat *pDerivedFrom ) +{ + SwTableFormat* pFormat = new SwTableFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + mpTableFrameFormatTable->push_back( pFormat ); + getIDocumentState().SetModified(); + + return pFormat; +} + +SwFrameFormat *SwDoc::MakeFrameFormat(const OUString &rFormatName, + SwFrameFormat *pDerivedFrom, + bool bBroadcast, bool bAuto) +{ + SwFrameFormat *pFormat = new SwFrameFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + + pFormat->SetAuto(bAuto); + mpFrameFormatTable->push_back( pFormat ); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pFormat, pDerivedFrom, this)); + } + + if (bBroadcast) + { + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Frame, + SfxHintId::StyleSheetCreated); + } + + return pFormat; +} + +SwFormat *SwDoc::MakeFrameFormat_(const OUString &rFormatName, + SwFormat *pDerivedFrom, + bool bBroadcast, bool bAuto) +{ + SwFrameFormat *pFrameFormat = dynamic_cast(pDerivedFrom); + pFrameFormat = MakeFrameFormat( rFormatName, pFrameFormat, bBroadcast, bAuto ); + return dynamic_cast(pFrameFormat); +} + +SwCharFormat *SwDoc::MakeCharFormat( const OUString &rFormatName, + SwCharFormat *pDerivedFrom, + bool bBroadcast ) +{ + SwCharFormat *pFormat = new SwCharFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + mpCharFormatTable->push_back( pFormat ); + pFormat->SetAuto(false); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pFormat, pDerivedFrom, this)); + } + + if (bBroadcast) + { + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Char, + SfxHintId::StyleSheetCreated); + } + + return pFormat; +} + +SwFormat *SwDoc::MakeCharFormat_(const OUString &rFormatName, + SwFormat *pDerivedFrom, + bool bBroadcast, bool /*bAuto*/) +{ + SwCharFormat *pCharFormat = dynamic_cast(pDerivedFrom); + pCharFormat = MakeCharFormat( rFormatName, pCharFormat, bBroadcast ); + return dynamic_cast(pCharFormat); +} + +/// Create the FormatCollections +SwTextFormatColl* SwDoc::MakeTextFormatColl( const OUString &rFormatName, + SwTextFormatColl *pDerivedFrom, + bool bBroadcast) +{ + SwTextFormatColl *pFormatColl = new SwTextFormatColl( GetAttrPool(), rFormatName, + pDerivedFrom ); + mpTextFormatCollTable->push_back(pFormatColl); + pFormatColl->SetAuto(false); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pFormatColl, pDerivedFrom, + this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Para, + SfxHintId::StyleSheetCreated); + + return pFormatColl; +} + +SwFormat *SwDoc::MakeTextFormatColl_(const OUString &rFormatName, + SwFormat *pDerivedFrom, + bool bBroadcast, bool /*bAuto*/) +{ + SwTextFormatColl *pTextFormatColl = dynamic_cast(pDerivedFrom); + pTextFormatColl = MakeTextFormatColl( rFormatName, pTextFormatColl, bBroadcast ); + return dynamic_cast(pTextFormatColl); +} + +//FEATURE::CONDCOLL +SwConditionTextFormatColl* SwDoc::MakeCondTextFormatColl( const OUString &rFormatName, + SwTextFormatColl *pDerivedFrom, + bool bBroadcast) +{ + SwConditionTextFormatColl*pFormatColl = new SwConditionTextFormatColl( GetAttrPool(), + rFormatName, pDerivedFrom ); + mpTextFormatCollTable->push_back(pFormatColl); + pFormatColl->SetAuto(false); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(pFormatColl, pDerivedFrom, + this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Para, + SfxHintId::StyleSheetCreated); + + return pFormatColl; +} +//FEATURE::CONDCOLL + +// GRF +SwGrfFormatColl* SwDoc::MakeGrfFormatColl( const OUString &rFormatName, + SwGrfFormatColl *pDerivedFrom ) +{ + SwGrfFormatColl *pFormatColl = new SwGrfFormatColl( GetAttrPool(), rFormatName, + pDerivedFrom ); + mpGrfFormatCollTable->push_back( pFormatColl ); + pFormatColl->SetAuto(false); + getIDocumentState().SetModified(); + return pFormatColl; +} + +void SwDoc::DelTextFormatColl(size_t nFormatColl, bool bBroadcast) +{ + OSL_ENSURE( nFormatColl, "Remove of Coll 0." ); + + // Who has the to-be-deleted as their Next? + SwTextFormatColl *pDel = (*mpTextFormatCollTable)[nFormatColl]; + if( mpDfltTextFormatColl.get() == pDel ) + return; // never delete default! + + if (bBroadcast) + BroadcastStyleOperation(pDel->GetName(), SfxStyleFamily::Para, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo; + if (RES_CONDTXTFMTCOLL == pDel->Which()) + { + pUndo.reset(new SwUndoCondTextFormatCollDelete(pDel, this)); + } + else + { + pUndo.reset(new SwUndoTextFormatCollDelete(pDel, this)); + } + + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + // Remove the FormatColl + mpTextFormatCollTable->erase(mpTextFormatCollTable->begin() + nFormatColl); + // Correct next + for( SwTextFormatColls::const_iterator it = mpTextFormatCollTable->begin() + 1; it != mpTextFormatCollTable->end(); ++it ) + SetTextFormatCollNext( *it, pDel ); + delete pDel; + getIDocumentState().SetModified(); +} + +void SwDoc::DelTextFormatColl( SwTextFormatColl const *pColl, bool bBroadcast ) +{ + size_t nFormat = mpTextFormatCollTable->GetPos( pColl ); + OSL_ENSURE( SIZE_MAX != nFormat, "Collection not found," ); + DelTextFormatColl( nFormat, bBroadcast ); +} + +static bool lcl_SetTextFormatColl( const SwNodePtr& rpNode, void* pArgs ) +{ + SwContentNode* pCNd = rpNode->GetTextNode(); + + if( pCNd == nullptr) + return true; + + sw::DocumentContentOperationsManager::ParaRstFormat* pPara = static_cast(pArgs); + + if (pPara->pLayout && pPara->pLayout->IsHideRedlines()) + { + if (pCNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; + } + if (pCNd->IsTextNode()) + { + pCNd = sw::GetParaPropsNode(*pPara->pLayout, SwNodeIndex(*pCNd)); + } + } + + SwTextFormatColl* pFormat = static_cast(pPara->pFormatColl); + if ( pPara->bReset ) + { + lcl_RstAttr(pCNd, pPara); + + // #i62675# check, if paragraph style has changed + if ( pPara->bResetListAttrs && + pFormat != pCNd->GetFormatColl() && + pFormat->GetItemState( RES_PARATR_NUMRULE ) == SfxItemState::SET ) + { + // Check, if the list style of the paragraph will change. + bool bChangeOfListStyleAtParagraph( true ); + SwTextNode& rTNd(dynamic_cast(*pCNd)); + { + SwNumRule* pNumRuleAtParagraph(rTNd.GetNumRule()); + if ( pNumRuleAtParagraph ) + { + const SwNumRuleItem& rNumRuleItemAtParagraphStyle = + pFormat->GetNumRule(); + if ( rNumRuleItemAtParagraphStyle.GetValue() == + pNumRuleAtParagraph->GetName() ) + { + bChangeOfListStyleAtParagraph = false; + } + } + } + + if ( bChangeOfListStyleAtParagraph ) + { + std::unique_ptr< SwRegHistory > pRegH; + if ( pPara->pHistory ) + { + pRegH.reset(new SwRegHistory(&rTNd, rTNd, pPara->pHistory)); + } + + pCNd->ResetAttr( RES_PARATR_NUMRULE ); + + // reset all list attributes + pCNd->ResetAttr( RES_PARATR_LIST_LEVEL ); + pCNd->ResetAttr( RES_PARATR_LIST_ISRESTART ); + pCNd->ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + pCNd->ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + pCNd->ResetAttr( RES_PARATR_LIST_ID ); + } + } + } + + // add to History so that old data is saved, if necessary + if( pPara->pHistory ) + pPara->pHistory->Add( pCNd->GetFormatColl(), pCNd->GetIndex(), + SwNodeType::Text ); + + pCNd->ChgFormatColl( pFormat ); + + pPara->nWhich++; + + return true; +} + +bool SwDoc::SetTextFormatColl(const SwPaM &rRg, + SwTextFormatColl *pFormat, + const bool bReset, + const bool bResetListAttrs, + SwRootFrame const*const pLayout) +{ + SwDataChanged aTmp( rRg ); + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + SwHistory* pHst = nullptr; + bool bRet = true; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo(new SwUndoFormatColl( rRg, pFormat, + bReset, + bResetListAttrs )); + pHst = pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout); + aPara.pFormatColl = pFormat; + aPara.bReset = bReset; + // #i62675# + aPara.bResetListAttrs = bResetListAttrs; + + GetNodes().ForEach( pStt->nNode.GetIndex(), pEnd->nNode.GetIndex()+1, + lcl_SetTextFormatColl, &aPara ); + if( !aPara.nWhich ) + bRet = false; // didn't find a valid Node + + if (bRet) + { + getIDocumentState().SetModified(); + } + + return bRet; +} + +/// Copy the formats to itself +SwFormat* SwDoc::CopyFormat( const SwFormat& rFormat, + const SwFormatsBase& rFormatArr, + FNCopyFormat fnCopyFormat, const SwFormat& rDfltFormat ) +{ + // It's no autoformat, default format or collection format, + // then search for it. + if( !rFormat.IsAuto() || !rFormat.GetRegisteredIn() ) + for( size_t n = 0; n < rFormatArr.GetFormatCount(); ++n ) + { + // Does the Doc already contain the template? + if( rFormatArr.GetFormat(n)->GetName()==rFormat.GetName() ) + return rFormatArr.GetFormat(n); + } + + // Search for the "parent" first + SwFormat* pParent = const_cast(&rDfltFormat); + if( rFormat.DerivedFrom() && pParent != rFormat.DerivedFrom() ) + pParent = CopyFormat( *rFormat.DerivedFrom(), rFormatArr, + fnCopyFormat, rDfltFormat ); + + // Create the format and copy the attributes + // #i40550# + SwFormat* pNewFormat = (this->*fnCopyFormat)( rFormat.GetName(), pParent, false, true ); + pNewFormat->SetAuto( rFormat.IsAuto() ); + pNewFormat->CopyAttrs( rFormat ); // copy the attributes + + pNewFormat->SetPoolFormatId( rFormat.GetPoolFormatId() ); + pNewFormat->SetPoolHelpId( rFormat.GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pNewFormat->SetPoolHlpFileId( UCHAR_MAX ); + + return pNewFormat; +} + +/// copy the frame format +SwFrameFormat* SwDoc::CopyFrameFormat( const SwFrameFormat& rFormat ) +{ + return static_cast(CopyFormat( rFormat, *GetFrameFormats(), &SwDoc::MakeFrameFormat_, + *GetDfltFrameFormat() )); +} + +/// copy the char format +SwCharFormat* SwDoc::CopyCharFormat( const SwCharFormat& rFormat ) +{ + return static_cast(CopyFormat( rFormat, *GetCharFormats(), + &SwDoc::MakeCharFormat_, + *GetDfltCharFormat() )); +} + +/// copy TextNodes +SwTextFormatColl* SwDoc::CopyTextColl( const SwTextFormatColl& rColl ) +{ + SwTextFormatColl* pNewColl = FindTextFormatCollByName( rColl.GetName() ); + if( pNewColl ) + return pNewColl; + + // search for the "parent" first + SwTextFormatColl* pParent = mpDfltTextFormatColl.get(); + if( pParent != rColl.DerivedFrom() ) + pParent = CopyTextColl( *static_cast(rColl.DerivedFrom()) ); + +//FEATURE::CONDCOLL + if( RES_CONDTXTFMTCOLL == rColl.Which() ) + { + pNewColl = new SwConditionTextFormatColl( GetAttrPool(), rColl.GetName(), + pParent); + mpTextFormatCollTable->push_back( pNewColl ); + pNewColl->SetAuto(false); + getIDocumentState().SetModified(); + + // copy the conditions + static_cast(pNewColl)->SetConditions( + static_cast(rColl).GetCondColls() ); + } + else +//FEATURE::CONDCOLL + pNewColl = MakeTextFormatColl( rColl.GetName(), pParent ); + + // copy the auto formats or the attributes + pNewColl->CopyAttrs( rColl ); + + if(rColl.IsAssignedToListLevelOfOutlineStyle()) + pNewColl->AssignToListLevelOfOutlineStyle(rColl.GetAssignedOutlineStyleLevel()); + pNewColl->SetPoolFormatId( rColl.GetPoolFormatId() ); + pNewColl->SetPoolHelpId( rColl.GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pNewColl->SetPoolHlpFileId( UCHAR_MAX ); + + if( &rColl.GetNextTextFormatColl() != &rColl ) + pNewColl->SetNextTextFormatColl( *CopyTextColl( rColl.GetNextTextFormatColl() )); + + // create the NumRule if necessary + if( this != rColl.GetDoc() ) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pNewColl->GetItemState( RES_PARATR_NUMRULE, + false, &pItem )) + { + const OUString& rName = static_cast(pItem)->GetValue(); + if( !rName.isEmpty() ) + { + const SwNumRule* pRule = rColl.GetDoc()->FindNumRulePtr( rName ); + if( pRule && !pRule->IsAutoRule() ) + { + SwNumRule* pDestRule = FindNumRulePtr( rName ); + if( pDestRule ) + pDestRule->SetInvalidRule( true ); + else + MakeNumRule( rName, pRule ); + } + } + } + } + return pNewColl; +} + +/// copy the graphic nodes +SwGrfFormatColl* SwDoc::CopyGrfColl( const SwGrfFormatColl& rColl ) +{ + SwGrfFormatColl* pNewColl = static_cast(FindFormatByName( static_cast(*mpGrfFormatCollTable), rColl.GetName() )); + if( pNewColl ) + return pNewColl; + + // Search for the "parent" first + SwGrfFormatColl* pParent = mpDfltGrfFormatColl.get(); + if( pParent != rColl.DerivedFrom() ) + pParent = CopyGrfColl( *static_cast(rColl.DerivedFrom()) ); + + // if not, copy them + pNewColl = MakeGrfFormatColl( rColl.GetName(), pParent ); + + // copy the attributes + pNewColl->CopyAttrs( rColl ); + + pNewColl->SetPoolFormatId( rColl.GetPoolFormatId() ); + pNewColl->SetPoolHelpId( rColl.GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pNewColl->SetPoolHlpFileId( UCHAR_MAX ); + + return pNewColl; +} + +void SwDoc::CopyFormatArr( const SwFormatsBase& rSourceArr, + SwFormatsBase const & rDestArr, + FNCopyFormat fnCopyFormat, + SwFormat& rDfltFormat ) +{ + SwFormat* pSrc, *pDest; + + // 1st step: Create all formats (skip the 0th - it's the default one) + for( size_t nSrc = rSourceArr.GetFormatCount(); nSrc > 1; ) + { + pSrc = rSourceArr.GetFormat( --nSrc ); + if( pSrc->IsDefault() || pSrc->IsAuto() ) + continue; + + if( nullptr == FindFormatByName( rDestArr, pSrc->GetName() ) ) + { + if( RES_CONDTXTFMTCOLL == pSrc->Which() ) + MakeCondTextFormatColl( pSrc->GetName(), static_cast(&rDfltFormat) ); + else + // #i40550# + (this->*fnCopyFormat)( pSrc->GetName(), &rDfltFormat, false, true ); + } + } + + // 2nd step: Copy all attributes, set the right parents + for( size_t nSrc = rSourceArr.GetFormatCount(); nSrc > 1; ) + { + pSrc = rSourceArr.GetFormat( --nSrc ); + if( pSrc->IsDefault() || pSrc->IsAuto() ) + continue; + + pDest = FindFormatByName( rDestArr, pSrc->GetName() ); + pDest->SetAuto(false); + pDest->DelDiffs( *pSrc ); + + // #i94285#: existing instance, before copying attributes + const SfxPoolItem* pItem; + if( &GetAttrPool() != pSrc->GetAttrSet().GetPool() && + SfxItemState::SET == pSrc->GetAttrSet().GetItemState( + RES_PAGEDESC, false, &pItem ) && + static_cast(pItem)->GetPageDesc() ) + { + SwFormatPageDesc aPageDesc( *static_cast(pItem) ); + const OUString& rNm = aPageDesc.GetPageDesc()->GetName(); + SwPageDesc* pPageDesc = FindPageDesc( rNm ); + if( !pPageDesc ) + { + pPageDesc = MakePageDesc(rNm); + } + aPageDesc.RegisterToPageDesc( *pPageDesc ); + SwAttrSet aTmpAttrSet( pSrc->GetAttrSet() ); + aTmpAttrSet.Put( aPageDesc ); + pDest->SetFormatAttr( aTmpAttrSet ); + } + else + { + pDest->SetFormatAttr( pSrc->GetAttrSet() ); + } + + pDest->SetPoolFormatId( pSrc->GetPoolFormatId() ); + pDest->SetPoolHelpId( pSrc->GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pDest->SetPoolHlpFileId( UCHAR_MAX ); + + if( pSrc->DerivedFrom() ) + pDest->SetDerivedFrom( FindFormatByName( rDestArr, + pSrc->DerivedFrom()->GetName() ) ); + if( RES_TXTFMTCOLL == pSrc->Which() || + RES_CONDTXTFMTCOLL == pSrc->Which() ) + { + SwTextFormatColl* pSrcColl = static_cast(pSrc), + * pDstColl = static_cast(pDest); + if( &pSrcColl->GetNextTextFormatColl() != pSrcColl ) + pDstColl->SetNextTextFormatColl( *static_cast(FindFormatByName( + rDestArr, pSrcColl->GetNextTextFormatColl().GetName() ) ) ); + + if(pSrcColl->IsAssignedToListLevelOfOutlineStyle()) + pDstColl->AssignToListLevelOfOutlineStyle(pSrcColl->GetAssignedOutlineStyleLevel()); + +//FEATURE::CONDCOLL + if( RES_CONDTXTFMTCOLL == pSrc->Which() ) + // Copy the conditions, but delete the old ones first! + static_cast(pDstColl)->SetConditions( + static_cast(pSrc)->GetCondColls() ); +//FEATURE::CONDCOLL + } + } +} + +void SwDoc::CopyPageDescHeaderFooterImpl( bool bCpyHeader, + const SwFrameFormat& rSrcFormat, SwFrameFormat& rDestFormat ) +{ + // Treat the header and footer attributes in the right way: + // Copy content nodes across documents! + sal_uInt16 nAttr = bCpyHeader ? sal_uInt16(RES_HEADER) : sal_uInt16(RES_FOOTER); + const SfxPoolItem* pItem; + if( SfxItemState::SET != rSrcFormat.GetAttrSet().GetItemState( nAttr, false, &pItem )) + return ; + + // The header only contains the reference to the format from the other document! + std::unique_ptr pNewItem(pItem->Clone()); + + SwFrameFormat* pOldFormat; + if( bCpyHeader ) + pOldFormat = static_cast(pNewItem.get())->GetHeaderFormat(); + else + pOldFormat = static_cast(pNewItem.get())->GetFooterFormat(); + + if( pOldFormat ) + { + SwFrameFormat* pNewFormat = new SwFrameFormat( GetAttrPool(), "CpyDesc", + GetDfltFrameFormat() ); + pNewFormat->CopyAttrs( *pOldFormat ); + + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState( + RES_CNTNT, false, &pItem )) + { + const SwFormatContent* pContent = static_cast(pItem); + if( pContent->GetContentIdx() ) + { + SwNodeIndex aTmpIdx( GetNodes().GetEndOfAutotext() ); + const SwNodes& rSrcNds = rSrcFormat.GetDoc()->GetNodes(); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aTmpIdx, + bCpyHeader + ? SwHeaderStartNode + : SwFooterStartNode ); + const SwNode& rCSttNd = pContent->GetContentIdx()->GetNode(); + SwNodeRange aRg( rCSttNd, 0, *rCSttNd.EndOfSectionNode() ); + aTmpIdx = *pSttNd->EndOfSectionNode(); + rSrcNds.Copy_( aRg, aTmpIdx ); + aTmpIdx = *pSttNd; + rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aTmpIdx); + // TODO: investigate calling CopyWithFlyInFly? + SwPaM const source(aRg.aStart, aRg.aEnd); + SwPosition dest(aTmpIdx); + sw::CopyBookmarks(source, dest); + pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); + } + else + pNewFormat->ResetFormatAttr( RES_CNTNT ); + } + if( bCpyHeader ) + static_cast(pNewItem.get())->RegisterToFormat(*pNewFormat); + else + static_cast(pNewItem.get())->RegisterToFormat(*pNewFormat); + rDestFormat.SetFormatAttr( *pNewItem ); + } +} + +void SwDoc::CopyPageDesc( const SwPageDesc& rSrcDesc, SwPageDesc& rDstDesc, + bool bCopyPoolIds ) +{ + bool bNotifyLayout = false; + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + + rDstDesc.SetLandscape( rSrcDesc.GetLandscape() ); + rDstDesc.SetNumType( rSrcDesc.GetNumType() ); + if( rDstDesc.ReadUseOn() != rSrcDesc.ReadUseOn() ) + { + rDstDesc.WriteUseOn( rSrcDesc.ReadUseOn() ); + bNotifyLayout = true; + } + + if( bCopyPoolIds ) + { + rDstDesc.SetPoolFormatId( rSrcDesc.GetPoolFormatId() ); + rDstDesc.SetPoolHelpId( rSrcDesc.GetPoolHelpId() ); + // Always set the HelpFile Id to default! + rDstDesc.SetPoolHlpFileId( UCHAR_MAX ); + } + + if( rSrcDesc.GetFollow() != &rSrcDesc ) + { + const SwPageDesc* pSrcFollow = rSrcDesc.GetFollow(); + SwPageDesc* pFollow = FindPageDesc( pSrcFollow->GetName() ); + if( !pFollow ) + { + // copy + pFollow = MakePageDesc( pSrcFollow->GetName() ); + CopyPageDesc( *pSrcFollow, *pFollow ); + } + rDstDesc.SetFollow( pFollow ); + bNotifyLayout = true; + } + + // the header and footer attributes are copied separately + // the content sections have to be copied in their entirety + { + SfxItemSet aAttrSet( rSrcDesc.GetMaster().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetMaster().DelDiffs( aAttrSet ); + rDstDesc.GetMaster().SetFormatAttr( aAttrSet ); + + aAttrSet.ClearItem(); + aAttrSet.Put( rSrcDesc.GetLeft().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetLeft().DelDiffs( aAttrSet ); + rDstDesc.GetLeft().SetFormatAttr( aAttrSet ); + + aAttrSet.ClearItem(); + aAttrSet.Put( rSrcDesc.GetFirstMaster().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetFirstMaster().DelDiffs( aAttrSet ); + rDstDesc.GetFirstMaster().SetFormatAttr( aAttrSet ); + + aAttrSet.ClearItem(); + aAttrSet.Put( rSrcDesc.GetFirstLeft().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetFirstLeft().DelDiffs( aAttrSet ); + rDstDesc.GetFirstLeft().SetFormatAttr( aAttrSet ); + } + + CopyHeader( rSrcDesc.GetMaster(), rDstDesc.GetMaster() ); + CopyFooter( rSrcDesc.GetMaster(), rDstDesc.GetMaster() ); + if( !rDstDesc.IsHeaderShared() ) + CopyHeader( rSrcDesc.GetLeft(), rDstDesc.GetLeft() ); + else + rDstDesc.GetLeft().SetFormatAttr( rDstDesc.GetMaster().GetHeader() ); + if( !rDstDesc.IsFirstShared() ) + { + CopyHeader( rSrcDesc.GetFirstMaster(), rDstDesc.GetFirstMaster() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetFirstMaster().GetHeader()); + } + else + { + rDstDesc.GetFirstMaster().SetFormatAttr( rDstDesc.GetMaster().GetHeader() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetLeft().GetHeader()); + } + + if( !rDstDesc.IsFooterShared() ) + CopyFooter( rSrcDesc.GetLeft(), rDstDesc.GetLeft() ); + else + rDstDesc.GetLeft().SetFormatAttr( rDstDesc.GetMaster().GetFooter() ); + if( !rDstDesc.IsFirstShared() ) + { + CopyFooter( rSrcDesc.GetFirstMaster(), rDstDesc.GetFirstMaster() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetFirstMaster().GetFooter()); + } + else + { + rDstDesc.GetFirstMaster().SetFormatAttr( rDstDesc.GetMaster().GetFooter() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetLeft().GetFooter()); + } + + if( bNotifyLayout && pTmpRoot ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->AllCheckPageDescs(); + } + + // If foot notes change the pages have to be triggered + if( !(rDstDesc.GetFootnoteInfo() == rSrcDesc.GetFootnoteInfo()) ) + { + sw::PageFootnoteHint aHint; + rDstDesc.SetFootnoteInfo( rSrcDesc.GetFootnoteInfo() ); + rDstDesc.GetMaster().CallSwClientNotify(aHint); + rDstDesc.GetLeft().CallSwClientNotify(aHint); + rDstDesc.GetFirstMaster().CallSwClientNotify(aHint); + rDstDesc.GetFirstLeft().CallSwClientNotify(aHint); + } +} + +void SwDoc::ReplaceStyles( const SwDoc& rSource, bool bIncludePageStyles ) +{ + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + CopyFormatArr( *rSource.mpCharFormatTable, *mpCharFormatTable, + &SwDoc::MakeCharFormat_, *mpDfltCharFormat ); + CopyFormatArr( *rSource.mpFrameFormatTable, *mpFrameFormatTable, + &SwDoc::MakeFrameFormat_, *mpDfltFrameFormat ); + CopyFormatArr( *rSource.mpTextFormatCollTable, *mpTextFormatCollTable, + &SwDoc::MakeTextFormatColl_, *mpDfltTextFormatColl ); + + //To-Do: + // a) in rtf export don't export our hideous pgdsctbl + // extension to rtf anymore + // b) in sd rtf import (View::InsertData) don't use + // a super-fragile test for mere presence of \trowd to + // indicate import of rtf into a table + // c) then drop use of bIncludePageStyles + if (bIncludePageStyles) + { + // and now the page templates + SwPageDescs::size_type nCnt = rSource.m_PageDescs.size(); + if( nCnt ) + { + // a different Doc -> Number formatter needs to be merged + SwTableNumFormatMerge aTNFM( rSource, *this ); + + // 1st step: Create all formats (skip the 0th - it's the default!) + while( nCnt ) + { + const SwPageDesc &rSrc = *rSource.m_PageDescs[ --nCnt ]; + if( nullptr == FindPageDesc( rSrc.GetName() ) ) + MakePageDesc( rSrc.GetName() ); + } + + // 2nd step: Copy all attributes, set the right parents + for (SwPageDescs::size_type i = rSource.m_PageDescs.size(); i; ) + { + const SwPageDesc &rSrc = *rSource.m_PageDescs[ --i ]; + SwPageDesc* pDesc = FindPageDesc( rSrc.GetName() ); + CopyPageDesc( rSrc, *pDesc); + } + } + } + + // then there are the numbering templates + const SwNumRuleTable::size_type nCnt = rSource.GetNumRuleTable().size(); + if( nCnt ) + { + const SwNumRuleTable& rArr = rSource.GetNumRuleTable(); + for( SwNumRuleTable::size_type n = 0; n < nCnt; ++n ) + { + const SwNumRule& rR = *rArr[ n ]; + SwNumRule* pNew = FindNumRulePtr( rR.GetName()); + if( pNew ) + pNew->CopyNumRule( this, rR ); + else + { + if( !rR.IsAutoRule() ) + MakeNumRule( rR.GetName(), &rR ); + else + { + // as we reset all styles, there shouldn't be any unknown + // automatic SwNumRules, because all should have been + // created by the style copying! + // So just warn and ignore. + SAL_WARN( "sw.core", "Found unknown auto SwNumRule during reset!" ); + } + } + } + } + + if (undoGuard.UndoWasEnabled()) + { + // nodes array was modified! + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + getIDocumentState().SetModified(); +} + +SwFormat* SwDoc::FindFormatByName( const SwFormatsBase& rFormatArr, + const OUString& rName ) +{ + SwFormat* pFnd = nullptr; + for( size_t n = 0; n < rFormatArr.GetFormatCount(); ++n ) + { + // Does the Doc already contain the template? + if( rFormatArr.GetFormat(n)->HasName( rName ) ) + { + pFnd = rFormatArr.GetFormat(n); + break; + } + } + return pFnd; +} + +void SwDoc::MoveLeftMargin(const SwPaM& rPam, bool bRight, bool bModulus, + SwRootFrame const*const pLayout) +{ + SwHistory* pHistory = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo(new SwUndoMoveLeftMargin( rPam, bRight, + bModulus )); + pHistory = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + const SvxTabStopItem& rTabItem = GetDefault( RES_PARATR_TABSTOP ); + const sal_Int32 nDefDist = rTabItem.Count() ? rTabItem[0].GetTabPos() : 1134; + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + SwNodeIndex aIdx( rStt.nNode ); + while( aIdx <= rEnd.nNode ) + { + SwTextNode* pTNd = aIdx.GetNode().GetTextNode(); + if( pTNd ) + { + pTNd = sw::GetParaPropsNode(*pLayout, aIdx); + SvxLRSpaceItem aLS( static_cast(pTNd->SwContentNode::GetAttr( RES_LR_SPACE )) ); + + // #i93873# See also lcl_MergeListLevelIndentAsLRSpaceItem in thints.cxx + if ( pTNd->AreListLevelIndentsApplicable() ) + { + const SwNumRule* pRule = pTNd->GetNumRule(); + if ( pRule ) + { + const int nListLevel = pTNd->GetActualListLevel(); + if ( nListLevel >= 0 ) + { + const SwNumFormat& rFormat = pRule->Get(static_cast(nListLevel)); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aLS.SetTextLeft( rFormat.GetIndentAt() ); + aLS.SetTextFirstLineOffset( static_cast(rFormat.GetFirstLineIndent()) ); + } + } + } + } + + long nNext = aLS.GetTextLeft(); + if( bModulus ) + nNext = ( nNext / nDefDist ) * nDefDist; + + if( bRight ) + nNext += nDefDist; + else + if(nNext >0) // fdo#75936 set limit for decreasing indent + nNext -= nDefDist; + + aLS.SetTextLeft( nNext ); + + SwRegHistory aRegH( pTNd, *pTNd, pHistory ); + pTNd->SetAttr( aLS ); + aIdx = *sw::GetFirstAndLastNode(*pLayout, aIdx).second; + } + ++aIdx; + } + getIDocumentState().SetModified(); +} + +bool SwDoc::DontExpandFormat( const SwPosition& rPos, bool bFlag ) +{ + bool bRet = false; + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( pTextNd ) + { + bRet = pTextNd->DontExpandFormat( rPos.nContent, bFlag ); + if( bRet && GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique(rPos) ); + } + } + return bRet; +} + +SwTableBoxFormat* SwDoc::MakeTableBoxFormat() +{ + SwTableBoxFormat* pFormat = new SwTableBoxFormat( GetAttrPool(), mpDfltFrameFormat.get() ); + pFormat->SetName("TableBox" + OUString::number(reinterpret_cast(pFormat))); + getIDocumentState().SetModified(); + return pFormat; +} + +SwTableLineFormat* SwDoc::MakeTableLineFormat() +{ + SwTableLineFormat* pFormat = new SwTableLineFormat( GetAttrPool(), mpDfltFrameFormat.get() ); + pFormat->SetName("TableLine" + OUString::number(reinterpret_cast(pFormat))); + getIDocumentState().SetModified(); + return pFormat; +} + +void SwDoc::EnsureNumberFormatter() +{ + comphelper::doubleCheckedInit(mpNumberFormatter, []() + { + LanguageType eLang = LANGUAGE_SYSTEM; + SvNumberFormatter* pRet = new SvNumberFormatter(comphelper::getProcessComponentContext(), eLang); + pRet->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL ); + if (!utl::ConfigManager::IsFuzzing()) + pRet->SetYear2000(static_cast(::utl::MiscCfg().GetYear2000())); + return pRet; + }); +} + +SwTableNumFormatMerge::SwTableNumFormatMerge( const SwDoc& rSrc, SwDoc& rDest ) + : pNFormat( nullptr ) +{ + // a different Doc -> Number formatter needs to be merged + if( &rSrc != &rDest ) + { + SvNumberFormatter* pN = const_cast(rSrc).GetNumberFormatter( false ); + if( pN ) + { + pNFormat = rDest.GetNumberFormatter(); + pNFormat->MergeFormatter( *pN ); + } + } + + if( &rSrc != &rDest ) + static_cast(rSrc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::GetRef ))-> + MergeWithOtherDoc( rDest ); +} + +SwTableNumFormatMerge::~SwTableNumFormatMerge() +{ + if( pNFormat ) + pNFormat->ClearMergeTable(); +} + +void SwDoc::SetTextFormatCollByAutoFormat( const SwPosition& rPos, sal_uInt16 nPoolId, + const SfxItemSet* pSet ) +{ + SwPaM aPam( rPos ); + SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + assert(pTNd); + + if (mbIsAutoFormatRedline) + { + // create the redline object + const SwTextFormatColl& rColl = *pTNd->GetTextColl(); + SwRangeRedline* pRedl = new SwRangeRedline( RedlineType::FmtColl, aPam ); + pRedl->SetMark(); + + // Only those items that are not set by the Set again in the Node + // are of interest. Thus, we take the difference. + SwRedlineExtraData_FormatColl aExtraData( rColl.GetName(), + rColl.GetPoolFormatId() ); + if( pSet && pTNd->HasSwAttrSet() ) + { + SfxItemSet aTmp( *pTNd->GetpSwAttrSet() ); + aTmp.Differentiate( *pSet ); + // we handle the adjust item separately + const SfxPoolItem* pItem; + if( SfxItemState::SET == pTNd->GetpSwAttrSet()->GetItemState( + RES_PARATR_ADJUST, false, &pItem )) + aTmp.Put( *pItem ); + aExtraData.SetItemSet( aTmp ); + } + pRedl->SetExtraData( &aExtraData ); + + //TODO: Undo is still missing! + getIDocumentRedlineAccess().AppendRedline( pRedl, true ); + } + + SetTextFormatColl( aPam, getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId ) ); + + if (pSet && pSet->Count()) + { + aPam.SetMark(); + aPam.GetMark()->nContent.Assign(pTNd, pTNd->GetText().getLength()); + // sw_redlinehide: don't need layout currently because the only caller + // passes in the properties node + assert(static_cast(pTNd->getLayoutFrame(nullptr))->GetTextNodeForParaProps() == pTNd); + getIDocumentContentOperations().InsertItemSet( aPam, *pSet ); + } +} + +void SwDoc::SetFormatItemByAutoFormat( const SwPaM& rPam, const SfxItemSet& rSet ) +{ + SwTextNode* pTNd = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + assert(pTNd); + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + + if (mbIsAutoFormatRedline) + { + // create the redline object + SwRangeRedline* pRedl = new SwRangeRedline( RedlineType::Format, rPam ); + if( !pRedl->HasMark() ) + pRedl->SetMark(); + + // Only those items that are not set by the Set again in the Node + // are of interest. Thus, we take the difference. + SwRedlineExtraData_Format aExtraData( rSet ); + + pRedl->SetExtraData( &aExtraData ); + + //TODO: Undo is still missing! + getIDocumentRedlineAccess().AppendRedline( pRedl, true ); + + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore ); + } + + const sal_Int32 nEnd(rPam.End()->nContent.GetIndex()); + std::vector whichIds; + SfxItemIter iter(rSet); + for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem()) + { + whichIds.push_back(pItem->Which()); + whichIds.push_back(pItem->Which()); + } + whichIds.push_back(0); + SfxItemSet currentSet(GetAttrPool(), whichIds.data()); + pTNd->GetParaAttr(currentSet, nEnd, nEnd); + for (size_t i = 0; whichIds[i]; i += 2) + { // yuk - want to explicitly set the pool defaults too :-/ + currentSet.Put(currentSet.Get(whichIds[i])); + } + + getIDocumentContentOperations().InsertItemSet( rPam, rSet, SetAttrMode::DONTEXPAND ); + + // fdo#62536: DONTEXPAND does not work when there is already an AUTOFMT + // here, so insert the old attributes as an empty hint to stop expand + SwPaM endPam(*pTNd, nEnd); + endPam.SetMark(); + getIDocumentContentOperations().InsertItemSet(endPam, currentSet); + + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwDoc::ChgFormat(SwFormat & rFormat, const SfxItemSet & rSet) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + // copying to + SfxItemSet aSet(rSet); + // remove from all items, which are already set at the format + aSet.Differentiate(rFormat.GetAttrSet()); + // contains now all *new* items for the format + + // copying current format item set to + SfxItemSet aOldSet(rFormat.GetAttrSet()); + // insert new items into + aOldSet.Put(aSet); + // invalidate all new items in in order to clear these items, + // if the undo action is triggered. + { + SfxItemIter aIter(aSet); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + aOldSet.InvalidateItem(pItem->Which()); + } + } + + GetIDocumentUndoRedo().AppendUndo( + std::make_unique(aOldSet, rFormat, /*bSaveDrawPt*/true)); + } + + rFormat.SetFormatAttr(rSet); +} + +void SwDoc::RenameFormat(SwFormat & rFormat, const OUString & sNewName, + bool bBroadcast) +{ + SfxStyleFamily eFamily = SfxStyleFamily::All; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr pUndo; + + switch (rFormat.Which()) + { + case RES_CHRFMT: + pUndo.reset(new SwUndoRenameCharFormat(rFormat.GetName(), sNewName, this)); + eFamily = SfxStyleFamily::Char; + break; + case RES_TXTFMTCOLL: + pUndo.reset(new SwUndoRenameFormatColl(rFormat.GetName(), sNewName, this)); + eFamily = SfxStyleFamily::Para; + break; + case RES_FRMFMT: + pUndo.reset(new SwUndoRenameFrameFormat(rFormat.GetName(), sNewName, this)); + eFamily = SfxStyleFamily::Frame; + break; + + default: + break; + } + + if (pUndo) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + rFormat.SetName(sNewName); + + if (bBroadcast) + BroadcastStyleOperation(sNewName, eFamily, SfxHintId::StyleSheetModified); +} + +void SwDoc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("nodes.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + xmlTextWriterStartElement(pWriter, BAD_CAST("SwDoc")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + m_pNodes->dumpAsXml(pWriter); + m_PageDescs.dumpAsXml(pWriter); + maDBData.dumpAsXml(pWriter); + mpMarkManager->dumpAsXml(pWriter); + m_pUndoManager->dumpAsXml(pWriter); + m_pDocumentSettingManager->dumpAsXml(pWriter); + getIDocumentFieldsAccess().GetFieldTypes()->dumpAsXml(pWriter); + mpTextFormatCollTable->dumpAsXml(pWriter); + mpCharFormatTable->dumpAsXml(pWriter); + mpFrameFormatTable->dumpAsXml(pWriter, "frmFormatTable"); + mpSpzFrameFormatTable->dumpAsXml(pWriter, "spzFrameFormatTable"); + mpSectionFormatTable->dumpAsXml(pWriter); + mpTableFrameFormatTable->dumpAsXml(pWriter, "tableFrameFormatTable"); + mpNumRuleTable->dumpAsXml(pWriter); + getIDocumentRedlineAccess().GetRedlineTable().dumpAsXml(pWriter); + getIDocumentRedlineAccess().GetExtraRedlineTable().dumpAsXml(pWriter); + if (const SdrModel* pModel = getIDocumentDrawModelAccess().GetDrawModel()) + pModel->dumpAsXml(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbModified")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(getIDocumentState().IsModified()).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); + if (bOwns) + { + xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +void SwDBData::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwDBData")); + + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("sDataSource"), BAD_CAST(sDataSource.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("sCommand"), BAD_CAST(sCommand.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nCommandType"), BAD_CAST(OString::number(nCommandType).getStr())); + + xmlTextWriterEndElement(pWriter); +} + +std::set SwDoc::GetDocColors() +{ + std::set aDocColors; + SwAttrPool& rPool = GetAttrPool(); + const sal_uInt16 pAttribs[] = {RES_CHRATR_COLOR, RES_CHRATR_HIGHLIGHT, RES_BACKGROUND}; + for (sal_uInt16 nAttrib : pAttribs) + { + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(nAttrib)) + { + auto pColorItem = static_cast(pItem); + Color aColor( pColorItem->GetValue() ); + if (COL_AUTO != aColor) + aDocColors.insert(aColor); + } + } + return aDocColors; +} + +// #i69627# +namespace docfunc +{ + bool HasOutlineStyleToBeWrittenAsNormalListStyle( SwDoc& rDoc ) + { + // If a parent paragraph style of one of the paragraph styles, which + // are assigned to the list levels of the outline style, has a list style + // set or inherits a list style from its parent style, the outline style + // has to be written as a normal list style to the OpenDocument file + // format or the OpenOffice.org file format. + bool bRet( false ); + + const SwTextFormatColls* pTextFormatColls( rDoc.GetTextFormatColls() ); + if ( pTextFormatColls ) + { + for ( auto pTextFormatColl : *pTextFormatColls ) + { + if ( pTextFormatColl->IsDefault() || + ! pTextFormatColl->IsAssignedToListLevelOfOutlineStyle() ) + { + continue; + } + + const SwTextFormatColl* pParentTextFormatColl = + dynamic_cast( pTextFormatColl->DerivedFrom()); + if ( !pParentTextFormatColl ) + continue; + + if ( SfxItemState::SET == pParentTextFormatColl->GetItemState( RES_PARATR_NUMRULE ) ) + { + // #i106218# consider that the outline style is set + const SwNumRuleItem& rDirectItem = pParentTextFormatColl->GetNumRule(); + if ( rDirectItem.GetValue() != rDoc.GetOutlineNumRule()->GetName() ) + { + bRet = true; + break; + } + } + } + + } + return bRet; + } +} + +SwFrameFormats::SwFrameFormats() + : m_PosIndex( m_Array.get<0>() ) + , m_TypeAndNameIndex( m_Array.get<1>() ) +{ +} + +SwFrameFormats::~SwFrameFormats() +{ + DeleteAndDestroyAll(); +} + +SwFrameFormats::const_iterator SwFrameFormats::find( const value_type& x ) const +{ + ByTypeAndName::iterator it = m_TypeAndNameIndex.find( + boost::make_tuple(x->Which(), x->GetName(), x) ); + return m_Array.project<0>( it ); +} + +std::pair +SwFrameFormats::rangeFind( sal_uInt16 type, const OUString& name ) const +{ + return m_TypeAndNameIndex.equal_range( boost::make_tuple(type, name) ); +} + +std::pair +SwFrameFormats::rangeFind( const value_type& x ) const +{ + return rangeFind( x->Which(), x->GetName() ); +} + +void SwFrameFormats::DeleteAndDestroyAll( bool keepDefault ) +{ + if ( empty() ) + return; + const int _offset = keepDefault ? 1 : 0; + for( const_iterator it = begin() + _offset; it != end(); ++it ) + delete *it; + if ( _offset ) + m_PosIndex.erase( begin() + _offset, end() ); + else + m_Array.clear(); +} + +std::pair SwFrameFormats::push_back( const value_type& x ) +{ + SAL_WARN_IF(x->m_ffList != nullptr, "sw.core", "Inserting already assigned item"); + assert(x->m_ffList == nullptr); + x->m_ffList = this; + return m_PosIndex.push_back( x ); +} + +bool SwFrameFormats::erase( const value_type& x ) +{ + const_iterator const ret = find( x ); + SAL_WARN_IF(x->m_ffList != this, "sw.core", "Removing invalid / unassigned item"); + if (ret != end()) { + assert( x == *ret ); + m_PosIndex.erase( ret ); + x->m_ffList = nullptr; + return true; + } + return false; +} + +void SwFrameFormats::erase( size_type index_ ) +{ + erase( begin() + index_ ); +} + +void SwFrameFormats::erase( const_iterator const& position ) +{ + (*position)->m_ffList = nullptr; + m_PosIndex.erase( begin() + (position - begin()) ); +} + +bool SwFrameFormats::ContainsFormat(const SwFrameFormat& x) const +{ + return (x.m_ffList == this); +} + +bool SwFrameFormats::IsAlive(SwFrameFormat const*const p) const +{ + return find(const_cast(p)) != end(); +} + +bool SwFrameFormats::newDefault( const value_type& x ) +{ + std::pair res = m_PosIndex.push_front( x ); + if( ! res.second ) + newDefault( res.first ); + return res.second; +} + +void SwFrameFormats::newDefault( const_iterator const& position ) +{ + if (position == begin()) + return; + m_PosIndex.relocate( begin(), position ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docftn.cxx b/sw/source/core/doc/docftn.cxx new file mode 100644 index 000000000..90899b91a --- /dev/null +++ b/sw/source/core/doc/docftn.cxx @@ -0,0 +1,547 @@ +/* -*- 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 + +SwEndNoteInfo& SwEndNoteInfo::operator=(const SwEndNoteInfo& rInfo) +{ + m_pTextFormatColl = rInfo.m_pTextFormatColl; + m_pPageDesc = rInfo.m_pPageDesc; + m_pCharFormat = rInfo.m_pCharFormat; + m_pAnchorFormat = rInfo.m_pAnchorFormat; + m_aDepends.EndListeningAll(); + m_aDepends.StartListening(m_pTextFormatColl); + m_aDepends.StartListening(m_pPageDesc); + m_aDepends.StartListening(m_pCharFormat); + m_aDepends.StartListening(m_pAnchorFormat); + + m_aFormat = rInfo.m_aFormat; + m_nFootnoteOffset = rInfo.m_nFootnoteOffset; + m_bEndNote = rInfo.m_bEndNote; + m_sPrefix = rInfo.m_sPrefix; + m_sSuffix = rInfo.m_sSuffix; + return *this; +} + +bool SwEndNoteInfo::operator==( const SwEndNoteInfo& rInfo ) const +{ + return + m_pTextFormatColl == rInfo.m_pTextFormatColl && + m_pPageDesc == rInfo.m_pPageDesc && + m_pCharFormat == rInfo.m_pCharFormat && + m_pAnchorFormat == rInfo.m_pAnchorFormat && + m_aFormat.GetNumberingType() == rInfo.m_aFormat.GetNumberingType() && + m_nFootnoteOffset == rInfo.m_nFootnoteOffset && + m_bEndNote == rInfo.m_bEndNote && + m_sPrefix == rInfo.m_sPrefix && + m_sSuffix == rInfo.m_sSuffix; +} + +SwEndNoteInfo::SwEndNoteInfo(const SwEndNoteInfo& rInfo) : + SwClient(nullptr), + m_aDepends(*this), + m_pTextFormatColl(rInfo.m_pTextFormatColl), + m_pPageDesc(rInfo.m_pPageDesc), + m_pCharFormat(rInfo.m_pCharFormat), + m_pAnchorFormat(rInfo.m_pAnchorFormat), + m_sPrefix( rInfo.m_sPrefix ), + m_sSuffix( rInfo.m_sSuffix ), + m_bEndNote( true ), + m_aFormat( rInfo.m_aFormat ), + m_nFootnoteOffset( rInfo.m_nFootnoteOffset ) +{ + m_aDepends.StartListening(m_pTextFormatColl); + m_aDepends.StartListening(m_pPageDesc); + m_aDepends.StartListening(m_pCharFormat); + m_aDepends.StartListening(m_pAnchorFormat); +} + +SwEndNoteInfo::SwEndNoteInfo() : + SwClient(nullptr), + m_aDepends(*this), + m_pTextFormatColl(nullptr), + m_pPageDesc(nullptr), + m_pCharFormat(nullptr), + m_pAnchorFormat(nullptr), + m_bEndNote( true ), + m_nFootnoteOffset( 0 ) +{ + m_aFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); +} + +SwPageDesc* SwEndNoteInfo::GetPageDesc(SwDoc& rDoc) const +{ + if(!m_pPageDesc) + { + m_pPageDesc = rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool( static_cast( + m_bEndNote ? RES_POOLPAGE_ENDNOTE : RES_POOLPAGE_FOOTNOTE ) ); + m_aDepends.StartListening(m_pPageDesc); + } + return m_pPageDesc; +} + +bool SwEndNoteInfo::KnowsPageDesc() const +{ + return m_pPageDesc != nullptr; +} + +bool SwEndNoteInfo::DependsOn(const SwPageDesc* pDesc) const +{ + return m_pPageDesc == pDesc; +} + +void SwEndNoteInfo::ChgPageDesc(SwPageDesc* pDesc) +{ + m_aDepends.EndListening(m_pPageDesc); + m_pPageDesc = pDesc; + m_aDepends.StartListening(m_pPageDesc); +} + +void SwEndNoteInfo::SetFootnoteTextColl(SwTextFormatColl& rFormat) +{ + m_aDepends.EndListening(m_pTextFormatColl); + m_pTextFormatColl = &rFormat; + m_aDepends.StartListening(m_pTextFormatColl); +} + +SwCharFormat* SwEndNoteInfo::GetCharFormat(SwDoc& rDoc) const +{ + auto pCharFormatFromDoc = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( static_cast( + m_bEndNote ? RES_POOLCHR_ENDNOTE : RES_POOLCHR_FOOTNOTE ) ); + if (m_pCharFormat != pCharFormatFromDoc) + { + m_aDepends.EndListening(m_pCharFormat); + m_aDepends.StartListening(pCharFormatFromDoc); + m_pCharFormat = pCharFormatFromDoc; + } + return m_pCharFormat; +} + +namespace +{ + void lcl_ResetPoolIdForDocAndSync(const sal_uInt16 nId, SwCharFormat* pFormat, const SwEndNoteInfo& rInfo) + { + auto pDoc = pFormat->GetDoc(); + if(!pDoc) + return; + for(auto pDocFormat : *pDoc->GetCharFormats()) + { + if(pDocFormat == pFormat) + pDocFormat->SetPoolFormatId(nId); + else if(pDocFormat->GetPoolFormatId() == nId) + pDocFormat->SetPoolFormatId(0); + } + rInfo.GetCharFormat(*pDoc); + rInfo.GetAnchorCharFormat(*pDoc); + } +} + +void SwEndNoteInfo::SetCharFormat(SwCharFormat* pFormat) +{ + lcl_ResetPoolIdForDocAndSync( + static_cast(m_bEndNote + ? RES_POOLCHR_ENDNOTE + : RES_POOLCHR_FOOTNOTE), + pFormat, + *this); +} + +SwCharFormat* SwEndNoteInfo::GetAnchorCharFormat(SwDoc& rDoc) const +{ + auto pAnchorFormatFromDoc = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( static_cast( + m_bEndNote ? RES_POOLCHR_ENDNOTE_ANCHOR : RES_POOLCHR_FOOTNOTE_ANCHOR ) ); + if(m_pAnchorFormat != pAnchorFormatFromDoc) + { + m_aDepends.EndListening(m_pAnchorFormat); + m_aDepends.StartListening(pAnchorFormatFromDoc); + m_pAnchorFormat = pAnchorFormatFromDoc; + } + return m_pAnchorFormat; +} + +void SwEndNoteInfo::SetAnchorCharFormat(SwCharFormat* pFormat) +{ + lcl_ResetPoolIdForDocAndSync( + static_cast(m_bEndNote + ? RES_POOLCHR_ENDNOTE_ANCHOR + : RES_POOLCHR_FOOTNOTE_ANCHOR), + pFormat, + *this); +} + +SwCharFormat* SwEndNoteInfo::GetCurrentCharFormat(const bool bAnchor) const +{ + return bAnchor + ? m_pAnchorFormat + : m_pCharFormat; +} + +void SwEndNoteInfo::SwClientNotify( const SwModify& rModify, const SfxHint& rHint) +{ + if (auto pLegacyHint = dynamic_cast(&rHint)) + { + const sal_uInt16 nWhich = pLegacyHint->m_pOld ? pLegacyHint->m_pOld->Which() : pLegacyHint->m_pNew ? pLegacyHint->m_pNew->Which() : 0 ; + if (RES_ATTRSET_CHG == nWhich || RES_FMT_CHG == nWhich) + { + auto pFormat = GetCurrentCharFormat(m_pCharFormat == nullptr); + if (!pFormat || !m_aDepends.IsListeningTo(pFormat) || pFormat->IsFormatInDTOR()) + return; + SwDoc* pDoc = pFormat->GetDoc(); + SwFootnoteIdxs& rFootnoteIdxs = pDoc->GetFootnoteIdxs(); + for( size_t nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() == m_bEndNote ) + { + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + } + else + CheckRegistration( pLegacyHint->m_pOld ); + } + else if (auto pModifyChangedHint = dynamic_cast(&rHint)) + { + auto pNew = const_cast(pModifyChangedHint->m_pNew); + if(m_pAnchorFormat == &rModify) + m_pAnchorFormat = static_cast(pNew); + else if(m_pCharFormat == &rModify) + m_pCharFormat = static_cast(pNew); + else if(m_pPageDesc == &rModify) + m_pPageDesc = static_cast(pNew); + else if(m_pTextFormatColl == &rModify) + m_pTextFormatColl = static_cast(pNew); + } +} + +SwFootnoteInfo& SwFootnoteInfo::operator=(const SwFootnoteInfo& rInfo) +{ + SwEndNoteInfo::operator=(rInfo); + m_aQuoVadis = rInfo.m_aQuoVadis; + m_aErgoSum = rInfo.m_aErgoSum; + m_ePos = rInfo.m_ePos; + m_eNum = rInfo.m_eNum; + return *this; +} + +bool SwFootnoteInfo::operator==( const SwFootnoteInfo& rInfo ) const +{ + return m_ePos == rInfo.m_ePos && + m_eNum == rInfo.m_eNum && + SwEndNoteInfo::operator==(rInfo) && + m_aQuoVadis == rInfo.m_aQuoVadis && + m_aErgoSum == rInfo.m_aErgoSum; +} + +SwFootnoteInfo::SwFootnoteInfo(const SwFootnoteInfo& rInfo) : + SwEndNoteInfo( rInfo ), + m_aQuoVadis( rInfo.m_aQuoVadis ), + m_aErgoSum( rInfo.m_aErgoSum ), + m_ePos( rInfo.m_ePos ), + m_eNum( rInfo.m_eNum ) +{ + m_bEndNote = false; +} + +SwFootnoteInfo::SwFootnoteInfo() : + SwEndNoteInfo(), + m_ePos( FTNPOS_PAGE ), + m_eNum( FTNNUM_DOC ) +{ + m_aFormat.SetNumberingType(SVX_NUM_ARABIC); + m_bEndNote = false; +} + +void SwDoc::SetFootnoteInfo(const SwFootnoteInfo& rInfo) +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if( GetFootnoteInfo() == rInfo ) + return; + + const SwFootnoteInfo &rOld = GetFootnoteInfo(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique(rOld, this) ); + } + + bool bFootnotePos = rInfo.m_ePos != rOld.m_ePos; + bool bFootnoteDesc = rOld.m_ePos == FTNPOS_CHAPTER && + rInfo.GetPageDesc( *this ) != rOld.GetPageDesc( *this ); + bool bExtra = rInfo.m_aQuoVadis != rOld.m_aQuoVadis || + rInfo.m_aErgoSum != rOld.m_aErgoSum || + rInfo.m_aFormat.GetNumberingType() != rOld.m_aFormat.GetNumberingType() || + rInfo.GetPrefix() != rOld.GetPrefix() || + rInfo.GetSuffix() != rOld.GetSuffix(); + SwCharFormat *pOldChrFormat = rOld.GetCharFormat( *this ), + *pNewChrFormat = rInfo.GetCharFormat( *this ); + bool bFootnoteChrFormats = pOldChrFormat != pNewChrFormat; + + *mpFootnoteInfo = rInfo; + + if (pTmpRoot) + { + o3tl::sorted_vector aAllLayouts = GetAllLayouts(); + if ( bFootnotePos ) + for( auto aLayout : aAllLayouts ) + aLayout->AllRemoveFootnotes(); + else + { + for( auto aLayout : aAllLayouts ) + aLayout->UpdateFootnoteNums(); + if ( bFootnoteDesc ) + for( auto aLayout : aAllLayouts ) + aLayout->CheckFootnotePageDescs(false); + if ( bExtra ) + { + // For messages regarding ErgoSum etc. we save the extra code and use the + // available methods. + SwFootnoteIdxs& rFootnoteIdxs = GetFootnoteIdxs(); + for( size_t nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if ( !rFootnote.IsEndNote() ) + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + } + } + if( FTNNUM_PAGE != rInfo.m_eNum ) + GetFootnoteIdxs().UpdateAllFootnote(); + else if( bFootnoteChrFormats ) + { + SwFormatChg aOld( pOldChrFormat ); + SwFormatChg aNew( pNewChrFormat ); + mpFootnoteInfo->ModifyNotification( &aOld, &aNew ); + } + + // #i81002# no update during loading + if ( !IsInReading() ) + { + getIDocumentFieldsAccess().UpdateRefFields(); + } + getIDocumentState().SetModified(); + +} + +void SwDoc::SetEndNoteInfo(const SwEndNoteInfo& rInfo) +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if( GetEndNoteInfo() == rInfo ) + return; + + if(GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique( GetEndNoteInfo(), this ) ); + } + + bool bNumChg = rInfo.m_nFootnoteOffset != GetEndNoteInfo().m_nFootnoteOffset; + // this seems to be an optimization: UpdateAllFootnote() is only called + // if the offset changes; if the offset is the same, + // but type/prefix/suffix changes, just set new numbers. + bool const bExtra = !bNumChg && + ( (rInfo.m_aFormat.GetNumberingType() != + GetEndNoteInfo().m_aFormat.GetNumberingType()) + || (rInfo.GetPrefix() != GetEndNoteInfo().GetPrefix()) + || (rInfo.GetSuffix() != GetEndNoteInfo().GetSuffix()) + ); + bool bFootnoteDesc = rInfo.GetPageDesc( *this ) != + GetEndNoteInfo().GetPageDesc( *this ); + SwCharFormat *pOldChrFormat = GetEndNoteInfo().GetCharFormat( *this ), + *pNewChrFormat = rInfo.GetCharFormat( *this ); + bool bFootnoteChrFormats = pOldChrFormat != pNewChrFormat; + + *mpEndNoteInfo = rInfo; + + if ( pTmpRoot ) + { + if ( bFootnoteDesc ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->CheckFootnotePageDescs(true); + } + if ( bExtra ) + { + // For messages regarding ErgoSum etc. we save the extra code and use the + // available methods. + SwFootnoteIdxs& rFootnoteIdxs = GetFootnoteIdxs(); + for( size_t nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() ) + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + } + if( bNumChg ) + GetFootnoteIdxs().UpdateAllFootnote(); + else if( bFootnoteChrFormats ) + { + SwFormatChg aOld( pOldChrFormat ); + SwFormatChg aNew( pNewChrFormat ); + mpEndNoteInfo->ModifyNotification( &aOld, &aNew ); + } + + // #i81002# no update during loading + if ( !IsInReading() ) + { + getIDocumentFieldsAccess().UpdateRefFields(); + } + getIDocumentState().SetModified(); + +} + +bool SwDoc::SetCurFootnote( const SwPaM& rPam, const OUString& rNumStr, + bool bIsEndNote) +{ + SwFootnoteIdxs& rFootnoteArr = GetFootnoteIdxs(); + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + const sal_uLong nSttNd = pStt->nNode.GetIndex(); + const sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + const sal_uLong nEndNd = pEnd->nNode.GetIndex(); + const sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + size_t nPos = 0; + rFootnoteArr.SeekEntry( pStt->nNode, &nPos ); + + std::unique_ptr pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); // AppendUndo far below, so leave it + pUndo.reset(new SwUndoChangeFootNote( rPam, rNumStr, bIsEndNote )); + } + + bool bChg = false; + bool bTypeChgd = false; + const size_t nPosSave = nPos; + while( nPos < rFootnoteArr.size() ) + { + SwTextFootnote* pTextFootnote = rFootnoteArr[ nPos++ ]; + sal_uLong nIdx = SwTextFootnote_GetIndex(pTextFootnote); + if( nIdx >= nEndNd && + ( nIdx != nEndNd || nEndCnt < pTextFootnote->GetStart() ) ) + continue; + if( nIdx > nSttNd || ( nIdx == nSttNd && + nSttCnt <= pTextFootnote->GetStart() ) ) + { + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr() != rNumStr || + rFootnote.IsEndNote() != bIsEndNote ) + { + bChg = true; + if ( pUndo ) + { + pUndo->GetHistory().Add( *pTextFootnote ); + } + + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rNumStr); + if( rFootnote.IsEndNote() != bIsEndNote ) + { + const_cast(rFootnote).SetEndNote( bIsEndNote ); + bTypeChgd = true; + pTextFootnote->CheckCondColl(); + //#i11339# dispose UNO wrapper when a footnote is changed to an endnote or vice versa + const_cast(rFootnote).InvalidateFootnote(); + } + } + } + } + + nPos = nPosSave; // There are more in the front! + while( nPos ) + { + SwTextFootnote* pTextFootnote = rFootnoteArr[ --nPos ]; + sal_uLong nIdx = SwTextFootnote_GetIndex(pTextFootnote); + if( nIdx <= nSttNd && + ( nIdx != nSttNd || nSttCnt > pTextFootnote->GetStart() ) ) + continue; + if( nIdx < nEndNd || ( nIdx == nEndNd && + nEndCnt >= pTextFootnote->GetStart() ) ) + { + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr() != rNumStr || + rFootnote.IsEndNote() != bIsEndNote ) + { + bChg = true; + if ( pUndo ) + { + pUndo->GetHistory().Add( *pTextFootnote ); + } + + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rNumStr); + if( rFootnote.IsEndNote() != bIsEndNote ) + { + const_cast(rFootnote).SetEndNote( bIsEndNote ); + bTypeChgd = true; + pTextFootnote->CheckCondColl(); + } + } + } + } + + // Who needs to be triggered? + if( bChg ) + { + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + if ( bTypeChgd ) + rFootnoteArr.UpdateAllFootnote(); + if( FTNNUM_PAGE != GetFootnoteInfo().m_eNum ) + { + if ( !bTypeChgd ) + rFootnoteArr.UpdateAllFootnote(); + } + else if( pTmpRoot ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->UpdateFootnoteNums(); + } + getIDocumentState().SetModified(); + } + return bChg; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docglbl.cxx b/sw/source/core/doc/docglbl.cxx new file mode 100644 index 000000000..b933843ff --- /dev/null +++ b/sw/source/core/doc/docglbl.cxx @@ -0,0 +1,523 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +namespace { + +enum SwSplitDocType +{ + SPLITDOC_TO_GLOBALDOC, + SPLITDOC_TO_HTML +}; + +} + +bool SwDoc::GenerateGlobalDoc( const OUString& rPath, + const SwTextFormatColl* pSplitColl ) +{ + return SplitDoc( SPLITDOC_TO_GLOBALDOC, rPath, false, pSplitColl ); +} + +bool SwDoc::GenerateGlobalDoc( const OUString& rPath, int nOutlineLevel ) +{ + return SplitDoc( SPLITDOC_TO_GLOBALDOC, rPath, true, nullptr, nOutlineLevel ); +} + +bool SwDoc::GenerateHTMLDoc( const OUString& rPath, int nOutlineLevel ) +{ + return SplitDoc( SPLITDOC_TO_HTML, rPath, true, nullptr, nOutlineLevel ); +} + +bool SwDoc::GenerateHTMLDoc( const OUString& rPath, + const SwTextFormatColl* pSplitColl ) +{ + return SplitDoc( SPLITDOC_TO_HTML, rPath, false, pSplitColl ); +} + +// two helpers for outline mode +static SwNodePtr GetStartNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) +{ + for( ; *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + SwNodePtr pNd = (*pOutlNds)[ *nOutl ]; + if( pNd->GetTextNode()->GetAttrOutlineLevel() == nOutlineLevel && !pNd->FindTableNode() ) + { + return pNd; + } + } + + return nullptr; +} + +static SwNodePtr GetEndNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) +{ + SwNodePtr pNd; + + for( ++(*nOutl); (*nOutl) < pOutlNds->size(); ++(*nOutl) ) + { + pNd = (*pOutlNds)[ *nOutl ]; + + const int nLevel = pNd->GetTextNode()->GetAttrOutlineLevel(); + + if( ( 0 < nLevel && nLevel <= nOutlineLevel ) && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +// two helpers for collection mode +static SwNodePtr GetStartNode( const SwOutlineNodes* pOutlNds, const SwTextFormatColl* pSplitColl, SwOutlineNodes::size_type* nOutl ) +{ + for( ; *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + SwNodePtr pNd = (*pOutlNds)[ *nOutl ]; + if( pNd->GetTextNode()->GetTextColl() == pSplitColl && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +static SwNodePtr GetEndNode( const SwOutlineNodes* pOutlNds, const SwTextFormatColl* pSplitColl, SwOutlineNodes::size_type* nOutl ) +{ + SwNodePtr pNd; + + for( ++(*nOutl); *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + pNd = (*pOutlNds)[ *nOutl ]; + SwTextFormatColl* pTColl = pNd->GetTextNode()->GetTextColl(); + + if( ( pTColl == pSplitColl || + ( pSplitColl->GetAttrOutlineLevel() > 0 && + pTColl->GetAttrOutlineLevel() > 0 && + pTColl->GetAttrOutlineLevel() < + pSplitColl->GetAttrOutlineLevel() )) && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, const SwTextFormatColl* pSplitColl, int nOutlineLevel ) +{ + // Iterate over all the template's Nodes, creating an own + // document for every single one and replace linked sections (GlobalDoc) for links (HTML). + // Finally, we save this document as a GlobalDoc/HTMLDoc. + if( !mpDocShell || !mpDocShell->GetMedium() || + ( SPLITDOC_TO_GLOBALDOC == eDocType && GetDocumentSettingManager().get(DocumentSettingId::GLOBAL_DOCUMENT) ) ) + return false; + + SwOutlineNodes::size_type nOutl = 0; + SwOutlineNodes* pOutlNds = const_cast(&GetNodes().GetOutLineNds()); + std::unique_ptr xTmpOutlNds; + SwNodePtr pStartNd; + + if ( !bOutline) { + if( pSplitColl ) + { + // If it isn't an OutlineNumbering, then use an own array and collect the Nodes. + if( pSplitColl->GetAttrOutlineLevel() == 0 ) + { + xTmpOutlNds.reset(new SwOutlineNodes); + pOutlNds = xTmpOutlNds.get(); + SwIterator aIter( *pSplitColl ); + for( SwTextNode* pTNd = aIter.First(); pTNd; pTNd = aIter.Next() ) + if( pTNd->GetNodes().IsDocNodes() ) + pOutlNds->insert( pTNd ); + + if( pOutlNds->empty() ) + return false; + } + } + else + { + // Look for the 1st level OutlineTemplate + const SwTextFormatColls& rFormatColls =*GetTextFormatColls(); + for( SwTextFormatColls::size_type n = rFormatColls.size(); n; ) + if ( rFormatColls[ --n ]->GetAttrOutlineLevel() == 1 ) + { + pSplitColl = rFormatColls[ n ]; + break; + } + + if( !pSplitColl ) + return false; + } + } + + std::shared_ptr pFilter; + switch( eDocType ) + { + case SPLITDOC_TO_HTML: + pFilter = SwIoSystem::GetFilterOfFormat("HTML"); + break; + + default: + pFilter = SwIoSystem::GetFilterOfFormat(FILTER_XML); + eDocType = SPLITDOC_TO_GLOBALDOC; + break; + } + + if( !pFilter ) + return false; + + // Deactivate Undo/Redline in any case + GetIDocumentUndoRedo().DoUndo(false); + getIDocumentRedlineAccess().SetRedlineFlags_intern( getIDocumentRedlineAccess().GetRedlineFlags() & ~RedlineFlags::On ); + + OUString sExt = pFilter->GetSuffixes().getToken(0, ','); + if( sExt.isEmpty() ) + { + sExt = ".sxw"; + } + else + { + if( '.' != sExt[ 0 ] ) + { + sExt = "." + sExt; + } + } + + INetURLObject aEntry(rPath); + OUString sLeading(aEntry.GetBase()); + aEntry.removeSegment(); + OUString sPath = aEntry.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + utl::TempFile aTemp(sLeading, true, &sExt, &sPath); + aTemp.EnableKillingFile(); + + DateTime aTmplDate( DateTime::SYSTEM ); + { + tools::Time a2Min( 0 ); a2Min.SetMin( 2 ); + aTmplDate += a2Min; + } + + // Skip all invalid ones + while( nOutl < pOutlNds->size() && + (*pOutlNds)[ nOutl ]->GetIndex() < GetNodes().GetEndOfExtras().GetIndex() ) + ++nOutl; + + do { + if( bOutline ) + pStartNd = GetStartNode( pOutlNds, nOutlineLevel, &nOutl ); + else + pStartNd = GetStartNode( pOutlNds, pSplitColl, &nOutl ); + + if( pStartNd ) + { + SwNodePtr pEndNd; + if( bOutline ) + pEndNd = GetEndNode( pOutlNds, nOutlineLevel, &nOutl ); + else + pEndNd = GetEndNode( pOutlNds, pSplitColl, &nOutl ); + SwNodeIndex aEndIdx( pEndNd ? *pEndNd + : GetNodes().GetEndOfContent() ); + + // Write out the Nodes completely + OUString sFileName; + if( pStartNd->GetIndex() + 1 < aEndIdx.GetIndex() ) + { + SfxObjectShellLock xDocSh( new SwDocShell( SfxObjectCreateMode::INTERNAL )); + if( xDocSh->DoInitNew() ) + { + SwDoc* pDoc = static_cast(&xDocSh)->GetDoc(); + + uno::Reference xDPS( + static_cast(&xDocSh)->GetModel(), + uno::UNO_QUERY_THROW); + uno::Reference xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + // the GlobalDoc is the template + xDocProps->setTemplateName(OUString()); + ::util::DateTime uDT = aTmplDate.GetUNODateTime(); + xDocProps->setTemplateDate(uDT); + xDocProps->setTemplateURL(rPath); + // Set the new doc's title to the text of the "split para". + // If the current doc has a title, insert it at the begin. + OUString sTitle( xDocProps->getTitle() ); + if (!sTitle.isEmpty()) + sTitle += ": "; + sTitle += pStartNd->GetTextNode()->GetExpandText(nullptr); + xDocProps->setTitle( sTitle ); + + // Replace template + pDoc->ReplaceStyles( *this ); + + // Take over chapter numbering + if( mpOutlineRule ) + pDoc->SetOutlineNumRule( *mpOutlineRule ); + + SwNodeRange aRg( *pStartNd, 0, aEndIdx.GetNode() ); + SwNodeIndex aTmpIdx( pDoc->GetNodes().GetEndOfContent() ); + GetNodes().Copy_( aRg, aTmpIdx, false ); + + // Delete the initial TextNode + SwNodeIndex aIdx( pDoc->GetNodes().GetEndOfExtras(), 2 ); + if( aIdx.GetIndex() + 1 != + pDoc->GetNodes().GetEndOfContent().GetIndex() ) + pDoc->GetNodes().Delete( aIdx ); + + // All Flys in the section + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aIdx); + + // And what's with all the Bookmarks? + // ????? + + utl::TempFile aTempFile2(sLeading, true, &sExt, &sPath); + sFileName = aTempFile2.GetURL(); + SfxMedium* pTmpMed = new SfxMedium( sFileName, + StreamMode::STD_READWRITE ); + pTmpMed->SetFilter( pFilter ); + + // We need to have a Layout for the HTMLFilter, so that + // TextFrames/Controls/OLE objects can be exported correctly as graphics. + if( SPLITDOC_TO_HTML == eDocType && + !pDoc->GetSpzFrameFormats()->empty() ) + { + SfxViewFrame::LoadHiddenDocument( *xDocSh, SFX_INTERFACE_NONE ); + } + xDocSh->DoSaveAs( *pTmpMed ); + xDocSh->DoSaveCompleted( pTmpMed ); + + // do not insert a FileLinkSection in case of error + if( xDocSh->GetError() ) + sFileName.clear(); + } + xDocSh->DoClose(); + } + + // We can now insert the section + if( !sFileName.isEmpty() ) + { + switch( eDocType ) + { + case SPLITDOC_TO_HTML: + { + // Delete all nodes in the section and, in the "start node", + // set the Link to the saved document. + sal_uLong nNodeDiff = aEndIdx.GetIndex() - + pStartNd->GetIndex() - 1; + if( nNodeDiff ) + { + SwPaM aTmp( *pStartNd, aEndIdx.GetNode(), 1, -1 ); + aTmp.GetPoint()->nContent.Assign( nullptr, 0 ); + aTmp.GetMark()->nContent.Assign( nullptr, 0 ); + SwNodeIndex aSIdx( aTmp.GetMark()->nNode ); + SwNodeIndex aEIdx( aTmp.GetPoint()->nNode ); + + // Try to move past the end + if( !aTmp.Move( fnMoveForward, GoInNode ) ) + { + // well then, back to the beginning + aTmp.Exchange(); + if( !aTmp.Move( fnMoveBackward, GoInNode )) + { + OSL_FAIL( "no more Nodes!" ); + } + } + // Move Bookmarks and so forth + CorrAbs( aSIdx, aEIdx, *aTmp.GetPoint(), true); + + // If FlyFrames are still around, delete these too + for( SwFrameFormats::size_type n = 0; n < GetSpzFrameFormats()->size(); ++n ) + { + SwFrameFormat* pFly = (*GetSpzFrameFormats())[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = + pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aSIdx <= pAPos->nNode && + pAPos->nNode < aEIdx ) + { + getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + --n; + } + } + + GetNodes().Delete( aSIdx, nNodeDiff ); + } + + // set the link in the StartNode + SwFormatINetFormat aINet( sFileName , OUString() ); + SwTextNode* pTNd = pStartNd->GetTextNode(); + pTNd->InsertItem(aINet, 0, pTNd->GetText().getLength()); + + // If the link cannot be found anymore, + // it has to be a bug! + if( !pOutlNds->Seek_Entry( pStartNd, &nOutl )) + pStartNd = nullptr; + ++nOutl ; + } + break; + + default: + { + const OUString sNm(INetURLObject(sFileName).GetLastName()); + SwSectionData aSectData( SectionType::FileLink, + GetUniqueSectionName( &sNm )); + SwSectionFormat* pFormat = MakeSectionFormat(); + aSectData.SetLinkFileName(sFileName); + aSectData.SetProtectFlag(true); + + --aEndIdx; // in the InsertSection the end is inclusive + while( aEndIdx.GetNode().IsStartNode() ) + --aEndIdx; + + // If any Section ends or starts in the new sectionrange, + // they must end or start before or after the range! + SwSectionNode* pSectNd = pStartNd->FindSectionNode(); + while( pSectNd && pSectNd->EndOfSectionIndex() + <= aEndIdx.GetIndex() ) + { + const SwNode* pSectEnd = pSectNd->EndOfSectionNode(); + if( pSectNd->GetIndex() + 1 == + pStartNd->GetIndex() ) + { + bool bMvIdx = aEndIdx == *pSectEnd; + DelSectionFormat( pSectNd->GetSection().GetFormat() ); + if( bMvIdx ) + --aEndIdx; + } + else + { + SwNodeRange aRg( *pStartNd, *pSectEnd ); + SwNodeIndex aIdx( *pSectEnd, 1 ); + GetNodes().MoveNodes( aRg, GetNodes(), aIdx ); + } + pSectNd = pStartNd->FindSectionNode(); + } + + pSectNd = aEndIdx.GetNode().FindSectionNode(); + while( pSectNd && pSectNd->GetIndex() > + pStartNd->GetIndex() ) + { + // #i15712# don't attempt to split sections if + // they are fully enclosed in [pSectNd,aEndIdx]. + if( aEndIdx < pSectNd->EndOfSectionIndex() ) + { + SwNodeRange aRg( *pSectNd, 1, aEndIdx, 1 ); + SwNodeIndex aIdx( *pSectNd ); + GetNodes().MoveNodes( aRg, GetNodes(), aIdx ); + } + + pSectNd = pStartNd->FindSectionNode(); + } + + // -> #i26762# + // Ensure order of start and end of section is sane. + SwNodeIndex aStartIdx(*pStartNd); + + if (aEndIdx >= aStartIdx) + { + pSectNd = GetNodes().InsertTextSection(aStartIdx, + *pFormat, aSectData, nullptr, &aEndIdx, false); + } + else + { + pSectNd = GetNodes().InsertTextSection(aEndIdx, + *pFormat, aSectData, nullptr, &aStartIdx, false); + } + // <- #i26762# + + pSectNd->GetSection().CreateLink( LinkCreateType::Connect ); + } + break; + } + } + } + } while( pStartNd ); + + xTmpOutlNds.reset(); + + switch( eDocType ) + { + case SPLITDOC_TO_HTML: + if( GetDocumentSettingManager().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + { + // save all remaining sections + while( !GetSections().empty() ) + DelSectionFormat( GetSections().front() ); + + SfxFilterContainer* pFCntnr = mpDocShell->GetFactory().GetFilterContainer(); + pFilter = pFCntnr->GetFilter4EA( pFilter->GetTypeName(), SfxFilterFlags::EXPORT ); + } + break; + + default: + // save the Globaldoc + GetDocumentSettingManager().set(DocumentSettingId::GLOBAL_DOCUMENT, true); + GetDocumentSettingManager().set(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS, false); + } + + // The medium isn't locked after reopening the document. + SfxRequest aReq( SID_SAVEASDOC, SfxCallMode::SYNCHRON, GetAttrPool() ); + aReq.AppendItem( SfxStringItem( SID_FILE_NAME, rPath ) ); + aReq.AppendItem( SfxBoolItem( SID_SAVETO, true ) ); + if(pFilter) + aReq.AppendItem( SfxStringItem( SID_FILTER_NAME, pFilter->GetName() ) ); + const SfxBoolItem *pRet = static_cast(mpDocShell->ExecuteSlot( aReq )); + + return pRet && pRet->GetValue(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docglos.cxx b/sw/source/core/doc/docglos.cxx new file mode 100644 index 000000000..269d00ad8 --- /dev/null +++ b/sw/source/core/doc/docglos.cxx @@ -0,0 +1,212 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +void SwDoc::ReplaceUserDefinedDocumentProperties( + const uno::Reference& xSourceDocProps) +{ + OSL_ENSURE(xSourceDocProps.is(), "null reference"); + + uno::Reference xDPS( + GetDocShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xDocProps( + xDPS->getDocumentProperties() ); + OSL_ENSURE(xDocProps.is(), "null reference"); + + uno::Reference xSourceUDSet( + xSourceDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW); + uno::Reference xTargetUD( + xDocProps->getUserDefinedProperties()); + uno::Reference xTargetUDSet(xTargetUD, + uno::UNO_QUERY_THROW); + const uno::Sequence tgtprops + = xTargetUDSet->getPropertySetInfo()->getProperties(); + + for (const auto& rTgtProp : tgtprops) { + try { + xTargetUD->removeProperty(rTgtProp.Name); + } catch (uno::Exception &) { + // ignore + } + } + + uno::Reference xSetInfo + = xSourceUDSet->getPropertySetInfo(); + const uno::Sequence srcprops = xSetInfo->getProperties(); + + for (const auto& rSrcProp : srcprops) { + try { + OUString name = rSrcProp.Name; + xTargetUD->addProperty(name, rSrcProp.Attributes, + xSourceUDSet->getPropertyValue(name)); + } catch (uno::Exception &) { + // ignore + } + } +} + +void SwDoc::ReplaceDocumentProperties(const SwDoc& rSource, bool mailMerge) +{ + uno::Reference xSourceDPS( + rSource.GetDocShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xSourceDocProps( + xSourceDPS->getDocumentProperties() ); + OSL_ENSURE(xSourceDocProps.is(), "null reference"); + + uno::Reference xDPS( + GetDocShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xDocProps( + xDPS->getDocumentProperties() ); + OSL_ENSURE(xDocProps.is(), "null reference"); + + xDocProps->setAuthor(xSourceDocProps->getAuthor()); + xDocProps->setGenerator(xSourceDocProps->getGenerator()); + xDocProps->setCreationDate(xSourceDocProps->getCreationDate()); + xDocProps->setTitle(xSourceDocProps->getTitle()); + xDocProps->setSubject(xSourceDocProps->getSubject()); + xDocProps->setDescription(xSourceDocProps->getDescription()); + xDocProps->setKeywords(xSourceDocProps->getKeywords()); + xDocProps->setLanguage(xSourceDocProps->getLanguage()); + // Note: These below originally weren't copied for mailmerge, but I don't see why not. + xDocProps->setModifiedBy(xSourceDocProps->getModifiedBy()); + xDocProps->setModificationDate(xSourceDocProps->getModificationDate()); + xDocProps->setPrintedBy(xSourceDocProps->getPrintedBy()); + xDocProps->setPrintDate(xSourceDocProps->getPrintDate()); + xDocProps->setTemplateName(xSourceDocProps->getTemplateName()); + xDocProps->setTemplateURL(xSourceDocProps->getTemplateURL()); + xDocProps->setTemplateDate(xSourceDocProps->getTemplateDate()); + xDocProps->setAutoloadURL(xSourceDocProps->getAutoloadURL()); + xDocProps->setAutoloadSecs(xSourceDocProps->getAutoloadSecs()); + xDocProps->setDefaultTarget(xSourceDocProps->getDefaultTarget()); + xDocProps->setDocumentStatistics(xSourceDocProps->getDocumentStatistics()); + xDocProps->setEditingCycles(xSourceDocProps->getEditingCycles()); + xDocProps->setEditingDuration(xSourceDocProps->getEditingDuration()); + + if( mailMerge ) // Note: Not sure this is needed. + { + // Manually set the creation date, otherwise author field isn't filled + // during MM, as it's set when saving the document the first time. + xDocProps->setCreationDate( xSourceDocProps->getModificationDate() ); + } + + ReplaceUserDefinedDocumentProperties( xSourceDocProps ); +} + +/// inserts an AutoText block +bool SwDoc::InsertGlossary( SwTextBlocks& rBlock, const OUString& rEntry, + SwPaM& rPaM, SwCursorShell* pShell ) +{ + bool bRet = false; + const sal_uInt16 nIdx = rBlock.GetIndex( rEntry ); + if( USHRT_MAX != nIdx ) + { + bool bSav_IsInsGlossary = mbInsOnlyTextGlssry; + mbInsOnlyTextGlssry = rBlock.IsOnlyTextBlock( nIdx ); + + if( rBlock.BeginGetDoc( nIdx ) ) + { + SwDoc* pGDoc = rBlock.GetDoc(); + + // Update all fixed fields, with the right DocInfo. + // FIXME: UGLY: Because we cannot limit the range in which to do + // field updates, we must update the fixed fields at the glossary + // entry document. + // To be able to do this, we copy the document properties of the + // target document to the glossary document + // OSL_ENSURE(GetDocShell(), "no SwDocShell"); // may be clipboard! + OSL_ENSURE(pGDoc->GetDocShell(), "no SwDocShell at glossary"); + if (GetDocShell() && pGDoc->GetDocShell()) + pGDoc->ReplaceDocumentProperties( *this ); + pGDoc->getIDocumentFieldsAccess().SetFixFields(nullptr); + + // StartAllAction(); + getIDocumentFieldsAccess().LockExpFields(); + + SwNodeIndex aStt( pGDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pGDoc->GetNodes().GoNext( &aStt ); + const SwTableNode* pTableNd = pContentNd->FindTableNode(); + SwPaM aCpyPam( pTableNd ? *const_cast(static_cast(pTableNd)) : *static_cast(pContentNd) ); + aCpyPam.SetMark(); + + // till the nodes array's end + aCpyPam.GetPoint()->nNode = pGDoc->GetNodes().GetEndOfContent().GetIndex()-1; + pContentNd = aCpyPam.GetContentNode(); + aCpyPam.GetPoint()->nContent.Assign( + pContentNd, pContentNd ? pContentNd->Len() : 0 ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSGLOSSARY, nullptr ); + SwPaM *_pStartCursor = &rPaM, *_pStartCursor2 = _pStartCursor; + do { + + SwPosition& rInsPos = *_pStartCursor->GetPoint(); + SwStartNode* pBoxSttNd = const_cast(rInsPos.nNode.GetNode(). + FindTableBoxStartNode()); + + if( pBoxSttNd && 2 == pBoxSttNd->EndOfSectionIndex() - + pBoxSttNd->GetIndex() && + aCpyPam.GetPoint()->nNode != aCpyPam.GetMark()->nNode ) + { + // We copy more than one Node to the current Box. + // However, we have to remove the BoxAttributes then. + ClearBoxNumAttrs( rInsPos.nNode ); + } + + SwDontExpandItem aACD; + aACD.SaveDontExpandItems( rInsPos ); + + pGDoc->getIDocumentContentOperations().CopyRange(aCpyPam, rInsPos, SwCopyFlags::CheckPosInFly); + + aACD.RestoreDontExpandItems( rInsPos ); + if( pShell ) + pShell->SaveTableBoxContent( &rInsPos ); + } while( (_pStartCursor = _pStartCursor->GetNext()) != + _pStartCursor2 ); + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSGLOSSARY, nullptr ); + + getIDocumentFieldsAccess().UnlockExpFields(); + if( !getIDocumentFieldsAccess().IsExpFieldsLocked() ) + getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + bRet = true; + } + mbInsOnlyTextGlssry = bSav_IsInsGlossary; + } + rBlock.EndGetDoc(); + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doclay.cxx b/sw/source/core/doc/doclay.cxx new file mode 100644 index 000000000..ce33b8c21 --- /dev/null +++ b/sw/source/core/doc/doclay.cxx @@ -0,0 +1,1682 @@ +/* -*- 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; + +#define DEF_FLY_WIDTH 2268 // Default width for FlyFrames (2268 == 4cm) + +static bool lcl_IsItemSet(const SwContentNode & rNode, sal_uInt16 which) +{ + bool bResult = false; + + if (SfxItemState::SET == rNode.GetSwAttrSet().GetItemState(which)) + bResult = true; + + return bResult; +} + +SdrObject* SwDoc::CloneSdrObj( const SdrObject& rObj, bool bMoveWithinDoc, + bool bInsInPage ) +{ + // #i52858# - method name changed + SdrPage *pPg = getIDocumentDrawModelAccess().GetOrCreateDrawModel()->GetPage( 0 ); + if( !pPg ) + { + pPg = getIDocumentDrawModelAccess().GetDrawModel()->AllocPage( false ); + getIDocumentDrawModelAccess().GetDrawModel()->InsertPage( pPg ); + } + + // TTTT Clone directly to target SdrModel + SdrObject *pObj(rObj.CloneSdrObject(*getIDocumentDrawModelAccess().GetDrawModel())); + + if( bMoveWithinDoc && SdrInventor::FmForm == pObj->GetObjInventor() ) + { + // We need to preserve the Name for Controls + uno::Reference< awt::XControlModel > xModel = static_cast(pObj)->GetUnoControlModel(); + uno::Any aVal; + uno::Reference< beans::XPropertySet > xSet(xModel, uno::UNO_QUERY); + const OUString sName("Name"); + if( xSet.is() ) + aVal = xSet->getPropertyValue( sName ); + if( bInsInPage ) + pPg->InsertObjectThenMakeNameUnique( pObj ); + if( xSet.is() ) + xSet->setPropertyValue( sName, aVal ); + } + else if( bInsInPage ) + pPg->InsertObjectThenMakeNameUnique( pObj ); + + // For drawing objects: set layer of cloned object to invisible layer + SdrLayerID nLayerIdForClone = rObj.GetLayer(); + if ( dynamic_cast( pObj) == nullptr && + dynamic_cast( pObj) == nullptr && + typeid(SdrObject) != typeid(pObj) ) + { + if ( getIDocumentDrawModelAccess().IsVisibleLayerId( nLayerIdForClone ) ) + { + nLayerIdForClone = getIDocumentDrawModelAccess().GetInvisibleLayerIdByVisibleOne( nLayerIdForClone ); + } + } + pObj->SetLayer( nLayerIdForClone ); + + return pObj; +} + +SwFlyFrameFormat* SwDoc::MakeFlySection_( const SwPosition& rAnchPos, + const SwContentNode& rNode, + RndStdIds eRequestId, + const SfxItemSet* pFlySet, + SwFrameFormat* pFrameFormat ) +{ + if( !pFrameFormat ) + pFrameFormat = getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ); + + OUString sName; + if( !mbInReading ) + switch( rNode.GetNodeType() ) + { + case SwNodeType::Grf: sName = GetUniqueGrfName(); break; + case SwNodeType::Ole: sName = GetUniqueOLEName(); break; + default: sName = GetUniqueFrameName(); break; + } + SwFlyFrameFormat* pFormat = MakeFlyFrameFormat( sName, pFrameFormat ); + + // Create content and connect to the format. + // Create ContentNode and put it into the autotext selection. + SwNodeRange aRange( GetNodes().GetEndOfAutotext(), -1, + GetNodes().GetEndOfAutotext() ); + GetNodes().SectionDown( &aRange, SwFlyStartNode ); + + pFormat->SetFormatAttr( SwFormatContent( rNode.StartOfSectionNode() )); + + const SwFormatAnchor* pAnchor = nullptr; + if( pFlySet ) + { + pFlySet->GetItemState( RES_ANCHOR, false, + reinterpret_cast(&pAnchor) ); + if( SfxItemState::SET == pFlySet->GetItemState( RES_CNTNT, false )) + { + SfxItemSet aTmpSet( *pFlySet ); + aTmpSet.ClearItem( RES_CNTNT ); + pFormat->SetFormatAttr( aTmpSet ); + } + else + pFormat->SetFormatAttr( *pFlySet ); + } + + // Anchor not yet set? + RndStdIds eAnchorId; + // #i107811# Assure that at-page anchored fly frames have a page num or a + // content anchor set. + if ( !pAnchor || + ( RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId() && + !pAnchor->GetContentAnchor() ) || + ( RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId() && + !pAnchor->GetContentAnchor() && + pAnchor->GetPageNum() == 0 ) ) + { + // set it again, needed for Undo + SwFormatAnchor aAnch( pFormat->GetAnchor() ); + if (pAnchor && (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())) + { + SwPosition aPos( *rAnchPos.nNode.GetNode().FindFlyStartNode() ); + aAnch.SetAnchor( &aPos ); + eAnchorId = RndStdIds::FLY_AT_FLY; + } + else + { + if( eRequestId != aAnch.GetAnchorId() && + SfxItemState::SET != pFormat->GetItemState( RES_ANCHOR ) ) + { + aAnch.SetType( eRequestId ); + } + + eAnchorId = aAnch.GetAnchorId(); + if ( RndStdIds::FLY_AT_PAGE != eAnchorId || !pAnchor || aAnch.GetPageNum() == 0) + { + aAnch.SetAnchor( &rAnchPos ); + } + } + pFormat->SetFormatAttr( aAnch ); + } + else + eAnchorId = pFormat->GetAnchor().GetAnchorId(); + + if ( RndStdIds::FLY_AS_CHAR == eAnchorId ) + { + const sal_Int32 nStt = rAnchPos.nContent.GetIndex(); + SwTextNode * pTextNode = rAnchPos.nNode.GetNode().GetTextNode(); + + OSL_ENSURE(pTextNode!= nullptr, "There should be a SwTextNode!"); + + if (pTextNode != nullptr) + { + SwFormatFlyCnt aFormat( pFormat ); + // may fail if there's no space left or header/ftr + if (!pTextNode->InsertItem(aFormat, nStt, nStt)) + { // pFormat is dead now + return nullptr; + } + } + } + + if( SfxItemState::SET != pFormat->GetAttrSet().GetItemState( RES_FRM_SIZE )) + { + SwFormatFrameSize aFormatSize( SwFrameSize::Variable, 0, DEF_FLY_WIDTH ); + const SwNoTextNode* pNoTextNode = rNode.GetNoTextNode(); + if( pNoTextNode ) + { + // Set size + Size aSize( pNoTextNode->GetTwipSize() ); + if( MINFLY > aSize.Width() ) + aSize.setWidth( DEF_FLY_WIDTH ); + aFormatSize.SetWidth( aSize.Width() ); + if( aSize.Height() ) + { + aFormatSize.SetHeight( aSize.Height() ); + aFormatSize.SetHeightSizeType( SwFrameSize::Fixed ); + } + } + pFormat->SetFormatAttr( aFormatSize ); + } + + // Set up frames + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + pFormat->MakeFrames(); // ??? + + if (GetIDocumentUndoRedo().DoesUndo()) + { + sal_uLong nNodeIdx = rAnchPos.nNode.GetIndex(); + const sal_Int32 nCntIdx = rAnchPos.nContent.GetIndex(); + GetIDocumentUndoRedo().AppendUndo( + std::make_unique( pFormat, nNodeIdx, nCntIdx )); + } + + getIDocumentState().SetModified(); + return pFormat; +} + +SwFlyFrameFormat* SwDoc::MakeFlySection( RndStdIds eAnchorType, + const SwPosition* pAnchorPos, + const SfxItemSet* pFlySet, + SwFrameFormat* pFrameFormat, bool bCalledFromShell ) +{ + SwFlyFrameFormat* pFormat = nullptr; + if ( !pAnchorPos && (RndStdIds::FLY_AT_PAGE != eAnchorType) ) + { + const SwFormatAnchor* pAnch; + if( (pFlySet && SfxItemState::SET == pFlySet->GetItemState( + RES_ANCHOR, false, reinterpret_cast(&pAnch) )) || + ( pFrameFormat && SfxItemState::SET == pFrameFormat->GetItemState( + RES_ANCHOR, true, reinterpret_cast(&pAnch) )) ) + { + if ( RndStdIds::FLY_AT_PAGE != pAnch->GetAnchorId() ) + { + pAnchorPos = pAnch->GetContentAnchor(); + } + } + } + + if (pAnchorPos) + { + if( !pFrameFormat ) + pFrameFormat = getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ); + + sal_uInt16 nCollId = static_cast( + GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ? RES_POOLCOLL_TEXT : RES_POOLCOLL_FRAME ); + + /* If there is no adjust item in the paragraph style for the content node of the new fly section + propagate an existing adjust item at the anchor to the new content node. */ + SwContentNode * pNewTextNd = GetNodes().MakeTextNode + (SwNodeIndex( GetNodes().GetEndOfAutotext()), + getIDocumentStylePoolAccess().GetTextCollFromPool( nCollId )); + SwContentNode * pAnchorNode = pAnchorPos->nNode.GetNode().GetContentNode(); + // pAnchorNode from cursor must be valid, unless a whole table is selected (in which + // case the node is not a content node, and pAnchorNode is nullptr). In the latter case, + // bCalledFromShell is false. + assert(!bCalledFromShell || pAnchorNode); + + const SfxPoolItem * pItem = nullptr; + + if (bCalledFromShell && !lcl_IsItemSet(*pNewTextNd, RES_PARATR_ADJUST) && + SfxItemState::SET == pAnchorNode->GetSwAttrSet(). + GetItemState(RES_PARATR_ADJUST, true, &pItem)) + { + pNewTextNd->SetAttr(*pItem); + } + + pFormat = MakeFlySection_( *pAnchorPos, *pNewTextNd, + eAnchorType, pFlySet, pFrameFormat ); + } + return pFormat; +} + +SwFlyFrameFormat* SwDoc::MakeFlyAndMove( const SwPaM& rPam, const SfxItemSet& rSet, + const SwSelBoxes* pSelBoxes, + SwFrameFormat *pParent ) +{ + const SwFormatAnchor& rAnch = rSet.Get( RES_ANCHOR ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSLAYFMT, nullptr ); + + SwFlyFrameFormat* pFormat = MakeFlySection( rAnch.GetAnchorId(), rPam.GetPoint(), + &rSet, pParent ); + + // If content is selected, it becomes the new frame's content. + // Namely, it is moved into the NodeArray's appropriate section. + + if( pFormat ) + { + do { // middle check loop + const SwFormatContent &rContent = pFormat->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "No content prepared." ); + SwNodeIndex aIndex( *(rContent.GetContentIdx()), 1 ); + SwContentNode *pNode = aIndex.GetNode().GetContentNode(); + + // Attention: Do not create an index on the stack, or we + // cannot delete ContentNode in the end! + SwPosition aPos( aIndex ); + aPos.nContent.Assign( pNode, 0 ); + + if( pSelBoxes && !pSelBoxes->empty() ) + { + // Table selection + // Copy parts of a table: create a table with the same width as the + // original one and move (copy and delete) the selected boxes. + // The size is corrected on a percentage basis. + + SwTableNode* pTableNd = const_cast((*pSelBoxes)[0]-> + GetSttNd()->FindTableNode()); + if( !pTableNd ) + break; + + SwTable& rTable = pTableNd->GetTable(); + + // Did we select the whole table? + if( pSelBoxes->size() == rTable.GetTabSortBoxes().size() ) + { + // move the whole table + SwNodeRange aRg( *pTableNd, 0, *pTableNd->EndOfSectionNode(), 1 ); + + // If we move the whole table and it is located within a + // FlyFrame, the we create a TextNode after it. + // So that this FlyFrame is preserved. + if( aRg.aEnd.GetNode().IsEndNode() ) + GetNodes().MakeTextNode( aRg.aStart, + GetDfltTextFormatColl() ); + + getIDocumentContentOperations().MoveNodeRange( aRg, aPos.nNode, SwMoveFlags::DEFAULT ); + } + else + { + rTable.MakeCopy( this, aPos, *pSelBoxes ); + // Don't delete a part of a table with row span!! + // You could delete the content instead -> ToDo + //rTable.DeleteSel( this, *pSelBoxes, 0, 0, true, true ); + } + + // If the table is within the frame, then copy without the following TextNode + aIndex = rContent.GetContentIdx()->GetNode().EndOfSectionIndex() - 1; + OSL_ENSURE( aIndex.GetNode().GetTextNode(), + "a TextNode should be here" ); + aPos.nContent.Assign( nullptr, 0 ); // Deregister index! + GetNodes().Delete( aIndex ); + + // This is a hack: whilst FlyFrames/Headers/Footers are not undoable we delete all Undo objects + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + } + else + { + // copy all Pams and then delete all + bool bOldFlag = mbCopyIsMove; + bool const bOldUndo = GetIDocumentUndoRedo().DoesUndo(); + bool const bOldRedlineMove(getIDocumentRedlineAccess().IsRedlineMove()); + mbCopyIsMove = true; + GetIDocumentUndoRedo().DoUndo(false); + getIDocumentRedlineAccess().SetRedlineMove(true); + for(const SwPaM& rTmp : rPam.GetRingContainer()) + { + if( rTmp.HasMark() && + *rTmp.GetPoint() != *rTmp.GetMark() ) + { + // aPos is the newly created fly section, so definitely outside rPam, it's pointless to check that again. + getIDocumentContentOperations().CopyRange(*const_cast(&rTmp), aPos, SwCopyFlags::IsMoveToFly); + } + } + getIDocumentRedlineAccess().SetRedlineMove(bOldRedlineMove); + mbCopyIsMove = bOldFlag; + GetIDocumentUndoRedo().DoUndo(bOldUndo); + + for(const SwPaM& rTmp : rPam.GetRingContainer()) + { + if( rTmp.HasMark() && + *rTmp.GetPoint() != *rTmp.GetMark() ) + { + getIDocumentContentOperations().DeleteAndJoin( *const_cast(&rTmp) ); + } + } + } + } while( false ); + } + + getIDocumentState().SetModified(); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSLAYFMT, nullptr ); + + return pFormat; +} + + +/* + * paragraph frames - o.k. if the PaM includes the paragraph from the beginning + * to the beginning of the next paragraph at least + * frames at character - o.k. if the PaM starts at least at the same position + * as the frame + */ +static bool lcl_TstFlyRange( const SwPaM* pPam, const SwPosition* pFlyPos, + RndStdIds nAnchorId ) +{ + bool bOk = false; + const SwPaM* pTmp = pPam; + do { + const sal_uInt32 nFlyIndex = pFlyPos->nNode.GetIndex(); + const SwPosition* pPaMStart = pTmp->Start(); + const SwPosition* pPaMEnd = pTmp->End(); + const sal_uInt32 nPamStartIndex = pPaMStart->nNode.GetIndex(); + const sal_uInt32 nPamEndIndex = pPaMEnd->nNode.GetIndex(); + if (RndStdIds::FLY_AT_PARA == nAnchorId) + bOk = (nPamStartIndex < nFlyIndex && nPamEndIndex > nFlyIndex) || + (((nPamStartIndex == nFlyIndex) && (pPaMStart->nContent.GetIndex() == 0)) && + (nPamEndIndex > nFlyIndex)); + else + { + const sal_Int32 nFlyContentIndex = pFlyPos->nContent.GetIndex(); + const sal_Int32 nPamEndContentIndex = pPaMEnd->nContent.GetIndex(); + bOk = (nPamStartIndex < nFlyIndex && + (( nPamEndIndex > nFlyIndex )|| + ((nPamEndIndex == nFlyIndex) && + (nPamEndContentIndex > nFlyContentIndex))) ) + || + (((nPamStartIndex == nFlyIndex) && + (pPaMStart->nContent.GetIndex() <= nFlyContentIndex)) && + ((nPamEndIndex > nFlyIndex) || + (nPamEndContentIndex > nFlyContentIndex ))); + } + + if( bOk ) + break; + pTmp = pTmp->GetNext(); + } while( pPam != pTmp ); + return bOk; +} + +SwPosFlyFrames SwDoc::GetAllFlyFormats( const SwPaM* pCmpRange, bool bDrawAlso, + bool bAsCharAlso ) const +{ + SwPosFlyFrames aRetval; + + // collect all anchored somehow to paragraphs + for( auto pFly : *GetSpzFrameFormats() ) + { + bool bDrawFormat = bDrawAlso && RES_DRAWFRMFMT == pFly->Which(); + bool bFlyFormat = RES_FLYFRMFMT == pFly->Which(); + if( bFlyFormat || bDrawFormat ) + { + const SwFormatAnchor& rAnchor = pFly->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) && bAsCharAlso))) + { + if( pCmpRange && + !lcl_TstFlyRange( pCmpRange, pAPos, rAnchor.GetAnchorId() )) + continue; // not a valid FlyFrame + aRetval.insert(std::make_shared(pAPos->nNode, pFly, aRetval.size())); + } + } + } + + // If we don't have a layout we can't get page anchored FlyFrames. + // Also, page anchored FlyFrames are only returned if no range is specified. + if( !getIDocumentLayoutAccess().GetCurrentViewShell() || pCmpRange ) + { + return aRetval; + } + + const SwPageFrame *pPage = static_cast(getIDocumentLayoutAccess().GetCurrentLayout()->GetLower()); + while( pPage ) + { + if( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for(SwAnchoredObject* pAnchoredObj : rObjs) + { + SwFrameFormat *pFly; + if ( dynamic_cast( pAnchoredObj) != nullptr ) + pFly = &(pAnchoredObj->GetFrameFormat()); + else if ( bDrawAlso ) + pFly = &(pAnchoredObj->GetFrameFormat()); + else + continue; + + const SwFormatAnchor& rAnchor = pFly->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) && + (RndStdIds::FLY_AT_FLY != rAnchor.GetAnchorId()) && + (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId())) + { + const SwContentFrame * pContentFrame = pPage->FindFirstBodyContent(); + if ( !pContentFrame ) + { + // Oops! An empty page. + // In order not to lose the whole frame (RTF) we + // look for the last Content before the page. + const SwPageFrame *pPrv = static_cast(pPage->GetPrev()); + while ( !pContentFrame && pPrv ) + { + pContentFrame = pPrv->FindFirstBodyContent(); + pPrv = static_cast(pPrv->GetPrev()); + } + } + if ( pContentFrame ) + { + SwNodeIndex aIdx( pContentFrame->IsTextFrame() + ? *static_cast(pContentFrame)->GetTextNodeFirst() + : *static_cast(pContentFrame)->GetNode() ); + aRetval.insert(std::make_shared(aIdx, pFly, aRetval.size())); + } + } + } + } + pPage = static_cast(pPage->GetNext()); + } + + return aRetval; +} + +/* #i6447# changed behaviour if lcl_CpyAttr: + + If the old item set contains the item to set (no inheritance) copy the item + into the new set. + + If the old item set contains the item by inheritance and the new set + contains the item, too: + If the two items differ copy the item from the old set to the new set. + + Otherwise the new set will not be changed. +*/ +static void lcl_CpyAttr( SfxItemSet &rNewSet, const SfxItemSet &rOldSet, sal_uInt16 nWhich ) +{ + const SfxPoolItem *pOldItem = nullptr; + + rOldSet.GetItemState( nWhich, false, &pOldItem); + if (pOldItem != nullptr) + rNewSet.Put( *pOldItem ); + else + { + pOldItem = rOldSet.GetItem( nWhich ); + if (pOldItem != nullptr) + { + const SfxPoolItem *pNewItem = rNewSet.GetItem( nWhich ); + if (pNewItem != nullptr) + { + if (*pOldItem != *pNewItem) + rNewSet.Put( *pOldItem ); + } + else { + OSL_FAIL("What am I doing here?"); + } + } + else { + OSL_FAIL("What am I doing here?"); + } + } + +} + +static SwFlyFrameFormat * +lcl_InsertLabel(SwDoc & rDoc, SwTextFormatColls *const pTextFormatCollTable, + SwUndoInsertLabel *const pUndo, + SwLabelType const eType, OUString const& rText, OUString const& rSeparator, + const OUString& rNumberingSeparator, + const bool bBefore, const sal_uInt16 nId, const sal_uLong nNdIdx, + const OUString& rCharacterStyle, + const bool bCpyBrd ) +{ + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + bool bTable = false; // To save some code. + + // Get the field first, because we retrieve the TextColl via the field's name + OSL_ENSURE( nId == USHRT_MAX || nId < rDoc.getIDocumentFieldsAccess().GetFieldTypes()->size(), + "FieldType index out of bounds." ); + SwFieldType *pType = (nId != USHRT_MAX) ? (*rDoc.getIDocumentFieldsAccess().GetFieldTypes())[nId].get() : nullptr; + OSL_ENSURE(!pType || pType->Which() == SwFieldIds::SetExp, "wrong Id for Label"); + + SwTextFormatColl * pColl = nullptr; + if( pType ) + { + for( auto i = pTextFormatCollTable->size(); i; ) + { + if( (*pTextFormatCollTable)[ --i ]->GetName()==pType->GetName() ) + { + pColl = (*pTextFormatCollTable)[i]; + break; + } + } + OSL_ENSURE( pColl, "no text collection found" ); + } + + if( !pColl ) + { + pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_LABEL ); + } + + SwTextNode *pNew = nullptr; + SwFlyFrameFormat* pNewFormat = nullptr; + + switch ( eType ) + { + case SwLabelType::Table: + bTable = true; + [[fallthrough]]; + case SwLabelType::Fly: + // At the FlySection's Beginning/End insert the corresponding Node with its Field. + // The Frame is created automatically. + { + SwStartNode *pSttNd = rDoc.GetNodes()[nNdIdx]->GetStartNode(); + OSL_ENSURE( pSttNd, "No StartNode in InsertLabel." ); + sal_uLong nNode; + if( bBefore ) + { + nNode = pSttNd->GetIndex(); + if( !bTable ) + ++nNode; + } + else + { + nNode = pSttNd->EndOfSectionIndex(); + if( bTable ) + ++nNode; + } + + if( pUndo ) + pUndo->SetNodePos( nNode ); + + // Create Node for labeling paragraph. + SwNodeIndex aIdx( rDoc.GetNodes(), nNode ); + pNew = rDoc.GetNodes().MakeTextNode( aIdx, pColl ); + } + break; + + case SwLabelType::Object: + { + // Destroy Frame, + // insert new Frame, + // insert the corresponding Node with Field into the new Frame, + // insert the old Frame with the Object (Picture/OLE) paragraph-bound into the new Frame, + // create Frames. + + // Get the FlyFrame's Format and decouple the Layout. + SwFrameFormat *pOldFormat = rDoc.GetNodes()[nNdIdx]->GetFlyFormat(); + OSL_ENSURE( pOldFormat, "Couldn't find the Fly's Format." ); + // #i115719# + // and <description> attributes are lost when calling <DelFrames()>. + // Thus, keep them and restore them after the calling <MakeFrames()> + const bool bIsSwFlyFrameFormatInstance( dynamic_cast<SwFlyFrameFormat*>(pOldFormat) != nullptr ); + const OUString sTitle( bIsSwFlyFrameFormatInstance + ? static_cast<SwFlyFrameFormat*>(pOldFormat)->GetObjTitle() + : OUString() ); + const OUString sDescription( bIsSwFlyFrameFormatInstance + ? static_cast<SwFlyFrameFormat*>(pOldFormat)->GetObjDescription() + : OUString() ); + pOldFormat->DelFrames(); + + pNewFormat = rDoc.MakeFlyFrameFormat( rDoc.GetUniqueFrameName(), + rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool(RES_POOLFRM_FRAME) ); + + /* #i6447#: Only the selected items are copied from the old + format. */ + std::unique_ptr<SfxItemSet> pNewSet = pNewFormat->GetAttrSet().Clone(); + + // Copy only the set attributes. + // The others should apply from the Templates. + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_PRINT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_OPAQUE ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_PROTECT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_SURROUND ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_VERT_ORIENT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_HORI_ORIENT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_LR_SPACE ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_UL_SPACE ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_BACKGROUND ); + if( bCpyBrd ) + { + // If there's no BoxItem at graphic, but the new Format has one, then set the + // default item in the new Set. Because the graphic's size has never changed! + const SfxPoolItem *pItem; + if( SfxItemState::SET == pOldFormat->GetAttrSet(). + GetItemState( RES_BOX, true, &pItem )) + pNewSet->Put( *pItem ); + else if( SfxItemState::SET == pNewFormat->GetAttrSet(). + GetItemState( RES_BOX )) + pNewSet->Put( *GetDfltAttr( RES_BOX ) ); + + if( SfxItemState::SET == pOldFormat->GetAttrSet(). + GetItemState( RES_SHADOW, true, &pItem )) + pNewSet->Put( *pItem ); + else if( SfxItemState::SET == pNewFormat->GetAttrSet(). + GetItemState( RES_SHADOW )) + pNewSet->Put( *GetDfltAttr( RES_SHADOW ) ); + } + else + { + // Hard-set the attributes, because they could come from the Template + // and then size calculations could not be correct anymore. + pNewSet->Put( SvxBoxItem(RES_BOX) ); + pNewSet->Put( SvxShadowItem(RES_SHADOW) ); + } + + // Always transfer the anchor, which is a hard attribute anyways. + pNewSet->Put( pOldFormat->GetAnchor() ); + + // The new one should be changeable in its height. + std::unique_ptr<SwFormatFrameSize> aFrameSize(pOldFormat->GetFrameSize().Clone()); + aFrameSize->SetHeightSizeType( SwFrameSize::Minimum ); + pNewSet->Put( std::move(aFrameSize) ); + + SwStartNode* pSttNd = rDoc.GetNodes().MakeTextSection( + SwNodeIndex( rDoc.GetNodes().GetEndOfAutotext() ), + SwFlyStartNode, pColl ); + pNewSet->Put( SwFormatContent( pSttNd )); + + pNewFormat->SetFormatAttr( *pNewSet ); + + // InContents need to be treated in a special way: + // The TextAttribute needs to be destroyed. + // Unfortunately, this also destroys the Format next to the Frames. + // To avoid this, we disconnect the attribute from the Format. + + const SwFormatAnchor& rAnchor = pNewFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId() ) + { + const SwPosition *pPos = rAnchor.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt(nIdx, RES_TXTATR_FLYCNT); + + assert(pHint && "Missing Hint."); + + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt().GetFrameFormat() == pOldFormat, + "Wrong TextFlyCnt-Hint." ); + + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat( + pNewFormat ); + } + + // The old one should not have a flow and it should be adjusted to above and + // middle. + // Also, the width should be 100% and it should also adjust the height, if changed. + pNewSet->ClearItem(); + + pNewSet->Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + pNewSet->Put( SvxOpaqueItem( RES_OPAQUE, true ) ); + + sal_Int16 eVert = bBefore ? text::VertOrientation::BOTTOM : text::VertOrientation::TOP; + pNewSet->Put( SwFormatVertOrient( 0, eVert ) ); + pNewSet->Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER ) ); + + aFrameSize.reset(pOldFormat->GetFrameSize().Clone()); + + SwOLENode* pOleNode = rDoc.GetNodes()[nNdIdx + 1]->GetOLENode(); + bool isMath = false; + if(pOleNode) + { + svt::EmbeddedObjectRef& xRef = pOleNode->GetOLEObj().GetObject(); + if(xRef.is()) + { + SvGlobalName aCLSID( xRef->getClassID() ); + isMath = ( SotExchange::IsMath( aCLSID ) != 0 ); + } + } + aFrameSize->SetWidthPercent(isMath ? 0 : 100); + aFrameSize->SetHeightPercent(SwFormatFrameSize::SYNCED); + pNewSet->Put( std::move(aFrameSize) ); + + // Hard-set the attributes, because they could come from the Template + // and then size calculations could not be correct anymore. + if( bCpyBrd ) + { + pNewSet->Put( SvxBoxItem(RES_BOX) ); + pNewSet->Put( SvxShadowItem(RES_SHADOW) ); + } + pNewSet->Put( SvxLRSpaceItem(RES_LR_SPACE) ); + pNewSet->Put( SvxULSpaceItem(RES_UL_SPACE) ); + + // The old one is paragraph-bound to the paragraph in the new one. + SwFormatAnchor aAnch( RndStdIds::FLY_AT_PARA ); + SwNodeIndex aAnchIdx( *pNewFormat->GetContent().GetContentIdx(), 1 ); + pNew = aAnchIdx.GetNode().GetTextNode(); + SwPosition aPos( aAnchIdx ); + aAnch.SetAnchor( &aPos ); + pNewSet->Put( aAnch ); + + if( pUndo ) + pUndo->SetFlys( *pOldFormat, *pNewSet, *pNewFormat ); + else + pOldFormat->SetFormatAttr( *pNewSet ); + + pNewSet.reset(); + + // Have only the FlyFrames created. + // We leave this to established methods (especially for InCntFlys). + pNewFormat->MakeFrames(); + // #i115719# + if ( bIsSwFlyFrameFormatInstance ) + { + static_cast<SwFlyFrameFormat*>(pOldFormat)->SetObjTitle( sTitle ); + static_cast<SwFlyFrameFormat*>(pOldFormat)->SetObjDescription( sDescription ); + } + } + break; + + default: + OSL_ENSURE(false, "unknown LabelType?"); + } + OSL_ENSURE( pNew, "No Label inserted" ); + if( pNew ) + { + // #i61007# order of captions + bool bOrderNumberingFirst = SW_MOD()->GetModuleConfig()->IsCaptionOrderNumberingFirst(); + // Work up OUString + OUString aText; + if( bOrderNumberingFirst ) + { + aText = rNumberingSeparator; + } + if( pType) + { + aText += pType->GetName(); + if( !bOrderNumberingFirst ) + aText += " "; + } + sal_Int32 nIdx = aText.getLength(); + if( !rText.isEmpty() ) + { + aText += rSeparator; + } + const sal_Int32 nSepIdx = aText.getLength(); + aText += rText; + + // Insert string + SwIndex aIdx( pNew, 0 ); + pNew->InsertText( aText, aIdx ); + + // Insert field + if(pType) + { + SwSetExpField aField( static_cast<SwSetExpFieldType*>(pType), OUString(), SVX_NUM_ARABIC); + if( bOrderNumberingFirst ) + nIdx = 0; + SwFormatField aFormat( aField ); + pNew->InsertItem( aFormat, nIdx, nIdx ); + if(!rCharacterStyle.isEmpty()) + { + SwCharFormat* pCharFormat = rDoc.FindCharFormatByName(rCharacterStyle); + if( !pCharFormat ) + { + const sal_uInt16 nMyId = SwStyleNameMapper::GetPoolIdFromUIName(rCharacterStyle, SwGetPoolIdFromName::ChrFmt); + pCharFormat = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( nMyId ); + } + if (pCharFormat) + { + SwFormatCharFormat aCharFormat( pCharFormat ); + pNew->InsertItem( aCharFormat, 0, + nSepIdx + 1, SetAttrMode::DONTEXPAND ); + } + } + } + + if ( bTable ) + { + if ( bBefore ) + { + if ( !pNew->GetSwAttrSet().GetKeep().GetValue() ) + pNew->SetAttr( SvxFormatKeepItem( true, RES_KEEP ) ); + } + else + { + SwTableNode *const pNd = + rDoc.GetNodes()[nNdIdx]->GetStartNode()->GetTableNode(); + SwTable &rTable = pNd->GetTable(); + if ( !rTable.GetFrameFormat()->GetKeep().GetValue() ) + rTable.GetFrameFormat()->SetFormatAttr( SvxFormatKeepItem( true, RES_KEEP ) ); + if ( pUndo ) + pUndo->SetUndoKeep(); + } + } + rDoc.getIDocumentState().SetModified(); + } + + return pNewFormat; +} + +SwFlyFrameFormat * +SwDoc::InsertLabel( + SwLabelType const eType, OUString const& rText, OUString const& rSeparator, + OUString const& rNumberingSeparator, + bool const bBefore, sal_uInt16 const nId, sal_uLong const nNdIdx, + OUString const& rCharacterStyle, + bool const bCpyBrd ) +{ + std::unique_ptr<SwUndoInsertLabel> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoInsertLabel( + eType, rText, rSeparator, rNumberingSeparator, + bBefore, nId, rCharacterStyle, bCpyBrd, this )); + } + + SwFlyFrameFormat *const pNewFormat = lcl_InsertLabel(*this, mpTextFormatCollTable.get(), pUndo.get(), + eType, rText, rSeparator, rNumberingSeparator, bBefore, + nId, nNdIdx, rCharacterStyle, bCpyBrd); + + if (pUndo) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + else + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + return pNewFormat; +} + +static SwFlyFrameFormat * +lcl_InsertDrawLabel( SwDoc & rDoc, SwTextFormatColls *const pTextFormatCollTable, + SwUndoInsertLabel *const pUndo, SwDrawFrameFormat *const pOldFormat, + OUString const& rText, + const OUString& rSeparator, + const OUString& rNumberSeparator, + const sal_uInt16 nId, + const OUString& rCharacterStyle, + SdrObject& rSdrObj ) +{ + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + ::sw::DrawUndoGuard const drawUndoGuard(rDoc.GetIDocumentUndoRedo()); + + // Because we get by the TextColl's name, we need to create the field first. + OSL_ENSURE( nId == USHRT_MAX || nId < rDoc.getIDocumentFieldsAccess().GetFieldTypes()->size(), + "FieldType index out of bounds" ); + SwFieldType *pType = nId != USHRT_MAX ? (*rDoc.getIDocumentFieldsAccess().GetFieldTypes())[nId].get() : nullptr; + OSL_ENSURE( !pType || pType->Which() == SwFieldIds::SetExp, "Wrong label id" ); + + SwTextFormatColl *pColl = nullptr; + if( pType ) + { + for( auto i = pTextFormatCollTable->size(); i; ) + { + if( (*pTextFormatCollTable)[ --i ]->GetName()==pType->GetName() ) + { + pColl = (*pTextFormatCollTable)[i]; + break; + } + } + OSL_ENSURE( pColl, "no text collection found" ); + } + + if( !pColl ) + { + pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_LABEL ); + } + + SwTextNode* pNew = nullptr; + SwFlyFrameFormat* pNewFormat = nullptr; + + // Destroy Frame, + // insert new Frame, + // insert the corresponding Node with Field into the new Frame, + // insert the old Frame with the Object (Picture/OLE) paragraph-bound into the new Frame, + // create Frames. + + // Keep layer ID of drawing object before removing + // its frames. + // Note: The layer ID is passed to the undo and have to be the correct value. + // Removing the frames of the drawing object changes its layer. + const SdrLayerID nLayerId = rSdrObj.GetLayer(); + + pOldFormat->DelFrames(); + + // InContents need to be treated in a special way: + // The TextAttribute needs to be destroyed. + // Unfortunately, this also destroys the Format next to the Frames. + // To avoid this, we disconnect the attribute from the Format. + std::unique_ptr<SfxItemSet> pNewSet = pOldFormat->GetAttrSet().Clone( false ); + + // Protect the Frame's size and position + if ( rSdrObj.IsMoveProtect() || rSdrObj.IsResizeProtect() ) + { + SvxProtectItem aProtect(RES_PROTECT); + aProtect.SetContentProtect( false ); + aProtect.SetPosProtect( rSdrObj.IsMoveProtect() ); + aProtect.SetSizeProtect( rSdrObj.IsResizeProtect() ); + pNewSet->Put( aProtect ); + } + + // Take over the text wrap + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_SURROUND ); + + // Send the frame to the back, if needed. + // Consider the 'invisible' hell layer. + if ( rDoc.getIDocumentDrawModelAccess().GetHellId() != nLayerId && + rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId() != nLayerId ) + { + SvxOpaqueItem aOpaque( RES_OPAQUE ); + aOpaque.SetValue( true ); + pNewSet->Put( aOpaque ); + } + + // Take over position + // #i26791# - use directly drawing object's positioning attributes + pNewSet->Put( pOldFormat->GetHoriOrient() ); + pNewSet->Put( pOldFormat->GetVertOrient() ); + + pNewSet->Put( pOldFormat->GetAnchor() ); + + // The new one should be variable in its height! + Size aSz( rSdrObj.GetCurrentBoundRect().GetSize() ); + SwFormatFrameSize aFrameSize( SwFrameSize::Minimum, aSz.Width(), aSz.Height() ); + pNewSet->Put( aFrameSize ); + + // Apply the margin to the new Frame. + // Don't set a border, use the one from the Template. + pNewSet->Put( pOldFormat->GetLRSpace() ); + pNewSet->Put( pOldFormat->GetULSpace() ); + + SwStartNode* pSttNd = + rDoc.GetNodes().MakeTextSection( + SwNodeIndex( rDoc.GetNodes().GetEndOfAutotext() ), + SwFlyStartNode, pColl ); + + pNewFormat = rDoc.MakeFlyFrameFormat( rDoc.GetUniqueFrameName(), + rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ) ); + + // Set border and shadow to default if the template contains any. + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState( RES_BOX )) + pNewSet->Put( *GetDfltAttr( RES_BOX ) ); + + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState(RES_SHADOW)) + pNewSet->Put( *GetDfltAttr( RES_SHADOW ) ); + + pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); + pNewFormat->SetFormatAttr( *pNewSet ); + + const SwFormatAnchor& rAnchor = pNewFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId() ) + { + const SwPosition *pPos = rAnchor.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); + + assert(pHint && "Missing Hint."); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt(). + GetFrameFormat() == static_cast<SwFrameFormat*>(pOldFormat), + "Wrong TextFlyCnt-Hint." ); +#endif + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat( pNewFormat ); + } + + // The old one should not have a flow + // and it should be adjusted to above and middle. + pNewSet->ClearItem(); + + pNewSet->Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + if (nLayerId == rDoc.getIDocumentDrawModelAccess().GetHellId()) + { + // Consider drawing objects in the 'invisible' hell layer + rSdrObj.SetLayer( rDoc.getIDocumentDrawModelAccess().GetHeavenId() ); + } + else if (nLayerId == rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId()) + { + rSdrObj.SetLayer( rDoc.getIDocumentDrawModelAccess().GetInvisibleHeavenId() ); + } + pNewSet->Put( SvxLRSpaceItem( RES_LR_SPACE ) ); + pNewSet->Put( SvxULSpaceItem( RES_UL_SPACE ) ); + + // #i26791# - set position of the drawing object, which is labeled. + pNewSet->Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ) ); + pNewSet->Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::FRAME ) ); + + // The old one is paragraph-bound to the new one's paragraph. + SwFormatAnchor aAnch( RndStdIds::FLY_AT_PARA ); + SwNodeIndex aAnchIdx( *pNewFormat->GetContent().GetContentIdx(), 1 ); + pNew = aAnchIdx.GetNode().GetTextNode(); + SwPosition aPos( aAnchIdx ); + aAnch.SetAnchor( &aPos ); + pNewSet->Put( aAnch ); + + if( pUndo ) + { + pUndo->SetFlys( *pOldFormat, *pNewSet, *pNewFormat ); + // #i26791# - position no longer needed + pUndo->SetDrawObj( nLayerId ); + } + else + pOldFormat->SetFormatAttr( *pNewSet ); + + pNewSet.reset(); + + // Have only the FlyFrames created. + // We leave this to established methods (especially for InCntFlys). + pNewFormat->MakeFrames(); + + OSL_ENSURE( pNew, "No Label inserted" ); + + if( pNew ) + { + //#i61007# order of captions + bool bOrderNumberingFirst = SW_MOD()->GetModuleConfig()->IsCaptionOrderNumberingFirst(); + + // prepare string + OUString aText; + if( bOrderNumberingFirst ) + { + aText = rNumberSeparator; + } + if ( pType ) + { + aText += pType->GetName(); + if( !bOrderNumberingFirst ) + aText += " "; + } + sal_Int32 nIdx = aText.getLength(); + aText += rSeparator; + const sal_Int32 nSepIdx = aText.getLength(); + aText += rText; + + // insert text + SwIndex aIdx( pNew, 0 ); + pNew->InsertText( aText, aIdx ); + + // insert field + if ( pType ) + { + SwSetExpField aField( static_cast<SwSetExpFieldType*>(pType), OUString(), SVX_NUM_ARABIC ); + if( bOrderNumberingFirst ) + nIdx = 0; + SwFormatField aFormat( aField ); + pNew->InsertItem( aFormat, nIdx, nIdx ); + if ( !rCharacterStyle.isEmpty() ) + { + SwCharFormat * pCharFormat = rDoc.FindCharFormatByName(rCharacterStyle); + if ( !pCharFormat ) + { + const sal_uInt16 nMyId = SwStyleNameMapper::GetPoolIdFromUIName( rCharacterStyle, SwGetPoolIdFromName::ChrFmt ); + pCharFormat = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( nMyId ); + } + if ( pCharFormat ) + { + SwFormatCharFormat aCharFormat( pCharFormat ); + pNew->InsertItem( aCharFormat, 0, nSepIdx + 1, + SetAttrMode::DONTEXPAND ); + } + } + } + } + + return pNewFormat; +} + +SwFlyFrameFormat* SwDoc::InsertDrawLabel( + OUString const& rText, + OUString const& rSeparator, + OUString const& rNumberSeparator, + sal_uInt16 const nId, + OUString const& rCharacterStyle, + SdrObject& rSdrObj ) +{ + SwDrawContact *const pContact = + static_cast<SwDrawContact*>(GetUserCall( &rSdrObj )); + if (!pContact) + return nullptr; + OSL_ENSURE( RES_DRAWFRMFMT == pContact->GetFormat()->Which(), + "InsertDrawLabel(): not a DrawFrameFormat" ); + + SwDrawFrameFormat* pOldFormat = static_cast<SwDrawFrameFormat *>(pContact->GetFormat()); + if (!pOldFormat) + return nullptr; + + std::unique_ptr<SwUndoInsertLabel> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoInsertLabel( + SwLabelType::Draw, rText, rSeparator, rNumberSeparator, false, + nId, rCharacterStyle, false, this )); + } + + SwFlyFrameFormat *const pNewFormat = lcl_InsertDrawLabel( + *this, mpTextFormatCollTable.get(), pUndo.get(), pOldFormat, + rText, rSeparator, rNumberSeparator, nId, rCharacterStyle, rSdrObj); + + if (pUndo) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + else + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + return pNewFormat; +} + +static void lcl_collectUsedNums(std::vector<unsigned int>& rSetFlags, sal_Int32 nNmLen, const OUString& rName, const OUString& rCmpName) +{ + if (rName.startsWith(rCmpName)) + { + // Only get and set the Flag + const sal_Int32 nNum = rName.copy(nNmLen).toInt32() - 1; + if (nNum >= 0) + rSetFlags.push_back(nNum); + } +} + +static void lcl_collectUsedNums(std::vector<unsigned int>& rSetFlags, sal_Int32 nNmLen, const SdrObject& rObj, const OUString& rCmpName) +{ + OUString sName = rObj.GetName(); + lcl_collectUsedNums(rSetFlags, nNmLen, sName, rCmpName); + // tdf#122487 take groups into account, iterate and recurse through their + // contents for name collision check + if (rObj.IsGroupObject()) + { + const SdrObjList* pSub(rObj.GetSubList()); + assert(pSub && "IsGroupObject is implemented as GetSubList != nullptr"); + const size_t nCount = pSub->GetObjCount(); + for (size_t i = 0; i < nCount; ++i) + { + SdrObject* pObj = pSub->GetObj(i); + if (!pObj) + continue; + lcl_collectUsedNums(rSetFlags, nNmLen, *pObj, rCmpName); + } + } +} + +namespace +{ + int first_available_number(std::vector<unsigned int>& numbers) + { + std::sort(numbers.begin(), numbers.end()); + auto last = std::unique(numbers.begin(), numbers.end()); + numbers.erase(last, numbers.end()); + + for (size_t i = 0; i < numbers.size(); ++i) + { + if (numbers[i] != i) + return i; + } + + return numbers.size(); + } +} + +static OUString lcl_GetUniqueFlyName(const SwDoc* pDoc, const char* pDefStrId, sal_uInt16 eType) +{ + assert(eType >= RES_FMT_BEGIN && eType < RES_FMT_END); + if( pDoc->IsInMailMerge()) + { + OUString newName = "MailMergeFly" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( pDoc->GetSpzFrameFormats()->size() + 1 ); + return newName; + } + + OUString aName(SwResId(pDefStrId)); + sal_Int32 nNmLen = aName.getLength(); + + const SwFrameFormats& rFormats = *pDoc->GetSpzFrameFormats(); + + std::vector<unsigned int> aUsedNums; + aUsedNums.reserve(rFormats.size()); + + for( SwFrameFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFlyFormat = rFormats[ n ]; + if (eType != pFlyFormat->Which()) + continue; + if (eType == RES_DRAWFRMFMT) + { + const SdrObject *pObj = pFlyFormat->FindSdrObject(); + if (pObj) + lcl_collectUsedNums(aUsedNums, nNmLen, *pObj, aName); + } + + OUString sName = pFlyFormat->GetName(); + lcl_collectUsedNums(aUsedNums, nNmLen, sName, aName); + } + + // All numbers are flagged accordingly, so determine the right one + SwFrameFormats::size_type nNum = first_available_number(aUsedNums) + 1; + return aName + OUString::number(nNum); +} + +OUString SwDoc::GetUniqueGrfName() const +{ + return lcl_GetUniqueFlyName(this, STR_GRAPHIC_DEFNAME, RES_FLYFRMFMT); +} + +OUString SwDoc::GetUniqueOLEName() const +{ + return lcl_GetUniqueFlyName(this, STR_OBJECT_DEFNAME, RES_FLYFRMFMT); +} + +OUString SwDoc::GetUniqueFrameName() const +{ + return lcl_GetUniqueFlyName(this, STR_FRAME_DEFNAME, RES_FLYFRMFMT); +} + +OUString SwDoc::GetUniqueShapeName() const +{ + return lcl_GetUniqueFlyName(this, STR_SHAPE_DEFNAME, RES_DRAWFRMFMT); +} + +OUString SwDoc::GetUniqueDrawObjectName() const +{ + return lcl_GetUniqueFlyName(this, "DrawObject", RES_DRAWFRMFMT); +} + +const SwFlyFrameFormat* SwDoc::FindFlyByName( const OUString& rName, SwNodeType nNdTyp ) const +{ + auto range = GetSpzFrameFormats()->rangeFind( RES_FLYFRMFMT, rName ); + for( auto it = range.first; it != range.second; it++ ) + { + const SwFrameFormat* pFlyFormat = *it; + if( RES_FLYFRMFMT != pFlyFormat->Which() || pFlyFormat->GetName() != rName ) + continue; + const SwNodeIndex* pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + if( nNdTyp != SwNodeType::NONE ) + { + // query for the right NodeType + const SwNode* pNd = GetNodes()[ pIdx->GetIndex()+1 ]; + if( nNdTyp == SwNodeType::Text + ? !pNd->IsNoTextNode() + : nNdTyp == pNd->GetNodeType() ) + return static_cast<const SwFlyFrameFormat*>(pFlyFormat); + } + else + return static_cast<const SwFlyFrameFormat*>(pFlyFormat); + } + } + return nullptr; +} + +void SwDoc::SetFlyName( SwFlyFrameFormat& rFormat, const OUString& rName ) +{ + OUString sName( rName ); + if( sName.isEmpty() || FindFlyByName( sName ) ) + { + const char* pTyp = STR_FRAME_DEFNAME; + const SwNodeIndex* pIdx = rFormat.GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + switch( GetNodes()[ pIdx->GetIndex() + 1 ]->GetNodeType() ) + { + case SwNodeType::Grf: + pTyp = STR_GRAPHIC_DEFNAME; + break; + case SwNodeType::Ole: + pTyp = STR_OBJECT_DEFNAME; + break; + default: break; + } + } + sName = lcl_GetUniqueFlyName(this, pTyp, RES_FLYFRMFMT); + } + rFormat.SetName( sName, true ); + getIDocumentState().SetModified(); +} + +void SwDoc::SetAllUniqueFlyNames() +{ + sal_Int32 n, nFlyNum = 0, nGrfNum = 0, nOLENum = 0; + + const OUString sFlyNm(SwResId(STR_FRAME_DEFNAME)); + const OUString sGrfNm(SwResId(STR_GRAPHIC_DEFNAME)); + const OUString sOLENm(SwResId(STR_OBJECT_DEFNAME)); + + if( 255 < ( n = GetSpzFrameFormats()->size() )) + n = 255; + SwFrameFormatsV aArr; + aArr.reserve( n ); + SwFrameFormat* pFlyFormat; + bool bContainsAtPageObjWithContentAnchor = false; + + for( n = GetSpzFrameFormats()->size(); n; ) + { + pFlyFormat = (*GetSpzFrameFormats())[ --n ]; + if( RES_FLYFRMFMT == pFlyFormat->Which() ) + { + const OUString& aNm = pFlyFormat->GetName(); + if ( !aNm.isEmpty() ) + { + sal_Int32 *pNum = nullptr; + sal_Int32 nLen = 0; + if ( aNm.startsWith(sGrfNm) ) + { + nLen = sGrfNm.getLength(); + pNum = &nGrfNum; + } + else if( aNm.startsWith(sFlyNm) ) + { + nLen = sFlyNm.getLength(); + pNum = &nFlyNum; + } + else if( aNm.startsWith(sOLENm) ) + { + nLen = sOLENm.getLength(); + pNum = &nOLENum; + } + + if ( pNum ) + { + const sal_Int32 nNewLen = aNm.copy( nLen ).toInt32(); + if (*pNum < nNewLen) + *pNum = nNewLen; + } + } + else + // we want to set that afterwards + aArr.push_back( pFlyFormat ); + + } + if ( !bContainsAtPageObjWithContentAnchor ) + { + const SwFormatAnchor& rAnchor = pFlyFormat->GetAnchor(); + if ( (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) && + rAnchor.GetContentAnchor() ) + { + bContainsAtPageObjWithContentAnchor = true; + } + } + } + SetContainsAtPageObjWithContentAnchor( bContainsAtPageObjWithContentAnchor ); + + for( n = aArr.size(); n; ) + { + pFlyFormat = aArr[ --n ]; + const SwNodeIndex* pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + switch( GetNodes()[ pIdx->GetIndex() + 1 ]->GetNodeType() ) + { + case SwNodeType::Grf: + pFlyFormat->SetName( sGrfNm + OUString::number( ++nGrfNum )); + break; + case SwNodeType::Ole: + pFlyFormat->SetName( sOLENm + OUString::number( ++nOLENum )); + break; + default: + pFlyFormat->SetName( sFlyNm + OUString::number( ++nFlyNum )); + break; + } + } + } + aArr.clear(); + + if( !GetFootnoteIdxs().empty() ) + { + SwTextFootnote::SetUniqueSeqRefNo( *this ); + // #i52775# Chapter footnotes did not get updated correctly. + // Calling UpdateAllFootnote() instead of UpdateFootnote() solves this problem, + // but I do not dare to call UpdateAllFootnote() in all cases: Safety first. + if ( FTNNUM_CHAPTER == GetFootnoteInfo().m_eNum ) + { + GetFootnoteIdxs().UpdateAllFootnote(); + } + else + { + SwNodeIndex aTmp( GetNodes() ); + GetFootnoteIdxs().UpdateFootnote( aTmp ); + } + } +} + +bool SwDoc::IsInHeaderFooter( const SwNodeIndex& rIdx ) const +{ + // That can also be a Fly in a Fly in the Header. + // Is also used by sw3io, to determine if a Redline object is + // in the Header or Footer. + // Because Redlines are also attached to Start and EndNode, + // the Index must not necessarily be from a ContentNode. + SwNode* pNd = &rIdx.GetNode(); + const SwNode* pFlyNd = pNd->FindFlyStartNode(); + while( pFlyNd ) + { + // get up by using the Anchor +#if OSL_DEBUG_LEVEL > 0 + std::vector<const SwFrameFormat*> checkFormats; + for( auto pFormat : *GetSpzFrameFormats() ) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + checkFormats.push_back( pFormat ); + } +#endif + std::vector<SwFrameFormat*> const*const pFlys(pFlyNd->GetAnchoredFlys()); + bool bFound(false); + for (size_t i = 0; pFlys && i < pFlys->size(); ++i) + { + const SwFrameFormat *const pFormat = (*pFlys)[i]; + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + { +#if OSL_DEBUG_LEVEL > 0 + auto checkPos = std::find( + checkFormats.begin(), checkFormats.end(), pFormat ); + assert( checkPos != checkFormats.end()); + checkFormats.erase( checkPos ); +#endif + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) || + !rAnchor.GetContentAnchor() ) + { + return false; + } + + pNd = &rAnchor.GetContentAnchor()->nNode.GetNode(); + pFlyNd = pNd->FindFlyStartNode(); + bFound = true; + break; + } + } + if (!bFound) + { + OSL_ENSURE(mbInReading, "Found a FlySection but not a Format!"); + return false; + } + } + + return nullptr != pNd->FindHeaderStartNode() || + nullptr != pNd->FindFooterStartNode(); +} + +SvxFrameDirection SwDoc::GetTextDirection( const SwPosition& rPos, + const Point* pPt ) const +{ + SvxFrameDirection nRet = SvxFrameDirection::Unknown; + + SwContentNode *pNd = rPos.nNode.GetNode().GetContentNode(); + + // #i42921# - use new method <SwContentNode::GetTextDirection(..)> + if ( pNd ) + { + nRet = pNd->GetTextDirection( rPos, pPt ); + } + if ( nRet == SvxFrameDirection::Unknown ) + { + const SvxFrameDirectionItem* pItem = nullptr; + if( pNd ) + { + // Are we in a FlyFrame? Then look at that for the correct attribute + const SwFrameFormat* pFlyFormat = pNd->GetFlyFormat(); + while( pFlyFormat ) + { + pItem = &pFlyFormat->GetFrameDir(); + if( SvxFrameDirection::Environment == pItem->GetValue() ) + { + pItem = nullptr; + const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) && + pAnchor->GetContentAnchor()) + { + pFlyFormat = pAnchor->GetContentAnchor()->nNode. + GetNode().GetFlyFormat(); + } + else + pFlyFormat = nullptr; + } + else + pFlyFormat = nullptr; + } + + if( !pItem ) + { + const SwPageDesc* pPgDsc = pNd->FindPageDesc(); + if( pPgDsc ) + pItem = &pPgDsc->GetMaster().GetFrameDir(); + } + } + if( !pItem ) + pItem = &GetAttrPool().GetDefaultItem( RES_FRAMEDIR ); + nRet = pItem->GetValue(); + } + return nRet; +} + +bool SwDoc::IsInVerticalText( const SwPosition& rPos ) const +{ + const SvxFrameDirection nDir = GetTextDirection( rPos ); + return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir; +} + +o3tl::sorted_vector<SwRootFrame*> SwDoc::GetAllLayouts() +{ + o3tl::sorted_vector<SwRootFrame*> aAllLayouts; + SwViewShell *pStart = getIDocumentLayoutAccess().GetCurrentViewShell(); + if(pStart) + { + for(const SwViewShell& rShell : pStart->GetRingContainer()) + { + if(rShell.GetLayout()) + aAllLayouts.insert(rShell.GetLayout()); + } + } + return aAllLayouts; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docnew.cxx b/sw/source/core/doc/docnew.cxx new file mode 100644 index 000000000..358887b21 --- /dev/null +++ b/sw/source/core/doc/docnew.cxx @@ -0,0 +1,1282 @@ +/* -*- 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 <config_features.h> + +#include <o3tl/sorted_vector.hxx> + +#include <doc.hxx> +#include <proofreadingiterator.hxx> +#include <com/sun/star/text/XFlatParagraphIteratorProvider.hpp> +#include <com/sun/star/linguistic2/XProofreadingIterator.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/random.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/XmlIdRegistry.hxx> +#include <sal/log.hxx> + +#include <sfx2/linkmgr.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <svl/zforlist.hxx> +#include <unotools/lingucfg.hxx> +#include <svx/svdpage.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <fmtfordr.hxx> +#include <fmtpdsc.hxx> +#include <pvprtdat.hxx> +#include <rootfrm.hxx> +#include <pagedesc.hxx> +#include <ndtxt.hxx> +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <charfmt.hxx> +#include <frmfmt.hxx> +#include <poolfmt.hxx> +#include <dbmgr.hxx> +#include <docsh.hxx> +#include <acorrect.hxx> +#include <visiturl.hxx> +#include <docary.hxx> +#include <lineinfo.hxx> +#include <drawdoc.hxx> +#include <extinput.hxx> +#include <viewsh.hxx> +#include <doctxm.hxx> +#include <shellres.hxx> +#include <laycache.hxx> +#include <mvsave.hxx> +#include <istyleaccess.hxx> +#include "swstylemanager.hxx" +#include <IGrammarContact.hxx> +#include <tblafmt.hxx> +#include <MarkManager.hxx> +#include <UndoManager.hxx> +#include <DocumentDeviceManager.hxx> +#include <DocumentSettingManager.hxx> +#include <DocumentDrawModelManager.hxx> +#include <DocumentChartDataProviderManager.hxx> +#include <DocumentTimerManager.hxx> +#include <DocumentLinksAdministrationManager.hxx> +#include <DocumentListItemsManager.hxx> +#include <DocumentListsManager.hxx> +#include <DocumentOutlineNodesManager.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <DocumentRedlineManager.hxx> +#include <DocumentFieldsManager.hxx> +#include <DocumentStatisticsManager.hxx> +#include <DocumentStateManager.hxx> +#include <DocumentLayoutManager.hxx> +#include <DocumentStylePoolManager.hxx> +#include <DocumentExternalDataManager.hxx> +#include <wrtsh.hxx> +#include <unocrsr.hxx> +#include <fmthdft.hxx> +#include <frameformats.hxx> + +#include <numrule.hxx> + +#include <sfx2/Metadatable.hxx> +#include <fmtmeta.hxx> + +#include <svx/xfillit0.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::document; + +/* + * global functions... + */ + uno::Reference< linguistic2::XProofreadingIterator > const & SwDoc::GetGCIterator() const +{ + if (!m_xGCIterator.is() && SvtLinguConfig().HasGrammarChecker()) + { + uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); + try + { + m_xGCIterator = sw::proofreadingiterator::get( xContext ); + } + catch (const uno::Exception &) + { + OSL_FAIL( "No GCIterator" ); + } + } + + return m_xGCIterator; +} + +bool SwDoc::StartGrammarChecking( bool bSkipStart ) +{ + // check for a visible view + bool bVisible = false; + bool bStarted = false; + const SwDocShell *pDocShell = GetDocShell(); + SfxViewFrame *pFrame = SfxViewFrame::GetFirst( pDocShell, false ); + while (pFrame && !bVisible) + { + if (pFrame->IsVisible()) + bVisible = true; + pFrame = SfxViewFrame::GetNext( *pFrame, pDocShell, false ); + } + + //!! only documents with visible views need to be checked + //!! (E.g. don't check temporary documents created for printing, see printing of notes and selections. + //!! Those get created on the fly and get hard deleted a bit later as well, and no one should have + //!! a UNO reference to them) + if (bVisible) + { + uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( GetGCIterator() ); + if ( xGCIterator.is() ) + { + uno::Reference< lang::XComponent > xDoc = GetDocShell()->GetBaseModel(); + uno::Reference< text::XFlatParagraphIteratorProvider > xFPIP( xDoc, uno::UNO_QUERY ); + + // start automatic background checking if not active already + if ( xFPIP.is() && !xGCIterator->isProofreading( xDoc ) ) + { + bStarted = true; + if ( !bSkipStart ) + { + for (auto pLayout : GetAllLayouts()) + { // we're starting it now, don't start grammar checker + // again until the user modifies the document + pLayout->SetNeedGrammarCheck(false); + } + xGCIterator->startProofreading( xDoc, xFPIP ); + } + } + } + } + + return bStarted; +} + +/* + * internal functions + */ +static void lcl_DelFormatIndices( SwFormat const * pFormat ) +{ + SwFormatContent &rFormatContent = const_cast<SwFormatContent&>(pFormat->GetContent()); + if ( rFormatContent.GetContentIdx() ) + rFormatContent.SetNewContentIdx( nullptr ); + SwFormatAnchor &rFormatAnchor = const_cast<SwFormatAnchor&>(pFormat->GetAnchor()); + if ( rFormatAnchor.GetContentAnchor() ) + rFormatAnchor.SetAnchor( nullptr ); +} + +/* + * exported methods + */ +SwDoc::SwDoc() + : m_pNodes( new SwNodes(this) ), + mpAttrPool(new SwAttrPool(this)), + mpMarkManager(new ::sw::mark::MarkManager(*this)), + m_pMetaFieldManager(new ::sw::MetaFieldManager()), + m_pDocumentDrawModelManager( new ::sw::DocumentDrawModelManager( *this ) ), + m_pDocumentRedlineManager( new ::sw::DocumentRedlineManager( *this ) ), + m_pDocumentStateManager( new ::sw::DocumentStateManager( *this ) ), + m_pUndoManager(new ::sw::UndoManager( + std::shared_ptr<SwNodes>(new SwNodes(this)), *m_pDocumentDrawModelManager, *m_pDocumentRedlineManager, *m_pDocumentStateManager)), + m_pDocumentSettingManager(new ::sw::DocumentSettingManager(*this)), + m_pDocumentChartDataProviderManager( new sw::DocumentChartDataProviderManager( *this ) ), + m_pDeviceAccess( new ::sw::DocumentDeviceManager( *this ) ), + m_pDocumentTimerManager( new ::sw::DocumentTimerManager( *this ) ), + m_pDocumentLinksAdministrationManager( new ::sw::DocumentLinksAdministrationManager( *this ) ), + m_pDocumentListItemsManager( new ::sw::DocumentListItemsManager() ), + m_pDocumentListsManager( new ::sw::DocumentListsManager( *this ) ), + m_pDocumentOutlineNodesManager( new ::sw::DocumentOutlineNodesManager( *this ) ), + m_pDocumentContentOperationsManager( new ::sw::DocumentContentOperationsManager( *this ) ), + m_pDocumentFieldsManager( new ::sw::DocumentFieldsManager( *this ) ), + m_pDocumentStatisticsManager( new ::sw::DocumentStatisticsManager( *this ) ), + m_pDocumentLayoutManager( new ::sw::DocumentLayoutManager( *this ) ), + m_pDocumentStylePoolManager( new ::sw::DocumentStylePoolManager( *this ) ), + m_pDocumentExternalDataManager( new ::sw::DocumentExternalDataManager ), + mpDfltFrameFormat( new SwFrameFormat( GetAttrPool(), "Frameformat", nullptr ) ), + mpEmptyPageFormat( new SwFrameFormat( GetAttrPool(), "Empty Page", mpDfltFrameFormat.get() ) ), + mpColumnContFormat( new SwFrameFormat( GetAttrPool(), "Columncontainer", mpDfltFrameFormat.get() ) ), + mpDfltCharFormat( new SwCharFormat( GetAttrPool(), "Character style", nullptr ) ), + mpDfltTextFormatColl( new SwTextFormatColl( GetAttrPool(), "Paragraph style" ) ), + mpDfltGrfFormatColl( new SwGrfFormatColl( GetAttrPool(), "Graphikformatvorlage" ) ), + mpFrameFormatTable( new SwFrameFormats() ), + mpCharFormatTable( new SwCharFormats ), + mpSpzFrameFormatTable( new SwFrameFormats() ), + mpSectionFormatTable( new SwSectionFormats ), + mpTableFrameFormatTable( new SwFrameFormats() ), + mpTextFormatCollTable( new SwTextFormatColls() ), + mpGrfFormatCollTable( new SwGrfFormatColls() ), + mpTOXTypes( new SwTOXTypes ), + mpDefTOXBases( new SwDefTOXBase_Impl() ), + mpOutlineRule( nullptr ), + mpFootnoteInfo( new SwFootnoteInfo ), + mpEndNoteInfo( new SwEndNoteInfo ), + mpLineNumberInfo( new SwLineNumberInfo ), + mpFootnoteIdxs( new SwFootnoteIdxs ), + mpDocShell( nullptr ), + mpNumberFormatter( nullptr ), + mpNumRuleTable( new SwNumRuleTable ), + mpExtInputRing( nullptr ), + mpGrammarContact(createGrammarContact()), + mpCellStyles(new SwCellStyleTable), + m_pXmlIdRegistry(), + mReferenceCount(0), + mbDtor(false), + mbCopyIsMove(false), + mbInReading(false), + mbInWriting(false), + mbInMailMerge(false), + mbInXMLImport(false), + mbInWriterfilterImport(false), + mbUpdateTOX(false), + mbInLoadAsynchron(false), + mbIsAutoFormatRedline(false), + mbOLEPrtNotifyPending(false), + mbAllOLENotify(false), + mbInsOnlyTextGlssry(false), + mbContains_MSVBasic(false), + mbClipBoard( false ), + mbColumnSelection( false ), + mbIsPrepareSelAll(false), + meDictionaryMissing( MissingDictionary::Undefined ), + mbContainsAtPageObjWithContentAnchor(false), //#i119292#, fdo#37024 + + meDocType(DOCTYPE_NATIVE) +{ + // The DrawingLayer ItemPool which is used as 2nd pool for Writer documents' pool + // has a default for the XFillStyleItem of XFILL_SOLID and the color for it is the default + // fill color (blue7 or similar). This is a problem, in Writer we want the default fill + // style to be drawing::FillStyle_NONE. This cannot simply be done by changing it in the 2nd pool at the + // pool defaults when the DrawingLayer ItemPool is used for Writer, that would lead to + // countless problems like DrawObjects initial fill and others. + // It is also hard to find all places where the initial ItemSets for Writer (including + // style hierarchies) are created and to always set (but only at the root) the FillStyle + // to NONE fixed; that will add that attribute to the file format. It will be hard to reset + // attribute sets (which is done at import and using UI). Also not a good solution. + // Luckily Writer uses pDfltTextFormatColl as default parent for all paragraphs and similar, thus + // it is possible to set this attribute here. It will be not reset when importing. + mpDfltTextFormatColl->SetFormatAttr(XFillStyleItem(drawing::FillStyle_NONE)); + mpDfltFrameFormat->SetFormatAttr(XFillStyleItem(drawing::FillStyle_NONE)); + // prevent paragraph default margins being applied to everything + mpDfltFrameFormat->SetFormatAttr(SvxULSpaceItem(RES_UL_SPACE)); + mpDfltFrameFormat->SetFormatAttr(SvxLRSpaceItem(RES_LR_SPACE)); + + /* + * DefaultFormats and DefaultFormatCollections (FormatColl) + * are inserted at position 0 at the respective array. + * The formats in the FormatColls are derived from the + * DefaultFormats and are also in the list. + */ + /* Formats */ + mpFrameFormatTable->push_back(mpDfltFrameFormat.get()); + mpCharFormatTable->push_back(mpDfltCharFormat.get()); + + /* FormatColls */ + // TXT + mpTextFormatCollTable->push_back(mpDfltTextFormatColl.get()); + // GRF + mpGrfFormatCollTable->push_back(mpDfltGrfFormatColl.get()); + + // Create PageDesc, EmptyPageFormat and ColumnFormat + if (m_PageDescs.empty()) + getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + + // Set to "Empty Page" + mpEmptyPageFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Fixed ) ); + // Set BodyFormat for columns + mpColumnContFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ) ); + + GetDocumentFieldsManager().InitFieldTypes(); + + // Create a default OutlineNumRule (for Filters) + mpOutlineRule = new SwNumRule( SwNumRule::GetOutlineRuleName(), + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode(), + OUTLINE_RULE ); + AddNumRule(mpOutlineRule); + // Counting of phantoms depends on <IsOldNumbering()> + mpOutlineRule->SetCountPhantoms( !GetDocumentSettingManager().get(DocumentSettingId::OLD_NUMBERING) ); + + new SwTextNode( + SwNodeIndex(GetUndoManager().GetUndoNodes().GetEndOfContent()), + mpDfltTextFormatColl.get() ); + new SwTextNode( SwNodeIndex( GetNodes().GetEndOfContent() ), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + + maOLEModifiedIdle.SetPriority( TaskPriority::LOWEST ); + maOLEModifiedIdle.SetInvokeHandler( LINK( this, SwDoc, DoUpdateModifiedOLE )); + maOLEModifiedIdle.SetDebugName( "sw::SwDoc maOLEModifiedIdle" ); + +#if HAVE_FEATURE_DBCONNECTIVITY + // Create DBManager + m_pOwnDBManager.reset(new SwDBManager(this)); + m_pDBManager = m_pOwnDBManager.get(); +#else + m_pDBManager = nullptr; +#endif + + // create TOXTypes + InitTOXTypes(); + + // pass empty item set containing the paragraph's list attributes + // as ignorable items to the stype manager. + { + SfxItemSet aIgnorableParagraphItems( GetAttrPool(), svl::Items<RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1>{}); + mpStyleAccess = createStyleManager( &aIgnorableParagraphItems ); + } + + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + if (bHack) + { + mnRsid = 0; + } + else + { + // Initialize the session id of the current document to a random number + // smaller than 2^21. + mnRsid = comphelper::rng::uniform_uint_distribution(1, (1 << 21) - 1); + } + mnRsidRoot = mnRsid; + + getIDocumentState().ResetModified(); +} + +/** + * Speciality: a member of the class SwDoc is located at + * position 0 in the array of the Format and GDI objects. + * This MUST not be destroyed using 'delete' in any case! + */ +SwDoc::~SwDoc() +{ + // nothing here should create Undo actions! + GetIDocumentUndoRedo().DoUndo(false); + + if (mpDocShell) + { + mpDocShell->SetUndoManager(nullptr); + } + + mpGrammarContact.reset(); + + getIDocumentTimerAccess().StopIdling(); // stop idle timer + + mpURLStateChgd.reset(); + + // Deactivate Undo notification from Draw + if( GetDocumentDrawModelManager().GetDrawModel() ) + { + GetDocumentDrawModelManager().DrawNotifyUndoHdl(); + ClrContourCache(); + } + + m_pPgPViewPrtData.reset(); + + mbDtor = true; + + //Clear the redline table before the nodes array is destroyed + getIDocumentRedlineAccess().GetRedlineTable().DeleteAndDestroyAll(); + getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAndDestroyAll(); + + const sw::UnoCursorHint aHint; + cleanupUnoCursorTable(); + for(const auto& pWeakCursor : mvUnoCursorTable) + { + auto pCursor(pWeakCursor.lock()); + if(pCursor) + pCursor->m_aNotifier.Broadcast(aHint); + } + mpACEWord.reset(); + + // Release the BaseLinks + { + ::sfx2::SvLinkSources aTemp(getIDocumentLinksAdministration().GetLinkManager().GetServers()); + for( const auto& rpLinkSrc : aTemp ) + rpLinkSrc->Closed(); + + if( !getIDocumentLinksAdministration().GetLinkManager().GetLinks().empty() ) + getIDocumentLinksAdministration().GetLinkManager().Remove( 0, getIDocumentLinksAdministration().GetLinkManager().GetLinks().size() ); + } + + // The ChapterNumbers/Numbers need to be deleted before the styles + // or we update all the time! + m_pNodes->m_pOutlineNodes->clear(); + SwNodes & rUndoNodes( GetUndoManager().GetUndoNodes() ); + rUndoNodes.m_pOutlineNodes->clear(); + + mpFootnoteIdxs->clear(); + + // indices could be registered in attributes + m_pUndoManager->DelAllUndoObj(); + + // The BookMarks contain indices to the Content. These must be deleted + // before deleting the Nodes. + mpMarkManager->clearAllMarks(); + + if( mpExtInputRing ) + { + SwPaM* pTmp = mpExtInputRing; + mpExtInputRing = nullptr; + while( pTmp->GetNext() != pTmp ) + delete pTmp->GetNext(); + delete pTmp; + } + + // Old - deletion without a Flag is expensive, because we send a Modify + // aTOXTypes.DeleteAndDestroy( 0, aTOXTypes.Count() ); + { + for( auto n = mpTOXTypes->size(); n; ) + { + (*mpTOXTypes)[ --n ]->SetInDocDTOR(); + (*mpTOXTypes)[ n ].reset(); + } + mpTOXTypes->clear(); + } + mpDefTOXBases.reset(); + + // Any of the FrameFormats can still have indices registered. + // These need to be destroyed now at the latest. + for( SwFrameFormat* pFormat : *mpFrameFormatTable ) + lcl_DelFormatIndices( pFormat ); + for( SwFrameFormat* pFormat : *mpSpzFrameFormatTable ) + lcl_DelFormatIndices( pFormat ); + for( SwSectionFormat* pFormat : *mpSectionFormatTable ) + lcl_DelFormatIndices( pFormat ); + + // The formats/styles that follow depend on the default formats. + // Destroy these only after destroying the FormatIndices, because the content + // of headers/footers has to be deleted as well. If in the headers/footers + // there are still Flys registered at that point, we have a problem. + for( SwPageDesc *pPageDesc : m_PageDescs ) + delete pPageDesc; + m_PageDescs.clear(); + + // Delete content selections. + // Don't wait for the SwNodes dtor to destroy them; so that Formats + // do not have any dependencies anymore. + m_pNodes->DelNodes( SwNodeIndex(*m_pNodes), m_pNodes->Count() ); + rUndoNodes.DelNodes( SwNodeIndex( rUndoNodes ), rUndoNodes.Count() ); + + // Delete Formats, make it permanent some time in the future + + // Delete for Collections + // So that we get rid of the dependencies + mpFootnoteInfo->EndListeningAll(); + mpEndNoteInfo->EndListeningAll(); + + assert(mpDfltTextFormatColl.get() == (*mpTextFormatCollTable)[0] + && "Default-Text-Collection must always be at the start"); + + // Optimization: Based on the fact that Standard is always 2nd in the + // array, we should delete it as the last. With this we avoid + // reparenting the Formats all the time! + if( 2 < mpTextFormatCollTable->size() ) + mpTextFormatCollTable->DeleteAndDestroy(2, mpTextFormatCollTable->size()); + mpTextFormatCollTable->DeleteAndDestroy(1, mpTextFormatCollTable->size()); + mpTextFormatCollTable.reset(); + + assert(mpDfltGrfFormatColl.get() == (*mpGrfFormatCollTable)[0] + && "DefaultGrfCollection must always be at the start"); + + mpGrfFormatCollTable->DeleteAndDestroy(1, mpGrfFormatCollTable->size()); + mpGrfFormatCollTable.reset(); + + // Without explicitly freeing the DocumentDeviceManager + // and relying on the implicit freeing there would be a crash + // due to it happening after SwAttrPool is freed. + m_pDeviceAccess.reset(); + + /* + * DefaultFormats and DefaultFormatCollections (FormatColl) + * are at position 0 of their respective arrays. + * In order to not be deleted by the array's dtor, we remove them + * now. + */ + mpFrameFormatTable->erase( mpFrameFormatTable->begin() ); + mpCharFormatTable->erase( mpCharFormatTable->begin() ); + +#if HAVE_FEATURE_DBCONNECTIVITY + // On load, SwDBManager::setEmbeddedName() may register a data source. + // If we have an embedded one, then sDataSource points to the registered name, so revoke it here. + if (!m_pOwnDBManager->getEmbeddedName().isEmpty() && !maDBData.sDataSource.isEmpty()) + { + // Remove the revoke listener here first, so that we don't remove the data source from the document. + m_pOwnDBManager->releaseRevokeListener(); + SwDBManager::RevokeDataSource(maDBData.sDataSource); + SwDBManager::RevokeDataSource(m_pOwnDBManager->getEmbeddedName()); + } + else if (!m_pOwnDBManager->getEmbeddedName().isEmpty()) + { + // Remove the revoke listener here first, so that we don't remove the data source from the document. + m_pOwnDBManager->releaseRevokeListener(); + // Remove connections which was committed but not used. + m_pOwnDBManager->RevokeNotUsedConnections(); + } + + m_pOwnDBManager.reset(); +#endif + + // All Flys need to be destroyed before the Drawing Model, + // because Flys can still contain DrawContacts, when no + // Layout could be constructed due to a read error. + mpSpzFrameFormatTable->DeleteAndDestroyAll(); + + // Only now destroy the Model, the drawing objects - which are also + // contained in the Undo - need to remove their attributes from the + // Model. Also, DrawContacts could exist before this. + GetDocumentDrawModelManager().ReleaseDrawModel(); + // Destroy DrawModel before the LinkManager, because it's always set + // in the DrawModel. + //The LinkManager gets destroyed automatically with m_pLinksAdministrationManager + + // Clear the Tables before deleting the defaults, or we crash due to + // dependencies on defaults. + mpFrameFormatTable.reset(); + mpSpzFrameFormatTable.reset(); + + mpStyleAccess.reset(); + + mpCharFormatTable.reset(); + mpSectionFormatTable.reset(); + mpTableFrameFormatTable.reset(); + mpDfltTextFormatColl.reset(); + mpDfltGrfFormatColl.reset(); + mpNumRuleTable.reset(); + + disposeXForms(); // #i113606#, dispose the XForms objects + + delete mpNumberFormatter.load(); mpNumberFormatter= nullptr; + mpFootnoteInfo.reset(); + mpEndNoteInfo.reset(); + mpLineNumberInfo.reset(); + mpFootnoteIdxs.reset(); + mpTOXTypes.reset(); + mpEmptyPageFormat.reset(); + mpColumnContFormat.reset(); + mpDfltCharFormat.reset(); + mpDfltFrameFormat.reset(); + mpLayoutCache.reset(); + + SfxItemPool::Free(mpAttrPool); +} + +void SwDoc::SetDocShell( SwDocShell* pDSh ) +{ + if( mpDocShell != pDSh ) + { + if (mpDocShell) + { + mpDocShell->SetUndoManager(nullptr); + } + mpDocShell = pDSh; + if (mpDocShell) + { + mpDocShell->SetUndoManager(& GetUndoManager()); + GetUndoManager().SetDocShell(mpDocShell); + } + + getIDocumentLinksAdministration().GetLinkManager().SetPersist( mpDocShell ); + + // set DocShell pointer also on DrawModel + InitDrawModelAndDocShell(mpDocShell, GetDocumentDrawModelManager().GetDrawModel()); + assert(!GetDocumentDrawModelManager().GetDrawModel() || + GetDocumentDrawModelManager().GetDrawModel()->GetPersist() == GetPersist()); + } +} + +// Convenience method; to avoid excessive includes from docsh.hxx +uno::Reference < embed::XStorage > SwDoc::GetDocStorage() +{ + if( mpDocShell ) + return mpDocShell->GetStorage(); + if( getIDocumentLinksAdministration().GetLinkManager().GetPersist() ) + return getIDocumentLinksAdministration().GetLinkManager().GetPersist()->GetStorage(); + return nullptr; +} + +SfxObjectShell* SwDoc::GetPersist() const +{ + return mpDocShell ? mpDocShell : getIDocumentLinksAdministration().GetLinkManager().GetPersist(); +} + +void SwDoc::ClearDoc() +{ + GetIDocumentUndoRedo().DelAllUndoObj(); + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Deactivate Undo notification from Draw + if( GetDocumentDrawModelManager().GetDrawModel() ) + { + GetDocumentDrawModelManager().DrawNotifyUndoHdl(); + ClrContourCache(); + } + + // if there are still FlyFrames dangling around, delete them too + while ( !mpSpzFrameFormatTable->empty() ) + getIDocumentLayoutAccess().DelLayoutFormat((*mpSpzFrameFormatTable)[mpSpzFrameFormatTable->size()-1]); + assert(!GetDocumentDrawModelManager().GetDrawModel() + || !GetDocumentDrawModelManager().GetDrawModel()->GetPage(0)->GetObjCount()); + + getIDocumentRedlineAccess().GetRedlineTable().DeleteAndDestroyAll(); + getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAndDestroyAll(); + + mpACEWord.reset(); + + // The BookMarks contain indices to the Content. These must be deleted + // before deleting the Nodes. + mpMarkManager->clearAllMarks(); + InitTOXTypes(); + + // create a dummy pagedesc for the layout + SwPageDesc* pDummyPgDsc = MakePageDesc("?DUMMY?"); + + SwNodeIndex aSttIdx( *GetNodes().GetEndOfContent().StartOfSectionNode(), 1 ); + // create the first one over and over again (without attributes/style etc. + SwTextNode* pFirstNd = GetNodes().MakeTextNode( aSttIdx, mpDfltTextFormatColl.get() ); + + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // set the layout to the dummy pagedesc + pFirstNd->SetAttr( SwFormatPageDesc( pDummyPgDsc )); + + SwPosition aPos( *pFirstNd, SwIndex( pFirstNd )); + SwPaM const tmpPaM(aSttIdx, SwNodeIndex(GetNodes().GetEndOfContent())); + ::PaMCorrAbs(tmpPaM, aPos); + } + + GetNodes().Delete( aSttIdx, + GetNodes().GetEndOfContent().GetIndex() - aSttIdx.GetIndex() ); + + // #i62440# + // destruction of numbering rules and creation of new outline rule + // *after* the document nodes are deleted. + mpOutlineRule = nullptr; + for( SwNumRule* pNumRule : *mpNumRuleTable ) + { + getIDocumentListsAccess().deleteListForListStyle(pNumRule->GetName()); + delete pNumRule; + } + mpNumRuleTable->clear(); + maNumRuleMap.clear(); + + // creation of new outline numbering rule + mpOutlineRule = new SwNumRule( SwNumRule::GetOutlineRuleName(), + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode(), + OUTLINE_RULE ); + AddNumRule(mpOutlineRule); + // Counting of phantoms depends on <IsOldNumbering()> + mpOutlineRule->SetCountPhantoms( !GetDocumentSettingManager().get(DocumentSettingId::OLD_NUMBERING) ); + + // remove the dummy pagedesc from the array and delete all the old ones + size_t nDummyPgDsc = 0; + if (FindPageDesc(pDummyPgDsc->GetName(), &nDummyPgDsc)) + m_PageDescs.erase( nDummyPgDsc ); + for( SwPageDesc *pPageDesc : m_PageDescs ) + delete pPageDesc; + m_PageDescs.clear(); + + // Delete for Collections + // So that we get rid of the dependencies + mpFootnoteInfo->EndListeningAll(); + mpEndNoteInfo->EndListeningAll(); + + // Optimization: Based on the fact that Standard is always 2nd in the + // array, we should delete it as the last. With this we avoid + // reparenting the Formats all the time! + if( 2 < mpTextFormatCollTable->size() ) + mpTextFormatCollTable->DeleteAndDestroy(2, mpTextFormatCollTable->size()); + mpTextFormatCollTable->DeleteAndDestroy(1, mpTextFormatCollTable->size()); + mpGrfFormatCollTable->DeleteAndDestroy(1, mpGrfFormatCollTable->size()); + mpCharFormatTable->DeleteAndDestroy(1, mpCharFormatTable->size()); + + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // search the FrameFormat of the root frm. This is not allowed to delete + mpFrameFormatTable->erase( getIDocumentLayoutAccess().GetCurrentViewShell()->GetLayout()->GetFormat() ); + mpFrameFormatTable->DeleteAndDestroyAll( true ); + mpFrameFormatTable->push_back( getIDocumentLayoutAccess().GetCurrentViewShell()->GetLayout()->GetFormat() ); + } + else + mpFrameFormatTable->DeleteAndDestroyAll( true ); + + GetDocumentFieldsManager().ClearFieldTypes(); + + delete mpNumberFormatter.load(); mpNumberFormatter= nullptr; + + getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + pFirstNd->ChgFormatColl( getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + nDummyPgDsc = m_PageDescs.size(); + m_PageDescs.push_back( pDummyPgDsc ); + // set the layout back to the new standard pagedesc + pFirstNd->ResetAllAttr(); + // delete now the dummy pagedesc + DelPageDesc( nDummyPgDsc ); +} + +void SwDoc::SetPreviewPrtData( const SwPagePreviewPrtData* pNew ) +{ + if( pNew ) + { + if (m_pPgPViewPrtData) + { + *m_pPgPViewPrtData = *pNew; + } + else + { + m_pPgPViewPrtData.reset(new SwPagePreviewPrtData(*pNew)); + } + } + else if (m_pPgPViewPrtData) + { + m_pPgPViewPrtData.reset(); + } + getIDocumentState().SetModified(); +} + +void SwDoc::SetOLEObjModified() +{ + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) maOLEModifiedIdle.Start(); +} + +/** SwDoc: Reading and writing of the layout cache. */ +void SwDoc::ReadLayoutCache( SvStream& rStream ) +{ + if( !mpLayoutCache ) + mpLayoutCache.reset( new SwLayoutCache() ); + if( !mpLayoutCache->IsLocked() ) + { + mpLayoutCache->GetLockCount() |= 0x8000; + mpLayoutCache->Read( rStream ); + mpLayoutCache->GetLockCount() &= 0x7fff; + } +} + +void SwDoc::WriteLayoutCache( SvStream& rStream ) +{ + SwLayoutCache::Write( rStream, *this ); +} + +IGrammarContact* getGrammarContact( const SwTextNode& rTextNode ) +{ + const SwDoc* pDoc = rTextNode.GetDoc(); + if( !pDoc || pDoc->IsInDtor() ) + return nullptr; + return pDoc->getGrammarContact(); +} + +::sfx2::IXmlIdRegistry& +SwDoc::GetXmlIdRegistry() +{ + // UGLY: this relies on SetClipBoard being called before GetXmlIdRegistry! + if (!m_pXmlIdRegistry) + { + m_pXmlIdRegistry.reset( ::sfx2::createXmlIdRegistry( IsClipBoard() ) ); + } + return *m_pXmlIdRegistry; +} + +void SwDoc::InitTOXTypes() +{ + ShellResource* pShellRes = SwViewShell::GetShellRes(); + SwTOXType* pNew = new SwTOXType(*this, TOX_CONTENT, pShellRes->aTOXContentName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_INDEX, pShellRes->aTOXIndexName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_USER, pShellRes->aTOXUserName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_ILLUSTRATIONS, pShellRes->aTOXIllustrationsName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_OBJECTS, pShellRes->aTOXObjectsName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_TABLES, pShellRes->aTOXTablesName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_AUTHORITIES, pShellRes->aTOXAuthoritiesName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_CITATION, pShellRes->aTOXCitationName); + mpTOXTypes->emplace_back( pNew ); +} + +void SwDoc::ReplaceDefaults(const SwDoc& rSource) +{ + // copy property defaults + const sal_uInt16 aRangeOfDefaults[] = + { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_PARATR_BEGIN, RES_PARATR_END-1, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + XATTR_START, XATTR_END-1, + 0 + }; + + SfxItemSet aNewDefaults(GetAttrPool(), aRangeOfDefaults); + + for (auto nRange = 0; aRangeOfDefaults[nRange] != 0; nRange += 2) + { + for (sal_uInt16 nWhich = aRangeOfDefaults[nRange]; + nWhich <= aRangeOfDefaults[nRange + 1]; ++nWhich) + { + const SfxPoolItem& rSourceAttr = + rSource.mpAttrPool->GetDefaultItem(nWhich); + if (rSourceAttr != mpAttrPool->GetDefaultItem(nWhich)) + aNewDefaults.Put(rSourceAttr); + } + } + + if (aNewDefaults.Count()) + SetDefault(aNewDefaults); +} + +void SwDoc::ReplaceCompatibilityOptions(const SwDoc& rSource) +{ + m_pDocumentSettingManager->ReplaceCompatibilityOptions(rSource.GetDocumentSettingManager()); +} + +#ifdef DBG_UTIL +#define CNTNT_DOC( doc ) \ + ((doc)->GetNodes().GetEndOfContent().GetIndex() - (doc)->GetNodes().GetEndOfExtras().GetIndex() - 2) +#define CNTNT_IDX( idx ) \ + ((idx).GetNode().GetIndex() - GetNodes().GetEndOfExtras().GetIndex() - 1) +#endif + +SfxObjectShell* SwDoc::CreateCopy( bool bCallInitNew, bool bEmpty ) const +{ + SAL_INFO( "sw.pageframe", "(SwDoc::CreateCopy in" ); + rtl::Reference<SwDoc> xRet( new SwDoc ); + + // we have to use pointer here, since the callee has to decide whether + // SfxObjectShellLock or SfxObjectShellRef should be used sometimes the + // object will be returned with refcount set to 0 ( if no DoInitNew is done ) + SfxObjectShell* pRetShell = new SwDocShell( xRet.get(), SfxObjectCreateMode::STANDARD ); + if( bCallInitNew ) + { + // it could happen that DoInitNew creates model, + // that increases the refcount of the object + pRetShell->DoInitNew(); + } + + xRet->ReplaceDefaults(*this); + + xRet->ReplaceCompatibilityOptions(*this); + + xRet->ReplaceStyles(*this); + + if( !bEmpty ) + { +#ifdef DBG_UTIL + SAL_INFO( "sw.createcopy", "CC-Nd-Src: " << CNTNT_DOC( this ) ); + SAL_INFO( "sw.createcopy", "CC-Nd: " << CNTNT_DOC( xRet ) ); +#endif + xRet->AppendDoc(*this, 0, bCallInitNew, 0, 0); +#ifdef DBG_UTIL + SAL_INFO( "sw.createcopy", "CC-Nd: " << CNTNT_DOC( xRet ) ); +#endif + } + + // remove the temporary shell if it is there as it was done before + xRet->SetTmpDocShell( nullptr ); + + SAL_INFO( "sw.pageframe", "SwDoc::CreateCopy out)" ); + return pRetShell; +} + +// save bulk letters as single documents +static OUString lcl_FindUniqueName(SwWrtShell* pTargetShell, const OUString& rStartingPageDesc, sal_uLong nDocNo ) +{ + do + { + OUString sTest = rStartingPageDesc + OUString::number( nDocNo ); + if( !pTargetShell->FindPageDescByName( sTest ) ) + return sTest; + ++nDocNo; + } + while( true ); +} + +/** Returns whether the passed SwPageDesc& or any of its (transitive) follows + contains a header or footer. */ +static bool lcl_PageDescOrFollowContainsHeaderFooter(const SwPageDesc& rPageDesc) +{ + // remember already checked page descs to avoid cycle + o3tl::sorted_vector<const SwPageDesc*> aCheckedPageDescs; + const SwPageDesc* pCurPageDesc = &rPageDesc; + while (aCheckedPageDescs.count(pCurPageDesc) == 0) + { + const SwFrameFormat& rMaster = pCurPageDesc->GetMaster(); + if (rMaster.GetHeader().IsActive() || rMaster.GetFooter().IsActive()) + return true; + + aCheckedPageDescs.insert(pCurPageDesc); + pCurPageDesc = pCurPageDesc->GetFollow(); + } + return false; +} + +static void lcl_CopyFollowPageDesc( + SwWrtShell& rTargetShell, + const SwPageDesc& rSourcePageDesc, + const SwPageDesc& rTargetPageDesc, + const sal_uLong nDocNo ) +{ + //now copy the follow page desc, too + // note: these may at any point form a cycle, so a loop is needed and it + // must be detected that the last iteration closes the cycle and doesn't + // copy the first page desc of the cycle again. + std::map<OUString, OUString> followMap{ { rSourcePageDesc.GetName(), rTargetPageDesc.GetName() } }; + SwPageDesc const* pCurSourcePageDesc(&rSourcePageDesc); + SwPageDesc const* pCurTargetPageDesc(&rTargetPageDesc); + do + { + const SwPageDesc* pFollowPageDesc = pCurSourcePageDesc->GetFollow(); + OUString sFollowPageDesc = pFollowPageDesc->GetName(); + if (sFollowPageDesc == pCurSourcePageDesc->GetName()) + { + break; + } + SwDoc* pTargetDoc = rTargetShell.GetDoc(); + SwPageDesc* pTargetFollowPageDesc(nullptr); + auto const itMapped(followMap.find(sFollowPageDesc)); + if (itMapped == followMap.end()) + { + OUString sNewFollowPageDesc = lcl_FindUniqueName(&rTargetShell, sFollowPageDesc, nDocNo); + pTargetFollowPageDesc = pTargetDoc->MakePageDesc(sNewFollowPageDesc); + pTargetDoc->CopyPageDesc(*pFollowPageDesc, *pTargetFollowPageDesc, false); + } + else + { + pTargetFollowPageDesc = pTargetDoc->FindPageDesc(itMapped->second); + } + SwPageDesc aDesc(*pCurTargetPageDesc); + aDesc.SetFollow(pTargetFollowPageDesc); + pTargetDoc->ChgPageDesc(pCurTargetPageDesc->GetName(), aDesc); + if (itMapped != followMap.end()) + { + break; // was already copied + } + pCurSourcePageDesc = pCurSourcePageDesc->GetFollow(); + pCurTargetPageDesc = pTargetFollowPageDesc; + followMap[pCurSourcePageDesc->GetName()] = pCurTargetPageDesc->GetName(); + } + while (true); +} + +// appends all pages of source SwDoc - based on SwFEShell::Paste( SwDoc* ) +SwNodeIndex SwDoc::AppendDoc(const SwDoc& rSource, sal_uInt16 const nStartPageNumber, + bool const bDeletePrevious, int pageOffset, const sal_uLong nDocNo) +{ + SAL_INFO( "sw.pageframe", "(SwDoc::AppendDoc in " << bDeletePrevious ); + + // GetEndOfExtras + 1 = StartOfContent == no content node! + // This ensures it won't be merged in the SwTextNode at the position. + SwNodeIndex aSourceIdx( rSource.GetNodes().GetEndOfExtras(), 1 ); + // CopyRange works on the range a [mark, point[ and considers an + // index < point outside the selection. + // @see IDocumentContentOperations::CopyRange + SwNodeIndex aSourceEndIdx( rSource.GetNodes().GetEndOfContent(), 0 ); + SwPaM aCpyPam( aSourceIdx, aSourceEndIdx ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceIdx.GetNode().GetIndex() ); + aSourceIdx++; + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceIdx.GetNode().GetIndex() ); + if ( aSourceIdx.GetNode().GetNodeType() != SwNodeType::End ) { + aSourceIdx++; + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceIdx.GetNode().GetNodeType()) << std::dec ); + aSourceIdx--; + } + aSourceIdx--; + SAL_INFO( "sw.docappend", ".." ); + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceEndIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceEndIdx.GetNode().GetIndex() ); + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceEndIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceEndIdx.GetNode().GetIndex() ); + SAL_INFO( "sw.docappend", "Src-Nd: " << CNTNT_DOC( &rSource ) ); + SAL_INFO( "sw.docappend", "Nd: " << CNTNT_DOC( this ) ); +#endif + + SwWrtShell* pTargetShell = GetDocShell()->GetWrtShell(); + SwPageDesc* pTargetPageDesc = nullptr; + + if ( pTargetShell ) { +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Has target write shell" ); +#endif + pTargetShell->StartAllAction(); + + if( nDocNo > 0 ) + { + // #i72517# put the styles to the target document + // if the source uses headers or footers the target document + // needs inidividual page styles + const SwWrtShell *pSourceShell = rSource.GetDocShell()->GetWrtShell(); + const SwPageDesc& rSourcePageDesc = pSourceShell->GetPageDesc( + pSourceShell->GetCurPageDesc()); + const OUString sStartingPageDesc = rSourcePageDesc.GetName(); + const bool bPageStylesWithHeaderFooter = lcl_PageDescOrFollowContainsHeaderFooter(rSourcePageDesc); + if( bPageStylesWithHeaderFooter ) + { + // create a new pagestyle + // copy the pagedesc from the current document to the new + // document and change the name of the to-be-applied style + OUString sNewPageDescName = lcl_FindUniqueName(pTargetShell, sStartingPageDesc, nDocNo ); + pTargetPageDesc = MakePageDesc( sNewPageDescName ); + if( pTargetPageDesc ) + { + CopyPageDesc( rSourcePageDesc, *pTargetPageDesc, false ); + lcl_CopyFollowPageDesc( *pTargetShell, rSourcePageDesc, *pTargetPageDesc, nDocNo ); + } + } + else + pTargetPageDesc = pTargetShell->FindPageDescByName( sStartingPageDesc ); + } + + // Otherwise we have to handle SwPlaceholderNodes as first node + if ( pTargetPageDesc ) + { + SwNodeIndex aBreakIdx( GetNodes().GetEndOfContent(), -1 ); + SwPosition aBreakPos( aBreakIdx ); + // InsertPageBreak just works on SwTextNode nodes, so make + // sure the last node is one! + bool bIsTextNode = aBreakIdx.GetNode().IsTextNode(); + if ( !bIsTextNode ) + getIDocumentContentOperations().AppendTextNode( aBreakPos ); + const OUString name = pTargetPageDesc->GetName(); + pTargetShell->InsertPageBreak( &name, nStartPageNumber ); + if ( !bIsTextNode ) + { + pTargetShell->SttEndDoc( false ); + --aBreakIdx; + GetNodes().Delete( aBreakIdx ); + } + + // There is now a new empty text node on the new page. If it has + // any marks, those are from the previous page: move them back + // there, otherwise later we can't delete that empty text node. + SwNodeIndex aNodeIndex(GetNodes().GetEndOfContent(), -1); + if (SwTextNode* pTextNode = aNodeIndex.GetNode().GetTextNode()) + { + // Position of the last paragraph on the previous page. + --aNodeIndex; + SwPaM aPaM(aNodeIndex); + // Collect the marks starting or ending at this text node. + o3tl::sorted_vector<sw::mark::IMark*> aSeenMarks; + IDocumentMarkAccess* pMarkAccess = getIDocumentMarkAccess(); + for (const SwIndex* pIndex = pTextNode->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + { + sw::mark::IMark* pMark = const_cast<sw::mark::IMark*>(pIndex->GetMark()); + if (!pMark) + continue; + if (!aSeenMarks.insert(pMark).second) + continue; + } + // And move them back. + for (sw::mark::IMark* pMark : aSeenMarks) + pMarkAccess->repositionMark(pMark, aPaM); + } + + // Flush the page break, if we want to keep it + if ( !bDeletePrevious ) + { + SAL_INFO( "sw.pageframe", "(Flush pagebreak AKA EndAllAction" ); + pTargetShell->EndAllAction(); + SAL_INFO( "sw.pageframe", "Flush changes AKA EndAllAction)" ); + pTargetShell->StartAllAction(); + } + } + } +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Nd: " << CNTNT_DOC( this ) ); +#endif + + // -1, otherwise aFixupIdx would move to new EOC + SwNodeIndex aFixupIdx( GetNodes().GetEndOfContent(), -1 ); + + // append at the end of document / content + SwNodeIndex aTargetIdx( GetNodes().GetEndOfContent() ); + SwPaM aInsertPam( aTargetIdx ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Pam-Nd: " << aCpyPam.GetNode().GetIndex() - aCpyPam.GetNode( false ).GetIndex() + 1 + << " (0x" << std::hex << static_cast<int>(aCpyPam.GetNode( false ).GetNodeType()) << std::dec + << " " << aCpyPam.GetNode( false ).GetIndex() + << " - 0x" << std::hex << static_cast<int>(aCpyPam.GetNode().GetNodeType()) << std::dec + << " " << aCpyPam.GetNode().GetIndex() << ")" ); +#endif + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSGLOSSARY, nullptr ); + getIDocumentFieldsAccess().LockExpFields(); + + // Position where the appended doc starts. Will be filled in later. + // Initially uses GetEndOfContent() because SwNodeIndex has no default ctor. + SwNodeIndex aStartAppendIndex( GetNodes().GetEndOfContent() ); + + { + // ** + // ** refer to SwFEShell::Paste, if you change the following code ** + // ** + + SwPosition& rInsPos = *aInsertPam.GetPoint(); + + { + SwNodeIndex aIndexBefore(rInsPos.nNode); + + aIndexBefore--; +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "CopyRange In: " << CNTNT_DOC( this ) ); +#endif + rSource.getIDocumentContentOperations().CopyRange(aCpyPam, rInsPos, SwCopyFlags::CopyAll|SwCopyFlags::CheckPosInFly); + // Note: aCpyPam is invalid now +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "CopyRange Out: " << CNTNT_DOC( this ) ); +#endif + + ++aIndexBefore; + SwPaM aPaM(SwPosition(aIndexBefore), + SwPosition(rInsPos.nNode)); + + aPaM.GetDoc()->MakeUniqueNumRules(aPaM); + + // Update the rsid of each pasted text node + SwNodes &rDestNodes = GetNodes(); + sal_uLong const nEndIdx = aPaM.End()->nNode.GetIndex(); + + for (sal_uLong nIdx = aPaM.Start()->nNode.GetIndex(); + nIdx <= nEndIdx; ++nIdx) + { + SwTextNode *const pTextNode = rDestNodes[nIdx]->GetTextNode(); + if ( pTextNode ) + UpdateParRsid( pTextNode ); + } + } + + { + sal_uLong iDelNodes = 0; + SwNodeIndex aDelIdx( aFixupIdx ); + + // we just need to set the new page description and reset numbering + // this keeps all other settings as in the pasted document + if ( nStartPageNumber || pTargetPageDesc ) { + std::unique_ptr<SfxPoolItem> pNewItem; + SwTextNode *aTextNd = nullptr; + SwFormat *pFormat = nullptr; + + // find the first node allowed to contain a RES_PAGEDESC + while (true) { + aFixupIdx++; + + SwNode &node = aFixupIdx.GetNode(); + if ( node.IsTextNode() ) { + // every document contains at least one text node! + aTextNd = node.GetTextNode(); + pNewItem.reset(aTextNd->GetAttr( RES_PAGEDESC ).Clone()); + break; + } + else if ( node.IsTableNode() ) { + pFormat = node.GetTableNode()->GetTable().GetFrameFormat(); + pNewItem.reset(pFormat->GetFormatAttr( RES_PAGEDESC ).Clone()); + break; + } + } + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Idx Del " << CNTNT_IDX( aDelIdx ) ); + SAL_INFO( "sw.docappend", "Idx Fix " << CNTNT_IDX( aFixupIdx ) ); +#endif + // just update the original instead of overwriting + SwFormatPageDesc *aDesc = static_cast< SwFormatPageDesc* >( pNewItem.get() ); +#ifdef DBG_UTIL + if ( aDesc->GetPageDesc() ) + SAL_INFO( "sw.docappend", "PD Update " << aDesc->GetPageDesc()->GetName() ); + else + SAL_INFO( "sw.docappend", "PD New" ); +#endif + if ( nStartPageNumber ) + aDesc->SetNumOffset( nStartPageNumber ); + if ( pTargetPageDesc ) + aDesc->RegisterToPageDesc( *pTargetPageDesc ); + if ( aTextNd ) + aTextNd->SetAttr( *aDesc ); + else + pFormat->SetFormatAttr( *aDesc ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Idx " << CNTNT_IDX( aDelIdx ) ); +#endif + iDelNodes++; + } + + if ( bDeletePrevious ) + iDelNodes++; + + if ( iDelNodes ) { + // delete leading empty page(s), e.g. from InsertPageBreak or + // new SwDoc. this has to be done before copying the page bound + // frames, otherwise the drawing layer gets confused. + if ( pTargetShell ) + pTargetShell->SttEndDoc( false ); + aDelIdx -= iDelNodes - 1; +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "iDelNodes: " << iDelNodes + << " Idx: " << aDelIdx.GetNode().GetIndex() + << " EOE: " << GetNodes().GetEndOfExtras().GetIndex() ); +#endif + GetNodes().Delete( aDelIdx, iDelNodes ); + aStartAppendIndex = aFixupIdx; + } + else + { + aStartAppendIndex = aFixupIdx; + ++aStartAppendIndex; + } + } + + // finally copy page bound frames + for ( auto pCpyFormat : *rSource.GetSpzFrameFormats() ) + { + const SwFrameFormat& rCpyFormat = *pCpyFormat; + SwFormatAnchor aAnchor( rCpyFormat.GetAnchor() ); + if (RndStdIds::FLY_AT_PAGE != aAnchor.GetAnchorId()) + continue; + SAL_INFO( "sw.docappend", "PaAn: " << aAnchor.GetPageNum() + << " => " << aAnchor.GetPageNum() + pageOffset ); + if ( pageOffset != 0 ) + aAnchor.SetPageNum( aAnchor.GetPageNum() + pageOffset ); + getIDocumentLayoutAccess().CopyLayoutFormat( rCpyFormat, aAnchor, true, true ); + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSGLOSSARY, nullptr ); + + getIDocumentFieldsAccess().UnlockExpFields(); + getIDocumentFieldsAccess().UpdateFields(false); + + if ( pTargetShell ) + pTargetShell->EndAllAction(); + + SAL_INFO( "sw.pageframe", "SwDoc::AppendDoc out)" ); + return aStartAppendIndex; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docnum.cxx b/sw/source/core/doc/docnum.cxx new file mode 100644 index 000000000..c16ffcdd0 --- /dev/null +++ b/sw/source/core/doc/docnum.cxx @@ -0,0 +1,2622 @@ +/* -*- 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 <hintids.hxx> +#include <editeng/lrspitem.hxx> +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentListsAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <UndoCore.hxx> +#include <UndoRedline.hxx> +#include <UndoNumbering.hxx> +#include <swundo.hxx> +#include <SwUndoFmt.hxx> +#include <rolbck.hxx> +#include <paratr.hxx> +#include <docary.hxx> +#include <mvsave.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <redline.hxx> +#include <strings.hrc> +#include <SwNodeNum.hxx> +#include <list.hxx> +#include <calbck.hxx> +#include <comphelper/string.hxx> +#include <comphelper/random.hxx> +#include <o3tl/safeint.hxx> +#include <tools/datetimeutils.hxx> + +#include <map> +#include <stdlib.h> + + +namespace { + void lcl_ResetIndentAttrs(SwDoc *pDoc, const SwPaM &rPam, sal_uInt16 marker, + SwRootFrame const*const pLayout) + { + std::set<sal_uInt16> aResetAttrsArray; + aResetAttrsArray.insert( marker ); + // #i114929# + // On a selection setup a corresponding Point-and-Mark in order to get + // the indentation attribute reset on all paragraphs touched by the selection + if ( rPam.HasMark() && + rPam.End()->nNode.GetNode().GetTextNode() ) + { + SwPaM aPam( rPam.Start()->nNode, + rPam.End()->nNode ); + aPam.Start()->nContent = 0; + aPam.End()->nContent = rPam.End()->nNode.GetNode().GetTextNode()->Len(); + pDoc->ResetAttrs( aPam, false, aResetAttrsArray, true, pLayout ); + } + else + { + pDoc->ResetAttrs( rPam, false, aResetAttrsArray, true, pLayout ); + } + } + + void ExpandPamForParaPropsNodes(SwPaM& rPam, SwRootFrame const*const pLayout) + { + if (pLayout) + { // ensure that selection from the Shell includes the para-props node + // to which the attributes should be applied + if (rPam.GetPoint()->nNode.GetNode().IsTextNode()) + { + rPam.GetPoint()->nNode = *sw::GetParaPropsNode(*pLayout, rPam.GetPoint()->nNode); + rPam.GetPoint()->nContent.Assign(rPam.GetPoint()->nNode.GetNode().GetContentNode(), 0); + } + if (rPam.GetMark()->nNode.GetNode().IsTextNode()) + { + rPam.GetMark()->nNode = *sw::GetParaPropsNode(*pLayout, rPam.GetMark()->nNode); + rPam.GetMark()->nContent.Assign(rPam.GetMark()->nNode.GetNode().GetContentNode(), 0); + } + } + } +} + +static sal_uInt8 GetUpperLvlChg( sal_uInt8 nCurLvl, sal_uInt8 nLevel, sal_uInt16 nMask ) +{ + if( 1 < nLevel ) + { + if( nCurLvl + 1 >= nLevel ) + nCurLvl -= nLevel - 1; + else + nCurLvl = 0; + } + return static_cast<sal_uInt8>((nMask - 1) & ~(( 1 << nCurLvl ) - 1)); +} + +void SwDoc::SetOutlineNumRule( const SwNumRule& rRule ) +{ + if( mpOutlineRule ) + (*mpOutlineRule) = rRule; + else + { + mpOutlineRule = new SwNumRule( rRule ); + + AddNumRule(mpOutlineRule); // #i36749# + } + + mpOutlineRule->SetRuleType( OUTLINE_RULE ); + mpOutlineRule->SetName(SwNumRule::GetOutlineRuleName(), getIDocumentListsAccess()); + + // assure that the outline numbering rule is an automatic rule + mpOutlineRule->SetAutoRule( true ); + + // test whether the optional CharFormats are defined in this Document + mpOutlineRule->CheckCharFormats( this ); + + // notify text nodes, which are registered at the outline style, about the + // changed outline style + SwNumRule::tTextNodeList aTextNodeList; + mpOutlineRule->GetTextNodeList( aTextNodeList ); + for ( SwTextNode* pTextNd : aTextNodeList ) + { + pTextNd->NumRuleChgd(); + + // assure that list level corresponds to outline level + if ( pTextNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle() && + pTextNd->GetAttrListLevel() != pTextNd->GetTextColl()->GetAssignedOutlineStyleLevel() ) + { + pTextNd->SetAttrListLevel( pTextNd->GetTextColl()->GetAssignedOutlineStyleLevel() ); + } + } + + PropagateOutlineRule(); + mpOutlineRule->SetInvalidRule(true); + UpdateNumRule(); + + // update if we have foot notes && numbering by chapter + if( !GetFootnoteIdxs().empty() && FTNNUM_CHAPTER == GetFootnoteInfo().m_eNum ) + GetFootnoteIdxs().UpdateAllFootnote(); + + getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + getIDocumentState().SetModified(); +} + +void SwDoc::PropagateOutlineRule() +{ + for (auto pColl : *mpTextFormatCollTable) + { + if(pColl->IsAssignedToListLevelOfOutlineStyle()) + { + // Check only the list style, which is set at the paragraph style + const SwNumRuleItem & rCollRuleItem = pColl->GetNumRule( false ); + + if ( rCollRuleItem.GetValue().isEmpty() ) + { + SwNumRule * pMyOutlineRule = GetOutlineNumRule(); + + if (pMyOutlineRule) + { + SwNumRuleItem aNumItem( pMyOutlineRule->GetName() ); + + pColl->SetFormatAttr(aNumItem); + } + } + } + } +} + +// Increase/Decrease +bool SwDoc::OutlineUpDown(const SwPaM& rPam, short nOffset, + SwRootFrame const*const pLayout) +{ + if( GetNodes().GetOutLineNds().empty() || !nOffset ) + return false; + + // calculate the range + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + const SwOutlineNodes& rOutlNds = GetNodes().GetOutLineNds(); + const SwNodePtr pSttNd = &aPam.Start()->nNode.GetNode(); + const SwNodePtr pEndNd = &aPam.End()->nNode.GetNode(); + SwOutlineNodes::size_type nSttPos, nEndPos; + + if( !rOutlNds.Seek_Entry( pSttNd, &nSttPos ) && + !nSttPos-- ) + // we're not in an "Outline section" + return false; + + if( rOutlNds.Seek_Entry( pEndNd, &nEndPos ) ) + ++nEndPos; + + // We now have the wanted range in the OutlineNodes array, + // so check now if we're not invalidating sublevels + // (stepping over the limits) + + // Here we go: + // 1. Create the style array: + SwTextFormatColl* aCollArr[ MAXLEVEL ]; + memset( aCollArr, 0, sizeof( SwTextFormatColl* ) * MAXLEVEL ); + + for( auto pTextFormatColl : *mpTextFormatCollTable ) + { + if (pTextFormatColl->IsAssignedToListLevelOfOutlineStyle()) + { + const int nLevel = pTextFormatColl->GetAssignedOutlineStyleLevel(); + aCollArr[ nLevel ] = pTextFormatColl; + } + } + + int n; + + /* Find the last occupied level (backward). */ + for (n = MAXLEVEL - 1; n > 0; n--) + { + if (aCollArr[n] != nullptr) + break; + } + + /* If an occupied level is found, choose next level (which IS + unoccupied) until a valid level is found. If no occupied level + was found n is 0 and aCollArr[0] is 0. In this case no demoting + is possible. */ + if (aCollArr[n] != nullptr) + { + while (n < MAXLEVEL - 1) + { + n++; + + SwTextFormatColl *aTmpColl = + getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + n)); + + if( aTmpColl->IsAssignedToListLevelOfOutlineStyle() && + aTmpColl->GetAssignedOutlineStyleLevel() == n ) + { + aCollArr[n] = aTmpColl; + break; + } + } + } + + /* Find the first occupied level (forward). */ + for (n = 0; n < MAXLEVEL - 1; n++) + { + if (aCollArr[n] != nullptr) + break; + } + + /* If an occupied level is found, choose previous level (which IS + unoccupied) until a valid level is found. If no occupied level + was found n is MAXLEVEL - 1 and aCollArr[MAXLEVEL - 1] is 0. In + this case no demoting is possible. */ + if (aCollArr[n] != nullptr) + { + while (n > 0) + { + n--; + + SwTextFormatColl *aTmpColl = + getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + n)); + + if( aTmpColl->IsAssignedToListLevelOfOutlineStyle() && + aTmpColl->GetAssignedOutlineStyleLevel() == n ) + { + aCollArr[n] = aTmpColl; + break; + } + } + } + + /* --> #i13747# + + Build a move table that states from which level to which other level + an outline will be moved. + + the move table: + aMoveArr[n] = m: replace aCollArr[n] with aCollArr[m] + */ + int aMoveArr[MAXLEVEL]; + int nStep; // step size for searching in aCollArr: -1 or 1 + int nNum; // amount of steps for stepping in aCollArr + + if (nOffset < 0) + { + nStep = -1; + nNum = -nOffset; + } + else + { + nStep = 1; + nNum = nOffset; + } + + /* traverse aCollArr */ + for (n = 0; n < MAXLEVEL; n++) + { + /* If outline level n has an assigned paragraph style step + nNum steps forwards (nStep == 1) or backwards (nStep == + -1). One step is to go to the next non-null entry in + aCollArr in the selected direction. If nNum steps were + possible write the index of the entry found to aCollArr[n], + i.e. outline level n will be replaced by outline level + aCollArr[n]. + + If outline level n has no assigned paragraph style + aMoveArr[n] is set to -1. + */ + if (aCollArr[n] != nullptr) + { + int m = n; + int nCount = nNum; + + while (nCount > 0 && m + nStep >= 0 && m + nStep < MAXLEVEL) + { + m += nStep; + + if (aCollArr[m] != nullptr) + nCount--; + } + + if (nCount == 0) + aMoveArr[n] = m; + else + aMoveArr[n] = -1; + } + else + aMoveArr[n] = -1; + } + + /* If moving of the outline levels is applicable, i.e. for all + outline levels occurring in the document there has to be a valid + target outline level implied by aMoveArr. */ + bool bMoveApplicable = true; + for (auto i = nSttPos; i < nEndPos; ++i) + { + SwTextNode* pTextNd = rOutlNds[ i ]->GetTextNode(); + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd)) + { + continue; + } + SwTextFormatColl* pColl = pTextNd->GetTextColl(); + + if( pColl->IsAssignedToListLevelOfOutlineStyle() ) + { + const int nLevel = pColl->GetAssignedOutlineStyleLevel(); + if (aMoveArr[nLevel] == -1) + bMoveApplicable = false; + } + + // Check on outline level attribute of text node, if text node is + // not an outline via a to outline style assigned paragraph style. + else + { + const int nNewOutlineLevel = pTextNd->GetAttrOutlineLevel() + nOffset; + if ( nNewOutlineLevel < 1 || nNewOutlineLevel > MAXLEVEL ) + { + bMoveApplicable = false; + } + } + } + + if (! bMoveApplicable ) + return false; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo(SwUndoId::OUTLINE_LR, nullptr); + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoOutlineLeftRight>(aPam, nOffset) ); + } + + // 2. Apply the new style to all Nodes + for (auto i = nSttPos; i < nEndPos; ++i) + { + SwTextNode* pTextNd = rOutlNds[ i ]->GetTextNode(); + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd)) + { + continue; + } + SwTextFormatColl* pColl = pTextNd->GetTextColl(); + + if( pColl->IsAssignedToListLevelOfOutlineStyle() ) + { + const int nLevel = pColl->GetAssignedOutlineStyleLevel(); + + OSL_ENSURE(aMoveArr[nLevel] >= 0, + "move table: current TextColl not found when building table!"); + + if (nLevel < MAXLEVEL && aMoveArr[nLevel] >= 0) + { + pColl = aCollArr[ aMoveArr[nLevel] ]; + + if (pColl != nullptr) + pTextNd->ChgFormatColl( pColl ); + } + + } + else if( pTextNd->GetAttrOutlineLevel() > 0) + { + int nLevel = pTextNd->GetAttrOutlineLevel() + nOffset; + if( 0 <= nLevel && nLevel <= MAXLEVEL) + pTextNd->SetAttrOutlineLevel( nLevel ); + + } + // Undo ??? + } + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().EndUndo(SwUndoId::OUTLINE_LR, nullptr); + } + + ChkCondColls(); + getIDocumentState().SetModified(); + + return true; +} + +// Move up/down +bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type nOffset ) +{ + // Do not move to special sections in the nodes array + const SwPosition& rStt = *rPam.Start(), + & rEnd = &rStt == rPam.GetPoint() ? *rPam.GetMark() + : *rPam.GetPoint(); + if( GetNodes().GetOutLineNds().empty() || !nOffset || + (rStt.nNode.GetIndex() < GetNodes().GetEndOfExtras().GetIndex()) || + (rEnd.nNode.GetIndex() < GetNodes().GetEndOfExtras().GetIndex())) + { + return false; + } + + SwOutlineNodes::size_type nCurrentPos = 0; + SwNodeIndex aSttRg( rStt.nNode ), aEndRg( rEnd.nNode ); + + int nOutLineLevel = MAXLEVEL; + SwNode* pSrch = &aSttRg.GetNode(); + + if( pSrch->IsTextNode()) + nOutLineLevel = static_cast<sal_uInt8>(pSrch->GetTextNode()->GetAttrOutlineLevel()-1); + SwNode* pEndSrch = &aEndRg.GetNode(); + if( !GetNodes().GetOutLineNds().Seek_Entry( pSrch, &nCurrentPos ) ) + { + if( !nCurrentPos ) + return false; // Promoting or demoting before the first outline => no. + if( --nCurrentPos ) + aSttRg = *GetNodes().GetOutLineNds()[ nCurrentPos ]; + else if( 0 > nOffset ) + return false; // Promoting at the top of document?! + else + aSttRg = *GetNodes().GetEndOfContent().StartOfSectionNode(); + } + SwOutlineNodes::size_type nTmpPos = 0; + // If the given range ends at an outlined text node we have to decide if it has to be a part of + // the moving range or not. Normally it will be a sub outline of our chapter + // and has to be moved, too. But if the chapter ends with a table(or a section end), + // the next text node will be chosen and this could be the next outline of the same level. + // The criteria has to be the outline level: sub level => incorporate, same/higher level => no. + if( GetNodes().GetOutLineNds().Seek_Entry( pEndSrch, &nTmpPos ) ) + { + if( !pEndSrch->IsTextNode() || pEndSrch == pSrch || + nOutLineLevel < pEndSrch->GetTextNode()->GetAttrOutlineLevel()-1 ) + ++nTmpPos; // For sub outlines only! + } + + aEndRg = nTmpPos < GetNodes().GetOutLineNds().size() + ? *GetNodes().GetOutLineNds()[ nTmpPos ] + : GetNodes().GetEndOfContent(); + if( nOffset >= 0 ) + nCurrentPos = nTmpPos; + if( aEndRg == aSttRg ) + { + OSL_FAIL( "Moving outlines: Surprising selection" ); + ++aEndRg; + } + + const SwNode* pNd; + // The following code corrects the range to handle sections (start/end nodes) + // The range will be extended if the least node before the range is a start node + // which ends inside the range => The complete section will be moved. + // The range will be shrunk if the last position is a start node. + // The range will be shrunk if the last node is an end node which starts before the range. + --aSttRg; + while( aSttRg.GetNode().IsStartNode() ) + { + pNd = aSttRg.GetNode().EndOfSectionNode(); + if( pNd->GetIndex() >= aEndRg.GetIndex() ) + break; + --aSttRg; + } + ++aSttRg; + + --aEndRg; + while( aEndRg.GetNode().IsStartNode() ) + --aEndRg; + + while( aEndRg.GetNode().IsEndNode() ) + { + pNd = aEndRg.GetNode().StartOfSectionNode(); + if( pNd->GetIndex() >= aSttRg.GetIndex() ) + break; + --aEndRg; + } + ++aEndRg; + + // calculation of the new position + if( nOffset < 0 && nCurrentPos < o3tl::make_unsigned(-nOffset) ) + pNd = GetNodes().GetEndOfContent().StartOfSectionNode(); + else if( nCurrentPos + nOffset >= GetNodes().GetOutLineNds().size() ) + pNd = &GetNodes().GetEndOfContent(); + else + pNd = GetNodes().GetOutLineNds()[ nCurrentPos + nOffset ]; + + sal_uLong nNewPos = pNd->GetIndex(); + + // And now a correction of the insert position if necessary... + SwNodeIndex aInsertPos( *pNd, -1 ); + while( aInsertPos.GetNode().IsStartNode() ) + { + // Just before the insert position starts a section: + // when I'm moving forward I do not want to enter the section, + // when I'm moving backward I want to stay in the section if I'm already a part of, + // I want to stay outside if I was outside before. + if( nOffset < 0 ) + { + pNd = aInsertPos.GetNode().EndOfSectionNode(); + if( pNd->GetIndex() >= aEndRg.GetIndex() ) + break; + } + --aInsertPos; + --nNewPos; + } + + if( nOffset >= 0 ) + { + // When just before the insert position a section ends, it is okay when I'm moving backward + // because I want to stay outside the section. + // When moving forward I've to check if I started inside or outside the section + // because I don't want to enter of leave such a section + while( aInsertPos.GetNode().IsEndNode() ) + { + pNd = aInsertPos.GetNode().StartOfSectionNode(); + if( pNd->GetIndex() >= aSttRg.GetIndex() ) + break; + --aInsertPos; + --nNewPos; + } + } + // We do not want to move into tables (at the moment) + ++aInsertPos; + pNd = &aInsertPos.GetNode(); + if( pNd->IsTableNode() ) + pNd = pNd->StartOfSectionNode(); + if( pNd->FindTableNode() ) + return false; + + OSL_ENSURE( aSttRg.GetIndex() > nNewPos || nNewPos >= aEndRg.GetIndex(), + "Position lies within Move range" ); + + // If a Position inside the special nodes array sections was calculated, + // set it to document start instead. + // Sections or Tables at the document start will be pushed backwards. + nNewPos = std::max( nNewPos, GetNodes().GetEndOfExtras().GetIndex() + 2 ); + + long nOffs = nNewPos - ( 0 < nOffset ? aEndRg.GetIndex() : aSttRg.GetIndex()); + SwPaM aPam( aSttRg, aEndRg, 0, -1 ); + return MoveParagraph( aPam, nOffs, true ); +} + +static SwTextNode* lcl_FindOutlineName(const SwOutlineNodes& rOutlNds, + SwRootFrame const*const pLayout, const OUString& rName, bool const bExact) +{ + SwTextNode * pExactButDeleted(nullptr); + SwTextNode* pSavedNode = nullptr; + for( auto pOutlNd : rOutlNds ) + { + SwTextNode* pTextNd = pOutlNd->GetTextNode(); + const OUString sText( pTextNd->GetExpandText(pLayout) ); + if (sText.startsWith(rName)) + { + if (sText.getLength() == rName.getLength()) + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd)) + { + pExactButDeleted = pTextNd; + } + else + { + // Found "exact", set Pos to the Node + return pTextNd; + } + } + if (!bExact && !pSavedNode + && (!pLayout || sw::IsParaPropsNode(*pLayout, *pTextNd))) + { + // maybe we just found the text's first part + pSavedNode = pTextNd; + } + } + } + + return bExact ? pExactButDeleted : pSavedNode; +} + +static SwTextNode* lcl_FindOutlineNum(const SwOutlineNodes& rOutlNds, + OUString& rName, SwRootFrame const*const pLayout) +{ + // Valid numbers are (always just offsets!): + // ([Number]+\.)+ (as a regular expression!) + // (Number followed by a period, with 5 repetitions) + // i.e.: "1.1.", "1.", "1.1.1." + sal_Int32 nPos = 0; + OUString sNum = rName.getToken( 0, '.', nPos ); + if( -1 == nPos ) + return nullptr; // invalid number! + + sal_uInt16 nLevelVal[ MAXLEVEL ]; // numbers of all levels + memset( nLevelVal, 0, MAXLEVEL * sizeof( nLevelVal[0] )); + int nLevel = 0; + OUString sName( rName ); + + while( -1 != nPos ) + { + sal_uInt16 nVal = 0; + for( sal_Int32 n = 0; n < sNum.getLength(); ++n ) + { + const sal_Unicode c {sNum[ n ]}; + if( '0' <= c && c <= '9' ) + { + nVal *= 10; + nVal += c - '0'; + } + else if( nLevel ) + break; // "almost" valid number + else + return nullptr; // invalid number! + } + + if( MAXLEVEL > nLevel ) + nLevelVal[ nLevel++ ] = nVal; + + sName = sName.copy( nPos ); + nPos = 0; + sNum = sName.getToken( 0, '.', nPos ); + // #i4533# without this check all parts delimited by a dot are treated as outline numbers + if(!comphelper::string::isdigitAsciiString(sNum)) + break; + } + rName = sName; // that's the follow-up text + + // read all levels, so search the document for this outline + + // Without OutlineNodes searching doesn't pay off + // and we save a crash + if( rOutlNds.empty() ) + return nullptr; + + // search in the existing outline nodes for the required outline num array + for( auto pOutlNd : rOutlNds ) + { + SwTextNode* pNd = pOutlNd->GetTextNode(); + if ( pNd->GetAttrOutlineLevel() == nLevel ) + { + // #i51089#, #i68289# + // Assure, that text node has the correct numbering level. Otherwise, + // its number vector will not fit to the searched level. + if (pNd->GetNum(pLayout) && pNd->GetActualListLevel() == nLevel - 1) + { + const SwNodeNum & rNdNum = *(pNd->GetNum(pLayout)); + SwNumberTree::tNumberVector aLevelVal = rNdNum.GetNumberVector(); + // now compare with the one searched for + bool bEqual = true; + nLevel = std::min<int>(nLevel, MAXLEVEL); + for( int n = 0; n < nLevel; ++n ) + { + if ( aLevelVal[n] != nLevelVal[n] ) + { + bEqual = false; + break; + } + } + if (bEqual) + return pNd; + } + else + { + // A text node, which has an outline paragraph style applied and + // has as hard attribute 'no numbering' set, has an outline level, + // but no numbering tree node. Thus, consider this situation in + // the assertion condition. + OSL_ENSURE( !pNd->GetNumRule(), + "<lcl_FindOutlineNum(..)> - text node with outline level and numbering rule, but without numbering tree node. This is a serious defect" ); + } + } + } + + return nullptr; +} + +// rName can contain a Number and/or the Text. +// First, we try to find the correct Entry via the Number. +// If it exists, we compare the Text to see if it's the right one. +// If that's not the case, we search again via the Text. If it is +// found, we got the right entry. Or else we use the one found by +// searching for the Number. +// If we don't have a Number, we search via the Text only. +bool SwDoc::GotoOutline(SwPosition& rPos, const OUString& rName, SwRootFrame const*const pLayout) const +{ + if( !rName.isEmpty() ) + { + const SwOutlineNodes& rOutlNds = GetNodes().GetOutLineNds(); + + // 1. step: via the Number: + OUString sName( rName ); + SwTextNode* pNd = ::lcl_FindOutlineNum(rOutlNds, sName, pLayout); + if ( pNd ) + { + OUString sExpandedText = pNd->GetExpandText(pLayout); + //#i4533# leading numbers followed by a dot have been remove while + //searching for the outline position + //to compensate this they must be removed from the paragraphs text content, too + while(!sExpandedText.isEmpty()) + { + sal_Int32 nPos = 0; + OUString sTempNum = sExpandedText.getToken(0, '.', nPos); + if( sTempNum.isEmpty() || -1 == nPos || + !comphelper::string::isdigitAsciiString(sTempNum)) + break; + sExpandedText = sExpandedText.copy(nPos); + } + + if( sExpandedText != sName ) + { + SwTextNode *pTmpNd = ::lcl_FindOutlineName(rOutlNds, pLayout, sName, true); + if ( pTmpNd ) // found via the Name + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTmpNd)) + { // found the correct node but it's deleted! + return false; // avoid fallback to inexact search + } + pNd = pTmpNd; + } + } + rPos.nNode = *pNd; + rPos.nContent.Assign( pNd, 0 ); + return true; + } + + pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, rName, false); + if ( pNd ) + { + rPos.nNode = *pNd; + rPos.nContent.Assign( pNd, 0 ); + return true; + } + + // #i68289# additional search on hyperlink URL without its outline numbering part + if ( sName != rName ) + { + pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, sName, false); + if ( pNd ) + { + rPos.nNode = *pNd; + rPos.nContent.Assign( pNd, 0 ); + return true; + } + } + } + return false; +} + +static void lcl_ChgNumRule( SwDoc& rDoc, const SwNumRule& rRule ) +{ + SwNumRule* pOld = rDoc.FindNumRulePtr( rRule.GetName() ); + if (!pOld) //we cannot proceed without the old NumRule + return; + + sal_uInt16 nChgFormatLevel = 0; + sal_uInt16 nMask = 1; + + for ( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nMask <<= 1 ) + { + const SwNumFormat& rOldFormat = pOld->Get( n ), &rNewFormat = rRule.Get( n ); + + if ( rOldFormat != rNewFormat ) + { + nChgFormatLevel |= nMask; + } + else if ( SVX_NUM_NUMBER_NONE > rNewFormat.GetNumberingType() + && 1 < rNewFormat.GetIncludeUpperLevels() + && 0 != ( nChgFormatLevel & GetUpperLvlChg( n, rNewFormat.GetIncludeUpperLevels(), nMask ) ) ) + { + nChgFormatLevel |= nMask; + } + } + + if( !nChgFormatLevel ) // Nothing has been changed? + { + const bool bInvalidateNumRule( pOld->IsContinusNum() != rRule.IsContinusNum() ); + pOld->CheckCharFormats( &rDoc ); + pOld->SetContinusNum( rRule.IsContinusNum() ); + + if ( bInvalidateNumRule ) + { + pOld->SetInvalidRule(true); + } + + return ; + } + + SwNumRule::tTextNodeList aTextNodeList; + pOld->GetTextNodeList( aTextNodeList ); + sal_uInt8 nLvl( 0 ); + for ( SwTextNode* pTextNd : aTextNodeList ) + { + nLvl = static_cast<sal_uInt8>(pTextNd->GetActualListLevel()); + + if( nLvl < MAXLEVEL ) + { + if( nChgFormatLevel & ( 1 << nLvl )) + { + pTextNd->NumRuleChgd(); + } + } + } + + for ( sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + if ( nChgFormatLevel & ( 1 << n ) ) + pOld->Set( n, rRule.GetNumFormat( n ) ); + + pOld->CheckCharFormats( &rDoc ); + pOld->SetInvalidRule( true ); + pOld->SetContinusNum( rRule.IsContinusNum() ); + + rDoc.UpdateNumRule(); +} + +OUString SwDoc::SetNumRule( const SwPaM& rPam, + const SwNumRule& rRule, + const bool bCreateNewList, + SwRootFrame const*const pLayout, + const OUString& sContinuedListId, + bool bSetItem, + const bool bResetIndentAttrs ) +{ + OUString sListId; + + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + + SwUndoInsNum * pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + // Start/End for attributes! + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSNUM, nullptr ); + pUndo = new SwUndoInsNum( aPam, rRule ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + SwNumRule* pNewOrChangedNumRule = FindNumRulePtr( rRule.GetName() ); + bool bNewNumRuleCreated = false; + if ( pNewOrChangedNumRule == nullptr ) + { + // create new numbering rule based on given one + pNewOrChangedNumRule = ( *mpNumRuleTable )[MakeNumRule( rRule.GetName(), &rRule )]; + bNewNumRuleCreated = true; + } + else if ( rRule != *pNewOrChangedNumRule ) + { + // change existing numbering rule + if (pUndo) + { + pUndo->SaveOldNumRule( *pNewOrChangedNumRule ); + } + ::lcl_ChgNumRule( *this, rRule ); + if (pUndo) + { + pUndo->SetLRSpaceEndPos(); + } + } + + if ( bSetItem ) + { + if ( bCreateNewList ) + { + if ( bNewNumRuleCreated ) + { + // apply list id of list, which has been created for the new list style + sListId = pNewOrChangedNumRule->GetDefaultListId(); + } + else + { + // create new list and apply its list id + const SwList* pNewList = getIDocumentListsAccess().createList( OUString(), pNewOrChangedNumRule->GetName() ); + OSL_ENSURE( pNewList, + "<SwDoc::SetNumRule(..)> - could not create new list. Serious defect." ); + sListId = pNewList->GetListId(); + } + } + else if ( !sContinuedListId.isEmpty() ) + { + // apply given list id + sListId = sContinuedListId; + } + if (!sListId.isEmpty()) + { + getIDocumentContentOperations().InsertPoolItem(aPam, + SfxStringItem(RES_PARATR_LIST_ID, sListId), + SetAttrMode::DEFAULT, pLayout); + } + } + + if (!aPam.HasMark()) + { + SwTextNode * pTextNd = aPam.GetPoint()->nNode.GetNode().GetTextNode(); + // robust code: consider case that the PaM doesn't denote a text node - e.g. it denotes a graphic node + if ( pTextNd != nullptr ) + { + assert(!pLayout || sw::IsParaPropsNode(*pLayout, *pTextNd)); + SwNumRule * pRule = pTextNd->GetNumRule(); + + if (pRule && pRule->GetName() == pNewOrChangedNumRule->GetName()) + { + bSetItem = false; + if ( !pTextNd->IsInList() ) + { + pTextNd->AddToList(); + } + } + // Only clear numbering attribute at text node, if at paragraph + // style the new numbering rule is found. + else if ( !pRule ) + { + SwTextFormatColl* pColl = pTextNd->GetTextColl(); + if ( pColl ) + { + SwNumRule* pCollRule = FindNumRulePtr(pColl->GetNumRule().GetValue()); + if ( pCollRule && pCollRule->GetName() == pNewOrChangedNumRule->GetName() ) + { + pTextNd->ResetAttr( RES_PARATR_NUMRULE ); + bSetItem = false; + } + } + } + } + } + + if ( bSetItem ) + { + getIDocumentContentOperations().InsertPoolItem(aPam, + SwNumRuleItem(pNewOrChangedNumRule->GetName()), + SetAttrMode::DEFAULT, pLayout); + } + + if ( bResetIndentAttrs + && pNewOrChangedNumRule->Get( 0 ).GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + ::lcl_ResetIndentAttrs(this, aPam, RES_LR_SPACE, pLayout); + } + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSNUM, nullptr ); + } + + getIDocumentState().SetModified(); + + return sListId; +} + +void SwDoc::SetCounted(const SwPaM & rPam, bool bCounted, + SwRootFrame const*const pLayout) +{ + if ( bCounted ) + { + ::lcl_ResetIndentAttrs(this, rPam, RES_PARATR_LIST_ISCOUNTED, pLayout); + } + else + { + getIDocumentContentOperations().InsertPoolItem(rPam, + SfxBoolItem(RES_PARATR_LIST_ISCOUNTED, false), + SetAttrMode::DEFAULT, pLayout); + } +} + +void SwDoc::SetNumRuleStart( const SwPosition& rPos, bool bFlag ) +{ + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + + if (pTextNd) + { + const SwNumRule* pRule = pTextNd->GetNumRule(); + if( pRule && !bFlag != !pTextNd->IsListRestart()) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumRuleStart>(rPos, bFlag) ); + } + + pTextNd->SetListRestart(bFlag); + + getIDocumentState().SetModified(); + } + } +} + +void SwDoc::SetNodeNumStart( const SwPosition& rPos, sal_uInt16 nStt ) +{ + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + + if (pTextNd) + { + if ( !pTextNd->HasAttrListRestartValue() || + pTextNd->GetAttrListRestartValue() != nStt ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumRuleStart>(rPos, nStt) ); + } + pTextNd->SetAttrListRestartValue( nStt ); + + getIDocumentState().SetModified(); + } + } +} + +// We can only delete if the Rule is unused! +bool SwDoc::DelNumRule( const OUString& rName, bool bBroadcast ) +{ + sal_uInt16 nPos = FindNumRule( rName ); + + if (nPos == USHRT_MAX) + return false; + + if ( (*mpNumRuleTable)[ nPos ] == GetOutlineNumRule() ) + { + OSL_FAIL( "<SwDoc::DelNumRule(..)> - No deletion of outline list style. This is serious defect" ); + return false; + } + + if( !IsUsed( *(*mpNumRuleTable)[ nPos ] )) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumruleDelete>(*(*mpNumRuleTable)[nPos], this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Pseudo, + SfxHintId::StyleSheetErased); + + getIDocumentListsAccess().deleteListForListStyle( rName ); + getIDocumentListsAccess().deleteListsByDefaultListStyle( rName ); + // #i34097# DeleteAndDestroy deletes rName if + // rName is directly taken from the numrule. + const OUString aTmpName( rName ); + delete (*mpNumRuleTable)[ nPos ]; + mpNumRuleTable->erase( mpNumRuleTable->begin() + nPos ); + maNumRuleMap.erase(aTmpName); + + getIDocumentState().SetModified(); + return true; + } + return false; +} + +void SwDoc::ChgNumRuleFormats( const SwNumRule& rRule ) +{ + SwNumRule* pRule = FindNumRulePtr( rRule.GetName() ); + if( pRule ) + { + SwUndoInsNum* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoInsNum( *pRule, rRule, this ); + pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + ::lcl_ChgNumRule( *this, rRule ); + if (pUndo) + { + pUndo->SetLRSpaceEndPos(); + } + + getIDocumentState().SetModified(); + } +} + +bool SwDoc::RenameNumRule(const OUString & rOldName, const OUString & rNewName, + bool bBroadcast) +{ + assert(!FindNumRulePtr(rNewName)); + + bool bResult = false; + SwNumRule * pNumRule = FindNumRulePtr(rOldName); + + if (pNumRule) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumruleRename>(rOldName, rNewName, this)); + } + + SwNumRule::tTextNodeList aTextNodeList; + pNumRule->GetTextNodeList( aTextNodeList ); + + pNumRule->SetName( rNewName, getIDocumentListsAccess() ); + + SwNumRuleItem aItem(rNewName); + + for ( SwTextNode* pTextNd : aTextNodeList ) + { + pTextNd->SetAttr(aItem); + } + + bResult = true; + + if (bBroadcast) + BroadcastStyleOperation(rOldName, SfxStyleFamily::Pseudo, + SfxHintId::StyleSheetModified); + } + + return bResult; +} + +void SwDoc::StopNumRuleAnimations( OutputDevice* pOut ) +{ + for( sal_uInt16 n = GetNumRuleTable().size(); n; ) + { + SwNumRule::tTextNodeList aTextNodeList; + GetNumRuleTable()[ --n ]->GetTextNodeList( aTextNodeList ); + for ( SwTextNode* pTNd : aTextNodeList ) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTNd); + for(SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + if (pFrame->HasAnimation() && + (!pFrame->GetMergedPara() || pFrame->GetMergedPara()->pParaPropsNode == pTNd)) + { + pFrame->StopAnimation( pOut ); + } + } + } +} + +bool SwDoc::ReplaceNumRule( const SwPosition& rPos, + const OUString& rOldRule, const OUString& rNewRule ) +{ + bool bRet = false; + SwNumRule *pOldRule = FindNumRulePtr( rOldRule ), + *pNewRule = FindNumRulePtr( rNewRule ); + if( pOldRule && pNewRule && pOldRule != pNewRule ) + { + SwUndoInsNum* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + // Start/End for attributes! + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + pUndo = new SwUndoInsNum( rPos, *pNewRule, rOldRule ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + SwNumRule::tTextNodeList aTextNodeList; + pOldRule->GetTextNodeList( aTextNodeList ); + if ( !aTextNodeList.empty() ) + { + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + sal_uInt16 nChgFormatLevel = 0; + for( sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + { + const SwNumFormat& rOldFormat = pOldRule->Get( n ), + & rNewFormat = pNewRule->Get( n ); + + if( rOldFormat.GetAbsLSpace() != rNewFormat.GetAbsLSpace() || + rOldFormat.GetFirstLineOffset() != rNewFormat.GetFirstLineOffset() ) + nChgFormatLevel |= ( 1 << n ); + } + + const SwTextNode* pGivenTextNode = rPos.nNode.GetNode().GetTextNode(); + SwNumRuleItem aRule( rNewRule ); + for ( SwTextNode* pTextNd : aTextNodeList ) + { + if ( pGivenTextNode && + pGivenTextNode->GetListId() == pTextNd->GetListId() ) + { + aRegH.RegisterInModify( pTextNd, *pTextNd ); + + pTextNd->SetAttr( aRule ); + pTextNd->NumRuleChgd(); + } + } + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + getIDocumentState().SetModified(); + + bRet = true; + } + } + + return bRet; +} + +namespace +{ + struct ListStyleData + { + SwNumRule* pReplaceNumRule; + bool bCreateNewList; + OUString sListId; + + ListStyleData() + : pReplaceNumRule( nullptr ), + bCreateNewList( false ), + sListId() + {} + }; +} + +void SwDoc::MakeUniqueNumRules(const SwPaM & rPaM) +{ + OSL_ENSURE( rPaM.GetDoc() == this, "need same doc" ); + + std::map<SwNumRule *, ListStyleData> aMyNumRuleMap; + + bool bFirst = true; + + const sal_uLong nStt = rPaM.Start()->nNode.GetIndex(); + const sal_uLong nEnd = rPaM.End()->nNode.GetIndex(); + for (sal_uLong n = nStt; n <= nEnd; n++) + { + SwTextNode * pCNd = GetNodes()[n]->GetTextNode(); + + if (pCNd) + { + SwNumRule * pRule = pCNd->GetNumRule(); + + if (pRule && pRule->IsAutoRule() && ! pRule->IsOutlineRule()) + { + ListStyleData aListStyleData = aMyNumRuleMap[pRule]; + + if ( aListStyleData.pReplaceNumRule == nullptr ) + { + if (bFirst) + { + SwPosition aPos(*pCNd); + aListStyleData.pReplaceNumRule = + const_cast<SwNumRule *> + (SearchNumRule( aPos, false, pCNd->HasNumber(), + false, 0, + aListStyleData.sListId, nullptr, true )); + } + + if ( aListStyleData.pReplaceNumRule == nullptr ) + { + aListStyleData.pReplaceNumRule = new SwNumRule(*pRule); + aListStyleData.pReplaceNumRule->SetName( GetUniqueNumRuleName(), getIDocumentListsAccess() ); + aListStyleData.bCreateNewList = true; + } + + aMyNumRuleMap[pRule] = aListStyleData; + } + + SwPaM aPam(*pCNd); + + SetNumRule( aPam, + *aListStyleData.pReplaceNumRule, + aListStyleData.bCreateNewList, + nullptr, + aListStyleData.sListId ); + if ( aListStyleData.bCreateNewList ) + { + aListStyleData.bCreateNewList = false; + aListStyleData.sListId = pCNd->GetListId(); + aMyNumRuleMap[pRule] = aListStyleData; + } + + bFirst = false; + } + } + } +} + +bool SwDoc::NoNum( const SwPaM& rPam ) +{ + + bool bRet = getIDocumentContentOperations().SplitNode( *rPam.GetPoint(), false ); + // Do we actually use Numbering at all? + if( bRet ) + { + // Set NoNum and Update + const SwNodeIndex& rIdx = rPam.GetPoint()->nNode; + SwTextNode* pNd = rIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + if( pRule ) + { + pNd->SetCountedInList(false); + + getIDocumentState().SetModified(); + } + else + bRet = false; // no Numbering or just always true? + } + return bRet; +} + +void SwDoc::DelNumRules(const SwPaM& rPam, SwRootFrame const*const pLayout) +{ + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + sal_uLong nStt = aPam.Start()->nNode.GetIndex(); + sal_uLong const nEnd = aPam.End()->nNode.GetIndex(); + + SwUndoDelNum* pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoDelNum( aPam ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + else + pUndo = nullptr; + + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + + SwNumRuleItem aEmptyRule; + const SwNode* pOutlNd = nullptr; + for( ; nStt <= nEnd; ++nStt ) + { + SwTextNode* pTNd = GetNodes()[ nStt ]->GetTextNode(); + if (pLayout && pTNd) + { + pTNd = sw::GetParaPropsNode(*pLayout, *pTNd); + } + SwNumRule* pNumRuleOfTextNode = pTNd ? pTNd->GetNumRule() : nullptr; + if ( pTNd && pNumRuleOfTextNode ) + { + // recognize changes of attribute for undo + aRegH.RegisterInModify( pTNd, *pTNd ); + + if( pUndo ) + pUndo->AddNode( *pTNd ); + + // directly set list style attribute is reset, otherwise empty + // list style is applied + const SfxItemSet* pAttrSet = pTNd->GetpSwAttrSet(); + if ( pAttrSet && + pAttrSet->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + pTNd->ResetAttr( RES_PARATR_NUMRULE ); + else + pTNd->SetAttr( aEmptyRule ); + + pTNd->ResetAttr( RES_PARATR_LIST_ID ); + pTNd->ResetAttr( RES_PARATR_LIST_LEVEL ); + pTNd->ResetAttr( RES_PARATR_LIST_ISRESTART ); + pTNd->ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + pTNd->ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + + if( RES_CONDTXTFMTCOLL == pTNd->GetFormatColl()->Which() ) + { + pTNd->ChkCondColl(); + } + else if( !pOutlNd && + static_cast<SwTextFormatColl*>(pTNd->GetFormatColl())->IsAssignedToListLevelOfOutlineStyle() ) + { + pOutlNd = pTNd; + } + } + } + + // Finally, update all + UpdateNumRule(); + + if( pOutlNd ) + GetNodes().UpdateOutlineIdx( *pOutlNd ); +} + +void SwDoc::InvalidateNumRules() +{ + for (size_t n = 0; n < mpNumRuleTable->size(); ++n) + (*mpNumRuleTable)[n]->SetInvalidRule(true); +} + +// To the next/preceding Bullet at the same Level +static bool lcl_IsNumOk( sal_uInt8 nSrchNum, sal_uInt8& rLower, sal_uInt8& rUpper, + bool bOverUpper, sal_uInt8 nNumber ) +{ + OSL_ENSURE( nNumber < MAXLEVEL, + "<lcl_IsNumOk(..)> - misusage of method" ); + + bool bRet = false; + { + if( bOverUpper ? nSrchNum == nNumber : nSrchNum >= nNumber ) + bRet = true; + else if( nNumber > rLower ) + rLower = nNumber; + else if( nNumber < rUpper ) + rUpper = nNumber; + } + return bRet; +} + +static bool lcl_IsValidPrevNextNumNode( const SwNodeIndex& rIdx ) +{ + bool bRet = false; + const SwNode& rNd = rIdx.GetNode(); + switch( rNd.GetNodeType() ) + { + case SwNodeType::End: + bRet = SwTableBoxStartNode == rNd.StartOfSectionNode()->GetStartNodeType() || + rNd.StartOfSectionNode()->IsSectionNode(); + break; + + case SwNodeType::Start: + bRet = SwTableBoxStartNode == static_cast<const SwStartNode&>(rNd).GetStartNodeType(); + break; + + case SwNodeType::Section: // that one's valid, so proceed + bRet = true; + break; + + default: break; + } + return bRet; +} + +namespace sw { + +void +GotoPrevLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const*const pLayout) +{ + if (pLayout && pLayout->IsHideRedlines()) + { + if (rIndex.GetNode().IsTextNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None) + { + rIndex = *static_cast<SwTextFrame*>(rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout))->GetMergedPara()->pFirstNode; + } + } + else if (rIndex.GetNode().IsEndNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + rIndex = *rIndex.GetNode().StartOfSectionNode(); + assert(rIndex.GetNode().IsTableNode()); + } + } + } + --rIndex; + if (pLayout && rIndex.GetNode().IsTextNode()) + { + rIndex = *sw::GetParaPropsNode(*pLayout, *rIndex.GetNode().GetTextNode()); + } +} + +void +GotoNextLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const*const pLayout) +{ + if (pLayout && pLayout->IsHideRedlines()) + { + if (rIndex.GetNode().IsTextNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None) + { + rIndex = *static_cast<SwTextFrame*>(rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout))->GetMergedPara()->pLastNode; + } + } + else if (rIndex.GetNode().IsTableNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + rIndex = *rIndex.GetNode().EndOfSectionNode(); + } + } + } + ++rIndex; + if (pLayout && rIndex.GetNode().IsTextNode()) + { + rIndex = *sw::GetParaPropsNode(*pLayout, *rIndex.GetNode().GetTextNode()); + } +} + +} // namespace sw + +static bool lcl_GotoNextPrevNum( SwPosition& rPos, bool bNext, + bool bOverUpper, sal_uInt8* pUpper, sal_uInt8* pLower, + SwRootFrame const*const pLayout) +{ + const SwTextNode* pNd = rPos.nNode.GetNode().GetTextNode(); + if (pNd && pLayout) + { + pNd = sw::GetParaPropsNode(*pLayout, *pNd); + } + if( !pNd || nullptr == pNd->GetNumRule() ) + return false; + + sal_uInt8 nSrchNum = static_cast<sal_uInt8>(pNd->GetActualListLevel()); + + SwNodeIndex aIdx( rPos.nNode ); + if( ! pNd->IsCountedInList() ) + { + // If NO_NUMLEVEL is switched on, we search the preceding Node with Numbering + bool bError = false; + do { + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + if( aIdx.GetNode().IsTextNode() ) + { + pNd = aIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + + sal_uInt8 nTmpNum; + + if( pRule ) + { + nTmpNum = static_cast<sal_uInt8>(pNd->GetActualListLevel()); + if( !( ! pNd->IsCountedInList() && + (nTmpNum >= nSrchNum )) ) + break; // found it! + } + else + bError = true; + } + else + bError = !lcl_IsValidPrevNextNumNode( aIdx ); + + } while( !bError ); + if( bError ) + return false; + } + + sal_uInt8 nLower = nSrchNum, nUpper = nSrchNum; + bool bRet = false; + + const SwTextNode* pLast; + if( bNext ) + { + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + pLast = pNd; + } + else + { + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + pLast = nullptr; + } + + while( bNext ? ( aIdx.GetIndex() < aIdx.GetNodes().Count() - 1 ) + : aIdx.GetIndex() != 0 ) + { + if( aIdx.GetNode().IsTextNode() ) + { + pNd = aIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + if( pRule ) + { + if( ::lcl_IsNumOk( nSrchNum, nLower, nUpper, bOverUpper, + static_cast<sal_uInt8>(pNd->GetActualListLevel()) )) + { + rPos.nNode = aIdx; + rPos.nContent.Assign( const_cast<SwTextNode*>(pNd), 0 ); + bRet = true; + break; + } + else + pLast = pNd; + } + else + break; + } + else if( !lcl_IsValidPrevNextNumNode( aIdx )) + break; + + if( bNext ) + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + else + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + } + + if( !bRet && !bOverUpper && pLast ) // do not iterate over higher numbers, but still to the end + { + if( bNext ) + { + rPos.nNode = aIdx; + if( aIdx.GetNode().IsContentNode() ) + rPos.nContent.Assign( aIdx.GetNode().GetContentNode(), 0 ); + } + else + { + rPos.nNode.Assign( *pLast ); + rPos.nContent.Assign( const_cast<SwTextNode*>(pLast), 0 ); + } + bRet = true; + } + + if( bRet ) + { + if( pUpper ) + *pUpper = nUpper; + if( pLower ) + *pLower = nLower; + } + return bRet; +} + +bool SwDoc::GotoNextNum(SwPosition& rPos, SwRootFrame const*const pLayout, + bool bOverUpper, sal_uInt8* pUpper, sal_uInt8* pLower) +{ + return ::lcl_GotoNextPrevNum(rPos, true, bOverUpper, pUpper, pLower, pLayout); +} + +const SwNumRule * SwDoc::SearchNumRule(const SwPosition & rPos, + const bool bForward, + const bool bNum, + const bool bOutline, + int nNonEmptyAllowed, + OUString& sListId, + SwRootFrame const* pLayout, + const bool bInvestigateStartNode) +{ + const SwNumRule * pResult = nullptr; + SwTextNode * pTextNd = rPos.nNode.GetNode().GetTextNode(); + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, rPos.nNode); + } + SwNode * pStartFromNode = pTextNd; + + if (pTextNd) + { + SwNodeIndex aIdx(rPos.nNode); + + // - the start node has also been investigated, if requested. + const SwNode * pNode = nullptr; + do + { + if ( !bInvestigateStartNode ) + { + if (bForward) + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + else + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + } + + if (aIdx.GetNode().IsTextNode()) + { + pTextNd = aIdx.GetNode().GetTextNode(); + + const SwNumRule * pNumRule = pTextNd->GetNumRule(); + if (pNumRule) + { + if ( ( pNumRule->IsOutlineRule() == bOutline ) && + ( ( bNum && pNumRule->Get(0).IsEnumeration()) || + ( !bNum && pNumRule->Get(0).IsItemize() ) ) ) // #i22362#, #i29560# + { + pResult = pTextNd->GetNumRule(); + // provide also the list id, to which the text node belongs. + sListId = pTextNd->GetListId(); + } + + break; + } + else if (pTextNd->Len() > 0 || nullptr != pTextNd->GetNumRule()) + { + if (nNonEmptyAllowed == 0) + break; + + nNonEmptyAllowed--; + + if (nNonEmptyAllowed < 0) + nNonEmptyAllowed = -1; + } + } + + if ( bInvestigateStartNode ) + { + if (bForward) + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + else + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + } + + pNode = &aIdx.GetNode(); + } + while (!(pNode == GetNodes().DocumentSectionStartNode(pStartFromNode) || + pNode == GetNodes().DocumentSectionEndNode(pStartFromNode))); + } + + return pResult; +} + +bool SwDoc::GotoPrevNum(SwPosition& rPos, SwRootFrame const*const pLayout, + bool bOverUpper) +{ + return ::lcl_GotoNextPrevNum(rPos, false, bOverUpper, nullptr, nullptr, pLayout); +} + +bool SwDoc::NumUpDown(const SwPaM& rPam, bool bDown, SwRootFrame const*const pLayout) +{ + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + sal_uLong nStt = aPam.Start()->nNode.GetIndex(); + sal_uLong const nEnd = aPam.End()->nNode.GetIndex(); + + // -> outline nodes are promoted or demoted differently + bool bOnlyOutline = true; + bool bOnlyNonOutline = true; + for (sal_uLong n = nStt; n <= nEnd; n++) + { + SwTextNode * pTextNd = GetNodes()[n]->GetTextNode(); + + if (pTextNd) + { + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd); + } + SwNumRule * pRule = pTextNd->GetNumRule(); + + if (pRule) + { + if (pRule->IsOutlineRule()) + bOnlyNonOutline = false; + else + bOnlyOutline = false; + } + } + } + + bool bRet = true; + sal_Int8 nDiff = bDown ? 1 : -1; + + if (bOnlyOutline) + bRet = OutlineUpDown(rPam, nDiff, pLayout); + else if (bOnlyNonOutline) + { + /* #i24560# + Only promote or demote if all selected paragraphs are + promotable resp. demotable. + */ + for (sal_uLong nTmp = nStt; nTmp <= nEnd; ++nTmp) + { + SwTextNode* pTNd = GetNodes()[ nTmp ]->GetTextNode(); + + // Make code robust: consider case that the node doesn't denote a + // text node. + if ( pTNd ) + { + if (pLayout) + { + pTNd = sw::GetParaPropsNode(*pLayout, *pTNd); + } + + SwNumRule * pRule = pTNd->GetNumRule(); + + if (pRule) + { + sal_uInt8 nLevel = static_cast<sal_uInt8>(pTNd->GetActualListLevel()); + if( (-1 == nDiff && 0 >= nLevel) || + (1 == nDiff && MAXLEVEL - 1 <= nLevel)) + bRet = false; + } + } + } + + if( bRet ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumUpDown>(aPam, nDiff) ); + } + + SwTextNode* pPrev = nullptr; + for(sal_uLong nTmp = nStt; nTmp <= nEnd; ++nTmp ) + { + SwTextNode* pTNd = GetNodes()[ nTmp ]->GetTextNode(); + + if( pTNd) + { + if (pLayout) + { + pTNd = sw::GetParaPropsNode(*pLayout, *pTNd); + if (pTNd == pPrev) + { + continue; + } + pPrev = pTNd; + } + + SwNumRule * pRule = pTNd->GetNumRule(); + + if (pRule) + { + sal_uInt8 nLevel = static_cast<sal_uInt8>(pTNd->GetActualListLevel()); + nLevel = nLevel + nDiff; + + pTNd->SetAttrListLevel(nLevel); + } + } + } + + ChkCondColls(); + getIDocumentState().SetModified(); + } + } + + return bRet; +} + +// this function doesn't contain any numbering-related code, but it is +// primarily called to move numbering-relevant paragraphs around, hence +// it will expand its selection to include full SwTextFrames. +bool SwDoc::MoveParagraph(SwPaM& rPam, long nOffset, bool const bIsOutlMv) +{ + // sw_redlinehide: as long as a layout with Hide mode exists, only + // move nodes that have merged frames *completely* + SwRootFrame const* pLayout(nullptr); + for (SwRootFrame const*const pLay : GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayout = pLay; + } + } + if (pLayout) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*pLayout, rPam.Start()->nNode)); + if (nodes.first && nodes.first != &rPam.Start()->nNode.GetNode()) + { + assert(nodes.second); + if (nOffset < 0) + { + nOffset += rPam.Start()->nNode.GetIndex() - nodes.first->GetIndex(); + if (0 <= nOffset) // hack: there are callers that know what + { // node they want; those should never need + nOffset = -1; // this; other callers just pass in -1 + } // and those should still move + } + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + assert(nodes.first->GetIndex() < rPam.Start()->nNode.GetIndex()); + rPam.Start()->nNode = *nodes.first; + rPam.Start()->nContent.Assign(nodes.first, 0); + } + nodes = sw::GetFirstAndLastNode(*pLayout, rPam.End()->nNode); + if (nodes.second && nodes.second != &rPam.End()->nNode.GetNode()) + { + assert(nodes.first); + if (0 < nOffset) + { + nOffset -= nodes.second->GetIndex() - rPam.End()->nNode.GetIndex(); + if (nOffset <= 0) // hack: there are callers that know what + { // node they want; those should never need + nOffset = +1; // this; other callers just pass in +1 + } // and those should still move + } + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + assert(rPam.End()->nNode.GetIndex() < nodes.second->GetIndex()); + rPam.End()->nNode = *nodes.second; + // until end, otherwise Impl will detect overlapping redline + rPam.End()->nContent.Assign(nodes.second, nodes.second->GetTextNode()->Len()); + } + + if (nOffset > 0) + { // sw_redlinehide: avoid moving into delete redline, skip forward + if (GetNodes().GetEndOfContent().GetIndex() <= rPam.End()->nNode.GetIndex() + nOffset) + { + return false; // can't move + } + SwNode const* pNode(GetNodes()[rPam.End()->nNode.GetIndex() + nOffset + 1]); + if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None + && pNode->GetRedlineMergeFlag() != SwNode::Merge::First) + { + for ( ; ; ++nOffset) + { + pNode = GetNodes()[rPam.End()->nNode.GetIndex() + nOffset]; + if (pNode->IsTextNode()) + { + nodes = GetFirstAndLastNode(*pLayout, *pNode->GetTextNode()); + assert(nodes.first && nodes.second); + nOffset += nodes.second->GetIndex() - pNode->GetIndex(); + // on last; will be incremented below to behind-last + break; + } + } + } + } + else + { // sw_redlinehide: avoid moving into delete redline, skip backward + if (rPam.Start()->nNode.GetIndex() + nOffset < 1) + { + return false; // can't move + } + SwNode const* pNode(GetNodes()[rPam.Start()->nNode.GetIndex() + nOffset]); + if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None + && pNode->GetRedlineMergeFlag() != SwNode::Merge::First) + { + for ( ; ; --nOffset) + { + pNode = GetNodes()[rPam.Start()->nNode.GetIndex() + nOffset]; + if (pNode->IsTextNode()) + { + nodes = GetFirstAndLastNode(*pLayout, *pNode->GetTextNode()); + assert(nodes.first && nodes.second); + nOffset -= pNode->GetIndex() - nodes.first->GetIndex(); + // on first + break; + } + } + } + } + } + return MoveParagraphImpl(rPam, nOffset, bIsOutlMv, pLayout); +} + +bool SwDoc::MoveParagraphImpl(SwPaM& rPam, long const nOffset, + bool const bIsOutlMv, SwRootFrame const*const pLayout) +{ + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + sal_uLong nStIdx = pStt->nNode.GetIndex(); + sal_uLong nEndIdx = pEnd->nNode.GetIndex(); + + // Here are some sophisticated checks whether the wished PaM will be moved or not. + // For moving outlines (bIsOutlMv) I've already done some checks, so here are two different + // checks... + SwNode *pTmp1; + SwNode *pTmp2; + if( bIsOutlMv ) + { + // For moving chapters (outline) the following reason will deny the move: + // if a start node is inside the moved range and its end node outside or vice versa. + // If a start node is the first moved paragraph, its end node has to be within the moved + // range, too (e.g. as last node). + // If an end node is the last node of the moved range, its start node has to be a part of + // the moved section, too. + pTmp1 = GetNodes()[ nStIdx ]; + if( pTmp1->IsStartNode() ) + { // First is a start node + pTmp2 = pTmp1->EndOfSectionNode(); + if( pTmp2->GetIndex() > nEndIdx ) + return false; // Its end node is behind the moved range + } + pTmp1 = pTmp1->StartOfSectionNode()->EndOfSectionNode(); + if( pTmp1->GetIndex() <= nEndIdx ) + return false; // End node inside but start node before moved range => no. + pTmp1 = GetNodes()[ nEndIdx ]; + if( pTmp1->IsEndNode() ) + { // The last one is an end node + pTmp1 = pTmp1->StartOfSectionNode(); + if( pTmp1->GetIndex() < nStIdx ) + return false; // Its start node is before the moved range. + } + pTmp1 = pTmp1->StartOfSectionNode(); + if( pTmp1->GetIndex() >= nStIdx ) + return false; // A start node which ends behind the moved range => no. + } + + sal_uLong nInStIdx, nInEndIdx; + long nOffs = nOffset; + if( nOffset > 0 ) + { + nInEndIdx = nEndIdx; + nEndIdx += nOffset; + ++nOffs; + } + else + { + // Impossible to move to negative index + if( o3tl::make_unsigned(std::abs( nOffset )) > nStIdx) + return false; + + nInEndIdx = nStIdx - 1; + nStIdx += nOffset; + } + nInStIdx = nInEndIdx + 1; + // The following paragraphs shall be swapped: + // Swap [ nStIdx, nInEndIdx ] with [ nInStIdx, nEndIdx ] + + if( nEndIdx >= GetNodes().GetEndOfContent().GetIndex() ) + return false; + + if( !bIsOutlMv ) + { // And here the restrictions for moving paragraphs other than chapters (outlines) + // The plan is to exchange [nStIdx,nInEndIdx] and [nStartIdx,nEndIdx] + // It will checked if the both "start" nodes as well as the both "end" notes belongs to + // the same start-end-section. This is more restrictive than the conditions checked above. + // E.g. a paragraph will not escape from a section or be inserted to another section. + pTmp1 = GetNodes()[ nStIdx ]->StartOfSectionNode(); + pTmp2 = GetNodes()[ nInStIdx ]->StartOfSectionNode(); + if( pTmp1 != pTmp2 ) + return false; // "start" nodes in different sections + pTmp1 = GetNodes()[ nEndIdx ]; + bool bIsEndNode = pTmp1->IsEndNode(); + if( !pTmp1->IsStartNode() ) + { + pTmp1 = pTmp1->StartOfSectionNode(); + if( bIsEndNode ) // For end nodes the first start node is of course inside the range, + pTmp1 = pTmp1->StartOfSectionNode(); // I've to check the start node of the start node. + } + pTmp1 = pTmp1->EndOfSectionNode(); + pTmp2 = GetNodes()[ nInEndIdx ]; + if( !pTmp2->IsStartNode() ) + { + bIsEndNode = pTmp2->IsEndNode(); + pTmp2 = pTmp2->StartOfSectionNode(); + if( bIsEndNode ) + pTmp2 = pTmp2->StartOfSectionNode(); + } + pTmp2 = pTmp2->EndOfSectionNode(); + if( pTmp1 != pTmp2 ) + return false; // The "end" notes are in different sections + } + + // Test for Redlining - Can the Selection be moved at all, actually? + if( !getIDocumentRedlineAccess().IsIgnoreRedline() ) + { + SwRedlineTable::size_type nRedlPos = getIDocumentRedlineAccess().GetRedlinePos( pStt->nNode.GetNode(), RedlineType::Delete ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwPosition aStPos( *pStt ), aEndPos( *pEnd ); + aStPos.nContent = 0; + SwContentNode* pCNd = pEnd->nNode.GetNode().GetContentNode(); + aEndPos.nContent = pCNd ? pCNd->Len() : 1; + bool bCheckDel = true; + + // There is a some Redline Delete Object for the range + for( ; nRedlPos < getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedlPos ) + { + const SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + if( !bCheckDel || RedlineType::Delete == pTmp->GetType() ) + { + const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); + switch( ComparePosition( *pRStt, *pREnd, aStPos, aEndPos )) + { + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: // Pos1 comes after Pos2 + nRedlPos = getIDocumentRedlineAccess().GetRedlineTable().size(); + break; + + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: // Pos1 comes before Pos2 + break; + case SwComparePosition::Inside: // Pos1 is completely inside Pos2 + // that's valid, but check all following for overlapping + bCheckDel = false; + break; + + case SwComparePosition::Outside: // Pos2 is completely inside Pos1 + case SwComparePosition::Equal: // Pos1 is equal to Pos2 + case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 in the beginning + case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at the end + return false; + } + } + } + } + } + + { + // Send DataChanged before moving. We then can detect + // which objects are still in the range. + // After the move they could come before/after the + // Position. + SwDataChanged aTmp( rPam ); + } + + SwNodeIndex aIdx( nOffset > 0 ? pEnd->nNode : pStt->nNode, nOffs ); + SwNodeRange aMvRg( pStt->nNode, 0, pEnd->nNode, +1 ); + + SwRangeRedline* pOwnRedl = nullptr; + if( getIDocumentRedlineAccess().IsRedlineOn() ) + { + // If the range is completely in the own Redline, we can move it! + SwRedlineTable::size_type nRedlPos = getIDocumentRedlineAccess().GetRedlinePos( pStt->nNode.GetNode(), RedlineType::Insert ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); + SwRangeRedline aTmpRedl( RedlineType::Insert, rPam ); + const SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + // Is completely in the range and is the own Redline too? + if( aTmpRedl.IsOwnRedline( *pTmp ) && + (pRStt->nNode < pStt->nNode || + (pRStt->nNode == pStt->nNode && !pRStt->nContent.GetIndex()) ) && + (pEnd->nNode < pREnd->nNode || + (pEnd->nNode == pREnd->nNode && + pCEndNd ? pREnd->nContent.GetIndex() == pCEndNd->Len() + : !pREnd->nContent.GetIndex() )) ) + { + pOwnRedl = pTmp; + if( nRedlPos + 1 < getIDocumentRedlineAccess().GetRedlineTable().size() ) + { + pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos+1 ]; + if( *pTmp->Start() == *pREnd ) + // then don't! + pOwnRedl = nullptr; + } + + if( pOwnRedl && + !( pRStt->nNode <= aIdx && aIdx <= pREnd->nNode )) + { + // it's not in itself, so don't move it + pOwnRedl = nullptr; + } + } + } + + if( !pOwnRedl ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + // First the Insert, then the Delete + SwPosition aInsPos( aIdx ); + aInsPos.nContent.Assign( aIdx.GetNode().GetContentNode(), 0 ); + + SwPaM aPam( pStt->nNode, 0, aMvRg.aEnd, 0 ); + + SwPaM& rOrigPam(rPam); + rOrigPam.DeleteMark(); + rOrigPam.GetPoint()->nNode = aIdx.GetIndex() - 1; + rOrigPam.GetPoint()->nContent.Assign( rOrigPam.GetContentNode(), 0 ); + + bool bDelLastPara = !aInsPos.nNode.GetNode().IsContentNode(); + + /* When copying to a non-content node Copy will + insert a paragraph before that node and insert before + that inserted node. Copy creates an SwUndoInserts that + does not cover the extra paragraph. Thus we insert the + extra paragraph ourselves, _with_ correct undo + information. */ + if (bDelLastPara) + { + /* aInsPos points to the non-content node. Move it to + the previous content node. */ + SwPaM aInsPam(aInsPos); + const bool bMoved = aInsPam.Move(fnMoveBackward); + OSL_ENSURE(bMoved, "No content node found!"); + + if (bMoved) + { + /* Append the new node after the content node + found. The new position to insert the moved + paragraph at is before the inserted + paragraph. */ + getIDocumentContentOperations().AppendTextNode(*aInsPam.GetPoint()); + aInsPos = *aInsPam.GetPoint(); + } + } + + --aIdx; // move before insertion + + getIDocumentContentOperations().CopyRange(aPam, aInsPos, SwCopyFlags::CheckPosInFly); + + // now delete all the delete redlines that were copied +#ifndef NDEBUG + size_t nRedlines(getIDocumentRedlineAccess().GetRedlineTable().size()); +#endif + if (nOffset > 0) + assert(aPam.End()->nNode.GetIndex() - aPam.Start()->nNode.GetIndex() + nOffset == aInsPos.nNode.GetIndex() - aPam.End()->nNode.GetIndex()); + else + assert(aPam.Start()->nNode.GetIndex() - aPam.End()->nNode.GetIndex() + nOffset == aInsPos.nNode.GetIndex() - aPam.End()->nNode.GetIndex()); + SwRedlineTable::size_type i; + getIDocumentRedlineAccess().GetRedline(*aPam.End(), &i); + for ( ; 0 < i; --i) + { // iterate backwards and offset via the start nodes difference + SwRangeRedline const*const pRedline = getIDocumentRedlineAccess().GetRedlineTable()[i - 1]; + if (*pRedline->End() < *aPam.Start()) + { + break; + } + if (pRedline->GetType() == RedlineType::Delete) + { + assert(*aPam.Start() <= *pRedline->Start()); // caller's fault + SwRangeRedline* pNewRedline; + { + SwPaM pam(*pRedline, nullptr); + sal_uLong const nCurrentOffset( + aIdx.GetIndex() + 1 - aPam.Start()->nNode.GetIndex()); + pam.GetPoint()->nNode += nCurrentOffset; + pam.GetPoint()->nContent.Assign(pam.GetPoint()->nNode.GetNode().GetContentNode(), pam.GetPoint()->nContent.GetIndex()); + pam.GetMark()->nNode += nCurrentOffset; + pam.GetMark()->nContent.Assign(pam.GetMark()->nNode.GetNode().GetContentNode(), pam.GetMark()->nContent.GetIndex()); + + pNewRedline = new SwRangeRedline( RedlineType::Delete, pam ); + } + // note: effectively this will DeleteAndJoin the pam! + getIDocumentRedlineAccess().AppendRedline(pNewRedline, true); + assert(getIDocumentRedlineAccess().GetRedlineTable().size() <= nRedlines); + } + } + + if( bDelLastPara ) + { + // We need to remove the last empty Node again + aIdx = aInsPos.nNode; + SwContentNode* pCNd = SwNodes::GoPrevious( &aInsPos.nNode ); + aInsPos.nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + + // All, that are in the to-be-deleted Node, need to be + // moved to the next Node + for(SwRangeRedline* pTmp : getIDocumentRedlineAccess().GetRedlineTable()) + { + SwPosition* pPos = &pTmp->GetBound(); + if( pPos->nNode == aIdx ) + { + ++pPos->nNode; + pPos->nContent.Assign( pPos->nNode.GetNode().GetContentNode(),0); + } + pPos = &pTmp->GetBound(false); + if( pPos->nNode == aIdx ) + { + ++pPos->nNode; + pPos->nContent.Assign( pPos->nNode.GetNode().GetContentNode(),0); + } + } + CorrRel( aIdx, aInsPos ); + + if (pCNd) + pCNd->JoinNext(); + } + + ++rOrigPam.GetPoint()->nNode; + rOrigPam.GetPoint()->nContent.Assign( rOrigPam.GetContentNode(), 0 ); + assert(*aPam.GetMark() < *aPam.GetPoint()); + if (aPam.GetPoint()->nNode.GetNode().IsEndNode()) + { // ensure redline ends on content node + --aPam.GetPoint()->nNode; + assert(aPam.GetPoint()->nNode.GetNode().IsTextNode()); + SwTextNode *const pNode(aPam.GetPoint()->nNode.GetNode().GetTextNode()); + aPam.GetPoint()->nContent.Assign(pNode, pNode->Len()); + } + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in DeleteAndJoin"); + + getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete ); + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRedlineDelete>(aPam, SwUndoId::DELETE)); + } + + SwRangeRedline* pNewRedline = new SwRangeRedline( RedlineType::Delete, aPam ); + + // prevent assertion from aPam's target being deleted + // (Alternatively, one could just let aPam go out of scope, but + // that requires touching a lot of code.) + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound(false).nContent.Assign( nullptr, 0 ); + + getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + + aPam.GetBound().nContent.Assign(aPam.GetBound().nNode.GetNode().GetContentNode(), 0); + aPam.GetBound(false).nContent.Assign(aPam.GetBound(false).nNode.GetNode().GetContentNode(), 0); + sw::UpdateFramesForAddDeleteRedline(*this, aPam); + + getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + getIDocumentState().SetModified(); + + return true; + } + } + + if( !pOwnRedl && !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aTemp(aIdx); + getIDocumentRedlineAccess().SplitRedline(aTemp); + } + + sal_uLong nRedlSttNd(0), nRedlEndNd(0); + if( pOwnRedl ) + { + const SwPosition *pRStt = pOwnRedl->Start(), *pREnd = pOwnRedl->End(); + nRedlSttNd = pRStt->nNode.GetIndex(); + nRedlEndNd = pREnd->nNode.GetIndex(); + } + + std::unique_ptr<SwUndoMoveNum> pUndo; + sal_uLong nMoved = 0; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoMoveNum( rPam, nOffset, bIsOutlMv )); + nMoved = rPam.End()->nNode.GetIndex() - rPam.Start()->nNode.GetIndex() + 1; + } + + (void) pLayout; // note: move will insert between aIdx-1 and aIdx + assert(!pLayout // check not moving *into* delete redline (caller's fault) + || aIdx.GetNode().GetRedlineMergeFlag() == SwNode::Merge::None + || aIdx.GetNode().GetRedlineMergeFlag() == SwNode::Merge::First); + getIDocumentContentOperations().MoveNodeRange( aMvRg, aIdx, SwMoveFlags::REDLINES ); + + if( pUndo ) + { + // i57907: Under circumstances (sections at the end of a chapter) + // the rPam.Start() is not moved to the new position. + // But aIdx should be at the new end position and as long as the + // number of moved paragraphs is nMoved, I know, where the new + // position is. + pUndo->SetStartNode( aIdx.GetIndex() - nMoved ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + if( pOwnRedl ) + { + SwPosition *pRStt = pOwnRedl->Start(), *pREnd = pOwnRedl->End(); + if( pRStt->nNode.GetIndex() != nRedlSttNd ) + { + pRStt->nNode = nRedlSttNd; + pRStt->nContent.Assign( pRStt->nNode.GetNode().GetContentNode(),0); + } + if( pREnd->nNode.GetIndex() != nRedlEndNd ) + { + pREnd->nNode = nRedlEndNd; + SwContentNode* pCNd = pREnd->nNode.GetNode().GetContentNode(); + pREnd->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + } + } + + getIDocumentState().SetModified(); + return true; +} + +bool SwDoc::NumOrNoNum( const SwNodeIndex& rIdx, bool bDel ) +{ + bool bResult = false; + SwTextNode * pTextNd = rIdx.GetNode().GetTextNode(); + + if (pTextNd && pTextNd->GetNumRule() != nullptr && + (pTextNd->HasNumber() || pTextNd->HasBullet())) + { + if ( !pTextNd->IsCountedInList() == !bDel) + { + bool bOldNum = bDel; + bool bNewNum = !bDel; + pTextNd->SetCountedInList(bNewNum); + + getIDocumentState().SetModified(); + + bResult = true; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumOrNoNum>(rIdx, bOldNum, bNewNum)); + } + } + else if (bDel && pTextNd->GetNumRule(false) && + pTextNd->GetActualListLevel() >= 0 && + pTextNd->GetActualListLevel() < MAXLEVEL) + { + SwPaM aPam(*pTextNd); + DelNumRules(aPam); + + bResult = true; + } + } + + return bResult; +} + +SwNumRule* SwDoc::GetNumRuleAtPos(SwPosition& rPos, + SwRootFrame const*const pLayout) +{ + SwNumRule* pRet = nullptr; + SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + + if ( pTNd != nullptr ) + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTNd)) + { + pTNd = static_cast<SwTextFrame*>(pTNd->getLayoutFrame(pLayout))->GetMergedPara()->pParaPropsNode; + rPos.nNode = *pTNd; + rPos.nContent.Assign(pTNd, 0); + } + pRet = pTNd->GetNumRule(); + } + + return pRet; +} + +sal_uInt16 SwDoc::FindNumRule( const OUString& rName ) const +{ + for( sal_uInt16 n = mpNumRuleTable->size(); n; ) + if( (*mpNumRuleTable)[ --n ]->GetName() == rName ) + return n; + + return USHRT_MAX; +} + +SwNumRule* SwDoc::FindNumRulePtr( const OUString& rName ) const +{ + SwNumRule * pResult = maNumRuleMap[rName]; + + if ( !pResult ) + { + for (size_t n = 0; n < mpNumRuleTable->size(); ++n) + { + if ((*mpNumRuleTable)[n]->GetName() == rName) + { + pResult = (*mpNumRuleTable)[n]; + + break; + } + } + } + + return pResult; +} + +void SwDoc::AddNumRule(SwNumRule * pRule) +{ + if ((SAL_MAX_UINT16 - 1) <= mpNumRuleTable->size()) + { + OSL_ENSURE(false, "SwDoc::AddNumRule: table full."); + abort(); // this should never happen on real documents + } + mpNumRuleTable->push_back(pRule); + maNumRuleMap[pRule->GetName()] = pRule; + pRule->SetNumRuleMap(&maNumRuleMap); + + getIDocumentListsAccess().createListForListStyle( pRule->GetName() ); +} + +sal_uInt16 SwDoc::MakeNumRule( const OUString &rName, + const SwNumRule* pCpy, + bool bBroadcast, + const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode ) +{ + SwNumRule* pNew; + if( pCpy ) + { + pNew = new SwNumRule( *pCpy ); + + pNew->SetName( GetUniqueNumRuleName( &rName ), getIDocumentListsAccess() ); + + if( pNew->GetName() != rName ) + { + pNew->SetPoolFormatId( USHRT_MAX ); + pNew->SetPoolHelpId( USHRT_MAX ); + pNew->SetPoolHlpFileId( UCHAR_MAX ); + pNew->SetDefaultListId( OUString() ); + } + pNew->CheckCharFormats( this ); + } + else + { + pNew = new SwNumRule( GetUniqueNumRuleName( &rName ), + eDefaultNumberFormatPositionAndSpaceMode ); + } + + sal_uInt16 nRet = mpNumRuleTable->size(); + + AddNumRule(pNew); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumruleCreate>(pNew, this)); + } + + if (bBroadcast) + BroadcastStyleOperation(pNew->GetName(), SfxStyleFamily::Pseudo, + SfxHintId::StyleSheetCreated); + + return nRet; +} + +OUString SwDoc::GetUniqueNumRuleName( const OUString* pChkStr, bool bAutoNum ) const +{ + // If we got pChkStr, then the caller expects that in case it's not yet + // used, it'll be returned. + if( IsInMailMerge() && !pChkStr ) + { + OUString newName = "MailMergeNumRule" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpNumRuleTable->size() + 1 ); + return newName; + } + + OUString aName; + if( bAutoNum ) + { + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + if (bHack) + { + static sal_Int64 nIdCounter = SAL_CONST_INT64(8000000000); + aName = OUString::number(nIdCounter++); + } + else + { + unsigned int const n(comphelper::rng::uniform_uint_distribution(0, + std::numeric_limits<unsigned int>::max())); + aName = OUString::number(n); + } + if( pChkStr && pChkStr->isEmpty() ) + pChkStr = nullptr; + } + else if( pChkStr && !pChkStr->isEmpty() ) + aName = *pChkStr; + else + { + pChkStr = nullptr; + aName = SwResId( STR_NUMRULE_DEFNAME ); + } + + sal_uInt16 nNum(0), nTmp, nFlagSize = ( mpNumRuleTable->size() / 8 ) +2; + std::unique_ptr<sal_uInt8[]> pSetFlags(new sal_uInt8[ nFlagSize ]); + memset( pSetFlags.get(), 0, nFlagSize ); + + sal_Int32 nNmLen = aName.getLength(); + if( !bAutoNum && pChkStr ) + { + while( nNmLen-- && '0' <= aName[nNmLen] && aName[nNmLen] <= '9' ) + ; //nop + + if( ++nNmLen < aName.getLength() ) + { + aName = aName.copy(0, nNmLen ); + pChkStr = nullptr; + } + } + + for( auto const & pNumRule: *mpNumRuleTable ) + if( nullptr != pNumRule ) + { + const OUString sNm = pNumRule->GetName(); + if( sNm.startsWith( aName ) ) + { + // Determine Number and set the Flag + nNum = static_cast<sal_uInt16>(sNm.copy( nNmLen ).toInt32()); + if( nNum-- && nNum < mpNumRuleTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + if( pChkStr && *pChkStr==sNm ) + pChkStr = nullptr; + } + + if( !pChkStr ) + { + // All Numbers have been flagged accordingly, so identify the right Number + nNum = mpNumRuleTable->size(); + for( sal_uInt16 n = 0; n < nFlagSize; ++n ) + if( 0xff != ( nTmp = pSetFlags[ n ] )) + { + // identify the Number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + if( pChkStr && !pChkStr->isEmpty() ) + return *pChkStr; + return aName + OUString::number( ++nNum ); +} + +void SwDoc::UpdateNumRule() +{ + const SwNumRuleTable& rNmTable = GetNumRuleTable(); + for( size_t n = 0; n < rNmTable.size(); ++n ) + if( rNmTable[ n ]->IsInvalidRule() ) + rNmTable[ n ]->Validate(); +} + +void SwDoc::MarkListLevel( const OUString& sListId, + const int nListLevel, + const bool bValue ) +{ + SwList* pList = getIDocumentListsAccess().getListByName( sListId ); + + if ( pList ) + { + // Set new marked list level and notify all affected nodes of the changed mark. + pList->MarkListLevel( nListLevel, bValue ); + } +} + +bool SwDoc::IsFirstOfNumRuleAtPos(const SwPosition & rPos, + SwRootFrame const& rLayout) +{ + bool bResult = false; + + const SwTextNode *const pTextNode = sw::GetParaPropsNode(rLayout, rPos.nNode); + if ( pTextNode != nullptr ) + { + bResult = pTextNode->IsFirstOfNumRule(rLayout); + } + + return bResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx new file mode 100644 index 000000000..b98e89caf --- /dev/null +++ b/sw/source/core/doc/docredln.cxx @@ -0,0 +1,1906 @@ +/* -*- 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 <libxml/xmlwriter.h> +#include <boost/property_tree/json_parser.hpp> + +#include <sal/log.hxx> +#include <tools/datetimeutils.hxx> +#include <hintids.hxx> +#include <svl/itemiter.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <unotools/datetime.hxx> +#include <sfx2/viewsh.hxx> +#include <swmodule.hxx> +#include <doc.hxx> +#include <docredln.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <redline.hxx> +#include <UndoCore.hxx> +#include <hints.hxx> +#include <pamtyp.hxx> +#include <poolfmt.hxx> +#include <view.hxx> +#include <viewsh.hxx> +#include <viscrs.hxx> +#include <rootfrm.hxx> +#include <strings.hrc> +#include <wrtsh.hxx> +#include <txtfld.hxx> + +#include <flowfrm.hxx> + +using namespace com::sun::star; + +#ifdef DBG_UTIL + + void sw_DebugRedline( const SwDoc* pDoc ) + { + static SwRedlineTable::size_type nWatch = 0; // loplugin:constvars:ignore + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( SwRedlineTable::size_type n = 0; n < rTable.size(); ++n ) + { + SwRedlineTable::size_type nDummy = 0; + const SwRangeRedline* pCurrent = rTable[ n ]; + const SwRangeRedline* pNext = n+1 < rTable.size() ? rTable[ n+1 ] : nullptr; + if( pCurrent == pNext ) + ++nDummy; + if( n == nWatch ) + ++nDummy; // Possible debugger breakpoint + } + } + +#endif + + +SwExtraRedlineTable::~SwExtraRedlineTable() +{ + DeleteAndDestroyAll(); +} + +void SwExtraRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedlineTable")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + for (sal_uInt16 nCurExtraRedlinePos = 0; nCurExtraRedlinePos < GetSize(); ++nCurExtraRedlinePos) + { + const SwExtraRedline* pExtraRedline = GetRedline(nCurExtraRedlinePos); + xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedline")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*pExtraRedline).name())); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); +} + +#if OSL_DEBUG_LEVEL > 0 +static bool CheckPosition( const SwPosition* pStt, const SwPosition* pEnd ) +{ + int nError = 0; + SwNode* pSttNode = &pStt->nNode.GetNode(); + SwNode* pEndNode = &pEnd->nNode.GetNode(); + SwNode* pSttTab = pSttNode->StartOfSectionNode()->FindTableNode(); + SwNode* pEndTab = pEndNode->StartOfSectionNode()->FindTableNode(); + SwNode* pSttStart = pSttNode; + while( pSttStart && (!pSttStart->IsStartNode() || pSttStart->IsSectionNode() || + pSttStart->IsTableNode() ) ) + pSttStart = pSttStart->StartOfSectionNode(); + SwNode* pEndStart = pEndNode; + while( pEndStart && (!pEndStart->IsStartNode() || pEndStart->IsSectionNode() || + pEndStart->IsTableNode() ) ) + pEndStart = pEndStart->StartOfSectionNode(); + assert(pSttTab == pEndTab); + if( pSttTab != pEndTab ) + nError = 1; + assert(pSttTab || pSttStart == pEndStart); + if( !pSttTab && pSttStart != pEndStart ) + nError |= 2; + if( nError ) + nError += 10; + return nError != 0; +} +#endif + +bool SwExtraRedlineTable::DeleteAllTableRedlines( SwDoc* pDoc, const SwTable& rTable, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines + /* + SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange ); + if( pUndo->GetRedlSaveCount() ) + { + GetIDocumentUndoRedo().AppendUndo(pUndo); + } + else + delete pUndo; + */ + } + + for (sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ) + { + SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos); + const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline); + if (pTableCellRedline) + { + const SwTableBox *pRedTabBox = &pTableCellRedline->GetTableBox(); + const SwTable& rRedTable = pRedTabBox->GetSttNd()->FindTableNode()->GetTable(); + if ( &rRedTable == &rTable ) + { + // Redline for this table + const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType) + { + + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + continue; // don't increment position after delete + } + } + } + else + { + const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline); + if (pTableRowRedline) + { + const SwTableLine *pRedTabLine = &pTableRowRedline->GetTableLine(); + const SwTableBoxes &rRedTabBoxes = pRedTabLine->GetTabBoxes(); + const SwTable& rRedTable = rRedTabBoxes[0]->GetSttNd()->FindTableNode()->GetTable(); + if ( &rRedTable == &rTable ) + { + // Redline for this table + const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType) + + { + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + continue; // don't increment position after delete + } + } + } + } + ++nCurRedlinePos; + } + + if( bChg ) + pDoc->getIDocumentState().SetModified(); + + return bChg; +} + +bool SwExtraRedlineTable::DeleteTableRowRedline( SwDoc* pDoc, const SwTableLine& rTableLine, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines + /* + SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange ); + if( pUndo->GetRedlSaveCount() ) + { + GetIDocumentUndoRedo().AppendUndo(pUndo); + } + else + delete pUndo; + */ + } + + for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos ) + { + SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos); + const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline); + const SwTableLine *pRedTabLine = pTableRowRedline ? &pTableRowRedline->GetTableLine() : nullptr; + if ( pRedTabLine == &rTableLine ) + { + // Redline for this table row + const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType ) + continue; + + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + } + } + + if( bChg ) + pDoc->getIDocumentState().SetModified(); + + return bChg; +} + +bool SwExtraRedlineTable::DeleteTableCellRedline( SwDoc* pDoc, const SwTableBox& rTableBox, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines + /* + SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange ); + if( pUndo->GetRedlSaveCount() ) + { + GetIDocumentUndoRedo().AppendUndo(pUndo); + } + else + delete pUndo; + */ + } + + for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos ) + { + SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos); + const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline); + const SwTableBox *pRedTabBox = pTableCellRedline ? &pTableCellRedline->GetTableBox() : nullptr; + if ( pRedTabBox == &rTableBox ) + { + // Redline for this table cell + const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType ) + continue; + + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + } + } + + if( bChg ) + pDoc->getIDocumentState().SetModified(); + + return bChg; +} + +namespace +{ + +void lcl_LOKInvalidateFrames(const SwModify& rMod, const SwRootFrame* pLayout, + SwFrameType const nFrameType, const Point* pPoint) +{ + SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti> aIter(rMod); + + for (SwFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() ) + { + if ((pTmpFrame->GetType() & nFrameType) && + (!pLayout || pLayout == pTmpFrame->getRootFrame()) && + (!pTmpFrame->IsFlowFrame() || !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow())) + { + if (pPoint) + { + pTmpFrame->InvalidateSize(); + } + } + } +} + +void lcl_LOKInvalidateStartEndFrames(SwShellCursor& rCursor) +{ + if (!(rCursor.HasMark() && + rCursor.GetPoint()->nNode.GetNode().IsContentNode() && + rCursor.GetPoint()->nNode.GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()) && + (rCursor.GetMark()->nNode == rCursor.GetPoint()->nNode || + (rCursor.GetMark()->nNode.GetNode().IsContentNode() && + rCursor.GetMark()->nNode.GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()))))) + { + return; + } + + + SwPosition *pStartPos = rCursor.Start(), + *pEndPos = rCursor.GetPoint() == pStartPos ? rCursor.GetMark() : rCursor.GetPoint(); + + + lcl_LOKInvalidateFrames(*(pStartPos->nNode.GetNode().GetContentNode()), + rCursor.GetShell()->GetLayout(), + FRM_CNTNT, &rCursor.GetSttPos()); + + lcl_LOKInvalidateFrames(*(pEndPos->nNode.GetNode().GetContentNode()), + rCursor.GetShell()->GetLayout(), + FRM_CNTNT, &rCursor.GetEndPos()); +} + +} // anonymous namespace + +/// Emits LOK notification about one addition / removal of a redline item. +void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, SwRangeRedline* pRedline) +{ + // Disable since usability is very low beyond some small number of changes. + static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr; + if (!comphelper::LibreOfficeKit::isActive() || bDisableRedlineComments) + return; + + boost::property_tree::ptree aRedline; + aRedline.put("action", (nType == RedlineNotification::Add ? "Add" : + (nType == RedlineNotification::Remove ? "Remove" : + (nType == RedlineNotification::Modify ? "Modify" : "???")))); + aRedline.put("index", pRedline->GetId()); + aRedline.put("author", pRedline->GetAuthorString(1).toUtf8().getStr()); + aRedline.put("type", SwRedlineTypeToOUString(pRedline->GetRedlineData().GetType()).toUtf8().getStr()); + aRedline.put("comment", pRedline->GetRedlineData().GetComment().toUtf8().getStr()); + aRedline.put("description", pRedline->GetDescr().toUtf8().getStr()); + OUString sDateTime = utl::toISO8601(pRedline->GetRedlineData().GetTimeStamp().GetUNODateTime()); + aRedline.put("dateTime", sDateTime.toUtf8().getStr()); + + SwPosition* pStartPos = pRedline->Start(); + SwPosition* pEndPos = pRedline->End(); + SwContentNode* pContentNd = pRedline->GetContentNode(); + SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current()); + if (pView && pContentNd) + { + SwShellCursor aCursor(pView->GetWrtShell(), *pStartPos); + aCursor.SetMark(); + aCursor.GetMark()->nNode = pEndPos->nNode; + aCursor.GetMark()->nContent = pEndPos->nContent; + + aCursor.FillRects(); + + SwRects* pRects(&aCursor); + std::vector<OString> aRects; + for(const SwRect& rNextRect : *pRects) + aRects.push_back(rNextRect.SVRect().toString()); + + const OString sRects = comphelper::string::join("; ", aRects); + aRedline.put("textRange", sRects.getStr()); + + lcl_LOKInvalidateStartEndFrames(aCursor); + + // When this notify method is called text invalidation is not done yet + // Calling FillRects updates the text area so invalidation will not run on the correct rects + // So we need to do an own invalidation here. It invalidates text frames containing the redlining + SwDoc* pDoc = pRedline->GetDoc(); + SwViewShell* pSh; + if( pDoc && !pDoc->IsInDtor() ) + { + pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh ) + for(SwNodeIndex nIdx = pStartPos->nNode; nIdx <= pEndPos->nNode; ++nIdx) + { + SwContentNode* pContentNode = nIdx.GetNode().GetContentNode(); + if (pContentNode) + pSh->InvalidateWindows(pContentNode->FindLayoutRect()); + } + } + } + + boost::property_tree::ptree aTree; + aTree.add_child("redline", aRedline); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aPayload = aStream.str(); + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + pViewShell->libreOfficeKitViewCallback(nType == RedlineNotification::Modify ? LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED : LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED, aPayload.c_str()); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +bool SwRedlineTable::Insert(SwRangeRedline*& p) +{ + if( p->HasValidRange() ) + { + std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p ); + size_type nP = rv.first - begin(); + LOKRedlineNotification(RedlineNotification::Add, p); + p->CallDisplayFunc(nP); + return rv.second; + } + return InsertWithValidRanges( p ); +} + +bool SwRedlineTable::Insert(SwRangeRedline*& p, size_type& rP) +{ + if( p->HasValidRange() ) + { + std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p ); + rP = rv.first - begin(); + p->CallDisplayFunc(rP); + return rv.second; + } + return InsertWithValidRanges( p, &rP ); +} + +namespace sw { + +std::vector<SwRangeRedline*> GetAllValidRanges(std::unique_ptr<SwRangeRedline> p) +{ + std::vector<SwRangeRedline*> ret; + // Create valid "sub-ranges" from the Selection + SwPosition* pStt = p->Start(), + * pEnd = pStt == p->GetPoint() ? p->GetMark() : p->GetPoint(); + SwPosition aNewStt( *pStt ); + SwNodes& rNds = aNewStt.nNode.GetNodes(); + SwContentNode* pC; + + if( !aNewStt.nNode.GetNode().IsContentNode() ) + { + pC = rNds.GoNext( &aNewStt.nNode ); + if( pC ) + aNewStt.nContent.Assign( pC, 0 ); + else + aNewStt.nNode = rNds.GetEndOfContent(); + } + + SwRangeRedline* pNew = nullptr; + + if( aNewStt < *pEnd ) + do { + if( !pNew ) + pNew = new SwRangeRedline( p->GetRedlineData(), aNewStt ); + else + { + pNew->DeleteMark(); + *pNew->GetPoint() = aNewStt; + } + + pNew->SetMark(); + GoEndSection( pNew->GetPoint() ); + // i60396: If the redlines starts before a table but the table is the last member + // of the section, the GoEndSection will end inside the table. + // This will result in an incorrect redline, so we've to go back + SwNode* pTab = pNew->GetPoint()->nNode.GetNode().StartOfSectionNode()->FindTableNode(); + // We end in a table when pTab != 0 + if( pTab && !pNew->GetMark()->nNode.GetNode().StartOfSectionNode()->FindTableNode() ) + { // but our Mark was outside the table => Correction + do + { + // We want to be before the table + *pNew->GetPoint() = SwPosition(*pTab); + pC = GoPreviousNds( &pNew->GetPoint()->nNode, false ); // here we are. + if( pC ) + pNew->GetPoint()->nContent.Assign( pC, 0 ); + pTab = pNew->GetPoint()->nNode.GetNode().StartOfSectionNode()->FindTableNode(); + } while( pTab ); // If there is another table we have to repeat our step backwards + } + + if( *pNew->GetPoint() > *pEnd ) + { + pC = nullptr; + if( aNewStt.nNode != pEnd->nNode ) + do { + SwNode& rCurNd = aNewStt.nNode.GetNode(); + if( rCurNd.IsStartNode() ) + { + if( rCurNd.EndOfSectionIndex() < pEnd->nNode.GetIndex() ) + aNewStt.nNode = *rCurNd.EndOfSectionNode(); + else + break; + } + else if( rCurNd.IsContentNode() ) + pC = rCurNd.GetContentNode(); + ++aNewStt.nNode; + } while( aNewStt.nNode.GetIndex() < pEnd->nNode.GetIndex() ); + + if( aNewStt.nNode == pEnd->nNode ) + aNewStt.nContent = pEnd->nContent; + else if( pC ) + { + aNewStt.nNode = *pC; + aNewStt.nContent.Assign( pC, pC->Len() ); + } + + if( aNewStt <= *pEnd ) + *pNew->GetPoint() = aNewStt; + } + else + aNewStt = *pNew->GetPoint(); +#if OSL_DEBUG_LEVEL > 0 + CheckPosition( pNew->GetPoint(), pNew->GetMark() ); +#endif + + if( *pNew->GetPoint() != *pNew->GetMark() && + pNew->HasValidRange()) + { + ret.push_back(pNew); + pNew = nullptr; + } + + if( aNewStt >= *pEnd ) + break; + pC = rNds.GoNext( &aNewStt.nNode ); + if( !pC ) + break; + + aNewStt.nContent.Assign( pC, 0 ); + + } while( aNewStt < *pEnd ); + + delete pNew; + p.reset(); + return ret; +} + +} // namespace sw + +bool SwRedlineTable::InsertWithValidRanges(SwRangeRedline*& p, size_type* pInsPos) +{ + bool bAnyIns = false; + std::vector<SwRangeRedline*> const redlines( + GetAllValidRanges(std::unique_ptr<SwRangeRedline>(p))); + for (SwRangeRedline * pRedline : redlines) + { + assert(pRedline->HasValidRange()); + size_type nInsPos; + if (Insert(pRedline, nInsPos)) + { + pRedline->CallDisplayFunc(nInsPos); + bAnyIns = true; + if (pInsPos && *pInsPos < nInsPos) + { + *pInsPos = nInsPos; + } + } + } + p = nullptr; + return bAnyIns; +} + +bool CompareSwRedlineTable::operator()(SwRangeRedline* const &lhs, SwRangeRedline* const &rhs) const +{ + return *lhs < *rhs; +} + +SwRedlineTable::~SwRedlineTable() +{ + maVector.DeleteAndDestroyAll(); +} + +SwRedlineTable::size_type SwRedlineTable::GetPos(const SwRangeRedline* p) const +{ + vector_type::const_iterator it = maVector.find(const_cast<SwRangeRedline*>(p)); + if( it == maVector.end() ) + return npos; + return it - maVector.begin(); +} + +void SwRedlineTable::Remove( const SwRangeRedline* p ) +{ + const size_type nPos = GetPos(p); + if (nPos == npos) + return; + Remove(nPos); +} + +void SwRedlineTable::Remove( size_type nP ) +{ + LOKRedlineNotification(RedlineNotification::Remove, maVector[nP]); + SwDoc* pDoc = nullptr; + if( !nP && 1 == size() ) + pDoc = maVector.front()->GetDoc(); + + maVector.erase( maVector.begin() + nP ); + + if( pDoc && !pDoc->IsInDtor() ) + { + SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh ) + pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) ); + } +} + +void SwRedlineTable::DeleteAndDestroyAll() +{ + while (!maVector.empty()) + { + auto const pRedline = maVector.back(); + maVector.erase(maVector.size() - 1); + LOKRedlineNotification(RedlineNotification::Remove, pRedline); + delete pRedline; + } +} + +void SwRedlineTable::DeleteAndDestroy(size_type const nP) +{ + auto const pRedline = maVector[nP]; + maVector.erase(maVector.begin() + nP); + LOKRedlineNotification(RedlineNotification::Remove, pRedline); + delete pRedline; +} + +SwRedlineTable::size_type SwRedlineTable::FindNextOfSeqNo( size_type nSttPos ) const +{ + return nSttPos + 1 < size() + ? FindNextSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos+1 ) + : npos; +} + +SwRedlineTable::size_type SwRedlineTable::FindPrevOfSeqNo( size_type nSttPos ) const +{ + return nSttPos ? FindPrevSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos-1 ) + : npos; +} + +/// Find the next or preceding Redline with the same seq.no. +/// We can limit the search using look ahead (0 searches the whole array). +SwRedlineTable::size_type SwRedlineTable::FindNextSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const +{ + auto const nLookahead = 20; + size_type nRet = npos; + if( nSeqNo && nSttPos < size() ) + { + size_type nEnd = size(); + const size_type nTmp = nSttPos + nLookahead; + if (nTmp < nEnd) + { + nEnd = nTmp; + } + + for( ; nSttPos < nEnd; ++nSttPos ) + if( nSeqNo == operator[]( nSttPos )->GetSeqNo() ) + { + nRet = nSttPos; + break; + } + } + return nRet; +} + +SwRedlineTable::size_type SwRedlineTable::FindPrevSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const +{ + auto const nLookahead = 20; + size_type nRet = npos; + if( nSeqNo && nSttPos < size() ) + { + size_type nEnd = 0; + if( nSttPos > nLookahead ) + nEnd = nSttPos - nLookahead; + + ++nSttPos; + while( nSttPos > nEnd ) + if( nSeqNo == operator[]( --nSttPos )->GetSeqNo() ) + { + nRet = nSttPos; + break; + } + } + return nRet; +} + +const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos, + size_type& rPos, + bool bNext ) const +{ + const SwRangeRedline* pFnd = nullptr; + for( ; rPos < maVector.size() ; ++rPos ) + { + const SwRangeRedline* pTmp = (*this)[ rPos ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + const SwPosition* pRStt = pTmp->Start(), + * pREnd = pRStt == pTmp->GetPoint() ? pTmp->GetMark() + : pTmp->GetPoint(); + if( bNext ? *pRStt <= rSttPos : *pRStt < rSttPos ) + { + if( bNext ? *pREnd > rSttPos : *pREnd >= rSttPos ) + { + pFnd = pTmp; + break; + } + } + else + break; + } + } + return pFnd; +} + +void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineTable")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + for (SwRedlineTable::size_type nCurRedlinePos = 0; nCurRedlinePos < size(); ++nCurRedlinePos) + operator[](nCurRedlinePos)->dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +SwRedlineExtraData::~SwRedlineExtraData() +{ +} + +void SwRedlineExtraData::Reject( SwPaM& ) const +{ +} + +bool SwRedlineExtraData::operator == ( const SwRedlineExtraData& ) const +{ + return false; +} + +SwRedlineExtraData_FormatColl::SwRedlineExtraData_FormatColl( const OUString& rColl, + sal_uInt16 nPoolFormatId, + const SfxItemSet* pItemSet, + bool bFormatAll ) + : m_sFormatNm(rColl), m_nPoolId(nPoolFormatId), m_bFormatAll(bFormatAll) +{ + if( pItemSet && pItemSet->Count() ) + m_pSet.reset( new SfxItemSet( *pItemSet ) ); +} + +SwRedlineExtraData_FormatColl::~SwRedlineExtraData_FormatColl() +{ +} + +SwRedlineExtraData* SwRedlineExtraData_FormatColl::CreateNew() const +{ + return new SwRedlineExtraData_FormatColl( m_sFormatNm, m_nPoolId, m_pSet.get(), m_bFormatAll ); +} + +void SwRedlineExtraData_FormatColl::Reject( SwPaM& rPam ) const +{ + SwDoc* pDoc = rPam.GetDoc(); + + // What about Undo? Is it turned off? + SwTextFormatColl* pColl = USHRT_MAX == m_nPoolId + ? pDoc->FindTextFormatCollByName( m_sFormatNm ) + : pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( m_nPoolId ); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + + const SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + + if ( !m_bFormatAll || pEnd->nContent == 0 ) + { + // don't reject the format of the next paragraph (that is handled by the next redline) + if (aPam.GetPoint()->nNode > aPam.GetMark()->nNode) + { + aPam.GetPoint()->nNode--; + SwContentNode* pNode = aPam.GetPoint()->nNode.GetNode().GetContentNode(); + aPam.GetPoint()->nContent.Assign( pNode, pNode->Len() ); + } + else if (aPam.GetPoint()->nNode < aPam.GetMark()->nNode) + { + aPam.GetMark()->nNode--; + SwContentNode* pNode = aPam.GetMark()->nNode.GetNode().GetContentNode(); + aPam.GetMark()->nContent.Assign( pNode, pNode->Len() ); + } + } + + if( pColl ) + pDoc->SetTextFormatColl( aPam, pColl, false ); + + if( m_pSet ) + pDoc->getIDocumentContentOperations().InsertItemSet( aPam, *m_pSet ); + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwRedlineExtraData_FormatColl::operator == ( const SwRedlineExtraData& r) const +{ + const SwRedlineExtraData_FormatColl& rCmp = static_cast<const SwRedlineExtraData_FormatColl&>(r); + return m_sFormatNm == rCmp.m_sFormatNm && m_nPoolId == rCmp.m_nPoolId && + m_bFormatAll == rCmp.m_bFormatAll && + ( ( !m_pSet && !rCmp.m_pSet ) || + ( m_pSet && rCmp.m_pSet && *m_pSet == *rCmp.m_pSet ) ); +} + +void SwRedlineExtraData_FormatColl::SetItemSet( const SfxItemSet& rSet ) +{ + if( rSet.Count() ) + m_pSet.reset( new SfxItemSet( rSet ) ); + else + m_pSet.reset(); +} + +SwRedlineExtraData_Format::SwRedlineExtraData_Format( const SfxItemSet& rSet ) +{ + SfxItemIter aIter( rSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + m_aWhichIds.push_back( pItem->Which() ); + } +} + +SwRedlineExtraData_Format::SwRedlineExtraData_Format( + const SwRedlineExtraData_Format& rCpy ) + : SwRedlineExtraData() +{ + m_aWhichIds.insert( m_aWhichIds.begin(), rCpy.m_aWhichIds.begin(), rCpy.m_aWhichIds.end() ); +} + +SwRedlineExtraData_Format::~SwRedlineExtraData_Format() +{ +} + +SwRedlineExtraData* SwRedlineExtraData_Format::CreateNew() const +{ + return new SwRedlineExtraData_Format( *this ); +} + +void SwRedlineExtraData_Format::Reject( SwPaM& rPam ) const +{ + SwDoc* pDoc = rPam.GetDoc(); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + // Actually we need to reset the Attribute here! + for( const auto& rWhichId : m_aWhichIds ) + { + pDoc->getIDocumentContentOperations().InsertPoolItem( rPam, *GetDfltAttr( rWhichId ), + SetAttrMode::DONTEXPAND ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) const +{ + const size_t nEnd = m_aWhichIds.size(); + if( nEnd != static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds.size() ) + return false; + + for( size_t n = 0; n < nEnd; ++n ) + { + if( static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds[n] != m_aWhichIds[n]) + { + return false; + } + } + return true; +} + +SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut ) + : m_pNext( nullptr ), m_pExtraData( nullptr ), + m_aStamp( DateTime::SYSTEM ), + m_eType( eT ), m_bAutoFormat(false), m_nAuthor( nAut ), m_nSeqNo( 0 ) +{ + m_aStamp.SetNanoSec( 0 ); +} + +SwRedlineData::SwRedlineData( + const SwRedlineData& rCpy, + bool bCpyNext ) + : m_pNext( ( bCpyNext && rCpy.m_pNext ) ? new SwRedlineData( *rCpy.m_pNext ) : nullptr ) + , m_pExtraData( rCpy.m_pExtraData ? rCpy.m_pExtraData->CreateNew() : nullptr ) + , m_sComment( rCpy.m_sComment ) + , m_aStamp( rCpy.m_aStamp ) + , m_eType( rCpy.m_eType ) + , m_bAutoFormat(false) + , m_nAuthor( rCpy.m_nAuthor ) + , m_nSeqNo( rCpy.m_nSeqNo ) +{ +} + +// For sw3io: We now own pNext! +SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT, + const OUString& rCmnt, SwRedlineData *pNxt) + : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(rCmnt), m_aStamp(rDT), + m_eType(eT), m_bAutoFormat(false), m_nAuthor(nAut), m_nSeqNo(0) +{ +} + +SwRedlineData::~SwRedlineData() +{ + delete m_pExtraData; + delete m_pNext; +} + +bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const +{ + DateTime aTime = GetTimeStamp(); + aTime.SetSec(0); + DateTime aCompareTime = rCmp.GetTimeStamp(); + aCompareTime.SetSec(0); + return m_nAuthor == rCmp.m_nAuthor && + m_eType == rCmp.m_eType && + m_sComment == rCmp.m_sComment && + aTime == aCompareTime && + (( !m_pNext && !rCmp.m_pNext ) || + ( m_pNext && rCmp.m_pNext && + m_pNext->CanCombine( *rCmp.m_pNext ))) && + (( !m_pExtraData && !rCmp.m_pExtraData ) || + ( m_pExtraData && rCmp.m_pExtraData && + *m_pExtraData == *rCmp.m_pExtraData )); +} + +/// ExtraData is copied. The Pointer's ownership is thus NOT transferred +/// to the Redline Object! +void SwRedlineData::SetExtraData( const SwRedlineExtraData* pData ) +{ + delete m_pExtraData; + + // Check if there is data - and if so - delete it + if( pData ) + m_pExtraData = pData->CreateNew(); + else + m_pExtraData = nullptr; +} + +static const char* STR_REDLINE_ARY[] = +{ + STR_UNDO_REDLINE_INSERT, + STR_UNDO_REDLINE_DELETE, + STR_UNDO_REDLINE_FORMAT, + STR_UNDO_REDLINE_TABLE, + STR_UNDO_REDLINE_FMTCOLL, + STR_UNDO_REDLINE_PARAGRAPH_FORMAT, + STR_UNDO_REDLINE_TABLE_ROW_INSERT, + STR_UNDO_REDLINE_TABLE_ROW_DELETE, + STR_UNDO_REDLINE_TABLE_CELL_INSERT, + STR_UNDO_REDLINE_TABLE_CELL_DELETE +}; + +OUString SwRedlineData::GetDescr() const +{ + return SwResId(STR_REDLINE_ARY[static_cast<int>(GetType())]); +} + +sal_uInt32 SwRangeRedline::m_nLastId = 1; + +SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), + m_pRedlineData( new SwRedlineData( eTyp, GetDoc()->getIDocumentRedlineAccess().GetRedlineAuthor() ) ), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rPam.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), + m_pRedlineData( new SwRedlineData( rData )), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rPam.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos ) + : SwPaM( rPos ), + m_pRedlineData( new SwRedlineData( rData )), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; +} + +SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy ) + : SwPaM( *rCpy.GetMark(), *rCpy.GetPoint() ), + m_pRedlineData( new SwRedlineData( *rCpy.m_pRedlineData )), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rCpy.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::~SwRangeRedline() +{ + if( m_pContentSect ) + { + // delete the ContentSection + if( !GetDoc()->IsInDtor() ) + GetDoc()->getIDocumentContentOperations().DeleteSection( &m_pContentSect->GetNode() ); + delete m_pContentSect; + } + delete m_pRedlineData; +} + +void MaybeNotifyRedlineModification(SwRangeRedline* pRedline, SwDoc* pDoc) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + const SwRedlineTable& rRedTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type i = 0; i < rRedTable.size(); ++i) + { + if (rRedTable[i] == pRedline) + { + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedline); + break; + } + } +} + +void SwRangeRedline::MaybeNotifyRedlinePositionModification(long nTop) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + if(!m_oLOKLastNodeTop || *m_oLOKLastNodeTop != nTop) + { + m_oLOKLastNodeTop = nTop; + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, this); + } +} + +void SwRangeRedline::SetStart( const SwPosition& rPos, SwPosition* pSttPtr ) +{ + if( !pSttPtr ) pSttPtr = Start(); + *pSttPtr = rPos; + + MaybeNotifyRedlineModification(this, GetDoc()); +} + +void SwRangeRedline::SetEnd( const SwPosition& rPos, SwPosition* pEndPtr ) +{ + if( !pEndPtr ) pEndPtr = End(); + *pEndPtr = rPos; + + MaybeNotifyRedlineModification(this, GetDoc()); +} + +/// Do we have a valid Selection? +bool SwRangeRedline::HasValidRange() const +{ + const SwNode* pPtNd = &GetPoint()->nNode.GetNode(), + * pMkNd = &GetMark()->nNode.GetNode(); + if( pPtNd->StartOfSectionNode() == pMkNd->StartOfSectionNode() && + !pPtNd->StartOfSectionNode()->IsTableNode() && + // invalid if points on the end of content + // end-of-content only invalid if no content index exists + ( pPtNd != pMkNd || GetContentIdx() != nullptr || + pPtNd != &pPtNd->GetNodes().GetEndOfContent() ) + ) + return true; + return false; +} + +void SwRangeRedline::CallDisplayFunc(size_t nMyPos) +{ + RedlineFlags eShow = RedlineFlags::ShowMask & GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + if (eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + Show(0, nMyPos); + else if (eShow == RedlineFlags::ShowInsert) + Hide(0, nMyPos); + else if (eShow == RedlineFlags::ShowDelete) + ShowOriginal(0, nMyPos); +} + +void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos) +{ + if( 1 <= nLoop ) + { + SwDoc* pDoc = GetDoc(); + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + switch( GetType() ) + { + case RedlineType::Insert: // Content has been inserted + m_bIsVisible = true; + MoveFromSection(nMyPos); + break; + + case RedlineType::Delete: // Content has been deleted + m_bIsVisible = true; + MoveFromSection(nMyPos); + break; + + case RedlineType::Format: // Attributes have been applied + case RedlineType::Table: // Table structure has been modified + InvalidateRange(Invalidation::Add); + break; + default: + break; + } + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } +} + +void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos) +{ + SwDoc* pDoc = GetDoc(); + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + switch( GetType() ) + { + case RedlineType::Insert: // Content has been inserted + m_bIsVisible = true; + if( 1 <= nLoop ) + MoveFromSection(nMyPos); + break; + + case RedlineType::Delete: // Content has been deleted + m_bIsVisible = false; + switch( nLoop ) + { + case 0: MoveToSection(); break; + case 1: CopyToSection(); break; + case 2: DelCopyOfSection(nMyPos); break; + } + break; + + case RedlineType::Format: // Attributes have been applied + case RedlineType::Table: // Table structure has been modified + if( 1 <= nLoop ) + InvalidateRange(Invalidation::Remove); + break; + default: + break; + } + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwRangeRedline::ShowOriginal(sal_uInt16 nLoop, size_t nMyPos) +{ + SwDoc* pDoc = GetDoc(); + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + SwRedlineData* pCur; + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + // Determine the Type, it's the first on Stack + for( pCur = m_pRedlineData; pCur->m_pNext; ) + pCur = pCur->m_pNext; + + switch( pCur->m_eType ) + { + case RedlineType::Insert: // Content has been inserted + m_bIsVisible = false; + switch( nLoop ) + { + case 0: MoveToSection(); break; + case 1: CopyToSection(); break; + case 2: DelCopyOfSection(nMyPos); break; + } + break; + + case RedlineType::Delete: // Content has been deleted + m_bIsVisible = true; + if( 1 <= nLoop ) + MoveFromSection(nMyPos); + break; + + case RedlineType::Format: // Attributes have been applied + case RedlineType::Table: // Table structure has been modified + if( 1 <= nLoop ) + InvalidateRange(Invalidation::Remove); + break; + default: + break; + } + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +// trigger the Layout +void SwRangeRedline::InvalidateRange(Invalidation const eWhy) +{ + sal_uLong nSttNd = GetMark()->nNode.GetIndex(), + nEndNd = GetPoint()->nNode.GetIndex(); + sal_Int32 nSttCnt = GetMark()->nContent.GetIndex(); + sal_Int32 nEndCnt = GetPoint()->nContent.GetIndex(); + + if( nSttNd > nEndNd || ( nSttNd == nEndNd && nSttCnt > nEndCnt )) + { + sal_uLong nTmp = nSttNd; nSttNd = nEndNd; nEndNd = nTmp; + sal_Int32 nTmp2 = nSttCnt; nSttCnt = nEndCnt; nEndCnt = nTmp2; + } + + SwNodes& rNds = GetDoc()->GetNodes(); + for (sal_uLong n(nSttNd); n <= nEndNd; ++n) + { + SwNode* pNode = rNds[n]; + + if (pNode && pNode->IsTextNode()) + { + SwTextNode* pNd = pNode->GetTextNode(); + + SwUpdateAttr aHt( + n == nSttNd ? nSttCnt : 0, + n == nEndNd ? nEndCnt : pNd->GetText().getLength(), + RES_FMT_CHG); + + pNd->ModifyNotification(&aHt, &aHt); + + // SwUpdateAttr must be handled first, otherwise indexes are off + if (GetType() == RedlineType::Delete) + { + sal_Int32 const nStart(n == nSttNd ? nSttCnt : 0); + sal_Int32 const nLen((n == nEndNd ? nEndCnt : pNd->GetText().getLength()) - nStart); + if (eWhy == Invalidation::Add) + { + sw::RedlineDelText const hint(nStart, nLen); + pNd->CallSwClientNotify(hint); + } + else + { + sw::RedlineUnDelText const hint(nStart, nLen); + pNd->CallSwClientNotify(hint); + } + } + } + } +} + +/** Calculates the start and end position of the intersection rTmp and + text node nNdIdx */ +void SwRangeRedline::CalcStartEnd( sal_uLong nNdIdx, sal_Int32& rStart, sal_Int32& rEnd ) const +{ + const SwPosition *pRStt = Start(), *pREnd = End(); + if( pRStt->nNode < nNdIdx ) + { + if( pREnd->nNode > nNdIdx ) + { + rStart = 0; // Paragraph is completely enclosed + rEnd = COMPLETE_STRING; + } + else if (pREnd->nNode == nNdIdx) + { + rStart = 0; // Paragraph is overlapped in the beginning + rEnd = pREnd->nContent.GetIndex(); + } + else // redline ends before paragraph + { + rStart = COMPLETE_STRING; + rEnd = COMPLETE_STRING; + } + } + else if( pRStt->nNode == nNdIdx ) + { + rStart = pRStt->nContent.GetIndex(); + if( pREnd->nNode == nNdIdx ) + rEnd = pREnd->nContent.GetIndex(); // Within the Paragraph + else + rEnd = COMPLETE_STRING; // Paragraph is overlapped in the end + } + else + { + rStart = COMPLETE_STRING; + rEnd = COMPLETE_STRING; + } +} + +void SwRangeRedline::MoveToSection() +{ + if( !m_pContentSect ) + { + const SwPosition* pStt = Start(), + * pEnd = pStt == GetPoint() ? GetMark() : GetPoint(); + + SwDoc* pDoc = GetDoc(); + SwPaM aPam( *pStt, *pEnd ); + SwContentNode* pCSttNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + + if( !pCSttNd ) + { + // In order to not move other Redlines' indices, we set them + // to the end (is exclusive) + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + if( pRedl->GetBound() == *pStt ) + pRedl->GetBound() = *pEnd; + if( pRedl->GetBound(false) == *pStt ) + pRedl->GetBound(false) = *pEnd; + } + } + + SwStartNode* pSttNd; + SwNodes& rNds = pDoc->GetNodes(); + if( pCSttNd || pCEndNd ) + { + SwTextFormatColl* pColl = (pCSttNd && pCSttNd->IsTextNode() ) + ? pCSttNd->GetTextNode()->GetTextColl() + : (pCEndNd && pCEndNd->IsTextNode() ) + ? pCEndNd->GetTextNode()->GetTextColl() + : pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + pSttNd = rNds.MakeTextSection( SwNodeIndex( rNds.GetEndOfRedlines() ), + SwNormalStartNode, pColl ); + SwTextNode* pTextNd = rNds[ pSttNd->GetIndex() + 1 ]->GetTextNode(); + + SwNodeIndex aNdIdx( *pTextNd ); + SwPosition aPos( aNdIdx, SwIndex( pTextNd )); + if( pCSttNd && pCEndNd ) + pDoc->getIDocumentContentOperations().MoveAndJoin( aPam, aPos ); + else + { + if( pCSttNd && !pCEndNd ) + m_bDelLastPara = true; + pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::DEFAULT ); + } + } + else + { + pSttNd = SwNodes::MakeEmptySection( SwNodeIndex( rNds.GetEndOfRedlines() ) ); + + SwPosition aPos( *pSttNd->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::DEFAULT ); + } + m_pContentSect = new SwNodeIndex( *pSttNd ); + + if( pStt == GetPoint() ) + Exchange(); + + DeleteMark(); + } + else + InvalidateRange(Invalidation::Remove); +} + +void SwRangeRedline::CopyToSection() +{ + if( m_pContentSect ) + return; + + const SwPosition* pStt = Start(), + * pEnd = pStt == GetPoint() ? GetMark() : GetPoint(); + + SwContentNode* pCSttNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + + SwStartNode* pSttNd; + SwDoc* pDoc = GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + + bool bSaveCopyFlag = pDoc->IsCopyIsMove(), + bSaveRdlMoveFlg = pDoc->getIDocumentRedlineAccess().IsRedlineMove(); + pDoc->SetCopyIsMove( true ); + + // The IsRedlineMove() flag causes the behaviour of the + // DocumentContentOperationsManager::CopyFlyInFlyImpl() method to change, + // which will eventually be called by the CopyRange() below. + pDoc->getIDocumentRedlineAccess().SetRedlineMove(true); + + if( pCSttNd ) + { + SwTextFormatColl* pColl = pCSttNd->IsTextNode() + ? pCSttNd->GetTextNode()->GetTextColl() + : pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + pSttNd = rNds.MakeTextSection( SwNodeIndex( rNds.GetEndOfRedlines() ), + SwNormalStartNode, pColl ); + + SwNodeIndex aNdIdx( *pSttNd, 1 ); + SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode(); + SwPosition aPos( aNdIdx, SwIndex( pTextNd )); + pDoc->getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly); + + // Take over the style from the EndNode if needed + // We don't want this in Doc::Copy + if( pCEndNd && pCEndNd != pCSttNd ) + { + SwContentNode* pDestNd = aPos.nNode.GetNode().GetContentNode(); + if( pDestNd ) + { + if( pDestNd->IsTextNode() && pCEndNd->IsTextNode() ) + pCEndNd->GetTextNode()->CopyCollFormat(*pDestNd->GetTextNode()); + else + pDestNd->ChgFormatColl( pCEndNd->GetFormatColl() ); + } + } + } + else + { + pSttNd = SwNodes::MakeEmptySection( SwNodeIndex( rNds.GetEndOfRedlines() ) ); + + if( pCEndNd ) + { + SwPosition aPos( *pSttNd->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly); + } + else + { + SwNodeIndex aInsPos( *pSttNd->EndOfSectionNode() ); + SwNodeRange aRg( pStt->nNode, 0, pEnd->nNode, 1 ); + pDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos); + } + } + m_pContentSect = new SwNodeIndex( *pSttNd ); + + pDoc->SetCopyIsMove( bSaveCopyFlag ); + pDoc->getIDocumentRedlineAccess().SetRedlineMove( bSaveRdlMoveFlg ); + +} + +void SwRangeRedline::DelCopyOfSection(size_t nMyPos) +{ + if( m_pContentSect ) + { + const SwPosition* pStt = Start(), + * pEnd = pStt == GetPoint() ? GetMark() : GetPoint(); + + SwDoc* pDoc = GetDoc(); + SwPaM aPam( *pStt, *pEnd ); + SwContentNode* pCSttNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + + if( !pCSttNd ) + { + // In order to not move other Redlines' indices, we set them + // to the end (is exclusive) + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + if( pRedl->GetBound() == *pStt ) + pRedl->GetBound() = *pEnd; + if( pRedl->GetBound(false) == *pStt ) + pRedl->GetBound(false) = *pEnd; + } + } + + if( pCSttNd && pCEndNd ) + { + // #i100466# - force a <join next> on <delete and join> operation + // tdf#125319 - rather not? + pDoc->getIDocumentContentOperations().DeleteAndJoin(aPam/*, true*/); + } + else if( pCSttNd || pCEndNd ) + { + if( pCSttNd && !pCEndNd ) + m_bDelLastPara = true; + pDoc->getIDocumentContentOperations().DeleteRange( aPam ); + + if( m_bDelLastPara ) + { + // To prevent dangling references to the paragraph to + // be deleted, redline that point into this paragraph should be + // moved to the new end position. Since redlines in the redline + // table are sorted and the pEnd position is an endnode (see + // bDelLastPara condition above), only redlines before the + // current ones can be affected. + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + size_t n = nMyPos; + for( bool bBreak = false; !bBreak && n > 0; ) + { + --n; + bBreak = true; + if( rTable[ n ]->GetBound() == *aPam.GetPoint() ) + { + rTable[ n ]->GetBound() = *pEnd; + bBreak = false; + } + if( rTable[ n ]->GetBound(false) == *aPam.GetPoint() ) + { + rTable[ n ]->GetBound(false) = *pEnd; + bBreak = false; + } + } + + *GetPoint() = *pEnd; + *GetMark() = *pEnd; + DeleteMark(); + + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + aPam.DeleteMark(); + pDoc->getIDocumentContentOperations().DelFullPara( aPam ); + } + } + else + { + pDoc->getIDocumentContentOperations().DeleteRange( aPam ); + } + + if( pStt == GetPoint() ) + Exchange(); + + DeleteMark(); + } +} + +void SwRangeRedline::MoveFromSection(size_t nMyPos) +{ + if( m_pContentSect ) + { + SwDoc* pDoc = GetDoc(); + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + std::vector<SwPosition*> aBeforeArr, aBehindArr; + bool bBreak = false; + SwRedlineTable::size_type n; + + for( n = nMyPos+1; !bBreak && n < rTable.size(); ++n ) + { + bBreak = true; + if( rTable[ n ]->GetBound() == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBehindArr.push_back(&pRedl->GetBound()); + bBreak = false; + } + if( rTable[ n ]->GetBound(false) == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBehindArr.push_back(&pRedl->GetBound(false)); + bBreak = false; + } + } + for( bBreak = false, n = nMyPos; !bBreak && n ; ) + { + --n; + bBreak = true; + if( rTable[ n ]->GetBound() == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBeforeArr.push_back(&pRedl->GetBound()); + bBreak = false; + } + if( rTable[ n ]->GetBound(false) == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBeforeArr.push_back(&pRedl->GetBound(false)); + bBreak = false; + } + } + + const SwNode* pKeptContentSectNode( &m_pContentSect->GetNode() ); // #i95711# + { + SwPaM aPam( m_pContentSect->GetNode(), + *m_pContentSect->GetNode().EndOfSectionNode(), 1, + ( m_bDelLastPara ? -2 : -1 ) ); + SwContentNode* pCNd = aPam.GetContentNode(); + if( pCNd ) + aPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + else + ++aPam.GetPoint()->nNode; + + SwFormatColl* pColl = pCNd && pCNd->Len() && aPam.GetPoint()->nNode != + aPam.GetMark()->nNode + ? pCNd->GetFormatColl() : nullptr; + + SwNodeIndex aNdIdx( GetPoint()->nNode, -1 ); + const sal_Int32 nPos = GetPoint()->nContent.GetIndex(); + + SwPosition aPos( *GetPoint() ); + if( m_bDelLastPara && *aPam.GetPoint() == *aPam.GetMark() ) + { + --aPos.nNode; + + pDoc->getIDocumentContentOperations().AppendTextNode( aPos ); + } + else + { + pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::ALLFLYS ); + } + + SetMark(); + *GetPoint() = aPos; + GetMark()->nNode = aNdIdx.GetIndex() + 1; + pCNd = GetMark()->nNode.GetNode().GetContentNode(); + GetMark()->nContent.Assign( pCNd, nPos ); + + if( m_bDelLastPara ) + { + ++GetPoint()->nNode; + pCNd = GetContentNode(); + GetPoint()->nContent.Assign( pCNd, 0 ); + m_bDelLastPara = false; + } + else if( pColl ) + pCNd = GetContentNode(); + + if( pColl && pCNd ) + pCNd->ChgFormatColl( pColl ); + } + + // #i95771# + // Under certain conditions the previous <SwDoc::Move(..)> has already + // removed the change tracking section of this <SwRangeRedline> instance from + // the change tracking nodes area. + // Thus, check if <pContentSect> still points to the change tracking section + // by comparing it with the "indexed" <SwNode> instance copied before + // perform the intrinsic move. + // Note: Such condition is e.g. a "delete" change tracking only containing a table. + if ( &m_pContentSect->GetNode() == pKeptContentSectNode ) + { + pDoc->getIDocumentContentOperations().DeleteSection( &m_pContentSect->GetNode() ); + } + delete m_pContentSect; + m_pContentSect = nullptr; + + // adjustment of redline table positions must take start and + // end into account, not point and mark. + for( auto& pItem : aBeforeArr ) + *pItem = *Start(); + for( auto& pItem : aBehindArr ) + *pItem = *End(); + } + else + InvalidateRange(Invalidation::Add); +} + +// for Undo +void SwRangeRedline::SetContentIdx( const SwNodeIndex* pIdx ) +{ + if( pIdx && !m_pContentSect ) + { + m_pContentSect = new SwNodeIndex( *pIdx ); + m_bIsVisible = false; + } + else if( !pIdx && m_pContentSect ) + { + delete m_pContentSect; + m_pContentSect = nullptr; + m_bIsVisible = false; + } + else + { + OSL_FAIL("SwRangeRedline::SetContentIdx: invalid state"); + } +} + +bool SwRangeRedline::CanCombine( const SwRangeRedline& rRedl ) const +{ + return IsVisible() && rRedl.IsVisible() && + m_pRedlineData->CanCombine( *rRedl.m_pRedlineData ); +} + +void SwRangeRedline::PushData( const SwRangeRedline& rRedl, bool bOwnAsNext ) +{ + SwRedlineData* pNew = new SwRedlineData( *rRedl.m_pRedlineData, false ); + if( bOwnAsNext ) + { + pNew->m_pNext = m_pRedlineData; + m_pRedlineData = pNew; + } + else + { + pNew->m_pNext = m_pRedlineData->m_pNext; + m_pRedlineData->m_pNext = pNew; + } +} + +bool SwRangeRedline::PopData() +{ + if( !m_pRedlineData->m_pNext ) + return false; + SwRedlineData* pCur = m_pRedlineData; + m_pRedlineData = pCur->m_pNext; + pCur->m_pNext = nullptr; + delete pCur; + return true; +} + +sal_uInt16 SwRangeRedline::GetStackCount() const +{ + sal_uInt16 nRet = 1; + for( SwRedlineData* pCur = m_pRedlineData; pCur->m_pNext; pCur = pCur->m_pNext ) + ++nRet; + return nRet; +} + +std::size_t SwRangeRedline::GetAuthor( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_nAuthor; +} + +OUString const & SwRangeRedline::GetAuthorString( sal_uInt16 nPos ) const +{ + return SW_MOD()->GetRedlineAuthor(GetRedlineData(nPos).m_nAuthor); +} + +const DateTime& SwRangeRedline::GetTimeStamp( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_aStamp; +} + +RedlineType SwRangeRedline::GetType( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_eType; +} + +const OUString& SwRangeRedline::GetComment( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_sComment; +} + +bool SwRangeRedline::operator<( const SwRangeRedline& rCmp ) const +{ + if (*Start() < *rCmp.Start()) + return true; + + return *Start() == *rCmp.Start() && *End() < *rCmp.End(); +} + +const SwRedlineData & SwRangeRedline::GetRedlineData(const sal_uInt16 nPos) const +{ + SwRedlineData * pCur = m_pRedlineData; + + sal_uInt16 nP = nPos; + + while (nP > 0 && nullptr != pCur->m_pNext) + { + pCur = pCur->m_pNext; + + nP--; + } + + SAL_WARN_IF( nP != 0, "sw.core", "Pos " << nPos << " is " << nP << " too big"); + + return *pCur; +} + +OUString SwRangeRedline::GetDescr() +{ + // get description of redline data (e.g.: "insert $1") + OUString aResult = GetRedlineData().GetDescr(); + + SwPaM * pPaM = nullptr; + bool bDeletePaM = false; + + // if this redline is visible the content is in this PaM + if (nullptr == m_pContentSect) + { + pPaM = this; + } + else // otherwise it is saved in pContentSect + { + SwNodeIndex aTmpIdx( *m_pContentSect->GetNode().EndOfSectionNode() ); + pPaM = new SwPaM(*m_pContentSect, aTmpIdx ); + bDeletePaM = true; + } + + OUString sDescr = DenoteSpecialCharacters(pPaM->GetText()); + if (const SwTextNode *pTextNode = pPaM->GetNode().GetTextNode()) + { + if (const SwTextAttr* pTextAttr = pTextNode->GetFieldTextAttrAt(pPaM->GetPoint()->nContent.GetIndex() - 1, true )) + { + sDescr = SwResId(STR_START_QUOTE) + + pTextAttr->GetFormatField().GetField()->GetFieldName() + + SwResId(STR_END_QUOTE); + } + } + + // replace $1 in description by description of the redlines text + const OUString aTmpStr = ShortenString(sDescr, nUndoStringLength, SwResId(STR_LDOTS)); + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + aResult = aRewriter.Apply(aResult); + + if (bDeletePaM) + delete pPaM; + + return aResult; +} + +void SwRangeRedline::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwRangeRedline")); + + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(GetSeqNo()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("author"), BAD_CAST(SW_MOD()->GetRedlineAuthor(GetAuthor()).toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), BAD_CAST(DateTimeToOString(GetTimeStamp()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("descr"), BAD_CAST(const_cast<SwRangeRedline*>(this)->GetDescr().toUtf8().getStr())); + + OString sRedlineType; + switch (GetType()) + { + case RedlineType::Insert: + sRedlineType = "REDLINE_INSERT"; + break; + case RedlineType::Delete: + sRedlineType = "REDLINE_DELETE"; + break; + case RedlineType::Format: + sRedlineType = "REDLINE_FORMAT"; + break; + default: + sRedlineType = "UNKNOWN"; + break; + } + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr())); + + SwPaM::dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +void SwExtraRedlineTable::Insert( SwExtraRedline* p ) +{ + m_aExtraRedlines.push_back( p ); + //p->CallDisplayFunc(); +} + +void SwExtraRedlineTable::DeleteAndDestroy(sal_uInt16 const nPos) +{ + /* + SwDoc* pDoc = 0; + if( !nP && nL && nL == size() ) + pDoc = front()->GetDoc(); + */ + + delete m_aExtraRedlines[nPos]; + m_aExtraRedlines.erase(m_aExtraRedlines.begin() + nPos); + + /* + SwViewShell* pSh; + if( pDoc && !pDoc->IsInDtor() && + 0 != ( pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) ) + pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) ); + */ +} + +void SwExtraRedlineTable::DeleteAndDestroyAll() +{ + while (!m_aExtraRedlines.empty()) + { + auto const pRedline = m_aExtraRedlines.back(); + m_aExtraRedlines.pop_back(); + delete pRedline; + } +} + +SwExtraRedline::~SwExtraRedline() +{ +} + +SwTableRowRedline::SwTableRowRedline(const SwRedlineData& rData, const SwTableLine& rTableLine) + : m_aRedlineData(rData) + , m_rTableLine(rTableLine) +{ +} + +SwTableRowRedline::~SwTableRowRedline() +{ +} + +SwTableCellRedline::SwTableCellRedline(const SwRedlineData& rData, const SwTableBox& rTableBox) + : m_aRedlineData(rData) + , m_rTableBox(rTableBox) +{ +} + +SwTableCellRedline::~SwTableCellRedline() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docruby.cxx b/sw/source/core/doc/docruby.cxx new file mode 100644 index 000000000..f1ce56a0d --- /dev/null +++ b/sw/source/core/doc/docruby.cxx @@ -0,0 +1,327 @@ +/* -*- 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 <memory> +#include <string.h> + +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> + +#include <unotools/charclass.hxx> + +#include <hintids.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <ndtxt.hxx> +#include <txatbase.hxx> +#include <rubylist.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <breakit.hxx> +#include <swcrsr.hxx> + +using namespace ::com::sun::star::i18n; + +/* + * Members in the list: + * - String - the orig text + * - SwFormatRuby - the ruby attribute + */ +sal_uInt16 SwDoc::FillRubyList( const SwPaM& rPam, SwRubyList& rList ) +{ + const SwPaM *_pStartCursor = rPam.GetNext(), + *_pStartCursor2 = _pStartCursor; + bool bCheckEmpty = &rPam != _pStartCursor; + do { + const SwPosition* pStt = _pStartCursor->Start(), + * pEnd = pStt == _pStartCursor->GetPoint() + ? _pStartCursor->GetMark() + : _pStartCursor->GetPoint(); + if( !bCheckEmpty || ( pStt != pEnd && *pStt != *pEnd )) + { + SwPaM aPam( *pStt ); + do { + std::unique_ptr<SwRubyListEntry> pNew(new SwRubyListEntry); + if( pEnd != pStt ) + { + aPam.SetMark(); + *aPam.GetMark() = *pEnd; + } + if( SelectNextRubyChars( aPam, *pNew )) + { + rList.push_back(std::move(pNew)); + aPam.DeleteMark(); + } + else + { + if( *aPam.GetPoint() < *pEnd ) + { + // goto next paragraph + aPam.DeleteMark(); + aPam.Move( fnMoveForward, GoInNode ); + } + else + break; + } + } while( 30 > rList.size() && *aPam.GetPoint() < *pEnd ); + } + if( 30 <= rList.size() ) + break; + _pStartCursor = _pStartCursor->GetNext(); + } while( _pStartCursor != _pStartCursor2 ); + + return rList.size(); +} + +void SwDoc::SetRubyList( const SwPaM& rPam, const SwRubyList& rList ) +{ + GetIDocumentUndoRedo().StartUndo( SwUndoId::SETRUBYATTR, nullptr ); + std::set<sal_uInt16> aDelArr; + aDelArr.insert( RES_TXTATR_CJK_RUBY ); + + SwRubyList::size_type nListEntry = 0; + + const SwPaM *_pStartCursor = rPam.GetNext(), + *_pStartCursor2 = _pStartCursor; + bool bCheckEmpty = &rPam != _pStartCursor; + do { + const SwPosition* pStt = _pStartCursor->Start(), + * pEnd = pStt == _pStartCursor->GetPoint() + ? _pStartCursor->GetMark() + : _pStartCursor->GetPoint(); + if( !bCheckEmpty || ( pStt != pEnd && *pStt != *pEnd )) + { + + SwPaM aPam( *pStt ); + do { + SwRubyListEntry aCheckEntry; + if( pEnd != pStt ) + { + aPam.SetMark(); + *aPam.GetMark() = *pEnd; + } + if( SelectNextRubyChars( aPam, aCheckEntry )) + { + const SwRubyListEntry* pEntry = rList[ nListEntry++ ].get(); + if( aCheckEntry.GetRubyAttr() != pEntry->GetRubyAttr() ) + { + // set/reset the attribute + if( !pEntry->GetRubyAttr().GetText().isEmpty() ) + { + getIDocumentContentOperations().InsertPoolItem( aPam, pEntry->GetRubyAttr() ); + } + else + { + ResetAttrs( aPam, true, aDelArr ); + } + } + + if( !pEntry->GetText().isEmpty() && + aCheckEntry.GetText() != pEntry->GetText() ) + { + // text is changed, so replace the original + getIDocumentContentOperations().ReplaceRange( aPam, pEntry->GetText(), false ); + } + aPam.DeleteMark(); + } + else + { + if( *aPam.GetPoint() < *pEnd ) + { + // goto next paragraph + aPam.DeleteMark(); + aPam.Move( fnMoveForward, GoInNode ); + } + else + { + const SwRubyListEntry* pEntry = rList[ nListEntry++ ].get(); + + // set/reset the attribute + if( !pEntry->GetRubyAttr().GetText().isEmpty() && + !pEntry->GetText().isEmpty() ) + { + getIDocumentContentOperations().InsertString( aPam, pEntry->GetText() ); + aPam.SetMark(); + aPam.GetMark()->nContent -= pEntry->GetText().getLength(); + getIDocumentContentOperations().InsertPoolItem( + aPam, pEntry->GetRubyAttr(), SetAttrMode::DONTEXPAND ); + } + else + break; + aPam.DeleteMark(); + } + } + } while( nListEntry < rList.size() && *aPam.GetPoint() < *pEnd ); + } + if( 30 <= rList.size() ) + break; + _pStartCursor = _pStartCursor->GetNext(); + } while( _pStartCursor != _pStartCursor2 ); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::SETRUBYATTR, nullptr ); +} + +bool SwDoc::SelectNextRubyChars( SwPaM& rPam, SwRubyListEntry& rEntry ) +{ + // Point must be the startposition, Mark is optional the end position + SwPosition* pPos = rPam.GetPoint(); + const SwTextNode* pTNd = pPos->nNode.GetNode().GetTextNode(); + OUString const& rText = pTNd->GetText(); + sal_Int32 nStart = pPos->nContent.GetIndex(); + sal_Int32 nEnd = rText.getLength(); + + bool bHasMark = rPam.HasMark(); + if( bHasMark ) + { + // in the same node? + if( rPam.GetMark()->nNode == pPos->nNode ) + { + // then use that end + const sal_Int32 nTEnd = rPam.GetMark()->nContent.GetIndex(); + if( nTEnd < nEnd ) + nEnd = nTEnd; + } + rPam.DeleteMark(); + } + + // search the start + // look where a ruby attribute starts + const SwpHints* pHts = pTNd->GetpSwpHints(); + const SwTextAttr* pAttr = nullptr; + if( pHts ) + { + for( size_t nHtIdx = 0; nHtIdx < pHts->Count(); ++nHtIdx ) + { + const SwTextAttr* pHt = pHts->Get(nHtIdx); + if( RES_TXTATR_CJK_RUBY == pHt->Which() && + pHt->GetAnyEnd() > nStart ) + { + if( pHt->GetStart() < nEnd ) + { + pAttr = pHt; + if( !bHasMark && nStart > pAttr->GetStart() ) + { + nStart = pAttr->GetStart(); + pPos->nContent = nStart; + } + } + break; + } + } + } + + if( !bHasMark && nStart && ( !pAttr || nStart != pAttr->GetStart()) ) + { + // skip to the word begin! + const sal_Int32 nWordStt = g_pBreakIt->GetBreakIter()->getWordBoundary( + rText, nStart, + g_pBreakIt->GetLocale( pTNd->GetLang( nStart )), + WordType::ANYWORD_IGNOREWHITESPACES, + true ).startPos; + if (nWordStt < nStart && nWordStt >= 0) + { + nStart = nWordStt; + pPos->nContent = nStart; + } + } + + bool bAlphaNum = false; + sal_Int32 nWordEnd = nEnd; + CharClass& rCC = GetAppCharClass(); + while( nStart < nEnd ) + { + if( pAttr && nStart == pAttr->GetStart() ) + { + pPos->nContent = nStart; + if( !rPam.HasMark() ) + { + rPam.SetMark(); + pPos->nContent = pAttr->GetAnyEnd(); + if( pPos->nContent.GetIndex() > nEnd ) + pPos->nContent = nEnd; + rEntry.SetRubyAttr( pAttr->GetRuby() ); + } + break; + } + + sal_Int32 nChType = rCC.getType(rText, nStart); + bool bIgnoreChar = false, bIsAlphaNum = false, bChkNxtWrd = false; + switch( nChType ) + { + case UnicodeType::UPPERCASE_LETTER: + case UnicodeType::LOWERCASE_LETTER: + case UnicodeType::TITLECASE_LETTER: + case UnicodeType::DECIMAL_DIGIT_NUMBER: + bChkNxtWrd = bIsAlphaNum = true; + break; + + case UnicodeType::SPACE_SEPARATOR: + case UnicodeType::CONTROL: +/*??*/ case UnicodeType::PRIVATE_USE: + case UnicodeType::START_PUNCTUATION: + case UnicodeType::END_PUNCTUATION: + bIgnoreChar = true; + break; + + case UnicodeType::OTHER_LETTER: + bChkNxtWrd = true; + [[fallthrough]]; + default: + bIsAlphaNum = false; + break; + } + + if( rPam.HasMark() ) + { + if( bIgnoreChar || bIsAlphaNum != bAlphaNum || nStart >= nWordEnd ) + break; + } + else if( !bIgnoreChar ) + { + rPam.SetMark(); + bAlphaNum = bIsAlphaNum; + if (bChkNxtWrd) + { + // search the end of this word + nWordEnd = g_pBreakIt->GetBreakIter()->getWordBoundary( + rText, nStart, + g_pBreakIt->GetLocale( pTNd->GetLang( nStart )), + WordType::ANYWORD_IGNOREWHITESPACES, + true ).endPos; + if( 0 > nWordEnd || nWordEnd > nEnd || nWordEnd == nStart ) + nWordEnd = nEnd; + } + } + pTNd->GoNext( &pPos->nContent, CRSR_SKIP_CHARS ); + nStart = pPos->nContent.GetIndex(); + } + + nStart = rPam.GetMark()->nContent.GetIndex(); + rEntry.SetText( rText.copy( nStart, + rPam.GetPoint()->nContent.GetIndex() - nStart )); + return rPam.HasMark(); +} + +SwRubyListEntry::~SwRubyListEntry() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docsort.cxx b/sw/source/core/doc/docsort.cxx new file mode 100644 index 000000000..21ff81b7b --- /dev/null +++ b/sw/source/core/doc/docsort.cxx @@ -0,0 +1,937 @@ +/* -*- 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 <hintids.hxx> +#include <osl/diagnose.h> +#include <unotools/collatorwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <docary.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <sortopt.hxx> +#include <docsort.hxx> +#include <UndoSort.hxx> +#include <UndoRedline.hxx> +#include <hints.hxx> +#include <tblsel.hxx> +#include <cellatr.hxx> +#include <redline.hxx> +#include <node2lay.hxx> +#include <frameformats.hxx> + +#include <set> +#include <utility> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star; + +SwSortOptions* SwSortElement::pOptions = nullptr; +SwDoc* SwSortElement::pDoc = nullptr; +const FlatFndBox* SwSortElement::pBox = nullptr; +CollatorWrapper* SwSortElement::pSortCollator = nullptr; +lang::Locale* SwSortElement::pLocale = nullptr; +OUString* SwSortElement::pLastAlgorithm = nullptr; +LocaleDataWrapper* SwSortElement::pLclData = nullptr; + +// List of all sorted elements + +/// Construct a SortElement for the Sort +void SwSortElement::Init( SwDoc* pD, const SwSortOptions& rOpt, + FlatFndBox const * pFltBx ) +{ + OSL_ENSURE( !pDoc && !pOptions && !pBox, "Who forgot to call Finit?" ); + pDoc = pD; + pOptions = new SwSortOptions( rOpt ); + pBox = pFltBx; + + LanguageType nLang = rOpt.nLanguage; + if ( nLang.anyOf( + LANGUAGE_NONE, + LANGUAGE_DONTKNOW)) + nLang = GetAppLanguage(); + pLocale = new lang::Locale( LanguageTag::convertToLocale( nLang ) ); + + pSortCollator = new CollatorWrapper( ::comphelper::getProcessComponentContext() ); +} + +void SwSortElement::Finit() +{ + delete pOptions; + pOptions = nullptr; + delete pLocale; + pLocale = nullptr; + delete pLastAlgorithm; + pLastAlgorithm = nullptr; + delete pSortCollator; + pSortCollator = nullptr; + delete pLclData; + pLclData = nullptr; + pDoc = nullptr; + pBox = nullptr; +} + +SwSortElement::~SwSortElement() +{ +} + +double SwSortElement::StrToDouble( const OUString& rStr ) +{ + if( !pLclData ) + pLclData = new LocaleDataWrapper( LanguageTag( *pLocale )); + + rtl_math_ConversionStatus eStatus; + sal_Int32 nEnd; + double nRet = pLclData->stringToDouble( rStr, true, &eStatus, &nEnd ); + + if( rtl_math_ConversionStatus_Ok != eStatus || nEnd == 0 ) + nRet = 0.0; + return nRet; +} + +int SwSortElement::keycompare(const SwSortElement& rCmp, sal_uInt16 nKey) const +{ + int nCmp = 0; + // The actual comparison + const SwSortElement *pOrig, *pCmp; + + const SwSortKey* pSrtKey = pOptions->aKeys[ nKey ].get(); + if( pSrtKey->eSortOrder == SwSortOrder::Ascending ) + { + pOrig = this; + pCmp = &rCmp; + } + else + { + pOrig = &rCmp; + pCmp = this; + } + + if( pSrtKey->bIsNumeric ) + { + double n1 = pOrig->GetValue( nKey ); + double n2 = pCmp->GetValue( nKey ); + + nCmp = n1 < n2 ? -1 : n1 == n2 ? 0 : 1; + } + else + { + if( !pLastAlgorithm || *pLastAlgorithm != pSrtKey->sSortType ) + { + if( pLastAlgorithm ) + *pLastAlgorithm = pSrtKey->sSortType; + else + pLastAlgorithm = new OUString( pSrtKey->sSortType ); + pSortCollator->loadCollatorAlgorithm( *pLastAlgorithm, + *pLocale, + pOptions->bIgnoreCase ? SW_COLLATOR_IGNORES : 0 ); + } + + nCmp = pSortCollator->compareString( + pOrig->GetKey( nKey ), pCmp->GetKey( nKey )); + } + return nCmp; +} + +bool SwSortElement::operator<(const SwSortElement& rCmp) const +{ + // The actual comparison + for(size_t nKey = 0; nKey < pOptions->aKeys.size(); ++nKey) + { + int nCmp = keycompare(rCmp, nKey); + + if (nCmp == 0) + continue; + + return nCmp < 0; + } + + return false; +} + +double SwSortElement::GetValue( sal_uInt16 nKey ) const +{ + return StrToDouble( GetKey( nKey )); +} + +/// SortingElement for Text +SwSortTextElement::SwSortTextElement(const SwNodeIndex& rPos) + : nOrg(rPos.GetIndex()), aPos(rPos) +{ +} + +OUString SwSortTextElement::GetKey(sal_uInt16 nId) const +{ + SwTextNode* pTextNd = aPos.GetNode().GetTextNode(); + if( !pTextNd ) + return OUString(); + + // for TextNodes + const OUString& rStr = pTextNd->GetText(); + + sal_Unicode nDeli = pOptions->cDeli; + sal_uInt16 nDCount = pOptions->aKeys[nId]->nColumnId, i = 1; + sal_Int32 nStart = 0; + + // Find the delimiter + while( nStart != -1 && i < nDCount) + if( -1 != ( nStart = rStr.indexOf( nDeli, nStart ) ) ) + { + nStart++; + i++; + } + + // Found next delimiter or end of String + // and copy + sal_Int32 nEnd = rStr.indexOf( nDeli, nStart+1 ); + if (nEnd == -1) + return rStr.copy( nStart ); + return rStr.copy( nStart, nEnd-nStart ); +} + +/// SortingElement for Tables +SwSortBoxElement::SwSortBoxElement( sal_uInt16 nRC ) + : nRow( nRC ) +{ +} + +/// Get Key for a cell +OUString SwSortBoxElement::GetKey(sal_uInt16 nKey) const +{ + const FndBox_* pFndBox; + sal_uInt16 nCol = pOptions->aKeys[nKey]->nColumnId-1; + + if( SwSortDirection::Rows == pOptions->eDirection ) + pFndBox = pBox->GetBox(nCol, nRow); // Sort rows + else + pFndBox = pBox->GetBox(nRow, nCol); // Sort columns + + // Extract the Text + OUStringBuffer aRetStr; + if( pFndBox ) + { // Get StartNode and skip it + const SwTableBox* pMyBox = pFndBox->GetBox(); + OSL_ENSURE(pMyBox, "No atomic Box"); + + if( pMyBox->GetSttNd() ) + { + // Iterate over all the Box's TextNodes + const SwNode *pNd = nullptr, *pEndNd = pMyBox->GetSttNd()->EndOfSectionNode(); + for( sal_uLong nIdx = pMyBox->GetSttIdx() + 1; pNd != pEndNd; ++nIdx ) + { + pNd = pDoc->GetNodes()[ nIdx ]; + if( pNd->IsTextNode() ) + aRetStr.append(pNd->GetTextNode()->GetText()); + } + } + } + return aRetStr.makeStringAndClear(); +} + +double SwSortBoxElement::GetValue( sal_uInt16 nKey ) const +{ + const FndBox_* pFndBox; + sal_uInt16 nCol = pOptions->aKeys[nKey]->nColumnId-1; + + if( SwSortDirection::Rows == pOptions->eDirection ) + pFndBox = pBox->GetBox(nCol, nRow); // Sort rows + else + pFndBox = pBox->GetBox(nRow, nCol); // Sort columns + + double nVal; + if( pFndBox ) + { + const SwFormat *pFormat = pFndBox->GetBox()->GetFrameFormat(); + if (pDoc->GetNumberFormatter()->IsTextFormat( pFormat->GetTableBoxNumFormat().GetValue())) + nVal = SwSortElement::GetValue( nKey ); + else + nVal = pFormat->GetTableBoxValue().GetValue(); + } + else + nVal = 0; + + return nVal; +} + +/// Sort Text in the Document +bool SwDoc::SortText(const SwPaM& rPaM, const SwSortOptions& rOpt) +{ + // Check if Frame is in the Text + const SwPosition *pStart = rPaM.Start(), *pEnd = rPaM.End(); + + // Set index to the Selection's start + for ( const auto *pFormat : *GetSpzFrameFormats() ) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + + if (pAPos && (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) && + pStart->nNode <= pAPos->nNode && pAPos->nNode <= pEnd->nNode ) + return false; + } + + // Check if only TextNodes are within the Selection + { + sal_uLong nStart = pStart->nNode.GetIndex(), + nEnd = pEnd->nNode.GetIndex(); + while( nStart <= nEnd ) + // Iterate over a selected range + if( !GetNodes()[ nStart++ ]->IsTextNode() ) + return false; + } + + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if( bUndo ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + } + + SwPaM* pRedlPam = nullptr; + SwUndoRedlineSort* pRedlUndo = nullptr; + SwUndoSort* pUndoSort = nullptr; + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + pRedlPam = new SwPaM( pStart->nNode, pEnd->nNode, -1, 1 ); + SwContentNode* pCNd = pRedlPam->GetContentNode( false ); + if( pCNd ) + pRedlPam->GetMark()->nContent = pCNd->Len(); + + if( getIDocumentRedlineAccess().IsRedlineOn() && !IDocumentRedlineAccess::IsShowOriginal( getIDocumentRedlineAccess().GetRedlineFlags() ) ) + { + if( bUndo ) + { + pRedlUndo = new SwUndoRedlineSort( *pRedlPam,rOpt ); + GetIDocumentUndoRedo().DoUndo(false); + } + // First copy the range + SwNodeIndex aEndIdx( pEnd->nNode, 1 ); + SwNodeRange aRg( pStart->nNode, aEndIdx ); + GetNodes().Copy_( aRg, aEndIdx ); + + // range is new from pEnd->nNode+1 to aEndIdx + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + + pRedlPam->GetMark()->nNode.Assign( pEnd->nNode.GetNode(), 1 ); + pCNd = pRedlPam->GetContentNode( false ); + pRedlPam->GetMark()->nContent.Assign( pCNd, 0 ); + + pRedlPam->GetPoint()->nNode.Assign( aEndIdx.GetNode() ); + pCNd = pRedlPam->GetContentNode(); + sal_Int32 nCLen = 0; + if( !pCNd ) + { + pCNd = GetNodes()[ aEndIdx.GetIndex()-1 ]->GetContentNode(); + if( pCNd ) + { + nCLen = pCNd->Len(); + pRedlPam->GetPoint()->nNode.Assign( *pCNd ); + } + } + pRedlPam->GetPoint()->nContent.Assign( pCNd, nCLen ); + + if( pRedlUndo ) + pRedlUndo->SetValues( rPaM ); + } + else + { + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + delete pRedlPam; + pRedlPam = nullptr; + } + } + + SwNodeIndex aStart(pStart->nNode); + SwSortElement::Init( this, rOpt ); + std::multiset<SwSortTextElement> aSortSet; + while( aStart <= pEnd->nNode ) + { + // Iterate over a selected range + aSortSet.insert(SwSortTextElement(aStart)); + ++aStart; + } + + // Now comes the tricky part: Move Nodes (and always keep Undo in mind) + sal_uLong nBeg = pStart->nNode.GetIndex(); + SwNodeRange aRg( aStart, aStart ); + + if( bUndo && !pRedlUndo ) + { + pUndoSort = new SwUndoSort(rPaM, rOpt); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + + GetIDocumentUndoRedo().DoUndo(false); + + size_t n = 0; + for (const auto& rElem : aSortSet) + { + aStart = nBeg + n; + aRg.aStart = rElem.aPos.GetIndex(); + aRg.aEnd = aRg.aStart.GetIndex() + 1; + + // Move Nodes + getIDocumentContentOperations().MoveNodeRange( aRg, aStart, + SwMoveFlags::DEFAULT ); + + // Insert Move in Undo + if(pUndoSort) + { + pUndoSort->Insert(rElem.nOrg, nBeg + n); + } + ++n; + } + // Delete all elements from the SortArray + aSortSet.clear(); + SwSortElement::Finit(); + + if( pRedlPam ) + { + if( pRedlUndo ) + { + pRedlUndo->SetSaveRange( *pRedlPam ); + // UGLY: temp. enable Undo + GetIDocumentUndoRedo().DoUndo(true); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pRedlUndo) ); + GetIDocumentUndoRedo().DoUndo(false); + } + + // nBeg is start of sorted range + SwNodeIndex aSttIdx( GetNodes(), nBeg ); + + // the copied range is deleted + SwRangeRedline *const pDeleteRedline( + new SwRangeRedline( RedlineType::Delete, *pRedlPam )); + + // pRedlPam points to nodes that may be deleted (hidden) by + // AppendRedline, so adjust it beforehand to prevent ASSERT + pRedlPam->GetPoint()->nNode = aSttIdx; + SwContentNode* pCNd = aSttIdx.GetNode().GetContentNode(); + pRedlPam->GetPoint()->nContent.Assign( pCNd, 0 ); + + getIDocumentRedlineAccess().AppendRedline(pDeleteRedline, true); + + // the sorted range is inserted + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlPam ), true); + + if( pRedlUndo ) + { + SwNodeIndex aInsEndIdx( pRedlPam->GetMark()->nNode, -1 ); + pRedlPam->GetMark()->nNode = aInsEndIdx; + SwContentNode *const pPrevNode = + pRedlPam->GetMark()->nNode.GetNode().GetContentNode(); + pRedlPam->GetMark()->nContent.Assign( pPrevNode, pPrevNode->Len() ); + + pRedlUndo->SetValues( *pRedlPam ); + } + + delete pRedlPam; + pRedlPam = nullptr; + } + GetIDocumentUndoRedo().DoUndo( bUndo ); + if( bUndo ) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + + return true; +} + +/// Sort Table in the Document +bool SwDoc::SortTable(const SwSelBoxes& rBoxes, const SwSortOptions& rOpt) +{ + // Via SwDoc for Undo! + OSL_ENSURE( !rBoxes.empty(), "no valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // We begin sorting + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if(aFndBox.GetLines().empty()) + return false; + + if( !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ) + getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any ); + + FndLines_t::size_type nStart = 0; + if( pTableNd->GetTable().GetRowsToRepeat() > 0 && rOpt.eDirection == SwSortDirection::Rows ) + { + // Uppermost selected Cell + FndLines_t& rLines = aFndBox.GetLines(); + + while( nStart < rLines.size() ) + { + // Respect Split Merge nesting, + // extract the upper most + SwTableLine* pLine = rLines[nStart]->GetLine(); + while ( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + if( pTableNd->GetTable().IsHeadline( *pLine ) ) + nStart++; + else + break; + } + // Are all selected in the HeaderLine? -> no Offset + if( nStart == rLines.size() ) + nStart = 0; + } + + // Switch to relative Formulas + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + // Table as a flat array structure + FlatFndBox aFlatBox(this, aFndBox); + + if(!aFlatBox.IsSymmetric()) + return false; + + // Delete HTML layout + pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + // Delete the Table's Frames + pTableNd->DelFrames(); + // ? TL_CHART2: ? + + SwUndoSort* pUndoSort = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndoSort = new SwUndoSort( rBoxes[0]->GetSttIdx(), + rBoxes.back()->GetSttIdx(), + *pTableNd, rOpt, aFlatBox.HasItemSets() ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Insert KeyElements + sal_uInt16 nCount = (rOpt.eDirection == SwSortDirection::Rows) ? + aFlatBox.GetRows() : aFlatBox.GetCols(); + + // Sort SortList by Key + SwSortElement::Init( this, rOpt, &aFlatBox ); + std::multiset<SwSortBoxElement> aSortList; + + // When sorting, do not include the first row if the HeaderLine is repeated + for( sal_uInt16 i = static_cast<sal_uInt16>(nStart); i < nCount; ++i) + { + aSortList.insert(SwSortBoxElement(i)); + } + + // Move after Sorting + SwMovedBoxes aMovedList; + sal_uInt16 i = 0; + for (const auto& rElem : aSortList) + { + if(rOpt.eDirection == SwSortDirection::Rows) + { + MoveRow(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort); + } + else + { + MoveCol(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort); + } + ++i; + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const sal_uLong nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + // TL_CHART2: need to inform chart of probably changed cell names + UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + + // Delete all Elements in the SortArray + aSortList.clear(); + SwSortElement::Finit(); + + getIDocumentState().SetModified(); + return true; +} + +/// Move a row +void MoveRow(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT, + SwMovedBoxes& rMovedList, SwUndoSort* pUD) +{ + for( sal_uInt16 i=0; i < rBox.GetCols(); ++i ) + { // Get old cell position and remember it + const FndBox_* pSource = rBox.GetBox(i, nS); + + // new cell position + const FndBox_* pTarget = rBox.GetBox(i, nT); + + const SwTableBox* pT = pTarget->GetBox(); + const SwTableBox* pS = pSource->GetBox(); + + bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX; + + // and move it + MoveCell(pDoc, pS, pT, bMoved, pUD); + + rMovedList.push_back(pS); + + if( pS != pT ) + { + SwFrameFormat* pTFormat = pT->GetFrameFormat(); + const SfxItemSet* pSSet = rBox.GetItemSet( i, nS ); + + if( pSSet || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat(); + pTFormat->LockModify(); + if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) ) + pTFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( pSSet ) + pTFormat->SetFormatAttr( *pSSet ); + pTFormat->UnlockModify(); + } + } + } +} + +/// Move a column +void MoveCol(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT, + SwMovedBoxes& rMovedList, SwUndoSort* pUD) +{ + for(sal_uInt16 i=0; i < rBox.GetRows(); ++i) + { // Get old cell position and remember it + const FndBox_* pSource = rBox.GetBox(nS, i); + + // new cell position + const FndBox_* pTarget = rBox.GetBox(nT, i); + + // and move it + const SwTableBox* pT = pTarget->GetBox(); + const SwTableBox* pS = pSource->GetBox(); + + // and move it + bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX; + MoveCell(pDoc, pS, pT, bMoved, pUD); + + rMovedList.push_back(pS); + + if( pS != pT ) + { + SwFrameFormat* pTFormat = pT->GetFrameFormat(); + const SfxItemSet* pSSet = rBox.GetItemSet( nS, i ); + + if( pSSet || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat(); + pTFormat->LockModify(); + if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) ) + pTFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( pSSet ) + pTFormat->SetFormatAttr( *pSSet ); + pTFormat->UnlockModify(); + } + } + } +} + +/// Move a single Cell +void MoveCell(SwDoc* pDoc, const SwTableBox* pSource, const SwTableBox* pTar, + bool bMovedBefore, SwUndoSort* pUD) +{ + OSL_ENSURE(pSource && pTar,"Source or target missing"); + + if(pSource == pTar) + return; + + if(pUD) + pUD->Insert( pSource->GetName(), pTar->GetName() ); + + // Set Pam source to the first ContentNode + SwNodeRange aRg( *pSource->GetSttNd(), 0, *pSource->GetSttNd() ); + SwNode* pNd = pDoc->GetNodes().GoNext( &aRg.aStart ); + + // If the Cell (Source) wasn't moved + // -> insert an empty Node and move the rest or the Mark + // points to the first ContentNode + if( pNd->StartOfSectionNode() == pSource->GetSttNd() ) + pNd = pDoc->GetNodes().MakeTextNode( aRg.aStart, + pDoc->GetDfltTextFormatColl() ); + aRg.aEnd = *pNd->EndOfSectionNode(); + + // If the Target is empty (there is one empty Node) + // -> move and delete it + SwNodeIndex aTar( *pTar->GetSttNd() ); + pNd = pDoc->GetNodes().GoNext( &aTar ); // next ContentNode + sal_uLong nCount = pNd->EndOfSectionIndex() - pNd->StartOfSectionIndex(); + + bool bDelFirst = false; + if( nCount == 2 ) + { + OSL_ENSURE( pNd->GetContentNode(), "No ContentNode"); + bDelFirst = !pNd->GetContentNode()->Len() && bMovedBefore; + } + + if(!bDelFirst) + { // We already have Content -> old Content Section Down + SwNodeRange aRgTar( aTar.GetNode(), 0, *pNd->EndOfSectionNode() ); + pDoc->GetNodes().SectionDown( &aRgTar ); + } + + // Insert the Source + SwNodeIndex aIns( *pTar->GetSttNd()->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIns, + SwMoveFlags::DEFAULT ); + + // If first Node is empty -> delete it + if(bDelFirst) + pDoc->GetNodes().Delete( aTar ); +} + +/// Generate two-dimensional array of FndBoxes +FlatFndBox::FlatFndBox(SwDoc* pDocPtr, const FndBox_& rBoxRef) : + pDoc(pDocPtr), + nRow(0), + nCol(0) +{ // If the array is symmetric + bSym = CheckLineSymmetry(rBoxRef); + if( bSym ) + { + // Determine column/row count + nCols = GetColCount(rBoxRef); + nRows = GetRowCount(rBoxRef); + + // Create linear array + size_t nCount = static_cast<size_t>(nRows) * nCols; + pArr = std::make_unique<FndBox_ const *[]>(nCount); + memset(pArr.get(), 0, sizeof(const FndBox_*) * nCount); + + FillFlat( rBoxRef ); + } +} + +FlatFndBox::~FlatFndBox() +{ +} + +/// All Lines of a Box need to have same number of Boxes +bool FlatFndBox::CheckLineSymmetry(const FndBox_& rBox) +{ + const FndLines_t &rLines = rBox.GetLines(); + FndBoxes_t::size_type nBoxes {0}; + + for (FndLines_t::size_type i=0; i < rLines.size(); ++i) + { + const FndLine_* pLn = rLines[i].get(); + const FndBoxes_t& rBoxes = pLn->GetBoxes(); + + // Number of Boxes of all Lines is unequal -> no symmetry + if( i && nBoxes != rBoxes.size()) + return false; + + nBoxes = rBoxes.size(); + if( !CheckBoxSymmetry( *pLn ) ) + return false; + } + return true; +} + +/// Check Box for symmetry (All Boxes of a Line need to have same number of Lines) +bool FlatFndBox::CheckBoxSymmetry(const FndLine_& rLn) +{ + const FndBoxes_t &rBoxes = rLn.GetBoxes(); + FndLines_t::size_type nLines {0}; + + for (FndBoxes_t::size_type i = 0; i < rBoxes.size(); ++i) + { + FndBox_ const*const pBox = rBoxes[i].get(); + const FndLines_t& rLines = pBox->GetLines(); + + // Number of Lines of all Boxes is unequal -> no symmetry + if( i && nLines != rLines.size() ) + return false; + + nLines = rLines.size(); + if( nLines && !CheckLineSymmetry( *pBox ) ) + return false; + } + return true; +} + +/// Maximum count of Columns (Boxes) +sal_uInt16 FlatFndBox::GetColCount(const FndBox_& rBox) +{ + const FndLines_t& rLines = rBox.GetLines(); + // Iterate over Lines + if( rLines.empty() ) + return 1; + + sal_uInt16 nSum = 0; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + sal_uInt16 nCount = 0; + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + for (const auto &rpB : rBoxes) + { // Iterate recursively over the Lines + nCount += rpB->GetLines().empty() ? 1 : GetColCount(*rpB); + } + + if( nSum < nCount ) + nSum = nCount; + } + return nSum; +} + +/// Maximum count of Rows (Lines) +sal_uInt16 FlatFndBox::GetRowCount(const FndBox_& rBox) +{ + const FndLines_t& rLines = rBox.GetLines(); + if( rLines.empty() ) + return 1; + + sal_uInt16 nLines = 0; + for (const auto & pLine : rLines) + { // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nLn = 1; + for (const auto &rpB : rBoxes) + { + if (!rpB->GetLines().empty()) + { // Iterate recursively over the Lines + nLn = std::max(GetRowCount(*rpB), nLn); + } + } + + nLines = nLines + nLn; + } + return nLines; +} + +/// Create a linear array of atomic FndBoxes +void FlatFndBox::FillFlat(const FndBox_& rBox, bool bLastBox) +{ + bool bModRow = false; + const FndLines_t& rLines = rBox.GetLines(); + + // Iterate over Lines + sal_uInt16 nOldRow = nRow; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nOldCol = nCol; + for( FndBoxes_t::size_type j = 0; j < rBoxes.size(); ++j ) + { + // Check the Box if it's an atomic one + const FndBox_ *const pBox = rBoxes[j].get(); + + if( pBox->GetLines().empty() ) + { + // save it + sal_uInt16 nOff = nRow * nCols + nCol; + pArr[nOff] = pBox; + + // Save the Formula/Format/Value values + const SwFrameFormat* pFormat = pBox->GetBox()->GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + auto pSet = std::make_unique<SfxItemSet>( + pDoc->GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + pSet->Put( pFormat->GetAttrSet() ); + if( ppItemSets.empty() ) + { + size_t nCount = static_cast<size_t>(nRows) * nCols; + ppItemSets.resize(nCount); + } + ppItemSets[nOff] = std::move(pSet); + } + + bModRow = true; + } + else + { + // Iterate recursively over the Lines of a Box + FillFlat( *pBox, ( j+1 == rBoxes.size() ) ); + } + nCol++; + } + if(bModRow) + nRow++; + nCol = nOldCol; + } + if(!bLastBox) + nRow = nOldRow; +} + +/// Access a specific Cell +const FndBox_* FlatFndBox::GetBox(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + sal_uInt16 nOff = n_Row * nCols + n_Col; + const FndBox_* pTmp = pArr[nOff]; + + OSL_ENSURE(n_Col < nCols && n_Row < nRows && pTmp, "invalid array access"); + return pTmp; +} + +const SfxItemSet* FlatFndBox::GetItemSet(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + OSL_ENSURE( ppItemSets.empty() || ( n_Col < nCols && n_Row < nRows), "invalid array access"); + + return !ppItemSets.empty() ? ppItemSets[unsigned(n_Row * nCols) + n_Col].get() : nullptr; +} + +sal_uInt16 SwMovedBoxes::GetPos(const SwTableBox* pTableBox) const +{ + std::vector<const SwTableBox*>::const_iterator it = std::find(mBoxes.begin(), mBoxes.end(), pTableBox); + return it == mBoxes.end() ? USHRT_MAX : it - mBoxes.begin(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docstat.cxx b/sw/source/core/doc/docstat.cxx new file mode 100644 index 000000000..c34e8d094 --- /dev/null +++ b/sw/source/core/doc/docstat.cxx @@ -0,0 +1,51 @@ +/* -*- 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 <docstat.hxx> + +SwDocStat::SwDocStat() : + nTable(0), + nGrf(0), + nOLE(0), + nPage(1), + nPara(1), + nAllPara(1), + nWord(0), + nAsianWord(0), + nChar(0), + nCharExcludingSpaces(0), + bModified(true) +{} + +void SwDocStat::Reset() +{ + nTable = 0; + nGrf = 0; + nOLE = 0; + nPage = 1; + nPara = 1; + nAllPara= 1; + nWord = 0; + nAsianWord = 0; + nChar = 0; + nCharExcludingSpaces = 0; + bModified = true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doctxm.cxx b/sw/source/core/doc/doctxm.cxx new file mode 100644 index 000000000..7ca7103ff --- /dev/null +++ b/sw/source/core/doc/doctxm.cxx @@ -0,0 +1,2076 @@ +/* -*- 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 <limits.h> +#include <hintids.hxx> +#include <editeng/formatbreakitem.hxx> +#include <comphelper/classids.hxx> +#include <docsh.hxx> +#include <ndole.hxx> +#include <txttxmrk.hxx> +#include <fmtpdsc.hxx> +#include <frmatr.hxx> +#include <pagedesc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pagefrm.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <doctxm.hxx> +#include <txmsrt.hxx> +#include <rolbck.hxx> +#include <poolfmt.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <UndoAttribute.hxx> +#include <UndoSection.hxx> +#include <swundo.hxx> +#include <mdiexp.hxx> +#include <docary.hxx> +#include <charfmt.hxx> +#include <fchrfmt.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <expfld.hxx> +#include <mvsave.hxx> +#include <node2lay.hxx> +#include <SwStyleNameMapper.hxx> +#include <breakit.hxx> +#include <scriptinfo.hxx> +#include <calbck.hxx> +#include <ToxTextGenerator.hxx> +#include <ToxTabStopTokenHandler.hxx> +#include <frameformats.hxx> +#include <tools/datetimeutils.hxx> +#include <tools/globname.hxx> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <o3tl/safeint.hxx> + +#include <memory> + +using namespace ::com::sun::star; + +template<typename T, typename... Args> static +typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type +MakeSwTOXSortTabBase(SwRootFrame const*const pLayout, Args&& ... args) +{ + std::unique_ptr<T> pRet(new T(std::forward<Args>(args)...)); + pRet->InitText(pLayout); // ensure it's expanded with the layout + return pRet; +} + +void SwDoc::GetTOIKeys(SwTOIKeyType eTyp, std::vector<OUString>& rArr, + SwRootFrame const& rLayout) const +{ + rArr.clear(); + + // Look up all Primary and Secondary via the Pool + for (const SfxPoolItem* pPoolItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_TOXMARK)) + { + const SwTOXMark* pItem = dynamic_cast<const SwTOXMark*>(pPoolItem); + if( !pItem ) + continue; + const SwTOXType* pTOXType = pItem->GetTOXType(); + if ( !pTOXType || pTOXType->GetType()!=TOX_INDEX ) + continue; + const SwTextTOXMark* pMark = pItem->GetTextTOXMark(); + if ( pMark && pMark->GetpTextNd() && + pMark->GetpTextNd()->GetNodes().IsDocNodes() && + (!rLayout.IsHideRedlines() + || !sw::IsMarkHintHidden(rLayout, *pMark->GetpTextNd(), *pMark))) + { + const OUString sStr = TOI_PRIMARY == eTyp + ? pItem->GetPrimaryKey() + : pItem->GetSecondaryKey(); + + if( !sStr.isEmpty() ) + rArr.push_back( sStr ); + } + } +} + +/// Get current table of contents Mark. +sal_uInt16 SwDoc::GetCurTOXMark( const SwPosition& rPos, + SwTOXMarks& rArr ) +{ + // search on Position rPos for all SwTOXMarks + SwTextNode *const pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( !pTextNd || !pTextNd->GetpSwpHints() ) + return 0; + + const SwpHints & rHts = *pTextNd->GetpSwpHints(); + sal_Int32 nSttIdx; + const sal_Int32 *pEndIdx; + + const sal_Int32 nCurrentPos = rPos.nContent.GetIndex(); + + for( size_t n = 0; n < rHts.Count(); ++n ) + { + const SwTextAttr* pHt = rHts.Get(n); + if( RES_TXTATR_TOXMARK != pHt->Which() ) + continue; + if( ( nSttIdx = pHt->GetStart() ) < nCurrentPos ) + { + // also check the end + pEndIdx = pHt->End(); + if( nullptr == pEndIdx || *pEndIdx <= nCurrentPos ) + continue; // keep searching + } + else if( nSttIdx > nCurrentPos ) + // If Hint's Start is greater than rPos, break, because + // the attributes are sorted by Start! + break; + + SwTOXMark* pTMark = const_cast<SwTOXMark*>(&pHt->GetTOXMark()); + rArr.push_back( pTMark ); + } + return rArr.size(); +} + +/// Delete table of contents Mark +void SwDoc::DeleteTOXMark( const SwTOXMark* pTOXMark ) +{ + const SwTextTOXMark* pTextTOXMark = pTOXMark->GetTextTOXMark(); + assert(pTextTOXMark); + + SwTextNode& rTextNd = const_cast<SwTextNode&>(pTextTOXMark->GetTextNode()); + assert(rTextNd.GetpSwpHints()); + + if (pTextTOXMark->HasDummyChar()) + { + // tdf#106377 don't use SwUndoResetAttr, it uses NOTXTATRCHR + SwPaM tmp(rTextNd, pTextTOXMark->GetStart(), + rTextNd, pTextTOXMark->GetStart()+1); + assert(rTextNd.GetText()[pTextTOXMark->GetStart()] == CH_TXTATR_INWORD); + getIDocumentContentOperations().DeleteRange(tmp); + } + else + { + std::unique_ptr<SwRegHistory> aRHst; + if (GetIDocumentUndoRedo().DoesUndo()) + { + // save attributes for Undo + SwUndoResetAttr* pUndo = new SwUndoResetAttr( + SwPosition( rTextNd, SwIndex( &rTextNd, pTextTOXMark->GetStart() ) ), + RES_TXTATR_TOXMARK ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + + aRHst.reset(new SwRegHistory(rTextNd, &pUndo->GetHistory())); + rTextNd.GetpSwpHints()->Register(aRHst.get()); + } + + rTextNd.DeleteAttribute( const_cast<SwTextTOXMark*>(pTextTOXMark) ); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + if( rTextNd.GetpSwpHints() ) + rTextNd.GetpSwpHints()->DeRegister(); + } + } + + getIDocumentState().SetModified(); +} + +namespace { + +/// Travel between table of content Marks +class CompareNodeContent +{ + sal_uLong nNode; + sal_Int32 nContent; +public: + CompareNodeContent( sal_uLong nNd, sal_Int32 nCnt ) + : nNode( nNd ), nContent( nCnt ) {} + + bool operator==( const CompareNodeContent& rCmp ) const + { return nNode == rCmp.nNode && nContent == rCmp.nContent; } + bool operator!=( const CompareNodeContent& rCmp ) const + { return nNode != rCmp.nNode || nContent != rCmp.nContent; } + bool operator< ( const CompareNodeContent& rCmp ) const + { return nNode < rCmp.nNode || + ( nNode == rCmp.nNode && nContent < rCmp.nContent); } + bool operator<=( const CompareNodeContent& rCmp ) const + { return nNode < rCmp.nNode || + ( nNode == rCmp.nNode && nContent <= rCmp.nContent); } + bool operator> ( const CompareNodeContent& rCmp ) const + { return nNode > rCmp.nNode || + ( nNode == rCmp.nNode && nContent > rCmp.nContent); } + bool operator>=( const CompareNodeContent& rCmp ) const + { return nNode > rCmp.nNode || + ( nNode == rCmp.nNode && nContent >= rCmp.nContent); } +}; + +} + +const SwTOXMark& SwDoc::GotoTOXMark( const SwTOXMark& rCurTOXMark, + SwTOXSearch eDir, bool bInReadOnly ) +{ + const SwTextTOXMark* pMark = rCurTOXMark.GetTextTOXMark(); + OSL_ENSURE(pMark, "pMark==0 invalid TextTOXMark"); + + const SwTextNode *pTOXSrc = pMark->GetpTextNd(); + + CompareNodeContent aAbsIdx( pTOXSrc->GetIndex(), pMark->GetStart() ); + CompareNodeContent aPrevPos( 0, 0 ); + CompareNodeContent aNextPos( ULONG_MAX, SAL_MAX_INT32 ); + CompareNodeContent aMax( 0, 0 ); + CompareNodeContent aMin( ULONG_MAX, SAL_MAX_INT32 ); + + const SwTOXMark* pNew = nullptr; + const SwTOXMark* pMax = &rCurTOXMark; + const SwTOXMark* pMin = &rCurTOXMark; + + const SwTOXType* pType = rCurTOXMark.GetTOXType(); + SwTOXMarks aMarks; + SwTOXMark::InsertTOXMarks( aMarks, *pType ); + + for(SwTOXMark* pTOXMark : aMarks) + { + if ( pTOXMark == &rCurTOXMark ) + continue; + + pMark = pTOXMark->GetTextTOXMark(); + if (!pMark) + continue; + + pTOXSrc = pMark->GetpTextNd(); + if (!pTOXSrc) + continue; + + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame* pCFrame = pTOXSrc->getLayoutFrame( + getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); + if (!pCFrame) + continue; + + if ( bInReadOnly || !pCFrame->IsProtected() ) + { + CompareNodeContent aAbsNew( pTOXSrc->GetIndex(), pMark->GetStart() ); + switch( eDir ) + { + // The following (a bit more complicated) statements make it + // possible to also travel across Entries on the same (!) + // position. If someone has time, please feel free to optimize. + case TOX_SAME_PRV: + if (pTOXMark->GetText(nullptr) != rCurTOXMark.GetText(nullptr)) + break; + [[fallthrough]]; + case TOX_PRV: + if ( (aAbsNew < aAbsIdx && aAbsNew > aPrevPos) || + (aAbsIdx == aAbsNew && + (reinterpret_cast<sal_uLong>(&rCurTOXMark) > reinterpret_cast<sal_uLong>(pTOXMark) && + (!pNew || aPrevPos < aAbsIdx || reinterpret_cast<sal_uLong>(pNew) < reinterpret_cast<sal_uLong>(pTOXMark) ) )) || + (aPrevPos == aAbsNew && aAbsIdx != aAbsNew && + reinterpret_cast<sal_uLong>(pTOXMark) > reinterpret_cast<sal_uLong>(pNew)) ) + { + pNew = pTOXMark; + aPrevPos = aAbsNew; + if ( aAbsNew >= aMax ) + { + aMax = aAbsNew; + pMax = pTOXMark; + } + } + break; + + case TOX_SAME_NXT: + if (pTOXMark->GetText(nullptr) != rCurTOXMark.GetText(nullptr)) + break; + [[fallthrough]]; + case TOX_NXT: + if ( (aAbsNew > aAbsIdx && aAbsNew < aNextPos) || + (aAbsIdx == aAbsNew && + (reinterpret_cast<sal_uLong>(&rCurTOXMark) < reinterpret_cast<sal_uLong>(pTOXMark) && + (!pNew || aNextPos > aAbsIdx || reinterpret_cast<sal_uLong>(pNew) > reinterpret_cast<sal_uLong>(pTOXMark)) )) || + (aNextPos == aAbsNew && aAbsIdx != aAbsNew && + reinterpret_cast<sal_uLong>(pTOXMark) < reinterpret_cast<sal_uLong>(pNew)) ) + { + pNew = pTOXMark; + aNextPos = aAbsNew; + if ( aAbsNew <= aMin ) + { + aMin = aAbsNew; + pMin = pTOXMark; + } + } + break; + } + } + } + + // We couldn't find a successor + // Use minimum or maximum + if(!pNew) + { + switch(eDir) + { + case TOX_PRV: + case TOX_SAME_PRV: + pNew = pMax; + break; + case TOX_NXT: + case TOX_SAME_NXT: + pNew = pMin; + break; + default: + pNew = &rCurTOXMark; + } + } + return *pNew; +} + +SwTOXBaseSection* SwDoc::InsertTableOf( const SwPosition& rPos, + const SwTOXBase& rTOX, + const SfxItemSet* pSet, + bool bExpand, + SwRootFrame const*const pLayout) +{ + SwPaM aPam( rPos ); + return InsertTableOf( aPam, rTOX, pSet, bExpand, pLayout ); +} + +SwTOXBaseSection* SwDoc::InsertTableOf( const SwPaM& aPam, + const SwTOXBase& rTOX, + const SfxItemSet* pSet, + bool bExpand, + SwRootFrame const*const pLayout ) +{ + assert(!bExpand || pLayout != nullptr); + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSTOX, nullptr ); + + OUString sSectNm = GetUniqueTOXBaseName( *rTOX.GetTOXType(), rTOX.GetTOXName() ); + SwSectionData aSectionData( SectionType::ToxContent, sSectNm ); + + std::pair<SwTOXBase const*, sw::RedlineMode> const tmp(&rTOX, + pLayout && pLayout->IsHideRedlines() + ? sw::RedlineMode::Hidden + : sw::RedlineMode::Shown); + SwTOXBaseSection *const pNewSection = dynamic_cast<SwTOXBaseSection *>( + InsertSwSection(aPam, aSectionData, & tmp, pSet, false)); + if (pNewSection) + { + SwSectionNode *const pSectNd = pNewSection->GetFormat()->GetSectionNode(); + pNewSection->SetTOXName(sSectNm); // rTOX may have had no name... + + if( bExpand ) + { + // add value for 2nd parameter = true to + // indicate, that a creation of a new table of content has to be performed. + // Value of 1st parameter = default value. + pNewSection->Update( nullptr, pLayout, true ); + } + else if( rTOX.GetTitle().getLength()==1 && IsInReading() ) + // insert title of TOX + { + // then insert the headline section + SwNodeIndex aIdx( *pSectNd, +1 ); + + SwTextNode* pHeadNd = GetNodes().MakeTextNode( aIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + + SwSectionData headerData( SectionType::ToxHeader, pNewSection->GetTOXName()+"_Head" ); + + SwNodeIndex aStt( *pHeadNd ); --aIdx; + SwSectionFormat* pSectFormat = MakeSectionFormat(); + GetNodes().InsertTextSection( + aStt, *pSectFormat, headerData, nullptr, &aIdx, true, false); + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSTOX, nullptr ); + + return pNewSection; +} + +void SwDoc::InsertTableOf( sal_uLong nSttNd, sal_uLong nEndNd, + const SwTOXBase& rTOX, + const SfxItemSet* pSet ) +{ + // check for recursive TOX + SwNode* pNd = GetNodes()[ nSttNd ]; + SwSectionNode* pSectNd = pNd->FindSectionNode(); + while( pSectNd ) + { + SectionType eT = pSectNd->GetSection().GetType(); + if( SectionType::ToxHeader == eT || SectionType::ToxContent == eT ) + return; + pSectNd = pSectNd->StartOfSectionNode()->FindSectionNode(); + } + + const OUString sSectNm = GetUniqueTOXBaseName(*rTOX.GetTOXType(), rTOX.GetTOXName()); + + SwSectionData aSectionData( SectionType::ToxContent, sSectNm ); + + SwNodeIndex aStt( GetNodes(), nSttNd ), aEnd( GetNodes(), nEndNd ); + SwSectionFormat* pFormat = MakeSectionFormat(); + if(pSet) + pFormat->SetFormatAttr(*pSet); + + SwSectionNode *const pNewSectionNode = + GetNodes().InsertTextSection(aStt, *pFormat, aSectionData, &rTOX, &aEnd); + if (!pNewSectionNode) + { + DelSectionFormat( pFormat ); + return; + } + + SwTOXBaseSection *const pNewSection( + dynamic_cast<SwTOXBaseSection*>(& pNewSectionNode->GetSection())); + if (pNewSection) + pNewSection->SetTOXName(sSectNm); // rTOX may have had no name... +} + +/// Get current table of contents +SwTOXBase* SwDoc::GetCurTOX( const SwPosition& rPos ) +{ + SwNode& rNd = rPos.nNode.GetNode(); + SwSectionNode* pSectNd = rNd.FindSectionNode(); + while( pSectNd ) + { + SectionType eT = pSectNd->GetSection().GetType(); + if( SectionType::ToxContent == eT ) + { + OSL_ENSURE( dynamic_cast< const SwTOXBaseSection *>( &pSectNd->GetSection()) != nullptr, + "no TOXBaseSection!" ); + SwTOXBaseSection& rTOXSect = static_cast<SwTOXBaseSection&>( + pSectNd->GetSection()); + return &rTOXSect; + } + pSectNd = pSectNd->StartOfSectionNode()->FindSectionNode(); + } + return nullptr; +} + +const SwAttrSet& SwDoc::GetTOXBaseAttrSet(const SwTOXBase& rTOXBase) +{ + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" ); + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + SwSectionFormat const * pFormat = rTOXSect.GetFormat(); + OSL_ENSURE( pFormat, "invalid TOXBaseSection!" ); + return pFormat->GetAttrSet(); +} + +const SwTOXBase* SwDoc::GetDefaultTOXBase( TOXTypes eTyp, bool bCreate ) +{ + std::unique_ptr<SwTOXBase>* prBase = nullptr; + switch(eTyp) + { + case TOX_CONTENT: prBase = &mpDefTOXBases->pContBase; break; + case TOX_INDEX: prBase = &mpDefTOXBases->pIdxBase; break; + case TOX_USER: prBase = &mpDefTOXBases->pUserBase; break; + case TOX_TABLES: prBase = &mpDefTOXBases->pTableBase; break; + case TOX_OBJECTS: prBase = &mpDefTOXBases->pObjBase; break; + case TOX_ILLUSTRATIONS: prBase = &mpDefTOXBases->pIllBase; break; + case TOX_AUTHORITIES: prBase = &mpDefTOXBases->pAuthBase; break; + case TOX_BIBLIOGRAPHY: prBase = &mpDefTOXBases->pBiblioBase; break; + case TOX_CITATION: /** TODO */break; + } + if (!prBase) + return nullptr; + if(!(*prBase) && bCreate) + { + SwForm aForm(eTyp); + const SwTOXType* pType = GetTOXType(eTyp, 0); + prBase->reset(new SwTOXBase(pType, aForm, SwTOXElement::NONE, pType->GetTypeName())); + } + return prBase->get(); +} + +void SwDoc::SetDefaultTOXBase(const SwTOXBase& rBase) +{ + std::unique_ptr<SwTOXBase>* prBase = nullptr; + switch(rBase.GetType()) + { + case TOX_CONTENT: prBase = &mpDefTOXBases->pContBase; break; + case TOX_INDEX: prBase = &mpDefTOXBases->pIdxBase; break; + case TOX_USER: prBase = &mpDefTOXBases->pUserBase; break; + case TOX_TABLES: prBase = &mpDefTOXBases->pTableBase; break; + case TOX_OBJECTS: prBase = &mpDefTOXBases->pObjBase; break; + case TOX_ILLUSTRATIONS: prBase = &mpDefTOXBases->pIllBase; break; + case TOX_AUTHORITIES: prBase = &mpDefTOXBases->pAuthBase; break; + case TOX_BIBLIOGRAPHY: prBase = &mpDefTOXBases->pBiblioBase; break; + case TOX_CITATION: /** TODO */break; + } + if (!prBase) + return; + prBase->reset(new SwTOXBase(rBase)); +} + +/// Delete table of contents +bool SwDoc::DeleteTOX( const SwTOXBase& rTOXBase, bool bDelNodes ) +{ + // We only delete the TOX, not the Nodes + bool bRet = false; + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" ); + + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + SwSectionFormat const * pFormat = rTOXSect.GetFormat(); + /* Save the start node of the TOX' section. */ + SwSectionNode const * pMyNode = pFormat ? pFormat->GetSectionNode() : nullptr; + if (pMyNode) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::CLEARTOXRANGE, nullptr ); + + /* Save start node of section's surrounding. */ + SwNode const * pStartNd = pMyNode->StartOfSectionNode(); + + /* Look for the point where to move the cursors in the area to + delete to. This is done by first searching forward from the + end of the TOX' section. If no content node is found behind + the TOX one is searched before it. If this is not + successful, too, insert new text node behind the end of + the TOX' section. The cursors from the TOX' section will be + moved to the content node found or the new text node. */ + + /* Set PaM to end of TOX' section and search following content node. + aSearchPam will contain the point where to move the cursors + to. */ + SwPaM aSearchPam(*pMyNode->EndOfSectionNode()); + SwPosition aEndPos(*pStartNd->EndOfSectionNode()); + if (! aSearchPam.Move() /* no content node found */ + || *aSearchPam.GetPoint() >= aEndPos /* content node found + outside surrounding */ + ) + { + /* Set PaM to beginning of TOX' section and search previous + content node */ + SwPaM aTmpPam(*pMyNode); + aSearchPam = aTmpPam; + SwPosition aStartPos(*pStartNd); + + if ( ! aSearchPam.Move(fnMoveBackward) /* no content node found */ + || *aSearchPam.GetPoint() <= aStartPos /* content node + found outside + surrounding */ + ) + { + /* There is no content node in the surrounding of + TOX'. Append text node behind TOX' section. */ + + SwPosition aInsPos(*pMyNode->EndOfSectionNode()); + getIDocumentContentOperations().AppendTextNode(aInsPos); + + SwPaM aTmpPam1(aInsPos); + aSearchPam = aTmpPam1; + } + } + + /* PaM containing the TOX. */ + SwPaM aPam(*pMyNode->EndOfSectionNode(), *pMyNode); + + /* Move cursors contained in TOX to the above calculated point. */ + PaMCorrAbs(aPam, *aSearchPam.GetPoint()); + + if( !bDelNodes ) + { + SwSections aArr( 0 ); + pFormat->GetChildSections( aArr, SectionSort::Not, false ); + for( const auto pSect : aArr ) + { + if( SectionType::ToxHeader == pSect->GetType() ) + { + DelSectionFormat( pSect->GetFormat(), bDelNodes ); + } + } + } + + DelSectionFormat( const_cast<SwSectionFormat *>(pFormat), bDelNodes ); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::CLEARTOXRANGE, nullptr ); + bRet = true; + } + + return bRet; +} + +/// Manage table of content types +sal_uInt16 SwDoc::GetTOXTypeCount(TOXTypes eTyp) const +{ + sal_uInt16 nCnt = 0; + for( auto const & pTOXType : *mpTOXTypes ) + if( eTyp == pTOXType->GetType() ) + ++nCnt; + return nCnt; +} + +const SwTOXType* SwDoc::GetTOXType( TOXTypes eTyp, sal_uInt16 nId ) const +{ + sal_uInt16 nCnt = 0; + for( auto const & pTOXType : *mpTOXTypes ) + if( eTyp == pTOXType->GetType() && nCnt++ == nId ) + return pTOXType.get(); + return nullptr; +} + +const SwTOXType* SwDoc::InsertTOXType( const SwTOXType& rTyp ) +{ + SwTOXType * pNew = new SwTOXType(rTyp); + mpTOXTypes->emplace_back( pNew ); + return pNew; +} + +OUString SwDoc::GetUniqueTOXBaseName( const SwTOXType& rType, + const OUString& sChkStr ) const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeTOX" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpSectionFormatTable->size() + 1 ); + if( !sChkStr.isEmpty()) + newName += sChkStr; + return newName; + } + + bool bUseChkStr = !sChkStr.isEmpty(); + const OUString& aName( rType.GetTypeName() ); + const sal_Int32 nNmLen = aName.getLength(); + + SwSectionFormats::size_type nNum = 0; + const SwSectionFormats::size_type nFlagSize = ( mpSectionFormatTable->size() / 8 ) +2; + std::unique_ptr<sal_uInt8[]> pSetFlags(new sal_uInt8[ nFlagSize ]); + memset( pSetFlags.get(), 0, nFlagSize ); + + for( auto pSectionFormat : *mpSectionFormatTable ) + { + const SwSectionNode *pSectNd = pSectionFormat->GetSectionNode(); + if ( !pSectNd ) + continue; + + const SwSection& rSect = pSectNd->GetSection(); + if (rSect.GetType()==SectionType::ToxContent) + { + const OUString& rNm = rSect.GetSectionName(); + if ( rNm.startsWith(aName) ) + { + // Calculate number and set the Flag + nNum = rNm.copy( nNmLen ).toInt32(); + if( nNum-- && nNum < mpSectionFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + if ( bUseChkStr && sChkStr==rNm ) + bUseChkStr = false; + } + } + + if( !bUseChkStr ) + { + // All Numbers have been flagged accordingly, so get the right Number + nNum = mpSectionFormatTable->size(); + for( SwSectionFormats::size_type n = 0; n < nFlagSize; ++n ) + { + sal_uInt8 nTmp = pSetFlags[ n ]; + if( nTmp != 0xff ) + { + // so get the Number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + } + if ( bUseChkStr ) + return sChkStr; + return aName + OUString::number( ++nNum ); +} + +bool SwDoc::SetTOXBaseName(const SwTOXBase& rTOXBase, const OUString& rName) +{ + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, + "no TOXBaseSection!" ); + SwTOXBaseSection* pTOX = const_cast<SwTOXBaseSection*>(static_cast<const SwTOXBaseSection*>(&rTOXBase)); + + if (GetUniqueTOXBaseName(*rTOXBase.GetTOXType(), rName) == rName) + { + pTOX->SetTOXName(rName); + pTOX->SetSectionName(rName); + getIDocumentState().SetModified(); + return true; + } + return false; +} + +static const SwTextNode* lcl_FindChapterNode( const SwNode& rNd, + SwRootFrame const*const pLayout, sal_uInt8 const nLvl = 0 ) +{ + const SwNode* pNd = &rNd; + if( pNd->GetNodes().GetEndOfExtras().GetIndex() > pNd->GetIndex() ) + { + // then find the "Anchor" (Body) position + Point aPt; + SwNode2Layout aNode2Layout( *pNd, pNd->GetIndex() ); + const SwFrame* pFrame = aNode2Layout.GetFrame( &aPt ); + + if( pFrame ) + { + SwPosition aPos( *pNd ); + pNd = GetBodyTextNode( *pNd->GetDoc(), aPos, *pFrame ); + OSL_ENSURE( pNd, "Where's the paragraph?" ); + } + } + return pNd ? pNd->FindOutlineNodeOfLevel(nLvl, pLayout) : nullptr; +} + +// Table of contents class +SwTOXBaseSection::SwTOXBaseSection(SwTOXBase const& rBase, SwSectionFormat & rFormat) + : SwTOXBase( rBase ) + , SwSection( SectionType::ToxContent, OUString(), rFormat ) +{ + SetProtect( rBase.IsProtected() ); + SetSectionName( GetTOXName() ); +} + +SwTOXBaseSection::~SwTOXBaseSection() +{ +} + +bool SwTOXBaseSection::SetPosAtStartEnd( SwPosition& rPos ) const +{ + bool bRet = false; + const SwSectionNode* pSectNd = GetFormat()->GetSectionNode(); + if( pSectNd ) + { + rPos.nNode = *pSectNd; + SwContentNode* pCNd = pSectNd->GetDoc()->GetNodes().GoNext( &rPos.nNode ); + rPos.nContent.Assign( pCNd, 0 ); + bRet = true; + } + return bRet; +} + +/// Collect table of contents content +void SwTOXBaseSection::Update(const SfxItemSet* pAttr, + SwRootFrame const*const pLayout, + const bool _bNewTOX) +{ + if (!SwTOXBase::GetRegisteredIn()->HasWriterListeners() || + !GetFormat()) + { + return; + } + SwSectionNode const*const pSectNd(GetFormat()->GetSectionNode()); + if (nullptr == pSectNd || + !pSectNd->GetNodes().IsDocNodes() || + IsHiddenFlag() || + (pLayout->IsHideRedlines() && pSectNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)) + { + return; + } + + if ( !mbKeepExpression ) + { + maMSTOCExpression.clear(); + } + + SwDoc* pDoc = const_cast<SwDoc*>(pSectNd->GetDoc()); + + assert(pDoc); //Where is the document? + + if (pAttr && GetFormat()) + pDoc->ChgFormat(*GetFormat(), *pAttr); + + // determine default page description, which will be used by the content nodes, + // if no appropriate one is found. + const SwPageDesc* pDefaultPageDesc; + { + pDefaultPageDesc = + pSectNd->GetSection().GetFormat()->GetPageDesc().GetPageDesc(); + if ( !_bNewTOX && !pDefaultPageDesc ) + { + // determine page description of table-of-content + size_t nPgDescNdIdx = pSectNd->GetIndex() + 1; + size_t* pPgDescNdIdx = &nPgDescNdIdx; + pDefaultPageDesc = pSectNd->FindPageDesc( pPgDescNdIdx ); + if ( nPgDescNdIdx < pSectNd->GetIndex() ) + { + pDefaultPageDesc = nullptr; + } + } + // consider end node of content section in the node array. + if ( !pDefaultPageDesc && + ( pSectNd->EndOfSectionNode()->GetIndex() < + (pSectNd->GetNodes().GetEndOfContent().GetIndex() - 1) ) + ) + { + // determine page description of content after table-of-content + SwNodeIndex aIdx( *(pSectNd->EndOfSectionNode()) ); + const SwContentNode* pNdAfterTOX = pSectNd->GetNodes().GoNext( &aIdx ); + const SwAttrSet& aNdAttrSet = pNdAfterTOX->GetSwAttrSet(); + const SvxBreak eBreak = aNdAttrSet.GetBreak().GetBreak(); + if ( !( eBreak == SvxBreak::PageBefore || + eBreak == SvxBreak::PageBoth ) + ) + { + pDefaultPageDesc = pNdAfterTOX->FindPageDesc(); + } + } + // consider start node of content section in the node array. + if ( !pDefaultPageDesc && + ( pSectNd->GetIndex() > + (pSectNd->GetNodes().GetEndOfContent().StartOfSectionIndex() + 1) ) + ) + { + // determine page description of content before table-of-content + SwNodeIndex aIdx( *pSectNd ); + pDefaultPageDesc = + SwNodes::GoPrevious( &aIdx )->FindPageDesc(); + + } + if ( !pDefaultPageDesc ) + { + // determine default page description + pDefaultPageDesc = &pDoc->GetPageDesc( 0 ); + } + } + + pDoc->getIDocumentState().SetModified(); + + // get current Language + SwTOXInternational aIntl( GetLanguage(), + TOX_INDEX == GetTOXType()->GetType() ? + GetOptions() : SwTOIOptions::NONE, + GetSortAlgorithm() ); + + m_aSortArr.clear(); + + // find the first layout node for this TOX, if it only find the content + // in his own chapter + const SwTextNode* pOwnChapterNode = IsFromChapter() + ? ::lcl_FindChapterNode( *pSectNd, pLayout ) + : nullptr; + + SwNode2LayoutSaveUpperFrames aN2L(*pSectNd); + const_cast<SwSectionNode*>(pSectNd)->DelFrames(); + + // This would be a good time to update the Numbering + pDoc->UpdateNumRule(); + + if( GetCreateType() & SwTOXElement::Mark ) + UpdateMarks( aIntl, pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::OutlineLevel ) + UpdateOutline( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Template ) + UpdateTemplate( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Ole || + TOX_OBJECTS == SwTOXBase::GetType()) + UpdateContent( SwTOXElement::Ole, pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Table || + (TOX_TABLES == SwTOXBase::GetType() && IsFromObjectNames()) ) + UpdateTable( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Graphic || + (TOX_ILLUSTRATIONS == SwTOXBase::GetType() && IsFromObjectNames())) + UpdateContent( SwTOXElement::Graphic, pOwnChapterNode, pLayout ); + + if( !GetSequenceName().isEmpty() && !IsFromObjectNames() && + (TOX_TABLES == SwTOXBase::GetType() || + TOX_ILLUSTRATIONS == SwTOXBase::GetType() ) ) + UpdateSequence( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Frame ) + UpdateContent( SwTOXElement::Frame, pOwnChapterNode, pLayout ); + + if(TOX_AUTHORITIES == SwTOXBase::GetType()) + UpdateAuthorities( aIntl, pLayout ); + + // Insert AlphaDelimiters if needed (just for keywords) + if( TOX_INDEX == SwTOXBase::GetType() && + ( GetOptions() & SwTOIOptions::AlphaDelimiter ) ) + InsertAlphaDelimiter( aIntl ); + + // remove old content an insert one empty textnode (to hold the layout!) + SwTextNode* pFirstEmptyNd; + + SwUndoUpdateIndex * pUndo(nullptr); + { + pDoc->getIDocumentRedlineAccess().DeleteRedline( *pSectNd, true, RedlineType::Any ); + + SwNodeIndex aSttIdx( *pSectNd, +1 ); + SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() ); + pFirstEmptyNd = pDoc->GetNodes().MakeTextNode( aEndIdx, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + + { + // Task 70995 - save and restore PageDesc and Break Attributes + SwNodeIndex aNxtIdx( aSttIdx ); + const SwContentNode* pCNd = aNxtIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &aNxtIdx ); + assert(pCNd != pFirstEmptyNd); + assert(pCNd->GetIndex() < pFirstEmptyNd->GetIndex()); + if( pCNd->HasSwAttrSet() ) + { + SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); + aBrkSet.Put( *pCNd->GetpSwAttrSet() ); + if( aBrkSet.Count() ) + pFirstEmptyNd->SetAttr( aBrkSet ); + } + } + + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // note: this will first append a SwUndoDelSection from the ctor... + pUndo = new SwUndoUpdateIndex(*this); + // tdf#123313 insert Undo *after* all CrossRefBookmark Undos have + // been inserted by the Update*() functions + pDoc->GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndoUpdateIndex>(pUndo)); + } + else + { + --aEndIdx; + SwPosition aPos( aEndIdx, SwIndex( pFirstEmptyNd, 0 )); + SwDoc::CorrAbs( aSttIdx, aEndIdx, aPos, true ); + + // delete flys in whole range including start node which requires + // giving the node before start node as Mark parameter, hence -1. + // (flys must be deleted because the anchor nodes are removed) + DelFlyInRange( SwNodeIndex(aSttIdx, -1), aEndIdx ); + + pDoc->GetNodes().Delete( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() ); + } + } + + // insert title of TOX + if ( !GetTitle().isEmpty() ) + { + // then insert the headline section + SwNodeIndex aIdx( *pSectNd, +1 ); + + SwTextNode* pHeadNd = pDoc->GetNodes().MakeTextNode( aIdx, + GetTextFormatColl( FORM_TITLE ) ); + pHeadNd->InsertText( GetTitle(), SwIndex( pHeadNd ) ); + + SwSectionData headerData( SectionType::ToxHeader, GetTOXName()+"_Head" ); + + SwNodeIndex aStt( *pHeadNd ); --aIdx; + SwSectionFormat* pSectFormat = pDoc->MakeSectionFormat(); + pDoc->GetNodes().InsertTextSection( + aStt, *pSectFormat, headerData, nullptr, &aIdx, true, false); + + if (pUndo) + { + pUndo->TitleSectionInserted(*pSectFormat); + } + } + + // Sort the List of all TOC Marks and TOC Sections + std::vector<SwTextFormatColl*> aCollArr( GetTOXForm().GetFormMax(), nullptr ); + SwNodeIndex aInsPos( *pFirstEmptyNd, 1 ); + for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + // Put the Text into the TOC + sal_uInt16 nLvl = m_aSortArr[ nCnt ]->GetLevel(); + SwTextFormatColl* pColl = aCollArr[ nLvl ]; + if( !pColl ) + { + pColl = GetTextFormatColl( nLvl ); + aCollArr[ nLvl ] = pColl; + } + + // Generate: Set dynamic TabStops + SwTextNode* pTOXNd = pDoc->GetNodes().MakeTextNode( aInsPos , pColl ); + m_aSortArr[ nCnt ]->pTOXNd = pTOXNd; + + // Generate: Evaluate Form and insert the place holder for the + // page number. If it is a TOX_INDEX and the SwForm IsCommaSeparated() + // then a range of entries must be generated into one paragraph + size_t nRange = 1; + if(TOX_INDEX == SwTOXBase::GetType() && + GetTOXForm().IsCommaSeparated() && + m_aSortArr[nCnt]->GetType() == TOX_SORT_INDEX) + { + const SwTOXMark& rMark = m_aSortArr[nCnt]->pTextMark->GetTOXMark(); + const OUString& sPrimKey = rMark.GetPrimaryKey(); + const OUString& sSecKey = rMark.GetSecondaryKey(); + const SwTOXMark* pNextMark = nullptr; + while(m_aSortArr.size() > (nCnt + nRange) && + m_aSortArr[nCnt + nRange]->GetType() == TOX_SORT_INDEX ) + { + pNextMark = &(m_aSortArr[nCnt + nRange]->pTextMark->GetTOXMark()); + if( !pNextMark || + pNextMark->GetPrimaryKey() != sPrimKey || + pNextMark->GetSecondaryKey() != sSecKey) + break; + nRange++; + } + } + // pass node index of table-of-content section and default page description + // to method <GenerateText(..)>. + ::SetProgressState( 0, pDoc->GetDocShell() ); + + std::shared_ptr<sw::ToxTabStopTokenHandler> tabStopTokenHandler = + std::make_shared<sw::DefaultToxTabStopTokenHandler>( + pSectNd->GetIndex(), *pDefaultPageDesc, GetTOXForm().IsRelTabPos(), + pDoc->GetDocumentSettingManager().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ? + sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_INDENT : + sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_PAGE); + sw::ToxTextGenerator ttgn(GetTOXForm(), tabStopTokenHandler); + ttgn.GenerateText(GetFormat()->GetDoc(), m_aSortArr, nCnt, nRange, pLayout); + nCnt += nRange - 1; + } + + // delete the first dummy node and remove all Cursor into the previous node + aInsPos = *pFirstEmptyNd; + { + SwPaM aCorPam( *pFirstEmptyNd ); + aCorPam.GetPoint()->nContent.Assign( pFirstEmptyNd, 0 ); + if( !aCorPam.Move( fnMoveForward ) ) + aCorPam.Move( fnMoveBackward ); + SwNodeIndex aEndIdx( aInsPos, 1 ); + SwDoc::CorrAbs( aInsPos, aEndIdx, *aCorPam.GetPoint(), true ); + + // Task 70995 - save and restore PageDesc and Break Attributes + if( pFirstEmptyNd->HasSwAttrSet() ) + { + if( !GetTitle().isEmpty() ) + aEndIdx = *pSectNd; + else + aEndIdx = *pFirstEmptyNd; + SwContentNode* pCNd = pDoc->GetNodes().GoNext( &aEndIdx ); + if( pCNd ) // Robust against defect documents, e.g. i60336 + pCNd->SetAttr( *pFirstEmptyNd->GetpSwAttrSet() ); + } + } + + // now create the new Frames + sal_uLong nIdx = pSectNd->GetIndex(); + // don't delete if index is empty + if(nIdx + 2 < pSectNd->EndOfSectionIndex()) + pDoc->GetNodes().Delete( aInsPos ); + + aN2L.RestoreUpperFrames( pDoc->GetNodes(), nIdx, nIdx + 1 ); + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = pDoc->GetAllLayouts(); + for ( const auto& rpLayout : aAllLayouts ) + { + SwFrame::CheckPageDescs( static_cast<SwPageFrame*>(rpLayout->Lower()) ); + } + + SetProtect( SwTOXBase::IsProtected() ); +} + +void SwTOXBaseSection::InsertAlphaDelimiter( const SwTOXInternational& rIntl ) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + OUString sLastDeli; + size_t i = 0; + while( i < m_aSortArr.size() ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + sal_uInt16 nLevel = m_aSortArr[i]->GetLevel(); + + // Skip AlphaDelimiter + if( nLevel == FORM_ALPHA_DELIMITER ) + continue; + + const OUString sDeli = rIntl.GetIndexKey( m_aSortArr[i]->GetText(), + m_aSortArr[i]->GetLocale() ); + + // Do we already have a Delimiter? + if( !sDeli.isEmpty() && sLastDeli != sDeli ) + { + // We skip all that are less than a small Blank (these are special characters) + if( ' ' <= sDeli[0] ) + { + std::unique_ptr<SwTOXCustom> pCst( + MakeSwTOXSortTabBase<SwTOXCustom>(nullptr, + TextAndReading(sDeli, OUString()), + FORM_ALPHA_DELIMITER, + rIntl, m_aSortArr[i]->GetLocale() )); + m_aSortArr.insert( m_aSortArr.begin() + i, std::move(pCst)); + i++; + } + sLastDeli = sDeli; + } + + // Skip until we get to the same or a lower Level + do { + i++; + } while (i < m_aSortArr.size() && m_aSortArr[i]->GetLevel() > nLevel); + } +} + +/// Evaluate Template +SwTextFormatColl* SwTOXBaseSection::GetTextFormatColl( sal_uInt16 nLevel ) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + const OUString& rName = GetTOXForm().GetTemplate( nLevel ); + SwTextFormatColl* pColl = !rName.isEmpty() ? pDoc->FindTextFormatCollByName(rName) :nullptr; + if( !pColl ) + { + sal_uInt16 nPoolFormat = 0; + const TOXTypes eMyType = SwTOXBase::GetType(); + switch( eMyType ) + { + case TOX_INDEX: nPoolFormat = RES_POOLCOLL_TOX_IDXH; break; + case TOX_USER: + if( nLevel < 6 ) + nPoolFormat = RES_POOLCOLL_TOX_USERH; + else + nPoolFormat = RES_POOLCOLL_TOX_USER6 - 6; + break; + case TOX_ILLUSTRATIONS: nPoolFormat = RES_POOLCOLL_TOX_ILLUSH; break; + case TOX_OBJECTS: nPoolFormat = RES_POOLCOLL_TOX_OBJECTH; break; + case TOX_TABLES: nPoolFormat = RES_POOLCOLL_TOX_TABLESH; break; + case TOX_AUTHORITIES: + case TOX_BIBLIOGRAPHY: + nPoolFormat = RES_POOLCOLL_TOX_AUTHORITIESH; break; + case TOX_CITATION: /** TODO */break; + case TOX_CONTENT: + // There's a jump in the ContentArea! + if( nLevel < 6 ) + nPoolFormat = RES_POOLCOLL_TOX_CNTNTH; + else + nPoolFormat = RES_POOLCOLL_TOX_CNTNT6 - 6; + break; + } + + if(eMyType == TOX_AUTHORITIES && nLevel) + nPoolFormat = nPoolFormat + 1; + else if(eMyType == TOX_INDEX && nLevel) + { + // pool: Level 1,2,3, Delimiter + // SwForm: Delimiter, Level 1,2,3 + nPoolFormat += 1 == nLevel ? nLevel + 3 : nLevel - 1; + } + else + nPoolFormat = nPoolFormat + nLevel; + pColl = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolFormat ); + } + return pColl; +} + +/// Create from Marks +void SwTOXBaseSection::UpdateMarks( const SwTOXInternational& rIntl, + const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + const SwTOXType* pType = static_cast<SwTOXType*>( SwTOXBase::GetRegisteredIn() ); + if( !pType->HasWriterListeners() ) + return; + + SwDoc* pDoc = GetFormat()->GetDoc(); + TOXTypes eTOXTyp = GetTOXType()->GetType(); + SwIterator<SwTOXMark,SwTOXType> aIter( *pType ); + + for (SwTOXMark* pMark = aIter.First(); pMark; pMark = aIter.Next()) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (pMark->GetTOXType()->GetType() == eTOXTyp) + { + SwTextTOXMark *const pTextMark(pMark->GetTextTOXMark()); + if (nullptr == pTextMark) + continue; + const SwTextNode* pTOXSrc = pTextMark->GetpTextNd(); + // Only insert TOXMarks from the Doc, not from the + // UNDO. + + // If selected use marks from the same chapter only + if( pTOXSrc->GetNodes().IsDocNodes() && + pTOXSrc->GetText().getLength() && pTOXSrc->HasWriterListeners() && + pTOXSrc->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) && + (!IsFromChapter() || ::lcl_FindChapterNode(*pTOXSrc, pLayout) == pOwnChapterNode) && + !pTOXSrc->IsHiddenByParaField() && + !SwScriptInfo::IsInHiddenRange(*pTOXSrc, pTextMark->GetStart()) && + (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsMarkHintHidden(*pLayout, *pTOXSrc, *pTextMark))) + { + if(TOX_INDEX == eTOXTyp) + { + // index entry mark + assert(g_pBreakIt); + lang::Locale aLocale = g_pBreakIt->GetLocale(pTOXSrc->GetLang(pTextMark->GetStart())); + + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, *pTOXSrc, pTextMark, + GetOptions(), FORM_ENTRY, rIntl, aLocale )); + if(GetOptions() & SwTOIOptions::KeyAsEntry && + !pTextMark->GetTOXMark().GetPrimaryKey().isEmpty()) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, *pTOXSrc, pTextMark, + GetOptions(), FORM_PRIMARY_KEY, rIntl, aLocale )); + if (!pTextMark->GetTOXMark().GetSecondaryKey().isEmpty()) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, *pTOXSrc, pTextMark, + GetOptions(), FORM_SECONDARY_KEY, rIntl, aLocale )); + } + } + } + else if( TOX_USER == eTOXTyp || + pMark->GetLevel() <= GetLevel()) + { // table of content mark + // also used for user marks + InsertSorted(MakeSwTOXSortTabBase<SwTOXContent>(pLayout, *pTOXSrc, pTextMark, rIntl)); + } + } + } + } +} + +/// Generate table of contents from outline +void SwTOXBaseSection::UpdateOutline( const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + + const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds(); + for( auto pOutlineNode : rOutlNds ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + SwTextNode* pTextNd = pOutlineNode->GetTextNode(); + if( pTextNd && pTextNd->Len() && pTextNd->HasWriterListeners() && + o3tl::make_unsigned( pTextNd->GetAttrOutlineLevel()) <= GetLevel() && + pTextNd->getLayoutFrame(pLayout) && + !pTextNd->IsHiddenByParaField() && + !pTextNd->HasHiddenCharAttribute( true ) && + (!pLayout || !pLayout->IsHideRedlines() + || static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLayout))->GetTextNodeForParaProps() == pTextNd) && + ( !IsFromChapter() || + ::lcl_FindChapterNode(*pTextNd, pLayout) == pOwnChapterNode )) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXPara>(pLayout, *pTextNd, SwTOXElement::OutlineLevel)); + } + } +} + +/// Generate table of contents from template areas +void SwTOXBaseSection::UpdateTemplate(const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + for(sal_uInt16 i = 0; i < MAXLEVEL; i++) + { + const OUString sTmpStyleNames = GetStyleNames(i); + if (sTmpStyleNames.isEmpty()) + continue; + + sal_Int32 nIndex = 0; + while (nIndex >= 0) + { + SwTextFormatColl* pColl = pDoc->FindTextFormatCollByName( + sTmpStyleNames.getToken( 0, TOX_STYLE_DELIMITER, nIndex )); + //TODO: no outline Collections in content indexes if OutlineLevels are already included + if( !pColl || + ( TOX_CONTENT == SwTOXBase::GetType() && + GetCreateType() & SwTOXElement::OutlineLevel && + pColl->IsAssignedToListLevelOfOutlineStyle()) ) + continue; + + SwIterator<SwTextNode,SwFormatColl> aIter( *pColl ); + for( SwTextNode* pTextNd = aIter.First(); pTextNd; pTextNd = aIter.Next() ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (pTextNd->GetText().getLength() && + pTextNd->getLayoutFrame(pLayout) && + pTextNd->GetNodes().IsDocNodes() && + (!pLayout || !pLayout->IsHideRedlines() + || static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLayout))->GetTextNodeForParaProps() == pTextNd) && + (!IsFromChapter() || pOwnChapterNode == + ::lcl_FindChapterNode(*pTextNd, pLayout))) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXPara>(pLayout, *pTextNd, SwTOXElement::Template, i + 1)); + } + } + } + } +} + +/// Generate content from sequence fields +void SwTOXBaseSection::UpdateSequence(const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwFieldType* pSeqField = pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, GetSequenceName(), false); + if(!pSeqField) + return; + + std::vector<SwFormatField*> vFields; + pSeqField->GatherFields(vFields); + for(auto pFormatField: vFields) + { + const SwTextField* pTextField = pFormatField->GetTextField(); + SwTextNode& rTextNode = pTextField->GetTextNode(); + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (rTextNode.GetText().getLength() && + rTextNode.getLayoutFrame(pLayout) && + ( !IsFromChapter() || + ::lcl_FindChapterNode(rTextNode, pLayout) == pOwnChapterNode) + && (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsFieldDeletedInModel(pDoc->getIDocumentRedlineAccess(), *pTextField))) + { + const SwSetExpField& rSeqField = dynamic_cast<const SwSetExpField&>(*(pFormatField->GetField())); + const OUString sName = GetSequenceName() + + OUStringChar(cSequenceMarkSeparator) + + OUString::number( rSeqField.GetSeqNumber() ); + std::unique_ptr<SwTOXPara> pNew(new SwTOXPara( rTextNode, SwTOXElement::Sequence, 1, sName )); + // set indexes if the number or the reference text are to be displayed + if( GetCaptionDisplay() == CAPTION_TEXT ) + { + pNew->SetStartIndex( + SwGetExpField::GetReferenceTextPos( *pFormatField, *pDoc )); + } + else if(GetCaptionDisplay() == CAPTION_NUMBER) + { + pNew->SetEndIndex(pTextField->GetStart() + 1); + } + pNew->InitText(pLayout); + InsertSorted(std::move(pNew)); + } + } +} + +void SwTOXBaseSection::UpdateAuthorities(const SwTOXInternational& rIntl, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwFieldType* pAuthField = pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::TableOfAuthorities, OUString(), false); + if(!pAuthField) + return; + + std::vector<SwFormatField*> vFields; + pAuthField->GatherFields(vFields); + for(auto pFormatField: vFields) + { + const auto pTextField = pFormatField->GetTextField(); + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (rTextNode.GetText().getLength() && + rTextNode.getLayoutFrame(pLayout) && + (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsFieldDeletedInModel(pDoc->getIDocumentRedlineAccess(), *pTextField))) + { + //#106485# the body node has to be used! + SwContentFrame *const pFrame = rTextNode.getLayoutFrame(pLayout); + SwPosition aFieldPos(rTextNode); + const SwTextNode* pTextNode = nullptr; + if(pFrame && !pFrame->IsInDocBody()) + pTextNode = GetBodyTextNode( *pDoc, aFieldPos, *pFrame ); + if(!pTextNode) + pTextNode = &rTextNode; + + InsertSorted(MakeSwTOXSortTabBase<SwTOXAuthority>(pLayout, *pTextNode, *pFormatField, rIntl)); + } + } +} + +static SwTOOElements lcl_IsSOObject( const SvGlobalName& rFactoryNm ) +{ + static const struct SoObjType { + SwTOOElements nFlag; + // GlobalNameId + struct { + sal_uInt32 n1; + sal_uInt16 n2, n3; + sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15; + } aGlNmIds[4]; + } aArr[] = { + { SwTOOElements::Math, + { {SO3_SM_CLASSID_60},{SO3_SM_CLASSID_50}, + {SO3_SM_CLASSID_40},{SO3_SM_CLASSID_30} } }, + { SwTOOElements::Chart, + { {SO3_SCH_CLASSID_60},{SO3_SCH_CLASSID_50}, + {SO3_SCH_CLASSID_40},{SO3_SCH_CLASSID_30} } }, + { SwTOOElements::Calc, + { {SO3_SC_CLASSID_60},{SO3_SC_CLASSID_50}, + {SO3_SC_CLASSID_40},{SO3_SC_CLASSID_30} } }, + { SwTOOElements::DrawImpress, + { {SO3_SIMPRESS_CLASSID_60},{SO3_SIMPRESS_CLASSID_50}, + {SO3_SIMPRESS_CLASSID_40},{SO3_SIMPRESS_CLASSID_30} } }, + { SwTOOElements::DrawImpress, + { {SO3_SDRAW_CLASSID_60},{SO3_SDRAW_CLASSID_50} } } + }; + + for( SoObjType const & rArr : aArr ) + for (auto & rId : rArr.aGlNmIds) + { + if( !rId.n1 ) + break; + SvGlobalName aGlbNm( rId.n1, rId.n2, rId.n3, + rId.b8, rId.b9, rId.b10, rId.b11, + rId.b12, rId.b13, rId.b14, rId.b15 ); + if( rFactoryNm == aGlbNm ) + { + return rArr.nFlag; + } + } + + return SwTOOElements::NONE; +} + +void SwTOXBaseSection::UpdateContent( SwTOXElement eMyType, + const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + // on the 1st Node of the 1st Section + sal_uLong nIdx = rNds.GetEndOfAutotext().StartOfSectionIndex() + 2, + nEndIdx = rNds.GetEndOfAutotext().GetIndex(); + + while( nIdx < nEndIdx ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + SwNode* pNd = rNds[ nIdx ]; + SwContentNode* pCNd = nullptr; + switch( eMyType ) + { + case SwTOXElement::Frame: + if( !pNd->IsNoTextNode() ) + { + pCNd = pNd->GetContentNode(); + if( !pCNd ) + { + SwNodeIndex aTmp( *pNd ); + pCNd = rNds.GoNext( &aTmp ); + } + } + break; + case SwTOXElement::Graphic: + if( pNd->IsGrfNode() ) + pCNd = static_cast<SwContentNode*>(pNd); + break; + case SwTOXElement::Ole: + if( pNd->IsOLENode() ) + { + bool bInclude = true; + if(TOX_OBJECTS == SwTOXBase::GetType()) + { + SwOLENode* pOLENode = pNd->GetOLENode(); + SwTOOElements nMyOLEOptions = GetOLEOptions(); + SwOLEObj& rOLEObj = pOLENode->GetOLEObj(); + + if( rOLEObj.IsOleRef() ) // Not yet loaded + { + SvGlobalName aTmpName( rOLEObj.GetOleRef()->getClassID() ); + SwTOOElements nObj = ::lcl_IsSOObject( aTmpName ); + bInclude = ( (nMyOLEOptions & SwTOOElements::Other) && SwTOOElements::NONE == nObj ) + || (nMyOLEOptions & nObj); + } + else + { + OSL_FAIL("OLE Object no loaded?"); + bInclude = false; + } + } + + if(bInclude) + pCNd = static_cast<SwContentNode*>(pNd); + } + break; + default: break; + } + + if( pCNd ) + { + // find node in body text + int nSetLevel = USHRT_MAX; + + //#111105# tables of tables|illustrations|objects don't support hierarchies + if( IsLevelFromChapter() && + TOX_TABLES != SwTOXBase::GetType() && + TOX_ILLUSTRATIONS != SwTOXBase::GetType() && + TOX_OBJECTS != SwTOXBase::GetType() ) + { + const SwTextNode* pOutlNd = ::lcl_FindChapterNode( *pCNd, + pLayout, MAXLEVEL - 1); + if( pOutlNd ) + { + if( pOutlNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle()) + { + nSetLevel = pOutlNd->GetTextColl()->GetAttrOutlineLevel(); + } + } + } + + if (pCNd->getLayoutFrame(pLayout) + && (!pLayout || !pLayout->IsHideRedlines() + || pCNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + && ( !IsFromChapter() || + ::lcl_FindChapterNode(*pCNd, pLayout) == pOwnChapterNode )) + { + std::unique_ptr<SwTOXPara> pNew( MakeSwTOXSortTabBase<SwTOXPara>( + pLayout, *pCNd, eMyType, + ( USHRT_MAX != nSetLevel ) + ? static_cast<sal_uInt16>(nSetLevel) + : FORM_ALPHA_DELIMITER ) ); + InsertSorted( std::move(pNew) ); + } + } + + nIdx = pNd->StartOfSectionNode()->EndOfSectionIndex() + 2; // 2 == End/Start Node + } +} + +/// Collect table entries +void SwTOXBaseSection::UpdateTable(const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + const SwFrameFormats& rArr = *pDoc->GetTableFrameFormats(); + + for( auto pFrameFormat : rArr ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + SwTable* pTmpTable = SwTable::FindTable( pFrameFormat ); + SwTableBox* pFBox; + if( pTmpTable && nullptr != (pFBox = pTmpTable->GetTabSortBoxes()[0] ) && + pFBox->GetSttNd() && pFBox->GetSttNd()->GetNodes().IsDocNodes() ) + { + const SwTableNode* pTableNd = pFBox->GetSttNd()->FindTableNode(); + SwNodeIndex aContentIdx( *pTableNd, 1 ); + + SwContentNode* pCNd; + while( nullptr != ( pCNd = rNds.GoNext( &aContentIdx ) ) && + aContentIdx.GetIndex() < pTableNd->EndOfSectionIndex() ) + { + if (pCNd->getLayoutFrame(pLayout) + && (!pLayout || !pLayout->IsHideRedlines() + || pCNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + && (!IsFromChapter() + || ::lcl_FindChapterNode(*pCNd, pLayout) == pOwnChapterNode)) + { + std::unique_ptr<SwTOXTable> pNew(new SwTOXTable( *pCNd )); + if( IsLevelFromChapter() && TOX_TABLES != SwTOXBase::GetType()) + { + const SwTextNode* pOutlNd = + ::lcl_FindChapterNode(*pCNd, pLayout, MAXLEVEL - 1); + if( pOutlNd ) + { + if( pOutlNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle()) + { + const int nTmp = pOutlNd->GetTextColl()->GetAttrOutlineLevel(); + pNew->SetLevel(static_cast<sal_uInt16>(nTmp)); + } + } + } + pNew->InitText(pLayout); + InsertSorted(std::move(pNew)); + break; + } + } + } + } +} + +/// Calculate PageNumber and insert after formatting +void SwTOXBaseSection::UpdatePageNum() +{ + if( m_aSortArr.empty() ) + return ; + + // Insert the current PageNumber into the TOC + SwPageFrame* pCurrentPage = nullptr; + sal_uInt16 nPage = 0; + SwDoc* pDoc = GetFormat()->GetDoc(); + + SwTOXInternational aIntl( GetLanguage(), + TOX_INDEX == GetTOXType()->GetType() ? + GetOptions() : SwTOIOptions::NONE, + GetSortAlgorithm() ); + + for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) + { + // Loop over all SourceNodes + + // process run in lines + size_t nRange = 0; + if(GetTOXForm().IsCommaSeparated() && + m_aSortArr[nCnt]->GetType() == TOX_SORT_INDEX) + { + const SwTOXMark& rMark = m_aSortArr[nCnt]->pTextMark->GetTOXMark(); + const OUString& sPrimKey = rMark.GetPrimaryKey(); + const OUString& sSecKey = rMark.GetSecondaryKey(); + const SwTOXMark* pNextMark = nullptr; + while(m_aSortArr.size() > (nCnt + nRange)&& + m_aSortArr[nCnt + nRange]->GetType() == TOX_SORT_INDEX && + nullptr != (pNextMark = &(m_aSortArr[nCnt + nRange]->pTextMark->GetTOXMark())) && + pNextMark->GetPrimaryKey() == sPrimKey && + pNextMark->GetSecondaryKey() == sSecKey) + nRange++; + } + else + nRange = 1; + + for(size_t nRunInEntry = nCnt; nRunInEntry < nCnt + nRange; ++nRunInEntry) + { + std::vector<sal_uInt16> aNums; // the PageNumber + std::vector<SwPageDesc*> aDescs; // The PageDescriptors matching the PageNumbers + std::vector<sal_uInt16> aMainNums; // contains page numbers of main entries + SwTOXSortTabBase* pSortBase = m_aSortArr[nRunInEntry].get(); + size_t nSize = pSortBase->aTOXSources.size(); + for (size_t j = 0; j < nSize; ++j) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + SwTOXSource& rTOXSource = pSortBase->aTOXSources[j]; + if( rTOXSource.pNd ) + { + SwContentFrame* pFrame = rTOXSource.pNd->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + OSL_ENSURE( pFrame || pDoc->IsUpdateTOX(), "TOX, no Frame found"); + if( !pFrame ) + continue; + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->HasFollow() ) + { + // find the right one + SwTextFrame* pNext; + TextFrameIndex const nPos(static_cast<SwTextFrame*>(pFrame) + ->MapModelToView(static_cast<SwTextNode const*>(rTOXSource.pNd), + rTOXSource.nPos)); + for (;;) + { + pNext = static_cast<SwTextFrame*>(pFrame->GetFollow()); + if (!pNext || nPos < pNext->GetOffset()) + break; + pFrame = pNext; + } + } + + SwPageFrame* pTmpPage = pFrame->FindPageFrame(); + if( pTmpPage != pCurrentPage ) + { + nPage = pTmpPage->GetVirtPageNum(); + pCurrentPage = pTmpPage; + } + + // Insert as sorted + std::vector<sal_uInt16>::size_type i; + for( i = 0; i < aNums.size() && aNums[i] < nPage; ++i ) + ; + + if( i >= aNums.size() || aNums[ i ] != nPage ) + { + aNums.insert(aNums.begin() + i, nPage); + aDescs.insert(aDescs.begin() + i, pCurrentPage->GetPageDesc() ); + } + // is it a main entry? + if(TOX_SORT_INDEX == pSortBase->GetType() && + rTOXSource.bMainEntry) + { + aMainNums.push_back(nPage); + } + } + } + // Insert the PageNumber into the TOC TextNode + const SwTOXSortTabBase* pBase = m_aSortArr[ nCnt ].get(); + if(pBase->pTOXNd) + { + const SwTextNode* pTextNd = pBase->pTOXNd->GetTextNode(); + OSL_ENSURE( pTextNd, "no TextNode, wrong TOC" ); + + UpdatePageNum_( const_cast<SwTextNode*>(pTextNd), aNums, aDescs, &aMainNums, + aIntl ); + } + } + } + // Delete the mapping array after setting the right PageNumber + m_aSortArr.clear(); +} + +/// Replace the PageNumber place holders. Search for the page no. in the array +/// of main entry page numbers. +static bool lcl_HasMainEntry( const std::vector<sal_uInt16>* pMainEntryNums, sal_uInt16 nToFind ) +{ + if (!pMainEntryNums) + return false; + + for( auto nMainEntry : *pMainEntryNums ) + if (nToFind == nMainEntry) + return true; + return false; +} + +void SwTOXBaseSection::UpdatePageNum_( SwTextNode* pNd, + const std::vector<sal_uInt16>& rNums, + const std::vector<SwPageDesc*>& rDescs, + const std::vector<sal_uInt16>* pMainEntryNums, + const SwTOXInternational& rIntl ) +{ + // collect starts end ends of main entry character style + std::unique_ptr< std::vector<sal_uInt16> > xCharStyleIdx(pMainEntryNums ? new std::vector<sal_uInt16> : nullptr); + + OUString sSrchStr = OUStringChar(C_NUM_REPL) + S_PAGE_DELI + OUStringChar(C_NUM_REPL); + sal_Int32 nStartPos = pNd->GetText().indexOf(sSrchStr); + sSrchStr = OUStringChar(C_NUM_REPL) + OUStringChar(C_END_PAGE_NUM); + sal_Int32 nEndPos = pNd->GetText().indexOf(sSrchStr); + + if (-1 == nEndPos || rNums.empty()) + return; + + if (-1 == nStartPos || nStartPos > nEndPos) + nStartPos = nEndPos; + + sal_uInt16 nOld = rNums[0], + nBeg = nOld, + nCount = 0; + OUString aNumStr( rDescs[0]->GetNumType().GetNumStr( nBeg ) ); + if( xCharStyleIdx && lcl_HasMainEntry( pMainEntryNums, nBeg )) + { + xCharStyleIdx->push_back( 0 ); + } + + // Delete place holder + SwIndex aPos(pNd, nStartPos); + SwCharFormat* pPageNoCharFormat = nullptr; + SwpHints* pHints = pNd->GetpSwpHints(); + if(pHints) + for(size_t nHintIdx = 0; nHintIdx < pHints->Count(); ++nHintIdx) + { + const SwTextAttr* pAttr = pHints->Get(nHintIdx); + const sal_Int32 nTmpEnd = pAttr->End() ? *pAttr->End() : 0; + if( nStartPos >= pAttr->GetStart() && + (nStartPos + 2) <= nTmpEnd && + pAttr->Which() == RES_TXTATR_CHARFMT) + { + pPageNoCharFormat = pAttr->GetCharFormat().GetCharFormat(); + break; + } + } + pNd->EraseText(aPos, nEndPos - nStartPos + 2); + + std::vector<sal_uInt16>::size_type i; + for( i = 1; i < rNums.size(); ++i) + { + SvxNumberType aType( rDescs[i]->GetNumType() ); + if( TOX_INDEX == SwTOXBase::GetType() ) + { // Summarize for the following + // Add up all following + // break up if main entry starts or ends and + // insert a char style index + bool bMainEntryChanges = lcl_HasMainEntry(pMainEntryNums, nOld) + != lcl_HasMainEntry(pMainEntryNums, rNums[i]); + + if(nOld == rNums[i]-1 && !bMainEntryChanges && + (GetOptions() & (SwTOIOptions::FF|SwTOIOptions::Dash))) + nCount++; + else + { + // Flush for the following old values + if(GetOptions() & SwTOIOptions::FF) + { + if ( nCount >= 1 ) + aNumStr += rIntl.GetFollowingText( nCount > 1 ); + } + else if (nCount) //#58127# If nCount == 0, then the only PageNumber is already in aNumStr! + { + if (nCount == 1 ) + aNumStr += S_PAGE_DELI; + else + aNumStr += "-"; + + aNumStr += aType.GetNumStr( nBeg + nCount ); + } + + // Create new String + nBeg = rNums[i]; + aNumStr += S_PAGE_DELI; + //the change of the character style must apply after sPageDeli is appended + if (xCharStyleIdx && bMainEntryChanges) + { + xCharStyleIdx->push_back(aNumStr.getLength()); + } + aNumStr += aType.GetNumStr( nBeg ); + nCount = 0; + } + nOld = rNums[i]; + } + else + { // Insert all Numbers + aNumStr += aType.GetNumStr( rNums[i] ); + if (i+1 != rNums.size()) + aNumStr += S_PAGE_DELI; + } + } + // Flush when ending and the following old values + if( TOX_INDEX == SwTOXBase::GetType() ) + { + if(GetOptions() & SwTOIOptions::FF) + { + if( nCount >= 1 ) + aNumStr += rIntl.GetFollowingText( nCount > 1 ); + } + else + { + if(nCount >= 2) + aNumStr += "-"; + else if(nCount == 1) + aNumStr += S_PAGE_DELI; + //#58127# If nCount == 0, then the only PageNumber is already in aNumStr! + if(nCount) + aNumStr += rDescs[i-1]->GetNumType().GetNumStr( nBeg+nCount ); + } + } + pNd->InsertText( aNumStr, aPos, SwInsertFlags::EMPTYEXPAND | SwInsertFlags::FORCEHINTEXPAND ); + if(pPageNoCharFormat) + { + SwFormatCharFormat aCharFormat( pPageNoCharFormat ); + pNd->InsertItem(aCharFormat, nStartPos, nStartPos + aNumStr.getLength(), SetAttrMode::DONTEXPAND); + } + + // The main entries should get their character style + if (xCharStyleIdx && !xCharStyleIdx->empty() && !GetMainEntryCharStyle().isEmpty()) + { + // eventually the last index must me appended + if (xCharStyleIdx->size()&0x01) + xCharStyleIdx->push_back(aNumStr.getLength()); + + // search by name + SwDoc* pDoc = pNd->GetDoc(); + sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromUIName( GetMainEntryCharStyle(), SwGetPoolIdFromName::ChrFmt ); + SwCharFormat* pCharFormat = nullptr; + if(USHRT_MAX != nPoolId) + pCharFormat = pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(nPoolId); + else + pCharFormat = pDoc->FindCharFormatByName( GetMainEntryCharStyle() ); + if(!pCharFormat) + pCharFormat = pDoc->MakeCharFormat(GetMainEntryCharStyle(), nullptr); + + // find the page numbers in aNumStr and set the character style + sal_Int32 nOffset = pNd->GetText().getLength() - aNumStr.getLength(); + SwFormatCharFormat aCharFormat(pCharFormat); + for (size_t j = 0; j < xCharStyleIdx->size(); j += 2) + { + sal_Int32 nStartIdx = (*xCharStyleIdx)[j] + nOffset; + sal_Int32 nEndIdx = (*xCharStyleIdx)[j + 1] + nOffset; + pNd->InsertItem(aCharFormat, nStartIdx, nEndIdx, SetAttrMode::DONTEXPAND); + } + + } +} + +void SwTOXBaseSection::InsertSorted(std::unique_ptr<SwTOXSortTabBase> pNew) +{ + Range aRange(0, m_aSortArr.size()); + if( TOX_INDEX == SwTOXBase::GetType() && pNew->pTextMark ) + { + const SwTOXMark& rMark = pNew->pTextMark->GetTOXMark(); + // Evaluate Key + // Calculate the range where to insert + if( !(GetOptions() & SwTOIOptions::KeyAsEntry) && + !rMark.GetPrimaryKey().isEmpty() ) + { + aRange = GetKeyRange( rMark.GetPrimaryKey(), + rMark.GetPrimaryKeyReading(), + *pNew, FORM_PRIMARY_KEY, aRange ); + + if( !rMark.GetSecondaryKey().isEmpty() ) + aRange = GetKeyRange( rMark.GetSecondaryKey(), + rMark.GetSecondaryKeyReading(), + *pNew, FORM_SECONDARY_KEY, aRange ); + } + } + // Search for identical entries and remove the trailing one + if(TOX_AUTHORITIES == SwTOXBase::GetType()) + { + for(short i = static_cast<short>(aRange.Min()); i < static_cast<short>(aRange.Max()); ++i) + { + SwTOXSortTabBase* pOld = m_aSortArr[i].get(); + if (pOld->equivalent(*pNew)) + { + if (pOld->sort_lt(*pNew)) + { + return; + } + else + { + // remove the old content + m_aSortArr.erase( m_aSortArr.begin() + i ); + aRange.Max()--; + break; + } + } + } + } + + // find position and insert + long i; + + for( i = aRange.Min(); i < aRange.Max(); ++i) + { // Only check for same level + SwTOXSortTabBase* pOld = m_aSortArr[i].get(); + if (pOld->equivalent(*pNew)) + { + if(TOX_AUTHORITIES != SwTOXBase::GetType()) + { + // Own entry for double entries or keywords + if( pOld->GetType() == TOX_SORT_CUSTOM && + SwTOXSortTabBase::GetOptions() & SwTOIOptions::KeyAsEntry) + continue; + + if(!(SwTOXSortTabBase::GetOptions() & SwTOIOptions::SameEntry)) + { // Own entry + m_aSortArr.insert(m_aSortArr.begin() + i, std::move(pNew)); + return; + } + // If the own entry is already present, add it to the references list + pOld->aTOXSources.push_back(pNew->aTOXSources[0]); + + return; + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_FAIL("Bibliography entries cannot be found here"); +#endif + } + if (pNew->sort_lt(*pOld)) + break; + } + // Skip SubLevel + while( TOX_INDEX == SwTOXBase::GetType() && i < aRange.Max() && + m_aSortArr[i]->GetLevel() > pNew->GetLevel() ) + i++; + + // Insert at position i + m_aSortArr.insert(m_aSortArr.begin()+i, std::move(pNew)); +} + +/// Find Key Range and insert if possible +Range SwTOXBaseSection::GetKeyRange(const OUString& rStr, const OUString& rStrReading, + const SwTOXSortTabBase& rNew, + sal_uInt16 nLevel, const Range& rRange ) +{ + const SwTOXInternational& rIntl = *rNew.pTOXIntl; + TextAndReading aToCompare(rStr, rStrReading); + + if( SwTOIOptions::InitialCaps & GetOptions() ) + { + aToCompare.sText = rIntl.ToUpper( aToCompare.sText, 0 ) + + aToCompare.sText.copy(1); + } + + OSL_ENSURE(rRange.Min() >= 0 && rRange.Max() >= 0, "Min Max < 0"); + + const long nMin = rRange.Min(); + const long nMax = rRange.Max(); + + long i; + + for( i = nMin; i < nMax; ++i) + { + SwTOXSortTabBase* pBase = m_aSortArr[i].get(); + + if( rIntl.IsEqual( pBase->GetText(), pBase->GetLocale(), + aToCompare, rNew.GetLocale() ) && + pBase->GetLevel() == nLevel ) + break; + } + if(i == nMax) + { // If not already present, create and insert + std::unique_ptr<SwTOXCustom> pKey(MakeSwTOXSortTabBase<SwTOXCustom>( + nullptr, aToCompare, nLevel, rIntl, rNew.GetLocale() )); + for(i = nMin; i < nMax; ++i) + { + if (nLevel == m_aSortArr[i]->GetLevel() && pKey->sort_lt(*m_aSortArr[i])) + break; + } + m_aSortArr.insert(m_aSortArr.begin() + i, std::move(pKey)); + } + const long nStart = i+1; + const long nEnd = m_aSortArr.size(); + + // Find end of range + for(i = nStart; i < nEnd; ++i) + { + if(m_aSortArr[i]->GetLevel() <= nLevel) + { + return Range(nStart, i); + } + } + return Range(nStart, nEnd); +} + +bool SwTOXBase::IsTOXBaseInReadonly() const +{ + const SwTOXBaseSection *pSect = dynamic_cast<const SwTOXBaseSection*>(this); + if (!pSect || !pSect->GetFormat()) + return false; + + const SwSectionNode* pSectNode = pSect->GetFormat()->GetSectionNode(); + if (!pSectNode) + return false; + + const SwDocShell* pDocSh = pSectNode->GetDoc()->GetDocShell(); + if (!pDocSh) + return false; + + if (pDocSh->IsReadOnly()) + return true; + + pSectNode = pSectNode->StartOfSectionNode()->FindSectionNode(); + if (!pSectNode) + return false; + + return pSectNode->GetSection().IsProtectFlag(); +} + +const SfxItemSet* SwTOXBase::GetAttrSet() const +{ + const SwTOXBaseSection *pSect = dynamic_cast<const SwTOXBaseSection*>(this); + if(pSect && pSect->GetFormat()) + return &pSect->GetFormat()->GetAttrSet(); + return nullptr; +} + +void SwTOXBase::SetAttrSet( const SfxItemSet& rSet ) +{ + SwTOXBaseSection *pSect = dynamic_cast<SwTOXBaseSection*>(this); + if( pSect && pSect->GetFormat() ) + pSect->GetFormat()->SetFormatAttr( rSet ); +} + +bool SwTOXBase::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_CONTENT_VISIBLE: + { + const SwTOXBaseSection *pSect = dynamic_cast<const SwTOXBaseSection*>(this); + if( pSect && pSect->GetFormat() ) + pSect->GetFormat()->GetInfo( rInfo ); + } + return false; + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docxforms.cxx b/sw/source/core/doc/docxforms.cxx new file mode 100644 index 000000000..ea827d58d --- /dev/null +++ b/sw/source/core/doc/docxforms.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <doc.hxx> +#include <docsh.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/frame/XModule.hpp> +#include <com/sun/star/xforms/Model.hpp> +#include <com/sun/star/xforms/XModel2.hpp> +#include <com/sun/star/xforms/XFormsUIHelper1.hpp> +#include <com/sun/star/xforms/XForms.hpp> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/container/XIndexAccess.hpp> + +using namespace ::com::sun::star; + +using uno::Reference; +using uno::UNO_QUERY; +using uno::makeAny; +using uno::Exception; +using xforms::XModel2; +using frame::XModule; +using xforms::XFormsUIHelper1; +using com::sun::star::container::XIndexAccess; + + +bool SwDoc::isXForms() const +{ + return mxXForms.is(); +} + +void SwDoc::initXForms( bool bCreateDefaultModel ) +{ + OSL_ENSURE( ! isXForms(), "please initialize only once" ); + + try + { + // create XForms components + mxXForms = xforms::XForms::create( comphelper::getProcessComponentContext() ); + + // change our module identifier, to be able to have a dedicated UI + Reference< XModule > xModule; + SwDocShell* pShell( GetDocShell() ); + if ( pShell ) + xModule.set(pShell->GetModel(), css::uno::UNO_QUERY); + OSL_ENSURE( xModule.is(), "SwDoc::initXForms: no XModule at the document!" ); + if ( xModule.is() ) + xModule->setIdentifier( "com.sun.star.xforms.XMLFormDocument" ); + + // create default model + if( bCreateDefaultModel && mxXForms.is() ) + { + OUString sName("Model 1"); + Reference<XModel2> xModel = xforms::Model::create( comphelper::getProcessComponentContext() ); + xModel->setID( sName ); + Reference<XFormsUIHelper1>( xModel, uno::UNO_QUERY_THROW )->newInstance( + "Instance 1", + OUString(), true ); + xModel->initialize(); + mxXForms->insertByName( sName, makeAny( xModel ) ); + OSL_ENSURE( mxXForms->hasElements(), "can't create XForms model" ); + } + + OSL_ENSURE( isXForms(), "initialization failed" ); + } + catch( const Exception& ) + { + } +} + +// #i113606#, to release the cyclic reference between XFormModel and bindings/submissions. +void SwDoc::disposeXForms( ) +{ + // get XForms models + if( mxXForms.is() ) + { + // iterate over all models + const uno::Sequence<OUString> aNames = mxXForms->getElementNames(); + for( const OUString& rName : aNames ) + { + Reference< xforms::XModel > xModel( + mxXForms->getByName( rName ), UNO_QUERY ); + + if( xModel.is() ) + { + // ask model for bindings + Reference< XIndexAccess > xBindings( + xModel->getBindings(), UNO_QUERY ); + + // Then release them one by one + int nCount = xBindings->getCount(); + for( int i = nCount-1; i >= 0; i-- ) + { + xModel->getBindings()->remove(xBindings->getByIndex( i )); + } + + // ask model for Submissions + Reference< XIndexAccess > xSubmissions( + xModel->getSubmissions(), UNO_QUERY ); + + // Then release them one by one + nCount = xSubmissions->getCount(); + for( int i = nCount-1; i >= 0; i-- ) + { + xModel->getSubmissions()->remove(xSubmissions->getByIndex( i )); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/extinput.cxx b/sw/source/core/doc/extinput.cxx new file mode 100644 index 000000000..d9d22c25f --- /dev/null +++ b/sw/source/core/doc/extinput.cxx @@ -0,0 +1,304 @@ +/* -*- 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 <algorithm> + +#include <com/sun/star/i18n/ScriptType.hpp> + +#include <editeng/langitem.hxx> +#include <osl/diagnose.h> +#include <svl/languageoptions.hxx> +#include <vcl/commandevent.hxx> + +#include <hintids.hxx> +#include <extinput.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <index.hxx> +#include <ndtxt.hxx> +#include <swundo.hxx> + +using namespace ::com::sun::star; + +SwExtTextInput::SwExtTextInput( const SwPaM& rPam, Ring* pRing ) + : SwPaM( *rPam.GetPoint(), static_cast<SwPaM*>(pRing) ), + m_eInputLanguage(LANGUAGE_DONTKNOW) +{ + m_bIsOverwriteCursor = false; + m_bInsText = true; +} + +SwExtTextInput::~SwExtTextInput() +{ + SwDoc *const pDoc = GetDoc(); + if (pDoc->IsInDtor()) { return; /* #i58606# */ } + + SwTextNode* pTNd = GetPoint()->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + SwIndex& rIdx = GetPoint()->nContent; + sal_Int32 nSttCnt = rIdx.GetIndex(); + sal_Int32 nEndCnt = GetMark()->nContent.GetIndex(); + if( nEndCnt != nSttCnt ) + { + // Prevent IME edited text being grouped with non-IME edited text. + bool bKeepGroupUndo = pDoc->GetIDocumentUndoRedo().DoesGroupUndo(); + pDoc->GetIDocumentUndoRedo().DoGroupUndo(false); + if( nEndCnt < nSttCnt ) + { + std::swap(nSttCnt, nEndCnt); + } + + // In order to get Undo/Redlining etc. working correctly, + // we need to go through the Doc interface + rIdx = nSttCnt; + const OUString sText( pTNd->GetText().copy(nSttCnt, nEndCnt - nSttCnt)); + if( m_bIsOverwriteCursor && !m_sOverwriteText.isEmpty() ) + { + const sal_Int32 nLen = sText.getLength(); + const sal_Int32 nOWLen = m_sOverwriteText.getLength(); + if( nLen > nOWLen ) + { + rIdx += nOWLen; + pTNd->EraseText( rIdx, nLen - nOWLen ); + rIdx = nSttCnt; + pTNd->ReplaceText( rIdx, nOWLen, m_sOverwriteText ); + if( m_bInsText ) + { + rIdx = nSttCnt; + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::OVERWRITE, nullptr ); + pDoc->getIDocumentContentOperations().Overwrite( *this, sText.copy( 0, nOWLen ) ); + pDoc->getIDocumentContentOperations().InsertString( *this, sText.copy( nOWLen ) ); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::OVERWRITE, nullptr ); + } + } + else + { + pTNd->ReplaceText( rIdx, nLen, m_sOverwriteText.copy( 0, nLen )); + if( m_bInsText ) + { + rIdx = nSttCnt; + pDoc->getIDocumentContentOperations().Overwrite( *this, sText ); + } + } + } + else + { + pTNd->EraseText( rIdx, nEndCnt - nSttCnt ); + + if( m_bInsText ) + { + pDoc->getIDocumentContentOperations().InsertString( *this, sText ); + } + } + pDoc->GetIDocumentUndoRedo().DoGroupUndo(bKeepGroupUndo); + if (m_eInputLanguage != LANGUAGE_DONTKNOW) + { + sal_uInt16 nWhich = RES_CHRATR_LANGUAGE; + sal_Int16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage(m_eInputLanguage); + switch(nScriptType) + { + case i18n::ScriptType::ASIAN: + nWhich = RES_CHRATR_CJK_LANGUAGE; break; + case i18n::ScriptType::COMPLEX: + nWhich = RES_CHRATR_CTL_LANGUAGE; break; + } + // #i41974# Only set language attribute for CJK/CTL scripts. + if (RES_CHRATR_LANGUAGE != nWhich && pTNd->GetLang( nSttCnt, nEndCnt-nSttCnt, nScriptType) != m_eInputLanguage) + { + SvxLanguageItem aLangItem( m_eInputLanguage, nWhich ); + rIdx = nSttCnt; + GetMark()->nContent = nEndCnt; + pDoc->getIDocumentContentOperations().InsertPoolItem(*this, aLangItem ); + } + + } + } + } +} + +void SwExtTextInput::SetInputData( const CommandExtTextInputData& rData ) +{ + SwTextNode* pTNd = GetPoint()->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + sal_Int32 nSttCnt = GetPoint()->nContent.GetIndex(); + sal_Int32 nEndCnt = GetMark()->nContent.GetIndex(); + if( nEndCnt < nSttCnt ) + { + std::swap(nSttCnt, nEndCnt); + } + + SwIndex aIdx( pTNd, nSttCnt ); + const OUString& rNewStr = rData.GetText(); + + if( m_bIsOverwriteCursor && !m_sOverwriteText.isEmpty() ) + { + sal_Int32 nReplace = nEndCnt - nSttCnt; + const sal_Int32 nNewLen = rNewStr.getLength(); + if( nNewLen < nReplace ) + { + // We have to insert some characters from the saved original text + nReplace -= nNewLen; + aIdx += nNewLen; + pTNd->ReplaceText( aIdx, nReplace, + m_sOverwriteText.copy( nNewLen, nReplace )); + aIdx = nSttCnt; + nReplace = nNewLen; + } + else + { + const sal_Int32 nOWLen = m_sOverwriteText.getLength(); + if( nOWLen < nReplace ) + { + aIdx += nOWLen; + pTNd->EraseText( aIdx, nReplace-nOWLen ); + aIdx = nSttCnt; + nReplace = nOWLen; + } + else + { + nReplace = std::min(nOWLen, nNewLen); + } + } + + pTNd->ReplaceText( aIdx, nReplace, rNewStr ); + if( !HasMark() ) + SetMark(); + GetMark()->nContent = aIdx; + } + else + { + if( nSttCnt < nEndCnt ) + { + pTNd->EraseText( aIdx, nEndCnt - nSttCnt ); + } + + pTNd->InsertText( rNewStr, aIdx, + SwInsertFlags::EMPTYEXPAND ); + if( !HasMark() ) + SetMark(); + } + + GetPoint()->nContent = nSttCnt; + + m_aAttrs.clear(); + if( rData.GetTextAttr() ) + { + const ExtTextInputAttr *pAttrs = rData.GetTextAttr(); + m_aAttrs.insert( m_aAttrs.begin(), pAttrs, pAttrs + rData.GetText().getLength() ); + } + } +} + +void SwExtTextInput::SetOverwriteCursor( bool bFlag ) +{ + m_bIsOverwriteCursor = bFlag; + if (!m_bIsOverwriteCursor) + return; + + const SwTextNode *const pTNd = GetPoint()->nNode.GetNode().GetTextNode(); + if (pTNd) + { + const sal_Int32 nSttCnt = GetPoint()->nContent.GetIndex(); + const sal_Int32 nEndCnt = GetMark()->nContent.GetIndex(); + m_sOverwriteText = pTNd->GetText().copy( std::min(nSttCnt, nEndCnt) ); + if( !m_sOverwriteText.isEmpty() ) + { + const sal_Int32 nInPos = m_sOverwriteText.indexOf( CH_TXTATR_INWORD ); + const sal_Int32 nBrkPos = m_sOverwriteText.indexOf( CH_TXTATR_BREAKWORD ); + + // Find the first attr found, if any. + sal_Int32 nPos = std::min(nInPos, nBrkPos); + if (nPos<0) + { + nPos = std::max(nInPos, nBrkPos); + } + if (nPos>=0) + { + m_sOverwriteText = m_sOverwriteText.copy( 0, nPos ); + } + } + } +} + +// The Doc interfaces + +SwExtTextInput* SwDoc::CreateExtTextInput( const SwPaM& rPam ) +{ + SwExtTextInput* pNew = new SwExtTextInput( rPam, mpExtInputRing ); + if( !mpExtInputRing ) + mpExtInputRing = pNew; + pNew->SetMark(); + return pNew; +} + +void SwDoc::DeleteExtTextInput( SwExtTextInput* pDel ) +{ + if( pDel == mpExtInputRing ) + { + if( pDel->GetNext() != mpExtInputRing ) + mpExtInputRing = pDel->GetNext(); + else + mpExtInputRing = nullptr; + } + delete pDel; +} + +SwExtTextInput* SwDoc::GetExtTextInput( const SwNode& rNd, + sal_Int32 nContentPos ) const +{ + SwExtTextInput* pRet = nullptr; + if( mpExtInputRing ) + { + sal_uLong nNdIdx = rNd.GetIndex(); + SwExtTextInput* pTmp = mpExtInputRing; + do { + sal_uLong nPt = pTmp->GetPoint()->nNode.GetIndex(), + nMk = pTmp->GetMark()->nNode.GetIndex(); + sal_Int32 nPtCnt = pTmp->GetPoint()->nContent.GetIndex(); + sal_Int32 nMkCnt = pTmp->GetMark()->nContent.GetIndex(); + + if( nPt < nMk || ( nPt == nMk && nPtCnt < nMkCnt )) + { + sal_uLong nTmp = nMk; nMk = nPt; nPt = nTmp; + sal_Int32 nTmp2 = nMkCnt; nMkCnt = nPtCnt; nPtCnt = nTmp2; + } + + if( nMk <= nNdIdx && nNdIdx <= nPt && + ( nContentPos<0 || + ( nMkCnt <= nContentPos && nContentPos <= nPtCnt ))) + { + pRet = pTmp; + break; + } + pTmp = pTmp->GetNext(); + } while ( pTmp!=mpExtInputRing ); + } + return pRet; +} + +SwExtTextInput* SwDoc::GetExtTextInput() const +{ + OSL_ENSURE( !mpExtInputRing || !mpExtInputRing->IsMultiSelection(), + "more than one InputEngine available" ); + return mpExtInputRing; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/fmtcol.cxx b/sw/source/core/doc/fmtcol.cxx new file mode 100644 index 000000000..ab39abf31 --- /dev/null +++ b/sw/source/core/doc/fmtcol.cxx @@ -0,0 +1,627 @@ +/* -*- 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 <memory> +#include <libxml/xmlwriter.h> + +#include <sal/macros.h> +#include <osl/diagnose.h> +#include <hintids.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <doc.hxx> +#include <fmtcol.hxx> +#include <fmtcolfunc.hxx> +#include <hints.hxx> +#include <node.hxx> +#include <numrule.hxx> +#include <paratr.hxx> +#include <calbck.hxx> +#include <svl/intitem.hxx> + +namespace TextFormatCollFunc +{ + // #i71574# + void CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( + SwFormat* pFormat, + const SwNumRuleItem* pNewNumRuleItem ) + { + SwTextFormatColl* pTextFormatColl = dynamic_cast<SwTextFormatColl*>(pFormat); + if ( !pTextFormatColl ) + { + OSL_FAIL( "<TextFormatCollFunc::CheckTextFormatCollFuncForDeletionOfAssignmentToOutlineStyle> - misuse of method - it's only for instances of <SwTextFormatColl>" ); + return; + } + + // #i73790# + if ( !pTextFormatColl->StayAssignedToListLevelOfOutlineStyle() && + pTextFormatColl->IsAssignedToListLevelOfOutlineStyle() ) + { + if (!pNewNumRuleItem) + { + (void)pTextFormatColl->GetItemState(RES_PARATR_NUMRULE, false, reinterpret_cast<const SfxPoolItem**>(&pNewNumRuleItem)); + } + if (pNewNumRuleItem) + { + const OUString& sNumRuleName = pNewNumRuleItem->GetValue(); + if ( sNumRuleName.isEmpty() || + sNumRuleName != pTextFormatColl->GetDoc()->GetOutlineNumRule()->GetName() ) + { + // delete assignment of paragraph style to list level of outline style. + pTextFormatColl->DeleteAssignmentToListLevelOfOutlineStyle(); + } + } + } + } + + SwNumRule* GetNumRule( SwTextFormatColl& rTextFormatColl ) + { + SwNumRule* pNumRule( nullptr ); + + const SwNumRuleItem* pNumRuleItem(nullptr); + (void)rTextFormatColl.GetItemState(RES_PARATR_NUMRULE, false, reinterpret_cast<const SfxPoolItem**>(&pNumRuleItem)); + if (pNumRuleItem) + { + const OUString& sNumRuleName = pNumRuleItem->GetValue(); + if ( !sNumRuleName.isEmpty() ) + { + pNumRule = rTextFormatColl.GetDoc()->FindNumRulePtr( sNumRuleName ); + } + } + + return pNumRule; + } + + void AddToNumRule( SwTextFormatColl& rTextFormatColl ) + { + SwNumRule* pNumRule = GetNumRule( rTextFormatColl ); + if ( pNumRule ) + { + pNumRule->AddParagraphStyle( rTextFormatColl ); + } + } + + void RemoveFromNumRule( SwTextFormatColl& rTextFormatColl ) + { + SwNumRule* pNumRule = GetNumRule( rTextFormatColl ); + if ( pNumRule ) + { + pNumRule->RemoveParagraphStyle( rTextFormatColl ); + } + } +} // end of namespace TextFormatCollFunc + +void SwTextFormatColl::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( GetDoc()->IsInDtor() ) + { + SwFormatColl::Modify( pOld, pNew ); + return; + } + + bool bNewParent( false ); // #i66431# - adjust type of <bNewParent> + const SvxULSpaceItem *pNewULSpace = nullptr, *pOldULSpace = nullptr; + const SvxLRSpaceItem *pNewLRSpace = nullptr, *pOldLRSpace = nullptr; + const SvxFontHeightItem* aFontSizeArr[3] = {nullptr,nullptr,nullptr}; + // #i70223# + const bool bAssignedToListLevelOfOutlineStyle(IsAssignedToListLevelOfOutlineStyle()); + const SwNumRuleItem* pNewNumRuleItem( nullptr ); + + const SwAttrSetChg *pNewChgSet = nullptr, *pOldChgSet = nullptr; + + switch( pOld ? pOld->Which() : pNew ? pNew->Which() : 0 ) + { + case RES_ATTRSET_CHG: + // Only recalculate if we're not the sender! + pNewChgSet = static_cast<const SwAttrSetChg*>(pNew); + pOldChgSet = static_cast<const SwAttrSetChg*>(pOld); + pNewChgSet->GetChgSet()->GetItemState( + RES_LR_SPACE, false, reinterpret_cast<const SfxPoolItem**>(&pNewLRSpace) ); + pNewChgSet->GetChgSet()->GetItemState( + RES_UL_SPACE, false, reinterpret_cast<const SfxPoolItem**>(&pNewULSpace) ); + pNewChgSet->GetChgSet()->GetItemState( RES_CHRATR_FONTSIZE, + false, reinterpret_cast<const SfxPoolItem**>(&(aFontSizeArr[0])) ); + pNewChgSet->GetChgSet()->GetItemState( RES_CHRATR_CJK_FONTSIZE, + false, reinterpret_cast<const SfxPoolItem**>(&(aFontSizeArr[1])) ); + pNewChgSet->GetChgSet()->GetItemState( RES_CHRATR_CTL_FONTSIZE, + false, reinterpret_cast<const SfxPoolItem**>(&(aFontSizeArr[2])) ); + // #i70223#, #i84745# + // check, if attribute set is applied to this paragraph style + if ( bAssignedToListLevelOfOutlineStyle && + pNewChgSet->GetTheChgdSet() == &GetAttrSet() ) + { + pNewChgSet->GetChgSet()->GetItemState( RES_PARATR_NUMRULE, false, + reinterpret_cast<const SfxPoolItem**>(&pNewNumRuleItem) ); + } + + break; + + case RES_FMT_CHG: + if( GetAttrSet().GetParent() ) + { + const SfxItemSet* pParent = GetAttrSet().GetParent(); + pNewLRSpace = &pParent->Get( RES_LR_SPACE ); + pNewULSpace = &pParent->Get( RES_UL_SPACE ); + aFontSizeArr[0] = &pParent->Get( RES_CHRATR_FONTSIZE ); + aFontSizeArr[1] = &pParent->Get( RES_CHRATR_CJK_FONTSIZE ); + aFontSizeArr[2] = &pParent->Get( RES_CHRATR_CTL_FONTSIZE ); + // #i66431# - modify has to be propagated, because of new parent format. + bNewParent = true; + } + break; + + case RES_LR_SPACE: + pNewLRSpace = static_cast<const SvxLRSpaceItem*>(pNew); + break; + case RES_UL_SPACE: + pNewULSpace = static_cast<const SvxULSpaceItem*>(pNew); + break; + case RES_CHRATR_FONTSIZE: + aFontSizeArr[0] = static_cast<const SvxFontHeightItem*>(pNew); + break; + case RES_CHRATR_CJK_FONTSIZE: + aFontSizeArr[1] = static_cast<const SvxFontHeightItem*>(pNew); + break; + case RES_CHRATR_CTL_FONTSIZE: + aFontSizeArr[2] = static_cast<const SvxFontHeightItem*>(pNew); + break; + // #i70223# + case RES_PARATR_NUMRULE: + if (bAssignedToListLevelOfOutlineStyle) + { + pNewNumRuleItem = static_cast<const SwNumRuleItem*>(pNew); + } + break; + default: + break; + } + + // #i70223# + if ( bAssignedToListLevelOfOutlineStyle && pNewNumRuleItem ) + { + TextFormatCollFunc::CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( + this, pNewNumRuleItem ); + } + + bool bContinue = true; + + // Check against the own attributes + if( pNewLRSpace && SfxItemState::SET == GetItemState( RES_LR_SPACE, false, + reinterpret_cast<const SfxPoolItem**>(&pOldLRSpace) )) + { + if( pOldLRSpace != pNewLRSpace ) // Avoid recursion (SetAttr!) + { + bool bChg = false; + SvxLRSpaceItem aNew( *pOldLRSpace ); + // We had a relative value -> recalculate + if( 100 != aNew.GetPropLeft() ) + { + long nTmp = aNew.GetLeft(); // keep so that we can compare + aNew.SetLeft( pNewLRSpace->GetLeft(), aNew.GetPropLeft() ); + bChg |= nTmp != aNew.GetLeft(); + } + // We had a relative value -> recalculate + if( 100 != aNew.GetPropRight() ) + { + long nTmp = aNew.GetRight(); // keep so that we can compare + aNew.SetRight( pNewLRSpace->GetRight(), aNew.GetPropRight() ); + bChg |= nTmp != aNew.GetRight(); + } + // We had a relative value -> recalculate + if( 100 != aNew.GetPropTextFirstLineOffset() ) + { + short nTmp = aNew.GetTextFirstLineOffset(); // keep so that we can compare + aNew.SetTextFirstLineOffset( pNewLRSpace->GetTextFirstLineOffset(), + aNew.GetPropTextFirstLineOffset() ); + bChg |= nTmp != aNew.GetTextFirstLineOffset(); + } + if( bChg ) + { + SetFormatAttr( aNew ); + bContinue = nullptr != pOldChgSet || bNewParent; + } + // We set it to absolute -> do not propagate it further, unless + // we set it! + else if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + } + + if( pNewULSpace && SfxItemState::SET == GetItemState( + RES_UL_SPACE, false, reinterpret_cast<const SfxPoolItem**>(&pOldULSpace) ) && + pOldULSpace != pNewULSpace ) // Avoid recursion (SetAttr!) + { + SvxULSpaceItem aNew( *pOldULSpace ); + bool bChg = false; + // We had a relative value -> recalculate + if( 100 != aNew.GetPropUpper() ) + { + sal_uInt16 nTmp = aNew.GetUpper(); // keep so that we can compare + aNew.SetUpper( pNewULSpace->GetUpper(), aNew.GetPropUpper() ); + bChg |= nTmp != aNew.GetUpper(); + } + // We had a relative value -> recalculate + if( 100 != aNew.GetPropLower() ) + { + sal_uInt16 nTmp = aNew.GetLower(); // keep so that we can compare + aNew.SetLower( pNewULSpace->GetLower(), aNew.GetPropLower() ); + bChg |= nTmp != aNew.GetLower(); + } + if( bChg ) + { + SetFormatAttr( aNew ); + bContinue = nullptr != pOldChgSet || bNewParent; + } + // We set it to absolute -> do not propagate it further, unless + // we set it! + else if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + + for( int nC = 0; nC < int(SAL_N_ELEMENTS(aFontSizeArr)); ++nC ) + { + const SvxFontHeightItem *pFSize = aFontSizeArr[ nC ], *pOldFSize; + if( pFSize && SfxItemState::SET == GetItemState( + pFSize->Which(), false, reinterpret_cast<const SfxPoolItem**>(&pOldFSize) ) && + // Avoid recursion (SetAttr!) + pFSize != pOldFSize ) + { + if( 100 == pOldFSize->GetProp() && + MapUnit::MapRelative == pOldFSize->GetPropUnit() ) + { + // We set it to absolute -> do not propagate it further, unless + // we set it! + if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + else + { + // We had a relative value -> recalculate + sal_uInt32 nTmp = pOldFSize->GetHeight(); // keep so that we can compare + SvxFontHeightItem aNew(240 , 100, pFSize->Which()); + aNew.SetHeight( pFSize->GetHeight(), pOldFSize->GetProp(), + pOldFSize->GetPropUnit() ); + if( nTmp != aNew.GetHeight() ) + { + SetFormatAttr( aNew ); + bContinue = nullptr != pOldChgSet || bNewParent; + } + // We set it to absolute -> do not propagate it further, unless + // we set it! + else if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + } + } + + if( bContinue ) + SwFormatColl::Modify( pOld, pNew ); +} + +bool SwTextFormatColl::IsAtDocNodeSet() const +{ + SwIterator<SwContentNode,SwFormatColl> aIter( *this ); + const SwNodes& rNds = GetDoc()->GetNodes(); + for( SwContentNode* pNode = aIter.First(); pNode; pNode = aIter.Next() ) + if( &(pNode->GetNodes()) == &rNds ) + return true; + + return false; +} + +bool SwTextFormatColl::SetFormatAttr( const SfxPoolItem& rAttr ) +{ + const bool bIsNumRuleItem = rAttr.Which() == RES_PARATR_NUMRULE; + if ( bIsNumRuleItem ) + { + TextFormatCollFunc::RemoveFromNumRule( *this ); + } + + const bool bRet = SwFormatColl::SetFormatAttr( rAttr ); + + if ( bIsNumRuleItem ) + { + TextFormatCollFunc::AddToNumRule( *this ); + } + + return bRet; +} + +bool SwTextFormatColl::SetFormatAttr( const SfxItemSet& rSet ) +{ + const bool bIsNumRuleItemAffected = + rSet.GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET; + if ( bIsNumRuleItemAffected ) + { + TextFormatCollFunc::RemoveFromNumRule( *this ); + } + + const bool bRet = SwFormatColl::SetFormatAttr( rSet ); + + if ( bIsNumRuleItemAffected ) + { + TextFormatCollFunc::AddToNumRule( *this ); + } + + return bRet; +} + +bool SwTextFormatColl::ResetFormatAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 ) +{ + const bool bIsNumRuleItemAffected = + ( nWhich2 != 0 && nWhich2 > nWhich1 ) + ? ( nWhich1 <= RES_PARATR_NUMRULE && + RES_PARATR_NUMRULE <= nWhich2 ) + : nWhich1 == RES_PARATR_NUMRULE; + if ( bIsNumRuleItemAffected ) + { + TextFormatCollFunc::RemoveFromNumRule( *this ); + } + + const bool bRet = SwFormatColl::ResetFormatAttr( nWhich1, nWhich2 ); + + return bRet; +} + +// #i73790# +sal_uInt16 SwTextFormatColl::ResetAllFormatAttr() +{ + const bool bOldState( mbStayAssignedToListLevelOfOutlineStyle ); + mbStayAssignedToListLevelOfOutlineStyle = true; + // #i70748# + // Outline level is no longer a member, it is an attribute now. + // Thus, it needs to be restored, if the paragraph style is assigned + // to the outline style + const int nAssignedOutlineStyleLevel = IsAssignedToListLevelOfOutlineStyle() + ? GetAssignedOutlineStyleLevel() + : -1; + + sal_uInt16 nRet = SwFormatColl::ResetAllFormatAttr(); + + // #i70748# + if ( nAssignedOutlineStyleLevel != -1 ) + { + AssignToListLevelOfOutlineStyle( nAssignedOutlineStyleLevel ); + } + + mbStayAssignedToListLevelOfOutlineStyle = bOldState; + + return nRet; +} + +bool SwTextFormatColl::AreListLevelIndentsApplicable() const +{ + bool bAreListLevelIndentsApplicable( true ); + + if ( GetItemState( RES_PARATR_NUMRULE ) != SfxItemState::SET ) + { + // no list style applied to paragraph style + bAreListLevelIndentsApplicable = false; + } + else if ( GetItemState( RES_LR_SPACE, false ) == SfxItemState::SET ) + { + // paragraph style has hard-set indent attributes + bAreListLevelIndentsApplicable = false; + } + else if ( GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + { + // list style is directly applied to paragraph style and paragraph + // style has no hard-set indent attributes + bAreListLevelIndentsApplicable = true; + } + else + { + // list style is applied through one of the parent paragraph styles and + // paragraph style has no hard-set indent attributes + + // check parent paragraph styles + const SwTextFormatColl* pColl = dynamic_cast<const SwTextFormatColl*>(DerivedFrom()); + while ( pColl ) + { + if ( pColl->GetAttrSet().GetItemState( RES_LR_SPACE, false ) == SfxItemState::SET ) + { + // indent attributes found in the paragraph style hierarchy. + bAreListLevelIndentsApplicable = false; + break; + } + + if ( pColl->GetAttrSet().GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + { + // paragraph style with the list style found and until now no + // indent attributes are found in the paragraph style hierarchy. + bAreListLevelIndentsApplicable = true; + break; + } + + pColl = dynamic_cast<const SwTextFormatColl*>(pColl->DerivedFrom()); + OSL_ENSURE( pColl, + "<SwTextFormatColl::AreListLevelIndentsApplicable()> - something wrong in paragraph style hierarchy. The applied list style is not found." ); + } + } + + return bAreListLevelIndentsApplicable; +} + +void SwTextFormatColl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + GetAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +void SwTextFormatColls::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColls")); + for (size_t i = 0; i < size(); ++i) + GetFormat(i)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +//FEATURE::CONDCOLL + +SwCollCondition::SwCollCondition( SwTextFormatColl* pColl, Master_CollCondition nMasterCond, + sal_uLong nSubCond ) + : SwClient( pColl ), m_nCondition( nMasterCond ), + m_nSubCondition( nSubCond ) +{ +} + +SwCollCondition::SwCollCondition( const SwCollCondition& rCopy ) + : SwClient( const_cast<SwModify*>(rCopy.GetRegisteredIn()) ), + m_nCondition( rCopy.m_nCondition ), + m_nSubCondition( rCopy.m_nSubCondition ) +{ +} + +SwCollCondition::~SwCollCondition() +{ +} + +void SwCollCondition::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +bool SwCollCondition::operator==( const SwCollCondition& rCmp ) const +{ + return ( m_nCondition == rCmp.m_nCondition ) + && ( m_nSubCondition == rCmp.m_nSubCondition ); +} + +void SwCollCondition::SetCondition( Master_CollCondition nCond, sal_uLong nSubCond ) +{ + m_nCondition = nCond; + m_nSubCondition = nSubCond; +} + +SwConditionTextFormatColl::~SwConditionTextFormatColl() +{ +} + +const SwCollCondition* SwConditionTextFormatColl::HasCondition( + const SwCollCondition& rCond ) const +{ + for (const auto &rpFnd : m_CondColls) + { + if (*rpFnd == rCond) + return rpFnd.get(); + } + + return nullptr; +} + +void SwConditionTextFormatColl::InsertCondition( const SwCollCondition& rCond ) +{ + for (SwFormatCollConditions::size_type n = 0; n < m_CondColls.size(); ++n) + { + if (*m_CondColls[ n ] == rCond) + { + m_CondColls.erase( m_CondColls.begin() + n ); + break; + } + } + + // Not found -> so insert it + m_CondColls.push_back( std::make_unique<SwCollCondition> (rCond) ); +} + +void SwConditionTextFormatColl::RemoveCondition( const SwCollCondition& rCond ) +{ + for (SwFormatCollConditions::size_type n = 0; n < m_CondColls.size(); ++n) + { + if (*m_CondColls[ n ] == rCond) + { + m_CondColls.erase( m_CondColls.begin() + n ); + } + } +} + +void SwConditionTextFormatColl::SetConditions( const SwFormatCollConditions& rCndClls ) +{ + // Copy the Conditions, but first delete the old ones + m_CondColls.clear(); + SwDoc& rDoc = *GetDoc(); + for (const auto &rpFnd : rCndClls) + { + SwTextFormatColl *const pTmpColl = rpFnd->GetTextFormatColl() + ? rDoc.CopyTextColl( *rpFnd->GetTextFormatColl() ) + : nullptr; + std::unique_ptr<SwCollCondition> pNew; + pNew.reset(new SwCollCondition( pTmpColl, rpFnd->GetCondition(), + rpFnd->GetSubCondition() )); + m_CondColls.push_back( std::move(pNew) ); + } +} + +// FEATURE::CONDCOLL +void SwTextFormatColl::SetAttrOutlineLevel( int nLevel) +{ + OSL_ENSURE( 0 <= nLevel && nLevel <= MAXLEVEL ,"SwTextFormatColl: Level Out Of Range" ); + SetFormatAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL, + static_cast<sal_uInt16>(nLevel) ) ); +} + +int SwTextFormatColl::GetAttrOutlineLevel() const +{ + return GetFormatAttr(RES_PARATR_OUTLINELEVEL).GetValue(); +} + +int SwTextFormatColl::GetAssignedOutlineStyleLevel() const +{ + OSL_ENSURE( IsAssignedToListLevelOfOutlineStyle(), + "<SwTextFormatColl::GetAssignedOutlineStyleLevel()> - misuse of method"); + return GetAttrOutlineLevel() - 1; +} + +void SwTextFormatColl::AssignToListLevelOfOutlineStyle(const int nAssignedListLevel) +{ + mbAssignedToOutlineStyle = true; + SetAttrOutlineLevel(nAssignedListLevel+1); + + // #i100277# + SwIterator<SwTextFormatColl,SwFormatColl> aIter( *this ); + SwTextFormatColl* pDerivedTextFormatColl = aIter.First(); + while ( pDerivedTextFormatColl != nullptr ) + { + if ( !pDerivedTextFormatColl->IsAssignedToListLevelOfOutlineStyle() ) + { + if ( pDerivedTextFormatColl->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::DEFAULT ) + { + SwNumRuleItem aItem; + pDerivedTextFormatColl->SetFormatAttr( aItem ); + } + if ( pDerivedTextFormatColl->GetItemState( RES_PARATR_OUTLINELEVEL, false ) == SfxItemState::DEFAULT ) + { + pDerivedTextFormatColl->SetAttrOutlineLevel( 0 ); + } + } + + pDerivedTextFormatColl = aIter.Next(); + } +} + +void SwTextFormatColl::DeleteAssignmentToListLevelOfOutlineStyle() +{ + mbAssignedToOutlineStyle = false; + ResetFormatAttr(RES_PARATR_OUTLINELEVEL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/ftnidx.cxx b/sw/source/core/doc/ftnidx.cxx new file mode 100644 index 000000000..07250b97e --- /dev/null +++ b/sw/source/core/doc/ftnidx.cxx @@ -0,0 +1,520 @@ +/* -*- 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 <txtftn.hxx> +#include <fmtftn.hxx> +#include <ftninfo.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <redline.hxx> +#include <ftnidx.hxx> +#include <ndtxt.hxx> +#include <ndindex.hxx> +#include <section.hxx> +#include <fmtftntx.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> + +namespace sw { + +bool IsFootnoteDeleted(IDocumentRedlineAccess const& rIDRA, + SwTextFootnote const& rTextFootnote) +{ + SwRedlineTable::size_type tmp; + SwPosition const pos(const_cast<SwTextNode&>(rTextFootnote.GetTextNode()), + rTextFootnote.GetStart()); + SwRangeRedline const*const pRedline(rIDRA.GetRedline(pos, &tmp)); + return (pRedline + && pRedline->GetType() == RedlineType::Delete + && *pRedline->GetPoint() != *pRedline->GetMark()); +} + +} + +using sw::IsFootnoteDeleted; + +bool CompareSwFootnoteIdxs::operator()(SwTextFootnote* const& lhs, SwTextFootnote* const& rhs) const +{ + sal_uLong nIdxLHS = SwTextFootnote_GetIndex( lhs ); + sal_uLong nIdxRHS = SwTextFootnote_GetIndex( rhs ); + return ( nIdxLHS == nIdxRHS && lhs->GetStart() < rhs->GetStart() ) || nIdxLHS < nIdxRHS; +} + +void SwFootnoteIdxs::UpdateFootnote( const SwNodeIndex& rStt ) +{ + if( empty() ) + return; + + // Get the NodesArray using the first foot note's StartIndex + SwDoc* pDoc = rStt.GetNode().GetDoc(); + if( pDoc->IsInReading() ) + return ; + SwTextFootnote* pTextFootnote; + + const SwEndNoteInfo& rEndInfo = pDoc->GetEndNoteInfo(); + const SwFootnoteInfo& rFootnoteInfo = pDoc->GetFootnoteInfo(); + IDocumentRedlineAccess const& rIDRA(pDoc->getIDocumentRedlineAccess()); + + // For normal foot notes we treat per-chapter and per-document numbering + // separately. For Endnotes we only have per-document numbering. + if( FTNNUM_CHAPTER == rFootnoteInfo.m_eNum ) + { + SwRootFrame const* pLayout(nullptr); + o3tl::sorted_vector<SwRootFrame*> layouts = pDoc->GetAllLayouts(); + // sw_redlinehide: here we need to know if there's *any* layout with + // IsHideRedlines(), because then the hidden-numbers have to be updated + for (SwRootFrame const* pTmp : layouts) + { + if (pTmp->IsHideRedlines()) + { + pLayout = pTmp; + } + } + + const SwOutlineNodes& rOutlNds = pDoc->GetNodes().GetOutLineNds(); + const SwNode *pChapterStartHidden(&pDoc->GetNodes().GetEndOfExtras()); + sal_uLong nChapterStart(pChapterStartHidden->GetIndex()); + sal_uLong nChapterEnd(pDoc->GetNodes().GetEndOfContent().GetIndex()); + sal_uLong nChapterEndHidden(nChapterEnd); + if( !rOutlNds.empty() ) + { + // Find the Chapter's start, which contains rStt + size_t n = 0; + + for( ; n < rOutlNds.size(); ++n ) + if( rOutlNds[ n ]->GetIndex() > rStt.GetIndex() ) + break; // found it! + else if ( rOutlNds[ n ]->GetTextNode()->GetAttrOutlineLevel() == 1 ) + { + nChapterStart = rOutlNds[ n ]->GetIndex(); + if (!pLayout || sw::IsParaPropsNode(*pLayout, *rOutlNds[n]->GetTextNode())) + { + pChapterStartHidden = rOutlNds[ n ]; + } + } + // now find the end of the range + for( ; n < rOutlNds.size(); ++n ) + if ( rOutlNds[ n ]->GetTextNode()->GetAttrOutlineLevel() == 1 ) + { + nChapterEnd = rOutlNds[ n ]->GetIndex(); + break; + } + + // continue to find end of hidden-chapter + for ( ; n < rOutlNds.size(); ++n) + { + if (rOutlNds[n]->GetTextNode()->GetAttrOutlineLevel() == 1 + && (!pLayout || sw::IsParaPropsNode(*pLayout, *rOutlNds[n]->GetTextNode()))) + { + nChapterEndHidden = rOutlNds[n]->GetIndex(); + break; + } + } + } + + size_t nPos = 0; + size_t nFootnoteNo = 1; + size_t nFootnoteNoHidden = 1; + if (SeekEntry( *pChapterStartHidden, &nPos ) && nPos) + { + // Step forward until the Index is not the same anymore + const SwNode* pCmpNd = &rStt.GetNode(); + while( nPos && pCmpNd == &((*this)[ --nPos ]->GetTextNode()) ) + ; + ++nPos; + } + + if( nPos == size() ) // nothing found + return; + + if( rOutlNds.empty() ) + { + nFootnoteNo = nPos+1; + if (nPos) + { + nFootnoteNoHidden = (*this)[nPos - 1]->GetFootnote().GetNumberRLHidden() + 1; + } + } + + for( ; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + sal_uLong const nNode(pTextFootnote->GetTextNode().GetIndex()); + if (nChapterEndHidden <= nNode) + break; + + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() && !rFootnote.IsEndNote() && + !SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *pTextFootnote )) + { + pTextFootnote->SetNumber( + (nChapterStart <= nNode && nNode < nChapterEnd) + ? rFootnoteInfo.m_nFootnoteOffset + nFootnoteNo + : rFootnote.GetNumber(), + rFootnoteInfo.m_nFootnoteOffset + nFootnoteNoHidden, + rFootnote.GetNumStr() ); + if (nChapterStart <= nNode && nNode < nChapterEnd) + { + ++nFootnoteNo; + } + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + } + + SwUpdFootnoteEndNtAtEnd aNumArr; + + // unless we have per-document numbering, only look at endnotes here + const bool bEndNoteOnly = FTNNUM_DOC != rFootnoteInfo.m_eNum; + + size_t nPos; + size_t nFootnoteNo = 1; + size_t nEndNo = 1; + size_t nFootnoteNoHidden = 1; + size_t nEndNoHidden = 1; + sal_uLong nUpdNdIdx = rStt.GetIndex(); + for( nPos = 0; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + if( nUpdNdIdx <= pTextFootnote->GetTextNode().GetIndex() ) + break; + + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() ) + { + if (!aNumArr.ChkNumber(rIDRA, *pTextFootnote).first) + { + if( pTextFootnote->GetFootnote().IsEndNote() ) + { + nEndNo++; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nEndNoHidden; + } + } + else + { + nFootnoteNo++; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + } + } + + // Set the array number for all footnotes starting from nPos + for( ; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() ) + { + std::pair<sal_uInt16, sal_uInt16> nSectNo = aNumArr.ChkNumber(rIDRA, *pTextFootnote); + if (!nSectNo.first && (rFootnote.IsEndNote() || !bEndNoteOnly)) + { + if (rFootnote.IsEndNote()) + { + nSectNo.first = rEndInfo.m_nFootnoteOffset + nEndNo; + ++nEndNo; + nSectNo.second = rEndInfo.m_nFootnoteOffset + nEndNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nEndNoHidden; + } + } + else + { + nSectNo.first = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNo; + ++nFootnoteNo; + nSectNo.second = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + + if (nSectNo.first) + { + pTextFootnote->SetNumber(nSectNo.first, nSectNo.second, rFootnote.GetNumStr()); + } + } + } +} + +void SwFootnoteIdxs::UpdateAllFootnote() +{ + if( empty() ) + return; + + // Get the NodesArray via the StartIndex of the first Footnote + SwDoc* pDoc = const_cast<SwDoc*>((*this)[ 0 ]->GetTextNode().GetDoc()); + SwTextFootnote* pTextFootnote; + const SwEndNoteInfo& rEndInfo = pDoc->GetEndNoteInfo(); + const SwFootnoteInfo& rFootnoteInfo = pDoc->GetFootnoteInfo(); + IDocumentRedlineAccess const& rIDRA(pDoc->getIDocumentRedlineAccess()); + + SwUpdFootnoteEndNtAtEnd aNumArr; + + SwRootFrame const* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = pDoc->GetAllLayouts(); + // For normal Footnotes per-chapter and per-document numbering are treated separately. + // For Endnotes we only have document-wise numbering. + if( FTNNUM_CHAPTER == rFootnoteInfo.m_eNum ) + { + // sw_redlinehide: here we need to know if there's *any* layout with + // IsHideRedlines(), because then the hidden-numbers have to be updated + for (SwRootFrame const* pTmp : aAllLayouts) + { + if (pTmp->IsHideRedlines()) + { + pLayout = pTmp; + } + } + + const SwOutlineNodes& rOutlNds = pDoc->GetNodes().GetOutLineNds(); + sal_uInt16 nNo = 1; // Number for the Footnotes + sal_uInt16 nNoNo = 1; + size_t nFootnoteIdx = 0; // Index into theFootnoteIdx array + for( size_t n = 0; n < rOutlNds.size(); ++n ) + { + if ( rOutlNds[ n ]->GetTextNode()->GetAttrOutlineLevel() == 1 ) + { + sal_uLong nCapStt = rOutlNds[ n ]->GetIndex(); // Start of a new chapter + for( ; nFootnoteIdx < size(); ++nFootnoteIdx ) + { + pTextFootnote = (*this)[ nFootnoteIdx ]; + if( pTextFootnote->GetTextNode().GetIndex() >= nCapStt ) + break; + + // Endnotes are per-document only + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( !rFootnote.IsEndNote() && rFootnote.GetNumStr().isEmpty() && + !SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *pTextFootnote )) + { + pTextFootnote->SetNumber( + rFootnoteInfo.m_nFootnoteOffset + nNo, + rFootnoteInfo.m_nFootnoteOffset + nNoNo, + rFootnote.GetNumStr() ); + ++nNo; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nNoNo; + } + } + } + if( nFootnoteIdx >= size() ) + break; // ok, everything is updated + nNo = 1; + // sw_redlinehide: this means the numbers are layout dependent in chapter case + if (!pLayout || sw::IsParaPropsNode(*pLayout, *rOutlNds[ n ]->GetTextNode())) + { + nNoNo = 1; + } + } + } + + for (nNo = 1, nNoNo = 1; nFootnoteIdx < size(); ++nFootnoteIdx) + { + // Endnotes are per-document + pTextFootnote = (*this)[ nFootnoteIdx ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( !rFootnote.IsEndNote() && rFootnote.GetNumStr().isEmpty() && + !SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *pTextFootnote )) + { + pTextFootnote->SetNumber( + rFootnoteInfo.m_nFootnoteOffset + nNo, + rFootnoteInfo.m_nFootnoteOffset + nNoNo, + rFootnote.GetNumStr() ); + ++nNo; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nNoNo; + } + } + } + } + + // We use bool here, so that we also iterate through the Endnotes with a chapter setting. + const bool bEndNoteOnly = FTNNUM_DOC != rFootnoteInfo.m_eNum; + sal_uInt16 nFootnoteNo = 1; + sal_uInt16 nEndnoteNo = 1; + sal_uInt16 nFootnoteNoHidden = 1; + sal_uInt16 nEndnoteNoHidden = 1; + for( size_t nPos = 0; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() ) + { + std::pair<sal_uInt16, sal_uInt16> nSectNo = aNumArr.ChkNumber(rIDRA, *pTextFootnote); + if (!nSectNo.first && (rFootnote.IsEndNote() || !bEndNoteOnly)) + { + if (rFootnote.IsEndNote()) + { + nSectNo.first = rEndInfo.m_nFootnoteOffset + nEndnoteNo; + ++nEndnoteNo; + nSectNo.second = rEndInfo.m_nFootnoteOffset + nEndnoteNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nEndnoteNoHidden; + } + } + else + { + nSectNo.first = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNo; + ++nFootnoteNo; + nSectNo.second = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + + if (nSectNo.first) + { + pTextFootnote->SetNumber(nSectNo.first, nSectNo.second, rFootnote.GetNumStr()); + } + } + } + + if (pLayout && FTNNUM_PAGE == rFootnoteInfo.m_eNum) + for( auto aLayout : aAllLayouts ) + aLayout->UpdateFootnoteNums(); +} + +SwTextFootnote* SwFootnoteIdxs::SeekEntry( const SwNodeIndex& rPos, size_t* pFndPos ) const +{ + sal_uLong nIdx = rPos.GetIndex(); + + size_t nO = size(); + size_t nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + const size_t nM = nU + ( nO - nU ) / 2; + sal_uLong nNdIdx = SwTextFootnote_GetIndex( (*this)[ nM ] ); + if( nNdIdx == nIdx ) + { + if( pFndPos ) + *pFndPos = nM; + return (*this)[ nM ]; + } + else if( nNdIdx < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + { + if( pFndPos ) + *pFndPos = nU; + return nullptr; + } + else + nO = nM - 1; + } + } + if( pFndPos ) + *pFndPos = nU; + return nullptr; +} + +const SwSectionNode* SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( + const SwTextFootnote& rTextFootnote ) +{ + sal_uInt16 nWh = rTextFootnote.GetFootnote().IsEndNote() ? + sal_uInt16(RES_END_AT_TXTEND) : sal_uInt16(RES_FTN_AT_TXTEND); + const SwSectionNode* pNd = rTextFootnote.GetTextNode().FindSectionNode(); + while( pNd ) + { + sal_uInt16 nVal = static_cast<const SwFormatFootnoteEndAtTextEnd&>(pNd->GetSection().GetFormat()-> + GetFormatAttr( nWh )).GetValue(); + if( FTNEND_ATTXTEND_OWNNUMSEQ == nVal || FTNEND_ATTXTEND_OWNNUMANDFMT == nVal ) + break; + pNd = pNd->StartOfSectionNode()->FindSectionNode(); + } + + return pNd; +} + +std::pair<sal_uInt16, sal_uInt16> SwUpdFootnoteEndNtAtEnd::GetNumber( + IDocumentRedlineAccess const& rIDRA, + const SwTextFootnote& rTextFootnote, + const SwSectionNode& rNd ) +{ + std::pair<sal_uInt16, sal_uInt16> nRet(0, 0); + sal_uInt16 nWh; + std::vector<const SwSectionNode*>* pArr; + std::vector<std::pair<sal_uInt16, sal_uInt16>> *pNum; + if( rTextFootnote.GetFootnote().IsEndNote() ) + { + pArr = &m_aEndSections; + pNum = &m_aEndNumbers; + nWh = RES_END_AT_TXTEND; + } + else + { + pArr = &m_aFootnoteSections; + pNum = &m_aFootnoteNumbers; + nWh = RES_FTN_AT_TXTEND; + } + + for( size_t n = pArr->size(); n; ) + if( (*pArr)[ --n ] == &rNd ) + { + nRet.first = ++((*pNum)[ n ].first); + if (!IsFootnoteDeleted(rIDRA, rTextFootnote)) + { + ++((*pNum)[ n ].second); + } + nRet.second = ((*pNum)[ n ].second); + break; + } + + if (!nRet.first) + { + pArr->push_back( &rNd ); + sal_uInt16 const tmp = static_cast<const SwFormatFootnoteEndAtTextEnd&>( + rNd.GetSection().GetFormat()-> + GetFormatAttr( nWh )).GetOffset(); + nRet.first = tmp + 1; + nRet.second = tmp + 1; + pNum->push_back( nRet ); + } + return nRet; +} + +std::pair<sal_uInt16, sal_uInt16> SwUpdFootnoteEndNtAtEnd::ChkNumber( + IDocumentRedlineAccess const& rIDRA, + const SwTextFootnote& rTextFootnote) +{ + const SwSectionNode* pSectNd = FindSectNdWithEndAttr( rTextFootnote ); + return pSectNd + ? GetNumber(rIDRA, rTextFootnote, *pSectNd) + : std::pair<sal_uInt16, sal_uInt16>(0, 0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/gctable.cxx b/sw/source/core/doc/gctable.cxx new file mode 100644 index 000000000..8a7105c43 --- /dev/null +++ b/sw/source/core/doc/gctable.cxx @@ -0,0 +1,450 @@ +/* -*- 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 <hintids.hxx> +#include <tblrwcl.hxx> +#include <algorithm> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <osl/diagnose.h> + +using namespace ::editeng; + +static const SvxBorderLine* GetLineTB( const SvxBoxItem* pBox, bool bTop ) +{ + return bTop ? pBox->GetTop() : pBox->GetBottom(); +} + +bool SwGCBorder_BoxBrd::CheckLeftBorderOfFormat( const SwFrameFormat& rFormat ) +{ + const SfxPoolItem* pItem; + if( SfxItemState::SET == rFormat.GetItemState( RES_BOX, true, &pItem ) ) + { + const SvxBorderLine* pBrd = static_cast<const SvxBoxItem*>(pItem)->GetLeft(); + if( pBrd ) + { + if( *pBrdLn == *pBrd ) + bAnyBorderFnd = true; + return true; + } + } + return false; +} + +static bool lcl_GCBorder_ChkBoxBrd_B( const SwTableBox* pBox, SwGCBorder_BoxBrd* pPara ); + +static bool lcl_GCBorder_ChkBoxBrd_L( const SwTableLine* pLine, SwGCBorder_BoxBrd* pPara ) +{ + const SwTableBox* pBox = pLine->GetTabBoxes().front(); + return lcl_GCBorder_ChkBoxBrd_B( pBox, pPara ); +} + +static bool lcl_GCBorder_ChkBoxBrd_B( const SwTableBox* pBox, SwGCBorder_BoxBrd* pPara ) +{ + if( !pBox->GetTabLines().empty() ) + { + for( auto pLine : pBox->GetTabLines() ) + { + if (!lcl_GCBorder_ChkBoxBrd_L( pLine, pPara )) + { + return false; + } + } + return true; + } + + return pPara->CheckLeftBorderOfFormat( *pBox->GetFrameFormat() ); +} + +static void lcl_GCBorder_GetLastBox_B( const SwTableBox* pBox, SwTableBoxes* pPara ); + +static void lcl_GCBorder_GetLastBox_L( const SwTableLine* pLine, SwTableBoxes* pPara ) +{ + const SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + SwTableBox* pBox = rBoxes.back(); + lcl_GCBorder_GetLastBox_B( pBox, pPara ); +} + +static void lcl_GCBorder_GetLastBox_B( const SwTableBox* pBox, SwTableBoxes* pPara ) +{ + const SwTableLines& rLines = pBox->GetTabLines(); + if( !rLines.empty() ) + { + for( const SwTableLine* pLine : rLines ) + lcl_GCBorder_GetLastBox_L( pLine, pPara ); + } + else + pPara->push_back( const_cast<SwTableBox*>(pBox) ); +} + +// Find the "end" of the passed BorderLine. Returns the "Layout"Pos! +static sal_uInt16 lcl_FindEndPosOfBorder( const SwCollectTableLineBoxes& rCollTLB, + const SvxBorderLine& rBrdLn, size_t& rStt, bool bTop ) +{ + sal_uInt16 nPos, nLastPos = 0; + for( size_t nEnd = rCollTLB.Count(); rStt < nEnd; ++rStt ) + { + const SfxPoolItem* pItem; + const SvxBorderLine* pBrd; + const SwTableBox& rBox = rCollTLB.GetBox( rStt, &nPos ); + + if( SfxItemState::SET != rBox.GetFrameFormat()->GetItemState(RES_BOX,true, &pItem ) ) + break; + pBrd = GetLineTB( static_cast<const SvxBoxItem*>(pItem), bTop ); + if( !pBrd || *pBrd != rBrdLn ) + break; + nLastPos = nPos; + } + return nLastPos; +} + +static const SvxBorderLine* lcl_GCBorder_GetBorder( const SwTableBox& rBox, + bool bTop, + const SfxPoolItem** ppItem ) +{ + return SfxItemState::SET == rBox.GetFrameFormat()->GetItemState( RES_BOX, true, ppItem ) + ? GetLineTB( static_cast<const SvxBoxItem*>(*ppItem), bTop ) + : nullptr; +} + +static void lcl_GCBorder_DelBorder( const SwCollectTableLineBoxes& rCollTLB, + size_t& rStt, bool bTop, + const SvxBorderLine& rLine, + const SfxPoolItem* pItem, + sal_uInt16 nEndPos, + SwShareBoxFormats* pShareFormats ) +{ + SwTableBox* pBox = const_cast<SwTableBox*>(&rCollTLB.GetBox( rStt )); + sal_uInt16 nNextPos; + const SvxBorderLine* pLn = &rLine; + + do { + if( pLn && *pLn == rLine ) + { + SvxBoxItem aBox( *static_cast<const SvxBoxItem*>(pItem) ); + if( bTop ) + aBox.SetLine( nullptr, SvxBoxItemLine::TOP ); + else + aBox.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); + + if( pShareFormats ) + pShareFormats->SetAttr( *pBox, aBox ); + else + pBox->ClaimFrameFormat()->SetFormatAttr( aBox ); + } + + if( ++rStt >= rCollTLB.Count() ) + break; + + pBox = const_cast<SwTableBox*>(&rCollTLB.GetBox( rStt, &nNextPos )); + if( nNextPos > nEndPos ) + break; + + pLn = lcl_GCBorder_GetBorder( *pBox, bTop, &pItem ); + + } while( true ); +} + +static void lcl_GC_Box_Border( const SwTableBox* pBox, SwGCLineBorder* pPara ); + +void sw_GC_Line_Border( const SwTableLine* pLine, SwGCLineBorder* pGCPara ) +{ + // First the right edge with the left edge of the succeeding Box within this Line + { + SwGCBorder_BoxBrd aBPara; + const SvxBorderLine* pBrd; + const SfxPoolItem* pItem; + const SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( SwTableBoxes::size_type n = 0, nBoxes = rBoxes.size() - 1; n < nBoxes; ++n ) + { + SwTableBoxes aBoxes; + { + SwTableBox* pBox = rBoxes[ n ]; + if( pBox->GetSttNd() ) + aBoxes.insert( aBoxes.begin(), pBox ); + else + lcl_GCBorder_GetLastBox_B( pBox, &aBoxes ); + } + + for( SwTableBoxes::size_type i = aBoxes.size(); i; ) + { + SwTableBox* pBox = aBoxes[ --i ]; + if( SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( RES_BOX, true, &pItem ) ) + { + pBrd = static_cast<const SvxBoxItem*>(pItem)->GetRight(); + if( pBrd ) + { + aBPara.SetBorder( *pBrd ); + const SwTableBox* pNextBox = rBoxes[n+1]; + if( lcl_GCBorder_ChkBoxBrd_B( pNextBox, &aBPara ) && + aBPara.IsAnyBorderFound() ) + { + SvxBoxItem aBox( *static_cast<const SvxBoxItem*>(pItem) ); + aBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + if( pGCPara->pShareFormats ) + pGCPara->pShareFormats->SetAttr( *pBox, aBox ); + else + pBox->ClaimFrameFormat()->SetFormatAttr( aBox ); + } + } + } + } + + aBoxes.clear(); + } + } + + // And now the own bottom edge with the succeeding top edge + if( !pGCPara->IsLastLine() ) + { + SwCollectTableLineBoxes aBottom( false ); + SwCollectTableLineBoxes aTop( true ); + + sw_Line_CollectBox( pLine, &aBottom ); + + const SwTableLine* pNextLine = (*pGCPara->pLines)[ pGCPara->nLinePos+1 ]; + sw_Line_CollectBox( pNextLine, &aTop ); + + // remove all "duplicated" Lines that are the same + sal_uInt16 nBtmPos, nTopPos; + + size_t nSttBtm {0}; + size_t nSttTop {0}; + const size_t nEndBtm {aBottom.Count()}; + const size_t nEndTop {aTop.Count()}; + + const SwTableBox *pBtmBox = &aBottom.GetBox( nSttBtm++, &nBtmPos ); + const SwTableBox *pTopBox = &aTop.GetBox( nSttTop++, &nTopPos ); + const SfxPoolItem *pBtmItem = nullptr, *pTopItem = nullptr; + const SvxBorderLine *pBtmLine(nullptr), *pTopLine(nullptr); + bool bGetTopItem = true, bGetBtmItem = true; + + do { + if( bGetBtmItem ) + pBtmLine = lcl_GCBorder_GetBorder( *pBtmBox, false, &pBtmItem ); + if( bGetTopItem ) + pTopLine = lcl_GCBorder_GetBorder( *pTopBox, true, &pTopItem ); + + if( pTopLine && pBtmLine && *pTopLine == *pBtmLine ) + { + // We can remove one, but which one? + const size_t nSavSttBtm {nSttBtm}; + const size_t nSavSttTop {nSttTop}; + sal_uInt16 nBtmEndPos = ::lcl_FindEndPosOfBorder( aBottom, + *pTopLine, nSttBtm, false ); + if( !nBtmEndPos ) nBtmEndPos = nBtmPos; + sal_uInt16 nTopEndPos = ::lcl_FindEndPosOfBorder( aTop, + *pTopLine, nSttTop, true ); + if( !nTopEndPos ) nTopEndPos = nTopPos; + + if( nTopEndPos <= nBtmEndPos ) + { + // Delete the TopBorders until BottomEndPos + nSttTop = nSavSttTop; + if( nTopPos <= nBtmEndPos ) + lcl_GCBorder_DelBorder( aTop, --nSttTop, true, + *pBtmLine, pTopItem, nBtmEndPos, + pGCPara->pShareFormats ); + else + nSttBtm = nSavSttBtm; + } + else + { + // Else delete the BottomBorders until TopEndPos + nSttBtm = nSavSttBtm; + if( nBtmPos <= nTopEndPos ) + lcl_GCBorder_DelBorder( aBottom, --nSttBtm, false, + *pTopLine, pBtmItem, nTopEndPos, + pGCPara->pShareFormats ); + else + nSttTop = nSavSttTop; + } + nTopPos = nBtmPos; + } + + if( nTopPos == nBtmPos ) + { + if( nSttBtm >= nEndBtm || nSttTop >= nEndTop ) + break; + + pBtmBox = &aBottom.GetBox( nSttBtm++, &nBtmPos ); + pTopBox = &aTop.GetBox( nSttTop++, &nTopPos ); + bGetTopItem = bGetBtmItem = true; + } + else if( nTopPos < nBtmPos ) + { + if( nSttTop >= nEndTop ) + break; + pTopBox = &aTop.GetBox( nSttTop++, &nTopPos ); + bGetTopItem = true; + bGetBtmItem = false; + } + else + { + if( nSttBtm >= nEndBtm ) + break; + pBtmBox = &aBottom.GetBox( nSttBtm++, &nBtmPos ); + bGetTopItem = false; + bGetBtmItem = true; + } + + } while( true ); + } + + for( const auto& rpBox : pLine->GetTabBoxes() ) + lcl_GC_Box_Border(rpBox, pGCPara ); + + ++pGCPara->nLinePos; +} + +static void lcl_GC_Box_Border( const SwTableBox* pBox, SwGCLineBorder* pPara ) +{ + if( !pBox->GetTabLines().empty() ) + { + SwGCLineBorder aPara( *pBox ); + aPara.pShareFormats = pPara->pShareFormats; + for( const SwTableLine* pLine : pBox->GetTabLines() ) + sw_GC_Line_Border( pLine, &aPara ); + } +} + +namespace { + +struct GCLinePara +{ + SwTableLines* pLns; + SwShareBoxFormats* pShareFormats; + + GCLinePara( SwTableLines& rLns, GCLinePara* pPara = nullptr ) + : pLns( &rLns ), pShareFormats( pPara ? pPara->pShareFormats : nullptr ) + {} +}; + +} + +static bool lcl_MergeGCLine(SwTableLine* pLine, GCLinePara* pPara); + +static bool lcl_MergeGCBox(SwTableBox* pTableBox, GCLinePara* pPara) +{ + if( !pTableBox->GetTabLines().empty() ) + { + // ATTENTION: The Line count can change! + GCLinePara aPara( pTableBox->GetTabLines(), pPara ); + for( SwTableLines::size_type n = 0; + n < pTableBox->GetTabLines().size() && lcl_MergeGCLine( pTableBox->GetTabLines()[n], &aPara ); + ++n ) + ; + + if( 1 == pTableBox->GetTabLines().size() ) + { + // we have a box with a single line, so we just replace it by the line's boxes + SwTableLine* pInsLine = pTableBox->GetUpper(); + SwTableLine* pCpyLine = pTableBox->GetTabLines()[0]; + SwTableBoxes::iterator it = std::find( pInsLine->GetTabBoxes().begin(), pInsLine->GetTabBoxes().end(), pTableBox ); + for( auto pTabBox : pCpyLine->GetTabBoxes() ) + pTabBox->SetUpper( pInsLine ); + + // remove the old box from its parent line + it = pInsLine->GetTabBoxes().erase( it ); + // insert the nested line's boxes in its place + pInsLine->GetTabBoxes().insert( it, pCpyLine->GetTabBoxes().begin(), pCpyLine->GetTabBoxes().end()); + pCpyLine->GetTabBoxes().clear(); + // destroy the removed box + delete pTableBox; + + return false; // set up anew + } + } + return true; +} + +static bool lcl_MergeGCLine(SwTableLine* pLn, GCLinePara* pGCPara) +{ + SwTableBoxes::size_type nBoxes = pLn->GetTabBoxes().size(); + if( nBoxes ) + { + while( 1 == nBoxes ) + { + // We have a Box with Lines + SwTableBox* pBox = pLn->GetTabBoxes().front(); + if( pBox->GetTabLines().empty() ) + break; + + SwTableLine* pLine = pBox->GetTabLines()[0]; + + // pLine turns into the current Line (that is rpLine), the rest is moved + // into the LinesArray past the current one. + // The LinesArray is in pPara! + SwTableLines::size_type nLines = pBox->GetTabLines().size(); + + SwTableLines& rLns = *pGCPara->pLns; + sal_uInt16 nInsPos = rLns.GetPos( pLn ); + OSL_ENSURE( USHRT_MAX != nInsPos, "Could not find Line!" ); + + SwTableBox* pUpper = pLn->GetUpper(); + + rLns.erase( rLns.begin() + nInsPos ); // remove the Line from the array + rLns.insert( rLns.begin() + nInsPos, pBox->GetTabLines().begin(), pBox->GetTabLines().end() ); + + // JP 31.03.99: Bug 60000 + // Pass the attributes of the to-be-deleted Lines to the "inserted" one + const SfxPoolItem* pItem; + if( SfxItemState::SET == pLn->GetFrameFormat()->GetItemState( + RES_BACKGROUND, true, &pItem )) + { + SwTableLines& rBoxLns = pBox->GetTabLines(); + for( auto pBoxLine : rBoxLns ) + if( SfxItemState::SET != pBoxLine->GetFrameFormat()-> + GetItemState( RES_BACKGROUND )) + pGCPara->pShareFormats->SetAttr( *pBoxLine, *pItem ); + } + + pBox->GetTabLines().erase( pBox->GetTabLines().begin(), pBox->GetTabLines().begin() + nLines ); // Remove Lines from the array + + delete pLn; + + // Set the dependency anew + while( nLines-- ) + rLns[ nInsPos++ ]->SetUpper( pUpper ); + + pLn = pLine; // and set up anew + nBoxes = pLn->GetTabBoxes().size(); + } + + // ATTENTION: The number of boxes can change! + for( SwTableBoxes::size_type nLen = 0; nLen < pLn->GetTabBoxes().size(); ++nLen ) + if( !lcl_MergeGCBox( pLn->GetTabBoxes()[nLen], pGCPara )) + --nLen; + } + return true; +} + +// Clean structure a bit +void SwTable::GCLines() +{ + // ATTENTION: The Line count can change! + GCLinePara aPara( GetTabLines() ); + SwShareBoxFormats aShareFormats; + aPara.pShareFormats = &aShareFormats; + for( SwTableLines::size_type n = 0; n < GetTabLines().size() && + lcl_MergeGCLine( GetTabLines()[n], &aPara ); ++n ) + ; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/htmltbl.cxx b/sw/source/core/doc/htmltbl.cxx new file mode 100644 index 000000000..f791e7d34 --- /dev/null +++ b/sw/source/core/doc/htmltbl.cxx @@ -0,0 +1,1768 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> +#include <memory> + +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <frmfmt.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> +#include <flyfrm.hxx> +#include <poolfmt.hxx> +#include <viewsh.hxx> +#include <tabfrm.hxx> +#include <viewopt.hxx> +#include <htmltbl.hxx> +#include <calbck.hxx> +#include <o3tl/numeric.hxx> +#ifdef DBG_UTIL +#include <tblrwcl.hxx> +#endif + +using namespace ::com::sun::star; + +#define COLFUZZY 20 +#define MAX_TABWIDTH (USHRT_MAX - 2001) + +namespace { + +class SwHTMLTableLayoutConstraints +{ + sal_uInt16 nRow; // start row + sal_uInt16 nCol; // start column + sal_uInt16 nColSpan; // the column's COLSPAN + + std::unique_ptr<SwHTMLTableLayoutConstraints> pNext; // the next constraint + + sal_uLong nMinNoAlign, nMaxNoAlign; // provisional result of AL-Pass 1 + +public: + SwHTMLTableLayoutConstraints( sal_uLong nMin, sal_uLong nMax, sal_uInt16 nRow, + sal_uInt16 nCol, sal_uInt16 nColSp ); + + sal_uLong GetMinNoAlign() const { return nMinNoAlign; } + sal_uLong GetMaxNoAlign() const { return nMaxNoAlign; } + + SwHTMLTableLayoutConstraints *InsertNext( SwHTMLTableLayoutConstraints *pNxt ); + SwHTMLTableLayoutConstraints* GetNext() const { return pNext.get(); } + + sal_uInt16 GetColSpan() const { return nColSpan; } + sal_uInt16 GetColumn() const { return nCol; } +}; + +} + +SwHTMLTableLayoutCnts::SwHTMLTableLayoutCnts(const SwStartNode *pSttNd, + std::shared_ptr<SwHTMLTableLayout> const& rTab, + bool bNoBrTag, + std::shared_ptr<SwHTMLTableLayoutCnts> const& rNxt ) : + xNext( rNxt ), pBox( nullptr ), xTable( rTab ), pStartNode( pSttNd ), + nPass1Done( 0 ), nWidthSet( 0 ), bNoBreakTag( bNoBrTag ) +{} + +const SwStartNode *SwHTMLTableLayoutCnts::GetStartNode() const +{ + return pBox ? pBox->GetSttNd() : pStartNode; +} + +SwHTMLTableLayoutCell::SwHTMLTableLayoutCell(std::shared_ptr<SwHTMLTableLayoutCnts> const& rCnts, + sal_uInt16 nRSpan, sal_uInt16 nCSpan, + sal_uInt16 nWidth, bool bPercentWidth, + bool bNWrapOpt ) : + xContents(rCnts), + nRowSpan( nRSpan ), nColSpan( nCSpan ), + nWidthOption( nWidth ), bPercentWidthOption( bPercentWidth ), + bNoWrapOption( bNWrapOpt ) +{} + +SwHTMLTableLayoutColumn::SwHTMLTableLayoutColumn( sal_uInt16 nWidth, + bool bRelWidth, + bool bLBorder ) : + nMinNoAlign(MINLAY), nMaxNoAlign(MINLAY), nAbsMinNoAlign(MINLAY), + nMin(0), nMax(0), + nAbsColWidth(0), nRelColWidth(0), + nWidthOption( nWidth ), bRelWidthOption( bRelWidth ), + bLeftBorder( bLBorder ) +{} + +SwHTMLTableLayoutConstraints::SwHTMLTableLayoutConstraints( + sal_uLong nMin, sal_uLong nMax, sal_uInt16 nRw, sal_uInt16 nColumn, sal_uInt16 nColSp ): + nRow( nRw ), nCol( nColumn ), nColSpan( nColSp ), + nMinNoAlign( nMin ), nMaxNoAlign( nMax ) +{} + +SwHTMLTableLayoutConstraints *SwHTMLTableLayoutConstraints::InsertNext( + SwHTMLTableLayoutConstraints *pNxt ) +{ + SwHTMLTableLayoutConstraints *pPrev = nullptr; + SwHTMLTableLayoutConstraints *pConstr = this; + while( pConstr ) + { + if( pConstr->nRow > pNxt->nRow || + pConstr->GetColumn() > pNxt->GetColumn() ) + break; + pPrev = pConstr; + pConstr = pConstr->GetNext(); + } + + if( pPrev ) + { + pNxt->pNext = std::move(pPrev->pNext); + pPrev->pNext.reset( pNxt ); + pConstr = this; + } + else + { + pNxt->pNext.reset( this ); + pConstr = pNxt; + } + + return pConstr; +} + +SwHTMLTableLayout::SwHTMLTableLayout( const SwTable * pTable, + sal_uInt16 nRws, sal_uInt16 nCls, + bool bColsOpt, bool bColTgs, + sal_uInt16 nWdth, bool bPercentWdth, + sal_uInt16 nBorderOpt, sal_uInt16 nCellPad, + sal_uInt16 nCellSp, SvxAdjust eAdjust, + sal_uInt16 nLMargin, sal_uInt16 nRMargin, + sal_uInt16 nBWidth, sal_uInt16 nLeftBWidth, + sal_uInt16 nRightBWidth ) + : m_aColumns( nCls ) + , m_aCells( static_cast<size_t>(nRws)*nCls ) + , m_pSwTable( pTable ) + , m_nMin( 0 ) + , m_nMax( 0 ) + , m_nRows( nRws ) + , m_nCols( nCls ) + , m_nLeftMargin( nLMargin ) + , m_nRightMargin( nRMargin ) + , m_nInhAbsLeftSpace( 0 ) + , m_nInhAbsRightSpace( 0 ) + , m_nRelLeftFill( 0 ) + , m_nRelRightFill( 0 ) + , m_nRelTabWidth( 0 ) + , m_nWidthOption( nWdth ) + , m_nCellPadding( nCellPad ) + , m_nCellSpacing( nCellSp ) + , m_nBorder( nBorderOpt ) + , m_nLeftBorderWidth( nLeftBWidth ) + , m_nRightBorderWidth( nRightBWidth ) + , m_nInhLeftBorderWidth( 0 ) + , m_nInhRightBorderWidth( 0 ) + , m_nBorderWidth( nBWidth ) + , m_nDelayedResizeAbsAvail( 0 ) + , m_nLastResizeAbsAvail( 0 ) + , m_nPass1Done( 0 ) + , m_nWidthSet( 0 ) + , m_eTableAdjust( eAdjust ) + , m_bColsOption( bColsOpt ) + , m_bColTags( bColTgs ) + , m_bPercentWidthOption( bPercentWdth ) + , m_bUseRelWidth( false ) + , m_bMustResize( true ) + , m_bExportable( true ) + , m_bBordersChanged( false ) + , m_bMayBeInFlyFrame( false ) + , m_bDelayedResizeRecalc( false) + , m_bMustNotResize( false ) + , m_bMustNotRecalc( false ) +{ + m_aResizeTimer.SetInvokeHandler( LINK( this, SwHTMLTableLayout, + DelayedResize_Impl ) ); +} + +SwHTMLTableLayout::~SwHTMLTableLayout() +{ +} + +/// The border widths are calculated like in Netscape: +/// Outer border: BORDER + CELLSPACING + CELLPADDING +/// Inner border: CELLSPACING + CELLPADDING +/// However, we respect the border widths in SW if bSwBorders is set, +/// so that we don't wrap wrongly. +/// We also need to respect the distance to the content. Even if +/// only the opposite side has a border. +sal_uInt16 SwHTMLTableLayout::GetLeftCellSpace( sal_uInt16 nCol, sal_uInt16 nColSpan, + bool bSwBorders ) const +{ + sal_uInt16 nSpace = m_nCellSpacing + m_nCellPadding; + + if( nCol == 0 ) + { + nSpace = nSpace + m_nBorder; + + if( bSwBorders && nSpace < m_nLeftBorderWidth ) + nSpace = m_nLeftBorderWidth; + } + else if( bSwBorders ) + { + if( GetColumn(nCol)->HasLeftBorder() ) + { + if( nSpace < m_nBorderWidth ) + nSpace = m_nBorderWidth; + } + else if( nCol+nColSpan == m_nCols && m_nRightBorderWidth && + nSpace < MIN_BORDER_DIST ) + { + OSL_ENSURE( !m_nCellPadding, "GetLeftCellSpace: CELLPADDING!=0" ); + // If the opposite side has a border we need to respect at + // least the minimum distance to the content. + // Additionally, we could also use nCellPadding for this. + nSpace = MIN_BORDER_DIST; + } + } + + return nSpace; +} + +sal_uInt16 SwHTMLTableLayout::GetRightCellSpace( sal_uInt16 nCol, sal_uInt16 nColSpan, + bool bSwBorders ) const +{ + sal_uInt16 nSpace = m_nCellPadding; + + if( nCol+nColSpan == m_nCols ) + { + nSpace += m_nBorder + m_nCellSpacing; + if( bSwBorders && nSpace < m_nRightBorderWidth ) + nSpace = m_nRightBorderWidth; + } + else if( bSwBorders && GetColumn(nCol)->HasLeftBorder() && + nSpace < MIN_BORDER_DIST ) + { + OSL_ENSURE( !m_nCellPadding, "GetRightCellSpace: CELLPADDING!=0" ); + // If the opposite side has a border we need to respect at + // least the minimum distance to the content. + // Additionally, we could also use nCellPadding for this. + nSpace = MIN_BORDER_DIST; + } + + return nSpace; +} + +void SwHTMLTableLayout::AddBorderWidth( sal_uLong &rMin, sal_uLong &rMax, + sal_uLong &rAbsMin, + sal_uInt16 nCol, sal_uInt16 nColSpan, + bool bSwBorders ) const +{ + sal_uLong nAdd = GetLeftCellSpace( nCol, nColSpan, bSwBorders ) + + GetRightCellSpace( nCol, nColSpan, bSwBorders ); + + rMin += nAdd; + rMax += nAdd; + rAbsMin += nAdd; +} + +void SwHTMLTableLayout::SetBoxWidth( SwTableBox *pBox, sal_uInt16 nCol, + sal_uInt16 nColSpan ) const +{ + SwFrameFormat *pFrameFormat = pBox->GetFrameFormat(); + + // calculate the box's width + SwTwips nFrameWidth = 0; + while( nColSpan-- ) + nFrameWidth += GetColumn( nCol++ )->GetRelColWidth(); + + // and reset + pFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nFrameWidth, 0 )); +} + +void SwHTMLTableLayout::GetAvail( sal_uInt16 nCol, sal_uInt16 nColSpan, + sal_uInt16& rAbsAvail, sal_uInt16& rRelAvail ) const +{ + rAbsAvail = 0; + rRelAvail = 0; + for( sal_uInt16 i=nCol; i<nCol+nColSpan;i++ ) + { + const SwHTMLTableLayoutColumn *pColumn = GetColumn(i); + rAbsAvail = rAbsAvail + pColumn->GetAbsColWidth(); + rRelAvail = rRelAvail + pColumn->GetRelColWidth(); + } +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidthByVisArea( const SwDoc& rDoc ) +{ + SwViewShell const *pVSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pVSh ) + { + return static_cast<sal_uInt16>(pVSh->GetBrowseWidth()); + } + + return 0; +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidth( const SwDoc& rDoc ) +{ + // If we have a layout, we can get the width from there. + const SwRootFrame *pRootFrame = rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pRootFrame ) + { + const SwFrame *pPageFrame = pRootFrame->GetLower(); + if( pPageFrame ) + return static_cast<sal_uInt16>(pPageFrame->getFramePrintArea().Width()); + } + + // #i91658# + // Assertion removed which state that no browse width is available. + // Investigation reveals that all calls can handle the case that no browse + // width is provided. + return GetBrowseWidthByVisArea( rDoc ); +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidthByTabFrame( + const SwTabFrame& rTabFrame ) const +{ + SwTwips nWidth = 0; + + const SwFrame *pUpper = rTabFrame.GetUpper(); + if( MayBeInFlyFrame() && pUpper->IsFlyFrame() && + static_cast<const SwFlyFrame *>(pUpper)->GetAnchorFrame() ) + { + // If the table is located within a self-created frame, the anchor's + // width is relevant not the frame's width. + // For paragraph-bound frames we don't respect paragraph indents. + const SwFrame *pAnchor = static_cast<const SwFlyFrame *>(pUpper)->GetAnchorFrame(); + if( pAnchor->IsTextFrame() ) + nWidth = pAnchor->getFrameArea().Width(); + else + nWidth = pAnchor->getFramePrintArea().Width(); + } + else + { + nWidth = pUpper->getFramePrintArea().Width(); + } + + SwTwips nUpperDummy = 0; + long nRightOffset = 0, + nLeftOffset = 0; + rTabFrame.CalcFlyOffsets( nUpperDummy, nLeftOffset, nRightOffset ); + nWidth -= (nLeftOffset + nRightOffset); + + return static_cast<sal_uInt16>(std::min(nWidth, SwTwips(SAL_MAX_UINT16))); +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidthByTable( const SwDoc& rDoc ) const +{ + sal_uInt16 nBrowseWidth = 0; + SwTabFrame* pFrame = SwIterator<SwTabFrame,SwFormat>( *m_pSwTable->GetFrameFormat() ).First(); + if( pFrame ) + { + nBrowseWidth = GetBrowseWidthByTabFrame( *pFrame ); + } + else + { + nBrowseWidth = SwHTMLTableLayout::GetBrowseWidth( rDoc ); + } + + return nBrowseWidth; +} + +const SwStartNode *SwHTMLTableLayout::GetAnyBoxStartNode() const +{ + const SwStartNode *pBoxSttNd; + + const SwTableBox* pBox = m_pSwTable->GetTabLines()[0]->GetTabBoxes()[0]; + while( nullptr == (pBoxSttNd = pBox->GetSttNd()) ) + { + OSL_ENSURE( !pBox->GetTabLines().empty(), + "Box without start node and lines" ); + OSL_ENSURE( !pBox->GetTabLines().front()->GetTabBoxes().empty(), + "Line without boxes" ); + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + } + + return pBoxSttNd; +} + +SwFrameFormat *SwHTMLTableLayout::FindFlyFrameFormat() const +{ + const SwTableNode *pTableNd = GetAnyBoxStartNode()->FindTableNode(); + OSL_ENSURE( pTableNd, "No Table-Node?" ); + return pTableNd->GetFlyFormat(); +} + +static void lcl_GetMinMaxSize( sal_uLong& rMinNoAlignCnts, sal_uLong& rMaxNoAlignCnts, + sal_uLong& rAbsMinNoAlignCnts, + SwTextNode const *pTextNd, sal_uLong nIdx, bool bNoBreak ) +{ + pTextNd->GetMinMaxSize( nIdx, rMinNoAlignCnts, rMaxNoAlignCnts, + rAbsMinNoAlignCnts ); + OSL_ENSURE( rAbsMinNoAlignCnts <= rMinNoAlignCnts, + "GetMinMaxSize: absmin > min" ); + OSL_ENSURE( rMinNoAlignCnts <= rMaxNoAlignCnts, + "GetMinMaxSize: max > min" ); + + // The maximal width for a <PRE> paragraph is the minimal width + const SwFormatColl *pColl = &pTextNd->GetAnyFormatColl(); + while( pColl && !pColl->IsDefault() && + (USER_FMT & pColl->GetPoolFormatId()) ) + { + pColl = static_cast<const SwFormatColl *>(pColl->DerivedFrom()); + } + + // <NOBR> in the whole cell apply to text but not to tables. + // Netscape only considers this for graphics. + if( (pColl && RES_POOLCOLL_HTML_PRE==pColl->GetPoolFormatId()) || bNoBreak ) + { + rMinNoAlignCnts = rMaxNoAlignCnts; + rAbsMinNoAlignCnts = rMaxNoAlignCnts; + } +} + +void SwHTMLTableLayout::AutoLayoutPass1() +{ + m_nPass1Done++; + + m_nMin = m_nMax = 0; // clear pass1 info + + bool bFixRelWidths = false; + sal_uInt16 i; + + std::unique_ptr<SwHTMLTableLayoutConstraints> xConstraints; + + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + pColumn->ClearPass1Info( !HasColTags() ); + sal_uInt16 nMinColSpan = USHRT_MAX; // Column count to which the calculated width refers to + sal_uInt16 nColSkip = USHRT_MAX; // How many columns need to be skipped + + for( sal_uInt16 j=0; j<m_nRows; j++ ) + { + SwHTMLTableLayoutCell *pCell = GetCell(j,i); + SwHTMLTableLayoutCnts *pCnts = pCell->GetContents().get(); + + // We need to examine all rows in order to + // get the column that should be calculated next. + sal_uInt16 nColSpan = pCell->GetColSpan(); + if( nColSpan < nColSkip ) + nColSkip = nColSpan; + + if( !pCnts || !pCnts->IsPass1Done(m_nPass1Done) ) + { + // The cell is empty or it's content was not edited + if( nColSpan < nMinColSpan ) + nMinColSpan = nColSpan; + + sal_uLong nMinNoAlignCell = 0; + sal_uLong nMaxNoAlignCell = 0; + sal_uLong nAbsMinNoAlignCell = 0; + sal_uLong nMaxTableCell = 0; + sal_uLong nAbsMinTableCell = 0; + + while( pCnts ) + { + const SwStartNode *pSttNd = pCnts->GetStartNode(); + if( pSttNd ) + { + const SwDoc *pDoc = pSttNd->GetDoc(); + sal_uLong nIdx = pSttNd->GetIndex(); + while( !(pDoc->GetNodes()[nIdx])->IsEndNode() ) + { + SwTextNode *pTextNd = (pDoc->GetNodes()[nIdx])->GetTextNode(); + if( pTextNd ) + { + sal_uLong nMinNoAlignCnts = 0; + sal_uLong nMaxNoAlignCnts = 0; + sal_uLong nAbsMinNoAlignCnts = 0; + + lcl_GetMinMaxSize( nMinNoAlignCnts, + nMaxNoAlignCnts, + nAbsMinNoAlignCnts, + pTextNd, nIdx, + pCnts->HasNoBreakTag() ); + + if( nMinNoAlignCnts > nMinNoAlignCell ) + nMinNoAlignCell = nMinNoAlignCnts; + if( nMaxNoAlignCnts > nMaxNoAlignCell ) + nMaxNoAlignCell = nMaxNoAlignCnts; + if( nAbsMinNoAlignCnts > nAbsMinNoAlignCell ) + nAbsMinNoAlignCell = nAbsMinNoAlignCnts; + } + else + { + SwTableNode *pTabNd = (pDoc->GetNodes()[nIdx])->GetTableNode(); + if( pTabNd ) + { + SwHTMLTableLayout *pChild = pTabNd->GetTable().GetHTMLTableLayout(); + if( pChild ) + { + pChild->AutoLayoutPass1(); + sal_uLong nMaxTableCnts = pChild->m_nMax; + sal_uLong nAbsMinTableCnts = pChild->m_nMin; + + // A fixed table width is taken over as minimum and + // maximum at the same time + if( !pChild->m_bPercentWidthOption && pChild->m_nWidthOption ) + { + sal_uLong nTabWidth = pChild->m_nWidthOption; + if( nTabWidth >= nAbsMinTableCnts ) + { + nMaxTableCnts = nTabWidth; + nAbsMinTableCnts = nTabWidth; + } + else + { + nMaxTableCnts = nAbsMinTableCnts; + } + } + + if( nMaxTableCnts > nMaxTableCell ) + nMaxTableCell = nMaxTableCnts; + if( nAbsMinTableCnts > nAbsMinTableCell ) + nAbsMinTableCell = nAbsMinTableCnts; + } + nIdx = pTabNd->EndOfSectionNode()->GetIndex(); + } + } + nIdx++; + } + } + else if (SwHTMLTableLayout *pChild = pCnts->GetTable()) + { + OSL_ENSURE( false, "Sub tables in HTML import?" ); + pChild->AutoLayoutPass1(); + sal_uLong nMaxTableCnts = pChild->m_nMax; + sal_uLong nAbsMinTableCnts = pChild->m_nMin; + + // A fixed table width is taken over as minimum and + // maximum at the same time + if( !pChild->m_bPercentWidthOption && pChild->m_nWidthOption ) + { + sal_uLong nTabWidth = pChild->m_nWidthOption; + if( nTabWidth >= nAbsMinTableCnts ) + { + nMaxTableCnts = nTabWidth; + nAbsMinTableCnts = nTabWidth; + } + else + { + nMaxTableCnts = nAbsMinTableCnts; + } + } + + if( nMaxTableCnts > nMaxTableCell ) + nMaxTableCell = nMaxTableCnts; + if( nAbsMinTableCnts > nAbsMinTableCell ) + nAbsMinTableCell = nAbsMinTableCnts; + } + pCnts->SetPass1Done( m_nPass1Done ); + pCnts = pCnts->GetNext().get(); + } + +// This code previously came after AddBorderWidth + // If a table's width is wider in a cell than what we've calculated + // for the other content we need to use the table's width. + if( nMaxTableCell > nMaxNoAlignCell ) + nMaxNoAlignCell = nMaxTableCell; + if( nAbsMinTableCell > nAbsMinNoAlignCell ) + { + nAbsMinNoAlignCell = nAbsMinTableCell; + if( nMinNoAlignCell < nAbsMinNoAlignCell ) + nMinNoAlignCell = nAbsMinNoAlignCell; + if( nMaxNoAlignCell < nMinNoAlignCell ) + nMaxNoAlignCell = nMinNoAlignCell; + } +// This code previously came after AddBorderWidth + + bool bRelWidth = pCell->IsPercentWidthOption(); + sal_uInt16 nWidth = pCell->GetWidthOption(); + + // A NOWRAP option applies to text and tables, but is + // not applied for fixed cell width. + // Instead, the stated cell width behaves like a minimal + // width. + if( pCell->HasNoWrapOption() ) + { + if( nWidth==0 || bRelWidth ) + { + nMinNoAlignCell = nMaxNoAlignCell; + nAbsMinNoAlignCell = nMaxNoAlignCell; + } + else + { + if( nWidth>nMinNoAlignCell ) + nMinNoAlignCell = nWidth; + if( nWidth>nAbsMinNoAlignCell ) + nAbsMinNoAlignCell = nWidth; + } + } + + // Respect minimum width for content + if( nMinNoAlignCell < MINLAY ) + nMinNoAlignCell = MINLAY; + if( nMaxNoAlignCell < MINLAY ) + nMaxNoAlignCell = MINLAY; + if( nAbsMinNoAlignCell < MINLAY ) + nAbsMinNoAlignCell = MINLAY; + + // Respect the border and distance to the content + AddBorderWidth( nMinNoAlignCell, nMaxNoAlignCell, + nAbsMinNoAlignCell, i, nColSpan ); + + if( 1==nColSpan ) + { + // take over the values directly + pColumn->MergeMinMaxNoAlign( nMinNoAlignCell, + nMaxNoAlignCell, + nAbsMinNoAlignCell ); + + // the widest WIDTH wins + if( !HasColTags() ) + pColumn->MergeCellWidthOption( nWidth, bRelWidth ); + } + else + { + // Process the data line by line from left to right at the end + + // When which values is taken over will be explained further down. + if( !HasColTags() && nWidth && !bRelWidth ) + { + sal_uLong nAbsWidth = nWidth, nDummy = 0, nDummy2 = 0; + AddBorderWidth( nAbsWidth, nDummy, nDummy2, + i, nColSpan, false ); + + if( nAbsWidth >= nMinNoAlignCell ) + { + nMaxNoAlignCell = nAbsWidth; + if( HasColsOption() ) + nMinNoAlignCell = nAbsWidth; + } + else if( nAbsWidth >= nAbsMinNoAlignCell ) + { + nMaxNoAlignCell = nAbsWidth; + nMinNoAlignCell = nAbsWidth; + } + else + { + nMaxNoAlignCell = nAbsMinNoAlignCell; + nMinNoAlignCell = nAbsMinNoAlignCell; + } + } + else if( HasColsOption() || HasColTags() ) + nMinNoAlignCell = nAbsMinNoAlignCell; + + SwHTMLTableLayoutConstraints *pConstr = + new SwHTMLTableLayoutConstraints( nMinNoAlignCell, + nMaxNoAlignCell, j, i, nColSpan ); + if (xConstraints) + { + SwHTMLTableLayoutConstraints* pConstraints = xConstraints->InsertNext(pConstr); + xConstraints.release(); + xConstraints.reset(pConstraints); + } + else + xConstraints.reset(pConstr); + } + } + } + + OSL_ENSURE( nMinColSpan>0 && nColSkip>0 && nColSkip <= nMinColSpan, + "Layout pass 1: Columns are being forgotten!" ); + OSL_ENSURE( nMinColSpan!=USHRT_MAX, + "Layout pass 1: unnecessary pass through the loop or a bug" ); + + if( 1==nMinColSpan ) + { + // There are cells with COLSPAN 1 and therefore also useful + // values in pColumn + + // Take over values according to the following table (Netscape 4.0 pv 3): + + // WIDTH: no COLS COLS + + // none min = min min = absmin + // max = max max = max + + // >= min min = min min = width + // max = width max = width + + // >= absmin min = width(*) min = width + // max = width max = width + + // < absmin min = absmin min = absmin + // max = absmin max = absmin + + // (*) Netscape uses the minimum width without a break before + // the last graphic here. We don't have that (yet?), + // so we leave it set to width. + + if( pColumn->GetWidthOption() && !pColumn->IsRelWidthOption() ) + { + // Take over absolute widths as minimal and maximal widths. + sal_uLong nAbsWidth = pColumn->GetWidthOption(); + sal_uLong nDummy = 0, nDummy2 = 0; + AddBorderWidth( nAbsWidth, nDummy, nDummy2, i, 1, false ); + + if( nAbsWidth >= pColumn->GetMinNoAlign() ) + { + pColumn->SetMinMax( HasColsOption() ? nAbsWidth + : pColumn->GetMinNoAlign(), + nAbsWidth ); + } + else if( nAbsWidth >= pColumn->GetAbsMinNoAlign() ) + { + pColumn->SetMinMax( nAbsWidth, nAbsWidth ); + } + else + { + pColumn->SetMinMax( pColumn->GetAbsMinNoAlign(), + pColumn->GetAbsMinNoAlign() ); + } + } + else + { + pColumn->SetMinMax( HasColsOption() ? pColumn->GetAbsMinNoAlign() + : pColumn->GetMinNoAlign(), + pColumn->GetMaxNoAlign() ); + } + } + else if( USHRT_MAX!=nMinColSpan ) + { + // Can be anything != 0, because it is altered by the constraints. + pColumn->SetMinMax( MINLAY, MINLAY ); + + // the next columns need not to be processed + i += (nColSkip-1); + } + + m_nMin += pColumn->GetMin(); + m_nMax += pColumn->GetMax(); + if (pColumn->IsRelWidthOption()) bFixRelWidths = true; + } + + // Now process the constraints + SwHTMLTableLayoutConstraints *pConstr = xConstraints.get(); + while( pConstr ) + { + // At first we need to process the width in the same way + // as the column widths + sal_uInt16 nCol = pConstr->GetColumn(); + sal_uInt16 nColSpan = pConstr->GetColSpan(); + sal_uLong nConstrMin = pConstr->GetMinNoAlign(); + sal_uLong nConstrMax = pConstr->GetMaxNoAlign(); + + // We get the hitherto width of the spanned columns + sal_uLong nColsMin = 0; + sal_uLong nColsMax = 0; + for( sal_uInt16 j=nCol; j<nCol+nColSpan; j++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( j ); + nColsMin += pColumn->GetMin(); + nColsMax += pColumn->GetMax(); + } + + if( nColsMin<nConstrMin ) + { + // Proportionately distribute the minimum value to the columns + sal_uLong nMinD = nConstrMin-nColsMin; + + if( nConstrMin > nColsMax ) + { + // Proportional according to the minimum widths + sal_uInt16 nEndCol = nCol+nColSpan; + sal_uLong nDiff = nMinD; + for( sal_uInt16 ic=nCol; ic<nEndCol; ic++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( ic ); + + sal_uLong nColMin = pColumn->GetMin(); + sal_uLong nColMax = pColumn->GetMax(); + + m_nMin -= nColMin; + sal_uLong nAdd; + if (ic < nEndCol-1) + { + if (nColsMin == 0) + throw o3tl::divide_by_zero(); + nAdd = (nColMin * nMinD) / nColsMin; + } + else + { + nAdd = nDiff; + } + nColMin += nAdd; + m_nMin += nColMin; + OSL_ENSURE( nDiff >= nAdd, "Ooops: nDiff is not correct anymore" ); + nDiff -= nAdd; + + if( nColMax < nColMin ) + { + m_nMax -= nColMax; + nColsMax -= nColMax; + nColMax = nColMin; + m_nMax += nColMax; + nColsMax += nColMax; + } + + pColumn->SetMinMax( nColMin, nColMax ); + } + } + else + { + // Proportional according to the difference of max and min + for( sal_uInt16 ic=nCol; ic<nCol+nColSpan; ic++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( ic ); + + sal_uLong nDiff = pColumn->GetMax()-pColumn->GetMin(); + if( nMinD < nDiff ) + nDiff = nMinD; + + pColumn->AddToMin( nDiff ); + + OSL_ENSURE( pColumn->GetMax() >= pColumn->GetMin(), + "Why is the Column suddenly too narrow?" ); + + m_nMin += nDiff; + nMinD -= nDiff; + } + } + } + + if( !HasColTags() && nColsMax<nConstrMax ) + { + sal_uLong nMaxD = nConstrMax-nColsMax; + + for( sal_uInt16 ic=nCol; ic<nCol+nColSpan; ic++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( ic ); + + m_nMax -= pColumn->GetMax(); + + pColumn->AddToMax( (pColumn->GetMax() * nMaxD) / nColsMax ); + + m_nMax += pColumn->GetMax(); + } + } + + pConstr = pConstr->GetNext(); + } + + if( bFixRelWidths ) + { + if( HasColTags() ) + { + // To adapt the relative widths, in a first step we multiply the + // minimum width of all affected cells with the relative width + // of the column. + // Thus, the width ratio among the columns is correct. + + // Furthermore, a factor is calculated that says by how much the + // cell has gotten wider than the minimum width. + + // In the second step the calculated widths are divided by this + // factor. Thereby a cell's width is preserved and serves as a + // basis for the other cells. + // We only change the maximum widths here! + + sal_uLong nAbsMin = 0; // absolute minimum width of all widths with relative width + sal_uLong nRel = 0; // sum of all relative widths of all columns + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() ) + { + nAbsMin += pColumn->GetMin(); + nRel += pColumn->GetWidthOption(); + } + } + + sal_uLong nQuot = ULONG_MAX; + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() ) + { + m_nMax -= pColumn->GetMax(); + if( pColumn->GetWidthOption() && pColumn->GetMin() ) + { + pColumn->SetMax( nAbsMin * pColumn->GetWidthOption() ); + sal_uLong nColQuot = pColumn->GetMax() / pColumn->GetMin(); + if( nColQuot<nQuot ) + nQuot = nColQuot; + } + } + } + OSL_ENSURE( 0==nRel || nQuot!=ULONG_MAX, + "Where did the relative columns go?" ); + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() ) + { + if( pColumn->GetWidthOption() ) + pColumn->SetMax( pColumn->GetMax() / nQuot ); + else + pColumn->SetMax( pColumn->GetMin() ); + OSL_ENSURE( pColumn->GetMax() >= pColumn->GetMin(), + "Maximum column width is lower than the minimum column width" ); + m_nMax += pColumn->GetMax(); + } + } + } + else + { + sal_uInt16 nRel = 0; // sum of the relative widths of all columns + sal_uInt16 nRelCols = 0; // count of the columns with a relative setting + sal_uLong nRelMax = 0; // fraction of the maximum of this column + for( i=0; i<m_nCols; i++ ) + { + OSL_ENSURE( nRel<=100, "relative width of all columns > 100%" ); + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() ) + { + // Make sure that the relative widths don't go above 100% + sal_uInt16 nColWidth = pColumn->GetWidthOption(); + if( nRel+nColWidth > 100 ) + { + nColWidth = 100 - nRel; + pColumn->SetWidthOption( nColWidth ); + } + nRelMax += pColumn->GetMax(); + nRel = nRel + nColWidth; + nRelCols++; + } + else if( !pColumn->GetMin() ) + { + // The column is empty (so it was solely created by + // COLSPAN) and therefore must not be assigned a % width. + nRelCols++; + } + } + + // If there are percentages left we distribute them to the columns + // that don't have a width setting. Like in Netscape we distribute + // the remaining percentages according to the ratio of the maximum + // width of the affected columns. + // For the maximum widths we also take the fixed-width columns + // into account. Is that correct? + sal_uLong nFixMax = 0; + if( nRel < 100 && nRelCols < m_nCols ) + { + nFixMax = m_nMax - nRelMax; + SAL_WARN_IF(!nFixMax, "sw.core", "bad fixed width max"); + } + if (nFixMax) + { + sal_uInt16 nRelLeft = 100 - nRel; + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( !pColumn->IsRelWidthOption() && + !pColumn->GetWidthOption() && + pColumn->GetMin() ) + { + // the next column gets the rest + sal_uInt16 nColWidth = + static_cast<sal_uInt16>((pColumn->GetMax() * nRelLeft) / nFixMax); + pColumn->SetWidthOption( nColWidth ); + } + } + } + + // adjust the maximum widths now accordingly + sal_uLong nQuotMax = ULONG_MAX; + sal_uLong nOldMax = m_nMax; + m_nMax = 0; + for( i=0; i<m_nCols; i++ ) + { + // Columns with a % setting are adapted accordingly. + // Columns, that + // - do not have a % setting and are located within a tables + // with COLS and WIDTH, or + // - their width is 0% + // get set to the minimum width. + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() ) + { + sal_uLong nNewMax; + sal_uLong nColQuotMax; + if( !m_nWidthOption ) + { + nNewMax = nOldMax * pColumn->GetWidthOption(); + nColQuotMax = nNewMax / pColumn->GetMax(); + } + else + { + nNewMax = m_nMin * pColumn->GetWidthOption(); + nColQuotMax = nNewMax / pColumn->GetMin(); + } + pColumn->SetMax( nNewMax ); + if( nColQuotMax < nQuotMax ) + nQuotMax = nColQuotMax; + } + else if( HasColsOption() || m_nWidthOption || + (pColumn->IsRelWidthOption() && + !pColumn->GetWidthOption()) ) + pColumn->SetMax( pColumn->GetMin() ); + } + // and divide by the quotient + SAL_WARN_IF(!nQuotMax, "sw.core", "Where did the relative columns go?"); + for (i = 0; i < m_nCols; ++i) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if (pColumn->IsRelWidthOption() && pColumn->GetWidthOption() && nQuotMax) + { + pColumn->SetMax( pColumn->GetMax() / nQuotMax ); + OSL_ENSURE( pColumn->GetMax() >= pColumn->GetMin(), + "Minimum width is one column bigger than maximum" ); + if( pColumn->GetMax() < pColumn->GetMin() ) + pColumn->SetMax( pColumn->GetMin() ); + } + m_nMax += pColumn->GetMax(); + } + } + } +} + +//TODO: provide documentation +/** + + @param nAbsAvail available space in TWIPS. + @param nRelAvail available space related to USHRT_MAX or 0 + @param nAbsSpace fraction of nAbsAvail, which is reserved by the surrounding + cell for the border and the distance to the paragraph. +*/ +void SwHTMLTableLayout::AutoLayoutPass2( sal_uInt16 nAbsAvail, sal_uInt16 nRelAvail, + sal_uInt16 nAbsLeftSpace, + sal_uInt16 nAbsRightSpace, + sal_uInt16 nParentInhAbsSpace ) +{ + // For a start we do a lot of plausibility tests + + // An absolute width always has to be passed + OSL_ENSURE( nAbsAvail, "AutoLayout pass 2: No absolute width given" ); + + // A relative width must only be passed for tables within tables (?) + OSL_ENSURE( IsTopTable() == (nRelAvail==0), + "AutoLayout pass 2: Relative width at table in table or the other way around" ); + + // The table's minimum width must not be bigger than its maximum width + OSL_ENSURE( m_nMin<=m_nMax, "AutoLayout pass 2: nMin > nMax" ); + + // Remember the available width for which the table was calculated. + // This is a good place as we pass by here for the initial calculation + // of the table in the parser and for each Resize_ call. + m_nLastResizeAbsAvail = nAbsAvail; + + // Step 1: The available space is readjusted for the left/right border, + // possibly existing filler cells and distances. + + // Distance to the content and border + sal_uInt16 nAbsLeftFill = 0, nAbsRightFill = 0; + if( !IsTopTable() && + GetMin() + nAbsLeftSpace + nAbsRightSpace <= nAbsAvail ) + { + nAbsLeftFill = nAbsLeftSpace; + nAbsRightFill = nAbsRightSpace; + } + + // Left and right distance + if( m_nLeftMargin || m_nRightMargin ) + { + if( IsTopTable() ) + { + // For the top table we always respect the borders, because we + // never go below the table's minimum width. + nAbsAvail -= (m_nLeftMargin + m_nRightMargin); + } + else if( GetMin() + m_nLeftMargin + m_nRightMargin <= nAbsAvail ) + { + // Else, we only respect the borders if there's space available + // for them (nMin has already been calculated!) + nAbsLeftFill = nAbsLeftFill + m_nLeftMargin; + nAbsRightFill = nAbsRightFill + m_nRightMargin; + } + } + + // Read just the available space + m_nRelLeftFill = 0; + m_nRelRightFill = 0; + if( !IsTopTable() && (nAbsLeftFill>0 || nAbsRightFill) ) + { + sal_uLong nAbsLeftFillL = nAbsLeftFill, nAbsRightFillL = nAbsRightFill; + + m_nRelLeftFill = static_cast<sal_uInt16>((nAbsLeftFillL * nRelAvail) / nAbsAvail); + m_nRelRightFill = static_cast<sal_uInt16>((nAbsRightFillL * nRelAvail) / nAbsAvail); + + nAbsAvail -= (nAbsLeftFill + nAbsRightFill); + if( nRelAvail ) + nRelAvail -= (m_nRelLeftFill + m_nRelRightFill); + } + + // Step 2: Calculate the absolute table width. + sal_uInt16 nAbsTabWidth = 0; + m_bUseRelWidth = false; + if( m_nWidthOption ) + { + if( m_bPercentWidthOption ) + { + OSL_ENSURE( m_nWidthOption<=100, "Percentage value too high" ); + if( m_nWidthOption > 100 ) + m_nWidthOption = 100; + + // The absolute width is equal to the given percentage of + // the available width. + // Top tables only get a relative width if the available space + // is *strictly larger* than the minimum width. + + // CAUTION: We need the "strictly larger" because changing from a + // relative width to an absolute width by resizing would lead + // to an infinite loop. + + // Because we do not call resize for tables in frames if the + // frame has a non-relative width, we cannot play such games. + + // Let's play such games now anyway. We had a graphic in a 1% wide + // table and it didn't fit in of course. + nAbsTabWidth = static_cast<sal_uInt16>( (static_cast<sal_uLong>(nAbsAvail) * m_nWidthOption) / 100 ); + if( IsTopTable() && + ( /*MayBeInFlyFrame() ||*/ static_cast<sal_uLong>(nAbsTabWidth) > m_nMin ) ) + { + nRelAvail = USHRT_MAX; + m_bUseRelWidth = true; + } + } + else + { + nAbsTabWidth = m_nWidthOption; + if( nAbsTabWidth > MAX_TABWIDTH ) + nAbsTabWidth = MAX_TABWIDTH; + + // Tables within tables must never get wider than the available + // space. + if( !IsTopTable() && nAbsTabWidth > nAbsAvail ) + nAbsTabWidth = nAbsAvail; + } + } + + OSL_ENSURE( IsTopTable() || nAbsTabWidth<=nAbsAvail, + "AutoLayout pass 2: nAbsTabWidth > nAbsAvail for table in table" ); + OSL_ENSURE( !nRelAvail || nAbsTabWidth<=nAbsAvail, + "AutoLayout pass 2: nAbsTabWidth > nAbsAvail for relative width" ); + + // Catch for the two asserts above (we never know!) + if( (!IsTopTable() || nRelAvail>0) && nAbsTabWidth>nAbsAvail ) + nAbsTabWidth = nAbsAvail; + + // Step 3: Identify the column width and, if applicable, the absolute + // and relative table widths. + if( (!IsTopTable() && m_nMin > static_cast<sal_uLong>(nAbsAvail)) || + m_nMin > MAX_TABWIDTH ) + { + // If + // - an inner table's minimum is larger than the available space, or + // - a top table's minimum is larger than USHORT_MAX the table + // has to be adapted to the available space or USHORT_MAX. + // We preserve the widths' ratio amongst themselves, however. + + nAbsTabWidth = IsTopTable() ? MAX_TABWIDTH : nAbsAvail; + m_nRelTabWidth = (nRelAvail ? nRelAvail : nAbsTabWidth ); + + // First of all, we check whether we can fit the layout constrains, + // which are: Every cell's width excluding the borders must be at least + // MINLAY: + + sal_uLong nRealMin = 0; + for( sal_uInt16 i=0; i<m_nCols; i++ ) + { + sal_uLong nRealColMin = MINLAY, nDummy1 = 0, nDummy2 = 0; + AddBorderWidth( nRealColMin, nDummy1, nDummy2, i, 1 ); + nRealMin += nRealColMin; + } + if( (nRealMin >= nAbsTabWidth) || (nRealMin >= m_nMin) ) + { + // "Rien ne va plus": we cannot get the minimum column widths + // the layout wants to have. + + sal_uInt16 nAbs = 0, nRel = 0; + SwHTMLTableLayoutColumn *pColumn; + for( sal_uInt16 i=0; i<m_nCols-1; i++ ) + { + pColumn = GetColumn( i ); + sal_uLong nColMin = pColumn->GetMin(); + if( nColMin <= USHRT_MAX ) + { + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((nColMin * nAbsTabWidth) / m_nMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((nColMin * m_nRelTabWidth) / m_nMin) ); + } + else + { + double nColMinD = nColMin; + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((nColMinD * nAbsTabWidth) / m_nMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((nColMinD * m_nRelTabWidth) / m_nMin) ); + } + + nAbs = nAbs + pColumn->GetAbsColWidth(); + nRel = nRel + pColumn->GetRelColWidth(); + } + pColumn = GetColumn( m_nCols-1 ); + pColumn->SetAbsColWidth( nAbsTabWidth - nAbs ); + pColumn->SetRelColWidth( m_nRelTabWidth - nRel ); + } + else + { + sal_uLong nDistAbs = nAbsTabWidth - nRealMin; + sal_uLong nDistRel = m_nRelTabWidth - nRealMin; + sal_uLong nDistMin = m_nMin - nRealMin; + sal_uInt16 nAbs = 0, nRel = 0; + SwHTMLTableLayoutColumn *pColumn; + for( sal_uInt16 i=0; i<m_nCols-1; i++ ) + { + pColumn = GetColumn( i ); + sal_uLong nColMin = pColumn->GetMin(); + sal_uLong nRealColMin = MINLAY, nDummy1 = 0, nDummy2 = 0; + AddBorderWidth( nRealColMin, nDummy1, nDummy2, i, 1 ); + + if( nColMin <= USHRT_MAX ) + { + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((((nColMin-nRealColMin) * nDistAbs) / nDistMin) + nRealColMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((((nColMin-nRealColMin) * nDistRel) / nDistMin) + nRealColMin) ); + } + else + { + double nColMinD = nColMin; + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((((nColMinD-nRealColMin) * nDistAbs) / nDistMin) + nRealColMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((((nColMinD-nRealColMin) * nDistRel) / nDistMin) + nRealColMin) ); + } + + nAbs = nAbs + pColumn->GetAbsColWidth(); + nRel = nRel + pColumn->GetRelColWidth(); + } + pColumn = GetColumn( m_nCols-1 ); + pColumn->SetAbsColWidth( nAbsTabWidth - nAbs ); + pColumn->SetRelColWidth( m_nRelTabWidth - nRel ); + } + } + else if( m_nMax <= static_cast<sal_uLong>(nAbsTabWidth ? nAbsTabWidth : nAbsAvail) ) + { + // If + // - the table has a fixed width and the table's maximum is + // smaller, or + //- the maximum is smaller than the available space, + // we can take over the maximum as it is. Respectively + // the table can only be adapted to the fixed width by + // respecting the maximum. + + // No fixed width, use the maximum. + if( !nAbsTabWidth ) + nAbsTabWidth = static_cast<sal_uInt16>(m_nMax); + + // A top table may also get wider then the available space. + if( nAbsTabWidth > nAbsAvail ) + { + OSL_ENSURE( IsTopTable(), + "Table in table should get wider than the surrounding cell." ); + nAbsAvail = nAbsTabWidth; + } + + // Only use the relative widths' fraction, that is used for the + // absolute width. + sal_uLong nAbsTabWidthL = nAbsTabWidth; + if (nRelAvail) + { + if (nAbsAvail == 0) + throw o3tl::divide_by_zero(); + m_nRelTabWidth = static_cast<sal_uInt16>((nAbsTabWidthL * nRelAvail) / nAbsAvail); + } + else + m_nRelTabWidth = nAbsTabWidth; + + // Are there columns width a percentage setting and some without one? + sal_uLong nFixMax = m_nMax; + for( sal_uInt16 i=0; i<m_nCols; i++ ) + { + const SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption()>0 ) + nFixMax -= pColumn->GetMax(); + } + + if( nFixMax > 0 && nFixMax < m_nMax ) + { + // Yes, distribute the to-be-distributed space only to the + // columns with a percentage setting. + + // In this case (and in this case only) there are columns + // that exactly keep their maximum width, that is they neither + // get smaller nor wider. When calculating the absolute width + // from the relative width we can get rounding errors. + // To correct this, we first make the fixed widths compensate for + // this error. We then fix the relative widths the same way. + + sal_uInt16 nAbs = 0, nRel = 0; + sal_uInt16 nFixedCols = 0; + sal_uInt16 i; + + for( i = 0; i < m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( !pColumn->IsRelWidthOption() || !pColumn->GetWidthOption() ) + { + // The column keeps its width. + nFixedCols++; + sal_uLong nColMax = pColumn->GetMax(); + pColumn->SetAbsColWidth( static_cast<sal_uInt16>(nColMax) ); + + sal_uLong nRelColWidth = + (nColMax * m_nRelTabWidth) / nAbsTabWidth; + sal_uLong nChkWidth = + (nRelColWidth * nAbsTabWidth) / m_nRelTabWidth; + if( nChkWidth < nColMax ) + nRelColWidth++; + else if( nChkWidth > nColMax ) + nRelColWidth--; + pColumn->SetRelColWidth( static_cast<sal_uInt16>(nRelColWidth) ); + + nAbs = nAbs + static_cast<sal_uInt16>(nColMax); + nRel = nRel + static_cast<sal_uInt16>(nRelColWidth); + } + } + + // The to-be-distributed percentage of the maximum, the + // relative and absolute widths. Here, nFixMax corresponds + // to nAbs, so that we could've called it nAbs. + // The code is, however, more readable like that. + OSL_ENSURE( nFixMax == nAbs, "Two loops, two sums?" ); + sal_uLong nDistMax = m_nMax - nFixMax; + sal_uInt16 nDistAbsTabWidth = nAbsTabWidth - nAbs; + sal_uInt16 nDistRelTabWidth = m_nRelTabWidth - nRel; + + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() > 0 ) + { + // The column gets proportionately wider. + nFixedCols++; + if( nFixedCols == m_nCols ) + { + pColumn->SetAbsColWidth( nAbsTabWidth-nAbs ); + pColumn->SetRelColWidth( m_nRelTabWidth-nRel ); + } + else + { + sal_uLong nColMax = pColumn->GetMax(); + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((nColMax * nDistAbsTabWidth) / nDistMax) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((nColMax * nDistRelTabWidth) / nDistMax) ); + } + nAbs = nAbs + pColumn->GetAbsColWidth(); + nRel = nRel + pColumn->GetRelColWidth(); + } + } + OSL_ENSURE( m_nCols==nFixedCols, "Missed a column!" ); + } + else if (m_nCols > 0) + { + if (m_nMax == 0) + throw o3tl::divide_by_zero(); + // No. So distribute the space regularly among all columns. + for (sal_uInt16 i=0; i < m_nCols; ++i) + { + sal_uLong nColMax = GetColumn( i )->GetMax(); + GetColumn( i )->SetAbsColWidth( + static_cast<sal_uInt16>((nColMax * nAbsTabWidth) / m_nMax) ); + GetColumn( i )->SetRelColWidth( + static_cast<sal_uInt16>((nColMax * m_nRelTabWidth) / m_nMax) ); + } + } + } + else + { + // Proportionately distribute the space that extends over the minimum + // width among the columns. + if( !nAbsTabWidth ) + nAbsTabWidth = nAbsAvail; + if( nAbsTabWidth < m_nMin ) + nAbsTabWidth = static_cast<sal_uInt16>(m_nMin); + + if( nAbsTabWidth > nAbsAvail ) + { + OSL_ENSURE( IsTopTable(), + "A nested table should become wider than the available space." ); + nAbsAvail = nAbsTabWidth; + } + + sal_uLong nAbsTabWidthL = nAbsTabWidth; + if (nRelAvail) + { + if (nAbsAvail == 0) + throw o3tl::divide_by_zero(); + m_nRelTabWidth = static_cast<sal_uInt16>((nAbsTabWidthL * nRelAvail) / nAbsAvail); + } + else + m_nRelTabWidth = nAbsTabWidth; + double nW = nAbsTabWidth - m_nMin; + double nD = (m_nMax==m_nMin ? 1 : m_nMax-m_nMin); + sal_uInt16 nAbs = 0, nRel = 0; + for( sal_uInt16 i=0; i<m_nCols-1; i++ ) + { + double nd = GetColumn( i )->GetMax() - GetColumn( i )->GetMin(); + sal_uLong nAbsColWidth = GetColumn( i )->GetMin() + static_cast<sal_uLong>((nd*nW)/nD); + sal_uLong nRelColWidth = nRelAvail + ? (nAbsColWidth * m_nRelTabWidth) / nAbsTabWidth + : nAbsColWidth; + + GetColumn( i )->SetAbsColWidth( static_cast<sal_uInt16>(nAbsColWidth) ); + GetColumn( i )->SetRelColWidth( static_cast<sal_uInt16>(nRelColWidth) ); + nAbs = nAbs + static_cast<sal_uInt16>(nAbsColWidth); + nRel = nRel + static_cast<sal_uInt16>(nRelColWidth); + } + GetColumn( m_nCols-1 )->SetAbsColWidth( nAbsTabWidth - nAbs ); + GetColumn( m_nCols-1 )->SetRelColWidth( m_nRelTabWidth - nRel ); + + } + + // Step 4: For nested tables we can have balancing cells on the + // left or right. Here we calculate their width. + m_nInhAbsLeftSpace = 0; + m_nInhAbsRightSpace = 0; + if( !IsTopTable() && (m_nRelLeftFill>0 || m_nRelRightFill>0 || + nAbsTabWidth<nAbsAvail) ) + { + // Calculate the width of additional cells we use for + // aligning inner tables. + sal_uInt16 nAbsDist = static_cast<sal_uInt16>(nAbsAvail-nAbsTabWidth); + sal_uInt16 nRelDist = static_cast<sal_uInt16>(nRelAvail-m_nRelTabWidth); + sal_uInt16 nParentInhAbsLeftSpace = 0, nParentInhAbsRightSpace = 0; + + // Calculate the size and position of the additional cells. + switch( m_eTableAdjust ) + { + case SvxAdjust::Right: + nAbsLeftFill = nAbsLeftFill + nAbsDist; + m_nRelLeftFill = m_nRelLeftFill + nRelDist; + nParentInhAbsLeftSpace = nParentInhAbsSpace; + break; + case SvxAdjust::Center: + { + sal_uInt16 nAbsLeftDist = nAbsDist / 2; + nAbsLeftFill = nAbsLeftFill + nAbsLeftDist; + nAbsRightFill += nAbsDist - nAbsLeftDist; + sal_uInt16 nRelLeftDist = nRelDist / 2; + m_nRelLeftFill = m_nRelLeftFill + nRelLeftDist; + m_nRelRightFill += nRelDist - nRelLeftDist; + nParentInhAbsLeftSpace = nParentInhAbsSpace / 2; + nParentInhAbsRightSpace = nParentInhAbsSpace - + nParentInhAbsLeftSpace; + } + break; + case SvxAdjust::Left: + default: + nAbsRightFill = nAbsRightFill + nAbsDist; + m_nRelRightFill = m_nRelRightFill + nRelDist; + nParentInhAbsRightSpace = nParentInhAbsSpace; + break; + } + + // Filler widths are added to the outer columns, if there are no boxes + // for them after the first pass (nWidth>0) or their width would become + // too small or if there are COL tags and the filler width corresponds + // to the border width. + // In the last case we probably exported the table ourselves. + if( m_nRelLeftFill && + ( m_nWidthSet>0 || nAbsLeftFill<MINLAY+m_nInhLeftBorderWidth || + (HasColTags() && nAbsLeftFill < nAbsLeftSpace+nParentInhAbsLeftSpace+20) ) ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( 0 ); + pColumn->SetAbsColWidth( pColumn->GetAbsColWidth()+nAbsLeftFill ); + pColumn->SetRelColWidth( pColumn->GetRelColWidth()+m_nRelLeftFill ); + m_nRelLeftFill = 0; + m_nInhAbsLeftSpace = nAbsLeftSpace + nParentInhAbsLeftSpace; + } + if( m_nRelRightFill && + ( m_nWidthSet>0 || nAbsRightFill<MINLAY+m_nInhRightBorderWidth || + (HasColTags() && nAbsRightFill < nAbsRightSpace+nParentInhAbsRightSpace+20) ) ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( m_nCols-1 ); + pColumn->SetAbsColWidth( pColumn->GetAbsColWidth()+nAbsRightFill ); + pColumn->SetRelColWidth( pColumn->GetRelColWidth()+m_nRelRightFill ); + m_nRelRightFill = 0; + m_nInhAbsRightSpace = nAbsRightSpace + nParentInhAbsRightSpace; + } + } +} + +static void lcl_ResizeLine( const SwTableLine* pLine, SwTwips *pWidth ); + +static void lcl_ResizeBox( const SwTableBox* pBox, SwTwips* pWidth ) +{ + if( !pBox->GetSttNd() ) + { + SwTwips nWidth = 0; + for( const SwTableLine *pLine : pBox->GetTabLines() ) + lcl_ResizeLine( pLine, &nWidth ); + pBox->GetFrameFormat()->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth, 0 )); + *pWidth = *pWidth + nWidth; + } + else + { + *pWidth = *pWidth + pBox->GetFrameFormat()->GetFrameSize().GetSize().Width(); + } +} + +static void lcl_ResizeLine( const SwTableLine* pLine, SwTwips *pWidth ) +{ + SwTwips nOldWidth = *pWidth; + *pWidth = 0; + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + lcl_ResizeBox(pBox, pWidth ); + + SAL_WARN_IF( nOldWidth && std::abs(*pWidth-nOldWidth) >= COLFUZZY, "sw.core", + "A box's rows have all a different length" ); +} + +void SwHTMLTableLayout::SetWidths( bool bCallPass2, sal_uInt16 nAbsAvail, + sal_uInt16 nRelAvail, sal_uInt16 nAbsLeftSpace, + sal_uInt16 nAbsRightSpace, + sal_uInt16 nParentInhAbsSpace ) +{ + // SetWidth must have been passed through once more for every cell in the + // end. + m_nWidthSet++; + + // Step 0: If necessary, we call the layout algorithm of Pass2. + if( bCallPass2 ) + AutoLayoutPass2( nAbsAvail, nRelAvail, nAbsLeftSpace, nAbsRightSpace, + nParentInhAbsSpace ); + + // Step 1: Set the new width in all content boxes. + // Because the boxes don't know anything about the HTML table structure, + // we iterate over the HTML table structure. + // For tables in tables in tables we call SetWidth recursively. + for( sal_uInt16 i=0; i<m_nRows; i++ ) + { + for( sal_uInt16 j=0; j<m_nCols; j++ ) + { + SwHTMLTableLayoutCell *pCell = GetCell( i, j ); + + SwHTMLTableLayoutCnts* pContents = pCell->GetContents().get(); + while( pContents && !pContents->IsWidthSet(m_nWidthSet) ) + { + SwTableBox *pBox = pContents->GetTableBox(); + if( pBox ) + { + SetBoxWidth( pBox, j, pCell->GetColSpan() ); + } + else if (SwHTMLTableLayout *pTable = pContents->GetTable()) + { + sal_uInt16 nAbs = 0, nRel = 0, nLSpace = 0, nRSpace = 0, + nInhSpace = 0; + if( bCallPass2 ) + { + sal_uInt16 nColSpan = pCell->GetColSpan(); + GetAvail( j, nColSpan, nAbs, nRel ); + nLSpace = GetLeftCellSpace( j, nColSpan ); + nRSpace = GetRightCellSpace( j, nColSpan ); + nInhSpace = GetInhCellSpace( j, nColSpan ); + } + pTable->SetWidths( bCallPass2, nAbs, nRel, + nLSpace, nRSpace, + nInhSpace ); + } + + pContents->SetWidthSet( m_nWidthSet ); + pContents = pContents->GetNext().get(); + } + } + } + + // Step 2: If we have a top table, we adapt the formats of the + // non-content-boxes. Because they are not known in the HTML table + // due to garbage collection there, we need the iterate over the + // whole table. + // We also adapt the table frame format. For nested tables we set the + // filler cell's width instead. + if( IsTopTable() ) + { + SwTwips nCalcTabWidth = 0; + for( const SwTableLine *pLine : m_pSwTable->GetTabLines() ) + lcl_ResizeLine( pLine, &nCalcTabWidth ); + SAL_WARN_IF( std::abs( m_nRelTabWidth-nCalcTabWidth ) >= COLFUZZY, "sw.core", + "Table width is not equal to the row width" ); + + // Lock the table format when altering it, or else the box formats + // are altered again. + // Also, we need to preserve a percent setting if it exists. + SwFrameFormat *pFrameFormat = m_pSwTable->GetFrameFormat(); + const_cast<SwTable *>(m_pSwTable)->LockModify(); + SwFormatFrameSize aFrameSize( pFrameFormat->GetFrameSize() ); + aFrameSize.SetWidth( m_nRelTabWidth ); + bool bRel = m_bUseRelWidth && + text::HoriOrientation::FULL!=pFrameFormat->GetHoriOrient().GetHoriOrient(); + aFrameSize.SetWidthPercent( static_cast<sal_uInt8>(bRel ? m_nWidthOption : 0) ); + pFrameFormat->SetFormatAttr( aFrameSize ); + const_cast<SwTable *>(m_pSwTable)->UnlockModify(); + + // If the table is located in a frame, we also need to adapt the + // frame's width. + if( MayBeInFlyFrame() ) + { + SwFrameFormat *pFlyFrameFormat = FindFlyFrameFormat(); + if( pFlyFrameFormat ) + { + SwFormatFrameSize aFlyFrameSize( SwFrameSize::Variable, m_nRelTabWidth, MINLAY ); + + if( m_bUseRelWidth ) + { + // For percentage settings we set the width to the minimum. + aFlyFrameSize.SetWidth( m_nMin > USHRT_MAX ? USHRT_MAX + : m_nMin ); + aFlyFrameSize.SetWidthPercent( static_cast<sal_uInt8>(m_nWidthOption) ); + } + pFlyFrameFormat->SetFormatAttr( aFlyFrameSize ); + } + } + +#ifdef DBG_UTIL + { + // check if the tables have correct widths + SwTwips nSize = m_pSwTable->GetFrameFormat()->GetFrameSize().GetWidth(); + const SwTableLines& rLines = m_pSwTable->GetTabLines(); + for (size_t n = 0; n < rLines.size(); ++n) + { + CheckBoxWidth( *rLines[ n ], nSize ); + } + } +#endif + + } +} + +void SwHTMLTableLayout::Resize_( sal_uInt16 nAbsAvail, bool bRecalc ) +{ + // If bRecalc is set, the table's content changed. + // We need to execute pass 1 again. + if( bRecalc ) + AutoLayoutPass1(); + + SwRootFrame *pRoot = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()->GetLayout(); + if ( pRoot && pRoot->IsCallbackActionEnabled() ) + pRoot->StartAllAction(); + + // Else we can set the widths, in which we have to run Pass 2 in each case. + SetWidths( true, nAbsAvail ); + + if ( pRoot && pRoot->IsCallbackActionEnabled() ) + pRoot->EndAllAction( true ); //True per VirDev (browsing is calmer) +} + +IMPL_LINK_NOARG( SwHTMLTableLayout, DelayedResize_Impl, Timer*, void ) +{ + m_aResizeTimer.Stop(); + Resize_( m_nDelayedResizeAbsAvail, m_bDelayedResizeRecalc ); +} + +bool SwHTMLTableLayout::Resize( sal_uInt16 nAbsAvail, bool bRecalc, + bool bForce, sal_uLong nDelay ) +{ + if( 0 == nAbsAvail ) + return false; + OSL_ENSURE( IsTopTable(), "Resize must only be called for top tables!" ); + + // May the table be resized at all? Or is it forced? + if( m_bMustNotResize && !bForce ) + return false; + + // May the table be recalculated? Or is it forced? + if( m_bMustNotRecalc && !bForce ) + bRecalc = false; + + const SwDoc *pDoc = GetDoc(); + + // If there is a layout, the root frame's size instead of the + // VisArea's size was potentially passed. + // If we're not in a frame we need to calculate the table for the VisArea, + // because switching from relative to absolute wouldn't work. + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && pDoc->getIDocumentLayoutAccess().GetCurrentViewShell()->GetViewOptions()->getBrowseMode() ) + { + const sal_uInt16 nVisAreaWidth = GetBrowseWidthByVisArea( *pDoc ); + if( nVisAreaWidth < nAbsAvail && !FindFlyFrameFormat() ) + nAbsAvail = nVisAreaWidth; + } + + if( nDelay==0 && m_aResizeTimer.IsActive() ) + { + m_nDelayedResizeAbsAvail = nAbsAvail; + return false; + } + + // Optimisation: + // If the minimum or maximum should not be recalculated and + // - the table's width never needs to be recalculated, or + // - the table was already calculated for the passed width, or + // - the available space is less or equal to the minimum width + // and the table already has the minimum width, or + // - the available space is larger than the maximum width and + // the table already has the maximum width + // nothing will happen to the table. + if( !bRecalc && ( !m_bMustResize || + (m_nLastResizeAbsAvail==nAbsAvail) || + (nAbsAvail<=m_nMin && m_nRelTabWidth==m_nMin) || + (!m_bPercentWidthOption && nAbsAvail>=m_nMax && m_nRelTabWidth==m_nMax) ) ) + return false; + + if( nDelay==HTMLTABLE_RESIZE_NOW ) + { + if( m_aResizeTimer.IsActive() ) + m_aResizeTimer.Stop(); + Resize_( nAbsAvail, bRecalc ); + } + else if( nDelay > 0 ) + { + m_nDelayedResizeAbsAvail = nAbsAvail; + m_bDelayedResizeRecalc = bRecalc; + m_aResizeTimer.SetTimeout( nDelay ); + m_aResizeTimer.Start(); + } + else + { + Resize_( nAbsAvail, bRecalc ); + } + + return true; +} + +void SwHTMLTableLayout::BordersChanged( sal_uInt16 nAbsAvail ) +{ + m_bBordersChanged = true; + + Resize( nAbsAvail, true/*bRecalc*/ ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/lineinfo.cxx b/sw/source/core/doc/lineinfo.cxx new file mode 100644 index 000000000..9922e3aa1 --- /dev/null +++ b/sw/source/core/doc/lineinfo.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentState.hxx> +#include <lineinfo.hxx> +#include <charfmt.hxx> +#include <poolfmt.hxx> +#include <rootfrm.hxx> +#include <set> + +void SwDoc::SetLineNumberInfo( const SwLineNumberInfo &rNew ) +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if ( pTmpRoot && + (rNew.IsCountBlankLines() != mpLineNumberInfo->IsCountBlankLines() || + rNew.IsRestartEachPage() != mpLineNumberInfo->IsRestartEachPage()) ) + { + pTmpRoot->StartAllAction(); + // FME 2007-08-14 #i80120# Invalidate size, because ChgThisLines() + // is only (and may only be) called by the formatting routines + //pTmpRoot->InvalidateAllContent( SwInvalidateFlags::LineNum | SwInvalidateFlags::Size ); + for( auto aLayout : GetAllLayouts() ) + aLayout->InvalidateAllContent( SwInvalidateFlags::LineNum | SwInvalidateFlags::Size ); + pTmpRoot->EndAllAction(); + } + *mpLineNumberInfo = rNew; + getIDocumentState().SetModified(); +} + +const SwLineNumberInfo& SwDoc::GetLineNumberInfo() const +{ + return *mpLineNumberInfo; +} + +SwLineNumberInfo::SwLineNumberInfo() : + m_nPosFromLeft( MM50 ), + m_nCountBy( 5 ), + m_nDividerCountBy( 3 ), + m_ePos( LINENUMBER_POS_LEFT ), + m_bPaintLineNumbers( false ), + m_bCountBlankLines( true ), + m_bCountInFlys( false ), + m_bRestartEachPage( false ) +{ +} + +SwLineNumberInfo::SwLineNumberInfo(const SwLineNumberInfo &rCpy ) : SwClient(), + m_aType( rCpy.GetNumType() ), + m_aDivider( rCpy.GetDivider() ), + m_nPosFromLeft( rCpy.GetPosFromLeft() ), + m_nCountBy( rCpy.GetCountBy() ), + m_nDividerCountBy( rCpy.GetDividerCountBy() ), + m_ePos( rCpy.GetPos() ), + m_bPaintLineNumbers( rCpy.IsPaintLineNumbers() ), + m_bCountBlankLines( rCpy.IsCountBlankLines() ), + m_bCountInFlys( rCpy.IsCountInFlys() ), + m_bRestartEachPage( rCpy.IsRestartEachPage() ) +{ + StartListeningToSameModifyAs(rCpy); +} + +SwLineNumberInfo& SwLineNumberInfo::operator=(const SwLineNumberInfo &rCpy) +{ + StartListeningToSameModifyAs(rCpy); + + m_aType = rCpy.GetNumType(); + m_aDivider = rCpy.GetDivider(); + m_nPosFromLeft = rCpy.GetPosFromLeft(); + m_nCountBy = rCpy.GetCountBy(); + m_nDividerCountBy = rCpy.GetDividerCountBy(); + m_ePos = rCpy.GetPos(); + m_bPaintLineNumbers = rCpy.IsPaintLineNumbers(); + m_bCountBlankLines = rCpy.IsCountBlankLines(); + m_bCountInFlys = rCpy.IsCountInFlys(); + m_bRestartEachPage = rCpy.IsRestartEachPage(); + + return *this; +} + +SwCharFormat* SwLineNumberInfo::GetCharFormat( IDocumentStylePoolAccess& rIDSPA ) const +{ + if ( !GetRegisteredIn() ) + { + SwCharFormat* pFormat = rIDSPA.GetCharFormatFromPool( RES_POOLCHR_LINENUM ); + pFormat->Add( const_cast<SwLineNumberInfo*>(this) ); + } + return const_cast<SwCharFormat*>(static_cast<const SwCharFormat*>(GetRegisteredIn())); +} + +void SwLineNumberInfo::SetCharFormat( SwCharFormat *pChFormat ) +{ + OSL_ENSURE( pChFormat, "SetCharFormat, 0 is not a valid pointer" ); + pChFormat->Add( this ); +} + +void SwLineNumberInfo::Modify( const SfxPoolItem* pOld, const SfxPoolItem* /*pNew*/ ) +{ + CheckRegistration( pOld ); + SwDoc *pDoc = static_cast<SwCharFormat*>(GetRegisteredIn())->GetDoc(); + SwRootFrame* pRoot = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if( pRoot ) + { + pRoot->StartAllAction(); + for( auto aLayout : pDoc->GetAllLayouts() ) + aLayout->AllAddPaintRect(); + pRoot->EndAllAction(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/list.cxx b/sw/source/core/doc/list.cxx new file mode 100644 index 000000000..a87570131 --- /dev/null +++ b/sw/source/core/doc/list.cxx @@ -0,0 +1,274 @@ +/* -*- 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 <list.hxx> + +#include <vector> +#include <numrule.hxx> +#include <ndarr.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <SwNodeNum.hxx> + +// implementation class for SwList +class SwListImpl +{ + public: + SwListImpl( const OUString& sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ); + ~SwListImpl() COVERITY_NOEXCEPT_FALSE; + + const OUString& GetListId() const { return msListId;} + + const OUString& GetDefaultListStyleName() const { return msDefaultListStyleName;} + + void InsertListItem( SwNodeNum& rNodeNum, bool isHiddenRedlines, + const int nLevel ); + static void RemoveListItem( SwNodeNum& rNodeNum ); + + void InvalidateListTree(); + void ValidateListTree(); + + void MarkListLevel( const int nListLevel, + const bool bValue ); + + bool IsListLevelMarked( const int nListLevel ) const; + + // unique identifier of the list + const OUString msListId; + // default list style for the list items, identified by the list style name + OUString msDefaultListStyleName; + + // list trees for certain document ranges + struct tListTreeForRange + { + /// tree always corresponds to document model + std::unique_ptr<SwNodeNum> pRoot; + /// Tree that is missing those nodes that are merged or hidden + /// by delete redlines; this is only used if there is a layout + /// that has IsHideRedlines() enabled. + /// A second tree is needed because not only are the numbers in + /// the nodes different, the structure of the tree may be different + /// as well, if a high-level node is hidden its children go under + /// the previous node on the same level. + /// The nodes of pRootRLHidden are a subset of the nodes of pRoot. + std::unique_ptr<SwNodeNum> pRootRLHidden; + /// top-level SwNodes section + std::unique_ptr<SwPaM> pSection; + tListTreeForRange(std::unique_ptr<SwNodeNum> p1, std::unique_ptr<SwNodeNum> p2, std::unique_ptr<SwPaM> p3) + : pRoot(std::move(p1)), pRootRLHidden(std::move(p2)), pSection(std::move(p3)) {} + }; + std::vector<tListTreeForRange> maListTrees; + + int mnMarkedListLevel; + + void NotifyItemsOnListLevel( const int nLevel ); +}; + +SwListImpl::SwListImpl( const OUString& sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ) + : msListId( sListId ), + msDefaultListStyleName( rDefaultListStyle.GetName() ), + maListTrees(), + mnMarkedListLevel( MAXLEVEL ) +{ + // create empty list trees for the document ranges + const SwNode* pNode = rNodes[0]; + do + { + SwPaM aPam( *pNode, *pNode->EndOfSectionNode() ); + + maListTrees.emplace_back( + std::make_unique<SwNodeNum>( &rDefaultListStyle ), + std::make_unique<SwNodeNum>( &rDefaultListStyle ), + std::make_unique<SwPaM>( *(aPam.Start()), *(aPam.End()) )); + + pNode = pNode->EndOfSectionNode(); + if (pNode != &rNodes.GetEndOfContent()) + { + sal_uLong nIndex = pNode->GetIndex(); + nIndex++; + pNode = rNodes[nIndex]; + } + } + while ( pNode != &rNodes.GetEndOfContent() ); +} + +SwListImpl::~SwListImpl() COVERITY_NOEXCEPT_FALSE +{ + for ( auto& rNumberTree : maListTrees ) + { + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRoot)); + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRootRLHidden)); + } +} + +void SwListImpl::InsertListItem( SwNodeNum& rNodeNum, bool const isHiddenRedlines, + const int nLevel ) +{ + const SwPosition aPosOfNodeNum( rNodeNum.GetPosition() ); + const SwNodes* pNodesOfNodeNum = &(aPosOfNodeNum.nNode.GetNode().GetNodes()); + + for ( const auto& rNumberTree : maListTrees ) + { + const SwPosition* pStart = rNumberTree.pSection->Start(); + const SwPosition* pEnd = rNumberTree.pSection->End(); + const SwNodes* pRangeNodes = &(pStart->nNode.GetNode().GetNodes()); + + if ( pRangeNodes == pNodesOfNodeNum && + *pStart <= aPosOfNodeNum && aPosOfNodeNum <= *pEnd) + { + auto const& pRoot(isHiddenRedlines + ? rNumberTree.pRootRLHidden + : rNumberTree.pRoot); + pRoot->AddChild(&rNodeNum, nLevel); + break; + } + } +} + +void SwListImpl::RemoveListItem( SwNodeNum& rNodeNum ) +{ + rNodeNum.RemoveMe(); +} + +void SwListImpl::InvalidateListTree() +{ + for ( const auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->InvalidateTree(); + rNumberTree.pRootRLHidden->InvalidateTree(); + } +} + +void SwListImpl::ValidateListTree() +{ + for ( auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->NotifyInvalidChildren(); + rNumberTree.pRootRLHidden->NotifyInvalidChildren(); + } +} + +void SwListImpl::MarkListLevel( const int nListLevel, + const bool bValue ) +{ + if ( bValue ) + { + if ( nListLevel != mnMarkedListLevel ) + { + if ( mnMarkedListLevel != MAXLEVEL ) + { + // notify former marked list nodes + NotifyItemsOnListLevel( mnMarkedListLevel ); + } + + mnMarkedListLevel = nListLevel; + + // notify new marked list nodes + NotifyItemsOnListLevel( mnMarkedListLevel ); + } + } + else + { + if ( mnMarkedListLevel != MAXLEVEL ) + { + // notify former marked list nodes + NotifyItemsOnListLevel( mnMarkedListLevel ); + } + + mnMarkedListLevel = MAXLEVEL; + } +} + +bool SwListImpl::IsListLevelMarked( const int nListLevel ) const +{ + return nListLevel == mnMarkedListLevel; +} + +void SwListImpl::NotifyItemsOnListLevel( const int nLevel ) +{ + for ( auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->NotifyNodesOnListLevel( nLevel ); + rNumberTree.pRootRLHidden->NotifyNodesOnListLevel( nLevel ); + } +} + +SwList::SwList( const OUString& sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ) + : mpListImpl( new SwListImpl( sListId, rDefaultListStyle, rNodes ) ) +{ +} + +SwList::~SwList() +{ +} + +const OUString & SwList::GetListId() const +{ + return mpListImpl->GetListId(); +} + +const OUString & SwList::GetDefaultListStyleName() const +{ + return mpListImpl->GetDefaultListStyleName(); +} + +void SwList::SetDefaultListStyleName(OUString const& rNew) +{ + mpListImpl->msDefaultListStyleName = rNew; +} + +void SwList::InsertListItem( SwNodeNum& rNodeNum, bool const isHiddenRedlines, + const int nLevel ) +{ + mpListImpl->InsertListItem( rNodeNum, isHiddenRedlines, nLevel ); +} + +void SwList::RemoveListItem( SwNodeNum& rNodeNum ) +{ + SwListImpl::RemoveListItem( rNodeNum ); +} + +void SwList::InvalidateListTree() +{ + mpListImpl->InvalidateListTree(); +} + +void SwList::ValidateListTree() +{ + mpListImpl->ValidateListTree(); +} + +void SwList::MarkListLevel( const int nListLevel, + const bool bValue ) +{ + mpListImpl->MarkListLevel( nListLevel, bValue ); +} + +bool SwList::IsListLevelMarked( const int nListLevel ) const +{ + return mpListImpl->IsListLevelMarked( nListLevel ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/notxtfrm.cxx b/sw/source/core/doc/notxtfrm.cxx new file mode 100644 index 000000000..550f0df50 --- /dev/null +++ b/sw/source/core/doc/notxtfrm.cxx @@ -0,0 +1,1549 @@ +/* -*- 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 <hintids.hxx> +#include <tools/urlobj.hxx> +#include <vcl/imapobj.hxx> +#include <vcl/imap.hxx> +#include <svl/urihelper.hxx> +#include <sfx2/progress.hxx> +#include <sfx2/printer.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/boxitem.hxx> +#include <fmturl.hxx> +#include <fmtsrnd.hxx> +#include <frmfmt.hxx> +#include <swrect.hxx> +#include <fesh.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <flyfrm.hxx> +#include <flyfrms.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <viewimp.hxx> +#include <pam.hxx> +#include <hints.hxx> +#include <rootfrm.hxx> +#include <dflyobj.hxx> +#include <pagefrm.hxx> +#include <notxtfrm.hxx> +#include <grfatr.hxx> +#include <charatr.hxx> +#include <ndnotxt.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <swregion.hxx> +#include <poolfmt.hxx> +#include <mdiexp.hxx> +#include <strings.hrc> +#include <accessibilityoptions.hxx> +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <svtools/embedhlp.hxx> +#include <dview.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/b2dclipstate.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <txtfly.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> + +// MM02 needed for VOC mechanism and getting the OC - may be moved to an own file +#include <svx/sdrpagewindow.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/sdr/contact/displayinfo.hxx> + +using namespace com::sun::star; + +static bool GetRealURL( const SwGrfNode& rNd, OUString& rText ) +{ + bool bRet = rNd.GetFileFilterNms( &rText, nullptr ); + if( bRet ) + rText = URIHelper::removePassword( rText, INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::Unambiguous); + if (rText.startsWith("data:image")) rText = "inline image"; + + return bRet; +} + +static void lcl_PaintReplacement( const SwRect &rRect, const OUString &rText, + const SwViewShell &rSh, const SwNoTextFrame *pFrame, + bool bDefect ) +{ + static vcl::Font aFont = [&]() + { + vcl::Font tmp; + tmp.SetWeight( WEIGHT_BOLD ); + tmp.SetStyleName( OUString() ); + tmp.SetFamilyName("Arial Unicode"); + tmp.SetFamily( FAMILY_SWISS ); + tmp.SetTransparent( true ); + return tmp; + }(); + + Color aCol( COL_RED ); + FontLineStyle eUnderline = LINESTYLE_NONE; + const SwFormatURL &rURL = pFrame->FindFlyFrame()->GetFormat()->GetURL(); + if( !rURL.GetURL().isEmpty() || rURL.GetMap() ) + { + bool bVisited = false; + if ( rURL.GetMap() ) + { + ImageMap *pMap = const_cast<ImageMap*>(rURL.GetMap()); + for( size_t i = 0; i < pMap->GetIMapObjectCount(); ++i ) + { + IMapObject *pObj = pMap->GetIMapObject( i ); + if( rSh.GetDoc()->IsVisitedURL( pObj->GetURL() ) ) + { + bVisited = true; + break; + } + } + } + else if ( !rURL.GetURL().isEmpty() ) + bVisited = rSh.GetDoc()->IsVisitedURL( rURL.GetURL() ); + + SwFormat *pFormat = rSh.GetDoc()->getIDocumentStylePoolAccess().GetFormatFromPool( static_cast<sal_uInt16> + (bVisited ? RES_POOLCHR_INET_VISIT : RES_POOLCHR_INET_NORMAL ) ); + aCol = pFormat->GetColor().GetValue(); + eUnderline = pFormat->GetUnderline().GetLineStyle(); + } + + aFont.SetUnderline( eUnderline ); + aFont.SetColor( aCol ); + + const BitmapEx& rBmp = const_cast<SwViewShell&>(rSh).GetReplacementBitmap(bDefect); + Graphic::DrawEx( rSh.GetOut(), rText, aFont, rBmp, rRect.Pos(), rRect.SSize() ); +} + +SwNoTextFrame::SwNoTextFrame(SwNoTextNode * const pNode, SwFrame* pSib ) +: SwContentFrame( pNode, pSib ), + // RotateFlyFrame3 + mpTransformableSwFrame(), + // MM02 + mpViewContact() +{ + mnFrameType = SwFrameType::NoTxt; +} + +SwContentFrame *SwNoTextNode::MakeFrame( SwFrame* pSib ) +{ + return new SwNoTextFrame(this, pSib); +} + +void SwNoTextFrame::DestroyImpl() +{ + StopAnimation(); + + SwContentFrame::DestroyImpl(); +} + +SwNoTextFrame::~SwNoTextFrame() +{ +} + +void SetOutDev( SwViewShell *pSh, OutputDevice *pOut ) +{ + pSh->mpOut = pOut; +} + +static void lcl_ClearArea( const SwFrame &rFrame, + vcl::RenderContext &rOut, const SwRect& rPtArea, + const SwRect &rGrfArea ) +{ + SwRegionRects aRegion( rPtArea, 4 ); + aRegion -= rGrfArea; + + if ( !aRegion.empty() ) + { + const SvxBrushItem *pItem; + const Color *pCol; + SwRect aOrigRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( rFrame.GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigRect, false, /*bConsiderTextBox=*/false ) ) + { + SwRegionRects const region(rPtArea); + basegfx::utils::B2DClipState aClipState; + const bool bDone(::DrawFillAttributes(aFillAttributes, aOrigRect, region, aClipState, rOut)); + + if(!bDone) + { + for( const auto &rRegion : aRegion ) + { + ::DrawGraphic( pItem, &rOut, aOrigRect, rRegion ); + } + } + } + else + { + rOut.Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + rOut.SetFillColor( rFrame.getRootFrame()->GetCurrShell()->Imp()->GetRetoucheColor()); + rOut.SetLineColor(); + for( const auto &rRegion : aRegion ) + rOut.DrawRect( rRegion.SVRect() ); + rOut.Pop(); + } + } +} + +void SwNoTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + if ( getFrameArea().IsEmpty() ) + return; + + const SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if( !pSh->GetViewOptions()->IsGraphic() ) + { + StopAnimation(); + // #i6467# - no paint of placeholder for page preview + if ( pSh->GetWin() && !pSh->IsPreview() ) + { + const SwNoTextNode* pNd = GetNode()->GetNoTextNode(); + OUString aText( pNd->GetTitle() ); + if ( aText.isEmpty() && pNd->IsGrfNode() ) + GetRealURL( *static_cast<const SwGrfNode*>(pNd), aText ); + if( aText.isEmpty() ) + aText = FindFlyFrame()->GetFormat()->GetName(); + lcl_PaintReplacement( getFrameArea(), aText, *pSh, this, false ); + } + return; + } + + if( pSh->GetAccessibilityOptions()->IsStopAnimatedGraphics() || + // #i9684# Stop animation during printing/pdf export + !pSh->GetWin() ) + StopAnimation(); + + SfxProgress::EnterLock(); // No progress reschedules in paint (SwapIn) + + rRenderContext.Push(); + bool bClip = true; + tools::PolyPolygon aPoly; + + SwNoTextNode& rNoTNd = const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(GetNode())); + SwGrfNode* pGrfNd = rNoTNd.GetGrfNode(); + if( pGrfNd ) + pGrfNd->SetFrameInPaint( true ); + + // #i13147# - add 2nd parameter with value <true> to + // method call <FindFlyFrame().GetContour(..)> to indicate that it is called + // for paint in order to avoid load of the intrinsic graphic. + if ( ( !rRenderContext.GetConnectMetaFile() || + !pSh->GetWin() ) && + FindFlyFrame()->GetContour( aPoly, true ) + ) + { + rRenderContext.SetClipRegion(vcl::Region(aPoly)); + bClip = false; + } + + SwRect aOrigPaint( rRect ); + if ( HasAnimation() && pSh->GetWin() ) + { + aOrigPaint = getFrameArea(); aOrigPaint += getFramePrintArea().Pos(); + } + + SwRect aGrfArea( getFrameArea() ); + SwRect aPaintArea( aGrfArea ); + + // In case the picture fly frm was clipped, render it with the origin + // size instead of scaling it + if ( pGrfNd && rNoTNd.getIDocumentSettingAccess()->get( DocumentSettingId::CLIPPED_PICTURES ) ) + { + const SwFlyFreeFrame *pFly = dynamic_cast< const SwFlyFreeFrame* >( FindFlyFrame() ); + if( pFly ) + { + bool bGetUnclippedFrame=true; + const SfxPoolItem* pItem; + if( pFly->GetFormat() && SfxItemState::SET == pFly->GetFormat()->GetItemState(RES_BOX, false, &pItem) ) + { + const SvxBoxItem& rBox = *static_cast<const SvxBoxItem*>(pItem); + if( rBox.HasBorder( /*bTreatPaddingAsBorder*/true) ) + bGetUnclippedFrame = false; + } + + if( bGetUnclippedFrame ) + aGrfArea = SwRect( getFrameArea().Pos( ), pFly->GetUnclippedFrame( ).SSize( ) ); + } + } + + aPaintArea.Intersection_( aOrigPaint ); + + SwRect aNormal( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + aNormal.Justify(); // Normalized rectangle for the comparisons + + if( aPaintArea.IsOver( aNormal ) ) + { + // Calculate the four to-be-deleted rectangles + if( pSh->GetWin() ) + ::lcl_ClearArea( *this, rRenderContext, aPaintArea, aNormal ); + + // The intersection of the PaintArea and the Bitmap contains the absolutely visible area of the Frame + aPaintArea.Intersection_( aNormal ); + + if ( bClip ) + rRenderContext.IntersectClipRegion( aPaintArea.SVRect() ); + /// delete unused 3rd parameter + PaintPicture( &rRenderContext, aGrfArea ); + } + else + // If it's not visible, simply delete the given Area + lcl_ClearArea( *this, rRenderContext, aPaintArea, SwRect() ); + if( pGrfNd ) + pGrfNd->SetFrameInPaint( false ); + + rRenderContext.Pop(); + SfxProgress::LeaveLock(); +} + +/** Calculate the position and the size of the graphic in the Frame, + corresponding to the current graphic attributes + + @param Point the position in the Frame (also returned) + @param Size the graphic's size (also returned) + @param nMirror the current mirror attribute +*/ +static void lcl_CalcRect( Point& rPt, Size& rDim, MirrorGraph nMirror ) +{ + if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both ) + { + rPt.setX(rPt.getX() + rDim.Width() -1); + rDim.setWidth( -rDim.Width() ); + } + + if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both ) + { + rPt.setY(rPt.getY() + rDim.Height() -1); + rDim.setHeight( -rDim.Height() ); + } +} + +/** Calculate the Bitmap's position and the size within the passed rectangle */ +void SwNoTextFrame::GetGrfArea( SwRect &rRect, SwRect* pOrigRect ) const +{ + // Currently only used for scaling, cropping and mirroring the contour of graphics! + // Everything else is handled by GraphicObject + // We put the graphic's visible rectangle into rRect. + // pOrigRect contains position and size of the whole graphic. + + // RotateFlyFrame3: SwFrame may be transformed. Get untransformed + // SwRect(s) as base of calculation + const TransformableSwFrame* pTransformableSwFrame(getTransformableSwFrame()); + const SwRect aFrameArea(pTransformableSwFrame ? pTransformableSwFrame->getUntransformedFrameArea() : getFrameArea()); + const SwRect aFramePrintArea(pTransformableSwFrame ? pTransformableSwFrame->getUntransformedFramePrintArea() : getFramePrintArea()); + + const SwAttrSet& rAttrSet = GetNode()->GetSwAttrSet(); + const SwCropGrf& rCrop = rAttrSet.GetCropGrf(); + MirrorGraph nMirror = rAttrSet.GetMirrorGrf().GetValue(); + + if( rAttrSet.GetMirrorGrf().IsGrfToggle() ) + { + if( !(FindPageFrame()->GetVirtPageNum() % 2) ) + { + switch ( nMirror ) + { + case MirrorGraph::Dont: nMirror = MirrorGraph::Vertical; break; + case MirrorGraph::Vertical: nMirror = MirrorGraph::Dont; break; + case MirrorGraph::Horizontal: nMirror = MirrorGraph::Both; break; + default: nMirror = MirrorGraph::Horizontal; break; + } + } + } + + // We read graphic from the Node, if needed. + // It may fail, however. + long nLeftCrop, nRightCrop, nTopCrop, nBottomCrop; + Size aOrigSz( static_cast<const SwNoTextNode*>(GetNode())->GetTwipSize() ); + if ( !aOrigSz.Width() ) + { + aOrigSz.setWidth( aFramePrintArea.Width() ); + nLeftCrop = -rCrop.GetLeft(); + nRightCrop = -rCrop.GetRight(); + } + else + { + nLeftCrop = std::max( aOrigSz.Width() - + (rCrop.GetRight() + rCrop.GetLeft()), long(1) ); + const double nScale = double(aFramePrintArea.Width()) / double(nLeftCrop); + nLeftCrop = long(nScale * -rCrop.GetLeft() ); + nRightCrop = long(nScale * -rCrop.GetRight() ); + } + + // crop values have to be mirrored too + if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both ) + { + long nTmpCrop = nLeftCrop; + nLeftCrop = nRightCrop; + nRightCrop= nTmpCrop; + } + + if( !aOrigSz.Height() ) + { + aOrigSz.setHeight( aFramePrintArea.Height() ); + nTopCrop = -rCrop.GetTop(); + nBottomCrop= -rCrop.GetBottom(); + } + else + { + nTopCrop = std::max( aOrigSz.Height() - (rCrop.GetTop() + rCrop.GetBottom()), long(1) ); + const double nScale = double(aFramePrintArea.Height()) / double(nTopCrop); + nTopCrop = long(nScale * -rCrop.GetTop() ); + nBottomCrop= long(nScale * -rCrop.GetBottom() ); + } + + // crop values have to be mirrored too + if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both ) + { + long nTmpCrop = nTopCrop; + nTopCrop = nBottomCrop; + nBottomCrop= nTmpCrop; + } + + Size aVisSz( aFramePrintArea.SSize() ); + Size aGrfSz( aVisSz ); + Point aVisPt( aFrameArea.Pos() + aFramePrintArea.Pos() ); + Point aGrfPt( aVisPt ); + + // Set the "visible" rectangle first + if ( nLeftCrop > 0 ) + { + aVisPt.setX(aVisPt.getX() + nLeftCrop); + aVisSz.AdjustWidth( -nLeftCrop ); + } + if ( nTopCrop > 0 ) + { + aVisPt.setY(aVisPt.getY() + nTopCrop); + aVisSz.AdjustHeight( -nTopCrop ); + } + if ( nRightCrop > 0 ) + aVisSz.AdjustWidth( -nRightCrop ); + if ( nBottomCrop > 0 ) + aVisSz.AdjustHeight( -nBottomCrop ); + + rRect.Pos ( aVisPt ); + rRect.SSize( aVisSz ); + + // Calculate the whole graphic if needed + if ( pOrigRect ) + { + Size aTmpSz( aGrfSz ); + aGrfPt.setX(aGrfPt.getX() + nLeftCrop); + aTmpSz.AdjustWidth( -(nLeftCrop + nRightCrop) ); + aGrfPt.setY(aGrfPt.getY() + nTopCrop); + aTmpSz.AdjustHeight( -(nTopCrop + nBottomCrop) ); + + if( MirrorGraph::Dont != nMirror ) + lcl_CalcRect( aGrfPt, aTmpSz, nMirror ); + + pOrigRect->Pos ( aGrfPt ); + pOrigRect->SSize( aTmpSz ); + } +} + +/** By returning the surrounding Fly's size which equals the graphic's size */ +const Size& SwNoTextFrame::GetSize() const +{ + // Return the Frame's size + const SwFrame *pFly = FindFlyFrame(); + if( !pFly ) + pFly = this; + return pFly->getFramePrintArea().SSize(); +} + +void SwNoTextFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + // RotateFlyFrame3 - inner frame. Get rotation and check if used + const double fRotation(getLocalFrameRotation()); + const bool bRotated(!basegfx::fTools::equalZero(fRotation)); + + if(bRotated) + { + SwFlyFreeFrame* pUpperFly(dynamic_cast< SwFlyFreeFrame* >(GetUpper())); + + if(pUpperFly) + { + if(!pUpperFly->isFrameAreaDefinitionValid()) + { + // RotateFlyFrame3: outer frame *needs* to be layouted first, force this by calling + // it's ::Calc directly + pUpperFly->Calc(pRenderContext); + } + + // Reset outer frame to unrotated state. This is necessary to make the + // layouting below work as currently implemented in Writer. As expected + // using Transformations allows to do this on the fly due to all information + // being included there. + // The full solution would be to adapt the whole layouting + // process of Writer to take care of Transformations, but that + // is currently beyond scope + if(pUpperFly->isTransformableSwFrame()) + { + pUpperFly->getTransformableSwFrame()->restoreFrameAreas(); + } + } + + // Re-layout may be partially (see all isFrameAreaDefinitionValid() flags), + // so resetting the local SwFrame(s) in the local SwFrameAreaDefinition is also + // needed (e.g. for PrintPreview). + // Reset to BoundAreas will be done below automatically + if(isTransformableSwFrame()) + { + getTransformableSwFrame()->restoreFrameAreas(); + } + } + + SwContentNotify aNotify( this ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + MakePos(); + + if ( !isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( GetUpper()->getFramePrintArea().Width() ); + } + + MakePrtArea( rAttrs ); + + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + Format(getRootFrame()->GetCurrShell()->GetOut()); + } + } + + // RotateFlyFrame3 - inner frame + if(bRotated) + { + SwFlyFreeFrame* pUpperFly(dynamic_cast< SwFlyFreeFrame* >(GetUpper())); + + if(pUpperFly) + { + // restore outer frame back to Transformed state, that means + // set the SwFrameAreaDefinition(s) back to BoundAreas of + // the transformed SwFrame. All needed information is part + // of the already correctly created Transformations of the + // upper frame, so it can be re-created on the fly + if(pUpperFly->isTransformableSwFrame()) + { + pUpperFly->getTransformableSwFrame()->adaptFrameAreasToTransformations(); + } + } + + // After the unrotated layout is finished, apply possible set rotation to it + // get center from outer frame (layout frame) to be on the safe side + const Point aCenter(GetUpper() ? GetUpper()->getFrameArea().Center() : getFrameArea().Center()); + const basegfx::B2DPoint aB2DCenter(aCenter.X(), aCenter.Y()); + + if(!mpTransformableSwFrame) + { + mpTransformableSwFrame.reset(new TransformableSwFrame(*this)); + } + + getTransformableSwFrame()->createFrameAreaTransformations( + fRotation, + aB2DCenter); + getTransformableSwFrame()->adaptFrameAreasToTransformations(); + } + else + { + // reset transformations to show that they are not used + mpTransformableSwFrame.reset(); + } +} + +// RotateFlyFrame3 - Support for Transformations - outer frame +basegfx::B2DHomMatrix SwNoTextFrame::getFrameAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFrameAreaTransformation(); + } + + // call parent + return SwContentFrame::getFrameAreaTransformation(); +} + +basegfx::B2DHomMatrix SwNoTextFrame::getFramePrintAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFramePrintAreaTransformation(); + } + + // call parent + return SwContentFrame::getFramePrintAreaTransformation(); +} + +// RotateFlyFrame3 - Support for Transformations +void SwNoTextFrame::transform_translate(const Point& rOffset) +{ + // call parent - this will do the basic transform for SwRect(s) + // in the SwFrameAreaDefinition + SwContentFrame::transform_translate(rOffset); + + // check if the Transformations need to be adapted + if(isTransformableSwFrame()) + { + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createTranslateB2DHomMatrix( + rOffset.X(), rOffset.Y())); + + // transform using TransformableSwFrame + getTransformableSwFrame()->transform(aTransform); + } +} + +// RotateFlyFrame3 - inner frame +// Check if we contain a SwGrfNode and get possible rotation from it +double SwNoTextFrame::getLocalFrameRotation() const +{ + const SwNoTextNode* pSwNoTextNode(nullptr != GetNode() ? GetNode()->GetNoTextNode() : nullptr); + + if(nullptr != pSwNoTextNode) + { + const SwGrfNode* pSwGrfNode(pSwNoTextNode->GetGrfNode()); + + if(nullptr != pSwGrfNode) + { + const SwAttrSet& rSwAttrSet(pSwGrfNode->GetSwAttrSet()); + const SwRotationGrf& rSwRotationGrf(rSwAttrSet.GetRotationGrf()); + const double fRotate(static_cast< double >(-rSwRotationGrf.GetValue()) * (M_PI/1800.0)); + + return basegfx::normalizeToRange(fRotate, F_2PI); + } + } + + // no rotation + return 0.0; +} + +/** Calculate the Bitmap's site, if needed */ +void SwNoTextFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + const Size aNewSize( GetSize() ); + + // Did the height change? + SwTwips nChgHght = IsVertical() ? + static_cast<SwTwips>(aNewSize.Width() - getFramePrintArea().Width()) : + static_cast<SwTwips>(aNewSize.Height() - getFramePrintArea().Height()); + if( nChgHght > 0) + Grow( nChgHght ); + else if( nChgHght < 0) + Shrink( std::min(getFramePrintArea().Height(), -nChgHght) ); +} + +bool SwNoTextFrame::GetCharRect( SwRect &rRect, const SwPosition& rPos, + SwCursorMoveState *pCMS, bool /*bAllowFarAway*/ ) const +{ + if ( &rPos.nNode.GetNode() != static_cast<SwNode const *>(GetNode()) ) + return false; + + Calc(getRootFrame()->GetCurrShell()->GetOut()); + SwRect aFrameRect( getFrameArea() ); + rRect = aFrameRect; + rRect.Pos( getFrameArea().Pos() + getFramePrintArea().Pos() ); + rRect.SSize( getFramePrintArea().SSize() ); + + rRect.Justify(); + + // Is the Bitmap in the visible area at all? + if( !aFrameRect.IsOver( rRect ) ) + { + // If not, then the Cursor is on the Frame + rRect = aFrameRect; + rRect.Width( 1 ); + } + else + rRect.Intersection_( aFrameRect ); + + if ( pCMS && pCMS->m_bRealHeight ) + { + pCMS->m_aRealHeight.setY(rRect.Height()); + pCMS->m_aRealHeight.setX(0); + } + + return true; +} + +bool SwNoTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& , + SwCursorMoveState*, bool ) const +{ + SwContentNode* pCNd = const_cast<SwContentNode*>(GetNode()); + pPos->nNode = *pCNd; + pPos->nContent.Assign( pCNd, 0 ); + return true; +} + +void SwNoTextFrame::ClearCache() +{ + SwFlyFrame* pFly = FindFlyFrame(); + if( pFly && pFly->GetFormat()->GetSurround().IsContour() ) + { + ClrContourCache( pFly->GetVirtDrawObj() ); + pFly->NotifyBackground( FindPageFrame(), getFramePrintArea(), PrepareHint::FlyFrameAttributesChanged ); + } +} + +void SwNoTextFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + sal_uInt16 nWhich = pNew ? pNew->Which() : pOld ? pOld->Which() : 0; + + // #i73788# + // no <SwContentFrame::Modify(..)> for RES_LINKED_GRAPHIC_STREAM_ARRIVED + if ( RES_GRAPHIC_PIECE_ARRIVED != nWhich && + RES_GRAPHIC_ARRIVED != nWhich && + RES_GRF_REREAD_AND_INCACHE != nWhich && + RES_LINKED_GRAPHIC_STREAM_ARRIVED != nWhich ) + { + SwContentFrame::Modify( pOld, pNew ); + } + + bool bComplete = true; + + switch( nWhich ) + { + case RES_OBJECTDYING: + break; + + case RES_GRF_REREAD_AND_INCACHE: + if( SwNodeType::Grf == GetNode()->GetNodeType() ) + { + // TODO: Remove - due to GraphicObject refactoring + bComplete = false; + } + break; + + case RES_UPDATE_ATTR: + if (GetNode()->GetNodeType() != SwNodeType::Grf) { + break; + } + [[fallthrough]]; + case RES_FMT_CHG: + ClearCache(); + break; + + case RES_ATTRSET_CHG: + { + sal_uInt16 n; + for( n = RES_GRFATR_BEGIN; n < RES_GRFATR_END; ++n ) + if( SfxItemState::SET == static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()-> + GetItemState( n, false )) + { + ClearCache(); + + if(RES_GRFATR_ROTATION == n) + { + // RotGrfFlyFrame: Update Handles in view, these may be rotation-dependent + // (e.g. crop handles) and need a visualisation update + if ( GetNode()->GetNodeType() == SwNodeType::Grf ) + { + SwGrfNode* pNd = static_cast<SwGrfNode*>( GetNode()); + SwViewShell *pVSh = pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + if(pVSh) + { + SdrView* pDrawView = pVSh->GetDrawView(); + + if(pDrawView) + { + pDrawView->AdjustMarkHdl(nullptr); + } + } + + // RotateFlyFrame3 - invalidate needed for ContentFrame (inner, this) + // and LayoutFrame (outer, GetUpper). It is possible to only invalidate + // the outer frame, but that leads to an in-between state that gets + // potentially painted + if(GetUpper()) + { + GetUpper()->InvalidateAll_(); + } + + InvalidateAll_(); + } + } + break; + } + if( RES_GRFATR_END == n ) // not found + return ; + } + break; + + case RES_GRAPHIC_PIECE_ARRIVED: + case RES_GRAPHIC_ARRIVED: + // i73788# - handle RES_LINKED_GRAPHIC_STREAM_ARRIVED as RES_GRAPHIC_ARRIVED + case RES_LINKED_GRAPHIC_STREAM_ARRIVED: + if ( GetNode()->GetNodeType() == SwNodeType::Grf ) + { + bComplete = false; + SwGrfNode* pNd = static_cast<SwGrfNode*>( GetNode()); + + ClearCache(); + + SwRect aRect( getFrameArea() ); + + SwViewShell *pVSh = pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( !pVSh ) + break; + + for(SwViewShell& rShell : pVSh->GetRingContainer()) + { + SET_CURR_SHELL( &rShell ); + if( rShell.IsPreview() ) + { + if( rShell.GetWin() ) + ::RepaintPagePreview( &rShell, aRect ); + } + else if ( rShell.VisArea().IsOver( aRect ) && + OUTDEV_WINDOW == rShell.GetOut()->GetOutDevType() ) + { + // invalidate instead of painting + rShell.GetWin()->Invalidate( aRect.SVRect() ); + } + } + } + break; + + default: + if ( !pNew || !isGRFATR(nWhich) ) + return; + } + + if( bComplete ) + { + InvalidatePrt(); + SetCompletePaint(); + } +} + +static void lcl_correctlyAlignRect( SwRect& rAlignedGrfArea, const SwRect& rInArea, vcl::RenderContext const * pOut ) +{ + + if(!pOut) + return; + tools::Rectangle aPxRect = pOut->LogicToPixel( rInArea.SVRect() ); + tools::Rectangle aNewPxRect( aPxRect ); + while( aNewPxRect.Left() < aPxRect.Left() ) + { + rAlignedGrfArea.AddLeft( 1 ); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } + while( aNewPxRect.Top() < aPxRect.Top() ) + { + rAlignedGrfArea.AddTop(+1); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } + while( aNewPxRect.Bottom() > aPxRect.Bottom() ) + { + rAlignedGrfArea.AddBottom( -1 ); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } + while( aNewPxRect.Right() > aPxRect.Right() ) + { + rAlignedGrfArea.AddRight(-1); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } +} + +static bool paintUsingPrimitivesHelper( + vcl::RenderContext& rOutputDevice, + const drawinglayer::primitive2d::Primitive2DContainer& rSequence, + const basegfx::B2DRange& rSourceRange, + const basegfx::B2DRange& rTargetRange) +{ + if(!rSequence.empty() && !basegfx::fTools::equalZero(rSourceRange.getWidth()) && !basegfx::fTools::equalZero(rSourceRange.getHeight())) + { + if(!basegfx::fTools::equalZero(rTargetRange.getWidth()) && !basegfx::fTools::equalZero(rTargetRange.getHeight())) + { + // map graphic range to target range. This will e.g. automatically include + // the mapping from 1/100th mm content to twips if needed when the target + // range is defined in twips + const basegfx::B2DHomMatrix aMappingTransform( + basegfx::utils::createSourceRangeTargetRangeTransform( + rSourceRange, + rTargetRange)); + + // Fill ViewInformation. Use MappingTransform here, so there is no need to + // embed the primitives to it. Use original TargetRange here so there is also + // no need to embed the primitives to a MaskPrimitive for cropping. This works + // only in this case where the graphic object cannot be rotated, though. + const drawinglayer::geometry::ViewInformation2D aViewInformation2D( + aMappingTransform, + rOutputDevice.GetViewTransformation(), + rTargetRange, + nullptr, + 0.0, + uno::Sequence< beans::PropertyValue >()); + + // get a primitive processor for rendering + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D( + drawinglayer::processor2d::createProcessor2DFromOutputDevice( + rOutputDevice, aViewInformation2D) ); + if(pProcessor2D) + { + // render and cleanup + pProcessor2D->process(rSequence); + return true; + } + } + } + + return false; +} + +// MM02 original using falölback to VOC and primitive-based version +void paintGraphicUsingPrimitivesHelper( + vcl::RenderContext & rOutputDevice, + GraphicObject const& rGrfObj, + GraphicAttr const& rGraphicAttr, + const basegfx::B2DHomMatrix& rGraphicTransform, + const OUString& rName, + const OUString& rTitle, + const OUString& rDescription) +{ + // RotGrfFlyFrame: unify using GraphicPrimitive2D + // -> the primitive handles all crop and mirror stuff + // -> the primitive renderer will create the needed pdf export data + // -> if bitmap content, it will be cached system-dependent + drawinglayer::primitive2d::Primitive2DContainer aContent(1); + aContent[0] = new drawinglayer::primitive2d::GraphicPrimitive2D( + rGraphicTransform, + rGrfObj, + rGraphicAttr); + + // MM02 use primitive-based version for visualization + paintGraphicUsingPrimitivesHelper( + rOutputDevice, + aContent, + rGraphicTransform, + rName, + rTitle, + rDescription); +} + +// MM02 new VOC and primitive-based version +void paintGraphicUsingPrimitivesHelper( + vcl::RenderContext & rOutputDevice, + drawinglayer::primitive2d::Primitive2DContainer& rContent, + const basegfx::B2DHomMatrix& rGraphicTransform, + const OUString& rName, + const OUString& rTitle, + const OUString& rDescription) +{ + // RotateFlyFrame3: If ClipRegion is set at OutputDevice, we + // need to use that. Usually the renderer would be a VCL-based + // PrimitiveRenderer, but there are system-specific shortcuts that + // will *not* use the VCL-Paint of Bitmap and thus ignore this. + // Anyways, indirectly using a CLipRegion set at the target OutDev + // when using a PrimitiveRenderer is a non-valid implication. + // First tried only to use when HasPolyPolygonOrB2DPolyPolygon(), + // but there is an optimization at ClipRegion creation that detects + // a single Rectangle in a tools::PolyPolygon and forces to a simple + // RegionBand-based implementation, so cannot use it here. + if(rOutputDevice.IsClipRegion()) + { + const basegfx::B2DPolyPolygon aClip(rOutputDevice.GetClipRegion().GetAsB2DPolyPolygon()); + + if(0 != aClip.count()) + { + // tdf#114076: Expand ClipRange to next PixelBound + // Do this by going to basegfx::B2DRange, adding a + // single pixel size and using floor/ceil to go to + // full integer (as needed for pixels). Also need + // to go back to basegfx::B2DPolyPolygon for the + // creation of the needed MaskPrimitive2D. + // The general problem is that Writer is scrolling + // using blitting the unchanged parts, this forces + // this part of the scroll to pixel coordinate steps, + // while the ViewTransformation for paint nowadays has + // a sub-pixel precision. This results in an offset + // up to one pixel in radius. To solve this for now, + // we need to expand to the next outer pixel bound. + // Hopefully in the future we will someday be able to + // stay on the full available precision, but this + // will need a change in the repaint/scroll paradigm. + const basegfx::B2DRange aClipRange(aClip.getB2DRange()); + const basegfx::B2DVector aSinglePixelXY(rOutputDevice.GetInverseViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + const basegfx::B2DRange aExpandedClipRange( + floor(aClipRange.getMinX() - aSinglePixelXY.getX()), + floor(aClipRange.getMinY() - aSinglePixelXY.getY()), + ceil(aClipRange.getMaxX() + aSinglePixelXY.getX()), + ceil(aClipRange.getMaxY() + aSinglePixelXY.getY())); + + // create the enclosing rectangle as polygon + basegfx::B2DPolyPolygon aTarget(basegfx::utils::createPolygonFromRect(aExpandedClipRange)); + + // tdf#124272 the fix above (tdf#114076) was too rough - the + // clip region used may be a PolyPolygon. In that case that + // PolyPolygon would have to be scaled to mentioned PixelBounds. + // Since that is not really possible geometrically (would need + // more some 'grow in outside direction' but with unequal grow + // values in all directions - just maaany problems + // involved), use a graphical trick: The topology of the + // PolyPolygon uses the standard FillRule, so adding the now + // guaranteed to be bigger or equal bounding (enclosing) + // rectangle twice as polygon will expand the BoundRange, but + // not change the geometry visualization at all + if(!rOutputDevice.GetClipRegion().IsRectangle()) + { + // double the outer rectangle range polygon to have it + // included twice + aTarget.append(aTarget.getB2DPolygon(0)); + + // add the original clip 'inside' (due to being smaller + // or equal). That PolyPolygon may have an unknown number + // of polygons (>=1) + aTarget.append(aClip); + } + + drawinglayer::primitive2d::MaskPrimitive2D* pNew( + new drawinglayer::primitive2d::MaskPrimitive2D( + aTarget, + rContent)); + rContent.resize(1); + rContent[0] = pNew; + } + } + + if(!rName.isEmpty() || !rTitle.isEmpty() || !rDescription.isEmpty()) + { + // Embed to ObjectInfoPrimitive2D when we have Name/Title/Description + // information available + drawinglayer::primitive2d::ObjectInfoPrimitive2D* pNew( + new drawinglayer::primitive2d::ObjectInfoPrimitive2D( + rContent, + rName, + rTitle, + rDescription)); + rContent.resize(1); + rContent[0] = pNew; + } + + basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0); + aTargetRange.transform(rGraphicTransform); + + paintUsingPrimitivesHelper( + rOutputDevice, + rContent, + aTargetRange, + aTargetRange); +} + +// DrawContact section +namespace { // anonymous namespace +class ViewObjectContactOfSwNoTextFrame : public sdr::contact::ViewObjectContact +{ +protected: + virtual drawinglayer::primitive2d::Primitive2DContainer createPrimitive2DSequence( + const sdr::contact::DisplayInfo& rDisplayInfo) const override; + +public: + ViewObjectContactOfSwNoTextFrame( + sdr::contact::ObjectContact& rObjectContact, + sdr::contact::ViewContact& rViewContact); +}; + +class ViewContactOfSwNoTextFrame : public sdr::contact::ViewContact +{ +private: + // owner + const SwNoTextFrame& mrSwNoTextFrame; + +protected: + // Create an Object-Specific ViewObjectContact, set ViewContact and + // ObjectContact. Always needs to return something. + virtual sdr::contact::ViewObjectContact& CreateObjectSpecificViewObjectContact( + sdr::contact::ObjectContact& rObjectContact) override; + +public: + // read-access to owner + const SwNoTextFrame& getSwNoTextFrame() const { return mrSwNoTextFrame; } + + // basic constructor, used from SwNoTextFrame. + explicit ViewContactOfSwNoTextFrame(const SwNoTextFrame& rSwNoTextFrame); +}; + +drawinglayer::primitive2d::Primitive2DContainer ViewObjectContactOfSwNoTextFrame::createPrimitive2DSequence( + const sdr::contact::DisplayInfo& /*rDisplayInfo*/) const +{ + // MM02 get all the parameters formally used in paintGraphicUsingPrimitivesHelper + ViewContactOfSwNoTextFrame& rVCOfNTF(static_cast<ViewContactOfSwNoTextFrame&>(GetViewContact())); + const SwNoTextFrame& rSwNoTextFrame(rVCOfNTF.getSwNoTextFrame()); + SwNoTextNode& rNoTNd(const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(rSwNoTextFrame.GetNode()))); + SwGrfNode* pGrfNd(rNoTNd.GetGrfNode()); + + if(nullptr != pGrfNd) + { + const bool bPrn(GetObjectContact().isOutputToPrinter() || GetObjectContact().isOutputToRecordingMetaFile()); + const GraphicObject& rGrfObj(pGrfNd->GetGrfObj(bPrn)); + GraphicAttr aGraphicAttr; + pGrfNd->GetGraphicAttr(aGraphicAttr, &rSwNoTextFrame); + const basegfx::B2DHomMatrix aGraphicTransform(rSwNoTextFrame.getFrameAreaTransformation()); + + // MM02 this is the right place in the VOC-Mechanism to create + // the primitives for visualization - these will be automatically + // buffered and reused + drawinglayer::primitive2d::Primitive2DContainer aContent(1); + aContent[0] = new drawinglayer::primitive2d::GraphicPrimitive2D( + aGraphicTransform, + rGrfObj, + aGraphicAttr); + + return aContent; + } + + return drawinglayer::primitive2d::Primitive2DContainer(); +} + +ViewObjectContactOfSwNoTextFrame::ViewObjectContactOfSwNoTextFrame( + sdr::contact::ObjectContact& rObjectContact, + sdr::contact::ViewContact& rViewContact) +: sdr::contact::ViewObjectContact(rObjectContact, rViewContact) +{ +} + +sdr::contact::ViewObjectContact& ViewContactOfSwNoTextFrame::CreateObjectSpecificViewObjectContact( + sdr::contact::ObjectContact& rObjectContact) +{ + sdr::contact::ViewObjectContact* pRetval = new ViewObjectContactOfSwNoTextFrame(rObjectContact, *this); + return *pRetval; +} + +ViewContactOfSwNoTextFrame::ViewContactOfSwNoTextFrame( + const SwNoTextFrame& rSwNoTextFrame +) +: sdr::contact::ViewContact(), + mrSwNoTextFrame(rSwNoTextFrame) +{ +} +} // end of anonymous namespace + +sdr::contact::ViewContact& SwNoTextFrame::GetViewContact() const +{ + if(!mpViewContact) + { + const_cast< SwNoTextFrame* >(this)->mpViewContact = + std::make_unique<ViewContactOfSwNoTextFrame>(*this); + } + + return *mpViewContact; +} + +/** Paint the graphic. + + We require either a QuickDraw-Bitmap or a graphic here. If we do not have + either, we return a replacement. + + @todo use aligned rectangle for drawing graphic. + @todo pixel-align coordinations for drawing graphic. */ +void SwNoTextFrame::PaintPicture( vcl::RenderContext* pOut, const SwRect &rGrfArea ) const +{ + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + + SwNoTextNode& rNoTNd = const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(GetNode())); + SwGrfNode* pGrfNd = rNoTNd.GetGrfNode(); + SwOLENode* pOLENd = rNoTNd.GetOLENode(); + + const bool bPrn = pOut == rNoTNd.getIDocumentDeviceAccess().getPrinter( false ) || + pOut->GetConnectMetaFile(); + + const bool bIsChart = pOLENd && pOLENd->GetOLEObj().GetObject().IsChart(); + + // calculate aligned rectangle from parameter <rGrfArea>. + // Use aligned rectangle <aAlignedGrfArea> instead of <rGrfArea> in + // the following code. + SwRect aAlignedGrfArea = rGrfArea; + ::SwAlignRect( aAlignedGrfArea, pShell, pOut ); + + if( !bIsChart ) + { + // Because for drawing a graphic left-top-corner and size coordinations are + // used, these coordinations have to be determined on pixel level. + ::SwAlignGrfRect( &aAlignedGrfArea, *pOut ); + } + else //if( bIsChart ) + { + // #i78025# charts own borders are not completely visible + // the above pixel correction is not correct - at least not for charts + // so a different pixel correction is chosen here + // this might be a good idea for all other OLE objects also, + // but as I cannot oversee the consequences I fix it only for charts for now + lcl_correctlyAlignRect( aAlignedGrfArea, rGrfArea, pOut ); + } + + if( pGrfNd ) + { + // Fix for bug fdo#33781 + const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() ); + if (pShell->Imp()->GetDrawView()->IsAntiAliasing()) + { + pOut->SetAntialiasing( nFormerAntialiasingAtOutput | AntialiasingFlags::EnableB2dDraw ); + } + + bool bContinue = true; + const GraphicObject& rGrfObj = pGrfNd->GetGrfObj(bPrn); + + GraphicAttr aGrfAttr; + pGrfNd->GetGraphicAttr( aGrfAttr, this ); + + if( !bPrn ) + { + // #i73788# + if ( pGrfNd->IsLinkedInputStreamReady() ) + { + pGrfNd->UpdateLinkWithInputStream(); + } + // #i85717#, #i90395# - check, if asynchronous retrieval + // if input stream for the graphic is possible + else if ( ( rGrfObj.GetType() == GraphicType::Default || + rGrfObj.GetType() == GraphicType::NONE ) && + pGrfNd->IsLinkedFile() && + pGrfNd->IsAsyncRetrieveInputStreamPossible() ) + { + Size aTmpSz; + ::sfx2::SvLinkSource* pGrfObj = pGrfNd->GetLink()->GetObj(); + if( !pGrfObj || + !pGrfObj->IsDataComplete() || + !(aTmpSz = pGrfNd->GetTwipSize()).Width() || + !aTmpSz.Height()) + { + pGrfNd->TriggerAsyncRetrieveInputStream(); // #i73788# + } + OUString aText( pGrfNd->GetTitle() ); + if ( aText.isEmpty() ) + GetRealURL( *pGrfNd, aText ); + ::lcl_PaintReplacement( aAlignedGrfArea, aText, *pShell, this, false ); + bContinue = false; + } + } + + if( bContinue ) + { + if( rGrfObj.GetGraphic().IsSupportedGraphic()) + { + const bool bAnimate = rGrfObj.IsAnimated() && + !pShell->IsPreview() && + !pShell->GetAccessibilityOptions()->IsStopAnimatedGraphics() && + // #i9684# Stop animation during printing/pdf export + pShell->GetWin(); + + if( bAnimate && + FindFlyFrame() != ::GetFlyFromMarked( nullptr, pShell )) + { + OutputDevice* pVout; + if( pOut == pShell->GetOut() && SwRootFrame::FlushVout() ) + { + pVout = pOut; + pOut = pShell->GetOut(); + } + else if( pShell->GetWin() && pOut->IsVirtual() ) + { + pVout = pOut; + pOut = pShell->GetWin(); + } + else + pVout = nullptr; + + OSL_ENSURE( !pOut->IsVirtual() || + pShell->GetViewOptions()->IsPDFExport() || pShell->isOutputToWindow(), + "pOut should not be a virtual device" ); + + pGrfNd->StartGraphicAnimation(pOut, aAlignedGrfArea.Pos(), + aAlignedGrfArea.SSize(), reinterpret_cast<sal_IntPtr>(this), + pVout ); + } + else + { + // MM02 To allow system-dependent buffering of the involved + // bitmaps it is necessary to re-use the involved primitives + // and their already executed decomposition (also for + // performance reasons). This is usually done in DrawingLayer + // by using the VOC-Mechanism (see descriptions elsewhere). + // To get that here, make the involved SwNoTextFrame (this) + // a sdr::contact::ViewContact supplier by supporting + // a GetViewContact() - call. For ObjectContact we can use + // the already existing ObjectContact from the involved + // DrawingLayer. For this, the helper classes + // ViewObjectContactOfSwNoTextFrame + // ViewContactOfSwNoTextFrame + // are created which support the VOC-mechanism in its minimal + // form. This allows automatic and view-dependent (multiple edit + // windows, print, etc.) re-use of the created primitives. + // Also: Will be very useful when completely changing the Writer + // repaint to VOC and Primitives, too. + static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES")); + static bool bUseViewObjectContactMechanism(nullptr == pDisableMM02Goodies); + // tdf#130951 for safety reasons use fallback if ViewObjectContactMechanism + // fails for some reason - usually could only be not to find the correct + // SdrPageWindow + bool bSucceeded(false); + + if(bUseViewObjectContactMechanism) + { + // MM02 use VOC-mechanism and buffer primitives + SwViewShellImp* pImp(pShell->Imp()); + SdrPageView* pPageView(nullptr != pImp + ? pImp->GetPageView() + : nullptr); + // tdf#130951 caution - target may be Window, use the correct OutputDevice + OutputDevice* pTarget(pShell->isOutputToWindow() + ? pShell->GetWin() + : pShell->GetOut()); + SdrPageWindow* pPageWindow(nullptr != pPageView && nullptr != pTarget + ? pPageView->FindPageWindow(*pTarget) + : nullptr); + + if(nullptr != pPageWindow) + { + sdr::contact::ObjectContact& rOC(pPageWindow->GetObjectContact()); + sdr::contact::ViewContact& rVC(GetViewContact()); + sdr::contact::ViewObjectContact& rVOC(rVC.GetViewObjectContact(rOC)); + sdr::contact::DisplayInfo aDisplayInfo; + + drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rVOC.getPrimitive2DSequence(aDisplayInfo)); + const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation()); + + paintGraphicUsingPrimitivesHelper( + *pOut, + aPrimitives, + aGraphicTransform, + nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(), + rNoTNd.GetTitle(), + rNoTNd.GetDescription()); + bSucceeded = true; + } + } + + if(!bSucceeded) + { + // MM02 fallback to direct paint with primitive-recreation + // which will block reusage of system-dependent bitmap data + const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation()); + + paintGraphicUsingPrimitivesHelper( + *pOut, + rGrfObj, + aGrfAttr, + aGraphicTransform, + nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(), + rNoTNd.GetTitle(), + rNoTNd.GetDescription()); + } + } + } + else + { + const char* pResId = nullptr; + + if( GraphicType::NONE == rGrfObj.GetType() ) + pResId = STR_COMCORE_READERROR; + else if ( !rGrfObj.GetGraphic().IsSupportedGraphic() ) + pResId = STR_COMCORE_CANT_SHOW; + + OUString aText; + if ( !pResId && + (aText = pGrfNd->GetTitle()).isEmpty() && + (!GetRealURL( *pGrfNd, aText ) || aText.isEmpty())) + { + pResId = STR_COMCORE_READERROR; + } + if (pResId) + aText = SwResId(pResId); + + ::lcl_PaintReplacement( aAlignedGrfArea, aText, *pShell, this, true ); + } + } + + if ( pShell->Imp()->GetDrawView()->IsAntiAliasing() ) + pOut->SetAntialiasing( nFormerAntialiasingAtOutput ); + } + else // bIsChart || pOLENd + { + // Fix for bug fdo#33781 + const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() ); + if (pShell->Imp()->GetDrawView()->IsAntiAliasing()) + { + AntialiasingFlags nNewAntialiasingAtOutput = nFormerAntialiasingAtOutput | AntialiasingFlags::EnableB2dDraw; + + // #i99665# + // Adjust AntiAliasing mode at output device for chart OLE + if ( pOLENd->IsChart() ) + nNewAntialiasingAtOutput |= AntialiasingFlags::PixelSnapHairline; + + pOut->SetAntialiasing( nNewAntialiasingAtOutput ); + } + + bool bDone(false); + + if(bIsChart) + { + basegfx::B2DRange aSourceRange; + const drawinglayer::primitive2d::Primitive2DContainer aSequence( + pOLENd->GetOLEObj().tryToGetChartContentAsPrimitive2DSequence( + aSourceRange, + bPrn)); + + if(!aSequence.empty() && !aSourceRange.isEmpty()) + { + const basegfx::B2DRange aTargetRange( + aAlignedGrfArea.Left(), aAlignedGrfArea.Top(), + aAlignedGrfArea.Right(), aAlignedGrfArea.Bottom()); + + bDone = paintUsingPrimitivesHelper( + *pOut, + aSequence, + aSourceRange, + aTargetRange); + } + } + + if(!bDone && pOLENd) + { + // SwOLENode does not have a known GraphicObject, need to + // work with Graphic instead + const Graphic* pGraphic = pOLENd->GetGraphic(); + const Point aPosition(aAlignedGrfArea.Pos()); + const Size aSize(aAlignedGrfArea.SSize()); + + if ( pGraphic && pGraphic->GetType() != GraphicType::NONE ) + { + pGraphic->Draw( pOut, aPosition, aSize ); + + // shade the representation if the object is activated outplace + uno::Reference < embed::XEmbeddedObject > xObj = pOLENd->GetOLEObj().GetOleRef(); + if ( xObj.is() && xObj->getCurrentState() == embed::EmbedStates::ACTIVE ) + { + + ::svt::EmbeddedObjectRef::DrawShading( + tools::Rectangle( + aPosition, + aSize), + pOut); + } + } + else + { + ::svt::EmbeddedObjectRef::DrawPaintReplacement( + tools::Rectangle(aPosition, aSize), + pOLENd->GetOLEObj().GetCurrentPersistName(), + pOut); + } + + sal_Int64 nMiscStatus = pOLENd->GetOLEObj().GetOleRef()->getStatus( pOLENd->GetAspect() ); + if ( !bPrn && dynamic_cast< const SwCursorShell *>( pShell ) != nullptr && + (nMiscStatus & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE)) + { + const SwFlyFrame *pFly = FindFlyFrame(); + assert( pFly != nullptr ); + static_cast<SwFEShell*>(pShell)->ConnectObj( pOLENd->GetOLEObj().GetObject(), pFly->getFramePrintArea(), pFly->getFrameArea()); + } + } + + // see #i99665# + if (pShell->Imp()->GetDrawView()->IsAntiAliasing()) + { + pOut->SetAntialiasing( nFormerAntialiasingAtOutput ); + } + } +} + +bool SwNoTextFrame::IsTransparent() const +{ + const SwViewShell* pSh = getRootFrame()->GetCurrShell(); + + if ( !pSh || !pSh->GetViewOptions()->IsGraphic() ) + { + return true; + } + + const SwGrfNode *pNd; + + if( nullptr != (pNd = GetNode()->GetGrfNode()) ) + { + if(pNd->IsTransparent()) + { + return true; + } + } + + // RotateFlyFrame3: If we are transformed, there are 'free' areas between + // the Graphic and the Border/Padding stuff - at least as long as those + // (Border and Padding) are not transformed, too + if(isTransformableSwFrame()) + { + // we can be more specific - rotations of multiples of + // 90 degrees will leave no gaps. Go from [0.0 .. F_2PI] + // to [0 .. 360] and check modulo 90 + const long nRot(static_cast<long>(basegfx::rad2deg(getLocalFrameRotation()))); + const bool bMultipleOf90(0 == (nRot % 90)); + + if(!bMultipleOf90) + { + return true; + } + } + + //#29381# OLE are always transparent + if(nullptr != GetNode()->GetOLENode()) + { + return true; + } + + // return false by default to avoid background paint + return false; +} + +void SwNoTextFrame::StopAnimation( OutputDevice* pOut ) const +{ + // Stop animated graphics + const SwGrfNode* pGrfNd = GetNode()->GetGrfNode(); + + if( pGrfNd && pGrfNd->IsAnimated() ) + { + const_cast< SwGrfNode* >(pGrfNd)->StopGraphicAnimation( pOut, reinterpret_cast<sal_IntPtr>(this) ); + } +} + +bool SwNoTextFrame::HasAnimation() const +{ + const SwGrfNode* pGrfNd = GetNode()->GetGrfNode(); + return pGrfNd && pGrfNd->IsAnimated(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/number.cxx b/sw/source/core/doc/number.cxx new file mode 100644 index 000000000..1de08ae9f --- /dev/null +++ b/sw/source/core/doc/number.cxx @@ -0,0 +1,1475 @@ +/* -*- 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 <memory> +#include <hintids.hxx> + +#include <vcl/font.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/numitem.hxx> +#include <svl/grabbagitem.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> +#include <docary.hxx> +#include <SwStyleNameMapper.hxx> + +// Needed to load default bullet list configuration +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> + +#include <numrule.hxx> +#include <SwNodeNum.hxx> + +#include <list.hxx> + +#include <algorithm> +#include <unordered_map> +#include <libxml/xmlwriter.h> + +#include <rtl/ustrbuf.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/saveopt.hxx> +#include <osl/diagnose.h> + +#include <IDocumentListsAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentState.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +using namespace ::com::sun::star; + +sal_uInt16 SwNumRule::mnRefCount = 0; +SwNumFormat* SwNumRule::maBaseFormats[ RULE_END ][ MAXLEVEL ] = { + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } }; + +SwNumFormat* SwNumRule::maLabelAlignmentBaseFormats[ RULE_END ][ MAXLEVEL ] = { + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } }; + +const sal_uInt16 SwNumRule::maDefNumIndents[ MAXLEVEL ] = { +//inch: 0,5 1,0 1,5 2,0 2,5 3,0 3,5 4,0 4,5 5,0 + 1440/4, 1440/2, 1440*3/4, 1440, 1440*5/4, 1440*3/2, 1440*7/4, 1440*2, + 1440*9/4, 1440*5/2 +}; + +OUString SwNumRule::GetOutlineRuleName() +{ + return "Outline"; +} + +const SwNumFormat& SwNumRule::Get( sal_uInt16 i ) const +{ + assert( i < MAXLEVEL && meRuleType < RULE_END ); + return maFormats[ i ] + ? *maFormats[ i ] + : ( meDefaultNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION + ? *maBaseFormats[ meRuleType ][ i ] + : *maLabelAlignmentBaseFormats[ meRuleType ][ i ] ); +} + +const SwNumFormat* SwNumRule::GetNumFormat( sal_uInt16 i ) const +{ + const SwNumFormat * pResult = nullptr; + + assert( i < MAXLEVEL && meRuleType < RULE_END ); + if ( i < MAXLEVEL && meRuleType < RULE_END) + { + pResult = maFormats[ i ].get(); + } + + return pResult; +} + +// #i91400# +void SwNumRule::SetName( const OUString & rName, + IDocumentListsAccess& rDocListAccess) +{ + if ( msName != rName ) + { + if (mpNumRuleMap) + { + mpNumRuleMap->erase(msName); + (*mpNumRuleMap)[rName] = this; + + if ( !GetDefaultListId().isEmpty() ) + { + rDocListAccess.trackChangeOfListStyleName( msName, rName ); + } + } + + msName = rName; + } +} + +void SwNumRule::GetTextNodeList( SwNumRule::tTextNodeList& rTextNodeList ) const +{ + rTextNodeList = maTextNodeList; +} + +SwNumRule::tTextNodeList::size_type SwNumRule::GetTextNodeListSize() const +{ + return maTextNodeList.size(); +} + +void SwNumRule::AddTextNode( SwTextNode& rTextNode ) +{ + tTextNodeList::iterator aIter = + std::find( maTextNodeList.begin(), maTextNodeList.end(), &rTextNode ); + + if ( aIter == maTextNodeList.end() ) + { + maTextNodeList.push_back( &rTextNode ); + } +} + +void SwNumRule::RemoveTextNode( SwTextNode& rTextNode ) +{ + tTextNodeList::iterator aIter = + std::find( maTextNodeList.begin(), maTextNodeList.end(), &rTextNode ); + + if ( aIter != maTextNodeList.end() ) + { + maTextNodeList.erase( aIter ); + } +} + +void SwNumRule::SetNumRuleMap(std::unordered_map<OUString, SwNumRule *> * + pNumRuleMap) +{ + mpNumRuleMap = pNumRuleMap; +} + +sal_uInt16 SwNumRule::GetNumIndent( sal_uInt8 nLvl ) +{ + OSL_ENSURE( MAXLEVEL > nLvl, "NumLevel is out of range" ); + return maDefNumIndents[ nLvl ]; +} + +sal_uInt16 SwNumRule::GetBullIndent( sal_uInt8 nLvl ) +{ + OSL_ENSURE( MAXLEVEL > nLvl, "NumLevel is out of range" ); + return maDefNumIndents[ nLvl ]; +} + +static void lcl_SetRuleChgd( SwTextNode& rNd, sal_uInt8 nLevel ) +{ + if( rNd.GetActualListLevel() == nLevel ) + rNd.NumRuleChgd(); +} + +SwNumFormat::SwNumFormat() : + SvxNumberFormat(SVX_NUM_ARABIC), + SwClient( nullptr ), + m_pVertOrient(new SwFormatVertOrient( 0, text::VertOrientation::NONE)) + ,m_cGrfBulletCP(USHRT_MAX)//For i120928,record the cp info of graphic within bullet +{ +} + +SwNumFormat::SwNumFormat( const SwNumFormat& rFormat) : + SvxNumberFormat(rFormat), + SwClient( rFormat.GetRegisteredInNonConst() ), + m_pVertOrient(new SwFormatVertOrient( 0, rFormat.GetVertOrient())) + ,m_cGrfBulletCP(rFormat.m_cGrfBulletCP)//For i120928,record the cp info of graphic within bullet +{ + sal_Int16 eMyVertOrient = rFormat.GetVertOrient(); + SetGraphicBrush( rFormat.GetBrush(), &rFormat.GetGraphicSize(), + &eMyVertOrient); +} + +SwNumFormat::SwNumFormat(const SvxNumberFormat& rNumFormat, SwDoc* pDoc) + : SvxNumberFormat(rNumFormat) + , m_pVertOrient(new SwFormatVertOrient( 0, rNumFormat.GetVertOrient())) + , m_cGrfBulletCP(USHRT_MAX) +{ + sal_Int16 eMyVertOrient = rNumFormat.GetVertOrient(); + SetGraphicBrush( rNumFormat.GetBrush(), &rNumFormat.GetGraphicSize(), + &eMyVertOrient); + const OUString rCharStyleName = rNumFormat.SvxNumberFormat::GetCharFormatName(); + if( !rCharStyleName.isEmpty() ) + { + SwCharFormat* pCFormat = pDoc->FindCharFormatByName( rCharStyleName ); + if( !pCFormat ) + { + sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName( rCharStyleName, + SwGetPoolIdFromName::ChrFmt ); + pCFormat = nId != USHRT_MAX + ? pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( nId ) + : pDoc->MakeCharFormat( rCharStyleName, nullptr ); + } + pCFormat->Add( this ); + } + else + EndListeningAll(); +} + +SwNumFormat::~SwNumFormat() +{ +} + +// #i22362# +bool SwNumFormat::IsEnumeration() const +{ + // #i30655# native numbering did not work any longer + // using this code. Therefore HBRINKM and I agreed upon defining + // IsEnumeration() as !IsItemize() + return !IsItemize(); +} + +bool SwNumFormat::IsItemize() const +{ + bool bResult; + + switch(GetNumberingType()) + { + case SVX_NUM_CHAR_SPECIAL: + case SVX_NUM_BITMAP: + bResult = true; + + break; + + default: + bResult = false; + } + + return bResult; + +} + +SwNumFormat& SwNumFormat::operator=( const SwNumFormat& rNumFormat) +{ + SvxNumberFormat::operator=(rNumFormat); + StartListeningToSameModifyAs(rNumFormat); + //For i120928,record the cp info of graphic within bullet + m_cGrfBulletCP = rNumFormat.m_cGrfBulletCP; + return *this; +} + +bool SwNumFormat::operator==( const SwNumFormat& rNumFormat) const +{ + bool bRet = SvxNumberFormat::operator==(rNumFormat) && + GetRegisteredIn() == rNumFormat.GetRegisteredIn(); + return bRet; +} + +void SwNumFormat::SetCharFormat( SwCharFormat* pChFormat) +{ + if( pChFormat ) + pChFormat->Add( this ); + else + EndListeningAll(); +} + +void SwNumFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + // Look for the NumRules object in the Doc where this NumFormat is set. + // The format does not need to exist! + const SwCharFormat* pFormat = nullptr; + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_ATTRSET_CHG: + case RES_FMT_CHG: + pFormat = GetCharFormat(); + break; + } + + if( pFormat && !pFormat->GetDoc()->IsInDtor() ) + UpdateNumNodes( const_cast<SwDoc*>(pFormat->GetDoc()) ); + else + CheckRegistration( pOld ); +} + +OUString SwNumFormat::GetCharFormatName() const +{ + if(static_cast<const SwCharFormat*>(GetRegisteredIn())) + return static_cast<const SwCharFormat*>(GetRegisteredIn())->GetName(); + + return OUString(); +} + +void SwNumFormat::SetGraphicBrush( const SvxBrushItem* pBrushItem, const Size* pSize, + const sal_Int16* pOrient) +{ + if(pOrient) + m_pVertOrient->SetVertOrient( *pOrient ); + SvxNumberFormat::SetGraphicBrush( pBrushItem, pSize, pOrient); +} + +void SwNumFormat::UpdateNumNodes( SwDoc* pDoc ) +{ + bool bDocIsModified = pDoc->getIDocumentState().IsModified(); + bool bFnd = false; + for( SwNumRuleTable::size_type n = pDoc->GetNumRuleTable().size(); !bFnd && n; ) + { + const SwNumRule* pRule = pDoc->GetNumRuleTable()[ --n ]; + for( sal_uInt8 i = 0; i < MAXLEVEL; ++i ) + if( pRule->GetNumFormat( i ) == this ) + { + SwNumRule::tTextNodeList aTextNodeList; + pRule->GetTextNodeList( aTextNodeList ); + for ( auto& rpTextNode : aTextNodeList ) + { + lcl_SetRuleChgd( *rpTextNode, i ); + } + bFnd = true; + break; + } + } + + if( bFnd && !bDocIsModified ) + pDoc->getIDocumentState().ResetModified(); +} + +const SwFormatVertOrient* SwNumFormat::GetGraphicOrientation() const +{ + sal_Int16 eOrient = SvxNumberFormat::GetVertOrient(); + if(text::VertOrientation::NONE == eOrient) + return nullptr; + else + { + m_pVertOrient->SetVertOrient(eOrient); + return m_pVertOrient.get(); + } +} + +SwNumRule::SwNumRule( const OUString& rNm, + const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode, + SwNumRuleType eType ) + : maTextNodeList(), + maParagraphStyleList(), + mpNumRuleMap(nullptr), + msName( rNm ), + meRuleType( eType ), + mnPoolFormatId( USHRT_MAX ), + mnPoolHelpId( USHRT_MAX ), + mnPoolHlpFileId( UCHAR_MAX ), + mbAutoRuleFlag( true ), + mbInvalidRuleFlag( true ), + mbContinusNum( false ), + mbAbsSpaces( false ), + mbHidden( false ), + mbCountPhantoms( true ), + mbUsedByRedline( false ), + meDefaultNumberFormatPositionAndSpaceMode( eDefaultNumberFormatPositionAndSpaceMode ), + msDefaultListId() +{ + if( !mnRefCount++ ) // for the first time, initialize + { + SwNumFormat* pFormat; + sal_uInt8 n; + + // numbering: + // position-and-space mode LABEL_WIDTH_AND_POSITION: + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetIncludeUpperLevels( 1 ); + pFormat->SetStart( 1 ); + pFormat->SetAbsLSpace( lNumberIndent + SwNumRule::GetNumIndent( n ) ); + pFormat->SetFirstLineOffset( lNumberFirstLineOffset ); + pFormat->SetSuffix( "." ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maBaseFormats[ NUM_RULE ][ n ] = pFormat; + } + // position-and-space mode LABEL_ALIGNMENT + // first line indent of general numbering in inch: -0,25 inch + const long cFirstLineIndent = -1440/4; + // indent values of general numbering in inch: + // 0,5 0,75 1,0 1,25 1,5 + // 1,75 2,0 2,25 2,5 2,75 + const long cIndentAt[ MAXLEVEL ] = { + 1440/2, 1440*3/4, 1440, 1440*5/4, 1440*3/2, + 1440*7/4, 1440*2, 1440*9/4, 1440*5/2, 1440*11/4 }; + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetIncludeUpperLevels( 1 ); + pFormat->SetStart( 1 ); + pFormat->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + pFormat->SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + pFormat->SetListtabPos( cIndentAt[ n ] ); + pFormat->SetFirstLineIndent( cFirstLineIndent ); + pFormat->SetIndentAt( cIndentAt[ n ] ); + pFormat->SetSuffix( "." ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maLabelAlignmentBaseFormats[ NUM_RULE ][ n ] = pFormat; + } + + // outline: + // position-and-space mode LABEL_WIDTH_AND_POSITION: + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetNumberingType(SVX_NUM_NUMBER_NONE); + pFormat->SetIncludeUpperLevels( MAXLEVEL ); + pFormat->SetStart( 1 ); + pFormat->SetCharTextDistance( lOutlineMinTextDistance ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maBaseFormats[ OUTLINE_RULE ][ n ] = pFormat; + } + // position-and-space mode LABEL_ALIGNMENT: + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetNumberingType(SVX_NUM_NUMBER_NONE); + pFormat->SetIncludeUpperLevels( MAXLEVEL ); + pFormat->SetStart( 1 ); + pFormat->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maLabelAlignmentBaseFormats[ OUTLINE_RULE ][ n ] = pFormat; + } + } + OSL_ENSURE( !msName.isEmpty(), "NumRule without a name!" ); +} + +SwNumRule::SwNumRule( const SwNumRule& rNumRule ) + : maTextNodeList(), + maParagraphStyleList(), + mpNumRuleMap(nullptr), + msName( rNumRule.msName ), + meRuleType( rNumRule.meRuleType ), + mnPoolFormatId( rNumRule.GetPoolFormatId() ), + mnPoolHelpId( rNumRule.GetPoolHelpId() ), + mnPoolHlpFileId( rNumRule.GetPoolHlpFileId() ), + mbAutoRuleFlag( rNumRule.mbAutoRuleFlag ), + mbInvalidRuleFlag( true ), + mbContinusNum( rNumRule.mbContinusNum ), + mbAbsSpaces( rNumRule.mbAbsSpaces ), + mbHidden( rNumRule.mbHidden ), + mbCountPhantoms( true ), + mbUsedByRedline( false ), + meDefaultNumberFormatPositionAndSpaceMode( rNumRule.meDefaultNumberFormatPositionAndSpaceMode ), + msDefaultListId( rNumRule.msDefaultListId ) +{ + ++mnRefCount; + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + if( rNumRule.maFormats[ n ] ) + Set( n, *rNumRule.maFormats[ n ] ); +} + +SwNumRule::~SwNumRule() +{ + for (auto & i : maFormats) + i.reset(); + + if (mpNumRuleMap) + { + mpNumRuleMap->erase(GetName()); + } + + if( !--mnRefCount ) // the last one closes the door (?) + { + // Numbering: + SwNumFormat** ppFormats = &SwNumRule::maBaseFormats[0][0]; + int n; + + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + + // Outline: + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + + ppFormats = &SwNumRule::maLabelAlignmentBaseFormats[0][0]; + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + } + + maTextNodeList.clear(); + maParagraphStyleList.clear(); +} + +void SwNumRule::CheckCharFormats( SwDoc* pDoc ) +{ + for(auto& rpNumFormat : maFormats) + { + if( rpNumFormat ) + { + SwCharFormat* pFormat = rpNumFormat->GetCharFormat(); + if( pFormat && pFormat->GetDoc() != pDoc ) + { + // copy + SwNumFormat* pNew = new SwNumFormat( *rpNumFormat ); + pNew->SetCharFormat( pDoc->CopyCharFormat( *pFormat ) ); + rpNumFormat.reset(pNew); + } + } + } +} + +SwNumRule& SwNumRule::operator=( const SwNumRule& rNumRule ) +{ + if( this != &rNumRule ) + { + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + Set( n, rNumRule.maFormats[ n ].get() ); + + meRuleType = rNumRule.meRuleType; + msName = rNumRule.msName; + mbAutoRuleFlag = rNumRule.mbAutoRuleFlag; + mbInvalidRuleFlag = true; + mbContinusNum = rNumRule.mbContinusNum; + mbAbsSpaces = rNumRule.mbAbsSpaces; + mbHidden = rNumRule.mbHidden; + mnPoolFormatId = rNumRule.GetPoolFormatId(); + mnPoolHelpId = rNumRule.GetPoolHelpId(); + mnPoolHlpFileId = rNumRule.GetPoolHlpFileId(); + } + return *this; +} + +bool SwNumRule::operator==( const SwNumRule& rRule ) const +{ + bool bRet = meRuleType == rRule.meRuleType && + msName == rRule.msName && + mbAutoRuleFlag == rRule.mbAutoRuleFlag && + mbContinusNum == rRule.mbContinusNum && + mbAbsSpaces == rRule.mbAbsSpaces && + mnPoolFormatId == rRule.GetPoolFormatId() && + mnPoolHelpId == rRule.GetPoolHelpId() && + mnPoolHlpFileId == rRule.GetPoolHlpFileId(); + if( bRet ) + { + for( sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + if( rRule.Get( n ) != Get( n ) ) + { + bRet = false; + break; + } + } + return bRet; +} + +void SwNumRule::Set( sal_uInt16 i, const SwNumFormat& rNumFormat ) +{ + OSL_ENSURE( i < MAXLEVEL, "Serious defect" ); + if( i < MAXLEVEL ) + { + if( !maFormats[ i ] || (rNumFormat != Get( i )) ) + { + maFormats[ i ].reset(new SwNumFormat( rNumFormat )); + mbInvalidRuleFlag = true; + } + } +} + +void SwNumRule::Set( sal_uInt16 i, const SwNumFormat* pNumFormat ) +{ + OSL_ENSURE( i < MAXLEVEL, "Serious defect" ); + if( i >= MAXLEVEL ) + return; + if( !maFormats[ i ] ) + { + if( pNumFormat ) + { + maFormats[ i ].reset(new SwNumFormat( *pNumFormat )); + mbInvalidRuleFlag = true; + } + } + else if( !pNumFormat ) + { + maFormats[ i ].reset(); + mbInvalidRuleFlag = true; + } + else if( *maFormats[i] != *pNumFormat ) + { + *maFormats[ i ] = *pNumFormat; + mbInvalidRuleFlag = true; + } +} + +OUString SwNumRule::MakeNumString( const SwNodeNum& rNum, bool bInclStrings ) const +{ + if (rNum.IsCounted()) + return MakeNumString(rNum.GetNumberVector(), bInclStrings); + + return OUString(); +} + +OUString SwNumRule::MakeNumString( const SwNumberTree::tNumberVector & rNumVector, + const bool bInclStrings, + const bool bOnlyArabic, + const unsigned int _nRestrictToThisLevel, + SwNumRule::Extremities* pExtremities, + LanguageType nLang ) const +{ + OUStringBuffer aStr; + + SwNumberTree::tNumberVector::size_type nLevel = rNumVector.size() - 1; + + if ( pExtremities ) + pExtremities->nPrefixChars = pExtremities->nSuffixChars = 0; + + if ( nLevel > _nRestrictToThisLevel ) + { + nLevel = _nRestrictToThisLevel; + } + + if (nLevel < MAXLEVEL) + { + const SwNumFormat& rMyNFormat = Get( static_cast<sal_uInt16>(nLevel) ); + + { + css::lang::Locale aLocale( LanguageTag::convertToLocale(nLang)); + + if (rMyNFormat.HasListFormat()) + { + OUString sLevelFormat = rMyNFormat.GetListFormat(); + // In this case we are ignoring GetIncludeUpperLevels: we put all + // level numbers requested by level format + for (SwNumberTree::tNumberVector::size_type i=0; i <= nLevel; ++i) + { + OUString sReplacement; + if (rNumVector[i]) + { + if (bOnlyArabic) + sReplacement = OUString::number(rNumVector[i]); + else + sReplacement = Get(i).GetNumStr(rNumVector[i], aLocale); + } + else + sReplacement = "0"; // all 0 level are a 0 + + OUString sFind("%" + OUString::number(i + 1)); + sal_Int32 nPosition = sLevelFormat.indexOf(sFind); + if (nPosition >= 0) + sLevelFormat = sLevelFormat.replaceAt(nPosition, sFind.getLength(), sReplacement); + } + + // As a fallback: caller code expects nonempty string as a result. + // But if we have empty string (and had no errors before) this is valid result. + // So use classical hack with zero-width-space as a string filling. + if (sLevelFormat.isEmpty()) + sLevelFormat = OUStringChar(CHAR_ZWSP); + + aStr = sLevelFormat; + } + else + { + // Fallback case: level format is not defined + // So use old way with levels joining by dot "." + SwNumberTree::tNumberVector::size_type i = nLevel; + + if (!IsContinusNum() && + // - do not include upper levels, if level isn't numbered. + rMyNFormat.GetNumberingType() != SVX_NUM_NUMBER_NONE && + rMyNFormat.GetIncludeUpperLevels()) // Just the own level? + { + sal_uInt8 n = rMyNFormat.GetIncludeUpperLevels(); + if (1 < n) + { + if (i + 1 >= n) + i -= n - 1; + else + i = 0; + } + } + + for (; i <= nLevel; ++i) + { + const SwNumFormat& rNFormat = Get(i); + if (SVX_NUM_NUMBER_NONE == rNFormat.GetNumberingType()) + { + // Should 1.1.1 --> 2. NoNum --> 1..1 or 1.1 ?? + // if( i != rNum.nMyLevel ) + // aStr += "."; + continue; + } + + if (rNumVector[i]) + { + if (bOnlyArabic) + aStr.append(OUString::number(rNumVector[i])); + else + aStr.append(rNFormat.GetNumStr(rNumVector[i], aLocale)); + } + else + aStr.append("0"); // all 0 level are a 0 + if (i != nLevel && !aStr.isEmpty()) + aStr.append("."); + } + + // The type doesn't have any number, so don't append + // the post-/prefix string + if (bInclStrings && !bOnlyArabic && + SVX_NUM_CHAR_SPECIAL != rMyNFormat.GetNumberingType() && + SVX_NUM_BITMAP != rMyNFormat.GetNumberingType()) + { + const OUString& sPrefix = rMyNFormat.GetPrefix(); + const OUString& sSuffix = rMyNFormat.GetSuffix(); + + aStr.insert(0, sPrefix); + aStr.append(sSuffix); + if (pExtremities) + { + pExtremities->nPrefixChars = sPrefix.getLength(); + pExtremities->nSuffixChars = sSuffix.getLength(); + } + } + } + } + } + + return aStr.makeStringAndClear(); +} + +OUString SwNumRule::MakeRefNumString( const SwNodeNum& rNodeNum, + const bool bInclSuperiorNumLabels, + const int nRestrictInclToThisLevel ) const +{ + OUString aRefNumStr; + + if ( rNodeNum.GetLevelInListTree() >= 0 ) + { + bool bOldHadPrefix = true; + + const SwNodeNum* pWorkingNodeNum( &rNodeNum ); + do + { + bool bMakeNumStringForPhantom( false ); + if ( pWorkingNodeNum->IsPhantom() ) + { + int nListLevel = pWorkingNodeNum->GetLevelInListTree(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + SwNumFormat aFormat( Get( static_cast<sal_uInt16>(nListLevel) ) ); + bMakeNumStringForPhantom = aFormat.IsEnumeration() && + SVX_NUM_NUMBER_NONE != aFormat.GetNumberingType(); + + } + if ( bMakeNumStringForPhantom || + ( !pWorkingNodeNum->IsPhantom() && + pWorkingNodeNum->GetTextNode() && + pWorkingNodeNum->GetTextNode()->HasNumber() ) ) + { + Extremities aExtremities; + OUString aPrevStr = MakeNumString( pWorkingNodeNum->GetNumberVector(), + true, false, MAXLEVEL, + &aExtremities); + sal_Int32 nStrip = 0; + while ( nStrip < aExtremities.nPrefixChars ) + { + const sal_Unicode c = aPrevStr[nStrip]; + if ( c!='\t' && c!=' ') + break; + ++nStrip; + } + + if (nStrip) + { + aPrevStr = aPrevStr.copy( nStrip ); + aExtremities.nPrefixChars -= nStrip; + } + + if (bOldHadPrefix && + aExtremities.nSuffixChars && + !aExtremities.nPrefixChars + ) + { + aPrevStr = aPrevStr.copy(0, + aPrevStr.getLength() - aExtremities.nSuffixChars); + } + + bOldHadPrefix = ( aExtremities.nPrefixChars > 0); + + aRefNumStr = aPrevStr + aRefNumStr; + } + + if ( bInclSuperiorNumLabels && pWorkingNodeNum->GetLevelInListTree() > 0 ) + { + sal_uInt8 n = Get( static_cast<sal_uInt16>(pWorkingNodeNum->GetLevelInListTree()) ).GetIncludeUpperLevels(); + pWorkingNodeNum = dynamic_cast<SwNodeNum*>(pWorkingNodeNum->GetParent()); + // skip parents, whose list label is already contained in the actual list label. + while ( pWorkingNodeNum && n > 1 ) + { + pWorkingNodeNum = dynamic_cast<SwNodeNum*>(pWorkingNodeNum->GetParent()); + --n; + } + } + else + { + break; + } + } while ( pWorkingNodeNum && + pWorkingNodeNum->GetLevelInListTree() >= 0 && + pWorkingNodeNum->GetLevelInListTree() >= nRestrictInclToThisLevel ); + } + + return aRefNumStr; +} + +OUString SwNumRule::MakeParagraphStyleListString() const +{ + OUString aParagraphStyleListString; + for (const auto& rParagraphStyle : maParagraphStyleList) + { + if (!aParagraphStyleListString.isEmpty()) + aParagraphStyleListString += ", "; + aParagraphStyleListString += rParagraphStyle->GetName(); + } + return aParagraphStyleListString; +} + +/** Copy method of SwNumRule + + A kind of copy constructor, so that the num formats are attached to the + right CharFormats of a Document. + Copies the NumFormats and returns itself. */ +SwNumRule& SwNumRule::CopyNumRule( SwDoc* pDoc, const SwNumRule& rNumRule ) +{ + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + Set( n, rNumRule.maFormats[ n ].get() ); + if( maFormats[ n ] && maFormats[ n ]->GetCharFormat() && + !pDoc->GetCharFormats()->IsAlive(maFormats[n]->GetCharFormat())) + { + // If we copy across different Documents, then copy the + // corresponding CharFormat into the new Document. + maFormats[n]->SetCharFormat( pDoc->CopyCharFormat( *maFormats[n]-> + GetCharFormat() ) ); + } + } + meRuleType = rNumRule.meRuleType; + msName = rNumRule.msName; + mbAutoRuleFlag = rNumRule.mbAutoRuleFlag; + mnPoolFormatId = rNumRule.GetPoolFormatId(); + mnPoolHelpId = rNumRule.GetPoolHelpId(); + mnPoolHlpFileId = rNumRule.GetPoolHlpFileId(); + mbInvalidRuleFlag = true; + return *this; +} + +void SwNumRule::SetSvxRule(const SvxNumRule& rNumRule, SwDoc* pDoc) +{ + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + const SvxNumberFormat* pSvxFormat = rNumRule.Get(n); + maFormats[n].reset( pSvxFormat ? new SwNumFormat(*pSvxFormat, pDoc) : nullptr ); + } + + mbInvalidRuleFlag = true; + mbContinusNum = rNumRule.IsContinuousNumbering(); +} + +SvxNumRule SwNumRule::MakeSvxNumRule() const +{ + SvxNumRule aRule(SvxNumRuleFlags::CONTINUOUS | SvxNumRuleFlags::CHAR_STYLE | + SvxNumRuleFlags::ENABLE_LINKED_BMP | SvxNumRuleFlags::ENABLE_EMBEDDED_BMP, + MAXLEVEL, mbContinusNum, + meRuleType == NUM_RULE ? SvxNumRuleType::NUMBERING : SvxNumRuleType::OUTLINE_NUMBERING ); + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + SwNumFormat aNumFormat = Get(n); + if(aNumFormat.GetCharFormat()) + aNumFormat.SetCharFormatName(aNumFormat.GetCharFormat()->GetName()); + aRule.SetLevel(n, aNumFormat, maFormats[n] != nullptr); + } + return aRule; +} + +void SwNumRule::SetInvalidRule(bool bFlag) +{ + if (bFlag) + { + o3tl::sorted_vector< SwList* > aLists; + for ( const SwTextNode* pTextNode : maTextNodeList ) + { + // #i111681# - applying patch from cmc + SwList* pList = pTextNode->GetDoc()->getIDocumentListsAccess().getListByName( pTextNode->GetListId() ); + OSL_ENSURE( pList, "<SwNumRule::SetInvalidRule(..)> - list at which the text node is registered at does not exist. This is a serious issue."); + if ( pList ) + { + aLists.insert( pList ); + } + } + for ( auto aList : aLists ) + aList->InvalidateListTree(); + } + + mbInvalidRuleFlag = bFlag; +} + +/// change indent of all list levels by given difference +void SwNumRule::ChangeIndent( const sal_Int32 nDiff ) +{ + for ( sal_uInt16 i = 0; i < MAXLEVEL; ++i ) + { + SwNumFormat aTmpNumFormat( Get(i) ); + + const SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode( + aTmpNumFormat.GetPositionAndSpaceMode() ); + if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + auto nNewIndent = nDiff + + aTmpNumFormat.GetAbsLSpace(); + if ( nNewIndent < 0 ) + { + nNewIndent = 0; + } + aTmpNumFormat.SetAbsLSpace( nNewIndent ); + } + else if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + // adjust also the list tab position, if a list tab stop is applied + if ( aTmpNumFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB ) + { + const long nNewListTab = aTmpNumFormat.GetListtabPos() + nDiff; + aTmpNumFormat.SetListtabPos( nNewListTab ); + } + + const long nNewIndent = nDiff + + aTmpNumFormat.GetIndentAt(); + aTmpNumFormat.SetIndentAt( nNewIndent ); + } + + Set( i, aTmpNumFormat ); + } + + SetInvalidRule( true ); +} + +/// set indent of certain list level to given value +void SwNumRule::SetIndent( const short nNewIndent, + const sal_uInt16 nListLevel ) +{ + SwNumFormat aTmpNumFormat( Get(nListLevel) ); + + const SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode( + aTmpNumFormat.GetPositionAndSpaceMode() ); + if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aTmpNumFormat.SetAbsLSpace( nNewIndent ); + } + else if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + // adjust also the list tab position, if a list tab stop is applied + if ( aTmpNumFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB ) + { + const long nNewListTab = aTmpNumFormat.GetListtabPos() + + ( nNewIndent - aTmpNumFormat.GetIndentAt() ); + aTmpNumFormat.SetListtabPos( nNewListTab ); + } + + aTmpNumFormat.SetIndentAt( nNewIndent ); + } + + SetInvalidRule( true ); +} + +/// set indent of first list level to given value and change other list level's +/// indents accordingly +void SwNumRule::SetIndentOfFirstListLevelAndChangeOthers( const short nNewIndent ) +{ + SwNumFormat aTmpNumFormat( Get(0) ); + + sal_Int32 nDiff( 0 ); + const SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode( + aTmpNumFormat.GetPositionAndSpaceMode() ); + if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + nDiff = nNewIndent + - aTmpNumFormat.GetFirstLineOffset() + - aTmpNumFormat.GetAbsLSpace(); + } + else if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + nDiff = nNewIndent - aTmpNumFormat.GetIndentAt(); + } + if ( nDiff != 0 ) + { + ChangeIndent( nDiff ); + } +} + +void SwNumRule::Validate() +{ + o3tl::sorted_vector< SwList* > aLists; + for ( const SwTextNode* pTextNode : maTextNodeList ) + { + aLists.insert( pTextNode->GetDoc()->getIDocumentListsAccess().getListByName( pTextNode->GetListId() ) ); + } + for ( auto aList : aLists ) + aList->ValidateListTree(); + + SetInvalidRule(false); +} + +void SwNumRule::SetCountPhantoms(bool bCountPhantoms) +{ + mbCountPhantoms = bCountPhantoms; +} + +SwNumRule::tParagraphStyleList::size_type SwNumRule::GetParagraphStyleListSize() const +{ + return maParagraphStyleList.size(); +} + +void SwNumRule::AddParagraphStyle( SwTextFormatColl& rTextFormatColl ) +{ + tParagraphStyleList::iterator aIter = + std::find( maParagraphStyleList.begin(), maParagraphStyleList.end(), &rTextFormatColl ); + + if ( aIter == maParagraphStyleList.end() ) + { + maParagraphStyleList.push_back( &rTextFormatColl ); + } +} + +void SwNumRule::RemoveParagraphStyle( SwTextFormatColl& rTextFormatColl ) +{ + tParagraphStyleList::iterator aIter = + std::find( maParagraphStyleList.begin(), maParagraphStyleList.end(), &rTextFormatColl ); + + if ( aIter != maParagraphStyleList.end() ) + { + maParagraphStyleList.erase( aIter ); + } +} + +void SwNumRule::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRule")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("msName"), BAD_CAST(msName.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mnPoolFormatId"), BAD_CAST(OString::number(mnPoolFormatId).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mbAutoRuleFlag"), BAD_CAST(OString::boolean(mbAutoRuleFlag).getStr())); + + for (const auto& pFormat : maFormats) + { + if (!pFormat) + { + continue; + } + + pFormat->dumpAsXml(pWriter); + } + + xmlTextWriterEndElement(pWriter); +} + +void SwNumRule::GetGrabBagItem(uno::Any& rVal) const +{ + if (mpGrabBagItem) + mpGrabBagItem->QueryValue(rVal); + else + rVal <<= uno::Sequence<beans::PropertyValue>(); +} + +void SwNumRule::SetGrabBagItem(const uno::Any& rVal) +{ + if (!mpGrabBagItem) + mpGrabBagItem = std::make_shared<SfxGrabBagItem>(); + + mpGrabBagItem->PutValue(rVal, 0); +} + +namespace numfunc +{ + namespace { + + /** class containing default bullet list configuration data */ + class SwDefBulletConfig : private utl::ConfigItem + { + public: + static SwDefBulletConfig& getInstance(); + + const OUString& GetFontname() const + { + return msFontname; + } + + bool IsFontnameUserDefined() const + { + return mbUserDefinedFontname; + } + + const vcl::Font& GetFont() const + { + return *mpFont; + } + + sal_Unicode GetChar( sal_uInt8 p_nListLevel ) const + { + if (p_nListLevel >= MAXLEVEL) + { + p_nListLevel = MAXLEVEL - 1; + } + + return mnLevelChars[p_nListLevel]; + } + + SwDefBulletConfig(); + + private: + /** sets internal default bullet configuration data to default values */ + void SetToDefault(); + + /** returns sequence of default bullet configuration property names */ + static uno::Sequence<OUString> GetPropNames(); + + /** loads default bullet configuration properties and applies + values to internal data */ + void LoadConfig(); + + /** initialize font instance for default bullet list */ + void InitFont(); + + /** catches notification about changed default bullet configuration data */ + virtual void Notify( const uno::Sequence<OUString>& aPropertyNames ) override; + virtual void ImplCommit() override; + + // default bullet list configuration data + OUString msFontname; + bool mbUserDefinedFontname; + FontWeight meFontWeight; + FontItalic meFontItalic; + sal_Unicode mnLevelChars[MAXLEVEL]; + + // default bullet list font instance + std::unique_ptr<vcl::Font> mpFont; + }; + + class theSwDefBulletConfig + : public rtl::Static<SwDefBulletConfig, theSwDefBulletConfig>{}; + } + + SwDefBulletConfig& SwDefBulletConfig::getInstance() + { + return theSwDefBulletConfig::get(); + } + + SwDefBulletConfig::SwDefBulletConfig() + : ConfigItem( "Office.Writer/Numbering/DefaultBulletList" ), + // default bullet font is now OpenSymbol + msFontname( OUString("OpenSymbol") ), + mbUserDefinedFontname( false ), + meFontWeight( WEIGHT_DONTKNOW ), + meFontItalic( ITALIC_NONE ) + { + SetToDefault(); + LoadConfig(); + InitFont(); + + // enable notification for changes on default bullet configuration change + EnableNotification( GetPropNames() ); + } + + void SwDefBulletConfig::SetToDefault() + { + msFontname = "OpenSymbol"; + mbUserDefinedFontname = false; + meFontWeight = WEIGHT_DONTKNOW; + meFontItalic = ITALIC_NONE; + + mnLevelChars[0] = 0x2022; + mnLevelChars[1] = 0x25e6; + mnLevelChars[2] = 0x25aa; + mnLevelChars[3] = 0x2022; + mnLevelChars[4] = 0x25e6; + mnLevelChars[5] = 0x25aa; + mnLevelChars[6] = 0x2022; + mnLevelChars[7] = 0x25e6; + mnLevelChars[8] = 0x25aa; + mnLevelChars[9] = 0x2022; + } + + uno::Sequence<OUString> SwDefBulletConfig::GetPropNames() + { + uno::Sequence<OUString> aPropNames(13); + OUString* pNames = aPropNames.getArray(); + pNames[0] = "BulletFont/FontFamilyname"; + pNames[1] = "BulletFont/FontWeight"; + pNames[2] = "BulletFont/FontItalic"; + pNames[3] = "BulletCharLvl1"; + pNames[4] = "BulletCharLvl2"; + pNames[5] = "BulletCharLvl3"; + pNames[6] = "BulletCharLvl4"; + pNames[7] = "BulletCharLvl5"; + pNames[8] = "BulletCharLvl6"; + pNames[9] = "BulletCharLvl7"; + pNames[10] = "BulletCharLvl8"; + pNames[11] = "BulletCharLvl9"; + pNames[12] = "BulletCharLvl10"; + + return aPropNames; + } + + void SwDefBulletConfig::LoadConfig() + { + uno::Sequence<OUString> aPropNames = GetPropNames(); + uno::Sequence<uno::Any> aValues = + GetProperties( aPropNames ); + const uno::Any* pValues = aValues.getConstArray(); + OSL_ENSURE( aValues.getLength() == aPropNames.getLength(), + "<SwDefBulletConfig::SwDefBulletConfig()> - GetProperties failed"); + if ( aValues.getLength() == aPropNames.getLength() ) + { + for ( int nProp = 0; nProp < aPropNames.getLength(); ++nProp ) + { + if ( pValues[nProp].hasValue() ) + { + switch ( nProp ) + { + case 0: + { + OUString aStr; + pValues[nProp] >>= aStr; + msFontname = aStr; + mbUserDefinedFontname = true; + } + break; + case 1: + case 2: + { + sal_Int16 nTmp = 0; + pValues[nProp] >>= nTmp; + if ( nProp == 1 ) + meFontWeight = static_cast<FontWeight>(nTmp); + else if ( nProp == 2 ) + meFontItalic = static_cast<FontItalic>(nTmp); + } + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + { + sal_Unicode cChar = sal_Unicode(); + pValues[nProp] >>= cChar; + mnLevelChars[nProp-3] = cChar; + } + break; + } + } + } + } + + } + + void SwDefBulletConfig::InitFont() + { + mpFont.reset( new vcl::Font( msFontname, OUString(), Size( 0, 14 ) ) ); + mpFont->SetWeight( meFontWeight ); + mpFont->SetItalic( meFontItalic ); + mpFont->SetCharSet( RTL_TEXTENCODING_SYMBOL ); + } + + void SwDefBulletConfig::Notify( const uno::Sequence<OUString>& ) + { + SetToDefault(); + LoadConfig(); + InitFont(); + } + + void SwDefBulletConfig::ImplCommit() + { + } + + OUString const & GetDefBulletFontname() + { + return SwDefBulletConfig::getInstance().GetFontname(); + } + + bool IsDefBulletFontUserDefined() + { + return SwDefBulletConfig::getInstance().IsFontnameUserDefined(); + } + + const vcl::Font& GetDefBulletFont() + { + return SwDefBulletConfig::getInstance().GetFont(); + } + + sal_Unicode GetBulletChar( sal_uInt8 nLevel ) + { + return SwDefBulletConfig::getInstance().GetChar( nLevel ); + } + + namespace { + + /** class containing configuration data about user interface behavior + regarding lists and list items. + configuration item about behavior of <TAB>/<SHIFT-TAB>-key at first + position of first list item + */ + class SwNumberingUIBehaviorConfig : private utl::ConfigItem + { + public: + static SwNumberingUIBehaviorConfig& getInstance(); + + bool ChangeIndentOnTabAtFirstPosOfFirstListItem() const + { + return mbChangeIndentOnTabAtFirstPosOfFirstListItem; + } + + SwNumberingUIBehaviorConfig(); + + private: + + /** sets internal configuration data to default values */ + void SetToDefault(); + + /** returns sequence of configuration property names */ + static css::uno::Sequence<OUString> GetPropNames(); + + /** loads configuration properties and applies values to internal data */ + void LoadConfig(); + + /** catches notification about changed configuration data */ + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames ) override; + virtual void ImplCommit() override; + + // configuration data + bool mbChangeIndentOnTabAtFirstPosOfFirstListItem; + }; + + class theSwNumberingUIBehaviorConfig : public rtl::Static<SwNumberingUIBehaviorConfig, theSwNumberingUIBehaviorConfig>{}; + } + + SwNumberingUIBehaviorConfig& SwNumberingUIBehaviorConfig::getInstance() + { + return theSwNumberingUIBehaviorConfig::get(); + } + + SwNumberingUIBehaviorConfig::SwNumberingUIBehaviorConfig() + : ConfigItem( "Office.Writer/Numbering/UserInterfaceBehavior" ), + mbChangeIndentOnTabAtFirstPosOfFirstListItem( true ) + { + SetToDefault(); + LoadConfig(); + + // enable notification for changes on configuration change + EnableNotification( GetPropNames() ); + } + + void SwNumberingUIBehaviorConfig::SetToDefault() + { + mbChangeIndentOnTabAtFirstPosOfFirstListItem = true; + } + + css::uno::Sequence<OUString> SwNumberingUIBehaviorConfig::GetPropNames() + { + css::uno::Sequence<OUString> aPropNames { "ChangeIndentOnTabAtFirstPosOfFirstListItem" }; + + return aPropNames; + } + + void SwNumberingUIBehaviorConfig::ImplCommit() {} + + void SwNumberingUIBehaviorConfig::LoadConfig() + { + css::uno::Sequence<OUString> aPropNames = GetPropNames(); + css::uno::Sequence<css::uno::Any> aValues = GetProperties( aPropNames ); + const css::uno::Any* pValues = aValues.getConstArray(); + OSL_ENSURE( aValues.getLength() == aPropNames.getLength(), + "<SwNumberingUIBehaviorConfig::LoadConfig()> - GetProperties failed"); + if ( aValues.getLength() == aPropNames.getLength() ) + { + for ( int nProp = 0; nProp < aPropNames.getLength(); ++nProp ) + { + if ( pValues[nProp].hasValue() ) + { + switch ( nProp ) + { + case 0: + { + pValues[nProp] >>= mbChangeIndentOnTabAtFirstPosOfFirstListItem; + } + break; + default: + { + OSL_FAIL( "<SwNumberingUIBehaviorConfig::LoadConfig()> - unknown configuration property"); + } + } + } + } + } + } + + void SwNumberingUIBehaviorConfig::Notify( const css::uno::Sequence<OUString>& ) + { + SetToDefault(); + LoadConfig(); + } + + bool ChangeIndentOnTabAtFirstPosOfFirstListItem() + { + return SwNumberingUIBehaviorConfig::getInstance().ChangeIndentOnTabAtFirstPosOfFirstListItem(); + } + + SvxNumberFormat::SvxNumPositionAndSpaceMode GetDefaultPositionAndSpaceMode() + { + if (utl::ConfigManager::IsFuzzing()) + return SvxNumberFormat::LABEL_ALIGNMENT; + + SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode; + SvtSaveOptions aSaveOptions; + switch (aSaveOptions.GetODFSaneDefaultVersion()) + { + case SvtSaveOptions::ODFSVER_010: + case SvtSaveOptions::ODFSVER_011: + { + ePosAndSpaceMode = SvxNumberFormat::LABEL_WIDTH_AND_POSITION; + } + break; + default: // >= ODFSVER_012 + { + ePosAndSpaceMode = SvxNumberFormat::LABEL_ALIGNMENT; + } + } + + return ePosAndSpaceMode; + } +} + +void SwNumRuleTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRuleTable")); + for (SwNumRule* pNumRule : *this) + pNumRule->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/poolfmt.cxx b/sw/source/core/doc/poolfmt.cxx new file mode 100644 index 000000000..326e05eaf --- /dev/null +++ b/sw/source/core/doc/poolfmt.cxx @@ -0,0 +1,301 @@ +/* -*- 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 <hintids.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <osl/diagnose.h> +#include <doc.hxx> +#include <IDocumentState.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <fmtcol.hxx> +#include <numrule.hxx> +#include <swtable.hxx> +#include <tblafmt.hxx> +#include <hints.hxx> + +using namespace ::editeng; +using namespace ::com::sun::star; + +void SetAllScriptItem( SfxItemSet& rSet, const SfxPoolItem& rItem ) +{ + rSet.Put( rItem ); + sal_uInt16 nWhCJK = 0, nWhCTL = 0; + switch( rItem.Which() ) + { + case RES_CHRATR_FONTSIZE: + nWhCJK = RES_CHRATR_CJK_FONTSIZE; + nWhCTL = RES_CHRATR_CTL_FONTSIZE; + break; + case RES_CHRATR_FONT: + nWhCJK = RES_CHRATR_CJK_FONT; + nWhCTL = RES_CHRATR_CTL_FONT; + break; + case RES_CHRATR_LANGUAGE: + nWhCJK = RES_CHRATR_CJK_LANGUAGE; + nWhCTL = RES_CHRATR_CTL_LANGUAGE; + break; + case RES_CHRATR_POSTURE: + nWhCJK = RES_CHRATR_CJK_POSTURE; + nWhCTL = RES_CHRATR_CTL_POSTURE; + break; + case RES_CHRATR_WEIGHT: + nWhCJK = RES_CHRATR_CJK_WEIGHT; + nWhCTL = RES_CHRATR_CTL_WEIGHT; + break; + } + + if( nWhCJK ) + rSet.Put( rItem.CloneSetWhich(nWhCJK) ); + if( nWhCTL ) + rSet.Put( rItem.CloneSetWhich(nWhCTL) ); +} + +/// Return the AutoCollection by its Id. If it doesn't +/// exist yet, create it. +/// If the String pointer is defined, then only query for +/// the Attribute descriptions. It doesn't create a style! +SvxFrameDirection GetDefaultFrameDirection(LanguageType nLanguage) +{ + return MsLangId::isRightToLeft(nLanguage) ? + SvxFrameDirection::Horizontal_RL_TB : SvxFrameDirection::Horizontal_LR_TB; +} + +// See if the Paragraph/Character/Frame/Page style is in use +bool SwDoc::IsUsed( const SwModify& rModify ) const +{ + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + SwAutoFormatGetDocNode aGetHt( &GetNodes() ); + return !rModify.GetInfo( aGetHt ); +} + +// See if Table style is in use +bool SwDoc::IsUsed( const SwTableAutoFormat& rTableAutoFormat) const +{ + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == rTableAutoFormat.GetName()) + return true; + } + return false; +} + +// See if the NumRule is used +bool SwDoc::IsUsed( const SwNumRule& rRule ) +{ + bool bUsed = rRule.GetTextNodeListSize() > 0 || + rRule.GetParagraphStyleListSize() > 0 || + rRule.IsUsedByRedline(); + + return bUsed; +} + +const OUString* SwDoc::GetDocPattern(size_t const nPos) const +{ + if (nPos >= m_PatternNames.size()) + return nullptr; + return &m_PatternNames[nPos]; +} + +// Look for the style name's position. If it doesn't exist, +// insert an anew +size_t SwDoc::SetDocPattern(const OUString& rPatternName) +{ + OSL_ENSURE( !rPatternName.isEmpty(), "no Document style name" ); + + auto const iter( + std::find(m_PatternNames.begin(), m_PatternNames.end(), rPatternName)); + if (iter != m_PatternNames.end()) + { + return std::distance(m_PatternNames.begin(), iter); + } + else + { + m_PatternNames.push_back(rPatternName); + getIDocumentState().SetModified(); + return m_PatternNames.size() - 1; + } +} + +sal_uInt16 GetPoolParent( sal_uInt16 nId ) +{ + sal_uInt16 nRet = USHRT_MAX; + if( POOLGRP_NOCOLLID & nId ) // 1 == Formats / 0 == Collections + { + switch( ( COLL_GET_RANGE_BITS | POOLGRP_NOCOLLID ) & nId ) + { + case POOLGRP_CHARFMT: + case POOLGRP_FRAMEFMT: + nRet = 0; // derived from the default + break; + case POOLGRP_PAGEDESC: + case POOLGRP_NUMRULE: + break; // there are no derivations + } + } + else + { + switch( COLL_GET_RANGE_BITS & nId ) + { + case COLL_TEXT_BITS: + switch( nId ) + { + case RES_POOLCOLL_STANDARD: + nRet = 0; break; + case RES_POOLCOLL_TEXT_IDENT: + case RES_POOLCOLL_TEXT_NEGIDENT: + case RES_POOLCOLL_TEXT_MOVE: + case RES_POOLCOLL_CONFRONTATION: + case RES_POOLCOLL_MARGINAL: + nRet = RES_POOLCOLL_TEXT; break; + + case RES_POOLCOLL_TEXT: + case RES_POOLCOLL_GREETING: + case RES_POOLCOLL_SIGNATURE: + case RES_POOLCOLL_HEADLINE_BASE: + nRet = RES_POOLCOLL_STANDARD; break; + + case RES_POOLCOLL_HEADLINE1: + case RES_POOLCOLL_HEADLINE2: + case RES_POOLCOLL_HEADLINE3: + case RES_POOLCOLL_HEADLINE4: + case RES_POOLCOLL_HEADLINE5: + case RES_POOLCOLL_HEADLINE6: + case RES_POOLCOLL_HEADLINE7: + case RES_POOLCOLL_HEADLINE8: + case RES_POOLCOLL_HEADLINE9: + case RES_POOLCOLL_HEADLINE10: + nRet = RES_POOLCOLL_HEADLINE_BASE; break; + } + break; + + case COLL_LISTS_BITS: + switch( nId ) + { + case RES_POOLCOLL_NUMBER_BULLET_BASE: + nRet = RES_POOLCOLL_TEXT; break; + + default: + nRet = RES_POOLCOLL_NUMBER_BULLET_BASE; break; + } + break; + + case COLL_EXTRA_BITS: + switch( nId ) + { + case RES_POOLCOLL_TABLE_HDLN: + nRet = RES_POOLCOLL_TABLE; break; + + case RES_POOLCOLL_FRAME: + case RES_POOLCOLL_TABLE: + case RES_POOLCOLL_FOOTNOTE: + case RES_POOLCOLL_ENDNOTE: + case RES_POOLCOLL_JAKETADRESS: + case RES_POOLCOLL_SENDADRESS: + case RES_POOLCOLL_HEADERFOOTER: + case RES_POOLCOLL_LABEL: + nRet = RES_POOLCOLL_STANDARD; break; + case RES_POOLCOLL_HEADER: + nRet = RES_POOLCOLL_HEADERFOOTER; break; + case RES_POOLCOLL_HEADERL: + case RES_POOLCOLL_HEADERR: + nRet = RES_POOLCOLL_HEADER; break; + case RES_POOLCOLL_FOOTER: + nRet = RES_POOLCOLL_HEADERFOOTER; break; + case RES_POOLCOLL_FOOTERL: + case RES_POOLCOLL_FOOTERR: + nRet = RES_POOLCOLL_FOOTER; break; + + case RES_POOLCOLL_LABEL_ABB: + case RES_POOLCOLL_LABEL_TABLE: + case RES_POOLCOLL_LABEL_FRAME: + case RES_POOLCOLL_LABEL_DRAWING: + case RES_POOLCOLL_LABEL_FIGURE: + nRet = RES_POOLCOLL_LABEL; break; + } + break; + + case COLL_REGISTER_BITS: + switch( nId ) + { + case RES_POOLCOLL_REGISTER_BASE: + nRet = RES_POOLCOLL_STANDARD; break; + + case RES_POOLCOLL_TOX_IDXH: + nRet = RES_POOLCOLL_HEADLINE_BASE; break; + + case RES_POOLCOLL_TOX_USERH: + case RES_POOLCOLL_TOX_CNTNTH: + case RES_POOLCOLL_TOX_ILLUSH: + case RES_POOLCOLL_TOX_OBJECTH: + case RES_POOLCOLL_TOX_TABLESH: + case RES_POOLCOLL_TOX_AUTHORITIESH: + nRet = RES_POOLCOLL_TOX_IDXH; break; + + default: + nRet = RES_POOLCOLL_REGISTER_BASE; break; + } + break; + + case COLL_DOC_BITS: + nRet = RES_POOLCOLL_HEADLINE_BASE; + break; + + case COLL_HTML_BITS: + nRet = RES_POOLCOLL_STANDARD; + break; + } + } + + return nRet; +} + +void SwDoc::RemoveAllFormatLanguageDependencies() +{ + /* Restore the language independent pool defaults and styles. */ + GetAttrPool().ResetPoolDefaultItem( RES_PARATR_ADJUST ); + + SwTextFormatColl * pTextFormatColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ); + + pTextFormatColl->ResetFormatAttr( RES_PARATR_ADJUST ); + /* koreans do not like SvxScriptItem(TRUE) */ + pTextFormatColl->ResetFormatAttr( RES_PARATR_SCRIPTSPACE ); + + SvxFrameDirectionItem aFrameDir( SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR ); + + size_t nCount = GetPageDescCnt(); + for( size_t i=0; i<nCount; ++i ) + { + SwPageDesc& rDesc = GetPageDesc( i ); + rDesc.GetMaster().SetFormatAttr( aFrameDir ); + rDesc.GetLeft().SetFormatAttr( aFrameDir ); + } + + //#i16874# AutoKerning as default for new documents + GetAttrPool().ResetPoolDefaultItem( RES_CHRATR_AUTOKERN ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/rdfhelper.cxx b/sw/source/core/doc/rdfhelper.cxx new file mode 100644 index 000000000..d404b477e --- /dev/null +++ b/sw/source/core/doc/rdfhelper.cxx @@ -0,0 +1,264 @@ +/* -*- 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 <rdfhelper.hxx> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/rdf/Literal.hpp> +#include <com/sun/star/rdf/Statement.hpp> +#include <com/sun/star/rdf/URI.hpp> +#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp> + +#include <comphelper/processfactory.hxx> + +#include <doc.hxx> +#include <docsh.hxx> +#include <ndtxt.hxx> +#include <unoparagraph.hxx> + +using namespace com::sun::star; + +css::uno::Sequence<css::uno::Reference<css::rdf::XURI>> SwRDFHelper::getGraphNames( + const css::uno::Reference<rdf::XDocumentMetadataAccess>& xDocumentMetadataAccess, + const css::uno::Reference<rdf::XURI>& xType) +{ + try + { + return xDocumentMetadataAccess->getMetadataGraphsWithType(xType); + } + catch (const uno::RuntimeException&) + { + return uno::Sequence<uno::Reference<rdf::XURI>>(); + } +} + +css::uno::Sequence<uno::Reference<css::rdf::XURI>> +SwRDFHelper::getGraphNames(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType) +{ + try + { + uno::Reference<uno::XComponentContext> xComponentContext( + comphelper::getProcessComponentContext()); + // rdf::URI::create may fail with type: com.sun.star.uno.DeploymentException + // message: component context fails to supply service com.sun.star.rdf.URI of type com.sun.star.rdf.XURI + // context: cppu::ComponentContext + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, + uno::UNO_QUERY); + return getGraphNames(xDocumentMetadataAccess, xType); + } + catch (const ::css::uno::Exception&) + { + return uno::Sequence<uno::Reference<rdf::XURI>>(); + } +} + +std::map<OUString, OUString> +SwRDFHelper::getStatements(const css::uno::Reference<css::frame::XModel>& xModel, + const uno::Sequence<uno::Reference<css::rdf::XURI>>& rGraphNames, + const css::uno::Reference<css::rdf::XResource>& xSubject) +{ + std::map<OUString, OUString> aRet; + if (!rGraphNames.hasElements()) + return aRet; + + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Reference<rdf::XRepository>& xRepo = xDocumentMetadataAccess->getRDFRepository(); + for (const uno::Reference<rdf::XURI>& xGraphName : rGraphNames) + { + uno::Reference<rdf::XNamedGraph> xGraph = xRepo->getGraph(xGraphName); + if (!xGraph.is()) + continue; + + uno::Reference<container::XEnumeration> xStatements = xGraph->getStatements( + xSubject, uno::Reference<rdf::XURI>(), uno::Reference<rdf::XURI>()); + while (xStatements->hasMoreElements()) + { + const rdf::Statement aStatement = xStatements->nextElement().get<rdf::Statement>(); + aRet[aStatement.Predicate->getStringValue()] = aStatement.Object->getStringValue(); + } + } + + return aRet; +} + +std::map<OUString, OUString> +SwRDFHelper::getStatements(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSubject) +{ + return getStatements(xModel, getGraphNames(xModel, rType), xSubject); +} + +void SwRDFHelper::addStatement(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, const OUString& rPath, + const css::uno::Reference<css::rdf::XResource>& xSubject, + const OUString& rKey, const OUString& rValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + uno::Reference<rdf::XURI> xGraphName; + if (aGraphNames.hasElements()) + xGraphName = aGraphNames[0]; + else + { + uno::Sequence< uno::Reference<rdf::XURI> > xTypes = { xType }; + xGraphName = xDocumentMetadataAccess->addMetadataFile(rPath, xTypes); + } + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, rValue); + xGraph->addStatement(xSubject, xKey, xValue); +} + +bool SwRDFHelper::hasMetadataGraph(const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rType) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + return getGraphNames(xDocumentMetadataAccess, xType).hasElements(); +} + +void SwRDFHelper::removeStatement(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSubject, + const OUString& rKey, const OUString& rValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(aGraphNames[0]); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, rValue); + xGraph->removeStatements(xSubject, xKey, xValue); +} + +void SwRDFHelper::clearStatements(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSubject) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + for (const uno::Reference<rdf::XURI>& xGraphName : aGraphNames) + { + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<container::XEnumeration> xStatements = xGraph->getStatements(xSubject, uno::Reference<rdf::XURI>(), uno::Reference<rdf::XURI>()); + while (xStatements->hasMoreElements()) + { + rdf::Statement aStatement = xStatements->nextElement().get<rdf::Statement>(); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, aStatement.Predicate->getStringValue()); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, aStatement.Object->getStringValue()); + xGraph->removeStatements(xSubject, xKey, xValue); + } + } +} + +void SwRDFHelper::cloneStatements(const css::uno::Reference<css::frame::XModel>& xSrcModel, + const css::uno::Reference<css::frame::XModel>& xDstModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSrcSubject, + const css::uno::Reference<css::rdf::XResource>& xDstSubject) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xSrcModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + for (const uno::Reference<rdf::XURI>& xGraphName : aGraphNames) + { + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<container::XEnumeration> xStatements = xGraph->getStatements(xSrcSubject, uno::Reference<rdf::XURI>(), uno::Reference<rdf::XURI>()); + while (xStatements->hasMoreElements()) + { + const rdf::Statement aStatement = xStatements->nextElement().get<rdf::Statement>(); + + const OUString sKey = aStatement.Predicate->getStringValue(); + const OUString sValue = aStatement.Object->getStringValue(); + addStatement(xDstModel, rType, xGraphName->getLocalName(), xDstSubject, sKey, sValue); + } + } +} + +std::map<OUString, OUString> SwRDFHelper::getTextNodeStatements(const OUString& rType, SwTextNode& rTextNode) +{ + uno::Reference<rdf::XResource> xTextNode(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + return getStatements(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), rType, xTextNode); +} + +void SwRDFHelper::addTextNodeStatement(const OUString& rType, const OUString& rPath, SwTextNode& rTextNode, const OUString& rKey, const OUString& rValue) +{ + uno::Reference<rdf::XResource> xSubject(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + addStatement(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), rType, rPath, xSubject, rKey, rValue); +} + +void SwRDFHelper::removeTextNodeStatement(const OUString& rType, SwTextNode& rTextNode, const OUString& rKey, const OUString& rValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + uno::Reference<rdf::XURI> xGraphName = aGraphNames[0]; + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XResource> xSubject(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, rValue); + xGraph->removeStatements(xSubject, xKey, xValue); +} + +void SwRDFHelper::updateTextNodeStatement(const OUString& rType, const OUString& rPath, SwTextNode& rTextNode, const OUString& rKey, const OUString& rOldValue, const OUString& rNewValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + uno::Reference<rdf::XURI> xGraphName; + if (aGraphNames.hasElements()) + { + xGraphName = aGraphNames[0]; + } + else + { + uno::Sequence< uno::Reference<rdf::XURI> > xTypes = { xType }; + xGraphName = xDocumentMetadataAccess->addMetadataFile(rPath, xTypes); + } + + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XResource> xSubject(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + + if (aGraphNames.hasElements()) + { + // Remove the old value. + uno::Reference<rdf::XLiteral> xOldValue = rdf::Literal::create(xComponentContext, rOldValue); + xGraph->removeStatements(xSubject, xKey, xOldValue); + } + + // Now add it with new value. + uno::Reference<rdf::XLiteral> xNewValue = rdf::Literal::create(xComponentContext, rNewValue); + xGraph->addStatement(xSubject, xKey, xNewValue); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/sortopt.cxx b/sw/source/core/doc/sortopt.cxx new file mode 100644 index 000000000..06ac05856 --- /dev/null +++ b/sw/source/core/doc/sortopt.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <i18nlangtag/lang.h> +#include <sortopt.hxx> + +SwSortKey::SwSortKey() : + eSortOrder( SwSortOrder::Ascending ), + nColumnId( 0 ), + bIsNumeric( true ) +{ +} + +SwSortKey::SwSortKey(sal_uInt16 nId, const OUString& rSrtType, SwSortOrder eOrder) : + sSortType( rSrtType ), + eSortOrder( eOrder ), + nColumnId( nId ), + bIsNumeric( rSrtType.isEmpty() ) +{ +} + +SwSortOptions::SwSortOptions() + : eDirection( SwSortDirection::Rows ), + cDeli( 9 ), + nLanguage( LANGUAGE_SYSTEM ), + bTable( false ), + bIgnoreCase( false ) +{ +} + +SwSortOptions::SwSortOptions(const SwSortOptions& rOpt) : + eDirection( rOpt.eDirection ), + cDeli( rOpt.cDeli ), + nLanguage( rOpt.nLanguage ), + bTable( rOpt.bTable ), + bIgnoreCase( rOpt.bIgnoreCase ) +{ + for(auto const & pKey : rOpt.aKeys) + { + aKeys.push_back( std::make_unique<SwSortKey>(*pKey) ); + } +} + +SwSortOptions::~SwSortOptions() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/swserv.cxx b/sw/source/core/doc/swserv.cxx new file mode 100644 index 000000000..95b3982ea --- /dev/null +++ b/sw/source/core/doc/swserv.cxx @@ -0,0 +1,320 @@ +/* -*- 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 <sot/exchange.hxx> +#include <sfx2/linkmgr.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <swserv.hxx> +#include <swbaslnk.hxx> +#include <mvsave.hxx> +#include <IMark.hxx> +#include <bookmrk.hxx> +#include <pam.hxx> +#include <shellio.hxx> + +using namespace ::com::sun::star; + +SwServerObject::~SwServerObject() +{ +} + +bool SwServerObject::GetData( uno::Any & rData, + const OUString & rMimeType, bool ) +{ + bool bRet = false; + WriterRef xWrt; + switch( SotExchange::GetFormatIdFromMimeType( rMimeType ) ) + { + case SotClipboardFormatId::STRING: + ::GetASCWriter( OUString(), OUString(), xWrt ); + break; + + case SotClipboardFormatId::RTF: + case SotClipboardFormatId::RICHTEXT: + // mba: no BaseURL for data exchange + ::GetRTFWriter( OUString(), OUString(), xWrt ); + break; + default: break; + } + + if( xWrt.is() ) + { + SwPaM* pPam = nullptr; + switch( m_eType ) + { + case BOOKMARK_SERVER: + if( m_CNTNT_TYPE.pBkmk->IsExpanded() ) + { + // Span area + pPam = new SwPaM( m_CNTNT_TYPE.pBkmk->GetMarkPos(), + m_CNTNT_TYPE.pBkmk->GetOtherMarkPos() ); + } + break; + + case TABLE_SERVER: + pPam = new SwPaM( *m_CNTNT_TYPE.pTableNd, + *m_CNTNT_TYPE.pTableNd->EndOfSectionNode() ); + break; + + case SECTION_SERVER: + pPam = new SwPaM( SwPosition( *m_CNTNT_TYPE.pSectNd ) ); + pPam->Move( fnMoveForward ); + pPam->SetMark(); + pPam->GetPoint()->nNode = *m_CNTNT_TYPE.pSectNd->EndOfSectionNode(); + pPam->Move( fnMoveBackward ); + break; + case NONE_SERVER: break; + } + + if( pPam ) + { + // Create stream + SvMemoryStream aMemStm( 65535, 65535 ); + SwWriter aWrt( aMemStm, *pPam, false ); + if( !aWrt.Write( xWrt ).IsError() ) + { + aMemStm.WriteChar( '\0' ); // append a zero char + rData <<= uno::Sequence< sal_Int8 >( + static_cast<sal_Int8 const *>(aMemStm.GetData()), + aMemStm.Tell() ); + bRet = true; + } + delete pPam; + } + } + return bRet; +} + +void SwServerObject::SendDataChanged( const SwPosition& rPos ) +{ + // Is someone interested in our changes? + if( HasDataLinks() ) + { + bool bCall = false; + const SwStartNode* pNd = nullptr; + switch( m_eType ) + { + case BOOKMARK_SERVER: + if( m_CNTNT_TYPE.pBkmk->IsExpanded() ) + { + bCall = m_CNTNT_TYPE.pBkmk->GetMarkStart() <= rPos + && rPos < m_CNTNT_TYPE.pBkmk->GetMarkEnd(); + } + break; + + case TABLE_SERVER: pNd = m_CNTNT_TYPE.pTableNd; break; + case SECTION_SERVER: pNd = m_CNTNT_TYPE.pSectNd; break; + case NONE_SERVER: break; + } + if( pNd ) + { + sal_uLong nNd = rPos.nNode.GetIndex(); + bCall = pNd->GetIndex() < nNd && nNd < pNd->EndOfSectionIndex(); + } + + if( bCall ) + { + // Recognize recursions and flag them + IsLinkInServer( nullptr ); + SvLinkSource::NotifyDataChanged(); + } + } +} + +void SwServerObject::SendDataChanged( const SwPaM& rRange ) +{ + // Is someone interested in our changes? + if( HasDataLinks() ) + { + bool bCall = false; + const SwStartNode* pNd = nullptr; + const SwPosition* pStt = rRange.Start(), *pEnd = rRange.End(); + switch( m_eType ) + { + case BOOKMARK_SERVER: + if(m_CNTNT_TYPE.pBkmk->IsExpanded()) + { + bCall = *pStt <= m_CNTNT_TYPE.pBkmk->GetMarkEnd() + && *pEnd > m_CNTNT_TYPE.pBkmk->GetMarkStart(); + } + break; + + case TABLE_SERVER: pNd = m_CNTNT_TYPE.pTableNd; break; + case SECTION_SERVER: pNd = m_CNTNT_TYPE.pSectNd; break; + case NONE_SERVER: break; + } + if( pNd ) + { + // Is the start area within the node area? + bCall = pStt->nNode.GetIndex() < pNd->EndOfSectionIndex() && + pEnd->nNode.GetIndex() >= pNd->GetIndex(); + } + + if( bCall ) + { + // Recognize recursions and flag them + IsLinkInServer( nullptr ); + SvLinkSource::NotifyDataChanged(); + } + } +} + +bool SwServerObject::IsLinkInServer( const SwBaseLink* pChkLnk ) const +{ + sal_uLong nSttNd = 0, nEndNd = 0; + const SwNode* pNd = nullptr; + const SwNodes* pNds = nullptr; + + switch( m_eType ) + { + case BOOKMARK_SERVER: + if( m_CNTNT_TYPE.pBkmk->IsExpanded() ) + { + const SwPosition* pStt = &m_CNTNT_TYPE.pBkmk->GetMarkStart(), + * pEnd = &m_CNTNT_TYPE.pBkmk->GetMarkEnd(); + + nSttNd = pStt->nNode.GetIndex(); + nEndNd = pEnd->nNode.GetIndex(); + pNds = &pStt->nNode.GetNodes(); + } + break; + + case TABLE_SERVER: pNd = m_CNTNT_TYPE.pTableNd; break; + case SECTION_SERVER: pNd = m_CNTNT_TYPE.pSectNd; break; + + case SECTION_SERVER+1: + return true; + } + + if( pNd ) + { + nSttNd = pNd->GetIndex(); + nEndNd = pNd->EndOfSectionIndex(); + pNds = &pNd->GetNodes(); + } + + if( nSttNd && nEndNd ) + { + // Get LinkManager + const ::sfx2::SvBaseLinks& rLnks = pNds->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + + // To avoid recursions: convert ServerType! + SwServerObject::ServerModes eSave = m_eType; + if( !pChkLnk ) + const_cast<SwServerObject*>(this)->m_eType = NONE_SERVER; + for( size_t n = rLnks.size(); n; ) + { + const ::sfx2::SvBaseLink* pLnk = &(*rLnks[ --n ]); + if (sfx2::SvBaseLinkObjectType::ClientGraphic != pLnk->GetObjType() && + dynamic_cast<const SwBaseLink*>( pLnk) != nullptr && + !static_cast<const SwBaseLink*>(pLnk)->IsNoDataFlag() && + static_cast<const SwBaseLink*>(pLnk)->IsInRange( nSttNd, nEndNd )) + { + if( pChkLnk ) + { + if( pLnk == pChkLnk || + static_cast<const SwBaseLink*>(pLnk)->IsRecursion( pChkLnk ) ) + return true; + } + else if( static_cast<const SwBaseLink*>(pLnk)->IsRecursion( static_cast<const SwBaseLink*>(pLnk) ) ) + const_cast<SwBaseLink*>(static_cast<const SwBaseLink*>(pLnk))->SetNoDataFlag(); + } + } + if( !pChkLnk ) + const_cast<SwServerObject*>(this)->m_eType = eSave; + } + + return false; +} + +void SwServerObject::SetNoServer() +{ + if(m_eType == BOOKMARK_SERVER && m_CNTNT_TYPE.pBkmk) + { + ::sw::mark::DdeBookmark* const pDdeBookmark = dynamic_cast< ::sw::mark::DdeBookmark* >(m_CNTNT_TYPE.pBkmk); + if(pDdeBookmark) + { + m_CNTNT_TYPE.pBkmk = nullptr; + m_eType = NONE_SERVER; + pDdeBookmark->SetRefObject(nullptr); + } + } +} + +void SwServerObject::SetDdeBookmark( ::sw::mark::IMark& rBookmark) +{ + ::sw::mark::DdeBookmark* const pDdeBookmark = dynamic_cast< ::sw::mark::DdeBookmark* >(&rBookmark); + if(pDdeBookmark) + { + m_eType = BOOKMARK_SERVER; + m_CNTNT_TYPE.pBkmk = &rBookmark; + pDdeBookmark->SetRefObject(this); + } + else + OSL_FAIL("SwServerObject::SetNoServer(..)" + " - setting a bookmark that is not DDE-capable"); +} + +SwDataChanged::SwDataChanged( const SwPaM& rPam ) + : m_pPam( &rPam ), m_pPos( nullptr ), m_pDoc( rPam.GetDoc() ) +{ + m_nContent = rPam.GetPoint()->nContent.GetIndex(); +} + +SwDataChanged::SwDataChanged( SwDoc* pDc, const SwPosition& rPos ) + : m_pPam( nullptr ), m_pPos( &rPos ), m_pDoc( pDc ) +{ + m_nContent = rPos.nContent.GetIndex(); +} + +SwDataChanged::~SwDataChanged() +{ + // JP 09.04.96: Only if the Layout is available (thus during input) + if( m_pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + const ::sfx2::SvLinkSources& rServers = m_pDoc->getIDocumentLinksAdministration().GetLinkManager().GetServers(); + + ::sfx2::SvLinkSources aTemp(rServers); + for( const auto& rpLinkSrc : aTemp ) + { + ::sfx2::SvLinkSourceRef refObj( rpLinkSrc ); + // Anyone else interested in the Object? + if( refObj->HasDataLinks() && dynamic_cast<const SwServerObject*>( refObj.get() ) != nullptr) + { + SwServerObject& rObj = *static_cast<SwServerObject*>( refObj.get() ); + if( m_pPos ) + rObj.SendDataChanged( *m_pPos ); + else + rObj.SendDataChanged( *m_pPam ); + } + + // We shouldn't have a connection anymore + if( !refObj->HasDataLinks() ) + { + // Then remove from the list + m_pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( rpLinkSrc ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/swstylemanager.cxx b/sw/source/core/doc/swstylemanager.cxx new file mode 100644 index 000000000..cfca5ee5b --- /dev/null +++ b/sw/source/core/doc/swstylemanager.cxx @@ -0,0 +1,158 @@ +/* -*- 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 "swstylemanager.hxx" +#include <svl/stylepool.hxx> +#include <istyleaccess.hxx> +#include <unordered_map> +#include <osl/diagnose.h> + +typedef std::unordered_map< OUString, + std::shared_ptr<SfxItemSet> > SwStyleNameCache; + +namespace { + +class SwStyleCache +{ + SwStyleNameCache mMap; +public: + SwStyleCache() {} + void addStyleName( const std::shared_ptr<SfxItemSet>& pStyle ) + { mMap[ StylePool::nameOf(pStyle) ] = pStyle; } + void addCompletePool( StylePool& rPool ); + std::shared_ptr<SfxItemSet> getByName( const OUString& rName ) { return mMap[rName]; } +}; + +} + +void SwStyleCache::addCompletePool( StylePool& rPool ) +{ + std::unique_ptr<IStylePoolIteratorAccess> pIter = rPool.createIterator(); + std::shared_ptr<SfxItemSet> pStyle = pIter->getNext(); + while( pStyle ) + { + OUString aName( StylePool::nameOf(pStyle) ); + mMap[ aName ] = pStyle; + pStyle = pIter->getNext(); + } +} + +namespace { + +class SwStyleManager : public IStyleAccess +{ + StylePool aAutoCharPool; + StylePool aAutoParaPool; + std::unique_ptr<SwStyleCache> mpCharCache; + std::unique_ptr<SwStyleCache> mpParaCache; + +public: + // accept empty item set for ignorable paragraph items. + explicit SwStyleManager( SfxItemSet const * pIgnorableParagraphItems ) + : aAutoCharPool(), + aAutoParaPool( pIgnorableParagraphItems ) + {} + virtual std::shared_ptr<SfxItemSet> getAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName = nullptr ) override; + virtual std::shared_ptr<SfxItemSet> getByName( const OUString& rName, + IStyleAccess::SwAutoStyleFamily eFamily ) override; + virtual void getAllStyles( std::vector<std::shared_ptr<SfxItemSet>> &rStyles, + IStyleAccess::SwAutoStyleFamily eFamily ) override; + virtual std::shared_ptr<SfxItemSet> cacheAutomaticStyle( const SfxItemSet& rSet, + SwAutoStyleFamily eFamily ) override; + virtual void clearCaches() override; +}; + +} + +std::unique_ptr<IStyleAccess> createStyleManager( SfxItemSet const * pIgnorableParagraphItems ) +{ + return std::make_unique<SwStyleManager>( pIgnorableParagraphItems ); +} + +void SwStyleManager::clearCaches() +{ + mpCharCache.reset(); + mpParaCache.reset(); +} + +std::shared_ptr<SfxItemSet> SwStyleManager::getAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + return rAutoPool.insertItemSet( rSet, pParentName ); +} + +std::shared_ptr<SfxItemSet> SwStyleManager::cacheAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + std::shared_ptr<SfxItemSet> pStyle = rAutoPool.insertItemSet( rSet ); + if (eFamily == IStyleAccess::AUTO_STYLE_CHAR) + { + if (!mpCharCache) + mpCharCache.reset(new SwStyleCache()); + mpCharCache->addStyleName( pStyle ); + } + else + { + if (!mpParaCache) + mpParaCache.reset(new SwStyleCache()); + mpParaCache->addStyleName( pStyle ); + } + return pStyle; +} + +std::shared_ptr<SfxItemSet> SwStyleManager::getByName( const OUString& rName, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + std::unique_ptr<SwStyleCache> &rpCache = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? mpCharCache : mpParaCache; + if( !rpCache ) + rpCache.reset(new SwStyleCache()); + std::shared_ptr<SfxItemSet> pStyle = rpCache->getByName( rName ); + if( !pStyle ) + { + // Ok, ok, it's allowed to ask for uncached styles (from UNO) but it should not be done + // during loading a document + OSL_FAIL( "Don't ask for uncached styles" ); + rpCache->addCompletePool( rAutoPool ); + pStyle = rpCache->getByName( rName ); + } + return pStyle; +} + +void SwStyleManager::getAllStyles( std::vector<std::shared_ptr<SfxItemSet>> &rStyles, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + // setup <StylePool> iterator, which skips unused styles and ignorable items + std::unique_ptr<IStylePoolIteratorAccess> pIter = rAutoPool.createIterator( true, true ); + std::shared_ptr<SfxItemSet> pStyle = pIter->getNext(); + while( pStyle ) + { + rStyles.push_back( pStyle ); + + pStyle = pIter->getNext(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/swstylemanager.hxx b/sw/source/core/doc/swstylemanager.hxx new file mode 100644 index 000000000..b1301b2f1 --- /dev/null +++ b/sw/source/core/doc/swstylemanager.hxx @@ -0,0 +1,32 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_DOC_SWSTYLEMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_DOC_SWSTYLEMANAGER_HXX + +#include <memory> + +class IStyleAccess; +class SfxItemSet; + +std::unique_ptr<IStyleAccess> createStyleManager( SfxItemSet const * pIgnorableParagraphItems ); + +#endif // INCLUDED_SW_SOURCE_CORE_DOC_SWSTYLEMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/tblafmt.cxx b/sw/source/core/doc/tblafmt.cxx new file mode 100644 index 000000000..293e87898 --- /dev/null +++ b/sw/source/core/doc/tblafmt.cxx @@ -0,0 +1,1251 @@ +/* -*- 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 <comphelper/fileformat.h> +#include <tools/stream.hxx> +#include <sfx2/docfile.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/pathoptions.hxx> +#include <swtable.hxx> +#include <swtblfmt.hxx> +#include <com/sun/star/text/VertOrientation.hpp> +#include <swtypes.hxx> +#include <doc.hxx> +#include <poolfmt.hxx> +#include <tblafmt.hxx> +#include <cellatr.hxx> +#include <SwStyleNameMapper.hxx> +#include <hintids.hxx> +#include <fmtornt.hxx> +#include <editsh.hxx> +#include <fmtlsplt.hxx> +#include <fmtrowsplt.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include <editeng/adjustitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/legacyitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <svx/algitem.hxx> +#include <svx/rotmodit.hxx> +#include <legacyitem.hxx> + +#include <memory> +#include <vector> + +/* + * XXX: BIG RED NOTICE! Changes MUST be binary file format compatible and MUST + * be synchronized with Calc's ScAutoFormat sc/source/core/tool/autoform.cxx + */ + +using ::editeng::SvxBorderLine; + +// until SO5PF +const sal_uInt16 AUTOFORMAT_ID_X = 9501; +const sal_uInt16 AUTOFORMAT_ID_358 = 9601; +const sal_uInt16 AUTOFORMAT_DATA_ID_X = 9502; + +// from SO5 +//! In follow-up versions these IDs' values need to increase +const sal_uInt16 AUTOFORMAT_ID_504 = 9801; +const sal_uInt16 AUTOFORMAT_DATA_ID_504 = 9802; + +const sal_uInt16 AUTOFORMAT_DATA_ID_552 = 9902; + +// --- from 680/dr25 on: store strings as UTF-8 +const sal_uInt16 AUTOFORMAT_ID_680DR25 = 10021; + +// --- Bug fix to fdo#31005: Table Autoformats does not save/apply all properties (Writer and Calc) +const sal_uInt16 AUTOFORMAT_ID_31005 = 10041; +const sal_uInt16 AUTOFORMAT_DATA_ID_31005 = 10042; + +// current version +const sal_uInt16 AUTOFORMAT_ID = AUTOFORMAT_ID_31005; +const sal_uInt16 AUTOFORMAT_DATA_ID = AUTOFORMAT_DATA_ID_31005; +const sal_uInt16 AUTOFORMAT_FILE_VERSION= SOFFICE_FILEFORMAT_50; + +SwBoxAutoFormat* SwTableAutoFormat::pDfltBoxAutoFormat = nullptr; + +#define AUTOTABLE_FORMAT_NAME "autotbl.fmt" + +namespace +{ + /// Begins a writer-specific data block. Call before serializing any writer-specific properties. + sal_uInt64 BeginSwBlock(SvStream& rStream) + { + // We need to write down the offset of the end of the writer-specific data, so that + // calc can skip it. We'll only have that value after writing the data, so we + // write a placeholder value first, write the data, then jump back and write the + // real offset. + + // Note that we explicitly use sal_uInt64 instead of sal_Size (which can be 32 + // or 64 depending on platform) to ensure 64-bit portability on this front. I don't + // actually know if autotbl.fmt as a whole is portable, since that requires all serialization + // logic to be written with portability in mind. + sal_uInt64 whereToWriteEndOfSwBlock = rStream.Tell(); + + rStream.WriteUInt64( 0 ); // endOfSwBlock + + return whereToWriteEndOfSwBlock; + } + + /// Ends a writer-specific data block. Call after serializing writer-specific properties. + /// Closes a corresponding BeginSwBlock call. + void EndSwBlock(SvStream& rStream, sal_uInt64 whereToWriteEndOfSwBlock) + { + sal_uInt64 endOfSwBlock = rStream.Tell(); + rStream.Seek(whereToWriteEndOfSwBlock); + rStream.WriteUInt64( endOfSwBlock ); + rStream.Seek(endOfSwBlock); + } + + /** + Helper class for writer-specific blocks. Begins a writer-specific block on construction, + and closes it on destruction. + + See also: BeginSwBlock and EndSwBlock. + */ + class WriterSpecificAutoFormatBlock + { + public: + explicit WriterSpecificAutoFormatBlock(SvStream &rStream) : _rStream(rStream), _whereToWriteEndOfBlock(BeginSwBlock(rStream)) + { + } + + ~WriterSpecificAutoFormatBlock() + { + EndSwBlock(_rStream, _whereToWriteEndOfBlock); + } + + private: + WriterSpecificAutoFormatBlock(WriterSpecificAutoFormatBlock const&) = delete; + WriterSpecificAutoFormatBlock& operator=(WriterSpecificAutoFormatBlock const&) = delete; + + SvStream &_rStream; + sal_uInt64 _whereToWriteEndOfBlock; + }; + + /// Checks whether a writer-specific block exists (i.e. size is not zero) + sal_Int64 WriterSpecificBlockExists(SvStream &stream) + { + sal_uInt64 endOfSwBlock = 0; + stream.ReadUInt64( endOfSwBlock ); + + // end-of-block pointing to itself indicates a zero-size block. + return endOfSwBlock - stream.Tell(); + } +} + +// Struct with version numbers of the Items + +struct SwAfVersions : public AutoFormatVersions +{ +public: + sal_uInt16 m_nTextOrientationVersion; + sal_uInt16 m_nVerticalAlignmentVersion; + + SwAfVersions(); + void Load( SvStream& rStream, sal_uInt16 nVer ); + static void Write(SvStream& rStream, sal_uInt16 fileVersion); +}; + +SwAfVersions::SwAfVersions() +: AutoFormatVersions(), + m_nTextOrientationVersion(0), + m_nVerticalAlignmentVersion(0) +{ +} + +void SwAfVersions::Load( SvStream& rStream, sal_uInt16 nVer ) +{ + LoadBlockA(rStream, nVer); + if (nVer >= AUTOFORMAT_ID_31005 && WriterSpecificBlockExists(rStream)) + { + rStream.ReadUInt16( m_nTextOrientationVersion ); + rStream.ReadUInt16( m_nVerticalAlignmentVersion ); + } + LoadBlockB(rStream, nVer); +} + +void SwAfVersions::Write(SvStream& rStream, sal_uInt16 fileVersion) +{ + AutoFormatVersions::WriteBlockA(rStream, fileVersion); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + { + WriterSpecificAutoFormatBlock block(rStream); + + rStream.WriteUInt16(legacy::SvxFrameDirection::GetVersion(fileVersion)); + rStream.WriteUInt16(legacy::SwFormatVert::GetVersion(fileVersion)); + } + + AutoFormatVersions::WriteBlockB(rStream, fileVersion); +} + + + +SwBoxAutoFormat::SwBoxAutoFormat() +: AutoFormatBase(), + m_aTextOrientation(std::make_unique<SvxFrameDirectionItem>(SvxFrameDirection::Environment, RES_FRAMEDIR)), + m_aVerticalAlignment(std::make_unique<SwFormatVertOrient>(0, css::text::VertOrientation::NONE, css::text::RelOrientation::FRAME)), + m_sNumFormatString(), + m_eSysLanguage(::GetAppLanguage()), + m_eNumFormatLanguage(::GetAppLanguage()), + m_wXObject() +{ + // need to set default instances for base class AutoFormatBase here + // due to resource defines (e.g. RES_CHRATR_FONT) which are not available + // in svx and different in the different usages of derivations + m_aFont = std::make_unique<SvxFontItem>(*GetDfltAttr( RES_CHRATR_FONT ) ); + m_aHeight = std::make_unique<SvxFontHeightItem>(240, 100, RES_CHRATR_FONTSIZE ); + m_aWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, RES_CHRATR_WEIGHT ); + m_aPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, RES_CHRATR_POSTURE ); + m_aCJKFont = std::make_unique<SvxFontItem>(*GetDfltAttr( RES_CHRATR_CJK_FONT ) ); + m_aCJKHeight = std::make_unique<SvxFontHeightItem>(240, 100, RES_CHRATR_CJK_FONTSIZE ); + m_aCJKWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, RES_CHRATR_CJK_WEIGHT ); + m_aCJKPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, RES_CHRATR_CJK_POSTURE ); + m_aCTLFont = std::make_unique<SvxFontItem>(*GetDfltAttr( RES_CHRATR_CTL_FONT ) ); + m_aCTLHeight = std::make_unique<SvxFontHeightItem>(240, 100, RES_CHRATR_CTL_FONTSIZE ); + m_aCTLWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, RES_CHRATR_CTL_WEIGHT ); + m_aCTLPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, RES_CHRATR_CTL_POSTURE ); + m_aUnderline = std::make_unique<SvxUnderlineItem>(LINESTYLE_NONE, RES_CHRATR_UNDERLINE ); + m_aOverline = std::make_unique<SvxOverlineItem>(LINESTYLE_NONE, RES_CHRATR_OVERLINE ); + m_aCrossedOut = std::make_unique<SvxCrossedOutItem>(STRIKEOUT_NONE, RES_CHRATR_CROSSEDOUT ); + m_aContour = std::make_unique<SvxContourItem>(false, RES_CHRATR_CONTOUR ); + m_aShadowed = std::make_unique<SvxShadowedItem>(false, RES_CHRATR_SHADOWED ); + m_aColor = std::make_unique<SvxColorItem>(RES_CHRATR_COLOR ); + m_aBox = std::make_unique<SvxBoxItem>(RES_BOX ); + m_aTLBR = std::make_unique<SvxLineItem>(0 ); + m_aBLTR = std::make_unique<SvxLineItem>(0 ); + m_aBackground = std::make_unique<SvxBrushItem>(RES_BACKGROUND ); + m_aAdjust = std::make_unique<SvxAdjustItem>(SvxAdjust::Left, RES_PARATR_ADJUST ); + m_aHorJustify = std::make_unique<SvxHorJustifyItem>(SvxCellHorJustify::Standard, 0); + m_aVerJustify = std::make_unique<SvxVerJustifyItem>(SvxCellVerJustify::Standard, 0); + m_aStacked = std::make_unique<SfxBoolItem>(0 ); + m_aMargin = std::make_unique<SvxMarginItem>(0 ); + m_aLinebreak = std::make_unique<SfxBoolItem>(0 ); + m_aRotateAngle = std::make_unique<SfxInt32Item>(0 ); + m_aRotateMode = std::make_unique<SvxRotateModeItem>(SVX_ROTATE_MODE_STANDARD, 0 ); + +// FIXME - add attribute IDs for the diagonal line items +// aTLBR( RES_... ), +// aBLTR( RES_... ), + m_aBox->SetAllDistances(55); +} + +SwBoxAutoFormat::SwBoxAutoFormat( const SwBoxAutoFormat& rNew ) +: AutoFormatBase(rNew), + m_aTextOrientation(rNew.m_aTextOrientation->Clone()), + m_aVerticalAlignment(rNew.m_aVerticalAlignment->Clone()), + m_sNumFormatString( rNew.m_sNumFormatString ), + m_eSysLanguage( rNew.m_eSysLanguage ), + m_eNumFormatLanguage( rNew.m_eNumFormatLanguage ), + m_wXObject() +{ +} + +SwBoxAutoFormat::~SwBoxAutoFormat() +{ +} + +SwBoxAutoFormat& SwBoxAutoFormat::operator=(const SwBoxAutoFormat& rRef) +{ + // check self-assignment + if(this == &rRef) + { + return *this; + } + + // call baseclass implementation + AutoFormatBase::operator=(rRef); + + // copy local members - this will use ::Clone() on all involved Items + SetTextOrientation(rRef.GetTextOrientation()); + SetVerticalAlignment(rRef.GetVerticalAlignment()); + SetNumFormatString(rRef.GetNumFormatString()); + SetSysLanguage(rRef.GetSysLanguage()); + SetNumFormatLanguage(rRef.GetNumFormatLanguage()); + + // m_wXObject used to not be copied before 1e2682235cded9a7cd90e55f0bfc60a1285e9a46 + // "WIP: Further preparations for deeper Item changes" by this operator, so do not do it now, too + // rRef.SetXObject(GetXObject()); + + return *this; +} + +bool SwBoxAutoFormat::operator==(const SwBoxAutoFormat& rRight) const +{ + return GetBackground().GetColor() == rRight.GetBackground().GetColor(); +} + +bool SwBoxAutoFormat::Load( SvStream& rStream, const SwAfVersions& rVersions, sal_uInt16 nVer ) +{ + LoadBlockA( rStream, rVersions, nVer ); + + if (nVer >= AUTOFORMAT_DATA_ID_31005) + { + sal_Int64 const nSize(WriterSpecificBlockExists(rStream)); + if (0 < nSize && nSize < std::numeric_limits<sal_uInt16>::max()) + { + legacy::SvxFrameDirection::Create(*m_aTextOrientation, rStream, rVersions.m_nTextOrientationVersion); + // HORRIBLE HACK to read both 32-bit and 64-bit "long": abuse nSize + legacy::SwFormatVert::Create(*m_aVerticalAlignment, rStream, /*rVersions.m_nVerticalAlignmentVersion*/ nSize); + } + } + + LoadBlockB( rStream, rVersions, nVer ); + + if( 0 == rVersions.nNumFormatVersion ) + { + sal_uInt16 eSys, eLge; + // --- from 680/dr25 on: store strings as UTF-8 + rtl_TextEncoding eCharSet = (nVer >= AUTOFORMAT_ID_680DR25) ? RTL_TEXTENCODING_UTF8 : rStream.GetStreamCharSet(); + m_sNumFormatString = rStream.ReadUniOrByteString( eCharSet ); + rStream.ReadUInt16( eSys ).ReadUInt16( eLge ); + m_eSysLanguage = LanguageType(eSys); + m_eNumFormatLanguage = LanguageType(eLge); + if ( m_eSysLanguage == LANGUAGE_SYSTEM ) // from old versions (Calc) + m_eSysLanguage = ::GetAppLanguage(); + } + + return ERRCODE_NONE == rStream.GetError(); +} + +bool SwBoxAutoFormat::Save( SvStream& rStream, sal_uInt16 fileVersion ) const +{ + SaveBlockA( rStream, fileVersion ); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + { + WriterSpecificAutoFormatBlock block(rStream); + + legacy::SvxFrameDirection::Store(*m_aTextOrientation, rStream, legacy::SvxFrameDirection::GetVersion(fileVersion)); + legacy::SwFormatVert::Store(*m_aVerticalAlignment, rStream, legacy::SwFormatVert::GetVersion(fileVersion)); + } + + SaveBlockB( rStream, fileVersion ); + + // --- from 680/dr25 on: store strings as UTF-8 + write_uInt16_lenPrefixed_uInt8s_FromOUString(rStream, m_sNumFormatString, + RTL_TEXTENCODING_UTF8); + rStream.WriteUInt16( static_cast<sal_uInt16>(m_eSysLanguage) ).WriteUInt16( static_cast<sal_uInt16>(m_eNumFormatLanguage) ); + + return ERRCODE_NONE == rStream.GetError(); +} + +SwTableAutoFormat::SwTableAutoFormat( const OUString& rName ) + : m_aName( rName ) + , m_nStrResId( USHRT_MAX ) + , m_aBreak(std::make_shared<SvxFormatBreakItem>(SvxBreak::NONE, RES_BREAK)) + , m_aKeepWithNextPara(std::make_shared<SvxFormatKeepItem>(false, RES_KEEP)) + , m_aRepeatHeading( 0 ) + , m_bLayoutSplit( true ) + , m_bRowSplit( true ) + , m_bCollapsingBorders(true) + , m_aShadow(std::make_shared<SvxShadowItem>(RES_SHADOW)) + , m_bHidden( false ) + , m_bUserDefined( true ) +{ + m_bInclFont = true; + m_bInclJustify = true; + m_bInclFrame = true; + m_bInclBackground = true; + m_bInclValueFormat = true; + m_bInclWidthHeight = true; +} + +SwTableAutoFormat::SwTableAutoFormat( const SwTableAutoFormat& rNew ) + : m_aBreak() + , m_aKeepWithNextPara() + , m_aShadow(std::make_shared<SvxShadowItem>(RES_SHADOW)) +{ + for(SwBoxAutoFormat* & rp : m_aBoxAutoFormat) + rp = nullptr; + *this = rNew; +} + +SwTableAutoFormat& SwTableAutoFormat::operator=( const SwTableAutoFormat& rNew ) +{ + if (&rNew == this) + return *this; + + for( sal_uInt8 n = 0; n < 16; ++n ) + { + if( m_aBoxAutoFormat[ n ] ) + delete m_aBoxAutoFormat[ n ]; + + SwBoxAutoFormat* pFormat = rNew.m_aBoxAutoFormat[ n ]; + if( pFormat ) // if is set -> copy + m_aBoxAutoFormat[ n ] = new SwBoxAutoFormat( *pFormat ); + else // else default + m_aBoxAutoFormat[ n ] = nullptr; + } + + m_aName = rNew.m_aName; + m_nStrResId = rNew.m_nStrResId; + m_bInclFont = rNew.m_bInclFont; + m_bInclJustify = rNew.m_bInclJustify; + m_bInclFrame = rNew.m_bInclFrame; + m_bInclBackground = rNew.m_bInclBackground; + m_bInclValueFormat = rNew.m_bInclValueFormat; + m_bInclWidthHeight = rNew.m_bInclWidthHeight; + + m_aBreak.reset(rNew.m_aBreak->Clone()); + m_aPageDesc = rNew.m_aPageDesc; + m_aKeepWithNextPara.reset(rNew.m_aKeepWithNextPara->Clone()); + m_aRepeatHeading = rNew.m_aRepeatHeading; + m_bLayoutSplit = rNew.m_bLayoutSplit; + m_bRowSplit = rNew.m_bRowSplit; + m_bCollapsingBorders = rNew.m_bCollapsingBorders; + m_aShadow.reset(rNew.m_aShadow->Clone()); + m_bHidden = rNew.m_bHidden; + m_bUserDefined = rNew.m_bUserDefined; + + return *this; +} + +SwTableAutoFormat::~SwTableAutoFormat() +{ + SwBoxAutoFormat** ppFormat = m_aBoxAutoFormat; + for( sal_uInt8 n = 0; n < 16; ++n, ++ppFormat ) + if( *ppFormat ) + delete *ppFormat; +} + +void SwTableAutoFormat::SetBoxFormat( const SwBoxAutoFormat& rNew, sal_uInt8 nPos ) +{ + OSL_ENSURE( nPos < 16, "wrong area" ); + + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ nPos ]; + if( pFormat ) // if is set -> copy + *m_aBoxAutoFormat[ nPos ] = rNew; + else // else set anew + m_aBoxAutoFormat[ nPos ] = new SwBoxAutoFormat( rNew ); +} + +const SwBoxAutoFormat& SwTableAutoFormat::GetBoxFormat( sal_uInt8 nPos ) const +{ + OSL_ENSURE( nPos < 16, "wrong area" ); + + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ nPos ]; + if( pFormat ) // if is set -> copy + return *pFormat; + else // else return the default + { + // If it doesn't exist yet: + if( !pDfltBoxAutoFormat ) + pDfltBoxAutoFormat = new SwBoxAutoFormat; + return *pDfltBoxAutoFormat; + } +} + +SwBoxAutoFormat& SwTableAutoFormat::GetBoxFormat( sal_uInt8 nPos ) +{ + SAL_WARN_IF(!(nPos < 16), "sw.core", "GetBoxFormat wrong area"); + + SwBoxAutoFormat** pFormat = &m_aBoxAutoFormat[ nPos ]; + if( !*pFormat ) + { + // If default doesn't exist yet: + if( !pDfltBoxAutoFormat ) + pDfltBoxAutoFormat = new SwBoxAutoFormat(); + *pFormat = new SwBoxAutoFormat(*pDfltBoxAutoFormat); + } + return **pFormat; +} + +const SwBoxAutoFormat& SwTableAutoFormat::GetDefaultBoxFormat() +{ + if(!pDfltBoxAutoFormat) + pDfltBoxAutoFormat = new SwBoxAutoFormat(); + + return *pDfltBoxAutoFormat; +} + +void SwTableAutoFormat::UpdateFromSet( sal_uInt8 nPos, + const SfxItemSet& rSet, + SwTableAutoFormatUpdateFlags eFlags, + SvNumberFormatter const * pNFormatr) +{ + OSL_ENSURE( nPos < 16, "wrong area" ); + + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ nPos ]; + if( !pFormat ) // if is set -> copy + { + pFormat = new SwBoxAutoFormat; + m_aBoxAutoFormat[ nPos ] = pFormat; + } + + if( SwTableAutoFormatUpdateFlags::Char & eFlags ) + { + pFormat->SetFont( rSet.Get( RES_CHRATR_FONT ) ); + pFormat->SetHeight( rSet.Get( RES_CHRATR_FONTSIZE ) ); + pFormat->SetWeight( rSet.Get( RES_CHRATR_WEIGHT ) ); + pFormat->SetPosture( rSet.Get( RES_CHRATR_POSTURE ) ); + pFormat->SetCJKFont( rSet.Get( RES_CHRATR_CJK_FONT ) ); + pFormat->SetCJKHeight( rSet.Get( RES_CHRATR_CJK_FONTSIZE ) ); + pFormat->SetCJKWeight( rSet.Get( RES_CHRATR_CJK_WEIGHT ) ); + pFormat->SetCJKPosture( rSet.Get( RES_CHRATR_CJK_POSTURE ) ); + pFormat->SetCTLFont( rSet.Get( RES_CHRATR_CTL_FONT ) ); + pFormat->SetCTLHeight( rSet.Get( RES_CHRATR_CTL_FONTSIZE ) ); + pFormat->SetCTLWeight( rSet.Get( RES_CHRATR_CTL_WEIGHT ) ); + pFormat->SetCTLPosture( rSet.Get( RES_CHRATR_CTL_POSTURE ) ); + pFormat->SetUnderline( rSet.Get( RES_CHRATR_UNDERLINE ) ); + pFormat->SetOverline( rSet.Get( RES_CHRATR_OVERLINE ) ); + pFormat->SetCrossedOut( rSet.Get( RES_CHRATR_CROSSEDOUT ) ); + pFormat->SetContour( rSet.Get( RES_CHRATR_CONTOUR ) ); + pFormat->SetShadowed( rSet.Get( RES_CHRATR_SHADOWED ) ); + pFormat->SetColor( rSet.Get( RES_CHRATR_COLOR ) ); + pFormat->SetAdjust( rSet.Get( RES_PARATR_ADJUST ) ); + } + if( SwTableAutoFormatUpdateFlags::Box & eFlags ) + { + pFormat->SetBox( rSet.Get( RES_BOX ) ); +// FIXME - add attribute IDs for the diagonal line items +// pFormat->SetTLBR( (SvxLineItem&)rSet.Get( RES_... ) ); +// pFormat->SetBLTR( (SvxLineItem&)rSet.Get( RES_... ) ); + pFormat->SetBackground( rSet.Get( RES_BACKGROUND ) ); + pFormat->SetTextOrientation(rSet.Get(RES_FRAMEDIR)); + pFormat->SetVerticalAlignment(rSet.Get(RES_VERT_ORIENT)); + + const SwTableBoxNumFormat* pNumFormatItem; + const SvNumberformat* pNumFormat = nullptr; + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMAT, true, + reinterpret_cast<const SfxPoolItem**>(&pNumFormatItem) ) && pNFormatr && + nullptr != (pNumFormat = pNFormatr->GetEntry( pNumFormatItem->GetValue() )) ) + pFormat->SetValueFormat( pNumFormat->GetFormatstring(), + pNumFormat->GetLanguage(), + ::GetAppLanguage()); + else + { + // default + pFormat->SetValueFormat( OUString(), LANGUAGE_SYSTEM, + ::GetAppLanguage() ); + } + } + + // we cannot handle the rest, that's specific to StarCalc +} + +void SwTableAutoFormat::UpdateToSet(const sal_uInt8 nPos, const bool bSingleRowTable, const bool bSingleColTable, SfxItemSet& rSet, + SwTableAutoFormatUpdateFlags eFlags, SvNumberFormatter* pNFormatr) const +{ + const SwBoxAutoFormat& rChg = GetBoxFormat( nPos ); + + if( SwTableAutoFormatUpdateFlags::Char & eFlags ) + { + if( IsFont() ) + { + rSet.Put( rChg.GetFont() ); + rSet.Put( rChg.GetHeight() ); + rSet.Put( rChg.GetWeight() ); + rSet.Put( rChg.GetPosture() ); + // do not insert empty CJK font + const SvxFontItem& rCJKFont = rChg.GetCJKFont(); + if (!rCJKFont.GetStyleName().isEmpty()) + { + rSet.Put( rChg.GetCJKFont() ); + rSet.Put( rChg.GetCJKHeight() ); + rSet.Put( rChg.GetCJKWeight() ); + rSet.Put( rChg.GetCJKPosture() ); + } + else + { + rSet.Put( rChg.GetHeight().CloneSetWhich(RES_CHRATR_CJK_FONTSIZE) ); + rSet.Put( rChg.GetWeight().CloneSetWhich(RES_CHRATR_CJK_WEIGHT) ); + rSet.Put( rChg.GetPosture().CloneSetWhich(RES_CHRATR_CJK_POSTURE) ); + } + // do not insert empty CTL font + const SvxFontItem& rCTLFont = rChg.GetCTLFont(); + if (!rCTLFont.GetStyleName().isEmpty()) + { + rSet.Put( rChg.GetCTLFont() ); + rSet.Put( rChg.GetCTLHeight() ); + rSet.Put( rChg.GetCTLWeight() ); + rSet.Put( rChg.GetCTLPosture() ); + } + else + { + rSet.Put( rChg.GetHeight().CloneSetWhich(RES_CHRATR_CTL_FONTSIZE) ); + rSet.Put( rChg.GetWeight().CloneSetWhich(RES_CHRATR_CTL_WEIGHT) ); + rSet.Put( rChg.GetPosture().CloneSetWhich(RES_CHRATR_CTL_POSTURE) ); + } + rSet.Put( rChg.GetUnderline() ); + rSet.Put( rChg.GetOverline() ); + rSet.Put( rChg.GetCrossedOut() ); + rSet.Put( rChg.GetContour() ); + rSet.Put( rChg.GetShadowed() ); + rSet.Put( rChg.GetColor() ); + } + if( IsJustify() ) + rSet.Put( rChg.GetAdjust() ); + } + + if( SwTableAutoFormatUpdateFlags::Box & eFlags ) + { + if( IsFrame() ) + { + SvxBoxItem aAutoFormatBox = rChg.GetBox(); + + // No format box is adequate to specify the borders of single column/row tables, so combine first/last. + if ( bSingleRowTable || bSingleColTable ) + { + sal_uInt8 nSingleRowOrColumnId = 15; //LAST_ROW_END_COLUMN + if ( !bSingleRowTable ) + nSingleRowOrColumnId = nPos + 3; //LAST COLUMN (3, 7, 11, 15) + else if ( !bSingleColTable ) + nSingleRowOrColumnId = nPos + 12; //LAST ROW (12, 13, 14, 15) + + assert( nSingleRowOrColumnId < 16 ); + const SvxBoxItem aLastAutoFormatBox( GetBoxFormat(nSingleRowOrColumnId).GetBox() ); + if ( bSingleRowTable ) + aAutoFormatBox.SetLine( aLastAutoFormatBox.GetLine(SvxBoxItemLine::BOTTOM), SvxBoxItemLine::BOTTOM ); + if ( bSingleColTable ) + aAutoFormatBox.SetLine( aLastAutoFormatBox.GetLine(SvxBoxItemLine::RIGHT), SvxBoxItemLine::RIGHT ); + } + + rSet.Put( aAutoFormatBox ); +// FIXME - uncomment the lines to put the diagonal line items +// rSet.Put( rChg.GetTLBR() ); +// rSet.Put( rChg.GetBLTR() ); + } + if( IsBackground() ) + rSet.Put( rChg.GetBackground() ); + + rSet.Put(rChg.GetTextOrientation()); + + // Do not put a VertAlign when it has default value. + // It prevents the export of default value by automatic cell-styles export. + if (rChg.GetVerticalAlignment().GetVertOrient() != GetDefaultBoxFormat().GetVerticalAlignment().GetVertOrient()) + rSet.Put(rChg.GetVerticalAlignment()); + + if( IsValueFormat() && pNFormatr ) + { + OUString sFormat; + LanguageType eLng, eSys; + rChg.GetValueFormat( sFormat, eLng, eSys ); + if( !sFormat.isEmpty() ) + { + SvNumFormatType nType; + bool bNew; + sal_Int32 nCheckPos; + sal_uInt32 nKey = pNFormatr->GetIndexPuttingAndConverting( sFormat, eLng, + eSys, nType, bNew, nCheckPos); + rSet.Put( SwTableBoxNumFormat( nKey )); + } + else + rSet.ClearItem( RES_BOXATR_FORMAT ); + } + } + + // we cannot handle the rest, that's specific to StarCalc +} + +void SwTableAutoFormat::RestoreTableProperties(SwTable &table) const +{ + SwTableFormat* pFormat = table.GetFrameFormat(); + if (!pFormat) + return; + + SwDoc *pDoc = pFormat->GetDoc(); + if (!pDoc) + return; + + SfxItemSet rSet(pDoc->GetAttrPool(), aTableSetRange); + + if ( m_aBreak->GetBreak() != SvxBreak::NONE ) + rSet.Put(*m_aBreak); + rSet.Put(m_aPageDesc); + rSet.Put(SwFormatLayoutSplit(m_bLayoutSplit)); + rSet.Put(SfxBoolItem(RES_COLLAPSING_BORDERS, m_bCollapsingBorders)); + if ( m_aKeepWithNextPara->GetValue() ) + rSet.Put(*m_aKeepWithNextPara); + rSet.Put(*m_aShadow); + + pFormat->SetFormatAttr(rSet); + + SwEditShell *pShell = pDoc->GetEditShell(); + pDoc->SetRowSplit(*pShell->getShellCursor(false), SwFormatRowSplit(m_bRowSplit)); + + table.SetRowsToRepeat(m_aRepeatHeading); +} + +void SwTableAutoFormat::StoreTableProperties(const SwTable &table) +{ + SwTableFormat* pFormat = table.GetFrameFormat(); + if (!pFormat) + return; + + SwDoc *pDoc = pFormat->GetDoc(); + if (!pDoc) + return; + + SwEditShell *pShell = pDoc->GetEditShell(); + std::unique_ptr<SwFormatRowSplit> pRowSplit = SwDoc::GetRowSplit(*pShell->getShellCursor(false)); + m_bRowSplit = pRowSplit && pRowSplit->GetValue(); + pRowSplit.reset(); + + const SfxItemSet &rSet = pFormat->GetAttrSet(); + + m_aBreak.reset(rSet.Get(RES_BREAK).Clone()); + m_aPageDesc = rSet.Get(RES_PAGEDESC); + const SwFormatLayoutSplit &layoutSplit = rSet.Get(RES_LAYOUT_SPLIT); + m_bLayoutSplit = layoutSplit.GetValue(); + m_bCollapsingBorders = rSet.Get(RES_COLLAPSING_BORDERS).GetValue(); + + m_aKeepWithNextPara.reset(rSet.Get(RES_KEEP).Clone()); + m_aRepeatHeading = table.GetRowsToRepeat(); + m_aShadow.reset(rSet.Get(RES_SHADOW).Clone()); +} + +bool SwTableAutoFormat::FirstRowEndColumnIsRow() +{ + return GetBoxFormat(3) == GetBoxFormat(2); +} +bool SwTableAutoFormat::FirstRowStartColumnIsRow() +{ + return GetBoxFormat(0) == GetBoxFormat(1); +} +bool SwTableAutoFormat::LastRowEndColumnIsRow() +{ + return GetBoxFormat(14) == GetBoxFormat(15); +} +bool SwTableAutoFormat::LastRowStartColumnIsRow() +{ + return GetBoxFormat(12) == GetBoxFormat(13); +} + +bool SwTableAutoFormat::Load( SvStream& rStream, const SwAfVersions& rVersions ) +{ + sal_uInt16 nVal = 0; + rStream.ReadUInt16( nVal ); + bool bRet = ERRCODE_NONE == rStream.GetError(); + + if( bRet && (nVal == AUTOFORMAT_DATA_ID_X || + (AUTOFORMAT_DATA_ID_504 <= nVal && nVal <= AUTOFORMAT_DATA_ID)) ) + { + bool b; + // --- from 680/dr25 on: store strings as UTF-8 + rtl_TextEncoding eCharSet = (nVal >= AUTOFORMAT_ID_680DR25) ? RTL_TEXTENCODING_UTF8 : rStream.GetStreamCharSet(); + m_aName = rStream.ReadUniOrByteString( eCharSet ); + if( AUTOFORMAT_DATA_ID_552 <= nVal ) + { + rStream.ReadUInt16( m_nStrResId ); + // start from 3d because default is added via constructor + if( m_nStrResId < RES_POOLTABLESTYLE_END - RES_POOLTABLESTYLE_3D ) + { + m_aName = SwStyleNameMapper::GetUIName(RES_POOLTABLESTYLE_3D + m_nStrResId, m_aName); + } + else + m_nStrResId = USHRT_MAX; + } + rStream.ReadCharAsBool( b ); m_bInclFont = b; + rStream.ReadCharAsBool( b ); m_bInclJustify = b; + rStream.ReadCharAsBool( b ); m_bInclFrame = b; + rStream.ReadCharAsBool( b ); m_bInclBackground = b; + rStream.ReadCharAsBool( b ); m_bInclValueFormat = b; + rStream.ReadCharAsBool( b ); m_bInclWidthHeight = b; + + if (nVal >= AUTOFORMAT_DATA_ID_31005 && WriterSpecificBlockExists(rStream)) + { + legacy::SvxFormatBreak::Create(*m_aBreak, rStream, AUTOFORMAT_FILE_VERSION); +//unimplemented READ(m_aPageDesc, SwFormatPageDesc, AUTOFORMAT_FILE_VERSION); + legacy::SvxFormatKeep::Create(*m_aKeepWithNextPara, rStream, AUTOFORMAT_FILE_VERSION); + + rStream.ReadUInt16( m_aRepeatHeading ).ReadCharAsBool( m_bLayoutSplit ).ReadCharAsBool( m_bRowSplit ).ReadCharAsBool( m_bCollapsingBorders ); + + legacy::SvxShadow::Create(*m_aShadow, rStream, AUTOFORMAT_FILE_VERSION); + } + + bRet = ERRCODE_NONE== rStream.GetError(); + + for( sal_uInt8 i = 0; bRet && i < 16; ++i ) + { + SwBoxAutoFormat* pFormat = new SwBoxAutoFormat; + bRet = pFormat->Load( rStream, rVersions, nVal ); + if( bRet ) + m_aBoxAutoFormat[ i ] = pFormat; + else + { + delete pFormat; + break; + } + } + } + m_bUserDefined = false; + return bRet; +} + +bool SwTableAutoFormat::Save( SvStream& rStream, sal_uInt16 fileVersion ) const +{ + rStream.WriteUInt16( AUTOFORMAT_DATA_ID ); + // --- from 680/dr25 on: store strings as UTF-8 + write_uInt16_lenPrefixed_uInt8s_FromOUString(rStream, m_aName, + RTL_TEXTENCODING_UTF8 ); + rStream.WriteUInt16( m_nStrResId ); + rStream.WriteBool( m_bInclFont ); + rStream.WriteBool( m_bInclJustify ); + rStream.WriteBool( m_bInclFrame ); + rStream.WriteBool( m_bInclBackground ); + rStream.WriteBool( m_bInclValueFormat ); + rStream.WriteBool( m_bInclWidthHeight ); + + { + WriterSpecificAutoFormatBlock block(rStream); + + legacy::SvxFormatBreak::Store(*m_aBreak, rStream, legacy::SvxFormatBreak::GetVersion(fileVersion)); +//unimplemented m_aPageDesc.Store(rStream, m_aPageDesc.GetVersion(fileVersion)); + legacy::SvxFormatKeep::Store(*m_aKeepWithNextPara, rStream, legacy::SvxFormatKeep::GetVersion(fileVersion)); + rStream.WriteUInt16( m_aRepeatHeading ).WriteBool( m_bLayoutSplit ).WriteBool( m_bRowSplit ).WriteBool( m_bCollapsingBorders ); + legacy::SvxShadow::Store(*m_aShadow, rStream, legacy::SvxShadow::GetVersion(fileVersion)); + } + + bool bRet = ERRCODE_NONE == rStream.GetError(); + + for( int i = 0; bRet && i < 16; ++i ) + { + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ i ]; + if( !pFormat ) // if not set -> write default + { + // If it doesn't exist yet: + if( !pDfltBoxAutoFormat ) + pDfltBoxAutoFormat = new SwBoxAutoFormat; + pFormat = pDfltBoxAutoFormat; + } + bRet = pFormat->Save( rStream, fileVersion ); + } + return bRet; +} + +OUString SwTableAutoFormat::GetTableTemplateCellSubName(const SwBoxAutoFormat& rBoxFormat) const +{ + sal_Int32 nIndex = 0; + for (; nIndex < 16; ++nIndex) + if (m_aBoxAutoFormat[nIndex] == &rBoxFormat) break; + + // box format doesn't belong to this table format + if (16 <= nIndex) + return OUString(); + + const std::vector<sal_Int32> aTableTemplateMap = GetTableTemplateMap(); + for (size_t i=0; i < aTableTemplateMap.size(); ++i) + { + if (aTableTemplateMap[i] == nIndex) + return "." + OUString::number(i + 1); + } + + // box format doesn't belong to a table template + return OUString(); +} + +/* + * Mapping schema + * 0 1 2 3 4 5 + * +-----------------------------------------------------------------------+ + * 0 | FRSC | FR | FREC | | | FRENC | + * +-----------------------------------------------------------------------+ + * 1 | FC | ER | EC | | | LC | + * +-----------------------------------------------------------------------+ + * 2 | OR | OC | BODY | | | BCKG | + * +-----------------------------------------------------------------------+ + * 3 | | | | | | | + * +-----------------------------------------------------------------------+ + * 4 | | | | | | | + * +-----------------------------------------------------------------------+ + * 5 | LRSC | LR | LREC | | | LRENC | + * +-----------+-----------+-----------+-----------+-----------+-----------+ + * ODD = 1, 3, 5, ... + * EVEN = 2, 4, 6, ... + */ +const std::vector<sal_Int32> & SwTableAutoFormat::GetTableTemplateMap() +{ + static std::vector<sal_Int32> const aTableTemplateMap + { + 1 , // FIRST_ROW // FR + 13, // LAST_ROW // LR + 4 , // FIRST_COLUMN // FC + 7 , // LAST_COLUMN // LC + 5 , // EVEN_ROWS // ER + 8 , // ODD_ROWS // OR + 6 , // EVEN_COLUMNS // EC + 9 , // ODD_COLUMNS // OC + 10, // BODY + 11, // BACKGROUND // BCKG + 0 , // FIRST_ROW_START_COLUMN // FRSC + 3 , // FIRST_ROW_END_COLUMN // FRENC + 12, // LAST_ROW_START_COLUMN // LRSC + 15, // LAST_ROW_END_COLUMN // LRENC + 2 , // FIRST_ROW_EVEN_COLUMN // FREC + 14, // LAST_ROW_EVEN_COLUMN // LREC + }; + return aTableTemplateMap; +} + +sal_uInt8 SwTableAutoFormat::CountPos(sal_uInt32 nCol, sal_uInt32 nCols, sal_uInt32 nRow, + sal_uInt32 nRows) +{ + sal_uInt8 nRet = static_cast<sal_uInt8>( + !nRow ? 0 : ((nRow + 1 == nRows) ? 12 : (4 * (1 + ((nRow - 1) & 1))))); + nRet = nRet + + static_cast<sal_uInt8>(!nCol ? 0 : (nCol + 1 == nCols ? 3 : (1 + ((nCol - 1) & 1)))); + return nRet; +} + +struct SwTableAutoFormatTable::Impl +{ + std::vector<std::unique_ptr<SwTableAutoFormat>> m_AutoFormats; +}; + +size_t SwTableAutoFormatTable::size() const +{ + return m_pImpl->m_AutoFormats.size(); +} + +SwTableAutoFormat const& SwTableAutoFormatTable::operator[](size_t const i) const +{ + return *m_pImpl->m_AutoFormats[i]; +} +SwTableAutoFormat & SwTableAutoFormatTable::operator[](size_t const i) +{ + return *m_pImpl->m_AutoFormats[i]; +} + +void SwTableAutoFormatTable::AddAutoFormat(const SwTableAutoFormat& rTableStyle) +{ + // don't insert when we already have style of this name + if (FindAutoFormat(rTableStyle.GetName())) + return; + + InsertAutoFormat(size(), std::make_unique<SwTableAutoFormat>(rTableStyle)); +} + +void SwTableAutoFormatTable::InsertAutoFormat(size_t const i, std::unique_ptr<SwTableAutoFormat> pFormat) +{ + m_pImpl->m_AutoFormats.insert(m_pImpl->m_AutoFormats.begin() + i, std::move(pFormat)); +} + +void SwTableAutoFormatTable::EraseAutoFormat(size_t const i) +{ + m_pImpl->m_AutoFormats.erase(m_pImpl->m_AutoFormats.begin() + i); +} + +void SwTableAutoFormatTable::EraseAutoFormat(const OUString& rName) +{ + auto iter = std::find_if(m_pImpl->m_AutoFormats.begin(), m_pImpl->m_AutoFormats.end(), + [&rName](const std::unique_ptr<SwTableAutoFormat>& rpFormat) { return rpFormat->GetName() == rName; }); + if (iter != m_pImpl->m_AutoFormats.end()) + { + m_pImpl->m_AutoFormats.erase(iter); + return; + } + SAL_INFO("sw.core", "SwTableAutoFormatTable::EraseAutoFormat, SwTableAutoFormat with given name not found"); +} + +std::unique_ptr<SwTableAutoFormat> SwTableAutoFormatTable::ReleaseAutoFormat(size_t const i) +{ + auto const iter(m_pImpl->m_AutoFormats.begin() + i); + std::unique_ptr<SwTableAutoFormat> pRet(std::move(*iter)); + m_pImpl->m_AutoFormats.erase(iter); + return pRet; +} + +std::unique_ptr<SwTableAutoFormat> SwTableAutoFormatTable::ReleaseAutoFormat(const OUString& rName) +{ + std::unique_ptr<SwTableAutoFormat> pRet; + auto iter = std::find_if(m_pImpl->m_AutoFormats.begin(), m_pImpl->m_AutoFormats.end(), + [&rName](const std::unique_ptr<SwTableAutoFormat>& rpFormat) { return rpFormat->GetName() == rName; }); + if (iter != m_pImpl->m_AutoFormats.end()) + { + pRet = std::move(*iter); + m_pImpl->m_AutoFormats.erase(iter); + } + return pRet; +} + +SwTableAutoFormat* SwTableAutoFormatTable::FindAutoFormat(const OUString& rName) const +{ + for (const auto &rFormat : m_pImpl->m_AutoFormats) + { + if (rFormat->GetName() == rName) + return rFormat.get(); + } + + return nullptr; +} + +SwTableAutoFormatTable::~SwTableAutoFormatTable() +{ +} + +SwTableAutoFormatTable::SwTableAutoFormatTable() + : m_pImpl(new Impl) +{ + std::unique_ptr<SwTableAutoFormat> pNew(new SwTableAutoFormat( + SwStyleNameMapper::GetUIName(RES_POOLTABLESTYLE_DEFAULT, OUString()))); + + sal_uInt8 i; + + Color aColor( COL_BLACK ); + SvxBoxItem aBox( RES_BOX ); + + aBox.SetAllDistances(55); + SvxBorderLine aLn( &aColor, DEF_LINE_WIDTH_5 ); + aBox.SetLine( &aLn, SvxBoxItemLine::LEFT ); + aBox.SetLine( &aLn, SvxBoxItemLine::BOTTOM ); + + for( i = 0; i <= 15; ++i ) + { + aBox.SetLine( i <= 3 ? &aLn : nullptr, SvxBoxItemLine::TOP ); + aBox.SetLine( (3 == ( i & 3 )) ? &aLn : nullptr, SvxBoxItemLine::RIGHT ); + pNew->GetBoxFormat( i ).SetBox( aBox ); + } + + pNew->SetUserDefined(false); + m_pImpl->m_AutoFormats.push_back(std::move(pNew)); +} + +void SwTableAutoFormatTable::Load() +{ + if (utl::ConfigManager::IsFuzzing()) + return; + OUString sNm(AUTOTABLE_FORMAT_NAME); + SvtPathOptions aOpt; + if( aOpt.SearchFile( sNm )) + { + SfxMedium aStream( sNm, StreamMode::STD_READ ); + Load( *aStream.GetInStream() ); + } +} + +bool SwTableAutoFormatTable::Save() const +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + SvtPathOptions aPathOpt; + const OUString sNm( aPathOpt.GetUserConfigPath() + "/" AUTOTABLE_FORMAT_NAME ); + SfxMedium aStream(sNm, StreamMode::STD_WRITE ); + return Save( *aStream.GetOutStream() ) && aStream.Commit(); +} + +bool SwTableAutoFormatTable::Load( SvStream& rStream ) +{ + bool bRet = ERRCODE_NONE == rStream.GetError(); + if (bRet) + { + // Attention: We need to read a general Header here + sal_uInt16 nVal = 0; + rStream.ReadUInt16( nVal ); + bRet = ERRCODE_NONE == rStream.GetError(); + + if( bRet ) + { + SwAfVersions aVersions; + + // Default version is 5.0, unless we detect an old format ID. + sal_uInt16 nFileVers = SOFFICE_FILEFORMAT_50; + if(nVal < AUTOFORMAT_ID_31005) + nFileVers = SOFFICE_FILEFORMAT_40; + + if( nVal == AUTOFORMAT_ID_358 || + (AUTOFORMAT_ID_504 <= nVal && nVal <= AUTOFORMAT_ID) ) + { + sal_uInt8 nChrSet, nCnt; + long nPos = rStream.Tell(); + rStream.ReadUChar( nCnt ).ReadUChar( nChrSet ); + if( rStream.Tell() != sal_uLong(nPos + nCnt) ) + { + OSL_ENSURE( false, "The Header contains more or newer Data" ); + rStream.Seek( nPos + nCnt ); + } + rStream.SetStreamCharSet( static_cast<rtl_TextEncoding>(nChrSet) ); + rStream.SetVersion( nFileVers ); + } + + if( nVal == AUTOFORMAT_ID_358 || nVal == AUTOFORMAT_ID_X || + (AUTOFORMAT_ID_504 <= nVal && nVal <= AUTOFORMAT_ID) ) + { + aVersions.Load( rStream, nVal ); // Item versions + + sal_uInt16 nCount = 0; + rStream.ReadUInt16( nCount ); + + bRet = ERRCODE_NONE== rStream.GetError(); + if (bRet) + { + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = rStream.remainingSize() / nMinRecordSize; + if (nCount > nMaxRecords) + { + SAL_WARN("sw.core", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nCount << " claimed, truncating"); + nCount = nMaxRecords; + } + for (sal_uInt16 i = 0; i < nCount; ++i) + { + std::unique_ptr<SwTableAutoFormat> pNew( + new SwTableAutoFormat( OUString() )); + bRet = pNew->Load( rStream, aVersions ); + if( bRet ) + { + m_pImpl->m_AutoFormats.push_back(std::move(pNew)); + } + else + { + break; + } + } + } + } + else + { + bRet = false; + } + } + } + return bRet; +} + +bool SwTableAutoFormatTable::Save( SvStream& rStream ) const +{ + bool bRet = ERRCODE_NONE == rStream.GetError(); + if (bRet) + { + rStream.SetVersion(AUTOFORMAT_FILE_VERSION); + + // Attention: We need to save a general Header here + rStream.WriteUInt16( AUTOFORMAT_ID ) + .WriteUChar( 2 ) // Character count of the Header including this value + .WriteUChar( GetStoreCharSet( ::osl_getThreadTextEncoding() ) ); + + bRet = ERRCODE_NONE == rStream.GetError(); + if (!bRet) + return false; + + // Write this version number for all attributes + SwAfVersions::Write(rStream, AUTOFORMAT_FILE_VERSION); + + rStream.WriteUInt16( m_pImpl->m_AutoFormats.size() - 1 ); + bRet = ERRCODE_NONE == rStream.GetError(); + + for (size_t i = 1; bRet && i < m_pImpl->m_AutoFormats.size(); ++i) + { + SwTableAutoFormat const& rFormat = *m_pImpl->m_AutoFormats[i]; + bRet = rFormat.Save(rStream, AUTOFORMAT_FILE_VERSION); + } + } + rStream.Flush(); + return bRet; +} + +SwCellStyleTable::SwCellStyleTable() +{ } + +SwCellStyleTable::~SwCellStyleTable() +{ +} + +size_t SwCellStyleTable::size() const +{ + return m_aCellStyles.size(); +} + +void SwCellStyleTable::clear() +{ + m_aCellStyles.clear(); +} + +SwCellStyleDescriptor SwCellStyleTable::operator[](size_t i) const +{ + return SwCellStyleDescriptor(m_aCellStyles[i]); +} + +void SwCellStyleTable::AddBoxFormat(const SwBoxAutoFormat& rBoxFormat, const OUString& sName) +{ + m_aCellStyles.emplace_back(sName, std::make_unique<SwBoxAutoFormat>(rBoxFormat)); +} + +void SwCellStyleTable::RemoveBoxFormat(const OUString& sName) +{ + auto iter = std::find_if(m_aCellStyles.begin(), m_aCellStyles.end(), + [&sName](const std::pair<OUString, std::unique_ptr<SwBoxAutoFormat>>& rStyle) { return rStyle.first == sName; }); + if (iter != m_aCellStyles.end()) + { + m_aCellStyles.erase(iter); + return; + } + SAL_INFO("sw.core", "SwCellStyleTable::RemoveBoxFormat, format with given name doesn't exists"); +} + +OUString SwCellStyleTable::GetBoxFormatName(const SwBoxAutoFormat& rBoxFormat) const +{ + for (size_t i=0; i < m_aCellStyles.size(); ++i) + { + if (m_aCellStyles[i].second.get() == &rBoxFormat) + return m_aCellStyles[i].first; + } + + // box format not found + return OUString(); +} + +SwBoxAutoFormat* SwCellStyleTable::GetBoxFormat(const OUString& sName) const +{ + for (size_t i=0; i < m_aCellStyles.size(); ++i) + { + if (m_aCellStyles[i].first == sName) + return m_aCellStyles[i].second.get(); + } + + return nullptr; +} + +void SwCellStyleTable::ChangeBoxFormatName(const OUString& sFromName, const OUString& sToName) +{ + if (!GetBoxFormat(sToName)) + { + SAL_INFO("sw.core", "SwCellStyleTable::ChangeBoxName, box with given name already exists"); + return; + } + for (size_t i=0; i < m_aCellStyles.size(); ++i) + { + if (m_aCellStyles[i].first == sFromName) + { + m_aCellStyles[i].first = sToName; + // changed successfully + return; + } + } + SAL_INFO("sw.core", "SwCellStyleTable::ChangeBoxName, box with given name not found"); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/tblcpy.cxx b/sw/source/core/doc/tblcpy.cxx new file mode 100644 index 000000000..164a33ae5 --- /dev/null +++ b/sw/source/core/doc/tblcpy.cxx @@ -0,0 +1,1042 @@ +/* -*- 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 <hintids.hxx> + +#include <osl/diagnose.h> +#include <svl/zforlist.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <tblsel.hxx> +#include <poolfmt.hxx> +#include <cellatr.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <fmtanchr.hxx> +#include <hints.hxx> +#include <UndoTable.hxx> +#include <fmtfsize.hxx> +#include <frameformats.hxx> +#include <deque> +#include <memory> +#include <numeric> + +static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, + SwTable& rDstTable, SwTableBox* pDstBox, + bool bDelContent, SwUndoTableCpyTable* pUndo ); + +// The following type will be used by table copy functions to describe +// the structure of tables (or parts of tables). +// It's for new table model only. + +namespace +{ + struct BoxSpanInfo + { + SwTableBox* mpBox; + SwTableBox* mpCopy; + sal_uInt16 mnColSpan; + bool mbSelected; + }; + + typedef std::vector< BoxSpanInfo > BoxStructure; + typedef std::vector< BoxStructure > LineStructure; + typedef std::deque< sal_uLong > ColumnStructure; + + struct SubBox + { + SwTableBox *mpBox; + bool mbCovered; + }; + + typedef std::vector< SubBox > SubLine; + typedef std::vector< SubLine > SubTable; + + class TableStructure + { + public: + LineStructure maLines; + ColumnStructure maCols; + sal_uInt16 mnStartCol; + sal_uInt16 mnAddLine; + void addLine( sal_uInt16 &rLine, const SwTableBoxes&, const SwSelBoxes*, + bool bNewModel ); + void addBox( sal_uInt16 nLine, const SwSelBoxes*, SwTableBox *pBox, + sal_uLong &rnB, sal_uInt16 &rnC, ColumnStructure::iterator& rpCl, + BoxStructure::iterator& rpSel, bool &rbSel, bool bCover ); + void incColSpan( sal_uInt16 nLine, sal_uInt16 nCol ); + explicit TableStructure( const SwTable& rTable ); + TableStructure( const SwTable& rTable, FndBox_ &rFndBox, + const SwSelBoxes& rSelBoxes, + LineStructure::size_type nMinSize ); + LineStructure::size_type getLineCount() const + { return maLines.size(); } + void moreLines( const SwTable& rTable ); + void assignBoxes( const TableStructure &rSource ); + void copyBoxes( const SwTable& rSource, SwTable& rDstTable, + SwUndoTableCpyTable* pUndo ) const; + }; + + SubTable::iterator insertSubLine( SubTable& rSubTable, SwTableLine& rLine, + const SubTable::iterator& pStartLn ); + + SubTable::iterator insertSubBox( SubTable& rSubTable, SwTableBox& rBox, + SubTable::iterator pStartLn, const SubTable::iterator& pEndLn ) + { + if( !rBox.GetTabLines().empty() ) + { + SubTable::size_type nSize = static_cast<SubTable::size_type>(std::distance( pStartLn, pEndLn )); + if( nSize < rBox.GetTabLines().size() ) + { + SubLine aSubLine; + for( const auto& rSubBox : *pStartLn ) + { + SubBox aSub; + aSub.mpBox = rSubBox.mpBox; + aSub.mbCovered = true; + aSubLine.push_back( aSub ); + } + do + { + rSubTable.insert( pEndLn, aSubLine ); + } while( ++nSize < rBox.GetTabLines().size() ); + } + for( auto pLine : rBox.GetTabLines() ) + pStartLn = insertSubLine( rSubTable, *pLine, pStartLn ); + OSL_ENSURE( pStartLn == pEndLn, "Sub line confusion" ); + } + else + { + SubBox aSub; + aSub.mpBox = &rBox; + aSub.mbCovered = false; + while( pStartLn != pEndLn ) + { + pStartLn->push_back( aSub ); + aSub.mbCovered = true; + ++pStartLn; + } + } + return pStartLn; + } + + SubTable::iterator insertSubLine( SubTable& rSubTable, SwTableLine& rLine, + const SubTable::iterator& pStartLn ) + { + SubTable::iterator pMax = pStartLn; + ++pMax; + SubTable::difference_type nMax = 1; + for( auto pBox : rLine.GetTabBoxes() ) + { + SubTable::iterator pTmp = insertSubBox( rSubTable, *pBox, pStartLn, pMax ); + SubTable::difference_type nTmp = std::distance( pStartLn, pTmp ); + if( nTmp > nMax ) + { + pMax = pTmp; + nMax = nTmp; + } + } + return pMax; + } + + TableStructure::TableStructure( const SwTable& rTable ) : + maLines( rTable.GetTabLines().size() ), mnStartCol(USHRT_MAX), + mnAddLine(0) + { + maCols.push_front(0); + sal_uInt16 nCnt = 0; + for( auto pLine : rTable.GetTabLines() ) + addLine( nCnt, pLine->GetTabBoxes(), nullptr, rTable.IsNewModel() ); + } + + TableStructure::TableStructure( const SwTable& rTable, + FndBox_ &rFndBox, const SwSelBoxes& rSelBoxes, + LineStructure::size_type nMinSize ) + : mnStartCol(USHRT_MAX), mnAddLine(0) + { + if( !rFndBox.GetLines().empty() ) + { + bool bNoSelection = rSelBoxes.size() < 2; + FndLines_t &rFndLines = rFndBox.GetLines(); + maCols.push_front(0); + const SwTableLine* pLine = rFndLines.front()->GetLine(); + const sal_uInt16 nStartLn = rTable.GetTabLines().GetPos( pLine ); + SwTableLines::size_type nEndLn = nStartLn; + if( rFndLines.size() > 1 ) + { + pLine = rFndLines.back()->GetLine(); + nEndLn = rTable.GetTabLines().GetPos( pLine ); + } + if( nStartLn < USHRT_MAX && nEndLn < USHRT_MAX ) + { + const SwTableLines &rLines = rTable.GetTabLines(); + if( bNoSelection && nMinSize > nEndLn - nStartLn + 1 ) + { + SwTableLines::size_type nNewEndLn = nStartLn + nMinSize - 1; + if( nNewEndLn >= rLines.size() ) + { + mnAddLine = nNewEndLn - rLines.size() + 1; + nNewEndLn = rLines.size() - 1; + } + while( nEndLn < nNewEndLn ) + { + SwTableLine *pLine2 = rLines[ ++nEndLn ]; + SwTableBox *pTmpBox = pLine2->GetTabBoxes()[0]; + FndLine_ *pInsLine = new FndLine_( pLine2, &rFndBox ); + pInsLine->GetBoxes().insert(pInsLine->GetBoxes().begin(), std::make_unique<FndBox_>(pTmpBox, pInsLine)); + rFndLines.push_back(std::unique_ptr<FndLine_>(pInsLine)); + } + } + maLines.resize( nEndLn - nStartLn + 1 ); + const SwSelBoxes* pSelBoxes = &rSelBoxes; + sal_uInt16 nCnt = 0; + for( SwTableLines::size_type nLine = nStartLn; nLine <= nEndLn; ++nLine ) + { + addLine( nCnt, rLines[nLine]->GetTabBoxes(), + pSelBoxes, rTable.IsNewModel() ); + if( bNoSelection ) + pSelBoxes = nullptr; + } + } + if( bNoSelection && mnStartCol < USHRT_MAX ) + { + sal_uInt16 nIdx = std::min(mnStartCol, static_cast<sal_uInt16>(maLines[0].size())); + mnStartCol = std::accumulate(maLines[0].begin(), maLines[0].begin() + nIdx, sal_uInt16(0), + [](sal_uInt16 sum, const BoxSpanInfo& rInfo) { return sum + rInfo.mnColSpan; }); + } + else + mnStartCol = USHRT_MAX; + } + } + + void TableStructure::addLine( sal_uInt16 &rLine, const SwTableBoxes& rBoxes, + const SwSelBoxes* pSelBoxes, bool bNewModel ) + { + bool bComplex = false; + if( !bNewModel ) + for( SwTableBoxes::size_type nBox = 0; !bComplex && nBox < rBoxes.size(); ++nBox ) + bComplex = !rBoxes[nBox]->GetTabLines().empty(); + if( bComplex ) + { + SubTable aSubTable; + SubLine aSubLine; + aSubTable.push_back( aSubLine ); + SubTable::iterator pStartLn = aSubTable.begin(); + SubTable::iterator pEndLn = aSubTable.end(); + for( auto pBox : rBoxes ) + insertSubBox( aSubTable, *pBox, pStartLn, pEndLn ); + SubTable::size_type nSize = aSubTable.size(); + if( nSize ) + { + maLines.resize( maLines.size() + nSize - 1 ); + while( pStartLn != pEndLn ) + { + bool bSelected = false; + sal_uLong nBorder = 0; + sal_uInt16 nCol = 0; + maLines[rLine].reserve( pStartLn->size() ); + BoxStructure::iterator pSel = maLines[rLine].end(); + ColumnStructure::iterator pCol = maCols.begin(); + for( const auto& rBox : *pStartLn ) + { + addBox( rLine, pSelBoxes, rBox.mpBox, nBorder, nCol, + pCol, pSel, bSelected, rBox.mbCovered ); + } + ++rLine; + ++pStartLn; + } + } + } + else + { + bool bSelected = false; + sal_uLong nBorder = 0; + sal_uInt16 nCol = 0; + maLines[rLine].reserve( rBoxes.size() ); + ColumnStructure::iterator pCol = maCols.begin(); + BoxStructure::iterator pSel = maLines[rLine].end(); + for( auto pBox : rBoxes ) + addBox( rLine, pSelBoxes, pBox, nBorder, nCol, + pCol, pSel, bSelected, false ); + ++rLine; + } + } + + void TableStructure::addBox( sal_uInt16 nLine, const SwSelBoxes* pSelBoxes, + SwTableBox *pBox, sal_uLong &rnBorder, sal_uInt16 &rnCol, + ColumnStructure::iterator& rpCol, BoxStructure::iterator& rpSel, + bool &rbSelected, bool bCovered ) + { + BoxSpanInfo aInfo; + if( pSelBoxes && + pSelBoxes->end() != pSelBoxes->find( pBox ) ) + { + aInfo.mbSelected = true; + if( mnStartCol == USHRT_MAX ) + { + mnStartCol = static_cast<sal_uInt16>(maLines[nLine].size()); + if( pSelBoxes->size() < 2 ) + { + pSelBoxes = nullptr; + aInfo.mbSelected = false; + } + } + } + else + aInfo.mbSelected = false; + rnBorder += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + const sal_uInt16 nLeftCol = rnCol; + while( rpCol != maCols.end() && *rpCol < rnBorder ) + { + ++rnCol; + ++rpCol; + } + if( rpCol == maCols.end() || *rpCol > rnBorder ) + { + rpCol = maCols.insert( rpCol, rnBorder ); + incColSpan( nLine, rnCol ); + } + aInfo.mnColSpan = rnCol - nLeftCol; + aInfo.mpCopy = nullptr; + aInfo.mpBox = bCovered ? nullptr : pBox; + maLines[nLine].push_back( aInfo ); + if( aInfo.mbSelected ) + { + if( rbSelected ) + { + while( rpSel != maLines[nLine].end() ) + { + rpSel->mbSelected = true; + ++rpSel; + } + } + else + { + rpSel = maLines[nLine].end(); + rbSelected = true; + } + --rpSel; + } + } + + void TableStructure::moreLines( const SwTable& rTable ) + { + if( mnAddLine ) + { + const SwTableLines &rLines = rTable.GetTabLines(); + const sal_uInt16 nLineCount = rLines.size(); + if( nLineCount < mnAddLine ) + mnAddLine = nLineCount; + sal_uInt16 nLine = static_cast<sal_uInt16>(maLines.size()); + maLines.resize( nLine + mnAddLine ); + while( mnAddLine ) + { + SwTableLine *pLine = rLines[ nLineCount - mnAddLine ]; + addLine( nLine, pLine->GetTabBoxes(), nullptr, rTable.IsNewModel() ); + --mnAddLine; + } + } + } + + void TableStructure::incColSpan( sal_uInt16 nLineMax, sal_uInt16 nNewCol ) + { + for( sal_uInt16 nLine = 0; nLine < nLineMax; ++nLine ) + { + BoxStructure::iterator pInfo = maLines[nLine].begin(); + BoxStructure::iterator pEnd = maLines[nLine].end(); + long nCol = pInfo->mnColSpan; + while( nNewCol > nCol && ++pInfo != pEnd ) + nCol += pInfo->mnColSpan; + if( pInfo != pEnd ) + ++(pInfo->mnColSpan); + } + } + + void TableStructure::assignBoxes( const TableStructure &rSource ) + { + LineStructure::const_iterator pFirstLine = rSource.maLines.begin(); + LineStructure::const_iterator pLastLine = rSource.maLines.end(); + if( pFirstLine == pLastLine ) + return; + LineStructure::const_iterator pCurrLine = pFirstLine; + LineStructure::size_type nLineCount = maLines.size(); + sal_uInt16 nFirstStartCol = 0; + { + BoxStructure::const_iterator pFirstBox = pFirstLine->begin(); + if( pFirstBox != pFirstLine->end() && pFirstBox->mpBox && + pFirstBox->mpBox->getDummyFlag() ) + nFirstStartCol = pFirstBox->mnColSpan; + } + for( LineStructure::size_type nLine = 0; nLine < nLineCount; ++nLine ) + { + BoxStructure::const_iterator pFirstBox = pCurrLine->begin(); + BoxStructure::const_iterator pLastBox = pCurrLine->end(); + sal_uInt16 nCurrStartCol = mnStartCol; + if( pFirstBox != pLastBox ) + { + BoxStructure::const_iterator pTmpBox = pLastBox; + --pTmpBox; + if( pTmpBox->mpBox && pTmpBox->mpBox->getDummyFlag() ) + --pLastBox; + if( pFirstBox != pLastBox && pFirstBox->mpBox && + pFirstBox->mpBox->getDummyFlag() ) + { + if( nCurrStartCol < USHRT_MAX ) + { + if( pFirstBox->mnColSpan > nFirstStartCol ) + nCurrStartCol += pFirstBox->mnColSpan - nFirstStartCol; + } + ++pFirstBox; + } + } + if( pFirstBox != pLastBox ) + { + BoxStructure::const_iterator pCurrBox = pFirstBox; + BoxStructure &rBox = maLines[nLine]; + BoxStructure::size_type nBoxCount = rBox.size(); + sal_uInt16 nCol = 0; + for( BoxStructure::size_type nBox = 0; nBox < nBoxCount; ++nBox ) + { + BoxSpanInfo& rInfo = rBox[nBox]; + nCol += rInfo.mnColSpan; + if( rInfo.mbSelected || nCol > nCurrStartCol ) + { + rInfo.mpCopy = pCurrBox->mpBox; + if( rInfo.mbSelected && rInfo.mpCopy->getDummyFlag() ) + { + ++pCurrBox; + if( pCurrBox == pLastBox ) + { + pCurrBox = pFirstBox; + if( pCurrBox->mpBox->getDummyFlag() ) + ++pCurrBox; + } + rInfo.mpCopy = pCurrBox->mpBox; + } + ++pCurrBox; + if( pCurrBox == pLastBox ) + { + if( rInfo.mbSelected ) + pCurrBox = pFirstBox; + else + { + rInfo.mbSelected = rInfo.mpCopy == nullptr; + break; + } + } + rInfo.mbSelected = rInfo.mpCopy == nullptr; + } + } + } + ++pCurrLine; + if( pCurrLine == pLastLine ) + pCurrLine = pFirstLine; + } + } + + void TableStructure::copyBoxes( const SwTable& rSource, SwTable& rDstTable, + SwUndoTableCpyTable* pUndo ) const + { + LineStructure::size_type nLineCount = maLines.size(); + for( LineStructure::size_type nLine = 0; nLine < nLineCount; ++nLine ) + { + const BoxStructure &rBox = maLines[nLine]; + BoxStructure::size_type nBoxCount = rBox.size(); + for( BoxStructure::size_type nBox = 0; nBox < nBoxCount; ++nBox ) + { + const BoxSpanInfo& rInfo = rBox[nBox]; + if( ( rInfo.mpCopy && !rInfo.mpCopy->getDummyFlag() ) + || rInfo.mbSelected ) + { + SwTableBox *pBox = rInfo.mpBox; + if( pBox && pBox->getRowSpan() > 0 ) + lcl_CpyBox( rSource, rInfo.mpCopy, rDstTable, pBox, + true, pUndo ); + } + } + } + } +} + +/** Copy Table into this Box. + Copy all Boxes of a Line into the corresponding Boxes. The old content + is deleted by doing this. + If no Box is left the remaining content goes to the Box of a "BaseLine". + If there's no Line anymore, put it also into the last Box of a "BaseLine". */ +static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, + SwTable& rDstTable, SwTableBox* pDstBox, + bool bDelContent, SwUndoTableCpyTable* pUndo ) +{ + OSL_ENSURE( ( !pCpyBox || pCpyBox->GetSttNd() ) && pDstBox->GetSttNd(), + "No content in this Box" ); + + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + SwDoc* pDoc = rDstTable.GetFrameFormat()->GetDoc(); + + // First copy the new content and then delete the old one. + // Do not create empty Sections, otherwise they will be deleted! + std::unique_ptr< SwNodeRange > pRg( pCpyBox ? + new SwNodeRange ( *pCpyBox->GetSttNd(), 1, + *pCpyBox->GetSttNd()->EndOfSectionNode() ) : nullptr ); + + SwNodeIndex aInsIdx( *pDstBox->GetSttNd(), bDelContent ? 1 : + pDstBox->GetSttNd()->EndOfSectionIndex() - + pDstBox->GetSttIdx() ); + + if( pUndo ) + pUndo->AddBoxBefore( *pDstBox, bDelContent ); + + bool bUndoRedline = pUndo && pDoc->getIDocumentRedlineAccess().IsRedlineOn(); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodeIndex aSavePos( aInsIdx, -1 ); + if (pRg) + pCpyDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pRg, aInsIdx, nullptr, false); + else + pDoc->GetNodes().MakeTextNode( aInsIdx, pDoc->GetDfltTextFormatColl() ); + ++aSavePos; + + SwTableLine* pLine = pDstBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + bool bReplaceColl = true; + if( bDelContent && !bUndoRedline ) + { + // Delete the Fly first, then the corresponding Nodes + SwNodeIndex aEndNdIdx( *aInsIdx.GetNode().EndOfSectionNode() ); + + // Move Bookmarks + { + SwPosition aMvPos( aInsIdx ); + SwContentNode* pCNd = SwNodes::GoPrevious( &aMvPos.nNode ); + aMvPos.nContent.Assign( pCNd, pCNd->Len() ); + SwDoc::CorrAbs( aInsIdx, aEndNdIdx, aMvPos ); + } + + // If we still have FlyFrames hanging around, delete them too + for( const auto pFly : *pDoc->GetSpzFrameFormats() ) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aInsIdx <= pAPos->nNode && pAPos->nNode <= aEndNdIdx ) + { + pDoc->getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + } + } + + // If DestBox is a Headline Box and has Table style set, then + // DO NOT automatically set the TableHeadline style! + if( 1 < rDstTable.GetTabLines().size() && + pLine == rDstTable.GetTabLines().front() ) + { + SwContentNode* pCNd = aInsIdx.GetNode().GetContentNode(); + if( !pCNd ) + { + SwNodeIndex aTmp( aInsIdx ); + pCNd = pDoc->GetNodes().GoNext( &aTmp ); + } + + if( pCNd && + RES_POOLCOLL_TABLE_HDLN != + pCNd->GetFormatColl()->GetPoolFormatId() ) + bReplaceColl = false; + } + + pDoc->GetNodes().Delete( aInsIdx, aEndNdIdx.GetIndex() - aInsIdx.GetIndex() ); + } + + //b6341295: Table copy redlining will be managed by AddBoxAfter() + if( pUndo ) + pUndo->AddBoxAfter( *pDstBox, aInsIdx, bDelContent ); + + // heading + SwTextNode *const pTextNd = aSavePos.GetNode().GetTextNode(); + if( pTextNd ) + { + const sal_uInt16 nPoolId = pTextNd->GetTextColl()->GetPoolFormatId(); + if( bReplaceColl && + (( 1 < rDstTable.GetTabLines().size() && + pLine == rDstTable.GetTabLines().front() ) + // Is the Table's content still valid? + ? RES_POOLCOLL_TABLE == nPoolId + : RES_POOLCOLL_TABLE_HDLN == nPoolId ) ) + { + SwTextFormatColl* pColl = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( + static_cast<sal_uInt16>( + RES_POOLCOLL_TABLE == nPoolId + ? RES_POOLCOLL_TABLE_HDLN + : RES_POOLCOLL_TABLE ) ); + if( pColl ) // Apply style + { + SwPaM aPam( aSavePos ); + aPam.SetMark(); + aPam.Move( fnMoveForward, GoInSection ); + pDoc->SetTextFormatColl( aPam, pColl ); + } + } + + // Delete the current Formula/Format/Value values + if( SfxItemState::SET == pDstBox->GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pDstBox->GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pDstBox->GetFrameFormat()->GetItemState( RES_BOXATR_VALUE ) ) + { + pDstBox->ClaimFrameFormat()->ResetFormatAttr( RES_BOXATR_FORMAT, + RES_BOXATR_VALUE ); + } + + // Copy the TableBoxAttributes - Formula/Format/Value + if( pCpyBox ) + { + SfxItemSet aBoxAttrSet( pCpyDoc->GetAttrPool(), svl::Items<RES_BOXATR_FORMAT, + RES_BOXATR_VALUE>{} ); + aBoxAttrSet.Put( pCpyBox->GetFrameFormat()->GetAttrSet() ); + if( aBoxAttrSet.Count() ) + { + const SfxPoolItem* pItem; + SvNumberFormatter* pN = pDoc->GetNumberFormatter( false ); + if( pN && pN->HasMergeFormatTable() && SfxItemState::SET == aBoxAttrSet. + GetItemState( RES_BOXATR_FORMAT, false, &pItem ) ) + { + sal_uLong nOldIdx = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + sal_uLong nNewIdx = pN->GetMergeFormatIndex( nOldIdx ); + if( nNewIdx != nOldIdx ) + aBoxAttrSet.Put( SwTableBoxNumFormat( nNewIdx )); + } + pDstBox->ClaimFrameFormat()->SetFormatAttr( aBoxAttrSet ); + } + } + } +} + +bool SwTable::InsNewTable( const SwTable& rCpyTable, const SwSelBoxes& rSelBoxes, + SwUndoTableCpyTable* pUndo ) +{ + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + + SwTableNumFormatMerge aTNFM( *pCpyDoc, *pDoc ); + + // Analyze source structure + TableStructure aCopyStruct( rCpyTable ); + + // Analyze target structure (from start box) and selected substructure + FndBox_ aFndBox( nullptr, nullptr ); + { // get all boxes/lines + FndPara aPara( rSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + TableStructure aTarget( *this, aFndBox, rSelBoxes, aCopyStruct.getLineCount() ); + + bool bClear = false; + if( aTarget.mnAddLine && IsNewModel() ) + { + SwSelBoxes aBoxes; + aBoxes.insert( GetTabLines().back()->GetTabBoxes().front() ); + if( pUndo ) + pUndo->InsertRow( *this, aBoxes, aTarget.mnAddLine ); + else + InsertRow( pDoc, aBoxes, aTarget.mnAddLine, /*bBehind*/true ); + + aTarget.moreLines( *this ); + bClear = true; + } + + // Find mapping, if needed extend target table and/or selection + aTarget.assignBoxes( aCopyStruct ); + + { + // Change table formulas into relative representation + SwTableFormulaUpdate aMsgHint( &rCpyTable ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pCpyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + // delete frames + aFndBox.SetTableLines( *this ); + if( bClear ) + aFndBox.ClearLineBehind(); + aFndBox.DelFrames( *this ); + + // copy boxes + aTarget.copyBoxes( rCpyTable, *this, pUndo ); + + // adjust row span attributes accordingly + + // make frames + aFndBox.MakeFrames( *this ); + + return true; +} + +/** Copy Table into this Box. + Copy all Boxes of a Line into the corresponding Boxes. The old content is + deleted by doing this. + If no Box is left the remaining content goes to the Box of a "BaseLine". + If there's no Line anymore, put it also into the last Box of a "BaseLine". */ +bool SwTable::InsTable( const SwTable& rCpyTable, const SwNodeIndex& rSttBox, + SwUndoTableCpyTable* pUndo ) +{ + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + + SwTableNode* pTableNd = pDoc->IsIdxInTable( rSttBox ); + + // Find the Box, to which should be copied: + SwTableBox* pMyBox = GetTableBox( + rSttBox.GetNode().FindTableBoxStartNode()->GetIndex() ); + + OSL_ENSURE( pMyBox, "Index is not in a Box in this Table" ); + + // First delete the Table's Frames + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.DelFrames( pTableNd->GetTable() ); + + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + + { + // Convert Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( &rCpyTable ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pCpyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + SwTableNumFormatMerge aTNFM( *pCpyDoc, *pDoc ); + + bool bDelContent = true; + const SwTableBox* pTmp; + + for( auto pLine : rCpyTable.GetTabLines() ) + { + // Get the first from the CopyLine + const SwTableBox* pCpyBox = pLine->GetTabBoxes().front(); + while( !pCpyBox->GetTabLines().empty() ) + pCpyBox = pCpyBox->GetTabLines().front()->GetTabBoxes().front(); + + do { + // First copy the new content and then delete the old one. + // Do not create empty Sections, otherwise they will be deleted! + lcl_CpyBox( rCpyTable, pCpyBox, *this, pMyBox, bDelContent, pUndo ); + + if( nullptr == (pTmp = pCpyBox->FindNextBox( rCpyTable, pCpyBox, false ))) + break; // no more Boxes + pCpyBox = pTmp; + + if( nullptr == ( pTmp = pMyBox->FindNextBox( *this, pMyBox, false ))) + bDelContent = false; // No space left? + else + pMyBox = const_cast<SwTableBox*>(pTmp); + + } while( true ); + + // Find the topmost Line + SwTableLine* pNxtLine = pMyBox->GetUpper(); + while( pNxtLine->GetUpper() ) + pNxtLine = pNxtLine->GetUpper()->GetUpper(); + const SwTableLines::size_type nPos = GetTabLines().GetPos( pNxtLine ) + 1; + // Is there a next? + if( nPos >= GetTabLines().size() ) + bDelContent = false; // there is none, all goes into the last Box + else + { + // Find the next Box with content + pNxtLine = GetTabLines()[ nPos ]; + pMyBox = pNxtLine->GetTabBoxes().front(); + while( !pMyBox->GetTabLines().empty() ) + pMyBox = pMyBox->GetTabLines().front()->GetTabBoxes().front(); + bDelContent = true; + } + } + + aFndBox.MakeFrames( pTableNd->GetTable() ); // Create the Frames anew + return true; +} + +bool SwTable::InsTable( const SwTable& rCpyTable, const SwSelBoxes& rSelBoxes, + SwUndoTableCpyTable* pUndo ) +{ + OSL_ENSURE( !rSelBoxes.empty(), "Missing selection" ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + if( IsNewModel() || rCpyTable.IsNewModel() ) + return InsNewTable( rCpyTable, rSelBoxes, pUndo ); + + OSL_ENSURE( !rCpyTable.IsTableComplex(), "Table too complex" ); + + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + + SwTableNumFormatMerge aTNFM( *pCpyDoc, *pDoc ); + + FndLine_ *pFLine; + FndBox_ aFndBox( nullptr, nullptr ); + // Find all Boxes/Lines + { + FndPara aPara( rSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + + // Special case: If a Box is located in a Table, copy it to all selected + // Boxes! + if( 1 != rCpyTable.GetTabSortBoxes().size() ) + { + FndBox_* pFndBox; + + const FndLines_t::size_type nFndCnt = aFndBox.GetLines().size(); + if( !nFndCnt ) + return false; + + // Check if we have enough space for all Lines and Boxes + SwTableLines::size_type nTstLns = 0; + pFLine = aFndBox.GetLines().front().get(); + sal_uInt16 nSttLine = GetTabLines().GetPos( pFLine->GetLine() ); + // Do we have as many rows, actually? + if( 1 == nFndCnt ) + { + // Is there still enough space in the Table? + if( (GetTabLines().size() - nSttLine ) < + rCpyTable.GetTabLines().size() ) + { + // If we don't have enough Lines, then see if we can insert + // new ones to reach our goal. But only if the SSelection + // contains a Box! + if( 1 < rSelBoxes.size() ) + return false; + + const sal_uInt16 nNewLns = rCpyTable.GetTabLines().size() - + (GetTabLines().size() - nSttLine ); + + // See if the Box count is high enough for the Lines + SwTableLine* pLastLn = GetTabLines().back(); + + SwTableBox* pSttBox = pFLine->GetBoxes()[0]->GetBox(); + const SwTableBoxes::size_type nSttBox = pFLine->GetLine()->GetBoxPos( pSttBox ); + for( SwTableLines::size_type n = rCpyTable.GetTabLines().size() - nNewLns; + n < rCpyTable.GetTabLines().size(); ++n ) + { + SwTableLine* pCpyLn = rCpyTable.GetTabLines()[ n ]; + + if( pLastLn->GetTabBoxes().size() < nSttBox || + ( pLastLn->GetTabBoxes().size() - nSttBox ) < + pCpyLn->GetTabBoxes().size() ) + return false; + + // Test for nesting + for( SwTableBoxes::size_type nBx = 0; nBx < pCpyLn->GetTabBoxes().size(); ++nBx ) + if( !pLastLn->GetTabBoxes()[ nSttBox + nBx ]->GetSttNd() ) + return false; + } + // We have enough space for the to-be-copied, so insert new + // rows accordingly. + SwTableBox* pInsBox = pLastLn->GetTabBoxes()[ nSttBox ]; + OSL_ENSURE( pInsBox && pInsBox->GetSttNd(), + "no ContentBox or it's not in this Table" ); + SwSelBoxes aBoxes; + + if( pUndo + ? !pUndo->InsertRow( *this, SelLineFromBox( pInsBox, + aBoxes ), nNewLns ) + : !InsertRow( pDoc, SelLineFromBox( pInsBox, + aBoxes ), nNewLns, /*bBehind*/true ) ) + return false; + } + + nTstLns = rCpyTable.GetTabLines().size(); // copy this many + } + else if( 0 == (nFndCnt % rCpyTable.GetTabLines().size()) ) + nTstLns = nFndCnt; + else + return false; // not enough space for the rows + + for( SwTableLines::size_type nLn = 0; nLn < nTstLns; ++nLn ) + { + // We have enough rows, so check the Boxes per row + pFLine = aFndBox.GetLines()[ nLn % nFndCnt ].get(); + SwTableLine* pLine = pFLine->GetLine(); + SwTableBox* pSttBox = pFLine->GetBoxes()[0]->GetBox(); + const SwTableBoxes::size_type nSttBox = pLine->GetBoxPos( pSttBox ); + std::unique_ptr<FndLine_> pInsFLine; + if( nLn >= nFndCnt ) + { + // We have more rows in the ClipBoard than we have selected + pInsFLine.reset(new FndLine_( GetTabLines()[ nSttLine + nLn ], + &aFndBox )); + pLine = pInsFLine->GetLine(); + } + SwTableLine* pCpyLn = rCpyTable.GetTabLines()[ nLn % + rCpyTable.GetTabLines().size() ]; + + // Selected too few rows? + if( pInsFLine ) + { + // We insert a new row into the FndBox + if( pLine->GetTabBoxes().size() < nSttBox || + pLine->GetTabBoxes().size() - nSttBox < pFLine->GetBoxes().size() ) + { + return false; + } + + // Test for nesting + for (FndBoxes_t::size_type nBx = 0; nBx < pFLine->GetBoxes().size(); ++nBx) + { + SwTableBox *pTmpBox = pLine->GetTabBoxes()[ nSttBox + nBx ]; + if( !pTmpBox->GetSttNd() ) + { + return false; + } + // if Ok, insert the Box into the FndLine + pFndBox = new FndBox_( pTmpBox, pInsFLine.get() ); + pInsFLine->GetBoxes().insert( pInsFLine->GetBoxes().begin() + nBx, + std::unique_ptr<FndBox_>(pFndBox)); + } + aFndBox.GetLines().insert( aFndBox.GetLines().begin() + nLn, std::move(pInsFLine)); + } + else if( pFLine->GetBoxes().size() == 1 ) + { + if( pLine->GetTabBoxes().size() < nSttBox || + ( pLine->GetTabBoxes().size() - nSttBox ) < + pCpyLn->GetTabBoxes().size() ) + return false; + + // Test for nesting + for( SwTableBoxes::size_type nBx = 0; nBx < pCpyLn->GetTabBoxes().size(); ++nBx ) + { + SwTableBox *pTmpBox = pLine->GetTabBoxes()[ nSttBox + nBx ]; + if( !pTmpBox->GetSttNd() ) + return false; + // if Ok, insert the Box into the FndLine + if( nBx == pFLine->GetBoxes().size() ) + { + pFndBox = new FndBox_( pTmpBox, pFLine ); + pFLine->GetBoxes().insert(pFLine->GetBoxes().begin() + nBx, + std::unique_ptr<FndBox_>(pFndBox)); + } + } + } + else + { + // Match the selected Boxes with the ones in the Clipboard + // (n times) + if( 0 != ( pFLine->GetBoxes().size() % + pCpyLn->GetTabBoxes().size() )) + return false; + + // Test for nesting + for (auto &rpBox : pFLine->GetBoxes()) + { + if (!rpBox->GetBox()->GetSttNd()) + return false; + } + } + } + + if( aFndBox.GetLines().empty() ) + return false; + } + + { + // Convert Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( &rCpyTable ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pCpyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + // Delete the Frames + aFndBox.SetTableLines( *this ); + //Not dispose accessible table + aFndBox.DelFrames( *this ); + + if( 1 == rCpyTable.GetTabSortBoxes().size() ) + { + SwTableBox *pTmpBx = rCpyTable.GetTabSortBoxes()[0]; + for (size_t n = 0; n < rSelBoxes.size(); ++n) + { + lcl_CpyBox( rCpyTable, pTmpBx, *this, + rSelBoxes[n], true, pUndo ); + } + } + else + for (FndLines_t::size_type nLn = 0; nLn < aFndBox.GetLines().size(); ++nLn) + { + pFLine = aFndBox.GetLines()[ nLn ].get(); + SwTableLine* pCpyLn = rCpyTable.GetTabLines()[ + nLn % rCpyTable.GetTabLines().size() ]; + for (FndBoxes_t::size_type nBx = 0; nBx < pFLine->GetBoxes().size(); ++nBx) + { + // Copy the pCpyBox into pMyBox + lcl_CpyBox( rCpyTable, pCpyLn->GetTabBoxes()[ + nBx % pCpyLn->GetTabBoxes().size() ], + *this, pFLine->GetBoxes()[nBx]->GetBox(), true, pUndo ); + } + } + + aFndBox.MakeFrames( *this ); + return true; +} + +static void FndContentLine( const SwTableLine* pLine, SwSelBoxes* pPara ); + +static void FndContentBox( const SwTableBox* pBox, SwSelBoxes* pPara ) +{ + if( !pBox->GetTabLines().empty() ) + { + for( const SwTableLine* pLine : pBox->GetTabLines() ) + FndContentLine( pLine, pPara ); + } + else + pPara->insert( const_cast<SwTableBox*>(pBox) ); +} + +static void FndContentLine( const SwTableLine* pLine, SwSelBoxes* pPara ) +{ + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + FndContentBox(pBox, pPara ); +} + +// Find all Boxes with content in this Box +SwSelBoxes& SwTable::SelLineFromBox( const SwTableBox* pBox, + SwSelBoxes& rBoxes, bool bToTop ) +{ + SwTableLine* pLine = const_cast<SwTableLine*>(pBox->GetUpper()); + if( bToTop ) + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // Delete all old ones + rBoxes.clear(); + for( const auto& rpBox : pLine->GetTabBoxes() ) + FndContentBox(rpBox, &rBoxes ); + return rBoxes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/tblrwcl.cxx b/sw/source/core/doc/tblrwcl.cxx new file mode 100644 index 000000000..5eb2ff999 --- /dev/null +++ b/sw/source/core/doc/tblrwcl.cxx @@ -0,0 +1,3403 @@ +/* -*- 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 <memory> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <hintids.hxx> + +#include <editeng/lrspitem.hxx> +#include <editeng/boxitem.hxx> +#include <tools/fract.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docsh.hxx> +#include <fesh.hxx> +#include <tabfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <tblsel.hxx> +#include <fldbas.hxx> +#include <rowfrm.hxx> +#include <ddefld.hxx> +#include <hints.hxx> +#include <UndoTable.hxx> +#include <cellatr.hxx> +#include <mvsave.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <poolfmt.hxx> +#include <tblrwcl.hxx> +#include <unochart.hxx> +#include <o3tl/numeric.hxx> +#include <calbck.hxx> +#include <docary.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; + +#define COLFUZZY 20 +#define ROWFUZZY 10 + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +namespace { + +// In order to set the Frame Formats for the Boxes, it's enough to look +// up the current one in the array. If it's already there return the new one. +struct CpyTabFrame +{ + SwFrameFormat* pFrameFormat; + SwTableBoxFormat *pNewFrameFormat; + + explicit CpyTabFrame(SwFrameFormat* pCurrentFrameFormat) : pNewFrameFormat( nullptr ) + { pFrameFormat = pCurrentFrameFormat; } + + bool operator==( const CpyTabFrame& rCpyTabFrame ) const + { return pFrameFormat == rCpyTabFrame.pFrameFormat; } + bool operator<( const CpyTabFrame& rCpyTabFrame ) const + { return pFrameFormat < rCpyTabFrame.pFrameFormat; } +}; + +struct CR_SetBoxWidth +{ + SwShareBoxFormats aShareFormats; + SwTableNode* pTableNd; + SwTwips nDiff, nSide, nMaxSize, nLowerDiff; + TableChgMode nMode; + bool bBigger, bLeft; + + CR_SetBoxWidth( TableChgWidthHeightType eType, SwTwips nDif, SwTwips nSid, + SwTwips nMax, SwTableNode* pTNd ) + : pTableNd( pTNd ), + nDiff( nDif ), nSide( nSid ), nMaxSize( nMax ), nLowerDiff( 0 ) + { + bLeft = TableChgWidthHeightType::ColLeft == extractPosition( eType ) || + TableChgWidthHeightType::CellLeft == extractPosition( eType ); + bBigger = bool(eType & TableChgWidthHeightType::BiggerMode ); + nMode = pTableNd->GetTable().GetTableChgMode(); + } + CR_SetBoxWidth( const CR_SetBoxWidth& rCpy ) + : pTableNd( rCpy.pTableNd ), + nDiff( rCpy.nDiff ), nSide( rCpy.nSide ), + nMaxSize( rCpy.nMaxSize ), nLowerDiff( 0 ), + nMode( rCpy.nMode ), + bBigger( rCpy.bBigger ), bLeft( rCpy.bLeft ) + { + } + + void LoopClear() + { + nLowerDiff = 0; + } +}; + +} + +static bool lcl_SetSelBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ); +static bool lcl_SetOtherBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ); + +typedef bool (*FN_lcl_SetBoxWidth)(SwTableLine*, CR_SetBoxWidth&, SwTwips, bool ); + +#ifdef DBG_UTIL + +#define CHECKBOXWIDTH \ + { \ + SwTwips nSize = GetFrameFormat()->GetFrameSize().GetWidth(); \ + for (size_t nTmp = 0; nTmp < m_aLines.size(); ++nTmp) \ + ::CheckBoxWidth( *m_aLines[ nTmp ], nSize ); \ + } + +#define CHECKTABLELAYOUT \ + { \ + for ( size_t i = 0; i < GetTabLines().size(); ++i ) \ + { \ + SwFrameFormat* pFormat = GetTabLines()[i]->GetFrameFormat(); \ + SwIterator<SwRowFrame,SwFormat> aIter( *pFormat ); \ + for (SwRowFrame* pFrame=aIter.First(); pFrame; pFrame=aIter.Next())\ + { \ + if ( pFrame->GetTabLine() == GetTabLines()[i] ) \ + { \ + OSL_ENSURE( pFrame->GetUpper()->IsTabFrame(), \ + "Table layout does not match table structure" ); \ + } \ + } \ + } \ + } + +#else + +#define CHECKBOXWIDTH +#define CHECKTABLELAYOUT + +#endif // DBG_UTIL + +namespace { + +struct CR_SetLineHeight +{ + SwTableNode* pTableNd; + SwTwips nMaxSpace, nMaxHeight; + TableChgMode nMode; + bool bBigger; + + CR_SetLineHeight( TableChgWidthHeightType eType, SwTableNode* pTNd ) + : pTableNd( pTNd ), + nMaxSpace( 0 ), nMaxHeight( 0 ) + { + bBigger = bool(eType & TableChgWidthHeightType::BiggerMode ); + nMode = pTableNd->GetTable().GetTableChgMode(); + } + CR_SetLineHeight( const CR_SetLineHeight& rCpy ) + : pTableNd( rCpy.pTableNd ), + nMaxSpace( rCpy.nMaxSpace ), nMaxHeight( rCpy.nMaxHeight ), + nMode( rCpy.nMode ), + bBigger( rCpy.bBigger ) + {} +}; + +} + +static bool lcl_SetSelLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ); +static bool lcl_SetOtherLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ); + +typedef bool (*FN_lcl_SetLineHeight)(SwTableLine*, CR_SetLineHeight&, SwTwips, bool ); + +typedef o3tl::sorted_vector<CpyTabFrame> CpyTabFrames; + +namespace { + +struct CpyPara +{ + std::shared_ptr< std::vector< std::vector< sal_uLong > > > pWidths; + SwDoc* pDoc; + SwTableNode* pTableNd; + CpyTabFrames& rTabFrameArr; + SwTableLine* pInsLine; + SwTableBox* pInsBox; + sal_uLong nOldSize, nNewSize; // in order to correct the size attributes + sal_uLong nMinLeft, nMaxRight; + sal_uInt16 nCpyCnt, nInsPos; + sal_uInt16 nLnIdx, nBoxIdx; + sal_uInt8 nDelBorderFlag; + bool bCpyContent; + + CpyPara( SwTableNode* pNd, sal_uInt16 nCopies, CpyTabFrames& rFrameArr ) + : pDoc( pNd->GetDoc() ), pTableNd( pNd ), rTabFrameArr(rFrameArr), + pInsLine(nullptr), pInsBox(nullptr), nOldSize(0), nNewSize(0), + nMinLeft(ULONG_MAX), nMaxRight(0), + nCpyCnt(nCopies), nInsPos(0), + nLnIdx(0), nBoxIdx(0), + nDelBorderFlag(0), bCpyContent( true ) + {} + CpyPara( const CpyPara& rPara, SwTableLine* pLine ) + : pWidths( rPara.pWidths ), pDoc(rPara.pDoc), pTableNd(rPara.pTableNd), + rTabFrameArr(rPara.rTabFrameArr), pInsLine(pLine), pInsBox(rPara.pInsBox), + nOldSize(0), nNewSize(rPara.nNewSize), nMinLeft( rPara.nMinLeft ), + nMaxRight( rPara.nMaxRight ), nCpyCnt(rPara.nCpyCnt), nInsPos(0), + nLnIdx( rPara.nLnIdx), nBoxIdx( rPara.nBoxIdx ), + nDelBorderFlag( rPara.nDelBorderFlag ), bCpyContent( rPara.bCpyContent ) + {} + CpyPara( const CpyPara& rPara, SwTableBox* pBox ) + : pWidths( rPara.pWidths ), pDoc(rPara.pDoc), pTableNd(rPara.pTableNd), + rTabFrameArr(rPara.rTabFrameArr), pInsLine(rPara.pInsLine), pInsBox(pBox), + nOldSize(rPara.nOldSize), nNewSize(rPara.nNewSize), + nMinLeft( rPara.nMinLeft ), nMaxRight( rPara.nMaxRight ), + nCpyCnt(rPara.nCpyCnt), nInsPos(0), nLnIdx(rPara.nLnIdx), nBoxIdx(rPara.nBoxIdx), + nDelBorderFlag( rPara.nDelBorderFlag ), bCpyContent( rPara.bCpyContent ) + {} +}; + +} + +static void lcl_CopyRow(FndLine_ & rFndLine, CpyPara *const pCpyPara); + +static void lcl_CopyCol( FndBox_ & rFndBox, CpyPara *const pCpyPara) +{ + // Look up the Frame Format in the Frame Format Array + SwTableBox* pBox = rFndBox.GetBox(); + CpyTabFrame aFindFrame(pBox->GetFrameFormat()); + + sal_uInt16 nFndPos; + if( pCpyPara->nCpyCnt ) + { + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.lower_bound( aFindFrame ); + nFndPos = itFind - pCpyPara->rTabFrameArr.begin(); + if( itFind == pCpyPara->rTabFrameArr.end() || !(*itFind == aFindFrame) ) + { + // For nested copying, also save the new Format as an old one. + SwTableBoxFormat* pNewFormat = static_cast<SwTableBoxFormat*>(pBox->ClaimFrameFormat()); + + // Find the selected Boxes in the Line: + FndLine_ const* pCmpLine = nullptr; + SwFormatFrameSize aFrameSz( pNewFormat->GetFrameSize() ); + + bool bDiffCount = false; + if( !pBox->GetTabLines().empty() ) + { + pCmpLine = rFndBox.GetLines().front().get(); + if ( pCmpLine->GetBoxes().size() != pCmpLine->GetLine()->GetTabBoxes().size() ) + bDiffCount = true; + } + + if( bDiffCount ) + { + // The first Line should be enough + FndBoxes_t const& rFndBoxes = pCmpLine->GetBoxes(); + long nSz = 0; + for( auto n = rFndBoxes.size(); n; ) + { + nSz += rFndBoxes[--n]->GetBox()-> + GetFrameFormat()->GetFrameSize().GetWidth(); + } + aFrameSz.SetWidth( aFrameSz.GetWidth() - + nSz / ( pCpyPara->nCpyCnt + 1 ) ); + pNewFormat->SetFormatAttr( aFrameSz ); + aFrameSz.SetWidth( nSz / ( pCpyPara->nCpyCnt + 1 ) ); + + // Create a new Format for the new Box, specifying its size. + aFindFrame.pNewFrameFormat = reinterpret_cast<SwTableBoxFormat*>(pNewFormat->GetDoc()-> + MakeTableLineFormat()); + *aFindFrame.pNewFrameFormat = *pNewFormat; + aFindFrame.pNewFrameFormat->SetFormatAttr( aFrameSz ); + } + else + { + aFrameSz.SetWidth( aFrameSz.GetWidth() / ( pCpyPara->nCpyCnt + 1 ) ); + pNewFormat->SetFormatAttr( aFrameSz ); + + aFindFrame.pNewFrameFormat = pNewFormat; + pCpyPara->rTabFrameArr.insert( aFindFrame ); + aFindFrame.pFrameFormat = pNewFormat; + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + } + else + { + aFindFrame = pCpyPara->rTabFrameArr[ nFndPos ]; + pBox->ChgFrameFormat( aFindFrame.pNewFrameFormat ); + } + } + else + { + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.find( aFindFrame ); + if( pCpyPara->nDelBorderFlag && + itFind != pCpyPara->rTabFrameArr.end() ) + aFindFrame = *itFind; + else + aFindFrame.pNewFrameFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + } + + if (!rFndBox.GetLines().empty()) + { + pBox = new SwTableBox( aFindFrame.pNewFrameFormat, + rFndBox.GetLines().size(), pCpyPara->pInsLine ); + pCpyPara->pInsLine->GetTabBoxes().insert( pCpyPara->pInsLine->GetTabBoxes().begin() + pCpyPara->nInsPos++, pBox ); + CpyPara aPara( *pCpyPara, pBox ); + aPara.nDelBorderFlag &= 7; + + for (auto const& pFndLine : rFndBox.GetLines()) + { + lcl_CopyRow(*pFndLine, &aPara); + } + } + else + { + ::InsTableBox( pCpyPara->pDoc, pCpyPara->pTableNd, pCpyPara->pInsLine, + aFindFrame.pNewFrameFormat, pBox, pCpyPara->nInsPos++ ); + + const FndBoxes_t& rFndBxs = rFndBox.GetUpper()->GetBoxes(); + if( 8 > pCpyPara->nDelBorderFlag + ? pCpyPara->nDelBorderFlag != 0 + : &rFndBox == rFndBxs[rFndBxs.size() - 1].get()) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + if( 8 > pCpyPara->nDelBorderFlag + ? rBoxItem.GetTop() + : rBoxItem.GetRight() ) + { + aFindFrame.pFrameFormat = pBox->GetFrameFormat(); + + SvxBoxItem aNew( rBoxItem ); + if( 8 > pCpyPara->nDelBorderFlag ) + aNew.SetLine( nullptr, SvxBoxItemLine::TOP ); + else + aNew.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + + if( 1 == pCpyPara->nDelBorderFlag || + 8 == pCpyPara->nDelBorderFlag ) + { + // For all Boxes that delete TopBorderLine, we copy after that + pBox = pCpyPara->pInsLine->GetTabBoxes()[ + pCpyPara->nInsPos - 1 ]; + } + + aFindFrame.pNewFrameFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + + // Else we copy before that and the first Line keeps the TopLine + // and we remove it at the original + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + + if( !pCpyPara->nCpyCnt ) + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + } + } +} + +static void lcl_CopyRow(FndLine_& rFndLine, CpyPara *const pCpyPara) +{ + SwTableLine* pNewLine = new SwTableLine( + static_cast<SwTableLineFormat*>(rFndLine.GetLine()->GetFrameFormat()), + rFndLine.GetBoxes().size(), pCpyPara->pInsBox ); + if( pCpyPara->pInsBox ) + { + SwTableLines& rLines = pCpyPara->pInsBox->GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine ); + } + else + { + SwTableLines& rLines = pCpyPara->pTableNd->GetTable().GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine ); + } + + CpyPara aPara( *pCpyPara, pNewLine ); + for (auto const& it : rFndLine.GetBoxes()) + { + lcl_CopyCol(*it, &aPara); + } + + pCpyPara->nDelBorderFlag &= 0xf8; +} + +static void lcl_InsCol( FndLine_* pFndLn, CpyPara& rCpyPara, sal_uInt16 nCpyCnt, + bool bBehind ) +{ + // Bug 29124: Not only copy in the BaseLines. If possible, we go down as far as possible + FndBox_* pFBox; + if( 1 == pFndLn->GetBoxes().size() && + !( pFBox = pFndLn->GetBoxes()[0].get() )->GetBox()->GetSttNd() ) + { + // A Box with multiple Lines, so insert into these Lines + for (auto &rpLine : pFBox->GetLines()) + { + lcl_InsCol( rpLine.get(), rCpyPara, nCpyCnt, bBehind ); + } + } + else + { + rCpyPara.pInsLine = pFndLn->GetLine(); + SwTableBox* pBox = pFndLn->GetBoxes()[ bBehind ? + pFndLn->GetBoxes().size()-1 : 0 ]->GetBox(); + rCpyPara.nInsPos = pFndLn->GetLine()->GetBoxPos( pBox ); + if( bBehind ) + ++rCpyPara.nInsPos; + + for( sal_uInt16 n = 0; n < nCpyCnt; ++n ) + { + if( n + 1 == nCpyCnt && bBehind ) + rCpyPara.nDelBorderFlag = 9; + else + rCpyPara.nDelBorderFlag = 8; + for (auto const& it : pFndLn->GetBoxes()) + { + lcl_CopyCol(*it, &rCpyPara); + } + } + } +} + +static SwRowFrame* GetRowFrame( SwTableLine& rLine ) +{ + SwIterator<SwRowFrame,SwFormat> aIter( *rLine.GetFrameFormat() ); + for( SwRowFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + if( pFrame->GetTabLine() == &rLine ) + return pFrame; + return nullptr; +} + +bool SwTable::InsertCol( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( !rBoxes.empty() && nCnt, "No valid Box List" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + bool bRes = true; + if( IsNewModel() ) + bRes = NewInsertCol( pDoc, rBoxes, nCnt, bBehind ); + else + { + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + // Find Lines for the layout update + aFndBox.SetTableLines( *this ); + aFndBox.DelFrames( *this ); + + // TL_CHART2: nothing to be done since chart2 currently does not want to + // get notified about new rows/cols. + + CpyTabFrames aTabFrameArr; + CpyPara aCpyPara( pTableNd, nCnt, aTabFrameArr ); + + for (auto & rpLine : aFndBox.GetLines()) + { + lcl_InsCol( rpLine.get(), aCpyPara, nCnt, bBehind ); + } + + // clean up this Line's structure once again, generally all of them + GCLines(); + + // Update Layout + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH; + CHECKTABLELAYOUT; + bRes = true; + } + + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD && nCnt) + pPCD->AddRowCols( *this, rBoxes, nCnt, bBehind ); + pDoc->UpdateCharts( GetFrameFormat()->GetName() ); + + pDoc->GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + return bRes; +} + +bool SwTable::InsertRow_( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( pDoc && !rBoxes.empty() && nCnt, "No valid Box List" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + FndBox_* pFndBox = &aFndBox; + { + FndLine_* pFndLine; + while( 1 == pFndBox->GetLines().size() ) + { + pFndLine = pFndBox->GetLines()[0].get(); + if( 1 != pFndLine->GetBoxes().size() ) + break; + // Don't go down too far! One Line with Box needs to remain! + FndBox_ *const pTmpBox = pFndLine->GetBoxes().front().get(); + if( !pTmpBox->GetLines().empty() ) + pFndBox = pTmpBox; + else + break; + } + } + + // Find Lines for the layout update + const bool bLayout = !IsNewModel() && + nullptr != SwIterator<SwTabFrame,SwFormat>( *GetFrameFormat() ).First(); + + if ( bLayout ) + { + aFndBox.SetTableLines( *this ); + if( pFndBox != &aFndBox ) + aFndBox.DelFrames( *this ); + // TL_CHART2: nothing to be done since chart2 currently does not want to + // get notified about new rows/cols. + } + + CpyTabFrames aTabFrameArr; + CpyPara aCpyPara( pTableNd, 0, aTabFrameArr ); + + SwTableLine* pLine = pFndBox->GetLines()[ bBehind ? + pFndBox->GetLines().size()-1 : 0 ]->GetLine(); + if( &aFndBox == pFndBox ) + aCpyPara.nInsPos = GetTabLines().GetPos( pLine ); + else + { + aCpyPara.pInsBox = pFndBox->GetBox(); + aCpyPara.nInsPos = pFndBox->GetBox()->GetTabLines().GetPos( pLine ); + } + + if( bBehind ) + { + ++aCpyPara.nInsPos; + aCpyPara.nDelBorderFlag = 1; + } + else + aCpyPara.nDelBorderFlag = 2; + + for( sal_uInt16 nCpyCnt = 0; nCpyCnt < nCnt; ++nCpyCnt ) + { + if( bBehind ) + aCpyPara.nDelBorderFlag = 1; + for (auto & rpFndLine : pFndBox->GetLines()) + lcl_CopyRow( *rpFndLine, &aCpyPara ); + } + + // clean up this Line's structure once again, generally all of them + if( !pDoc->IsInReading() ) + GCLines(); + + // Update Layout + if ( bLayout ) + { + if( pFndBox != &aFndBox ) + aFndBox.MakeFrames( *this ); + else + aFndBox.MakeNewFrames( *this, nCnt, bBehind ); + } + + CHECKBOXWIDTH; + CHECKTABLELAYOUT; + + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD && nCnt) + pPCD->AddRowCols( *this, rBoxes, nCnt, bBehind ); + pDoc->UpdateCharts( GetFrameFormat()->GetName() ); + + pDoc->GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + return true; +} + +static void lcl_LastBoxSetWidth( SwTableBoxes &rBoxes, const long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ); + +static void lcl_LastBoxSetWidthLine( SwTableLines &rLines, const long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ) +{ + for ( auto pLine : rLines ) + ::lcl_LastBoxSetWidth( pLine->GetTabBoxes(), nOffset, bFirst, rShareFormats ); +} + +static void lcl_LastBoxSetWidth( SwTableBoxes &rBoxes, const long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ) +{ + SwTableBox& rBox = *(bFirst ? rBoxes.front() : rBoxes.back()); + if( !rBox.GetSttNd() ) + ::lcl_LastBoxSetWidthLine( rBox.GetTabLines(), nOffset, + bFirst, rShareFormats ); + + // Adapt the Box + const SwFrameFormat *pBoxFormat = rBox.GetFrameFormat(); + SwFormatFrameSize aNew( pBoxFormat->GetFrameSize() ); + aNew.SetWidth( aNew.GetWidth() + nOffset ); + SwFrameFormat *pFormat = rShareFormats.GetFormat( *pBoxFormat, aNew ); + if( pFormat ) + rBox.ChgFrameFormat( static_cast<SwTableBoxFormat*>(pFormat) ); + else + { + pFormat = rBox.ClaimFrameFormat(); + + pFormat->LockModify(); + pFormat->SetFormatAttr( aNew ); + pFormat->UnlockModify(); + + rShareFormats.AddFormat( *pBoxFormat, *pFormat ); + } +} + +void DeleteBox_( SwTable& rTable, SwTableBox* pBox, SwUndo* pUndo, + bool bCalcNewSize, const bool bCorrBorder, + SwShareBoxFormats* pShareFormats ) +{ + do { + SwTwips nBoxSz = bCalcNewSize ? + pBox->GetFrameFormat()->GetFrameSize().GetWidth() : 0; + SwTableLine* pLine = pBox->GetUpper(); + SwTableBoxes& rTableBoxes = pLine->GetTabBoxes(); + sal_uInt16 nDelPos = pLine->GetBoxPos( pBox ); + SwTableBox* pUpperBox = pBox->GetUpper()->GetUpper(); + + // Special treatment for the border: + if( bCorrBorder && 1 < rTableBoxes.size() ) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + + if( rBoxItem.GetLeft() || rBoxItem.GetRight() ) + { + bool bChgd = false; + + // JP 02.04.97: 1st part for Bug 36271 + // First the left/right edges + if( nDelPos + 1 < static_cast<sal_uInt16>(rTableBoxes.size()) ) + { + SwTableBox* pNxtBox = rTableBoxes[ nDelPos + 1 ]; + const SvxBoxItem& rNxtBoxItem = pNxtBox->GetFrameFormat()->GetBox(); + + SwTableBox* pPrvBox = nDelPos ? rTableBoxes[ nDelPos - 1 ] : nullptr; + + if( pNxtBox->GetSttNd() && !rNxtBoxItem.GetLeft() && + ( !pPrvBox || !pPrvBox->GetFrameFormat()->GetBox().GetRight()) ) + { + SvxBoxItem aTmp( rNxtBoxItem ); + aTmp.SetLine( rBoxItem.GetLeft() ? rBoxItem.GetLeft() + : rBoxItem.GetRight(), + SvxBoxItemLine::LEFT ); + if( pShareFormats ) + pShareFormats->SetAttr( *pNxtBox, aTmp ); + else + pNxtBox->ClaimFrameFormat()->SetFormatAttr( aTmp ); + bChgd = true; + } + } + if( !bChgd && nDelPos ) + { + SwTableBox* pPrvBox = rTableBoxes[ nDelPos - 1 ]; + const SvxBoxItem& rPrvBoxItem = pPrvBox->GetFrameFormat()->GetBox(); + + SwTableBox* pNxtBox = nDelPos + 1 < static_cast<sal_uInt16>(rTableBoxes.size()) + ? rTableBoxes[ nDelPos + 1 ] : nullptr; + + if( pPrvBox->GetSttNd() && !rPrvBoxItem.GetRight() && + ( !pNxtBox || !pNxtBox->GetFrameFormat()->GetBox().GetLeft()) ) + { + SvxBoxItem aTmp( rPrvBoxItem ); + aTmp.SetLine( rBoxItem.GetLeft() ? rBoxItem.GetLeft() + : rBoxItem.GetRight(), + SvxBoxItemLine::RIGHT ); + if( pShareFormats ) + pShareFormats->SetAttr( *pPrvBox, aTmp ); + else + pPrvBox->ClaimFrameFormat()->SetFormatAttr( aTmp ); + } + } + } + } + + // Delete the Box first, then the Nodes! + SwStartNode* pSttNd = const_cast<SwStartNode*>(pBox->GetSttNd()); + if( pShareFormats ) + pShareFormats->RemoveFormat( *rTableBoxes[ nDelPos ]->GetFrameFormat() ); + + // Before deleting the 'Table Box' from memory - delete any redlines attached to it + if ( rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteTableCellRedline( rTable.GetFrameFormat()->GetDoc(), *(rTableBoxes[nDelPos]), true, RedlineType::Any ); + delete rTableBoxes[nDelPos]; + rTableBoxes.erase( rTableBoxes.begin() + nDelPos ); + + if( pSttNd ) + { + // Has the UndoObject been prepared to save the Section? + if( pUndo && pUndo->IsDelBox() ) + static_cast<SwUndoTableNdsChg*>(pUndo)->SaveSection( pSttNd ); + else + pSttNd->GetDoc()->getIDocumentContentOperations().DeleteSection( pSttNd ); + } + + // Also delete the Line? + if( !rTableBoxes.empty() ) + { + // Then adapt the Frame-SSize + bool bLastBox = nDelPos == rTableBoxes.size(); + if( bLastBox ) + --nDelPos; + pBox = rTableBoxes[nDelPos]; + if( bCalcNewSize ) + { + SwFormatFrameSize aNew( pBox->GetFrameFormat()->GetFrameSize() ); + aNew.SetWidth( aNew.GetWidth() + nBoxSz ); + if( pShareFormats ) + pShareFormats->SetSize( *pBox, aNew ); + else + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + + if( !pBox->GetSttNd() ) + { + // We need to this recursively in all Lines in all Cells! + SwShareBoxFormats aShareFormats; + ::lcl_LastBoxSetWidthLine( pBox->GetTabLines(), nBoxSz, + !bLastBox, + pShareFormats ? *pShareFormats + : aShareFormats ); + } + } + break; // Stop deleting + } + // Delete the Line from the Table/Box + if( !pUpperBox ) + { + // Also delete the Line from the Table + nDelPos = rTable.GetTabLines().GetPos( pLine ); + if( pShareFormats ) + pShareFormats->RemoveFormat( *rTable.GetTabLines()[ nDelPos ]->GetFrameFormat() ); + + SwTableLine* pTabLineToDelete = rTable.GetTabLines()[ nDelPos ]; + // Before deleting the 'Table Line' from memory - delete any redlines attached to it + if ( rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteTableRowRedline( rTable.GetFrameFormat()->GetDoc(), *pTabLineToDelete, true, RedlineType::Any ); + delete pTabLineToDelete; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + nDelPos ); + break; // we cannot delete more + } + + // finally also delete the Line + pBox = pUpperBox; + nDelPos = pBox->GetTabLines().GetPos( pLine ); + if( pShareFormats ) + pShareFormats->RemoveFormat( *pBox->GetTabLines()[ nDelPos ]->GetFrameFormat() ); + + SwTableLine* pTabLineToDelete = pBox->GetTabLines()[ nDelPos ]; + // Before deleting the 'Table Line' from memory - delete any redlines attached to it + if ( rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteTableRowRedline( rTable.GetFrameFormat()->GetDoc(), *pTabLineToDelete, true, RedlineType::Any ); + delete pTabLineToDelete; + pBox->GetTabLines().erase( pBox->GetTabLines().begin() + nDelPos ); + } while( pBox->GetTabLines().empty() ); +} + +static SwTableBox* +lcl_FndNxtPrvDelBox( const SwTableLines& rTableLns, + SwTwips nBoxStt, SwTwips nBoxWidth, + sal_uInt16 nLinePos, bool bNxt, + SwSelBoxes* pAllDelBoxes, size_t *const pCurPos) +{ + SwTableBox* pFndBox = nullptr; + do { + if( bNxt ) + ++nLinePos; + else + --nLinePos; + SwTableLine* pLine = rTableLns[ nLinePos ]; + SwTwips nFndBoxWidth = 0; + SwTwips nFndWidth = nBoxStt + nBoxWidth; + + pFndBox = pLine->GetTabBoxes()[ 0 ]; + for( auto pBox : pLine->GetTabBoxes() ) + { + if ( nFndWidth <= 0 ) + { + break; + } + pFndBox = pBox; + nFndBoxWidth = pFndBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nFndWidth -= nFndBoxWidth; + } + + // Find the first ContentBox + while( !pFndBox->GetSttNd() ) + { + const SwTableLines& rLowLns = pFndBox->GetTabLines(); + if( bNxt ) + pFndBox = rLowLns.front()->GetTabBoxes().front(); + else + pFndBox = rLowLns.back()->GetTabBoxes().front(); + } + + if( std::abs( nFndWidth ) > COLFUZZY || + std::abs( nBoxWidth - nFndBoxWidth ) > COLFUZZY ) + pFndBox = nullptr; + else if( pAllDelBoxes ) + { + // If the predecessor will also be deleted, there's nothing to do + SwSelBoxes::const_iterator aFndIt = pAllDelBoxes->find( pFndBox); + if( aFndIt == pAllDelBoxes->end() ) + break; + size_t const nFndPos = aFndIt - pAllDelBoxes->begin() ; + + // else, we keep on searching. + // We do not need to recheck the Box, however + pFndBox = nullptr; + if( nFndPos <= *pCurPos ) + --*pCurPos; + pAllDelBoxes->erase( pAllDelBoxes->begin() + nFndPos ); + } + } while( bNxt ? ( nLinePos + 1 < static_cast<sal_uInt16>(rTableLns.size()) ) : nLinePos != 0 ); + return pFndBox; +} + +static void +lcl_SaveUpperLowerBorder( SwTable& rTable, const SwTableBox& rBox, + SwShareBoxFormats& rShareFormats, + SwSelBoxes* pAllDelBoxes = nullptr, + size_t *const pCurPos = nullptr ) +{ +//JP 16.04.97: 2. part for Bug 36271 + const SwTableLine* pLine = rBox.GetUpper(); + const SwTableBoxes& rTableBoxes = pLine->GetTabBoxes(); + const SwTableBox* pUpperBox = &rBox; + sal_uInt16 nDelPos = pLine->GetBoxPos( pUpperBox ); + pUpperBox = rBox.GetUpper()->GetUpper(); + const SvxBoxItem& rBoxItem = rBox.GetFrameFormat()->GetBox(); + + // then the top/bottom edges + if( !rBoxItem.GetTop() && !rBoxItem.GetBottom() ) + return; + + bool bChgd = false; + const SwTableLines* pTableLns; + if( pUpperBox ) + pTableLns = &pUpperBox->GetTabLines(); + else + pTableLns = &rTable.GetTabLines(); + + sal_uInt16 nLnPos = pTableLns->GetPos( pLine ); + + // Calculate the attribute position of the top-be-deleted Box and then + // search in the top/bottom Line of the respective counterparts. + SwTwips nBoxStt = 0; + for( sal_uInt16 n = 0; n < nDelPos; ++n ) + nBoxStt += rTableBoxes[ n ]->GetFrameFormat()->GetFrameSize().GetWidth(); + SwTwips nBoxWidth = rBox.GetFrameFormat()->GetFrameSize().GetWidth(); + + SwTableBox *pPrvBox = nullptr, *pNxtBox = nullptr; + if( nLnPos ) // Predecessor? + pPrvBox = ::lcl_FndNxtPrvDelBox( *pTableLns, nBoxStt, nBoxWidth, + nLnPos, false, pAllDelBoxes, pCurPos ); + + if( nLnPos + 1 < static_cast<sal_uInt16>(pTableLns->size()) ) // Successor? + pNxtBox = ::lcl_FndNxtPrvDelBox( *pTableLns, nBoxStt, nBoxWidth, + nLnPos, true, pAllDelBoxes, pCurPos ); + + if( pNxtBox && pNxtBox->GetSttNd() ) + { + const SvxBoxItem& rNxtBoxItem = pNxtBox->GetFrameFormat()->GetBox(); + if( !rNxtBoxItem.GetTop() && ( !pPrvBox || + !pPrvBox->GetFrameFormat()->GetBox().GetBottom()) ) + { + SvxBoxItem aTmp( rNxtBoxItem ); + aTmp.SetLine( rBoxItem.GetTop() ? rBoxItem.GetTop() + : rBoxItem.GetBottom(), + SvxBoxItemLine::TOP ); + rShareFormats.SetAttr( *pNxtBox, aTmp ); + bChgd = true; + } + } + if( !bChgd && pPrvBox && pPrvBox->GetSttNd() ) + { + const SvxBoxItem& rPrvBoxItem = pPrvBox->GetFrameFormat()->GetBox(); + if( !rPrvBoxItem.GetTop() && ( !pNxtBox || + !pNxtBox->GetFrameFormat()->GetBox().GetTop()) ) + { + SvxBoxItem aTmp( rPrvBoxItem ); + aTmp.SetLine( rBoxItem.GetTop() ? rBoxItem.GetTop() + : rBoxItem.GetBottom(), + SvxBoxItemLine::BOTTOM ); + rShareFormats.SetAttr( *pPrvBox, aTmp ); + } + } + +} + +bool SwTable::DeleteSel( + SwDoc* pDoc + , + const SwSelBoxes& rBoxes, + const SwSelBoxes* pMerged, SwUndo* pUndo, + const bool bDelMakeFrames, const bool bCorrBorder ) +{ + OSL_ENSURE( pDoc, "No doc?" ); + SwTableNode* pTableNd = nullptr; + if( !rBoxes.empty() ) + { + pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + } + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + if ( bDelMakeFrames ) + { + if( pMerged && !pMerged->empty() ) + aFndBox.SetTableLines( *pMerged, *this ); + else if( !rBoxes.empty() ) + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + } + + SwShareBoxFormats aShareFormats; + + // First switch the Border, then delete + if( bCorrBorder ) + { + SwSelBoxes aBoxes( rBoxes ); + for (size_t n = 0; n < aBoxes.size(); ++n) + { + ::lcl_SaveUpperLowerBorder( *this, *rBoxes[ n ], aShareFormats, + &aBoxes, &n ); + } + } + + PrepareDelBoxes( rBoxes ); + + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + // Delete boxes from last to first + for (size_t n = 0; n < rBoxes.size(); ++n) + { + size_t const nIdx = rBoxes.size() - 1 - n; + + // First adapt the data-sequence for chart if necessary + // (needed to move the implementation cursor properly to its new + // position which can't be done properly if the cell is already gone) + if (pPCD && pTableNd) + pPCD->DeleteBox( &pTableNd->GetTable(), *rBoxes[nIdx] ); + + // ... then delete the boxes + DeleteBox_( *this, rBoxes[nIdx], pUndo, true, bCorrBorder, &aShareFormats ); + } + + // then clean up the structure of all Lines + GCLines(); + + if( bDelMakeFrames && aFndBox.AreLinesToRestore( *this ) ) + aFndBox.MakeFrames( *this ); + + // TL_CHART2: now inform chart that sth has changed + pDoc->UpdateCharts( GetFrameFormat()->GetName() ); + + CHECKTABLELAYOUT; + CHECK_TABLE( *this ); + + return true; +} + +bool SwTable::OldSplitRow( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, + bool bSameHeight ) +{ + OSL_ENSURE( pDoc && !rBoxes.empty() && nCnt, "No valid values" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // TL_CHART2: splitting/merging of a number of cells or rows will usually make + // the table too complex to be handled with chart. + // Thus we tell the charts to use their own data provider and forget about this table + pDoc->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( this ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + // If the rows should get the same (min) height, we first have + // to store the old row heights before deleting the frames + std::unique_ptr<long[]> pRowHeights; + if ( bSameHeight ) + { + pRowHeights.reset(new long[ rBoxes.size() ]); + for (size_t n = 0; n < rBoxes.size(); ++n) + { + SwTableBox* pSelBox = rBoxes[n]; + const SwRowFrame* pRow = GetRowFrame( *pSelBox->GetUpper() ); + OSL_ENSURE( pRow, "Where is the SwTableLine's Frame?" ); + SwRectFnSet aRectFnSet(pRow); + pRowHeights[ n ] = aRectFnSet.GetHeight(pRow->getFrameArea()); + } + } + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + + for (size_t n = 0; n < rBoxes.size(); ++n) + { + SwTableBox* pSelBox = rBoxes[n]; + OSL_ENSURE( pSelBox, "Box is not within the Table" ); + + // Insert nCnt new Lines into the Box + SwTableLine* pInsLine = pSelBox->GetUpper(); + SwTableBoxFormat* pFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()); + + // Respect the Line's height, reset if needed + SwFormatFrameSize aFSz( pInsLine->GetFrameFormat()->GetFrameSize() ); + if ( bSameHeight && SwFrameSize::Variable == aFSz.GetHeightSizeType() ) + aFSz.SetHeightSizeType( SwFrameSize::Minimum ); + + bool bChgLineSz = 0 != aFSz.GetHeight() || bSameHeight; + if ( bChgLineSz ) + aFSz.SetHeight( ( bSameHeight ? pRowHeights[ n ] : aFSz.GetHeight() ) / + (nCnt + 1) ); + + SwTableBox* pNewBox = new SwTableBox( pFrameFormat, nCnt, pInsLine ); + sal_uInt16 nBoxPos = pInsLine->GetBoxPos( pSelBox ); + pInsLine->GetTabBoxes()[nBoxPos] = pNewBox; // overwrite old one + + // Delete background/border attribute + SwTableBox* pLastBox = pSelBox; // To distribute the TextNodes! + // If Areas are contained in the Box, it stays as is + // !! If this is changed we need to adapt the Undo, too !!! + bool bMoveNodes = true; + { + sal_uLong nSttNd = pLastBox->GetSttIdx() + 1, + nEndNd = pLastBox->GetSttNd()->EndOfSectionIndex(); + while( nSttNd < nEndNd ) + if( !pDoc->GetNodes()[ nSttNd++ ]->IsTextNode() ) + { + bMoveNodes = false; + break; + } + } + + SwTableBoxFormat* pCpyBoxFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()); + bool bChkBorder = nullptr != pCpyBoxFrameFormat->GetBox().GetTop(); + if( bChkBorder ) + pCpyBoxFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->ClaimFrameFormat()); + + for( sal_uInt16 i = 0; i <= nCnt; ++i ) + { + // Create a new Line in the new Box + SwTableLine* pNewLine = new SwTableLine( + static_cast<SwTableLineFormat*>(pInsLine->GetFrameFormat()), 1, pNewBox ); + if( bChgLineSz ) + { + pNewLine->ClaimFrameFormat()->SetFormatAttr( aFSz ); + } + + pNewBox->GetTabLines().insert( pNewBox->GetTabLines().begin() + i, pNewLine ); + // then a new Box in the Line + if( !i ) // hang up the original Box + { + pSelBox->SetUpper( pNewLine ); + pNewLine->GetTabBoxes().insert( pNewLine->GetTabBoxes().begin(), pSelBox ); + } + else + { + ::InsTableBox( pDoc, pTableNd, pNewLine, pCpyBoxFrameFormat, + pLastBox, 0 ); + + if( bChkBorder ) + { + pCpyBoxFrameFormat = static_cast<SwTableBoxFormat*>(pNewLine->GetTabBoxes()[ 0 ]->ClaimFrameFormat()); + SvxBoxItem aTmp( pCpyBoxFrameFormat->GetBox() ); + aTmp.SetLine( nullptr, SvxBoxItemLine::TOP ); + pCpyBoxFrameFormat->SetFormatAttr( aTmp ); + bChkBorder = false; + } + + if( bMoveNodes ) + { + const SwNode* pEndNd = pLastBox->GetSttNd()->EndOfSectionNode(); + if( pLastBox->GetSttIdx()+2 != pEndNd->GetIndex() ) + { + // Move TextNodes + SwNodeRange aRg( *pLastBox->GetSttNd(), +2, *pEndNd ); + pLastBox = pNewLine->GetTabBoxes()[0]; // reset + SwNodeIndex aInsPos( *pLastBox->GetSttNd(), 1 ); + pDoc->GetNodes().MoveNodes(aRg, pDoc->GetNodes(), aInsPos, false); + pDoc->GetNodes().Delete( aInsPos ); // delete the empty one + } + } + } + } + // In Boxes with Lines, we can only have Size/Fillorder + pFrameFormat = static_cast<SwTableBoxFormat*>(pNewBox->ClaimFrameFormat()); + pFrameFormat->ResetFormatAttr( RES_LR_SPACE, RES_FRMATR_END - 1 ); + pFrameFormat->ResetFormatAttr( RES_BOXATR_BEGIN, RES_BOXATR_END - 1 ); + } + + pRowHeights.reset(); + + GCLines(); + + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH + CHECKTABLELAYOUT + return true; +} + +bool SwTable::SplitCol( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ) +{ + OSL_ENSURE( pDoc && !rBoxes.empty() && nCnt, "No valid values" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // TL_CHART2: splitting/merging of a number of cells or rows will usually make + // the table too complex to be handled with chart. + // Thus we tell the charts to use their own data provider and forget about this table + pDoc->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( this ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + SwSelBoxes aSelBoxes(rBoxes); + ExpandSelection( aSelBoxes ); + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( aSelBoxes, *this ); + aFndBox.DelFrames( *this ); + + CpyTabFrames aFrameArr; + std::vector<SwTableBoxFormat*> aLastBoxArr; + for (size_t n = 0; n < aSelBoxes.size(); ++n) + { + SwTableBox* pSelBox = aSelBoxes[n]; + OSL_ENSURE( pSelBox, "Box is not in the table" ); + + // We don't want to split small table cells into very very small cells + if( pSelBox->GetFrameFormat()->GetFrameSize().GetWidth()/( nCnt + 1 ) < 10 ) + continue; + + // Then split the nCnt Box up into nCnt Boxes + SwTableLine* pInsLine = pSelBox->GetUpper(); + sal_uInt16 nBoxPos = pInsLine->GetBoxPos( pSelBox ); + + // Find the Frame Format in the Frame Format Array + SwTableBoxFormat* pLastBoxFormat; + CpyTabFrame aFindFrame( static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()) ); + CpyTabFrames::const_iterator itFind = aFrameArr.lower_bound( aFindFrame ); + const size_t nFndPos = itFind - aFrameArr.begin(); + if( itFind == aFrameArr.end() || !(*itFind == aFindFrame) ) + { + // Change the FrameFormat + aFindFrame.pNewFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->ClaimFrameFormat()); + SwTwips nBoxSz = aFindFrame.pNewFrameFormat->GetFrameSize().GetWidth(); + SwTwips nNewBoxSz = nBoxSz / ( nCnt + 1 ); + aFindFrame.pNewFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + nNewBoxSz, 0 ) ); + aFrameArr.insert( aFindFrame ); + + pLastBoxFormat = aFindFrame.pNewFrameFormat; + if( nBoxSz != ( nNewBoxSz * (nCnt + 1))) + { + // We have a remainder, so we need to define an own Format + // for the last Box. + pLastBoxFormat = new SwTableBoxFormat( *aFindFrame.pNewFrameFormat ); + pLastBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + nBoxSz - ( nNewBoxSz * nCnt ), 0 ) ); + } + aLastBoxArr.insert( aLastBoxArr.begin() + nFndPos, pLastBoxFormat ); + } + else + { + aFindFrame = aFrameArr[ nFndPos ]; + pSelBox->ChgFrameFormat( aFindFrame.pNewFrameFormat ); + pLastBoxFormat = aLastBoxArr[ nFndPos ]; + } + + // Insert the Boxes at the Position + for( sal_uInt16 i = 1; i < nCnt; ++i ) + ::InsTableBox( pDoc, pTableNd, pInsLine, aFindFrame.pNewFrameFormat, + pSelBox, nBoxPos + i ); // insert after + + ::InsTableBox( pDoc, pTableNd, pInsLine, pLastBoxFormat, + pSelBox, nBoxPos + nCnt ); // insert after + + // Special treatment for the Border: + const SvxBoxItem& aSelBoxItem = aFindFrame.pNewFrameFormat->GetBox(); + if( aSelBoxItem.GetRight() ) + { + pInsLine->GetTabBoxes()[ nBoxPos + nCnt ]->ClaimFrameFormat(); + + SvxBoxItem aTmp( aSelBoxItem ); + aTmp.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + aFindFrame.pNewFrameFormat->SetFormatAttr( aTmp ); + + // Remove the Format from the "cache" + for( auto i = aFrameArr.size(); i; ) + { + const CpyTabFrame& rCTF = aFrameArr[ --i ]; + if( rCTF.pNewFrameFormat == aFindFrame.pNewFrameFormat || + rCTF.pFrameFormat == aFindFrame.pNewFrameFormat ) + { + aFrameArr.erase( aFrameArr.begin() + i ); + aLastBoxArr.erase( aLastBoxArr.begin() + i ); + } + } + } + } + + // Update Layout + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH + CHECKTABLELAYOUT + return true; +} + +/* + * >> MERGE << + * Algorithm: + * If we only have one Line in the FndBox_, take this Line and test + * the Box count: + * If we have more than one Box, we merge on Box level, meaning + * the new Box will be as wide as the old ones. + * All Lines that are above/under the Area, are inserted into + * the Box as Line + Box. + * All Lines that come before/after the Area, are inserted into + * the Boxes Left/Right. + * + * >> MERGE << + */ +static void lcl_CpyLines( sal_uInt16 nStt, sal_uInt16 nEnd, + SwTableLines& rLines, + SwTableBox* pInsBox, + sal_uInt16 nPos = USHRT_MAX ) +{ + for( sal_uInt16 n = nStt; n < nEnd; ++n ) + rLines[n]->SetUpper( pInsBox ); + if( USHRT_MAX == nPos ) + nPos = pInsBox->GetTabLines().size(); + pInsBox->GetTabLines().insert( pInsBox->GetTabLines().begin() + nPos, + rLines.begin() + nStt, rLines.begin() + nEnd ); + rLines.erase( rLines.begin() + nStt, rLines.begin() + nEnd ); +} + +static void lcl_CpyBoxes( sal_uInt16 nStt, sal_uInt16 nEnd, + SwTableBoxes& rBoxes, + SwTableLine* pInsLine ) +{ + for( sal_uInt16 n = nStt; n < nEnd; ++n ) + rBoxes[n]->SetUpper( pInsLine ); + sal_uInt16 nPos = pInsLine->GetTabBoxes().size(); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin() + nPos, + rBoxes.begin() + nStt, rBoxes.begin() + nEnd ); + rBoxes.erase( rBoxes.begin() + nStt, rBoxes.begin() + nEnd ); +} + +static void lcl_CalcWidth( SwTableBox* pBox ) +{ + // Assertion: Every Line in the Box is as large + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + OSL_ENSURE( pBox->GetTabLines().size(), "Box does not have any Lines" ); + + SwTableLine* pLine = pBox->GetTabLines()[0]; + OSL_ENSURE( pLine, "Box is not within a Line" ); + + long nWidth = 0; + for( auto pTabBox : pLine->GetTabBoxes() ) + nWidth += pTabBox->GetFrameFormat()->GetFrameSize().GetWidth(); + + pFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth, 0 )); + + // Boxes with Lines can only have Size/Fillorder + pFormat->ResetFormatAttr( RES_LR_SPACE, RES_FRMATR_END - 1 ); + pFormat->ResetFormatAttr( RES_BOXATR_BEGIN, RES_BOXATR_END - 1 ); +} + +namespace { + +struct InsULPara +{ + SwTableNode* pTableNd; + SwTableLine* pInsLine; + SwTableBox* pInsBox; + bool bUL_LR : 1; // Upper-Lower(true) or Left-Right(false) ? + bool bUL : 1; // Upper-Left(true) or Lower-Right(false) ? + + SwTableBox* pLeftBox; + + InsULPara( SwTableNode* pTNd, + SwTableBox* pLeft, + SwTableLine* pLine ) + : pTableNd( pTNd ), pInsLine( pLine ), pInsBox( nullptr ), + pLeftBox( pLeft ) + { bUL_LR = true; bUL = true; } + + void SetLeft( SwTableBox* pBox ) + { bUL_LR = false; bUL = true; if( pBox ) pInsBox = pBox; } + void SetRight( SwTableBox* pBox ) + { bUL_LR = false; bUL = false; if( pBox ) pInsBox = pBox; } + void SetLower( SwTableLine* pLine ) + { bUL_LR = true; bUL = false; if( pLine ) pInsLine = pLine; } +}; + +} + +static void lcl_Merge_MoveLine(FndLine_ & rFndLine, InsULPara *const pULPara); + +static void lcl_Merge_MoveBox(FndBox_ & rFndBox, InsULPara *const pULPara) +{ + SwTableBoxes* pBoxes; + + sal_uInt16 nStt = 0, nEnd = rFndBox.GetLines().size(); + sal_uInt16 nInsPos = USHRT_MAX; + if( !pULPara->bUL_LR ) // Left/Right + { + sal_uInt16 nPos; + SwTableBox* pFndTableBox = rFndBox.GetBox(); + pBoxes = &pFndTableBox->GetUpper()->GetTabBoxes(); + if( pULPara->bUL ) // Left ? + { + // if there are Boxes before it, move them + if( 0 != ( nPos = pFndTableBox->GetUpper()->GetBoxPos( pFndTableBox ) ) ) + lcl_CpyBoxes( 0, nPos, *pBoxes, pULPara->pInsLine ); + } + else // Right + { + // if there are Boxes behind it, move them + nPos = pFndTableBox->GetUpper()->GetBoxPos( pFndTableBox ); + if( nPos +1 < static_cast<sal_uInt16>(pBoxes->size()) ) + { + nInsPos = pULPara->pInsLine->GetTabBoxes().size(); + lcl_CpyBoxes( nPos+1, pBoxes->size(), + *pBoxes, pULPara->pInsLine ); + } + } + } + // Upper/Lower and still deeper? + else if (!rFndBox.GetLines().empty()) + { + // Only search the Line from which we need to move + nStt = pULPara->bUL ? 0 : rFndBox.GetLines().size()-1; + nEnd = nStt+1; + } + + pBoxes = &pULPara->pInsLine->GetTabBoxes(); + + // Is there still a level to step down to? + if (!rFndBox.GetBox()->GetTabLines().empty()) + { + SwTableBox* pBox = new SwTableBox( + static_cast<SwTableBoxFormat*>(rFndBox.GetBox()->GetFrameFormat()), + 0, pULPara->pInsLine ); + InsULPara aPara( *pULPara ); + aPara.pInsBox = pBox; + for (FndLines_t::iterator it = rFndBox.GetLines().begin() + nStt; + it != rFndBox.GetLines().begin() + nEnd; ++it ) + { + lcl_Merge_MoveLine(**it, &aPara); + } + if( !pBox->GetTabLines().empty() ) + { + if( USHRT_MAX == nInsPos ) + nInsPos = pBoxes->size(); + pBoxes->insert( pBoxes->begin() + nInsPos, pBox ); + lcl_CalcWidth( pBox ); // calculate the Box's width + } + else + delete pBox; + } +} + +static void lcl_Merge_MoveLine(FndLine_& rFndLine, InsULPara *const pULPara) +{ + SwTableLines* pLines; + + sal_uInt16 nStt = 0, nEnd = rFndLine.GetBoxes().size(); + sal_uInt16 nInsPos = USHRT_MAX; + if( pULPara->bUL_LR ) // UpperLower ? + { + sal_uInt16 nPos; + SwTableLine* pFndLn = rFndLine.GetLine(); + pLines = pFndLn->GetUpper() ? + &pFndLn->GetUpper()->GetTabLines() : + &pULPara->pTableNd->GetTable().GetTabLines(); + + SwTableBox* pLBx = rFndLine.GetBoxes().front()->GetBox(); + SwTableBox* pRBx = rFndLine.GetBoxes().back()->GetBox(); + sal_uInt16 nLeft = pFndLn->GetBoxPos( pLBx ); + sal_uInt16 nRight = pFndLn->GetBoxPos( pRBx ); + + if( !nLeft || nRight == pFndLn->GetTabBoxes().size() ) + { + if( pULPara->bUL ) // Upper ? + { + // If there are Lines before it, move them + nPos = pLines->GetPos( pFndLn ); + if( 0 != nPos ) + lcl_CpyLines( 0, nPos, *pLines, pULPara->pInsBox ); + } + else + // If there are Lines after it, move them + if( (nPos = pLines->GetPos( pFndLn )) + 1 < static_cast<sal_uInt16>(pLines->size()) ) + { + nInsPos = pULPara->pInsBox->GetTabLines().size(); + lcl_CpyLines( nPos+1, pLines->size(), *pLines, + pULPara->pInsBox ); + } + } + else + { + // There are still Boxes on the left side, so put the Left- + // and Merge-Box into one Box and Line, insert before/after + // a Line with a Box, into which the upper/lower Lines are + // inserted + SwTableLine* pInsLine = pULPara->pLeftBox->GetUpper(); + SwTableBox* pLMBox = new SwTableBox( + static_cast<SwTableBoxFormat*>(pULPara->pLeftBox->GetFrameFormat()), 0, pInsLine ); + SwTableLine* pLMLn = new SwTableLine( + static_cast<SwTableLineFormat*>(pInsLine->GetFrameFormat()), 2, pLMBox ); + pLMLn->ClaimFrameFormat()->ResetFormatAttr( RES_FRM_SIZE ); + + pLMBox->GetTabLines().insert( pLMBox->GetTabLines().begin(), pLMLn ); + + lcl_CpyBoxes( 0, 2, pInsLine->GetTabBoxes(), pLMLn ); + + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin(), pLMBox ); + + if( pULPara->bUL ) // Upper ? + { + // If there are Lines before it, move them + nPos = pLines->GetPos( pFndLn ); + if( 0 != nPos ) + lcl_CpyLines( 0, nPos, *pLines, pLMBox, 0 ); + } + else + // If there are Lines after it, move them + if( (nPos = pLines->GetPos( pFndLn )) + 1 < static_cast<sal_uInt16>(pLines->size()) ) + lcl_CpyLines( nPos+1, pLines->size(), *pLines, + pLMBox ); + lcl_CalcWidth( pLMBox ); // calculate the Box's width + } + } + // Left/Right + else + { + // Find only the Line from which we need to move + nStt = pULPara->bUL ? 0 : rFndLine.GetBoxes().size()-1; + nEnd = nStt+1; + } + pLines = &pULPara->pInsBox->GetTabLines(); + + SwTableLine* pNewLine = new SwTableLine( + static_cast<SwTableLineFormat*>(rFndLine.GetLine()->GetFrameFormat()), 0, pULPara->pInsBox ); + InsULPara aPara( *pULPara ); // copying + aPara.pInsLine = pNewLine; + FndBoxes_t & rLineBoxes = rFndLine.GetBoxes(); + for (FndBoxes_t::iterator it = rLineBoxes.begin() + nStt; + it != rLineBoxes.begin() + nEnd; ++it) + { + lcl_Merge_MoveBox(**it, &aPara); + } + + if( !pNewLine->GetTabBoxes().empty() ) + { + if( USHRT_MAX == nInsPos ) + nInsPos = pLines->size(); + pLines->insert( pLines->begin() + nInsPos, pNewLine ); + } + else + delete pNewLine; +} + +static void lcl_BoxSetHeadCondColl( const SwTableBox* pBox ); + +bool SwTable::OldMerge( SwDoc* pDoc, const SwSelBoxes& rBoxes, + SwTableBox* pMergeBox, SwUndoTableMerge* pUndo ) +{ + OSL_ENSURE( !rBoxes.empty() && pMergeBox, "no valid values" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // TL_CHART2: splitting/merging of a number of cells or rows will usually make + // the table too complex to be handled with chart. + // Thus we tell the charts to use their own data provider and forget about this table + pDoc->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( this ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + if( pUndo ) + pUndo->SetSelBoxes( rBoxes ); + + // Find Lines for the Layout update + aFndBox.SetTableLines( *this ); + aFndBox.DelFrames( *this ); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size() ) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes().front().get(); + } + + SwTableLine* pInsLine = new SwTableLine( + static_cast<SwTableLineFormat*>(pFndBox->GetLines().front()->GetLine()->GetFrameFormat()), 0, + !pFndBox->GetUpper() ? nullptr : pFndBox->GetBox() ); + pInsLine->ClaimFrameFormat()->ResetFormatAttr( RES_FRM_SIZE ); + + // Add the new Line + SwTableLines* pLines = pFndBox->GetUpper() ? + &pFndBox->GetBox()->GetTabLines() : &GetTabLines(); + + SwTableLine* pNewLine = pFndBox->GetLines().front()->GetLine(); + sal_uInt16 nInsPos = pLines->GetPos( pNewLine ); + pLines->insert( pLines->begin() + nInsPos, pInsLine ); + + SwTableBox* pLeftBox = new SwTableBox( static_cast<SwTableBoxFormat*>(pMergeBox->GetFrameFormat()), 0, pInsLine ); + SwTableBox* pRightBox = new SwTableBox( static_cast<SwTableBoxFormat*>(pMergeBox->GetFrameFormat()), 0, pInsLine ); + pMergeBox->SetUpper( pInsLine ); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin(), pLeftBox ); + pLeftBox->ClaimFrameFormat(); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin() + 1, pMergeBox); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin() + 2, pRightBox ); + pRightBox->ClaimFrameFormat(); + + // This contains all Lines that are above the selected Area, + // thus they form a Upper/Lower Line + InsULPara aPara( pTableNd, pLeftBox, pInsLine ); + + // Move the overlapping upper/lower Lines of the selected Area + for (auto & it : pFndBox->GetLines().front()->GetBoxes()) + { + lcl_Merge_MoveBox(*it, &aPara); + } + aPara.SetLower( pInsLine ); + const auto nEnd = pFndBox->GetLines().size()-1; + for (auto & it : pFndBox->GetLines()[nEnd]->GetBoxes()) + { + lcl_Merge_MoveBox(*it, &aPara); + } + + // Move the Boxes extending into the selected Area from left/right + aPara.SetLeft( pLeftBox ); + for (auto & rpFndLine : pFndBox->GetLines()) + { + lcl_Merge_MoveLine( *rpFndLine, &aPara ); + } + + aPara.SetRight( pRightBox ); + for (auto & rpFndLine : pFndBox->GetLines()) + { + lcl_Merge_MoveLine( *rpFndLine, &aPara ); + } + + if( pLeftBox->GetTabLines().empty() ) + DeleteBox_( *this, pLeftBox, nullptr, false, false ); + else + { + lcl_CalcWidth( pLeftBox ); // calculate the Box's width + if( pUndo && pLeftBox->GetSttNd() ) + pUndo->AddNewBox( pLeftBox->GetSttIdx() ); + } + if( pRightBox->GetTabLines().empty() ) + DeleteBox_( *this, pRightBox, nullptr, false, false ); + else + { + lcl_CalcWidth( pRightBox ); // calculate the Box's width + if( pUndo && pRightBox->GetSttNd() ) + pUndo->AddNewBox( pRightBox->GetSttIdx() ); + } + + DeleteSel( pDoc, rBoxes, nullptr, nullptr, false, false ); + + // Clean up this Line's structure once again, generally all of them + GCLines(); + + for( const auto& rpBox : GetTabLines()[0]->GetTabBoxes() ) + lcl_BoxSetHeadCondColl(rpBox); + + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH + CHECKTABLELAYOUT + + return true; +} + +static void lcl_CheckRowSpan( SwTable &rTable ) +{ + const long nLineCount = static_cast<long>(rTable.GetTabLines().size()); + long nMaxSpan = nLineCount; + long nMinSpan = 1; + while( nMaxSpan ) + { + SwTableLine* pLine = rTable.GetTabLines()[ nLineCount - nMaxSpan ]; + for( auto pBox : pLine->GetTabBoxes() ) + { + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan > nMaxSpan ) + pBox->setRowSpan( nMaxSpan ); + else if( nRowSpan < nMinSpan ) + pBox->setRowSpan( nMinSpan > 0 ? nMaxSpan : nMinSpan ); + } + --nMaxSpan; + nMinSpan = -nMaxSpan; + } +} + +static sal_uInt16 lcl_GetBoxOffset( const FndBox_& rBox ) +{ + // Find the first Box + const FndBox_* pFirstBox = &rBox; + while (!pFirstBox->GetLines().empty()) + { + pFirstBox = pFirstBox->GetLines().front()->GetBoxes().front().get(); + } + + sal_uInt16 nRet = 0; + // Calculate the position relative to above via the Lines + const SwTableBox* pBox = pFirstBox->GetBox(); + do { + const SwTableBoxes& rBoxes = pBox->GetUpper()->GetTabBoxes(); + for( auto pCmp : rBoxes ) + { + if (pBox==pCmp) + break; + nRet = nRet + static_cast<sal_uInt16>(pCmp->GetFrameFormat()->GetFrameSize().GetWidth()); + } + pBox = pBox->GetUpper()->GetUpper(); + } while( pBox ); + return nRet; +} + +static sal_uInt16 lcl_GetLineWidth( const FndLine_& rLine ) +{ + sal_uInt16 nRet = 0; + for( auto n = rLine.GetBoxes().size(); n; ) + { + nRet = nRet + static_cast<sal_uInt16>(rLine.GetBoxes()[--n]->GetBox() + ->GetFrameFormat()->GetFrameSize().GetWidth()); + } + return nRet; +} + +static void lcl_CalcNewWidths(const FndLines_t& rFndLines, CpyPara& rPara) +{ + rPara.pWidths.reset(); + const size_t nLineCount = rFndLines.size(); + if( nLineCount ) + { + rPara.pWidths = std::make_shared< std::vector< std::vector< sal_uLong > > > + ( nLineCount ); + // First we collect information about the left/right borders of all + // selected cells + for( size_t nLine = 0; nLine < nLineCount; ++nLine ) + { + std::vector< sal_uLong > &rWidth = (*rPara.pWidths)[ nLine ]; + const FndLine_ *pFndLine = rFndLines[ nLine ].get(); + if( pFndLine && !pFndLine->GetBoxes().empty() ) + { + const SwTableLine *pLine = pFndLine->GetLine(); + if( pLine && !pLine->GetTabBoxes().empty() ) + { + size_t nBoxCount = pLine->GetTabBoxes().size(); + sal_uLong nPos = 0; + // The first selected box... + const SwTableBox *const pSel = + pFndLine->GetBoxes().front()->GetBox(); + size_t nBox = 0; + // Sum up the width of all boxes before the first selected box + while( nBox < nBoxCount ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nBox++]; + if( pBox != pSel ) + nPos += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + else + break; + } + // nPos is now the left border of the first selected box + if( rPara.nMinLeft > nPos ) + rPara.nMinLeft = nPos; + nBoxCount = pFndLine->GetBoxes().size(); + rWidth = std::vector< sal_uLong >( nBoxCount+2 ); + rWidth[ 0 ] = nPos; + // Add now the widths of all selected boxes and store + // the positions in the vector + for( nBox = 0; nBox < nBoxCount; ) + { + nPos += pFndLine->GetBoxes()[nBox] + ->GetBox()->GetFrameFormat()->GetFrameSize().GetWidth(); + rWidth[ ++nBox ] = nPos; + } + // nPos: The right border of the last selected box + if( rPara.nMaxRight < nPos ) + rPara.nMaxRight = nPos; + if( nPos <= rWidth[ 0 ] ) + rWidth.clear(); + } + } + } + } + // Second step: calculate the new widths for the copied cells + sal_uLong nSelSize = rPara.nMaxRight - rPara.nMinLeft; + if( nSelSize ) + { + for( size_t nLine = 0; nLine < nLineCount; ++nLine ) + { + std::vector< sal_uLong > &rWidth = (*rPara.pWidths)[ nLine ]; + const size_t nCount = rWidth.size(); + if( nCount > 2 ) + { + rWidth[ nCount - 1 ] = rPara.nMaxRight; + sal_uLong nLastPos = 0; + for( size_t nBox = 0; nBox < nCount; ++nBox ) + { + sal_uInt64 nNextPos = rWidth[ nBox ]; + nNextPos -= rPara.nMinLeft; + nNextPos *= rPara.nNewSize; + nNextPos /= nSelSize; + rWidth[ nBox ] = static_cast<sal_uLong>(nNextPos - nLastPos); + nLastPos = static_cast<sal_uLong>(nNextPos); + } + } + } + } +} + +static void +lcl_CopyLineToDoc(FndLine_ const& rpFndLn, CpyPara *const pCpyPara); + +static void lcl_CopyBoxToDoc(FndBox_ const& rFndBox, CpyPara *const pCpyPara) +{ + // Calculation of new size + sal_uLong nRealSize; + sal_uLong nDummy1 = 0; + sal_uLong nDummy2 = 0; + if( pCpyPara->pTableNd->GetTable().IsNewModel() ) + { + if( pCpyPara->nBoxIdx == 1 ) + nDummy1 = (*pCpyPara->pWidths)[pCpyPara->nLnIdx][0]; + nRealSize = (*pCpyPara->pWidths)[pCpyPara->nLnIdx][pCpyPara->nBoxIdx++]; + if( pCpyPara->nBoxIdx == (*pCpyPara->pWidths)[pCpyPara->nLnIdx].size()-1 ) + nDummy2 = (*pCpyPara->pWidths)[pCpyPara->nLnIdx][pCpyPara->nBoxIdx]; + } + else + { + nRealSize = pCpyPara->nNewSize; + nRealSize *= rFndBox.GetBox()->GetFrameFormat()->GetFrameSize().GetWidth(); + if (pCpyPara->nOldSize == 0) + throw o3tl::divide_by_zero(); + nRealSize /= pCpyPara->nOldSize; + } + + sal_uLong nSize; + bool bDummy = nDummy1 > 0; + if( bDummy ) + nSize = nDummy1; + else + { + nSize = nRealSize; + nRealSize = 0; + } + do + { + // Find the Frame Format in the list of all Frame Formats + CpyTabFrame aFindFrame(static_cast<SwTableBoxFormat*>(rFndBox.GetBox()->GetFrameFormat())); + + std::shared_ptr<SwFormatFrameSize> aFrameSz(std::make_shared<SwFormatFrameSize>()); + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.lower_bound( aFindFrame ); + const CpyTabFrames::size_type nFndPos = itFind - pCpyPara->rTabFrameArr.begin(); + + // It *is* sometimes cool to have multiple tests/if's and assignments + // in a single statement, and it is technically possible. But it is definitely + // not simply readable - where from my POV reading code is done 1000 times + // more often than writing it. Thus I dismantled the expression in smaller + // chunks to keep it handy/understandable/changeable (hopefully without error) + // The original for reference: + // if( itFind == pCpyPara->rTabFrameArr.end() || !(*itFind == aFindFrame) || + // ( aFrameSz = ( aFindFrame = pCpyPara->rTabFrameArr[ nFndPos ]).pNewFrameFormat-> + // GetFrameSize()).GetWidth() != static_cast<SwTwips>(nSize) ) + + bool DoCopyIt(itFind == pCpyPara->rTabFrameArr.end()); + + if(!DoCopyIt) + { + DoCopyIt = !(*itFind == aFindFrame); + } + + if(!DoCopyIt) + { + aFindFrame = pCpyPara->rTabFrameArr[ nFndPos ]; + aFrameSz.reset(aFindFrame.pNewFrameFormat->GetFrameSize().Clone()); + DoCopyIt = aFrameSz->GetWidth() != static_cast<SwTwips>(nSize); + } + + if(DoCopyIt) + { + // It doesn't exist yet, so copy it + aFindFrame.pNewFrameFormat = pCpyPara->pDoc->MakeTableBoxFormat(); + aFindFrame.pNewFrameFormat->CopyAttrs( *rFndBox.GetBox()->GetFrameFormat() ); + if( !pCpyPara->bCpyContent ) + aFindFrame.pNewFrameFormat->ResetFormatAttr( RES_BOXATR_FORMULA, RES_BOXATR_VALUE ); + aFrameSz->SetWidth( nSize ); + aFindFrame.pNewFrameFormat->SetFormatAttr( *aFrameSz ); + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + + SwTableBox* pBox; + if (!rFndBox.GetLines().empty()) + { + pBox = new SwTableBox( aFindFrame.pNewFrameFormat, + rFndBox.GetLines().size(), pCpyPara->pInsLine ); + pCpyPara->pInsLine->GetTabBoxes().insert( pCpyPara->pInsLine->GetTabBoxes().begin() + pCpyPara->nInsPos++, pBox ); + CpyPara aPara( *pCpyPara, pBox ); + aPara.nNewSize = nSize; // get the size + for (auto const& rpFndLine : rFndBox.GetLines()) + { + lcl_CopyLineToDoc( *rpFndLine, &aPara ); + } + } + else + { + // Create an empty Box + pCpyPara->pDoc->GetNodes().InsBoxen( pCpyPara->pTableNd, pCpyPara->pInsLine, + aFindFrame.pNewFrameFormat, + pCpyPara->pDoc->GetDfltTextFormatColl(), + nullptr, pCpyPara->nInsPos ); + pBox = pCpyPara->pInsLine->GetTabBoxes()[ pCpyPara->nInsPos ]; + if( bDummy ) + pBox->setDummyFlag( true ); + else if( pCpyPara->bCpyContent ) + { + // Copy the content into this empty Box + pBox->setRowSpan(rFndBox.GetBox()->getRowSpan()); + + // We can also copy formulas and values, if we copy the content + { + SfxItemSet aBoxAttrSet( pCpyPara->pDoc->GetAttrPool(), + svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{} ); + aBoxAttrSet.Put(rFndBox.GetBox()->GetFrameFormat()->GetAttrSet()); + if( aBoxAttrSet.Count() ) + { + const SfxPoolItem* pItem; + SvNumberFormatter* pN = pCpyPara->pDoc->GetNumberFormatter( false ); + if( pN && pN->HasMergeFormatTable() && SfxItemState::SET == aBoxAttrSet. + GetItemState( RES_BOXATR_FORMAT, false, &pItem ) ) + { + sal_uLong nOldIdx = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + sal_uLong nNewIdx = pN->GetMergeFormatIndex( nOldIdx ); + if( nNewIdx != nOldIdx ) + aBoxAttrSet.Put( SwTableBoxNumFormat( nNewIdx )); + } + pBox->ClaimFrameFormat()->SetFormatAttr( aBoxAttrSet ); + } + } + SwDoc* pFromDoc = rFndBox.GetBox()->GetFrameFormat()->GetDoc(); + SwNodeRange aCpyRg( *rFndBox.GetBox()->GetSttNd(), 1, + *rFndBox.GetBox()->GetSttNd()->EndOfSectionNode() ); + SwNodeIndex aInsIdx( *pBox->GetSttNd(), 1 ); + + pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aCpyRg, aInsIdx, nullptr, false); + // Delete the initial TextNode + pCpyPara->pDoc->GetNodes().Delete( aInsIdx ); + } + ++pCpyPara->nInsPos; + } + if( nRealSize ) + { + bDummy = false; + nSize = nRealSize; + nRealSize = 0; + } + else + { + bDummy = true; + nSize = nDummy2; + nDummy2 = 0; + } + } + while( nSize ); +} + +static void +lcl_CopyLineToDoc(const FndLine_& rFndLine, CpyPara *const pCpyPara) +{ + // Find the Frame Format in the list of all Frame Formats + CpyTabFrame aFindFrame( rFndLine.GetLine()->GetFrameFormat() ); + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.find( aFindFrame ); + if( itFind == pCpyPara->rTabFrameArr.end() ) + { + // It doesn't exist yet, so copy it + aFindFrame.pNewFrameFormat = reinterpret_cast<SwTableBoxFormat*>(pCpyPara->pDoc->MakeTableLineFormat()); + aFindFrame.pNewFrameFormat->CopyAttrs( *rFndLine.GetLine()->GetFrameFormat() ); + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + else + aFindFrame = *itFind; + + SwTableLine* pNewLine = new SwTableLine( reinterpret_cast<SwTableLineFormat*>(aFindFrame.pNewFrameFormat), + rFndLine.GetBoxes().size(), pCpyPara->pInsBox ); + if( pCpyPara->pInsBox ) + { + SwTableLines& rLines = pCpyPara->pInsBox->GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine ); + } + else + { + SwTableLines& rLines = pCpyPara->pTableNd->GetTable().GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine); + } + + CpyPara aPara( *pCpyPara, pNewLine ); + + if( pCpyPara->pTableNd->GetTable().IsNewModel() ) + { + aPara.nOldSize = 0; // will not be used + aPara.nBoxIdx = 1; + } + else if( rFndLine.GetBoxes().size() == + rFndLine.GetLine()->GetTabBoxes().size() ) + { + // Get the Parent's size + const SwFrameFormat* pFormat; + + if( rFndLine.GetLine()->GetUpper() ) + pFormat = rFndLine.GetLine()->GetUpper()->GetFrameFormat(); + else + pFormat = pCpyPara->pTableNd->GetTable().GetFrameFormat(); + aPara.nOldSize = pFormat->GetFrameSize().GetWidth(); + } + else + // Calculate it + for (auto &rpBox : rFndLine.GetBoxes()) + { + aPara.nOldSize += rpBox->GetBox()->GetFrameFormat()->GetFrameSize().GetWidth(); + } + + const FndBoxes_t& rBoxes = rFndLine.GetBoxes(); + for (auto const& it : rBoxes) + { + lcl_CopyBoxToDoc(*it, &aPara); + } + if( pCpyPara->pTableNd->GetTable().IsNewModel() ) + ++pCpyPara->nLnIdx; +} + +void SwTable::CopyHeadlineIntoTable( SwTableNode& rTableNd ) +{ + // Find all Boxes/Lines + SwSelBoxes aSelBoxes; + SwTableBox* pBox = GetTabSortBoxes()[ 0 ]; + pBox = GetTableBox( pBox->GetSttNd()->StartOfSectionNode()->GetIndex() + 1 ); + SelLineFromBox( pBox, aSelBoxes ); + + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( aSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return; + + { + // Convert Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( this ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + GetFrameFormat()->GetDoc()->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + CpyTabFrames aCpyFormat; + CpyPara aPara( &rTableNd, 1, aCpyFormat ); + aPara.nNewSize = aPara.nOldSize = rTableNd.GetTable().GetFrameFormat()->GetFrameSize().GetWidth(); + // Copy + if( IsNewModel() ) + lcl_CalcNewWidths( aFndBox.GetLines(), aPara ); + for (const auto & rpFndLine : aFndBox.GetLines()) + { + lcl_CopyLineToDoc( *rpFndLine, &aPara ); + } + if( rTableNd.GetTable().IsNewModel() ) + { // The copied line must not contain any row span attributes > 1 + SwTableLine* pLine = rTableNd.GetTable().GetTabLines()[0]; + OSL_ENSURE( !pLine->GetTabBoxes().empty(), "Empty Table Line" ); + for( auto pTableBox : pLine->GetTabBoxes() ) + { + OSL_ENSURE( pTableBox, "Missing Table Box" ); + pTableBox->setRowSpan( 1 ); + } + } +} + +bool SwTable::MakeCopy( SwDoc* pInsDoc, const SwPosition& rPos, + const SwSelBoxes& rSelBoxes, + bool bCpyName ) const +{ + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( const_cast<SwTableLines&>(GetTabLines()), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // First copy the PoolTemplates for the Table, so that the Tables are + // actually copied and have valid values. + SwDoc* pSrcDoc = GetFrameFormat()->GetDoc(); + if( pSrcDoc != pInsDoc ) + { + pInsDoc->CopyTextColl( *pSrcDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ) ); + pInsDoc->CopyTextColl( *pSrcDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN ) ); + } + + SwTable* pNewTable = const_cast<SwTable*>(pInsDoc->InsertTable( + SwInsertTableOptions( SwInsertTableFlags::HeadlineNoBorder, 1 ), + rPos, 1, 1, GetFrameFormat()->GetHoriOrient().GetHoriOrient(), + nullptr, nullptr, false, IsNewModel() )); + if( !pNewTable ) + return false; + + SwNodeIndex aIdx( rPos.nNode, -1 ); + SwTableNode* pTableNd = aIdx.GetNode().FindTableNode(); + ++aIdx; + OSL_ENSURE( pTableNd, "Where is the TableNode now?" ); + + pTableNd->GetTable().SetRowsToRepeat( GetRowsToRepeat() ); + + pNewTable->SetTableStyleName(pTableNd->GetTable().GetTableStyleName()); + + if( auto pSwDDETable = dynamic_cast<const SwDDETable*>(this) ) + { + // A DDE-Table is being copied + // Does the new Document actually have it's FieldType? + SwFieldType* pFieldType = pInsDoc->getIDocumentFieldsAccess().InsertFieldType( + *pSwDDETable->GetDDEFieldType() ); + OSL_ENSURE( pFieldType, "unknown FieldType" ); + + // Change the Table Pointer at the Node + pNewTable = new SwDDETable( *pNewTable, + static_cast<SwDDEFieldType*>(pFieldType) ); + pTableNd->SetNewTable( std::unique_ptr<SwTable>(pNewTable), false ); + } + + pNewTable->GetFrameFormat()->CopyAttrs( *GetFrameFormat() ); + pNewTable->SetTableChgMode( GetTableChgMode() ); + + // Destroy the already created Frames + pTableNd->DelFrames(); + + { + // Convert the Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( this ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pSrcDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + SwTableNumFormatMerge aTNFM( *pSrcDoc, *pInsDoc ); + + // Also copy Names or enforce a new unique one + if( bCpyName ) + pNewTable->GetFrameFormat()->SetName( GetFrameFormat()->GetName() ); + + CpyTabFrames aCpyFormat; + CpyPara aPara( pTableNd, 1, aCpyFormat ); + aPara.nNewSize = aPara.nOldSize = GetFrameFormat()->GetFrameSize().GetWidth(); + + if( IsNewModel() ) + lcl_CalcNewWidths( aFndBox.GetLines(), aPara ); + // Copy + for (const auto & rpFndLine : aFndBox.GetLines()) + { + lcl_CopyLineToDoc( *rpFndLine, &aPara ); + } + + // Set the "right" margin above/below + { + FndLine_* pFndLn = aFndBox.GetLines().front().get(); + SwTableLine* pLn = pFndLn->GetLine(); + const SwTableLine* pTmp = pLn; + sal_uInt16 nLnPos = GetTabLines().GetPos( pTmp ); + if( USHRT_MAX != nLnPos && nLnPos ) + { + // There is a Line before it + SwCollectTableLineBoxes aLnPara( false, SplitTable_HeadlineOption::BorderCopy ); + + pLn = GetTabLines()[ nLnPos - 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox( rpBox, &aLnPara ); + + if( aLnPara.Resize( lcl_GetBoxOffset( aFndBox ), + lcl_GetLineWidth( *pFndLn )) ) + { + aLnPara.SetValues( true ); + pLn = pNewTable->GetTabLines()[ 0 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aLnPara ); + } + } + + pFndLn = aFndBox.GetLines().back().get(); + pLn = pFndLn->GetLine(); + pTmp = pLn; + nLnPos = GetTabLines().GetPos( pTmp ); + if( nLnPos < GetTabLines().size() - 1 ) + { + // There is a Line following it + SwCollectTableLineBoxes aLnPara( true, SplitTable_HeadlineOption::BorderCopy ); + + pLn = GetTabLines()[ nLnPos + 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox( rpBox, &aLnPara ); + + if( aLnPara.Resize( lcl_GetBoxOffset( aFndBox ), + lcl_GetLineWidth( *pFndLn )) ) + { + aLnPara.SetValues( false ); + pLn = pNewTable->GetTabLines().back(); + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aLnPara ); + } + } + } + + // We need to delete the initial Box + DeleteBox_( *pNewTable, pNewTable->GetTabLines().back()->GetTabBoxes()[0], + nullptr, false, false ); + + if( pNewTable->IsNewModel() ) + lcl_CheckRowSpan( *pNewTable ); + // Clean up + pNewTable->GCLines(); + + pTableNd->MakeOwnFrames( &aIdx ); // re-generate the Frames + + CHECKTABLELAYOUT + + return true; +} + +// Find the next Box with content from this Line +SwTableBox* SwTableLine::FindNextBox( const SwTable& rTable, + const SwTableBox* pSrchBox, bool bOvrTableLns ) const +{ + const SwTableLine* pLine = this; // for M800 + SwTableBox* pBox; + sal_uInt16 nFndPos; + if( !GetTabBoxes().empty() && pSrchBox ) + { + nFndPos = GetBoxPos( pSrchBox ); + if( USHRT_MAX != nFndPos && + nFndPos + 1 != static_cast<sal_uInt16>(GetTabBoxes().size()) ) + { + pBox = GetTabBoxes()[ nFndPos + 1 ]; + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes()[0]; + return pBox; + } + } + + if( GetUpper() ) + { + nFndPos = GetUpper()->GetTabLines().GetPos( pLine ); + OSL_ENSURE( USHRT_MAX != nFndPos, "Line is not in the Table" ); + // Is there another Line? + if( nFndPos+1 >= static_cast<sal_uInt16>(GetUpper()->GetTabLines().size()) ) + return GetUpper()->GetUpper()->FindNextBox( rTable, GetUpper(), bOvrTableLns ); + pLine = GetUpper()->GetTabLines()[nFndPos+1]; + } + else if( bOvrTableLns ) // Over a Table's the "BaseLines"?? + { + // Search for the next Line in the Table + nFndPos = rTable.GetTabLines().GetPos( pLine ); + if( nFndPos + 1 >= static_cast<sal_uInt16>(rTable.GetTabLines().size()) ) + return nullptr; // there are no more Boxes + + pLine = rTable.GetTabLines()[ nFndPos+1 ]; + } + else + return nullptr; + + if( !pLine->GetTabBoxes().empty() ) + { + pBox = pLine->GetTabBoxes().front(); + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + return pBox; + } + return pLine->FindNextBox( rTable, nullptr, bOvrTableLns ); +} + +// Find the previous Box from this Line +SwTableBox* SwTableLine::FindPreviousBox( const SwTable& rTable, + const SwTableBox* pSrchBox, bool bOvrTableLns ) const +{ + const SwTableLine* pLine = this; // for M800 + SwTableBox* pBox; + sal_uInt16 nFndPos; + if( !GetTabBoxes().empty() && pSrchBox ) + { + nFndPos = GetBoxPos( pSrchBox ); + if( USHRT_MAX != nFndPos && nFndPos ) + { + pBox = GetTabBoxes()[ nFndPos - 1 ]; + while( !pBox->GetTabLines().empty() ) + { + pLine = pBox->GetTabLines().back(); + pBox = pLine->GetTabBoxes().back(); + } + return pBox; + } + } + + if( GetUpper() ) + { + nFndPos = GetUpper()->GetTabLines().GetPos( pLine ); + OSL_ENSURE( USHRT_MAX != nFndPos, "Line is not in the Table" ); + // Is there another Line? + if( !nFndPos ) + return GetUpper()->GetUpper()->FindPreviousBox( rTable, GetUpper(), bOvrTableLns ); + pLine = GetUpper()->GetTabLines()[nFndPos-1]; + } + else if( bOvrTableLns ) // Over a Table's the "BaseLines"?? + { + // Search for the next Line in the Table + nFndPos = rTable.GetTabLines().GetPos( pLine ); + if( !nFndPos ) + return nullptr; // there are no more Boxes + + pLine = rTable.GetTabLines()[ nFndPos-1 ]; + } + else + return nullptr; + + if( !pLine->GetTabBoxes().empty() ) + { + pBox = pLine->GetTabBoxes().back(); + while( !pBox->GetTabLines().empty() ) + { + pLine = pBox->GetTabLines().back(); + pBox = pLine->GetTabBoxes().back(); + } + return pBox; + } + return pLine->FindPreviousBox( rTable, nullptr, bOvrTableLns ); +} + +// Find the next Box with content from this Line +SwTableBox* SwTableBox::FindNextBox( const SwTable& rTable, + const SwTableBox* pSrchBox, bool bOvrTableLns ) const +{ + if( !pSrchBox && GetTabLines().empty() ) + return const_cast<SwTableBox*>(this); + return GetUpper()->FindNextBox( rTable, pSrchBox ? pSrchBox : this, + bOvrTableLns ); + +} + +// Find the next Box with content from this Line +SwTableBox* SwTableBox::FindPreviousBox( const SwTable& rTable, + const SwTableBox* pSrchBox ) const +{ + if( !pSrchBox && GetTabLines().empty() ) + return const_cast<SwTableBox*>(this); + return GetUpper()->FindPreviousBox( rTable, pSrchBox ? pSrchBox : this ); +} + +static void lcl_BoxSetHeadCondColl( const SwTableBox* pBox ) +{ + // We need to adapt the paragraphs with conditional templates in the HeadLine + const SwStartNode* pSttNd = pBox->GetSttNd(); + if( pSttNd ) + pSttNd->CheckSectionCondColl(); + else + for( const SwTableLine* pLine : pBox->GetTabLines() ) + sw_LineSetHeadCondColl( pLine ); +} + +void sw_LineSetHeadCondColl( const SwTableLine* pLine ) +{ + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + lcl_BoxSetHeadCondColl(pBox); +} + +static SwTwips lcl_GetDistance( SwTableBox* pBox, bool bLeft ) +{ + bool bFirst = true; + SwTwips nRet = 0; + SwTableLine* pLine; + while( pBox ) + { + pLine = pBox->GetUpper(); + if( !pLine ) + break; + sal_uInt16 nStt = 0, nPos = pLine->GetBoxPos( pBox ); + + if( bFirst && !bLeft ) + ++nPos; + bFirst = false; + + while( nStt < nPos ) + nRet += pLine->GetTabBoxes()[ nStt++ ]->GetFrameFormat() + ->GetFrameSize().GetWidth(); + pBox = pLine->GetUpper(); + } + return nRet; +} + +static bool lcl_SetSelBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ) +{ + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( auto pBox : rBoxes ) + { + SwFrameFormat* pFormat = pBox->GetFrameFormat(); + const SwFormatFrameSize& rSz = pFormat->GetFrameSize(); + SwTwips nWidth = rSz.GetWidth(); + bool bGreaterBox = false; + + if( bCheck ) + { + for( auto pLn : pBox->GetTabLines() ) + if( !::lcl_SetSelBoxWidth( pLn, rParam, nDist, true )) + return false; + + // Collect all "ContentBoxes" + bGreaterBox = (TableChgMode::FixedWidthChangeAbs != rParam.nMode) + && ((nDist + (rParam.bLeft ? 0 : nWidth)) >= rParam.nSide); + if (bGreaterBox + || (!rParam.bBigger + && (std::abs(nDist + ((rParam.nMode != TableChgMode::FixedWidthChangeAbs && rParam.bLeft) ? 0 : nWidth) - rParam.nSide) < COLFUZZY))) + { + SwTwips nLowerDiff; + if( bGreaterBox && TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + // The "other Boxes" have been adapted, so change by this value + nLowerDiff = (nDist + ( rParam.bLeft ? 0 : nWidth ) ) - rParam.nSide; + nLowerDiff *= rParam.nDiff; + nLowerDiff /= rParam.nMaxSize; + nLowerDiff = rParam.nDiff - nLowerDiff; + } + else + nLowerDiff = rParam.nDiff; + + if( nWidth < nLowerDiff || nWidth - nLowerDiff < MINLAY ) + return false; + } + } + else + { + SwTwips nLowerDiff = 0, nOldLower = rParam.nLowerDiff; + for( auto pLn : pBox->GetTabLines() ) + { + rParam.nLowerDiff = 0; + lcl_SetSelBoxWidth( pLn, rParam, nDist, false ); + + if( nLowerDiff < rParam.nLowerDiff ) + nLowerDiff = rParam.nLowerDiff; + } + rParam.nLowerDiff = nOldLower; + + if( nLowerDiff || + (bGreaterBox = !nOldLower && TableChgMode::FixedWidthChangeAbs != rParam.nMode && + ( nDist + ( rParam.bLeft ? 0 : nWidth ) ) >= rParam.nSide) || + ( std::abs( nDist + ( (rParam.nMode != TableChgMode::FixedWidthChangeAbs && rParam.bLeft) ? 0 : nWidth ) + - rParam.nSide ) < COLFUZZY )) + { + // This column contains the Cursor - so decrease/increase + SwFormatFrameSize aNew( rSz ); + + if( !nLowerDiff ) + { + if( bGreaterBox && TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + // The "other Boxes" have been adapted, so change by this value + nLowerDiff = (nDist + ( rParam.bLeft ? 0 : nWidth ) ) - rParam.nSide; + nLowerDiff *= rParam.nDiff; + nLowerDiff /= rParam.nMaxSize; + nLowerDiff = rParam.nDiff - nLowerDiff; + } + else + nLowerDiff = rParam.nDiff; + } + + rParam.nLowerDiff += nLowerDiff; + + if( rParam.bBigger ) + aNew.SetWidth( nWidth + nLowerDiff ); + else + aNew.SetWidth( nWidth - nLowerDiff ); + rParam.aShareFormats.SetSize( *pBox, aNew ); + break; + } + } + + if( rParam.bLeft && rParam.nMode != TableChgMode::FixedWidthChangeAbs && nDist >= rParam.nSide ) + break; + + nDist += nWidth; + + // If it gets bigger, then that's it + if( ( TableChgMode::FixedWidthChangeAbs == rParam.nMode || !rParam.bLeft ) && + nDist >= rParam.nSide ) + break; + } + return true; +} + +static bool lcl_SetOtherBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ) +{ + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( auto pBox : rBoxes ) + { + SwFrameFormat* pFormat = pBox->GetFrameFormat(); + const SwFormatFrameSize& rSz = pFormat->GetFrameSize(); + SwTwips nWidth = rSz.GetWidth(); + + if( bCheck ) + { + for( auto pLn : pBox->GetTabLines() ) + if( !::lcl_SetOtherBoxWidth( pLn, rParam, nDist, true )) + return false; + + if( rParam.bBigger && ( TableChgMode::FixedWidthChangeAbs == rParam.nMode + ? std::abs( nDist - rParam.nSide ) < COLFUZZY + : ( rParam.bLeft ? nDist < rParam.nSide - COLFUZZY + : nDist >= rParam.nSide - COLFUZZY )) ) + { + SwTwips nDiff; + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) // Table fixed, proportional + { + // calculate relative + nDiff = nWidth; + nDiff *= rParam.nDiff; + nDiff /= rParam.nMaxSize; + } + else + nDiff = rParam.nDiff; + + if( nWidth < nDiff || nWidth - nDiff < MINLAY ) + return false; + } + } + else + { + SwTwips nLowerDiff = 0, nOldLower = rParam.nLowerDiff; + for( auto pLn : pBox->GetTabLines() ) + { + rParam.nLowerDiff = 0; + lcl_SetOtherBoxWidth( pLn, rParam, nDist, false ); + + if( nLowerDiff < rParam.nLowerDiff ) + nLowerDiff = rParam.nLowerDiff; + } + rParam.nLowerDiff = nOldLower; + + if( nLowerDiff || + ( TableChgMode::FixedWidthChangeAbs == rParam.nMode + ? std::abs( nDist - rParam.nSide ) < COLFUZZY + : ( rParam.bLeft ? nDist < rParam.nSide - COLFUZZY + : nDist >= rParam.nSide - COLFUZZY) + ) ) + { + SwFormatFrameSize aNew( rSz ); + + if( !nLowerDiff ) + { + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) // Table fixed, proportional + { + // calculate relative + nLowerDiff = nWidth; + nLowerDiff *= rParam.nDiff; + nLowerDiff /= rParam.nMaxSize; + } + else + nLowerDiff = rParam.nDiff; + } + + rParam.nLowerDiff += nLowerDiff; + + if( rParam.bBigger ) + aNew.SetWidth( nWidth - nLowerDiff ); + else + aNew.SetWidth( nWidth + nLowerDiff ); + + rParam.aShareFormats.SetSize( *pBox, aNew ); + } + } + + nDist += nWidth; + if( ( TableChgMode::FixedWidthChangeAbs == rParam.nMode || rParam.bLeft ) && + nDist > rParam.nSide ) + break; + } + return true; +} + +static void lcl_AjustLines( SwTableLine* pLine, CR_SetBoxWidth& rParam ) +{ + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( auto pBox : rBoxes ) + { + SwFormatFrameSize aSz( pBox->GetFrameFormat()->GetFrameSize() ); + SwTwips nWidth = aSz.GetWidth(); + nWidth *= rParam.nDiff; + nWidth /= rParam.nMaxSize; + aSz.SetWidth( nWidth ); + rParam.aShareFormats.SetSize( *pBox, aSz ); + + for( auto pLn : pBox->GetTabLines() ) + ::lcl_AjustLines( pLn, rParam ); + } +} + +#ifdef DBG_UTIL +void CheckBoxWidth( const SwTableLine& rLine, SwTwips nSize ) +{ + const SwTableBoxes& rBoxes = rLine.GetTabBoxes(); + + SwTwips nCurrentSize = 0; + // See if the tables have a correct width + for (const SwTableBox* pBox : rBoxes) + { + const SwTwips nBoxW = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nCurrentSize += nBoxW; + + for( auto pLn : pBox->GetTabLines() ) + CheckBoxWidth( *pLn, nBoxW ); + } + + if (sal::static_int_cast< unsigned long >(std::abs(nCurrentSize - nSize)) > + (COLFUZZY * rBoxes.size())) + { + OSL_FAIL( "Line's Boxes are too small or too large" ); + } +} +#endif + +bool SwTable::SetColWidth( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff, std::unique_ptr<SwUndo>* ppUndo ) +{ + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + const SwFormatFrameSize& rSz = GetFrameFormat()->GetFrameSize(); + const SvxLRSpaceItem& rLR = GetFrameFormat()->GetLRSpace(); + + std::unique_ptr<FndBox_> xFndBox; // for insertion/deletion + bool bBigger, + bRet = false, + bLeft = TableChgWidthHeightType::ColLeft == extractPosition( eType ) || + TableChgWidthHeightType::CellLeft == extractPosition( eType ); + + // Get the current Box's edge + // Only needed for manipulating the width + const SwTwips nDist = ::lcl_GetDistance( &rCurrentBox, bLeft ); + SwTwips nDistStt = 0; + CR_SetBoxWidth aParam( eType, nRelDiff, nDist, + bLeft ? nDist : rSz.GetWidth() - nDist, + const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()) ); + bBigger = aParam.bBigger; + + FN_lcl_SetBoxWidth fnSelBox, fnOtherBox; + fnSelBox = lcl_SetSelBoxWidth; + fnOtherBox = lcl_SetOtherBoxWidth; + + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::ColRight: + case TableChgWidthHeightType::ColLeft: + if( TableChgMode::VarWidthChangeAbs == m_eTableChgMode ) + { + // First test if we have room at all + bool bChgLRSpace = true; + if( bBigger ) + { + if( GetFrameFormat()->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE) && + !rSz.GetWidthPercent() ) + { + // silence -Wsign-compare on Android with the static cast + bRet = rSz.GetWidth() < static_cast<unsigned short>(USHRT_MAX) - nRelDiff; + bChgLRSpace = bLeft ? rLR.GetLeft() >= nAbsDiff + : rLR.GetRight() >= nAbsDiff; + } + else + bRet = bLeft ? rLR.GetLeft() >= nAbsDiff + : rLR.GetRight() >= nAbsDiff; + + if( !bRet ) + { + // Then call itself recursively; only with another mode (proportional) + TableChgMode eOld = m_eTableChgMode; + m_eTableChgMode = TableChgMode::FixedWidthChangeProp; + + bRet = SetColWidth( rCurrentBox, eType, nAbsDiff, nRelDiff, + ppUndo ); + m_eTableChgMode = eOld; + return bRet; + } + } + else + { + bRet = true; + for( auto const & n: m_aLines ) + { + aParam.LoopClear(); + if( !(*fnSelBox)( n, aParam, nDistStt, true )) + { + bRet = false; + break; + } + } + } + + if( bRet ) + { + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + long nFrameWidth = LONG_MAX; + LockModify(); + SwFormatFrameSize aSz( rSz ); + SvxLRSpaceItem aLR( rLR ); + if( bBigger ) + { + // If the Table does not have any room to grow, we need to create some! + // silence -Wsign-compare on Android with the static cast + if( aSz.GetWidth() + nRelDiff > static_cast<unsigned short>(USHRT_MAX) ) + { + // Break down to USHRT_MAX / 2 + CR_SetBoxWidth aTmpPara( TableChgWidthHeightType::ColLeft, aSz.GetWidth() / 2, + 0, aSz.GetWidth(), aParam.pTableNd ); + for( size_t nLn = 0; nLn < m_aLines.size(); ++nLn ) + ::lcl_AjustLines( m_aLines[ nLn ], aTmpPara ); + aSz.SetWidth( aSz.GetWidth() / 2 ); + aParam.nDiff = nRelDiff /= 2; + aParam.nSide /= 2; + aParam.nMaxSize /= 2; + } + + if( bLeft ) + aLR.SetLeft( sal_uInt16( aLR.GetLeft() - nAbsDiff ) ); + else + aLR.SetRight( sal_uInt16( aLR.GetRight() - nAbsDiff ) ); + } + else if( bLeft ) + aLR.SetLeft( sal_uInt16( aLR.GetLeft() + nAbsDiff ) ); + else + aLR.SetRight( sal_uInt16( aLR.GetRight() + nAbsDiff ) ); + + if( bChgLRSpace ) + GetFrameFormat()->SetFormatAttr( aLR ); + const SwFormatHoriOrient& rHOri = GetFrameFormat()->GetHoriOrient(); + if( text::HoriOrientation::FULL == rHOri.GetHoriOrient() || + (text::HoriOrientation::LEFT == rHOri.GetHoriOrient() && aLR.GetLeft()) || + (text::HoriOrientation::RIGHT == rHOri.GetHoriOrient() && aLR.GetRight())) + { + SwFormatHoriOrient aHOri( rHOri ); + aHOri.SetHoriOrient( text::HoriOrientation::NONE ); + GetFrameFormat()->SetFormatAttr( aHOri ); + + // If the Table happens to contain relative values (USHORT_MAX), + // we need to convert them to absolute ones now. + // Bug 61494 + if( GetFrameFormat()->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE) && + !rSz.GetWidthPercent() ) + { + SwTabFrame* pTabFrame = SwIterator<SwTabFrame,SwFormat>( *GetFrameFormat() ).First(); + if( pTabFrame && + pTabFrame->getFramePrintArea().Width() != rSz.GetWidth() ) + { + nFrameWidth = pTabFrame->getFramePrintArea().Width(); + if( bBigger ) + nFrameWidth += nAbsDiff; + else + nFrameWidth -= nAbsDiff; + } + } + } + + if( bBigger ) + aSz.SetWidth( aSz.GetWidth() + nRelDiff ); + else + aSz.SetWidth( aSz.GetWidth() - nRelDiff ); + + if( rSz.GetWidthPercent() ) + aSz.SetWidthPercent( static_cast<sal_uInt8>(( aSz.GetWidth() * 100 ) / + ( aSz.GetWidth() + aLR.GetRight() + aLR.GetLeft()))); + + GetFrameFormat()->SetFormatAttr( aSz ); + + UnlockModify(); + + for( sal_uInt16 n = m_aLines.size(); n; ) + { + --n; + aParam.LoopClear(); + (*fnSelBox)( m_aLines[ n ], aParam, nDistStt, false ); + } + + // If the Table happens to contain relative values (USHORT_MAX), + // we need to convert them to absolute ones now. + // Bug 61494 + if( LONG_MAX != nFrameWidth ) + { + SwFormatFrameSize aAbsSz( aSz ); + aAbsSz.SetWidth( nFrameWidth ); + GetFrameFormat()->SetFormatAttr( aAbsSz ); + } + } + } + else if( bLeft ? nDist != 0 : std::abs( rSz.GetWidth() - nDist ) > COLFUZZY ) + { + bRet = true; + if( bLeft && TableChgMode::FixedWidthChangeAbs == m_eTableChgMode ) + aParam.bBigger = !bBigger; + + // First test if we have room at all + if( aParam.bBigger ) + { + for( auto const & n: m_aLines ) + { + aParam.LoopClear(); + if( !(*fnOtherBox)( n, aParam, 0, true )) + { + bRet = false; + break; + } + } + } + else + { + for( auto const & n: m_aLines ) + { + aParam.LoopClear(); + if( !(*fnSelBox)( n, aParam, nDistStt, true )) + { + bRet = false; + break; + } + } + } + + // If true, set it + if( bRet ) + { + CR_SetBoxWidth aParam1( aParam ); + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + if( TableChgMode::FixedWidthChangeAbs != m_eTableChgMode && bLeft ) + { + for( sal_uInt16 n = m_aLines.size(); n; ) + { + --n; + aParam.LoopClear(); + aParam1.LoopClear(); + (*fnSelBox)( m_aLines[ n ], aParam, nDistStt, false ); + (*fnOtherBox)( m_aLines[ n ], aParam1, nDistStt, false ); + } + } + else + { + for( sal_uInt16 n = m_aLines.size(); n; ) + { + --n; + aParam.LoopClear(); + aParam1.LoopClear(); + (*fnOtherBox)( m_aLines[ n ], aParam1, nDistStt, false ); + (*fnSelBox)( m_aLines[ n ], aParam, nDistStt, false ); + } + } + } + } + break; + + case TableChgWidthHeightType::CellRight: + case TableChgWidthHeightType::CellLeft: + if( TableChgMode::VarWidthChangeAbs == m_eTableChgMode ) + { + // Then call itself recursively; only with another mode (proportional) + TableChgMode eOld = m_eTableChgMode; + m_eTableChgMode = TableChgMode::FixedWidthChangeAbs; + + bRet = SetColWidth( rCurrentBox, eType, nAbsDiff, nRelDiff, + ppUndo ); + m_eTableChgMode = eOld; + return bRet; + } + else if( bLeft ? nDist != 0 : (rSz.GetWidth() - nDist) > COLFUZZY ) + { + if( bLeft && TableChgMode::FixedWidthChangeAbs == m_eTableChgMode ) + aParam.bBigger = !bBigger; + + // First, see if there is enough room at all + SwTableBox* pBox = &rCurrentBox; + SwTableLine* pLine = rCurrentBox.GetUpper(); + while( pLine->GetUpper() ) + { + const SwTableBoxes::size_type nPos = pLine->GetBoxPos( pBox ); + if( bLeft ? nPos != 0 : nPos + 1 != pLine->GetTabBoxes().size() ) + break; + + pBox = pLine->GetUpper(); + pLine = pBox->GetUpper(); + } + + if( pLine->GetUpper() ) + { + // We need to correct the distance once again! + aParam.nSide -= ::lcl_GetDistance( pLine->GetUpper(), true ); + + if( bLeft ) + aParam.nMaxSize = aParam.nSide; + else + aParam.nMaxSize = pLine->GetUpper()->GetFrameFormat()-> + GetFrameSize().GetWidth() - aParam.nSide; + } + + // First, see if there is enough room at all + FN_lcl_SetBoxWidth fnTmp = aParam.bBigger ? fnOtherBox : fnSelBox; + bRet = (*fnTmp)( pLine, aParam, nDistStt, true ); + + // If true, set it + if( bRet ) + { + CR_SetBoxWidth aParam1( aParam ); + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + if( TableChgMode::FixedWidthChangeAbs != m_eTableChgMode && bLeft ) + { + (*fnSelBox)( pLine, aParam, nDistStt, false ); + (*fnOtherBox)( pLine, aParam1, nDistStt, false ); + } + else + { + (*fnOtherBox)( pLine, aParam1, nDistStt, false ); + (*fnSelBox)( pLine, aParam, nDistStt, false ); + } + } + } + break; + default: break; + } + + if( xFndBox ) + { + // Clean up the structure of all Lines + GCLines(); + + // Update Layout + if( !bBigger || xFndBox->AreLinesToRestore( *this ) ) + xFndBox->MakeFrames( *this ); + + // TL_CHART2: it is currently unclear if sth has to be done here. + // The function name hints that nothing needs to be done, on the other + // hand there is a case where sth gets deleted. :-( + + xFndBox.reset(); + } + +#if defined DBG_UTIL + if( bRet ) + { + CHECKBOXWIDTH + CHECKTABLELAYOUT + } +#endif + + return bRet; +} + +static void SetLineHeight( SwTableLine& rLine, SwTwips nOldHeight, SwTwips nNewHeight, + bool bMinSize ) +{ + SwLayoutFrame* pLineFrame = GetRowFrame( rLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine?" ); + + SwFrameFormat* pFormat = rLine.ClaimFrameFormat(); + + SwTwips nMyNewH, nMyOldH = pLineFrame->getFrameArea().Height(); + if( !nOldHeight ) // the BaseLine and absolute + nMyNewH = nMyOldH + nNewHeight; + else + { + // Calculate as exactly as possible + Fraction aTmp( nMyOldH ); + aTmp *= Fraction( nNewHeight, nOldHeight ); + aTmp += Fraction( 1, 2 ); // round up if needed + nMyNewH = long(aTmp); + } + + SwFrameSize eSize = SwFrameSize::Minimum; + if( !bMinSize && + ( nMyOldH - nMyNewH ) > ( CalcRowRstHeight( pLineFrame ) + ROWFUZZY )) + eSize = SwFrameSize::Fixed; + + pFormat->SetFormatAttr( SwFormatFrameSize( eSize, 0, nMyNewH ) ); + + // First adapt all internal ones + for( auto pBox : rLine.GetTabBoxes() ) + { + for( auto pLine : pBox->GetTabLines() ) + SetLineHeight( *pLine, nMyOldH, nMyNewH, bMinSize ); + } +} + +static bool lcl_SetSelLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ) +{ + bool bRet = true; + if( !bCheck ) + { + // Set line height + SetLineHeight( *pLine, 0, rParam.bBigger ? nDist : -nDist, + rParam.bBigger ); + } + else if( !rParam.bBigger ) + { + // Calculate the new relative size by means of the old one + SwLayoutFrame* pLineFrame = GetRowFrame( *pLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine?" ); + SwTwips nRstHeight = CalcRowRstHeight( pLineFrame ); + if( (nRstHeight + ROWFUZZY) < nDist ) + bRet = false; + } + return bRet; +} + +static bool lcl_SetOtherLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ) +{ + bool bRet = true; + if( bCheck ) + { + if( rParam.bBigger ) + { + // Calculate the new relative size by means of the old one + SwLayoutFrame* pLineFrame = GetRowFrame( *pLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine?" ); + + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + nDist *= pLineFrame->getFrameArea().Height(); + nDist /= rParam.nMaxHeight; + } + bRet = nDist <= CalcRowRstHeight( pLineFrame ); + } + } + else + { + // Set line height + // pLine is the following/preceding, thus adjust it + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + SwLayoutFrame* pLineFrame = GetRowFrame( *pLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine??" ); + + // Calculate the new relative size by means of the old one + // If the selected Box get bigger, adjust via the max space else + // via the max height. + if( (true) /*!rParam.bBigger*/ ) + { + nDist *= pLineFrame->getFrameArea().Height(); + nDist /= rParam.nMaxHeight; + } + else + { + // Calculate the new relative size by means of the old one + nDist *= CalcRowRstHeight( pLineFrame ); + nDist /= rParam.nMaxSpace; + } + } + SetLineHeight( *pLine, 0, rParam.bBigger ? -nDist : nDist, + !rParam.bBigger ); + } + return bRet; +} + +bool SwTable::SetRowHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff, std::unique_ptr<SwUndo>* ppUndo ) +{ + SwTableLine* pLine = rCurrentBox.GetUpper(); + + SwTableLine* pBaseLine = pLine; + while( pBaseLine->GetUpper() ) + pBaseLine = pBaseLine->GetUpper()->GetUpper(); + + std::unique_ptr<FndBox_> xFndBox; // for insertion/deletion + bool bBigger, + bRet = false, + bTop = TableChgWidthHeightType::CellTop == extractPosition( eType ); + sal_uInt16 nBaseLinePos = GetTabLines().GetPos( pBaseLine ); + + CR_SetLineHeight aParam( eType, + const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()) ); + bBigger = aParam.bBigger; + + SwTableLines* pLines = &m_aLines; + + // How do we get to the height? + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::CellTop: + case TableChgWidthHeightType::CellBottom: + if( pLine == pBaseLine ) + break; // it doesn't work then! + + // Is a nested Line (Box!) + pLines = &pLine->GetUpper()->GetTabLines(); + nBaseLinePos = pLines->GetPos( pLine ); + [[fallthrough]]; + + case TableChgWidthHeightType::RowBottom: + { + if( TableChgMode::VarWidthChangeAbs == m_eTableChgMode ) + { + // First test if we have room at all + if( bBigger ) + bRet = true; + else + bRet = lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, true ); + + if( bRet ) + { + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, false ); + } + } + else + { + bRet = true; + SwTableLines::size_type nStt; + SwTableLines::size_type nEnd; + if( bTop ) + { + nStt = 0; + nEnd = nBaseLinePos; + } + else + { + nStt = nBaseLinePos + 1; + nEnd = pLines->size(); + } + + // Get the current Lines' height + if( TableChgMode::FixedWidthChangeProp == m_eTableChgMode ) + { + for( auto n = nStt; n < nEnd; ++n ) + { + SwLayoutFrame* pLineFrame = GetRowFrame( *(*pLines)[ n ] ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine??" ); + aParam.nMaxSpace += CalcRowRstHeight( pLineFrame ); + aParam.nMaxHeight += pLineFrame->getFrameArea().Height(); + } + if( bBigger && aParam.nMaxSpace < nAbsDiff ) + bRet = false; + } + else + { + if( bTop ? nEnd != 0 : nStt < nEnd ) + { + if( bTop ) + nStt = nEnd - 1; + else + nEnd = nStt + 1; + } + else + bRet = false; + } + + if( bRet ) + { + if( bBigger ) + { + for( auto n = nStt; n < nEnd; ++n ) + { + if( !lcl_SetOtherLineHeight( (*pLines)[ n ], aParam, + nAbsDiff, true )) + { + bRet = false; + break; + } + } + } + else + bRet = lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, true ); + } + + if( bRet ) + { + // Adjust + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + CR_SetLineHeight aParam1( aParam ); + + if( bTop ) + { + lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, false ); + for( auto n = nStt; n < nEnd; ++n ) + lcl_SetOtherLineHeight( (*pLines)[ n ], aParam1, + nAbsDiff, false ); + } + else + { + for( auto n = nStt; n < nEnd; ++n ) + lcl_SetOtherLineHeight( (*pLines)[ n ], aParam1, + nAbsDiff, false ); + lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, false ); + } + } + else + { + // Then call itself recursively; only with another mode (proportional) + TableChgMode eOld = m_eTableChgMode; + m_eTableChgMode = TableChgMode::VarWidthChangeAbs; + + bRet = SetRowHeight( rCurrentBox, eType, nAbsDiff, + nRelDiff, ppUndo ); + + m_eTableChgMode = eOld; + xFndBox.reset(); + } + } + } + break; + default: break; + } + + if( xFndBox ) + { + // then clean up the structure of all Lines + GCLines(); + + // Update Layout + if( bBigger || xFndBox->AreLinesToRestore( *this ) ) + xFndBox->MakeFrames( *this ); + + // TL_CHART2: it is currently unclear if sth has to be done here. + + xFndBox.reset(); + } + + CHECKTABLELAYOUT + + return bRet; +} + +SwFrameFormat* SwShareBoxFormat::GetFormat( long nWidth ) const +{ + SwFrameFormat *pRet = nullptr, *pTmp; + for( auto n = aNewFormats.size(); n; ) + if( ( pTmp = aNewFormats[ --n ])->GetFrameSize().GetWidth() + == nWidth ) + { + pRet = pTmp; + break; + } + return pRet; +} + +SwFrameFormat* SwShareBoxFormat::GetFormat( const SfxPoolItem& rItem ) const +{ + const SfxPoolItem* pItem; + sal_uInt16 nWhich = rItem.Which(); + SwFrameFormat *pRet = nullptr, *pTmp; + const SfxPoolItem& rFrameSz = pOldFormat->GetFormatAttr( RES_FRM_SIZE, false ); + for( auto n = aNewFormats.size(); n; ) + if( SfxItemState::SET == ( pTmp = aNewFormats[ --n ])-> + GetItemState( nWhich, false, &pItem ) && *pItem == rItem && + pTmp->GetFormatAttr( RES_FRM_SIZE, false ) == rFrameSz ) + { + pRet = pTmp; + break; + } + return pRet; +} + +void SwShareBoxFormat::AddFormat( SwFrameFormat& rNew ) +{ + aNewFormats.push_back( &rNew ); +} + +bool SwShareBoxFormat::RemoveFormat( const SwFrameFormat& rFormat ) +{ + // returns true, if we can delete + if( pOldFormat == &rFormat ) + return true; + + std::vector<SwFrameFormat*>::iterator it = std::find( aNewFormats.begin(), aNewFormats.end(), &rFormat ); + if( aNewFormats.end() != it ) + aNewFormats.erase( it ); + return aNewFormats.empty(); +} + +SwShareBoxFormats::~SwShareBoxFormats() +{ +} + +SwFrameFormat* SwShareBoxFormats::GetFormat( const SwFrameFormat& rFormat, long nWidth ) const +{ + sal_uInt16 nPos; + return Seek_Entry( rFormat, &nPos ) + ? m_ShareArr[ nPos ]->GetFormat(nWidth) + : nullptr; +} +SwFrameFormat* SwShareBoxFormats::GetFormat( const SwFrameFormat& rFormat, + const SfxPoolItem& rItem ) const +{ + sal_uInt16 nPos; + return Seek_Entry( rFormat, &nPos ) + ? m_ShareArr[ nPos ]->GetFormat(rItem) + : nullptr; +} + +void SwShareBoxFormats::AddFormat( const SwFrameFormat& rOld, SwFrameFormat& rNew ) +{ + sal_uInt16 nPos; + SwShareBoxFormat* pEntry; + if( !Seek_Entry( rOld, &nPos )) + { + pEntry = new SwShareBoxFormat( rOld ); + m_ShareArr.insert(m_ShareArr.begin() + nPos, std::unique_ptr<SwShareBoxFormat>(pEntry)); + } + else + pEntry = m_ShareArr[ nPos ].get(); + + pEntry->AddFormat( rNew ); +} + +void SwShareBoxFormats::ChangeFrameFormat( SwTableBox* pBox, SwTableLine* pLn, + SwFrameFormat& rFormat ) +{ + SwClient aCl; + SwFrameFormat* pOld = nullptr; + if( pBox ) + { + pOld = pBox->GetFrameFormat(); + pOld->Add( &aCl ); + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(&rFormat) ); + } + else if( pLn ) + { + pOld = pLn->GetFrameFormat(); + pOld->Add( &aCl ); + pLn->ChgFrameFormat( static_cast<SwTableLineFormat*>(&rFormat) ); + } + if( pOld && pOld->HasOnlyOneListener() ) + { + RemoveFormat( *pOld ); + delete pOld; + } +} + +void SwShareBoxFormats::SetSize( SwTableBox& rBox, const SwFormatFrameSize& rSz ) +{ + SwFrameFormat *pBoxFormat = rBox.GetFrameFormat(), + *pRet = GetFormat( *pBoxFormat, rSz.GetWidth() ); + if( pRet ) + ChangeFrameFormat( &rBox, nullptr, *pRet ); + else + { + pRet = rBox.ClaimFrameFormat(); + pRet->SetFormatAttr( rSz ); + AddFormat( *pBoxFormat, *pRet ); + } +} + +void SwShareBoxFormats::SetAttr( SwTableBox& rBox, const SfxPoolItem& rItem ) +{ + SwFrameFormat *pBoxFormat = rBox.GetFrameFormat(), + *pRet = GetFormat( *pBoxFormat, rItem ); + if( pRet ) + ChangeFrameFormat( &rBox, nullptr, *pRet ); + else + { + pRet = rBox.ClaimFrameFormat(); + pRet->SetFormatAttr( rItem ); + AddFormat( *pBoxFormat, *pRet ); + } +} + +void SwShareBoxFormats::SetAttr( SwTableLine& rLine, const SfxPoolItem& rItem ) +{ + SwFrameFormat *pLineFormat = rLine.GetFrameFormat(), + *pRet = GetFormat( *pLineFormat, rItem ); + if( pRet ) + ChangeFrameFormat( nullptr, &rLine, *pRet ); + else + { + pRet = rLine.ClaimFrameFormat(); + pRet->SetFormatAttr( rItem ); + AddFormat( *pLineFormat, *pRet ); + } +} + +void SwShareBoxFormats::RemoveFormat( const SwFrameFormat& rFormat ) +{ + for (auto i = m_ShareArr.size(); i; ) + { + if (m_ShareArr[ --i ]->RemoveFormat(rFormat)) + { + m_ShareArr.erase( m_ShareArr.begin() + i ); + } + } +} + +bool SwShareBoxFormats::Seek_Entry( const SwFrameFormat& rFormat, sal_uInt16* pPos ) const +{ + sal_uIntPtr nIdx = reinterpret_cast<sal_uIntPtr>(&rFormat); + auto nO = m_ShareArr.size(); + decltype(nO) nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + const auto nM = nU + ( nO - nU ) / 2; + sal_uIntPtr nFormat = reinterpret_cast<sal_uIntPtr>(&m_ShareArr[ nM ]->GetOldFormat()); + if( nFormat == nIdx ) + { + if( pPos ) + *pPos = nM; + return true; + } + else if( nFormat < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + { + if( pPos ) + *pPos = nU; + return false; + } + else + nO = nM - 1; + } + } + if( pPos ) + *pPos = nU; + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx new file mode 100644 index 000000000..9a2cc95dc --- /dev/null +++ b/sw/source/core/doc/textboxhelper.cxx @@ -0,0 +1,774 @@ +/* -*- 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 <textboxhelper.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtcnct.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docsh.hxx> +#include <unocoll.hxx> +#include <unoframe.hxx> +#include <unodraw.hxx> +#include <unotextrange.hxx> +#include <cmdid.h> +#include <unomid.h> +#include <unoprnms.hxx> +#include <mvsave.hxx> +#include <fmtsrnd.hxx> +#include <frmfmt.hxx> +#include <frameformats.hxx> + +#include <editeng/unoprnms.hxx> +#include <editeng/memberids.h> +#include <svx/svdoashp.hxx> +#include <svx/svdpage.hxx> +#include <svl/itemiter.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <sal/log.hxx> +#include <svx/anchorid.hxx> + +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> + +using namespace com::sun::star; + +void SwTextBoxHelper::create(SwFrameFormat* pShape) +{ + // If TextBox wasn't enabled previously + if (pShape->GetAttrSet().HasItem(RES_CNTNT)) + return; + + // Create the associated TextFrame and insert it into the document. + uno::Reference<text::XTextContent> xTextFrame( + SwXServiceProvider::MakeInstance(SwServiceType::TypeTextFrame, *pShape->GetDoc()), + uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument( + pShape->GetDoc()->GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + uno::Reference<text::XTextContentAppend> xTextContentAppend(xTextDocument->getText(), + uno::UNO_QUERY); + try + { + SdrObject* pSourceSDRShape = pShape->FindRealSdrObject(); + uno::Reference<text::XTextContent> XSourceShape(pSourceSDRShape->getUnoShape(), + uno::UNO_QUERY_THROW); + xTextContentAppend->insertTextContentWithProperties( + xTextFrame, uno::Sequence<beans::PropertyValue>(), XSourceShape->getAnchor()); + } + catch (uno::Exception&) + { + xTextContentAppend->appendTextContent(xTextFrame, uno::Sequence<beans::PropertyValue>()); + } + // Link FLY and DRAW formats, so it becomes a text box (needed for syncProperty calls). + uno::Reference<text::XTextFrame> xRealTextFrame(xTextFrame, uno::UNO_QUERY); + auto pTextFrame = dynamic_cast<SwXTextFrame*>(xRealTextFrame.get()); + assert(nullptr != pTextFrame); + SwFrameFormat* pFormat = pTextFrame->GetFrameFormat(); + + assert(nullptr != dynamic_cast<SwDrawFrameFormat*>(pShape)); + assert(nullptr != dynamic_cast<SwFlyFrameFormat*>(pFormat)); + + pShape->SetOtherTextBoxFormat(pFormat); + pFormat->SetOtherTextBoxFormat(pShape); + + // Initialize properties. + uno::Reference<beans::XPropertySet> xPropertySet(xTextFrame, uno::UNO_QUERY); + uno::Any aEmptyBorder = uno::makeAny(table::BorderLine2()); + xPropertySet->setPropertyValue(UNO_NAME_TOP_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_BOTTOM_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_LEFT_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_RIGHT_BORDER, aEmptyBorder); + + xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::makeAny(sal_Int32(100))); + + xPropertySet->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::makeAny(text::SizeType::FIX)); + + xPropertySet->setPropertyValue(UNO_NAME_SURROUND, uno::makeAny(text::WrapTextMode_THROUGH)); + + uno::Reference<container::XNamed> xNamed(xTextFrame, uno::UNO_QUERY); + xNamed->setName(pShape->GetDoc()->GetUniqueFrameName()); + + // Link its text range to the original shape. + uno::Reference<text::XTextRange> xTextBox(xTextFrame, uno::UNO_QUERY_THROW); + SwUnoInternalPaM aInternalPaM(*pShape->GetDoc()); + if (sw::XTextRangeToSwPaM(aInternalPaM, xTextBox)) + { + SwAttrSet aSet(pShape->GetAttrSet()); + SwFormatContent aContent(aInternalPaM.GetNode().StartOfSectionNode()); + aSet.Put(aContent); + pShape->SetFormatAttr(aSet); + } + + // Also initialize the properties, which are not constant, but inherited from the shape's ones. + uno::Reference<drawing::XShape> xShape(pShape->FindRealSdrObject()->getUnoShape(), + uno::UNO_QUERY); + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::makeAny(xShape->getSize())); + + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + syncProperty(pShape, RES_FOLLOW_TEXT_FLOW, MID_FOLLOW_TEXT_FLOW, + xShapePropertySet->getPropertyValue(UNO_NAME_IS_FOLLOWING_TEXT_FLOW)); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT)); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION)); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT)); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION)); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION)); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION)); + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT)); + syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST)); + text::WritingMode eMode; + if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::makeAny(sal_Int16(eMode))); +} + +void SwTextBoxHelper::destroy(SwFrameFormat* pShape) +{ + // If a TextBox was enabled previously + if (pShape->GetAttrSet().HasItem(RES_CNTNT)) + { + SwFrameFormat* pFormat = pShape->GetOtherTextBoxFormat(); + + // Unlink the TextBox's text range from the original shape. + pShape->ResetFormatAttr(RES_CNTNT); + + // Delete the associated TextFrame. + if (pFormat) + pShape->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat(pFormat); + } +} + +bool SwTextBoxHelper::isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType) +{ + assert(nType == RES_FLYFRMFMT || nType == RES_DRAWFRMFMT); + if (!pFormat || pFormat->Which() != nType || !pFormat->GetAttrSet().HasItem(RES_CNTNT)) + return false; + + sal_uInt16 nOtherType = (pFormat->Which() == RES_FLYFRMFMT) ? sal_uInt16(RES_DRAWFRMFMT) + : sal_uInt16(RES_FLYFRMFMT); + SwFrameFormat* pOtherFormat = pFormat->GetOtherTextBoxFormat(); + if (!pOtherFormat) + return false; + + assert(pOtherFormat->Which() == nOtherType); + if (pOtherFormat->Which() != nOtherType) + return false; + + const SwFormatContent& rContent = pFormat->GetContent(); + return pOtherFormat->GetAttrSet().HasItem(RES_CNTNT) && pOtherFormat->GetContent() == rContent; +} + +sal_Int32 SwTextBoxHelper::getCount(SdrPage const* pPage) +{ + sal_Int32 nRet = 0; + for (std::size_t i = 0; i < pPage->GetObjCount(); ++i) + { + SdrObject* p = pPage->GetObj(i); + if (p && p->IsTextBox()) + continue; + ++nRet; + } + return nRet; +} + +sal_Int32 SwTextBoxHelper::getCount(const SwDoc* pDoc) +{ + sal_Int32 nRet = 0; + const SwFrameFormats& rSpzFrameFormats = *pDoc->GetSpzFrameFormats(); + for (const auto pFormat : rSpzFrameFormats) + { + if (isTextBox(pFormat, RES_FLYFRMFMT)) + ++nRet; + } + return nRet; +} + +uno::Any SwTextBoxHelper::getByIndex(SdrPage const* pPage, sal_Int32 nIndex) +{ + if (nIndex < 0) + throw lang::IndexOutOfBoundsException(); + + SdrObject* pRet = nullptr; + sal_Int32 nCount = 0; // Current logical index. + for (std::size_t i = 0; i < pPage->GetObjCount(); ++i) + { + SdrObject* p = pPage->GetObj(i); + if (p && p->IsTextBox()) + continue; + if (nCount == nIndex) + { + pRet = p; + break; + } + ++nCount; + } + + if (!pRet) + throw lang::IndexOutOfBoundsException(); + + return uno::makeAny(uno::Reference<drawing::XShape>(pRet->getUnoShape(), uno::UNO_QUERY)); +} + +sal_Int32 SwTextBoxHelper::getOrdNum(const SdrObject* pObject) +{ + if (const SdrPage* pPage = pObject->getSdrPageFromSdrObject()) + { + sal_Int32 nOrder = 0; // Current logical order. + for (std::size_t i = 0; i < pPage->GetObjCount(); ++i) + { + SdrObject* p = pPage->GetObj(i); + if (p && p->IsTextBox()) + continue; + if (p == pObject) + return nOrder; + ++nOrder; + } + } + + SAL_WARN("sw.core", "SwTextBoxHelper::getOrdNum: no page or page doesn't contain the object"); + return pObject->GetOrdNum(); +} + +void SwTextBoxHelper::getShapeWrapThrough(const SwFrameFormat* pTextBox, bool& rWrapThrough) +{ + SwFrameFormat* pShape = SwTextBoxHelper::getOtherTextBoxFormat(pTextBox, RES_FLYFRMFMT); + if (pShape) + rWrapThrough = pShape->GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH; +} + +SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(const SwFrameFormat* pFormat, + sal_uInt16 nType) +{ + if (!isTextBox(pFormat, nType)) + return nullptr; + return pFormat->GetOtherTextBoxFormat(); +} + +SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(uno::Reference<drawing::XShape> const& xShape) +{ + auto pShape = dynamic_cast<SwXShape*>(xShape.get()); + if (!pShape) + return nullptr; + + SwFrameFormat* pFormat = pShape->GetFrameFormat(); + return getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT); +} + +template <typename T> static void lcl_queryInterface(const SwFrameFormat* pShape, uno::Any& rAny) +{ + if (SwFrameFormat* pFormat = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + { + uno::Reference<T> const xInterface( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), uno::UNO_QUERY); + rAny <<= xInterface; + } +} + +uno::Any SwTextBoxHelper::queryInterface(const SwFrameFormat* pShape, const uno::Type& rType) +{ + uno::Any aRet; + + if (rType == cppu::UnoType<css::text::XTextAppend>::get()) + { + lcl_queryInterface<text::XTextAppend>(pShape, aRet); + } + else if (rType == cppu::UnoType<css::text::XText>::get()) + { + lcl_queryInterface<text::XText>(pShape, aRet); + } + else if (rType == cppu::UnoType<css::text::XTextRange>::get()) + { + lcl_queryInterface<text::XTextRange>(pShape, aRet); + } + + return aRet; +} + +tools::Rectangle SwTextBoxHelper::getTextRectangle(SwFrameFormat* pShape, bool bAbsolute) +{ + tools::Rectangle aRet; + aRet.SetEmpty(); + auto pSdrShape = pShape->FindRealSdrObject(); + auto pCustomShape = dynamic_cast<SdrObjCustomShape*>(pSdrShape); + if (pCustomShape) + { + // Need to temporarily release the lock acquired in + // SdXMLShapeContext::AddShape(), otherwise we get an empty rectangle, + // see EnhancedCustomShapeEngine::getTextBounds(). + uno::Reference<document::XActionLockable> xLockable(pCustomShape->getUnoShape(), + uno::UNO_QUERY); + sal_Int16 nLocks = 0; + if (xLockable.is()) + nLocks = xLockable->resetActionLocks(); + pCustomShape->GetTextBounds(aRet); + if (nLocks) + xLockable->setActionLocks(nLocks); + } + else if (pSdrShape) + { + // fallback - get *any* bound rect we can possibly get hold of + aRet = pSdrShape->GetCurrentBoundRect(); + } + + if (!bAbsolute && pSdrShape) + { + // Relative, so count the logic (reference) rectangle, see the EnhancedCustomShape2d ctor. + Point aPoint(pSdrShape->GetSnapRect().Center()); + Size aSize(pSdrShape->GetLogicRect().GetSize()); + aPoint.AdjustX(-(aSize.Width() / 2)); + aPoint.AdjustY(-(aSize.Height() / 2)); + tools::Rectangle aLogicRect(aPoint, aSize); + aRet.Move(-1 * aLogicRect.Left(), -1 * aLogicRect.Top()); + } + + return aRet; +} + +void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, const OUString& rPropertyName, + const css::uno::Any& rValue) +{ + if (rPropertyName == "CustomShapeGeometry") + { + // CustomShapeGeometry changes the textbox position offset and size, so adjust both. + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any()); + + SdrObject* pObject = pShape->FindRealSdrObject(); + if (pObject) + { + tools::Rectangle aRectangle(pObject->GetSnapRect()); + syncProperty( + pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + uno::makeAny(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Left())))); + syncProperty( + pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + uno::makeAny(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Top())))); + } + + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT); + if (!pFormat) + return; + + comphelper::SequenceAsHashMap aCustomShapeGeometry(rValue); + auto it = aCustomShapeGeometry.find("TextPreRotateAngle"); + if (it == aCustomShapeGeometry.end()) + { + it = aCustomShapeGeometry.find("TextRotateAngle"); + } + + if (it != aCustomShapeGeometry.end()) + { + auto nAngle = it->second.has<sal_Int32>() ? it->second.get<sal_Int32>() : 0; + if (nAngle == 0) + { + nAngle = it->second.has<double>() ? it->second.get<double>() : 0; + } + + sal_Int16 nDirection = 0; + switch (nAngle) + { + case -90: + nDirection = text::WritingMode2::TB_RL; + break; + case -270: + nDirection = text::WritingMode2::BT_LR; + break; + } + + if (nDirection) + { + syncProperty(pShape, RES_FRAMEDIR, 0, uno::makeAny(nDirection)); + } + } + } + else if (rPropertyName == UNO_NAME_TEXT_VERT_ADJUST) + syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, rValue); + else if (rPropertyName == UNO_NAME_TEXT_AUTOGROWHEIGHT) + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, rValue); + else if (rPropertyName == UNO_NAME_TEXT_LEFTDIST) + syncProperty(pShape, RES_BOX, LEFT_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_RIGHTDIST) + syncProperty(pShape, RES_BOX, RIGHT_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_UPPERDIST) + syncProperty(pShape, RES_BOX, TOP_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_LOWERDIST) + syncProperty(pShape, RES_BOX, BOTTOM_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_WRITINGMODE) + { + text::WritingMode eMode; + if (rValue >>= eMode) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::makeAny(sal_Int16(eMode))); + } +} + +void SwTextBoxHelper::getProperty(SwFrameFormat const* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, + css::uno::Any& rValue) +{ + if (!pShape) + return; + + nMemberID &= ~CONVERT_TWIPS; + + if (SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + { + if (nWID == RES_CHAIN) + { + switch (nMemberID) + { + case MID_CHAIN_PREVNAME: + case MID_CHAIN_NEXTNAME: + { + const SwFormatChain& rChain = pFormat->GetChain(); + rChain.QueryValue(rValue, nMemberID); + } + break; + case MID_CHAIN_NAME: + rValue <<= pFormat->GetName(); + break; + } + } + } +} + +void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, + const css::uno::Any& rValue) +{ + // No shape yet? Then nothing to do, initial properties are set by create(). + if (!pShape) + return; + + uno::Any aValue(rValue); + nMemberID &= ~CONVERT_TWIPS; + + if (SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + { + OUString aPropertyName; + bool bAdjustX = false; + bool bAdjustY = false; + bool bAdjustSize = false; + switch (nWID) + { + case RES_HORI_ORIENT: + switch (nMemberID) + { + case MID_HORIORIENT_ORIENT: + aPropertyName = UNO_NAME_HORI_ORIENT; + break; + case MID_HORIORIENT_RELATION: + aPropertyName = UNO_NAME_HORI_ORIENT_RELATION; + break; + case MID_HORIORIENT_POSITION: + aPropertyName = UNO_NAME_HORI_ORIENT_POSITION; + bAdjustX = true; + break; + } + break; + case RES_LR_SPACE: + { + switch (nMemberID) + { + case MID_L_MARGIN: + aPropertyName = UNO_NAME_LEFT_MARGIN; + break; + case MID_R_MARGIN: + aPropertyName = UNO_NAME_RIGHT_MARGIN; + break; + } + break; + } + case RES_VERT_ORIENT: + switch (nMemberID) + { + case MID_VERTORIENT_ORIENT: + aPropertyName = UNO_NAME_VERT_ORIENT; + break; + case MID_VERTORIENT_RELATION: + aPropertyName = UNO_NAME_VERT_ORIENT_RELATION; + break; + case MID_VERTORIENT_POSITION: + aPropertyName = UNO_NAME_VERT_ORIENT_POSITION; + bAdjustY = true; + break; + } + break; + case RES_FRM_SIZE: + switch (nMemberID) + { + case MID_FRMSIZE_IS_AUTO_HEIGHT: + aPropertyName = UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT; + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + aPropertyName = UNO_NAME_RELATIVE_HEIGHT_RELATION; + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + aPropertyName = UNO_NAME_RELATIVE_WIDTH_RELATION; + break; + default: + aPropertyName = UNO_NAME_SIZE; + bAdjustSize = true; + break; + } + break; + case RES_ANCHOR: + switch (nMemberID) + { + case MID_ANCHOR_ANCHORTYPE: + if (aValue.get<text::TextContentAnchorType>() + == text::TextContentAnchorType_AS_CHARACTER) + { + uno::Reference<beans::XPropertySet> const xPropertySet( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), + uno::UNO_QUERY); + xPropertySet->setPropertyValue( + UNO_NAME_SURROUND, uno::makeAny(text::WrapTextMode_THROUGH)); + return; + } + break; + } + break; + case FN_TEXT_RANGE: + { + uno::Reference<text::XTextRange> xRange; + rValue >>= xRange; + SwUnoInternalPaM aInternalPaM(*pFormat->GetDoc()); + if (sw::XTextRangeToSwPaM(aInternalPaM, xRange)) + { + SwFormatAnchor aAnchor(pFormat->GetAnchor()); + aAnchor.SetAnchor(aInternalPaM.Start()); + pFormat->SetFormatAttr(aAnchor); + } + } + break; + case RES_CHAIN: + switch (nMemberID) + { + case MID_CHAIN_PREVNAME: + aPropertyName = UNO_NAME_CHAIN_PREV_NAME; + break; + case MID_CHAIN_NEXTNAME: + aPropertyName = UNO_NAME_CHAIN_NEXT_NAME; + break; + } + break; + case RES_TEXT_VERT_ADJUST: + aPropertyName = UNO_NAME_TEXT_VERT_ADJUST; + break; + case RES_BOX: + switch (nMemberID) + { + case LEFT_BORDER_DISTANCE: + aPropertyName = UNO_NAME_LEFT_BORDER_DISTANCE; + break; + case RIGHT_BORDER_DISTANCE: + aPropertyName = UNO_NAME_RIGHT_BORDER_DISTANCE; + break; + case TOP_BORDER_DISTANCE: + aPropertyName = UNO_NAME_TOP_BORDER_DISTANCE; + break; + case BOTTOM_BORDER_DISTANCE: + aPropertyName = UNO_NAME_BOTTOM_BORDER_DISTANCE; + break; + } + break; + case RES_OPAQUE: + aPropertyName = UNO_NAME_OPAQUE; + break; + case RES_FRAMEDIR: + aPropertyName = UNO_NAME_WRITING_MODE; + break; + case RES_WRAP_INFLUENCE_ON_OBJPOS: + switch (nMemberID) + { + case MID_ALLOW_OVERLAP: + aPropertyName = UNO_NAME_ALLOW_OVERLAP; + break; + } + break; + } + + if (!aPropertyName.isEmpty()) + { + // Position/size should be the text position/size, not the shape one as-is. + if (bAdjustX || bAdjustY || bAdjustSize) + { + tools::Rectangle aRect = getTextRectangle(pShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + { + if (bAdjustX || bAdjustY) + { + sal_Int32 nValue; + if (aValue >>= nValue) + { + if (bAdjustX) + nValue += TWIPS_TO_MM(aRect.getX()); + else if (bAdjustY) + nValue += TWIPS_TO_MM(aRect.getY()); + aValue <<= nValue; + } + } + else if (bAdjustSize) + { + awt::Size aSize(TWIPS_TO_MM(aRect.getWidth()), + TWIPS_TO_MM(aRect.getHeight())); + aValue <<= aSize; + } + } + } + + uno::Reference<beans::XPropertySet> const xPropertySet( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), uno::UNO_QUERY); + xPropertySet->setPropertyValue(aPropertyName, aValue); + } + } +} + +void SwTextBoxHelper::saveLinks(const SwFrameFormats& rFormats, + std::map<const SwFrameFormat*, const SwFrameFormat*>& rLinks) +{ + for (const auto pFormat : rFormats) + { + if (SwFrameFormat* pTextBox = getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT)) + rLinks[pFormat] = pTextBox; + } +} + +void SwTextBoxHelper::restoreLinks(std::set<ZSortFly>& rOld, std::vector<SwFrameFormat*>& rNew, + SavedLink& rSavedLinks) +{ + std::size_t i = 0; + for (const auto& rIt : rOld) + { + auto aTextBoxIt = rSavedLinks.find(rIt.GetFormat()); + if (aTextBoxIt != rSavedLinks.end()) + { + std::size_t j = 0; + for (const auto& rJt : rOld) + { + if (rJt.GetFormat() == aTextBoxIt->second) + rNew[i]->SetFormatAttr(rNew[j]->GetContent()); + ++j; + } + } + ++i; + } +} + +void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& rSet) +{ + if (SwFrameFormat* pFormat = getOtherTextBoxFormat(&rShape, RES_DRAWFRMFMT)) + { + SfxItemSet aTextBoxSet(pFormat->GetDoc()->GetAttrPool(), aFrameFormatSetRange); + + SfxItemIter aIter(rSet); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if (rShape.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + { + SwFormatAnchor pShapeAnch = rShape.GetAnchor(); + aTextBoxSet.Put(pShapeAnch); + } + + switch (pItem->Which()) + { + case RES_VERT_ORIENT: + { + auto& rOrient = static_cast<const SwFormatVertOrient&>(*pItem); + SwFormatVertOrient aOrient(rOrient); + + tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + aOrient.SetPos(aOrient.GetPos() + aRect.getY()); + + if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + aOrient.SetRelationOrient(rShape.GetVertOrient().GetRelationOrient()); + } + aTextBoxSet.Put(aOrient); + + // restore height (shrunk for extending beyond the page bottom - tdf#91260) + SwFormatFrameSize aSize(pFormat->GetFrameSize()); + if (!aRect.IsEmpty()) + { + aSize.SetHeight(aRect.getHeight()); + aTextBoxSet.Put(aSize); + } + } + break; + case RES_HORI_ORIENT: + { + auto& rOrient = static_cast<const SwFormatHoriOrient&>(*pItem); + SwFormatHoriOrient aOrient(rOrient); + + tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + aOrient.SetPos(aOrient.GetPos() + aRect.getX()); + + if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + aOrient.SetRelationOrient(rShape.GetHoriOrient().GetRelationOrient()); + } + aTextBoxSet.Put(aOrient); + } + break; + case RES_FRM_SIZE: + { + // In case the shape got resized, then we need to adjust both + // the position and the size of the textbox (e.g. larger + // rounded edges of a rectangle -> need to push right/down the + // textbox). + SwFormatVertOrient aVertOrient(rShape.GetVertOrient()); + SwFormatHoriOrient aHoriOrient(rShape.GetHoriOrient()); + SwFormatFrameSize aSize(pFormat->GetFrameSize()); + + tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + { + aVertOrient.SetPos(aVertOrient.GetPos() + aRect.getY()); + aTextBoxSet.Put(aVertOrient); + + aHoriOrient.SetPos(aHoriOrient.GetPos() + aRect.getX()); + aTextBoxSet.Put(aHoriOrient); + + aSize.SetWidth(aRect.getWidth()); + aSize.SetHeight(aRect.getHeight()); + aTextBoxSet.Put(aSize); + } + } + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncFlyFrameAttr: unhandled which-id: " + << pItem->Which()); + break; + } + + pItem = aIter.NextItem(); + } while (pItem && (0 != pItem->Which())); + + if (aTextBoxSet.Count()) + pFormat->GetDoc()->SetFlyFrameAttr(*pFormat, aTextBoxSet); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/visiturl.cxx b/sw/source/core/doc/visiturl.cxx new file mode 100644 index 000000000..1e284f1d8 --- /dev/null +++ b/sw/source/core/doc/visiturl.cxx @@ -0,0 +1,125 @@ +/* -*- 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 <sfx2/docfile.hxx> +#include <svl/inethist.hxx> +#include <fmtinfmt.hxx> +#include <txtinet.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <visiturl.hxx> +#include <hints.hxx> +#include <ndtxt.hxx> +#include <editsh.hxx> +#include <docsh.hxx> + +SwURLStateChanged::SwURLStateChanged( SwDoc* pD ) + : pDoc( pD ) +{ + StartListening( *INetURLHistory::GetOrCreate() ); +} + +SwURLStateChanged::~SwURLStateChanged() +{ + EndListening( *INetURLHistory::GetOrCreate() ); +} + +void SwURLStateChanged::Notify( SfxBroadcaster& , const SfxHint& rHint ) +{ + if( dynamic_cast<const INetURLHistoryHint*>(&rHint) && pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // This URL has been changed: + const INetURLObject* pIURL = static_cast<const INetURLHistoryHint&>(rHint).GetObject(); + OUString sURL( pIURL->GetMainURL( INetURLObject::DecodeMechanism::NONE ) ), sBkmk; + + SwEditShell* pESh = pDoc->GetEditShell(); + + if( pDoc->GetDocShell() && pDoc->GetDocShell()->GetMedium() && + // If this is our Doc, we can also have local jumps! + pDoc->GetDocShell()->GetMedium()->GetName() == sURL ) + sBkmk = "#" + pIURL->GetMark(); + + bool bAction = false, bUnLockView = false; + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + const SwFormatINetFormat* pFormatItem = dynamic_cast<const SwFormatINetFormat*>(pItem); + if( pFormatItem != nullptr && + ( pFormatItem->GetValue() == sURL || ( !sBkmk.isEmpty() && pFormatItem->GetValue() == sBkmk ))) + { + const SwTextINetFormat* pTextAttr = pFormatItem->GetTextINetFormat(); + if (pTextAttr != nullptr) + { + const SwTextNode* pTextNd = pTextAttr->GetpTextNode(); + if (pTextNd != nullptr) + { + if( !bAction && pESh ) + { + pESh->StartAllAction(); + bAction = true; + bUnLockView = !pESh->IsViewLocked(); + pESh->LockView( true ); + } + const_cast<SwTextINetFormat*>(pTextAttr)->SetVisitedValid(false); + const SwTextAttr* pAttr = pTextAttr; + SwUpdateAttr aUpdateAttr( + pAttr->GetStart(), + *pAttr->End(), + RES_FMT_CHG); + + const_cast< SwTextNode* >(pTextNd)->ModifyNotification(&aUpdateAttr, &aUpdateAttr); + } + } + } + } + + if( bAction ) + pESh->EndAllAction(); + if( bUnLockView ) + pESh->LockView( false ); + } +} + +// Check if the URL has been visited before. Via the Doc, if only one Bookmark is set +// We need to put the Doc's name before it! +bool SwDoc::IsVisitedURL( const OUString& rURL ) +{ + bool bRet = false; + if( !rURL.isEmpty() ) + { + INetURLHistory *pHist = INetURLHistory::GetOrCreate(); + if( '#' == rURL[0] && mpDocShell && mpDocShell->GetMedium() ) + { + INetURLObject aIObj( mpDocShell->GetMedium()->GetURLObject() ); + aIObj.SetMark( rURL.copy( 1 ) ); + bRet = pHist->QueryUrl( aIObj ); + } + else + bRet = pHist->QueryUrl( rURL ); + + // We also want to be informed about status updates in the History + if( !mpURLStateChgd ) + { + SwDoc* pD = this; + pD->mpURLStateChgd.reset( new SwURLStateChanged( this ) ); + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/cancellablejob.cxx b/sw/source/core/docnode/cancellablejob.cxx new file mode 100644 index 000000000..eecd1d336 --- /dev/null +++ b/sw/source/core/docnode/cancellablejob.cxx @@ -0,0 +1,33 @@ +/* -*- 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 "cancellablejob.hxx" +#include <observablethread.hxx> + +CancellableJob::CancellableJob( const rtl::Reference< ObservableThread >& rThread ) : + mrThread( rThread ) +{ +} + +// css::util::XCancellable: +void SAL_CALL CancellableJob::cancel() +{ + mrThread->join(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/cancellablejob.hxx b/sw/source/core/docnode/cancellablejob.hxx new file mode 100644 index 000000000..ecd226b51 --- /dev/null +++ b/sw/source/core/docnode/cancellablejob.hxx @@ -0,0 +1,47 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_DOCNODE_CANCELLABLEJOB_HXX +#define INCLUDED_SW_SOURCE_CORE_DOCNODE_CANCELLABLEJOB_HXX + +#include <sal/config.h> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/util/XCancellable.hpp> + +#include <rtl/ref.hxx> + +#include <observablethread.hxx> + +class CancellableJob : public ::cppu::WeakImplHelper<css::util::XCancellable> +{ +public: + explicit CancellableJob( const ::rtl::Reference< ObservableThread >& rThread ); + + // css::util::XCancellable: + virtual void SAL_CALL cancel() override; + +private: + CancellableJob( CancellableJob const & ) = delete; + void operator =( CancellableJob const & ) = delete; + + ::rtl::Reference< ObservableThread > mrThread; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/finalthreadmanager.cxx b/sw/source/core/docnode/finalthreadmanager.cxx new file mode 100644 index 000000000..80ddd75c3 --- /dev/null +++ b/sw/source/core/docnode/finalthreadmanager.cxx @@ -0,0 +1,429 @@ +/* -*- 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 <finalthreadmanager.hxx> + +#include <osl/diagnose.h> +#include <osl/thread.hxx> +#include <pausethreadstarting.hxx> +#include <swthreadjoiner.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <rtl/ustring.hxx> +#include <cppuhelper/supportsservice.hxx> + +/** thread to cancel a give list of cancellable jobs + + helper class for FinalThreadManager +*/ +class CancelJobsThread : public osl::Thread +{ + public: + explicit CancelJobsThread( const std::list< css::uno::Reference< css::util::XCancellable > >& rJobs ) + : osl::Thread(), + maMutex(), + maJobs( rJobs ), + mbAllJobsCancelled( false ), + mbStopped( false ) + { + } + + void addJobs( std::list< css::uno::Reference< css::util::XCancellable > >& rJobs ); + bool allJobsCancelled() const; + void stopWhenAllJobsCancelled(); + + private: + bool existJobs() const; + + css::uno::Reference< css::util::XCancellable > getNextJob(); + + bool stopped() const; + virtual void SAL_CALL run() override; + mutable osl::Mutex maMutex; + + std::list< css::uno::Reference< css::util::XCancellable > > maJobs; + + bool mbAllJobsCancelled; + bool mbStopped; +}; + +void CancelJobsThread::addJobs( std::list< css::uno::Reference< css::util::XCancellable > >& rJobs ) +{ + osl::MutexGuard aGuard(maMutex); + + maJobs.insert( maJobs.end(), rJobs.begin(), rJobs.end() ); + mbAllJobsCancelled = !maJobs.empty(); +} + +bool CancelJobsThread::existJobs() const +{ + osl::MutexGuard aGuard(maMutex); + + return !maJobs.empty(); +} + +bool CancelJobsThread::allJobsCancelled() const +{ + osl::MutexGuard aGuard(maMutex); + + return maJobs.empty() && mbAllJobsCancelled; +} + +void CancelJobsThread::stopWhenAllJobsCancelled() +{ + osl::MutexGuard aGuard(maMutex); + + mbStopped = true; +} + +css::uno::Reference< css::util::XCancellable > CancelJobsThread::getNextJob() +{ + css::uno::Reference< css::util::XCancellable > xRet; + + { + osl::MutexGuard aGuard(maMutex); + + if ( !maJobs.empty() ) + { + xRet = maJobs.front(); + maJobs.pop_front(); + } + } + + return xRet; +} + +bool CancelJobsThread::stopped() const +{ + osl::MutexGuard aGuard(maMutex); + + return mbStopped; +} + +void SAL_CALL CancelJobsThread::run() +{ + osl_setThreadName("sw CancelJobsThread"); + + while ( !stopped() ) + { + while ( existJobs() ) + { + css::uno::Reference< css::util::XCancellable > aJob( getNextJob() ); + if ( aJob.is() ) + aJob->cancel(); + } + + mbAllJobsCancelled = true; + + { + osl::Thread::wait(std::chrono::seconds(1)); + } + } +} + +/** thread to terminate office, when all jobs are cancelled. + + helper class for FinalThreadManager +*/ +class TerminateOfficeThread : public osl::Thread +{ + public: + TerminateOfficeThread( CancelJobsThread const & rCancelJobsThread, + css::uno::Reference< css::uno::XComponentContext > const & xContext ) + : osl::Thread(), + maMutex(), + mrCancelJobsThread( rCancelJobsThread ), + mbStopOfficeTermination( false ), + mxContext( xContext ) + { + } + + void StopOfficeTermination(); + + private: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + bool OfficeTerminationStopped(); + void PerformOfficeTermination(); + + osl::Mutex maMutex; + + const CancelJobsThread& mrCancelJobsThread; + bool mbStopOfficeTermination; + + css::uno::Reference< css::uno::XComponentContext > mxContext; +}; + +void TerminateOfficeThread::StopOfficeTermination() +{ + osl::MutexGuard aGuard(maMutex); + + mbStopOfficeTermination = true; +} + +bool TerminateOfficeThread::OfficeTerminationStopped() +{ + osl::MutexGuard aGuard(maMutex); + + return mbStopOfficeTermination; +} + +void SAL_CALL TerminateOfficeThread::run() +{ + osl_setThreadName("sw TerminateOfficeThread"); + + while ( !OfficeTerminationStopped() ) + { + osl::MutexGuard aGuard(maMutex); + + if ( mrCancelJobsThread.allJobsCancelled() ) + break; + } + + if ( !OfficeTerminationStopped() ) + PerformOfficeTermination(); +} + +void TerminateOfficeThread::PerformOfficeTermination() +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(mxContext); + + css::uno::Reference< css::container::XElementAccess > xList = xDesktop->getFrames(); + if ( !xList.is() ) + { + OSL_FAIL( "<TerminateOfficeThread::PerformOfficeTermination()> - no XElementAccess!" ); + return; + } + + if ( !xList->hasElements() ) + { + if ( !OfficeTerminationStopped() ) + xDesktop->terminate(); + } +} + +void SAL_CALL TerminateOfficeThread::onTerminated() +{ + if ( OfficeTerminationStopped() ) + delete this; +} + +FinalThreadManager::FinalThreadManager(css::uno::Reference< css::uno::XComponentContext > const & context) + : m_xContext(context), + maMutex(), + maThreads(), + mpTerminateOfficeThread( nullptr ), + mbRegisteredAtDesktop( false ) +{ + +} + +void FinalThreadManager::registerAsListenerAtDesktop() +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext); + xDesktop->addTerminateListener( css::uno::Reference< css::frame::XTerminateListener >( static_cast< cppu::OWeakObject* >( this ), css::uno::UNO_QUERY ) ); +} + +FinalThreadManager::~FinalThreadManager() +{ + if ( mpPauseThreadStarting ) + { + mpPauseThreadStarting.reset(); + } + + if ( mpTerminateOfficeThread != nullptr ) + { + mpTerminateOfficeThread->StopOfficeTermination(); // thread kills itself. + mpTerminateOfficeThread = nullptr; + } + + if ( !maThreads.empty() ) + { + OSL_FAIL( "<FinalThreadManager::~FinalThreadManager()> - still registered jobs are existing -> perform cancellation" ); + cancelAllJobs(); + } + + if ( mpCancelJobsThread != nullptr ) + { + if ( !mpCancelJobsThread->allJobsCancelled() ) + OSL_FAIL( "<FinalThreadManager::~FinalThreadManager()> - cancellation of registered jobs not yet finished -> wait for its finish" ); + + mpCancelJobsThread->stopWhenAllJobsCancelled(); + mpCancelJobsThread->join(); + mpCancelJobsThread.reset(); + } +} + +// com.sun.star.uno.XServiceInfo: +OUString SAL_CALL FinalThreadManager::getImplementationName() +{ + return "com.sun.star.util.comp.FinalThreadManager"; +} + +sal_Bool SAL_CALL FinalThreadManager::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +css::uno::Sequence< OUString > SAL_CALL FinalThreadManager::getSupportedServiceNames() +{ + return { "com.sun.star.util.JobManager" }; +} + +// css::util::XJobManager: +void SAL_CALL FinalThreadManager::registerJob(const css::uno::Reference< css::util::XCancellable > & Job) +{ + osl::MutexGuard aGuard(maMutex); + + maThreads.push_back( Job ); + + if ( !mbRegisteredAtDesktop ) + { + registerAsListenerAtDesktop(); + mbRegisteredAtDesktop = true; + } +} + +void SAL_CALL FinalThreadManager::releaseJob(const css::uno::Reference< css::util::XCancellable > & Job) +{ + osl::MutexGuard aGuard(maMutex); + + maThreads.remove( Job ); +} + +void SAL_CALL FinalThreadManager::cancelAllJobs() +{ + std::list< css::uno::Reference< css::util::XCancellable > > aThreads; + { + osl::MutexGuard aGuard(maMutex); + + aThreads.insert( aThreads.end(), maThreads.begin(), maThreads.end() ); + maThreads.clear(); + } + + if ( !aThreads.empty() ) + { + osl::MutexGuard aGuard(maMutex); + + if ( mpCancelJobsThread == nullptr ) + { + mpCancelJobsThread.reset(new CancelJobsThread( aThreads )); + if ( !mpCancelJobsThread->create() ) + { + mpCancelJobsThread.reset(); + for (auto const& elem : aThreads) + { + elem->cancel(); + } + aThreads.clear(); + } + } + else + mpCancelJobsThread->addJobs( aThreads ); + } +} + +// css::frame::XTerminateListener +void SAL_CALL FinalThreadManager::queryTermination( const css::lang::EventObject& ) +{ + osl::MutexGuard aGuard(maMutex); + + cancelAllJobs(); + // Sleep 1 second to give the thread for job cancellation some time. + // Probably, all started threads have already finished its work. + if ( mpCancelJobsThread != nullptr && + !mpCancelJobsThread->allJobsCancelled() ) + { + osl::Thread::wait(std::chrono::seconds(1)); + } + + if ( mpCancelJobsThread != nullptr && + !mpCancelJobsThread->allJobsCancelled() ) + { + if ( mpTerminateOfficeThread != nullptr ) + { + if ( mpTerminateOfficeThread->isRunning() ) + mpTerminateOfficeThread->StopOfficeTermination(); // thread kills itself. + else + delete mpTerminateOfficeThread; + + mpTerminateOfficeThread = nullptr; + } + mpTerminateOfficeThread = new TerminateOfficeThread( *mpCancelJobsThread, + m_xContext ); + if ( !mpTerminateOfficeThread->create() ) + { + delete mpTerminateOfficeThread; + mpTerminateOfficeThread = nullptr; + } + + throw css::frame::TerminationVetoException(); + } + + mpPauseThreadStarting.reset(new SwPauseThreadStarting()); +} + +void SAL_CALL FinalThreadManager::cancelTermination( const css::lang::EventObject& ) +{ + mpPauseThreadStarting.reset(); +} + +void SAL_CALL FinalThreadManager::notifyTermination( const css::lang::EventObject& ) +{ + if ( mpTerminateOfficeThread != nullptr ) + { + if ( mpTerminateOfficeThread->isRunning() ) + mpTerminateOfficeThread->StopOfficeTermination(); // thread kills itself. + else + delete mpTerminateOfficeThread; + + mpTerminateOfficeThread = nullptr; + } + + if ( !maThreads.empty() ) + cancelAllJobs(); + + if ( mpCancelJobsThread != nullptr ) + { + mpCancelJobsThread->stopWhenAllJobsCancelled(); + mpCancelJobsThread->join(); + mpCancelJobsThread.reset(); + } + + // get reference of this + css::uno::Reference< css::uno::XInterface > aOwnRef( static_cast< cppu::OWeakObject* >( this )); + // notify <SwThreadJoiner> to release its reference + SwThreadJoiner::ReleaseThreadJoiner(); +} + +// ::com::sun:star::lang::XEventListener (inherited via css::frame::XTerminateListener) +void SAL_CALL FinalThreadManager::disposing( const css::lang::EventObject& ) +{ + // nothing to do, because instance doesn't hold any references of observed objects +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_util_comp_FinalThreadManager_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new FinalThreadManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndcopy.cxx b/sw/source/core/docnode/ndcopy.cxx new file mode 100644 index 000000000..4d12c2d2e --- /dev/null +++ b/sw/source/core/docnode/ndcopy.cxx @@ -0,0 +1,361 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <node.hxx> +#include <frmfmt.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <swtblfmt.hxx> +#include <cellatr.hxx> +#include <docary.hxx> +#include <ddefld.hxx> +#include <swddetbl.hxx> +#include <ndindex.hxx> +#include <frameformats.hxx> +#include <vector> +#include <osl/diagnose.h> + + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +namespace { + +// Structure for the mapping from old and new frame formats to the +// boxes and lines of a table +struct MapTableFrameFormat +{ + const SwFrameFormat *pOld; + SwFrameFormat *pNew; + MapTableFrameFormat( const SwFrameFormat *pOldFormat, SwFrameFormat*pNewFormat ) + : pOld( pOldFormat ), pNew( pNewFormat ) + {} +}; + +} + +typedef std::vector<MapTableFrameFormat> MapTableFrameFormats; + +SwContentNode* SwTextNode::MakeCopy(SwDoc* pDoc, const SwNodeIndex& rIdx, bool const bNewFrames) const +{ + // the Copy-Textnode is the Node with the Text, the Copy-Attrnode is the + // node with the collection and hard attributes. Normally is the same + // node, but if insert a glossary without formatting, then the Attrnode + // is the prev node of the destination position in dest. document. + SwTextNode* pCpyTextNd = const_cast<SwTextNode*>(this); + SwTextNode* pCpyAttrNd = pCpyTextNd; + + // Copy the formats to the other document + SwTextFormatColl* pColl = nullptr; + if( pDoc->IsInsOnlyTextGlossary() ) + { + SwNodeIndex aIdx( rIdx, -1 ); + if( aIdx.GetNode().IsTextNode() ) + { + pCpyAttrNd = aIdx.GetNode().GetTextNode(); + pColl = &pCpyAttrNd->GetTextColl()->GetNextTextFormatColl(); + } + } + if( !pColl ) + pColl = pDoc->CopyTextColl( *GetTextColl() ); + + SwTextNode* pTextNd = pDoc->GetNodes().MakeTextNode(rIdx, pColl, bNewFrames); + + // METADATA: register copy + pTextNd->RegisterAsCopyOf(*pCpyTextNd); + + // Copy Attribute/Text + if( !pCpyAttrNd->HasSwAttrSet() ) + // An AttrSet was added for numbering, so delete it + pTextNd->ResetAllAttr(); + + // if Copy-Textnode unequal to Copy-Attrnode, then copy first + // the attributes into the new Node. + if( pCpyAttrNd != pCpyTextNd ) + { + pCpyAttrNd->CopyAttr( pTextNd, 0, 0 ); + if( pCpyAttrNd->HasSwAttrSet() ) + { + SwAttrSet aSet( *pCpyAttrNd->GetpSwAttrSet() ); + aSet.ClearItem( RES_PAGEDESC ); + aSet.ClearItem( RES_BREAK ); + aSet.CopyToModify( *pTextNd ); + } + } + + // Is that enough? What about PostIts/Fields/FieldTypes? + // #i96213# - force copy of all attributes + pCpyTextNd->CopyText( pTextNd, SwIndex( pCpyTextNd ), + pCpyTextNd->GetText().getLength(), true ); + + if( RES_CONDTXTFMTCOLL == pColl->Which() ) + pTextNd->ChkCondColl(); + + return pTextNd; +} + +static bool lcl_SrchNew( const MapTableFrameFormat& rMap, SwFrameFormat** pPara ) +{ + if( rMap.pOld != *pPara ) + return true; + *pPara = rMap.pNew; + return false; +} + +namespace { + +struct CopyTable +{ + SwDoc* m_pDoc; + sal_uLong m_nOldTableSttIdx; + MapTableFrameFormats& m_rMapArr; + SwTableLine* m_pInsLine; + SwTableBox* m_pInsBox; + SwTableNode *m_pTableNd; + const SwTable *m_pOldTable; + + CopyTable(SwDoc* pDc, MapTableFrameFormats& rArr, sal_uLong nOldStt, + SwTableNode& rTableNd, const SwTable* pOldTable) + : m_pDoc(pDc), m_nOldTableSttIdx(nOldStt), m_rMapArr(rArr), + m_pInsLine(nullptr), m_pInsBox(nullptr), m_pTableNd(&rTableNd), m_pOldTable(pOldTable) + {} +}; + +} + +static void lcl_CopyTableLine( const SwTableLine* pLine, CopyTable* pCT ); + +static void lcl_CopyTableBox( SwTableBox* pBox, CopyTable* pCT ) +{ + SwTableBoxFormat * pBoxFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + for (const auto& rMap : pCT->m_rMapArr) + if ( !lcl_SrchNew( rMap, reinterpret_cast<SwFrameFormat**>(&pBoxFormat) ) ) + break; + + if (pBoxFormat == pBox->GetFrameFormat()) // Create a new one? + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_FORMULA, false, + &pItem ) && static_cast<const SwTableBoxFormula*>(pItem)->IsIntrnlName() ) + { + const_cast<SwTableBoxFormula*>(static_cast<const SwTableBoxFormula*>(pItem))->PtrToBoxNm(pCT->m_pOldTable); + } + + pBoxFormat = pCT->m_pDoc->MakeTableBoxFormat(); + pBoxFormat->CopyAttrs( *pBox->GetFrameFormat() ); + + if( pBox->GetSttIdx() ) + { + SvNumberFormatter* pN = pCT->m_pDoc->GetNumberFormatter(false); + if( pN && pN->HasMergeFormatTable() && SfxItemState::SET == pBoxFormat-> + GetItemState( RES_BOXATR_FORMAT, false, &pItem ) ) + { + sal_uLong nOldIdx = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + sal_uLong nNewIdx = pN->GetMergeFormatIndex( nOldIdx ); + if( nNewIdx != nOldIdx ) + pBoxFormat->SetFormatAttr( SwTableBoxNumFormat( nNewIdx )); + + } + } + + pCT->m_rMapArr.emplace_back(pBox->GetFrameFormat(), pBoxFormat); + } + + sal_uInt16 nLines = pBox->GetTabLines().size(); + SwTableBox* pNewBox; + if( nLines ) + pNewBox = new SwTableBox(pBoxFormat, nLines, pCT->m_pInsLine); + else + { + SwNodeIndex aNewIdx(*pCT->m_pTableNd, pBox->GetSttIdx() - pCT->m_nOldTableSttIdx); + assert(aNewIdx.GetNode().IsStartNode() && "Index is not on the start node"); + + pNewBox = new SwTableBox(pBoxFormat, aNewIdx, pCT->m_pInsLine); + pNewBox->setRowSpan( pBox->getRowSpan() ); + } + + pCT->m_pInsLine->GetTabBoxes().push_back( pNewBox ); + + if (nLines) + { + CopyTable aPara(*pCT); + aPara.m_pInsBox = pNewBox; + for( const SwTableLine* pLine : pBox->GetTabLines() ) + lcl_CopyTableLine( pLine, &aPara ); + } + else if (pNewBox->IsInHeadline(&pCT->m_pTableNd->GetTable())) + { + // In the headline, the paragraphs must match conditional styles + pNewBox->GetSttNd()->CheckSectionCondColl(); + } +} + +static void lcl_CopyTableLine( const SwTableLine* pLine, CopyTable* pCT ) +{ + SwTableLineFormat * pLineFormat = static_cast<SwTableLineFormat*>(pLine->GetFrameFormat()); + for (const auto& rMap : pCT->m_rMapArr) + if ( !lcl_SrchNew( rMap, reinterpret_cast<SwFrameFormat**>(&pLineFormat) ) ) + break; + + if( pLineFormat == pLine->GetFrameFormat() ) // Create a new one? + { + pLineFormat = pCT->m_pDoc->MakeTableLineFormat(); + pLineFormat->CopyAttrs( *pLine->GetFrameFormat() ); + pCT->m_rMapArr.emplace_back(pLine->GetFrameFormat(), pLineFormat); + } + + SwTableLine* pNewLine = new SwTableLine(pLineFormat, pLine->GetTabBoxes().size(), pCT->m_pInsBox); + // Insert the new row into the table + if (pCT->m_pInsBox) + { + pCT->m_pInsBox->GetTabLines().push_back(pNewLine); + } + else + { + pCT->m_pTableNd->GetTable().GetTabLines().push_back(pNewLine); + } + + pCT->m_pInsLine = pNewLine; + for( auto& rpBox : const_cast<SwTableLine*>(pLine)->GetTabBoxes() ) + lcl_CopyTableBox(rpBox, pCT); +} + +SwTableNode* SwTableNode::MakeCopy( SwDoc* pDoc, const SwNodeIndex& rIdx ) const +{ + // In which array are we? Nodes? UndoNodes? + SwNodes& rNds = const_cast<SwNodes&>(GetNodes()); + + { + if( rIdx < pDoc->GetNodes().GetEndOfInserts().GetIndex() && + rIdx >= pDoc->GetNodes().GetEndOfInserts().StartOfSectionIndex() ) + return nullptr; + } + + // Copy the TableFrameFormat + OUString sTableName( GetTable().GetFrameFormat()->GetName() ); + if( !pDoc->IsCopyIsMove() ) + { + const SwFrameFormats& rTableFormats = *pDoc->GetTableFrameFormats(); + for( size_t n = rTableFormats.size(); n; ) + if( rTableFormats[ --n ]->GetName() == sTableName ) + { + sTableName = pDoc->GetUniqueTableName(); + break; + } + } + + SwFrameFormat* pTableFormat = pDoc->MakeTableFrameFormat( sTableName, pDoc->GetDfltFrameFormat() ); + pTableFormat->CopyAttrs( *GetTable().GetFrameFormat() ); + SwTableNode* pTableNd = new SwTableNode( rIdx ); + SwEndNode* pEndNd = new SwEndNode( rIdx, *pTableNd ); + SwNodeIndex aInsPos( *pEndNd ); + + SwTable& rTable = pTableNd->GetTable(); + rTable.RegisterToFormat( *pTableFormat ); + + rTable.SetRowsToRepeat( GetTable().GetRowsToRepeat() ); + rTable.SetTableChgMode( GetTable().GetTableChgMode() ); + rTable.SetTableModel( GetTable().IsNewModel() ); + + SwDDEFieldType* pDDEType = nullptr; + if( auto pSwDDETable = dynamic_cast<const SwDDETable*>( &GetTable() ) ) + { + // We're copying a DDE table + // Is the field type available in the new document? + pDDEType = const_cast<SwDDETable*>(pSwDDETable)->GetDDEFieldType(); + if( pDDEType->IsDeleted() ) + pDoc->getIDocumentFieldsAccess().InsDeletedFieldType( *pDDEType ); + else + pDDEType = static_cast<SwDDEFieldType*>(pDoc->getIDocumentFieldsAccess().InsertFieldType( *pDDEType )); + OSL_ENSURE( pDDEType, "unknown FieldType" ); + + // Swap the table pointers in the node + std::unique_ptr<SwDDETable> pNewTable(new SwDDETable( pTableNd->GetTable(), pDDEType )); + pTableNd->SetNewTable( std::move(pNewTable), false ); + } + // First copy the content of the tables, we will later assign the + // boxes/lines and create the frames + SwNodeRange aRg( *this, +1, *EndOfSectionNode() ); + + // If there is a table in this table, the table format for the outer table + // does not seem to be used, because the table does not have any contents yet + // (see IsUsed). Therefore the inner table gets the same name as the outer table. + // We have to make sure that the table node of the SwTable is accessible, even + // without any content in m_TabSortContentBoxes. #i26629# + pTableNd->GetTable().SetTableNode( pTableNd ); + rNds.Copy_( aRg, aInsPos, false ); + pTableNd->GetTable().SetTableNode( nullptr ); + + // Special case for a single box + if( 1 == GetTable().GetTabSortBoxes().size() ) + { + aRg.aStart.Assign( *pTableNd, 1 ); + aRg.aEnd.Assign( *pTableNd->EndOfSectionNode() ); + pDoc->GetNodes().SectionDown( &aRg, SwTableBoxStartNode ); + } + + // Delete all frames from the copied area, they will be created + // during the generation of the table frame + pTableNd->DelFrames(); + + MapTableFrameFormats aMapArr; + CopyTable aPara( pDoc, aMapArr, GetIndex(), *pTableNd, &GetTable() ); + + for( const SwTableLine* pLine : GetTable().GetTabLines() ) + lcl_CopyTableLine( pLine, &aPara ); + + if( pDDEType ) + pDDEType->IncRefCnt(); + + CHECK_TABLE( GetTable() ); + return pTableNd; +} + +void SwTextNode::CopyCollFormat( SwTextNode& rDestNd ) +{ + // Copy the formats into the other document: + // Special case for PageBreak/PageDesc/ColBrk + SwDoc* pDestDoc = rDestNd.GetDoc(); + SwAttrSet aPgBrkSet( pDestDoc->GetAttrPool(), aBreakSetRange ); + const SwAttrSet* pSet; + + if( nullptr != ( pSet = rDestNd.GetpSwAttrSet() ) ) + { + // Special cases for Break-Attributes + const SfxPoolItem* pAttr; + if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pAttr ) ) + aPgBrkSet.Put( *pAttr ); + + if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pAttr ) ) + aPgBrkSet.Put( *pAttr ); + } + + rDestNd.ChgFormatColl( pDestDoc->CopyTextColl( *GetTextColl() )); + if( nullptr != ( pSet = GetpSwAttrSet() ) ) + pSet->CopyToModify( rDestNd ); + + if( aPgBrkSet.Count() ) + rDestNd.SetAttr( aPgBrkSet ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndnotxt.cxx b/sw/source/core/docnode/ndnotxt.cxx new file mode 100644 index 000000000..9029f9348 --- /dev/null +++ b/sw/source/core/docnode/ndnotxt.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 <hintids.hxx> +#include <tools/poly.hxx> +#include <svl/stritem.hxx> +#include <svx/contdlg.hxx> +#include <vcl/svapp.hxx> +#include <doc.hxx> +#include <fmtcol.hxx> +#include <ndnotxt.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <ndindex.hxx> +#include <istyleaccess.hxx> +#include <SwStyleNameMapper.hxx> + +#include <frmfmt.hxx> + +SwNoTextNode::SwNoTextNode( const SwNodeIndex & rWhere, + const SwNodeType nNdType, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwContentNode( rWhere, nNdType, pGrfColl ), + m_bAutomaticContour( false ), + m_bContourMapModeValid( true ), + m_bPixelContour( false ) +{ + // Should this set a hard attribute? + if( pAutoAttr ) + SetAttr( *pAutoAttr ); +} + +SwNoTextNode::~SwNoTextNode() +{ +} + +/// Creates an AttrSet for all derivations with ranges for frame- +/// and graphics-attributes. +void SwNoTextNode::NewAttrSet( SwAttrPool& rPool ) +{ + OSL_ENSURE( !mpAttrSet, "AttrSet is already set" ); + SwAttrSet aNewAttrSet( rPool, aNoTextNodeSetRange ); + + // put names of parent style and conditional style: + const SwFormatColl* pFormatColl = GetFormatColl(); + OUString sVal; + SwStyleNameMapper::FillProgName( pFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + SfxStringItem aFormatColl( RES_FRMATR_STYLE_NAME, sVal ); + aNewAttrSet.Put( aFormatColl ); + + aNewAttrSet.SetParent( &GetFormatColl()->GetAttrSet() ); + mpAttrSet = GetDoc()->GetIStyleAccess().getAutomaticStyle( aNewAttrSet, IStyleAccess::AUTO_STYLE_NOTXT ); +} + +/// Dummies for loading/saving of persistent data +/// when working with graphics and OLE objects +bool SwNoTextNode::RestorePersistentData() +{ + return true; +} + +bool SwNoTextNode::SavePersistentData() +{ + return true; +} + +void SwNoTextNode::SetContour( const tools::PolyPolygon *pPoly, bool bAutomatic ) +{ + if ( pPoly ) + m_pContour.reset( new tools::PolyPolygon( *pPoly ) ); + else + m_pContour.reset(); + m_bAutomaticContour = bAutomatic; + m_bContourMapModeValid = true; + m_bPixelContour = false; +} + +void SwNoTextNode::CreateContour() +{ + OSL_ENSURE( !m_pContour, "Contour available." ); + m_pContour.reset( new tools::PolyPolygon(SvxContourDlg::CreateAutoContour(GetGraphic())) ); + m_bAutomaticContour = true; + m_bContourMapModeValid = true; + m_bPixelContour = false; +} + +const tools::PolyPolygon *SwNoTextNode::HasContour() const +{ + if( !m_bContourMapModeValid ) + { + const MapMode aGrfMap( GetGraphic().GetPrefMapMode() ); + bool bPixelGrf = aGrfMap.GetMapUnit() == MapUnit::MapPixel; + const MapMode aContourMap( bPixelGrf ? MapUnit::MapPixel : MapUnit::Map100thMM ); + if( bPixelGrf ? !m_bPixelContour : aGrfMap != aContourMap ) + { + double nGrfDPIx = 0.0; + double nGrfDPIy = 0.0; + { + if ( !bPixelGrf && m_bPixelContour ) + { + basegfx::B2DSize aDPI = GetGraphic().GetPPI(); + nGrfDPIx = aDPI.getX(); + nGrfDPIy = aDPI.getY(); + } + } + OSL_ENSURE( !bPixelGrf || aGrfMap == aContourMap, + "scale factor for pixel unsupported" ); + OutputDevice* pOutDev = + (bPixelGrf || m_bPixelContour) ? Application::GetDefaultDevice() + : nullptr; + sal_uInt16 nPolyCount = m_pContour->Count(); + for( sal_uInt16 j=0; j<nPolyCount; j++ ) + { + tools::Polygon& rPoly = (*m_pContour)[j]; + + sal_uInt16 nCount = rPoly.GetSize(); + for( sal_uInt16 i=0 ; i<nCount; i++ ) + { + if( bPixelGrf ) + rPoly[i] = pOutDev->LogicToPixel( rPoly[i], + aContourMap ); + else if( m_bPixelContour ) + { + rPoly[i] = pOutDev->PixelToLogic( rPoly[i], aGrfMap ); + + if ( nGrfDPIx != 0 && nGrfDPIy != 0 ) + { + rPoly[i] = Point( rPoly[i].getX() * pOutDev->GetDPIX() / nGrfDPIx, + rPoly[i].getY() * pOutDev->GetDPIY() / nGrfDPIy ); + } + } + else + rPoly[i] = OutputDevice::LogicToLogic( rPoly[i], + aContourMap, + aGrfMap ); + } + } + } + const_cast<SwNoTextNode *>(this)->m_bContourMapModeValid = true; + const_cast<SwNoTextNode *>(this)->m_bPixelContour = false; + } + + return m_pContour.get(); +} + +void SwNoTextNode::GetContour( tools::PolyPolygon &rPoly ) const +{ + OSL_ENSURE( m_pContour, "Contour not available." ); + rPoly = *HasContour(); +} + +void SwNoTextNode::SetContourAPI( const tools::PolyPolygon *pPoly ) +{ + if ( pPoly ) + m_pContour.reset( new tools::PolyPolygon( *pPoly ) ); + else + m_pContour.reset(); + m_bContourMapModeValid = false; +} + +bool SwNoTextNode::GetContourAPI( tools::PolyPolygon &rContour ) const +{ + if( !m_pContour ) + return false; + + rContour = *m_pContour; + if( m_bContourMapModeValid ) + { + const MapMode aGrfMap( GetGraphic().GetPrefMapMode() ); + const MapMode aContourMap( MapUnit::Map100thMM ); + OSL_ENSURE( aGrfMap.GetMapUnit() != MapUnit::MapPixel || + aGrfMap == MapMode( MapUnit::MapPixel ), + "scale factor for pixel unsupported" ); + if( aGrfMap.GetMapUnit() != MapUnit::MapPixel && + aGrfMap != aContourMap ) + { + sal_uInt16 nPolyCount = rContour.Count(); + for( sal_uInt16 j=0; j<nPolyCount; j++ ) + { + tools::Polygon& rPoly = rContour[j]; + + sal_uInt16 nCount = rPoly.GetSize(); + for( sal_uInt16 i=0 ; i<nCount; i++ ) + { + rPoly[i] = OutputDevice::LogicToLogic( rPoly[i], aGrfMap, + aContourMap ); + } + } + } + } + + return true; +} + +bool SwNoTextNode::IsPixelContour() const +{ + bool bRet; + if( m_bContourMapModeValid ) + { + const MapMode aGrfMap( GetGraphic().GetPrefMapMode() ); + bRet = aGrfMap.GetMapUnit() == MapUnit::MapPixel; + } + else + { + bRet = m_bPixelContour; + } + + return bRet; +} + +Graphic SwNoTextNode::GetGraphic() const +{ + Graphic aRet; + if ( GetGrfNode() ) + { + aRet = static_cast<const SwGrfNode*>(this)->GetGrf(true); + } + else + { + OSL_ENSURE( GetOLENode(), "new type of Node?" ); + aRet = *const_cast<SwOLENode*>(static_cast<const SwOLENode*>(this))->SwOLENode::GetGraphic(); + } + return aRet; +} + +// #i73249# +void SwNoTextNode::SetTitle( const OUString& rTitle ) +{ + // Title attribute of <SdrObject> replaces own AlternateText attribute + SwFlyFrameFormat* pFlyFormat = dynamic_cast<SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::SetTitle(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return; + } + + pFlyFormat->SetObjTitle( rTitle ); +} + +OUString SwNoTextNode::GetTitle() const +{ + const SwFlyFrameFormat* pFlyFormat = dynamic_cast<const SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::GetTitle(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return OUString(); + } + + return pFlyFormat->GetObjTitle(); +} + +void SwNoTextNode::SetDescription( const OUString& rDescription ) +{ + SwFlyFrameFormat* pFlyFormat = dynamic_cast<SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::SetDescription(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return; + } + + pFlyFormat->SetObjDescription( rDescription ); +} + +OUString SwNoTextNode::GetDescription() const +{ + const SwFlyFrameFormat* pFlyFormat = dynamic_cast<const SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::GetDescription(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return OUString(); + } + + return pFlyFormat->GetObjDescription(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndnum.cxx b/sw/source/core/docnode/ndnum.cxx new file mode 100644 index 000000000..1e8e75256 --- /dev/null +++ b/sw/source/core/docnode/ndnum.cxx @@ -0,0 +1,96 @@ +/* -*- 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 <node.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <ndtxt.hxx> +#include <fldbas.hxx> +#include <osl/diagnose.h> + +bool CompareSwOutlineNodes::operator()( SwNode* const& lhs, SwNode* const& rhs) const +{ + return lhs->GetIndex() < rhs->GetIndex(); +} + +bool SwOutlineNodes::Seek_Entry(SwNode* rP, size_type* pnPos) const +{ + const_iterator it = lower_bound(rP); + *pnPos = it - begin(); + return it != end() && rP->GetIndex() == (*it)->GetIndex(); +} + +void SwNodes::UpdateOutlineNode(SwNode & rNd) +{ + SwTextNode * pTextNd = rNd.GetTextNode(); + + if (pTextNd && pTextNd->IsOutlineStateChanged()) + { + bool bFound = m_pOutlineNodes->find(pTextNd) != m_pOutlineNodes->end(); + + if (pTextNd->IsOutline()) + { + if (! bFound) + { + // assure that text is in the correct nodes array + if ( &(pTextNd->GetNodes()) == this ) + { + m_pOutlineNodes->insert(pTextNd); + } + else + { + OSL_FAIL( "<SwNodes::UpdateOutlineNode(..)> - given text node isn't in the correct nodes array. This is a serious defect" ); + } + } + } + else + { + if (bFound) + m_pOutlineNodes->erase(pTextNd); + } + + pTextNd->UpdateOutlineState(); + + // update the structure fields + GetDoc()->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Chapter )->UpdateFields(); + } +} + +void SwNodes::UpdateOutlineIdx( const SwNode& rNd ) +{ + if( m_pOutlineNodes->empty() ) // no OutlineNodes present ? + return; + + const SwNodePtr pSrch = const_cast<SwNodePtr>(&rNd); + + SwOutlineNodes::size_type nPos; + if (!m_pOutlineNodes->Seek_Entry(pSrch, &nPos)) + return; + if( nPos == m_pOutlineNodes->size() ) // none present for updating ? + return; + + if( nPos ) + --nPos; + + if( !GetDoc()->IsInDtor() && IsDocNodes() ) + UpdateOutlineNode( *(*m_pOutlineNodes)[ nPos ]); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndsect.cxx b/sw/source/core/docnode/ndsect.cxx new file mode 100644 index 000000000..eeac5d109 --- /dev/null +++ b/sw/source/core/docnode/ndsect.cxx @@ -0,0 +1,1422 @@ +/* -*- 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 <hintids.hxx> +#include <sfx2/linkmgr.hxx> +#include <svl/itemiter.hxx> +#include <sal/log.hxx> +#include <fmtcntnt.hxx> +#include <txtftn.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <rootfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <section.hxx> +#include <UndoSection.hxx> +#include <UndoDelete.hxx> +#include <swundo.hxx> +#include <calc.hxx> +#include <swtable.hxx> +#include <swserv.hxx> +#include <frmfmt.hxx> +#include <frmtool.hxx> +#include <ftnidx.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <sectfrm.hxx> +#include <cntfrm.hxx> +#include <node2lay.hxx> +#include <doctxm.hxx> +#include <fmtftntx.hxx> +#include <strings.hrc> +#include <viewsh.hxx> +#include <txtfrm.hxx> +#include <hints.hxx> +#include <memory> +#include "ndsect.hxx" +#include <tools/datetimeutils.hxx> + +// #i21457# - new implementation of local method <lcl_IsInSameTableBox(..)>. +// Method now determines the previous/next on its own. Thus, it can be controlled, +// for which previous/next is checked, if it's visible. +static bool lcl_IsInSameTableBox( SwNodes const & _rNds, + const SwNode& _rNd, + const bool _bPrev ) +{ + const SwTableNode* pTableNd = _rNd.FindTableNode(); + if ( !pTableNd ) + { + return true; + } + + // determine index to be checked. Its assumed that a previous/next exist. + SwNodeIndex aChkIdx( _rNd ); + { + // determine index of previous/next - skip hidden ones, which are + // inside the table. + // If found one is before/after table, this one isn't in the same + // table box as <_rNd>. + bool bFound = false; + do + { + if ( _bPrev + ? !SwNodes::GoPrevSection( &aChkIdx, false, false ) + : !_rNds.GoNextSection( &aChkIdx, false, false ) ) + { + OSL_FAIL( "<lcl_IsInSameTableBox(..)> - no previous/next!" ); + return false; + } + else + { + if ( aChkIdx < pTableNd->GetIndex() || + aChkIdx > pTableNd->EndOfSectionNode()->GetIndex() ) + { + return false; + } + else + { + // check, if found one isn't inside a hidden section, which + // is also inside the table. + SwSectionNode* pSectNd = aChkIdx.GetNode().FindSectionNode(); + if ( !pSectNd || + pSectNd->GetIndex() < pTableNd->GetIndex() || + !pSectNd->GetSection().IsHiddenFlag() ) + { + bFound = true; + } + } + } + } while ( !bFound ); + } + + // Find the Box's StartNode + const SwTableSortBoxes& rSortBoxes = pTableNd->GetTable().GetTabSortBoxes(); + sal_uLong nIdx = _rNd.GetIndex(); + for (size_t n = 0; n < rSortBoxes.size(); ++n) + { + const SwStartNode* pNd = rSortBoxes[ n ]->GetSttNd(); + if ( pNd->GetIndex() < nIdx && nIdx < pNd->EndOfSectionIndex() ) + { + // The other index needs to be within the same Section + nIdx = aChkIdx.GetIndex(); + return pNd->GetIndex() < nIdx && nIdx < pNd->EndOfSectionIndex(); + } + } + + return true; +} + +static void lcl_CheckEmptyLayFrame( SwNodes const & rNds, SwSectionData& rSectionData, + const SwNode& rStt, const SwNode& rEnd ) +{ + SwNodeIndex aIdx( rStt ); + if( !SwNodes::GoPrevSection( &aIdx, true, false ) || + !CheckNodesRange( rStt, aIdx, true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, rStt, true )) + { + aIdx = rEnd; + if( !rNds.GoNextSection( &aIdx, true, false ) || + !CheckNodesRange( rEnd, aIdx, true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, rEnd, false )) + { + rSectionData.SetHidden( false ); + } + } +} + +SwSection * +SwDoc::InsertSwSection(SwPaM const& rRange, SwSectionData & rNewData, + std::pair<SwTOXBase const*, sw::RedlineMode> const*const pTOXBaseAndMode, + SfxItemSet const*const pAttr, bool const bUpdate) +{ + const SwNode* pPrvNd = nullptr; + sal_uInt16 nRegionRet = 0; + if( rRange.HasMark() ) + { + nRegionRet = IsInsRegionAvailable( rRange, &pPrvNd ); + if( 0 == nRegionRet ) + { + // demoted to info because this is called from SwXTextSection::attach, + // so it could be invalid input + SAL_INFO("sw.core" , "InsertSwSection: rRange overlaps other sections"); + return nullptr; + } + } + + // See if the whole Document should be hidden, which we currently are not able to do. + if (rNewData.IsHidden() && rRange.HasMark()) + { + const SwPosition *pStt = rRange.Start(), *pEnd = rRange.End(); + if( !pStt->nContent.GetIndex() && + pEnd->nNode.GetNode().GetContentNode()->Len() == + pEnd->nContent.GetIndex() ) + { + ::lcl_CheckEmptyLayFrame( GetNodes(), + rNewData, + pStt->nNode.GetNode(), + pEnd->nNode.GetNode() ); + } + } + + SwUndoInsSection* pUndoInsSect = nullptr; + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + pUndoInsSect = new SwUndoInsSection(rRange, rNewData, pAttr, pTOXBaseAndMode); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndoInsSect) ); + GetIDocumentUndoRedo().DoUndo(false); + } + + SwSectionFormat* const pFormat = MakeSectionFormat(); + pFormat->SetName(rNewData.GetSectionName()); + if ( pAttr ) + { + pFormat->SetFormatAttr( *pAttr ); + } + + SwTOXBase const*const pTOXBase(pTOXBaseAndMode ? pTOXBaseAndMode->first : nullptr); + SwSectionNode* pNewSectNode = nullptr; + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + getIDocumentRedlineAccess().SetRedlineFlags_intern( (eOld & ~RedlineFlags::ShowMask) | RedlineFlags::Ignore ); + + if( rRange.HasMark() ) + { + SwPosition *pSttPos = const_cast<SwPosition*>(rRange.Start()), + *pEndPos = const_cast<SwPosition*>(rRange.End()); + if( pPrvNd && 3 == nRegionRet ) + { + OSL_ENSURE( pPrvNd, "The SectionNode is missing" ); + SwNodeIndex aStt( pSttPos->nNode ), aEnd( pEndPos->nNode, +1 ); + while( pPrvNd != aStt.GetNode().StartOfSectionNode() ) + --aStt; + while( pPrvNd != aEnd.GetNode().StartOfSectionNode() ) + ++aEnd; + + --aEnd; // End is inclusive in the InsertSection + pNewSectNode = GetNodes().InsertTextSection( + aStt, *pFormat, rNewData, pTOXBase, & aEnd); + } + else + { + if( pUndoInsSect ) + { + if( !( pPrvNd && 1 == nRegionRet ) && + pSttPos->nContent.GetIndex() ) + { + SwTextNode* const pTNd = + pSttPos->nNode.GetNode().GetTextNode(); + if (pTNd) + { + pUndoInsSect->SaveSplitNode( pTNd, true ); + } + } + + if ( !( pPrvNd && 2 == nRegionRet ) ) + { + SwTextNode *const pTNd = + pEndPos->nNode.GetNode().GetTextNode(); + if (pTNd && (pTNd->GetText().getLength() + != pEndPos->nContent.GetIndex())) + { + pUndoInsSect->SaveSplitNode( pTNd, false ); + } + } + } + + if( pPrvNd && 1 == nRegionRet ) + { + pSttPos->nNode.Assign( *pPrvNd ); + pSttPos->nContent.Assign( pSttPos->nNode.GetNode().GetContentNode(), 0 ); + } + else if( pSttPos->nContent.GetIndex() ) + { + getIDocumentContentOperations().SplitNode( *pSttPos, false ); + } + + if( pPrvNd && 2 == nRegionRet ) + { + pEndPos->nNode.Assign( *pPrvNd ); + pEndPos->nContent.Assign( pEndPos->nNode.GetNode().GetContentNode(), 0 ); + } + else + { + const SwContentNode* pCNd = pEndPos->nNode.GetNode().GetContentNode(); + if( pCNd && pCNd->Len() != pEndPos->nContent.GetIndex() ) + { + sal_Int32 nContent = pSttPos->nContent.GetIndex(); + getIDocumentContentOperations().SplitNode( *pEndPos, false ); + + SwTextNode* pTNd; + if( pEndPos->nNode.GetIndex() == pSttPos->nNode.GetIndex() ) + { + --pSttPos->nNode; + --pEndPos->nNode; + pTNd = pSttPos->nNode.GetNode().GetTextNode(); + pSttPos->nContent.Assign( pTNd, nContent ); + } + else + { + // Set to the end of the previous + --pEndPos->nNode; + pTNd = pEndPos->nNode.GetNode().GetTextNode(); + } + nContent = pTNd ? pTNd->GetText().getLength() : 0; + pEndPos->nContent.Assign( pTNd, nContent ); + } + } + pNewSectNode = GetNodes().InsertTextSection( + pSttPos->nNode, *pFormat, rNewData, pTOXBase, &pEndPos->nNode); + } + } + else + { + const SwPosition* pPos = rRange.GetPoint(); + const SwContentNode* pCNd = pPos->nNode.GetNode().GetContentNode(); + if( !pPos->nContent.GetIndex() ) + { + pNewSectNode = GetNodes().InsertTextSection( + pPos->nNode, *pFormat, rNewData, pTOXBase, nullptr); + } + else if( pPos->nContent.GetIndex() == pCNd->Len() ) + { + pNewSectNode = GetNodes().InsertTextSection( + pPos->nNode, *pFormat, rNewData, pTOXBase, nullptr, false); + } + else + { + if( pUndoInsSect && pCNd->IsTextNode() ) + { + pUndoInsSect->SaveSplitNode( const_cast<SwTextNode*>(static_cast<const SwTextNode*>(pCNd)), true ); + } + getIDocumentContentOperations().SplitNode( *pPos, false ); + pNewSectNode = GetNodes().InsertTextSection( + pPos->nNode, *pFormat, rNewData, pTOXBase, nullptr); + } + } + +//FEATURE::CONDCOLL + pNewSectNode->CheckSectionCondColl(); +//FEATURE::CONDCOLL + + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( *pNewSectNode->EndOfSectionNode(), *pNewSectNode, 1 ); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + { + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + else + { + getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + + // Is a Condition set? + if (rNewData.IsHidden() && !rNewData.GetCondition().isEmpty()) + { + // The calculate up to that position + SwCalc aCalc( *this ); + if( ! IsInReading() ) + { + getIDocumentFieldsAccess().FieldsToCalc( aCalc, pNewSectNode->GetIndex(), USHRT_MAX ); + } + SwSection& rNewSect = pNewSectNode->GetSection(); + rNewSect.SetCondHidden( aCalc.Calculate( rNewSect.GetCondition() ).GetBool() ); + } + + bool bUpdateFootnote = false; + if( !GetFootnoteIdxs().empty() && pAttr ) + { + sal_uInt16 nVal = pAttr->Get( RES_FTN_AT_TXTEND ).GetValue(); + if( ( FTNEND_ATTXTEND_OWNNUMSEQ == nVal || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal ) || + ( FTNEND_ATTXTEND_OWNNUMSEQ == ( nVal = pAttr->Get( RES_END_AT_TXTEND ).GetValue() ) || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal )) + { + bUpdateFootnote = true; + } + } + + if( pUndoInsSect ) + { + pUndoInsSect->SetSectNdPos( pNewSectNode->GetIndex() ); + pUndoInsSect->SetUpdateFootnoteFlag( bUpdateFootnote ); + GetIDocumentUndoRedo().DoUndo(bUndo); + } + + if (rNewData.IsLinkType()) + { + pNewSectNode->GetSection().CreateLink( bUpdate ? LinkCreateType::Update : LinkCreateType::Connect ); + } + + if( bUpdateFootnote ) + { + GetFootnoteIdxs().UpdateFootnote( SwNodeIndex( *pNewSectNode )); + } + + getIDocumentState().SetModified(); + return &pNewSectNode->GetSection(); +} + +sal_uInt16 SwDoc::IsInsRegionAvailable( const SwPaM& rRange, + const SwNode** ppSttNd ) +{ + sal_uInt16 nRet = 1; + if( rRange.HasMark() ) + { + // See if we have a valid Section + const SwPosition* pStt = rRange.Start(); + const SwPosition* pEnd = rRange.End(); + + const SwContentNode* pCNd = pEnd->nNode.GetNode().GetContentNode(); + const SwNode* pNd = &pStt->nNode.GetNode(); + const SwSectionNode* pSectNd = pNd->FindSectionNode(); + const SwSectionNode* pEndSectNd = pCNd ? pCNd->FindSectionNode() : nullptr; + if( pSectNd && pEndSectNd && pSectNd != pEndSectNd ) + { + // Try to create an enclosing Section, but only if Start is + // located at the Section's beginning and End at it's end + nRet = 0; + if( !pStt->nContent.GetIndex() + && pSectNd->GetIndex() == pStt->nNode.GetIndex() - 1 + && pEnd->nContent.GetIndex() == pCNd->Len() ) + { + SwNodeIndex aIdx( pStt->nNode, -1 ); + sal_uLong nCmp = pEnd->nNode.GetIndex(); + const SwStartNode* pPrvNd; + const SwEndNode* pNxtNd; + while( nullptr != ( pPrvNd = (pNd = &aIdx.GetNode())->GetSectionNode() ) && + !( aIdx.GetIndex() < nCmp && + nCmp < pPrvNd->EndOfSectionIndex() ) ) + { + --aIdx; + } + if( !pPrvNd ) + pPrvNd = pNd->IsStartNode() ? static_cast<const SwStartNode*>(pNd) + : pNd->StartOfSectionNode(); + + aIdx = pEnd->nNode.GetIndex() + 1; + nCmp = pStt->nNode.GetIndex(); + while( nullptr != ( pNxtNd = (pNd = &aIdx.GetNode())->GetEndNode() ) && + pNxtNd->StartOfSectionNode()->IsSectionNode() && + !( pNxtNd->StartOfSectionIndex() < nCmp && + nCmp < aIdx.GetIndex() ) ) + { + ++aIdx; + } + if( !pNxtNd ) + pNxtNd = pNd->EndOfSectionNode(); + + if( pPrvNd && pNxtNd && pPrvNd == pNxtNd->StartOfSectionNode() ) + { + nRet = 3; + + if( ppSttNd ) + *ppSttNd = pPrvNd; + } + } + } + else if( !pSectNd && pEndSectNd ) + { + // Try to create an enclosing Section, but only if the End + // is at the Section's end. + nRet = 0; + if( pEnd->nContent.GetIndex() == pCNd->Len() ) + { + SwNodeIndex aIdx( pEnd->nNode, 1 ); + if( aIdx.GetNode().IsEndNode() && + nullptr != aIdx.GetNode().FindSectionNode() ) + { + do { + ++aIdx; + } while( aIdx.GetNode().IsEndNode() && + nullptr != aIdx.GetNode().FindSectionNode() ); + { + nRet = 2; + if( ppSttNd ) + { + --aIdx; + *ppSttNd = &aIdx.GetNode(); + } + } + } + } + } + else if( pSectNd && !pEndSectNd ) + { + // Try to create an enclosing Section, but only if Start + // is at the Section's start. + nRet = 0; + if( !pStt->nContent.GetIndex() ) + { + SwNodeIndex aIdx( pStt->nNode, -1 ); + if( aIdx.GetNode().IsSectionNode() ) + { + do { + --aIdx; + } while( aIdx.GetNode().IsSectionNode() ); + if( !aIdx.GetNode().IsSectionNode() ) + { + nRet = 1; + if( ppSttNd ) + { + ++aIdx; + *ppSttNd = &aIdx.GetNode(); + } + } + } + } + } + } + return nRet; +} + +SwSection* SwDoc::GetCurrSection( const SwPosition& rPos ) +{ + const SwSectionNode* pSectNd = rPos.nNode.GetNode().FindSectionNode(); + if( pSectNd ) + return const_cast<SwSection*>(&pSectNd->GetSection()); + return nullptr; +} + +SwSectionFormat* SwDoc::MakeSectionFormat() +{ + SwSectionFormat* pNew = new SwSectionFormat( mpDfltFrameFormat.get(), this ); + mpSectionFormatTable->push_back( pNew ); + return pNew; +} + +void SwDoc::DelSectionFormat( SwSectionFormat *pFormat, bool bDelNodes ) +{ + SwSectionFormats::iterator itFormatPos = std::find( mpSectionFormatTable->begin(), mpSectionFormatTable->end(), pFormat ); + + GetIDocumentUndoRedo().StartUndo(SwUndoId::DELSECTION, nullptr); + + if( mpSectionFormatTable->end() != itFormatPos ) + { + const SwNodeIndex* pIdx = pFormat->GetContent( false ).GetContentIdx(); + const SfxPoolItem* pFootnoteEndAtTextEnd; + if( SfxItemState::SET != pFormat->GetItemState( + RES_FTN_AT_TXTEND, true, &pFootnoteEndAtTextEnd ) || + SfxItemState::SET != pFormat->GetItemState( + RES_END_AT_TXTEND, true, &pFootnoteEndAtTextEnd )) + pFootnoteEndAtTextEnd = nullptr; + + const SwSectionNode* pSectNd; + + if( GetIDocumentUndoRedo().DoesUndo() ) + { + if( bDelNodes && pIdx && &GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwNodeIndex aUpdIdx( *pIdx ); + SwPaM aPaM( *pSectNd->EndOfSectionNode(), *pSectNd ); + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDelete>( aPaM )); + if( pFootnoteEndAtTextEnd ) + GetFootnoteIdxs().UpdateFootnote( aUpdIdx ); + getIDocumentState().SetModified(); + //#126178# start/end undo have to be pairs! + GetIDocumentUndoRedo().EndUndo(SwUndoId::DELSECTION, nullptr); + return ; + } + GetIDocumentUndoRedo().AppendUndo( MakeUndoDelSection( *pFormat ) ); + } + else if( bDelNodes && pIdx && &GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwNodeIndex aUpdIdx( *pIdx ); + getIDocumentContentOperations().DeleteSection( const_cast<SwNode*>(static_cast<SwNode const *>(pSectNd)) ); + if( pFootnoteEndAtTextEnd ) + GetFootnoteIdxs().UpdateFootnote( aUpdIdx ); + getIDocumentState().SetModified(); + //#126178# start/end undo have to be pairs! + GetIDocumentUndoRedo().EndUndo(SwUndoId::DELSECTION, nullptr); + return ; + } + + { + SwPtrMsgPoolItem aMsgHint( RES_REMOVE_UNO_OBJECT, pFormat ); + pFormat->ModifyNotification( &aMsgHint, &aMsgHint ); + } + + // A ClearRedo could result in a recursive call of this function and delete some section + // formats, thus the position inside the SectionFormatTable could have changed + itFormatPos = std::find( mpSectionFormatTable->begin(), mpSectionFormatTable->end(), pFormat ); + + // WARNING: First remove from the array and then delete, + // as the Section DTOR tries to delete it's format itself. + mpSectionFormatTable->erase( itFormatPos ); +//FEATURE::CONDCOLL + sal_uLong nCnt = 0, nSttNd = 0; + if( pIdx && &GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + nSttNd = pSectNd->GetIndex(); + nCnt = pSectNd->EndOfSectionIndex() - nSttNd - 1; + } +//FEATURE::CONDCOLL + + delete pFormat; + + if( nSttNd && pFootnoteEndAtTextEnd ) + { + SwNodeIndex aUpdIdx( GetNodes(), nSttNd ); + GetFootnoteIdxs().UpdateFootnote( aUpdIdx ); + } + +//FEATURE::CONDCOLL + SwContentNode* pCNd; + for( ; nCnt--; ++nSttNd ) + if( nullptr != (pCNd = GetNodes()[ nSttNd ]->GetContentNode() ) && + RES_CONDTXTFMTCOLL == pCNd->GetFormatColl()->Which() ) + pCNd->ChkCondColl(); +//FEATURE::CONDCOLL + } + + GetIDocumentUndoRedo().EndUndo(SwUndoId::DELSECTION, nullptr); + + if (GetIDocumentUndoRedo().DoesUndo()) + { // TODO is this ever needed? + getIDocumentState().SetModified(); + } +} + +void SwDoc::UpdateSection( size_t const nPos, SwSectionData & rNewData, + SfxItemSet const*const pAttr, bool const bPreventLinkUpdate ) +{ + SwSectionFormat* pFormat = (*mpSectionFormatTable)[ nPos ]; + SwSection* pSection = pFormat->GetSection(); + + /// remember hidden condition flag of SwSection before changes + bool bOldCondHidden = pSection->IsCondHidden(); + + if (pSection->DataEquals(rNewData)) + { + // Check Attributes + bool bOnlyAttrChg = false; + if( pAttr && pAttr->Count() ) + { + SfxItemIter aIter( *pAttr ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if (pFormat->GetFormatAttr(pItem->Which()) != *pItem) + { + bOnlyAttrChg = true; + break; + } + + pItem = aIter.NextItem(); + } while (pItem); + } + + if( bOnlyAttrChg ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + MakeUndoUpdateSection( *pFormat, true ) ); + } + // #i32968# Inserting columns in the section causes MakeFrameFormat + // to put two objects of type SwUndoFrameFormat on the undo stack. + // We don't want them. + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + pFormat->SetFormatAttr( *pAttr ); + getIDocumentState().SetModified(); + } + return; + } + + // Test if the whole Content Section (Document/TableBox/Fly) should be hidden, + // which we're currently not able to do. + const SwNodeIndex* pIdx = nullptr; + { + if (rNewData.IsHidden()) + { + pIdx = pFormat->GetContent().GetContentIdx(); + if (pIdx) + { + const SwSectionNode* pSectNd = + pIdx->GetNode().GetSectionNode(); + if (pSectNd) + { + ::lcl_CheckEmptyLayFrame( GetNodes(), rNewData, + *pSectNd, *pSectNd->EndOfSectionNode() ); + } + } + } + } + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(MakeUndoUpdateSection(*pFormat, false)); + } + // #i32968# Inserting columns in the section causes MakeFrameFormat to put two + // objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // The LinkFileName could only consist of separators + OUString sCompareString = OUStringChar(sfx2::cTokenSeparator) + OUStringChar(sfx2::cTokenSeparator); + const bool bUpdate = + (!pSection->IsLinkType() && rNewData.IsLinkType()) + || (!rNewData.GetLinkFileName().isEmpty() + && (rNewData.GetLinkFileName() != sCompareString) + && (rNewData.GetLinkFileName() != pSection->GetLinkFileName())); + + OUString sSectName( rNewData.GetSectionName() ); + if (sSectName != pSection->GetSectionName()) + sSectName = GetUniqueSectionName( &sSectName ); + else + sSectName.clear(); + + /// In SwSection::operator=(..) class member m_bCondHiddenFlag is always set to true. + /// IMHO this have to be changed, but I can't estimate the consequences: + /// Either it is set to true using corresponding method <SwSection.SetCondHidden(..)>, + /// or it is set to the value of SwSection which is assigned to it. + /// Discussion with AMA results that the adjustment to the assignment operator + /// could be very risky. + pSection->SetSectionData(rNewData); + + if( pAttr ) + pSection->GetFormat()->SetFormatAttr( *pAttr ); + + if( !sSectName.isEmpty() ) + { + pSection->SetSectionName( sSectName ); + } + + // Is a Condition set + if( pSection->IsHidden() && !pSection->GetCondition().isEmpty() ) + { + // Then calculate up to that position + SwCalc aCalc( *this ); + if( !pIdx ) + pIdx = pFormat->GetContent().GetContentIdx(); + getIDocumentFieldsAccess().FieldsToCalc( aCalc, pIdx->GetIndex(), USHRT_MAX ); + + /// Because on using SwSection::operator=() to set up <pSection> + /// with <rNewData> and the above given note, the hidden condition flag + /// has to be set to false, if hidden condition flag of <pFormat->GetSection()> + /// (SwSection before the changes) is false (already saved in <bOldCondHidden>) + /// and new calculated condition is true. + /// This is necessary, because otherwise the <SetCondHidden> would have + /// no effect. + bool bCalculatedCondHidden = + aCalc.Calculate( pSection->GetCondition() ).GetBool(); + if ( bCalculatedCondHidden && !bOldCondHidden ) + { + pSection->SetCondHidden( false ); + } + pSection->SetCondHidden( bCalculatedCondHidden ); + } + + if( bUpdate ) + pSection->CreateLink( bPreventLinkUpdate ? LinkCreateType::Connect : LinkCreateType::Update ); + else if( !pSection->IsLinkType() && pSection->IsConnected() ) + { + pSection->Disconnect(); + getIDocumentLinksAdministration().GetLinkManager().Remove( &pSection->GetBaseLink() ); + } + + getIDocumentState().SetModified(); +} + +void sw_DeleteFootnote( SwSectionNode *pNd, sal_uLong nStt, sal_uLong nEnd ) +{ + SwFootnoteIdxs& rFootnoteArr = pNd->GetDoc()->GetFootnoteIdxs(); + if( !rFootnoteArr.empty() ) + { + size_t nPos = 0; + rFootnoteArr.SeekEntry( SwNodeIndex( *pNd ), &nPos ); + SwTextFootnote* pSrch; + + // Delete all succeeding Footnotes + while( nPos < rFootnoteArr.size() && + SwTextFootnote_GetIndex( (pSrch = rFootnoteArr[ nPos ]) ) <= nEnd ) + { + // If the Nodes are not deleted, they need to deregister at the Pages + // (delete Frames) or else they will remain there (Undo does not delete them!) + pSrch->DelFrames(nullptr); + ++nPos; + } + + while( nPos-- && + SwTextFootnote_GetIndex( (pSrch = rFootnoteArr[ nPos ]) ) >= nStt ) + { + // If the Nodes are not deleted, they need to deregister at the Pages + // (delete Frames) or else they will remain there (Undo does not delete them!) + pSrch->DelFrames(nullptr); + } + } +} + +static bool lcl_IsTOXSection(SwSectionData const& rSectionData) +{ + return (SectionType::ToxContent == rSectionData.GetType()) + || (SectionType::ToxHeader == rSectionData.GetType()); +} + +SwSectionNode* SwNodes::InsertTextSection(SwNodeIndex const& rNdIdx, + SwSectionFormat& rSectionFormat, + SwSectionData const& rSectionData, + SwTOXBase const*const pTOXBase, + SwNodeIndex const*const pEnd, + bool const bInsAtStart, bool const bCreateFrames) +{ + SwNodeIndex aInsPos( rNdIdx ); + if( !pEnd ) // No Area, thus create a new Section before/after it + { + // #i26762# + OSL_ENSURE(!pEnd || rNdIdx <= *pEnd, + "Section start and end in wrong order!"); + + if( bInsAtStart ) + { + if (!lcl_IsTOXSection(rSectionData)) + { + do { + --aInsPos; + } while( aInsPos.GetNode().IsSectionNode() ); + ++aInsPos; + } + } + else + { + ++aInsPos; + if (!lcl_IsTOXSection(rSectionData)) + { + SwNode* pNd; + while( aInsPos.GetIndex() < Count() - 1 && + ( pNd = &aInsPos.GetNode())->IsEndNode() && + pNd->StartOfSectionNode()->IsSectionNode()) + { + ++aInsPos; + } + } + } + } + + SwSectionNode *const pSectNd = + new SwSectionNode(aInsPos, rSectionFormat, pTOXBase); + if( pEnd ) + { + // Special case for the Reader/Writer + if( &pEnd->GetNode() != &GetEndOfContent() ) + aInsPos = pEnd->GetIndex()+1; + // #i58710: We created a RTF document with a section break inside a table cell + // We are not able to handle a section start inside a table and the section end outside. + const SwNode* pLastNode = pSectNd->StartOfSectionNode()->EndOfSectionNode(); + if( aInsPos > pLastNode->GetIndex() ) + aInsPos = pLastNode->GetIndex(); + // Another way round: if the section starts outside a table but the end is inside... + // aInsPos is at the moment the Position where my EndNode will be inserted + const SwStartNode* pStartNode = aInsPos.GetNode().StartOfSectionNode(); + // This StartNode should be in front of me, but if not, I want to survive + sal_uLong nMyIndex = pSectNd->GetIndex(); + if( pStartNode->GetIndex() > nMyIndex ) // Suspicious! + { + const SwNode* pTemp; + do + { + pTemp = pStartNode; // pTemp is a suspicious one + pStartNode = pStartNode->StartOfSectionNode(); + } + while( pStartNode->GetIndex() > nMyIndex ); + pTemp = pTemp->EndOfSectionNode(); + // If it starts behind me but ends behind my end... + if( pTemp->GetIndex() >= aInsPos.GetIndex() ) + aInsPos = pTemp->GetIndex()+1; // ...I have to correct my end position + } + } + else + { + SwTextNode* pCpyTNd = rNdIdx.GetNode().GetTextNode(); + if( pCpyTNd ) + { + SwTextNode* pTNd = new SwTextNode( aInsPos, pCpyTNd->GetTextColl() ); + if( pCpyTNd->HasSwAttrSet() ) + { + // Move PageDesc/Break to the first Node of the section + const SfxItemSet& rSet = *pCpyTNd->GetpSwAttrSet(); + if( SfxItemState::SET == rSet.GetItemState( RES_BREAK ) || + SfxItemState::SET == rSet.GetItemState( RES_PAGEDESC )) + { + SfxItemSet aSet( rSet ); + if( bInsAtStart ) + pCpyTNd->ResetAttr( RES_PAGEDESC, RES_BREAK ); + else + { + aSet.ClearItem( RES_PAGEDESC ); + aSet.ClearItem( RES_BREAK ); + } + pTNd->SetAttr( aSet ); + } + else + pTNd->SetAttr( rSet ); + } + // Do not forget to create the Frame! + pCpyTNd->MakeFramesForAdjacentContentNode(*pTNd); + } + else + new SwTextNode( aInsPos, GetDoc()->GetDfltTextFormatColl() ); + } + new SwEndNode( aInsPos, *pSectNd ); + + pSectNd->GetSection().SetSectionData(rSectionData); + SwSectionFormat* pSectFormat = pSectNd->GetSection().GetFormat(); + + // We could optimize this, by not removing already contained Frames and recreating them, + // but by simply rewiring them + bool bInsFrame = bCreateFrames && !pSectNd->GetSection().IsHidden() && + GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwNode2LayoutSaveUpperFrames *pNode2Layout = nullptr; + if( bInsFrame ) + { + SwNodeIndex aTmp( *pSectNd ); + if( !pSectNd->GetNodes().FindPrvNxtFrameNode( aTmp, pSectNd->EndOfSectionNode() ) ) + // Collect all Uppers + pNode2Layout = new SwNode2LayoutSaveUpperFrames(*pSectNd); + } + + // Set the right StartNode for all in this Area + sal_uLong nEnd = pSectNd->EndOfSectionIndex(); + sal_uLong nStart = pSectNd->GetIndex()+1; + sal_uLong nSkipIdx = ULONG_MAX; + for( sal_uLong n = nStart; n < nEnd; ++n ) + { + SwNode* pNd = (*this)[n]; + + // Attach all Sections in the NodeSection underneath the new one + if( ULONG_MAX == nSkipIdx ) + pNd->m_pStartOfSection = pSectNd; + else if( n >= nSkipIdx ) + nSkipIdx = ULONG_MAX; + + if( pNd->IsStartNode() ) + { + // Make up the Format's nesting + if( pNd->IsSectionNode() ) + { + static_cast<SwSectionNode*>(pNd)->GetSection().GetFormat()-> + SetDerivedFrom( pSectFormat ); + static_cast<SwSectionNode*>(pNd)->DelFrames(); + n = pNd->EndOfSectionIndex(); + } + else + { + if( pNd->IsTableNode() ) + static_cast<SwTableNode*>(pNd)->DelFrames(); + + if( ULONG_MAX == nSkipIdx ) + nSkipIdx = pNd->EndOfSectionIndex(); + } + } + else if( pNd->IsContentNode() ) + static_cast<SwContentNode*>(pNd)->DelFrames(nullptr); + } + + sw_DeleteFootnote( pSectNd, nStart, nEnd ); + + if( bInsFrame ) + { + if( pNode2Layout ) + { + sal_uLong nIdx = pSectNd->GetIndex(); + pNode2Layout->RestoreUpperFrames( pSectNd->GetNodes(), nIdx, nIdx + 1 ); + delete pNode2Layout; + } + else + pSectNd->MakeOwnFrames(&aInsPos); + } + + return pSectNd; +} + +SwSectionNode* SwNode::FindSectionNode() +{ + if( IsSectionNode() ) + return GetSectionNode(); + SwStartNode* pTmp = m_pStartOfSection; + while( !pTmp->IsSectionNode() && pTmp->GetIndex() ) + pTmp = pTmp->m_pStartOfSection; + return pTmp->GetSectionNode(); +} + +// SwSectionNode + +// ugly hack to make m_pSection const +static SwSectionFormat & +lcl_initParent(SwSectionNode & rThis, SwSectionFormat & rFormat) +{ + SwSectionNode *const pParent = + rThis.StartOfSectionNode()->FindSectionNode(); + if( pParent ) + { + // Register the Format at the right Parent + rFormat.SetDerivedFrom( pParent->GetSection().GetFormat() ); + } + return rFormat; +} + +SwSectionNode::SwSectionNode(SwNodeIndex const& rIdx, + SwSectionFormat & rFormat, SwTOXBase const*const pTOXBase) + : SwStartNode( rIdx, SwNodeType::Section ) + , m_pSection( pTOXBase + ? new SwTOXBaseSection(*pTOXBase, lcl_initParent(*this, rFormat)) + : new SwSection( SectionType::Content, rFormat.GetName(), + lcl_initParent(*this, rFormat) ) ) +{ + // Set the connection from Format to Node + // Suppress Modify; no one's interested anyway + rFormat.LockModify(); + rFormat.SetFormatAttr( SwFormatContent( this ) ); + rFormat.UnlockModify(); +} + +SwSectionNode::~SwSectionNode() +{ + // mba: test if iteration works as clients will be removed in callback + // use hint which allows to specify, if the content shall be saved or not + m_pSection->GetFormat()->CallSwClientNotify( SwSectionFrameMoveAndDeleteHint( true ) ); + SwSectionFormat* pFormat = m_pSection->GetFormat(); + if( pFormat ) + { + // Remove the Attribute, because the Section deletes it's Format + // and it will neutralize the Section, if the Content Attribute is set + pFormat->LockModify(); + pFormat->ResetFormatAttr( RES_CNTNT ); + pFormat->UnlockModify(); + } +} + +SwFrame *SwSectionNode::MakeFrame( SwFrame *pSib ) +{ + m_pSection->m_Data.SetHiddenFlag(false); + return new SwSectionFrame( *m_pSection, pSib ); +} + +// Creates all Document Views for the preceding Node. +// The created ContentFrames are attached to the corresponding Layout +void SwSectionNode::MakeFramesForAdjacentContentNode(const SwNodeIndex & rIdx) +{ + // Take my successive or preceding ContentFrame + SwNodes& rNds = GetNodes(); + if( rNds.IsDocNodes() && rNds.GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + if( GetSection().IsHidden() || IsContentHidden() ) + { + SwNodeIndex aIdx( *EndOfSectionNode() ); + SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false ); + if( !pCNd ) + { + aIdx = *this; + pCNd = SwNodes::GoPrevSection(&aIdx, true, false); + if (!pCNd) + return; + } + pCNd = aIdx.GetNode().GetContentNode(); + pCNd->MakeFramesForAdjacentContentNode(static_cast<SwContentNode&>(rIdx.GetNode())); + } + else + { + SwNode2Layout aNode2Layout( *this, rIdx.GetIndex() ); + SwFrame *pFrame; + while( nullptr != (pFrame = aNode2Layout.NextFrame()) ) + { + OSL_ENSURE( pFrame->IsSctFrame(), "Depend of Section not a Section." ); + if (pFrame->getRootFrame()->IsHideRedlines() + && !rIdx.GetNode().IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwFrame *pNew = rIdx.GetNode().GetContentNode()->MakeFrame( pFrame ); + + SwSectionNode* pS = rIdx.GetNode().FindSectionNode(); + + // Assure that node is not inside a table, which is inside the + // found section. + if ( pS ) + { + SwTableNode* pTableNode = rIdx.GetNode().FindTableNode(); + if ( pTableNode && + pTableNode->GetIndex() > pS->GetIndex() ) + { + pS = nullptr; + } + } + + // if the node is in a section, the sectionframe now + // has to be created... + // boolean to control <Init()> of a new section frame. + bool bInitNewSect = false; + if( pS ) + { + SwSectionFrame *pSct = new SwSectionFrame( pS->GetSection(), pFrame ); + // prepare <Init()> of new section frame. + bInitNewSect = true; + SwLayoutFrame* pUp = pSct; + while( pUp->Lower() ) // for columned sections + { + OSL_ENSURE( pUp->Lower()->IsLayoutFrame(),"Who's in there?" ); + pUp = static_cast<SwLayoutFrame*>(pUp->Lower()); + } + pNew->Paste( pUp ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + if ( pNew->IsTextFrame() ) + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pNew->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pNew->FindPrevCnt()) ); + } + } + pNew = pSct; + } + + // If a Node got Frames attached before or after + if ( rIdx < GetIndex() ) + // the new one precedes me + pNew->Paste( pFrame->GetUpper(), pFrame ); + else + // the new one succeeds me + pNew->Paste( pFrame->GetUpper(), pFrame->GetNext() ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + if ( pNew->IsTextFrame() ) + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pNew->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pNew->FindPrevCnt()) ); + } + } + if ( bInitNewSect ) + static_cast<SwSectionFrame*>(pNew)->Init(); + } + } + } +} + +// Create a new SectionFrame for every occurrence in the Layout and insert before +// the corresponding ContentFrame +void SwSectionNode::MakeOwnFrames(SwNodeIndex* pIdxBehind, SwNodeIndex* pEndIdx) +{ + OSL_ENSURE( pIdxBehind, "no Index" ); + SwNodes& rNds = GetNodes(); + SwDoc* pDoc = rNds.GetDoc(); + + *pIdxBehind = *this; + + m_pSection->m_Data.SetHiddenFlag(true); + + if( rNds.IsDocNodes() ) + { + SwNodeIndex *pEnd = pEndIdx ? pEndIdx : + new SwNodeIndex( *EndOfSectionNode(), 1 ); + ::MakeFrames( pDoc, *pIdxBehind, *pEnd ); + if( !pEndIdx ) + delete pEnd; + } +} + +void SwSectionNode::DelFrames(SwRootFrame const*const /*FIXME TODO*/, bool const bForce) +{ + sal_uLong nStt = GetIndex()+1, nEnd = EndOfSectionIndex(); + if( nStt >= nEnd ) + { + return ; + } + + SwNodes& rNds = GetNodes(); + m_pSection->GetFormat()->DelFrames(); + + // Update our Flag + m_pSection->m_Data.SetHiddenFlag(true); + + // If the Area is within a Fly or TableBox, we can only hide it if + // there is more Content which has Frames. + // Or else the Fly/TableBox Frame does not have a Lower! + if (!bForce) + { + SwNodeIndex aIdx( *this ); + if( !SwNodes::GoPrevSection( &aIdx, true, false ) || + !CheckNodesRange( *this, aIdx, true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, *this, true )) + { + aIdx = *EndOfSectionNode(); + if( !rNds.GoNextSection( &aIdx, true, false ) || + !CheckNodesRange( *EndOfSectionNode(), aIdx, true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, *EndOfSectionNode(), false )) + { + m_pSection->m_Data.SetHiddenFlag(false); + } + } + } +} + +SwSectionNode* SwSectionNode::MakeCopy( SwDoc* pDoc, const SwNodeIndex& rIdx ) const +{ + // In which array am I: Nodes, UndoNodes? + const SwNodes& rNds = GetNodes(); + + // Copy the SectionFrameFormat + SwSectionFormat* pSectFormat = pDoc->MakeSectionFormat(); + pSectFormat->CopyAttrs( *GetSection().GetFormat() ); + + std::unique_ptr<SwTOXBase> pTOXBase; + if (SectionType::ToxContent == GetSection().GetType()) + { + OSL_ENSURE( dynamic_cast< const SwTOXBaseSection* >( &GetSection() ) != nullptr , "no TOXBaseSection!" ); + SwTOXBaseSection const& rTBS( + dynamic_cast<SwTOXBaseSection const&>(GetSection())); + pTOXBase.reset( new SwTOXBase(rTBS, pDoc) ); + } + + SwSectionNode *const pSectNd = + new SwSectionNode(rIdx, *pSectFormat, pTOXBase.get()); + SwEndNode* pEndNd = new SwEndNode( rIdx, *pSectNd ); + SwNodeIndex aInsPos( *pEndNd ); + + // Take over values + SwSection *const pNewSect = pSectNd->m_pSection.get(); + + if (SectionType::ToxContent != GetSection().GetType()) + { + // Keep the Name for Move + if( rNds.GetDoc() == pDoc && pDoc->IsCopyIsMove() ) + { + pNewSect->SetSectionName( GetSection().GetSectionName() ); + } + else + { + const OUString sSectionName(GetSection().GetSectionName()); + pNewSect->SetSectionName(pDoc->GetUniqueSectionName( &sSectionName )); + } + } + + pNewSect->SetType( GetSection().GetType() ); + pNewSect->SetCondition( GetSection().GetCondition() ); + pNewSect->SetLinkFileName( GetSection().GetLinkFileName() ); + if( !pNewSect->IsHiddenFlag() && GetSection().IsHidden() ) + pNewSect->SetHidden(); + if( !pNewSect->IsProtectFlag() && GetSection().IsProtect() ) + pNewSect->SetProtect(); + // edit in readonly sections + if( !pNewSect->IsEditInReadonlyFlag() && GetSection().IsEditInReadonly() ) + pNewSect->SetEditInReadonly(); + + SwNodeRange aRg( *this, +1, *EndOfSectionNode() ); // Where am I? + rNds.Copy_( aRg, aInsPos, false ); + + // Delete all Frames from the copied Area. They are created when creating + // the SectionFrames. + pSectNd->DelFrames(); + + // Copy the Links/Server + if( pNewSect->IsLinkType() ) // Add the Link + pNewSect->CreateLink( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ? LinkCreateType::Connect : LinkCreateType::NONE ); + + // If we copy from the Undo as Server, enter it again + if (m_pSection->IsServer() + && pDoc->GetIDocumentUndoRedo().IsUndoNodes(rNds)) + { + pNewSect->SetRefObject( m_pSection->GetObject() ); + pDoc->getIDocumentLinksAdministration().GetLinkManager().InsertServer( pNewSect->GetObject() ); + } + + // METADATA: copy xml:id; must be done after insertion of node + pSectFormat->RegisterAsCopyOf(*GetSection().GetFormat()); + + return pSectNd; +} + +bool SwSectionNode::IsContentHidden() const +{ + OSL_ENSURE( !m_pSection->IsHidden(), + "That's simple: Hidden Section => Hidden Content" ); + SwNodeIndex aTmp( *this, 1 ); + sal_uLong nEnd = EndOfSectionIndex(); + while( aTmp < nEnd ) + { + if( aTmp.GetNode().IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode&>(aTmp.GetNode()).GetSection(); + if( rSect.IsHiddenFlag() ) + // Skip this Section + aTmp = *aTmp.GetNode().EndOfSectionNode(); + } + else + { + if( aTmp.GetNode().IsContentNode() || aTmp.GetNode().IsTableNode() ) + return false; // We found non-hidden content + OSL_ENSURE( aTmp.GetNode().IsEndNode(), "EndNode expected" ); + } + ++aTmp; + } + return true; // Hide everything +} + +void SwSectionNode::NodesArrChgd() +{ + SwSectionFormat *const pFormat = m_pSection->GetFormat(); + if( !pFormat ) + return; + + SwNodes& rNds = GetNodes(); + SwDoc* pDoc = pFormat->GetDoc(); + + if( !rNds.IsDocNodes() ) + { + SwPtrMsgPoolItem aMsgHint( RES_REMOVE_UNO_OBJECT, pFormat ); + pFormat->ModifyNotification( &aMsgHint, &aMsgHint ); + } + + pFormat->LockModify(); + pFormat->SetFormatAttr( SwFormatContent( this )); + pFormat->UnlockModify(); + + SwSectionNode* pSectNd = StartOfSectionNode()->FindSectionNode(); + // set the correct parent from the new section + pFormat->SetDerivedFrom( pSectNd ? pSectNd->GetSection().GetFormat() + : pDoc->GetDfltFrameFormat() ); + + // Set the right StartNode for all in this Area + sal_uLong nStart = GetIndex()+1, nEnd = EndOfSectionIndex(); + for( sal_uLong n = nStart; n < nEnd; ++n ) + // Make up the Format's nesting + if( nullptr != ( pSectNd = rNds[ n ]->GetSectionNode() ) ) + { + pSectNd->GetSection().GetFormat()->SetDerivedFrom( pFormat ); + n = pSectNd->EndOfSectionIndex(); + } + + // Moving Nodes to the UndoNodes array? + if( rNds.IsDocNodes() ) + { + OSL_ENSURE( pDoc == GetDoc(), + "Moving to different Documents?" ); + if( m_pSection->IsLinkType() ) // Remove the Link + m_pSection->CreateLink( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ? LinkCreateType::Connect : LinkCreateType::NONE ); + + if (m_pSection->IsServer()) + pDoc->getIDocumentLinksAdministration().GetLinkManager().InsertServer( m_pSection->GetObject() ); + } + else + { + if (SectionType::Content != m_pSection->GetType() + && m_pSection->IsConnected()) + { + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( &m_pSection->GetBaseLink() ); + } + if (m_pSection->IsServer()) + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( m_pSection->GetObject() ); + } + +} + +OUString SwDoc::GetUniqueSectionName( const OUString* pChkStr ) const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeSection" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpSectionFormatTable->size() + 1 ); + if( pChkStr ) + newName += *pChkStr; + return newName; + } + + const OUString aName(SwResId(STR_REGION_DEFNAME)); + + SwSectionFormats::size_type nNum = 0; + const SwSectionFormats::size_type nFlagSize = ( mpSectionFormatTable->size() / 8 ) + 2; + std::unique_ptr<sal_uInt8[]> pSetFlags(new sal_uInt8[ nFlagSize ]); + memset( pSetFlags.get(), 0, nFlagSize ); + + for( auto pFormat : *mpSectionFormatTable ) + { + const SwSectionNode *const pSectNd = pFormat->GetSectionNode(); + if( pSectNd != nullptr ) + { + const OUString& rNm = pSectNd->GetSection().GetSectionName(); + if (rNm.startsWith( aName )) + { + // Calculate the Number and reset the Flag + nNum = rNm.copy( aName.getLength() ).toInt32(); + if( nNum-- && nNum < mpSectionFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + if( pChkStr && *pChkStr==rNm ) + pChkStr = nullptr; + } + } + + if( !pChkStr ) + { + // Flagged all Numbers accordingly, so get the right Number + nNum = mpSectionFormatTable->size(); + for( SwSectionFormats::size_type n = 0; n < nFlagSize; ++n ) + { + auto nTmp = pSetFlags[ n ]; + if( nTmp != 0xFF ) + { + // Calculate the Number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + } + pSetFlags.reset(); + if( pChkStr ) + return *pChkStr; + return aName + OUString::number( ++nNum ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndsect.hxx b/sw/source/core/docnode/ndsect.hxx new file mode 100644 index 000000000..70e3ed72f --- /dev/null +++ b/sw/source/core/docnode/ndsect.hxx @@ -0,0 +1,32 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_DOCNODE_NDSECT_HXX +#define INCLUDED_SW_SOURCE_CORE_DOCNODE_NDSECT_HXX + +#include <tools/solar.h> + +class SwSectionNode; + +void sw_DeleteFootnote( SwSectionNode *pNd, sal_uLong nStt, sal_uLong nEnd ); + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx new file mode 100644 index 000000000..5dcbdf865 --- /dev/null +++ b/sw/source/core/docnode/ndtbl.cxx @@ -0,0 +1,4645 @@ +/* -*- 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 <memory> +#include <fesh.hxx> +#include <hintids.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/boxitem.hxx> +#include <svl/stritem.hxx> +#include <editeng/shaditem.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtfordr.hxx> +#include <fmtpdsc.hxx> +#include <fmtanchr.hxx> +#include <fmtlsplt.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <pagefrm.hxx> +#include <tabcol.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <UndoManager.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentState.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <swcrsr.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <tblsel.hxx> +#include <poolfmt.hxx> +#include <tabfrm.hxx> +#include <UndoCore.hxx> +#include <UndoRedline.hxx> +#include <UndoDelete.hxx> +#include <UndoNumbering.hxx> +#include <UndoTable.hxx> +#include <hints.hxx> +#include <tblafmt.hxx> +#include <frminf.hxx> +#include <cellatr.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <rolbck.hxx> +#include <tblrwcl.hxx> +#include <editsh.hxx> +#include <txtfrm.hxx> +#include <section.hxx> +#include <frmtool.hxx> +#include <node2lay.hxx> +#include <strings.hrc> +#include <docsh.hxx> +#include <unochart.hxx> +#include <node.hxx> +#include <ndtxt.hxx> +#include <cstdlib> +#include <map> +#include <algorithm> +#include <rootfrm.hxx> +#include <fldupde.hxx> +#include <calbck.hxx> +#include <fntcache.hxx> +#include <frameformats.hxx> +#include <o3tl/numeric.hxx> +#include <tools/datetimeutils.hxx> +#include <sal/log.hxx> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +const sal_Unicode T2T_PARA = 0x0a; + +static void lcl_SetDfltBoxAttr( SwFrameFormat& rFormat, sal_uInt8 nId ) +{ + bool bTop = false, bBottom = false, bLeft = false, bRight = false; + switch ( nId ) + { + case 0: bTop = bBottom = bLeft = true; break; + case 1: bTop = bBottom = bLeft = bRight = true; break; + case 2: bBottom = bLeft = true; break; + case 3: bBottom = bLeft = bRight = true; break; + } + + const bool bHTML = rFormat.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE); + Color aCol( bHTML ? COL_GRAY : COL_BLACK ); + SvxBorderLine aLine( &aCol, DEF_LINE_WIDTH_0 ); + if ( bHTML ) + { + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + aLine.SetWidth( DEF_LINE_WIDTH_0 ); + } + SvxBoxItem aBox(RES_BOX); + aBox.SetAllDistances(55); + if ( bTop ) + aBox.SetLine( &aLine, SvxBoxItemLine::TOP ); + if ( bBottom ) + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + if ( bLeft ) + aBox.SetLine( &aLine, SvxBoxItemLine::LEFT ); + if ( bRight ) + aBox.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + rFormat.SetFormatAttr( aBox ); +} + +typedef std::map<SwFrameFormat *, SwTableBoxFormat *> DfltBoxAttrMap_t; +typedef std::vector<DfltBoxAttrMap_t *> DfltBoxAttrList_t; + +static void +lcl_SetDfltBoxAttr(SwTableBox& rBox, DfltBoxAttrList_t & rBoxFormatArr, + sal_uInt8 const nId, SwTableAutoFormat const*const pAutoFormat = nullptr) +{ + DfltBoxAttrMap_t * pMap = rBoxFormatArr[ nId ]; + if (!pMap) + { + pMap = new DfltBoxAttrMap_t; + rBoxFormatArr[ nId ] = pMap; + } + + SwTableBoxFormat* pNewTableBoxFormat = nullptr; + SwFrameFormat* pBoxFrameFormat = rBox.GetFrameFormat(); + DfltBoxAttrMap_t::iterator const iter(pMap->find(pBoxFrameFormat)); + if (pMap->end() != iter) + { + pNewTableBoxFormat = iter->second; + } + else + { + SwDoc* pDoc = pBoxFrameFormat->GetDoc(); + // format does not exist, so create it + pNewTableBoxFormat = pDoc->MakeTableBoxFormat(); + pNewTableBoxFormat->SetFormatAttr( pBoxFrameFormat->GetAttrSet().Get( RES_FRM_SIZE ) ); + + if( pAutoFormat ) + pAutoFormat->UpdateToSet( nId, false, false, + const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pNewTableBoxFormat->GetAttrSet())), + SwTableAutoFormatUpdateFlags::Box, + pDoc->GetNumberFormatter() ); + else + ::lcl_SetDfltBoxAttr( *pNewTableBoxFormat, nId ); + + (*pMap)[pBoxFrameFormat] = pNewTableBoxFormat; + } + rBox.ChgFrameFormat( pNewTableBoxFormat ); +} + +static SwTableBoxFormat *lcl_CreateDfltBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr, + sal_uInt16 nCols, sal_uInt8 nId ) +{ + if ( !rBoxFormatArr[nId] ) + { + SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat(); + if( USHRT_MAX != nCols ) + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + USHRT_MAX / nCols, 0 )); + ::lcl_SetDfltBoxAttr( *pBoxFormat, nId ); + rBoxFormatArr[ nId ] = pBoxFormat; + } + return rBoxFormatArr[nId]; +} + +static SwTableBoxFormat *lcl_CreateAFormatBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr, + const SwTableAutoFormat& rAutoFormat, + const sal_uInt16 nRows, const sal_uInt16 nCols, sal_uInt8 nId ) +{ + if( !rBoxFormatArr[nId] ) + { + SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat(); + rAutoFormat.UpdateToSet( nId, nRows==1, nCols==1, + const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pBoxFormat->GetAttrSet())), + SwTableAutoFormatUpdateFlags::Box, + rDoc.GetNumberFormatter( ) ); + if( USHRT_MAX != nCols ) + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + USHRT_MAX / nCols, 0 )); + rBoxFormatArr[ nId ] = pBoxFormat; + } + return rBoxFormatArr[nId]; +} + +SwTableNode* SwDoc::IsIdxInTable(const SwNodeIndex& rIdx) +{ + SwTableNode* pTableNd = nullptr; + sal_uLong nIndex = rIdx.GetIndex(); + do { + SwNode* pNd = GetNodes()[ nIndex ]->StartOfSectionNode(); + if( nullptr != ( pTableNd = pNd->GetTableNode() ) ) + break; + + nIndex = pNd->GetIndex(); + } while ( nIndex ); + return pTableNd; +} + +/** + * Insert a new Box before the InsPos + */ +bool SwNodes::InsBoxen( SwTableNode* pTableNd, + SwTableLine* pLine, + SwTableBoxFormat* pBoxFormat, + SwTextFormatColl* pTextColl, + const SfxItemSet* pAutoAttr, + sal_uInt16 nInsPos, + sal_uInt16 nCnt ) +{ + if( !nCnt ) + return false; + OSL_ENSURE( pLine, "No valid Line" ); + + // Move Index after the Line's last Box + sal_uLong nIdxPos = 0; + SwTableBox *pPrvBox = nullptr, *pNxtBox = nullptr; + if( !pLine->GetTabBoxes().empty() ) + { + if( nInsPos < pLine->GetTabBoxes().size() ) + { + if( nullptr == (pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable(), + pLine->GetTabBoxes()[ nInsPos ] ))) + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() ); + } + else + { + if( nullptr == (pNxtBox = pLine->FindNextBox( pTableNd->GetTable(), + pLine->GetTabBoxes().back() ))) + pNxtBox = pLine->FindNextBox( pTableNd->GetTable() ); + } + } + else if( nullptr == ( pNxtBox = pLine->FindNextBox( pTableNd->GetTable() ))) + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() ); + + if( !pPrvBox && !pNxtBox ) + { + bool bSetIdxPos = true; + if( !pTableNd->GetTable().GetTabLines().empty() && !nInsPos ) + { + const SwTableLine* pTableLn = pLine; + while( pTableLn->GetUpper() ) + pTableLn = pTableLn->GetUpper()->GetUpper(); + + if( pTableNd->GetTable().GetTabLines()[ 0 ] == pTableLn ) + { + // Before the Table's first Box + while( !( pNxtBox = pLine->GetTabBoxes()[0])->GetTabLines().empty() ) + pLine = pNxtBox->GetTabLines()[0]; + nIdxPos = pNxtBox->GetSttIdx(); + bSetIdxPos = false; + } + } + if( bSetIdxPos ) + // Tables without content or at the end; move before the End + nIdxPos = pTableNd->EndOfSectionIndex(); + } + else if( pNxtBox ) // There is a successor + nIdxPos = pNxtBox->GetSttIdx(); + else // There is a predecessor + nIdxPos = pPrvBox->GetSttNd()->EndOfSectionIndex() + 1; + + SwNodeIndex aEndIdx( *this, nIdxPos ); + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwStartNode* pSttNd = new SwStartNode( aEndIdx, SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + new SwEndNode( aEndIdx, *pSttNd ); + + pPrvBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + + SwTableBoxes & rTabBoxes = pLine->GetTabBoxes(); + sal_uInt16 nRealInsPos = nInsPos + n; + if (nRealInsPos > rTabBoxes.size()) + nRealInsPos = rTabBoxes.size(); + + rTabBoxes.insert( rTabBoxes.begin() + nRealInsPos, pPrvBox ); + + if( ! pTextColl->IsAssignedToListLevelOfOutlineStyle() +//FEATURE::CONDCOLL + && RES_CONDTXTFMTCOLL != pTextColl->Which() +//FEATURE::CONDCOLL + ) + new SwTextNode( SwNodeIndex( *pSttNd->EndOfSectionNode() ), + pTextColl, pAutoAttr ); + else + { + // Handle Outline numbering correctly! + SwTextNode* pTNd = new SwTextNode( + SwNodeIndex( *pSttNd->EndOfSectionNode() ), + GetDoc()->GetDfltTextFormatColl(), + pAutoAttr ); + pTNd->ChgFormatColl( pTextColl ); + } + } + return true; +} + +/** + * Insert a new Table + */ +const SwTable* SwDoc::InsertTable( const SwInsertTableOptions& rInsTableOpts, + const SwPosition& rPos, sal_uInt16 nRows, + sal_uInt16 nCols, sal_Int16 eAdjust, + const SwTableAutoFormat* pTAFormat, + const std::vector<sal_uInt16> *pColArr, + bool bCalledFromShell, + bool bNewModel ) +{ + assert(nRows && "Table without line?"); + assert(nCols && "Table without rows?"); + + { + // Do not copy into Footnotes! + if( rPos.nNode < GetNodes().GetEndOfInserts().GetIndex() && + rPos.nNode >= GetNodes().GetEndOfInserts().StartOfSectionIndex() ) + return nullptr; + + // If the ColumnArray has a wrong count, ignore it! + if( pColArr && + static_cast<size_t>(nCols + ( text::HoriOrientation::NONE == eAdjust ? 2 : 1 )) != pColArr->size() ) + pColArr = nullptr; + } + + OUString aTableName = GetUniqueTableName(); + + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsTable>( rPos, nCols, nRows, static_cast<sal_uInt16>(eAdjust), + rInsTableOpts, pTAFormat, pColArr, + aTableName)); + } + + // Start with inserting the Nodes and get the AutoFormat for the Table + SwTextFormatColl *pBodyColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ), + *pHeadColl = pBodyColl; + + bool bDfltBorders( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder ); + + if( (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) && (1 != nRows || !bDfltBorders) ) + pHeadColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN ); + + const sal_uInt16 nRowsToRepeat = + SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ? + rInsTableOpts.mnRowsToRepeat : + 0; + + /* Save content node to extract FRAMEDIR from. */ + const SwContentNode * pContentNd = rPos.nNode.GetNode().GetContentNode(); + + /* If we are called from a shell pass the attrset from + pContentNd (aka the node the table is inserted at) thus causing + SwNodes::InsertTable to propagate an adjust item if + necessary. */ + SwTableNode *pTableNd = SwNodes::InsertTable( + rPos.nNode, + nCols, + pBodyColl, + nRows, + nRowsToRepeat, + pHeadColl, + bCalledFromShell ? &pContentNd->GetSwAttrSet() : nullptr ); + + // Create the Box/Line/Table construct + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( aTableName, GetDfltFrameFormat() ); + + /* If the node to insert the table at is a context node and has a + non-default FRAMEDIR propagate it to the table. */ + if (pContentNd) + { + const SwAttrSet & aNdSet = pContentNd->GetSwAttrSet(); + const SfxPoolItem *pItem = nullptr; + + if (SfxItemState::SET == aNdSet.GetItemState( RES_FRAMEDIR, true, &pItem ) + && pItem != nullptr) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + // Set Orientation at the Table's Format + pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) ); + // All lines use the left-to-right Fill-Order! + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + + // Set USHRT_MAX as the Table's default SSize + SwTwips nWidth = USHRT_MAX; + if( pColArr ) + { + sal_uInt16 nSttPos = pColArr->front(); + sal_uInt16 nLastPos = pColArr->back(); + if( text::HoriOrientation::NONE == eAdjust ) + { + sal_uInt16 nFrameWidth = nLastPos; + nLastPos = (*pColArr)[ pColArr->size()-2 ]; + pTableFormat->SetFormatAttr( SvxLRSpaceItem( nSttPos, nFrameWidth - nLastPos, 0, 0, RES_LR_SPACE ) ); + } + nWidth = nLastPos - nSttPos; + } + else + { + nWidth /= nCols; + nWidth *= nCols; // to avoid rounding problems + } + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth )); + if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) ) + pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false )); + + // Move the hard PageDesc/PageBreak Attributes if needed + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ] + ->GetContentNode(); + if( pNextNd && pNextNd->HasSwAttrSet() ) + { + const SfxItemSet* pNdSet = pNextNd->GetpSwAttrSet(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pNdSet->GetItemState( RES_PAGEDESC, false, + &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + pNextNd->ResetAttr( RES_PAGEDESC ); + pNdSet = pNextNd->GetpSwAttrSet(); + } + if( pNdSet && SfxItemState::SET == pNdSet->GetItemState( RES_BREAK, false, + &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + pNextNd->ResetAttr( RES_BREAK ); + } + } + + SwTable& rNdTable = pTableNd->GetTable(); + rNdTable.RegisterToFormat( *pTableFormat ); + + rNdTable.SetRowsToRepeat( nRowsToRepeat ); + rNdTable.SetTableModel( bNewModel ); + + std::vector<SwTableBoxFormat*> aBoxFormatArr; + SwTableBoxFormat* pBoxFormat = nullptr; + if( !bDfltBorders && !pTAFormat ) + { + pBoxFormat = MakeTableBoxFormat(); + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX / nCols, 0 )); + } + else + { + const sal_uInt16 nBoxArrLen = pTAFormat ? 16 : 4; + aBoxFormatArr.resize( nBoxArrLen, nullptr ); + } + SfxItemSet aCharSet( GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1>{} ); + + SwNodeIndex aNdIdx( *pTableNd, 1 ); // Set to StartNode of first Box + SwTableLines& rLines = rNdTable.GetTabLines(); + for( sal_uInt16 n = 0; n < nRows; ++n ) + { + SwTableLine* pLine = new SwTableLine( pLineFormat, nCols, nullptr ); + rLines.insert( rLines.begin() + n, pLine ); + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( sal_uInt16 i = 0; i < nCols; ++i ) + { + SwTableBoxFormat *pBoxF; + if( pTAFormat ) + { + sal_uInt8 nId = SwTableAutoFormat::CountPos(i, nCols, n, nRows); + pBoxF = ::lcl_CreateAFormatBoxFormat( *this, aBoxFormatArr, *pTAFormat, + nRows, nCols, nId ); + + // Set the Paragraph/Character Attributes if needed + if( pTAFormat->IsFont() || pTAFormat->IsJustify() ) + { + aCharSet.ClearItem(); + pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet, + SwTableAutoFormatUpdateFlags::Char, nullptr ); + if( aCharSet.Count() ) + GetNodes()[ aNdIdx.GetIndex()+1 ]->GetContentNode()-> + SetAttr( aCharSet ); + } + } + else if( bDfltBorders ) + { + sal_uInt8 nBoxId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 ); + pBoxF = ::lcl_CreateDfltBoxFormat( *this, aBoxFormatArr, nCols, nBoxId); + } + else + pBoxF = pBoxFormat; + + // For AutoFormat on input: the columns are set when inserting the Table + // The Array contains the columns positions and not their widths! + if( pColArr ) + { + nWidth = (*pColArr)[ i + 1 ] - (*pColArr)[ i ]; + if( pBoxF->GetFrameSize().GetWidth() != nWidth ) + { + if( pBoxF->HasWriterListeners() ) // Create new Format + { + SwTableBoxFormat *pNewFormat = MakeTableBoxFormat(); + *pNewFormat = *pBoxF; + pBoxF = pNewFormat; + } + pBoxF->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth )); + } + } + + SwTableBox *pBox = new SwTableBox( pBoxF, aNdIdx, pLine); + rBoxes.insert( rBoxes.begin() + i, pBox ); + aNdIdx += 3; // StartNode, TextNode, EndNode == 3 Nodes + } + } + // Insert Frames + GetNodes().GoNext( &aNdIdx ); // Go to the next ContentNode + pTableNd->MakeOwnFrames( &aNdIdx ); + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( *pTableNd->EndOfSectionNode(), *pTableNd, 1 ); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + getIDocumentState().SetModified(); + CHECK_TABLE(rNdTable); + return &rNdTable; +} + +SwTableNode* SwNodes::InsertTable( const SwNodeIndex& rNdIdx, + sal_uInt16 nBoxes, + SwTextFormatColl* pContentTextColl, + sal_uInt16 nLines, + sal_uInt16 nRepeat, + SwTextFormatColl* pHeadlineTextColl, + const SwAttrSet * pAttrSet) +{ + if( !nBoxes ) + return nullptr; + + // If Lines is given, create the Matrix from Lines and Boxes + if( !pHeadlineTextColl || !nLines ) + pHeadlineTextColl = pContentTextColl; + + SwTableNode * pTableNd = new SwTableNode( rNdIdx ); + SwEndNode* pEndNd = new SwEndNode( rNdIdx, *pTableNd ); + + if( !nLines ) // For the for loop + ++nLines; + + SwNodeIndex aIdx( *pEndNd ); + SwTextFormatColl* pTextColl = pHeadlineTextColl; + for( sal_uInt16 nL = 0; nL < nLines; ++nL ) + { + for( sal_uInt16 nB = 0; nB < nBoxes; ++nB ) + { + SwStartNode* pSttNd = new SwStartNode( aIdx, SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + + SwTextNode * pTmpNd = new SwTextNode( aIdx, pTextColl ); + + // #i60422# Propagate some more attributes. + const SfxPoolItem* pItem = nullptr; + if ( nullptr != pAttrSet ) + { + static const sal_uInt16 aPropagateItems[] = { + RES_PARATR_ADJUST, + RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, 0 }; + + const sal_uInt16* pIdx = aPropagateItems; + while ( *pIdx != 0 ) + { + if ( SfxItemState::SET != pTmpNd->GetSwAttrSet().GetItemState( *pIdx ) && + SfxItemState::SET == pAttrSet->GetItemState( *pIdx, true, &pItem ) ) + static_cast<SwContentNode *>(pTmpNd)->SetAttr(*pItem); + ++pIdx; + } + } + + new SwEndNode( aIdx, *pSttNd ); + } + if ( nL + 1 >= nRepeat ) + pTextColl = pContentTextColl; + } + return pTableNd; +} + +/** + * Text to Table + */ +const SwTable* SwDoc::TextToTable( const SwInsertTableOptions& rInsTableOpts, + const SwPaM& rRange, sal_Unicode cCh, + sal_Int16 eAdjust, + const SwTableAutoFormat* pTAFormat ) +{ + // See if the selection contains a Table + const SwPosition *pStt = rRange.Start(), *pEnd = rRange.End(); + { + sal_uLong nCnt = pStt->nNode.GetIndex(); + for( ; nCnt <= pEnd->nNode.GetIndex(); ++nCnt ) + if( !GetNodes()[ nCnt ]->IsTextNode() ) + return nullptr; + } + + // Save first node in the selection if it is a context node + SwContentNode * pSttContentNd = pStt->nNode.GetNode().GetContentNode(); + + SwPaM aOriginal( *pStt, *pEnd ); + pStt = aOriginal.GetMark(); + pEnd = aOriginal.GetPoint(); + + SwUndoTextToTable* pUndo = nullptr; + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TEXTTOTABLE, nullptr ); + pUndo = new SwUndoTextToTable( aOriginal, rInsTableOpts, cCh, + static_cast<sal_uInt16>(eAdjust), pTAFormat ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + + // Do not add splitting the TextNode to the Undo history + GetIDocumentUndoRedo().DoUndo( false ); + } + + ::PaMCorrAbs( aOriginal, *pEnd ); + + // Make sure that the range is on Node Edges + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + if( pStt->nContent.GetIndex() ) + getIDocumentContentOperations().SplitNode( *pStt, false ); + + bool bEndContent = 0 != pEnd->nContent.GetIndex(); + + // Do not split at the End of a Line (except at the End of the Doc) + if( bEndContent ) + { + if( pEnd->nNode.GetNode().GetContentNode()->Len() != pEnd->nContent.GetIndex() + || pEnd->nNode.GetIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 ) + { + getIDocumentContentOperations().SplitNode( *pEnd, false ); + --const_cast<SwNodeIndex&>(pEnd->nNode); + const_cast<SwIndex&>(pEnd->nContent).Assign( + pEnd->nNode.GetNode().GetContentNode(), 0 ); + // A Node and at the End? + if( pStt->nNode.GetIndex() >= pEnd->nNode.GetIndex() ) + --aRg.aStart; + } + else + ++aRg.aEnd; + } + + if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() ) + { + OSL_FAIL( "empty range" ); + ++aRg.aEnd; + } + + // We always use Upper to insert the Table + SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() ); + + GetIDocumentUndoRedo().DoUndo( nullptr != pUndo ); + + // Create the Box/Line/Table construct + SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() ); + + // All Lines have a left-to-right Fill Order + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + // The Table's SSize is USHRT_MAX + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX )); + if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) ) + pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false )); + + /* If the first node in the selection is a context node and if it + has an item FRAMEDIR set (no default) propagate the item to the + replacing table. */ + if (pSttContentNd) + { + const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet(); + const SfxPoolItem *pItem = nullptr; + + if (SfxItemState::SET == aNdSet.GetItemState( RES_FRAMEDIR, true, &pItem ) + && pItem != nullptr) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications + //until after RegisterToFormat is completed + bool bEnableSetModified = getIDocumentState().IsEnableSetModified(); + getIDocumentState().SetEnableSetModified(false); + + SwTableNode* pTableNd = GetNodes().TextToTable( + aRg, cCh, pTableFormat, pLineFormat, pBoxFormat, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ), pUndo ); + + SwTable& rNdTable = pTableNd->GetTable(); + + const sal_uInt16 nRowsToRepeat = + SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ? + rInsTableOpts.mnRowsToRepeat : + 0; + rNdTable.SetRowsToRepeat(nRowsToRepeat); + + bool bUseBoxFormat = false; + if( !pBoxFormat->HasWriterListeners() ) + { + // The Box's Formats already have the right size, we must only set + // the right Border/AutoFormat. + bUseBoxFormat = true; + pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() ); + delete pBoxFormat; + eAdjust = text::HoriOrientation::NONE; + } + + // Set Orientation in the Table's Format + pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) ); + rNdTable.RegisterToFormat(*pTableFormat); + + if( pTAFormat || ( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder) ) + { + sal_uInt8 nBoxArrLen = pTAFormat ? 16 : 4; + std::unique_ptr< DfltBoxAttrList_t > aBoxFormatArr1; + std::unique_ptr< std::vector<SwTableBoxFormat*> > aBoxFormatArr2; + if( bUseBoxFormat ) + { + aBoxFormatArr1.reset(new DfltBoxAttrList_t( nBoxArrLen, nullptr )); + } + else + { + aBoxFormatArr2.reset(new std::vector<SwTableBoxFormat*>( nBoxArrLen, nullptr )); + } + + SfxItemSet aCharSet( GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1>{} ); + + SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr; + + SwTableBoxFormat *pBoxF = nullptr; + SwTableLines& rLines = rNdTable.GetTabLines(); + const SwTableLines::size_type nRows = rLines.size(); + for( SwTableLines::size_type n = 0; n < nRows; ++n ) + { + SwTableBoxes& rBoxes = rLines[ n ]->GetTabBoxes(); + const SwTableBoxes::size_type nCols = rBoxes.size(); + for( SwTableBoxes::size_type i = 0; i < nCols; ++i ) + { + SwTableBox* pBox = rBoxes[ i ]; + bool bChgSz = false; + + if( pTAFormat ) + { + sal_uInt8 nId = static_cast<sal_uInt8>(!n ? 0 : (( n+1 == nRows ) + ? 12 : (4 * (1 + ((n-1) & 1 ))))); + nId = nId + static_cast<sal_uInt8>(!i ? 0 : + ( i+1 == nCols ? 3 : (1 + ((i-1) & 1)))); + if( bUseBoxFormat ) + ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId, pTAFormat ); + else + { + bChgSz = nullptr == (*aBoxFormatArr2)[ nId ]; + pBoxF = ::lcl_CreateAFormatBoxFormat( *this, *aBoxFormatArr2, + *pTAFormat, USHRT_MAX, USHRT_MAX, nId ); + } + + // Set Paragraph/Character Attributes if needed + if( pTAFormat->IsFont() || pTAFormat->IsJustify() ) + { + aCharSet.ClearItem(); + pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet, + SwTableAutoFormatUpdateFlags::Char, nullptr ); + if( aCharSet.Count() ) + { + sal_uLong nSttNd = pBox->GetSttIdx()+1; + sal_uLong nEndNd = pBox->GetSttNd()->EndOfSectionIndex(); + for( ; nSttNd < nEndNd; ++nSttNd ) + { + SwContentNode* pNd = GetNodes()[ nSttNd ]->GetContentNode(); + if( pNd ) + { + if( pHistory ) + { + SwRegHistory aReg( pNd, *pNd, pHistory ); + pNd->SetAttr( aCharSet ); + } + else + pNd->SetAttr( aCharSet ); + } + } + } + } + } + else + { + sal_uInt8 nId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 ); + if( bUseBoxFormat ) + ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId ); + else + { + bChgSz = nullptr == (*aBoxFormatArr2)[ nId ]; + pBoxF = ::lcl_CreateDfltBoxFormat( *this, *aBoxFormatArr2, + USHRT_MAX, nId ); + } + } + + if( !bUseBoxFormat ) + { + if( bChgSz ) + pBoxF->SetFormatAttr( pBox->GetFrameFormat()->GetFrameSize() ); + pBox->ChgFrameFormat( pBoxF ); + } + } + } + + if( bUseBoxFormat ) + { + for( sal_uInt8 i = 0; i < nBoxArrLen; ++i ) + { + delete (*aBoxFormatArr1)[ i ]; + } + } + } + + // Check the boxes for numbers + if( IsInsTableFormatNum() ) + { + for (size_t nBoxes = rNdTable.GetTabSortBoxes().size(); nBoxes; ) + { + ChkBoxNumFormat(*rNdTable.GetTabSortBoxes()[ --nBoxes ], false); + } + } + + sal_uLong nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + { + SwPaM& rTmp = const_cast<SwPaM&>(rRange); // Point always at the Start + rTmp.DeleteMark(); + rTmp.GetPoint()->nNode = *pTableNd; + SwContentNode* pCNd = GetNodes().GoNext( &rTmp.GetPoint()->nNode ); + rTmp.GetPoint()->nContent.Assign( pCNd, 0 ); + } + + if( pUndo ) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::TEXTTOTABLE, nullptr ); + } + + getIDocumentState().SetEnableSetModified(bEnableSetModified); + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty(true, nullptr, 0); + return &rNdTable; +} + +static void lcl_RemoveBreaks(SwContentNode & rNode, SwTableFormat *const pTableFormat) +{ + // delete old layout frames, new ones need to be created... + rNode.DelFrames(nullptr); + + if (!rNode.IsTextNode()) + { + return; + } + + SwTextNode & rTextNode = *rNode.GetTextNode(); + // remove PageBreaks/PageDesc/ColBreak + SfxItemSet const* pSet = rTextNode.GetpSwAttrSet(); + if (pSet) + { + const SfxPoolItem* pItem; + if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false, &pItem)) + { + if (pTableFormat) + { + pTableFormat->SetFormatAttr(*pItem); + } + rTextNode.ResetAttr(RES_BREAK); + pSet = rTextNode.GetpSwAttrSet(); + } + + if (pSet + && (SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false, &pItem)) + && static_cast<SwFormatPageDesc const*>(pItem)->GetPageDesc()) + { + if (pTableFormat) + { + pTableFormat->SetFormatAttr(*pItem); + } + rTextNode.ResetAttr(RES_PAGEDESC); + } + } +} + +/** + * balance lines in table, insert empty boxes so all lines have the size + */ +static void +lcl_BalanceTable(SwTable & rTable, size_t const nMaxBoxes, + SwTableNode & rTableNd, SwTableBoxFormat & rBoxFormat, SwTextFormatColl & rTextColl, + SwUndoTextToTable *const pUndo, std::vector<sal_uInt16> *const pPositions) +{ + for (size_t n = 0; n < rTable.GetTabLines().size(); ++n) + { + SwTableLine *const pCurrLine = rTable.GetTabLines()[ n ]; + size_t const nBoxes = pCurrLine->GetTabBoxes().size(); + if (nMaxBoxes != nBoxes) + { + rTableNd.GetNodes().InsBoxen(&rTableNd, pCurrLine, &rBoxFormat, &rTextColl, + nullptr, nBoxes, nMaxBoxes - nBoxes); + + if (pUndo) + { + for (size_t i = nBoxes; i < nMaxBoxes; ++i) + { + pUndo->AddFillBox( *pCurrLine->GetTabBoxes()[i] ); + } + } + + // if the first line is missing boxes, the width array is useless! + if (!n && pPositions) + { + pPositions->clear(); + } + } + } +} + +static void +lcl_SetTableBoxWidths(SwTable & rTable, size_t const nMaxBoxes, + SwTableBoxFormat & rBoxFormat, SwDoc & rDoc, + std::vector<sal_uInt16> *const pPositions) +{ + if (pPositions && !pPositions->empty()) + { + SwTableLines& rLns = rTable.GetTabLines(); + sal_uInt16 nLastPos = 0; + for (size_t n = 0; n < pPositions->size(); ++n) + { + SwTableBoxFormat *pNewFormat = rDoc.MakeTableBoxFormat(); + pNewFormat->SetFormatAttr( + SwFormatFrameSize(SwFrameSize::Variable, (*pPositions)[n] - nLastPos)); + for (size_t nTmpLine = 0; nTmpLine < rLns.size(); ++nTmpLine) + { + // Have to do an Add here, because the BoxFormat + // is still needed by the caller + pNewFormat->Add( rLns[ nTmpLine ]->GetTabBoxes()[ n ] ); + } + + nLastPos = (*pPositions)[ n ]; + } + + // propagate size upwards from format, so the table gets the right size + SAL_WARN_IF(rBoxFormat.HasWriterListeners(), "sw.core", + "who is still registered in the format?"); + rBoxFormat.SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nLastPos )); + } + else + { + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth)); + } +} + +SwTableNode* SwNodes::TextToTable( const SwNodeRange& rRange, sal_Unicode cCh, + SwTableFormat* pTableFormat, + SwTableLineFormat* pLineFormat, + SwTableBoxFormat* pBoxFormat, + SwTextFormatColl* pTextColl, + SwUndoTextToTable* pUndo ) +{ + if( rRange.aStart >= rRange.aEnd ) + return nullptr; + + SwTableNode * pTableNd = new SwTableNode( rRange.aStart ); + new SwEndNode( rRange.aEnd, *pTableNd ); + + SwDoc* pDoc = GetDoc(); + std::vector<sal_uInt16> aPosArr; + SwTable& rTable = pTableNd->GetTable(); + SwTableBox* pBox; + sal_uInt16 nBoxes, nLines, nMaxBoxes = 0; + + SwNodeIndex aSttIdx( *pTableNd, 1 ); + SwNodeIndex aEndIdx( rRange.aEnd, -1 ); + for( nLines = 0, nBoxes = 0; + aSttIdx.GetIndex() < aEndIdx.GetIndex(); + aSttIdx += 2, nLines++, nBoxes = 0 ) + { + SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "Only add TextNodes to the Table" ); + + if( !nLines && 0x0b == cCh ) + { + cCh = 0x09; + + // Get the separator's position from the first Node, in order for the Boxes to be set accordingly + SwTextFrameInfo aFInfo( static_cast<SwTextFrame*>(pTextNd->getLayoutFrame( pTextNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() )) ); + if( aFInfo.IsOneLine() ) // only makes sense in this case + { + OUString const& rText(pTextNd->GetText()); + for (sal_Int32 nChPos = 0; nChPos < rText.getLength(); ++nChPos) + { + if (rText[nChPos] == cCh) + { + // sw_redlinehide: no idea if this makes any sense... + TextFrameIndex const nPos(aFInfo.GetFrame()->MapModelToView(pTextNd, nChPos)); + aPosArr.push_back( static_cast<sal_uInt16>( + aFInfo.GetCharPos(nPos+TextFrameIndex(1), false)) ); + } + } + + aPosArr.push_back( + static_cast<sal_uInt16>(aFInfo.GetFrame()->IsVertical() ? + aFInfo.GetFrame()->getFramePrintArea().Bottom() : + aFInfo.GetFrame()->getFramePrintArea().Right()) ); + + } + } + + lcl_RemoveBreaks(*pTextNd, (0 == nLines) ? pTableFormat : nullptr); + + // Set the TableNode as StartNode for all TextNodes in the Table + pTextNd->m_pStartOfSection = pTableNd; + + SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr ); + rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine); + + SwStartNode* pSttNd; + SwPosition aCntPos( aSttIdx, SwIndex( pTextNd )); + + const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( pDoc, aSttIdx.GetIndex(), pTextNd->GetText().getLength() ); + + if( T2T_PARA != cCh ) + { + for (sal_Int32 nChPos = 0; nChPos < pTextNd->GetText().getLength();) + { + if (pTextNd->GetText()[nChPos] == cCh) + { + aCntPos.nContent = nChPos; + std::function<void (SwTextNode *, sw::mark::RestoreMode)> restoreFunc( + [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(*pNewNode, nChPos, nChPos + 1, eMode); + } + }); + SwContentNode *const pNewNd = + pTextNd->SplitContentNode(aCntPos, &restoreFunc); + + // Delete separator and correct search string + pTextNd->EraseText( aCntPos.nContent, 1 ); + nChPos = 0; + + // Set the TableNode as StartNode for all TextNodes in the Table + const SwNodeIndex aTmpIdx( aCntPos.nNode, -1 ); + pSttNd = new SwStartNode( aTmpIdx, SwNodeType::Start, + SwTableBoxStartNode ); + new SwEndNode( aCntPos.nNode, *pSttNd ); + pNewNd->m_pStartOfSection = pSttNd; + + // Assign Section to the Box + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + } + else + { + ++nChPos; + } + } + } + + // Now for the last substring + if( !pContentStore->Empty()) + pContentStore->Restore( *pTextNd, pTextNd->GetText().getLength(), pTextNd->GetText().getLength()+1 ); + + pSttNd = new SwStartNode( aCntPos.nNode, SwNodeType::Start, SwTableBoxStartNode ); + const SwNodeIndex aTmpIdx( aCntPos.nNode, 1 ); + new SwEndNode( aTmpIdx, *pSttNd ); + pTextNd->m_pStartOfSection = pSttNd; + + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + if( nMaxBoxes < nBoxes ) + nMaxBoxes = nBoxes; + } + + lcl_BalanceTable(rTable, nMaxBoxes, *pTableNd, *pBoxFormat, *pTextColl, + pUndo, &aPosArr); + lcl_SetTableBoxWidths(rTable, nMaxBoxes, *pBoxFormat, *pDoc, &aPosArr); + + return pTableNd; +} + +const SwTable* SwDoc::TextToTable( const std::vector< std::vector<SwNodeRange> >& rTableNodes ) +{ + if (rTableNodes.empty()) + return nullptr; + + const std::vector<SwNodeRange>& rFirstRange = *rTableNodes.begin(); + + if (rFirstRange.empty()) + return nullptr; + + const std::vector<SwNodeRange>& rLastRange = *rTableNodes.rbegin(); + + if (rLastRange.empty()) + return nullptr; + + /* Save first node in the selection if it is a content node. */ + SwContentNode * pSttContentNd = rFirstRange.begin()->aStart.GetNode().GetContentNode(); + + const SwNodeRange& rStartRange = *rFirstRange.begin(); + const SwNodeRange& rEndRange = *rLastRange.rbegin(); + + //!!! not necessarily TextNodes !!! + SwPaM aOriginal( rStartRange.aStart, rEndRange.aEnd ); + const SwPosition *pStt = aOriginal.GetMark(); + const SwPosition *pEnd = aOriginal.GetPoint(); + + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + // Do not add splitting the TextNode to the Undo history + GetIDocumentUndoRedo().DoUndo(false); + } + + ::PaMCorrAbs( aOriginal, *pEnd ); + + // make sure that the range is on Node Edges + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + if( pStt->nContent.GetIndex() ) + getIDocumentContentOperations().SplitNode( *pStt, false ); + + bool bEndContent = 0 != pEnd->nContent.GetIndex(); + + // Do not split at the End of a Line (except at the End of the Doc) + if( bEndContent ) + { + if( pEnd->nNode.GetNode().GetContentNode()->Len() != pEnd->nContent.GetIndex() + || pEnd->nNode.GetIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 ) + { + getIDocumentContentOperations().SplitNode( *pEnd, false ); + --const_cast<SwNodeIndex&>(pEnd->nNode); + const_cast<SwIndex&>(pEnd->nContent).Assign( + pEnd->nNode.GetNode().GetContentNode(), 0 ); + // A Node and at the End? + if( pStt->nNode.GetIndex() >= pEnd->nNode.GetIndex() ) + --aRg.aStart; + } + else + ++aRg.aEnd; + } + + assert(aRg.aEnd == pEnd->nNode); + assert(aRg.aStart == pStt->nNode); + if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() ) + { + OSL_FAIL( "empty range" ); + ++aRg.aEnd; + } + + + { + // TODO: this is not Undo-able - only good enough for file import + IDocumentRedlineAccess & rIDRA(getIDocumentRedlineAccess()); + SwNodeIndex const prev(rTableNodes.begin()->begin()->aStart, -1); + SwNodeIndex const* pPrev(&prev); + // pPrev could point to non-textnode now + for (const auto& rRow : rTableNodes) + { + for (const auto& rCell : rRow) + { + assert(SwNodeIndex(*pPrev, +1) == rCell.aStart); + SwPaM pam(rCell.aStart, 0, *pPrev, + (pPrev->GetNode().IsContentNode()) + ? pPrev->GetNode().GetContentNode()->Len() : 0); + rIDRA.SplitRedline(pam); + pPrev = &rCell.aEnd; + } + } + // another one to break between last cell and node after table + SwPaM pam(SwNodeIndex(*pPrev, +1), 0, *pPrev, + (pPrev->GetNode().IsContentNode()) + ? pPrev->GetNode().GetContentNode()->Len() : 0); + rIDRA.SplitRedline(pam); + } + + // We always use Upper to insert the Table + SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() ); + + GetIDocumentUndoRedo().DoUndo(bUndo); + + // Create the Box/Line/Table construct + SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() ); + + // All Lines have a left-to-right Fill Order + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + // The Table's SSize is USHRT_MAX + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX )); + + /* If the first node in the selection is a context node and if it + has an item FRAMEDIR set (no default) propagate the item to the + replacing table. */ + if (pSttContentNd) + { + const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet(); + const SfxPoolItem *pItem = nullptr; + + if (SfxItemState::SET == aNdSet.GetItemState( RES_FRAMEDIR, true, &pItem ) + && pItem != nullptr) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications + //until after RegisterToFormat is completed + bool bEnableSetModified = getIDocumentState().IsEnableSetModified(); + getIDocumentState().SetEnableSetModified(false); + + SwTableNode* pTableNd = GetNodes().TextToTable( + rTableNodes, pTableFormat, pLineFormat, pBoxFormat ); + + SwTable& rNdTable = pTableNd->GetTable(); + rNdTable.RegisterToFormat(*pTableFormat); + + if( !pBoxFormat->HasWriterListeners() ) + { + // The Box's Formats already have the right size, we must only set + // the right Border/AutoFormat. + pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() ); + delete pBoxFormat; + } + + sal_uLong nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + getIDocumentState().SetEnableSetModified(bEnableSetModified); + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + return &rNdTable; +} + +std::unique_ptr<SwNodeRange> SwNodes::ExpandRangeForTableBox(const SwNodeRange & rRange) +{ + bool bChanged = false; + + SwNodeIndex aNewStart = rRange.aStart; + SwNodeIndex aNewEnd = rRange.aEnd; + + SwNodeIndex aEndIndex = rRange.aEnd; + SwNodeIndex aIndex = rRange.aStart; + + while (aIndex < aEndIndex) + { + SwNode& rNode = aIndex.GetNode(); + + if (rNode.IsStartNode()) + { + // advance aIndex to the end node of this start node + SwNode * pEndNode = rNode.EndOfSectionNode(); + aIndex = *pEndNode; + + if (aIndex > aNewEnd) + { + aNewEnd = aIndex; + bChanged = true; + } + } + else if (rNode.IsEndNode()) + { + SwNode * pStartNode = rNode.StartOfSectionNode(); + SwNodeIndex aStartIndex = *pStartNode; + + if (aStartIndex < aNewStart) + { + aNewStart = aStartIndex; + bChanged = true; + } + } + + if (aIndex < aEndIndex) + ++aIndex; + } + + SwNode * pNode = &aIndex.GetNode(); + while (pNode->IsEndNode() && aIndex < Count() - 1) + { + SwNode * pStartNode = pNode->StartOfSectionNode(); + SwNodeIndex aStartIndex(*pStartNode); + aNewStart = aStartIndex; + aNewEnd = aIndex; + bChanged = true; + + ++aIndex; + pNode = &aIndex.GetNode(); + } + + std::unique_ptr<SwNodeRange> pResult; + if (bChanged) + pResult.reset(new SwNodeRange(aNewStart, aNewEnd)); + return pResult; +} + +static void +lcl_SetTableBoxWidths2(SwTable & rTable, size_t const nMaxBoxes, + SwTableBoxFormat & rBoxFormat, SwDoc & rDoc) +{ + // rhbz#820283, fdo#55462: set default box widths so table width is covered + SwTableLines & rLines = rTable.GetTabLines(); + for (size_t nTmpLine = 0; nTmpLine < rLines.size(); ++nTmpLine) + { + SwTableBoxes & rBoxes = rLines[nTmpLine]->GetTabBoxes(); + assert(!rBoxes.empty()); // ensured by convertToTable + size_t const nMissing = nMaxBoxes - rBoxes.size(); + if (nMissing) + { + // default width for box at the end of an incomplete line + SwTableBoxFormat *const pNewFormat = rDoc.MakeTableBoxFormat(); + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + pNewFormat->SetFormatAttr( SwFormatFrameSize(SwFrameSize::Variable, + nWidth * (nMissing + 1)) ); + pNewFormat->Add(rBoxes.back()); + } + } + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + // default width for all boxes not at the end of an incomplete line + rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth)); +} + +SwTableNode* SwNodes::TextToTable( const SwNodes::TableRanges_t & rTableNodes, + SwTableFormat* pTableFormat, + SwTableLineFormat* pLineFormat, + SwTableBoxFormat* pBoxFormat ) +{ + if( rTableNodes.empty() ) + return nullptr; + + SwTableNode * pTableNd = new SwTableNode( rTableNodes.begin()->begin()->aStart ); + //insert the end node after the last text node + SwNodeIndex aInsertIndex( rTableNodes.rbegin()->rbegin()->aEnd ); + ++aInsertIndex; + + //!! ownership will be transferred in c-tor to SwNodes array. + //!! Thus no real problem here... + new SwEndNode( aInsertIndex, *pTableNd ); + + SwDoc* pDoc = GetDoc(); + SwTable& rTable = pTableNd->GetTable(); + SwTableBox* pBox; + sal_uInt16 nLines, nMaxBoxes = 0; + + SwNodeIndex aNodeIndex = rTableNodes.begin()->begin()->aStart; + // delete frames of all contained content nodes + for( nLines = 0; aNodeIndex <= rTableNodes.rbegin()->rbegin()->aEnd; ++aNodeIndex,++nLines ) + { + SwNode& rNode = aNodeIndex.GetNode(); + if( rNode.IsContentNode() ) + { + lcl_RemoveBreaks(static_cast<SwContentNode&>(rNode), + (0 == nLines) ? pTableFormat : nullptr); + } + } + + nLines = 0; + for( const auto& rRow : rTableNodes ) + { + sal_uInt16 nBoxes = 0; + SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr ); + rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine); + + for( const auto& rCell : rRow ) + { + const SwNodeIndex aTmpIdx( rCell.aStart, 0 ); + + SwNodeIndex aCellEndIdx(rCell.aEnd); + ++aCellEndIdx; + SwStartNode* pSttNd = new SwStartNode( aTmpIdx, SwNodeType::Start, + SwTableBoxStartNode ); + + // Quotation of http://nabble.documentfoundation.org/Some-strange-lines-by-taking-a-look-at-the-bt-of-fdo-51916-tp3994561p3994639.html + // SwNode's constructor adds itself to the same SwNodes array as the other node (pSttNd). + // So this statement is only executed for the side-effect. + new SwEndNode( aCellEndIdx, *pSttNd ); + + //set the start node on all node of the current cell + SwNodeIndex aCellNodeIdx = rCell.aStart; + for(;aCellNodeIdx <= rCell.aEnd; ++aCellNodeIdx ) + { + aCellNodeIdx.GetNode().m_pStartOfSection = pSttNd; + //skip start/end node pairs + if( aCellNodeIdx.GetNode().IsStartNode() ) + aCellNodeIdx.Assign(*aCellNodeIdx.GetNode().EndOfSectionNode()); + } + + // assign Section to the Box + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + } + if( nMaxBoxes < nBoxes ) + nMaxBoxes = nBoxes; + + nLines++; + } + + lcl_SetTableBoxWidths2(rTable, nMaxBoxes, *pBoxFormat, *pDoc); + + return pTableNd; +} + +/** + * Table to Text + */ +bool SwDoc::TableToText( const SwTableNode* pTableNd, sal_Unicode cCh ) +{ + if( !pTableNd ) + return false; + + // #i34471# + // If this is triggered by SwUndoTableToText::Repeat() nobody ever deleted + // the table cursor. + SwEditShell* pESh = GetEditShell(); + if( pESh && pESh->IsTableMode() ) + pESh->ClearMark(); + + SwNodeRange aRg( *pTableNd, 0, *pTableNd->EndOfSectionNode() ); + std::unique_ptr<SwUndoTableToText> pUndo; + SwNodeRange* pUndoRg = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndoRg = new SwNodeRange( aRg.aStart, -1, aRg.aEnd, +1 ); + pUndo.reset(new SwUndoTableToText( pTableNd->GetTable(), cCh )); + } + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXNAME; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bool bRet = GetNodes().TableToText( aRg, cCh, pUndo.get() ); + if( pUndoRg ) + { + ++pUndoRg->aStart; + --pUndoRg->aEnd; + pUndo->SetRange( *pUndoRg ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + delete pUndoRg; + } + + if( bRet ) + getIDocumentState().SetModified(); + + return bRet; +} + +namespace { + +/** + * Use the ForEach method from PtrArray to recreate Text from a Table. + * The Boxes can also contain Lines! + */ +struct DelTabPara +{ + SwTextNode* pLastNd; + SwNodes& rNds; + SwUndoTableToText* pUndo; + sal_Unicode cCh; + + DelTabPara( SwNodes& rNodes, sal_Unicode cChar, SwUndoTableToText* pU ) : + pLastNd(nullptr), rNds( rNodes ), pUndo( pU ), cCh( cChar ) {} +}; + +} + +// Forward declare so that the Lines and Boxes can use recursion +static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara ); + +static void lcl_DelLine( SwTableLine* pLine, DelTabPara* pPara ) +{ + assert(pPara && "The parameters are missing!"); + DelTabPara aPara( *pPara ); + for( auto& rpBox : pLine->GetTabBoxes() ) + lcl_DelBox(rpBox, &aPara ); + if( pLine->GetUpper() ) // Is there a parent Box? + // Return the last TextNode + pPara->pLastNd = aPara.pLastNd; +} + +static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara ) +{ + assert(pDelPara && "The parameters are missing"); + + // Delete the Box's Lines + if( !pBox->GetTabLines().empty() ) + { + for( SwTableLine* pLine : pBox->GetTabLines() ) + lcl_DelLine( pLine, pDelPara ); + } + else + { + SwDoc* pDoc = pDelPara->rNds.GetDoc(); + SwNodeRange aDelRg( *pBox->GetSttNd(), 0, + *pBox->GetSttNd()->EndOfSectionNode() ); + // Delete the Section + pDelPara->rNds.SectionUp( &aDelRg ); + const SwTextNode* pCurTextNd = nullptr; + if (T2T_PARA != pDelPara->cCh && pDelPara->pLastNd) + pCurTextNd = aDelRg.aStart.GetNode().GetTextNode(); + if (nullptr != pCurTextNd) + { + // Join the current text node with the last from the previous box if possible + sal_uLong nNdIdx = aDelRg.aStart.GetIndex(); + --aDelRg.aStart; + if( pDelPara->pLastNd == &aDelRg.aStart.GetNode() ) + { + // Inserting the separator + SwIndex aCntIdx( pDelPara->pLastNd, + pDelPara->pLastNd->GetText().getLength()); + pDelPara->pLastNd->InsertText( OUString(pDelPara->cCh), aCntIdx, + SwInsertFlags::EMPTYEXPAND ); + if( pDelPara->pUndo ) + pDelPara->pUndo->AddBoxPos( *pDoc, nNdIdx, aDelRg.aEnd.GetIndex(), + aCntIdx.GetIndex() ); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + const sal_Int32 nOldTextLen = aCntIdx.GetIndex(); + pContentStore->Save( pDoc, nNdIdx, pCurTextNd->GetText().getLength() ); + + pDelPara->pLastNd->JoinNext(); + + if( !pContentStore->Empty() ) + pContentStore->Restore( pDoc, pDelPara->pLastNd->GetIndex(), nOldTextLen ); + } + else if( pDelPara->pUndo ) + { + ++aDelRg.aStart; + pDelPara->pUndo->AddBoxPos( *pDoc, nNdIdx, aDelRg.aEnd.GetIndex() ); + } + } + else if( pDelPara->pUndo ) + pDelPara->pUndo->AddBoxPos( *pDoc, aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() ); + --aDelRg.aEnd; + pDelPara->pLastNd = aDelRg.aEnd.GetNode().GetTextNode(); + + // Do not take over the NumberFormatting's adjustment + if( pDelPara->pLastNd && pDelPara->pLastNd->HasSwAttrSet() ) + pDelPara->pLastNd->ResetAttr( RES_PARATR_ADJUST ); + } +} + +bool SwNodes::TableToText( const SwNodeRange& rRange, sal_Unicode cCh, + SwUndoTableToText* pUndo ) +{ + // Is a Table selected? + if (rRange.aStart.GetIndex() >= rRange.aEnd.GetIndex()) + return false; + SwTableNode *const pTableNd(rRange.aStart.GetNode().GetTableNode()); + if (nullptr == pTableNd || + &rRange.aEnd.GetNode() != pTableNd->EndOfSectionNode() ) + return false; + + // If the Table was alone in a Section, create the Frames via the Table's Upper + SwNode2LayoutSaveUpperFrames * pNode2Layout = nullptr; + SwNodeIndex aFrameIdx( rRange.aStart ); + SwNode* pFrameNd = FindPrvNxtFrameNode( aFrameIdx, &rRange.aEnd.GetNode() ); + if( !pFrameNd ) + // Collect all Uppers + pNode2Layout = new SwNode2LayoutSaveUpperFrames(*pTableNd); + + // Delete the Frames + pTableNd->DelFrames(); + + // "Delete" the Table and merge all Lines/Boxes + DelTabPara aDelPara( *this, cCh, pUndo ); + for( SwTableLine *pLine : pTableNd->m_pTable->GetTabLines() ) + lcl_DelLine( pLine, &aDelPara ); + + // We just created a TextNode with fitting separator for every TableLine. + // Now we only need to delete the TableSection and create the Frames for the + // new TextNode. + SwNodeRange aDelRg( rRange.aStart, rRange.aEnd ); + + // If the Table has PageDesc/Break Attributes, carry them over to the + // first Text Node + { + // What about UNDO? + const SfxItemSet& rTableSet = pTableNd->m_pTable->GetFrameFormat()->GetAttrSet(); + const SfxPoolItem *pBreak, *pDesc; + if( SfxItemState::SET != rTableSet.GetItemState( RES_PAGEDESC, false, &pDesc )) + pDesc = nullptr; + if( SfxItemState::SET != rTableSet.GetItemState( RES_BREAK, false, &pBreak )) + pBreak = nullptr; + + if( pBreak || pDesc ) + { + SwNodeIndex aIdx( *pTableNd ); + SwContentNode* pCNd = GoNext( &aIdx ); + if( pBreak ) + pCNd->SetAttr( *pBreak ); + if( pDesc ) + pCNd->SetAttr( *pDesc ); + } + } + + SectionUp( &aDelRg ); // Delete this Section and by that the Table + // #i28006# + sal_uLong nStt = aDelRg.aStart.GetIndex(), nEnd = aDelRg.aEnd.GetIndex(); + if( !pFrameNd ) + { + pNode2Layout->RestoreUpperFrames( *this, + aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() ); + delete pNode2Layout; + } + else + { + SwContentNode *pCNd; + SwSectionNode *pSNd; + while( aDelRg.aStart.GetIndex() < nEnd ) + { + if( nullptr != ( pCNd = aDelRg.aStart.GetNode().GetContentNode())) + { + if( pFrameNd->IsContentNode() ) + static_cast<SwContentNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); + else if( pFrameNd->IsTableNode() ) + static_cast<SwTableNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); + else if( pFrameNd->IsSectionNode() ) + static_cast<SwSectionNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); + pFrameNd = pCNd; + } + else if( nullptr != ( pSNd = aDelRg.aStart.GetNode().GetSectionNode())) + { + if( !pSNd->GetSection().IsHidden() && !pSNd->IsContentHidden() ) + { + pSNd->MakeOwnFrames(&aFrameIdx, &aDelRg.aEnd); + break; + } + aDelRg.aStart = *pSNd->EndOfSectionNode(); + } + ++aDelRg.aStart; + } + } + + // #i28006# Fly frames have to be restored even if the table was + // #alone in the section + const SwFrameFormats& rFlyArr = *GetDoc()->GetSpzFrameFormats(); + for( auto pFly : rFlyArr ) + { + SwFrameFormat *const pFormat = pFly; + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) && + nStt <= pAPos->nNode.GetIndex() && + pAPos->nNode.GetIndex() < nEnd ) + { + pFormat->MakeFrames(); + } + } + + return true; +} + +/** + * Inserting Columns/Rows + */ +void SwDoc::InsertCol( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind ) +{ + if( !::CheckSplitCells( rCursor, nCnt + 1, SwTableSearchType::Col ) ) + return; + + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + ::GetTableSel( rCursor, aBoxes, SwTableSearchType::Col ); + + if( !aBoxes.empty() ) + InsertCol( aBoxes, nCnt, bBehind ); +} + +bool SwDoc::InsertCol( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSCOL, rBoxes, *pTableNd, + 0, 0, nCnt, bBehind, false )); + aTmpLst.insert( rTable.GetTabSortBoxes() ); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bRet = rTable.InsertCol( this, rBoxes, nCnt, bBehind ); + if (bRet) + { + getIDocumentState().SetModified(); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + } + } + + if( pUndo && bRet ) + { + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bRet; +} + +void SwDoc::InsertRow( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Row ); + + if( !aBoxes.empty() ) + InsertRow( aBoxes, nCnt, bBehind ); +} + +bool SwDoc::InsertRow( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW,rBoxes, *pTableNd, + 0, 0, nCnt, bBehind, false )); + aTmpLst.insert( rTable.GetTabSortBoxes() ); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bRet = rTable.InsertRow( this, rBoxes, nCnt, bBehind ); + if (bRet) + { + getIDocumentState().SetModified(); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + } + } + + if( pUndo && bRet ) + { + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bRet; + +} + +/** + * Deleting Columns/Rows + */ +void SwDoc::DeleteRow( const SwCursor& rCursor ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Row ); + if( ::HasProtectedCells( aBoxes )) + return; + + // Remove the Cursor from the to-be-deleted Section. + // The Cursor is placed after the table, except for + // - when there's another Line, we place it in that one + // - when a Line precedes it, we place it in that one + { + SwTableNode* pTableNd = rCursor.GetNode().FindTableNode(); + + if(dynamic_cast<const SwDDETable*>( & pTableNd->GetTable()) != nullptr) + return; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( aBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if( aFndBox.GetLines().empty() ) + return; + + SwEditShell* pESh = GetEditShell(); + if( pESh ) + { + pESh->KillPams(); + // FIXME: actually we should be iterating over all Shells! + } + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size() ) + { + FndBox_ *const pTmp = pFndBox->GetLines().front()->GetBoxes()[0].get(); + if( pTmp->GetBox()->GetSttNd() ) + break; // Else it gets too far + pFndBox = pTmp; + } + + SwTableLine* pDelLine = pFndBox->GetLines().back()->GetLine(); + SwTableBox* pDelBox = pDelLine->GetTabBoxes().back(); + while( !pDelBox->GetSttNd() ) + { + SwTableLine* pLn = pDelBox->GetTabLines()[ + pDelBox->GetTabLines().size()-1 ]; + pDelBox = pLn->GetTabBoxes().back(); + } + SwTableBox* pNextBox = pDelLine->FindNextBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindNextBox( pTableNd->GetTable(), pNextBox ); + + if( !pNextBox ) // No succeeding Boxes? Then take the preceding one + { + pDelLine = pFndBox->GetLines().front()->GetLine(); + pDelBox = pDelLine->GetTabBoxes()[ 0 ]; + while( !pDelBox->GetSttNd() ) + pDelBox = pDelBox->GetTabLines()[0]->GetTabBoxes()[0]; + pNextBox = pDelLine->FindPreviousBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindPreviousBox( pTableNd->GetTable(), pNextBox ); + } + + sal_uLong nIdx; + if( pNextBox ) // Place the Cursor here + nIdx = pNextBox->GetSttIdx() + 1; + else // Else after the Table + nIdx = pTableNd->EndOfSectionIndex() + 1; + + SwNodeIndex aIdx( GetNodes(), nIdx ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetNodes().GoNext( &aIdx ); + + if( pCNd ) + { + // Change the Shell's Cursor or the one passed? + SwPaM* pPam = const_cast<SwPaM*>(static_cast<SwPaM const *>(&rCursor)); + pPam->GetPoint()->nNode = aIdx; + pPam->GetPoint()->nContent.Assign( pCNd, 0 ); + pPam->SetMark(); // Both want a part of it + pPam->DeleteMark(); + } + } + + // Thus delete the Rows + GetIDocumentUndoRedo().StartUndo(SwUndoId::ROW_DELETE, nullptr); + DeleteRowCol( aBoxes ); + GetIDocumentUndoRedo().EndUndo(SwUndoId::ROW_DELETE, nullptr); +} + +void SwDoc::DeleteCol( const SwCursor& rCursor ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Col ); + if( ::HasProtectedCells( aBoxes )) + return; + + // The Cursors need to be removed from the to-be-deleted range. + // Always place them after/on top of the Table; they are always set + // to the old position via the document position. + SwEditShell* pESh = GetEditShell(); + if( pESh ) + { + const SwNode* pNd = rCursor.GetNode().FindTableBoxStartNode(); + pESh->ParkCursor( SwNodeIndex( *pNd ) ); + } + + // Thus delete the Columns + GetIDocumentUndoRedo().StartUndo(SwUndoId::COL_DELETE, nullptr); + DeleteRowCol( aBoxes, true ); + GetIDocumentUndoRedo().EndUndo(SwUndoId::COL_DELETE, nullptr); +} + +bool SwDoc::DeleteRowCol( const SwSelBoxes& rBoxes, bool bColumn ) +{ + if( ::HasProtectedCells( rBoxes )) + return false; + + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + if( dynamic_cast<const SwDDETable*>( &pTableNd->GetTable() ) != nullptr) + return false; + + ::ClearFEShellTabCols(*this, nullptr); + SwSelBoxes aSelBoxes( rBoxes ); + SwTable &rTable = pTableNd->GetTable(); + long nMin = 0; + long nMax = 0; + if( rTable.IsNewModel() ) + { + if( bColumn ) + rTable.ExpandColumnSelection( aSelBoxes, nMin, nMax ); + else + rTable.FindSuperfluousRows( aSelBoxes ); + } + + // Are we deleting the whole Table? + const sal_uLong nTmpIdx1 = pTableNd->GetIndex(); + const sal_uLong nTmpIdx2 = aSelBoxes.back()->GetSttNd()->EndOfSectionIndex() + 1; + if( pTableNd->GetTable().GetTabSortBoxes().size() == aSelBoxes.size() && + aSelBoxes[0]->GetSttIdx()-1 == nTmpIdx1 && + nTmpIdx2 == pTableNd->EndOfSectionIndex() ) + { + bool bNewTextNd = false; + // Is it alone in a FlyFrame? + SwNodeIndex aIdx( *pTableNd, -1 ); + const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode(); + if( pSttNd ) + { + const sal_uLong nTableEnd = pTableNd->EndOfSectionIndex() + 1; + const sal_uLong nSectEnd = pSttNd->EndOfSectionIndex(); + if( nTableEnd == nSectEnd ) + { + if( SwFlyStartNode == pSttNd->GetStartNodeType() ) + { + SwFrameFormat* pFormat = pSttNd->GetFlyFormat(); + if( pFormat ) + { + // That's the FlyFormat we're looking for + getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + return true; + } + } + // No Fly? Thus Header or Footer: always leave a TextNode + // We can forget about Undo then! + bNewTextNd = true; + } + } + + // No Fly? Then it is a Header or Footer, so keep always a TextNode + ++aIdx; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() ); + + if( bNewTextNd ) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); + if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + { + *aSavePaM.GetMark() = SwPosition( *pTableNd ); + aSavePaM.Move( fnMoveBackward, GoInNode ); + } + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + } + + // Move hard PageBreaks to the succeeding Node + bool bSavePageBreak = false, bSavePageDesc = false; + sal_uLong nNextNd = pTableNd->EndOfSectionIndex()+1; + SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pNextNd->SetAttr( *pItem ); + bSavePageDesc = true; + } + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pNextNd->SetAttr( *pItem ); + bSavePageBreak = true; + } + } + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete( aPaM )); + if( bNewTextNd ) + pUndo->SetTableDelLastNd(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName()); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + else + { + if( bNewTextNd ) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); + if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + { + *aSavePaM.GetMark() = SwPosition( *pTableNd ); + aSavePaM.Move( fnMoveBackward, GoInNode ); + } + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + } + + // Move hard PageBreaks to the succeeding Node + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + } + + pTableNd->DelFrames(); + getIDocumentContentOperations().DeleteSection( pTableNd ); + } + + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + + return true; + } + + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_DELBOX, aSelBoxes, *pTableNd, + nMin, nMax, 0, false, false )); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + if (rTable.IsNewModel()) + { + if (bColumn) + rTable.PrepareDeleteCol( nMin, nMax ); + rTable.FindSuperfluousRows( aSelBoxes ); + if (pUndo) + pUndo->ReNewBoxes( aSelBoxes ); + } + bRet = rTable.DeleteSel( this, aSelBoxes, nullptr, pUndo.get(), true, true ); + if (bRet) + { + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + } + } + + if( pUndo && bRet ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + return bRet; +} + +/** + * Split up/merge Boxes in the Table + */ +bool SwDoc::SplitTable( const SwSelBoxes& rBoxes, bool bVert, sal_uInt16 nCnt, + bool bSameHeight ) +{ + OSL_ENSURE( !rBoxes.empty() && nCnt, "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + std::vector<sal_uLong> aNdsCnts; + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_SPLIT, rBoxes, *pTableNd, 0, 0, + nCnt, bVert, bSameHeight )); + + aTmpLst.insert( rTable.GetTabSortBoxes() ); + if( !bVert ) + { + for (size_t n = 0; n < rBoxes.size(); ++n) + { + const SwStartNode* pSttNd = rBoxes[ n ]->GetSttNd(); + aNdsCnts.push_back( pSttNd->EndOfSectionIndex() - + pSttNd->GetIndex() ); + } + } + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + if (bVert) + bRet = rTable.SplitCol( this, rBoxes, nCnt ); + else + bRet = rTable.SplitRow( this, rBoxes, nCnt, bSameHeight ); + + if (bRet) + { + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + } + } + + if( pUndo && bRet ) + { + if( bVert ) + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + else + pUndo->SaveNewBoxes( *pTableNd, aTmpLst, rBoxes, aNdsCnts ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + return bRet; +} + +TableMergeErr SwDoc::MergeTable( SwPaM& rPam ) +{ + // Check if the current cursor's Point/Mark are inside a Table + SwTableNode* pTableNd = rPam.GetNode().FindTableNode(); + if( !pTableNd ) + return TableMergeErr::NoSelection; + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr ) + return TableMergeErr::NoSelection; + TableMergeErr nRet = TableMergeErr::NoSelection; + if( !rTable.IsNewModel() ) + { + nRet =::CheckMergeSel( rPam ); + if( TableMergeErr::Ok != nRet ) + return nRet; + nRet = TableMergeErr::NoSelection; + } + + // #i33394# + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_MERGE, nullptr ); + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + std::unique_ptr<SwUndoTableMerge> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoTableMerge( rPam )); + + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + SwSelBoxes aMerged; + SwTableBox* pMergeBox; + + if( !rTable.PrepareMerge( rPam, aBoxes, aMerged, &pMergeBox, pUndo.get() ) ) + { // No cells found to merge + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pUndo ) + { + pUndo.reset(); + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, & nLastUndoId) + && (SwUndoId::REDLINE == nLastUndoId)) + { + // FIXME: why is this horrible cleanup necessary? + SwUndoRedline *const pU = dynamic_cast<SwUndoRedline*>( + GetUndoManager().RemoveLastUndo()); + if (pU && pU->GetRedlSaveCount()) + { + SwEditShell *const pEditShell(GetEditShell()); + assert(pEditShell); + ::sw::UndoRedoContext context(*this, *pEditShell); + static_cast<SfxUndoAction *>(pU)->UndoWithContext(context); + } + delete pU; + } + } + } + else + { + // The PaMs need to be removed from the to-be-deleted range. Thus always place + // them at the end of/on top of the Table; it's always set to the old position via + // the Document Position. + // For a start remember an index for the temporary position, because we cannot + // access it after GetMergeSel + { + rPam.DeleteMark(); + rPam.GetPoint()->nNode = *pMergeBox->GetSttNd(); + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); + rPam.SetMark(); + rPam.DeleteMark(); + + SwPaM* pTmp = &rPam; + while( &rPam != ( pTmp = pTmp->GetNext() )) + for( int i = 0; i < 2; ++i ) + pTmp->GetBound( static_cast<bool>(i) ) = *rPam.GetPoint(); + + if (SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(&rPam)) + { + // tdf#135098 update selection so rPam's m_SelectedBoxes is updated + // to not contain the soon to-be-deleted SwTableBox so if the rPam + // is queried via a11y it doesn't claim the deleted cell still + // exists + pTableCursor->NewTableSelection(); + } + } + + // Merge them + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + if( pTableNd->GetTable().Merge( this, aBoxes, aMerged, pMergeBox, pUndo.get() )) + { + nRet = TableMergeErr::Ok; + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + + rPam.GetPoint()->nNode = *pMergeBox->GetSttNd(); + rPam.Move(); + + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_MERGE, nullptr ); + return nRet; +} + +SwTableNode::SwTableNode( const SwNodeIndex& rIdx ) + : SwStartNode( rIdx, SwNodeType::Table ) +{ + m_pTable.reset(new SwTable); +} + +SwTableNode::~SwTableNode() +{ + // Notify UNO wrappers + GetTable().GetFrameFormat()->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying)); + DelFrames(); + m_pTable->SetTableNode(this); // set this so that ~SwDDETable can read it! + m_pTable.reset(); +} + +SwTabFrame *SwTableNode::MakeFrame( SwFrame* pSib ) +{ + return new SwTabFrame( *m_pTable, pSib ); +} + +/** + * Creates all Views from the Document for the preceding Node. The resulting ContentFrames + * are added to the corresponding Layout. + */ +void SwTableNode::MakeFramesForAdjacentContentNode(const SwNodeIndex & rIdx) +{ + if( !GetTable().GetFrameFormat()->HasWriterListeners()) // Do we actually have Frame? + return; + + SwFrame *pFrame; + SwContentNode * pNode = rIdx.GetNode().GetContentNode(); + + OSL_ENSURE( pNode, "No ContentNode or CopyNode and new Node is identical"); + + bool bBefore = rIdx < GetIndex(); + + SwNode2Layout aNode2Layout( *this, rIdx.GetIndex() ); + + while( nullptr != (pFrame = aNode2Layout.NextFrame()) ) + { + if (pFrame->getRootFrame()->IsHideRedlines() + && !pNode->IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwFrame *pNew = pNode->MakeFrame( pFrame ); + // Will the Node receive Frames before or after? + if ( bBefore ) + // The new one precedes me + pNew->Paste( pFrame->GetUpper(), pFrame ); + else + // The new one succeeds me + pNew->Paste( pFrame->GetUpper(), pFrame->GetNext() ); + } +} + +/** + * Create a TableFrame for every Shell and insert before the corresponding ContentFrame. + */ +void SwTableNode::MakeOwnFrames(SwNodeIndex* pIdxBehind) +{ + OSL_ENSURE( pIdxBehind, "No Index" ); + *pIdxBehind = *this; + SwNode *pNd = GetNodes().FindPrvNxtFrameNode( *pIdxBehind, EndOfSectionNode() ); + if( !pNd ) + return ; + + SwFrame *pFrame( nullptr ); + SwLayoutFrame *pUpper( nullptr ); + SwNode2Layout aNode2Layout( *pNd, GetIndex() ); + while( nullptr != (pUpper = aNode2Layout.UpperFrame( pFrame, *this )) ) + { + if (pUpper->getRootFrame()->IsHideRedlines() + && !IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwTabFrame* pNew = MakeFrame( pUpper ); + pNew->Paste( pUpper, pFrame ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pNew->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pNew->FindPrevCnt()) ); + } + } + pNew->RegistFlys(); + } +} + +void SwTableNode::DelFrames(SwRootFrame const*const pLayout) +{ + /* For a start, cut out and delete the TabFrames (which will also delete the Columns and Rows) + The TabFrames are attached to the FrameFormat of the SwTable. + We need to delete them in a more cumbersome way, for the Master to also delete the Follows. */ + + SwIterator<SwTabFrame,SwFormat> aIter( *(m_pTable->GetFrameFormat()) ); + SwTabFrame *pFrame = aIter.First(); + while ( pFrame ) + { + bool bAgain = false; + { + if (!pFrame->IsFollow() && (!pLayout || pLayout == pFrame->getRootFrame())) + { + while ( pFrame->HasFollow() ) + pFrame->JoinAndDelFollows(); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pFrame->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pFrame->FindPrevCnt()) ); + } + } + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + bAgain = true; + } + } + pFrame = bAgain ? aIter.First() : aIter.Next(); + } +} + +void SwTableNode::SetNewTable( std::unique_ptr<SwTable> pNewTable, bool bNewFrames ) +{ + DelFrames(); + m_pTable->SetTableNode(this); + m_pTable = std::move(pNewTable); + if( bNewFrames ) + { + SwNodeIndex aIdx( *EndOfSectionNode()); + GetNodes().GoNext( &aIdx ); + MakeOwnFrames(&aIdx); + } +} + +void SwTableNode::RemoveRedlines() +{ + SwDoc* pDoc = GetDoc(); + if (pDoc) + { + SwTable& rTable = GetTable(); + if ( pDoc->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + pDoc->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAllTableRedlines( pDoc, rTable, true, RedlineType::Any ); + } +} + +void SwDoc::GetTabCols( SwTabCols &rFill, const SwCellFrame* pBoxFrame ) +{ + OSL_ENSURE( pBoxFrame, "pBoxFrame needs to be specified!" ); + if( !pBoxFrame ) + return; + + SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + const SwTableBox* pBox = pBoxFrame->GetTabBox(); + + // Set fixed points, LeftMin in Document coordinates, all others relative + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + rFill.SetLeftMin ( nLeftMin ); + rFill.SetLeft ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) ); + rFill.SetRight ( aRectFnSet.GetRight(pTab->getFramePrintArea())); + rFill.SetRightMax( nRightMax - nLeftMin ); + + pTab->GetTable()->GetTabCols( rFill, pBox ); +} + +// Here are some little helpers used in SwDoc::GetTabRows + +#define ROWFUZZY 25 + +namespace { + +struct FuzzyCompare +{ + bool operator() ( long s1, long s2 ) const; +}; + +} + +bool FuzzyCompare::operator() ( long s1, long s2 ) const +{ + return ( s1 < s2 && std::abs( s1 - s2 ) > ROWFUZZY ); +} + +static bool lcl_IsFrameInColumn( const SwCellFrame& rFrame, SwSelBoxes const & rBoxes ) +{ + for (size_t i = 0; i < rBoxes.size(); ++i) + { + if ( rFrame.GetTabBox() == rBoxes[ i ] ) + return true; + } + + return false; +} + +void SwDoc::GetTabRows( SwTabCols &rFill, const SwCellFrame* pBoxFrame ) +{ + OSL_ENSURE( pBoxFrame, "GetTabRows called without pBoxFrame" ); + + // Make code robust: + if ( !pBoxFrame ) + return; + + // #i39552# Collection of the boxes of the current + // column has to be done at the beginning of this function, because + // the table may be formatted in ::GetTableSel. + SwDeletionChecker aDelCheck( pBoxFrame ); + + SwSelBoxes aBoxes; + const SwContentFrame* pContent = ::GetCellContent( *pBoxFrame ); + if ( pContent && pContent->IsTextFrame() ) + { + const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst()); + const SwCursor aTmpCursor( aPos, nullptr ); + ::GetTableSel( aTmpCursor, aBoxes, SwTableSearchType::Col ); + } + + // Make code robust: + if ( aDelCheck.HasBeenDeleted() ) + { + OSL_FAIL( "Current box has been deleted during GetTabRows()" ); + return; + } + + // Make code robust: + const SwTabFrame* pTab = pBoxFrame->FindTabFrame(); + OSL_ENSURE( pTab, "GetTabRows called without a table" ); + if ( !pTab ) + return; + + const SwFrame* pFrame = pTab->GetNextLayoutLeaf(); + + // Set fixed points, LeftMin in Document coordinates, all others relative + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const long nLeftMin = ( aRectFnSet.IsVert() ? + pTab->GetPrtLeft() - pPage->getFrameArea().Left() : + pTab->GetPrtTop() - pPage->getFrameArea().Top() ); + const long nLeft = aRectFnSet.IsVert() ? LONG_MAX : 0; + const long nRight = aRectFnSet.GetHeight(pTab->getFramePrintArea()); + const long nRightMax = aRectFnSet.IsVert() ? nRight : LONG_MAX; + + rFill.SetLeftMin( nLeftMin ); + rFill.SetLeft( nLeft ); + rFill.SetRight( nRight ); + rFill.SetRightMax( nRightMax ); + + typedef std::map< long, std::pair< long, long >, FuzzyCompare > BoundaryMap; + BoundaryMap aBoundaries; + BoundaryMap::iterator aIter; + std::pair< long, long > aPair; + + typedef std::map< long, bool > HiddenMap; + HiddenMap aHidden; + HiddenMap::iterator aHiddenIter; + + while ( pFrame && pTab->IsAnLower( pFrame ) ) + { + if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab ) + { + // upper and lower borders of current cell frame: + long nUpperBorder = aRectFnSet.GetTop(pFrame->getFrameArea()); + long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea()); + + // get boundaries for nUpperBorder: + aIter = aBoundaries.find( nUpperBorder ); + if ( aIter == aBoundaries.end() ) + { + aPair.first = nUpperBorder; aPair.second = LONG_MAX; + aBoundaries[ nUpperBorder ] = aPair; + } + + // get boundaries for nLowerBorder: + aIter = aBoundaries.find( nLowerBorder ); + if ( aIter == aBoundaries.end() ) + { + aPair.first = nUpperBorder; aPair.second = LONG_MAX; + } + else + { + nLowerBorder = (*aIter).first; + long nNewLowerBorderUpperBoundary = std::max( (*aIter).second.first, nUpperBorder ); + aPair.first = nNewLowerBorderUpperBoundary; aPair.second = LONG_MAX; + } + aBoundaries[ nLowerBorder ] = aPair; + + // calculate hidden flags for entry nUpperBorder/nLowerBorder: + long nTmpVal = nUpperBorder; + for ( sal_uInt8 i = 0; i < 2; ++i ) + { + aHiddenIter = aHidden.find( nTmpVal ); + if ( aHiddenIter == aHidden.end() ) + aHidden[ nTmpVal ] = !lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ); + else + { + if ( aHidden[ nTmpVal ] && + lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ) ) + aHidden[ nTmpVal ] = false; + } + nTmpVal = nLowerBorder; + } + } + + pFrame = pFrame->GetNextLayoutLeaf(); + } + + // transfer calculated values from BoundaryMap and HiddenMap into rFill: + size_t nIdx = 0; + for ( const auto& rEntry : aBoundaries ) + { + const long nTabTop = aRectFnSet.GetPrtTop(*pTab); + const long nKey = aRectFnSet.YDiff( rEntry.first, nTabTop ); + const std::pair< long, long > aTmpPair = rEntry.second; + const long nFirst = aRectFnSet.YDiff( aTmpPair.first, nTabTop ); + const long nSecond = aTmpPair.second; + + aHiddenIter = aHidden.find( rEntry.first ); + const bool bHidden = aHiddenIter != aHidden.end() && (*aHiddenIter).second; + rFill.Insert( nKey, nFirst, nSecond, bHidden, nIdx++ ); + } + + // delete first and last entry + OSL_ENSURE( rFill.Count(), "Deleting from empty vector. Fasten your seatbelts!" ); + // #i60818# There may be only one entry in rFill. Make + // code robust by checking count of rFill. + if ( rFill.Count() ) rFill.Remove( 0 ); + if ( rFill.Count() ) rFill.Remove( rFill.Count() - 1 ); + rFill.SetLastRowAllowedToChange( !pTab->HasFollowFlowLine() ); +} + +void SwDoc::SetTabCols( const SwTabCols &rNew, bool bCurRowOnly, + const SwCellFrame* pBoxFrame ) +{ + const SwTableBox* pBox = nullptr; + SwTabFrame *pTab = nullptr; + + if( pBoxFrame ) + { + pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + pBox = pBoxFrame->GetTabBox(); + } + else + { + OSL_ENSURE( false, "must specify pBoxFrame" ); + return ; + } + + // If the Table is still using relative values (USHRT_MAX) + // we need to switch to absolute ones. + SwTable& rTab = *pTab->GetTable(); + const SwFormatFrameSize& rTableFrameSz = rTab.GetFrameFormat()->GetFrameSize(); + SwRectFnSet aRectFnSet(pTab); + // #i17174# - With fix for #i9040# the shadow size is taken + // from the table width. Thus, add its left and right size to current table + // printing area width in order to get the correct table size attribute. + SwTwips nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + { + SvxShadowItem aShadow( rTab.GetFrameFormat()->GetShadow() ); + nPrtWidth += aShadow.CalcShadowSpace( SvxShadowItemSide::LEFT ) + + aShadow.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + } + if( nPrtWidth != rTableFrameSz.GetWidth() ) + { + SwFormatFrameSize aSz( rTableFrameSz ); + aSz.SetWidth( nPrtWidth ); + rTab.GetFrameFormat()->SetFormatAttr( aSz ); + } + + SwTabCols aOld( rNew.Count() ); + + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + // Set fixed points, LeftMin in Document coordinates, all others relative + aOld.SetLeftMin ( nLeftMin ); + aOld.SetLeft ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) ); + aOld.SetRight ( aRectFnSet.GetRight(pTab->getFramePrintArea())); + aOld.SetRightMax( nRightMax - nLeftMin ); + + rTab.GetTabCols( aOld, pBox ); + SetTabCols(rTab, rNew, aOld, pBox, bCurRowOnly ); +} + +void SwDoc::SetTabRows( const SwTabCols &rNew, bool bCurColOnly, + const SwCellFrame* pBoxFrame ) +{ + SwTabFrame *pTab = nullptr; + + if( pBoxFrame ) + { + pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + } + else + { + OSL_ENSURE( false, "must specify pBoxFrame" ); + return ; + } + + // If the Table is still using relative values (USHRT_MAX) + // we need to switch to absolute ones. + SwRectFnSet aRectFnSet(pTab); + SwTabCols aOld( rNew.Count() ); + + // Set fixed points, LeftMin in Document coordinates, all others relative + const SwPageFrame* pPage = pTab->FindPageFrame(); + + aOld.SetRight( aRectFnSet.GetHeight(pTab->getFramePrintArea()) ); + long nLeftMin; + if ( aRectFnSet.IsVert() ) + { + nLeftMin = pTab->GetPrtLeft() - pPage->getFrameArea().Left(); + aOld.SetLeft ( LONG_MAX ); + aOld.SetRightMax( aOld.GetRight() ); + + } + else + { + nLeftMin = pTab->GetPrtTop() - pPage->getFrameArea().Top(); + aOld.SetLeft ( 0 ); + aOld.SetRightMax( LONG_MAX ); + } + aOld.SetLeftMin ( nLeftMin ); + + GetTabRows( aOld, pBoxFrame ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_ATTR, nullptr ); + + // check for differences between aOld and rNew: + const size_t nCount = rNew.Count(); + const SwTable* pTable = pTab->GetTable(); + OSL_ENSURE( pTable, "My colleague told me, this couldn't happen" ); + + for ( size_t i = 0; i <= nCount; ++i ) + { + const size_t nIdxStt = aRectFnSet.IsVert() ? nCount - i : i - 1; + const size_t nIdxEnd = aRectFnSet.IsVert() ? nCount - i - 1 : i; + + const long nOldRowStart = i == 0 ? 0 : aOld[ nIdxStt ]; + const long nOldRowEnd = i == nCount ? aOld.GetRight() : aOld[ nIdxEnd ]; + const long nOldRowHeight = nOldRowEnd - nOldRowStart; + + const long nNewRowStart = i == 0 ? 0 : rNew[ nIdxStt ]; + const long nNewRowEnd = i == nCount ? rNew.GetRight() : rNew[ nIdxEnd ]; + const long nNewRowHeight = nNewRowEnd - nNewRowStart; + + const long nDiff = nNewRowHeight - nOldRowHeight; + if ( std::abs( nDiff ) >= ROWFUZZY ) + { + // For the old table model pTextFrame and pLine will be set for every box. + // For the new table model pTextFrame will be set if the box is not covered, + // but the pLine will be set if the box is not an overlapping box + // In the new table model the row height can be adjusted, + // when both variables are set. + const SwTextFrame* pTextFrame = nullptr; + const SwTableLine* pLine = nullptr; + + // Iterate over all SwCellFrames with Bottom = nOldPos + const SwFrame* pFrame = pTab->GetNextLayoutLeaf(); + while ( pFrame && pTab->IsAnLower( pFrame ) ) + { + if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab ) + { + const long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea()); + const sal_uLong nTabTop = aRectFnSet.GetPrtTop(*pTab); + if ( std::abs( aRectFnSet.YInc( nTabTop, nOldRowEnd ) - nLowerBorder ) <= ROWFUZZY ) + { + if ( !bCurColOnly || pFrame == pBoxFrame ) + { + const SwFrame* pContent = ::GetCellContent( static_cast<const SwCellFrame&>(*pFrame) ); + + if ( pContent && pContent->IsTextFrame() ) + { + const SwTableBox* pBox = static_cast<const SwCellFrame*>(pFrame)->GetTabBox(); + const long nRowSpan = pBox->getRowSpan(); + if( nRowSpan > 0 ) // Not overlapped + pTextFrame = static_cast<const SwTextFrame*>(pContent); + if( nRowSpan < 2 ) // Not overlapping for row height + pLine = pBox->GetUpper(); + if( pLine && pTextFrame ) // always for old table model + { + // The new row height must not to be calculated from an overlapping box + SwFormatFrameSize aNew( pLine->GetFrameFormat()->GetFrameSize() ); + const long nNewSize = aRectFnSet.GetHeight(pFrame->getFrameArea()) + nDiff; + if( nNewSize != aNew.GetHeight() ) + { + aNew.SetHeight( nNewSize ); + if ( SwFrameSize::Variable == aNew.GetHeightSizeType() ) + aNew.SetHeightSizeType( SwFrameSize::Minimum ); + // This position must not be in an overlapped box + const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst()); + const SwCursor aTmpCursor( aPos, nullptr ); + SetRowHeight( aTmpCursor, aNew ); + // For the new table model we're done, for the old one + // there might be another (sub)row to adjust... + if( pTable->IsNewModel() ) + break; + } + pLine = nullptr; + } + } + } + } + } + pFrame = pFrame->GetNextLayoutLeaf(); + } + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_ATTR, nullptr ); + + ::ClearFEShellTabCols(*this, nullptr); +} + +/** + * Direct access for UNO + */ +void SwDoc::SetTabCols(SwTable& rTab, const SwTabCols &rNew, const SwTabCols &rOld, + const SwTableBox *pStart, bool bCurRowOnly ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAttrTable>( *rTab.GetTableNode(), true )); + } + rTab.SetTabCols( rNew, rOld, pStart, bCurRowOnly ); + ::ClearFEShellTabCols(*this, nullptr); + SwClearFntCacheTextGlyphs(); + getIDocumentState().SetModified(); +} + +void SwDoc::SetRowsToRepeat( SwTable &rTable, sal_uInt16 nSet ) +{ + if( nSet == rTable.GetRowsToRepeat() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableHeadline>(rTable, rTable.GetRowsToRepeat(), nSet) ); + } + + SwMsgPoolItem aChg( RES_TBLHEADLINECHG ); + rTable.SetRowsToRepeat( nSet ); + rTable.GetFrameFormat()->ModifyNotification( &aChg, &aChg ); + getIDocumentState().SetModified(); +} + +void SwCollectTableLineBoxes::AddToUndoHistory( const SwContentNode& rNd ) +{ + if( pHst ) + pHst->Add( rNd.GetFormatColl(), rNd.GetIndex(), SwNodeType::Text ); +} + +void SwCollectTableLineBoxes::AddBox( const SwTableBox& rBox ) +{ + aPosArr.push_back(nWidth); + SwTableBox* p = const_cast<SwTableBox*>(&rBox); + m_Boxes.push_back(p); + nWidth = nWidth + static_cast<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); +} + +const SwTableBox* SwCollectTableLineBoxes::GetBoxOfPos( const SwTableBox& rBox ) +{ + const SwTableBox* pRet = nullptr; + + if( !aPosArr.empty() ) + { + std::vector<sal_uInt16>::size_type n; + for( n = 0; n < aPosArr.size(); ++n ) + if( aPosArr[ n ] == nWidth ) + break; + else if( aPosArr[ n ] > nWidth ) + { + if( n ) + --n; + break; + } + + if( n >= aPosArr.size() ) + --n; + + nWidth = nWidth + static_cast<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); + pRet = m_Boxes[ n ]; + } + return pRet; +} + +bool SwCollectTableLineBoxes::Resize( sal_uInt16 nOffset, sal_uInt16 nOldWidth ) +{ + if( !aPosArr.empty() ) + { + std::vector<sal_uInt16>::size_type n; + for( n = 0; n < aPosArr.size(); ++n ) + { + if( aPosArr[ n ] == nOffset ) + break; + else if( aPosArr[ n ] > nOffset ) + { + if( n ) + --n; + break; + } + } + + aPosArr.erase( aPosArr.begin(), aPosArr.begin() + n ); + m_Boxes.erase(m_Boxes.begin(), m_Boxes.begin() + n); + + size_t nArrSize = aPosArr.size(); + if (nArrSize) + { + if (nOldWidth == 0) + throw o3tl::divide_by_zero(); + + // Adapt the positions to the new Size + for( n = 0; n < nArrSize; ++n ) + { + sal_uLong nSize = nWidth; + nSize *= ( aPosArr[ n ] - nOffset ); + nSize /= nOldWidth; + aPosArr[ n ] = sal_uInt16( nSize ); + } + } + } + return !aPosArr.empty(); +} + +bool sw_Line_CollectBox( const SwTableLine*& rpLine, void* pPara ) +{ + SwCollectTableLineBoxes* pSplPara = static_cast<SwCollectTableLineBoxes*>(pPara); + if( pSplPara->IsGetValues() ) + for( const auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, pSplPara ); + else + for( auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, pSplPara ); + return true; +} + +void sw_Box_CollectBox( const SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ) +{ + auto nLen = pBox->GetTabLines().size(); + if( nLen ) + { + // Continue with the actual Line + if( pSplPara->IsGetFromTop() ) + nLen = 0; + else + --nLen; + + const SwTableLine* pLn = pBox->GetTabLines()[ nLen ]; + sw_Line_CollectBox( pLn, pSplPara ); + } + else + pSplPara->AddBox( *pBox ); +} + +void sw_BoxSetSplitBoxFormats( SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ) +{ + auto nLen = pBox->GetTabLines().size(); + if( nLen ) + { + // Continue with the actual Line + if( pSplPara->IsGetFromTop() ) + nLen = 0; + else + --nLen; + + const SwTableLine* pLn = pBox->GetTabLines()[ nLen ]; + sw_Line_CollectBox( pLn, pSplPara ); + } + else + { + const SwTableBox* pSrcBox = pSplPara->GetBoxOfPos( *pBox ); + SwFrameFormat* pFormat = pSrcBox->GetFrameFormat(); + + if( SplitTable_HeadlineOption::BorderCopy == pSplPara->GetMode() ) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + if( !rBoxItem.GetTop() ) + { + SvxBoxItem aNew( rBoxItem ); + aNew.SetLine( pFormat->GetBox().GetBottom(), SvxBoxItemLine::TOP ); + if( aNew != rBoxItem ) + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + } + } + else + { + sal_uInt16 const aTableSplitBoxSetRange[] { + RES_LR_SPACE, RES_UL_SPACE, + RES_BACKGROUND, RES_SHADOW, + RES_PROTECT, RES_PROTECT, + RES_VERT_ORIENT, RES_VERT_ORIENT, + 0 }; + + SfxItemSet aTmpSet( pFormat->GetDoc()->GetAttrPool(), + aTableSplitBoxSetRange ); + aTmpSet.Put( pFormat->GetAttrSet() ); + if( aTmpSet.Count() ) + pBox->ClaimFrameFormat()->SetFormatAttr( aTmpSet ); + + if( SplitTable_HeadlineOption::BoxAttrAllCopy == pSplPara->GetMode() ) + { + SwNodeIndex aIdx( *pSrcBox->GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + aIdx = *pBox->GetSttNd(); + SwContentNode* pDNd = aIdx.GetNodes().GoNext( &aIdx ); + + // If the Node is alone in the Section + if( 2 == pDNd->EndOfSectionIndex() - + pDNd->StartOfSectionIndex() ) + { + pSplPara->AddToUndoHistory( *pDNd ); + pDNd->ChgFormatColl( pCNd->GetFormatColl() ); + } + } + + // note conditional template + pBox->GetSttNd()->CheckSectionCondColl(); + } + } +} + +/** + * Splits a Table in the top-level Line which contains the Index. + * All succeeding top-level Lines go into a new Table/Node. + * + * @param bCalcNewSize true + * Calculate the new Size for both from the + * Boxes' Max; but only if Size is using absolute + * values (USHRT_MAX) + */ +bool SwDoc::SplitTable( const SwPosition& rPos, SplitTable_HeadlineOption eHdlnMode, + bool bCalcNewSize ) +{ + SwNode* pNd = &rPos.nNode.GetNode(); + SwTableNode* pTNd = pNd->FindTableNode(); + if( !pTNd || pNd->IsTableNode() ) + return false; + + if( dynamic_cast<const SwDDETable*>( &pTNd->GetTable() ) != nullptr) + return false; + + SwTable& rTable = pTNd->GetTable(); + rTable.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + SwTableFormulaUpdate aMsgHint( &rTable ); + + SwHistory aHistory; + if (GetIDocumentUndoRedo().DoesUndo()) + { + aMsgHint.m_pHistory = &aHistory; + } + + { + sal_uLong nSttIdx = pNd->FindTableBoxStartNode()->GetIndex(); + + // Find top-level Line + SwTableBox* pBox = rTable.GetTableBox( nSttIdx ); + if( pBox ) + { + SwTableLine* pLine = pBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // pLine contains the top-level Line now + aMsgHint.m_nSplitLine = rTable.GetTabLines().GetPos( pLine ); + } + + OUString sNewTableNm( GetUniqueTableName() ); + aMsgHint.m_aData.pNewTableNm = &sNewTableNm; + aMsgHint.m_eFlags = TBL_SPLITTBL; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rTable ); + aFndBox.DelFrames( rTable ); + + SwTableNode* pNew = GetNodes().SplitTable( rPos.nNode, false, bCalcNewSize ); + + if( pNew ) + { + std::unique_ptr<SwSaveRowSpan> pSaveRowSp = pNew->GetTable().CleanUpTopRowSpan( rTable.GetTabLines().size() ); + SwUndoSplitTable* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoSplitTable( + *pNew, std::move(pSaveRowSp), eHdlnMode, bCalcNewSize); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + if( aHistory.Count() ) + pUndo->SaveFormula( aHistory ); + } + + switch( eHdlnMode ) + { + // Set the lower Border of the preceding Line to + // the upper Border of the current one + case SplitTable_HeadlineOption::BorderCopy: + { + SwCollectTableLineBoxes aPara( false, eHdlnMode ); + SwTableLine* pLn = rTable.GetTabLines()[ + rTable.GetTabLines().size() - 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, &aPara ); + + aPara.SetValues( true ); + pLn = pNew->GetTable().GetTabLines()[ 0 ]; + for( auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aPara ); + + // Switch off repeating Header + pNew->GetTable().SetRowsToRepeat( 0 ); + } + break; + + // Take over the Attributes of the first Line to the new one + case SplitTable_HeadlineOption::BoxAttrCopy: + case SplitTable_HeadlineOption::BoxAttrAllCopy: + { + SwHistory* pHst = nullptr; + if( SplitTable_HeadlineOption::BoxAttrAllCopy == eHdlnMode && pUndo ) + pHst = pUndo->GetHistory(); + + SwCollectTableLineBoxes aPara( true, eHdlnMode, pHst ); + SwTableLine* pLn = rTable.GetTabLines()[ 0 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, &aPara ); + + aPara.SetValues( true ); + pLn = pNew->GetTable().GetTabLines()[ 0 ]; + for( auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aPara ); + } + break; + + case SplitTable_HeadlineOption::ContentCopy: + rTable.CopyHeadlineIntoTable( *pNew ); + if( pUndo ) + pUndo->SetTableNodeOffset( pNew->GetIndex() ); + break; + + case SplitTable_HeadlineOption::NONE: + // Switch off repeating the Header + pNew->GetTable().SetRowsToRepeat( 0 ); + break; + } + + // And insert Frames + SwNodeIndex aNdIdx( *pNew->EndOfSectionNode() ); + GetNodes().GoNext( &aNdIdx ); // To the next ContentNode + pNew->MakeOwnFrames( &aNdIdx ); + + // Insert a paragraph between the Table + GetNodes().MakeTextNode( SwNodeIndex( *pNew ), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + } + + // Update Layout + aFndBox.MakeFrames( rTable ); + + // TL_CHART2: need to inform chart of probably changed cell names + UpdateCharts( rTable.GetFrameFormat()->GetName() ); + + // update table style formatting of both the tables + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(pTNd); + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(pNew); + + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + + return nullptr != pNew; +} + +static bool lcl_ChgTableSize( SwTable& rTable ) +{ + // The Attribute must not be set via the Modify or else all Boxes are + // set back to 0. + // So lock the Format. + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SwFormatFrameSize aTableMaxSz( pFormat->GetFrameSize() ); + + if( USHRT_MAX == aTableMaxSz.GetWidth() ) + return false; + + bool bLocked = pFormat->IsModifyLocked(); + pFormat->LockModify(); + + aTableMaxSz.SetWidth( 0 ); + + SwTableLines& rLns = rTable.GetTabLines(); + for( auto pLn : rLns ) + { + SwTwips nMaxLnWidth = 0; + SwTableBoxes& rBoxes = pLn->GetTabBoxes(); + for( auto pBox : rBoxes ) + nMaxLnWidth += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + + if( nMaxLnWidth > aTableMaxSz.GetWidth() ) + aTableMaxSz.SetWidth( nMaxLnWidth ); + } + pFormat->SetFormatAttr( aTableMaxSz ); + if( !bLocked ) // Release the Lock if appropriate + pFormat->UnlockModify(); + + return true; +} + +namespace { + +class SplitTable_Para +{ + std::map<SwFrameFormat const *, SwFrameFormat*> aSrcDestMap; + SwTableNode* pNewTableNd; + SwTable& rOldTable; + +public: + SplitTable_Para( SwTableNode* pNew, SwTable& rOld ) + : aSrcDestMap(), pNewTableNd( pNew ), rOldTable( rOld ) + {} + SwFrameFormat* GetDestFormat( SwFrameFormat* pSrcFormat ) const + { + auto it = aSrcDestMap.find( pSrcFormat ); + return it == aSrcDestMap.end() ? nullptr : it->second; + } + + void InsertSrcDest( SwFrameFormat const * pSrcFormat, SwFrameFormat* pDestFormat ) + { aSrcDestMap[ pSrcFormat ] = pDestFormat; } + + void ChgBox( SwTableBox* pBox ) + { + rOldTable.GetTabSortBoxes().erase( pBox ); + pNewTableNd->GetTable().GetTabSortBoxes().insert( pBox ); + } +}; + +} + +static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara ); + +static void lcl_SplitTable_CpyLine( SwTableLine* pLn, SplitTable_Para* pPara ) +{ + SwFrameFormat *pSrcFormat = pLn->GetFrameFormat(); + SwTableLineFormat* pDestFormat = static_cast<SwTableLineFormat*>( pPara->GetDestFormat( pSrcFormat ) ); + if( pDestFormat == nullptr ) + { + pPara->InsertSrcDest( pSrcFormat, pLn->ClaimFrameFormat() ); + } + else + pLn->ChgFrameFormat( pDestFormat ); + + for( auto& rpBox : pLn->GetTabBoxes() ) + lcl_SplitTable_CpyBox(rpBox, pPara ); +} + +static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara ) +{ + SwFrameFormat *pSrcFormat = pBox->GetFrameFormat(); + SwTableBoxFormat* pDestFormat = static_cast<SwTableBoxFormat*>(pPara->GetDestFormat( pSrcFormat )); + if( pDestFormat == nullptr ) + { + pPara->InsertSrcDest( pSrcFormat, pBox->ClaimFrameFormat() ); + } + else + pBox->ChgFrameFormat( pDestFormat ); + + if( pBox->GetSttNd() ) + pPara->ChgBox( pBox ); + else + for( SwTableLine* pLine : pBox->GetTabLines() ) + lcl_SplitTable_CpyLine( pLine, pPara ); +} + +SwTableNode* SwNodes::SplitTable( const SwNodeIndex& rPos, bool bAfter, + bool bCalcNewSize ) +{ + SwNode* pNd = &rPos.GetNode(); + SwTableNode* pTNd = pNd->FindTableNode(); + if( !pTNd || pNd->IsTableNode() ) + return nullptr; + + sal_uLong nSttIdx = pNd->FindTableBoxStartNode()->GetIndex(); + + // Find this Box/top-level line + SwTable& rTable = pTNd->GetTable(); + SwTableBox* pBox = rTable.GetTableBox( nSttIdx ); + if( !pBox ) + return nullptr; + + SwTableLine* pLine = pBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // pLine now contains the top-level line + sal_uInt16 nLinePos = rTable.GetTabLines().GetPos( pLine ); + if( USHRT_MAX == nLinePos || + ( bAfter ? ++nLinePos >= rTable.GetTabLines().size() : !nLinePos )) + return nullptr; // Not found or last Line! + + // Find the first Box of the succeeding Line + SwTableLine* pNextLine = rTable.GetTabLines()[ nLinePos ]; + pBox = pNextLine->GetTabBoxes()[0]; + while( !pBox->GetSttNd() ) + pBox = pBox->GetTabLines()[0]->GetTabBoxes()[0]; + + // Insert an EndNode and TableNode into the Nodes Array + SwTableNode * pNewTableNd; + { + SwEndNode* pOldTableEndNd = pTNd->EndOfSectionNode()->GetEndNode(); + assert(pOldTableEndNd && "Where is the EndNode?"); + + SwNodeIndex aIdx( *pBox->GetSttNd() ); + new SwEndNode( aIdx, *pTNd ); + pNewTableNd = new SwTableNode( aIdx ); + pNewTableNd->GetTable().SetTableModel( rTable.IsNewModel() ); + + pOldTableEndNd->m_pStartOfSection = pNewTableNd; + pNewTableNd->m_pEndOfSection = pOldTableEndNd; + + SwNode* pBoxNd = aIdx.GetNode().GetStartNode(); + do { + OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" ); + pBoxNd->m_pStartOfSection = pNewTableNd; + pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ]; + } while( pBoxNd != pOldTableEndNd ); + } + + { + // Move the Lines + SwTable& rNewTable = pNewTableNd->GetTable(); + rNewTable.GetTabLines().insert( rNewTable.GetTabLines().begin(), + rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() ); + + /* From the back (bottom right) to the front (top left) deregister all Boxes from the + Chart Data Provider. The Modify event is triggered in the calling function. + TL_CHART2: */ + SwChartDataProvider *pPCD = rTable.GetFrameFormat()->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if( pPCD ) + { + for (SwTableLines::size_type k = nLinePos; k < rTable.GetTabLines().size(); ++k) + { + const SwTableLines::size_type nLineIdx = (rTable.GetTabLines().size() - 1) - k + nLinePos; + const SwTableBoxes::size_type nBoxCnt = rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes().size(); + for (SwTableBoxes::size_type j = 0; j < nBoxCnt; ++j) + { + const SwTableBoxes::size_type nIdx = nBoxCnt - 1 - j; + pPCD->DeleteBox( &rTable, *rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes()[nIdx] ); + } + } + } + + // Delete + sal_uInt16 nDeleted = rTable.GetTabLines().size() - nLinePos; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() ); + + // Move the affected Boxes. Make the Formats unique and correct the StartNodes + SplitTable_Para aPara( pNewTableNd, rTable ); + for( SwTableLine* pNewLine : rNewTable.GetTabLines() ) + lcl_SplitTable_CpyLine( pNewLine, &aPara ); + rTable.CleanUpBottomRowSpan( nDeleted ); + } + + { + // Copy the Table FrameFormat + SwFrameFormat* pOldTableFormat = rTable.GetFrameFormat(); + SwFrameFormat* pNewTableFormat = pOldTableFormat->GetDoc()->MakeTableFrameFormat( + pOldTableFormat->GetDoc()->GetUniqueTableName(), + pOldTableFormat->GetDoc()->GetDfltFrameFormat() ); + + *pNewTableFormat = *pOldTableFormat; + pNewTableNd->GetTable().RegisterToFormat( *pNewTableFormat ); + + pNewTableNd->GetTable().SetTableStyleName(rTable.GetTableStyleName()); + + // Calculate a new Size? + // lcl_ChgTableSize: Only execute the second call if the first call was + // successful, thus has an absolute Size + if( bCalcNewSize && lcl_ChgTableSize( rTable ) ) + lcl_ChgTableSize( pNewTableNd->GetTable() ); + } + + // TL_CHART2: need to inform chart of probably changed cell names + rTable.UpdateCharts(); + + return pNewTableNd; // That's it! +} + +/** + * rPos needs to be in the Table that remains + * + * @param bWithPrev merge the current Table with the preceding + * or succeeding one + */ +bool SwDoc::MergeTable( const SwPosition& rPos, bool bWithPrev, sal_uInt16 nMode ) +{ + SwTableNode* pTableNd = rPos.nNode.GetNode().FindTableNode(), *pDelTableNd; + if( !pTableNd ) + return false; + + SwNodes& rNds = GetNodes(); + if( bWithPrev ) + pDelTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + else + pDelTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + if( !pDelTableNd ) + return false; + + if( dynamic_cast<const SwDDETable*>( &pTableNd->GetTable() ) != nullptr || + dynamic_cast<const SwDDETable*>( &pDelTableNd->GetTable() ) != nullptr) + return false; + + // Delete HTML Layout + pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + pDelTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + // Both Tables are present; we can start + SwUndoMergeTable* pUndo = nullptr; + std::unique_ptr<SwHistory> pHistory; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoMergeTable( *pTableNd, *pDelTableNd, bWithPrev, nMode ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + pHistory.reset(new SwHistory); + } + + // Adapt all "TableFormulas" + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_aData.pDelTable = &pDelTableNd->GetTable(); + aMsgHint.m_eFlags = TBL_MERGETBL; + aMsgHint.m_pHistory = pHistory.get(); + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + // The actual merge + SwNodeIndex aIdx( bWithPrev ? *pTableNd : *pDelTableNd ); + bool bRet = rNds.MergeTable( aIdx, !bWithPrev, nMode ); + + if( pHistory ) + { + if( pHistory->Count() ) + pUndo->SaveFormula( *pHistory ); + pHistory.reset(); + } + if( bRet ) + { + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + } + return bRet; +} + +bool SwNodes::MergeTable( const SwNodeIndex& rPos, bool bWithPrev, + sal_uInt16 nMode ) +{ + SwTableNode* pDelTableNd = rPos.GetNode().GetTableNode(); + OSL_ENSURE( pDelTableNd, "Where did the TableNode go?" ); + + SwTableNode* pTableNd = (*this)[ rPos.GetIndex() - 1]->FindTableNode(); + OSL_ENSURE( pTableNd, "Where did the TableNode go?" ); + + if( !pDelTableNd || !pTableNd ) + return false; + + pDelTableNd->DelFrames(); + + SwTable& rDelTable = pDelTableNd->GetTable(); + SwTable& rTable = pTableNd->GetTable(); + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rTable ); + aFndBox.DelFrames( rTable ); + + // TL_CHART2: + // tell the charts about the table to be deleted and have them use their own data + GetDoc()->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &rDelTable ); + + // Sync the TableFormat's Width + { + const SwFormatFrameSize& rTableSz = rTable.GetFrameFormat()->GetFrameSize(); + const SwFormatFrameSize& rDelTableSz = rDelTable.GetFrameFormat()->GetFrameSize(); + if( rTableSz != rDelTableSz ) + { + // The needs correction + if( bWithPrev ) + rDelTable.GetFrameFormat()->SetFormatAttr( rTableSz ); + else + rTable.GetFrameFormat()->SetFormatAttr( rDelTableSz ); + } + } + + if( !bWithPrev ) + { + // Transfer all Attributes of the succeeding Table to the preceding one + // We do this, because the succeeding one is deleted when deleting the Node + rTable.SetRowsToRepeat( rDelTable.GetRowsToRepeat() ); + rTable.SetTableChgMode( rDelTable.GetTableChgMode() ); + + rTable.GetFrameFormat()->LockModify(); + *rTable.GetFrameFormat() = *rDelTable.GetFrameFormat(); + // Also switch the Name + rTable.GetFrameFormat()->SetName( rDelTable.GetFrameFormat()->GetName() ); + rTable.GetFrameFormat()->UnlockModify(); + } + + // Move the Lines and Boxes + SwTableLines::size_type nOldSize = rTable.GetTabLines().size(); + rTable.GetTabLines().insert( rTable.GetTabLines().begin() + nOldSize, + rDelTable.GetTabLines().begin(), rDelTable.GetTabLines().end() ); + rDelTable.GetTabLines().clear(); + + rTable.GetTabSortBoxes().insert( rDelTable.GetTabSortBoxes() ); + rDelTable.GetTabSortBoxes().clear(); + + // The preceding Table always remains, while the succeeding one is deleted + SwEndNode* pTableEndNd = pDelTableNd->EndOfSectionNode(); + pTableNd->m_pEndOfSection = pTableEndNd; + + SwNodeIndex aIdx( *pDelTableNd, 1 ); + + SwNode* pBoxNd = aIdx.GetNode().GetStartNode(); + do { + OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" ); + pBoxNd->m_pStartOfSection = pTableNd; + pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ]; + } while( pBoxNd != pTableEndNd ); + pBoxNd->m_pStartOfSection = pTableNd; + + aIdx -= 2; + DelNodes( aIdx, 2 ); + + // tweak the conditional styles at the first inserted Line + const SwTableLine* pFirstLn = rTable.GetTabLines()[ nOldSize ]; + if( 1 == nMode ) + { + // Set Header Template in the Line and save in the History + // if needed for Undo! + } + sw_LineSetHeadCondColl( pFirstLn ); + + // Clean up the Borders + if( nOldSize ) + { + SwGCLineBorder aPara( rTable ); + aPara.nLinePos = --nOldSize; + pFirstLn = rTable.GetTabLines()[ nOldSize ]; + sw_GC_Line_Border( pFirstLn, &aPara ); + } + + // Update Layout + aFndBox.MakeFrames( rTable ); + + return true; +} + +namespace { + +// Use the PtrArray's ForEach method +struct SetAFormatTabPara +{ + SwTableAutoFormat& rTableFormat; + SwUndoTableAutoFormat* pUndo; + sal_uInt16 nEndBox, nCurBox; + sal_uInt8 nAFormatLine, nAFormatBox; + bool bSingleRowTable; + + explicit SetAFormatTabPara( const SwTableAutoFormat& rNew ) + : rTableFormat( const_cast<SwTableAutoFormat&>(rNew) ), pUndo( nullptr ), + nEndBox( 0 ), nCurBox( 0 ), nAFormatLine( 0 ), nAFormatBox( 0 ), bSingleRowTable(false) + {} +}; + +} + +// Forward declare so that the Lines and Boxes can use recursion +static bool lcl_SetAFormatBox(FndBox_ &, SetAFormatTabPara *pSetPara, bool bResetDirect); +static bool lcl_SetAFormatLine(FndLine_ &, SetAFormatTabPara *pPara, bool bResetDirect); + +static bool lcl_SetAFormatLine(FndLine_ & rLine, SetAFormatTabPara *pPara, bool bResetDirect) +{ + for (auto const& it : rLine.GetBoxes()) + { + lcl_SetAFormatBox(*it, pPara, bResetDirect); + } + return true; +} + +static bool lcl_SetAFormatBox(FndBox_ & rBox, SetAFormatTabPara *pSetPara, bool bResetDirect) +{ + if (!rBox.GetUpper()->GetUpper()) // Box on first level? + { + if( !pSetPara->nCurBox ) + pSetPara->nAFormatBox = 0; + else if( pSetPara->nCurBox == pSetPara->nEndBox ) + pSetPara->nAFormatBox = 3; + else //Even column(1) or Odd column(2) + pSetPara->nAFormatBox = static_cast<sal_uInt8>(1 + ((pSetPara->nCurBox-1) & 1)); + } + + if (rBox.GetBox()->GetSttNd()) + { + SwTableBox* pSetBox = rBox.GetBox(); + if (!pSetBox->HasDirectFormatting() || bResetDirect) + { + if (bResetDirect) + pSetBox->SetDirectFormatting(false); + + SwDoc* pDoc = pSetBox->GetFrameFormat()->GetDoc(); + SfxItemSet aCharSet(pDoc->GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1>{}); + SfxItemSet aBoxSet(pDoc->GetAttrPool(), aTableBoxSetRange); + sal_uInt8 nPos = pSetPara->nAFormatLine * 4 + pSetPara->nAFormatBox; + const bool bSingleRowTable = pSetPara->bSingleRowTable; + const bool bSingleColTable = pSetPara->nEndBox == 0; + pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aCharSet, SwTableAutoFormatUpdateFlags::Char, nullptr); + pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aBoxSet, SwTableAutoFormatUpdateFlags::Box, pDoc->GetNumberFormatter()); + + if (aCharSet.Count()) + { + sal_uLong nSttNd = pSetBox->GetSttIdx()+1; + sal_uLong nEndNd = pSetBox->GetSttNd()->EndOfSectionIndex(); + for (; nSttNd < nEndNd; ++nSttNd) + { + SwContentNode* pNd = pDoc->GetNodes()[ nSttNd ]->GetContentNode(); + if (pNd) + pNd->SetAttr(aCharSet); + } + } + + if (aBoxSet.Count()) + { + if (pSetPara->pUndo && SfxItemState::SET == aBoxSet.GetItemState(RES_BOXATR_FORMAT)) + pSetPara->pUndo->SaveBoxContent( *pSetBox ); + + pSetBox->ClaimFrameFormat()->SetFormatAttr(aBoxSet); + } + } + } + else + { + // Not sure how this situation can occur, but apparently we have some kind of table in table. + // I am guessing at how to best handle singlerow in this situation. + const bool bOrigSingleRowTable = pSetPara->bSingleRowTable; + pSetPara->bSingleRowTable = rBox.GetLines().size() == 1; + for (auto const& rpFndLine : rBox.GetLines()) + { + lcl_SetAFormatLine(*rpFndLine, pSetPara, bResetDirect); + } + pSetPara->bSingleRowTable = bOrigSingleRowTable; + } + + if (!rBox.GetUpper()->GetUpper()) // a BaseLine + ++pSetPara->nCurBox; + return true; +} + +bool SwDoc::SetTableAutoFormat(const SwSelBoxes& rBoxes, const SwTableAutoFormat& rNew, bool bResetDirect, bool const isSetStyleName) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SwTable &table = pTableNd->GetTable(); + table.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get(); + } + + if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box) + pFndBox = pFndBox->GetUpper()->GetUpper(); + + // Disable Undo, but first store parameters + SwUndoTableAutoFormat* pUndo = nullptr; + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + pUndo = new SwUndoTableAutoFormat( *pTableNd, rNew ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + GetIDocumentUndoRedo().DoUndo(false); + } + + if (isSetStyleName) + { // tdf#98226 do this here where undo can record it + pTableNd->GetTable().SetTableStyleName(rNew.GetName()); + } + + rNew.RestoreTableProperties(table); + + SetAFormatTabPara aPara( rNew ); + FndLines_t& rFLns = pFndBox->GetLines(); + aPara.bSingleRowTable = rFLns.size() == 1; + + for (FndLines_t::size_type n = 0; n < rFLns.size(); ++n) + { + FndLine_* pLine = rFLns[n].get(); + + // Set Upper to 0 (thus simulate BaseLine) + FndBox_* pSaveBox = pLine->GetUpper(); + pLine->SetUpper( nullptr ); + + if( !n ) + aPara.nAFormatLine = 0; + else if (static_cast<size_t>(n+1) == rFLns.size()) + aPara.nAFormatLine = 3; + else + aPara.nAFormatLine = static_cast<sal_uInt8>(1 + ((n-1) & 1 )); + + aPara.nAFormatBox = 0; + aPara.nCurBox = 0; + aPara.nEndBox = pLine->GetBoxes().size()-1; + aPara.pUndo = pUndo; + for (auto const& it : pLine->GetBoxes()) + { + lcl_SetAFormatBox(*it, &aPara, bResetDirect); + } + + pLine->SetUpper( pSaveBox ); + } + + if( pUndo ) + { + GetIDocumentUndoRedo().DoUndo(bUndo); + } + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + + return true; +} + +/** + * Find out who has the Attributes + */ +bool SwDoc::GetTableAutoFormat( const SwSelBoxes& rBoxes, SwTableAutoFormat& rGet ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // Store table properties + SwTable &table = pTableNd->GetTable(); + rGet.StoreTableProperties(table); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get(); + } + + if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box) + pFndBox = pFndBox->GetUpper()->GetUpper(); + + FndLines_t& rFLns = pFndBox->GetLines(); + + sal_uInt16 aLnArr[4]; + aLnArr[0] = 0; + aLnArr[1] = 1 < rFLns.size() ? 1 : 0; + aLnArr[2] = 2 < rFLns.size() ? 2 : aLnArr[1]; + aLnArr[3] = rFLns.size() - 1; + + for( sal_uInt8 nLine = 0; nLine < 4; ++nLine ) + { + FndLine_& rLine = *rFLns[ aLnArr[ nLine ] ]; + + sal_uInt16 aBoxArr[4]; + aBoxArr[0] = 0; + aBoxArr[1] = 1 < rLine.GetBoxes().size() ? 1 : 0; + aBoxArr[2] = 2 < rLine.GetBoxes().size() ? 2 : aBoxArr[1]; + aBoxArr[3] = rLine.GetBoxes().size() - 1; + + for( sal_uInt8 nBox = 0; nBox < 4; ++nBox ) + { + SwTableBox* pFBox = rLine.GetBoxes()[ aBoxArr[ nBox ] ]->GetBox(); + // Always apply to the first ones + while( !pFBox->GetSttNd() ) + pFBox = pFBox->GetTabLines()[0]->GetTabBoxes()[0]; + + sal_uInt8 nPos = nLine * 4 + nBox; + SwNodeIndex aIdx( *pFBox->GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetNodes().GoNext( &aIdx ); + + if( pCNd ) + rGet.UpdateFromSet( nPos, pCNd->GetSwAttrSet(), + SwTableAutoFormatUpdateFlags::Char, nullptr ); + rGet.UpdateFromSet( nPos, pFBox->GetFrameFormat()->GetAttrSet(), + SwTableAutoFormatUpdateFlags::Box, + GetNumberFormatter() ); + } + } + + return true; +} + +SwTableAutoFormatTable& SwDoc::GetTableStyles() +{ + if (!m_pTableStyles) + { + m_pTableStyles.reset(new SwTableAutoFormatTable); + m_pTableStyles->Load(); + } + return *m_pTableStyles; +} + +OUString SwDoc::GetUniqueTableName() const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeTable" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpTableFrameFormatTable->size() + 1 ); + return newName; + } + + const OUString aName(SwResId(STR_TABLE_DEFNAME)); + + const size_t nFlagSize = ( mpTableFrameFormatTable->size() / 8 ) + 2; + + std::unique_ptr<sal_uInt8[]> pSetFlags( new sal_uInt8[ nFlagSize ] ); + memset( pSetFlags.get(), 0, nFlagSize ); + + for( size_t n = 0; n < mpTableFrameFormatTable->size(); ++n ) + { + const SwFrameFormat* pFormat = (*mpTableFrameFormatTable)[ n ]; + if( !pFormat->IsDefault() && IsUsed( *pFormat ) && + pFormat->GetName().startsWith( aName ) ) + { + // Get number and set the Flag + const sal_Int32 nNmLen = aName.getLength(); + size_t nNum = pFormat->GetName().copy( nNmLen ).toInt32(); + if( nNum-- && nNum < mpTableFrameFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + } + + // All numbers are flagged properly, thus calculate the right number + size_t nNum = mpTableFrameFormatTable->size(); + for( size_t n = 0; n < nFlagSize; ++n ) + { + auto nTmp = pSetFlags[ n ]; + if( nTmp != 0xFF ) + { + // Calculate the number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + + return aName + OUString::number( ++nNum ); +} + +SwTableFormat* SwDoc::FindTableFormatByName( const OUString& rName, bool bAll ) const +{ + const SwFormat* pRet = nullptr; + if( bAll ) + pRet = FindFormatByName( *mpTableFrameFormatTable, rName ); + else + { + // Only the ones set in the Doc + for( size_t n = 0; n < mpTableFrameFormatTable->size(); ++n ) + { + const SwFrameFormat* pFormat = (*mpTableFrameFormatTable)[ n ]; + if( !pFormat->IsDefault() && IsUsed( *pFormat ) && + pFormat->GetName() == rName ) + { + pRet = pFormat; + break; + } + } + } + return const_cast<SwTableFormat*>(static_cast<const SwTableFormat*>(pRet)); +} + +bool SwDoc::SetColRowWidthHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff ) +{ + SwTableNode* pTableNd = const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()); + std::unique_ptr<SwUndo> pUndo; + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + bool bRet = false; + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::ColLeft: + case TableChgWidthHeightType::ColRight: + case TableChgWidthHeightType::CellLeft: + case TableChgWidthHeightType::CellRight: + { + bRet = pTableNd->GetTable().SetColWidth( rCurrentBox, + eType, nAbsDiff, nRelDiff, + bUndo ? &pUndo : nullptr ); + } + break; + case TableChgWidthHeightType::RowBottom: + case TableChgWidthHeightType::CellTop: + case TableChgWidthHeightType::CellBottom: + bRet = pTableNd->GetTable().SetRowHeight( rCurrentBox, + eType, nAbsDiff, nRelDiff, + bUndo ? &pUndo : nullptr ); + break; + default: break; + } + + GetIDocumentUndoRedo().DoUndo(bUndo); // SetColWidth can turn it off + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + if( bRet ) + { + getIDocumentState().SetModified(); + } + return bRet; +} + +bool SwDoc::IsNumberFormat( const OUString& rString, sal_uInt32& F_Index, double& fOutNumber ) +{ + if( rString.getLength() > 308 ) // optimization matches svl:IsNumberFormat arbitrary value + return false; + + // remove any comment anchor marks + OUStringBuffer sStringBuffer(rString); + sal_Int32 nCommentPosition = sStringBuffer.indexOf( CH_TXTATR_INWORD ); + while( nCommentPosition != -1 ) + { + sStringBuffer.remove( nCommentPosition, 1 ); + nCommentPosition = sStringBuffer.indexOf( CH_TXTATR_INWORD, nCommentPosition ); + } + + return GetNumberFormatter()->IsNumberFormat( sStringBuffer.makeStringAndClear(), F_Index, fOutNumber ); +} + +void SwDoc::ChkBoxNumFormat( SwTableBox& rBox, bool bCallUpdate ) +{ + // Optimization: If the Box says it's Text, it remains Text + const SfxPoolItem* pNumFormatItem = nullptr; + if( SfxItemState::SET == rBox.GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT, + false, &pNumFormatItem ) && GetNumberFormatter()->IsTextFormat( + static_cast<const SwTableBoxNumFormat*>(pNumFormatItem)->GetValue() )) + return ; + + std::unique_ptr<SwUndoTableNumFormat> pUndo; + + bool bIsEmptyTextNd; + bool bChgd = true; + sal_uInt32 nFormatIdx; + double fNumber; + if( rBox.HasNumContent( fNumber, nFormatIdx, bIsEmptyTextNd ) ) + { + if( !rBox.IsNumberChanged() ) + bChgd = false; + else + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr ); + pUndo.reset(new SwUndoTableNumFormat( rBox )); + pUndo->SetNumFormat( nFormatIdx, fNumber ); + } + + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat()); + SfxItemSet aBoxSet( GetAttrPool(), svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{} ); + + bool bLockModify = true; + bool bSetNumberFormat = IsInsTableFormatNum(); + const bool bForceNumberFormat = IsInsTableFormatNum() && IsInsTableChangeNumFormat(); + + // if the user forced a number format in this cell previously, + // keep it, unless the user set that she wants the full number + // format recognition + if( pNumFormatItem && !bForceNumberFormat ) + { + sal_uLong nOldNumFormat = static_cast<const SwTableBoxNumFormat*>(pNumFormatItem)->GetValue(); + SvNumberFormatter* pNumFormatr = GetNumberFormatter(); + + SvNumFormatType nFormatType = pNumFormatr->GetType( nFormatIdx ); + if( nFormatType == pNumFormatr->GetType( nOldNumFormat ) || SvNumFormatType::NUMBER == nFormatType ) + { + // Current and specified NumFormat match + // -> keep old Format + nFormatIdx = nOldNumFormat; + bSetNumberFormat = true; + } + else + { + // Current and specified NumFormat do not match + // -> insert as Text + bLockModify = bSetNumberFormat = false; + } + } + + if( bSetNumberFormat || bForceNumberFormat ) + { + pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat()); + + aBoxSet.Put( SwTableBoxValue( fNumber )); + aBoxSet.Put( SwTableBoxNumFormat( nFormatIdx )); + } + + // It's not enough to only reset the Formula. + // Make sure that the Text is formatted accordingly + if( !bSetNumberFormat && !bIsEmptyTextNd && pNumFormatItem ) + { + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + } + + if( bLockModify ) pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + if( bLockModify ) pBoxFormat->UnlockModify(); + + if( bSetNumberFormat ) + pBoxFormat->SetFormatAttr( aBoxSet ); + } + } + else + { + // It's not a number + const SfxPoolItem* pValueItem = nullptr, *pFormatItem = nullptr; + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat()); + if( SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_FORMAT, + false, &pFormatItem ) || + SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_VALUE, + false, &pValueItem )) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr ); + pUndo.reset(new SwUndoTableNumFormat( rBox )); + } + + pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat()); + + // Remove all number formats + sal_uInt16 nWhich1 = RES_BOXATR_FORMULA; + if( !bIsEmptyTextNd ) + { + nWhich1 = RES_BOXATR_FORMAT; + + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( nWhich1 )); + } + pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE ); + } + else + bChgd = false; + } + + if( bChgd ) + { + if( pUndo ) + { + pUndo->SetBox( rBox ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + + const SwTableNode* pTableNd = rBox.GetSttNd()->FindTableNode(); + if( bCallUpdate ) + { + SwTableFormulaUpdate aTableUpdate( &pTableNd->GetTable() ); + getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); + + // TL_CHART2: update charts (when cursor leaves cell and + // automatic update is enabled) + if (AUTOUPD_FIELD_AND_CHARTS == GetDocumentSettingManager().getFieldUpdateFlags(true)) + pTableNd->GetTable().UpdateCharts(); + } + getIDocumentState().SetModified(); + } +} + +void SwDoc::SetTableBoxFormulaAttrs( SwTableBox& rBox, const SfxItemSet& rSet ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoTableNumFormat>(rBox, &rSet) ); + } + + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA )) + { + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + pBoxFormat->UnlockModify(); + } + else if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE )) + { + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + pBoxFormat->UnlockModify(); + } + pBoxFormat->SetFormatAttr( rSet ); + getIDocumentState().SetModified(); +} + +void SwDoc::ClearLineNumAttrs( SwPosition const & rPos ) +{ + SwPaM aPam(rPos); + aPam.Move(fnMoveBackward); + SwContentNode *pNode = aPam.GetContentNode(); + if ( nullptr == pNode ) + return ; + if( pNode->IsTextNode() ) + { + SwTextNode * pTextNode = pNode->GetTextNode(); + if (pTextNode && pTextNode->IsNumbered() + && pTextNode->GetText().isEmpty()) + { + const SfxPoolItem* pFormatItem = nullptr; + SfxItemSet rSet( pTextNode->GetDoc()->GetAttrPool(), + svl::Items<RES_PARATR_BEGIN, RES_PARATR_END - 1>{}); + pTextNode->SwContentNode::GetAttr( rSet ); + if ( SfxItemState::SET == rSet.GetItemState( RES_PARATR_NUMRULE , false , &pFormatItem ) ) + { + SwUndoDelNum * pUndo; + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo = new SwUndoDelNum( aPam ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + else + pUndo = nullptr; + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + aRegH.RegisterInModify( pTextNode , *pTextNode ); + if ( pUndo ) + pUndo->AddNode( *pTextNode ); + std::unique_ptr<SfxStringItem> pNewItem(static_cast<SfxStringItem*>(pFormatItem->Clone())); + pNewItem->SetValue(OUString()); + rSet.Put( std::move(pNewItem) ); + pTextNode->SetAttr( rSet ); + } + } + } +} + +void SwDoc::ClearBoxNumAttrs( const SwNodeIndex& rNode ) +{ + SwStartNode* pSttNd; + if( nullptr != ( pSttNd = rNode.GetNode(). + FindSttNodeByType( SwTableBoxStartNode )) && + 2 == pSttNd->EndOfSectionIndex() - pSttNd->GetIndex() ) + { + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + + const SfxPoolItem* pFormatItem = nullptr; + const SfxItemSet& rSet = pBox->GetFrameFormat()->GetAttrSet(); + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMAT, false, &pFormatItem ) || + SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA, false ) || + SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE, false )) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoTableNumFormat>(*pBox)); + } + + SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat(); + + // Keep TextFormats! + sal_uInt16 nWhich1 = RES_BOXATR_FORMAT; + if( pFormatItem && GetNumberFormatter()->IsTextFormat( + static_cast<const SwTableBoxNumFormat*>(pFormatItem)->GetValue() )) + nWhich1 = RES_BOXATR_FORMULA; + else + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + + pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE ); + getIDocumentState().SetModified(); + } + } +} + +/** + * Copies a Table from the same or another Doc into itself + * We create a new Table or an existing one is filled with the Content. + * We either fill in the Content from a certain Box or a certain TableSelection + * + * This method is called by edglss.cxx/fecopy.cxx + */ +bool SwDoc::InsCopyOfTable( SwPosition& rInsPos, const SwSelBoxes& rBoxes, + const SwTable* pCpyTable, bool bCpyName, bool bCorrPos ) +{ + bool bRet; + + const SwTableNode* pSrcTableNd = pCpyTable + ? pCpyTable->GetTableNode() + : rBoxes[ 0 ]->GetSttNd()->FindTableNode(); + + SwTableNode * pInsTableNd = rInsPos.nNode.GetNode().FindTableNode(); + + bool const bUndo( GetIDocumentUndoRedo().DoesUndo() ); + if( !pCpyTable && !pInsTableNd ) + { + std::unique_ptr<SwUndoCpyTable> pUndo; + if (bUndo) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoCpyTable(this)); + } + + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + bRet = pSrcTableNd->GetTable().MakeCopy( this, rInsPos, rBoxes, + bCpyName ); + } + + if( pUndo && bRet ) + { + pInsTableNd = GetNodes()[ rInsPos.nNode.GetIndex() - 1 ]->FindTableNode(); + + pUndo->SetTableSttIdx( pInsTableNd->GetIndex() ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else + { + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::On | + RedlineFlags::ShowInsert | + RedlineFlags::ShowDelete ); + + std::unique_ptr<SwUndoTableCpyTable> pUndo; + if (bUndo) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoTableCpyTable(this)); + GetIDocumentUndoRedo().DoUndo(false); + } + + rtl::Reference<SwDoc> xCpyDoc( const_cast<SwDoc*>(pSrcTableNd->GetDoc()) ); + bool bDelCpyDoc = xCpyDoc == this; + + if( bDelCpyDoc ) + { + // Copy the Table into a temporary Doc + xCpyDoc = new SwDoc; + + SwPosition aPos( SwNodeIndex( xCpyDoc->GetNodes().GetEndOfContent() )); + if( !pSrcTableNd->GetTable().MakeCopy( xCpyDoc.get(), aPos, rBoxes, true )) + { + xCpyDoc.clear(); + + if( pUndo ) + { + GetIDocumentUndoRedo().DoUndo(bUndo); + } + return false; + } + aPos.nNode -= 1; // Set to the Table's EndNode + pSrcTableNd = aPos.nNode.GetNode().FindTableNode(); + } + + const SwStartNode* pSttNd = rInsPos.nNode.GetNode().FindTableBoxStartNode(); + + rInsPos.nContent.Assign( nullptr, 0 ); + + // no complex into complex, but copy into or from new model is welcome + if( ( !pSrcTableNd->GetTable().IsTableComplex() || pInsTableNd->GetTable().IsNewModel() ) + && ( bDelCpyDoc || !rBoxes.empty() ) ) + { + // Copy the Table "relatively" + const SwSelBoxes* pBoxes; + SwSelBoxes aBoxes; + + if( bDelCpyDoc ) + { + SwTableBox* pBox = pInsTableNd->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "Box is not in this Table" ); + aBoxes.insert( pBox ); + pBoxes = &aBoxes; + } + else + pBoxes = &rBoxes; + + // Copy Table to the selected Lines + bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(), + *pBoxes, pUndo.get() ); + } + else + { + SwNodeIndex aNdIdx( *pSttNd, 1 ); + bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(), + aNdIdx, pUndo.get() ); + } + + xCpyDoc.clear(); + + if( pUndo ) + { + // If the Table could not be copied, delete the Undo object + GetIDocumentUndoRedo().DoUndo(bUndo); + if( bRet || !pUndo->IsEmpty() ) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + if( bCorrPos ) + { + rInsPos.nNode = *pSttNd; + rInsPos.nContent.Assign( GetNodes().GoNext( &rInsPos.nNode ), 0 ); + } + getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + if( bRet ) + { + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + } + return bRet; +} + +bool SwDoc::UnProtectTableCells( SwTable& rTable ) +{ + bool bChgd = false; + std::unique_ptr<SwUndoAttrTable> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoAttrTable( *rTable.GetTableNode() )); + + SwTableSortBoxes& rSrtBox = rTable.GetTabSortBoxes(); + for (size_t i = rSrtBox.size(); i; ) + { + SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + pBoxFormat->ResetFormatAttr( RES_PROTECT ); + bChgd = true; + } + } + + if( pUndo && bChgd ) + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + return bChgd; +} + +void SwDoc::UnProtectCells( const OUString& rName ) +{ + bool bChgd = false; + SwTableFormat* pFormat = FindTableFormatByName( rName ); + if( pFormat ) + { + bChgd = UnProtectTableCells( *SwTable::FindTable( pFormat ) ); + if( bChgd ) + getIDocumentState().SetModified(); + } +} + +bool SwDoc::UnProtectCells( const SwSelBoxes& rBoxes ) +{ + bool bChgd = false; + if( !rBoxes.empty() ) + { + std::unique_ptr<SwUndoAttrTable> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoAttrTable( *rBoxes[0]->GetSttNd()->FindTableNode() )); + + std::map<SwFrameFormat*, SwTableBoxFormat*> aFormatsMap; + for (size_t i = rBoxes.size(); i; ) + { + SwTableBox* pBox = rBoxes[ --i ]; + SwFrameFormat* pBoxFormat = pBox->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + std::map<SwFrameFormat*, SwTableBoxFormat*>::const_iterator const it = + aFormatsMap.find(pBoxFormat); + if (aFormatsMap.end() != it) + pBox->ChgFrameFormat(it->second); + else + { + SwTableBoxFormat *const pNewBoxFormat( + static_cast<SwTableBoxFormat*>(pBox->ClaimFrameFormat())); + pNewBoxFormat->ResetFormatAttr( RES_PROTECT ); + aFormatsMap.insert(std::make_pair(pBoxFormat, pNewBoxFormat)); + } + bChgd = true; + } + } + + if( pUndo && bChgd ) + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bChgd; +} + +void SwDoc::UnProtectTables( const SwPaM& rPam ) +{ + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + bool bChgd = false, bHasSel = rPam.HasMark() || + rPam.GetNext() != &rPam; + SwFrameFormats& rFormats = *GetTableFrameFormats(); + SwTable* pTable; + const SwTableNode* pTableNd; + for( auto n = rFormats.size(); n ; ) + if( nullptr != (pTable = SwTable::FindTable( rFormats[ --n ] )) && + nullptr != (pTableNd = pTable->GetTableNode() ) && + pTableNd->GetNodes().IsDocNodes() ) + { + sal_uLong nTableIdx = pTableNd->GetIndex(); + + // Check whether the Table is within the Selection + if( bHasSel ) + { + bool bFound = false; + SwPaM* pTmp = const_cast<SwPaM*>(&rPam); + do { + const SwPosition *pStt = pTmp->Start(), + *pEnd = pTmp->End(); + bFound = pStt->nNode.GetIndex() < nTableIdx && + nTableIdx < pEnd->nNode.GetIndex(); + + } while( !bFound && &rPam != ( pTmp = pTmp->GetNext() ) ); + if( !bFound ) + continue; // Continue searching + } + + // Lift the protection + bChgd |= UnProtectTableCells( *pTable ); + } + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + if( bChgd ) + getIDocumentState().SetModified(); +} + +bool SwDoc::HasTableAnyProtection( const SwPosition* pPos, + const OUString* pTableName, + bool* pFullTableProtection ) +{ + bool bHasProtection = false; + SwTable* pTable = nullptr; + if( pTableName ) + pTable = SwTable::FindTable( FindTableFormatByName( *pTableName ) ); + else if( pPos ) + { + SwTableNode* pTableNd = pPos->nNode.GetNode().FindTableNode(); + if( pTableNd ) + pTable = &pTableNd->GetTable(); + } + + if( pTable ) + { + SwTableSortBoxes& rSrtBox = pTable->GetTabSortBoxes(); + for (size_t i = rSrtBox.size(); i; ) + { + SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + if( !bHasProtection ) + { + bHasProtection = true; + if( !pFullTableProtection ) + break; + *pFullTableProtection = true; + } + } + else if( bHasProtection && pFullTableProtection ) + { + *pFullTableProtection = false; + break; + } + } + } + return bHasProtection; +} + +SwTableAutoFormat* SwDoc::MakeTableStyle(const OUString& rName, bool bBroadcast) +{ + SwTableAutoFormat aTableFormat(rName); + GetTableStyles().AddAutoFormat(aTableFormat); + SwTableAutoFormat* pTableFormat = GetTableStyles().FindAutoFormat(rName); + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleMake>(rName, this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetCreated); + + return pTableFormat; +} + +std::unique_ptr<SwTableAutoFormat> SwDoc::DelTableStyle(const OUString& rName, bool bBroadcast) +{ + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetErased); + + std::unique_ptr<SwTableAutoFormat> pReleasedFormat = GetTableStyles().ReleaseAutoFormat(rName); + + std::vector<SwTable*> vAffectedTables; + if (pReleasedFormat) + { + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == pReleasedFormat->GetName()) + { + pTable->SetTableStyleName(""); + vAffectedTables.push_back(pTable); + } + } + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleDelete>(std::move(pReleasedFormat), vAffectedTables, this)); + } + } + + return pReleasedFormat; +} + +void SwDoc::ChgTableStyle(const OUString& rName, const SwTableAutoFormat& rNewFormat) +{ + SwTableAutoFormat* pFormat = GetTableStyles().FindAutoFormat(rName); + if (pFormat) + { + SwTableAutoFormat aOldFormat = *pFormat; + *pFormat = rNewFormat; + pFormat->SetName(rName); + + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == rName) + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(pTable->GetTableNode()); + } + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleUpdate>(*pFormat, aOldFormat, this)); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndtbl1.cxx b/sw/source/core/docnode/ndtbl1.cxx new file mode 100644 index 000000000..6dc3814db --- /dev/null +++ b/sw/source/core/docnode/ndtbl1.cxx @@ -0,0 +1,1631 @@ +/* -*- 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 <hintids.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <fesh.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmtrowsplt.hxx> +#include <tabcol.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <tabfrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <svx/svxids.hrc> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <swcrsr.hxx> +#include <viscrs.hxx> +#include <swtable.hxx> +#include <htmltbl.hxx> +#include <tblsel.hxx> +#include <swtblfmt.hxx> +#include <ndindex.hxx> +#include <undobj.hxx> +#include <calbck.hxx> +#include <UndoTable.hxx> +#include <o3tl/enumrange.hxx> +#include <o3tl/safeint.hxx> + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +// See swtable.cxx too +#define COLFUZZY 20L + +static bool IsSame( long nA, long nB ) { return std::abs(nA-nB) <= COLFUZZY; } + +namespace { + +// SwTableLine::ChgFrameFormat may delete old format which doesn't have writer listeners anymore. +// This may invalidate my pointers, and lead to use-after-free. For this reason, I register myself +// as a writer listener for the old format here, and take care to delete formats without listeners +// in my own dtor. +class SwTableFormatCmp : public SwClient +{ +public: + SwTableFormatCmp( SwFrameFormat *pOld, SwFrameFormat *pNew, sal_Int16 nType ); + ~SwTableFormatCmp() override; + + static SwFrameFormat* FindNewFormat(std::vector<std::unique_ptr<SwTableFormatCmp>>& rArr, + SwFrameFormat const* pOld, sal_Int16 nType); + +private: + SwFrameFormat *pOld, *pNew; + sal_Int16 nType; +}; + +} + +SwTableFormatCmp::SwTableFormatCmp( SwFrameFormat *pO, SwFrameFormat *pN, sal_Int16 nT ) + : pOld ( pO ), pNew ( pN ), nType( nT ) +{ + if (pOld) + pOld->Add(this); +} + +SwTableFormatCmp::~SwTableFormatCmp() +{ + if (pOld) + { + pOld->Remove(this); + if (!pOld->HasWriterListeners()) + delete pOld; + } +} + +// static +SwFrameFormat* SwTableFormatCmp::FindNewFormat(std::vector<std::unique_ptr<SwTableFormatCmp>>& rArr, + SwFrameFormat const* pOld, sal_Int16 nType) +{ + for (const auto& pCmp : rArr) + { + if ( pCmp->pOld == pOld && pCmp->nType == nType ) + return pCmp->pNew; + } + return nullptr; +} + +static void lcl_GetStartEndCell( const SwCursor& rCursor, + SwLayoutFrame *&prStart, SwLayoutFrame *&prEnd ) +{ + OSL_ENSURE( rCursor.GetContentNode() && rCursor.GetContentNode( false ), + "Tab selection not at ContentNode" ); + + Point aPtPos, aMkPos; + const SwShellCursor* pShCursor = dynamic_cast<const SwShellCursor*>(&rCursor); + if( pShCursor ) + { + aPtPos = pShCursor->GetPtPos(); + aMkPos = pShCursor->GetMkPos(); + } + + // Robust: + SwContentNode* pPointNd = rCursor.GetContentNode(); + SwContentNode* pMarkNd = rCursor.GetContentNode(false); + + std::pair<Point, bool> tmp(aPtPos, true); + SwFrame *const pPointFrame = pPointNd ? pPointNd->getLayoutFrame(pPointNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + tmp.first = aMkPos; + SwFrame *const pMarkFrame = pMarkNd ? pMarkNd->getLayoutFrame(pMarkNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + + prStart = pPointFrame ? pPointFrame->GetUpper() : nullptr; + prEnd = pMarkFrame ? pMarkFrame->GetUpper() : nullptr; +} + +static bool lcl_GetBoxSel( const SwCursor& rCursor, SwSelBoxes& rBoxes, + bool bAllCursor = false ) +{ + const SwTableCursor* pTableCursor = + dynamic_cast<const SwTableCursor*>(&rCursor); + if( pTableCursor ) + ::GetTableSelCrs( *pTableCursor, rBoxes ); + else + { + const SwPaM *pCurPam = &rCursor, *pSttPam = pCurPam; + do { + const SwNode* pNd = pCurPam->GetNode().FindTableBoxStartNode(); + if( pNd ) + { + SwTableBox* pBox = const_cast<SwTableBox*>(pNd->FindTableNode()->GetTable(). + GetTableBox( pNd->GetIndex() )); + rBoxes.insert( pBox ); + } + } while( bAllCursor && + pSttPam != ( pCurPam = pCurPam->GetNext()) ); + } + return !rBoxes.empty(); +} + +static void InsertLine( std::vector<SwTableLine*>& rLineArr, SwTableLine* pLine ) +{ + if( rLineArr.end() == std::find( rLineArr.begin(), rLineArr.end(), pLine ) ) + rLineArr.push_back( pLine ); +} + +static bool lcl_IsAnLower( const SwTableLine *pLine, const SwTableLine *pAssumed ) +{ + const SwTableLine *pTmp = pAssumed->GetUpper() ? + pAssumed->GetUpper()->GetUpper() : nullptr; + while ( pTmp ) + { + if ( pTmp == pLine ) + return true; + pTmp = pTmp->GetUpper() ? pTmp->GetUpper()->GetUpper() : nullptr; + } + return false; +} + +namespace { + +struct LinesAndTable +{ + std::vector<SwTableLine*> &m_rLines; + const SwTable &m_rTable; + bool m_bInsertLines; + + LinesAndTable(std::vector<SwTableLine*> &rL, const SwTable &rTable) : + m_rLines(rL), m_rTable(rTable), m_bInsertLines(true) {} +}; + +} + +static bool FindLine_( FndLine_ & rLine, LinesAndTable* pPara ); + +static bool FindBox_( FndBox_ & rBox, LinesAndTable* pPara ) +{ + if (!rBox.GetLines().empty()) + { + pPara->m_bInsertLines = true; + for (auto const& rpFndLine : rBox.GetLines()) + { + FindLine_(*rpFndLine, pPara); + } + + if (pPara->m_bInsertLines) + { + const SwTableLines &rLines = (rBox.GetBox()) + ? rBox.GetBox()->GetTabLines() + : pPara->m_rTable.GetTabLines(); + if (rBox.GetLines().size() == rLines.size()) + { + for ( auto pLine : rLines ) + ::InsertLine(pPara->m_rLines, pLine); + } + else + pPara->m_bInsertLines = false; + } + } + else if (rBox.GetBox()) + { + ::InsertLine(pPara->m_rLines, rBox.GetBox()->GetUpper()); + } + return true; +} + +bool FindLine_( FndLine_& rLine, LinesAndTable* pPara ) +{ + for (auto const& it : rLine.GetBoxes()) + { + FindBox_(*it, pPara); + } + return true; +} + +static void lcl_CollectLines( std::vector<SwTableLine*> &rArr, const SwCursor& rCursor, bool bRemoveLines ) +{ + // Collect the selected Boxes first + SwSelBoxes aBoxes; + if( !::lcl_GetBoxSel( rCursor, aBoxes )) + return ; + + // Copy the selected structure + const SwTable &rTable = aBoxes[0]->GetSttNd()->FindTableNode()->GetTable(); + LinesAndTable aPara( rArr, rTable ); + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aTmpPara( aBoxes, &aFndBox ); + ForEach_FndLineCopyCol( const_cast<SwTableLines&>(rTable.GetTabLines()), &aTmpPara ); + } + + // Collect the Lines which only contain selected Boxes + ::FindBox_(aFndBox, &aPara); + + // Remove lines, that have a common superordinate row. + // (Not for row split) + if ( bRemoveLines ) + { + for ( std::vector<SwTableLine*>::size_type i = 0; i < rArr.size(); ++i ) + { + SwTableLine *pUpLine = rArr[i]; + for ( std::vector<SwTableLine*>::size_type k = 0; k < rArr.size(); ++k ) + { + if ( k != i && ::lcl_IsAnLower( pUpLine, rArr[k] ) ) + { + rArr.erase( rArr.begin() + k ); + if ( k <= i ) + --i; + --k; + } + } + } + } +} + +static void lcl_ProcessRowAttr(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableLine* pLine, const SfxPoolItem& rNew) +{ + SwFrameFormat *pNewFormat; + if ( nullptr != (pNewFormat = SwTableFormatCmp::FindNewFormat( rFormatCmp, pLine->GetFrameFormat(), 0 ))) + pLine->ChgFrameFormat( static_cast<SwTableLineFormat*>(pNewFormat) ); + else + { + SwFrameFormat *pOld = pLine->GetFrameFormat(); + SwFrameFormat *pNew = pLine->ClaimFrameFormat(); + pNew->SetFormatAttr( rNew ); + rFormatCmp.push_back(std::make_unique<SwTableFormatCmp>(pOld, pNew, 0)); + } +} + +static void lcl_ProcessBoxSize(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableBox* pBox, const SwFormatFrameSize& rNew); + +static void lcl_ProcessRowSize(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableLine* pLine, const SwFormatFrameSize& rNew) +{ + lcl_ProcessRowAttr( rFormatCmp, pLine, rNew ); + SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( auto pBox : rBoxes ) + ::lcl_ProcessBoxSize( rFormatCmp, pBox, rNew ); +} + +static void lcl_ProcessBoxSize(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableBox* pBox, const SwFormatFrameSize& rNew) +{ + SwTableLines &rLines = pBox->GetTabLines(); + if ( !rLines.empty() ) + { + SwFormatFrameSize aSz( rNew ); + aSz.SetHeight( rNew.GetHeight() ? rNew.GetHeight() / rLines.size() : 0 ); + for ( auto pLine : rLines ) + ::lcl_ProcessRowSize( rFormatCmp, pLine, aSz ); + } +} + +void SwDoc::SetRowSplit( const SwCursor& rCursor, const SwFormatRowSplit &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, false ); + + if( !aRowArr.empty() ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + + for( auto pLn : aRowArr ) + ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); + + getIDocumentState().SetModified(); + } + } +} + +std::unique_ptr<SwFormatRowSplit> SwDoc::GetRowSplit( const SwCursor& rCursor ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( !pTableNd ) + return nullptr; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, false ); + + if( aRowArr.empty() ) + return nullptr; + + SwFormatRowSplit* pSz = &const_cast<SwFormatRowSplit&>(aRowArr[0]->GetFrameFormat()->GetRowSplit()); + + for ( auto pLn : aRowArr ) + { + if ( pSz->GetValue() != pLn->GetFrameFormat()->GetRowSplit().GetValue() ) + { + return nullptr; + } + } + return std::make_unique<SwFormatRowSplit>( *pSz ); +} + +/* Class: SwDoc + * Methods: SetRowHeight(), GetRowHeight() + * + * The line height is calculated from the Selection. + * Starting with every Cell within the Selection, all Cells are iterated + * through in an upwards fashion. + * + * The topmost Line gets the requested value, all Lines below it get + * a respective value that is calculated from the relation of the old and + * new size of the topmost Line in the lower line's own size. + * + * All changed Lines may get an own FrameFormat. + * Of course we can only touch every Line once. + */ + +void SwDoc::SetRowHeight( const SwCursor& rCursor, const SwFormatFrameSize &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( !aRowArr.empty() ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + for ( auto pLn : aRowArr ) + ::lcl_ProcessRowSize( aFormatCmp, pLn, rNew ); + + getIDocumentState().SetModified(); + } + } +} + +std::unique_ptr<SwFormatFrameSize> SwDoc::GetRowHeight( const SwCursor& rCursor ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( !pTableNd ) + return nullptr; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( aRowArr.empty() ) + return nullptr; + + SwFormatFrameSize* pSz = &const_cast<SwFormatFrameSize&>(aRowArr[0]->GetFrameFormat()->GetFrameSize()); + + for ( auto pLn : aRowArr ) + { + if ( *pSz != pLn->GetFrameFormat()->GetFrameSize() ) + return nullptr; + } + return std::make_unique<SwFormatFrameSize>( *pSz ); +} + +bool SwDoc::BalanceRowHeight( const SwCursor& rCursor, bool bTstOnly, const bool bOptimize ) +{ + bool bRet = false; + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( 1 < aRowArr.size() ) + { + if( !bTstOnly ) + { + long nHeight = 0; + sal_Int32 nTotalHeight = 0; + for ( auto pLn : aRowArr ) + { + SwIterator<SwFrame,SwFormat> aIter( *pLn->GetFrameFormat() ); + SwFrame* pFrame = aIter.First(); + while ( pFrame ) + { + nHeight = std::max( nHeight, pFrame->getFrameArea().Height() ); + pFrame = aIter.Next(); + } + nTotalHeight += nHeight; + } + + if ( bOptimize ) + nHeight = nTotalHeight / aRowArr.size(); + + SwFormatFrameSize aNew( SwFrameSize::Minimum, 0, nHeight ); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + for( auto pLn : aRowArr ) + ::lcl_ProcessRowSize( aFormatCmp, pLn, aNew ); + + getIDocumentState().SetModified(); + } + bRet = true; + } + } + return bRet; +} + +void SwDoc::SetRowBackground( const SwCursor& rCursor, const SvxBrushItem &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( !aRowArr.empty() ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + + for( auto pLn : aRowArr ) + ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); + + getIDocumentState().SetModified(); + } + } +} + +bool SwDoc::GetRowBackground( const SwCursor& rCursor, std::unique_ptr<SvxBrushItem>& rToFill ) +{ + bool bRet = false; + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( !aRowArr.empty() ) + { + rToFill = aRowArr[0]->GetFrameFormat()->makeBackgroundBrushItem(); + + bRet = true; + for ( std::vector<SwTableLine*>::size_type i = 1; i < aRowArr.size(); ++i ) + { + std::unique_ptr<SvxBrushItem> aAlternative(aRowArr[i]->GetFrameFormat()->makeBackgroundBrushItem()); + + if ( rToFill && aAlternative && *rToFill != *aAlternative ) + { + bRet = false; + break; + } + } + } + } + return bRet; +} + +static void InsertCell( std::vector<SwCellFrame*>& rCellArr, SwCellFrame* pCellFrame ) +{ + if( rCellArr.end() == std::find( rCellArr.begin(), rCellArr.end(), pCellFrame ) ) + rCellArr.push_back( pCellFrame ); +} + +static void lcl_CollectCells( std::vector<SwCellFrame*> &rArr, const SwRect &rUnion, + SwTabFrame *pTab ) +{ + SwLayoutFrame *pCell = pTab->FirstCell(); + do + { + // If the Cell contains a CellFrame, we need to use it + // in order to get to the Cell + while ( !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "Frame is not a Cell" ); + if ( rUnion.IsOver( pCell->getFrameArea() ) ) + ::InsertCell( rArr, static_cast<SwCellFrame*>(pCell) ); + + // Make sure the Cell is left (Areas) + SwLayoutFrame *pTmp = pCell; + do + { pTmp = pTmp->GetNextLayoutLeaf(); + } while ( pCell->IsAnLower( pTmp ) ); + pCell = pTmp; + } while( pCell && pTab->IsAnLower( pCell ) ); +} + +void SwDoc::SetTabBorders( const SwCursor& rCursor, const SfxItemSet& rSet ) +{ + SwContentNode* pCntNd = rCursor.GetPoint()->nNode.GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + if( aUnions.empty() ) + return; + + SwTable& rTable = pTableNd->GetTable(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoAttrTable>(*pTableNd) ); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( 255 ); + const SvxBoxItem* pSetBox; + const SvxBoxInfoItem *pSetBoxInfo; + + const SvxBorderLine* pLeft = nullptr; + const SvxBorderLine* pRight = nullptr; + const SvxBorderLine* pTop = nullptr; + const SvxBorderLine* pBottom = nullptr; + const SvxBorderLine* pHori = nullptr; + const SvxBorderLine* pVert = nullptr; + bool bHoriValid = true, bVertValid = true, + bTopValid = true, bBottomValid = true, + bLeftValid = true, bRightValid = true; + + // The Flags in the BoxInfo Item decide whether a BorderLine is valid! + if( SfxItemState::SET == rSet.GetItemState( SID_ATTR_BORDER_INNER, false, + reinterpret_cast<const SfxPoolItem**>(&pSetBoxInfo)) ) + { + pHori = pSetBoxInfo->GetHori(); + pVert = pSetBoxInfo->GetVert(); + + bHoriValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::HORI); + bVertValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::VERT); + + // Do we want to evaluate these? + bTopValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::TOP); + bBottomValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::BOTTOM); + bLeftValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::LEFT); + bRightValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::RIGHT); + } + + if( SfxItemState::SET == rSet.GetItemState( RES_BOX, false, + reinterpret_cast<const SfxPoolItem**>(&pSetBox)) ) + { + pLeft = pSetBox->GetLeft(); + pRight = pSetBox->GetRight(); + pTop = pSetBox->GetTop(); + pBottom = pSetBox->GetBottom(); + } + else + { + // Not set, thus not valid values + bTopValid = bBottomValid = bLeftValid = bRightValid = false; + pSetBox = nullptr; + } + + bool bFirst = true; + for ( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + SwSelUnion *pUnion = &aUnions[i]; + SwTabFrame *pTab = pUnion->GetTable(); + const SwRect &rUnion = pUnion->GetUnion(); + const bool bLast = (i == aUnions.size() - 1); + + std::vector<SwCellFrame*> aCellArr; + aCellArr.reserve( 255 ); + ::lcl_CollectCells( aCellArr, pUnion->GetUnion(), pTab ); + + // All Cell Borders that match the UnionRect or extend it are + // Outer Borders. All others are Inner Borders. + + // New: The Outer Borders can, depending on whether it's a + // Start/Middle/Follow Table (for Selection via FollowTabs), + // also not be Outer Borders. + // Outer Borders are set on the left, right, at the top and at the bottom. + // Inner Borders are only set at the top and on the left. + for ( auto pCell : aCellArr ) + { + const bool bVert = pTab->IsVertical(); + const bool bRTL = pTab->IsRightToLeft(); + bool bTopOver, bLeftOver, bRightOver, bBottomOver; + if ( bVert ) + { + bTopOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bLeftOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bRightOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + bBottomOver = pCell->getFrameArea().Left() <= rUnion.Left(); + } + else + { + bTopOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bLeftOver = pCell->getFrameArea().Left() <= rUnion.Left(); + bRightOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bBottomOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + } + + if ( bRTL ) + { + bool bTmp = bRightOver; + bRightOver = bLeftOver; + bLeftOver = bTmp; + } + + // Do not set anything by default in HeadlineRepeats + if ( pTab->IsFollow() && + ( pTab->IsInHeadline( *pCell ) || + // Same holds for follow flow rows + pCell->IsInFollowFlowRow() ) ) + continue; + + SvxBoxItem aBox( pCell->GetFormat()->GetBox() ); + + sal_Int16 nType = 0; + + // Top Border + if( bTopValid ) + { + if ( bFirst && bTopOver ) + { + aBox.SetLine( pTop, SvxBoxItemLine::TOP ); + nType |= 0x0001; + } + else if ( bHoriValid ) + { + aBox.SetLine( nullptr, SvxBoxItemLine::TOP ); + nType |= 0x0002; + } + } + + // Fix fdo#62470 correct the input for RTL table + if (bRTL) + { + if( bLeftOver && bRightOver) + { + if ( bLeftValid ) + { + aBox.SetLine( pLeft, SvxBoxItemLine::RIGHT ); + nType |= 0x0010; + } + if ( bRightValid ) + { + aBox.SetLine( pRight, SvxBoxItemLine::LEFT ); + nType |= 0x0004; + } + } + else + { + if ( bLeftValid ) + { + aBox.SetLine( bRightOver ? pLeft : nullptr, SvxBoxItemLine::RIGHT ); + if (bVertValid) + nType |= 0x0020; + else + nType |= 0x0010; + } + if ( bLeftOver ) + { + if ( bRightValid ) + { + aBox.SetLine( pRight, SvxBoxItemLine::LEFT ); + nType |= 0x0004; + } + } + else if ( bVertValid ) + { + aBox.SetLine( pVert, SvxBoxItemLine::LEFT ); + nType |= 0x0008; + } + } + } + else + { + // Left Border + if ( bLeftOver ) + { + if( bLeftValid ) + { + aBox.SetLine( pLeft, SvxBoxItemLine::LEFT ); + nType |= 0x0004; + } + } + else if( bVertValid ) + { + aBox.SetLine( pVert, SvxBoxItemLine::LEFT ); + nType |= 0x0008; + } + + // Right Border + if( bRightValid ) + { + if ( bRightOver ) + { + aBox.SetLine( pRight, SvxBoxItemLine::RIGHT ); + nType |= 0x0010; + } + else if ( bVertValid ) + { + aBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + nType |= 0x0020; + } + } + } + + // Bottom Border + if ( bLast && bBottomOver ) + { + if( bBottomValid ) + { + aBox.SetLine( pBottom, SvxBoxItemLine::BOTTOM ); + nType |= 0x0040; + } + } + else if( bHoriValid ) + { + aBox.SetLine( pHori, SvxBoxItemLine::BOTTOM ); + nType |= 0x0080; + } + + if( pSetBox ) + { + for( SvxBoxItemLine k : o3tl::enumrange<SvxBoxItemLine>() ) + aBox.SetDistance( pSetBox->GetDistance( k ), k ); + } + + SwTableBox *pBox = const_cast<SwTableBox*>(pCell->GetTabBox()); + SwFrameFormat *pNewFormat; + if ( nullptr != (pNewFormat = SwTableFormatCmp::FindNewFormat( aFormatCmp, pBox->GetFrameFormat(), nType ))) + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pNewFormat) ); + else + { + SwFrameFormat *pOld = pBox->GetFrameFormat(); + SwFrameFormat *pNew = pBox->ClaimFrameFormat(); + pNew->SetFormatAttr( aBox ); + aFormatCmp.push_back(std::make_unique<SwTableFormatCmp>(pOld, pNew, nType)); + } + } + + bFirst = false; + } + + SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); + if( pTableLayout ) + { + SwContentFrame* pFrame = rCursor.GetContentNode()->getLayoutFrame( rCursor.GetContentNode()->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); + + pTableLayout->BordersChanged( + pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ) ); + } + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentState().SetModified(); +} + +static void lcl_SetLineStyle( SvxBorderLine *pToSet, + const Color *pColor, const SvxBorderLine *pBorderLine) +{ + if ( pBorderLine ) + { + if ( !pColor ) + { + Color aTmp( pToSet->GetColor() ); + *pToSet = *pBorderLine; + pToSet->SetColor( aTmp ); + } + else + *pToSet = *pBorderLine; + } + if ( pColor ) + pToSet->SetColor( *pColor ); +} + +void SwDoc::SetTabLineStyle( const SwCursor& rCursor, + const Color* pColor, bool bSetLine, + const SvxBorderLine* pBorderLine ) +{ + SwContentNode* pCntNd = rCursor.GetPoint()->nNode.GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + if( !aUnions.empty() ) + { + SwTable& rTable = pTableNd->GetTable(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + for( auto &rU : aUnions ) + { + SwSelUnion *pUnion = &rU; + SwTabFrame *pTab = pUnion->GetTable(); + std::vector<SwCellFrame*> aCellArr; + aCellArr.reserve( 255 ); + ::lcl_CollectCells( aCellArr, pUnion->GetUnion(), pTab ); + + for ( auto pCell : aCellArr ) + { + // Do not set anything by default in HeadlineRepeats + if ( pTab->IsFollow() && pTab->IsInHeadline( *pCell ) ) + continue; + + const_cast<SwTableBox*>(pCell->GetTabBox())->ClaimFrameFormat(); + SwFrameFormat *pFormat = pCell->GetFormat(); + std::unique_ptr<SvxBoxItem> aBox(pFormat->GetBox().Clone()); + + if ( !pBorderLine && bSetLine ) + { + aBox.reset(::GetDfltAttr(RES_BOX)->Clone()); + } + else + { + if ( aBox->GetTop() ) + ::lcl_SetLineStyle( const_cast<SvxBorderLine*>(aBox->GetTop()), + pColor, pBorderLine ); + if ( aBox->GetBottom() ) + ::lcl_SetLineStyle( const_cast<SvxBorderLine*>(aBox->GetBottom()), + pColor, pBorderLine ); + if ( aBox->GetLeft() ) + ::lcl_SetLineStyle( const_cast<SvxBorderLine*>(aBox->GetLeft()), + pColor, pBorderLine ); + if ( aBox->GetRight() ) + ::lcl_SetLineStyle( const_cast<SvxBorderLine*>(aBox->GetRight()), + pColor, pBorderLine ); + } + pFormat->SetFormatAttr( *aBox ); + } + } + + SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); + if( pTableLayout ) + { + SwContentFrame* pFrame = rCursor.GetContentNode()->getLayoutFrame( rCursor.GetContentNode()->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); + + pTableLayout->BordersChanged( + pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ) ); + } + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentState().SetModified(); + } +} + +void SwDoc::GetTabBorders( const SwCursor& rCursor, SfxItemSet& rSet ) +{ + SwContentNode* pCntNd = rCursor.GetPoint()->nNode.GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + if( !aUnions.empty() ) + { + SvxBoxItem aSetBox ( rSet.Get(RES_BOX ) ); + SvxBoxInfoItem aSetBoxInfo( rSet.Get(SID_ATTR_BORDER_INNER) ); + + bool bTopSet = false, + bBottomSet = false, + bLeftSet = false, + bRightSet = false, + bHoriSet = false, + bVertSet = false, + bDistanceSet = false, + bRTLTab = false; + + aSetBoxInfo.ResetFlags(); + + for ( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + SwSelUnion *pUnion = &aUnions[i]; + const SwTabFrame *pTab = pUnion->GetTable(); + const SwRect &rUnion = pUnion->GetUnion(); + const bool bFirst = i == 0; + const bool bLast = (i == aUnions.size() - 1); + + std::vector<SwCellFrame*> aCellArr; + aCellArr.reserve(255); + ::lcl_CollectCells( aCellArr, rUnion, const_cast<SwTabFrame*>(pTab) ); + + for ( auto pCell : aCellArr ) + { + const bool bVert = pTab->IsVertical(); + const bool bRTL = bRTLTab = pTab->IsRightToLeft(); + bool bTopOver, bLeftOver, bRightOver, bBottomOver; + if ( bVert ) + { + bTopOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bLeftOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bRightOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + bBottomOver = pCell->getFrameArea().Left() <= rUnion.Left(); + } + else + { + bTopOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bLeftOver = pCell->getFrameArea().Left() <= rUnion.Left(); + bRightOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bBottomOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + } + + if ( bRTL ) + { + bool bTmp = bRightOver; + bRightOver = bLeftOver; + bLeftOver = bTmp; + } + + const SwFrameFormat *pFormat = pCell->GetFormat(); + const SvxBoxItem &rBox = pFormat->GetBox(); + + // Top Border + if ( bFirst && bTopOver ) + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::TOP)) + { + if ( !bTopSet ) + { bTopSet = true; + aSetBox.SetLine( rBox.GetTop(), SvxBoxItemLine::TOP ); + } + else if ((aSetBox.GetTop() && rBox.GetTop() && + (*aSetBox.GetTop() != *rBox.GetTop())) || + ((!aSetBox.GetTop()) != (!rBox.GetTop()))) // != expression is true, if one and only one of the two pointers is !0 + { + aSetBoxInfo.SetValid(SvxBoxInfoItemValidFlags::TOP, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::TOP ); + } + } + } + + // Left Border + if ( bLeftOver ) + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT)) + { + if ( !bLeftSet ) + { bLeftSet = true; + aSetBox.SetLine( rBox.GetLeft(), SvxBoxItemLine::LEFT ); + } + else if ((aSetBox.GetLeft() && rBox.GetLeft() && + (*aSetBox.GetLeft() != *rBox.GetLeft())) || + ((!aSetBox.GetLeft()) != (!rBox.GetLeft()))) + { + aSetBoxInfo.SetValid(SvxBoxInfoItemValidFlags::LEFT, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::LEFT ); + } + } + } + else + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::VERT)) + { + if ( !bVertSet ) + { bVertSet = true; + aSetBoxInfo.SetLine( rBox.GetLeft(), SvxBoxInfoItemLine::VERT ); + } + else if ((aSetBoxInfo.GetVert() && rBox.GetLeft() && + (*aSetBoxInfo.GetVert() != *rBox.GetLeft())) || + ((!aSetBoxInfo.GetVert()) != (!rBox.GetLeft()))) + { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::VERT, false ); + aSetBoxInfo.SetLine( nullptr, SvxBoxInfoItemLine::VERT ); + } + } + } + + // Right Border + if ( aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::RIGHT) && bRightOver ) + { + if ( !bRightSet ) + { bRightSet = true; + aSetBox.SetLine( rBox.GetRight(), SvxBoxItemLine::RIGHT ); + } + else if ((aSetBox.GetRight() && rBox.GetRight() && + (*aSetBox.GetRight() != *rBox.GetRight())) || + (!aSetBox.GetRight() != !rBox.GetRight())) + { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + } + + // Bottom Border + if ( bLast && bBottomOver ) + { + if ( aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::BOTTOM) ) + { + if ( !bBottomSet ) + { bBottomSet = true; + aSetBox.SetLine( rBox.GetBottom(), SvxBoxItemLine::BOTTOM ); + } + else if ((aSetBox.GetBottom() && rBox.GetBottom() && + (*aSetBox.GetBottom() != *rBox.GetBottom())) || + (!aSetBox.GetBottom() != !rBox.GetBottom())) + { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); + } + } + } + // In all Lines, except for the last one, the horizontal Line + // is taken from the Bottom Line. + else + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::HORI)) + { + if ( !bHoriSet ) + { bHoriSet = true; + aSetBoxInfo.SetLine( rBox.GetBottom(), SvxBoxInfoItemLine::HORI ); + } + else if ((aSetBoxInfo.GetHori() && rBox.GetBottom() && + (*aSetBoxInfo.GetHori() != *rBox.GetBottom())) || + ((!aSetBoxInfo.GetHori()) != (!rBox.GetBottom()))) + { + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::HORI, false ); + aSetBoxInfo.SetLine( nullptr, SvxBoxInfoItemLine::HORI ); + } + } + } + + // Distance to text + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::DISTANCE)) + { + if( !bDistanceSet ) // Set on first iteration + { + bDistanceSet = true; + for( SvxBoxItemLine k : o3tl::enumrange<SvxBoxItemLine>() ) + aSetBox.SetDistance( rBox.GetDistance( k ), k ); + } + else + { + for( SvxBoxItemLine k : o3tl::enumrange<SvxBoxItemLine>() ) + if( aSetBox.GetDistance( k ) != + rBox.GetDistance( k ) ) + { + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::DISTANCE, false ); + aSetBox.SetAllDistances(0); + break; + } + } + } + } + } + + // fdo#62470 fix the reading for table format. + if ( bRTLTab ) + { + SvxBoxItem aTempBox ( rSet.Get(RES_BOX ) ); + SvxBoxInfoItem aTempBoxInfo( rSet.Get(SID_ATTR_BORDER_INNER) ); + + aTempBox.SetLine( aSetBox.GetRight(), SvxBoxItemLine::RIGHT); + aSetBox.SetLine( aSetBox.GetLeft(), SvxBoxItemLine::RIGHT); + aSetBox.SetLine( aTempBox.GetRight(), SvxBoxItemLine::LEFT); + + aTempBoxInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT, aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT) ); + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT, aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::RIGHT) ); + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT, aTempBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT) ); + } + + rSet.Put( aSetBox ); + rSet.Put( aSetBoxInfo ); + } +} + +void SwDoc::SetBoxAttr( const SwCursor& rCursor, const SfxPoolItem &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + SwSelBoxes aBoxes; + if( pTableNd && ::lcl_GetBoxSel( rCursor, aBoxes, true ) ) + { + SwTable& rTable = pTableNd->GetTable(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoAttrTable>(*pTableNd) ); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve(std::max<size_t>(255, aBoxes.size())); + for (size_t i = 0; i < aBoxes.size(); ++i) + { + SwTableBox *pBox = aBoxes[i]; + + SwFrameFormat *pNewFormat; + if ( nullptr != (pNewFormat = SwTableFormatCmp::FindNewFormat( aFormatCmp, pBox->GetFrameFormat(), 0 ))) + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pNewFormat) ); + else + { + SwFrameFormat *pOld = pBox->GetFrameFormat(); + SwFrameFormat *pNew = pBox->ClaimFrameFormat(); + pNew->SetFormatAttr( rNew ); + aFormatCmp.push_back(std::make_unique<SwTableFormatCmp>(pOld, pNew, 0)); + } + + pBox->SetDirectFormatting(true); + } + + SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); + if( pTableLayout ) + { + SwContentFrame* pFrame = rCursor.GetContentNode()->getLayoutFrame( rCursor.GetContentNode()->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); + + pTableLayout->Resize( + pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ), true ); + } + getIDocumentState().SetModified(); + } +} + +bool SwDoc::GetBoxAttr( const SwCursor& rCursor, std::unique_ptr<SfxPoolItem>& rToFill ) +{ + bool bRet = false; + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + SwSelBoxes aBoxes; + if( pTableNd && lcl_GetBoxSel( rCursor, aBoxes )) + { + bRet = true; + bool bOneFound = false; + const sal_uInt16 nWhich = rToFill->Which(); + for (size_t i = 0; i < aBoxes.size(); ++i) + { + switch ( nWhich ) + { + case RES_BACKGROUND: + { + std::unique_ptr<SvxBrushItem> xBack = + aBoxes[i]->GetFrameFormat()->makeBackgroundBrushItem(); + if( !bOneFound ) + { + rToFill = std::move(xBack); + bOneFound = true; + } + else if( *rToFill != *xBack ) + bRet = false; + } + break; + + case RES_FRAMEDIR: + { + const SvxFrameDirectionItem& rDir = + aBoxes[i]->GetFrameFormat()->GetFrameDir(); + if( !bOneFound ) + { + rToFill.reset(rDir.Clone()); + bOneFound = true; + } + else if( rToFill && *rToFill != rDir ) + bRet = false; + } + break; + case RES_VERT_ORIENT: + { + const SwFormatVertOrient& rOrient = + aBoxes[i]->GetFrameFormat()->GetVertOrient(); + if( !bOneFound ) + { + rToFill.reset(rOrient.Clone()); + bOneFound = true; + } + else if( rToFill && *rToFill != rOrient ) + bRet = false; + } + break; + } + + if ( !bRet ) + break; + } + } + return bRet; +} + +void SwDoc::SetBoxAlign( const SwCursor& rCursor, sal_uInt16 nAlign ) +{ + OSL_ENSURE( nAlign == text::VertOrientation::NONE || + nAlign == text::VertOrientation::CENTER || + nAlign == text::VertOrientation::BOTTOM, "Wrong alignment" ); + SwFormatVertOrient aVertOri( 0, nAlign ); + SetBoxAttr( rCursor, aVertOri ); +} + +sal_uInt16 SwDoc::GetBoxAlign( const SwCursor& rCursor ) +{ + sal_uInt16 nAlign = USHRT_MAX; + SwTableNode* pTableNd = rCursor.GetPoint()->nNode.GetNode().FindTableNode(); + SwSelBoxes aBoxes; + if( pTableNd && ::lcl_GetBoxSel( rCursor, aBoxes )) + { + for (size_t i = 0; i < aBoxes.size(); ++i) + { + const SwFormatVertOrient &rOri = + aBoxes[i]->GetFrameFormat()->GetVertOrient(); + if( USHRT_MAX == nAlign ) + nAlign = static_cast<sal_uInt16>(rOri.GetVertOrient()); + else if( rOri.GetVertOrient() != nAlign ) + { + nAlign = USHRT_MAX; + break; + } + } + } + return nAlign; +} + +static sal_uInt16 lcl_CalcCellFit( const SwLayoutFrame *pCell ) +{ + SwTwips nRet = 0; + const SwFrame *pFrame = pCell->Lower(); // The whole Line + SwRectFnSet aRectFnSet(pCell); + while ( pFrame ) + { + const SwTwips nAdd = aRectFnSet.GetWidth(pFrame->getFrameArea()) - + aRectFnSet.GetWidth(pFrame->getFramePrintArea()); + + // pFrame does not necessarily have to be a SwTextFrame! + const SwTwips nCalcFitToContent = pFrame->IsTextFrame() ? + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pFrame))->CalcFitToContent() : + aRectFnSet.GetWidth(pFrame->getFramePrintArea()); + + nRet = std::max( nRet, nCalcFitToContent + nAdd ); + pFrame = pFrame->GetNext(); + } + // Surrounding border as well as left and Right Border also need to be respected + nRet += aRectFnSet.GetWidth(pCell->getFrameArea()) - + aRectFnSet.GetWidth(pCell->getFramePrintArea()); + + // To compensate for the accuracy of calculation later on in SwTable::SetTabCols + // we keep adding up a little. + nRet += COLFUZZY; + return static_cast<sal_uInt16>(std::max( long(MINLAY), nRet )); +} + +/* The Line is within the Selection but not outlined by the TabCols. + * + * That means that the Line has been "split" by other Cells due to the + * two-dimensional representation used. Thus, we have to distribute the cell's + * default or minimum value amongst the Cell it has been split by. + * + * First, we collect the Columns (not the Column separators) which overlap + * with the Cell. We then distribute the desired value according to the + * amount of overlapping amongst the Cells. + * + * A Cell's default value stays the same if it already has a larger value than + * the desired one. It's overwritten if it's smaller. + */ +static void lcl_CalcSubColValues( std::vector<sal_uInt16> &rToFill, const SwTabCols &rCols, + const SwLayoutFrame *pCell, const SwLayoutFrame *pTab, + bool bWishValues ) +{ + const sal_uInt16 nWish = bWishValues ? + ::lcl_CalcCellFit( pCell ) : + MINLAY + sal_uInt16(pCell->getFrameArea().Width() - pCell->getFramePrintArea().Width()); + + SwRectFnSet aRectFnSet(pTab); + + for ( size_t i = 0 ; i <= rCols.Count(); ++i ) + { + long nColLeft = i == 0 ? rCols.GetLeft() : rCols[i-1]; + long nColRight = i == rCols.Count() ? rCols.GetRight() : rCols[i]; + nColLeft += rCols.GetLeftMin(); + nColRight += rCols.GetLeftMin(); + + // Adapt values to the proportions of the Table (Follows) + if ( rCols.GetLeftMin() != aRectFnSet.GetLeft(pTab->getFrameArea()) ) + { + const long nDiff = aRectFnSet.GetLeft(pTab->getFrameArea()) - rCols.GetLeftMin(); + nColLeft += nDiff; + nColRight += nDiff; + } + const long nCellLeft = aRectFnSet.GetLeft(pCell->getFrameArea()); + const long nCellRight = aRectFnSet.GetRight(pCell->getFrameArea()); + + // Calculate overlapping value + long nWidth = 0; + if ( nColLeft <= nCellLeft && nColRight >= (nCellLeft+COLFUZZY) ) + nWidth = nColRight - nCellLeft; + else if ( nColLeft <= (nCellRight-COLFUZZY) && nColRight >= nCellRight ) + nWidth = nCellRight - nColLeft; + else if ( nColLeft >= nCellLeft && nColRight <= nCellRight ) + nWidth = nColRight - nColLeft; + if ( nWidth && pCell->getFrameArea().Width() ) + { + long nTmp = nWidth * nWish / pCell->getFrameArea().Width(); + if ( o3tl::make_unsigned(nTmp) > rToFill[i] ) + rToFill[i] = sal_uInt16(nTmp); + } + } +} + +/** + * Retrieves new values to set the TabCols. + * + * We do not iterate over the TabCols' entries, but over the gaps that describe Cells. + * We set TabCol entries for which we did not calculate Cells to 0. + * + * @param bWishValues == true: We calculate the desired value of all affected + * Cells for the current Selection/current Cell. + * If more Cells are within a Column, the highest + * desired value is returned. + * We set TabCol entries for which we did not calculate + * Cells to 0. + * + * @param bWishValues == false: The Selection is expanded vertically. + * We calculate the minimum value for every + * Column in the TabCols that intersects with the + * Selection. + */ +static void lcl_CalcColValues( std::vector<sal_uInt16> &rToFill, const SwTabCols &rCols, + const SwLayoutFrame *pStart, const SwLayoutFrame *pEnd, + bool bWishValues ) +{ + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd, + bWishValues ? SwTableSearchType::NONE : SwTableSearchType::Col ); + + for ( auto &rU : aUnions ) + { + SwSelUnion *pSelUnion = &rU; + const SwTabFrame *pTab = pSelUnion->GetTable(); + const SwRect &rUnion = pSelUnion->GetUnion(); + + SwRectFnSet aRectFnSet(pTab); + bool bRTL = pTab->IsRightToLeft(); + + const SwLayoutFrame *pCell = pTab->FirstCell(); + if (!pCell) + continue; + do + { + if ( pCell->IsCellFrame() && pCell->FindTabFrame() == pTab && ::IsFrameInTableSel( rUnion, pCell ) ) + { + const long nCLeft = aRectFnSet.GetLeft(pCell->getFrameArea()); + const long nCRight = aRectFnSet.GetRight(pCell->getFrameArea()); + + bool bNotInCols = true; + + for ( size_t i = 0; i <= rCols.Count(); ++i ) + { + sal_uInt16 nFit = rToFill[i]; + long nColLeft = i == 0 ? rCols.GetLeft() : rCols[i-1]; + long nColRight = i == rCols.Count() ? rCols.GetRight() : rCols[i]; + + if ( bRTL ) + { + long nTmpRight = nColRight; + nColRight = rCols.GetRight() - nColLeft; + nColLeft = rCols.GetRight() - nTmpRight; + } + + nColLeft += rCols.GetLeftMin(); + nColRight += rCols.GetLeftMin(); + + // Adapt values to the proportions of the Table (Follows) + long nLeftA = nColLeft; + long nRightA = nColRight; + if ( rCols.GetLeftMin() != sal_uInt16(aRectFnSet.GetLeft(pTab->getFrameArea())) ) + { + const long nDiff = aRectFnSet.GetLeft(pTab->getFrameArea()) - rCols.GetLeftMin(); + nLeftA += nDiff; + nRightA += nDiff; + } + + // We don't want to take a too close look + if ( ::IsSame(nCLeft, nLeftA) && ::IsSame(nCRight, nRightA)) + { + bNotInCols = false; + if ( bWishValues ) + { + const sal_uInt16 nWish = ::lcl_CalcCellFit( pCell ); + if ( nWish > nFit ) + nFit = nWish; + } + else + { const sal_uInt16 nMin = MINLAY + sal_uInt16(pCell->getFrameArea().Width() - + pCell->getFramePrintArea().Width()); + if ( !nFit || nMin < nFit ) + nFit = nMin; + } + if ( rToFill[i] < nFit ) + rToFill[i] = nFit; + } + } + if ( bNotInCols ) + ::lcl_CalcSubColValues( rToFill, rCols, pCell, pTab, bWishValues ); + } + do { + pCell = pCell->GetNextLayoutLeaf(); + } while( pCell && pCell->getFrameArea().Width() == 0 ); + } while ( pCell && pTab->IsAnLower( pCell ) ); + } +} + +void SwDoc::AdjustCellWidth( const SwCursor& rCursor, + const bool bBalance, + const bool bNoShrink ) +{ + // Check whether the current Cursor has it's Point/Mark in a Table + SwContentNode* pCntNd = rCursor.GetPoint()->nNode.GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + // Collect TabCols; we reset the Table with them + SwFrame* pBoxFrame = pStart; + while( pBoxFrame && !pBoxFrame->IsCellFrame() ) + pBoxFrame = pBoxFrame->GetUpper(); + + if ( !pBoxFrame ) + return; // Robust + + SwTabCols aTabCols; + GetTabCols( aTabCols, static_cast<SwCellFrame*>(pBoxFrame) ); + + if ( ! aTabCols.Count() ) + return; + + std::vector<sal_uInt16> aWish(aTabCols.Count() + 1); + std::vector<sal_uInt16> aMins(aTabCols.Count() + 1); + + ::lcl_CalcColValues( aWish, aTabCols, pStart, pEnd, /*bWishValues=*/true ); + + // It's more robust if we calculate the minimum values for the whole Table + const SwTabFrame *pTab = pStart->ImplFindTabFrame(); + pStart = const_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame const *>(pTab->FirstCell())); + pEnd = const_cast<SwLayoutFrame*>(pTab->FindLastContentOrTable()->GetUpper()); + while( !pEnd->IsCellFrame() ) + pEnd = pEnd->GetUpper(); + ::lcl_CalcColValues( aMins, aTabCols, pStart, pEnd, /*bWishValues=*/false ); + + sal_uInt16 nSelectedWidth = 0, nCols = 0; + float fTotalWish = 0; + if ( bBalance || bNoShrink ) + { + // Find the combined size of the selected columns + for ( size_t i = 0; i <= aTabCols.Count(); ++i ) + { + if ( aWish[i] ) + { + if ( i == 0 ) + nSelectedWidth += aTabCols[i] - aTabCols.GetLeft(); + else if ( i == aTabCols.Count() ) + nSelectedWidth += aTabCols.GetRight() - aTabCols[i-1]; + else + nSelectedWidth += aTabCols[i] - aTabCols[i-1]; + ++nCols; + } + fTotalWish += aWish[i]; + } + const sal_uInt16 nEqualWidth = nSelectedWidth / nCols; + // bBalance: Distribute the width evenly + for (sal_uInt16 & rn : aWish) + if ( rn && bBalance ) + rn = nEqualWidth; + } + + const long nOldRight = aTabCols.GetRight(); + + // In order to make the implementation easier, but still use the available + // space properly, we do this twice. + + // The problem: The first column is getting wider, the others get slimmer + // only afterwards. + // The first column's desired width would be discarded as it would cause + // the Table's width to exceed the maximum width. + const sal_uInt16 nEqualWidth = (aTabCols.GetRight() - aTabCols.GetLeft()) / (aTabCols.Count() + 1); + const sal_Int16 nTablePadding = nSelectedWidth - fTotalWish; + for ( int k = 0; k < 2; ++k ) + { + for ( size_t i = 0; i <= aTabCols.Count(); ++i ) + { + // bNoShrink: distribute excess space proportionately on pass 2. + if ( bNoShrink && k && nTablePadding > 0 && fTotalWish > 0 ) + aWish[i] += round( aWish[i] / fTotalWish * nTablePadding ); + + // First pass is primarily a shrink pass. Give all columns a chance + // to grow by requesting the maximum width as "balanced". + // Second pass is a first-come, first-served chance to max out. + int nDiff = k ? aWish[i] : std::min(aWish[i], nEqualWidth); + if ( nDiff ) + { + int nMin = aMins[i]; + if ( nMin > nDiff ) + nDiff = nMin; + + if ( i == 0 ) + { + if( aTabCols.Count() ) + nDiff -= aTabCols[0] - aTabCols.GetLeft(); + else + nDiff -= aTabCols.GetRight() - aTabCols.GetLeft(); + } + else if ( i == aTabCols.Count() ) + nDiff -= aTabCols.GetRight() - aTabCols[i-1]; + else + nDiff -= aTabCols[i] - aTabCols[i-1]; + + long nTabRight = aTabCols.GetRight() + nDiff; + + // If the Table would become too wide, we restrict the + // adjusted amount to the allowed maximum. + if ( !bBalance && nTabRight > aTabCols.GetRightMax() ) + { + const long nTmpD = nTabRight - aTabCols.GetRightMax(); + nDiff -= nTmpD; + nTabRight -= nTmpD; + } + for ( size_t i2 = i; i2 < aTabCols.Count(); ++i2 ) + aTabCols[i2] += nDiff; + aTabCols.SetRight( nTabRight ); + } + } + } + + const long nNewRight = aTabCols.GetRight(); + + SwFrameFormat *pFormat = pTableNd->GetTable().GetFrameFormat(); + const sal_Int16 nOriHori = pFormat->GetHoriOrient().GetHoriOrient(); + + // We can leave the "real" work to the SwTable now + SetTabCols( aTabCols, false, static_cast<SwCellFrame*>(pBoxFrame) ); + + // Alignment might have been changed in SetTabCols; restore old value + const SwFormatHoriOrient &rHori = pFormat->GetHoriOrient(); + SwFormatHoriOrient aHori( rHori ); + if ( aHori.GetHoriOrient() != nOriHori ) + { + aHori.SetHoriOrient( nOriHori ); + pFormat->SetFormatAttr( aHori ); + } + + // We switch to left-adjusted for automatic width + // We adjust the right border for Border attributes + if( !bBalance && nNewRight < nOldRight ) + { + if( aHori.GetHoriOrient() == text::HoriOrientation::FULL ) + { + aHori.SetHoriOrient( text::HoriOrientation::LEFT ); + pFormat->SetFormatAttr( aHori ); + } + } + + getIDocumentState().SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/node.cxx b/sw/source/core/docnode/node.cxx new file mode 100644 index 000000000..e5b0a4061 --- /dev/null +++ b/sw/source/core/docnode/node.cxx @@ -0,0 +1,2160 @@ +/* -*- 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 <hintids.hxx> +#include <editeng/protitem.hxx> +#include <tools/gen.hxx> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <txtftn.hxx> +#include <ftnfrm.hxx> +#include <doc.hxx> +#include <docary.hxx> +#include <node.hxx> +#include <ndindex.hxx> +#include <numrule.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <swcache.hxx> +#include <section.hxx> +#include <cntfrm.hxx> +#include <flyfrm.hxx> +#include <txtfrm.hxx> +#include <tabfrm.hxx> +#include <viewsh.hxx> +#include <paratr.hxx> +#include <ftnidx.hxx> +#include <fmtftn.hxx> +#include <fmthdft.hxx> +#include <frmatr.hxx> +#include <fmtautofmt.hxx> +#include <frmtool.hxx> +#include <pagefrm.hxx> +#include <node2lay.hxx> +#include <pagedesc.hxx> +#include <fmtpdsc.hxx> +#include <breakit.hxx> +#include <SwStyleNameMapper.hxx> +#include <scriptinfo.hxx> +#include <rootfrm.hxx> +#include <istyleaccess.hxx> +#include <IDocumentListItems.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <calbck.hxx> +#include <ndole.hxx> +#include <memory> +#include <swcrsr.hxx> +#include <hints.hxx> +#include <frameformats.hxx> + +using namespace ::com::sun::star::i18n; + + +/* + * Some local helper functions for the attribute set handle of a content node. + * Since the attribute set of a content node may not be modified directly, + * we always have to create a new SwAttrSet, do the modifications, and get + * a new handle from the style access + */ + +namespace AttrSetHandleHelper +{ + +static void GetNewAutoStyle( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, + SwAttrSet const & rNewAttrSet ) +{ + const SwAttrSet* pAttrSet = static_cast<const SwAttrSet*>(rpAttrSet.get()); + if( rNode.GetModifyAtAttr() ) + const_cast<SwAttrSet*>(pAttrSet)->SetModifyAtAttr( nullptr ); + IStyleAccess& rSA = pAttrSet->GetPool()->GetDoc()->GetIStyleAccess(); + rpAttrSet = rSA.getAutomaticStyle( rNewAttrSet, rNode.IsTextNode() ? + IStyleAccess::AUTO_STYLE_PARA : + IStyleAccess::AUTO_STYLE_NOTXT ); + const bool bSetModifyAtAttr = const_cast<SwAttrSet*>(static_cast<const SwAttrSet*>(rpAttrSet.get()))->SetModifyAtAttr( &rNode ); + rNode.SetModifyAtAttr( bSetModifyAtAttr ); +} + +static void SetParent( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, + const SwFormat* pParentFormat, + const SwFormat* pConditionalFormat ) +{ + const SwAttrSet* pAttrSet = static_cast<const SwAttrSet*>(rpAttrSet.get()); + OSL_ENSURE( pAttrSet, "no SwAttrSet" ); + OSL_ENSURE( pParentFormat || !pConditionalFormat, "ConditionalFormat without ParentFormat?" ); + + const SwAttrSet* pParentSet = pParentFormat ? &pParentFormat->GetAttrSet() : nullptr; + + if ( pParentSet != pAttrSet->GetParent() ) + { + SwAttrSet aNewSet( *pAttrSet ); + aNewSet.SetParent( pParentSet ); + aNewSet.ClearItem( RES_FRMATR_STYLE_NAME ); + aNewSet.ClearItem( RES_FRMATR_CONDITIONAL_STYLE_NAME ); + OUString sVal; + + if ( pParentFormat ) + { + SwStyleNameMapper::FillProgName( pParentFormat->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + const SfxStringItem aAnyFormatColl( RES_FRMATR_STYLE_NAME, sVal ); + aNewSet.Put( aAnyFormatColl ); + + if ( pConditionalFormat != pParentFormat ) + SwStyleNameMapper::FillProgName( pConditionalFormat->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + + const SfxStringItem aFormatColl( RES_FRMATR_CONDITIONAL_STYLE_NAME, sVal ); + aNewSet.Put( aFormatColl ); + } + + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + } +} + +static const SfxPoolItem* Put( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, + const SfxPoolItem& rAttr ) +{ + SwAttrSet aNewSet( static_cast<const SwAttrSet&>(*rpAttrSet) ); + const SfxPoolItem* pRet = aNewSet.Put( rAttr ); + if ( pRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + return pRet; +} + +static bool Put( std::shared_ptr<const SfxItemSet>& rpAttrSet, const SwContentNode& rNode, + const SfxItemSet& rSet ) +{ + SwAttrSet aNewSet( static_cast<const SwAttrSet&>(*rpAttrSet) ); + + // #i76273# Robust + std::unique_ptr<SfxItemSet> pStyleNames; + if ( SfxItemState::SET == rSet.GetItemState( RES_FRMATR_STYLE_NAME, false ) ) + { + pStyleNames.reset(new SfxItemSet( *aNewSet.GetPool(), svl::Items<RES_FRMATR_STYLE_NAME, RES_FRMATR_CONDITIONAL_STYLE_NAME>{} )); + pStyleNames->Put( aNewSet ); + } + + const bool bRet = aNewSet.Put( rSet ); + + // #i76273# Robust + if ( pStyleNames ) + { + aNewSet.Put( *pStyleNames ); + } + + if ( bRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + + return bRet; +} + +static bool Put_BC( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, const SfxPoolItem& rAttr, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( static_cast<const SwAttrSet&>(*rpAttrSet) ); + + // for a correct broadcast, we need to do a SetModifyAtAttr with the items + // from aNewSet. The 'regular' SetModifyAtAttr is done in GetNewAutoStyle + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + + const bool bRet = aNewSet.Put_BC( rAttr, pOld, pNew ); + + if ( bRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + + return bRet; +} + +static bool Put_BC( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, const SfxItemSet& rSet, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( static_cast<const SwAttrSet&>(*rpAttrSet) ); + + // #i76273# Robust + std::unique_ptr<SfxItemSet> pStyleNames; + if ( SfxItemState::SET == rSet.GetItemState( RES_FRMATR_STYLE_NAME, false ) ) + { + pStyleNames.reset(new SfxItemSet( *aNewSet.GetPool(), svl::Items<RES_FRMATR_STYLE_NAME, RES_FRMATR_CONDITIONAL_STYLE_NAME>{} )); + pStyleNames->Put( aNewSet ); + } + + // for a correct broadcast, we need to do a SetModifyAtAttr with the items + // from aNewSet. The 'regular' SetModifyAtAttr is done in GetNewAutoStyle + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + + const bool bRet = aNewSet.Put_BC( rSet, pOld, pNew ); + + // #i76273# Robust + if ( pStyleNames ) + { + aNewSet.Put( *pStyleNames ); + } + + if ( bRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + + return bRet; +} + +static sal_uInt16 ClearItem_BC( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, sal_uInt16 nWhich, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( static_cast<const SwAttrSet&>(*rpAttrSet) ); + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + const sal_uInt16 nRet = aNewSet.ClearItem_BC( nWhich, pOld, pNew ); + if ( nRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + return nRet; +} + +static sal_uInt16 ClearItem_BC( std::shared_ptr<const SfxItemSet>& rpAttrSet, + const SwContentNode& rNode, + sal_uInt16 nWhich1, sal_uInt16 nWhich2, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( static_cast<const SwAttrSet&>(*rpAttrSet) ); + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + const sal_uInt16 nRet = aNewSet.ClearItem_BC( nWhich1, nWhich2, pOld, pNew ); + if ( nRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + return nRet; +} + +} + +/** Returns the section level at the position given by aIndex. + * + * We use the following logic: + * S = Start, E = End, C = ContentNode + * Level 0 = E + * 1 = S E + * 2 = SC + * + * All EndNodes of the BaseSection have level 0 + * All StartNodes of the BaseSection have level 1 + */ +sal_uInt16 SwNode::GetSectionLevel() const +{ + // EndNode of a BaseSection? They are always 0! + if( IsEndNode() && 0 == m_pStartOfSection->StartOfSectionIndex() ) + return 0; + + sal_uInt16 nLevel; + const SwNode* pNode = IsStartNode() ? this : m_pStartOfSection; + for( nLevel = 1; 0 != pNode->StartOfSectionIndex(); ++nLevel ) + pNode = pNode->m_pStartOfSection; + return IsEndNode() ? nLevel-1 : nLevel; +} + +#ifdef DBG_UTIL +long SwNode::s_nSerial = 0; +#endif + +SwNode::SwNode( const SwNodeIndex &rWhere, const SwNodeType nNdType ) + : m_nNodeType( nNdType ) + , m_nAFormatNumLvl( 0 ) + , m_bIgnoreDontExpand( false) + , m_eMerge(Merge::None) +#ifdef DBG_UTIL + , m_nSerial( s_nSerial++) +#endif + , m_pStartOfSection( nullptr ) +{ + if( rWhere.GetIndex() ) + { + SwNodes& rNodes = const_cast<SwNodes&> (rWhere.GetNodes()); + SwNode* pNd = rNodes[ rWhere.GetIndex() -1 ]; + rNodes.InsertNode( this, rWhere ); + if( nullptr == ( m_pStartOfSection = pNd->GetStartNode()) ) + { + m_pStartOfSection = pNd->m_pStartOfSection; + if( pNd->GetEndNode() ) // Skip EndNode ? Section + { + pNd = m_pStartOfSection; + m_pStartOfSection = pNd->m_pStartOfSection; + } + } + } +} + +/** Inserts a node into the rNodes array at the rWhere position + * + * @param rNodes the variable array in that the node will be inserted + * @param nPos position within the array where the node will be inserted + * @param nNdType the type of node to insert + */ +SwNode::SwNode( SwNodes& rNodes, sal_uLong nPos, const SwNodeType nNdType ) + : m_nNodeType( nNdType ) + , m_nAFormatNumLvl( 0 ) + , m_bIgnoreDontExpand( false) + , m_eMerge(Merge::None) +#ifdef DBG_UTIL + , m_nSerial( s_nSerial++) +#endif + , m_pStartOfSection( nullptr ) +{ + if( nPos ) + { + SwNode* pNd = rNodes[ nPos - 1 ]; + rNodes.InsertNode( this, nPos ); + if( nullptr == ( m_pStartOfSection = pNd->GetStartNode()) ) + { + m_pStartOfSection = pNd->m_pStartOfSection; + if( pNd->GetEndNode() ) // Skip EndNode ? Section! + { + pNd = m_pStartOfSection; + m_pStartOfSection = pNd->m_pStartOfSection; + } + } + } +} + +SwNode::~SwNode() +{ + assert(!m_pAnchoredFlys || GetDoc()->IsInDtor()); // must all be deleted +} + +/// Find the TableNode in which it is located. +/// If we're not in a table: return 0 +SwTableNode* SwNode::FindTableNode() +{ + if( IsTableNode() ) + return GetTableNode(); + SwStartNode* pTmp = m_pStartOfSection; + while( !pTmp->IsTableNode() && pTmp->GetIndex() ) + pTmp = pTmp->m_pStartOfSection; + return pTmp->GetTableNode(); +} + +/// Is the node located in the visible area of the Shell? +bool SwNode::IsInVisibleArea( SwViewShell const * pSh ) const +{ + bool bRet = false; + const SwContentNode* pNd; + + if( SwNodeType::Start & m_nNodeType ) + { + SwNodeIndex aIdx( *this ); + pNd = GetNodes().GoNext( &aIdx ); + } + else if( SwNodeType::End & m_nNodeType ) + { + SwNodeIndex aIdx( *EndOfSectionNode() ); + pNd = SwNodes::GoPrevious( &aIdx ); + } + else + pNd = GetContentNode(); + + if( !pSh ) + // Get the Shell from the Doc + pSh = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + if( pSh ) + { + const SwFrame* pFrame; + if (pNd && nullptr != (pFrame = pNd->getLayoutFrame(pSh->GetLayout(), nullptr, nullptr))) + { + + if ( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + + if( !pFrame->isFrameAreaDefinitionValid() ) + { + do + { + pFrame = pFrame->FindPrev(); + } + while ( pFrame && !pFrame->isFrameAreaDefinitionValid() ); + } + + if( !pFrame || pSh->VisArea().IsOver( pFrame->getFrameArea() ) ) + bRet = true; + } + } + + return bRet; +} + +bool SwNode::IsInProtectSect() const +{ + const SwNode* pNd = SwNodeType::Section == m_nNodeType ? m_pStartOfSection : this; + const SwSectionNode* pSectNd = pNd->FindSectionNode(); + return pSectNd && pSectNd->GetSection().IsProtectFlag(); +} + +/// Does the node contain anything protected? +/// I.e.: Area/Frame/Table rows/... including the Anchor for +/// Frames/Footnotes/... +bool SwNode::IsProtect() const +{ + const SwNode* pNd = SwNodeType::Section == m_nNodeType ? m_pStartOfSection : this; + const SwStartNode* pSttNd = pNd->FindSectionNode(); + if( pSttNd && static_cast<const SwSectionNode*>(pSttNd)->GetSection().IsProtectFlag() ) + return true; + + if( nullptr != ( pSttNd = FindTableBoxStartNode() ) ) + { + SwContentFrame* pCFrame; + if( IsContentNode() && nullptr != (pCFrame = static_cast<const SwContentNode*>(this)->getLayoutFrame( GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) )) + return pCFrame->IsProtected(); + + const SwTableBox* pBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + //Robust #149568 + if( pBox && pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + return true; + } + + SwFrameFormat* pFlyFormat = GetFlyFormat(); + if( pFlyFormat ) + { + if (pFlyFormat->GetProtect().IsContentProtected()) + return true; + const SwFormatAnchor& rAnchor = pFlyFormat->GetAnchor(); + const SwPosition* pAnchorPos = rAnchor.GetContentAnchor(); + if (!pAnchorPos) + return false; + const SwNode& rAnchorNd = pAnchorPos->nNode.GetNode(); + return &rAnchorNd != this && rAnchorNd.IsProtect(); + } + + if( nullptr != ( pSttNd = FindFootnoteStartNode() ) ) + { + const SwTextFootnote* pTFootnote = GetDoc()->GetFootnoteIdxs().SeekEntry( + SwNodeIndex( *pSttNd ) ); + if( pTFootnote ) + return pTFootnote->GetTextNode().IsProtect(); + } + + return false; +} + +/// Find the PageDesc that is used to format this node. If the Layout is available, +/// we search through that. Else we can only do it the hard way by searching onwards through the nodes. +const SwPageDesc* SwNode::FindPageDesc( size_t* pPgDescNdIdx ) const +{ + if ( !GetNodes().IsDocNodes() ) + { + return nullptr; + } + + const SwPageDesc* pPgDesc = nullptr; + + const SwContentNode* pNode; + if( SwNodeType::Start & m_nNodeType ) + { + SwNodeIndex aIdx( *this ); + pNode = GetNodes().GoNext( &aIdx ); + } + else if( SwNodeType::End & m_nNodeType ) + { + SwNodeIndex aIdx( *EndOfSectionNode() ); + pNode = SwNodes::GoPrevious( &aIdx ); + } + else + { + pNode = GetContentNode(); + if( pNode ) + pPgDesc = static_cast<const SwFormatPageDesc&>(pNode->GetAttr( RES_PAGEDESC )).GetPageDesc(); + } + + // Are we going through the layout? + if( !pPgDesc ) + { + const SwFrame* pFrame; + const SwPageFrame* pPage; + if (pNode && nullptr != (pFrame = pNode->getLayoutFrame(pNode->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) && + nullptr != ( pPage = pFrame->FindPageFrame() ) ) + { + pPgDesc = pPage->GetPageDesc(); + if ( pPgDescNdIdx ) + { + *pPgDescNdIdx = pNode->GetIndex(); + } + } + } + + if( !pPgDesc ) + { + // Thus via the nodes array + const SwDoc* pDoc = GetDoc(); + const SwNode* pNd = this; + const SwStartNode* pSttNd; + if( pNd->GetIndex() < GetNodes().GetEndOfExtras().GetIndex() && + nullptr != ( pSttNd = pNd->FindFlyStartNode() ) ) + { + // Find the right Anchor first + const SwFrameFormat* pFormat = nullptr; + const SwFrameFormats& rFormats = *pDoc->GetSpzFrameFormats(); + + for( size_t n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFrameFormat = rFormats[ n ]; + const SwFormatContent& rContent = pFrameFormat->GetContent(); + if( rContent.GetContentIdx() && + &rContent.GetContentIdx()->GetNode() == static_cast<SwNode const *>(pSttNd) ) + { + pFormat = pFrameFormat; + break; + } + } + + if( pFormat ) + { + const SwFormatAnchor* pAnchor = &pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) && + pAnchor->GetContentAnchor() ) + { + pNd = &pAnchor->GetContentAnchor()->nNode.GetNode(); + const SwNode* pFlyNd = pNd->FindFlyStartNode(); + while( pFlyNd ) + { + // Get up through the Anchor + size_t n; + for( n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFrameFormat = rFormats[ n ]; + const SwNodeIndex* pIdx = pFrameFormat->GetContent(). + GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + { + if( pFormat == pFrameFormat ) + { + pNd = pFlyNd; + pFlyNd = nullptr; + break; + } + pAnchor = &pFrameFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId()) || + !pAnchor->GetContentAnchor() ) + { + pFlyNd = nullptr; + break; + } + + pFlyNd = pAnchor->GetContentAnchor()->nNode. + GetNode().FindFlyStartNode(); + break; + } + } + if( n >= rFormats.size() ) + { + OSL_ENSURE( false, "FlySection, but no Format found" ); + return nullptr; + } + } + } + } + // pNd should now contain the correct Anchor or it's still this + } + + if( pNd->GetIndex() < GetNodes().GetEndOfExtras().GetIndex() ) + { + if( pNd->GetIndex() > GetNodes().GetEndOfAutotext().GetIndex() ) + { + pPgDesc = &pDoc->GetPageDesc( 0 ); + pNd = nullptr; + } + else + { + // Find the Body text node + if( nullptr != ( pSttNd = pNd->FindHeaderStartNode() ) || + nullptr != ( pSttNd = pNd->FindFooterStartNode() )) + { + // Then find this StartNode in the PageDescs + sal_uInt16 nId; + UseOnPage eAskUse; + if( SwHeaderStartNode == pSttNd->GetStartNodeType()) + { + nId = RES_HEADER; + eAskUse = UseOnPage::HeaderShare; + } + else + { + nId = RES_FOOTER; + eAskUse = UseOnPage::FooterShare; + } + + for( size_t n = pDoc->GetPageDescCnt(); n && !pPgDesc; ) + { + const SwPageDesc& rPgDsc = pDoc->GetPageDesc( --n ); + const SwFrameFormat* pFormat = &rPgDsc.GetMaster(); + int nStt = 0, nLast = 1; + if( !( eAskUse & rPgDsc.ReadUseOn() )) ++nLast; + + for( ; nStt < nLast; ++nStt, pFormat = &rPgDsc.GetLeft() ) + { + const SwFrameFormat * pHdFtFormat = nId == RES_HEADER + ? static_cast<SwFormatHeader const &>( + pFormat->GetFormatAttr(nId)).GetHeaderFormat() + : static_cast<SwFormatFooter const &>( + pFormat->GetFormatAttr(nId)).GetFooterFormat(); + if( pHdFtFormat ) + { + const SwFormatContent& rContent = pHdFtFormat->GetContent(); + if( rContent.GetContentIdx() && + &rContent.GetContentIdx()->GetNode() == + static_cast<SwNode const *>(pSttNd) ) + { + pPgDesc = &rPgDsc; + break; + } + } + } + } + + if( !pPgDesc ) + pPgDesc = &pDoc->GetPageDesc( 0 ); + pNd = nullptr; + } + else if( nullptr != ( pSttNd = pNd->FindFootnoteStartNode() )) + { + // the Anchor can only be in the Body text + const SwTextFootnote* pTextFootnote; + const SwFootnoteIdxs& rFootnoteArr = pDoc->GetFootnoteIdxs(); + for( size_t n = 0; n < rFootnoteArr.size(); ++n ) + if( nullptr != ( pTextFootnote = rFootnoteArr[ n ])->GetStartNode() && + static_cast<SwNode const *>(pSttNd) == + &pTextFootnote->GetStartNode()->GetNode() ) + { + pNd = &pTextFootnote->GetTextNode(); + break; + } + } + else + { + // Can only be a page-bound Fly (or something newer). + // we can only return the standard here + OSL_ENSURE( pNd->FindFlyStartNode(), + "Where is this Node?" ); + + pPgDesc = &pDoc->GetPageDesc( 0 ); + pNd = nullptr; + } + } + } + + if( pNd ) + { + SwFindNearestNode aInfo( *pNd ); + // Over all Nodes of all PageDescs + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_PAGEDESC)) + { + auto pPageDescItem = dynamic_cast<const SwFormatPageDesc*>(pItem); + if( pPageDescItem && pPageDescItem->GetDefinedIn() ) + { + const SwModify* pMod = pPageDescItem->GetDefinedIn(); + if( auto pContentNode = dynamic_cast<const SwContentNode*>( pMod) ) + aInfo.CheckNode( *pContentNode ); + else if( auto pFormat = dynamic_cast<const SwFormat*>( pMod) ) + pFormat->GetInfo( aInfo ); + } + } + + if( nullptr != ( pNd = aInfo.GetFoundNode() )) + { + if( pNd->IsContentNode() ) + pPgDesc = static_cast<const SwFormatPageDesc&>(pNd->GetContentNode()-> + GetAttr( RES_PAGEDESC )).GetPageDesc(); + else if( pNd->IsTableNode() ) + pPgDesc = pNd->GetTableNode()->GetTable(). + GetFrameFormat()->GetPageDesc().GetPageDesc(); + else if( pNd->IsSectionNode() ) + pPgDesc = pNd->GetSectionNode()->GetSection(). + GetFormat()->GetPageDesc().GetPageDesc(); + if ( pPgDescNdIdx ) + { + *pPgDescNdIdx = pNd->GetIndex(); + } + } + if( !pPgDesc ) + pPgDesc = &pDoc->GetPageDesc( 0 ); + } + } + return pPgDesc; +} + +/// If the node is located in a Fly, we return it formatted accordingly +SwFrameFormat* SwNode::GetFlyFormat() const +{ + SwFrameFormat* pRet = nullptr; + const SwNode* pSttNd = FindFlyStartNode(); + if( pSttNd ) + { + if( IsContentNode() ) + { + SwContentFrame* pFrame = SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<const SwContentNode*>(this)).First(); + if( pFrame ) + pRet = pFrame->FindFlyFrame()->GetFormat(); + } + if( !pRet ) + { + // The hard way through the Doc is our last way out + const SwFrameFormats& rFrameFormatTable = *GetDoc()->GetSpzFrameFormats(); + for( size_t n = 0; n < rFrameFormatTable.size(); ++n ) + { + SwFrameFormat* pFormat = rFrameFormatTable[n]; + // Only Writer fly frames can contain Writer nodes. + if (pFormat->Which() != RES_FLYFRMFMT) + continue; + const SwFormatContent& rContent = pFormat->GetContent(); + if( rContent.GetContentIdx() && + &rContent.GetContentIdx()->GetNode() == pSttNd ) + { + pRet = pFormat; + break; + } + } + } + } + return pRet; +} + +SwTableBox* SwNode::GetTableBox() const +{ + SwTableBox* pBox = nullptr; + const SwNode* pSttNd = FindTableBoxStartNode(); + if( pSttNd ) + pBox = const_cast<SwTableBox*>(pSttNd->FindTableNode()->GetTable().GetTableBox( + pSttNd->GetIndex() )); + return pBox; +} + +SwStartNode* SwNode::FindSttNodeByType( SwStartNodeType eTyp ) +{ + SwStartNode* pTmp = IsStartNode() ? static_cast<SwStartNode*>(this) : m_pStartOfSection; + + while( eTyp != pTmp->GetStartNodeType() && pTmp->GetIndex() ) + pTmp = pTmp->m_pStartOfSection; + return eTyp == pTmp->GetStartNodeType() ? pTmp : nullptr; +} + +const SwTextNode* SwNode::FindOutlineNodeOfLevel(sal_uInt8 const nLvl, + SwRootFrame const*const pLayout) const +{ + const SwTextNode* pRet = nullptr; + const SwOutlineNodes& rONds = GetNodes().GetOutLineNds(); + if( MAXLEVEL > nLvl && !rONds.empty() ) + { + SwOutlineNodes::size_type nPos; + SwNode* pNd = const_cast<SwNode*>(this); + bool bCheckFirst = false; + if( !rONds.Seek_Entry( pNd, &nPos )) + { + if (nPos == 0) + bCheckFirst = true; + } + else + { + ++nPos; + } + + if( bCheckFirst ) + { + // The first OutlineNode comes after the one asking. + // Test if both are on the same page. + // If not it's invalid. + for (nPos = 0; nPos < rONds.size(); ++nPos) + { + pRet = rONds[nPos]->GetTextNode(); + if (!pLayout || sw::IsParaPropsNode(*pLayout, *pRet)) + { + break; + } + } + if (nPos == rONds.size()) + { + return nullptr; + } + + const SwContentNode* pCNd = GetContentNode(); + + Point aPt( 0, 0 ); + std::pair<Point, bool> const tmp(aPt, false); + const SwFrame* pFrame = pRet->getLayoutFrame(pRet->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp), + * pMyFrame = pCNd ? pCNd->getLayoutFrame(pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + const SwPageFrame* pPgFrame = pFrame ? pFrame->FindPageFrame() : nullptr; + if( pPgFrame && pMyFrame && + pPgFrame->getFrameArea().Top() > pMyFrame->getFrameArea().Top() ) + { + // The one asking precedes the Page, thus its invalid + pRet = nullptr; + } + } + else + { + for ( ; 0 < nPos; --nPos) + { + SwTextNode const*const pNode = rONds[nPos - 1]->GetTextNode(); + if ((nPos == 1 /*as before*/ || pNode->GetAttrOutlineLevel() - 1 <= nLvl) + && (!pLayout || sw::IsParaPropsNode(*pLayout, *pNode))) + { + pRet = pNode; + break; + } + } + } + } + return pRet; +} + +static bool IsValidNextPrevNd( const SwNode& rNd ) +{ + return SwNodeType::Table == rNd.GetNodeType() || + ( SwNodeType::ContentMask & rNd.GetNodeType() ) || + ( SwNodeType::End == rNd.GetNodeType() && rNd.StartOfSectionNode() && + SwNodeType::Table == rNd.StartOfSectionNode()->GetNodeType() ); +} + +sal_uInt8 SwNode::HasPrevNextLayNode() const +{ + // assumption: <this> node is a node inside the document nodes array section. + + sal_uInt8 nRet = 0; + if( IsValidNextPrevNd( *this )) + { + SwNodeIndex aIdx( *this, -1 ); + // #i77805# - skip section start and end nodes + while ( aIdx.GetNode().IsSectionNode() || + ( aIdx.GetNode().IsEndNode() && + aIdx.GetNode().StartOfSectionNode()->IsSectionNode() ) ) + { + --aIdx; + } + if( IsValidNextPrevNd( aIdx.GetNode() )) + nRet |= ND_HAS_PREV_LAYNODE; + // #i77805# - skip section start and end nodes + aIdx.Assign(*this, +1); + while ( aIdx.GetNode().IsSectionNode() || + ( aIdx.GetNode().IsEndNode() && + aIdx.GetNode().StartOfSectionNode()->IsSectionNode() ) ) + { + ++aIdx; + } + if( IsValidNextPrevNd( aIdx.GetNode() )) + nRet |= ND_HAS_NEXT_LAYNODE; + } + return nRet; +} + +void SwNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + const char* pName = "???"; + switch (GetNodeType()) + { + case SwNodeType::End: + pName = "end"; + break; + case SwNodeType::Start: + case SwNodeType::Text: + case SwNodeType::Ole: + abort(); // overridden + case SwNodeType::Table: + pName = "table"; + break; + case SwNodeType::Grf: + pName = "grf"; + break; + default: break; + } + xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); + + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(OString::number(static_cast<sal_uInt8>(GetNodeType())).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(GetIndex()).getStr())); + + switch (GetNodeType()) + { + case SwNodeType::Grf: + { + auto pNoTextNode = static_cast<const SwNoTextNode*>(this); + const tools::PolyPolygon* pContour = pNoTextNode->HasContour(); + if (pContour) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("pContour")); + for (sal_uInt16 i = 0; i < pContour->Count(); ++i) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("polygon")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(i).getStr())); + const tools::Polygon& rPolygon = pContour->GetObject(i); + for (sal_uInt16 j = 0; j < rPolygon.GetSize(); ++j) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("point")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(j).getStr())); + const Point& rPoint = rPolygon.GetPoint(j); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("x"), + BAD_CAST(OString::number(rPoint.X()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("y"), + BAD_CAST(OString::number(rPoint.Y()).getStr())); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); + } + } + break; + default: + break; + } + + xmlTextWriterEndElement(pWriter); + if (GetNodeType() == SwNodeType::End) + xmlTextWriterEndElement(pWriter); // end start node +} + +SwStartNode::SwStartNode( const SwNodeIndex &rWhere, const SwNodeType nNdType, + SwStartNodeType eSttNd ) + : SwNode( rWhere, nNdType ), m_eStartNodeType( eSttNd ) +{ + if( !rWhere.GetIndex() ) + { + SwNodes& rNodes = const_cast<SwNodes&> (rWhere.GetNodes()); + rNodes.InsertNode( this, rWhere ); + m_pStartOfSection = this; + } + // Just do this temporarily until the EndNode is inserted + m_pEndOfSection = reinterpret_cast<SwEndNode*>(this); +} + +SwStartNode::SwStartNode( SwNodes& rNodes, sal_uLong nPos ) + : SwNode( rNodes, nPos, SwNodeType::Start ), m_eStartNodeType( SwNormalStartNode ) +{ + if( !nPos ) + { + rNodes.InsertNode( this, nPos ); + m_pStartOfSection = this; + } + // Just do this temporarily until the EndNode is inserted + m_pEndOfSection = reinterpret_cast<SwEndNode*>(this); +} + +void SwStartNode::CheckSectionCondColl() const +{ +//FEATURE::CONDCOLL + SwNodeIndex aIdx( *this ); + sal_uLong nEndIdx = EndOfSectionIndex(); + const SwNodes& rNds = GetNodes(); + SwContentNode* pCNd; + while( nullptr != ( pCNd = rNds.GoNext( &aIdx )) && pCNd->GetIndex() < nEndIdx ) + pCNd->ChkCondColl(); +//FEATURE::CONDCOLL +} + +void SwStartNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + const char* pName = "???"; + switch (GetNodeType()) + { + case SwNodeType::Table: + pName = "table"; + break; + case SwNodeType::Section: + pName = "section"; + break; + default: + switch(GetStartNodeType()) + { + case SwNormalStartNode: + pName = "start"; + break; + case SwTableBoxStartNode: + pName = "tablebox"; + break; + case SwFlyStartNode: + pName = "fly"; + break; + case SwFootnoteStartNode: + pName = "footnote"; + break; + case SwHeaderStartNode: + pName = "header"; + break; + case SwFooterStartNode: + pName = "footer"; + break; + } + break; + } + + xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(OString::number(static_cast<sal_uInt8>(GetNodeType())).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(GetIndex()).getStr())); + + if (IsTableNode()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("attrset")); + GetTableNode()->GetTable().GetFrameFormat()->GetAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + else if (GetStartNodeType() == SwTableBoxStartNode) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("attrset")); + if (SwTableBox* pBox = GetTableBox()) + pBox->GetFrameFormat()->GetAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + + // xmlTextWriterEndElement(pWriter); - it is a start node, so don't end, will make xml better nested +} + + +/** Insert a node into the array + * + * The StartOfSection pointer is set to the given node. + * + * The EndOfSection pointer of the corresponding start node is set to this node. + * + * @param rWhere position where the node shoul be inserted + * @param rSttNd the start note of the section + */ + +SwEndNode::SwEndNode( const SwNodeIndex &rWhere, SwStartNode& rSttNd ) + : SwNode( rWhere, SwNodeType::End ) +{ + m_pStartOfSection = &rSttNd; + m_pStartOfSection->m_pEndOfSection = this; +} + +SwEndNode::SwEndNode( SwNodes& rNds, sal_uLong nPos, SwStartNode& rSttNd ) + : SwNode( rNds, nPos, SwNodeType::End ) +{ + m_pStartOfSection = &rSttNd; + m_pStartOfSection->m_pEndOfSection = this; +} + +SwContentNode::SwContentNode( const SwNodeIndex &rWhere, const SwNodeType nNdType, + SwFormatColl *pColl ) + : SwNode( rWhere, nNdType ) + , m_aCondCollListener( *this ) + , m_pCondColl( nullptr ) + , mbSetModifyAtAttr( false ) +{ + if(pColl) + pColl->Add(this); +} + +SwContentNode::~SwContentNode() +{ + // The base class SwClient of SwFrame excludes itself from the dependency list! + // Thus, we need to delete all Frames in the dependency list. + if (!IsTextNode()) // see ~SwTextNode + { + DelFrames(nullptr); + } + + m_aCondCollListener.EndListeningAll(); + m_pCondColl = nullptr; + + if ( mpAttrSet && mbSetModifyAtAttr ) + const_cast<SwAttrSet*>(static_cast<const SwAttrSet*>(mpAttrSet.get()))->SetModifyAtAttr( nullptr ); +} + +void SwContentNode::SwClientNotify( const SwModify&, const SfxHint& rHint) +{ + if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + const sal_uInt16 nWhich = pLegacyHint->m_pOld + ? pLegacyHint->m_pOld->Which() + : pLegacyHint->m_pNew + ? pLegacyHint->m_pNew->Which() + : 0 ; + + bool bSetParent = false; + bool bCalcHidden = false; + SwFormatColl* pFormatColl = nullptr; + switch(nWhich) + { + case RES_OBJECTDYING: + { + SwFormat* pFormat = pLegacyHint->m_pNew + ? static_cast<SwFormat*>(static_cast<const SwPtrMsgPoolItem*>(pLegacyHint->m_pNew)->pObject) + : nullptr; + // Do not mangle pointers if it is the upper-most format! + if(pFormat && GetRegisteredIn() == pFormat) + { + if(pFormat->GetRegisteredIn()) + { + // If Parent, register anew in the new Parent + pFormat->GetRegisteredIn()->Add(this); + pFormatColl = GetFormatColl(); + } + else + EndListeningAll(); + bSetParent = true; + } + } + break; + + case RES_FMT_CHG: + // If the Format parent was switched, register the Attrset at the new one + // Skip own Modify! + if(GetpSwAttrSet() + && pLegacyHint->m_pNew + && static_cast<const SwFormatChg*>(pLegacyHint->m_pNew)->pChangedFormat == GetRegisteredIn()) + { + pFormatColl = GetFormatColl(); + bSetParent = true; + } + break; + + case RES_CONDCOLL_CONDCHG: + if(pLegacyHint->m_pNew + && static_cast<const SwCondCollCondChg*>(pLegacyHint->m_pNew)->pChangedFormat == GetRegisteredIn() + && &GetNodes() == &GetDoc()->GetNodes() ) + ChkCondColl(); + return; // Do not pass through to the base class/Frames + + case RES_ATTRSET_CHG: + if (GetNodes().IsDocNodes() + && IsTextNode() + && pLegacyHint->m_pOld + && SfxItemState::SET == static_cast<const SwAttrSetChg*>(pLegacyHint->m_pOld)->GetChgSet()->GetItemState(RES_CHRATR_HIDDEN, false)) + bCalcHidden = true; + break; + + case RES_UPDATE_ATTR: + if (GetNodes().IsDocNodes() + && IsTextNode() + && pLegacyHint->m_pNew + && RES_ATTRSET_CHG == static_cast<const SwUpdateAttr*>(pLegacyHint->m_pNew)->getWhichAttr()) + bCalcHidden = true; + break; + } + if(bSetParent && GetpSwAttrSet()) + AttrSetHandleHelper::SetParent(mpAttrSet, *this, pFormatColl, pFormatColl); + if(bCalcHidden) + static_cast<SwTextNode*>(this)->SetCalcHiddenCharFlags(); + NotifyClients(pLegacyHint->m_pOld, pLegacyHint->m_pNew); + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + m_pCondColl = const_cast<SwFormatColl*>(static_cast<const SwFormatColl*>(pModifyChangedHint->m_pNew)); + } +} + +bool SwContentNode::InvalidateNumRule() +{ + SwNumRule* pRule = nullptr; + const SfxPoolItem* pItem; + if( GetNodes().IsDocNodes() && + nullptr != ( pItem = GetNoCondAttr( RES_PARATR_NUMRULE, true )) && + !static_cast<const SwNumRuleItem*>(pItem)->GetValue().isEmpty() && + nullptr != (pRule = GetDoc()->FindNumRulePtr( + static_cast<const SwNumRuleItem*>(pItem)->GetValue() ) ) ) + { + pRule->SetInvalidRule( true ); + } + return nullptr != pRule; +} + +SwContentFrame *SwContentNode::getLayoutFrame( const SwRootFrame* _pRoot, + const SwPosition *const pPos, + std::pair<Point, bool> const*const pViewPosAndCalcFrame) const +{ + return static_cast<SwContentFrame*>( ::GetFrameOfModify( _pRoot, *this, FRM_CNTNT, + pPos, pViewPosAndCalcFrame)); +} + +SwRect SwContentNode::FindLayoutRect( const bool bPrtArea, const Point* pPoint ) const +{ + SwRect aRet; + std::pair<Point, bool> tmp; + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = false; + } + SwContentFrame* pFrame = static_cast<SwContentFrame*>( ::GetFrameOfModify( nullptr, *this, + FRM_CNTNT, nullptr, pPoint ? &tmp : nullptr) ); + if( pFrame ) + aRet = bPrtArea ? pFrame->getFramePrintArea() : pFrame->getFrameArea(); + return aRet; +} + +SwRect SwContentNode::FindPageFrameRect() const +{ + SwRect aRet; + SwFrame* pFrame = ::GetFrameOfModify( nullptr, *this, FRM_CNTNT ); + if( pFrame && nullptr != ( pFrame = pFrame->FindPageFrame() )) + aRet = pFrame->getFrameArea(); + return aRet; +} + +sal_Int32 SwContentNode::Len() const { return 0; } + +SwFormatColl *SwContentNode::ChgFormatColl( SwFormatColl *pNewColl ) +{ + OSL_ENSURE( pNewColl, "Collectionpointer is 0." ); + SwFormatColl *pOldColl = GetFormatColl(); + + if( pNewColl != pOldColl ) + { + pNewColl->Add( this ); + + // Set the Parent of out AutoAttributes to the new Collection + if( GetpSwAttrSet() ) + AttrSetHandleHelper::SetParent( mpAttrSet, *this, pNewColl, pNewColl ); + + SetCondFormatColl( nullptr ); + + if( !IsModifyLocked() ) + { + SwFormatChg aTmp1( pOldColl ); + SwFormatChg aTmp2( pNewColl ); + SwClientNotify( *this, sw::LegacyModifyHint(&aTmp1, &aTmp2) ); + } + } + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + return pOldColl; +} + +bool SwContentNode::GoNext(SwIndex * pIdx, sal_uInt16 nMode ) const +{ + bool bRet = true; + if( pIdx->GetIndex() < Len() ) + { + if( !IsTextNode() ) + ++(*pIdx); + else + { + const SwTextNode& rTNd = *GetTextNode(); + sal_Int32 nPos = pIdx->GetIndex(); + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + sal_Int32 nDone = 0; + sal_uInt16 nItrMode = ( CRSR_SKIP_CELLS & nMode ) ? + CharacterIteratorMode::SKIPCELL : + CharacterIteratorMode::SKIPCONTROLCHARACTER; + nPos = g_pBreakIt->GetBreakIter()->nextCharacters( rTNd.GetText(), nPos, + g_pBreakIt->GetLocale( rTNd.GetLang( nPos ) ), + nItrMode, 1, nDone ); + + // Check if nPos is inside hidden text range: + if ( CRSR_SKIP_HIDDEN & nMode ) + { + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( rTNd, nPos, nHiddenStart, nHiddenEnd ); + if ( nHiddenStart != COMPLETE_STRING && nHiddenStart != nPos ) + nPos = nHiddenEnd; + } + + if( 1 == nDone ) + *pIdx = nPos; + else + bRet = false; + } + } + else + bRet = false; + return bRet; +} + +bool SwContentNode::GoPrevious(SwIndex * pIdx, sal_uInt16 nMode ) const +{ + bool bRet = true; + if( pIdx->GetIndex() > 0 ) + { + if( !IsTextNode() ) + --(*pIdx); + else + { + const SwTextNode& rTNd = *GetTextNode(); + sal_Int32 nPos = pIdx->GetIndex(); + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + sal_Int32 nDone = 0; + sal_uInt16 nItrMode = ( CRSR_SKIP_CELLS & nMode ) ? + CharacterIteratorMode::SKIPCELL : + CharacterIteratorMode::SKIPCONTROLCHARACTER; + nPos = g_pBreakIt->GetBreakIter()->previousCharacters( rTNd.GetText(), nPos, + g_pBreakIt->GetLocale( rTNd.GetLang( nPos ) ), + nItrMode, 1, nDone ); + + // Check if nPos is inside hidden text range: + if ( CRSR_SKIP_HIDDEN & nMode ) + { + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( rTNd, nPos, nHiddenStart, nHiddenEnd ); + if ( nHiddenStart != COMPLETE_STRING ) + nPos = nHiddenStart; + } + + if( 1 == nDone ) + *pIdx = nPos; + else + bRet = false; + } + } + else + bRet = false; + return bRet; +} + +/** + * Creates all Views for the Doc for this Node. + * The created ContentFrames are attached to the corresponding Layout. + */ +void SwContentNode::MakeFramesForAdjacentContentNode(SwContentNode& rNode) +{ + OSL_ENSURE( &rNode != this, + "No ContentNode or CopyNode and new Node identical." ); + + if( !HasWriterListeners() || &rNode == this ) // Do we actually have Frames? + return; + + SwFrame *pFrame; + SwLayoutFrame *pUpper; + // Create Frames for Nodes which come after the Table? + OSL_ENSURE( FindTableNode() == rNode.FindTableNode(), "Table confusion" ); + + SwNode2Layout aNode2Layout( *this, rNode.GetIndex() ); + + while( nullptr != (pUpper = aNode2Layout.UpperFrame( pFrame, rNode )) ) + { + if (pUpper->getRootFrame()->IsHideRedlines() + && !rNode.IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwFrame *pNew = rNode.MakeFrame( pUpper ); + pNew->Paste( pUpper, pFrame ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + if ( pNew->IsTextFrame() ) + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pNew->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pNew->FindPrevCnt()) ); + } + } + } +} + +/** + * Deletes all Views from the Doc for this Node. + * The ContentFrames are removed from the corresponding Layout. + */ +void SwContentNode::DelFrames(SwRootFrame const*const pLayout) +{ + if( !HasWriterListeners() ) + return; + + SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for( SwContentFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + if (pLayout && pLayout != pFrame->getRootFrame()) + { + continue; // skip it + } + if (pFrame->IsTextFrame()) + { + if (sw::MergedPara * pMerged = + static_cast<SwTextFrame *>(pFrame)->GetMergedPara()) + { + if (this != pMerged->pFirstNode) + { + // SwNodes::RemoveNode iterates *backwards* - so + // ensure there are no more extents pointing to this + // node as SwFrame::InvalidatePage() will access them. + // Note: cannot send via SwClientNotify from dtor + // because that would access deleted wrong-lists + sw::UpdateMergedParaForDelete(*pMerged, true, + *static_cast<SwTextNode*>(this), 0, Len()); + if (this == pMerged->pParaPropsNode) + { + // otherwise pointer should have been updated to a different node + assert(this == pMerged->pLastNode); + assert(pMerged->extents.empty()); + for (sal_uLong i = pMerged->pLastNode->GetIndex() - 1;; + --i) + { + assert(pMerged->pFirstNode->GetIndex() <= i); + SwNode *const pNode(GetNodes()[i]); + if (pNode->IsTextNode() + && pNode->GetRedlineMergeFlag() != Merge::Hidden) + { + pMerged->pParaPropsNode = pNode->GetTextNode(); + break; + } + } + assert(pMerged->listener.IsListeningTo(pMerged->pParaPropsNode)); + } + assert(GetIndex() <= pMerged->pLastNode->GetIndex()); + if (this == pMerged->pLastNode) + { + // tdf#130680 find the previous node that is a + // listener of pMerged; see CheckParaRedlineMerge() + for (sal_uLong i = GetIndex() - 1; + this == pMerged->pLastNode; --i) + { + SwNode *const pNode = GetNodes()[i]; + if (pNode->IsTextNode()) + { + pMerged->pLastNode = pNode->GetTextNode(); + } + else if (SwEndNode const*const pEnd = pNode->GetEndNode()) + { + SwStartNode const*const pStart(pEnd->StartOfSectionNode()); + i = pStart->GetIndex(); // skip table or section + } + } + assert(pMerged->pFirstNode->GetIndex() <= pMerged->pLastNode->GetIndex()); + assert(pMerged->listener.IsListeningTo(pMerged->pLastNode)); + } + // avoid re-parenting mess (ModifyChangedHint) + pMerged->listener.EndListening(this); + continue; // don't delete + } + } + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pFrame->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pFrame->FindPrevCnt()) ); + } + } + + if( pFrame->IsFollow() ) + { + SwContentFrame* pMaster = pFrame->FindMaster(); + pMaster->SetFollow( pFrame->GetFollow() ); + } + pFrame->SetFollow( nullptr );//So it doesn't get funny ideas. + //Otherwise it could be possible that a follow + //gets destroyed before its master. Following + //the now invalid pointer will then lead to an + //illegal memory access. The chain can be + //crushed here because we'll destroy all of it + //anyway. + + if( pFrame->GetUpper() && pFrame->IsInFootnote() && !pFrame->GetIndNext() && + !pFrame->GetIndPrev() ) + { + SwFootnoteFrame *pFootnote = pFrame->FindFootnoteFrame(); + OSL_ENSURE( pFootnote, "You promised a FootnoteFrame?" ); + SwContentFrame* pCFrame; + if( !pFootnote->GetFollow() && !pFootnote->GetMaster() && + nullptr != ( pCFrame = pFootnote->GetRefFromAttr()) && pCFrame->IsFollow() ) + { + OSL_ENSURE( pCFrame->IsTextFrame(), "NoTextFrame has Footnote?" ); + pCFrame->FindMaster()->Prepare( PrepareHint::FootnoteInvalidationGone ); + } + } + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } +} + +SwContentNode *SwContentNode::JoinNext() +{ + return this; +} + +/// Get info from Modify +bool SwContentNode::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_AUTOFMT_DOCNODE: + if( &GetNodes() == static_cast<SwAutoFormatGetDocNode&>(rInfo).pNodes ) + { + return false; + } + break; + + case RES_FINDNEARESTNODE: + if( static_cast<const SwFormatPageDesc&>(GetAttr( RES_PAGEDESC )).GetPageDesc() ) + static_cast<SwFindNearestNode&>(rInfo).CheckNode( *this ); + return true; + + case RES_CONTENT_VISIBLE: + { + static_cast<SwPtrMsgPoolItem&>(rInfo).pObject = + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*this).First(); + } + return false; + } + + return SwModify::GetInfo( rInfo ); +} + +/// @param rAttr the attribute to set +bool SwContentNode::SetAttr(const SfxPoolItem& rAttr ) +{ + if( !GetpSwAttrSet() ) // Have the Nodes created by the corresponding AttrSets + NewAttrSet( GetDoc()->GetAttrPool() ); + + OSL_ENSURE( GetpSwAttrSet(), "Why did't we create an AttrSet?"); + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + + bool bRet = false; + // If Modify is locked, we do not send any Modifys + if( IsModifyLocked() || + ( !HasWriterListeners() && RES_PARATR_NUMRULE != rAttr.Which() )) + { + bRet = nullptr != AttrSetHandleHelper::Put( mpAttrSet, *this, rAttr ); + } + else + { + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bRet = AttrSetHandleHelper::Put_BC( mpAttrSet, *this, rAttr, &aOld, &aNew ); + if( bRet ) + { + SwAttrSetChg aChgOld( *GetpSwAttrSet(), aOld ); + SwAttrSetChg aChgNew( *GetpSwAttrSet(), aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // Send all changed ones + } + } + return bRet; +} + +bool SwContentNode::SetAttr( const SfxItemSet& rSet ) +{ + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + + const SfxPoolItem* pFnd = nullptr; + if( SfxItemState::SET == rSet.GetItemState( RES_AUTO_STYLE, false, &pFnd ) ) + { + OSL_ENSURE( rSet.Count() == 1, "SetAutoStyle mixed with other attributes?!" ); + const SwFormatAutoFormat* pTmp = static_cast<const SwFormatAutoFormat*>(pFnd); + + // If there already is an attribute set (usually containing a numbering + // item), we have to merge the attribute of the new set into the old set: + bool bSetParent = true; + if ( GetpSwAttrSet() ) + { + bSetParent = false; + AttrSetHandleHelper::Put( mpAttrSet, *this, *pTmp->GetStyleHandle() ); + } + else + { + mpAttrSet = pTmp->GetStyleHandle(); + } + + if ( bSetParent ) + { + // If the content node has a conditional style, we have to set the + // string item containing the correct conditional style name (the + // style name property has already been set during the import!) + // In case we do not have a conditional style, we make use of the + // fact that nobody else uses the attribute set behind the handle. + // FME 2007-07-10 #i78124# If autostyle does not have a parent, + // the string is empty. + const SfxPoolItem* pNameItem = nullptr; + if ( nullptr != GetCondFormatColl() || + SfxItemState::SET != mpAttrSet->GetItemState( RES_FRMATR_STYLE_NAME, false, &pNameItem ) || + static_cast<const SfxStringItem*>(pNameItem)->GetValue().isEmpty() ) + AttrSetHandleHelper::SetParent( mpAttrSet, *this, &GetAnyFormatColl(), GetFormatColl() ); + else + const_cast<SfxItemSet*>(mpAttrSet.get())->SetParent( &GetFormatColl()->GetAttrSet() ); + } + + return true; + } + + if( !GetpSwAttrSet() ) // Have the AttrsSets created by the corresponding Nodes + NewAttrSet( GetDoc()->GetAttrPool() ); + + bool bRet = false; + // If Modify is locked, do not send any Modifys + if ( IsModifyLocked() || + ( !HasWriterListeners() && + SfxItemState::SET != rSet.GetItemState( RES_PARATR_NUMRULE, false ) ) ) + { + // Some special treatment for Attributes + bRet = AttrSetHandleHelper::Put( mpAttrSet, *this, rSet ); + } + else + { + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bRet = AttrSetHandleHelper::Put_BC( mpAttrSet, *this, rSet, &aOld, &aNew ); + if( bRet ) + { + // Some special treatment for Attributes + SwAttrSetChg aChgOld( *GetpSwAttrSet(), aOld ); + SwAttrSetChg aChgNew( *GetpSwAttrSet(), aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // Send out all changed ones + } + } + return bRet; +} + +// With nWhich it takes the Hint from the Delta array +bool SwContentNode::ResetAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 ) +{ + if( !GetpSwAttrSet() ) + return false; + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + + // If Modify is locked, do not send out any Modifys + if( IsModifyLocked() ) + { + sal_uInt16 nDel = 0; + if ( !nWhich2 || nWhich2 < nWhich1 ) + { + std::vector<sal_uInt16> aClearWhichIds; + aClearWhichIds.push_back( nWhich1 ); + nDel = ClearItemsFromAttrSet( aClearWhichIds ); + } + else + nDel = AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, nWhich1, nWhich2, nullptr, nullptr ); + + if( !GetpSwAttrSet()->Count() ) // Empty? Delete + mpAttrSet.reset(); + return 0 != nDel; + } + + // No valid area defined? + if( !nWhich2 || nWhich2 < nWhich1 ) + nWhich2 = nWhich1; // Then set only this Item to 1st Id + + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bool bRet = 0 != AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, nWhich1, nWhich2, &aOld, &aNew ); + + if( bRet ) + { + SwAttrSetChg aChgOld( *GetpSwAttrSet(), aOld ); + SwAttrSetChg aChgNew( *GetpSwAttrSet(), aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // All changed ones are sent + + if( !GetpSwAttrSet()->Count() ) // Empty?, delete it + mpAttrSet.reset(); + } + return bRet; +} + +bool SwContentNode::ResetAttr( const std::vector<sal_uInt16>& rWhichArr ) +{ + if( !GetpSwAttrSet() ) + return false; + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + + // If Modify is locked, do not send out any Modifys + sal_uInt16 nDel = 0; + if( IsModifyLocked() ) + { + nDel = ClearItemsFromAttrSet( rWhichArr ); + } + else + { + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + + for ( const auto& rWhich : rWhichArr ) + if( AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, rWhich, &aOld, &aNew )) + ++nDel; + + if( nDel ) + { + SwAttrSetChg aChgOld( *GetpSwAttrSet(), aOld ); + SwAttrSetChg aChgNew( *GetpSwAttrSet(), aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // All changed ones are sent + } + } + if( !GetpSwAttrSet()->Count() ) // Empty?, delete it + mpAttrSet.reset(); + return 0 != nDel ; +} + +sal_uInt16 SwContentNode::ResetAllAttr() +{ + if( !GetpSwAttrSet() ) + return 0; + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + + // If Modify is locked, do not send out any Modifys + if( IsModifyLocked() ) + { + std::vector<sal_uInt16> aClearWhichIds; + aClearWhichIds.push_back(0); + sal_uInt16 nDel = ClearItemsFromAttrSet( aClearWhichIds ); + if( !GetpSwAttrSet()->Count() ) // Empty? Delete + mpAttrSet.reset(); + return nDel; + } + + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bool bRet = 0 != AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, 0, &aOld, &aNew ); + + if( bRet ) + { + SwAttrSetChg aChgOld( *GetpSwAttrSet(), aOld ); + SwAttrSetChg aChgNew( *GetpSwAttrSet(), aNew ); + ModifyNotification( &aChgOld, &aChgNew ); // All changed ones are sent + + if( !GetpSwAttrSet()->Count() ) // Empty? Delete + mpAttrSet.reset(); + } + return aNew.Count(); +} + +bool SwContentNode::GetAttr( SfxItemSet& rSet ) const +{ + if( rSet.Count() ) + rSet.ClearItem(); + + const SwAttrSet& rAttrSet = GetSwAttrSet(); + return rSet.Set( rAttrSet ); +} + +sal_uInt16 SwContentNode::ClearItemsFromAttrSet( const std::vector<sal_uInt16>& rWhichIds ) +{ + sal_uInt16 nRet = 0; + if ( rWhichIds.empty() ) + return nRet; + + OSL_ENSURE( GetpSwAttrSet(), "no item set" ); + SwAttrSet aNewAttrSet( *GetpSwAttrSet() ); + for ( const auto& rWhichId : rWhichIds ) + { + nRet = nRet + aNewAttrSet.ClearItem( rWhichId ); + } + if ( nRet ) + AttrSetHandleHelper::GetNewAutoStyle( mpAttrSet, *this, aNewAttrSet ); + + return nRet; +} + +const SfxPoolItem* SwContentNode::GetNoCondAttr( sal_uInt16 nWhich, + bool bInParents ) const +{ + const SfxPoolItem* pFnd = nullptr; + if( m_pCondColl && m_pCondColl->GetRegisteredIn() ) + { + if( !GetpSwAttrSet() || ( SfxItemState::SET != GetpSwAttrSet()->GetItemState( + nWhich, false, &pFnd ) && bInParents )) + { + (void)static_cast<const SwFormat*>(GetRegisteredIn())->GetItemState( nWhich, bInParents, &pFnd ); + } + } + // undo change of issue #i51029# + // Note: <GetSwAttrSet()> returns <mpAttrSet>, if set, otherwise it returns + // the attribute set of the paragraph style, which is valid for the + // content node - see file <node.hxx> + else + { + GetSwAttrSet().GetItemState( nWhich, bInParents, &pFnd ); + } + return pFnd; +} + +static bool lcl_CheckMaxLength(SwNode const& rPrev, SwNode const& rNext) +{ + if (rPrev.GetNodeType() != rNext.GetNodeType()) + { + return false; + } + if (!rPrev.IsTextNode()) + { + return true; + } + + // Check if a node can contain the other (order is not significant) + return rPrev.GetTextNode()->GetSpaceLeft() > rNext.GetTextNode()->Len(); +} + +/// Can we join two Nodes? +/// We can return the 2nd position in pIdx. +bool SwContentNode::CanJoinNext( SwNodeIndex* pIdx ) const +{ + const SwNodes& rNds = GetNodes(); + SwNodeIndex aIdx( *this, 1 ); + + const SwNode* pNd = this; + while( aIdx < rNds.Count()-1 && + (( pNd = &aIdx.GetNode())->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ))) + ++aIdx; + + if (rNds.Count()-1 == aIdx.GetIndex()) + return false; + if (!lcl_CheckMaxLength(*this, *pNd)) + { + return false; + } + if( pIdx ) + *pIdx = aIdx; + return true; +} + +/// Can we join two Nodes? +/// We can return the 2nd position in pIdx. +bool SwContentNode::CanJoinPrev( SwNodeIndex* pIdx ) const +{ + SwNodeIndex aIdx( *this, -1 ); + + const SwNode* pNd = this; + while( aIdx.GetIndex() && + (( pNd = &aIdx.GetNode())->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ))) + --aIdx; + + if (0 == aIdx.GetIndex()) + return false; + if (!lcl_CheckMaxLength(*pNd, *this)) + { + return false; + } + if( pIdx ) + *pIdx = aIdx; + return true; +} + +void SwContentNode::SetCondFormatColl(SwFormatColl* pColl) +{ + if( (!pColl && m_pCondColl) || ( pColl && !m_pCondColl ) || + ( pColl && pColl != m_pCondColl->GetRegisteredIn() ) ) + { + SwFormatColl* pOldColl = GetCondFormatColl(); + m_aCondCollListener.EndListeningAll(); + if(pColl) + m_aCondCollListener.StartListening(pColl); + m_pCondColl = pColl; + if(GetpSwAttrSet()) + AttrSetHandleHelper::SetParent(mpAttrSet, *this, &GetAnyFormatColl(), GetFormatColl()); + + if(!IsModifyLocked()) + { + SwFormatChg aTmp1(pOldColl ? pOldColl : GetFormatColl()); + SwFormatChg aTmp2(pColl ? pColl : GetFormatColl()); + NotifyClients(&aTmp1, &aTmp2); + } + if(IsInCache()) + { + SwFrame::GetCache().Delete(this); + SetInCache(false); + } + } +} + +bool SwContentNode::IsAnyCondition( SwCollCondition& rTmp ) const +{ + const SwNodes& rNds = GetNodes(); + { + Master_CollCondition nCond = Master_CollCondition::NONE; + const SwStartNode* pSttNd = StartOfSectionNode(); + while( pSttNd ) + { + switch( pSttNd->GetNodeType() ) + { + case SwNodeType::Table: nCond = Master_CollCondition::PARA_IN_TABLEBODY; break; + case SwNodeType::Section: nCond = Master_CollCondition::PARA_IN_SECTION; break; + + default: + switch( pSttNd->GetStartNodeType() ) + { + case SwTableBoxStartNode: + { + nCond = Master_CollCondition::PARA_IN_TABLEBODY; + const SwTableNode* pTableNd = pSttNd->FindTableNode(); + const SwTableBox* pBox; + if( pTableNd && nullptr != ( pBox = pTableNd->GetTable(). + GetTableBox(pSttNd->GetIndex()) ) && + pBox->IsInHeadline( &pTableNd->GetTable() ) ) + nCond = Master_CollCondition::PARA_IN_TABLEHEAD; + } + break; + case SwFlyStartNode: nCond = Master_CollCondition::PARA_IN_FRAME; break; + case SwFootnoteStartNode: + { + nCond = Master_CollCondition::PARA_IN_FOOTNOTE; + const SwFootnoteIdxs& rFootnoteArr = rNds.GetDoc()->GetFootnoteIdxs(); + const SwTextFootnote* pTextFootnote; + const SwNode* pSrchNd = pSttNd; + + for( size_t n = 0; n < rFootnoteArr.size(); ++n ) + if( nullptr != ( pTextFootnote = rFootnoteArr[ n ])->GetStartNode() && + pSrchNd == &pTextFootnote->GetStartNode()->GetNode() ) + { + if( pTextFootnote->GetFootnote().IsEndNote() ) + nCond = Master_CollCondition::PARA_IN_ENDNOTE; + break; + } + } + break; + case SwHeaderStartNode: nCond = Master_CollCondition::PARA_IN_HEADER; break; + case SwFooterStartNode: nCond = Master_CollCondition::PARA_IN_FOOTER; break; + case SwNormalStartNode: break; + } + } + + if( nCond != Master_CollCondition::NONE ) + { + rTmp.SetCondition( nCond, 0 ); + return true; + } + pSttNd = pSttNd->GetIndex() + ? pSttNd->StartOfSectionNode() + : nullptr; + } + } + + { + SwOutlineNodes::size_type nPos; + const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds(); + if( !rOutlNds.empty() ) + { + if( !rOutlNds.Seek_Entry( const_cast<SwContentNode*>(this), &nPos ) && nPos ) + --nPos; + if( nPos < rOutlNds.size() && + rOutlNds[ nPos ]->GetIndex() < GetIndex() ) + { + SwTextNode* pOutlNd = rOutlNds[ nPos ]->GetTextNode(); + + if( pOutlNd->IsOutline()) + { + rTmp.SetCondition( Master_CollCondition::PARA_IN_OUTLINE, pOutlNd->GetAttrOutlineLevel() - 1 ); + return true; + } + } + } + } + + return false; +} + +void SwContentNode::ChkCondColl() +{ + // Check, just to be sure + if( RES_CONDTXTFMTCOLL == GetFormatColl()->Which() ) + { + SwCollCondition aTmp( nullptr, Master_CollCondition::NONE, 0 ); + const SwCollCondition* pCColl; + + bool bDone = false; + + if( IsAnyCondition( aTmp )) + { + pCColl = static_cast<SwConditionTextFormatColl*>(GetFormatColl()) + ->HasCondition( aTmp ); + + if (pCColl) + { + SetCondFormatColl( pCColl->GetTextFormatColl() ); + bDone = true; + } + } + + if (!bDone) + { + if( IsTextNode() && static_cast<SwTextNode*>(this)->GetNumRule()) + { + // Is at which Level in a list? + aTmp.SetCondition( Master_CollCondition::PARA_IN_LIST, + static_cast<SwTextNode*>(this)->GetActualListLevel() ); + pCColl = static_cast<SwConditionTextFormatColl*>(GetFormatColl())-> + HasCondition( aTmp ); + } + else + pCColl = nullptr; + + if( pCColl ) + SetCondFormatColl( pCColl->GetTextFormatColl() ); + else if( m_pCondColl ) + SetCondFormatColl( nullptr ); + } + } +} + +// #i42921# +SvxFrameDirection SwContentNode::GetTextDirection( const SwPosition& rPos, + const Point* pPt ) const +{ + SvxFrameDirection nRet = SvxFrameDirection::Unknown; + + Point aPt; + if( pPt ) + aPt = *pPt; + + // #i72024# - No format of the frame, because this can cause recursive layout actions + std::pair<Point, bool> const tmp(aPt, false); + SwFrame* pFrame = getLayoutFrame( GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), &rPos, &tmp); + + if ( pFrame ) + { + if ( pFrame->IsVertical() ) + { + if (pFrame->IsVertLRBT()) + nRet = SvxFrameDirection::Vertical_LR_BT; + else if (pFrame->IsRightToLeft()) + nRet = SvxFrameDirection::Vertical_LR_TB; + else + nRet = SvxFrameDirection::Vertical_RL_TB; + } + else + { + if ( pFrame->IsRightToLeft() ) + nRet = SvxFrameDirection::Horizontal_RL_TB; + else + nRet = SvxFrameDirection::Horizontal_LR_TB; + } + } + + return nRet; +} + +std::unique_ptr<SwOLENodes> SwContentNode::CreateOLENodesArray( const SwFormatColl& rColl, bool bOnlyWithInvalidSize ) +{ + std::unique_ptr<SwOLENodes> pNodes; + SwIterator<SwContentNode,SwFormatColl> aIter( rColl ); + for( SwContentNode* pNd = aIter.First(); pNd; pNd = aIter.Next() ) + { + SwOLENode *pONd = pNd->GetOLENode(); + if ( pONd && (!bOnlyWithInvalidSize || pONd->IsOLESizeInvalid()) ) + { + if ( !pNodes ) + pNodes.reset(new SwOLENodes); + pNodes->push_back( pONd ); + } + } + + return pNodes; +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwContentNode::getSdrAllFillAttributesHelper() const +{ + return drawinglayer::attribute::SdrAllFillAttributesHelperPtr(); +} + +/* + * Document Interface Access + */ +const IDocumentSettingAccess* SwNode::getIDocumentSettingAccess() const { return &GetDoc()->GetDocumentSettingManager(); } +const IDocumentDeviceAccess& SwNode::getIDocumentDeviceAccess() const { return GetDoc()->getIDocumentDeviceAccess(); } +const IDocumentRedlineAccess& SwNode::getIDocumentRedlineAccess() const { return GetDoc()->getIDocumentRedlineAccess(); } +const IDocumentStylePoolAccess& SwNode::getIDocumentStylePoolAccess() const { return GetDoc()->getIDocumentStylePoolAccess(); } +const IDocumentDrawModelAccess& SwNode::getIDocumentDrawModelAccess() const { return GetDoc()->getIDocumentDrawModelAccess(); } +const IDocumentLayoutAccess& SwNode::getIDocumentLayoutAccess() const { return GetDoc()->getIDocumentLayoutAccess(); } +IDocumentLayoutAccess& SwNode::getIDocumentLayoutAccess() { return GetDoc()->getIDocumentLayoutAccess(); } +const IDocumentLinksAdministration& SwNode::getIDocumentLinksAdministration() const { return GetDoc()->getIDocumentLinksAdministration(); } +IDocumentLinksAdministration& SwNode::getIDocumentLinksAdministration() { return GetDoc()->getIDocumentLinksAdministration(); } +const IDocumentFieldsAccess& SwNode::getIDocumentFieldsAccess() const { return GetDoc()->getIDocumentFieldsAccess(); } +IDocumentFieldsAccess& SwNode::getIDocumentFieldsAccess() { return GetDoc()->getIDocumentFieldsAccess(); } +IDocumentContentOperations& SwNode::getIDocumentContentOperations() { return GetDoc()->getIDocumentContentOperations(); } +IDocumentListItems& SwNode::getIDocumentListItems() { return GetDoc()->getIDocumentListItems(); } // #i83479# + +const IDocumentMarkAccess* SwNode::getIDocumentMarkAccess() const { return GetDoc()->getIDocumentMarkAccess(); } +IStyleAccess& SwNode::getIDocumentStyleAccess() { return GetDoc()->GetIStyleAccess(); } + +bool SwNode::IsInRedlines() const +{ + const SwDoc * pDoc = GetDoc(); + bool bResult = false; + + if (pDoc != nullptr) + bResult = pDoc->getIDocumentRedlineAccess().IsInRedlines(*this); + + return bResult; +} + +void SwNode::AddAnchoredFly(SwFrameFormat *const pFlyFormat) +{ + assert(pFlyFormat); + assert(&pFlyFormat->GetAnchor(false).GetContentAnchor()->nNode.GetNode() == this); + // check node type, cf. SwFormatAnchor::SetAnchor() + assert(IsTextNode() || IsStartNode() || IsTableNode()); + if (!m_pAnchoredFlys) + { + m_pAnchoredFlys.reset(new std::vector<SwFrameFormat*>); + } + m_pAnchoredFlys->push_back(pFlyFormat); +} + +void SwNode::RemoveAnchoredFly(SwFrameFormat *const pFlyFormat) +{ + assert(pFlyFormat); + // cannot assert this in Remove because it is called when new anchor is already set +// assert(&pFlyFormat->GetAnchor(false).GetContentAnchor()->nNode.GetNode() == this); + assert(IsTextNode() || IsStartNode() || IsTableNode()); + assert(m_pAnchoredFlys); + auto it(std::find(m_pAnchoredFlys->begin(), m_pAnchoredFlys->end(), pFlyFormat)); + assert(it != m_pAnchoredFlys->end()); + m_pAnchoredFlys->erase(it); + if (m_pAnchoredFlys->empty()) + { + m_pAnchoredFlys.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/node2lay.cxx b/sw/source/core/docnode/node2lay.cxx new file mode 100644 index 000000000..a62f55ec6 --- /dev/null +++ b/sw/source/core/docnode/node2lay.cxx @@ -0,0 +1,467 @@ +/* -*- 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 <calbck.hxx> +#include <node.hxx> +#include <ndindex.hxx> +#include <swtable.hxx> +#include <ftnfrm.hxx> +#include <sectfrm.hxx> +#include <cntfrm.hxx> +#include <tabfrm.hxx> +#include <frmtool.hxx> +#include <section.hxx> +#include <node2lay.hxx> + +/** + * The SwNode2LayImpl class does the actual work, the SwNode2Layout class is + * just the public interface. + */ +class SwNode2LayImpl +{ + std::unique_ptr<SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti>> pIter; + SwModify* pMod; + std::vector<SwFrame*> mvUpperFrames; // To collect the Upper + sal_uLong nIndex; // The Index of the to-be-inserted Nodes + bool bMaster : 1; // true => only Master, false => only Frames without Follow + bool bInit : 1; // Did we already call First() at SwClient? + + SwNode2LayImpl(const SwNode2LayImpl&) = delete; + SwNode2LayImpl& operator=(const SwNode2LayImpl&) = delete; + +public: + SwNode2LayImpl( const SwNode& rNode, sal_uLong nIdx, bool bSearch ); + SwFrame* NextFrame(); // Returns the next "useful" Frame + SwLayoutFrame* UpperFrame( SwFrame* &rpFrame, const SwNode &rNode ); + void SaveUpperFrames(); // Saves (and locks if needed) the pUpper + // Inserts a Frame under every pUpper of the array + void RestoreUpperFrames( SwNodes& rNds, sal_uLong nStt, sal_uLong nEnd ); + + SwFrame* GetFrame( const Point* pDocPos ) const; +}; + +static SwNode* GoNextWithFrame(const SwNodes& rNodes, SwNodeIndex *pIdx) +{ + if( pIdx->GetIndex() >= rNodes.Count() - 1 ) + return nullptr; + + SwNodeIndex aTmp(*pIdx, +1); + SwNode* pNd = nullptr; + while( aTmp < rNodes.Count()-1 ) + { + pNd = &aTmp.GetNode(); + bool bFound = false; + if ( pNd->IsContentNode() ) + // sw_redlinehide: assume that it's OK to find a node with the same + // frame as the caller's one + bFound = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<SwContentNode*>(pNd)).First(); + else if ( pNd->IsTableNode() ) + bFound = SwIterator<SwFrame,SwFormat>(*static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat()).First() ; + else if( pNd->IsEndNode() && !pNd->StartOfSectionNode()->IsSectionNode() ) + { + pNd = nullptr; + break; + } + if ( bFound ) + break; + ++aTmp; + } + + if( aTmp == rNodes.Count()-1 ) + pNd = nullptr; + else if( pNd ) + (*pIdx) = aTmp; + return pNd; +} + +static SwNode* GoPreviousWithFrame(SwNodeIndex *pIdx) +{ + if( !pIdx->GetIndex() ) + return nullptr; + + SwNodeIndex aTmp( *pIdx, -1 ); + SwNode* pNd(nullptr); + while( aTmp.GetIndex() ) + { + pNd = &aTmp.GetNode(); + bool bFound = false; + if ( pNd->IsContentNode() ) + // sw_redlinehide: assume that it's OK to find a node with the same + // frame as the caller's one + bFound = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<SwContentNode*>(pNd)).First(); + else if ( pNd->IsTableNode() ) + bFound = SwIterator<SwFrame,SwFormat>(*static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat()).First(); + else if( pNd->IsStartNode() && !pNd->IsSectionNode() ) + { + pNd = nullptr; + break; + } + if ( bFound ) + break; + --aTmp; + } + + if( !aTmp.GetIndex() ) + pNd = nullptr; + else if( pNd ) + (*pIdx) = aTmp; + return pNd; +} + +/** + * The main purpose of this ctor is to find the right SwModify to iterate over. + * + * @param bSearch true: find the next Content or TableNode which contains + * Frames (to collect the pUpper). + * Else we assume that rNode points already to such a + * Content or TableNode. + * We insert before or after it. + */ +SwNode2LayImpl::SwNode2LayImpl( const SwNode& rNode, sal_uLong nIdx, bool bSearch ) + : nIndex( nIdx ), bInit( false ) +{ + const SwNode* pNd; + if( bSearch || rNode.IsSectionNode() ) + { + // Find the next Content/TableNode that contains a Frame, so that we can add + // ourselves before/after it + if( !bSearch && rNode.GetIndex() < nIndex ) + { + SwNodeIndex aTmp( *rNode.EndOfSectionNode(), +1 ); + pNd = GoPreviousWithFrame( &aTmp ); + if( pNd && rNode.GetIndex() > pNd->GetIndex() ) + pNd = nullptr; // Do not go over the limits + bMaster = false; + } + else + { + SwNodeIndex aTmp( rNode, -1 ); + pNd = GoNextWithFrame( rNode.GetNodes(), &aTmp ); + bMaster = true; + if( !bSearch && pNd && rNode.EndOfSectionIndex() < pNd->GetIndex() ) + pNd = nullptr; // Do not go over the limits + } + } + else + { + pNd = &rNode; + bMaster = nIndex < rNode.GetIndex(); + } + if( pNd ) + { + if( pNd->IsContentNode() ) + pMod = const_cast<SwModify*>(static_cast<SwModify const *>(pNd->GetContentNode())); + else + { + assert(pNd->IsTableNode()); + pMod = pNd->GetTableNode()->GetTable().GetFrameFormat(); + } + pIter.reset(new SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti>(*pMod)); + } + else + { + pIter = nullptr; + pMod = nullptr; + } +} + +/** + * Returns the next "useful" Frame. + * + * When calling this method for the first time, a First is triggered at the + * actual Iterator. The result is check for suitability: Follows are not + * accepted, a Master is accepted when collecting the pUpper and when + * inserting before it. + * When inserting after it, we find and return the last Follow starting + * from the Master. + * + * If the Frame is located in a SectionFrame, we check to see whether the + * SectionFrame is the suitable return value (instead of the Frame itself). + * This is the case if the to-be-inserted Node is outside of the Section. + */ +SwFrame* SwNode2LayImpl::NextFrame() +{ + SwFrame* pRet; + if( !pIter ) + return nullptr; + if( !bInit ) + { + pRet = pIter->First(); + bInit = true; + } + else + pRet = pIter->Next(); + while( pRet ) + { + SwFlowFrame* pFlow = SwFlowFrame::CastFlowFrame( pRet ); + assert(pFlow); + // Follows are pretty volatile, thus we ignore them. + // Even if we insert after the Frame, we start from the Master + // and iterate through it until the last Follow + if( !pFlow->IsFollow() ) + { + if( !bMaster ) + { + while( pFlow->HasFollow() ) + pFlow = pFlow->GetFollow(); + pRet = &(pFlow->GetFrame()); + } + if( pRet->IsInSct() ) + { + SwSectionFrame* pSct = pRet->FindSctFrame(); + // ATTENTION: If we are in a Footnote, from a Layout point of view + // it could be located in a Section with columns, although it + // should be outside of it when looking at the Nodes. + // Thus, when dealing with Footnotes, we need to check whether the + // SectionFrame is also located within the Footnote and not outside of it. + if( !pRet->IsInFootnote() || pSct->IsInFootnote() ) + { + assert(pSct && pSct->GetSection()); + SwSectionNode* pNd = pSct->GetSection()->GetFormat()->GetSectionNode(); + assert(pNd); + // If the result Frame is located within a Section Frame + // whose Section does not contain the Node, we return with + // the SectionFrame, else we return with the Content/TabFrame + if( bMaster ) + { + if( pNd->GetIndex() >= nIndex ) + pRet = pSct; + } + else if( pNd->EndOfSectionIndex() < nIndex ) + pRet = pSct; + } + } + return pRet; + } + pRet = pIter->Next(); + } + return nullptr; +} + +void SwNode2LayImpl::SaveUpperFrames() +{ + SwFrame* pFrame; + while( nullptr != (pFrame = NextFrame()) ) + { + SwFrame* pPrv = pFrame->GetPrev(); + pFrame = pFrame->GetUpper(); + if( pFrame ) + { + if( pFrame->IsFootnoteFrame() ) + static_cast<SwFootnoteFrame*>(pFrame)->ColLock(); + else if( pFrame->IsInSct() ) + pFrame->FindSctFrame()->ColLock(); + if( pPrv && pPrv->IsSctFrame() ) + static_cast<SwSectionFrame*>(pPrv)->LockJoin(); + mvUpperFrames.push_back( pPrv ); + mvUpperFrames.push_back( pFrame ); + } + } + pIter.reset(); + pMod = nullptr; +} + +SwLayoutFrame* SwNode2LayImpl::UpperFrame( SwFrame* &rpFrame, const SwNode &rNode ) +{ + rpFrame = NextFrame(); + if( !rpFrame ) + return nullptr; + SwLayoutFrame* pUpper = rpFrame->GetUpper(); + if( rpFrame->IsSctFrame() ) + { + const SwNode* pNode = rNode.StartOfSectionNode(); + if( pNode->IsSectionNode() ) + { + SwFrame* pFrame = bMaster ? rpFrame->FindPrev() : rpFrame->FindNext(); + if( pFrame && pFrame->IsSctFrame() ) + { + // pFrame could be a "dummy"-section + if( static_cast<SwSectionFrame*>(pFrame)->GetSection() && + (&static_cast<const SwSectionNode*>(pNode)->GetSection() == + static_cast<SwSectionFrame*>(pFrame)->GetSection()) ) + { + // #i22922# - consider columned sections + // 'Go down' the section frame as long as the layout frame + // is found, which would contain content. + while ( pFrame->IsLayoutFrame() && + static_cast<SwLayoutFrame*>(pFrame)->Lower() && + !static_cast<SwLayoutFrame*>(pFrame)->Lower()->IsFlowFrame() && + static_cast<SwLayoutFrame*>(pFrame)->Lower()->IsLayoutFrame() ) + { + pFrame = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + } + assert(pFrame->IsLayoutFrame()); + rpFrame = bMaster ? nullptr + : static_cast<SwLayoutFrame*>(pFrame)->Lower(); + assert((!rpFrame || rpFrame->IsFlowFrame()) && + "<SwNode2LayImpl::UpperFrame(..)> - expected sibling isn't a flow frame." ); + return static_cast<SwLayoutFrame*>(pFrame); + } + + pUpper = new SwSectionFrame(const_cast<SwSectionNode*>(static_cast<const SwSectionNode*>(pNode))->GetSection(), rpFrame); + pUpper->Paste( rpFrame->GetUpper(), + bMaster ? rpFrame : rpFrame->GetNext() ); + static_cast<SwSectionFrame*>(pUpper)->Init(); + rpFrame = nullptr; + // 'Go down' the section frame as long as the layout frame + // is found, which would contain content. + while ( pUpper->Lower() && + !pUpper->Lower()->IsFlowFrame() && + pUpper->Lower()->IsLayoutFrame() ) + { + pUpper = static_cast<SwLayoutFrame*>(pUpper->Lower()); + } + return pUpper; + } + } + } + if( !bMaster ) + rpFrame = rpFrame->GetNext(); + return pUpper; +} + +void SwNode2LayImpl::RestoreUpperFrames( SwNodes& rNds, sal_uLong nStt, sal_uLong nEnd ) +{ + SwNode* pNd; + SwDoc *pDoc = rNds.GetDoc(); + bool bFirst = true; + for( ; nStt < nEnd; ++nStt ) + { + SwFrame* pNew = nullptr; + SwFrame* pNxt; + SwLayoutFrame* pUp; + if( (pNd = rNds[nStt])->IsContentNode() ) + for( std::vector<SwFrame*>::size_type n = 0; n < mvUpperFrames.size(); ) + { + pNxt = mvUpperFrames[n++]; + if( bFirst && pNxt && pNxt->IsSctFrame() ) + static_cast<SwSectionFrame*>(pNxt)->UnlockJoin(); + pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[n++]); + if( pNxt ) + pNxt = pNxt->GetNext(); + else + pNxt = pUp->Lower(); + pNew = static_cast<SwContentNode*>(pNd)->MakeFrame( pUp ); + pNew->Paste( pUp, pNxt ); + mvUpperFrames[n-2] = pNew; + } + else if( pNd->IsTableNode() ) + for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ) + { + pNxt = mvUpperFrames[x++]; + if( bFirst && pNxt && pNxt->IsSctFrame() ) + static_cast<SwSectionFrame*>(pNxt)->UnlockJoin(); + pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[x++]); + if( pNxt ) + pNxt = pNxt->GetNext(); + else + pNxt = pUp->Lower(); + pNew = static_cast<SwTableNode*>(pNd)->MakeFrame( pUp ); + assert(pNew->IsTabFrame()); + pNew->Paste( pUp, pNxt ); + static_cast<SwTabFrame*>(pNew)->RegistFlys(); + mvUpperFrames[x-2] = pNew; + } + else if( pNd->IsSectionNode() ) + { + nStt = pNd->EndOfSectionIndex(); + for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ) + { + pNxt = mvUpperFrames[x++]; + if( bFirst && pNxt && pNxt->IsSctFrame() ) + static_cast<SwSectionFrame*>(pNxt)->UnlockJoin(); + pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[x++]); + OSL_ENSURE( pUp->GetUpper() || pUp->IsFlyFrame(), "Lost Upper" ); + ::InsertCnt_( pUp, pDoc, pNd->GetIndex(), false, nStt+1, pNxt ); + pNxt = pUp->GetLastLower(); + mvUpperFrames[x-2] = pNxt; + } + } + bFirst = false; + } + for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ++x ) + { + SwFrame* pTmp = mvUpperFrames[++x]; + if( pTmp->IsFootnoteFrame() ) + static_cast<SwFootnoteFrame*>(pTmp)->ColUnlock(); + else if ( pTmp->IsInSct() ) + { + SwSectionFrame* pSctFrame = pTmp->FindSctFrame(); + pSctFrame->ColUnlock(); + // #i18103# - invalidate size of section in order to + // assure, that the section is formatted, unless it was 'Collocked' + // from its 'collection' until its 'restoration'. + pSctFrame->InvalidateSize_(); + } + } +} + +SwFrame* SwNode2LayImpl::GetFrame( const Point* pDocPos ) const +{ + // test if change of member pIter -> pMod broke anything + std::pair<Point, bool> tmp; + if (pDocPos) + { + tmp.first = *pDocPos; + tmp.second = false; + } + return pMod ? ::GetFrameOfModify(nullptr, *pMod, FRM_ALL, nullptr, pDocPos ? &tmp : nullptr) : nullptr; +} + +SwNode2Layout::SwNode2Layout( const SwNode& rNd, sal_uLong nIdx ) + : pImpl( new SwNode2LayImpl( rNd, nIdx, false ) ) +{ +} + +SwNode2LayoutSaveUpperFrames::SwNode2LayoutSaveUpperFrames(const SwNode& rNd) + : pImpl( new SwNode2LayImpl( rNd, rNd.GetIndex(), true ) ) +{ + pImpl->SaveUpperFrames(); +} + +void SwNode2LayoutSaveUpperFrames::RestoreUpperFrames( + SwNodes& rNds, sal_uLong const nStt, sal_uLong const nEnd) +{ + pImpl->RestoreUpperFrames( rNds, nStt, nEnd ); +} + +SwFrame* SwNode2Layout::NextFrame() +{ + return pImpl->NextFrame(); +} + +SwLayoutFrame* SwNode2Layout::UpperFrame( SwFrame* &rpFrame, const SwNode &rNode ) +{ + return pImpl->UpperFrame( rpFrame, rNode ); +} + +SwNode2Layout::~SwNode2Layout() +{ +} + +SwNode2LayoutSaveUpperFrames::~SwNode2LayoutSaveUpperFrames() +{ +} + +SwFrame* SwNode2Layout::GetFrame( const Point* pDocPos ) const +{ + return pImpl->GetFrame( pDocPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/nodes.cxx b/sw/source/core/docnode/nodes.cxx new file mode 100644 index 000000000..01febcc78 --- /dev/null +++ b/sw/source/core/docnode/nodes.cxx @@ -0,0 +1,2335 @@ +/* -*- 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 <stdlib.h> + +#include <libxml/xmlwriter.h> +#include <osl/diagnose.h> + +#include <node.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <txtfld.hxx> +#include <fmtfld.hxx> +#include <numrule.hxx> +#include <ndtxt.hxx> +#include <ndnotxt.hxx> +#include <swtable.hxx> +#include <section.hxx> +#include <ddefld.hxx> +#include <swddetbl.hxx> +#include <txtatr.hxx> +#include <tox.hxx> +#include <fmtrfmrk.hxx> +#include <fmtftn.hxx> + +#include <docsh.hxx> + +typedef std::vector<SwStartNode*> SwStartNodePointers; + +// function to determine the highest level in the given range +static sal_uInt16 HighestLevel( SwNodes & rNodes, const SwNodeRange & rRange ); + +/** Constructor + * + * creates the base sections (PostIts, Inserts, AutoText, RedLines, Content) + * + * @param pDocument TODO: provide documentation + */ +SwNodes::SwNodes( SwDoc* pDocument ) + : m_vIndices(nullptr), m_pMyDoc( pDocument ) +{ + m_bInNodesDel = m_bInDelUpdOutline = false; + + assert(m_pMyDoc); + + sal_uLong nPos = 0; + SwStartNode* pSttNd = new SwStartNode( *this, nPos++ ); + m_pEndOfPostIts = new SwEndNode( *this, nPos++, *pSttNd ); + + SwStartNode* pTmp = new SwStartNode( *this, nPos++ ); + m_pEndOfInserts = new SwEndNode( *this, nPos++, *pTmp ); + + pTmp = new SwStartNode( *this, nPos++ ); + pTmp->m_pStartOfSection = pSttNd; + m_pEndOfAutotext = new SwEndNode( *this, nPos++, *pTmp ); + + pTmp = new SwStartNode( *this, nPos++ ); + pTmp->m_pStartOfSection = pSttNd; + m_pEndOfRedlines = new SwEndNode( *this, nPos++, *pTmp ); + + pTmp = new SwStartNode( *this, nPos++ ); + pTmp->m_pStartOfSection = pSttNd; + m_pEndOfContent.reset(new SwEndNode( *this, nPos++, *pTmp )); + + m_pOutlineNodes.reset(new SwOutlineNodes); +} + +/** Destructor + * + * Deletes all nodes whose pointer are in a dynamic array. This should be no + * problem as nodes cannot be created outside this array and, thus, cannot be + * part of multiple arrays. + */ +SwNodes::~SwNodes() +{ + m_pOutlineNodes.reset(); + + { + SwNodeIndex aNdIdx( *this ); + while( true ) + { + SwNode *pNode = &aNdIdx.GetNode(); + if( pNode == m_pEndOfContent.get() ) + break; + + ++aNdIdx; + delete pNode; + } + } + + // here, all SwNodeIndices must be unregistered + m_pEndOfContent.reset(); +} + +void SwNodes::ChgNode( SwNodeIndex const & rDelPos, sal_uLong nSz, + SwNodeIndex& rInsPos, bool bNewFrames ) +{ + // no need for frames in the UndoArea + SwNodes& rNds = rInsPos.GetNodes(); + const SwNode* pPrevInsNd = rNds[ rInsPos.GetIndex() -1 ]; + + // declare all fields as invalid, updating will happen + // in the idle-handler of the doc + if( GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, &rDelPos.GetNode(), nSz ) && + rNds.GetDoc() != GetDoc() ) + rNds.GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + + // NEVER include nodes from the RedLineArea + sal_uLong nNd = rInsPos.GetIndex(); + bool bInsOutlineIdx = !( + rNds.GetEndOfRedlines().StartOfSectionNode()->GetIndex() < nNd && + nNd < rNds.GetEndOfRedlines().GetIndex() ); + + if( &rNds == this ) // if in the same node array -> move + { + // Move order: from front to back, so that new entries are added at + // first position, thus, deletion position stays the same + const sal_uLong nDiff = rDelPos.GetIndex() < rInsPos.GetIndex() ? 0 : 1; + + for( sal_uLong n = rDelPos.GetIndex(); nSz; n += nDiff, --nSz ) + { + SwNodeIndex aDelIdx( *this, n ); + SwNode& rNd = aDelIdx.GetNode(); + + // #i57920# - correction of refactoring done by cws swnumtree: + // - <SwTextNode::SetLevel( NO_NUMBERING ) is deprecated and + // set <IsCounted> state of the text node to <false>, which + // isn't correct here. + if ( rNd.IsTextNode() ) + { + SwTextNode* pTextNode = rNd.GetTextNode(); + + pTextNode->RemoveFromList(); + + if (pTextNode->IsOutline()) + { + const SwNodePtr pSrch = &rNd; + m_pOutlineNodes->erase( pSrch ); + } + } + + BigPtrArray::Move( aDelIdx.GetIndex(), rInsPos.GetIndex() ); + + if( rNd.IsTextNode() ) + { + SwTextNode& rTextNd = static_cast<SwTextNode&>(rNd); + + rTextNd.AddToList(); + + if (bInsOutlineIdx && rTextNd.IsOutline()) + { + const SwNodePtr pSrch = &rNd; + m_pOutlineNodes->insert( pSrch ); + } + rTextNd.InvalidateNumRule(); + +//FEATURE::CONDCOLL + if( RES_CONDTXTFMTCOLL == rTextNd.GetTextColl()->Which() ) + rTextNd.ChkCondColl(); +//FEATURE::CONDCOLL + } + else if( rNd.IsContentNode() ) + static_cast<SwContentNode&>(rNd).InvalidateNumRule(); + } + } + else + { + bool bSavePersData(GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNds)); + bool bRestPersData(GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(*this)); + SwDoc* pDestDoc = rNds.GetDoc() != GetDoc() ? rNds.GetDoc() : nullptr; + OSL_ENSURE(!pDestDoc, "SwNodes::ChgNode(): " + "the code to handle text fields here looks broken\n" + "if the target is in a different document."); + if( !bRestPersData && !bSavePersData && pDestDoc ) + bSavePersData = bRestPersData = true; + + OUString sNumRule; + for( sal_uLong n = 0; n < nSz; n++ ) + { + SwNode* pNd = &rDelPos.GetNode(); + + // NoTextNode keep their persistent data + if( pNd->IsNoTextNode() ) + { + if( bSavePersData ) + static_cast<SwNoTextNode*>(pNd)->SavePersistentData(); + } + else if( pNd->IsTextNode() ) + { + SwTextNode* pTextNd = static_cast<SwTextNode*>(pNd); + + // remove outline index from old nodes array + if (pTextNd->IsOutline()) + { + m_pOutlineNodes->erase( pNd ); + } + + // copy rules if needed + if( pDestDoc ) + { + const SwNumRule* pNumRule = pTextNd->GetNumRule(); + if( pNumRule && sNumRule != pNumRule->GetName() ) + { + sNumRule = pNumRule->GetName(); + SwNumRule* pDestRule = pDestDoc->FindNumRulePtr( sNumRule ); + if( pDestRule ) + pDestRule->SetInvalidRule( true ); + else + pDestDoc->MakeNumRule( sNumRule, pNumRule ); + } + } + else + // if movement into the UndoNodes-array, update numbering + pTextNd->InvalidateNumRule(); + + pTextNd->RemoveFromList(); + } + + RemoveNode( rDelPos.GetIndex(), 1, false ); // move indices + SwContentNode * pCNd = pNd->GetContentNode(); + rNds.InsertNode( pNd, rInsPos ); + + if( pCNd ) + { + SwTextNode* pTextNd = pCNd->GetTextNode(); + if( pTextNd ) + { + SwpHints * const pHts = pTextNd->GetpSwpHints(); + // OutlineNodes set the new nodes in the array + if (bInsOutlineIdx && pTextNd->IsOutline()) + { + rNds.m_pOutlineNodes->insert( pTextNd ); + } + + pTextNd->AddToList(); + + // special treatment for fields + if( pHts && pHts->Count() ) + { + bool const bToUndo = !pDestDoc && + GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNds); + for( size_t i = pHts->Count(); i; ) + { + SwTextAttr * const pAttr = pHts->Get( --i ); + switch ( pAttr->Which() ) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + { + SwTextField* pTextField = static_txtattr_cast<SwTextField*>(pAttr); + rNds.GetDoc()->getIDocumentFieldsAccess().InsDelFieldInFieldLst( !bToUndo, *pTextField ); + + const SwFieldType* pTyp = pTextField->GetFormatField().GetField()->GetTyp(); + if ( SwFieldIds::Postit == pTyp->Which() ) + { + rNds.GetDoc()->GetDocShell()->Broadcast( + SwFormatFieldHint( + &pTextField->GetFormatField(), + ( pTextField->GetFormatField().IsFieldInDoc() + ? SwFormatFieldHintWhich::INSERTED + : SwFormatFieldHintWhich::REMOVED ) ) ); + } + else if( SwFieldIds::Dde == pTyp->Which() ) + { + if( bToUndo ) + const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pTyp))->DecRefCnt(); + else + const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pTyp))->IncRefCnt(); + } + static_cast<SwFormatField&>(pAttr->GetAttr()) + .InvalidateField(); + } + break; + + case RES_TXTATR_FTN: + static_cast<SwFormatFootnote&>(pAttr->GetAttr()) + .InvalidateFootnote(); + break; + + case RES_TXTATR_TOXMARK: + static_cast<SwTOXMark&>(pAttr->GetAttr()) + .InvalidateTOXMark(); + break; + + case RES_TXTATR_REFMARK: + static_cast<SwFormatRefMark&>(pAttr->GetAttr()) + .InvalidateRefMark(); + break; + + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + SwTextMeta *const pTextMeta( + static_txtattr_cast<SwTextMeta*>(pAttr)); + // force removal of UNO object + pTextMeta->ChgTextNode(nullptr); + pTextMeta->ChgTextNode(pTextNd); + } + break; + + default: + break; + } + } + } + //FEATURE::CONDCOLL + if( RES_CONDTXTFMTCOLL == pTextNd->GetTextColl()->Which() ) + pTextNd->ChkCondColl(); + //FEATURE::CONDCOLL + } + else + { + // Moved into different Docs? Persist data again! + if( pCNd->IsNoTextNode() && bRestPersData ) + static_cast<SwNoTextNode*>(pCNd)->RestorePersistentData(); + } + } + } + } + + // declare all fields as invalid, updating will happen + // in the idle-handler of the doc + GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + if( rNds.GetDoc() != GetDoc() ) + rNds.GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); + + if( bNewFrames ) + bNewFrames = &GetDoc()->GetNodes() == &rNds && + GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + if( bNewFrames ) + { + // get the frames: + SwNodeIndex aIdx( *pPrevInsNd, 1 ); + SwNodeIndex aFrameNdIdx( aIdx ); + SwNode* pFrameNd = rNds.FindPrvNxtFrameNode( aFrameNdIdx, + rNds[ rInsPos.GetIndex() - 1 ] ); + + if( pFrameNd ) + while( aIdx != rInsPos ) + { + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( pCNd ) + { + if( pFrameNd->IsTableNode() ) + static_cast<SwTableNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aIdx); + else if( pFrameNd->IsSectionNode() ) + static_cast<SwSectionNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aIdx); + else + static_cast<SwContentNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); + pFrameNd = pCNd; + } + ++aIdx; + } + } +} + +// TODO: provide documentation +/** move the node pointer + * + * Move the node pointer from "(inclusive) start position to (exclusive) end + * position" to target position. + * If the target is in front of the first or in the area between first and + * last element to move, nothing happens. + * If the area to move is empty or the end position is before the start + * position, nothing happens. + * + * @param aRange range to move (excluding end node) + * @param rNodes + * @param aIndex + * @param bNewFrames + * @return + */ +bool SwNodes::MoveNodes( const SwNodeRange& aRange, SwNodes & rNodes, + const SwNodeIndex& aIndex, bool bNewFrames ) +{ + SwNode * pCurrentNode; + if( aIndex == 0 || + ( (pCurrentNode = &aIndex.GetNode())->GetStartNode() && + !pCurrentNode->StartOfSectionIndex() )) + return false; + + SwNodeRange aRg( aRange ); + + // skip "simple" start or end nodes + while( SwNodeType::Start == (pCurrentNode = &aRg.aStart.GetNode())->GetNodeType() + || ( pCurrentNode->IsEndNode() && + !pCurrentNode->m_pStartOfSection->IsSectionNode() ) ) + ++aRg.aStart; + --aRg.aStart; + + // if aEnd-1 points to no ContentNode, search previous one + --aRg.aEnd; + while( ( (( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && + !pCurrentNode->IsSectionNode() ) || + ( pCurrentNode->IsEndNode() && + SwNodeType::Start == pCurrentNode->m_pStartOfSection->GetNodeType()) ) && + aRg.aEnd > aRg.aStart ) + --aRg.aEnd; + + // if in same array, check insertion position + if( aRg.aStart >= aRg.aEnd ) + return false; + + if( this == &rNodes ) + { + if( ( aIndex.GetIndex()-1 >= aRg.aStart.GetIndex() && + aIndex.GetIndex()-1 < aRg.aEnd.GetIndex()) || + ( aIndex.GetIndex()-1 == aRg.aEnd.GetIndex() ) ) + return false; + } + + sal_uLong nInsPos = 0; // counter for tmp array + + // array as a stack, storing all StartOfSelections + SwStartNodePointers aSttNdStack; + SwStartNodePointers::size_type nLevel = 0; // level counter + + // set start index + SwNodeIndex aIdx( aIndex ); + + SwStartNode* pStartNode = aIdx.GetNode().m_pStartOfSection; + aSttNdStack.insert( aSttNdStack.begin(), pStartNode ); + + SwNodeRange aOrigInsPos( aIdx, -1, aIdx ); // original insertion position + + // call DelFrames/MakeFrames for the upmost SectionNode + int nSectNdCnt = 0; + bool bSaveNewFrames = bNewFrames; + + // continue until everything has been moved + while( aRg.aStart < aRg.aEnd ) + { + pCurrentNode = &aRg.aEnd.GetNode(); + switch( pCurrentNode->GetNodeType() ) + { + case SwNodeType::End: + { + if( nInsPos ) // move everything until here + { + // delete and copy. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos; + nInsPos = 0; + } + + SwStartNode* pSttNd = pCurrentNode->m_pStartOfSection; + if( pSttNd->IsTableNode() ) + { + SwTableNode* pTableNd = static_cast<SwTableNode*>(pSttNd); + + // move the whole table/range + nInsPos = (aRg.aEnd.GetIndex() - + pSttNd->GetIndex() )+1; + aRg.aEnd -= nInsPos; + + // NEVER include nodes from the RedLineArea + sal_uLong nNd = aIdx.GetIndex(); + bool bInsOutlineIdx = !( rNodes.GetEndOfRedlines(). + StartOfSectionNode()->GetIndex() < nNd && + nNd < rNodes.GetEndOfRedlines().GetIndex() ); + + if( bNewFrames ) + // delete all frames + pTableNd->DelFrames(nullptr); + if( &rNodes == this ) // move into self? + { + // move all Start/End/ContentNodes + // ContentNodes: delete also the frames! + pTableNd->m_pStartOfSection = aIdx.GetNode().m_pStartOfSection; + for( sal_uLong n = 0; n < nInsPos; ++n ) + { + SwNodeIndex aMvIdx( aRg.aEnd, 1 ); + SwContentNode* pCNd = nullptr; + SwNode* pTmpNd = &aMvIdx.GetNode(); + if( pTmpNd->IsContentNode() ) + { + pCNd = static_cast<SwContentNode*>(pTmpNd); + if( pTmpNd->IsTextNode() ) + static_cast<SwTextNode*>(pTmpNd)->RemoveFromList(); + + // remove outline index from old nodes array + if (pCNd->IsTextNode() && pCNd->GetTextNode()->IsOutline()) + { + m_pOutlineNodes->erase( pCNd ); + } + else + pCNd = nullptr; + } + + BigPtrArray::Move( aMvIdx.GetIndex(), aIdx.GetIndex() ); + + if( bInsOutlineIdx && pCNd ) + m_pOutlineNodes->insert( pCNd ); + if( pTmpNd->IsTextNode() ) + static_cast<SwTextNode*>(pTmpNd)->AddToList(); + } + } + else + { + // get StartNode + // Even aIdx points to a startnode, we need the startnode + // of the environment of aIdx (#i80941) + SwStartNode* pSttNode = aIdx.GetNode().m_pStartOfSection; + + // get all boxes with content because their indices + // pointing to the StartNodes need to be reset + // (copying the array and deleting all found ones eases + // searching) + SwNodeIndex aMvIdx( aRg.aEnd, 1 ); + for( sal_uLong n = 0; n < nInsPos; ++n ) + { + SwNode* pNd = &aMvIdx.GetNode(); + + const bool bOutlNd = pNd->IsTextNode() && pNd->GetTextNode()->IsOutline(); + // delete outline indices from old node array + if( bOutlNd ) + m_pOutlineNodes->erase( pNd ); + + RemoveNode( aMvIdx.GetIndex(), 1, false ); + pNd->m_pStartOfSection = pSttNode; + rNodes.InsertNode( pNd, aIdx ); + + // set correct indices in Start/EndNodes + if( bInsOutlineIdx && bOutlNd ) + // and put them into the new node array + rNodes.m_pOutlineNodes->insert( pNd ); + else if( pNd->IsStartNode() ) + pSttNode = static_cast<SwStartNode*>(pNd); + else if( pNd->IsEndNode() ) + { + pSttNode->m_pEndOfSection = static_cast<SwEndNode*>(pNd); + if( pSttNode->IsSectionNode() ) + static_cast<SwSectionNode*>(pSttNode)->NodesArrChgd(); + pSttNode = pSttNode->m_pStartOfSection; + } + } + + if( dynamic_cast<const SwDDETable*>(&pTableNd->GetTable()) != nullptr ) + { + SwDDEFieldType* pTyp = static_cast<SwDDETable&>(pTableNd-> + GetTable()).GetDDEFieldType(); + if( pTyp ) + { + if( rNodes.IsDocNodes() ) + pTyp->IncRefCnt(); + else + pTyp->DecRefCnt(); + } + } + + if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes( + rNodes)) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + pTableFormat->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying)); + } + } + if( bNewFrames ) + { + SwNodeIndex aTmp( aIdx ); + pTableNd->MakeOwnFrames(&aTmp); + } + aIdx -= nInsPos; + nInsPos = 0; + } + else if( pSttNd->GetIndex() < aRg.aStart.GetIndex() ) + { + // SectionNode: not the whole section will be moved, thus, + // move only the ContentNodes + // StartNode: create a new section at the given position + do { // middle check loop + if( !pSttNd->IsSectionNode() ) + { + // create StartNode and EndNode at InsertPos + SwStartNode* pTmp = new SwStartNode( aIdx, + SwNodeType::Start, +/*?? NodeType ??*/ SwNormalStartNode ); + + nLevel++; // put the index to StartNode on the stack + aSttNdStack.insert( aSttNdStack.begin() + nLevel, pTmp ); + + // create EndNode + new SwEndNode( aIdx, *pTmp ); + } + else if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes( + rNodes)) + { + // use placeholder in UndoNodes array + new SwPlaceholderNode(aIdx); + } + else + { + // JP 18.5.2001 (Bug 70454) creating new section? + --aRg.aEnd; + break; + + } + + --aRg.aEnd; + --aIdx; + } while( false ); + } + else + { + // move StartNode and EndNode in total + + // if Start is exactly the Start of the area, + // then the Node needs to be re-visited + if( &aRg.aStart.GetNode() == pSttNd ) + --aRg.aStart; + + SwSectionNode* pSctNd = pSttNd->GetSectionNode(); + if( bNewFrames && pSctNd ) + { // tdf#135056 skip over code in DelFrames() that moves + // SwNodeIndex around because in case of nested + // sections, m_pStartOfSection will point between + // undo nodes-array and doc nodes-array + pSctNd->DelFrames(nullptr, true); + } + + RemoveNode( aRg.aEnd.GetIndex(), 1, false ); // delete EndNode + sal_uLong nSttPos = pSttNd->GetIndex(); + + // this StartNode will be removed later + SwStartNode* pTmpSttNd = new SwStartNode( *this, nSttPos+1 ); + pTmpSttNd->m_pStartOfSection = pSttNd->m_pStartOfSection; + + RemoveNode( nSttPos, 1, false ); // delete SttNode + + pSttNd->m_pStartOfSection = aIdx.GetNode().m_pStartOfSection; + rNodes.InsertNode( pSttNd, aIdx ); + rNodes.InsertNode( pCurrentNode, aIdx ); + --aIdx; + pSttNd->m_pEndOfSection = static_cast<SwEndNode*>(pCurrentNode); + + --aRg.aEnd; + + nLevel++; // put the index pointing to the StartNode onto the stack + aSttNdStack.insert( aSttNdStack.begin() + nLevel, pSttNd ); + + // reset remaining indices if SectionNode + if( pSctNd ) + { + pSctNd->NodesArrChgd(); + ++nSectNdCnt; + // tdf#132326 do not let frames survive in undo nodes + if (!GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNodes)) + { + bNewFrames = false; + } + } + } + } + break; + + case SwNodeType::Section: + if( !nLevel && + GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(rNodes)) + { + // here, a SectionDummyNode needs to be inserted at the current position + if( nInsPos ) // move everything until here + { + // delete and copy. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos; + nInsPos = 0; + } + new SwPlaceholderNode(aIdx); + --aRg.aEnd; + --aIdx; + break; + } + [[fallthrough]]; + case SwNodeType::Table: + case SwNodeType::Start: + { + // empty section -> nothing to do + // and only if it's a top level section + if( !nInsPos && !nLevel ) + { + --aRg.aEnd; + break; + } + + if( !nLevel ) // level is decreasing + { + // create decrease + SwNodeIndex aTmpSIdx( aOrigInsPos.aStart, 1 ); + SwStartNode* pTmpStt = new SwStartNode( aTmpSIdx, + SwNodeType::Start, + static_cast<SwStartNode*>(pCurrentNode)->GetStartNodeType() ); + + --aTmpSIdx; + + SwNodeIndex aTmpEIdx( aOrigInsPos.aEnd ); + new SwEndNode( aTmpEIdx, *pTmpStt ); + --aTmpEIdx; + ++aTmpSIdx; + + // set correct StartOfSection + ++aRg.aEnd; + { + SwNodeIndex aCntIdx( aRg.aEnd ); + for( sal_uLong n = 0; n < nInsPos; n++, ++aCntIdx) + aCntIdx.GetNode().m_pStartOfSection = pTmpStt; + } + + // also set correct StartNode for all decreased nodes + while( aTmpSIdx < aTmpEIdx ) + if( nullptr != (( pCurrentNode = &aTmpEIdx.GetNode())->GetEndNode()) ) + aTmpEIdx = pCurrentNode->StartOfSectionIndex(); + else + { + pCurrentNode->m_pStartOfSection = pTmpStt; + --aTmpEIdx; + } + + --aIdx; // after the inserted StartNode + --aRg.aEnd; // before StartNode + // copy array. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos+1; + nInsPos = 0; + } + else // all nodes between StartNode and EndNode were moved + { + OSL_ENSURE( pCurrentNode == aSttNdStack[nLevel] || + ( pCurrentNode->IsStartNode() && + aSttNdStack[nLevel]->IsSectionNode()), + "wrong StartNode" ); + + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos+1; // before inserted StartNode + nInsPos = 0; + + // remove pointer from node array + RemoveNode( aRg.aEnd.GetIndex(), 1, true ); + --aRg.aEnd; + + SwSectionNode* pSectNd = aSttNdStack[ nLevel ]->GetSectionNode(); + if( pSectNd && !--nSectNdCnt ) + { + SwNodeIndex aTmp( *pSectNd ); + pSectNd->MakeOwnFrames(&aTmp); + bNewFrames = bSaveNewFrames; + } + aSttNdStack.erase( aSttNdStack.begin() + nLevel ); // remove from stack + nLevel--; + } + + // delete all resulting empty start/end node pairs + SwNode* pTmpNode = (*this)[ aRg.aEnd.GetIndex()+1 ]->GetEndNode(); + if( pTmpNode && SwNodeType::Start == (pCurrentNode = &aRg.aEnd.GetNode()) + ->GetNodeType() && pCurrentNode->StartOfSectionIndex() && + pTmpNode->StartOfSectionNode() == pCurrentNode ) + { + DelNodes( aRg.aEnd, 2 ); + --aRg.aEnd; + } + } + break; + + case SwNodeType::Text: + //Add special function to text node. + { + if( bNewFrames && pCurrentNode->GetContentNode() ) + static_cast<SwContentNode*>(pCurrentNode)->DelFrames(nullptr); + pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; + nInsPos++; + --aRg.aEnd; + } + break; + case SwNodeType::Grf: + case SwNodeType::Ole: + { + if( bNewFrames && pCurrentNode->GetContentNode() ) + static_cast<SwContentNode*>(pCurrentNode)->DelFrames(nullptr); + + pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; + nInsPos++; + --aRg.aEnd; + } + break; + + case SwNodeType::PlaceHolder: + if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(*this)) + { + if( &rNodes == this ) // inside UndoNodesArray + { + // move everything + pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; + nInsPos++; + } + else // move into "normal" node array + { + // than a SectionNode (start/end) is needed at the current + // InsPos; if so skip it, otherwise ignore current node + if( nInsPos ) // move everything until here + { + // delete and copy. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos; + nInsPos = 0; + } + SwNode* pTmpNd = &aIdx.GetNode(); + if( pTmpNd->IsSectionNode() || + pTmpNd->StartOfSectionNode()->IsSectionNode() ) + --aIdx; // skip + } + } + else { + assert(!"How can this node be in the node array?"); + } + --aRg.aEnd; + break; + + default: + assert(!"Unknown node type"); + break; + } + } + + if( nInsPos ) // copy remaining rest + { + // rest should be ok + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + } + ++aRg.aEnd; // again, exclusive end + + // delete all resulting empty start/end node pairs + if( ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && + pCurrentNode->StartOfSectionIndex() && + aRg.aEnd.GetNode().GetEndNode() ) + DelNodes( aRg.aStart, 2 ); + + // initialize numbering update + ++aOrigInsPos.aStart; + // Moved in same node array? Then call update top down! + if( this == &rNodes && + aRg.aEnd.GetIndex() >= aOrigInsPos.aStart.GetIndex() ) + { + UpdateOutlineIdx( aOrigInsPos.aStart.GetNode() ); + UpdateOutlineIdx( aRg.aEnd.GetNode() ); + } + else + { + UpdateOutlineIdx( aRg.aEnd.GetNode() ); + rNodes.UpdateOutlineIdx( aOrigInsPos.aStart.GetNode() ); + } + + return true; +} + +/** create a start/end section pair + * + * Other nodes might be in between. + * + * After this method call, the start node of pRange will be pointing to the + * first node after the start section node and the end node will be the index + * of the end section node. If this method is called multiple times with the + * same input, multiple sections containing the previous ones will be created + * (no content nodes between start or end node). + * + * @note Start and end node of the range must be on the same level but MUST + * NOT be on the top level. + * + * @param [IN,OUT] pRange the range (excl. end) + * @param eSttNdTyp type of the start node + */ +void SwNodes::SectionDown(SwNodeRange *pRange, SwStartNodeType eSttNdTyp ) +{ + if( pRange->aStart >= pRange->aEnd || + pRange->aEnd >= Count() || + !::CheckNodesRange(pRange->aStart, pRange->aEnd, false)) + { + return; + } + + // If the beginning of a range is before or at a start node position, so + // delete it, otherwise empty S/E or E/S nodes would be created. + // For other nodes, create a new start node. + SwNode * pCurrentNode = &pRange->aStart.GetNode(); + SwNodeIndex aTmpIdx( *pCurrentNode->StartOfSectionNode() ); + + if( pCurrentNode->GetEndNode() ) + DelNodes( pRange->aStart ); // prevent empty section + else + { + // insert a new StartNode + SwNode* pSttNd = new SwStartNode( pRange->aStart, SwNodeType::Start, eSttNdTyp ); + pRange->aStart = *pSttNd; + aTmpIdx = pRange->aStart; + } + + // If the end of a range is before or at a StartNode, so delete it, + // otherwise empty S/E or E/S nodes would be created. + // For other nodes, insert a new end node. + --pRange->aEnd; + if( pRange->aEnd.GetNode().GetStartNode() ) + DelNodes( pRange->aEnd ); + else + { + ++pRange->aEnd; + // insert a new EndNode + new SwEndNode( pRange->aEnd, *pRange->aStart.GetNode().GetStartNode() ); + } + --pRange->aEnd; + + SectionUpDown( aTmpIdx, pRange->aEnd ); +} + +/** increase level of the given range + * + * The range contained in pRange will be lifted to the next higher level. + * This is done by adding an end node at pRange.start and a start node at + * pRange.end. Furthermore all indices for this range will be updated. + * + * After this method call, the start node of pRange will be pointing to the + * first node inside the lifted range and the end node will be pointing to the + * last position inside the lifted range. + * + * @param [IN,OUT] pRange the range of nodes where the level should be increased + */ +void SwNodes::SectionUp(SwNodeRange *pRange) +{ + if( pRange->aStart >= pRange->aEnd || + pRange->aEnd >= Count() || + !::CheckNodesRange(pRange->aStart, pRange->aEnd, false) || + ( HighestLevel( *this, *pRange ) <= 1 )) + { + return; + } + + // If the beginning of a range is before or at a start node position, so + // delete it, otherwise empty S/E or E/S nodes would be created. + // For other nodes, create a new start node. + SwNode * pCurrentNode = &pRange->aStart.GetNode(); + SwNodeIndex aIdx( *pCurrentNode->StartOfSectionNode() ); + if( pCurrentNode->IsStartNode() ) // is StartNode itself + { + SwEndNode* pEndNd = pRange->aEnd.GetNode().GetEndNode(); + if (pEndNd && pCurrentNode == pEndNd->m_pStartOfSection) + { + // there was a pairwise reset, adjust only those in the range + SwStartNode* pTmpSttNd = pCurrentNode->m_pStartOfSection; + RemoveNode( pRange->aStart.GetIndex(), 1, true ); + RemoveNode( pRange->aEnd.GetIndex(), 1, true ); + + SwNodeIndex aTmpIdx( pRange->aStart ); + while( aTmpIdx < pRange->aEnd ) + { + pCurrentNode = &aTmpIdx.GetNode(); + pCurrentNode->m_pStartOfSection = pTmpSttNd; + if( pCurrentNode->IsStartNode() ) + aTmpIdx = pCurrentNode->EndOfSectionIndex() + 1; + else + ++aTmpIdx; + } + return ; + } + DelNodes( pRange->aStart ); + } + else if( aIdx == pRange->aStart.GetIndex()-1 ) // before StartNode + DelNodes( aIdx ); + else + new SwEndNode( pRange->aStart, *aIdx.GetNode().GetStartNode() ); + + // If the end of a range is before or at a StartNode, so delete it, + // otherwise empty S/E or E/S nodes would be created. + // For other nodes, insert a new end node. + SwNodeIndex aTmpIdx( pRange->aEnd ); + if( pRange->aEnd.GetNode().IsEndNode() ) + DelNodes( pRange->aEnd ); + else + { + new SwStartNode( pRange->aEnd ); +/*?? which NodeType ??*/ + aTmpIdx = *pRange->aEnd.GetNode().EndOfSectionNode(); + --pRange->aEnd; + } + + SectionUpDown( aIdx, aTmpIdx ); +} + +/** correct indices after movement + * + * Update all indices after movement so that the levels are consistent again. + * + * @param aStart index of the start node + * @param aEnd index of the end point + * + * @see SwNodes::SectionUp + * @see SwNodes::SectionDown + */ +void SwNodes::SectionUpDown( const SwNodeIndex & aStart, const SwNodeIndex & aEnd ) +{ + SwNodeIndex aTmpIdx( aStart, +1 ); + // array forms a stack, holding all StartOfSelections + SwStartNodePointers aSttNdStack; + SwStartNode* pTmp = aStart.GetNode().GetStartNode(); + aSttNdStack.push_back( pTmp ); + + // loop until the first start node that needs to be change was found + // (the indices are updated from the end node backwards to the start) + for( ;; ++aTmpIdx ) + { + SwNode * pCurrentNode = &aTmpIdx.GetNode(); + pCurrentNode->m_pStartOfSection = aSttNdStack[ aSttNdStack.size()-1 ]; + + if( pCurrentNode->GetStartNode() ) + { + pTmp = static_cast<SwStartNode*>(pCurrentNode); + aSttNdStack.push_back( pTmp ); + } + else if( pCurrentNode->GetEndNode() ) + { + SwStartNode* pSttNd = aSttNdStack[ aSttNdStack.size() - 1 ]; + pSttNd->m_pEndOfSection = static_cast<SwEndNode*>(pCurrentNode); + aSttNdStack.pop_back(); + if( !aSttNdStack.empty() ) + continue; // still enough EndNodes on the stack + + else if( aTmpIdx < aEnd ) // too many StartNodes + // if the end is not reached, yet, get the start of the section above + { + aSttNdStack.insert( aSttNdStack.begin(), pSttNd->m_pStartOfSection ); + } + else // finished, as soon as out of the range + break; + } + } +} + +/** delete nodes + * + * This is a specific implementation of a delete function for a variable array. + * It is necessary as there might be inconsistencies after deleting start or + * end nodes. This method can clean those up. + * + * @param rIndex position to delete at (unchanged afterwards) + * @param nNodes number of nodes to delete (default: 1) + */ +void SwNodes::Delete(const SwNodeIndex &rIndex, sal_uLong nNodes) +{ + int nLevel = 0; // level counter + SwNode * pCurrentNode; + + sal_uLong nCnt = Count() - rIndex.GetIndex() - 1; + if( nCnt > nNodes ) nCnt = nNodes; + + if( nCnt == 0 ) // no count -> return + return; + + SwNodeRange aRg( rIndex, 0, rIndex, nCnt-1 ); + // check if [rIndex..rIndex + nCnt] is larger than the range + if( ( !aRg.aStart.GetNode().StartOfSectionIndex() && + !aRg.aStart.GetIndex() ) || + !::CheckNodesRange(aRg.aStart, aRg.aEnd, false)) + { + return; + } + + // if aEnd is not on a ContentNode, search the previous one + while( ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() || + ( pCurrentNode->GetEndNode() && + !pCurrentNode->m_pStartOfSection->IsTableNode() )) + --aRg.aEnd; + + nCnt = 0; +//TODO: check/improve comment + // increase start so that we are able to use "<" (using "<=" might cause + // problems if aEnd == aStart and aEnd is deleted, so aEnd <= aStart) + --aRg.aStart; + + bool bSaveInNodesDel = m_bInNodesDel; + m_bInNodesDel = true; + bool bUpdateOutline = false; + + // loop until everything is deleted + while( aRg.aStart < aRg.aEnd ) + { + pCurrentNode = &aRg.aEnd.GetNode(); + + if( pCurrentNode->GetEndNode() ) + { + // delete the whole section? + if( pCurrentNode->StartOfSectionIndex() > aRg.aStart.GetIndex() ) + { + SwTableNode* pTableNd = pCurrentNode->m_pStartOfSection->GetTableNode(); + if( pTableNd ) + pTableNd->DelFrames(); + + SwNode *pNd, *pChkNd = pCurrentNode->m_pStartOfSection; + SwOutlineNodes::size_type nIdxPos; + do { + pNd = &aRg.aEnd.GetNode(); + + if( pNd->IsTextNode() ) + { + SwTextNode *const pTextNode(pNd->GetTextNode()); + if (pTextNode->IsOutline() && + m_pOutlineNodes->Seek_Entry( pNd, &nIdxPos )) + { + // remove outline indices + m_pOutlineNodes->erase(nIdxPos); + bUpdateOutline = true; + } + pTextNode->InvalidateNumRule(); + } + else if( pNd->IsEndNode() && + pNd->m_pStartOfSection->IsTableNode() ) + static_cast<SwTableNode*>(pNd->m_pStartOfSection)->DelFrames(); + + --aRg.aEnd; + nCnt++; + + } while( pNd != pChkNd ); + } + else + { + RemoveNode( aRg.aEnd.GetIndex()+1, nCnt, true ); // delete + nCnt = 0; + --aRg.aEnd; // before the EndNode + nLevel++; + } + } + else if( pCurrentNode->GetStartNode() ) // found StartNode + { + if( nLevel == 0 ) // decrease one level + { + if( nCnt ) + { + // now delete array + ++aRg.aEnd; + RemoveNode( aRg.aEnd.GetIndex(), nCnt, true ); + nCnt = 0; + } + } + else // remove all nodes between start and end node (incl. both) + { + RemoveNode( aRg.aEnd.GetIndex(), nCnt + 2, true ); // delete array + nCnt = 0; + nLevel--; + } + + // after deletion, aEnd might point to an EndNode... + // delete all empty start/end node pairs + SwNode* pTmpNode = aRg.aEnd.GetNode().GetEndNode(); + --aRg.aEnd; + while( pTmpNode && + ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && + pCurrentNode->StartOfSectionIndex() ) + { + // remove end and start node + DelNodes( aRg.aEnd, 2 ); + pTmpNode = aRg.aEnd.GetNode().GetEndNode(); + --aRg.aEnd; + } + } + else // "normal" node, so insert into TmpArray + { + SwTextNode* pTextNd = pCurrentNode->GetTextNode(); + if( pTextNd ) + { + if( pTextNd->IsOutline()) + { + // delete outline indices + m_pOutlineNodes->erase( pTextNd ); + bUpdateOutline = true; + } + pTextNd->InvalidateNumRule(); + } + else if( pCurrentNode->IsContentNode() ) + static_cast<SwContentNode*>(pCurrentNode)->InvalidateNumRule(); + + --aRg.aEnd; + nCnt++; + } + } + + ++aRg.aEnd; + if( nCnt != 0 ) + RemoveNode( aRg.aEnd.GetIndex(), nCnt, true ); // delete the rest + + // delete all empty start/end node pairs + while( aRg.aEnd.GetNode().GetEndNode() && + ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && + pCurrentNode->StartOfSectionIndex() ) + // but none of the holy 5. (???) + { + DelNodes( aRg.aStart, 2 ); // delete start and end node + --aRg.aStart; + } + + m_bInNodesDel = bSaveInNodesDel; + + if( !m_bInNodesDel ) + { + // update numbering + if( bUpdateOutline || m_bInDelUpdOutline ) + { + UpdateOutlineIdx( aRg.aEnd.GetNode() ); + m_bInDelUpdOutline = false; + } + + } + else + { + if( bUpdateOutline ) + m_bInDelUpdOutline = true; + } +} + +/** get section level at the given position + * + * @note The first node in an array should always be a start node. + * Because of this, there is a special treatment here based on the + * assumption that this is true in this context as well. + * + * @param rIdx position of the node + * @return section level at the given position + */ +sal_uInt16 SwNodes::GetSectionLevel(const SwNodeIndex &rIdx) +{ + // special treatment for 1st Node + if(rIdx == 0) return 1; + // no recursion! This calls a SwNode::GetSectionLevel (missing "s") + return rIdx.GetNode().GetSectionLevel(); +} + +void SwNodes::GoStartOfSection(SwNodeIndex *pIdx) +{ + // after the next start node + SwNodeIndex aTmp( *pIdx->GetNode().StartOfSectionNode(), +1 ); + + // If index points to no ContentNode, then go to one. + // If there is no further available, do not change the index' position! + while( !aTmp.GetNode().IsContentNode() ) + { // go from this StartNode (can only be one) to its end + if( *pIdx <= aTmp ) + return; // ERROR: already after the section + aTmp = aTmp.GetNode().EndOfSectionIndex()+1; + if( *pIdx <= aTmp ) + return; // ERROR: already after the section + } + (*pIdx) = aTmp; // is on a ContentNode +} + +void SwNodes::GoEndOfSection(SwNodeIndex *pIdx) +{ + if( !pIdx->GetNode().IsEndNode() ) + (*pIdx) = *pIdx->GetNode().EndOfSectionNode(); +} + +SwContentNode* SwNodes::GoNext(SwNodeIndex *pIdx) const +{ + if( pIdx->GetIndex() >= Count() - 1 ) + return nullptr; + + SwNodeIndex aTmp(*pIdx, +1); + SwNode* pNd = nullptr; + while( aTmp < Count()-1 && !( pNd = &aTmp.GetNode())->IsContentNode() ) + ++aTmp; + + if( aTmp == Count()-1 ) + pNd = nullptr; + else + (*pIdx) = aTmp; + return static_cast<SwContentNode*>(pNd); +} + +SwContentNode* SwNodes::GoPrevious(SwNodeIndex *pIdx) +{ + if( !pIdx->GetIndex() ) + return nullptr; + + SwNodeIndex aTmp( *pIdx, -1 ); + SwNode* pNd = nullptr; + while( aTmp.GetIndex() && !( pNd = &aTmp.GetNode())->IsContentNode() ) + --aTmp; + + if( !aTmp.GetIndex() ) + pNd = nullptr; + else + (*pIdx) = aTmp; + return static_cast<SwContentNode*>(pNd); +} + +/** Delete a number of nodes + * + * @param rStart starting position in this nodes array + * @param nCnt number of nodes to delete + */ +void SwNodes::DelNodes( const SwNodeIndex & rStart, sal_uLong nCnt ) +{ + sal_uLong nSttIdx = rStart.GetIndex(); + + if( !nSttIdx && nCnt == GetEndOfContent().GetIndex()+1 ) + { + // The whole nodes array will be destroyed, you're in the Doc's DTOR! + // The initial start/end nodes should be only destroyed in the SwNodes' DTOR! + SwNode* aEndNdArr[] = { m_pEndOfContent.get(), + m_pEndOfPostIts, m_pEndOfInserts, + m_pEndOfAutotext, m_pEndOfRedlines, + nullptr + }; + + SwNode** ppEndNdArr = aEndNdArr; + while( *ppEndNdArr ) + { + nSttIdx = (*ppEndNdArr)->StartOfSectionIndex() + 1; + sal_uLong nEndIdx = (*ppEndNdArr)->GetIndex(); + + if( nSttIdx != nEndIdx ) + RemoveNode( nSttIdx, nEndIdx - nSttIdx, true ); + + ++ppEndNdArr; + } + } + else + { + int bUpdateNum = 0; + for( sal_uLong n = nSttIdx, nEnd = nSttIdx + nCnt; n < nEnd; ++n ) + { + SwNode* pNd = (*this)[ n ]; + + if (pNd->IsTextNode() && pNd->GetTextNode()->IsOutline()) + { + // remove the outline indices + SwOutlineNodes::size_type nIdxPos; + if( m_pOutlineNodes->Seek_Entry( pNd, &nIdxPos )) + { + m_pOutlineNodes->erase(nIdxPos); + bUpdateNum = 1; + } + } + if( pNd->IsContentNode() ) + { + static_cast<SwContentNode*>(pNd)->InvalidateNumRule(); + static_cast<SwContentNode*>(pNd)->DelFrames(nullptr); + } + } + RemoveNode( nSttIdx, nCnt, true ); + + // update numbering + if( bUpdateNum ) + UpdateOutlineIdx( rStart.GetNode() ); + } +} + +namespace { + +struct HighLevel +{ + sal_uInt16 nLevel, nTop; + explicit HighLevel( sal_uInt16 nLv ) : nLevel( nLv ), nTop( nLv ) {} +}; + +} + +static bool lcl_HighestLevel( const SwNodePtr& rpNode, void * pPara ) +{ + HighLevel * pHL = static_cast<HighLevel*>(pPara); + if( rpNode->GetStartNode() ) + pHL->nLevel++; + else if( rpNode->GetEndNode() ) + pHL->nLevel--; + if( pHL->nTop > pHL->nLevel ) + pHL->nTop = pHL->nLevel; + return true; + +} + +/** Calculate the highest level in a range + * + * @param rNodes the nodes array + * @param rRange the range to inspect + * @return the highest level + */ +sal_uInt16 HighestLevel( SwNodes & rNodes, const SwNodeRange & rRange ) +{ + HighLevel aPara( SwNodes::GetSectionLevel( rRange.aStart )); + rNodes.ForEach( rRange.aStart, rRange.aEnd, lcl_HighestLevel, &aPara ); + return aPara.nTop; + +} + +/** move a range + * + * @param rPam the range to move + * @param rPos to destination position in the given nodes array + * @param rNodes the node array to move the range into + */ +void SwNodes::MoveRange( SwPaM & rPam, SwPosition & rPos, SwNodes& rNodes ) +{ + SwPosition * const pStt = rPam.Start(); + SwPosition * const pEnd = rPam.End(); + + if( !rPam.HasMark() || *pStt >= *pEnd ) + return; + + if( this == &rNodes && *pStt <= rPos && rPos < *pEnd ) + return; + + SwNodeIndex aEndIdx( pEnd->nNode ); + SwNodeIndex aSttIdx( pStt->nNode ); + SwTextNode *const pSrcNd = aSttIdx.GetNode().GetTextNode(); + SwTextNode * pDestNd = rPos.nNode.GetNode().GetTextNode(); + bool bSplitDestNd = true; + bool bCopyCollFormat = pDestNd && pDestNd->GetText().isEmpty(); + + if( pSrcNd ) + { + // if the first node is a TextNode, then there must + // be also a TextNode in the NodesArray to store the content + if( !pDestNd ) + { + pDestNd = rNodes.MakeTextNode( rPos.nNode, pSrcNd->GetTextColl() ); + --rPos.nNode; + rPos.nContent.Assign( pDestNd, 0 ); + bCopyCollFormat = true; + } + bSplitDestNd = pDestNd->Len() > rPos.nContent.GetIndex() || + pEnd->nNode.GetNode().IsTextNode(); + + // move the content into the new node + bool bOneNd = pStt->nNode == pEnd->nNode; + const sal_Int32 nLen = + ( bOneNd ? std::min(pEnd->nContent.GetIndex(), pSrcNd->Len()) : pSrcNd->Len() ) + - pStt->nContent.GetIndex(); + + if( !pEnd->nNode.GetNode().IsContentNode() ) + { + bOneNd = true; + sal_uLong nSttNdIdx = pStt->nNode.GetIndex() + 1; + const sal_uLong nEndNdIdx = pEnd->nNode.GetIndex(); + for( ; nSttNdIdx < nEndNdIdx; ++nSttNdIdx ) + { + if( (*this)[ nSttNdIdx ]->IsContentNode() ) + { + bOneNd = false; + break; + } + } + } + + // templates must be copied/set after a split + if( !bOneNd && bSplitDestNd ) + { + if( !rPos.nContent.GetIndex() ) + { + bCopyCollFormat = true; + } + if( rNodes.IsDocNodes() ) + { + SwDoc* const pInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const ug(pInsDoc->GetIDocumentUndoRedo()); + pInsDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + else + { + pDestNd->SplitContentNode(rPos, nullptr); + } + + if( rPos.nNode == aEndIdx ) + { + --aEndIdx; + } + bSplitDestNd = true; + + pDestNd = rNodes[ rPos.nNode.GetIndex() - 1 ]->GetTextNode(); + if( nLen ) + { + pSrcNd->CutText( pDestNd, SwIndex( pDestNd, pDestNd->Len()), + pStt->nContent, nLen ); + } + } + else if ( nLen ) + { + pSrcNd->CutText( pDestNd, rPos.nContent, pStt->nContent, nLen ); + } + + if( bCopyCollFormat ) + { + SwDoc* const pInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const undoGuard(pInsDoc->GetIDocumentUndoRedo()); + pSrcNd->CopyCollFormat( *pDestNd ); + bCopyCollFormat = false; + } + + if( bOneNd ) + { + // Correct the PaM, because it might have happened that the move + // went over the node borders (so the data might be in different nodes). + // Also, a selection is invalidated. + pEnd->nContent = pStt->nContent; + rPam.DeleteMark(); + GetDoc()->GetDocShell()->Broadcast( SwFormatFieldHint( nullptr, + rNodes.IsDocNodes() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ); + return; + } + + ++aSttIdx; + } + else if( pDestNd ) + { + if( rPos.nContent.GetIndex() ) + { + if( rPos.nContent.GetIndex() == pDestNd->Len() ) + { + ++rPos.nNode; + } + else if( rPos.nContent.GetIndex() ) + { + // if the EndNode is split than correct the EndIdx + const bool bCorrEnd = aEndIdx == rPos.nNode; + + // if no text is attached to the TextNode, split it + if( rNodes.IsDocNodes() ) + { + SwDoc* const pInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const ug(pInsDoc->GetIDocumentUndoRedo()); + pInsDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + else + { + pDestNd->SplitContentNode(rPos, nullptr); + } + + if ( bCorrEnd ) + { + --aEndIdx; + } + } + } + // at the end only an empty TextNode is left over + bSplitDestNd = true; + } + + SwTextNode* const pEndSrcNd = aEndIdx.GetNode().GetTextNode(); + if ( pEndSrcNd ) + { + // at the end of this range a new TextNode will be created + if( !bSplitDestNd ) + { + if( rPos.nNode < rNodes.GetEndOfContent().GetIndex() ) + { + ++rPos.nNode; + } + + pDestNd = + rNodes.MakeTextNode( rPos.nNode, pEndSrcNd->GetTextColl() ); + --rPos.nNode; + rPos.nContent.Assign( pDestNd, 0 ); + } + else + { + pDestNd = rPos.nNode.GetNode().GetTextNode(); + } + + if (pDestNd && pEnd->nContent.GetIndex()) + { + // move the content into the new node + SwIndex aIdx( pEndSrcNd, 0 ); + pEndSrcNd->CutText( pDestNd, rPos.nContent, aIdx, + pEnd->nContent.GetIndex()); + } + + if (pDestNd && bCopyCollFormat) + { + SwDoc* const pInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const ug(pInsDoc->GetIDocumentUndoRedo()); + pEndSrcNd->CopyCollFormat( *pDestNd ); + } + } + else + { + if ( pSrcNd && aEndIdx.GetNode().IsContentNode() ) + { + ++aEndIdx; + } + if( !bSplitDestNd ) + { + ++rPos.nNode; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), 0 ); + } + } + + if( aEndIdx != aSttIdx ) + { + // move the nodes into the NodesArary + const sal_uLong nSttDiff = aSttIdx.GetIndex() - pStt->nNode.GetIndex(); + SwNodeRange aRg( aSttIdx, aEndIdx ); + MoveNodes( aRg, rNodes, rPos.nNode ); + + // if in the same node array, all indices are now at new positions (so correct them) + if( &rNodes == this ) + { + pStt->nNode = aRg.aEnd.GetIndex() - nSttDiff; + } + } + + // if the StartNode was moved to whom the cursor pointed, so + // the content must be registered in the current content! + if ( &pStt->nNode.GetNode() == &GetEndOfContent() ) + { + const bool bSuccess = GoPrevious( &pStt->nNode ); + OSL_ENSURE( bSuccess, "Move() - no ContentNode here" ); + } + pStt->nContent.Assign( pStt->nNode.GetNode().GetContentNode(), + pStt->nContent.GetIndex() ); + // Correct the PaM, because it might have happened that the move + // went over the node borders (so the data might be in different nodes). + // Also, a selection is invalidated. + *pEnd = *pStt; + rPam.DeleteMark(); + GetDoc()->GetDocShell()->Broadcast( SwFormatFieldHint( nullptr, + rNodes.IsDocNodes() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ); +} + +///@see SwNodes::MoveNodes (TODO: seems to be C&P programming here) +void SwNodes::CopyNodes( const SwNodeRange& rRange, + const SwNodeIndex& rIndex, bool bNewFrames, bool bTableInsDummyNode ) const +{ + SwDoc* pDoc = rIndex.GetNode().GetDoc(); + + SwNode * pCurrentNode; + if( rIndex == 0 || + ( (pCurrentNode = &rIndex.GetNode())->GetStartNode() && + !pCurrentNode->StartOfSectionIndex() )) + return; + + SwNodeRange aRg( rRange ); + + // skip "simple" StartNodes or EndNodes + while( SwNodeType::Start == (pCurrentNode = & aRg.aStart.GetNode())->GetNodeType() + || ( pCurrentNode->IsEndNode() && + !pCurrentNode->m_pStartOfSection->IsSectionNode() ) ) + ++aRg.aStart; + + const SwNode *aEndNode = &aRg.aEnd.GetNode(); + int nIsEndOfContent = (aEndNode == &aEndNode->GetNodes().GetEndOfContent()) ? 1 : 0; + + if (0 == nIsEndOfContent) + { + // if aEnd-1 points to no ContentNode, search previous one + --aRg.aEnd; + // #i107142#: if aEnd is start node of a special section, do nothing. + // Otherwise this could lead to crash: going through all previous + // special section nodes and then one before the first. + if (aRg.aEnd.GetNode().StartOfSectionIndex() != 0) + { + while( ((pCurrentNode = & aRg.aEnd.GetNode())->GetStartNode() && + !pCurrentNode->IsSectionNode() ) || + ( pCurrentNode->IsEndNode() && + SwNodeType::Start == pCurrentNode->m_pStartOfSection->GetNodeType()) ) + { + --aRg.aEnd; + } + } + ++aRg.aEnd; + } + + // is there anything left to copy? + if( aRg.aStart >= aRg.aEnd ) + return; + + // when inserting into the source range, nothing need to be done + OSL_ENSURE( &aRg.aStart.GetNodes() == this, + "aRg should use this node array" ); + OSL_ENSURE( &aRg.aStart.GetNodes() == &aRg.aEnd.GetNodes(), + "Range across different nodes arrays? You deserve punishment!"); + if( &rIndex.GetNodes() == &aRg.aStart.GetNodes() && + rIndex.GetIndex() >= aRg.aStart.GetIndex() && + rIndex.GetIndex() < aRg.aEnd.GetIndex() ) + return; + + SwNodeIndex aInsPos( rIndex ); + SwNodeIndex aOrigInsPos( rIndex, -1 ); // original insertion position + int nLevel = 0; // level counter + + for( long nNodeCnt = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); + nNodeCnt > 0; --nNodeCnt ) + { + pCurrentNode = &aRg.aStart.GetNode(); + switch( pCurrentNode->GetNodeType() ) + { + case SwNodeType::Table: + // Does it copy a table in(to) a footnote? + if( aInsPos < pDoc->GetNodes().GetEndOfInserts().GetIndex() && + pDoc->GetNodes().GetEndOfInserts().StartOfSectionIndex() + < aInsPos.GetIndex() ) + { + const long nDistance = + pCurrentNode->EndOfSectionIndex() - + aRg.aStart.GetIndex(); + if (nDistance < nNodeCnt) + nNodeCnt -= nDistance; + else + nNodeCnt = 1; + + // insert a DummyNode for a TableNode + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos); + + // copy all of the table's nodes into the current cell + for( ++aRg.aStart; aRg.aStart.GetIndex() < + pCurrentNode->EndOfSectionIndex(); + ++aRg.aStart ) + { + // insert a DummyNode for the box-StartNode? + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos); + + SwStartNode* pSttNd = aRg.aStart.GetNode().GetStartNode(); + CopyNodes( SwNodeRange( *pSttNd, + 1, + *pSttNd->EndOfSectionNode() ), + aInsPos, bNewFrames ); + + // insert a DummyNode for the box-EndNode? + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos); + aRg.aStart = *pSttNd->EndOfSectionNode(); + } + // insert a DummyNode for the table-EndNode + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos); + aRg.aStart = *pCurrentNode->EndOfSectionNode(); + } + else + { + SwNodeIndex nStt( aInsPos, -1 ); + SwTableNode* pTableNd = static_cast<SwTableNode*>(pCurrentNode)-> + MakeCopy( pDoc, aInsPos ); + const long nDistance = aInsPos.GetIndex() - nStt.GetIndex() - 2; + if (nDistance < nNodeCnt) + nNodeCnt -= nDistance; + else + nNodeCnt = 1 - nIsEndOfContent; + + aRg.aStart = pCurrentNode->EndOfSectionIndex(); + + if( bNewFrames && pTableNd ) + { + nStt = aInsPos; + pTableNd->MakeOwnFrames(&nStt); + } + } + break; + + case SwNodeType::Section: + // If the end of the section is outside the copy range, + // the section node will skipped, not copied! + // If someone want to change this behaviour, he has to adjust the function + // lcl_NonCopyCount(..) in ndcopy.cxx which relies on it. + if( pCurrentNode->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) + { + // copy of the whole section, so create a new SectionNode + SwNodeIndex nStt( aInsPos, -1 ); + SwSectionNode* pSectNd = static_cast<SwSectionNode*>(pCurrentNode)-> + MakeCopy( pDoc, aInsPos ); + + const long nDistance = aInsPos.GetIndex() - nStt.GetIndex() - 2; + if (nDistance < nNodeCnt) + nNodeCnt -= nDistance; + else + nNodeCnt = 1 - nIsEndOfContent; + aRg.aStart = pCurrentNode->EndOfSectionIndex(); + + if( bNewFrames && pSectNd && + !pSectNd->GetSection().IsHidden() ) + pSectNd->MakeOwnFrames(&nStt); + } + break; + + case SwNodeType::Start: + { + SwStartNode* pTmp = new SwStartNode( aInsPos, SwNodeType::Start, + static_cast<SwStartNode*>(pCurrentNode)->GetStartNodeType() ); + new SwEndNode( aInsPos, *pTmp ); + --aInsPos; + nLevel++; + } + break; + + case SwNodeType::End: + if( nLevel ) // complete section + { + --nLevel; + ++aInsPos; // EndNode already exists + } + else if( 1 == nNodeCnt && 1 == nIsEndOfContent ) + // we have reached the EndOfContent node - nothing to do! + continue; + else if( !pCurrentNode->m_pStartOfSection->IsSectionNode() ) + { + // create a section at the original InsertPosition + SwNodeRange aTmpRg( aOrigInsPos, 1, aInsPos ); + pDoc->GetNodes().SectionDown( &aTmpRg, + pCurrentNode->m_pStartOfSection->GetStartNodeType() ); + } + break; + + case SwNodeType::Text: + case SwNodeType::Grf: + case SwNodeType::Ole: + { + static_cast<SwContentNode*>(pCurrentNode)->MakeCopy( + pDoc, aInsPos, bNewFrames); + } + break; + + case SwNodeType::PlaceHolder: + if (GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(*this)) + { + // than a SectionNode (start/end) is needed at the current + // InsPos; if so skip it, otherwise ignore current node + SwNode *const pTmpNd = & aInsPos.GetNode(); + if( pTmpNd->IsSectionNode() || + pTmpNd->StartOfSectionNode()->IsSectionNode() ) + ++aInsPos; // skip + } + else { + assert(!"How can this node be in the node array?"); + } + break; + + default: + assert(false); + } + ++aRg.aStart; + } +} + +void SwNodes::DelDummyNodes( const SwNodeRange& rRg ) +{ + SwNodeIndex aIdx( rRg.aStart ); + while( aIdx.GetIndex() < rRg.aEnd.GetIndex() ) + { + if (SwNodeType::PlaceHolder == aIdx.GetNode().GetNodeType()) + RemoveNode( aIdx.GetIndex(), 1, true ); + else + ++aIdx; + } +} + +SwStartNode* SwNodes::MakeEmptySection( const SwNodeIndex& rIdx, + SwStartNodeType eSttNdTyp ) +{ + SwStartNode* pSttNd = new SwStartNode( rIdx, SwNodeType::Start, eSttNdTyp ); + new SwEndNode( rIdx, *pSttNd ); + return pSttNd; +} + +SwStartNode* SwNodes::MakeTextSection( const SwNodeIndex & rWhere, + SwStartNodeType eSttNdTyp, + SwTextFormatColl *pColl ) +{ + SwStartNode* pSttNd = new SwStartNode( rWhere, SwNodeType::Start, eSttNdTyp ); + new SwEndNode( rWhere, *pSttNd ); + MakeTextNode( SwNodeIndex( rWhere, - 1 ), pColl ); + return pSttNd; +} + +//TODO: provide better documentation +/** go to next section that is not protected nor hidden + * + * @note if !bSkipHidden and !bSkipProtect, use GoNext/GoPrevious + * + * @param pIdx + * @param bSkipHidden + * @param bSkipProtect + * @return + * @see SwNodes::GoNext + * @see SwNodes::GoPrevious + * @see SwNodes::GoNextSection (TODO: seems to be C&P programming here) +*/ +SwContentNode* SwNodes::GoNextSection( SwNodeIndex * pIdx, + bool bSkipHidden, bool bSkipProtect ) const +{ + bool bFirst = true; + SwNodeIndex aTmp( *pIdx ); + const SwNode* pNd; + while( aTmp < Count() - 1 ) + { + pNd = & aTmp.GetNode(); + if (SwNodeType::Section == pNd->GetNodeType()) + { + const SwSection& rSect = static_cast<const SwSectionNode*>(pNd)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip the section + aTmp = *pNd->EndOfSectionNode(); + } + else if( bFirst ) + { + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip the section + aTmp = *pNd->EndOfSectionNode(); + } + } + else if( SwNodeType::ContentMask & pNd->GetNodeType() ) + { + const SwSectionNode* pSectNd; + if( ( bSkipHidden || bSkipProtect ) && + nullptr != (pSectNd = pNd->FindSectionNode() ) && + ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || + ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) + { + aTmp = *pSectNd->EndOfSectionNode(); + } + else + { + (*pIdx) = aTmp; + return const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)); + } + } + ++aTmp; + bFirst = false; + } + return nullptr; +} + +///@see SwNodes::GoNextSection (TODO: seems to be C&P programming here) +SwContentNode* SwNodes::GoPrevSection( SwNodeIndex * pIdx, + bool bSkipHidden, bool bSkipProtect ) +{ + bool bFirst = true; + SwNodeIndex aTmp( *pIdx ); + const SwNode* pNd; + while( aTmp > 0 ) + { + pNd = & aTmp.GetNode(); + if (SwNodeType::End == pNd->GetNodeType()) + { + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip section + aTmp = *pNd->StartOfSectionNode(); + } + bFirst = false; + } + else if( bFirst ) + { + bFirst = false; + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip section + aTmp = *pNd->StartOfSectionNode(); + } + } + else if( SwNodeType::ContentMask & pNd->GetNodeType() ) + { + const SwSectionNode* pSectNd; + if( ( bSkipHidden || bSkipProtect ) && + nullptr != (pSectNd = pNd->FindSectionNode() ) && + ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || + ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) + { + aTmp = *pSectNd; + } + else + { + (*pIdx) = aTmp; + return const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)); + } + } + --aTmp; + } + return nullptr; +} + +//TODO: improve documentation +//TODO: The inventor of the "single responsibility principle" will be crying if you ever show this code to him! +/** find the next/previous ContentNode or a table node with frames + * + * If no pEnd is given, search is started with FrameIndex; otherwise + * search is started with the one before rFrameIdx and after pEnd. + * + * @param rFrameIdx node with frames to search in + * @param pEnd ??? + * @return result node; 0 (!!!) if not found + */ +SwNode* SwNodes::FindPrvNxtFrameNode( SwNodeIndex& rFrameIdx, + const SwNode* pEnd ) const +{ + SwNode* pFrameNd = nullptr; + + // no layout -> skip + if( GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + SwNode* pSttNd = &rFrameIdx.GetNode(); + + // move of a hidden section? + SwSectionNode* pSectNd = pSttNd->IsSectionNode() + ? pSttNd->StartOfSectionNode()->FindSectionNode() + : pSttNd->FindSectionNode(); + if( !( pSectNd && pSectNd->GetSection().CalcHiddenFlag() ) ) + { + // in a table in table situation we have to assure that we don't leave the + // outer table cell when the inner table is looking for a PrvNxt... + SwTableNode* pTableNd = pSttNd->IsTableNode() + ? pSttNd->StartOfSectionNode()->FindTableNode() + : pSttNd->FindTableNode(); + SwNodeIndex aIdx( rFrameIdx ); + SwNode* pNd; + if( pEnd ) + { + --aIdx; + pNd = &aIdx.GetNode(); + } + else + pNd = pSttNd; + + if( ( pFrameNd = pNd )->IsContentNode() ) + rFrameIdx = aIdx; + + // search forward or backward for a content node + else if( nullptr != ( pFrameNd = GoPrevSection( &aIdx, true, false )) && + ::CheckNodesRange( aIdx, rFrameIdx, true ) && + // Never out of the table at the start + pFrameNd->FindTableNode() == pTableNd && + // Bug 37652: Never out of the table at the end + (!pFrameNd->FindTableNode() || pFrameNd->FindTableBoxStartNode() + == pSttNd->FindTableBoxStartNode() ) && + (!pSectNd || pSttNd->IsSectionNode() || + pSectNd->GetIndex() < pFrameNd->GetIndex()) + ) + { + rFrameIdx = aIdx; + } + else + { + if( pEnd ) + aIdx = pEnd->GetIndex() + 1; + else + aIdx = rFrameIdx; + + // NEVER leave the section when doing this! + if( ( pEnd && ( pFrameNd = &aIdx.GetNode())->IsContentNode() ) || + ( nullptr != ( pFrameNd = GoNextSection( &aIdx, true, false )) && + ::CheckNodesRange( aIdx, rFrameIdx, true ) && + ( pFrameNd->FindTableNode() == pTableNd && + // NEVER go out of the table cell at the end + (!pFrameNd->FindTableNode() || pFrameNd->FindTableBoxStartNode() + == pSttNd->FindTableBoxStartNode() ) ) && + (!pSectNd || pSttNd->IsSectionNode() || + pSectNd->EndOfSectionIndex() > pFrameNd->GetIndex()) + )) + { + // Undo when merging a table with one before, if there is also one after it. + // However, if the node is in a table, it needs to be returned if the + // SttNode is a section or a table! + SwTableNode* pTableNode; + if (pSttNd->IsTableNode() && + nullptr != (pTableNode = pFrameNd->FindTableNode()) && + // TABLE IN TABLE: + pTableNode != pSttNd->StartOfSectionNode()->FindTableNode()) + { + pFrameNd = pTableNode; + rFrameIdx = *pFrameNd; + } + else + rFrameIdx = aIdx; + } + else if( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() ) + { + pFrameNd = pNd->StartOfSectionNode(); + rFrameIdx = *pFrameNd; + } + else + { + if( pEnd ) + aIdx = pEnd->GetIndex() + 1; + else + aIdx = rFrameIdx.GetIndex() + 1; + + if( (pFrameNd = &aIdx.GetNode())->IsTableNode() ) + rFrameIdx = aIdx; + else + { + pFrameNd = nullptr; + + // is there some sectionnodes before a tablenode? + while( aIdx.GetNode().IsSectionNode() ) + { + const SwSection& rSect = aIdx.GetNode(). + GetSectionNode()->GetSection(); + if( rSect.IsHiddenFlag() ) + aIdx = aIdx.GetNode().EndOfSectionIndex()+1; + else + ++aIdx; + } + if( aIdx.GetNode().IsTableNode() ) + { + rFrameIdx = aIdx; + pFrameNd = &aIdx.GetNode(); + } + } + } + } + } + } + return pFrameNd; +} + +void SwNodes::ForEach( sal_uLong nStart, sal_uLong nEnd, + FnForEach_SwNodes fn, void* pArgs ) +{ + if( nEnd > m_nSize ) + nEnd = m_nSize; + + if( nStart < nEnd ) + { + sal_uInt16 cur = Index2Block( nStart ); + BlockInfo** pp = m_ppInf.get() + cur; + BlockInfo* p = *pp; + sal_uInt16 nElem = sal_uInt16( nStart - p->nStart ); + auto pElem = p->mvData.begin() + nElem; + nElem = p->nElem - nElem; + for(;;) + { + if( !(*fn)( static_cast<SwNode *>(*pElem++), pArgs ) || ++nStart >= nEnd ) + break; + + // next element + if( !--nElem ) + { + // new block + p = *++pp; + pElem = p->mvData.begin(); + nElem = p->nElem; + } + } + } +} + +void SwNodes::ForEach( const SwNodeIndex& rStart, const SwNodeIndex& rEnd, + FnForEach_SwNodes fnForEach, void* pArgs ) +{ + ForEach( rStart.GetIndex(), rEnd.GetIndex(), fnForEach, pArgs ); +} + +void SwNodes::RemoveNode( sal_uLong nDelPos, sal_uLong nSz, bool bDel ) +{ +#ifndef NDEBUG + SwNode *const pFirst((*this)[nDelPos]); +#endif + for (sal_uLong nCnt = 0; nCnt < nSz; nCnt++) + { + SwNode* pNode = (*this)[ nDelPos + nCnt ]; + SwTextNode * pTextNd = pNode->GetTextNode(); + + if (pTextNd) + { + pTextNd->RemoveFromList(); + // remove RndStdIds::FLY_AS_CHAR *before* adjusting SwNodeIndex + // so their anchor still points to correct node when deleted! + // NOTE: this will call RemoveNode() recursively! + // so adjust our indexes to account for removed nodes + sal_uLong const nPos = pTextNd->GetIndex(); + SwpHints *const pHints(pTextNd->GetpSwpHints()); + if (pHints) + { + std::vector<SwTextAttr*> flys; + for (size_t i = 0; i < pHints->Count(); ++i) + { + SwTextAttr *const pHint(pHints->Get(i)); + if (RES_TXTATR_FLYCNT == pHint->Which()) + { + flys.push_back(pHint); + } + } + for (SwTextAttr * pHint : flys) + { + pTextNd->DeleteAttribute(pHint); + } // pHints may be dead now + sal_uLong const nDiff = nPos - pTextNd->GetIndex(); + if (nDiff) + { + nDelPos -= nDiff; + } + assert(pTextNd == (*this)[nDelPos + nCnt]); + assert(pFirst == (*this)[nDelPos]); + } + } + SwTableNode* pTableNode = pNode->GetTableNode(); + if (pTableNode) + { + // The node that is deleted is a table node. + // Need to make sure that all the redlines that are + // related to this table are removed from the + // 'Extra Redlines' array + pTableNode->RemoveRedlines(); + } + } + + sal_uLong nEnd = nDelPos + nSz; + SwNode* pNew = (*this)[ nEnd ]; + + for (SwNodeIndex& rIndex : m_vIndices->GetRingContainer()) + { + sal_uLong const nIdx = rIndex.GetIndex(); + if (nDelPos <= nIdx && nIdx < nEnd) + rIndex = *pNew; + } + + std::vector<BigPtrEntry> aTempEntries; + if( bDel ) + { + sal_uLong nCnt = nSz; + BigPtrEntry *pDel = (*this)[ nDelPos+nCnt-1 ], *pPrev = (*this)[ nDelPos+nCnt-2 ]; + + // set temporary object + // JP 24.08.98: this should actually be removed because one could + // call Remove recursively, e.g. for character bound frames. However, + // since there happens way too much here, this temporary object was + // inserted that will be deleted in Remove again (see Bug 55406) + aTempEntries.resize(nCnt); + + while( nCnt-- ) + { + delete pDel; + pDel = pPrev; + sal_uLong nPrevNdIdx = pPrev->GetPos(); + BigPtrEntry* pTempEntry = &aTempEntries[nCnt]; + BigPtrArray::Replace( nPrevNdIdx+1, pTempEntry ); + if( nCnt ) + pPrev = BigPtrArray::operator []( nPrevNdIdx - 1 ); + // the accessed element can be a naked BigPtrEntry from + // aTempEntries, so the downcast to SwNode* in + // SwNodes::operator[] would be illegal (and unnecessary) + } + nDelPos = pDel->GetPos() + 1; + } + + BigPtrArray::Remove( nDelPos, nSz ); +} + +void SwNodes::InsertNode( const SwNodePtr pNode, + const SwNodeIndex& rPos ) +{ + BigPtrEntry* pIns = pNode; + BigPtrArray::Insert( pIns, rPos.GetIndex() ); +} + +void SwNodes::InsertNode( const SwNodePtr pNode, + sal_uLong nPos ) +{ + BigPtrEntry* pIns = pNode; + BigPtrArray::Insert( pIns, nPos ); +} + +// ->#112139# +SwNode * SwNodes::DocumentSectionStartNode(SwNode * pNode) const +{ + if (nullptr != pNode) + { + SwNodeIndex aIdx(*pNode); + + if (aIdx <= (*this)[0]->EndOfSectionIndex()) + pNode = (*this)[0]; + else + { + while ((*this)[0] != pNode->StartOfSectionNode()) + pNode = pNode->StartOfSectionNode(); + } + } + + return pNode; +} + +SwNode * SwNodes::DocumentSectionEndNode(SwNode * pNode) const +{ + return DocumentSectionStartNode(pNode)->EndOfSectionNode(); +} + +bool SwNodes::IsDocNodes() const +{ + return this == &m_pMyDoc->GetNodes(); +} + +void SwNodes::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwNodes")); + for (sal_uLong i = 0; i < Count(); ++i) + (*this)[i]->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/observablethread.cxx b/sw/source/core/docnode/observablethread.cxx new file mode 100644 index 000000000..273dd45c2 --- /dev/null +++ b/sw/source/core/docnode/observablethread.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <observablethread.hxx> +#include <ifinishedthreadlistener.hxx> +#include <memory> + +/* class for an observable thread + + #i73788# +*/ +ObservableThread::ObservableThread() + : mnThreadID( 0 ), + mpThreadListener() +{ +} + +ObservableThread::~ObservableThread() +{ +} + +void ObservableThread::SetListener( std::weak_ptr< IFinishedThreadListener > const & pThreadListener, + const oslInterlockedCount nThreadID ) +{ + mpThreadListener = pThreadListener; + mnThreadID = nThreadID; +} + +void SAL_CALL ObservableThread::run() +{ + acquire(); + + threadFunction(); +} + +void SAL_CALL ObservableThread::onTerminated() +{ + // notify observer + std::shared_ptr< IFinishedThreadListener > pThreadListener = mpThreadListener.lock(); + if ( pThreadListener ) + { + pThreadListener->NotifyAboutFinishedThread( mnThreadID ); + } + + release(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/pausethreadstarting.cxx b/sw/source/core/docnode/pausethreadstarting.cxx new file mode 100644 index 000000000..5d93ea923 --- /dev/null +++ b/sw/source/core/docnode/pausethreadstarting.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <pausethreadstarting.hxx> +#include <swthreadmanager.hxx> + +/* Helper class to pause starting of threads during existence of an instance + of this class + + #i73788# +*/ + +SwPauseThreadStarting::SwPauseThreadStarting() + : mbPausedThreadStarting( false ) +{ + if ( SwThreadManager::ExistsThreadManager() && + !SwThreadManager::GetThreadManager().StartingOfThreadsSuspended() ) + { + SwThreadManager::GetThreadManager().SuspendStartingOfThreads(); + mbPausedThreadStarting = true; + } +} + +SwPauseThreadStarting::~SwPauseThreadStarting() COVERITY_NOEXCEPT_FALSE +{ + if ( mbPausedThreadStarting ) + { + SwThreadManager::GetThreadManager().ResumeStartingOfThreads(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/retrievedinputstreamdata.cxx b/sw/source/core/docnode/retrievedinputstreamdata.cxx new file mode 100644 index 000000000..b41125698 --- /dev/null +++ b/sw/source/core/docnode/retrievedinputstreamdata.cxx @@ -0,0 +1,153 @@ +/* -*- 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 <retrievedinputstreamdata.hxx> +#include <retrieveinputstreamconsumer.hxx> +#include <vcl/svapp.hxx> + +// #i73788# + +SwRetrievedInputStreamDataManager::tDataKey SwRetrievedInputStreamDataManager::mnNextKeyValue = 1; + +namespace +{ + class theSwRetrievedInputStreamDataManager : + public rtl::Static< SwRetrievedInputStreamDataManager, theSwRetrievedInputStreamDataManager> + { + }; +} + +SwRetrievedInputStreamDataManager& SwRetrievedInputStreamDataManager::GetManager() +{ + return theSwRetrievedInputStreamDataManager::get(); +} + +SwRetrievedInputStreamDataManager::tDataKey SwRetrievedInputStreamDataManager::ReserveData( + std::weak_ptr< SwAsyncRetrieveInputStreamThreadConsumer > const & pThreadConsumer ) +{ + osl::MutexGuard aGuard(maMutex); + + // create empty data container for given thread Consumer + tDataKey nDataKey( mnNextKeyValue ); + tData aNewEntry( pThreadConsumer ); + maInputStreamData[ nDataKey ] = aNewEntry; + + // prepare next data key value + if ( mnNextKeyValue < SAL_MAX_UINT64 ) + { + ++mnNextKeyValue; + } + else + { + mnNextKeyValue = 1; + } + + return nDataKey; +} + +void SwRetrievedInputStreamDataManager::PushData( + const tDataKey nDataKey, + css::uno::Reference<css::io::XInputStream> const & xInputStream, + const bool bIsStreamReadOnly ) +{ + osl::MutexGuard aGuard(maMutex); + + std::map< tDataKey, tData >::iterator aIter = maInputStreamData.find( nDataKey ); + + if ( aIter != maInputStreamData.end() ) + { + // Fill data container. + (*aIter).second.mxInputStream = xInputStream; + (*aIter).second.mbIsStreamReadOnly = bIsStreamReadOnly; + + // post user event to process the retrieved input stream data + if ( GetpApp() ) + { + + tDataKey* pDataKey = new tDataKey; + *pDataKey = nDataKey; + Application::PostUserEvent( LINK( this, SwRetrievedInputStreamDataManager, LinkedInputStreamReady ), pDataKey ); + } + else + { + // no application available -> discard data + maInputStreamData.erase( aIter ); + } + } +} + +bool SwRetrievedInputStreamDataManager::PopData( const tDataKey nDataKey, + tData& rData ) +{ + osl::MutexGuard aGuard(maMutex); + + bool bDataProvided( false ); + + std::map< tDataKey, tData >::iterator aIter = maInputStreamData.find( nDataKey ); + + if ( aIter != maInputStreamData.end() ) + { + rData.mpThreadConsumer = (*aIter).second.mpThreadConsumer; + rData.mxInputStream = (*aIter).second.mxInputStream; + rData.mbIsStreamReadOnly = (*aIter).second.mbIsStreamReadOnly; + + maInputStreamData.erase( aIter ); + + bDataProvided = true; + } + + return bDataProvided; +} + +/** callback function, which is triggered by input stream data manager on + filling of the data container to provide retrieved input stream to the + thread Consumer using <Application::PostUserEvent(..)> + + #i73788# + Note: This method has to be run in the main thread. +*/ +IMPL_LINK( SwRetrievedInputStreamDataManager, + LinkedInputStreamReady, + void*, p, void ) +{ + SwRetrievedInputStreamDataManager::tDataKey* pDataKey = static_cast<SwRetrievedInputStreamDataManager::tDataKey*>(p); + if ( !pDataKey ) + { + return; + } + + osl::MutexGuard aGuard(maMutex); + + SwRetrievedInputStreamDataManager& rDataManager = + SwRetrievedInputStreamDataManager::GetManager(); + SwRetrievedInputStreamDataManager::tData aInputStreamData; + if ( rDataManager.PopData( *pDataKey, aInputStreamData ) ) + { + std::shared_ptr< SwAsyncRetrieveInputStreamThreadConsumer > pThreadConsumer = + aInputStreamData.mpThreadConsumer.lock(); + if ( pThreadConsumer ) + { + pThreadConsumer->ApplyInputStream( aInputStreamData.mxInputStream, + aInputStreamData.mbIsStreamReadOnly ); + } + } + delete pDataKey; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/retrieveinputstream.cxx b/sw/source/core/docnode/retrieveinputstream.cxx new file mode 100644 index 000000000..cfe313ae5 --- /dev/null +++ b/sw/source/core/docnode/retrieveinputstream.cxx @@ -0,0 +1,83 @@ +/* -*- 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 <retrieveinputstream.hxx> +#include <unotools/mediadescriptor.hxx> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +/* class for a thread to retrieve an input stream given by a URL + + #i73788# +*/ +::rtl::Reference< ObservableThread > SwAsyncRetrieveInputStreamThread::createThread( + const SwRetrievedInputStreamDataManager::tDataKey nDataKey, + const OUString& rLinkedURL, const OUString& rReferer ) +{ + SwAsyncRetrieveInputStreamThread* pNewThread = + new SwAsyncRetrieveInputStreamThread( nDataKey, rLinkedURL, rReferer ); + return pNewThread; +} + +SwAsyncRetrieveInputStreamThread::SwAsyncRetrieveInputStreamThread( + const SwRetrievedInputStreamDataManager::tDataKey nDataKey, + const OUString& rLinkedURL, + const OUString& rReferer ) + : ObservableThread(), + mnDataKey( nDataKey ), + mrLinkedURL( rLinkedURL ), + mrReferer( rReferer ) +{ +} + +SwAsyncRetrieveInputStreamThread::~SwAsyncRetrieveInputStreamThread() +{ +} + +void SwAsyncRetrieveInputStreamThread::threadFunction() +{ + osl_setThreadName("SwAsyncRetrieveInputStreamThread"); + + css::uno::Sequence < css::beans::PropertyValue > xProps( 2 ); + xProps[0].Name = "URL"; + xProps[0].Value <<= mrLinkedURL; + xProps[1].Name = "Referer"; + xProps[1].Value <<= mrReferer; + utl::MediaDescriptor aMedium( xProps ); + + aMedium.addInputStream(); + + css::uno::Reference<css::io::XInputStream> xInputStream; + aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM()] >>= xInputStream; + if ( !xInputStream.is() ) + { + css::uno::Reference<css::io::XStream> xStream; + aMedium[utl::MediaDescriptor::PROP_STREAM()] >>= xStream; + if ( xStream.is() ) + { + xInputStream = xStream->getInputStream(); + } + } + + SwRetrievedInputStreamDataManager::GetManager().PushData( mnDataKey, + xInputStream, + aMedium.isStreamReadOnly() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/retrieveinputstreamconsumer.cxx b/sw/source/core/docnode/retrieveinputstreamconsumer.cxx new file mode 100644 index 000000000..d89a05361 --- /dev/null +++ b/sw/source/core/docnode/retrieveinputstreamconsumer.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <retrieveinputstreamconsumer.hxx> +#include <ndgrf.hxx> +#include <retrieveinputstream.hxx> +#include <swthreadmanager.hxx> + +/* class to provide creation of a thread to retrieve an input stream given by + a URL and to consume the retrieved input stream. + + #i73788# +*/ +SwAsyncRetrieveInputStreamThreadConsumer::SwAsyncRetrieveInputStreamThreadConsumer( + SwGrfNode& rGrfNode ) + : mrGrfNode( rGrfNode ), + mnThreadID( 0 ) +{ +} + +SwAsyncRetrieveInputStreamThreadConsumer::~SwAsyncRetrieveInputStreamThreadConsumer() COVERITY_NOEXCEPT_FALSE +{ + SwThreadManager::GetThreadManager().RemoveThread( mnThreadID ); +} + +void SwAsyncRetrieveInputStreamThreadConsumer::CreateThread( const OUString& rURL, const OUString& rReferer ) +{ + // Get new data container for input stream data + SwRetrievedInputStreamDataManager::tDataKey nDataKey = + SwRetrievedInputStreamDataManager::GetManager().ReserveData( + mrGrfNode.GetThreadConsumer() ); + + rtl::Reference< ObservableThread > pNewThread = + SwAsyncRetrieveInputStreamThread::createThread( nDataKey, rURL, rReferer ); + + // Add thread to thread manager and pass ownership of thread to thread manager. + mnThreadID = SwThreadManager::GetThreadManager().AddThread( pNewThread ); +} + +void SwAsyncRetrieveInputStreamThreadConsumer::ApplyInputStream( + css::uno::Reference<css::io::XInputStream> const & xInputStream, + const bool bIsStreamReadOnly ) +{ + mrGrfNode.ApplyInputStream( xInputStream, bIsStreamReadOnly ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/section.cxx b/sw/source/core/docnode/section.cxx new file mode 100644 index 000000000..c1f078429 --- /dev/null +++ b/sw/source/core/docnode/section.cxx @@ -0,0 +1,1594 @@ +/* -*- 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 <libxml/xmlstring.h> +#include <libxml/xmlwriter.h> +#include <stdlib.h> +#include <hintids.hxx> +#include <sot/exchange.hxx> +#include <svl/stritem.hxx> +#include <sfx2/docfile.hxx> +#include <editeng/protitem.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/sfxsids.hrc> +#include <docary.hxx> +#include <fmtcntnt.hxx> +#include <fmtpdsc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentLinksAdministrationManager.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <editsh.hxx> +#include <hints.hxx> +#include <docsh.hxx> +#include <ndtxt.hxx> +#include <section.hxx> +#include <swserv.hxx> +#include <shellio.hxx> +#include <poolfmt.hxx> +#include <swbaslnk.hxx> +#include <mvsave.hxx> +#include <fmtftntx.hxx> +#include <ftnidx.hxx> +#include <doctxm.hxx> +#include <fmteiro.hxx> +#include <unosection.hxx> +#include <calbck.hxx> +#include <fmtclds.hxx> +#include <algorithm> +#include "ndsect.hxx" + +using namespace ::com::sun::star; + +namespace { + +class SwIntrnlSectRefLink : public SwBaseLink +{ + SwSectionFormat& rSectFormat; +public: + SwIntrnlSectRefLink( SwSectionFormat& rFormat, SfxLinkUpdateMode nUpdateType ) + : SwBaseLink( nUpdateType, SotClipboardFormatId::RTF ), + rSectFormat( rFormat ) + {} + + virtual void Closed() override; + virtual ::sfx2::SvBaseLink::UpdateResult DataChanged( + const OUString& rMimeType, const css::uno::Any & rValue ) override; + + virtual const SwNode* GetAnchor() const override; + virtual bool IsInRange( sal_uLong nSttNd, sal_uLong nEndNd ) const override; + + SwSectionNode* GetSectNode() + { + const SwNode* pSectNd( GetAnchor() ); + return const_cast<SwSectionNode*>( dynamic_cast<const SwSectionNode*>( pSectNd ) ); + } +}; + +} + +SwSectionData::SwSectionData(SectionType const eType, OUString const& rName) + : m_eType(eType) + , m_sSectionName(rName) + , m_bHiddenFlag(false) + , m_bProtectFlag(false) + , m_bEditInReadonlyFlag(false) // edit in readonly sections + , m_bHidden(false) + , m_bCondHiddenFlag(true) + , m_bConnectFlag(true) +{ +} + +// this must have the same semantics as operator=() +SwSectionData::SwSectionData(SwSection const& rSection) + : m_eType(rSection.GetType()) + , m_sSectionName(rSection.GetSectionName()) + , m_sCondition(rSection.GetCondition()) + , m_sLinkFileName(rSection.GetLinkFileName()) + , m_sLinkFilePassword(rSection.GetLinkFilePassword()) + , m_Password(rSection.GetPassword()) + , m_bHiddenFlag(rSection.IsHiddenFlag()) + , m_bProtectFlag(rSection.IsProtect()) + // edit in readonly sections + , m_bEditInReadonlyFlag(rSection.IsEditInReadonly()) + , m_bHidden(rSection.IsHidden()) + , m_bCondHiddenFlag(true) + , m_bConnectFlag(rSection.IsConnectFlag()) +{ +} + +// this must have the same semantics as operator=() +SwSectionData::SwSectionData(SwSectionData const& rOther) + : m_eType(rOther.m_eType) + , m_sSectionName(rOther.m_sSectionName) + , m_sCondition(rOther.m_sCondition) + , m_sLinkFileName(rOther.m_sLinkFileName) + , m_sLinkFilePassword(rOther.m_sLinkFilePassword) + , m_Password(rOther.m_Password) + , m_bHiddenFlag(rOther.m_bHiddenFlag) + , m_bProtectFlag(rOther.m_bProtectFlag) + // edit in readonly sections + , m_bEditInReadonlyFlag(rOther.m_bEditInReadonlyFlag) + , m_bHidden(rOther.m_bHidden) + , m_bCondHiddenFlag(true) + , m_bConnectFlag(rOther.m_bConnectFlag) +{ +} + +// the semantics here are weird for reasons of backward compatibility +SwSectionData & SwSectionData::operator= (SwSectionData const& rOther) +{ + m_eType = rOther.m_eType; + m_sSectionName = rOther.m_sSectionName; + m_sCondition = rOther.m_sCondition; + m_sLinkFileName = rOther.m_sLinkFileName; + m_sLinkFilePassword = rOther.m_sLinkFilePassword; + m_bConnectFlag = rOther.m_bConnectFlag; + m_Password = rOther.m_Password; + + m_bEditInReadonlyFlag = rOther.m_bEditInReadonlyFlag; + m_bProtectFlag = rOther.m_bProtectFlag; + + m_bHidden = rOther.m_bHidden; + // FIXME: old code did not assign m_bHiddenFlag ? + // FIXME: why should m_bCondHiddenFlag always default to true? + m_bCondHiddenFlag = true; + + return *this; +} + +// the semantics here are weird for reasons of backward compatibility +bool SwSectionData::operator==(SwSectionData const& rOther) const +{ + return (m_eType == rOther.m_eType) + && (m_sSectionName == rOther.m_sSectionName) + && (m_sCondition == rOther.m_sCondition) + && (m_bHidden == rOther.m_bHidden) + && (m_bProtectFlag == rOther.m_bProtectFlag) + && (m_bEditInReadonlyFlag == rOther.m_bEditInReadonlyFlag) + && (m_sLinkFileName == rOther.m_sLinkFileName) + && (m_sLinkFilePassword == rOther.m_sLinkFilePassword) + && (m_Password == rOther.m_Password); + // FIXME: old code ignored m_bCondHiddenFlag m_bHiddenFlag m_bConnectFlag +} + +OUString SwSectionData::CollapseWhiteSpaces(const OUString& sName) +{ + const sal_Int32 nLen = sName.getLength(); + const sal_Unicode cRef = ' '; + OUStringBuffer aBuf(nLen+1); + for (sal_Int32 i = 0; i<nLen; ) + { + const sal_Unicode cCur = sName[i++]; + aBuf.append(cCur); + if (cCur!=cRef) + continue; + while (i<nLen && sName[i]==cRef) + ++i; + } + return aBuf.makeStringAndClear(); +} + +SwSection::SwSection( + SectionType const eType, OUString const& rName, SwSectionFormat & rFormat) + : SwClient(& rFormat) + , m_Data(eType, rName) +{ + SwSection *const pParentSect = GetParent(); + if( pParentSect ) + { + if( pParentSect->IsHiddenFlag() ) + { + SetHidden(); + } + + m_Data.SetProtectFlag( pParentSect->IsProtectFlag() ); + // edit in readonly sections + m_Data.SetEditInReadonlyFlag( pParentSect->IsEditInReadonlyFlag() ); + } + + if (!m_Data.IsProtectFlag()) + { + m_Data.SetProtectFlag( rFormat.GetProtect().IsContentProtected() ); + } + + if (!m_Data.IsEditInReadonlyFlag()) // edit in readonly sections + { + m_Data.SetEditInReadonlyFlag( rFormat.GetEditInReadonly().GetValue() ); + } +} + +SwSection::~SwSection() +{ + SwSectionFormat* pFormat = GetFormat(); + if( !pFormat ) + return; + + SwDoc* pDoc = pFormat->GetDoc(); + if( pDoc->IsInDtor() ) + { + // We reattach our Format to the default FrameFormat + // to not get any dependencies + if( pFormat->DerivedFrom() != pDoc->GetDfltFrameFormat() ) + pFormat->RegisterToFormat( *pDoc->GetDfltFrameFormat() ); + } + else + { + pFormat->Remove( this ); // remove + + if (SectionType::Content != m_Data.GetType()) + { + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( m_RefLink.get() ); + } + + if (m_RefObj.is()) + { + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( m_RefObj.get() ); + } + + // If the Section is the last Client in the Format we can delete it + SwPtrMsgPoolItem aMsgHint( RES_REMOVE_UNO_OBJECT, pFormat ); + pFormat->ModifyNotification( &aMsgHint, &aMsgHint ); + if( !pFormat->HasWriterListeners() ) + { + // Do not add to the Undo. This should've happened earlier. + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + pDoc->DelSectionFormat( pFormat ); + } + } + if (m_RefObj.is()) + { + m_RefObj->Closed(); + } +} + +void SwSection::SetSectionData(SwSectionData const& rData) +{ + bool const bOldHidden( m_Data.IsHidden() ); + m_Data = rData; + // The next two may actually overwrite the m_Data.m_bProtect or EditInReadonly Flag + // in Modify, which should result in same flag value as the old code! + SetProtect(m_Data.IsProtectFlag()); + SetEditInReadonly(m_Data.IsEditInReadonlyFlag()); + if (bOldHidden != m_Data.IsHidden()) // check if changed... + { + ImplSetHiddenFlag(m_Data.IsHidden(), m_Data.IsCondHidden()); + } +} + +bool SwSection::DataEquals(SwSectionData const& rCmp) const +{ + // note that the old code compared the flags of the parameter with the + // format attributes of this; the following mess should do the same... + (void) GetLinkFileName(); // updates m_sLinkFileName + bool const bProtect(m_Data.IsProtectFlag()); + bool const bEditInReadonly(m_Data.IsEditInReadonlyFlag()); + m_Data.SetProtectFlag(IsProtect()); + m_Data.SetEditInReadonlyFlag(IsEditInReadonly()); + bool const bResult( m_Data == rCmp ); + m_Data.SetProtectFlag(bProtect); + m_Data.SetEditInReadonlyFlag(bEditInReadonly); + return bResult; +} + +void SwSection::ImplSetHiddenFlag(bool const bTmpHidden, bool const bCondition) +{ + SwSectionFormat* pFormat = GetFormat(); + OSL_ENSURE(pFormat, "ImplSetHiddenFlag: no format?"); + if( pFormat ) + { + const bool bHide = bTmpHidden && bCondition; + + if (bHide) // should be hidden + { + if (!m_Data.IsHiddenFlag()) // is not hidden + { + // Is the Parent hidden? + // This should be shown by the bHiddenFlag. + + // Tell all Children that they are hidden + SwMsgPoolItem aMsgItem( RES_SECTION_HIDDEN ); + pFormat->ModifyNotification( &aMsgItem, &aMsgItem ); + + // Delete all Frames + pFormat->DelFrames(); + } + } + else if (m_Data.IsHiddenFlag()) // show Nodes again + { + // Show all Frames (Child Sections are accounted for by MakeFrames) + // Only if the Parent Section is not restricting us! + SwSection* pParentSect = pFormat->GetParentSection(); + if( !pParentSect || !pParentSect->IsHiddenFlag() ) + { + // Tell all Children that the Parent is not hidden anymore + SwMsgPoolItem aMsgItem( RES_SECTION_NOT_HIDDEN ); + pFormat->ModifyNotification( &aMsgItem, &aMsgItem ); + + pFormat->MakeFrames(); + } + } + } +} + +bool SwSection::CalcHiddenFlag() const +{ + const SwSection* pSect = this; + do { + if( pSect->IsHidden() && pSect->IsCondHidden() ) + return true; + } while( nullptr != ( pSect = pSect->GetParent()) ); + + return false; +} + +bool SwSection::IsProtect() const +{ + SwSectionFormat const *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::IsProtect: no format?"); + return pFormat + ? pFormat->GetProtect().IsContentProtected() + : IsProtectFlag(); +} + +// edit in readonly sections +bool SwSection::IsEditInReadonly() const +{ + SwSectionFormat const *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::IsEditInReadonly: no format?"); + return pFormat + ? pFormat->GetEditInReadonly().GetValue() + : IsEditInReadonlyFlag(); +} + +void SwSection::SetHidden(bool const bFlag) +{ + if (!m_Data.IsHidden() == !bFlag) + return; + + m_Data.SetHidden(bFlag); + ImplSetHiddenFlag(bFlag, m_Data.IsCondHidden()); +} + +void SwSection::SetProtect(bool const bFlag) +{ + SwSectionFormat *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::SetProtect: no format?"); + if (pFormat) + { + SvxProtectItem aItem( RES_PROTECT ); + aItem.SetContentProtect( bFlag ); + pFormat->SetFormatAttr( aItem ); + // note: this will call m_Data.SetProtectFlag via Modify! + } + else + { + m_Data.SetProtectFlag(bFlag); + } +} + +// edit in readonly sections +void SwSection::SetEditInReadonly(bool const bFlag) +{ + SwSectionFormat *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::SetEditInReadonly: no format?"); + if (pFormat) + { + SwFormatEditInReadonly aItem; + aItem.SetValue( bFlag ); + pFormat->SetFormatAttr( aItem ); + // note: this will call m_Data.SetEditInReadonlyFlag via Modify! + } + else + { + m_Data.SetEditInReadonlyFlag(bFlag); + } +} + +void SwSection::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + bool bUpdateFootnote = false; + switch( pOld ? pOld->Which() : pNew ? pNew->Which() : 0 ) + { + case RES_ATTRSET_CHG: + if (pNew && pOld) + { + SfxItemSet* pNewSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pNew))->GetChgSet(); + SfxItemSet* pOldSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pOld))->GetChgSet(); + const SfxPoolItem* pItem; + + if( SfxItemState::SET == pNewSet->GetItemState( + RES_PROTECT, false, &pItem ) ) + { + m_Data.SetProtectFlag( static_cast<SvxProtectItem const*>(pItem) + ->IsContentProtected() ); + pNewSet->ClearItem( RES_PROTECT ); + pOldSet->ClearItem( RES_PROTECT ); + } + + // --> edit in readonly sections + if( SfxItemState::SET == pNewSet->GetItemState( + RES_EDIT_IN_READONLY, false, &pItem ) ) + { + m_Data.SetEditInReadonlyFlag( + static_cast<SwFormatEditInReadonly const*>(pItem)->GetValue()); + pNewSet->ClearItem( RES_EDIT_IN_READONLY ); + pOldSet->ClearItem( RES_EDIT_IN_READONLY ); + } + + if( SfxItemState::SET == pNewSet->GetItemState( + RES_FTN_AT_TXTEND, false, &pItem ) || + SfxItemState::SET == pNewSet->GetItemState( + RES_END_AT_TXTEND, false, &pItem )) + { + bUpdateFootnote = true; + } + + if( !pNewSet->Count() ) + return; + } + break; + + case RES_PROTECT: + if( pNew ) + { + bool bNewFlag = + static_cast<const SvxProtectItem*>(pNew)->IsContentProtected(); + if( !bNewFlag ) + { + // Switching off: See if there is protection transferred + // by the Parents + const SwSection* pSect = this; + do { + if( pSect->IsProtect() ) + { + bNewFlag = true; + break; + } + pSect = pSect->GetParent(); + } while (pSect); + } + + m_Data.SetProtectFlag( bNewFlag ); + } + return; + // edit in readonly sections + case RES_EDIT_IN_READONLY: + if( pNew ) + { + const bool bNewFlag = + static_cast<const SwFormatEditInReadonly*>(pNew)->GetValue(); + m_Data.SetEditInReadonlyFlag( bNewFlag ); + } + return; + + case RES_SECTION_HIDDEN: + m_Data.SetHiddenFlag(true); + return; + + case RES_SECTION_NOT_HIDDEN: + m_Data.SetHiddenFlag( m_Data.IsHidden() && m_Data.IsCondHidden() ); + return; + + case RES_COL: + // Is handled by the Layout, if appropriate + break; + + case RES_FTN_AT_TXTEND: + if( pNew && pOld ) + { + bUpdateFootnote = true; + } + break; + + case RES_END_AT_TXTEND: + if( pNew && pOld ) + { + bUpdateFootnote = true; + } + break; + + default: + CheckRegistration( pOld ); + break; + } + + if( bUpdateFootnote ) + { + SwSectionNode* pSectNd = GetFormat()->GetSectionNode(); + if( pSectNd ) + pSectNd->GetDoc()->GetFootnoteIdxs().UpdateFootnote(SwNodeIndex( *pSectNd )); + } +} + +void SwSection::SetRefObject( SwServerObject* pObj ) +{ + m_RefObj = pObj; +} + +void SwSection::SetCondHidden(bool const bFlag) +{ + if (!m_Data.IsCondHidden() == !bFlag) + return; + + m_Data.SetCondHidden(bFlag); + ImplSetHiddenFlag(m_Data.IsHidden(), bFlag); +} + +// Set/remove the linked FileName +OUString const & SwSection::GetLinkFileName() const +{ + if (m_RefLink.is()) + { + OUString sTmp; + switch (m_Data.GetType()) + { + case SectionType::DdeLink: + sTmp = m_RefLink->GetLinkSourceName(); + break; + + case SectionType::FileLink: + { + OUString sRange; + OUString sFilter; + if (m_RefLink->GetLinkManager() && + sfx2::LinkManager::GetDisplayNames( + m_RefLink.get(), nullptr, &sTmp, &sRange, &sFilter )) + { + sTmp += OUStringChar(sfx2::cTokenSeparator) + sFilter + + OUStringChar(sfx2::cTokenSeparator) + sRange; + } + else if( GetFormat() && !GetFormat()->GetSectionNode() ) + { + // If the Section is in the UndoNodesArray, the LinkManager + // does not contain the Link, thus it cannot be queried for it. + // Thus return the current Name. + return m_Data.GetLinkFileName(); + } + } + break; + default: break; + } + m_Data.SetLinkFileName(sTmp); + } + return m_Data.GetLinkFileName(); +} + +void SwSection::SetLinkFileName(const OUString& rNew) +{ + if (m_RefLink.is()) + { + m_RefLink->SetLinkSourceName( rNew ); + } + m_Data.SetLinkFileName(rNew); +} + +// If it was a Linked Section, we need to make all Child Links visible +void SwSection::MakeChildLinksVisible( const SwSectionNode& rSectNd ) +{ + const SwNode* pNd; + const ::sfx2::SvBaseLinks& rLnks = rSectNd.GetDoc()->getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + for( auto n = rLnks.size(); n; ) + { + sfx2::SvBaseLink& rBLnk = *rLnks[--n]; + if (!rBLnk.IsVisible() && dynamic_cast<const SwBaseLink*>(&rBLnk) != nullptr + && nullptr != (pNd = static_cast<SwBaseLink&>(rBLnk).GetAnchor())) + { + pNd = pNd->StartOfSectionNode(); // If it's a SectionNode + const SwSectionNode* pParent; + while( nullptr != ( pParent = pNd->FindSectionNode() ) && + ( SectionType::Content == pParent->GetSection().GetType() + || pNd == &rSectNd )) + pNd = pParent->StartOfSectionNode(); + + // It's within a normal Section, so show again + if( !pParent ) + rBLnk.SetVisible(true); + } + } +} + +const SwTOXBase* SwSection::GetTOXBase() const +{ + const SwTOXBase* pRet = nullptr; + if( SectionType::ToxContent == GetType() ) + pRet = dynamic_cast<const SwTOXBaseSection*>(this); + return pRet; +} + +SwSectionFormat::SwSectionFormat( SwFrameFormat* pDrvdFrame, SwDoc *pDoc ) + : SwFrameFormat( pDoc->GetAttrPool(), OUString(), pDrvdFrame ) +{ + LockModify(); + SetFormatAttr( *GetDfltAttr( RES_COL ) ); + UnlockModify(); +} + +SwSectionFormat::~SwSectionFormat() +{ + if( !GetDoc()->IsInDtor() ) + { + SwSectionNode* pSectNd; + const SwNodeIndex* pIdx = GetContent( false ).GetContentIdx(); + if( pIdx && &GetDoc()->GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwSection& rSect = pSectNd->GetSection(); + // If it was a linked Section, we need to make all Child Links + // visible again + if( rSect.IsConnected() ) + SwSection::MakeChildLinksVisible( *pSectNd ); + + // Check whether we need to be visible, before deleting the Nodes + if( rSect.IsHiddenFlag() ) + { + SwSection* pParentSect = rSect.GetParent(); + if( !pParentSect || !pParentSect->IsHiddenFlag() ) + { + // Make Nodes visible again + rSect.SetHidden(false); + } + } + // mba: test iteration; objects are removed while iterating + // use hint which allows to specify, if the content shall be saved or not + CallSwClientNotify( SwSectionFrameMoveAndDeleteHint( true ) ); + + // Raise the Section up + SwNodeRange aRg( *pSectNd, 0, *pSectNd->EndOfSectionNode() ); + GetDoc()->GetNodes().SectionUp( &aRg ); + } + LockModify(); + ResetFormatAttr( RES_CNTNT ); + UnlockModify(); + } +} + +SwSection * SwSectionFormat::GetSection() const +{ + return SwIterator<SwSection,SwSectionFormat>( *this ).First(); +} + +// Do not destroy all Frames in aDepend (Frames are recognized with a dynamic_cast). +void SwSectionFormat::DelFrames() +{ + SwSectionNode* pSectNd; + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + if( pIdx && &GetDoc()->GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + // First delete the <SwSectionFrame> of the <SwSectionFormat> instance + // mba: test iteration as objects are removed in iteration + // use hint which allows to specify, if the content shall be saved or not + CallSwClientNotify( SwSectionFrameMoveAndDeleteHint( false ) ); + + // Then delete frames of the nested <SwSectionFormat> instances + SwIterator<SwSectionFormat,SwSectionFormat> aIter( *this ); + SwSectionFormat *pLast = aIter.First(); + while ( pLast ) + { + pLast->DelFrames(); + pLast = aIter.Next(); + } + + sal_uLong nEnd = pSectNd->EndOfSectionIndex(); + sal_uLong nStart = pSectNd->GetIndex()+1; + sw_DeleteFootnote( pSectNd, nStart, nEnd ); + } + if( pIdx ) + { + // Send Hint for PageDesc. Actually the Layout contained in the + // Paste of the Frame itself would need to do this. But that leads + // to subsequent errors, which we'd need to solve at run-time. + SwNodeIndex aNextNd( *pIdx ); + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( &aNextNd, true, false ); + if( pCNd ) + { + const SfxPoolItem& rItem = pCNd->GetSwAttrSet().Get( RES_PAGEDESC ); + pCNd->ModifyNotification( &rItem, &rItem ); + } + } +} + +// Create the Views +void SwSectionFormat::MakeFrames() +{ + SwSectionNode* pSectNd; + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + + if( pIdx && &GetDoc()->GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwNodeIndex aIdx( *pIdx ); + pSectNd->MakeOwnFrames( &aIdx ); + } +} + +void SwSectionFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + bool bClients = false; + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_ATTRSET_CHG: + if (HasWriterListeners() && pOld && pNew) + { + SfxItemSet* pNewSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pNew))->GetChgSet(); + SfxItemSet* pOldSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pOld))->GetChgSet(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pNewSet->GetItemState( + RES_PROTECT, false, &pItem )) + { + ModifyBroadcast( pItem, pItem ); + pNewSet->ClearItem( RES_PROTECT ); + pOldSet->ClearItem( RES_PROTECT ); + } + + // --> edit in readonly sections + if( SfxItemState::SET == pNewSet->GetItemState( + RES_EDIT_IN_READONLY, false, &pItem ) ) + { + ModifyBroadcast( pItem, pItem ); + pNewSet->ClearItem( RES_EDIT_IN_READONLY ); + pOldSet->ClearItem( RES_EDIT_IN_READONLY ); + } + + if( SfxItemState::SET == pNewSet->GetItemState( + RES_FTN_AT_TXTEND, false, &pItem )) + { + ModifyBroadcast( &pOldSet->Get( RES_FTN_AT_TXTEND ), pItem ); + pNewSet->ClearItem( RES_FTN_AT_TXTEND ); + pOldSet->ClearItem( RES_FTN_AT_TXTEND ); + } + if( SfxItemState::SET == pNewSet->GetItemState( + RES_END_AT_TXTEND, false, &pItem )) + { + ModifyBroadcast( &pOldSet->Get( RES_END_AT_TXTEND ), pItem ); + pNewSet->ClearItem( RES_END_AT_TXTEND ); + pOldSet->ClearItem( RES_END_AT_TXTEND ); + } + if( !static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->Count() ) + return; + } + break; + + case RES_FTN_AT_TXTEND: + case RES_END_AT_TXTEND : bClients = true; + [[fallthrough]]; + case RES_SECTION_HIDDEN: + case RES_SECTION_NOT_HIDDEN: + { + SwSection* pSect = GetSection(); + if( pSect && ( bClients || ( RES_SECTION_HIDDEN == nWhich ? + !pSect->IsHiddenFlag() : pSect->IsHiddenFlag() ) ) ) + { + ModifyBroadcast( pOld, pNew ); + } + } + return ; + + case RES_PROTECT: + case RES_EDIT_IN_READONLY: // edit in readonly sections + // Pass through these Messages until the End of the tree! + if( HasWriterListeners() ) + { + ModifyBroadcast( pOld, pNew ); + } + return; // That's it! + + case RES_OBJECTDYING: + if( !GetDoc()->IsInDtor() && pOld && + static_cast<const SwPtrMsgPoolItem *>(pOld)->pObject == static_cast<void*>(GetRegisteredIn()) ) + { + // My Parents will be destroyed, so get the Parent's Parent + // and update + SwFrameFormat::Modify( pOld, pNew ); // Rewire first! + UpdateParent(); + return; + } + break; + + case RES_FMT_CHG: + if( !GetDoc()->IsInDtor() && + static_cast<const SwFormatChg*>(pNew)->pChangedFormat == static_cast<void*>(GetRegisteredIn()) && + dynamic_cast<const SwSectionFormat*>(static_cast<const SwFormatChg*>(pNew)->pChangedFormat) != nullptr ) + { + // My Parent will be changed, thus I need to update + SwFrameFormat::Modify( pOld, pNew ); // Rewire first! + UpdateParent(); + return; + } + break; + } + SwFrameFormat::Modify( pOld, pNew ); + + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXTextSection(uno::Reference<text::XTextSection>(nullptr)); + } +} + +// Get info from the Format +bool SwSectionFormat::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_FINDNEARESTNODE: + if( GetFormatAttr( RES_PAGEDESC ).GetPageDesc() ) + { + const SwSectionNode* pNd = GetSectionNode(); + if( pNd ) + static_cast<SwFindNearestNode&>(rInfo).CheckNode( *pNd ); + } + return true; + + case RES_CONTENT_VISIBLE: + { + SwFrame* pFrame = SwIterator<SwFrame,SwFormat>(*this).First(); + // if the current section has no own frame search for the children + if(!pFrame) + { + SwIterator<SwSectionFormat,SwSectionFormat> aFormatIter(*this); + SwSectionFormat* pChild = aFormatIter.First(); + while(pChild && !pFrame) + { + pFrame = SwIterator<SwFrame,SwFormat>(*pChild).First(); + pChild = aFormatIter.Next(); + } + } + static_cast<SwPtrMsgPoolItem&>(rInfo).pObject = pFrame; + } + return false; + } + return SwModify::GetInfo( rInfo ); +} + +static bool lcl_SectionCmpPos( const SwSection *pFirst, const SwSection *pSecond) +{ + const SwSectionFormat* pFSectFormat = pFirst->GetFormat(); + const SwSectionFormat* pSSectFormat = pSecond->GetFormat(); + OSL_ENSURE( pFSectFormat && pSSectFormat && + pFSectFormat->GetContent(false).GetContentIdx() && + pSSectFormat->GetContent(false).GetContentIdx(), + "Invalid sections" ); + return pFSectFormat->GetContent(false).GetContentIdx()->GetIndex() < + pSSectFormat->GetContent(false).GetContentIdx()->GetIndex(); +} + +// get all Sections that have been derived from this one +void SwSectionFormat::GetChildSections( SwSections& rArr, + SectionSort eSort, + bool bAllSections ) const +{ + rArr.clear(); + + if( HasWriterListeners() ) + { + SwIterator<SwSectionFormat,SwSectionFormat> aIter(*this); + const SwNodeIndex* pIdx; + for( SwSectionFormat* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + if( bAllSections || + ( nullptr != ( pIdx = pLast->GetContent(false). + GetContentIdx()) && &pIdx->GetNodes() == &GetDoc()->GetNodes() )) + { + SwSection* pDummy = pLast->GetSection(); + rArr.push_back( pDummy ); + } + + // Do we need any sorting? + if( 1 < rArr.size() ) + switch( eSort ) + { + case SectionSort::Pos: + std::sort( rArr.begin(), rArr.end(), lcl_SectionCmpPos ); + break; + case SectionSort::Not: break; + } + } +} + +// See whether the Section is within the Nodes or the UndoNodes array +bool SwSectionFormat::IsInNodesArr() const +{ + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + return pIdx && &pIdx->GetNodes() == &GetDoc()->GetNodes(); +} + +// Parent was changed +void SwSectionFormat::UpdateParent() +{ + if( !HasWriterListeners() ) + return; + + SwSection* pSection = nullptr; + const SvxProtectItem* pProtect(nullptr); + // edit in readonly sections + const SwFormatEditInReadonly* pEditInReadonly = nullptr; + bool bIsHidden = false; + + SwIterator<SwClient,SwSectionFormat> aIter(*this); + for(SwClient* pLast = aIter.First(); pLast; pLast = aIter.Next()) + { + if( dynamic_cast<const SwSectionFormat*>(pLast) != nullptr ) + { + if( !pSection ) + { + pSection = GetSection(); + if( GetRegisteredIn() ) + { + const SwSection* pPS = GetParentSection(); + pProtect = &pPS->GetFormat()->GetProtect(); + // edit in readonly sections + pEditInReadonly = &pPS->GetFormat()->GetEditInReadonly(); + bIsHidden = pPS->IsHiddenFlag(); + } + else + { + pProtect = &GetProtect(); + // edit in readonly sections + pEditInReadonly = &GetEditInReadonly(); + bIsHidden = pSection->IsHidden(); + } + } + if (!pProtect->IsContentProtected() != + !pSection->IsProtectFlag()) + { + pLast->ModifyNotification( static_cast<SfxPoolItem const *>(pProtect), + static_cast<SfxPoolItem const *>(pProtect) ); + } + + // edit in readonly sections + if (!pEditInReadonly->GetValue() != + !pSection->IsEditInReadonlyFlag()) + { + pLast->ModifyNotification( static_cast<SfxPoolItem const *>(pEditInReadonly), + static_cast<SfxPoolItem const *>(pEditInReadonly) ); + } + + if( bIsHidden == pSection->IsHiddenFlag() ) + { + SwMsgPoolItem aMsgItem( static_cast<sal_uInt16>(bIsHidden + ? RES_SECTION_HIDDEN + : RES_SECTION_NOT_HIDDEN ) ); + pLast->ModifyNotification( &aMsgItem, &aMsgItem ); + } + } + else if( !pSection && + dynamic_cast<const SwSection*>(pLast) != nullptr ) + { + pSection = static_cast<SwSection*>(pLast); + if( GetRegisteredIn() ) + { + const SwSection* pPS = GetParentSection(); + pProtect = &pPS->GetFormat()->GetProtect(); + // edit in readonly sections + pEditInReadonly = &pPS->GetFormat()->GetEditInReadonly(); + bIsHidden = pPS->IsHiddenFlag(); + } + else + { + pProtect = &GetProtect(); + // edit in readonly sections + pEditInReadonly = &GetEditInReadonly(); + bIsHidden = pSection->IsHidden(); + } + } + } +} + +SwSectionNode* SwSectionFormat::GetSectionNode() +{ + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + if( pIdx && ( &pIdx->GetNodes() == &GetDoc()->GetNodes() )) + return pIdx->GetNode().GetSectionNode(); + return nullptr; +} + +// Is this Section valid for the GlobalDocument? +const SwSection* SwSectionFormat::GetGlobalDocSection() const +{ + const SwSectionNode* pNd = GetSectionNode(); + if( pNd && + ( SectionType::FileLink == pNd->GetSection().GetType() || + SectionType::ToxContent == pNd->GetSection().GetType() ) && + pNd->GetIndex() > pNd->GetNodes().GetEndOfExtras().GetIndex() && + !pNd->StartOfSectionNode()->IsSectionNode() && + !pNd->StartOfSectionNode()->FindSectionNode() ) + return &pNd->GetSection(); + return nullptr; +} + +// sw::Metadatable +::sfx2::IXmlIdRegistry& SwSectionFormat::GetRegistry() +{ + return GetDoc()->GetXmlIdRegistry(); +} + +bool SwSectionFormat::IsInClipboard() const +{ + return GetDoc()->IsClipBoard(); +} + +bool SwSectionFormat::IsInUndo() const +{ + return !IsInNodesArr(); +} + +bool SwSectionFormat::IsInContent() const +{ + SwNodeIndex const*const pIdx = GetContent(false).GetContentIdx(); + OSL_ENSURE(pIdx, "SwSectionFormat::IsInContent: no index?"); + return pIdx == nullptr || !GetDoc()->IsInHeaderFooter(*pIdx); +} + +// n.b.: if the section format represents an index, then there is both a +// SwXDocumentIndex and a SwXTextSection instance for this single core object. +// these two can both implement XMetadatable and forward to the same core +// section format. but here only one UNO object can be returned, +// so always return the text section. +uno::Reference< rdf::XMetadatable > +SwSectionFormat::MakeUnoObject() +{ + uno::Reference<rdf::XMetadatable> xMeta; + SwSection *const pSection( GetSection() ); + if (pSection) + { + xMeta.set( SwXTextSection::CreateXTextSection(this, + SectionType::ToxHeader == pSection->GetType()), + uno::UNO_QUERY ); + } + return xMeta; +} + +bool SwSectionFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +void SwSectionFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwSectionFormat")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + GetAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +void SwSectionFormats::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwSectionFormats")); + for (size_t i = 0; i < size(); ++i) + GetFormat(i)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +// Method to break section links inside a linked section +static void lcl_BreakSectionLinksInSect( const SwSectionNode& rSectNd ) +{ + if ( !rSectNd.GetDoc() ) + { + OSL_FAIL( "method <lcl_RemoveSectionLinksInSect(..)> - no Doc at SectionNode" ); + return; + } + + if ( !rSectNd.GetSection().IsConnected() ) + { + OSL_FAIL( "method <lcl_RemoveSectionLinksInSect(..)> - no Link at Section of SectionNode" ); + return; + } + const ::sfx2::SvBaseLink* pOwnLink( &(rSectNd.GetSection().GetBaseLink() ) ); + const ::sfx2::SvBaseLinks& rLnks = rSectNd.GetDoc()->getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + for ( auto n = rLnks.size(); n > 0; ) + { + SwIntrnlSectRefLink* pSectLnk = dynamic_cast<SwIntrnlSectRefLink*>(&(*rLnks[ --n ])); + if ( pSectLnk && pSectLnk != pOwnLink && + pSectLnk->IsInRange( rSectNd.GetIndex(), rSectNd.EndOfSectionIndex() ) ) + { + // break the link of the corresponding section. + // the link is also removed from the link manager + SwSectionNode* pSectNode = pSectLnk->GetSectNode(); + assert(pSectNode); + pSectNode->GetSection().BreakLink(); + + // for robustness, because link is removed from the link manager + if ( n > rLnks.size() ) + { + n = rLnks.size(); + } + } + } +} + +static void lcl_UpdateLinksInSect( SwBaseLink& rUpdLnk, SwSectionNode& rSectNd ) +{ + SwDoc* pDoc = rSectNd.GetDoc(); + SwDocShell* pDShell = pDoc->GetDocShell(); + if( !pDShell || !pDShell->GetMedium() ) + return ; + + const OUString sName( pDShell->GetMedium()->GetName() ); + const OUString sMimeType( SotExchange::GetFormatMimeType( SotClipboardFormatId::SIMPLE_FILE )); + uno::Any aValue; + aValue <<= sName; // Arbitrary name + + const ::sfx2::SvBaseLinks& rLnks = pDoc->getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + for( auto n = rLnks.size(); n; ) + { + SwBaseLink* pBLink; + + ::sfx2::SvBaseLink* pLnk = &(*rLnks[ --n ]); + if( pLnk != &rUpdLnk && + sfx2::SvBaseLinkObjectType::ClientFile == pLnk->GetObjType() && + dynamic_cast< const SwBaseLink *>( pLnk ) != nullptr && + ( pBLink = static_cast<SwBaseLink*>(pLnk) )->IsInRange( rSectNd.GetIndex(), + rSectNd.EndOfSectionIndex() ) ) + { + // It's in the Section, so update. But only if it's not in the same File! + OUString sFName; + sfx2::LinkManager::GetDisplayNames( pBLink, nullptr, &sFName ); + if( sFName != sName ) + { + pBLink->DataChanged( sMimeType, aValue ); + + // If needed find the Link pointer to avoid skipping one or calling one twice + if( n >= rLnks.size() && 0 != ( n = rLnks.size() )) + --n; + + if( n && pLnk != &(*rLnks[ n ]) ) + { + // Find - it can only precede it! + while( n ) + if( pLnk == &(*rLnks[ --n ] ) ) + break; + } + } + } + } +} + +::sfx2::SvBaseLink::UpdateResult SwIntrnlSectRefLink::DataChanged( + const OUString& rMimeType, const uno::Any & rValue ) +{ + SwSectionNode* pSectNd = rSectFormat.GetSectionNode(); + SwDoc* pDoc = rSectFormat.GetDoc(); + + SotClipboardFormatId nDataFormat = SotExchange::GetFormatIdFromMimeType( rMimeType ); + + if( !pSectNd || !pDoc || pDoc->IsInDtor() || ChkNoDataFlag() || + sfx2::LinkManager::RegisterStatusInfoId() == nDataFormat ) + { + // Should we be in the Undo already? + return SUCCESS; + } + + // #i38810# - Due to possible existing signatures, the + // document has to be modified after updating a link. + pDoc->getIDocumentState().SetModified(); + // set additional flag that links have been updated, in order to check this + // during load. + pDoc->getIDocumentLinksAdministration().SetLinksUpdated( true ); + + // Always switch off Undo + bool const bWasUndo = pDoc->GetIDocumentUndoRedo().DoesUndo(); + pDoc->GetIDocumentUndoRedo().DoUndo(false); + bool bWasVisibleLinks = pDoc->getIDocumentLinksAdministration().IsVisibleLinks(); + pDoc->getIDocumentLinksAdministration().SetVisibleLinks( false ); + + SwPaM* pPam; + SwViewShell* pVSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwEditShell* pESh = pDoc->GetEditShell(); + pDoc->getIDocumentFieldsAccess().LockExpFields(); + { + // Insert an empty TextNode at the Section's start + SwNodeIndex aIdx( *pSectNd, +1 ); + SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() ); + SwTextNode* pNewNd = pDoc->GetNodes().MakeTextNode( aIdx, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + + if( pESh ) + pESh->StartAllAction(); + else if( pVSh ) + pVSh->StartAction(); + + SwPosition aPos( aIdx, SwIndex( pNewNd, 0 )); + --aPos.nNode; + SwDoc::CorrAbs( aIdx, aEndIdx, aPos, true ); + + pPam = new SwPaM( aPos ); + + // Delete everything succeeding it + --aIdx; + DelFlyInRange( aIdx, aEndIdx ); + DelBookmarks(aIdx, aEndIdx); + ++aIdx; + + pDoc->GetNodes().Delete( aIdx, aEndIdx.GetIndex() - aIdx.GetIndex() ); + } + + SwSection& rSection = pSectNd->GetSection(); + rSection.SetConnectFlag(false); + + Reader* pRead = nullptr; + switch( nDataFormat ) + { + case SotClipboardFormatId::STRING: + pRead = ReadAscii; + break; + + case SotClipboardFormatId::RICHTEXT: + case SotClipboardFormatId::RTF: + pRead = SwReaderWriter::GetRtfReader(); + break; + + case SotClipboardFormatId::SIMPLE_FILE: + if ( rValue.hasValue() ) + { + OUString sFileName; + if ( !(rValue >>= sFileName) ) + break; + OUString sFilter; + OUString sRange; + sfx2::LinkManager::GetDisplayNames( this, nullptr, &sFileName, + &sRange, &sFilter ); + + RedlineFlags eOldRedlineFlags = RedlineFlags::NONE; + SfxObjectShellRef xDocSh; + SfxObjectShellLock xLockRef; + int nRet; + if( sFileName.isEmpty() ) + { + xDocSh = pDoc->GetDocShell(); + nRet = 1; + } + else + { + nRet = SwFindDocShell( xDocSh, xLockRef, sFileName, + rSection.GetLinkFilePassword(), + sFilter, 0, pDoc->GetDocShell() ); + if( nRet ) + { + SwDoc* pSrcDoc = static_cast<SwDocShell*>( xDocSh.get() )->GetDoc(); + eOldRedlineFlags = pSrcDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pSrcDoc->getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowInsert ); + } + } + + if( nRet ) + { + rSection.SetConnectFlag(); + + SwNodeIndex aSave( pPam->GetPoint()->nNode, -1 ); + std::unique_ptr<SwNodeRange> pCpyRg; + + if( xDocSh->GetMedium() && + rSection.GetLinkFilePassword().isEmpty() ) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == xDocSh->GetMedium()->GetItemSet()-> + GetItemState( SID_PASSWORD, false, &pItem ) ) + rSection.SetLinkFilePassword( + static_cast<const SfxStringItem*>(pItem)->GetValue() ); + } + + SwDoc* pSrcDoc = static_cast<SwDocShell*>( xDocSh.get() )->GetDoc(); + + if( !sRange.isEmpty() ) + { + // Catch recursion + bool bRecursion = false; + if( pSrcDoc == pDoc ) + { + tools::SvRef<SwServerObject> refObj( static_cast<SwServerObject*>( + pDoc->getIDocumentLinksAdministration().CreateLinkSource( sRange ))); + if( refObj.is() ) + { + bRecursion = refObj->IsLinkInServer( this ) || + ChkNoDataFlag(); + } + } + + SwNodeIndex& rInsPos = pPam->GetPoint()->nNode; + + SwPaM* pCpyPam = nullptr; + if( !bRecursion && + pSrcDoc->GetDocumentLinksAdministrationManager().SelectServerObj( sRange, pCpyPam, pCpyRg ) + && pCpyPam ) + { + if( pSrcDoc != pDoc || + pCpyPam->Start()->nNode > rInsPos || + rInsPos >= pCpyPam->End()->nNode ) + { + pSrcDoc->getIDocumentContentOperations().CopyRange(*pCpyPam, *pPam->GetPoint(), SwCopyFlags::CheckPosInFly); + } + delete pCpyPam; + } + if( pCpyRg && pSrcDoc == pDoc && + pCpyRg->aStart < rInsPos && rInsPos < pCpyRg->aEnd ) + { + pCpyRg.reset(); + } + } + else if( pSrcDoc != pDoc ) + pCpyRg.reset(new SwNodeRange( pSrcDoc->GetNodes().GetEndOfExtras(), 2, + pSrcDoc->GetNodes().GetEndOfContent() )); + + // #i81653# + // Update links of extern linked document or extern linked + // document section, if section is protected. + if ( pSrcDoc != pDoc && + rSection.IsProtectFlag() ) + { + pSrcDoc->getIDocumentLinksAdministration().GetLinkManager().UpdateAllLinks( false, false, nullptr ); + } + + if( pCpyRg ) + { + SwNodeIndex& rInsPos = pPam->GetPoint()->nNode; + bool bCreateFrame = rInsPos.GetIndex() <= + pDoc->GetNodes().GetEndOfExtras().GetIndex() || + rInsPos.GetNode().FindTableNode(); + + SwTableNumFormatMerge aTNFM( *pSrcDoc, *pDoc ); + + pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pCpyRg, rInsPos, nullptr, bCreateFrame); + ++aSave; + + if( !bCreateFrame ) + ::MakeFrames( pDoc, aSave, rInsPos ); + + // Delete last Node, only if it was copied successfully + // (the Section contains more than one Node) + if( 2 < pSectNd->EndOfSectionIndex() - pSectNd->GetIndex() ) + { + aSave = rInsPos; + pPam->Move( fnMoveBackward, GoInNode ); + pPam->SetMark(); // Rewire both SwPositions + + pDoc->CorrAbs( aSave, *pPam->GetPoint(), 0, true ); + pDoc->GetNodes().Delete( aSave ); + } + pCpyRg.reset(); + } + + lcl_BreakSectionLinksInSect( *pSectNd ); + + // Update all Links in this Section + lcl_UpdateLinksInSect( *this, *pSectNd ); + } + if( xDocSh.is() ) + { + if( 2 == nRet ) + xDocSh->DoClose(); + else if( static_cast<SwDocShell*>( xDocSh.get() )->GetDoc() ) + static_cast<SwDocShell*>( xDocSh.get() )->GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( + eOldRedlineFlags ); + } + } + break; + default: break; + } + + // Only create DDE if Shell is available! + uno::Sequence< sal_Int8 > aSeq; + if( pRead && rValue.hasValue() && ( rValue >>= aSeq ) ) + { + if( pESh ) + { + pESh->Push(); + SwPaM* pCursor = pESh->GetCursor(); + *pCursor->GetPoint() = *pPam->GetPoint(); + delete pPam; + pPam = pCursor; + } + + SvMemoryStream aStrm( const_cast<sal_Int8 *>(aSeq.getConstArray()), aSeq.getLength(), + StreamMode::READ ); + aStrm.Seek( 0 ); + + // TODO/MBA: it's impossible to set a BaseURL here! + SwReader aTmpReader( aStrm, OUString(), pDoc->GetDocShell()->GetMedium()->GetBaseURL(), *pPam ); + + if( ! aTmpReader.Read( *pRead ).IsError() ) + { + rSection.SetConnectFlag(); + } + + if( pESh ) + { + pESh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pPam = nullptr; // pam was deleted earlier + } + } + + // remove all undo actions and turn undo on again + pDoc->GetIDocumentUndoRedo().DelAllUndoObj(); + pDoc->GetIDocumentUndoRedo().DoUndo(bWasUndo); + pDoc->getIDocumentLinksAdministration().SetVisibleLinks( bWasVisibleLinks ); + + pDoc->getIDocumentFieldsAccess().UnlockExpFields(); + if( !pDoc->getIDocumentFieldsAccess().IsExpFieldsLocked() ) + pDoc->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + if( pESh ) + pESh->EndAllAction(); + else if( pVSh ) + pVSh->EndAction(); + delete pPam; // Was created at the start + + return SUCCESS; +} + +void SwIntrnlSectRefLink::Closed() +{ + SwDoc* pDoc = rSectFormat.GetDoc(); + if( pDoc && !pDoc->IsInDtor() ) + { + // Advise says goodbye: mark the Section as not protected + // and change the Flag + const SwSectionFormats& rFormats = pDoc->GetSections(); + for( auto n = rFormats.size(); n; ) + if( rFormats[ --n ] == &rSectFormat ) + { + SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwEditShell* pESh = pDoc->GetEditShell(); + + if( pESh ) + pESh->StartAllAction(); + else + pSh->StartAction(); + + SwSectionData aSectionData(*rSectFormat.GetSection()); + aSectionData.SetType( SectionType::Content ); + aSectionData.SetLinkFileName( OUString() ); + aSectionData.SetProtectFlag( false ); + // edit in readonly sections + aSectionData.SetEditInReadonlyFlag( false ); + + aSectionData.SetConnectFlag( false ); + + pDoc->UpdateSection( n, aSectionData ); + + // Make all Links within the Section visible again + SwSectionNode* pSectNd = rSectFormat.GetSectionNode(); + if( pSectNd ) + SwSection::MakeChildLinksVisible( *pSectNd ); + + if( pESh ) + pESh->EndAllAction(); + else + pSh->EndAction(); + break; + } + } + SvBaseLink::Closed(); +} + +void SwSection::CreateLink( LinkCreateType eCreateType ) +{ + SwSectionFormat* pFormat = GetFormat(); + OSL_ENSURE(pFormat, "SwSection::CreateLink: no format?"); + if (!pFormat || (SectionType::Content == m_Data.GetType())) + return ; + + SfxLinkUpdateMode nUpdateType = SfxLinkUpdateMode::ALWAYS; + + if (!m_RefLink.is()) + { + // create BaseLink + m_RefLink = new SwIntrnlSectRefLink( *pFormat, nUpdateType ); + } + else + { + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( m_RefLink.get() ); + } + + SwIntrnlSectRefLink *const pLnk = + static_cast<SwIntrnlSectRefLink*>( m_RefLink.get() ); + + const OUString sCmd(SwSectionData::CollapseWhiteSpaces(m_Data.GetLinkFileName())); + pLnk->SetUpdateMode( nUpdateType ); + pLnk->SetVisible( pFormat->GetDoc()->getIDocumentLinksAdministration().IsVisibleLinks() ); + + switch (m_Data.GetType()) + { + case SectionType::DdeLink: + pLnk->SetLinkSourceName( sCmd ); + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().InsertDDELink( pLnk ); + break; + case SectionType::FileLink: + { + pLnk->SetContentType( SotClipboardFormatId::SIMPLE_FILE ); + sal_Int32 nIndex = 0; + const OUString sFile(sCmd.getToken( 0, sfx2::cTokenSeparator, nIndex )); + const OUString sFltr(sCmd.getToken( 0, sfx2::cTokenSeparator, nIndex )); + const OUString sRange(sCmd.getToken( 0, sfx2::cTokenSeparator, nIndex )); + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().InsertFileLink( *pLnk, + static_cast<sfx2::SvBaseLinkObjectType>(m_Data.GetType()), + sFile, + ( !sFltr.isEmpty() ? &sFltr : nullptr ), + ( !sRange.isEmpty() ? &sRange : nullptr ) ); + } + break; + default: + OSL_ENSURE( false, "What kind of Link is this?" ); + } + + switch( eCreateType ) + { + case LinkCreateType::Connect: // Connect Link right away + pLnk->Connect(); + break; + + case LinkCreateType::Update: // Connect Link and update + pLnk->Update(); + break; + case LinkCreateType::NONE: break; + } +} + +void SwSection::BreakLink() +{ + const SectionType eCurrentType( GetType() ); + if ( eCurrentType == SectionType::Content || + eCurrentType == SectionType::ToxHeader || + eCurrentType == SectionType::ToxContent ) + { + // nothing to do + return; + } + + // Release link, if it exists + if (m_RefLink.is()) + { + SwSectionFormat *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::BreakLink: no format?"); + if (pFormat) + { + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( m_RefLink.get() ); + } + m_RefLink.clear(); + } + // change type + SetType( SectionType::Content ); + // reset linked file data + SetLinkFileName( OUString() ); + SetLinkFilePassword( OUString() ); +} + +const SwNode* SwIntrnlSectRefLink::GetAnchor() const +{ + return rSectFormat.GetSectionNode(); +} + +bool SwIntrnlSectRefLink::IsInRange( sal_uLong nSttNd, sal_uLong nEndNd ) const +{ + SwStartNode* pSttNd = rSectFormat.GetSectionNode(); + return pSttNd && + nSttNd < pSttNd->GetIndex() && + pSttNd->EndOfSectionIndex() < nEndNd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/swbaslnk.cxx b/sw/source/core/docnode/swbaslnk.cxx new file mode 100644 index 000000000..8b7832937 --- /dev/null +++ b/sw/source/core/docnode/swbaslnk.cxx @@ -0,0 +1,359 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/svapp.hxx> + +#include <sfx2/docfile.hxx> +#include <sfx2/lnkbase.hxx> +#include <sfx2/objsh.hxx> +#include <editeng/boxitem.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/event.hxx> +#include <sot/exchange.hxx> +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <frmatr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <DocumentLinksAdministrationManager.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <swevent.hxx> +#include <swbaslnk.hxx> +#include <swserv.hxx> +#include <viewsh.hxx> +#include <ndgrf.hxx> +#include <hints.hxx> +#include <cntfrm.hxx> +#include <htmltbl.hxx> +#include <calbck.hxx> +#include <dialoghelp.hxx> +#include <memory> + +using namespace com::sun::star; + +static bool SetGrfFlySize( const Size& rGrfSz, SwGrfNode* pGrfNd, const Size &rOrigGrfSize ); + + +static void lcl_CallModify( SwGrfNode& rGrfNd, SfxPoolItem& rItem ) +{ + //call first all not SwNoTextFrames, then the SwNoTextFrames. + // The reason is, that in the SwNoTextFrames the Graphic + // after a Paint will be swapped out! So all other "behind" + // them haven't a loaded Graphic. + rGrfNd.LockModify(); + { + SwIterator<SwClient,SwGrfNode> aIter(rGrfNd); + for(SwClient* pLast = aIter.First(); pLast; pLast = aIter.Next()) + if(dynamic_cast<const SwContentFrame*>( pLast) == nullptr) + pLast->ModifyNotification(&rItem, &rItem); + } + { + SwIterator<SwContentFrame,SwGrfNode> aIter(rGrfNd); + for(SwClient* pLast = aIter.First(); pLast; pLast = aIter.Next()) + pLast->ModifyNotification(&rItem, &rItem); + } + rGrfNd.UnlockModify(); +} + +::sfx2::SvBaseLink::UpdateResult SwBaseLink::DataChanged( + const OUString& rMimeType, const uno::Any & rValue ) +{ + if( !m_pContentNode ) + { + OSL_ENSURE(false, "DataChanged without ContentNode" ); + return ERROR_GENERAL; + } + + SwDoc* pDoc = m_pContentNode->GetDoc(); + if( pDoc->IsInDtor() || ChkNoDataFlag() ) + { + return SUCCESS; + } + + SotClipboardFormatId nFormat = SotExchange::GetFormatIdFromMimeType( rMimeType ); + + if( m_pContentNode->IsNoTextNode() && + nFormat == sfx2::LinkManager::RegisterStatusInfoId() ) + { + // Only a status change - serve Events? + OUString sState; + + if( rValue.hasValue() && ( rValue >>= sState )) + { + SvMacroItemId nEvent = SvMacroItemId::NONE; + switch( sState.toInt32() ) + { + case sfx2::LinkManager::STATE_LOAD_OK: nEvent = SvMacroItemId::OnImageLoadDone; break; + case sfx2::LinkManager::STATE_LOAD_ERROR: nEvent = SvMacroItemId::OnImageLoadError; break; + case sfx2::LinkManager::STATE_LOAD_ABORT: nEvent = SvMacroItemId::OnImageLoadCancel; break; + } + + SwFrameFormat* pFormat; + if( nEvent != SvMacroItemId::NONE && nullptr != ( pFormat = m_pContentNode->GetFlyFormat() )) + { + SwCallMouseEvent aCallEvent; + aCallEvent.Set( EVENT_OBJECT_IMAGE, pFormat ); + pDoc->CallEvent( nEvent, aCallEvent ); + } + } + return SUCCESS; // That's it! + } + + bool bUpdate = false; + bool bFrameInPaint = false; + Size aGrfSz, aOldSz; + + SwGrfNode* pSwGrfNode = nullptr; + + if (m_pContentNode->IsGrfNode()) + { + pSwGrfNode = m_pContentNode->GetGrfNode(); + assert(pSwGrfNode && "Error, pSwGrfNode expected when node answers IsGrfNode() with true (!)"); + aOldSz = pSwGrfNode->GetTwipSize(); + const GraphicObject& rGrfObj = pSwGrfNode->GetGrfObj(); + + bFrameInPaint = pSwGrfNode->IsFrameInPaint(); + + Graphic aGrf; + + // tdf#124698 if any auth dialog is needed, find what the parent window should be + weld::Window* pDlgParent = GetFrameWeld(pDoc); + + if (pDoc->getIDocumentLinksAdministration().GetLinkManager().GetGraphicFromAny(rMimeType, rValue, aGrf, pDlgParent) && + ( GraphicType::Default != aGrf.GetType() || + GraphicType::Default != rGrfObj.GetType() ) ) + { + aGrfSz = ::GetGraphicSizeTwip( aGrf, nullptr ); + + pSwGrfNode->SetGraphic(aGrf); + bUpdate = true; + + // Always use the correct graphic size + if( aGrfSz.Height() && aGrfSz.Width() && + aOldSz.Height() && aOldSz.Width() && + aGrfSz != aOldSz ) + { + pSwGrfNode->SetTwipSize(aGrfSz); + aOldSz = aGrfSz; + } + } + } + else if( m_pContentNode->IsOLENode() ) + bUpdate = true; + + if ( !bUpdate || bFrameInPaint ) + return SUCCESS; + + if (pSwGrfNode) + { + if (!SetGrfFlySize(aGrfSz, pSwGrfNode, aOldSz)) + { + SwMsgPoolItem aMsgHint(RES_GRAPHIC_ARRIVED); + lcl_CallModify(*pSwGrfNode, aMsgHint); + return SUCCESS; + } + } + + return SUCCESS; +} + +static bool SetGrfFlySize( const Size& rGrfSz, SwGrfNode* pGrfNd, const Size& rOrigGrfSize ) +{ + bool bRet = false; + SwViewShell *pSh = pGrfNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + std::unique_ptr<CurrShell> pCurr; + if ( pGrfNd->GetDoc()->GetEditShell() ) + pCurr.reset(new CurrShell( pSh )); + + Size aSz = rOrigGrfSize; + if ( !(aSz.Width() && aSz.Height()) && + rGrfSz.Width() && rGrfSz.Height() ) + { + SwFrameFormat* pFormat = nullptr; + if (pGrfNd->IsChgTwipSize()) + pFormat = pGrfNd->GetFlyFormat(); + if (nullptr != pFormat) + { + Size aCalcSz( aSz ); + if ( !aSz.Height() && aSz.Width() ) + // Calculate the right height + aCalcSz.setHeight( rGrfSz.Height() * + aSz.Width() / rGrfSz.Width() ); + else if ( !aSz.Width() && aSz.Height() ) + // Calculate the right width + aCalcSz.setWidth( rGrfSz.Width() * + aSz.Height() / rGrfSz.Height() ); + else + // Take over height and width + aCalcSz = rGrfSz; + + const SvxBoxItem &rBox = pFormat->GetBox(); + aCalcSz.AdjustWidth(rBox.CalcLineSpace(SvxBoxItemLine::LEFT) + + rBox.CalcLineSpace(SvxBoxItemLine::RIGHT) ); + aCalcSz.AdjustHeight(rBox.CalcLineSpace(SvxBoxItemLine::TOP) + + rBox.CalcLineSpace(SvxBoxItemLine::BOTTOM) ); + const SwFormatFrameSize& rOldAttr = pFormat->GetFrameSize(); + if( rOldAttr.GetSize() != aCalcSz ) + { + SwFormatFrameSize aAttr( rOldAttr ); + aAttr.SetSize( aCalcSz ); + pFormat->SetFormatAttr( aAttr ); + bRet = true; + } + + if( !aSz.Width() ) + { + // If the graphic is anchored in a table, we need to recalculate + // the table rows + const SwDoc *pDoc = pGrfNd->GetDoc(); + const SwPosition* pAPos = pFormat->GetAnchor().GetContentAnchor(); + SwTableNode *pTableNd; + if (pAPos && nullptr != (pTableNd = pAPos->nNode.GetNode().FindTableNode())) + { + const bool bLastGrf = !pTableNd->GetTable().DecGrfsThatResize(); + SwHTMLTableLayout *pLayout = + pTableNd->GetTable().GetHTMLTableLayout(); + if( pLayout ) + { + const sal_uInt16 nBrowseWidth = + pLayout->GetBrowseWidthByTable( *pDoc ); + if ( nBrowseWidth ) + { + pLayout->Resize( nBrowseWidth, true, true, + bLastGrf ? HTMLTABLE_RESIZE_NOW + : 500 ); + } + } + } + } + } + + // SetTwipSize rescales an ImageMap if needed for which + // it requires the Frame Format + pGrfNd->SetTwipSize( rGrfSz ); + } + + return bRet; +} + +bool SwBaseLink::SwapIn( bool bWaitForData, bool bNativFormat ) +{ + if( !GetObj() && ( bNativFormat || ( !IsSynchron() && bWaitForData ) )) + { + AddNextRef(); + GetRealObject_(); + ReleaseRef(); + } + + bool bRes = false; + + if( GetObj() ) + { + OUString aMimeType( SotExchange::GetFormatMimeType( GetContentType() )); + uno::Any aValue; + (void)GetObj()->GetData( aValue, aMimeType, !IsSynchron() && bWaitForData ); + + if( bWaitForData && !GetObj() ) + { + OSL_ENSURE( false, "The SvxFileObject was deleted in a GetData!" ); + } + else + { + bRes = aValue.hasValue(); + if ( bRes ) + { + DataChanged( aMimeType, aValue ); + } + } + } + else if( !IsSynchron() && bWaitForData ) + { + SetSynchron( true ); + bRes = Update(); + SetSynchron( false ); + } + else + bRes = Update(); + + return bRes; +} + +void SwBaseLink::Closed() +{ + if( m_pContentNode && !m_pContentNode->GetDoc()->IsInDtor() ) + { + // Delete the connection + if( m_pContentNode->IsGrfNode() ) + static_cast<SwGrfNode*>(m_pContentNode)->ReleaseLink(); + } + SvBaseLink::Closed(); +} + +const SwNode* SwBaseLink::GetAnchor() const +{ + if (m_pContentNode) + { + SwFrameFormat *const pFormat = m_pContentNode->GetFlyFormat(); + if (pFormat) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()))) + { + return &pAPos->nNode.GetNode(); + } + return nullptr; + } + } + + OSL_ENSURE( false, "GetAnchor is not shadowed" ); + return nullptr; +} + +bool SwBaseLink::IsRecursion( const SwBaseLink* pChkLnk ) const +{ + tools::SvRef<SwServerObject> aRef( static_cast<SwServerObject*>(GetObj()) ); + if( aRef.is() ) + { + // As it's a ServerObject, we query all contained Links + // if we are contained in them. Else we have a recursion. + return aRef->IsLinkInServer( pChkLnk ); + } + return false; +} + +bool SwBaseLink::IsInRange( sal_uLong, sal_uLong ) const +{ + // Not Graphic or OLE Links + // Fields or Sections have their own derivation! + return false; +} + +SwBaseLink::~SwBaseLink() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/swthreadjoiner.cxx b/sw/source/core/docnode/swthreadjoiner.cxx new file mode 100644 index 000000000..ee5a3144c --- /dev/null +++ b/sw/source/core/docnode/swthreadjoiner.cxx @@ -0,0 +1,54 @@ +/* -*- 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 <swthreadjoiner.hxx> +#include <com/sun/star/util/JobManager.hpp> +#include <comphelper/processfactory.hxx> +#include <osl/mutex.hxx> +#include <rtl/instance.hxx> + +// Testing + +using namespace ::com::sun::star; + +namespace +{ + class theJoinerMutex : public rtl::Static<osl::Mutex, theJoinerMutex> {}; + + uno::Reference< util::XJobManager > pThreadJoiner; +} + +uno::Reference< util::XJobManager >& SwThreadJoiner::GetThreadJoiner() +{ + osl::MutexGuard aGuard(theJoinerMutex::get()); + + if ( !pThreadJoiner.is() ) + { + pThreadJoiner = util::JobManager::create( comphelper::getProcessComponentContext() ); + } + + return pThreadJoiner; +} + +void SwThreadJoiner::ReleaseThreadJoiner() +{ + pThreadJoiner.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/swthreadmanager.cxx b/sw/source/core/docnode/swthreadmanager.cxx new file mode 100644 index 000000000..4b646e8b2 --- /dev/null +++ b/sw/source/core/docnode/swthreadmanager.cxx @@ -0,0 +1,83 @@ +/* -*- 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 <swthreadmanager.hxx> +#include <swthreadjoiner.hxx> +#include <observablethread.hxx> +#include "threadmanager.hxx" + +/** class to manage threads in Writer - it conforms the singleton pattern + + #i73788# +*/ +bool SwThreadManager::mbThreadManagerInstantiated = false; + +SwThreadManager::SwThreadManager() + : mpThreadManagerImpl( new ThreadManager( SwThreadJoiner::GetThreadJoiner() ) ) +{ + mpThreadManagerImpl->Init(); + mbThreadManagerInstantiated = true; +} + +SwThreadManager::~SwThreadManager() +{ +} + +namespace { + +struct InitInstance : public rtl::Static<SwThreadManager, InitInstance> {}; + +} + +SwThreadManager& SwThreadManager::GetThreadManager() +{ + return InitInstance::get(); +} + +bool SwThreadManager::ExistsThreadManager() +{ + return mbThreadManagerInstantiated; +} + +oslInterlockedCount SwThreadManager::AddThread( const rtl::Reference< ObservableThread >& rThread ) +{ + return mpThreadManagerImpl->AddThread( rThread ); +} + +void SwThreadManager::RemoveThread( const oslInterlockedCount nThreadID ) +{ + mpThreadManagerImpl->RemoveThread( nThreadID ); +} + +void SwThreadManager::SuspendStartingOfThreads() +{ + mpThreadManagerImpl->SuspendStartingOfThreads(); +} + +void SwThreadManager::ResumeStartingOfThreads() +{ + mpThreadManagerImpl->ResumeStartingOfThreads(); +} + +bool SwThreadManager::StartingOfThreadsSuspended() +{ + return mpThreadManagerImpl->StartingOfThreadsSuspended(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/threadlistener.cxx b/sw/source/core/docnode/threadlistener.cxx new file mode 100644 index 000000000..5ace4c002 --- /dev/null +++ b/sw/source/core/docnode/threadlistener.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <threadlistener.hxx> +#include "threadmanager.hxx" + +/** helper class to observe threads + + #i73788# +*/ +ThreadListener::ThreadListener( ThreadManager& rThreadListenerOwner ) + : IFinishedThreadListener(), + mrThreadListenerOwner( rThreadListenerOwner ) +{ +} + +ThreadListener::~ThreadListener() +{ +} + +void ThreadListener::ListenToThread( const oslInterlockedCount nThreadID, + ObservableThread& rThread ) +{ + rThread.SetListener( mrThreadListenerOwner.GetThreadListenerWeakRef(), + nThreadID ); +} + +void ThreadListener::NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ) +{ + mrThreadListenerOwner.NotifyAboutFinishedThread( nThreadID ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/threadmanager.cxx b/sw/source/core/docnode/threadmanager.cxx new file mode 100644 index 000000000..c2e7c46ed --- /dev/null +++ b/sw/source/core/docnode/threadmanager.cxx @@ -0,0 +1,253 @@ +/* -*- 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 "cancellablejob.hxx" +#include "threadmanager.hxx" +#include <threadlistener.hxx> + +#include <osl/diagnose.h> + +#include <algorithm> + +#include <com/sun/star/util/XJobManager.hpp> + +using namespace ::com::sun::star; + +/** class to manage threads + + #i73788# +*/ +const std::deque< ThreadManager::tThreadData >::size_type ThreadManager::mnStartedSize = 10; + +ThreadManager::ThreadManager( uno::Reference< util::XJobManager > const & rThreadJoiner ) + : maMutex(), + mrThreadJoiner( rThreadJoiner ), + mpThreadListener(), + mnThreadIDCounter( 0 ), + maWaitingForStartThreads(), + maStartedThreads(), + maStartNewThreadIdle("SW ThreadManager StartNewThreadIdle"), + mbStartingOfThreadsSuspended( false ) +{ +} + +void ThreadManager::Init() +{ + mpThreadListener = std::make_shared<ThreadListener>( *this ); + + maStartNewThreadIdle.SetPriority( TaskPriority::LOWEST ); + maStartNewThreadIdle.SetInvokeHandler( LINK( this, ThreadManager, TryToStartNewThread ) ); +} + +ThreadManager::~ThreadManager() +{ + maWaitingForStartThreads.clear(); + maStartedThreads.clear(); +} + +std::weak_ptr< IFinishedThreadListener > ThreadManager::GetThreadListenerWeakRef() const +{ + return mpThreadListener; +} + +void ThreadManager::NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ) +{ + RemoveThread( nThreadID, true ); +} + +oslInterlockedCount ThreadManager::AddThread( + const rtl::Reference< ObservableThread >& rThread ) + +{ + osl::MutexGuard aGuard(maMutex); + + // create new thread + tThreadData aThreadData; + oslInterlockedCount nNewThreadID( osl_atomic_increment( &mnThreadIDCounter ) ); + { + aThreadData.nThreadID = nNewThreadID; + + aThreadData.pThread = rThread; + aThreadData.aJob = new CancellableJob( aThreadData.pThread ); + + aThreadData.pThread->setPriority( osl_Thread_PriorityBelowNormal ); + mpThreadListener->ListenToThread( aThreadData.nThreadID, + *(aThreadData.pThread) ); + } + + // add thread to manager + if ( maStartedThreads.size() < mnStartedSize && + !StartingOfThreadsSuspended() ) + { + // Try to start thread + if ( !StartThread( aThreadData ) ) + { + // No success on starting thread + // If no more started threads exist, but still threads are waiting, + // setup Timer to start thread from waiting ones + if ( maStartedThreads.empty() && !maWaitingForStartThreads.empty() ) + { + maStartNewThreadIdle.Start(); + } + } + } + else + { + // Thread will be started later + maWaitingForStartThreads.push_back( aThreadData ); + } + + return nNewThreadID; +} + +void ThreadManager::RemoveThread( const oslInterlockedCount nThreadID, + const bool bThreadFinished ) +{ + // --> SAFE ---- + osl::MutexGuard aGuard(maMutex); + + std::deque< tThreadData >::iterator aIter = + std::find_if( maStartedThreads.begin(), maStartedThreads.end(), + ThreadPred( nThreadID ) ); + + if ( aIter != maStartedThreads.end() ) + { + tThreadData aTmpThreadData( *aIter ); + + maStartedThreads.erase( aIter ); + + if ( bThreadFinished ) + { + // release thread as job from thread joiner instance + css::uno::Reference< css::util::XJobManager > rThreadJoiner( mrThreadJoiner ); + if ( rThreadJoiner.is() ) + { + rThreadJoiner->releaseJob( aTmpThreadData.aJob ); + } + else + { + OSL_FAIL( "<ThreadManager::RemoveThread(..)> - ThreadJoiner already gone!" ); + } + } + + // Try to start thread from waiting ones + TryToStartNewThread( nullptr ); + } + else + { + aIter = std::find_if( maWaitingForStartThreads.begin(), + maWaitingForStartThreads.end(), ThreadPred( nThreadID ) ); + + if ( aIter != maWaitingForStartThreads.end() ) + { + maWaitingForStartThreads.erase( aIter ); + } + } + // <-- SAFE ---- +} + +bool ThreadManager::StartWaitingThread() +{ + if ( !maWaitingForStartThreads.empty() ) + { + tThreadData aThreadData( maWaitingForStartThreads.front() ); + maWaitingForStartThreads.pop_front(); + return StartThread( aThreadData ); + } + else + { + return false; + } +} + +bool ThreadManager::StartThread( const tThreadData& rThreadData ) +{ + bool bThreadStarted( false ); + + if ( rThreadData.pThread->create() ) + { + // start of thread successful. + bThreadStarted = true; + + maStartedThreads.push_back( rThreadData ); + + // register thread as job at thread joiner instance + css::uno::Reference< css::util::XJobManager > rThreadJoiner( mrThreadJoiner ); + if ( rThreadJoiner.is() ) + { + rThreadJoiner->registerJob( rThreadData.aJob ); + } + else + { + OSL_FAIL( "<ThreadManager::StartThread(..)> - ThreadJoiner already gone!" ); + } + } + else + { + // thread couldn't be started. + maWaitingForStartThreads.push_front( rThreadData ); + } + + return bThreadStarted; +} + +IMPL_LINK_NOARG(ThreadManager, TryToStartNewThread, Timer *, void) +{ + osl::MutexGuard aGuard(maMutex); + + if ( !StartingOfThreadsSuspended() ) + { + // Try to start thread from waiting ones + if ( !StartWaitingThread() ) + { + // No success on starting thread + // If no more started threads exist, but still threads are waiting, + // setup Timer to start thread from waiting ones + if ( maStartedThreads.empty() && !maWaitingForStartThreads.empty() ) + { + maStartNewThreadIdle.Start(); + } + } + } +} + +void ThreadManager::ResumeStartingOfThreads() +{ + osl::MutexGuard aGuard(maMutex); + + mbStartingOfThreadsSuspended = false; + + while ( maStartedThreads.size() < mnStartedSize && + !maWaitingForStartThreads.empty() ) + { + if ( !StartWaitingThread() ) + { + // No success on starting thread + // If no more started threads exist, but still threads are waiting, + // setup Timer to start thread from waiting ones + if ( maStartedThreads.empty() && !maWaitingForStartThreads.empty() ) + { + maStartNewThreadIdle.Start(); + break; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/threadmanager.hxx b/sw/source/core/docnode/threadmanager.hxx new file mode 100644 index 000000000..21078e7ab --- /dev/null +++ b/sw/source/core/docnode/threadmanager.hxx @@ -0,0 +1,148 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_DOCNODE_THREADMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_DOCNODE_THREADMANAGER_HXX + +#include <vcl/idle.hxx> +#include <osl/mutex.hxx> +#include <osl/interlck.h> +#include <rtl/ref.hxx> + +#include <deque> +#include <cppuhelper/weakref.hxx> +#include <observablethread.hxx> + +#include <memory> + +namespace com::sun::star::util { class XCancellable; } +namespace com::sun::star::util { class XJobManager; } + +class IFinishedThreadListener; +class ThreadListener; +class Timer; + +/** class to manage threads + + OD 2007-01-29 #i73788# + An instance of this class takes care of the starting of threads. + It assures that not more than <mnStartedSize> threads + are started. +*/ +class ThreadManager final +{ + public: + + explicit ThreadManager( css::uno::Reference< css::util::XJobManager > const & rThreadJoiner ); + ~ThreadManager(); + + std::weak_ptr< IFinishedThreadListener > GetThreadListenerWeakRef() const; + void NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ); + + /** initialization + + IMPORTANT NOTE: Needs to be called directly after construction + */ + void Init(); + + /** add thread to the thread manager and taking ownership for the thread + + @return unique ID for added thread + */ + oslInterlockedCount AddThread( + const ::rtl::Reference< ObservableThread >& rThread ); + + void RemoveThread( const oslInterlockedCount nThreadID, + const bool bThreadFinished = false ); + + DECL_LINK( TryToStartNewThread, Timer*, void ); + + /** suspend the starting of threads + + Suspending the starting of further threads is sensible during the + destruction of a Writer document. + */ + void SuspendStartingOfThreads() + { + osl::MutexGuard aGuard(maMutex); + + mbStartingOfThreadsSuspended = true; + } + + /** continues the starting of threads after it has been suspended + */ + void ResumeStartingOfThreads(); + + bool StartingOfThreadsSuspended() + { + osl::MutexGuard aGuard(maMutex); + + return mbStartingOfThreadsSuspended; + } + + struct tThreadData + { + oslInterlockedCount nThreadID; + ::rtl::Reference< ObservableThread > pThread; + css::uno::Reference< css::util::XCancellable > aJob; + + tThreadData() + : nThreadID( 0 ), + aJob() + {} + }; + + private: + + static const std::deque< tThreadData >::size_type mnStartedSize; + + osl::Mutex maMutex; + + css::uno::WeakReference< css::util::XJobManager > mrThreadJoiner; + + std::shared_ptr< ThreadListener > mpThreadListener; + + oslInterlockedCount mnThreadIDCounter; + + std::deque< tThreadData > maWaitingForStartThreads; + std::deque< tThreadData > maStartedThreads; + + Idle maStartNewThreadIdle; + + bool mbStartingOfThreadsSuspended; + + struct ThreadPred + { + oslInterlockedCount mnThreadID; + explicit ThreadPred( oslInterlockedCount nThreadID ) + : mnThreadID( nThreadID ) + {} + + bool operator() ( const tThreadData& rThreadData ) const + { + return rThreadData.nThreadID == mnThreadID; + } + }; + + bool StartWaitingThread(); + + bool StartThread( const tThreadData& aThreadData ); +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/dcontact.cxx b/sw/source/core/draw/dcontact.cxx new file mode 100644 index 000000000..ad672260a --- /dev/null +++ b/sw/source/core/draw/dcontact.cxx @@ -0,0 +1,2530 @@ +/* -*- 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 <memory> +#include <hintids.hxx> +#include <editeng/lrspitem.hxx> +#include <svx/svdpage.hxx> +#include <svx/svditer.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdviter.hxx> +#include <svx/svdview.hxx> +#include <svx/sdr/contact/displayinfo.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <drawdoc.hxx> +#include <fmtornt.hxx> +#include <viewimp.hxx> +#include <fmtsrnd.hxx> +#include <fmtanchr.hxx> +#include <node.hxx> +#include <fmtcntnt.hxx> +#include <fmtfsize.hxx> +#include <pam.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <frmtool.hxx> +#include <flyfrm.hxx> +#include <textboxhelper.hxx> +#include <frmfmt.hxx> +#include <fmtfollowtextflow.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <unodraw.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <doc.hxx> +#include <hints.hxx> +#include <txtfrm.hxx> +#include <frameformats.hxx> +#include <docary.hxx> +#include <sortedobjs.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/sdr/contact/viewcontactofvirtobj.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <svx/sdr/contact/viewobjectcontactofsdrobj.hxx> +#include <com/sun/star/text/WritingMode2.hpp> +#include <calbck.hxx> +#include <algorithm> +#include <txtfly.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; + +namespace +{ + /** unary function used to find a 'virtual' drawing object anchored at a given frame */ + struct VirtObjAnchoredAtFramePred + { + const SwFrame* m_pAnchorFrame; + + // #i26791# - compare with master frame + static const SwFrame* FindFrame(const SwFrame* pFrame) + { + if(!pFrame || !pFrame->IsContentFrame()) + return pFrame; + auto pContentFrame = static_cast<const SwContentFrame*>(pFrame); + while(pContentFrame->IsFollow()) + pContentFrame = pContentFrame->FindMaster(); + return pContentFrame; + } + + VirtObjAnchoredAtFramePred(const SwFrame* pAnchorFrame) + : m_pAnchorFrame(FindFrame(pAnchorFrame)) + {} + + bool operator()(const SwDrawVirtObjPtr& rpDrawVirtObj) + { + return FindFrame(rpDrawVirtObj->GetAnchorFrame()) == m_pAnchorFrame; + } + }; +} + +void setContextWritingMode(SdrObject* pObj, SwFrame const * pAnchor) +{ + if(!pObj || !pAnchor) + return; + short nWritingDirection = + pAnchor->IsVertical() ? text::WritingMode2::TB_RL : + pAnchor->IsRightToLeft() ? text::WritingMode2::RL_TB : + text::WritingMode2::LR_TB; + pObj->SetContextWritingMode(nWritingDirection); +} + + +/** The Get reverse way: seeks the format to the specified object. + * If the object is a SwVirtFlyDrawObj then the format of this + * will be acquired. + * Otherwise it is just a simple drawing object. This has a + * UserCall and is the client of the searched format. +*/ +SwFrameFormat *FindFrameFormat( SdrObject *pObj ) +{ + SwFrameFormat* pRetval = nullptr; + + if (SwVirtFlyDrawObj* pFlyDrawObj = dynamic_cast<SwVirtFlyDrawObj*>(pObj)) + { + pRetval = pFlyDrawObj->GetFormat(); + } + else + { + SwContact* pContact = GetUserCall(pObj); + if ( pContact ) + { + pRetval = pContact->GetFormat(); + } + } + return pRetval; +} + +bool HasWrap( const SdrObject* pObj ) +{ + if ( pObj ) + { + const SwFrameFormat* pFormat = ::FindFrameFormat( pObj ); + if ( pFormat ) + { + return css::text::WrapTextMode_THROUGH != pFormat->GetSurround().GetSurround(); + } + } + + return false; +} + +/// returns the BoundRect _inclusive_ distance of the object. +SwRect GetBoundRectOfAnchoredObj( const SdrObject* pObj ) +{ + SwRect aRet( pObj->GetCurrentBoundRect() ); + // #i68520# - call cache of <SwAnchoredObject> + SwContact* pContact( GetUserCall( pObj ) ); + if ( pContact ) + { + const SwAnchoredObject* pAnchoredObj( pContact->GetAnchoredObj( pObj ) ); + if ( pAnchoredObj ) + { + aRet = pAnchoredObj->GetObjRectWithSpaces(); + } + } + return aRet; +} + +/// Returns the UserCall if applicable from the group object +SwContact* GetUserCall( const SdrObject* pObj ) +{ + SdrObject *pTmp; + while ( !pObj->GetUserCall() && nullptr != (pTmp = pObj->getParentSdrObjectFromSdrObject()) ) + pObj = pTmp; + assert((!pObj->GetUserCall() || nullptr != dynamic_cast<const SwContact*>(pObj->GetUserCall())) && + "<::GetUserCall(..)> - wrong type of found object user call." ); + return static_cast<SwContact*>(pObj->GetUserCall()); +} + +/// Returns true if the SrdObject is a Marquee-Object (scrolling text) +bool IsMarqueeTextObj( const SdrObject& rObj ) +{ + SdrTextAniKind eTKind; + return SdrInventor::Default == rObj.GetObjInventor() && + OBJ_TEXT == rObj.GetObjIdentifier() && + ( SdrTextAniKind::Scroll == ( eTKind = static_cast<const SdrTextObj&>(rObj).GetTextAniKind()) + || SdrTextAniKind::Alternate == eTKind || SdrTextAniKind::Slide == eTKind ); +} + +SwContact::SwContact( SwFrameFormat *pToRegisterIn ) : + SwClient( pToRegisterIn ), + mbInDTOR( false ) +{} + +SwContact::~SwContact() +{ + SetInDTOR(); +} + + +void SwContact::SetInDTOR() +{ + mbInDTOR = true; +} + +/// method to move drawing object to corresponding visible layer +void SwContact::MoveObjToVisibleLayer( SdrObject* _pDrawObj ) +{ + // #i46297# - notify background about the arriving of + // the object and invalidate its position. + const bool bNotify( !GetFormat()->getIDocumentDrawModelAccess().IsVisibleLayerId( _pDrawObj->GetLayer() ) ); + + MoveObjToLayer( true, _pDrawObj ); + + // #i46297# + if ( bNotify ) + { + SwAnchoredObject* pAnchoredObj = GetAnchoredObj( _pDrawObj ); + assert(pAnchoredObj); + ::setContextWritingMode( _pDrawObj, pAnchoredObj->GetAnchorFrameContainingAnchPos() ); + // Note: as-character anchored objects aren't registered at a page frame and + // a notification of its background isn't needed. + if ( pAnchoredObj->GetPageFrame() ) + { + ::Notify_Background( _pDrawObj, pAnchoredObj->GetPageFrame(), + pAnchoredObj->GetObjRect(), PrepareHint::FlyFrameArrive, true ); + } + + pAnchoredObj->InvalidateObjPos(); + } +} + +/// method to move drawing object to corresponding invisible layer - #i18447# +void SwContact::MoveObjToInvisibleLayer( SdrObject* _pDrawObj ) +{ + // #i46297# - notify background about the leaving of the object. + const bool bNotify( GetFormat()->getIDocumentDrawModelAccess().IsVisibleLayerId( _pDrawObj->GetLayer() ) ); + + MoveObjToLayer( false, _pDrawObj ); + + // #i46297# + if ( bNotify ) + { + SwAnchoredObject* pAnchoredObj = GetAnchoredObj( _pDrawObj ); + assert(pAnchoredObj); + // Note: as-character anchored objects aren't registered at a page frame and + // a notification of its background isn't needed. + if (pAnchoredObj->GetPageFrame()) + { + ::Notify_Background( _pDrawObj, pAnchoredObj->GetPageFrame(), + pAnchoredObj->GetObjRect(), PrepareHint::FlyFrameLeave, true ); + } + } +} + +/** method to move object to visible/invisible layer - #i18447# + + implementation for the public method <MoveObjToVisibleLayer(..)> + and <MoveObjToInvisibleLayer(..)> +*/ +void SwContact::MoveObjToLayer( const bool _bToVisible, + SdrObject* _pDrawObj ) +{ + if ( !_pDrawObj ) + { + OSL_FAIL( "SwDrawContact::MoveObjToLayer(..) - no drawing object!" ); + return; + } + + if ( !GetRegisteredIn() ) + { + OSL_FAIL( "SwDrawContact::MoveObjToLayer(..) - no drawing frame format!" ); + return; + } + + const IDocumentDrawModelAccess& rIDDMA = static_cast<SwFrameFormat*>(GetRegisteredInNonConst())->getIDocumentDrawModelAccess(); + + SdrLayerID nToHellLayerId = + _bToVisible ? rIDDMA.GetHellId() : rIDDMA.GetInvisibleHellId(); + SdrLayerID nToHeavenLayerId = + _bToVisible ? rIDDMA.GetHeavenId() : rIDDMA.GetInvisibleHeavenId(); + SdrLayerID nToControlLayerId = + _bToVisible ? rIDDMA.GetControlsId() : rIDDMA.GetInvisibleControlsId(); + SdrLayerID nFromHellLayerId = + _bToVisible ? rIDDMA.GetInvisibleHellId() : rIDDMA.GetHellId(); + SdrLayerID nFromHeavenLayerId = + _bToVisible ? rIDDMA.GetInvisibleHeavenId() : rIDDMA.GetHeavenId(); + SdrLayerID nFromControlLayerId = + _bToVisible ? rIDDMA.GetInvisibleControlsId() : rIDDMA.GetControlsId(); + + if ( dynamic_cast<const SdrObjGroup*>( _pDrawObj) != nullptr ) + { + // determine layer for group object + { + // proposed layer of a group object is the hell layer + SdrLayerID nNewLayerId = nToHellLayerId; + if ( ::CheckControlLayer( _pDrawObj ) ) + { + // it has to be the control layer, if one of the member + // is a control + nNewLayerId = nToControlLayerId; + } + else if ( _pDrawObj->GetLayer() == rIDDMA.GetHeavenId() || + _pDrawObj->GetLayer() == rIDDMA.GetInvisibleHeavenId() ) + { + // it has to be the heaven layer, if method <GetLayer()> reveals + // a heaven layer + nNewLayerId = nToHeavenLayerId; + } + // set layer at group object, but do *not* broadcast and + // no propagation to the members. + // Thus, call <NbcSetLayer(..)> at super class + _pDrawObj->SdrObject::NbcSetLayer( nNewLayerId ); + } + + // call method recursively for group object members + const SdrObjList* pLst = + static_cast<SdrObjGroup*>(_pDrawObj)->GetSubList(); + if ( pLst ) + { + for ( size_t i = 0; i < pLst->GetObjCount(); ++i ) + { + MoveObjToLayer( _bToVisible, pLst->GetObj( i ) ); + } + } + } + else + { + const SdrLayerID nLayerIdOfObj = _pDrawObj->GetLayer(); + if ( nLayerIdOfObj == nFromHellLayerId ) + { + _pDrawObj->SetLayer( nToHellLayerId ); + } + else if ( nLayerIdOfObj == nFromHeavenLayerId ) + { + _pDrawObj->SetLayer( nToHeavenLayerId ); + } + else if ( nLayerIdOfObj == nFromControlLayerId ) + { + _pDrawObj->SetLayer( nToControlLayerId ); + } + } +} + +/// get minimum order number of anchored objects handled by with contact +sal_uInt32 SwContact::GetMinOrdNum() const +{ + sal_uInt32 nMinOrdNum( SAL_MAX_UINT32 ); + + std::vector< SwAnchoredObject* > aObjs; + GetAnchoredObjs( aObjs ); + + while ( !aObjs.empty() ) + { + sal_uInt32 nTmpOrdNum = aObjs.back()->GetDrawObj()->GetOrdNum(); + + if ( nTmpOrdNum < nMinOrdNum ) + { + nMinOrdNum = nTmpOrdNum; + } + + aObjs.pop_back(); + } + + OSL_ENSURE( nMinOrdNum != SAL_MAX_UINT32, + "<SwContact::GetMinOrdNum()> - no order number found." ); + return nMinOrdNum; +} + +/// get maximum order number of anchored objects handled by with contact +sal_uInt32 SwContact::GetMaxOrdNum() const +{ + sal_uInt32 nMaxOrdNum( 0 ); + + std::vector< SwAnchoredObject* > aObjs; + GetAnchoredObjs( aObjs ); + + while ( !aObjs.empty() ) + { + sal_uInt32 nTmpOrdNum = aObjs.back()->GetDrawObj()->GetOrdNum(); + + if ( nTmpOrdNum > nMaxOrdNum ) + { + nMaxOrdNum = nTmpOrdNum; + } + + aObjs.pop_back(); + } + + return nMaxOrdNum; +} + +namespace +{ + Point lcl_GetWW8Pos(SwAnchoredObject const * pAnchoredObj, const bool bFollowTextFlow, sw::WW8AnchorConv& reConv) + { + switch(reConv) + { + case sw::WW8AnchorConv::CONV2PG: + { + bool bRelToTableCell(false); + Point aPos(pAnchoredObj->GetRelPosToPageFrame(bFollowTextFlow, bRelToTableCell)); + if(bRelToTableCell) + reConv = sw::WW8AnchorConv::RELTOTABLECELL; + return aPos; + } + case sw::WW8AnchorConv::CONV2COL_OR_PARA: + return pAnchoredObj->GetRelPosToAnchorFrame(); + case sw::WW8AnchorConv::CONV2CHAR: + return pAnchoredObj->GetRelPosToChar(); + case sw::WW8AnchorConv::CONV2LINE: + return pAnchoredObj->GetRelPosToLine(); + default: ; + } + return Point(); + } +} +void SwContact::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + // this does not call SwClient::SwClientNotify and thus doesn't handle RES_OBJECTDYING as usual. Is this intentional? + if (auto pFindSdrObjectHint = dynamic_cast<const sw::FindSdrObjectHint*>(&rHint)) + { + if(!pFindSdrObjectHint->m_rpObject) + pFindSdrObjectHint->m_rpObject = GetMaster(); + } + else if (auto pWW8AnchorConvHint = dynamic_cast<const sw::WW8AnchorConvHint*>(&rHint)) + { + // determine anchored object + SwAnchoredObject* pAnchoredObj(nullptr); + { + std::vector<SwAnchoredObject*> aAnchoredObjs; + GetAnchoredObjs(aAnchoredObjs); + if(!aAnchoredObjs.empty()) + pAnchoredObj = aAnchoredObjs.front(); + } + // no anchored object found. Thus, the needed layout information can't + // be determined. --> no conversion + if(!pAnchoredObj) + return; + // no conversion for anchored drawing object, which aren't attached to an + // anchor frame. + // This is the case for drawing objects, which are anchored inside a page + // header/footer of an *unused* page style. + if(dynamic_cast<SwAnchoredDrawObject*>(pAnchoredObj) && !pAnchoredObj->GetAnchorFrame()) + return; + const bool bFollowTextFlow = static_cast<const SwFrameFormat&>(rMod).GetFollowTextFlow().GetValue(); + sw::WW8AnchorConvResult& rResult(pWW8AnchorConvHint->m_rResult); + // No distinction between layout directions, because of missing + // information about WW8 in vertical layout. + rResult.m_aPos.setX(lcl_GetWW8Pos(pAnchoredObj, bFollowTextFlow, rResult.m_eHoriConv).getX()); + rResult.m_aPos.setY(lcl_GetWW8Pos(pAnchoredObj, bFollowTextFlow, rResult.m_eVertConv).getY()); + rResult.m_bConverted = true; + } +} + + +SwFlyDrawContact::SwFlyDrawContact( + SwFlyFrameFormat *pToRegisterIn, + SdrModel& rTargetModel) +: SwContact(pToRegisterIn), + mpMasterObj(new SwFlyDrawObj(rTargetModel)) +{ + // #i26791# - class <SwFlyDrawContact> contains the 'master' + // drawing object of type <SwFlyDrawObj> on its own. + mpMasterObj->SetOrdNum( 0xFFFFFFFE ); + mpMasterObj->SetUserCall( this ); +} + +SwFlyDrawContact::~SwFlyDrawContact() +{ + if ( mpMasterObj ) + { + mpMasterObj->SetUserCall( nullptr ); + if ( mpMasterObj->getSdrPageFromSdrObject() ) + mpMasterObj->getSdrPageFromSdrObject()->RemoveObject( mpMasterObj->GetOrdNum() ); + } +} + +sal_uInt32 SwFlyDrawContact::GetOrdNumForNewRef(const SwFlyFrame* pFly) +{ + // search for another Writer fly frame registered at same frame format + SwIterator<SwFlyFrame,SwFormat> aIter(*GetFormat()); + const SwFlyFrame* pFlyFrame(nullptr); + for(pFlyFrame = aIter.First(); pFlyFrame; pFlyFrame = aIter.Next()) + { + if(pFlyFrame != pFly) + break; + } + + if(pFlyFrame) + { + // another Writer fly frame found. Take its order number + return pFlyFrame->GetVirtDrawObj()->GetOrdNum(); + } + // no other Writer fly frame found. Take order number of 'master' object + // #i35748# - use method <GetOrdNumDirect()> instead + // of method <GetOrdNum()> to avoid a recalculation of the order number, + // which isn't intended. + return GetMaster()->GetOrdNumDirect(); +} + +SwVirtFlyDrawObj* SwFlyDrawContact::CreateNewRef(SwFlyFrame* pFly, SwFlyFrameFormat* pFormat) +{ + // Find ContactObject from the Format. If there's already one, we just + // need to create a new Ref, else we create the Contact now. + + IDocumentDrawModelAccess& rIDDMA = pFormat->getIDocumentDrawModelAccess(); + SwFlyDrawContact* pContact = pFormat->GetOrCreateContact(); + SwVirtFlyDrawObj* pDrawObj( + new SwVirtFlyDrawObj( + pContact->GetMaster()->getSdrModelFromSdrObject(), + *pContact->GetMaster(), + pFly)); + pDrawObj->SetUserCall(pContact); + + // The Reader creates the Masters and inserts them into the Page in + // order to transport the z-order. + // After creating the first Reference the Masters are removed from the + // List and are not important anymore. + SdrPage* pPg(nullptr); + if(nullptr != (pPg = pContact->GetMaster()->getSdrPageFromSdrObject())) + { + const size_t nOrdNum = pContact->GetMaster()->GetOrdNum(); + pPg->ReplaceObject(pDrawObj, nOrdNum); + } + // #i27030# - insert new <SwVirtFlyDrawObj> instance + // into drawing page with correct order number + else + rIDDMA.GetDrawModel()->GetPage(0)->InsertObject(pDrawObj, pContact->GetOrdNumForNewRef(pFly)); + // #i38889# - assure, that new <SwVirtFlyDrawObj> instance + // is in a visible layer. + pContact->MoveObjToVisibleLayer(pDrawObj); + return pDrawObj; +} + +// #i26791# +const SwAnchoredObject* SwFlyDrawContact::GetAnchoredObj(const SdrObject* pSdrObj) const +{ + assert(pSdrObj); + assert(dynamic_cast<const SwVirtFlyDrawObj*>(pSdrObj) != nullptr); + assert(GetUserCall(pSdrObj) == this && + "<SwFlyDrawContact::GetAnchoredObj(..)> - provided object doesn't belong to this contact"); + + const SwAnchoredObject *const pRetAnchoredObj = + static_cast<const SwVirtFlyDrawObj*>(pSdrObj)->GetFlyFrame(); + + return pRetAnchoredObj; +} + +SwAnchoredObject* SwFlyDrawContact::GetAnchoredObj(SdrObject *const pSdrObj) +{ + return const_cast<SwAnchoredObject *>(const_cast<SwFlyDrawContact const*>(this)->GetAnchoredObj(pSdrObj)); +} + +SdrObject* SwFlyDrawContact::GetMaster() +{ + return mpMasterObj.get(); +} + +/** + * @note Overriding method to control Writer fly frames, which are linked, and + * to assure that all objects anchored at/inside the Writer fly frame are + * also made visible. + */ +void SwFlyDrawContact::MoveObjToVisibleLayer( SdrObject* _pDrawObj ) +{ + assert(dynamic_cast<const SwVirtFlyDrawObj*>(_pDrawObj) != nullptr); + + if ( GetFormat()->getIDocumentDrawModelAccess().IsVisibleLayerId( _pDrawObj->GetLayer() ) ) + { + // nothing to do + return; + } + + SwFlyFrame* pFlyFrame = static_cast<SwVirtFlyDrawObj*>(_pDrawObj)->GetFlyFrame(); + + // #i44464# - consider, that Writer fly frame content + // already exists - (e.g. WW8 document is inserted into an existing document). + if ( !pFlyFrame->Lower() ) + { + pFlyFrame->InsertColumns(); + pFlyFrame->Chain( pFlyFrame->AnchorFrame() ); + pFlyFrame->InsertCnt(); + } + if ( pFlyFrame->GetDrawObjs() ) + { + for (SwAnchoredObject* i : *pFlyFrame->GetDrawObjs()) + { + // #i28701# - consider type of objects in sorted object list. + SdrObject* pObj = i->DrawObj(); + SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall()); + pContact->MoveObjToVisibleLayer( pObj ); + } + } + + // make fly frame visible + SwContact::MoveObjToVisibleLayer( _pDrawObj ); +} + +/** + * @note Override method to control Writer fly frames, which are linked, and + * to assure that all objects anchored at/inside the Writer fly frame are + * also made invisible. + */ +void SwFlyDrawContact::MoveObjToInvisibleLayer( SdrObject* _pDrawObj ) +{ + assert(dynamic_cast<const SwVirtFlyDrawObj*>(_pDrawObj) != nullptr); + + if ( !GetFormat()->getIDocumentDrawModelAccess().IsVisibleLayerId( _pDrawObj->GetLayer() ) ) + { + // nothing to do + return; + } + + SwFlyFrame* pFlyFrame = static_cast<SwVirtFlyDrawObj*>(_pDrawObj)->GetFlyFrame(); + + pFlyFrame->Unchain(); + pFlyFrame->DeleteCnt(); + if ( pFlyFrame->GetDrawObjs() ) + { + for (SwAnchoredObject* i : *pFlyFrame->GetDrawObjs()) + { + // #i28701# - consider type of objects in sorted object list. + SdrObject* pObj = i->DrawObj(); + SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall()); + pContact->MoveObjToInvisibleLayer( pObj ); + } + } + + // make fly frame invisible + SwContact::MoveObjToInvisibleLayer( _pDrawObj ); +} + +/// get data collection of anchored objects, handled by with contact +void SwFlyDrawContact::GetAnchoredObjs( std::vector<SwAnchoredObject*>& _roAnchoredObjs ) const +{ + const SwFrameFormat* pFormat = GetFormat(); + SwFlyFrame::GetAnchoredObjects( _roAnchoredObjs, *pFormat ); +} +void SwFlyDrawContact::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + SwContact::SwClientNotify(rMod, rHint); + if(auto pGetZOrdnerHint = dynamic_cast<const sw::GetZOrderHint*>(&rHint)) + { + // #i11176# + // This also needs to work when no layout exists. Thus, for + // FlyFrames an alternative method is used now in that case. + auto pFormat(dynamic_cast<const SwFrameFormat*>(&rMod)); + if (pFormat && pFormat->Which() == RES_FLYFRMFMT && !pFormat->getIDocumentLayoutAccess().GetCurrentViewShell()) + pGetZOrdnerHint->m_rnZOrder = GetMaster()->GetOrdNum(); + } +} + +// SwDrawContact + +bool CheckControlLayer( const SdrObject *pObj ) +{ + if ( SdrInventor::FmForm == pObj->GetObjInventor() ) + return true; + if (const SdrObjGroup *pObjGroup = dynamic_cast<const SdrObjGroup*>(pObj)) + { + const SdrObjList *pLst = pObjGroup->GetSubList(); + for ( size_t i = 0; i < pLst->GetObjCount(); ++i ) + { + if ( ::CheckControlLayer( pLst->GetObj( i ) ) ) + { + // #i18447# - return correct value ;-) + return true; + } + } + } + return false; +} + +SwDrawContact::SwDrawContact( SwFrameFormat* pToRegisterIn, SdrObject* pObj ) : + SwContact( pToRegisterIn ), + maAnchoredDrawObj(), + mbMasterObjCleared( false ), + mbDisconnectInProgress( false ), + mbUserCallActive( false ), + // Note: value of <meEventTypeOfCurrentUserCall> isn't of relevance, because + // <mbUserCallActive> is false. + meEventTypeOfCurrentUserCall( SdrUserCallType::MoveOnly ) +{ + // --> #i33909# - assure, that drawing object is inserted + // in the drawing page. + if ( !pObj->IsInserted() ) + { + pToRegisterIn->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0)-> + InsertObject( pObj, pObj->GetOrdNumDirect() ); + } + + // Controls have to be always in the Control-Layer. This is also true for + // group objects, if they contain controls. + if ( ::CheckControlLayer( pObj ) ) + { + // set layer of object to corresponding invisible layer. + pObj->SetLayer( pToRegisterIn->getIDocumentDrawModelAccess().GetInvisibleControlsId() ); + } + + // #i26791# + pObj->SetUserCall( this ); + maAnchoredDrawObj.SetDrawObj( *pObj ); + + // if there already exists an SwXShape for the object, ensure it knows about us, and the SdrObject + // #i99056# + SwXShape::AddExistingShapeToFormat( *pObj ); +} + +SwDrawContact::~SwDrawContact() +{ + SetInDTOR(); + + DisconnectFromLayout(); + + // remove 'master' from drawing page + RemoveMasterFromDrawPage(); + + // remove and destroy 'virtual' drawing objects. + RemoveAllVirtObjs(); + + if ( !mbMasterObjCleared ) + { + SdrObject* pObject = const_cast< SdrObject* >( maAnchoredDrawObj.GetDrawObj() ); + SdrObject::Free( pObject ); + } +} + +void SwDrawContact::GetTextObjectsFromFormat(std::list<SdrTextObj*>& o_rTextObjects, SwDoc* pDoc) +{ + for(auto& rpFly : *pDoc->GetSpzFrameFormats()) + { + if(dynamic_cast<const SwDrawFrameFormat*>(rpFly)) + rpFly->CallSwClientNotify(sw::CollectTextObjectsHint(o_rTextObjects)); + } +} + +// #i26791# +const SwAnchoredObject* SwDrawContact::GetAnchoredObj(const SdrObject* pSdrObj ) const +{ + // handle default parameter value + if (!pSdrObj) + { + pSdrObj = GetMaster(); + } + + assert(pSdrObj); + assert(dynamic_cast<const SwDrawVirtObj*>(pSdrObj) != nullptr || + dynamic_cast<const SdrVirtObj*>(pSdrObj) == nullptr); + assert((GetUserCall(pSdrObj) == this || + pSdrObj == GetMaster()) && + "<SwDrawContact::GetAnchoredObj(..)> - provided object doesn't belongs to this contact" ); + + const SwAnchoredObject* pRetAnchoredObj = nullptr; + + if (auto pVirtObj = dynamic_cast<const SwDrawVirtObj*>(pSdrObj)) + { + pRetAnchoredObj = &(pVirtObj->GetAnchoredObj()); + } + else + { + assert(dynamic_cast<const SdrVirtObj*>(pSdrObj) == nullptr); + pRetAnchoredObj = &maAnchoredDrawObj; + } + + return pRetAnchoredObj; +} + +SwAnchoredObject* SwDrawContact::GetAnchoredObj(SdrObject *const pSdrObj) +{ + return const_cast<SwAnchoredObject*>(const_cast<SwDrawContact const*>(this)->GetAnchoredObj(pSdrObj)); +} + +SdrObject* SwDrawContact::GetMaster() +{ + return !mbMasterObjCleared + ? maAnchoredDrawObj.DrawObj() + : nullptr; +} + +const SwFrame* SwDrawContact::GetAnchorFrame( const SdrObject* _pDrawObj ) const +{ + const SwFrame* pAnchorFrame = nullptr; + if ( !_pDrawObj || + _pDrawObj == GetMaster() || + ( !_pDrawObj->GetUserCall() && + GetUserCall( _pDrawObj ) == this ) ) + { + pAnchorFrame = maAnchoredDrawObj.GetAnchorFrame(); + } + else + { + assert(dynamic_cast<SwDrawVirtObj const*>(_pDrawObj) != nullptr); + pAnchorFrame = static_cast<const SwDrawVirtObj*>(_pDrawObj)->GetAnchorFrame(); + } + + return pAnchorFrame; +} + +SwFrame* SwDrawContact::GetAnchorFrame(SdrObject const *const pDrawObj) +{ + return const_cast<SwFrame *>(const_cast<SwDrawContact const*>(this)->GetAnchorFrame(pDrawObj)); +} + +/** add a 'virtual' drawing object to drawing page. + */ +SwDrawVirtObj* SwDrawContact::AddVirtObj() +{ + maDrawVirtObjs.push_back( + SwDrawVirtObjPtr( + new SwDrawVirtObj( + GetMaster()->getSdrModelFromSdrObject(), + *GetMaster(), + *this))); + maDrawVirtObjs.back()->AddToDrawingPage(); + return maDrawVirtObjs.back().get(); +} + +/// remove 'virtual' drawing objects and destroy them. +void SwDrawContact::RemoveAllVirtObjs() +{ + for(auto& rpDrawVirtObj : maDrawVirtObjs) + { + // remove and destroy 'virtual object' + rpDrawVirtObj->RemoveFromWriterLayout(); + rpDrawVirtObj->RemoveFromDrawingPage(); + } + maDrawVirtObjs.clear(); +} + + +/// get drawing object ('master' or 'virtual') by frame. +SdrObject* SwDrawContact::GetDrawObjectByAnchorFrame( const SwFrame& _rAnchorFrame ) +{ + SdrObject* pRetDrawObj = nullptr; + + // #i26791# - compare master frames instead of direct frames + const SwFrame* pProposedAnchorFrame = &_rAnchorFrame; + if ( pProposedAnchorFrame->IsContentFrame() ) + { + const SwContentFrame* pTmpFrame = + static_cast<const SwContentFrame*>( pProposedAnchorFrame ); + while ( pTmpFrame->IsFollow() ) + { + pTmpFrame = pTmpFrame->FindMaster(); + } + pProposedAnchorFrame = pTmpFrame; + } + + const SwFrame* pMasterObjAnchorFrame = GetAnchorFrame(); + if ( pMasterObjAnchorFrame && pMasterObjAnchorFrame->IsContentFrame() ) + { + const SwContentFrame* pTmpFrame = + static_cast<const SwContentFrame*>( pMasterObjAnchorFrame ); + while ( pTmpFrame->IsFollow() ) + { + pTmpFrame = pTmpFrame->FindMaster(); + } + pMasterObjAnchorFrame = pTmpFrame; + } + + if ( pMasterObjAnchorFrame && pMasterObjAnchorFrame == pProposedAnchorFrame ) + { + pRetDrawObj = GetMaster(); + } + else + { + const auto ppFoundVirtObj(std::find_if(maDrawVirtObjs.begin(), maDrawVirtObjs.end(), + VirtObjAnchoredAtFramePred(pProposedAnchorFrame))); + if(ppFoundVirtObj != maDrawVirtObjs.end()) + pRetDrawObj = ppFoundVirtObj->get(); + } + + return pRetDrawObj; +} + +void SwDrawContact::NotifyBackgrdOfAllVirtObjs(const tools::Rectangle* pOldBoundRect) +{ + for(const auto& rpDrawVirtObj : maDrawVirtObjs) + { + SwDrawVirtObj* pDrawVirtObj(rpDrawVirtObj.get()); + if ( pDrawVirtObj->GetAnchorFrame() ) + { + // #i34640# - determine correct page frame + SwPageFrame* pPage = pDrawVirtObj->AnchoredObj().FindPageFrameOfAnchor(); + if( pOldBoundRect && pPage ) + { + SwRect aOldRect( *pOldBoundRect ); + aOldRect.Pos() += pDrawVirtObj->GetOffset(); + if( aOldRect.HasArea() ) + ::Notify_Background( pDrawVirtObj, pPage, + aOldRect, PrepareHint::FlyFrameLeave,true); + } + // #i34640# - include spacing for wrapping + SwRect aRect( pDrawVirtObj->GetAnchoredObj().GetObjRectWithSpaces() ); + if (aRect.HasArea() && pPage) + { + SwPageFrame* pPg = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(::FindPage( aRect, pPage ))); + if ( pPg ) + ::Notify_Background( pDrawVirtObj, pPg, aRect, + PrepareHint::FlyFrameArrive, true ); + } + ::ClrContourCache( pDrawVirtObj ); + } + } +} + +/// local method to notify the background for a drawing object - #i26791# +static void lcl_NotifyBackgroundOfObj( SwDrawContact const & _rDrawContact, + const SdrObject& _rObj, + const tools::Rectangle* _pOldObjRect ) +{ + // #i34640# + SwAnchoredObject* pAnchoredObj = + const_cast<SwAnchoredObject*>(_rDrawContact.GetAnchoredObj( &_rObj )); + if ( pAnchoredObj && pAnchoredObj->GetAnchorFrame() ) + { + // #i34640# - determine correct page frame + SwPageFrame* pPageFrame = pAnchoredObj->FindPageFrameOfAnchor(); + if( _pOldObjRect && pPageFrame ) + { + SwRect aOldRect( *_pOldObjRect ); + if( aOldRect.HasArea() ) + { + // #i34640# - determine correct page frame + SwPageFrame* pOldPageFrame = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(::FindPage( aOldRect, pPageFrame ))); + ::Notify_Background( &_rObj, pOldPageFrame, aOldRect, + PrepareHint::FlyFrameLeave, true); + } + } + // #i34640# - include spacing for wrapping + SwRect aNewRect( pAnchoredObj->GetObjRectWithSpaces() ); + if( aNewRect.HasArea() && pPageFrame ) + { + pPageFrame = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(::FindPage( aNewRect, pPageFrame ))); + ::Notify_Background( &_rObj, pPageFrame, aNewRect, + PrepareHint::FlyFrameArrive, true ); + } + ClrContourCache( &_rObj ); + } +} + +void SwDrawContact::Changed( const SdrObject& rObj, + SdrUserCallType eType, + const tools::Rectangle& rOldBoundRect ) +{ + // #i26791# - no event handling, if existing <SwViewShell> + // is in construction + SwDoc* pDoc = GetFormat()->GetDoc(); + if ( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && + pDoc->getIDocumentLayoutAccess().GetCurrentViewShell()->IsInConstructor() ) + { + return; + } + + // #i44339# + // no event handling, if document is in destruction. + // Exception: It's the SdrUserCallType::Delete event + if ( pDoc->IsInDtor() && eType != SdrUserCallType::Delete ) + { + return; + } + + //Put on Action, but not if presently anywhere an action runs. + bool bHasActions(true); + SwRootFrame *pTmpRoot = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if ( pTmpRoot && pTmpRoot->IsCallbackActionEnabled() ) + { + SwViewShell* const pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(SwViewShell& rShell : pSh->GetRingContainer() ) + { + if ( rShell.Imp()->IsAction() || rShell.Imp()->IsIdleAction() ) + { + bHasActions = true; + break; + } + bHasActions = false; + } + } + if(!bHasActions) + pTmpRoot->StartAllAction(); + } + SdrObjUserCall::Changed( rObj, eType, rOldBoundRect ); + Changed_( rObj, eType, &rOldBoundRect ); //Attention, possibly suicidal! + + if(!bHasActions) + pTmpRoot->EndAllAction(); +} + +/// helper class for method <SwDrawContact::Changed_(..)> for handling nested +/// <SdrObjUserCall> events +class NestedUserCallHdl +{ + private: + SwDrawContact* mpDrawContact; + bool mbParentUserCallActive; + SdrUserCallType meParentUserCallEventType; + + public: + NestedUserCallHdl( SwDrawContact* _pDrawContact, + SdrUserCallType _eEventType ) + : mpDrawContact( _pDrawContact ), + mbParentUserCallActive( _pDrawContact->mbUserCallActive ), + meParentUserCallEventType( _pDrawContact->meEventTypeOfCurrentUserCall ) + { + mpDrawContact->mbUserCallActive = true; + mpDrawContact->meEventTypeOfCurrentUserCall = _eEventType; + } + + ~NestedUserCallHdl() + { + if ( mpDrawContact ) + { + mpDrawContact->mbUserCallActive = mbParentUserCallActive; + mpDrawContact->meEventTypeOfCurrentUserCall = meParentUserCallEventType; + } + } + + void DrawContactDeleted() + { + mpDrawContact = nullptr; + } + + bool IsNestedUserCall() const + { + return mbParentUserCallActive; + } + + void AssertNestedUserCall() + { + if ( IsNestedUserCall() ) + { + bool bTmpAssert( true ); + // Currently its known, that a nested event SdrUserCallType::Resize + // could occur during parent user call SdrUserCallType::Inserted, + // SdrUserCallType::Delete and SdrUserCallType::Resize for edge objects. + // Also possible are nested SdrUserCallType::ChildResize events for + // edge objects + // Thus, assert all other combinations + if ( ( meParentUserCallEventType == SdrUserCallType::Inserted || + meParentUserCallEventType == SdrUserCallType::Delete || + meParentUserCallEventType == SdrUserCallType::Resize ) && + mpDrawContact->meEventTypeOfCurrentUserCall == SdrUserCallType::Resize ) + { + bTmpAssert = false; + } + else if ( meParentUserCallEventType == SdrUserCallType::ChildResize && + mpDrawContact->meEventTypeOfCurrentUserCall == SdrUserCallType::ChildResize ) + { + bTmpAssert = false; + } + + if ( bTmpAssert ) + { + OSL_FAIL( "<SwDrawContact::Changed_(..)> - unknown nested <UserCall> event. This is serious." ); + } + } + } +}; + +/// Notify the format's textbox that it should reconsider its position / size. +static void lcl_textBoxSizeNotify(SwFrameFormat* pFormat) +{ + if (SwTextBoxHelper::isTextBox(pFormat, RES_DRAWFRMFMT)) + { + // Just notify the textbox that the size has changed, the actual object size is not interesting. + SfxItemSet aResizeSet(pFormat->GetDoc()->GetAttrPool(), svl::Items<RES_FRM_SIZE, RES_FRM_SIZE>{}); + SwFormatFrameSize aSize; + aResizeSet.Put(aSize); + SwTextBoxHelper::syncFlyFrameAttr(*pFormat, aResizeSet); + } +} + +// !!!ATTENTION!!! The object may commit suicide!!! + +void SwDrawContact::Changed_( const SdrObject& rObj, + SdrUserCallType eType, + const tools::Rectangle* pOldBoundRect ) +{ + // suppress handling of nested <SdrObjUserCall> events + NestedUserCallHdl aNestedUserCallHdl( this, eType ); + if ( aNestedUserCallHdl.IsNestedUserCall() ) + { + aNestedUserCallHdl.AssertNestedUserCall(); + return; + } + // do *not* notify, if document is destructing + // #i35912# - do *not* notify for as-character anchored + // drawing objects. + // #i35007# + // improvement: determine as-character anchored object flag only once. + const bool bAnchoredAsChar = ObjAnchoredAsChar(); + const bool bNotify = !(GetFormat()->GetDoc()->IsInDtor()) && + ( css::text::WrapTextMode_THROUGH != GetFormat()->GetSurround().GetSurround() ) && + !bAnchoredAsChar; + switch( eType ) + { + case SdrUserCallType::Delete: + { + if ( bNotify ) + { + lcl_NotifyBackgroundOfObj( *this, rObj, pOldBoundRect ); + // --> #i36181# - background of 'virtual' + // drawing objects have also been notified. + NotifyBackgrdOfAllVirtObjs( pOldBoundRect ); + } + DisconnectFromLayout( false ); + mbMasterObjCleared = true; + delete this; + // --> #i65784# Prevent memory corruption + aNestedUserCallHdl.DrawContactDeleted(); + break; + } + case SdrUserCallType::Inserted: + { + if ( mbDisconnectInProgress ) + { + OSL_FAIL( "<SwDrawContact::Changed_(..)> - Insert event during disconnection from layout is invalid." ); + } + else + { + ConnectToLayout(); + if ( bNotify ) + { + lcl_NotifyBackgroundOfObj( *this, rObj, pOldBoundRect ); + } + } + break; + } + case SdrUserCallType::Removed: + { + if ( bNotify ) + { + lcl_NotifyBackgroundOfObj( *this, rObj, pOldBoundRect ); + } + DisconnectFromLayout( false ); + break; + } + case SdrUserCallType::ChildInserted : + case SdrUserCallType::ChildRemoved : + { + // --> #i113730# + // force layer of controls for group objects containing control objects + if(dynamic_cast< SdrObjGroup* >(maAnchoredDrawObj.DrawObj())) + { + if(::CheckControlLayer(maAnchoredDrawObj.DrawObj())) + { + const IDocumentDrawModelAccess& rIDDMA = static_cast<SwFrameFormat*>(GetRegisteredInNonConst())->getIDocumentDrawModelAccess(); + const SdrLayerID aCurrentLayer(maAnchoredDrawObj.DrawObj()->GetLayer()); + const SdrLayerID aControlLayerID(rIDDMA.GetControlsId()); + const SdrLayerID aInvisibleControlLayerID(rIDDMA.GetInvisibleControlsId()); + + if(aCurrentLayer != aControlLayerID && aCurrentLayer != aInvisibleControlLayerID) + { + if ( aCurrentLayer == rIDDMA.GetInvisibleHellId() || + aCurrentLayer == rIDDMA.GetInvisibleHeavenId() ) + { + maAnchoredDrawObj.DrawObj()->SetLayer(aInvisibleControlLayerID); + } + else + { + maAnchoredDrawObj.DrawObj()->SetLayer(aControlLayerID); + } + } + } + } + [[fallthrough]]; + } + case SdrUserCallType::MoveOnly: + case SdrUserCallType::Resize: + case SdrUserCallType::ChildMoveOnly : + case SdrUserCallType::ChildResize : + case SdrUserCallType::ChildChangeAttr : + case SdrUserCallType::ChildDelete : + { + // #i31698# - improvement + // get instance <SwAnchoredDrawObject> only once + const SwAnchoredDrawObject* pAnchoredDrawObj = + static_cast<const SwAnchoredDrawObject*>( GetAnchoredObj( &rObj ) ); + + /* protect against NULL pointer dereferencing */ + if(!pAnchoredDrawObj) + { + break; + } + + // #i26791# - adjust positioning and alignment attributes, + // if positioning of drawing object isn't in progress. + // #i53320# - no adjust of positioning attributes, + // if drawing object isn't positioned. + if ( !pAnchoredDrawObj->IsPositioningInProgress() && + !pAnchoredDrawObj->NotYetPositioned() ) + { + // #i34748# - If no last object rectangle is + // provided by the anchored object, use parameter <pOldBoundRect>. + const tools::Rectangle& aOldObjRect = pAnchoredDrawObj->GetLastObjRect() + ? *(pAnchoredDrawObj->GetLastObjRect()) + : *pOldBoundRect; + // #i79400# + // always invalidate object rectangle inclusive spaces + pAnchoredDrawObj->InvalidateObjRectWithSpaces(); + // #i41324# - notify background before + // adjusting position + if ( bNotify ) + { + // #i31573# - correction + // background of given drawing object. + lcl_NotifyBackgroundOfObj( *this, rObj, &aOldObjRect ); + } + // #i31698# - determine layout direction + // via draw frame format. + SwFrameFormat::tLayoutDir eLayoutDir = + pAnchoredDrawObj->GetFrameFormat().GetLayoutDir(); + // use geometry of drawing object + SwRect aObjRect( rObj.GetSnapRect() ); + // If drawing object is a member of a group, the adjustment + // of the positioning and the alignment attributes has to + // be done for the top group object. + if ( rObj.getParentSdrObjectFromSdrObject() ) + { + const SdrObject* pGroupObj = rObj.getParentSdrObjectFromSdrObject(); + while ( pGroupObj->getParentSdrObjectFromSdrObject() ) + { + pGroupObj = pGroupObj->getParentSdrObjectFromSdrObject(); + } + // use geometry of drawing object + aObjRect = pGroupObj->GetSnapRect(); + } + SwTwips nXPosDiff(0); + SwTwips nYPosDiff(0); + switch ( eLayoutDir ) + { + case SwFrameFormat::HORI_L2R: + { + nXPosDiff = aObjRect.Left() - aOldObjRect.Left(); + nYPosDiff = aObjRect.Top() - aOldObjRect.Top(); + } + break; + case SwFrameFormat::HORI_R2L: + { + nXPosDiff = aOldObjRect.Right() - aObjRect.Right(); + nYPosDiff = aObjRect.Top() - aOldObjRect.Top(); + } + break; + case SwFrameFormat::VERT_R2L: + { + nXPosDiff = aObjRect.Top() - aOldObjRect.Top(); + nYPosDiff = aOldObjRect.Right() - aObjRect.Right(); + } + break; + default: + { + assert(!"<SwDrawContact::Changed_(..)> - unsupported layout direction"); + } + } + SfxItemSet aSet( GetFormat()->GetDoc()->GetAttrPool(), + svl::Items<RES_VERT_ORIENT, RES_HORI_ORIENT>{} ); + const SwFormatVertOrient& rVert = GetFormat()->GetVertOrient(); + if ( nYPosDiff != 0 ) + { + + if ( rVert.GetRelationOrient() == text::RelOrientation::CHAR || + rVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + nYPosDiff = -nYPosDiff; + } + aSet.Put( SwFormatVertOrient( rVert.GetPos()+nYPosDiff, + text::VertOrientation::NONE, + rVert.GetRelationOrient() ) ); + } + + const SwFormatHoriOrient& rHori = GetFormat()->GetHoriOrient(); + if ( !bAnchoredAsChar && nXPosDiff != 0 ) + { + aSet.Put( SwFormatHoriOrient( rHori.GetPos()+nXPosDiff, + text::HoriOrientation::NONE, + rHori.GetRelationOrient() ) ); + } + + if ( nYPosDiff || + ( !bAnchoredAsChar && nXPosDiff != 0 ) ) + { + GetFormat()->GetDoc()->SetFlyFrameAttr( *(GetFormat()), aSet ); + // keep new object rectangle, to avoid multiple + // changes of the attributes by multiple event from + // the drawing layer - e.g. group objects and its members + // #i34748# - use new method + // <SwAnchoredDrawObject::SetLastObjRect(..)>. + const_cast<SwAnchoredDrawObject*>(pAnchoredDrawObj) + ->SetLastObjRect( aObjRect.SVRect() ); + } + else if ( aObjRect.SSize() != aOldObjRect.GetSize() ) + { + InvalidateObjs_(); + // #i35007# - notify anchor frame + // of as-character anchored object + if ( bAnchoredAsChar ) + { + SwFrame* pAnchorFrame = const_cast<SwAnchoredDrawObject*>(pAnchoredDrawObj)->AnchorFrame(); + if(pAnchorFrame) + { + pAnchorFrame->Prepare( PrepareHint::FlyFrameAttributesChanged, GetFormat() ); + } + } + + lcl_textBoxSizeNotify(GetFormat()); + } + else if (eType == SdrUserCallType::Resize) + // Even if the bounding box of the shape didn't change, + // notify about the size change, as an adjustment change + // may affect the size of the underlying textbox. + lcl_textBoxSizeNotify(GetFormat()); + } + } + break; + case SdrUserCallType::ChangeAttr: + if ( bNotify ) + { + lcl_NotifyBackgroundOfObj( *this, rObj, pOldBoundRect ); + } + break; + default: + break; + } +} + +namespace +{ + const SwFormatAnchor* lcl_getAnchorFormat( const SfxPoolItem& _rItem ) + { + sal_uInt16 nWhich = _rItem.Which(); + const SwFormatAnchor* pAnchorFormat = nullptr; + if ( RES_ATTRSET_CHG == nWhich ) + { + static_cast<const SwAttrSetChg&>(_rItem).GetChgSet()-> + GetItemState( RES_ANCHOR, false, reinterpret_cast<const SfxPoolItem**>(&pAnchorFormat) ); + } + else if ( RES_ANCHOR == nWhich ) + { + pAnchorFormat = &static_cast<const SwFormatAnchor&>(_rItem); + } + return pAnchorFormat; + } +} + +void SwDrawContact::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + SwClient::SwClientNotify(rMod, rHint); // needed as SwContact::SwClientNotify doesn't explicitly call SwClient::SwClientNotify + SwContact::SwClientNotify(rMod, rHint); + if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + SAL_WARN_IF(mbDisconnectInProgress, "sw.core", "<SwDrawContact::Modify(..)> called during disconnection."); + + const SfxPoolItem* pNew = pLegacyHint->m_pNew; + sal_uInt16 nWhich = pNew ? pNew->Which() : 0; + if(const SwFormatAnchor* pNewAnchorFormat = pNew ? lcl_getAnchorFormat(*pNew) : nullptr) + { + // Do not respond to a Reset Anchor! + if(GetFormat()->GetAttrSet().GetItemState(RES_ANCHOR, false) == SfxItemState::SET) + { + // no connect to layout during disconnection + if(!mbDisconnectInProgress) + { + // determine old object rectangle of 'master' drawing object + // for notification + const tools::Rectangle* pOldRect = nullptr; + tools::Rectangle aOldRect; + if(GetAnchorFrame()) + { + // --> #i36181# - include spacing in object + // rectangle for notification. + aOldRect = maAnchoredDrawObj.GetObjRectWithSpaces().SVRect(); + pOldRect = &aOldRect; + } + // re-connect to layout due to anchor format change + ConnectToLayout(pNewAnchorFormat); + // notify background of drawing objects + lcl_NotifyBackgroundOfObj(*this, *GetMaster(), pOldRect); + NotifyBackgrdOfAllVirtObjs(pOldRect); + + const SwFormatAnchor* pOldAnchorFormat = pLegacyHint->m_pOld ? lcl_getAnchorFormat(*pLegacyHint->m_pOld) : nullptr; + if(!pOldAnchorFormat || (pOldAnchorFormat->GetAnchorId() != pNewAnchorFormat->GetAnchorId())) + { + if(maAnchoredDrawObj.DrawObj()) + { + // --> #i102752# + // assure that a ShapePropertyChangeNotifier exists + maAnchoredDrawObj.DrawObj()->notifyShapePropertyChange(svx::ShapeProperty::TextDocAnchor); + } + else + SAL_WARN("sw.core", "SwDrawContact::Modify: no draw object here?"); + } + } + } + else + DisconnectFromLayout(); + } + else if (nWhich == RES_REMOVE_UNO_OBJECT) + {} // nothing to do + // --> #i62875# - no further notification, if not connected to Writer layout + else if ( maAnchoredDrawObj.GetAnchorFrame() && + maAnchoredDrawObj.GetDrawObj()->GetUserCall() ) + { + bool bUpdateSortedObjsList(false); + switch(nWhich) + { + case RES_UL_SPACE: + case RES_LR_SPACE: + case RES_HORI_ORIENT: + case RES_VERT_ORIENT: + case RES_FOLLOW_TEXT_FLOW: // #i28701# - add attribute 'Follow text flow' + break; + case RES_SURROUND: + case RES_OPAQUE: + case RES_WRAP_INFLUENCE_ON_OBJPOS: + // --> #i28701# - on change of wrapping style, hell|heaven layer, + // or wrapping style influence an update of the <SwSortedObjs> list, + // the drawing object is registered in, has to be performed. This is triggered + // by the 1st parameter of method call <InvalidateObjs_(..)>. + bUpdateSortedObjsList = true; + break; + case RES_ATTRSET_CHG: // #i35443# + { + auto pChgSet = static_cast<const SwAttrSetChg*>(pNew)->GetChgSet(); + if(pChgSet->GetItemState(RES_SURROUND, false) == SfxItemState::SET || + pChgSet->GetItemState(RES_OPAQUE, false) == SfxItemState::SET || + pChgSet->GetItemState(RES_WRAP_INFLUENCE_ON_OBJPOS, false) == SfxItemState::SET) + bUpdateSortedObjsList = true; + } + break; + default: + assert(!"<SwDraw Contact::Modify(..)> - unhandled attribute?"); + } + lcl_NotifyBackgroundOfObj(*this, *GetMaster(), nullptr); + NotifyBackgrdOfAllVirtObjs(nullptr); + InvalidateObjs_(bUpdateSortedObjsList); + } + + // #i51474# + GetAnchoredObj(nullptr)->ResetLayoutProcessBools(); + } + else if (auto pDrawFrameFormatHint = dynamic_cast<const sw::DrawFrameFormatHint*>(&rHint)) + { + switch(pDrawFrameFormatHint->m_eId) + { + case sw::DrawFrameFormatHintId::DYING: + delete this; + break; + case sw::DrawFrameFormatHintId::PREPPASTING: + MoveObjToVisibleLayer(GetMaster()); + break; + case sw::DrawFrameFormatHintId::PREP_INSERT_FLY: + InsertMasterIntoDrawPage(); + // #i40845# - follow-up of #i35635# + // move object to visible layer + MoveObjToVisibleLayer(GetMaster()); + // tdf#135661 InsertMasterIntoDrawPage may have created a new + // SwXShape with null m_pFormat; fix that + SwXShape::AddExistingShapeToFormat(*GetMaster()); + break; + case sw::DrawFrameFormatHintId::PREP_DELETE_FLY: + RemoveMasterFromDrawPage(); + break; + case sw::DrawFrameFormatHintId::PAGE_OUT_OF_BOUNDS: + case sw::DrawFrameFormatHintId::DELETE_FRAMES: + DisconnectFromLayout(); + break; + case sw::DrawFrameFormatHintId::MAKE_FRAMES: + ConnectToLayout(); + break; + case sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR: + GetAnchoredObj(GetMaster())->MakeObjPos(); + break; + default: + ; + } + } + else if (auto pCheckDrawFrameFormatLayerHint = dynamic_cast<const sw::CheckDrawFrameFormatLayerHint*>(&rHint)) + { + *(pCheckDrawFrameFormatLayerHint->m_bCheckControlLayer) |= (GetMaster() && CheckControlLayer(GetMaster())); + } + else if (auto pContactChangedHint = dynamic_cast<const sw::ContactChangedHint*>(&rHint)) + { + if(!*pContactChangedHint->m_ppObject) + *pContactChangedHint->m_ppObject = GetMaster(); + auto pObject = *pContactChangedHint->m_ppObject; + Changed(*pObject, SdrUserCallType::Delete, pObject->GetLastBoundRect()); + } + else if (auto pDrawFormatLayoutCopyHint = dynamic_cast<const sw::DrawFormatLayoutCopyHint*>(&rHint)) + { + const SwDrawFrameFormat& rFormat = static_cast<const SwDrawFrameFormat&>(rMod); + new SwDrawContact( + &pDrawFormatLayoutCopyHint->m_rDestFormat, + pDrawFormatLayoutCopyHint->m_rDestDoc.CloneSdrObj( + *GetMaster(), + pDrawFormatLayoutCopyHint->m_rDestDoc.IsCopyIsMove() && &pDrawFormatLayoutCopyHint->m_rDestDoc == rFormat.GetDoc())); + // #i49730# - notify draw frame format that position attributes are + // already set, if the position attributes are already set at the + // source draw frame format. + if(rFormat.IsPosAttrSet()) + pDrawFormatLayoutCopyHint->m_rDestFormat.PosAttrSet(); + } + else if (auto pRestoreFlyAnchorHint = dynamic_cast<const sw::RestoreFlyAnchorHint*>(&rHint)) + { + SdrObject* pObj = GetMaster(); + if(GetAnchorFrame() && !pObj->IsInserted()) + { + auto pDrawModel = const_cast<SwDrawFrameFormat&>(static_cast<const SwDrawFrameFormat&>(rMod)).GetDoc()->getIDocumentDrawModelAccess().GetDrawModel(); + assert(pDrawModel); + pDrawModel->GetPage(0)->InsertObject(pObj); + } + pObj->SetRelativePos(pRestoreFlyAnchorHint->m_aPos); + } + else if (auto pCreatePortionHint = dynamic_cast<const sw::CreatePortionHint*>(&rHint)) + { + if(*pCreatePortionHint->m_ppContact) + return; + *pCreatePortionHint->m_ppContact = this; // This is kind of ridiculous: the FrameFormat doesn't even hold a pointer to the contact itself, but here we are leaking it out randomly + if(!GetAnchorFrame()) + { + // No direct positioning needed any more + ConnectToLayout(); + // Move object to visible layer + MoveObjToVisibleLayer(GetMaster()); + } + } + else if (auto pCollectTextObjectsHint = dynamic_cast<const sw::CollectTextObjectsHint*>(&rHint)) + { + auto pSdrO = GetMaster(); + if(!pSdrO) + return; + if(dynamic_cast<const SdrObjGroup*>(pSdrO)) + { + SdrObjListIter aListIter(*pSdrO, SdrIterMode::DeepNoGroups); + //iterate inside of a grouped object + while(aListIter.IsMore()) + { + SdrObject* pSdrOElement = aListIter.Next(); + auto pTextObj = const_cast<SdrTextObj*>(dynamic_cast<const SdrTextObj*>(pSdrOElement)); + if(pTextObj && pTextObj->HasText()) + pCollectTextObjectsHint->m_rTextObjects.push_back(pTextObj); + } + } + else if(auto pTextObj = const_cast<SdrTextObj*>(dynamic_cast<const SdrTextObj*>(pSdrO))) + { + if(pTextObj->HasText()) + pCollectTextObjectsHint->m_rTextObjects.push_back(pTextObj); + } + } + else if (auto pGetZOrdnerHint = dynamic_cast<const sw::GetZOrderHint*>(&rHint)) + { + auto pFormat(dynamic_cast<const SwFrameFormat*>(&rMod)); + if(pFormat->Which() == RES_DRAWFRMFMT) + pGetZOrdnerHint->m_rnZOrder = GetMaster()->GetOrdNum(); + } + else if (auto pConnectedHint = dynamic_cast<const sw::GetObjectConnectedHint*>(&rHint)) + { + pConnectedHint->m_risConnected |= (GetAnchorFrame() != nullptr); + } +} + +// #i26791# +// #i28701# - added parameter <_bUpdateSortedObjsList> +void SwDrawContact::InvalidateObjs_( const bool _bUpdateSortedObjsList ) +{ + for(const auto& rpDrawVirtObj : maDrawVirtObjs) + // invalidate position of existing 'virtual' drawing objects + { + SwDrawVirtObj* pDrawVirtObj(rpDrawVirtObj.get()); + // #i33313# - invalidation only for connected + // 'virtual' drawing objects + if ( pDrawVirtObj->IsConnected() ) + { + pDrawVirtObj->AnchoredObj().InvalidateObjPos(); + // #i28701# + if ( _bUpdateSortedObjsList ) + { + pDrawVirtObj->AnchoredObj().UpdateObjInSortedList(); + } + } + } + + // invalidate position of 'master' drawing object + SwAnchoredObject* pAnchoredObj = GetAnchoredObj( nullptr ); + pAnchoredObj->InvalidateObjPos(); + // #i28701# + if ( _bUpdateSortedObjsList ) + { + pAnchoredObj->UpdateObjInSortedList(); + } +} + +void SwDrawContact::DisconnectFromLayout( bool _bMoveMasterToInvisibleLayer ) +{ + mbDisconnectInProgress = true; + + // --> #i36181# - notify background of drawing object + if ( _bMoveMasterToInvisibleLayer && + !(GetFormat()->GetDoc()->IsInDtor()) && + GetAnchorFrame() && !GetAnchorFrame()->IsInDtor() ) + { + const tools::Rectangle aOldRect( maAnchoredDrawObj.GetObjRectWithSpaces().SVRect() ); + lcl_NotifyBackgroundOfObj( *this, *GetMaster(), &aOldRect ); + NotifyBackgrdOfAllVirtObjs( &aOldRect ); + } + + // remove 'virtual' drawing objects from writer + // layout and from drawing page + for(auto& rpVirtDrawObj : maDrawVirtObjs) + { + rpVirtDrawObj->RemoveFromWriterLayout(); + rpVirtDrawObj->RemoveFromDrawingPage(); + } + + if ( maAnchoredDrawObj.GetAnchorFrame() ) + { + maAnchoredDrawObj.AnchorFrame()->RemoveDrawObj( maAnchoredDrawObj ); + } + + if ( _bMoveMasterToInvisibleLayer && GetMaster() && GetMaster()->IsInserted() ) + { + SdrViewIter aIter( GetMaster() ); + for( SdrView* pView = aIter.FirstView(); pView; + pView = aIter.NextView() ) + { + pView->MarkObj( GetMaster(), pView->GetSdrPageView(), true ); + } + + // Instead of removing 'master' object from drawing page, move the + // 'master' drawing object into the corresponding invisible layer. + { + //static_cast<SwFrameFormat*>(GetRegisteredIn())->getIDocumentDrawModelAccess()->GetDrawModel()->GetPage(0)-> + // RemoveObject( GetMaster()->GetOrdNum() ); + // #i18447# - in order to consider group object correct + // use new method <SwDrawContact::MoveObjToInvisibleLayer(..)> + MoveObjToInvisibleLayer( GetMaster() ); + } + } + + mbDisconnectInProgress = false; +} + +/// method to remove 'master' drawing object from drawing page. +void SwDrawContact::RemoveMasterFromDrawPage() +{ + if ( GetMaster() ) + { + GetMaster()->SetUserCall( nullptr ); + if ( GetMaster()->IsInserted() ) + { + static_cast<SwFrameFormat*>(GetRegisteredIn())->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0)-> + RemoveObject( GetMaster()->GetOrdNum() ); + } + } +} + +// disconnect for a dedicated drawing object - could be 'master' or 'virtual'. +// a 'master' drawing object will disconnect a 'virtual' drawing object +// in order to take its place. +// #i19919# - no special case, if drawing object isn't in +// page header/footer, in order to get drawing objects in repeating table headers +// also working. +void SwDrawContact::DisconnectObjFromLayout( SdrObject* _pDrawObj ) +{ + if ( dynamic_cast<const SwDrawVirtObj*>( _pDrawObj) != nullptr ) + { + SwDrawVirtObj* pDrawVirtObj = static_cast<SwDrawVirtObj*>(_pDrawObj); + pDrawVirtObj->RemoveFromWriterLayout(); + pDrawVirtObj->RemoveFromDrawingPage(); + } + else + { + const auto ppVirtDrawObj(std::find_if(maDrawVirtObjs.begin(), maDrawVirtObjs.end(), + [] (const SwDrawVirtObjPtr& pObj) { return pObj->IsConnected(); })); + + if(ppVirtDrawObj != maDrawVirtObjs.end()) + { + // replace found 'virtual' drawing object by 'master' drawing + // object and disconnect the 'virtual' one + SwDrawVirtObj* pDrawVirtObj(ppVirtDrawObj->get()); + SwFrame* pNewAnchorFrameOfMaster = pDrawVirtObj->AnchorFrame(); + // disconnect 'virtual' drawing object + pDrawVirtObj->RemoveFromWriterLayout(); + pDrawVirtObj->RemoveFromDrawingPage(); + // disconnect 'master' drawing object from current frame + GetAnchorFrame()->RemoveDrawObj( maAnchoredDrawObj ); + // re-connect 'master' drawing object to frame of found 'virtual' + // drawing object. + pNewAnchorFrameOfMaster->AppendDrawObj( maAnchoredDrawObj ); + } + else + { + // no connected 'virtual' drawing object found. Thus, disconnect + // completely from layout. + DisconnectFromLayout(); + } + } +} + +static SwTextFrame* lcl_GetFlyInContentAnchor( SwTextFrame* _pProposedAnchorFrame, + SwPosition const& rAnchorPos) +{ + SwTextFrame* pAct = _pProposedAnchorFrame; + SwTextFrame* pTmp; + TextFrameIndex const nTextOffset(_pProposedAnchorFrame->MapModelToViewPos(rAnchorPos)); + do + { + pTmp = pAct; + pAct = pTmp->GetFollow(); + } + while (pAct && nTextOffset >= pAct->GetOffset()); + return pTmp; +} + +void SwDrawContact::ConnectToLayout( const SwFormatAnchor* pAnch ) +{ + // *no* connect to layout during disconnection from layout. + if ( mbDisconnectInProgress ) + { + OSL_FAIL( "<SwDrawContact::ConnectToLayout(..)> called during disconnection."); + return; + } + + // --> #i33909# - *no* connect to layout, if 'master' drawing + // object isn't inserted in the drawing page + if ( !GetMaster()->IsInserted() ) + { + OSL_FAIL( "<SwDrawContact::ConnectToLayout(..)> - master drawing object not inserted -> no connect to layout. Please inform od@openoffice.org" ); + return; + } + + SwFrameFormat* pDrawFrameFormat = static_cast<SwFrameFormat*>(GetRegisteredIn()); + + if( !pDrawFrameFormat->getIDocumentLayoutAccess().GetCurrentViewShell() ) + return; + + // remove 'virtual' drawing objects from writer + // layout and from drawing page, and remove 'master' drawing object from + // writer layout - 'master' object will remain in drawing page. + DisconnectFromLayout( false ); + + if ( !pAnch ) + { + pAnch = &(pDrawFrameFormat->GetAnchor()); + } + + switch ( pAnch->GetAnchorId() ) + { + case RndStdIds::FLY_AT_PAGE: + { + sal_uInt16 nPgNum = pAnch->GetPageNum(); + SwViewShell *pShell = pDrawFrameFormat->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( !pShell ) + break; + SwRootFrame* pRoot = pShell->GetLayout(); + SwPageFrame *pPage = static_cast<SwPageFrame*>(pRoot->Lower()); + + for ( sal_uInt16 i = 1; i < nPgNum && pPage; ++i ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + if ( pPage ) + { + pPage->AppendDrawObj( maAnchoredDrawObj ); + } + else + //Looks stupid but is allowed (compare SwFEShell::SetPageObjsNewPage) + pRoot->SetAssertFlyPages(); + } + break; + + case RndStdIds::FLY_AT_CHAR: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + case RndStdIds::FLY_AS_CHAR: + { + if ( pAnch->GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + ClrContourCache( GetMaster() ); + } + // support drawing objects in header/footer, + // but not control objects: + // anchor at first found frame the 'master' object and + // at the following frames 'virtual' drawing objects. + // Note: method is similar to <SwFlyFrameFormat::MakeFrames(..)> + SwModify *pModify = nullptr; + if( pAnch->GetContentAnchor() ) + { + if ( pAnch->GetAnchorId() == RndStdIds::FLY_AT_FLY ) + { + SwNodeIndex aIdx( pAnch->GetContentAnchor()->nNode ); + SwContentNode* pCNd = pDrawFrameFormat->GetDoc()->GetNodes().GoNext( &aIdx ); + if (SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*pCNd).First()) + pModify = pCNd; + else + { + const SwNodeIndex& rIdx = pAnch->GetContentAnchor()->nNode; + SwFrameFormats& rFormats = *(pDrawFrameFormat->GetDoc()->GetSpzFrameFormats()); + for( auto pFlyFormat : rFormats ) + { + if( pFlyFormat->GetContent().GetContentIdx() && + rIdx == *(pFlyFormat->GetContent().GetContentIdx()) ) + { + pModify = pFlyFormat; + break; + } + } + } + } + else + { + pModify = pAnch->GetContentAnchor()->nNode.GetNode().GetContentNode(); + } + } + + // #i29199# - It is possible, that + // the anchor doesn't exist - E.g., reordering the + // sub-documents in a master document. + // Note: The anchor will be inserted later. + if ( !pModify ) + { + // break to end of the current switch case. + break; + } + + SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti> aIter(*pModify); + SwFrame* pAnchorFrameOfMaster = nullptr; + for( SwFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + // append drawing object, if + // (1) proposed anchor frame isn't a follow and + // (2) drawing object isn't a control object to be anchored + // in header/footer. + bool bAdd = ( !pFrame->IsContentFrame() || + !static_cast<SwContentFrame*>(pFrame)->IsFollow() ) && + ( !::CheckControlLayer( GetMaster() ) || + !pFrame->FindFooterOrHeader() ); + + if (bAdd && RndStdIds::FLY_AT_FLY != pAnch->GetAnchorId()) + { + assert(pFrame->IsTextFrame()); + bAdd = IsAnchoredObjShown(*static_cast<SwTextFrame*>(pFrame), *pAnch); + } + + if( bAdd ) + { + if ( RndStdIds::FLY_AT_FLY == pAnch->GetAnchorId() && !pFrame->IsFlyFrame() ) + { + pFrame = pFrame->FindFlyFrame(); + assert(pFrame); + } + + // find correct follow for as character anchored objects + if ((pAnch->GetAnchorId() == RndStdIds::FLY_AS_CHAR) && + pFrame->IsTextFrame() ) + { + pFrame = lcl_GetFlyInContentAnchor( + static_cast<SwTextFrame*>(pFrame), + *pAnch->GetContentAnchor()); + } + + if ( !pAnchorFrameOfMaster ) + { + // append 'master' drawing object + pAnchorFrameOfMaster = pFrame; + pFrame->AppendDrawObj( maAnchoredDrawObj ); + } + else + { + // append 'virtual' drawing object + SwDrawVirtObj* pDrawVirtObj = AddVirtObj(); + if ( pAnch->GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + ClrContourCache( pDrawVirtObj ); + } + pFrame->AppendDrawObj( pDrawVirtObj->AnchoredObj() ); + + pDrawVirtObj->ActionChanged(); + } + + if ( pAnch->GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + pFrame->InvalidatePrt(); + } + } + } + } + break; + default: + assert(!"Unknown Anchor."); + break; + } + if ( GetAnchorFrame() ) + { + ::setContextWritingMode( maAnchoredDrawObj.DrawObj(), GetAnchorFrame() ); + // #i26791# - invalidate objects instead of direct positioning + InvalidateObjs_(); + } +} + +/// insert 'master' drawing object into drawing page +void SwDrawContact::InsertMasterIntoDrawPage() +{ + if ( !GetMaster()->IsInserted() ) + { + GetFormat()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0) + ->InsertObject( GetMaster(), GetMaster()->GetOrdNumDirect() ); + } + GetMaster()->SetUserCall( this ); +} + +SwPageFrame* SwDrawContact::FindPage( const SwRect &rRect ) +{ + // --> #i28701# - use method <GetPageFrame()> + SwPageFrame* pPg = GetPageFrame(); + if ( !pPg && GetAnchorFrame() ) + pPg = GetAnchorFrame()->FindPageFrame(); + if ( pPg ) + pPg = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(::FindPage( rRect, pPg ))); + return pPg; +} + +void SwDrawContact::ChkPage() +{ + if ( mbDisconnectInProgress ) + { + OSL_FAIL( "<SwDrawContact::ChkPage()> called during disconnection." ); + return; + } + + // --> #i28701# + SwPageFrame* pPg = ( maAnchoredDrawObj.GetAnchorFrame() && + maAnchoredDrawObj.GetAnchorFrame()->IsPageFrame() ) + ? GetPageFrame() + : FindPage( GetMaster()->GetCurrentBoundRect() ); + if ( GetPageFrame() != pPg ) + { + // if drawing object is anchor in header/footer a change of the page + // is a dramatic change. Thus, completely re-connect to the layout + if ( maAnchoredDrawObj.GetAnchorFrame() && + maAnchoredDrawObj.GetAnchorFrame()->FindFooterOrHeader() ) + { + ConnectToLayout(); + } + else + { + // --> #i28701# - use methods <GetPageFrame()> and <SetPageFrame> + if ( GetPageFrame() ) + GetPageFrame()->RemoveDrawObjFromPage( maAnchoredDrawObj ); + pPg->AppendDrawObjToPage( maAnchoredDrawObj ); + maAnchoredDrawObj.SetPageFrame( pPg ); + } + } +} + +// Important note: +// method is called by method <SwDPage::ReplaceObject(..)>, which called its +// corresponding superclass method <FmFormPage::ReplaceObject(..)>. +// Note: 'master' drawing object *has* to be connected to layout triggered +// by the caller of this, if method is called. +void SwDrawContact::ChangeMasterObject(SdrObject* pNewMaster) +{ + DisconnectFromLayout( false ); + // consider 'virtual' drawing objects + RemoveAllVirtObjs(); + + GetMaster()->SetUserCall( nullptr ); + if(pNewMaster) + maAnchoredDrawObj.SetDrawObj(*pNewMaster); + else + mbMasterObjCleared = true; + GetMaster()->SetUserCall( this ); + + InvalidateObjs_(); +} + +/// get data collection of anchored objects, handled by with contact +void SwDrawContact::GetAnchoredObjs(std::vector<SwAnchoredObject*>& o_rAnchoredObjs) const +{ + o_rAnchoredObjs.push_back(const_cast<SwAnchoredDrawObject*>(&maAnchoredDrawObj)); + + for(auto& rpDrawVirtObj : maDrawVirtObjs) + o_rAnchoredObjs.push_back(&rpDrawVirtObj->AnchoredObj()); +} + +// AW: own sdr::contact::ViewContact (VC) sdr::contact::ViewObjectContact (VOC) needed +// since offset is defined different from SdrVirtObj's sdr::contact::ViewContactOfVirtObj. +// For paint, that offset is used by setting at the OutputDevice; for primitives this is +// not possible since we have no OutputDevice, but define the geometry itself. + +namespace sdr::contact +{ + namespace { + + class VOCOfDrawVirtObj : public ViewObjectContactOfSdrObj + { + protected: + /** + * This method is responsible for creating the graphical visualisation data which is + * stored/cached in the local primitive. Default gets view-independent Primitive from + * the ViewContact using ViewContact::getViewIndependentPrimitive2DContainer(), takes + * care of visibility, handles glue and ghosted. + * + * This method will not handle included hierarchies and not check geometric visibility. + */ + virtual drawinglayer::primitive2d::Primitive2DContainer createPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const override; + + public: + VOCOfDrawVirtObj(ObjectContact& rObjectContact, ViewContact& rViewContact) + : ViewObjectContactOfSdrObj(rObjectContact, rViewContact) + { + } + }; + + class VCOfDrawVirtObj : public ViewContactOfVirtObj + { + protected: + /** Create an Object-Specific ViewObjectContact, set ViewContact and ObjectContact. + * + * Always needs to return something. Default is to create a standard ViewObjectContact + * containing the given ObjectContact and *this. + */ + virtual ViewObjectContact& CreateObjectSpecificViewObjectContact(ObjectContact& rObjectContact) override; + + public: + /// basic constructor, used from SdrObject. + explicit VCOfDrawVirtObj(SwDrawVirtObj& rObj) + : ViewContactOfVirtObj(rObj) + { + } + + /// access to SwDrawVirtObj + SwDrawVirtObj& GetSwDrawVirtObj() const + { + return static_cast<SwDrawVirtObj&>(mrObject); + } + }; + + } +} // end of namespace sdr::contact + +namespace sdr::contact +{ + /// recursively collect primitive data from given VOC with given offset + static void impAddPrimitivesFromGroup(const ViewObjectContact& rVOC, const basegfx::B2DHomMatrix& rOffsetMatrix, const DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DContainer& rxTarget) + { + const sal_uInt32 nSubHierarchyCount(rVOC.GetViewContact().GetObjectCount()); + + for(sal_uInt32 a(0); a < nSubHierarchyCount; a++) + { + const ViewObjectContact& rCandidate(rVOC.GetViewContact().GetViewContact(a).GetViewObjectContact(rVOC.GetObjectContact())); + + if(rCandidate.GetViewContact().GetObjectCount()) + { + // is a group object itself, call recursively + impAddPrimitivesFromGroup(rCandidate, rOffsetMatrix, rDisplayInfo, rxTarget); + } + else + { + // single object, add primitives; check model-view visibility + if(rCandidate.isPrimitiveVisible(rDisplayInfo)) + { + drawinglayer::primitive2d::Primitive2DContainer aNewSequence(rCandidate.getPrimitive2DSequence(rDisplayInfo)); + + if(!aNewSequence.empty()) + { + // get ranges + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(rCandidate.GetObjectContact().getViewInformation2D()); + const basegfx::B2DRange aViewRange(rViewInformation2D.getViewport()); + basegfx::B2DRange aObjectRange(rCandidate.getObjectRange()); + + // correct with virtual object's offset + aObjectRange.transform(rOffsetMatrix); + + // check geometrical visibility (with offset) + if(!aViewRange.overlaps(aObjectRange)) + { + // not visible, release + aNewSequence.clear(); + } + } + + if(!aNewSequence.empty()) + { + rxTarget.append(aNewSequence); + } + } + } + } + } + + drawinglayer::primitive2d::Primitive2DContainer VOCOfDrawVirtObj::createPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const + { + // tdf#91260 have already checked top-level one is on the right page + assert(isPrimitiveVisible(rDisplayInfo)); + // nasty corner case: override to clear page frame to disable the + // sub-objects' anchor check, because their anchor is always on + // the first page that the page style is applied to + DisplayInfo aDisplayInfo(rDisplayInfo); + aDisplayInfo.SetWriterPageFrame(basegfx::B2IRectangle()); + const VCOfDrawVirtObj& rVC = static_cast< const VCOfDrawVirtObj& >(GetViewContact()); + const SdrObject& rReferencedObject = rVC.GetSwDrawVirtObj().GetReferencedObj(); + drawinglayer::primitive2d::Primitive2DContainer xRetval; + + // create offset transformation + basegfx::B2DHomMatrix aOffsetMatrix; + const Point aLocalOffset(rVC.GetSwDrawVirtObj().GetOffset()); + + if(aLocalOffset.X() || aLocalOffset.Y()) + { + aOffsetMatrix.set(0, 2, aLocalOffset.X()); + aOffsetMatrix.set(1, 2, aLocalOffset.Y()); + } + + if(dynamic_cast<const SdrObjGroup*>( &rReferencedObject) != nullptr) + { + // group object. Since the VOC/OC/VC hierarchy does not represent the + // hierarchy virtual objects when they have group objects + // (ViewContactOfVirtObj::GetObjectCount() returns null for that purpose) + // to avoid multiple usages of VOCs (which would not work), the primitives + // for the sub-hierarchy need to be collected here + + // Get the VOC of the referenced object (the Group) and fetch primitives from it + const ViewObjectContact& rVOCOfRefObj = rReferencedObject.GetViewContact().GetViewObjectContact(GetObjectContact()); + impAddPrimitivesFromGroup(rVOCOfRefObj, aOffsetMatrix, aDisplayInfo, xRetval); + } + else + { + // single object, use method from referenced object to get the Primitive2DSequence + xRetval = rReferencedObject.GetViewContact().getViewIndependentPrimitive2DContainer(); + } + + if(!xRetval.empty()) + { + // create transform primitive + const drawinglayer::primitive2d::Primitive2DReference xReference(new drawinglayer::primitive2d::TransformPrimitive2D(aOffsetMatrix, xRetval)); + xRetval = drawinglayer::primitive2d::Primitive2DContainer { xReference }; + } + + return xRetval; + } + + ViewObjectContact& VCOfDrawVirtObj::CreateObjectSpecificViewObjectContact(ObjectContact& rObjectContact) + { + return *(new VOCOfDrawVirtObj(rObjectContact, *this)); + } + +} // end of namespace sdr::contact + +/// implementation of class <SwDrawVirtObj> +std::unique_ptr<sdr::contact::ViewContact> SwDrawVirtObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::VCOfDrawVirtObj>(*this); +} + +SwDrawVirtObj::SwDrawVirtObj( + SdrModel& rSdrModel, + SdrObject& _rNewObj, + SwDrawContact& _rDrawContact) +: SdrVirtObj(rSdrModel, _rNewObj ), + maAnchoredDrawObj(), + mrDrawContact(_rDrawContact) +{ + // #i26791# + maAnchoredDrawObj.SetDrawObj( *this ); + + // #i35635# - set initial position out of sight + NbcMove( Size( -16000, -16000 ) ); +} + +SwDrawVirtObj::~SwDrawVirtObj() +{ +} + +SwDrawVirtObj& SwDrawVirtObj::operator=( const SwDrawVirtObj& rObj ) +{ + SdrVirtObj::operator=(rObj); + // Note: Members <maAnchoredDrawObj> and <mrDrawContact> + // haven't to be considered. + return *this; +} + +SwDrawVirtObj* SwDrawVirtObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + SwDrawVirtObj* pObj = new SwDrawVirtObj( + rTargetModel, + rRefObj, + mrDrawContact); + + pObj->operator=( *this ); + // Note: Member <maAnchoredDrawObj> hasn't to be considered. + + return pObj; +} + +const SwFrame* SwDrawVirtObj::GetAnchorFrame() const +{ + // #i26791# - use new member <maAnchoredDrawObj> + return maAnchoredDrawObj.GetAnchorFrame(); +} + +SwFrame* SwDrawVirtObj::AnchorFrame() +{ + // #i26791# - use new member <maAnchoredDrawObj> + return maAnchoredDrawObj.AnchorFrame(); +} + +void SwDrawVirtObj::RemoveFromWriterLayout() +{ + // remove contact object from frame for 'virtual' drawing object + // #i26791# - use new member <maAnchoredDrawObj> + if ( maAnchoredDrawObj.GetAnchorFrame() ) + { + maAnchoredDrawObj.AnchorFrame()->RemoveDrawObj( maAnchoredDrawObj ); + } +} + +void SwDrawVirtObj::AddToDrawingPage() +{ + // determine 'master' + SdrObject* pOrgMasterSdrObj = mrDrawContact.GetMaster(); + + // insert 'virtual' drawing object into page, set layer and user call. + SdrPage* pDrawPg; + // #i27030# - apply order number of referenced object + if ( nullptr != ( pDrawPg = pOrgMasterSdrObj->getSdrPageFromSdrObject() ) ) + { + // #i27030# - apply order number of referenced object + pDrawPg->InsertObject( this, GetReferencedObj().GetOrdNum() ); + } + else + { + pDrawPg = getSdrPageFromSdrObject(); + if ( pDrawPg ) + { + pDrawPg->SetObjectOrdNum( GetOrdNumDirect(), + GetReferencedObj().GetOrdNum() ); + } + else + { + SetOrdNum( GetReferencedObj().GetOrdNum() ); + } + } + SetUserCall( &mrDrawContact ); +} + +void SwDrawVirtObj::RemoveFromDrawingPage() +{ + SetUserCall( nullptr ); + if ( getSdrPageFromSdrObject() ) + { + getSdrPageFromSdrObject()->RemoveObject( GetOrdNum() ); + } +} + +/// Is 'virtual' drawing object connected to writer layout and to drawing layer? +bool SwDrawVirtObj::IsConnected() const +{ + bool bRetVal = GetAnchorFrame() && + ( getSdrPageFromSdrObject() && GetUserCall() ); + + return bRetVal; +} + +void SwDrawVirtObj::NbcSetAnchorPos(const Point& rPnt) +{ + SdrObject::NbcSetAnchorPos( rPnt ); +} + +// #i97197# +// the methods relevant for positioning + +const tools::Rectangle& SwDrawVirtObj::GetCurrentBoundRect() const +{ + if(aOutRect.IsEmpty()) + { + const_cast<SwDrawVirtObj*>(this)->RecalcBoundRect(); + } + + return aOutRect; +} + +const tools::Rectangle& SwDrawVirtObj::GetLastBoundRect() const +{ + return aOutRect; +} + +Point SwDrawVirtObj::GetOffset() const +{ + // do NOT use IsEmpty() here, there is already a useful offset + // in the position + if(aOutRect == tools::Rectangle()) + { + return Point(); + } + else + { + return aOutRect.TopLeft() - GetReferencedObj().GetCurrentBoundRect().TopLeft(); + } +} + +void SwDrawVirtObj::SetBoundRectDirty() +{ + // do nothing to not lose model information in aOutRect +} + +void SwDrawVirtObj::RecalcBoundRect() +{ + // #i26791# - switch order of calling <GetOffset()> and + // <ReferencedObj().GetCurrentBoundRect()>, because <GetOffset()> calculates + // its value by the 'BoundRect' of the referenced object. + + const Point aOffset(GetOffset()); + aOutRect = ReferencedObj().GetCurrentBoundRect() + aOffset; +} + +basegfx::B2DPolyPolygon SwDrawVirtObj::TakeXorPoly() const +{ + basegfx::B2DPolyPolygon aRetval(rRefObj.TakeXorPoly()); + aRetval.transform(basegfx::utils::createTranslateB2DHomMatrix(GetOffset().X(), GetOffset().Y())); + + return aRetval; +} + +basegfx::B2DPolyPolygon SwDrawVirtObj::TakeContour() const +{ + basegfx::B2DPolyPolygon aRetval(rRefObj.TakeContour()); + aRetval.transform(basegfx::utils::createTranslateB2DHomMatrix(GetOffset().X(), GetOffset().Y())); + + return aRetval; +} + +void SwDrawVirtObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + SdrHdlList tmpList(nullptr); + rRefObj.AddToHdlList(tmpList); + + size_t cnt = tmpList.GetHdlCount(); + for(size_t i=0; i < cnt; ++i) + { + SdrHdl* pHdl = tmpList.GetHdl(i); + Point aP(pHdl->GetPos() + GetOffset()); + pHdl->SetPos(aP); + } + tmpList.MoveTo(rHdlList); +} + +void SwDrawVirtObj::NbcMove(const Size& rSiz) +{ + SdrObject::NbcMove( rSiz ); +} + +void SwDrawVirtObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + rRefObj.NbcResize(rRef - GetOffset(), xFact, yFact); + SetRectsDirty(); +} + +void SwDrawVirtObj::NbcRotate(const Point& rRef, long nAngle, double sn, double cs) +{ + rRefObj.NbcRotate(rRef - GetOffset(), nAngle, sn, cs); + SetRectsDirty(); +} + +void SwDrawVirtObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + rRefObj.NbcMirror(rRef1 - GetOffset(), rRef2 - GetOffset()); + SetRectsDirty(); +} + +void SwDrawVirtObj::NbcShear(const Point& rRef, long nAngle, double tn, bool bVShear) +{ + rRefObj.NbcShear(rRef - GetOffset(), nAngle, tn, bVShear); + SetRectsDirty(); +} + +void SwDrawVirtObj::Move(const Size& rSiz) +{ + SdrObject::Move( rSiz ); +} + +void SwDrawVirtObj::Resize(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bUnsetRelative) +{ + if(xFact.GetNumerator() != xFact.GetDenominator() || yFact.GetNumerator() != yFact.GetDenominator()) + { + tools::Rectangle aBoundRect0; if(pUserCall) aBoundRect0 = GetLastBoundRect(); + rRefObj.Resize(rRef - GetOffset(), xFact, yFact, bUnsetRelative); + SetRectsDirty(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); + } +} + +void SwDrawVirtObj::Rotate(const Point& rRef, long nAngle, double sn, double cs) +{ + if(nAngle) + { + tools::Rectangle aBoundRect0; if(pUserCall) aBoundRect0 = GetLastBoundRect(); + rRefObj.Rotate(rRef - GetOffset(), nAngle, sn, cs); + SetRectsDirty(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); + } +} + +void SwDrawVirtObj::Mirror(const Point& rRef1, const Point& rRef2) +{ + tools::Rectangle aBoundRect0; if(pUserCall) aBoundRect0 = GetLastBoundRect(); + rRefObj.Mirror(rRef1 - GetOffset(), rRef2 - GetOffset()); + SetRectsDirty(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); +} + +void SwDrawVirtObj::Shear(const Point& rRef, long nAngle, double tn, bool bVShear) +{ + if(nAngle) + { + tools::Rectangle aBoundRect0; if(pUserCall) aBoundRect0 = GetLastBoundRect(); + rRefObj.Shear(rRef - GetOffset(), nAngle, tn, bVShear); + SetRectsDirty(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); + } +} + +void SwDrawVirtObj::RecalcSnapRect() +{ + aSnapRect = rRefObj.GetSnapRect(); + aSnapRect += GetOffset(); +} + +const tools::Rectangle& SwDrawVirtObj::GetSnapRect() const +{ + const_cast<SwDrawVirtObj*>(this)->aSnapRect = rRefObj.GetSnapRect(); + const_cast<SwDrawVirtObj*>(this)->aSnapRect += GetOffset(); + + return aSnapRect; +} + +void SwDrawVirtObj::SetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if(pUserCall) aBoundRect0 = GetLastBoundRect(); + tools::Rectangle aR(rRect); + aR -= GetOffset(); + rRefObj.SetSnapRect(aR); + SetRectsDirty(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); +} + +void SwDrawVirtObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aR(rRect); + aR -= GetOffset(); + SetRectsDirty(); + rRefObj.NbcSetSnapRect(aR); +} + +const tools::Rectangle& SwDrawVirtObj::GetLogicRect() const +{ + const_cast<SwDrawVirtObj*>(this)->aSnapRect = rRefObj.GetLogicRect(); + const_cast<SwDrawVirtObj*>(this)->aSnapRect += GetOffset(); + + return aSnapRect; +} + +void SwDrawVirtObj::SetLogicRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if(pUserCall) aBoundRect0 = GetLastBoundRect(); + tools::Rectangle aR(rRect); + aR -= GetOffset(); + rRefObj.SetLogicRect(aR); + SetRectsDirty(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); +} + +void SwDrawVirtObj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aR(rRect); + aR -= GetOffset(); + rRefObj.NbcSetLogicRect(aR); + SetRectsDirty(); +} + +Point SwDrawVirtObj::GetSnapPoint(sal_uInt32 i) const +{ + Point aP(rRefObj.GetSnapPoint(i)); + aP += GetOffset(); + + return aP; +} + +Point SwDrawVirtObj::GetPoint(sal_uInt32 i) const +{ + return rRefObj.GetPoint(i) + GetOffset(); +} + +void SwDrawVirtObj::NbcSetPoint(const Point& rPnt, sal_uInt32 i) +{ + Point aP(rPnt); + aP -= GetOffset(); + rRefObj.SetPoint(aP, i); + SetRectsDirty(); +} + +bool SwDrawVirtObj::HasTextEdit() const +{ + return rRefObj.HasTextEdit(); +} + +// override 'layer' methods for 'virtual' drawing object to assure +// that layer of 'virtual' object is the layer of the referenced object. +SdrLayerID SwDrawVirtObj::GetLayer() const +{ + return GetReferencedObj().GetLayer(); +} + +void SwDrawVirtObj::NbcSetLayer(SdrLayerID nLayer) +{ + ReferencedObj().NbcSetLayer( nLayer ); + SdrVirtObj::NbcSetLayer( ReferencedObj().GetLayer() ); +} + +void SwDrawVirtObj::SetLayer(SdrLayerID nLayer) +{ + ReferencedObj().SetLayer( nLayer ); + SdrVirtObj::NbcSetLayer( ReferencedObj().GetLayer() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/dflyobj.cxx b/sw/source/core/draw/dflyobj.cxx new file mode 100644 index 000000000..f794887fe --- /dev/null +++ b/sw/source/core/draw/dflyobj.cxx @@ -0,0 +1,1295 @@ +/* -*- 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 <hintids.hxx> +#include <comphelper/lok.hxx> +#include <tools/mapunit.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svdtrans.hxx> +#include <editeng/protitem.hxx> +#include <svx/svdpage.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/ptrstyle.hxx> + +#include <fmtclds.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmturl.hxx> +#include <viewsh.hxx> +#include <frmatr.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <dflyobj.hxx> +#include <flyfrm.hxx> +#include <frmfmt.hxx> +#include <viewopt.hxx> +#include <frmtool.hxx> +#include <flyfrms.hxx> +#include <ndnotxt.hxx> +#include <grfatr.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <textboxhelper.hxx> +#include <wrtsh.hxx> +#include <ndgrf.hxx> +#include <frmmgr.hxx> + +#include <svx/sdr/properties/defaultproperties.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> + +// AW: For VCOfDrawVirtObj and stuff +#include <svx/sdr/contact/viewcontactofvirtobj.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <sw_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/sdrdecompositiontools2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <notxtfrm.hxx> + +using namespace ::com::sun::star; + +static bool bInResize = false; + + +namespace sdr::contact +{ + namespace { + + /** + * @see #i95264# + * + * currently needed since createViewIndependentPrimitive2DSequence() is called when + * RecalcBoundRect() is used. There should currently no VOCs being constructed since it + * gets not visualized (instead the corresponding SwVirtFlyDrawObj's referencing this one + * are visualized). + */ + class VCOfSwFlyDrawObj : public ViewContactOfSdrObj + { + protected: + /** This method is responsible for creating the graphical visualisation data + * + * @note ONLY based on model data + */ + virtual drawinglayer::primitive2d::Primitive2DContainer createViewIndependentPrimitive2DSequence() const override; + + public: + /// basic constructor, used from SdrObject. + explicit VCOfSwFlyDrawObj(SwFlyDrawObj& rObj) + : ViewContactOfSdrObj(rObj) + { + } + }; + + } + + drawinglayer::primitive2d::Primitive2DContainer VCOfSwFlyDrawObj::createViewIndependentPrimitive2DSequence() const + { + // currently gets not visualized, return empty sequence + return drawinglayer::primitive2d::Primitive2DContainer(); + } + +} // end of namespace sdr::contact + +std::unique_ptr<sdr::properties::BaseProperties> SwFlyDrawObj::CreateObjectSpecificProperties() +{ + // create default properties + return std::make_unique<sdr::properties::DefaultProperties>(*this); +} + +std::unique_ptr<sdr::contact::ViewContact> SwFlyDrawObj::CreateObjectSpecificViewContact() +{ + // needs an own VC since createViewIndependentPrimitive2DSequence() + // is called when RecalcBoundRect() is used + return std::make_unique<sdr::contact::VCOfSwFlyDrawObj>(*this); +} + +SwFlyDrawObj::SwFlyDrawObj(SdrModel& rSdrModel) +: SdrObject(rSdrModel), + mbIsTextBox(false) +{ +} + +SwFlyDrawObj::~SwFlyDrawObj() +{ +} + +// SwFlyDrawObj - Factory-Methods +SdrInventor SwFlyDrawObj::GetObjInventor() const +{ + return SdrInventor::Swg; +} + +sal_uInt16 SwFlyDrawObj::GetObjIdentifier() const +{ + return SwFlyDrawObjIdentifier; +} + +// TODO: Need own primitive to get the FlyFrame paint working +namespace drawinglayer::primitive2d +{ + namespace { + + class SwVirtFlyDrawObjPrimitive : public BufferedDecompositionPrimitive2D + { + private: + const SwVirtFlyDrawObj& mrSwVirtFlyDrawObj; + const basegfx::B2DRange maOuterRange; + + protected: + /// method which is to be used to implement the local decomposition of a 2D primitive + virtual void create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const override; + + public: + SwVirtFlyDrawObjPrimitive( + const SwVirtFlyDrawObj& rSwVirtFlyDrawObj, + const basegfx::B2DRange &rOuterRange) + : BufferedDecompositionPrimitive2D(), + mrSwVirtFlyDrawObj(rSwVirtFlyDrawObj), + maOuterRange(rOuterRange) + { + } + + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + + virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override; + + // override to allow callbacks to wrap_DoPaintObject + virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override; + + // data read access + const SwVirtFlyDrawObj& getSwVirtFlyDrawObj() const { return mrSwVirtFlyDrawObj; } + const basegfx::B2DRange& getOuterRange() const { return maOuterRange; } + + /// provide unique ID + virtual sal_uInt32 getPrimitive2DID() const override; + }; + + } +} // end of namespace drawinglayer::primitive2d + +namespace drawinglayer::primitive2d +{ + void SwVirtFlyDrawObjPrimitive::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getOuterRange().isEmpty()) + { + // currently this SW object has no primitive representation. As long as this is the case, + // create invisible geometry to allow correct HitTest and BoundRect calculations for the + // object. Use a filled primitive to get 'inside' as default object hit. The special cases from + // the old SwVirtFlyDrawObj::CheckHit implementation are handled now in SwDrawView::PickObj; + // this removed the 'hack' to get a view from inside model data or to react on null-tolerance + // as it was done in the old implementation + rContainer.push_back( + createHiddenGeometryPrimitives2D( + true, + getOuterRange())); + } + } + + bool SwVirtFlyDrawObjPrimitive::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const SwVirtFlyDrawObjPrimitive& rCompare = static_cast<const SwVirtFlyDrawObjPrimitive&>(rPrimitive); + + return (&getSwVirtFlyDrawObj() == &rCompare.getSwVirtFlyDrawObj() + && getOuterRange() == rCompare.getOuterRange()); + } + + return false; + } + + basegfx::B2DRange SwVirtFlyDrawObjPrimitive::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + return getOuterRange(); + } + + void SwVirtFlyDrawObjPrimitive::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // This is the callback to keep the FlyFrame painting in SW alive as long as it + // is not changed to primitives. This is the method which will be called by the processors + // when they do not know this primitive (and they do not). Inside wrap_DoPaintObject + // there needs to be a test that paint is only done during SW repaints (see there). + // Using this mechanism guarantees the correct Z-Order of the VirtualObject-based FlyFrames. + getSwVirtFlyDrawObj().wrap_DoPaintObject(rViewInformation); + + // call parent + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(SwVirtFlyDrawObjPrimitive, PRIMITIVE2D_ID_SWVIRTFLYDRAWOBJPRIMITIVE2D) + +} // end of namespace drawinglayer::primitive2d + +// AW: own sdr::contact::ViewContact (VC) sdr::contact::ViewObjectContact (VOC) needed +// since offset is defined different from SdrVirtObj's sdr::contact::ViewContactOfVirtObj. +// For paint, that offset is used by setting at the OutputDevice; for primitives this is +// not possible since we have no OutputDevice, but define the geometry itself. + +namespace sdr::contact +{ + namespace { + + class VCOfSwVirtFlyDrawObj : public ViewContactOfVirtObj + { + protected: + /** This method is responsible for creating the graphical visualisation data + * + * @note ONLY based on model data + */ + virtual drawinglayer::primitive2d::Primitive2DContainer createViewIndependentPrimitive2DSequence() const override; + + public: + /// basic constructor, used from SdrObject. + explicit VCOfSwVirtFlyDrawObj(SwVirtFlyDrawObj& rObj) + : ViewContactOfVirtObj(rObj) + { + } + + /// access to SwVirtFlyDrawObj + SwVirtFlyDrawObj& GetSwVirtFlyDrawObj() const + { + return static_cast<SwVirtFlyDrawObj&>(mrObject); + } + }; + + } +} // end of namespace sdr::contact + +namespace sdr::contact +{ + drawinglayer::primitive2d::Primitive2DContainer VCOfSwVirtFlyDrawObj::createViewIndependentPrimitive2DSequence() const + { + drawinglayer::primitive2d::Primitive2DContainer xRetval; + const SdrObject& rReferencedObject = GetSwVirtFlyDrawObj().GetReferencedObj(); + + if(dynamic_cast<const SwFlyDrawObj*>( &rReferencedObject) != nullptr) + { + // create an own specialized primitive which is used as repaint callpoint and HitTest + // for HitTest processor (see primitive implementation above) + const basegfx::B2DRange aOuterRange(GetSwVirtFlyDrawObj().getOuterBound()); + + if(!aOuterRange.isEmpty()) + { + const drawinglayer::primitive2d::Primitive2DReference xPrimitive( + new drawinglayer::primitive2d::SwVirtFlyDrawObjPrimitive( + GetSwVirtFlyDrawObj(), + aOuterRange)); + + xRetval = drawinglayer::primitive2d::Primitive2DContainer { xPrimitive }; + } + } + + return xRetval; + } + +} // end of namespace sdr::contact + +basegfx::B2DRange SwVirtFlyDrawObj::getOuterBound() const +{ + basegfx::B2DRange aOuterRange; + const SdrObject& rReferencedObject = GetReferencedObj(); + + if(dynamic_cast<const SwFlyDrawObj*>( &rReferencedObject) != nullptr) + { + const SwFlyFrame* pFlyFrame = GetFlyFrame(); + + if(pFlyFrame) + { + const tools::Rectangle aOuterRectangle(pFlyFrame->getFrameArea().Pos(), pFlyFrame->getFrameArea().SSize()); + + if(!aOuterRectangle.IsEmpty()) + { + aOuterRange.expand(basegfx::B2DTuple(aOuterRectangle.Left(), aOuterRectangle.Top())); + aOuterRange.expand(basegfx::B2DTuple(aOuterRectangle.Right(), aOuterRectangle.Bottom())); + } + } + } + + return aOuterRange; +} + +basegfx::B2DRange SwVirtFlyDrawObj::getInnerBound() const +{ + basegfx::B2DRange aInnerRange; + const SdrObject& rReferencedObject = GetReferencedObj(); + + if(dynamic_cast<const SwFlyDrawObj*>( &rReferencedObject) != nullptr) + { + const SwFlyFrame* pFlyFrame = GetFlyFrame(); + + if(pFlyFrame) + { + const tools::Rectangle aInnerRectangle(pFlyFrame->getFrameArea().Pos() + pFlyFrame->getFramePrintArea().Pos(), pFlyFrame->getFramePrintArea().SSize()); + + if(!aInnerRectangle.IsEmpty()) + { + aInnerRange.expand(basegfx::B2DTuple(aInnerRectangle.Left(), aInnerRectangle.Top())); + aInnerRange.expand(basegfx::B2DTuple(aInnerRectangle.Right(), aInnerRectangle.Bottom())); + } + } + } + + return aInnerRange; +} + +bool SwVirtFlyDrawObj::ContainsSwGrfNode() const +{ + // RotGrfFlyFrame: Check if this is a SwGrfNode + const SwFlyFrame* pFlyFrame(GetFlyFrame()); + + if(nullptr != pFlyFrame && pFlyFrame->Lower() && pFlyFrame->Lower()->IsNoTextFrame()) + { + const SwNoTextFrame *const pNTF(static_cast<const SwNoTextFrame*>(pFlyFrame->Lower())); + + const SwGrfNode *const pGrfNd(pNTF->GetNode()->GetGrfNode()); + + return nullptr != pGrfNd; + } + + return false; +} + +bool SwVirtFlyDrawObj::HasLimitedRotation() const +{ + // RotGrfFlyFrame: If true, this SdrObject supports only limited rotation. + // This is the case for SwGrfNode instances + return ContainsSwGrfNode(); +} + +void SwVirtFlyDrawObj::Rotate(const Point& rRef, long nAngle, double sn, double cs) +{ + if(ContainsSwGrfNode()) + { + // RotGrfFlyFrame: Here is where the positively completed rotate interaction is executed. + // Rotation is in 1/100th degree and may be signed (!) + nAngle /= 10; + + while(nAngle < 0) + { + nAngle += 3600; + } + + SwWrtShell *pShForAngle = nAngle ? dynamic_cast<SwWrtShell*>(GetFlyFrame()->getRootFrame()->GetCurrShell()) : nullptr; + if (pShForAngle) + { + // RotGrfFlyFrame: Add transformation to placeholder object + Size aSize; + const sal_uInt16 nOldRot(SwVirtFlyDrawObj::getPossibleRotationFromFraphicFrame(aSize)); + SwFlyFrameAttrMgr aMgr(false, pShForAngle, Frmmgr_Type::NONE, nullptr); + + aMgr.SetRotation(nOldRot, (nOldRot + static_cast<sal_uInt16>(nAngle)) % 3600, aSize); + } + } + else + { + // call parent + SdrVirtObj::Rotate(rRef, nAngle, sn, cs); + } +} + +std::unique_ptr<sdr::contact::ViewContact> SwVirtFlyDrawObj::CreateObjectSpecificViewContact() +{ + // need an own ViewContact (VC) to allow creation of a specialized primitive + // for being able to visualize the FlyFrames in primitive renderers + return std::make_unique<sdr::contact::VCOfSwVirtFlyDrawObj>(*this); +} + +SwVirtFlyDrawObj::SwVirtFlyDrawObj( + SdrModel& rSdrModel, + SdrObject& rNew, + SwFlyFrame* pFly) +: SdrVirtObj(rSdrModel, rNew), + m_pFlyFrame(pFly) +{ + const SvxProtectItem &rP = m_pFlyFrame->GetFormat()->GetProtect(); + bMovProt = rP.IsPosProtected(); + bSizProt = rP.IsSizeProtected(); +} + +SwVirtFlyDrawObj::~SwVirtFlyDrawObj() +{ + if ( getSdrPageFromSdrObject() ) //Withdraw SdrPage the responsibility. + getSdrPageFromSdrObject()->RemoveObject( GetOrdNum() ); +} + +const SwFrameFormat *SwVirtFlyDrawObj::GetFormat() const +{ + return GetFlyFrame()->GetFormat(); +} +SwFrameFormat *SwVirtFlyDrawObj::GetFormat() +{ + return GetFlyFrame()->GetFormat(); +} + +// --> OD #i102707# +namespace +{ + class RestoreMapMode + { + public: + explicit RestoreMapMode( SwViewShell const * pViewShell ) + : mbMapModeRestored( false ) + , mpOutDev( pViewShell->GetOut() ) + { + if ( pViewShell->getPrePostMapMode() != mpOutDev->GetMapMode() ) + { + mpOutDev->Push(PushFlags::MAPMODE); + + GDIMetaFile* pMetaFile = mpOutDev->GetConnectMetaFile(); + if ( pMetaFile && + pMetaFile->IsRecord() && !pMetaFile->IsPause() ) + { + OSL_FAIL( "MapMode restoration during meta file creation is somehow suspect - using <SetRelativeMapMode(..)>, but not sure, if correct." ); + mpOutDev->SetRelativeMapMode( pViewShell->getPrePostMapMode() ); + } + else + { + mpOutDev->SetMapMode( pViewShell->getPrePostMapMode() ); + } + + mbMapModeRestored = true; + } + }; + + ~RestoreMapMode() + { + if ( mbMapModeRestored ) + { + mpOutDev->Pop(); + } + }; + + private: + bool mbMapModeRestored; + VclPtr<OutputDevice> mpOutDev; + }; +} +// <-- + +void SwVirtFlyDrawObj::wrap_DoPaintObject( + drawinglayer::geometry::ViewInformation2D const& rViewInformation) const +{ + SwViewShell* pShell = m_pFlyFrame->getRootFrame()->GetCurrShell(); + + // Only paint when we have a current shell and a DrawingLayer paint is in progress. + // This avoids evtl. problems with renderers which do processing stuff, + // but no paints. IsPaintInProgress() depends on SW repaint, so, as long + // as SW paints self and calls DrawLayer() for Heaven and Hell, this will + // be correct + if ( pShell && pShell->IsDrawingLayerPaintInProgress() ) + { + bool bDrawObject(true); + + if ( !SwFlyFrame::IsPaint( const_cast<SwVirtFlyDrawObj*>(this), pShell ) ) + { + bDrawObject = false; + } + + if ( bDrawObject ) + { + // if there's no viewport set, all fly-frames will be painted, + // which is slow, wastes memory, and can cause other trouble. + (void) rViewInformation; // suppress "unused parameter" warning + assert(comphelper::LibreOfficeKit::isActive() || !rViewInformation.getViewport().isEmpty()); + if ( !m_pFlyFrame->IsFlyInContentFrame() ) + { + // it is also necessary to restore the VCL MapMode from ViewInformation since e.g. + // the VCL PixelRenderer resets it at the used OutputDevice. Unfortunately, this + // excludes shears and rotates which are not expressible in MapMode. + // OD #i102707# + // new helper class to restore MapMode - restoration, only if + // needed and consideration of paint for meta file creation . + RestoreMapMode aRestoreMapModeIfNeeded( pShell ); + + // paint the FlyFrame (use standard VCL-Paint) + m_pFlyFrame->PaintSwFrame( *pShell->GetOut(), GetFlyFrame()->getFrameArea() ); + } + } + } +} + +void SwVirtFlyDrawObj::TakeObjInfo( SdrObjTransformInfoRec& rInfo ) const +{ + rInfo.bMoveAllowed = + rInfo.bResizeFreeAllowed = rInfo.bResizePropAllowed = true; + + // RotGrfFlyFrame: Some rotation may be allowed + rInfo.bRotateFreeAllowed = rInfo.bRotate90Allowed = HasLimitedRotation(); + + rInfo.bMirrorFreeAllowed = rInfo.bMirror45Allowed = + rInfo.bMirror90Allowed = rInfo.bShearAllowed = + rInfo.bCanConvToPath = rInfo.bCanConvToPoly = + rInfo.bCanConvToPathLineToArea = rInfo.bCanConvToPolyLineToArea = false; +} + +// SwVirtFlyDrawObj - Size Determination + +void SwVirtFlyDrawObj::SetRect() const +{ + if ( GetFlyFrame()->getFrameArea().HasArea() ) + const_cast<SwVirtFlyDrawObj*>(this)->aOutRect = GetFlyFrame()->getFrameArea().SVRect(); + else + const_cast<SwVirtFlyDrawObj*>(this)->aOutRect = tools::Rectangle(); +} + +const tools::Rectangle& SwVirtFlyDrawObj::GetCurrentBoundRect() const +{ + SetRect(); + return aOutRect; +} + +const tools::Rectangle& SwVirtFlyDrawObj::GetLastBoundRect() const +{ + return GetCurrentBoundRect(); +} + +void SwVirtFlyDrawObj::RecalcBoundRect() +{ + SetRect(); +} + +void SwVirtFlyDrawObj::RecalcSnapRect() +{ + SetRect(); +} + +const tools::Rectangle& SwVirtFlyDrawObj::GetSnapRect() const +{ + SetRect(); + return aOutRect; +} + +void SwVirtFlyDrawObj::SetSnapRect(const tools::Rectangle& ) +{ + tools::Rectangle aTmp( GetLastBoundRect() ); + SetRect(); + SetChanged(); + BroadcastObjectChange(); + if (pUserCall!=nullptr) + pUserCall->Changed(*this, SdrUserCallType::Resize, aTmp); +} + +void SwVirtFlyDrawObj::NbcSetSnapRect(const tools::Rectangle& ) +{ + SetRect(); +} + +const tools::Rectangle& SwVirtFlyDrawObj::GetLogicRect() const +{ + SetRect(); + return aOutRect; +} + +void SwVirtFlyDrawObj::SetLogicRect(const tools::Rectangle& ) +{ + tools::Rectangle aTmp( GetLastBoundRect() ); + SetRect(); + SetChanged(); + BroadcastObjectChange(); + if (pUserCall!=nullptr) + pUserCall->Changed(*this, SdrUserCallType::Resize, aTmp); +} + +void SwVirtFlyDrawObj::NbcSetLogicRect(const tools::Rectangle& ) +{ + SetRect(); +} + +::basegfx::B2DPolyPolygon SwVirtFlyDrawObj::TakeXorPoly() const +{ + const tools::Rectangle aSourceRectangle(GetFlyFrame()->getFrameArea().SVRect()); + const ::basegfx::B2DRange aSourceRange = vcl::unotools::b2DRectangleFromRectangle(aSourceRectangle); + ::basegfx::B2DPolyPolygon aRetval; + + aRetval.append(::basegfx::utils::createPolygonFromRect(aSourceRange)); + + return aRetval; +} + +// SwVirtFlyDrawObj::Move() and Resize() +void SwVirtFlyDrawObj::NbcMove(const Size& rSiz) +{ + if(GetFlyFrame()->IsFlyFreeFrame() && static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame()) + { + // RotateFlyFrame3: When we have a change and are in transformed state (e.g. rotation used), + // we need to fall back to the un-transformed state to keep the old code below + // working properly. Restore FrameArea and use aOutRect from old FrameArea. + TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(GetFlyFrame())->getTransformableSwFrame()); + pTransformableSwFrame->restoreFrameAreas(); + aOutRect = GetFlyFrame()->getFrameArea().SVRect(); + } + + aOutRect.Move( rSiz ); + const Point aOldPos( GetFlyFrame()->getFrameArea().Pos() ); + const Point aNewPos( aOutRect.TopLeft() ); + const SwRect aFlyRect( aOutRect ); + + //If the Fly has an automatic align (right or top), + //so preserve the automatic. + SwFrameFormat *pFormat = GetFlyFrame()->GetFormat(); + const sal_Int16 eHori = pFormat->GetHoriOrient().GetHoriOrient(); + const sal_Int16 eVert = pFormat->GetVertOrient().GetVertOrient(); + const sal_Int16 eRelHori = pFormat->GetHoriOrient().GetRelationOrient(); + const sal_Int16 eRelVert = pFormat->GetVertOrient().GetRelationOrient(); + //On paragraph bound Flys starting from the new position a new + //anchor must be set. Anchor and the new RelPos is calculated and + //placed by the Fly itself. + if( GetFlyFrame()->IsFlyAtContentFrame() ) + { + static_cast<SwFlyAtContentFrame*>(GetFlyFrame())->SetAbsPos( aNewPos ); + } + else + { + const SwFrameFormat *pTmpFormat = GetFormat(); + const SwFormatVertOrient &rVert = pTmpFormat->GetVertOrient(); + const SwFormatHoriOrient &rHori = pTmpFormat->GetHoriOrient(); + long lXDiff = aNewPos.X() - aOldPos.X(); + if( rHori.IsPosToggle() && text::HoriOrientation::NONE == eHori && + !GetFlyFrame()->FindPageFrame()->OnRightPage() ) + lXDiff = -lXDiff; + + if( GetFlyFrame()->GetAnchorFrame()->IsRightToLeft() && + text::HoriOrientation::NONE == eHori ) + lXDiff = -lXDiff; + + long lYDiff = aNewPos.Y() - aOldPos.Y(); + if( GetFlyFrame()->GetAnchorFrame()->IsVertical() ) + { + //lXDiff -= rVert.GetPos(); + //lYDiff += rHori.GetPos(); + + if ( GetFlyFrame()->GetAnchorFrame()->IsVertLR() ) + { + lXDiff += rVert.GetPos(); + lXDiff = -lXDiff; + } + else + { + lXDiff -= rVert.GetPos(); + lYDiff += rHori.GetPos(); + } + } + else + { + lXDiff += rHori.GetPos(); + lYDiff += rVert.GetPos(); + } + + if( GetFlyFrame()->GetAnchorFrame()->IsRightToLeft() && + text::HoriOrientation::NONE != eHori ) + lXDiff = GetFlyFrame()->GetAnchorFrame()->getFrameArea().Width() - + aFlyRect.Width() - lXDiff; + + const Point aTmp( lXDiff, lYDiff ); + GetFlyFrame()->ChgRelPos( aTmp ); + } + + SwAttrSet aSet( pFormat->GetDoc()->GetAttrPool(), + RES_VERT_ORIENT, RES_HORI_ORIENT ); + SwFormatHoriOrient aHori( pFormat->GetHoriOrient() ); + SwFormatVertOrient aVert( pFormat->GetVertOrient() ); + bool bPut = false; + + if( !GetFlyFrame()->IsFlyLayFrame() && + ::GetHtmlMode(pFormat->GetDoc()->GetDocShell()) ) + { + //In HTML-Mode only automatic aligns are allowed. + //Only we can try a snap to left/right respectively left-/right border + const SwFrame* pAnch = GetFlyFrame()->GetAnchorFrame(); + bool bNextLine = false; + + if( !GetFlyFrame()->IsAutoPos() || text::RelOrientation::PAGE_FRAME != aHori.GetRelationOrient() ) + { + if( text::RelOrientation::CHAR == eRelHori ) + { + aHori.SetHoriOrient( text::HoriOrientation::LEFT ); + aHori.SetRelationOrient( text::RelOrientation::CHAR ); + } + else + { + bNextLine = true; + //Horizontal Align: + const bool bLeftFrame = + aFlyRect.Left() < pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Left(), + bLeftPrt = aFlyRect.Left() + aFlyRect.Width() < + pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width()/2; + if ( bLeftFrame || bLeftPrt ) + { + aHori.SetHoriOrient( text::HoriOrientation::LEFT ); + aHori.SetRelationOrient( bLeftFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA ); + } + else + { + const bool bRightFrame = aFlyRect.Left() > + pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width(); + aHori.SetHoriOrient( text::HoriOrientation::RIGHT ); + aHori.SetRelationOrient( bRightFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA ); + } + } + aSet.Put( aHori ); + } + //Vertical alignment simply is retained principally, + //only on manual align will be switched over. + bool bRelChar = text::RelOrientation::CHAR == eRelVert; + aVert.SetVertOrient( eVert != text::VertOrientation::NONE ? eVert : + GetFlyFrame()->IsFlyInContentFrame() ? text::VertOrientation::CHAR_CENTER : + bRelChar && bNextLine ? text::VertOrientation::CHAR_TOP : text::VertOrientation::TOP ); + if( bRelChar ) + aVert.SetRelationOrient( text::RelOrientation::CHAR ); + else + aVert.SetRelationOrient( text::RelOrientation::PRINT_AREA ); + aSet.Put( aVert ); + bPut = true; + } + + //We want preferably not to lose the automatic alignments. + if ( !bPut && bInResize ) + { + if ( text::HoriOrientation::NONE != eHori ) + { + aHori.SetHoriOrient( eHori ); + aHori.SetRelationOrient( eRelHori ); + aSet.Put( aHori ); + bPut = true; + } + if ( text::VertOrientation::NONE != eVert ) + { + aVert.SetVertOrient( eVert ); + aVert.SetRelationOrient( eRelVert ); + aSet.Put( aVert ); + bPut = true; + } + } + if ( bPut ) + pFormat->SetFormatAttr( aSet ); +} + + +void SwVirtFlyDrawObj::NbcCrop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) +{ + // Get Wrt Shell + SwWrtShell *pSh = dynamic_cast<SwWrtShell*>( GetFlyFrame()->getRootFrame()->GetCurrShell() ); + + if (!pSh) + { + return; + } + + GraphicObject const *pGraphicObject = pSh->GetGraphicObj(); + + if (!pGraphicObject) + { + return; + } + + // Get graphic object size in 100th of mm + const MapMode aMapMode100thmm(MapUnit::Map100thMM); + Size aGraphicSize(pGraphicObject->GetPrefSize()); + + if( MapUnit::MapPixel == pGraphicObject->GetPrefMapMode().GetMapUnit() ) + { + aGraphicSize = Application::GetDefaultDevice()->PixelToLogic( aGraphicSize, aMapMode100thmm ); + } + else + { + aGraphicSize = OutputDevice::LogicToLogic( aGraphicSize, pGraphicObject->GetPrefMapMode(), aMapMode100thmm); + } + + if( aGraphicSize.IsEmpty() ) + { + return ; + } + + const bool bIsTransformableSwFrame( + GetFlyFrame()->IsFlyFreeFrame() && + static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame()); + + if(bIsTransformableSwFrame) + { + // When we have a change and are in transformed state (e.g. rotation used), + // we need to fall back to the un-transformed state to keep the old code below + // working properly. Restore FrameArea and use aOutRect from old FrameArea. + TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(GetFlyFrame())->getTransformableSwFrame()); + pTransformableSwFrame->restoreFrameAreas(); + aOutRect = GetFlyFrame()->getFrameArea().SVRect(); + } + + // Compute old and new rect. This will give us the deformation to apply to + // the object to crop. OldRect is the inner frame, see getFullDragClone() + // below where getFramePrintAreaTransformation is used as object geometry for Crop + const tools::Rectangle aOldRect( + GetFlyFrame()->getFrameArea().TopLeft() + GetFlyFrame()->getFramePrintArea().TopLeft(), + GetFlyFrame()->getFramePrintArea().SSize()); + const long nOldWidth(aOldRect.GetWidth()); + const long nOldHeight(aOldRect.GetHeight()); + + if (!nOldWidth || !nOldHeight) + { + return; + } + + // rRef is relative to the Crop-Action, si in X/Y-Ranges of [0.0 .. 1.0], + // to get the correct absolute position, transform using the old Rect + const Point aRef( + aOldRect.Left() + basegfx::fround(aOldRect.GetWidth() * rRef.getX()), + aOldRect.Top() + basegfx::fround(aOldRect.GetHeight() * rRef.getY())); + + // apply transformation, use old ResizeRect for now + tools::Rectangle aNewRect( aOldRect ); + ResizeRect( + aNewRect, + aRef, + Fraction(fxFact), + Fraction(fyFact)); + + // Get old values for crop in 10th of mm + SfxItemSet aSet( pSh->GetAttrPool(), svl::Items<RES_GRFATR_CROPGRF, RES_GRFATR_CROPGRF>{} ); + pSh->GetCurAttr( aSet ); + SwCropGrf aCrop( aSet.Get(RES_GRFATR_CROPGRF) ); + + tools::Rectangle aCropRectangle( + convertTwipToMm100(aCrop.GetLeft()), + convertTwipToMm100(aCrop.GetTop()), + convertTwipToMm100(aCrop.GetRight()), + convertTwipToMm100(aCrop.GetBottom()) ); + + // Compute delta to apply + double fScaleX = ( aGraphicSize.Width() - aCropRectangle.Left() - aCropRectangle.Right() ) / static_cast<double>(nOldWidth); + double fScaleY = ( aGraphicSize.Height() - aCropRectangle.Top() - aCropRectangle.Bottom() ) / static_cast<double>(nOldHeight); + + sal_Int32 nDiffLeft = aNewRect.Left() - aOldRect.Left(); + sal_Int32 nDiffTop = aNewRect.Top() - aOldRect.Top(); + sal_Int32 nDiffRight = aNewRect.Right() - aOldRect.Right(); + sal_Int32 nDiffBottom = aNewRect.Bottom() - aOldRect.Bottom(); + + // Compute new values in 10th of mm + sal_Int32 nLeftCrop = static_cast<sal_Int32>( aCropRectangle.Left() + nDiffLeft * fScaleX ); + sal_Int32 nTopCrop = static_cast<sal_Int32>( aCropRectangle.Top() + nDiffTop * fScaleY ); + sal_Int32 nRightCrop = static_cast<sal_Int32>( aCropRectangle.Right() - nDiffRight * fScaleX ); + sal_Int32 nBottomCrop = static_cast<sal_Int32>( aCropRectangle.Bottom() - nDiffBottom * fScaleY ); + + // Apply values + pSh->StartAllAction(); + // pSh->StartUndo(SwUndoId::START); + + // Set new crop values in twips + aCrop.SetLeft (convertMm100ToTwip(nLeftCrop)); + aCrop.SetTop (convertMm100ToTwip(nTopCrop)); + aCrop.SetRight (convertMm100ToTwip(nRightCrop)); + aCrop.SetBottom(convertMm100ToTwip(nBottomCrop)); + pSh->SetAttrItem(aCrop); + + // Set new frame size + SwFrameFormat *pFormat = GetFormat(); + SwFormatFrameSize aSz( pFormat->GetFrameSize() ); + const long aNewWidth(aNewRect.GetWidth() + (aOutRect.GetWidth() - aOldRect.GetWidth())); + const long aNewHeight(aNewRect.GetHeight() + (aOutRect.GetHeight() - aOldRect.GetHeight())); + aSz.SetWidth(aNewWidth); + aSz.SetHeight(aNewHeight); + pFormat->GetDoc()->SetAttr( aSz, *pFormat ); + + // add move - to make result look better. Fill with defaults + // for the untransformed case + Point aNewTopLeft(aNewRect.TopLeft()); + const Point aOldTopLeft(aOldRect.TopLeft()); + + if(bIsTransformableSwFrame) + { + // Need to correct the NewTopLeft position in transformed state to make + // the interaction look correct. First, extract rotation + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + GetFlyFrame()->getFrameAreaTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // calc the center of the unchanged object + const basegfx::B2DPoint aFormerCenter( + GetFlyFrame()->getFrameAreaTransformation() * basegfx::B2DPoint(0.5, 0.5)); + + // define the existing rotation around that former center + const basegfx::B2DHomMatrix aRotFormerCenter( + basegfx::utils::createRotateAroundPoint( + aFormerCenter.getX(), + aFormerCenter.getY(), + fRotate)); + + // use the new center of the unrotated object, rotate it around the + // former center + const Point aNewCenter(aNewRect.Center()); + const basegfx::B2DPoint aRotNewCenter( + aRotFormerCenter * basegfx::B2DPoint(aNewCenter.X(), aNewCenter.Y())); + + // Create the new TopLeft of the unrotated, cropped object by creating + // as if re-creating the unrotated geometry + aNewTopLeft = Point( + basegfx::fround(aRotNewCenter.getX() - (0.5 * aNewRect.getWidth())), + basegfx::fround(aRotNewCenter.getY() - (0.5 * aNewRect.getHeight()))); + } + + // check if we have movement and execute if yes + const Size aDeltaMove( + aNewTopLeft.X() - aOldTopLeft.X(), + aNewTopLeft.Y() - aOldTopLeft.Y()); + + if(0 != aDeltaMove.Width() || 0 != aDeltaMove.Height()) + { + NbcMove(aDeltaMove); + } + + // pSh->EndUndo(SwUndoId::END); + pSh->EndAllAction(); +} + +void SwVirtFlyDrawObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + const SwFrame* pTmpFrame = GetFlyFrame()->GetAnchorFrame(); + + if( !pTmpFrame ) + { + pTmpFrame = GetFlyFrame(); + } + + const bool bVertX(pTmpFrame->IsVertical()); + const bool bRTL(pTmpFrame->IsRightToLeft()); + const bool bVertL2RX(pTmpFrame->IsVertLR()); + const bool bUseRightEdge((bVertX && !bVertL2RX ) || bRTL); + const bool bIsTransformableSwFrame( + GetFlyFrame()->IsFlyFreeFrame() && + static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame()); + + if(bIsTransformableSwFrame) + { + // When we have a change in transformed state, we need to fall back to the + // state without possible transformations. + // In the Resize case to correctly handle the changes, apply to the transformation + // and extract the new, untransformed state from that modified transformation + basegfx::B2DHomMatrix aNewMat(GetFlyFrame()->getFrameAreaTransformation()); + const basegfx::B2DPoint aRef(rRef.X(), rRef.Y()); + + // apply state to already valid transformation + aNewMat.translate(-aRef.getX(), -aRef.getY()); + aNewMat.scale(double(xFact), double(yFact)); + aNewMat.translate(aRef.getX(), aRef.getY()); + + // get center of transformed state + const basegfx::B2DPoint aCenter(aNewMat * basegfx::B2DPoint(0.5, 0.5)); + + // decompose to extract scale + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + aNewMat.decompose(aScale, aTranslate, fRotate, fShearX); + const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale)); + + // create new modified, but untransformed OutRect + aOutRect = tools::Rectangle( + basegfx::fround(aCenter.getX() - (0.5 * aAbsScale.getX())), + basegfx::fround(aCenter.getY() - (0.5 * aAbsScale.getY())), + basegfx::fround(aCenter.getX() + (0.5 * aAbsScale.getX())), + basegfx::fround(aCenter.getY() + (0.5 * aAbsScale.getY()))); + + // restore FrameAreas so that actions below not adapted to new + // full transformations take the correct actions + TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(GetFlyFrame())->getTransformableSwFrame()); + pTransformableSwFrame->restoreFrameAreas(); + } + else + { + ResizeRect( aOutRect, rRef, xFact, yFact ); + } + + // Position may also change, remember old one. This is now already + // the one in the unrotated, old coordinate system + Point aOldPos(bUseRightEdge ? GetFlyFrame()->getFrameArea().TopRight() : GetFlyFrame()->getFrameArea().Pos()); + + // get target size in old coordinate system + Size aSz( aOutRect.Right() - aOutRect.Left() + 1, aOutRect.Bottom()- aOutRect.Top() + 1 ); + + // compare with restored FrameArea + if( aSz != GetFlyFrame()->getFrameArea().SSize() ) + { + //The width of the columns should not be too narrow + if ( GetFlyFrame()->Lower() && GetFlyFrame()->Lower()->IsColumnFrame() ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), GetFlyFrame() ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + long nMin = rAttrs.CalcLeftLine()+rAttrs.CalcRightLine(); + const SwFormatCol& rCol = rAttrs.GetAttrSet().GetCol(); + if ( rCol.GetColumns().size() > 1 ) + { + for ( const auto &rC : rCol.GetColumns() ) + { + nMin += rC.GetLeft() + rC.GetRight() + MINFLY; + } + nMin -= MINFLY; + } + aSz.setWidth( std::max( aSz.Width(), nMin ) ); + } + + SwFrameFormat *pFormat = GetFormat(); + const SwFormatFrameSize aOldFrameSz( pFormat->GetFrameSize() ); + GetFlyFrame()->ChgSize( aSz ); + SwFormatFrameSize aFrameSz( pFormat->GetFrameSize() ); + + if ( aFrameSz.GetWidthPercent() || aFrameSz.GetHeightPercent() ) + { + long nRelWidth, nRelHeight; + const SwFrame *pRel = GetFlyFrame()->IsFlyLayFrame() ? + GetFlyFrame()->GetAnchorFrame() : + GetFlyFrame()->GetAnchorFrame()->GetUpper(); + const SwViewShell *pSh = GetFlyFrame()->getRootFrame()->GetCurrShell(); + + if ( pSh && pRel->IsBodyFrame() && + pSh->GetViewOptions()->getBrowseMode() && + pSh->VisArea().HasArea() ) + { + nRelWidth = pSh->GetBrowseWidth(); + nRelHeight = pSh->VisArea().Height(); + const Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); + nRelHeight -= 2*aBorder.Height(); + } + else + { + nRelWidth = pRel->getFramePrintArea().Width(); + nRelHeight = pRel->getFramePrintArea().Height(); + } + + if ( aFrameSz.GetWidthPercent() && aFrameSz.GetWidthPercent() != SwFormatFrameSize::SYNCED && + aOldFrameSz.GetWidth() != aFrameSz.GetWidth() ) + { + aFrameSz.SetWidthPercent( sal_uInt8(aSz.Width() * 100.0 / nRelWidth + 0.5) ); + } + + if ( aFrameSz.GetHeightPercent() && aFrameSz.GetHeightPercent() != SwFormatFrameSize::SYNCED && + aOldFrameSz.GetHeight() != aFrameSz.GetHeight() ) + { + aFrameSz.SetHeightPercent( sal_uInt8(aSz.Height() * 100.0 / nRelHeight + 0.5) ); + } + + pFormat->GetDoc()->SetAttr( aFrameSz, *pFormat ); + } + } + + //Position can also be changed, get new one + const Point aNewPos(bUseRightEdge ? aOutRect.Right() + 1 : aOutRect.Left(), aOutRect.Top()); + + if ( aNewPos != aOldPos ) + { + // Former late change in aOutRect by ChgSize + // is now taken into account directly by calculating + // aNewPos *after* calling ChgSize (see old code). + // Still need to adapt aOutRect since the 'Move' is already applied + // here (see ResizeRect) and it's the same SdrObject + const Size aDeltaMove( + aNewPos.X() - aOldPos.X(), + aNewPos.Y() - aOldPos.Y()); + aOutRect.Move(-aDeltaMove.Width(), -aDeltaMove.Height()); + + // Now, move as needed (no empty delta which was a hack anyways) + if(bIsTransformableSwFrame) + { + // need to save aOutRect to FrameArea, will be restored to aOutRect in + // SwVirtFlyDrawObj::NbcMove currently for TransformableSwFrames + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*GetFlyFrame()); + aFrm.setSwRect(aOutRect); + } + + // keep old hack - not clear what happens here + bInResize = true; + NbcMove(aDeltaMove); + bInResize = false; + } +} + +void SwVirtFlyDrawObj::Move(const Size& rSiz) +{ + NbcMove( rSiz ); + SetChanged(); + GetFormat()->GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); +} + +void SwVirtFlyDrawObj::Resize(const Point& rRef, + const Fraction& xFact, const Fraction& yFact, bool /*bUnsetRelative*/) +{ + NbcResize( rRef, xFact, yFact ); + SetChanged(); + GetFormat()->GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); +} + +void SwVirtFlyDrawObj::Crop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) +{ + NbcCrop( rRef, fxFact, fyFact ); + SetChanged(); + GetFormat()->GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); +} + +// RotGrfFlyFrame: Helper to access possible rotation of Graphic contained in FlyFrame +sal_uInt16 SwVirtFlyDrawObj::getPossibleRotationFromFraphicFrame(Size& rSize) const +{ + sal_uInt16 nRetval(0); + const SwNoTextFrame* pNoTx = dynamic_cast< const SwNoTextFrame* >(GetFlyFrame()->Lower()); + + if(pNoTx) + { + SwNoTextNode& rNoTNd = const_cast< SwNoTextNode& >(*static_cast<const SwNoTextNode*>(pNoTx->GetNode())); + SwGrfNode* pGrfNd = rNoTNd.GetGrfNode(); + + if(nullptr != pGrfNd) + { + const SwAttrSet& rSet = pGrfNd->GetSwAttrSet(); + const SwRotationGrf& rRotation = rSet.GetRotationGrf(); + + rSize = rRotation.GetUnrotatedSize(); + nRetval = rRotation.GetValue(); + } + } + + return nRetval; +} + +long SwVirtFlyDrawObj::GetRotateAngle() const +{ + if(ContainsSwGrfNode()) + { + Size aSize; + return getPossibleRotationFromFraphicFrame(aSize); + } + else + { + return SdrVirtObj::GetRotateAngle(); + } +} + +SdrObjectUniquePtr SwVirtFlyDrawObj::getFullDragClone() const +{ + // call parent + SdrObjectUniquePtr pRetval = SdrVirtObj::getFullDragClone(); + + if(pRetval && GetFlyFrame() && ContainsSwGrfNode()) + { + // RotGrfFlyFrame3: get inner bounds/transformation + const basegfx::B2DHomMatrix aTargetTransform(GetFlyFrame()->getFramePrintAreaTransformation()); + + pRetval->TRSetBaseGeometry(aTargetTransform, basegfx::B2DPolyPolygon()); + } + + return pRetval; +} + +void SwVirtFlyDrawObj::addCropHandles(SdrHdlList& rTarget) const +{ + // RotGrfFlyFrame: Adapt to possible rotated Graphic contained in FlyFrame + if(GetFlyFrame()->getFrameArea().HasArea()) + { + // Use InnerBound, OuterBound (same as GetFlyFrame()->getFrameArea().SVRect()) + // may have a distance to InnerBound which needs to be taken into account. + // The Graphic is mapped to InnerBound, as is the rotated Graphic. + const basegfx::B2DRange aTargetRange(getInnerBound()); + + if(!aTargetRange.isEmpty()) + { + // RotGrfFlyFrame3: get inner bounds/transformation + const basegfx::B2DHomMatrix aTargetTransform(GetFlyFrame()->getFramePrintAreaTransformation()); + + // break up matrix + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate(0.0); + double fShearX(0.0); + aTargetTransform.decompose(aScale, aTranslate, fRotate, fShearX); + basegfx::B2DPoint aPos; + + aPos = aTargetTransform * basegfx::B2DPoint(0.0, 0.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::UpperLeft, fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(0.5, 0.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Upper, fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(1.0, 0.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::UpperRight, fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(0.0, 0.5); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Left , fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(1.0, 0.5); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Right, fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(0.0, 1.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::LowerLeft, fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(0.5, 1.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Lower, fShearX, fRotate)); + aPos = aTargetTransform * basegfx::B2DPoint(1.0, 1.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::LowerRight, fShearX, fRotate)); + } + } +} + +// Macro + +PointerStyle SwVirtFlyDrawObj::GetMacroPointer( + const SdrObjMacroHitRec& ) const +{ + return PointerStyle::RefHand; +} + +bool SwVirtFlyDrawObj::HasMacro() const +{ + const SwFormatURL &rURL = m_pFlyFrame->GetFormat()->GetURL(); + return rURL.GetMap() || !rURL.GetURL().isEmpty(); +} + +SdrObject* SwVirtFlyDrawObj::CheckMacroHit( const SdrObjMacroHitRec& rRec ) const +{ + const SwFormatURL &rURL = m_pFlyFrame->GetFormat()->GetURL(); + if( rURL.GetMap() || !rURL.GetURL().isEmpty() ) + { + SwRect aRect; + if ( m_pFlyFrame->Lower() && m_pFlyFrame->Lower()->IsNoTextFrame() ) + { + aRect = m_pFlyFrame->getFramePrintArea(); + aRect += m_pFlyFrame->getFrameArea().Pos(); + } + else + aRect = m_pFlyFrame->getFrameArea(); + + if( aRect.IsInside( rRec.aPos ) ) + { + aRect.Pos().setX(aRect.Pos().getX() + rRec.nTol); + aRect.Pos().setY(aRect.Pos().getY() + rRec.nTol); + aRect.AddHeight( -(2 * rRec.nTol) ); + aRect.AddWidth( -(2 * rRec.nTol) ); + + if( aRect.IsInside( rRec.aPos ) ) + { + if( !rURL.GetMap() || + m_pFlyFrame->GetFormat()->GetIMapObject( rRec.aPos, m_pFlyFrame )) + return const_cast<SwVirtFlyDrawObj*>(this); + + return nullptr; + } + } + } + return SdrObject::CheckMacroHit( rRec ); +} + +bool SwVirtFlyDrawObj::IsTextBox() const +{ + return SwTextBoxHelper::isTextBox(GetFormat(), RES_FLYFRMFMT); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/dobjfac.cxx b/sw/source/core/draw/dobjfac.cxx new file mode 100644 index 000000000..31bef2f94 --- /dev/null +++ b/sw/source/core/draw/dobjfac.cxx @@ -0,0 +1,38 @@ +/* -*- 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 <dobjfac.hxx> +#include <dflyobj.hxx> + +SwObjectFactory aSwObjectFactory; + +IMPL_STATIC_LINK( + SwObjectFactory, MakeObject, SdrObjCreatorParams, aParams, SdrObject* ) +{ + if ( aParams.nInventor == SdrInventor::Swg ) + { + // No switch, there's only one at the moment + OSL_ENSURE( aParams.nObjIdentifier == SwFlyDrawObjIdentifier, + "Wrong inventor or identifier" ); + return new SwFlyDrawObj(aParams.rSdrModel); + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/dpage.cxx b/sw/source/core/draw/dpage.cxx new file mode 100644 index 000000000..39e5f93e3 --- /dev/null +++ b/sw/source/core/draw/dpage.cxx @@ -0,0 +1,254 @@ +/* -*- 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 <editeng/flditem.hxx> +#include <vcl/imapobj.hxx> +#include <svl/urihelper.hxx> +#include <sfx2/sfxhelp.hxx> +#include <vcl/help.hxx> +#include <svx/svdview.hxx> +#include <fmturl.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <viewimp.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <viewsh.hxx> +#include <drawdoc.hxx> +#include <dpage.hxx> +#include <dcontact.hxx> +#include <dflyobj.hxx> +#include <docsh.hxx> +#include <flyfrm.hxx> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::frame; + +SwDPage::SwDPage(SwDrawModel& rNewModel, bool bMasterPage) +: FmFormPage(rNewModel, bMasterPage), + pDoc(&rNewModel.GetDoc()) +{ +} + +SwDPage::~SwDPage() +{ +} + +void SwDPage::lateInit(const SwDPage& rSrcPage) +{ + FmFormPage::lateInit( rSrcPage ); + + if ( rSrcPage.pGridLst ) + { + pGridLst.reset( new SdrPageGridFrameList ); + for ( sal_uInt16 i = 0; i != rSrcPage.pGridLst->GetCount(); ++i ) + pGridLst->Insert( ( *rSrcPage.pGridLst )[ i ] ); + } +} + +SwDPage* SwDPage::CloneSdrPage(SdrModel& rTargetModel) const +{ + SwDrawModel& rSwDrawModel(static_cast< SwDrawModel& >(rTargetModel)); + SwDPage* pClonedSwDPage( + new SwDPage( + rSwDrawModel, + IsMasterPage())); + pClonedSwDPage->lateInit(*this); + return pClonedSwDPage; +} + +SdrObject* SwDPage::ReplaceObject( SdrObject* pNewObj, size_t nObjNum ) +{ + SdrObject *pOld = GetObj( nObjNum ); + OSL_ENSURE( pOld, "Oups, Object not replaced" ); + SdrObjUserCall* pContact; + if ( nullptr != ( pContact = GetUserCall(pOld) ) && + RES_DRAWFRMFMT == static_cast<SwContact*>(pContact)->GetFormat()->Which()) + static_cast<SwDrawContact*>(pContact)->ChangeMasterObject( pNewObj ); + return FmFormPage::ReplaceObject( pNewObj, nObjNum ); +} + +static void InsertGridFrame( SdrPageGridFrameList *pLst, const SwFrame *pPg ) +{ + SwRect aPrt( pPg->getFramePrintArea() ); + aPrt += pPg->getFrameArea().Pos(); + const tools::Rectangle aUser( aPrt.SVRect() ); + const tools::Rectangle aPaper( pPg->getFrameArea().SVRect() ); + pLst->Insert( SdrPageGridFrame( aPaper, aUser ) ); +} + +const SdrPageGridFrameList* SwDPage::GetGridFrameList( + const SdrPageView* pPV, const tools::Rectangle *pRect ) const +{ + SwViewShell* pSh = static_cast< SwDrawModel& >(getSdrModelFromSdrPage()).GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell(); + if(pSh) + { + for(SwViewShell& rShell : pSh->GetRingContainer()) + { + if(rShell.Imp()->GetPageView() == pPV) + { + pSh = &rShell; + break; + } + } + if ( pGridLst ) + const_cast<SwDPage*>(this)->pGridLst->Clear(); + else + const_cast<SwDPage*>(this)->pGridLst.reset( new SdrPageGridFrameList ); + + if ( pRect ) + { + //The drawing demands all pages which overlap with the rest. + const SwRect aRect( *pRect ); + const SwFrame *pPg = pSh->GetLayout()->Lower(); + do + { if ( pPg->getFrameArea().IsOver( aRect ) ) + ::InsertGridFrame( const_cast<SwDPage*>(this)->pGridLst.get(), pPg ); + pPg = pPg->GetNext(); + } while ( pPg ); + } + else + { + //The drawing demands all visible pages + const SwFrame *pPg = pSh->Imp()->GetFirstVisPage(pSh->GetOut()); + if ( pPg ) + do + { ::InsertGridFrame( const_cast<SwDPage*>(this)->pGridLst.get(), pPg ); + pPg = pPg->GetNext(); + } while ( pPg && pPg->getFrameArea().IsOver( pSh->VisArea() ) ); + } + } + return pGridLst.get(); +} + +bool SwDPage::RequestHelp( vcl::Window* pWindow, SdrView const * pView, + const HelpEvent& rEvt ) +{ + assert( pDoc ); + + bool bContinue = true; + + if( rEvt.GetMode() & ( HelpEventMode::QUICK | HelpEventMode::BALLOON )) + { + Point aPos( rEvt.GetMousePosPixel() ); + aPos = pWindow->ScreenToOutputPixel( aPos ); + aPos = pWindow->PixelToLogic( aPos ); + + SdrPageView* pPV; + SdrObject* pObj = pView->PickObj(aPos, 0, pPV, SdrSearchOptions::PICKMACRO); + SwVirtFlyDrawObj* pDrawObj = dynamic_cast<SwVirtFlyDrawObj*>(pObj); + OUString sText; + tools::Rectangle aPixRect; + if (pDrawObj) + { + SwFlyFrame *pFly = pDrawObj->GetFlyFrame(); + + aPixRect = pWindow->LogicToPixel(pFly->getFrameArea().SVRect()); + + const SwFormatURL &rURL = pFly->GetFormat()->GetURL(); + if( rURL.GetMap() ) + { + IMapObject *pTmpObj = pFly->GetFormat()->GetIMapObject( aPos, pFly ); + if( pTmpObj ) + { + sText = pTmpObj->GetAltText(); + if ( sText.isEmpty() ) + sText = URIHelper::removePassword( pTmpObj->GetURL(), + INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::Unambiguous); + } + } + else if ( !rURL.GetURL().isEmpty() ) + { + sText = URIHelper::removePassword( rURL.GetURL(), + INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::Unambiguous); + + if( rURL.IsServerMap() ) + { + // then append the relative pixel position!! + Point aPt( aPos ); + aPt -= pFly->getFrameArea().Pos(); + // without MapMode-Offset !!!!! + // without MapMode-Offset, without Offset, w ... !!!!! + aPt = pWindow->LogicToPixel( + aPt, MapMode( MapUnit::MapTwip ) ); + sText += "?" + OUString::number( aPt.getX() ) + + "," + OUString::number( aPt.getY() ); + } + } + } + else + { + SdrViewEvent aVEvt; + MouseEvent aMEvt(pWindow->ScreenToOutputPixel(rEvt.GetMousePosPixel()), 1, + MouseEventModifiers::NONE, MOUSE_LEFT); + pView->PickAnything(aMEvt, SdrMouseEventKind::BUTTONDOWN, aVEvt); + if (aVEvt.eEvent == SdrEventKind::ExecuteUrl) + { + sText = aVEvt.pURLField->GetURL(); + aPixRect = pWindow->LogicToPixel(aVEvt.pObj->GetLogicRect()); + } + } + + if (!sText.isEmpty()) + { + // #i80029# + bool bExecHyperlinks = pDoc->GetDocShell()->IsReadOnly(); + if (!bExecHyperlinks) + sText = SfxHelp::GetURLHelpText(sText); + + // then display the help: + tools::Rectangle aScreenRect(pWindow->OutputToScreenPixel(aPixRect.TopLeft()), + pWindow->OutputToScreenPixel(aPixRect.BottomRight())); + + if (rEvt.GetMode() & HelpEventMode::BALLOON) + Help::ShowBalloon(pWindow, rEvt.GetMousePosPixel(), aScreenRect, sText); + else + Help::ShowQuickHelp(pWindow, aScreenRect, sText); + bContinue = false; + } + } + + if( bContinue ) + bContinue = !FmFormPage::RequestHelp( pWindow, pView, rEvt ); + + return bContinue; +} + +Reference< XInterface > SwDPage::createUnoPage() +{ + assert( pDoc ); + + Reference < XInterface > xRet; + SwDocShell* pDocShell = pDoc->GetDocShell(); + if ( pDocShell ) + { + Reference<XModel> xModel = pDocShell->GetBaseModel(); + Reference<XDrawPageSupplier> xPageSupp(xModel, UNO_QUERY); + xRet = xPageSupp->getDrawPage(); + } + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/drawdoc.cxx b/sw/source/core/draw/drawdoc.cxx new file mode 100644 index 000000000..7fa2bd715 --- /dev/null +++ b/sw/source/core/draw/drawdoc.cxx @@ -0,0 +1,138 @@ +/* -*- 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 <svx/svxids.hrc> + +#include <com/sun/star/frame/XModel.hpp> +#include <svx/drawitem.hxx> +#include <doc.hxx> +#include <drawdoc.hxx> +#include <dpage.hxx> +#include <docsh.hxx> +#include <hintids.hxx> +#include <DocumentSettingManager.hxx> + +using namespace com::sun::star; + +// Constructor +SwDrawModel::SwDrawModel(SwDoc *const pDoc) +: FmFormModel( + &pDoc->GetAttrPool(), + pDoc->GetDocShell()) + , m_pDoc( pDoc ) +{ + SetScaleUnit( MapUnit::MapTwip ); + SetSwapGraphics(); + + // use common InitDrawModelAndDocShell which will set the associations as needed, + // including SvxColorTableItem with WhichID SID_COLOR_TABLE + InitDrawModelAndDocShell(m_pDoc->GetDocShell(), this); + + // copy all the default values to the SdrModel + SfxItemPool* pSdrPool = m_pDoc->GetAttrPool().GetSecondaryPool(); + if( pSdrPool ) + { + const sal_uInt16 aWhichRanges[] = + { + RES_CHRATR_BEGIN, RES_CHRATR_END, + RES_PARATR_BEGIN, RES_PARATR_END, + 0 + }; + + SfxItemPool& rDocPool = m_pDoc->GetAttrPool(); + sal_uInt16 nEdtWhich, nSlotId; + const SfxPoolItem* pItem; + for( const sal_uInt16* pRangeArr = aWhichRanges; + *pRangeArr; pRangeArr += 2 ) + for( sal_uInt16 nW = *pRangeArr, nEnd = *(pRangeArr+1); + nW < nEnd; ++nW ) + if( nullptr != (pItem = rDocPool.GetPoolDefaultItem( nW )) && + 0 != (nSlotId = rDocPool.GetSlotId( nW ) ) && + nSlotId != nW && + 0 != (nEdtWhich = pSdrPool->GetWhich( nSlotId )) && + nSlotId != nEdtWhich ) + { + std::unique_ptr<SfxPoolItem> pCpy(pItem->Clone()); + pCpy->SetWhich( nEdtWhich ); + pSdrPool->SetPoolDefaultItem( *pCpy ); + } + } + + SetForbiddenCharsTable(m_pDoc->GetDocumentSettingManager().getForbiddenCharacterTable()); + // Implementation for asian compression + SetCharCompressType( m_pDoc->GetDocumentSettingManager().getCharacterCompressionType() ); +} + +// Destructor + +SwDrawModel::~SwDrawModel() +{ + Broadcast(SdrHint(SdrHintKind::ModelCleared)); + + ClearModel(true); +} + +/** Create a new page (SdPage) and return a pointer to it back. + * + * The drawing engine is using this method while loading for the creating of + * pages (whose type it not even know, because they are inherited from SdrPage). + * + * @return Pointer to the new page. + */ +SdrPage* SwDrawModel::AllocPage(bool bMasterPage) +{ + SwDPage* pPage = new SwDPage(*this, bMasterPage); + pPage->SetName("Controls"); + return pPage; +} + +uno::Reference<embed::XStorage> SwDrawModel::GetDocumentStorage() const +{ + return m_pDoc->GetDocStorage(); +} + +uno::Reference< uno::XInterface > SwDrawModel::createUnoModel() +{ + uno::Reference< uno::XInterface > xModel; + + try + { + if ( GetDoc().GetDocShell() ) + { + xModel = GetDoc().GetDocShell()->GetModel(); + } + } + catch( uno::RuntimeException& ) + { + OSL_FAIL( "<SwDrawModel::createUnoModel()> - could *not* retrieve model at <SwDocShell>" ); + } + + return xModel; +} + +void SwDrawModel::PutAreaListItems(SfxItemSet& rSet) const +{ + rSet.Put(SvxColorListItem(GetColorList(), SID_COLOR_TABLE)); + rSet.Put(SvxGradientListItem(GetGradientList(), SID_GRADIENT_LIST)); + rSet.Put(SvxHatchListItem(GetHatchList(), SID_HATCH_LIST)); + rSet.Put(SvxBitmapListItem(GetBitmapList(), SID_BITMAP_LIST)); + rSet.Put(SvxPatternListItem(GetPatternList(), SID_PATTERN_LIST)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/draw/dview.cxx b/sw/source/core/draw/dview.cxx new file mode 100644 index 000000000..d02f57e9f --- /dev/null +++ b/sw/source/core/draw/dview.cxx @@ -0,0 +1,998 @@ +/* -*- 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 <hintids.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/fmmodel.hxx> +#include <sot/exchange.hxx> +#include <svx/sdrundomanager.hxx> +#include <tools/globname.hxx> +#include <editeng/outliner.hxx> +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrm.hxx> +#include <frmfmt.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <textboxhelper.hxx> +#include <viewsh.hxx> +#include <viewimp.hxx> +#include <dview.hxx> +#include <doc.hxx> +#include <mdiexp.hxx> +#include <ndole.hxx> +#include <ndgrf.hxx> +#include <fmtanchr.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentLayoutAccess.hxx> + +#include <com/sun/star/embed/Aspects.hpp> + +#include <vector> + +#include <sortedobjs.hxx> +#include <UndoManager.hxx> + +using namespace com::sun::star; + +namespace { + +class SwSdrHdl : public SdrHdl +{ +public: + SwSdrHdl(const Point& rPnt, bool bTopRight ) : + SdrHdl( rPnt, bTopRight ? SdrHdlKind::Anchor_TR : SdrHdlKind::Anchor ) {} + virtual bool IsFocusHdl() const override; +}; + +} + +bool SwSdrHdl::IsFocusHdl() const +{ + if( SdrHdlKind::Anchor == eKind || SdrHdlKind::Anchor_TR == eKind ) + return true; + return SdrHdl::IsFocusHdl(); +} + +static const SwFrame *lcl_FindAnchor( const SdrObject *pObj, bool bAll ) +{ + const SwVirtFlyDrawObj *pVirt = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) != nullptr ? + static_cast<const SwVirtFlyDrawObj*>(pObj) : nullptr; + if ( pVirt ) + { + if ( bAll || !pVirt->GetFlyFrame()->IsFlyInContentFrame() ) + return pVirt->GetFlyFrame()->GetAnchorFrame(); + } + else + { + const SwDrawContact *pCont = static_cast<const SwDrawContact*>(GetUserCall(pObj)); + if ( pCont ) + return pCont->GetAnchorFrame( pObj ); + } + return nullptr; +} + +SwDrawView::SwDrawView( + SwViewShellImp& rI, + FmFormModel& rFmFormModel, + OutputDevice* pOutDev) +: FmFormView(rFmFormModel, pOutDev), + m_rImp( rI ) +{ + SetPageVisible( false ); + SetBordVisible( false ); + SetGridVisible( false ); + SetHlplVisible( false ); + SetGlueVisible( false ); + SetFrameDragSingles(); + SetSwapAsynchron(); + + EnableExtendedKeyInputDispatcher( false ); + EnableExtendedMouseEventDispatcher( false ); + + SetHitTolerancePixel( GetMarkHdlSizePixel()/2 ); + + SetPrintPreview( rI.GetShell()->IsPreview() ); + + // #i73602# Use default from the configuration + SetBufferedOverlayAllowed(getOptionsDrawinglayer().IsOverlayBuffer_Writer()); + + // #i74769#, #i75172# Use default from the configuration + SetBufferedOutputAllowed(getOptionsDrawinglayer().IsPaintBuffer_Writer()); +} + +// #i99665# +bool SwDrawView::IsAntiAliasing() const +{ + return getOptionsDrawinglayer().IsAntiAliasing(); +} + +static SdrObject* impLocalHitCorrection(SdrObject* pRetval, const Point& rPnt, sal_uInt16 nTol, const SdrMarkList &rMrkList) +{ + if(!nTol) + { + // the old method forced back to outer bounds test when nTol == 0, so + // do not try to correct when nTol is not set (used from HelpContent) + } + else + { + // rebuild logic from former SwVirtFlyDrawObj::CheckSdrObjectHit. This is needed since + // the SdrObject-specific CheckHit implementations are now replaced with primitives and + // 'tricks' like in the old implementation (e.g. using a view from a model-data class to + // detect if object is selected) are no longer valid. + // The standard primitive hit-test for SwVirtFlyDrawObj now is the outer bound. The old + // implementation reduced this excluding the inner bound when the object was not selected. + SwVirtFlyDrawObj* pSwVirtFlyDrawObj = dynamic_cast< SwVirtFlyDrawObj* >(pRetval); + + if(pSwVirtFlyDrawObj) + { + if(pSwVirtFlyDrawObj->GetFlyFrame()->Lower() && pSwVirtFlyDrawObj->GetFlyFrame()->Lower()->IsNoTextFrame()) + { + // the old method used IsNoTextFrame (should be for SW's own OLE and + // graphic's) to accept hit only based on outer bounds; nothing to do + } + else + { + // check if the object is selected in this view + const size_t nMarkCount(rMrkList.GetMarkCount()); + bool bSelected(false); + + for(size_t a = 0; !bSelected && a < nMarkCount; ++a) + { + if(pSwVirtFlyDrawObj == rMrkList.GetMark(a)->GetMarkedSdrObj()) + { + bSelected = true; + } + } + + if(!bSelected) + { + // when not selected, the object is not hit when hit position is inside + // inner range. Get and shrink inner range + basegfx::B2DRange aInnerBound(pSwVirtFlyDrawObj->getInnerBound()); + + aInnerBound.grow(-1.0 * nTol); + + if(aInnerBound.isInside(basegfx::B2DPoint(rPnt.X(), rPnt.Y()))) + { + // exclude this hit + pRetval = nullptr; + } + } + } + } + } + + return pRetval; +} + +SdrObject* SwDrawView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObject* pObj, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay) const +{ + // call parent + SdrObject* pRetval = FmFormView::CheckSingleSdrObjectHit(rPnt, nTol, pObj, pPV, nOptions, pMVisLay); + + if(pRetval) + { + // override to allow extra handling when picking SwVirtFlyDrawObj's + pRetval = impLocalHitCorrection(pRetval, rPnt, nTol, GetMarkedObjectList()); + } + + return pRetval; +} + +/// Gets called every time the handles need to be build +void SwDrawView::AddCustomHdl() +{ + const SdrMarkList &rMrkList = GetMarkedObjectList(); + + if(rMrkList.GetMarkCount() != 1 || !GetUserCall(rMrkList.GetMark( 0 )->GetMarkedSdrObj())) + return; + + SdrObject *pObj = rMrkList.GetMark(0)->GetMarkedSdrObj(); + // make code robust + SwFrameFormat* pFrameFormat( ::FindFrameFormat( pObj ) ); + if ( !pFrameFormat ) + { + OSL_FAIL( "<SwDrawView::AddCustomHdl()> - missing frame format!" ); + return; + } + const SwFormatAnchor &rAnchor = pFrameFormat->GetAnchor(); + + if (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) + return; + + const SwFrame* pAnch; + if(nullptr == (pAnch = CalcAnchor())) + return; + + Point aPos(m_aAnchorPoint); + + if ( RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() ) + { + // #i28701# - use last character rectangle saved at object + // in order to avoid a format of the anchor frame + SwAnchoredObject* pAnchoredObj = ::GetUserCall( pObj )->GetAnchoredObj( pObj ); + SwRect aAutoPos = pAnchoredObj->GetLastCharRect(); + if ( aAutoPos.Height() ) + { + aPos = aAutoPos.Pos(); + } + } + + // add anchor handle: + maHdlList.AddHdl( std::make_unique<SwSdrHdl>( aPos, ( pAnch->IsVertical() && !pAnch->IsVertLR() ) || + pAnch->IsRightToLeft() ) ); +} + +SdrObject* SwDrawView::GetMaxToTopObj( SdrObject* pObj ) const +{ + if ( GetUserCall(pObj) ) + { + const SwFrame *pAnch = ::lcl_FindAnchor( pObj, false ); + if ( pAnch ) + { + //The topmost Obj within the anchor must not be overtaken. + const SwFlyFrame *pFly = pAnch->FindFlyFrame(); + if ( pFly ) + { + const SwPageFrame *pPage = pFly->FindPageFrame(); + if ( pPage->GetSortedObjs() ) + { + size_t nOrdNum = 0; + for (SwAnchoredObject* i : *pPage->GetSortedObjs()) + { + const SdrObject *pO = i->GetDrawObj(); + + if ( pO->GetOrdNumDirect() > nOrdNum ) + { + const SwFrame *pTmpAnch = ::lcl_FindAnchor( pO, false ); + if ( pFly->IsAnLower( pTmpAnch ) ) + { + nOrdNum = pO->GetOrdNumDirect(); + } + } + } + if ( nOrdNum ) + { + SdrPage *pTmpPage = GetModel()->GetPage( 0 ); + ++nOrdNum; + if ( nOrdNum < pTmpPage->GetObjCount() ) + { + return pTmpPage->GetObj( nOrdNum ); + } + } + } + } + } + } + return nullptr; +} + +SdrObject* SwDrawView::GetMaxToBtmObj(SdrObject* pObj) const +{ + if ( GetUserCall(pObj) ) + { + const SwFrame *pAnch = ::lcl_FindAnchor( pObj, false ); + if ( pAnch ) + { + //The Fly of the anchor must not be "flying under". + const SwFlyFrame *pFly = pAnch->FindFlyFrame(); + if ( pFly ) + { + SdrObject *pRet = const_cast<SdrObject*>(static_cast<SdrObject const *>(pFly->GetVirtDrawObj())); + return pRet != pObj ? pRet : nullptr; + } + } + } + return nullptr; +} + +/// determine maximal order number for a 'child' object of given 'parent' object +sal_uInt32 SwDrawView::GetMaxChildOrdNum( const SwFlyFrame& _rParentObj, + const SdrObject* _pExclChildObj ) +{ + sal_uInt32 nMaxChildOrdNum = _rParentObj.GetDrawObj()->GetOrdNum(); + + const SdrPage* pDrawPage = _rParentObj.GetDrawObj()->getSdrPageFromSdrObject(); + OSL_ENSURE( pDrawPage, + "<SwDrawView::GetMaxChildOrdNum(..) - missing drawing page at parent object - crash!" ); + + const size_t nObjCount = pDrawPage->GetObjCount(); + for ( size_t i = nObjCount-1; i > _rParentObj.GetDrawObj()->GetOrdNum() ; --i ) + { + const SdrObject* pObj = pDrawPage->GetObj( i ); + + // Don't consider 'child' object <_pExclChildObj> + if ( pObj == _pExclChildObj ) + { + continue; + } + + if ( pObj->GetOrdNum() > nMaxChildOrdNum && + _rParentObj.IsAnLower( lcl_FindAnchor( pObj, true ) ) ) + { + nMaxChildOrdNum = pObj->GetOrdNum(); + break; + } + } + + return nMaxChildOrdNum; +} + +/// method to move 'repeated' objects of the given moved object to the according level +void SwDrawView::MoveRepeatedObjs( const SwAnchoredObject& _rMovedAnchoredObj, + const std::vector<SdrObject*>& _rMovedChildObjs ) const +{ + // determine 'repeated' objects of already moved object <_rMovedAnchoredObj> + std::vector<SwAnchoredObject*> aAnchoredObjs; + { + const SwContact* pContact = ::GetUserCall( _rMovedAnchoredObj.GetDrawObj() ); + assert(pContact && "SwDrawView::MoveRepeatedObjs(..) - missing contact object -> crash."); + pContact->GetAnchoredObjs( aAnchoredObjs ); + } + + // check, if 'repeated' objects exists. + if ( aAnchoredObjs.size() > 1 ) + { + SdrPage* pDrawPage = GetModel()->GetPage( 0 ); + + // move 'repeated' ones to the same order number as the already moved one. + const size_t nNewPos = _rMovedAnchoredObj.GetDrawObj()->GetOrdNum(); + while ( !aAnchoredObjs.empty() ) + { + SwAnchoredObject* pAnchoredObj = aAnchoredObjs.back(); + if ( pAnchoredObj != &_rMovedAnchoredObj ) + { + pDrawPage->SetObjectOrdNum( pAnchoredObj->GetDrawObj()->GetOrdNum(), + nNewPos ); + pDrawPage->RecalcObjOrdNums(); + // adjustments for accessibility API + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + const SwFlyFrame *pTmpFlyFrame = static_cast<SwFlyFrame*>(pAnchoredObj); + m_rImp.DisposeAccessibleFrame( pTmpFlyFrame ); + m_rImp.AddAccessibleFrame( pTmpFlyFrame ); + } + else + { + m_rImp.DisposeAccessibleObj(pAnchoredObj->GetDrawObj(), true); + m_rImp.AddAccessibleObj( pAnchoredObj->GetDrawObj() ); + } + } + aAnchoredObjs.pop_back(); + } + + // move 'repeated' ones of 'child' objects + for ( SdrObject* pChildObj : _rMovedChildObjs ) + { + { + const SwContact* pContact = ::GetUserCall( pChildObj ); + assert(pContact && "SwDrawView::MoveRepeatedObjs(..) - missing contact object -> crash."); + pContact->GetAnchoredObjs( aAnchoredObjs ); + } + // move 'repeated' ones to the same order number as the already moved one. + const size_t nTmpNewPos = pChildObj->GetOrdNum(); + while ( !aAnchoredObjs.empty() ) + { + SwAnchoredObject* pAnchoredObj = aAnchoredObjs.back(); + if ( pAnchoredObj->GetDrawObj() != pChildObj ) + { + pDrawPage->SetObjectOrdNum( pAnchoredObj->GetDrawObj()->GetOrdNum(), + nTmpNewPos ); + pDrawPage->RecalcObjOrdNums(); + // adjustments for accessibility API + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + const SwFlyFrame *pTmpFlyFrame = static_cast<SwFlyFrame*>(pAnchoredObj); + m_rImp.DisposeAccessibleFrame( pTmpFlyFrame ); + m_rImp.AddAccessibleFrame( pTmpFlyFrame ); + } + else + { + m_rImp.DisposeAccessibleObj(pAnchoredObj->GetDrawObj(), true); + m_rImp.AddAccessibleObj( pAnchoredObj->GetDrawObj() ); + } + } + aAnchoredObjs.pop_back(); + } + } + } +} + +// --> adjustment and re-factoring of method +void SwDrawView::ObjOrderChanged( SdrObject* pObj, size_t nOldPos, + size_t nNewPos ) +{ + // nothing to do for group members + if ( pObj->getParentSdrObjectFromSdrObject() ) + { + return; + } + + // determine drawing page and assure that the order numbers are correct. + SdrPage* pDrawPage = GetModel()->GetPage( 0 ); + if ( pDrawPage->IsObjOrdNumsDirty() ) + pDrawPage->RecalcObjOrdNums(); + const size_t nObjCount = pDrawPage->GetObjCount(); + + SwAnchoredObject* pMovedAnchoredObj = + ::GetUserCall( pObj )->GetAnchoredObj( pObj ); + const SwFlyFrame* pParentAnchoredObj = + pMovedAnchoredObj->GetAnchorFrame()->FindFlyFrame(); + + const bool bMovedForward = nOldPos < nNewPos; + + // assure for a 'child' object, that it doesn't exceed the limits of its 'parent' + if ( pParentAnchoredObj ) + { + if ( bMovedForward ) + { + const size_t nMaxChildOrdNumWithoutMoved = + GetMaxChildOrdNum( *pParentAnchoredObj, pMovedAnchoredObj->GetDrawObj() ); + if ( nNewPos > nMaxChildOrdNumWithoutMoved+1 ) + { + // set position to the top of the 'child' object group + pDrawPage->SetObjectOrdNum( nNewPos, nMaxChildOrdNumWithoutMoved+1 ); + nNewPos = nMaxChildOrdNumWithoutMoved+1; + } + } + else + { + const size_t nParentOrdNum = pParentAnchoredObj->GetDrawObj()->GetOrdNum(); + if ( nNewPos < nParentOrdNum ) + { + // set position to the bottom of the 'child' object group + pDrawPage->SetObjectOrdNum( nNewPos, nParentOrdNum ); + nNewPos = nParentOrdNum; + } + } + if ( pDrawPage->IsObjOrdNumsDirty() ) + pDrawPage->RecalcObjOrdNums(); + } + + // Assure, that object isn't positioned between 'repeated' ones + if ( ( bMovedForward && nNewPos < nObjCount - 1 ) || + ( !bMovedForward && nNewPos > 0 ) ) + { + const SdrObject* pTmpObj = + pDrawPage->GetObj( bMovedForward ? nNewPos - 1 : nNewPos + 1 ); + if ( pTmpObj ) + { + size_t nTmpNewPos( nNewPos ); + if ( bMovedForward ) + { + // move before the top 'repeated' object + const sal_uInt32 nTmpMaxOrdNum = + ::GetUserCall( pTmpObj )->GetMaxOrdNum(); + if ( nTmpMaxOrdNum > nNewPos ) + nTmpNewPos = nTmpMaxOrdNum; + } + else + { + // move behind the bottom 'repeated' object + const sal_uInt32 nTmpMinOrdNum = + ::GetUserCall( pTmpObj )->GetMinOrdNum(); + if ( nTmpMinOrdNum < nNewPos ) + nTmpNewPos = nTmpMinOrdNum; + } + if ( nTmpNewPos != nNewPos ) + { + pDrawPage->SetObjectOrdNum( nNewPos, nTmpNewPos ); + nNewPos = nTmpNewPos; + pDrawPage->RecalcObjOrdNums(); + } + } + } + + // On move forward, assure that object is moved before its own children. + // Only Writer fly frames can have children. + if ( dynamic_cast< const SwFlyFrame *>( pMovedAnchoredObj ) != nullptr && + bMovedForward && nNewPos < nObjCount - 1 ) + { + sal_uInt32 nMaxChildOrdNum = + GetMaxChildOrdNum( *static_cast<const SwFlyFrame*>(pMovedAnchoredObj) ); + if ( nNewPos < nMaxChildOrdNum ) + { + // determine position before the object before its top 'child' object + const SdrObject* pTmpObj = pDrawPage->GetObj( nMaxChildOrdNum ); + size_t nTmpNewPos = ::GetUserCall( pTmpObj )->GetMaxOrdNum() + 1; + if ( nTmpNewPos >= nObjCount ) + { + --nTmpNewPos; + } + // assure, that determined position isn't between 'repeated' objects + pTmpObj = pDrawPage->GetObj( nTmpNewPos ); + nTmpNewPos = ::GetUserCall( pTmpObj )->GetMaxOrdNum(); + // apply new position + pDrawPage->SetObjectOrdNum( nNewPos, nTmpNewPos ); + nNewPos = nTmpNewPos; + pDrawPage->RecalcObjOrdNums(); + } + } + + // Assure, that object isn't positioned between nested objects + if ( ( bMovedForward && nNewPos < nObjCount - 1 ) || + ( !bMovedForward && nNewPos > 0 ) ) + { + size_t nTmpNewPos( nNewPos ); + const SwFrameFormat* pParentFrameFormat = + pParentAnchoredObj ? &(pParentAnchoredObj->GetFrameFormat()) : nullptr; + const SdrObject* pTmpObj = pDrawPage->GetObj( nNewPos + 1 ); + while ( pTmpObj ) + { + // #i38563# - assure, that anchor frame exists. + // If object is anchored inside an invisible part of the document + // (e.g. page header, whose page style isn't applied, or hidden + // section), no anchor frame exists. + const SwFrame* pTmpAnchorFrame = lcl_FindAnchor( pTmpObj, true ); + const SwFlyFrame* pTmpParentObj = pTmpAnchorFrame + ? pTmpAnchorFrame->FindFlyFrame() : nullptr; + if ( pTmpParentObj && + &(pTmpParentObj->GetFrameFormat()) != pParentFrameFormat ) + { + if ( bMovedForward ) + { + nTmpNewPos = ::GetUserCall( pTmpObj )->GetMaxOrdNum(); + pTmpObj = pDrawPage->GetObj( nTmpNewPos + 1 ); + } + else + { + nTmpNewPos = ::GetUserCall( pTmpParentObj->GetDrawObj() ) + ->GetMinOrdNum(); + pTmpObj = pTmpParentObj->GetDrawObj(); + } + } + else + break; + } + if ( nTmpNewPos != nNewPos ) + { + pDrawPage->SetObjectOrdNum( nNewPos, nTmpNewPos ); + nNewPos = nTmpNewPos; + pDrawPage->RecalcObjOrdNums(); + } + } + + // setup collection of moved 'child' objects to move its 'repeated' objects. + std::vector< SdrObject* > aMovedChildObjs; + + // move 'children' accordingly + if ( dynamic_cast< const SwFlyFrame *>( pMovedAnchoredObj ) != nullptr ) + { + const SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pMovedAnchoredObj); + + // adjustments for accessibility API + m_rImp.DisposeAccessibleFrame( pFlyFrame ); + m_rImp.AddAccessibleFrame( pFlyFrame ); + + const sal_uInt32 nChildNewPos = bMovedForward ? nNewPos : nNewPos+1; + size_t i = bMovedForward ? nOldPos : nObjCount-1; + do + { + SdrObject* pTmpObj = pDrawPage->GetObj( i ); + if ( pTmpObj == pObj ) + break; + + // #i38563# - assure, that anchor frame exists. + // If object is anchored inside an invisible part of the document + // (e.g. page header, whose page style isn't applied, or hidden + // section), no anchor frame exists. + const SwFrame* pTmpAnchorFrame = lcl_FindAnchor( pTmpObj, true ); + const SwFlyFrame* pTmpParentObj = pTmpAnchorFrame + ? pTmpAnchorFrame->FindFlyFrame() : nullptr; + if ( pTmpParentObj && + ( ( pTmpParentObj == pFlyFrame ) || + ( pFlyFrame->IsUpperOf( *pTmpParentObj ) ) ) ) + { + // move child object., + pDrawPage->SetObjectOrdNum( i, nChildNewPos ); + pDrawPage->RecalcObjOrdNums(); + // collect 'child' object + aMovedChildObjs.push_back( pTmpObj ); + // adjustments for accessibility API + if ( dynamic_cast< const SwVirtFlyDrawObj *>( pTmpObj ) != nullptr ) + { + const SwFlyFrame *pTmpFlyFrame = + static_cast<SwVirtFlyDrawObj*>(pTmpObj)->GetFlyFrame(); + m_rImp.DisposeAccessibleFrame( pTmpFlyFrame ); + m_rImp.AddAccessibleFrame( pTmpFlyFrame ); + } + else + { + m_rImp.DisposeAccessibleObj(pTmpObj, true); + m_rImp.AddAccessibleObj( pTmpObj ); + } + } + else + { + // adjust loop counter + if ( bMovedForward ) + ++i; + else if (i > 0) + --i; + } + + } while ( ( bMovedForward && i < ( nObjCount - aMovedChildObjs.size() ) ) || + ( !bMovedForward && i > ( nNewPos + aMovedChildObjs.size() ) ) ); + } + else + { + // adjustments for accessibility API + m_rImp.DisposeAccessibleObj(pObj, true); + m_rImp.AddAccessibleObj( pObj ); + } + + MoveRepeatedObjs( *pMovedAnchoredObj, aMovedChildObjs ); +} + +bool SwDrawView::TakeDragLimit( SdrDragMode eMode, + tools::Rectangle& rRect ) const +{ + const SdrMarkList &rMrkList = GetMarkedObjectList(); + bool bRet = false; + if( 1 == rMrkList.GetMarkCount() ) + { + const SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + SwRect aRect; + if( ::CalcClipRect( pObj, aRect, eMode == SdrDragMode::Move ) ) + { + rRect = aRect.SVRect(); + bRet = true; + } + } + return bRet; +} + +const SwFrame* SwDrawView::CalcAnchor() +{ + const SdrMarkList &rMrkList = GetMarkedObjectList(); + if ( rMrkList.GetMarkCount() != 1 ) + return nullptr; + + SdrObject* pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + + //Search for paragraph bound objects, otherwise only the + //current anchor. Search only if we currently drag. + const SwFrame* pAnch; + tools::Rectangle aMyRect; + const bool bFly = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) != nullptr; + if ( bFly ) + { + pAnch = static_cast<SwVirtFlyDrawObj*>(pObj)->GetFlyFrame()->GetAnchorFrame(); + aMyRect = static_cast<SwVirtFlyDrawObj*>(pObj)->GetFlyFrame()->getFrameArea().SVRect(); + } + else + { + SwDrawContact *pC = static_cast<SwDrawContact*>(GetUserCall(pObj)); + // determine correct anchor position for 'virtual' drawing objects. + // #i26791# + pAnch = pC->GetAnchorFrame( pObj ); + if( !pAnch ) + { + pC->ConnectToLayout(); + // determine correct anchor position for 'virtual' drawing objects. + // #i26791# + pAnch = pC->GetAnchorFrame( pObj ); + } + aMyRect = pObj->GetSnapRect(); + } + + const bool bTopRight = pAnch && ( ( pAnch->IsVertical() && + !pAnch->IsVertLR() ) || + pAnch->IsRightToLeft() ); + const Point aMyPt = bTopRight ? aMyRect.TopRight() : aMyRect.TopLeft(); + + Point aPt; + if ( IsAction() ) + { + if ( !TakeDragObjAnchorPos( aPt, bTopRight ) ) + return nullptr; + } + else + { + tools::Rectangle aRect = pObj->GetSnapRect(); + aPt = bTopRight ? aRect.TopRight() : aRect.TopLeft(); + } + + if ( aPt != aMyPt ) + { + if ( pAnch && pAnch->IsContentFrame() ) + { + // allow drawing objects in header/footer, + // but exclude control objects. + bool bBodyOnly = CheckControlLayer( pObj ); + pAnch = ::FindAnchor( static_cast<const SwContentFrame*>(pAnch), aPt, bBodyOnly ); + } + else if ( !bFly ) + { + const SwRect aRect( aPt.getX(), aPt.getY(), 1, 1 ); + + SwDrawContact* pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + if ( pContact->GetAnchorFrame( pObj ) && + pContact->GetAnchorFrame( pObj )->IsPageFrame() ) + pAnch = pContact->GetPageFrame(); + else + pAnch = pContact->FindPage( aRect ); + } + } + if( pAnch && !pAnch->IsProtected() ) + m_aAnchorPoint = pAnch->GetFrameAnchorPos( ::HasWrap( pObj ) ); + else + pAnch = nullptr; + return pAnch; +} + +void SwDrawView::ShowDragAnchor() +{ + SdrHdl* pHdl = maHdlList.GetHdl(SdrHdlKind::Anchor); + if ( ! pHdl ) + pHdl = maHdlList.GetHdl(SdrHdlKind::Anchor_TR); + + if(pHdl) + { + CalcAnchor(); + pHdl->SetPos(m_aAnchorPoint); + } +} + +void SwDrawView::MarkListHasChanged() +{ + Imp().GetShell()->DrawSelChanged(); + FmFormView::MarkListHasChanged(); +} + +// #i7672# +void SwDrawView::ModelHasChanged() +{ + // The ModelHasChanged() call in DrawingLayer also updates + // an eventually active text edit view (OutlinerView). This also leads + // to newly setting the background color for that edit view. Thus, + // this method rescues the current background color if an OutlinerView + // exists and re-establishes it then. To be more safe, the OutlinerView + // will be fetched again (maybe textedit has ended). + OutlinerView* pView = GetTextEditOutlinerView(); + Color aBackColor; + bool bColorWasSaved(false); + + if(pView) + { + aBackColor = pView->GetBackgroundColor(); + bColorWasSaved = true; + } + + // call parent + FmFormView::ModelHasChanged(); + + if(bColorWasSaved) + { + pView = GetTextEditOutlinerView(); + + if(pView) + { + pView->SetBackgroundColor(aBackColor); + } + } +} + +void SwDrawView::MakeVisible( const tools::Rectangle &rRect, vcl::Window & ) +{ + OSL_ENSURE( m_rImp.GetShell()->GetWin(), "MakeVisible, unknown Window"); + m_rImp.GetShell()->MakeVisible( SwRect( rRect ) ); +} + +void SwDrawView::CheckPossibilities() +{ + FmFormView::CheckPossibilities(); + + //In addition to the existing flags of the objects themselves, + //which are evaluated by the DrawingEngine, other circumstances + //lead to a protection. + //Objects that are anchored in frames need to be protected + //if the content of the frame is protected. + //OLE-Objects may themselves wish a resize protection (StarMath) + + const SdrMarkList &rMrkList = GetMarkedObjectList(); + bool bProtect = false; + bool bSzProtect = false; + bool bRotate(false); + + for ( size_t i = 0; !bProtect && i < rMrkList.GetMarkCount(); ++i ) + { + const SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + const SwFrame *pFrame = nullptr; + if ( auto pVirtFlyDrawObj = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) ) + { + const SwFlyFrame *pFly = pVirtFlyDrawObj->GetFlyFrame(); + if ( pFly ) + { + pFrame = pFly->GetAnchorFrame(); + if ( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + const SwNoTextFrame *const pNTF(static_cast<const SwNoTextFrame*>(pFly->Lower())); + const SwOLENode *const pOLENd = pNTF->GetNode()->GetOLENode(); + const SwGrfNode *const pGrfNd = pNTF->GetNode()->GetGrfNode(); + + if ( pOLENd ) + { + const uno::Reference < embed::XEmbeddedObject > xObj = const_cast< SwOLEObj& >(pOLENd->GetOLEObj()).GetOleRef(); + + if ( xObj.is() ) + { + // --> improvement for the future, when more + // than one Writer fly frame can be selected. + + // TODO/LATER: retrieve Aspect - from where?! + bSzProtect |= ( embed::EmbedMisc::EMBED_NEVERRESIZE & xObj->getStatus( embed::Aspects::MSOLE_CONTENT ) ) != 0; + + // #i972: protect position if it is a Math object anchored 'as char' and baseline alignment is activated + SwDoc* pDoc = Imp().GetShell()->GetDoc(); + const bool bProtectMathPos = SotExchange::IsMath( xObj->getClassID() ) + && RndStdIds::FLY_AS_CHAR == pFly->GetFormat()->GetAnchor().GetAnchorId() + && pDoc->GetDocumentSettingManager().get( DocumentSettingId::MATH_BASELINE_ALIGNMENT ); + if (bProtectMathPos) + m_bMoveProtect = true; + } + } + else if(pGrfNd) + { + // RotGrfFlyFrame: GraphicNode allows rotation(s). The loop ew are in stops + // as soon as bMoveProtect is set, but since rotation is valid only with + // a single object selected this makes no difference + bRotate = true; + } + } + } + } + else + { + SwDrawContact *pC = static_cast<SwDrawContact*>(GetUserCall(pObj)); + if ( pC ) + pFrame = pC->GetAnchorFrame( pObj ); + } + if ( pFrame ) + bProtect = pFrame->IsProtected(); //Frames, areas etc. + { + SwFrameFormat* pFrameFormat( ::FindFrameFormat( const_cast<SdrObject*>(pObj) ) ); + if ( !pFrameFormat ) + { + OSL_FAIL( "<SwDrawView::CheckPossibilities()> - missing frame format" ); + bProtect = true; + } + else if ((RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId()) && + rMrkList.GetMarkCount() > 1 ) + { + bProtect = true; + } + } + } + m_bMoveProtect |= bProtect; + m_bResizeProtect |= bProtect || bSzProtect; + + // RotGrfFlyFrame: allow rotation when SwGrfNode is selected and not size protected + m_bRotateFreeAllowed |= bRotate && !bProtect; + m_bRotate90Allowed |= m_bRotateFreeAllowed; +} + +/// replace marked <SwDrawVirtObj>-objects by its reference object for delete marked objects. +void SwDrawView::ReplaceMarkedDrawVirtObjs( SdrMarkView& _rMarkView ) +{ + SdrPageView* pDrawPageView = _rMarkView.GetSdrPageView(); + const SdrMarkList& rMarkList = _rMarkView.GetMarkedObjectList(); + + if( rMarkList.GetMarkCount() ) + { + // collect marked objects in a local data structure + std::vector<SdrObject*> aMarkedObjs; + for( size_t i = 0; i < rMarkList.GetMarkCount(); ++i ) + { + SdrObject* pMarkedObj = rMarkList.GetMark( i )->GetMarkedSdrObj(); + aMarkedObjs.push_back( pMarkedObj ); + } + // unmark all objects + _rMarkView.UnmarkAllObj(); + // re-mark objects, but for marked <SwDrawVirtObj>-objects marked its + // reference object. + while ( !aMarkedObjs.empty() ) + { + SdrObject* pMarkObj = aMarkedObjs.back(); + if ( dynamic_cast< const SwDrawVirtObj *>( pMarkObj ) != nullptr ) + { + SdrObject* pRefObj = &(static_cast<SwDrawVirtObj*>(pMarkObj)->ReferencedObj()); + if ( !_rMarkView.IsObjMarked( pRefObj ) ) + { + _rMarkView.MarkObj( pRefObj, pDrawPageView ); + } + } + else + { + _rMarkView.MarkObj( pMarkObj, pDrawPageView ); + } + + aMarkedObjs.pop_back(); + } + // sort marked list in order to assure consistent state in drawing layer + _rMarkView.SortMarkedObjects(); + } +} + +SfxViewShell* SwDrawView::GetSfxViewShell() const +{ + return m_rImp.GetShell()->GetSfxViewShell(); +} + +void SwDrawView::DeleteMarked() +{ + SwDoc* pDoc = Imp().GetShell()->GetDoc(); + SwRootFrame *pTmpRoot = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if ( pTmpRoot ) + pTmpRoot->StartAllAction(); + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + // replace marked <SwDrawVirtObj>-objects by its reference objects. + if (SdrPageView* pDrawPageView = m_rImp.GetPageView()) + { + ReplaceMarkedDrawVirtObjs(pDrawPageView->GetView()); + } + + // Check what textboxes have to be deleted afterwards. + const SdrMarkList& rMarkList = GetMarkedObjectList(); + std::vector<SwFrameFormat*> aTextBoxesToDelete; + for (size_t i = 0; i < rMarkList.GetMarkCount(); ++i) + { + SdrObject *pObject = rMarkList.GetMark(i)->GetMarkedSdrObj(); + SwContact* pContact = GetUserCall(pObject); + SwFrameFormat* pFormat = pContact->GetFormat(); + if (SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT)) + aTextBoxesToDelete.push_back(pTextBox); + } + + if ( pDoc->DeleteSelection( *this ) ) + { + FmFormView::DeleteMarked(); + ::FrameNotify( Imp().GetShell(), FLY_DRAG_END ); + } + + // Only delete these now: earlier deletion would clear the mark list as well. + // Delete in reverse order, assuming that the container is sorted by anchor positions. + for (int i = aTextBoxesToDelete.size() - 1; i >= 0; --i) + { + SwFrameFormat*& rpTextBox = aTextBoxesToDelete[i]; + pDoc->getIDocumentLayoutAccess().DelLayoutFormat(rpTextBox); + } + + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + if( pTmpRoot ) + pTmpRoot->EndAllAction(); +} + +// support enhanced text edit for draw objects +SdrUndoManager* SwDrawView::getSdrUndoManagerForEnhancedTextEdit() const +{ + SwDoc* pDoc = Imp().GetShell()->GetDoc(); + + return pDoc ? dynamic_cast< SdrUndoManager* >(&(pDoc->GetUndoManager())) : nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/acorrect.cxx b/sw/source/core/edit/acorrect.cxx new file mode 100644 index 000000000..f26f23732 --- /dev/null +++ b/sw/source/core/edit/acorrect.cxx @@ -0,0 +1,693 @@ +/* -*- 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 <hintids.hxx> + +#include <fmtinfmt.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <unocrsr.hxx> +#include <txatbase.hxx> +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <acorrect.hxx> +#include <shellio.hxx> +#include <swundo.hxx> +#include <viscrs.hxx> +#include <com/sun/star/i18n/BreakType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> + +#include <editeng/acorrcfg.hxx> + +using namespace ::com::sun::star; + +namespace { + +class PaMIntoCursorShellRing +{ + SwPaM &rDelPam, &rCursor; + SwPaM* pPrevDelPam; + SwPaM* pPrevCursor; + + static void RemoveFromRing( SwPaM& rPam, SwPaM const * pPrev ); +public: + PaMIntoCursorShellRing( SwCursorShell& rSh, SwPaM& rCursor, SwPaM& rPam ); + ~PaMIntoCursorShellRing(); +}; + +} + +PaMIntoCursorShellRing::PaMIntoCursorShellRing( SwCursorShell& rCSh, + SwPaM& rShCursor, SwPaM& rPam ) + : rDelPam( rPam ), rCursor( rShCursor ) +{ + SwPaM* pShCursor = rCSh.GetCursor_(); + + pPrevDelPam = rDelPam.GetPrev(); + pPrevCursor = rCursor.GetPrev(); + + rDelPam.GetRingContainer().merge( pShCursor->GetRingContainer() ); + rCursor.GetRingContainer().merge( pShCursor->GetRingContainer() ); +} + +PaMIntoCursorShellRing::~PaMIntoCursorShellRing() +{ + // and take out the Pam again: + RemoveFromRing( rDelPam, pPrevDelPam ); + RemoveFromRing( rCursor, pPrevCursor ); +} + +void PaMIntoCursorShellRing::RemoveFromRing( SwPaM& rPam, SwPaM const * pPrev ) +{ + SwPaM* p; + SwPaM* pNext = &rPam; + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo( &rPam ); + } while( p != pPrev ); +} + +SwAutoCorrDoc::SwAutoCorrDoc( SwEditShell& rEditShell, SwPaM& rPam, + sal_Unicode cIns ) + : m_rEditSh( rEditShell ), m_rCursor( rPam ) + , m_nEndUndoCounter(0) + , m_bUndoIdInitialized( cIns == 0 ) +{ +} + +SwAutoCorrDoc::~SwAutoCorrDoc() +{ + for (int i = 0; i < m_nEndUndoCounter; ++i) + { + m_rEditSh.EndUndo(); + } +} + +void SwAutoCorrDoc::DeleteSel( SwPaM& rDelPam ) +{ + // this should work with plain SwPaM as well because start and end + // are always in same node, but since there is GetRanges already... + std::vector<std::shared_ptr<SwUnoCursor>> ranges; + if (sw::GetRanges(ranges, *m_rEditSh.GetDoc(), rDelPam)) + { + DeleteSelImpl(rDelPam); + } + else + { + for (auto const& pCursor : ranges) + { + DeleteSelImpl(*pCursor); + } + } +} + +void SwAutoCorrDoc::DeleteSelImpl(SwPaM & rDelPam) +{ + SwDoc* pDoc = m_rEditSh.GetDoc(); + if( pDoc->IsAutoFormatRedline() ) + { + // so that also the DelPam be moved, include it in the + // Shell-Cursr-Ring !! + // ??? is that really necessary - this should never join nodes, so Update should be enough? +// PaMIntoCursorShellRing aTmp( rEditSh, rCursor, rDelPam ); + assert(rDelPam.GetPoint()->nNode == rDelPam.GetMark()->nNode); + pDoc->getIDocumentContentOperations().DeleteAndJoin( rDelPam ); + } + else + { + pDoc->getIDocumentContentOperations().DeleteRange( rDelPam ); + } +} + +bool SwAutoCorrDoc::Delete( sal_Int32 nStt, sal_Int32 nEnd ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aSel(pFrame->MapViewToModelPos(TextFrameIndex(nStt)), + pFrame->MapViewToModelPos(TextFrameIndex(nEnd))); + DeleteSel( aSel ); + + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + return true; +} + +bool SwAutoCorrDoc::Insert( sal_Int32 nPos, const OUString& rText ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nPos))); + m_rEditSh.GetDoc()->getIDocumentContentOperations().InsertString( aPam, rText ); + if( !m_bUndoIdInitialized ) + { + m_bUndoIdInitialized = true; + if( 1 == rText.getLength() ) + { + m_rEditSh.StartUndo( SwUndoId::AUTOCORRECT ); + ++m_nEndUndoCounter; + } + } + return true; +} + +bool SwAutoCorrDoc::Replace( sal_Int32 nPos, const OUString& rText ) +{ + return ReplaceRange( nPos, rText.getLength(), rText ); +} + +bool SwAutoCorrDoc::ReplaceRange( sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rText ) +{ + assert(nSourceLength == 1); // sw_redlinehide: this is currently the case, + // and ensures that the replace range cannot *contain* delete redlines, + // so we don't need something along the lines of: + // if (sw::GetRanges(ranges, *rEditSh.GetDoc(), aPam)) + // ReplaceImpl(...) + // else + // ReplaceImpl(ranges.begin()) + // for (ranges.begin() + 1; ranges.end(); ) + // DeleteImpl(*it) + + SwTextNode * const pNd = m_rCursor.GetNode().GetTextNode(); + if ( !pNd ) + { + return false; + } + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + std::pair<SwTextNode *, sal_Int32> const pos(pFrame->MapViewToModel(TextFrameIndex(nPos))); + + SwPaM* pPam = &m_rCursor; + if (pPam->GetPoint()->nNode != *pos.first + || pPam->GetPoint()->nContent != pos.second) + { + pPam = new SwPaM(*pos.first, pos.second); + } + + // text attributes with dummy characters must not be replaced! + bool bDoReplace = true; + sal_Int32 const nLen = rText.getLength(); + for (sal_Int32 n = 0; n < nLen && n + nPos < pFrame->GetText().getLength(); ++n) + { + sal_Unicode const Char = pFrame->GetText()[n + nPos]; + if (CH_TXTATR_BREAKWORD == Char || CH_TXTATR_INWORD == Char) + { + assert(pFrame->MapViewToModel(TextFrameIndex(n+nPos)).first->GetTextAttrForCharAt(pFrame->MapViewToModel(TextFrameIndex(n+nPos)).second)); + bDoReplace = false; + break; + } + } + + if ( bDoReplace ) + { + SwDoc* pDoc = m_rEditSh.GetDoc(); + + if( pDoc->IsAutoFormatRedline() ) + { + if (nPos == pFrame->GetText().getLength()) // at the End do an Insert + { + pDoc->getIDocumentContentOperations().InsertString( *pPam, rText ); + } + else + { + assert(pos.second != pos.first->Len()); // must be _before_ char + PaMIntoCursorShellRing aTmp( m_rEditSh, m_rCursor, *pPam ); + + pPam->SetMark(); + pPam->GetPoint()->nContent = std::min<sal_Int32>( + pos.first->GetText().getLength(), pos.second + nSourceLength); + pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false ); + pPam->Exchange(); + pPam->DeleteMark(); + } + } + else + { + if( nSourceLength != rText.getLength() ) + { + pPam->SetMark(); + pPam->GetPoint()->nContent = std::min<sal_Int32>( + pos.first->GetText().getLength(), pos.second + nSourceLength); + pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false ); + pPam->Exchange(); + pPam->DeleteMark(); + } + else + pDoc->getIDocumentContentOperations().Overwrite( *pPam, rText ); + } + + if( m_bUndoIdInitialized ) + { + m_bUndoIdInitialized = true; + if( 1 == rText.getLength() ) + { + m_rEditSh.StartUndo( SwUndoId::AUTOCORRECT ); + ++m_nEndUndoCounter; + } + } + } + + if( pPam != &m_rCursor ) + delete pPam; + + return true; +} + +void SwAutoCorrDoc::SetAttr( sal_Int32 nStt, sal_Int32 nEnd, sal_uInt16 nSlotId, + SfxPoolItem& rItem ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nStt)), + pFrame->MapViewToModelPos(TextFrameIndex(nEnd))); + + SfxItemPool& rPool = m_rEditSh.GetDoc()->GetAttrPool(); + sal_uInt16 nWhich = rPool.GetWhich( nSlotId, false ); + if( nWhich ) + { + rItem.SetWhich( nWhich ); + + SfxItemSet aSet( rPool, aCharFormatSetRange ); + SetAllScriptItem( aSet, rItem ); + + m_rEditSh.GetDoc()->SetFormatItemByAutoFormat( aPam, aSet ); + + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + } +} + +bool SwAutoCorrDoc::SetINetAttr( sal_Int32 nStt, sal_Int32 nEnd, const OUString& rURL ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nStt)), + pFrame->MapViewToModelPos(TextFrameIndex(nEnd))); + + SfxItemSet aSet( m_rEditSh.GetDoc()->GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{} ); + aSet.Put( SwFormatINetFormat( rURL, OUString() )); + m_rEditSh.GetDoc()->SetFormatItemByAutoFormat( aPam, aSet ); + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + return true; +} + +/** Return the text of a previous paragraph + * + * @param bAtNormalPos If <true> before the normal insert position; if <false> in which the + * corrected word was inserted. (Doesn't need to be the same paragraph!) + * @return text or 0, if previous paragraph does not exists or there are only blankness + */ +OUString const* SwAutoCorrDoc::GetPrevPara(bool const bAtNormalPos) +{ + OUString const* pStr(nullptr); + + if( bAtNormalPos || !m_pIndex ) + { + m_pIndex.reset(new SwNodeIndex(m_rCursor.GetPoint()->nNode)); + } + sw::GotoPrevLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout()); + + SwTextFrame const* pFrame(nullptr); + for (SwTextNode * pTextNd = m_pIndex->GetNode().GetTextNode(); + pTextNd; pTextNd = m_pIndex->GetNode().GetTextNode()) + { + pFrame = static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout())); + if (pFrame && !pFrame->GetText().isEmpty()) + { + break; + } + sw::GotoPrevLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout()); + } + if (pFrame && 0 == pFrame->GetTextNodeForParaProps()->GetAttrOutlineLevel()) + pStr = & pFrame->GetText(); + + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + + return pStr; +} + +bool SwAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos, sal_Int32 nEndPos, + SvxAutoCorrect& rACorrect, + OUString* pPara ) +{ + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + + // Found a beginning of a paragraph or a Blank, + // search for the word Kuerzel (Shortcut) in the Auto + SwTextNode* pTextNd = m_rCursor.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "where is the TextNode?" ); + + bool bRet = false; + if( nEndPos == rSttPos ) + return bRet; + + LanguageType eLang = GetLanguage(nEndPos); + if(LANGUAGE_SYSTEM == eLang) + eLang = GetAppLanguage(); + LanguageTag aLanguageTag( eLang); + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + + const OUString sFrameText = pFrame->GetText(); + const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList( + sFrameText, rSttPos, nEndPos, *this, aLanguageTag); + SwDoc* pDoc = m_rEditSh.GetDoc(); + if( pFnd ) + { + // replace also last colon of keywords surrounded by colons (for example, ":name:") + const bool replaceLastChar = sFrameText.getLength() > nEndPos && pFnd->GetShort()[0] == ':' + && pFnd->GetShort().endsWith(":"); + + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(rSttPos)), + pFrame->MapViewToModelPos(TextFrameIndex(nEndPos + (replaceLastChar ? 1 : 0)))); + + if( pFnd->IsTextOnly() ) + { + //JP 22.04.99: Bug 63883 - Special treatment for dots. + const bool bLastCharIsPoint + = nEndPos < sFrameText.getLength() && ('.' == sFrameText[nEndPos]); + if( !bLastCharIsPoint || pFnd->GetLong().isEmpty() || + '.' != pFnd->GetLong()[ pFnd->GetLong().getLength() - 1 ] ) + { + // replace the selection + std::vector<std::shared_ptr<SwUnoCursor>> ranges; + if (sw::GetRanges(ranges, *m_rEditSh.GetDoc(), aPam)) + { + pDoc->getIDocumentContentOperations().ReplaceRange(aPam, pFnd->GetLong(), false); + bRet = true; + } + else if (!ranges.empty()) + { + assert(ranges.front()->GetPoint()->nNode == ranges.front()->GetMark()->nNode); + pDoc->getIDocumentContentOperations().ReplaceRange( + *ranges.front(), pFnd->GetLong(), false); + for (auto it = ranges.begin() + 1; it != ranges.end(); ++it) + { + DeleteSelImpl(**it); + } + bRet = true; + } + + // tdf#83260 After calling sw::DocumentContentOperationsManager::ReplaceRange + // pTextNd may become invalid when change tracking is on and Edit -> Track Changes -> Show == OFF. + // ReplaceRange shows changes, this moves deleted nodes from special section to document. + // Then Show mode is disabled again. As a result pTextNd may be invalidated. + pTextNd = m_rCursor.GetNode().GetTextNode(); + } + } + else + { + SwTextBlocks aTBlks( rACorrect.GetAutoCorrFileName( aLanguageTag, false, true )); + sal_uInt16 nPos = aTBlks.GetIndex( pFnd->GetShort() ); + if( USHRT_MAX != nPos && aTBlks.BeginGetDoc( nPos ) ) + { + DeleteSel( aPam ); + pDoc->DontExpandFormat( *aPam.GetPoint() ); + + if( pPara ) + { + OSL_ENSURE( !m_pIndex, "who has not deleted his Index?" ); + m_pIndex.reset(new SwNodeIndex( m_rCursor.GetPoint()->nNode )); + sw::GotoPrevLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout()); + } + + SwDoc* pAutoDoc = aTBlks.GetDoc(); + SwNodeIndex aSttIdx( pAutoDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pAutoDoc->GetNodes().GoNext( &aSttIdx ); + SwPaM aCpyPam( aSttIdx ); + + const SwTableNode* pTableNd = pContentNd->FindTableNode(); + if( pTableNd ) + { + aCpyPam.GetPoint()->nContent.Assign( nullptr, 0 ); + aCpyPam.GetPoint()->nNode = *pTableNd; + } + aCpyPam.SetMark(); + + // then until the end of the Nodes Array + aCpyPam.GetPoint()->nNode.Assign( pAutoDoc->GetNodes().GetEndOfContent(), -1 ); + pContentNd = aCpyPam.GetContentNode(); + aCpyPam.GetPoint()->nContent.Assign( + pContentNd, pContentNd ? pContentNd->Len() : 0); + + SwDontExpandItem aExpItem; + aExpItem.SaveDontExpandItems( *aPam.GetPoint() ); + + pAutoDoc->getIDocumentContentOperations().CopyRange(aCpyPam, *aPam.GetPoint(), SwCopyFlags::CheckPosInFly); + + aExpItem.RestoreDontExpandItems( *aPam.GetPoint() ); + + if( pPara ) + { + sw::GotoNextLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout()); + pTextNd = m_pIndex->GetNode().GetTextNode(); + } + bRet = true; + } + aTBlks.EndGetDoc(); + } + } + + if( bRet && pPara && pTextNd ) + { + SwTextFrame const*const pNewFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + *pPara = pNewFrame->GetText(); + } + + return bRet; +} + +bool SwAutoCorrDoc::TransliterateRTLWord( sal_Int32& rSttPos, sal_Int32 nEndPos ) +{ + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + + SwTextNode* pTextNd = m_rCursor.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "where is the TextNode?" ); + + bool bRet = false; + if( nEndPos == rSttPos ) + return bRet; + + LanguageType eLang = GetLanguage(nEndPos); + if(LANGUAGE_SYSTEM == eLang) + eLang = GetAppLanguage(); + LanguageTag aLanguageTag(eLang); + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + + const OUString sFrameText = pFrame->GetText(); + SwDoc* pDoc = m_rEditSh.GetDoc(); + if ( pFrame->IsRightToLeft() ) + { + // transliterate to Old Hungarian using Numbertext via NatNum12 number format modifier + OUString sWord(sFrameText.copy(rSttPos, nEndPos - rSttPos)); + // Consonant disambiguation using hyphenation + uno::Reference< linguistic2::XHyphenator > xHyph; + xHyph = ::GetHyphenator(); + OUStringBuffer sDisambiguatedWord; + + const ::css::uno::Sequence< ::css::beans::PropertyValue > aProperties; + css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord; + for (int i = 0; i+1 < sWord.getLength(); i++ ) + { + xHyphWord = xHyph->hyphenate( sWord, + aLanguageTag.getLocale(), + i, + aProperties ); + // insert ZWSP at a hyphenation point, if it's not an alternative one (i.e. ssz->sz-sz) + if (xHyphWord.is() && xHyphWord->getHyphenationPos()+1 == i && !xHyphWord->isAlternativeSpelling()) + { + sDisambiguatedWord.append(CHAR_ZWSP); + } + sDisambiguatedWord.append(sWord[i]); + } + sDisambiguatedWord.append(sWord[sWord.getLength()-1]); + + SvNumberFormatter* pFormatter = pDoc->GetNumberFormatter(); + OUString sConverted; + if (pFormatter && !sWord.isEmpty()) + { + Color* pColor = nullptr; + Color** ppColor = &pColor; + // Send text as NatNum12 prefix + OUString sPrefix("[NatNum12 " + sDisambiguatedWord.makeStringAndClear() + "]0"); + if (pFormatter->GetPreviewString(sPrefix, 0, sConverted, ppColor, LANGUAGE_USER_HUNGARIAN_ROVAS)) + bRet = true; + } + + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(rSttPos)), + pFrame->MapViewToModelPos(TextFrameIndex(nEndPos))); + if (bRet && nEndPos <= sFrameText.getLength()) + pDoc->getIDocumentContentOperations().ReplaceRange(aPam, sConverted, false); + } + + return bRet; +} + +// Called by the functions: +// - FnCapitalStartWord +// - FnCapitalStartSentence +// after the exchange of characters. Then the words, if necessary, can be inserted +// into the exception list. +void SwAutoCorrDoc::SaveCpltSttWord( ACFlags nFlag, sal_Int32 nPos, + const OUString& rExceptWord, + sal_Unicode cChar ) +{ + sal_uLong nNode = m_pIndex ? m_pIndex->GetIndex() : m_rCursor.GetPoint()->nNode.GetIndex(); + LanguageType eLang = GetLanguage(nPos); + m_rEditSh.GetDoc()->SetAutoCorrExceptWord( std::make_unique<SwAutoCorrExceptWord>( nFlag, + nNode, nPos, rExceptWord, cChar, eLang )); +} + +LanguageType SwAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const +{ + LanguageType eRet = LANGUAGE_SYSTEM; + + SwTextNode* pNd = m_rCursor.GetPoint()->nNode.GetNode().GetTextNode(); + + if( pNd ) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + eRet = pFrame->GetLangOfChar(TextFrameIndex(nPos), 0, true); + } + if(LANGUAGE_SYSTEM == eRet) + eRet = GetAppLanguage(); + return eRet; +} + +void SwAutoCorrExceptWord::CheckChar( const SwPosition& rPos, sal_Unicode cChr ) +{ + // test only if this is an improvement. + // If yes, then add the word to the list. + if (m_cChar == cChr && rPos.nNode.GetIndex() == m_nNode && rPos.nContent.GetIndex() == m_nContent) + { + // get the current autocorrection: + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + + // then add to the list: + if (ACFlags::CapitalStartWord & m_nFlags) + pACorr->AddWrtSttException(m_sWord, m_eLanguage); + else if (ACFlags::CapitalStartSentence & m_nFlags) + pACorr->AddCplSttException(m_sWord, m_eLanguage); + } +} + +bool SwAutoCorrExceptWord::CheckDelChar( const SwPosition& rPos ) +{ + bool bRet = false; + if (!m_bDeleted && rPos.nNode.GetIndex() == m_nNode && rPos.nContent.GetIndex() == m_nContent) + m_bDeleted = bRet = true; + return bRet; +} + +SwDontExpandItem::~SwDontExpandItem() +{ +} + +void SwDontExpandItem::SaveDontExpandItems( const SwPosition& rPos ) +{ + const SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( pTextNd ) + { + m_pDontExpandItems.reset( new SfxItemSet( const_cast<SwDoc*>(pTextNd->GetDoc())->GetAttrPool(), + aCharFormatSetRange ) ); + const sal_Int32 n = rPos.nContent.GetIndex(); + if (!pTextNd->GetParaAttr( *m_pDontExpandItems, n, n, + n != pTextNd->GetText().getLength() )) + { + m_pDontExpandItems.reset(); + } + } +} + +void SwDontExpandItem::RestoreDontExpandItems( const SwPosition& rPos ) +{ + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( pTextNd ) + { + const sal_Int32 nStart = rPos.nContent.GetIndex(); + if( nStart == pTextNd->GetText().getLength() ) + pTextNd->FormatToTextAttr( pTextNd ); + + if( pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count() ) + { + const size_t nSize = pTextNd->GetpSwpHints()->Count(); + sal_Int32 nAttrStart; + + for( size_t n = 0; n < nSize; ++n ) + { + SwTextAttr* pHt = pTextNd->GetpSwpHints()->Get( n ); + nAttrStart = pHt->GetStart(); + if( nAttrStart > nStart ) // beyond the area + break; + + const sal_Int32* pAttrEnd; + if( nullptr != ( pAttrEnd = pHt->End() ) && + ( ( nAttrStart < nStart && + ( pHt->DontExpand() ? nStart < *pAttrEnd + : nStart <= *pAttrEnd )) || + ( nStart == nAttrStart && + ( nAttrStart == *pAttrEnd || !nStart ))) ) + { + const SfxPoolItem* pItem; + if( !m_pDontExpandItems || SfxItemState::SET != m_pDontExpandItems-> + GetItemState( pHt->Which(), false, &pItem ) || + *pItem != pHt->GetAttr() ) + { + // The attribute was not previously set in this form in the + // paragraph, so it can only be created through insert/copy + // Because of that it is a candidate for DontExpand + pHt->SetDontExpand( true ); + } + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/autofmt.cxx b/sw/source/core/edit/autofmt.cxx new file mode 100644 index 000000000..7588104d5 --- /dev/null +++ b/sw/source/core/edit/autofmt.cxx @@ -0,0 +1,2834 @@ +/* -*- 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 <hintids.hxx> + +#include <unotools/charclass.hxx> + +#include <editeng/boxitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/acorrcfg.hxx> + +#include <swwait.hxx> +#include <fmtpdsc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentRedlineManager.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <redline.hxx> +#include <unocrsr.hxx> +#include <docary.hxx> +#include <editsh.hxx> +#include <index.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <poolfmt.hxx> +#include <ndtxt.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <frminf.hxx> +#include <pagedesc.hxx> +#include <paratr.hxx> +#include <acorrect.hxx> +#include <shellres.hxx> +#include <section.hxx> +#include <frmatr.hxx> +#include <charatr.hxx> +#include <mdiexp.hxx> +#include <strings.hrc> +#include <comcore.hxx> +#include <numrule.hxx> +#include <itabenum.hxx> + +#include <memory> + +using namespace ::com::sun::star; + +//JP 16.12.99: definition: +// from pos cPosEnDash to cPosEmDash all chars changed to em dashes, +// from pos cPosEmDash to cPosEnd all chars changed to em dashes +// all other chars are changed to the user configuration + +const sal_Unicode pBulletChar[6] = { '+', '*', '-', 0x2013, 0x2014, 0 }; +const int cnPosEnDash = 2, cnPosEmDash = 4; + +const sal_Unicode cStarSymbolEnDash = 0x2013; +const sal_Unicode cStarSymbolEmDash = 0x2014; + +SvxSwAutoFormatFlags* SwEditShell::s_pAutoFormatFlags = nullptr; + +// Number of num-/bullet-paragraph templates. MAXLEVEL will soon be raised +// to x, but not the number of templates. (Artifact from <= 4.0) +const sal_uInt16 cnNumBullColls = 4; + +class SwAutoFormat +{ + SvxSwAutoFormatFlags m_aFlags; + SwPaM m_aDelPam; // a Pam that can be used + SwNodeIndex m_aNdIdx; // the index on the current TextNode + SwNodeIndex m_aEndNdIdx; // index on the end of the area + + SwEditShell* m_pEditShell; + SwDoc* m_pDoc; + SwTextNode* m_pCurTextNd; // the current TextNode + SwTextFrame* m_pCurTextFrame; // frame of the current TextNode + sal_uLong m_nEndNdIdx; // for the percentage-display + mutable std::unique_ptr<CharClass> m_pCharClass; // Character classification + mutable LanguageType m_eCharClassLang; + + sal_uInt16 m_nRedlAutoFormatSeqId; + + enum + { + NONE = 0, + DELIM = 1, + DIGIT = 2, + CHG = 4, + LOWER_ALPHA = 8, + UPPER_ALPHA = 16, + LOWER_ROMAN = 32, + UPPER_ROMAN = 64, + NO_DELIM = (DIGIT|LOWER_ALPHA|UPPER_ALPHA|LOWER_ROMAN|UPPER_ROMAN) + }; + + bool m_bEnd : 1; + bool m_bMoreLines : 1; + + CharClass& GetCharClass( LanguageType eLang ) const + { + if( !m_pCharClass || eLang != m_eCharClassLang ) + { + m_pCharClass.reset( new CharClass( LanguageTag( eLang ) ) ); + m_eCharClassLang = eLang; + } + return *m_pCharClass; + } + + static bool IsSpace( const sal_Unicode c ) + { return (' ' == c || '\t' == c || 0x0a == c|| 0x3000 == c /* Jap. space */); } + + void SetColl( sal_uInt16 nId, bool bHdLineOrText = false ); + void GoNextPara(); + static bool HasObjects(const SwTextFrame &); + + // TextNode methods + const SwTextFrame * GetNextNode(bool isCheckEnd = true) const; + static bool IsEmptyLine(const SwTextFrame & rFrame) + { + return rFrame.GetText().isEmpty() + || rFrame.GetText().getLength() == GetLeadingBlanks(rFrame.GetText()); + } + + bool IsOneLine(const SwTextFrame &) const; + bool IsFastFullLine(const SwTextFrame &) const; + bool IsNoAlphaLine(const SwTextFrame &) const; + bool IsEnumericChar(const SwTextFrame &) const; + static bool IsBlanksInString(const SwTextFrame&); + sal_uInt16 CalcLevel(const SwTextFrame&, sal_uInt16 *pDigitLvl = nullptr) const; + sal_Int32 GetBigIndent(TextFrameIndex & rCurrentSpacePos) const; + + static OUString DelLeadingBlanks(const OUString& rStr); + static OUString DelTrailingBlanks( const OUString& rStr ); + static sal_Int32 GetLeadingBlanks( const OUString& rStr ); + static sal_Int32 GetTrailingBlanks( const OUString& rStr ); + + bool IsFirstCharCapital(const SwTextFrame & rNd) const; + sal_uInt16 GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos, + OUString* pPrefix = nullptr, OUString* pPostfix = nullptr, + OUString* pNumTypes = nullptr ) const; + /// get the FORMATTED TextFrame + SwTextFrame* GetFrame( const SwTextNode& rTextNd ) const; + SwTextFrame * EnsureFormatted(SwTextFrame const&) const; + + void BuildIndent(); + void BuildText(); + void BuildTextIndent(); + void BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel ); + void BuildNegIndent( SwTwips nSpaces ); + void BuildHeadLine( sal_uInt16 nLvl ); + + static bool HasBreakAttr(const SwTextFrame &); + void DeleteSel( SwPaM& rPam ); + void DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect); + bool DeleteJoinCurNextPara(SwTextFrame const* pNextFrame, bool bIgnoreLeadingBlanks = false); + /// delete in the node start and/or end + void DeleteLeadingTrailingBlanks( bool bStart = true, bool bEnd = true ); + void DelEmptyLine( bool bTstNextPara = true ); + /// when using multiline paragraphs delete the "left" and/or + /// "right" margins + void DelMoreLinesBlanks( bool bWithLineBreaks = false ); + /// join with the previous paragraph + void JoinPrevPara(); + /// execute AutoCorrect on current TextNode + void AutoCorrect(TextFrameIndex nSttPos = TextFrameIndex(0)); + + bool CanJoin(const SwTextFrame * pNextFrame) const + { + return !m_bEnd && pNextFrame + && !IsEmptyLine(*pNextFrame) + && !IsNoAlphaLine(*pNextFrame) + && !IsEnumericChar(*pNextFrame) + // check the last / first nodes here... + && ((COMPLETE_STRING - 50 - pNextFrame->GetTextNodeFirst()->GetText().getLength()) + > (m_pCurTextFrame->GetMergedPara() + ? m_pCurTextFrame->GetMergedPara()->pLastNode + : m_pCurTextNd)->GetText().getLength()) + && !HasBreakAttr(*pNextFrame); + } + + /// is a dot at the end ?? + static bool IsSentenceAtEnd(const SwTextFrame & rTextFrame); + + bool DoUnderline(); + bool DoTable(); + + void SetRedlineText_( sal_uInt16 nId ); + bool SetRedlineText( sal_uInt16 nId ) { + if( m_aFlags.bWithRedlining ) + SetRedlineText_( nId ); + return true; + } + void ClearRedlineText() { + if( m_aFlags.bWithRedlining ) + m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment(nullptr); + } + +public: + SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags const & rFlags, + SwNodeIndex const * pSttNd = nullptr, SwNodeIndex const * pEndNd = nullptr ); +}; + +static const sal_Unicode* StrChr( const sal_Unicode* pSrc, sal_Unicode c ) +{ + while( *pSrc && *pSrc != c ) + ++pSrc; + return *pSrc ? pSrc : nullptr; +} + +SwTextFrame* SwAutoFormat::GetFrame( const SwTextNode& rTextNd ) const +{ + // get the Frame + const SwContentFrame *pFrame = rTextNd.getLayoutFrame( m_pEditShell->GetLayout() ); + assert(pFrame && "For Autoformat a Layout is needed"); + return EnsureFormatted(*static_cast<SwTextFrame const*>(pFrame)); +} + +SwTextFrame * SwAutoFormat::EnsureFormatted(SwTextFrame const& rFrame) const +{ + SwTextFrame *const pFrame(const_cast<SwTextFrame*>(&rFrame)); + if( m_aFlags.bAFormatByInput && !pFrame->isFrameAreaDefinitionValid() ) + { + DisableCallbackAction a(*pFrame->getRootFrame()); + SwRect aTmpFrame( pFrame->getFrameArea() ); + SwRect aTmpPrt( pFrame->getFramePrintArea() ); + pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + + if( pFrame->getFrameArea() != aTmpFrame || pFrame->getFramePrintArea() != aTmpPrt || + !pFrame->GetPaintSwRect().IsEmpty()) + { + pFrame->SetCompletePaint(); + } + } + + return pFrame->GetFormatted(); +} + +void SwAutoFormat::SetRedlineText_( sal_uInt16 nActionId ) +{ + OUString sText; + sal_uInt16 nSeqNo = 0; + if( STR_AUTOFMTREDL_END > nActionId ) + { + sText = SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ nActionId ]; + switch( nActionId ) + { + case STR_AUTOFMTREDL_SET_NUMBER_BULLET: + case STR_AUTOFMTREDL_DEL_MORELINES: + + // AutoCorrect actions + case STR_AUTOFMTREDL_USE_REPLACE: + case STR_AUTOFMTREDL_CPTL_STT_WORD: + case STR_AUTOFMTREDL_CPTL_STT_SENT: + case STR_AUTOFMTREDL_TYPO: + case STR_AUTOFMTREDL_UNDER: + case STR_AUTOFMTREDL_BOLD: + case STR_AUTOFMTREDL_FRACTION: + case STR_AUTOFMTREDL_DASH: + case STR_AUTOFMTREDL_ORDINAL: + case STR_AUTOFMTREDL_NON_BREAK_SPACE: + case STR_AUTOFMTREDL_TRANSLITERATE_RTL: + nSeqNo = ++m_nRedlAutoFormatSeqId; + break; + } + } +#if OSL_DEBUG_LEVEL > 0 + else + sText = "Action text is missing"; +#endif + + m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText, nSeqNo ); +} + +void SwAutoFormat::GoNextPara() +{ + SwNode* pNewNd = nullptr; + do { + // has to be checked twice before and after incrementation + if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() ) + { + m_bEnd = true; + return; + } + + sw::GotoNextLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout()); + if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() ) + { + m_bEnd = true; + return; + } + else + pNewNd = &m_aNdIdx.GetNode(); + + // not a TextNode -> + // TableNode : skip table + // NoTextNode : skip nodes + // EndNode : at the end, terminate + if( pNewNd->IsEndNode() ) + { + m_bEnd = true; + return; + } + else if( pNewNd->IsTableNode() ) + m_aNdIdx = *pNewNd->EndOfSectionNode(); + else if( pNewNd->IsSectionNode() ) + { + const SwSection& rSect = pNewNd->GetSectionNode()->GetSection(); + if( rSect.IsHiddenFlag() || rSect.IsProtectFlag() ) + m_aNdIdx = *pNewNd->EndOfSectionNode(); + } + } while( !pNewNd->IsTextNode() ); + + if( !m_aFlags.bAFormatByInput ) + ::SetProgressState( m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex(), + m_pDoc->GetDocShell() ); + + m_pCurTextNd = static_cast<SwTextNode*>(pNewNd); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); +} + +bool SwAutoFormat::HasObjects(const SwTextFrame & rFrame) +{ + // Is there something bound to the paragraph in the paragraph + // like Frames, DrawObjects, ... + SwNodeIndex node(*rFrame.GetTextNodeFirst()); + do + { + if (node.GetNode().GetAnchoredFlys() != nullptr) + { + assert(!node.GetNode().GetAnchoredFlys()->empty()); + return true; + } + ++node; + } + while (sw::FrameContainsNode(rFrame, node.GetIndex())); + return false; +} + +const SwTextFrame* SwAutoFormat::GetNextNode(bool const isCheckEnd) const +{ + SwNodeIndex tmp(m_aNdIdx); + sw::GotoNextLayoutTextFrame(tmp, m_pEditShell->GetLayout()); + if ((isCheckEnd && m_aEndNdIdx <= tmp) || !tmp.GetNode().IsTextNode()) + return nullptr; + // note: the returned frame is not necessarily formatted, have to call + // EnsureFormatted for that + return static_cast<SwTextFrame*>(tmp.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout())); +} + +bool SwAutoFormat::IsOneLine(const SwTextFrame & rFrame) const +{ + SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) ); + return aFInfo.IsOneLine(); +} + +bool SwAutoFormat::IsFastFullLine(const SwTextFrame & rFrame) const +{ + bool bRet = m_aFlags.bRightMargin; + if( bRet ) + { + SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) ); + bRet = aFInfo.IsFilled( m_aFlags.nRightMargin ); + } + return bRet; +} + +bool SwAutoFormat::IsEnumericChar(const SwTextFrame& rFrame) const +{ + const OUString& rText = rFrame.GetText(); + TextFrameIndex nBlanks(GetLeadingBlanks(rText)); + const TextFrameIndex nLen = TextFrameIndex(rText.getLength()) - nBlanks; + if( !nLen ) + return false; + + // -, +, * separated by blank ?? + if (TextFrameIndex(2) < nLen && IsSpace(rText[sal_Int32(nBlanks) + 1])) + { + if (StrChr(pBulletChar, rText[sal_Int32(nBlanks)])) + return true; + // Should there be a symbol font at the position? + SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) ); + if (aFInfo.IsBullet(nBlanks)) + return true; + } + + // 1.) / 1. / 1.1.1 / (1). / (1) / ... + return USHRT_MAX != GetDigitLevel(rFrame, nBlanks); +} + +bool SwAutoFormat::IsBlanksInString(const SwTextFrame& rFrame) +{ + // Search more than 5 consecutive blanks/tabs in the string. + OUString sTmp( DelLeadingBlanks(rFrame.GetText()) ); + const sal_Int32 nLen = sTmp.getLength(); + sal_Int32 nIdx = 0; + while (nIdx < nLen) + { + // Skip non-blanks + while (nIdx < nLen && !IsSpace(sTmp[nIdx])) ++nIdx; + if (nIdx == nLen) + return false; + // Then count consecutive blanks + const sal_Int32 nFirst = nIdx; + while (nIdx < nLen && IsSpace(sTmp[nIdx])) ++nIdx; + // And exit if enough consecutive blanks were found + if (nIdx-nFirst > 5) + return true; + } + return false; +} + +sal_uInt16 SwAutoFormat::CalcLevel(const SwTextFrame & rFrame, + sal_uInt16 *const pDigitLvl) const +{ + sal_uInt16 nLvl = 0, nBlnk = 0; + const OUString& rText = rFrame.GetText(); + if( pDigitLvl ) + *pDigitLvl = USHRT_MAX; + + if (RES_POOLCOLL_TEXT_MOVE == rFrame.GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId()) + { + if( m_aFlags.bAFormatByInput ) + { + // this is very non-obvious: on the *first* invocation of + // AutoFormat, the node will have the tabs (any number) converted + // to a fixed indent in BuildTextIndent(), and the number of tabs + // is stored in the node; + // on the *second* invocation of AutoFormat, CalcLevel() will + // retrieve the stored number, and it will be used by + // BuildHeadLine() to select the corresponding heading style. + nLvl = rFrame.GetTextNodeForParaProps()->GetAutoFormatLvl(); + const_cast<SwTextNode *>(rFrame.GetTextNodeForParaProps())->SetAutoFormatLvl(0); + if( nLvl ) + return nLvl; + } + ++nLvl; + } + + for (TextFrameIndex n(0), + nEnd(rText.getLength()); n < nEnd; ++n) + { + switch (rText[sal_Int32(n)]) + { + case ' ': if( 3 == ++nBlnk ) + { + ++nLvl; + nBlnk = 0; + } + break; + case '\t': ++nLvl; + nBlnk = 0; + break; + default: + if( pDigitLvl ) + // test 1.) / 1. / 1.1.1 / (1). / (1) / ... + *pDigitLvl = GetDigitLevel(rFrame, n); + return nLvl; + } + } + return nLvl; +} + +sal_Int32 SwAutoFormat::GetBigIndent(TextFrameIndex & rCurrentSpacePos) const +{ + SwTextFrameInfo aFInfo( m_pCurTextFrame ); + const SwTextFrame* pNextFrame = nullptr; + + if( !m_bMoreLines ) + { + pNextFrame = GetNextNode(); + if (!CanJoin(pNextFrame) || !IsOneLine(*pNextFrame)) + return 0; + + pNextFrame = EnsureFormatted(*pNextFrame); + } + + return aFInfo.GetBigIndent( rCurrentSpacePos, pNextFrame ); +} + +bool SwAutoFormat::IsNoAlphaLine(const SwTextFrame & rFrame) const +{ + const OUString& rStr = rFrame.GetText(); + if( rStr.isEmpty() ) + return false; + // or better: determine via number of AlphaNum and !AlphaNum characters + sal_Int32 nANChar = 0, nBlnk = 0; + + for (TextFrameIndex n(0), + nEnd(rStr.getLength()); n < nEnd; ++n) + if (IsSpace(rStr[sal_Int32(n)])) + ++nBlnk; + else + { + auto const pair = rFrame.MapViewToModel(n); + CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage()); + if (rCC.isLetterNumeric(rStr, sal_Int32(n))) + ++nANChar; + } + + // If there are 75% of non-alphanumeric characters, then true + sal_uLong nLen = rStr.getLength() - nBlnk; + nLen = ( nLen * 3 ) / 4; // long overflow, if the strlen > sal_uInt16 + return sal_Int32(nLen) < (rStr.getLength() - nANChar - nBlnk); +} + +bool SwAutoFormat::DoUnderline() +{ + if( !m_aFlags.bSetBorder ) + return false; + + OUString const& rText(m_pCurTextFrame->GetText()); + int eState = 0; + sal_Int32 nCnt = 0; + while (nCnt < rText.getLength()) + { + int eTmp = 0; + switch (rText[nCnt]) + { + case '-': eTmp = 1; break; + case '_': eTmp = 2; break; + case '=': eTmp = 3; break; + case '*': eTmp = 4; break; + case '~': eTmp = 5; break; + case '#': eTmp = 6; break; + default: + return false; + } + if( 0 == eState ) + eState = eTmp; + else if( eState != eTmp ) + return false; + ++nCnt; + } + + if( 2 < nCnt ) + { + // then underline the previous paragraph if one exists + DelEmptyLine( false ); // -> point will be on end of current paragraph + // WARNING: rText may be deleted now, m_pCurTextFrame may be nullptr + m_aDelPam.SetMark(); + // apply to last node & rely on InsertItemSet to apply it to props-node + m_aDelPam.GetMark()->nContent = 0; + + editeng::SvxBorderLine aLine; + switch( eState ) + { + case 1: // single, 0.05 pt + aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID); + aLine.SetWidth( DEF_LINE_WIDTH_0 ); + break; + case 2: // single, 1.0 pt + aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID); + aLine.SetWidth( DEF_LINE_WIDTH_1 ); + break; + case 3: // double, 1.0 pt + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + aLine.SetWidth( DEF_LINE_WIDTH_1 ); + break; + case 4: // double (thick/thin), 4.0 pt + aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP); + aLine.SetWidth( DEF_LINE_WIDTH_3 ); + break; + case 5: // double (thin/thick), 4.0 pt + aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP); + aLine.SetWidth( DEF_LINE_WIDTH_3 ); + break; + case 6: // double, 2.5 pt + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + aLine.SetWidth( DEF_LINE_WIDTH_2 ); + break; + } + SfxItemSet aSet(m_pDoc->GetAttrPool(), + svl::Items<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER, + RES_BOX, RES_BOX>{}); + aSet.Put( SwParaConnectBorderItem( false ) ); + SvxBoxItem aBox( RES_BOX ); + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + aBox.SetDistance(42, SvxBoxItemLine::BOTTOM ); // ~0,75 mm + aSet.Put(aBox); + m_pDoc->getIDocumentContentOperations().InsertItemSet(m_aDelPam, aSet, + SetAttrMode::DEFAULT, m_pEditShell->GetLayout()); + + m_aDelPam.DeleteMark(); + } + return 2 < nCnt; +} + +bool SwAutoFormat::DoTable() +{ + if( !m_aFlags.bCreateTable || !m_aFlags.bAFormatByInput || + m_pCurTextNd->FindTableNode() ) + return false; + + const OUString& rTmp = m_pCurTextFrame->GetText(); + TextFrameIndex nSttPlus(GetLeadingBlanks(rTmp)); + TextFrameIndex nEndPlus(GetTrailingBlanks(rTmp)); + sal_Unicode cChar; + + if (TextFrameIndex(2) > nEndPlus - nSttPlus + || ('+' != (cChar = rTmp[sal_Int32(nSttPlus)]) && '|' != cChar) + || ('+' != (cChar = rTmp[sal_Int32(nEndPlus) - 1]) && '|' != cChar)) + return false; + + SwTextFrameInfo aInfo( m_pCurTextFrame ); + + TextFrameIndex n = nSttPlus; + std::vector<sal_uInt16> aPosArr; + + while (n < TextFrameIndex(rTmp.getLength())) + { + switch (rTmp[sal_Int32(n)]) + { + case '-': + case '_': + case '=': + case ' ': + case '\t': + break; + + case '+': + case '|': + aPosArr.push_back( static_cast<sal_uInt16>(aInfo.GetCharPos(n)) ); + break; + + default: + return false; + } + if( ++n == nEndPlus ) + break; + } + + if( 1 < aPosArr.size() ) + { + // get the text node's alignment + sal_uInt16 nColCnt = aPosArr.size() - 1; + SwTwips nSttPos = aPosArr[ 0 ]; + sal_Int16 eHori; + switch (m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust()) + { + case SvxAdjust::Center: eHori = text::HoriOrientation::CENTER; break; + case SvxAdjust::Right: eHori = text::HoriOrientation::RIGHT; break; + + default: + if( nSttPos ) + { + eHori = text::HoriOrientation::NONE; + // then - as last - we need to add the current frame width into the array + aPosArr.push_back( static_cast<sal_uInt16>(m_pCurTextFrame->getFrameArea().Width()) ); + } + else + eHori = text::HoriOrientation::LEFT; + break; + } + + // then create a table that matches the character + DelEmptyLine(); + // WARNING: rTmp may be deleted now, m_pCurTextFrame may be nullptr + SwNodeIndex aIdx( m_aDelPam.GetPoint()->nNode ); + m_aDelPam.Move( fnMoveForward ); + m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ), + *m_aDelPam.GetPoint(), 1, nColCnt, eHori, + nullptr, &aPosArr ); + m_aDelPam.GetPoint()->nNode = aIdx; + } + return 1 < aPosArr.size(); +} + +OUString SwAutoFormat::DelLeadingBlanks( const OUString& rStr ) +{ + sal_Int32 nL, n; + for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[n] ); ++n ) + ; + if( n ) // no Spaces + return rStr.copy(n); + return rStr; +} + +OUString SwAutoFormat::DelTrailingBlanks( const OUString& rStr ) +{ + sal_Int32 nL = rStr.getLength(), n = nL; + if( !nL ) + return rStr; + + while( --n && IsSpace( rStr[ n ] ) ) + ; + if( n+1 != nL ) // no Spaces + return rStr.copy( 0, n+1 ); + return rStr; +} + +sal_Int32 SwAutoFormat::GetLeadingBlanks( const OUString& rStr ) +{ + sal_Int32 nL; + sal_Int32 n; + + for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[ n ] ); ++n ) + ; + return n; +} + +sal_Int32 SwAutoFormat::GetTrailingBlanks( const OUString& rStr ) +{ + sal_Int32 nL = rStr.getLength(), n = nL; + if( !nL ) + return 0; + + while( --n && IsSpace( rStr[ n ] ) ) + ; + return ++n; +} + +bool SwAutoFormat::IsFirstCharCapital(const SwTextFrame& rFrame) const +{ + const OUString& rText = rFrame.GetText(); + for (TextFrameIndex n(0), + nEnd(rText.getLength()); n < nEnd; ++n) + if (!IsSpace(rText[sal_Int32(n)])) + { + auto const pair = rFrame.MapViewToModel(n); + CharClass& rCC = GetCharClass( pair.first->GetSwAttrSet(). + GetLanguage().GetLanguage() ); + sal_Int32 nCharType = rCC.getCharacterType(rText, sal_Int32(n)); + return CharClass::isLetterType( nCharType ) && + 0 != ( i18n::KCharacterType::UPPER & + nCharType ); + } + return false; +} + +sal_uInt16 +SwAutoFormat::GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos, + OUString* pPrefix, OUString* pPostfix, OUString* pNumTypes ) const +{ + + // check for 1.) / 1. / 1.1.1 / (1). / (1) / ... + const OUString& rText = rFrame.GetText(); + sal_Int32 nPos(rPos); + int eScan = NONE; + + sal_uInt16 nStart = 0; + sal_uInt8 nDigitLvl = 0, nDigitCnt = 0; + // count number of parenthesis to assure a sensible order is found + sal_uInt16 nOpeningParentheses = 0; + sal_uInt16 nClosingParentheses = 0; + + while (nPos < rText.getLength() && nDigitLvl < MAXLEVEL - 1) + { + auto const pair = rFrame.MapViewToModel(TextFrameIndex(nPos)); + CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage()); + const sal_Unicode cCurrentChar = rText[nPos]; + if( ('0' <= cCurrentChar && '9' >= cCurrentChar) || + (0xff10 <= cCurrentChar && 0xff19 >= cCurrentChar) ) + { + if( eScan & DELIM ) + { + if( eScan & CHG ) // not if it starts with a number + { + ++nDigitLvl; + if( pPostfix ) + *pPostfix += "\x01"; + } + + if( pNumTypes ) + *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC)); + + eScan = eScan | CHG; + } + else if( pNumTypes && !(eScan & DIGIT) ) + *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC)); + + eScan &= ~DELIM; // remove Delim + if( 0 != (eScan & ~CHG) && DIGIT != (eScan & ~CHG)) + return USHRT_MAX; + + eScan |= DIGIT; // add Digit + if( 3 == ++nDigitCnt ) // more than 2 numbers are not an enum anymore + return USHRT_MAX; + + nStart *= 10; + nStart += cCurrentChar <= '9' ? cCurrentChar - '0' : cCurrentChar - 0xff10; + } + else if( rCC.isAlpha( rText, nPos ) ) + { + bool bIsUpper = + 0 != ( i18n::KCharacterType::UPPER & + rCC.getCharacterType( rText, nPos )); + sal_Unicode cLow = rCC.lowercase(rText, nPos, 1)[0], cNumTyp; + int eTmpScan; + + // Roman numbers are "mdclxvi". Since we want to start numbering with c or d more often, + // convert first to characters and later to roman numbers if needed. + if( 256 > cLow && strchr( "mdclxvi", cLow ) ) + { + if( bIsUpper ) + { + cNumTyp = '0' + SVX_NUM_ROMAN_UPPER; + eTmpScan = UPPER_ROMAN; + } + else + { + cNumTyp = '0' + SVX_NUM_ROMAN_LOWER; + eTmpScan = LOWER_ROMAN; + } + } + else if( bIsUpper ) + { + cNumTyp = '0' + SVX_NUM_CHARS_UPPER_LETTER; + eTmpScan = UPPER_ALPHA; + } + else + { + cNumTyp = '0' + SVX_NUM_CHARS_LOWER_LETTER; + eTmpScan = LOWER_ALPHA; + } + + // Switch to roman numbers (only for c/d!) + if( 1 == nDigitCnt && ( eScan & (UPPER_ALPHA|LOWER_ALPHA) ) && + ( 3 == nStart || 4 == nStart) && 256 > cLow && + strchr( "mdclxvi", cLow ) && + (( eScan & UPPER_ALPHA ) ? (eTmpScan & (UPPER_ALPHA|UPPER_ROMAN)) + : (eTmpScan & (LOWER_ALPHA|LOWER_ROMAN))) ) + { + sal_Unicode c = '0'; + nStart = 3 == nStart ? 100 : 500; + if( UPPER_ALPHA == eTmpScan ) + { + eTmpScan = UPPER_ROMAN; + c += SVX_NUM_ROMAN_UPPER; + } + else + { + eTmpScan = LOWER_ROMAN; + c += SVX_NUM_ROMAN_LOWER; + } + + eScan = (eScan & ~(UPPER_ALPHA|LOWER_ALPHA)) | eTmpScan; + if( pNumTypes ) + (*pNumTypes) = pNumTypes->replaceAt( pNumTypes->getLength() - 1, 1, OUString(c) ); + } + + if( eScan & DELIM ) + { + if( eScan & CHG ) // not if it starts with a number + { + ++nDigitLvl; + if( pPostfix ) + *pPostfix += "\x01"; + } + + if( pNumTypes ) + *pNumTypes += OUStringChar(cNumTyp); + eScan = eScan | CHG; + } + else if( pNumTypes && !(eScan & eTmpScan) ) + *pNumTypes += OUStringChar(cNumTyp); + + eScan &= ~DELIM; // remove Delim + + // if another type is set, stop here + if( 0 != ( eScan & ~CHG ) && eTmpScan != ( eScan & ~CHG )) + return USHRT_MAX; + + if( eTmpScan & (UPPER_ALPHA | LOWER_ALPHA) ) + { + // allow characters only if they appear once + return USHRT_MAX; + } + else + { + // roman numbers, check if valid characters + sal_uInt16 nVal; + bool bError = false; + switch( cLow ) + { + case 'm': nVal = 1000; goto CHECK_ROMAN_1; + case 'd': nVal = 500; goto CHECK_ROMAN_5; + case 'c': nVal = 100; goto CHECK_ROMAN_1; + case 'l': nVal = 50; goto CHECK_ROMAN_5; + case 'x': nVal = 10; goto CHECK_ROMAN_1; + case 'v': nVal = 5; goto CHECK_ROMAN_5; + +CHECK_ROMAN_1: + { + int nMod5 = nStart % (nVal * 5); + int nLast = nStart % nVal; + int n10 = nVal / 10; + + if( nMod5 == ((3 * nVal) + n10 ) || + nMod5 == ((4 * nVal) + n10 ) || + nLast == n10 ) + nStart = static_cast<sal_uInt16>(nStart + (n10 * 8)); + else if( nMod5 == 0 || + nMod5 == (1 * nVal) || + nMod5 == (2 * nVal) ) + nStart = nStart + nVal; + else + bError = true; + } + break; + +CHECK_ROMAN_5: + { + if( ( nStart / nVal ) & 1 ) + bError = true; + else + { + int nMod = nStart % nVal; + int n10 = nVal / 5; + if( n10 == nMod ) + nStart = static_cast<sal_uInt16>(nStart + (3 * n10)); + else if( 0 == nMod ) + nStart = nStart + nVal; + else + bError = true; + } + } + break; + + case 'i': + if( nStart % 5 >= 3 ) + bError = true; + else + nStart += 1; + break; + + default: + bError = true; + } + + if( bError ) + return USHRT_MAX; + } + eScan |= eTmpScan; // add Digit + ++nDigitCnt; + } + else if( (256 > cCurrentChar && + strchr( ".)(", cCurrentChar )) || + 0x3002 == cCurrentChar /* Chinese trad. dot */|| + 0xff0e == cCurrentChar /* Japanese dot */|| + 0xFF08 == cCurrentChar /* opening bracket Chin./Jap.*/|| + 0xFF09 == cCurrentChar )/* closing bracket Chin./Jap. */ + { + if(cCurrentChar == '(' || cCurrentChar == 0xFF09) + nOpeningParentheses++; + else if(cCurrentChar == ')'|| cCurrentChar == 0xFF08) + nClosingParentheses++; + // only if no numbers were read until here + if( pPrefix && !( eScan & ( NO_DELIM | CHG )) ) + *pPrefix += OUStringChar(rText[nPos]); + else if( pPostfix ) + *pPostfix += OUStringChar(rText[nPos]); + + if( NO_DELIM & eScan ) + { + eScan |= CHG; + if( pPrefix ) + *pPrefix += "\x01" + OUString::number( nStart ); + } + eScan &= ~NO_DELIM; // remove Delim + eScan |= DELIM; // add Digit + nDigitCnt = 0; + nStart = 0; + } + else + break; + ++nPos; + } + if (!( CHG & eScan ) || rPos == TextFrameIndex(nPos) || + nPos == rText.getLength() || !IsSpace(rText[nPos]) || + (nOpeningParentheses > nClosingParentheses)) + return USHRT_MAX; + + if( (NO_DELIM & eScan) && pPrefix ) // do not forget the last one + *pPrefix += "\x01" + OUString::number( nStart ); + + rPos = TextFrameIndex(nPos); + return nDigitLvl; // 0 .. 9 (MAXLEVEL - 1) +} + +void SwAutoFormat::SetColl( sal_uInt16 nId, bool bHdLineOrText ) +{ + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->nNode = *m_pCurTextFrame->GetTextNodeForParaProps(); + m_aDelPam.GetPoint()->nContent.Assign(m_aDelPam.GetPoint()->nNode.GetNode().GetContentNode(), 0); + + // keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes + SfxItemSet aSet( + m_pDoc->GetAttrPool(), + svl::Items< + RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE, + RES_PARATR_ADJUST, RES_PARATR_ADJUST, + RES_PARATR_TABSTOP, RES_PARATR_DROP, + RES_BACKGROUND, RES_SHADOW>{}); + + if (m_aDelPam.GetPoint()->nNode.GetNode().GetTextNode()->HasSwAttrSet()) + { + aSet.Put(*m_aDelPam.GetPoint()->nNode.GetNode().GetTextNode()->GetpSwAttrSet()); + // take HeaderLine/TextBody only if centered or right aligned, otherwise only justification + SvxAdjustItem const * pAdj; + if( SfxItemState::SET == aSet.GetItemState( RES_PARATR_ADJUST, + false, reinterpret_cast<const SfxPoolItem**>(&pAdj) )) + { + SvxAdjust eAdj = pAdj->GetAdjust(); + if( bHdLineOrText ? (SvxAdjust::Right != eAdj && + SvxAdjust::Center != eAdj) + : SvxAdjust::Block != eAdj ) + aSet.ClearItem( RES_PARATR_ADJUST ); + } + } + + m_pDoc->SetTextFormatCollByAutoFormat( *m_aDelPam.GetPoint(), nId, &aSet ); +} + +static bool HasSelBlanks( + SwTextFrame const*const pStartFrame, TextFrameIndex & rStartIndex, + SwTextFrame const*const pEndFrame, TextFrameIndex & rEndIndex) +{ + if (TextFrameIndex(0) < rEndIndex + && rEndIndex < TextFrameIndex(pEndFrame->GetText().getLength()) + && ' ' == pEndFrame->GetText()[sal_Int32(rEndIndex) - 1]) + { + --rEndIndex; + return true; + } + if (rStartIndex < TextFrameIndex(pStartFrame->GetText().getLength()) + && ' ' == pStartFrame->GetText()[sal_Int32(rStartIndex)]) + { + ++rStartIndex; + return true; + } + return false; +} + +bool SwAutoFormat::HasBreakAttr(const SwTextFrame& rTextFrame) +{ + const SfxItemSet *const pSet = rTextFrame.GetTextNodeFirst()->GetpSwAttrSet(); + if( !pSet ) + return false; + + const SfxPoolItem* pItem; + if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pItem ) + && SvxBreak::NONE != static_cast<const SvxFormatBreakItem*>(pItem)->GetBreak() ) + return true; + + if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pItem ) + && static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc() + && UseOnPage::NONE != static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc()->GetUseOn() ) + return true; + return false; +} + +/// Is there a dot at the end? +bool SwAutoFormat::IsSentenceAtEnd(const SwTextFrame & rTextFrame) +{ + const OUString& rStr = rTextFrame.GetText(); + sal_Int32 n = rStr.getLength(); + if( !n ) + return true; + + while( --n && IsSpace( rStr[ n ] ) ) + ; + return '.' == rStr[ n ]; +} + +/// Delete beginning and/or end in a node +void SwAutoFormat::DeleteLeadingTrailingBlanks(bool bStart, bool bEnd) +{ + if( m_aFlags.bAFormatByInput + ? m_aFlags.bAFormatByInpDelSpacesAtSttEnd + : m_aFlags.bAFormatDelSpacesAtSttEnd ) + { + // delete blanks at the end of the current and at the beginning of the next one + m_aDelPam.DeleteMark(); + TextFrameIndex nPos(GetLeadingBlanks(m_pCurTextFrame->GetText())); + if (bStart && TextFrameIndex(0) != nPos) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + DeleteSel( m_aDelPam ); + m_aDelPam.DeleteMark(); + } + nPos = TextFrameIndex(GetTrailingBlanks(m_pCurTextFrame->GetText())); + if (bEnd && TextFrameIndex(m_pCurTextFrame->GetText().getLength()) != nPos) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(m_pCurTextFrame->GetText().getLength())); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + DeleteSel( m_aDelPam ); + m_aDelPam.DeleteMark(); + } + } +} + +namespace sw { + +bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges, + SwDoc & rDoc, SwPaM const& rDelPam) +{ + bool isNoRedline(true); + SwRedlineTable::size_type tmp; + IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess()); + if (!(rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete)) + { + return isNoRedline; + } + rIDRA.GetRedline(*rDelPam.Start(), &tmp); + SwPosition const* pCurrent(rDelPam.Start()); + for ( ; tmp < rIDRA.GetRedlineTable().size(); ++tmp) + { + SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]); + if (*rDelPam.End() <= *pRedline->Start()) + { + break; + } + if (*pRedline->End() <= *rDelPam.Start()) + { + continue; + } + if (pRedline->GetType() == RedlineType::Delete) + { + assert(*pRedline->Start() != *pRedline->End()); + isNoRedline = false; + if (*pCurrent < *pRedline->Start()) + { + rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent)); + rRanges.back()->SetMark(); + *rRanges.back()->GetPoint() = *pRedline->Start(); + } + pCurrent = pRedline->End(); + } + } + if (!isNoRedline && *pCurrent < *rDelPam.End()) + { + rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent)); + rRanges.back()->SetMark(); + *rRanges.back()->GetPoint() = *rDelPam.End(); + } + return isNoRedline; +} + +} // namespace sw + +void SwAutoFormat::DeleteSel(SwPaM & rDelPam) +{ + std::vector<std::shared_ptr<SwUnoCursor>> ranges; // need correcting cursor + if (GetRanges(ranges, *m_pDoc, rDelPam)) + { + DeleteSelImpl(rDelPam, rDelPam); + } + else + { + for (auto const& pCursor : ranges) + { + DeleteSelImpl(*pCursor, rDelPam); + } + } +} + +void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect) +{ + if (m_aFlags.bWithRedlining || &rDelPam != &rPamToCorrect) + { + // Add to Shell-Cursor-Ring so that DelPam will be moved as well! + SwPaM* pShCursor = m_pEditShell->GetCursor_(); + SwPaM aTmp( *m_pCurTextNd, 0, pShCursor ); + + SwPaM* pPrev = rPamToCorrect.GetPrev(); + rPamToCorrect.GetRingContainer().merge( pShCursor->GetRingContainer() ); + + m_pEditShell->DeleteSel( rDelPam ); + + // and remove Pam again: + SwPaM* p; + SwPaM* pNext = &rPamToCorrect; + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo( &rPamToCorrect ); + } while( p != pPrev ); + + m_aNdIdx = aTmp.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date + } + else + m_pEditShell->DeleteSel( rDelPam ); +} + +bool SwAutoFormat::DeleteJoinCurNextPara(SwTextFrame const*const pNextFrame, + bool const bIgnoreLeadingBlanks) +{ + // delete blanks at the end of the current and at the beginning of the next one + m_aDelPam.DeleteMark(); + TextFrameIndex nTrailingPos(GetTrailingBlanks(m_pCurTextFrame->GetText())); + + SwTextFrame const*const pEndFrame(pNextFrame ? pNextFrame : m_pCurTextFrame); + TextFrameIndex nLeadingPos(0); + if (pNextFrame) + { + nLeadingPos = TextFrameIndex( + bIgnoreLeadingBlanks ? 0 : GetLeadingBlanks(pNextFrame->GetText())); + } + else + { + nLeadingPos = TextFrameIndex(m_pCurTextFrame->GetText().getLength()); + } + + // Is there a Blank at the beginning or end? + // Do not delete it, it will be inserted again. + bool bHasBlnks = HasSelBlanks(m_pCurTextFrame, nTrailingPos, pEndFrame, nLeadingPos); + + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTrailingPos); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = pEndFrame->MapViewToModelPos(nLeadingPos); + + if( *m_aDelPam.GetPoint() != *m_aDelPam.GetMark() ) + DeleteSel( m_aDelPam ); + m_aDelPam.DeleteMark(); + // note: keep m_aDelPam point at insert pos. for clients + + return !bHasBlnks; +} + +void SwAutoFormat::DelEmptyLine( bool bTstNextPara ) +{ + SetRedlineText( STR_AUTOFMTREDL_DEL_EMPTY_PARA ); + // delete blanks in empty paragraph + m_aDelPam.DeleteMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(0)); + m_aDelPam.SetMark(); + + m_aDelPam.GetMark()->nNode = m_pCurTextFrame->GetTextNodeFirst()->GetIndex() - 1; + SwTextNode* pTNd = m_aDelPam.GetNode( false ).GetTextNode(); + if( pTNd ) + // first use the previous text node + m_aDelPam.GetMark()->nContent.Assign(pTNd, pTNd->GetText().getLength()); + else if( bTstNextPara ) + { + // then try the next (at the beginning of a Doc, table cells, frames, ...) + m_aDelPam.GetMark()->nNode = (m_pCurTextFrame->GetMergedPara() + ? m_pCurTextFrame->GetMergedPara()->pLastNode + : m_pCurTextNd + )->GetIndex() + 1; + pTNd = m_aDelPam.GetNode( false ).GetTextNode(); + if( pTNd ) + { + m_aDelPam.GetMark()->nContent.Assign( pTNd, 0 ); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(m_pCurTextFrame->GetText().getLength())); + } + } + if( pTNd ) + { // join with previous or next paragraph + DeleteSel(m_aDelPam); + } + assert(m_aDelPam.GetNode().IsTextNode()); + assert(!m_aDelPam.HasMark()); + m_aDelPam.SetMark(); // mark remains at join position + m_pCurTextFrame = GetFrame(*m_aDelPam.GetNode().GetTextNode()); + // replace until the end of the merged paragraph + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(m_pCurTextFrame->GetText().getLength())); + if (*m_aDelPam.GetPoint() != *m_aDelPam.GetMark()) + { // tdf#137245 replace (not delete) to preserve any flys + m_pDoc->getIDocumentContentOperations().ReplaceRange(m_aDelPam, "", false); + } + + m_aDelPam.DeleteMark(); + ClearRedlineText(); + // note: this likely has deleted m_pCurTextFrame - update it... + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = m_pCurTextNd ? GetFrame( *m_pCurTextNd ) : nullptr; +} + +void SwAutoFormat::DelMoreLinesBlanks( bool bWithLineBreaks ) +{ + if( m_aFlags.bAFormatByInput + ? m_aFlags.bAFormatByInpDelSpacesBetweenLines + : m_aFlags.bAFormatDelSpacesBetweenLines ) + { + // delete all blanks on the left and right of the indentation + m_aDelPam.DeleteMark(); + + SwTextFrameInfo aFInfo( m_pCurTextFrame ); + std::vector<std::pair<TextFrameIndex, TextFrameIndex>> spaces; + aFInfo.GetSpaces(spaces, !m_aFlags.bAFormatByInput || bWithLineBreaks); + + // tdf#123285 iterate backwards - delete invalidates following indexes + for (auto iter = spaces.rbegin(); iter != spaces.rend(); ++iter) + { + auto & rSpaceRange(*iter); + assert(rSpaceRange.first != rSpaceRange.second); + bool const bHasBlanks = HasSelBlanks( + m_pCurTextFrame, rSpaceRange.first, + m_pCurTextFrame, rSpaceRange.second); + if (rSpaceRange.first != rSpaceRange.second) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.first); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.second); + DeleteSel(m_aDelPam); + if (!bHasBlanks) + { + m_pDoc->getIDocumentContentOperations().InsertString(m_aDelPam, OUString(' ')); + } + m_aDelPam.DeleteMark(); + } + } + } +} + +void SwAutoFormat::JoinPrevPara() +{ + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->nNode = *m_pCurTextFrame->GetTextNodeFirst(); + m_aDelPam.GetPoint()->nContent.Assign(m_pCurTextFrame->GetTextNodeFirst(), 0); + m_aDelPam.SetMark(); + + --m_aDelPam.GetPoint()->nNode; + SwTextNode* pTNd = m_aDelPam.GetNode().GetTextNode(); + if( pTNd ) + { + // use the previous text node first + m_aDelPam.GetPoint()->nContent.Assign(pTNd, pTNd->GetText().getLength()); + DeleteSel( m_aDelPam ); + } + m_aDelPam.DeleteMark(); +} + +void SwAutoFormat::BuildIndent() +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_INDENT ); + + // read all succeeding paragraphs that belong to this indentation + bool bBreak = true; + if( m_bMoreLines ) + DelMoreLinesBlanks( true ); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + SetColl( RES_POOLCOLL_TEXT_IDENT ); + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + const SwTextFrame * pNextFrame = GetNextNode(); + if (pNextFrame && !m_bEnd) + { + do { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + pNextFrame = GetNextNode(); + } + while (CanJoin(pNextFrame) + && !CalcLevel(*pNextFrame)); + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildTextIndent() +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT_INDENT); + // read all succeeding paragraphs that belong to this indentation + bool bBreak = true; + if( m_bMoreLines ) + DelMoreLinesBlanks( true ); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + + if( m_aFlags.bAFormatByInput ) + { + const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAutoFormatLvl( + static_cast<sal_uInt8>(CalcLevel(*m_pCurTextFrame))); + } + + SetColl( RES_POOLCOLL_TEXT_MOVE ); + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) && + CalcLevel(*pNextFrame)) + { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + pNextFrame = GetNextNode(); + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildText() +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT ); + // read all succeeding paragraphs that belong to this text without indentation + bool bBreak = true; + if( m_bMoreLines ) + DelMoreLinesBlanks(); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + SetColl( RES_POOLCOLL_TEXT, true ); + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) && + !CalcLevel(*pNextFrame)) + { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + const SwTextFrame *const pCurrNode = pNextFrame; + pNextFrame = GetNextNode(); + if (!pNextFrame || pCurrNode == pNextFrame) + break; + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel ) +{ + SetRedlineText( STR_AUTOFMTREDL_SET_NUMBER_BULLET ); + + bool bBreak = true; + + // first, determine current indentation and frame width + SwTwips nFrameWidth = m_pCurTextFrame->getFramePrintArea().Width(); + SwTwips nLeftTextPos; + { + TextFrameIndex nPos(0); + while (nPos < TextFrameIndex(m_pCurTextFrame->GetText().getLength()) + && IsSpace(m_pCurTextFrame->GetText()[sal_Int32(nPos)])) + { + ++nPos; + } + + SwTextFrameInfo aInfo( m_pCurTextFrame ); + nLeftTextPos = aInfo.GetCharPos(nPos); + nLeftTextPos -= m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace().GetLeft(); + } + + if( m_bMoreLines ) + DelMoreLinesBlanks(); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + bool bRTL = m_pEditShell->IsInRightToLeftText(); + DeleteLeadingTrailingBlanks(); + + bool bChgBullet = false, bChgEnum = false; + TextFrameIndex nAutoCorrPos(0); + + // if numbering is set, get the current one + SwNumRule aRule( m_pDoc->GetUniqueNumRuleName(), + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode() ); + + const SwNumRule* pCur = nullptr; + if (m_aFlags.bSetNumRule) + { + pCur = m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule(); + if (pCur) + { + aRule = *pCur; + } + } + + // replace bullet character with defined one + const OUString& rStr = m_pCurTextFrame->GetText(); + TextFrameIndex nTextStt(0); + const sal_Unicode* pFndBulletChr = nullptr; + if (m_aFlags.bChgEnumNum && 2 < rStr.getLength()) + pFndBulletChr = StrChr(pBulletChar, rStr[sal_Int32(nTextStt)]); + if (nullptr != pFndBulletChr && IsSpace(rStr[sal_Int32(nTextStt) + 1])) + { + if( m_aFlags.bAFormatByInput ) + { + if( m_aFlags.bSetNumRule ) + { + SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_BULLET_LEVEL ); + bChgBullet = true; + // Was the format already somewhere adjusted? + if( !aRule.GetNumFormat( nLvl ) ) + { + int nBulletPos = pFndBulletChr - pBulletChar; + sal_Unicode cBullChar; + const vcl::Font* pBullFnt( nullptr ); + if( nBulletPos < cnPosEnDash ) + { + cBullChar = m_aFlags.cBullet; + pBullFnt = &m_aFlags.aBulletFont; + } + else + { + cBullChar = nBulletPos < cnPosEmDash + ? cStarSymbolEnDash + : cStarSymbolEmDash; + // #i63395# + // Only apply user defined default bullet font + if ( numfunc::IsDefBulletFontUserDefined() ) + { + pBullFnt = &numfunc::GetDefBulletFont(); + } + } + + sal_Int32 nAbsPos = lBulletIndent; + SwTwips nSpaceSteps = nLvl + ? nLeftTextPos / nLvl + : lBulletIndent; + for( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nAbsPos = nAbsPos + nSpaceSteps ) + { + SwNumFormat aFormat( aRule.Get( n ) ); + aFormat.SetBulletFont( pBullFnt ); + aFormat.SetBulletChar( cBullChar ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + // #i93908# clear suffix for bullet lists + aFormat.SetPrefix(OUString()); + aFormat.SetSuffix(OUString()); + aFormat.SetFirstLineOffset( lBulletFirstLineOffset ); + aFormat.SetAbsLSpace( nAbsPos ); + if( !aFormat.GetCharFormat() ) + aFormat.SetCharFormat( pCFormat ); + if( bRTL ) + aFormat.SetNumAdjust( SvxAdjust::Right ); + + aRule.Set( n, aFormat ); + + if( n == nLvl && + nFrameWidth < ( nSpaceSteps * MAXLEVEL ) ) + nSpaceSteps = ( nFrameWidth - nLeftTextPos ) / + ( MAXLEVEL - nLvl ); + } + } + } + } + else + { + bChgBullet = true; + SetColl( static_cast<sal_uInt16>(RES_POOLCOLL_BULLET_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 )) ); + } + } + else + { + // Then it is a numbering + + //JP 21.11.97: The NumLevel is either the DigitLevel or, if the latter is not existent or 0, + // it is determined by the indentation level. + + OUString aPostfix, aPrefix, aNumTypes; + nDigitLevel = GetDigitLevel(*m_pCurTextFrame, nTextStt, + &aPrefix, &aPostfix, &aNumTypes); + if (USHRT_MAX != nDigitLevel) + { + bChgEnum = true; + + // Level 0 and Indentation, determine level by left indentation and default NumIndent + if( !nDigitLevel && nLeftTextPos ) + nLvl = std::min( sal_uInt16( nLeftTextPos / lNumberIndent ), + sal_uInt16( MAXLEVEL - 1 ) ); + else + nLvl = nDigitLevel; + } + + if( bChgEnum && m_aFlags.bSetNumRule ) + { + if( !pCur ) // adjust NumRule if it is new + { + SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_NUM_LEVEL ); + + sal_Int32 nPrefixIdx{ 0 }; + if( !nDigitLevel ) + { + SwNumFormat aFormat( aRule.Get( nLvl ) ); + aFormat.SetPrefix( aPrefix.getToken( 0, u'\x0001', nPrefixIdx )); + aFormat.SetStart( static_cast<sal_uInt16>(aPrefix.getToken( 0, u'\x0001', nPrefixIdx ).toInt32())); + aFormat.SetSuffix( aPostfix.getToken( 0, u'\x0001' )); + aFormat.SetIncludeUpperLevels( 0 ); + + if( !aFormat.GetCharFormat() ) + aFormat.SetCharFormat( pCFormat ); + + if( !aNumTypes.isEmpty() ) + aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ 0 ] - '0')); + + if( bRTL ) + aFormat.SetNumAdjust( SvxAdjust::Right ); + aRule.Set( nLvl, aFormat ); + } + else + { + auto const nSpaceSteps = nLvl ? nLeftTextPos / nLvl : 0; + sal_uInt16 n; + sal_Int32 nPostfixIdx{ 0 }; + for( n = 0; n <= nLvl; ++n ) + { + SwNumFormat aFormat( aRule.Get( n ) ); + + if( !n ) + aFormat.SetPrefix( aPrefix.getToken( 0, u'\x0001', nPrefixIdx )); // token 0, read only on first loop + aFormat.SetStart( static_cast<sal_uInt16>(aPrefix.getToken( 0, u'\x0001', nPrefixIdx ).toInt32() )); + aFormat.SetSuffix( aPostfix.getToken( 0, u'\x0001', nPostfixIdx )); + aFormat.SetIncludeUpperLevels( MAXLEVEL ); + if( n < aNumTypes.getLength() ) + aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ n ] - '0')); + + aFormat.SetAbsLSpace( nSpaceSteps * n + + lNumberIndent ); + + if( !aFormat.GetCharFormat() ) + aFormat.SetCharFormat( pCFormat ); + if( bRTL ) + aFormat.SetNumAdjust( SvxAdjust::Right ); + + aRule.Set( n, aFormat ); + } + + // Does it fit completely into the frame? + bool bDefStep = nFrameWidth < (nSpaceSteps * MAXLEVEL); + for( ; n < MAXLEVEL; ++n ) + { + SwNumFormat aFormat( aRule.Get( n ) ); + aFormat.SetIncludeUpperLevels( MAXLEVEL ); + if( bDefStep ) + aFormat.SetAbsLSpace( nLeftTextPos + + SwNumRule::GetNumIndent(static_cast<sal_uInt8>(n-nLvl))); + else + aFormat.SetAbsLSpace( nSpaceSteps * n + + lNumberIndent ); + aRule.Set( n, aFormat ); + } + } + } + } + else if( !m_aFlags.bAFormatByInput ) + SetColl( static_cast<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) )); + else + bChgEnum = false; + } + + if ( bChgEnum || bChgBullet ) + { + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->nNode = *m_pCurTextFrame->GetTextNodeForParaProps(); + + if( m_aFlags.bSetNumRule ) + { + if( m_aFlags.bAFormatByInput ) + { + m_aDelPam.SetMark(); + SwTextFrame const*const pNextFrame = GetNextNode(false); + assert(pNextFrame); + m_aDelPam.GetMark()->nNode = *pNextFrame->GetTextNodeForParaProps(); + m_aDelPam.GetNode(false).GetTextNode()->SetAttrListLevel( nLvl ); + } + + const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAttrListLevel(nLvl); + + // start new list + m_pDoc->SetNumRule(m_aDelPam, aRule, true, m_pEditShell->GetLayout()); + m_aDelPam.DeleteMark(); + + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + bChgEnum ? nTextStt : TextFrameIndex(0)); + } + m_aDelPam.SetMark(); + + if ( bChgBullet ) + nTextStt += TextFrameIndex(2); + + while (nTextStt < TextFrameIndex(rStr.getLength()) && IsSpace(rStr[sal_Int32(nTextStt)])) + nTextStt++; + + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTextStt); + DeleteSel( m_aDelPam ); + + if( !m_aFlags.bSetNumRule ) + { + OUString sChgStr('\t'); + if( bChgBullet ) + sChgStr = OUStringChar( m_aFlags.cBullet ) + sChgStr; + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, sChgStr ); + + SfxItemSet aSet( m_pDoc->GetAttrPool(), aTextNodeSetRange ); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + assert(&m_aDelPam.GetPoint()->nNode.GetNode() == m_pCurTextFrame->GetTextNodeForParaProps()); + if( bChgBullet ) + { + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(1)); + SetAllScriptItem( aSet, + SvxFontItem( m_aFlags.aBulletFont.GetFamilyType(), + m_aFlags.aBulletFont.GetFamilyName(), + m_aFlags.aBulletFont.GetStyleName(), + m_aFlags.aBulletFont.GetPitch(), + m_aFlags.aBulletFont.GetCharSet(), + RES_CHRATR_FONT ) ); + m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet ); + m_aDelPam.DeleteMark(); + nAutoCorrPos = TextFrameIndex(2); + aSet.ClearItem(); + } + SvxTabStopItem aTStops( RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( 0 ) ); + aSet.Put( aTStops ); + assert(&m_aDelPam.GetPoint()->nNode.GetNode() == m_pCurTextFrame->GetTextNodeForParaProps()); + m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet ); + } + } + + if( bBreak ) + { + AutoCorrect( nAutoCorrPos ); /* Offset due to Bullet + Tab */ + return; + } + + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) + && nLvl == CalcLevel(*pNextFrame)) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + const SwTextFrame *const pCurrNode = pNextFrame; + pNextFrame = GetNextNode(); + if (!pNextFrame || pCurrNode == pNextFrame) + break; + } + DeleteLeadingTrailingBlanks( false ); + AutoCorrect( nAutoCorrPos ); +} + +void SwAutoFormat::BuildNegIndent( SwTwips nSpaces ) +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_NEG_INDENT ); + // Test of contraposition (n words, divided by spaces/tabs, with same indentation in 2nd line) + + // read all succeeding paragraphs that belong to this enumeration + bool bBreak = true; + TextFrameIndex nSpacePos(0); + const sal_Int32 nTextPos = GetBigIndent( nSpacePos ); + if( m_bMoreLines ) + DelMoreLinesBlanks( true ); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || (!nTextPos && IsBlanksInString(*m_pCurTextFrame)) + || IsSentenceAtEnd(*m_pCurTextFrame); + + SetColl( static_cast<sal_uInt16>( nTextPos + ? RES_POOLCOLL_CONFRONTATION + : RES_POOLCOLL_TEXT_NEGIDENT ) ); + + if( nTextPos ) + { + const OUString& rStr = m_pCurTextFrame->GetText(); + bool bInsTab = true; + + if ('\t' == rStr[sal_Int32(nSpacePos) + 1]) // leave tab alone + { + --nSpacePos; + bInsTab = false; + } + + TextFrameIndex nSpaceStt = nSpacePos; + while (nSpaceStt && IsSpace(rStr[sal_Int32(--nSpaceStt)])) + ; + ++nSpaceStt; + + if (bInsTab && '\t' == rStr[sal_Int32(nSpaceStt)]) // leave tab alone + { + ++nSpaceStt; + bInsTab = false; + } + + m_aDelPam.DeleteMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nSpacePos); + + // delete old Spaces, etc. + if( nSpaceStt < nSpacePos ) + { + m_aDelPam.SetMark(); + *m_aDelPam.GetMark() = m_pCurTextFrame->MapViewToModelPos(nSpaceStt); + DeleteSel( m_aDelPam ); + if( bInsTab ) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString('\t') ); + } + } + } + + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + SwTextFrameInfo aFInfo( m_pCurTextFrame ); + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) && + 20 < std::abs( static_cast<long>(nSpaces - aFInfo.SetFrame( + EnsureFormatted(*pNextFrame)).GetLineStart()) ) + ) + { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + pNextFrame = GetNextNode(); + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildHeadLine( sal_uInt16 nLvl ) +{ + if( m_aFlags.bWithRedlining ) + { + OUString sText(SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ + STR_AUTOFMTREDL_SET_TMPL_HEADLINE ] ); + sText = sText.replaceAll( "$(ARG1)", OUString::number( nLvl + 1 ) ); + m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText ); + } + + SetColl( static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true ); + if( m_aFlags.bAFormatByInput ) + { + SwTextFormatColl& rNxtColl = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetNextTextFormatColl(); + + JoinPrevPara(); + + DeleteLeadingTrailingBlanks( true, false ); + const SwTextFrame *const pNextFrame = GetNextNode(false); + (void)DeleteJoinCurNextPara(pNextFrame, true); + + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->nNode = *GetNextNode(false)->GetTextNodeForParaProps(); + m_aDelPam.GetPoint()->nContent.Assign( m_aDelPam.GetContentNode(), 0 ); + m_pDoc->SetTextFormatColl( m_aDelPam, &rNxtColl ); + } + else + { + DeleteLeadingTrailingBlanks(); + AutoCorrect(); + } +} + +/// Start autocorrection for the current TextNode +void SwAutoFormat::AutoCorrect(TextFrameIndex nPos) +{ + SvxAutoCorrect* pATst = SvxAutoCorrCfg::Get().GetAutoCorrect(); + ACFlags aSvxFlags = pATst->GetFlags( ); + bool bReplaceQuote( aSvxFlags & ACFlags::ChgQuotes ); + bool bReplaceSglQuote( aSvxFlags & ACFlags::ChgSglQuotes ); + + if( m_aFlags.bAFormatByInput || + (!m_aFlags.bAutoCorrect && !bReplaceQuote && !bReplaceSglQuote && + !m_aFlags.bCapitalStartSentence && !m_aFlags.bCapitalStartWord && + !m_aFlags.bChgOrdinalNumber && !m_aFlags.bTransliterateRTL && + !m_aFlags.bChgToEnEmDash && !m_aFlags.bSetINetAttr && + !m_aFlags.bChgWeightUnderl && !m_aFlags.bAddNonBrkSpace) ) + return; + + const OUString* pText = &m_pCurTextFrame->GetText(); + if (TextFrameIndex(pText->getLength()) <= nPos) + return; + + bool bGetLanguage = m_aFlags.bChgOrdinalNumber || m_aFlags.bTransliterateRTL || + m_aFlags.bChgToEnEmDash || m_aFlags.bSetINetAttr || + m_aFlags.bCapitalStartWord || m_aFlags.bCapitalStartSentence || + m_aFlags.bAddNonBrkSpace; + + m_aDelPam.DeleteMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + + SwAutoCorrDoc aACorrDoc( *m_pEditShell, m_aDelPam ); + + SwTextFrameInfo aFInfo( nullptr ); + + TextFrameIndex nSttPos, nLastBlank = nPos; + bool bFirst = m_aFlags.bCapitalStartSentence, bFirstSent = bFirst; + sal_Unicode cChar = 0; + bool bNbspRunNext = false; + + CharClass& rAppCC = GetAppCharClass(); + + do { + while (nPos < TextFrameIndex(pText->getLength()) + && IsSpace(cChar = (*pText)[sal_Int32(nPos)])) + ++nPos; + if (nPos == TextFrameIndex(pText->getLength())) + break; // that's it + + if( ( ( bReplaceQuote && '\"' == cChar ) || + ( bReplaceSglQuote && '\'' == cChar ) ) && + (!nPos || ' ' == (*pText)[sal_Int32(nPos)-1])) + { + + // note: special case symbol fonts !!! + if( !aFInfo.GetFrame() ) + aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) ); + if( !aFInfo.IsBullet( nPos )) + { + SetRedlineText( STR_AUTOFMTREDL_TYPO ); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + bool bSetHardBlank = false; + + OUString sReplace( pATst->GetQuote( aACorrDoc, + sal_Int32(nPos), cChar, true )); + + m_aDelPam.SetMark(); + m_aDelPam.GetPoint()->nContent = m_aDelPam.GetMark()->nContent.GetIndex() + 1; + if( 2 == sReplace.getLength() && ' ' == sReplace[ 1 ]) + { + sReplace = sReplace.copy( 0, 1 ); + bSetHardBlank = true; + } + m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false ); + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + aFInfo.SetFrame( nullptr ); + } + + nPos += TextFrameIndex(sReplace.getLength() - 1); + m_aDelPam.DeleteMark(); + if( bSetHardBlank ) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) ); + ++nPos; + } + } + } + + bool bCallACorr = false; + int bBreak = 0; + if (nPos && IsSpace((*pText)[sal_Int32(nPos) - 1])) + nLastBlank = nPos; + for (nSttPos = nPos; !bBreak && nPos < TextFrameIndex(pText->getLength()); ++nPos) + { + cChar = (*pText)[sal_Int32(nPos)]; + switch (cChar) + { + case '\"': + case '\'': + if( ( cChar == '\"' && bReplaceQuote ) || ( cChar == '\'' && bReplaceSglQuote ) ) + { + // consider Symbolfonts! + if( !aFInfo.GetFrame() ) + aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) ); + if( !aFInfo.IsBullet( nPos )) + { + SetRedlineText( STR_AUTOFMTREDL_TYPO ); + bool bSetHardBlank = false; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + OUString sReplace( pATst->GetQuote( aACorrDoc, + sal_Int32(nPos), cChar, false) ); + + if( 2 == sReplace.getLength() && ' ' == sReplace[ 0 ]) + { + sReplace = sReplace.copy( 1 ); + bSetHardBlank = true; + } + + m_aDelPam.SetMark(); + m_aDelPam.GetPoint()->nContent = m_aDelPam.GetMark()->nContent.GetIndex() + 1; + m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false ); + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + aFInfo.SetFrame( nullptr ); + } + + nPos += TextFrameIndex(sReplace.getLength() - 1); + m_aDelPam.DeleteMark(); + + if( bSetHardBlank ) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) ); + ++nPos; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + } + } + } + break; + case '*': + case '_': + if( m_aFlags.bChgWeightUnderl ) + { + // consider Symbolfonts! + if( !aFInfo.GetFrame() ) + aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) ); + if( !aFInfo.IsBullet( nPos )) + { + SetRedlineText( '*' == cChar + ? STR_AUTOFMTREDL_BOLD + : STR_AUTOFMTREDL_UNDER ); + + sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + + if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos))) + { + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + aFInfo.SetFrame( nullptr ); + } + //#125102# in case of the mode RedlineFlags::ShowDelete the ** are still contained in pText + if(!(m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete)) + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()) - TextFrameIndex(1); + // Was a character deleted before starting? + if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1]) + --nSttPos; + } + } + } + break; + case '/': + if ( m_aFlags.bAddNonBrkSpace ) + { + LanguageType eLang = bGetLanguage + ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true) + : LANGUAGE_SYSTEM; + + SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE ); + if (pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext)) + --nPos; + } + break; + + case '.': + case '!': + case '?': + if( m_aFlags.bCapitalStartSentence ) + bFirstSent = true; + [[fallthrough]]; + default: + if (!(rAppCC.isLetterNumeric(*pText, sal_Int32(nPos)) + || '/' == cChar )) // '/' should not be a word separator (e.g. '1/2' needs to be handled as one word for replacement) + { + --nPos; // revert ++nPos which was decremented in for loop + ++bBreak; + } + break; + } + } + + if( nPos == nSttPos ) + { + if (++nPos == TextFrameIndex(pText->getLength())) + bCallACorr = true; + } + else + bCallACorr = true; + + if( bCallACorr ) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + SetRedlineText( STR_AUTOFMTREDL_USE_REPLACE ); + if( m_aFlags.bAutoCorrect && + aACorrDoc.ChgAutoCorrWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), *pATst, nullptr)) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()); + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + } + + continue; // do not check further + } + + LanguageType eLang = bGetLanguage + ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true) + : LANGUAGE_SYSTEM; + + if( m_aFlags.bTransliterateRTL && eLang == LANGUAGE_HUNGARIAN && + SetRedlineText( STR_AUTOFMTREDL_TRANSLITERATE_RTL ) && + aACorrDoc.TransliterateRTLWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos))) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()); + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + } + + continue; // do not check further + } + + if ( m_aFlags.bAddNonBrkSpace ) + { + SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE ); + pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); + } + + if( ( m_aFlags.bChgOrdinalNumber && + SetRedlineText( STR_AUTOFMTREDL_ORDINAL ) && + pATst->FnChgOrdinalNumber(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) || + ( m_aFlags.bChgToEnEmDash && + SetRedlineText( STR_AUTOFMTREDL_DASH ) && + pATst->FnChgToEnEmDash(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) || + ( m_aFlags.bSetINetAttr && + (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) && + SetRedlineText( STR_AUTOFMTREDL_DETECT_URL ) && + pATst->FnSetINetAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang))) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()); + } + else + { + // two capital letters at the beginning of a word? + if( m_aFlags.bCapitalStartWord ) + { + SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_WORD ); + pATst->FnCapitalStartWord(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang); + } + // capital letter at the beginning of a sentence? + if( m_aFlags.bCapitalStartSentence && bFirst ) + { + SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_SENT ); + pATst->FnCapitalStartSentence(aACorrDoc, *pText, true, sal_Int32(nSttPos), sal_Int32(nPos), eLang); + } + + bFirst = bFirstSent; + bFirstSent = false; + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->nNode; + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + } + } + } + } + while (nPos < TextFrameIndex(pText->getLength())); + ClearRedlineText(); +} + +SwAutoFormat::SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags const & rFlags, + SwNodeIndex const * pSttNd, SwNodeIndex const * pEndNd ) + : m_aFlags( rFlags ), + m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ), + m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), +1 ), + m_aEndNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfContent() ), + m_pEditShell( pEdShell ), + m_pDoc( pEdShell->GetDoc() ), + m_pCurTextNd( nullptr ), m_pCurTextFrame( nullptr ), + m_nRedlAutoFormatSeqId( 0 ) +{ + OSL_ENSURE( (pSttNd && pEndNd) || (!pSttNd && !pEndNd), + "Got no area" ); + + if( m_aFlags.bSetNumRule && !m_aFlags.bAFormatByInput ) + m_aFlags.bSetNumRule = false; + + bool bReplaceStyles = !m_aFlags.bAFormatByInput || m_aFlags.bReplaceStyles; + + const SwTextFrame * pNextFrame = nullptr; + bool bNxtEmpty = false; + bool bNxtAlpha = false; + sal_uInt16 nNxtLevel = 0; + bool bEmptyLine; + + // set area for autoformatting + if( pSttNd ) + { + m_aNdIdx = *pSttNd; + // for GoNextPara, one paragraph prior to that + sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout()); + m_aEndNdIdx = *pEndNd; + sw::GotoNextLayoutTextFrame(m_aEndNdIdx, m_pEditShell->GetLayout()); + + // check the previous TextNode + SwTextFrame const*const pPrevFrame = m_aNdIdx.GetNode().GetTextNode() + ? static_cast<SwTextFrame const*>(m_aNdIdx.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout())) + : nullptr; + bEmptyLine = !pPrevFrame + || IsEmptyLine(*pPrevFrame) + || IsNoAlphaLine(*pPrevFrame); + } + else + bEmptyLine = true; // at document beginning + + m_bEnd = false; + + // set value for percentage display + m_nEndNdIdx = m_aEndNdIdx.GetIndex(); + + if( !m_aFlags.bAFormatByInput ) + { + m_nEndNdIdx = m_aEndNdIdx.GetIndex(); + ::StartProgress( STR_STATSTR_AUTOFORMAT, m_aNdIdx.GetIndex(), + m_nEndNdIdx, + m_pDoc->GetDocShell() ); + } + + RedlineFlags eRedlMode = m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode; + if( m_aFlags.bWithRedlining ) + { + m_pDoc->SetAutoFormatRedline( true ); + eRedlMode = RedlineFlags::On | (eOldMode & RedlineFlags::ShowMask); + } + else + eRedlMode = RedlineFlags::Ignore | (eOldMode & RedlineFlags::ShowMask); + m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode ); + + // save undo state (might be turned off) + bool const bUndoState = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + + // If multiple lines, then do not merge with next paragraph + m_bMoreLines = false; + + sal_uInt16 nLastCalcHeadLvl = 0; + sal_uInt16 nLastHeadLvl = USHRT_MAX; + sal_uInt16 nLevel = 0; + sal_uInt16 nDigitLvl = 0; + + // set defaults + SwTextFrameInfo aFInfo( nullptr ); + + enum Format_Status + { + READ_NEXT_PARA, // -> ISEND, TST_EMPTY_LINE + TST_EMPTY_LINE, // -> READ_NEXT_PARA, TST_ALPHA_LINE + TST_ALPHA_LINE, // -> READ_NEXT_PARA, GET_ALL_INFO, IS_END + GET_ALL_INFO, // -> READ_NEXT_PARA, IS_ONE_LINE, TST_ENUMERIC, HAS_FMTCOLL + IS_ONE_LINE, // -> READ_NEXT_PARA, TST_ENUMERIC + TST_ENUMERIC, // -> READ_NEXT_PARA, TST_IDENT, TST_NEG_IDENT + TST_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY + TST_NEG_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY + TST_TXT_BODY, // -> READ_NEXT_PARA + HAS_FMTCOLL, // -> READ_NEXT_PARA + IS_END + } eStat; + + // This is the automat for autoformatting + eStat = READ_NEXT_PARA; + while( !m_bEnd ) + { + switch( eStat ) + { + case READ_NEXT_PARA: + { + GoNextPara(); + eStat = m_bEnd ? IS_END : TST_EMPTY_LINE; + } + break; + + case TST_EMPTY_LINE: + if (IsEmptyLine(*m_pCurTextFrame)) + { + if (m_aFlags.bDelEmptyNode && !HasObjects(*m_pCurTextFrame)) + { + bEmptyLine = true; + sal_uLong nOldCnt = m_pDoc->GetNodes().Count(); + DelEmptyLine(); + // Was there really a deletion of a node? + if( nOldCnt != m_pDoc->GetNodes().Count() ) + { + // do not skip the next paragraph + sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout()); + } + } + eStat = READ_NEXT_PARA; + } + else + eStat = TST_ALPHA_LINE; + break; + + case TST_ALPHA_LINE: + if (IsNoAlphaLine(*m_pCurTextFrame)) + { + // recognize a table definition +---+---+ + if( m_aFlags.bAFormatByInput && m_aFlags.bCreateTable && DoTable() ) + { + //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor after AutoFormat! + pEdShell->Pop(SwCursorShell::PopMode::DeleteCurrent); + *pEdShell->GetCursor() = m_aDelPam; + pEdShell->Push(); + + eStat = IS_END; + break; + } + + // Check for 3 "---" or "===". In this case, the previous paragraph should be + // underlined and the current be deleted! + if( !DoUnderline() && bReplaceStyles ) + { + SetColl( RES_POOLCOLL_STANDARD, true ); + bEmptyLine = true; + } + eStat = READ_NEXT_PARA; + } + else + eStat = GET_ALL_INFO; + break; + + case GET_ALL_INFO: + { + if (m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule()) + { + // do nothing in numbering, go to next + bEmptyLine = false; + eStat = READ_NEXT_PARA; + // delete all blanks at beginning/end and in between + //JP 29.04.98: first only "all in between" + DelMoreLinesBlanks(); + break; + } + + aFInfo.SetFrame( m_pCurTextFrame ); + + // so far: if there were templates assigned, keep these and go to next node + sal_uInt16 nPoolId = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId(); + if( IsPoolUserFormat( nPoolId ) + ? !m_aFlags.bChgUserColl + : ( RES_POOLCOLL_STANDARD != nPoolId && + ( !m_aFlags.bAFormatByInput || + (RES_POOLCOLL_TEXT_MOVE != nPoolId && + RES_POOLCOLL_TEXT != nPoolId )) )) + { + eStat = HAS_FMTCOLL; + break; + } + + // check for hard spaces or LRSpaces set by the template + if( IsPoolUserFormat( nPoolId ) || + RES_POOLCOLL_STANDARD == nPoolId ) + { + short nSz; + SvxLRSpaceItem const * pLRSpace; + if (SfxItemState::SET == m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet(). + GetItemState( RES_LR_SPACE, true, + reinterpret_cast<const SfxPoolItem**>(&pLRSpace) ) && + ( 0 != (nSz = pLRSpace->GetTextFirstLineOffset()) || + 0 != pLRSpace->GetTextLeft() ) ) + { + // exception: numbering/enumeration can have an indentation + if (IsEnumericChar(*m_pCurTextFrame)) + { + nLevel = CalcLevel(*m_pCurTextFrame, &nDigitLvl); + if( nLevel >= MAXLEVEL ) + nLevel = MAXLEVEL-1; + BuildEnum( nLevel, nDigitLvl ); + eStat = READ_NEXT_PARA; + break; + } + + // never merge (maybe only indent as exception) + m_bMoreLines = true; + + if( bReplaceStyles ) + { + // then use one of our templates + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else if( pLRSpace->GetTextLeft() ) // is indentation + BuildTextIndent(); + } + eStat = READ_NEXT_PARA; + break; + } + } + + nLevel = CalcLevel( *m_pCurTextFrame, &nDigitLvl ); + m_bMoreLines = !IsOneLine(*m_pCurTextFrame); + // note: every use of pNextFrame in following states, until the + // next READ_NEXT_PARA, relies on this update + pNextFrame = GetNextNode(); + if (pNextFrame) + { + bNxtEmpty = IsEmptyLine(*pNextFrame); + bNxtAlpha = IsNoAlphaLine(*pNextFrame); + nNxtLevel = CalcLevel(*pNextFrame); + + if (!bEmptyLine && HasBreakAttr(*m_pCurTextFrame)) + bEmptyLine = true; + if (!bNxtEmpty && HasBreakAttr(*pNextFrame)) + bNxtEmpty = true; + + } + else + { + bNxtEmpty = false; + bNxtAlpha = false; + nNxtLevel = 0; + } + eStat = !m_bMoreLines ? IS_ONE_LINE : TST_ENUMERIC; + } + break; + + case IS_ONE_LINE: + { + eStat = TST_ENUMERIC; + if( !bReplaceStyles ) + break; + + const OUString sClrStr( DelLeadingBlanks(m_pCurTextFrame->GetText()) ); + + if( sClrStr.isEmpty() ) + { + bEmptyLine = true; + eStat = READ_NEXT_PARA; + break; // read next paragraph + } + + // check if headline + if (!bEmptyLine || !IsFirstCharCapital(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame)) + break; + + bEmptyLine = false; + const OUString sEndClrStr( DelTrailingBlanks(sClrStr) ); + const sal_Unicode cLast = sEndClrStr[sEndClrStr.getLength() - 1]; + + // not, then check if headline + if( ':' == cLast ) + { + BuildHeadLine( 2 ); + eStat = READ_NEXT_PARA; + break; + } + else if( 256 <= cLast || !strchr( ",.;", cLast ) ) + { + if( bNxtEmpty || bNxtAlpha + || (pNextFrame && IsEnumericChar(*pNextFrame))) + { + + // one level below? + if( nLevel >= MAXLEVEL ) + nLevel = MAXLEVEL-1; + + if( USHRT_MAX == nLastHeadLvl ) + nLastHeadLvl = 0; + else if( nLastCalcHeadLvl < nLevel ) + { + if( nLastHeadLvl+1 < MAXLEVEL ) + ++nLastHeadLvl; + } + // one level above? + else if( nLastCalcHeadLvl > nLevel ) + { + if( nLastHeadLvl ) + --nLastHeadLvl; + } + nLastCalcHeadLvl = nLevel; + + if( m_aFlags.bAFormatByInput ) + BuildHeadLine( nLevel ); + else + BuildHeadLine( nLastHeadLvl ); + eStat = READ_NEXT_PARA; + break; + } + } + } + break; + + case TST_ENUMERIC: + { + bEmptyLine = false; + if (IsEnumericChar(*m_pCurTextFrame)) + { + if( nLevel >= MAXLEVEL ) + nLevel = MAXLEVEL-1; + BuildEnum( nLevel, nDigitLvl ); + eStat = READ_NEXT_PARA; + } + else if( bReplaceStyles ) + eStat = nLevel ? TST_IDENT : TST_NEG_IDENT; + else + eStat = READ_NEXT_PARA; + } + break; + + case TST_IDENT: + // Spaces at the beginning, check again for indentation + if( m_bMoreLines && nLevel ) + { + SwTwips nSz = aFInfo.GetFirstIndent(); + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else // is indentation + BuildTextIndent(); + eStat = READ_NEXT_PARA; + } + else if (nLevel && pNextFrame && + !bNxtEmpty && !bNxtAlpha && !nNxtLevel && + !IsEnumericChar(*pNextFrame)) + { + // is an indentation + BuildIndent(); + eStat = READ_NEXT_PARA; + } + else + eStat = TST_TXT_BODY; + break; + + case TST_NEG_IDENT: + // no spaces at the beginning, check again for negative indentation + { + if( m_bMoreLines && !nLevel ) + { + SwTwips nSz = aFInfo.GetFirstIndent(); + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else // is _no_ indentation + BuildText(); + eStat = READ_NEXT_PARA; + } + else if (!nLevel && pNextFrame && + !bNxtEmpty && !bNxtAlpha && nNxtLevel && + !IsEnumericChar(*pNextFrame)) + { + // is a negative indentation + BuildNegIndent( aFInfo.GetLineStart() ); + eStat = READ_NEXT_PARA; + } + else + eStat = TST_TXT_BODY; + } + break; + + case TST_TXT_BODY: + { + if( m_bMoreLines ) + { + SwTwips nSz = aFInfo.GetFirstIndent(); + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else if( nLevel ) // is indentation + BuildTextIndent(); + else + BuildText(); + } + else if( nLevel ) + BuildTextIndent(); + else + BuildText(); + eStat = READ_NEXT_PARA; + } + break; + + case HAS_FMTCOLL: + { + // so far: if there were templates assigned, keep these and go to next node + bEmptyLine = false; + eStat = READ_NEXT_PARA; + // delete all blanks at beginning/end and in between + //JP 29.04.98: first only "all in between" + DelMoreLinesBlanks(); + + // handle hard attributes + if (m_pCurTextFrame->GetTextNodeForParaProps()->HasSwAttrSet()) + { + short nSz; + SvxLRSpaceItem const * pLRSpace; + if( bReplaceStyles && + SfxItemState::SET == m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet(). + GetItemState( RES_LR_SPACE, false, + reinterpret_cast<const SfxPoolItem**>(&pLRSpace) ) && + ( 0 != (nSz = pLRSpace->GetTextFirstLineOffset()) || + 0 != pLRSpace->GetTextLeft() ) ) + { + // then use one of our templates + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + { + BuildNegIndent( aFInfo.GetLineStart() ); + } + else if( pLRSpace->GetTextLeft() ) // is indentation + BuildTextIndent(); + else + BuildText(); + } + } + } + break; + + case IS_END: + m_bEnd = true; + break; + } + } + + if( m_aFlags.bWithRedlining ) + m_pDoc->SetAutoFormatRedline( false ); + m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode ); + + // restore undo (in case it has been changed) + m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoState); + + // disable display of percentage again + if( !m_aFlags.bAFormatByInput ) + ::EndProgress( m_pDoc->GetDocShell() ); +} + +void SwEditShell::AutoFormat( const SvxSwAutoFormatFlags* pAFlags ) +{ + std::unique_ptr<SwWait> pWait; + + SET_CURR_SHELL( this ); + StartAllAction(); + StartUndo( SwUndoId::AUTOFORMAT ); + + SvxSwAutoFormatFlags aAFFlags; // use default values or add params? + if( pAFlags ) + { + aAFFlags = *pAFlags; + if( !aAFFlags.bAFormatByInput ) + pWait.reset(new SwWait( *GetDoc()->GetDocShell(), true )); + } + + SwPaM* pCursor = GetCursor(); + // There are more than one or a selection is open + if( pCursor->GetNext() != pCursor || pCursor->HasMark() ) + { + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + { + SwAutoFormat aFormat( this, aAFFlags, &(rPaM.Start()->nNode), + &(rPaM.End()->nNode) ); + } + } + } + else + { + SwAutoFormat aFormat( this, aAFFlags ); + } + + EndUndo( SwUndoId::AUTOFORMAT ); + EndAllAction(); +} + +void SwEditShell::AutoFormatBySplitNode() +{ + SET_CURR_SHELL( this ); + SwPaM* pCursor = GetCursor(); + if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) ) + return; + + StartAllAction(); + StartUndo( SwUndoId::AUTOFORMAT ); + + bool bRange = false; + pCursor->SetMark(); + SwIndex* pContent = &pCursor->GetMark()->nContent; + if( pContent->GetIndex() ) + { + *pContent = 0; + bRange = true; + } + else + { + // then go one node backwards + SwNodeIndex aNdIdx(pCursor->GetMark()->nNode); + sw::GotoPrevLayoutTextFrame(aNdIdx, GetLayout()); + SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode(); + if (pTextNd && !pTextNd->GetText().isEmpty()) + { + pContent->Assign( pTextNd, 0 ); + pCursor->GetMark()->nNode = aNdIdx; + bRange = true; + } + } + + if( bRange ) + { + Push(); // save cursor + + SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far + + SwAutoFormat aFormat( this, aAFFlags, &pCursor->GetMark()->nNode, + &pCursor->GetPoint()->nNode ); + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + if( pACorr && !pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord | + ACFlags::AddNonBrkSpace | ACFlags::ChgOrdinalNumber | ACFlags::TransliterateRTL | + ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | ACFlags::Autocorrect )) + pACorr = nullptr; + + if( pACorr ) + AutoCorrect( *pACorr,false, u'\0' ); + + //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor! + Pop(PopMode::DeleteCurrent); + pCursor = GetCursor(); + } + pCursor->DeleteMark(); + pCursor->Move( fnMoveForward, GoInNode ); + + EndUndo( SwUndoId::AUTOFORMAT ); + EndAllAction(); + +} + +SvxSwAutoFormatFlags* SwEditShell::GetAutoFormatFlags() +{ + if (!s_pAutoFormatFlags) + s_pAutoFormatFlags = new SvxSwAutoFormatFlags; + + return s_pAutoFormatFlags; +} + +void SwEditShell::SetAutoFormatFlags(SvxSwAutoFormatFlags const * pFlags) +{ + SvxSwAutoFormatFlags* pEditFlags = GetAutoFormatFlags(); + + pEditFlags->bSetNumRule = pFlags->bSetNumRule; + pEditFlags->bChgEnumNum = pFlags->bChgEnumNum; + pEditFlags->bSetBorder = pFlags->bSetBorder; + pEditFlags->bCreateTable = pFlags->bCreateTable; + pEditFlags->bReplaceStyles = pFlags->bReplaceStyles; + pEditFlags->bAFormatByInpDelSpacesAtSttEnd = + pFlags->bAFormatByInpDelSpacesAtSttEnd; + pEditFlags->bAFormatByInpDelSpacesBetweenLines = + pFlags->bAFormatByInpDelSpacesBetweenLines; + + //JP 15.12.98: copy BulletChar and Font into "normal" ones + // because AutoFormat can only work with the latter! + pEditFlags->cBullet = pFlags->cByInputBullet; + pEditFlags->aBulletFont = pFlags->aByInputBulletFont; + pEditFlags->cByInputBullet = pFlags->cByInputBullet; + pEditFlags->aByInputBulletFont = pFlags->aByInputBulletFont; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edatmisc.cxx b/sw/source/core/edit/edatmisc.cxx new file mode 100644 index 000000000..06546d81d --- /dev/null +++ b/sw/source/core/edit/edatmisc.cxx @@ -0,0 +1,190 @@ +/* -*- 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 <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> + +/* + * hard formatting (Attribute) + */ + +void SwEditShell::ResetAttr( const std::set<sal_uInt16> &attrs, SwPaM* pPaM ) +{ + SET_CURR_SHELL( this ); + SwPaM* pCursor = pPaM ? pPaM : GetCursor( ); + + StartAllAction(); + bool bUndoGroup = pCursor->GetNext() != pCursor; + if( bUndoGroup ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::RESETATTR, nullptr); + } + + for(const SwPaM& rCurrentCursor : pCursor->GetRingContainer()) + GetDoc()->ResetAttrs(rCurrentCursor, true, attrs, true, GetLayout()); + + if( bUndoGroup ) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::RESETATTR, nullptr); + } + CallChgLnk(); + EndAllAction(); +} + +void SwEditShell::GCAttr() +{ + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if ( !rPaM.HasMark() ) + { + SwTextNode *const pTextNode = + rPaM.GetPoint()->nNode.GetNode().GetTextNode(); + if (pTextNode) + { + pTextNode->GCAttr(); + } + } + else + { + const SwNodeIndex& rEnd = rPaM.End()->nNode; + SwNodeIndex aIdx( rPaM.Start()->nNode ); + SwNode* pNd = &aIdx.GetNode(); + do { + if( pNd->IsTextNode() ) + static_cast<SwTextNode*>(pNd)->GCAttr(); + } + while( nullptr != ( pNd = GetDoc()->GetNodes().GoNext( &aIdx )) && + aIdx <= rEnd ); + } + } +} + +/// Set the attribute as new default attribute in the document. +void SwEditShell::SetDefault( const SfxPoolItem& rFormatHint ) +{ + // 7502: Action-Parenthesis + StartAllAction(); + GetDoc()->SetDefault( rFormatHint ); + EndAllAction(); +} + +/// request the default attribute in this document. +const SfxPoolItem& SwEditShell::GetDefault( sal_uInt16 nFormatHint ) const +{ + return GetDoc()->GetDefault( nFormatHint ); +} + +// tdf#122893 turn off ShowChanges mode to apply paragraph formatting permanently with redlining +// ie. in all directly preceding deleted paragraphs at the actual cursor positions +static void lcl_disableShowChangesIfNeeded( SwDoc *const pDoc, const SwNode& rNode, RedlineFlags &eRedlMode ) +{ + if ( IDocumentRedlineAccess::IsShowChanges(eRedlMode) && + // is there redlining at beginning of the position (possible redline block before the modified node) + pDoc->getIDocumentRedlineAccess().GetRedlinePos( rNode, RedlineType::Any ) < + pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ) + { + eRedlMode = RedlineFlags::ShowInsert | RedlineFlags::Ignore; + pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode ); + } +} + +void SwEditShell::SetAttrItem( const SfxPoolItem& rHint, SetAttrMode nFlags, const bool bParagraphSetting ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + RedlineFlags eRedlMode = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode; + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) // Ring of Cursors + { + bool bIsTableMode = IsTableMode(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() && ( bIsTableMode || + *rPaM.GetPoint() != *rPaM.GetMark() )) + { + if (bParagraphSetting) + lcl_disableShowChangesIfNeeded( GetDoc(), (*rPaM.Start()).nNode.GetNode(), eRedlMode); + + GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, rHint, nFlags, GetLayout()); + } + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr); + } + else + { + if( !HasSelection() ) + UpdateAttr(); + + if (bParagraphSetting) + lcl_disableShowChangesIfNeeded( GetDoc(), (*pCursor->Start()).nNode.GetNode(), eRedlMode); + + GetDoc()->getIDocumentContentOperations().InsertPoolItem(*pCursor, rHint, nFlags, GetLayout()); + } + EndAllAction(); + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode ); +} + +void SwEditShell::SetAttrSet( const SfxItemSet& rSet, SetAttrMode nFlags, SwPaM* pPaM, const bool bParagraphSetting ) +{ + SET_CURR_SHELL( this ); + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + StartAllAction(); + RedlineFlags eRedlMode = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode; + if( pCursor->GetNext() != pCursor ) // Ring of Cursors + { + bool bIsTableMode = IsTableMode(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr); + + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + if( rTmpCursor.HasMark() && ( bIsTableMode || + *rTmpCursor.GetPoint() != *rTmpCursor.GetMark() )) + { + if (bParagraphSetting) + lcl_disableShowChangesIfNeeded( GetDoc(), (*rTmpCursor.Start()).nNode.GetNode(), eRedlMode); + + GetDoc()->getIDocumentContentOperations().InsertItemSet(rTmpCursor, rSet, nFlags, GetLayout()); + } + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr); + } + else + { + if( !HasSelection() ) + UpdateAttr(); + + if (bParagraphSetting) + lcl_disableShowChangesIfNeeded( GetDoc(), (*pCursor->Start()).nNode.GetNode(), eRedlMode); + + GetDoc()->getIDocumentContentOperations().InsertItemSet(*pCursor, rSet, nFlags, GetLayout()); + } + EndAllAction(); + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edattr.cxx b/sw/source/core/edit/edattr.cxx new file mode 100644 index 000000000..20abffc64 --- /dev/null +++ b/sw/source/core/edit/edattr.cxx @@ -0,0 +1,853 @@ +/* -*- 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 <memory> +#include <hintids.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/lrspitem.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <txatbase.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <editsh.hxx> +#include <edimp.hxx> +#include <doc.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> +#include <ftnidx.hxx> +#include <expfld.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <breakit.hxx> +#include <fmtfld.hxx> +#include <txtfrm.hxx> +#include <scriptinfo.hxx> +#include <svl/itemiter.hxx> +#include <svl/languageoptions.hxx> +#include <charfmt.hxx> +#include <numrule.hxx> + +/* + * hard Formatting (Attributes) + */ + +// if selection is bigger as max nodes or more than max selections +// => no attributes +static sal_uInt16 getMaxLookup() +{ + return 10000; +} + +bool SwEditShell::GetPaMAttr( SwPaM* pPaM, SfxItemSet& rSet, + const bool bMergeIndentValuesOfNumRule ) const +{ + // ??? pPaM can be different from the Cursor ??? + if( GetCursorCnt() > getMaxLookup() ) + { + rSet.InvalidateAllItems(); + return false; + } + + SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() ); + SfxItemSet *pSet = &rSet; + + for(SwPaM& rCurrentPaM : pPaM->GetRingContainer()) + { + // #i27615# if the cursor is in front of the numbering label + // the attributes to get are those from the numbering format. + if (rCurrentPaM.IsInFrontOfLabel()) + { + SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), + rCurrentPaM.GetPoint()->nNode); + + if (pTextNd) + { + SwNumRule * pNumRule = pTextNd->GetNumRule(); + + if (pNumRule) + { + int nListLevel = pTextNd->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const OUString & aCharFormatName = + pNumRule->Get(static_cast<sal_uInt16>(nListLevel)).GetCharFormatName(); + SwCharFormat * pCharFormat = + GetDoc()->FindCharFormatByName(aCharFormatName); + + if (pCharFormat) + rSet.Put(pCharFormat->GetAttrSet()); + } + } + + continue; + } + + sal_uLong nSttNd = rCurrentPaM.GetMark()->nNode.GetIndex(), + nEndNd = rCurrentPaM.GetPoint()->nNode.GetIndex(); + sal_Int32 nSttCnt = rCurrentPaM.GetMark()->nContent.GetIndex(); + sal_Int32 nEndCnt = rCurrentPaM.GetPoint()->nContent.GetIndex(); + + if( nSttNd > nEndNd || ( nSttNd == nEndNd && nSttCnt > nEndCnt )) + { + std::swap(nSttNd, nEndNd); + std::swap(nSttCnt, nEndCnt); + } + + if( nEndNd - nSttNd >= getMaxLookup() ) + { + rSet.ClearItem(); + rSet.InvalidateAllItems(); + return false; + } + + // at first node the node enter his values into the GetSet (Initial) + // all additional nodes are additional merged to GetSet + for( sal_uLong n = nSttNd; n <= nEndNd; ++n ) + { + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + { + const sal_Int32 nStt = (n == nSttNd) ? nSttCnt : 0; + const sal_Int32 nEnd = (n == nEndNd) + ? nEndCnt + : pNd->GetTextNode()->GetText().getLength(); + + static_cast<SwTextNode*>(pNd)->GetParaAttr(*pSet, nStt, nEnd, + false, true, + bMergeIndentValuesOfNumRule, + GetLayout()); + } + break; + case SwNodeType::Grf: + case SwNodeType::Ole: + static_cast<SwContentNode*>(pNd)->GetAttr( *pSet ); + break; + + default: + pNd = nullptr; + } + + if( pNd ) + { + if( pSet != &rSet ) + { + if (!GetLayout()->IsHideRedlines() + || pNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + { + rSet.MergeValues( aSet ); + } + } + + if( aSet.Count() ) + aSet.ClearItem(); + } + pSet = &aSet; + } + + } + + return true; +} + +bool SwEditShell::GetCurAttr( SfxItemSet& rSet, + const bool bMergeIndentValuesOfNumRule ) const +{ + return GetPaMAttr( GetCursor(), rSet, bMergeIndentValuesOfNumRule ); +} + +void SwEditShell::GetCurParAttr( SfxItemSet& rSet) const +{ + GetPaMParAttr( GetCursor(), rSet ); +} + +bool SwEditShell::GetPaMParAttr( SwPaM* pPaM, SfxItemSet& rSet ) const +{ + // number of nodes the function has explored so far + sal_uInt16 numberOfLookup = 0; + + SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() ); + SfxItemSet* pSet = &rSet; + + for(SwPaM& rCurrentPaM : pPaM->GetRingContainer()) + { // for all the point and mark (selections) + + // get the start and the end node of the current selection + sal_uLong nSttNd = rCurrentPaM.GetMark()->nNode.GetIndex(), + nEndNd = rCurrentPaM.GetPoint()->nNode.GetIndex(); + + // reverse start and end if there number aren't sorted correctly + if( nSttNd > nEndNd ) + std::swap(nSttNd, nEndNd); + + // for all the nodes in the current selection + // get the node (paragraph) attributes + // and merge them in rSet + for( sal_uLong n = nSttNd; n <= nEndNd; ++n ) + { + // get the node + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + + if (GetLayout()->IsHideRedlines() + && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + continue; + } + + if( pNd->IsTextNode() ) + { + // get the node (paragraph) attributes + sw::GetAttrMerged(*pSet, *pNd->GetTextNode(), GetLayout()); + + if( pSet != &rSet && aSet.Count() ) + { + rSet.MergeValues( aSet ); + aSet.ClearItem(); + } + + pSet = &aSet; + } + + ++numberOfLookup; + + // if the maximum number of node that can be inspected has been reached + if (numberOfLookup >= getMaxLookup()) + return false; + } + } + + return true; +} + +SwTextFormatColl* SwEditShell::GetCurTextFormatColl( ) const +{ + return GetPaMTextFormatColl( GetCursor() ); +} + +SwTextFormatColl* SwEditShell::GetPaMTextFormatColl( SwPaM* pPaM ) const +{ + // number of nodes the function have explored so far + sal_uInt16 numberOfLookup = 0; + + for(SwPaM& rCurrentPaM : pPaM->GetRingContainer()) + { // for all the point and mark (selections) + + // get the start and the end node of the current selection + sal_uLong nSttNd = rCurrentPaM.GetMark()->nNode.GetIndex(), + nEndNd = rCurrentPaM.GetPoint()->nNode.GetIndex(); + + // reverse start and end if they aren't sorted correctly + if( nSttNd > nEndNd ) + std::swap(nSttNd, nEndNd); + + // for all the nodes in the current Point and Mark + for( sal_uLong n = nSttNd; n <= nEndNd; ++n ) + { + // get the node + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + + ++numberOfLookup; + + // if the maximum number of node that can be inspected has been reached + if (numberOfLookup >= getMaxLookup()) + return nullptr; + + if( pNd->IsTextNode() ) + { + SwTextNode *const pTextNode(sw::GetParaPropsNode(*GetLayout(), SwNodeIndex(*pNd))); + // if it's a text node get its named paragraph format + SwTextFormatColl *const pFormat = pTextNode->GetTextColl(); + + // if the paragraph format exist stop here and return it + if( pFormat != nullptr ) + return pFormat; + } + } + } + + // if none of the selected node contain a named paragraph format + return nullptr; +} + +std::vector<std::pair< const SfxPoolItem*, std::unique_ptr<SwPaM> >> SwEditShell::GetItemWithPaM( sal_uInt16 nWhich ) +{ + assert(isCHRATR(nWhich)); // sw_redlinehide: only thing that works + std::vector<std::pair< const SfxPoolItem*, std::unique_ptr<SwPaM> >> vItem; + for(SwPaM& rCurrentPaM : GetCursor()->GetRingContainer()) + { // for all the point and mark (selections) + + // get the start and the end node of the current selection + sal_uLong nSttNd = rCurrentPaM.Start()->nNode.GetIndex(), + nEndNd = rCurrentPaM.End()->nNode.GetIndex(); + sal_Int32 nSttCnt = rCurrentPaM.Start()->nContent.GetIndex(); + sal_Int32 nEndCnt = rCurrentPaM.End()->nContent.GetIndex(); + + SwPaM* pNewPaM = nullptr; + const SfxPoolItem* pItem = nullptr; + + // for all the nodes in the current selection + for( sal_uLong n = nSttNd; n <= nEndNd; ++n ) + { + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + if( pNd->IsTextNode() ) + { + SwTextNode* pTextNd = static_cast< SwTextNode* >( pNd ); + const sal_Int32 nStt = (n == nSttNd) ? nSttCnt : 0; + const sal_Int32 nEnd = (n == nEndNd) + ? nEndCnt : pTextNd->GetText().getLength(); + SwTextFrame const* pFrame; + const SwScriptInfo *const pScriptInfo = + SwScriptInfo::GetScriptInfo(*pTextNd, &pFrame); + TextFrameIndex const iStt(pScriptInfo + ? pFrame->MapModelToView(pTextNd, nStt) + : TextFrameIndex(-1/*invalid, do not use*/)); + sal_uInt8 nScript = pScriptInfo + ? pScriptInfo->ScriptType(iStt) + : css::i18n::ScriptType::WEAK; + nWhich = GetWhichOfScript( nWhich, nScript ); + + // item from attribute set + if( pTextNd->HasSwAttrSet() ) + { + pNewPaM = new SwPaM(*pNd, nStt, *pNd, nEnd); + pItem = pTextNd->GetSwAttrSet().GetItem( nWhich ); + vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) ); + } + + if( !pTextNd->HasHints() ) + continue; + + // items with limited range + const size_t nSize = pTextNd->GetpSwpHints()->Count(); + for( size_t m = 0; m < nSize; m++ ) + { + const SwTextAttr* pHt = pTextNd->GetpSwpHints()->Get(m); + if( pHt->Which() == RES_TXTATR_AUTOFMT || + pHt->Which() == RES_TXTATR_CHARFMT || + pHt->Which() == RES_TXTATR_INETFMT ) + { + const sal_Int32 nAttrStart = pHt->GetStart(); + const sal_Int32* pAttrEnd = pHt->End(); + + // Ignore items not in selection + if( nAttrStart > nEnd ) + break; + if( *pAttrEnd <= nStt ) + continue; + + nScript = pScriptInfo + ? pScriptInfo->ScriptType(iStt) + : css::i18n::ScriptType::WEAK; + nWhich = GetWhichOfScript( nWhich, nScript ); + const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() ); + if( pAutoSet ) + { + SfxItemIter aItemIter( *pAutoSet ); + pItem = aItemIter.GetCurItem(); + while( pItem ) + { + if( pItem->Which() == nWhich ) + { + sal_Int32 nStart = 0, nStop = 0; + if( nAttrStart < nStt ) // Attribute starts before selection + nStart = nStt; + else + nStart = nAttrStart; + if( *pAttrEnd > nEnd ) // Attribute ends after selection + nStop = nEnd; + else + nStop = *pAttrEnd; + pNewPaM = new SwPaM(*pNd, nStart, *pNd, nStop); + vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) ); + break; + } + pItem = aItemIter.NextItem(); + } + // default item + if( !pItem && !pTextNd->HasSwAttrSet() ) + { + pNewPaM = new SwPaM(*pNd, nStt, *pNd, nEnd); + pItem = pAutoSet->GetPool()->GetPoolDefaultItem( nWhich ); + vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) ); + } + } + } + } + } + } + } + return vItem; +} + +bool SwEditShell::GetCurFootnote( SwFormatFootnote* pFillFootnote ) +{ + // The cursor must be positioned on the current footnotes anchor: + SwPaM* pCursor = GetCursor(); + SwTextNode* pTextNd = pCursor->GetNode().GetTextNode(); + if( !pTextNd ) + return false; + + SwTextAttr *const pFootnote = pTextNd->GetTextAttrForCharAt( + pCursor->GetPoint()->nContent.GetIndex(), RES_TXTATR_FTN); + if( pFootnote && pFillFootnote ) + { + // Transfer data from the attribute + const SwFormatFootnote &rFootnote = static_cast<SwTextFootnote*>(pFootnote)->GetFootnote(); + pFillFootnote->SetNumber( rFootnote ); + pFillFootnote->SetEndNote( rFootnote.IsEndNote() ); + } + return nullptr != pFootnote; +} + +bool SwEditShell::SetCurFootnote( const SwFormatFootnote& rFillFootnote ) +{ + bool bChgd = false; + StartAllAction(); + + for(const SwPaM& rCursor : GetCursor()->GetRingContainer()) + { + bChgd |= + mxDoc->SetCurFootnote(rCursor, rFillFootnote.GetNumStr(), rFillFootnote.IsEndNote()); + + } + + EndAllAction(); + return bChgd; +} + +bool SwEditShell::HasFootnotes( bool bEndNotes ) const +{ + const SwFootnoteIdxs &rIdxs = mxDoc->GetFootnoteIdxs(); + for ( auto pIdx : rIdxs ) + { + const SwFormatFootnote &rFootnote = pIdx->GetFootnote(); + if ( bEndNotes == rFootnote.IsEndNote() ) + return true; + } + return false; +} + +/// Give a List of all footnotes and their beginning texts +size_t SwEditShell::GetSeqFootnoteList( SwSeqFieldList& rList, bool bEndNotes ) +{ + rList.Clear(); + + IDocumentRedlineAccess & rIDRA(mxDoc->getIDocumentRedlineAccess()); + + const size_t nFootnoteCnt = mxDoc->GetFootnoteIdxs().size(); + SwTextFootnote* pTextFootnote; + for( size_t n = 0; n < nFootnoteCnt; ++n ) + { + pTextFootnote = mxDoc->GetFootnoteIdxs()[ n ]; + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() != bEndNotes ) + continue; + + SwNodeIndex* pIdx = pTextFootnote->GetStartNode(); + if( pIdx ) + { + SwNodeIndex aIdx( *pIdx, 1 ); + SwTextNode* pTextNd = aIdx.GetNode().GetTextNode(); + if( !pTextNd ) + pTextNd = static_cast<SwTextNode*>(mxDoc->GetNodes().GoNext( &aIdx )); + + if( pTextNd ) + { + if (GetLayout()->IsHideRedlines() + && sw::IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + continue; + } + + OUString sText(rFootnote.GetViewNumStr(*mxDoc, GetLayout())); + if( !sText.isEmpty() ) + sText += " "; + sText += pTextNd->GetExpandText(GetLayout()); + + SeqFieldLstElem aNew( sText, pTextFootnote->GetSeqRefNo() ); + while( rList.InsertSort( aNew ) ) + aNew.sDlgEntry += " "; + } + } + } + + return rList.Count(); +} + +/// Adjust left margin via object bar (similar to adjustment of numerations). +bool SwEditShell::IsMoveLeftMargin( bool bRight, bool bModulus ) const +{ + bool bRet = true; + + const SvxTabStopItem& rTabItem = GetDoc()->GetDefault( RES_PARATR_TABSTOP ); + sal_uInt16 nDefDist = static_cast<sal_uInt16>(rTabItem.Count() ? rTabItem[0].GetTabPos() : 1134); + if( !nDefDist ) + return false; + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + sal_uLong nSttNd = rPaM.GetMark()->nNode.GetIndex(), + nEndNd = rPaM.GetPoint()->nNode.GetIndex(); + + if( nSttNd > nEndNd ) + std::swap(nSttNd, nEndNd); + + SwContentNode* pCNd; + for( sal_uLong n = nSttNd; bRet && n <= nEndNd; ++n ) + if( nullptr != ( pCNd = GetDoc()->GetNodes()[ n ]->GetTextNode() )) + { + pCNd = sw::GetParaPropsNode(*GetLayout(), *pCNd); + const SvxLRSpaceItem& rLS = static_cast<const SvxLRSpaceItem&>( + pCNd->GetAttr( RES_LR_SPACE )); + if( bRight ) + { + long nNext = rLS.GetTextLeft() + nDefDist; + if( bModulus ) + nNext = ( nNext / nDefDist ) * nDefDist; + SwFrame* pFrame = pCNd->getLayoutFrame( GetLayout() ); + if ( pFrame ) + { + const sal_uInt16 nFrameWidth = static_cast<sal_uInt16>( pFrame->IsVertical() ? + pFrame->getFrameArea().Height() : + pFrame->getFrameArea().Width() ); + bRet = nFrameWidth > ( nNext + MM50 ); + } + else + bRet = false; + } + } + + if( !bRet ) + break; + + } + return bRet; +} + +void SwEditShell::MoveLeftMargin( bool bRight, bool bModulus ) +{ + StartAllAction(); + StartUndo( SwUndoId::START ); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) // Multiple selection ? + { + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + GetDoc()->MoveLeftMargin( aRangeArr.SetPam( n, aPam ), + bRight, bModulus, GetLayout() ); + } + else + GetDoc()->MoveLeftMargin( *pCursor, bRight, bModulus, GetLayout() ); + + EndUndo( SwUndoId::END ); + EndAllAction(); +} + +static SvtScriptType lcl_SetScriptFlags( sal_uInt16 nType ) +{ + switch( nType ) + { + case css::i18n::ScriptType::LATIN: + return SvtScriptType::LATIN; + case css::i18n::ScriptType::ASIAN: + return SvtScriptType::ASIAN; + case css::i18n::ScriptType::COMPLEX: + return SvtScriptType::COMPLEX; + default: + return SvtScriptType::NONE; + } +} + +static bool lcl_IsNoEndTextAttrAtPos(SwRootFrame const& rLayout, + const SwTextNode& rTNd, sal_Int32 const nPos, + SvtScriptType &rScrpt, bool bInSelection, bool bNum ) +{ + bool bRet = false; + OUString sExp; + + // consider numbering + if ( bNum ) + { + bRet = false; + SwTextNode const*const pPropsNode(sw::GetParaPropsNode(rLayout, rTNd)); + if (pPropsNode->IsInList()) + { + OSL_ENSURE( pPropsNode->GetNumRule(), + "<lcl_IsNoEndTextAttrAtPos(..)> - no list style found at text node. Serious defect." ); + const SwNumRule* pNumRule = pPropsNode->GetNumRule(); + if(pNumRule) + { + int nListLevel = pPropsNode->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const SwNumFormat &rNumFormat = pNumRule->Get( static_cast<sal_uInt16>(nListLevel) ); + if( SVX_NUM_BITMAP != rNumFormat.GetNumberingType() ) + { + if ( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() ) + sExp = OUString(rNumFormat.GetBulletChar()); + else + sExp = pPropsNode->GetNumString(true, MAXLEVEL, &rLayout); + } + } + } + } + + // and fields + if (nPos < rTNd.GetText().getLength() && CH_TXTATR_BREAKWORD == rTNd.GetText()[nPos]) + { + const SwTextAttr* const pAttr = rTNd.GetTextAttrForCharAt( nPos ); + if (pAttr) + { + bRet = true; // all other than fields can be + // defined as weak-script ? + if ( RES_TXTATR_FIELD == pAttr->Which() ) + { + const SwField* const pField = pAttr->GetFormatField().GetField(); + if (pField) + { + sExp += pField->ExpandField(true, &rLayout); + } + } + } + } + + const sal_Int32 nEnd = sExp.getLength(); + if ( nEnd ) + { + if( bInSelection ) + { + sal_uInt16 nScript; + for( sal_Int32 n = 0; n < nEnd; + n = g_pBreakIt->GetBreakIter()->endOfScript( sExp, n, nScript )) + { + nScript = g_pBreakIt->GetBreakIter()->getScriptType( sExp, n ); + rScrpt |= lcl_SetScriptFlags( nScript ); + } + } + else + rScrpt |= lcl_SetScriptFlags( g_pBreakIt->GetBreakIter()-> + getScriptType( sExp, nEnd-1 )); + } + + return bRet; +} + +/// returns the script type of the selection +SvtScriptType SwEditShell::GetScriptType() const +{ + SvtScriptType nRet = SvtScriptType::NONE; + + { + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + const SwPosition *pStt = rPaM.Start(), + *pEnd = pStt == rPaM.GetMark() + ? rPaM.GetPoint() + : rPaM.GetMark(); + if( pStt == pEnd || *pStt == *pEnd ) + { + const SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + // try to get SwScriptInfo + SwTextFrame const* pFrame; + const SwScriptInfo *const pScriptInfo = + SwScriptInfo::GetScriptInfo(*pTNd, &pFrame); + + sal_Int32 nPos = pStt->nContent.GetIndex(); + //Task 90448: we need the scripttype of the previous + // position, if no selection exist! + if( nPos ) + { + SwIndex aIdx( pStt->nContent ); + if( pTNd->GoPrevious( &aIdx, CRSR_SKIP_CHARS ) ) + nPos = aIdx.GetIndex(); + } + + sal_uInt16 nScript; + + if (!pTNd->GetText().isEmpty()) + { + nScript = pScriptInfo + ? pScriptInfo->ScriptType(pFrame->MapModelToView(pTNd, nPos)) + : g_pBreakIt->GetBreakIter()->getScriptType( pTNd->GetText(), nPos ); + } + else + nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + + if (!lcl_IsNoEndTextAttrAtPos(*GetLayout(), *pTNd, nPos, nRet, false, false)) + nRet |= lcl_SetScriptFlags( nScript ); + } + } + else + { + sal_uLong nEndIdx = pEnd->nNode.GetIndex(); + SwNodeIndex aIdx( pStt->nNode ); + for( ; aIdx.GetIndex() <= nEndIdx; ++aIdx ) + if( aIdx.GetNode().IsTextNode() ) + { + const SwTextNode* pTNd = aIdx.GetNode().GetTextNode(); + const OUString& rText = pTNd->GetText(); + + // try to get SwScriptInfo + SwTextFrame const* pFrame; + const SwScriptInfo *const pScriptInfo = + SwScriptInfo::GetScriptInfo(*pTNd, &pFrame); + + sal_Int32 nChg = aIdx == pStt->nNode + ? pStt->nContent.GetIndex() + : 0; + sal_Int32 nEndPos = aIdx == nEndIdx + ? pEnd->nContent.GetIndex() + : rText.getLength(); + + OSL_ENSURE( nEndPos <= rText.getLength(), + "Index outside the range - endless loop!" ); + if (nEndPos > rText.getLength()) + nEndPos = rText.getLength(); + + bool const isUntilEnd(pScriptInfo + ? pFrame->MapViewToModelPos(TextFrameIndex(pFrame->GetText().getLength())) <= *pEnd + : rText.getLength() == nEndPos); + sal_uInt16 nScript; + while( nChg < nEndPos ) + { + TextFrameIndex iChg(pScriptInfo + ? pFrame->MapModelToView(pTNd, nChg) + : TextFrameIndex(-1/*invalid, do not use*/)); + nScript = pScriptInfo ? + pScriptInfo->ScriptType( iChg ) : + g_pBreakIt->GetBreakIter()->getScriptType( + rText, nChg ); + + if (!lcl_IsNoEndTextAttrAtPos(*GetLayout(), *pTNd, nChg, nRet, true, + TextFrameIndex(0) == iChg && isUntilEnd)) + { + nRet |= lcl_SetScriptFlags( nScript ); + } + + if( (SvtScriptType::LATIN | SvtScriptType::ASIAN | + SvtScriptType::COMPLEX) == nRet ) + break; + + sal_Int32 nFieldPos = nChg+1; + + if (pScriptInfo) + { + iChg = pScriptInfo->NextScriptChg(iChg); + if (iChg == TextFrameIndex(COMPLETE_STRING)) + { + nChg = pTNd->Len(); + } + else + { + std::pair<SwTextNode*, sal_Int32> const tmp( + pFrame->MapViewToModel(iChg)); + nChg = (tmp.first == pTNd) + ? tmp.second + : pTNd->Len(); + } + } + else + { + nChg = g_pBreakIt->GetBreakIter()->endOfScript( + rText, nChg, nScript ); + } + + nFieldPos = rText.indexOf( + CH_TXTATR_BREAKWORD, nFieldPos); + if ((-1 != nFieldPos) && (nFieldPos < nChg)) + nChg = nFieldPos; + } + if( (SvtScriptType::LATIN | SvtScriptType::ASIAN | + SvtScriptType::COMPLEX) == nRet ) + break; + } + } + if( (SvtScriptType::LATIN | SvtScriptType::ASIAN | + SvtScriptType::COMPLEX) == nRet ) + break; + + } + } + if( nRet == SvtScriptType::NONE ) + nRet = SvtLanguageOptions::GetScriptTypeOfLanguage( LANGUAGE_SYSTEM ); + return nRet; +} + +LanguageType SwEditShell::GetCurLang() const +{ + const SwPaM* pCursor = GetCursor(); + const SwPosition& rPos = *pCursor->GetPoint(); + const SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + LanguageType nLang; + if( pTNd ) + { + //JP 24.9.2001: if exist no selection, then get the language before + // the current character! + sal_Int32 nPos = rPos.nContent.GetIndex(); + if( nPos && !pCursor->HasMark() ) + --nPos; + nLang = pTNd->GetLang( nPos ); + } + else + nLang = LANGUAGE_DONTKNOW; + return nLang; +} + +sal_uInt16 SwEditShell::GetScalingOfSelectedText() const +{ + const SwPaM* pCursor = GetCursor(); + const SwPosition* pStt = pCursor->Start(); + const SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTNd, "no textnode available" ); + + sal_uInt16 nScaleWidth; + if( pTNd ) + { + SwTextFrame *const pFrame(static_cast<SwTextFrame *>( + pTNd->getLayoutFrame(GetLayout(), pStt))); + assert(pFrame); // shell cursor must be positioned in node with frame + TextFrameIndex const nStart(pFrame->MapModelToViewPos(*pStt)); + TextFrameIndex const nEnd( + sw::FrameContainsNode(*pFrame, pCursor->End()->nNode.GetIndex()) + ? pFrame->MapModelToViewPos(*pCursor->End()) + : TextFrameIndex(pFrame->GetText().getLength())); + nScaleWidth = pFrame->GetScalingOfSelectedText(nStart, nEnd); + } + else + nScaleWidth = 100; // default are no scaling -> 100% + return nScaleWidth; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx new file mode 100644 index 000000000..660f1f79a --- /dev/null +++ b/sw/source/core/edit/eddel.cxx @@ -0,0 +1,363 @@ +/* -*- 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 <memory> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <editsh.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <undobj.hxx> +#include <SwRewriter.hxx> +#include <osl/diagnose.h> + +#include <strings.hrc> +#include <vector> + +void SwEditShell::DeleteSel( SwPaM& rPam, bool* pUndo ) +{ + bool bSelectAll = StartsWithTable() && ExtendedSelectedAll(); + // only for selections + if (!rPam.HasMark() + || (*rPam.GetPoint() == *rPam.GetMark() + && !IsFlySelectedByCursor(*GetDoc(), *rPam.Start(), *rPam.End()))) + { + return; + } + + // Is the selection in a table? Then delete only the content of the selected boxes. + // Here, there are two cases: + // 1. Point and Mark are in one box, delete selection as usual + // 2. Point and Mark are in different boxes, search all selected boxes and delete content + // 3. Point and Mark are at the document start and end, Point is in a table: delete selection as usual + if( rPam.GetNode().FindTableNode() && + rPam.GetNode().StartOfSectionNode() != + rPam.GetNode(false).StartOfSectionNode() && !bSelectAll ) + { + // group the Undo in the table + if( pUndo && !*pUndo ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + *pUndo = true; + } + SwPaM aDelPam( *rPam.Start() ); + const SwPosition* pEndSelPos = rPam.End(); + do { + aDelPam.SetMark(); + SwNode& rNd = aDelPam.GetNode(); + const SwNode& rEndNd = *rNd.EndOfSectionNode(); + if( pEndSelPos->nNode.GetIndex() <= rEndNd.GetIndex() ) + { + *aDelPam.GetPoint() = *pEndSelPos; + pEndSelPos = nullptr; // misuse a pointer as a flag + } + else + { + // then go to the end of the selection + aDelPam.GetPoint()->nNode = rEndNd; + aDelPam.Move( fnMoveBackward, GoInContent ); + } + // skip protected boxes + if( !rNd.IsContentNode() || + !rNd.IsInProtectSect() ) + { + // delete everything + GetDoc()->getIDocumentContentOperations().DeleteAndJoin( aDelPam ); + SaveTableBoxContent( aDelPam.GetPoint() ); + } + + if( !pEndSelPos ) // at the end of a selection + break; + aDelPam.DeleteMark(); + aDelPam.Move( fnMoveForward, GoInContent ); // next box + } while( pEndSelPos ); + } + else + { + std::unique_ptr<SwPaM> pNewPam; + SwPaM * pPam = &rPam; + if (bSelectAll) + { + assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected pam + pNewPam.reset(new SwPaM(*rPam.GetMark(), *rPam.GetPoint())); + // Selection starts at the first para of the first cell, but we + // want to delete the table node before the first cell as well. + while (SwTableNode const* pTableNode = + pNewPam->Start()->nNode.GetNode().StartOfSectionNode()->FindTableNode()) + { + pNewPam->Start()->nNode = *pTableNode; + } + // tdf#133990 ensure section is included in SwUndoDelete + while (SwSectionNode const* pSectionNode = + pNewPam->Start()->nNode.GetNode().StartOfSectionNode()->FindSectionNode()) + { + pNewPam->Start()->nNode = *pSectionNode; + } + pNewPam->Start()->nContent.Assign(nullptr, 0); + pPam = pNewPam.get(); + } + // delete everything + GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam); + SaveTableBoxContent( pPam->GetPoint() ); + } + + // Selection is not needed anymore + rPam.DeleteMark(); +} + +bool SwEditShell::Delete() +{ + SET_CURR_SHELL( this ); + bool bRet = false; + if ( !HasReadonlySel() || CursorInsideInputField() ) + { + StartAllAction(); + + bool bUndo = GetCursor()->GetNext() != GetCursor(); + if( bUndo ) // more than one selection? + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_MULTISEL)); + + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + } + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + DeleteSel( rPaM, &bUndo ); + } + + // If undo container then close here + if( bUndo ) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + EndAllAction(); + bRet = true; + } + else + { + bRet = RemoveParagraphMetadataFieldAtCursor(); + } + + return bRet; +} + +bool SwEditShell::Copy( SwEditShell* pDestShell ) +{ + if( !pDestShell ) + pDestShell = this; + + SET_CURR_SHELL( pDestShell ); + + // List of insert positions for smart insert of block selections + std::vector< std::shared_ptr<SwPosition> > aInsertList; + + // Fill list of insert positions + { + SwPosition * pPos = nullptr; + std::shared_ptr<SwPosition> pInsertPos; + sal_uInt16 nMove = 0; + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !pPos ) + { + if( pDestShell == this ) + { + // First cursor represents the target position!! + rPaM.DeleteMark(); + pPos = rPaM.GetPoint(); + continue; + } + else + pPos = pDestShell->GetCursor()->GetPoint(); + } + if( IsBlockMode() ) + { // In block mode different insert positions will be calculated + // by simulated cursor movements from the given first insert position + if( nMove ) + { + SwCursor aCursor( *pPos, nullptr); + if (aCursor.UpDown(false, nMove, nullptr, 0, *GetLayout())) + { + pInsertPos = std::make_shared<SwPosition>( *aCursor.GetPoint() ); + aInsertList.push_back( pInsertPos ); + } + } + else + pInsertPos = std::make_shared<SwPosition>( *pPos ); + ++nMove; + } + SwPosition *pTmp = IsBlockMode() ? pInsertPos.get() : pPos; + // Check if a selection would be copied into itself + if( pDestShell->GetDoc() == GetDoc() && + *rPaM.Start() <= *pTmp && *pTmp < *rPaM.End() ) + return false; + } + } + + pDestShell->StartAllAction(); + SwPosition *pPos = nullptr; + bool bRet = false; + bool bFirstMove = true; + SwNodeIndex aSttNdIdx( pDestShell->GetDoc()->GetNodes() ); + sal_Int32 nSttCntIdx = 0; + // For block selection this list is filled with the insert positions + auto pNextInsert = aInsertList.begin(); + + pDestShell->GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !pPos ) + { + if( pDestShell == this ) + { + // First cursor represents the target position!! + rPaM.DeleteMark(); + pPos = rPaM.GetPoint(); + continue; + } + else + pPos = pDestShell->GetCursor()->GetPoint(); + } + if( !bFirstMove ) + { + if( pNextInsert != aInsertList.end() ) + { + pPos = pNextInsert->get(); + ++pNextInsert; + } + else if( IsBlockMode() ) + GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false ); + } + + // Only for a selection (non-text nodes have selection but Point/GetMark are equal) + if( !rPaM.HasMark() || *rPaM.GetPoint() == *rPaM.GetMark() ) + continue; + + if( bFirstMove ) + { + // Store start position of the new area + aSttNdIdx = pPos->nNode.GetIndex()-1; + nSttCntIdx = pPos->nContent.GetIndex(); + bFirstMove = false; + } + + const bool bSuccess( GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, *pPos, SwCopyFlags::CheckPosInFly) ); + if (!bSuccess) + continue; + + SwPaM aInsertPaM(*pPos, SwPosition(aSttNdIdx)); + pDestShell->GetDoc()->MakeUniqueNumRules(aInsertPaM); + + bRet = true; + } + + // Maybe nothing has been moved? + if( !bFirstMove ) + { + SwPaM* pCursor = pDestShell->GetCursor(); + pCursor->SetMark(); + pCursor->GetPoint()->nNode = aSttNdIdx.GetIndex()+1; + pCursor->GetPoint()->nContent.Assign( pCursor->GetContentNode(),nSttCntIdx); + pCursor->Exchange(); + } + else + { + // If the cursor moved during move process, move also its GetMark + pDestShell->GetCursor()->SetMark(); + pDestShell->GetCursor()->DeleteMark(); + } +#if OSL_DEBUG_LEVEL > 0 + // check if the indices are registered in the correct nodes + { + for(SwPaM& rCmp : pDestShell->GetCursor()->GetRingContainer()) + { + OSL_ENSURE( rCmp.GetPoint()->nContent.GetIdxReg() + == rCmp.GetContentNode(), "Point in wrong Node" ); + OSL_ENSURE( rCmp.GetMark()->nContent.GetIdxReg() + == rCmp.GetContentNode(false), "Mark in wrong Node" ); + } + } +#endif + + // close Undo container here + pDestShell->GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + pDestShell->EndAllAction(); + + pDestShell->SaveTableBoxContent( pDestShell->GetCursor()->GetPoint() ); + + return bRet; +} + +/** Replace a selected area in a text node with a given string. + * + * Intended for "search & replace". + * + * @param bRegExpRplc if <true> replace tabs (\\t) and replace with found string (not \&). + * E.g. [Fnd: "zzz", Repl: "xx\t\\t..&..\&"] --> "xx\t<Tab>..zzz..&" + */ +bool SwEditShell::Replace( const OUString& rNewStr, bool bRegExpRplc ) +{ + SET_CURR_SHELL( this ); + + bool bRet = false; + if( !HasReadonlySel() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() && *rPaM.GetMark() != *rPaM.GetPoint() ) + { + bRet = sw::ReplaceImpl(rPaM, rNewStr, bRegExpRplc, *GetDoc(), GetLayout()) + || bRet; + SaveTableBoxContent( rPaM.GetPoint() ); + } + } + + // close Undo container here + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + EndAllAction(); + } + return bRet; +} + +/// special method for JOE's wizards +bool SwEditShell::DelFullPara() +{ + bool bRet = false; + if( !IsTableMode() ) + { + SwPaM* pCursor = GetCursor(); + // no multi selection + if( !pCursor->IsMultiSelection() && !HasReadonlySel() ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( *pCursor ); + EndAllAction(); + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfcol.cxx b/sw/source/core/edit/edfcol.cxx new file mode 100644 index 000000000..d0154b488 --- /dev/null +++ b/sw/source/core/edit/edfcol.cxx @@ -0,0 +1,2287 @@ +/* -*- 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 <editsh.hxx> + +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/HomogenMatrix3.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/text/XParagraphAppend.hpp> +#include <com/sun/star/text/XParagraphCursor.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/rdf/XMetadatable.hpp> +#include <com/sun/star/security/DocumentDigitalSignatures.hpp> +#include <com/sun/star/security/XCertificate.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> +#include <editeng/unoprnms.hxx> +#include <sfx2/classificationhelper.hxx> +#include <svx/ClassificationCommon.hxx> +#include <svx/ClassificationField.hxx> +#include <svl/cryptosign.hxx> +#include <svl/sigstruct.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/virdev.hxx> + +#include <hintids.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> +#include <viewopt.hxx> +#include <SwRewriter.hxx> +#include <numrule.hxx> +#include <swundo.hxx> +#include <docary.hxx> +#include <docsh.hxx> +#include <unoprnms.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <rdfhelper.hxx> +#include <sfx2/watermarkitem.hxx> + +#include <unoparagraph.hxx> +#include <strings.hrc> +#include <undobj.hxx> +#include <UndoParagraphSignature.hxx> +#include <txtatr.hxx> +#include <fmtmeta.hxx> + +#include <tools/diagnose_ex.h> +#include <IDocumentRedlineAccess.hxx> + +#define WATERMARK_NAME "PowerPlusWaterMarkObject" +#define WATERMARK_AUTO_SIZE sal_uInt32(1) + +namespace +{ +static const OUString MetaFilename("tscp/bails.rdf"); +static const OUString MetaNS("urn:bails"); +static const OUString ParagraphSignatureRDFNamespace = "urn:bails:loext:paragraph:signature:"; +static const OUString ParagraphSignatureIdRDFName = "urn:bails:loext:paragraph:signature:id"; +static const OUString ParagraphSignatureDigestRDFName = ":digest"; +static const OUString ParagraphSignatureDateRDFName = ":date"; +static const OUString ParagraphSignatureUsageRDFName = ":usage"; +static const OUString ParagraphSignatureLastIdRDFName = "urn:bails:loext:paragraph:signature:lastid"; +static const OUString ParagraphClassificationNameRDFName = "urn:bails:loext:paragraph:classification:name"; +static const OUString ParagraphClassificationValueRDFName = "urn:bails:loext:paragraph:classification:value"; +static const OUString ParagraphClassificationAbbrRDFName = "urn:bails:loext:paragraph:classification:abbreviation"; +static const OUString ParagraphClassificationFieldNamesRDFName = "urn:bails:loext:paragraph:classification:fields"; +static const OUString MetadataFieldServiceName = "com.sun.star.text.textfield.MetadataField"; +static const OUString DocInfoServiceName = "com.sun.star.text.TextField.DocInfo.Custom"; + +/// Find all page styles which are currently used in the document. +std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell) +{ + std::vector<OUString> aReturn; + + SwRootFrame* pLayout = pShell->GetLayout(); + for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext()) + { + SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame); + if (const SwPageDesc *pDesc = pPage->FindPageDesc()) + aReturn.push_back(pDesc->GetName()); + } + + return aReturn; +} + +/// Search for a field named rFieldName of type rServiceName in xText and return it. +uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, const OUString& rFieldName) +{ + uno::Reference<text::XTextField> xField; + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(rServiceName)) + continue; + + OUString aName; + uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + if (aName == rFieldName) + { + xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY); + break; + } + } + } + + return xField; +} + +/// Search for a field named rFieldName of type rServiceName in xText and return true iff found. +bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, const OUString& rFieldName) +{ + return lcl_findField(xText, rServiceName, rFieldName).is(); +} + +/// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName. +uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText, + const OUString& rServiceName, OUString& rShapeName, bool& bSuccess) +{ + bSuccess = false; + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + continue; + + bSuccess = true; + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != "Frame") + continue; + + uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY); + if (!xContentEnumerationAccess.is()) + continue; + + uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration("com.sun.star.text.TextContent"); + if (!xEnumeration->hasMoreElements()) + continue; + + uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY); + if (!xWatermark->supportsService(rServiceName)) + continue; + + uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY); + + if (!xNamed->getName().match(WATERMARK_NAME)) + continue; + + rShapeName = xNamed->getName(); + + uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY); + return xShape; + } + } + + return uno::Reference<drawing::XShape>(); +} + +/// Extract the text of the paragraph without any of the fields. +/// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper. +OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText) +{ + OUStringBuffer strBuf; + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return OString(); + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Any elem = xTextPortions->nextElement(); + + //TODO: Consider including hidden and conditional texts/portions. + OUString aTextPortionType; + uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType == "Text") + { + uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY); + if (xTextRange.is()) + strBuf.append(xTextRange->getString()); + } + } + + // Cleanup the dummy characters added by fields (which we exclude). + comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART); + comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND); + comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD); + + return strBuf.makeStringAndClear().trim().toUtf8(); +} + +template <typename T> +std::map<OUString, OUString> lcl_getRDFStatements(const uno::Reference<frame::XModel>& xModel, + const T& xRef) +{ + try + { + const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY); + return SwRDFHelper::getStatements(xModel, MetaNS, xSubject); + } + catch (const ::css::uno::Exception&) + { + } + + return std::map<OUString, OUString>(); +} + +/// Returns RDF (key, value) pair associated with the field, if any. +std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField, + const OUString& sPrefix) +{ + for (const auto& pair : lcl_getRDFStatements(xModel, xField)) + { + if (pair.first.startsWith(sPrefix)) + return pair; + } + + return std::make_pair(OUString(), OUString()); +} + +/// Returns RDF (key, value) pair associated with the field, if any. +template <typename T> +std::pair<OUString, OUString> lcl_getRDF(const uno::Reference<frame::XModel>& xModel, + const T& xRef, + const OUString& sRDFName) +{ + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef); + const auto it = aStatements.find(sRDFName); + return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString()); +} + +/// Returns true iff the field in question is paragraph signature. +/// Note: must have associated RDF, since signatures are otherwise just metadata fields. +bool lcl_IsParagraphSignatureField(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField) +{ + return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName); +} + +uno::Reference<text::XTextField> lcl_findFieldByRDF(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph, + const OUString& sRDFName, + const OUString& sRDFValue) +{ + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return uno::Reference<text::XTextField>(); + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + if (!xTextPortions.is()) + return uno::Reference<text::XTextField>(); + + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY); + const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName); + if (pair.first == sRDFName && (sRDFValue.isEmpty() || sRDFValue == pair.second)) + return xField; + } + + return uno::Reference<text::XTextField>(); +} + +struct SignatureDescr +{ + OUString msSignature; + OUString msUsage; + OUString msDate; + + bool isValid() const { return !msSignature.isEmpty(); } +}; + +SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const OUString& sFieldId) +{ + SignatureDescr aDescr; + + const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId; + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph); + + const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName); + aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString()); + + const auto itDate = aStatements.find(prefix + ParagraphSignatureDateRDFName); + aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString()); + + const auto itUsage = aStatements.find(prefix + ParagraphSignatureUsageRDFName); + aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString()); + + return aDescr; +} + +SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const uno::Reference<css::text::XTextField>& xField) +{ + const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second; + if (!sFieldId.isEmpty()) + return lcl_getSignatureDescr(xModel, xParagraph, sFieldId); + + return SignatureDescr(); +} + +/// Validate and create the signature field display text from the fields. +std::pair<bool, OUString> lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr, + const OString& utf8Text) +{ + OUString msg = SwResId(STR_INVALID_SIGNATURE); + bool valid = false; + + if (aDescr.isValid()) + { + const char* pData = utf8Text.getStr(); + const std::vector<unsigned char> data(pData, pData + utf8Text.getLength()); + + OString encSignature; + if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0)) + { + const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature)); + SignatureInformation aInfo(0); + valid = svl::crypto::Signing::Verify(data, false, sig, aInfo); + valid = valid + && aInfo.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; + + msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.ouSubject + ", " + + aDescr.msDate; + msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : OUString(": ")); + msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID)); + } + } + + return std::make_pair(valid, msg); +} + +/// Validate and return validation result and signature field display text. +std::pair<bool, OUString> +lcl_MakeParagraphSignatureFieldText(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const uno::Reference<css::text::XTextField>& xField, + const OString& utf8Text) +{ + const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField); + return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text); +} + +/// Generate the next valid ID for the new signature on this paragraph. +OUString lcl_getNextSignatureId(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph) +{ + const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second; + return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1); +} + +/// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry +uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph, + const OUString& signature, + const OUString& usage) +{ + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY); + + // Add the signature at the end. + xField->attach(xParagraph->getAnchor()->getEnd()); + + const OUString sId = lcl_getNextSignatureId(xModel, xParagraph); + + const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId); + + // First convert the UTC UNIX timestamp to a tools::DateTime then to local time. + DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr)); + aDateTime.ConvertToLocalTime(); + OUStringBuffer rBuffer; + rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear())); + rBuffer.append('-'); + if (aDateTime.GetMonth() < 10) + rBuffer.append('0'); + rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth())); + rBuffer.append('-'); + if (aDateTime.GetDay() < 10) + rBuffer.append('0'); + rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay())); + + // Now set the RDF on the paragraph, since that's what is preserved in .doc(x). + const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY); + const OUString prefix = ParagraphSignatureRDFNamespace + sId; + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear()); + + return xField; +} + +/// Updates the signature field text if changed and returns true only iff updated. +bool lcl_DoUpdateParagraphSignatureField(SwDoc* pDoc, + const uno::Reference<css::text::XTextField>& xField, + const OUString& sDisplayText) +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo(); + pDoc->GetIDocumentUndoRedo().DoUndo(false); + comphelper::ScopeGuard const g([pDoc, isUndoEnabled]() { + pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + try + { + uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY); + const OUString curText = xText->getString(); + if (curText != sDisplayText) + { + xText->setString(sDisplayText); + return true; + } + } + catch (const uno::Exception&) + { + // We failed; avoid crashing. + DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature"); + } + + return false; +} + +/// Updates the signature field text if changed and returns true only iff updated. +bool lcl_UpdateParagraphSignatureField(SwDoc* pDoc, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const uno::Reference<css::text::XTextField>& xField, + const OString& utf8Text) +{ + const OUString sDisplayText + = lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second; + return lcl_DoUpdateParagraphSignatureField(pDoc, xField, sDisplayText); +} + +void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField) +{ + uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor()); + xParagraph->getText()->removeTextContent(xField); +} + +/// Returns true iff the field in question is paragraph classification. +/// Note: must have associated RDF, since classifications are otherwise just metadata fields. +bool lcl_IsParagraphClassificationField(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField, + const OUString& sKey) +{ + const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName); + return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.isEmpty() || rdfPair.second == sKey); +} + +uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph, + const OUString& sKey = OUString()) +{ + uno::Reference<text::XTextField> xTextField; + + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return xTextField; + + // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only. + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xServiceInfo; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo; + if (!xServiceInfo->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY); + if (lcl_IsParagraphClassificationField(xModel, xField, sKey)) + { + xTextField = xField; + break; + } + } + + return xTextField; +} + +/// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry +uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParent) +{ + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY); + + // Add the classification at the start. + xField->attach(xParent->getAnchor()->getStart()); + return xField; +} + +/// Updates the paragraph classification field text if changed and returns true only iff updated. +bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xTextNode, + const OUString& sKey, + const OUString& sValue, + const OUString& sDisplayText) +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo(); + pDoc->GetIDocumentUndoRedo().DoUndo(false); + comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () { + pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode); + + css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue); + + css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue); + + return lcl_DoUpdateParagraphSignatureField(pDoc, xField, sDisplayText); +} + +void lcl_ValidateParagraphSignatures(SwDoc* pDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove) +{ + SwDocShell* pDocShell = pDoc->GetDocShell(); + if (!pDocShell) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + + // Check if the paragraph is signed. + try + { + const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName); + if (pair.second.isEmpty()) + return; + } + catch (const ::css::uno::Exception&) + { + return; + } + + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return; + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + if (!xTextPortions.is()) + return; + + // Get the text (without fields). + const OString utf8Text = lcl_getParagraphBodyText(xParagraph); + if (utf8Text.isEmpty()) + return; + + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY); + if (!lcl_IsParagraphSignatureField(xModel, xField)) + { + continue; + } + + if (updateDontRemove) + { + lcl_UpdateParagraphSignatureField(pDoc, xModel, xParagraph, xField, utf8Text); + } + else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first) + { + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr); + pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoParagraphSigning>(pDoc, xField, xParagraph, false)); + lcl_RemoveParagraphMetadataField(xField); + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr); + } + } +} + +} // anonymous namespace + +SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const +{ + return *GetDoc()->GetDfltTextFormatColl(); +} + +sal_uInt16 SwEditShell::GetTextFormatCollCount() const +{ + return GetDoc()->GetTextFormatColls()->size(); +} + +SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const +{ + return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]); +} + +static void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory, + uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor, + OUString const & rsKey) +{ + uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY); + xField->setPropertyValue(UNO_NAME_NAME, uno::makeAny(rsKey)); + uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY); + + rxText->insertTextContent(rxParagraphCursor, xTextContent, false); +} + +static void removeAllClassificationFields(OUString const & rPolicy, uno::Reference<text::XText> const & rxText) +{ + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(DocInfoServiceName)) + continue; + + OUString aName; + uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + if (aName.startsWith(rPolicy)) + { + uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY); + rxText->removeTextContent(xField); + } + } + } +} + +static sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText) +{ + uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration(); + sal_Int32 nResult = 0; + while (xParagraphEnum->hasMoreElements()) + { + xParagraphEnum->nextElement(); + nResult++; + } + return nResult; +} + +static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText) +{ + sal_Int32 nNumberOfParagraphs = 0; + for (svx::ClassificationResult const & rResult : rResults) + { + if (rResult.meType == svx::ClassificationType::PARAGRAPH) + nNumberOfParagraphs++; + } + + while (getNumberOfParagraphs(xText) < nNumberOfParagraphs) + { + uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY); + xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>()); + } +} + +void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !SfxObjectShell::Current()) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + + uno::Reference<document::XDocumentProperties> xDocumentProperties = SfxObjectShell::Current()->getDocProperties(); + + const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType()); + const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this); + for (const OUString& rPageStyleName : aUsedPageStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + // HEADER + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + uno::Reference<text::XText> xHeaderText; + if (bHeaderIsOn) + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + if (xHeaderText.is()) + removeAllClassificationFields(sPolicy, xHeaderText); + + // FOOTER + bool bFooterIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn; + uno::Reference<text::XText> xFooterText; + if (bFooterIsOn) + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText; + if (xFooterText.is()) + removeAllClassificationFields(sPolicy, xFooterText); + } + + // Clear properties + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + svx::classification::removeAllProperties(xPropertyContainer); + + SfxClassificationHelper aHelper(xDocumentProperties); + + // Apply properties from the BA policy + for (svx::ClassificationResult const & rResult : rResults) + { + if (rResult.meType == svx::ClassificationType::CATEGORY) + { + aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType()); + } + } + + sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType()); + + // Insert origin document property + svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL); + + // Insert full text as document property + svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults); + + for (const OUString& rPageStyleName : aUsedPageStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + // HEADER + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::makeAny(true)); + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + equaliseNumberOfParagraph(rResults, xHeaderText); + + // FOOTER + bool bFooterIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn; + if (!bFooterIsOn) + xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::makeAny(true)); + uno::Reference<text::XText> xFooterText; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText; + equaliseNumberOfParagraph(rResults, xFooterText); + + // SET/DELETE WATERMARK + SfxWatermarkItem aWatermarkItem; + aWatermarkItem.SetText(aHelper.GetDocumentWatermark()); + SetWatermark(aWatermarkItem); + + uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY); + uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY); + + sal_Int32 nParagraph = -1; + + for (svx::ClassificationResult const & rResult : rResults) + { + switch(rResult.meType) + { + case svx::ClassificationType::TEXT: + { + OUString sKey = aCreator.makeNumberedTextKey(); + + svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::CATEGORY: + { + OUString sKey = aCreator.makeCategoryNameKey(); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::MARKING: + { + OUString sKey = aCreator.makeNumberedMarkingKey(); + svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART: + { + OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey(); + svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::PARAGRAPH: + { + nParagraph++; + + if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph + { + xHeaderParagraphCursor->gotoNextParagraph(false); + xFooterParagraphCursor->gotoNextParagraph(false); + } + + xHeaderParagraphCursor->gotoStartOfParagraph(false); + xFooterParagraphCursor->gotoStartOfParagraph(false); + + uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW); + if (rResult.msName == "BOLD") + { + xHeaderPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::BOLD)); + xFooterPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::BOLD)); + } + else + { + xHeaderPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::NORMAL)); + xFooterPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::NORMAL)); + } + } + break; + + default: + break; + } + } + } +} + +std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification() +{ + std::vector<svx::ClassificationResult> aResult; + + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !SfxObjectShell::Current()) + return aResult; + + const OUString sBlank; + + uno::Reference<document::XDocumentProperties> xDocumentProperties = SfxObjectShell::Current()->getDocProperties(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType()); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + + std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this); + OUString aPageStyleString = aPageStyles.back(); + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY); + + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey()); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + + return aResult; + } + + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + + // set to true if category was found in the header + bool bFoundClassificationCategory = false; + + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + continue; + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + + // Check font weight + uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW); + uno::Any aAny = xParagraphPropertySet->getPropertyValue("CharWeight"); + + OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? OUString("BOLD") : OUString("NORMAL"); + + aResult.push_back({ svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank }); + + // Process portions + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(DocInfoServiceName)) + continue; + + OUString aName; + uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + + if (aCreator.isMarkingTextKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank }); + } + else if (aCreator.isCategoryNameKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + bFoundClassificationCategory = true; + } + else if (aCreator.isCategoryIdentifierKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue }); + bFoundClassificationCategory = true; + } + else if (aCreator.isMarkingKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank }); + } + else if (aCreator.isIntellectualPropertyPartKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank }); + } + } + } + + if (!bFoundClassificationCategory) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey()); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + } + + return aResult; +} + +void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + + SfxClassificationHelper aHelper(pDocShell->getDocProperties()); + + const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty(); + + // This updates the infobar as well. + aHelper.SetBACName(rName, eType); + + // Insert origin document property + uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties(); + sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType()); + svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY); + + bool bHeaderIsNeeded = aHelper.HasDocumentHeader(); + bool bFooterIsNeeded = aHelper.HasDocumentFooter(); + OUString aWatermark = aHelper.GetDocumentWatermark(); + bool bWatermarkIsNeeded = !aWatermark.isEmpty(); + + if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames(); + + for (const OUString& rPageStyleName : aStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + + if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark) + { + // If the header is off, turn it on. + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::makeAny(true)); + + // If the header already contains a document header field, no need to do anything. + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + + if (bHeaderIsNeeded) + { + if (!lcl_hasField(xHeaderText, DocInfoServiceName, SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())) + { + // Append a field to the end of the header text. + uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY); + xField->setPropertyValue(UNO_NAME_NAME, uno::makeAny(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())); + uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY); + xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false); + } + } + + SfxWatermarkItem aWatermarkItem; + aWatermarkItem.SetText(aWatermark); + SetWatermark(aWatermarkItem); + } + + if (bFooterIsNeeded) + { + // If the footer is off, turn it on. + bool bFooterIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn; + if (!bFooterIsOn) + xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::makeAny(true)); + + // If the footer already contains a document header field, no need to do anything. + uno::Reference<text::XText> xFooterText; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText; + static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER(); + if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter)) + { + // Append a field to the end of the footer text. + uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY); + xField->setPropertyValue(UNO_NAME_NAME, uno::makeAny(sFooter)); + uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY); + xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false); + } + } + } +} + +// We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is +// on a performance-sensitive path. +static void lcl_ApplyParagraphClassification(SwDoc* pDoc, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParent, + const css::uno::Reference<css::rdf::XResource>& xNodeSubject, + std::vector<svx::ClassificationResult> aResults) +{ + if (!xNodeSubject.is()) + return; + + // Remove all paragraph classification fields. + for (;;) + { + uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent); + if (!xTextField.is()) + break; + lcl_RemoveParagraphMetadataField(xTextField); + } + + if (aResults.empty()) + return; + + // Since we always insert at the start of the paragraph, + // need to insert in reverse order. + std::reverse(aResults.begin(), aResults.end()); + // Ignore "PARAGRAPH" types + aResults.erase(std::remove_if(aResults.begin(), + aResults.end(), + [&](const svx::ClassificationResult& rResult)-> bool + { return rResult.meType == svx::ClassificationType::PARAGRAPH; }), + aResults.end()); + + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + std::vector<OUString> aFieldNames; + for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex) + { + const svx::ClassificationResult& rResult = aResults[nIndex]; + + const bool isLast = nIndex == 0; + const bool isFirst = (nIndex == aResults.size() - 1); + OUString sKey; + OUString sValue = rResult.msName; + switch (rResult.meType) + { + case svx::ClassificationType::TEXT: + { + sKey = aKeyCreator.makeNumberedTextKey(); + } + break; + + case svx::ClassificationType::CATEGORY: + { + if (rResult.msIdentifier.isEmpty()) + { + sKey = aKeyCreator.makeCategoryNameKey(); + } + else + { + sValue = rResult.msIdentifier; + sKey = aKeyCreator.makeCategoryIdentifierKey(); + } + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName); + } + break; + + case svx::ClassificationType::MARKING: + { + sKey = aKeyCreator.makeNumberedMarkingKey(); + } + break; + + case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART: + { + sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey(); + } + break; + + default: + break; + } + + OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName); + if (isLast) + sDisplayText += ")"; + lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText); + aFieldNames.emplace_back(sKey); + } + + // Correct the order + std::reverse(aFieldNames.begin(), aFieldNames.end()); + OUStringBuffer sFieldNames; + bool first = true; + for (const OUString& rFieldName : aFieldNames) + { + if (!first) + sFieldNames.append("/"); + sFieldNames.append(rFieldName); + first = false; + } + + const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second; + SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear()); +} + +void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start()) + return; + + SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode(); + if (pNode == nullptr) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag]() { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode); + lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent, uno::UNO_QUERY), std::move(aResults)); +} + +static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const uno::Reference<frame::XModel>& xModel, const uno::Reference<text::XTextContent>& xParagraph) +{ + std::vector<svx::ClassificationResult> aResult; + + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return aResult; + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + + const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField; + if (!xField->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY); + const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType()); + const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy); + + uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY); + const OUString aName = rdfNamePair.first; + const OUString aValue = rdfNamePair.second; + const OUString sBlank(""); + if (aKeyCreator.isMarkingTextKey(aName)) + { + aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank }); + } + else if (aKeyCreator.isCategoryNameKey(aName)) + { + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + } + else if (aKeyCreator.isCategoryIdentifierKey(aName)) + { + aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue }); + } + else if (aKeyCreator.isMarkingKey(aName)) + { + aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank }); + } + else if (aKeyCreator.isIntellectualPropertyPartKey(aName)) + { + aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank }); + } + } + + return aResult; +} + +std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification() +{ + std::vector<svx::ClassificationResult> aResult; + + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start()) + return aResult; + + SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode(); + if (pNode == nullptr) + return aResult; + + uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode); + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + return lcl_CollectParagraphClassification(xModel, xParent); +} + +static sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix) +{ + basegfx::B2DHomMatrix aTransformation; + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate = 0; + double fShear = 0; + + aTransformation.set(0, 0, rMatrix.Line1.Column1); + aTransformation.set(0, 1, rMatrix.Line1.Column2); + aTransformation.set(0, 2, rMatrix.Line1.Column3); + aTransformation.set(1, 0, rMatrix.Line2.Column1); + aTransformation.set(1, 1, rMatrix.Line2.Column2); + aTransformation.set(1, 2, rMatrix.Line2.Column3); + aTransformation.set(2, 0, rMatrix.Line3.Column1); + aTransformation.set(2, 1, rMatrix.Line3.Column2); + aTransformation.set(2, 2, rMatrix.Line3.Column3); + + aTransformation.decompose(aScale, aTranslate, fRotate, fShear); + sal_Int16 nDeg = round(basegfx::rad2deg(fRotate)); + return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg); +} + +SfxWatermarkItem SwEditShell::GetWatermark() const +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return SfxWatermarkItem(); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this); + for (const OUString& rPageStyleName : aUsedPageStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + return SfxWatermarkItem(); + + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + + OUString aShapeServiceName = "com.sun.star.drawing.CustomShape"; + OUString sWatermark = ""; + bool bSuccess = false; + uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess); + + if (xWatermark.is()) + { + SfxWatermarkItem aItem; + uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY); + Color nColor; + sal_Int16 nTransparency; + OUString aFont; + drawing::HomogenMatrix3 aMatrix; + + aItem.SetText(xTextRange->getString()); + + if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont) + aItem.SetFont(aFont); + if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor) + aItem.SetColor(nColor); + if (xPropertySet->getPropertyValue("Transformation") >>= aMatrix) + aItem.SetAngle(lcl_GetAngle(aMatrix)); + if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency) + aItem.SetTransparency(nTransparency); + + return aItem; + } + } + return SfxWatermarkItem(); +} + +static void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<beans::XPropertySet>& xPageStyle, + const uno::Reference<text::XText>& xHeaderText) +{ + if (!xHeaderText.is()) + return; + + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + OUString aShapeServiceName = "com.sun.star.drawing.CustomShape"; + OUString sWatermark = WATERMARK_NAME; + bool bSuccess = false; + uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess); + + bool bDeleteWatermark = rWatermark.GetText().isEmpty(); + if (xWatermark.is()) + { + drawing::HomogenMatrix3 aMatrix; + Color nColor = 0xc0c0c0; + sal_Int16 nTransparency = 50; + sal_Int16 nAngle = 45; + OUString aFont = ""; + + uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont; + xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor; + xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency; + xPropertySet->getPropertyValue("Transformation") >>= aMatrix; + nAngle = lcl_GetAngle(aMatrix); + + // If the header already contains a watermark, see if it its text is up to date. + uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY); + if (xTextRange->getString() != rWatermark.GetText() + || aFont != rWatermark.GetFont() + || nColor != rWatermark.GetColor() + || nAngle != rWatermark.GetAngle() + || nTransparency != rWatermark.GetTransparency() + || bDeleteWatermark) + { + // No: delete it and we'll insert a replacement. + uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY); + xComponent->dispose(); + xWatermark.clear(); + } + } + + if (!bSuccess || xWatermark.is() || bDeleteWatermark) + return; + + const OUString& sFont = rWatermark.GetFont(); + sal_Int16 nAngle = rWatermark.GetAngle(); + sal_Int16 nTransparency = rWatermark.GetTransparency(); + Color nColor = rWatermark.GetColor(); + + // Calc the ratio. + double fRatio = 0; + + ScopedVclPtrInstance<VirtualDevice> pDevice; + vcl::Font aFont = pDevice->GetFont(); + aFont.SetFamilyName(sFont); + aFont.SetFontSize(Size(0, 96)); + pDevice->SetFont(aFont); + + auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText()); + if (nTextWidth) + { + fRatio = pDevice->GetTextHeight(); + fRatio /= nTextWidth; + } + + // Calc the size. + sal_Int32 nWidth = 0; + awt::Size aSize; + xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize; + if (aSize.Width < aSize.Height) + { + // Portrait. + sal_Int32 nLeftMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin; + sal_Int32 nRightMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin; + nWidth = aSize.Width - nLeftMargin - nRightMargin; + } + else + { + // Landscape. + sal_Int32 nTopMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin; + sal_Int32 nBottomMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin; + nWidth = aSize.Height - nTopMargin - nBottomMargin; + } + sal_Int32 nHeight = fRatio * nWidth; + + // Create and insert the shape. + uno::Reference<drawing::XShape> xShape(xMultiServiceFactory->createInstance(aShapeServiceName), uno::UNO_QUERY); + + uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY); + xNamed->setName(sWatermark); + + basegfx::B2DHomMatrix aTransformation; + aTransformation.identity(); + aTransformation.scale(nWidth, nHeight); + aTransformation.rotate(-basegfx::deg2rad(nAngle)); + drawing::HomogenMatrix3 aMatrix; + aMatrix.Line1.Column1 = aTransformation.get(0, 0); + aMatrix.Line1.Column2 = aTransformation.get(0, 1); + aMatrix.Line1.Column3 = aTransformation.get(0, 2); + aMatrix.Line2.Column1 = aTransformation.get(1, 0); + aMatrix.Line2.Column2 = aTransformation.get(1, 1); + aMatrix.Line2.Column3 = aTransformation.get(1, 2); + aMatrix.Line3.Column1 = aTransformation.get(2, 0); + aMatrix.Line3.Column2 = aTransformation.get(2, 1); + aMatrix.Line3.Column3 = aTransformation.get(2, 2); + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::makeAny(text::TextContentAnchorType_AT_CHARACTER)); + uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY); + xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false); + + // The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering. + uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY); + xLockable->addActionLock(); + xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::makeAny(static_cast<sal_Int32>(nColor))); + xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::makeAny(drawing::FillStyle_SOLID)); + xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::makeAny(nTransparency)); + xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::makeAny(drawing::LineStyle_NONE)); + xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::makeAny(false)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::makeAny(false)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::makeAny(false)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::makeAny(nHeight)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::makeAny(nWidth)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::makeAny(text::WrapTextMode_THROUGH)); + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::makeAny(text::RelOrientation::PAGE_PRINT_AREA)); + xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::makeAny(text::RelOrientation::PAGE_PRINT_AREA)); + xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::makeAny(sFont)); + xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::makeAny(WATERMARK_AUTO_SIZE)); + xPropertySet->setPropertyValue("Transformation", uno::makeAny(aMatrix)); + + uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY); + xTextRange->setString(rWatermark.GetText()); + + uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY); + xDefaulter->createCustomShapeDefaults("fontwork-plain-text"); + + auto aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry").get< uno::Sequence<beans::PropertyValue> >(); + auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"TextPath", uno::makeAny(true)}, + })); + auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "TextPath"; + }); + if (it == aGeomPropVec.end()) + aGeomPropVec.push_back(comphelper::makePropertyValue("TextPath", aPropertyValues)); + else + it->Value <<= aPropertyValues; + xPropertySet->setPropertyValue("CustomShapeGeometry", uno::makeAny(comphelper::containerToSequence(aGeomPropVec))); + + // tdf#108494, tdf#109313 the header height was switched to height of a watermark + // and shape was moved to the lower part of a page, force position update + xPropertySet->getPropertyValue("Transformation") >>= aMatrix; + xPropertySet->setPropertyValue("Transformation", uno::makeAny(aMatrix)); + + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::makeAny(text::HoriOrientation::CENTER)); + xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::makeAny(text::VertOrientation::CENTER)); + + xLockable->removeActionLock(); +} + +void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + const bool bNoWatermark = rWatermark.GetText().isEmpty(); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames(); + + for (const OUString& rPageStyleName : aStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + // If the header is off, turn it on. + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + { + if (bNoWatermark) + continue; // the style doesn't have any watermark - no need to do anything + + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::makeAny(true)); + } + + // backup header height + bool bDynamicHeight = true; + sal_Int32 nOldValue; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight; + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false)); + + // If the header already contains a document header field, no need to do anything. + uno::Reference<text::XText> xHeaderText; + uno::Reference<text::XText> xHeaderTextFirst; + uno::Reference<text::XText> xHeaderTextLeft; + uno::Reference<text::XText> xHeaderTextRight; + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText); + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst); + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft); + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight); + + // tdf#108494 the header height was switched to height of a watermark + // and shape was moved to the lower part of a page + xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::makeAny(sal_Int32(11))); + xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::makeAny(nOldValue)); + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight)); + } +} + +SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc* pDoc, + const uno::Reference<text::XTextField>& xField, + const uno::Reference<text::XTextContent>& xParent, + const bool bRemove) + : SwUndo(SwUndoId::PARA_SIGN_ADD, pDoc), + m_pDoc(pDoc), + m_xField(xField), + m_xParent(xParent), + m_bRemove(bRemove) +{ + // Save the metadata and field content to undo/redo. + uno::Reference<frame::XModel> xModel = m_pDoc->GetDocShell()->GetBaseModel(); + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField); + const auto it = aStatements.find(ParagraphSignatureIdRDFName); + if (it != aStatements.end()) + m_signature = it->second; + + const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName); + if (it2 != aStatements.end()) + m_usage = it2->second; + + uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY); + m_display = xText->getString(); +} + +void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&) +{ + if (m_bRemove) + Remove(); + else + Insert(); +} + +void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&) +{ + if (m_bRemove) + Insert(); + else + Remove(); +} + +void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&) +{ +} + +void SwUndoParagraphSigning::Insert() +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + m_pDoc->GetIDocumentUndoRedo().DoUndo(false); + + // Prevent validation since this will trigger a premature validation + // upon inserting, but before setting the metadata. + SwEditShell* pEditSh = m_pDoc->GetEditShell(); + const bool bOldValidationFlag = pEditSh->SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([&] () { + pEditSh->SetParagraphSignatureValidation(bOldValidationFlag); + m_pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + m_xField = lcl_InsertParagraphSignature(m_pDoc->GetDocShell()->GetBaseModel(), m_xParent, m_signature, m_usage); + lcl_DoUpdateParagraphSignatureField(m_pDoc, m_xField, m_display); +} + +void SwUndoParagraphSigning::Remove() +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + m_pDoc->GetIDocumentUndoRedo().DoUndo(false); + + // Prevent validation since this will trigger a premature validation + // upon removing. + SwEditShell* pEditSh = m_pDoc->GetEditShell(); + const bool bOldValidationFlag = pEditSh->SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([&] () { + pEditSh->SetParagraphSignatureValidation(bOldValidationFlag); + m_pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + lcl_RemoveParagraphMetadataField(m_xField); +} + +void SwEditShell::SignParagraph() +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start()) + return; + const SwPosition* pPosStart = GetCursor()->Start(); + if (!pPosStart) + return; + SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode(); + if (!pNode) + return; + + // Table text signing is not supported. + if (pNode->FindTableNode() != nullptr) + return; + + // 1. Get the text (without fields). + const uno::Reference<text::XTextContent> xParagraph = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode); + const OString utf8Text = lcl_getParagraphBodyText(xParagraph); + if (utf8Text.isEmpty()) + return; + + // 2. Get certificate. + uno::Reference<security::XDocumentDigitalSignatures> xSigner( + // here none of the version-dependent methods are called + security::DocumentDigitalSignatures::createDefault( + comphelper::getProcessComponentContext())); + + uno::Sequence<css::beans::PropertyValue> aProperties; + uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties); + if (!xCertificate.is()) + return; + + // 3. Sign it. + svl::crypto::Signing signing(xCertificate); + signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength()); + OStringBuffer sigBuf; + if (!signing.Sign(sigBuf)) + return; + + const OUString signature = OStringToOUString(sigBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8, 0); + + std::vector<css::beans::PropertyValue> vec = comphelper::sequenceToContainer<std::vector<css::beans::PropertyValue>>(aProperties); + auto it = std::find_if(vec.begin(), vec.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "Usage"; + }); + + OUString aUsage; + if (it != vec.end()) + it->Value >>= aUsage; + + // 4. Add metadata + // Prevent validation since this will trigger a premature validation + // upon inserting, but before setting the metadata. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr); + + const uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage); + + lcl_UpdateParagraphSignatureField(GetDoc(), xModel, xParagraph, xField, utf8Text); + + GetDoc()->GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoParagraphSigning>(GetDoc(), xField, xParagraph, true)); + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr); +} + +void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove) +{ + if (!pNode || !IsParagraphSignatureValidationEnabled()) + return; + + // Table text signing is not supported. + if (pNode->FindTableNode() != nullptr) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<text::XTextContent> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode); + lcl_ValidateParagraphSignatures(GetDoc(), xParentText, updateDontRemove); +} + +void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled()) + return; + + SwPaM* pPaM = GetCursor(); + const SwPosition* pPosStart = pPaM->Start(); + SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode(); + ValidateParagraphSignatures(pNode, updateDontRemove); +} + +void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !IsParagraphSignatureValidationEnabled()) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + const uno::Reference<text::XTextDocument> xDoc(xModel, uno::UNO_QUERY); + uno::Reference<text::XText> xParent = xDoc->getText(); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY); + if (!xParagraphEnumerationAccess.is()) + return; + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + if (!xParagraphs.is()) + return; + while (xParagraphs->hasMoreElements()) + { + uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY); + lcl_ValidateParagraphSignatures(GetDoc(), xParagraph, updateDontRemove); + } +} + +static uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index) +{ + uno::Reference<text::XTextField> xTextField; + if (pNode != nullptr && pDocSh != nullptr) + { + SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD); + SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr); + if (pTextMeta != nullptr) + { + SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr())); + if (::sw::Meta* pMeta = rFormatMeta.GetMeta()) + { + const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject(); + uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel(); + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject); + if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() || + aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end()) + { + xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY); + } + } + } + } + + return xTextField; +} + +void SwEditShell::RestoreMetadataFieldsAndValidateParagraphSignatures() +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !IsParagraphSignatureValidationEnabled()) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + const uno::Reference<text::XTextDocument> xDoc(xModel, uno::UNO_QUERY); + uno::Reference<text::XText> xParent = xDoc->getText(); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY); + if (!xParagraphEnumerationAccess.is()) + return; + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + if (!xParagraphs.is()) + return; + + static const OUString sBlank(""); + const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + const css::uno::Sequence<css::uno::Reference<rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS); + + while (xParagraphs->hasMoreElements()) + { + uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY); + + try + { + const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY); + const std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject); + + const auto it = aStatements.find(ParagraphClassificationFieldNamesRDFName); + const OUString sFieldNames = (it != aStatements.end() ? it->second : sBlank); + std::vector<svx::ClassificationResult> aResults; + if (!sFieldNames.isEmpty()) + { + // Order the fields + sal_Int32 nIndex = 0; + do + { + const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex); + if (sCurFieldName.isEmpty()) + break; + + const auto it2 = aStatements.find(sCurFieldName); + const OUString sName = (it2 != aStatements.end() ? it->first : sBlank); + const OUString sValue = (it2 != aStatements.end() ? it->second : sBlank); + + if (aKeyCreator.isMarkingTextKey(sName)) + { + aResults.push_back({ svx::ClassificationType::TEXT, sValue, sValue, sBlank }); + } + else if (aKeyCreator.isCategoryNameKey(sName)) + { + const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName); + const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue); + aResults.push_back({ svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank }); + } + else if (aKeyCreator.isCategoryIdentifierKey(sName)) + { + const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName); + const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue); + aResults.push_back({ svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue }); + } + else if (aKeyCreator.isMarkingKey(sName)) + { + aResults.push_back({ svx::ClassificationType::MARKING, sValue, sValue, sBlank }); + } + else if (aKeyCreator.isIntellectualPropertyPartKey(sName)) + { + aResults.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank }); + } + } + while (nIndex >= 0); + } + + // Update classification based on results. + lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, aResults); + + // Get Signatures + std::map<OUString, SignatureDescr> aSignatures; + for (const auto& pair : lcl_getRDFStatements(xModel, xParagraph)) + { + const OUString& sName = pair.first; + if (sName.startsWith(ParagraphSignatureRDFNamespace)) + { + const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength()); + const sal_Int32 index = sSuffix.indexOf(":"); + if (index >= 0) + { + const OUString id = sSuffix.copy(0, index); + const OUString type = sSuffix.copy(index); + const OUString& sValue = pair.second; + if (type == ParagraphSignatureDateRDFName) + aSignatures[id].msDate = sValue; + else if (type == ParagraphSignatureUsageRDFName) + aSignatures[id].msUsage = sValue; + else if (type == ParagraphSignatureDigestRDFName) + aSignatures[id].msSignature = sValue; + } + } + } + + for (const auto& pair : aSignatures) + { + uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first); + if (!xField.is()) + { + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY); + + // Add the signature at the end. + xField->attach(xParagraph->getAnchor()->getEnd()); + + const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first); + + const OString utf8Text = lcl_getParagraphBodyText(xParagraph); + lcl_UpdateParagraphSignatureField(GetDoc(), xModel, xParagraph, xField, utf8Text); + } + } + + lcl_ValidateParagraphSignatures(GetDoc(), xParagraph, true); // Validate and Update signatures. + } + catch (const std::exception&) + { + } + } +} + +bool SwEditShell::IsCursorInParagraphMetadataField() const +{ + if (GetCursor() && GetCursor()->Start()) + { + SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode(); + const sal_uLong index = GetCursor()->Start()->nContent.GetIndex(); + uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index); + return xField.is(); + } + + return false; +} + +bool SwEditShell::RemoveParagraphMetadataFieldAtCursor() +{ + if (GetCursor() && GetCursor()->Start()) + { + SwTextNode* pNode = GetCursor()->Start()->nNode.GetNode().GetTextNode(); + sal_uLong index = GetCursor()->Start()->nContent.GetIndex(); + uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index); + if (!xField.is()) + { + // Try moving the cursor to see if we're _facing_ a metafield or not, + // as opposed to being within one. + index--; // Backspace moves left + + xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index); + } + + if (xField.is()) + { + lcl_RemoveParagraphMetadataField(xField); + return true; + } + } + + return false; +} + +static OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator, + const uno::Reference<frame::XModel>& xModel, const uno::Reference<text::XTextContent>& xParagraph) +{ + uno::Reference<text::XTextField> xTextField; + xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey()); + if (xTextField.is()) + { + const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName); + return rHelper.GetBACNameForIdentifier(rdfValuePair.second); + } + + xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey()); + if (xTextField.is()) + { + return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second; + } + + return OUString(); +} + +static OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor) +{ + OUString sHighestClass; + + SwTextNode* pNode = pCursor->Start()->nNode.GetNode().GetTextNode(); + if (pNode == nullptr) + return sHighestClass; + + SwDocShell* pDocShell = pNode->GetDoc()->GetDocShell(); + if (!pDocShell) + return sHighestClass; + + SfxClassificationHelper aHelper(pDocShell->getDocProperties()); + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + const uno::Reference< text::XTextDocument > xDoc(xModel, uno::UNO_QUERY); + uno::Reference<text::XText> xParent = xDoc->getText(); + + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY); + const OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph); + sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass); + } + + return sHighestClass; +} + +void SwEditShell::ClassifyDocPerHighestParagraphClass() +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + + // Bail out as early as possible if we don't have paragraph classification. + if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS)) + return; + + uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + SfxClassificationHelper aHelper(xDocumentProperties); + + OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor()); + + const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey()); + + if (!aClassificationCategory.isEmpty()) + { + sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory); + } + + if (aClassificationCategory != sHighestClass) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::Ok, + SwResId(STR_CLASSIFICATION_LEVEL_CHANGED))); + xQueryBox->run(); + } + + const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass); + + // Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag]() { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + // Check the origin, if "manual" (created via advanced classification dialog), + // then we just need to set the category name. + if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL) + { + aHelper.SetBACName(sHighestClass, eHighestClassType); + ApplyAdvancedClassification(CollectAdvancedClassification()); + } + else + { + SetClassification(sHighestClass, eHighestClassType); + } +} + +// #i62675# +void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat, + const bool bResetListAttrs) +{ + SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0]; + StartAllAction(); + + RedlineFlags eRedlMode = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode; + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, pLocal->GetName()); + + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + + if ( !rPaM.HasReadonlySel( GetViewOptions()->IsFormView() ) ) + { + // tdf#105413 turn off ShowChanges mode for the next loops to apply styles permanently with redlining, + // ie. in all directly preceding deleted paragraphs at the actual cursor positions + if ( IDocumentRedlineAccess::IsShowChanges(eRedlMode) && + // is there redlining at beginning of the position (possible redline block before the modified node) + GetDoc()->getIDocumentRedlineAccess().GetRedlinePos( (*rPaM.Start()).nNode.GetNode(), RedlineType::Any ) < + GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size() ) + { + eRedlMode = RedlineFlags::ShowInsert | RedlineFlags::Ignore; + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode ); + } + + // Change the paragraph style to pLocal and remove all direct paragraph formatting. + GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, GetLayout()); + + // If there are hints on the nodes which cover the whole node, then remove those, too. + SwPaM aPaM(*rPaM.Start(), *rPaM.End()); + if (SwTextNode* pEndTextNode = aPaM.End()->nNode.GetNode().GetTextNode()) + { + aPaM.Start()->nContent = 0; + aPaM.End()->nContent = pEndTextNode->GetText().getLength(); + } + GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout()); + } + + } + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter); + EndAllAction(); + + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode ); +} + +SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName, + SwTextFormatColl* pParent) +{ + SwTextFormatColl *pColl; + if ( pParent == nullptr ) + pParent = &GetTextFormatColl(0); + if ( (pColl=GetDoc()->MakeTextFormatColl(rFormatCollName, pParent)) == nullptr ) + { + OSL_FAIL( "MakeTextFormatColl failed" ); + } + return pColl; + +} + +void SwEditShell::FillByEx(SwTextFormatColl* pColl) +{ + SwPaM * pCursor = GetCursor(); + SwContentNode * pCnt = pCursor->GetContentNode(); + if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not? + { // only need properties-node because BREAK/PAGEDESC filtered anyway! + pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + } + const SfxItemSet* pSet = pCnt->GetpSwAttrSet(); + if( pSet ) + { + // JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is + // in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!) + // Bug 57568 + + // Do NOT copy AutoNumRules into the template + const SfxPoolItem* pItem; + const SwNumRule* pRule = nullptr; + if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false) + || SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false) + || (SfxItemState::SET == pSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem) + && nullptr != (pRule = GetDoc()->FindNumRulePtr( + static_cast<const SwNumRuleItem*>(pItem)->GetValue())) + && pRule->IsAutoRule())) + { + SfxItemSet aSet( *pSet ); + aSet.ClearItem( RES_BREAK ); + aSet.ClearItem( RES_PAGEDESC ); + + if (pRule + || (SfxItemState::SET == pSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem) + && nullptr != (pRule = GetDoc()->FindNumRulePtr( + static_cast<const SwNumRuleItem*>(pItem)->GetValue())) + && pRule->IsAutoRule())) + aSet.ClearItem( RES_PARATR_NUMRULE ); + + if( aSet.Count() ) + GetDoc()->ChgFormat(*pColl, aSet ); + } + else + GetDoc()->ChgFormat(*pColl, *pSet ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfld.cxx b/sw/source/core/edit/edfld.cxx new file mode 100644 index 000000000..dd05535e4 --- /dev/null +++ b/sw/source/core/edit/edfld.cxx @@ -0,0 +1,417 @@ +/* -*- 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 <config_features.h> + +#include <osl/diagnose.h> +#include <unotools/charclass.hxx> +#include <editsh.hxx> +#include <fldbas.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <docary.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <pamtyp.hxx> +#include <expfld.hxx> +#include <swundo.hxx> +#include <dbmgr.hxx> +#include <hints.hxx> +#include <calbck.hxx> +#include <fieldhint.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentContentOperations.hxx> + +/// count field types with a ResId, if SwFieldIds::Unknown count all +size_t SwEditShell::GetFieldTypeCount(SwFieldIds nResId ) const +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + + if(nResId == SwFieldIds::Unknown) + { + return static_cast<sal_uInt16>(pFieldTypes->size()); + } + + // all types with the same ResId + size_t nIdx = 0; + for(const auto & pFieldType : *pFieldTypes) + { + // same ResId -> increment index + if(pFieldType->Which() == nResId) + nIdx++; + } + return nIdx; +} + +/// get field types with a ResId, if 0 get all +SwFieldType* SwEditShell::GetFieldType(size_t nField, SwFieldIds nResId ) const +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + + if(nResId == SwFieldIds::Unknown && nField < pFieldTypes->size()) + { + return (*pFieldTypes)[nField].get(); + } + + size_t nIdx = 0; + for(const auto & pFieldType : *pFieldTypes) + { + // same ResId -> increment index + if(pFieldType->Which() == nResId) + { + if(nIdx == nField) + return pFieldType.get(); + nIdx++; + } + } + return nullptr; +} + +/// get first type with given ResId and name +SwFieldType* SwEditShell::GetFieldType(SwFieldIds nResId, const OUString& rName) const +{ + return GetDoc()->getIDocumentFieldsAccess().GetFieldType( nResId, rName, false ); +} + +/// delete field type +void SwEditShell::RemoveFieldType(size_t nField) +{ + GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(nField); +} + +/// delete field type based on its name +void SwEditShell::RemoveFieldType(SwFieldIds nResId, const OUString& rStr) +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + const SwFieldTypes::size_type nSize = pFieldTypes->size(); + const CharClass& rCC = GetAppCharClass(); + + OUString aTmp( rCC.lowercase( rStr )); + + for(SwFieldTypes::size_type i = 0; i < nSize; ++i) + { + // same ResId -> increment index + SwFieldType* pFieldType = (*pFieldTypes)[i].get(); + if( pFieldType->Which() == nResId ) + { + if( aTmp == rCC.lowercase( pFieldType->GetName() ) ) + { + GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(i); + return; + } + } + } +} + +void SwEditShell::FieldToText( SwFieldType const * pType ) +{ + if( !pType->HasWriterListeners() ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + StartUndo( SwUndoId::DELETE ); + Push(); + SwPaM* pPaM = GetCursor(); + // TODO: this is really hackish + SwFieldHint aHint(pPaM, GetLayout()); + SwIterator<SwClient,SwFieldType> aIter(*pType); + for( SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next() ) + { + pPaM->DeleteMark(); + pClient->SwClientNotifyCall( *pType, aHint ); + } + + Pop(PopMode::DeleteCurrent); + EndAllAction(); + EndUndo( SwUndoId::DELETE ); +} + +/// add a field at the cursor position +void SwEditShell::Insert2(SwField const & rField, const bool bForceExpandHints) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + SwFormatField aField( rField ); + + const SetAttrMode nInsertFlags = bForceExpandHints + ? SetAttrMode::FORCEHINTEXPAND + : SetAttrMode::DEFAULT; + + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM + { + const bool bSuccess(GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, aField, nInsertFlags)); + OSL_ENSURE( bSuccess, "Doc->Insert(Field) failed"); + } + + EndAllAction(); +} + +/// Are the PaMs positioned on fields? +static SwTextField* lcl_FindInputField( SwDoc* pDoc, SwField& rField ) +{ + // Search field via its address. For input fields this needs to be done in protected fields. + SwTextField* pTField = nullptr; + if (SwFieldIds::Input == rField.Which() + || (SwFieldIds::SetExp == rField.Which() + && static_cast<SwSetExpField&>(rField).GetInputFlag() + && (static_cast<SwSetExpFieldType*>(rField.GetTyp())->GetType() + & nsSwGetSetExpType::GSE_STRING))) + { + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INPUTFIELD)) + { + auto pFormatField = dynamic_cast<const SwFormatField*>(pItem); + if( pFormatField && pFormatField->GetField() == &rField ) + { + pTField = const_cast<SwFormatField*>(pFormatField)->GetTextField(); + break; + } + } + } + else if( SwFieldIds::SetExp == rField.Which() + && static_cast<SwSetExpField&>(rField).GetInputFlag() ) + { + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_FIELD)) + { + auto pFormatField = dynamic_cast<const SwFormatField*>(pItem); + if( pFormatField && pFormatField->GetField() == &rField ) + { + pTField = const_cast<SwFormatField*>(pFormatField)->GetTextField(); + break; + } + } + } + return pTField; +} + +void SwEditShell::UpdateOneField(SwField &rField) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + { + // If there are no selections so take the value of the current cursor position. + SwMsgPoolItem* pMsgHint = nullptr; + SwRefMarkFieldUpdate aRefMkHt( GetOut() ); + SwFieldIds nFieldWhich = rField.GetTyp()->Which(); + if( SwFieldIds::GetRef == nFieldWhich ) + pMsgHint = &aRefMkHt; + + SwPaM* pCursor = GetCursor(); + SwTextField *pTextField; + SwFormatField *pFormatField; + + if ( !pCursor->IsMultiSelection() && !pCursor->HasMark()) + { + pTextField = GetTextFieldAtPos( pCursor->Start(), true ); + + if (!pTextField) // #i30221# + pTextField = lcl_FindInputField( GetDoc(), rField); + + if (pTextField != nullptr) + GetDoc()->getIDocumentFieldsAccess().UpdateField(pTextField, rField, pMsgHint, true); + } + + // bOkay (instead of return because of EndAllAction) becomes false, + // 1) if only one PaM has more than one field or + // 2) if there are mixed field types + bool bOkay = true; + bool bTableSelBreak = false; + + SwMsgPoolItem aFieldHint( RES_TXTATR_FIELD ); // Search-Hint + SwMsgPoolItem aAnnotationFieldHint( RES_TXTATR_ANNOTATION ); + SwMsgPoolItem aInputFieldHint( RES_TXTATR_INPUTFIELD ); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM + { + if( rPaM.HasMark() && bOkay ) // ... with selection + { + // copy of the PaM + SwPaM aCurPam( *rPaM.GetMark(), *rPaM.GetPoint() ); + SwPaM aPam( *rPaM.GetPoint() ); + + SwPosition *pCurStt = aCurPam.Start(), *pCurEnd = + aCurPam.End(); + /* + * In case that there are two contiguous fields in a PaM, the aPam goes step by step + * to the end. aCurPam is reduced in each loop. If aCurPam was searched completely, + * the loop terminates because Start = End. + */ + + // Search for SwTextField ... + while( bOkay + && pCurStt->nContent != pCurEnd->nContent + && (sw::FindAttrImpl(aPam, aFieldHint, fnMoveForward, aCurPam, true, GetLayout()) + || sw::FindAttrImpl(aPam, aAnnotationFieldHint, fnMoveForward, aCurPam, false, GetLayout()) + || sw::FindAttrImpl(aPam, aInputFieldHint, fnMoveForward, aCurPam, false, GetLayout()))) + { + // if only one PaM has more than one field ... + if( aPam.Start()->nContent != pCurStt->nContent ) + bOkay = false; + + if( nullptr != (pTextField = GetTextFieldAtPos( pCurStt, true )) ) + { + pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField()); + SwField *pCurField = pFormatField->GetField(); + + // if there are mixed field types + if( pCurField->GetTyp()->Which() != + rField.GetTyp()->Which() ) + bOkay = false; + + bTableSelBreak = GetDoc()->getIDocumentFieldsAccess().UpdateField(pTextField, rField, + pMsgHint, false); + } + // The search area is reduced by the found area: + ++pCurStt->nContent; + } + } + + if( bTableSelBreak ) // If table section and table formula are updated -> finish + break; + + } + } + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +SwDBData const & SwEditShell::GetDBData() const +{ + return GetDoc()->GetDBData(); +} + +const SwDBData& SwEditShell::GetDBDesc() const +{ + return GetDoc()->GetDBDesc(); +} + +void SwEditShell::ChgDBData(const SwDBData& rNewData) +{ + GetDoc()->ChgDBData(rNewData); +} + +void SwEditShell::GetAllUsedDB( std::vector<OUString>& rDBNameList, + std::vector<OUString> const * pAllDBNames ) +{ + GetDoc()->GetAllUsedDB( rDBNameList, pAllDBNames ); +} + +void SwEditShell::ChangeDBFields( const std::vector<OUString>& rOldNames, + const OUString& rNewName ) +{ + GetDoc()->ChangeDBFields( rOldNames, rNewName ); +} + +/// Update all expression fields +void SwEditShell::UpdateExpFields(bool bCloseDB) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + if (bCloseDB) + { +#if HAVE_FEATURE_DBCONNECTIVITY + GetDoc()->GetDBManager()->CloseAll(); // close all database connections +#endif + } + EndAllAction(); +} + +SwDBManager* SwEditShell::GetDBManager() const +{ +#if HAVE_FEATURE_DBCONNECTIVITY + return GetDoc()->GetDBManager(); +#else + return NULL; +#endif +} + +/// insert field type +SwFieldType* SwEditShell::InsertFieldType(const SwFieldType& rFieldType) +{ + return GetDoc()->getIDocumentFieldsAccess().InsertFieldType(rFieldType); +} + +void SwEditShell::LockExpFields() +{ + GetDoc()->getIDocumentFieldsAccess().LockExpFields(); +} + +void SwEditShell::UnlockExpFields() +{ + GetDoc()->getIDocumentFieldsAccess().UnlockExpFields(); +} + +bool SwEditShell::IsExpFieldsLocked() const +{ + return GetDoc()->getIDocumentFieldsAccess().IsExpFieldsLocked(); +} + +void SwEditShell::SetFieldUpdateFlags( SwFieldUpdateFlags eFlags ) +{ + getIDocumentSettingAccess().setFieldUpdateFlags( eFlags ); +} + +SwFieldUpdateFlags SwEditShell::GetFieldUpdateFlags() const +{ + return getIDocumentSettingAccess().getFieldUpdateFlags( false ); +} + +void SwEditShell::SetLabelDoc( bool bFlag ) +{ + GetDoc()->GetDocumentSettingManager().set(DocumentSettingId::LABEL_DOCUMENT, bFlag ); +} + +bool SwEditShell::IsLabelDoc() const +{ + return getIDocumentSettingAccess().get(DocumentSettingId::LABEL_DOCUMENT); +} + +void SwEditShell::ChangeAuthorityData(const SwAuthEntry* pNewData) +{ + GetDoc()->ChangeAuthorityData(pNewData); +} + +bool SwEditShell::IsAnyDatabaseFieldInDoc()const +{ + const SwFieldTypes * pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + for(const auto & pFieldType : *pFieldTypes) + { + if(IsUsed(*pFieldType)) + { + switch(pFieldType->Which()) + { + case SwFieldIds::Database: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + { + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + return vFields.size(); + } + break; + default: break; + } + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfldexp.cxx b/sw/source/core/edit/edfldexp.cxx new file mode 100644 index 000000000..c0a8fef2a --- /dev/null +++ b/sw/source/core/edit/edfldexp.cxx @@ -0,0 +1,58 @@ +/* -*- 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 <editsh.hxx> +#include <dbfld.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <fmtfld.hxx> +#include <calbck.hxx> + +using namespace com::sun::star; + +bool SwEditShell::IsFieldDataSourceAvailable(OUString& rUsedDataSource) const +{ + const SwFieldTypes * pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext); + std::vector<SwFormatField*> vFields; + for(const auto& pFieldType : *pFieldTypes) + { + if(IsUsed(*pFieldType) && pFieldType->Which() == SwFieldIds::Database) + pFieldType->GatherFields(vFields); + } + if(!vFields.size()) + return true; + + const SwDBData& rData = static_cast<SwDBFieldType*>(vFields.front()->GetField()->GetTyp())->GetDBData(); + try + { + return xDBContext->getByName(rData.sDataSource).hasValue(); + } + catch(uno::Exception const &) + { + rUsedDataSource = rData.sDataSource; + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfmt.cxx b/sw/source/core/edit/edfmt.cxx new file mode 100644 index 000000000..de6e0a18a --- /dev/null +++ b/sw/source/core/edit/edfmt.cxx @@ -0,0 +1,159 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <editsh.hxx> +#include <pam.hxx> +#include <docary.hxx> +#include <fchrfmt.hxx> +#include <frmfmt.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> + +sal_uInt16 SwEditShell::GetCharFormatCount() const +{ + return GetDoc()->GetCharFormats()->size(); +} + +SwCharFormat& SwEditShell::GetCharFormat(sal_uInt16 nFormat) const +{ + return *((*(GetDoc()->GetCharFormats()))[nFormat]); +} + +SwCharFormat* SwEditShell::GetCurCharFormat() const +{ + SwCharFormat *pFormat = nullptr; + SfxItemSet aSet( GetDoc()->GetAttrPool(), svl::Items<RES_TXTATR_CHARFMT, + RES_TXTATR_CHARFMT>{} ); + const SfxPoolItem* pItem; + if( GetCurAttr( aSet ) && SfxItemState::SET == + aSet.GetItemState( RES_TXTATR_CHARFMT, false, &pItem ) ) + pFormat = static_cast<const SwFormatCharFormat*>(pItem)->GetCharFormat(); + + return pFormat; +} + +void SwEditShell::FillByEx(SwCharFormat* pCharFormat) +{ + SwPaM* pPam = GetCursor(); + const SwContentNode* pCNd = pPam->GetContentNode(); + if( pCNd->IsTextNode() ) + { + SwTextNode const*const pTextNode(pCNd->GetTextNode()); + sal_Int32 nStt; + sal_Int32 nEnd; + if( pPam->HasMark() ) + { + const SwPosition* pPtPos = pPam->GetPoint(); + const SwPosition* pMkPos = pPam->GetMark(); + if( pPtPos->nNode == pMkPos->nNode ) // in the same node? + { + nStt = pPtPos->nContent.GetIndex(); + if( nStt < pMkPos->nContent.GetIndex() ) + nEnd = pMkPos->nContent.GetIndex(); + else + { + nEnd = nStt; + nStt = pMkPos->nContent.GetIndex(); + } + } + else + { + nStt = pMkPos->nContent.GetIndex(); + if( pPtPos->nNode < pMkPos->nNode ) + { + nEnd = nStt; + nStt = 0; + } + else + nEnd = pTextNode->GetText().getLength(); + } + } + else + nStt = nEnd = pPam->GetPoint()->nContent.GetIndex(); + + SfxItemSet aSet( mxDoc->GetAttrPool(), + pCharFormat->GetAttrSet().GetRanges() ); + pTextNode->GetParaAttr(aSet, nStt, nEnd, false, true, false, GetLayout()); + pCharFormat->SetFormatAttr( aSet ); + } + else if( pCNd->HasSwAttrSet() ) + pCharFormat->SetFormatAttr( *pCNd->GetpSwAttrSet() ); +} + +size_t SwEditShell::GetTableFrameFormatCount(bool bUsed) const +{ + return GetDoc()->GetTableFrameFormatCount(bUsed); +} + +SwFrameFormat& SwEditShell::GetTableFrameFormat(size_t nFormat, bool bUsed ) const +{ + return GetDoc()->GetTableFrameFormat(nFormat, bUsed ); +} + +OUString SwEditShell::GetUniqueTableName() const +{ + return GetDoc()->GetUniqueTableName(); +} + +SwCharFormat* SwEditShell::MakeCharFormat( const OUString& rName ) +{ + SwCharFormat* pDerivedFrom = GetDoc()->GetDfltCharFormat(); + + return GetDoc()->MakeCharFormat( rName, pDerivedFrom ); +} + +SwTextFormatColl* SwEditShell::GetTextCollFromPool( sal_uInt16 nId ) +{ + return GetDoc()->getIDocumentStylePoolAccess().GetTextCollFromPool( nId ); +} + +/// return the requested automatic format - base-class ! +SwFormat* SwEditShell::GetFormatFromPool( sal_uInt16 nId ) +{ + return GetDoc()->getIDocumentStylePoolAccess().GetFormatFromPool( nId ); +} + +SwPageDesc* SwEditShell::GetPageDescFromPool( sal_uInt16 nId ) +{ + return GetDoc()->getIDocumentStylePoolAccess().GetPageDescFromPool( nId ); +} + +bool SwEditShell::IsUsed( const SwModify& rModify ) const +{ + return mxDoc->IsUsed( rModify ); +} + +const SwFlyFrameFormat* SwEditShell::FindFlyByName( const OUString& rName ) const +{ + return mxDoc->FindFlyByName(rName); +} + +SwCharFormat* SwEditShell::FindCharFormatByName( const OUString& rName ) const +{ + return mxDoc->FindCharFormatByName( rName ); +} + +SwTextFormatColl* SwEditShell::FindTextFormatCollByName( const OUString& rName ) const +{ + return mxDoc->FindTextFormatCollByName( rName ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edglbldc.cxx b/sw/source/core/edit/edglbldc.cxx new file mode 100644 index 000000000..add383a8c --- /dev/null +++ b/sw/source/core/edit/edglbldc.cxx @@ -0,0 +1,384 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentState.hxx> +#include <editsh.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <section.hxx> +#include <doctxm.hxx> +#include <edglbldc.hxx> +#include <osl/diagnose.h> + +bool SwEditShell::IsGlobalDoc() const +{ + return getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT); +} + +void SwEditShell::SetGlblDocSaveLinks( bool bFlag ) +{ + getIDocumentSettingAccess().set(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS, bFlag); + if( !GetDoc()->getIDocumentState().IsModified() ) // Bug 57028 + { + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); + } + GetDoc()->getIDocumentState().SetModified(); +} + +bool SwEditShell::IsGlblDocSaveLinks() const +{ + return getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS); +} + +void SwEditShell::GetGlobalDocContent( SwGlblDocContents& rArr ) const +{ + rArr.clear(); + + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + // then all linked areas on the topmost level + SwDoc* pMyDoc = GetDoc(); + const SwSectionFormats& rSectFormats = pMyDoc->GetSections(); + + for( auto n = rSectFormats.size(); n; ) + { + const SwSection* pSect = rSectFormats[ --n ]->GetGlobalDocSection(); + if( pSect ) + { + std::unique_ptr<SwGlblDocContent> pNew; + switch( pSect->GetType() ) + { + case SectionType::ToxHeader: + break; // ignore + case SectionType::ToxContent: + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( pSect) != nullptr, "no TOXBaseSection!" ); + pNew.reset(new SwGlblDocContent( static_cast<const SwTOXBaseSection*>(pSect) )); + break; + + default: + pNew.reset(new SwGlblDocContent( pSect )); + break; + } + rArr.insert( std::move(pNew) ); + } + } + + // and finally add the dummies (other text) + SwNode* pNd; + sal_uLong nSttIdx = pMyDoc->GetNodes().GetEndOfExtras().GetIndex() + 2; + for( SwGlblDocContents::size_type n = 0; n < rArr.size(); ++n ) + { + const SwGlblDocContent& rNew = *rArr[ n ]; + // Search from StartPos until rNew.DocPos for a content node. + // If one exists then a dummy entry is needed. + for( ; nSttIdx < rNew.GetDocPos(); ++nSttIdx ) + if( ( pNd = pMyDoc->GetNodes()[ nSttIdx ])->IsContentNode() + || pNd->IsSectionNode() || pNd->IsTableNode() ) + { + std::unique_ptr<SwGlblDocContent> pNew(new SwGlblDocContent( nSttIdx )); + if( rArr.insert( std::move(pNew) ).second ) + ++n; // to the next position + break; + } + + // set StartPosition to the end + nSttIdx = pMyDoc->GetNodes()[ rNew.GetDocPos() ]->EndOfSectionIndex(); + ++nSttIdx; + } + + // Should the end also be set? + if( !rArr.empty() ) + { + sal_uLong nNdEnd = pMyDoc->GetNodes().GetEndOfContent().GetIndex(); + for( ; nSttIdx < nNdEnd; ++nSttIdx ) + if( ( pNd = pMyDoc->GetNodes()[ nSttIdx ])->IsContentNode() + || pNd->IsSectionNode() || pNd->IsTableNode() ) + { + rArr.insert( std::make_unique<SwGlblDocContent>( nSttIdx ) ); + break; + } + } + else + { + std::unique_ptr<SwGlblDocContent> pNew(new SwGlblDocContent( + pMyDoc->GetNodes().GetEndOfExtras().GetIndex() + 2 )); + rArr.insert( std::move(pNew) ); + } +} + +void SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos, + SwSectionData & rNew) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + rPos.nNode = rInsPos.GetDocPos(); + + bool bEndUndo = false; + SwDoc* pMyDoc = GetDoc(); + SwTextNode *const pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( pTextNd ) + rPos.nContent.Assign( pTextNd, 0 ); + else + { + bEndUndo = true; + pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + --rPos.nNode; + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + pCursor->SetMark(); + } + + InsertSection( rNew ); + + if( bEndUndo ) + { + pMyDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + EndAllAction(); +} + +bool SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos, + const SwTOXBase& rTOX ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return false; + + SET_CURR_SHELL( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + rPos.nNode = rInsPos.GetDocPos(); + + bool bEndUndo = false; + SwDoc* pMyDoc = GetDoc(); + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + if (pTextNd && pTextNd->GetText().getLength() && rPos.nNode.GetIndex() + 1 != + pMyDoc->GetNodes().GetEndOfContent().GetIndex() ) + rPos.nContent.Assign( pTextNd, 0 ); + else + { + bEndUndo = true; + pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + --rPos.nNode; + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + } + + InsertTableOf( rTOX ); + + if( bEndUndo ) + { + pMyDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + EndAllAction(); + + return true; +} + +bool SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return false; + + SET_CURR_SHELL( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + rPos.nNode = rInsPos.GetDocPos() - 1; + rPos.nContent.Assign( nullptr, 0 ); + + SwDoc* pMyDoc = GetDoc(); + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + EndAllAction(); + return true; +} + +void SwEditShell::DeleteGlobalDocContent( const SwGlblDocContents& rArr , + size_t nDelPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + StartUndo( SwUndoId::START ); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + + SwDoc* pMyDoc = GetDoc(); + const SwGlblDocContent& rDelPos = *rArr[ nDelPos ]; + sal_uLong nDelIdx = rDelPos.GetDocPos(); + if( 1 == rArr.size() ) + { + // we need at least one node! + rPos.nNode = nDelIdx - 1; + rPos.nContent.Assign( nullptr, 0 ); + + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + ++nDelIdx; + } + + switch( rDelPos.GetType() ) + { + case GLBLDOC_UNKNOWN: + { + rPos.nNode = nDelIdx; + pCursor->SetMark(); + if( ++nDelPos < rArr.size() ) + rPos.nNode = rArr[ nDelPos ]->GetDocPos(); + else + rPos.nNode = pMyDoc->GetNodes().GetEndOfContent(); + --rPos.nNode; + if( !pMyDoc->getIDocumentContentOperations().DelFullPara( *pCursor ) ) + Delete(); + } + break; + + case GLBLDOC_TOXBASE: + { + const SwTOXBaseSection* pTOX = static_cast<const SwTOXBaseSection*>(rDelPos.GetTOX()); + pMyDoc->DeleteTOX( *pTOX, true ); + } + break; + + case GLBLDOC_SECTION: + { + SwSectionFormat* pSectFormat = const_cast<SwSectionFormat*>(rDelPos.GetSection()->GetFormat()); + pMyDoc->DelSectionFormat( pSectFormat, true ); + } + break; + } + + EndUndo( SwUndoId::END ); + EndAllAction(); +} + +bool SwEditShell::MoveGlobalDocContent( const SwGlblDocContents& rArr , + size_t nFromPos, size_t nToPos, + size_t nInsPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) || + nFromPos >= rArr.size() || nToPos > rArr.size() || + nInsPos > rArr.size() || nFromPos >= nToPos || + ( nFromPos <= nInsPos && nInsPos <= nToPos ) ) + return false; + + SET_CURR_SHELL( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwDoc* pMyDoc = GetDoc(); + SwNodeRange aRg( pMyDoc->GetNodes(), rArr[ nFromPos ]->GetDocPos() ); + if( nToPos < rArr.size() ) + aRg.aEnd = rArr[ nToPos ]->GetDocPos(); + else + aRg.aEnd = pMyDoc->GetNodes().GetEndOfContent(); + + SwNodeIndex aInsPos( pMyDoc->GetNodes() ); + if( nInsPos < rArr.size() ) + aInsPos = rArr[ nInsPos ]->GetDocPos(); + else + aInsPos = pMyDoc->GetNodes().GetEndOfContent(); + + bool bRet = pMyDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aInsPos, + SwMoveFlags::CREATEUNDOOBJ ); + + EndAllAction(); + return bRet; +} + +void SwEditShell::GotoGlobalDocContent( const SwGlblDocContent& rPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + SET_CURR_SHELL( this ); + SttCursorMove(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rCursorPos = *pCursor->GetPoint(); + rCursorPos.nNode = rPos.GetDocPos(); + + SwDoc* pMyDoc = GetDoc(); + SwContentNode * pCNd = rCursorPos.nNode.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pMyDoc->GetNodes().GoNext( &rCursorPos.nNode ); + + rCursorPos.nContent.Assign( pCNd, 0 ); + + EndCursorMove(); +} + +SwGlblDocContent::SwGlblDocContent( sal_uLong nPos ) +{ + eType = GLBLDOC_UNKNOWN; + PTR.pTOX = nullptr; + nDocPos = nPos; +} + +SwGlblDocContent::SwGlblDocContent( const SwTOXBaseSection* pTOX ) +{ + eType = GLBLDOC_TOXBASE; + PTR.pTOX = pTOX; + + const SwSectionNode* pSectNd = pTOX->GetFormat()->GetSectionNode(); + nDocPos = pSectNd ? pSectNd->GetIndex() : 0; +} + +SwGlblDocContent::SwGlblDocContent( const SwSection* pSect ) +{ + eType = GLBLDOC_SECTION; + PTR.pSect = pSect; + + const SwSectionNode* pSectNd = pSect->GetFormat()->GetSectionNode(); + nDocPos = pSectNd ? pSectNd->GetIndex() : 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx new file mode 100644 index 000000000..5c37ec22a --- /dev/null +++ b/sw/source/core/edit/edglss.cxx @@ -0,0 +1,337 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <osl/endian.h> +#include <tools/urlobj.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <pam.hxx> +#include <docary.hxx> +#include <editsh.hxx> +#include <frmfmt.hxx> +#include <rootfrm.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <shellio.hxx> +#include <iodetect.hxx> +#include <frameformats.hxx> + +void SwEditShell::InsertGlossary( SwTextBlocks& rGlossary, const OUString& rStr ) +{ + StartAllAction(); + GetDoc()->InsertGlossary( rGlossary, rStr, *GetCursor(), this ); + EndAllAction(); +} + +/// convert current selection into text block and add to the text block document, incl. templates +sal_uInt16 SwEditShell::MakeGlossary( SwTextBlocks& rBlks, const OUString& rName, const OUString& rShortName, + bool bSaveRelFile, const OUString* pOnlyText ) +{ + SwDoc* pGDoc = rBlks.GetDoc(); + + OUString sBase; + if(bSaveRelFile) + { + INetURLObject aURL( rBlks.GetFileName() ); + sBase = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + rBlks.SetBaseURL( sBase ); + + if( pOnlyText ) + return rBlks.PutText( rShortName, rName, *pOnlyText ); + + rBlks.ClearDoc(); + if( rBlks.BeginPutDoc( rShortName, rName ) ) + { + rBlks.GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::DeleteRedlines ); + CopySelToDoc( pGDoc ); + rBlks.GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::NONE ); + return rBlks.PutDoc(); + } + + return USHRT_MAX; +} + +sal_uInt16 SwEditShell::SaveGlossaryDoc( SwTextBlocks& rBlock, + const OUString& rName, + const OUString& rShortName, + bool bSaveRelFile, + bool bOnlyText ) +{ + StartAllAction(); + + SwDoc* pGDoc = rBlock.GetDoc(); + SwDoc* pMyDoc = GetDoc(); + + OUString sBase; + if(bSaveRelFile) + { + INetURLObject aURL( rBlock.GetFileName() ); + sBase = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + rBlock.SetBaseURL( sBase ); + sal_uInt16 nRet = USHRT_MAX; + + if( bOnlyText ) + { + KillPams(); + + SwPaM* pCursor = GetCursor(); + + SwNodeIndex aStt( pMyDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pMyDoc->GetNodes().GoNext( &aStt ); + const SwNode* pNd = pContentNd->FindTableNode(); + if( !pNd ) + pNd = pContentNd; + + pCursor->GetPoint()->nNode = *pNd; + if( pNd == pContentNd ) + pCursor->GetPoint()->nContent.Assign( pContentNd, 0 ); + pCursor->SetMark(); + + // then until the end of the Node array + pCursor->GetPoint()->nNode = pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1; + pContentNd = pCursor->GetContentNode(); + if( pContentNd ) + pCursor->GetPoint()->nContent.Assign( pContentNd, pContentNd->Len() ); + + OUString sBuf; + GetSelectedText( sBuf, ParaBreakType::ToOnlyCR ); + if( !sBuf.isEmpty() ) + nRet = rBlock.PutText( rShortName, rName, sBuf ); + } + else + { + rBlock.ClearDoc(); + if( rBlock.BeginPutDoc( rShortName, rName ) ) + { + SwNodeIndex aStt( pMyDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pMyDoc->GetNodes().GoNext( &aStt ); + const SwNode* pNd = pContentNd->FindTableNode(); + if( !pNd ) pNd = pContentNd; + SwPaM aCpyPam( *pNd ); + aCpyPam.SetMark(); + + // then until the end of the nodes array + aCpyPam.GetPoint()->nNode = pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1; + pContentNd = aCpyPam.GetContentNode(); + aCpyPam.GetPoint()->nContent.Assign( + pContentNd, pContentNd ? pContentNd->Len() : 0); + + aStt = pGDoc->GetNodes().GetEndOfExtras(); + pContentNd = pGDoc->GetNodes().GoNext( &aStt ); + SwPosition aInsPos( aStt, SwIndex( pContentNd )); + pMyDoc->getIDocumentContentOperations().CopyRange(aCpyPam, aInsPos, SwCopyFlags::CheckPosInFly); + + nRet = rBlock.PutDoc(); + } + } + EndAllAction(); + return nRet; +} + +/// copy all selections to the doc +bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc ) +{ + OSL_ENSURE( pInsDoc, "no Ins.Document" ); + + SwNodes& rNds = pInsDoc->GetNodes(); + + SwNodeIndex aIdx( rNds.GetEndOfContent(), -1 ); + SwContentNode *const pContentNode = aIdx.GetNode().GetContentNode(); + SwPosition aPos( aIdx, + SwIndex(pContentNode, pContentNode ? pContentNode->Len() : 0)); + + bool bRet = false; + SET_CURR_SHELL( this ); + + pInsDoc->getIDocumentFieldsAccess().LockExpFields(); + + if( IsTableMode() ) + { + // Copy parts of a table: create a table with the width of the original one and copy the + // selected boxes. The sizes are corrected on a percentage basis. + + // search boxes using the layout + SwTableNode* pTableNd; + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes ); + if( !aBoxes.empty() && nullptr != (pTableNd = const_cast<SwTableNode*>(aBoxes[0] + ->GetSttNd()->FindTableNode()) )) + { + // check if the table name can be copied + bool bCpyTableNm = aBoxes.size() == pTableNd->GetTable().GetTabSortBoxes().size(); + if( bCpyTableNm ) + { + const OUString rTableName = pTableNd->GetTable().GetFrameFormat()->GetName(); + const SwFrameFormats& rTableFormats = *pInsDoc->GetTableFrameFormats(); + for( auto n = rTableFormats.size(); n; ) + if( rTableFormats[ --n ]->GetName() == rTableName ) + { + bCpyTableNm = false; + break; + } + } + bRet = pInsDoc->InsCopyOfTable( aPos, aBoxes, nullptr, bCpyTableNm ); + } + else + bRet = false; + } + else + { + bool bColSel = GetCursor_()->IsColumnSelection(); + if( bColSel && pInsDoc->IsClipBoard() ) + pInsDoc->SetColumnSelection( true ); + bool bSelectAll = StartsWithTable() && ExtendedSelectedAll(); + { + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !rPaM.HasMark() ) + { + SwContentNode *const pNd = rPaM.GetContentNode(); + if (nullptr != pNd && + ( bColSel || !pNd->GetTextNode() ) ) + { + rPaM.SetMark(); + rPaM.Move( fnMoveForward, GoInContent ); + bRet = GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, aPos, SwCopyFlags::CheckPosInFly) + || bRet; + rPaM.Exchange(); + rPaM.DeleteMark(); + } + } + else + { + // Make a copy, so that in case we need to adjust the selection + // for the purpose of copying, our shell cursor is not touched. + // (Otherwise we would have to restore it.) + SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint()); + if (bSelectAll) + { + // Selection starts at the first para of the first cell, + // but we want to copy the table and the start node before + // the first cell as well. + // tdf#133982 tables can be nested + while (SwTableNode const* pTableNode = + aPaM.Start()->nNode.GetNode().StartOfSectionNode()->FindTableNode()) + { + aPaM.Start()->nNode = *pTableNode; + } + aPaM.Start()->nContent.Assign(nullptr, 0); + } + bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, SwCopyFlags::CheckPosInFly) + || bRet; + } + } + } + } + + pInsDoc->getIDocumentFieldsAccess().UnlockExpFields(); + if( !pInsDoc->getIDocumentFieldsAccess().IsExpFieldsLocked() ) + pInsDoc->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + return bRet; +} + +/** Get text in a Selection + */ +void SwEditShell::GetSelectedText( OUString &rBuf, ParaBreakType nHndlParaBrk ) +{ + GetCursor(); // creates all cursors if needed + if( IsSelOnePara() ) + { + rBuf = GetSelText(); + if( ParaBreakType::ToBlank == nHndlParaBrk ) + { + rBuf = rBuf.replaceAll("\x0a", " "); + } + else if( IsSelFullPara() && + ParaBreakType::ToOnlyCR != nHndlParaBrk ) + { +#ifdef _WIN32 + rBuf += "\015\012"; +#else + rBuf += "\012"; +#endif + } + } + else if( IsSelection() ) + { + SvMemoryStream aStream; +#ifdef OSL_BIGENDIAN + aStream.SetEndian( SvStreamEndian::BIG ); +#else + aStream.SetEndian( SvStreamEndian::LITTLE ); +#endif + WriterRef xWrt; + SwReaderWriter::GetWriter( FILTER_TEXT, OUString(), xWrt ); + if( xWrt.is() ) + { + // write selected areas into an ASCII document + SwWriter aWriter( aStream, *this); + xWrt->SetShowProgress(false); + + switch( nHndlParaBrk ) + { + case ParaBreakType::ToBlank: + xWrt->m_bASCII_ParaAsBlank = true; + xWrt->m_bASCII_NoLastLineEnd = true; + break; + + case ParaBreakType::ToOnlyCR: + xWrt->m_bASCII_ParaAsCR = true; + xWrt->m_bASCII_NoLastLineEnd = true; + break; + } + + //JP 09.05.00: write as UNICODE ! (and not as ANSI) + SwAsciiOptions aAsciiOpt( xWrt->GetAsciiOptions() ); + aAsciiOpt.SetCharSet( RTL_TEXTENCODING_UCS2 ); + xWrt->SetAsciiOptions( aAsciiOpt ); + xWrt->m_bUCS2_WithStartChar = false; + xWrt->m_bHideDeleteRedlines = GetLayout()->IsHideRedlines(); + + if ( ! aWriter.Write(xWrt).IsError() ) + { + aStream.WriteUInt16( '\0' ); + + const sal_Unicode *p = static_cast<sal_Unicode const *>(aStream.GetData()); + if (p) + rBuf = OUString(p); + else + { + const sal_uInt64 nLen = aStream.GetSize(); + OSL_ENSURE( nLen/sizeof( sal_Unicode )<o3tl::make_unsigned(SAL_MAX_INT32), "Stream can't fit in OUString" ); + rtl_uString *pStr = rtl_uString_alloc(static_cast<sal_Int32>(nLen / sizeof( sal_Unicode ))); + aStream.Seek( 0 ); + aStream.ResetError(); + //endian specific?, yipes! + aStream.ReadBytes(pStr->buffer, nLen); + rBuf = OUString(pStr, SAL_NO_ACQUIRE); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/editsh.cxx b/sw/source/core/edit/editsh.cxx new file mode 100644 index 000000000..0b932fba1 --- /dev/null +++ b/sw/source/core/edit/editsh.cxx @@ -0,0 +1,1056 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/commandevent.hxx> +#include <unotools/charclass.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <fmtsrnd.hxx> +#include <fmtinfmt.hxx> +#include <txtinet.hxx> +#include <frmfmt.hxx> +#include <charfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentState.hxx> +#include <docary.hxx> +#include <editsh.hxx> +#include <frame.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <flyfrm.hxx> +#include <swundo.hxx> +#include <calc.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <extinput.hxx> +#include <scriptinfo.hxx> +#include <unocrsrhelper.hxx> +#include <section.hxx> +#include <numrule.hxx> +#include <SwNodeNum.hxx> +#include <unocrsr.hxx> +#include <calbck.hxx> + +using namespace com::sun::star; + +void SwEditShell::Insert( sal_Unicode c, bool bOnlyCurrCursor ) +{ + StartAllAction(); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + const bool bSuccess = GetDoc()->getIDocumentContentOperations().InsertString(rPaM, OUString(c)); + OSL_ENSURE( bSuccess, "Doc->Insert() failed." ); + + SaveTableBoxContent( rPaM.GetPoint() ); + if( bOnlyCurrCursor ) + break; + + } + + EndAllAction(); +} + +void SwEditShell::Insert2(const OUString &rStr, const bool bForceExpandHints ) +{ + StartAllAction(); + { + const SwInsertFlags nInsertFlags = + bForceExpandHints + ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND) + : SwInsertFlags::EMPTYEXPAND; + + for(SwPaM& rCurrentCursor : getShellCursor( true )->GetRingContainer()) + { + //OPT: GetSystemCharSet + const bool bSuccess = + GetDoc()->getIDocumentContentOperations().InsertString(rCurrentCursor, rStr, nInsertFlags); + OSL_ENSURE( bSuccess, "Doc->Insert() failed." ); + + if (bSuccess) + { + GetDoc()->UpdateRsid( rCurrentCursor, rStr.getLength() ); + + // Set paragraph rsid if beginning of paragraph + SwTextNode *const pTextNode = + rCurrentCursor.GetPoint()->nNode.GetNode().GetTextNode(); + if( pTextNode && pTextNode->Len() == 1) + GetDoc()->UpdateParRsid( pTextNode ); + } + + SaveTableBoxContent( rCurrentCursor.GetPoint() ); + + } + } + + // calculate cursor bidi level + SwCursor* pTmpCursor = GetCursor_(); + const bool bDoNotSetBidiLevel = ! pTmpCursor || + ( dynamic_cast<SwUnoCursor*>(pTmpCursor) != nullptr ); + + if ( ! bDoNotSetBidiLevel ) + { + SwNode& rNode = pTmpCursor->GetPoint()->nNode.GetNode(); + if ( rNode.IsTextNode() ) + { + SwIndex& rIdx = pTmpCursor->GetPoint()->nContent; + sal_Int32 nPrevPos = rIdx.GetIndex(); + if ( nPrevPos ) + --nPrevPos; + + SwTextFrame const* pFrame; + SwScriptInfo *const pSI = SwScriptInfo::GetScriptInfo( + static_cast<SwTextNode&>(rNode), &pFrame, true); + + sal_uInt8 nLevel = 0; + if ( ! pSI ) + { + // seems to be an empty paragraph. + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + pFrame = static_cast<SwTextFrame*>( + static_cast<SwTextNode&>(rNode).getLayoutFrame( + GetLayout(), pTmpCursor->GetPoint(), &tmp)); + + SwScriptInfo aScriptInfo; + aScriptInfo.InitScriptInfo(static_cast<SwTextNode&>(rNode), + pFrame->GetMergedPara(), pFrame->IsRightToLeft()); + TextFrameIndex const iPrevPos(pFrame->MapModelToView( + &static_cast<SwTextNode&>(rNode), nPrevPos)); + nLevel = aScriptInfo.DirType( iPrevPos ); + } + else + { + if (TextFrameIndex(COMPLETE_STRING) != pSI->GetInvalidityA()) + { + // mystery why this doesn't use the other overload? + pSI->InitScriptInfo(static_cast<SwTextNode&>(rNode), pFrame->GetMergedPara()); + } + TextFrameIndex const iPrevPos(pFrame->MapModelToView( + &static_cast<SwTextNode&>(rNode), nPrevPos)); + nLevel = pSI->DirType(iPrevPos); + } + + pTmpCursor->SetCursorBidiLevel( nLevel ); + } + } + + SetInFrontOfLabel( false ); // #i27615# + + EndAllAction(); +} + +void SwEditShell::Overwrite(const OUString &rStr) +{ + StartAllAction(); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !GetDoc()->getIDocumentContentOperations().Overwrite(rPaM, rStr ) ) + { + OSL_FAIL( "Doc->getIDocumentContentOperations().Overwrite(Str) failed." ); + } + SaveTableBoxContent( rPaM.GetPoint() ); + } + EndAllAction(); +} + +void SwEditShell::SplitNode( bool bAutoFormat, bool bCheckTableStart ) +{ + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + // Here, a table cell becomes a normal text cell. + GetDoc()->ClearBoxNumAttrs( rPaM.GetPoint()->nNode ); + GetDoc()->getIDocumentContentOperations().SplitNode( *rPaM.GetPoint(), bCheckTableStart ); + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + if( bAutoFormat ) + AutoFormatBySplitNode(); + + ClearTableBoxContent(); + + EndAllAction(); +} + +bool SwEditShell::AppendTextNode() +{ + bool bRet = false; + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + GetDoc()->ClearBoxNumAttrs( rPaM.GetPoint()->nNode ); + bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( *rPaM.GetPoint()) || bRet; + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + ClearTableBoxContent(); + + EndAllAction(); + return bRet; +} + +// the returned SwGrfNode pointer is used in GetGraphic() and GetGraphicSize() +SwGrfNode * SwEditShell::GetGrfNode_() const +{ + SwGrfNode *pGrfNode = nullptr; + SwPaM* pCursor = GetCursor(); + if( !pCursor->HasMark() || + pCursor->GetPoint()->nNode == pCursor->GetMark()->nNode ) + pGrfNode = pCursor->GetPoint()->nNode.GetNode().GetGrfNode(); + + return pGrfNode; +} + +// returns a Graphic pointer if CurrentCursor->GetPoint() points to a SwGrfNode and +// GetMark is not set or points to the same Graphic +const Graphic* SwEditShell::GetGraphic( bool bWait ) const +{ + SwGrfNode* pGrfNode = GetGrfNode_(); + const Graphic* pGrf( nullptr ); + if ( pGrfNode ) + { + pGrf = &(pGrfNode->GetGrf(bWait && GraphicType::Default == pGrfNode->GetGrf().GetType())); + } + return pGrf; +} + +bool SwEditShell::IsLinkedGrfSwapOut() const +{ + SwGrfNode *pGrfNode = GetGrfNode_(); + return pGrfNode && + pGrfNode->IsLinkedFile() && + GraphicType::Default == pGrfNode->GetGrfObj().GetType(); +} + +const GraphicObject* SwEditShell::GetGraphicObj() const +{ + SwGrfNode* pGrfNode = GetGrfNode_(); + return pGrfNode ? &(pGrfNode->GetGrfObj()) : nullptr; +} + +const GraphicAttr* SwEditShell::GetGraphicAttr( GraphicAttr& rGA ) const +{ + SwGrfNode* pGrfNode = GetGrfNode_(); + const SwFrame* pFrame = GetCurrFrame(false); + return pGrfNode ? &(pGrfNode->GetGraphicAttr( rGA, pFrame )) : nullptr; +} + +GraphicType SwEditShell::GetGraphicType() const +{ + SwGrfNode *pGrfNode = GetGrfNode_(); + return pGrfNode ? pGrfNode->GetGrfObj().GetType() : GraphicType::NONE; +} + +// returns the size of a graphic in <rSz> if CurrentCursor->GetPoint() points to a SwGrfNode and +// GetMark is not set or points to the same graphic +bool SwEditShell::GetGrfSize(Size& rSz) const +{ + SwNoTextNode* pNoTextNd; + SwPaM* pCurrentCursor = GetCursor(); + if( ( !pCurrentCursor->HasMark() + || pCurrentCursor->GetPoint()->nNode == pCurrentCursor->GetMark()->nNode ) + && nullptr != ( pNoTextNd = pCurrentCursor->GetNode().GetNoTextNode() ) ) + { + rSz = pNoTextNd->GetTwipSize(); + return true; + } + return false; + +} + +/// Read again if graphic is not OK and replace old one +void SwEditShell::ReRead( const OUString& rGrfName, const OUString& rFltName, + const Graphic* pGraphic ) +{ + StartAllAction(); + mxDoc->getIDocumentContentOperations().ReRead( *GetCursor(), rGrfName, rFltName, pGraphic ); + EndAllAction(); +} + +/// Returns the name and the filter name of a graphic if the pointer is on a graphic. +/// If a String-pointer is != 0 then return corresponding name. +void SwEditShell::GetGrfNms( OUString* pGrfName, OUString* pFltName, + const SwFlyFrameFormat* pFormat ) const +{ + OSL_ENSURE( pGrfName || pFltName, "No parameters" ); + if( pFormat ) + SwDoc::GetGrfNms( *pFormat, pGrfName, pFltName ); + else + { + SwGrfNode *pGrfNode = GetGrfNode_(); + if( pGrfNode && pGrfNode->IsLinkedFile() ) + pGrfNode->GetFileFilterNms( pGrfName, pFltName ); + } +} + +const tools::PolyPolygon *SwEditShell::GetGraphicPolygon() const +{ + SwNoTextNode *pNd = GetCursor()->GetNode().GetNoTextNode(); + return pNd->HasContour(); +} + +void SwEditShell::SetGraphicPolygon( const tools::PolyPolygon *pPoly ) +{ + SwNoTextNode *pNd = GetCursor()->GetNode().GetNoTextNode(); + StartAllAction(); + pNd->SetContour( pPoly ); + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper()); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + pFly->GetFormat()->NotifyClients( &rSur, &rSur ); + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +void SwEditShell::ClearAutomaticContour() +{ + SwNoTextNode *pNd = GetCursor()->GetNode().GetNoTextNode(); + OSL_ENSURE( pNd, "is no NoTextNode!" ); + if( pNd->HasAutomaticContour() ) + { + StartAllAction(); + pNd->SetContour( nullptr ); + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper()); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + pFly->GetFormat()->NotifyClients( &rSur, &rSur ); + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + } +} + +/** Get OLE object at pointer. + * + * Returns a pointer to a SvInPlaceObjectRef if CurrentCursor->GetPoint() points to a SwOLENode and + * GetMark is not set or points to the same object reference. Gets this pointer from the Doc + * if the object should be searched by name. + */ +svt::EmbeddedObjectRef& SwEditShell::GetOLEObject() const +{ + OSL_ENSURE( CNT_OLE == GetCntType(), "GetOLEObj: no OLENode." ); + OSL_ENSURE( !GetCursor()->HasMark() || + (GetCursor()->HasMark() && + GetCursor()->GetPoint()->nNode == GetCursor()->GetMark()->nNode), + "GetOLEObj: no OLENode." ); + + SwOLENode *pOLENode = GetCursor()->GetNode().GetOLENode(); + OSL_ENSURE( pOLENode, "GetOLEObj: no OLENode." ); + SwOLEObj& rOObj = pOLENode->GetOLEObj(); + return rOObj.GetObject(); +} + +bool SwEditShell::HasOLEObj( const OUString &rName ) const +{ + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwNode& rNd = aIdx.GetNode(); + if( rNd.IsOLENode() && + rName == static_cast<SwOLENode&>(rNd).GetChartTableName() && + static_cast<SwOLENode&>(rNd).getLayoutFrame( GetLayout() ) ) + return true; + + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + return false; +} + +void SwEditShell::SetChartName( const OUString &rName ) +{ + SwOLENode *pONd = GetCursor()->GetNode().GetOLENode(); + OSL_ENSURE( pONd, "ChartNode not found" ); + pONd->SetChartTableName( rName ); +} + +void SwEditShell::UpdateCharts( const OUString &rName ) +{ + GetDoc()->UpdateCharts( rName ); +} + +/// change table name +void SwEditShell::SetTableName( SwFrameFormat& rTableFormat, const OUString &rNewName ) +{ + GetDoc()->SetTableName( rTableFormat, rNewName ); +} + +/// request current word +OUString SwEditShell::GetCurWord() const +{ + const SwPaM& rPaM = *GetCursor(); + const SwTextNode* pNd = rPaM.GetNode().GetTextNode(); + if (!pNd) + { + return OUString(); + } + SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(pNd->getLayoutFrame(GetLayout()))); + if (pFrame) + { + return pFrame->GetCurWord(*rPaM.GetPoint()); + } + return OUString(); +} + +void SwEditShell::UpdateDocStat( ) +{ + StartAllAction(); + GetDoc()->getIDocumentStatistics().UpdateDocStat( false, true ); + EndAllAction(); +} + +const SwDocStat& SwEditShell::GetUpdatedDocStat() +{ + StartAllAction(); + const SwDocStat &rRet = GetDoc()->getIDocumentStatistics().GetUpdatedDocStat( false, true ); + EndAllAction(); + return rRet; +} + +/// get the reference of a given name in the Doc +const SwFormatRefMark* SwEditShell::GetRefMark( const OUString& rName ) const +{ + return GetDoc()->GetRefMark( rName ); +} + +/// get the names of all references in a Doc +sal_uInt16 SwEditShell::GetRefMarks( std::vector<OUString>* pStrings ) const +{ + return GetDoc()->GetRefMarks( pStrings ); +} + +OUString SwEditShell::GetDropText( const sal_Int32 nChars ) const +{ + /* + * pb: made changes for #i74939# + * + * always return a string even though there is a selection + */ + + OUString aText; + SwPaM* pCursor = GetCursor(); + if ( IsMultiSelection() ) + { + // if a multi selection exists, search for the first line + // -> it is the cursor with the lowest index + sal_uLong nIndex = pCursor->GetMark()->nNode.GetIndex(); + bool bPrev = true; + SwPaM* pLast = pCursor; + SwPaM* pTemp = pCursor; + while ( bPrev ) + { + SwPaM* pPrev2 = pTemp->GetPrev(); + bPrev = ( pPrev2 && pPrev2 != pLast ); + if ( bPrev ) + { + pTemp = pPrev2; + sal_uLong nTemp = pPrev2->GetMark()->nNode.GetIndex(); + if ( nTemp < nIndex ) + { + nIndex = nTemp; + pCursor = pPrev2; + } + } + } + } + + SwTextNode const*const pTextNd = pCursor->GetNode(false).GetTextNode(); + if( pTextNd ) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(GetLayout()))); + SAL_WARN_IF(!pTextFrame, "sw.core", "GetDropText cursor has no frame?"); + if (pTextFrame) + { + TextFrameIndex const nDropLen(pTextFrame->GetDropLen(TextFrameIndex(nChars))); + aText = pTextFrame->GetText().copy(0, sal_Int32(nDropLen)); + } + } + + return aText; +} + +void SwEditShell::ReplaceDropText( const OUString &rStr, SwPaM* pPaM ) +{ + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + if( pCursor->GetPoint()->nNode == pCursor->GetMark()->nNode && + pCursor->GetNode().GetTextNode()->IsTextNode() ) + { + StartAllAction(); + + const SwNodeIndex& rNd = pCursor->GetPoint()->nNode; + SwPaM aPam( rNd, rStr.getLength(), rNd, 0 ); + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>( + rNd.GetNode().GetTextNode()->getLayoutFrame(GetLayout()))); + if (pTextFrame) + { + *aPam.GetPoint() = pTextFrame->MapViewToModelPos(TextFrameIndex(0)); + *aPam.GetMark() = pTextFrame->MapViewToModelPos(TextFrameIndex( + std::min(rStr.getLength(), pTextFrame->GetText().getLength()))); + } + if( !GetDoc()->getIDocumentContentOperations().Overwrite( aPam, rStr ) ) + { + OSL_FAIL( "Doc->getIDocumentContentOperations().Overwrite(Str) failed." ); + } + + EndAllAction(); + } +} + +OUString SwEditShell::Calculate() +{ + OUStringBuffer aFormel; // the final formula + SwCalc aCalc( *GetDoc() ); + const CharClass& rCC = GetAppCharClass(); + + for(SwPaM& rCurrentPaM : GetCursor()->GetNext()->GetRingContainer()) + { + SwTextNode* pTextNd = rCurrentPaM.GetNode().GetTextNode(); + if(pTextNd) + { + const SwPosition *pStart = rCurrentPaM.Start(), *pEnd = rCurrentPaM.End(); + const sal_Int32 nStt = pStart->nContent.GetIndex(); + OUString aStr = pTextNd->GetExpandText(GetLayout(), + nStt, pEnd->nContent.GetIndex() - nStt); + + aStr = rCC.lowercase( aStr ); + + bool bValidFields = false; + sal_Int32 nPos = 0; + + while( nPos < aStr.getLength() ) + { + sal_Unicode ch = aStr[ nPos++ ]; + if( rCC.isLetter( aStr, nPos-1 ) || ch == '_' ) + { + sal_Int32 nTmpStt = nPos-1; + while( nPos < aStr.getLength() && + 0 != ( ch = aStr[ nPos++ ]) && + (rCC.isLetterNumeric( aStr, nPos - 1 ) || + ch == '_'|| ch == '.' )) + ; + + if( nPos < aStr.getLength() ) + --nPos; + + OUString sVar = aStr.copy( nTmpStt, nPos - nTmpStt ); + if( !::FindOperator( sVar ) && + (aCalc.GetVarTable().Find(sVar) || + aCalc.VarLook( sVar )) ) + { + if( !bValidFields ) + { + GetDoc()->getIDocumentFieldsAccess().FieldsToCalc( aCalc, + pStart->nNode.GetIndex(), + pStart->nContent.GetIndex() ); + bValidFields = true; + } + aFormel.append("(").append(aCalc.GetStrResult( aCalc.VarLook( sVar )->nValue )).append(")"); + } + else + aFormel.append(sVar); + } + else + aFormel.append(ch); + } + } + } + + return aCalc.GetStrResult( aCalc.Calculate(aFormel.makeStringAndClear()) ); +} + +sfx2::LinkManager& SwEditShell::GetLinkManager() +{ + return mxDoc->getIDocumentLinksAdministration().GetLinkManager(); +} + +void *SwEditShell::GetIMapInventor() const +{ + // The node on which the cursor points should be sufficient as a unique identifier + return static_cast<void*>(&(GetCursor()->GetNode())); +} + +// #i73788# +Graphic SwEditShell::GetIMapGraphic() const +{ + // returns always a graphic if the cursor is in a Fly + SET_CURR_SHELL( const_cast<SwEditShell*>(this) ); + Graphic aRet; + SwPaM* pCursor = GetCursor(); + if ( !pCursor->HasMark() ) + { + SwNode& rNd =pCursor->GetNode(); + if( rNd.IsGrfNode() ) + { + SwGrfNode & rGrfNode(static_cast<SwGrfNode&>(rNd)); + aRet = rGrfNode.GetGrf(GraphicType::Default == rGrfNode.GetGrf().GetType()); + } + else if ( rNd.IsOLENode() ) + { + if (const Graphic* pGraphic = static_cast<SwOLENode&>(rNd).GetGraphic()) + aRet = *pGraphic; + } + else + { + SwFlyFrame* pFlyFrame = rNd.GetContentNode()->getLayoutFrame( GetLayout() )->FindFlyFrame(); + if(pFlyFrame) + aRet = pFlyFrame->GetFormat()->MakeGraphic(); + } + } + return aRet; +} + +bool SwEditShell::InsertURL( const SwFormatINetFormat& rFormat, const OUString& rStr, bool bKeepSelection ) +{ + // URL and hint text (directly or via selection) necessary + if( rFormat.GetValue().isEmpty() || ( rStr.isEmpty() && !HasSelection() ) ) + return false; + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_INSERT_URLTXT, nullptr); + bool bInsText = true; + + if( !rStr.isEmpty() ) + { + SwPaM* pCursor = GetCursor(); + if( pCursor->HasMark() && *pCursor->GetPoint() != *pCursor->GetMark() ) + { + // Selection existent, multi selection? + bool bDelText = true; + if( !pCursor->IsMultiSelection() ) + { + // simple selection -> check the text + const OUString sText(comphelper::string::stripEnd(GetSelText(), ' ')); + if( sText == rStr ) + bDelText = bInsText = false; + } + else if( rFormat.GetValue() == rStr ) // Are Name and URL equal? + bDelText = bInsText = false; + + if( bDelText ) + Delete(); + } + else if( pCursor->IsMultiSelection() && rFormat.GetValue() == rStr ) + bInsText = false; + + if( bInsText ) + { + Insert2( rStr ); + SetMark(); + ExtendSelection( false, rStr.getLength() ); + } + } + else + bInsText = false; + + SetAttrItem( rFormat ); + if (bInsText && !IsCursorPtAtEnd()) + SwapPam(); + if(!bKeepSelection) + ClearMark(); + if( bInsText ) + DontExpandFormat(); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_INSERT_URLTXT, nullptr ); + EndAllAction(); + return true; +} + +void SwEditShell::GetINetAttrs( SwGetINetAttrs& rArr ) +{ + rArr.clear(); + + const SwCharFormats* pFormats = GetDoc()->GetCharFormats(); + for( auto n = pFormats->size(); 1 < n; ) + { + SwIterator<SwTextINetFormat,SwCharFormat> aIter(*(*pFormats)[--n]); + for( SwTextINetFormat* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + SwTextNode const*const pTextNd(pFnd->GetpTextNode()); + SwTextFrame const*const pFrame(pTextNd + ? static_cast<SwTextFrame const*>(pTextNd->getLayoutFrame(GetLayout())) + : nullptr); + if (nullptr != pTextNd && nullptr != pFrame + && pTextNd->GetNodes().IsDocNodes() + // check it's not fully deleted + && pFrame->MapModelToView(pTextNd, pFnd->GetStart()) + != pFrame->MapModelToView(pTextNd, *pFnd->GetEnd())) + { + SwTextINetFormat& rAttr = *pFnd; + OUString sText( pTextNd->GetExpandText(GetLayout(), + rAttr.GetStart(), *rAttr.GetEnd() - rAttr.GetStart()) ); + + sText = sText.replaceAll("\x0a", ""); + sText = comphelper::string::strip(sText, ' '); + + if( !sText.isEmpty() ) + { + rArr.emplace_back(sText, rAttr); + } + } + } + } +} + +/// If the cursor is in an INetAttribute then it will be deleted completely (incl. hint text, the +/// latter is needed for drag & drop) +void SwEditShell::DelINetAttrWithText() +{ + bool bRet = SelectTextAttr( RES_TXTATR_INETFMT, false ); + if( bRet ) + DeleteSel( *GetCursor() ); +} + +/// Set the DontExpand flag at the text character attributes +bool SwEditShell::DontExpandFormat() +{ + bool bRet = false; + if( !IsTableMode() && GetDoc()->DontExpandFormat( *GetCursor()->GetPoint() )) + { + bRet = true; + CallChgLnk(); + } + return bRet; +} + +SvNumberFormatter* SwEditShell::GetNumberFormatter() +{ + return GetDoc()->GetNumberFormatter(); +} + +bool SwEditShell::ConvertFieldsToText() +{ + StartAllAction(); + bool bRet = GetDoc()->ConvertFieldsToText(*GetLayout()); + EndAllAction(); + return bRet; +} + +void SwEditShell::SetNumberingRestart() +{ + StartAllAction(); + Push(); + // iterate over all text contents - body, frames, header, footer, footnote text + SwPaM* pCursor = GetCursor(); + for(int i = 0; i < 2; i++) + { + if(!i) + MakeFindRange(SwDocPositions::Start, SwDocPositions::End, pCursor); // body content + else + MakeFindRange(SwDocPositions::OtherStart, SwDocPositions::OtherEnd, pCursor); // extra content + SwPosition* pSttPos = pCursor->Start(), *pEndPos = pCursor->End(); + sal_uLong nCurrNd = pSttPos->nNode.GetIndex(); + sal_uLong nEndNd = pEndPos->nNode.GetIndex(); + if( nCurrNd <= nEndNd ) + { + SwContentFrame* pContentFrame; + bool bGoOn = true; + // iterate over all paragraphs + while( bGoOn ) + { + SwNode* pNd = GetDoc()->GetNodes()[ nCurrNd ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + if( nullptr != ( pContentFrame = static_cast<SwTextNode*>(pNd)->getLayoutFrame( GetLayout() )) ) + { + // skip hidden frames - ignore protection! + if( !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() ) + { + // if the node is numbered and the starting value of the numbering equals the + // start value of the numbering rule then set this value as hard starting value + + // get the node num + // OD 2005-11-09 + SwTextNode* pTextNd( pNd->GetTextNode() ); + SwNumRule* pNumRule( pTextNd->GetNumRule() ); + + // sw_redlinehide: not sure what this should do, only called from mail-merge + bool bIsNodeNum = + ( pNumRule && pTextNd->GetNum() && + ( pTextNd->HasNumber() || pTextNd->HasBullet() ) && + pTextNd->IsCountedInList() && + !pTextNd->IsListRestart() ); + if (bIsNodeNum) + { + int nListLevel = pTextNd->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + bIsNodeNum = pTextNd->GetNum()->GetNumber() == + pNumRule->Get( static_cast<sal_uInt16>(nListLevel) ).GetStart(); + } + if (bIsNodeNum) + { + // now set the start value as attribute + SwPosition aCurrentNode(*pNd); + GetDoc()->SetNumRuleStart( aCurrentNode ); + } + } + } + break; + case SwNodeType::Section: + // skip hidden sections - ignore protection! + if(static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() ) + nCurrNd = pNd->EndOfSectionIndex(); + break; + default: break; + } + + bGoOn = nCurrNd < nEndNd; + ++nCurrNd; + } + } + } + + Pop(PopMode::DeleteCurrent); + EndAllAction(); +} + +sal_uInt16 SwEditShell::GetLineCount() +{ + sal_uInt16 nRet = 0; + CalcLayout(); + SwPaM* pPam = GetCursor(); + SwNodeIndex& rPtIdx = pPam->GetPoint()->nNode; + SwNodeIndex aStart( rPtIdx ); + SwContentNode* pCNd; + SwContentFrame *pContentFrame = nullptr; + + aStart = 0; + + while( nullptr != ( pCNd = GetDoc()->GetNodes().GoNextSection( + &aStart, true, false )) ) + { + if( nullptr != ( pContentFrame = pCNd->getLayoutFrame( GetLayout() ) ) && pContentFrame->IsTextFrame() ) + { + SwTextFrame *const pFrame(static_cast<SwTextFrame*>(pContentFrame)); + nRet = nRet + pFrame->GetLineCount(TextFrameIndex(COMPLETE_STRING)); + if (GetLayout()->IsHideRedlines()) + { + if (auto const*const pMerged = pFrame->GetMergedPara()) + { + aStart = *pMerged->pLastNode; + } + } + } + } + return nRet; +} + +long SwEditShell::CompareDoc( const SwDoc& rDoc ) +{ + StartAllAction(); + long nRet = GetDoc()->CompareDoc( rDoc ); + EndAllAction(); + return nRet; +} + +long SwEditShell::MergeDoc( const SwDoc& rDoc ) +{ + StartAllAction(); + long nRet = GetDoc()->MergeDoc( rDoc ); + EndAllAction(); + return nRet; +} + +const SwFootnoteInfo& SwEditShell::GetFootnoteInfo() const +{ + return GetDoc()->GetFootnoteInfo(); +} + +void SwEditShell::SetFootnoteInfo(const SwFootnoteInfo& rInfo) +{ + StartAllAction(); + SET_CURR_SHELL( this ); + GetDoc()->SetFootnoteInfo(rInfo); + CallChgLnk(); + EndAllAction(); +} + +const SwEndNoteInfo& SwEditShell::GetEndNoteInfo() const +{ + return GetDoc()->GetEndNoteInfo(); +} + +void SwEditShell::SetEndNoteInfo(const SwEndNoteInfo& rInfo) +{ + StartAllAction(); + SET_CURR_SHELL( this ); + GetDoc()->SetEndNoteInfo(rInfo); + EndAllAction(); +} + +const SwLineNumberInfo& SwEditShell::GetLineNumberInfo() const +{ + return GetDoc()->GetLineNumberInfo(); +} + +void SwEditShell::SetLineNumberInfo(const SwLineNumberInfo& rInfo) +{ + StartAllAction(); + SET_CURR_SHELL( this ); + GetDoc()->SetLineNumberInfo(rInfo); + AddPaintRect( GetLayout()->getFrameArea() ); + EndAllAction(); +} + +sal_uInt16 SwEditShell::GetLinkUpdMode() const +{ + return getIDocumentSettingAccess().getLinkUpdateMode( false ); +} + +void SwEditShell::SetLinkUpdMode( sal_uInt16 nMode ) +{ + getIDocumentSettingAccess().setLinkUpdateMode( nMode ); +} + +// Interface for TextInputData - (for text input of japanese/chinese characters) +void SwEditShell::CreateExtTextInput(LanguageType eInputLanguage) +{ + SwExtTextInput* pRet = GetDoc()->CreateExtTextInput( *GetCursor() ); + pRet->SetLanguage(eInputLanguage); + pRet->SetOverwriteCursor( SwCursorShell::IsOverwriteCursor() ); +} + +OUString SwEditShell::DeleteExtTextInput( bool bInsText ) +{ + const SwPosition& rPos = *GetCursor()->GetPoint(); + SwExtTextInput* pDel = GetDoc()->GetExtTextInput( rPos.nNode.GetNode(), + rPos.nContent.GetIndex() ); + if( !pDel ) + { + //JP 25.10.2001: under UNIX the cursor is moved before the Input- + // Engine event comes in. So take any - normally there + // exist only one at the time. -- Task 92016 + pDel = GetDoc()->GetExtTextInput(); + } + OUString sRet; + if( pDel ) + { + OUString sTmp; + SwUnoCursorHelper::GetTextFromPam(*pDel, sTmp); + sRet = sTmp; + SET_CURR_SHELL( this ); + StartAllAction(); + pDel->SetInsText( bInsText ); + SetOverwriteCursor( pDel->IsOverwriteCursor() ); + const SwPosition aPos( *pDel->GetPoint() ); + GetDoc()->DeleteExtTextInput( pDel ); + + // In this case, the "replace" function did not set the cursor + // to the original position. Therefore we have to do this manually. + if ( ! bInsText && IsOverwriteCursor() ) + *GetCursor()->GetPoint() = aPos; + + EndAllAction(); + } + return sRet; +} + +void SwEditShell::SetExtTextInputData( const CommandExtTextInputData& rData ) +{ + const SwPosition& rPos = *GetCursor()->GetPoint(); + SwExtTextInput* pInput = GetDoc()->GetExtTextInput( rPos.nNode.GetNode() ); + if( !pInput ) + return; + + StartAllAction(); + SET_CURR_SHELL( this ); + + if( !rData.IsOnlyCursorChanged() ) + pInput->SetInputData( rData ); + // position cursor + const SwPosition& rStt = *pInput->Start(); + const sal_Int32 nNewCursorPos = rStt.nContent.GetIndex() + rData.GetCursorPos(); + + // ugly but works + ShowCursor(); + const sal_Int32 nDiff = nNewCursorPos - rPos.nContent.GetIndex(); + if( 0 > nDiff ) + Left( -nDiff, CRSR_SKIP_CHARS ); + else if( 0 < nDiff ) + Right( nDiff, CRSR_SKIP_CHARS ); + + SetOverwriteCursor( rData.IsCursorOverwrite() ); + + EndAllAction(); + + if( !rData.IsCursorVisible() ) // must be called after the EndAction + HideCursor(); + +} + +void SwEditShell::TransliterateText( TransliterationFlags nType ) +{ + utl::TransliterationWrapper aTrans( ::comphelper::getProcessComponentContext(), nType ); + StartAllAction(); + SET_CURR_SHELL( this ); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + GetDoc()->getIDocumentContentOperations().TransliterateText( rPaM, aTrans ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + else + GetDoc()->getIDocumentContentOperations().TransliterateText( *pCursor, aTrans ); + + EndAllAction(); +} + +void SwEditShell::CountWords( SwDocStat& rStat ) const +{ + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + SwDoc::CountWords( rPaM, rStat ); + + } +} + +void SwEditShell::ApplyViewOptions( const SwViewOption &rOpt ) +{ + SwCursorShell::StartAction(); + SwViewShell::ApplyViewOptions( rOpt ); + SwEditShell::EndAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edlingu.cxx b/sw/source/core/edit/edlingu.cxx new file mode 100644 index 000000000..15e8532f0 --- /dev/null +++ b/sw/source/core/edit/edlingu.cxx @@ -0,0 +1,1712 @@ +/* -*- 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 <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/linguistic2/ProofreadingResult.hpp> +#include <com/sun/star/linguistic2/XProofreadingIterator.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <com/sun/star/linguistic2/XLinguProperties.hpp> +#include <com/sun/star/text/XFlatParagraph.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <o3tl/any.hxx> + +#include <unoflatpara.hxx> + +#include <strings.hrc> +#include <hintids.hxx> +#include <unotools/linguprops.hxx> +#include <linguistic/lngprops.hxx> +#include <editeng/langitem.hxx> +#include <editeng/SpellPortions.hxx> +#include <svl/languageoptions.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <rootfrm.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> +#include <viewopt.hxx> +#include <SwGrammarMarkUp.hxx> +#include <mdiexp.hxx> +#include <cntfrm.hxx> +#include <splargs.hxx> +#include <redline.hxx> +#include <docary.hxx> +#include <docsh.hxx> +#include <txatbase.hxx> +#include <txtfrm.hxx> + +using namespace ::svx; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + +namespace { + +class SwLinguIter +{ + SwEditShell *pSh; + std::unique_ptr<SwPosition> m_pStart; + std::unique_ptr<SwPosition> m_pEnd; + std::unique_ptr<SwPosition> m_pCurr; + std::unique_ptr<SwPosition> m_pCurrX; + sal_uInt16 nCursorCnt; +public: + SwLinguIter(); + + SwEditShell *GetSh() { return pSh; } + + const SwPosition *GetEnd() const { return m_pEnd.get(); } + void SetEnd(SwPosition* pNew) { m_pEnd.reset(pNew); } + + const SwPosition *GetStart() const { return m_pStart.get(); } + void SetStart(SwPosition* pNew) { m_pStart.reset(pNew); } + + const SwPosition *GetCurr() const { return m_pCurr.get(); } + void SetCurr(SwPosition* pNew) { m_pCurr.reset(pNew); } + + const SwPosition *GetCurrX() const { return m_pCurrX.get(); } + void SetCurrX(SwPosition* pNew) { m_pCurrX.reset(pNew); } + + sal_uInt16& GetCursorCnt(){ return nCursorCnt; } + + // for the UI: + void Start_( SwEditShell *pSh, SwDocPositions eStart, + SwDocPositions eEnd ); + void End_(bool bRestoreSelection = true); +}; + +// #i18881# to be able to identify the positions of the changed words +// the content positions of each portion need to be saved +struct SpellContentPosition +{ + sal_Int32 nLeft; + sal_Int32 nRight; +}; + +} + +typedef std::vector<SpellContentPosition> SpellContentPositions; + +namespace { + +class SwSpellIter : public SwLinguIter +{ + uno::Reference< XSpellChecker1 > xSpeller; + svx::SpellPortions aLastPortions; + + SpellContentPositions aLastPositions; + bool bBackToStartOfSentence; + + void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt, + linguistic2::ProofreadingResult* pGrammarResult, + bool bIsField, bool bIsHidden); + + void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt, + linguistic2::ProofreadingResult* pGrammarResult, + const SpellContentPositions& rDeletedRedlines); +public: + SwSpellIter() : + bBackToStartOfSentence(false) {} + + void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd ); + + uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ); + + bool SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck); + void ToSentenceStart(); + const svx::SpellPortions& GetLastPortions() const { return aLastPortions;} + const SpellContentPositions& GetLastPositions() const {return aLastPositions;} +}; + +/// used for text conversion +class SwConvIter : public SwLinguIter +{ + SwConversionArgs &rArgs; +public: + explicit SwConvIter(SwConversionArgs &rConvArgs) + : rArgs(rConvArgs) + { + } + + void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd ); + + uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ); +}; + +class SwHyphIter : public SwLinguIter +{ + // With that we save a GetFrame() in Hyphenate //TODO: does it actually matter? + const SwTextNode *m_pLastNode; + SwTextFrame *m_pLastFrame; + friend SwTextFrame * sw::SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& rCreator); + + bool bOldIdle; + static void DelSoftHyph( SwPaM &rPam ); + +public: + SwHyphIter() : m_pLastNode(nullptr), m_pLastFrame(nullptr), bOldIdle(false) {} + + void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd ); + void End(); + + void Ignore(); + + uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ); + + static bool IsAuto(); + void InsertSoftHyph( const sal_Int32 nHyphPos ); + void ShowSelection(); +}; + +} + +static SwSpellIter* g_pSpellIter = nullptr; +static SwConvIter* g_pConvIter = nullptr; +static SwHyphIter* g_pHyphIter = nullptr; + +SwLinguIter::SwLinguIter() + : pSh(nullptr) + , nCursorCnt(0) +{ + // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc. +} + +void SwLinguIter::Start_( SwEditShell *pShell, SwDocPositions eStart, + SwDocPositions eEnd ) +{ + // TODO missing: ensurance of re-entrance, locking + if( pSh ) + return; + + bool bSetCurr; + + pSh = pShell; + + SET_CURR_SHELL( pSh ); + + OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?"); + + SwPaM *pCursor = pSh->GetCursor(); + + if( pShell->HasSelection() || pCursor != pCursor->GetNext() ) + { + bSetCurr = nullptr != GetCurr(); + nCursorCnt = pSh->GetCursorCnt(); + if( pSh->IsTableMode() ) + pSh->TableCursorToCursor(); + + pSh->Push(); + sal_uInt16 n; + for( n = 0; n < nCursorCnt; ++n ) + { + pSh->Push(); + pSh->DestroyCursor(); + } + pSh->Pop(SwCursorShell::PopMode::DeleteCurrent); + } + else + { + bSetCurr = false; + nCursorCnt = 1; + pSh->Push(); + pSh->SetLinguRange( eStart, eEnd ); + } + + pCursor = pSh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + + m_pStart.reset(new SwPosition(*pCursor->GetPoint())); + m_pEnd.reset(new SwPosition(*pCursor->GetMark())); + if( bSetCurr ) + { + SwPosition* pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + } + + pCursor->SetMark(); +} + +void SwLinguIter::End_(bool bRestoreSelection) +{ + if( !pSh ) + return; + + OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?"); + if(bRestoreSelection) + { + while( nCursorCnt-- ) + pSh->Pop(SwCursorShell::PopMode::DeleteCurrent); + + pSh->KillPams(); + pSh->ClearMark(); + } + m_pStart.reset(); + m_pEnd.reset(); + m_pCurr.reset(); + m_pCurrX.reset(); + + pSh = nullptr; +} + +void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart, + SwDocPositions eEnd ) +{ + if( GetSh() ) + return; + + xSpeller = ::GetSpellChecker(); + if ( xSpeller.is() ) + Start_( pShell, eStart, eEnd ); + aLastPortions.clear(); + aLastPositions.clear(); +} + +// This method is the origin of SwEditShell::SpellContinue() +uno::Any SwSpellIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + //!! + //!! Please check SwConvIter also when modifying this + //!! + + uno::Any aSpellRet; + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return aSpellRet; + + OSL_ENSURE( GetEnd(), "SwSpellIter::Continue without start?"); + + uno::Reference< uno::XInterface > xSpellRet; + bool bGoOn = true; + do { + SwPaM *pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + + *pMySh->GetCursor()->GetPoint() = *GetCurr(); + *pMySh->GetCursor()->GetMark() = *GetEnd(); + pMySh->GetDoc()->Spell(*pMySh->GetCursor(), + xSpeller, pPageCnt, pPageSt, false, pMySh->GetLayout()) >>= xSpellRet; + bGoOn = GetCursorCnt() > 1; + if( xSpellRet.is() ) + { + bGoOn = false; + SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() ); + SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() ); + SetCurr( pNewPoint ); + SetCurrX( pNewMark ); + } + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition( *pCursor->GetPoint() ); + SetStart( pNew ); + pNew = new SwPosition( *pCursor->GetMark() ); + SetEnd( pNew ); + pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + }while ( bGoOn ); + aSpellRet <<= xSpellRet; + return aSpellRet; +} + +void SwConvIter::Start( SwEditShell *pShell, SwDocPositions eStart, + SwDocPositions eEnd ) +{ + if( GetSh() ) + return; + Start_( pShell, eStart, eEnd ); +} + +uno::Any SwConvIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + //!! + //!! Please check SwSpellIter also when modifying this + //!! + + uno::Any aConvRet( makeAny( OUString() ) ); + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return aConvRet; + + OSL_ENSURE( GetEnd(), "SwConvIter::Continue() without Start?"); + + OUString aConvText; + bool bGoOn = true; + do { + SwPaM *pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + + *pMySh->GetCursor()->GetPoint() = *GetCurr(); + *pMySh->GetCursor()->GetMark() = *GetEnd(); + + // call function to find next text portion to be converted + uno::Reference< linguistic2::XSpellChecker1 > xEmpty; + pMySh->GetDoc()->Spell( *pMySh->GetCursor(), + xEmpty, pPageCnt, pPageSt, false, pMySh->GetLayout(), &rArgs) >>= aConvText; + + bGoOn = GetCursorCnt() > 1; + if( !aConvText.isEmpty() ) + { + bGoOn = false; + SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() ); + SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() ); + + SetCurr( pNewPoint ); + SetCurrX( pNewMark ); + } + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition( *pCursor->GetPoint() ); + SetStart( pNew ); + pNew = new SwPosition( *pCursor->GetMark() ); + SetEnd( pNew ); + pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + }while ( bGoOn ); + return makeAny( aConvText ); +} + +bool SwHyphIter::IsAuto() +{ + uno::Reference< beans::XPropertySet > xProp( ::GetLinguPropertySet() ); + return xProp.is() && *o3tl::doAccess<bool>(xProp->getPropertyValue( + UPN_IS_HYPH_AUTO )); +} + +void SwHyphIter::ShowSelection() +{ + SwEditShell *pMySh = GetSh(); + if( pMySh ) + { + pMySh->StartAction(); + // Caution! Due to EndAction() formatting is started which can lead to the fact that new + // words are added to/set in the Hyphenator. Thus: save! + pMySh->EndAction(); + } +} + +void SwHyphIter::Start( SwEditShell *pShell, SwDocPositions eStart, SwDocPositions eEnd ) +{ + // robust + if( GetSh() || GetEnd() ) + { + OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" ); + return; + } + + // nothing to do (at least not in the way as in the "else" part) + bOldIdle = pShell->GetViewOptions()->IsIdle(); + pShell->GetViewOptions()->SetIdle( false ); + Start_( pShell, eStart, eEnd ); +} + +// restore selections +void SwHyphIter::End() +{ + if( !GetSh() ) + return; + GetSh()->GetViewOptions()->SetIdle( bOldIdle ); + End_(); +} + +uno::Any SwHyphIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + uno::Any aHyphRet; + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return aHyphRet; + + const bool bAuto = IsAuto(); + uno::Reference< XHyphenatedWord > xHyphWord; + bool bGoOn = false; + do { + SwPaM *pCursor; + do { + OSL_ENSURE( GetEnd(), "SwHyphIter::Continue without Start?" ); + pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + if ( *pCursor->GetPoint() < *pCursor->GetMark() ) + { + pCursor->Exchange(); + pCursor->SetMark(); + } + + if ( *pCursor->End() <= *GetEnd() ) + { + *pCursor->GetMark() = *GetEnd(); + + // Do we need to break the word at the current cursor position? + const Point aCursorPos( pMySh->GetCharRect().Pos() ); + xHyphWord = pMySh->GetDoc()->Hyphenate( pCursor, aCursorPos, + pPageCnt, pPageSt ); + } + + if( bAuto && xHyphWord.is() ) + { + SwEditShell::InsertSoftHyph( xHyphWord->getHyphenationPos() + 1); + } + } while( bAuto && xHyphWord.is() ); //end of do-while + bGoOn = !xHyphWord.is() && GetCursorCnt() > 1; + + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition(*pCursor->End()); + SetEnd( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + } while ( bGoOn ); + aHyphRet <<= xHyphWord; + return aHyphRet; +} + +/// ignore hyphenation +void SwHyphIter::Ignore() +{ + SwEditShell *pMySh = GetSh(); + SwPaM *pCursor = pMySh->GetCursor(); + + // delete old SoftHyphen + DelSoftHyph( *pCursor ); + + // and continue + pCursor->Start()->nContent = pCursor->End()->nContent; + pCursor->SetMark(); +} + +void SwHyphIter::DelSoftHyph( SwPaM &rPam ) +{ + const SwPosition* pStt = rPam.Start(); + const sal_Int32 nStart = pStt->nContent.GetIndex(); + const sal_Int32 nEnd = rPam.End()->nContent.GetIndex(); + SwTextNode *pNode = pStt->nNode.GetNode().GetTextNode(); + pNode->DelSoftHyph( nStart, nEnd ); +} + +void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos ) +{ + SwEditShell *pMySh = GetSh(); + OSL_ENSURE( pMySh, "SwHyphIter::InsertSoftHyph: missing HyphStart()"); + if( !pMySh ) + return; + + SwPaM *pCursor = pMySh->GetCursor(); + SwPosition* pSttPos = pCursor->Start(); + SwPosition* pEndPos = pCursor->End(); + + const sal_Int32 nLastHyphLen = GetEnd()->nContent.GetIndex() - + pSttPos->nContent.GetIndex(); + + if( pSttPos->nNode != pEndPos->nNode || !nLastHyphLen ) + { + OSL_ENSURE( pSttPos->nNode == pEndPos->nNode, + "SwHyphIter::InsertSoftHyph: node warp during hyphenation" ); + OSL_ENSURE(nLastHyphLen, "SwHyphIter::InsertSoftHyph: missing HyphContinue()"); + *pSttPos = *pEndPos; + return; + } + + pMySh->StartAction(); + { + SwDoc *pDoc = pMySh->GetDoc(); + DelSoftHyph( *pCursor ); + pSttPos->nContent += nHyphPos; + SwPaM aRg( *pSttPos ); + pDoc->getIDocumentContentOperations().InsertString( aRg, OUString(CHAR_SOFTHYPHEN) ); + } + // revoke selection + pCursor->DeleteMark(); + pMySh->EndAction(); + pCursor->SetMark(); +} + +namespace sw { + +SwTextFrame * +SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& create) +{ + assert(g_pHyphIter); + if (pNode != g_pHyphIter->m_pLastNode || !g_pHyphIter->m_pLastFrame) + { + g_pHyphIter->m_pLastNode = pNode; + g_pHyphIter->m_pLastFrame = create(); + } + return g_pHyphIter->m_pLastFrame; +} + +} + +bool SwEditShell::HasLastSentenceGotGrammarChecked() +{ + bool bTextWasGrammarChecked = false; + if (g_pSpellIter) + { + svx::SpellPortions aLastPortions( g_pSpellIter->GetLastPortions() ); + for (size_t i = 0; i < aLastPortions.size() && !bTextWasGrammarChecked; ++i) + { + // bIsGrammarError is also true if the text was only checked but no + // grammar error was found. (That is if a ProofreadingResult was obtained in + // SwDoc::Spell and in turn bIsGrammarError was set in SwSpellIter::CreatePortion) + if (aLastPortions[i].bIsGrammarError) + bTextWasGrammarChecked = true; + } + } + return bTextWasGrammarChecked; +} + +bool SwEditShell::HasConvIter() +{ + return nullptr != g_pConvIter; +} + +bool SwEditShell::HasHyphIter() +{ + return nullptr != g_pHyphIter; +} + +void SwEditShell::SetLinguRange( SwDocPositions eStart, SwDocPositions eEnd ) +{ + SwPaM *pCursor = GetCursor(); + MakeFindRange( eStart, eEnd, pCursor ); + if( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); +} + +void SwEditShell::SpellStart( + SwDocPositions eStart, SwDocPositions eEnd, SwDocPositions eCurr, + SwConversionArgs *pConvArgs ) +{ + SwLinguIter *pLinguIter = nullptr; + + // do not spell if interactive spelling is active elsewhere + if (!pConvArgs && !g_pSpellIter) + { + g_pSpellIter = new SwSpellIter; + pLinguIter = g_pSpellIter; + } + // do not do text conversion if it is active elsewhere + if (pConvArgs && !g_pConvIter) + { + g_pConvIter = new SwConvIter( *pConvArgs ); + pLinguIter = g_pConvIter; + } + + if (pLinguIter) + { + SwCursor* pSwCursor = GetSwCursor(); + + SwPosition *pTmp = new SwPosition( *pSwCursor->GetPoint() ); + pSwCursor->FillFindPos( eCurr, *pTmp ); + pLinguIter->SetCurr( pTmp ); + + pTmp = new SwPosition( *pTmp ); + pLinguIter->SetCurrX( pTmp ); + } + + if (!pConvArgs && g_pSpellIter) + g_pSpellIter->Start( this, eStart, eEnd ); + if (pConvArgs && g_pConvIter) + g_pConvIter->Start( this, eStart, eEnd ); +} + +void SwEditShell::SpellEnd( SwConversionArgs const *pConvArgs, bool bRestoreSelection ) +{ + if (!pConvArgs && g_pSpellIter && g_pSpellIter->GetSh() == this) + { + g_pSpellIter->End_(bRestoreSelection); + delete g_pSpellIter; + g_pSpellIter = nullptr; + } + if (pConvArgs && g_pConvIter && g_pConvIter->GetSh() == this) + { + g_pConvIter->End_(); + delete g_pConvIter; + g_pConvIter = nullptr; + } +} + +/// @returns SPL_ return values as in splchk.hxx +uno::Any SwEditShell::SpellContinue( + sal_uInt16* pPageCnt, sal_uInt16* pPageSt, + SwConversionArgs const *pConvArgs ) +{ + uno::Any aRes; + + if ((!pConvArgs && g_pSpellIter->GetSh() != this) || + ( pConvArgs && g_pConvIter->GetSh() != this)) + return aRes; + + if( pPageCnt && !*pPageCnt ) + { + sal_uInt16 nEndPage = GetLayout()->GetPageNum(); + nEndPage += nEndPage * 10 / 100; + *pPageCnt = nEndPage; + if( nEndPage ) + ::StartProgress( STR_STATSTR_SPELL, 0, nEndPage, GetDoc()->GetDocShell() ); + } + + OSL_ENSURE( pConvArgs || g_pSpellIter, "SpellIter missing" ); + OSL_ENSURE( !pConvArgs || g_pConvIter, "ConvIter missing" ); + //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all + // Paints are also disabled. + ++mnStartAction; + OUString aRet; + uno::Reference< uno::XInterface > xRet; + if (pConvArgs) + { + g_pConvIter->Continue( pPageCnt, pPageSt ) >>= aRet; + aRes <<= aRet; + } + else + { + g_pSpellIter->Continue( pPageCnt, pPageSt ) >>= xRet; + aRes <<= xRet; + } + --mnStartAction; + + if( !aRet.isEmpty() || xRet.is() ) + { + // then make awt::Selection again visible + StartAction(); + EndAction(); + } + return aRes; +} + +/* Interactive Hyphenation (BP 10.03.93) + * + * 1) HyphStart + * - Revoke all Selections + * - Save current Cursor + * - if no selections existent: + * - create new selection reaching until document end + * 2) HyphContinue + * - add nLastHyphLen onto SelectionStart + * - iterate over all selected areas + * - pDoc->Hyphenate() iterates over all Nodes of a selection + * - pTextNode->Hyphenate() calls SwTextFrame::Hyphenate of the EditShell + * - SwTextFrame:Hyphenate() iterates over all rows of the Pam + * - LineIter::Hyphenate() sets the Hyphenator and the Pam based on + * the to be separated word. + * - Returns true if there is a hyphenation and false if the Pam is processed. + * - If true, show the selected word and set nLastHyphLen. + * - If false, delete current selection and select next one. Returns HYPH_OK if no more. + * 3) InsertSoftHyph (might be called by UI if needed) + * - Place current cursor and add attribute. + * 4) HyphEnd + * - Restore old cursor, EndAction + */ +void SwEditShell::HyphStart( SwDocPositions eStart, SwDocPositions eEnd ) +{ + // do not hyphenate if interactive hyphenation is active elsewhere + if (!g_pHyphIter) + { + g_pHyphIter = new SwHyphIter; + g_pHyphIter->Start( this, eStart, eEnd ); + } +} + +/// restore selections +void SwEditShell::HyphEnd() +{ + assert(g_pHyphIter); + if (g_pHyphIter->GetSh() == this) + { + g_pHyphIter->End(); + delete g_pHyphIter; + g_pHyphIter = nullptr; + } +} + +/// @returns HYPH_CONTINUE if hyphenation, HYPH_OK if selected area was processed. +uno::Reference< uno::XInterface > + SwEditShell::HyphContinue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + assert(g_pHyphIter); + if (g_pHyphIter->GetSh() != this) + return nullptr; + + if( pPageCnt && !*pPageCnt && !*pPageSt ) + { + sal_uInt16 nEndPage = GetLayout()->GetPageNum(); + nEndPage += nEndPage * 10 / 100; + if( nEndPage > 14 ) + { + *pPageCnt = nEndPage; + ::StartProgress( STR_STATSTR_HYPHEN, 0, nEndPage, GetDoc()->GetDocShell()); + } + else // here we once and for all suppress StatLineStartPercent + *pPageSt = 1; + } + + //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all + // Paints are also disabled. + ++mnStartAction; + uno::Reference< uno::XInterface > xRet; + g_pHyphIter->Continue( pPageCnt, pPageSt ) >>= xRet; + --mnStartAction; + + if( xRet.is() ) + g_pHyphIter->ShowSelection(); + + return xRet; +} + +/** Insert soft hyphen + * + * @param nHyphPos Offset in the to be separated word + */ +void SwEditShell::InsertSoftHyph( const sal_Int32 nHyphPos ) +{ + assert(g_pHyphIter); + g_pHyphIter->InsertSoftHyph( nHyphPos ); +} + +/// ignore hyphenation +void SwEditShell::HyphIgnore() +{ + assert(g_pHyphIter); + //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all + // Paints are also disabled. + ++mnStartAction; + g_pHyphIter->Ignore(); + --mnStartAction; + + g_pHyphIter->ShowSelection(); +} + +void SwEditShell::HandleCorrectionError(const OUString& aText, SwPosition aPos, sal_Int32 nBegin, + sal_Int32 nLen, const Point* pPt, + SwRect& rSelectRect) +{ + // save the start and end positions of the line and the starting point + Push(); + LeftMargin(); + const sal_Int32 nLineStart = GetCursor()->GetPoint()->nContent.GetIndex(); + RightMargin(); + const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex(); + Pop(PopMode::DeleteCurrent); + + // make sure the selection build later from the data below does + // not "in word" character to the left and right in order to + // preserve those. Therefore count those "in words" in order to + // modify the selection accordingly. + const sal_Unicode* pChar = aText.getStr(); + sal_Int32 nLeft = 0; + while (*pChar++ == CH_TXTATR_INWORD) + ++nLeft; + pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr; + sal_Int32 nRight = 0; + while (pChar && *pChar-- == CH_TXTATR_INWORD) + ++nRight; + + aPos.nContent = nBegin + nLeft; + SwPaM* pCursor = GetCursor(); + *pCursor->GetPoint() = aPos; + pCursor->SetMark(); + ExtendSelection( true, nLen - nLeft - nRight ); + // don't determine the rectangle in the current line + const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft; + // take one less than the line end - otherwise the next line would be calculated + const sal_Int32 nWordEnd = (nBegin + nLen - nLeft - nRight) > nLineEnd + ? nLineEnd : (nBegin + nLen - nLeft - nRight); + Push(); + pCursor->DeleteMark(); + SwIndex& rContent = GetCursor()->GetPoint()->nContent; + rContent = nWordStart; + SwRect aStartRect; + SwCursorMoveState aState; + aState.m_bRealWidth = true; + SwContentNode* pContentNode = pCursor->GetContentNode(); + std::pair<Point, bool> tmp; + if (pPt) + { + tmp.first = *pPt; + tmp.second = false; + } + SwContentFrame *const pContentFrame = pContentNode->getLayoutFrame(GetLayout(), pCursor->GetPoint(), pPt ? &tmp : nullptr); + + pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); + rContent = nWordEnd - 1; + SwRect aEndRect; + pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState ); + rSelectRect = aStartRect.Union( aEndRect ); + Pop(PopMode::DeleteCurrent); +} + +/** Get a list of potential corrections for misspelled word. + * + * If empty, word is unknown but there are no corrections available. + * If NULL then the word is not misspelled but correct. + * + * @brief SwEditShell::GetCorrection + * @return list or NULL pointer + */ +uno::Reference< XSpellAlternatives > + SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect ) +{ + uno::Reference< XSpellAlternatives > xSpellAlt; + + if( IsTableMode() ) + return nullptr; + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + SwTextNode *pNode = nullptr; + SwWrongList *pWrong = nullptr; + if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState )) + pNode = aPos.nNode.GetNode().GetTextNode(); + if (nullptr == pNode) + pNode = pCursor->GetNode().GetTextNode(); + if (nullptr != pNode) + pWrong = pNode->GetWrong(); + if (nullptr != pWrong && !pNode->IsInProtectSect()) + { + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin)) + { + const OUString aText(pNode->GetText().copy(nBegin, nLen)); + OUString aWord = aText.replaceAll(OUStringChar(CH_TXTATR_BREAKWORD), "") + .replaceAll(OUStringChar(CH_TXTATR_INWORD), ""); + + uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() ); + if( xSpell.is() ) + { + LanguageType eActLang = pNode->GetLang( nBegin, nLen ); + if( xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) )) + { + // restrict the maximal number of suggestions displayed + // in the context menu. + // Note: That could of course be done by clipping the + // resulting sequence but the current third party + // implementations result differs greatly if the number of + // suggestions to be returned gets changed. Statistically + // it gets much better if told to return e.g. only 7 strings + // than returning e.g. 16 suggestions and using only the + // first 7. Thus we hand down the value to use to that + // implementation here by providing an additional parameter. + Sequence< PropertyValue > aPropVals(1); + PropertyValue &rVal = aPropVals.getArray()[0]; + rVal.Name = UPN_MAX_NUMBER_OF_SUGGESTIONS; + rVal.Value <<= sal_Int16(7); + + xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals ); + } + } + + if ( xSpellAlt.is() ) // error found? + { + HandleCorrectionError( aText, aPos, nBegin, nLen, pPt, rSelectRect ); + } + } + } + return xSpellAlt; +} + +bool SwEditShell::GetGrammarCorrection( + linguistic2::ProofreadingResult /*out*/ &rResult, // the complete result + sal_Int32 /*out*/ &rErrorPosInText, // offset of error position in string that was grammar checked... + sal_Int32 /*out*/ &rErrorIndexInResult, // index of error in rResult.aGrammarErrors + uno::Sequence< OUString > /*out*/ &rSuggestions, // suggestions to be used for the error found + const Point *pPt, SwRect &rSelectRect ) +{ + bool bRes = false; + + if( IsTableMode() ) + return bRes; + + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + SwTextNode *pNode = nullptr; + SwGrammarMarkUp *pWrong = nullptr; + if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState )) + pNode = aPos.nNode.GetNode().GetTextNode(); + if (nullptr == pNode) + pNode = pCursor->GetNode().GetTextNode(); + if (nullptr != pNode) + pWrong = pNode->GetGrammarCheck(); + if (nullptr != pWrong && !pNode->IsInProtectSect()) + { + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + if (pWrong->InWrongWord(nBegin, nLen)) + { + const OUString aText(pNode->GetText().copy(nBegin, nLen)); + + uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( mxDoc->GetGCIterator() ); + if (xGCIterator.is()) + { + uno::Reference< lang::XComponent > xDoc = mxDoc->GetDocShell()->GetBaseModel(); + + // Expand the string: + const ModelToViewHelper aConversionMap(*pNode, GetLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + // get XFlatParagraph to use... + uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNode, aExpandText, aConversionMap ); + + // get error position of cursor in XFlatParagraph + rErrorPosInText = aConversionMap.ConvertToViewPosition( nBegin ); + + const sal_Int32 nStartOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceStart( nBegin ) ); + const sal_Int32 nEndOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceEnd( nBegin ) ); + + rResult = xGCIterator->checkSentenceAtPosition( + xDoc, xFlatPara, aExpandText, lang::Locale(), nStartOfSentence, + nEndOfSentence == COMPLETE_STRING ? aExpandText.getLength() : nEndOfSentence, + rErrorPosInText ); + bRes = true; + + // get suggestions to use for the specific error position + rSuggestions.realloc( 0 ); + // return suggestions for first error that includes the given error position + auto pError = std::find_if(rResult.aErrors.begin(), rResult.aErrors.end(), + [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) { + return rError.nErrorStart <= rErrorPosInText + && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength; }); + if (pError != rResult.aErrors.end()) + { + rSuggestions = pError->aSuggestions; + rErrorIndexInResult = static_cast<sal_Int32>(std::distance(rResult.aErrors.begin(), pError)); + } + } + + if (rResult.aErrors.hasElements()) // error found? + { + HandleCorrectionError( aText, aPos, nBegin, nLen, pPt, rSelectRect ); + } + } + } + + return bRes; +} + +bool SwEditShell::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck) +{ + OSL_ENSURE( g_pSpellIter, "SpellIter missing" ); + if (!g_pSpellIter) + return false; + bool bRet = g_pSpellIter->SpellSentence(rPortions, bIsGrammarCheck); + + // make Selection visible - this should simply move the + // cursor to the end of the sentence + StartAction(); + EndAction(); + return bRet; +} + +///make SpellIter start with the current sentence when called next time +void SwEditShell::PutSpellingToSentenceStart() +{ + OSL_ENSURE( g_pSpellIter, "SpellIter missing" ); + if (!g_pSpellIter) + return; + g_pSpellIter->ToSentenceStart(); +} + +static sal_uInt32 lcl_CountRedlines(const svx::SpellPortions& rLastPortions) +{ + return static_cast<sal_uInt32>(std::count_if(rLastPortions.begin(), rLastPortions.end(), + [](const svx::SpellPortion& rPortion) { return rPortion.bIsHidden; })); +} + +void SwEditShell::MoveContinuationPosToEndOfCheckedSentence() +{ + // give hint that continuation position for spell/grammar checking is + // at the end of this sentence + if (g_pSpellIter) + { + g_pSpellIter->SetCurr( new SwPosition( *g_pSpellIter->GetCurrX() ) ); + } +} + +void SwEditShell::ApplyChangedSentence(const svx::SpellPortions& rNewPortions, bool bRecheck) +{ + // Note: rNewPortions.size() == 0 is valid and happens when the whole + // sentence got removed in the dialog + + OSL_ENSURE( g_pSpellIter, "SpellIter missing" ); + if (!g_pSpellIter || + g_pSpellIter->GetLastPortions().empty()) // no portions -> no text to be changed + return; + + const SpellPortions& rLastPortions = g_pSpellIter->GetLastPortions(); + const SpellContentPositions rLastPositions = g_pSpellIter->GetLastPositions(); + OSL_ENSURE(!rLastPortions.empty() && + rLastPortions.size() == rLastPositions.size(), + "last vectors of spelling results are not set or not equal"); + + // iterate over the new portions, beginning at the end to take advantage of the previously + // saved content positions + + mxDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr ); + StartAction(); + + SwPaM *pCursor = GetCursor(); + // save cursor position (which should be at the end of the current sentence) + // for later restoration + Push(); + + sal_uInt32 nRedlinePortions = lcl_CountRedlines(rLastPortions); + if((rLastPortions.size() - nRedlinePortions) == rNewPortions.size()) + { + OSL_ENSURE( !rNewPortions.empty(), "rNewPortions should not be empty here" ); + OSL_ENSURE( !rLastPortions.empty(), "rLastPortions should not be empty here" ); + OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" ); + + // the simple case: the same number of elements on both sides + // each changed element has to be applied to the corresponding source element + svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end(); + SpellPortions::const_iterator aCurrentOldPortion = rLastPortions.end(); + SpellContentPositions::const_iterator aCurrentOldPosition = rLastPositions.end(); + do + { + --aCurrentNewPortion; + --aCurrentOldPortion; + --aCurrentOldPosition; + //jump over redline portions + while(aCurrentOldPortion->bIsHidden) + { + if (aCurrentOldPortion != rLastPortions.begin() && + aCurrentOldPosition != rLastPositions.begin()) + { + --aCurrentOldPortion; + --aCurrentOldPosition; + } + else + { + OSL_FAIL("ApplyChangedSentence: iterator positions broken" ); + break; + } + } + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + pCursor->GetPoint()->nContent = aCurrentOldPosition->nLeft; + pCursor->GetMark()->nContent = aCurrentOldPosition->nRight; + sal_uInt16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion->eLanguage ); + sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE; + switch(nScriptType) + { + case css::i18n::ScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break; + case css::i18n::ScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break; + } + if(aCurrentNewPortion->sText != aCurrentOldPortion->sText) + { + // change text ... + // ... and apply language if necessary + if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage) + SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) ); + mxDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, aCurrentNewPortion->sText, false); + } + else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage) + { + // apply language + SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) ); + } + else if( aCurrentNewPortion->bIgnoreThisError ) + { + // add the 'ignore' markup to the TextNode's grammar ignore markup list + IgnoreGrammarErrorAt( *pCursor ); + OSL_FAIL("TODO: add ignore mark to text node"); + } + } + while(aCurrentNewPortion != rNewPortions.begin()); + } + else + { + OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" ); + + // select the complete sentence + SpellContentPositions::const_iterator aCurrentEndPosition = rLastPositions.end(); + --aCurrentEndPosition; + SpellContentPositions::const_iterator aCurrentStartPosition = rLastPositions.begin(); + pCursor->GetPoint()->nContent = aCurrentStartPosition->nLeft; + pCursor->GetMark()->nContent = aCurrentEndPosition->nRight; + + // delete the sentence completely + mxDoc->getIDocumentContentOperations().DeleteAndJoin(*pCursor); + for(const auto& rCurrentNewPortion : rNewPortions) + { + // set the language attribute + SvtScriptType nScriptType = GetScriptType(); + sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE; + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break; + case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break; + default: break; + } + SfxItemSet aSet(GetAttrPool(), {{nLangWhichId, nLangWhichId}}); + GetCurAttr( aSet ); + const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId)); + if(rLang.GetLanguage() != rCurrentNewPortion.eLanguage) + SetAttrItem( SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId) ); + // insert the new string + mxDoc->getIDocumentContentOperations().InsertString(*pCursor, rCurrentNewPortion.sText); + + // set the cursor to the end of the inserted string + *pCursor->Start() = *pCursor->End(); + } + } + + // restore cursor to the end of the sentence + // (will work also if the sentence length has changed, + // since cursors get updated automatically!) + Pop(PopMode::DeleteCurrent); + + // collapse cursor to the end of the modified sentence + *pCursor->Start() = *pCursor->End(); + if (bRecheck) + { + // in grammar check the current sentence has to be checked again + GoStartSentence(); + } + // set continuation position for spell/grammar checking to the end of this sentence + g_pSpellIter->SetCurr( new SwPosition(*pCursor->Start()) ); + + mxDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr ); + EndAction(); + +} +/** Collect all deleted redlines of the current text node + * beginning at the start of the cursor position + */ +static SpellContentPositions lcl_CollectDeletedRedlines(SwEditShell const * pSh) +{ + SpellContentPositions aRedlines; + SwDoc* pDoc = pSh->GetDoc(); + const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + if ( bShowChg ) + { + SwPaM *pCursor = pSh->GetCursor(); + const SwPosition* pStartPos = pCursor->Start(); + const SwTextNode* pTextNode = pCursor->GetNode().GetTextNode(); + + SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any ); + const sal_Int32 nStartIndex = pStartPos->nContent.GetIndex(); + for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ ) + { + const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ]; + + if ( pRed->Start()->nNode > pTextNode->GetIndex() ) + break; + + if( RedlineType::Delete == pRed->GetType() ) + { + sal_Int32 nStart_, nEnd_; + pRed->CalcStartEnd( pTextNode->GetIndex(), nStart_, nEnd_ ); + sal_Int32 nStart = nStart_; + sal_Int32 nEnd = nEnd_; + if(nStart >= nStartIndex || nEnd >= nStartIndex) + { + SpellContentPosition aAdd; + aAdd.nLeft = nStart; + aAdd.nRight = nEnd; + aRedlines.push_back(aAdd); + } + } + } + } + return aRedlines; +} + +/// remove the redline positions after the current selection +static void lcl_CutRedlines( SpellContentPositions& aDeletedRedlines, SwEditShell const * pSh ) +{ + if(!aDeletedRedlines.empty()) + { + SwPaM *pCursor = pSh->GetCursor(); + const SwPosition* pEndPos = pCursor->End(); + const sal_Int32 nEnd = pEndPos->nContent.GetIndex(); + while(!aDeletedRedlines.empty() && + aDeletedRedlines.back().nLeft > nEnd) + { + aDeletedRedlines.pop_back(); + } + } +} + +static SpellContentPosition lcl_FindNextDeletedRedline( + const SpellContentPositions& rDeletedRedlines, + sal_Int32 nSearchFrom ) +{ + SpellContentPosition aRet; + aRet.nLeft = aRet.nRight = SAL_MAX_INT32; + if(!rDeletedRedlines.empty()) + { + auto aIter = std::find_if_not(rDeletedRedlines.begin(), rDeletedRedlines.end(), + [nSearchFrom](const SpellContentPosition& rPos) { return rPos.nLeft < nSearchFrom; }); + if (aIter != rDeletedRedlines.end()) + aRet = *aIter; + } + return aRet; +} + +bool SwSpellIter::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck) +{ + bool bRet = false; + aLastPortions.clear(); + aLastPositions.clear(); + + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return false; + + OSL_ENSURE( GetEnd(), "SwSpellIter::SpellSentence without Start?"); + + uno::Reference< XSpellAlternatives > xSpellRet; + linguistic2::ProofreadingResult aGrammarResult; + bool bGoOn = true; + bool bGrammarErrorFound = false; + do { + SwPaM *pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + + *pCursor->GetPoint() = *GetCurr(); + *pCursor->GetMark() = *GetEnd(); + + if( bBackToStartOfSentence ) + { + pMySh->GoStartSentence(); + bBackToStartOfSentence = false; + } + uno::Any aSpellRet = + pMySh->GetDoc()->Spell(*pCursor, + xSpeller, nullptr, nullptr, bIsGrammarCheck, pMySh->GetLayout()); + aSpellRet >>= xSpellRet; + aSpellRet >>= aGrammarResult; + bGoOn = GetCursorCnt() > 1; + bGrammarErrorFound = aGrammarResult.aErrors.hasElements(); + if( xSpellRet.is() || bGrammarErrorFound ) + { + bGoOn = false; + SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() ); + SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() ); + + SetCurr( pNewPoint ); + SetCurrX( pNewMark ); + } + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition( *pCursor->GetPoint() ); + SetStart( pNew ); + pNew = new SwPosition( *pCursor->GetMark() ); + SetEnd( pNew ); + pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + } while ( bGoOn ); + + if(xSpellRet.is() || bGrammarErrorFound) + { + // an error has been found + // To fill the spell portions the beginning of the sentence has to be found + SwPaM *pCursor = pMySh->GetCursor(); + // set the mark to the right if necessary + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error + pCursor->DeleteMark(); + pCursor->SetMark(); + bool bStartSent = pMySh->GoStartSentence(); + SpellContentPositions aDeletedRedlines = lcl_CollectDeletedRedlines(pMySh); + if(bStartSent) + { + // create a portion from the start part + AddPortion(nullptr, nullptr, aDeletedRedlines); + } + // Set the cursor to the error already found + *pCursor->GetPoint() = *GetCurrX(); + *pCursor->GetMark() = *GetCurr(); + AddPortion(xSpellRet, &aGrammarResult, aDeletedRedlines); + + // save the end position of the error to continue from here + SwPosition aSaveStartPos = *pCursor->End(); + // determine the end of the current sentence + if ( *pCursor->GetPoint() < *pCursor->GetMark() ) + pCursor->Exchange(); + // again collapse to start marking after the end of the error + pCursor->DeleteMark(); + pCursor->SetMark(); + + pMySh->GoEndSentence(); + if( bGrammarErrorFound ) + { + const ModelToViewHelper aConversionMap(static_cast<SwTextNode&>(pCursor->GetNode()), pMySh->GetLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + sal_Int32 nSentenceEnd = + aConversionMap.ConvertToViewPosition( aGrammarResult.nBehindEndOfSentencePosition ); + // remove trailing space + if( aExpandText[nSentenceEnd - 1] == ' ' ) + --nSentenceEnd; + if( pCursor->End()->nContent.GetIndex() < nSentenceEnd ) + { + pCursor->End()->nContent.Assign( + pCursor->End()->nNode.GetNode().GetContentNode(), nSentenceEnd); + } + } + + lcl_CutRedlines( aDeletedRedlines, pMySh ); + // save the 'global' end of the spellchecking + const SwPosition aSaveEndPos = *GetEnd(); + // set the sentence end as 'local' end + SetEnd( new SwPosition( *pCursor->End() )); + + *pCursor->GetPoint() = aSaveStartPos; + *pCursor->GetMark() = *GetEnd(); + // now the rest of the sentence has to be searched for errors + // for each error the non-error text between the current and the last error has + // to be added to the portions - if necessary broken into same-language-portions + if( !bGrammarErrorFound ) //in grammar check there's only one error returned + { + do + { + xSpellRet = nullptr; + // don't search for grammar errors here anymore! + pMySh->GetDoc()->Spell(*pCursor, + xSpeller, nullptr, nullptr, false, pMySh->GetLayout()) >>= xSpellRet; + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SetCurr( new SwPosition( *pCursor->GetPoint() )); + SetCurrX( new SwPosition( *pCursor->GetMark() )); + + // if an error has been found go back to the text preceding the error + if(xSpellRet.is()) + { + *pCursor->GetPoint() = aSaveStartPos; + *pCursor->GetMark() = *GetCurr(); + } + // add the portion + AddPortion(nullptr, nullptr, aDeletedRedlines); + + if(xSpellRet.is()) + { + *pCursor->GetPoint() = *GetCurr(); + *pCursor->GetMark() = *GetCurrX(); + AddPortion(xSpellRet, nullptr, aDeletedRedlines); + // move the cursor to the end of the error string + *pCursor->GetPoint() = *GetCurrX(); + // and save the end of the error as new start position + aSaveStartPos = *GetCurrX(); + // and the end of the sentence + *pCursor->GetMark() = *GetEnd(); + } + // if the end of the sentence has already been reached then break here + if(*GetCurrX() >= *GetEnd()) + break; + } + while(xSpellRet.is()); + } + else + { + // go to the end of sentence as the grammar check returned it + // at this time the Point is behind the grammar error + // and the mark points to the sentence end as + if ( *pCursor->GetPoint() < *pCursor->GetMark() ) + pCursor->Exchange(); + } + + // the part between the last error and the end of the sentence has to be added + *pMySh->GetCursor()->GetPoint() = *GetEnd(); + if(*GetCurrX() < *GetEnd()) + { + AddPortion(nullptr, nullptr, aDeletedRedlines); + } + // set the shell cursor to the end of the sentence to prevent a visible selection + *pCursor->GetMark() = *GetEnd(); + if( !bIsGrammarCheck ) + { + // set the current position to the end of the sentence + SetCurr( new SwPosition(*GetEnd()) ); + } + // restore the 'global' end + SetEnd( new SwPosition(aSaveEndPos) ); + rPortions = aLastPortions; + bRet = true; + } + else + { + // if no error could be found the selection has to be corrected - at least if it's not in the body + *pMySh->GetCursor()->GetPoint() = *GetEnd(); + pMySh->GetCursor()->DeleteMark(); + } + + return bRet; +} + +void SwSpellIter::ToSentenceStart() +{ + bBackToStartOfSentence = true; +} + +static LanguageType lcl_GetLanguage(SwEditShell& rSh) +{ + SvtScriptType nScriptType = rSh.GetScriptType(); + sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE; + + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break; + case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break; + default: break; + } + SfxItemSet aSet(rSh.GetAttrPool(), {{nLangWhichId, nLangWhichId}}); + rSh.GetCurAttr( aSet ); + const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId)); + return rLang.GetLanguage(); +} + +/// create a text portion at the given position +void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt, + linguistic2::ProofreadingResult* pGrammarResult, + bool bIsField, bool bIsHidden) +{ + svx::SpellPortion aPortion; + OUString sText; + GetSh()->GetSelectedText( sText ); + if(sText.isEmpty()) + return; + + // in case of redlined deletions the selection of an error is not the same as the _real_ word + if(xAlt.is()) + aPortion.sText = xAlt->getWord(); + else if(pGrammarResult) + { + aPortion.bIsGrammarError = true; + if(pGrammarResult->aErrors.hasElements()) + { + aPortion.aGrammarError = pGrammarResult->aErrors[0]; + aPortion.sText = pGrammarResult->aText.copy( aPortion.aGrammarError.nErrorStart, aPortion.aGrammarError.nErrorLength ); + aPortion.xGrammarChecker = pGrammarResult->xProofreader; + auto pProperty = std::find_if(pGrammarResult->aProperties.begin(), pGrammarResult->aProperties.end(), + [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; }); + if (pProperty != pGrammarResult->aProperties.end()) + pProperty->Value >>= aPortion.sDialogTitle; + } + } + else + aPortion.sText = sText; + aPortion.eLanguage = lcl_GetLanguage(*GetSh()); + aPortion.bIsField = bIsField; + aPortion.bIsHidden = bIsHidden; + aPortion.xAlternatives = xAlt; + SpellContentPosition aPosition; + SwPaM *pCursor = GetSh()->GetCursor(); + aPosition.nLeft = pCursor->Start()->nContent.GetIndex(); + aPosition.nRight = pCursor->End()->nContent.GetIndex(); + aLastPortions.push_back(aPortion); + aLastPositions.push_back(aPosition); + +} + +void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt, + linguistic2::ProofreadingResult* pGrammarResult, + const SpellContentPositions& rDeletedRedlines) +{ + SwEditShell *pMySh = GetSh(); + OUString sText; + pMySh->GetSelectedText( sText ); + if(!sText.isEmpty()) + { + if(xAlt.is() || pGrammarResult != nullptr) + { + CreatePortion(xAlt, pGrammarResult, false, false); + } + else + { + SwPaM *pCursor = GetSh()->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + // save the start and end positions + SwPosition aStart(*pCursor->GetPoint()); + SwPosition aEnd(*pCursor->GetMark()); + // iterate over the text to find changes in language + // set the mark equal to the point + *pCursor->GetMark() = aStart; + SwTextNode* pTextNode = pCursor->GetNode().GetTextNode(); + LanguageType eStartLanguage = lcl_GetLanguage(*GetSh()); + SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline( + rDeletedRedlines, aStart.nContent.GetIndex() ); + if( aNextRedline.nLeft == aStart.nContent.GetIndex() ) + { + // select until the end of the current redline + const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ? + aEnd.nContent.GetIndex() : aNextRedline.nRight; + pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd ); + CreatePortion(xAlt, pGrammarResult, false, true); + aStart = *pCursor->End(); + // search for next redline + aNextRedline = lcl_FindNextDeletedRedline( + rDeletedRedlines, aStart.nContent.GetIndex() ); + } + while(*pCursor->GetPoint() < aEnd) + { + // #125786 in table cell with fixed row height the cursor might not move forward + if(!GetSh()->Right(1, CRSR_SKIP_CELLS)) + break; + + bool bField = false; + // read the character at the current position to check if it's a field + sal_Unicode const cChar = + pTextNode->GetText()[pCursor->GetMark()->nContent.GetIndex()]; + if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) + { + const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt( + pCursor->GetMark()->nContent.GetIndex() ); + const sal_uInt16 nWhich = pTextAttr + ? pTextAttr->Which() + : RES_TXTATR_END; + switch (nWhich) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_FTN: + case RES_TXTATR_FLYCNT: + bField = true; + break; + } + } + else if (cChar == CH_TXT_ATR_FORMELEMENT) + { + SwPosition aPos(*pCursor->GetMark()); + bField = pMySh->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos); + } + + LanguageType eCurLanguage = lcl_GetLanguage(*GetSh()); + bool bRedline = aNextRedline.nLeft == pCursor->GetPoint()->nContent.GetIndex(); + // create a portion if the next character + // - is a field, + // - is at the beginning of a deleted redline + // - has a different language + if(bField || bRedline || eCurLanguage != eStartLanguage) + { + eStartLanguage = eCurLanguage; + // go one step back - the cursor currently selects the first character + // with a different language + // in the case of redlining it's different + if(eCurLanguage != eStartLanguage || bField) + *pCursor->GetPoint() = *pCursor->GetMark(); + // set to the last start + *pCursor->GetMark() = aStart; + // create portion should only be called if a selection exists + // there's no selection if there's a field at the beginning + if(*pCursor->Start() != *pCursor->End()) + CreatePortion(xAlt, pGrammarResult, false, false); + aStart = *pCursor->End(); + // now export the field - if there is any + if(bField) + { + *pCursor->GetMark() = *pCursor->GetPoint(); + GetSh()->Right(1, CRSR_SKIP_CELLS); + CreatePortion(xAlt, pGrammarResult, true, false); + aStart = *pCursor->End(); + } + } + // if a redline start then create a portion for it + if(bRedline) + { + *pCursor->GetMark() = *pCursor->GetPoint(); + // select until the end of the current redline + const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ? + aEnd.nContent.GetIndex() : aNextRedline.nRight; + pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd ); + CreatePortion(xAlt, pGrammarResult, false, true); + aStart = *pCursor->End(); + // search for next redline + aNextRedline = lcl_FindNextDeletedRedline( + rDeletedRedlines, aStart.nContent.GetIndex() ); + } + *pCursor->GetMark() = *pCursor->GetPoint(); + } + pCursor->SetMark(); + *pCursor->GetMark() = aStart; + CreatePortion(xAlt, pGrammarResult, false, false); + } + } +} + +void SwEditShell::IgnoreGrammarErrorAt( SwPaM& rErrorPosition ) +{ + SwTextNode *pNode; + SwWrongList *pWrong; + SwNodeIndex aIdx = rErrorPosition.Start()->nNode; + SwNodeIndex aEndIdx = rErrorPosition.Start()->nNode; + sal_Int32 nStart = rErrorPosition.Start()->nContent.GetIndex(); + sal_Int32 nEnd = COMPLETE_STRING; + while( aIdx <= aEndIdx ) + { + pNode = aIdx.GetNode().GetTextNode(); + if( pNode ) { + if( aIdx == aEndIdx ) + nEnd = rErrorPosition.End()->nContent.GetIndex(); + pWrong = pNode->GetGrammarCheck(); + if( pWrong ) + pWrong->RemoveEntry( nStart, nEnd ); + pWrong = pNode->GetWrong(); + if( pWrong ) + pWrong->RemoveEntry( nStart, nEnd ); + SwTextFrame::repaintTextFrames( *pNode ); + } + ++aIdx; + nStart = 0; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/ednumber.cxx b/sw/source/core/edit/ednumber.cxx new file mode 100644 index 000000000..0b96be07b --- /dev/null +++ b/sw/source/core/edit/ednumber.cxx @@ -0,0 +1,912 @@ +/* -*- 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 <editsh.hxx> +#include <edimp.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <swundo.hxx> +#include <numrule.hxx> + +SwPamRanges::SwPamRanges( const SwPaM& rRing ) +{ + for(SwPaM& rTmp : const_cast<SwPaM*>(&rRing)->GetRingContainer()) + Insert( rTmp.GetMark()->nNode, rTmp.GetPoint()->nNode ); +} + +void SwPamRanges::Insert( const SwNodeIndex& rIdx1, const SwNodeIndex& rIdx2 ) +{ + SwPamRange aRg( rIdx1.GetIndex(), rIdx2.GetIndex() ); + if( aRg.nEnd < aRg.nStart ) + { aRg.nStart = aRg.nEnd; aRg.nEnd = rIdx1.GetIndex(); } + + o3tl::sorted_vector<SwPamRange>::const_iterator it = maVector.lower_bound(aRg); //search Insert Position + size_t nPos = it - maVector.begin(); + if (!maVector.empty() && (it != maVector.end()) && (*it) == aRg) + { + // is the one in the Array smaller? + SwPamRange const& rTmp = maVector[nPos]; + if( rTmp.nEnd < aRg.nEnd ) + { + aRg.nEnd = rTmp.nEnd; + maVector.erase(maVector.begin() + nPos); // combine + } + else + return; // done, because by precondition everything is combined + } + + bool bEnd; + do { + bEnd = true; + + // combine with predecessor? + if( nPos > 0 ) + { + SwPamRange const& rTmp = maVector[nPos-1]; + if( rTmp.nEnd == aRg.nStart + || rTmp.nEnd+1 == aRg.nStart ) + { + aRg.nStart = rTmp.nStart; + bEnd = false; + maVector.erase( maVector.begin() + --nPos ); // combine + } + // range contained in rTmp? + else if( rTmp.nStart <= aRg.nStart && aRg.nEnd <= rTmp.nEnd ) + return; + } + // combine with successor? + if( nPos < maVector.size() ) + { + SwPamRange const& rTmp = maVector[nPos]; + if( rTmp.nStart == aRg.nEnd || + rTmp.nStart == aRg.nEnd+1 ) + { + aRg.nEnd = rTmp.nEnd; + bEnd = false; + maVector.erase( maVector.begin() + nPos ); // combine + } + + // range contained in rTmp? + else if( rTmp.nStart <= aRg.nStart && aRg.nEnd <= rTmp.nEnd ) + return; + } + } while( !bEnd ); + + maVector.insert( aRg ); +} + +SwPaM& SwPamRanges::SetPam( size_t nArrPos, SwPaM& rPam ) +{ + assert( nArrPos < Count() ); + const SwPamRange& rTmp = maVector[ nArrPos ]; + rPam.GetPoint()->nNode = rTmp.nStart; + rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), 0 ); + rPam.SetMark(); + rPam.GetPoint()->nNode = rTmp.nEnd; + rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), 0 ); + return rPam; +} + +// Rule book for outline numbering + +void SwEditShell::SetOutlineNumRule(const SwNumRule& rRule) +{ + StartAllAction(); // bracketing for updating! + GetDoc()->SetOutlineNumRule(rRule); + EndAllAction(); +} + +const SwNumRule* SwEditShell::GetOutlineNumRule() const +{ + return GetDoc()->GetOutlineNumRule(); +} + +// Set if there is no numbering yet, else update. +// Works with old and new rules. Update only differences. + +// paragraphs without numbering, with indentations +void SwEditShell::NoNum() +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) // Multiple selection? + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + GetDoc()->NoNum( aRangeArr.SetPam( n, aPam )); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + // sw_redlinehide: leave cursor as is, will be split at Point & apply to new node + GetDoc()->NoNum( *pCursor ); + + EndAllAction(); +} + +bool SwEditShell::SelectionHasNumber() const +{ + bool bResult = HasNumber(); + const SwTextNode * pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode); + if (!bResult && pTextNd && pTextNd->Len()==0 && !pTextNd->GetNumRule()) { + SwPamRanges aRangeArr( *GetCursor() ); + SwPaM aPam( *GetCursor()->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + aRangeArr.SetPam( n, aPam ); + { + sal_uInt32 nStt = aPam.GetPoint()->nNode.GetIndex(), + nEnd = aPam.GetMark()->nNode.GetIndex(); + if( nStt > nEnd ) + { + sal_uInt32 nTmp = nStt; nStt = nEnd; nEnd = nTmp; + } + for (sal_uInt32 nPos = nStt; nPos<=nEnd; nPos++) + { + pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode(); + if (pTextNd) + { + pTextNd = sw::GetParaPropsNode(*GetLayout(), SwNodeIndex(*pTextNd)); + } + if (pTextNd && pTextNd->Len()!=0) + { + bResult = pTextNd->HasNumber(); + + // #b6340308# special case: outline numbered, not counted paragraph + if ( bResult && + pTextNd->GetNumRule() == GetDoc()->GetOutlineNumRule() && + !pTextNd->IsCountedInList() ) + { + bResult = false; + } + if (!bResult) { + break; + } + } + } + } + } + + } + + return bResult; +} + +// add a new function to determine number on/off status +bool SwEditShell::SelectionHasBullet() const +{ + bool bResult = HasBullet(); + const SwTextNode * pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode); + if (!bResult && pTextNd && pTextNd->Len()==0 && !pTextNd->GetNumRule()) { + SwPamRanges aRangeArr( *GetCursor() ); + SwPaM aPam( *GetCursor()->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + aRangeArr.SetPam( n, aPam ); + { + sal_uInt32 nStt = aPam.GetPoint()->nNode.GetIndex(), + nEnd = aPam.GetMark()->nNode.GetIndex(); + if( nStt > nEnd ) + { + sal_uInt32 nTmp = nStt; nStt = nEnd; nEnd = nTmp; + } + for (sal_uInt32 nPos = nStt; nPos<=nEnd; nPos++) + { + pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode(); + if (pTextNd) + { + pTextNd = sw::GetParaPropsNode(*GetLayout(), SwNodeIndex(*pTextNd)); + } + if (pTextNd && pTextNd->Len()!=0) + { + bResult = pTextNd->HasBullet(); + + if (!bResult) { + break; + } + } + } + } + } + } + + return bResult; +} + +// -> #i29560# +bool SwEditShell::HasNumber() const +{ + bool bResult = false; + + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode); + + if (pTextNd) + { + bResult = pTextNd->HasNumber(); + + // special case: outline numbered, not counted paragraph + if ( bResult && + pTextNd->GetNumRule() == GetDoc()->GetOutlineNumRule() && + !pTextNd->IsCountedInList() ) + { + bResult = false; + } + } + + return bResult; +} + +bool SwEditShell::HasBullet() const +{ + bool bResult = false; + + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode); + + if (pTextNd) + { + bResult = pTextNd->HasBullet(); + } + + return bResult; +} +// <- #i29560# + +// delete, split list +void SwEditShell::DelNumRules() +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->IsMultiSelection() ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + GetDoc()->DelNumRules(aRangeArr.SetPam( n, aPam ), GetLayout()); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + GetDoc()->DelNumRules(*pCursor, GetLayout()); + + // Call AttrChangeNotify on the UI-side. Should actually be redundant but there was a bug once. + CallChgLnk(); + + // Cursor cannot be in front of a label anymore, because numbering/bullet is deleted. + SetInFrontOfLabel( false ); + + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +// up- & downgrading +void SwEditShell::NumUpDown( bool bDown ) +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( !pCursor->IsMultiSelection() ) + GetDoc()->NumUpDown(*pCursor, bDown, GetLayout()); + else + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + GetDoc()->NumUpDown(aRangeArr.SetPam( n, aPam ), bDown, GetLayout()); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + GetDoc()->getIDocumentState().SetModified(); + + // #i54693# Update marked numbering levels + if ( IsInFrontOfLabel() ) + UpdateMarkedListLevel(); + + CallChgLnk(); + + EndAllAction(); +} + +bool SwEditShell::IsFirstOfNumRuleAtCursorPos() const +{ + return SwDoc::IsFirstOfNumRuleAtPos(*GetCursor()->GetPoint(), *GetLayout()); +} + +// -> #i23725#, #i90078# +void SwEditShell::ChangeIndentOfAllListLevels( const sal_Int32 nDiff ) +{ + StartAllAction(); + + const SwNumRule *pCurNumRule = GetNumRuleAtCurrCursorPos(); + if ( pCurNumRule != nullptr ) + { + SwNumRule aRule(*pCurNumRule); + const SwNumFormat& aRootNumFormat(aRule.Get(0)); + if( nDiff > 0 || aRootNumFormat.GetIndentAt() + nDiff > 0) // fdo#42708 + { + // #i90078# + aRule.ChangeIndent( nDiff ); + } + // no start of new list + SetCurNumRule( aRule, false ); + } + + EndAllAction(); +} + +// #i90078# +void SwEditShell::SetIndent(short nIndent, const SwPosition & rPos) +{ + StartAllAction(); + + SwPosition pos(rPos); + SwNumRule *pCurNumRule = SwDoc::GetNumRuleAtPos(pos, GetLayout()); + + if (pCurNumRule) + { + SwNumRule aRule(*pCurNumRule); + if ( !IsMultiSelection() && IsFirstOfNumRuleAtCursorPos() ) + { + aRule.SetIndentOfFirstListLevelAndChangeOthers( nIndent ); + } + else + { + const SwTextNode* pTextNode = pos.nNode.GetNode().GetTextNode(); + if ( pTextNode != nullptr + && pTextNode->GetActualListLevel() >= 0 ) + { + aRule.SetIndent( nIndent, static_cast< sal_uInt16 >( pTextNode->GetActualListLevel() ) ); + } + } + + // change numbering rule - changed numbering rule is not applied at <aPaM> + SwPaM aPaM(pos); + GetDoc()->SetNumRule(aPaM, aRule, false, GetLayout(), OUString(), false); + } + + EndAllAction(); +} + +bool SwEditShell::MoveParagraph( long nOffset ) +{ + StartAllAction(); + + SwPaM *pCursor = GetCursor(); + + bool bRet = GetDoc()->MoveParagraph( *pCursor, nOffset ); + + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + return bRet; +} + +int SwEditShell::GetCurrentParaOutlineLevel( ) const +{ + int nLevel = 0; + + SwPaM* pCursor = GetCursor(); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + if (pTextNd) + nLevel = pTextNd->GetAttrOutlineLevel(); + return nLevel; +} + +void SwEditShell::GetCurrentOutlineLevels( sal_uInt8& rUpper, sal_uInt8& rLower ) +{ + SwPaM* pCursor = GetCursor(); + SwPaM aCursor( *pCursor->Start() ); + aCursor.SetMark(); + if( pCursor->HasMark() ) + *aCursor.GetPoint() = *pCursor->End(); + SwDoc::GotoNextNum(*aCursor.GetPoint(), GetLayout(), false, &rUpper, &rLower); +} + +bool SwEditShell::MoveNumParas( bool bUpperLower, bool bUpperLeft ) +{ + StartAllAction(); + + // On all selections? + SwPaM* pCursor = GetCursor(); + SwPaM aCursor( *pCursor->Start() ); + aCursor.SetMark(); + + if( pCursor->HasMark() ) + *aCursor.GetPoint() = *pCursor->End(); + + bool bRet = false; + sal_uInt8 nUpperLevel, nLowerLevel; + if (SwDoc::GotoNextNum( *aCursor.GetPoint(), GetLayout(), false, + &nUpperLevel, &nLowerLevel )) + { + if( bUpperLower ) + { + // on top of the next numbering + long nOffset = 0; + const SwNode* pNd; + + if( bUpperLeft ) // move up + { + SwPosition aPos( *aCursor.GetMark() ); + if (SwDoc::GotoPrevNum( aPos, GetLayout(), false )) + nOffset = aPos.nNode.GetIndex() - + aCursor.GetMark()->nNode.GetIndex(); + else + { + sal_uLong nStt = aPos.nNode.GetIndex(), nIdx = nStt - 1; + + if (SwTextNode const*const pStt = aPos.nNode.GetNode().GetTextNode()) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*GetLayout(), *pStt)); + nIdx = nodes.first->GetIndex() - 1; + } + while( nIdx && ( + ( pNd = GetDoc()->GetNodes()[ nIdx ])->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode()))) + --nIdx; + if( GetDoc()->GetNodes()[ nIdx ]->IsTextNode() ) + nOffset = nIdx - nStt; + } + } + else // move down + { + assert(!aCursor.GetNode().IsTextNode() + || sw::IsParaPropsNode(*GetLayout(), *aCursor.GetNode().GetTextNode())); + const SwNumRule* pOrig = sw::GetParaPropsNode(*GetLayout(), *aCursor.GetNode(false).GetTextNode())->GetNumRule(); + if( aCursor.GetNode().IsTextNode() && + pOrig == aCursor.GetNode().GetTextNode()->GetNumRule() ) + { + sal_uLong nStt = aCursor.GetPoint()->nNode.GetIndex(), nIdx = nStt+1; + if (SwTextNode const*const pStt = aCursor.GetPoint()->nNode.GetNode().GetTextNode()) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*GetLayout(), *pStt)); + nIdx = nodes.second->GetIndex() + 1; + } + + while (nIdx < GetDoc()->GetNodes().Count()-1) + { + pNd = GetDoc()->GetNodes()[ nIdx ]; + + if (pNd->IsSectionNode() || + (pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode())) + { + ++nIdx; + } + else if (pNd->IsTextNode()) + { + SwTextNode const*const pTextNode = + sw::GetParaPropsNode(*GetLayout(), SwNodeIndex(*pNd)); + if (pOrig == pTextNode->GetNumRule() + && pTextNode->GetActualListLevel() > nUpperLevel) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*GetLayout(), *pTextNode)); + nIdx = nodes.second->GetIndex() + 1; + } + else + { + break; + } + } + // #i57856# + else + { + break; + } + } + + if( nStt == nIdx || !GetDoc()->GetNodes()[ nIdx ]->IsTextNode() ) + nOffset = 1; + else + nOffset = nIdx - nStt; + } + else + nOffset = 1; + } + + if( nOffset ) + { + aCursor.Move( fnMoveBackward, GoInNode ); + bRet = GetDoc()->MoveParagraph( aCursor, nOffset ); + } + } + else if( (bUpperLeft ? nUpperLevel : nLowerLevel+1) < MAXLEVEL ) + { + aCursor.Move( fnMoveBackward, GoInNode ); + bRet = GetDoc()->NumUpDown(aCursor, !bUpperLeft, GetLayout()); + } + } + + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + return bRet; +} + +bool SwEditShell::OutlineUpDown( short nOffset ) +{ + StartAllAction(); + + bool bRet = true; + SwPaM* pCursor = GetCursor(); + if( !pCursor->IsMultiSelection() ) + bRet = GetDoc()->OutlineUpDown(*pCursor, nOffset, GetLayout()); + else + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + bRet = bRet && GetDoc()->OutlineUpDown( + aRangeArr.SetPam(n, aPam), nOffset, GetLayout()); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + return bRet; +} + +bool SwEditShell::MoveOutlinePara( SwOutlineNodes::difference_type nOffset ) +{ + StartAllAction(); + bool bRet = GetDoc()->MoveOutlinePara( *GetCursor(), nOffset ); + EndAllAction(); + return bRet; +} + +// Outlines and SubOutline are ReadOnly? +bool SwEditShell::IsProtectedOutlinePara() const +{ + bool bRet = false; + const SwNode& rNd = GetCursor()->Start()->nNode.GetNode(); + if( rNd.IsTextNode() ) + { + const SwOutlineNodes& rOutlNd = GetDoc()->GetNodes().GetOutLineNds(); + SwNodePtr pNd = const_cast<SwNodePtr>(&rNd); + bool bFirst = true; + SwOutlineNodes::size_type nPos; + int nLvl(0); + if( !rOutlNd.Seek_Entry( pNd, &nPos ) && nPos ) + --nPos; + + for( ; nPos < rOutlNd.size(); ++nPos ) + { + SwNodePtr pTmpNd = rOutlNd[ nPos ]; + + if (!sw::IsParaPropsNode(*GetLayout(), *pTmpNd->GetTextNode())) + { + continue; + } + + int nTmpLvl = pTmpNd->GetTextNode()->GetAttrOutlineLevel(); + + OSL_ENSURE( nTmpLvl >= 0 && nTmpLvl <= MAXLEVEL, + "<SwEditShell::IsProtectedOutlinePara()>" ); + + if( bFirst ) + { + nLvl = nTmpLvl; + bFirst = false; + } + else if( nLvl >= nTmpLvl ) + break; + + if( pTmpNd->IsProtect() ) + { + bRet = true; + break; + } + } + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL("Cursor not on an outline node"); + } +#endif + return bRet; +} + +/** Test whether outline may be moved (bCopy == false) + * or copied (bCopy == true) + * Verify these conditions: + * 1) outline must be within main body (and not in redline) + * 2) outline must not be within table + * 3) if bCopy is set, outline must not be write protected + */ +static bool lcl_IsOutlineMoveAndCopyable(SwEditShell const& rShell, + SwOutlineNodes::size_type const nIdx, bool const bCopy) +{ + const SwNodes& rNds = rShell.GetDoc()->GetNodes(); + const SwNode* pNd = rNds.GetOutLineNds()[ nIdx ]; + return pNd->GetIndex() >= rNds.GetEndOfExtras().GetIndex() && // 1) body + !pNd->FindTableNode() && // 2) table + sw::IsParaPropsNode(*rShell.GetLayout(), *pNd->GetTextNode()) && + ( bCopy || !pNd->IsProtect() ); // 3) write +} + +bool SwEditShell::IsOutlineMovable( SwOutlineNodes::size_type nIdx ) const +{ + return lcl_IsOutlineMoveAndCopyable( *this, nIdx, false ); +} + +bool SwEditShell::IsOutlineCopyable( SwOutlineNodes::size_type nIdx ) const +{ + return lcl_IsOutlineMoveAndCopyable( *this, nIdx, true ); +} + +bool SwEditShell::NumOrNoNum( + bool bNumOn, + bool bChkStart ) +{ + bool bRet = false; + + if ( !IsMultiSelection() + && !HasSelection() + && ( !bChkStart || IsSttPara() ) ) + { + StartAllAction(); + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint())); + bRet = GetDoc()->NumOrNoNum(pos.nNode, !bNumOn); + EndAllAction(); + } + return bRet; +} + +bool SwEditShell::IsNoNum( bool bChkStart ) const +{ + // a Backspace in the paragraph without number becomes a Delete + bool bResult = false; + + if ( !IsMultiSelection() + && !HasSelection() + && ( !bChkStart || IsSttPara() ) ) + { + const SwTextNode* pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode); + if ( pTextNd != nullptr ) + { + bResult = !pTextNd->IsCountedInList(); + } + } + + return bResult; +} + +sal_uInt8 SwEditShell::GetNumLevel() const +{ + // return current level where the point of the cursor is + sal_uInt8 nLevel = MAXLEVEL; + + SwPaM* pCursor = GetCursor(); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + + OSL_ENSURE( pTextNd, "GetNumLevel() without text node" ); + if ( pTextNd == nullptr ) + return nLevel; + + const SwNumRule* pRule = pTextNd->GetNumRule(); + if ( pRule != nullptr ) + { + const int nListLevelOfTextNode( pTextNd->GetActualListLevel() ); + if ( nListLevelOfTextNode >= 0 ) + { + nLevel = static_cast<sal_uInt8>( nListLevelOfTextNode ); + } + } + + return nLevel; +} + +const SwNumRule* SwEditShell::GetNumRuleAtCurrCursorPos() const +{ + SwPosition pos(*GetCursor()->GetPoint()); + return SwDoc::GetNumRuleAtPos( pos, GetLayout() ); +} + +const SwNumRule* SwEditShell::GetNumRuleAtCurrentSelection() const +{ + const SwNumRule* pNumRuleAtCurrentSelection = nullptr; + + bool bDifferentNumRuleFound = false; + for(const SwPaM& rCurrentCursor : GetCursor()->GetRingContainer()) + { + const SwNodeIndex aEndNode = rCurrentCursor.End()->nNode; + + for ( SwNodeIndex aNode = rCurrentCursor.Start()->nNode; aNode <= aEndNode; ++aNode ) + { + SwPosition pos(aNode); + const SwNumRule* pNumRule = SwDoc::GetNumRuleAtPos(pos, GetLayout()); + if ( pNumRule == nullptr ) + { + continue; + } + else if ( pNumRule != pNumRuleAtCurrentSelection ) + { + if ( pNumRuleAtCurrentSelection == nullptr ) + { + pNumRuleAtCurrentSelection = pNumRule; + } + else + { + pNumRuleAtCurrentSelection = nullptr; + bDifferentNumRuleFound = true; + break; + } + } + } + if(bDifferentNumRuleFound) + break; + } + + return pNumRuleAtCurrentSelection; +} + +void SwEditShell::SetCurNumRule( const SwNumRule& rRule, + bool bCreateNewList, + const OUString& rContinuedListId, + const bool bResetIndentAttrs ) +{ + StartAllAction(); + + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + SwPaM* pCursor = GetCursor(); + if( IsMultiSelection() ) + { + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + OUString sContinuedListId(rContinuedListId); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + aRangeArr.SetPam( n, aPam ); + OUString sListId = GetDoc()->SetNumRule( aPam, rRule, + bCreateNewList, GetLayout(), sContinuedListId, + true, bResetIndentAttrs ); + + //tdf#87548 On creating a new list for a multi-selection only + //create a single new list for the multi-selection, not one per selection + if (bCreateNewList) + { + sContinuedListId = sListId; + bCreateNewList = false; + } + + GetDoc()->SetCounted(aPam, true, GetLayout()); + } + } + else + { + GetDoc()->SetNumRule( *pCursor, rRule, + bCreateNewList, GetLayout(), rContinuedListId, + true, bResetIndentAttrs ); + GetDoc()->SetCounted( *pCursor, true, GetLayout() ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + + EndAllAction(); +} + +OUString SwEditShell::GetUniqueNumRuleName() const +{ + return GetDoc()->GetUniqueNumRuleName(); +} + +void SwEditShell::ChgNumRuleFormats( const SwNumRule& rRule ) +{ + StartAllAction(); + GetDoc()->ChgNumRuleFormats( rRule ); + EndAllAction(); +} + +void SwEditShell::ReplaceNumRule( const OUString& rOldRule, const OUString& rNewRule ) +{ + StartAllAction(); + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint())); + GetDoc()->ReplaceNumRule( pos, rOldRule, rNewRule ); + EndAllAction(); +} + +void SwEditShell::SetNumRuleStart( bool bFlag, SwPaM* pPaM ) +{ + StartAllAction(); + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + if( pCursor->IsMultiSelection() ) // multiple selection ? + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *aRangeArr.SetPam( n, aPam ).GetPoint())); + GetDoc()->SetNumRuleStart( pos, bFlag ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint())); + GetDoc()->SetNumRuleStart(pos, bFlag); + } + + EndAllAction(); +} + +bool SwEditShell::IsNumRuleStart( SwPaM* pPaM ) const +{ + SwPaM* pCursor = pPaM ? pPaM : GetCursor( ); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + return pTextNd && pTextNd->IsListRestart(); +} + +void SwEditShell::SetNodeNumStart( sal_uInt16 nStt ) +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->IsMultiSelection() ) // multiple selection ? + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *aRangeArr.SetPam( n, aPam ).GetPoint())); + GetDoc()->SetNodeNumStart( pos, nStt ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *pCursor->GetPoint())); + GetDoc()->SetNodeNumStart( pos, nStt ); + } + + EndAllAction(); +} + +sal_uInt16 SwEditShell::GetNodeNumStart( SwPaM* pPaM ) const +{ + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->nNode); + // correction: check, if list restart value is set at text node and + // use new method <SwTextNode::GetAttrListRestartValue()>. + // return USHRT_MAX, if no list restart value is found. + if ( pTextNd && pTextNd->HasAttrListRestartValue() ) + { + return static_cast<sal_uInt16>(pTextNd->GetAttrListRestartValue()); + } + return USHRT_MAX; +} + +const SwNumRule * SwEditShell::SearchNumRule( const bool bNum, + OUString& sListId ) +{ + return GetDoc()->SearchNumRule( *(GetCursor()->Start()), + false/*bForward*/, bNum, false/*bOutline*/, -1/*nNonEmptyAllowe*/, + sListId, GetLayout() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edredln.cxx b/sw/source/core/edit/edredln.cxx new file mode 100644 index 000000000..f65f6319c --- /dev/null +++ b/sw/source/core/edit/edredln.cxx @@ -0,0 +1,153 @@ +/* -*- 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 <docary.hxx> +#include <redline.hxx> +#include <doc.hxx> +#include <editsh.hxx> +#include <frmtool.hxx> + +RedlineFlags SwEditShell::GetRedlineFlags() const +{ + return GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); +} + +void SwEditShell::SetRedlineFlags( RedlineFlags eMode ) +{ + if( eMode != GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eMode ); + EndAllAction(); + } +} + +bool SwEditShell::IsRedlineOn() const +{ + return GetDoc()->getIDocumentRedlineAccess().IsRedlineOn(); +} + +SwRedlineTable::size_type SwEditShell::GetRedlineCount() const +{ + return GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size(); +} + +const SwRangeRedline& SwEditShell::GetRedline( SwRedlineTable::size_type nPos ) const +{ + return *GetDoc()->getIDocumentRedlineAccess().GetRedlineTable()[ nPos ]; +} + +static void lcl_InvalidateAll( SwViewShell* pSh ) +{ + for(SwViewShell& rCurrentShell : pSh->GetRingContainer()) + { + if ( rCurrentShell.GetWin() ) + rCurrentShell.GetWin()->Invalidate(); + } +} + +bool SwEditShell::AcceptRedline( SwRedlineTable::size_type nPos ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + bool bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( nPos, true ); + if( !nPos && !::IsExtraData( GetDoc() ) ) + lcl_InvalidateAll( this ); + EndAllAction(); + return bRet; +} + +bool SwEditShell::RejectRedline( SwRedlineTable::size_type nPos ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + bool bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( nPos, true ); + if( !nPos && !::IsExtraData( GetDoc() ) ) + lcl_InvalidateAll( this ); + EndAllAction(); + return bRet; +} + +bool SwEditShell::AcceptRedlinesInSelection() +{ + SET_CURR_SHELL( this ); + StartAllAction(); + bool bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( *GetCursor(), true ); + EndAllAction(); + return bRet; +} + +bool SwEditShell::RejectRedlinesInSelection() +{ + SET_CURR_SHELL( this ); + StartAllAction(); + bool bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( *GetCursor(), true ); + EndAllAction(); + return bRet; +} + +// Set the comment at the Redline +bool SwEditShell::SetRedlineComment( const OUString& rS ) +{ + bool bRet = false; + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + bRet = bRet || GetDoc()->getIDocumentRedlineAccess().SetRedlineComment( rPaM, rS ); + } + + return bRet; +} + +const SwRangeRedline* SwEditShell::GetCurrRedline() const +{ + if (const SwRangeRedline* pRed = GetDoc()->getIDocumentRedlineAccess().GetRedline( *GetCursor()->GetPoint(), nullptr )) + return pRed; + // check the other side of the selection to handle completely selected changes, where the Point is at the end + return GetDoc()->getIDocumentRedlineAccess().GetRedline( *GetCursor()->GetMark(), nullptr ); +} + +void SwEditShell::UpdateRedlineAttr() +{ + if( IDocumentRedlineAccess::IsShowChanges(GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags()) ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + + GetDoc()->getIDocumentRedlineAccess().UpdateRedlineAttr(); + + EndAllAction(); + } +} + +/** Search the Redline of the data given + * + * @return Returns the Pos of the Array, or SwRedlineTable::npos if not present + */ +SwRedlineTable::size_type SwEditShell::FindRedlineOfData( const SwRedlineData& rData ) const +{ + const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + + for( SwRedlineTable::size_type i = 0, nCnt = rTable.size(); i < nCnt; ++i ) + if( &rTable[ i ]->GetRedlineData() == &rData ) + return i; + return SwRedlineTable::npos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edsect.cxx b/sw/source/core/edit/edsect.cxx new file mode 100644 index 000000000..ba765eee4 --- /dev/null +++ b/sw/source/core/edit/edsect.cxx @@ -0,0 +1,421 @@ +/* -*- 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 <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <pam.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <section.hxx> +#include <sectfrm.hxx> +#include <cntfrm.hxx> +#include <tabfrm.hxx> +#include <rootfrm.hxx> + +SwSection const* +SwEditShell::InsertSection( + SwSectionData & rNewData, SfxItemSet const*const pAttr) +{ + const SwSection* pRet = nullptr; + if( !IsTableMode() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSSECTION, nullptr ); + + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwSection const*const pNew = + GetDoc()->InsertSwSection( rPaM, rNewData, nullptr, pAttr ); + if( !pRet ) + pRet = pNew; + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSSECTION, nullptr ); + EndAllAction(); + } + return pRet; +} + +bool SwEditShell::IsInsRegionAvailable() const +{ + if( IsTableMode() ) + return false; + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) + return false; + if( pCursor->HasMark() ) + return 0 != SwDoc::IsInsRegionAvailable( *pCursor ); + + return true; +} + +const SwSection* SwEditShell::GetCurrSection() const +{ + if( IsTableMode() ) + return nullptr; + + return SwDoc::GetCurrSection( *GetCursor()->GetPoint() ); +} + +/** Deliver the responsible area of the columns. + * + * In footnotes it may not be the area within the footnote. + */ +SwSection* SwEditShell::GetAnySection( bool bOutOfTab, const Point* pPt ) +{ + SwFrame *pFrame; + if ( pPt ) + { + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( *pPt ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt ); + SwContentNode *pNd = aPos.nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(*pPt, true); + pFrame = pNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + } + else + pFrame = GetCurrFrame( false ); + + if( bOutOfTab && pFrame ) + pFrame = pFrame->FindTabFrame(); + if( pFrame && pFrame->IsInSct() ) + { + SwSectionFrame* pSect = pFrame->FindSctFrame(); + OSL_ENSURE( pSect, "GetAnySection: Where's my Sect?" ); + if( pSect->IsInFootnote() && pSect->GetUpper()->IsInSct() ) + { + pSect = pSect->GetUpper()->FindSctFrame(); + OSL_ENSURE( pSect, "GetAnySection: Where's my SectFrame?" ); + } + return pSect->GetSection(); + } + return nullptr; +} + +size_t SwEditShell::GetSectionFormatCount() const +{ + return GetDoc()->GetSections().size(); +} + +bool SwEditShell::IsAnySectionInDoc() const +{ + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + + for( const SwSectionFormat* pFormat : rFormats ) + { + SectionType eTmpType; + if( pFormat->IsInNodesArr() && + ( (eTmpType = pFormat->GetSection()->GetType()) != SectionType::ToxContent + && SectionType::ToxHeader != eTmpType ) ) + { + return true; + } + } + return false; +} + +size_t SwEditShell::GetSectionFormatPos( const SwSectionFormat& rFormat ) const +{ + SwSectionFormat* pFormat = const_cast<SwSectionFormat*>(&rFormat); + return GetDoc()->GetSections().GetPos( pFormat ); +} + +const SwSectionFormat& SwEditShell::GetSectionFormat(size_t nFormat) const +{ + return *GetDoc()->GetSections()[ nFormat ]; +} + +void SwEditShell::DelSectionFormat(size_t nFormat) +{ + StartAllAction(); + GetDoc()->DelSectionFormat( GetDoc()->GetSections()[ nFormat ] ); + // Call the AttrChangeNotify on the UI page. + CallChgLnk(); + EndAllAction(); +} + +void SwEditShell::UpdateSection(size_t const nSect, + SwSectionData & rNewData, SfxItemSet const*const pAttr) +{ + StartAllAction(); + GetDoc()->UpdateSection( nSect, rNewData, pAttr ); + // Call the AttrChangeNotify on the UI page. + CallChgLnk(); + EndAllAction(); +} + +OUString SwEditShell::GetUniqueSectionName( const OUString* pChkStr ) const +{ + return GetDoc()->GetUniqueSectionName( pChkStr ); +} + +void SwEditShell::SetSectionAttr( const SfxItemSet& rSet, + SwSectionFormat* pSectFormat ) +{ + if( pSectFormat ) + SetSectionAttr_( *pSectFormat, rSet ); + else + { + // for all section in the selection + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + const SwPosition* pStt = rPaM.Start(), + * pEnd = rPaM.End(); + + SwSectionNode* pSttSectNd = pStt->nNode.GetNode().FindSectionNode(), + * pEndSectNd = pEnd->nNode.GetNode().FindSectionNode(); + + if( pSttSectNd || pEndSectNd ) + { + if( pSttSectNd ) + SetSectionAttr_( *pSttSectNd->GetSection().GetFormat(), + rSet ); + if( pEndSectNd && pSttSectNd != pEndSectNd ) + SetSectionAttr_( *pEndSectNd->GetSection().GetFormat(), + rSet ); + + if( pSttSectNd && pEndSectNd ) + { + SwNodeIndex aSIdx( pStt->nNode ); + SwNodeIndex aEIdx( pEnd->nNode ); + if( pSttSectNd->EndOfSectionIndex() < + pEndSectNd->GetIndex() ) + { + aSIdx = pSttSectNd->EndOfSectionIndex() + 1; + aEIdx = *pEndSectNd; + } + + while( aSIdx < aEIdx ) + { + if( nullptr != (pSttSectNd = aSIdx.GetNode().GetSectionNode()) + || ( aSIdx.GetNode().IsEndNode() && + nullptr != ( pSttSectNd = aSIdx.GetNode(). + StartOfSectionNode()->GetSectionNode())) ) + SetSectionAttr_( *pSttSectNd->GetSection().GetFormat(), + rSet ); + ++aSIdx; + } + } + } + + } + } +} + +void SwEditShell::SetSectionAttr_( SwSectionFormat& rSectFormat, + const SfxItemSet& rSet ) +{ + StartAllAction(); + if(SfxItemState::SET == rSet.GetItemState(RES_CNTNT, false)) + { + SfxItemSet aSet(rSet); + aSet.ClearItem(RES_CNTNT); + GetDoc()->SetAttr( aSet, rSectFormat ); + } + else + GetDoc()->SetAttr( rSet, rSectFormat ); + + // Call the AttrChangeNotify on the UI page. + CallChgLnk(); + EndAllAction(); +} + +/** Search inside the cursor selection for full selected sections. + * + * @return If any part of section in the selection return 0, if more than one return the count. + */ +sal_uInt16 SwEditShell::GetFullSelectedSectionCount() const +{ + sal_uInt16 nRet = 0; + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + + const SwPosition* pStt = rPaM.Start(), + * pEnd = rPaM.End(); + const SwContentNode* pCNd; + // check the selection, if Start at Node begin and End at Node end + if( pStt->nContent.GetIndex() || + ( nullptr == ( pCNd = pEnd->nNode.GetNode().GetContentNode() )) || + pCNd->Len() != pEnd->nContent.GetIndex() ) + { + nRet = 0; + break; + } + +// !!! +// what about table at start or end ? +// There is no selection possible! +// What about only a table inside the section ? +// There is only a table selection possible! + + SwNodeIndex aSIdx( pStt->nNode, -1 ), aEIdx( pEnd->nNode, +1 ); + if( !aSIdx.GetNode().IsSectionNode() || + !aEIdx.GetNode().IsEndNode() || + !aEIdx.GetNode().StartOfSectionNode()->IsSectionNode() ) + { + nRet = 0; + break; + } + + ++nRet; + if( &aSIdx.GetNode() != aEIdx.GetNode().StartOfSectionNode() ) + ++nRet; + + } + return nRet; +} + +/** Find the suitable node for a special insert (alt-enter). + * + * This should enable inserting text before/after sections and tables. + * + * A node is found if: + * 1) the innermost table/section is not in a write-protected area + * 2) pCurrentPos is at or just before an end node + * (or at or just after a start node) + * 3) there are only start/end nodes between pCurrentPos and the innermost + * table/section + * + * If a suitable node is found, an SwNode* is returned; else it is NULL. + */ +static const SwNode* lcl_SpecialInsertNode(const SwPosition* pCurrentPos) +{ + const SwNode* pReturn = nullptr; + + // the current position + OSL_ENSURE( pCurrentPos != nullptr, "Strange, we have no position!" ); + const SwNode& rCurrentNode = pCurrentPos->nNode.GetNode(); + + // find innermost section or table. At the end of this scope, + // pInntermostNode contain the section/table before/after which we should + // insert our empty paragraph, or it will be NULL if none is found. + const SwNode* pInnermostNode = nullptr; + { + const SwNode* pTableNode = rCurrentNode.FindTableNode(); + const SwNode* pSectionNode = rCurrentNode.FindSectionNode(); + + // find the table/section which is close + if( pTableNode == nullptr ) + pInnermostNode = pSectionNode; + else if ( pSectionNode == nullptr ) + pInnermostNode = pTableNode; + else + { + // compare and choose the larger one + pInnermostNode = + ( pSectionNode->GetIndex() > pTableNode->GetIndex() ) + ? pSectionNode : pTableNode; + } + } + + // The previous version had a check to skip empty read-only sections. Those + // shouldn't occur, so we only need to check whether our pInnermostNode is + // inside a protected area. + + // Now, pInnermostNode is NULL or the innermost section or table node. + if( (pInnermostNode != nullptr) && !pInnermostNode->IsProtect() ) + { + OSL_ENSURE( pInnermostNode->IsTableNode() || + pInnermostNode->IsSectionNode(), "wrong node found" ); + OSL_ENSURE( ( pInnermostNode->GetIndex() <= rCurrentNode.GetIndex() )&& + ( pInnermostNode->EndOfSectionNode()->GetIndex() >= + rCurrentNode.GetIndex() ), "wrong node found" ); + + // we now need to find the possible start/end positions + + // we found a start if + // - we're at or just before a start node + // - there are only start nodes between the current and pInnermostNode + SwNodeIndex aBegin( pCurrentPos->nNode ); + if( rCurrentNode.IsContentNode() && + (pCurrentPos->nContent.GetIndex() == 0)) + --aBegin; + while( (aBegin != pInnermostNode->GetIndex()) && + aBegin.GetNode().IsStartNode() ) + --aBegin; + bool bStart = ( aBegin == pInnermostNode->GetIndex() ); + + // we found an end if + // - we're at or just before an end node + // - there are only end nodes between the current node and + // pInnermostNode's end node + SwNodeIndex aEnd( pCurrentPos->nNode ); + if( rCurrentNode.IsContentNode() && + ( pCurrentPos->nContent.GetIndex() == + rCurrentNode.GetContentNode()->Len() ) ) + ++aEnd; + while( (aEnd != pInnermostNode->EndOfSectionNode()->GetIndex()) && + aEnd.GetNode().IsEndNode() ) + ++aEnd; + bool bEnd = ( aEnd == pInnermostNode->EndOfSectionNode()->GetIndex() ); + + // evaluate result: if both start + end, end is preferred + if( bEnd ) + pReturn = pInnermostNode->EndOfSectionNode(); + else if ( bStart ) + pReturn = pInnermostNode; + } + + OSL_ENSURE( ( pReturn == nullptr ) || pReturn->IsStartNode() || + pReturn->IsEndNode(), + "SpecialInsertNode failed" ); + return pReturn; +} + +/** a node can be special-inserted (alt-Enter) whenever lcl_SpecialInsertNode + finds a suitable position +*/ +bool SwEditShell::CanSpecialInsert() const +{ + return nullptr != lcl_SpecialInsertNode( GetCursor()->GetPoint() ); +} + +/** check whether a node can be special-inserted (alt-Enter), and do so. Return + whether insertion was possible. + */ +void SwEditShell::DoSpecialInsert() +{ + // get current node + SwPosition* pCursorPos = GetCursor()->GetPoint(); + const SwNode* pInsertNode = lcl_SpecialInsertNode( pCursorPos ); + if( pInsertNode != nullptr ) + { + StartAllAction(); + + // adjust insert position to insert before start nodes and after end + // nodes + SwNodeIndex aInsertIndex( *pInsertNode, + pInsertNode->IsStartNode() ? -1 : 0 ); + SwPosition aInsertPos( aInsertIndex ); + + // insert a new text node, and set the cursor + GetDoc()->getIDocumentContentOperations().AppendTextNode( aInsertPos ); + *pCursorPos = aInsertPos; + + // call AttrChangeNotify for the UI + CallChgLnk(); + + EndAllAction(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edtab.cxx b/sw/source/core/edit/edtab.cxx new file mode 100644 index 000000000..93036f735 --- /dev/null +++ b/sw/source/core/edit/edtab.cxx @@ -0,0 +1,531 @@ +/* -*- 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 <fesh.hxx> +#include <hintids.hxx> +#include <hints.hxx> + +#include <swwait.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <tblsel.hxx> +#include <cellfrm.hxx> +#include <cellatr.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <mdiexp.hxx> +#include <itabenum.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +namespace { + +void collectUIInformation(const OUString& rAction, const OUString& aParameters) +{ + EventDescription aDescription; + aDescription.aAction = rAction; + aDescription.aParameters = {{"parameters", aParameters}}; + aDescription.aID = "writer_edit"; + aDescription.aKeyWord = "SwEditWinUIObject"; + aDescription.aParent = "MainWindow"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +//Added for bug #i119954# Application crashed if undo/redo convert nest table to text +static bool ConvertTableToText( const SwTableNode *pTableNode, sal_Unicode cCh ); + +static void ConvertNestedTablesToText( const SwTableLines &rTableLines, sal_Unicode cCh ) +{ + for (size_t n = 0; n < rTableLines.size(); ++n) + { + SwTableLine* pTableLine = rTableLines[ n ]; + for (size_t i = 0; i < pTableLine->GetTabBoxes().size(); ++i) + { + SwTableBox* pTableBox = pTableLine->GetTabBoxes()[ i ]; + if (pTableBox->GetTabLines().empty()) + { + SwNodeIndex nodeIndex( *pTableBox->GetSttNd(), 1 ); + SwNodeIndex endNodeIndex( *pTableBox->GetSttNd()->EndOfSectionNode() ); + for( ; nodeIndex < endNodeIndex ; ++nodeIndex ) + { + if ( SwTableNode* pTableNode = nodeIndex.GetNode().GetTableNode() ) + ConvertTableToText( pTableNode, cCh ); + } + } + else + { + ConvertNestedTablesToText( pTableBox->GetTabLines(), cCh ); + } + } + } +} + +bool ConvertTableToText( const SwTableNode *pConstTableNode, sal_Unicode cCh ) +{ + SwTableNode *pTableNode = const_cast< SwTableNode* >( pConstTableNode ); + ConvertNestedTablesToText( pTableNode->GetTable().GetTabLines(), cCh ); + return pTableNode->GetDoc()->TableToText( pTableNode, cCh ); +} +//End for bug #i119954# + +const SwTable& SwEditShell::InsertTable( const SwInsertTableOptions& rInsTableOpts, + sal_uInt16 nRows, sal_uInt16 nCols, + const SwTableAutoFormat* pTAFormat ) +{ + StartAllAction(); + SwPosition* pPos = GetCursor()->GetPoint(); + + bool bEndUndo = 0 != pPos->nContent.GetIndex(); + if( bEndUndo ) + { + StartUndo( SwUndoId::START ); + GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false ); + } + + // If called from a shell the adjust item is propagated + // from pPos to the new content nodes in the table. + const SwTable *pTable = GetDoc()->InsertTable( rInsTableOpts, *pPos, + nRows, nCols, + css::text::HoriOrientation::FULL, pTAFormat, + nullptr, true ); + if( bEndUndo ) + EndUndo( SwUndoId::END ); + + EndAllAction(); + + OUString parameter = " Columns : " + OUString::number( nCols ) + " , Rows : " + OUString::number( nRows ) + " "; + collectUIInformation("CREATE_TABLE", parameter); + + return *pTable; +} + +bool SwEditShell::TextToTable( const SwInsertTableOptions& rInsTableOpts, + sal_Unicode cCh, + const SwTableAutoFormat* pTAFormat ) +{ + SwWait aWait( *GetDoc()->GetDocShell(), true ); + bool bRet = false; + StartAllAction(); + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + bRet |= nullptr != GetDoc()->TextToTable( rInsTableOpts, rPaM, cCh, + css::text::HoriOrientation::FULL, pTAFormat ); + } + EndAllAction(); + return bRet; +} + +bool SwEditShell::TableToText( sal_Unicode cCh ) +{ + SwWait aWait( *GetDoc()->GetDocShell(), true ); + bool bRet = false; + SwPaM* pCursor = GetCursor(); + const SwTableNode* pTableNd = + GetDoc()->IsIdxInTable( pCursor->GetPoint()->nNode ); + if( IsTableMode() ) + { + ClearMark(); + pCursor = GetCursor(); + } + else if( !pTableNd || pCursor->GetNext() != pCursor ) + return bRet; + + // TL_CHART2: + // tell the charts about the table to be deleted and have them use their own data + GetDoc()->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &pTableNd->GetTable() ); + + StartAllAction(); + + // move current Cursor out of the listing area + SwNodeIndex aTabIdx( *pTableNd ); + pCursor->DeleteMark(); + pCursor->GetPoint()->nNode = *pTableNd->EndOfSectionNode(); + pCursor->GetPoint()->nContent.Assign( nullptr, 0 ); + // move sPoint and Mark out of the area! + pCursor->SetMark(); + pCursor->DeleteMark(); + + //Modified for bug #i119954# Application crashed if undo/redo convert nest table to text + StartUndo(); + bRet = ConvertTableToText( pTableNd, cCh ); + EndUndo(); + //End for bug #i119954# + pCursor->GetPoint()->nNode = aTabIdx; + + SwContentNode* pCNd = pCursor->GetContentNode(); + if( !pCNd ) + pCursor->Move( fnMoveForward, GoInContent ); + else + pCursor->GetPoint()->nContent.Assign( pCNd, 0 ); + + EndAllAction(); + return bRet; +} + +bool SwEditShell::IsTextToTableAvailable() const +{ + bool bOnlyText = false; + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() && *rPaM.GetPoint() != *rPaM.GetMark() ) + { + bOnlyText = true; + + // check if selection is in listing + sal_uLong nStt = rPaM.GetMark()->nNode.GetIndex(), + nEnd = rPaM.GetPoint()->nNode.GetIndex(); + if( nStt > nEnd ) { sal_uLong n = nStt; nStt = nEnd; nEnd = n; } + + for( ; nStt <= nEnd; ++nStt ) + if( !GetDoc()->GetNodes()[ nStt ]->IsTextNode() ) + { + bOnlyText = false; + break; + } + + if( !bOnlyText ) + break; + } + } + + return bOnlyText; +} + +void SwEditShell::InsertDDETable( const SwInsertTableOptions& rInsTableOpts, + SwDDEFieldType* pDDEType, + sal_uInt16 nRows, sal_uInt16 nCols ) +{ + SwPosition* pPos = GetCursor()->GetPoint(); + + StartAllAction(); + + bool bEndUndo = 0 != pPos->nContent.GetIndex(); + if( bEndUndo ) + { + StartUndo( SwUndoId::START ); + GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false ); + } + + const SwInsertTableOptions aInsTableOpts( rInsTableOpts.mnInsMode | SwInsertTableFlags::DefaultBorder, + rInsTableOpts.mnRowsToRepeat ); + SwTable* pTable = const_cast<SwTable*>(GetDoc()->InsertTable( aInsTableOpts, *pPos, + nRows, nCols, css::text::HoriOrientation::FULL )); + + SwTableNode* pTableNode = const_cast<SwTableNode*>(pTable->GetTabSortBoxes()[ 0 ]-> + GetSttNd()->FindTableNode()); + std::unique_ptr<SwDDETable> pDDETable(new SwDDETable( *pTable, pDDEType )); + pTableNode->SetNewTable( std::move(pDDETable) ); // set the DDE table + + if( bEndUndo ) + EndUndo( SwUndoId::END ); + + EndAllAction(); +} + +/** update fields of a listing */ +void SwEditShell::UpdateTable() +{ + const SwTableNode* pTableNd = IsCursorInTable(); + + if( pTableNd ) + { + StartAllAction(); + if( DoesUndo() ) + StartUndo(); + EndAllTableBoxEdit(); + SwTableFormulaUpdate aTableUpdate( &pTableNd->GetTable() ); + GetDoc()->getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); + if( DoesUndo() ) + EndUndo(); + EndAllAction(); + } +} + +// get/set Change Mode + +TableChgMode SwEditShell::GetTableChgMode() const +{ + TableChgMode eMode; + const SwTableNode* pTableNd = IsCursorInTable(); + if( pTableNd ) + eMode = pTableNd->GetTable().GetTableChgMode(); + else + eMode = GetTableChgDefaultMode(); + return eMode; +} + +void SwEditShell::SetTableChgMode( TableChgMode eMode ) +{ + const SwTableNode* pTableNd = IsCursorInTable(); + + if( pTableNd ) + { + const_cast<SwTable&>(pTableNd->GetTable()).SetTableChgMode( eMode ); + if( !GetDoc()->getIDocumentState().IsModified() ) // Bug 57028 + { + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); + } + GetDoc()->getIDocumentState().SetModified(); + } +} + +bool SwEditShell::GetTableBoxFormulaAttrs( SfxItemSet& rSet ) const +{ + SwSelBoxes aBoxes; + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + do { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + { + SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert( pBox ); + } + } while( false ); + } + + for (size_t n = 0; n < aBoxes.size(); ++n) + { + const SwTableBox* pSelBox = aBoxes[ n ]; + const SwTableBoxFormat* pTableFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()); + if( !n ) + { + // Convert formulae into external presentation + const SwTable& rTable = pSelBox->GetSttNd()->FindTableNode()->GetTable(); + + SwTableFormulaUpdate aTableUpdate( &rTable ); + aTableUpdate.m_eFlags = TBL_BOXNAME; + GetDoc()->getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); + + rSet.Put( pTableFormat->GetAttrSet() ); + } + else + rSet.MergeValues( pTableFormat->GetAttrSet() ); + } + return 0 != rSet.Count(); +} + +void SwEditShell::SetTableBoxFormulaAttrs( const SfxItemSet& rSet ) +{ + SET_CURR_SHELL( this ); + SwSelBoxes aBoxes; + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + do { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + { + SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert( pBox ); + } + } while( false ); + } + + // When setting a formula, do not check further! + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA )) + ClearTableBoxContent(); + + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + for (size_t n = 0; n < aBoxes.size(); ++n) + { + GetDoc()->SetTableBoxFormulaAttrs( *aBoxes[ n ], rSet ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + EndAllAction(); +} + +bool SwEditShell::IsTableBoxTextFormat() const +{ + if( IsTableMode() ) + return false; + + const SwTableBox *pBox = nullptr; + { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + pBox = static_cast<SwCellFrame*>(pFrame)->GetTabBox(); + } + + if( !pBox ) + return false; + + sal_uInt32 nFormat = 0; + const SfxPoolItem* pItem; + if( SfxItemState::SET == pBox->GetFrameFormat()->GetAttrSet().GetItemState( + RES_BOXATR_FORMAT, true, &pItem )) + { + nFormat = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + return GetDoc()->GetNumberFormatter()->IsTextFormat( nFormat ); + } + + sal_uLong nNd = pBox->IsValidNumTextNd(); + if( ULONG_MAX == nNd ) + return true; + + const OUString& rText = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText(); + if( rText.isEmpty() ) + return false; + + double fVal; + return !GetDoc()->IsNumberFormat( rText, nFormat, fVal ); +} + +OUString SwEditShell::GetTableBoxText() const +{ + OUString sRet; + if( !IsTableMode() ) + { + const SwTableBox *pBox = nullptr; + { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + pBox = static_cast<SwCellFrame*>(pFrame)->GetTabBox(); + } + + sal_uLong nNd; + if( pBox && ULONG_MAX != ( nNd = pBox->IsValidNumTextNd() ) ) + sRet = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText(); + } + return sRet; +} + +void SwEditShell::SplitTable( SplitTable_HeadlineOption eMode ) +{ + SwPaM *pCursor = GetCursor(); + if( pCursor->GetNode().FindTableNode() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + GetDoc()->SplitTable( *pCursor->GetPoint(), eMode, true ); + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + ClearFEShellTabCols(*GetDoc(), nullptr); + EndAllAction(); + } +} + +bool SwEditShell::MergeTable( bool bWithPrev ) +{ + bool bRet = false; + SwPaM *pCursor = GetCursor(); + if( pCursor->GetNode().FindTableNode() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + bRet = GetDoc()->MergeTable( *pCursor->GetPoint(), bWithPrev ); + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + ClearFEShellTabCols(*GetDoc(), nullptr); + EndAllAction(); + } + return bRet; +} + +bool SwEditShell::CanMergeTable( bool bWithPrev, bool* pChkNxtPrv ) const +{ + bool bRet = false; + const SwPaM *pCursor = GetCursor(); + const SwTableNode* pTableNd = pCursor->GetNode().FindTableNode(); + if( pTableNd && dynamic_cast< const SwDDETable* >(&pTableNd->GetTable()) == nullptr) + { + bool bNew = pTableNd->GetTable().IsNewModel(); + const SwNodes& rNds = GetDoc()->GetNodes(); + if( pChkNxtPrv ) + { + const SwTableNode* pChkNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + if( pChkNd && dynamic_cast< const SwDDETable* >(&pChkNd->GetTable()) == nullptr && + bNew == pChkNd->GetTable().IsNewModel() && + // Consider table in table case + pChkNd->EndOfSectionIndex() == pTableNd->GetIndex() - 1 ) + { + *pChkNxtPrv = true; + bRet = true; // using Prev is possible + } + else + { + pChkNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + if( pChkNd && dynamic_cast< const SwDDETable* >(&pChkNd->GetTable()) == nullptr && + bNew == pChkNd->GetTable().IsNewModel() ) + { + *pChkNxtPrv = false; + bRet = true; // using Next is possible + } + } + } + else + { + const SwTableNode* pTmpTableNd = nullptr; + + if( bWithPrev ) + { + pTmpTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + // Consider table in table case + if ( pTmpTableNd && pTmpTableNd->EndOfSectionIndex() != pTableNd->GetIndex() - 1 ) + pTmpTableNd = nullptr; + } + else + pTmpTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + + bRet = pTmpTableNd && dynamic_cast< const SwDDETable* >(&pTmpTableNd->GetTable()) == nullptr && + bNew == pTmpTableNd->GetTable().IsNewModel(); + } + } + return bRet; +} + +/** create InsertDB as table Undo */ +void SwEditShell::AppendUndoForInsertFromDB( bool bIsTable ) +{ + GetDoc()->AppendUndoForInsertFromDB( *GetCursor(), bIsTable ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edtox.cxx b/sw/source/core/edit/edtox.cxx new file mode 100644 index 000000000..e652551d1 --- /dev/null +++ b/sw/source/core/edit/edtox.cxx @@ -0,0 +1,388 @@ +/* -*- 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 <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/transliteration.hxx> +#include <i18nutil/searchopt.hxx> +#include <svl/fstathelper.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <unotools/syslocale.hxx> + +#include <sfx2/docfile.hxx> + +#include <swtypes.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentUndoRedo.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <tox.hxx> +#include <doctxm.hxx> +#include <docary.hxx> +#include <mdiexp.hxx> +#include <strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::util; + +// Add/delete listing markers to a document + +void SwEditShell::Insert(const SwTOXMark& rMark) +{ + bool bInsAtPos = rMark.IsAlternativeText(); + StartAllAction(); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + const SwPosition *pStt = rPaM.Start(), + *pEnd = rPaM.End(); + if( bInsAtPos ) + { + SwPaM aTmp( *pStt ); + GetDoc()->getIDocumentContentOperations().InsertPoolItem( aTmp, rMark ); + } + else if( *pEnd != *pStt ) + { + GetDoc()->getIDocumentContentOperations().InsertPoolItem( + rPaM, rMark, SetAttrMode::DONTEXPAND ); + } + + } + EndAllAction(); +} + +void SwEditShell::DeleteTOXMark( SwTOXMark const * pMark ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + + mxDoc->DeleteTOXMark( pMark ); + + EndAllAction(); +} + +/// Collect all listing markers +void SwEditShell::GetCurTOXMarks(SwTOXMarks& rMarks) const +{ + SwDoc::GetCurTOXMark( *GetCursor()->Start(), rMarks ); +} + +bool SwEditShell::IsTOXBaseReadonly(const SwTOXBase& rTOXBase) +{ + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" ); + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + return rTOXSect.IsProtect(); +} + +void SwEditShell::SetTOXBaseReadonly(const SwTOXBase& rTOXBase, bool bReadonly) +{ + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" ); + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + const_cast<SwTOXBase&>(rTOXBase).SetProtected(bReadonly); + OSL_ENSURE( rTOXSect.SwSection::GetType() == SectionType::ToxContent, "not a TOXContentSection" ); + + SwSectionData aSectionData(rTOXSect); + aSectionData.SetProtectFlag(bReadonly); + UpdateSection( GetSectionFormatPos( *rTOXSect.GetFormat() ), aSectionData ); +} + +const SwTOXBase* SwEditShell::GetDefaultTOXBase( TOXTypes eTyp, bool bCreate ) +{ + return GetDoc()->GetDefaultTOXBase( eTyp, bCreate ); +} + +void SwEditShell::SetDefaultTOXBase(const SwTOXBase& rBase) +{ + GetDoc()->SetDefaultTOXBase(rBase); +} + +/// Insert listing and create content +void SwEditShell::InsertTableOf( const SwTOXBase& rTOX, const SfxItemSet* pSet ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + + SwDocShell* pDocSh = GetDoc()->GetDocShell(); + ::StartProgress( STR_STATSTR_TOX_INSERT, 0, 0, pDocSh ); + + // Insert listing + const SwTOXBaseSection* pTOX = mxDoc->InsertTableOf( + *GetCursor()->GetPoint(), rTOX, pSet, true, GetLayout() ); + OSL_ENSURE(pTOX, "No current TOX"); + + // start formatting + CalcLayout(); + + // insert page numbering + const_cast<SwTOXBaseSection*>(pTOX)->UpdatePageNum(); + + pTOX->SetPosAtStartEnd( *GetCursor()->GetPoint() ); + + // Fix for empty listing + InvalidateWindows( maVisArea ); + ::EndProgress( pDocSh ); + EndAllAction(); +} + +/// update tables of content +void SwEditShell::UpdateTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet) +{ + assert(dynamic_cast<const SwTOXBaseSection*>(&rTOX) && "no TOXBaseSection!"); + SwTOXBaseSection& rTOXSect = static_cast<SwTOXBaseSection&>(const_cast<SwTOXBase&>(rTOX)); + if (rTOXSect.GetFormat()->GetSectionNode()) + { + SwDoc* pMyDoc = GetDoc(); + SwDocShell* pDocSh = pMyDoc->GetDocShell(); + + bool bInIndex = &rTOX == GetCurTOX(); + SET_CURR_SHELL( this ); + StartAllAction(); + + ::StartProgress( STR_STATSTR_TOX_UPDATE, 0, 0, pDocSh ); + + pMyDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::TOXCHANGE, nullptr); + + // create listing stub + rTOXSect.Update(pSet, GetLayout()); + + // correct Cursor + if( bInIndex ) + rTOXSect.SetPosAtStartEnd(*GetCursor()->GetPoint()); + + // start formatting + CalcLayout(); + + // insert page numbering + rTOXSect.UpdatePageNum(); + + pMyDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::TOXCHANGE, nullptr); + + ::EndProgress( pDocSh ); + EndAllAction(); + } +} + +/// Get current listing before or at the Cursor +const SwTOXBase* SwEditShell::GetCurTOX() const +{ + return SwDoc::GetCurTOX( *GetCursor()->GetPoint() ); +} + +bool SwEditShell::DeleteTOX( const SwTOXBase& rTOXBase, bool bDelNodes ) +{ + return GetDoc()->DeleteTOX( rTOXBase, bDelNodes ); +} + +// manage types of listings + +const SwTOXType* SwEditShell::GetTOXType(TOXTypes eTyp, sal_uInt16 nId) const +{ + return mxDoc->GetTOXType(eTyp, nId); +} + +// manage keys for the alphabetical index + +void SwEditShell::GetTOIKeys( SwTOIKeyType eTyp, std::vector<OUString>& rArr ) const +{ + GetDoc()->GetTOIKeys( eTyp, rArr, *GetLayout() ); +} + +sal_uInt16 SwEditShell::GetTOXCount() const +{ + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + sal_uInt16 nRet = 0; + for( auto n = rFormats.size(); n; ) + { + const SwSection* pSect = rFormats[ --n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() ) + ++nRet; + } + return nRet; +} + +const SwTOXBase* SwEditShell::GetTOX( sal_uInt16 nPos ) const +{ + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + sal_uInt16 nCnt {0}; + for( const SwSectionFormat *pFormat : rFormats ) + { + const SwSection* pSect = pFormat->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() && + nCnt++ == nPos ) + { + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( pSect) != nullptr, "no TOXBaseSection!" ); + return static_cast<const SwTOXBaseSection*>(pSect); + } + } + return nullptr; +} + +/** Update of all listings after reading-in a file */ +void SwEditShell::SetUpdateTOX( bool bFlag ) +{ + GetDoc()->SetUpdateTOX( bFlag ); +} + +bool SwEditShell::IsUpdateTOX() const +{ + return GetDoc()->IsUpdateTOX(); +} + +OUString const & SwEditShell::GetTOIAutoMarkURL() const +{ + return GetDoc()->GetTOIAutoMarkURL(); +} + +void SwEditShell::SetTOIAutoMarkURL(const OUString& rSet) +{ + GetDoc()->SetTOIAutoMarkURL(rSet); +} + +void SwEditShell::ApplyAutoMark() +{ + StartAllAction(); + bool bDoesUndo = DoesUndo(); + DoUndo(false); + //1. remove all automatic generated index entries if AutoMarkURL has a + // length and the file exists + //2. load file + //3. select all occurrences of the searched words + //4. apply index entries + + OUString sAutoMarkURL(GetDoc()->GetTOIAutoMarkURL()); + if( !sAutoMarkURL.isEmpty() && FStatHelper::IsDocument( sAutoMarkURL )) + { + //1. + const SwTOXType* pTOXType = GetTOXType(TOX_INDEX, 0); + + SwTOXMarks aMarks; + SwTOXMark::InsertTOXMarks( aMarks, *pTOXType ); + for( SwTOXMark* pMark : aMarks ) + { + if(pMark->IsAutoGenerated() && pMark->GetTextTOXMark()) + // mba: test iteration; objects are deleted in iteration + DeleteTOXMark(pMark); + } + + //2. + SfxMedium aMedium( sAutoMarkURL, StreamMode::STD_READ ); + SvStream& rStrm = *aMedium.GetInStream(); + Push(); + rtl_TextEncoding eChrSet = ::osl_getThreadTextEncoding(); + + // SearchOptions to be used in loop below + sal_Int32 const nLEV_Other = 2; // -> changedChars; + sal_Int32 const nLEV_Longer = 3; //! -> deletedChars; + sal_Int32 const nLEV_Shorter = 1; //! -> insertedChars; + + i18nutil::SearchOptions2 aSearchOpt( + SearchAlgorithms_ABSOLUTE, + SearchFlags::LEV_RELAXED, + "", "", + SvtSysLocale().GetLanguageTag().getLocale(), + nLEV_Other, nLEV_Longer, nLEV_Shorter, + TransliterationFlags::NONE, + SearchAlgorithms2::ABSOLUTE, + '\\' ); + + while (rStrm.good()) + { + OString aRdLine; + rStrm.ReadLine( aRdLine ); + + // # -> comment + // ; -> delimiter between entries -> + // Format: TextToSearchFor;AlternativeString;PrimaryKey;SecondaryKey;CaseSensitive;WordOnly + // Leading and trailing blanks are ignored + if( !aRdLine.isEmpty() && '#' != aRdLine[0] ) + { + OUString sLine(OStringToOUString(aRdLine, eChrSet)); + + sal_Int32 nTokenPos = 0; + OUString sToSelect( sLine.getToken(0, ';', nTokenPos ) ); + if( !sToSelect.isEmpty() ) + { + OUString sAlternative = sLine.getToken(0, ';', nTokenPos); + OUString sPrimary = sLine.getToken(0, ';', nTokenPos); + OUString sSecondary = sLine.getToken(0, ';', nTokenPos); + OUString sCase = sLine.getToken(0, ';', nTokenPos); + OUString sWordOnly = sLine.getToken(0, ';', nTokenPos); + + //3. + bool bCaseSensitive = !sCase.isEmpty() && sCase != "0"; + bool bWordOnly = !sWordOnly.isEmpty() && sWordOnly != "0"; + + if (!bCaseSensitive) + { + aSearchOpt.transliterateFlags |= + TransliterationFlags::IGNORE_CASE; + } + else + { + aSearchOpt.transliterateFlags &= + ~TransliterationFlags::IGNORE_CASE; + } + if ( bWordOnly) + aSearchOpt.searchFlag |= SearchFlags::NORM_WORD_ONLY; + else + aSearchOpt.searchFlag &= ~SearchFlags::NORM_WORD_ONLY; + + aSearchOpt.searchString = sToSelect; + + KillPams(); + bool bCancel; + + // todo/mba: assuming that notes shouldn't be searched + sal_uLong nRet = Find_Text(aSearchOpt, false/*bSearchInNotes*/, SwDocPositions::Start, SwDocPositions::End, bCancel, + FindRanges::InSelAll ); + + if(nRet) + { + SwTOXMark* pTmpMark = new SwTOXMark(pTOXType); + if( !sPrimary.isEmpty() ) + { + pTmpMark->SetPrimaryKey( sPrimary ); + if( !sSecondary.isEmpty() ) + pTmpMark->SetSecondaryKey( sSecondary ); + } + if( !sAlternative.isEmpty() ) + pTmpMark->SetAlternativeText(sAlternative); + pTmpMark->SetMainEntry(false); + pTmpMark->SetAutoGenerated(true); + //4. + SwEditShell::Insert(*pTmpMark); + } + } + } + } + KillPams(); + Pop(PopMode::DeleteCurrent); + } + DoUndo(bDoesUndo); + EndAllAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edundo.cxx b/sw/source/core/edit/edundo.cxx new file mode 100644 index 000000000..6a093a342 --- /dev/null +++ b/sw/source/core/edit/edundo.cxx @@ -0,0 +1,246 @@ +/* -*- 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 <svx/svdmark.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/frame/XModel.hpp> + +#include <editsh.hxx> +#include <fesh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <pam.hxx> +#include <UndoCore.hxx> +#include <swundo.hxx> +#include <flyfrm.hxx> +#include <frmfmt.hxx> +#include <docsh.hxx> +#include <pagefrm.hxx> + +/** helper function to select all objects in an SdrMarkList; + * implementation: see below */ +static void lcl_SelectSdrMarkList( SwEditShell* pShell, + const SdrMarkList* pSdrMarkList ); + +bool SwEditShell::CursorsLocked() const +{ + return GetDoc()->GetDocShell()->GetModel()->hasControllersLocked(); +} + +void SwEditShell::HandleUndoRedoContext(::sw::UndoRedoContext & rContext) +{ + // do nothing if somebody has locked controllers! + if (CursorsLocked()) + { + return; + } + + SwFrameFormat * pSelFormat(nullptr); + SdrMarkList * pMarkList(nullptr); + rContext.GetSelections(pSelFormat, pMarkList); + + if (pSelFormat) // select frame + { + if (RES_DRAWFRMFMT == pSelFormat->Which()) + { + SdrObject* pSObj = pSelFormat->FindSdrObject(); + static_cast<SwFEShell*>(this)->SelectObj( + pSObj->GetCurrentBoundRect().Center() ); + } + else + { + Point aPt; + SwFlyFrame *const pFly = + static_cast<SwFlyFrameFormat*>(pSelFormat)->GetFrame(& aPt); + if (pFly) + { + // fdo#36681: Invalidate the content and layout to refresh + // the picture anchoring properly + SwPageFrame* pPageFrame = pFly->FindPageFrameOfAnchor(); + pPageFrame->InvalidateFlyLayout(); + pPageFrame->InvalidateContent(); + + static_cast<SwFEShell*>(this)->SelectFlyFrame(*pFly); + } + } + } + else if (pMarkList) + { + lcl_SelectSdrMarkList( this, pMarkList ); + } + else if (GetCursor()->GetNext() != GetCursor()) + { + // current cursor is the last one: + // go around the ring, to the first cursor + GoNextCursor(); + } +} + +void SwEditShell::Undo(sal_uInt16 const nCount) +{ + SET_CURR_SHELL( this ); + + // current undo state was not saved + ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + bool bRet = false; + + StartAllAction(); + { + // Actually it should be enough to just work on the current Cursor, i.e. if there is a cycle + // cancel the latter temporarily, so that an insert during Undo is not done in all areas. + KillPams(); + SetMark(); // Bound1 and Bound2 in the same Node + ClearMark(); + + // Keep Cursor - so that we're able to set it at + // the same position for autoformat or autocorrection + SwUndoId nLastUndoId(SwUndoId::EMPTY); + GetLastUndoInfo(nullptr, & nLastUndoId); + const bool bRestoreCursor = nCount == 1 + && ( SwUndoId::AUTOFORMAT == nLastUndoId + || SwUndoId::AUTOCORRECT == nLastUndoId + || SwUndoId::SETDEFTATTR == nLastUndoId ); + Push(); + + // Destroy stored TableBoxPtr. A detection is only permitted for the new "Box"! + ClearTableBoxContent(); + + const RedlineFlags eOld = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + + try { + for (sal_uInt16 i = 0; i < nCount; ++i) + { + bRet = GetDoc()->GetIDocumentUndoRedo().Undo() + || bRet; + } + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Undo()"); + } + + if (bRestoreCursor) + { // fdo#39003 Pop does not touch the rest of the cursor ring + KillPams(); // so call this first to get rid of unwanted cursors + } + Pop(bRestoreCursor ? PopMode::DeleteCurrent : PopMode::DeleteStack); + + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + GetDoc()->getIDocumentRedlineAccess().CompressRedlines(); + + // automatic detection of the new "Box" + SaveTableBoxContent(); + } + EndAllAction(); +} + +void SwEditShell::Redo(sal_uInt16 const nCount) +{ + SET_CURR_SHELL( this ); + + bool bRet = false; + + // undo state was not saved + ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + + StartAllAction(); + + { + // Actually it should be enough to just work on the current Cursor, i.e. if there is a cycle + // cancel the latter temporarily, so that an insert during Undo is not done in all areas. + KillPams(); + SetMark(); // Bound1 and Bound2 in the same Node + ClearMark(); + + SwUndoId nFirstRedoId(SwUndoId::EMPTY); + GetDoc()->GetIDocumentUndoRedo().GetFirstRedoInfo(nullptr, & nFirstRedoId); + const bool bRestoreCursor = nCount == 1 && SwUndoId::SETDEFTATTR == nFirstRedoId; + Push(); + + // Destroy stored TableBoxPtr. A detection is only permitted for the new "Box"! + ClearTableBoxContent(); + + RedlineFlags eOld = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + + try { + for (sal_uInt16 i = 0; i < nCount; ++i) + { + bRet = GetDoc()->GetIDocumentUndoRedo().Redo() + || bRet; + } + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Redo()"); + } + + Pop(bRestoreCursor ? PopMode::DeleteCurrent : PopMode::DeleteStack); + + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + GetDoc()->getIDocumentRedlineAccess().CompressRedlines(); + + // automatic detection of the new "Box" + SaveTableBoxContent(); + } + + EndAllAction(); +} + +void SwEditShell::Repeat(sal_uInt16 const nCount) +{ + SET_CURR_SHELL( this ); + + StartAllAction(); + + try { + ::sw::RepeatContext context(*GetDoc(), *GetCursor()); + GetDoc()->GetIDocumentUndoRedo().Repeat( context, nCount ); + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Repeat()"); + } + + EndAllAction(); +} + +static void lcl_SelectSdrMarkList( SwEditShell* pShell, + const SdrMarkList* pSdrMarkList ) +{ + OSL_ENSURE( pShell != nullptr, "need shell!" ); + OSL_ENSURE( pSdrMarkList != nullptr, "need mark list" ); + + if( dynamic_cast<const SwFEShell*>( pShell) != nullptr ) + { + SwFEShell* pFEShell = static_cast<SwFEShell*>( pShell ); + bool bFirst = true; + for( size_t i = 0; i < pSdrMarkList->GetMarkCount(); ++i ) + { + SdrObject *pObj = pSdrMarkList->GetMark( i )->GetMarkedSdrObj(); + if( pObj ) + { + pFEShell->SelectObj( Point(), bFirst ? 0 : SW_ADD_SELECT, pObj ); + bFirst = false; + } + } + + // the old implementation would always unselect + // objects, even if no new ones were selected. If this + // is a problem, we need to re-work this a little. + OSL_ENSURE( pSdrMarkList->GetMarkCount() != 0, "empty mark list" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx new file mode 100644 index 000000000..b17b557df --- /dev/null +++ b/sw/source/core/edit/edws.cxx @@ -0,0 +1,321 @@ +/* -*- 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 <editsh.hxx> + +#include <officecfg/Office/Common.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/window.hxx> + +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <pam.hxx> +#include <docary.hxx> +#include <acorrect.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <swundo.hxx> +#include <SwRewriter.hxx> +#include <frameformats.hxx> + +// masqueraded copy constructor +SwEditShell::SwEditShell( SwEditShell& rEdSH, vcl::Window *pWindow ) + : SwCursorShell( rEdSH, pWindow ) + , m_bNbspRunNext(false) // TODO: would copying that make sense? only if editing continues + , m_bDoParagraphSignatureValidation(true) +{ +} + +SwEditShell::SwEditShell( SwDoc& rDoc, vcl::Window *pWindow, const SwViewOption *pOptions ) + : SwCursorShell( rDoc, pWindow, pOptions ) + , m_bNbspRunNext(false) + , m_bDoParagraphSignatureValidation(true) +{ + if (!utl::ConfigManager::IsFuzzing() && 0 < officecfg::Office::Common::Undo::Steps::get()) + { + GetDoc()->GetIDocumentUndoRedo().DoUndo(true); + } + + // Restore the paragraph metadata fields and validate signatures. + RestoreMetadataFieldsAndValidateParagraphSignatures(); +} + +SwEditShell::~SwEditShell() // USED +{ +} + +bool SwEditShell::IsModified() const +{ + return GetDoc()->getIDocumentState().IsModified(); +} + +void SwEditShell::SetModified() +{ + GetDoc()->getIDocumentState().SetModified(); +} + +void SwEditShell::ResetModified() +{ + GetDoc()->getIDocumentState().ResetModified(); +} + +void SwEditShell::SetUndoNoResetModified() +{ + GetDoc()->getIDocumentState().SetModified(); + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); +} + +void SwEditShell::StartAllAction() +{ + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if (SwEditShell* pEditShell = dynamic_cast<SwEditShell*>(&rCurrentShell)) + pEditShell->StartAction(); + else + rCurrentShell.StartAction(); + } +} + +void SwEditShell::EndAllAction() +{ + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if( dynamic_cast<const SwEditShell *>(&rCurrentShell) != nullptr ) + static_cast<SwEditShell*>(&rCurrentShell)->EndAction(); + else + rCurrentShell.EndAction(); + } +} + +void SwEditShell::CalcLayout() +{ + StartAllAction(); + SwViewShell::CalcLayout(); + + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if ( rCurrentShell.GetWin() ) + rCurrentShell.GetWin()->Invalidate(); + } + + EndAllAction(); +} + +/** Get the content type of a shell + * + * @todo Is this called for every attribute? + */ +sal_uInt16 SwEditShell::GetCntType() const +{ + sal_uInt16 nRet = 0; + if( IsTableMode() ) + nRet = CNT_TXT; + else + switch( GetCursor()->GetNode().GetNodeType() ) + { + case SwNodeType::Text: nRet = CNT_TXT; break; + case SwNodeType::Grf: nRet = CNT_GRF; break; + case SwNodeType::Ole: nRet = CNT_OLE; break; + default: break; + } + + OSL_ASSERT( nRet ); + return nRet; +} + +bool SwEditShell::HasOtherCnt() const + +{ + if ( !GetDoc()->GetSpzFrameFormats()->empty() ) + return true; + + const SwNodes &rNds = GetDoc()->GetNodes(); + const SwNode *pNd; + + pNd = &rNds.GetEndOfInserts(); + if ( 1 != (pNd->GetIndex() - pNd->StartOfSectionIndex()) ) + return true; + + pNd = &rNds.GetEndOfAutotext(); + return 1 != (pNd->GetIndex() - pNd->StartOfSectionIndex()); +} + +SwActContext::SwActContext(SwEditShell *pShell) + : m_rShell(*pShell) +{ + m_rShell.StartAction(); +} + +SwActContext::~SwActContext() COVERITY_NOEXCEPT_FALSE +{ + m_rShell.EndAction(); +} + +SwMvContext::SwMvContext(SwEditShell *pShell) + : m_rShell(*pShell) +{ + m_rShell.SttCursorMove(); +} + +SwMvContext::~SwMvContext() COVERITY_NOEXCEPT_FALSE +{ + m_rShell.EndCursorMove(); +} + +SwFrameFormat *SwEditShell::GetTableFormat() // fastest test on a table +{ + const SwTableNode* pTableNd = IsCursorInTable(); + return pTableNd ? static_cast<SwFrameFormat*>(pTableNd->GetTable().GetFrameFormat()) : nullptr; +} + +// TODO: Why is this called 3x for a new document? +sal_uInt16 SwEditShell::GetTOXTypeCount(TOXTypes eTyp) const +{ + return mxDoc->GetTOXTypeCount(eTyp); +} + +void SwEditShell::InsertTOXType(const SwTOXType& rTyp) +{ + mxDoc->InsertTOXType(rTyp); +} + +void SwEditShell::DoUndo( bool bOn ) +{ GetDoc()->GetIDocumentUndoRedo().DoUndo( bOn ); } + +bool SwEditShell::DoesUndo() const +{ return GetDoc()->GetIDocumentUndoRedo().DoesUndo(); } + +void SwEditShell::DoGroupUndo( bool bOn ) +{ GetDoc()->GetIDocumentUndoRedo().DoGroupUndo( bOn ); } + +bool SwEditShell::DoesGroupUndo() const +{ return GetDoc()->GetIDocumentUndoRedo().DoesGroupUndo(); } + +void SwEditShell::DelAllUndoObj() +{ + GetDoc()->GetIDocumentUndoRedo().DelAllUndoObj(); +} + +// Combine continuous calls of Insert/Delete/Overwrite on characters. Default: sdbcx::Group-Undo. + +/** open undo container + * + * @return nUndoId ID of the container + */ +SwUndoId SwEditShell::StartUndo( SwUndoId eUndoId, + const SwRewriter *pRewriter ) +{ return GetDoc()->GetIDocumentUndoRedo().StartUndo( eUndoId, pRewriter ); } + +/** close undo container + * + * not used by UI + * + * @param eUndoId ID of the undo container + * @param pRewriter ? +*/ +SwUndoId SwEditShell::EndUndo(SwUndoId eUndoId, const SwRewriter *pRewriter) +{ return GetDoc()->GetIDocumentUndoRedo().EndUndo(eUndoId, pRewriter); } + +bool SwEditShell::GetLastUndoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView) const +{ + return GetDoc()->GetIDocumentUndoRedo().GetLastUndoInfo(o_pStr, o_pId, pView); +} + +bool SwEditShell::GetFirstRedoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView) const +{ + return GetDoc()->GetIDocumentUndoRedo().GetFirstRedoInfo(o_pStr, o_pId, pView); +} + +SwUndoId SwEditShell::GetRepeatInfo(OUString *const o_pStr) const +{ return GetDoc()->GetIDocumentUndoRedo().GetRepeatInfo(o_pStr); } + +/** Auto correction */ +void SwEditShell::AutoCorrect( SvxAutoCorrect& rACorr, bool bInsert, + sal_Unicode cChar ) +{ + SET_CURR_SHELL( this ); + + StartAllAction(); + + SwPaM* pCursor = getShellCursor( true ); + SwTextNode* pTNd = pCursor->GetNode().GetTextNode(); + + SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, cChar ); + // FIXME: this _must_ be called with reference to the actual node text! + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout()))); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + OUString const& rMergedText(pFrame->GetText()); + rACorr.DoAutoCorrect( aSwAutoCorrDoc, + rMergedText, sal_Int32(nPos), + cChar, bInsert, m_bNbspRunNext, GetWin() ); + if( cChar ) + SaveTableBoxContent( pCursor->GetPoint() ); + EndAllAction(); +} + +void SwEditShell::SetNewDoc() +{ + GetDoc()->getIDocumentState().SetNewDoc(true); +} + +OUString SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr) +{ + SET_CURR_SHELL( this ); + + OUString sRet; + SwPaM* pCursor = getShellCursor( true ); + SwTextNode* pTNd = pCursor->GetNode().GetTextNode(); + if (pTNd) + { + SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 ); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout()))); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pFrame->GetText(), sal_Int32(nPos)); + } + return sRet; +} + +std::vector<OUString> SwEditShell::GetChunkForAutoText() +{ + SET_CURR_SHELL(this); + + std::vector<OUString> aRet; + SwPaM* pCursor = getShellCursor(true); + SwTextNode* pTNd = pCursor->GetNode().GetTextNode(); + if (pTNd) + { + const auto pFrame = static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout())); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + aRet = SvxAutoCorrect::GetChunkForAutoText(pFrame->GetText(), sal_Int32(nPos)); + } + return aRet; +} + +SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords() +{ + return SwDoc::GetAutoCompleteWords(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/authfld.cxx b/sw/source/core/fields/authfld.cxx new file mode 100644 index 000000000..48a58c07b --- /dev/null +++ b/sw/source/core/fields/authfld.cxx @@ -0,0 +1,668 @@ +/* -*- 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 <memory> +#include <comphelper/string.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/any.hxx> +#include <swtypes.hxx> +#include <strings.hrc> +#include <authfld.hxx> +#include <expfld.hxx> +#include <pam.hxx> +#include <cntfrm.hxx> +#include <rootfrm.hxx> +#include <tox.hxx> +#include <txmsrt.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <unofldmid.h> +#include <unoprnms.hxx> +#include <calbck.hxx> + +#include <com/sun/star/beans/PropertyValues.hpp> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; + +SwAuthEntry::SwAuthEntry(const SwAuthEntry& rCopy) + : SimpleReferenceObject() +{ + for(int i = 0; i < AUTH_FIELD_END; ++i) + m_aAuthFields[i] = rCopy.m_aAuthFields[i]; +} + +bool SwAuthEntry::operator==(const SwAuthEntry& rComp) const +{ + for(int i = 0; i < AUTH_FIELD_END; ++i) + if(m_aAuthFields[i] != rComp.m_aAuthFields[i]) + return false; + return true; +} + +SwAuthorityFieldType::SwAuthorityFieldType(SwDoc* pDoc) + : SwFieldType( SwFieldIds::TableOfAuthorities ), + m_pDoc(pDoc), + m_cPrefix('['), + m_cSuffix(']'), + m_bIsSequence(false), + m_bSortByDocument(true), + m_eLanguage(::GetAppLanguage()) +{ +} + +SwAuthorityFieldType::~SwAuthorityFieldType() +{ +} + +std::unique_ptr<SwFieldType> SwAuthorityFieldType::Copy() const +{ + return std::make_unique<SwAuthorityFieldType>(m_pDoc); +} + +void SwAuthorityFieldType::RemoveField(const SwAuthEntry* pEntry) +{ + for(SwAuthDataArr::size_type j = 0; j < m_DataArr.size(); ++j) + { + if(m_DataArr[j].get() == pEntry) + { + if (m_DataArr[j]->m_nCount <= 1) + { + m_DataArr.erase(m_DataArr.begin() + j); + //re-generate positions of the fields + DelSequenceArray(); + } + return; + } + } + assert(false); + OSL_FAIL("Field unknown" ); +} + +SwAuthEntry* SwAuthorityFieldType::AddField(const OUString& rFieldContents) +{ + rtl::Reference<SwAuthEntry> pEntry(new SwAuthEntry); + sal_Int32 nIdx{ 0 }; + for( sal_Int32 i = 0; i < AUTH_FIELD_END; ++i ) + pEntry->SetAuthorField( static_cast<ToxAuthorityField>(i), + rFieldContents.getToken( 0, TOX_STYLE_DELIMITER, nIdx )); + + for (auto &rpTemp : m_DataArr) + { + if (*rpTemp == *pEntry) + { + return rpTemp.get(); + } + } + + //if it is a new Entry - insert + m_DataArr.push_back(std::move(pEntry)); + //re-generate positions of the fields + DelSequenceArray(); + return m_DataArr.back().get(); +} + +void SwAuthorityFieldType::GetAllEntryIdentifiers( + std::vector<OUString>& rToFill )const +{ + for (const auto & rpTemp : m_DataArr) + { + rToFill.push_back(rpTemp->GetAuthorField(AUTH_FIELD_IDENTIFIER)); + } +} + +SwAuthEntry* SwAuthorityFieldType::GetEntryByIdentifier( + const OUString& rIdentifier)const +{ + for (const auto &rpTemp : m_DataArr) + { + if (rIdentifier == rpTemp->GetAuthorField(AUTH_FIELD_IDENTIFIER)) + { + return rpTemp.get(); + } + } + return nullptr; +} + +bool SwAuthorityFieldType::ChangeEntryContent(const SwAuthEntry* pNewEntry) +{ + for (auto &rpTemp : m_DataArr) + { + if (rpTemp->GetAuthorField(AUTH_FIELD_IDENTIFIER) == + pNewEntry->GetAuthorField(AUTH_FIELD_IDENTIFIER)) + { + for(int i = 0; i < AUTH_FIELD_END; ++i) + { + rpTemp->SetAuthorField(static_cast<ToxAuthorityField>(i), + pNewEntry->GetAuthorField(static_cast<ToxAuthorityField>(i))); + } + return true; + } + } + return false; +} + +/// appends a new entry (if new) and returns the array position +sal_uInt16 SwAuthorityFieldType::AppendField( const SwAuthEntry& rInsert ) +{ + for( SwAuthDataArr::size_type nRet = 0; nRet < m_DataArr.size(); ++nRet ) + { + if( *m_DataArr[ nRet ] == rInsert ) + return nRet; + } + + //if it is a new Entry - insert + m_DataArr.push_back(new SwAuthEntry(rInsert)); + return m_DataArr.size()-1; +} + +sal_uInt16 SwAuthorityFieldType::GetSequencePos(const SwAuthEntry* pAuthEntry, + SwRootFrame const*const pLayout) +{ + //find the field in a sorted array of handles, + if(!m_SequArr.empty() && m_SequArr.size() != m_DataArr.size()) + DelSequenceArray(); + if(m_SequArr.empty()) + { + IDocumentRedlineAccess const& rIDRA(m_pDoc->getIDocumentRedlineAccess()); + SwTOXInternational aIntl(m_eLanguage, SwTOIOptions::NONE, m_sSortAlgorithm); + // sw_redlinehide: need 2 arrays because the sorting may be different, + // if multiple fields refer to the same entry and first one is deleted + std::vector<std::unique_ptr<SwTOXSortTabBase>> aSortArr; + std::vector<std::unique_ptr<SwTOXSortTabBase>> aSortArrRLHidden; + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(SwFormatField* pFormatField : vFields) + { + const SwTextField* pTextField = pFormatField->GetTextField(); + if(!pTextField || !pTextField->GetpTextNode()) + { + continue; + } + const SwTextNode& rFieldTextNode = pTextField->GetTextNode(); + SwPosition aFieldPos(rFieldTextNode); + SwDoc& rDoc = *const_cast<SwDoc*>(rFieldTextNode.GetDoc()); + SwContentFrame *pFrame = rFieldTextNode.getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ); + const SwTextNode* pTextNode = nullptr; + if(pFrame && !pFrame->IsInDocBody()) + pTextNode = GetBodyTextNode( rDoc, aFieldPos, *pFrame ); + //if no text node could be found or the field is in the document + //body the directly available text node will be used + if(!pTextNode) + pTextNode = &rFieldTextNode; + if (pTextNode->GetText().isEmpty() + || !pTextNode->getLayoutFrame(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()) + || !pTextNode->GetNodes().IsDocNodes()) + { + continue; + } + auto const InsertImpl = [&aIntl, pTextNode, pFormatField] + (std::vector<std::unique_ptr<SwTOXSortTabBase>> & rSortArr) + { + std::unique_ptr<SwTOXAuthority> pNew( + new SwTOXAuthority(*pTextNode, *pFormatField, aIntl)); + + for (size_t i = 0; i < rSortArr.size(); ++i) + { + SwTOXSortTabBase* pOld = rSortArr[i].get(); + if (pOld->equivalent(*pNew)) + { + //only the first occurrence in the document + //has to be in the array + if (pOld->sort_lt(*pNew)) + pNew.reset(); + else // remove the old content + rSortArr.erase(rSortArr.begin() + i); + break; + } + } + //if it still exists - insert at the correct position + if (pNew) + { + size_t j {0}; + + while (j < rSortArr.size()) + { + SwTOXSortTabBase* pOld = rSortArr[j].get(); + if (pNew->sort_lt(*pOld)) + break; + ++j; + } + rSortArr.insert(rSortArr.begin() + j, std::move(pNew)); + } + }; + InsertImpl(aSortArr); + if (!sw::IsFieldDeletedInModel(rIDRA, *pTextField)) + { + InsertImpl(aSortArrRLHidden); + } + } + + for(auto & pBase : aSortArr) + { + SwFormatField& rFormatField = static_cast<SwTOXAuthority&>(*pBase).GetFieldFormat(); + SwAuthorityField* pAField = static_cast<SwAuthorityField*>(rFormatField.GetField()); + m_SequArr.push_back(pAField->GetAuthEntry()); + } + for (auto & pBase : aSortArrRLHidden) + { + SwFormatField& rFormatField = static_cast<SwTOXAuthority&>(*pBase).GetFieldFormat(); + SwAuthorityField* pAField = static_cast<SwAuthorityField*>(rFormatField.GetField()); + m_SequArrRLHidden.push_back(pAField->GetAuthEntry()); + } + } + //find nHandle + auto const& rSequArr(pLayout && pLayout->IsHideRedlines() ? m_SequArrRLHidden : m_SequArr); + for (std::vector<sal_IntPtr>::size_type i = 0; i < rSequArr.size(); ++i) + { + if (rSequArr[i] == pAuthEntry) + { + return i + 1; + } + } + return 0; +} + +void SwAuthorityFieldType::QueryValue( Any& rVal, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + case FIELD_PROP_PAR2: + { + OUString sVal; + sal_Unicode uRet = FIELD_PROP_PAR1 == nWhichId ? m_cPrefix : m_cSuffix; + if(uRet) + sVal = OUString(uRet); + rVal <<= sVal; + } + break; + case FIELD_PROP_PAR3: + rVal <<= GetSortAlgorithm(); + break; + + case FIELD_PROP_BOOL1: + rVal <<= m_bIsSequence; + break; + + case FIELD_PROP_BOOL2: + rVal <<= m_bSortByDocument; + break; + + case FIELD_PROP_LOCALE: + rVal <<= LanguageTag(GetLanguage()).getLocale(); + break; + + case FIELD_PROP_PROP_SEQ: + { + Sequence<PropertyValues> aRet(m_SortKeyArr.size()); + PropertyValues* pValues = aRet.getArray(); + for(SortKeyArr::size_type i = 0; i < m_SortKeyArr.size(); ++i) + { + const SwTOXSortKey* pKey = &m_SortKeyArr[i]; + pValues[i].realloc(2); + PropertyValue* pValue = pValues[i].getArray(); + pValue[0].Name = UNO_NAME_SORT_KEY; + pValue[0].Value <<= sal_Int16(pKey->eField); + pValue[1].Name = UNO_NAME_IS_SORT_ASCENDING; + pValue[1].Value <<= pKey->bSortAscending; + } + rVal <<= aRet; + } + break; + default: + assert(false); + } +} + +void SwAuthorityFieldType::PutValue( const Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + case FIELD_PROP_PAR2: + { + OUString sTmp; + rAny >>= sTmp; + const sal_Unicode uSet = !sTmp.isEmpty() ? sTmp[0] : 0; + if( FIELD_PROP_PAR1 == nWhichId ) + m_cPrefix = uSet; + else + m_cSuffix = uSet; + } + break; + case FIELD_PROP_PAR3: + { + OUString sTmp; + rAny >>= sTmp; + SetSortAlgorithm(sTmp); + break; + } + case FIELD_PROP_BOOL1: + m_bIsSequence = *o3tl::doAccess<bool>(rAny); + break; + case FIELD_PROP_BOOL2: + m_bSortByDocument = *o3tl::doAccess<bool>(rAny); + break; + + case FIELD_PROP_LOCALE: + { + css::lang::Locale aLocale; + if( rAny >>= aLocale ) + SetLanguage( LanguageTag::convertToLanguageType( aLocale )); + } + break; + + case FIELD_PROP_PROP_SEQ: + { + Sequence<PropertyValues> aSeq; + if( rAny >>= aSeq ) + { + m_SortKeyArr.clear(); + const PropertyValues* pValues = aSeq.getConstArray(); + //TODO: Limiting to the first SAL_MAX_UINT16 elements of aSeq so that size of + // m_SortKeyArr remains in range of sal_uInt16, as GetSortKeyCount and GetSortKey + // still expect m_SortKeyArr to be indexed by sal_uInt16: + auto nSize = std::min<sal_Int32>(aSeq.getLength(), SAL_MAX_UINT16); + for(sal_Int32 i = 0; i < nSize; i++) + { + SwTOXSortKey aSortKey; + for(const PropertyValue& rValue : pValues[i]) + { + if(rValue.Name == UNO_NAME_SORT_KEY) + { + sal_Int16 nVal = -1; rValue.Value >>= nVal; + if(nVal >= 0 && nVal < AUTH_FIELD_END) + aSortKey.eField = static_cast<ToxAuthorityField>(nVal); + } + else if(rValue.Name == UNO_NAME_IS_SORT_ASCENDING) + { + aSortKey.bSortAscending = *o3tl::doAccess<bool>(rValue.Value); + } + } + m_SortKeyArr.push_back(aSortKey); + } + } + } + break; + default: + assert(false); + } +} + +void SwAuthorityFieldType::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + //re-generate positions of the fields + DelSequenceArray(); + NotifyClients( pOld, pNew ); +} + +sal_uInt16 SwAuthorityFieldType::GetSortKeyCount() const +{ + return m_SortKeyArr.size(); +} + +const SwTOXSortKey* SwAuthorityFieldType::GetSortKey(sal_uInt16 nIdx) const +{ + if(m_SortKeyArr.size() > nIdx) + return &m_SortKeyArr[nIdx]; + OSL_FAIL("Sort key not found"); + return nullptr; +} + +void SwAuthorityFieldType::SetSortKeys(sal_uInt16 nKeyCount, SwTOXSortKey const aKeys[]) +{ + m_SortKeyArr.clear(); + for(sal_uInt16 i = 0; i < nKeyCount; i++) + if(aKeys[i].eField < AUTH_FIELD_END) + m_SortKeyArr.push_back(aKeys[i]); +} + +SwAuthorityField::SwAuthorityField( SwAuthorityFieldType* pInitType, + const OUString& rFieldContents ) + : SwField(pInitType) + , m_nTempSequencePos( -1 ) + , m_nTempSequencePosRLHidden( -1 ) +{ + m_xAuthEntry = pInitType->AddField( rFieldContents ); +} + +SwAuthorityField::SwAuthorityField( SwAuthorityFieldType* pInitType, + SwAuthEntry* pAuthEntry ) + : SwField( pInitType ) + , m_xAuthEntry( pAuthEntry ) + , m_nTempSequencePos( -1 ) + , m_nTempSequencePosRLHidden( -1 ) +{ +} + +SwAuthorityField::~SwAuthorityField() +{ + static_cast<SwAuthorityFieldType* >(GetTyp())->RemoveField(m_xAuthEntry.get()); +} + +OUString SwAuthorityField::ExpandImpl(SwRootFrame const*const pLayout) const +{ + return ConditionalExpandAuthIdentifier(pLayout); +} + +OUString SwAuthorityField::ConditionalExpandAuthIdentifier( + SwRootFrame const*const pLayout) const +{ + SwAuthorityFieldType* pAuthType = static_cast<SwAuthorityFieldType*>(GetTyp()); + OUString sRet; + if(pAuthType->GetPrefix()) + sRet = OUString(pAuthType->GetPrefix()); + + if( pAuthType->IsSequence() ) + { + sal_IntPtr & rnTempSequencePos(pLayout && pLayout->IsHideRedlines() + ? m_nTempSequencePosRLHidden : m_nTempSequencePos); + if(!pAuthType->GetDoc()->getIDocumentFieldsAccess().IsExpFieldsLocked()) + rnTempSequencePos = pAuthType->GetSequencePos(m_xAuthEntry.get(), pLayout); + if (0 <= rnTempSequencePos) + sRet += OUString::number(rnTempSequencePos); + } + else + { + //TODO: Expand to: identifier, number sequence, ... + if(m_xAuthEntry) + { + OUString sIdentifier(m_xAuthEntry->GetAuthorField(AUTH_FIELD_IDENTIFIER)); + // tdf#107784 Use title if it's a ooxml citation + if (sIdentifier.trim().startsWith("CITATION")) + return m_xAuthEntry->GetAuthorField(AUTH_FIELD_TITLE); + else + sRet += sIdentifier; + } + } + if(pAuthType->GetSuffix()) + sRet += OUStringChar(pAuthType->GetSuffix()); + return sRet; +} + +OUString SwAuthorityField::ExpandCitation(ToxAuthorityField eField, + SwRootFrame const*const pLayout) const +{ + SwAuthorityFieldType* pAuthType = static_cast<SwAuthorityFieldType*>(GetTyp()); + OUString sRet; + + if( pAuthType->IsSequence() ) + { + sal_IntPtr & rnTempSequencePos(pLayout && pLayout->IsHideRedlines() + ? m_nTempSequencePosRLHidden : m_nTempSequencePos); + if(!pAuthType->GetDoc()->getIDocumentFieldsAccess().IsExpFieldsLocked()) + rnTempSequencePos = pAuthType->GetSequencePos(m_xAuthEntry.get(), pLayout); + if (0 <= rnTempSequencePos) + sRet += OUString::number(rnTempSequencePos); + } + else + { + //TODO: Expand to: identifier, number sequence, ... + if(m_xAuthEntry) + sRet += m_xAuthEntry->GetAuthorField(eField); + } + return sRet; +} + +std::unique_ptr<SwField> SwAuthorityField::Copy() const +{ + SwAuthorityFieldType* pAuthType = static_cast<SwAuthorityFieldType*>(GetTyp()); + return std::make_unique<SwAuthorityField>(pAuthType, m_xAuthEntry.get()); +} + +const OUString & SwAuthorityField::GetFieldText(ToxAuthorityField eField) const +{ + return m_xAuthEntry->GetAuthorField( eField ); +} + +void SwAuthorityField::SetPar1(const OUString& rStr) +{ + SwAuthorityFieldType* pInitType = static_cast<SwAuthorityFieldType* >(GetTyp()); + pInitType->RemoveField(m_xAuthEntry.get()); + m_xAuthEntry = pInitType->AddField(rStr); +} + +OUString SwAuthorityField::GetDescription() const +{ + return SwResId(STR_AUTHORITY_ENTRY); +} + +const char* const aFieldNames[] = +{ + "Identifier", + "BibiliographicType", + "Address", + "Annote", + "Author", + "Booktitle", + "Chapter", + "Edition", + "Editor", + "Howpublished", + "Institution", + "Journal", + "Month", + "Note", + "Number", + "Organizations", + "Pages", + "Publisher", + "School", + "Series", + "Title", + "Report_Type", + "Volume", + "Year", + "URL", + "Custom1", + "Custom2", + "Custom3", + "Custom4", + "Custom5", + "ISBN" +}; + +bool SwAuthorityField::QueryValue( Any& rAny, sal_uInt16 /*nWhichId*/ ) const +{ + if(!GetTyp()) + return false; + if(!m_xAuthEntry) + return false; + Sequence <PropertyValue> aRet(AUTH_FIELD_END); + PropertyValue* pValues = aRet.getArray(); + for(int i = 0; i < AUTH_FIELD_END; ++i) + { + pValues[i].Name = OUString::createFromAscii(aFieldNames[i]); + const OUString& sField = m_xAuthEntry->GetAuthorField(static_cast<ToxAuthorityField>(i)); + if(i == AUTH_FIELD_AUTHORITY_TYPE) + pValues[i].Value <<= sal_Int16(sField.toInt32()); + else + pValues[i].Value <<= sField; + } + rAny <<= aRet; + /* FIXME: it is weird that we always return false here */ + return false; +} + +static sal_Int32 lcl_Find(const OUString& rFieldName) +{ + for(sal_Int32 i = 0; i < AUTH_FIELD_END; ++i) + if(rFieldName.equalsAscii(aFieldNames[i])) + return i; + return -1; +} + +bool SwAuthorityField::PutValue( const Any& rAny, sal_uInt16 /*nWhichId*/ ) +{ + if(!GetTyp() || !m_xAuthEntry) + return false; + + Sequence <PropertyValue> aParam; + if(!(rAny >>= aParam)) + return false; + + OUStringBuffer sBuf; + comphelper::string::padToLength(sBuf, AUTH_FIELD_ISBN, TOX_STYLE_DELIMITER); + OUString sToSet(sBuf.makeStringAndClear()); + for(const PropertyValue& rParam : std::as_const(aParam)) + { + const sal_Int32 nFound = lcl_Find(rParam.Name); + if(nFound >= 0) + { + OUString sContent; + if(AUTH_FIELD_AUTHORITY_TYPE == nFound) + { + sal_Int16 nVal = 0; + rParam.Value >>= nVal; + sContent = OUString::number(nVal); + } + else + rParam.Value >>= sContent; + sToSet = comphelper::string::setToken(sToSet, nFound, TOX_STYLE_DELIMITER, sContent); + } + } + + static_cast<SwAuthorityFieldType*>(GetTyp())->RemoveField(m_xAuthEntry.get()); + m_xAuthEntry = static_cast<SwAuthorityFieldType*>(GetTyp())->AddField(sToSet); + + /* FIXME: it is weird that we always return false here */ + return false; +} + +SwFieldType* SwAuthorityField::ChgTyp( SwFieldType* pFieldTyp ) +{ + SwAuthorityFieldType* pSrcTyp = static_cast<SwAuthorityFieldType*>(GetTyp()), + * pDstTyp = static_cast<SwAuthorityFieldType*>(pFieldTyp); + if( pSrcTyp != pDstTyp ) + { + const SwAuthEntry* pSrcEntry = m_xAuthEntry.get(); + pDstTyp->AppendField( *pSrcEntry ); + pSrcTyp->RemoveField( pSrcEntry ); + SwField::ChgTyp( pFieldTyp ); + } + return pSrcTyp; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/cellfml.cxx b/sw/source/core/fields/cellfml.cxx new file mode 100644 index 000000000..db2375997 --- /dev/null +++ b/sw/source/core/fields/cellfml.cxx @@ -0,0 +1,1218 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <float.h> +#include <hintids.hxx> +#include <hints.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <frmfmt.hxx> +#include <layfrm.hxx> +#include <cntfrm.hxx> +#include <tabfrm.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <tblsel.hxx> +#include <cellfml.hxx> +#include <calc.hxx> +#include <expfld.hxx> +#include <usrfld.hxx> +#include <flddat.hxx> +#include <cellatr.hxx> +#include <ndindex.hxx> +#include <frameformats.hxx> +#include <comphelper/string.hxx> +#include <o3tl/safeint.hxx> + +namespace +{ + +const sal_Unicode cRelSeparator = ','; +const sal_Unicode cRelIdentifier = '\x12'; // CTRL-R + +enum +{ + cMAXSTACKSIZE = 50 +}; + +} + +static const SwFrame* lcl_GetBoxFrame( const SwTableBox& rBox ); +static sal_Int32 lcl_GetLongBoxNum( OUString& rStr ); +static const SwTableBox* lcl_RelToBox( const SwTable& rTable, + const SwTableBox* pRefBox, + const OUString& sGetName); +static OUString lcl_BoxNmToRel( const SwTable& rTable, + const SwTableNode& rTableNd, + const OUString& sRefBoxNm, + const OUString& sGetStr, + bool bExtrnlNm); + +/** Get value of this box. + * + * The value is comes from the first TextNode. If it starts with a number/ + * formula then calculate it, if it starts with a field then get the value. + * All other conditions return 0 (and an error?). + */ +double SwTableBox::GetValue( SwTableCalcPara& rCalcPara ) const +{ + double nRet = 0; + + if( rCalcPara.m_rCalc.IsCalcError() ) + return nRet; // stop if there is already an error set + + rCalcPara.m_rCalc.SetCalcError( SwCalcError::Syntax ); // default: error + + // no content box? + if( !m_pStartNode ) + return nRet; + + if( rCalcPara.IncStackCnt() ) + return nRet; + + rCalcPara.SetLastTableBox( this ); + + // Does it create a recursion? + SwTableBox* pBox = const_cast<SwTableBox*>(this); + if( rCalcPara.m_pBoxStack->find( pBox ) != rCalcPara.m_pBoxStack->end() ) + return nRet; // already on the stack: error + + // re-start with this box + rCalcPara.SetLastTableBox( this ); + + rCalcPara.m_pBoxStack->insert( pBox ); // add + do { // Middle-Check-Loop, so that we can jump from here. Used so that the box pointer + // will be removed from stack at the end. + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + + const SfxPoolItem* pItem; + if( SfxItemState::SET == GetFrameFormat()->GetItemState( + RES_BOXATR_FORMULA, false, &pItem ) ) + { + rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status + if( !static_cast<const SwTableBoxFormula*>(pItem)->IsValid() ) + { + // calculate + const SwTable* pTmp = rCalcPara.m_pTable; + rCalcPara.m_pTable = &pBox->GetSttNd()->FindTableNode()->GetTable(); + const_cast<SwTableBoxFormula*>(static_cast<const SwTableBoxFormula*>(pItem))->Calc( rCalcPara, nRet ); + + if( !rCalcPara.IsStackOverflow() ) + { + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + SfxItemSet aTmp( pDoc->GetAttrPool(), + svl::Items<RES_BOXATR_BEGIN,RES_BOXATR_END-1>{} ); + aTmp.Put( SwTableBoxValue( nRet ) ); + if( SfxItemState::SET != pFormat->GetItemState( RES_BOXATR_FORMAT )) + aTmp.Put( SwTableBoxNumFormat( 0 )); + pFormat->SetFormatAttr( aTmp ); + } + rCalcPara.m_pTable = pTmp; + } + else + nRet = GetFrameFormat()->GetTableBoxValue().GetValue(); + break; + } + else if( SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_VALUE, false, &pItem ) ) + { + rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status + nRet = static_cast<const SwTableBoxValue*>(pItem)->GetValue(); + break; + } + + SwTextNode* pTextNd = pDoc->GetNodes()[ m_pStartNode->GetIndex() + 1 ]->GetTextNode(); + if( !pTextNd ) + break; + + sal_Int32 nSttPos = 0; + OUString sText = pTextNd->GetText(); + while ( nSttPos < sText.getLength() && ( sText[nSttPos]==' ' || sText[nSttPos]=='\t' ) ) + ++nSttPos; + + // if there is a calculation field at position 1, get the value of it + const bool bOK = nSttPos<sText.getLength(); + const sal_Unicode Char = bOK ? sText[nSttPos] : 0; + SwTextField * pTextField = nullptr; + if ( bOK && (Char==CH_TXTATR_BREAKWORD || Char==CH_TXTATR_INWORD) ) + { + pTextField = static_txtattr_cast<SwTextField*>(pTextNd->GetTextAttrForCharAt(nSttPos, RES_TXTATR_FIELD)); + } + if ( pTextField != nullptr ) + { + rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status + + const SwField* pField = pTextField->GetFormatField().GetField(); + switch ( pField->GetTyp()->Which() ) + { + case SwFieldIds::SetExp: + nRet = static_cast<const SwSetExpField*>(pField)->GetValue(rCalcPara.m_pLayout); + break; + case SwFieldIds::User: + nRet = static_cast<const SwUserField*>(pField)->GetValue(); + break; + case SwFieldIds::Table: + { + SwTableField* pTableField = const_cast<SwTableField*>(static_cast<const SwTableField*>(pField)); + if( !pTableField->IsValid() ) + { + // use the right table! + const SwTable* pTmp = rCalcPara.m_pTable; + rCalcPara.m_pTable = &pTextNd->FindTableNode()->GetTable(); + pTableField->CalcField( rCalcPara ); + rCalcPara.m_pTable = pTmp; + } + nRet = pTableField->GetValue(); + } + break; + + case SwFieldIds::DateTime: + nRet = static_cast<const SwDateTimeField*>( pField )->GetValue(); + break; + + case SwFieldIds::JumpEdit: + //JP 14.09.98: Bug 56112 - placeholder never have the right content! + nRet = 0; + break; + + default: + nRet = rCalcPara.m_rCalc.Calculate( pField->ExpandField(true, nullptr) ).GetDouble(); + } + } + else if ( nSttPos < sText.getLength() + && Char == CH_TXT_ATR_INPUTFIELDSTART ) + { + const SwTextInputField * pTextInputField = + dynamic_cast< const SwTextInputField* >( + pTextNd->GetTextAttrAt( nSttPos, RES_TXTATR_INPUTFIELD ) ); + if ( pTextInputField == nullptr ) + break; + nRet = rCalcPara.m_rCalc.Calculate( pTextInputField->GetFieldContent() ).GetDouble(); + } + else if ( Char != CH_TXTATR_BREAKWORD ) + { + // result is 0 but no error! + rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status + + double aNum = 0.0; + sText = bOK ? sText.copy( nSttPos ) : OUString(); + sal_uInt32 nFormatIndex = GetFrameFormat()->GetTableBoxNumFormat().GetValue(); + + SvNumberFormatter* pNumFormatr = pDoc->GetNumberFormatter(); + + const SvNumFormatType nFormatType = pNumFormatr->GetType( nFormatIndex ); + if( nFormatType == SvNumFormatType::TEXT ) + nFormatIndex = 0; + // JP 22.04.98: Bug 49659 - special treatment for percentages + else if( !sText.isEmpty() && + SvNumFormatType::PERCENT == nFormatType) + { + sal_uInt32 nTmpFormat = 0; + if( pDoc->IsNumberFormat( sText, nTmpFormat, aNum ) && + SvNumFormatType::NUMBER == pNumFormatr->GetType( nTmpFormat )) + sText += "%"; + } + + if( pDoc->IsNumberFormat( sText, nFormatIndex, aNum )) + nRet = aNum; + } + // ?? otherwise it is an error + } while( false ); + + if( !rCalcPara.IsStackOverflow() ) + { + rCalcPara.m_pBoxStack->erase( pBox ); // remove from stack + rCalcPara.DecStackCnt(); + } + + //JP 12.01.99: error detection, Bug 60794 + if( DBL_MAX == nRet ) + rCalcPara.m_rCalc.SetCalcError( SwCalcError::Syntax ); // set error + + return nRet; +} + +// structure needed for calculation of tables + +SwTableCalcPara::SwTableCalcPara(SwCalc& rCalculator, const SwTable& rTable, + SwRootFrame const*const pLayout) + : m_pLastTableBox(nullptr) + , m_nStackCount( 0 ) + , m_nMaxSize( cMAXSTACKSIZE ) + , m_pLayout(pLayout) + , m_pBoxStack( new SwTableSortBoxes ) + , m_rCalc( rCalculator ) + , m_pTable( &rTable ) +{ +} + +SwTableCalcPara::~SwTableCalcPara() +{ +} + +bool SwTableCalcPara::CalcWithStackOverflow() +{ + // If a stack overflow was detected, redo with last box. + sal_uInt16 nSaveMaxSize = m_nMaxSize; + + m_nMaxSize = cMAXSTACKSIZE - 5; + sal_uInt16 nCnt = 0; + SwTableBoxes aStackOverflows; + do { + SwTableBox* pBox = const_cast<SwTableBox*>(m_pLastTableBox); + m_nStackCount = 0; + m_rCalc.SetCalcError( SwCalcError::NONE ); + aStackOverflows.insert( aStackOverflows.begin() + nCnt++, pBox ); + + m_pBoxStack->erase( pBox ); + pBox->GetValue( *this ); + } while( IsStackOverflow() ); + + m_nMaxSize = cMAXSTACKSIZE - 3; // decrease at least one level + + // if recursion was detected + m_nStackCount = 0; + m_rCalc.SetCalcError( SwCalcError::NONE ); + m_pBoxStack->clear(); + + while( !m_rCalc.IsCalcError() && nCnt ) + { + aStackOverflows[ --nCnt ]->GetValue( *this ); + if( IsStackOverflow() && !CalcWithStackOverflow() ) + break; + } + + m_nMaxSize = nSaveMaxSize; + aStackOverflows.clear(); + return !m_rCalc.IsCalcError(); +} + +SwTableFormula::SwTableFormula( const OUString& rFormula ) +: m_sFormula( rFormula ) +, m_eNmType( EXTRNL_NAME ) +, m_bValidValue( false ) +{ +} + +SwTableFormula::~SwTableFormula() +{ +} + +void SwTableFormula::MakeFormula_( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + SwTableCalcPara* pCalcPara = static_cast<SwTableCalcPara*>(pPara); + if( pCalcPara->m_rCalc.IsCalcError() ) // stop if there is already an error set + return; + + SwTableBox *pEndBox = nullptr; + + rFirstBox = rFirstBox.copy(1); // erase label of this box + // a region in this area? + if( pLastBox ) + { + pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64())); + + // Is it actually a valid pointer? + if( rTable.GetTabSortBoxes().find( pEndBox ) == rTable.GetTabSortBoxes().end() ) + pEndBox = nullptr; + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + SwTableBox* pSttBox = reinterpret_cast<SwTableBox*>( + sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64())); + // Is it actually a valid pointer? + if( rTable.GetTabSortBoxes().find( pSttBox ) == rTable.GetTabSortBoxes().end() ) + pSttBox = nullptr; + + rNewStr.append(" "); + if( pEndBox && pSttBox ) // area? + { + // get all selected boxes via layout and calculate their values + SwSelBoxes aBoxes; + GetBoxes( *pSttBox, *pEndBox, aBoxes ); + + rNewStr.append("("); + bool bDelim = false; + for (size_t n = 0; n < aBoxes.size() && + !pCalcPara->m_rCalc.IsCalcError(); ++n) + { + const SwTableBox* pTableBox = aBoxes[n]; + if ( pTableBox->getRowSpan() >= 1 ) + { + if( bDelim ) + rNewStr.append(cListDelim); + bDelim = true; + rNewStr.append(pCalcPara->m_rCalc.GetStrResult( + pTableBox->GetValue( *pCalcPara ) )); + } + } + rNewStr.append(")"); + } + else if( pSttBox && !pLastBox ) // only the StartBox? + { + // JP 12.01.99: and no EndBox in the formula! + // calculate the value of the box + if ( pSttBox->getRowSpan() >= 1 ) + { + rNewStr.append("("); + rNewStr.append(pCalcPara->m_rCalc.GetStrResult( + pSttBox->GetValue( *pCalcPara ) )); + rNewStr.append(")"); + } + } + else + pCalcPara->m_rCalc.SetCalcError( SwCalcError::Syntax ); // set error + rNewStr.append(" "); +} + +void SwTableFormula::RelNmsToBoxNms( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + // relative name w.r.t. box name (external presentation) + SwNode* pNd = static_cast<SwNode*>(pPara); + OSL_ENSURE( pNd, "Field isn't in any TextNode" ); + const SwTableBox *pBox = rTable.GetTableBox( + pNd->FindTableBoxStartNode()->GetIndex() ); + + rNewStr.append(rFirstBox[0]); // get label for the box + rFirstBox = rFirstBox.copy(1); + if( pLastBox ) + { + const SwTableBox *pRelLastBox = lcl_RelToBox( rTable, pBox, *pLastBox ); + if ( pRelLastBox ) + rNewStr.append(pRelLastBox->GetName()); + else + rNewStr.append("A1"); + rNewStr.append(":"); + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + + const SwTableBox *pRelFirstBox = lcl_RelToBox( rTable, pBox, rFirstBox ); + + if (pRelFirstBox) + rNewStr.append(pRelFirstBox->GetName()); + else + rNewStr.append("A1"); + + // get label for the box + rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]); +} + +void SwTableFormula::RelBoxNmsToPtr( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + // relative name w.r.t. box name (internal presentation) + SwNode* pNd = static_cast<SwNode*>(pPara); + OSL_ENSURE( pNd, "Field not placed in any Node" ); + const SwTableBox *pBox = rTable.GetTableBox( + pNd->FindTableBoxStartNode()->GetIndex() ); + + rNewStr.append(rFirstBox[0]); // get label for the box + rFirstBox = rFirstBox.copy(1); + if( pLastBox ) + { + const SwTableBox *pRelLastBox = lcl_RelToBox( rTable, pBox, *pLastBox ); + if ( pRelLastBox ) + rNewStr.append(OUString::number(reinterpret_cast<sal_PtrDiff>(pRelLastBox))); + else + rNewStr.append("0"); + rNewStr.append(":"); + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + + const SwTableBox *pRelFirstBox = lcl_RelToBox( rTable, pBox, rFirstBox ); + if ( pRelFirstBox ) + rNewStr.append(OUString::number(reinterpret_cast<sal_PtrDiff>(pRelFirstBox))); + else + rNewStr.append("0"); + + // get label for the box + rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]); +} + +void SwTableFormula::BoxNmsToRelNm( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + // box name (external presentation) w.r.t. relative name + SwNode* pNd = static_cast<SwNode*>(pPara); + OSL_ENSURE( pNd, "Field not placed in any Node" ); + const SwTableNode* pTableNd = pNd->FindTableNode(); + + OUString sRefBoxNm; + if( &pTableNd->GetTable() == &rTable ) + { + const SwTableBox *pBox = rTable.GetTableBox( + pNd->FindTableBoxStartNode()->GetIndex() ); + OSL_ENSURE( pBox, "Field not placed in any Table" ); + sRefBoxNm = pBox->GetName(); + } + + rNewStr.append(rFirstBox[0]); // get label for the box + rFirstBox = rFirstBox.copy(1); + if( pLastBox ) + { + rNewStr.append(lcl_BoxNmToRel( rTable, *pTableNd, sRefBoxNm, *pLastBox, + m_eNmType == EXTRNL_NAME )); + rNewStr.append(":"); + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + + rNewStr.append(lcl_BoxNmToRel( rTable, *pTableNd, sRefBoxNm, rFirstBox, + m_eNmType == EXTRNL_NAME )); + + // get label for the box + rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]); +} + +void SwTableFormula::PtrToBoxNms( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* ) const +{ + // area in these parentheses? + SwTableBox* pBox; + + rNewStr.append(rFirstBox[0]); // get label for the box + rFirstBox = rFirstBox.copy(1); + if( pLastBox ) + { + pBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64())); + + // Is it actually a valid pointer? + if( rTable.GetTabSortBoxes().find( pBox ) != rTable.GetTabSortBoxes().end() ) + rNewStr.append(pBox->GetName()); + else + rNewStr.append("?"); + rNewStr.append(":"); + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + + pBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64())); + // Is it actually a valid pointer? + if( rTable.GetTabSortBoxes().find( pBox ) != rTable.GetTabSortBoxes().end() ) + rNewStr.append(pBox->GetName()); + else + rNewStr.append("?"); + + // get label for the box + rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]); +} + +void SwTableFormula::BoxNmsToPtr( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* ) const +{ + // area in these parentheses? + const SwTableBox* pBox; + + rNewStr.append(rFirstBox[0]); // get label for the box + rFirstBox = rFirstBox.copy(1); + if( pLastBox ) + { + pBox = rTable.GetTableBox( *pLastBox ); + rNewStr.append(OUString::number(reinterpret_cast<sal_PtrDiff>(pBox))) + .append(":"); + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + + pBox = rTable.GetTableBox( rFirstBox ); + rNewStr.append(OUString::number(reinterpret_cast<sal_PtrDiff>(pBox))) + .append(rFirstBox[ rFirstBox.getLength()-1 ]); // get label for the box +} + +/// create external formula (for UI) +void SwTableFormula::PtrToBoxNm( const SwTable* pTable ) +{ + const SwNode* pNd = nullptr; + FnScanFormula fnFormula = nullptr; + switch (m_eNmType) + { + case INTRNL_NAME: + if( pTable ) + fnFormula = &SwTableFormula::PtrToBoxNms; + break; + case REL_NAME: + if( pTable ) + { + fnFormula = &SwTableFormula::RelNmsToBoxNms; + pNd = GetNodeOfFormula(); + } + break; + case EXTRNL_NAME: + return; + } + m_sFormula = ScanString( fnFormula, *pTable, const_cast<void*>(static_cast<void const *>(pNd)) ); + m_eNmType = EXTRNL_NAME; +} + +/// create internal formula (in CORE) +void SwTableFormula::BoxNmToPtr( const SwTable* pTable ) +{ + const SwNode* pNd = nullptr; + FnScanFormula fnFormula = nullptr; + switch (m_eNmType) + { + case EXTRNL_NAME: + if( pTable ) + fnFormula = &SwTableFormula::BoxNmsToPtr; + break; + case REL_NAME: + if( pTable ) + { + fnFormula = &SwTableFormula::RelBoxNmsToPtr; + pNd = GetNodeOfFormula(); + } + break; + case INTRNL_NAME: + return; + } + m_sFormula = ScanString( fnFormula, *pTable, const_cast<void*>(static_cast<void const *>(pNd)) ); + m_eNmType = INTRNL_NAME; +} + +/// create relative formula (for copy) +void SwTableFormula::ToRelBoxNm( const SwTable* pTable ) +{ + const SwNode* pNd = nullptr; + FnScanFormula fnFormula = nullptr; + switch (m_eNmType) + { + case INTRNL_NAME: + case EXTRNL_NAME: + if( pTable ) + { + fnFormula = &SwTableFormula::BoxNmsToRelNm; + pNd = GetNodeOfFormula(); + } + break; + case REL_NAME: + return; + } + m_sFormula = ScanString( fnFormula, *pTable, const_cast<void*>(static_cast<void const *>(pNd)) ); + m_eNmType = REL_NAME; +} + +OUString SwTableFormula::ScanString( FnScanFormula fnFormula, const SwTable& rTable, + void* pPara ) const +{ + OUStringBuffer aStr; + sal_Int32 nFormula = 0; + sal_Int32 nEnd = 0; + + do { + // If the formula is preceded by a name, use this table! + const SwTable* pTable = &rTable; + + sal_Int32 nStt = m_sFormula.indexOf( '<', nFormula ); + if ( nStt>=0 ) + { + while ( nStt>=0 ) + { + const sal_Int32 nNxt = nStt+1; + if (nNxt>=m_sFormula.getLength()) + { + nStt = -1; + break; + } + if ( m_sFormula[nNxt]!=' ' && m_sFormula[nNxt]!='=' ) + break; + nStt = m_sFormula.indexOf( '<', nNxt ); + } + + if ( nStt>=0 ) + // Start searching from current position, which is valid for sure + nEnd = m_sFormula.indexOf( '>', nStt ); + } + if (nStt<0 || nEnd<0 ) + { + // set the rest and finish + aStr.append(std::u16string_view(m_sFormula).substr(nFormula)); + break; + } + + // write beginning + aStr.append(std::u16string_view(m_sFormula).substr(nFormula, nStt - nFormula)); + + if (fnFormula) + { + sal_Int32 nSeparator = 0; + // Is a table name preceded? + // JP 16.02.99: SplitMergeBoxNm take care of the name themself + // JP 22.02.99: Linux compiler needs cast + // JP 28.06.99: rel. BoxName has no preceding tablename! + if( fnFormula != &SwTableFormula::SplitMergeBoxNm_ && + m_sFormula.getLength()>(nStt+1) && cRelIdentifier != m_sFormula[nStt+1] && + (nSeparator = m_sFormula.indexOf( '.', nStt ))>=0 + && nSeparator < nEnd ) + { + OUString sTableNm( m_sFormula.copy( nStt, nEnd - nStt )); + + // If there are dots in the name, then they appear in pairs (e.g. A1.1.1)! + if( (comphelper::string::getTokenCount(sTableNm, '.') - 1) & 1 ) + { + sTableNm = sTableNm.copy( 0, nSeparator - nStt ); + + // when creating a formula the table name is unwanted + if( fnFormula != &SwTableFormula::MakeFormula_ ) + aStr.append(sTableNm); + nStt = nSeparator; + + sTableNm = sTableNm.copy( 1 ); // delete separator + if( sTableNm != rTable.GetFrameFormat()->GetName() ) + { + // then search for table + const SwTable* pFnd = FindTable( + *rTable.GetFrameFormat()->GetDoc(), + sTableNm ); + if( pFnd ) + pTable = pFnd; + // ?? + OSL_ENSURE( pFnd, "No table found. What now?" ); + } + } + } + + OUString sBox( m_sFormula.copy( nStt, nEnd - nStt + 1 )); + // area in these parentheses? + nSeparator = m_sFormula.indexOf( ':', nStt ); + if ( nSeparator>=0 && nSeparator<nEnd ) + { + // without opening parenthesis + OUString aFirstBox( m_sFormula.copy( nStt+1, nSeparator - nStt - 1 )); + (this->*fnFormula)( *pTable, aStr, sBox, &aFirstBox, pPara ); + } + else + (this->*fnFormula)( *pTable, aStr, sBox, nullptr, pPara ); + } + + nFormula = nEnd+1; + } while( true ); + return aStr.makeStringAndClear(); +} + +const SwTable* SwTableFormula::FindTable( SwDoc& rDoc, const OUString& rNm ) +{ + const SwFrameFormats& rTableFormats = *rDoc.GetTableFrameFormats(); + const SwTable* pTmpTable = nullptr, *pRet = nullptr; + for( auto nFormatCnt = rTableFormats.size(); nFormatCnt; ) + { + SwFrameFormat* pFormat = rTableFormats[ --nFormatCnt ]; + // if we are called from Sw3Writer, a number is dependent on the format name + SwTableBox* pFBox; + if ( rNm == pFormat->GetName().getToken(0, 0x0a) && + nullptr != ( pTmpTable = SwTable::FindTable( pFormat ) ) && + nullptr != (pFBox = pTmpTable->GetTabSortBoxes()[0] ) && + pFBox->GetSttNd() && + pFBox->GetSttNd()->GetNodes().IsDocNodes() ) + { + // a table in the normal NodesArr + pRet = pTmpTable; + break; + } + } + return pRet; +} + +static const SwFrame* lcl_GetBoxFrame( const SwTableBox& rBox ) +{ + SwNodeIndex aIdx( *rBox.GetSttNd() ); + SwContentNode* pCNd = aIdx.GetNodes().GoNext( &aIdx ); + OSL_ENSURE( pCNd, "Box has no TextNode" ); + Point aPt; // get the first frame of the layout - table headline + std::pair<Point, bool> const tmp(aPt, false); + return pCNd->getLayoutFrame(pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); +} + +static sal_Int32 lcl_GetLongBoxNum( OUString& rStr ) +{ + sal_Int32 nRet; + const sal_Int32 nPos = rStr.indexOf( cRelSeparator ); + if ( nPos<0 ) + { + nRet = rStr.toInt32(); + rStr.clear(); + } + else + { + nRet = rStr.copy( 0, nPos ).toInt32(); + rStr = rStr.copy( nPos+1 ); + } + return nRet; +} + +static const SwTableBox* lcl_RelToBox( const SwTable& rTable, + const SwTableBox* pRefBox, + const OUString& _sGetName ) +{ + // get line + const SwTableBox* pBox = nullptr; + OUString sGetName = _sGetName; + + // Is it really a relative value? + if ( cRelIdentifier == sGetName[0] ) // yes + { + if( !pRefBox ) + return nullptr; + + sGetName = sGetName.copy( 1 ); + + const SwTableLines* pLines = &rTable.GetTabLines(); + const SwTableBoxes* pBoxes; + const SwTableLine* pLine; + + // determine starting values of the box,... + pBox = pRefBox; + pLine = pBox->GetUpper(); + while( pLine->GetUpper() ) + { + pBox = pLine->GetUpper(); + pLine = pBox->GetUpper(); + } + sal_uInt16 nSttBox = pLine->GetBoxPos( pBox ); + sal_uInt16 nSttLine = rTable.GetTabLines().GetPos( pLine ); + + const sal_Int32 nBoxOffset = lcl_GetLongBoxNum( sGetName ) + nSttBox; + const sal_Int32 nLineOffset = lcl_GetLongBoxNum( sGetName ) + nSttLine; + + if( nBoxOffset < 0 || + nLineOffset < 0 ) + return nullptr; + + if( o3tl::make_unsigned(nLineOffset) >= pLines->size() ) + return nullptr; + + pLine = (*pLines)[ nLineOffset ]; + + // ... then search the box + pBoxes = &pLine->GetTabBoxes(); + if( o3tl::make_unsigned(nBoxOffset) >= pBoxes->size() ) + return nullptr; + pBox = (*pBoxes)[ nBoxOffset ]; + + while (!sGetName.isEmpty()) + { + nSttBox = SwTable::GetBoxNum( sGetName ); + pLines = &pBox->GetTabLines(); + if( nSttBox ) + --nSttBox; + + nSttLine = SwTable::GetBoxNum( sGetName ); + + // determine line + if( !nSttLine || nSttLine > pLines->size() ) + break; + pLine = (*pLines)[ nSttLine-1 ]; + + // determine box + pBoxes = &pLine->GetTabBoxes(); + if( nSttBox >= pBoxes->size() ) + break; + pBox = (*pBoxes)[ nSttBox ]; + } + + if( pBox ) + { + if( !pBox->GetSttNd() ) + // "bubble up" to first box + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + } + } + else + { + // otherwise it is an absolute external presentation + pBox = rTable.GetTableBox( sGetName ); + } + return pBox; +} + +static OUString lcl_BoxNmToRel( const SwTable& rTable, const SwTableNode& rTableNd, + const OUString& _sRefBoxNm, const OUString& _sTmp, bool bExtrnlNm ) +{ + OUString sTmp = _sTmp; + OUString sRefBoxNm = _sRefBoxNm; + if( !bExtrnlNm ) + { + // convert into external presentation + SwTableBox* pBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(sTmp.toInt64())); + if( rTable.GetTabSortBoxes().find( pBox ) == rTable.GetTabSortBoxes().end() ) + return OUString('?'); + sTmp = pBox->GetName(); + } + + // If the formula is spanning over a table then keep external presentation + if( &rTable == &rTableNd.GetTable() ) + { + long nBox = SwTable::GetBoxNum( sTmp, true ); + nBox -= SwTable::GetBoxNum( sRefBoxNm, true ); + long nLine = SwTable::GetBoxNum( sTmp ); + nLine -= SwTable::GetBoxNum( sRefBoxNm ); + + const OUString sCpy = sTmp; //JP 01.11.95: add rest from box name + + sTmp = OUStringChar(cRelIdentifier) + OUString::number( nBox ) + + OUStringChar(cRelSeparator) + OUString::number( nLine ); + + if (!sCpy.isEmpty()) + { + sTmp += OUStringChar(cRelSeparator) + sCpy; + } + } + + if (sTmp.endsWith(">")) + return sTmp.copy(0, sTmp.getLength()-1 ); + + return sTmp; +} + +void SwTableFormula::GetBoxesOfFormula( const SwTable& rTable, + SwSelBoxes& rBoxes ) +{ + rBoxes.clear(); + + BoxNmToPtr( &rTable ); + ScanString( &SwTableFormula::GetFormulaBoxes, rTable, &rBoxes ); +} + +void SwTableFormula::GetFormulaBoxes( const SwTable& rTable, OUStringBuffer& , + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + SwSelBoxes* pBoxes = static_cast<SwSelBoxes*>(pPara); + SwTableBox* pEndBox = nullptr; + + rFirstBox = rFirstBox.copy(1); // delete box label + // area in these parentheses? + if( pLastBox ) + { + pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64())); + + // Is it actually a valid pointer? + if( rTable.GetTabSortBoxes().find( pEndBox ) == rTable.GetTabSortBoxes().end() ) + pEndBox = nullptr; + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + } + + SwTableBox *pSttBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64())); + // Is it actually a valid pointer? + if( !pSttBox || rTable.GetTabSortBoxes().find( pSttBox ) == rTable.GetTabSortBoxes().end() ) + return; + + if ( pEndBox ) // area? + { + // get all selected boxes via layout and calculate their values + SwSelBoxes aBoxes; + GetBoxes( *pSttBox, *pEndBox, aBoxes ); + pBoxes->insert( aBoxes ); + } + else // only the StartBox? + pBoxes->insert( pSttBox ); +} + +void SwTableFormula::GetBoxes( const SwTableBox& rSttBox, + const SwTableBox& rEndBox, + SwSelBoxes& rBoxes ) +{ + // get all selected boxes via layout + const SwLayoutFrame *pStt, *pEnd; + const SwFrame* pFrame = lcl_GetBoxFrame( rSttBox ); + pStt = pFrame ? pFrame->GetUpper() : nullptr; + pFrame = lcl_GetBoxFrame( rEndBox ); + pEnd = pFrame ? pFrame->GetUpper() : nullptr; + if( !pStt || !pEnd ) + return ; // no valid selection + + GetTableSel( pStt, pEnd, rBoxes, nullptr ); + + const SwTable* pTable = pStt->FindTabFrame()->GetTable(); + + // filter headline boxes + if( pTable->GetRowsToRepeat() > 0 ) + { + do { // middle-check loop + const SwTableLine* pLine = rSttBox.GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + if( pTable->IsHeadline( *pLine ) ) + break; // headline in this area! + + // maybe start and end are swapped + pLine = rEndBox.GetUpper(); + while ( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + if( pTable->IsHeadline( *pLine ) ) + break; // headline in this area! + + const SwTabFrame *pStartTable = pStt->FindTabFrame(); + const SwTabFrame *pEndTable = pEnd->FindTabFrame(); + + if (pStartTable == pEndTable) // no split table + break; + + // then remove table headers + for (size_t n = 0; n < rBoxes.size(); ++n) + { + pLine = rBoxes[n]->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + if( pTable->IsHeadline( *pLine ) ) + rBoxes.erase( rBoxes.begin() + n-- ); + } + } while( false ); + } +} + +/// Are all boxes valid that are referenced by the formula? +void SwTableFormula::HasValidBoxes_( const SwTable& rTable, OUStringBuffer& , + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + bool* pBValid = static_cast<bool*>(pPara); + if( *pBValid ) // wrong is wrong + { + SwTableBox* pSttBox = nullptr, *pEndBox = nullptr; + rFirstBox = rFirstBox.copy(1); // delete identifier of box + + // area in this parenthesis? + if( pLastBox ) + rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 ); + + switch (m_eNmType) + { + case INTRNL_NAME: + if( pLastBox ) + pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64())); + pSttBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64())); + break; + + case REL_NAME: + { + const SwNode* pNd = GetNodeOfFormula(); + const SwTableBox* pBox = !pNd ? nullptr + : const_cast<SwTableBox *>(rTable.GetTableBox( + pNd->FindTableBoxStartNode()->GetIndex() )); + if( pLastBox ) + pEndBox = const_cast<SwTableBox*>(lcl_RelToBox( rTable, pBox, *pLastBox )); + pSttBox = const_cast<SwTableBox*>(lcl_RelToBox( rTable, pBox, rFirstBox )); + } + break; + + case EXTRNL_NAME: + if( pLastBox ) + pEndBox = const_cast<SwTableBox*>(rTable.GetTableBox( *pLastBox )); + pSttBox = const_cast<SwTableBox*>(rTable.GetTableBox( rFirstBox )); + break; + } + + // Are these valid pointers? + if( ( pLastBox && + ( !pEndBox || rTable.GetTabSortBoxes().find( pEndBox ) == rTable.GetTabSortBoxes().end() ) ) || + ( !pSttBox || rTable.GetTabSortBoxes().find( pSttBox ) == rTable.GetTabSortBoxes().end() ) ) + *pBValid = false; + } +} + +bool SwTableFormula::HasValidBoxes() const +{ + bool bRet = true; + const SwNode* pNd = GetNodeOfFormula(); + if( pNd && nullptr != ( pNd = pNd->FindTableNode() ) ) + ScanString( &SwTableFormula::HasValidBoxes_, + static_cast<const SwTableNode*>(pNd)->GetTable(), &bRet ); + return bRet; +} + +sal_uInt16 SwTableFormula::GetLnPosInTable( const SwTable& rTable, const SwTableBox* pBox ) +{ + sal_uInt16 nRet = USHRT_MAX; + if( pBox ) + { + const SwTableLine* pLn = pBox->GetUpper(); + while( pLn->GetUpper() ) + pLn = pLn->GetUpper()->GetUpper(); + nRet = rTable.GetTabLines().GetPos( pLn ); + } + return nRet; +} + +void SwTableFormula::SplitMergeBoxNm_( const SwTable& rTable, OUStringBuffer& rNewStr, + OUString& rFirstBox, OUString* pLastBox, void* pPara ) const +{ + SwTableFormulaUpdate& rTableUpd = *static_cast<SwTableFormulaUpdate*>(pPara); + + rNewStr.append(rFirstBox[0]); // get label for the box + rFirstBox = rFirstBox.copy(1); + + OUString sTableNm; + const SwTable* pTable = &rTable; + + OUString* pTableNmBox = pLastBox ? pLastBox : &rFirstBox; + + const sal_Int32 nLastBoxLen = pTableNmBox->getLength(); + const sal_Int32 nSeparator = pTableNmBox->indexOf('.'); + if ( nSeparator>=0 && + // If there are dots in the name, then these appear in pairs (e.g. A1.1.1)! + (comphelper::string::getTokenCount(*pTableNmBox, '.') - 1) & 1 ) + { + sTableNm = pTableNmBox->copy( 0, nSeparator ); + *pTableNmBox = pTableNmBox->copy( nSeparator + 1); // remove dot + const SwTable* pFnd = FindTable( *rTable.GetFrameFormat()->GetDoc(), sTableNm ); + if( pFnd ) + pTable = pFnd; + + if( TBL_MERGETBL == rTableUpd.m_eFlags ) + { + if( pFnd ) + { + if( pFnd == rTableUpd.m_aData.pDelTable ) + { + if( rTableUpd.m_pTable != &rTable ) // not the current one + rNewStr.append(rTableUpd.m_pTable->GetFrameFormat()->GetName()).append("."); // set new table name + rTableUpd.m_bModified = true; + } + else if( pFnd != rTableUpd.m_pTable || + ( rTableUpd.m_pTable != &rTable && &rTable != rTableUpd.m_aData.pDelTable)) + rNewStr.append(sTableNm).append("."); // keep table name + else + rTableUpd.m_bModified = true; + } + else + rNewStr.append(sTableNm).append("."); // keep table name + } + } + if( pTableNmBox == pLastBox ) + rFirstBox = rFirstBox.copy( nLastBoxLen + 1 ); + + SwTableBox* pSttBox = nullptr, *pEndBox = nullptr; + switch (m_eNmType) + { + case INTRNL_NAME: + if( pLastBox ) + pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64())); + pSttBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64())); + break; + + case REL_NAME: + { + const SwNode* pNd = GetNodeOfFormula(); + const SwTableBox* pBox = pNd ? pTable->GetTableBox( + pNd->FindTableBoxStartNode()->GetIndex() ) : nullptr; + if( pLastBox ) + pEndBox = const_cast<SwTableBox*>(lcl_RelToBox( *pTable, pBox, *pLastBox )); + pSttBox = const_cast<SwTableBox*>(lcl_RelToBox( *pTable, pBox, rFirstBox )); + } + break; + + case EXTRNL_NAME: + if( pLastBox ) + pEndBox = const_cast<SwTableBox*>(pTable->GetTableBox( *pLastBox )); + pSttBox = const_cast<SwTableBox*>(pTable->GetTableBox( rFirstBox )); + break; + } + + if( pLastBox && pTable->GetTabSortBoxes().find( pEndBox ) == pTable->GetTabSortBoxes().end() ) + pEndBox = nullptr; + if( pTable->GetTabSortBoxes().find( pSttBox ) == pTable->GetTabSortBoxes().end() ) + pSttBox = nullptr; + + if( TBL_SPLITTBL == rTableUpd.m_eFlags ) + { + // Where are the boxes - in the old or in the new table? + bool bInNewTable = false; + if( pLastBox ) + { + // It is the "first" box in this selection. It determines if the formula is placed in + // the new or the old table. + sal_uInt16 nEndLnPos = SwTableFormula::GetLnPosInTable( *pTable, pEndBox ), + nSttLnPos = SwTableFormula::GetLnPosInTable( *pTable, pSttBox ); + + if( USHRT_MAX != nSttLnPos && USHRT_MAX != nEndLnPos && + ((rTableUpd.m_nSplitLine <= nSttLnPos) == + (rTableUpd.m_nSplitLine <= nEndLnPos)) ) + { + // stay in same table + bInNewTable = rTableUpd.m_nSplitLine <= nEndLnPos && + pTable == rTableUpd.m_pTable; + } + else + { + // this is definitely an invalid formula, also mark as modified for Undo + rTableUpd.m_bModified = true; + if( pEndBox ) + bInNewTable = USHRT_MAX != nEndLnPos && + rTableUpd.m_nSplitLine <= nEndLnPos && + pTable == rTableUpd.m_pTable; + } + } + else + { + sal_uInt16 nSttLnPos = SwTableFormula::GetLnPosInTable( *pTable, pSttBox ); + // Put it in the new table? + bInNewTable = USHRT_MAX != nSttLnPos && + rTableUpd.m_nSplitLine <= nSttLnPos && + pTable == rTableUpd.m_pTable; + } + + // formula goes into new table + if( rTableUpd.m_bBehindSplitLine ) + { + if( !bInNewTable ) + { + rTableUpd.m_bModified = true; + rNewStr.append(rTableUpd.m_pTable->GetFrameFormat()->GetName()).append("."); + } + else if( !sTableNm.isEmpty() ) + rNewStr.append(sTableNm).append("."); + } + else if( bInNewTable ) + { + rTableUpd.m_bModified = true; + rNewStr.append(*rTableUpd.m_aData.pNewTableNm).append("."); + } + else if( !sTableNm.isEmpty() ) + rNewStr.append(sTableNm).append("."); + } + + if( pLastBox ) + rNewStr.append(OUString::number(reinterpret_cast<sal_PtrDiff>(pEndBox))).append(":"); + + rNewStr.append(OUString::number(reinterpret_cast<sal_PtrDiff>(pSttBox))) + .append(rFirstBox[ rFirstBox.getLength()-1] ); +} + +/// Create external formula but remember that the formula is placed in a split/merged table +void SwTableFormula::ToSplitMergeBoxNm( SwTableFormulaUpdate& rTableUpd ) +{ + const SwTable* pTable; + const SwNode* pNd = GetNodeOfFormula(); + if( pNd && nullptr != ( pNd = pNd->FindTableNode() )) + pTable = &static_cast<const SwTableNode*>(pNd)->GetTable(); + else + pTable = rTableUpd.m_pTable; + + m_sFormula = ScanString( &SwTableFormula::SplitMergeBoxNm_, *pTable, static_cast<void*>(&rTableUpd) ); + m_eNmType = INTRNL_NAME; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/chpfld.cxx b/sw/source/core/fields/chpfld.cxx new file mode 100644 index 000000000..9473b086d --- /dev/null +++ b/sw/source/core/fields/chpfld.cxx @@ -0,0 +1,301 @@ +/* -*- 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 <com/sun/star/text/ChapterFormat.hpp> +#include <doc.hxx> +#include <frame.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <chpfld.hxx> +#include <expfld.hxx> +#include <unofldmid.h> +#include <numrule.hxx> + +using namespace ::com::sun::star; + +namespace +{ + +OUString removeControlChars(const OUString& sIn) +{ + OUStringBuffer aBuf(sIn.replace('\n', ' ')); + sal_Int32 nLen = aBuf.getLength(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + if (aBuf[i] < ' ') + { + sal_Int32 j = i+1; + while (j<nLen && aBuf[j]<' ') ++j; + aBuf.remove(i, j-i); + nLen = aBuf.getLength(); + } + } + return aBuf.makeStringAndClear(); +} + +} + +SwChapterFieldType::SwChapterFieldType() + : SwFieldType( SwFieldIds::Chapter ) +{ +} + +std::unique_ptr<SwFieldType> SwChapterFieldType::Copy() const +{ + return std::make_unique<SwChapterFieldType>(); +} + +// chapter field + +SwChapterField::SwChapterField(SwChapterFieldType* pTyp, sal_uInt32 nFormat) + : SwField(pTyp, nFormat) +{ +} + +sal_uInt8 SwChapterField::GetLevel(SwRootFrame const*const pLayout) const +{ + State const& rState(pLayout && pLayout->IsHideRedlines() ? m_StateRLHidden : m_State); + return rState.nLevel; +} + +// this is called from UI or from import filters, so override both states +void SwChapterField::SetLevel(sal_uInt8 nLev) +{ + m_State.nLevel = nLev; + m_StateRLHidden.nLevel = nLev; +} + +const OUString& SwChapterField::GetNumber(SwRootFrame const*const pLayout) const +{ + State const& rState(pLayout && pLayout->IsHideRedlines() ? m_StateRLHidden : m_State); + return rState.sNumber; +} + +const OUString& SwChapterField::GetTitle(SwRootFrame const*const pLayout) const +{ + State const& rState(pLayout && pLayout->IsHideRedlines() ? m_StateRLHidden : m_State); + return rState.sTitle; +} + +OUString SwChapterField::ExpandImpl(SwRootFrame const*const pLayout) const +{ + State const& rState(pLayout && pLayout->IsHideRedlines() ? m_StateRLHidden : m_State); + switch( GetFormat() ) + { + case CF_TITLE: + return rState.sTitle; + case CF_NUMBER: + return rState.sPre + rState.sNumber + rState.sPost; + case CF_NUM_TITLE: + return rState.sPre + rState.sNumber + rState.sPost + rState.sTitle; + case CF_NUM_NOPREPST_TITLE: + return rState.sNumber + rState.sTitle; + } + // CF_NUMBER_NOPREPST + return rState.sNumber; +} + +std::unique_ptr<SwField> SwChapterField::Copy() const +{ + std::unique_ptr<SwChapterField> pTmp( + new SwChapterField(static_cast<SwChapterFieldType*>(GetTyp()), GetFormat())); + pTmp->m_State = m_State; + pTmp->m_StateRLHidden = m_StateRLHidden; + + return std::unique_ptr<SwField>(pTmp.release()); +} + +// #i53420# +void SwChapterField::ChangeExpansion(const SwFrame & rFrame, + const SwContentNode* pContentNode, + bool bSrchNum ) +{ + SwDoc* pDoc = const_cast<SwDoc*>(pContentNode->GetDoc()); + + const SwTextNode* pTextNode = dynamic_cast<const SwTextNode*>(pContentNode); + if (!pTextNode || !rFrame.IsInDocBody()) + { + SwPosition aDummyPos( pDoc->GetNodes().GetEndOfContent() ); + pTextNode = GetBodyTextNode( *pDoc, aDummyPos, rFrame ); + } + + if ( pTextNode ) + { + ChangeExpansion( *pTextNode, bSrchNum, rFrame.getRootFrame() ); + } +} + +void SwChapterField::ChangeExpansion(const SwTextNode &rTextNd, bool bSrchNum, + SwRootFrame const*const pLayout) +{ + State & rState(pLayout && pLayout->IsHideRedlines() ? m_StateRLHidden : m_State); + rState.sNumber.clear(); + rState.sTitle.clear(); + rState.sPost.clear(); + rState.sPre.clear(); + + SwDoc* pDoc = const_cast<SwDoc*>(rTextNd.GetDoc()); + const SwTextNode *pTextNd = rTextNd.FindOutlineNodeOfLevel(rState.nLevel, pLayout); + if( pTextNd ) + { + if( bSrchNum ) + { + const SwTextNode* pONd = pTextNd; + do { + if( pONd && pONd->GetTextColl() ) + { + sal_uInt8 nPrevLvl = rState.nLevel; + + OSL_ENSURE( pONd->GetAttrOutlineLevel() >= 0 && pONd->GetAttrOutlineLevel() <= MAXLEVEL, + "<SwChapterField::ChangeExpansion(..)> - outline node with inconsistent outline level. Serious defect." ); + rState.nLevel = static_cast<sal_uInt8>(pONd->GetAttrOutlineLevel()); + + if (nPrevLvl < rState.nLevel) + rState.nLevel = nPrevLvl; + else if( SVX_NUM_NUMBER_NONE != pDoc->GetOutlineNumRule() + ->Get( rState.nLevel ).GetNumberingType() ) + { + pTextNd = pONd; + break; + } + + if (!rState.nLevel--) + break; + pONd = pTextNd->FindOutlineNodeOfLevel(rState.nLevel, pLayout); + } + else + break; + } while( true ); + } + + // get the number without Pre-/Post-fixstrings + + if ( pTextNd->IsOutline() ) + { + // correction of refactoring done by cws swnumtree: + // retrieve numbering string without prefix and suffix strings + // as stated in the above german comment. + rState.sNumber = pTextNd->GetNumString(false, MAXLEVEL, pLayout); + + SwNumRule* pRule( pTextNd->GetNumRule() ); + if ( pTextNd->IsCountedInList() && pRule ) + { + int nListLevel = pTextNd->GetActualListLevel(); + if (nListLevel < 0) + nListLevel = 0; + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const SwNumFormat& rNFormat = pRule->Get(nListLevel); + rState.sPost = rNFormat.GetSuffix(); + rState.sPre = rNFormat.GetPrefix(); + } + } + else + { + rState.sNumber = "??"; + } + + rState.sTitle = removeControlChars(sw::GetExpandTextMerged(pLayout, + *pTextNd, false, false, ExpandMode(0))); + } +} + +bool SwChapterField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BYTE1: + rAny <<= static_cast<sal_Int8>(m_State.nLevel); + break; + + case FIELD_PROP_USHORT1: + { + sal_Int16 nRet; + switch( GetFormat() ) + { + case CF_NUMBER: nRet = text::ChapterFormat::NUMBER; break; + case CF_TITLE: nRet = text::ChapterFormat::NAME; break; + case CF_NUMBER_NOPREPST: + nRet = text::ChapterFormat::DIGIT; + break; + case CF_NUM_NOPREPST_TITLE: + nRet = text::ChapterFormat::NO_PREFIX_SUFFIX; + break; + case CF_NUM_TITLE: + default: nRet = text::ChapterFormat::NAME_NUMBER; + } + rAny <<= nRet; + } + break; + + default: + assert(false); + } + return true; +} + +bool SwChapterField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_BYTE1: + { + sal_Int8 nTmp = 0; + rAny >>= nTmp; + if(nTmp >= 0 && nTmp < MAXLEVEL) + { + m_State.nLevel = nTmp; + m_StateRLHidden.nLevel = nTmp; + } + else + bRet = false; + break; + } + + case FIELD_PROP_USHORT1: + { + sal_Int16 nVal = 0; + rAny >>= nVal; + switch( nVal ) + { + case text::ChapterFormat::NAME: SetFormat(CF_TITLE); break; + case text::ChapterFormat::NUMBER: SetFormat(CF_NUMBER); break; + case text::ChapterFormat::NO_PREFIX_SUFFIX: + SetFormat(CF_NUM_NOPREPST_TITLE); + break; + case text::ChapterFormat::DIGIT: + SetFormat(CF_NUMBER_NOPREPST); + break; + + default: SetFormat(CF_NUM_TITLE); + } + } + break; + + default: + assert(false); + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/dbfld.cxx b/sw/source/core/fields/dbfld.cxx new file mode 100644 index 000000000..abc9fbd37 --- /dev/null +++ b/sw/source/core/fields/dbfld.cxx @@ -0,0 +1,870 @@ +/* -*- 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 <float.h> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <svl/zforlist.hxx> +#include <com/sun/star/sdbc/DataType.hpp> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <calc.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <fldbas.hxx> +#include <ndtxt.hxx> +#include <dbfld.hxx> +#include <dbmgr.hxx> +#include <unofldmid.h> +#include <calbck.hxx> + +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star; + +/// replace database separator by dots for display +static OUString lcl_DBSeparatorConvert(const OUString& aContent) +{ + return aContent.replaceAll(OUStringChar(DB_DELIM), "."); +} + +// database field type + +SwDBFieldType::SwDBFieldType(SwDoc* pDocPtr, const OUString& rNam, const SwDBData& rDBData ) : + SwValueFieldType( pDocPtr, SwFieldIds::Database ), + m_aDBData(rDBData), + m_sName(rNam), + m_sColumn(rNam), + m_nRefCnt(0) +{ + if(!m_aDBData.sDataSource.isEmpty() || !m_aDBData.sCommand.isEmpty()) + { + m_sName = m_aDBData.sDataSource + + OUStringChar(DB_DELIM) + + m_aDBData.sCommand + + OUStringChar(DB_DELIM) + + m_sName; + } +} + +SwDBFieldType::~SwDBFieldType() +{ +} + +std::unique_ptr<SwFieldType> SwDBFieldType::Copy() const +{ + return std::make_unique<SwDBFieldType>(GetDoc(), m_sColumn, m_aDBData); +} + +OUString SwDBFieldType::GetName() const +{ + return m_sName; +} + +void SwDBFieldType::ReleaseRef() +{ + OSL_ENSURE(m_nRefCnt > 0, "RefCount < 0!"); + + if (--m_nRefCnt <= 0) + { + size_t nPos = 0; + for (auto const & pFieldType : *GetDoc()->getIDocumentFieldsAccess().GetFieldTypes()) + { + if (pFieldType.get() == this) + break; + ++nPos; + } + if (nPos < GetDoc()->getIDocumentFieldsAccess().GetFieldTypes()->size()) + { + GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(nPos); + delete this; + } + } +} + +void SwDBFieldType::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_aDBData.sDataSource; + break; + case FIELD_PROP_PAR2: + rAny <<= m_aDBData.sCommand; + break; + case FIELD_PROP_PAR3: + rAny <<= m_sColumn; + break; + case FIELD_PROP_SHORT1: + rAny <<= m_aDBData.nCommandType; + break; + default: + assert(false); + } +} + +void SwDBFieldType::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= m_aDBData.sDataSource; + break; + case FIELD_PROP_PAR2: + rAny >>= m_aDBData.sCommand; + break; + case FIELD_PROP_PAR3: + { + OUString sTmp; + rAny >>= sTmp; + if( sTmp != m_sColumn ) + { + m_sColumn = sTmp; + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(auto pFormatField: vFields) + { + SwDBField* pDBField = static_cast<SwDBField*>(pFormatField->GetField()); + pDBField->ClearInitialized(); + pDBField->InitContent(); + } + } + } + break; + case FIELD_PROP_SHORT1: + rAny >>= m_aDBData.nCommandType; + break; + default: + assert(false); + } +} + +// database field + +SwDBField::SwDBField(SwDBFieldType* pTyp, sal_uInt32 nFormat) + : SwValueField(pTyp, nFormat), + m_nSubType(0), + m_bIsInBodyText(true), + m_bValidValue(false), + m_bInitialized(false) +{ + if (GetTyp()) + static_cast<SwDBFieldType*>(GetTyp())->AddRef(); + InitContent(); +} + +SwDBField::~SwDBField() +{ + if (GetTyp()) + static_cast<SwDBFieldType*>(GetTyp())->ReleaseRef(); +} + +void SwDBField::InitContent() +{ + if (!IsInitialized()) + { + m_aContent = "<" + static_cast<const SwDBFieldType*>(GetTyp())->GetColumnName() + ">"; + } +} + +void SwDBField::InitContent(const OUString& rExpansion) +{ + if (rExpansion.startsWith("<") && rExpansion.endsWith(">")) + { + const OUString sColumn( rExpansion.copy( 1, rExpansion.getLength() - 2 ) ); + if( ::GetAppCmpStrIgnore().isEqual( sColumn, + static_cast<SwDBFieldType *>(GetTyp())->GetColumnName() )) + { + InitContent(); + return; + } + } + SetExpansion( rExpansion ); +} + +OUString SwDBField::ExpandImpl(SwRootFrame const*const) const +{ + if(0 ==(GetSubType() & nsSwExtendedSubType::SUB_INVISIBLE)) + return lcl_DBSeparatorConvert(m_aContent); + return OUString(); +} + +std::unique_ptr<SwField> SwDBField::Copy() const +{ + std::unique_ptr<SwDBField> pTmp(new SwDBField(static_cast<SwDBFieldType*>(GetTyp()), GetFormat())); + pTmp->m_aContent = m_aContent; + pTmp->m_bIsInBodyText = m_bIsInBodyText; + pTmp->m_bValidValue = m_bValidValue; + pTmp->m_bInitialized = m_bInitialized; + pTmp->m_nSubType = m_nSubType; + pTmp->SetValue(GetValue()); + pTmp->m_sFieldCode = m_sFieldCode; + + return std::unique_ptr<SwField>(pTmp.release()); +} + +OUString SwDBField::GetFieldName() const +{ + const OUString rDBName = static_cast<SwDBFieldType*>(GetTyp())->GetName(); + + OUString sContent( rDBName.getToken(0, DB_DELIM) ); + + if (sContent.getLength() > 1) + { + sContent += OUStringChar(DB_DELIM) + + rDBName.getToken(1, DB_DELIM) + + OUStringChar(DB_DELIM) + + rDBName.getToken(2, DB_DELIM); + } + return lcl_DBSeparatorConvert(sContent); +} + +void SwDBField::ChgValue( double d, bool bVal ) +{ + m_bValidValue = bVal; + SetValue(d); + + if( m_bValidValue ) + m_aContent = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue(d, GetFormat(), GetLanguage()); +} + +SwFieldType* SwDBField::ChgTyp( SwFieldType* pNewType ) +{ + SwFieldType* pOld = SwValueField::ChgTyp( pNewType ); + + static_cast<SwDBFieldType*>(pNewType)->AddRef(); + static_cast<SwDBFieldType*>(pOld)->ReleaseRef(); + + return pOld; +} + +bool SwDBField::FormatValue( SvNumberFormatter const * pDocFormatter, OUString const &aString, sal_uInt32 nFormat, + double &aNumber, sal_Int32 nColumnType, SwDBField *pField ) +{ + bool bValidValue = false; + + if( DBL_MAX != aNumber ) + { + if( DataType::DATE == nColumnType || DataType::TIME == nColumnType || + DataType::TIMESTAMP == nColumnType ) + { + Date aStandard( 1, 1, 1900 ); + if( pDocFormatter->GetNullDate() != aStandard ) + aNumber += (aStandard - pDocFormatter->GetNullDate()); + } + bValidValue = true; + if( pField ) + pField->SetValue( aNumber ); + } + else + { + SwSbxValue aVal; + aVal.PutString( aString ); + + if (aVal.IsNumeric()) + { + if( pField ) + pField->SetValue(aVal.GetDouble()); + else + aNumber = aVal.GetDouble(); + + if (nFormat && nFormat != SAL_MAX_UINT32 && !pDocFormatter->IsTextFormat(nFormat)) + bValidValue = true; // because of bug #60339 not for all strings + } + else + { + // if string length > 0 then true, else false + if( pField ) + pField->SetValue(aString.isEmpty() ? 0 : 1); + else + aNumber = aString.isEmpty() ? 0 : 1; + } + } + + return bValidValue; +} + +/// get current field value and cache it +void SwDBField::Evaluate() +{ + SwDBManager* pMgr = GetDoc()->GetDBManager(); + + // first delete + m_bValidValue = false; + double nValue = DBL_MAX; + const SwDBData& aTmpData = GetDBData(); + + if(!pMgr || !pMgr->IsDataSourceOpen(aTmpData.sDataSource, aTmpData.sCommand, true)) + return ; + + sal_uInt32 nFormat = 0; + + // search corresponding column name + OUString aColNm( static_cast<SwDBFieldType*>(GetTyp())->GetColumnName() ); + + SvNumberFormatter* pDocFormatter = GetDoc()->GetNumberFormatter(); + pMgr->GetMergeColumnCnt(aColNm, GetLanguage(), m_aContent, &nValue); + if( !( m_nSubType & nsSwExtendedSubType::SUB_OWN_FMT ) ) + { + nFormat = pMgr->GetColumnFormat( aTmpData.sDataSource, aTmpData.sCommand, + aColNm, pDocFormatter, GetLanguage() ); + SetFormat( nFormat ); + } + + sal_Int32 nColumnType = nValue == DBL_MAX + ? 0 + : pMgr->GetColumnType(aTmpData.sDataSource, aTmpData.sCommand, aColNm); + + m_bValidValue = FormatValue( pDocFormatter, m_aContent, nFormat, nValue, nColumnType, this ); + + if( DBL_MAX != nValue ) + m_aContent = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue(nValue, GetFormat(), GetLanguage()); + + m_bInitialized = true; +} + +/// get name +OUString SwDBField::GetPar1() const +{ + return static_cast<const SwDBFieldType*>(GetTyp())->GetName(); +} + +sal_uInt16 SwDBField::GetSubType() const +{ + return m_nSubType; +} + +void SwDBField::SetSubType(sal_uInt16 nType) +{ + m_nSubType = nType; +} + +bool SwDBField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + rAny <<= 0 == (GetSubType()&nsSwExtendedSubType::SUB_OWN_FMT); + break; + case FIELD_PROP_BOOL2: + rAny <<= 0 == (GetSubType() & nsSwExtendedSubType::SUB_INVISIBLE); + break; + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int32>(GetFormat()); + break; + case FIELD_PROP_PAR1: + rAny <<= m_aContent; + break; + case FIELD_PROP_PAR2: + rAny <<= m_sFieldCode; + break; + default: + OSL_FAIL("illegal property"); + } + return true; +} + +bool SwDBField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + if( *o3tl::doAccess<bool>(rAny) ) + SetSubType(GetSubType()&~nsSwExtendedSubType::SUB_OWN_FMT); + else + SetSubType(GetSubType()|nsSwExtendedSubType::SUB_OWN_FMT); + break; + case FIELD_PROP_BOOL2: + { + sal_uInt16 nSubTyp = GetSubType(); + bool bVisible = false; + if(!(rAny >>= bVisible)) + return false; + if(bVisible) + nSubTyp &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + nSubTyp |= nsSwExtendedSubType::SUB_INVISIBLE; + SetSubType(nSubTyp); + //invalidate text node + auto pType = GetTyp(); + if(!pType) + break; + std::vector<SwFormatField*> vFields; + pType->GatherFields(vFields, false); + for(auto pFormatField: vFields) + { + SwTextField* pTextField = pFormatField->GetTextField(); + if(pTextField && static_cast<SwDBField*>(pFormatField->GetField()) == this) + { + //notify the change + pTextField->NotifyContentChange(*pFormatField); + break; + } + } + } + break; + case FIELD_PROP_FORMAT: + { + sal_Int32 nTemp = 0; + rAny >>= nTemp; + SetFormat(nTemp); + } + break; + case FIELD_PROP_PAR1: + rAny >>= m_aContent; + break; + case FIELD_PROP_PAR2: + rAny >>= m_sFieldCode; + break; + default: + OSL_FAIL("illegal property"); + } + return true; +} + +// base class for all further database fields + +SwDBNameInfField::SwDBNameInfField(SwFieldType* pTyp, const SwDBData& rDBData, sal_uInt32 nFormat) : + SwField(pTyp, nFormat), + m_aDBData(rDBData), + m_nSubType(0) +{ +} + +SwDBData SwDBNameInfField::GetDBData(SwDoc* pDoc) +{ + SwDBData aRet; + if(!m_aDBData.sDataSource.isEmpty()) + aRet = m_aDBData; + else + aRet = pDoc->GetDBData(); + return aRet; +} + +void SwDBNameInfField::SetDBData(const SwDBData & rDBData) +{ + m_aDBData = rDBData; +} + +OUString SwDBNameInfField::GetFieldName() const +{ + OUString sStr( SwField::GetFieldName() ); + if (!m_aDBData.sDataSource.isEmpty()) + { + sStr += ":" + + m_aDBData.sDataSource + + OUStringChar(DB_DELIM) + + m_aDBData.sCommand; + } + return lcl_DBSeparatorConvert(sStr); +} + +bool SwDBNameInfField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_aDBData.sDataSource; + break; + case FIELD_PROP_PAR2: + rAny <<= m_aDBData.sCommand; + break; + case FIELD_PROP_SHORT1: + rAny <<= m_aDBData.nCommandType; + break; + case FIELD_PROP_BOOL2: + rAny <<= 0 == (GetSubType() & nsSwExtendedSubType::SUB_INVISIBLE); + break; + default: + assert(false); + } + return true; +} + +bool SwDBNameInfField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= m_aDBData.sDataSource; + break; + case FIELD_PROP_PAR2: + rAny >>= m_aDBData.sCommand; + break; + case FIELD_PROP_SHORT1: + rAny >>= m_aDBData.nCommandType; + break; + case FIELD_PROP_BOOL2: + { + sal_uInt16 nSubTyp = GetSubType(); + bool bVisible = false; + if(!(rAny >>= bVisible)) + return false; + if(bVisible) + nSubTyp &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + nSubTyp |= nsSwExtendedSubType::SUB_INVISIBLE; + SetSubType(nSubTyp); + } + break; + default: + assert(false); + } + return true; +} + +sal_uInt16 SwDBNameInfField::GetSubType() const +{ + return m_nSubType; +} + +void SwDBNameInfField::SetSubType(sal_uInt16 nType) +{ + m_nSubType = nType; +} + +// next dataset + +SwDBNextSetFieldType::SwDBNextSetFieldType() + : SwFieldType( SwFieldIds::DbNextSet ) +{ +} + +std::unique_ptr<SwFieldType> SwDBNextSetFieldType::Copy() const +{ + return std::make_unique<SwDBNextSetFieldType>(); +} + +// SwDBSetField + +SwDBNextSetField::SwDBNextSetField(SwDBNextSetFieldType* pTyp, + const OUString& rCond, + const SwDBData& rDBData) : + SwDBNameInfField(pTyp, rDBData), m_aCond(rCond), m_bCondValid(true) +{} + +OUString SwDBNextSetField::ExpandImpl(SwRootFrame const*const) const +{ + return OUString(); +} + +std::unique_ptr<SwField> SwDBNextSetField::Copy() const +{ + std::unique_ptr<SwDBNextSetField> pTmp(new SwDBNextSetField(static_cast<SwDBNextSetFieldType*>(GetTyp()), + m_aCond, GetDBData())); + pTmp->SetSubType(GetSubType()); + pTmp->m_bCondValid = m_bCondValid; + return std::unique_ptr<SwField>(pTmp.release()); +} + +void SwDBNextSetField::Evaluate(SwDoc const * pDoc) +{ + SwDBManager* pMgr = pDoc->GetDBManager(); + const SwDBData& rData = GetDBData(); + if( !m_bCondValid || + !pMgr || !pMgr->IsDataSourceOpen(rData.sDataSource, rData.sCommand, false)) + return ; + pMgr->ToNextRecord(rData.sDataSource, rData.sCommand); +} + +/// get condition +OUString SwDBNextSetField::GetPar1() const +{ + return m_aCond; +} + +/// set condition +void SwDBNextSetField::SetPar1(const OUString& rStr) +{ + m_aCond = rStr; +} + +bool SwDBNextSetField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_PAR3: + rAny <<= m_aCond; + break; + default: + bRet = SwDBNameInfField::QueryValue( rAny, nWhichId ); + } + return bRet; +} + +bool SwDBNextSetField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_PAR3: + rAny >>= m_aCond; + break; + default: + bRet = SwDBNameInfField::PutValue( rAny, nWhichId ); + } + return bRet; +} + +// dataset with certain ID + +SwDBNumSetFieldType::SwDBNumSetFieldType() : + SwFieldType( SwFieldIds::DbNumSet ) +{ +} + +std::unique_ptr<SwFieldType> SwDBNumSetFieldType::Copy() const +{ + return std::make_unique<SwDBNumSetFieldType>(); +} + +SwDBNumSetField::SwDBNumSetField(SwDBNumSetFieldType* pTyp, + const OUString& rCond, + const OUString& rDBNum, + const SwDBData& rDBData) : + SwDBNameInfField(pTyp, rDBData), + m_aCond(rCond), + m_aPar2(rDBNum), + m_bCondValid(true) +{} + +OUString SwDBNumSetField::ExpandImpl(SwRootFrame const*const) const +{ + return OUString(); +} + +std::unique_ptr<SwField> SwDBNumSetField::Copy() const +{ + std::unique_ptr<SwDBNumSetField> pTmp(new SwDBNumSetField(static_cast<SwDBNumSetFieldType*>(GetTyp()), + m_aCond, m_aPar2, GetDBData())); + pTmp->m_bCondValid = m_bCondValid; + pTmp->SetSubType(GetSubType()); + return std::unique_ptr<SwField>(pTmp.release()); +} + +void SwDBNumSetField::Evaluate(SwDoc const * pDoc) +{ + SwDBManager* pMgr = pDoc->GetDBManager(); + const SwDBData& aTmpData = GetDBData(); + + if( m_bCondValid && pMgr && pMgr->IsInMerge() && + pMgr->IsDataSourceOpen(aTmpData.sDataSource, aTmpData.sCommand, true)) + { // condition OK -> adjust current Set + pMgr->ToRecordId(std::max(static_cast<sal_uInt16>(m_aPar2.toInt32()), sal_uInt16(1))-1); + } +} + +/// get LogDBName +OUString SwDBNumSetField::GetPar1() const +{ + return m_aCond; +} + +/// set LogDBName +void SwDBNumSetField::SetPar1(const OUString& rStr) +{ + m_aCond = rStr; +} + +/// get condition +OUString SwDBNumSetField::GetPar2() const +{ + return m_aPar2; +} + +/// set condition +void SwDBNumSetField::SetPar2(const OUString& rStr) +{ + m_aPar2 = rStr; +} + +bool SwDBNumSetField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_PAR3: + rAny <<= m_aCond; + break; + case FIELD_PROP_FORMAT: + rAny <<= m_aPar2.toInt32(); + break; + default: + bRet = SwDBNameInfField::QueryValue(rAny, nWhichId ); + } + return bRet; +} + +bool SwDBNumSetField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_PAR3: + rAny >>= m_aCond; + break; + case FIELD_PROP_FORMAT: + { + sal_Int32 nVal = 0; + rAny >>= nVal; + m_aPar2 = OUString::number(nVal); + } + break; + default: + bRet = SwDBNameInfField::PutValue(rAny, nWhichId ); + } + return bRet; +} + +SwDBNameFieldType::SwDBNameFieldType(SwDoc* pDocument) + : SwFieldType( SwFieldIds::DatabaseName ) +{ + m_pDoc = pDocument; +} + +OUString SwDBNameFieldType::Expand() const +{ + const SwDBData aData = m_pDoc->GetDBData(); + return aData.sDataSource + "." + aData.sCommand; +} + +std::unique_ptr<SwFieldType> SwDBNameFieldType::Copy() const +{ + return std::make_unique<SwDBNameFieldType>(m_pDoc); +} + +// name of the connected database + +SwDBNameField::SwDBNameField(SwDBNameFieldType* pTyp, const SwDBData& rDBData) + : SwDBNameInfField(pTyp, rDBData, 0) +{} + +OUString SwDBNameField::ExpandImpl(SwRootFrame const*const) const +{ + if(0 ==(GetSubType() & nsSwExtendedSubType::SUB_INVISIBLE)) + return static_cast<SwDBNameFieldType*>(GetTyp())->Expand(); + return OUString(); +} + +std::unique_ptr<SwField> SwDBNameField::Copy() const +{ + std::unique_ptr<SwDBNameField> pTmp(new SwDBNameField(static_cast<SwDBNameFieldType*>(GetTyp()), GetDBData())); + pTmp->ChangeFormat(GetFormat()); + pTmp->SetLanguage(GetLanguage()); + pTmp->SetSubType(GetSubType()); + return std::unique_ptr<SwField>(pTmp.release()); +} + +bool SwDBNameField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + return SwDBNameInfField::QueryValue(rAny, nWhichId ); +} + +bool SwDBNameField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + return SwDBNameInfField::PutValue(rAny, nWhichId ); +} + +SwDBSetNumberFieldType::SwDBSetNumberFieldType() + : SwFieldType( SwFieldIds::DbSetNumber ) +{ +} + +std::unique_ptr<SwFieldType> SwDBSetNumberFieldType::Copy() const +{ + return std::make_unique<SwDBSetNumberFieldType>(); +} + +// set-number of the connected database + +SwDBSetNumberField::SwDBSetNumberField(SwDBSetNumberFieldType* pTyp, + const SwDBData& rDBData, + sal_uInt32 nFormat) + : SwDBNameInfField(pTyp, rDBData, nFormat), m_nNumber(0) +{} + +OUString SwDBSetNumberField::ExpandImpl(SwRootFrame const*const) const +{ + if(0 !=(GetSubType() & nsSwExtendedSubType::SUB_INVISIBLE) || m_nNumber == 0) + return OUString(); + return FormatNumber(m_nNumber, static_cast<SvxNumType>(GetFormat())); +} + +void SwDBSetNumberField::Evaluate(SwDoc const * pDoc) +{ + SwDBManager* pMgr = pDoc->GetDBManager(); + + const SwDBData& aTmpData = GetDBData(); + if (!pMgr || !pMgr->IsInMerge() || + !pMgr->IsDataSourceOpen(aTmpData.sDataSource, aTmpData.sCommand, false)) + return; + m_nNumber = pMgr->GetSelectedRecordId(); +} + +std::unique_ptr<SwField> SwDBSetNumberField::Copy() const +{ + std::unique_ptr<SwDBSetNumberField> pTmp( + new SwDBSetNumberField(static_cast<SwDBSetNumberFieldType*>(GetTyp()), GetDBData(), GetFormat())); + pTmp->SetLanguage(GetLanguage()); + pTmp->SetSetNumber(m_nNumber); + pTmp->SetSubType(GetSubType()); + return std::unique_ptr<SwField>(pTmp.release()); +} + +bool SwDBSetNumberField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + rAny <<= static_cast<sal_Int16>(GetFormat()); + break; + case FIELD_PROP_FORMAT: + rAny <<= m_nNumber; + break; + default: + bRet = SwDBNameInfField::QueryValue( rAny, nWhichId ); + } + return bRet; +} + +bool SwDBSetNumberField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = true; + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + { + sal_Int16 nSet = 0; + rAny >>= nSet; + if(nSet < css::style::NumberingType::NUMBER_NONE ) + SetFormat(nSet); + } + break; + case FIELD_PROP_FORMAT: + rAny >>= m_nNumber; + break; + default: + bRet = SwDBNameInfField::PutValue( rAny, nWhichId ); + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/ddefld.cxx b/sw/source/core/fields/ddefld.cxx new file mode 100644 index 000000000..f03bf0257 --- /dev/null +++ b/sw/source/core/fields/ddefld.cxx @@ -0,0 +1,377 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <rtl/ustrbuf.hxx> +#include <sfx2/linkmgr.hxx> +#include <sot/exchange.hxx> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <editsh.hxx> +#include <fmtfld.hxx> +#include <ddefld.hxx> +#include <swbaslnk.hxx> +#include <unofldmid.h> +#include <hints.hxx> +#include <calbck.hxx> + +using namespace ::com::sun::star; + +#define DDE_TXT_ENCODING osl_getThreadTextEncoding() + +namespace { + +class SwIntrnlRefLink : public SwBaseLink +{ + SwDDEFieldType& rFieldType; +public: + SwIntrnlRefLink( SwDDEFieldType& rType, SfxLinkUpdateMode nUpdateType ) + : SwBaseLink( nUpdateType, SotClipboardFormatId::STRING ), + rFieldType( rType ) + {} + + virtual void Closed() override; + virtual ::sfx2::SvBaseLink::UpdateResult DataChanged( + const OUString& rMimeType, const css::uno::Any & rValue ) override; + + virtual const SwNode* GetAnchor() const override; + virtual bool IsInRange( sal_uLong nSttNd, sal_uLong nEndNd ) const override; +}; + +} + +::sfx2::SvBaseLink::UpdateResult SwIntrnlRefLink::DataChanged( const OUString& rMimeType, + const uno::Any & rValue ) +{ + switch( SotExchange::GetFormatIdFromMimeType( rMimeType ) ) + { + case SotClipboardFormatId::STRING: + if( !IsNoDataFlag() ) + { + uno::Sequence< sal_Int8 > aSeq; + rValue >>= aSeq; + OUString sStr( reinterpret_cast<char const *>(aSeq.getConstArray()), aSeq.getLength(), DDE_TXT_ENCODING ); + + // remove not needed CR-LF at the end + sal_Int32 n = sStr.getLength(); + while( n && 0 == sStr[ n-1 ] ) + --n; + if( n && 0x0a == sStr[ n-1 ] ) + --n; + if( n && 0x0d == sStr[ n-1 ] ) + --n; + + bool bDel = n != sStr.getLength(); + if( bDel ) + sStr = sStr.copy( 0, n ); + + rFieldType.SetExpansion( sStr ); + // set Expansion first! (otherwise this flag will be deleted) + rFieldType.SetCRLFDelFlag( bDel ); + } + break; + + // other formats + default: + return SUCCESS; + } + + OSL_ENSURE( rFieldType.GetDoc(), "no pDoc" ); + + // no dependencies left? + if( rFieldType.HasWriterListeners() && !rFieldType.IsModifyLocked() && !ChkNoDataFlag() ) + { + SwViewShell* pSh = rFieldType.GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwEditShell* pESh = rFieldType.GetDoc()->GetEditShell(); + + // Search for fields. If no valid found, disconnect. + SwMsgPoolItem aUpdateDDE( RES_UPDATEDDETBL ); + rFieldType.LockModify(); + + std::vector<SwFormatField*> vFields; + rFieldType.GatherFields(vFields, false); + if(vFields.size()) + { + if(pESh) + pESh->StartAllAction(); + else if(pSh) + pSh->StartAction(); + } + + for(auto pFormatField: vFields) + { + // a DDE table or a DDE field attribute in the text + if(pFormatField->GetTextField()) + pFormatField->UpdateTextNode( nullptr, &aUpdateDDE ); + } + + rFieldType.UnlockModify(); + + if(vFields.size()) + { + if(pESh) + pESh->EndAllAction(); + else if(pSh) + pSh->EndAction(); + + if(pSh) + pSh->GetDoc()->getIDocumentState().SetModified(); + } + } + + return SUCCESS; +} + +void SwIntrnlRefLink::Closed() +{ + if( rFieldType.GetDoc() && !rFieldType.GetDoc()->IsInDtor() ) + { + // advise goes, convert all fields into text? + SwViewShell* pSh = rFieldType.GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwEditShell* pESh = rFieldType.GetDoc()->GetEditShell(); + if( pESh ) + { + pESh->StartAllAction(); + pESh->FieldToText( &rFieldType ); + pESh->EndAllAction(); + } + else + { + pSh->StartAction(); + // to call at the doc ?? + pSh->EndAction(); + } + } + SvBaseLink::Closed(); +} + +sw::LinkAnchorSearchHint::~LinkAnchorSearchHint() {}; + +const SwNode* SwIntrnlRefLink::GetAnchor() const +{ + // here, any anchor of the normal NodesArray should be sufficient + const SwNode* pNd = nullptr; + rFieldType.CallSwClientNotify(sw::LinkAnchorSearchHint(rFieldType.GetDoc()->GetNodes(), pNd)); + return pNd; +} + +bool SwIntrnlRefLink::IsInRange( sal_uLong nSttNd, sal_uLong nEndNd ) const +{ + bool bInRange = false; + rFieldType.CallSwClientNotify(sw::InRangeSearchHint( + nSttNd, nEndNd, bInRange)); + return bInRange; +} + +SwDDEFieldType::SwDDEFieldType(const OUString& rName, + const OUString& rCmd, SfxLinkUpdateMode nUpdateType ) + : SwFieldType( SwFieldIds::Dde ), + aName( rName ), pDoc( nullptr ), nRefCnt( 0 ) +{ + bCRLFFlag = bDeleted = false; + refLink = new SwIntrnlRefLink( *this, nUpdateType ); + SetCmd( rCmd ); +} + +SwDDEFieldType::~SwDDEFieldType() +{ + if( pDoc && !pDoc->IsInDtor() ) + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( refLink.get() ); + refLink->Disconnect(); +} + +std::unique_ptr<SwFieldType> SwDDEFieldType::Copy() const +{ + std::unique_ptr<SwDDEFieldType> pType(new SwDDEFieldType( aName, GetCmd(), GetType() )); + pType->aExpansion = aExpansion; + pType->bCRLFFlag = bCRLFFlag; + pType->bDeleted = bDeleted; + pType->SetDoc( pDoc ); + return pType; +} + +OUString SwDDEFieldType::GetName() const +{ + return aName; +} + +void SwDDEFieldType::SetCmd( const OUString& _aStr ) +{ + OUString aStr = _aStr; + sal_Int32 nIndex = 0; + do + { + aStr = aStr.replaceFirst(" ", " ", &nIndex); + } while (nIndex>=0); + refLink->SetLinkSourceName( aStr ); +} + +OUString const & SwDDEFieldType::GetCmd() const +{ + return refLink->GetLinkSourceName(); +} + +void SwDDEFieldType::SetDoc( SwDoc* pNewDoc ) +{ + if( pNewDoc == pDoc ) + return; + + if( pDoc && refLink.is() ) + { + OSL_ENSURE( !nRefCnt, "How do we get the references?" ); + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( refLink.get() ); + } + + pDoc = pNewDoc; + if( pDoc && nRefCnt ) + { + refLink->SetVisible( pDoc->getIDocumentLinksAdministration().IsVisibleLinks() ); + pDoc->getIDocumentLinksAdministration().GetLinkManager().InsertDDELink( refLink.get() ); + } +} + +void SwDDEFieldType::RefCntChgd() +{ + if( nRefCnt ) + { + refLink->SetVisible( pDoc->getIDocumentLinksAdministration().IsVisibleLinks() ); + pDoc->getIDocumentLinksAdministration().GetLinkManager().InsertDDELink( refLink.get() ); + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + refLink->Update(); + } + else + { + Disconnect(); + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( refLink.get() ); + } +} + +void SwDDEFieldType::QueryValue( uno::Any& rVal, sal_uInt16 nWhichId ) const +{ + sal_Int32 nPart = -1; + switch( nWhichId ) + { + case FIELD_PROP_PAR2: nPart = 2; break; + case FIELD_PROP_PAR4: nPart = 1; break; + case FIELD_PROP_SUBTYPE: nPart = 0; break; + case FIELD_PROP_BOOL1: + rVal <<= GetType() == SfxLinkUpdateMode::ALWAYS; + break; + case FIELD_PROP_PAR5: + rVal <<= aExpansion; + break; + default: + assert(false); + } + if ( nPart>=0 ) + rVal <<= GetCmd().getToken(nPart, sfx2::cTokenSeparator); +} + +void SwDDEFieldType::PutValue( const uno::Any& rVal, sal_uInt16 nWhichId ) +{ + sal_Int32 nPart = -1; + switch( nWhichId ) + { + case FIELD_PROP_PAR2: nPart = 2; break; + case FIELD_PROP_PAR4: nPart = 1; break; + case FIELD_PROP_SUBTYPE: nPart = 0; break; + case FIELD_PROP_BOOL1: + SetType( *o3tl::doAccess<bool>(rVal) ? + SfxLinkUpdateMode::ALWAYS : + SfxLinkUpdateMode::ONCALL ); + break; + case FIELD_PROP_PAR5: + rVal >>= aExpansion; + break; + default: + assert(false); + } + if( nPart>=0 ) + { + const OUString sOldCmd( GetCmd() ); + OUStringBuffer sNewCmd; + sal_Int32 nIndex = 0; + for (sal_Int32 i=0; i<3; ++i) + { + OUString sToken = sOldCmd.getToken(0, sfx2::cTokenSeparator, nIndex); + if (i==nPart) + { + rVal >>= sToken; + } + sNewCmd.append((i < 2) + ? sToken + OUStringChar(sfx2::cTokenSeparator) : sToken); + } + SetCmd( sNewCmd.makeStringAndClear() ); + } +} + +SwDDEField::SwDDEField( SwDDEFieldType* pInitType ) + : SwField(pInitType) +{ +} + +SwDDEField::~SwDDEField() +{ + if( GetTyp()->HasOnlyOneListener() ) + static_cast<SwDDEFieldType*>(GetTyp())->Disconnect(); +} + +OUString SwDDEField::ExpandImpl(SwRootFrame const*const) const +{ + OUString aStr = static_cast<SwDDEFieldType*>(GetTyp())->GetExpansion(); + aStr = aStr.replaceAll("\r", ""); + aStr = aStr.replaceAll("\t", " "); + aStr = aStr.replaceAll("\n", "|"); + if (aStr.endsWith("|")) + { + return aStr.copy(0, aStr.getLength()-1); + } + return aStr; +} + +std::unique_ptr<SwField> SwDDEField::Copy() const +{ + return std::make_unique<SwDDEField>(static_cast<SwDDEFieldType*>(GetTyp())); +} + +/// get field type name +OUString SwDDEField::GetPar1() const +{ + return static_cast<const SwDDEFieldType*>(GetTyp())->GetName(); +} + +/// get field type command +OUString SwDDEField::GetPar2() const +{ + return static_cast<const SwDDEFieldType*>(GetTyp())->GetCmd(); +} + +/// set field type command +void SwDDEField::SetPar2(const OUString& rStr) +{ + static_cast<SwDDEFieldType*>(GetTyp())->SetCmd(rStr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/ddetbl.cxx b/sw/source/core/fields/ddetbl.cxx new file mode 100644 index 000000000..5e7f3f7e5 --- /dev/null +++ b/sw/source/core/fields/ddetbl.cxx @@ -0,0 +1,207 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <index.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swddetbl.hxx> +#include <ddefld.hxx> +#include <ndindex.hxx> +#include <fldupde.hxx> +#include <swtblfmt.hxx> +#include <fieldhint.hxx> +#include <osl/diagnose.h> + +/// Ctor moves all lines/boxes from a SwTable into itself. +/// Afterwards the SwTable is empty and must be deleted. +SwDDETable::SwDDETable( SwTable& rTable, SwDDEFieldType* pDDEType, bool bUpdate ) + : SwTable(rTable), m_aDepends(*this), m_pDDEType(pDDEType) +{ + m_aDepends.StartListening(m_pDDEType); + // copy the table data + m_TabSortContentBoxes.insert(rTable.GetTabSortBoxes()); + rTable.GetTabSortBoxes().clear(); + + m_aLines.insert( m_aLines.begin(), + rTable.GetTabLines().begin(), rTable.GetTabLines().end() ); // move lines + rTable.GetTabLines().clear(); + + if( !m_aLines.empty() ) + { + const SwNode& rNd = *GetTabSortBoxes()[0]->GetSttNd(); + if( rNd.GetNodes().IsDocNodes() ) + { + pDDEType->IncRefCnt(); + + // update box content only if update flag is set (false in import) + if (bUpdate) + ChangeContent(); + } + } +} + +SwDDETable::~SwDDETable() +{ + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + if (!pDoc->IsInDtor() && !m_aLines.empty()) + { + assert(m_pTableNode); + if (m_pTableNode->GetNodes().IsDocNodes()) + { + m_pDDEType->DecRefCnt(); + } + } + + // If it is the last dependent of the "deleted field" than delete it finally + if( m_pDDEType->IsDeleted() && m_pDDEType->HasOnlyOneListener() ) + { + m_aDepends.EndListeningAll(); + delete m_pDDEType; + m_pDDEType = nullptr; + } +} + +void SwDDETable::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( pNew && RES_UPDATEDDETBL == pNew->Which() ) + ChangeContent(); + else + SwTable::Modify( pOld, pNew ); +} + +void SwDDETable::SwClientNotify( const SwModify& rModify, const SfxHint& rHint ) +{ + SwClient::SwClientNotify(rModify, rHint); + if(dynamic_cast<const SwFieldHint*>(&rHint)) + // replace DDETable by real table + NoDDETable(); + else if(const auto pLinkAnchorHint = dynamic_cast<const sw::LinkAnchorSearchHint*>(&rHint)) + { + if(pLinkAnchorHint->m_rpFoundNode) + return; + const auto pNd = GetTabSortBoxes()[0]->GetSttNd(); + if( pNd && &pLinkAnchorHint->m_rNodes == &pNd->GetNodes() ) + pLinkAnchorHint->m_rpFoundNode = pNd; + } + else if(const sw::InRangeSearchHint* pInRangeHint = dynamic_cast<const sw::InRangeSearchHint*>(&rHint)) + { + if(pInRangeHint->m_rIsInRange) + return; + const SwTableNode* pTableNd = GetTabSortBoxes()[0]->GetSttNd()->FindTableNode(); + if( pTableNd->GetNodes().IsDocNodes() && + pInRangeHint->m_nSttNd < pTableNd->EndOfSectionIndex() && + pInRangeHint->m_nEndNd > pTableNd->GetIndex() ) + pInRangeHint->m_rIsInRange = true; + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + if(m_pDDEType == &rModify) + m_pDDEType = const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pModifyChangedHint->m_pNew)); + } +} + +void SwDDETable::ChangeContent() +{ + OSL_ENSURE( GetFrameFormat(), "No FrameFormat" ); + + // Is this the correct NodesArray? (because of UNDO) + if( m_aLines.empty() ) + return; + OSL_ENSURE( !GetTabSortBoxes().empty(), "Table without content?" ); + if( !GetTabSortBoxes()[0]->GetSttNd()->GetNodes().IsDocNodes() ) + return; + + + OUString aExpand = m_pDDEType->GetExpansion().replaceAll("\r", ""); + sal_Int32 nExpandTokenPos = 0; + + for( size_t n = 0; n < m_aLines.size(); ++n ) + { + OUString aLine = aExpand.getToken( 0, '\n', nExpandTokenPos ); + sal_Int32 nLineTokenPos = 0; + SwTableLine* pLine = m_aLines[ n ]; + for( size_t i = 0; i < pLine->GetTabBoxes().size(); ++i ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[ i ]; + OSL_ENSURE( pBox->GetSttIdx(), "no content box" ); + SwNodeIndex aNdIdx( *pBox->GetSttNd(), 1 ); + SwTextNode* pTextNode = aNdIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode, "No Node" ); + SwIndex aCntIdx( pTextNode, 0 ); + pTextNode->EraseText( aCntIdx ); + pTextNode->InsertText( aLine.getToken( 0, '\t', nLineTokenPos ), aCntIdx ); + + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + pBoxFormat->UnlockModify(); + } + } + + const IDocumentSettingAccess& rIDSA = GetFrameFormat()->getIDocumentSettingAccess(); + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + if( AUTOUPD_FIELD_AND_CHARTS == rIDSA.getFieldUpdateFlags(true) ) + pDoc->getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); +} + +SwDDEFieldType* SwDDETable::GetDDEFieldType() +{ + return m_pDDEType; +} + +bool SwDDETable::NoDDETable() +{ + // search table node + OSL_ENSURE( GetFrameFormat(), "No FrameFormat" ); + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + + // Is this the correct NodesArray? (because of UNDO) + if( m_aLines.empty() ) + return false; + OSL_ENSURE( !GetTabSortBoxes().empty(), "Table without content?" ); + SwNode* pNd = const_cast<SwNode*>(static_cast<SwNode const *>(GetTabSortBoxes()[0]->GetSttNd())); + if( !pNd->GetNodes().IsDocNodes() ) + return false; + + SwTableNode* pTableNd = pNd->FindTableNode(); + OSL_ENSURE( pTableNd, "Where is the table?"); + + std::unique_ptr<SwTable> pNewTable(new SwTable( *this )); + + // copy the table data + pNewTable->GetTabSortBoxes().insert( GetTabSortBoxes() ); // move content boxes + GetTabSortBoxes().clear(); + + pNewTable->GetTabLines().insert( pNewTable->GetTabLines().begin(), + GetTabLines().begin(), GetTabLines().end() ); // move lines + GetTabLines().clear(); + + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + m_pDDEType->DecRefCnt(); + + pTableNd->SetNewTable( std::move(pNewTable) ); // replace table + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/docufld.cxx b/sw/source/core/fields/docufld.cxx new file mode 100644 index 000000000..92afe5024 --- /dev/null +++ b/sw/source/core/fields/docufld.cxx @@ -0,0 +1,2646 @@ +/* -*- 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 <config_features.h> + +#include <textapi.hxx> + +#include <hintids.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <com/sun/star/text/PlaceholderType.hpp> +#include <com/sun/star/text/TemplateDisplayFormat.hpp> +#include <com/sun/star/text/PageNumberType.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Duration.hpp> +#include <o3tl/any.hxx> +#include <unotools/localedatawrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <tools/urlobj.hxx> +#include <svl/urihelper.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/syslocale.hxx> +#include <svl/zforlist.hxx> +#include <libxml/xmlstring.h> +#include <libxml/xmlwriter.h> + +#include <tools/time.hxx> +#include <tools/datetime.hxx> + +#include <com/sun/star/util/DateTime.hpp> + +#include <swmodule.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/doctempl.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <charfmt.hxx> +#include <docstat.hxx> +#include <pagedesc.hxx> +#include <fmtpdsc.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <viewsh.hxx> +#include <dbmgr.hxx> +#include <shellres.hxx> +#include <docufld.hxx> +#include <flddat.hxx> +#include <docfld.hxx> +#include <ndtxt.hxx> +#include <expfld.hxx> +#include <poolfmt.hxx> +#include <docsh.hxx> +#include <unofldmid.h> +#include <swunohelper.hxx> +#include <strings.hrc> + +#include <editeng/outlobj.hxx> +#include <calbck.hxx> +#include <hints.hxx> + +#define URL_DECODE INetURLObject::DecodeMechanism::Unambiguous + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace nsSwDocInfoSubType; + +SwPageNumberFieldType::SwPageNumberFieldType() + : SwFieldType( SwFieldIds::PageNumber ), + m_nNumberingType( SVX_NUM_ARABIC ), + m_bVirtual( false ) +{ +} + +OUString SwPageNumberFieldType::Expand( SvxNumType nFormat, short nOff, + sal_uInt16 const nPageNumber, sal_uInt16 const nMaxPage, + const OUString& rUserStr, LanguageType nLang ) const +{ + SvxNumType nTmpFormat = (SVX_NUM_PAGEDESC == nFormat) ? m_nNumberingType : nFormat; + int const nTmp = nPageNumber + nOff; + + if (0 > nTmp || SVX_NUM_NUMBER_NONE == nTmpFormat || (!m_bVirtual && nTmp > nMaxPage)) + return OUString(); + + if( SVX_NUM_CHAR_SPECIAL == nTmpFormat ) + return rUserStr; + + return FormatNumber( nTmp, nTmpFormat, nLang ); +} + +std::unique_ptr<SwFieldType> SwPageNumberFieldType::Copy() const +{ + std::unique_ptr<SwPageNumberFieldType> pTmp(new SwPageNumberFieldType()); + + pTmp->m_nNumberingType = m_nNumberingType; + pTmp->m_bVirtual = m_bVirtual; + + return pTmp; +} + +void SwPageNumberFieldType::ChangeExpansion( SwDoc* pDoc, + bool bVirt, + const SvxNumType* pNumFormat ) +{ + if( pNumFormat ) + m_nNumberingType = *pNumFormat; + + m_bVirtual = false; + if (bVirt && pDoc) + { + // check the flag since the layout NEVER sets it back + const SfxItemPool &rPool = pDoc->GetAttrPool(); + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(RES_PAGEDESC)) + { + auto pDesc = dynamic_cast<const SwFormatPageDesc*>(pItem); + if( pDesc && pDesc->GetNumOffset() && pDesc->GetDefinedIn() ) + { + const SwContentNode* pNd = dynamic_cast<const SwContentNode*>( pDesc->GetDefinedIn() ); + if( pNd ) + { + if (SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*pNd).First()) + // sw_redlinehide: not sure if this should happen only if + // it's the first node, because that's where RES_PAGEDESC + // is effective? + m_bVirtual = true; + } + else if( dynamic_cast< const SwFormat* >(pDesc->GetDefinedIn()) != nullptr) + { + SwAutoFormatGetDocNode aGetHt( &pDoc->GetNodes() ); + m_bVirtual = !pDesc->GetDefinedIn()->GetInfo( aGetHt ); + break; + } + } + } + } +} + +SwPageNumberField::SwPageNumberField(SwPageNumberFieldType* pTyp, + sal_uInt16 nSub, sal_uInt32 nFormat, short nOff, + sal_uInt16 const nPageNumber, sal_uInt16 const nMaxPage) + : SwField(pTyp, nFormat), m_nSubType(nSub), m_nOffset(nOff) + , m_nPageNumber(nPageNumber) + , m_nMaxPage(nMaxPage) +{ +} + +void SwPageNumberField::ChangeExpansion(sal_uInt16 const nPageNumber, + sal_uInt16 const nMaxPage) +{ + m_nPageNumber = nPageNumber; + m_nMaxPage = nMaxPage; +} + +OUString SwPageNumberField::ExpandImpl(SwRootFrame const*const) const +{ + OUString sRet; + SwPageNumberFieldType* pFieldType = static_cast<SwPageNumberFieldType*>(GetTyp()); + + if( PG_NEXT == m_nSubType && 1 != m_nOffset ) + { + sRet = pFieldType->Expand(static_cast<SvxNumType>(GetFormat()), 1, m_nPageNumber, m_nMaxPage, m_sUserStr, GetLanguage()); + if (!sRet.isEmpty()) + { + sRet = pFieldType->Expand(static_cast<SvxNumType>(GetFormat()), m_nOffset, m_nPageNumber, m_nMaxPage, m_sUserStr, GetLanguage()); + } + } + else if( PG_PREV == m_nSubType && -1 != m_nOffset ) + { + sRet = pFieldType->Expand(static_cast<SvxNumType>(GetFormat()), -1, m_nPageNumber, m_nMaxPage, m_sUserStr, GetLanguage()); + if (!sRet.isEmpty()) + { + sRet = pFieldType->Expand(static_cast<SvxNumType>(GetFormat()), m_nOffset, m_nPageNumber, m_nMaxPage, m_sUserStr, GetLanguage()); + } + } + else + sRet = pFieldType->Expand(static_cast<SvxNumType>(GetFormat()), m_nOffset, m_nPageNumber, m_nMaxPage, m_sUserStr, GetLanguage()); + return sRet; +} + +std::unique_ptr<SwField> SwPageNumberField::Copy() const +{ + std::unique_ptr<SwPageNumberField> pTmp(new SwPageNumberField( + static_cast<SwPageNumberFieldType*>(GetTyp()), m_nSubType, + GetFormat(), m_nOffset, m_nPageNumber, m_nMaxPage)); + pTmp->SetLanguage( GetLanguage() ); + pTmp->SetUserString( m_sUserStr ); + return std::unique_ptr<SwField>(pTmp.release()); +} + +OUString SwPageNumberField::GetPar2() const +{ + return OUString::number(m_nOffset); +} + +void SwPageNumberField::SetPar2(const OUString& rStr) +{ + m_nOffset = static_cast<short>(rStr.toInt32()); +} + +sal_uInt16 SwPageNumberField::GetSubType() const +{ + return m_nSubType; +} + +bool SwPageNumberField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int16>(GetFormat()); + break; + case FIELD_PROP_USHORT1: + rAny <<= m_nOffset; + break; + case FIELD_PROP_SUBTYPE: + { + text::PageNumberType eType; + eType = text::PageNumberType_CURRENT; + if(m_nSubType == PG_PREV) + eType = text::PageNumberType_PREV; + else if(m_nSubType == PG_NEXT) + eType = text::PageNumberType_NEXT; + rAny <<= eType; + } + break; + case FIELD_PROP_PAR1: + rAny <<= m_sUserStr; + break; + + default: + assert(false); + } + return true; +} + +bool SwPageNumberField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = true; + sal_Int16 nSet = 0; + switch( nWhichId ) + { + case FIELD_PROP_FORMAT: + rAny >>= nSet; + + // TODO: where do the defines come from? + if(nSet <= SVX_NUM_PAGEDESC ) + SetFormat(nSet); + break; + case FIELD_PROP_USHORT1: + rAny >>= nSet; + m_nOffset = nSet; + break; + case FIELD_PROP_SUBTYPE: + switch( static_cast<text::PageNumberType>(SWUnoHelper::GetEnumAsInt32( rAny )) ) + { + case text::PageNumberType_CURRENT: + m_nSubType = PG_RANDOM; + break; + case text::PageNumberType_PREV: + m_nSubType = PG_PREV; + break; + case text::PageNumberType_NEXT: + m_nSubType = PG_NEXT; + break; + default: + bRet = false; + } + break; + case FIELD_PROP_PAR1: + rAny >>= m_sUserStr; + break; + + default: + assert(false); + } + return bRet; +} + +SwAuthorFieldType::SwAuthorFieldType() + : SwFieldType( SwFieldIds::Author ) +{ +} + +OUString SwAuthorFieldType::Expand(sal_uLong nFormat) +{ + SvtUserOptions& rOpt = SW_MOD()->GetUserOptions(); + if((nFormat & 0xff) == AF_NAME) + return rOpt.GetFullName(); + + return rOpt.GetID(); +} + +std::unique_ptr<SwFieldType> SwAuthorFieldType::Copy() const +{ + return std::make_unique<SwAuthorFieldType>(); +} + +SwAuthorField::SwAuthorField(SwAuthorFieldType* pTyp, sal_uInt32 nFormat) + : SwField(pTyp, nFormat) +{ + m_aContent = SwAuthorFieldType::Expand(GetFormat()); +} + +OUString SwAuthorField::ExpandImpl(SwRootFrame const*const) const +{ + if (!IsFixed()) + const_cast<SwAuthorField*>(this)->m_aContent = + SwAuthorFieldType::Expand(GetFormat()); + + return m_aContent; +} + +std::unique_ptr<SwField> SwAuthorField::Copy() const +{ + std::unique_ptr<SwAuthorField> pTmp(new SwAuthorField( static_cast<SwAuthorFieldType*>(GetTyp()), + GetFormat())); + pTmp->SetExpansion(m_aContent); + return std::unique_ptr<SwField>(pTmp.release()); +} + +bool SwAuthorField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + rAny <<= (GetFormat() & 0xff) == AF_NAME; + break; + + case FIELD_PROP_BOOL2: + rAny <<= IsFixed(); + break; + + case FIELD_PROP_PAR1: + rAny <<= m_aContent; + break; + + default: + assert(false); + } + return true; +} + +bool SwAuthorField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + SetFormat( *o3tl::doAccess<bool>(rAny) ? AF_NAME : AF_SHORTCUT ); + break; + + case FIELD_PROP_BOOL2: + if( *o3tl::doAccess<bool>(rAny) ) + SetFormat( GetFormat() | AF_FIXED); + else + SetFormat( GetFormat() & ~AF_FIXED); + break; + + case FIELD_PROP_PAR1: + rAny >>= m_aContent; + break; + + default: + assert(false); + } + return true; +} + +SwFileNameFieldType::SwFileNameFieldType(SwDoc *pDocument) + : SwFieldType( SwFieldIds::Filename ) +{ + m_pDoc = pDocument; +} + +OUString SwFileNameFieldType::Expand(sal_uLong nFormat) const +{ + OUString aRet; + const SwDocShell* pDShell = m_pDoc->GetDocShell(); + if( pDShell && pDShell->HasName() ) + { + const INetURLObject& rURLObj = pDShell->GetMedium()->GetURLObject(); + switch( nFormat & ~FF_FIXED ) + { + case FF_PATH: + { + if( INetProtocol::File == rURLObj.GetProtocol() ) + { + INetURLObject aTemp(rURLObj); + aTemp.removeSegment(); + // last slash should belong to the pathname + aRet = aTemp.PathToFileName(); + } + else + { + aRet = URIHelper::removePassword( + rURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + INetURLObject::EncodeMechanism::WasEncoded, URL_DECODE ); + const sal_Int32 nPos = aRet.indexOf(rURLObj.GetLastName( URL_DECODE )); + if (nPos>=0) + { + aRet = aRet.copy(0, nPos); + } + } + } + break; + + case FF_NAME: + aRet = rURLObj.GetLastName( INetURLObject::DecodeMechanism::WithCharset ); + break; + + case FF_NAME_NOEXT: + aRet = rURLObj.GetBase(); + break; + + default: + if( INetProtocol::File == rURLObj.GetProtocol() ) + aRet = rURLObj.GetFull(); + else + aRet = URIHelper::removePassword( + rURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + INetURLObject::EncodeMechanism::WasEncoded, URL_DECODE ); + } + } + return aRet; +} + +std::unique_ptr<SwFieldType> SwFileNameFieldType::Copy() const +{ + return std::make_unique<SwFileNameFieldType>(m_pDoc); +} + +SwFileNameField::SwFileNameField(SwFileNameFieldType* pTyp, sal_uInt32 nFormat) + : SwField(pTyp, nFormat) +{ + m_aContent = static_cast<SwFileNameFieldType*>(GetTyp())->Expand(GetFormat()); +} + +OUString SwFileNameField::ExpandImpl(SwRootFrame const*const) const +{ + if (!IsFixed()) + const_cast<SwFileNameField*>(this)->m_aContent = static_cast<SwFileNameFieldType*>(GetTyp())->Expand(GetFormat()); + + return m_aContent; +} + +std::unique_ptr<SwField> SwFileNameField::Copy() const +{ + std::unique_ptr<SwFileNameField> pTmp( + new SwFileNameField(static_cast<SwFileNameFieldType*>(GetTyp()), GetFormat())); + pTmp->SetExpansion(m_aContent); + + return std::unique_ptr<SwField>(pTmp.release()); +} + +bool SwFileNameField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_FORMAT: + { + sal_Int16 nRet; + switch( GetFormat() &(~FF_FIXED) ) + { + case FF_PATH: + nRet = text::FilenameDisplayFormat::PATH; + break; + case FF_NAME_NOEXT: + nRet = text::FilenameDisplayFormat::NAME; + break; + case FF_NAME: + nRet = text::FilenameDisplayFormat::NAME_AND_EXT; + break; + default: nRet = text::FilenameDisplayFormat::FULL; + } + rAny <<= nRet; + } + break; + + case FIELD_PROP_BOOL2: + rAny <<= IsFixed(); + break; + + case FIELD_PROP_PAR3: + rAny <<= m_aContent; + break; + + default: + assert(false); + } + return true; +} + +bool SwFileNameField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_FORMAT: + { + //JP 24.10.2001: int32 because in UnoField.cxx a putvalue is + // called with a int32 value! But normally we need + // here only a int16 + sal_Int32 nType = 0; + rAny >>= nType; + bool bFixed = IsFixed(); + switch( nType ) + { + case text::FilenameDisplayFormat::PATH: + nType = FF_PATH; + break; + case text::FilenameDisplayFormat::NAME: + nType = FF_NAME_NOEXT; + break; + case text::FilenameDisplayFormat::NAME_AND_EXT: + nType = FF_NAME; + break; + default: nType = FF_PATHNAME; + } + if(bFixed) + nType |= FF_FIXED; + SetFormat(nType); + } + break; + + case FIELD_PROP_BOOL2: + if( *o3tl::doAccess<bool>(rAny) ) + SetFormat( GetFormat() | FF_FIXED); + else + SetFormat( GetFormat() & ~FF_FIXED); + break; + + case FIELD_PROP_PAR3: + rAny >>= m_aContent; + break; + + default: + assert(false); + } + return true; +} + +SwTemplNameFieldType::SwTemplNameFieldType(SwDoc *pDocument) + : SwFieldType( SwFieldIds::TemplateName ) +{ + m_pDoc = pDocument; +} + +OUString SwTemplNameFieldType::Expand(sal_uLong nFormat) const +{ + OSL_ENSURE( nFormat < FF_END, "Expand: no valid Format!" ); + + OUString aRet; + SwDocShell *pDocShell(m_pDoc->GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + + if( FF_UI_NAME == nFormat ) + aRet = xDocProps->getTemplateName(); + else if( !xDocProps->getTemplateURL().isEmpty() ) + { + if( FF_UI_RANGE == nFormat ) + { + // for getting region names! + SfxDocumentTemplates aFac; + OUString sTmp; + OUString sRegion; + aFac.GetLogicNames( xDocProps->getTemplateURL(), sRegion, sTmp ); + aRet = sRegion; + } + else + { + INetURLObject aPathName( xDocProps->getTemplateURL() ); + if( FF_NAME == nFormat ) + aRet = aPathName.GetLastName(URL_DECODE); + else if( FF_NAME_NOEXT == nFormat ) + aRet = aPathName.GetBase(); + else + { + if( FF_PATH == nFormat ) + { + aPathName.removeSegment(); + aRet = aPathName.GetFull(); + } + else + aRet = aPathName.GetFull(); + } + } + } + } + return aRet; +} + +std::unique_ptr<SwFieldType> SwTemplNameFieldType::Copy() const +{ + return std::make_unique<SwTemplNameFieldType>(m_pDoc); +} + +SwTemplNameField::SwTemplNameField(SwTemplNameFieldType* pTyp, sal_uInt32 nFormat) + : SwField(pTyp, nFormat) +{} + +OUString SwTemplNameField::ExpandImpl(SwRootFrame const*const) const +{ + return static_cast<SwTemplNameFieldType*>(GetTyp())->Expand(GetFormat()); +} + +std::unique_ptr<SwField> SwTemplNameField::Copy() const +{ + return std::make_unique<SwTemplNameField>(static_cast<SwTemplNameFieldType*>(GetTyp()), GetFormat()); +} + +bool SwTemplNameField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch ( nWhichId ) + { + case FIELD_PROP_FORMAT: + { + sal_Int16 nRet; + switch( GetFormat() ) + { + case FF_PATH: nRet = text::FilenameDisplayFormat::PATH; break; + case FF_NAME_NOEXT: nRet = text::FilenameDisplayFormat::NAME; break; + case FF_NAME: nRet = text::FilenameDisplayFormat::NAME_AND_EXT; break; + case FF_UI_RANGE: nRet = text::TemplateDisplayFormat::AREA; break; + case FF_UI_NAME: nRet = text::TemplateDisplayFormat::TITLE; break; + default: nRet = text::FilenameDisplayFormat::FULL; + + } + rAny <<= nRet; + } + break; + + default: + assert(false); + } + return true; +} + +bool SwTemplNameField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch ( nWhichId ) + { + case FIELD_PROP_FORMAT: + { + //JP 24.10.2001: int32 because in UnoField.cxx a putvalue is + // called with a int32 value! But normally we need + // here only a int16 + sal_Int32 nType = 0; + rAny >>= nType; + switch( nType ) + { + case text::FilenameDisplayFormat::PATH: + SetFormat(FF_PATH); + break; + case text::FilenameDisplayFormat::NAME: + SetFormat(FF_NAME_NOEXT); + break; + case text::FilenameDisplayFormat::NAME_AND_EXT: + SetFormat(FF_NAME); + break; + case text::TemplateDisplayFormat::AREA : + SetFormat(FF_UI_RANGE); + break; + case text::TemplateDisplayFormat::TITLE : + SetFormat(FF_UI_NAME); + break; + default: SetFormat(FF_PATHNAME); + } + } + break; + + default: + assert(false); + } + return true; +} + +SwDocStatFieldType::SwDocStatFieldType(SwDoc* pDocument) + : SwFieldType( SwFieldIds::DocStat ), m_nNumberingType( SVX_NUM_ARABIC ) +{ + m_pDoc = pDocument; +} + +OUString SwDocStatFieldType::Expand(sal_uInt16 nSubType, SvxNumType nFormat) const +{ + sal_uInt32 nVal = 0; + const SwDocStat& rDStat = m_pDoc->getIDocumentStatistics().GetDocStat(); + switch( nSubType ) + { + case DS_TBL: nVal = rDStat.nTable; break; + case DS_GRF: nVal = rDStat.nGrf; break; + case DS_OLE: nVal = rDStat.nOLE; break; + case DS_PARA: nVal = rDStat.nPara; break; + case DS_WORD: nVal = rDStat.nWord; break; + case DS_CHAR: nVal = rDStat.nChar; break; + case DS_PAGE: + if( m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) + const_cast<SwDocStat &>(rDStat).nPage = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->GetPageNum(); + nVal = rDStat.nPage; + if( SVX_NUM_PAGEDESC == nFormat ) + nFormat = m_nNumberingType; + break; + default: + OSL_FAIL( "SwDocStatFieldType::Expand: unknown SubType" ); + } + + if( nVal <= SHRT_MAX ) + return FormatNumber( nVal, nFormat ); + + return OUString::number( nVal ); +} + +std::unique_ptr<SwFieldType> SwDocStatFieldType::Copy() const +{ + return std::make_unique<SwDocStatFieldType>(m_pDoc); +} + +/** + * @param pTyp + * @param nSub SubType + * @param nFormat + */ +SwDocStatField::SwDocStatField(SwDocStatFieldType* pTyp, sal_uInt16 nSub, sal_uInt32 nFormat) + : SwField(pTyp, nFormat), + m_nSubType(nSub) +{} + +OUString SwDocStatField::ExpandImpl(SwRootFrame const*const) const +{ + return static_cast<SwDocStatFieldType*>(GetTyp())->Expand(m_nSubType, static_cast<SvxNumType>(GetFormat())); +} + +std::unique_ptr<SwField> SwDocStatField::Copy() const +{ + return std::make_unique<SwDocStatField>( + static_cast<SwDocStatFieldType*>(GetTyp()), m_nSubType, GetFormat() ); +} + +sal_uInt16 SwDocStatField::GetSubType() const +{ + return m_nSubType; +} + +void SwDocStatField::SetSubType(sal_uInt16 nSub) +{ + m_nSubType = nSub; +} + +void SwDocStatField::ChangeExpansion( const SwFrame* pFrame ) +{ + if( DS_PAGE == m_nSubType && SVX_NUM_PAGEDESC == GetFormat() ) + static_cast<SwDocStatFieldType*>(GetTyp())->SetNumFormat( + pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType() ); +} + +bool SwDocStatField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch ( nWhichId ) + { + case FIELD_PROP_USHORT2: + rAny <<= static_cast<sal_Int16>(GetFormat()); + break; + + default: + assert(false); + } + return true; +} + +bool SwDocStatField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = false; + switch ( nWhichId ) + { + case FIELD_PROP_USHORT2: + { + sal_Int16 nSet = 0; + rAny >>= nSet; + if(nSet <= SVX_NUM_CHARS_LOWER_LETTER_N && + nSet != SVX_NUM_CHAR_SPECIAL && + nSet != SVX_NUM_BITMAP) + { + SetFormat(nSet); + bRet = true; + } + } + break; + + default: + assert(false); + } + return bRet; +} + +// Document info field type + +SwDocInfoFieldType::SwDocInfoFieldType(SwDoc* pDc) + : SwValueFieldType( pDc, SwFieldIds::DocInfo ) +{ +} + +std::unique_ptr<SwFieldType> SwDocInfoFieldType::Copy() const +{ + return std::make_unique<SwDocInfoFieldType>(GetDoc()); +} + +static void lcl_GetLocalDataWrapper( LanguageType nLang, + const LocaleDataWrapper **ppAppLocalData, + const LocaleDataWrapper **ppLocalData ) +{ + SvtSysLocale aLocale; + *ppAppLocalData = &aLocale.GetLocaleData(); + *ppLocalData = *ppAppLocalData; + if( nLang != (*ppLocalData)->getLanguageTag().getLanguageType() ) + *ppLocalData = new LocaleDataWrapper(LanguageTag( nLang )); +} + +OUString SwDocInfoFieldType::Expand( sal_uInt16 nSub, sal_uInt32 nFormat, + LanguageType nLang, const OUString& rName ) const +{ + const LocaleDataWrapper *pAppLocalData = nullptr, *pLocalData = nullptr; + SwDocShell *pDocShell(GetDoc()->GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (!pDocShell) { return OUString(); } + + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + + sal_uInt16 nExtSub = nSub & 0xff00; + nSub &= 0xff; // do not consider extended SubTypes + + OUString aStr; + switch(nSub) + { + case DI_TITLE: aStr = xDocProps->getTitle(); break; + case DI_SUBJECT:aStr = xDocProps->getSubject(); break; + case DI_KEYS: aStr = ::comphelper::string::convertCommaSeparated( + xDocProps->getKeywords()); + break; + case DI_COMMENT:aStr = xDocProps->getDescription(); break; + case DI_DOCNO: aStr = OUString::number( + xDocProps->getEditingCycles() ); + break; + case DI_EDIT: + if ( !nFormat ) + { + lcl_GetLocalDataWrapper( nLang, &pAppLocalData, &pLocalData ); + sal_Int32 dur = xDocProps->getEditingDuration(); + // If Seconds > 0 then bSec should be TRUE otherwise Seconds + // information will be lost if file has EditTime in Seconds format. + aStr = pLocalData->getTime( tools::Time(dur/3600, (dur%3600)/60, dur%60), + dur%60 > 0); + } + else + { + sal_Int32 dur = xDocProps->getEditingDuration(); + double fVal = tools::Time(dur/3600, (dur%3600)/60, dur%60).GetTimeInDays(); + aStr = ExpandValue(fVal, nFormat, nLang); + } + break; + case DI_CUSTOM: + { + OUString sVal; + try + { + uno::Any aAny; + uno::Reference < beans::XPropertySet > xSet( + xDocProps->getUserDefinedProperties(), + uno::UNO_QUERY_THROW); + aAny = xSet->getPropertyValue( rName ); + + uno::Reference < script::XTypeConverter > xConverter( script::Converter::create(comphelper::getProcessComponentContext()) ); + uno::Any aNew = xConverter->convertToSimpleType( aAny, uno::TypeClass_STRING ); + aNew >>= sVal; + } + catch (uno::Exception&) {} + return sVal; + } + + default: + { + OUString aName( xDocProps->getAuthor() ); + util::DateTime uDT( xDocProps->getCreationDate() ); + DateTime aDate(uDT); + if( nSub == DI_CREATE ) + ; // that's it !! + else if( nSub == DI_CHANGE ) + { + aName = xDocProps->getModifiedBy(); + uDT = xDocProps->getModificationDate(); + aDate = DateTime(uDT); + } + else if( nSub == DI_PRINT ) + { + aName = xDocProps->getPrintedBy(); + uDT = xDocProps->getPrintDate(); + aDate = DateTime(uDT); + } + else + break; + + if (aDate.IsValidAndGregorian()) + { + switch (nExtSub & ~DI_SUB_FIXED) + { + case DI_SUB_AUTHOR: + aStr = aName; + break; + + case DI_SUB_TIME: + if (!nFormat) + { + lcl_GetLocalDataWrapper( nLang, &pAppLocalData, + &pLocalData ); + aStr = pLocalData->getTime( aDate, + false); + } + else + { + // start the number formatter + double fVal = SwDateTimeField::GetDateTime( GetDoc(), + aDate); + aStr = ExpandValue(fVal, nFormat, nLang); + } + break; + + case DI_SUB_DATE: + if (!nFormat) + { + lcl_GetLocalDataWrapper( nLang, &pAppLocalData, + &pLocalData ); + aStr = pLocalData->getDate( aDate ); + } + else + { + // start the number formatter + double fVal = SwDateTimeField::GetDateTime( GetDoc(), + aDate); + aStr = ExpandValue(fVal, nFormat, nLang); + } + break; + } + } + } + break; + } + + if( pAppLocalData != pLocalData ) + delete pLocalData; + + return aStr; +} + +// document info field + +SwDocInfoField::SwDocInfoField(SwDocInfoFieldType* pTyp, sal_uInt16 nSub, const OUString& rName, sal_uInt32 nFormat) : + SwValueField(pTyp, nFormat), m_nSubType(nSub) +{ + m_aName = rName; + m_aContent = static_cast<SwDocInfoFieldType*>(GetTyp())->Expand(m_nSubType, nFormat, GetLanguage(), m_aName); +} + +SwDocInfoField::SwDocInfoField(SwDocInfoFieldType* pTyp, sal_uInt16 nSub, const OUString& rName, const OUString& rValue, sal_uInt32 nFormat) : + SwValueField(pTyp, nFormat), m_nSubType(nSub) +{ + m_aName = rName; + m_aContent = rValue; +} + +template<class T> +static double lcl_TimeToDouble( const T& rTime ) +{ + const double fNanoSecondsPerDay = 86400000000000.0; + return ( (rTime.Hours * SAL_CONST_INT64(3600000000000)) + + (rTime.Minutes * SAL_CONST_INT64( 60000000000)) + + (rTime.Seconds * SAL_CONST_INT64( 1000000000)) + + (rTime.NanoSeconds)) + / fNanoSecondsPerDay; +} + +template<class D> +static double lcl_DateToDouble( const D& rDate, const Date& rNullDate ) +{ + long nDate = Date::DateToDays( rDate.Day, rDate.Month, rDate.Year ); + long nNullDate = Date::DateToDays( rNullDate.GetDay(), rNullDate.GetMonth(), rNullDate.GetYear() ); + return double( nDate - nNullDate ); +} + +OUString SwDocInfoField::ExpandImpl(SwRootFrame const*const) const +{ + if ( ( m_nSubType & 0xFF ) == DI_CUSTOM ) + { + // custom properties currently need special treatment + // We don't have a secure way to detect "real" custom properties in Word import of text + // fields, so we treat *every* unknown property as a custom property, even the "built-in" + // section in Word's document summary information stream as these properties have not been + // inserted when the document summary information was imported, we do it here. + // This approach is still a lot better than the old one to import such fields as + // "user fields" and simple text + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if( !pDocShell ) + return m_aContent; + try + { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( xDPS->getDocumentProperties()); + uno::Reference < beans::XPropertySet > xSet( xDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW); + uno::Reference < beans::XPropertySetInfo > xSetInfo = xSet->getPropertySetInfo(); + + uno::Any aAny; + if( xSetInfo->hasPropertyByName( m_aName ) ) + aAny = xSet->getPropertyValue( m_aName ); + if ( aAny.getValueType() != cppu::UnoType<void>::get() ) + { + // "void" type means that the property has not been inserted until now + if ( !IsFixed() ) + { + // if the field is "fixed" we don't update it from the property + OUString sVal; + uno::Reference < script::XTypeConverter > xConverter( script::Converter::create(comphelper::getProcessComponentContext()) ); + util::Date aDate; + util::DateTime aDateTime; + util::Duration aDuration; + if( aAny >>= aDate) + { + SvNumberFormatter* pFormatter = pDocShell->GetDoc()->GetNumberFormatter(); + const Date& rNullDate = pFormatter->GetNullDate(); + sVal = ExpandValue( lcl_DateToDouble<util::Date>( aDate, rNullDate ), GetFormat(), GetLanguage()); + } + else if( aAny >>= aDateTime ) + { + double fDateTime = lcl_TimeToDouble<util::DateTime>( aDateTime ); + SvNumberFormatter* pFormatter = pDocShell->GetDoc()->GetNumberFormatter(); + const Date& rNullDate = pFormatter->GetNullDate(); + fDateTime += lcl_DateToDouble<util::DateTime>( aDateTime, rNullDate ); + sVal = ExpandValue( fDateTime, GetFormat(), GetLanguage()); + } + else if( aAny >>= aDuration ) + { + sVal = OUStringChar(aDuration.Negative ? '-' : '+') + + SwViewShell::GetShellRes()->sDurationFormat; + sVal = sVal.replaceFirst("%1", OUString::number( aDuration.Years ) ); + sVal = sVal.replaceFirst("%2", OUString::number( aDuration.Months ) ); + sVal = sVal.replaceFirst("%3", OUString::number( aDuration.Days ) ); + sVal = sVal.replaceFirst("%4", OUString::number( aDuration.Hours ) ); + sVal = sVal.replaceFirst("%5", OUString::number( aDuration.Minutes) ); + sVal = sVal.replaceFirst("%6", OUString::number( aDuration.Seconds) ); + } + else + { + uno::Any aNew = xConverter->convertToSimpleType( aAny, uno::TypeClass_STRING ); + aNew >>= sVal; + } + const_cast<SwDocInfoField*>(this)->m_aContent = sVal; + } + } + } + catch (uno::Exception&) {} + } + else if ( !IsFixed() ) + const_cast<SwDocInfoField*>(this)->m_aContent = static_cast<SwDocInfoFieldType*>(GetTyp())->Expand(m_nSubType, GetFormat(), GetLanguage(), m_aName); + + return m_aContent; +} + +OUString SwDocInfoField::GetFieldName() const +{ + OUString aStr(SwFieldType::GetTypeStr(GetTypeId()) + ":"); + + sal_uInt16 const nSub = m_nSubType & 0xff; + + switch (nSub) + { + case DI_CUSTOM: + aStr += m_aName; + break; + + default: + aStr += SwViewShell::GetShellRes() + ->aDocInfoLst[ nSub - DI_SUBTYPE_BEGIN ]; + break; + } + if (IsFixed()) + { + aStr += " " + SwViewShell::GetShellRes()->aFixedStr; + } + return aStr; +} + +std::unique_ptr<SwField> SwDocInfoField::Copy() const +{ + std::unique_ptr<SwDocInfoField> pField(new SwDocInfoField(static_cast<SwDocInfoFieldType*>(GetTyp()), m_nSubType, m_aName, GetFormat())); + pField->SetAutomaticLanguage(IsAutomaticLanguage()); + pField->m_aContent = m_aContent; + + return std::unique_ptr<SwField>(pField.release()); +} + +sal_uInt16 SwDocInfoField::GetSubType() const +{ + return m_nSubType; +} + +void SwDocInfoField::SetSubType(sal_uInt16 nSub) +{ + m_nSubType = nSub; +} + +void SwDocInfoField::SetLanguage(LanguageType nLng) +{ + if (!GetFormat()) + SwField::SetLanguage(nLng); + else + SwValueField::SetLanguage(nLng); +} + +bool SwDocInfoField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_aContent; + break; + + case FIELD_PROP_PAR4: + rAny <<= m_aName; + break; + + case FIELD_PROP_USHORT1: + rAny <<= static_cast<sal_Int16>(m_aContent.toInt32()); + break; + + case FIELD_PROP_BOOL1: + rAny <<= 0 != (m_nSubType & DI_SUB_FIXED); + break; + + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int32>(GetFormat()); + break; + + case FIELD_PROP_DOUBLE: + { + double fVal = GetValue(); + rAny <<= fVal; + } + break; + case FIELD_PROP_PAR3: + rAny <<= ExpandImpl(nullptr); + break; + case FIELD_PROP_BOOL2: + { + sal_uInt16 nExtSub = (m_nSubType & 0xff00) & ~DI_SUB_FIXED; + rAny <<= nExtSub == DI_SUB_DATE; + } + break; + default: + return SwField::QueryValue(rAny, nWhichId); + } + return true; +} + +bool SwDocInfoField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + sal_Int32 nValue = 0; + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + if( m_nSubType & DI_SUB_FIXED ) + rAny >>= m_aContent; + break; + + case FIELD_PROP_USHORT1: + if( m_nSubType & DI_SUB_FIXED ) + { + rAny >>= nValue; + m_aContent = OUString::number(nValue); + } + break; + + case FIELD_PROP_BOOL1: + if(*o3tl::doAccess<bool>(rAny)) + m_nSubType |= DI_SUB_FIXED; + else + m_nSubType &= ~DI_SUB_FIXED; + break; + case FIELD_PROP_FORMAT: + { + rAny >>= nValue; + if( nValue >= 0) + SetFormat(nValue); + } + break; + + case FIELD_PROP_PAR3: + rAny >>= m_aContent; + break; + case FIELD_PROP_BOOL2: + m_nSubType &= 0xf0ff; + if(*o3tl::doAccess<bool>(rAny)) + m_nSubType |= DI_SUB_DATE; + else + m_nSubType |= DI_SUB_TIME; + break; + default: + return SwField::PutValue(rAny, nWhichId); + } + return true; +} + +SwHiddenTextFieldType::SwHiddenTextFieldType( bool bSetHidden ) + : SwFieldType( SwFieldIds::HiddenText ), m_bHidden( bSetHidden ) +{ +} + +std::unique_ptr<SwFieldType> SwHiddenTextFieldType::Copy() const +{ + return std::make_unique<SwHiddenTextFieldType>( m_bHidden ); +} + +void SwHiddenTextFieldType::SetHiddenFlag( bool bSetHidden ) +{ + if( m_bHidden != bSetHidden ) + { + m_bHidden = bSetHidden; + UpdateFields(); // notify all HiddenTexts + } +} + +SwHiddenTextField::SwHiddenTextField( SwHiddenTextFieldType* pFieldType, + bool bConditional, + const OUString& rCond, + const OUString& rStr, + bool bHidden, + SwFieldTypesEnum nSub) : + SwField( pFieldType ), m_aCond(rCond), m_nSubType(nSub), + m_bCanToggle(bConditional), m_bIsHidden(bHidden), m_bValid(false) +{ + if(m_nSubType == SwFieldTypesEnum::ConditionalText) + { + sal_Int32 nPos = 0; + m_aTRUEText = rStr.getToken(0, '|', nPos); + + if(nPos != -1) + { + m_aFALSEText = rStr.getToken(0, '|', nPos); + if(nPos != -1) + { + m_aContent = rStr.getToken(0, '|', nPos); + m_bValid = true; + } + } + } + else + m_aTRUEText = rStr; +} + +SwHiddenTextField::SwHiddenTextField( SwHiddenTextFieldType* pFieldType, + const OUString& rCond, + const OUString& rTrue, + const OUString& rFalse, + SwFieldTypesEnum nSub) + : SwField( pFieldType ), m_aTRUEText(rTrue), m_aFALSEText(rFalse), m_aCond(rCond), m_nSubType(nSub), + m_bIsHidden(true), m_bValid(false) +{ + m_bCanToggle = !m_aCond.isEmpty(); +} + +OUString SwHiddenTextField::ExpandImpl(SwRootFrame const*const) const +{ + // Type: !Hidden -> show always + // Hide -> evaluate condition + + if( SwFieldTypesEnum::ConditionalText == m_nSubType ) + { + if( m_bValid ) + return m_aContent; + + if( m_bCanToggle && !m_bIsHidden ) + return m_aTRUEText; + } + else if( !static_cast<SwHiddenTextFieldType*>(GetTyp())->GetHiddenFlag() || + ( m_bCanToggle && m_bIsHidden )) + return m_aTRUEText; + + return m_aFALSEText; +} + +/// get current field value and cache it +void SwHiddenTextField::Evaluate(SwDoc* pDoc) +{ + OSL_ENSURE(pDoc, "got no document"); + + if( SwFieldTypesEnum::ConditionalText == m_nSubType ) + { +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) pDoc; +#else + SwDBManager* pMgr = pDoc->GetDBManager(); +#endif + m_bValid = false; + OUString sTmpName = (m_bCanToggle && !m_bIsHidden) ? m_aTRUEText : m_aFALSEText; + + // Database expressions need to be different from normal text. Therefore, normal text is set + // in quotes. If the latter exist they will be removed. If not, check if potential DB name. + // Only if there are two or more dots and no quotes, we assume a database. + if (sTmpName.getLength()>1 && + sTmpName.startsWith("\"") && + sTmpName.endsWith("\"")) + { + m_aContent = sTmpName.copy(1, sTmpName.getLength() - 2); + m_bValid = true; + } + else if(sTmpName.indexOf('\"')<0 && + comphelper::string::getTokenCount(sTmpName, '.') > 2) + { + sTmpName = ::ReplacePoint(sTmpName); + if(sTmpName.startsWith("[") && sTmpName.endsWith("]")) + { // remove brackets + sTmpName = sTmpName.copy(1, sTmpName.getLength() - 2); + } +#if HAVE_FEATURE_DBCONNECTIVITY + if( pMgr) + { + sal_Int32 nIdx{ 0 }; + OUString sDBName( GetDBName( sTmpName, pDoc )); + OUString sDataSource(sDBName.getToken(0, DB_DELIM, nIdx)); + OUString sDataTableOrQuery(sDBName.getToken(0, DB_DELIM, nIdx)); + if( pMgr->IsInMerge() && !sDBName.isEmpty() && + pMgr->IsDataSourceOpen( sDataSource, + sDataTableOrQuery, false)) + { + double fNumber; + pMgr->GetMergeColumnCnt(GetColumnName( sTmpName ), + GetLanguage(), m_aContent, &fNumber ); + m_bValid = true; + } + else if( !sDBName.isEmpty() && !sDataSource.isEmpty() && + !sDataTableOrQuery.isEmpty() ) + m_bValid = true; + } +#endif + } + } +} + +OUString SwHiddenTextField::GetFieldName() const +{ + OUString aStr = SwFieldType::GetTypeStr(m_nSubType) + + " " + m_aCond + " " + m_aTRUEText; + + if (m_nSubType == SwFieldTypesEnum::ConditionalText) + { + aStr += " : " + m_aFALSEText; + } + return aStr; +} + +std::unique_ptr<SwField> SwHiddenTextField::Copy() const +{ + std::unique_ptr<SwHiddenTextField> pField( + new SwHiddenTextField(static_cast<SwHiddenTextFieldType*>(GetTyp()), m_aCond, + m_aTRUEText, m_aFALSEText)); + pField->m_bIsHidden = m_bIsHidden; + pField->m_bValid = m_bValid; + pField->m_aContent = m_aContent; + pField->SetFormat(GetFormat()); + pField->m_nSubType = m_nSubType; + return std::unique_ptr<SwField>(pField.release()); +} + +/// set condition +void SwHiddenTextField::SetPar1(const OUString& rStr) +{ + m_aCond = rStr; + m_bCanToggle = !m_aCond.isEmpty(); +} + +OUString SwHiddenTextField::GetPar1() const +{ + return m_aCond; +} + +/// set True/False text +void SwHiddenTextField::SetPar2(const OUString& rStr) +{ + if (m_nSubType == SwFieldTypesEnum::ConditionalText) + { + sal_Int32 nPos = rStr.indexOf('|'); + if (nPos == -1) + m_aTRUEText = rStr; + else + { + m_aTRUEText = rStr.copy(0, nPos); + m_aFALSEText = rStr.copy(nPos + 1); + } + } + else + m_aTRUEText = rStr; +} + +/// get True/False text +OUString SwHiddenTextField::GetPar2() const +{ + if(m_nSubType != SwFieldTypesEnum::ConditionalText) + { + return m_aTRUEText; + } + return m_aTRUEText + "|" + m_aFALSEText; +} + +sal_uInt16 SwHiddenTextField::GetSubType() const +{ + return static_cast<sal_uInt16>(m_nSubType); +} + +bool SwHiddenTextField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_aCond; + break; + case FIELD_PROP_PAR2: + rAny <<= m_aTRUEText; + break; + case FIELD_PROP_PAR3: + rAny <<= m_aFALSEText; + break; + case FIELD_PROP_PAR4 : + rAny <<= m_aContent; + break; + case FIELD_PROP_BOOL1: + rAny <<= m_bIsHidden; + break; + default: + assert(false); + } + return true; +} + +bool SwHiddenTextField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + { + OUString sVal; + rAny >>= sVal; + SetPar1(sVal); + } + break; + case FIELD_PROP_PAR2: + rAny >>= m_aTRUEText; + break; + case FIELD_PROP_PAR3: + rAny >>= m_aFALSEText; + break; + case FIELD_PROP_BOOL1: + m_bIsHidden = *o3tl::doAccess<bool>(rAny); + break; + case FIELD_PROP_PAR4: + rAny >>= m_aContent; + m_bValid = true; + break; + default: + assert(false); + } + return true; +} + +OUString SwHiddenTextField::GetColumnName(const OUString& rName) +{ + sal_Int32 nPos = rName.indexOf(DB_DELIM); + if( nPos>=0 ) + { + nPos = rName.indexOf(DB_DELIM, nPos + 1); + + if( nPos>=0 ) + return rName.copy(nPos + 1); + } + return rName; +} + +OUString SwHiddenTextField::GetDBName(const OUString& rName, SwDoc *pDoc) +{ + sal_Int32 nPos = rName.indexOf(DB_DELIM); + if( nPos>=0 ) + { + nPos = rName.indexOf(DB_DELIM, nPos + 1); + + if( nPos>=0 ) + return rName.copy(0, nPos); + } + + SwDBData aData = pDoc->GetDBData(); + return aData.sDataSource + OUStringChar(DB_DELIM) + aData.sCommand; +} + +// [aFieldDefinition] value sample : " IF A == B \"TrueText\" \"FalseText\"" +void SwHiddenTextField::ParseIfFieldDefinition(const OUString& aFieldDefinition, + OUString& rCondition, + OUString& rTrue, + OUString& rFalse) +{ + // get all positions inside the input string where words are started + // + // In: " IF A == B \"TrueText\" \"FalseText\"" + // 0 1 2 3 + // 01234567890 123456789 01 2345678901 2 + // + // result: + // [1, 4, 6, 9, 11, 22] + std::vector<sal_Int32> wordPosition; + { + bool quoted = false; + bool insideWord = false; + for (sal_Int32 i = 0; i < aFieldDefinition.getLength(); i++) + { + if (quoted) + { + if (aFieldDefinition[i] == '\"') + { + quoted = false; + insideWord = false; + } + } + else + { + if (aFieldDefinition[i] == ' ') + { + // word delimiter + insideWord = false; + } + else + { + if (insideWord) + { + quoted = (aFieldDefinition[i] == '\"'); + } + else + { + insideWord = true; + wordPosition.push_back(i); + quoted = (aFieldDefinition[i] == '\"'); + } + } + } + } + } + + // first word is always "IF" + // last two words are: true-case and false-case, + // everything before is treated as condition expression + // => we need at least 4 words to be inside the input string + if (wordPosition.size() < 4) + { + return; + } + + + const sal_Int32 conditionBegin = wordPosition[1]; + const sal_Int32 trueBegin = wordPosition[wordPosition.size() - 2]; + const sal_Int32 falseBegin = wordPosition[wordPosition.size() - 1]; + + const sal_Int32 conditionLength = trueBegin - conditionBegin; + const sal_Int32 trueLength = falseBegin - trueBegin; + + // Syntax + // OUString::copy( sal_Int32 beginIndex, sal_Int32 count ) + rCondition = aFieldDefinition.copy(conditionBegin, conditionLength); + rTrue = aFieldDefinition.copy(trueBegin, trueLength); + rFalse = aFieldDefinition.copy(falseBegin); + + // trim + rCondition = rCondition.trim(); + rTrue = rTrue.trim(); + rFalse = rFalse.trim(); + + // remove quotes + if (rCondition.getLength() >= 2) + { + if (rCondition[0] == '\"' && rCondition[rCondition.getLength() - 1] == '\"') + rCondition = rCondition.copy(1, rCondition.getLength() - 2); + } + if (rTrue.getLength() >= 2) + { + if (rTrue[0] == '\"' && rTrue[rTrue.getLength() - 1] == '\"') + rTrue = rTrue.copy(1, rTrue.getLength() - 2); + } + if (rFalse.getLength() >= 2) + { + if (rFalse[0] == '\"' && rFalse[rFalse.getLength() - 1] == '\"') + rFalse = rFalse.copy(1, rFalse.getLength() - 2); + } + + // Note: do not make trim once again, while this is a user defined data +} + +// field type for line height 0 + +SwHiddenParaFieldType::SwHiddenParaFieldType() + : SwFieldType( SwFieldIds::HiddenPara ) +{ +} + +std::unique_ptr<SwFieldType> SwHiddenParaFieldType::Copy() const +{ + return std::make_unique<SwHiddenParaFieldType>(); +} + +// field for line height 0 + +SwHiddenParaField::SwHiddenParaField(SwHiddenParaFieldType* pTyp, const OUString& rStr) + : SwField(pTyp), m_aCond(rStr) +{ + m_bIsHidden = false; +} + +OUString SwHiddenParaField::ExpandImpl(SwRootFrame const*const) const +{ + return OUString(); +} + +std::unique_ptr<SwField> SwHiddenParaField::Copy() const +{ + std::unique_ptr<SwHiddenParaField> pField(new SwHiddenParaField(static_cast<SwHiddenParaFieldType*>(GetTyp()), m_aCond)); + pField->m_bIsHidden = m_bIsHidden; + return std::unique_ptr<SwField>(pField.release()); +} + +bool SwHiddenParaField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch ( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_aCond; + break; + case FIELD_PROP_BOOL1: + rAny <<= m_bIsHidden; + break; + + default: + assert(false); + } + return true; +} + +bool SwHiddenParaField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch ( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= m_aCond; + break; + case FIELD_PROP_BOOL1: + m_bIsHidden = *o3tl::doAccess<bool>(rAny); + break; + + default: + assert(false); + } + return true; +} + +/// set condition +void SwHiddenParaField::SetPar1(const OUString& rStr) +{ + m_aCond = rStr; +} + +OUString SwHiddenParaField::GetPar1() const +{ + return m_aCond; +} + +// PostIt field type + +SwPostItFieldType::SwPostItFieldType(SwDoc *pDoc) + : SwFieldType( SwFieldIds::Postit ) + , mpDoc(pDoc) +{} + +std::unique_ptr<SwFieldType> SwPostItFieldType::Copy() const +{ + return std::make_unique<SwPostItFieldType>(mpDoc); +} + +// PostIt field + +sal_uInt32 SwPostItField::m_nLastPostItId = 1; + +SwPostItField::SwPostItField( SwPostItFieldType* pT, + const OUString& rAuthor, + const OUString& rText, + const OUString& rInitials, + const OUString& rName, + const DateTime& rDateTime, + const bool bResolved, + const sal_uInt32 nPostItId +) + : SwField( pT ) + , m_sText( rText ) + , m_sAuthor( rAuthor ) + , m_sInitials( rInitials ) + , m_sName( rName ) + , m_aDateTime( rDateTime ) + , m_bResolved( bResolved ) +{ + m_nPostItId = nPostItId == 0 ? m_nLastPostItId++ : nPostItId; +} + +SwPostItField::~SwPostItField() +{ + if ( m_xTextObject.is() ) + { + m_xTextObject->DisposeEditSource(); + } + + mpText.reset(); +} + +OUString SwPostItField::ExpandImpl(SwRootFrame const*const) const +{ + return OUString(); +} + +OUString SwPostItField::GetDescription() const +{ + return SwResId(STR_NOTE); +} + +void SwPostItField::SetResolved(bool bNewState) +{ + m_bResolved = bNewState; +} + +void SwPostItField::ToggleResolved() +{ + m_bResolved = !m_bResolved; +} + +bool SwPostItField::GetResolved() const +{ + return m_bResolved; +} + +std::unique_ptr<SwField> SwPostItField::Copy() const +{ + std::unique_ptr<SwPostItField> pRet(new SwPostItField( static_cast<SwPostItFieldType*>(GetTyp()), m_sAuthor, m_sText, m_sInitials, m_sName, + m_aDateTime, m_bResolved, m_nPostItId)); + if (mpText) + pRet->SetTextObject( std::make_unique<OutlinerParaObject>(*mpText) ); + + // Note: member <m_xTextObject> not copied. + + return std::unique_ptr<SwField>(pRet.release()); +} + +/// set author +void SwPostItField::SetPar1(const OUString& rStr) +{ + m_sAuthor = rStr; +} + +/// get author +OUString SwPostItField::GetPar1() const +{ + return m_sAuthor; +} + +/// set the PostIt's text +void SwPostItField::SetPar2(const OUString& rStr) +{ + m_sText = rStr; +} + +/// get the PostIt's text +OUString SwPostItField::GetPar2() const +{ + return m_sText; +} + + +void SwPostItField::SetName(const OUString& rName) +{ + m_sName = rName; +} + + +void SwPostItField::SetTextObject( std::unique_ptr<OutlinerParaObject> pText ) +{ + mpText = std::move(pText); +} + +sal_Int32 SwPostItField::GetNumberOfParagraphs() const +{ + return mpText ? mpText->Count() : 1; +} + +bool SwPostItField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_sAuthor; + break; + case FIELD_PROP_PAR2: + { + rAny <<= m_sText; + break; + } + case FIELD_PROP_PAR3: + rAny <<= m_sInitials; + break; + case FIELD_PROP_PAR4: + rAny <<= m_sName; + break; + case FIELD_PROP_BOOL1: + rAny <<= m_bResolved; + break; + case FIELD_PROP_TEXT: + { + if ( !m_xTextObject.is() ) + { + SwPostItFieldType* pGetType = static_cast<SwPostItFieldType*>(GetTyp()); + SwDoc* pDoc = pGetType->GetDoc(); + auto pObj = std::make_unique<SwTextAPIEditSource>( pDoc ); + const_cast <SwPostItField*> (this)->m_xTextObject = new SwTextAPIObject( std::move(pObj) ); + } + + if ( mpText ) + m_xTextObject->SetText( *mpText ); + else + m_xTextObject->SetString( m_sText ); + + uno::Reference < text::XText > xText( m_xTextObject.get() ); + rAny <<= xText; + break; + } + case FIELD_PROP_DATE: + { + rAny <<= m_aDateTime.GetUNODate(); + } + break; + case FIELD_PROP_DATE_TIME: + { + rAny <<= m_aDateTime.GetUNODateTime(); + } + break; + default: + assert(false); + } + return true; +} + +bool SwPostItField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= m_sAuthor; + break; + case FIELD_PROP_PAR2: + rAny >>= m_sText; + //#i100374# new string via api, delete complex text object so SwPostItNote picks up the new string + mpText.reset(); + break; + case FIELD_PROP_PAR3: + rAny >>= m_sInitials; + break; + case FIELD_PROP_PAR4: + rAny >>= m_sName; + break; + case FIELD_PROP_BOOL1: + rAny >>= m_bResolved; + break; + case FIELD_PROP_TEXT: + OSL_FAIL("Not implemented!"); + break; + case FIELD_PROP_DATE: + if( auto aSetDate = o3tl::tryAccess<util::Date>(rAny) ) + { + m_aDateTime = Date(aSetDate->Day, aSetDate->Month, aSetDate->Year); + } + break; + case FIELD_PROP_DATE_TIME: + { + util::DateTime aDateTimeValue; + if(!(rAny >>= aDateTimeValue)) + return false; + m_aDateTime = DateTime(aDateTimeValue); + } + break; + default: + assert(false); + } + return true; +} + +void SwPostItField::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPostItField")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + + SwField::dumpAsXml(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mpText")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", mpText.get()); + if (mpText) + mpText->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +// extended user information field type + +SwExtUserFieldType::SwExtUserFieldType() + : SwFieldType( SwFieldIds::ExtUser ) +{ +} + +std::unique_ptr<SwFieldType> SwExtUserFieldType::Copy() const +{ + return std::make_unique<SwExtUserFieldType>(); +} + +OUString SwExtUserFieldType::Expand(sal_uInt16 nSub ) +{ + UserOptToken nRet = static_cast<UserOptToken>(USHRT_MAX); + switch(nSub) + { + case EU_FIRSTNAME: nRet = UserOptToken::FirstName; break; + case EU_NAME: nRet = UserOptToken::LastName; break; + case EU_SHORTCUT: nRet = UserOptToken::ID; break; + + case EU_COMPANY: nRet = UserOptToken::Company; break; + case EU_STREET: nRet = UserOptToken::Street; break; + case EU_TITLE: nRet = UserOptToken::Title; break; + case EU_POSITION: nRet = UserOptToken::Position; break; + case EU_PHONE_PRIVATE: nRet = UserOptToken::TelephoneHome; break; + case EU_PHONE_COMPANY: nRet = UserOptToken::TelephoneWork; break; + case EU_FAX: nRet = UserOptToken::Fax; break; + case EU_EMAIL: nRet = UserOptToken::Email; break; + case EU_COUNTRY: nRet = UserOptToken::Country; break; + case EU_ZIP: nRet = UserOptToken::Zip; break; + case EU_CITY: nRet = UserOptToken::City; break; + case EU_STATE: nRet = UserOptToken::State; break; + case EU_FATHERSNAME: nRet = UserOptToken::FathersName; break; + case EU_APARTMENT: nRet = UserOptToken::Apartment; break; + default: OSL_ENSURE( false, "Field unknown"); + } + if( static_cast<UserOptToken>(USHRT_MAX) != nRet ) + { + SvtUserOptions& rUserOpt = SW_MOD()->GetUserOptions(); + return rUserOpt.GetToken( nRet ); + } + return OUString(); +} + +// extended user information field + +SwExtUserField::SwExtUserField(SwExtUserFieldType* pTyp, sal_uInt16 nSubTyp, sal_uInt32 nFormat) : + SwField(pTyp, nFormat), m_nType(nSubTyp) +{ + m_aContent = SwExtUserFieldType::Expand(m_nType); +} + +OUString SwExtUserField::ExpandImpl(SwRootFrame const*const) const +{ + if (!IsFixed()) + const_cast<SwExtUserField*>(this)->m_aContent = SwExtUserFieldType::Expand(m_nType); + + return m_aContent; +} + +std::unique_ptr<SwField> SwExtUserField::Copy() const +{ + std::unique_ptr<SwExtUserField> pField(new SwExtUserField(static_cast<SwExtUserFieldType*>(GetTyp()), m_nType, GetFormat())); + pField->SetExpansion(m_aContent); + + return std::unique_ptr<SwField>(pField.release()); +} + +sal_uInt16 SwExtUserField::GetSubType() const +{ + return m_nType; +} + +void SwExtUserField::SetSubType(sal_uInt16 nSub) +{ + m_nType = nSub; +} + +bool SwExtUserField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_aContent; + break; + + case FIELD_PROP_USHORT1: + { + sal_Int16 nTmp = m_nType; + rAny <<= nTmp; + } + break; + case FIELD_PROP_BOOL1: + rAny <<= IsFixed(); + break; + default: + assert(false); + } + return true; +} + +bool SwExtUserField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= m_aContent; + break; + + case FIELD_PROP_USHORT1: + { + sal_Int16 nTmp = 0; + rAny >>= nTmp; + m_nType = nTmp; + } + break; + case FIELD_PROP_BOOL1: + if( *o3tl::doAccess<bool>(rAny) ) + SetFormat(GetFormat() | AF_FIXED); + else + SetFormat(GetFormat() & ~AF_FIXED); + break; + default: + assert(false); + } + return true; +} + +// field type for relative page numbers + +SwRefPageSetFieldType::SwRefPageSetFieldType() + : SwFieldType( SwFieldIds::RefPageSet ) +{ +} + +std::unique_ptr<SwFieldType> SwRefPageSetFieldType::Copy() const +{ + return std::make_unique<SwRefPageSetFieldType>(); +} + +// overridden since there is nothing to update +void SwRefPageSetFieldType::Modify( const SfxPoolItem*, const SfxPoolItem * ) +{ +} + +// field for relative page numbers + +SwRefPageSetField::SwRefPageSetField( SwRefPageSetFieldType* pTyp, + short nOff, bool bFlag ) + : SwField( pTyp ), m_nOffset( nOff ), m_bOn( bFlag ) +{ +} + +OUString SwRefPageSetField::ExpandImpl(SwRootFrame const*const) const +{ + return OUString(); +} + +std::unique_ptr<SwField> SwRefPageSetField::Copy() const +{ + return std::make_unique<SwRefPageSetField>( static_cast<SwRefPageSetFieldType*>(GetTyp()), m_nOffset, m_bOn ); +} + +OUString SwRefPageSetField::GetPar2() const +{ + return OUString::number(GetOffset()); +} + +void SwRefPageSetField::SetPar2(const OUString& rStr) +{ + SetOffset( static_cast<short>(rStr.toInt32()) ); +} + +bool SwRefPageSetField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + rAny <<= m_bOn; + break; + case FIELD_PROP_USHORT1: + rAny <<= static_cast<sal_Int16>(m_nOffset); + break; + default: + assert(false); + } + return true; +} + +bool SwRefPageSetField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + m_bOn = *o3tl::doAccess<bool>(rAny); + break; + case FIELD_PROP_USHORT1: + rAny >>=m_nOffset; + break; + default: + assert(false); + } + return true; +} + +// relative page numbers - query field + +SwRefPageGetFieldType::SwRefPageGetFieldType( SwDoc* pDc ) + : SwFieldType( SwFieldIds::RefPageGet ), m_pDoc( pDc ), m_nNumberingType( SVX_NUM_ARABIC ) +{ +} + +std::unique_ptr<SwFieldType> SwRefPageGetFieldType::Copy() const +{ + std::unique_ptr<SwRefPageGetFieldType> pNew(new SwRefPageGetFieldType( m_pDoc )); + pNew->m_nNumberingType = m_nNumberingType; + return pNew; +} + +void SwRefPageGetFieldType::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + auto const ModifyImpl = [this](SwRootFrame const*const pLayout) + { + // first collect all SetPageRefFields + SetGetExpFields aTmpLst; + if (MakeSetList(aTmpLst, pLayout)) + { + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(auto pFormatField: vFields) + UpdateField(pFormatField->GetTextField(), aTmpLst, pLayout); + } + }; + + // update all GetReference fields + if( !pNew && !pOld && HasWriterListeners() ) + { + SwRootFrame const* pLayout(nullptr); + SwRootFrame const* pLayoutRLHidden(nullptr); + for (SwRootFrame const*const pLay : m_pDoc->GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayoutRLHidden = pLay; + } + else + { + pLayout = pLay; + } + } + ModifyImpl(pLayout); + if (pLayoutRLHidden) + { + ModifyImpl(pLayoutRLHidden); + } + } + + // forward to text fields, they "expand" the text + NotifyClients( pOld, pNew ); +} + +bool SwRefPageGetFieldType::MakeSetList(SetGetExpFields& rTmpLst, + SwRootFrame const*const pLayout) +{ + IDocumentRedlineAccess const& rIDRA(m_pDoc->getIDocumentRedlineAccess()); + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(auto pFormatField: vFields) + { + // update only the GetRef fields + const SwTextField* pTField = pFormatField->GetTextField(); + if (!pLayout || !pLayout->IsHideRedlines() || !sw::IsFieldDeletedInModel(rIDRA, *pTField)) + { + const SwTextNode& rTextNd = pTField->GetTextNode(); + + // Always the first! (in Tab-Headline, header/footer ) + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame *const pFrame = rTextNd.getLayoutFrame( + pLayout, nullptr, &tmp); + + std::unique_ptr<SetGetExpField> pNew; + + if( !pFrame || + pFrame->IsInDocBody() || + // #i31868# + // Check if pFrame is not yet connected to the layout. + !pFrame->FindPageFrame() ) + { + // create index for determination of the TextNode + SwNodeIndex aIdx( rTextNd ); + pNew.reset( new SetGetExpField( aIdx, pTField ) ); + } + else + { + // create index for determination of the TextNode + SwPosition aPos( m_pDoc->GetNodes().GetEndOfPostIts() ); + bool const bResult = GetBodyTextNode( *m_pDoc, aPos, *pFrame ); + OSL_ENSURE(bResult, "where is the Field?"); + pNew.reset( new SetGetExpField( aPos.nNode, pTField, + &aPos.nContent ) ); + } + + rTmpLst.insert( std::move(pNew) ); + } + } + return !rTmpLst.empty(); +} + +void SwRefPageGetFieldType::UpdateField( SwTextField const * pTextField, + SetGetExpFields const & rSetList, + SwRootFrame const*const pLayout) +{ + SwRefPageGetField* pGetField = const_cast<SwRefPageGetField*>(static_cast<const SwRefPageGetField*>(pTextField->GetFormatField().GetField())); + pGetField->SetText( OUString(), pLayout ); + + // then search the correct RefPageSet field + SwTextNode* pTextNode = &pTextField->GetTextNode(); + if( pTextNode->StartOfSectionIndex() > + m_pDoc->GetNodes().GetEndOfExtras().GetIndex() ) + { + SwNodeIndex aIdx( *pTextNode ); + SetGetExpField aEndField( aIdx, pTextField ); + + SetGetExpFields::const_iterator itLast = rSetList.lower_bound( &aEndField ); + + if( itLast != rSetList.begin() ) + { + --itLast; + const SwTextField* pRefTextField = (*itLast)->GetTextField(); + const SwRefPageSetField* pSetField = + static_cast<const SwRefPageSetField*>(pRefTextField->GetFormatField().GetField()); + if( pSetField->IsOn() ) + { + // determine the correct offset + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame *const pFrame = pTextNode->getLayoutFrame( + pLayout, nullptr, &tmp); + const SwContentFrame *const pRefFrame = pRefTextField->GetTextNode().getLayoutFrame( + pLayout, nullptr, &tmp); + const SwPageFrame* pPgFrame = nullptr; + short nDiff = 1; + if ( pFrame && pRefFrame ) + { + pPgFrame = pFrame->FindPageFrame(); + nDiff = pPgFrame->GetPhyPageNum() - + pRefFrame->FindPageFrame()->GetPhyPageNum() + 1; + } + + SvxNumType nTmpFormat = SVX_NUM_PAGEDESC == static_cast<SvxNumType>(pGetField->GetFormat()) + ? ( !pPgFrame + ? SVX_NUM_ARABIC + : pPgFrame->GetPageDesc()->GetNumType().GetNumberingType() ) + : static_cast<SvxNumType>(pGetField->GetFormat()); + const short nPageNum = std::max<short>(0, pSetField->GetOffset() + nDiff); + pGetField->SetText(FormatNumber(nPageNum, nTmpFormat), pLayout); + } + } + } + // start formatting + const_cast<SwFormatField&>(pTextField->GetFormatField()).ModifyNotification( nullptr, nullptr ); +} + +// queries for relative page numbering + +SwRefPageGetField::SwRefPageGetField( SwRefPageGetFieldType* pTyp, + sal_uInt32 nFormat ) + : SwField( pTyp, nFormat ) +{ +} + +void SwRefPageGetField::SetText(const OUString& rText, + SwRootFrame const*const pLayout) +{ + if (!pLayout || !pLayout->IsHideRedlines()) + { + m_sText = rText; + } + if (!pLayout || pLayout->IsHideRedlines()) + { + m_sTextRLHidden = rText; + } +} + +OUString SwRefPageGetField::ExpandImpl(SwRootFrame const*const pLayout) const +{ + return pLayout && pLayout->IsHideRedlines() ? m_sTextRLHidden : m_sText; +} + +std::unique_ptr<SwField> SwRefPageGetField::Copy() const +{ + std::unique_ptr<SwRefPageGetField> pCpy(new SwRefPageGetField( + static_cast<SwRefPageGetFieldType*>(GetTyp()), GetFormat() )); + pCpy->m_sText = m_sText; + pCpy->m_sTextRLHidden = m_sTextRLHidden; + return std::unique_ptr<SwField>(pCpy.release()); +} + +void SwRefPageGetField::ChangeExpansion(const SwFrame& rFrame, + const SwTextField* pField ) +{ + // only fields in Footer, Header, FootNote, Flys + SwRefPageGetFieldType* pGetType = static_cast<SwRefPageGetFieldType*>(GetTyp()); + SwDoc* pDoc = pGetType->GetDoc(); + if( pField->GetTextNode().StartOfSectionIndex() > + pDoc->GetNodes().GetEndOfExtras().GetIndex() ) + return; + + SwRootFrame const& rLayout(*rFrame.getRootFrame()); + OUString & rText(rLayout.IsHideRedlines() ? m_sTextRLHidden : m_sText); + rText.clear(); + + OSL_ENSURE(!rFrame.IsInDocBody(), "Flag incorrect, frame is in DocBody"); + + // collect all SetPageRefFields + SetGetExpFields aTmpLst; + if (!pGetType->MakeSetList(aTmpLst, &rLayout)) + return ; + + // create index for determination of the TextNode + SwPosition aPos( SwNodeIndex( pDoc->GetNodes() ) ); + SwTextNode* pTextNode = const_cast<SwTextNode*>(GetBodyTextNode(*pDoc, aPos, rFrame)); + + // If no layout exists, ChangeExpansion is called for header and + // footer lines via layout formatting without existing TextNode. + if(!pTextNode) + return; + + SetGetExpField aEndField( aPos.nNode, pField, &aPos.nContent ); + + SetGetExpFields::const_iterator itLast = aTmpLst.lower_bound( &aEndField ); + + if( itLast == aTmpLst.begin() ) + return; // there is no corresponding set-field in front + --itLast; + + const SwTextField* pRefTextField = (*itLast)->GetTextField(); + const SwRefPageSetField* pSetField = + static_cast<const SwRefPageSetField*>(pRefTextField->GetFormatField().GetField()); + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame *const pRefFrame = pRefTextField->GetTextNode().getLayoutFrame( + &rLayout, nullptr, &tmp); + if( pSetField->IsOn() && pRefFrame ) + { + // determine the correct offset + const SwPageFrame* pPgFrame = rFrame.FindPageFrame(); + const short nDiff = pPgFrame->GetPhyPageNum() - + pRefFrame->FindPageFrame()->GetPhyPageNum() + 1; + + SwRefPageGetField* pGetField = const_cast<SwRefPageGetField*>(static_cast<const SwRefPageGetField*>(pField->GetFormatField().GetField())); + SvxNumType nTmpFormat = SVX_NUM_PAGEDESC == pGetField->GetFormat() + ? pPgFrame->GetPageDesc()->GetNumType().GetNumberingType() + : static_cast<SvxNumType>(pGetField->GetFormat()); + const short nPageNum = std::max<short>(0, pSetField->GetOffset() + nDiff); + pGetField->SetText(FormatNumber(nPageNum, nTmpFormat), &rLayout); + } +} + +bool SwRefPageGetField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + rAny <<= static_cast<sal_Int16>(GetFormat()); + break; + case FIELD_PROP_PAR1: + rAny <<= m_sText; + break; + default: + assert(false); + } + return true; +} + +bool SwRefPageGetField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + { + sal_Int16 nSet = 0; + rAny >>= nSet; + if(nSet <= SVX_NUM_PAGEDESC ) + SetFormat(nSet); + } + break; + case FIELD_PROP_PAR1: + rAny >>= m_sText; + m_sTextRLHidden = m_sText; + break; + default: + assert(false); + } + return true; +} + +// field type to jump to and edit + +SwJumpEditFieldType::SwJumpEditFieldType( SwDoc* pD ) + : SwFieldType( SwFieldIds::JumpEdit ), m_pDoc( pD ), m_aDep( *this ) +{ +} + +std::unique_ptr<SwFieldType> SwJumpEditFieldType::Copy() const +{ + return std::make_unique<SwJumpEditFieldType>( m_pDoc ); +} + +SwCharFormat* SwJumpEditFieldType::GetCharFormat() +{ + SwCharFormat* pFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( RES_POOLCHR_JUMPEDIT ); + m_aDep.StartListening(pFormat); + return pFormat; +} + +SwJumpEditField::SwJumpEditField( SwJumpEditFieldType* pTyp, sal_uInt32 nForm, + const OUString& rText, const OUString& rHelp ) + : SwField( pTyp, nForm ), m_sText( rText ), m_sHelp( rHelp ) +{ +} + +OUString SwJumpEditField::ExpandImpl(SwRootFrame const*const) const +{ + return "<" + m_sText + ">"; +} + +std::unique_ptr<SwField> SwJumpEditField::Copy() const +{ + return std::make_unique<SwJumpEditField>( static_cast<SwJumpEditFieldType*>(GetTyp()), GetFormat(), + m_sText, m_sHelp ); +} + +/// get place holder text +OUString SwJumpEditField::GetPar1() const +{ + return m_sText; +} + +/// set place holder text +void SwJumpEditField::SetPar1(const OUString& rStr) +{ + m_sText = rStr; +} + +/// get hint text +OUString SwJumpEditField::GetPar2() const +{ + return m_sHelp; +} + +/// set hint text +void SwJumpEditField::SetPar2(const OUString& rStr) +{ + m_sHelp = rStr; +} + +bool SwJumpEditField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + { + sal_Int16 nRet; + switch( GetFormat() ) + { + case JE_FMT_TABLE: nRet = text::PlaceholderType::TABLE; break; + case JE_FMT_FRAME: nRet = text::PlaceholderType::TEXTFRAME; break; + case JE_FMT_GRAPHIC:nRet = text::PlaceholderType::GRAPHIC; break; + case JE_FMT_OLE: nRet = text::PlaceholderType::OBJECT; break; + default: + nRet = text::PlaceholderType::TEXT; break; + } + rAny <<= nRet; + } + break; + case FIELD_PROP_PAR1 : + rAny <<= m_sHelp; + break; + case FIELD_PROP_PAR2 : + rAny <<= m_sText; + break; + default: + assert(false); + } + return true; +} + +bool SwJumpEditField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + { + //JP 24.10.2001: int32 because in UnoField.cxx a putvalue is + // called with a int32 value! But normally we need + // here only a int16 + sal_Int32 nSet = 0; + rAny >>= nSet; + switch( nSet ) + { + case text::PlaceholderType::TEXT : SetFormat(JE_FMT_TEXT); break; + case text::PlaceholderType::TABLE : SetFormat(JE_FMT_TABLE); break; + case text::PlaceholderType::TEXTFRAME: SetFormat(JE_FMT_FRAME); break; + case text::PlaceholderType::GRAPHIC : SetFormat(JE_FMT_GRAPHIC); break; + case text::PlaceholderType::OBJECT : SetFormat(JE_FMT_OLE); break; + } + } + break; + case FIELD_PROP_PAR1 : + rAny >>= m_sHelp; + break; + case FIELD_PROP_PAR2 : + rAny >>= m_sText; + break; + default: + assert(false); + } + return true; +} + +// combined character field type + +SwCombinedCharFieldType::SwCombinedCharFieldType() + : SwFieldType( SwFieldIds::CombinedChars ) +{ +} + +std::unique_ptr<SwFieldType> SwCombinedCharFieldType::Copy() const +{ + return std::make_unique<SwCombinedCharFieldType>(); +} + +// combined character field + +SwCombinedCharField::SwCombinedCharField( SwCombinedCharFieldType* pFTyp, + const OUString& rChars ) + : SwField( pFTyp, 0 ), + m_sCharacters( rChars.copy( 0, std::min<sal_Int32>(rChars.getLength(), MAX_COMBINED_CHARACTERS) )) +{ +} + +OUString SwCombinedCharField::ExpandImpl(SwRootFrame const*const) const +{ + return m_sCharacters; +} + +std::unique_ptr<SwField> SwCombinedCharField::Copy() const +{ + return std::make_unique<SwCombinedCharField>( static_cast<SwCombinedCharFieldType*>(GetTyp()), + m_sCharacters ); +} + +OUString SwCombinedCharField::GetPar1() const +{ + return m_sCharacters; +} + +void SwCombinedCharField::SetPar1(const OUString& rStr) +{ + m_sCharacters = rStr.copy(0, std::min<sal_Int32>(rStr.getLength(), MAX_COMBINED_CHARACTERS)); +} + +bool SwCombinedCharField::QueryValue( uno::Any& rAny, + sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_sCharacters; + break; + default: + assert(false); + } + return true; +} + +bool SwCombinedCharField::PutValue( const uno::Any& rAny, + sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + { + OUString sTmp; + rAny >>= sTmp; + SetPar1(sTmp); + } + break; + default: + assert(false); + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/expfld.cxx b/sw/source/core/fields/expfld.cxx new file mode 100644 index 000000000..2872101dc --- /dev/null +++ b/sw/source/core/fields/expfld.cxx @@ -0,0 +1,1432 @@ +/* -*- 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 <sal/config.h> + +#include <limits> + +#include <UndoTable.hxx> +#include <hintids.hxx> +#include <o3tl/any.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/charclass.hxx> +#include <editeng/langitem.hxx> +#include <editeng/fontitem.hxx> +#include <com/sun/star/text/SetVariableType.hpp> +#include <unofield.hxx> +#include <frmfmt.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <fmtanchr.hxx> +#include <txtftn.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <layfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <tabfrm.hxx> +#include <flyfrm.hxx> +#include <ftnfrm.hxx> +#include <rowfrm.hxx> +#include <expfld.hxx> +#include <usrfld.hxx> +#include <ndtxt.hxx> +#include <calc.hxx> +#include <pam.hxx> +#include <docfld.hxx> +#include <swtable.hxx> +#include <breakit.hxx> +#include <SwStyleNameMapper.hxx> +#include <unofldmid.h> +#include <numrule.hxx> +#include <calbck.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::text; + +static sal_Int16 lcl_SubTypeToAPI(sal_uInt16 nSubType) +{ + sal_Int16 nRet = 0; + switch(nSubType) + { + case nsSwGetSetExpType::GSE_EXPR: + nRet = SetVariableType::VAR; // 0 + break; + case nsSwGetSetExpType::GSE_SEQ: + nRet = SetVariableType::SEQUENCE; // 1 + break; + case nsSwGetSetExpType::GSE_FORMULA: + nRet = SetVariableType::FORMULA; // 2 + break; + case nsSwGetSetExpType::GSE_STRING: + nRet = SetVariableType::STRING; // 3 + break; + } + return nRet; +} + +static sal_Int32 lcl_APIToSubType(const uno::Any& rAny) +{ + sal_Int16 nVal = 0; + rAny >>= nVal; + sal_Int32 nSet = 0; + switch(nVal) + { + case SetVariableType::VAR: nSet = nsSwGetSetExpType::GSE_EXPR; break; + case SetVariableType::SEQUENCE: nSet = nsSwGetSetExpType::GSE_SEQ; break; + case SetVariableType::FORMULA: nSet = nsSwGetSetExpType::GSE_FORMULA; break; + case SetVariableType::STRING: nSet = nsSwGetSetExpType::GSE_STRING; break; + default: + OSL_FAIL("wrong value"); + nSet = -1; + } + return nSet; +} + +OUString ReplacePoint( const OUString& rTmpName, bool bWithCommandType ) +{ + // replace first and last (if bWithCommandType: last two) dot + // since table names may contain dots + + sal_Int32 nIndex = rTmpName.lastIndexOf('.'); + if (nIndex<0) + { + return rTmpName; + } + + OUString sRes = rTmpName.replaceAt(nIndex, 1, OUString(DB_DELIM)); + + if (bWithCommandType) + { + nIndex = sRes.lastIndexOf('.', nIndex); + if (nIndex<0) + { + return sRes; + } + sRes = sRes.replaceAt(nIndex, 1, OUString(DB_DELIM)); + } + + nIndex = sRes.indexOf('.'); + if (nIndex>=0) + { + sRes = sRes.replaceAt(nIndex, 1, OUString(DB_DELIM)); + } + return sRes; +} + +static SwTextNode* GetFirstTextNode( const SwDoc& rDoc, SwPosition& rPos, + const SwContentFrame *pCFrame, Point &rPt ) +{ + SwTextNode* pTextNode = nullptr; + if ( !pCFrame ) + { + const SwNodes& rNodes = rDoc.GetNodes(); + rPos.nNode = *rNodes.GetEndOfContent().StartOfSectionNode(); + SwContentNode* pCNd; + while( nullptr != (pCNd = rNodes.GoNext( &rPos.nNode ) ) && + nullptr == ( pTextNode = pCNd->GetTextNode() ) ) + ; + OSL_ENSURE( pTextNode, "Where is the 1. TextNode?" ); + rPos.nContent.Assign( pTextNode, 0 ); + } + else if ( !pCFrame->isFrameAreaDefinitionValid() ) + { + assert(pCFrame->IsTextFrame()); + rPos = static_cast<SwTextFrame const*>(pCFrame)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + pCFrame->GetModelPositionForViewPoint( &rPos, rPt ); + pTextNode = rPos.nNode.GetNode().GetTextNode(); + } + return pTextNode; +} + +const SwTextNode* GetBodyTextNode( const SwDoc& rDoc, SwPosition& rPos, + const SwFrame& rFrame ) +{ + const SwLayoutFrame* pLayout = rFrame.GetUpper(); + const SwTextNode* pTextNode = nullptr; + + while( pLayout ) + { + if( pLayout->IsFlyFrame() ) + { + // get the FlyFormat + const SwFrameFormat* pFlyFormat = static_cast<const SwFlyFrame*>(pLayout)->GetFormat(); + OSL_ENSURE( pFlyFormat, "Could not find FlyFormat, where is the field?" ); + + const SwFormatAnchor &rAnchor = pFlyFormat->GetAnchor(); + + if( RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId() ) + { + // the fly needs to be attached somewhere, so ask it + pLayout = static_cast<const SwLayoutFrame*>(static_cast<const SwFlyFrame*>(pLayout)->GetAnchorFrame()); + continue; + } + else if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId())) + { + OSL_ENSURE( rAnchor.GetContentAnchor(), "no valid position" ); + rPos = *rAnchor.GetContentAnchor(); + pTextNode = rPos.nNode.GetNode().GetTextNode(); + if ( RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId() ) + { + const_cast<SwTextNode*>(pTextNode)->MakeStartIndex( + &rPos.nContent ); + } + + // do not break yet, might be as well in Header/Footer/Footnote/Fly + pLayout = static_cast<const SwFlyFrame*>(pLayout)->GetAnchorFrame() + ? static_cast<const SwFlyFrame*>(pLayout)->GetAnchorFrame()->GetUpper() : nullptr; + continue; + } + else + { + pLayout->FindPageFrame()->GetContentPosition( + pLayout->getFrameArea().Pos(), rPos ); + pTextNode = rPos.nNode.GetNode().GetTextNode(); + } + } + else if( pLayout->IsFootnoteFrame() ) + { + // get the anchor's node + const SwTextFootnote* pFootnote = static_cast<const SwFootnoteFrame*>(pLayout)->GetAttr(); + pTextNode = &pFootnote->GetTextNode(); + rPos.nNode = *pTextNode; + rPos.nContent = pFootnote->GetStart(); + } + else if( pLayout->IsHeaderFrame() || pLayout->IsFooterFrame() ) + { + const SwContentFrame* pContentFrame; + const SwPageFrame* pPgFrame = pLayout->FindPageFrame(); + if( pLayout->IsHeaderFrame() ) + { + const SwTabFrame *pTab; + if( nullptr != ( pContentFrame = pPgFrame->FindFirstBodyContent()) && + nullptr != (pTab = pContentFrame->FindTabFrame()) && pTab->IsFollow() && + pTab->GetTable()->GetRowsToRepeat() > 0 && + pTab->IsInHeadline( *pContentFrame ) ) + { + // take the next line + const SwLayoutFrame* pRow = pTab->GetFirstNonHeadlineRow(); + pContentFrame = pRow->ContainsContent(); + } + } + else + pContentFrame = pPgFrame->FindLastBodyContent(); + + if( pContentFrame ) + { + assert(pContentFrame->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pContentFrame)); + rPos = pFrame->MapViewToModelPos(TextFrameIndex(pFrame->GetText().getLength())); + pTextNode = rPos.nNode.GetNode().GetTextNode(); + assert(pTextNode); + } + else + { + Point aPt( pLayout->getFrameArea().Pos() ); + aPt.AdjustY( 1 ); // get out of the header + pContentFrame = pPgFrame->GetContentPos( aPt, false, true ); + pTextNode = GetFirstTextNode( rDoc, rPos, pContentFrame, aPt ); + } + } + else + { + pLayout = pLayout->GetUpper(); + continue; + } + break; // found, so finish loop + } + return pTextNode; +} + +SwGetExpFieldType::SwGetExpFieldType(SwDoc* pDc) + : SwValueFieldType( pDc, SwFieldIds::GetExp ) +{ +} + +std::unique_ptr<SwFieldType> SwGetExpFieldType::Copy() const +{ + return std::make_unique<SwGetExpFieldType>(GetDoc()); +} + +void SwGetExpFieldType::Modify( const SfxPoolItem*, const SfxPoolItem* pNew ) +{ + if( pNew && RES_DOCPOS_UPDATE == pNew->Which() ) + NotifyClients( nullptr, pNew ); + // do not expand anything else +} + +SwGetExpField::SwGetExpField(SwGetExpFieldType* pTyp, const OUString& rFormel, + sal_uInt16 nSub, sal_uLong nFormat) + : SwFormulaField( pTyp, nFormat, 0.0 ) + , m_fValueRLHidden(0.0) + , + m_bIsInBodyText( true ), + m_nSubType(nSub), + m_bLateInitialization( false ) +{ + SetFormula( rFormel ); +} + +void SwGetExpField::ChgExpStr(const OUString& rExpand, SwRootFrame const*const pLayout) +{ + if (!pLayout || pLayout->IsHideRedlines()) + { + m_sExpandRLHidden = rExpand; + } + if (!pLayout || !pLayout->IsHideRedlines()) + { + m_sExpand = rExpand; + } +} + +OUString SwGetExpField::ExpandImpl(SwRootFrame const*const pLayout) const +{ + if(m_nSubType & nsSwExtendedSubType::SUB_CMD) + return GetFormula(); + + return (pLayout && pLayout->IsHideRedlines()) ? m_sExpandRLHidden : m_sExpand; +} + +OUString SwGetExpField::GetFieldName() const +{ + const SwFieldTypesEnum nType = + (nsSwGetSetExpType::GSE_FORMULA & m_nSubType) + ? SwFieldTypesEnum::Formel + : SwFieldTypesEnum::Get; + + return SwFieldType::GetTypeStr(nType) + " " + GetFormula(); +} + +std::unique_ptr<SwField> SwGetExpField::Copy() const +{ + std::unique_ptr<SwGetExpField> pTmp(new SwGetExpField(static_cast<SwGetExpFieldType*>(GetTyp()), + GetFormula(), m_nSubType, GetFormat())); + pTmp->SetLanguage(GetLanguage()); + pTmp->m_fValueRLHidden = m_fValueRLHidden; + pTmp->SwValueField::SetValue(GetValue()); + pTmp->m_sExpand = m_sExpand; + pTmp->m_sExpandRLHidden = m_sExpandRLHidden; + pTmp->m_bIsInBodyText = m_bIsInBodyText; + pTmp->SetAutomaticLanguage(IsAutomaticLanguage()); + if( m_bLateInitialization ) + pTmp->SetLateInitialization(); + + return std::unique_ptr<SwField>(pTmp.release()); +} + +void SwGetExpField::ChangeExpansion( const SwFrame& rFrame, const SwTextField& rField ) +{ + if( m_bIsInBodyText ) // only fields in Footer, Header, FootNote, Flys + return; + + OSL_ENSURE( !rFrame.IsInDocBody(), "Flag incorrect, frame is in DocBody" ); + + // determine document (or is there an easier way?) + const SwTextNode* pTextNode = &rField.GetTextNode(); + SwDoc& rDoc = *const_cast<SwDoc*>(pTextNode->GetDoc()); + + // create index for determination of the TextNode + SwPosition aPos( SwNodeIndex( rDoc.GetNodes() ) ); + pTextNode = GetBodyTextNode( rDoc, aPos, rFrame ); + + // If no layout exists, ChangeExpansion is called for header and + // footer lines via layout formatting without existing TextNode. + if(!pTextNode) + return; + // #i82544# + if( m_bLateInitialization ) + { + SwFieldType* pSetExpField = rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, GetFormula(), false); + if( pSetExpField ) + { + m_bLateInitialization = false; + if( !(GetSubType() & nsSwGetSetExpType::GSE_STRING) && + static_cast< SwSetExpFieldType* >(pSetExpField)->GetType() == nsSwGetSetExpType::GSE_STRING ) + SetSubType( nsSwGetSetExpType::GSE_STRING ); + } + } + + SwRootFrame const& rLayout(*rFrame.getRootFrame()); + OUString & rExpand(rLayout.IsHideRedlines() ? m_sExpandRLHidden : m_sExpand); + SetGetExpField aEndField( aPos.nNode, &rField, &aPos.nContent ); + if(GetSubType() & nsSwGetSetExpType::GSE_STRING) + { + SwHashTable<HashStr> aHashTable(0); + rDoc.getIDocumentFieldsAccess().FieldsToExpand(aHashTable, aEndField, rLayout); + rExpand = LookString( aHashTable, GetFormula() ); + } + else + { + // fill calculator with values + SwCalc aCalc( rDoc ); + rDoc.getIDocumentFieldsAccess().FieldsToCalc(aCalc, aEndField, &rLayout); + + // calculate value + SetValue(aCalc.Calculate(GetFormula()).GetDouble(), &rLayout); + + // analyse based on format + rExpand = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue( + GetValue(&rLayout), GetFormat(), GetLanguage()); + } +} + +OUString SwGetExpField::GetPar2() const +{ + return GetFormula(); +} + +void SwGetExpField::SetPar2(const OUString& rStr) +{ + SetFormula(rStr); +} + +sal_uInt16 SwGetExpField::GetSubType() const +{ + return m_nSubType; +} + +void SwGetExpField::SetSubType(sal_uInt16 nType) +{ + m_nSubType = nType; +} + +void SwGetExpField::SetLanguage(LanguageType nLng) +{ + if (m_nSubType & nsSwExtendedSubType::SUB_CMD) + SwField::SetLanguage(nLng); + else + SwValueField::SetLanguage(nLng); +} + +bool SwGetExpField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_DOUBLE: + rAny <<= GetValue(); + break; + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int32>(GetFormat()); + break; + case FIELD_PROP_USHORT1: + rAny <<= static_cast<sal_Int16>(m_nSubType); + break; + case FIELD_PROP_PAR1: + rAny <<= GetFormula(); + break; + case FIELD_PROP_SUBTYPE: + { + sal_Int16 nRet = lcl_SubTypeToAPI(GetSubType() & 0xff); + rAny <<= nRet; + } + break; + case FIELD_PROP_BOOL2: + rAny <<= 0 != (m_nSubType & nsSwExtendedSubType::SUB_CMD); + break; + case FIELD_PROP_PAR4: + rAny <<= m_sExpand; + break; + default: + return SwField::QueryValue(rAny, nWhichId); + } + return true; +} + +bool SwGetExpField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + sal_Int32 nTmp = 0; + switch( nWhichId ) + { + case FIELD_PROP_DOUBLE: + SwValueField::SetValue(*o3tl::doAccess<double>(rAny)); + m_fValueRLHidden = *o3tl::doAccess<double>(rAny); + break; + case FIELD_PROP_FORMAT: + rAny >>= nTmp; + SetFormat(nTmp); + break; + case FIELD_PROP_USHORT1: + rAny >>= nTmp; + m_nSubType = static_cast<sal_uInt16>(nTmp); + break; + case FIELD_PROP_PAR1: + { + OUString sTmp; + rAny >>= sTmp; + SetFormula(sTmp); + break; + } + case FIELD_PROP_SUBTYPE: + nTmp = lcl_APIToSubType(rAny); + if( nTmp >=0 ) + SetSubType( static_cast<sal_uInt16>((GetSubType() & 0xff00) | nTmp)); + break; + case FIELD_PROP_BOOL2: + if(*o3tl::doAccess<bool>(rAny)) + m_nSubType |= nsSwExtendedSubType::SUB_CMD; + else + m_nSubType &= (~nsSwExtendedSubType::SUB_CMD); + break; + case FIELD_PROP_PAR4: + { + OUString sTmp; + rAny >>= sTmp; + ChgExpStr(sTmp, nullptr); + break; + } + default: + return SwField::PutValue(rAny, nWhichId); + } + return true; +} + +SwSetExpFieldType::SwSetExpFieldType( SwDoc* pDc, const OUString& rName, sal_uInt16 nTyp ) + : SwValueFieldType( pDc, SwFieldIds::SetExp ), + m_sName( rName ), + m_sDelim( "." ), + m_nType(nTyp), m_nLevel( UCHAR_MAX ), + m_bDeleted( false ) +{ + if( ( nsSwGetSetExpType::GSE_SEQ | nsSwGetSetExpType::GSE_STRING ) & m_nType ) + EnableFormat(false); // do not use Numberformatter +} + +std::unique_ptr<SwFieldType> SwSetExpFieldType::Copy() const +{ + std::unique_ptr<SwSetExpFieldType> pNew(new SwSetExpFieldType(GetDoc(), m_sName, m_nType)); + pNew->m_bDeleted = m_bDeleted; + pNew->m_sDelim = m_sDelim; + pNew->m_nLevel = m_nLevel; + + return pNew; +} + +OUString SwSetExpFieldType::GetName() const +{ + return m_sName; +} + +const OUString& SwSetExpField::GetExpStr(SwRootFrame const*const pLayout) const +{ + return (pLayout && pLayout->IsHideRedlines()) ? msExpandRLHidden : msExpand; +} + +void SwSetExpField::ChgExpStr(const OUString& rExpand, SwRootFrame const*const pLayout) +{ + if (!pLayout || pLayout->IsHideRedlines()) + { + msExpandRLHidden = rExpand; + } + if (!pLayout || !pLayout->IsHideRedlines()) + { + msExpand = rExpand; + } +} + +void SwSetExpFieldType::Modify( const SfxPoolItem*, const SfxPoolItem* ) +{ + // do not expand further +} + +void SwSetExpFieldType::SetSeqFormat(sal_uLong nFormat) +{ + std::vector<SwFormatField*> vFields; + GatherFields(vFields, false); + for(auto pFormatField: vFields) + pFormatField->GetField()->ChangeFormat(nFormat); +} + +sal_uLong SwSetExpFieldType::GetSeqFormat() const +{ + if( !HasWriterListeners() ) + return SVX_NUM_ARABIC; + + std::vector<SwFormatField*> vFields; + GatherFields(vFields, false); + return vFields.front()->GetField()->GetFormat(); +} + +void SwSetExpFieldType::SetSeqRefNo( SwSetExpField& rField ) +{ + if( !HasWriterListeners() || !(nsSwGetSetExpType::GSE_SEQ & m_nType) ) + return; + + std::vector<sal_uInt16> aArr; + + // check if number is already used and if a new one needs to be created + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(SwFormatField* pF: vFields) + if(pF->GetField() != &rField) + InsertSort(aArr, static_cast<SwSetExpField*>(pF->GetField())->GetSeqNumber()); + + // check first if number already exists + sal_uInt16 nNum = rField.GetSeqNumber(); + if( USHRT_MAX != nNum ) + { + std::vector<sal_uInt16>::size_type n {0}; + + for( n = 0; n < aArr.size(); ++n ) + if( aArr[ n ] >= nNum ) + break; + + if( n == aArr.size() || aArr[ n ] > nNum ) + return; // no -> use it + } + + // flagged all numbers, so determine the right number + std::vector<sal_uInt16>::size_type n = aArr.size(); + OSL_ENSURE( n <= std::numeric_limits<sal_uInt16>::max(), "Array is too big for using a sal_uInt16 index" ); + + if ( n > 0 && aArr[ n-1 ] != n-1 ) + { + for( n = 0; n < aArr.size(); ++n ) + if( n != aArr[ n ] ) + break; + } + + rField.SetSeqNumber( n ); +} + +size_t SwSetExpFieldType::GetSeqFieldList(SwSeqFieldList& rList, + SwRootFrame const*const pLayout) +{ + rList.Clear(); + + IDocumentRedlineAccess const& rIDRA(GetDoc()->getIDocumentRedlineAccess()); + + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(SwFormatField* pF: vFields) + { + const SwTextNode* pNd; + if( nullptr != ( pNd = pF->GetTextField()->GetpTextNode() ) + && (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsFieldDeletedInModel(rIDRA, *pF->GetTextField()))) + { + SeqFieldLstElem aNew( + pNd->GetExpandText(pLayout), + static_cast<SwSetExpField*>(pF->GetField())->GetSeqNumber() ); + rList.InsertSort( aNew ); + } + } + return rList.Count(); +} + +void SwSetExpFieldType::SetChapter(SwSetExpField& rField, const SwNode& rNd, + SwRootFrame const*const pLayout) +{ + const SwTextNode* pTextNd = rNd.FindOutlineNodeOfLevel(m_nLevel, pLayout); + if( pTextNd ) + { + SwNumRule * pRule = pTextNd->GetNumRule(); + + if (pRule) + { + // --> OD 2005-11-02 #i51089 - TUNING# + if (SwNodeNum const*const pNum = pTextNd->GetNum(pLayout)) + { + // only get the number, without pre-/post-fixstrings + OUString const sNumber(pRule->MakeNumString(*pNum, false)); + + if( !sNumber.isEmpty() ) + rField.ChgExpStr(sNumber + m_sDelim + rField.GetExpStr(pLayout), pLayout); + } + else + { + OSL_ENSURE(pTextNd->GetNum(nullptr), "<SwSetExpFieldType::SetChapter(..)> - text node with numbering rule, but without number. This is a serious defect"); + } + } + } +} + +void SwSetExpFieldType::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_SUBTYPE: + { + sal_Int16 nRet = lcl_SubTypeToAPI(GetType()); + rAny <<= nRet; + } + break; + case FIELD_PROP_PAR2: + rAny <<= GetDelimiter(); + break; + case FIELD_PROP_SHORT1: + { + sal_Int8 nRet = m_nLevel < MAXLEVEL? m_nLevel : -1; + rAny <<= nRet; + } + break; + default: + assert(false); + } +} + +void SwSetExpFieldType::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_SUBTYPE: + { + sal_Int32 nSet = lcl_APIToSubType(rAny); + if(nSet >=0) + SetType(static_cast<sal_uInt16>(nSet)); + } + break; + case FIELD_PROP_PAR2: + { + OUString sTmp; + rAny >>= sTmp; + if( !sTmp.isEmpty() ) + SetDelimiter( sTmp ); + else + SetDelimiter( " " ); + } + break; + case FIELD_PROP_SHORT1: + { + sal_Int8 nLvl = 0; + rAny >>= nLvl; + if(nLvl < 0 || nLvl >= MAXLEVEL) + SetOutlineLvl(UCHAR_MAX); + else + SetOutlineLvl(nLvl); + } + break; + default: + assert(false); + } +} + +bool SwSeqFieldList::InsertSort( SeqFieldLstElem aNew ) +{ + OUStringBuffer aBuf(aNew.sDlgEntry); + const sal_Int32 nLen = aBuf.getLength(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + if (aBuf[i]<' ') + { + aBuf[i]=' '; + } + } + aNew.sDlgEntry = aBuf.makeStringAndClear(); + + size_t nPos = 0; + bool bRet = SeekEntry( aNew, &nPos ); + if( !bRet ) + maData.insert( maData.begin() + nPos, aNew ); + return bRet; +} + +bool SwSeqFieldList::SeekEntry( const SeqFieldLstElem& rNew, size_t* pP ) const +{ + size_t nO = maData.size(); + size_t nU = 0; + if( nO > 0 ) + { + CollatorWrapper & rCaseColl = ::GetAppCaseCollator(), + & rColl = ::GetAppCollator(); + const CharClass& rCC = GetAppCharClass(); + + //#59900# Sorting should sort number correctly (e.g. "10" after "9" not after "1") + const OUString rTmp2 = rNew.sDlgEntry; + sal_Int32 nFndPos2 = 0; + const OUString sNum2( rTmp2.getToken( 0, ' ', nFndPos2 )); + bool bIsNum2IsNumeric = CharClass::isAsciiNumeric( sNum2 ); + sal_Int32 nNum2 = bIsNum2IsNumeric ? sNum2.toInt32() : 0; + + nO--; + while( nU <= nO ) + { + const size_t nM = nU + ( nO - nU ) / 2; + + //#59900# Sorting should sort number correctly (e.g. "10" after "9" not after "1") + const OUString rTmp1 = maData[nM].sDlgEntry; + sal_Int32 nFndPos1 = 0; + const OUString sNum1( rTmp1.getToken( 0, ' ', nFndPos1 )); + sal_Int32 nCmp; + + if( bIsNum2IsNumeric && rCC.isNumeric( sNum1 ) ) + { + sal_Int32 nNum1 = sNum1.toInt32(); + nCmp = nNum2 - nNum1; + if( 0 == nCmp ) + { + OUString aTmp1 = nFndPos1 != -1 ? rTmp1.copy(nFndPos1) : OUString(); + OUString aTmp2 = nFndPos2 != -1 ? rTmp2.copy(nFndPos2) : OUString(); + nCmp = rCaseColl.compareString(aTmp2, aTmp1); + } + } + else + nCmp = rColl.compareString( rTmp2, rTmp1 ); + + if( 0 == nCmp ) + { + if( pP ) *pP = nM; + return true; + } + else if( 0 < nCmp ) + nU = nM + 1; + else if( nM == 0 ) + break; + else + nO = nM - 1; + } + } + if( pP ) *pP = nU; + return false; +} + +SwSetExpField::SwSetExpField(SwSetExpFieldType* pTyp, const OUString& rFormel, + sal_uLong nFormat) + : SwFormulaField( pTyp, nFormat, 0.0 ) + , m_fValueRLHidden(0.0) + , mnSeqNo( USHRT_MAX ) + , mnSubType(0) + , mpFormatField(nullptr) +{ + SetFormula(rFormel); + // ignore SubType + mbInput = false; + if( IsSequenceField() ) + { + SwValueField::SetValue(1.0); + m_fValueRLHidden = 1.0; + if( rFormel.isEmpty() ) + { + SetFormula(pTyp->GetName() + "+1"); + } + } +} + +void SwSetExpField::SetFormatField(SwFormatField & rFormatField) +{ + mpFormatField = &rFormatField; +} + +OUString SwSetExpField::ExpandImpl(SwRootFrame const*const pLayout) const +{ + if (mnSubType & nsSwExtendedSubType::SUB_CMD) + { // we need the CommandString + return GetTyp()->GetName() + " = " + GetFormula(); + } + if(!(mnSubType & nsSwExtendedSubType::SUB_INVISIBLE)) + { // value is visible + return (pLayout && pLayout->IsHideRedlines()) ? msExpandRLHidden : msExpand; + } + return OUString(); +} + +/// @return the field name +OUString SwSetExpField::GetFieldName() const +{ + SwFieldTypesEnum const nStrType( (IsSequenceField()) + ? SwFieldTypesEnum::Sequence + : mbInput + ? SwFieldTypesEnum::SetInput + : SwFieldTypesEnum::Set ); + + OUString aStr( + SwFieldType::GetTypeStr( nStrType ) + + " " + + GetTyp()->GetName() ); + + // Sequence: without formula + if (SwFieldTypesEnum::Sequence != nStrType) + { + aStr += " = " + GetFormula(); + } + return aStr; +} + +std::unique_ptr<SwField> SwSetExpField::Copy() const +{ + std::unique_ptr<SwSetExpField> pTmp(new SwSetExpField(static_cast<SwSetExpFieldType*>(GetTyp()), + GetFormula(), GetFormat())); + pTmp->SwValueField::SetValue(GetValue()); + pTmp->m_fValueRLHidden = m_fValueRLHidden; + pTmp->msExpand = msExpand; + pTmp->msExpandRLHidden = msExpandRLHidden; + pTmp->SetAutomaticLanguage(IsAutomaticLanguage()); + pTmp->SetLanguage(GetLanguage()); + pTmp->maPText = maPText; + pTmp->mbInput = mbInput; + pTmp->mnSeqNo = mnSeqNo; + pTmp->SetSubType(GetSubType()); + + return std::unique_ptr<SwField>(pTmp.release()); +} + +void SwSetExpField::SetSubType(sal_uInt16 nSub) +{ + static_cast<SwSetExpFieldType*>(GetTyp())->SetType(nSub & 0xff); + mnSubType = nSub & 0xff00; + + OSL_ENSURE( (nSub & 0xff) != 3, "SubType is illegal!" ); +} + +sal_uInt16 SwSetExpField::GetSubType() const +{ + return static_cast<SwSetExpFieldType*>(GetTyp())->GetType() | mnSubType; +} + +void SwSetExpField::SetValue( const double& rAny ) +{ + SwValueField::SetValue(rAny); + + if( IsSequenceField() ) + msExpand = FormatNumber( GetValue(), static_cast<SvxNumType>(GetFormat()), GetLanguage() ); + else + msExpand = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue( rAny, + GetFormat(), GetLanguage()); +} + +void SwSetExpField::SetValue(const double& rValue, SwRootFrame const*const pLayout) +{ + if (!pLayout || !pLayout->IsHideRedlines()) + { + SetValue(rValue); + } + if (!pLayout || pLayout->IsHideRedlines()) + { + m_fValueRLHidden = rValue; + if (IsSequenceField()) + { + msExpandRLHidden = FormatNumber(rValue, static_cast<SvxNumType>(GetFormat()), GetLanguage()); + } + else + { + msExpandRLHidden = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue( + rValue, GetFormat(), GetLanguage()); + } + } +} + +double SwSetExpField::GetValue(SwRootFrame const* pLayout) const +{ + return (pLayout && pLayout->IsHideRedlines()) ? m_fValueRLHidden : GetValue(); +} + +void SwGetExpField::SetValue( const double& rAny ) +{ + SwValueField::SetValue(rAny); + m_sExpand = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue( rAny, GetFormat(), + GetLanguage()); +} + +void SwGetExpField::SetValue(const double& rValue, SwRootFrame const*const pLayout) +{ + if (!pLayout || !pLayout->IsHideRedlines()) + { + SetValue(rValue); + } + if (!pLayout || pLayout->IsHideRedlines()) + { + m_fValueRLHidden = rValue; + m_sExpandRLHidden = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue( + rValue, GetFormat(), GetLanguage()); + } +} + +double SwGetExpField::GetValue(SwRootFrame const* pLayout) const +{ + return (pLayout && pLayout->IsHideRedlines()) ? m_fValueRLHidden : GetValue(); +} + +/** Find the index of the reference text following the current field + * + * @param rFormat + * @param rDoc + * @param nHint search starting position after the current field (or 0 if default) + * @return + */ +sal_Int32 SwGetExpField::GetReferenceTextPos( const SwFormatField& rFormat, SwDoc& rDoc, sal_Int32 nHint) +{ + + const SwTextField* pTextField = rFormat.GetTextField(); + const SwTextNode& rTextNode = pTextField->GetTextNode(); + + sal_Int32 nRet = nHint ? nHint : pTextField->GetStart() + 1; + OUString sNodeText = rTextNode.GetText(); + + if(nRet<sNodeText.getLength()) + { + sNodeText = sNodeText.copy(nRet); + + // now check if sNodeText starts with a non-alphanumeric character plus blanks + sal_uInt16 nSrcpt = g_pBreakIt->GetRealScriptOfText( sNodeText, 0 ); + + static const sal_uInt16 nIds[] = + { + RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE, + RES_CHRATR_FONT, RES_CHRATR_FONT, + RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CJK_LANGUAGE, + RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONT, + RES_CHRATR_CTL_LANGUAGE, RES_CHRATR_CTL_LANGUAGE, + RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONT, + 0, 0 + }; + SwAttrSet aSet(rDoc.GetAttrPool(), nIds); + rTextNode.GetParaAttr(aSet, nRet, nRet+1); + + if( RTL_TEXTENCODING_SYMBOL != static_cast<const SvxFontItem&>(aSet.Get( + GetWhichOfScript( RES_CHRATR_FONT, nSrcpt )) ).GetCharSet() ) + { + LanguageType eLang = static_cast<const SvxLanguageItem&>(aSet.Get( + GetWhichOfScript( RES_CHRATR_LANGUAGE, nSrcpt )) ).GetLanguage(); + LanguageTag aLanguageTag( eLang); + CharClass aCC( aLanguageTag); + sal_Unicode c0 = sNodeText[0]; + bool bIsAlphaNum = aCC.isAlphaNumeric( sNodeText, 0 ); + if( !bIsAlphaNum || + (c0 == ' ' || c0 == '\t')) + { + // ignoring blanks + nRet++; + const sal_Int32 nLen = sNodeText.getLength(); + for (sal_Int32 i = 1; + i<nLen && (sNodeText[i]==' ' || sNodeText[i]=='\t'); + ++i + ) + ++nRet; + } + } + } + return nRet; +} + +OUString SwSetExpField::GetPar1() const +{ + return static_cast<const SwSetExpFieldType*>(GetTyp())->GetName(); +} + +OUString SwSetExpField::GetPar2() const +{ + sal_uInt16 nType = static_cast<SwSetExpFieldType*>(GetTyp())->GetType(); + + if (nType & nsSwGetSetExpType::GSE_STRING) + return GetFormula(); + return GetExpandedFormula(); +} + +void SwSetExpField::SetPar2(const OUString& rStr) +{ + sal_uInt16 nType = static_cast<SwSetExpFieldType*>(GetTyp())->GetType(); + + if( !(nType & nsSwGetSetExpType::GSE_SEQ) || !rStr.isEmpty() ) + { + if (nType & nsSwGetSetExpType::GSE_STRING) + SetFormula(rStr); + else + SetExpandedFormula(rStr); + } +} + +bool SwSetExpField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + sal_Int32 nTmp32 = 0; + sal_Int16 nTmp16 = 0; + switch( nWhichId ) + { + case FIELD_PROP_BOOL2: + if(*o3tl::doAccess<bool>(rAny)) + mnSubType &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + mnSubType |= nsSwExtendedSubType::SUB_INVISIBLE; + break; + case FIELD_PROP_FORMAT: + rAny >>= nTmp32; + SetFormat(nTmp32); + break; + case FIELD_PROP_USHORT2: + { + rAny >>= nTmp16; + if(nTmp16 <= css::style::NumberingType::NUMBER_NONE ) + SetFormat(nTmp16); + else { + //exception(wrong_value) + ; + } + } + break; + case FIELD_PROP_USHORT1: + rAny >>= nTmp16; + mnSeqNo = nTmp16; + break; + case FIELD_PROP_PAR1: + { + OUString sTmp; + rAny >>= sTmp; + SetPar1( SwStyleNameMapper::GetUIName( sTmp, SwGetPoolIdFromName::TxtColl ) ); + } + break; + case FIELD_PROP_PAR2: + { + OUString uTmp; + rAny >>= uTmp; + //I18N - if the formula contains only "TypeName+1" + //and it's one of the initially created sequence fields + //then the localized names has to be replaced by a programmatic name + OUString sMyFormula = SwXFieldMaster::LocalizeFormula(*this, uTmp, false); + SetFormula( sMyFormula ); + } + break; + case FIELD_PROP_DOUBLE: + { + double fVal = 0.0; + rAny >>= fVal; + SetValue(fVal); + m_fValueRLHidden = fVal; + } + break; + case FIELD_PROP_SUBTYPE: + nTmp32 = lcl_APIToSubType(rAny); + if(nTmp32 >= 0) + SetSubType(static_cast<sal_uInt16>((GetSubType() & 0xff00) | nTmp32)); + break; + case FIELD_PROP_PAR3: + rAny >>= maPText; + break; + case FIELD_PROP_BOOL3: + if(*o3tl::doAccess<bool>(rAny)) + mnSubType |= nsSwExtendedSubType::SUB_CMD; + else + mnSubType &= (~nsSwExtendedSubType::SUB_CMD); + break; + case FIELD_PROP_BOOL1: + { + bool newInput(*o3tl::doAccess<bool>(rAny)); + if (newInput != GetInputFlag()) + { + if (static_cast<SwSetExpFieldType*>(GetTyp())->GetType() + & nsSwGetSetExpType::GSE_STRING) + { + SwXTextField::TransmuteLeadToInputField(*this); + } + else + { + SetInputFlag(newInput); + } + } + } + break; + case FIELD_PROP_PAR4: + { + OUString sTmp; + rAny >>= sTmp; + ChgExpStr(sTmp, nullptr); + } + break; + default: + return SwField::PutValue(rAny, nWhichId); + } + return true; +} + +bool SwSetExpField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL2: + rAny <<= 0 == (mnSubType & nsSwExtendedSubType::SUB_INVISIBLE); + break; + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int32>(GetFormat()); + break; + case FIELD_PROP_USHORT2: + rAny <<= static_cast<sal_Int16>(GetFormat()); + break; + case FIELD_PROP_USHORT1: + rAny <<= static_cast<sal_Int16>(mnSeqNo); + break; + case FIELD_PROP_PAR1: + rAny <<= SwStyleNameMapper::GetProgName(GetPar1(), SwGetPoolIdFromName::TxtColl ); + break; + case FIELD_PROP_PAR2: + { + //I18N - if the formula contains only "TypeName+1" + //and it's one of the initially created sequence fields + //then the localized names has to be replaced by a programmatic name + OUString sMyFormula = SwXFieldMaster::LocalizeFormula(*this, GetFormula(), true); + rAny <<= sMyFormula; + } + break; + case FIELD_PROP_DOUBLE: + rAny <<= GetValue(); + break; + case FIELD_PROP_SUBTYPE: + { + sal_Int16 nRet = lcl_SubTypeToAPI(GetSubType() & 0xff); + rAny <<= nRet; + } + break; + case FIELD_PROP_PAR3: + rAny <<= maPText; + break; + case FIELD_PROP_BOOL3: + rAny <<= 0 != (mnSubType & nsSwExtendedSubType::SUB_CMD); + break; + case FIELD_PROP_BOOL1: + rAny <<= GetInputFlag(); + break; + case FIELD_PROP_PAR4: + rAny <<= GetExpStr(nullptr); + break; + default: + return SwField::QueryValue(rAny, nWhichId); + } + return true; +} + +SwInputFieldType::SwInputFieldType( SwDoc* pD ) + : SwFieldType( SwFieldIds::Input ) + , mpDoc( pD ) +{ +} + +std::unique_ptr<SwFieldType> SwInputFieldType::Copy() const +{ + return std::make_unique<SwInputFieldType>( mpDoc ); +} + +SwInputField::SwInputField( SwInputFieldType* pFieldType, + const OUString& rContent, + const OUString& rPrompt, + sal_uInt16 nSub, + sal_uLong nFormat, + bool bIsFormField ) + : SwField( pFieldType, nFormat, LANGUAGE_SYSTEM, false ) + , maContent(rContent) + , maPText(rPrompt) + , mnSubType(nSub) + , mbIsFormField( bIsFormField ) + , mpFormatField( nullptr ) +{ +} + +SwInputField::~SwInputField() +{ +} + +void SwInputField::SetFormatField( SwFormatField& rFormatField ) +{ + mpFormatField = &rFormatField; +} + + +void SwInputField::applyFieldContent( const OUString& rNewFieldContent ) +{ + if ( (mnSubType & 0x00ff) == INP_TXT ) + { + maContent = rNewFieldContent; + } + else if( (mnSubType & 0x00ff) == INP_USR ) + { + SwUserFieldType* pUserTyp = static_cast<SwUserFieldType*>( + static_cast<SwInputFieldType*>(GetTyp())->GetDoc()->getIDocumentFieldsAccess().GetFieldType( SwFieldIds::User, getContent(), false ) ); + if( pUserTyp ) + { + pUserTyp->SetContent( rNewFieldContent ); + if (!pUserTyp->IsModifyLocked()) + { + // trigger update of the corresponding User Fields and other + // related Input Fields + bool bUnlock(false); + if (GetFormatField() != nullptr) + { + SwTextInputField *const pTextInputField = + dynamic_cast<SwTextInputField*>(GetFormatField()->GetTextField()); + if (pTextInputField != nullptr) + { + bUnlock = pTextInputField->LockNotifyContentChange(); + } + } + pUserTyp->UpdateFields(); + if (bUnlock) + { + SwTextInputField *const pTextInputField = + dynamic_cast<SwTextInputField*>(GetFormatField()->GetTextField()); + if (pTextInputField != nullptr) + { + pTextInputField->UnlockNotifyContentChange(); + } + } + } + } + } +} + +OUString SwInputField::GetFieldName() const +{ + OUString aStr(SwField::GetFieldName()); + if ((mnSubType & 0x00ff) == INP_USR) + { + aStr += GetTyp()->GetName() + " " + getContent(); + } + return aStr; +} + +std::unique_ptr<SwField> SwInputField::Copy() const +{ + std::unique_ptr<SwInputField> pField( + new SwInputField( + static_cast<SwInputFieldType*>(GetTyp()), + getContent(), + maPText, + GetSubType(), + GetFormat(), + mbIsFormField )); + + pField->SetHelp( maHelp ); + pField->SetToolTip( maToolTip ); + + pField->SetAutomaticLanguage(IsAutomaticLanguage()); + return std::unique_ptr<SwField>(pField.release()); +} + +OUString SwInputField::ExpandImpl(SwRootFrame const*const) const +{ + if((mnSubType & 0x00ff) == INP_TXT) + { + return getContent(); + } + + if( (mnSubType & 0x00ff) == INP_USR ) + { + SwUserFieldType* pUserTyp = static_cast<SwUserFieldType*>( + static_cast<SwInputFieldType*>(GetTyp())->GetDoc()->getIDocumentFieldsAccess().GetFieldType( SwFieldIds::User, getContent(), false ) ); + if( pUserTyp ) + return pUserTyp->GetContent(); + } + + return OUString(); +} + +bool SwInputField::isFormField() const +{ + return mbIsFormField + || !maHelp.isEmpty() + || !maToolTip.isEmpty(); +} + +bool SwInputField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= getContent(); + break; + case FIELD_PROP_PAR2: + rAny <<= maPText; + break; + case FIELD_PROP_PAR3: + rAny <<= maHelp; + break; + case FIELD_PROP_PAR4: + rAny <<= maToolTip; + break; + default: + assert(false); + } + return true; +} + +bool SwInputField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= maContent; + break; + case FIELD_PROP_PAR2: + rAny >>= maPText; + break; + case FIELD_PROP_PAR3: + rAny >>= maHelp; + break; + case FIELD_PROP_PAR4: + rAny >>= maToolTip; + break; + default: + assert(false); + } + return true; +} + +/// set condition +void SwInputField::SetPar1(const OUString& rStr) +{ + maContent = rStr; +} + +OUString SwInputField::GetPar1() const +{ + return getContent(); +} + +void SwInputField::SetPar2(const OUString& rStr) +{ + maPText = rStr; +} + +OUString SwInputField::GetPar2() const +{ + return maPText; +} + +void SwInputField::SetHelp(const OUString & rStr) +{ + maHelp = rStr; +} + +const OUString& SwInputField::GetHelp() const +{ + return maHelp; +} + +void SwInputField::SetToolTip(const OUString & rStr) +{ + maToolTip = rStr; +} + +const OUString& SwInputField::GetToolTip() const +{ + return maToolTip; +} + +sal_uInt16 SwInputField::GetSubType() const +{ + return mnSubType; +} + +void SwInputField::SetSubType(sal_uInt16 nSub) +{ + mnSubType = nSub; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/fldbas.cxx b/sw/source/core/fields/fldbas.cxx new file mode 100644 index 000000000..382880ecd --- /dev/null +++ b/sw/source/core/fields/fldbas.cxx @@ -0,0 +1,824 @@ +/* -*- 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 <fldbas.hxx> + +#include <float.h> + +#include <libxml/xmlwriter.h> + +#include <rtl/math.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <o3tl/enumarray.hxx> +#include <osl/diagnose.h> +#include <unofldmid.h> +#include <doc.hxx> +#include <fmtfld.hxx> +#include <docufld.hxx> +#include <expfld.hxx> +#include <shellres.hxx> +#include <calc.hxx> +#include <strings.hrc> +#include <docary.hxx> +#include <authfld.hxx> +#include <calbck.hxx> +#include <viewsh.hxx> + +using namespace ::com::sun::star; +using namespace nsSwDocInfoSubType; + +static LanguageType lcl_GetLanguageOfFormat( LanguageType nLng, sal_uLong nFormat, + const SvNumberFormatter& rFormatter ) +{ + if( nLng == LANGUAGE_NONE ) // Bug #60010 + nLng = LANGUAGE_SYSTEM; + else if( nLng == ::GetAppLanguage() ) + switch( rFormatter.GetIndexTableOffset( nFormat )) + { + case NF_NUMBER_SYSTEM: + case NF_DATE_SYSTEM_SHORT: + case NF_DATE_SYSTEM_LONG: + case NF_DATETIME_SYSTEM_SHORT_HHMM: + nLng = LANGUAGE_SYSTEM; + break; + default: break; + } + return nLng; +} + +// Globals + +/// field names +std::vector<OUString>* SwFieldType::s_pFieldNames = nullptr; + +namespace +{ + + const o3tl::enumarray<SwFieldIds,SwFieldTypesEnum> aTypeTab { + /* SwFieldIds::Database */ SwFieldTypesEnum::Database, + /* SwFieldIds::User */ SwFieldTypesEnum::User, + /* SwFieldIds::Filename */ SwFieldTypesEnum::Filename, + /* SwFieldIds::DatabaseName */ SwFieldTypesEnum::DatabaseName, + /* SwFieldIds::Date */ SwFieldTypesEnum::Date, + /* SwFieldIds::Time */ SwFieldTypesEnum::Time, + /* SwFieldIds::PageNumber */ SwFieldTypesEnum::PageNumber, // dynamic + /* SwFieldIds::Author */ SwFieldTypesEnum::Author, + /* SwFieldIds::Chapter */ SwFieldTypesEnum::Chapter, + /* SwFieldIds::DocStat */ SwFieldTypesEnum::DocumentStatistics, + /* SwFieldIds::GetExp */ SwFieldTypesEnum::Get, // dynamic + /* SwFieldIds::SetExp */ SwFieldTypesEnum::Set, // dynamic + /* SwFieldIds::GetRef */ SwFieldTypesEnum::GetRef, + /* SwFieldIds::HiddenText */ SwFieldTypesEnum::HiddenText, + /* SwFieldIds::Postit */ SwFieldTypesEnum::Postit, + /* SwFieldIds::FixDate */ SwFieldTypesEnum::FixedDate, + /* SwFieldIds::FixTime */ SwFieldTypesEnum::FixedTime, + /* SwFieldIds::Reg */ SwFieldTypesEnum::Begin, // old (no change since 2000) + /* SwFieldIds::VarReg */ SwFieldTypesEnum::Begin, // old (no change since 2000) + /* SwFieldIds::SetRef */ SwFieldTypesEnum::SetRef, + /* SwFieldIds::Input */ SwFieldTypesEnum::Input, + /* SwFieldIds::Macro */ SwFieldTypesEnum::Macro, + /* SwFieldIds::Dde */ SwFieldTypesEnum::DDE, + /* SwFieldIds::Table */ SwFieldTypesEnum::Formel, + /* SwFieldIds::HiddenPara */ SwFieldTypesEnum::HiddenParagraph, + /* SwFieldIds::DocInfo */ SwFieldTypesEnum::DocumentInfo, + /* SwFieldIds::TemplateName */ SwFieldTypesEnum::TemplateName, + /* SwFieldIds::DbNextSet */ SwFieldTypesEnum::DatabaseNextSet, + /* SwFieldIds::DbNumSet */ SwFieldTypesEnum::DatabaseNumberSet, + /* SwFieldIds::DbSetNumber */ SwFieldTypesEnum::DatabaseSetNumber, + /* SwFieldIds::ExtUser */ SwFieldTypesEnum::ExtendedUser, + /* SwFieldIds::RefPageSet */ SwFieldTypesEnum::SetRefPage, + /* SwFieldIds::RefPageGet */ SwFieldTypesEnum::GetRefPage, + /* SwFieldIds::Internet */ SwFieldTypesEnum::Internet, + /* SwFieldIds::JumpEdit */ SwFieldTypesEnum::JumpEdit, + /* SwFieldIds::Script */ SwFieldTypesEnum::Script, + /* SwFieldIds::DateTime */ SwFieldTypesEnum::Begin, // dynamic + /* SwFieldIds::TableOfAuthorities*/ SwFieldTypesEnum::Authority, + /* SwFieldIds::CombinedChars */ SwFieldTypesEnum::CombinedChars, + /* SwFieldIds::Dropdown */ SwFieldTypesEnum::Dropdown, + /* SwFieldIds::ParagraphSignature */ SwFieldTypesEnum::ParagraphSignature + }; + +} + +OUString SwFieldType::GetTypeStr(SwFieldTypesEnum nTypeId) +{ + if (!s_pFieldNames) + GetFieldName_(); + + return (*SwFieldType::s_pFieldNames)[static_cast<int>(nTypeId)]; +} + +// each field references a field type that is unique for each document +SwFieldType::SwFieldType( SwFieldIds nWhichId ) + : SwModify() + , m_nWhich(nWhichId) +{ +} + +OUString SwFieldType::GetName() const +{ + return OUString(); +} + +void SwFieldType::QueryValue( uno::Any&, sal_uInt16 ) const +{ +} +void SwFieldType::PutValue( const uno::Any& , sal_uInt16 ) +{ +} + +void SwFieldType::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + if(!vFields.size()) + return; + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFieldType")); + for(const auto pFormatField: vFields) + pFormatField->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +SwFormatField* SwFieldType::FindFormatForField(const SwField* pField) const { + SwFormatField* pFormat = nullptr; + CallSwClientNotify(sw::FindFormatForFieldHint(pField, pFormat)); + return pFormat; +} + +SwFormatField* SwFieldType::FindFormatForPostItId(sal_uInt32 nPostItId) const { + SwFormatField* pFormat = nullptr; + CallSwClientNotify(sw::FindFormatForPostItIdHint(nPostItId, pFormat)); + return pFormat; +} + +void SwFieldType::CollectPostIts(std::vector<SwFormatField*>& rvFormatFields, IDocumentRedlineAccess const& rIDRA, const bool bHideRedlines) +{ + CallSwClientNotify(sw::CollectPostItsHint(rvFormatFields, rIDRA, bHideRedlines)); +} + +bool SwFieldType::HasHiddenInformationNotes() +{ + bool bHasHiddenInformationNotes = false; + CallSwClientNotify(sw::HasHiddenInformationNotesHint(bHasHiddenInformationNotes)); + return bHasHiddenInformationNotes; +} + +void SwFieldType::GatherNodeIndex(std::vector<sal_uLong>& rvNodeIndex) +{ + CallSwClientNotify(sw::GatherNodeIndexHint(rvNodeIndex)); +} + +void SwFieldType::GatherRefFields(std::vector<SwGetRefField*>& rvRFields, const sal_uInt16 nTyp) +{ + CallSwClientNotify(sw::GatherRefFieldsHint(rvRFields, nTyp)); +} + +void SwFieldType::GatherFields(std::vector<SwFormatField*>& rvFields, bool bCollectOnlyInDocNodes) const +{ + CallSwClientNotify(sw::GatherFieldsHint(rvFields, bCollectOnlyInDocNodes)); +} + +void SwFieldTypes::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFieldTypes")); + sal_uInt16 nCount = size(); + for (sal_uInt16 nType = 0; nType < nCount; ++nType) + (*this)[nType]->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +// Base class for all fields. +// A field (multiple can exist) references a field type (can exists only once) +SwField::SwField( + SwFieldType* pType, + sal_uInt32 nFormat, + LanguageType nLang, + bool bUseFieldValueCache) + : m_Cache() + , m_bUseFieldValueCache( bUseFieldValueCache ) + , m_nLang( nLang ) + , m_bIsAutomaticLanguage( true ) + , m_nFormat( nFormat ) + , m_pType( pType ) +{ + assert(m_pType); +} + +SwField::~SwField() +{ +} + +// instead of indirectly via the type + +#ifdef DBG_UTIL +SwFieldIds SwField::Which() const +{ + assert(m_pType); + return m_pType->Which(); +} +#endif + +SwFieldTypesEnum SwField::GetTypeId() const +{ + + SwFieldTypesEnum nRet; + switch (m_pType->Which()) + { + case SwFieldIds::DateTime: + if (GetSubType() & FIXEDFLD) + nRet = GetSubType() & DATEFLD ? SwFieldTypesEnum::FixedDate : SwFieldTypesEnum::FixedTime; + else + nRet = GetSubType() & DATEFLD ? SwFieldTypesEnum::Date : SwFieldTypesEnum::Time; + break; + case SwFieldIds::GetExp: + nRet = nsSwGetSetExpType::GSE_FORMULA & GetSubType() ? SwFieldTypesEnum::Formel : SwFieldTypesEnum::Get; + break; + + case SwFieldIds::HiddenText: + nRet = static_cast<SwFieldTypesEnum>(GetSubType()); + break; + + case SwFieldIds::SetExp: + if( nsSwGetSetExpType::GSE_SEQ & GetSubType() ) + nRet = SwFieldTypesEnum::Sequence; + else if( static_cast<const SwSetExpField*>(this)->GetInputFlag() ) + nRet = SwFieldTypesEnum::SetInput; + else + nRet = SwFieldTypesEnum::Set; + break; + + case SwFieldIds::PageNumber: + { + auto nSubType = GetSubType(); + if( PG_NEXT == nSubType ) + nRet = SwFieldTypesEnum::NextPage; + else if( PG_PREV == nSubType ) + nRet = SwFieldTypesEnum::PreviousPage; + else + nRet = SwFieldTypesEnum::PageNumber; + } + break; + + default: + nRet = aTypeTab[ m_pType->Which() ]; + } + return nRet; +} + +/// get name or content +OUString SwField::GetFieldName() const +{ + SwFieldTypesEnum nTypeId = GetTypeId(); + if (SwFieldIds::DateTime == GetTyp()->Which()) + { + nTypeId = + ((GetSubType() & DATEFLD) != 0) ? SwFieldTypesEnum::Date : SwFieldTypesEnum::Time; + } + OUString sRet = SwFieldType::GetTypeStr( nTypeId ); + if (IsFixed()) + { + sRet += " " + SwViewShell::GetShellRes()->aFixedStr; + } + return sRet; +} + +OUString SwField::GetPar1() const +{ + return OUString(); +} + +OUString SwField::GetPar2() const +{ + return OUString(); +} + +OUString SwField::GetFormula() const +{ + return GetPar2(); +} + +void SwField::SetPar1(const OUString& ) +{} + +void SwField::SetPar2(const OUString& ) +{} + +sal_uInt16 SwField::GetSubType() const +{ + return 0; +} + +void SwField::SetSubType(sal_uInt16 ) +{ +} + +bool SwField::QueryValue( uno::Any& rVal, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL4: + rVal <<= !m_bIsAutomaticLanguage; + break; + default: + assert(false); + } + return true; +} + +bool SwField::PutValue( const uno::Any& rVal, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL4: + { + bool bFixed = false; + if(rVal >>= bFixed) + m_bIsAutomaticLanguage = !bFixed; + } + break; + default: + assert(false); + } + return true; +} + +/** Set a new type + * + * This is needed/used for copying between documents. + * Needs to be always of the same type. + * @param pNewType The new type. + * @return The old type. + */ +SwFieldType* SwField::ChgTyp( SwFieldType* pNewType ) +{ + assert(pNewType && pNewType->Which() == m_pType->Which()); + + SwFieldType* pOld = m_pType; + m_pType = pNewType; + return pOld; +} + +/// Does the field have an action on a ClickHandler? (E.g. INetFields,...) +bool SwField::HasClickHdl() const +{ + bool bRet = false; + switch (m_pType->Which()) + { + case SwFieldIds::Internet: + case SwFieldIds::JumpEdit: + case SwFieldIds::GetRef: + case SwFieldIds::Macro: + case SwFieldIds::Input: + case SwFieldIds::Dropdown : + bRet = true; + break; + + case SwFieldIds::SetExp: + bRet = static_cast<const SwSetExpField*>(this)->GetInputFlag(); + break; + default: break; + } + return bRet; +} + +void SwField::SetLanguage(LanguageType const nLang) +{ + m_nLang = nLang; +} + +void SwField::ChangeFormat(sal_uInt32 const nFormat) +{ + m_nFormat = nFormat; +} + +bool SwField::IsFixed() const +{ + bool bRet = false; + switch (m_pType->Which()) + { + case SwFieldIds::FixDate: + case SwFieldIds::FixTime: + bRet = true; + break; + + case SwFieldIds::DateTime: + bRet = 0 != (GetSubType() & FIXEDFLD); + break; + + case SwFieldIds::ExtUser: + case SwFieldIds::Author: + bRet = 0 != (GetFormat() & AF_FIXED); + break; + + case SwFieldIds::Filename: + bRet = 0 != (GetFormat() & FF_FIXED); + break; + + case SwFieldIds::DocInfo: + bRet = 0 != (GetSubType() & DI_SUB_FIXED); + break; + default: break; + } + return bRet; +} + +OUString +SwField::ExpandField(bool const bCached, SwRootFrame const*const pLayout) const +{ + if ( m_bUseFieldValueCache ) + { + if (!bCached) // #i85766# do not expand fields in clipboard documents + { + if (GetTypeId() == SwFieldTypesEnum::Authority) + { + const SwAuthorityField* pAuthorityField = static_cast<const SwAuthorityField*>(this); + m_Cache = pAuthorityField->ConditionalExpandAuthIdentifier(pLayout); + } + else + m_Cache = ExpandImpl(pLayout); + } + return m_Cache; + } + + return ExpandImpl(pLayout); +} + +std::unique_ptr<SwField> SwField::CopyField() const +{ + std::unique_ptr<SwField> pNew = Copy(); + // #i85766# cache expansion of source (for clipboard) + // use this->cache, not this->Expand(): only text formatting calls Expand() + pNew->m_Cache = m_Cache; + pNew->m_bUseFieldValueCache = m_bUseFieldValueCache; + + return pNew; +} + +/// expand numbering +OUString FormatNumber(sal_uInt32 nNum, SvxNumType nFormat, LanguageType nLang) +{ + if(SVX_NUM_PAGEDESC == nFormat) + return OUString::number( nNum ); + SvxNumberType aNumber; + + OSL_ENSURE(nFormat != SVX_NUM_NUMBER_NONE, "wrong number format" ); + + aNumber.SetNumberingType(nFormat); + + if (nLang == LANGUAGE_NONE) + return aNumber.GetNumStr(nNum); + else + return aNumber.GetNumStr(nNum, LanguageTag::convertToLocale(nLang)); +} + +SwValueFieldType::SwValueFieldType(SwDoc *const pDoc, SwFieldIds const nWhichId) + : SwFieldType(nWhichId) + , m_pDoc(pDoc) + , m_bUseFormat(true) +{ +} + +SwValueFieldType::SwValueFieldType( const SwValueFieldType& rTyp ) + : SwFieldType(rTyp.Which()) + , m_pDoc(rTyp.GetDoc()) + , m_bUseFormat(rTyp.UseFormat()) +{ +} + +/// return value formatted as string +OUString SwValueFieldType::ExpandValue( const double& rVal, + sal_uInt32 nFormat, LanguageType nLng) const +{ + if (rVal >= DBL_MAX) // error string for calculator + return SwViewShell::GetShellRes()->aCalc_Error; + + OUString sExpand; + SvNumberFormatter* pFormatter = m_pDoc->GetNumberFormatter(); + Color* pCol = nullptr; + + // Bug #60010 + LanguageType nFormatLng = ::lcl_GetLanguageOfFormat( nLng, nFormat, *pFormatter ); + + if( nFormat < SV_COUNTRY_LANGUAGE_OFFSET && LANGUAGE_SYSTEM != nFormatLng ) + { + SvNumFormatType nType = SvNumFormatType::DEFINED; + sal_Int32 nDummy; + + const SvNumberformat* pEntry = pFormatter->GetEntry(nFormat); + + if (pEntry && nLng != pEntry->GetLanguage()) + { + sal_uInt32 nNewFormat = pFormatter->GetFormatForLanguageIfBuiltIn(nFormat, + nFormatLng); + + if (nNewFormat == nFormat) + { + // probably user-defined format + OUString sFormat(pEntry->GetFormatstring()); + + pFormatter->PutandConvertEntry(sFormat, nDummy, nType, nFormat, + pEntry->GetLanguage(), nFormatLng, false); + } + else + nFormat = nNewFormat; + } + OSL_ENSURE(pEntry, "unknown number format!"); + } + + if( pFormatter->IsTextFormat( nFormat ) ) + { + pFormatter->GetOutputString(DoubleToString(rVal, nFormatLng), nFormat, + sExpand, &pCol); + } + else + { + pFormatter->GetOutputString(rVal, nFormat, sExpand, &pCol); + } + return sExpand; +} + +OUString SwValueFieldType::DoubleToString(const double &rVal, + sal_uInt32 nFormat) const +{ + SvNumberFormatter* pFormatter = m_pDoc->GetNumberFormatter(); + const SvNumberformat* pEntry = pFormatter->GetEntry(nFormat); + + if (!pEntry) + return OUString(); + + return DoubleToString(rVal, pEntry->GetLanguage()); +} + +OUString SwValueFieldType::DoubleToString( const double &rVal, + LanguageType nLng ) const +{ + SvNumberFormatter* pFormatter = m_pDoc->GetNumberFormatter(); + + // Bug #60010 + if( nLng == LANGUAGE_NONE ) + nLng = LANGUAGE_SYSTEM; + + pFormatter->ChangeIntl( nLng ); // get separator in the correct language + return ::rtl::math::doubleToUString( rVal, rtl_math_StringFormat_F, 12, + pFormatter->GetNumDecimalSep()[0], true ); +} + +SwValueField::SwValueField( SwValueFieldType* pFieldType, sal_uInt32 nFormat, + LanguageType nLng, const double fVal ) + : SwField(pFieldType, nFormat, nLng) + , m_fValue(fVal) +{ +} + +SwValueField::SwValueField( const SwValueField& rField ) + : SwField(rField) + , m_fValue(rField.GetValue()) +{ +} + +SwValueField::~SwValueField() +{ +} + +/** Set a new type + * + * This is needed/used for copying between documents. + * Needs to be always of the same type. + * @param pNewType The new type. + * @return The old type. + */ +SwFieldType* SwValueField::ChgTyp( SwFieldType* pNewType ) +{ + SwDoc* pNewDoc = static_cast<SwValueFieldType *>(pNewType)->GetDoc(); + SwDoc* pDoc = GetDoc(); + + if( pNewDoc && pDoc && pDoc != pNewDoc) + { + SvNumberFormatter* pFormatter = pNewDoc->GetNumberFormatter(); + + if( pFormatter && pFormatter->HasMergeFormatTable() && + static_cast<SwValueFieldType *>(GetTyp())->UseFormat() ) + SetFormat(pFormatter->GetMergeFormatIndex( GetFormat() )); + } + + return SwField::ChgTyp(pNewType); +} + +/// get format in office language +sal_uInt32 SwValueField::GetSystemFormat(SvNumberFormatter* pFormatter, sal_uInt32 nFormat) +{ + const SvNumberformat* pEntry = pFormatter->GetEntry(nFormat); + LanguageType nLng = SvtSysLocale().GetLanguageTag().getLanguageType(); + + if (pEntry && nLng != pEntry->GetLanguage()) + { + sal_uInt32 nNewFormat = pFormatter->GetFormatForLanguageIfBuiltIn(nFormat, + nLng); + + if (nNewFormat == nFormat) + { + // probably user-defined format + SvNumFormatType nType = SvNumFormatType::DEFINED; + sal_Int32 nDummy; + + OUString sFormat(pEntry->GetFormatstring()); + + sal_uInt32 nTempFormat = nFormat; + pFormatter->PutandConvertEntry(sFormat, nDummy, nType, + nTempFormat, pEntry->GetLanguage(), nLng, true); + nFormat = nTempFormat; + } + else + nFormat = nNewFormat; + } + + return nFormat; +} + +void SwValueField::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwValueField")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_fValue"), BAD_CAST(OString::number(m_fValue).getStr())); + SwField::dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +/// set language of the format +void SwValueField::SetLanguage( LanguageType nLng ) +{ + if( IsAutomaticLanguage() && + static_cast<SwValueFieldType *>(GetTyp())->UseFormat() && + GetFormat() != SAL_MAX_UINT32 ) + { + // Bug #60010 + SvNumberFormatter* pFormatter = GetDoc()->GetNumberFormatter(); + LanguageType nFormatLng = ::lcl_GetLanguageOfFormat( nLng, GetFormat(), + *pFormatter ); + + if( (GetFormat() >= SV_COUNTRY_LANGUAGE_OFFSET || + LANGUAGE_SYSTEM != nFormatLng ) && + !(Which() == SwFieldIds::User && (GetSubType()&nsSwExtendedSubType::SUB_CMD) ) ) + { + const SvNumberformat* pEntry = pFormatter->GetEntry(GetFormat()); + + if( pEntry && nFormatLng != pEntry->GetLanguage() ) + { + sal_uInt32 nNewFormat = pFormatter->GetFormatForLanguageIfBuiltIn( + GetFormat(), nFormatLng ); + + if( nNewFormat == GetFormat() ) + { + // probably user-defined format + SvNumFormatType nType = SvNumFormatType::DEFINED; + sal_Int32 nDummy; + OUString sFormat( pEntry->GetFormatstring() ); + pFormatter->PutandConvertEntry( sFormat, nDummy, nType, + nNewFormat, + pEntry->GetLanguage(), + nFormatLng, false); + } + SetFormat( nNewFormat ); + } + OSL_ENSURE(pEntry, "unknown number format!"); + } + } + + SwField::SetLanguage(nLng); +} + +double SwValueField::GetValue() const +{ + return m_fValue; +} + +void SwValueField::SetValue( const double& rVal ) +{ + m_fValue = rVal; +} + +SwFormulaField::SwFormulaField( SwValueFieldType* pFieldType, sal_uInt32 nFormat, const double fVal) + : SwValueField(pFieldType, nFormat, LANGUAGE_SYSTEM, fVal) +{ +} + +SwFormulaField::SwFormulaField( const SwFormulaField& rField ) + : SwValueField(static_cast<SwValueFieldType *>(rField.GetTyp()), rField.GetFormat(), + rField.GetLanguage(), rField.GetValue()) +{ +} + +OUString SwFormulaField::GetFormula() const +{ + return m_sFormula; +} + +void SwFormulaField::SetFormula(const OUString& rStr) +{ + m_sFormula = rStr; + + sal_uLong nFormat(GetFormat()); + + if( nFormat && SAL_MAX_UINT32 != nFormat ) + { + sal_Int32 nPos = 0; + double fTmpValue; + if( SwCalc::Str2Double( rStr, nPos, fTmpValue, GetDoc() ) ) + SwValueField::SetValue( fTmpValue ); + } +} + +void SwFormulaField::SetExpandedFormula( const OUString& rStr ) +{ + sal_uInt32 nFormat(GetFormat()); + + if (nFormat && nFormat != SAL_MAX_UINT32 && static_cast<SwValueFieldType *>(GetTyp())->UseFormat()) + { + double fTmpValue; + + if (GetDoc()->IsNumberFormat(rStr, nFormat, fTmpValue)) + { + SwValueField::SetValue(fTmpValue); + + m_sFormula = static_cast<SwValueFieldType *>(GetTyp())->DoubleToString(fTmpValue, nFormat); + return; + } + } + m_sFormula = rStr; +} + +OUString SwFormulaField::GetExpandedFormula() const +{ + sal_uInt32 nFormat(GetFormat()); + + if (nFormat && nFormat != SAL_MAX_UINT32 && static_cast<SwValueFieldType *>(GetTyp())->UseFormat()) + { + OUString sFormattedValue; + Color* pCol = nullptr; + + SvNumberFormatter* pFormatter = GetDoc()->GetNumberFormatter(); + + if (pFormatter->IsTextFormat(nFormat)) + { + OUString sTempIn(static_cast<SwValueFieldType *>(GetTyp())->DoubleToString(GetValue(), nFormat)); + pFormatter->GetOutputString(sTempIn, nFormat, sFormattedValue, &pCol); + } + else + { + pFormatter->GetOutputString(GetValue(), nFormat, sFormattedValue, &pCol); + } + return sFormattedValue; + } + else + return GetFormula(); +} + +OUString SwField::GetDescription() const +{ + return SwResId(STR_FIELD); +} + +bool SwField::IsClickable() const +{ + switch (Which()) + { + case SwFieldIds::JumpEdit: + case SwFieldIds::Macro: + case SwFieldIds::GetRef: + case SwFieldIds::Input: + case SwFieldIds::SetExp: + case SwFieldIds::Dropdown: + return true; + default: break; + } + return false; +} + +void SwField::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwField")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*this).name())); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nFormat"), BAD_CAST(OString::number(m_nFormat).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nLang"), BAD_CAST(OString::number(m_nLang.get()).getStr())); + + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/flddat.cxx b/sw/source/core/fields/flddat.cxx new file mode 100644 index 000000000..f2a6a779a --- /dev/null +++ b/sw/source/core/fields/flddat.cxx @@ -0,0 +1,232 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/any.hxx> +#include <o3tl/temporary.hxx> +#include <tools/datetime.hxx> +#include <svl/zforlist.hxx> +#include <com/sun/star/util/DateTime.hpp> +#include <doc.hxx> +#include <fldbas.hxx> +#include <flddat.hxx> +#include <unofldmid.h> + +using namespace ::com::sun::star; + +SwDateTimeFieldType::SwDateTimeFieldType(SwDoc* pInitDoc) + : SwValueFieldType( pInitDoc, SwFieldIds::DateTime ) +{} + +std::unique_ptr<SwFieldType> SwDateTimeFieldType::Copy() const +{ + return std::make_unique<SwDateTimeFieldType>(GetDoc()); +} + +SwDateTimeField::SwDateTimeField(SwDateTimeFieldType* pInitType, sal_uInt16 nSub, sal_uLong nFormat, LanguageType nLng) + : SwValueField(pInitType, nFormat, nLng, 0.0), + m_nSubType(nSub), + m_nOffset(0) +{ + if (!nFormat) + { + SvNumberFormatter* pFormatter = GetDoc()->GetNumberFormatter(); + if (m_nSubType & DATEFLD) + ChangeFormat(pFormatter->GetFormatIndex(NF_DATE_SYSTEM_SHORT, GetLanguage())); + else + ChangeFormat(pFormatter->GetFormatIndex(NF_TIME_HHMMSS, GetLanguage())); + } + if (IsFixed()) + { + DateTime aDateTime( DateTime::SYSTEM ); + SetDateTime(aDateTime); + } +} + +OUString SwDateTimeField::ExpandImpl(SwRootFrame const*const) const +{ + double fVal; + + if (!(IsFixed())) + { + DateTime aDateTime( DateTime::SYSTEM ); + fVal = GetDateTime(GetDoc(), aDateTime); + } + else + fVal = GetValue(); + + if (m_nOffset) + fVal += m_nOffset * ( 60 / 86400.0 ); + + return ExpandValue(fVal, GetFormat(), GetLanguage()); +} + +std::unique_ptr<SwField> SwDateTimeField::Copy() const +{ + std::unique_ptr<SwDateTimeField> pTmp( + new SwDateTimeField(static_cast<SwDateTimeFieldType*>(GetTyp()), m_nSubType, + GetFormat(), GetLanguage()) ); + + pTmp->SetValue(GetValue()); + pTmp->SetOffset(m_nOffset); + pTmp->SetAutomaticLanguage(IsAutomaticLanguage()); + + return std::unique_ptr<SwField>(pTmp.release()); +} + +sal_uInt16 SwDateTimeField::GetSubType() const +{ + return m_nSubType; +} + +void SwDateTimeField::SetSubType(sal_uInt16 nType) +{ + m_nSubType = nType; +} + +void SwDateTimeField::SetPar2(const OUString& rStr) +{ + m_nOffset = rStr.toInt32(); +} + +OUString SwDateTimeField::GetPar2() const +{ + if (m_nOffset) + return OUString::number(m_nOffset); + return OUString(); +} + +void SwDateTimeField::SetDateTime(const DateTime& rDT) +{ + SetValue(GetDateTime(GetDoc(), rDT)); +} + +double SwDateTimeField::GetDateTime(SwDoc* pDoc, const DateTime& rDT) +{ + SvNumberFormatter* pFormatter = pDoc->GetNumberFormatter(); + const Date& rNullDate = pFormatter->GetNullDate(); + + double fResult = rDT - DateTime(rNullDate); + + return fResult; +} + +double SwDateTimeField::GetValue() const +{ + if (IsFixed()) + return SwValueField::GetValue(); + else + return GetDateTime(GetDoc(), DateTime( DateTime::SYSTEM )); +} + +Date SwDateTimeField::GetDate() const +{ + SvNumberFormatter* pFormatter = GetDoc()->GetNumberFormatter(); + const Date& rNullDate = pFormatter->GetNullDate(); + + long nVal = static_cast<long>( GetValue() ); + + Date aDate = rNullDate + nVal; + + return aDate; +} + +tools::Time SwDateTimeField::GetTime() const +{ + double fFract = modf(GetValue(), &o3tl::temporary(double())); + DateTime aDT( DateTime::EMPTY ); + aDT.AddTime(fFract); + return static_cast<tools::Time>(aDT); +} + +bool SwDateTimeField::QueryValue( uno::Any& rVal, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + rVal <<= IsFixed(); + break; + case FIELD_PROP_BOOL2: + rVal <<= (m_nSubType & DATEFLD) != 0; + break; + case FIELD_PROP_FORMAT: + rVal <<= static_cast<sal_Int32>(GetFormat()); + break; + case FIELD_PROP_SUBTYPE: + rVal <<= static_cast<sal_Int32>(m_nOffset); + break; + case FIELD_PROP_DATE_TIME: + { + DateTime aDateTime(GetDate(), GetTime()); + rVal <<= aDateTime.GetUNODateTime(); + } + break; + default: + return SwField::QueryValue(rVal, nWhichId); + } + return true; +} + +bool SwDateTimeField::PutValue( const uno::Any& rVal, sal_uInt16 nWhichId ) +{ + sal_Int32 nTmp = 0; + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + if(*o3tl::doAccess<bool>(rVal)) + m_nSubType |= FIXEDFLD; + else + m_nSubType &= ~FIXEDFLD; + break; + case FIELD_PROP_BOOL2: + m_nSubType &= ~(DATEFLD|TIMEFLD); + m_nSubType |= *o3tl::doAccess<bool>(rVal) ? DATEFLD : TIMEFLD; + break; + case FIELD_PROP_FORMAT: + rVal >>= nTmp; + ChangeFormat(nTmp); + break; + case FIELD_PROP_SUBTYPE: + rVal >>= nTmp; + m_nOffset = nTmp; + break; + case FIELD_PROP_DATE_TIME: + { + util::DateTime aDateTimeValue; + if(!(rVal >>= aDateTimeValue)) + return false; + DateTime aDateTime( DateTime::EMPTY ); + aDateTime.SetNanoSec(aDateTimeValue.NanoSeconds); + aDateTime.SetSec(aDateTimeValue.Seconds); + aDateTime.SetMin(aDateTimeValue.Minutes); + aDateTime.SetHour(aDateTimeValue.Hours); + aDateTime.SetDay(aDateTimeValue.Day); + aDateTime.SetMonth(aDateTimeValue.Month); + aDateTime.SetYear(aDateTimeValue.Year); + SetDateTime(aDateTime); + } + break; + default: + return SwField::PutValue(rVal, nWhichId); + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/flddropdown.cxx b/sw/source/core/fields/flddropdown.cxx new file mode 100644 index 000000000..25c31230a --- /dev/null +++ b/sw/source/core/fields/flddropdown.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <flddropdown.hxx> + +#include <algorithm> + +#include <svl/poolitem.hxx> +#include <comphelper/sequence.hxx> + +#include <unofldmid.h> + +using namespace com::sun::star; + +using std::vector; + +SwDropDownFieldType::SwDropDownFieldType() + : SwFieldType(SwFieldIds::Dropdown) +{ +} + +SwDropDownFieldType::~SwDropDownFieldType() +{ +} + +std::unique_ptr<SwFieldType> SwDropDownFieldType::Copy() const +{ + return std::make_unique<SwDropDownFieldType>(); +} + +SwDropDownField::SwDropDownField(SwFieldType * pTyp) + : SwField(pTyp, 0, LANGUAGE_SYSTEM) +{ +} + +SwDropDownField::SwDropDownField(const SwDropDownField & rSrc) + : SwField(rSrc.GetTyp(), rSrc.GetFormat(), rSrc.GetLanguage()), + aValues(rSrc.aValues), aSelectedItem(rSrc.aSelectedItem), + aName(rSrc.aName), aHelp(rSrc.aHelp), aToolTip(rSrc.aToolTip) +{ +} + +SwDropDownField::~SwDropDownField() +{ +} + +OUString SwDropDownField::ExpandImpl(SwRootFrame const*const) const +{ + OUString sSelect = GetSelectedItem(); + if (sSelect.isEmpty()) + { + vector<OUString>::const_iterator aIt = aValues.begin(); + if ( aIt != aValues.end()) + sSelect = *aIt; + } + // if still no list value is available a default text of 10 spaces is to be set + if (sSelect.isEmpty()) + sSelect = " "; + return sSelect; +} + +std::unique_ptr<SwField> SwDropDownField::Copy() const +{ + return std::make_unique<SwDropDownField>(*this); +} + +OUString SwDropDownField::GetPar1() const +{ + return GetSelectedItem(); +} + +OUString SwDropDownField::GetPar2() const +{ + return GetName(); +} + +void SwDropDownField::SetPar1(const OUString & rStr) +{ + SetSelectedItem(rStr); +} + +void SwDropDownField::SetPar2(const OUString & rName) +{ + SetName(rName); +} + +void SwDropDownField::SetItems(const vector<OUString> & rItems) +{ + aValues = rItems; + aSelectedItem.clear(); +} + +void SwDropDownField::SetItems(const uno::Sequence<OUString> & rItems) +{ + aValues.clear(); + + comphelper::sequenceToContainer(aValues, rItems); + + aSelectedItem.clear(); +} + +uno::Sequence<OUString> SwDropDownField::GetItemSequence() const +{ + return comphelper::containerToSequence(aValues); +} + + +void SwDropDownField::SetSelectedItem(const OUString & rItem) +{ + vector<OUString>::const_iterator aIt = + std::find(aValues.begin(), aValues.end(), rItem); + + if (aIt != aValues.end()) + aSelectedItem = *aIt; + else + aSelectedItem.clear(); +} + +void SwDropDownField::SetName(const OUString & rName) +{ + aName = rName; +} + +void SwDropDownField::SetHelp(const OUString & rHelp) +{ + aHelp = rHelp; +} + +void SwDropDownField::SetToolTip(const OUString & rToolTip) +{ + aToolTip = rToolTip; +} + +bool SwDropDownField::QueryValue(::uno::Any &rVal, sal_uInt16 nWhich) const +{ + nWhich &= ~CONVERT_TWIPS; + switch( nWhich ) + { + case FIELD_PROP_PAR1: + rVal <<= aSelectedItem; + break; + case FIELD_PROP_PAR2: + rVal <<= aName; + break; + case FIELD_PROP_PAR3: + rVal <<= aHelp; + break; + case FIELD_PROP_PAR4: + rVal <<= aToolTip; + break; + case FIELD_PROP_STRINGS: + rVal <<= GetItemSequence(); + break; + + default: + assert(false); + } + return true; +} + +bool SwDropDownField::PutValue(const uno::Any &rVal, + sal_uInt16 nWhich) +{ + switch( nWhich ) + { + case FIELD_PROP_PAR1: + { + OUString aTmpStr; + rVal >>= aTmpStr; + + SetSelectedItem(aTmpStr); + } + break; + + case FIELD_PROP_PAR2: + rVal >>= aName; + break; + + case FIELD_PROP_PAR3: + rVal >>= aHelp; + break; + + case FIELD_PROP_PAR4: + rVal >>= aToolTip; + break; + + case FIELD_PROP_STRINGS: + { + uno::Sequence<OUString> aSeq; + rVal >>= aSeq; + SetItems(aSeq); + } + break; + + default: + assert(false); + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/fldlst.cxx b/sw/source/core/fields/fldlst.cxx new file mode 100644 index 000000000..aa937cb22 --- /dev/null +++ b/sw/source/core/fields/fldlst.cxx @@ -0,0 +1,153 @@ +/* -*- 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 <calbck.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <expfld.hxx> +#include <pam.hxx> +#include <docfld.hxx> +#include <ndtxt.hxx> + +#include <osl/diagnose.h> + +// sort input values + +SwInputFieldList::SwInputFieldList( SwEditShell* pShell, bool bBuildTmpLst ) + : mpSh(pShell) +{ + // create sorted list of all input fields + mpSrtLst.reset( new SetGetExpFields ); + + const SwFieldTypes& rFieldTypes = *mpSh->GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + const size_t nSize = rFieldTypes.size(); + + // iterate over all types + std::vector<SwFormatField*> vFields; + for(size_t i = 0; i < nSize; ++i) + { + SwFieldType* pFieldType = rFieldTypes[ i ].get(); + const SwFieldIds nType = pFieldType->Which(); + if(SwFieldIds::SetExp == nType || SwFieldIds::Input == nType || SwFieldIds::Dropdown == nType) + pFieldType->GatherFields(vFields); + } + for(auto pFormatField: vFields) + { + auto pSetExpField = dynamic_cast<SwSetExpField*>(pFormatField->GetField()); + if(pSetExpField && !pSetExpField->GetInputFlag()) + continue; + const SwTextField* pTextField = pFormatField->GetTextField(); + if(bBuildTmpLst) + maTmpLst.insert(pTextField); + else + { + SwNodeIndex aIdx(pTextField->GetTextNode()); + std::unique_ptr<SetGetExpField> pNew(new SetGetExpField(aIdx, pTextField)); + mpSrtLst->insert(std::move(pNew)); + } + } +} + +SwInputFieldList::~SwInputFieldList() +{ +} + +size_t SwInputFieldList::Count() const +{ + return mpSrtLst->size(); +} + +// get field from list in sorted order +SwField* SwInputFieldList::GetField(size_t nId) +{ + const SwTextField* pTextField = (*mpSrtLst)[ nId ]->GetTextField(); + OSL_ENSURE( pTextField, "no TextField" ); + return const_cast<SwField*>(pTextField->GetFormatField().GetField()); +} + +/// save cursor +void SwInputFieldList::PushCursor() +{ + mpSh->Push(); + mpSh->ClearMark(); +} + +/// get cursor +void SwInputFieldList::PopCursor() +{ + mpSh->Pop(SwCursorShell::PopMode::DeleteCurrent); +} + +/// go to position of a field +void SwInputFieldList::GotoFieldPos(size_t nId) +{ + mpSh->StartAllAction(); + (*mpSrtLst)[ nId ]->GetPosOfContent( *mpSh->GetCursor()->GetPoint() ); + mpSh->EndAllAction(); +} + +/** Compare TmpLst with current fields. + * + * All new ones are added to SortList so that they can be updated. + * For text blocks: update only input fields. + * + * @return true if not empty + */ +bool SwInputFieldList::BuildSortLst() +{ + const SwFieldTypes& rFieldTypes = *mpSh->GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + const size_t nSize = rFieldTypes.size(); + + // iterate over all types + std::vector<SwFormatField*> vFields; + for(size_t i = 0; i < nSize; ++i) + { + SwFieldType* pFieldType = rFieldTypes[ i ].get(); + const SwFieldIds nType = pFieldType->Which(); + if(SwFieldIds::SetExp == nType || SwFieldIds::Input == nType) + pFieldType->GatherFields(vFields); + } + for(auto pFormatField: vFields) + { + auto pSetExpField = dynamic_cast<SwSetExpField*>(pFormatField->GetField()); + if(pSetExpField && !pSetExpField->GetInputFlag()) + continue; + const SwTextField* pTextField = pFormatField->GetTextField(); + // not in TempList, thus add to SortList + auto it = maTmpLst.find(pTextField); + if(maTmpLst.end() == it) + { + SwNodeIndex aIdx(pTextField->GetTextNode()); + std::unique_ptr<SetGetExpField> pNew(new SetGetExpField(aIdx, pTextField )); + mpSrtLst->insert(std::move(pNew)); + } + else + maTmpLst.erase(it); + } + + // the pointers are not needed anymore + maTmpLst.clear(); + return !mpSrtLst->empty(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/macrofld.cxx b/sw/source/core/fields/macrofld.cxx new file mode 100644 index 000000000..2847ecbb1 --- /dev/null +++ b/sw/source/core/fields/macrofld.cxx @@ -0,0 +1,222 @@ +/* -*- 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 <doc.hxx> +#include <docufld.hxx> +#include <unofldmid.h> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +SwMacroFieldType::SwMacroFieldType(SwDoc* pDocument) + : SwFieldType( SwFieldIds::Macro ), + m_pDoc(pDocument) +{ +} + +std::unique_ptr<SwFieldType> SwMacroFieldType::Copy() const +{ + return std::make_unique<SwMacroFieldType>(m_pDoc); +} + +SwMacroField::SwMacroField(SwMacroFieldType* pInitType, + const OUString& rLibAndName, const OUString& rText) : + SwField(pInitType), m_aMacro(rLibAndName), m_aText(rText), m_bIsScriptURL(false) +{ + m_bIsScriptURL = isScriptURL(m_aMacro); +} + +OUString SwMacroField::ExpandImpl(SwRootFrame const*const) const +{ + return m_aText ; +} + +std::unique_ptr<SwField> SwMacroField::Copy() const +{ + return std::make_unique<SwMacroField>(static_cast<SwMacroFieldType*>(GetTyp()), m_aMacro, m_aText); +} + +OUString SwMacroField::GetFieldName() const +{ + return GetTyp()->GetName() + " " + m_aMacro; +} + +OUString SwMacroField::GetLibName() const +{ + // if it is a Scripting Framework macro return an empty string + if (m_bIsScriptURL) + { + return OUString(); + } + + if (!m_aMacro.isEmpty()) + { + sal_Int32 nPos = m_aMacro.getLength(); + + for (sal_Int32 i = 0; i < 3 && nPos > 0; i++) + while (m_aMacro[--nPos] != '.' && nPos > 0) ; + + return m_aMacro.copy(0, nPos); + } + + OSL_FAIL("No LibName"); + return OUString(); +} + +OUString SwMacroField::GetMacroName() const +{ + if (!m_aMacro.isEmpty()) + { + if (m_bIsScriptURL) + { + return m_aMacro; + } + else + { + sal_Int32 nPos = m_aMacro.getLength(); + + for (sal_Int32 i = 0; i < 3 && nPos > 0; i++) + while (m_aMacro[--nPos] != '.' && nPos > 0) ; + + return m_aMacro.copy( ++nPos ); + } + } + + OSL_FAIL("No MacroName"); + return OUString(); +} + +SvxMacro SwMacroField::GetSvxMacro() const +{ + if (m_bIsScriptURL) + { + return SvxMacro(m_aMacro, OUString(), EXTENDED_STYPE); + } + else + { + return SvxMacro(GetMacroName(), GetLibName(), STARBASIC); + } +} + +/// LibName and MacroName +void SwMacroField::SetPar1(const OUString& rStr) +{ + m_aMacro = rStr; + m_bIsScriptURL = isScriptURL(m_aMacro); +} + +/// Get macro +OUString SwMacroField::GetPar1() const +{ + return m_aMacro; +} + +/// set macro text +void SwMacroField::SetPar2(const OUString& rStr) +{ + m_aText = rStr; +} + +/// get macro text +OUString SwMacroField::GetPar2() const +{ + return m_aText; +} + +bool SwMacroField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= GetMacroName(); + break; + case FIELD_PROP_PAR2: + rAny <<= m_aText; + break; + case FIELD_PROP_PAR3: + rAny <<= GetLibName(); + break; + case FIELD_PROP_PAR4: + rAny <<= m_bIsScriptURL ? GetMacroName() : OUString(); + break; + default: + assert(false); + } + return true; +} + +bool SwMacroField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + OUString sTmp; + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= sTmp; + CreateMacroString( m_aMacro, sTmp, GetLibName()); + break; + case FIELD_PROP_PAR2: + rAny >>= m_aText; + break; + case FIELD_PROP_PAR3: + rAny >>= sTmp; + CreateMacroString(m_aMacro, GetMacroName(), sTmp ); + break; + case FIELD_PROP_PAR4: + rAny >>= m_aMacro; + m_bIsScriptURL = isScriptURL(m_aMacro); + break; + default: + assert(false); + } + + return true; +} + +/// create an internally used macro name from the library and macro name parts +void SwMacroField::CreateMacroString( + OUString& rMacro, + const OUString& rMacroName, + const OUString& rLibraryName ) +{ + // concatenate library and name; use dot only if both strings have content + rMacro = rLibraryName; + if ( !rLibraryName.isEmpty() && !rMacroName.isEmpty() ) + rMacro += "."; + rMacro += rMacroName; +} + +bool SwMacroField::isScriptURL( const OUString& str ) +{ + try + { + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + uno::Reference<uri::XUriReferenceFactory> xFactory = uri::UriReferenceFactory::create(xContext); + uno::Reference<uri::XVndSunStarScriptUrl> xUrl(xFactory->parse(str), uno::UNO_QUERY); + return xUrl.is(); + } + catch (...) + { + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/postithelper.cxx b/sw/source/core/fields/postithelper.cxx new file mode 100644 index 000000000..614d73cf2 --- /dev/null +++ b/sw/source/core/fields/postithelper.cxx @@ -0,0 +1,253 @@ +/* -*- 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 <postithelper.hxx> +#include <PostItMgr.hxx> +#include <AnnotationWin.hxx> + +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <ndtxt.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <redline.hxx> +#include <scriptinfo.hxx> +#include <calbck.hxx> +#include <IMark.hxx> +#include <sortedobjs.hxx> +#include <anchoredobject.hxx> +#include <fmtanchr.hxx> + +class Point; + +namespace +{ +/// Checks if pAnnotationMark covers exactly rAnchorPos (the comment anchor). +bool AnnotationMarkCoversCommentAnchor(const sw::mark::IMark* pAnnotationMark, + const SwPosition& rAnchorPos) +{ + if (!pAnnotationMark) + { + return false; + } + + const SwPosition& rMarkStart = pAnnotationMark->GetMarkStart(); + const SwPosition& rMarkEnd = pAnnotationMark->GetMarkEnd(); + + if (rMarkStart != rAnchorPos) + { + // This can be the as-char case: the comment placeholder character is exactly between the + // annotation mark start and end. + SwPosition aPosition(rMarkStart); + ++aPosition.nContent; + if (aPosition != rAnchorPos) + { + return false; + } + + ++aPosition.nContent; + if (aPosition != rMarkEnd) + { + return false; + } + + return true; + } + + if (rMarkStart.nNode != rMarkEnd.nNode) + { + return false; + } + + return rMarkEnd.nContent.GetIndex() == rMarkStart.nContent.GetIndex() + 1; +} + +/** + * Finds the first draw object of rTextFrame which has the same anchor position as the start of + * rAnnotationMark. + */ +SwAnchoredObject* GetAnchoredObjectOfAnnotationMark(const sw::mark::IMark& rAnnotationMark, + const SwTextFrame& rTextFrame) +{ + const SwSortedObjs* pAnchored = rTextFrame.GetDrawObjs(); + if (!pAnchored) + { + return nullptr; + } + + for (SwAnchoredObject* pObject : *pAnchored) + { + SwFrameFormat& rFrameFormat = pObject->GetFrameFormat(); + const SwPosition* pFrameAnchor = rFrameFormat.GetAnchor().GetContentAnchor(); + if (!pFrameAnchor) + { + continue; + } + + if (rAnnotationMark.GetMarkStart() == *pFrameAnchor) + { + return pObject; + } + } + + return nullptr; +} +} + +SwPostItHelper::SwLayoutStatus SwPostItHelper::getLayoutInfos( + SwLayoutInfo& o_rInfo, + const SwPosition& rAnchorPos, + const sw::mark::IMark* pAnnotationMark ) +{ + SwLayoutStatus aRet = INVISIBLE; + SwTextNode* pTextNode = rAnchorPos.nNode.GetNode().GetTextNode(); + if ( pTextNode == nullptr ) + return aRet; + + SwIterator<SwTextFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pTextNode); + for( SwTextFrame* pTextFrame = aIter.First(); pTextFrame != nullptr; pTextFrame = aIter.Next() ) + { + if( !pTextFrame->IsFollow() ) + { + pTextFrame = pTextFrame->GetFrameAtPos( rAnchorPos ); + SwPageFrame *pPage = pTextFrame ? pTextFrame->FindPageFrame() : nullptr; + if ( pPage != nullptr && !pPage->IsInvalid() && !pPage->IsInvalidFly() ) + { + aRet = VISIBLE; + + o_rInfo.mpAnchorFrame = pTextFrame; + { + DisableCallbackAction a(*pTextFrame->getRootFrame()); + bool bPositionFromCommentAnchor = true; + if (AnnotationMarkCoversCommentAnchor(pAnnotationMark, rAnchorPos)) + { + SwAnchoredObject* pFrame + = GetAnchoredObjectOfAnnotationMark(*pAnnotationMark, *pTextFrame); + if (pFrame) + { + o_rInfo.mPosition = pFrame->GetObjRect(); + bPositionFromCommentAnchor = false; + } + } + if (bPositionFromCommentAnchor) + { + pTextFrame->GetCharRect(o_rInfo.mPosition, rAnchorPos, nullptr, false); + } + o_rInfo.mPositionFromCommentAnchor = bPositionFromCommentAnchor; + } + if (pAnnotationMark != nullptr) + { + const SwPosition& rAnnotationStartPos = pAnnotationMark->GetMarkStart(); + o_rInfo.mnStartNodeIdx = rAnnotationStartPos.nNode.GetIndex(); + o_rInfo.mnStartContent = rAnnotationStartPos.nContent.GetIndex(); + } + else + { + o_rInfo.mnStartNodeIdx = 0; + o_rInfo.mnStartContent = -1; + } + o_rInfo.mPageFrame = pPage->getFrameArea(); + o_rInfo.mPagePrtArea = pPage->getFramePrintArea(); + o_rInfo.mPagePrtArea.Pos() += o_rInfo.mPageFrame.Pos(); + o_rInfo.mnPageNumber = pPage->GetPhyPageNum(); + o_rInfo.meSidebarPosition = pPage->SidebarPosition(); + o_rInfo.mRedlineAuthor = 0; + + const IDocumentRedlineAccess& rIDRA = pTextNode->getIDocumentRedlineAccess(); + if( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) ) + { + const SwRangeRedline* pRedline = rIDRA.GetRedline( rAnchorPos, nullptr ); + if( pRedline ) + { + if( RedlineType::Insert == pRedline->GetType() ) + aRet = INSERTED; + else if( RedlineType::Delete == pRedline->GetType() ) + aRet = DELETED; + o_rInfo.mRedlineAuthor = pRedline->GetAuthor(); + } + } + } + } + } + + return ( (aRet==VISIBLE) && SwScriptInfo::IsInHiddenRange( *pTextNode , rAnchorPos.nContent.GetIndex()) ) + ? HIDDEN + : aRet; +} + +long SwPostItHelper::getLayoutHeight( const SwRootFrame* pRoot ) +{ + long nRet = pRoot ? pRoot->getFrameArea().Height() : 0; + return nRet; +} + +void SwPostItHelper::setSidebarChanged( SwRootFrame* pRoot, bool bBrowseMode ) +{ + if( pRoot ) + { + pRoot->SetSidebarChanged(); + if( bBrowseMode ) + pRoot->InvalidateBrowseWidth(); + } +} + +unsigned long SwPostItHelper::getPageInfo( SwRect& rPageFrame, const SwRootFrame* pRoot, const Point& rPoint ) +{ + unsigned long nRet = 0; + const SwFrame* pPage = pRoot->GetPageAtPos( rPoint, nullptr, true ); + if( pPage ) + { + nRet = pPage->GetPhyPageNum(); + rPageFrame = pPage->getFrameArea(); + } + return nRet; +} + +SwPosition SwAnnotationItem::GetAnchorPosition() const +{ + SwTextField* pTextField = mrFormatField.GetTextField(); + SwTextNode* pTextNode = pTextField->GetpTextNode(); + + SwPosition aPos( *pTextNode ); + aPos.nContent.Assign( pTextNode, pTextField->GetStart() ); + return aPos; +} + +bool SwAnnotationItem::UseElement(SwRootFrame const& rLayout, + IDocumentRedlineAccess const& rIDRA) +{ + return mrFormatField.IsFieldInDoc() + && (!rLayout.IsHideRedlines() + || !sw::IsFieldDeletedInModel(rIDRA, *mrFormatField.GetTextField())); +} + +VclPtr<sw::annotation::SwAnnotationWin> SwAnnotationItem::GetSidebarWindow( + SwEditWin& rEditWin, + SwPostItMgr& aMgr) +{ + return VclPtr<sw::annotation::SwAnnotationWin>::Create( rEditWin, + aMgr, + *this, + &mrFormatField ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/reffld.cxx b/sw/source/core/fields/reffld.cxx new file mode 100644 index 000000000..db2cdc3bf --- /dev/null +++ b/sw/source/core/fields/reffld.cxx @@ -0,0 +1,1462 @@ +/* -*- 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 <com/sun/star/text/ReferenceFieldPart.hpp> +#include <com/sun/star/text/ReferenceFieldSource.hpp> +#include <o3tl/unreachable.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/charclass.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <pam.hxx> +#include <cntfrm.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <modeltoviewhelper.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <txtftn.hxx> +#include <fmtrfmrk.hxx> +#include <txtrfmrk.hxx> +#include <fmtftn.hxx> +#include <ndtxt.hxx> +#include <chpfld.hxx> +#include <reffld.hxx> +#include <expfld.hxx> +#include <txtfrm.hxx> +#include <flyfrm.hxx> +#include <pagedesc.hxx> +#include <IMark.hxx> +#include <crossrefbookmark.hxx> +#include <ftnidx.hxx> +#include <viewsh.hxx> +#include <unofldmid.h> +#include <SwStyleNameMapper.hxx> +#include <shellres.hxx> +#include <poolfmt.hxx> +#include <strings.hrc> +#include <numrule.hxx> +#include <SwNodeNum.hxx> +#include <calbck.hxx> + +#include <cstddef> +#include <memory> +#include <vector> +#include <set> +#include <map> +#include <algorithm> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::lang; + +static std::pair<OUString, bool> MakeRefNumStr(SwRootFrame const* pLayout, + const SwTextNode& rTextNodeOfField, + const SwTextNode& rTextNodeOfReferencedItem, + sal_uInt32 nRefNumFormat); + +static void lcl_GetLayTree( const SwFrame* pFrame, std::vector<const SwFrame*>& rArr ) +{ + while( pFrame ) + { + if( pFrame->IsBodyFrame() ) // unspectacular + pFrame = pFrame->GetUpper(); + else + { + rArr.push_back( pFrame ); + + // this is the last page + if( pFrame->IsPageFrame() ) + break; + + if( pFrame->IsFlyFrame() ) + pFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + else + pFrame = pFrame->GetUpper(); + } + } +} + +bool IsFrameBehind( const SwTextNode& rMyNd, sal_Int32 nMySttPos, + const SwTextNode& rBehindNd, sal_Int32 nSttPos ) +{ + const SwTextFrame * pMyFrame = static_cast<SwTextFrame*>(rMyNd.getLayoutFrame( + rMyNd.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)); + const SwTextFrame * pFrame = static_cast<SwTextFrame*>(rBehindNd.getLayoutFrame( + rBehindNd.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)); + + if( !pFrame || !pMyFrame) + return false; + + TextFrameIndex const nMySttPosIndex(pMyFrame->MapModelToView(&rMyNd, nMySttPos)); + TextFrameIndex const nSttPosIndex(pFrame->MapModelToView(&rBehindNd, nSttPos)); + while (pFrame && !pFrame->IsInside(nSttPosIndex)) + pFrame = pFrame->GetFollow(); + while (pMyFrame && !pMyFrame->IsInside(nMySttPosIndex)) + pMyFrame = pMyFrame->GetFollow(); + + if( !pFrame || !pMyFrame || pFrame == pMyFrame ) + return false; + + std::vector<const SwFrame*> aRefArr, aArr; + ::lcl_GetLayTree( pFrame, aRefArr ); + ::lcl_GetLayTree( pMyFrame, aArr ); + + size_t nRefCnt = aRefArr.size() - 1, nCnt = aArr.size() - 1; + bool bVert = false; + bool bR2L = false; + + // Loop as long as a frame does not equal? + while( nRefCnt && nCnt && aRefArr[ nRefCnt ] == aArr[ nCnt ] ) + { + const SwFrame* pTmpFrame = aArr[ nCnt ]; + bVert = pTmpFrame->IsVertical(); + bR2L = pTmpFrame->IsRightToLeft(); + --nCnt; + --nRefCnt; + } + + // If a counter overflows? + if( aRefArr[ nRefCnt ] == aArr[ nCnt ] ) + { + if( nCnt ) + --nCnt; + else + --nRefCnt; + } + + const SwFrame* pRefFrame = aRefArr[ nRefCnt ]; + const SwFrame* pFieldFrame = aArr[ nCnt ]; + + // different frames, check their Y-/X-position + bool bRefIsLower = false; + if( ( SwFrameType::Column | SwFrameType::Cell ) & pFieldFrame->GetType() || + ( SwFrameType::Column | SwFrameType::Cell ) & pRefFrame->GetType() ) + { + if( pFieldFrame->GetType() == pRefFrame->GetType() ) + { + // here, the X-pos is more important + if( bVert ) + { + if( bR2L ) + bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || + ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && + pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() ); + else + bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || + ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && + pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() ); + } + else if( bR2L ) + bRefIsLower = pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() || + ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && + pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); + else + bRefIsLower = pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() || + ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && + pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); + pRefFrame = nullptr; + } + else if( ( SwFrameType::Column | SwFrameType::Cell ) & pFieldFrame->GetType() ) + pFieldFrame = aArr[ nCnt - 1 ]; + else + pRefFrame = aRefArr[ nRefCnt - 1 ]; + } + + if( pRefFrame ) // misuse as flag + { + if( bVert ) + { + if( bR2L ) + bRefIsLower = pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() || + ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && + pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); + else + bRefIsLower = pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() || + ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && + pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); + } + else if( bR2L ) + bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || + ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && + pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() ); + else + bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || + ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && + pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() ); + } + return bRefIsLower; +} + +// tdf#115319 create alternative reference formats, if the user asked for it +// (ReferenceFieldLanguage attribute of the reference field is not empty), and +// language of the text and ReferenceFieldLanguage are the same. +// Right now only HUNGARIAN seems to need this (as in the related issue, +// the reversed caption order in autocaption, solved by #i61007#) +static void lcl_formatReferenceLanguage( OUString& rRefText, + bool bClosingParenthesis, LanguageType eLang, + const OUString& rReferenceLanguage) +{ + if (eLang != LANGUAGE_HUNGARIAN || (rReferenceLanguage != "hu" && rReferenceLanguage != "Hu")) + return; + + // Add Hungarian definitive article (a/az) before references, + // similar to \aref, \apageref etc. of LaTeX Babel package. + // + // for example: + // + // "az 1. oldalon" ("on page 1"), but + // "a 2. oldalon" ("on page 2") + // "a fentebbi", "az alábbi" (above/below) + // "a Lorem", "az Ipsum" + // + // Support following numberings of EU publications: + // + // 1., 1a., a), (1), (1a), iii., III., IA. + // + // (http://publications.europa.eu/code/hu/hu-120700.htm, + // http://publications.europa.eu/code/hu/hu-4100600.htm) + + LanguageTag aLanguageTag(eLang); + CharClass aCharClass( aLanguageTag ); + sal_Int32 nLen = rRefText.getLength(); + sal_Int32 i; + // substring of rRefText starting with letter or number + OUString sNumbering; + // is article "az"? + bool bArticleAz = false; + // is numbering a number? + bool bNum = false; + + // search first member of the numbering (numbers or letters) + for (i=0; i<nLen && (sNumbering.isEmpty() || + ((bNum && aCharClass.isDigit(rRefText, i)) || + (!bNum && aCharClass.isLetter(rRefText, i)))); ++i) + { + // start of numbering within the field text + if (sNumbering.isEmpty() && aCharClass.isLetterNumeric(rRefText, i)) { + sNumbering = rRefText.copy(i); + bNum = aCharClass.isDigit(rRefText, i); + } + } + + // length of numbering + nLen = i - (rRefText.getLength() - sNumbering.getLength()); + + if (bNum) + { + // az 1, 1000, 1000000, 1000000000... + // az 5, 50, 500... + if ((sNumbering.startsWith("1") && (nLen == 1 || nLen == 4 || nLen == 7 || nLen == 10)) || + sNumbering.startsWith("5")) + bArticleAz = true; + } + else if (nLen == 1 && sNumbering[0] < 128) + { + // ASCII 1-letter numbering + // az a), e), f) ... x) + // az i., v. (but, a x.) + static const OUString sLettersStartingWithVowels = "aefilmnorsuxyAEFILMNORSUXY"; + if (sLettersStartingWithVowels.indexOf(sNumbering[0]) != -1) + { + // x), X) are letters, but x. and X. etc. are Roman numbers + if (bClosingParenthesis || + (sNumbering[0] != 'x' && sNumbering[0] != 'X')) + bArticleAz = true; + } else if ((sNumbering[0] == 'v' || sNumbering[0] == 'V') && !bClosingParenthesis) + // v), V) are letters, but v. and V. are Roman numbers + bArticleAz = true; + } + else + { + static const sal_Unicode sVowelsWithDiacritic[] = { + 0x00E1, 0x00C1, 0x00E9, 0x00C9, 0x00ED, 0x00CD, + 0x00F3, 0x00D3, 0x00F6, 0x00D6, 0x0151, 0x0150, + 0x00FA, 0x00DA, 0x00FC, 0x00DC, 0x0171, 0x0170, 0 }; + static OUString sVowels = OUStringLiteral("aAeEiIoOuU") + sVowelsWithDiacritic; + + // handle more than 1-letter long Roman numbers and + // their possible combinations with letters: + // az IA, a IIB, a IIIC., az Ia, a IIb., a iiic), az LVIII. szonett + bool bRomanNumber = false; + if (nLen > 1 && (nLen + 1 >= sNumbering.getLength() || sNumbering[nLen] == '.')) + { + sal_Unicode last = sNumbering[nLen - 1]; + OUString sNumberingTrim; + if ((last >= 'A' && last < 'I') || (last >= 'a' && last < 'i')) + sNumberingTrim = sNumbering.copy(0, nLen - 1); + else + sNumberingTrim = sNumbering.copy(0, nLen); + bRomanNumber = + sNumberingTrim.replaceAll("i", "").replaceAll("v", "").replaceAll("x", "").replaceAll("l", "").replaceAll("c", "").isEmpty() || + sNumberingTrim.replaceAll("I", "").replaceAll("V", "").replaceAll("X", "").replaceAll("L", "").replaceAll("C", "").isEmpty(); + } + + if ( + // Roman number and a letter optionally + ( bRomanNumber && ( + (sNumbering[0] == 'i' && sNumbering[1] != 'i' && sNumbering[1] != 'v' && sNumbering[1] != 'x') || + (sNumbering[0] == 'I' && sNumbering[1] != 'I' && sNumbering[1] != 'V' && sNumbering[1] != 'X') || + (sNumbering[0] == 'v' && sNumbering[1] != 'i') || + (sNumbering[0] == 'V' && sNumbering[1] != 'I') || + (sNumbering[0] == 'l' && sNumbering[1] != 'x') || + (sNumbering[0] == 'L' && sNumbering[1] != 'X')) ) || + // a word starting with vowel (not Roman number) + ( !bRomanNumber && sVowels.indexOf(sNumbering[0]) != -1)) + { + bArticleAz = true; + } + } + // not a title text starting already with a definitive article + if ( !sNumbering.startsWith("A ") && !sNumbering.startsWith("Az ") && + !sNumbering.startsWith("a ") && !sNumbering.startsWith("az ") ) + { + // lowercase, if rReferenceLanguage == "hu", not "Hu" + OUString sArticle; + + if ( rReferenceLanguage == "hu" ) + sArticle = "a"; + else + sArticle = "A"; + + if (bArticleAz) + sArticle += "z"; + + rRefText = sArticle + " " + rRefText; + } +} + +/// get references +SwGetRefField::SwGetRefField( SwGetRefFieldType* pFieldType, + const OUString& rSetRef, const OUString& rSetReferenceLanguage, sal_uInt16 nSubTyp, + sal_uInt16 nSequenceNo, sal_uLong nFormat ) + : SwField( pFieldType, nFormat ), + m_sSetRefName( rSetRef ), + m_sSetReferenceLanguage( rSetReferenceLanguage ), + m_nSubType( nSubTyp ), + m_nSeqNo( nSequenceNo ) +{ +} + +SwGetRefField::~SwGetRefField() +{ +} + +OUString SwGetRefField::GetDescription() const +{ + return SwResId(STR_REFERENCE); +} + +sal_uInt16 SwGetRefField::GetSubType() const +{ + return m_nSubType; +} + +void SwGetRefField::SetSubType( sal_uInt16 n ) +{ + m_nSubType = n; +} + +// #i81002# +bool SwGetRefField::IsRefToHeadingCrossRefBookmark() const +{ + return GetSubType() == REF_BOOKMARK && + ::sw::mark::CrossRefHeadingBookmark::IsLegalName(m_sSetRefName); +} + +bool SwGetRefField::IsRefToNumItemCrossRefBookmark() const +{ + return GetSubType() == REF_BOOKMARK && + ::sw::mark::CrossRefNumItemBookmark::IsLegalName(m_sSetRefName); +} + +const SwTextNode* SwGetRefField::GetReferencedTextNode() const +{ + SwGetRefFieldType *pTyp = dynamic_cast<SwGetRefFieldType*>(GetTyp()); + if (!pTyp) + return nullptr; + sal_Int32 nDummy = -1; + return SwGetRefFieldType::FindAnchor( pTyp->GetDoc(), m_sSetRefName, m_nSubType, m_nSeqNo, &nDummy ); +} + +// #i85090# +OUString SwGetRefField::GetExpandedTextOfReferencedTextNode( + SwRootFrame const& rLayout) const +{ + const SwTextNode* pReferencedTextNode( GetReferencedTextNode() ); + return pReferencedTextNode + ? sw::GetExpandTextMerged(&rLayout, *pReferencedTextNode, true, false, ExpandMode(0)) + : OUString(); +} + +void SwGetRefField::SetExpand( const OUString& rStr ) +{ + m_sText = rStr; + m_sTextRLHidden = rStr; +} + +OUString SwGetRefField::ExpandImpl(SwRootFrame const*const pLayout) const +{ + return pLayout && pLayout->IsHideRedlines() ? m_sTextRLHidden : m_sText; +} + +OUString SwGetRefField::GetFieldName() const +{ + const OUString aName = GetTyp()->GetName(); + if ( !aName.isEmpty() || !m_sSetRefName.isEmpty() ) + { + return aName + " " + m_sSetRefName; + } + return ExpandImpl(nullptr); +} + + +static void FilterText(OUString & rText, LanguageType const eLang, + OUString const& rSetReferenceLanguage) +{ + // remove all special characters (replace them with blanks) + if (!rText.isEmpty()) + { + rText = rText.replaceAll(u"\u00ad", ""); + OUStringBuffer aBuf(rText); + const sal_Int32 l = aBuf.getLength(); + for (sal_Int32 i = 0; i < l; ++i) + { + if (aBuf[i] < ' ') + { + aBuf[i] = ' '; + } + else if (aBuf[i] == 0x2011) + { + aBuf[i] = '-'; + } + } + rText = aBuf.makeStringAndClear(); + if (!rSetReferenceLanguage.isEmpty()) + { + lcl_formatReferenceLanguage(rText, false, eLang, rSetReferenceLanguage); + } + } +} + +// #i81002# - parameter <pFieldTextAttr> added +void SwGetRefField::UpdateField( const SwTextField* pFieldTextAttr ) +{ + m_sText.clear(); + m_sTextRLHidden.clear(); + + SwDoc* pDoc = static_cast<SwGetRefFieldType*>(GetTyp())->GetDoc(); + // finding the reference target (the number) + sal_Int32 nNumStart = -1; + sal_Int32 nNumEnd = -1; + SwTextNode* pTextNd = SwGetRefFieldType::FindAnchor( + pDoc, m_sSetRefName, m_nSubType, m_nSeqNo, &nNumStart, &nNumEnd + ); + // not found? + if ( !pTextNd ) + { + m_sText = SwViewShell::GetShellRes()->aGetRefField_RefItemNotFound; + m_sTextRLHidden = m_sText; + return ; + } + + SwRootFrame const* pLayout(nullptr); + SwRootFrame const* pLayoutRLHidden(nullptr); + for (SwRootFrame const*const pLay : pDoc->GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayoutRLHidden = pLay; + } + else + { + pLayout = pLay; + } + } + + // where is the category name (e.g. "Illustration")? + const OUString aText = pTextNd->GetText(); + const sal_Int32 nCatStart = aText.indexOf(m_sSetRefName); + const bool bHasCat = nCatStart>=0; + const sal_Int32 nCatEnd = bHasCat ? nCatStart + m_sSetRefName.getLength() : -1; + + // length of the referenced text + const sal_Int32 nLen = aText.getLength(); + + // which format? + switch( GetFormat() ) + { + case REF_CONTENT: + case REF_ONLYNUMBER: + case REF_ONLYCAPTION: + case REF_ONLYSEQNO: + { + // needed part of Text + sal_Int32 nStart; + sal_Int32 nEnd; + + switch( m_nSubType ) + { + case REF_SEQUENCEFLD: + + switch( GetFormat() ) + { + // "Category and Number" + case REF_ONLYNUMBER: + if (bHasCat) { + nStart = std::min(nNumStart, nCatStart); + nEnd = std::max(nNumEnd, nCatEnd); + } else { + nStart = nNumStart; + nEnd = nNumEnd; + } + break; + + // "Caption Text" + case REF_ONLYCAPTION: { + // next alphanumeric character after category+number + if (const SwTextAttr* const pTextAttr = + pTextNd->GetTextAttrForCharAt(nNumStart, RES_TXTATR_FIELD) + ) { + // start searching from nFrom + const sal_Int32 nFrom = bHasCat + ? std::max(nNumStart + 1, nCatEnd) + : nNumStart + 1; + nStart = SwGetExpField::GetReferenceTextPos( pTextAttr->GetFormatField(), *pDoc, nFrom ); + } else { + nStart = bHasCat ? std::max(nNumEnd, nCatEnd) : nNumEnd; + } + nEnd = nLen; + break; + } + + // "Numbering" + case REF_ONLYSEQNO: + nStart = nNumStart; + nEnd = std::min(nStart + 1, nLen); + break; + + // "Reference" (whole Text) + case REF_CONTENT: + nStart = 0; + nEnd = nLen; + break; + + default: + O3TL_UNREACHABLE; + } + break; + + case REF_BOOKMARK: + nStart = nNumStart; + // text is spread across multiple nodes - get whole text or only until end of node? + nEnd = nNumEnd<0 ? nLen : nNumEnd; + break; + + case REF_OUTLINE: + nStart = nNumStart; + nEnd = nNumEnd; + break; + + case REF_FOOTNOTE: + case REF_ENDNOTE: + // get number or numString + for( size_t i = 0; i < pDoc->GetFootnoteIdxs().size(); ++i ) + { + SwTextFootnote* const pFootnoteIdx = pDoc->GetFootnoteIdxs()[i]; + if( m_nSeqNo == pFootnoteIdx->GetSeqRefNo() ) + { + m_sText = pFootnoteIdx->GetFootnote().GetViewNumStr(*pDoc, nullptr); + m_sTextRLHidden = pFootnoteIdx->GetFootnote().GetViewNumStr(*pDoc, pLayoutRLHidden); + if (!m_sSetReferenceLanguage.isEmpty()) + { + lcl_formatReferenceLanguage(m_sText, false, GetLanguage(), m_sSetReferenceLanguage); + lcl_formatReferenceLanguage(m_sTextRLHidden, false, GetLanguage(), m_sSetReferenceLanguage); + } + break; + } + } + return; + + case REF_SETREFATTR: + nStart = nNumStart; + nEnd = nNumEnd; + break; + + default: + O3TL_UNREACHABLE; + } + + if( nStart != nEnd ) // a section? + { + m_sText = pTextNd->GetExpandText(pLayout, nStart, nEnd - nStart, false, false, false, ExpandMode(0)); + if (m_nSubType == REF_OUTLINE + || (m_nSubType == REF_SEQUENCEFLD && REF_CONTENT == GetFormat())) + { + m_sTextRLHidden = sw::GetExpandTextMerged( + pLayoutRLHidden, *pTextNd, false, false, ExpandMode(0)); + } + else + { + m_sTextRLHidden = pTextNd->GetExpandText(pLayoutRLHidden, + nStart, nEnd - nStart, false, false, false, ExpandMode::HideDeletions); + } + FilterText(m_sText, GetLanguage(), m_sSetReferenceLanguage); + FilterText(m_sTextRLHidden, GetLanguage(), m_sSetReferenceLanguage); + } + } + break; + + case REF_PAGE: + case REF_PAGE_PGDESC: + { + auto const func = + [this, pTextNd, nNumStart](OUString & rText, SwRootFrame const*const pLay) + { + SwTextFrame const* pFrame = static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLay, nullptr, nullptr)); + SwTextFrame const*const pSave = pFrame; + if (pFrame) + { + TextFrameIndex const nNumStartIndex(pFrame->MapModelToView(pTextNd, nNumStart)); + while (pFrame && !pFrame->IsInside(nNumStartIndex)) + pFrame = pFrame->GetFollow(); + } + + if( pFrame || nullptr != ( pFrame = pSave )) + { + sal_uInt16 nPageNo = pFrame->GetVirtPageNum(); + const SwPageFrame *pPage; + if( REF_PAGE_PGDESC == GetFormat() && + nullptr != ( pPage = pFrame->FindPageFrame() ) && + pPage->GetPageDesc() ) + { + rText = pPage->GetPageDesc()->GetNumType().GetNumStr(nPageNo); + } + else + { + rText = OUString::number(nPageNo); + } + + if (!m_sSetReferenceLanguage.isEmpty()) + lcl_formatReferenceLanguage(rText, false, GetLanguage(), m_sSetReferenceLanguage); + } + }; + // sw_redlinehide: currently only one of these layouts will exist, + // so the getLayoutFrame will use the same frame in both cases + func(m_sText, pLayout); + func(m_sTextRLHidden, pLayoutRLHidden); + } + break; + + case REF_CHAPTER: + { + auto const func = + [this, pTextNd](OUString & rText, SwRootFrame const*const pLay) + { + // a bit tricky: search any frame + SwFrame const*const pFrame = pTextNd->getLayoutFrame(pLay); + if( pFrame ) + { + SwChapterFieldType aFieldTyp; + SwChapterField aField( &aFieldTyp, 0 ); + aField.SetLevel( MAXLEVEL - 1 ); + aField.ChangeExpansion( *pFrame, pTextNd, true ); + rText = aField.GetNumber(pLay); + + if (!m_sSetReferenceLanguage.isEmpty()) + lcl_formatReferenceLanguage(rText, false, GetLanguage(), m_sSetReferenceLanguage); + } + }; + func(m_sText, pLayout); + func(m_sTextRLHidden, pLayoutRLHidden); + } + break; + + case REF_UPDOWN: + { + // #i81002# + // simplified: use parameter <pFieldTextAttr> + if( !pFieldTextAttr || !pFieldTextAttr->GetpTextNode() ) + break; + + LanguageTag aLanguageTag( GetLanguage()); + LocaleDataWrapper aLocaleData( aLanguageTag ); + + // first a "short" test - in case both are in the same node + if( pFieldTextAttr->GetpTextNode() == pTextNd ) + { + m_sText = nNumStart < pFieldTextAttr->GetStart() + ? aLocaleData.getAboveWord() + : aLocaleData.getBelowWord(); + m_sTextRLHidden = m_sText; + break; + } + + m_sText = ::IsFrameBehind( *pFieldTextAttr->GetpTextNode(), pFieldTextAttr->GetStart(), + *pTextNd, nNumStart ) + ? aLocaleData.getAboveWord() + : aLocaleData.getBelowWord(); + + if (!m_sSetReferenceLanguage.isEmpty()) + lcl_formatReferenceLanguage(m_sText, false, GetLanguage(), m_sSetReferenceLanguage); + + m_sTextRLHidden = m_sText; + } + break; + // #i81002# + case REF_NUMBER: + case REF_NUMBER_NO_CONTEXT: + case REF_NUMBER_FULL_CONTEXT: + { + if ( pFieldTextAttr && pFieldTextAttr->GetpTextNode() ) + { + auto result = + MakeRefNumStr(pLayout, pFieldTextAttr->GetTextNode(), *pTextNd, GetFormat()); + m_sText = result.first; + // for differentiation of Roman numbers and letters in Hungarian article handling + bool bClosingParenthesis = result.second; + if (!m_sSetReferenceLanguage.isEmpty()) + { + lcl_formatReferenceLanguage(m_sText, bClosingParenthesis, GetLanguage(), m_sSetReferenceLanguage); + } + result = + MakeRefNumStr(pLayoutRLHidden, pFieldTextAttr->GetTextNode(), *pTextNd, GetFormat()); + m_sTextRLHidden = result.first; + bClosingParenthesis = result.second; + if (!m_sSetReferenceLanguage.isEmpty()) + { + lcl_formatReferenceLanguage(m_sTextRLHidden, bClosingParenthesis, GetLanguage(), m_sSetReferenceLanguage); + } + } + } + break; + + default: + OSL_FAIL("<SwGetRefField::UpdateField(..)> - unknown format type"); + } +} + +// #i81002# +static std::pair<OUString, bool> MakeRefNumStr( + SwRootFrame const*const pLayout, + const SwTextNode& i_rTextNodeOfField, + const SwTextNode& i_rTextNodeOfReferencedItem, + const sal_uInt32 nRefNumFormat) +{ + SwTextNode const& rTextNodeOfField(pLayout + ? *sw::GetParaPropsNode(*pLayout, i_rTextNodeOfField) + : i_rTextNodeOfField); + SwTextNode const& rTextNodeOfReferencedItem(pLayout + ? *sw::GetParaPropsNode(*pLayout, i_rTextNodeOfReferencedItem) + : i_rTextNodeOfReferencedItem); + if ( rTextNodeOfReferencedItem.HasNumber() && + rTextNodeOfReferencedItem.IsCountedInList() ) + { + OSL_ENSURE( rTextNodeOfReferencedItem.GetNum(pLayout), + "<SwGetRefField::MakeRefNumStr(..)> - referenced paragraph has number, but no <SwNodeNum> instance!" ); + + // Determine, up to which level the superior list labels have to be + // included - default is to include all superior list labels. + int nRestrictInclToThisLevel( 0 ); + // Determine for format REF_NUMBER the level, up to which the superior + // list labels have to be restricted, if the text node of the reference + // field and the text node of the referenced item are in the same + // document context. + if ( nRefNumFormat == REF_NUMBER && + rTextNodeOfField.FindFlyStartNode() + == rTextNodeOfReferencedItem.FindFlyStartNode() && + rTextNodeOfField.FindFootnoteStartNode() + == rTextNodeOfReferencedItem.FindFootnoteStartNode() && + rTextNodeOfField.FindHeaderStartNode() + == rTextNodeOfReferencedItem.FindHeaderStartNode() && + rTextNodeOfField.FindFooterStartNode() + == rTextNodeOfReferencedItem.FindFooterStartNode() ) + { + const SwNodeNum* pNodeNumForTextNodeOfField( nullptr ); + if ( rTextNodeOfField.HasNumber() && + rTextNodeOfField.GetNumRule() == rTextNodeOfReferencedItem.GetNumRule() ) + { + pNodeNumForTextNodeOfField = rTextNodeOfField.GetNum(pLayout); + } + else + { + pNodeNumForTextNodeOfField = + rTextNodeOfReferencedItem.GetNum(pLayout)->GetPrecedingNodeNumOf(rTextNodeOfField); + } + if ( pNodeNumForTextNodeOfField ) + { + const SwNumberTree::tNumberVector rFieldNumVec = + pNodeNumForTextNodeOfField->GetNumberVector(); + const SwNumberTree::tNumberVector rRefItemNumVec = + rTextNodeOfReferencedItem.GetNum()->GetNumberVector(); + std::size_t nLevel( 0 ); + while ( nLevel < rFieldNumVec.size() && nLevel < rRefItemNumVec.size() ) + { + if ( rRefItemNumVec[nLevel] == rFieldNumVec[nLevel] ) + { + nRestrictInclToThisLevel = nLevel + 1; + } + else + { + break; + } + ++nLevel; + } + } + } + + // Determine, if superior list labels have to be included + const bool bInclSuperiorNumLabels( + ( nRestrictInclToThisLevel < rTextNodeOfReferencedItem.GetActualListLevel() && + ( nRefNumFormat == REF_NUMBER || nRefNumFormat == REF_NUMBER_FULL_CONTEXT ) ) ); + + OSL_ENSURE( rTextNodeOfReferencedItem.GetNumRule(), + "<SwGetRefField::MakeRefNumStr(..)> - referenced numbered paragraph has no numbering rule set!" ); + return std::make_pair( + rTextNodeOfReferencedItem.GetNumRule()->MakeRefNumString( + *(rTextNodeOfReferencedItem.GetNum(pLayout)), + bInclSuperiorNumLabels, + nRestrictInclToThisLevel ), + rTextNodeOfReferencedItem.GetNumRule()->MakeNumString( + *(rTextNodeOfReferencedItem.GetNum(pLayout)), + true).endsWith(")") ); + } + + return std::make_pair(OUString(), false); +} + +std::unique_ptr<SwField> SwGetRefField::Copy() const +{ + std::unique_ptr<SwGetRefField> pField( new SwGetRefField( static_cast<SwGetRefFieldType*>(GetTyp()), + m_sSetRefName, m_sSetReferenceLanguage, m_nSubType, + m_nSeqNo, GetFormat() ) ); + pField->m_sText = m_sText; + pField->m_sTextRLHidden = m_sTextRLHidden; + return std::unique_ptr<SwField>(pField.release()); +} + +/// get reference name +OUString SwGetRefField::GetPar1() const +{ + return m_sSetRefName; +} + +/// set reference name +void SwGetRefField::SetPar1( const OUString& rName ) +{ + m_sSetRefName = rName; +} + +OUString SwGetRefField::GetPar2() const +{ + return ExpandImpl(nullptr); +} + +bool SwGetRefField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + { + sal_Int16 nPart = 0; + switch(GetFormat()) + { + case REF_PAGE : nPart = ReferenceFieldPart::PAGE ; break; + case REF_CHAPTER : nPart = ReferenceFieldPart::CHAPTER ; break; + case REF_CONTENT : nPart = ReferenceFieldPart::TEXT ; break; + case REF_UPDOWN : nPart = ReferenceFieldPart::UP_DOWN ; break; + case REF_PAGE_PGDESC: nPart = ReferenceFieldPart::PAGE_DESC ; break; + case REF_ONLYNUMBER : nPart = ReferenceFieldPart::CATEGORY_AND_NUMBER ; break; + case REF_ONLYCAPTION: nPart = ReferenceFieldPart::ONLY_CAPTION ; break; + case REF_ONLYSEQNO : nPart = ReferenceFieldPart::ONLY_SEQUENCE_NUMBER; break; + // #i81002# + case REF_NUMBER: nPart = ReferenceFieldPart::NUMBER; break; + case REF_NUMBER_NO_CONTEXT: nPart = ReferenceFieldPart::NUMBER_NO_CONTEXT; break; + case REF_NUMBER_FULL_CONTEXT: nPart = ReferenceFieldPart::NUMBER_FULL_CONTEXT; break; + } + rAny <<= nPart; + } + break; + case FIELD_PROP_USHORT2: + { + sal_Int16 nSource = 0; + switch(m_nSubType) + { + case REF_SETREFATTR : nSource = ReferenceFieldSource::REFERENCE_MARK; break; + case REF_SEQUENCEFLD: nSource = ReferenceFieldSource::SEQUENCE_FIELD; break; + case REF_BOOKMARK : nSource = ReferenceFieldSource::BOOKMARK; break; + case REF_OUTLINE : OSL_FAIL("not implemented"); break; + case REF_FOOTNOTE : nSource = ReferenceFieldSource::FOOTNOTE; break; + case REF_ENDNOTE : nSource = ReferenceFieldSource::ENDNOTE; break; + } + rAny <<= nSource; + } + break; + case FIELD_PROP_PAR1: + { + OUString sTmp(GetPar1()); + if(REF_SEQUENCEFLD == m_nSubType) + { + sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromUIName( sTmp, SwGetPoolIdFromName::TxtColl ); + switch( nPoolId ) + { + case RES_POOLCOLL_LABEL_ABB: + case RES_POOLCOLL_LABEL_TABLE: + case RES_POOLCOLL_LABEL_FRAME: + case RES_POOLCOLL_LABEL_DRAWING: + case RES_POOLCOLL_LABEL_FIGURE: + SwStyleNameMapper::FillProgName(nPoolId, sTmp) ; + break; + } + } + rAny <<= sTmp; + } + break; + case FIELD_PROP_PAR3: + rAny <<= ExpandImpl(nullptr); + break; + case FIELD_PROP_PAR4: + rAny <<= m_sSetReferenceLanguage; + break; + case FIELD_PROP_SHORT1: + rAny <<= static_cast<sal_Int16>(m_nSeqNo); + break; + default: + assert(false); + } + return true; +} + +bool SwGetRefField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_USHORT1: + { + sal_Int16 nPart = 0; + rAny >>= nPart; + switch(nPart) + { + case ReferenceFieldPart::PAGE: nPart = REF_PAGE; break; + case ReferenceFieldPart::CHAPTER: nPart = REF_CHAPTER; break; + case ReferenceFieldPart::TEXT: nPart = REF_CONTENT; break; + case ReferenceFieldPart::UP_DOWN: nPart = REF_UPDOWN; break; + case ReferenceFieldPart::PAGE_DESC: nPart = REF_PAGE_PGDESC; break; + case ReferenceFieldPart::CATEGORY_AND_NUMBER: nPart = REF_ONLYNUMBER; break; + case ReferenceFieldPart::ONLY_CAPTION: nPart = REF_ONLYCAPTION; break; + case ReferenceFieldPart::ONLY_SEQUENCE_NUMBER : nPart = REF_ONLYSEQNO; break; + // #i81002# + case ReferenceFieldPart::NUMBER: nPart = REF_NUMBER; break; + case ReferenceFieldPart::NUMBER_NO_CONTEXT: nPart = REF_NUMBER_NO_CONTEXT; break; + case ReferenceFieldPart::NUMBER_FULL_CONTEXT: nPart = REF_NUMBER_FULL_CONTEXT; break; + default: return false; + } + SetFormat(nPart); + } + break; + case FIELD_PROP_USHORT2: + { + sal_Int16 nSource = 0; + rAny >>= nSource; + switch(nSource) + { + case ReferenceFieldSource::REFERENCE_MARK : m_nSubType = REF_SETREFATTR ; break; + case ReferenceFieldSource::SEQUENCE_FIELD : + { + if(REF_SEQUENCEFLD == m_nSubType) + break; + m_nSubType = REF_SEQUENCEFLD; + ConvertProgrammaticToUIName(); + } + break; + case ReferenceFieldSource::BOOKMARK : m_nSubType = REF_BOOKMARK ; break; + case ReferenceFieldSource::FOOTNOTE : m_nSubType = REF_FOOTNOTE ; break; + case ReferenceFieldSource::ENDNOTE : m_nSubType = REF_ENDNOTE ; break; + } + } + break; + case FIELD_PROP_PAR1: + { + OUString sTmpStr; + rAny >>= sTmpStr; + SetPar1(sTmpStr); + ConvertProgrammaticToUIName(); + } + break; + case FIELD_PROP_PAR3: + { + OUString sTmpStr; + rAny >>= sTmpStr; + SetExpand( sTmpStr ); + } + break; + case FIELD_PROP_PAR4: + rAny >>= m_sSetReferenceLanguage; + break; + case FIELD_PROP_SHORT1: + { + sal_Int16 nSetSeq = 0; + rAny >>= nSetSeq; + if(nSetSeq >= 0) + m_nSeqNo = nSetSeq; + } + break; + default: + assert(false); + } + return true; +} + +void SwGetRefField::ConvertProgrammaticToUIName() +{ + if(GetTyp() && REF_SEQUENCEFLD == m_nSubType) + { + SwDoc* pDoc = static_cast<SwGetRefFieldType*>(GetTyp())->GetDoc(); + const OUString rPar1 = GetPar1(); + // don't convert when the name points to an existing field type + if(!pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, rPar1, false)) + { + sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromProgName( rPar1, SwGetPoolIdFromName::TxtColl ); + const char* pResId = nullptr; + switch( nPoolId ) + { + case RES_POOLCOLL_LABEL_ABB: + pResId = STR_POOLCOLL_LABEL_ABB; + break; + case RES_POOLCOLL_LABEL_TABLE: + pResId = STR_POOLCOLL_LABEL_TABLE; + break; + case RES_POOLCOLL_LABEL_FRAME: + pResId = STR_POOLCOLL_LABEL_FRAME; + break; + case RES_POOLCOLL_LABEL_DRAWING: + pResId = STR_POOLCOLL_LABEL_DRAWING; + break; + case RES_POOLCOLL_LABEL_FIGURE: + pResId = STR_POOLCOLL_LABEL_FIGURE; + break; + } + if (pResId) + SetPar1(SwResId(pResId)); + } + } +} + +SwGetRefFieldType::SwGetRefFieldType( SwDoc* pDc ) + : SwFieldType( SwFieldIds::GetRef ), m_pDoc( pDc ) +{} + +std::unique_ptr<SwFieldType> SwGetRefFieldType::Copy() const +{ + return std::make_unique<SwGetRefFieldType>( m_pDoc ); +} + +void SwGetRefFieldType::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + // update to all GetReference fields + if( !pNew && !pOld ) + { + std::vector<SwFormatField*> vFields; + GatherFields(vFields, false); + for(auto pFormatField: vFields) + { + // update only the GetRef fields + //JP 3.4.2001: Task 71231 - we need the correct language + SwGetRefField* pGRef = static_cast<SwGetRefField*>(pFormatField->GetField()); + const SwTextField* pTField; + if( !pGRef->GetLanguage() && + nullptr != ( pTField = pFormatField->GetTextField()) && + pTField->GetpTextNode() ) + { + pGRef->SetLanguage( pTField->GetpTextNode()->GetLang( + pTField->GetStart() ) ); + } + + // #i81002# + pGRef->UpdateField( pFormatField->GetTextField() ); + } + } + // forward to text fields, they "expand" the text + NotifyClients( pOld, pNew ); +} + +namespace sw { + +bool IsMarkHintHidden(SwRootFrame const& rLayout, + SwTextNode const& rNode, SwTextAttrEnd const& rHint) +{ + if (!rLayout.IsHideRedlines()) + { + return false; + } + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + rNode.getLayoutFrame(&rLayout))); + if (!pFrame) + { + return true; + } + sal_Int32 const*const pEnd(rHint.GetEnd()); + if (pEnd) + { + return pFrame->MapModelToView(&rNode, rHint.GetStart()) + == pFrame->MapModelToView(&rNode, *pEnd); + } + else + { + assert(rHint.HasDummyChar()); + return pFrame->MapModelToView(&rNode, rHint.GetStart()) + == pFrame->MapModelToView(&rNode, rHint.GetStart() + 1); + } +} + +} // namespace sw + +SwTextNode* SwGetRefFieldType::FindAnchor( SwDoc* pDoc, const OUString& rRefMark, + sal_uInt16 nSubType, sal_uInt16 nSeqNo, + sal_Int32* pStt, sal_Int32* pEnd, + SwRootFrame const*const pLayout) +{ + OSL_ENSURE( pStt, "Why did no one check the StartPos?" ); + + IDocumentRedlineAccess & rIDRA(pDoc->getIDocumentRedlineAccess()); + SwTextNode* pTextNd = nullptr; + switch( nSubType ) + { + case REF_SETREFATTR: + { + const SwFormatRefMark *pRef = pDoc->GetRefMark( rRefMark ); + SwTextRefMark const*const pRefMark(pRef ? pRef->GetTextRefMark() : nullptr); + if (pRefMark && (!pLayout || !sw::IsMarkHintHidden(*pLayout, + pRefMark->GetTextNode(), *pRefMark))) + { + pTextNd = const_cast<SwTextNode*>(&pRef->GetTextRefMark()->GetTextNode()); + *pStt = pRef->GetTextRefMark()->GetStart(); + if( pEnd ) + *pEnd = pRef->GetTextRefMark()->GetAnyEnd(); + } + } + break; + + case REF_SEQUENCEFLD: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetFieldType( SwFieldIds::SetExp, rRefMark, false ); + if( pFieldType && pFieldType->HasWriterListeners() && + nsSwGetSetExpType::GSE_SEQ & static_cast<SwSetExpFieldType*>(pFieldType)->GetType() ) + { + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields, false); + for(auto pFormatField: vFields) + { + SwTextField *const pTextField(pFormatField->GetTextField()); + if (pTextField && nSeqNo == + static_cast<SwSetExpField*>(pFormatField->GetField())->GetSeqNumber() + && (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsFieldDeletedInModel(rIDRA, *pTextField))) + { + pTextNd = pTextField->GetpTextNode(); + *pStt = pTextField->GetStart(); + if( pEnd ) + *pEnd = (*pStt) + 1; + break; + } + } + } + } + break; + + case REF_BOOKMARK: + { + IDocumentMarkAccess::const_iterator_t ppMark = pDoc->getIDocumentMarkAccess()->findMark(rRefMark); + if (ppMark != pDoc->getIDocumentMarkAccess()->getAllMarksEnd() + && (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsMarkHidden(*pLayout, **ppMark))) + { + const ::sw::mark::IMark* pBkmk = *ppMark; + const SwPosition* pPos = &pBkmk->GetMarkStart(); + + pTextNd = pPos->nNode.GetNode().GetTextNode(); + *pStt = pPos->nContent.GetIndex(); + if(pEnd) + { + if(!pBkmk->IsExpanded()) + { + *pEnd = *pStt; + // #i81002# + if(dynamic_cast< ::sw::mark::CrossRefBookmark const *>(pBkmk)) + { + OSL_ENSURE( pTextNd, + "<SwGetRefFieldType::FindAnchor(..)> - node marked by cross-reference bookmark isn't a text node --> crash" ); + *pEnd = pTextNd->Len(); + } + } + else if(pBkmk->GetOtherMarkPos().nNode == pBkmk->GetMarkPos().nNode) + *pEnd = pBkmk->GetMarkEnd().nContent.GetIndex(); + else + *pEnd = -1; + } + } + } + break; + + case REF_OUTLINE: + break; + + case REF_FOOTNOTE: + case REF_ENDNOTE: + { + for( auto pFootnoteIdx : pDoc->GetFootnoteIdxs() ) + if( nSeqNo == pFootnoteIdx->GetSeqRefNo() ) + { + if (pLayout && pLayout->IsHideRedlines() + && sw::IsFootnoteDeleted(rIDRA, *pFootnoteIdx)) + { + return nullptr; + } + // otherwise: the position at the start of the footnote + // will be mapped to something visible at least... + SwNodeIndex* pIdx = pFootnoteIdx->GetStartNode(); + if( pIdx ) + { + SwNodeIndex aIdx( *pIdx, 1 ); + if( nullptr == ( pTextNd = aIdx.GetNode().GetTextNode())) + pTextNd = static_cast<SwTextNode*>(pDoc->GetNodes().GoNext( &aIdx )); + } + *pStt = 0; + if( pEnd ) + *pEnd = 0; + break; + } + } + break; + } + + return pTextNd; +} + +namespace { + +struct RefIdsMap +{ +private: + OUString aName; + std::set<sal_uInt16> aIds; + std::set<sal_uInt16> aDstIds; + std::map<sal_uInt16, sal_uInt16> sequencedIds; /// ID numbers sorted by sequence number. + bool bInit; + + void Init(SwDoc& rDoc, SwDoc& rDestDoc, bool bField ); + static void GetNoteIdsFromDoc( SwDoc& rDoc, std::set<sal_uInt16> &rIds ); + void GetFieldIdsFromDoc( SwDoc& rDoc, std::set<sal_uInt16> &rIds ); + void AddId( sal_uInt16 id, sal_uInt16 seqNum ); + static sal_uInt16 GetFirstUnusedId( std::set<sal_uInt16> &rIds ); + +public: + explicit RefIdsMap( const OUString& rName ) : aName( rName ), bInit( false ) {} + + void Check( SwDoc& rDoc, SwDoc& rDestDoc, SwGetRefField& rField, bool bField ); + + const OUString& GetName() const { return aName; } +}; + +} + +/// Get a sorted list of the field IDs from a document. +/// @param[in] rDoc The document to search. +/// @param[in,out] rIds The list of IDs found in the document. +void RefIdsMap::GetFieldIdsFromDoc( SwDoc& rDoc, std::set<sal_uInt16> &rIds) +{ + SwFieldType *const pType = rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, aName, false); + if (!pType) + return; + std::vector<SwFormatField*> vFields; + pType->GatherFields(vFields); + for(const auto pF: vFields) + rIds.insert(static_cast<SwSetExpField const*>(pF->GetField())->GetSeqNumber()); +} + +/// Get a sorted list of the footnote/endnote IDs from a document. +/// @param[in] rDoc The document to search. +/// @param[in,out] rIds The list of IDs found in the document. +void RefIdsMap::GetNoteIdsFromDoc( SwDoc& rDoc, std::set<sal_uInt16> &rIds) +{ + for( auto n = rDoc.GetFootnoteIdxs().size(); n; ) + rIds.insert( rDoc.GetFootnoteIdxs()[ --n ]->GetSeqRefNo() ); +} + +/// Initialise the aIds and aDestIds collections from the source documents. +/// @param[in] rDoc The source document. +/// @param[in] rDestDoc The destination document. +/// @param[in] bField True if we're interested in all fields, false for footnotes. +void RefIdsMap::Init( SwDoc& rDoc, SwDoc& rDestDoc, bool bField ) +{ + if( bInit ) + return; + + if( bField ) + { + GetFieldIdsFromDoc( rDestDoc, aIds ); + GetFieldIdsFromDoc( rDoc, aDstIds ); + + // Map all the new src fields to the next available unused id + for (const auto& rId : aDstIds) + AddId( GetFirstUnusedId(aIds), rId ); + + // Change the Sequence number of all SetExp fields in the source document + SwFieldType* pType = rDoc.getIDocumentFieldsAccess().GetFieldType( SwFieldIds::SetExp, aName, false ); + if(pType) + { + std::vector<SwFormatField*> vFields; + pType->GatherFields(vFields, false); + for(auto pF: vFields) + { + if(!pF->GetTextField()) + continue; + SwSetExpField *const pSetExp(static_cast<SwSetExpField *>(pF->GetField())); + sal_uInt16 const n = pSetExp->GetSeqNumber(); + pSetExp->SetSeqNumber(sequencedIds[n]); + } + } + } + else + { + GetNoteIdsFromDoc( rDestDoc, aIds ); + GetNoteIdsFromDoc( rDoc, aDstIds ); + + for (const auto& rId : aDstIds) + AddId( GetFirstUnusedId(aIds), rId ); + + // Change the footnotes/endnotes in the source doc to the new ID + for ( const auto pFootnoteIdx : rDoc.GetFootnoteIdxs() ) + { + sal_uInt16 const n = pFootnoteIdx->GetSeqRefNo(); + pFootnoteIdx->SetSeqNo(sequencedIds[n]); + } + } + bInit = true; +} + +/// Get the lowest number unused in the passed set. +/// @param[in] rIds The set of used ID numbers. +/// @returns The lowest number unused by the passed set +sal_uInt16 RefIdsMap::GetFirstUnusedId( std::set<sal_uInt16> &rIds ) +{ + sal_uInt16 num(0); + + for( const auto& rId : rIds ) + { + if( num != rId ) + { + return num; + } + ++num; + } + return num; +} + +/// Add a new ID and sequence number to the "occupied" collection. +/// @param[in] id The ID number. +/// @param[in] seqNum The sequence number. +void RefIdsMap::AddId( sal_uInt16 id, sal_uInt16 seqNum ) +{ + aIds.insert( id ); + sequencedIds[ seqNum ] = id; +} + +void RefIdsMap::Check( SwDoc& rDoc, SwDoc& rDestDoc, SwGetRefField& rField, + bool bField ) +{ + Init( rDoc, rDestDoc, bField); + + sal_uInt16 const nSeqNo = rField.GetSeqNo(); + + // check if it needs to be remapped + // if sequencedIds doesn't contain the number, it means there is no + // SetExp field / footnote in the source document: do not modify + // the number, which works well for copy from/paste to same document + // (and if it is not the same document, there's no "correct" result anyway) + if (sequencedIds.count(nSeqNo)) + { + rField.SetSeqNo( sequencedIds[nSeqNo] ); + } +} + +/// 1. if _both_ SetExp + GetExp / Footnote + GetExp field are copied, +/// ensure that both get a new unused matching number +/// 2. if only SetExp / Footnote is copied, it gets a new unused number +/// 3. if only GetExp field is copied, for the case of copy from / paste to +/// same document it's desirable to keep the same number; +/// for other cases of copy/paste or master documents it's not obvious +/// what is most desirable since it's going to be wrong anyway +void SwGetRefFieldType::MergeWithOtherDoc( SwDoc& rDestDoc ) +{ + if( &rDestDoc != m_pDoc ) + { + if (rDestDoc.IsClipBoard()) + { + // when copying _to_ clipboard, expectation is that no fields exist + // so no re-mapping is required to avoid collisions + assert(!rDestDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::GetRef)->HasWriterListeners()); + return; // don't modify the fields in the source doc + } + + // then there are RefFields in the DescDox - so all RefFields in the SourceDoc + // need to be converted to have unique IDs for both documents + RefIdsMap aFntMap { OUString() }; + std::vector<std::unique_ptr<RefIdsMap>> aFieldMap; + + std::vector<SwFormatField*> vFields; + GatherFields(vFields); + for(auto pField: vFields) + { + SwGetRefField& rRefField = *static_cast<SwGetRefField*>(pField->GetField()); + switch( rRefField.GetSubType() ) + { + case REF_SEQUENCEFLD: + { + RefIdsMap* pMap = nullptr; + for( auto n = aFieldMap.size(); n; ) + { + if (aFieldMap[ --n ]->GetName() == rRefField.GetSetRefName()) + { + pMap = aFieldMap[ n ].get(); + break; + } + } + if( !pMap ) + { + pMap = new RefIdsMap( rRefField.GetSetRefName() ); + aFieldMap.push_back(std::unique_ptr<RefIdsMap>(pMap)); + } + + pMap->Check( *m_pDoc, rDestDoc, rRefField, true ); + } + break; + + case REF_FOOTNOTE: + case REF_ENDNOTE: + aFntMap.Check( *m_pDoc, rDestDoc, rRefField, false ); + break; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/scrptfld.cxx b/sw/source/core/fields/scrptfld.cxx new file mode 100644 index 000000000..37f2e3dc2 --- /dev/null +++ b/sw/source/core/fields/scrptfld.cxx @@ -0,0 +1,119 @@ +/* -*- 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 <docufld.hxx> +#include <unofldmid.h> +#include <strings.hrc> +#include <o3tl/any.hxx> +#include <swtypes.hxx> + +using namespace ::com::sun::star; + +SwScriptFieldType::SwScriptFieldType( SwDoc* pD ) + : SwFieldType( SwFieldIds::Script ), m_pDoc( pD ) +{} + +std::unique_ptr<SwFieldType> SwScriptFieldType::Copy() const +{ + return std::make_unique<SwScriptFieldType>( m_pDoc ); +} + +SwScriptField::SwScriptField( SwScriptFieldType* pInitType, + const OUString& rType, const OUString& rCode, + bool bURL ) + : SwField( pInitType ), m_sType( rType ), m_sCode( rCode ), m_bCodeURL( bURL ) +{ +} + +OUString SwScriptField::GetDescription() const +{ + return SwResId(STR_SCRIPT); +} + +OUString SwScriptField::ExpandImpl(SwRootFrame const*const) const +{ + return OUString(); +} + +std::unique_ptr<SwField> SwScriptField::Copy() const +{ + return std::make_unique<SwScriptField>( static_cast<SwScriptFieldType*>(GetTyp()), m_sType, m_sCode, m_bCodeURL ); +} + +/// set type +void SwScriptField::SetPar1( const OUString& rStr ) +{ + m_sType = rStr; +} + +OUString SwScriptField::GetPar1() const +{ + return m_sType; +} + +/// set code +void SwScriptField::SetPar2( const OUString& rStr ) +{ + m_sCode = rStr; +} + +OUString SwScriptField::GetPar2() const +{ + return m_sCode; +} + +bool SwScriptField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny <<= m_sType; + break; + case FIELD_PROP_PAR2: + rAny <<= m_sCode; + break; + case FIELD_PROP_BOOL1: + rAny <<= m_bCodeURL; + break; + default: + assert(false); + } + return true; +} + +bool SwScriptField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_PAR1: + rAny >>= m_sType; + break; + case FIELD_PROP_PAR2: + rAny >>= m_sCode; + break; + case FIELD_PROP_BOOL1: + m_bCodeURL = *o3tl::doAccess<bool>(rAny); + break; + default: + assert(false); + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/tblcalc.cxx b/sw/source/core/fields/tblcalc.cxx new file mode 100644 index 000000000..c93d04a5c --- /dev/null +++ b/sw/source/core/fields/tblcalc.cxx @@ -0,0 +1,212 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/any.hxx> + +#include <calc.hxx> +#include <calbck.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <expfld.hxx> +#include <unofldmid.h> + +using namespace ::com::sun::star; + +SwTableFieldType::SwTableFieldType(SwDoc* pDocPtr) + : SwValueFieldType( pDocPtr, SwFieldIds::Table ) +{} + +std::unique_ptr<SwFieldType> SwTableFieldType::Copy() const +{ + return std::make_unique<SwTableFieldType>(GetDoc()); +} + +void SwTableField::CalcField( SwTableCalcPara& rCalcPara ) +{ + if( rCalcPara.m_rCalc.IsCalcError() ) // stop if there is already an error set + return; + + // create pointers from box name + BoxNmToPtr( rCalcPara.m_pTable ); + OUString sFormula( MakeFormula( rCalcPara )); + SetValue( rCalcPara.m_rCalc.Calculate( sFormula ).GetDouble() ); + ChgValid( !rCalcPara.IsStackOverflow() ); // is the value again valid? +} + +SwTableField::SwTableField( SwTableFieldType* pInitType, const OUString& rFormel, + sal_uInt16 nType, sal_uLong nFormat ) + : SwValueField( pInitType, nFormat ), SwTableFormula( rFormel ), + m_nSubType(nType) +{ + m_sExpand = "0"; +} + +std::unique_ptr<SwField> SwTableField::Copy() const +{ + std::unique_ptr<SwTableField> pTmp(new SwTableField( static_cast<SwTableFieldType*>(GetTyp()), + SwTableFormula::GetFormula(), m_nSubType, GetFormat() )); + pTmp->m_sExpand = m_sExpand; + pTmp->SwValueField::SetValue(GetValue()); + pTmp->SwTableFormula::operator=( *this ); + pTmp->SetAutomaticLanguage(IsAutomaticLanguage()); + return std::unique_ptr<SwField>(pTmp.release()); +} + +OUString SwTableField::GetFieldName() const +{ + return GetTyp()->GetName() + " " + const_cast<SwTableField *>(this)->GetCommand(); +} + +/// search TextNode containing this field +const SwNode* SwTableField::GetNodeOfFormula() const +{ + auto pFormat = GetTyp()->FindFormatForField(this); + return pFormat ? &pFormat->GetTextField()->GetTextNode() : nullptr; +} + +OUString SwTableField::GetCommand() +{ + if (EXTRNL_NAME != GetNameType()) + { + SwNode const*const pNd = GetNodeOfFormula(); + SwTableNode const*const pTableNd = pNd ? pNd->FindTableNode() : nullptr; + if (pTableNd) + { + PtrToBoxNm( &pTableNd->GetTable() ); + } + } + return (EXTRNL_NAME == GetNameType()) + ? SwTableFormula::GetFormula() + : OUString(); +} + +OUString SwTableField::ExpandImpl(SwRootFrame const*const) const +{ + if (m_nSubType & nsSwExtendedSubType::SUB_CMD) + { + return const_cast<SwTableField *>(this)->GetCommand(); + } + + if(m_nSubType & nsSwGetSetExpType::GSE_STRING) + { + // it is a string + return m_sExpand.copy(1, m_sExpand.getLength()-2); + } + + return m_sExpand; +} + +sal_uInt16 SwTableField::GetSubType() const +{ + return m_nSubType; +} + +void SwTableField::SetSubType(sal_uInt16 nType) +{ + m_nSubType = nType; +} + +void SwTableField::SetValue( const double& rVal ) +{ + SwValueField::SetValue(rVal); + m_sExpand = static_cast<SwValueFieldType*>(GetTyp())->ExpandValue(rVal, GetFormat(), GetLanguage()); +} + +OUString SwTableField::GetPar2() const +{ + return SwTableFormula::GetFormula(); +} + +void SwTableField::SetPar2(const OUString& rStr) +{ + SetFormula( rStr ); +} + +bool SwTableField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + bool bRet = true; + switch ( nWhichId ) + { + case FIELD_PROP_PAR2: + { + sal_uInt16 nOldSubType = m_nSubType; + SwTableField* pThis = const_cast<SwTableField*>(this); + pThis->m_nSubType |= nsSwExtendedSubType::SUB_CMD; + rAny <<= ExpandImpl(nullptr); + pThis->m_nSubType = nOldSubType; + } + break; + case FIELD_PROP_BOOL1: + rAny <<= 0 != (nsSwExtendedSubType::SUB_CMD & m_nSubType); + break; + case FIELD_PROP_PAR1: + rAny <<= m_sExpand; + break; + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int32>(GetFormat()); + break; + default: + bRet = false; + } + return bRet; +} + +bool SwTableField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + bool bRet = true; + switch ( nWhichId ) + { + case FIELD_PROP_PAR2: + { + OUString sTmp; + rAny >>= sTmp; + SetFormula( sTmp ); + } + break; + case FIELD_PROP_BOOL1: + if(*o3tl::doAccess<bool>(rAny)) + m_nSubType = nsSwGetSetExpType::GSE_FORMULA|nsSwExtendedSubType::SUB_CMD; + else + m_nSubType = nsSwGetSetExpType::GSE_FORMULA; + break; + case FIELD_PROP_PAR1: + { + OUString sTmp; + rAny >>= sTmp; + ChgExpStr( sTmp ); + } + break; + case FIELD_PROP_FORMAT: + { + sal_Int32 nTmp = 0; + rAny >>= nTmp; + SetFormat(nTmp); + } + break; + default: + bRet = false; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/textapi.cxx b/sw/source/core/fields/textapi.cxx new file mode 100644 index 000000000..08331d40b --- /dev/null +++ b/sw/source/core/fields/textapi.cxx @@ -0,0 +1,193 @@ +/* -*- 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 <textapi.hxx> +#include <doc.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <docsh.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unoforou.hxx> +#include <editeng/unoipset.hxx> + +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/lang/Locale.hpp> + +using namespace com::sun::star; + +static const SvxItemPropertySet* ImplGetSvxTextPortionPropertySet() +{ + static const SfxItemPropertyMapEntry aSvxTextPortionPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + {OUString("TextField"), EE_FEATURE_FIELD, + cppu::UnoType<text::XTextField>::get(), beans::PropertyAttribute::READONLY, 0 }, + {OUString("TextPortionType"), WID_PORTIONTYPE, + ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 }, + {OUString("TextUserDefinedAttributes"), EE_CHAR_XMLATTRIBS, + cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + {OUString("ParaUserDefinedAttributes"), EE_PARA_XMLATTRIBS, + cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + static SvxItemPropertySet aSvxTextPortionPropertySet( aSvxTextPortionPropertyMap, EditEngine::GetGlobalItemPool() ); + return &aSvxTextPortionPropertySet; +} + +SwTextAPIObject::SwTextAPIObject( std::unique_ptr<SwTextAPIEditSource> p ) +: SvxUnoText( p.get(), ImplGetSvxTextPortionPropertySet(), uno::Reference < text::XText >() ) +, pSource(std::move(p)) +{ +} + +SwTextAPIObject::~SwTextAPIObject() throw() +{ + pSource->Dispose(); + pSource.reset(); +} + +struct SwTextAPIEditSource_Impl +{ + // needed for "internal" refcounting + SfxItemPool* mpPool; + SwDoc* mpDoc; + std::unique_ptr<Outliner> mpOutliner; + std::unique_ptr<SvxOutlinerForwarder> mpTextForwarder; + sal_Int32 mnRef; +}; + +SwTextAPIEditSource::SwTextAPIEditSource( const SwTextAPIEditSource& rSource ) +: SvxEditSource( *this ) +{ + // shallow copy; uses internal refcounting + pImpl = rSource.pImpl; + pImpl->mnRef++; +} + +std::unique_ptr<SvxEditSource> SwTextAPIEditSource::Clone() const +{ + return std::unique_ptr<SvxEditSource>(new SwTextAPIEditSource( *this )); +} + +void SwTextAPIEditSource::UpdateData() +{ + // data is kept in outliner all the time +} + +SwTextAPIEditSource::SwTextAPIEditSource(SwDoc* pDoc) +: pImpl(new SwTextAPIEditSource_Impl) +{ + pImpl->mpPool = &pDoc->GetDocShell()->GetPool(); + pImpl->mpDoc = pDoc; + pImpl->mnRef = 1; +} + +SwTextAPIEditSource::~SwTextAPIEditSource() +{ + if (!--pImpl->mnRef) + delete pImpl; +} + +void SwTextAPIEditSource::Dispose() +{ + pImpl->mpPool=nullptr; + pImpl->mpDoc=nullptr; + pImpl->mpTextForwarder.reset(); + pImpl->mpOutliner.reset(); +} + +SvxTextForwarder* SwTextAPIEditSource::GetTextForwarder() +{ + if( !pImpl->mpPool ) + return nullptr; // mpPool == 0 can be used to flag this as disposed + + if( !pImpl->mpOutliner ) + { + //init draw model first + pImpl->mpDoc->getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + pImpl->mpOutliner.reset(new Outliner(pImpl->mpPool, OutlinerMode::TextObject)); + pImpl->mpDoc->SetCalcFieldValueHdl(pImpl->mpOutliner.get()); + } + + if( !pImpl->mpTextForwarder ) + { + pImpl->mpTextForwarder.reset(new SvxOutlinerForwarder(*pImpl->mpOutliner, false)); + } + + return pImpl->mpTextForwarder.get(); +} + +void SwTextAPIEditSource::SetText( OutlinerParaObject const & rText ) +{ + if ( pImpl->mpPool ) + { + if( !pImpl->mpOutliner ) + { + //init draw model first + pImpl->mpDoc->getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + pImpl->mpOutliner.reset(new Outliner(pImpl->mpPool, OutlinerMode::TextObject)); + pImpl->mpDoc->SetCalcFieldValueHdl(pImpl->mpOutliner.get()); + } + + pImpl->mpOutliner->SetText( rText ); + } +} + +void SwTextAPIEditSource::SetString( const OUString& rText ) +{ + if ( pImpl->mpPool ) + { + if( !pImpl->mpOutliner ) + { + //init draw model first + pImpl->mpDoc->getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + pImpl->mpOutliner.reset(new Outliner(pImpl->mpPool, OutlinerMode::TextObject)); + pImpl->mpDoc->SetCalcFieldValueHdl(pImpl->mpOutliner.get()); + } + else + pImpl->mpOutliner->Clear(); + pImpl->mpOutliner->Insert( rText ); + } +} + +std::unique_ptr<OutlinerParaObject> SwTextAPIEditSource::CreateText() +{ + if ( pImpl->mpPool && pImpl->mpOutliner ) + return pImpl->mpOutliner->CreateParaObject(); + else + return nullptr; +} + +OUString SwTextAPIEditSource::GetText() const +{ + if ( pImpl->mpPool && pImpl->mpOutliner ) + return pImpl->mpOutliner->GetEditEngine().GetText(); + else + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/fields/usrfld.cxx b/sw/source/core/fields/usrfld.cxx new file mode 100644 index 000000000..642f47c7f --- /dev/null +++ b/sw/source/core/fields/usrfld.cxx @@ -0,0 +1,368 @@ +/* -*- 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 <sal/config.h> + +#include <libxml/xmlwriter.h> + +#include <o3tl/any.hxx> + +#include <svl/zforlist.hxx> +#include <unotools/charclass.hxx> + +#include <calc.hxx> +#include <usrfld.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <unofldmid.h> + +using namespace ::com::sun::star; + +namespace +{ +/** + * Returns the language used for float <-> string conversions in + * SwUserFieldType. + */ +LanguageType GetFieldTypeLanguage() +{ + return LANGUAGE_SYSTEM; +} +} + +// Userfields + +SwUserField::SwUserField(SwUserFieldType* pTyp, sal_uInt16 nSub, sal_uInt32 nFormat) + : SwValueField(pTyp, nFormat), + m_nSubType(nSub) +{ +} + +OUString SwUserField::ExpandImpl(SwRootFrame const*const) const +{ + if(!(m_nSubType & nsSwExtendedSubType::SUB_INVISIBLE)) + return static_cast<SwUserFieldType*>(GetTyp())->Expand(GetFormat(), m_nSubType, GetLanguage()); + + return OUString(); +} + +std::unique_ptr<SwField> SwUserField::Copy() const +{ + std::unique_ptr<SwField> pTmp(new SwUserField(static_cast<SwUserFieldType*>(GetTyp()), m_nSubType, GetFormat())); + pTmp->SetAutomaticLanguage(IsAutomaticLanguage()); + return pTmp; +} + +OUString SwUserField::GetFieldName() const +{ + return SwFieldType::GetTypeStr(SwFieldTypesEnum::User) + + " " + GetTyp()->GetName() + " = " + + static_cast<SwUserFieldType*>(GetTyp())->GetContent(); +} + +double SwUserField::GetValue() const +{ + return static_cast<SwUserFieldType*>(GetTyp())->GetValue(); +} + +void SwUserField::SetValue( const double& rVal ) +{ + static_cast<SwUserFieldType*>(GetTyp())->SetValue(rVal); +} + +/// Get name +OUString SwUserField::GetPar1() const +{ + return static_cast<const SwUserFieldType*>(GetTyp())->GetName(); +} + +/// Get content +OUString SwUserField::GetPar2() const +{ + return static_cast<SwUserFieldType*>(GetTyp())->GetContent(GetFormat()); +} + +void SwUserField::SetPar2(const OUString& rStr) +{ + static_cast<SwUserFieldType*>(GetTyp())->SetContent(rStr, GetFormat()); +} + +sal_uInt16 SwUserField::GetSubType() const +{ + return static_cast<SwUserFieldType*>(GetTyp())->GetType() | m_nSubType; +} + +void SwUserField::SetSubType(sal_uInt16 nSub) +{ + static_cast<SwUserFieldType*>(GetTyp())->SetType(nSub & 0x00ff); + m_nSubType = nSub & 0xff00; +} + +bool SwUserField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL2: + rAny <<= 0 != (m_nSubType & nsSwExtendedSubType::SUB_CMD); + break; + case FIELD_PROP_BOOL1: + rAny <<= 0 == (m_nSubType & nsSwExtendedSubType::SUB_INVISIBLE); + break; + case FIELD_PROP_FORMAT: + rAny <<= static_cast<sal_Int32>(GetFormat()); + break; + default: + return SwField::QueryValue(rAny, nWhichId); + } + return true; +} + +bool SwUserField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_BOOL1: + if(*o3tl::doAccess<bool>(rAny)) + m_nSubType &= (~nsSwExtendedSubType::SUB_INVISIBLE); + else + m_nSubType |= nsSwExtendedSubType::SUB_INVISIBLE; + break; + case FIELD_PROP_BOOL2: + if(*o3tl::doAccess<bool>(rAny)) + m_nSubType |= nsSwExtendedSubType::SUB_CMD; + else + m_nSubType &= (~nsSwExtendedSubType::SUB_CMD); + break; + case FIELD_PROP_FORMAT: + { + sal_Int32 nTmp = 0; + rAny >>= nTmp; + SetFormat(nTmp); + } + break; + default: + return SwField::PutValue(rAny, nWhichId); + } + return true; +} + +void SwUserField::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwUserField")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nSubType"), BAD_CAST(OString::number(m_nSubType).getStr())); + SwValueField::dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +SwUserFieldType::SwUserFieldType( SwDoc* pDocPtr, const OUString& aNam ) + : SwValueFieldType( pDocPtr, SwFieldIds::User ), + m_nValue( 0 ), + m_nType(nsSwGetSetExpType::GSE_STRING) +{ + m_bValidValue = m_bDeleted = false; + m_aName = aNam; + + EnableFormat(false); // Do not use a Numberformatter for nsSwGetSetExpType::GSE_STRING +} + +OUString SwUserFieldType::Expand(sal_uInt32 nFormat, sal_uInt16 nSubType, LanguageType nLng) +{ + if((m_nType & nsSwGetSetExpType::GSE_EXPR) && !(nSubType & nsSwExtendedSubType::SUB_CMD)) + { + EnableFormat(); + return ExpandValue(m_nValue, nFormat, nLng); + } + + EnableFormat(false); // Do not use a Numberformatter + return m_aContent; +} + +std::unique_ptr<SwFieldType> SwUserFieldType::Copy() const +{ + std::unique_ptr<SwUserFieldType> pTmp(new SwUserFieldType( GetDoc(), m_aName )); + pTmp->m_aContent = m_aContent; + pTmp->m_nType = m_nType; + pTmp->m_bValidValue = m_bValidValue; + pTmp->m_nValue = m_nValue; + pTmp->m_bDeleted = m_bDeleted; + + return pTmp; +} + +OUString SwUserFieldType::GetName() const +{ + return m_aName; +} + +void SwUserFieldType::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( !pOld && !pNew ) + m_bValidValue = false; + + NotifyClients( pOld, pNew ); + + // update input fields that might be connected to the user field + if ( !IsModifyLocked() ) + { + LockModify(); + GetDoc()->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Input )->UpdateFields(); + UnlockModify(); + } +} + +double SwUserFieldType::GetValue( SwCalc& rCalc ) +{ + if(m_bValidValue) + return m_nValue; + + if(!rCalc.Push( this )) + { + rCalc.SetCalcError( SwCalcError::Syntax ); + return 0; + } + + // See if we need to temporarily switch rCalc's language: in case it + // differs from the field type locale. + CharClass* pCharClass = rCalc.GetCharClass(); + LanguageTag aCalcLanguage = pCharClass->getLanguageTag(); + LanguageTag aFieldTypeLanguage(GetFieldTypeLanguage()); + bool bSwitchLanguage = aCalcLanguage != aFieldTypeLanguage; + if (bSwitchLanguage) + pCharClass->setLanguageTag(aFieldTypeLanguage); + + m_nValue = rCalc.Calculate( m_aContent ).GetDouble(); + + if (bSwitchLanguage) + pCharClass->setLanguageTag(aCalcLanguage); + + rCalc.Pop(); + + if( !rCalc.IsCalcError() ) + m_bValidValue = true; + else + m_nValue = 0; + + return m_nValue; +} + +OUString SwUserFieldType::GetContent( sal_uInt32 nFormat ) +{ + if (nFormat && nFormat != SAL_MAX_UINT32) + { + OUString sFormattedValue; + Color* pCol = nullptr; + + SvNumberFormatter* pFormatter = GetDoc()->GetNumberFormatter(); + + pFormatter->GetOutputString(GetValue(), nFormat, sFormattedValue, &pCol); + return sFormattedValue; + } + + return m_aContent; +} + +void SwUserFieldType::SetContent( const OUString& rStr, sal_uInt32 nFormat ) +{ + if( m_aContent != rStr ) + { + m_aContent = rStr; + + if (nFormat && nFormat != SAL_MAX_UINT32) + { + double fValue; + + if (GetDoc()->IsNumberFormat(rStr, nFormat, fValue)) + { + SetValue(fValue); + m_aContent = DoubleToString(fValue, nFormat); + } + } + + bool bModified = GetDoc()->getIDocumentState().IsModified(); + GetDoc()->getIDocumentState().SetModified(); + if( !bModified ) // Bug 57028 + { + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); + } + } +} + +void SwUserFieldType::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const +{ + switch( nWhichId ) + { + case FIELD_PROP_DOUBLE: + rAny <<= m_nValue; + break; + case FIELD_PROP_PAR2: + rAny <<= m_aContent; + break; + case FIELD_PROP_BOOL1: + rAny <<= 0 != (nsSwGetSetExpType::GSE_EXPR&m_nType); + break; + default: + assert(false); + } +} + +void SwUserFieldType::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) +{ + switch( nWhichId ) + { + case FIELD_PROP_DOUBLE: + { + double fVal = 0; + rAny >>= fVal; + m_nValue = fVal; + + m_aContent = DoubleToString(m_nValue, static_cast<sal_uInt16>(GetFieldTypeLanguage())); + } + break; + case FIELD_PROP_PAR2: + rAny >>= m_aContent; + break; + case FIELD_PROP_BOOL1: + if(*o3tl::doAccess<bool>(rAny)) + { + m_nType |= nsSwGetSetExpType::GSE_EXPR; + m_nType &= ~nsSwGetSetExpType::GSE_STRING; + } + else + { + m_nType &= ~nsSwGetSetExpType::GSE_EXPR; + m_nType |= nsSwGetSetExpType::GSE_STRING; + } + break; + default: + assert(false); + } +} + +void SwUserFieldType::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwUserFieldType")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nValue"), BAD_CAST(OString::number(m_nValue).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("aContent"), BAD_CAST(m_aContent.toUtf8().getStr())); + SwFieldType::dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/fecopy.cxx b/sw/source/core/frmedt/fecopy.cxx new file mode 100644 index 000000000..67027a4c5 --- /dev/null +++ b/sw/source/core/frmedt/fecopy.cxx @@ -0,0 +1,1626 @@ +/* -*- 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 <memory> +#include <hintids.hxx> + +#include <vcl/graph.hxx> +#include <sot/formats.hxx> +#include <sfx2/docfile.hxx> +#include <svx/xfillit0.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdouno.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoole2.hxx> +#include <svx/fmmodel.hxx> +#include <svx/unomodel.hxx> +#include <svx/svditer.hxx> +#include <svx/svdograf.hxx> +#include <unotools/streamwrap.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <fmtornt.hxx> +#include <fmtflcnt.hxx> +#include <frmfmt.hxx> +#include <docary.hxx> +#include <txtfrm.hxx> +#include <txtflcnt.hxx> +#include <fesh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <DocumentFieldsManager.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <rootfrm.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <tblsel.hxx> +#include <swtable.hxx> +#include <flyfrm.hxx> +#include <pagefrm.hxx> +#include <fldbas.hxx> +#include <swundo.hxx> +#include <viewimp.hxx> +#include <dview.hxx> +#include <dcontact.hxx> +#include <dflyobj.hxx> +#include <docsh.hxx> +#include <pagedesc.hxx> +#include <mvsave.hxx> +#include <textboxhelper.hxx> +#include <frameformats.hxx> +#include <vcl/virdev.hxx> +#include <svx/svdundo.hxx> + +using namespace ::com::sun::star; + +// Copy for the internal clipboard. Copies all selections to the clipboard. +void SwFEShell::Copy( SwDoc* pClpDoc, const OUString* pNewClpText ) +{ + OSL_ENSURE( pClpDoc, "No Clipboard document" ); + + pClpDoc->GetIDocumentUndoRedo().DoUndo(false); // always false! + + // delete content if ClpDocument contains content + SwNodeIndex aSttIdx( pClpDoc->GetNodes().GetEndOfExtras(), 2 ); + SwNodeIndex aEndNdIdx( *aSttIdx.GetNode().EndOfSectionNode() ); + SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode(); + if (!pTextNd || !pTextNd->GetText().isEmpty() || + aSttIdx.GetIndex()+1 != pClpDoc->GetNodes().GetEndOfContent().GetIndex() ) + { + pClpDoc->GetNodes().Delete( aSttIdx, + pClpDoc->GetNodes().GetEndOfContent().GetIndex() - aSttIdx.GetIndex() ); + pTextNd = pClpDoc->GetNodes().MakeTextNode( aSttIdx, + pClpDoc->GetDfltTextFormatColl() ); + --aSttIdx; + } + + // also delete surrounding FlyFrames if any + for( const auto pFly : *pClpDoc->GetSpzFrameFormats() ) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aSttIdx <= pAPos->nNode && pAPos->nNode <= aEndNdIdx ) + { + pClpDoc->getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + } + } + + pClpDoc->GetDocumentFieldsManager().GCFieldTypes(); // delete the FieldTypes + + // if a string was passed, copy it to the clipboard- + // document. Then also the Calculator can use the internal + // clipboard + if( pNewClpText ) + { + pTextNd->InsertText( *pNewClpText, SwIndex( pTextNd ) ); + return; // that's it + } + + pClpDoc->getIDocumentFieldsAccess().LockExpFields(); + pClpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::DeleteRedlines ); + + // do we want to copy a FlyFrame? + if( IsFrameSelected() ) + { + // get the FlyFormat + SwFlyFrame* pFly = GetSelectedFlyFrame(); + SwFrameFormat* pFlyFormat = pFly->GetFormat(); + SwFormatAnchor aAnchor( pFlyFormat->GetAnchor() ); + + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + SwPosition aPos( aSttIdx ); + if ( RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId() ) + { + aPos.nContent.Assign( pTextNd, 0 ); + } + aAnchor.SetAnchor( &aPos ); + } + pFlyFormat = pClpDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *pFlyFormat, aAnchor, true, true ); + + // assure the "RootFormat" is the first element in Spz-Array + // (if necessary Flys were copied in Flys) + SwFrameFormats& rSpzFrameFormats = *pClpDoc->GetSpzFrameFormats(); + if( rSpzFrameFormats[ 0 ] != pFlyFormat ) + { +#ifndef NDEBUG + bool inserted = +#endif + rSpzFrameFormats.newDefault( pFlyFormat ); + assert( !inserted && "Fly not contained in Spz-Array" ); + } + + if ( RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId() ) + { + // JP 13.02.99 Bug 61863: if a frameselection is passed to the + // clipboard, it should be found at pasting. Therefore + // the copied TextAttribut should be removed in the node + // otherwise it will be recognised as TextSelektion + const SwIndex& rIdx = pFlyFormat->GetAnchor().GetContentAnchor()->nContent; + SwTextFlyCnt *const pTextFly = static_cast<SwTextFlyCnt *>( + pTextNd->GetTextAttrForCharAt( + rIdx.GetIndex(), RES_TXTATR_FLYCNT)); + if( pTextFly ) + { + const_cast<SwFormatFlyCnt&>(pTextFly->GetFlyCnt()).SetFlyFormat(); + pTextNd->EraseText( rIdx, 1 ); + } + } + } + else if ( IsObjSelected() ) + { + SwPosition aPos( aSttIdx, SwIndex( pTextNd, 0 )); + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + + if( Imp()->GetDrawView()->IsGroupEntered() || + ( !pObj->GetUserCall() && pObj->getParentSdrObjectFromSdrObject()) ) + { + SfxItemSet aSet( pClpDoc->GetAttrPool(), aFrameFormatSetRange ); + + SwFormatAnchor aAnchor( RndStdIds::FLY_AT_PARA ); + aAnchor.SetAnchor( &aPos ); + aSet.Put( aAnchor ); + + SdrObject *const pNew = + pClpDoc->CloneSdrObj( *pObj ); + + SwPaM aTemp(aPos); + pClpDoc->getIDocumentContentOperations().InsertDrawObj(aTemp, *pNew, aSet ); + } + else + { + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall( pObj )); + SwFrameFormat *pFormat = pContact->GetFormat(); + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + aAnchor.SetAnchor( &aPos ); + } + + pClpDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *pFormat, aAnchor, true, true ); + } + } + } + else + CopySelToDoc( pClpDoc ); // copy the selections + + pClpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::NONE ); + pClpDoc->getIDocumentFieldsAccess().UnlockExpFields(); + if( !pClpDoc->getIDocumentFieldsAccess().IsExpFieldsLocked() ) + pClpDoc->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); +} + +static const Point &lcl_FindBasePos( const SwFrame *pFrame, const Point &rPt ) +{ + const SwFrame *pF = pFrame; + while ( pF && !pF->getFrameArea().IsInside( rPt ) ) + { + if ( pF->IsContentFrame() ) + pF = static_cast<const SwContentFrame*>(pF)->GetFollow(); + else + pF = nullptr; + } + if ( pF ) + return pF->getFrameArea().Pos(); + else + return pFrame->getFrameArea().Pos(); +} + +static bool lcl_SetAnchor( const SwPosition& rPos, const SwNode& rNd, SwFlyFrame const * pFly, + const Point& rInsPt, SwFEShell const & rDestShell, SwFormatAnchor& rAnchor, + Point& rNewPos, bool bCheckFlyRecur ) +{ + bool bRet = true; + rAnchor.SetAnchor( &rPos ); + std::pair<Point, bool> const tmp(rInsPt, false); + SwContentFrame *const pTmpFrame = rNd.GetContentNode()->getLayoutFrame( + rDestShell.GetLayout(), nullptr, &tmp); + SwFlyFrame *pTmpFly = pTmpFrame->FindFlyFrame(); + if( pTmpFly && bCheckFlyRecur && pFly->IsUpperOf( *pTmpFly ) ) + { + bRet = false; + } + else if ( RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId() ) + { + if( pTmpFly ) + { + const SwNodeIndex& rIdx = *pTmpFly->GetFormat()->GetContent().GetContentIdx(); + SwPosition aPos( rIdx ); + rAnchor.SetAnchor( &aPos ); + rNewPos = pTmpFly->getFrameArea().Pos(); + } + else + { + rAnchor.SetType( RndStdIds::FLY_AT_PAGE ); + rAnchor.SetPageNum( rDestShell.GetPageNumber( rInsPt ) ); + const SwFrame *pPg = pTmpFrame->FindPageFrame(); + rNewPos = pPg->getFrameArea().Pos(); + } + } + else + rNewPos = ::lcl_FindBasePos( pTmpFrame, rInsPt ); + return bRet; +} + +bool SwFEShell::CopyDrawSel( SwFEShell* pDestShell, const Point& rSttPt, + const Point& rInsPt, bool bIsMove, bool bSelectInsert ) +{ + bool bRet = true; + + // The list should be copied, because below new objects will be selected + const SdrMarkList aMrkList( Imp()->GetDrawView()->GetMarkedObjectList() ); + const size_t nMarkCount = aMrkList.GetMarkCount(); + if( !pDestShell->Imp()->GetDrawView() ) + // should create it now + pDestShell->MakeDrawView(); + else if( bSelectInsert ) + pDestShell->Imp()->GetDrawView()->UnmarkAll(); + + SdrPageView *pDestPgView = pDestShell->Imp()->GetPageView(), + *pSrcPgView = Imp()->GetPageView(); + SwDrawView *pDestDrwView = pDestShell->Imp()->GetDrawView(), + *pSrcDrwView = Imp()->GetDrawView(); + SwDoc* pDestDoc = pDestShell->GetDoc(); + + Size aSiz( rInsPt.X() - rSttPt.X(), rInsPt.Y() - rSttPt.Y() ); + for( size_t i = 0; i < nMarkCount; ++i ) + { + SdrObject *pObj = aMrkList.GetMark( i )->GetMarkedSdrObj(); + + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall( pObj )); + SwFrameFormat *pFormat = pContact->GetFormat(); + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + + bool bInsWithFormat = true; + + if( pDestDrwView->IsGroupEntered() ) + { + // insert into the group, when it belongs to an entered group + // or when the object is not anchored as a character + if( pSrcDrwView->IsGroupEntered() || + (RndStdIds::FLY_AS_CHAR != rAnchor.GetAnchorId()) ) + + { + SdrObject* pNew = pDestDoc->CloneSdrObj( *pObj, bIsMove && + GetDoc() == pDestDoc, false ); + pNew->NbcMove( aSiz ); + pDestDrwView->InsertObjectAtView( pNew, *pDestPgView ); + bInsWithFormat = false; + } + } + + if( bInsWithFormat ) + { + SwFormatAnchor aAnchor( rAnchor ); + Point aNewAnch; + + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + if ( this == pDestShell ) + { + // same shell? Then request the position + // from the passed DocumentPosition + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( rInsPt ); + aPt -= rSttPt - pObj->GetSnapRect().TopLeft(); + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aState ); + const SwNode *pNd; + if( (pNd = &aPos.nNode.GetNode())->IsNoTextNode() ) + bRet = false; + else + bRet = ::lcl_SetAnchor( aPos, *pNd, nullptr, rInsPt, + *pDestShell, aAnchor, aNewAnch, false ); + } + else + { + SwPaM *pCursor = pDestShell->GetCursor(); + if( pCursor->GetNode().IsNoTextNode() ) + bRet = false; + else + bRet = ::lcl_SetAnchor( *pCursor->GetPoint(), + pCursor->GetNode(), nullptr, rInsPt, + *pDestShell, aAnchor, + aNewAnch, false ); + } + } + else if ( RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId() ) + { + aAnchor.SetPageNum( pDestShell->GetPageNumber( rInsPt ) ); + const SwRootFrame* pTmpRoot = pDestShell->GetLayout(); + const SwFrame* pPg = pTmpRoot->GetPageAtPos( rInsPt, nullptr, true ); + if ( pPg ) + aNewAnch = pPg->getFrameArea().Pos(); + } + + if( bRet ) + { + if( pSrcDrwView->IsGroupEntered() || + ( !pObj->GetUserCall() && pObj->getParentSdrObjectFromSdrObject()) ) + { + SfxItemSet aSet( pDestDoc->GetAttrPool(),aFrameFormatSetRange); + aSet.Put( aAnchor ); + SdrObject* pNew = pDestDoc->CloneSdrObj( *pObj, bIsMove && + GetDoc() == pDestDoc ); + pFormat = pDestDoc->getIDocumentContentOperations().InsertDrawObj( *pDestShell->GetCursor(), *pNew, aSet ); + } + else + pFormat = pDestDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *pFormat, aAnchor, true, true ); + + // Can be 0, as Draws are not allowed in Headers/Footers + if ( pFormat ) + { + // #tdf33692 - drawing object has to be made visible on ctrl+drag copy. + pFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PREPPASTING)); + SdrObject* pNew = pFormat->FindSdrObject(); + if ( RndStdIds::FLY_AS_CHAR != aAnchor.GetAnchorId() ) + { + Point aPos( rInsPt ); + aPos -= aNewAnch; + aPos -= rSttPt - pObj->GetSnapRect().TopLeft(); + // OD 2004-04-05 #i26791# - change attributes instead of + // direct positioning + pFormat->SetFormatAttr( SwFormatHoriOrient( aPos.getX(), text::HoriOrientation::NONE, text::RelOrientation::FRAME ) ); + pFormat->SetFormatAttr( SwFormatVertOrient( aPos.getY(), text::VertOrientation::NONE, text::RelOrientation::FRAME ) ); + // #i47455# - notify draw frame format + // that position attributes are already set. + if ( dynamic_cast<const SwDrawFrameFormat*>( pFormat) != nullptr ) + { + static_cast<SwDrawFrameFormat*>(pFormat)->PosAttrSet(); + } + } + if( bSelectInsert ) + pDestDrwView->MarkObj( pNew, pDestPgView ); + } + } + } + } + + if ( bIsMove && bRet ) + { + if( pDestShell == this ) + { + const SdrMarkList aList( pSrcDrwView->GetMarkedObjectList() ); + pSrcDrwView->UnmarkAll(); + + for ( size_t i = 0, nMrkCnt = aMrkList.GetMarkCount(); i < nMrkCnt; ++i ) + { + SdrObject *pObj = aMrkList.GetMark( i )->GetMarkedSdrObj(); + pSrcDrwView->MarkObj( pObj, pSrcPgView ); + } + DelSelectedObj(); + for ( size_t i = 0, nMrkCnt = aList.GetMarkCount(); i < nMrkCnt; ++i ) + { + SdrObject *pObj = aList.GetMark( i )->GetMarkedSdrObj(); + pSrcDrwView->MarkObj( pObj, pSrcPgView ); + } + } + else + DelSelectedObj(); + } + + return bRet; +} + +bool SwFEShell::Copy( SwFEShell* pDestShell, const Point& rSttPt, + const Point& rInsPt, bool bIsMove, bool bSelectInsert ) +{ + bool bRet = false; + + OSL_ENSURE( pDestShell, "Copy without DestShell." ); + OSL_ENSURE( this == pDestShell || !pDestShell->IsObjSelected(), + "Dest-Shell cannot be in Obj-Mode" ); + + SET_CURR_SHELL( pDestShell ); + + pDestShell->StartAllAction(); + pDestShell->GetDoc()->getIDocumentFieldsAccess().LockExpFields(); + + // Shift references + bool bCopyIsMove = mxDoc->IsCopyIsMove(); + if( bIsMove ) + // set a flag in Doc, handled in TextNodes + mxDoc->SetCopyIsMove( true ); + + RedlineFlags eOldRedlMode = pDestShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + pDestShell->GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOldRedlMode | RedlineFlags::DeleteRedlines ); + + // If there are table formulas in the area, then display the table first + // so that the table formula can calculate a new value first + // (individual boxes in the area are retrieved via the layout) + SwFieldType* pTableFieldTyp = pDestShell->GetDoc()->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Table ); + + if( IsFrameSelected() ) + { + SwFlyFrame* pFly = GetSelectedFlyFrame(); + SwFrameFormat* pFlyFormat = pFly->GetFormat(); + SwFormatAnchor aAnchor( pFlyFormat->GetAnchor() ); + bRet = true; + Point aNewAnch; + + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + if ( this == pDestShell ) + { + // same shell? Then request the position + // from the passed DocumentPosition + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( rInsPt ); + aPt -= rSttPt - pFly->getFrameArea().Pos(); + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aState ); + const SwNode *pNd; + if( (pNd = &aPos.nNode.GetNode())->IsNoTextNode() ) + bRet = false; + else + { + // do not copy in itself + const SwNodeIndex *pTmp = pFlyFormat->GetContent().GetContentIdx(); + if ( aPos.nNode > *pTmp && aPos.nNode < + pTmp->GetNode().EndOfSectionIndex() ) + { + bRet = false; + } + else + bRet = ::lcl_SetAnchor( aPos, *pNd, pFly, rInsPt, + *pDestShell, aAnchor, aNewAnch, true ); + } + } + else + { + const SwPaM *pCursor = pDestShell->GetCursor(); + if( pCursor->GetNode().IsNoTextNode() ) + bRet = false; + else + bRet = ::lcl_SetAnchor( *pCursor->GetPoint(), pCursor->GetNode(), + pFly, rInsPt, *pDestShell, aAnchor, + aNewAnch, GetDoc() == pDestShell->GetDoc()); + } + } + else if ( RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId() ) + { + aAnchor.SetPageNum( pDestShell->GetPageNumber( rInsPt ) ); + const SwRootFrame* pTmpRoot = pDestShell->GetLayout(); + const SwFrame* pPg = pTmpRoot->GetPageAtPos( rInsPt, nullptr, true ); + if ( pPg ) + aNewAnch = pPg->getFrameArea().Pos(); + } + else { + OSL_ENSURE( false, "what anchor is it?" ); + } + + if( bRet ) + { + SwFrameFormat *pOldFormat = pFlyFormat; + pFlyFormat = pDestShell->GetDoc()->getIDocumentLayoutAccess().CopyLayoutFormat( *pFlyFormat, aAnchor, true, true ); + + if ( RndStdIds::FLY_AS_CHAR != aAnchor.GetAnchorId() ) + { + Point aPos( rInsPt ); + aPos -= aNewAnch; + aPos -= rSttPt - pFly->getFrameArea().Pos(); + pFlyFormat->SetFormatAttr( SwFormatHoriOrient( aPos.getX(),text::HoriOrientation::NONE, text::RelOrientation::FRAME ) ); + pFlyFormat->SetFormatAttr( SwFormatVertOrient( aPos.getY(),text::VertOrientation::NONE, text::RelOrientation::FRAME ) ); + } + + const Point aPt( pDestShell->GetCursorDocPos() ); + + if( bIsMove ) + GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pOldFormat ); + + // only select if it can be shifted/copied in the same shell + if( bSelectInsert ) + { + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrameFormat*>(pFlyFormat)->GetFrame( &aPt ); + if( pFlyFrame ) + { + //JP 12.05.98: should this be in SelectFlyFrame??? + pDestShell->Imp()->GetDrawView()->UnmarkAll(); + pDestShell->SelectFlyFrame( *pFlyFrame ); + } + } + + if (this != pDestShell && !pDestShell->HasShellFocus()) + pDestShell->Imp()->GetDrawView()->hideMarkHandles(); + } + } + else if ( IsObjSelected() ) + bRet = CopyDrawSel( pDestShell, rSttPt, rInsPt, bIsMove, bSelectInsert ); + else if( IsTableMode() ) + { + // Copy parts from a table: create a table with the same + // width as the original and copy the selected boxes. + // Sizes will be corrected by percentage. + + // find boxes via the layout + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes ); + SwTableNode const*const pTableNd( + aBoxes.empty() ? nullptr : aBoxes[0]->GetSttNd()->FindTableNode()); + if (nullptr != pTableNd) + { + std::unique_ptr<SwPosition> pDstPos; + if( this == pDestShell ) + { + // same shell? Then create new Cursor at the + // DocumentPosition passed + pDstPos.reset(new SwPosition( *GetCursor()->GetPoint() )); + Point aPt( rInsPt ); + GetLayout()->GetModelPositionForViewPoint( pDstPos.get(), aPt ); + if( !pDstPos->nNode.GetNode().IsNoTextNode() ) + bRet = true; + } + else if( !pDestShell->GetCursor()->GetNode().IsNoTextNode() ) + { + pDstPos.reset(new SwPosition( *pDestShell->GetCursor()->GetPoint() )); + bRet = true; + } + + if( bRet ) + { + if( GetDoc() == pDestShell->GetDoc() ) + ParkTableCursor(); + + bRet = pDestShell->GetDoc()->InsCopyOfTable( *pDstPos, aBoxes,nullptr, + bIsMove && this == pDestShell && + aBoxes.size() == pTableNd->GetTable(). + GetTabSortBoxes().size(), + this != pDestShell ); + + if( this != pDestShell ) + *pDestShell->GetCursor()->GetPoint() = *pDstPos; + + // create all parked Cursor? + if( GetDoc() == pDestShell->GetDoc() ) + GetCursor(); + + // JP 16.04.99: Bug 64908 - Set InsPos, to assure the parked + // Cursor is positioned at the insert position + if( this == pDestShell ) + GetCursorDocPos() = rInsPt; + } + } + } + else + { + bRet = true; + if( this == pDestShell ) + { + // same shell? then request the position + // at the passed document position + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( rInsPt ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt ); + bRet = !aPos.nNode.GetNode().IsNoTextNode(); + } + else if( pDestShell->GetCursor()->GetNode().IsNoTextNode() ) + bRet = false; + + if( bRet ) + bRet = SwEditShell::Copy( pDestShell ); + } + + pDestShell->GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOldRedlMode ); + mxDoc->SetCopyIsMove( bCopyIsMove ); + + // have new table formulas been inserted? + if( pTableFieldTyp->HasWriterListeners() ) + { + // finish old actions: the table frames are created and + // a selection can be made + sal_uInt16 nActCnt; + for( nActCnt = 0; pDestShell->ActionPend(); ++nActCnt ) + pDestShell->EndAllAction(); + + for( ; nActCnt; --nActCnt ) + pDestShell->StartAllAction(); + } + pDestShell->GetDoc()->getIDocumentFieldsAccess().UnlockExpFields(); + pDestShell->GetDoc()->getIDocumentFieldsAccess().UpdateFields(false); + + pDestShell->EndAllAction(); + return bRet; +} + +// Paste for the internal clipboard. Copy the content of the clipboard +// in the document +namespace { + typedef std::shared_ptr<SwPaM> PaMPtr; + typedef std::shared_ptr<SwPosition> PositionPtr; + typedef std::pair< PaMPtr, PositionPtr > Insertion; + + bool PamHasSelection(const SwPaM& rPaM) + { + return rPaM.HasMark() && *rPaM.GetPoint() != *rPaM.GetMark(); + } + + /// Is pFormat anchored in a fly frame which has an associated draw format? + bool IsInTextBox(const SwFrameFormat* pFormat) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + const SwPosition* pPosition = rAnchor.GetContentAnchor(); + if (!pPosition) + { + return false; + } + + const SwStartNode* pFlyNode = pPosition->nNode.GetNode().FindFlyStartNode(); + if (!pFlyNode) + { + return false; + } + + for ( const auto& pSpzFormat : *pFormat->GetDoc()->GetSpzFrameFormats() ) + { + if (pSpzFormat->Which() != RES_FLYFRMFMT) + { + continue; + } + + const SwNodeIndex* pIdx = pSpzFormat->GetContent().GetContentIdx(); + if (!pIdx || pFlyNode != &pIdx->GetNode()) + { + continue; + } + + return SwTextBoxHelper::isTextBox(pSpzFormat, RES_FLYFRMFMT); + } + + return false; + } +} + +bool SwFEShell::Paste( SwDoc* pClpDoc, bool bNestedTable ) +{ + SET_CURR_SHELL( this ); + OSL_ENSURE( pClpDoc, "no clipboard document" ); + // then till end of the nodes array + SwNodeIndex aIdx( pClpDoc->GetNodes().GetEndOfExtras(), 2 ); + SwPaM aCpyPam( aIdx ); //DocStart + + // If there are table formulas in the area, then display the table first + // so that the table formula can calculate a new value first + // (individual boxes in the area are retrieved via the layout) + SwFieldType* pTableFieldTyp = GetDoc()->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Table ); + + SwTableNode *const pSrcNd = aCpyPam.GetNode().GetTableNode(); + if( !pSrcNd ) // table node ? + { // don't skip !! + SwContentNode* pCNd = aCpyPam.GetNode().GetContentNode(); + if( pCNd ) + aCpyPam.GetPoint()->nContent.Assign( pCNd, 0 ); + else if( !aCpyPam.Move( fnMoveForward, GoInNode )) + aCpyPam.Move( fnMoveBackward, GoInNode ); + } + + aCpyPam.SetMark(); + aCpyPam.Move( fnMoveForward, GoInDoc ); + + bool bRet = true; + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSGLOSSARY, nullptr ); + GetDoc()->getIDocumentFieldsAccess().LockExpFields(); + + // When the clipboard content has been created by a rectangular selection + // the pasting is more sophisticated: + // every paragraph will be inserted into another position. + // The first positions are given by the actual cursor ring, + // if there are more text portions to insert than cursor in this ring, + // the additional insert positions will be created by moving the last + // cursor position into the next line (like pressing the cursor down key) + if( pClpDoc->IsColumnSelection() && !IsTableMode() ) + { + // Creation of the list of insert positions + std::vector< Insertion > aCopyVector; + // The number of text portions of the rectangular selection + const sal_uInt32 nSelCount = aCpyPam.GetPoint()->nNode.GetIndex() + - aCpyPam.GetMark()->nNode.GetIndex(); + sal_uInt32 nCount = nSelCount; + SwNodeIndex aClpIdx( aIdx ); + SwPaM* pStartCursor = GetCursor(); + SwPaM* pCurrCursor = pStartCursor; + sal_uInt32 nCursorCount = pStartCursor->GetRingContainer().size(); + // If the target selection is a multi-selection, often the last and first + // cursor of the ring points to identical document positions. Then + // we should avoid double insertion of text portions... + while( nCursorCount > 1 && *pCurrCursor->GetPoint() == + *(pCurrCursor->GetPrev()->GetPoint()) ) + { + --nCursorCount; + pCurrCursor = pCurrCursor->GetNext(); + pStartCursor = pCurrCursor; + } + SwPosition aStartPos( *pStartCursor->GetPoint() ); + SwPosition aInsertPos( aStartPos ); // first insertion position + bool bCompletePara = false; + sal_uInt16 nMove = 0; + while( nCount ) + { + --nCount; + OSL_ENSURE( aIdx.GetNode().GetContentNode(), "Who filled the clipboard?!" ); + if( aIdx.GetNode().GetContentNode() ) // robust + { + Insertion aInsertion( std::make_shared<SwPaM>( aIdx ), + std::make_shared<SwPosition>( aInsertPos ) ); + ++aIdx; + aInsertion.first->SetMark(); + if( pStartCursor == pCurrCursor->GetNext() ) + { // Now we have to look for insertion positions... + if( !nMove ) // Annotate the last given insert position + aStartPos = aInsertPos; + SwCursor aCursor( aStartPos, nullptr); + // Check if we find another insert position by moving + // down the last given position + if (aCursor.UpDown(false, ++nMove, nullptr, 0, *GetLayout())) + aInsertPos = *aCursor.GetPoint(); + else // if there is no paragraph we have to create it + bCompletePara = nCount > 0; + nCursorCount = 0; + } + else // as long as we find more insert positions in the cursor ring + { // we'll take them + pCurrCursor = pCurrCursor->GetNext(); + aInsertPos = *pCurrCursor->GetPoint(); + --nCursorCount; + } + // If there are no more paragraphs e.g. at the end of a document, + // we insert complete paragraphs instead of text portions + if( bCompletePara ) + aInsertion.first->GetPoint()->nNode = aIdx; + else + aInsertion.first->GetPoint()->nContent = + aInsertion.first->GetContentNode()->Len(); + aCopyVector.push_back( aInsertion ); + } + // If there are no text portions left but there are some more + // cursor positions to fill we have to restart with the first + // text portion + if( !nCount && nCursorCount ) + { + nCount = std::min( nSelCount, nCursorCount ); + aIdx = aClpIdx; // Start of clipboard content + } + } + for (auto const& item : aCopyVector) + { + SwPosition& rInsPos = *item.second; + SwPaM& rCopy = *item.first; + const SwStartNode* pBoxNd = rInsPos.nNode.GetNode().FindTableBoxStartNode(); + if( pBoxNd && 2 == pBoxNd->EndOfSectionIndex() - pBoxNd->GetIndex() && + rCopy.GetPoint()->nNode != rCopy.GetMark()->nNode ) + { + // if more than one node will be copied into a cell + // the box attributes have to be removed + GetDoc()->ClearBoxNumAttrs( rInsPos.nNode ); + } + { + SwNodeIndex aIndexBefore(rInsPos.nNode); + --aIndexBefore; + pClpDoc->getIDocumentContentOperations().CopyRange(rCopy, rInsPos, SwCopyFlags::CheckPosInFly); + { + ++aIndexBefore; + SwPaM aPaM(SwPosition(aIndexBefore), + SwPosition(rInsPos.nNode)); + aPaM.GetDoc()->MakeUniqueNumRules(aPaM); + } + } + SaveTableBoxContent( &rInsPos ); + } + } + else + { + bool bDelTable = true; + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + + SwTableNode *const pDestNd(GetDoc()->IsIdxInTable(rPaM.GetPoint()->nNode)); + if (pSrcNd && nullptr != pDestNd && + // not a forced nested table insertion + !bNestedTable && + // are we at the beginning of the cell? (if not, we will insert a nested table) + // first paragraph of the cell? + rPaM.GetNode().GetIndex() == rPaM.GetNode().FindTableBoxStartNode()->GetIndex()+1 && + // beginning of the paragraph? + !rPaM.GetPoint()->nContent.GetIndex()) + { + SwPosition aDestPos( *rPaM.GetPoint() ); + + bool bParkTableCursor = false; + const SwStartNode* pSttNd = rPaM.GetNode().FindTableBoxStartNode(); + + // TABLE IN TABLE: copy table in table + // search boxes via the layout + SwSelBoxes aBoxes; + if( IsTableMode() ) // table selection? + { + GetTableSel( *this, aBoxes ); + ParkTableCursor(); + bParkTableCursor = true; + } + else if( !PamHasSelection(rPaM) && rPaM.GetNext() == &rPaM && + ( !pSrcNd->GetTable().IsTableComplex() || + pDestNd->GetTable().IsNewModel() ) ) + { + // make relative table copy + SwTableBox* pBox = pDestNd->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "Box is not in this table" ); + aBoxes.insert( pBox ); + } + + SwNodeIndex aNdIdx( *pDestNd->EndOfSectionNode()); + if( !bParkTableCursor ) + { + // exit first the complete table + // ???? what about only table in a frame ????? + SwContentNode* pCNd = GetDoc()->GetNodes().GoNext( &aNdIdx ); + SwPosition aPos( aNdIdx, SwIndex( pCNd, 0 )); + // #i59539: Don't remove all redline + SwPaM const tmpPaM(*pDestNd, *pDestNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, aPos); + } + + bRet = GetDoc()->InsCopyOfTable( aDestPos, aBoxes, &pSrcNd->GetTable() ); + + if( bParkTableCursor ) + GetCursor(); + else + { + // return to the box + aNdIdx = *pSttNd; + SwContentNode* pCNd = GetDoc()->GetNodes().GoNext( &aNdIdx ); + SwPosition aPos( aNdIdx, SwIndex( pCNd, 0 )); + // #i59539: Don't remove all redline + SwNode & rNode(rPaM.GetPoint()->nNode.GetNode()); + SwContentNode *const pContentNode( rNode.GetContentNode() ); + SwPaM const tmpPam(rNode, 0, + rNode, pContentNode ? pContentNode->Len() : 0); + ::PaMCorrAbs(tmpPam, aPos); + } + + break; // exit the "while-loop" + } + else if( *aCpyPam.GetPoint() == *aCpyPam.GetMark() && + !pClpDoc->GetSpzFrameFormats()->empty() ) + { + // we need a DrawView + if( !Imp()->GetDrawView() ) + MakeDrawView(); + + for ( auto pCpyFormat : *pClpDoc->GetSpzFrameFormats() ) + { + bool bInsWithFormat = true; + + if( Imp()->GetDrawView()->IsGroupEntered() && + RES_DRAWFRMFMT == pCpyFormat->Which() && + (RndStdIds::FLY_AS_CHAR != pCpyFormat->GetAnchor().GetAnchorId()) ) + { + const SdrObject* pSdrObj = pCpyFormat->FindSdrObject(); + if( pSdrObj ) + { + SdrObject* pNew = GetDoc()->CloneSdrObj( *pSdrObj, + false, false ); + + // Insert object sets any anchor position to 0. + // Therefore we calculate the absolute position here + // and after the insert the anchor of the object + // is set to the anchor of the group object. + tools::Rectangle aSnapRect = pNew->GetSnapRect(); + if( pNew->GetAnchorPos().X() || pNew->GetAnchorPos().Y() ) + { + const Point aPoint( 0, 0 ); + // OD 2004-04-05 #i26791# - direct drawing object + // positioning for group members + pNew->NbcSetAnchorPos( aPoint ); + pNew->NbcSetSnapRect( aSnapRect ); + } + + Imp()->GetDrawView()->InsertObjectAtView( pNew, *Imp()->GetPageView() ); + + Point aGrpAnchor( 0, 0 ); + SdrObjList* pList = pNew->getParentSdrObjListFromSdrObject(); + if ( pList ) + { + SdrObjGroup* pOwner(dynamic_cast< SdrObjGroup* >(pList->getSdrObjectFromSdrObjList())); + + if(nullptr != pOwner) + { + aGrpAnchor = pOwner->GetAnchorPos(); + } + } + + // OD 2004-04-05 #i26791# - direct drawing object + // positioning for group members + pNew->NbcSetAnchorPos( aGrpAnchor ); + pNew->SetSnapRect( aSnapRect ); + + bInsWithFormat = false; + } + } + + if( bInsWithFormat ) + { + SwFormatAnchor aAnchor( pCpyFormat->GetAnchor() ); + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + SwPosition* pPos = rPaM.GetPoint(); + // allow shapes (no controls) in header/footer + if( RES_DRAWFRMFMT == pCpyFormat->Which() && + GetDoc()->IsInHeaderFooter( pPos->nNode ) ) + { + const SdrObject *pCpyObj = pCpyFormat->FindSdrObject(); + if (pCpyObj && CheckControlLayer(pCpyObj)) + continue; + } + else if (pCpyFormat->Which() == RES_FLYFRMFMT && IsInTextBox(pCpyFormat)) + { + // This is a fly frame which is anchored in a TextBox, ignore it as + // it's already copied as part of copying the content of the + // TextBox. + continue; + } + + // Ignore TextBoxes, they are already handled in sw::DocumentLayoutManager::CopyLayoutFormat(). + if (SwTextBoxHelper::isTextBox(pCpyFormat, RES_FLYFRMFMT)) + continue; + + aAnchor.SetAnchor( pPos ); + } + else if ( RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId() ) + { + aAnchor.SetPageNum( GetPhyPageNum() ); + } + else if( RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId() ) + { + Point aPt; + (void)lcl_SetAnchor( *rPaM.GetPoint(), rPaM.GetNode(), + nullptr, aPt, *this, aAnchor, aPt, false ); + } + + SwFrameFormat * pNew = GetDoc()->getIDocumentLayoutAccess().CopyLayoutFormat( *pCpyFormat, aAnchor, true, true ); + + if( pNew ) + { + if( RES_FLYFRMFMT == pNew->Which() ) + { + const Point aPt( GetCursorDocPos() ); + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrameFormat*>(pNew)-> + GetFrame( &aPt ); + if( pFlyFrame ) + SelectFlyFrame( *pFlyFrame ); + // always pick the first FlyFrame only; the others + // were copied to the clipboard via Fly in Fly + break; + } + else + { + OSL_ENSURE( RES_DRAWFRMFMT == pNew->Which(), "New format."); + // #i52780# - drawing object has to be made visible on paste. + pNew->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PREPPASTING)); + SdrObject *pObj = pNew->FindSdrObject(); + SwDrawView *pDV = Imp()->GetDrawView(); + pDV->MarkObj( pObj, pDV->GetSdrPageView() ); + // #i47455# - notify draw frame format + // that position attributes are already set. + if ( dynamic_cast<const SwDrawFrameFormat*>( pNew) != nullptr ) + { + static_cast<SwDrawFrameFormat*>(pNew)->PosAttrSet(); + } + } + } + } + } + } + else + { + if( bDelTable && IsTableMode() ) + { + SwEditShell::Delete(); + bDelTable = false; + } + + SwPosition& rInsPos = *rPaM.GetPoint(); + const SwStartNode* pBoxNd = rInsPos.nNode.GetNode(). + FindTableBoxStartNode(); + if( pBoxNd && 2 == pBoxNd->EndOfSectionIndex() - + pBoxNd->GetIndex() && + aCpyPam.GetPoint()->nNode != aCpyPam.GetMark()->nNode ) + { + // Copy more than 1 node in the current box. But + // then the BoxAttribute should be removed + GetDoc()->ClearBoxNumAttrs( rInsPos.nNode ); + } + + // ** + // ** Update SwDoc::Append, if you change the following code ** + // ** + { + SwNodeIndex aIndexBefore(rInsPos.nNode); + + --aIndexBefore; + + pClpDoc->getIDocumentContentOperations().CopyRange(aCpyPam, rInsPos, SwCopyFlags::CheckPosInFly); + // Note: aCpyPam is invalid now + + ++aIndexBefore; + SwPaM aPaM(SwPosition(aIndexBefore), + SwPosition(rInsPos.nNode)); + + aPaM.GetDoc()->MakeUniqueNumRules(aPaM); + + // Update the rsid of each pasted text node. + SwNodes &rDestNodes = GetDoc()->GetNodes(); + sal_uLong const nEndIdx = aPaM.End()->nNode.GetIndex(); + + for (sal_uLong nIdx = aPaM.Start()->nNode.GetIndex(); + nIdx <= nEndIdx; ++nIdx) + { + SwTextNode *const pTextNode = rDestNodes[nIdx]->GetTextNode(); + if ( pTextNode ) + { + GetDoc()->UpdateParRsid( pTextNode ); + } + } + } + + SaveTableBoxContent( &rInsPos ); + } + } + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSGLOSSARY, nullptr ); + + // have new table formulas been inserted? + if( pTableFieldTyp->HasWriterListeners() ) + { + // finish old action: table-frames have been created + // a selection can be made now + sal_uInt16 nActCnt; + for( nActCnt = 0; ActionPend(); ++nActCnt ) + EndAllAction(); + + for( ; nActCnt; --nActCnt ) + StartAllAction(); + } + GetDoc()->getIDocumentFieldsAccess().UnlockExpFields(); + GetDoc()->getIDocumentFieldsAccess().UpdateFields(false); + EndAllAction(); + + return bRet; +} + +void SwFEShell::PastePages( SwFEShell& rToFill, sal_uInt16 nStartPage, sal_uInt16 nEndPage) +{ + Push(); + if(!GotoPage(nStartPage)) + { + Pop(PopMode::DeleteCurrent); + return; + } + MovePage( GetThisFrame, GetFirstSub ); + SwPaM aCpyPam( *GetCursor()->GetPoint() ); + OUString sStartingPageDesc = GetPageDesc( GetCurPageDesc()).GetName(); + SwPageDesc* pDesc = rToFill.FindPageDescByName( sStartingPageDesc, true ); + if( pDesc ) + rToFill.ChgCurPageDesc( *pDesc ); + + if(!GotoPage(nEndPage)) + { + Pop(PopMode::DeleteCurrent); + return; + } + //if the page starts with a table a paragraph has to be inserted before + SwNode* pTableNode = aCpyPam.GetNode().FindTableNode(); + if(pTableNode) + { + //insert a paragraph + StartUndo(SwUndoId::INSERT); + SwNodeIndex aTableIdx( *pTableNode, -1 ); + SwPosition aBefore(aTableIdx); + if(GetDoc()->getIDocumentContentOperations().AppendTextNode( aBefore )) + { + SwPaM aTmp(aBefore); + aCpyPam = aTmp; + } + EndUndo(SwUndoId::INSERT); + } + + MovePage( GetThisFrame, GetLastSub ); + aCpyPam.SetMark(); + *aCpyPam.GetMark() = *GetCursor()->GetPoint(); + + SET_CURR_SHELL( this ); + + StartAllAction(); + GetDoc()->getIDocumentFieldsAccess().LockExpFields(); + SetSelection(aCpyPam); + // copy the text of the selection + SwEditShell::Copy(&rToFill); + + if(pTableNode) + { + //remove the inserted paragraph + Undo(); + //remove the paragraph in the second doc, too + SwNodeIndex aIdx( rToFill.GetDoc()->GetNodes().GetEndOfExtras(), 2 ); + SwPaM aPara( aIdx ); //DocStart + rToFill.GetDoc()->getIDocumentContentOperations().DelFullPara(aPara); + } + // now the page bound objects + // additionally copy page bound frames + if( !GetDoc()->GetSpzFrameFormats()->empty() ) + { + // create a draw view if necessary + if( !rToFill.Imp()->GetDrawView() ) + rToFill.MakeDrawView(); + + for ( auto pCpyFormat : *GetDoc()->GetSpzFrameFormats() ) + { + SwFormatAnchor aAnchor( pCpyFormat->GetAnchor() ); + if ((RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId()) && + aAnchor.GetPageNum() >= nStartPage && aAnchor.GetPageNum() <= nEndPage) + { + aAnchor.SetPageNum( aAnchor.GetPageNum() - nStartPage + 1); + } + else + continue; + rToFill.GetDoc()->getIDocumentLayoutAccess().CopyLayoutFormat( *pCpyFormat, aAnchor, true, true ); + } + } + GetDoc()->getIDocumentFieldsAccess().UnlockExpFields(); + GetDoc()->getIDocumentFieldsAccess().UpdateFields(false); + Pop(PopMode::DeleteCurrent); + EndAllAction(); +} + +comphelper::OInterfaceContainerHelper2& SwFEShell::GetPasteListeners() { return m_aPasteListeners; } + +bool SwFEShell::GetDrawObjGraphic( SotClipboardFormatId nFormat, Graphic& rGrf ) const +{ + OSL_ENSURE( Imp()->HasDrawView(), "GetDrawObjGraphic without DrawView?" ); + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + bool bConvert = true; + if( rMrkList.GetMarkCount() ) + { + if( rMrkList.GetMarkCount() == 1 && + dynamic_cast< const SwVirtFlyDrawObj* >(rMrkList.GetMark( 0 )->GetMarkedSdrObj()) != nullptr ) + { + // select frame + if( CNT_GRF == GetCntType() ) + { + const Graphic* pGrf( GetGraphic() ); + if ( pGrf ) + { + Graphic aGrf( *pGrf ); + if( SotClipboardFormatId::GDIMETAFILE == nFormat ) + { + if( GraphicType::Bitmap != aGrf.GetType() ) + { + rGrf = aGrf; + bConvert = false; + } + else if( GetWin() ) + { + Size aSz; + Point aPt; + GetGrfSize( aSz ); + + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + pVirtDev->EnableOutput( false ); + + MapMode aTmp( GetWin()->GetMapMode() ); + aTmp.SetOrigin( aPt ); + pVirtDev->SetMapMode( aTmp ); + + GDIMetaFile aMtf; + aMtf.Record( pVirtDev.get() ); + aGrf.Draw( pVirtDev, aPt, aSz ); + aMtf.Stop(); + aMtf.SetPrefMapMode( aTmp ); + aMtf.SetPrefSize( aSz ); + rGrf = aMtf; + } + } + else if( GraphicType::Bitmap == aGrf.GetType() ) + { + rGrf = aGrf; + bConvert = false; + } + else + { + // Not the original size, but the current one. + // Otherwise it could happen that for vector graphics + // many MB's of memory are allocated. + const Size aSz( GetSelectedFlyFrame()->getFramePrintArea().SSize() ); + ScopedVclPtrInstance< VirtualDevice > pVirtDev(*GetWin()); + + MapMode aTmp( MapUnit::MapTwip ); + pVirtDev->SetMapMode( aTmp ); + if( pVirtDev->SetOutputSize( aSz ) ) + { + aGrf.Draw( pVirtDev.get(), Point(), aSz ); + rGrf = pVirtDev->GetBitmapEx( Point(), aSz ); + } + else + { + rGrf = aGrf; + bConvert = false; + } + } + } + } + } + else if( SotClipboardFormatId::GDIMETAFILE == nFormat ) + rGrf = Imp()->GetDrawView()->GetMarkedObjMetaFile(); + else if( SotClipboardFormatId::BITMAP == nFormat || SotClipboardFormatId::PNG == nFormat ) + rGrf = Imp()->GetDrawView()->GetMarkedObjBitmapEx(); + } + return bConvert; +} + +// #i50824# +// replace method <lcl_RemoveOleObjsFromSdrModel> by <lcl_ConvertSdrOle2ObjsToSdrGrafObjs> +static void lcl_ConvertSdrOle2ObjsToSdrGrafObjs( SdrModel& _rModel ) +{ + for ( sal_uInt16 nPgNum = 0; nPgNum < _rModel.GetPageCount(); ++nPgNum ) + { + // setup object iterator in order to iterate through all objects + // including objects in group objects, but exclusive group objects. + SdrObjListIter aIter(_rModel.GetPage(nPgNum)); + while( aIter.IsMore() ) + { + SdrOle2Obj* pOle2Obj = dynamic_cast< SdrOle2Obj* >( aIter.Next() ); + if( pOle2Obj ) + { + // found an ole2 shape + SdrObjList* pObjList = pOle2Obj->getParentSdrObjListFromSdrObject(); + + // get its graphic + Graphic aGraphic; + pOle2Obj->Connect(); + const Graphic* pGraphic = pOle2Obj->GetGraphic(); + if( pGraphic ) + aGraphic = *pGraphic; + pOle2Obj->Disconnect(); + + // create new graphic shape with the ole graphic and shape size + SdrGrafObj* pGraphicObj = new SdrGrafObj( + _rModel, + aGraphic, + pOle2Obj->GetCurrentBoundRect()); + // apply layer of ole2 shape at graphic shape + pGraphicObj->SetLayer( pOle2Obj->GetLayer() ); + + // replace ole2 shape with the new graphic object and delete the ol2 shape + SdrObject* pRemovedObject = pObjList->ReplaceObject( pGraphicObj, pOle2Obj->GetOrdNum() ); + SdrObject::Free( pRemovedObject ); + } + } + } +} + +void SwFEShell::Paste( SvStream& rStrm, SwPasteSdr nAction, const Point* pPt ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + StartUndo(); + + std::unique_ptr< FmFormModel > pModel( + new FmFormModel( + nullptr, + GetDoc()->GetDocShell())); + + pModel->GetItemPool().FreezeIdRanges(); + + rStrm.Seek(0); + + uno::Reference< io::XInputStream > xInputStream( new utl::OInputStreamWrapper( rStrm ) ); + SvxDrawingLayerImport( pModel.get(), xInputStream ); + + if ( !Imp()->HasDrawView() ) + Imp()->MakeDrawView(); + + Point aPos( pPt ? *pPt : GetCharRect().Pos() ); + SdrView *pView = Imp()->GetDrawView(); + + // drop on the existing object: replace object or apply new attributes + if( pModel->GetPageCount() > 0 && + 1 == pModel->GetPage(0)->GetObjCount() && + 1 == pView->GetMarkedObjectList().GetMarkCount() ) + { + // replace a marked 'virtual' drawing object + // by its corresponding 'master' drawing object in the mark list. + SwDrawView::ReplaceMarkedDrawVirtObjs( *pView ); + + SdrObject* pClpObj = pModel->GetPage(0)->GetObj(0); + SdrObject* pOldObj = pView->GetMarkedObjectList().GetMark( 0 )->GetMarkedSdrObj(); + + if( SwPasteSdr::SetAttr == nAction && dynamic_cast<const SwVirtFlyDrawObj*>( pOldObj) != nullptr ) + nAction = SwPasteSdr::Replace; + + switch( nAction ) + { + case SwPasteSdr::Replace: + { + const SwFrameFormat* pFormat(nullptr); + const SwFrame* pAnchor(nullptr); + if( dynamic_cast<const SwVirtFlyDrawObj*>( pOldObj) != nullptr ) + { + pFormat = FindFrameFormat( pOldObj ); + + Point aNullPt; + SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrameFormat*>(pFormat)->GetFrame( &aNullPt ); + pAnchor = pFlyFrame ? pFlyFrame->GetAnchorFrame() : nullptr; + + if (!pAnchor || pAnchor->FindFooterOrHeader()) + { + // if there is a textframe in the header/footer: + // do not replace but insert + nAction = SwPasteSdr::Insert; + break; + } + } + + SdrObject* pNewObj(pClpObj->CloneSdrObject(pOldObj->getSdrModelFromSdrObject())); + tools::Rectangle aOldObjRect( pOldObj->GetCurrentBoundRect() ); + Size aOldObjSize( aOldObjRect.GetSize() ); + tools::Rectangle aNewRect( pNewObj->GetCurrentBoundRect() ); + Size aNewSize( aNewRect.GetSize() ); + + Fraction aScaleWidth( aOldObjSize.Width(), aNewSize.Width() ); + Fraction aScaleHeight( aOldObjSize.Height(), aNewSize.Height()); + pNewObj->NbcResize( aNewRect.TopLeft(), aScaleWidth, aScaleHeight); + + Point aVec = aOldObjRect.TopLeft() - aNewRect.TopLeft(); + pNewObj->NbcMove(Size(aVec.getX(), aVec.getY())); + + if( dynamic_cast<const SdrUnoObj*>( pNewObj) != nullptr ) + pNewObj->SetLayer( GetDoc()->getIDocumentDrawModelAccess().GetControlsId() ); + else if( dynamic_cast<const SdrUnoObj*>( pOldObj) != nullptr ) + pNewObj->SetLayer( GetDoc()->getIDocumentDrawModelAccess().GetHeavenId() ); + else + pNewObj->SetLayer( pOldObj->GetLayer() ); + + if( dynamic_cast<const SwVirtFlyDrawObj*>( pOldObj) != nullptr ) + { + // store attributes, then set SdrObject + SfxItemSet aFrameSet( mxDoc->GetAttrPool(), + svl::Items<RES_SURROUND, RES_ANCHOR>{} ); + aFrameSet.Set( pFormat->GetAttrSet() ); + + Point aNullPt; + if( pAnchor->IsTextFrame() && static_cast<const SwTextFrame*>(pAnchor)->IsFollow() ) + { + const SwTextFrame* pTmp = static_cast<const SwTextFrame*>(pAnchor); + do { + pTmp = pTmp->FindMaster(); + OSL_ENSURE( pTmp, "Where's my Master?" ); + } while( pTmp->IsFollow() ); + pAnchor = pTmp; + } + if( dynamic_cast<const SdrCaptionObj*>( pOldObj) != nullptr) + aNullPt = static_cast<SdrCaptionObj*>(pOldObj)->GetTailPos(); + else + aNullPt = aOldObjRect.TopLeft(); + + Point aNewAnchor = pAnchor->GetFrameAnchorPos( ::HasWrap( pOldObj ) ); + // OD 2004-04-05 #i26791# - direct positioning of Writer + // fly frame object for <SwDoc::Insert(..)> + pNewObj->NbcSetRelativePos( aNullPt - aNewAnchor ); + pNewObj->NbcSetAnchorPos( aNewAnchor ); + + pOldObj->GetOrdNum(); + + DelSelectedObj(); + + GetDoc()->getIDocumentContentOperations().InsertDrawObj( *GetCursor(), *pNewObj, aFrameSet ); + } + else + { + // #i123922# for handling MasterObject and virtual ones correctly, SW + // wants us to call ReplaceObject at the page, but that also + // triggers the same assertion (I tried it), so stay at the view method + pView->ReplaceObjectAtView(pOldObj, *Imp()->GetPageView(), pNewObj); + } + } + break; + + case SwPasteSdr::SetAttr: + { + SfxItemSet aSet( GetAttrPool() ); + const SdrGrafObj* pSdrGrafObj = dynamic_cast< const SdrGrafObj* >(pClpObj); + + if(pSdrGrafObj) + { + SdrObject* pTarget = nullptr; + + if(0 != pView->GetMarkedObjectList().GetMarkCount()) + { + // try to get target (if it's at least one, take first) + SdrMark* pMark = pView->GetMarkedObjectList().GetMark(0); + + if(pMark) + { + pTarget = pMark->GetMarkedSdrObj(); + } + } + + if(pTarget) + { + // copy ItemSet from target + aSet.Set(pTarget->GetMergedItemSet()); + } + + // for SdrGrafObj, use the graphic as fill style argument + const Graphic& rGraphic = pSdrGrafObj->GetGraphic(); + + if(GraphicType::NONE != rGraphic.GetType() && GraphicType::Default != rGraphic.GetType()) + { + aSet.Put(XFillBitmapItem(OUString(), rGraphic)); + aSet.Put(XFillStyleItem(drawing::FillStyle_BITMAP)); + } + } + else + { + aSet.Put(pClpObj->GetMergedItemSet()); + } + + pView->SetAttributes( aSet ); + } + break; + + default: + nAction = SwPasteSdr::Insert; + break; + } + } + else + nAction = SwPasteSdr::Insert; + + if( SwPasteSdr::Insert == nAction ) + { + ::sw::DrawUndoGuard drawUndoGuard(GetDoc()->GetIDocumentUndoRedo()); + + bool bDesignMode = pView->IsDesignMode(); + if( !bDesignMode ) + pView->SetDesignMode(); + + // #i50824# + // method <lcl_RemoveOleObjsFromSdrModel> replaced by <lcl_ConvertSdrOle2ObjsToSdrGrafObjs> + lcl_ConvertSdrOle2ObjsToSdrGrafObjs(*pModel); + pView->Paste(*pModel, aPos, nullptr, SdrInsertFlags::NONE); + + const size_t nCnt = pView->GetMarkedObjectList().GetMarkCount(); + if( nCnt ) + { + const Point aNull( 0, 0 ); + for( size_t i=0; i < nCnt; ++i ) + { + SdrObject *pObj = pView->GetMarkedObjectList().GetMark(i)->GetMarkedSdrObj(); + pObj->ImpSetAnchorPos( aNull ); + } + + pView->SetCurrentObj( OBJ_GRUP ); + if ( nCnt > 1 ) + pView->GroupMarked(); + SdrObject *pObj = pView->GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj(); + if( dynamic_cast<const SdrUnoObj*>( pObj) != nullptr ) + { + pObj->SetLayer( GetDoc()->getIDocumentDrawModelAccess().GetControlsId() ); + bDesignMode = true; + } + else + pObj->SetLayer( GetDoc()->getIDocumentDrawModelAccess().GetHeavenId() ); + const tools::Rectangle &rSnap = pObj->GetSnapRect(); + const Size aDiff( rSnap.GetWidth()/2, rSnap.GetHeight()/2 ); + pView->MoveMarkedObj( aDiff ); + ImpEndCreate(); + if( !bDesignMode ) + pView->SetDesignMode( false ); + } + } + EndUndo(); + EndAllAction(); +} + +bool SwFEShell::Paste(const Graphic &rGrf, const OUString& rURL) +{ + SET_CURR_SHELL( this ); + SdrObject* pObj = nullptr; + SdrView *pView = Imp()->GetDrawView(); + + bool bRet = 1 == pView->GetMarkedObjectList().GetMarkCount(); + if (bRet) + { + pObj = pView->GetMarkedObjectList().GetMark( 0 )->GetMarkedSdrObj(); + bRet = pObj->IsClosedObj() && dynamic_cast<const SdrOle2Obj*>( pObj) == nullptr; + } + + if( bRet && pObj ) + { + // #i123922# added code to handle the two cases of SdrGrafObj and a fillable, non- + // OLE object in focus + SdrObject* pResult = pObj; + + if(dynamic_cast< SdrGrafObj* >(pObj)) + { + SdrGrafObj* pNewGrafObj(static_cast<SdrGrafObj*>(pObj->CloneSdrObject(pObj->getSdrModelFromSdrObject()))); + + pNewGrafObj->SetGraphic(rGrf); + + // #i123922# for handling MasterObject and virtual ones correctly, SW + // wants us to call ReplaceObject at the page, but that also + // triggers the same assertion (I tried it), so stay at the view method + pView->ReplaceObjectAtView(pObj, *pView->GetSdrPageView(), pNewGrafObj); + + OUString aReferer; + SwDocShell *pDocShell = GetDoc()->GetDocShell(); + if (pDocShell->HasName()) { + aReferer = pDocShell->GetMedium()->GetName(); + } + + // set in all cases - the Clone() will have copied an existing link (!) + pNewGrafObj->SetGraphicLink(rURL, aReferer, OUString()); + + pResult = pNewGrafObj; + } + else + { + pView->AddUndo(std::make_unique<SdrUndoAttrObj>(*pObj)); + + SfxItemSet aSet(pView->GetModel()->GetItemPool(), svl::Items<XATTR_FILLSTYLE, XATTR_FILLBITMAP>{}); + + aSet.Put(XFillStyleItem(drawing::FillStyle_BITMAP)); + aSet.Put(XFillBitmapItem(OUString(), rGrf)); + pObj->SetMergedItemSetAndBroadcast(aSet); + } + + // we are done; mark the modified/new object + pView->MarkObj(pResult, pView->GetSdrPageView()); + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/fedesc.cxx b/sw/source/core/frmedt/fedesc.cxx new file mode 100644 index 000000000..819fd1d4c --- /dev/null +++ b/sw/source/core/frmedt/fedesc.cxx @@ -0,0 +1,244 @@ +/* -*- 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 <fesh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <pam.hxx> +#include <fmtpdsc.hxx> +#include <pagedesc.hxx> +#include <tabfrm.hxx> +#include <SwStyleNameMapper.hxx> +#include <ndtxt.hxx> + +size_t SwFEShell::GetPageDescCnt() const +{ + return GetDoc()->GetPageDescCnt(); +} + +void SwFEShell::ChgCurPageDesc( const SwPageDesc& rDesc ) +{ +#if OSL_DEBUG_LEVEL > 0 + // SS does not change PageDesc, but only sets the attribute. + // The Pagedesc should be available in the document + bool bFound = false; + for ( size_t nTst = 0; nTst < GetPageDescCnt(); ++nTst ) + if ( &rDesc == &GetPageDesc( nTst ) ) + bFound = true; + OSL_ENSURE( bFound, "ChgCurPageDesc with invalid descriptor." ); +#endif + + StartAllAction(); + + SwPageFrame *pPage = GetCurrFrame()->FindPageFrame(); + const SwFrame *pFlow = nullptr; + ::std::optional<sal_uInt16> oPageNumOffset; + + OSL_ENSURE( !GetCursor()->HasMark(), "ChgCurPageDesc only without selection!"); + + SET_CURR_SHELL( this ); + while ( pPage ) + { + pFlow = pPage->FindFirstBodyContent(); + if ( pFlow ) + { + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + const SwFormatPageDesc& rPgDesc = pFlow->GetPageDescItem(); + if( rPgDesc.GetPageDesc() ) + { + // we found the culprit + oPageNumOffset = rPgDesc.GetNumOffset(); + break; + } + } + pPage = static_cast<SwPageFrame*>( pPage->GetPrev() ); + } + if ( !pPage ) + { + pPage = static_cast<SwPageFrame*>(GetLayout()->Lower()); + pFlow = pPage->FindFirstBodyContent(); + if ( !pFlow ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pFlow = pPage->FindFirstBodyContent(); + OSL_ENSURE( pFlow, "Document without content?!?" ); + } + } + + // use pagenumber + SwFormatPageDesc aNew( &rDesc ); + aNew.SetNumOffset( oPageNumOffset ); + + if ( pFlow->IsInTab() ) + GetDoc()->SetAttr( aNew, *const_cast<SwFormat*>(static_cast<SwFormat const *>(pFlow->FindTabFrame()->GetFormat())) ); + else + { + assert(pFlow->IsContentFrame()); + SwPaM aPaM( pFlow->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pFlow)->GetTextNodeFirst() // first, for PAGEDESC + : *static_cast<const SwNoTextFrame*>(pFlow)->GetNode() ); + GetDoc()->getIDocumentContentOperations().InsertPoolItem( + aPaM, aNew, SetAttrMode::DEFAULT, GetLayout()); + } + EndAllActionAndCall(); +} + +void SwFEShell::ChgPageDesc( size_t i, const SwPageDesc &rChged ) +{ + StartAllAction(); + SET_CURR_SHELL( this ); + //Fix i64842: because Undo has a very special way to handle header/footer content + // we have to copy the page descriptor before calling ChgPageDesc. + SwPageDesc aDesc( rChged ); + { + ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + GetDoc()->CopyPageDesc(rChged, aDesc); + } + GetDoc()->ChgPageDesc( i, aDesc ); + EndAllActionAndCall(); +} + +const SwPageDesc& SwFEShell::GetPageDesc( size_t i ) const +{ + return GetDoc()->GetPageDesc( i ); +} + +SwPageDesc* SwFEShell::FindPageDescByName( const OUString& rName, + bool bGetFromPool, + size_t* pPos ) +{ + SwPageDesc* pDesc = GetDoc()->FindPageDesc(rName, pPos); + if( !pDesc && bGetFromPool ) + { + sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromUIName( rName, SwGetPoolIdFromName::PageDesc ); + if( USHRT_MAX != nPoolId && + nullptr != (pDesc = GetDoc()->getIDocumentStylePoolAccess().GetPageDescFromPool( nPoolId )) + && pPos ) + // appended always + *pPos = GetDoc()->GetPageDescCnt() - 1 ; + } + return pDesc; +} + +size_t SwFEShell::GetMousePageDesc( const Point &rPt ) const +{ + if( GetLayout() ) + { + const SwPageFrame* pPage = + static_cast<const SwPageFrame*>( GetLayout()->Lower() ); + if( pPage ) + { + while( pPage->GetNext() && rPt.Y() > pPage->getFrameArea().Bottom() ) + pPage = static_cast<const SwPageFrame*>( pPage->GetNext() ); + SwDoc *pMyDoc = GetDoc(); + size_t nPos; + if (pMyDoc->ContainsPageDesc( pPage->GetPageDesc(), &nPos ) ) + return nPos; + } + } + return 0; +} + +size_t SwFEShell::GetCurPageDesc( const bool bCalcFrame ) const +{ + const SwFrame *pFrame = GetCurrFrame( bCalcFrame ); + if ( pFrame ) + { + const SwPageFrame *pPage = pFrame->FindPageFrame(); + if ( pPage ) + { + size_t nPos; + if (GetDoc()->ContainsPageDesc( pPage->GetPageDesc(), &nPos )) + return nPos; + } + } + return 0; +} + +// if inside all selection only one PageDesc, return this. +// Otherwise return 0 pointer +const SwPageDesc* SwFEShell::GetSelectedPageDescs() const +{ + const SwContentNode* pCNd; + const SwFrame* pMkFrame, *pPtFrame; + const SwPageDesc* pFnd, *pRetDesc = reinterpret_cast<SwPageDesc*>(sal_IntPtr(-1)); + const Point aNulPt; + std::pair<Point, bool> const tmp(aNulPt, false); + + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + + if( nullptr != (pCNd = rPaM.GetContentNode() ) && + nullptr != (pPtFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp))) + pPtFrame = pPtFrame->FindPageFrame(); + else + pPtFrame = nullptr; + + if( rPaM.HasMark() && + nullptr != (pCNd = rPaM.GetContentNode( false ) ) && + nullptr != (pMkFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp))) + pMkFrame = pMkFrame->FindPageFrame(); + else + pMkFrame = pPtFrame; + + if( !pMkFrame || !pPtFrame ) + pFnd = nullptr; + else if( pMkFrame == pPtFrame ) + pFnd = static_cast<const SwPageFrame*>(pMkFrame)->GetPageDesc(); + else + { + // swap pointer if PtFrame before MkFrame + if( static_cast<const SwPageFrame*>(pMkFrame)->GetPhyPageNum() > + static_cast<const SwPageFrame*>(pPtFrame)->GetPhyPageNum() ) + { + const SwFrame* pTmp = pMkFrame; pMkFrame = pPtFrame; pPtFrame = pTmp; + } + + // now check from MkFrame to PtFrame for equal PageDescs + pFnd = static_cast<const SwPageFrame*>(pMkFrame)->GetPageDesc(); + while( pFnd && pMkFrame != pPtFrame ) + { + pMkFrame = pMkFrame->GetNext(); + if( !pMkFrame || pFnd != static_cast<const SwPageFrame*>(pMkFrame)->GetPageDesc() ) + pFnd = nullptr; + } + } + + if( reinterpret_cast<SwPageDesc*>(sal_IntPtr(-1)) == pRetDesc ) + pRetDesc = pFnd; + else if( pFnd != pRetDesc ) + { + pRetDesc = nullptr; + break; + } + + } + + return pRetDesc; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/fefly1.cxx b/sw/source/core/frmedt/fefly1.cxx new file mode 100644 index 000000000..4ad007164 --- /dev/null +++ b/sw/source/core/frmedt/fefly1.cxx @@ -0,0 +1,2110 @@ +/* -*- 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 <hintids.hxx> +#include <o3tl/any.hxx> +#include <svl/itemiter.hxx> +#include <vcl/imapobj.hxx> +#include <editeng/protitem.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdouno.hxx> +#include <tools/globname.hxx> +#include <sot/exchange.hxx> +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <comphelper/types.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <fmtornt.hxx> +#include <fmturl.hxx> +#include <fmtfsize.hxx> +#include <docary.hxx> +#include <fesh.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <viewimp.hxx> +#include <viscrs.hxx> +#include <doc.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <dview.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <frmfmt.hxx> +#include <flyfrm.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <ndgrf.hxx> +#include <flyfrms.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <swundo.hxx> +#include <txatbase.hxx> +#include <frame.hxx> +#include <notxtfrm.hxx> +#include <HandleAnchorNodeChg.hxx> +#include <frmatr.hxx> +#include <fmtsrnd.hxx> +#include <ndole.hxx> +#include <fefly.hxx> +#include <fmtcnct.hxx> +#include <frameformats.hxx> +#include <textboxhelper.hxx> + + +using namespace ::com::sun::star; + +// Based on the request, changes to the specific layouts will be made, to +// fit to the format +static bool lcl_SetNewFlyPos( const SwNode& rNode, SwFormatAnchor& rAnchor, + const Point& rPt ) +{ + bool bRet = false; + const SwStartNode* pStNode = rNode.FindFlyStartNode(); + if( pStNode ) + { + SwPosition aPos( *pStNode ); + rAnchor.SetAnchor( &aPos ); + bRet = true; + } + else + { + const SwContentNode *pCntNd = rNode.GetContentNode(); + std::pair<Point, bool> const tmp(rPt, false); + const SwContentFrame* pCFrame = pCntNd ? pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp) : nullptr; + const SwPageFrame *pPg = pCFrame ? pCFrame->FindPageFrame() : nullptr; + + rAnchor.SetPageNum( pPg ? pPg->GetPhyPageNum() : 1 ); + rAnchor.SetType( RndStdIds::FLY_AT_PAGE ); + } + return bRet; +} + +static bool lcl_FindAnchorPos( + SwDoc& rDoc, + const Point& rPt, + const SwFrame& rFrame, + SfxItemSet& rSet ) +{ + bool bRet = true; + SwFormatAnchor aNewAnch( rSet.Get( RES_ANCHOR ) ); + RndStdIds nNew = aNewAnch.GetAnchorId(); + const SwFrame *pNewAnch; + + //determine new anchor + Point aTmpPnt( rPt ); + switch( nNew ) + { + case RndStdIds::FLY_AS_CHAR: // also include this? + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL + { + // starting from the upper-left corner of the Fly, + // search nearest ContentFrame + const SwFrame* pFrame = rFrame.IsFlyFrame() ? static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame() + : &rFrame; + pNewAnch = ::FindAnchor( pFrame, aTmpPnt ); + if( pNewAnch->IsProtected() ) + { + bRet = false; + break; + } + SwPosition aPos( pNewAnch->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pNewAnch)->GetTextNodeForParaProps() + : *static_cast<const SwNoTextFrame*>(pNewAnch)->GetNode() ); + if ((RndStdIds::FLY_AT_CHAR == nNew) || (RndStdIds::FLY_AS_CHAR == nNew)) + { + // textnode should be found, as only in those + // a content bound frame can be anchored + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + aTmpPnt.setX(aTmpPnt.getX() - 1); // do not land in the fly! + if( !pNewAnch->GetModelPositionForViewPoint( &aPos, aTmpPnt, &aState ) ) + { + assert(pNewAnch->IsTextFrame()); // because AT_CHAR/AS_CHAR + SwTextFrame const*const pTextFrame( + static_cast<SwTextFrame const*>(pNewAnch)); + if( pNewAnch->getFrameArea().Bottom() < aTmpPnt.Y() ) + { + aPos = pTextFrame->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + aPos = pTextFrame->MapViewToModelPos( + TextFrameIndex(pTextFrame->GetText().getLength())); + } + } + else + { + if ( SwCursorShell::PosInsideInputField( aPos ) ) + { + aPos.nContent = SwCursorShell::StartOfInputFieldAtPos( aPos ); + } + } + } + aNewAnch.SetAnchor( &aPos ); + } + break; + + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + { + // starting from the upper-left corner of the Fly + // search nearest SwFlyFrame + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + SwPosition aPos( rDoc.GetNodes() ); + aTmpPnt.setX(aTmpPnt.getX() - 1); // do not land in the fly! + rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aTmpPnt, &aState ); + pNewAnch = ::FindAnchor( + aPos.nNode.GetNode().GetContentNode()->getLayoutFrame(rFrame.getRootFrame(), nullptr, nullptr), + aTmpPnt )->FindFlyFrame(); + + if( pNewAnch && &rFrame != pNewAnch && !pNewAnch->IsProtected() ) + { + aPos.nNode = *static_cast<const SwFlyFrame*>(pNewAnch)->GetFormat()->GetContent(). + GetContentIdx(); + aNewAnch.SetAnchor( &aPos ); + break; + } + } + + nNew = RndStdIds::FLY_AT_PAGE; + aNewAnch.SetType( nNew ); + [[fallthrough]]; + + case RndStdIds::FLY_AT_PAGE: + pNewAnch = rFrame.FindPageFrame(); + aNewAnch.SetPageNum( pNewAnch->GetPhyPageNum() ); + break; + + default: + OSL_ENSURE( false, "Wrong Id for new anchor." ); + } + + rSet.Put( aNewAnch ); + return bRet; +} + +//! also used in unoframe.cxx + +bool sw_ChkAndSetNewAnchor( + const SwFlyFrame& rFly, + SfxItemSet& rSet ) +{ + const SwFrameFormat& rFormat = *rFly.GetFormat(); + const SwFormatAnchor &rOldAnch = rFormat.GetAnchor(); + const RndStdIds nOld = rOldAnch.GetAnchorId(); + + RndStdIds nNew = rSet.Get( RES_ANCHOR ).GetAnchorId(); + + if( nOld == nNew ) + return false; + + SwDoc* pDoc = const_cast<SwDoc*>(rFormat.GetDoc()); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( !(nNew == RndStdIds::FLY_AT_PAGE && + (RndStdIds::FLY_AT_PARA==nOld || RndStdIds::FLY_AT_CHAR==nOld || RndStdIds::FLY_AS_CHAR==nOld ) && + pDoc->IsInHeaderFooter( rOldAnch.GetContentAnchor()->nNode )), + "forbidden anchor change in Head/Foot." ); +#endif + + return ::lcl_FindAnchorPos( *pDoc, rFly.getFrameArea().Pos(), rFly, rSet ); +} + +void SwFEShell::SelectFlyFrame( SwFlyFrame& rFrame ) +{ + SET_CURR_SHELL( this ); + + // The frame is new, thus select it. + // !! Always select the frame, if it's not selected. + // - it could be a new "old" one because the anchor was changed + // - "old" frames have had to be selected previously otherwise they could + // not have been changed + // The frames should not be selected by the document position, because + // it should have been selected! + SwViewShellImp *pImpl = Imp(); + if( GetWin() ) + { + OSL_ENSURE( rFrame.IsFlyFrame(), "SelectFlyFrame wants a Fly" ); + + // nothing to be done if the Fly already was selected + if (GetSelectedFlyFrame() == &rFrame) + return; + + // assure the anchor is drawn + if( rFrame.IsFlyInContentFrame() && rFrame.GetAnchorFrame() ) + rFrame.GetAnchorFrame()->SetCompletePaint(); + + if( pImpl->GetDrawView()->AreObjectsMarked() ) + pImpl->GetDrawView()->UnmarkAll(); + + pImpl->GetDrawView()->MarkObj( rFrame.GetVirtDrawObj(), + pImpl->GetPageView() ); + + rFrame.SelectionHasChanged(this); + + KillPams(); + ClearMark(); + SelFlyGrabCursor(); + } +} + +// Get selected fly +SwFlyFrame* SwFEShell::GetSelectedFlyFrame() const +{ + if ( Imp()->HasDrawView() ) + { + // A Fly is only accessible if it is selected + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if( rMrkList.GetMarkCount() != 1 ) + return nullptr; + + SdrObject *pO = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + + SwVirtFlyDrawObj *pFlyObj = dynamic_cast<SwVirtFlyDrawObj*>(pO); + + return pFlyObj ? pFlyObj->GetFlyFrame() : nullptr; + } + return nullptr; +} + +// Get current fly in which the cursor is positioned +SwFlyFrame* SwFEShell::GetCurrFlyFrame(const bool bCalcFrame) const +{ + SwContentFrame *pContent = GetCurrFrame(bCalcFrame); + return pContent ? pContent->FindFlyFrame() : nullptr; +} + +// Get selected fly, but if none Get current fly in which the cursor is positioned +SwFlyFrame* SwFEShell::GetSelectedOrCurrFlyFrame() const +{ + SwFlyFrame *pFly = GetSelectedFlyFrame(); + if (pFly) + return pFly; + return GetCurrFlyFrame(); +} + +// Returns non-null pointer, if the current Fly could be anchored to another one (so it is inside) +const SwFrameFormat* SwFEShell::IsFlyInFly() +{ + SET_CURR_SHELL( this ); + + if ( !Imp()->HasDrawView() ) + return nullptr; + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if ( !rMrkList.GetMarkCount() ) + { + SwFlyFrame *pFly = GetCurrFlyFrame(false); + if (!pFly) + return nullptr; + return pFly->GetFormat(); + } + else if ( rMrkList.GetMarkCount() != 1 || + !GetUserCall(rMrkList.GetMark( 0 )->GetMarkedSdrObj()) ) + return nullptr; + + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + + SwFrameFormat *pFormat = FindFrameFormat( pObj ); + if( pFormat && RndStdIds::FLY_AT_FLY == pFormat->GetAnchor().GetAnchorId() ) + { + const SwFrame* pFly; + if (SwVirtFlyDrawObj* pFlyObj = dynamic_cast<SwVirtFlyDrawObj *>(pObj)) + { + pFly = pFlyObj->GetFlyFrame()->GetAnchorFrame(); + } + else + { + pFly = static_cast<SwDrawContact*>(GetUserCall(pObj))->GetAnchorFrame(pObj); + } + + OSL_ENSURE( pFly, "IsFlyInFly: Where's my anchor?" ); + OSL_ENSURE( pFly->IsFlyFrame(), "IsFlyInFly: Funny anchor!" ); + return static_cast<const SwFlyFrame*>(pFly)->GetFormat(); + } + + Point aTmpPos = pObj->GetCurrentBoundRect().TopLeft(); + + SwFrame *pTextFrame; + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + SwNodeIndex aSwNodeIndex( GetDoc()->GetNodes() ); + SwPosition aPos( aSwNodeIndex ); + Point aPoint( aTmpPos ); + aPoint.setX(aPoint.getX() - 1); //do not land in the fly!! + GetLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + // determine text frame by left-top-corner of object + SwContentNode *pNd = aPos.nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(aTmpPos, false); + pTextFrame = pNd ? pNd->getLayoutFrame(GetLayout(), nullptr, &tmp) : nullptr; + } + const SwFrame *pTmp = pTextFrame ? ::FindAnchor(pTextFrame, aTmpPos) : nullptr; + const SwFlyFrame *pFly = pTmp ? pTmp->FindFlyFrame() : nullptr; + if( pFly ) + return pFly->GetFormat(); + return nullptr; +} + +void SwFEShell::SetFlyPos( const Point& rAbsPos ) +{ + SET_CURR_SHELL( this ); + + // Determine reference point in document coordinates + SwFlyFrame *pFly = GetCurrFlyFrame(false); + if (!pFly) + return; + + //SwSaveHdl aSaveX( Imp() ); + + // Set an anchor starting from the absolute position for paragraph bound Flys + // Anchor and new RelPos will be calculated and set by the Fly + if ( pFly->IsFlyAtContentFrame() ) + { + if(pFly->IsFlyFreeFrame() && static_cast< SwFlyFreeFrame* >(pFly)->isTransformableSwFrame()) + { + // RotateFlyFrame3: When we have a change and are in transformed state (e.g. rotation used), + // we need to correct the absolute position (rAbsPos) which was created in + // transformed coordinates to untransformed state + TransformableSwFrame* pTransformableSwFrame(static_cast<SwFlyFreeFrame*>(pFly)->getTransformableSwFrame()); + const SwRect aUntransformedFrameArea(pTransformableSwFrame->getUntransformedFrameArea()); + const Point aNewAbsPos( + rAbsPos.X() + aUntransformedFrameArea.Left() - pFly->getFrameArea().Left(), + rAbsPos.Y() + aUntransformedFrameArea.Top() - pFly->getFrameArea().Top()); + static_cast<SwFlyAtContentFrame*>(pFly)->SetAbsPos(aNewAbsPos); + } + else + { + static_cast<SwFlyAtContentFrame*>(pFly)->SetAbsPos( rAbsPos ); + } + } + else + { + const SwFrame *pAnch = pFly->GetAnchorFrame(); + Point aOrient( pAnch->getFrameArea().Pos() ); + + if ( pFly->IsFlyInContentFrame() ) + aOrient.setX(rAbsPos.getX()); + + // calculate RelPos. + aOrient.setX(rAbsPos.getX() - aOrient.getX()); + aOrient.setY(rAbsPos.getY() - aOrient.getY()); + pFly->ChgRelPos( aOrient ); + } + CallChgLnk(); // call the AttrChangeNotify on the UI-side. +} + +Point SwFEShell::FindAnchorPos( const Point& rAbsPos, bool bMoveIt ) +{ + Point aRet; + + SET_CURR_SHELL( this ); + + if ( !Imp()->HasDrawView() ) + return aRet; + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if ( rMrkList.GetMarkCount() != 1 || + !GetUserCall(rMrkList.GetMark( 0 )->GetMarkedSdrObj()) ) + return aRet; + + SdrObject* pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + // #i28701# + SwAnchoredObject* pAnchoredObj = ::GetUserCall( pObj )->GetAnchoredObj( pObj ); + SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + const RndStdIds nAnchorId = rFormat.GetAnchor().GetAnchorId(); + + if ( RndStdIds::FLY_AS_CHAR == nAnchorId ) + return aRet; + + bool bFlyFrame = dynamic_cast<SwVirtFlyDrawObj *>(pObj) != nullptr; + + bool bTextBox = false; + if (rFormat.Which() == RES_DRAWFRMFMT) + { + bTextBox = SwTextBoxHelper::isTextBox(&rFormat, RES_DRAWFRMFMT); + } + + SwFlyFrame* pFly = nullptr; + const SwFrame* pFooterOrHeader = nullptr; + + if( bFlyFrame ) + { + // Calculate reference point in document coordinates + SwContentFrame *pContent = GetCurrFrame( false ); + if( !pContent ) + return aRet; + pFly = pContent->FindFlyFrame(); + if ( !pFly ) + return aRet; + const SwFrame* pOldAnch = pFly->GetAnchorFrame(); + if( !pOldAnch ) + return aRet; + if ( RndStdIds::FLY_AT_PAGE != nAnchorId ) + { + pFooterOrHeader = pContent->FindFooterOrHeader(); + } + } + else if (bTextBox) + { + auto pFlyFormat = dynamic_cast<const SwFlyFrameFormat*>( + SwTextBoxHelper::getOtherTextBoxFormat(&rFormat, RES_DRAWFRMFMT)); + if (pFlyFormat) + { + pFly = pFlyFormat->GetFrame(); + } + } + + // set <pFooterOrHeader> also for drawing + // objects, but not for control objects. + // Necessary for moving 'anchor symbol' at the user interface inside header/footer. + else if ( !::CheckControlLayer( pObj ) ) + { + SwContentFrame *pContent = GetCurrFrame( false ); + if( !pContent ) + return aRet; + pFooterOrHeader = pContent->FindFooterOrHeader(); + } + + // Search nearest SwFlyFrame starting from the upper-left corner + // of the fly + SwContentFrame *pTextFrame = nullptr; + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + SwPosition aPos( GetDoc()->GetNodes().GetEndOfExtras() ); + Point aTmpPnt( rAbsPos ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aTmpPnt, &aState ); + if (aPos.nNode != GetDoc()->GetNodes().GetEndOfExtras().GetIndex() + && (nAnchorId != RndStdIds::FLY_AT_CHAR || !PosInsideInputField(aPos))) + { + SwContentNode* pCNode = aPos.nNode.GetNode().GetContentNode(); + assert(pCNode); + pTextFrame = pCNode->getLayoutFrame(GetLayout(), &aPos, nullptr); + } + } + const SwFrame *pNewAnch = nullptr; + if( pTextFrame != nullptr ) + { + if ( RndStdIds::FLY_AT_PAGE == nAnchorId ) + { + pNewAnch = pTextFrame->FindPageFrame(); + } + else + { + pNewAnch = ::FindAnchor( pTextFrame, rAbsPos ); + + if( RndStdIds::FLY_AT_FLY == nAnchorId ) // LAYER_IMPL + { + pNewAnch = pNewAnch->FindFlyFrame(); + } + } + } + + if( pNewAnch && !pNewAnch->IsProtected() ) + { + const SwFlyFrame* pCheck = (bFlyFrame || bTextBox) ? pNewAnch->FindFlyFrame() : nullptr; + // If we land inside the frame, make sure + // that the frame does not land inside its own content + while( pCheck ) + { + if( pCheck == pFly ) + break; + const SwFrame *pTmp = pCheck->GetAnchorFrame(); + pCheck = pTmp ? pTmp->FindFlyFrame() : nullptr; + } + + // Do not switch from header/footer to another area, + // do not switch to a header/footer + if( !pCheck && + pFooterOrHeader == pNewAnch->FindFooterOrHeader() ) + { + aRet = pNewAnch->GetFrameAnchorPos( ::HasWrap( pObj ) ); + + if ( bMoveIt || (nAnchorId == RndStdIds::FLY_AT_CHAR) ) + { + SwFormatAnchor aAnch( rFormat.GetAnchor() ); + switch ( nAnchorId ) + { + case RndStdIds::FLY_AT_PARA: + { + SwPosition pos = *aAnch.GetContentAnchor(); + pos.nNode = pTextFrame->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pTextFrame)->GetTextNodeForParaProps() + : *static_cast<const SwNoTextFrame*>(pTextFrame)->GetNode(); + pos.nContent.Assign(nullptr,0); + aAnch.SetAnchor( &pos ); + break; + } + case RndStdIds::FLY_AT_PAGE: + { + aAnch.SetPageNum( static_cast<const SwPageFrame*>(pNewAnch)-> + GetPhyPageNum() ); + break; + } + + case RndStdIds::FLY_AT_FLY: + { + SwPosition aPos( *static_cast<const SwFlyFrame*>(pNewAnch)->GetFormat()-> + GetContent().GetContentIdx() ); + aAnch.SetAnchor( &aPos ); + break; + } + + case RndStdIds::FLY_AT_CHAR: + { + SwPosition pos = *aAnch.GetContentAnchor(); + Point aTmpPnt( rAbsPos ); + if( pTextFrame->GetModelPositionForViewPoint( &pos, aTmpPnt ) ) + { + SwRect aTmpRect; + pTextFrame->GetCharRect( aTmpRect, pos ); + aRet = aTmpRect.Pos(); + } + else + { + pos = static_cast<SwTextFrame const*>(pTextFrame)->MapViewToModelPos(TextFrameIndex(0)); + } + aAnch.SetAnchor( &pos ); + break; + } + default: + break; + + } + + if( bMoveIt ) + { + StartAllAction(); + // --> handle change of anchor node: + // if count of the anchor frame also change, the fly frames have to be + // re-created. Thus, delete all fly frames except the <this> before the + // anchor attribute is change and re-create them afterwards. + { + std::unique_ptr<SwHandleAnchorNodeChg, o3tl::default_delete<SwHandleAnchorNodeChg>> pHandleAnchorNodeChg; + SwFlyFrameFormat* pFlyFrameFormat( dynamic_cast<SwFlyFrameFormat*>(&rFormat) ); + if ( pFlyFrameFormat ) + { + pHandleAnchorNodeChg.reset( + new SwHandleAnchorNodeChg( *pFlyFrameFormat, aAnch )); + } + rFormat.GetDoc()->SetAttr( aAnch, rFormat ); + } + // #i28701# - no call of method + // <CheckCharRectAndTopOfLine()> for to-character anchored + // Writer fly frame needed. This method call can cause a + // format of the anchor frame, which is no longer intended. + // Instead clear the anchor character rectangle and + // the top of line values for all to-character anchored objects. + pAnchoredObj->ClearCharRectAndTopOfLine(); + EndAllAction(); + } + } + + SwRect aTmpRect( aRet, rAbsPos ); + if( aTmpRect.HasArea() ) + MakeVisible( aTmpRect ); +#if OSL_DEBUG_LEVEL > 0 + //TODO: That doesn't seem to be intended + if( COL_TRANSPARENT != GetOut()->GetLineColor() ) + { + OSL_FAIL( "Hey, Joe: Where's my Null Pen?" ); + GetOut()->SetLineColor( COL_TRANSPARENT ); + } +#endif + } + } + + return aRet; +} + +const SwFrameFormat *SwFEShell::NewFlyFrame( const SfxItemSet& rSet, bool bAnchValid, + SwFrameFormat *pParent ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + const Point aPt( GetCursorDocPos() ); + + SwSelBoxes aBoxes; + bool bMoveContent = true; + if( IsTableMode() ) + { + GetTableSel( *this, aBoxes ); + if( !aBoxes.empty() ) + { + // Cursor should be removed from the removal area. + // Always put it after/on the table; via the + // document position they will be set to the old + // position + ParkCursor( SwNodeIndex( *aBoxes[0]->GetSttNd() )); + + // #i127787# pCurrentCursor will be deleted in ParkCursor, + // we better get the current pCurrentCursor instead of working with the + // deleted one: + pCursor = GetCursor(); + } + else + bMoveContent = false; + } + else if( !pCursor->HasMark() && !pCursor->IsMultiSelection() ) + bMoveContent = false; + + const SwPosition& rPos = *pCursor->Start(); + + SwFormatAnchor& rAnch = const_cast<SwFormatAnchor&>(rSet.Get( RES_ANCHOR )); + RndStdIds eRndId = rAnch.GetAnchorId(); + switch( eRndId ) + { + case RndStdIds::FLY_AT_PAGE: + if( !rAnch.GetPageNum() ) //HotFix: Bug in UpdateByExample + rAnch.SetPageNum( 1 ); + break; + + case RndStdIds::FLY_AT_FLY: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + case RndStdIds::FLY_AS_CHAR: + if( !bAnchValid ) + { + if( RndStdIds::FLY_AT_FLY != eRndId ) + { + rAnch.SetAnchor( &rPos ); + } + else if( lcl_SetNewFlyPos( rPos.nNode.GetNode(), rAnch, aPt ) ) + { + eRndId = RndStdIds::FLY_AT_PAGE; + } + } + break; + + default: + OSL_ENSURE( false, "What is the purpose of this Fly?" ); + break; + } + + SwFlyFrameFormat *pRet; + if( bMoveContent ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSLAYFMT, nullptr ); + std::unique_ptr<SwFormatAnchor> pOldAnchor; + bool bHOriChgd = false, bVOriChgd = false; + std::shared_ptr<SwFormatVertOrient> aOldV; + std::shared_ptr<SwFormatHoriOrient> aOldH; + + if ( RndStdIds::FLY_AT_PAGE != eRndId ) + { + // First as with page link. Paragraph/character link on if + // everything was shifted. Then the position is valid! + // JP 13.05.98: if necessary also convert the horizontal/vertical + // orientation, to prevent correction during re-anchoring + pOldAnchor.reset(new SwFormatAnchor( rAnch )); + const_cast<SfxItemSet&>(rSet).Put( SwFormatAnchor( RndStdIds::FLY_AT_PAGE, 1 ) ); + + const SfxPoolItem* pItem; + if( SfxItemState::SET == rSet.GetItemState( RES_HORI_ORIENT, false, &pItem ) + && text::HoriOrientation::NONE == static_cast<const SwFormatHoriOrient*>(pItem)->GetHoriOrient() ) + { + bHOriChgd = true; + aOldH.reset(static_cast<SwFormatHoriOrient*>(pItem->Clone())); + const_cast<SfxItemSet&>(rSet).Put( SwFormatHoriOrient( 0, text::HoriOrientation::LEFT ) ); + } + if( SfxItemState::SET == rSet.GetItemState( RES_VERT_ORIENT, false, &pItem ) + && text::VertOrientation::NONE == static_cast<const SwFormatVertOrient*>(pItem)->GetVertOrient() ) + { + bVOriChgd = true; + aOldV.reset(static_cast<SwFormatVertOrient*>(pItem->Clone())); + const_cast<SfxItemSet&>(rSet).Put( SwFormatVertOrient( 0, text::VertOrientation::TOP ) ); + } + } + + pRet = GetDoc()->MakeFlyAndMove( *pCursor, rSet, &aBoxes, pParent ); + + KillPams(); + + if( pOldAnchor ) + { + if( pRet ) + { + // calculate new position + // JP 24.03.97: also go via page links + // anchor should not lie in the shifted area + pRet->DelFrames(); + + const SwFrame* pAnch = ::FindAnchor( GetLayout(), aPt ); + SwPosition aPos( pAnch->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pAnch)->GetTextNodeForParaProps() + : *static_cast<const SwNoTextFrame*>(pAnch)->GetNode() ); + + if ( RndStdIds::FLY_AS_CHAR == eRndId ) + { + assert(pAnch->IsTextFrame()); + aPos = static_cast<SwTextFrame const*>(pAnch)->MapViewToModelPos(TextFrameIndex(0)); + } + pOldAnchor->SetAnchor( &aPos ); + + // shifting of table selection is not Undo-capable. therefore + // changing the anchors should not be recorded + bool const bDoesUndo = + GetDoc()->GetIDocumentUndoRedo().DoesUndo(); + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (bDoesUndo && + GetDoc()->GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, + & nLastUndoId)) + { + if (SwUndoId::INSLAYFMT == nLastUndoId) + { + GetDoc()->GetIDocumentUndoRedo().DoUndo(false); + } + } + + const_cast<SfxItemSet&>(rSet).Put( *pOldAnchor ); + + if( bHOriChgd ) + const_cast<SfxItemSet&>(rSet).Put( *aOldH ); + if( bVOriChgd ) + const_cast<SfxItemSet&>(rSet).Put( *aOldV ); + + GetDoc()->SetFlyFrameAttr( *pRet, const_cast<SfxItemSet&>(rSet) ); + GetDoc()->GetIDocumentUndoRedo().DoUndo(bDoesUndo); + } + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSLAYFMT, nullptr ); + } + else + /* If called from a shell try to propagate an + existing adjust item from rPos to the content node of the + new frame. */ + pRet = GetDoc()->MakeFlySection( eRndId, &rPos, &rSet, pParent, true ); + + if( pRet ) + { + SwFlyFrame* pFrame = pRet->GetFrame( &aPt ); + if( pFrame ) + SelectFlyFrame( *pFrame ); + else + { + GetLayout()->SetAssertFlyPages(); + pRet = nullptr; + } + } + EndAllActionAndCall(); + + return pRet; +} + +void SwFEShell::Insert( const OUString& rGrfName, const OUString& rFltName, + const Graphic* pGraphic, + const SfxItemSet* pFlyAttrSet ) +{ + SwFlyFrameFormat* pFormat = nullptr; + SET_CURR_SHELL( this ); + StartAllAction(); + SwShellCursor *pStartCursor = dynamic_cast<SwShellCursor*>(GetSwCursor()); + SwShellCursor *pCursor = pStartCursor; + do + { + if (!pCursor) + break; + + // Has the anchor not been set or been set incompletely? + if( pFlyAttrSet ) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pFlyAttrSet->GetItemState( RES_ANCHOR, false, + &pItem ) ) + { + SwFormatAnchor* pAnchor = const_cast<SwFormatAnchor*>(static_cast<const SwFormatAnchor*>(pItem)); + switch( pAnchor->GetAnchorId()) + { + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL + case RndStdIds::FLY_AS_CHAR: + if( !pAnchor->GetContentAnchor() ) + { + pAnchor->SetAnchor( pCursor->GetPoint() ); + } + break; + case RndStdIds::FLY_AT_FLY: + if( !pAnchor->GetContentAnchor() ) + { + lcl_SetNewFlyPos( pCursor->GetNode(), + *pAnchor, GetCursorDocPos() ); + } + break; + case RndStdIds::FLY_AT_PAGE: + if( !pAnchor->GetPageNum() ) + { + pAnchor->SetPageNum( pCursor->GetPageNum( + true, &pCursor->GetPtPos() ) ); + } + break; + default : + break; + } + } + } + pFormat = GetDoc()->getIDocumentContentOperations().InsertGraphic( + *pCursor, rGrfName, + rFltName, pGraphic, + pFlyAttrSet, + nullptr, nullptr ); + OSL_ENSURE(pFormat, "IDocumentContentOperations::InsertGraphic failed."); + + pCursor = pCursor->GetNext(); + } while( pCursor != pStartCursor ); + + EndAllAction(); + + if( pFormat ) + { + const Point aPt( GetCursorDocPos() ); + SwFlyFrame* pFrame = pFormat->GetFrame( &aPt ); + + if( pFrame ) + { + // fdo#36681: Invalidate the content and layout to refresh + // the picture anchoring properly + SwPageFrame* pPageFrame = pFrame->FindPageFrameOfAnchor(); + pPageFrame->InvalidateFlyLayout(); + pPageFrame->InvalidateContent(); + + SelectFlyFrame( *pFrame ); + } + else + GetLayout()->SetAssertFlyPages(); + } +} + +SwFlyFrameFormat* SwFEShell::InsertObject( const svt::EmbeddedObjectRef& xObj, + SfxItemSet* pFlyAttrSet ) +{ + SwFlyFrameFormat* pFormat = nullptr; + SET_CURR_SHELL( this ); + StartAllAction(); + { + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + pFormat = GetDoc()->getIDocumentContentOperations().InsertEmbObject( + rPaM, xObj, pFlyAttrSet ); + OSL_ENSURE(pFormat, "IDocumentContentOperations::InsertEmbObject failed."); + } + } + EndAllAction(); + + if( pFormat ) + { + const Point aPt( GetCursorDocPos() ); + SwFlyFrame* pFrame = pFormat->GetFrame( &aPt ); + + if( pFrame ) + SelectFlyFrame( *pFrame ); + else + GetLayout()->SetAssertFlyPages(); + } + + return pFormat; +} + +void SwFEShell::InsertDrawObj( SdrObject& rDrawObj, + const Point& rInsertPosition ) +{ + SET_CURR_SHELL( this ); + + SfxItemSet rFlyAttrSet( GetDoc()->GetAttrPool(), aFrameFormatSetRange ); + rFlyAttrSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + // #i89920# + rFlyAttrSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH ) ); + rDrawObj.SetLayer( getIDocumentDrawModelAccess().GetHeavenId() ); + + // find anchor position + SwPaM aPam( mxDoc->GetNodes() ); + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + Point aTmpPt( rInsertPosition ); + GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aTmpPt, &aState ); + const SwFrame* pFrame = aPam.GetContentNode()->getLayoutFrame(GetLayout(), nullptr, nullptr); + const Point aRelPos( rInsertPosition.X() - pFrame->getFrameArea().Left(), + rInsertPosition.Y() - pFrame->getFrameArea().Top() ); + rDrawObj.SetRelativePos( aRelPos ); + ::lcl_FindAnchorPos( *GetDoc(), rInsertPosition, *pFrame, rFlyAttrSet ); + } + // insert drawing object into the document creating a new <SwDrawFrameFormat> instance + SwDrawFrameFormat* pFormat = GetDoc()->getIDocumentContentOperations().InsertDrawObj( aPam, rDrawObj, rFlyAttrSet ); + + // move object to visible layer + SwContact* pContact = static_cast<SwContact*>(rDrawObj.GetUserCall()); + if ( pContact ) + { + pContact->MoveObjToVisibleLayer( &rDrawObj ); + } + + if (pFormat) + { + pFormat->SetName(rDrawObj.GetName()); + // select drawing object + Imp()->GetDrawView()->MarkObj( &rDrawObj, Imp()->GetPageView() ); + } + else + { + GetLayout()->SetAssertFlyPages(); + } +} + +void SwFEShell::GetPageObjs( std::vector<SwFrameFormat*>& rFillArr ) +{ + rFillArr.clear(); + + for( auto pFormat : *mxDoc->GetSpzFrameFormats() ) + { + if (RndStdIds::FLY_AT_PAGE == pFormat->GetAnchor().GetAnchorId()) + { + rFillArr.push_back( pFormat ); + } + } +} + +void SwFEShell::SetPageObjsNewPage( std::vector<SwFrameFormat*>& rFillArr ) +{ + if( rFillArr.empty() ) + return; + + StartAllAction(); + StartUndo(); + + SwRootFrame* pTmpRootFrame = GetLayout(); + sal_uInt16 nMaxPage = pTmpRootFrame->GetPageNum(); + bool bTmpAssert = false; + for( auto pFormat : rFillArr ) + { + if (mxDoc->GetSpzFrameFormats()->IsAlive(pFormat)) + { + // FlyFormat is still valid, therefore process + + SwFormatAnchor aNewAnchor( pFormat->GetAnchor() ); + if (RndStdIds::FLY_AT_PAGE != aNewAnchor.GetAnchorId()) + // Anchor has been changed, therefore: do not change! + continue; + sal_uInt16 nNewPage = aNewAnchor.GetPageNum() + 1; + if (nNewPage > nMaxPage) + { + if ( RES_DRAWFRMFMT == pFormat->Which() ) + pFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PAGE_OUT_OF_BOUNDS)); + else + pFormat->DelFrames(); + bTmpAssert = true; + } + aNewAnchor.SetPageNum(nNewPage); + mxDoc->SetAttr( aNewAnchor, *pFormat ); + } + } + + if( bTmpAssert ) + pTmpRootFrame->SetAssertFlyPages(); + + EndUndo(); + EndAllAction(); +} + +// All attributes in the "baskets" will be filled with the attributes of the +// current FlyFrames. Attributes which cannot be filled due to being at the +// wrong place or which are ambiguous (multiple selections) will be removed. +bool SwFEShell::GetFlyFrameAttr( SfxItemSet &rSet ) const +{ + SwFlyFrame *pFly = GetSelectedOrCurrFlyFrame(); + if (!pFly) + { + OSL_ENSURE( false, "GetFlyFrameAttr, no Fly selected." ); + return false; + } + + SET_CURR_SHELL( const_cast<SwFEShell*>(this) ); + + if( !rSet.Set( pFly->GetFormat()->GetAttrSet() ) ) + return false; + + // now examine all attributes. Remove forbidden attributes, then + // get all remaining attributes and enter them + const SfxPoolItem* pItem; + if( SfxItemState::SET == rSet.GetItemState( RES_ANCHOR, false, &pItem ) ) + { + const SwFormatAnchor* pAnchor = static_cast<const SwFormatAnchor*>(pItem); + RndStdIds eType = pAnchor->GetAnchorId(); + + if ( RndStdIds::FLY_AT_PAGE != eType ) + { + // OD 12.11.2003 #i22341# - content anchor of anchor item is needed. + // Thus, don't overwrite anchor item by default constructed anchor item. + if ( RndStdIds::FLY_AS_CHAR == eType ) + { + rSet.ClearItem( RES_OPAQUE ); + rSet.ClearItem( RES_SURROUND ); + } + } + } + rSet.SetParent( pFly->GetFormat()->GetAttrSet().GetParent() ); + // attributes must be removed + rSet.ClearItem( RES_FILL_ORDER ); + rSet.ClearItem( RES_CNTNT ); + //MA: remove first (Template by example etc.) + rSet.ClearItem( RES_CHAIN ); + return true; +} + +// Attributes of the current fly will change. +bool SwFEShell::SetFlyFrameAttr( SfxItemSet& rSet ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if( rSet.Count() ) + { + SwFlyFrame *pFly = GetSelectedOrCurrFlyFrame(); + OSL_ENSURE(pFly, "SetFlyFrameAttr, no Fly selected."); + if (pFly) + { + StartAllAction(); + const Point aPt( pFly->getFrameArea().Pos() ); + + if( SfxItemState::SET == rSet.GetItemState( RES_ANCHOR, false )) + sw_ChkAndSetNewAnchor( *pFly, rSet ); + SwFlyFrameFormat* pFlyFormat = pFly->GetFormat(); + + if( GetDoc()->SetFlyFrameAttr( *pFlyFormat, rSet )) + { + bRet = true; + SwFlyFrame* pFrame = pFlyFormat->GetFrame( &aPt ); + if( pFrame ) + SelectFlyFrame( *pFrame ); + else + GetLayout()->SetAssertFlyPages(); + } + + EndAllActionAndCall(); + } + } + return bRet; +} + +SfxItemSet SwFEShell::makeItemSetFromFormatAnchor(SfxItemPool& rPool, const SwFormatAnchor &rAnchor) +{ + // The set also includes VERT/HORI_ORIENT, because the align + // shall be changed in FEShell::SetFlyFrameAttr/SetFlyFrameAnchor, + // possibly as a result of the anchor change. + SfxItemSet aSet(rPool, svl::Items<RES_VERT_ORIENT, RES_ANCHOR>{}); + aSet.Put(rAnchor); + return aSet; +} + +bool SwFEShell::SetDrawingAttr( SfxItemSet& rSet ) +{ + bool bRet = false; + SET_CURR_SHELL( this ); + if ( !rSet.Count() || + !Imp()->HasDrawView() ) + return bRet; + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if ( rMrkList.GetMarkCount() != 1 ) + return bRet; + + StartUndo(); + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + SwFrameFormat *pFormat = FindFrameFormat( pObj ); + StartAllAction(); + if( SfxItemState::SET == rSet.GetItemState( RES_ANCHOR, false )) + { + RndStdIds nNew = rSet.Get( RES_ANCHOR ).GetAnchorId(); + if ( nNew != pFormat->GetAnchor().GetAnchorId() ) + { + ChgAnchor( nNew ); + // #i26791# - clear anchor attribute in item set, + // because method <ChgAnchor(..)> takes care of it. + rSet.ClearItem( RES_ANCHOR ); + } + } + + if( GetDoc()->SetFlyFrameAttr( *pFormat, rSet )) + { + bRet = true; + SelectObj( Point(), 0, pObj ); + } + EndAllActionAndCall(); + EndUndo(); + return bRet; +} + +// Reset attributes contained in the set. +void SwFEShell::ResetFlyFrameAttr( const SfxItemSet* pSet ) +{ + SET_CURR_SHELL( this ); + + SwFlyFrame *pFly = GetSelectedOrCurrFlyFrame(); + OSL_ENSURE( pFly, "SetFlyFrameAttr, no Fly selected." ); + if( pFly ) + { + StartAllAction(); + + SfxItemIter aIter( *pSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if( !IsInvalidItem( pItem ) ) + { + sal_uInt16 nWhich = pItem->Which(); + if( RES_ANCHOR != nWhich && RES_CHAIN != nWhich && RES_CNTNT != nWhich ) + pFly->GetFormat()->ResetFormatAttr( nWhich ); + } + } + + EndAllActionAndCall(); + GetDoc()->getIDocumentState().SetModified(); + } +} + +// Returns frame-format if frame, otherwise 0 +SwFrameFormat* SwFEShell::GetSelectedFrameFormat() const +{ + SwFrameFormat* pRet = nullptr; + SwLayoutFrame *pFly = GetSelectedFlyFrame(); + if( pFly && ( pRet = static_cast<SwFrameFormat*>(pFly->GetFormat()->DerivedFrom()) ) == + GetDoc()->GetDfltFrameFormat() ) + pRet = nullptr; + return pRet; +} + +void SwFEShell::SetFrameFormat( SwFrameFormat *pNewFormat, bool bKeepOrient, Point const * pDocPos ) +{ + SwFlyFrame *pFly = nullptr; + if(pDocPos) + { + const SwFrameFormat* pFormat = GetFormatFromObj( *pDocPos ); + + if (const SwFlyFrameFormat* pFlyFormat = dynamic_cast<const SwFlyFrameFormat*>(pFormat)) + pFly = pFlyFormat->GetFrame(); + } + else + pFly = GetSelectedFlyFrame(); + OSL_ENSURE( pFly, "SetFrameFormat: no frame" ); + if( pFly ) + { + StartAllAction(); + SET_CURR_SHELL( this ); + + SwFlyFrameFormat* pFlyFormat = pFly->GetFormat(); + const Point aPt( pFly->getFrameArea().Pos() ); + + std::unique_ptr<SfxItemSet> pSet; + const SfxPoolItem* pItem; + if( SfxItemState::SET == pNewFormat->GetItemState( RES_ANCHOR, false, &pItem )) + { + pSet.reset(new SfxItemSet( GetDoc()->GetAttrPool(), aFrameFormatSetRange )); + pSet->Put( *pItem ); + if( !sw_ChkAndSetNewAnchor( *pFly, *pSet )) + { + pSet.reset(); + } + } + + if( GetDoc()->SetFrameFormatToFly( *pFlyFormat, *pNewFormat, pSet.get(), bKeepOrient )) + { + SwFlyFrame* pFrame = pFlyFormat->GetFrame( &aPt ); + if( pFrame ) + SelectFlyFrame( *pFrame ); + else + GetLayout()->SetAssertFlyPages(); + } + pSet.reset(); + + EndAllActionAndCall(); + } +} + +const SwFrameFormat* SwFEShell::GetFlyFrameFormat() const +{ + const SwFlyFrame* pFly = GetSelectedOrCurrFlyFrame(); + if (pFly) + return pFly->GetFormat(); + return nullptr; +} + +SwFrameFormat* SwFEShell::GetFlyFrameFormat() +{ + SwFlyFrame* pFly = GetSelectedOrCurrFlyFrame(); + if (pFly) + return pFly->GetFormat(); + return nullptr; +} + +SwRect SwFEShell::GetFlyRect() const +{ + SwFlyFrame *pFly = GetCurrFlyFrame(false); + if (!pFly) + { + SwRect aRect; + return aRect; + } + else + return pFly->getFrameArea(); +} + +SwRect SwFEShell::GetObjRect() const +{ + if( Imp()->HasDrawView() ) + return Imp()->GetDrawView()->GetAllMarkedRect(); + else + { + SwRect aRect; + return aRect; + } +} + +void SwFEShell::SetObjRect( const SwRect& rRect ) +{ + if ( Imp()->HasDrawView() ) + { + Imp()->GetDrawView()->SetAllMarkedRect( rRect.SVRect() ); + CallChgLnk(); // call AttrChangeNotify on the UI-side. + } +} + +Size SwFEShell::RequestObjectResize( const SwRect &rRect, const uno::Reference < embed::XEmbeddedObject >& xObj ) +{ + Size aResult; + + SwFlyFrame *pFly = FindFlyFrame( xObj ); + if ( !pFly ) + { + aResult = rRect.SSize(); + return aResult; + } + + aResult = pFly->getFramePrintArea().SSize(); + + bool bPosProt = pFly->GetFormat()->GetProtect().IsPosProtected(); + bool bSizeProt = pFly->GetFormat()->GetProtect().IsSizeProtected(); + + StartAllAction(); + + // MA we do not allow to clip the Fly, as the OLE server can + // request various wishes. Clipping is done via the formatting. + // Correct display is done by scaling. + // Scaling is done by SwNoTextFrame::Format by calling + // SwWrtShell::CalcAndSetScale() + if ( rRect.SSize() != pFly->getFramePrintArea().SSize() && !bSizeProt ) + { + Size aSz( rRect.SSize() ); + + //JP 28.02.2001: Task 74707 - ask for fly in fly with automatic size + + const SwFrame* pAnchor; + const SwFormatFrameSize& rFrameSz = pFly->GetFormat()->GetFrameSize(); + if (m_bCheckForOLEInCaption && + 0 != rFrameSz.GetWidthPercent() && + nullptr != (pAnchor = pFly->GetAnchorFrame()) && + pAnchor->IsTextFrame() && + !pAnchor->GetNext() && !pAnchor->GetPrev() && + pAnchor->GetUpper()->IsFlyFrame()) + { + // search for a sequence field: + sw::MergedAttrIter iter(*static_cast<SwTextFrame const*>(pAnchor)); + for (SwTextAttr const* pHint = iter.NextAttr(); pHint; pHint = iter.NextAttr()) + { + const SfxPoolItem* pItem = &pHint->GetAttr(); + if( RES_TXTATR_FIELD == pItem->Which() + && SwFieldTypesEnum::Sequence == static_cast<const SwFormatField*>(pItem)->GetField()->GetTypeId() ) + { + // sequence field found + SwFlyFrame* pChgFly = const_cast<SwFlyFrame*>(static_cast<const SwFlyFrame*>(pAnchor->GetUpper())); + // calculate the changed size: + // width must change, height can change + Size aNewSz( aSz.Width() + pChgFly->getFrameArea().Width() - + pFly->getFramePrintArea().Width(), aSz.Height() ); + + SwFrameFormat *pFormat = pChgFly->GetFormat(); + SwFormatFrameSize aFrameSz( pFormat->GetFrameSize() ); + aFrameSz.SetWidth( aNewSz.Width() ); + if( SwFrameSize::Minimum != aFrameSz.GetHeightSizeType() ) + { + aNewSz.AdjustHeight(pChgFly->getFrameArea().Height() - + pFly->getFramePrintArea().Height() ); + if( std::abs( aNewSz.Height() - pChgFly->getFrameArea().Height()) > 1 ) + aFrameSz.SetHeight( aNewSz.Height() ); + } + // via Doc for the Undo! + pFormat->GetDoc()->SetAttr( aFrameSz, *pFormat ); + break; + } + } + } + + // set the new Size at the fly themself + if ( !pFly->getFramePrintArea().IsEmpty() ) + { + aSz.AdjustWidth(pFly->getFrameArea().Width() - pFly->getFramePrintArea().Width() ); + aSz.AdjustHeight(pFly->getFrameArea().Height()- pFly->getFramePrintArea().Height() ); + } + aResult = pFly->ChgSize( aSz ); + + // if the object changes, the contour is outside the object + assert(pFly->Lower()->IsNoTextFrame()); + SwNoTextNode *pNd = static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetNoTextNode(); + assert(pNd); + pNd->SetContour( nullptr ); + ClrContourCache(); + } + + // if only the size is to be adjusted, a position is transported with + // allocated values + Point aPt( pFly->getFramePrintArea().Pos() ); + aPt += pFly->getFrameArea().Pos(); + if ( rRect.Top() != LONG_MIN && rRect.Pos() != aPt && !bPosProt ) + { + aPt = rRect.Pos(); + aPt.setX(aPt.getX() - pFly->getFramePrintArea().Left()); + aPt.setY(aPt.getY() - pFly->getFramePrintArea().Top()); + + // in case of paragraph-bound Flys, starting from the new position, + // a new anchor is to be set. The anchor and the new RelPos are + // calculated by the Fly and set + if( pFly->IsFlyAtContentFrame() ) + static_cast<SwFlyAtContentFrame*>(pFly)->SetAbsPos( aPt ); + else + { + const SwFrameFormat *pFormat = pFly->GetFormat(); + const SwFormatVertOrient &rVert = pFormat->GetVertOrient(); + const SwFormatHoriOrient &rHori = pFormat->GetHoriOrient(); + const long lXDiff = aPt.getX() - pFly->getFrameArea().Left(); + const long lYDiff = aPt.getY() - pFly->getFrameArea().Top(); + const Point aTmp( rHori.GetPos() + lXDiff, + rVert.GetPos() + lYDiff ); + pFly->ChgRelPos( aTmp ); + } + } + + SwFlyFrameFormat *pFlyFrameFormat = pFly->GetFormat(); + OSL_ENSURE( pFlyFrameFormat, "fly frame format missing!" ); + if ( pFlyFrameFormat ) + pFlyFrameFormat->SetLastFlyFramePrtRectPos( pFly->getFramePrintArea().Pos() ); //stores the value of last Prt rect + + EndAllAction(); + + return aResult; +} + +SwFrameFormat* SwFEShell::WizardGetFly() +{ + // do not search the Fly via the layout. Now we can delete a frame + // without a valid layout. ( e.g. for the wizards ) + SwFrameFormats& rSpzArr = *mxDoc->GetSpzFrameFormats(); + if( !rSpzArr.empty() ) + { + SwNodeIndex& rCursorNd = GetCursor()->GetPoint()->nNode; + if( rCursorNd.GetIndex() > mxDoc->GetNodes().GetEndOfExtras().GetIndex() ) + // Cursor is in the body area! + return nullptr; + + for( auto pFormat : rSpzArr ) + { + const SwNodeIndex* pIdx = pFormat->GetContent( false ).GetContentIdx(); + SwStartNode* pSttNd; + if( pIdx && + nullptr != ( pSttNd = pIdx->GetNode().GetStartNode() ) && + pSttNd->GetIndex() < rCursorNd.GetIndex() && + rCursorNd.GetIndex() < pSttNd->EndOfSectionIndex() ) + { + // found: return immediately + return pFormat; + } + } + } + return nullptr; +} + +void SwFEShell::SetFlyName( const OUString& rName ) +{ + SwLayoutFrame *pFly = GetSelectedFlyFrame(); + if( pFly ) + GetDoc()->SetFlyName( *static_cast<SwFlyFrameFormat*>(pFly->GetFormat()), rName ); + else { + OSL_ENSURE( false, "no FlyFrame selected" ); + } +} + +OUString SwFEShell::GetFlyName() const +{ + SwLayoutFrame *pFly = GetSelectedFlyFrame(); + if( pFly ) + return pFly->GetFormat()->GetName(); + + OSL_ENSURE( false, "no FlyFrame selected" ); + return OUString(); +} + +uno::Reference < embed::XEmbeddedObject > SwFEShell::GetOleRef() const +{ + uno::Reference < embed::XEmbeddedObject > xObj; + SwFlyFrame * pFly = GetSelectedFlyFrame(); + if (pFly && pFly->Lower() && pFly->Lower()->IsNoTextFrame()) + { + SwOLENode *pNd = static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetOLENode(); + if (pNd) + xObj = pNd->GetOLEObj().GetOleRef(); + } + return xObj; +} + +OUString SwFEShell::GetUniqueGrfName() const +{ + return GetDoc()->GetUniqueGrfName(); +} + +const SwFrameFormat* SwFEShell::IsURLGrfAtPos( const Point& rPt, OUString* pURL, + OUString *pTargetFrameName, + OUString *pDescription ) const +{ + if( !Imp()->HasDrawView() ) + return nullptr; + + SdrPageView* pPV; + const SwFrameFormat* pRet = nullptr; + SwDrawView *pDView = const_cast<SwDrawView*>(Imp()->GetDrawView()); + + const auto nOld = pDView->GetHitTolerancePixel(); + pDView->SetHitTolerancePixel( 2 ); + + SdrObject* pObj = pDView->PickObj(rPt, pDView->getHitTolLog(), pPV, SdrSearchOptions::PICKMACRO); + SwVirtFlyDrawObj* pFlyObj = dynamic_cast<SwVirtFlyDrawObj*>(pObj); + if (pFlyObj) + { + SwFlyFrame *pFly = pFlyObj->GetFlyFrame(); + const SwFormatURL &rURL = pFly->GetFormat()->GetURL(); + if( !rURL.GetURL().isEmpty() || rURL.GetMap() ) + { + bool bSetTargetFrameName = pTargetFrameName != nullptr; + bool bSetDescription = pDescription != nullptr; + if ( rURL.GetMap() ) + { + IMapObject *pObject = pFly->GetFormat()->GetIMapObject( rPt, pFly ); + if ( pObject && !pObject->GetURL().isEmpty() ) + { + if( pURL ) + *pURL = pObject->GetURL(); + if ( bSetTargetFrameName && !pObject->GetTarget().isEmpty() ) + { + bSetTargetFrameName = false; + *pTargetFrameName = pObject->GetTarget(); + } + if ( bSetDescription ) + { + bSetDescription = false; + *pDescription = pObject->GetAltText(); + } + pRet = pFly->GetFormat(); + } + } + else + { + if( pURL ) + { + *pURL = rURL.GetURL(); + if( rURL.IsServerMap() ) + { + // append the relative pixel position !! + Point aPt( rPt ); + aPt -= pFly->getFrameArea().Pos(); + // without MapMode-Offset, without Offset, o ... !!!!! + aPt = GetOut()->LogicToPixel( + aPt, MapMode( MapUnit::MapTwip ) ); + *pURL = *pURL + "?" + OUString::number( aPt.getX() ) + + "," + OUString::number(aPt.getY() ); + } + } + pRet = pFly->GetFormat(); + } + if ( bSetTargetFrameName ) + *pTargetFrameName = rURL.GetTargetFrameName(); + if ( bSetDescription ) + *pDescription = pFly->GetFormat()->GetName(); + } + } + pDView->SetHitTolerancePixel( nOld ); + return pRet; +} + +const Graphic *SwFEShell::GetGrfAtPos( const Point &rPt, + OUString &rName, bool &rbLink ) const +{ + if( !Imp()->HasDrawView() ) + return nullptr; + + SdrPageView* pPV; + SwDrawView *pDView = const_cast<SwDrawView*>(Imp()->GetDrawView()); + + SdrObject* pObj = pDView->PickObj(rPt, pDView->getHitTolLog(), pPV); + SwVirtFlyDrawObj* pFlyObj = dynamic_cast<SwVirtFlyDrawObj*>(pObj); + if (pFlyObj) + { + SwFlyFrame *pFly = pFlyObj->GetFlyFrame(); + if ( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + SwGrfNode *const pNd = static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetGrfNode(); + if ( pNd ) + { + if ( pNd->IsGrfLink() ) + { + // halfway ready graphic? + ::sfx2::SvLinkSource* pLnkObj = pNd->GetLink()->GetObj(); + if( pLnkObj && pLnkObj->IsPending() ) + return nullptr; + rbLink = true; + } + + pNd->GetFileFilterNms( &rName, nullptr ); + if ( rName.isEmpty() ) + rName = pFly->GetFormat()->GetName(); + return &pNd->GetGrf(true); + } + } + } + return nullptr; +} + +const SwFrameFormat* SwFEShell::GetFormatFromObj( const Point& rPt, SwRect** pRectToFill ) const +{ + SwFrameFormat* pRet = nullptr; + + if( Imp()->HasDrawView() ) + { + SdrPageView* pPView; + + SwDrawView *pDView = const_cast<SwDrawView*>(Imp()->GetDrawView()); + + const auto nOld = pDView->GetHitTolerancePixel(); + // tolerance for Drawing-SS + pDView->SetHitTolerancePixel( pDView->GetMarkHdlSizePixel()/2 ); + + SdrObject* pObj = pDView->PickObj(rPt, pDView->getHitTolLog(), pPView, SdrSearchOptions::PICKMARKABLE); + if (pObj) + { + // first check it: + if (SwVirtFlyDrawObj* pFlyObj = dynamic_cast<SwVirtFlyDrawObj*>(pObj)) + pRet = pFlyObj->GetFormat(); + else if ( pObj->GetUserCall() ) //not for group objects + pRet = static_cast<SwDrawContact*>(pObj->GetUserCall())->GetFormat(); + if(pRet && pRectToFill) + **pRectToFill = pObj->GetCurrentBoundRect(); + } + pDView->SetHitTolerancePixel( nOld ); + } + return pRet; +} + +// returns a format too, if the point is over the text of any fly +const SwFrameFormat* SwFEShell::GetFormatFromAnyObj( const Point& rPt ) const +{ + const SwFrameFormat* pRet = GetFormatFromObj( rPt ); + if( !pRet || RES_FLYFRMFMT == pRet->Which() ) + { + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( rPt ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt ); + SwContentNode *pNd = aPos.nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(rPt, false); + SwFrame* pFrame = pNd->getLayoutFrame(GetLayout(), nullptr, &tmp)->FindFlyFrame(); + pRet = pFrame ? static_cast<SwLayoutFrame*>(pFrame)->GetFormat() : nullptr; + } + return pRet; +} + +ObjCntType SwFEShell::GetObjCntType( const SdrObject& rObj ) +{ + ObjCntType eType = OBJCNT_NONE; + + // investigate 'master' drawing object, if method + // is called for a 'virtual' drawing object. + const SdrObject* pInvestigatedObj; + if (const SwDrawVirtObj* pDrawVirtObj = dynamic_cast<const SwDrawVirtObj*>( &rObj)) + { + pInvestigatedObj = &(pDrawVirtObj->GetReferencedObj()); + } + else + { + pInvestigatedObj = &rObj; + } + + if( SdrInventor::FmForm == pInvestigatedObj->GetObjInventor() ) + { + eType = OBJCNT_CONTROL; + uno::Reference< awt::XControlModel > xModel = + static_cast<const SdrUnoObj&>(*pInvestigatedObj).GetUnoControlModel(); + if( xModel.is() ) + { + uno::Any aVal; + OUString sName("ButtonType"); + uno::Reference< beans::XPropertySet > xSet(xModel, uno::UNO_QUERY); + + uno::Reference< beans::XPropertySetInfo > xInfo = xSet->getPropertySetInfo(); + if(xInfo->hasPropertyByName( sName )) + { + aVal = xSet->getPropertyValue( sName ); + if( aVal.hasValue() && form::FormButtonType_URL == *o3tl::doAccess<form::FormButtonType>(aVal) ) + eType = OBJCNT_URLBUTTON; + } + } + } + else if (const SwVirtFlyDrawObj *pFlyObj = dynamic_cast<const SwVirtFlyDrawObj*>(pInvestigatedObj)) + { + const SwFlyFrame *pFly = pFlyObj->GetFlyFrame(); + if ( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + if (static_cast<const SwNoTextFrame*>(pFly->Lower())->GetNode()->GetGrfNode()) + eType = OBJCNT_GRF; + else + eType = OBJCNT_OLE; + } + else + eType = OBJCNT_FLY; + } + else if ( dynamic_cast<const SdrObjGroup*>( pInvestigatedObj) != nullptr ) + { + SwDrawContact* pDrawContact( dynamic_cast<SwDrawContact*>(GetUserCall( pInvestigatedObj ) ) ); + if ( !pDrawContact ) + { + OSL_FAIL( "<SwFEShell::GetObjCntType(..)> - missing draw contact object" ); + eType = OBJCNT_NONE; + } + else + { + SwFrameFormat* pFrameFormat( pDrawContact->GetFormat() ); + if ( !pFrameFormat ) + { + OSL_FAIL( "<SwFEShell::GetObjCntType(..)> - missing frame format" ); + eType = OBJCNT_NONE; + } + else if ( RndStdIds::FLY_AS_CHAR != pFrameFormat->GetAnchor().GetAnchorId() ) + { + eType = OBJCNT_GROUPOBJ; + } + } + } + else + eType = OBJCNT_SIMPLE; + return eType; +} + +ObjCntType SwFEShell::GetObjCntType( const Point &rPt, SdrObject *&rpObj ) const +{ + ObjCntType eType = OBJCNT_NONE; + + if( Imp()->HasDrawView() ) + { + SdrPageView* pPView; + + SwDrawView *pDView = const_cast<SwDrawView*>(Imp()->GetDrawView()); + + const auto nOld = pDView->GetHitTolerancePixel(); + // tolerance for Drawing-SS + pDView->SetHitTolerancePixel( pDView->GetMarkHdlSizePixel()/2 ); + + SdrObject* pObj = pDView->PickObj(rPt, pDView->getHitTolLog(), pPView, SdrSearchOptions::PICKMARKABLE); + if (pObj) + { + rpObj = pObj; + eType = GetObjCntType( *rpObj ); + } + + pDView->SetHitTolerancePixel( nOld ); + } + return eType; +} + +ObjCntType SwFEShell::GetObjCntTypeOfSelection() const +{ + ObjCntType eType = OBJCNT_NONE; + + if( Imp()->HasDrawView() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for( size_t i = 0, nE = rMrkList.GetMarkCount(); i < nE; ++i ) + { + SdrObject* pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if( !pObj ) + continue; + ObjCntType eTmp = GetObjCntType( *pObj ); + if( !i ) + { + eType = eTmp; + } + else if( eTmp != eType ) + { + eType = OBJCNT_DONTCARE; + // once DontCare, always DontCare! + break; + } + } + } + return eType; +} + +void SwFEShell::ReplaceSdrObj( const OUString& rGrfName, const Graphic* pGrf ) +{ + SET_CURR_SHELL( this ); + + const SdrMarkList *pMrkList; + if( Imp()->HasDrawView() && 1 == + ( pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList())->GetMarkCount() ) + { + SdrObject* pObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + SwFrameFormat *pFormat = FindFrameFormat( pObj ); + + // store attributes, then set the graphic + SfxItemSet aFrameSet( mxDoc->GetAttrPool(), + pFormat->GetAttrSet().GetRanges() ); + aFrameSet.Set( pFormat->GetAttrSet() ); + + // set size and position? + if( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + { + // then let's do it: + const tools::Rectangle &rBound = pObj->GetSnapRect(); + Point aRelPos( pObj->GetRelativePos() ); + + const long nWidth = rBound.Right() - rBound.Left(); + const long nHeight= rBound.Bottom() - rBound.Top(); + aFrameSet.Put( SwFormatFrameSize( SwFrameSize::Minimum, + std::max( nWidth, long(MINFLY) ), + std::max( nHeight, long(MINFLY) ))); + + if( SfxItemState::SET != aFrameSet.GetItemState( RES_HORI_ORIENT )) + aFrameSet.Put( SwFormatHoriOrient( aRelPos.getX(), text::HoriOrientation::NONE, text::RelOrientation::FRAME )); + + if( SfxItemState::SET != aFrameSet.GetItemState( RES_VERT_ORIENT )) + aFrameSet.Put( SwFormatVertOrient( aRelPos.getY(), text::VertOrientation::NONE, text::RelOrientation::FRAME )); + + } + + pObj->GetOrdNum(); + + StartAllAction(); + StartUndo(); + + // delete "Sdr-Object", insert the graphic instead + DelSelectedObj(); + + GetDoc()->getIDocumentContentOperations().InsertGraphic( + *GetCursor(), rGrfName, "", pGrf, &aFrameSet, nullptr, nullptr); + + EndUndo(); + EndAllAction(); + } +} + +static sal_uInt16 SwFormatGetPageNum(const SwFlyFrameFormat * pFormat) +{ + OSL_ENSURE(pFormat != nullptr, "invalid argument"); + + SwFlyFrame * pFrame = pFormat->GetFrame(); + + sal_uInt16 aResult; + + if (pFrame != nullptr) + aResult = pFrame->GetPhyPageNum(); + else + aResult = pFormat->GetAnchor().GetPageNum(); + + return aResult; +} + +void SwFEShell::GetConnectableFrameFormats(SwFrameFormat & rFormat, + const OUString & rReference, + bool bSuccessors, + std::vector< OUString > & aPrevPageVec, + std::vector< OUString > & aThisPageVec, + std::vector< OUString > & aNextPageVec, + std::vector< OUString > & aRestVec) +{ + StartAction(); + + SwFormatChain rChain = rFormat.GetChain(); + SwFrameFormat * pOldChainNext = rChain.GetNext(); + SwFrameFormat * pOldChainPrev = rChain.GetPrev(); + + if (pOldChainNext) + mxDoc->Unchain(rFormat); + + if (pOldChainPrev) + mxDoc->Unchain(*pOldChainPrev); + + const size_t nCnt = mxDoc->GetFlyCount(FLYCNTTYPE_FRM); + + /* potential successors resp. predecessors */ + std::vector< const SwFrameFormat * > aTmpSpzArray; + + mxDoc->FindFlyByName(rReference); + + for (size_t n = 0; n < nCnt; ++n) + { + const SwFrameFormat & rFormat1 = *(mxDoc->GetFlyNum(n, FLYCNTTYPE_FRM)); + + /* + pFormat is a potential successor of rFormat if it is chainable after + rFormat. + + pFormat is a potential predecessor of rFormat if rFormat is chainable + after pFormat. + */ + + SwChainRet nChainState; + + if (bSuccessors) + nChainState = mxDoc->Chainable(rFormat, rFormat1); + else + nChainState = mxDoc->Chainable(rFormat1, rFormat); + + if (nChainState == SwChainRet::OK) + { + aTmpSpzArray.push_back(&rFormat1); + + } + + } + + if (!aTmpSpzArray.empty()) + { + aPrevPageVec.clear(); + aThisPageVec.clear(); + aNextPageVec.clear(); + aRestVec.clear(); + + /* number of page rFormat resides on */ + sal_uInt16 nPageNum = SwFormatGetPageNum(static_cast<SwFlyFrameFormat *>(&rFormat)); + + for (const auto& rpFormat : aTmpSpzArray) + { + const OUString aString = rpFormat->GetName(); + + /* rFormat is not a valid successor or predecessor of + itself */ + if (aString != rReference && aString != rFormat.GetName()) + { + sal_uInt16 nNum1 = + SwFormatGetPageNum(static_cast<const SwFlyFrameFormat *>(rpFormat)); + + if (nNum1 == nPageNum -1) + aPrevPageVec.push_back(aString); + else if (nNum1 == nPageNum) + aThisPageVec.push_back(aString); + else if (nNum1 == nPageNum + 1) + aNextPageVec.push_back(aString); + else + aRestVec.push_back(aString); + } + } + + } + + if (pOldChainNext) + mxDoc->Chain(rFormat, *pOldChainNext); + + if (pOldChainPrev) + mxDoc->Chain(*pOldChainPrev, rFormat); + + EndAction(); +} + +// #i73249# +OUString SwFEShell::GetObjTitle() const +{ + if ( Imp()->HasDrawView() ) + { + const SdrMarkList *pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + if ( pMrkList->GetMarkCount() == 1 ) + { + const SdrObject* pObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + const SwFrameFormat* pFormat = FindFrameFormat( pObj ); + if ( pFormat->Which() == RES_FLYFRMFMT ) + { + return static_cast<const SwFlyFrameFormat*>(pFormat)->GetObjTitle(); + } + return pObj->GetTitle(); + } + } + + return OUString(); +} + +void SwFEShell::SetObjTitle( const OUString& rTitle ) +{ + if ( Imp()->HasDrawView() ) + { + const SdrMarkList *pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + if ( pMrkList->GetMarkCount() == 1 ) + { + SdrObject* pObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + SwFrameFormat* pFormat = FindFrameFormat( pObj ); + if ( pFormat->Which() == RES_FLYFRMFMT ) + { + GetDoc()->SetFlyFrameTitle( dynamic_cast<SwFlyFrameFormat&>(*pFormat), + rTitle ); + } + else + { + pObj->SetTitle( rTitle ); + } + } + } +} + +OUString SwFEShell::GetObjDescription() const +{ + if ( Imp()->HasDrawView() ) + { + const SdrMarkList *pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + if ( pMrkList->GetMarkCount() == 1 ) + { + const SdrObject* pObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + const SwFrameFormat* pFormat = FindFrameFormat( pObj ); + if ( pFormat->Which() == RES_FLYFRMFMT ) + { + return dynamic_cast<const SwFlyFrameFormat&>(*pFormat).GetObjDescription(); + } + return pObj->GetDescription(); + } + } + + return OUString(); +} + +void SwFEShell::SetObjDescription( const OUString& rDescription ) +{ + if ( Imp()->HasDrawView() ) + { + const SdrMarkList *pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + if ( pMrkList->GetMarkCount() == 1 ) + { + SdrObject* pObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + SwFrameFormat* pFormat = FindFrameFormat( pObj ); + if ( pFormat->Which() == RES_FLYFRMFMT ) + { + GetDoc()->SetFlyFrameDescription(dynamic_cast<SwFlyFrameFormat&>(*pFormat), + rDescription); + } + else + { + pObj->SetDescription( rDescription ); + } + } + } +} + +void SwFEShell::AlignFormulaToBaseline( const uno::Reference < embed::XEmbeddedObject >& xObj ) +{ +#if OSL_DEBUG_LEVEL > 0 + SvGlobalName aCLSID( xObj->getClassID() ); + const bool bStarMath = ( SotExchange::IsMath( aCLSID ) != 0 ); + OSL_ENSURE( bStarMath, "AlignFormulaToBaseline should only be called for Math objects" ); + + if ( !bStarMath ) + return; +#endif + + SwFlyFrame * pFly = FindFlyFrame( xObj ); + OSL_ENSURE( pFly , "No fly frame!" ); + SwFrameFormat * pFrameFormat = pFly ? pFly->GetFormat() : nullptr; + + // baseline to baseline alignment should only be applied to formulas anchored as char + if ( !pFly || !pFrameFormat || RndStdIds::FLY_AS_CHAR != pFrameFormat->GetAnchor().GetAnchorId() ) + return; + + // get baseline from Math object + uno::Any aBaseline; + if( svt::EmbeddedObjectRef::TryRunningState( xObj ) ) + { + uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY ); + if ( xSet.is() ) + { + try + { + aBaseline = xSet->getPropertyValue("BaseLine"); + } + catch ( uno::Exception& ) + { + OSL_FAIL( "Baseline could not be retrieved from Starmath!" ); + } + } + } + + sal_Int32 nBaseline = ::comphelper::getINT32(aBaseline); + const MapMode aSourceMapMode( MapUnit::Map100thMM ); + const MapMode aTargetMapMode( MapUnit::MapTwip ); + nBaseline = OutputDevice::LogicToLogic( nBaseline, aSourceMapMode.GetMapUnit(), aTargetMapMode.GetMapUnit() ); + + OSL_ENSURE( nBaseline > 0, "Wrong value of Baseline while retrieving from Starmath!" ); + //nBaseline must be moved by aPrt position + const SwFlyFrameFormat *pFlyFrameFormat = pFly->GetFormat(); + OSL_ENSURE( pFlyFrameFormat, "fly frame format missing!" ); + if ( pFlyFrameFormat ) + nBaseline += pFlyFrameFormat->GetLastFlyFramePrtRectPos().Y(); + + const SwFormatVertOrient &rVert = pFrameFormat->GetVertOrient(); + SwFormatVertOrient aVert( rVert ); + aVert.SetPos( -nBaseline ); + aVert.SetVertOrient( css::text::VertOrientation::NONE ); + + pFrameFormat->LockModify(); + pFrameFormat->SetFormatAttr( aVert ); + pFrameFormat->UnlockModify(); + pFly->InvalidatePos(); + +} + +void SwFEShell::AlignAllFormulasToBaseline() +{ + StartAllAction(); + + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwOLENode *pOleNode = dynamic_cast< SwOLENode * >( &aIdx.GetNode() ); + if ( pOleNode ) + { + const uno::Reference < embed::XEmbeddedObject > & xObj( pOleNode->GetOLEObj().GetOleRef() ); + if (xObj.is()) + { + SvGlobalName aCLSID( xObj->getClassID() ); + if ( SotExchange::IsMath( aCLSID ) ) + AlignFormulaToBaseline( xObj ); + } + } + + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + + EndAllAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/feflyole.cxx b/sw/source/core/frmedt/feflyole.cxx new file mode 100644 index 000000000..b34fb3f77 --- /dev/null +++ b/sw/source/core/frmedt/feflyole.cxx @@ -0,0 +1,127 @@ +/* -*- 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 <sfx2/ipclient.hxx> +#include <sfx2/viewsh.hxx> + +#include <fesh.hxx> +#include <cntfrm.hxx> +#include <flyfrm.hxx> +#include <doc.hxx> +#include <notxtfrm.hxx> +#include <ndole.hxx> +#include <swcli.hxx> +#include <docsh.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <sfx2/linkmgr.hxx> + +using namespace com::sun::star; + +SwFlyFrame *SwFEShell::FindFlyFrame( const uno::Reference < embed::XEmbeddedObject >& xObj ) const +{ + SwFlyFrame *pFly = GetSelectedFlyFrame(); + if ( pFly && pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + SwOLENode *pNd = static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetOLENode(); + if ( !pNd || pNd->GetOLEObj().GetOleRef() != xObj ) + pFly = nullptr; + } + else + pFly = nullptr; + + if ( !pFly ) + { + // No or wrong fly selected: we have to search. + bool bExist = false; + SwStartNode *pStNd; + sal_uLong nSttIdx = GetNodes().GetEndOfAutotext().StartOfSectionIndex() + 1, + nEndIdx = GetNodes().GetEndOfAutotext().GetIndex(); + while( nSttIdx < nEndIdx && + nullptr != (pStNd = GetNodes()[ nSttIdx ]->GetStartNode()) ) + { + SwNode *pNd = GetNodes()[ nSttIdx+1 ]; + if ( pNd->IsOLENode() && + static_cast<SwOLENode*>(pNd)->GetOLEObj().GetOleRef() == xObj ) + { + bExist = true; + SwFrame *pFrame = static_cast<SwOLENode*>(pNd)->getLayoutFrame( GetLayout() ); + if ( pFrame ) + pFly = pFrame->FindFlyFrame(); + break; + } + nSttIdx = pStNd->EndOfSectionIndex() + 1; + } + + OSL_ENSURE( bExist, "OLE-Object unknown and FlyFrame not found." ); + } + return pFly; +} + +OUString SwFEShell::GetUniqueOLEName() const +{ + return GetDoc()->GetUniqueOLEName(); +} + +OUString SwFEShell::GetUniqueFrameName() const +{ + return GetDoc()->GetUniqueFrameName(); +} + +OUString SwFEShell::GetUniqueShapeName() const +{ + return GetDoc()->GetUniqueShapeName(); +} + +bool SwFEShell::FinishOLEObj() // Server is terminated +{ + SfxInPlaceClient* pIPClient = GetSfxViewShell()->GetIPClient(); + if ( !pIPClient ) + return false; + + bool bRet = pIPClient->IsObjectInPlaceActive(); + if( bRet ) + { + if( CNT_OLE == GetCntType() ) + ClearAutomaticContour(); + + if( static_cast<SwOleClient*>(pIPClient)->IsCheckForOLEInCaption() != + IsCheckForOLEInCaption() ) + SetCheckForOLEInCaption( !IsCheckForOLEInCaption() ); + + // enable update of the link preview + comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = GetDoc()->GetDocShell()->getEmbeddedObjectContainer(); + const bool aUserAllowsLinkUpdate = rEmbeddedObjectContainer.getUserAllowsLinkUpdate(); + rEmbeddedObjectContainer.setUserAllowsLinkUpdate(true); + + // leave UIActive state + pIPClient->DeactivateObject(); + + // if we have more than one link let's update them too + sfx2::LinkManager& rLinkManager = GetDoc()->getIDocumentLinksAdministration().GetLinkManager(); + if (rLinkManager.GetLinks().size() > 1) + rLinkManager.UpdateAllLinks(false, false, nullptr); + + // return back original value of the "update of the link preview" flag + rEmbeddedObjectContainer.setUserAllowsLinkUpdate(aUserAllowsLinkUpdate); + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/feshview.cxx b/sw/source/core/frmedt/feshview.cxx new file mode 100644 index 000000000..9da71abf8 --- /dev/null +++ b/sw/source/core/frmedt/feshview.cxx @@ -0,0 +1,3262 @@ +/* -*- 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 <hintids.hxx> +#include <svx/strings.hrc> +#include <svx/sdrobjectfilter.hxx> +#include <svx/svddrgmt.hxx> +#include <svx/svditer.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdocirc.hxx> +#include <svx/svdopath.hxx> +#include <svx/sxciaitm.hxx> +#include <svx/svdocapt.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/svdomeas.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtacitm.hxx> +#include <svx/sdtaaitm.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/protitem.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/dialmgr.hxx> +#include <tools/globname.hxx> +#include <sot/exchange.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <drawdoc.hxx> +#include <textboxhelper.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <fmtcntnt.hxx> +#include <fmtflcnt.hxx> +#include <fmtcnct.hxx> +#include <swmodule.hxx> +#include <fesh.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <sectfrm.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <dview.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <viewimp.hxx> +#include <flyfrm.hxx> +#include <pam.hxx> +#include <ndole.hxx> +#include <ndgrf.hxx> +#include <ndtxt.hxx> +#include <viewopt.hxx> +#include <swundo.hxx> +#include <notxtfrm.hxx> +#include <txtfrm.hxx> +#include <mdiexp.hxx> +#include <sortedobjs.hxx> +#include <HandleAnchorNodeChg.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <calbck.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <svx/svxids.hrc> + +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <svx/srchdlg.hxx> + +#define SCROLLVAL 75 + +using namespace com::sun::star; + +/** + * set line starts and ends for the object to be created + */ + +namespace { + +::basegfx::B2DPolyPolygon getPolygon(const char* pResId, const SdrModel& rModel) +{ + ::basegfx::B2DPolyPolygon aRetval; + XLineEndListRef pLineEndList(rModel.GetLineEndList()); + + if( pLineEndList.is() ) + { + OUString aArrowName( SvxResId(pResId) ); + long nCount = pLineEndList->Count(); + long nIndex; + for( nIndex = 0; nIndex < nCount; nIndex++ ) + { + const XLineEndEntry* pEntry = pLineEndList->GetLineEnd(nIndex); + if( pEntry->GetName() == aArrowName ) + { + aRetval = pEntry->GetLineEnd(); + break; + } + } + } + + return aRetval; +} + +} + +SwFlyFrame *GetFlyFromMarked( const SdrMarkList *pLst, SwViewShell *pSh ) +{ + if ( !pLst ) + pLst = pSh->HasDrawView() ? &pSh->Imp()->GetDrawView()->GetMarkedObjectList():nullptr; + + if ( pLst && pLst->GetMarkCount() == 1 ) + { + SdrObject *pO = pLst->GetMark( 0 )->GetMarkedSdrObj(); + if (SwVirtFlyDrawObj* pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pO)) + return pVirtO->GetFlyFrame(); + } + return nullptr; +} + +static void lcl_GrabCursor( SwFEShell* pSh, SwFlyFrame* pOldSelFly) +{ + const SwFrameFormat *pFlyFormat = pSh->SelFlyGrabCursor(); + if( pFlyFormat && !pSh->ActionPend() && + (!pOldSelFly || pOldSelFly->GetFormat() != pFlyFormat) ) + { + // now call set macro if applicable + pSh->GetFlyMacroLnk().Call( static_cast<const SwFlyFrameFormat*>(pFlyFormat) ); + // if a dialog was started inside a macro, then + // MouseButtonUp arrives at macro and not to us. Therefore + // flag is always set here and will never be switched to + // respective Shell !!!!!!! + + g_bNoInterrupt = false; + } + else if( !pFlyFormat || RES_DRAWFRMFMT == pFlyFormat->Which() ) + { + // --> assure consistent cursor + pSh->KillPams(); + pSh->ClearMark(); + pSh->SetCursor( pSh->Imp()->GetDrawView()->GetAllMarkedRect().TopLeft(), true); + } +} + +bool SwFEShell::SelectObj( const Point& rPt, sal_uInt8 nFlag, SdrObject *pObj ) +{ + SwDrawView *pDView = Imp()->GetDrawView(); + if(!pDView) + return false; + SET_CURR_SHELL( this ); + StartAction(); // action is necessary to assure only one AttrChgdNotify + // (e.g. due to Unmark->MarkListHasChgd) arrives + + const SdrMarkList &rMrkList = pDView->GetMarkedObjectList(); + const bool bHadSelection = rMrkList.GetMarkCount(); + const bool bAddSelect = 0 != (SW_ADD_SELECT & nFlag); + const bool bEnterGroup = 0 != (SW_ENTER_GROUP & nFlag); + SwFlyFrame* pOldSelFly = nullptr; + const Point aOldPos( pDView->GetAllMarkedRect().TopLeft() ); + + if( bHadSelection ) + { + // call Unmark when !bAddSelect or if fly was selected + bool bUnmark = !bAddSelect; + + if ( rMrkList.GetMarkCount() == 1 ) + { + // if fly was selected, deselect it first + pOldSelFly = ::GetFlyFromMarked( &rMrkList, this ); + if ( pOldSelFly ) + { + const sal_uInt16 nType = GetCntType(); + if( nType != CNT_TXT || (SW_LEAVE_FRAME & nFlag) || + ( pOldSelFly->GetFormat()->GetProtect().IsContentProtected() + && !IsReadOnlyAvailable() )) + { + // If a fly is deselected, which contains graphic, OLE or + // otherwise, the cursor should be removed from it. + // Similar if a fly with protected content is deselected. + // For simplicity we put the cursor next to the upper-left + // corner. + Point aPt( pOldSelFly->getFrameArea().Pos() ); + aPt.setX(aPt.getX() - 1); + bool bUnLockView = !IsViewLocked(); + LockView( true ); + SetCursor( aPt, true ); + if( bUnLockView ) + LockView( false ); + } + if ( nType & CNT_GRF && + static_cast<SwNoTextFrame*>(pOldSelFly->Lower())->HasAnimation() ) + { + GetWin()->Invalidate( pOldSelFly->getFrameArea().SVRect() ); + } + + // Cancel crop mode + if ( SdrDragMode::Crop == GetDragMode() ) + SetDragMode( SdrDragMode::Move ); + + bUnmark = true; + } + } + if ( bUnmark ) + { + pDView->UnmarkAll(); + if (pOldSelFly) + pOldSelFly->SelectionHasChanged(this); + } + } + else + { + KillPams(); + ClearMark(); + } + + if ( pObj ) + { + OSL_ENSURE( !bEnterGroup, "SW_ENTER_GROUP is not supported" ); + pDView->MarkObj( pObj, Imp()->GetPageView() ); + } + else + { + // tolerance limit of Drawing-SS + const auto nHdlSizePixel = Imp()->GetDrawView()->GetMarkHdlSizePixel(); + const short nMinMove = static_cast<short>(GetOut()->PixelToLogic(Size(nHdlSizePixel/2, 0)).Width()); + pDView->MarkObj( rPt, nMinMove, bAddSelect, bEnterGroup ); + } + + const bool bRet = 0 != rMrkList.GetMarkCount(); + + if ( rMrkList.GetMarkCount() > 1 ) + { + // It sucks if Drawing objects were selected and now + // additionally a fly is selected. + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pTmpObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + bool bForget = dynamic_cast<const SwVirtFlyDrawObj*>( pTmpObj) != nullptr; + if( bForget ) + { + pDView->UnmarkAll(); + pDView->MarkObj( pTmpObj, Imp()->GetPageView(), bAddSelect, bEnterGroup ); + break; + } + } + } + + if ( rMrkList.GetMarkCount() == 1 ) + { + SwFlyFrame *pSelFly = ::GetFlyFromMarked( &rMrkList, this ); + if (pSelFly) + pSelFly->SelectionHasChanged(this); + } + + if (!(nFlag & SW_ALLOW_TEXTBOX)) + { + // If the fly frame is a textbox of a shape, then select the shape instead. + for (size_t i = 0; i < rMrkList.GetMarkCount(); ++i) + { + SdrObject* pObject = rMrkList.GetMark(i)->GetMarkedSdrObj(); + SwFrameFormat* pFormat = GetUserCall(pObject)->GetFormat(); + if (SwFrameFormat* pShapeFormat = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_FLYFRMFMT)) + { + SdrObject* pShape = pShapeFormat->FindSdrObject(); + pDView->UnmarkAll(); + pDView->MarkObj(pShape, Imp()->GetPageView(), bAddSelect, bEnterGroup); + break; + } + } + } + + if ( bRet ) + { + ::lcl_GrabCursor(this, pOldSelFly); + if ( GetCntType() & CNT_GRF ) + { + const SwFlyFrame *pTmp = GetFlyFromMarked( &rMrkList, this ); + OSL_ENSURE( pTmp, "Graphic without Fly" ); + if ( static_cast<const SwNoTextFrame*>(pTmp->Lower())->HasAnimation() ) + static_cast<const SwNoTextFrame*>(pTmp->Lower())->StopAnimation( GetOut() ); + } + } + else if ( !pOldSelFly && bHadSelection ) + SetCursor( aOldPos, true); + + if( bRet || !bHadSelection ) + CallChgLnk(); + + // update status line + ::FrameNotify( this, bRet ? FLY_DRAG_START : FLY_DRAG_END ); + + EndAction(); + return bRet; +} + +/* + * Description: MoveAnchor( nDir ) looked for an another Anchor for + * the selected drawing object (or fly frame) in the given direction. + * An object "as character" doesn't moves anyway. + * A page bounded object could move to the previous/next page with up/down, + * an object bounded "at paragraph" moves to the previous/next paragraph, too. + * An object bounded "at character" moves to the previous/next paragraph + * with up/down and to the previous/next character with left/right. + * If the anchor for at paragraph/character bounded objects has vertical or + * right_to_left text direction, the directions for up/down/left/right will + * interpreted accordingly. + * An object bounded "at fly" takes the center of the actual anchor and looks + * for the nearest fly frame in the given direction. + */ + +static bool LessX( Point const & aPt1, Point const & aPt2, bool bOld ) +{ + return aPt1.getX() < aPt2.getX() + || ( aPt1.getX() == aPt2.getX() + && ( aPt1.getY() < aPt2.getY() + || ( aPt1.getY() == aPt2.getY() && bOld ) ) ); +} +static bool LessY( Point const & aPt1, Point const & aPt2, bool bOld ) +{ + return aPt1.getY() < aPt2.getY() + || ( aPt1.getY() == aPt2.getY() + && ( aPt1.getX() < aPt2.getX() + || ( aPt1.getX() == aPt2.getX() && bOld ) ) ); +} + +bool SwFEShell::MoveAnchor( SwMove nDir ) +{ + if (!Imp()->GetDrawView()) + return false; + const SdrMarkList& pMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if (1 != pMrkList.GetMarkCount()) + return false; + SwFrame* pOld; + SwFlyFrame* pFly = nullptr; + SdrObject *pObj = pMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if (SwVirtFlyDrawObj* pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pObj)) + { + pFly = pVirtO->GetFlyFrame(); + pOld = pFly->AnchorFrame(); + } + else + pOld = static_cast<SwDrawContact*>(GetUserCall(pObj))->GetAnchorFrame( pObj ); + bool bRet = false; + if( pOld ) + { + SwFrame* pNew = pOld; + // #i28701# + SwAnchoredObject* pAnchoredObj = ::GetUserCall( pObj )->GetAnchoredObj( pObj ); + SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + SwFormatAnchor aAnch( rFormat.GetAnchor() ); + RndStdIds nAnchorId = aAnch.GetAnchorId(); + if ( RndStdIds::FLY_AS_CHAR == nAnchorId ) + return false; + if( pOld->IsVertical() ) + { + if( pOld->IsTextFrame() ) + { + switch( nDir ) { + case SwMove::UP: nDir = SwMove::LEFT; break; + case SwMove::DOWN: nDir = SwMove::RIGHT; break; + case SwMove::LEFT: nDir = SwMove::DOWN; break; + case SwMove::RIGHT: nDir = SwMove::UP; break; + } + if( pOld->IsRightToLeft() ) + { + if( nDir == SwMove::LEFT ) + nDir = SwMove::RIGHT; + else if( nDir == SwMove::RIGHT ) + nDir = SwMove::LEFT; + } + } + } + switch ( nAnchorId ) { + case RndStdIds::FLY_AT_PAGE: + { + OSL_ENSURE( pOld->IsPageFrame(), "Wrong anchor, page expected." ); + if( SwMove::UP == nDir ) + pNew = pOld->GetPrev(); + else if( SwMove::DOWN == nDir ) + pNew = pOld->GetNext(); + if( pNew && pNew != pOld ) + { + aAnch.SetPageNum( static_cast<SwPageFrame*>(pNew)->GetPhyPageNum() ); + bRet = true; + } + break; + } + case RndStdIds::FLY_AT_CHAR: + { + OSL_ENSURE(pOld->IsTextFrame(), "Wrong anchor, text frame expected."); + if( SwMove::LEFT == nDir || SwMove::RIGHT == nDir ) + { + SwPosition pos = *aAnch.GetContentAnchor(); + SwTextFrame *const pOldFrame(static_cast<SwTextFrame*>(pOld)); + TextFrameIndex const nAct(pOldFrame->MapModelToViewPos(pos)); + if( SwMove::LEFT == nDir ) + { + bRet = true; + if( nAct ) + { + pos = pOldFrame->MapViewToModelPos(nAct - TextFrameIndex(1)); + } + else + nDir = SwMove::UP; + } + else + { + TextFrameIndex const nMax(pOldFrame->GetText().getLength()); + if( nAct < nMax ) + { + bRet = true; + pos = pOldFrame->MapViewToModelPos(nAct + TextFrameIndex(1)); + } + else + nDir = SwMove::DOWN; + } + if( pos != *aAnch.GetContentAnchor()) + aAnch.SetAnchor( &pos ); + } + [[fallthrough]]; + } + case RndStdIds::FLY_AT_PARA: + { + OSL_ENSURE(pOld->IsTextFrame(), "Wrong anchor, text frame expected."); + if( SwMove::UP == nDir ) + pNew = pOld->FindPrev(); + else if( SwMove::DOWN == nDir ) + pNew = pOld->FindNext(); + if( pNew && pNew != pOld && pNew->IsContentFrame() ) + { + SwTextFrame *const pNewFrame(static_cast<SwTextFrame*>(pNew)); + SwPosition const pos = pNewFrame->MapViewToModelPos( + TextFrameIndex( + (bRet && pNewFrame->GetText().getLength() != 0) + ? pNewFrame->GetText().getLength() - 1 + : 0)); + aAnch.SetAnchor( &pos ); + bRet = true; + } + else if( SwMove::UP == nDir || SwMove::DOWN == nDir ) + bRet = false; + break; + } + case RndStdIds::FLY_AT_FLY: + { + OSL_ENSURE( pOld->IsFlyFrame(), "Wrong anchor, fly frame expected."); + SwPageFrame* pPage = pOld->FindPageFrame(); + OSL_ENSURE( pPage, "Where's my page?" ); + SwFlyFrame* pNewFly = nullptr; + if( pPage->GetSortedObjs() ) + { + bool bOld = false; + Point aCenter( pOld->getFrameArea().Left() + pOld->getFrameArea().Width()/2, + pOld->getFrameArea().Top() + pOld->getFrameArea().Height()/2 ); + Point aBest; + for(SwAnchoredObject* pAnchObj : *pPage->GetSortedObjs()) + { + if( dynamic_cast<const SwFlyFrame*>( pAnchObj) != nullptr ) + { + SwFlyFrame* pTmp = static_cast<SwFlyFrame*>(pAnchObj); + if( pTmp == pOld ) + bOld = true; + else + { + const SwFlyFrame* pCheck = pFly ? pTmp : nullptr; + while( pCheck ) + { + if( pCheck == pFly ) + break; + const SwFrame *pNxt = pCheck->GetAnchorFrame(); + pCheck = pNxt ? pNxt->FindFlyFrame() : nullptr; + } + if( pCheck || pTmp->IsProtected() ) + continue; + Point aNew( pTmp->getFrameArea().Left() + + pTmp->getFrameArea().Width()/2, + pTmp->getFrameArea().Top() + + pTmp->getFrameArea().Height()/2 ); + bool bAccept = false; + switch( nDir ) { + case SwMove::RIGHT: + { + bAccept = LessX( aCenter, aNew, bOld ) + && ( !pNewFly || + LessX( aNew, aBest, false ) ); + break; + } + case SwMove::LEFT: + { + bAccept = LessX( aNew, aCenter, !bOld ) + && ( !pNewFly || + LessX( aBest, aNew, true ) ); + break; + } + case SwMove::UP: + { + bAccept = LessY( aNew, aCenter, !bOld ) + && ( !pNewFly || + LessY( aBest, aNew, true ) ); + break; + } + case SwMove::DOWN: + { + bAccept = LessY( aCenter, aNew, bOld ) + && ( !pNewFly || + LessY( aNew, aBest, false ) ); + break; + } + } + if( bAccept ) + { + pNewFly = pTmp; + aBest = aNew; + } + } + } + } + } + + if( pNewFly ) + { + SwPosition aPos( *pNewFly->GetFormat()-> + GetContent().GetContentIdx()); + aAnch.SetAnchor( &aPos ); + bRet = true; + } + break; + } + default: break; + } + if( bRet ) + { + StartAllAction(); + // --> handle change of anchor node: + // if count of the anchor frame also change, the fly frames have to be + // re-created. Thus, delete all fly frames except the <this> before the + // anchor attribute is change and re-create them afterwards. + { + std::unique_ptr<SwHandleAnchorNodeChg, o3tl::default_delete<SwHandleAnchorNodeChg>> pHandleAnchorNodeChg; + SwFlyFrameFormat* pFlyFrameFormat( dynamic_cast<SwFlyFrameFormat*>(&rFormat) ); + if ( pFlyFrameFormat ) + { + pHandleAnchorNodeChg.reset( + new SwHandleAnchorNodeChg( *pFlyFrameFormat, aAnch )); + } + rFormat.GetDoc()->SetAttr( aAnch, rFormat ); + } + // #i28701# - no call of method + // <CheckCharRectAndTopOfLine()> for to-character anchored + // Writer fly frame needed. This method call can cause a + // format of the anchor frame, which is no longer intended. + // Instead clear the anchor character rectangle and + // the top of line values for all to-character anchored objects. + pAnchoredObj->ClearCharRectAndTopOfLine(); + EndAllAction(); + } + } + return bRet; +} + +const SdrMarkList* SwFEShell::GetMarkList_() const +{ + const SdrMarkList* pMarkList = nullptr; + if( Imp()->GetDrawView() != nullptr ) + pMarkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + return pMarkList; +} + +FrameTypeFlags SwFEShell::GetSelFrameType() const +{ + FrameTypeFlags eType; + + // get marked frame list, and check if anything is selected + const SdrMarkList* pMarkList = GetMarkList_(); + if( pMarkList == nullptr || pMarkList->GetMarkCount() == 0 ) + eType = FrameTypeFlags::NONE; + else + { + // obtain marked item as fly frame; if no fly frame, it must + // be a draw object + const SwFlyFrame* pFly = ::GetFlyFromMarked(pMarkList, const_cast<SwFEShell*>(this)); + if ( pFly != nullptr ) + { + if( pFly->IsFlyLayFrame() ) + eType = FrameTypeFlags::FLY_FREE; + else if( pFly->IsFlyAtContentFrame() ) + eType = FrameTypeFlags::FLY_ATCNT; + else + { + OSL_ENSURE( pFly->IsFlyInContentFrame(), "New frametype?" ); + eType = FrameTypeFlags::FLY_INCNT; + } + } + else + eType = FrameTypeFlags::DRAWOBJ; + } + + return eType; +} + +// does the draw selection contain a control? +bool SwFEShell::IsSelContainsControl() const +{ + bool bRet = false; + + // basically, copy the mechanism from GetSelFrameType(), but call + // CheckControl... if you get a drawing object + const SdrMarkList* pMarkList = GetMarkList_(); + if( pMarkList != nullptr && pMarkList->GetMarkCount() == 1 ) + { + // if we have one marked object, get the SdrObject and check + // whether it contains a control + const SdrObject* pSdrObject = pMarkList->GetMark( 0 )->GetMarkedSdrObj(); + bRet = pSdrObject && ::CheckControlLayer( pSdrObject ); + } + return bRet; +} + +void SwFEShell::ScrollTo( const Point &rPt ) +{ + const SwRect aRect( rPt, rPt ); + if ( IsScrollMDI( this, aRect ) && + (!Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() || + Imp()->IsDragPossible( rPt )) ) + { + ScrollMDI( this, aRect, SCROLLVAL, SCROLLVAL ); + } +} + +void SwFEShell::SetDragMode( SdrDragMode eDragMode ) +{ + if ( Imp()->HasDrawView() ) + Imp()->GetDrawView()->SetDragMode( eDragMode ); +} + +SdrDragMode SwFEShell::GetDragMode() const +{ + SdrDragMode nRet = SdrDragMode(0); + if ( Imp()->HasDrawView() ) + { + nRet = Imp()->GetDrawView()->GetDragMode(); + } + return nRet; +} + +void SwFEShell::StartCropImage() +{ + if ( !Imp()->HasDrawView() ) + { + return; + } + SdrView *pView = Imp()->GetDrawView(); + if (!pView) return; + + const SdrMarkList &rMarkList = pView->GetMarkedObjectList(); + if( 0 == rMarkList.GetMarkCount() ) { + // No object selected + return; + } + + // If more than a single SwVirtFlyDrawObj is selected, select only the first SwVirtFlyDrawObj + if ( rMarkList.GetMarkCount() > 1 ) + { + for ( size_t i = 0; i < rMarkList.GetMarkCount(); ++i ) + { + SdrObject *pTmpObj = rMarkList.GetMark( i )->GetMarkedSdrObj(); + bool bForget = dynamic_cast<const SwVirtFlyDrawObj*>( pTmpObj) != nullptr; + if( bForget ) + { + pView->UnmarkAll(); + pView->MarkObj( pTmpObj, Imp()->GetPageView() ); + break; + } + } + } + + // Activate CROP mode + pView->SetEditMode( SdrViewEditMode::Edit ); + SetDragMode( SdrDragMode::Crop ); +} + +void SwFEShell::BeginDrag( const Point* pPt, bool bIsShift) +{ + SdrView *pView = Imp()->GetDrawView(); + if ( pView && pView->AreObjectsMarked() ) + { + m_pChainFrom.reset(); + m_pChainTo.reset(); + SdrHdl* pHdl = pView->PickHandle( *pPt ); + if (pView->BegDragObj( *pPt, nullptr, pHdl )) + pView->GetDragMethod()->SetShiftPressed( bIsShift ); + ::FrameNotify( this ); + } +} + +void SwFEShell::Drag( const Point *pPt, bool ) +{ + OSL_ENSURE( Imp()->HasDrawView(), "Drag without DrawView?" ); + if ( HasDrawViewDrag() ) + { + ScrollTo( *pPt ); + Imp()->GetDrawView()->MovDragObj( *pPt ); + Imp()->GetDrawView()->ShowDragAnchor(); + ::FrameNotify( this ); + } +} + +void SwFEShell::EndDrag() +{ + OSL_ENSURE( Imp()->HasDrawView(), "EndDrag without DrawView?" ); + SdrView *pView = Imp()->GetDrawView(); + if ( pView->IsDragObj() ) + { + for(SwViewShell& rSh : GetRingContainer()) + rSh.StartAction(); + + StartUndo( SwUndoId::START ); + + // #50778# Bug during dragging: In StartAction a HideShowXor is called. + // In EndDragObj() this is reversed, for no reason and even wrong. + // To restore consistency we should bring up the Xor again. + + // Reanimation from the hack #50778 to fix bug #97057 + // May be not the best solution, but the one with lowest risc at the moment. + // pView->ShowShownXor( GetOut() ); + + pView->EndDragObj(); + + // DrawUndo on to flyframes are not stored + // The flys change the flag. + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(true); + ChgAnchor( RndStdIds::FLY_AT_PARA, true ); + + EndUndo( SwUndoId::END ); + + for(SwViewShell& rSh : GetRingContainer()) + { + rSh.EndAction(); + if( dynamic_cast<const SwCursorShell *>(&rSh) != nullptr ) + static_cast<SwCursorShell*>(&rSh)->CallChgLnk(); + } + + GetDoc()->getIDocumentState().SetModified(); + ::FrameNotify( this ); + } +} + +void SwFEShell::BreakDrag() +{ + OSL_ENSURE( Imp()->HasDrawView(), "BreakDrag without DrawView?" ); + if( HasDrawViewDrag() ) + Imp()->GetDrawView()->BrkDragObj(); + SetChainMarker(); +} + +// If a fly is selected, pulls the crsr in the first ContentFrame +const SwFrameFormat* SwFEShell::SelFlyGrabCursor() +{ + if ( Imp()->HasDrawView() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + SwFlyFrame *pFly = ::GetFlyFromMarked( &rMrkList, this ); + + if( pFly ) + { + SwContentFrame *pCFrame = pFly->ContainsContent(); + if ( pCFrame ) + { + // --> assure, that the cursor is consistent. + KillPams(); + ClearMark(); + SwPaM *pCursor = GetCursor(); + + if (pCFrame->IsTextFrame()) + { + *pCursor->GetPoint() = static_cast<SwTextFrame *>(pCFrame) + ->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + assert(pCFrame->IsNoTextFrame()); + SwContentNode *const pCNode = static_cast<SwNoTextFrame *>(pCFrame)->GetNode(); + pCursor->GetPoint()->nNode = *pCNode; + pCursor->GetPoint()->nContent.Assign( pCNode, 0 ); + } + + SwRect& rChrRect = const_cast<SwRect&>(GetCharRect()); + rChrRect = pFly->getFramePrintArea(); + rChrRect.Pos() += pFly->getFrameArea().Pos(); + GetCursorDocPos() = rChrRect.Pos(); + } + return pFly->GetFormat(); + } + } + return nullptr; +} + +// Selection to above/below (Z-Order) +static void lcl_NotifyNeighbours( const SdrMarkList *pLst ) +{ + // Rules for evasion have changed. + // 1. The environment of the fly and everything inside should be notified + // 2. The content of the frame itself has to be notified + // 3. Frames displaced by the frame have to be notified + // 4. Also Drawing objects can displace frames + for( size_t j = 0; j < pLst->GetMarkCount(); ++j ) + { + SwPageFrame *pPage; + bool bCheckNeighbours = false; + sal_Int16 aHori = text::HoriOrientation::NONE; + SwRect aRect; + SdrObject *pO = pLst->GetMark( j )->GetMarkedSdrObj(); + if (SwVirtFlyDrawObj* pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pO)) + { + SwFlyFrame *pFly = pVirtO->GetFlyFrame(); + + const SwFormatHoriOrient &rHori = pFly->GetFormat()->GetHoriOrient(); + aHori = rHori.GetHoriOrient(); + if( text::HoriOrientation::NONE != aHori && text::HoriOrientation::CENTER != aHori && + pFly->IsFlyAtContentFrame() ) + { + bCheckNeighbours = true; + pFly->InvalidatePos(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly); + aFrm.Pos().AdjustY(1 ); + } + + pPage = pFly->FindPageFrame(); + aRect = pFly->getFrameArea(); + } + else + { + SwFrame* pAnch = static_cast<SwDrawContact*>( GetUserCall(pO) )->GetAnchorFrame( pO ); + if( !pAnch ) + continue; + pPage = pAnch->FindPageFrame(); + // #i68520# - naming changed + aRect = GetBoundRectOfAnchoredObj( pO ); + } + + const size_t nCount = pPage->GetSortedObjs() ? pPage->GetSortedObjs()->size() : 0; + for ( size_t i = 0; i < nCount; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pPage->GetSortedObjs())[i]; + if ( dynamic_cast<const SwFlyFrame*>( pAnchoredObj) == nullptr ) + continue; + + SwFlyFrame* pAct = static_cast<SwFlyFrame*>(pAnchoredObj); + SwRect aTmpCalcPnt( pAct->getFramePrintArea() ); + aTmpCalcPnt += pAct->getFrameArea().Pos(); + if ( aRect.IsOver( aTmpCalcPnt ) ) + { + SwContentFrame *pCnt = pAct->ContainsContent(); + while ( pCnt ) + { + aTmpCalcPnt = pCnt->getFramePrintArea(); + aTmpCalcPnt += pCnt->getFrameArea().Pos(); + if ( aRect.IsOver( aTmpCalcPnt ) ) + static_cast<SwFrame*>(pCnt)->Prepare( PrepareHint::FlyFrameAttributesChanged ); + pCnt = pCnt->GetNextContentFrame(); + } + } + if ( bCheckNeighbours && pAct->IsFlyAtContentFrame() ) + { + const SwFormatHoriOrient &rH = pAct->GetFormat()->GetHoriOrient(); + if ( rH.GetHoriOrient() == aHori && + pAct->getFrameArea().Top() <= aRect.Bottom() && + pAct->getFrameArea().Bottom() >= aRect.Top() ) + { + pAct->InvalidatePos(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pAct); + aFrm.Pos().AdjustY(1 ); + } + } + } + } +} + +void SwFEShell::SetLineEnds(SfxItemSet& rAttr, SdrObject const & rObj, sal_uInt16 nSlotId) +{ + SdrModel& rModel(rObj.getSdrModelFromSdrObject()); + + if ( !(nSlotId == SID_LINE_ARROW_START || + nSlotId == SID_LINE_ARROW_END || + nSlotId == SID_LINE_ARROWS || + nSlotId == SID_LINE_ARROW_CIRCLE || + nSlotId == SID_LINE_CIRCLE_ARROW || + nSlotId == SID_LINE_ARROW_SQUARE || + nSlotId == SID_LINE_SQUARE_ARROW || + nSlotId == SID_DRAW_MEASURELINE) ) + return; + + // set attributes of line start and ends + + // arrowhead + ::basegfx::B2DPolyPolygon aArrow( getPolygon( RID_SVXSTR_ARROW, rModel ) ); + if( !aArrow.count() ) + { + ::basegfx::B2DPolygon aNewArrow; + aNewArrow.append(::basegfx::B2DPoint(10.0, 0.0)); + aNewArrow.append(::basegfx::B2DPoint(0.0, 30.0)); + aNewArrow.append(::basegfx::B2DPoint(20.0, 30.0)); + aNewArrow.setClosed(true); + aArrow.append(aNewArrow); + } + + // Circles + ::basegfx::B2DPolyPolygon aCircle( getPolygon( RID_SVXSTR_CIRCLE, rModel ) ); + if( !aCircle.count() ) + { + ::basegfx::B2DPolygon aNewCircle = ::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(0.0, 0.0), 250.0, 250.0); + aNewCircle.setClosed(true); + aCircle.append(aNewCircle); + } + + // Square + ::basegfx::B2DPolyPolygon aSquare( getPolygon( RID_SVXSTR_SQUARE, rModel ) ); + if( !aSquare.count() ) + { + ::basegfx::B2DPolygon aNewSquare; + aNewSquare.append(::basegfx::B2DPoint(0.0, 0.0)); + aNewSquare.append(::basegfx::B2DPoint(10.0, 0.0)); + aNewSquare.append(::basegfx::B2DPoint(10.0, 10.0)); + aNewSquare.append(::basegfx::B2DPoint(0.0, 10.0)); + aNewSquare.setClosed(true); + aSquare.append(aNewSquare); + } + + SfxItemSet aSet( rModel.GetItemPool() ); + long nWidth = 100; // (1/100th mm) + + // determine line width and calculate with it the line end width + if( aSet.GetItemState( XATTR_LINEWIDTH ) != SfxItemState::DONTCARE ) + { + long nValue = aSet.Get( XATTR_LINEWIDTH ).GetValue(); + if( nValue > 0 ) + nWidth = nValue * 3; + } + + switch (nSlotId) + { + case SID_LINE_ARROWS: + case SID_DRAW_MEASURELINE: + { + // connector with arrow ends + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineStartWidthItem(nWidth)); + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + + case SID_LINE_ARROW_START: + case SID_LINE_ARROW_CIRCLE: + case SID_LINE_ARROW_SQUARE: + { + // connector with arrow start + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineStartWidthItem(nWidth)); + } + break; + + case SID_LINE_ARROW_END: + case SID_LINE_CIRCLE_ARROW: + case SID_LINE_SQUARE_ARROW: + { + // connector with arrow end + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + } + + // and again, for the still missing ends + switch (nSlotId) + { + case SID_LINE_CIRCLE_ARROW: + { + // circle start + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_CIRCLE), aCircle)); + rAttr.Put(XLineStartWidthItem(nWidth)); + } + break; + + case SID_LINE_ARROW_SQUARE: + { + // square end + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_SQUARE), aSquare)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + + case SID_LINE_SQUARE_ARROW: + { + // square start + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_SQUARE), aSquare)); + rAttr.Put(XLineStartWidthItem(nWidth)); + } + break; + } + +} + +void SwFEShell::SelectionToTop( bool bTop ) +{ + OSL_ENSURE( Imp()->HasDrawView(), "SelectionToTop without DrawView?" ); + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + OSL_ENSURE( rMrkList.GetMarkCount(), "No object selected." ); + + SwFlyFrame *pFly = ::GetFlyFromMarked( &rMrkList, this ); + if ( pFly && pFly->IsFlyInContentFrame() ) + return; + + StartAllAction(); + if ( bTop ) + Imp()->GetDrawView()->PutMarkedToTop(); + else + Imp()->GetDrawView()->MovMarkedToTop(); + ::lcl_NotifyNeighbours( &rMrkList ); + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +void SwFEShell::SelectionToBottom( bool bBottom ) +{ + OSL_ENSURE( Imp()->HasDrawView(), "SelectionToBottom without DrawView?" ); + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + OSL_ENSURE( rMrkList.GetMarkCount(), "No object selected." ); + + SwFlyFrame *pFly = ::GetFlyFromMarked( &rMrkList, this ); + if ( pFly && pFly->IsFlyInContentFrame() ) + return; + + StartAllAction(); + if ( bBottom ) + Imp()->GetDrawView()->PutMarkedToBtm(); + else + Imp()->GetDrawView()->MovMarkedToBtm(); + ::lcl_NotifyNeighbours( &rMrkList ); + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +// Object above/below the document? 2 Controls, 1 Heaven, 0 Hell, +// SDRLAYER_NOTFOUND Ambiguous +SdrLayerID SwFEShell::GetLayerId() const +{ + if ( !Imp()->HasDrawView() ) + return SDRLAYER_NOTFOUND; + + SdrLayerID nRet = SDRLAYER_NOTFOUND; + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + const SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if( !pObj ) + continue; + if ( nRet == SDRLAYER_NOTFOUND ) + nRet = pObj->GetLayer(); + else if ( nRet != pObj->GetLayer() ) + { + return SDRLAYER_NOTFOUND; + } + } + return nRet; +} + +// Object above/below the document +// Note: only visible objects can be marked. Thus, objects with invisible +// layer IDs have not to be considered. +// If <SwFEShell> exists, layout exists!! +void SwFEShell::ChangeOpaque( SdrLayerID nLayerId ) +{ + if ( Imp()->HasDrawView() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + const IDocumentDrawModelAccess& rIDDMA = getIDocumentDrawModelAccess(); + // correct type of <nControls> + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject* pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if( !pObj ) + continue; + // or group objects containing controls. + // --> #i113730# + // consider that a member of a drawing group has been selected. + const SwContact* pContact = ::GetUserCall( pObj ); + OSL_ENSURE( pContact && pContact->GetMaster(), "<SwFEShell::ChangeOpaque(..)> - missing contact or missing master object at contact!" ); + const bool bControlObj = ( pContact && pContact->GetMaster() ) + ? ::CheckControlLayer( pContact->GetMaster() ) + : ::CheckControlLayer( pObj ); + if ( !bControlObj && pObj->GetLayer() != nLayerId ) + { + pObj->SetLayer( nLayerId ); + InvalidateWindows( SwRect( pObj->GetCurrentBoundRect() ) ); + if (SwVirtFlyDrawObj* pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pObj)) + { + SwFormat *pFormat = pVirtO->GetFlyFrame()->GetFormat(); + SvxOpaqueItem aOpa( pFormat->GetOpaque() ); + aOpa.SetValue( nLayerId == rIDDMA.GetHellId() ); + pFormat->SetFormatAttr( aOpa ); + } + } + } + GetDoc()->getIDocumentState().SetModified(); + } +} + +void SwFEShell::SelectionToHeaven() +{ + ChangeOpaque( getIDocumentDrawModelAccess().GetHeavenId() ); +} + +void SwFEShell::SelectionToHell() +{ + ChangeOpaque( getIDocumentDrawModelAccess().GetHellId() ); +} + +size_t SwFEShell::IsObjSelected() const +{ + if ( IsFrameSelected() || !Imp()->HasDrawView() ) + return 0; + + return Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount(); +} + +bool SwFEShell::IsFrameSelected() const +{ + if ( !Imp()->HasDrawView() ) + return false; + else + return nullptr != ::GetFlyFromMarked( &Imp()->GetDrawView()->GetMarkedObjectList(), + const_cast<SwFEShell*>(this) ); +} + +bool SwFEShell::IsObjSelected( const SdrObject& rObj ) const +{ + if ( IsFrameSelected() || !Imp()->HasDrawView() ) + return false; + else + return Imp()->GetDrawView()->IsObjMarked( &rObj ); +} + +bool SwFEShell::IsRotationOfSwGrfNodePossible() const +{ + // RotGrfFlyFrame: check if RotationMode is possible + const SdrView *pSdrView = Imp()->GetDrawView(); + + if(pSdrView) + { + const SdrMarkList& rList(pSdrView->GetMarkedObjectList()); + + if(1 == rList.GetMarkCount()) + { + const SwVirtFlyDrawObj* pVirtFlyDraw(dynamic_cast< const SwVirtFlyDrawObj* >(rList.GetMark(0)->GetMarkedSdrObj())); + + if(nullptr != pVirtFlyDraw) + { + return pVirtFlyDraw->ContainsSwGrfNode(); + } + } + } + + return false; +} + +bool SwFEShell::IsObjSameLevelWithMarked(const SdrObject* pObj) const +{ + if (pObj) + { + const SdrMarkList& aMarkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if (aMarkList.GetMarkCount() == 0) + { + return true; + } + SdrMark* pM=aMarkList.GetMark(0); + if (pM) + { + SdrObject* pMarkObj = pM->GetMarkedSdrObj(); + if (pMarkObj && pMarkObj->getParentSdrObjectFromSdrObject() == pObj->getParentSdrObjectFromSdrObject()) + return true; + } + } + return false; +} + +void SwFEShell::EndTextEdit() +{ + // Terminate the TextEditMode. If required (default if the object + // does not contain any more text and does not carry attributes) the object + // is deleted. All other objects marked are preserved. + + OSL_ENSURE( Imp()->HasDrawView() && Imp()->GetDrawView()->IsTextEdit(), + "EndTextEdit a no Object" ); + + StartAllAction(); + SdrView *pView = Imp()->GetDrawView(); + SdrObject *pObj = pView->GetTextEditObject(); + SdrObjUserCall* pUserCall; + if( nullptr != ( pUserCall = GetUserCall(pObj) ) ) + { + SdrObject *pTmp = static_cast<SwContact*>(pUserCall)->GetMaster(); + if( !pTmp ) + pTmp = pObj; + pUserCall->Changed( *pTmp, SdrUserCallType::Resize, pTmp->GetLastBoundRect() ); + } + if ( !pObj->getParentSdrObjectFromSdrObject() ) + { + if ( SdrEndTextEditKind::ShouldBeDeleted == pView->SdrEndTextEdit(true) ) + { + if ( pView->GetMarkedObjectList().GetMarkCount() > 1 ) + { + SdrMarkList aSave( pView->GetMarkedObjectList() ); + aSave.DeleteMark( aSave.FindObject( pObj ) ); + if ( aSave.GetMarkCount() ) + { + pView->UnmarkAll(); + pView->MarkObj( pObj, Imp()->GetPageView() ); + } + DelSelectedObj(); + for ( size_t i = 0; i < aSave.GetMarkCount(); ++i ) + pView->MarkObj( aSave.GetMark( i )->GetMarkedSdrObj(), Imp()->GetPageView() ); + } + else + DelSelectedObj(); + } + } + else + pView->SdrEndTextEdit(); + + if (comphelper::LibreOfficeKit::isActive()) + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_LOCK, "rectangle", "EMPTY"); + + EndAllAction(); +} + +bool SwFEShell::IsInsideSelectedObj( const Point &rPt ) +{ + if( Imp()->HasDrawView() ) + { + SwDrawView *pDView = Imp()->GetDrawView(); + + if( pDView->GetMarkedObjectList().GetMarkCount() && + pDView->IsMarkedObjHit( rPt ) ) + { + return true; + } + } + return false; +} + +bool SwFEShell::IsObjSelectable( const Point& rPt ) +{ + SET_CURR_SHELL(this); + SwDrawView *pDView = Imp()->GetDrawView(); + bool bRet = false; + if( pDView ) + { + SdrPageView* pPV; + const auto nOld = pDView->GetHitTolerancePixel(); + pDView->SetHitTolerancePixel( pDView->GetMarkHdlSizePixel()/2 ); + + bRet = pDView->PickObj(rPt, pDView->getHitTolLog(), pPV, SdrSearchOptions::PICKMARKABLE) != nullptr; + pDView->SetHitTolerancePixel( nOld ); + } + return bRet; +} + +SdrObject* SwFEShell::GetObjAt( const Point& rPt ) +{ + SdrObject* pRet = nullptr; + SET_CURR_SHELL(this); + SwDrawView *pDView = Imp()->GetDrawView(); + if( pDView ) + { + SdrPageView* pPV; + const auto nOld = pDView->GetHitTolerancePixel(); + pDView->SetHitTolerancePixel( pDView->GetMarkHdlSizePixel()/2 ); + + pRet = pDView->PickObj(rPt, pDView->getHitTolLog(), pPV, SdrSearchOptions::PICKMARKABLE); + pDView->SetHitTolerancePixel( nOld ); + } + return pRet; +} + +// Test if there is an object at that position and if it should be selected. +bool SwFEShell::ShouldObjectBeSelected(const Point& rPt) +{ + SET_CURR_SHELL(this); + SwDrawView *pDrawView = Imp()->GetDrawView(); + bool bRet(false); + + if(pDrawView) + { + SdrPageView* pPV; + const auto nOld(pDrawView->GetHitTolerancePixel()); + + pDrawView->SetHitTolerancePixel(pDrawView->GetMarkHdlSizePixel()/2); + SdrObject* pObj = pDrawView->PickObj(rPt, pDrawView->getHitTolLog(), pPV, SdrSearchOptions::PICKMARKABLE); + pDrawView->SetHitTolerancePixel(nOld); + + if (pObj) + { + bRet = true; + const IDocumentDrawModelAccess& rIDDMA = getIDocumentDrawModelAccess(); + // #i89920# + // Do not select object in background which is overlapping this text + // at the given position. + bool bObjInBackground( false ); + { + if ( pObj->GetLayer() == rIDDMA.GetHellId() ) + { + const SwAnchoredObject* pAnchoredObj = ::GetUserCall( pObj )->GetAnchoredObj( pObj ); + const SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatSurround& rSurround = rFormat.GetSurround(); + if ( rSurround.GetSurround() == css::text::WrapTextMode_THROUGH ) + { + bObjInBackground = true; + } + } + } + if ( bObjInBackground ) + { + const SwPageFrame* pPageFrame = GetLayout()->GetPageAtPos( rPt ); + if( pPageFrame ) + { + const SwContentFrame* pContentFrame( pPageFrame->ContainsContent() ); + while ( pContentFrame ) + { + if ( pContentFrame->UnionFrame().IsInside( rPt ) ) + { + const SwTextFrame* pTextFrame = + dynamic_cast<const SwTextFrame*>(pContentFrame); + if ( pTextFrame ) + { + SwPosition aPos(GetDoc()->GetNodes()); + Point aTmpPt( rPt ); + if (pTextFrame->GetKeyCursorOfst(&aPos, aTmpPt)) + { + SwRect aCursorCharRect; + if (pTextFrame->GetCharRect(aCursorCharRect, + aPos)) + { + if ( aCursorCharRect.IsOver( SwRect( pObj->GetLastBoundRect() ) ) ) + { + bRet = false; + } + } + } + } + else + { + bRet = false; + } + break; + } + + pContentFrame = pContentFrame->GetNextContentFrame(); + } + } + } + + // Don't select header / footer objects in body edition and vice-versa + SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall()); + if (pContact && !pContact->ObjAnchoredAtPage() ) + { + const SwPosition& rPos = pContact->GetContentAnchor(); + bool bInHdrFtr = GetDoc()->IsInHeaderFooter( rPos.nNode ); + if (IsHeaderFooterEdit() != bInHdrFtr) + { + bRet = false; + } + } + + if ( bRet ) + { + const SdrPage* pPage = rIDDMA.GetDrawModel()->GetPage(0); + for(size_t a = pObj->GetOrdNum()+1; bRet && a < pPage->GetObjCount(); ++a) + { + SdrObject *pCandidate = pPage->GetObj(a); + + SwVirtFlyDrawObj* pDrawObj = dynamic_cast<SwVirtFlyDrawObj*>(pCandidate); + if (pDrawObj && pDrawObj->GetCurrentBoundRect().IsInside(rPt)) + { + bRet = false; + } + } + } + } + } + + return bRet; +} + +/* + * If an object was selected, we assume its upper-left corner + * otherwise the middle of the current CharRects. + * Does the object include a control or groups, + * which comprise only controls + */ +static bool lcl_IsControlGroup( const SdrObject *pObj ) +{ + bool bRet = false; + if(dynamic_cast<const SdrUnoObj*>( pObj) != nullptr) + bRet = true; + else if( auto pObjGroup = dynamic_cast<const SdrObjGroup*>( pObj) ) + { + bRet = true; + const SdrObjList *pLst = pObjGroup->GetSubList(); + for ( size_t i = 0; i < pLst->GetObjCount(); ++i ) + if( !::lcl_IsControlGroup( pLst->GetObj( i ) ) ) + return false; + } + return bRet; +} + +namespace +{ + class MarkableObjectsOnly : public svx::ISdrObjectFilter + { + public: + explicit MarkableObjectsOnly( SdrPageView* i_pPV ) + :m_pPV( i_pPV ) + { + } + + virtual bool includeObject( const SdrObject& i_rObject ) const override + { + return m_pPV && m_pPV->GetView().IsObjMarkable( &i_rObject, m_pPV ); + } + + private: + SdrPageView* m_pPV; + }; +} + +const SdrObject* SwFEShell::GetBestObject( bool bNext, GotoObjFlags eType, bool bFlat, const svx::ISdrObjectFilter* pFilter ) +{ + if( !Imp()->HasDrawView() ) + return nullptr; + + const SdrObject *pBest = nullptr, + *pTop = nullptr; + + const long nTmp = bNext ? LONG_MAX : 0; + Point aBestPos( nTmp, nTmp ); + Point aTopPos( nTmp, nTmp ); + Point aCurPos; + Point aPos; + bool bNoDraw((GotoObjFlags::DrawAny & eType) == GotoObjFlags::NONE); + bool bNoFly((GotoObjFlags::FlyAny & eType) == GotoObjFlags::NONE); + + if( !bNoFly && bNoDraw ) + { + SwFlyFrame *pFly = GetCurrFrame( false )->FindFlyFrame(); + if( pFly ) + pBest = pFly->GetVirtDrawObj(); + } + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + SdrPageView* pPV = Imp()->GetDrawView()->GetSdrPageView(); + + MarkableObjectsOnly aDefaultFilter( pPV ); + if ( !pFilter ) + pFilter = &aDefaultFilter; + + if( !pBest || rMrkList.GetMarkCount() == 1 ) + { + // Determine starting point + SdrObjList* pList = nullptr; + if ( rMrkList.GetMarkCount() ) + { + const SdrObject* pStartObj = rMrkList.GetMark(0)->GetMarkedSdrObj(); + if( auto pVirtFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>( pStartObj) ) + aPos = pVirtFlyDrawObj->GetFlyFrame()->getFrameArea().Pos(); + else + aPos = pStartObj->GetSnapRect().TopLeft(); + + // If an object inside a group is selected, we want to + // iterate over the group members. + if ( ! pStartObj->GetUserCall() ) + pList = pStartObj->getParentSdrObjListFromSdrObject(); + } + else + { + // If no object is selected, we check if we just entered a group. + // In this case we want to iterate over the group members. + aPos = GetCharRect().Center(); + const SdrObject* pStartObj = pPV ? pPV->GetCurrentGroup() : nullptr; + if ( dynamic_cast<const SdrObjGroup*>( pStartObj) ) + pList = pStartObj->GetSubList(); + } + + if ( ! pList ) + { + // Here we are if + // A No object has been selected and no group has been entered or + // B An object has been selected and it is not inside a group + pList = getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 ); + } + + OSL_ENSURE( pList, "No object list to iterate" ); + + SdrObjListIter aObjIter( pList, bFlat ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups ); + while ( aObjIter.IsMore() ) + { + SdrObject* pObj = aObjIter.Next(); + SwVirtFlyDrawObj *pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pObj); + if( ( bNoFly && pVirtO ) || + ( bNoDraw && !pVirtO ) || + // Ignore TextBoxes of draw shapes here, so that + // SwFEShell::SelectObj() won't jump back on this list, meaning + // we never jump to the next draw shape. + (pVirtO && pVirtO->IsTextBox()) || + ( eType == GotoObjFlags::DrawSimple && lcl_IsControlGroup( pObj ) ) || + ( eType == GotoObjFlags::DrawControl && !lcl_IsControlGroup( pObj ) ) || + !pFilter->includeObject( *pObj ) ) + continue; + if (pVirtO) + { + SwFlyFrame *pFly = pVirtO->GetFlyFrame(); + if( GotoObjFlags::FlyAny != ( GotoObjFlags::FlyAny & eType ) ) + { + switch ( eType ) + { + case GotoObjFlags::FlyFrame: + if ( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + continue; + break; + case GotoObjFlags::FlyGrf: + if ( pFly->Lower() && + (!pFly->Lower()->IsNoTextFrame() || + !static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetGrfNode())) + continue; + break; + case GotoObjFlags::FlyOLE: + if ( pFly->Lower() && + (!pFly->Lower()->IsNoTextFrame() || + !static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetOLENode())) + continue; + break; + default: break; + } + } + aCurPos = pFly->getFrameArea().Pos(); + } + else + aCurPos = pObj->GetSnapRect().TopLeft(); + + // Special case if another object is on same Y. + if( aCurPos != aPos && // only when it is not me + aCurPos.getY() == aPos.getY() && // Y positions equal + (bNext? (aCurPos.getX() > aPos.getX()) : // lies next to me + (aCurPos.getX() < aPos.getX())) ) // " reverse + { + aBestPos = Point( nTmp, nTmp ); + SdrObjListIter aTmpIter( pList, bFlat ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups ); + while ( aTmpIter.IsMore() ) + { + SdrObject* pTmpObj = aTmpIter.Next(); + pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pTmpObj); + if( ( bNoFly && pVirtO ) || ( bNoDraw && !pVirtO ) ) + continue; + if (pVirtO) + { + aCurPos = pVirtO->GetFlyFrame()->getFrameArea().Pos(); + } + else + aCurPos = pTmpObj->GetCurrentBoundRect().TopLeft(); + + if( aCurPos != aPos && aCurPos.Y() == aPos.Y() && + (bNext? (aCurPos.getX() > aPos.getX()) : // lies next to me + (aCurPos.getX() < aPos.getX())) && // " reverse + (bNext? (aCurPos.getX() < aBestPos.getX()) : // better as best + (aCurPos.getX() > aBestPos.getX())) ) // " reverse + { + aBestPos = aCurPos; + pBest = pTmpObj; + } + } + break; + } + + if( ( + (bNext? (aPos.getY() < aCurPos.getY()) : // only below me + (aPos.getY() > aCurPos.getY())) && // " reverse + (bNext? (aBestPos.getY() > aCurPos.getY()) : // closer below + (aBestPos.getY() < aCurPos.getY())) + ) || // " reverse + (aBestPos.getY() == aCurPos.getY() && + (bNext? (aBestPos.getX() > aCurPos.getX()) : // further left + (aBestPos.getX() < aCurPos.getX())))) // " reverse + + { + aBestPos = aCurPos; + pBest = pObj; + } + + if( (bNext? (aTopPos.getY() > aCurPos.getY()) : // higher as best + (aTopPos.getY() < aCurPos.getY())) || // " reverse + (aTopPos.getY() == aCurPos.getY() && + (bNext? (aTopPos.getX() > aCurPos.getX()) : // further left + (aTopPos.getX() < aCurPos.getX())))) // " reverse + { + aTopPos = aCurPos; + pTop = pObj; + } + } + // unfortunately nothing found + if( bNext ? (aBestPos.getX() == LONG_MAX) : (aBestPos.getX() == 0) ) + { + pBest = pTop; + SvxSearchDialogWrapper::SetSearchLabel( bNext ? SearchLabel::EndWrapped : SearchLabel::StartWrapped ); + } + } + + return pBest; +} + +bool SwFEShell::GotoObj( bool bNext, GotoObjFlags eType ) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + const SdrObject* pBest = GetBestObject( bNext, eType ); + + if ( !pBest ) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + const SwVirtFlyDrawObj *pVirtO = dynamic_cast<const SwVirtFlyDrawObj*>(pBest); + if (pVirtO) + { + const SwRect& rFrame = pVirtO->GetFlyFrame()->getFrameArea(); + SelectObj( rFrame.Pos(), 0, const_cast<SdrObject*>(pBest) ); + if( !ActionPend() ) + MakeVisible( rFrame ); + } + else + { + SelectObj( Point(), 0, const_cast<SdrObject*>(pBest) ); + if( !ActionPend() ) + MakeVisible( pBest->GetCurrentBoundRect() ); + } + CallChgLnk(); + return true; +} + +bool SwFEShell::BeginCreate( sal_uInt16 /*SdrObjKind ?*/ eSdrObjectKind, const Point &rPos ) +{ + bool bRet = false; + + if ( !Imp()->HasDrawView() ) + Imp()->MakeDrawView(); + + if ( GetPageNumber( rPos ) ) + { + Imp()->GetDrawView()->SetCurrentObj( eSdrObjectKind ); + if ( eSdrObjectKind == OBJ_CAPTION ) + bRet = Imp()->GetDrawView()->BegCreateCaptionObj( + rPos, Size( lMinBorder - MINFLY, lMinBorder - MINFLY ), + GetOut() ); + else + bRet = Imp()->GetDrawView()->BegCreateObj( rPos, GetOut() ); + } + if ( bRet ) + { + ::FrameNotify( this, FLY_DRAG_START ); + } + return bRet; +} + +bool SwFEShell::BeginCreate( sal_uInt16 /*SdrObjKind ?*/ eSdrObjectKind, SdrInventor eObjInventor, + const Point &rPos ) +{ + bool bRet = false; + + if ( !Imp()->HasDrawView() ) + Imp()->MakeDrawView(); + + if ( GetPageNumber( rPos ) ) + { + Imp()->GetDrawView()->SetCurrentObj( eSdrObjectKind, eObjInventor ); + bRet = Imp()->GetDrawView()->BegCreateObj( rPos, GetOut() ); + } + if ( bRet ) + ::FrameNotify( this, FLY_DRAG_START ); + return bRet; +} + +void SwFEShell::MoveCreate( const Point &rPos ) +{ + OSL_ENSURE( Imp()->HasDrawView(), "MoveCreate without DrawView?" ); + if ( GetPageNumber( rPos ) ) + { + ScrollTo( rPos ); + Imp()->GetDrawView()->MovCreateObj( rPos ); + ::FrameNotify( this ); + } +} + +bool SwFEShell::EndCreate( SdrCreateCmd eSdrCreateCmd ) +{ + // To assure undo-object from the DrawEngine is not stored, + // (we create our own undo-object!), temporarily switch-off Undo + OSL_ENSURE( Imp()->HasDrawView(), "EndCreate without DrawView?" ); + if( !Imp()->GetDrawView()->IsGroupEntered() ) + { + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); + } + bool bCreate = Imp()->GetDrawView()->EndCreateObj( eSdrCreateCmd ); + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(true); + + if ( !bCreate ) + { + ::FrameNotify( this, FLY_DRAG_END ); + return false; + } + + if ( eSdrCreateCmd == SdrCreateCmd::NextPoint ) + { + ::FrameNotify( this ); + return true; + } + return ImpEndCreate(); +} + +bool SwFEShell::ImpEndCreate() +{ + OSL_ENSURE( Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() == 1, + "New object not selected." ); + + SdrObject& rSdrObj = *Imp()->GetDrawView()->GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj(); + + if( rSdrObj.GetSnapRect().IsEmpty() ) + { + // preferably we forget the object, only gives problems + Imp()->GetDrawView()->DeleteMarked(); + Imp()->GetDrawView()->UnmarkAll(); + ::FrameNotify( this, FLY_DRAG_END ); + return false; + } + + if( rSdrObj.getParentSdrObjectFromSdrObject() ) + { + Point aTmpPos( rSdrObj.GetSnapRect().TopLeft() ); + Point aNewAnchor( rSdrObj.getParentSdrObjectFromSdrObject()->GetAnchorPos() ); + // OD 2004-04-05 #i26791# - direct object positioning for group members + rSdrObj.NbcSetRelativePos( aTmpPos - aNewAnchor ); + rSdrObj.NbcSetAnchorPos( aNewAnchor ); + ::FrameNotify( this ); + return true; + } + + LockPaint(); + StartAllAction(); + + Imp()->GetDrawView()->UnmarkAll(); + + const tools::Rectangle &rBound = rSdrObj.GetSnapRect(); + Point aPt( rBound.TopRight() ); + + // alien identifier should end up on defaults + // duplications possible!! + sal_uInt16 nIdent = SdrInventor::Default == rSdrObj.GetObjInventor() + ? rSdrObj.GetObjIdentifier() + : 0xFFFF; + + // default for controls character bound, otherwise paragraph bound. + SwFormatAnchor aAnch; + const SwFrame *pAnch = nullptr; + bool bCharBound = false; + if( dynamic_cast<const SdrUnoObj*>( &rSdrObj) != nullptr ) + { + SwPosition aPos( GetDoc()->GetNodes() ); + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + Point aPoint( aPt.getX(), aPt.getY() + rBound.GetHeight()/2 ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + + // characterbinding not allowed in readonly-content + if( !aPos.nNode.GetNode().IsProtect() ) + { + std::pair<Point, bool> const tmp(aPoint, true); + pAnch = aPos.nNode.GetNode().GetContentNode()->getLayoutFrame(GetLayout(), &aPos, &tmp); + SwRect aTmp; + pAnch->GetCharRect( aTmp, aPos ); + + // The crsr should not be too far away + bCharBound = true; + tools::Rectangle aRect( aTmp.SVRect() ); + aRect.AdjustLeft( -(MM50*2) ); + aRect.AdjustTop( -(MM50*2) ); + aRect.AdjustRight(MM50*2 ); + aRect.AdjustBottom(MM50*2 ); + + if( !aRect.IsOver( rBound ) && !::GetHtmlMode( GetDoc()->GetDocShell() )) + bCharBound = false; + + // anchor in header/footer also not allowed. + if( bCharBound ) + bCharBound = !GetDoc()->IsInHeaderFooter( aPos.nNode ); + + if( bCharBound ) + { + aAnch.SetType( RndStdIds::FLY_AS_CHAR ); + aAnch.SetAnchor( &aPos ); + } + } + } + + if( !bCharBound ) + { + // allow native drawing objects in header/footer. + // Thus, set <bBodyOnly> to <false> for these objects using value + // of <nIdent> - value <0xFFFF> indicates control objects, which aren't + // allowed in header/footer. + //bool bBodyOnly = OBJ_NONE != nIdent; + bool bBodyOnly = 0xFFFF == nIdent; + bool bAtPage = false; + const SwFrame* pPage = nullptr; + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + Point aPoint( aPt ); + SwPosition aPos( GetDoc()->GetNodes() ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + + // do not set in ReadnOnly-content + if (aPos.nNode.GetNode().IsProtect()) + { + // then only page bound. Or should we + // search the next not-readonly position? + bAtPage = true; + } + + SwContentNode* pCNode = aPos.nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(aPoint, false); + pAnch = pCNode ? pCNode->getLayoutFrame(GetLayout(), nullptr, &tmp) : nullptr; + if (!pAnch) + { + // Hidden content. Anchor to the page instead + bAtPage = true; + } + + if( !bAtPage ) + { + const SwFlyFrame *pTmp = pAnch->FindFlyFrame(); + if( pTmp ) + { + const SwFrame* pTmpFrame = pAnch; + SwRect aBound( rBound ); + while( pTmp ) + { + if( pTmp->getFrameArea().IsInside( aBound ) ) + { + if( !bBodyOnly || !pTmp->FindFooterOrHeader() ) + pPage = pTmpFrame; + break; + } + pTmp = pTmp->GetAnchorFrame() + ? pTmp->GetAnchorFrame()->FindFlyFrame() + : nullptr; + pTmpFrame = pTmp; + } + } + + if( !pPage ) + pPage = pAnch->FindPageFrame(); + + // Always via FindAnchor, to assure the frame will be bound + // to the previous. With GetCrsOfst we can also reach the next. THIS IS WRONG. + pAnch = ::FindAnchor( pPage, aPt, bBodyOnly ); + if (pAnch->IsTextFrame()) + { + std::pair<SwTextNode const*, sal_Int32> const pos( + static_cast<SwTextFrame const*>(pAnch)->MapViewToModel(TextFrameIndex(0))); + aPos.nNode = *pos.first; + } + else + { + aPos.nNode = *static_cast<const SwNoTextFrame*>(pAnch)->GetNode(); + } + + // do not set in ReadnOnly-content + if( aPos.nNode.GetNode().IsProtect() ) + // then only page bound. Or should we + // search the next not-readonly position? + bAtPage = true; + else + { + aAnch.SetType( RndStdIds::FLY_AT_PARA ); + aAnch.SetAnchor( &aPos ); + } + } + + if( bAtPage ) + { + pPage = pAnch ? pAnch->FindPageFrame() : GetLayout()->GetPageAtPos(aPoint); + + aAnch.SetType( RndStdIds::FLY_AT_PAGE ); + aAnch.SetPageNum( pPage->GetPhyPageNum() ); + pAnch = pPage; // page becomes an anchor + } + } + + SfxItemSet aSet( GetDoc()->GetAttrPool(), svl::Items<RES_FRM_SIZE, RES_FRM_SIZE, + RES_SURROUND, RES_ANCHOR>{} ); + aSet.Put( aAnch ); + + // OD 2004-03-30 #i26791# - determine relative object position + SwTwips nXOffset; + SwTwips nYOffset = rBound.Top() - pAnch->getFrameArea().Top(); + { + if( pAnch->IsVertical() ) + { + nXOffset = nYOffset; + nYOffset = pAnch->getFrameArea().Left()+pAnch->getFrameArea().Width()-rBound.Right(); + } + else if( pAnch->IsRightToLeft() ) + nXOffset = pAnch->getFrameArea().Left()+pAnch->getFrameArea().Width()-rBound.Right(); + else + nXOffset = rBound.Left() - pAnch->getFrameArea().Left(); + if (pAnch->IsTextFrame()) + { + const SwTextFrame* pTmp = static_cast<const SwTextFrame*>(pAnch); + if (pTmp->IsFollow()) + { + do { + pTmp = pTmp->FindMaster(); + OSL_ENSURE(pTmp, "Where's my Master?"); + // OD 2004-03-30 #i26791# - correction: add frame area height + // of master frames. + nYOffset += pTmp->IsVertical() ? + pTmp->getFrameArea().Width() : pTmp->getFrameArea().Height(); + } while (pTmp->IsFollow()); + } + + nYOffset -= pTmp->GetBaseVertOffsetForFly(false); + } + } + + if( OBJ_NONE == nIdent ) + { + // For OBJ_NONE a fly is inserted. + const long nWidth = rBound.Right() - rBound.Left(); + const long nHeight= rBound.Bottom() - rBound.Top(); + aSet.Put( SwFormatFrameSize( SwFrameSize::Minimum, std::max( nWidth, long(MINFLY) ), + std::max( nHeight, long(MINFLY) ))); + + SwFormatHoriOrient aHori( nXOffset, text::HoriOrientation::NONE, text::RelOrientation::FRAME ); + SwFormatVertOrient aVert( nYOffset, text::VertOrientation::NONE, text::RelOrientation::FRAME ); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_PARALLEL ) ); + aSet.Put( aHori ); + aSet.Put( aVert ); + + // Quickly store the square + const SwRect aFlyRect( rBound ); + + // Throw away generated object, now the fly can nicely + // via the available SS be generated. + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); // see above + // #i52858# - method name changed + SdrPage *pPg = getIDocumentDrawModelAccess().GetOrCreateDrawModel()->GetPage( 0 ); + if( !pPg ) + { + SdrModel* pTmpSdrModel = getIDocumentDrawModelAccess().GetDrawModel(); + pPg = pTmpSdrModel->AllocPage( false ); + pTmpSdrModel->InsertPage( pPg ); + } + pPg->RecalcObjOrdNums(); + SdrObject* pRemovedObject = pPg->RemoveObject( rSdrObj.GetOrdNumDirect() ); + SdrObject::Free( pRemovedObject ); + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(true); + + SwFlyFrame* pFlyFrame; + if( NewFlyFrame( aSet, true ) && + ::GetHtmlMode( GetDoc()->GetDocShell() ) && + nullptr != ( pFlyFrame = GetSelectedFlyFrame() )) + { + SfxItemSet aHtmlSet( GetDoc()->GetAttrPool(), svl::Items<RES_VERT_ORIENT, RES_HORI_ORIENT>{} ); + // horizontal orientation: + const bool bLeftFrame = aFlyRect.Left() < + pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Left(), + bLeftPrt = aFlyRect.Left() + aFlyRect.Width() < + pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width()/2; + if( bLeftFrame || bLeftPrt ) + { + aHori.SetHoriOrient( text::HoriOrientation::LEFT ); + aHori.SetRelationOrient( bLeftFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA ); + } + else + { + const bool bRightFrame = aFlyRect.Left() > + pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width(); + aHori.SetHoriOrient( text::HoriOrientation::RIGHT ); + aHori.SetRelationOrient( bRightFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA ); + } + aHtmlSet.Put( aHori ); + aVert.SetVertOrient( text::VertOrientation::TOP ); + aVert.SetRelationOrient( text::RelOrientation::PRINT_AREA ); + aHtmlSet.Put( aVert ); + + GetDoc()->SetAttr( aHtmlSet, *pFlyFrame->GetFormat() ); + } + } + else + { + if (rSdrObj.GetName().isEmpty()) + { + bool bRestore = GetDoc()->GetIDocumentUndoRedo().DoesDrawUndo(); + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); + rSdrObj.SetName(GetUniqueShapeName()); + GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(bRestore); + } + + Point aRelNullPt; + if( OBJ_CAPTION == nIdent ) + aRelNullPt = static_cast<SdrCaptionObj&>(rSdrObj).GetTailPos(); + else + aRelNullPt = rBound.TopLeft(); + + aSet.Put( aAnch ); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH ) ); + // OD 2004-03-30 #i26791# - set horizontal position + SwFormatHoriOrient aHori( nXOffset, text::HoriOrientation::NONE, text::RelOrientation::FRAME ); + aSet.Put( aHori ); + // OD 2004-03-30 #i26791# - set vertical position + if( pAnch->IsTextFrame() && static_cast<const SwTextFrame*>(pAnch)->IsFollow() ) + { + const SwTextFrame* pTmp = static_cast<const SwTextFrame*>(pAnch); + do { + pTmp = pTmp->FindMaster(); + assert(pTmp && "Where's my Master?"); + nYOffset += pTmp->IsVertical() ? + pTmp->getFramePrintArea().Width() : pTmp->getFramePrintArea().Height(); + } while ( pTmp->IsFollow() ); + } + SwFormatVertOrient aVert( nYOffset, text::VertOrientation::NONE, text::RelOrientation::FRAME ); + aSet.Put( aVert ); + SwDrawFrameFormat* pFormat = static_cast<SwDrawFrameFormat*>(getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::DRAW_OBJECT, &aSet )); + // #i36010# - set layout direction of the position + pFormat->SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + // #i44344#, #i44681# - positioning attributes already set + pFormat->PosAttrSet(); + pFormat->SetName(rSdrObj.GetName()); + + SwDrawContact *pContact = new SwDrawContact( pFormat, &rSdrObj ); + // #i35635# + pContact->MoveObjToVisibleLayer( &rSdrObj ); + if( bCharBound ) + { + OSL_ENSURE( aAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR, "wrong AnchorType" ); + SwTextNode *pNd = aAnch.GetContentAnchor()->nNode.GetNode().GetTextNode(); + SwFormatFlyCnt aFormat( pFormat ); + pNd->InsertItem(aFormat, + aAnch.GetContentAnchor()->nContent.GetIndex(), 0 ); + SwFormatVertOrient aVertical( pFormat->GetVertOrient() ); + aVertical.SetVertOrient( text::VertOrientation::LINE_CENTER ); + pFormat->SetFormatAttr( aVertical ); + } + if( pAnch->IsTextFrame() && static_cast<const SwTextFrame*>(pAnch)->IsFollow() ) + { + const SwTextFrame* pTmp = static_cast<const SwTextFrame*>(pAnch); + do { + pTmp = pTmp->FindMaster(); + OSL_ENSURE( pTmp, "Where's my Master?" ); + } while( pTmp->IsFollow() ); + pAnch = pTmp; + } + + pContact->ConnectToLayout(); + + // mark object at frame the object is inserted at. + { + SdrObject* pMarkObj = pContact->GetDrawObjectByAnchorFrame( *pAnch ); + if ( pMarkObj ) + { + Imp()->GetDrawView()->MarkObj( pMarkObj, Imp()->GetPageView() ); + } + else + { + Imp()->GetDrawView()->MarkObj( &rSdrObj, Imp()->GetPageView() ); + } + } + } + + GetDoc()->getIDocumentState().SetModified(); + + KillPams(); + EndAllActionAndCall(); + UnlockPaint(); + return true; +} + +void SwFEShell::BreakCreate() +{ + OSL_ENSURE( Imp()->HasDrawView(), "BreakCreate without DrawView?" ); + Imp()->GetDrawView()->BrkCreateObj(); + ::FrameNotify( this, FLY_DRAG_END ); +} + +bool SwFEShell::IsDrawCreate() const +{ + return Imp()->HasDrawView() && Imp()->GetDrawView()->IsCreateObj(); +} + +bool SwFEShell::BeginMark( const Point &rPos ) +{ + if ( !Imp()->HasDrawView() ) + Imp()->MakeDrawView(); + + if ( GetPageNumber( rPos ) ) + { + SwDrawView* pDView = Imp()->GetDrawView(); + + if (pDView->HasMarkablePoints()) + return pDView->BegMarkPoints( rPos ); + else + { + pDView->BegMarkObj( rPos ); + return true; + } + } + else + return false; +} + +void SwFEShell::MoveMark( const Point &rPos ) +{ + OSL_ENSURE( Imp()->HasDrawView(), "MoveMark without DrawView?" ); + + if ( GetPageNumber( rPos ) ) + { + ScrollTo( rPos ); + SwDrawView* pDView = Imp()->GetDrawView(); + + if (pDView->IsInsObjPoint()) + pDView->MovInsObjPoint( rPos ); + else if (pDView->IsMarkPoints()) + pDView->MovMarkPoints( rPos ); + else + pDView->MovAction( rPos ); + } +} + +bool SwFEShell::EndMark() +{ + bool bRet = false; + OSL_ENSURE( Imp()->HasDrawView(), "EndMark without DrawView?" ); + + if (Imp()->GetDrawView()->IsMarkObj()) + { + bRet = Imp()->GetDrawView()->EndMarkObj(); + + if ( bRet ) + { + bool bShowHdl = false; + SwDrawView* pDView = Imp()->GetDrawView(); + // frames are not selected this way, except when + // it is only one frame + SdrMarkList &rMrkList = const_cast<SdrMarkList&>(pDView->GetMarkedObjectList()); + SwFlyFrame* pOldSelFly = ::GetFlyFromMarked( &rMrkList, this ); + + if ( rMrkList.GetMarkCount() > 1 ) + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) != nullptr ) + { + if ( !bShowHdl ) + { + bShowHdl = true; + } + rMrkList.DeleteMark( i ); + --i; // no exceptions + } + } + + if( bShowHdl ) + { + pDView->MarkListHasChanged(); + pDView->AdjustMarkHdl(); + } + + if ( rMrkList.GetMarkCount() ) + ::lcl_GrabCursor(this, pOldSelFly); + else + bRet = false; + } + if ( bRet ) + ::FrameNotify( this, FLY_DRAG_START ); + } + else + { + if (Imp()->GetDrawView()->IsMarkPoints()) + bRet = Imp()->GetDrawView()->EndMarkPoints(); + } + + SetChainMarker(); + return bRet; +} + +RndStdIds SwFEShell::GetAnchorId() const +{ + RndStdIds nRet = RndStdIds(SHRT_MAX); + if ( Imp()->HasDrawView() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) != nullptr ) + { + nRet = RndStdIds::UNKNOWN; + break; + } + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + RndStdIds nId = pContact->GetFormat()->GetAnchor().GetAnchorId(); + if ( nRet == RndStdIds(SHRT_MAX) ) + nRet = nId; + else if ( nRet != nId ) + { + nRet = RndStdIds::UNKNOWN; + break; + } + } + } + if ( nRet == RndStdIds(SHRT_MAX) ) + nRet = RndStdIds::UNKNOWN; + return nRet; +} + +void SwFEShell::ChgAnchor( RndStdIds eAnchorId, bool bSameOnly, bool bPosCorr ) +{ + OSL_ENSURE( Imp()->HasDrawView(), "ChgAnchor without DrawView?" ); + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if( rMrkList.GetMarkCount() && + !rMrkList.GetMark( 0 )->GetMarkedSdrObj()->getParentSdrObjectFromSdrObject() ) + { + StartAllAction(); + + if( GetDoc()->ChgAnchor( rMrkList, eAnchorId, bSameOnly, bPosCorr )) + Imp()->GetDrawView()->UnmarkAll(); + + EndAllAction(); + + ::FrameNotify( this ); + } +} + +void SwFEShell::DelSelectedObj() +{ + OSL_ENSURE( Imp()->HasDrawView(), "DelSelectedObj(), no DrawView available" ); + if ( Imp()->HasDrawView() ) + { + StartAllAction(); + Imp()->GetDrawView()->DeleteMarked(); + EndAllAction(); + ::FrameNotify( this, FLY_DRAG_END ); + } +} + +// For the statusline to request the current conditions +Size SwFEShell::GetObjSize() const +{ + tools::Rectangle aRect; + if ( Imp()->HasDrawView() ) + { + if ( Imp()->GetDrawView()->IsAction() ) + Imp()->GetDrawView()->TakeActionRect( aRect ); + else + aRect = Imp()->GetDrawView()->GetAllMarkedRect(); + } + return aRect.GetSize(); +} + +Point SwFEShell::GetAnchorObjDiff() const +{ + const SdrView *pView = Imp()->GetDrawView(); + OSL_ENSURE( pView, "GetAnchorObjDiff without DrawView?" ); + + tools::Rectangle aRect; + if ( Imp()->GetDrawView()->IsAction() ) + Imp()->GetDrawView()->TakeActionRect( aRect ); + else + aRect = Imp()->GetDrawView()->GetAllMarkedRect(); + + Point aRet( aRect.TopLeft() ); + + if ( IsFrameSelected() ) + { + SwFlyFrame *pFly = GetSelectedFlyFrame(); + aRet -= pFly->GetAnchorFrame()->getFrameArea().Pos(); + } + else + { + const SdrObject *pObj = pView->GetMarkedObjectList().GetMarkCount() == 1 ? + pView->GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj() : nullptr; + if ( pObj ) + aRet -= pObj->GetAnchorPos(); + } + + return aRet; +} + +Point SwFEShell::GetObjAbsPos() const +{ + OSL_ENSURE( Imp()->GetDrawView(), "GetObjAbsPos() without DrawView?" ); + return Imp()->GetDrawView()->GetDragStat().GetActionRect().TopLeft(); +} + +bool SwFEShell::IsGroupSelected() +{ + if ( IsObjSelected() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + // consider 'virtual' drawing objects. + // Thus, use corresponding method instead of checking type. + if ( pObj->IsGroupObject() && + // --> #i38505# No ungroup allowed for 3d objects + !pObj->Is3DObj() && + RndStdIds::FLY_AS_CHAR != static_cast<SwDrawContact*>(GetUserCall(pObj))-> + GetFormat()->GetAnchor().GetAnchorId() ) + { + return true; + } + } + } + return false; +} + +namespace +{ + bool HasSuitableGroupingAnchor(const SdrObject* pObj) + { + bool bSuitable = true; + SwFrameFormat* pFrameFormat(::FindFrameFormat(const_cast<SdrObject*>(pObj))); + if (!pFrameFormat) + { + OSL_FAIL( "<HasSuitableGroupingAnchor> - missing frame format" ); + bSuitable = false; + } + else if (RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId()) + { + bSuitable = false; + } + return bSuitable; + } +} + +// Change return type. +// Adjustments for drawing objects in header/footer: +// allow group, only if all selected objects are in the same header/footer +// or not in header/footer. +bool SwFEShell::IsGroupAllowed() const +{ + bool bIsGroupAllowed = false; + if ( IsObjSelected() > 1 ) + { + bIsGroupAllowed = true; + const SdrObject* pUpGroup = nullptr; + const SwFrame* pHeaderFooterFrame = nullptr; + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; bIsGroupAllowed && i < rMrkList.GetMarkCount(); ++i ) + { + const SdrObject* pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( i ) + bIsGroupAllowed = pObj->getParentSdrObjectFromSdrObject() == pUpGroup; + else + pUpGroup = pObj->getParentSdrObjectFromSdrObject(); + + if ( bIsGroupAllowed ) + bIsGroupAllowed = HasSuitableGroupingAnchor(pObj); + + // check, if all selected objects are in the + // same header/footer or not in header/footer. + if ( bIsGroupAllowed ) + { + const SwFrame* pAnchorFrame = nullptr; + if ( auto pVirtFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>( pObj) ) + { + const SwFlyFrame* pFlyFrame = pVirtFlyDrawObj->GetFlyFrame(); + if ( pFlyFrame ) + { + pAnchorFrame = pFlyFrame->GetAnchorFrame(); + } + } + else + { + SwDrawContact* pDrawContact = static_cast<SwDrawContact*>(GetUserCall( pObj )); + if ( pDrawContact ) + { + pAnchorFrame = pDrawContact->GetAnchorFrame( pObj ); + } + } + if ( pAnchorFrame ) + { + if ( i ) + { + bIsGroupAllowed = + ( pAnchorFrame->FindFooterOrHeader() == pHeaderFooterFrame ); + } + else + { + pHeaderFooterFrame = pAnchorFrame->FindFooterOrHeader(); + } + } + } + } + } + + return bIsGroupAllowed; +} + +bool SwFEShell::IsUnGroupAllowed() const +{ + bool bIsUnGroupAllowed = false; + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for (size_t i = 0; i < rMrkList.GetMarkCount(); ++i) + { + const SdrObject* pObj = rMrkList.GetMark(i)->GetMarkedSdrObj(); + bIsUnGroupAllowed = HasSuitableGroupingAnchor(pObj); + if (!bIsUnGroupAllowed) + break; + } + + return bIsUnGroupAllowed; +} + +// The group gets the anchor and the contactobject of the first in the selection +void SwFEShell::GroupSelection() +{ + if ( IsGroupAllowed() ) + { + StartAllAction(); + StartUndo( SwUndoId::START ); + + GetDoc()->GroupSelection( *Imp()->GetDrawView() ); + + EndUndo( SwUndoId::END ); + EndAllAction(); + } +} + +// The individual objects get a copy of the anchor and the contactobject of the group +void SwFEShell::UnGroupSelection() +{ + if ( IsGroupSelected() ) + { + StartAllAction(); + StartUndo( SwUndoId::START ); + + GetDoc()->UnGroupSelection( *Imp()->GetDrawView() ); + + EndUndo( SwUndoId::END ); + EndAllAction(); + } +} + +void SwFEShell::MirrorSelection( bool bHorizontal ) +{ + SdrView *pView = Imp()->GetDrawView(); + if ( IsObjSelected() && pView->IsMirrorAllowed() ) + { + if ( bHorizontal ) + pView->MirrorAllMarkedHorizontal(); + else + pView->MirrorAllMarkedVertical(); + } +} + +// jump to named frame (Graphic/OLE) + +bool SwFEShell::GotoFly( const OUString& rName, FlyCntType eType, bool bSelFrame ) +{ + bool bRet = false; + static SwNodeType const aChkArr[ 4 ] = { + /* FLYCNTTYPE_ALL */ SwNodeType::NONE, + /* FLYCNTTYPE_FRM */ SwNodeType::Text, + /* FLYCNTTYPE_GRF */ SwNodeType::Grf, + /* FLYCNTTYPE_OLE */ SwNodeType::Ole + }; + + const SwFlyFrameFormat* pFlyFormat = mxDoc->FindFlyByName( rName, aChkArr[ eType]); + if( pFlyFormat ) + { + SET_CURR_SHELL( this ); + + SwFlyFrame* pFrame = SwIterator<SwFlyFrame,SwFormat>( *pFlyFormat ).First(); + if( pFrame ) + { + if( bSelFrame ) + { + // first make visible, to get a11y events in proper order + if (!ActionPend()) + MakeVisible( pFrame->getFrameArea() ); + SelectObj( pFrame->getFrameArea().Pos(), 0, pFrame->GetVirtDrawObj() ); + } + else + { + SwContentFrame *pCFrame = pFrame->ContainsContent(); + if ( pCFrame ) + { + ClearMark(); + SwPaM* pCursor = GetCursor(); + + if (pCFrame->IsTextFrame()) + { + *pCursor->GetPoint() = static_cast<SwTextFrame *>(pCFrame) + ->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + assert(pCFrame->IsNoTextFrame()); + SwContentNode *const pCNode = static_cast<SwNoTextFrame *>(pCFrame)->GetNode(); + + pCursor->GetPoint()->nNode = *pCNode; + pCursor->GetPoint()->nContent.Assign( pCNode, 0 ); + } + + SwRect& rChrRect = const_cast<SwRect&>(GetCharRect()); + rChrRect = pFrame->getFramePrintArea(); + rChrRect.Pos() += pFrame->getFrameArea().Pos(); + GetCursorDocPos() = rChrRect.Pos(); + } + } + bRet = true; + } + } + return bRet; +} + +size_t SwFEShell::GetFlyCount( FlyCntType eType, bool bIgnoreTextBoxes ) const +{ + return GetDoc()->GetFlyCount(eType, bIgnoreTextBoxes); +} + +const SwFrameFormat* SwFEShell::GetFlyNum(size_t nIdx, FlyCntType eType, bool bIgnoreTextBoxes ) const +{ + return GetDoc()->GetFlyNum(nIdx, eType, bIgnoreTextBoxes); +} + +std::vector<SwFrameFormat const*> SwFEShell::GetFlyFrameFormats( + FlyCntType const eType, bool const bIgnoreTextBoxes) +{ + return GetDoc()->GetFlyFrameFormats(eType, bIgnoreTextBoxes); +} + +// show the current selected object +void SwFEShell::MakeSelVisible() +{ + if ( Imp()->HasDrawView() && + Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) + { + GetCurrFrame(); // just to trigger formatting in case the selected object is not formatted. + MakeVisible( Imp()->GetDrawView()->GetAllMarkedRect() ); + } + else + SwCursorShell::MakeSelVisible(); +} + +// how is the selected object protected? +FlyProtectFlags SwFEShell::IsSelObjProtected( FlyProtectFlags eType ) const +{ + FlyProtectFlags nChk = FlyProtectFlags::NONE; + const bool bParent(eType & FlyProtectFlags::Parent); + if( Imp()->HasDrawView() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for( size_t i = rMrkList.GetMarkCount(); i; ) + { + SdrObject *pObj = rMrkList.GetMark( --i )->GetMarkedSdrObj(); + if( !bParent ) + { + nChk |= ( pObj->IsMoveProtect() ? FlyProtectFlags::Pos : FlyProtectFlags::NONE ) | + ( pObj->IsResizeProtect()? FlyProtectFlags::Size : FlyProtectFlags::NONE ); + + if (SwVirtFlyDrawObj* pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pObj)) + { + SwFlyFrame *pFly = pVirtO->GetFlyFrame(); + if ( (FlyProtectFlags::Content & eType) && pFly->GetFormat()->GetProtect().IsContentProtected() ) + nChk |= FlyProtectFlags::Content; + + if ( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + SwOLENode *pNd = static_cast<SwNoTextFrame*>(pFly->Lower())->GetNode()->GetOLENode(); + uno::Reference < embed::XEmbeddedObject > xObj( pNd ? pNd->GetOLEObj().GetOleRef() : nullptr ); + if ( xObj.is() ) + { + // TODO/LATER: use correct aspect + const bool bNeverResize = (embed::EmbedMisc::EMBED_NEVERRESIZE & xObj->getStatus( embed::Aspects::MSOLE_CONTENT )); + if ( ( (FlyProtectFlags::Content & eType) || (FlyProtectFlags::Size & eType) ) && bNeverResize ) + { + nChk |= FlyProtectFlags::Size; + nChk |= FlyProtectFlags::Fixed; + } + + // set FlyProtectFlags::Pos if it is a Math object anchored 'as char' and baseline alignment is activated + const bool bProtectMathPos = SotExchange::IsMath( xObj->getClassID() ) + && RndStdIds::FLY_AS_CHAR == pFly->GetFormat()->GetAnchor().GetAnchorId() + && mxDoc->GetDocumentSettingManager().get( DocumentSettingId::MATH_BASELINE_ALIGNMENT ); + if ((FlyProtectFlags::Pos & eType) && bProtectMathPos) + nChk |= FlyProtectFlags::Pos; + } + } + } + nChk &= eType; + if( nChk == eType ) + return eType; + } + const SwFrame* pAnch; + if (SwVirtFlyDrawObj* pVirtO = dynamic_cast<SwVirtFlyDrawObj*>(pObj)) + pAnch = pVirtO->GetFlyFrame()->GetAnchorFrame(); + else + { + SwDrawContact* pTmp = static_cast<SwDrawContact*>(GetUserCall(pObj)); + pAnch = pTmp ? pTmp->GetAnchorFrame( pObj ) : nullptr; + } + if( pAnch && pAnch->IsProtected() ) + return eType; + } + } + return nChk; +} + +bool SwFEShell::GetObjAttr( SfxItemSet &rSet ) const +{ + if ( !IsObjSelected() ) + return false; + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + // --> make code robust + OSL_ENSURE( pContact, "<SwFEShell::GetObjAttr(..)> - missing <pContact>." ); + if ( pContact ) + { + if ( i ) + rSet.MergeValues( pContact->GetFormat()->GetAttrSet() ); + else + rSet.Put( pContact->GetFormat()->GetAttrSet() ); + } + } + return true; +} + +void SwFEShell::SetObjAttr( const SfxItemSet& rSet ) +{ + SET_CURR_SHELL( this ); + + if ( !rSet.Count() ) + { + OSL_ENSURE( false, "SetObjAttr, empty set." ); + return; + } + + StartAllAction(); + StartUndo( SwUndoId::INSATTR ); + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + GetDoc()->SetAttr( rSet, *pContact->GetFormat() ); + } + + EndUndo( SwUndoId::INSATTR ); + EndAllActionAndCall(); + GetDoc()->getIDocumentState().SetModified(); +} + +bool SwFEShell::IsAlignPossible() const +{ + return Imp()->GetDrawView()->IsAlignPossible(); +} + +void SwFEShell::CheckUnboundObjects() +{ + SET_CURR_SHELL( this ); + + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( !GetUserCall(pObj) ) + { + const tools::Rectangle &rBound = pObj->GetSnapRect(); + const Point aPt( rBound.TopLeft() ); + const SwFrame *pPage = GetLayout()->Lower(); + const SwFrame *pLast = pPage; + while ( pPage && !pPage->getFrameArea().IsInside( aPt ) ) + { + if ( aPt.Y() > pPage->getFrameArea().Bottom() ) + pLast = pPage; + pPage = pPage->GetNext(); + } + if ( !pPage ) + pPage = pLast; + OSL_ENSURE( pPage, "Page not found." ); + + // Alien identifier should roll into the default, + // Duplications are possible!! + sal_uInt16 nIdent = + Imp()->GetDrawView()->GetCurrentObjInventor() == SdrInventor::Default ? + Imp()->GetDrawView()->GetCurrentObjIdentifier() : 0xFFFF; + + SwFormatAnchor aAnch; + { + const SwContentFrame *const pAnch = ::FindAnchor(pPage, aPt, true); + SwPosition aPos( pAnch->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pAnch)->GetTextNodeForParaProps() + : *static_cast<SwNoTextFrame const*>(pAnch)->GetNode() ); + aAnch.SetType( RndStdIds::FLY_AT_PARA ); + aAnch.SetAnchor( &aPos ); + const_cast<SwRect&>(GetCharRect()).Pos() = aPt; + } + + // First the action here, to assure GetCharRect delivers current values. + StartAllAction(); + + SfxItemSet aSet( GetAttrPool(), svl::Items<RES_FRM_SIZE, RES_FRM_SIZE, + RES_SURROUND, RES_ANCHOR>{} ); + aSet.Put( aAnch ); + + Point aRelNullPt; + + if( OBJ_CAPTION == nIdent ) + aRelNullPt = static_cast<SdrCaptionObj*>(pObj)->GetTailPos(); + else + aRelNullPt = rBound.TopLeft(); + + aSet.Put( aAnch ); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH ) ); + SwFrameFormat* pFormat = getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::DRAW_OBJECT, &aSet ); + + SwDrawContact *pContact = new SwDrawContact( + static_cast<SwDrawFrameFormat*>(pFormat), pObj ); + + // #i35635# + pContact->MoveObjToVisibleLayer( pObj ); + pContact->ConnectToLayout(); + + EndAllAction(); + } + } +} + +void SwFEShell::SetCalcFieldValueHdl(Outliner* pOutliner) +{ + GetDoc()->SetCalcFieldValueHdl(pOutliner); +} + +SwChainRet SwFEShell::Chainable( SwRect &rRect, const SwFrameFormat &rSource, + const Point &rPt ) const +{ + rRect.Clear(); + + // The source is not allowed to have a follow. + const SwFormatChain &rChain = rSource.GetChain(); + if ( rChain.GetNext() ) + return SwChainRet::SOURCE_CHAINED; + + SwChainRet nRet = SwChainRet::NOT_FOUND; + if( Imp()->HasDrawView() ) + { + SdrPageView* pPView; + SwDrawView *pDView = const_cast<SwDrawView*>(Imp()->GetDrawView()); + const auto nOld = pDView->GetHitTolerancePixel(); + pDView->SetHitTolerancePixel( 0 ); + SdrObject* pObj = pDView->PickObj(rPt, pDView->getHitTolLog(), pPView, SdrSearchOptions::PICKMARKABLE); + SwVirtFlyDrawObj* pDrawObj = dynamic_cast<SwVirtFlyDrawObj*>(pObj); + if (pDrawObj) + { + SwFlyFrame *pFly = pDrawObj->GetFlyFrame(); + rRect = pFly->getFrameArea(); + + // Target and source should not be equal and the list + // should not be cyclic + SwFrameFormat *pFormat = pFly->GetFormat(); + nRet = GetDoc()->Chainable(rSource, *pFormat); + } + pDView->SetHitTolerancePixel( nOld ); + } + return nRet; +} + +void SwFEShell::Chain( SwFrameFormat &rSource, const SwFrameFormat &rDest ) +{ + GetDoc()->Chain(rSource, rDest); +} + +SwChainRet SwFEShell::Chain( SwFrameFormat &rSource, const Point &rPt ) +{ + SwRect aDummy; + SwChainRet nErr = Chainable( aDummy, rSource, rPt ); + if ( nErr == SwChainRet::OK ) + { + StartAllAction(); + SdrPageView* pPView; + SwDrawView *pDView = Imp()->GetDrawView(); + const auto nOld = pDView->GetHitTolerancePixel(); + pDView->SetHitTolerancePixel( 0 ); + SdrObject* pObj = pDView->PickObj(rPt, pDView->getHitTolLog(), pPView, SdrSearchOptions::PICKMARKABLE); + pDView->SetHitTolerancePixel( nOld ); + SwFlyFrame *pFly = static_cast<SwVirtFlyDrawObj*>(pObj)->GetFlyFrame(); + + SwFlyFrameFormat *pFormat = pFly->GetFormat(); + GetDoc()->Chain(rSource, *pFormat); + EndAllAction(); + SetChainMarker(); + } + return nErr; +} + +void SwFEShell::Unchain( SwFrameFormat &rFormat ) +{ + StartAllAction(); + GetDoc()->Unchain(rFormat); + EndAllAction(); +} + +void SwFEShell::HideChainMarker() +{ + m_pChainFrom.reset(); + m_pChainTo.reset(); +} + +void SwFEShell::SetChainMarker() +{ + bool bDelFrom = true, + bDelTo = true; + if ( IsFrameSelected() ) + { + SwFlyFrame *pFly = GetSelectedFlyFrame(); + + if ( pFly->GetPrevLink() ) + { + bDelFrom = false; + const SwFrame *pPre = pFly->GetPrevLink(); + + Point aStart( pPre->getFrameArea().Right(), pPre->getFrameArea().Bottom()); + Point aEnd(pFly->getFrameArea().Pos()); + + if (!m_pChainFrom) + { + m_pChainFrom.reset( + new SdrDropMarkerOverlay( *GetDrawView(), aStart, aEnd )); + } + } + if ( pFly->GetNextLink() ) + { + bDelTo = false; + const SwFlyFrame *pNxt = pFly->GetNextLink(); + + Point aStart( pFly->getFrameArea().Right(), pFly->getFrameArea().Bottom()); + Point aEnd(pNxt->getFrameArea().Pos()); + + if (!m_pChainTo) + { + m_pChainTo.reset( + new SdrDropMarkerOverlay( *GetDrawView(), aStart, aEnd )); + } + } + } + + if ( bDelFrom ) + { + m_pChainFrom.reset(); + } + + if ( bDelTo ) + { + m_pChainTo.reset(); + } +} + +long SwFEShell::GetSectionWidth( SwFormat const & rFormat ) const +{ + SwFrame *pFrame = GetCurrFrame(); + // Is the cursor at this moment in a SectionFrame? + if( pFrame && pFrame->IsInSct() ) + { + SwSectionFrame* pSect = pFrame->FindSctFrame(); + do + { + // Is it the right one? + if( pSect->KnowsFormat( rFormat ) ) + return pSect->getFrameArea().Width(); + // for nested areas + pSect = pSect->GetUpper()->FindSctFrame(); + } + while( pSect ); + } + SwIterator<SwSectionFrame,SwFormat> aIter( rFormat ); + for ( SwSectionFrame* pSct = aIter.First(); pSct; pSct = aIter.Next() ) + { + if( !pSct->IsFollow() ) + { + return pSct->getFrameArea().Width(); + } + } + return 0; +} + + void SwFEShell::CreateDefaultShape( sal_uInt16 /*SdrObjKind ?*/ eSdrObjectKind, const tools::Rectangle& rRect, + sal_uInt16 nSlotId) +{ + SdrView* pDrawView = GetDrawView(); + SdrModel* pDrawModel = pDrawView->GetModel(); + SdrObject* pObj = SdrObjFactory::MakeNewObject( + *pDrawModel, + SdrInventor::Default, + eSdrObjectKind); + + if(pObj) + { + tools::Rectangle aRect(rRect); + if(OBJ_CARC == eSdrObjectKind || OBJ_CCUT == eSdrObjectKind) + { + // force quadratic + if(aRect.GetWidth() > aRect.GetHeight()) + { + aRect = tools::Rectangle( + Point(aRect.Left() + ((aRect.GetWidth() - aRect.GetHeight()) / 2), aRect.Top()), + Size(aRect.GetHeight(), aRect.GetHeight())); + } + else + { + aRect = tools::Rectangle( + Point(aRect.Left(), aRect.Top() + ((aRect.GetHeight() - aRect.GetWidth()) / 2)), + Size(aRect.GetWidth(), aRect.GetWidth())); + } + } + pObj->SetLogicRect(aRect); + + Point aStart = aRect.TopLeft(); + Point aEnd = aRect.BottomRight(); + + if(dynamic_cast<const SdrCircObj*>( pObj) != nullptr) + { + SfxItemSet aAttr(pDrawModel->GetItemPool()); + aAttr.Put(makeSdrCircStartAngleItem(9000)); + aAttr.Put(makeSdrCircEndAngleItem(0)); + pObj->SetMergedItemSet(aAttr); + } + else if(dynamic_cast<const SdrPathObj*>( pObj) != nullptr) + { + basegfx::B2DPolyPolygon aPoly; + + switch(eSdrObjectKind) + { + case OBJ_PATHLINE: + case OBJ_PATHFILL: + { + basegfx::B2DPolygon aInnerPoly; + + aInnerPoly.append(basegfx::B2DPoint(aRect.Left(), aRect.Bottom())); + + const basegfx::B2DPoint aCenterBottom(aRect.Center().getX(), aRect.Bottom()); + aInnerPoly.appendBezierSegment( + aCenterBottom, + aCenterBottom, + basegfx::B2DPoint(aRect.Center().getX(), aRect.Center().getY())); + + const basegfx::B2DPoint aCenterTop(aRect.Center().getX(), aRect.Top()); + aInnerPoly.appendBezierSegment( + aCenterTop, + aCenterTop, + basegfx::B2DPoint(aRect.Right(), aRect.Top())); + + aInnerPoly.setClosed(true); + aPoly.append(aInnerPoly); + } + break; + case OBJ_FREELINE: + case OBJ_FREEFILL: + { + basegfx::B2DPolygon aInnerPoly; + + aInnerPoly.append(basegfx::B2DPoint(aRect.Left(), aRect.Bottom())); + + aInnerPoly.appendBezierSegment( + basegfx::B2DPoint(aRect.Left(), aRect.Top()), + basegfx::B2DPoint(aRect.Center().getX(), aRect.Top()), + basegfx::B2DPoint(aRect.Center().getX(), aRect.Center().getY())); + + aInnerPoly.appendBezierSegment( + basegfx::B2DPoint(aRect.Center().getX(), aRect.Bottom()), + basegfx::B2DPoint(aRect.Right(), aRect.Bottom()), + basegfx::B2DPoint(aRect.Right(), aRect.Top())); + + aInnerPoly.append(basegfx::B2DPoint(aRect.Right(), aRect.Bottom())); + aInnerPoly.setClosed(true); + aPoly.append(aInnerPoly); + } + break; + case OBJ_POLY: + case OBJ_PLIN: + { + basegfx::B2DPolygon aInnerPoly; + sal_Int32 nWdt(aRect.GetWidth()); + sal_Int32 nHgt(aRect.GetHeight()); + + aInnerPoly.append(basegfx::B2DPoint(aRect.Left(), aRect.Bottom())); + aInnerPoly.append(basegfx::B2DPoint(aRect.Left() + (nWdt * 30) / 100, aRect.Top() + (nHgt * 70) / 100)); + aInnerPoly.append(basegfx::B2DPoint(aRect.Left(), aRect.Top() + (nHgt * 15) / 100)); + aInnerPoly.append(basegfx::B2DPoint(aRect.Left() + (nWdt * 65) / 100, aRect.Top())); + aInnerPoly.append(basegfx::B2DPoint(aRect.Left() + nWdt, aRect.Top() + (nHgt * 30) / 100)); + aInnerPoly.append(basegfx::B2DPoint(aRect.Left() + (nWdt * 80) / 100, aRect.Top() + (nHgt * 50) / 100)); + aInnerPoly.append(basegfx::B2DPoint(aRect.Left() + (nWdt * 80) / 100, aRect.Top() + (nHgt * 75) / 100)); + aInnerPoly.append(basegfx::B2DPoint(aRect.Bottom(), aRect.Right())); + + if(OBJ_PLIN == eSdrObjectKind) + { + aInnerPoly.append(basegfx::B2DPoint(aRect.Center().getX(), aRect.Bottom())); + } + else + { + aInnerPoly.setClosed(true); + } + + aPoly.append(aInnerPoly); + } + break; + case OBJ_LINE : + { + sal_Int32 nYMiddle((aRect.Top() + aRect.Bottom()) / 2); + basegfx::B2DPolygon aTempPoly; + aTempPoly.append(basegfx::B2DPoint(aRect.TopLeft().getX(), nYMiddle)); + aTempPoly.append(basegfx::B2DPoint(aRect.BottomRight().getX(), nYMiddle)); + aPoly.append(aTempPoly); + + SfxItemSet aAttr(pObj->getSdrModelFromSdrObject().GetItemPool()); + SetLineEnds(aAttr, *pObj, nSlotId); + pObj->SetMergedItemSet(aAttr); + } + break; + } + + static_cast<SdrPathObj*>(pObj)->SetPathPoly(aPoly); + } + else if(dynamic_cast<const SdrMeasureObj*>( pObj) != nullptr) + { + sal_Int32 nYMiddle((aRect.Top() + aRect.Bottom()) / 2); + static_cast<SdrMeasureObj*>(pObj)->SetPoint(Point(aStart.X(), nYMiddle), 0); + static_cast<SdrMeasureObj*>(pObj)->SetPoint(Point(aEnd.X(), nYMiddle), 1); + + SfxItemSet aAttr(pObj->getSdrModelFromSdrObject().GetItemPool()); + SetLineEnds(aAttr, *pObj, nSlotId); + pObj->SetMergedItemSet(aAttr); + } + else if(dynamic_cast<const SdrCaptionObj*>( pObj) != nullptr) + { + bool bVerticalText = ( SID_DRAW_TEXT_VERTICAL == nSlotId || + SID_DRAW_CAPTION_VERTICAL == nSlotId ); + static_cast<SdrTextObj*>(pObj)->SetVerticalWriting(bVerticalText); + if(bVerticalText) + { + SfxItemSet aSet(pObj->GetMergedItemSet()); + aSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_CENTER)); + aSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT)); + pObj->SetMergedItemSet(aSet); + } + + static_cast<SdrCaptionObj*>(pObj)->SetLogicRect(aRect); + static_cast<SdrCaptionObj*>(pObj)->SetTailPos( + aRect.TopLeft() - Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2)); + } + else if(dynamic_cast<const SdrTextObj*>( pObj) != nullptr) + { + SdrTextObj* pText = static_cast<SdrTextObj*>(pObj); + pText->SetLogicRect(aRect); + + bool bVertical = (SID_DRAW_TEXT_VERTICAL == nSlotId); + bool bMarquee = (SID_DRAW_TEXT_MARQUEE == nSlotId); + + pText->SetVerticalWriting(bVertical); + + if(bVertical) + { + SfxItemSet aSet(pDrawModel->GetItemPool()); + aSet.Put(makeSdrTextAutoGrowWidthItem(true)); + aSet.Put(makeSdrTextAutoGrowHeightItem(false)); + aSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_TOP)); + aSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT)); + pText->SetMergedItemSet(aSet); + } + + if(bMarquee) + { + SfxItemSet aSet(pDrawModel->GetItemPool(), svl::Items<SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST>{}); + aSet.Put( makeSdrTextAutoGrowWidthItem( false ) ); + aSet.Put( makeSdrTextAutoGrowHeightItem( false ) ); + aSet.Put( SdrTextAniKindItem( SdrTextAniKind::Slide ) ); + aSet.Put( SdrTextAniDirectionItem( SdrTextAniDirection::Left ) ); + aSet.Put( SdrTextAniCountItem( 1 ) ); + aSet.Put( SdrTextAniAmountItem( static_cast<sal_Int16>(GetWin()->PixelToLogic(Size(2,1)).Width())) ); + pObj->SetMergedItemSetAndBroadcast(aSet); + } + } + SdrPageView* pPageView = pDrawView->GetSdrPageView(); + SdrCreateView::SetupObjLayer(pPageView, pDrawView->GetActiveLayer(), pObj); + // switch undo off or this combined with ImpEndCreate will cause two undos + // see comment made in SwFEShell::EndCreate (we create our own undo-object!) + const bool bUndo(GetDoc()->GetIDocumentUndoRedo().DoesUndo()); + GetDoc()->GetIDocumentUndoRedo().DoUndo(false); + pDrawView->InsertObjectAtView(pObj, *pPageView); + GetDoc()->GetIDocumentUndoRedo().DoUndo(bUndo); + } + ImpEndCreate(); +} + +/** SwFEShell::GetShapeBackgrd + method determines background color of the page the selected drawing + object is on and returns this color. + If no color is found, because no drawing object is selected or ..., + color COL_BLACK (default color on constructing object of class Color) + is returned. + + @returns an object of class Color +*/ +Color SwFEShell::GetShapeBackgrd() const +{ + Color aRetColor; + + // check, if a draw view exists + OSL_ENSURE( Imp()->GetDrawView(), "wrong usage of SwFEShell::GetShapeBackgrd - no draw view!"); + if( Imp()->GetDrawView() ) + { + // determine list of selected objects + const SdrMarkList* pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + // check, if exactly one object is selected. + OSL_ENSURE( pMrkList->GetMarkCount() == 1, "wrong usage of SwFEShell::GetShapeBackgrd - no selected object!"); + if ( pMrkList->GetMarkCount() == 1) + { + // get selected object + const SdrObject *pSdrObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + // check, if selected object is a shape (drawing object) + OSL_ENSURE( dynamic_cast<const SwVirtFlyDrawObj*>( pSdrObj) == nullptr, "wrong usage of SwFEShell::GetShapeBackgrd - selected object is not a drawing object!"); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pSdrObj) == nullptr ) + { + // determine page frame of the frame the shape is anchored. + const SwFrame* pAnchorFrame = + static_cast<SwDrawContact*>(GetUserCall(pSdrObj))->GetAnchorFrame( pSdrObj ); + OSL_ENSURE( pAnchorFrame, "inconsistent model - no anchor at shape!"); + if ( pAnchorFrame ) + { + const SwPageFrame* pPageFrame = pAnchorFrame->FindPageFrame(); + OSL_ENSURE( pPageFrame, "inconsistent model - no page!"); + if ( pPageFrame ) + { + aRetColor = pPageFrame->GetDrawBackgrdColor(); + } + } + } + } + } + + return aRetColor; +} + +/** Is default horizontal text direction for selected drawing object right-to-left + Because drawing objects only painted for each page only, the default + horizontal text direction of a drawing object is given by the corresponding + page property. + + @returns boolean, indicating, if the horizontal text direction of the + page, the selected drawing object is on, is right-to-left. +*/ +bool SwFEShell::IsShapeDefaultHoriTextDirR2L() const +{ + bool bRet = false; + + // check, if a draw view exists + OSL_ENSURE( Imp()->GetDrawView(), "wrong usage of SwFEShell::GetShapeBackgrd - no draw view!"); + if( Imp()->GetDrawView() ) + { + // determine list of selected objects + const SdrMarkList* pMrkList = &Imp()->GetDrawView()->GetMarkedObjectList(); + // check, if exactly one object is selected. + OSL_ENSURE( pMrkList->GetMarkCount() == 1, "wrong usage of SwFEShell::GetShapeBackgrd - no selected object!"); + if ( pMrkList->GetMarkCount() == 1) + { + // get selected object + const SdrObject *pSdrObj = pMrkList->GetMark( 0 )->GetMarkedSdrObj(); + // check, if selected object is a shape (drawing object) + OSL_ENSURE( dynamic_cast<const SwVirtFlyDrawObj*>( pSdrObj) == nullptr, "wrong usage of SwFEShell::GetShapeBackgrd - selected object is not a drawing object!"); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pSdrObj) == nullptr ) + { + // determine page frame of the frame the shape is anchored. + const SwFrame* pAnchorFrame = + static_cast<SwDrawContact*>(GetUserCall(pSdrObj))->GetAnchorFrame( pSdrObj ); + OSL_ENSURE( pAnchorFrame, "inconsistent model - no anchor at shape!"); + if ( pAnchorFrame ) + { + const SwPageFrame* pPageFrame = pAnchorFrame->FindPageFrame(); + OSL_ENSURE( pPageFrame, "inconsistent model - no page!"); + if ( pPageFrame ) + { + bRet = pPageFrame->IsRightToLeft(); + } + } + } + } + } + + return bRet; +} + +Point SwFEShell::GetRelativePagePosition(const Point& rDocPos) +{ + Point aRet(-1, -1); + const SwFrame *pPage = GetLayout()->Lower(); + while ( pPage && !pPage->getFrameArea().IsInside( rDocPos ) ) + { + pPage = pPage->GetNext(); + } + if(pPage) + { + aRet = rDocPos - pPage->getFrameArea().TopLeft(); + } + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/fetab.cxx b/sw/source/core/frmedt/fetab.cxx new file mode 100644 index 000000000..242c148f8 --- /dev/null +++ b/sw/source/core/frmedt/fetab.cxx @@ -0,0 +1,2324 @@ +/* -*- 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 <memory> +#include <hintids.hxx> + +#include <vcl/errinf.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <editeng/protitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <svtools/ruler.hxx> +#include <swwait.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <frmatr.hxx> +#include <fesh.hxx> +#include <doc.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <flyfrm.hxx> +#include <swtable.hxx> +#include <swddetbl.hxx> +#include <ndtxt.hxx> +#include <calc.hxx> +#include <dialoghelp.hxx> +#include <tabcol.hxx> +#include <tblafmt.hxx> +#include <cellatr.hxx> +#include <pam.hxx> +#include <viscrs.hxx> +#include <tblsel.hxx> +#include <swerror.h> +#include <swundo.hxx> +#include <frmtool.hxx> +#include <fmtrowsplt.hxx> +#include <node.hxx> +#include <sortedobjs.hxx> + +using namespace ::com::sun::star; + +// also see swtable.cxx +#define COLFUZZY 20L + +static bool IsSame( long nA, long nB ) { return std::abs(nA-nB) <= COLFUZZY; } + +namespace { + +class TableWait +{ + const std::unique_ptr<SwWait> m_pWait; + // this seems really fishy: do some locking, if an arbitrary number of lines is exceeded + static const size_t our_kLineLimit = 20; + static bool ShouldWait(size_t nCnt, SwFrame *pFrame, size_t nCnt2) + { return our_kLineLimit < nCnt || our_kLineLimit < nCnt2 || (pFrame && our_kLineLimit < pFrame->ImplFindTabFrame()->GetTable()->GetTabLines().size()); } +public: + TableWait(size_t nCnt, SwFrame *pFrame, SwDocShell &rDocShell, size_t nCnt2 = 0) + : m_pWait( ShouldWait(nCnt, pFrame, nCnt2) ? std::make_unique<SwWait>( rDocShell, true ) : nullptr ) + { } +}; + +} + +void SwFEShell::ParkCursorInTab() +{ + SwCursor * pSwCursor = GetSwCursor(); + + OSL_ENSURE(pSwCursor, "no SwCursor"); + + SwPosition aStartPos = *pSwCursor->GetPoint(), aEndPos = aStartPos; + + /* Search least and greatest position in current cursor ring. + */ + for(SwPaM& rTmpCursor : pSwCursor->GetRingContainer()) + { + SwCursor* pTmpCursor = static_cast<SwCursor *>(&rTmpCursor); + const SwPosition * pPt = pTmpCursor->GetPoint(), + * pMk = pTmpCursor->GetMark(); + + if (*pPt < aStartPos) + aStartPos = *pPt; + + if (*pPt > aEndPos) + aEndPos = *pPt; + + if (*pMk < aStartPos) + aStartPos = *pMk; + + if (*pMk > aEndPos) + aEndPos = *pMk; + + } + + KillPams(); + + /* @@@ semantic: SwCursor::operator=() is not implemented @@@ */ + + /* Set cursor to end of selection to ensure IsLastCellInRow works + properly. */ + { + SwCursor aTmpCursor( aEndPos, nullptr ); + *pSwCursor = aTmpCursor; + } + + /* Move the cursor out of the columns to delete and stay in the + same row. If the table has only one column the cursor will + stay in the row and the shell will take care of it. */ + if (IsLastCellInRow()) + { + /* If the cursor is in the last row of the table, first + try to move it to the previous cell. If that fails move + it to the next cell. */ + + { + SwCursor aTmpCursor( aStartPos, nullptr ); + *pSwCursor = aTmpCursor; + } + + if (! pSwCursor->GoPrevCell()) + { + SwCursor aTmpCursor( aEndPos, nullptr ); + *pSwCursor = aTmpCursor; + pSwCursor->GoNextCell(); + } + } + else + { + /* If the cursor is not in the last row of the table, first + try to move it to the next cell. If that fails move it + to the previous cell. */ + + { + SwCursor aTmpCursor( aEndPos, nullptr ); + *pSwCursor = aTmpCursor; + } + + if (! pSwCursor->GoNextCell()) + { + SwCursor aTmpCursor( aStartPos, nullptr ); + *pSwCursor = aTmpCursor; + pSwCursor->GoPrevCell(); + } + } +} + +void SwFEShell::InsertRow( sal_uInt16 nCnt, bool bBehind ) +{ + // check if Point/Mark of current cursor are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + + if( dynamic_cast< const SwDDETable* >(pFrame->ImplFindTabFrame()->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return; + } + + SET_CURR_SHELL( this ); + StartAllAction(); + + // search boxes via the layout + SwSelBoxes aBoxes; + bool bSelectAll = StartsWithTable() && ExtendedSelectedAll(); + if (bSelectAll) + { + // Set the end of the selection to the last paragraph of the last cell of the table. + SwPaM* pPaM = getShellCursor(false); + SwNode* pNode = pPaM->Start()->nNode.GetNode().FindTableNode()->EndOfSectionNode(); + // pNode is the end node of the table, we want the last node before the end node of the last cell. + pPaM->End()->nNode = pNode->GetIndex() - 2; + pPaM->End()->nContent.Assign(pPaM->End()->nNode.GetNode().GetContentNode(), 0); + } + GetTableSel( *this, aBoxes, SwTableSearchType::Row ); + + TableWait aWait( nCnt, pFrame, *GetDoc()->GetDocShell(), aBoxes.size() ); + + if ( !aBoxes.empty() ) + GetDoc()->InsertRow( aBoxes, nCnt, bBehind ); + + EndAllActionAndCall(); +} + +void SwFEShell::InsertCol( sal_uInt16 nCnt, bool bBehind ) +{ + // check if Point/Mark of current cursor are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + + if( dynamic_cast< const SwDDETable* >(pFrame->ImplFindTabFrame()->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return; + } + + SET_CURR_SHELL( this ); + + if( !CheckSplitCells( *this, nCnt + 1, SwTableSearchType::Col ) ) + { + ErrorHandler::HandleError( ERR_TBLINSCOL_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return; + } + + StartAllAction(); + // search boxes via the layout + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes, SwTableSearchType::Col ); + + TableWait aWait( nCnt, pFrame, *GetDoc()->GetDocShell(), aBoxes.size() ); + + if( !aBoxes.empty() ) + GetDoc()->InsertCol( aBoxes, nCnt, bBehind ); + + EndAllActionAndCall(); +} + +// Determines if the current cursor is in the last row of the table. +bool SwFEShell::IsLastCellInRow() const +{ + SwTabCols aTabCols; + GetTabCols( aTabCols ); + bool bResult = false; + + if (IsTableRightToLeft()) + /* If the table is right-to-left the last row is the most left one. */ + bResult = 0 == GetCurTabColNum(); + else + /* If the table is left-to-right the last row is the most right one. */ + bResult = aTabCols.Count() == GetCurTabColNum(); + + return bResult; +} + +bool SwFEShell::DeleteCol() +{ + // check if Point/Mark of current cursor are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return false; + + if( dynamic_cast< const SwDDETable* >(pFrame->ImplFindTabFrame()->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return false; + } + + SET_CURR_SHELL( this ); + StartAllAction(); + + // search boxes via the layout + bool bRet; + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes, SwTableSearchType::Col ); + if ( !aBoxes.empty() ) + { + TableWait aWait( aBoxes.size(), pFrame, *GetDoc()->GetDocShell() ); + + // remove crsr from the deletion area. + // Put them behind/on the table; via the + // document position they will be put to the old position + while( !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + + ParkCursorInTab(); + + // then delete the column + StartUndo(SwUndoId::COL_DELETE); + bRet = GetDoc()->DeleteRowCol( aBoxes, true ); + EndUndo(SwUndoId::COL_DELETE); + + } + else + bRet = false; + + EndAllActionAndCall(); + return bRet; +} + +void SwFEShell::DeleteTable() +{ + DeleteRow(true); +} + +bool SwFEShell::DeleteRow(bool bCompleteTable) +{ + // check if Point/Mark of current cursor are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return false; + + if( dynamic_cast< const SwDDETable* >(pFrame->ImplFindTabFrame()->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return false; + } + + SET_CURR_SHELL( this ); + StartAllAction(); + + // search for boxes via the layout + bool bRet; + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes, SwTableSearchType::Row ); + + if( !aBoxes.empty() ) + { + TableWait aWait( aBoxes.size(), pFrame, *GetDoc()->GetDocShell() ); + + // Delete cursors from the deletion area. + // Then the cursor is: + // 1. the following row, if there is another row after this + // 2. the preceding row, if there is another row before this + // 3. otherwise below the table + { + SwTableNode* pTableNd = pFrame->IsTextFrame() + ? static_cast<SwTextFrame*>(pFrame)->GetTextNodeFirst()->FindTableNode() + : static_cast<SwNoTextFrame*>(pFrame)->GetNode()->FindTableNode(); + + // search all boxes / lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( aBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if( aFndBox.GetLines().empty() ) + { + EndAllActionAndCall(); + return false; + } + + KillPams(); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + FndBox_ *const pTmp = pFndBox->GetLines().front()->GetBoxes()[0].get(); + if( pTmp->GetBox()->GetSttNd() ) + break; // otherwise too far + pFndBox = pTmp; + } + + SwTableLine* pDelLine = pFndBox->GetLines().back()->GetLine(); + SwTableBox* pDelBox = pDelLine->GetTabBoxes().back(); + while( !pDelBox->GetSttNd() ) + { + SwTableLine* pLn = pDelBox->GetTabLines().back(); + pDelBox = pLn->GetTabBoxes().back(); + } + SwTableBox* pNextBox = pDelLine->FindNextBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindNextBox( pTableNd->GetTable(), pNextBox ); + + if( !pNextBox ) // no next? then the previous + { + pDelLine = pFndBox->GetLines().front()->GetLine(); + pDelBox = pDelLine->GetTabBoxes()[ 0 ]; + while( !pDelBox->GetSttNd() ) + pDelBox = pDelBox->GetTabLines()[0]->GetTabBoxes()[0]; + pNextBox = pDelLine->FindPreviousBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindPreviousBox( pTableNd->GetTable(), pNextBox ); + } + + sal_uLong nIdx; + if( pNextBox ) // put cursor here + nIdx = pNextBox->GetSttIdx() + 1; + else // otherwise below the table + nIdx = pTableNd->EndOfSectionIndex() + 1; + + SwNodeIndex aIdx( GetDoc()->GetNodes(), nIdx ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + + if( pCNd ) + { + SwPaM* pPam = GetCursor(); + pPam->GetPoint()->nNode = aIdx; + pPam->GetPoint()->nContent.Assign( pCNd, 0 ); + pPam->SetMark(); // both want something + pPam->DeleteMark(); + } + } + + // now delete the lines + StartUndo(bCompleteTable ? SwUndoId::UI_TABLE_DELETE : SwUndoId::ROW_DELETE); + bRet = GetDoc()->DeleteRowCol( aBoxes ); + EndUndo(bCompleteTable ? SwUndoId::UI_TABLE_DELETE : SwUndoId::ROW_DELETE); + } + else + bRet = false; + + EndAllActionAndCall(); + return bRet; +} + +TableMergeErr SwFEShell::MergeTab() +{ + // check if Point/Mark of current cursor are in a table + TableMergeErr nRet = TableMergeErr::NoSelection; + if( IsTableMode() ) + { + SwShellTableCursor* pTableCursor = GetTableCursor(); + const SwTableNode* pTableNd = pTableCursor->GetNode().FindTableNode(); + if( dynamic_cast< const SwDDETable* >(&pTableNd->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + } + else + { + SET_CURR_SHELL( this ); + StartAllAction(); + + TableWait aWait(pTableCursor->GetSelectedBoxesCount(), nullptr, + *GetDoc()->GetDocShell(), + pTableNd->GetTable().GetTabLines().size() ); + + nRet = GetDoc()->MergeTable( *pTableCursor ); + + KillPams(); + + EndAllActionAndCall(); + } + } + return nRet; +} + +void SwFEShell::SplitTab( bool bVert, sal_uInt16 nCnt, bool bSameHeight ) +{ + // check if Point/Mark of current cursor are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + + if( dynamic_cast< const SwDDETable* >(pFrame->ImplFindTabFrame()->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return; + } + + SET_CURR_SHELL( this ); + + if( bVert && !CheckSplitCells( *this, nCnt + 1, SwTableSearchType::NONE ) ) + { + ErrorHandler::HandleError( ERR_TBLSPLIT_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return; + } + StartAllAction(); + // search boxes via the layout + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes ); + if( !aBoxes.empty() ) + { + TableWait aWait( nCnt, pFrame, *GetDoc()->GetDocShell(), aBoxes.size() ); + + // now delete the columns + GetDoc()->SplitTable( aBoxes, bVert, nCnt, bSameHeight ); + + ClearFEShellTabCols(*GetDoc(), nullptr); + } + EndAllActionAndCall(); +} + +void SwFEShell::GetTabCols_(SwTabCols &rToFill, const SwFrame *pBox) const +{ + const SwTabFrame *pTab = pBox->FindTabFrame(); + if (m_pColumnCache) + { + bool bDel = true; + if (m_pColumnCache->pLastTable == pTab->GetTable()) + { + bDel = false; + SwRectFnSet aRectFnSet(pTab); + + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + if (m_pColumnCache->pLastTabFrame != pTab) + { + // if TabFrame was changed, we only shift a little bit + // as the width is the same + SwRectFnSet fnRectX(m_pColumnCache->pLastTabFrame); + if (fnRectX.GetWidth(m_pColumnCache->pLastTabFrame->getFrameArea()) == + aRectFnSet.GetWidth(pTab->getFrameArea()) ) + { + m_pColumnCache->pLastCols->SetLeftMin( nLeftMin ); + + m_pColumnCache->pLastTabFrame = pTab; + } + else + bDel = true; + } + + if ( !bDel && + m_pColumnCache->pLastCols->GetLeftMin () == static_cast<sal_uInt16>(nLeftMin) && + m_pColumnCache->pLastCols->GetLeft () == static_cast<sal_uInt16>(aRectFnSet.GetLeft(pTab->getFramePrintArea())) && + m_pColumnCache->pLastCols->GetRight () == static_cast<sal_uInt16>(aRectFnSet.GetRight(pTab->getFramePrintArea()))&& + m_pColumnCache->pLastCols->GetRightMax() == static_cast<sal_uInt16>(nRightMax) - m_pColumnCache->pLastCols->GetLeftMin() ) + { + if (m_pColumnCache->pLastCellFrame != pBox) + { + pTab->GetTable()->GetTabCols( *m_pColumnCache->pLastCols, + static_cast<const SwCellFrame*>(pBox)->GetTabBox(), true); + m_pColumnCache->pLastCellFrame = pBox; + } + rToFill = *m_pColumnCache->pLastCols; + } + else + bDel = true; + } + if ( bDel ) + m_pColumnCache.reset(); + } + if (!m_pColumnCache) + { + SwDoc::GetTabCols( rToFill, static_cast<const SwCellFrame*>(pBox) ); + + m_pColumnCache.reset(new SwColCache); + m_pColumnCache->pLastCols.reset(new SwTabCols(rToFill)); + m_pColumnCache->pLastTable = pTab->GetTable(); + m_pColumnCache->pLastTabFrame = pTab; + m_pColumnCache->pLastCellFrame = pBox; + } +} + +void SwFEShell::GetTabRows_(SwTabCols &rToFill, const SwFrame *pBox) const +{ + const SwTabFrame *pTab = pBox->FindTabFrame(); + if (m_pRowCache) + { + bool bDel = true; + if (m_pRowCache->pLastTable == pTab->GetTable()) + { + bDel = false; + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const long nLeftMin = ( aRectFnSet.IsVert() ? + pTab->GetPrtLeft() - pPage->getFrameArea().Left() : + pTab->GetPrtTop() - pPage->getFrameArea().Top() ); + const long nLeft = aRectFnSet.IsVert() ? LONG_MAX : 0; + const long nRight = aRectFnSet.GetHeight(pTab->getFramePrintArea()); + const long nRightMax = aRectFnSet.IsVert() ? nRight : LONG_MAX; + + if (m_pRowCache->pLastTabFrame != pTab || m_pRowCache->pLastCellFrame != pBox) + bDel = true; + + if ( !bDel && + m_pRowCache->pLastCols->GetLeftMin () == nLeftMin && + m_pRowCache->pLastCols->GetLeft () == nLeft && + m_pRowCache->pLastCols->GetRight () == nRight && + m_pRowCache->pLastCols->GetRightMax() == nRightMax ) + { + rToFill = *m_pRowCache->pLastCols; + } + else + bDel = true; + } + if ( bDel ) + m_pRowCache.reset(); + } + if (!m_pRowCache) + { + SwDoc::GetTabRows( rToFill, static_cast<const SwCellFrame*>(pBox) ); + + m_pRowCache.reset(new SwColCache); + m_pRowCache->pLastCols.reset(new SwTabCols(rToFill)); + m_pRowCache->pLastTable = pTab->GetTable(); + m_pRowCache->pLastTabFrame = pTab; + m_pRowCache->pLastCellFrame = pBox; + } +} + +void SwFEShell::SetTabCols( const SwTabCols &rNew, bool bCurRowOnly ) +{ + SwFrame *pBox = GetCurrFrame(); + if( !pBox || !pBox->IsInTab() ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + + do + { + pBox = pBox->GetUpper(); + } while (pBox && !pBox->IsCellFrame()); + + GetDoc()->SetTabCols( rNew, bCurRowOnly, static_cast<SwCellFrame*>(pBox) ); + EndAllActionAndCall(); +} + +void SwFEShell::GetTabCols( SwTabCols &rToFill ) const +{ + const SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + do + { + pFrame = pFrame->GetUpper(); + } + while (pFrame && !pFrame->IsCellFrame()); + + if (!pFrame) + return; + + GetTabCols_( rToFill, pFrame ); +} + +void SwFEShell::GetTabRows( SwTabCols &rToFill ) const +{ + const SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + do + { + pFrame = pFrame->GetUpper(); + } while (pFrame && !pFrame->IsCellFrame()); + + if (!pFrame) + return; + + GetTabRows_( rToFill, pFrame ); +} + +void SwFEShell::SetTabRows( const SwTabCols &rNew, bool bCurColOnly ) +{ + SwFrame *pBox = GetCurrFrame(); + if( !pBox || !pBox->IsInTab() ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + + do + { + pBox = pBox->GetUpper(); + } while (pBox && !pBox->IsCellFrame()); + + GetDoc()->SetTabRows( rNew, bCurColOnly, static_cast<SwCellFrame*>(pBox) ); + EndAllActionAndCall(); +} + +void SwFEShell::GetMouseTabRows( SwTabCols &rToFill, const Point &rPt ) const +{ + const SwFrame *pBox = GetBox( rPt ); + if ( pBox ) + GetTabRows_( rToFill, pBox ); +} + +void SwFEShell::SetMouseTabRows( const SwTabCols &rNew, bool bCurColOnly, const Point &rPt ) +{ + const SwFrame *pBox = GetBox( rPt ); + if( pBox ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetTabRows( rNew, bCurColOnly, static_cast<const SwCellFrame*>(pBox) ); + EndAllActionAndCall(); + } +} + +void SwFEShell::SetRowSplit( const SwFormatRowSplit& rNew ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetRowSplit( *getShellCursor( false ), rNew ); + EndAllActionAndCall(); +} + +std::unique_ptr<SwFormatRowSplit> SwFEShell::GetRowSplit() const +{ + return SwDoc::GetRowSplit( *getShellCursor( false ) ); +} + +void SwFEShell::SetRowHeight( const SwFormatFrameSize &rNew ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetRowHeight( *getShellCursor( false ), rNew ); + EndAllActionAndCall(); +} + +std::unique_ptr<SwFormatFrameSize> SwFEShell::GetRowHeight() const +{ + return SwDoc::GetRowHeight( *getShellCursor( false ) ); +} + +bool SwFEShell::BalanceRowHeight( bool bTstOnly, const bool bOptimize ) +{ + SET_CURR_SHELL( this ); + if( !bTstOnly ) + StartAllAction(); + bool bRet = GetDoc()->BalanceRowHeight( *getShellCursor( false ), bTstOnly, bOptimize ); + if( !bTstOnly ) + EndAllActionAndCall(); + return bRet; +} + +void SwFEShell::SetRowBackground( const SvxBrushItem &rNew ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetRowBackground( *getShellCursor( false ), rNew ); + EndAllActionAndCall(); +} + +bool SwFEShell::GetRowBackground( std::unique_ptr<SvxBrushItem>& rToFill ) const +{ + return SwDoc::GetRowBackground( *getShellCursor( false ), rToFill ); +} + +void SwFEShell::SetTabBorders( const SfxItemSet& rSet ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetTabBorders( *getShellCursor( false ), rSet ); + EndAllActionAndCall(); +} + +void SwFEShell::SetTabLineStyle( const Color* pColor, bool bSetLine, + const editeng::SvxBorderLine* pBorderLine ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetTabLineStyle( *getShellCursor( false ), + pColor, bSetLine, pBorderLine ); + EndAllActionAndCall(); +} + +void SwFEShell::GetTabBorders( SfxItemSet& rSet ) const +{ + SwDoc::GetTabBorders( *getShellCursor( false ), rSet ); +} + +void SwFEShell::SetBoxBackground( const SvxBrushItem &rNew ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetBoxAttr( *getShellCursor( false ), rNew ); + EndAllActionAndCall(); +} + +bool SwFEShell::GetBoxBackground( std::unique_ptr<SvxBrushItem>& rToFill ) const +{ + std::unique_ptr<SfxPoolItem> aTemp = std::move(rToFill); + bool bRetval(SwDoc::GetBoxAttr(*getShellCursor( false ), aTemp)); + rToFill.reset(static_cast<SvxBrushItem*>(aTemp.release())); + return bRetval; +} + +void SwFEShell::SetBoxDirection( const SvxFrameDirectionItem& rNew ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetBoxAttr( *getShellCursor( false ), rNew ); + EndAllActionAndCall(); +} + +bool SwFEShell::GetBoxDirection( std::unique_ptr<SvxFrameDirectionItem>& rToFill ) const +{ + std::unique_ptr<SfxPoolItem> aTemp = std::move(rToFill); + bool bRetval(SwDoc::GetBoxAttr(*getShellCursor( false ), aTemp)); + rToFill.reset(static_cast<SvxFrameDirectionItem*>(aTemp.release())); + return bRetval; +} + +void SwFEShell::SetBoxAlign( sal_uInt16 nAlign ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetBoxAlign( *getShellCursor( false ), nAlign ); + EndAllActionAndCall(); +} + +sal_uInt16 SwFEShell::GetBoxAlign() const +{ + return SwDoc::GetBoxAlign( *getShellCursor( false ) ); +} + +void SwFEShell::SetTabBackground( const SvxBrushItem &rNew ) +{ + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetAttr( rNew, *pFrame->ImplFindTabFrame()->GetFormat() ); + EndAllAction(); // no call, nothing changes! + GetDoc()->getIDocumentState().SetModified(); +} + +void SwFEShell::GetTabBackground( std::unique_ptr<SvxBrushItem>& rToFill ) const +{ + SwFrame *pFrame = GetCurrFrame(); + if( pFrame && pFrame->IsInTab() ) + rToFill = pFrame->ImplFindTabFrame()->GetFormat()->makeBackgroundBrushItem(); +} + +bool SwFEShell::HasWholeTabSelection() const +{ + // whole table selected? + if ( IsTableMode() ) + { + SwSelBoxes aBoxes; + ::GetTableSelCrs( *this, aBoxes ); + if( !aBoxes.empty() ) + { + const SwTableNode *pTableNd = IsCursorInTable(); + return pTableNd && + aBoxes[0]->GetSttIdx() - 1 == pTableNd->EndOfSectionNode()->StartOfSectionIndex() && + aBoxes.back()->GetSttNd()->EndOfSectionIndex() + 1 == pTableNd->EndOfSectionIndex(); + } + } + return false; +} + +bool SwFEShell::HasBoxSelection() const +{ + if(!IsCursorInTable()) + return false; + // whole table selected? + if( IsTableMode() ) + return true; + SwPaM* pPam = GetCursor(); + // empty boxes are also selected as the absence of selection + bool bChg = false; + if( pPam->GetPoint() == pPam->End()) + { + bChg = true; + pPam->Exchange(); + } + SwNode* pNd; + if( pPam->GetPoint()->nNode.GetIndex() -1 == + ( pNd = &pPam->GetNode())->StartOfSectionIndex() && + !pPam->GetPoint()->nContent.GetIndex() && + pPam->GetMark()->nNode.GetIndex() + 1 == + pNd->EndOfSectionIndex()) + { + SwNodeIndex aIdx( *pNd->EndOfSectionNode(), -1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + { + pCNd = SwNodes::GoPrevious( &aIdx ); + OSL_ENSURE( pCNd, "no ContentNode in box ??" ); + } + if( pPam->GetMark()->nContent == pCNd->Len() ) + { + if( bChg ) + pPam->Exchange(); + return true; + } + } + if( bChg ) + pPam->Exchange(); + return false; +} + +void SwFEShell::ProtectCells() +{ + SvxProtectItem aProt( RES_PROTECT ); + aProt.SetContentProtect( true ); + + SET_CURR_SHELL( this ); + StartAllAction(); + + GetDoc()->SetBoxAttr( *getShellCursor( false ), aProt ); + + if( !IsCursorReadonly() ) + { + if( IsTableMode() ) + ClearMark(); + ParkCursorInTab(); + } + EndAllActionAndCall(); +} + +// cancel table selection +void SwFEShell::UnProtectCells() +{ + SET_CURR_SHELL( this ); + StartAllAction(); + + SwSelBoxes aBoxes; + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if( pFrame ) + { + SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert( pBox ); + } + } + + if( !aBoxes.empty() ) + GetDoc()->UnProtectCells( aBoxes ); + + EndAllActionAndCall(); +} + +void SwFEShell::UnProtectTables() +{ + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->UnProtectTables( *GetCursor() ); + EndAllActionAndCall(); +} + +bool SwFEShell::HasTableAnyProtection( const OUString* pTableName, + bool* pFullTableProtection ) +{ + return GetDoc()->HasTableAnyProtection( GetCursor()->GetPoint(), pTableName, + pFullTableProtection ); +} + +bool SwFEShell::CanUnProtectCells() const +{ + bool bUnProtectAvailable = false; + const SwTableNode *pTableNd = IsCursorInTable(); + if( pTableNd && !pTableNd->IsProtect() ) + { + SwSelBoxes aBoxes; + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if( pFrame ) + { + SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert( pBox ); + } + } + if( !aBoxes.empty() ) + bUnProtectAvailable = ::HasProtectedCells( aBoxes ); + } + return bUnProtectAvailable; +} + +sal_uInt16 SwFEShell::GetRowsToRepeat() const +{ + const SwFrame *pFrame = GetCurrFrame(); + const SwTabFrame *pTab = pFrame ? pFrame->FindTabFrame() : nullptr; + if( pTab ) + return pTab->GetTable()->GetRowsToRepeat(); + return 0; +} + +void SwFEShell::SetRowsToRepeat( sal_uInt16 nSet ) +{ + SwFrame *pFrame = GetCurrFrame(); + SwTabFrame *pTab = pFrame ? pFrame->FindTabFrame() : nullptr; + if( pTab && pTab->GetTable()->GetRowsToRepeat() != nSet ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetRowsToRepeat( *pTab->GetTable(), nSet ); + EndAllActionAndCall(); + } +} + +// returns the number of rows consecutively selected from top +static sal_uInt16 lcl_GetRowNumber( const SwPosition& rPos ) +{ + Point aTmpPt; + const SwContentNode *pNd; + const SwContentFrame *pFrame; + + std::pair<Point, bool> const tmp(aTmpPt, false); + if( nullptr != ( pNd = rPos.nNode.GetNode().GetContentNode() )) + pFrame = pNd->getLayoutFrame(pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), &rPos, &tmp); + else + pFrame = nullptr; + + const SwFrame* pRow = (pFrame && pFrame->IsInTab()) ? pFrame->GetUpper() : nullptr; + + while (pRow && (!pRow->GetUpper() || !pRow->GetUpper()->IsTabFrame())) + pRow = pRow->GetUpper(); + + if (!pRow) + return USHRT_MAX; + + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pRow->GetUpper()); + const SwTableLine* pTabLine = static_cast<const SwRowFrame*>(pRow)->GetTabLine(); + sal_uInt16 nRet = USHRT_MAX; + sal_uInt16 nI = 0; + while ( sal::static_int_cast<SwTableLines::size_type>(nI) < pTabFrame->GetTable()->GetTabLines().size() ) + { + if ( pTabFrame->GetTable()->GetTabLines()[ nI ] == pTabLine ) + { + nRet = nI; + break; + } + ++nI; + } + + return nRet; +} + +sal_uInt16 SwFEShell::GetRowSelectionFromTop() const +{ + sal_uInt16 nRet = 0; + const SwPaM* pPaM = IsTableMode() ? GetTableCursor() : GetCursor_(); + const sal_uInt16 nPtLine = lcl_GetRowNumber( *pPaM->GetPoint() ); + + if ( !IsTableMode() ) + { + nRet = 0 == nPtLine ? 1 : 0; + } + else + { + const sal_uInt16 nMkLine = lcl_GetRowNumber( *pPaM->GetMark() ); + + if ( ( nPtLine == 0 && nMkLine != USHRT_MAX ) || + ( nMkLine == 0 && nPtLine != USHRT_MAX ) ) + { + nRet = std::max( nPtLine, nMkLine ) + 1; + } + } + + return nRet; +} + +/* + * 1. case: bRepeat = true + * returns true if the current frame is located inside a table headline in + * a follow frame + * + * 2. case: bRepeat = false + * returns true if the current frame is located inside a table headline OR + * inside the first line of a table!!! + */ +bool SwFEShell::CheckHeadline( bool bRepeat ) const +{ + bool bRet = false; + if ( !IsTableMode() ) + { + SwFrame *pFrame = GetCurrFrame(); // DONE MULTIIHEADER + SwTabFrame* pTab = (pFrame && pFrame->IsInTab()) ? pFrame->FindTabFrame() : nullptr; + if (pTab) + { + if ( bRepeat ) + { + bRet = pTab->IsFollow() && pTab->IsInHeadline( *pFrame ); + } + else + { + bRet = static_cast<SwLayoutFrame*>(pTab->Lower())->IsAnLower( pFrame ) || + pTab->IsInHeadline( *pFrame ); + } + } + } + return bRet; +} + +void SwFEShell::AdjustCellWidth( const bool bBalance, const bool bNoShrink ) +{ + SET_CURR_SHELL( this ); + StartAllAction(); + + // switch on wait-cursor, as we do not know how + // much content is affected + TableWait aWait(std::numeric_limits<size_t>::max(), nullptr, + *GetDoc()->GetDocShell()); + + GetDoc()->AdjustCellWidth( *getShellCursor( false ), bBalance, bNoShrink ); + EndAllActionAndCall(); +} + +bool SwFEShell::IsAdjustCellWidthAllowed( bool bBalance ) const +{ + // at least one row with content should be contained in the selection + + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return false; + + SwSelBoxes aBoxes; + ::GetTableSelCrs( *this, aBoxes ); + + if ( bBalance ) + return aBoxes.size() > 1; + + if ( aBoxes.empty() ) + { + do + { + pFrame = pFrame->GetUpper(); + } + while (pFrame && !pFrame->IsCellFrame()); + + if (!pFrame) + return false; + + SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert( pBox ); + } + + for (size_t i = 0; i < aBoxes.size(); ++i) + { + SwTableBox *pBox = aBoxes[i]; + if ( pBox->GetSttNd() ) + { + SwNodeIndex aIdx( *pBox->GetSttNd(), 1 ); + SwTextNode* pCNd = aIdx.GetNode().GetTextNode(); + if( !pCNd ) + pCNd = static_cast<SwTextNode*>(GetDoc()->GetNodes().GoNext( &aIdx )); + + while ( pCNd ) + { + if (!pCNd->GetText().isEmpty()) + return true; + ++aIdx; + pCNd = aIdx.GetNode().GetTextNode(); + } + } + } + return false; +} + +bool SwFEShell::SetTableStyle(const OUString& rStyleName) +{ + // make sure SwDoc has the style + SwTableAutoFormat *pTableFormat = GetDoc()->GetTableStyles().FindAutoFormat(rStyleName); + if (!pTableFormat) + return false; + + SwTableNode *pTableNode = const_cast<SwTableNode*>(IsCursorInTable()); + if (!pTableNode) + return false; + + // set the name & update + return UpdateTableStyleFormatting(pTableNode, false, &rStyleName); +} + + // AutoFormat for the table/table selection +bool SwFEShell::SetTableStyle(const SwTableAutoFormat& rStyle) +{ + // make sure SwDoc has the style + GetDoc()->GetTableStyles().AddAutoFormat(rStyle); + + SwTableNode *pTableNode = const_cast<SwTableNode*>(IsCursorInTable()); + if (!pTableNode) + return false; + + // set the name & update + return UpdateTableStyleFormatting(pTableNode, false, &rStyle.GetName()); +} + +bool SwFEShell::UpdateTableStyleFormatting(SwTableNode *pTableNode, + bool bResetDirect, OUString const*const pStyleName) +{ + if (!pTableNode) + { + pTableNode = const_cast<SwTableNode*>(IsCursorInTable()); + if (!pTableNode || pTableNode->GetTable().IsTableComplex()) + return false; + } + + OUString const aTableStyleName(pStyleName + ? *pStyleName + : pTableNode->GetTable().GetTableStyleName()); + SwTableAutoFormat* pTableStyle = GetDoc()->GetTableStyles().FindAutoFormat(aTableStyleName); + if (!pTableStyle) + return false; + + SwSelBoxes aBoxes; + + // whole table or only current selection + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + const SwTableSortBoxes& rTBoxes = pTableNode->GetTable().GetTabSortBoxes(); + for (size_t n = 0; n < rTBoxes.size(); ++n) + { + SwTableBox* pBox = rTBoxes[ n ]; + aBoxes.insert( pBox ); + } + } + + bool bRet; + if( !aBoxes.empty() ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + bRet = GetDoc()->SetTableAutoFormat( + aBoxes, *pTableStyle, bResetDirect, pStyleName != nullptr); + ClearFEShellTabCols(*GetDoc(), nullptr); + EndAllActionAndCall(); + } + else + bRet = false; + return bRet; +} + +bool SwFEShell::GetTableAutoFormat( SwTableAutoFormat& rGet ) +{ + const SwTableNode *pTableNd = IsCursorInTable(); + if( !pTableNd || pTableNd->GetTable().IsTableComplex() ) + return false; + + SwSelBoxes aBoxes; + + if ( !IsTableMode() ) // if cursor are not current + GetCursor(); + + // whole table or only current selection + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + const SwTableSortBoxes& rTBoxes = pTableNd->GetTable().GetTabSortBoxes(); + for (size_t n = 0; n < rTBoxes.size(); ++n) + { + SwTableBox* pBox = rTBoxes[ n ]; + aBoxes.insert( pBox ); + } + } + + return GetDoc()->GetTableAutoFormat( aBoxes, rGet ); +} + +bool SwFEShell::DeleteTableSel() +{ + // check if SPoint/Mark of current cursor are in a table + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return false; + + if( dynamic_cast< const SwDDETable* >(pFrame->ImplFindTabFrame()->GetTable()) != nullptr ) + { + ErrorHandler::HandleError( ERR_TBLDDECHG_ERROR, GetFrameWeld(GetDoc()->GetDocShell()), + DialogMask::MessageInfo | DialogMask::ButtonsOk ); + return false; + } + + SET_CURR_SHELL( this ); + StartAllAction(); + + // search boxes via the layout + bool bRet; + SwSelBoxes aBoxes; + GetTableSelCrs( *this, aBoxes ); + if( !aBoxes.empty() ) + { + TableWait aWait( aBoxes.size(), pFrame, *GetDoc()->GetDocShell() ); + + // cursor should be removed from deletion area. + // Put them behind/on the table; via the document + // position they'll be set to the old position + while( !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + ParkCursor( SwNodeIndex( *static_cast<SwCellFrame*>(pFrame)->GetTabBox()->GetSttNd() )); + + bRet = GetDoc()->DeleteRowCol( aBoxes ); + + ClearFEShellTabCols(*GetDoc(), nullptr); + } + else + bRet = false; + EndAllActionAndCall(); + return bRet; +} + +size_t SwFEShell::GetCurTabColNum() const +{ + //!!!GetCurMouseTabColNum() mitpflegen!!!! + SwFrame *pFrame = GetCurrFrame(); + OSL_ENSURE( pFrame, "Cursor parked?" ); + + // check if SPoint/Mark of current cursor are in a table + if (!pFrame || !pFrame->IsInTab()) + return 0; + + do + { + // JP 26.09.95: why compare with ContentFrame + // and not with CellFrame ???? + pFrame = pFrame->GetUpper(); + } while (pFrame && !pFrame->IsCellFrame()); + + if (!pFrame) + return 0; + + size_t nRet = 0; + + SwRectFnSet aRectFnSet(pFrame); + + const SwPageFrame* pPage = pFrame->FindPageFrame(); + + // get TabCols, as only via these we get to the position + SwTabCols aTabCols; + GetTabCols( aTabCols ); + + if( pFrame->FindTabFrame()->IsRightToLeft() ) + { + long nX = aRectFnSet.GetRight(pFrame->getFrameArea()) - aRectFnSet.GetLeft(pPage->getFrameArea()); + + const long nRight = aTabCols.GetLeftMin() + aTabCols.GetRight(); + + if ( !::IsSame( nX, nRight ) ) + { + nX = nRight - nX + aTabCols.GetLeft(); + for ( size_t i = 0; i < aTabCols.Count(); ++i ) + if ( ::IsSame( nX, aTabCols[i] ) ) + { + nRet = i + 1; + break; + } + } + } + else + { + const long nX = aRectFnSet.GetLeft(pFrame->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + const long nLeft = aTabCols.GetLeftMin(); + + if ( !::IsSame( nX, nLeft + aTabCols.GetLeft() ) ) + { + for ( size_t i = 0; i < aTabCols.Count(); ++i ) + if ( ::IsSame( nX, nLeft + aTabCols[i] ) ) + { + nRet = i + 1; + break; + } + } + } + return nRet; +} + +static const SwFrame *lcl_FindFrameInTab( const SwLayoutFrame *pLay, const Point &rPt, SwTwips nFuzzy ) +{ + const SwFrame *pFrame = pLay->Lower(); + + while( pFrame && pLay->IsAnLower( pFrame ) ) + { + if ( pFrame->getFrameArea().IsNear( rPt, nFuzzy ) ) + { + if ( pFrame->IsLayoutFrame() ) + { + const SwFrame *pTmp = ::lcl_FindFrameInTab( static_cast<const SwLayoutFrame*>(pFrame), rPt, nFuzzy ); + if ( pTmp ) + return pTmp; + } + + return pFrame; + } + + pFrame = pFrame->FindNext(); + } + + return nullptr; +} + +static const SwCellFrame *lcl_FindFrame( const SwLayoutFrame *pLay, const Point &rPt, + SwTwips nFuzzy, bool* pbRow, bool* pbCol ) +{ + // bMouseMoveRowCols : + // Method is called for + // - Moving columns/rows with the mouse or + // - Enhanced table selection + const bool bMouseMoveRowCols = nullptr == pbCol; + + bool bCloseToRow = false; + bool bCloseToCol = false; + + const SwFrame *pFrame = pLay->ContainsContent(); + const SwFrame* pRet = nullptr; + + if ( pFrame ) + { + do + { + if ( pFrame->IsInTab() ) + pFrame = const_cast<SwFrame*>(pFrame)->ImplFindTabFrame(); + + if (!pFrame) + break; + + if ( pFrame->IsTabFrame() ) + { + Point aPt( rPt ); + bool bSearchForFrameInTab = true; + SwTwips nTmpFuzzy = nFuzzy; + + if ( !bMouseMoveRowCols ) + { + // We ignore nested tables for the enhanced table selection: + while ( pFrame->GetUpper()->IsInTab() ) + pFrame = pFrame->GetUpper()->FindTabFrame(); + + // We first check if the given point is 'close' to the left or top + // border of the table frame: + OSL_ENSURE( pFrame, "Nested table frame without outer table" ); + SwRectFnSet aRectFnSet(pFrame); + const bool bRTL = pFrame->IsRightToLeft(); + + SwRect aTabRect = pFrame->getFramePrintArea(); + aTabRect.Pos() += pFrame->getFrameArea().Pos(); + + const SwTwips nLeft = bRTL ? + aRectFnSet.GetRight(aTabRect) : + aRectFnSet.GetLeft(aTabRect); + const SwTwips nTop = aRectFnSet.GetTop(aTabRect); + + SwTwips const rPointX = aRectFnSet.IsVert() ? aPt.Y() : aPt.X(); + SwTwips const rPointY = aRectFnSet.IsVert() ? aPt.X() : aPt.Y(); + + const SwTwips nXDiff = aRectFnSet.XDiff( nLeft, rPointX ) * ( bRTL ? -1 : 1 ); + const SwTwips nYDiff = aRectFnSet.YDiff( nTop, rPointY ); + + bCloseToRow = nXDiff >= 0 && nXDiff < nFuzzy; + bCloseToCol = nYDiff >= 0 && nYDiff < nFuzzy; + + if ( bCloseToCol && 2 * nYDiff > nFuzzy ) + { + const SwFrame* pPrev = pFrame->GetPrev(); + if ( pPrev ) + { + SwRect aPrevRect = pPrev->getFramePrintArea(); + aPrevRect.Pos() += pPrev->getFrameArea().Pos(); + + if( aPrevRect.IsInside( rPt ) ) + { + bCloseToCol = false; + } + } + + } + + // If we found the point to be 'close' to the left or top border + // of the table frame, we adjust the point to be on that border: + if ( bCloseToRow && bCloseToCol ) + aPt = bRTL ? aTabRect.TopRight() : aRectFnSet.GetPos(aTabRect); + else if ( bCloseToRow ) + aRectFnSet.IsVert() ? aPt.setY(nLeft) : aPt.setX(nLeft); + else if ( bCloseToCol ) + aRectFnSet.IsVert() ? aPt.setX(nTop) : aPt.setY(nTop); + + if ( !bCloseToRow && !bCloseToCol ) + bSearchForFrameInTab = false; + + // Since the point has been adjusted, we call lcl_FindFrameInTab() + // with a fuzzy value of 1: + nTmpFuzzy = 1; + } + + const SwFrame* pTmp = bSearchForFrameInTab ? + ::lcl_FindFrameInTab( static_cast<const SwLayoutFrame*>(pFrame), aPt, nTmpFuzzy ) : + nullptr; + + if ( pTmp ) + { + pFrame = pTmp; + break; + } + } + pFrame = pFrame->FindNextCnt(); + + } while ( pFrame && pLay->IsAnLower( pFrame ) ); + } + + if ( pFrame && pFrame->IsInTab() && pLay->IsAnLower( pFrame ) ) + { + do + { + // We allow mouse drag of table borders within nested tables, + // but disallow hotspot selection of nested tables. + if ( bMouseMoveRowCols ) + { + // find the next cell frame + while ( pFrame && !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + } + else + { + // find the most upper cell frame: + while ( pFrame && + ( !pFrame->IsCellFrame() || + !pFrame->GetUpper()->GetUpper()->IsTabFrame() || + pFrame->GetUpper()->GetUpper()->GetUpper()->IsInTab() ) ) + pFrame = pFrame->GetUpper(); + } + + if ( pFrame ) // Note: this condition should be the same like the while condition!!! + { + // #i32329# Enhanced table selection + // used for hotspot selection of tab/cols/rows + if ( !bMouseMoveRowCols ) + { + + OSL_ENSURE( pbCol && pbRow, "pbCol or pbRow missing" ); + + if ( bCloseToRow || bCloseToCol ) + { + *pbRow = bCloseToRow; + *pbCol = bCloseToCol; + pRet = pFrame; + break; + } + } + else + { + // used for mouse move of columns/rows + const SwTabFrame* pTabFrame = pFrame->FindTabFrame(); + SwRect aTabRect = pTabFrame->getFramePrintArea(); + aTabRect.Pos() += pTabFrame->getFrameArea().Pos(); + + SwRectFnSet aRectFnSet(pTabFrame); + + const SwTwips nTabTop = aRectFnSet.GetTop(aTabRect); + const SwTwips nMouseTop = aRectFnSet.IsVert() ? rPt.X() : rPt.Y(); + + // Do not allow to drag upper table border: + if ( !::IsSame( nTabTop, nMouseTop ) ) + { + if ( ::IsSame( pFrame->getFrameArea().Left(), rPt.X() ) || + ::IsSame( pFrame->getFrameArea().Right(),rPt.X() ) ) + { + if ( pbRow ) *pbRow = false; + pRet = pFrame; + break; + } + if ( ::IsSame( pFrame->getFrameArea().Top(), rPt.Y() ) || + ::IsSame( pFrame->getFrameArea().Bottom(),rPt.Y() ) ) + { + if ( pbRow ) *pbRow = true; + pRet = pFrame; + break; + } + } + } + + pFrame = pFrame->GetUpper(); + } + } while ( pFrame ); + } + + // robust: + OSL_ENSURE( !pRet || pRet->IsCellFrame(), "lcl_FindFrame() is supposed to find a cell frame!" ); + return pRet && pRet->IsCellFrame() ? static_cast<const SwCellFrame*>(pRet) : nullptr; +} + +// pbCol = 0 => Used for moving table rows/cols with mouse +// pbCol != 0 => Used for selecting table/rows/cols + +#define ENHANCED_TABLE_SELECTION_FUZZY 10 + +const SwFrame* SwFEShell::GetBox( const Point &rPt, bool* pbRow, bool* pbCol ) const +{ + const SwPageFrame *pPage = static_cast<SwPageFrame*>(GetLayout()->Lower()); + vcl::Window* pOutWin = GetWin(); + SwTwips nFuzzy = COLFUZZY; + if( pOutWin ) + { + // #i32329# Enhanced table selection + SwTwips nSize = pbCol ? ENHANCED_TABLE_SELECTION_FUZZY : RULER_MOUSE_MARGINWIDTH; + Size aTmp( nSize, nSize ); + aTmp = pOutWin->PixelToLogic( aTmp ); + nFuzzy = aTmp.Width(); + } + + while ( pPage && !pPage->getFrameArea().IsNear( rPt, nFuzzy ) ) + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + + const SwCellFrame *pFrame = nullptr; + if ( pPage ) + { + // We cannot search the box by GetModelPositionForViewPoint or GetContentPos. + // This would lead to a performance collapse for documents + // with a lot of paragraphs/tables on one page + //(BrowseMode!) + + // check flys first + if ( pPage->GetSortedObjs() ) + { + for ( size_t i = 0; !pFrame && i < pPage->GetSortedObjs()->size(); ++i ) + { + SwAnchoredObject* pObj = (*pPage->GetSortedObjs())[i]; + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + pFrame = lcl_FindFrame( static_cast<SwFlyFrame*>(pObj), + rPt, nFuzzy, pbRow, pbCol ); + } + } + } + const SwLayoutFrame *pLay = static_cast<const SwLayoutFrame*>(pPage->Lower()); + while ( pLay && !pFrame ) + { + pFrame = lcl_FindFrame( pLay, rPt, nFuzzy, pbRow, pbCol ); + pLay = static_cast<const SwLayoutFrame*>(pLay->GetNext()); + } + } + return pFrame; +} + +/* Helper function*/ +/* calculated the distance between Point rC and Line Segment (rA, rB) */ +static double lcl_DistancePoint2Segment( const Point& rA, const Point& rB, const Point& rC ) +{ + double nRet = 0; + + const basegfx::B2DVector aBC( rC.X() - rB.X(), rC.Y() - rB.Y() ); + const basegfx::B2DVector aAB( rB.X() - rA.X(), rB.Y() - rA.Y() ); + const double nDot1 = aBC.scalar( aAB ); + + if ( nDot1 > 0 ) // check outside case 1 + nRet = aBC.getLength(); + else + { + const basegfx::B2DVector aAC( rC.X() - rA.X(), rC.Y() - rA.Y() ); + const basegfx::B2DVector aBA( rA.X() - rB.X(), rA.Y() - rB.Y() ); + const double nDot2 = aAC.scalar( aBA ); + + if ( nDot2 > 0 ) // check outside case 2 + nRet = aAC.getLength(); + else + { + const double nDiv = aAB.getLength(); + nRet = nDiv ? aAB.cross( aAC ) / nDiv : 0; + } + } + + return std::abs(nRet); +} + +/* Helper function*/ +static Point lcl_ProjectOntoClosestTableFrame( const SwTabFrame& rTab, const Point& rPoint, bool bRowDrag ) +{ + Point aRet( rPoint ); + const SwTabFrame* pCurrentTab = &rTab; + const bool bVert = pCurrentTab->IsVertical(); + const bool bRTL = pCurrentTab->IsRightToLeft(); + + // Western Layout: + // bRowDrag = true => compare to left border of table + // bRowDrag = false => compare to top border of table + + // Asian Layout: + // bRowDrag = true => compare to right border of table + // bRowDrag = false => compare to top border of table + + // RTL Layout: + // bRowDrag = true => compare to right border of table + // bRowDrag = false => compare to top border of table + bool bLeft = false; + bool bRight = false; + + if ( bRowDrag ) + { + if ( bVert || bRTL ) + bRight = true; + else + bLeft = true; + } + + // used to find the minimal distance + double nMin = -1; + Point aMin1; + Point aMin2; + + Point aS1; + Point aS2; + + while ( pCurrentTab ) + { + SwRect aTabRect( pCurrentTab->getFramePrintArea() ); + aTabRect += pCurrentTab->getFrameArea().Pos(); + + if ( bLeft ) + { + // distance to left table border + aS1 = aTabRect.TopLeft(); + aS2 = aTabRect.BottomLeft(); + } + else if ( bRight ) + { + // distance to right table border + aS1 = aTabRect.TopRight(); + aS2 = aTabRect.BottomRight(); + } + else //if ( bTop ) + { + // distance to top table border + aS1 = aTabRect.TopLeft(); + aS2 = aTabRect.TopRight(); + } + + const double nDist = lcl_DistancePoint2Segment( aS1, aS2, rPoint ); + + if ( nDist < nMin || -1 == nMin ) + { + aMin1 = aS1; + aMin2 = aS2; + nMin = nDist; + } + + pCurrentTab = pCurrentTab->GetFollow(); + } + + // project onto closest line: + if ( bLeft || bRight ) + { + aRet.setX(aMin1.getX()); + if ( aRet.getY() > aMin2.getY() ) + aRet.setY(aMin2.getY()); + else if ( aRet.getY() < aMin1.getY() ) + aRet.setY(aMin1.getY()); + } + else + { + aRet.setY(aMin1.getY()); + if ( aRet.getX() > aMin2.getX() ) + aRet.setX(aMin2.getX()); + else if ( aRet.getX() < aMin1.getX() ) + aRet.setX(aMin1.getX()); + } + + return aRet; +} + +// #i32329# Enhanced table selection +bool SwFEShell::SelTableRowCol( const Point& rPt, const Point* pEnd, bool bRowDrag ) +{ + bool bRet = false; + Point aEndPt; + if ( pEnd ) + aEndPt = *pEnd; + + SwPosition* ppPos[2] = { nullptr, nullptr }; + Point paPt [2] = { rPt, aEndPt }; + bool pbRow[2] = { false, false }; + bool pbCol[2] = { false, false }; + + // pEnd is set during dragging. + for ( sal_uInt16 i = 0; i < ( pEnd ? 2 : 1 ); ++i ) + { + const SwCellFrame* pFrame = + static_cast<const SwCellFrame*>(GetBox( paPt[i], &pbRow[i], &pbCol[i] ) ); + + if( pFrame ) + { + while( pFrame && pFrame->Lower() && pFrame->Lower()->IsRowFrame() ) + pFrame = static_cast<const SwCellFrame*>( static_cast<const SwLayoutFrame*>( pFrame->Lower() )->Lower() ); + if( pFrame && pFrame->GetTabBox()->GetSttNd() && + pFrame->GetTabBox()->GetSttNd()->IsInProtectSect() ) + pFrame = nullptr; + } + + if ( pFrame ) + { + const SwContentFrame* pContent = ::GetCellContent( *pFrame ); + + if ( pContent && pContent->IsTextFrame() ) + { + + ppPos[i] = new SwPosition(static_cast<SwTextFrame const*>(pContent)->MapViewToModelPos(TextFrameIndex(0))); + + // paPt[i] will not be used any longer, now we use it to store + // a position inside the content frame + paPt[i] = pContent->getFrameArea().Center(); + } + } + + // no calculation of end frame if start frame has not been found. + if ( 1 == i || !ppPos[0] || !pEnd || !pFrame ) + break; + + // find 'closest' table frame to pEnd: + const SwTabFrame* pCurrentTab = pFrame->FindTabFrame(); + if ( pCurrentTab->IsFollow() ) + pCurrentTab = pCurrentTab->FindMaster( true ); + + const Point aProjection = lcl_ProjectOntoClosestTableFrame( *pCurrentTab, *pEnd, bRowDrag ); + paPt[1] = aProjection; + } + + if ( ppPos[0] ) + { + SwShellCursor* pCursor = GetCursor_(); + SwCursorSaveState aSaveState( *pCursor ); + SwPosition aOldPos( *pCursor->GetPoint() ); + + pCursor->DeleteMark(); + *pCursor->GetPoint() = *ppPos[0]; + pCursor->GetPtPos() = paPt[0]; + + if ( !pCursor->IsInProtectTable() ) + { + bool bNewSelection = true; + + if ( ppPos[1] ) + { + if ( ppPos[1]->nNode.GetNode().StartOfSectionNode() != + aOldPos.nNode.GetNode().StartOfSectionNode() ) + { + pCursor->SetMark(); + SwCursorSaveState aSaveState2( *pCursor ); + *pCursor->GetPoint() = *ppPos[1]; + pCursor->GetPtPos() = paPt[1]; + + if ( pCursor->IsInProtectTable( false, false ) ) + { + pCursor->RestoreSavePos(); + bNewSelection = false; + } + } + else + { + pCursor->RestoreSavePos(); + bNewSelection = false; + } + } + + if ( bNewSelection ) + { + // #i35543# SelTableRowCol should remove any existing + // table cursor: + if ( IsTableMode() ) + TableCursorToCursor(); + + if ( pbRow[0] && pbCol[0] ) + bRet = SwCursorShell::SelTable(); + else if ( pbRow[0] ) + bRet = SwCursorShell::SelTableRowOrCol( true, true ); + else if ( pbCol[0] ) + bRet = SwCursorShell::SelTableRowOrCol( false, true ); + } + else + bRet = true; + } + + delete ppPos[0]; + delete ppPos[1]; + } + + return bRet; +} + +SwTab SwFEShell::WhichMouseTabCol( const Point &rPt ) const +{ + SwTab nRet = SwTab::COL_NONE; + bool bRow = false; + bool bCol = false; + bool bSelect = false; + + // First try: Do we get the row/col move cursor? + const SwCellFrame* pFrame = static_cast<const SwCellFrame*>(GetBox( rPt, &bRow )); + + if ( !pFrame ) + { + // Second try: Do we get the row/col/tab selection cursor? + pFrame = static_cast<const SwCellFrame*>(GetBox( rPt, &bRow, &bCol )); + bSelect = true; + } + + if( pFrame ) + { + while( pFrame && pFrame->Lower() && pFrame->Lower()->IsRowFrame() ) + pFrame = static_cast<const SwCellFrame*>(static_cast<const SwLayoutFrame*>(pFrame->Lower())->Lower()); + if( pFrame && pFrame->GetTabBox()->GetSttNd() && + pFrame->GetTabBox()->GetSttNd()->IsInProtectSect() ) + pFrame = nullptr; + } + + if( pFrame ) + { + if ( !bSelect ) + { + if ( pFrame->IsVertical() ) + nRet = bRow ? SwTab::COL_VERT : SwTab::ROW_VERT; + else + nRet = bRow ? SwTab::ROW_HORI : SwTab::COL_HORI; + } + else + { + const SwTabFrame* pTabFrame = pFrame->FindTabFrame(); + if ( pTabFrame->IsVertical() ) + { + if ( bRow && bCol ) + { + nRet = SwTab::SEL_VERT; + } + else if ( bRow ) + { + nRet = SwTab::ROWSEL_VERT; + } + else if ( bCol ) + { + nRet = SwTab::COLSEL_VERT; + } + } + else + { + if ( bRow && bCol ) + { + nRet = pTabFrame->IsRightToLeft() ? + SwTab::SEL_HORI_RTL : + SwTab::SEL_HORI; + } + else if ( bRow ) + { + nRet = pTabFrame->IsRightToLeft() ? + SwTab::ROWSEL_HORI_RTL : + SwTab::ROWSEL_HORI; + } + else if ( bCol ) + { + nRet = SwTab::COLSEL_HORI; + } + } + } + } + + return nRet; +} + +// -> #i23726# +SwTextNode * SwFEShell::GetNumRuleNodeAtPos( const Point &rPt) +{ + SwTextNode * pResult = nullptr; + + SwContentAtPos aContentAtPos(IsAttrAtPos::NumLabel); + + if( GetContentAtPos(rPt, aContentAtPos) && aContentAtPos.aFnd.pNode) + pResult = aContentAtPos.aFnd.pNode->GetTextNode(); + + return pResult; +} + +bool SwFEShell::IsNumLabel( const Point &rPt, int nMaxOffset ) +{ + bool bResult = false; + + SwContentAtPos aContentAtPos(IsAttrAtPos::NumLabel); + + if( GetContentAtPos(rPt, aContentAtPos)) + { + if ((nMaxOffset >= 0 && aContentAtPos.nDist <= nMaxOffset) || + (nMaxOffset < 0)) + bResult = true; + } + + return bResult; +} +// <- #i23726# + +// #i42921# +bool SwFEShell::IsVerticalModeAtNdAndPos( const SwTextNode& _rTextNode, + const Point& _rDocPos ) +{ + bool bRet( false ); + + const SvxFrameDirection nTextDir = + _rTextNode.GetTextDirection( SwPosition(_rTextNode), &_rDocPos ); + switch ( nTextDir ) + { + case SvxFrameDirection::Unknown: + case SvxFrameDirection::Horizontal_RL_TB: + case SvxFrameDirection::Horizontal_LR_TB: + { + bRet = false; + } + break; + case SvxFrameDirection::Vertical_LR_TB: + case SvxFrameDirection::Vertical_RL_TB: + { + bRet = true; + } + break; + default: break; + } + + return bRet; +} + +void SwFEShell::GetMouseTabCols( SwTabCols &rToFill, const Point &rPt ) const +{ + const SwFrame *pBox = GetBox( rPt ); + if ( pBox ) + GetTabCols_( rToFill, pBox ); +} + +void SwFEShell::SetMouseTabCols( const SwTabCols &rNew, bool bCurRowOnly, + const Point &rPt ) +{ + const SwFrame *pBox = GetBox( rPt ); + if( pBox ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + GetDoc()->SetTabCols( rNew, bCurRowOnly, static_cast<const SwCellFrame*>(pBox) ); + EndAllActionAndCall(); + } +} + +sal_uInt16 SwFEShell::GetCurMouseColNum( const Point &rPt ) const +{ + return GetCurColNum_( GetBox( rPt ), nullptr ); +} + +size_t SwFEShell::GetCurMouseTabColNum( const Point &rPt ) const +{ + //!!!GetCurTabColNum() mitpflegen!!!! + size_t nRet = 0; + + const SwFrame *pFrame = GetBox( rPt ); + OSL_ENSURE( pFrame, "Table not found" ); + if( pFrame ) + { + const long nX = pFrame->getFrameArea().Left(); + + // get TabCols, only via these we get the position + SwTabCols aTabCols; + GetMouseTabCols( aTabCols, rPt ); + + const long nLeft = aTabCols.GetLeftMin(); + + if ( !::IsSame( nX, nLeft + aTabCols.GetLeft() ) ) + { + for ( size_t i = 0; i < aTabCols.Count(); ++i ) + if ( ::IsSame( nX, nLeft + aTabCols[i] ) ) + { + nRet = i + 1; + break; + } + } + } + return nRet; +} + +void ClearFEShellTabCols(SwDoc & rDoc, SwTabFrame const*const pFrame) +{ + auto const pShell(rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()); + if (pShell) + { + for (SwViewShell& rCurrentShell : pShell->GetRingContainer()) + { + if (auto const pFE = dynamic_cast<SwFEShell *>(&rCurrentShell)) + { + pFE->ClearColumnRowCache(pFrame); + } + } + } +} + +void SwFEShell::ClearColumnRowCache(SwTabFrame const*const pFrame) +{ + if (m_pColumnCache) + { + if (pFrame == nullptr || pFrame == m_pColumnCache->pLastTabFrame) + { + m_pColumnCache.reset(); + } + } + if (m_pRowCache) + { + if (pFrame == nullptr || pFrame == m_pRowCache->pLastTabFrame) + { + m_pRowCache.reset(); + } + } +} + +void SwFEShell::GetTableAttr( SfxItemSet &rSet ) const +{ + SwFrame *pFrame = GetCurrFrame(); + if( pFrame && pFrame->IsInTab() ) + rSet.Put( pFrame->ImplFindTabFrame()->GetFormat()->GetAttrSet() ); +} + +void SwFEShell::SetTableAttr( const SfxItemSet &rNew ) +{ + SwFrame *pFrame = GetCurrFrame(); + if( pFrame && pFrame->IsInTab() ) + { + SET_CURR_SHELL( this ); + StartAllAction(); + SwTabFrame *pTab = pFrame->FindTabFrame(); + pTab->GetTable()->SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + GetDoc()->SetAttr( rNew, *pTab->GetFormat() ); + GetDoc()->getIDocumentState().SetModified(); + EndAllActionAndCall(); + } +} + +// change a cell width/cell height/column width/row height +void SwFEShell::SetColRowWidthHeight( TableChgWidthHeightType eType, sal_uInt16 nDiff ) +{ + SwFrame *pFrame = GetCurrFrame(); + if( !pFrame || !pFrame->IsInTab() ) + return; + + SET_CURR_SHELL( this ); + StartAllAction(); + + do { + pFrame = pFrame->GetUpper(); + } while( !pFrame->IsCellFrame() ); + + SwTabFrame *pTab = pFrame->ImplFindTabFrame(); + + // if the table is in relative values (USHRT_MAX) + // then it should be recalculated to absolute values now + const SwFormatFrameSize& rTableFrameSz = pTab->GetFormat()->GetFrameSize(); + SwRectFnSet aRectFnSet(pTab); + long nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + TableChgWidthHeightType eTypePos = extractPosition(eType); + if( TableChgMode::VarWidthChangeAbs == pTab->GetTable()->GetTableChgMode() && + ( eTypePos == TableChgWidthHeightType::ColLeft || eTypePos == TableChgWidthHeightType::ColRight ) && + text::HoriOrientation::NONE == pTab->GetFormat()->GetHoriOrient().GetHoriOrient() && + nPrtWidth != rTableFrameSz.GetWidth() ) + { + SwFormatFrameSize aSz( rTableFrameSz ); + aSz.SetWidth( pTab->getFramePrintArea().Width() ); + pTab->GetFormat()->SetFormatAttr( aSz ); + } + + SwTwips nLogDiff = nDiff; + nLogDiff *= pTab->GetFormat()->GetFrameSize().GetWidth(); + nLogDiff /= nPrtWidth; + + /** The cells are destroyed in here */ + GetDoc()->SetColRowWidthHeight( + *const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()), + eType, nDiff, nLogDiff ); + + ClearFEShellTabCols(*GetDoc(), nullptr); + EndAllActionAndCall(); +} + +static bool lcl_IsFormulaSelBoxes( const SwTable& rTable, const SwTableBoxFormula& rFormula, + SwCellFrames& rCells ) +{ + SwTableBoxFormula aTmp( rFormula ); + SwSelBoxes aBoxes; + aTmp.GetBoxesOfFormula(rTable, aBoxes); + for (size_t nSelBoxes = aBoxes.size(); nSelBoxes; ) + { + SwTableBox* pBox = aBoxes[ --nSelBoxes ]; + + if( std::none_of(rCells.begin(), rCells.end(), [&pBox](SwCellFrame* pFrame) { return pFrame->GetTabBox() == pBox; }) ) + return false; + } + + return true; +} + + // ask formula for auto-sum +void SwFEShell::GetAutoSum( OUString& rFormula ) const +{ + SwFrame *pFrame = GetCurrFrame(); + SwTabFrame *pTab = pFrame ? pFrame->ImplFindTabFrame() : nullptr; + if( !pTab ) + return; + + SwCellFrames aCells; + OUString sFields; + if( ::GetAutoSumSel( *this, aCells )) + { + sal_uInt16 nW = 0; + for( size_t n = aCells.size(); n; ) + { + SwCellFrame* pCFrame = aCells[ --n ]; + sal_uInt16 nBoxW = pCFrame->GetTabBox()->IsFormulaOrValueBox(); + if( !nBoxW ) + break; + + if( !nW ) + { + if( USHRT_MAX == nBoxW ) + continue; // skip space at beginning + + // formula only if box is contained + if( RES_BOXATR_FORMULA == nBoxW && + !::lcl_IsFormulaSelBoxes( *pTab->GetTable(), pCFrame-> + GetTabBox()->GetFrameFormat()->GetTableBoxFormula(), aCells)) + { + nW = RES_BOXATR_VALUE; + // restore previous spaces! + for( size_t i = aCells.size(); n+1 < i; ) + { + sFields = "|<" + aCells[--i]->GetTabBox()->GetName() + ">" + + sFields; + } + } + else + nW = nBoxW; + } + else if( RES_BOXATR_VALUE == nW ) + { + // search for values, Value/Formula/Text found -> include + if( RES_BOXATR_FORMULA == nBoxW && + ::lcl_IsFormulaSelBoxes( *pTab->GetTable(), pCFrame-> + GetTabBox()->GetFrameFormat()->GetTableBoxFormula(), aCells )) + break; + else if( USHRT_MAX != nBoxW ) + sFields = OUStringChar(cListDelim) + sFields; + else + break; + } + else if( RES_BOXATR_FORMULA == nW ) + { + // only continue search when the current formula points to + // all boxes contained in the selection + if( RES_BOXATR_FORMULA == nBoxW ) + { + if( !::lcl_IsFormulaSelBoxes( *pTab->GetTable(), pCFrame-> + GetTabBox()->GetFrameFormat()->GetTableBoxFormula(), aCells )) + { + // redo only for values! + + nW = RES_BOXATR_VALUE; + sFields.clear(); + // restore previous spaces! + for( size_t i = aCells.size(); n+1 < i; ) + { + sFields = "|<" + aCells[--i]->GetTabBox()->GetName() + ">" + + sFields; + } + } + else + sFields = OUStringChar(cListDelim) + sFields; + } + else if( USHRT_MAX == nBoxW ) + break; + else + continue; // ignore this box + } + else + // all other stuff terminates the loop + // possibly allow texts?? + break; + + sFields = "<" + pCFrame->GetTabBox()->GetName() + ">" + sFields; + } + } + + rFormula = OUString::createFromAscii( sCalc_Sum ); + if (!sFields.isEmpty()) + { + rFormula += "(" + sFields + ")"; + } +} + +bool SwFEShell::IsTableRightToLeft() const +{ + SwFrame *pFrame = GetCurrFrame(); + SwTabFrame *pTab = (pFrame && pFrame->IsInTab()) ? pFrame->ImplFindTabFrame() : nullptr; + if (!pTab) + return false; + return pTab->IsRightToLeft(); +} + +bool SwFEShell::IsMouseTableRightToLeft(const Point &rPt) const +{ + SwFrame *pFrame = const_cast<SwFrame *>(GetBox( rPt )); + const SwTabFrame* pTabFrame = pFrame ? pFrame->ImplFindTabFrame() : nullptr; + OSL_ENSURE( pTabFrame, "Table not found" ); + return pTabFrame && pTabFrame->IsRightToLeft(); +} + +bool SwFEShell::IsTableVertical() const +{ + SwFrame *pFrame = GetCurrFrame(); + SwTabFrame *pTab = (pFrame && pFrame->IsInTab()) ? pFrame->ImplFindTabFrame() : nullptr; + if (!pTab) + return false; + return pTab->IsVertical(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/fews.cxx b/sw/source/core/frmedt/fews.cxx new file mode 100644 index 000000000..e98406446 --- /dev/null +++ b/sw/source/core/frmedt/fews.cxx @@ -0,0 +1,1328 @@ +/* -*- 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 <svx/svdobj.hxx> +#include <comphelper/lok.hxx> +#include <init.hxx> +#include <fesh.hxx> +#include <tabcol.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <doc.hxx> +#include <frmtool.hxx> +#include <swtable.hxx> +#include <viewimp.hxx> +#include <dview.hxx> +#include <flyfrm.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <sectfrm.hxx> +#include <fmtpdsc.hxx> +#include <fmtsrnd.hxx> +#include <fmtcntnt.hxx> +#include <fmtfsize.hxx> +#include <tabfrm.hxx> +#include <flyfrms.hxx> +#include <txtfrm.hxx> +#include <mdiexp.hxx> +#include <pagedesc.hxx> +#include <fmtanchr.hxx> +#include <environmentofanchoredobject.hxx> +#include <ndtxt.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <UndoInsert.hxx> + +using namespace com::sun::star; + +namespace +{ +/** + * This mutex is only used for the paste listeners, where the solar mutex can't + * be used. + */ +osl::Mutex& GetPasteMutex() +{ + static osl::Mutex aMutex; + return aMutex; +} +} + +void SwFEShell::EndAllActionAndCall() +{ + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if( dynamic_cast<const SwCursorShell*>( &rCurrentShell) != nullptr ) + { + static_cast<SwFEShell*>(&rCurrentShell)->EndAction(); + static_cast<SwFEShell*>(&rCurrentShell)->CallChgLnk(); + } + else + rCurrentShell.EndAction(); + } +} + +// Determine the Content's nearest to the point +Point SwFEShell::GetContentPos( const Point& rPoint, bool bNext ) const +{ + SET_CURR_SHELL( const_cast<SwFEShell*>(this) ); + return GetLayout()->GetNextPrevContentPos( rPoint, bNext ); +} + +const SwRect& SwFEShell::GetAnyCurRect( CurRectType eType, const Point* pPt, + const uno::Reference < embed::XEmbeddedObject >& xObj ) const +{ + const SwFrame *pFrame = Imp()->HasDrawView() + ? ::GetFlyFromMarked( &Imp()->GetDrawView()->GetMarkedObjectList(), + const_cast<SwFEShell*>(this)) + : nullptr; + + if( !pFrame ) + { + if( pPt ) + { + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( *pPt ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt ); + SwContentNode *pNd = aPos.nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(*pPt, true); + pFrame = pNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + } + else + { + const bool bOldCallbackActionEnabled = GetLayout()->IsCallbackActionEnabled(); + if( bOldCallbackActionEnabled ) + GetLayout()->SetCallbackActionEnabled( false ); + pFrame = GetCurrFrame(); + if( bOldCallbackActionEnabled ) + GetLayout()->SetCallbackActionEnabled( true ); + } + } + + if( !pFrame ) + return GetLayout()->getFrameArea(); + + bool bFrame = true; + switch ( eType ) + { + case CurRectType::PagePrt: bFrame = false; + [[fallthrough]]; + case CurRectType::Page : pFrame = pFrame->FindPageFrame(); + break; + + case CurRectType::PageCalc: + { + DisableCallbackAction a(const_cast<SwRootFrame&>(*pFrame->getRootFrame())); + pFrame->Calc(Imp()->GetShell()->GetOut()); + pFrame = pFrame->FindPageFrame(); + pFrame->Calc(Imp()->GetShell()->GetOut()); + } + break; + + case CurRectType::FlyEmbeddedPrt: + bFrame = false; + [[fallthrough]]; + case CurRectType::FlyEmbedded: + { + const SwFrame *pFlyFrame = xObj.is() ? FindFlyFrame(xObj) : nullptr; + pFrame = pFlyFrame ? pFlyFrame + : pFrame->IsFlyFrame() + ? pFrame + : pFrame->FindFlyFrame(); + break; + } + case CurRectType::SectionOutsideTable : + if( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + else { + OSL_FAIL( "Missing Table" ); + } + [[fallthrough]]; + case CurRectType::SectionPrt: + case CurRectType::Section: + if( pFrame->IsInSct() ) + pFrame = pFrame->FindSctFrame(); + else { + OSL_FAIL( "Missing section" ); + } + + if( CurRectType::SectionPrt == eType ) + bFrame = false; + break; + + case CurRectType::HeaderFooter: + if( nullptr == (pFrame = pFrame->FindFooterOrHeader()) ) + return GetLayout()->getFrameArea(); + break; + + case CurRectType::PagesArea: + return GetLayout()->GetPagesArea(); + + default: break; + } + return bFrame ? pFrame->getFrameArea() : pFrame->getFramePrintArea(); +} + +sal_uInt16 SwFEShell::GetPageNumber( const Point &rPoint ) const +{ + const SwFrame *pPage = GetLayout()->Lower(); + while ( pPage && !pPage->getFrameArea().IsInside( rPoint ) ) + pPage = pPage->GetNext(); + if ( pPage ) + return static_cast<const SwPageFrame*>(pPage)->GetPhyPageNum(); + else + return 0; +} + +bool SwFEShell::GetPageNumber( long nYPos, bool bAtCursorPos, sal_uInt16& rPhyNum, sal_uInt16& rVirtNum, OUString &rDisplay) const +{ + const SwFrame *pPage; + + if ( bAtCursorPos ) // get page of Cursor + { + pPage = GetCurrFrame( false ); + if ( pPage ) + pPage = pPage->FindPageFrame(); + } + else if ( nYPos > -1 ) // determine page via the position + { + pPage = GetLayout()->Lower(); + while( pPage && (pPage->getFrameArea().Bottom() < nYPos || + nYPos < pPage->getFrameArea().Top() ) ) + pPage = pPage->GetNext(); + } + else // first visible page + { + pPage = Imp()->GetFirstVisPage(GetOut()); + if ( pPage && static_cast<const SwPageFrame*>(pPage)->IsEmptyPage() ) + pPage = pPage->GetNext(); + } + + if( pPage ) + { + rPhyNum = static_cast<const SwPageFrame*>(pPage)->GetPhyPageNum(); + rVirtNum = static_cast<const SwPageFrame*>(pPage)->GetVirtPageNum(); + const SvxNumberType& rNum = static_cast<const SwPageFrame*>(pPage)->GetPageDesc()->GetNumType(); + rDisplay = rNum.GetNumStr( rVirtNum ); + } + + return nullptr != pPage; +} + +bool SwFEShell::IsDirectlyInSection() const +{ + SwFrame* pFrame = GetCurrFrame( false ); + return pFrame && pFrame->GetUpper() && pFrame->GetUpper()->IsSctFrame(); +} + +FrameTypeFlags SwFEShell::GetFrameType( const Point *pPt, bool bStopAtFly ) const +{ + FrameTypeFlags nReturn = FrameTypeFlags::NONE; + const SwFrame *pFrame; + if ( pPt ) + { + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( *pPt ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt ); + SwContentNode *pNd = aPos.nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(*pPt, true); + pFrame = pNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + } + else + pFrame = GetCurrFrame( false ); + while ( pFrame ) + { + switch ( pFrame->GetType() ) + { + case SwFrameType::Column: + if( pFrame->GetUpper()->IsSctFrame() ) + { + // Check, if isn't not only a single column + // from a section with footnotes at the end. + if( pFrame->GetNext() || pFrame->GetPrev() ) + // Sectioncolumns + nReturn |= ( nReturn & FrameTypeFlags::TABLE ) ? + FrameTypeFlags::COLSECTOUTTAB : FrameTypeFlags::COLSECT; + } + else // only pages and frame columns + nReturn |= FrameTypeFlags::COLUMN; + break; + case SwFrameType::Page: + nReturn |= FrameTypeFlags::PAGE; + if( static_cast<const SwPageFrame*>(pFrame)->IsFootnotePage() ) + nReturn |= FrameTypeFlags::FTNPAGE; + break; + case SwFrameType::Header: nReturn |= FrameTypeFlags::HEADER; break; + case SwFrameType::Footer: nReturn |= FrameTypeFlags::FOOTER; break; + case SwFrameType::Body: + if( pFrame->GetUpper()->IsPageFrame() ) // not for ColumnFrames + nReturn |= FrameTypeFlags::BODY; + break; + case SwFrameType::Ftn: nReturn |= FrameTypeFlags::FOOTNOTE; break; + case SwFrameType::Fly: + if( static_cast<const SwFlyFrame*>(pFrame)->IsFlyLayFrame() ) + nReturn |= FrameTypeFlags::FLY_FREE; + else if ( static_cast<const SwFlyFrame*>(pFrame)->IsFlyAtContentFrame() ) + nReturn |= FrameTypeFlags::FLY_ATCNT; + else + { + OSL_ENSURE( static_cast<const SwFlyFrame*>(pFrame)->IsFlyInContentFrame(), + "New frametype?" ); + nReturn |= FrameTypeFlags::FLY_INCNT; + } + nReturn |= FrameTypeFlags::FLY_ANY; + if( bStopAtFly ) + return nReturn; + break; + case SwFrameType::Tab: + case SwFrameType::Row: + case SwFrameType::Cell: nReturn |= FrameTypeFlags::TABLE; break; + default: /* do nothing */ break; + } + if ( pFrame->IsFlyFrame() ) + pFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + else + pFrame = pFrame->GetUpper(); + } + return nReturn; +} + +void SwFEShell::ShellGetFocus() +{ + ::SetShell( this ); + SwCursorShell::ShellGetFocus(); + + if ( HasDrawView() ) + { + if (!comphelper::LibreOfficeKit::isActive()) + Imp()->GetDrawView()->showMarkHandles(); + if ( Imp()->GetDrawView()->AreObjectsMarked() ) + FrameNotify( this, FLY_DRAG_START ); + } +} + +void SwFEShell::ShellLoseFocus() +{ + SwCursorShell::ShellLoseFocus(); + + if ( HasDrawView() && Imp()->GetDrawView()->AreObjectsMarked() ) + { + if (!comphelper::LibreOfficeKit::isActive()) + Imp()->GetDrawView()->hideMarkHandles(); + FrameNotify( this, FLY_DRAG_END ); + } +} + +sal_uInt16 SwFEShell::GetPhyPageNum() const +{ + SwFrame *pFrame = GetCurrFrame(); + if ( pFrame ) + return pFrame->GetPhyPageNum(); + return 0; +} + +sal_uInt16 SwFEShell::GetVirtPageNum() const +{ + SwFrame *pFrame = GetCurrFrame(); + if ( pFrame ) + return pFrame->GetVirtPageNum(); + return 0; +} + +static void lcl_SetAPageOffset( sal_uInt16 nOffset, SwPageFrame* pPage, SwFEShell* pThis ) +{ + pThis->StartAllAction(); + OSL_ENSURE( pPage->FindFirstBodyContent(), + "SwFEShell _SetAPageOffset() without ContentFrame" ); + + SwFormatPageDesc aDesc( pPage->GetPageDesc() ); + aDesc.SetNumOffset( nOffset ); + + SwFrame *pFrame = pThis->GetCurrFrame( false ); + if ( pFrame->IsInTab() ) + pThis->GetDoc()->SetAttr( aDesc, *pFrame->FindTabFrame()->GetFormat() ); + else + { + pThis->GetDoc()->getIDocumentContentOperations().InsertPoolItem( + *pThis->GetCursor(), aDesc, SetAttrMode::DEFAULT, pThis->GetLayout()); + } + + pThis->EndAllAction(); +} + +void SwFEShell::SetNewPageOffset( sal_uInt16 nOffset ) +{ + GetLayout()->SetVirtPageNum( true ); + const SwPageFrame *pPage = GetCurrFrame( false )->FindPageFrame(); + lcl_SetAPageOffset( nOffset, const_cast<SwPageFrame*>(pPage), this ); +} + +void SwFEShell::SetPageOffset( sal_uInt16 nOffset ) +{ + const SwPageFrame *pPage = GetCurrFrame( false )->FindPageFrame(); + const SwRootFrame* pDocLayout = GetLayout(); + while ( pPage ) + { + const SwFrame *pFlow = pPage->FindFirstBodyContent(); + if ( pFlow ) + { + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + const SwFormatPageDesc& rPgDesc = pFlow->GetPageDescItem(); + if ( rPgDesc.GetNumOffset() ) + { + pDocLayout->SetVirtPageNum( true ); + lcl_SetAPageOffset( nOffset, const_cast<SwPageFrame*>(pPage), this ); + break; + } + } + pPage = static_cast<const SwPageFrame*>(pPage->GetPrev()); + } +} + +sal_uInt16 SwFEShell::GetPageOffset() const +{ + const SwPageFrame *pPage = GetCurrFrame()->FindPageFrame(); + while ( pPage ) + { + const SwFrame *pFlow = pPage->FindFirstBodyContent(); + if ( pFlow ) + { + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + ::std::optional<sal_uInt16> oNumOffset = pFlow->GetPageDescItem().GetNumOffset(); + if ( oNumOffset ) + return *oNumOffset; + } + pPage = static_cast<const SwPageFrame*>(pPage->GetPrev()); + } + return 0; +} + +void SwFEShell::InsertLabel( const SwLabelType eType, const OUString &rText, const OUString& rSeparator, + const OUString& rNumberSeparator, + const bool bBefore, const sal_uInt16 nId, + const OUString& rCharacterStyle, + const bool bCpyBrd ) +{ + // get node index of cursor position, SwDoc can do everything else itself + SwContentFrame *pCnt = SwLabelType::Draw==eType ? nullptr : GetCurrFrame( false ); + if( SwLabelType::Draw!=eType && !pCnt ) + return; + + StartAllAction(); + SwRewriter aRewriter(SwUndoInsertLabel::CreateRewriter(rText)); + StartUndo(SwUndoId::INSERTLABEL, &aRewriter); + + sal_uLong nIdx = 0; + bool bInnerCntIsFly = false; + SwFlyFrameFormat* pFlyFormat = nullptr; + switch( eType ) + { + case SwLabelType::Object: + case SwLabelType::Fly: + bInnerCntIsFly = pCnt->IsInFly(); + if (bInnerCntIsFly) + { + // pass down index to the startnode for flys + nIdx = pCnt->FindFlyFrame()-> + GetFormat()->GetContent().GetContentIdx()->GetIndex(); + } + break; + case SwLabelType::Table: + if( pCnt->IsInTab() ) + { + // pass down index to the TableNode for tables + const SwTable& rTable = *pCnt->FindTabFrame()->GetTable(); + nIdx = rTable.GetTabSortBoxes()[ 0 ] + ->GetSttNd()->FindTableNode()->GetIndex(); + } + break; + case SwLabelType::Draw: + if( Imp()->GetDrawView() ) + { + SwDrawView *pDView = Imp()->GetDrawView(); + const SdrMarkList& rMrkList = pDView->GetMarkedObjectList(); + + // copy marked drawing objects to + // local list to perform the corresponding action for each object + std::vector<SdrObject*> aDrawObjs; + { + for ( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject* pDrawObj = rMrkList.GetMark(i)->GetMarkedSdrObj(); + if( pDrawObj ) + aDrawObjs.push_back( pDrawObj ); + } + } + // loop on marked drawing objects + while ( !aDrawObjs.empty() ) + { + SdrObject* pDrawObj = aDrawObjs.back(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pDrawObj) == nullptr && + dynamic_cast<const SwFlyDrawObj*>( pDrawObj) == nullptr ) + { + SwFlyFrameFormat *pFormat = + GetDoc()->InsertDrawLabel( rText, rSeparator, rNumberSeparator, nId, rCharacterStyle, *pDrawObj ); + if( !pFlyFormat ) + pFlyFormat = pFormat; + } + + aDrawObjs.pop_back(); + } + + } + break; + default: + OSL_ENSURE( false, "Cursor neither in table nor in fly." ); + } + + if( nIdx ) + { + pFlyFormat = GetDoc()->InsertLabel(eType, rText, rSeparator, + rNumberSeparator, bBefore, nId, + nIdx, rCharacterStyle, bCpyBrd); + } + + if (pFlyFormat) + { + const Point aPt(GetCursorDocPos()); + if (SwFlyFrame* pFrame = pFlyFormat->GetFrame(&aPt)) + SelectFlyFrame(*pFrame); + } + EndUndo(); + EndAllActionAndCall(); + +} + +bool SwFEShell::Sort(const SwSortOptions& rOpt) +{ + if( !HasSelection() ) + return false; + + SET_CURR_SHELL( this ); + bool bRet = false; + StartAllAction(); + if(IsTableMode()) + { + // Sort table + // check if Point/Mark of current Cursor are in one table + SwFrame *pFrame = GetCurrFrame( false ); + OSL_ENSURE( pFrame->FindTabFrame(), "Cursor not in table." ); + + // search boxes via the layout + SwSelBoxes aBoxes; + GetTableSel(*this, aBoxes); + + // The Cursor should be removed from the deletion area. + // Always put them behind/on the table; via the + // document position they will always be set to the old position + while( !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + { + /* ParkCursor->ParkCursorTab */ + ParkCursorInTab(); + } + + // call sorting on document + bRet = mxDoc->SortTable(aBoxes, rOpt); + } + else + { + // Sort text nothing else + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwPaM* pPam = &rPaM; + + SwPosition* pStart = pPam->Start(); + SwPosition* pEnd = pPam->End(); + + SwNodeIndex aPrevIdx( pStart->nNode, -1 ); + sal_uLong nOffset = pEnd->nNode.GetIndex() - pStart->nNode.GetIndex(); + const sal_Int32 nCntStt = pStart->nContent.GetIndex(); + + // Sorting + bRet = mxDoc->SortText(*pPam, rOpt); + + // put selection again + pPam->DeleteMark(); + pPam->GetPoint()->nNode.Assign( aPrevIdx.GetNode(), +1 ); + SwContentNode* pCNd = pPam->GetContentNode(); + sal_Int32 nLen = pCNd->Len(); + if( nLen > nCntStt ) + nLen = nCntStt; + pPam->GetPoint()->nContent.Assign(pCNd, nLen ); + pPam->SetMark(); + + pPam->GetPoint()->nNode += nOffset; + pCNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + } + } + + EndAllAction(); + return bRet; +} + +bool SwFEShell::IsColRightToLeft() const +{ + SwFrame* pFrame = GetCurrFrame(); + while (pFrame) + { + pFrame = pFrame->GetUpper(); + if (pFrame && pFrame->IsColumnFrame()) + { + return pFrame->IsRightToLeft(); + } + } + return false; +} + +sal_uInt16 SwFEShell::GetCurColNum_( const SwFrame *pFrame, + SwGetCurColNumPara* pPara ) +{ + sal_uInt16 nRet = 0; + while ( pFrame ) + { + pFrame = pFrame->GetUpper(); + if( pFrame && pFrame->IsColumnFrame() ) + { + const SwFrame *pCurFrame = pFrame; + do { + ++nRet; + pFrame = pFrame->GetPrev(); + } while ( pFrame ); + + if( pPara ) + { + // now search the format, determining the columness + pFrame = pCurFrame->GetUpper(); + while( pFrame ) + { + if( ( SwFrameType::Page | SwFrameType::Fly | SwFrameType::Section ) & pFrame->GetType() ) + { + pPara->pFrameFormat = static_cast<const SwLayoutFrame*>(pFrame)->GetFormat(); + pPara->pPrtRect = &pFrame->getFramePrintArea(); + break; + } + pFrame = pFrame->GetUpper(); + } + if( !pFrame ) + { + pPara->pFrameFormat = nullptr; + pPara->pPrtRect = nullptr; + } + } + break; + } + } + return nRet; +} + +sal_uInt16 SwFEShell::GetCurColNum( SwGetCurColNumPara* pPara ) const +{ + OSL_ENSURE( GetCurrFrame(), "Cursor parked?" ); + return GetCurColNum_( GetCurrFrame(), pPara ); +} + +sal_uInt16 SwFEShell::GetCurOutColNum() const +{ + sal_uInt16 nRet = 0; + SwFrame* pFrame = GetCurrFrame(); + OSL_ENSURE( pFrame, "Cursor parked?" ); + if( pFrame ) + { + pFrame = pFrame->IsInTab() ? static_cast<SwFrame*>(pFrame->FindTabFrame()) + : static_cast<SwFrame*>(pFrame->FindSctFrame()); + OSL_ENSURE( pFrame, "No Tab, no Sect" ); + if( pFrame ) + nRet = GetCurColNum_( pFrame, nullptr ); + } + return nRet; +} + +SwFEShell::SwFEShell( SwDoc& rDoc, vcl::Window *pWindow, const SwViewOption *pOptions ) + : SwEditShell( rDoc, pWindow, pOptions ) + , m_bCheckForOLEInCaption(false) + , m_aPasteListeners(GetPasteMutex()) + , m_eTableInsertMode(SwTable::SEARCH_NONE) + , m_bTableCopied(false) +{ +} + +SwFEShell::SwFEShell( SwEditShell& rShell, vcl::Window *pWindow ) + : SwEditShell( rShell, pWindow ) + , m_bCheckForOLEInCaption(false) + , m_aPasteListeners(GetPasteMutex()) + , m_eTableInsertMode(SwTable::SEARCH_NONE) + , m_bTableCopied(false) +{ +} + +SwFEShell::~SwFEShell() +{ +} + +// #i17567# - adjustments for allowing +// negative vertical positions for fly frames anchored to paragraph/to character. +// #i22305# - adjustments for option 'Follow text flow' +// for to frame anchored objects. +// #i22341# - adjustments for vertical alignment at top of line +// for to character anchored objects. +void SwFEShell::CalcBoundRect( SwRect& _orRect, + const RndStdIds _nAnchorId, + const sal_Int16 _eHoriRelOrient, + const sal_Int16 _eVertRelOrient, + const SwPosition* _pToCharContentPos, + const bool _bFollowTextFlow, + bool _bMirror, + Point* _opRef, + Size* _opPercent, + const SwFormatFrameSize* pFormatFrameSize) const +{ + const SwFrame* pFrame; + const SwFlyFrame* pFly; + if( _opRef ) + { + pFrame = GetCurrFrame(); + if( nullptr != ( pFly = pFrame->FindFlyFrame() ) ) + pFrame = pFly->GetAnchorFrame(); + } + else + { + pFly = GetSelectedFlyFrame(); + pFrame = pFly ? pFly->GetAnchorFrame() : GetCurrFrame(); + } + + bool bWrapThrough = false; + if ( pFly ) + { + SwFlyFrameFormat* pFormat = const_cast<SwFlyFrameFormat*>(pFly->GetFormat()); + const SwFormatSurround& rSurround = pFormat->GetSurround(); + bWrapThrough = rSurround.GetSurround() == css::text::WrapTextMode_THROUGH; + } + + const SwPageFrame* pPage = pFrame->FindPageFrame(); + _bMirror = _bMirror && !pPage->OnRightPage(); + + Point aPos; + bool bVertic = false; + bool bRTL = false; + bool bVerticalL2R = false; + + if ((RndStdIds::FLY_AT_PAGE == _nAnchorId) || (RndStdIds::FLY_AT_FLY == _nAnchorId)) // LAYER_IMPL + { + const SwFrame* pTmp = pFrame; + // #i22305# + if ((RndStdIds::FLY_AT_PAGE == _nAnchorId) || + ((RndStdIds::FLY_AT_FLY == _nAnchorId) && !_bFollowTextFlow)) + { + pFrame = pPage; + } + else + { + pFrame = pFrame->FindFlyFrame(); + } + if ( !pFrame ) + pFrame = pTmp; + _orRect = pFrame->getFrameArea(); + SwRectFnSet aRectFnSet(pFrame); + bRTL = pFrame->IsRightToLeft(); + if ( bRTL ) + aPos = pFrame->getFrameArea().TopRight(); + else + aPos = aRectFnSet.GetPos(pFrame->getFrameArea()); + + if( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + { + bVertic = aRectFnSet.IsVert(); + bVerticalL2R = aRectFnSet.IsVertL2R(); + _bMirror = false; // no mirroring in vertical environment + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::PAGE_RIGHT: + case text::RelOrientation::FRAME_RIGHT: aPos.AdjustY(pFrame->getFramePrintArea().Height() ); + [[fallthrough]]; + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: aPos.AdjustY(pFrame->getFramePrintArea().Top() ); break; + default: break; + } + } + else if ( _bMirror ) + { + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: aPos.AdjustX(pFrame->getFramePrintArea().Width() ); + [[fallthrough]]; + case text::RelOrientation::PAGE_RIGHT: + case text::RelOrientation::FRAME_RIGHT: aPos.AdjustX(pFrame->getFramePrintArea().Left() ); break; + default: aPos.AdjustX(pFrame->getFrameArea().Width() ); + } + } + else if ( bRTL ) + { + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: aPos.AdjustX(pFrame->getFramePrintArea().Width() ); + [[fallthrough]]; + case text::RelOrientation::PAGE_LEFT: + case text::RelOrientation::FRAME_LEFT: aPos.AdjustX(pFrame->getFramePrintArea().Left() - + pFrame->getFrameArea().Width() ); break; + default: break; + } + } + else + { + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::PAGE_RIGHT: + case text::RelOrientation::FRAME_RIGHT: aPos.AdjustX(pFrame->getFramePrintArea().Width() ); + [[fallthrough]]; + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: aPos.AdjustX(pFrame->getFramePrintArea().Left() ); break; + default:break; + } + } + + if ( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + switch ( _eVertRelOrient ) + { + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: + { + aPos.AdjustX( -(pFrame->GetRightMargin()) ); + } + break; + } + } + else if ( aRectFnSet.IsVertL2R() ) + { + switch ( _eVertRelOrient ) + { + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: + { + aPos.AdjustX(pFrame->GetLeftMargin() ); + } + break; + } + } + else + { + switch ( _eVertRelOrient ) + { + case text::RelOrientation::PRINT_AREA: + case text::RelOrientation::PAGE_PRINT_AREA: + { + if ( pFrame->IsPageFrame() ) + { + aPos.setY( + static_cast<const SwPageFrame*>(pFrame)->PrtWithoutHeaderAndFooter().Top() ); + } + else + { + aPos.AdjustY(pFrame->getFramePrintArea().Top() ); + } + } + break; + } + } + if ( _opPercent ) + *_opPercent = pFrame->getFramePrintArea().SSize(); + } + else + { + const SwFrame* pUpper = ( pFrame->IsPageFrame() || pFrame->IsFlyFrame() ) ? + pFrame : pFrame->GetUpper(); + SwRectFnSet aRectFnSet(pUpper); + if ( _opPercent ) + { + // If the size is relative from page, then full size should be counted from the page frame. + if (pFormatFrameSize && pFormatFrameSize->GetWidthPercentRelation() == text::RelOrientation::PAGE_FRAME) + _opPercent->setWidth(pPage->getFrameArea().Width()); + else + _opPercent->setWidth(pUpper->getFramePrintArea().Width()); + + if (pFormatFrameSize && pFormatFrameSize->GetHeightPercentRelation() == text::RelOrientation::PAGE_FRAME) + // If the size is relative from page, then full size should be counted from the page frame. + _opPercent->setHeight(pPage->getFrameArea().Height()); + else + _opPercent->setHeight(pUpper->getFramePrintArea().Height()); + } + + bRTL = pFrame->IsRightToLeft(); + if ( bRTL ) + aPos = pFrame->getFrameArea().TopRight(); + else + aPos = aRectFnSet.GetPos(pFrame->getFrameArea()); + // #i17567# - allow negative positions + // for fly frames anchor to paragraph/to character. + if ((_nAnchorId == RndStdIds::FLY_AT_PARA) || (_nAnchorId == RndStdIds::FLY_AT_CHAR)) + { + // The rectangle, the fly frame can be positioned in, is determined + // horizontally by the frame area of the horizontal environment + // and vertically by the printing area of the vertical environment, + // if the object follows the text flow, or by the frame area of the + // vertical environment, if the object doesn't follow the text flow. + // new class <SwEnvironmentOfAnchoredObject> + objectpositioning::SwEnvironmentOfAnchoredObject aEnvOfObj( + _bFollowTextFlow ); + const SwLayoutFrame& rHoriEnvironLayFrame = + aEnvOfObj.GetHoriEnvironmentLayoutFrame( *pFrame ); + const SwLayoutFrame& rVertEnvironLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pFrame ); + const SwRect& aHoriEnvironRect( rHoriEnvironLayFrame.getFrameArea() ); + SwRect aVertEnvironRect; + if ( _bFollowTextFlow ) + { + aVertEnvironRect = rVertEnvironLayFrame.getFramePrintArea(); + aVertEnvironRect.Pos() += rVertEnvironLayFrame.getFrameArea().Pos(); + // #i18732# - adjust vertical 'virtual' anchor position + // (<aPos.Y()> respectively <aPos.X()>), if object is vertical aligned + // to page areas. + if ( _eVertRelOrient == text::RelOrientation::PAGE_FRAME || _eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA ) + { + if ( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + aPos.setX( aVertEnvironRect.Right() ); + } + else if ( aRectFnSet.IsVertL2R() ) + { + aPos.setX( aVertEnvironRect.Left() ); + } + else + { + aPos.setY( aVertEnvironRect.Top() ); + } + } + } + else + { + OSL_ENSURE( rVertEnvironLayFrame.IsPageFrame(), + "<SwFEShell::CalcBoundRect(..)> - not following text flow, but vertical environment *not* page!" ); + aVertEnvironRect = rVertEnvironLayFrame.getFrameArea(); + // #i18732# - adjustment vertical 'virtual' anchor position + // (<aPos.Y()> respectively <aPos.X()>), if object is vertical aligned + // to page areas. + if (_eVertRelOrient == text::RelOrientation::PAGE_FRAME + || _eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA + || _eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + { + if ( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + aPos.setX( aVertEnvironRect.Right() ); + if ( _eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA ) + { + aPos.setX(aPos.getX() - rVertEnvironLayFrame.GetRightMargin()); + } + } + else if ( aRectFnSet.IsVertL2R() ) + { + aPos.setX( aVertEnvironRect.Left() ); + if ( _eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA ) + { + aPos.setX(aPos.getX() + rVertEnvironLayFrame.GetLeftMargin()); + } + } + else + { + aPos.setY( aVertEnvironRect.Top() ); + if ( _eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA ) + { + aPos.setY(aPos.getY() + rVertEnvironLayFrame.GetTopMargin()); + // add height of page header + const SwFrame* pTmpFrame = rVertEnvironLayFrame.Lower(); + if ( pTmpFrame->IsHeaderFrame() ) + { + aPos.setY(aPos.getY() + pTmpFrame->getFrameArea().Height()); + } + } + else if (_eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + { + if (rVertEnvironLayFrame.IsPageFrame()) + { + auto& rPageFrame = static_cast<const SwPageFrame&>(rVertEnvironLayFrame); + aPos.setY(rPageFrame.PrtWithoutHeaderAndFooter().Bottom()); + } + else + { + aPos.AdjustY(rVertEnvironLayFrame.getFramePrintArea().Bottom()); + } + } + } + } + } + + // #i22341# - adjust vertical 'virtual' anchor position + // (<aPos.Y()> respectively <aPos.X()>), if object is anchored to + // character and vertical aligned at character or top of line + // <pFrame>, which is the anchor frame or the proposed anchor frame, + // doesn't have to be a text frame (e.g. edit a to-page anchored + // fly frame). Thus, assure this. + const SwTextFrame* pTextFrame( dynamic_cast<const SwTextFrame*>(pFrame) ); + if ( pTextFrame && + (_nAnchorId == RndStdIds::FLY_AT_CHAR) && + ( _eVertRelOrient == text::RelOrientation::CHAR || + _eVertRelOrient == text::RelOrientation::TEXT_LINE ) ) + { + SwTwips nTop = 0; + if ( _eVertRelOrient == text::RelOrientation::CHAR ) + { + SwRect aChRect; + if ( _pToCharContentPos ) + { + pTextFrame->GetAutoPos( aChRect, *_pToCharContentPos ); + } + else + { + // No content position provided. Thus, use a default one. + SwPosition aDefaultContentPos(*(pTextFrame->GetTextNodeFirst())); + pTextFrame->GetAutoPos( aChRect, aDefaultContentPos ); + } + nTop = aRectFnSet.GetBottom(aChRect); + } + else + { + if ( _pToCharContentPos ) + { + pTextFrame->GetTopOfLine( nTop, *_pToCharContentPos ); + } + else + { + // No content position provided. Thus, use a default one. + SwPosition aDefaultContentPos(*(pTextFrame->GetTextNodeFirst())); + pTextFrame->GetTopOfLine( nTop, aDefaultContentPos ); + } + } + if ( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + { + aPos.setX(nTop); + } + else + { + aPos.setY(nTop); + } + } + + // #i26945# - adjust horizontal 'virtual' anchor + // position (<aPos.X()> respectively <aPos.Y()>), if object is + // anchored to character and horizontal aligned at character. + if ( pTextFrame && + (_nAnchorId == RndStdIds::FLY_AT_CHAR) && + _eHoriRelOrient == text::RelOrientation::CHAR ) + { + SwTwips nLeft = 0; + SwRect aChRect; + if ( _pToCharContentPos ) + { + pTextFrame->GetAutoPos( aChRect, *_pToCharContentPos ); + } + else + { + // No content position provided. Thus, use a default one. + SwPosition aDefaultContentPos(*(pTextFrame->GetTextNodeFirst())); + pTextFrame->GetAutoPos( aChRect, aDefaultContentPos ); + } + nLeft = aRectFnSet.GetLeft(aChRect); + if ( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + { + aPos.setY(nLeft); + } + else + { + aPos.setX(nLeft); + } + } + if ( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + { + _orRect = SwRect( aVertEnvironRect.Left(), + aHoriEnvironRect.Top(), + aVertEnvironRect.Width(), + aHoriEnvironRect.Height() ); + } + else + { + _orRect = SwRect( aHoriEnvironRect.Left(), + aVertEnvironRect.Top(), + aHoriEnvironRect.Width(), + aVertEnvironRect.Height() ); + } + } + else + { + if( _opRef && pFly && pFly->IsFlyInContentFrame() ) + *_opRef = static_cast<const SwFlyInContentFrame*>( pFly )->GetRefPoint(); + + _orRect = pUpper->getFrameArea(); + if( !pUpper->IsBodyFrame() ) + { + _orRect += pUpper->getFramePrintArea().Pos(); + _orRect.SSize( pUpper->getFramePrintArea().SSize() ); + if ( pUpper->IsCellFrame() )//MA_FLY_HEIGHT + { + const SwFrame* pTab = pUpper->FindTabFrame(); + long nBottom = aRectFnSet.GetPrtBottom(*pTab->GetUpper()); + aRectFnSet.SetBottom( _orRect, nBottom ); + } + } + // only use 90% of height for character bound + { + if( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + _orRect.Width( (_orRect.Width()*9)/10 ); + else + _orRect.Height( (_orRect.Height()*9)/10 ); + } + } + + const SwTwips nBaseOfstForFly = ( pFrame->IsTextFrame() && pFly ) ? + static_cast<const SwTextFrame*>(pFrame)->GetBaseOffsetForFly( !bWrapThrough ) : + 0; + if( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + { + bVertic = aRectFnSet.IsVert(); + bVerticalL2R = aRectFnSet.IsVertL2R(); + _bMirror = false; + + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::FRAME_RIGHT: + { + aPos.setY(aPos.getY() + pFrame->getFramePrintArea().Height()); + aPos += aRectFnSet.GetPos(pFrame->getFramePrintArea()); + break; + } + case text::RelOrientation::PRINT_AREA: + { + aPos += aRectFnSet.GetPos(pFrame->getFramePrintArea()); + aPos.setY(aPos.getY() + nBaseOfstForFly); + break; + } + case text::RelOrientation::PAGE_RIGHT: + { + aPos.setY(pPage->getFrameArea().Top() + pPage->getFramePrintArea().Bottom()); + break; + } + case text::RelOrientation::PAGE_PRINT_AREA: + { + aPos.setY(pPage->getFrameArea().Top() + pPage->getFramePrintArea().Top()); + break; + } + case text::RelOrientation::PAGE_LEFT: + case text::RelOrientation::PAGE_FRAME: + { + aPos.setY(pPage->getFrameArea().Top()); + break; + } + case text::RelOrientation::FRAME: + { + aPos.setY(aPos.getY() + nBaseOfstForFly); + break; + } + default: break; + } + } + else if( _bMirror ) + { + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::FRAME_RIGHT: aPos.setX(aPos.getX() + pFrame->getFramePrintArea().Left()); break; + case text::RelOrientation::FRAME: + case text::RelOrientation::FRAME_LEFT: aPos.setX(aPos.getX() + pFrame->getFrameArea().Width()); break; + case text::RelOrientation::PRINT_AREA: aPos.setX(aPos.getX() + pFrame->getFramePrintArea().Right()); break; + case text::RelOrientation::PAGE_LEFT: + case text::RelOrientation::PAGE_FRAME: aPos.setX(pPage->getFrameArea().Right()); break; + case text::RelOrientation::PAGE_PRINT_AREA: aPos.setX(pPage->getFrameArea().Left() + + pPage->getFramePrintArea().Left()); break; + default: break; + } + } + else if ( bRTL ) + { + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::FRAME_LEFT: + aPos.setX(pFrame->getFrameArea().Left() + + pFrame->getFramePrintArea().Left()); + break; + + case text::RelOrientation::PRINT_AREA: + aPos.setX(pFrame->getFrameArea().Left() + pFrame->getFramePrintArea().Left() + + pFrame->getFramePrintArea().Width()); + aPos.setX(aPos.getX() + nBaseOfstForFly); + break; + + case text::RelOrientation::PAGE_LEFT: + aPos.setX(pPage->getFrameArea().Left() + pPage->getFramePrintArea().Left()); + break; + + case text::RelOrientation::PAGE_PRINT_AREA: + aPos.setX(pPage->getFrameArea().Left() + pPage->getFramePrintArea().Left() + + pPage->getFramePrintArea().Width()); + break; + + case text::RelOrientation::PAGE_RIGHT: + case text::RelOrientation::PAGE_FRAME: + aPos.setX(pPage->getFrameArea().Right()); + break; + + case text::RelOrientation::FRAME: + aPos.setX(aPos.getX() + nBaseOfstForFly); + break; + default: break; + } + } + else + { + switch ( _eHoriRelOrient ) + { + case text::RelOrientation::FRAME_RIGHT: + aPos.AdjustX(pFrame->getFramePrintArea().Width() ); + aPos += pFrame->getFramePrintArea().Pos(); + break; + case text::RelOrientation::PRINT_AREA: + aPos += pFrame->getFramePrintArea().Pos(); + aPos.setX(aPos.getX() + nBaseOfstForFly); + break; + case text::RelOrientation::PAGE_RIGHT: + aPos.setX(pPage->getFrameArea().Left() + pPage->getFramePrintArea().Right()); + break; + case text::RelOrientation::PAGE_PRINT_AREA: + aPos.setX(pPage->getFrameArea().Left() + pPage->getFramePrintArea().Left()); + break; + case text::RelOrientation::PAGE_LEFT: + case text::RelOrientation::PAGE_FRAME: + aPos.setX(pPage->getFrameArea().Left()); + break; + case text::RelOrientation::FRAME: + aPos.setX(aPos.getX() + nBaseOfstForFly); + break; + default: break; + } + } + + } + if( !_opRef ) + { + if( bVertic && !bVerticalL2R ) + _orRect.Pos( aPos.getX() - _orRect.Width() - _orRect.Left(), _orRect.Top() - aPos.getY() ); + else if( bVerticalL2R ) + _orRect.Pos( _orRect.Left() - aPos.getX(), _orRect.Top() - aPos.getY() ); + else if ( bRTL ) + _orRect.Pos( - ( _orRect.Right() - aPos.getX() ), _orRect.Top() - aPos.getY() ); + else + _orRect.Pos( _orRect.Left() - aPos.getX(), _orRect.Top() - aPos.getY() ); + if( _bMirror ) + _orRect.Pos( -_orRect.Right(), _orRect.Top() ); + } +} + +Size SwFEShell::GetGraphicDefaultSize() const +{ + Size aRet; + SwFlyFrame *pFly = GetSelectedFlyFrame(); + if ( pFly ) + { + // #i32951# - due to issue #i28701# no format of a + // newly inserted Writer fly frame or its anchor frame is performed + // any more. Thus, it could be possible (e.g. on insert of a horizontal + // line) that the anchor frame isn't formatted and its printing area + // size is (0,0). If this is the case the printing area of the upper + // of the anchor frame is taken. + const SwFrame* pAnchorFrame = pFly->GetAnchorFrame(); + aRet = pAnchorFrame->getFramePrintArea().SSize(); + if ( aRet.IsEmpty() && pAnchorFrame->GetUpper() ) + { + aRet = pAnchorFrame->GetUpper()->getFramePrintArea().SSize(); + } + + SwRect aBound; + CalcBoundRect( aBound, pFly->GetFormat()->GetAnchor().GetAnchorId()); + if ( pFly->GetAnchorFrame()->IsVertical() ) + aRet.setWidth( aBound.Width() ); + else + aRet.setHeight( aBound.Height() ); + } + return aRet; +} + +bool SwFEShell::IsFrameVertical(const bool bEnvironment, bool& bRTL, bool& bVertL2R) const +{ + bool bVert = false; + bRTL = false; + bVertL2R = false; + + if ( Imp()->HasDrawView() ) + { + const SdrMarkList &rMrkList = Imp()->GetDrawView()->GetMarkedObjectList(); + if( rMrkList.GetMarkCount() != 1 ) + return bVert; + + SdrObject* pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if ( !pObj ) + { + OSL_FAIL( "<SwFEShell::IsFrameVertical(..)> - missing SdrObject instance in marked object list -> This is a serious situation" ); + return bVert; + } + // #i26791# + SwContact* pContact = GetUserCall( pObj ); + if ( !pContact ) + { + OSL_FAIL( "<SwFEShell::IsFrameVertical(..)> - missing SwContact instance at marked object -> This is a serious situation" ); + return bVert; + } + const SwFrame* pRef = pContact->GetAnchoredObj( pObj )->GetAnchorFrame(); + if ( !pRef ) + { + OSL_FAIL( "<SwFEShell::IsFrameVertical(..)> - missing anchor frame at marked object -> This is a serious situation" ); + return bVert; + } + + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) != nullptr && !bEnvironment ) + pRef = static_cast<const SwVirtFlyDrawObj*>(pObj)->GetFlyFrame(); + + bVert = pRef->IsVertical(); + bRTL = pRef->IsRightToLeft(); + bVertL2R = pRef->IsVertLR(); + } + + return bVert; +} + +void SwFEShell::MoveObjectIfActive( svt::EmbeddedObjectRef&, const Point& ) +{ + // does not do anything, only avoids crash if the method is used for wrong shell +} + +void SwFEShell::ToggleHeaderFooterEdit() +{ + // Clear objects selection + if ( Imp()->GetDrawView()->AreObjectsMarked() ) + { + Imp()->GetDrawView()->UnmarkAll(); + ClearMark(); + } + + SwCursorShell::ToggleHeaderFooterEdit(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/frmedt/tblsel.cxx b/sw/source/core/frmedt/tblsel.cxx new file mode 100644 index 000000000..cdcb4451b --- /dev/null +++ b/sw/source/core/frmedt/tblsel.cxx @@ -0,0 +1,2601 @@ +/* -*- 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 <editeng/boxitem.hxx> +#include <editeng/protitem.hxx> + +#include <hintids.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <frmatr.hxx> +#include <tblsel.hxx> +#include <crsrsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <cntfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <rootfrm.hxx> +#include <viscrs.hxx> +#include <swtblfmt.hxx> +#include <UndoTable.hxx> +#include <sectfrm.hxx> +#include <frmtool.hxx> +#include <calbck.hxx> +#include <frameformats.hxx> +#include <deque> +#include <memory> + +// see also swtable.cxx +#define COLFUZZY 20L + +// macros, determining how table boxes are merged: +// - 1. remove empty lines, all boxes separated with blanks, +// all lines separated with ParaBreak +// - 2. remove all empty lines and remove all empty boxes at beginning and end, +// all boxes separated with Blank, +// all lines separated with ParaBreak +// - 3. remove all empty boxes, all boxes separated with blanks, +// all lines separated with ParaBreak + +#undef DEL_ONLY_EMPTY_LINES +#undef DEL_EMPTY_BOXES_AT_START_AND_END + +namespace { + +struct CmpLPt +{ + Point aPos; + const SwTableBox* pSelBox; + bool bVert; + + CmpLPt( const Point& rPt, const SwTableBox* pBox, bool bVertical ); + + bool operator<( const CmpLPt& rCmp ) const + { + if ( bVert ) + return X() > rCmp.X() || ( X() == rCmp.X() && Y() < rCmp.Y() ); + else + return Y() < rCmp.Y() || ( Y() == rCmp.Y() && X() < rCmp.X() ); + } + + long X() const { return aPos.X(); } + long Y() const { return aPos.Y(); } +}; + +} + +typedef o3tl::sorted_vector<CmpLPt> MergePos; + +namespace { + +struct Sort_CellFrame +{ + const SwCellFrame* pFrame; + + explicit Sort_CellFrame( const SwCellFrame& rCFrame ) + : pFrame( &rCFrame ) {} +}; + +} + +static const SwLayoutFrame *lcl_FindCellFrame( const SwLayoutFrame *pLay ) +{ + while ( pLay && !pLay->IsCellFrame() ) + pLay = pLay->GetUpper(); + return pLay; +} + +static const SwLayoutFrame *lcl_FindNextCellFrame( const SwLayoutFrame *pLay ) +{ + // ensure we leave the cell (sections) + const SwLayoutFrame *pTmp = pLay; + do { + pTmp = pTmp->GetNextLayoutLeaf(); + } while( pLay->IsAnLower( pTmp ) ); + + while( pTmp && !pTmp->IsCellFrame() ) + pTmp = pTmp->GetUpper(); + return pTmp; +} + +void GetTableSelCrs( const SwCursorShell &rShell, SwSelBoxes& rBoxes ) +{ + rBoxes.clear(); + if( rShell.IsTableMode() && const_cast<SwCursorShell&>(rShell).UpdateTableSelBoxes()) + { + rBoxes.insert(rShell.GetTableCursor()->GetSelectedBoxes()); + } +} + +void GetTableSelCrs( const SwTableCursor& rTableCursor, SwSelBoxes& rBoxes ) +{ + rBoxes.clear(); + + if (rTableCursor.IsChgd() || !rTableCursor.GetSelectedBoxesCount()) + { + SwTableCursor* pTCursor = const_cast<SwTableCursor*>(&rTableCursor); + pTCursor->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()->MakeTableCursors( *pTCursor ); + } + + if (rTableCursor.GetSelectedBoxesCount()) + { + rBoxes.insert(rTableCursor.GetSelectedBoxes()); + } +} + +void GetTableSel( const SwCursorShell& rShell, SwSelBoxes& rBoxes, + const SwTableSearchType eSearchType ) +{ + // get start and end cell + if ( !rShell.IsTableMode() ) + rShell.GetCursor(); + + GetTableSel( *rShell.getShellCursor(false), rBoxes, eSearchType ); +} + +void GetTableSel( const SwCursor& rCursor, SwSelBoxes& rBoxes, + const SwTableSearchType eSearchType ) +{ + // get start and end cell + OSL_ENSURE( rCursor.GetContentNode() && rCursor.GetContentNode( false ), + "Tabselection not on Cnt." ); + + // Row-selection: + // Check for complex tables. If Yes, search selected boxes via + // the layout. Otherwise via the table structure (for macros !!) + const SwContentNode* pContentNd = rCursor.GetNode().GetContentNode(); + const SwTableNode* pTableNd = pContentNd ? pContentNd->FindTableNode() : nullptr; + if( pTableNd && pTableNd->GetTable().IsNewModel() ) + { + SwTable::SearchType eSearch; + switch( SwTableSearchType::Col & eSearchType ) + { + case SwTableSearchType::Row: eSearch = SwTable::SEARCH_ROW; break; + case SwTableSearchType::Col: eSearch = SwTable::SEARCH_COL; break; + default: eSearch = SwTable::SEARCH_NONE; break; + } + const bool bChkP( SwTableSearchType::Protect & eSearchType ); + pTableNd->GetTable().CreateSelection( rCursor, rBoxes, eSearch, bChkP ); + return; + } + if( SwTableSearchType::Row == ((~SwTableSearchType::Protect ) & eSearchType ) && + pTableNd && !pTableNd->GetTable().IsTableComplex() ) + { + const SwTable& rTable = pTableNd->GetTable(); + const SwTableLines& rLines = rTable.GetTabLines(); + + const SwNode& rMarkNode = rCursor.GetNode( false ); + const sal_uLong nMarkSectionStart = rMarkNode.StartOfSectionIndex(); + const SwTableBox* pMarkBox = rTable.GetTableBox( nMarkSectionStart ); + + OSL_ENSURE( pMarkBox, "Point in table, mark outside?" ); + + const SwTableLine* pLine = pMarkBox ? pMarkBox->GetUpper() : nullptr; + sal_uInt16 nSttPos = rLines.GetPos( pLine ); + OSL_ENSURE( USHRT_MAX != nSttPos, "Where is my row in the table?" ); + pLine = rTable.GetTableBox( rCursor.GetNode().StartOfSectionIndex() )->GetUpper(); + sal_uInt16 nEndPos = rLines.GetPos( pLine ); + OSL_ENSURE( USHRT_MAX != nEndPos, "Where is my row in the table?" ); + // pb: #i20193# if tableintable then nSttPos == nEndPos == USHRT_MAX + if ( nSttPos != USHRT_MAX && nEndPos != USHRT_MAX ) + { + if( nEndPos < nSttPos ) // exchange + { + sal_uInt16 nTmp = nSttPos; nSttPos = nEndPos; nEndPos = nTmp; + } + + bool bChkProtected( SwTableSearchType::Protect & eSearchType ); + for( ; nSttPos <= nEndPos; ++nSttPos ) + { + pLine = rLines[ nSttPos ]; + for( auto n = pLine->GetTabBoxes().size(); n ; ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[ --n ]; + // check for cell protection?? + if( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + rBoxes.insert( pBox ); + } + } + } + } + else + { + Point aPtPos, aMkPos; + const SwShellCursor* pShCursor = dynamic_cast<const SwShellCursor*>(&rCursor); + if( pShCursor ) + { + aPtPos = pShCursor->GetPtPos(); + aMkPos = pShCursor->GetMkPos(); + } + const SwContentNode *pCntNd = rCursor.GetContentNode(); + std::pair<Point, bool> tmp(aPtPos, true); + const SwLayoutFrame *pStart = pCntNd ? + pCntNd->getLayoutFrame(pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp)->GetUpper() : nullptr; + pCntNd = rCursor.GetContentNode(false); + tmp.first = aMkPos; + const SwLayoutFrame *pEnd = pCntNd ? + pCntNd->getLayoutFrame(pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp)->GetUpper() : nullptr; + if( pStart && pEnd ) + GetTableSel( pStart, pEnd, rBoxes, nullptr, eSearchType ); + } +} + +void GetTableSel( const SwLayoutFrame* pStart, const SwLayoutFrame* pEnd, + SwSelBoxes& rBoxes, SwCellFrames* pCells, + const SwTableSearchType eSearchType ) +{ + const SwTabFrame* pStartTab = pStart->FindTabFrame(); + if ( !pStartTab ) + { + OSL_FAIL( "GetTableSel without start table" ); + return; + } + + bool bChkProtected( SwTableSearchType::Protect & eSearchType ); + + // #i55421# Reduced value 10 + int nLoopMax = 10; + + do { + bool bTableIsValid = true; + + // First, compute tables and rectangles + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd, eSearchType ); + + Point aCurrentTopLeft( LONG_MAX, LONG_MAX ); + Point aCurrentTopRight( 0, LONG_MAX ); + Point aCurrentBottomLeft( LONG_MAX, 0 ); + Point aCurrentBottomRight( 0, 0 ); + const SwCellFrame* pCurrentTopLeftFrame = nullptr; + const SwCellFrame* pCurrentTopRightFrame = nullptr; + const SwCellFrame* pCurrentBottomLeftFrame = nullptr; + const SwCellFrame* pCurrentBottomRightFrame = nullptr; + + // Now find boxes for each entry and emit + for (size_t i = 0; i < aUnions.size() && bTableIsValid; ++i) + { + SwSelUnion *pUnion = &aUnions[i]; + const SwTabFrame *pTable = pUnion->GetTable(); + + if( !pTable->isFrameAreaDefinitionValid() && nLoopMax ) + { + bTableIsValid = false; + break; + } + + // Skip any repeated headlines in the follow: + const SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTable->Lower()); + + while( pRow && bTableIsValid ) + { + if( !pRow->isFrameAreaDefinitionValid() && nLoopMax ) + { + bTableIsValid = false; + break; + } + + if ( pRow->getFrameArea().IsOver( pUnion->GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while (pCell && pRow->IsAnLower(pCell)) + { + if( !pCell->isFrameAreaDefinitionValid() && nLoopMax ) + { + bTableIsValid = false; + break; + } + + OSL_ENSURE( pCell->IsCellFrame(), "Frame without Cell" ); + if( ::IsFrameInTableSel( pUnion->GetUnion(), pCell ) ) + { + SwTableBox* pBox = const_cast<SwTableBox*>( + static_cast<const SwCellFrame*>(pCell)->GetTabBox()); + // check for cell protection?? + if( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + rBoxes.insert( pBox ); + + if ( pCells ) + { + const Point aTopLeft( pCell->getFrameArea().TopLeft() ); + const Point aTopRight( pCell->getFrameArea().TopRight() ); + const Point aBottomLeft( pCell->getFrameArea().BottomLeft() ); + const Point aBottomRight( pCell->getFrameArea().BottomRight() ); + + if ( aTopLeft.getY() < aCurrentTopLeft.getY() || + ( aTopLeft.getY() == aCurrentTopLeft.getY() && + aTopLeft.getX() < aCurrentTopLeft.getX() ) ) + { + aCurrentTopLeft = aTopLeft; + pCurrentTopLeftFrame = static_cast<const SwCellFrame*>( pCell ); + } + + if ( aTopRight.getY() < aCurrentTopRight.getY() || + ( aTopRight.getY() == aCurrentTopRight.getY() && + aTopRight.getX() > aCurrentTopRight.getX() ) ) + { + aCurrentTopRight = aTopRight; + pCurrentTopRightFrame = static_cast<const SwCellFrame*>( pCell ); + } + + if ( aBottomLeft.getY() > aCurrentBottomLeft.getY() || + ( aBottomLeft.getY() == aCurrentBottomLeft.getY() && + aBottomLeft.getX() < aCurrentBottomLeft.getX() ) ) + { + aCurrentBottomLeft = aBottomLeft; + pCurrentBottomLeftFrame = static_cast<const SwCellFrame*>( pCell ); + } + + if ( aBottomRight.getY() > aCurrentBottomRight.getY() || + ( aBottomRight.getY() == aCurrentBottomRight.getY() && + aBottomRight.getX() > aCurrentBottomRight.getX() ) ) + { + aCurrentBottomRight = aBottomRight; + pCurrentBottomRightFrame = static_cast<const SwCellFrame*>( pCell ); + } + + } + } + if ( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + pCell = ::lcl_FindNextCellFrame( pCell ); + } + } + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + } + } + + if ( pCells ) + { + pCells->clear(); + pCells->push_back( const_cast< SwCellFrame* >(pCurrentTopLeftFrame) ); + pCells->push_back( const_cast< SwCellFrame* >(pCurrentTopRightFrame) ); + pCells->push_back( const_cast< SwCellFrame* >(pCurrentBottomLeftFrame) ); + pCells->push_back( const_cast< SwCellFrame* >(pCurrentBottomRightFrame) ); + } + + if( bTableIsValid ) + break; + + SwDeletionChecker aDelCheck( pStart ); + + // otherwise quickly "calculate" the table layout and start over + SwTabFrame *pTable = aUnions.front().GetTable(); + while( pTable ) + { + if( pTable->isFrameAreaDefinitionValid() ) + { + pTable->InvalidatePos(); + } + + pTable->SetONECalcLowers(); + pTable->Calc(pTable->getRootFrame()->GetCurrShell()->GetOut()); + pTable->SetCompletePaint(); + + if( nullptr == (pTable = pTable->GetFollow()) ) + break; + } + + // --> Make code robust, check if pStart has + // been deleted due to the formatting of the table: + if ( aDelCheck.HasBeenDeleted() ) + { + OSL_FAIL( "Current box has been deleted during GetTableSel()" ); + break; + } + + rBoxes.clear(); + --nLoopMax; + + } while( true ); + OSL_ENSURE( nLoopMax, "Table layout is still invalid!" ); +} + +bool ChkChartSel( const SwNode& rSttNd, const SwNode& rEndNd ) +{ + const SwTableNode* pTNd = rSttNd.FindTableNode(); + if( !pTNd ) + return false; + + Point aNullPos; + SwNodeIndex aIdx( rSttNd ); + const SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNextSection( &aIdx, false, false ); + + // if table is invisible, return + // (layout needed for forming table selection further down, so we can't + // continue with invisible tables) + // #i22135# - Also the content of the table could be + // invisible - e.g. in a hidden section + // Robust: check, if content was found (e.g. empty table cells) + if ( !pCNd || pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) == nullptr ) + return false; + + std::pair<Point, bool> tmp(aNullPos, true); + const SwLayoutFrame *const pStart = pCNd->getLayoutFrame( + pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + OSL_ENSURE( pStart, "without frame nothing works" ); + + aIdx = rEndNd; + pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNextSection( &aIdx, false, false ); + + // #i22135# - Robust: check, if content was found and if it's visible + if ( !pCNd || pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) == nullptr ) + { + return false; + } + + const SwLayoutFrame *const pEnd = pCNd->getLayoutFrame( + pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + OSL_ENSURE( pEnd, "without frame nothing works" ); + + bool bValidChartSel; + // #i55421# Reduced value 10 + int nLoopMax = 10; //JP 28.06.99: max 100 loops - Bug 67292 + + do { + bool bTableIsValid = true; + bValidChartSel = true; + + sal_uInt16 nRowCells = USHRT_MAX; + + // First, compute tables and rectangles + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd, SwTableSearchType::NoUnionCorrect ); + + // find boxes for each entry and emit + for( auto & rSelUnion : aUnions ) + { + if (!bTableIsValid || !bValidChartSel) + break; + + SwSelUnion *pUnion = &rSelUnion; + const SwTabFrame *pTable = pUnion->GetTable(); + + SwRectFnSet aRectFnSet(pTable); + bool bRTL = pTable->IsRightToLeft(); + + if( !pTable->isFrameAreaDefinitionValid() && nLoopMax ) + { + bTableIsValid = false; + break; + } + + std::deque< Sort_CellFrame > aCellFrames; + + // Skip any repeated headlines in the follow: + const SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTable->Lower()); + + while( pRow && bTableIsValid && bValidChartSel ) + { + if( !pRow->isFrameAreaDefinitionValid() && nLoopMax ) + { + bTableIsValid = false; + break; + } + + if( pRow->getFrameArea().IsOver( pUnion->GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while (pCell && pRow->IsAnLower(pCell)) + { + if( !pCell->isFrameAreaDefinitionValid() && nLoopMax ) + { + bTableIsValid = false; + break; + } + + OSL_ENSURE( pCell->IsCellFrame(), "Frame without Cell" ); + const SwRect& rUnion = pUnion->GetUnion(), + & rFrameRect = pCell->getFrameArea(); + + const long nUnionRight = rUnion.Right(); + const long nUnionBottom = rUnion.Bottom(); + const long nFrameRight = rFrameRect.Right(); + const long nFrameBottom = rFrameRect.Bottom(); + + // ignore if FrameRect is outside the union + + const long nXFuzzy = aRectFnSet.IsVert() ? 0 : 20; + const long nYFuzzy = aRectFnSet.IsVert() ? 20 : 0; + + if( !( rUnion.Top() + nYFuzzy > nFrameBottom || + nUnionBottom < rFrameRect.Top() + nYFuzzy || + rUnion.Left() + nXFuzzy > nFrameRight || + nUnionRight < rFrameRect.Left() + nXFuzzy )) + { + // ok, rUnion is _not_ completely outside of rFrameRect + + // if not completely inside the union, then + // for Chart it is an invalid selection + if( rUnion.Left() <= rFrameRect.Left() + nXFuzzy && + rFrameRect.Left() <= nUnionRight && + rUnion.Left() <= nFrameRight && + nFrameRight <= nUnionRight + nXFuzzy && + rUnion.Top() <= rFrameRect.Top() + nYFuzzy && + rFrameRect.Top() <= nUnionBottom && + rUnion.Top() <= nFrameBottom && + nFrameBottom <= nUnionBottom+ nYFuzzy ) + + aCellFrames.emplace_back( *static_cast<const SwCellFrame*>(pCell) ); + else + { + bValidChartSel = false; + break; + } + } + if ( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + pCell = ::lcl_FindNextCellFrame( pCell ); + } + } + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + } + + if( !bValidChartSel ) + break; + + // all cells of the (part) table together. Now check if + // they're all adjacent + size_t n; + sal_uInt16 nCellCnt = 0; + long nYPos = LONG_MAX; + long nXPos = 0; + long nHeight = 0; + + for( n = 0 ; n < aCellFrames.size(); ++n ) + { + const Sort_CellFrame& rCF = aCellFrames[ n ]; + if( aRectFnSet.GetTop(rCF.pFrame->getFrameArea()) != nYPos ) + { + // new row + if( n ) + { + if( USHRT_MAX == nRowCells ) // 1. row change + nRowCells = nCellCnt; + else if( nRowCells != nCellCnt ) + { + bValidChartSel = false; + break; + } + } + nCellCnt = 1; + nYPos = aRectFnSet.GetTop(rCF.pFrame->getFrameArea()); + nHeight = aRectFnSet.GetHeight(rCF.pFrame->getFrameArea()); + + nXPos = bRTL ? + aRectFnSet.GetLeft(rCF.pFrame->getFrameArea()) : + aRectFnSet.GetRight(rCF.pFrame->getFrameArea()); + } + else if( nXPos == ( bRTL ? + aRectFnSet.GetRight(rCF.pFrame->getFrameArea()) : + aRectFnSet.GetLeft(rCF.pFrame->getFrameArea()) ) && + nHeight == aRectFnSet.GetHeight(rCF.pFrame->getFrameArea()) ) + { + nXPos += ( bRTL ? -1 : 1 ) * + aRectFnSet.GetWidth(rCF.pFrame->getFrameArea()); + ++nCellCnt; + } + else + { + bValidChartSel = false; + break; + } + } + if( bValidChartSel ) + { + if( USHRT_MAX == nRowCells ) + nRowCells = nCellCnt; + else if( nRowCells != nCellCnt ) + bValidChartSel = false; + } + } + + if( bTableIsValid ) + break; + + // otherwise quickly "calculate" table layout and start over + SwTabFrame *pTable = aUnions.front().GetTable(); + + for( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + if( pTable->isFrameAreaDefinitionValid() ) + { + pTable->InvalidatePos(); + } + + pTable->SetONECalcLowers(); + pTable->Calc(pTable->getRootFrame()->GetCurrShell()->GetOut()); + pTable->SetCompletePaint(); + + if( nullptr == (pTable = pTable->GetFollow()) ) + break; + } + --nLoopMax; + } while( true ); + + OSL_ENSURE( nLoopMax, "table layout is still invalid!" ); + + return bValidChartSel; +} + +bool IsFrameInTableSel( const SwRect& rUnion, const SwFrame* pCell ) +{ + OSL_ENSURE( pCell->IsCellFrame(), "Frame without Gazelle" ); + + if( pCell->FindTabFrame()->IsVertical() ) + return rUnion.Right() >= pCell->getFrameArea().Right() && + rUnion.Left() <= pCell->getFrameArea().Left() && + (( rUnion.Top() <= pCell->getFrameArea().Top()+20 && + rUnion.Bottom() > pCell->getFrameArea().Top() ) || + ( rUnion.Top() >= pCell->getFrameArea().Top() && + rUnion.Bottom() < pCell->getFrameArea().Bottom() )); + + return + rUnion.Top() <= pCell->getFrameArea().Top() && + rUnion.Bottom() >= pCell->getFrameArea().Bottom() && + + (( rUnion.Left() <= pCell->getFrameArea().Left()+20 && + rUnion.Right() > pCell->getFrameArea().Left() ) || + + ( rUnion.Left() >= pCell->getFrameArea().Left() && + rUnion.Right() < pCell->getFrameArea().Right() )); +} + +bool GetAutoSumSel( const SwCursorShell& rShell, SwCellFrames& rBoxes ) +{ + SwShellCursor* pCursor = rShell.m_pCurrentCursor; + if ( rShell.IsTableMode() ) + pCursor = rShell.m_pTableCursor; + + std::pair<Point, bool> tmp(pCursor->GetPtPos(), true); + const SwLayoutFrame *const pStart = pCursor->GetContentNode()->getLayoutFrame( + rShell.GetLayout(), nullptr, &tmp)->GetUpper(); + tmp.first = pCursor->GetMkPos(); + const SwLayoutFrame *const pEnd = pCursor->GetContentNode(false)->getLayoutFrame( + rShell.GetLayout(), nullptr, &tmp)->GetUpper(); + + const SwLayoutFrame* pSttCell = pStart; + while( pSttCell && !pSttCell->IsCellFrame() ) + pSttCell = pSttCell->GetUpper(); + + // First, compute tables and rectangles + SwSelUnions aUnions; + + // by default, first test above and then to the left + ::MakeSelUnions( aUnions, pStart, pEnd, SwTableSearchType::Col ); + + bool bTstRow = true, bFound = false; + + // 1. check if box above contains value/formula + for( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + SwSelUnion *pUnion = &aUnions[i]; + const SwTabFrame *pTable = pUnion->GetTable(); + + // Skip any repeated headlines in the follow: + const SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTable->Lower()); + + while( pRow ) + { + if( pRow->getFrameArea().IsOver( pUnion->GetUnion() ) ) + { + const SwCellFrame* pUpperCell = nullptr; + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while( pCell && pRow->IsAnLower( pCell ) ) + { + if( pCell == pSttCell ) + { + sal_uInt16 nWhichId = 0; + for( size_t n = rBoxes.size(); n; ) + if( USHRT_MAX != ( nWhichId = rBoxes[ --n ] + ->GetTabBox()->IsFormulaOrValueBox() )) + break; + + // all boxes together, do not check the + // row, if a formula or value was found + bTstRow = 0 == nWhichId || USHRT_MAX == nWhichId; + bFound = true; + break; + } + + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + if( ::IsFrameInTableSel( pUnion->GetUnion(), pCell ) ) + pUpperCell = static_cast<const SwCellFrame*>(pCell); + + if( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + pCell = ::lcl_FindNextCellFrame( pCell ); + } + + if( pUpperCell ) + rBoxes.push_back( const_cast< SwCellFrame* >(pUpperCell) ); + } + if( bFound ) + { + i = aUnions.size(); + break; + } + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + } + } + + // 2. check if box on left contains value/formula + if( bTstRow ) + { + bFound = false; + + rBoxes.clear(); + aUnions.clear(); + ::MakeSelUnions( aUnions, pStart, pEnd, SwTableSearchType::Row ); + + for( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + SwSelUnion *pUnion = &aUnions[i]; + const SwTabFrame *pTable = pUnion->GetTable(); + + // Skip any repeated headlines in the follow: + const SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTable->Lower()); + + while( pRow ) + { + if( pRow->getFrameArea().IsOver( pUnion->GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while( pCell && pRow->IsAnLower( pCell ) ) + { + if( pCell == pSttCell ) + { + sal_uInt16 nWhichId = 0; + for( size_t n = rBoxes.size(); n; ) + if( USHRT_MAX != ( nWhichId = rBoxes[ --n ] + ->GetTabBox()->IsFormulaOrValueBox() )) + break; + + // all boxes together, do not check the + // row if a formula or value was found + bFound = 0 != nWhichId && USHRT_MAX != nWhichId; + bTstRow = false; + break; + } + + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + if( ::IsFrameInTableSel( pUnion->GetUnion(), pCell ) ) + { + SwCellFrame* pC = const_cast<SwCellFrame*>(static_cast<const SwCellFrame*>(pCell)); + rBoxes.push_back( pC ); + } + if( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + pCell = ::lcl_FindNextCellFrame( pCell ); + } + } + if( !bTstRow ) + { + i = aUnions.size(); + break; + } + + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + } + } + } + + return bFound; +} + +bool HasProtectedCells( const SwSelBoxes& rBoxes ) +{ + bool bRet = false; + for (size_t n = 0; n < rBoxes.size(); ++n) + { + if( rBoxes[ n ]->GetFrameFormat()->GetProtect().IsContentProtected() ) + { + bRet = true; + break; + } + } + return bRet; +} + +CmpLPt::CmpLPt( const Point& rPt, const SwTableBox* pBox, bool bVertical ) + : aPos( rPt ), pSelBox( pBox ), bVert( bVertical ) +{} + +static void lcl_InsTableBox( SwTableNode* pTableNd, SwDoc* pDoc, SwTableBox* pBox, + sal_uInt16 nInsPos, sal_uInt16 nCnt = 1 ) +{ + OSL_ENSURE( pBox->GetSttNd(), "Box without Start-Node" ); + SwContentNode* pCNd = pDoc->GetNodes()[ pBox->GetSttIdx() + 1 ] + ->GetContentNode(); + if( pCNd && pCNd->IsTextNode() ) + pDoc->GetNodes().InsBoxen( pTableNd, pBox->GetUpper(), + static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()), + static_cast<SwTextNode*>(pCNd)->GetTextColl(), + pCNd->GetpSwAttrSet(), + nInsPos, nCnt ); + else + pDoc->GetNodes().InsBoxen( pTableNd, pBox->GetUpper(), + static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()), + pDoc->GetDfltTextFormatColl(), nullptr, + nInsPos, nCnt ); +} + +bool IsEmptyBox( const SwTableBox& rBox, SwPaM& rPam ) +{ + rPam.GetPoint()->nNode = *rBox.GetSttNd()->EndOfSectionNode(); + rPam.Move( fnMoveBackward, GoInContent ); + rPam.SetMark(); + rPam.GetPoint()->nNode = *rBox.GetSttNd(); + rPam.Move( fnMoveForward, GoInContent ); + bool bRet = *rPam.GetMark() == *rPam.GetPoint() + && ( rBox.GetSttNd()->GetIndex() + 1 == rPam.GetPoint()->nNode.GetIndex() ); + + if( bRet ) + { + // now check for paragraph bound flies + const SwFrameFormats& rFormats = *rPam.GetDoc()->GetSpzFrameFormats(); + sal_uLong nSttIdx = rPam.GetPoint()->nNode.GetIndex(), + nEndIdx = rBox.GetSttNd()->EndOfSectionIndex(), + nIdx; + + for( auto pFormat : rFormats ) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + const SwPosition* pAPos = rAnchor.GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) && + nSttIdx <= ( nIdx = pAPos->nNode.GetIndex() ) && + nIdx < nEndIdx ) + { + bRet = false; + break; + } + } + } + return bRet; +} + +void GetMergeSel( const SwPaM& rPam, SwSelBoxes& rBoxes, + SwTableBox** ppMergeBox, SwUndoTableMerge* pUndo ) +{ + rBoxes.clear(); + + OSL_ENSURE( rPam.GetContentNode() && rPam.GetContentNode( false ), + "Tabselection not on Cnt." ); + +//JP 24.09.96: Merge with repeating TableHeadLines does not work properly. +// Why not use point 0,0? Then it is assured the first +// headline is contained. + Point aPt( 0, 0 ); + + const SwContentNode* pCntNd = rPam.GetContentNode(); + std::pair<Point, bool> const tmp(aPt, true); + const SwLayoutFrame *const pStart = pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + pCntNd = rPam.GetContentNode(false); + const SwLayoutFrame *const pEnd = pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + + // First, compute tables and rectangles + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + if( aUnions.empty() ) + return; + + const SwTable *pTable = aUnions.front().GetTable()->GetTable(); + SwDoc* pDoc = const_cast<SwDoc*>(pStart->GetFormat()->GetDoc()); + SwTableNode* pTableNd = const_cast<SwTableNode*>(pTable->GetTabSortBoxes()[ 0 ]-> + GetSttNd()->FindTableNode()); + + MergePos aPosArr; // Sort-Array with the frame positions + long nWidth; + SwTableBox* pLastBox = nullptr; + + SwRectFnSet aRectFnSet(pStart->GetUpper()); + + for ( auto & rSelUnion : aUnions ) + { + const SwTabFrame *pTabFrame = rSelUnion.GetTable(); + + SwRect &rUnion = rSelUnion.GetUnion(); + + // Skip any repeated headlines in the follow: + const SwLayoutFrame* pRow = pTabFrame->IsFollow() ? + pTabFrame->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTabFrame->Lower()); + + while ( pRow ) + { + if ( pRow->getFrameArea().IsOver( rUnion ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while ( pCell && pRow->IsAnLower( pCell ) ) + { + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + // overlap in full width? + if( rUnion.Top() <= pCell->getFrameArea().Top() && + rUnion.Bottom() >= pCell->getFrameArea().Bottom() ) + { + SwTableBox* pBox = const_cast<SwTableBox*>(static_cast<const SwCellFrame*>(pCell)->GetTabBox()); + + // only overlap to the right? + if( ( rUnion.Left() - COLFUZZY ) <= pCell->getFrameArea().Left() && + ( rUnion.Right() - COLFUZZY ) > pCell->getFrameArea().Left() ) + { + if( ( rUnion.Right() + COLFUZZY ) < pCell->getFrameArea().Right() ) + { + sal_uInt16 nInsPos = pBox->GetUpper()->GetBoxPos( pBox )+1; + lcl_InsTableBox( pTableNd, pDoc, pBox, nInsPos ); + pBox->ClaimFrameFormat(); + SwFormatFrameSize aNew( + pBox->GetFrameFormat()->GetFrameSize() ); + nWidth = rUnion.Right() - pCell->getFrameArea().Left(); + nWidth = nWidth * aNew.GetWidth() / + pCell->getFrameArea().Width(); + long nTmpWidth = aNew.GetWidth() - nWidth; + aNew.SetWidth( nWidth ); + pBox->GetFrameFormat()->SetFormatAttr( aNew ); + // this box is selected + pLastBox = pBox; + rBoxes.insert( pBox ); + aPosArr.insert( + CmpLPt( aRectFnSet.GetPos(pCell->getFrameArea()), + pBox, aRectFnSet.IsVert() ) ); + + pBox = pBox->GetUpper()->GetTabBoxes()[ nInsPos ]; + aNew.SetWidth( nTmpWidth ); + pBox->ClaimFrameFormat(); + pBox->GetFrameFormat()->SetFormatAttr( aNew ); + + if( pUndo ) + pUndo->AddNewBox( pBox->GetSttIdx() ); + } + else + { + // this box is selected + pLastBox = pBox; + rBoxes.insert( pBox ); + aPosArr.insert( + CmpLPt( aRectFnSet.GetPos(pCell->getFrameArea()), + pBox, aRectFnSet.IsVert() ) ); + } + } + // overlapping on left- or right-side + else if( ( rUnion.Left() - COLFUZZY ) >= pCell->getFrameArea().Left() && + ( rUnion.Right() + COLFUZZY ) < pCell->getFrameArea().Right() ) + { + sal_uInt16 nInsPos = pBox->GetUpper()->GetBoxPos( pBox )+1; + lcl_InsTableBox( pTableNd, pDoc, pBox, nInsPos, 2 ); + pBox->ClaimFrameFormat(); + SwFormatFrameSize aNew( + pBox->GetFrameFormat()->GetFrameSize() ); + long nLeft = rUnion.Left() - pCell->getFrameArea().Left(); + nLeft = nLeft * aNew.GetWidth() / + pCell->getFrameArea().Width(); + long nRight = pCell->getFrameArea().Right() - rUnion.Right(); + nRight = nRight * aNew.GetWidth() / + pCell->getFrameArea().Width(); + nWidth = aNew.GetWidth() - nLeft - nRight; + + aNew.SetWidth( nLeft ); + pBox->GetFrameFormat()->SetFormatAttr( aNew ); + + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pBox->GetFrameFormat()->GetAttrSet() + .GetItemState( RES_BOX, false, &pItem )) + { + SvxBoxItem aBox( *static_cast<const SvxBoxItem*>(pItem) ); + aBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + pBox->GetFrameFormat()->SetFormatAttr( aBox ); + } + } + + pBox = pBox->GetUpper()->GetTabBoxes()[ nInsPos ]; + aNew.SetWidth( nWidth ); + pBox->ClaimFrameFormat(); + pBox->GetFrameFormat()->SetFormatAttr( aNew ); + + if( pUndo ) + pUndo->AddNewBox( pBox->GetSttIdx() ); + + // this box is selected + pLastBox = pBox; + rBoxes.insert( pBox ); + aPosArr.insert( + CmpLPt( aRectFnSet.GetPos(pCell->getFrameArea()), + pBox, aRectFnSet.IsVert() ) ); + + pBox = pBox->GetUpper()->GetTabBoxes()[ nInsPos+1 ]; + aNew.SetWidth( nRight ); + pBox->ClaimFrameFormat(); + pBox->GetFrameFormat()->SetFormatAttr( aNew ); + + if( pUndo ) + pUndo->AddNewBox( pBox->GetSttIdx() ); + } + // is right side of box part of the selected area? + else if( ( pCell->getFrameArea().Right() - COLFUZZY ) < rUnion.Right() && + ( pCell->getFrameArea().Right() - COLFUZZY ) > rUnion.Left() && + ( pCell->getFrameArea().Left() + COLFUZZY ) < rUnion.Left() ) + { + // then we should insert a new box and adjust the widths + sal_uInt16 nInsPos = pBox->GetUpper()->GetBoxPos( pBox )+1; + lcl_InsTableBox( pTableNd, pDoc, pBox, nInsPos ); + + SwFormatFrameSize aNew(pBox->GetFrameFormat()->GetFrameSize() ); + long nLeft = rUnion.Left() - pCell->getFrameArea().Left(), + nRight = pCell->getFrameArea().Right() - rUnion.Left(); + + nLeft = nLeft * aNew.GetWidth() / + pCell->getFrameArea().Width(); + nRight = nRight * aNew.GetWidth() / + pCell->getFrameArea().Width(); + + aNew.SetWidth( nLeft ); + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + + // this box is selected + pBox = pBox->GetUpper()->GetTabBoxes()[ nInsPos ]; + aNew.SetWidth( nRight ); + pBox->ClaimFrameFormat(); + pBox->GetFrameFormat()->SetFormatAttr( aNew ); + + pLastBox = pBox; + rBoxes.insert( pBox ); + aPosArr.insert( CmpLPt( Point( rUnion.Left(), + pCell->getFrameArea().Top()), pBox, aRectFnSet.IsVert() )); + + if( pUndo ) + pUndo->AddNewBox( pBox->GetSttIdx() ); + } + } + if ( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + // --> Check if table cell is not empty + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + pCell = ::lcl_FindNextCellFrame( pCell ); + } + } + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + } + } + + // no SSelection / no boxes found + if( 1 >= rBoxes.size() ) + return; + + // now search all horizontally adjacent boxes and connect + // their contents with blanks. All vertically adjacent will be tied + // together as paragraphs + + // 1. Solution: map array and all on same Y-level + // are separated with blanks + // all others are separated with paragraphs + bool bCalcWidth = true; + const SwTableBox* pFirstBox = aPosArr[ 0 ].pSelBox; + + // JP 27.03.98: Optimise - if boxes on one row are empty, + // then do not insert blanks or carriage returns + //Block to assure SwPaM, SwPosition are deleted from stack + { + SwPaM aPam( pDoc->GetNodes() ); + +#if defined( DEL_ONLY_EMPTY_LINES ) + nWidth = pFirstBox->GetFrameFormat()->GetFrameSize().GetWidth(); + bool bEmptyLine = sal_True; + sal_uInt16 n, nSttPos = 0; + + for( n = 0; n < aPosArr.Count(); ++n ) + { + const CmpLPt& rPt = aPosArr[ n ]; + if( n && aPosArr[ n - 1 ].Y() == rPt.Y() ) // same Y level? + { + if( bEmptyLine && !IsEmptyBox( *rPt.pSelBox, aPam )) + bEmptyLine = sal_False; + if( bCalcWidth ) + nWidth += rPt.pSelBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + else + { + if( bCalcWidth && n ) + bCalcWidth = false; // one line is ready + + if( bEmptyLine && nSttPos < n ) + { + // now complete line is empty and should not + // be filled with blanks and be inserted as paragraph + if( pUndo ) + for( sal_uInt16 i = nSttPos; i < n; ++i ) + pUndo->SaveCollection( *aPosArr[ i ].pSelBox ); + + aPosArr.Remove( nSttPos, n - nSttPos ); + n = nSttPos; + } + else + nSttPos = n; + + bEmptyLine = IsEmptyBox( *aPosArr[n].pSelBox, aPam ); + } + } + if( bEmptyLine && nSttPos < n ) + { + if( pUndo ) + for( sal_uInt16 i = nSttPos; i < n; ++i ) + pUndo->SaveCollection( *aPosArr[ i ].pSelBox ); + aPosArr.Remove( nSttPos, n - nSttPos ); + } +#elif defined( DEL_EMPTY_BOXES_AT_START_AND_END ) + + nWidth = pFirstBox->GetFrameFormat()->GetFrameSize().GetWidth(); + sal_uInt16 n, nSttPos = 0, nSEndPos = 0, nESttPos = 0; + + for( n = 0; n < aPosArr.Count(); ++n ) + { + const CmpLPt& rPt = aPosArr[ n ]; + if( n && aPosArr[ n - 1 ].Y() == rPt.Y() ) // same Y level? + { + bool bEmptyBox = IsEmptyBox( *rPt.pSelBox, aPam ); + if( bEmptyBox ) + { + if( nSEndPos == n ) // beginning is empty + nESttPos = ++nSEndPos; + } + else // end could be empty + nESttPos = n+1; + + if( bCalcWidth ) + nWidth += rPt.pSelBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + else + { + if( bCalcWidth && n ) + bCalcWidth = false; // one line ready + + // first those at the beginning + if( nSttPos < nSEndPos ) + { + // now the beginning of the line is empty and should + // not be filled with blanks + if( pUndo ) + for( sal_uInt16 i = nSttPos; i < nSEndPos; ++i ) + pUndo->SaveCollection( *aPosArr[ i ].pSelBox ); + + sal_uInt16 nCnt = nSEndPos - nSttPos; + aPosArr.Remove( nSttPos, nCnt ); + nESttPos -= nCnt; + n -= nCnt; + } + + if( nESttPos < n ) + { + // now the beginning of the line is empty and should + // not be filled with blanks + if( pUndo ) + for( sal_uInt16 i = nESttPos; i < n; ++i ) + pUndo->SaveCollection( *aPosArr[ i ].pSelBox ); + + sal_uInt16 nCnt = n - nESttPos; + aPosArr.Remove( nESttPos, nCnt ); + n -= nCnt; + } + + nSttPos = nSEndPos = nESttPos = n; + if( IsEmptyBox( *aPosArr[n].pSelBox, aPam )) + ++nSEndPos; + else + ++nESttPos; + } + } + + // first those at the beginning + if( nSttPos < nSEndPos ) + { + // now the beginning of the line is empty and should + // not be filled with blanks + if( pUndo ) + for( sal_uInt16 i = nSttPos; i < nSEndPos; ++i ) + pUndo->SaveCollection( *aPosArr[ i ].pSelBox ); + + sal_uInt16 nCnt = nSEndPos - nSttPos; + aPosArr.Remove( nSttPos, nCnt ); + nESttPos -= nCnt; + n -= nCnt; + } + if( nESttPos < n ) + { + // now the beginning of the line is empty and should + // not be filled with blanks + if( pUndo ) + for( sal_uInt16 i = nESttPos; i < n; ++i ) + pUndo->SaveCollection( *aPosArr[ i ].pSelBox ); + + sal_uInt16 nCnt = n - nESttPos; + aPosArr.Remove( nESttPos, nCnt ); + } +#else +// DEL_ALL_EMPTY_BOXES + + nWidth = 0; + long nY = !aPosArr.empty() ? + ( aRectFnSet.IsVert() ? + aPosArr[ 0 ].X() : + aPosArr[ 0 ].Y() ) : + 0; + + for( MergePos::size_type n = 0; n < aPosArr.size(); ++n ) + { + const CmpLPt& rPt = aPosArr[ n ]; + if( bCalcWidth ) + { + if( nY == ( aRectFnSet.IsVert() ? rPt.X() : rPt.Y() ) ) // same Y level? + nWidth += rPt.pSelBox->GetFrameFormat()->GetFrameSize().GetWidth(); + else + bCalcWidth = false; // one line ready + } + + if( IsEmptyBox( *rPt.pSelBox, aPam ) ) + { + if( pUndo ) + pUndo->SaveCollection( *rPt.pSelBox ); + + aPosArr.erase( aPosArr.begin() + n ); + --n; + } + } +#endif + } + + // first create new box + { + SwTableBox* pTmpBox = rBoxes[0]; + SwTableLine* pInsLine = pTmpBox->GetUpper(); + sal_uInt16 nInsPos = pInsLine->GetBoxPos( pTmpBox ); + + lcl_InsTableBox( pTableNd, pDoc, pTmpBox, nInsPos ); + (*ppMergeBox) = pInsLine->GetTabBoxes()[ nInsPos ]; + pInsLine->GetTabBoxes().erase( pInsLine->GetTabBoxes().begin() + nInsPos ); // remove again + (*ppMergeBox)->SetUpper( nullptr ); + (*ppMergeBox)->ClaimFrameFormat(); + + // define the border: the upper/left side of the first box, + // the lower/right side of the last box: + if( pLastBox && pFirstBox ) + { + SvxBoxItem aBox( pFirstBox->GetFrameFormat()->GetBox() ); + const SvxBoxItem& rBox = pLastBox->GetFrameFormat()->GetBox(); + aBox.SetLine( rBox.GetRight(), SvxBoxItemLine::RIGHT ); + aBox.SetLine( rBox.GetBottom(), SvxBoxItemLine::BOTTOM ); + if( aBox.GetLeft() || aBox.GetTop() || + aBox.GetRight() || aBox.GetBottom() ) + (*ppMergeBox)->GetFrameFormat()->SetFormatAttr( aBox ); + } + } + + //Block to delete SwPaM, SwPosition from stack + if( !aPosArr.empty() ) + { + SwPosition aInsPos( *(*ppMergeBox)->GetSttNd() ); + SwNodeIndex& rInsPosNd = aInsPos.nNode; + + SwPaM aPam( aInsPos ); + + for( const auto &rPt : aPosArr ) + { + aPam.GetPoint()->nNode.Assign( *rPt.pSelBox->GetSttNd()-> + EndOfSectionNode(), -1 ); + SwContentNode* pCNd = aPam.GetContentNode(); + aPam.GetPoint()->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + + SwNodeIndex aSttNdIdx( *rPt.pSelBox->GetSttNd(), 1 ); + // one node should be kept in the box (otherwise the + // section would be deleted during a move) + bool const bUndo(pDoc->GetIDocumentUndoRedo().DoesUndo()); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().DoUndo(false); + } + pDoc->getIDocumentContentOperations().AppendTextNode( *aPam.GetPoint() ); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().DoUndo(bUndo); + } + SwNodeRange aRg( aSttNdIdx, aPam.GetPoint()->nNode ); + ++rInsPosNd; + if( pUndo ) + pUndo->MoveBoxContent( pDoc, aRg, rInsPosNd ); + else + { + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, rInsPosNd, + SwMoveFlags::DEFAULT ); + } + // where is now aInsPos ?? + + if( bCalcWidth ) + bCalcWidth = false; // one line is ready + + // skip the first TextNode + rInsPosNd.Assign( pDoc->GetNodes(), + rInsPosNd.GetNode().EndOfSectionIndex() - 2 ); + SwTextNode* pTextNd = rInsPosNd.GetNode().GetTextNode(); + if( pTextNd ) + aInsPos.nContent.Assign(pTextNd, pTextNd->GetText().getLength()); + } + + // the MergeBox should contain the complete text + // now erase the initial TextNode + OSL_ENSURE( (*ppMergeBox)->GetSttIdx()+2 < + (*ppMergeBox)->GetSttNd()->EndOfSectionIndex(), + "empty box" ); + SwNodeIndex aIdx( *(*ppMergeBox)->GetSttNd()->EndOfSectionNode(), -1 ); + pDoc->GetNodes().Delete( aIdx ); + } + + // set width of the box + (*ppMergeBox)->GetFrameFormat()->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth, 0 )); + if( pUndo ) + pUndo->AddNewBox( (*ppMergeBox)->GetSttIdx() ); +} + +static bool lcl_CheckCol(FndBox_ const&, bool* pPara); + +static bool lcl_CheckRow( const FndLine_& rFndLine, bool* pPara ) +{ + for (auto const& it : rFndLine.GetBoxes()) + { + lcl_CheckCol(*it, pPara); + } + return *pPara; +} + +static bool lcl_CheckCol( FndBox_ const& rFndBox, bool* pPara ) +{ + if (!rFndBox.GetBox()->GetSttNd()) + { + if (rFndBox.GetLines().size() != + rFndBox.GetBox()->GetTabLines().size()) + { + *pPara = false; + } + else + { + for (auto const& rpFndLine : rFndBox.GetLines()) + { + lcl_CheckRow( *rpFndLine, pPara ); + } + } + } + // is box protected ?? + else if (rFndBox.GetBox()->GetFrameFormat()->GetProtect().IsContentProtected()) + *pPara = false; + return *pPara; +} + +TableMergeErr CheckMergeSel( const SwPaM& rPam ) +{ + SwSelBoxes aBoxes; +//JP 24.09.96: Merge with repeating TableHeadLines does not work properly. +// Why not use point 0,0? Then it is assured the first +// headline is contained. + + Point aPt; + const SwContentNode* pCntNd = rPam.GetContentNode(); + std::pair<Point, bool> tmp(aPt, true); + const SwLayoutFrame *const pStart = pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + pCntNd = rPam.GetContentNode(false); + const SwLayoutFrame *const pEnd = pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + GetTableSel( pStart, pEnd, aBoxes, nullptr ); + return CheckMergeSel( aBoxes ); +} + +TableMergeErr CheckMergeSel( const SwSelBoxes& rBoxes ) +{ + TableMergeErr eRet = TableMergeErr::NoSelection; + if( !rBoxes.empty() ) + { + eRet = TableMergeErr::Ok; + + FndBox_ aFndBox( nullptr, nullptr ); + FndPara aPara( rBoxes, &aFndBox ); + const SwTableNode* pTableNd = aPara.rBoxes[0]->GetSttNd()->FindTableNode(); + ForEach_FndLineCopyCol( const_cast<SwTableLines&>(pTableNd->GetTable().GetTabLines()), &aPara ); + if( !aFndBox.GetLines().empty() ) + { + bool bMergeSelOk = true; + FndBox_* pFndBox = &aFndBox; + FndLine_* pFndLine = nullptr; + while( pFndBox && 1 == pFndBox->GetLines().size() ) + { + pFndLine = pFndBox->GetLines().front().get(); + if( 1 == pFndLine->GetBoxes().size() ) + pFndBox = pFndLine->GetBoxes().front().get(); + else + pFndBox = nullptr; + } + if( pFndBox ) + { + for (auto const& it : pFndBox->GetLines()) + { + lcl_CheckRow(*it, &bMergeSelOk); + } + } + else if( pFndLine ) + { + for (auto const& it : pFndLine->GetBoxes()) + { + lcl_CheckCol(*it, &bMergeSelOk); + } + } + if( !bMergeSelOk ) + eRet = TableMergeErr::TooComplex; + } + else + eRet = TableMergeErr::NoSelection; + } + return eRet; +} + +static SwTwips lcl_CalcWish( const SwLayoutFrame *pCell, long nWish, + const long nAct ) +{ + const SwLayoutFrame *pTmp = pCell; + if ( !nWish ) + nWish = 1; + + const bool bRTL = pCell->IsRightToLeft(); + SwTwips nRet = bRTL ? + nAct - pCell->getFrameArea().Width() : + 0; + + while ( pTmp ) + { + while ( pTmp->GetPrev() ) + { + pTmp = static_cast<const SwLayoutFrame*>(pTmp->GetPrev()); + sal_Int64 nTmp = pTmp->GetFormat()->GetFrameSize().GetWidth(); + // multiply in 64-bit to avoid overflow here! + nRet += ( bRTL ? -1 : 1 ) * nTmp * nAct / nWish; + } + pTmp = pTmp->GetUpper()->GetUpper(); + if ( pTmp && !pTmp->IsCellFrame() ) + pTmp = nullptr; + } + return nRet; +} + +static void lcl_FindStartEndRow( const SwLayoutFrame *&rpStart, + const SwLayoutFrame *&rpEnd, + const bool bChkProtected ) +{ + // Put Start at beginning of a row. + // Put End at the end of its row. + rpStart = static_cast<const SwLayoutFrame*>(rpStart->GetUpper()->Lower()); + while ( rpEnd->GetNext() ) + rpEnd = static_cast<const SwLayoutFrame*>(rpEnd->GetNext()); + + std::deque<const SwLayoutFrame *> aSttArr, aEndArr; + const SwLayoutFrame *pTmp; + for( pTmp = rpStart; (SwFrameType::Cell|SwFrameType::Row) & pTmp->GetType(); + pTmp = pTmp->GetUpper() ) + { + aSttArr.push_front( pTmp ); + } + for( pTmp = rpEnd; (SwFrameType::Cell|SwFrameType::Row) & pTmp->GetType(); + pTmp = pTmp->GetUpper() ) + { + aEndArr.push_front( pTmp ); + } + + for( std::deque<const SwLayoutFrame *>::size_type n = 0; n < aEndArr.size() && n < aSttArr.size(); ++n ) + if( aSttArr[ n ] != aEndArr[ n ] ) + { + // first unequal line or box - all odds are + if( n & 1 ) // 1, 3, 5, ... are boxes + { + rpStart = aSttArr[ n ]; + rpEnd = aEndArr[ n ]; + } + else // 0, 2, 4, ... are lines + { + // check if start & end line are the first & last Line of the + // box. If not return these cells. + // Else the whole line with all Boxes has to be deleted. + rpStart = aSttArr[ n+1 ]; + rpEnd = aEndArr[ n+1 ]; + if( n ) + { + const SwCellFrame* pCellFrame = static_cast<const SwCellFrame*>(aSttArr[ n-1 ]); + const SwTableLines& rLns = pCellFrame-> + GetTabBox()->GetTabLines(); + if( rLns[ 0 ] == static_cast<const SwRowFrame*>(aSttArr[ n ])->GetTabLine() && + rLns[ rLns.size() - 1 ] == + static_cast<const SwRowFrame*>(aEndArr[ n ])->GetTabLine() ) + { + rpStart = rpEnd = pCellFrame; + while ( rpStart->GetPrev() ) + rpStart = static_cast<const SwLayoutFrame*>(rpStart->GetPrev()); + while ( rpEnd->GetNext() ) + rpEnd = static_cast<const SwLayoutFrame*>(rpEnd->GetNext()); + } + } + } + break; + } + + if( !bChkProtected ) // protected cell ? + return; + + // Beginning and end should not be in protected cells + while ( rpStart->GetFormat()->GetProtect().IsContentProtected() ) + rpStart = static_cast<const SwLayoutFrame*>(rpStart->GetNext()); + while ( rpEnd->GetFormat()->GetProtect().IsContentProtected() ) + rpEnd = static_cast<const SwLayoutFrame*>(rpEnd->GetPrev()); +} + +static void lcl_FindStartEndCol( const SwLayoutFrame *&rpStart, + const SwLayoutFrame *&rpEnd, + const bool bChkProtected ) +{ + // Beginning and end vertical till the border of the table; + // Consider the whole table, including master and follows. + // In order to start we need the mother-tableFrame + if( !rpStart ) + return; + const SwTabFrame *pOrg = rpStart->FindTabFrame(); + const SwTabFrame *pTab = pOrg; + + SwRectFnSet aRectFnSet(pTab); + + bool bRTL = pTab->IsRightToLeft(); + const long nTmpWish = pOrg->GetFormat()->GetFrameSize().GetWidth(); + const long nWish = ( nTmpWish > 0 ) ? nTmpWish : 1; + + while ( pTab->IsFollow() ) + { + const SwFrame *pTmp = pTab->FindPrev(); + OSL_ENSURE( pTmp->IsTabFrame(), "Predecessor of Follow is not Master." ); + pTab = static_cast<const SwTabFrame*>(pTmp); + } + + SwTwips nSX = 0; + SwTwips nSX2 = 0; + + if ( pTab->GetTable()->IsNewModel() ) + { + nSX = aRectFnSet.GetLeft(rpStart->getFrameArea()); + nSX2 = aRectFnSet.GetRight(rpStart->getFrameArea()); + } + else + { + const SwTwips nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + nSX = ::lcl_CalcWish( rpStart, nWish, nPrtWidth ) + aRectFnSet.GetPrtLeft(*pTab); + nSX2 = nSX + (rpStart->GetFormat()->GetFrameSize().GetWidth() * nPrtWidth / nWish); + } + + const SwLayoutFrame *pTmp = pTab->FirstCell(); + + while ( pTmp && + (!pTmp->IsCellFrame() || + ( ( ! bRTL && aRectFnSet.GetLeft(pTmp->getFrameArea()) < nSX && + aRectFnSet.GetRight(pTmp->getFrameArea())< nSX2 ) || + ( bRTL && aRectFnSet.GetLeft(pTmp->getFrameArea()) > nSX && + aRectFnSet.GetRight(pTmp->getFrameArea())> nSX2 ) ) ) ) + pTmp = pTmp->GetNextLayoutLeaf(); + + if ( pTmp ) + rpStart = pTmp; + + pTab = pOrg; + + const SwTabFrame* pLastValidTab = pTab; + while ( pTab->GetFollow() ) + { + + // Check if pTab->GetFollow() is a valid follow table: + // Only follow tables with at least on non-FollowFlowLine + // should be considered. + + if ( pTab->HasFollowFlowLine() ) + { + pTab = pTab->GetFollow(); + const SwFrame* pTmpRow = pTab->GetFirstNonHeadlineRow(); + if ( pTmpRow && pTmpRow->GetNext() ) + pLastValidTab = pTab; + } + else + pLastValidTab = pTab = pTab->GetFollow(); + } + pTab = pLastValidTab; + + SwTwips nEX = 0; + + if ( pTab->GetTable()->IsNewModel() ) + { + nEX = aRectFnSet.GetLeft(rpEnd->getFrameArea()); + } + else + { + const SwTwips nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + nEX = ::lcl_CalcWish( rpEnd, nWish, nPrtWidth ) + aRectFnSet.GetPrtLeft(*pTab); + } + + SwFrame const*const pLastContent = pTab->FindLastContentOrTable(); + rpEnd = pLastContent ? pLastContent->GetUpper() : nullptr; + // --> Made code robust. If pTab does not have a lower, + // we would crash here. + if ( !pLastContent ) return; + + while( !rpEnd->IsCellFrame() ) + rpEnd = rpEnd->GetUpper(); + + while ( ( bRTL && aRectFnSet.GetLeft(rpEnd->getFrameArea()) < nEX ) || + ( ! bRTL && aRectFnSet.GetLeft(rpEnd->getFrameArea()) > nEX ) ) + { + const SwLayoutFrame* pTmpLeaf = rpEnd->GetPrevLayoutLeaf(); + if( !pTmpLeaf || !pTab->IsAnLower( pTmpLeaf ) ) + break; + rpEnd = pTmpLeaf; + } + + if( !bChkProtected ) // check for protected cell ? + return; + + // Beginning and end should not be in protected cells. + // If necessary we should search backwards again + while ( rpStart->GetFormat()->GetProtect().IsContentProtected() ) + { + const SwLayoutFrame *pTmpLeaf = rpStart->GetNextLayoutLeaf(); + while ( pTmpLeaf && aRectFnSet.GetLeft(pTmpLeaf->getFrameArea()) > nEX ) // first skip line + pTmpLeaf = pTmpLeaf->GetNextLayoutLeaf(); + while ( pTmpLeaf && aRectFnSet.GetLeft(pTmpLeaf->getFrameArea()) < nSX && + aRectFnSet.GetRight(pTmpLeaf->getFrameArea())< nSX2 ) + pTmpLeaf = pTmpLeaf->GetNextLayoutLeaf(); + const SwTabFrame *pTmpTab = rpStart->FindTabFrame(); + if ( !pTmpTab->IsAnLower( pTmpLeaf ) ) + { + pTmpTab = pTmpTab->GetFollow(); + rpStart = pTmpTab->FirstCell(); + while ( rpStart && + aRectFnSet.GetLeft(rpStart->getFrameArea()) < nSX && + aRectFnSet.GetRight(rpStart->getFrameArea())< nSX2 ) + rpStart = rpStart->GetNextLayoutLeaf(); + } + else + rpStart = pTmpLeaf; + } + while ( rpEnd->GetFormat()->GetProtect().IsContentProtected() ) + { + const SwLayoutFrame *pTmpLeaf = rpEnd->GetPrevLayoutLeaf(); + while ( pTmpLeaf && aRectFnSet.GetLeft(pTmpLeaf->getFrameArea()) < nEX ) // skip the line for now + pTmpLeaf = pTmpLeaf->GetPrevLayoutLeaf(); + while ( pTmpLeaf && aRectFnSet.GetLeft(pTmpLeaf->getFrameArea()) > nEX ) + pTmpLeaf = pTmpLeaf->GetPrevLayoutLeaf(); + const SwTabFrame *pTmpTab = rpEnd->FindTabFrame(); + if ( !pTmpLeaf || !pTmpTab->IsAnLower( pTmpLeaf ) ) + { + pTmpTab = static_cast<const SwTabFrame*>(pTmpTab->FindPrev()); + OSL_ENSURE( pTmpTab->IsTabFrame(), "Predecessor of Follow not Master."); + rpEnd = pTmpTab->FindLastContentOrTable()->GetUpper(); + while( !rpEnd->IsCellFrame() ) + rpEnd = rpEnd->GetUpper(); + while ( aRectFnSet.GetLeft(rpEnd->getFrameArea()) > nEX ) + rpEnd = rpEnd->GetPrevLayoutLeaf(); + } + else + rpEnd = pTmpLeaf; + } +} + +void MakeSelUnions( SwSelUnions& rUnions, const SwLayoutFrame *pStart, + const SwLayoutFrame *pEnd, const SwTableSearchType eSearchType ) +{ + while ( pStart && !pStart->IsCellFrame() ) + pStart = pStart->GetUpper(); + while ( pEnd && !pEnd->IsCellFrame() ) + pEnd = pEnd->GetUpper(); + + if ( !pStart || !pEnd ) + { + OSL_FAIL( "MakeSelUnions with pStart or pEnd not in CellFrame" ); + return; + } + + const SwTabFrame *pTable = pStart->FindTabFrame(); + const SwTabFrame *pEndTable = pEnd->FindTabFrame(); + if( !pTable || !pEndTable ) + return; + bool bExchange = false; + + if ( pTable != pEndTable ) + { + if ( !pTable->IsAnFollow( pEndTable ) ) + { + OSL_ENSURE( pEndTable->IsAnFollow( pTable ), "Tabchain in knots." ); + bExchange = true; + } + } + else + { + SwRectFnSet aRectFnSet(pTable); + long nSttTop = aRectFnSet.GetTop(pStart->getFrameArea()); + long nEndTop = aRectFnSet.GetTop(pEnd->getFrameArea()); + if( nSttTop == nEndTop ) + { + if( aRectFnSet.GetLeft(pStart->getFrameArea()) > + aRectFnSet.GetLeft(pEnd->getFrameArea()) ) + bExchange = true; + } + else if( aRectFnSet.IsVert() == ( nSttTop < nEndTop ) ) + bExchange = true; + } + if ( bExchange ) + { + const SwLayoutFrame *pTmp = pStart; + pStart = pEnd; + pEnd = pTmp; + // do no resort pTable and pEndTable, set new below + // MA: 28. Dec. 93 Bug: 5190 + } + + // Beginning and end now nicely sorted, if required we + // should move them + if( SwTableSearchType::Row == ((~SwTableSearchType::Protect ) & eSearchType ) ) + ::lcl_FindStartEndRow( pStart, pEnd, bool(SwTableSearchType::Protect & eSearchType) ); + else if( SwTableSearchType::Col == ((~SwTableSearchType::Protect ) & eSearchType ) ) + ::lcl_FindStartEndCol( pStart, pEnd, bool(SwTableSearchType::Protect & eSearchType) ); + + if ( !pEnd || !pStart ) return; // Made code robust. + + // retrieve again, as they have been moved + pTable = pStart->FindTabFrame(); + pEndTable = pEnd->FindTabFrame(); + + const long nStSz = pStart->GetFormat()->GetFrameSize().GetWidth(); + const long nEdSz = pEnd->GetFormat()->GetFrameSize().GetWidth(); + const long nWish = std::max( 1L, pTable->GetFormat()->GetFrameSize().GetWidth() ); + while ( pTable ) + { + SwRectFnSet aRectFnSet(pTable); + const long nOfst = aRectFnSet.GetPrtLeft(*pTable); + const long nPrtWidth = aRectFnSet.GetWidth(pTable->getFramePrintArea()); + long nSt1 = ::lcl_CalcWish( pStart, nWish, nPrtWidth ) + nOfst; + long nEd1 = ::lcl_CalcWish( pEnd, nWish, nPrtWidth ) + nOfst; + + if ( nSt1 <= nEd1 ) + nEd1 += static_cast<long>((nEdSz * nPrtWidth) / nWish) - 1; + else + nSt1 += static_cast<long>((nStSz * nPrtWidth) / nWish) - 1; + + long nSt2; + long nEd2; + if( pTable->IsAnLower( pStart ) ) + nSt2 = aRectFnSet.GetTop(pStart->getFrameArea()); + else + nSt2 = aRectFnSet.GetTop(pTable->getFrameArea()); + if( pTable->IsAnLower( pEnd ) ) + nEd2 = aRectFnSet.GetBottom(pEnd->getFrameArea()); + else + nEd2 = aRectFnSet.GetBottom(pTable->getFrameArea()); + Point aSt, aEd; + if( nSt1 > nEd1 ) + { + long nTmp = nSt1; + nSt1 = nEd1; + nEd1 = nTmp; + } + if( nSt2 > nEd2 ) + { + long nTmp = nSt2; + nSt2 = nEd2; + nEd2 = nTmp; + } + if( aRectFnSet.IsVert() ) + { + aSt = Point( nSt2, nSt1 ); + aEd = Point( nEd2, nEd1 ); + } + else + { + aSt = Point( nSt1, nSt2 ); + aEd = Point( nEd1, nEd2 ); + } + + const Point aDiff( aEd - aSt ); + SwRect aUnion( aSt, Size( aDiff.X(), aDiff.Y() ) ); + aUnion.Justify(); + + if( !(SwTableSearchType::NoUnionCorrect & eSearchType )) + { + // Unfortunately the union contains rounding errors now, therefore + // erroneous results could occur during split/merge. + // To prevent these we will determine the first and last row + // within the union and use their values for a new union + const SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTable->Lower()); + + while ( pRow && !pRow->getFrameArea().IsOver( aUnion ) ) + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + + // #i31976# + // A follow flow row may contain empty cells. These are not + // considered by FirstCell(). Therefore we have to find + // the first cell manually: + const SwFrame* pTmpCell = nullptr; + if ( pTable->IsFollow() && pRow && pRow->IsInFollowFlowRow() ) + { + const SwFrame* pTmpRow = pRow; + while ( pTmpRow && pTmpRow->IsRowFrame() ) + { + pTmpCell = static_cast<const SwRowFrame*>(pTmpRow)->Lower(); + pTmpRow = static_cast<const SwCellFrame*>(pTmpCell)->Lower(); + } + OSL_ENSURE( !pTmpCell || pTmpCell->IsCellFrame(), "Lower of rowframe != cellframe?!" ); + } + + const SwLayoutFrame* pFirst = pTmpCell ? + static_cast<const SwLayoutFrame*>(pTmpCell) : + pRow ? + pRow->FirstCell() : + nullptr; + + while ( pFirst && !::IsFrameInTableSel( aUnion, pFirst ) ) + { + if ( pFirst->GetNext() ) + { + pFirst = static_cast<const SwLayoutFrame*>(pFirst->GetNext()); + if ( pFirst->Lower() && pFirst->Lower()->IsRowFrame() ) + pFirst = pFirst->FirstCell(); + } + else + pFirst = ::lcl_FindNextCellFrame( pFirst ); + } + const SwLayoutFrame* pLast = nullptr; + SwFrame const*const pLastContent = pTable->FindLastContentOrTable(); + if ( pLastContent ) + pLast = ::lcl_FindCellFrame( pLastContent->GetUpper() ); + + while ( pLast && !::IsFrameInTableSel( aUnion, pLast ) ) + pLast = ::lcl_FindCellFrame( pLast->GetPrevLayoutLeaf() ); + + if ( pFirst && pLast ) //Robust + { + aUnion = pFirst->getFrameArea(); + aUnion.Union( pLast->getFrameArea() ); + } + else + aUnion.Width( 0 ); + } + + if( aRectFnSet.GetWidth(aUnion) ) + { + rUnions.emplace_back(aUnion, const_cast<SwTabFrame*>(pTable)); + } + + pTable = pTable->GetFollow(); + if ( pTable != pEndTable && pEndTable->IsAnFollow( pTable ) ) + pTable = nullptr; + } +} + +bool CheckSplitCells( const SwCursorShell& rShell, sal_uInt16 nDiv, + const SwTableSearchType eSearchType ) +{ + if( !rShell.IsTableMode() ) + rShell.GetCursor(); + + return CheckSplitCells( *rShell.getShellCursor(false), nDiv, eSearchType ); +} + +bool CheckSplitCells( const SwCursor& rCursor, sal_uInt16 nDiv, + const SwTableSearchType eSearchType ) +{ + if( 1 >= nDiv ) + return false; + + sal_uInt16 nMinValue = nDiv * MINLAY; + + // Get start and end cell + Point aPtPos, aMkPos; + const SwShellCursor* pShCursor = dynamic_cast<const SwShellCursor*>(&rCursor); + if( pShCursor ) + { + aPtPos = pShCursor->GetPtPos(); + aMkPos = pShCursor->GetMkPos(); + } + + const SwContentNode* pCntNd = rCursor.GetContentNode(); + std::pair<Point, bool> tmp(aPtPos, true); + const SwLayoutFrame *const pStart = pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + pCntNd = rCursor.GetContentNode(false); + tmp.first = aMkPos; + const SwLayoutFrame *const pEnd = pCntNd->getLayoutFrame( + pCntNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp)->GetUpper(); + + SwRectFnSet aRectFnSet(pStart->GetUpper()); + + // First, compute tables and rectangles + SwSelUnions aUnions; + + ::MakeSelUnions( aUnions, pStart, pEnd, eSearchType ); + + // now search boxes for each entry and emit + for ( const auto& rSelUnion : aUnions ) + { + const SwTabFrame *pTable = rSelUnion.GetTable(); + + // Skip any repeated headlines in the follow: + const SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + static_cast<const SwLayoutFrame*>(pTable->Lower()); + + while ( pRow ) + { + if ( pRow->getFrameArea().IsOver( rSelUnion.GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while ( pCell && pRow->IsAnLower( pCell ) ) + { + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + if( ::IsFrameInTableSel( rSelUnion.GetUnion(), pCell ) ) + { + if( aRectFnSet.GetWidth(pCell->getFrameArea()) < nMinValue ) + return false; + } + + if ( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + pCell = ::lcl_FindNextCellFrame( pCell ); + } + } + pRow = static_cast<const SwLayoutFrame*>(pRow->GetNext()); + } + } + return true; +} + +// These Classes copy the current table selections (rBoxes), +// into a new structure, retaining the table structure +// new: SS for targeted erasing/restoring of the layout + +static void lcl_InsertRow( SwTableLine const &rLine, SwLayoutFrame *pUpper, SwFrame *pSibling ) +{ + SwRowFrame *pRow = new SwRowFrame( rLine, pUpper ); + if ( pUpper->IsTabFrame() && static_cast<SwTabFrame*>(pUpper)->IsFollow() ) + { + SwTabFrame* pTabFrame = static_cast<SwTabFrame*>(pUpper); + pTabFrame->FindMaster()->InvalidatePos(); //can absorb the line + + if ( pSibling && pTabFrame->IsInHeadline( *pSibling ) ) + { + // Skip any repeated headlines in the follow: + pSibling = pTabFrame->GetFirstNonHeadlineRow(); + } + } + pRow->Paste( pUpper, pSibling ); + pRow->RegistFlys(); +} + +static void FndBoxCopyCol( SwTableBox* pBox, FndPara* pFndPara ) +{ + std::unique_ptr<FndBox_> pFndBox(new FndBox_( pBox, pFndPara->pFndLine )); + if( !pBox->GetTabLines().empty() ) + { + FndPara aPara( *pFndPara, pFndBox.get() ); + ForEach_FndLineCopyCol( pFndBox->GetBox()->GetTabLines(), &aPara ); + if( pFndBox->GetLines().empty() ) + { + return; + } + } + else + { + if( pFndPara->rBoxes.find( pBox ) == pFndPara->rBoxes.end()) + { + return; + } + } + pFndPara->pFndLine->GetBoxes().push_back( std::move(pFndBox) ); +} + +static void FndLineCopyCol( SwTableLine* pLine, FndPara* pFndPara ) +{ + std::unique_ptr<FndLine_> pFndLine(new FndLine_(pLine, pFndPara->pFndBox)); + FndPara aPara(*pFndPara, pFndLine.get()); + for( auto& rpBox : pFndLine->GetLine()->GetTabBoxes() ) + FndBoxCopyCol(rpBox, &aPara ); + if( !pFndLine->GetBoxes().empty() ) + { + pFndPara->pFndBox->GetLines().push_back( std::move(pFndLine) ); + } +} + +void ForEach_FndLineCopyCol(SwTableLines& rLines, FndPara* pFndPara ) +{ + for( SwTableLines::iterator it = rLines.begin(); it != rLines.end(); ++it ) + FndLineCopyCol( *it, pFndPara ); +} + +void FndBox_::SetTableLines( const SwSelBoxes &rBoxes, const SwTable &rTable ) +{ + // Set pointers to lines before and after the area to process. + // If the first/last lines are contained in the area, then the pointers + // are 0. We first search for the positions of the first/last affected + // lines in array of the SwTable. In order to use 0 for 'no line' + // we adjust the positions by 1. + + sal_uInt16 nStPos = USHRT_MAX; + sal_uInt16 nEndPos= 0; + + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableLine *pLine = rBoxes[i]->GetUpper(); + while ( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + const sal_uInt16 nPos = rTable.GetTabLines().GetPos( + const_cast<const SwTableLine*&>(pLine) ) + 1; + + OSL_ENSURE( nPos != USHRT_MAX, "TableLine not found." ); + + if( nStPos > nPos ) + nStPos = nPos; + + if( nEndPos < nPos ) + nEndPos = nPos; + } + if (USHRT_MAX != nStPos && nStPos > 1) + m_pLineBefore = rTable.GetTabLines()[nStPos - 2]; + if ( nEndPos < rTable.GetTabLines().size() ) + m_pLineBehind = rTable.GetTabLines()[nEndPos]; +} + +void FndBox_::SetTableLines( const SwTable &rTable ) +{ + // Set pointers to lines before and after the area to process. + // If the first/last lines are contained in the area, then the pointers + // are 0. The positions of the first/last affected lines in the array + // of the SwTable are in FndBox. In order to use 0 for 'no line' + // we adjust the positions by 1. + + if( GetLines().empty() ) + return; + + SwTableLine* pTmpLine = GetLines().front()->GetLine(); + sal_uInt16 nPos = rTable.GetTabLines().GetPos( pTmpLine ); + OSL_ENSURE( USHRT_MAX != nPos, "Line is not in table" ); + if( nPos ) + m_pLineBefore = rTable.GetTabLines()[ nPos - 1 ]; + + pTmpLine = GetLines().back()->GetLine(); + nPos = rTable.GetTabLines().GetPos( pTmpLine ); + OSL_ENSURE( USHRT_MAX != nPos, "Line is not in the table" ); + if( ++nPos < rTable.GetTabLines().size() ) + m_pLineBehind = rTable.GetTabLines()[nPos]; +} + +inline void UnsetFollow( SwFlowFrame *pTab ) +{ + pTab->m_pPrecede = nullptr; +} + +void FndBox_::DelFrames( SwTable &rTable ) +{ + // All lines between pLineBefore and pLineBehind should be cut + // from the layout and erased. + // If this creates empty Follows we should destroy these. + // If a master is destroyed, the follow should become master. + // Always a TabFrame should remain. + + sal_uInt16 nStPos = 0; + sal_uInt16 nEndPos= rTable.GetTabLines().size() - 1; + if( rTable.IsNewModel() && m_pLineBefore ) + rTable.CheckRowSpan( m_pLineBefore, true ); + if ( m_pLineBefore ) + { + nStPos = rTable.GetTabLines().GetPos( + const_cast<const SwTableLine*&>(m_pLineBefore) ); + OSL_ENSURE( nStPos != USHRT_MAX, "The fox stole the line!" ); + ++nStPos; + } + if( rTable.IsNewModel() && m_pLineBehind ) + rTable.CheckRowSpan( m_pLineBehind, false ); + if ( m_pLineBehind ) + { + nEndPos = rTable.GetTabLines().GetPos( + const_cast<const SwTableLine*&>(m_pLineBehind) ); + OSL_ENSURE( nEndPos != USHRT_MAX, "The fox stole the line!" ); + if (nEndPos != 0) + --nEndPos; + } + + for ( sal_uInt16 i = nStPos; i <= nEndPos; ++i) + { + SwFrameFormat *pFormat = rTable.GetTabLines()[i]->GetFrameFormat(); + SwIterator<SwRowFrame,SwFormat> aIter( *pFormat ); + for ( SwRowFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + if ( pFrame->GetTabLine() == rTable.GetTabLines()[i] ) + { + bool bDel = true; + SwTabFrame *pUp = !pFrame->GetPrev() && !pFrame->GetNext() ? + static_cast<SwTabFrame*>(pFrame->GetUpper()) : nullptr; + if ( !pUp ) + { + const sal_uInt16 nRepeat = + static_cast<SwTabFrame*>(pFrame->GetUpper())->GetTable()->GetRowsToRepeat(); + if ( nRepeat > 0 && + static_cast<SwTabFrame*>(pFrame->GetUpper())->IsFollow() ) + { + if ( !pFrame->GetNext() ) + { + SwRowFrame* pFirstNonHeadline = + static_cast<SwTabFrame*>(pFrame->GetUpper())->GetFirstNonHeadlineRow(); + if ( pFirstNonHeadline == pFrame ) + { + pUp = static_cast<SwTabFrame*>(pFrame->GetUpper()); + } + } + } + } + if ( pUp ) + { + SwTabFrame *pFollow = pUp->GetFollow(); + SwTabFrame *pPrev = pUp->IsFollow() ? pUp : nullptr; + if ( pPrev ) + { + SwFrame *pTmp = pPrev->FindPrev(); + OSL_ENSURE( pTmp->IsTabFrame(), + "Predecessor of Follow is no Master."); + pPrev = static_cast<SwTabFrame*>(pTmp); + } + if ( pPrev ) + { + pPrev->SetFollow( pFollow ); + // #i60340# Do not transfer the + // flag from pUp to pPrev. pUp may still have the + // flag set although there is not more follow flow + // line associated with pUp. + pPrev->SetFollowFlowLine( false ); + } + else if ( pFollow ) + ::UnsetFollow( pFollow ); + + // A TableFrame should always remain! + if ( pPrev || pFollow ) + { + // OD 26.08.2003 #i18103# - if table is in a section, + // lock the section, to avoid its delete. + { + SwSectionFrame* pSctFrame = pUp->FindSctFrame(); + bool bOldSectLock = false; + if ( pSctFrame ) + { + bOldSectLock = pSctFrame->IsColLocked(); + pSctFrame->ColLock(); + } + pUp->Cut(); + if ( pSctFrame && !bOldSectLock ) + { + pSctFrame->ColUnlock(); + } + } + SwFrame::DestroyFrame(pUp); + bDel = false; // Row goes to /dev/null. + } + } + if ( bDel ) + { + SwFrame* pTabFrame = pFrame->GetUpper(); + if ( pTabFrame->IsTabFrame() && + !pFrame->GetNext() && + static_cast<SwTabFrame*>(pTabFrame)->GetFollow() ) + { + // We do not delete the follow flow line, + // this will be done automatically in the + // next turn. + static_cast<SwTabFrame*>(pTabFrame)->SetFollowFlowLine( false ); + } + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } + } + } + } +} + +static bool lcl_IsLineOfTableFrame( const SwTabFrame& rTable, const SwFrame& rChk ) +{ + const SwTabFrame* pTableFrame = rChk.FindTabFrame(); + if( pTableFrame->IsFollow() ) + pTableFrame = pTableFrame->FindMaster( true ); + return &rTable == pTableFrame; +} + +static void lcl_UpdateRepeatedHeadlines( SwTabFrame& rTabFrame, bool bCalcLowers ) +{ + OSL_ENSURE( rTabFrame.IsFollow(), "lcl_UpdateRepeatedHeadlines called for non-follow tab" ); + + // Delete remaining headlines: + SwRowFrame* pLower = nullptr; + while ( nullptr != ( pLower = static_cast<SwRowFrame*>(rTabFrame.Lower()) ) && pLower->IsRepeatedHeadline() ) + { + pLower->Cut(); + SwFrame::DestroyFrame(pLower); + } + + // Insert fresh set of headlines: + pLower = static_cast<SwRowFrame*>(rTabFrame.Lower()); + SwTable& rTable = *rTabFrame.GetTable(); + const sal_uInt16 nRepeat = rTable.GetRowsToRepeat(); + for ( sal_uInt16 nIdx = 0; nIdx < nRepeat; ++nIdx ) + { + SwRowFrame* pHeadline = new SwRowFrame( *rTable.GetTabLines()[ nIdx ], &rTabFrame ); + pHeadline->SetRepeatedHeadline( true ); + pHeadline->Paste( &rTabFrame, pLower ); + pHeadline->RegistFlys(); + } + + if ( bCalcLowers ) + rTabFrame.SetCalcLowers(); +} + +void FndBox_::MakeFrames( SwTable &rTable ) +{ + // All lines between pLineBefore and pLineBehind should be re-generated in layout. + // And this for all instances of a table (for example in header/footer). + sal_uInt16 nStPos = 0; + sal_uInt16 nEndPos= rTable.GetTabLines().size() - 1; + if ( m_pLineBefore ) + { + nStPos = rTable.GetTabLines().GetPos( + const_cast<const SwTableLine*&>(m_pLineBefore) ); + OSL_ENSURE( nStPos != USHRT_MAX, "Fox stole the line!" ); + ++nStPos; + + } + if ( m_pLineBehind ) + { + nEndPos = rTable.GetTabLines().GetPos( + const_cast<const SwTableLine*&>(m_pLineBehind) ); + OSL_ENSURE( nEndPos != USHRT_MAX, "Fox stole the line!" ); + --nEndPos; + } + // now big insert operation for all tables. + SwIterator<SwTabFrame,SwFormat> aTabIter( *rTable.GetFrameFormat() ); + for ( SwTabFrame *pTable = aTabIter.First(); pTable; pTable = aTabIter.Next() ) + { + if ( !pTable->IsFollow() ) + { + SwRowFrame *pSibling = nullptr; + SwFrame *pUpperFrame = nullptr; + int i; + for ( i = rTable.GetTabLines().size()-1; + i >= 0 && !pSibling; --i ) + { + SwTableLine *pLine = m_pLineBehind ? m_pLineBehind : + rTable.GetTabLines()[static_cast<sal_uInt16>(i)]; + SwIterator<SwRowFrame,SwFormat> aIter( *pLine->GetFrameFormat() ); + pSibling = aIter.First(); + while ( pSibling && ( + pSibling->GetTabLine() != pLine || + !lcl_IsLineOfTableFrame( *pTable, *pSibling ) || + pSibling->IsRepeatedHeadline() || + // #i53647# If !pLineBehind, + // IsInSplitTableRow() should be checked. + ( m_pLineBehind && pSibling->IsInFollowFlowRow() ) || + (!m_pLineBehind && pSibling->IsInSplitTableRow() ) ) ) + { + pSibling = aIter.Next(); + } + } + if ( pSibling ) + { + pUpperFrame = pSibling->GetUpper(); + if ( !m_pLineBehind ) + pSibling = nullptr; + } + else +// ???? or is this the last Follow of the table ???? + pUpperFrame = pTable; + + for ( sal_uInt16 j = nStPos; j <= nEndPos; ++j ) + ::lcl_InsertRow( *rTable.GetTabLines()[j], + static_cast<SwLayoutFrame*>(pUpperFrame), pSibling ); + if ( pUpperFrame->IsTabFrame() ) + static_cast<SwTabFrame*>(pUpperFrame)->SetCalcLowers(); + } + else if ( rTable.GetRowsToRepeat() > 0 ) + { + // Insert new headlines: + lcl_UpdateRepeatedHeadlines( *pTable, true ); + } + } +} + +void FndBox_::MakeNewFrames( SwTable &rTable, const sal_uInt16 nNumber, + const bool bBehind ) +{ + // Create Frames for newly inserted lines + // bBehind == true: before pLineBehind + // == false: after pLineBefore + const sal_uInt16 nBfPos = m_pLineBefore ? + rTable.GetTabLines().GetPos( const_cast<const SwTableLine*&>(m_pLineBefore) ) : + USHRT_MAX; + const sal_uInt16 nBhPos = m_pLineBehind ? + rTable.GetTabLines().GetPos( const_cast<const SwTableLine*&>(m_pLineBehind) ) : + USHRT_MAX; + + //nNumber: how often did we insert + //nCnt: how many were inserted nNumber times + + const sal_uInt16 nCnt = + ((nBhPos != USHRT_MAX ? nBhPos : rTable.GetTabLines().size()) - + (nBfPos != USHRT_MAX ? nBfPos + 1 : 0)) / (nNumber + 1); + + // search the Master-TabFrame + SwIterator<SwTabFrame,SwFormat> aTabIter( *rTable.GetFrameFormat() ); + SwTabFrame *pTable; + for ( pTable = aTabIter.First(); pTable; pTable = aTabIter.Next() ) + { + if( !pTable->IsFollow() ) + { + SwRowFrame* pSibling = nullptr; + SwLayoutFrame *pUpperFrame = nullptr; + if ( bBehind ) + { + if ( m_pLineBehind ) + { + SwIterator<SwRowFrame,SwFormat> aIter( *m_pLineBehind->GetFrameFormat() ); + pSibling = aIter.First(); + while ( pSibling && ( + // only consider row frames associated with pLineBehind: + pSibling->GetTabLine() != m_pLineBehind || + // only consider row frames that are in pTables Master-Follow chain: + !lcl_IsLineOfTableFrame( *pTable, *pSibling ) || + // only consider row frames that are not repeated headlines: + pSibling->IsRepeatedHeadline() || + // only consider row frames that are not follow flow rows + pSibling->IsInFollowFlowRow() ) ) + { + pSibling = aIter.Next(); + } + } + if ( pSibling ) + pUpperFrame = pSibling->GetUpper(); + else + { + while( pTable->GetFollow() ) + pTable = pTable->GetFollow(); + pUpperFrame = pTable; + } + const sal_uInt16 nMax = nBhPos != USHRT_MAX ? + nBhPos : rTable.GetTabLines().size(); + + sal_uInt16 i = nBfPos != USHRT_MAX ? nBfPos + 1 + nCnt : nCnt; + + for ( ; i < nMax; ++i ) + ::lcl_InsertRow( *rTable.GetTabLines()[i], pUpperFrame, pSibling ); + if ( pUpperFrame->IsTabFrame() ) + static_cast<SwTabFrame*>(pUpperFrame)->SetCalcLowers(); + } + else // insert before + { + sal_uInt16 i; + + // We are looking for the frame that is behind the row frame + // that should be inserted. + for ( i = 0; !pSibling; ++i ) + { + SwTableLine* pLine = m_pLineBefore ? m_pLineBefore : rTable.GetTabLines()[i]; + + SwIterator<SwRowFrame,SwFormat> aIter( *pLine->GetFrameFormat() ); + pSibling = aIter.First(); + + while ( pSibling && ( + // only consider row frames associated with pLineBefore: + pSibling->GetTabLine() != pLine || + // only consider row frames that are in pTables Master-Follow chain: + !lcl_IsLineOfTableFrame( *pTable, *pSibling ) || + // only consider row frames that are not repeated headlines: + pSibling->IsRepeatedHeadline() || + // 1. case: pLineBefore == 0: + // only consider row frames that are not follow flow rows + // 2. case: pLineBefore != 0: + // only consider row frames that are not split table rows + // #i37476# If !pLineBefore, + // check IsInFollowFlowRow instead of IsInSplitTableRow. + ( ( !m_pLineBefore && pSibling->IsInFollowFlowRow() ) || + ( m_pLineBefore && pSibling->IsInSplitTableRow() ) ) ) ) + { + pSibling = aIter.Next(); + } + } + + pUpperFrame = pSibling->GetUpper(); + if ( m_pLineBefore ) + pSibling = static_cast<SwRowFrame*>( pSibling->GetNext() ); + + sal_uInt16 nMax = nBhPos != USHRT_MAX ? + nBhPos - nCnt : + rTable.GetTabLines().size() - nCnt; + + i = nBfPos != USHRT_MAX ? nBfPos + 1 : 0; + for ( ; i < nMax; ++i ) + ::lcl_InsertRow( *rTable.GetTabLines()[i], + pUpperFrame, pSibling ); + if ( pUpperFrame->IsTabFrame() ) + static_cast<SwTabFrame*>(pUpperFrame)->SetCalcLowers(); + } + } + } + + // If necessary headlines should be processed. In order to + // not to fragment good code, we iterate once more. + const sal_uInt16 nRowsToRepeat = rTable.GetRowsToRepeat(); + if ( nRowsToRepeat > 0 && + ( ( !bBehind && ( nBfPos == USHRT_MAX || nBfPos + 1 < nRowsToRepeat ) ) || + ( bBehind && ( ( nBfPos == USHRT_MAX && nRowsToRepeat > 1 ) || nBfPos + 2 < nRowsToRepeat ) ) ) ) + { + for ( pTable = aTabIter.First(); pTable; pTable = aTabIter.Next() ) + { + if ( pTable->Lower() ) + { + if ( pTable->IsFollow() ) + { + lcl_UpdateRepeatedHeadlines( *pTable, true ); + } + + OSL_ENSURE( static_cast<SwRowFrame*>(pTable->Lower())->GetTabLine() == + rTable.GetTabLines()[0], "MakeNewFrames: Table corruption!" ); + } + } + } +} + +bool FndBox_::AreLinesToRestore( const SwTable &rTable ) const +{ + // Should we call MakeFrames here? + + if ( !m_pLineBefore && !m_pLineBehind && !rTable.GetTabLines().empty() ) + return true; + + sal_uInt16 nBfPos; + if(m_pLineBefore) + { + const SwTableLine* rLBefore = const_cast<const SwTableLine*>(m_pLineBefore); + nBfPos = rTable.GetTabLines().GetPos( rLBefore ); + } + else + nBfPos = USHRT_MAX; + + sal_uInt16 nBhPos; + if(m_pLineBehind) + { + const SwTableLine* rLBehind = const_cast<const SwTableLine*>(m_pLineBehind); + nBhPos = rTable.GetTabLines().GetPos( rLBehind ); + } + else + nBhPos = USHRT_MAX; + + if ( nBfPos == nBhPos ) // Should never occur. + { + OSL_FAIL( "Table, erase but not on any area !?!" ); + return false; + } + + if ( rTable.GetRowsToRepeat() > 0 ) + { + // oops: should the repeated headline have been deleted?? + SwIterator<SwTabFrame,SwFormat> aIter( *rTable.GetFrameFormat() ); + for( SwTabFrame* pTable = aIter.First(); pTable; pTable = aIter.Next() ) + { + if( pTable->IsFollow() ) + { + // Insert new headlines: + lcl_UpdateRepeatedHeadlines( *pTable, false ); + } + } + } + + // Some adjacent lines at the beginning of the table have been deleted: + if ( nBfPos == USHRT_MAX && nBhPos == 0 ) + return false; + + // Some adjacent lines at the end of the table have been deleted: + if ( nBhPos == USHRT_MAX && nBfPos == (rTable.GetTabLines().size() - 1) ) + return false; + + // Some adjacent lines in the middle of the table have been deleted: + if ( nBfPos != USHRT_MAX && nBhPos != USHRT_MAX && (nBfPos + 1) == nBhPos ) + return false; + + // The structure of the deleted lines is more complex due to split lines. + // A call of MakeFrames() is necessary. + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/graphic/grfatr.cxx b/sw/source/core/graphic/grfatr.cxx new file mode 100644 index 000000000..a8e2e63c7 --- /dev/null +++ b/sw/source/core/graphic/grfatr.cxx @@ -0,0 +1,338 @@ +/* -*- 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 <com/sun/star/drawing/ColorMode.hpp> +#include <o3tl/any.hxx> +#include <vcl/GraphicObject.hxx> +#include <grfatr.hxx> +#include <swunohelper.hxx> +#include <osl/diagnose.h> + +#include <unomid.h> + +using namespace ::com::sun::star; + +SwMirrorGrf* SwMirrorGrf::Clone( SfxItemPool* ) const +{ + return new SwMirrorGrf( *this ); +} + +sal_uInt16 SwMirrorGrf::GetValueCount() const +{ + return 4; +} + +bool SwMirrorGrf::operator==( const SfxPoolItem& rItem) const +{ + return SfxEnumItem::operator==(static_cast<const SfxEnumItem<MirrorGraph>&>(rItem)) && + static_cast<const SwMirrorGrf&>(rItem).IsGrfToggle() == IsGrfToggle(); +} + +static bool lcl_IsHoriOnEvenPages(MirrorGraph nEnum, bool bToggle) +{ + bool bEnum = nEnum == MirrorGraph::Vertical || + nEnum == MirrorGraph::Both; + return bEnum != bToggle; +} + +static bool lcl_IsHoriOnOddPages(MirrorGraph nEnum) +{ + bool bEnum = nEnum == MirrorGraph::Vertical || + nEnum == MirrorGraph::Both; + return bEnum; +} + +bool SwMirrorGrf::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bRet = true; + bool bVal = false; + // vertical and horizontal were swapped at some point + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_MIRROR_HORZ_EVEN_PAGES: + bVal = lcl_IsHoriOnEvenPages(GetValue(), IsGrfToggle()); + break; + case MID_MIRROR_HORZ_ODD_PAGES: + bVal = lcl_IsHoriOnOddPages(GetValue()); + break; + case MID_MIRROR_VERT: + bVal = GetValue() == MirrorGraph::Horizontal || + GetValue() == MirrorGraph::Both; + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + rVal <<= bVal; + return bRet; +} + +bool SwMirrorGrf::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = true; + bool bVal = *o3tl::doAccess<bool>(rVal); + // vertical and horizontal were swapped at some point + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_MIRROR_HORZ_EVEN_PAGES: + case MID_MIRROR_HORZ_ODD_PAGES: + { + bool bIsVert = GetValue() == MirrorGraph::Horizontal || + GetValue() == MirrorGraph::Both; + bool bOnOddPages = nMemberId == MID_MIRROR_HORZ_EVEN_PAGES ? + lcl_IsHoriOnOddPages(GetValue()) : bVal; + bool bOnEvenPages = nMemberId == MID_MIRROR_HORZ_ODD_PAGES ? + lcl_IsHoriOnEvenPages(GetValue(), IsGrfToggle()) : bVal; + MirrorGraph nEnum = bOnOddPages ? + bIsVert ? MirrorGraph::Both : MirrorGraph::Vertical : + bIsVert ? MirrorGraph::Horizontal : MirrorGraph::Dont; + bool bToggle = bOnOddPages != bOnEvenPages; + SetValue(nEnum); + SetGrfToggle( bToggle ); + } + break; + case MID_MIRROR_VERT: + if ( bVal ) + { + if ( GetValue() == MirrorGraph::Vertical ) + SetValue( MirrorGraph::Both ); + else if ( GetValue() != MirrorGraph::Both ) + SetValue( MirrorGraph::Horizontal ); + } + else + { + if ( GetValue() == MirrorGraph::Both ) + SetValue( MirrorGraph::Vertical ); + else if ( GetValue() == MirrorGraph::Horizontal ) + SetValue( MirrorGraph::Dont ); + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +SwCropGrf::SwCropGrf() + : SvxGrfCrop( RES_GRFATR_CROPGRF ) +{} + +SwCropGrf::SwCropGrf(sal_Int32 nL, sal_Int32 nR, sal_Int32 nT, sal_Int32 nB ) + : SvxGrfCrop( nL, nR, nT, nB, RES_GRFATR_CROPGRF ) +{} + +SwCropGrf* SwCropGrf::Clone( SfxItemPool* ) const +{ + return new SwCropGrf( *this ); +} + +sal_Int16 SwRotationGrf::checkAndCorrectValue(sal_Int16 nValue) +{ + if(nValue < 0) + { + // smaller zero, modulo (will keep negative) and add one range + DBG_ASSERT(false, "SwRotationGrf: Value is in 10th degree and *has* to be in [0 .. 3600[ (!)"); + return 3600 + (nValue % 3600); + } + else if (nValue >= 3600) + { + // bigger range, use modulo + DBG_ASSERT(false, "SwRotationGrf: Value is in 10th degree and *has* to be in [0 .. 3600[ (!)"); + return nValue % 3600; + } + + return nValue; +} + +SwRotationGrf::SwRotationGrf( sal_Int16 nVal, const Size& rSz ) + // tdf#115529 check and evtl. correct value +: SfxUInt16Item( RES_GRFATR_ROTATION, checkAndCorrectValue(nVal) ), + m_aUnrotatedSize( rSz ) +{ +} + +SwRotationGrf* SwRotationGrf::Clone( SfxItemPool * ) const +{ + return new SwRotationGrf( *this ); +} + +bool SwRotationGrf::operator==( const SfxPoolItem& rCmp ) const +{ + return SfxUInt16Item::operator==( rCmp ) && + GetUnrotatedSize() == static_cast<const SwRotationGrf&>(rCmp).GetUnrotatedSize(); +} + +bool SwRotationGrf::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + // SfxUInt16Item::QueryValue returns sal_Int32 in Any now... (srx642w) + // where we still want this to be a sal_Int16 + rVal <<= static_cast<sal_Int16>(GetValue()); + return true; +} + +bool SwRotationGrf::PutValue( const uno::Any& rVal, sal_uInt8 ) +{ + // SfxUInt16Item::QueryValue returns sal_Int32 in Any now... (srx642w) + // where we still want this to be a sal_Int16 + sal_Int16 nValue = 0; + if (rVal >>= nValue) + { + // sal_uInt16 argument needed + // tdf#115529 check and evtl. correct value + SetValue(static_cast<sal_uInt16>(checkAndCorrectValue(nValue))); + return true; + } + + OSL_FAIL( "SwRotationGrf::PutValue - Wrong type!" ); + return false; +} + +// Sw___Grf::Clone(..) + +SwLuminanceGrf* SwLuminanceGrf::Clone( SfxItemPool * ) const +{ + return new SwLuminanceGrf( *this ); +} + +SwContrastGrf* SwContrastGrf::Clone( SfxItemPool * ) const +{ + return new SwContrastGrf( *this ); +} + +SwChannelRGrf* SwChannelRGrf::Clone( SfxItemPool * ) const +{ + return new SwChannelRGrf( *this ); +} + +SwChannelGGrf* SwChannelGGrf::Clone( SfxItemPool * ) const +{ + return new SwChannelGGrf( *this ); +} + +SwChannelBGrf* SwChannelBGrf::Clone( SfxItemPool * ) const +{ + return new SwChannelBGrf( *this ); +} + +SwGammaGrf* SwGammaGrf::Clone( SfxItemPool * ) const +{ + return new SwGammaGrf( *this ); +} + +// SwGammaGrf + +bool SwGammaGrf::operator==( const SfxPoolItem& rCmp ) const +{ + return SfxPoolItem::operator==( rCmp ) && + m_nValue == static_cast<const SwGammaGrf&>(rCmp).GetValue(); +} + +bool SwGammaGrf::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + rVal <<= m_nValue; + return true; +} + +bool SwGammaGrf::PutValue( const uno::Any& rVal, sal_uInt8 ) +{ + return rVal >>= m_nValue; +} + +// Sw___Grf::Clone(..) cont'd + +SwInvertGrf* SwInvertGrf::Clone( SfxItemPool * ) const +{ + return new SwInvertGrf( *this ); +} + +SwTransparencyGrf* SwTransparencyGrf::Clone( SfxItemPool * ) const +{ + return new SwTransparencyGrf( *this ); +} + +// SwTransparencyGrf + +bool SwTransparencyGrf::QueryValue( uno::Any& rVal, + sal_uInt8 ) const +{ + OSL_ENSURE(dynamic_cast<const SfxByteItem*>( this ) != nullptr,"Put/QueryValue should be removed!"); + sal_Int16 nRet = GetValue(); + OSL_ENSURE( 0 <= nRet && nRet <= 100, "value out of range" ); + rVal <<= nRet; + return true; +} + +bool SwTransparencyGrf::PutValue( const uno::Any& rVal, + sal_uInt8 ) +{ + //temporary conversion until this is a SfxInt16Item! + OSL_ENSURE(dynamic_cast<const SfxByteItem*>( this ) != nullptr,"Put/QueryValue should be removed!"); + sal_Int16 nVal = 0; + if(!(rVal >>= nVal) || nVal < -100 || nVal > 100) + return false; + if(nVal < 0) + { + // for compatibility with old documents + // introduce rounding as for SO 6.0 PP2 + nVal = ( ( nVal * 128 ) - (99/2) ) / 100; + nVal += 128; + } + OSL_ENSURE( 0 <= nVal && nVal <= 100, "value out of range" ); + SetValue(static_cast<sal_uInt8>(nVal)); + return true; +} + +// Sw___Grf::Clone(..) cont'd + +SwDrawModeGrf* SwDrawModeGrf::Clone( SfxItemPool * ) const +{ + return new SwDrawModeGrf( *this ); +} + +// SwDrawModeGrf + +sal_uInt16 SwDrawModeGrf::GetValueCount() const +{ + return sal_uInt16(GraphicDrawMode::Watermark) + 1; +} + +bool SwDrawModeGrf::QueryValue( uno::Any& rVal, + sal_uInt8 ) const +{ + drawing::ColorMode eRet = static_cast<drawing::ColorMode>(GetEnumValue()); + rVal <<= eRet; + return true; +} + +bool SwDrawModeGrf::PutValue( const uno::Any& rVal, + sal_uInt8 ) +{ + sal_Int32 eVal = SWUnoHelper::GetEnumAsInt32( rVal ); + if(eVal >= 0 && eVal <= sal_uInt16(GraphicDrawMode::Watermark)) + { + SetEnumValue(static_cast<sal_uInt16>(eVal)); + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/graphic/ndgrf.cxx b/sw/source/core/graphic/ndgrf.cxx new file mode 100644 index 000000000..bb39141cc --- /dev/null +++ b/sw/source/core/graphic/ndgrf.cxx @@ -0,0 +1,874 @@ +/* -*- 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 <hintids.hxx> +#include <tools/helpers.hxx> +#include <tools/urlobj.hxx> +#include <tools/fract.hxx> +#include <svl/fstathelper.hxx> +#include <vcl/imap.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/linkmgr.hxx> +#include <editeng/boxitem.hxx> +#include <sot/formats.hxx> +#include <fmtfsize.hxx> +#include <fmturl.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <frmatr.hxx> +#include <grfatr.hxx> +#include <swtypes.hxx> +#include <ndgrf.hxx> +#include <fmtcol.hxx> +#include <hints.hxx> +#include <swbaslnk.hxx> +#include <pagefrm.hxx> + +#include <rtl/ustring.hxx> +#include <o3tl/deleter.hxx> +#include <retrieveinputstreamconsumer.hxx> +#include <drawinglayer/processor2d/objectinfoextractor2d.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> + +using namespace com::sun::star; + +SwGrfNode::SwGrfNode( + const SwNodeIndex & rWhere, + const OUString& rGrfName, + const OUString& rFltName, + const Graphic* pGraphic, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwNoTextNode( rWhere, SwNodeType::Grf, pGrfColl, pAutoAttr ), + maGrfObj(), + mbInBaseLinkSwapIn(true), + // #i73788# + mbLinkedInputStreamReady( false ), + mbIsStreamReadOnly( false ) +{ + mbInSwapIn = mbChangeTwipSize = + mbFrameInPaint = mbScaleImageMap = false; + + ReRead(rGrfName, rFltName, pGraphic, false); +} + +SwGrfNode::SwGrfNode( const SwNodeIndex & rWhere, + const GraphicObject& rGrfObj, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwNoTextNode( rWhere, SwNodeType::Grf, pGrfColl, pAutoAttr ), + maGrfObj(rGrfObj), + mbInBaseLinkSwapIn(true), + // #i73788# + mbLinkedInputStreamReady( false ), + mbIsStreamReadOnly( false ) +{ + mbInSwapIn = mbChangeTwipSize = + mbFrameInPaint = mbScaleImageMap = false; +} + +/** Create new SW/G reader. + * + * Use this ctor if you want to read a linked graphic. + * + * @note Does not read/open the image itself! + */ +SwGrfNode::SwGrfNode( const SwNodeIndex & rWhere, + const OUString& rGrfName, + const OUString& rFltName, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwNoTextNode( rWhere, SwNodeType::Grf, pGrfColl, pAutoAttr ), + maGrfObj(), + mbInBaseLinkSwapIn(true), + // #i73788# + mbLinkedInputStreamReady( false ), + mbIsStreamReadOnly( false ) +{ + Graphic aGrf; aGrf.SetDefaultType(); + maGrfObj.SetGraphic( aGrf, rGrfName ); + + mbInSwapIn = mbChangeTwipSize = + mbFrameInPaint = mbScaleImageMap = false; + + InsertLink( rGrfName, rFltName ); + if( IsLinkedFile() ) + { + INetURLObject aUrl( rGrfName ); + if( INetProtocol::File == aUrl.GetProtocol() && + FStatHelper::IsDocument( aUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )) + { + // file exists, so create connection without an update + static_cast<SwBaseLink*>( mxLink.get() )->Connect(); + } + } +} + +bool SwGrfNode::ReRead( + const OUString& rGrfName, const OUString& rFltName, + const Graphic* pGraphic, + bool bNewGrf ) +{ + bool bReadGrf = false; + bool bSetTwipSize = true; + mpReplacementGraphic.reset(); + + OSL_ENSURE( pGraphic || !rGrfName.isEmpty(), + "GraphicNode without a name, Graphic or GraphicObject" ); + + OUString sURLLink; + if (pGraphic) + { + Graphic aGraphic(*pGraphic); + + sURLLink = aGraphic.getOriginURL(); + if (sURLLink.isEmpty() && !rGrfName.isEmpty()) + { + sURLLink = rGrfName; + aGraphic.setOriginURL(sURLLink); + } + } + else + { + sURLLink = rGrfName; + } + + // with name + if( mxLink.is() ) + { + OSL_ENSURE( !mbInSwapIn, "ReRead: I am still in SwapIn" ); + + if( !sURLLink.isEmpty() ) + { + // Note: if there is DDE in the FltName, then it is a DDE-linked graphic + OUString sCmd( sURLLink ); + if( !rFltName.isEmpty() ) + { + sfx2::SvBaseLinkObjectType nNewType; + if( rFltName == "DDE" ) + nNewType = sfx2::SvBaseLinkObjectType::ClientDde; + else + { + sfx2::MakeLnkName( sCmd, nullptr, sURLLink, OUString(), &rFltName ); + nNewType = sfx2::SvBaseLinkObjectType::ClientGraphic; + } + + if( nNewType != mxLink->GetObjType() ) + { + mxLink->Disconnect(); + static_cast<SwBaseLink*>( mxLink.get() )->SetObjType( nNewType ); + } + } + + mxLink->SetLinkSourceName( sCmd ); + } + else // no name anymore, so remove link + { + GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( mxLink.get() ); + mxLink.clear(); + } + + if( pGraphic ) + { + maGrfObj.SetGraphic( *pGraphic, sURLLink ); + onGraphicChanged(); + bReadGrf = true; + } + else + { + // reset data of the old graphic so that the correct placeholder is + // shown in case the new link could not be loaded + Graphic aGrf; aGrf.SetDefaultType(); + maGrfObj.SetGraphic( aGrf, sURLLink ); + + if( mxLink.is() ) + { + if( getLayoutFrame( GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) ) + { + SwMsgPoolItem aMsgHint( RES_GRF_REREAD_AND_INCACHE ); + ModifyNotification( &aMsgHint, &aMsgHint ); + } + else if ( bNewGrf ) + { + //TODO refLink->setInputStream(getInputStream()); + static_cast<SwBaseLink*>( mxLink.get() )->SwapIn(); + } + } + onGraphicChanged(); + bSetTwipSize = false; + } + } + else if( pGraphic && sURLLink.isEmpty() ) + { + maGrfObj.SetGraphic( *pGraphic ); + onGraphicChanged(); + bReadGrf = true; + } + // Was the graphic already loaded? + else if( !bNewGrf && GraphicType::NONE != maGrfObj.GetType() ) + return true; + else + { + // create new link for the graphic object + InsertLink( sURLLink, rFltName ); + + if( GetNodes().IsDocNodes() ) + { + if( pGraphic ) + { + maGrfObj.SetGraphic( *pGraphic, sURLLink ); + onGraphicChanged(); + bReadGrf = true; + // create connection without update, as we have the graphic + static_cast<SwBaseLink*>( mxLink.get() )->Connect(); + } + else + { + Graphic aGrf; + aGrf.SetDefaultType(); + maGrfObj.SetGraphic( aGrf, sURLLink ); + onGraphicChanged(); + if ( bNewGrf ) + { + static_cast<SwBaseLink*>( mxLink.get() )->SwapIn(); + } + } + } + } + + // Bug 39281: Do not delete Size immediately - Events on ImageMaps should have + // something to work with when swapping + if( bSetTwipSize ) + SetTwipSize( ::GetGraphicSizeTwip( maGrfObj.GetGraphic(), nullptr ) ); + + // create an updates for the frames + if( bReadGrf && bNewGrf ) + { + SwMsgPoolItem aMsgHint( RES_UPDATE_ATTR ); + ModifyNotification( &aMsgHint, &aMsgHint ); + } + + return bReadGrf; +} + +SwGrfNode::~SwGrfNode() +{ + mpReplacementGraphic.reset(); + + // #i73788# + mpThreadConsumer.reset(); + + SwDoc* pDoc = GetDoc(); + if( mxLink.is() ) + { + OSL_ENSURE( !mbInSwapIn, "DTOR: I am still in SwapIn" ); + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( mxLink.get() ); + mxLink->Disconnect(); + } + else + { + // #i40014# - A graphic node, which is in a linked + // section, whose link is another section in the document, doesn't + // have to remove the stream from the storage. + // Because it's hard to detect this case here and it would only fix + // one problem with shared graphic files - there are also problems, + // a certain graphic file is referenced by two independent graphic nodes, + // brush item or drawing objects, the stream isn't no longer removed here. + // To do this stuff correctly, a reference counting on shared streams + // inside one document has to be implemented. + } + //#39289# delete frames already here since the Frames' dtor needs the graphic for its StopAnimation + if( HasWriterListeners() ) + DelFrames(nullptr); +} + +/// allow reaction on change of content of GraphicObject +void SwGrfNode::onGraphicChanged() +{ + // try to access SwFlyFrameFormat; since title/desc/name are set there, there is no + // use to continue if it is not yet set. If not yet set, call onGraphicChanged() + // when it is set. + SwFlyFrameFormat* pFlyFormat = dynamic_cast< SwFlyFrameFormat* >(GetFlyFormat()); + + if(pFlyFormat) + { + OUString aName; + OUString aTitle; + OUString aDesc; + auto const & rVectorGraphicDataPtr = GetGrf().getVectorGraphicData(); + + if (rVectorGraphicDataPtr) + { + const drawinglayer::primitive2d::Primitive2DContainer aSequence(rVectorGraphicDataPtr->getPrimitive2DSequence()); + + if(!aSequence.empty()) + { + drawinglayer::geometry::ViewInformation2D aViewInformation2D; + drawinglayer::processor2d::ObjectInfoPrimitiveExtractor2D aProcessor(aViewInformation2D); + + aProcessor.process(aSequence); + + const drawinglayer::primitive2d::ObjectInfoPrimitive2D* pResult = aProcessor.getResult(); + + if(pResult) + { + aName = pResult->getName(); + aTitle = pResult->getTitle(); + aDesc = pResult->getDesc(); + } + } + } + + if(!aTitle.isEmpty()) + { + SetTitle(aTitle); + } + else if (!aName.isEmpty()) + { + SetTitle(aName); + } + + if(!aDesc.isEmpty()) + { + SetDescription(aDesc); + } + } +} + +void SwGrfNode::SetGraphic(const Graphic& rGraphic) +{ + maGrfObj.SetGraphic(rGraphic, OUString()); + onGraphicChanged(); +} + +const Graphic& SwGrfNode::GetGrf(bool bWait) const +{ + const_cast<SwGrfNode*>(this)->SwapIn(bWait); + return maGrfObj.GetGraphic(); +} + +const GraphicObject& SwGrfNode::GetGrfObj(bool bWait) const +{ + const_cast<SwGrfNode*>(this)->SwapIn(bWait); + return maGrfObj; +} + +const GraphicObject* SwGrfNode::GetReplacementGrfObj() const +{ + if(!mpReplacementGraphic) + { + auto const & rVectorGraphicDataPtr = GetGrfObj().GetGraphic().getVectorGraphicData(); + + if (rVectorGraphicDataPtr) + { + const_cast< SwGrfNode* >(this)->mpReplacementGraphic.reset( new GraphicObject(rVectorGraphicDataPtr->getReplacement()) ); + } + else if (GetGrfObj().GetGraphic().GetType() == GraphicType::GdiMetafile) + { + // Replacement graphic for PDF and metafiles is just the bitmap. + const_cast<SwGrfNode*>(this)->mpReplacementGraphic.reset( new GraphicObject(GetGrfObj().GetGraphic().GetBitmapEx()) ); + } + } + + return mpReplacementGraphic.get(); +} + +SwGrfNode * SwNodes::MakeGrfNode( const SwNodeIndex & rWhere, + const OUString& rGrfName, + const OUString& rFltName, + const Graphic* pGraphic, + SwGrfFormatColl* pGrfColl, + SwAttrSet const * pAutoAttr ) +{ + OSL_ENSURE( pGrfColl, "MakeGrfNode: Formatpointer is 0." ); + SwGrfNode *pNode; + // create object delayed, only from a SW/G-reader + if( !pGraphic ) + pNode = new SwGrfNode( rWhere, rGrfName, + rFltName, pGrfColl, pAutoAttr ); + else + pNode = new SwGrfNode( rWhere, rGrfName, + rFltName, pGraphic, pGrfColl, pAutoAttr ); + return pNode; +} + +SwGrfNode * SwNodes::MakeGrfNode( const SwNodeIndex & rWhere, + const GraphicObject& rGrfObj, + SwGrfFormatColl* pGrfColl ) +{ + OSL_ENSURE( pGrfColl, "MakeGrfNode: Formatpointer is 0." ); + return new SwGrfNode( rWhere, rGrfObj, pGrfColl, nullptr ); +} + +Size SwGrfNode::GetTwipSize() const +{ + if( !mnGrfSize.Width() && !mnGrfSize.Height() ) + { + const_cast<SwGrfNode*>(this)->SwapIn(); + } + return mnGrfSize; +} + +/** + * @return true if ReRead or reading successful, + * false if not loaded + */ +bool SwGrfNode::SwapIn(bool bWaitForData) +{ + if(mbInSwapIn) // not recursively! + return true; + + bool bRet = false; + mbInSwapIn = true; + SwBaseLink* pLink = static_cast<SwBaseLink*>( mxLink.get() ); + + if( pLink ) + { + if( (GraphicType::NONE == maGrfObj.GetType() || + GraphicType::Default == maGrfObj.GetType()) && + mbInBaseLinkSwapIn) + { + // link was not loaded yet + if( pLink->SwapIn( bWaitForData ) ) + { + bRet = true; + mbInBaseLinkSwapIn = false; + } + else if( GraphicType::Default == maGrfObj.GetType() ) + { + // no default bitmap anymore, thus re-paint + mpReplacementGraphic.reset(); + + maGrfObj.SetGraphic( Graphic() ); + onGraphicChanged(); + SwMsgPoolItem aMsgHint( RES_GRAPHIC_PIECE_ARRIVED ); + ModifyNotification( &aMsgHint, &aMsgHint ); + } + } + else + { + bRet = true; + } + } + else + bRet = true; + + if (bRet) + { + if( !mnGrfSize.Width() && !mnGrfSize.Height() ) + SetTwipSize( ::GetGraphicSizeTwip( maGrfObj.GetGraphic(), nullptr ) ); + } + mbInSwapIn = false; + return bRet; +} + +bool SwGrfNode::GetFileFilterNms( OUString* pFileNm, OUString* pFilterNm ) const +{ + bool bRet = false; + if( mxLink.is() && mxLink->GetLinkManager() ) + { + sfx2::SvBaseLinkObjectType nType = mxLink->GetObjType(); + if( sfx2::SvBaseLinkObjectType::ClientGraphic == nType ) + bRet = sfx2::LinkManager::GetDisplayNames( + mxLink.get(), nullptr, pFileNm, nullptr, pFilterNm ); + else if( sfx2::SvBaseLinkObjectType::ClientDde == nType && pFileNm && pFilterNm ) + { + OUString sApp; + OUString sTopic; + OUString sItem; + if( sfx2::LinkManager::GetDisplayNames( + mxLink.get(), &sApp, &sTopic, &sItem ) ) + { + *pFileNm = sApp + OUStringChar(sfx2::cTokenSeparator) + + sTopic + OUStringChar(sfx2::cTokenSeparator) + + sItem; + *pFilterNm = "DDE"; + bRet = true; + } + } + } + return bRet; +} + +/** Make a graphic object ready for UNDO. + * + * If it is already in storage, it needs to be loaded. + */ +bool SwGrfNode::SavePersistentData() +{ + if( mxLink.is() ) + { + OSL_ENSURE( !mbInSwapIn, "SavePersistentData: I am still in SwapIn" ); + GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( mxLink.get() ); + return true; + } + + // swap in first if in storage + if( HasEmbeddedStreamName() && !SwapIn() ) + return false; + + // #i44367# + // Do not delete graphic file in storage, because the graphic file could + // be referenced by other graphic nodes. + // Because it's hard to detect this case here and it would only fix + // one problem with shared graphic files - there are also problems, if + // a certain graphic file is referenced by two independent graphic nodes, + // brush item or drawing objects, the stream isn't no longer removed here. + // To do this stuff correct, a reference counting on shared streams + // inside one document has to be implemented. + // Important note: see also fix for #i40014# + + // swap out into temp file + return true; +} + +bool SwGrfNode::RestorePersistentData() +{ + if( mxLink.is() ) + { + IDocumentLinksAdministration& rIDLA = getIDocumentLinksAdministration(); + mxLink->SetVisible( rIDLA.IsVisibleLinks() ); + rIDLA.GetLinkManager().InsertDDELink( mxLink.get() ); + if( getIDocumentLayoutAccess().GetCurrentLayout() ) + mxLink->Update(); + } + return true; +} + +void SwGrfNode::InsertLink( const OUString& rGrfName, const OUString& rFltName ) +{ + mxLink = new SwBaseLink( SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::GDIMETAFILE, this ); + + IDocumentLinksAdministration& rIDLA = getIDocumentLinksAdministration(); + if( GetNodes().IsDocNodes() ) + { + mxLink->SetVisible( rIDLA.IsVisibleLinks() ); + if( rFltName == "DDE" ) + { + sal_Int32 nTmp = 0; + const OUString sApp{ rGrfName.getToken( 0, sfx2::cTokenSeparator, nTmp ) }; + const OUString sTopic{ rGrfName.getToken( 0, sfx2::cTokenSeparator, nTmp ) }; + const OUString sItem{ rGrfName.copy( nTmp ) }; + rIDLA.GetLinkManager().InsertDDELink( mxLink.get(), sApp, sTopic, sItem ); + } + else + { + const bool bSync = rFltName == "SYNCHRON"; + mxLink->SetSynchron( bSync ); + mxLink->SetContentType( SotClipboardFormatId::SVXB ); + + rIDLA.GetLinkManager().InsertFileLink( *mxLink, + sfx2::SvBaseLinkObjectType::ClientGraphic, rGrfName, + (!bSync && !rFltName.isEmpty() ? &rFltName : nullptr) ); + } + } +} + +void SwGrfNode::ReleaseLink() +{ + if( mxLink.is() ) + { + Graphic aLocalGraphic(maGrfObj.GetGraphic()); + const bool bHasOriginalData(aLocalGraphic.IsGfxLink()); + + { + mbInSwapIn = true; + SwBaseLink* pLink = static_cast<SwBaseLink*>( mxLink.get() ); + pLink->SwapIn( true, true ); + mbInSwapIn = false; + } + + getIDocumentLinksAdministration().GetLinkManager().Remove( mxLink.get() ); + mxLink.clear(); + aLocalGraphic.setOriginURL(""); + + // #i15508# added extra processing after getting rid of the link. Use whatever is + // known from the formerly linked graphic to get to a state as close to a directly + // unlinked inserted graphic as possible. Goal is to have a valid GfxLink at the + // ImplGraphic (see there) that holds temporary data to the original data and type + // information about the original data. Only when this is given will + // SvXMLGraphicHelper::ImplInsertGraphicURL which is used at export use that type + // and use the original graphic at export for the ODF, without evtl. recoding + // of the bitmap graphic data to something without loss (e.g. PNG) but bigger + if(bHasOriginalData) + { + // #i15508# if we have the original data at the Graphic, let it survive + // by using that Graphic again, this time at a GraphicObject without link. + // This happens e.g. when inserting a linked graphic and breaking the link + maGrfObj.SetGraphic(aLocalGraphic); + } + } +} + +void SwGrfNode::SetTwipSize( const Size& rSz ) +{ + mnGrfSize = rSz; + if( IsScaleImageMap() && mnGrfSize.Width() && mnGrfSize.Height() ) + { + // resize Image-Map to size of the graphic + ScaleImageMap(); + + // do not re-scale Image-Map + SetScaleImageMap( false ); + } +} + +void SwGrfNode::ScaleImageMap() +{ + if( !mnGrfSize.Width() || !mnGrfSize.Height() ) + return; + + // re-scale Image-Map + SwFrameFormat* pFormat = GetFlyFormat(); + + if( !pFormat ) + return; + + SwFormatURL aURL( pFormat->GetURL() ); + if ( !aURL.GetMap() ) + return; + + bool bScale = false; + Fraction aScaleX( 1, 1 ); + Fraction aScaleY( 1, 1 ); + + const SwFormatFrameSize& rFrameSize = pFormat->GetFrameSize(); + const SvxBoxItem& rBox = pFormat->GetBox(); + + if( !rFrameSize.GetWidthPercent() ) + { + SwTwips nWidth = rFrameSize.GetWidth(); + + nWidth -= rBox.CalcLineSpace(SvxBoxItemLine::LEFT) + + rBox.CalcLineSpace(SvxBoxItemLine::RIGHT); + + OSL_ENSURE( nWidth>0, "Do any 0 twip wide graphics exist!?" ); + + if( mnGrfSize.Width() != nWidth ) + { + aScaleX = Fraction( mnGrfSize.Width(), nWidth ); + bScale = true; + } + } + if( !rFrameSize.GetHeightPercent() ) + { + SwTwips nHeight = rFrameSize.GetHeight(); + + nHeight -= rBox.CalcLineSpace(SvxBoxItemLine::TOP) + + rBox.CalcLineSpace(SvxBoxItemLine::BOTTOM); + + OSL_ENSURE( nHeight>0, "Do any 0 twip high graphics exist!?" ); + + if( mnGrfSize.Height() != nHeight ) + { + aScaleY = Fraction( mnGrfSize.Height(), nHeight ); + bScale = true; + } + } + + if( bScale ) + { + aURL.GetMap()->Scale( aScaleX, aScaleY ); + pFormat->SetFormatAttr( aURL ); + } +} + +SwContentNode* SwGrfNode::MakeCopy(SwDoc* pDoc, const SwNodeIndex& rIdx, bool) const +{ + // copy formats into the other document + SwGrfFormatColl* pColl = pDoc->CopyGrfColl( *GetGrfColl() ); + + Graphic aTmpGrf = GetGrf(); + + OUString sFile, sFilter; + if( IsLinkedFile() ) + sfx2::LinkManager::GetDisplayNames( mxLink.get(), nullptr, &sFile, nullptr, &sFilter ); + else if( IsLinkedDDE() ) + { + OUString sTmp1, sTmp2; + sfx2::LinkManager::GetDisplayNames( mxLink.get(), &sTmp1, &sTmp2, &sFilter ); + sfx2::MakeLnkName( sFile, &sTmp1, sTmp2, sFilter ); + sFilter = "DDE"; + } + + SwGrfNode* pGrfNd = SwNodes::MakeGrfNode( rIdx, sFile, sFilter, + &aTmpGrf, pColl, + GetpSwAttrSet() ); + pGrfNd->SetTitle( GetTitle() ); + pGrfNd->SetDescription( GetDescription() ); + pGrfNd->SetContour( HasContour(), HasAutomaticContour() ); + return pGrfNd; +} + +/// returns the Graphic-Attr-Structure filled with our graphic attributes +GraphicAttr& SwGrfNode::GetGraphicAttr( GraphicAttr& rGA, + const SwFrame* pFrame ) const +{ + const SwAttrSet& rSet = GetSwAttrSet(); + + rGA.SetDrawMode( rSet.GetDrawModeGrf().GetValue() ); + + const SwMirrorGrf & rMirror = rSet.GetMirrorGrf(); + BmpMirrorFlags nMirror = BmpMirrorFlags::NONE; + if( rMirror.IsGrfToggle() && pFrame && !pFrame->FindPageFrame()->OnRightPage() ) + { + switch( rMirror.GetValue() ) + { + case MirrorGraph::Dont: + nMirror = BmpMirrorFlags::Horizontal; + break; + case MirrorGraph::Vertical: + nMirror = BmpMirrorFlags::NONE; + break; + case MirrorGraph::Horizontal: + nMirror = BmpMirrorFlags::Horizontal|BmpMirrorFlags::Vertical; + break; + default: + nMirror = BmpMirrorFlags::Vertical; + break; + } + } + else + switch( rMirror.GetValue() ) + { + case MirrorGraph::Both: + nMirror = BmpMirrorFlags::Horizontal|BmpMirrorFlags::Vertical; + break; + case MirrorGraph::Vertical: + nMirror = BmpMirrorFlags::Horizontal; + break; + case MirrorGraph::Horizontal: + nMirror = BmpMirrorFlags::Vertical; + break; + default: break; + } + + rGA.SetMirrorFlags( nMirror ); + + const SwCropGrf& rCrop = rSet.GetCropGrf(); + rGA.SetCrop( convertTwipToMm100( rCrop.GetLeft() ), + convertTwipToMm100( rCrop.GetTop() ), + convertTwipToMm100( rCrop.GetRight() ), + convertTwipToMm100( rCrop.GetBottom() )); + + const SwRotationGrf& rRotation = rSet.GetRotationGrf(); + rGA.SetRotation( rRotation.GetValue() ); + + rGA.SetLuminance( rSet.GetLuminanceGrf().GetValue() ); + rGA.SetContrast( rSet.GetContrastGrf().GetValue() ); + rGA.SetChannelR( rSet.GetChannelRGrf().GetValue() ); + rGA.SetChannelG( rSet.GetChannelGGrf().GetValue() ); + rGA.SetChannelB( rSet.GetChannelBGrf().GetValue() ); + rGA.SetGamma( rSet.GetGammaGrf().GetValue() ); + rGA.SetInvert( rSet.GetInvertGrf().GetValue() ); + + const sal_uInt16 nTrans = rSet.GetTransparencyGrf().GetValue(); + rGA.SetTransparency( static_cast<sal_uInt8>(FRound( + std::min( nTrans, sal_uInt16(100) ) * 2.55 )) ); + + return rGA; +} + +bool SwGrfNode::IsTransparent() const +{ + return maGrfObj.IsTransparent() || + GetSwAttrSet().GetTransparencyGrf().GetValue() != 0; +} + +void SwGrfNode::TriggerAsyncRetrieveInputStream() +{ + if ( !IsLinkedFile() ) + { + OSL_FAIL( "<SwGrfNode::TriggerAsyncLoad()> - Method is misused. Method call is only valid for graphic nodes, which refer a linked graphic file" ); + return; + } + + if (mpThreadConsumer == nullptr) + { + mpThreadConsumer.reset(new SwAsyncRetrieveInputStreamThreadConsumer(*this), o3tl::default_delete<SwAsyncRetrieveInputStreamThreadConsumer>()); + + OUString sGrfNm; + sfx2::LinkManager::GetDisplayNames( mxLink.get(), nullptr, &sGrfNm ); + OUString sReferer; + SfxObjectShell * sh = GetDoc()->GetPersist(); + if (sh != nullptr && sh->HasName()) + { + sReferer = sh->GetMedium()->GetName(); + } + mpThreadConsumer->CreateThread( sGrfNm, sReferer ); + } +} + + +void SwGrfNode::ApplyInputStream( + const css::uno::Reference<css::io::XInputStream>& xInputStream, + const bool bIsStreamReadOnly ) +{ + if ( IsLinkedFile() ) + { + if ( xInputStream.is() ) + { + mxInputStream = xInputStream; + mbIsStreamReadOnly = bIsStreamReadOnly; + mbLinkedInputStreamReady = true; + SwMsgPoolItem aMsgHint( RES_LINKED_GRAPHIC_STREAM_ARRIVED ); + ModifyNotification( &aMsgHint, &aMsgHint ); + } + } +} + +void SwGrfNode::UpdateLinkWithInputStream() +{ + // do not work on link, if a <SwapIn> has been triggered. + if ( !mbInSwapIn && IsLinkedFile() ) + { + GetLink()->setStreamToLoadFrom( mxInputStream, mbIsStreamReadOnly ); + GetLink()->Update(); + SwMsgPoolItem aMsgHint( RES_GRAPHIC_ARRIVED ); + ModifyNotification( &aMsgHint, &aMsgHint ); + + // #i88291# + mxInputStream.clear(); + GetLink()->clearStreamToLoadFrom(); + mbLinkedInputStreamReady = false; + mpThreadConsumer.reset(); + } +} + +// #i90395# +bool SwGrfNode::IsAsyncRetrieveInputStreamPossible() const +{ + bool bRet = false; + + if ( IsLinkedFile() ) + { + OUString sGrfNm; + sfx2::LinkManager::GetDisplayNames( mxLink.get(), nullptr, &sGrfNm ); + if ( !sGrfNm.startsWith( "vnd.sun.star.pkg:" ) ) + { + bRet = true; + } + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/AccessibilityCheck.hxx b/sw/source/core/inc/AccessibilityCheck.hxx new file mode 100644 index 000000000..4bcc56000 --- /dev/null +++ b/sw/source/core/inc/AccessibilityCheck.hxx @@ -0,0 +1,38 @@ +/* -*- 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/. + * + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESSIBILITYCHECK_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESSIBILITYCHECK_HXX + +#include <sfx2/AccessibilityCheck.hxx> +#include <doc.hxx> + +namespace sw +{ +class SW_DLLPUBLIC AccessibilityCheck final : public sfx::AccessibilityCheck +{ +private: + SwDoc* m_pDoc; + +public: + AccessibilityCheck(SwDoc* pDoc) + : m_pDoc(pDoc) + { + } + + void check() override; + void checkObject(SdrObject* pObject); +}; + +} // end sw namespace + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/AccessibilityIssue.hxx b/sw/source/core/inc/AccessibilityIssue.hxx new file mode 100644 index 000000000..3b5e12c67 --- /dev/null +++ b/sw/source/core/inc/AccessibilityIssue.hxx @@ -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/. + * + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_ACCESSIBILITYISSUE_HXX +#define INCLUDED_SW_SOURCE_CORE_ACCESSIBILITYISSUE_HXX + +#include <sfx2/AccessibilityIssue.hxx> +#include <doc.hxx> + +namespace sw +{ +enum class IssueObject +{ + UNKNOWN, + GRAPHIC, + OLE, + TABLE, + TEXT, +}; + +class AccessibilityIssue final : public sfx::AccessibilityIssue +{ +private: + IssueObject m_eIssueObject; + SwDoc* m_pDoc; + OUString m_sObjectID; + std::vector<OUString> m_aIssueAdditionalInfo; + SwNode* m_pNode; + + sal_Int32 m_nStart; + sal_Int32 m_nEnd; + +public: + AccessibilityIssue(sfx::AccessibilityIssueID eIssueID = sfx::AccessibilityIssueID::UNSPECIFIED); + + void setIssueObject(IssueObject eIssueObject); + void setDoc(SwDoc* pDoc); + void setObjectID(OUString const& rID); + void setNode(SwNode* pNode) { m_pNode = pNode; } + + void setStart(sal_Int32 nStart) { m_nStart = nStart; } + + void setEnd(sal_Int32 nEnd) { m_nEnd = nEnd; } + + std::vector<OUString> const& getAdditionalInfo() const { return m_aIssueAdditionalInfo; } + + void setAdditionalInfo(std::vector<OUString> const& rIssueAdditionalInfo) + { + m_aIssueAdditionalInfo = rIssueAdditionalInfo; + } + + bool canGotoIssue() const override; + void gotoIssue() const override; +}; + +} // end sw namespace + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DateFormFieldButton.hxx b/sw/source/core/inc/DateFormFieldButton.hxx new file mode 100644 index 000000000..8d0e4d6c4 --- /dev/null +++ b/sw/source/core/inc/DateFormFieldButton.hxx @@ -0,0 +1,45 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_DATEFORMEFIELDBUTTO_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_DATEFORMEFIELDBUTTO_HXX + +#include "FormFieldButton.hxx" + +class SwEditWin; +class FloatingWindow; +class SvNumberFormatter; +namespace sw +{ +namespace mark +{ +class DateFieldmark; +} +} // namespace sw + +/** + * This button is shown when the cursor is on a date form field. + * The user can select a date from a date picker while filling in a form. + */ +class DateFormFieldButton : public FormFieldButton +{ +public: + DateFormFieldButton(SwEditWin* pEditWin, sw::mark::DateFieldmark& rFieldMark, + SvNumberFormatter* pNumberFormatter); + virtual ~DateFormFieldButton() override; + + virtual void InitPopup() override; + +private: + SvNumberFormatter* m_pNumberFormatter; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/inc/DocumentChartDataProviderManager.hxx b/sw/source/core/inc/DocumentChartDataProviderManager.hxx new file mode 100644 index 000000000..1759f5b94 --- /dev/null +++ b/sw/source/core/inc/DocumentChartDataProviderManager.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTCHARTDATAPROVIDEMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTCHARTDATAPROVIDEMANAGER_HXX + +#include <IDocumentChartDataProviderAccess.hxx> +#include <o3tl/deleter.hxx> +#include <rtl/ref.hxx> +#include <memory> + +class SwTable; +class SwChartDataProvider; +class SwChartLockController_Helper; +class SwDoc; + + +namespace sw { + +class DocumentChartDataProviderManager : public IDocumentChartDataProviderAccess +{ + +public: + + DocumentChartDataProviderManager( SwDoc& i_rSwdoc ); + + SwChartDataProvider * GetChartDataProvider( bool bCreate = false ) const override; + + void CreateChartInternalDataProviders( const SwTable *pTable ) override; + + SwChartLockController_Helper & GetChartControllerHelper() override; + + virtual ~DocumentChartDataProviderManager() override; + +private: + + DocumentChartDataProviderManager(DocumentChartDataProviderManager const&) = delete; + DocumentChartDataProviderManager& operator=(DocumentChartDataProviderManager const&) = delete; + + SwDoc& m_rDoc; + + mutable rtl::Reference<SwChartDataProvider> maChartDataProviderImplRef; + std::unique_ptr<SwChartLockController_Helper, o3tl::default_delete<SwChartLockController_Helper>> mpChartControllerHelper; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx b/sw/source/core/inc/DocumentContentOperationsManager.hxx new file mode 100644 index 000000000..3df395329 --- /dev/null +++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx @@ -0,0 +1,192 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTCONTENTOPERATIONSMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTCONTENTOPERATIONSMANAGER_HXX + +#include <IDocumentContentOperations.hxx> +#include <ndarr.hxx> //Only for lcl_RstTxtAttr + +class SwDoc; +class SwNoTextNode; +class SwFormatColl; +class SwHistory; + +namespace sw +{ + +class DocumentContentOperationsManager : public IDocumentContentOperations +{ +public: + DocumentContentOperationsManager( SwDoc& i_rSwdoc ); + + //Interface methods: + bool CopyRange(SwPaM&, SwPosition&, SwCopyFlags) const override; + + void DeleteSection(SwNode* pNode) override; + + void DeleteRange(SwPaM&) override; + + bool DelFullPara(SwPaM&) override; + + // Add optional parameter <bForceJoinNext>, default value <false> + // Needed for hiding of deletion redlines + bool DeleteAndJoin( SwPaM&, + const bool bForceJoinNext = false ) override; + + bool MoveRange(SwPaM&, SwPosition&, SwMoveFlags) override; + + bool MoveNodeRange(SwNodeRange&, SwNodeIndex&, SwMoveFlags) override; + + bool MoveAndJoin(SwPaM&, SwPosition&) override; + + bool Overwrite(const SwPaM &rRg, const OUString& rStr) override; + + bool InsertString(const SwPaM &rRg, const OUString&, + const SwInsertFlags nInsertMode = SwInsertFlags::EMPTYEXPAND ) override; + + void TransliterateText(const SwPaM& rPaM, utl::TransliterationWrapper&) override; + + SwFlyFrameFormat* InsertGraphic(const SwPaM &rRg, const OUString& rGrfName, const OUString& rFltName, const Graphic* pGraphic, + const SfxItemSet* pFlyAttrSet, const SfxItemSet* pGrfAttrSet, SwFrameFormat*) override; + + SwFlyFrameFormat* InsertGraphicObject(const SwPaM& rRg, const GraphicObject& rGrfObj, const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet) override; + + void ReRead(SwPaM&, const OUString& rGrfName, const OUString& rFltName, const Graphic* pGraphic) override; + + SwDrawFrameFormat* InsertDrawObj( const SwPaM &rRg, SdrObject& rDrawObj, const SfxItemSet& rFlyAttrSet ) override; + + SwFlyFrameFormat* InsertEmbObject(const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj, SfxItemSet* pFlyAttrSet) override; + + SwFlyFrameFormat* InsertOLE(const SwPaM &rRg, const OUString& rObjName, sal_Int64 nAspect, const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet) override; + + bool SplitNode(const SwPosition &rPos, bool bChkTableStart) override; + + bool AppendTextNode(SwPosition& rPos) override; + + bool ReplaceRange(SwPaM& rPam, const OUString& rNewStr, + const bool bRegExReplace) override; + + // Add a para for the char attribute exp... + bool InsertPoolItem(const SwPaM &rRg, const SfxPoolItem&, + const SetAttrMode nFlags = SetAttrMode::DEFAULT, + SwRootFrame const* pLayout = nullptr, + bool bExpandCharToPara = false, + SwTextAttr **ppNewTextAttr = nullptr) override; + + void InsertItemSet (const SwPaM &rRg, const SfxItemSet&, + const SetAttrMode nFlags = SetAttrMode::DEFAULT, + SwRootFrame const* pLayout = nullptr) override; + + void RemoveLeadingWhiteSpace(const SwPosition & rPos ) override; + + + //Non-Interface methods + + void DeleteDummyChar(SwPosition const& rPos, sal_Unicode cDummy); + + void CopyWithFlyInFly( const SwNodeRange& rRg, + const SwNodeIndex& rInsPos, + const std::pair<const SwPaM&, const SwPosition&> * pCopiedPaM = nullptr, + bool bMakeNewFrames = true, + bool bDelRedlines = true, + bool bCopyFlyAtFly = false, + SwCopyFlags flags = SwCopyFlags::Default) const; + void CopyFlyInFlyImpl( const SwNodeRange& rRg, + SwPaM const*const pCopiedPaM, + const SwNodeIndex& rStartIdx, + const bool bCopyFlyAtFly = false, + SwCopyFlags flags = SwCopyFlags::Default) const; + + /// Parameters for _Rst and lcl_SetTextFormatColl + //originallyfrom docfmt.cxx + struct ParaRstFormat + { + SwFormatColl* pFormatColl; + SwHistory* pHistory; + const SwPosition *pSttNd, *pEndNd; + const SfxItemSet* pDelSet; + SwRootFrame const*const pLayout; + sal_uInt16 nWhich; + bool bReset; + bool bResetListAttrs; // #i62575# + bool bResetAll; + bool bInclRefToxMark; + /// From the attributes included in the range, delete only the ones which have exactly same range. Don't delete the ones which are simply included in the range. + bool bExactRange; + + ParaRstFormat(const SwPosition* pStt, const SwPosition* pEnd, + SwHistory* pHst, const SfxItemSet* pSet = nullptr, + SwRootFrame const*const pLay = nullptr) + : pFormatColl(nullptr) + , pHistory(pHst) + , pSttNd(pStt) + , pEndNd(pEnd) + , pDelSet(pSet) + , pLayout(pLay) + , nWhich(0) + , bReset(false) // #i62675# + , bResetListAttrs(false) + , bResetAll(true) + , bInclRefToxMark(false) + , bExactRange(false) + { + } + }; + static bool lcl_RstTextAttr( const SwNodePtr& rpNd, void* pArgs ); //originally from docfmt.cxx + + + virtual ~DocumentContentOperationsManager() override; + +private: + SwDoc& m_rDoc; + + bool DeleteAndJoinImpl(SwPaM&, const bool); + bool DeleteAndJoinWithRedlineImpl(SwPaM&, const bool unused = false); + bool DeleteRangeImpl(SwPaM&, const bool unused = false); + bool DeleteRangeImplImpl(SwPaM &); + bool ReplaceRangeImpl(SwPaM&, OUString const&, const bool); + SwFlyFrameFormat* InsNoTextNode( const SwPosition&rPos, SwNoTextNode*, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* ); + /* Copy a range within the same or to another document. + Position may not lie within range! */ + bool CopyImpl( SwPaM&, SwPosition&, + SwCopyFlags flags, SwPaM *const pCpyRng /*= 0*/) const; + bool CopyImplImpl(SwPaM&, SwPosition&, + SwCopyFlags flags, SwPaM *const pCpyRng /*= 0*/) const; + + DocumentContentOperationsManager(DocumentContentOperationsManager const&) = delete; + DocumentContentOperationsManager& operator=(DocumentContentOperationsManager const&) = delete; +}; + + +void CopyBookmarks(const SwPaM& rPam, SwPosition& rTarget); + +void CalcBreaks(std::vector<std::pair<sal_uLong, sal_Int32>> & rBreaks, + SwPaM const & rPam, bool const isOnlyFieldmarks = false); + +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTCONTENTOPERATIONSMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentDeviceManager.hxx b/sw/source/core/inc/DocumentDeviceManager.hxx new file mode 100644 index 000000000..46c682817 --- /dev/null +++ b/sw/source/core/inc/DocumentDeviceManager.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTDEVICEMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTDEVICEMANAGER_HXX + +#include <IDocumentDeviceAccess.hxx> +#include <vcl/vclptr.hxx> +#include <memory> + +class SwDoc; +class SfxPrinter; +class VirtualDevice; +class OutputDevice; +class JobSetup; +class SwPrintData; + +namespace sw { + +class DocumentDeviceManager : public IDocumentDeviceAccess +{ + +public: + + DocumentDeviceManager( SwDoc& i_rSwdoc ); + + SfxPrinter* getPrinter(/*[in]*/ bool bCreate ) const override; + + void setPrinter(/*[in]*/ SfxPrinter *pP,/*[in]*/ bool bDeleteOld,/*[in]*/ bool bCallPrtDataChanged ) override; + + VirtualDevice* getVirtualDevice(/*[in]*/ bool bCreate ) const override; + + void setVirtualDevice(/*[in]*/ VirtualDevice* pVd ) override; + + OutputDevice* getReferenceDevice(/*[in]*/ bool bCreate ) const override; + + void setReferenceDeviceType(/*[in]*/ bool bNewVirtual, /*[in]*/ bool bNewHiRes ) override; + + const JobSetup* getJobsetup() const override; + + void setJobsetup(/*[in]*/ const JobSetup &rJobSetup ) override; + + const SwPrintData & getPrintData() const override; + + void setPrintData(/*[in]*/ const SwPrintData& rPrtData ) override; + + virtual ~DocumentDeviceManager() override; + +private: + + DocumentDeviceManager(DocumentDeviceManager const&) = delete; + DocumentDeviceManager& operator=(DocumentDeviceManager const&) = delete; + + VirtualDevice& CreateVirtualDevice_() const; + SfxPrinter& CreatePrinter_() const; + void PrtDataChanged(); /**< Printer or JobSetup altered. + Care has to be taken of the necessary + invalidations and notifications. */ + + SwDoc& m_rDoc; + VclPtr<SfxPrinter> mpPrt; + VclPtr<VirtualDevice> mpVirDev; + std::unique_ptr<SwPrintData> mpPrtData; +}; + +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentDrawModelManager.hxx b/sw/source/core/inc/DocumentDrawModelManager.hxx new file mode 100644 index 000000000..2f86e2d73 --- /dev/null +++ b/sw/source/core/inc/DocumentDrawModelManager.hxx @@ -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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTDRAWMODELMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTDRAWMODELMANAGER_HXX + +#include <IDocumentDrawModelAccess.hxx> +#include <svx/svdtypes.hxx> +#include <memory> +#include <drawdoc.hxx> + +class SdrPageView; +class SwDoc; + +namespace sw +{ + +class DocumentDrawModelManager : public IDocumentDrawModelAccess +{ +public: + + DocumentDrawModelManager( SwDoc& i_rSwdoc ); + + void InitDrawModel(); + void ReleaseDrawModel(); + void DrawNotifyUndoHdl(); + + //IDocumentDrawModelAccess + virtual const SwDrawModel* GetDrawModel() const override; + virtual SwDrawModel* GetDrawModel() override; + virtual SwDrawModel* MakeDrawModel_() override; + virtual SwDrawModel* GetOrCreateDrawModel() override; + virtual SdrLayerID GetHeavenId() const override; + virtual SdrLayerID GetHellId() const override; + virtual SdrLayerID GetControlsId() const override; + virtual SdrLayerID GetInvisibleHeavenId() const override; + virtual SdrLayerID GetInvisibleHellId() const override; + virtual SdrLayerID GetInvisibleControlsId() const override; + + virtual void NotifyInvisibleLayers( SdrPageView& _rSdrPageView ) override; + + virtual bool IsVisibleLayerId( SdrLayerID _nLayerId ) const override; + + virtual SdrLayerID GetInvisibleLayerIdByVisibleOne( SdrLayerID _nVisibleLayerId ) override; + + virtual bool Search(const SwPaM& rPaM, const SvxSearchItem& rSearchItem) override; + +private: + + DocumentDrawModelManager(DocumentDrawModelManager const&) = delete; + DocumentDrawModelManager& operator=(DocumentDrawModelManager const&) = delete; + + SwDoc& m_rDoc; + + std::unique_ptr<SwDrawModel> mpDrawModel; + + /** Draw Model Layer IDs + * LayerIds, Heaven == above document + * Hell == below document + * Controls == at the very top + */ + SdrLayerID mnHeaven; + SdrLayerID mnHell; + SdrLayerID mnControls; + SdrLayerID mnInvisibleHeaven; + SdrLayerID mnInvisibleHell; + SdrLayerID mnInvisibleControls; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentExternalDataManager.hxx b/sw/source/core/inc/DocumentExternalDataManager.hxx new file mode 100644 index 000000000..068d87eed --- /dev/null +++ b/sw/source/core/inc/DocumentExternalDataManager.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTEXTERNALDATAMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTEXTERNALDATAMANAGER_HXX + +#include <IDocumentExternalData.hxx> + +namespace sw { + +class DocumentExternalDataManager : public IDocumentExternalData +{ + +private: + DocumentExternalDataManager(DocumentExternalDataManager const&) = delete; + DocumentExternalDataManager& operator=(DocumentExternalDataManager const&) = delete; + +public: + DocumentExternalDataManager() = default; + + void setExternalData( ::sw::tExternalDataType eType, ::sw::tExternalDataPointer pPayload) override; + ::sw::tExternalDataPointer getExternalData(::sw::tExternalDataType eType) override; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/sw/source/core/inc/DocumentFieldsManager.hxx b/sw/source/core/inc/DocumentFieldsManager.hxx new file mode 100644 index 000000000..c73345f66 --- /dev/null +++ b/sw/source/core/inc/DocumentFieldsManager.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTFIELDSMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTFIELDSMANAGER_HXX + +#include <IDocumentFieldsAccess.hxx> +#include <sal/types.h> +#include <memory> + +class SwDoc; +class SwDBNameInfField; + +namespace sw { + +class DocumentFieldsManager : public IDocumentFieldsAccess +{ + +public: + + DocumentFieldsManager( SwDoc& i_rSwdoc ); + + virtual const SwFieldTypes *GetFieldTypes() const override; + virtual SwFieldType *InsertFieldType(const SwFieldType &) override; + virtual SwFieldType *GetSysFieldType( const SwFieldIds eWhich ) const override; + virtual SwFieldType* GetFieldType(SwFieldIds nResId, const OUString& rName, bool bDbFieldMatching) const override; + virtual void RemoveFieldType(size_t nField) override; + virtual void UpdateFields(bool bCloseDB) override; + virtual void InsDeletedFieldType(SwFieldType &) override; + virtual void PutValueToField(const SwPosition & rPos, const css::uno::Any& rVal, sal_uInt16 nWhich) override; + virtual bool UpdateField(SwTextField * rDstFormatField, SwField & rSrcField, SwMsgPoolItem * pMsgHint, bool bUpdateTableFields) override; + virtual void UpdateRefFields() override; + virtual void UpdateTableFields(SfxPoolItem* pHt) override; + virtual void UpdateExpFields(SwTextField* pField, bool bUpdateRefFields) override; + virtual void UpdateUsrFields() override; + virtual void UpdatePageFields(SfxPoolItem*) override; + virtual void LockExpFields() override; + virtual void UnlockExpFields() override; + virtual bool IsExpFieldsLocked() const override; + virtual SwDocUpdateField& GetUpdateFields() const override; + virtual bool SetFieldsDirty(bool b, const SwNode* pChk, sal_uLong nLen) override; + virtual void SetFixFields(const DateTime* pNewDateTime) override; + virtual void FieldsToCalc(SwCalc& rCalc, sal_uLong nLastNd, sal_uInt16 nLastCnt) override; + virtual void FieldsToCalc(SwCalc& rCalc, const SetGetExpField& rToThisField, SwRootFrame const* pLayout) override; + virtual void FieldsToExpand(SwHashTable<HashStr>& rTable, const SetGetExpField& rToThisField, SwRootFrame const& rLayout) override; + virtual bool IsNewFieldLst() const override; + virtual void SetNewFieldLst( bool bFlag) override; + virtual void InsDelFieldInFieldLst(bool bIns, const SwTextField& rField) override; + virtual sal_Int32 GetRecordsPerDocument() const override; + + //Non Interface methods + + /** Returns the field at a certain position. + @param rPos position to search at + @return pointer to field at the given position or NULL in case no field is found + */ + static SwField* GetFieldAtPos(const SwPosition& rPos); + + /** Returns the field at a certain position. + @param rPos position to search at + @return pointer to field at the given position or NULL in case no field is found + */ + static SwTextField* GetTextFieldAtPos(const SwPosition& rPos); + + bool containsUpdatableFields(); + + // Delete all unreferenced field types. + void GCFieldTypes(); + + void InitFieldTypes(); + + void ClearFieldTypes(); + + void UpdateDBNumFields( SwDBNameInfField& rDBField, SwCalc& rCalc ); + + virtual ~DocumentFieldsManager() override; + +private: + + DocumentFieldsManager(DocumentFieldsManager const&) = delete; + DocumentFieldsManager& operator=(DocumentFieldsManager const&) = delete; + + void UpdateExpFieldsImpl(SwTextField* pField, SwRootFrame const* pLayout); + + SwDoc& m_rDoc; + + bool mbNewFieldLst; //< TRUE: Rebuild field-list. + std::unique_ptr<SwDocUpdateField> mpUpdateFields; //< Struct for updating fields + std::unique_ptr<SwFieldTypes> mpFieldTypes; + sal_Int8 mnLockExpField; //< If != 0 UpdateExpFields() has no effect! +}; + +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentLayoutManager.hxx b/sw/source/core/inc/DocumentLayoutManager.hxx new file mode 100644 index 000000000..c9813dbf8 --- /dev/null +++ b/sw/source/core/inc/DocumentLayoutManager.hxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLAYOUTMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLAYOUTMANAGER_HXX + +#include <IDocumentLayoutAccess.hxx> +#include <memory> + +class SwDoc; +class SwViewShell; +class SwLayouter; + +namespace sw { + +class DocumentLayoutManager : public IDocumentLayoutAccess +{ + +public: + + DocumentLayoutManager( SwDoc& i_rSwdoc ); + + virtual const SwViewShell *GetCurrentViewShell() const override; + virtual SwViewShell *GetCurrentViewShell() override; //< It must be able to communicate to a SwViewShell.This is going to be removed later. + virtual void SetCurrentViewShell( SwViewShell* pNew ) override; + + virtual const SwRootFrame *GetCurrentLayout() const override; + virtual SwRootFrame *GetCurrentLayout() override; + virtual bool HasLayout() const override; + + virtual const SwLayouter* GetLayouter() const override; + virtual SwLayouter* GetLayouter() override; + virtual void SetLayouter( SwLayouter* pNew ) override; + + virtual SwFrameFormat* MakeLayoutFormat( RndStdIds eRequest, const SfxItemSet* pSet ) override; + virtual void DelLayoutFormat( SwFrameFormat *pFormat ) override; + virtual SwFrameFormat* CopyLayoutFormat( const SwFrameFormat& rSrc, const SwFormatAnchor& rNewAnchor, bool bSetTextFlyAtt, bool bMakeFrames ) override; + + //Non Interface methods + void ClearSwLayouterEntries(); + + virtual ~DocumentLayoutManager() override; + +private: + + DocumentLayoutManager(DocumentLayoutManager const&) = delete; + DocumentLayoutManager& operator=(DocumentLayoutManager const&) = delete; + + SwDoc& m_rDoc; + + SwViewShell *mpCurrentView; //< SwDoc should get a new member mpCurrentView + std::unique_ptr<SwLayouter> mpLayouter; /**< css::frame::Controller for complex layout formatting + like footnote/endnote in sections */ +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentLinksAdministrationManager.hxx b/sw/source/core/inc/DocumentLinksAdministrationManager.hxx new file mode 100644 index 000000000..8223591d3 --- /dev/null +++ b/sw/source/core/inc/DocumentLinksAdministrationManager.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLINKSADMINISTRATIONMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLINKSADMINISTRATIONMANAGER_HXX + +#include <IDocumentLinksAdministration.hxx> + +#include <memory> + +namespace sfx2 { class LinkManager; } +class SwDoc; +class SwPaM; +class SwNodeRange; + +namespace sw +{ + +class DocumentLinksAdministrationManager : public IDocumentLinksAdministration +{ +public: + + DocumentLinksAdministrationManager( SwDoc& i_rSwdoc ); + + bool IsVisibleLinks() const override; + + void SetVisibleLinks(bool bFlag) override; + + sfx2::LinkManager& GetLinkManager() override; + + const sfx2::LinkManager& GetLinkManager() const override; + + void UpdateLinks() override; + + bool GetData(const OUString& rItem, const OUString& rMimeType, css::uno::Any& rValue) const override; + + void SetData(const OUString& rItem) override; + + ::sfx2::SvLinkSource* CreateLinkSource(const OUString& rItem) override; + + bool EmbedAllLinks() override; + + void SetLinksUpdated(const bool bNewLinksUpdated) override; + + bool LinksUpdated() const override; + + //Non-Interface method + bool SelectServerObj( const OUString& rStr, SwPaM*& rpPam, std::unique_ptr<SwNodeRange>& rpRange ) const; + + virtual ~DocumentLinksAdministrationManager() override; + +private: + + DocumentLinksAdministrationManager(DocumentLinksAdministrationManager const&) = delete; + DocumentLinksAdministrationManager& operator=(DocumentLinksAdministrationManager const&) = delete; + + bool mbVisibleLinks; //< TRUE: Links are inserted visibly. + bool mbLinksUpdated; //< #i38810# flag indicating, that the links have been updated. + std::unique_ptr<sfx2::LinkManager> m_pLinkMgr; //< List of linked stuff (graphics/DDE/OLE). + + SwDoc& m_rDoc; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentListItemsManager.hxx b/sw/source/core/inc/DocumentListItemsManager.hxx new file mode 100644 index 000000000..7508a2b5d --- /dev/null +++ b/sw/source/core/inc/DocumentListItemsManager.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLISTITEMSMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLISTITEMSMANAGER_HXX + +#include <IDocumentListItems.hxx> +#include <memory> +#include <set> + +namespace sw +{ + +class DocumentListItemsManager : public IDocumentListItems +{ +public: + + DocumentListItemsManager(); + + void addListItem( const SwNodeNum& rNodeNum ) override; + void removeListItem( const SwNodeNum& rNodeNum ) override; + + OUString getListItemText(const SwNodeNum& rNodeNum, + SwRootFrame const& rLayout) const override; + + bool isNumberedInLayout(SwNodeNum const& rNodeNum, + SwRootFrame const& rLayout) const override; + + void getNumItems( IDocumentListItems::tSortedNodeNumList& orNodeNumList ) const override; + + virtual ~DocumentListItemsManager() override; + + + //Non Interface + struct lessThanNodeNum + { + bool operator()( const SwNodeNum* pNodeNumOne, + const SwNodeNum* pNodeNumTwo ) const; + }; + + typedef std::set< const SwNodeNum*, lessThanNodeNum > tImplSortedNodeNumList; + +private: + + DocumentListItemsManager(DocumentListItemsManager const&) = delete; + DocumentListItemsManager& operator=(DocumentListItemsManager const&) = delete; + + std::unique_ptr<tImplSortedNodeNumList> mpListItemsList; +}; + +} + + #endif // INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLISTITEMSMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentListsManager.hxx b/sw/source/core/inc/DocumentListsManager.hxx new file mode 100644 index 000000000..2ba7ececf --- /dev/null +++ b/sw/source/core/inc/DocumentListsManager.hxx @@ -0,0 +1,73 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLISTSMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLISTSMANAGER_HXX + +#include <IDocumentListsAccess.hxx> +#include <memory> +#include <unordered_map> + +class SwList; +class SwDoc; + +namespace sw +{ + + +class DocumentListsManager : public IDocumentListsAccess +{ + public: + + DocumentListsManager( SwDoc& i_rSwdoc ); + + SwList* createList( const OUString& rListId, + const OUString& rDefaultListStyleName ) override; + SwList* getListByName( const OUString& rListId ) const override; + + void createListForListStyle( const OUString& rListStyleName ) override; + SwList* getListForListStyle( const OUString& rListStyleName ) const override; + void deleteListForListStyle( const OUString& rListStyleName ) override; + void deleteListsByDefaultListStyle( const OUString& rListStyleName ) override; + // #i91400# + void trackChangeOfListStyleName( const OUString& rListStyleName, + const OUString& rNewListStyleName ) override; + virtual ~DocumentListsManager() override; + + private: + + DocumentListsManager(DocumentListsManager const&) = delete; + DocumentListsManager& operator=(DocumentListsManager const&) = delete; + + SwDoc& m_rDoc; + + // container to hold the lists of the text document + std::unordered_map<OUString, std::unique_ptr<SwList>> maLists; + // relation between list style and its default list + std::unordered_map<OUString, SwList*> maListStyleLists; + + OUString CreateUniqueListId(); + OUString MakeListIdUnique( const OUString& aSuggestedUniqueListId ); +}; + +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTLISTSMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentOutlineNodesManager.hxx b/sw/source/core/inc/DocumentOutlineNodesManager.hxx new file mode 100644 index 000000000..97cc23131 --- /dev/null +++ b/sw/source/core/inc/DocumentOutlineNodesManager.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTOUTLINENODESMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTOUTLINENODESMANAGER_HXX + +#include <IDocumentOutlineNodes.hxx> + +class SwDoc; + +namespace sw +{ + +class DocumentOutlineNodesManager : public IDocumentOutlineNodes +{ +public: + + DocumentOutlineNodesManager( SwDoc& i_rSwdoc ); + + //typedef std::vector< const SwTextNode* > tSortedOutlineNodeList; + + tSortedOutlineNodeList::size_type getOutlineNodesCount() const override; + + int getOutlineLevel( const tSortedOutlineNodeList::size_type nIdx ) const override; + OUString getOutlineText( const tSortedOutlineNodeList::size_type nIdx, + SwRootFrame const* pLayout, + const bool bWithNumber = true, + const bool bWithSpacesForLevel = false, + const bool bWithFootnote = true ) const override; + SwTextNode* getOutlineNode( const tSortedOutlineNodeList::size_type nIdx ) const override; + bool isOutlineInLayout(tSortedOutlineNodeList::size_type nIdx, + SwRootFrame const& rLayout) const override; + void getOutlineNodes( IDocumentOutlineNodes::tSortedOutlineNodeList& orOutlineNodeList ) const override; + + virtual ~DocumentOutlineNodesManager() override; + +private: + + DocumentOutlineNodesManager(DocumentOutlineNodesManager const&) = delete; + DocumentOutlineNodesManager& operator=(DocumentOutlineNodesManager const&) = delete; + + SwDoc& m_rDoc; +}; + +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTOUTLINENODESMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentRedlineManager.hxx b/sw/source/core/inc/DocumentRedlineManager.hxx new file mode 100644 index 000000000..a9811827e --- /dev/null +++ b/sw/source/core/inc/DocumentRedlineManager.hxx @@ -0,0 +1,157 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTREDLINEMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTREDLINEMANAGER_HXX + +#include <IDocumentRedlineAccess.hxx> +#include <memory> + +class SwDoc; + +namespace sw +{ + +class SAL_DLLPUBLIC_RTTI DocumentRedlineManager : public IDocumentRedlineAccess +{ +public: + DocumentRedlineManager( SwDoc& i_rSwdoc ); + + /** + * Replaced by SwRootFrame::IsHideRedlines() (this is model-level redline + * hiding). + */ + virtual RedlineFlags GetRedlineFlags() const override; + + virtual void SetRedlineFlags_intern(/*[in]*/RedlineFlags eMode) override; + + virtual void SetRedlineFlags(/*[in]*/RedlineFlags eMode) override; + + virtual bool IsRedlineOn() const override; + + virtual bool IsIgnoreRedline() const override; + + virtual const SwRedlineTable& GetRedlineTable() const override; + virtual SwRedlineTable& GetRedlineTable() override; + virtual const SwExtraRedlineTable& GetExtraRedlineTable() const override; + virtual SwExtraRedlineTable& GetExtraRedlineTable() override; + virtual bool HasExtraRedlineTable() const override; + + virtual bool IsInRedlines(const SwNode& rNode) const override; + + virtual AppendResult AppendRedline(/*[in]*/SwRangeRedline* pPtr, /*[in]*/bool bCallDelete) override; + + virtual bool AppendTableRowRedline(/*[in]*/SwTableRowRedline* pPtr) override; + virtual bool AppendTableCellRedline(/*[in]*/SwTableCellRedline* pPtr) override; + + virtual bool SplitRedline(/*[in]*/const SwPaM& rPam) override; + + virtual bool DeleteRedline( + /*[in]*/const SwPaM& rPam, + /*[in]*/bool bSaveInUndo, + /*[in]*/RedlineType nDelType) override; + + virtual bool DeleteRedline( + /*[in]*/const SwStartNode& rSection, + /*[in]*/bool bSaveInUndo, + /*[in]*/RedlineType nDelType) override; + + virtual SwRedlineTable::size_type GetRedlinePos( + /*[in]*/const SwNode& rNode, + /*[in]*/RedlineType nType) const override; + + virtual void CompressRedlines() override; + + virtual const SwRangeRedline* GetRedline( + /*[in]*/const SwPosition& rPos, + /*[in]*/SwRedlineTable::size_type* pFndPos) const override; + + virtual bool IsRedlineMove() const override; + + virtual void SetRedlineMove(/*[in]*/bool bFlag) override; + + virtual bool AcceptRedline(/*[in]*/SwRedlineTable::size_type nPos, /*[in]*/bool bCallDelete) override; + + virtual bool AcceptRedline(/*[in]*/const SwPaM& rPam, /*[in]*/bool bCallDelete) override; + + virtual void AcceptRedlineParagraphFormatting(/*[in]*/const SwPaM& rPam) override; + + virtual bool RejectRedline(/*[in]*/SwRedlineTable::size_type nPos, /*[in]*/bool bCallDelete) override; + + virtual bool RejectRedline(/*[in]*/const SwPaM& rPam, /*[in]*/bool bCallDelete) override; + + virtual void AcceptAllRedline(/*[in]*/bool bAcceptReject) override; + + virtual const SwRangeRedline* SelNextRedline(/*[in]*/SwPaM& rPam) const override; + + virtual const SwRangeRedline* SelPrevRedline(/*[in]*/SwPaM& rPam) const override; + + virtual void UpdateRedlineAttr() override; + + virtual std::size_t GetRedlineAuthor() override; + + virtual std::size_t InsertRedlineAuthor(const OUString& rAuthor) override; + + virtual bool SetRedlineComment( + /*[in]*/const SwPaM& rPam, + /*[in]*/const OUString& rComment) override; + + virtual const css::uno::Sequence <sal_Int8>& GetRedlinePassword() const override; + + virtual void SetRedlinePassword( + /*[in]*/const css::uno::Sequence <sal_Int8>& rNewPassword) override; + + //Non Interface methods; + + /** Set comment-text for Redline. It then comes in via AppendRedLine. + Used by AutoFormat. 0-pointer resets mode. + Sequence number is for conjoining of Redlines by the UI. */ + void SetAutoFormatRedlineComment( const OUString* pText, sal_uInt16 nSeqNo = 0 ); + + bool IsHideRedlines() const { return m_bHideRedlines; } + void SetHideRedlines(bool const bHideRedlines) { m_bHideRedlines = bHideRedlines; } + + virtual ~DocumentRedlineManager() override; + +private: + + DocumentRedlineManager(DocumentRedlineManager const&) = delete; + DocumentRedlineManager& operator=(DocumentRedlineManager const&) = delete; + + SwDoc& m_rDoc; + + RedlineFlags meRedlineFlags; //< Current Redline Mode. + std::unique_ptr<SwRedlineTable> mpRedlineTable; //< List of all Ranged Redlines. + std::unique_ptr<SwExtraRedlineTable> mpExtraRedlineTable; //< List of all Extra Redlines. + std::unique_ptr<OUString> mpAutoFormatRedlnComment; //< Comment for Redlines inserted via AutoFormat. + bool mbIsRedlineMove; //< true: Redlines are moved into to / out of the section. + sal_uInt16 mnAutoFormatRedlnCommentNo; /**< SeqNo for conjoining of AutoFormat-Redlines. + by the UI. Managed by SwAutoFormat! */ + css::uno::Sequence <sal_Int8 > maRedlinePasswd; + + /// this flag is necessary for file import because the ViewShell/layout is + /// created "too late" and the ShowRedlineChanges item is not below "Views" + bool m_bHideRedlines = false; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentSettingManager.hxx b/sw/source/core/inc/DocumentSettingManager.hxx new file mode 100644 index 000000000..958a2fcaa --- /dev/null +++ b/sw/source/core/inc/DocumentSettingManager.hxx @@ -0,0 +1,205 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSETTINGMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSETTINGMANAGER_HXX + +#include <IDocumentSettingAccess.hxx> +class SwDoc; +typedef struct _xmlTextWriter* xmlTextWriterPtr; + +namespace sw { +class DocumentSettingManager : + public IDocumentSettingAccess +{ + std::shared_ptr<SvxForbiddenCharactersTable> mxForbiddenCharsTable; + SwDoc &m_rDoc; + + sal_uInt16 mnLinkUpdMode; //< UpdateMode for links. + + SwFieldUpdateFlags meFieldUpdMode;//< Automatically Update Mode for fields/charts. + CharCompressType meChrCmprType;//< for ASIAN: compress punctuation/kana + + sal_uInt32 mn32DummyCompatibilityOptions1; + sal_uInt32 mn32DummyCompatibilityOptions2; + + // COMPATIBILITY FLAGS START + // + // + // HISTORY OF THE COMPATIBILITY FLAGS: + // + // SO5: + // mbParaSpaceMax def = false, true since SO8 + // mbParaSpaceMaxAtPages def = false, true since SO8 + // + // SO6: + // mbTabCompat def = false, true since SO8 + // + // SO7: + // mbUseVirtualDevice def = true + // mbAddFlyOffsets def = false, hidden + // + // SO7pp1: + // bOldNumbering def = false, hidden + // + // SO8: + // mbAddExternalLeading def = true + // mbUseHiResolutionVirtualDevice def = true, hidden + // mbOldLineSpacing def = false + // mbAddParaSpacingToTableCells def = true + // mbUseFormerObjectPos def = false + // mbUseFormerTextWrapping def = false + // mbConsiderWrapOnObjPos def = false + // + // SO8pp1: + // mbIgnoreFirstLineIndentInNumbering def = false, hidden + // mbDoNotJustifyLinesWithManualBreak def = false, hidden + // mbDoNotResetParaAttrsForNumFont def = false, hidden + // + // SO8pp3 + // mbDoNotCaptureDrawObjsOnPage def = false, hidden + // - Relevant for drawing objects, which don't follow the text flow, but + // whose position is outside the page area: + // false: Such drawing objects are captured on the page area of its anchor. + // true: Such drawing objects can leave the page area, they aren't captured. + // mbTableRowKeep def = false, hidden + // mbIgnoreTabsAndBlanksForLineCalculation def = false, hidden + // mbClipAsCharacterAnchoredWriterFlyFrame def = false, hidden + // - Introduced in order to re-activate clipping of as-character anchored + // Writer fly frames in method <SwFlyInContentFrame::MakeAll()> for documents, + // which are created with version prior SO8/OOo 2.0 + // + // SO8pp4 + // mbUnixForceZeroExtLeading def = false, hidden + // + // SO8pu8 + // + // SO9 + // #i24363# tab stops relative to indent + // mbTabRelativeToIndent def = true, hidden + // #i89181# suppress tab stop at left indent for paragraphs in lists, whose + // list level position and space mode equals LABEL_ALIGNMENT and whose list + // label is followed by a tab character. + // mbTabAtLeftIndentForParagraphsInList def = false, hidden + + bool mbHTMLMode : 1; //< true: Document is in HTMLMode. + bool mbIsGlobalDoc : 1; //< true: It's a global document. + bool mbGlblDocSaveLinks : 1; //< true: Save sections linked in global document. + bool mbIsLabelDoc : 1; //< true: It's a label document. + bool mbPurgeOLE : 1; //< true: Purge OLE-Objects + bool mbKernAsianPunctuation : 1; //< true: kerning also for ASIAN punctuation + + bool mbParaSpaceMax : 1; + bool mbParaSpaceMaxAtPages : 1; + bool mbTabCompat : 1; + bool mbUseVirtualDevice : 1; + bool mbAddFlyOffsets : 1; + bool mbAddVerticalFlyOffsets : 1; + bool mbAddExternalLeading : 1; + bool mbUseHiResolutionVirtualDevice : 1; + bool mbOldLineSpacing : 1; // #i11859# + bool mbAddParaSpacingToTableCells : 1; + bool mbUseFormerObjectPos : 1; // #i11860# + bool mbUseFormerTextWrapping : 1; + bool mbConsiderWrapOnObjPos : 1; // #i28701# + // true: object positioning algorithm has consider the wrapping style of // the floating screen objects as given by its attribute 'WrapInfluenceOnObjPos' + // floating screen objects as given by its + // attribute 'WrapInfluenceOnObjPos'. + bool mbMathBaselineAlignment : 1; // TL 2010-10-29 #i972# + bool mbStylesNoDefault : 1; + bool mbFloattableNomargins : 1; //< If paragraph margins next to a floating table should be ignored. + bool mEmbedFonts : 1; //< Whether to embed fonts when saving. + bool mEmbedUsedFonts : 1; //< Whether to embed fonts that are used by the document when saving. + bool mEmbedLatinScriptFonts : 1; //< Whether to embed latin script fonts when saving. + bool mEmbedAsianScriptFonts : 1; //< Whether to embed asian script fonts when saving. + bool mEmbedComplexScriptFonts : 1; //< Whether to embed complex script fonts when saving. + bool mEmbedSystemFonts : 1; //< Whether to embed also system fonts. + + // non-ui-compatibility flags: + bool mbOldNumbering : 1; + bool mbIgnoreFirstLineIndentInNumbering : 1; // #i47448# + bool mbDoNotJustifyLinesWithManualBreak : 1; // #i49277# + bool mbDoNotResetParaAttrsForNumFont : 1; // #i53199# + bool mbTableRowKeep : 1; + bool mbIgnoreTabsAndBlanksForLineCalculation : 1; // #i3952# + bool mbDoNotCaptureDrawObjsOnPage : 1; // #i62875# + bool mbClipAsCharacterAnchoredWriterFlyFrames : 1; + bool mbUnixForceZeroExtLeading : 1; // #i60945# + bool mbTabRelativeToIndent : 1; // #i24363# tab stops relative to indent + bool mbProtectForm : 1; + bool mbMsWordCompTrailingBlanks : 1; // tdf#104349 tdf#104668 + bool mbMsWordCompMinLineHeightByFly : 1; + bool mbInvertBorderSpacing : 1; + bool mbCollapseEmptyCellPara : 1; + bool mbTabAtLeftIndentForParagraphsInList; // #i89181# - see above + bool mbSmallCapsPercentage66; + bool mbTabOverflow; + bool mbUnbreakableNumberings; + bool mbClippedPictures; + bool mbBackgroundParaOverDrawings; + bool mbTabOverMargin; + bool mbTreatSingleColumnBreakAsPageBreak; // tdf#76349 + bool mbSurroundTextWrapSmall; + bool mbPropLineSpacingShrinksFirstLine; // fdo#79602 + bool mbSubtractFlys; // tdf#86578 + bool mApplyParagraphMarkFormatToNumbering; + bool mbAddParaLineSpacingToTableCells; // tdf#125300 tdf#134782 + + bool mbLastBrowseMode : 1; + bool mbDisableOffPagePositioning; // tdf#112443 + bool mbEmptyDbFieldHidesPara; + bool mbContinuousEndnotes = false; + bool mbProtectBookmarks; + bool mbProtectFields; + bool mbHeaderSpacingBelowLastPara; + +public: + + DocumentSettingManager(SwDoc &rDoc); + virtual ~DocumentSettingManager() override; + + // IDocumentSettingAccess + virtual bool get(/*[in]*/ DocumentSettingId id) const override; + virtual void set(/*[in]*/ DocumentSettingId id, /*[in]*/ bool value) override; + virtual const css::i18n::ForbiddenCharacters* getForbiddenCharacters(/*[in]*/ LanguageType nLang, /*[in]*/ bool bLocaleData ) const override; + virtual void setForbiddenCharacters(/*[in]*/ LanguageType nLang, /*[in]*/ const css::i18n::ForbiddenCharacters& rForbiddenCharacters ) override; + virtual std::shared_ptr<SvxForbiddenCharactersTable>& getForbiddenCharacterTable() override; + virtual const std::shared_ptr<SvxForbiddenCharactersTable>& getForbiddenCharacterTable() const override; + virtual sal_uInt16 getLinkUpdateMode( /*[in]*/bool bGlobalSettings ) const override; + virtual void setLinkUpdateMode( /*[in]*/ sal_uInt16 nMode ) override; + virtual SwFieldUpdateFlags getFieldUpdateFlags( /*[in]*/bool bGlobalSettings ) const override; + virtual void setFieldUpdateFlags( /*[in]*/ SwFieldUpdateFlags eMode ) override; + virtual CharCompressType getCharacterCompressionType() const override; + virtual void setCharacterCompressionType( /*[in]*/CharCompressType nType ) override; + + +// Replace all compatibility options with those from rSource. + void ReplaceCompatibilityOptions(const DocumentSettingManager& rSource); + + sal_uInt32 Getn32DummyCompatibilityOptions1() const override; + void Setn32DummyCompatibilityOptions1( const sal_uInt32 CompatibilityOptions1 ) override; + sal_uInt32 Getn32DummyCompatibilityOptions2() const override; + void Setn32DummyCompatibilityOptions2( const sal_uInt32 CompatibilityOptions2 ) override; + void dumpAsXml(xmlTextWriterPtr pWriter) const; +}; + +} + +#endif //_DOCSETTING_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentStateManager.hxx b/sw/source/core/inc/DocumentStateManager.hxx new file mode 100644 index 000000000..e9b7d09bb --- /dev/null +++ b/sw/source/core/inc/DocumentStateManager.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSTATEMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSTATEMANAGER_HXX + +#include <IDocumentState.hxx> + +class SwDoc; + + +namespace sw { + +class DocumentStateManager : public IDocumentState +{ + +public: + DocumentStateManager( SwDoc& i_rSwdoc ); + + void SetModified() override; + void ResetModified() override; + bool IsModified() const override; + bool IsEnableSetModified() const override; + void SetEnableSetModified(bool bEnableSetModified) override; + bool IsInCallModified() const override; + bool IsUpdateExpField() const override; + bool IsNewDoc() const override; + void SetNewDoc(bool b) override; + void SetUpdateExpFieldStat(bool b) override; + +private: + + DocumentStateManager(DocumentStateManager const&) = delete; + DocumentStateManager& operator=(DocumentStateManager const&) = delete; + + SwDoc& m_rDoc; + + bool mbEnableSetModified; //< FALSE: changing document modification status (temporarily) locked + bool mbModified ; //< TRUE: document has changed. + bool mbUpdateExpField; //< TRUE: Update expression fields. + bool mbNewDoc ; //< TRUE: new Doc. + bool mbInCallModified; //< TRUE: in Set/Reset-Modified link. +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentStatisticsManager.hxx b/sw/source/core/inc/DocumentStatisticsManager.hxx new file mode 100644 index 000000000..ab82a767a --- /dev/null +++ b/sw/source/core/inc/DocumentStatisticsManager.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSTATISTICSMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSTATISTICSMANAGER_HXX + +#include <IDocumentStatistics.hxx> +#include <SwDocIdle.hxx> +#include <memory> + +class SwDoc; +struct SwDocStat; + +namespace sw { + +class DocumentStatisticsManager : public IDocumentStatistics +{ + +public: + + DocumentStatisticsManager( SwDoc& i_rSwdoc ); + + void DocInfoChgd(bool isEnableSetModified) override; + const SwDocStat &GetDocStat() const override; + void SetDocStatModified(bool bSet); + const SwDocStat &GetUpdatedDocStat(bool bCompleteAsync, bool bFields) override; + void SetDocStat(const SwDocStat& rStat) override; + void UpdateDocStat(bool bCompleteAsync, bool bFields) override; + virtual ~DocumentStatisticsManager() override; + +private: + + DocumentStatisticsManager(DocumentStatisticsManager const&) = delete; + DocumentStatisticsManager& operator=(DocumentStatisticsManager const&) = delete; + + SwDoc& m_rDoc; + + /** continue computing a chunk of document statistics + * \param nChars number of characters to count before exiting + * \param bFields if stat. fields should be updated + * + * returns false when there is no more to calculate + */ + bool IncrementalDocStatCalculate(long nChars, bool bFields = true); + + // Our own 'StatsUpdateTimer' calls the following method + DECL_LINK( DoIdleStatsUpdate, Timer *, void ); + + std::unique_ptr<SwDocStat> mpDocStat;//< Statistics information + bool mbInitialized; //< allow first time update + SwDocIdle maStatsUpdateIdle; //< Idle for asynchronous stats calculation +}; + +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DocumentStylePoolManager.hxx b/sw/source/core/inc/DocumentStylePoolManager.hxx new file mode 100644 index 000000000..721ecba22 --- /dev/null +++ b/sw/source/core/inc/DocumentStylePoolManager.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSTYLEPOOLMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTSTYLEPOOLMANAGER_HXX + +#include <IDocumentStylePoolAccess.hxx> + +class SwDoc; + +namespace sw { + +class DocumentStylePoolManager : public IDocumentStylePoolAccess +{ + +public: + + DocumentStylePoolManager( SwDoc& i_rSwdoc ); + + virtual SwTextFormatColl* GetTextCollFromPool( sal_uInt16 nId, bool bRegardLanguage = true ) override; + virtual SwFormat* GetFormatFromPool( sal_uInt16 nId ) override; + virtual SwFrameFormat* GetFrameFormatFromPool( sal_uInt16 nId ) override; + virtual SwCharFormat* GetCharFormatFromPool( sal_uInt16 nId ) override; + virtual SwPageDesc* GetPageDescFromPool( sal_uInt16 nId, bool bRegardLanguage = true ) override; + virtual SwNumRule* GetNumRuleFromPool( sal_uInt16 nId ) override; + virtual bool IsPoolTextCollUsed( sal_uInt16 nId ) const override; + virtual bool IsPoolFormatUsed( sal_uInt16 nId ) const override; + virtual bool IsPoolPageDescUsed( sal_uInt16 nId ) const override; + + virtual ~DocumentStylePoolManager() override; + +private: + + DocumentStylePoolManager(DocumentStylePoolManager const&) = delete; + DocumentStylePoolManager& operator=(DocumentStylePoolManager const&) = delete; + + SwDoc& m_rDoc; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/sw/source/core/inc/DocumentTimerManager.hxx b/sw/source/core/inc/DocumentTimerManager.hxx new file mode 100644 index 000000000..65346efdb --- /dev/null +++ b/sw/source/core/inc/DocumentTimerManager.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTTIMERMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCUMENTTIMERMANAGER_HXX + +#include <IDocumentTimerAccess.hxx> +#include <SwDocIdle.hxx> + +#include <sal/types.h> +#include <tools/link.hxx> + +class SwDoc; + +namespace sw +{ + +class DocumentTimerManager : public IDocumentTimerAccess +{ +public: + enum class IdleJob + { + None, ///< document has no idle jobs to do + Busy, ///< document is busy and idle jobs are postponed + Grammar, + Layout, + Fields, + }; + + DocumentTimerManager( SwDoc& i_rSwdoc ); + virtual ~DocumentTimerManager() override; + + void StartIdling() override; + + void StopIdling() override; + + void BlockIdling() override; + + void UnblockIdling() override; + + bool IsDocIdle() const override; + +private: + DocumentTimerManager(DocumentTimerManager const&) = delete; + DocumentTimerManager& operator=(DocumentTimerManager const&) = delete; + + /// Delay starting idle jobs to allow for post-load activity. + /// Used by LOK only. + DECL_LINK( FireIdleJobsTimeout, Timer *, void ); + + DECL_LINK( DoIdleJobs, Timer *, void ); + + IdleJob GetNextIdleJob() const; + + SwDoc& m_rDoc; + + sal_uInt32 m_nIdleBlockCount; ///< Don't run the Idle, if > 0 + bool m_bStartOnUnblock; ///< true, if the last unblock should start the timer + SwDocIdle m_aDocIdle; + Timer m_aFireIdleJobsTimer; + bool m_bWaitForLokInit; ///< true if we waited for LOK to initialize already. +}; + +inline bool DocumentTimerManager::IsDocIdle() const +{ + return ((0 == m_nIdleBlockCount) && (GetNextIdleJob() != IdleJob::Busy)); +} + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/DropDownFormFieldButton.hxx b/sw/source/core/inc/DropDownFormFieldButton.hxx new file mode 100644 index 000000000..a19470498 --- /dev/null +++ b/sw/source/core/inc/DropDownFormFieldButton.hxx @@ -0,0 +1,40 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_DROPDOWNFORMEFIELDBUTTO_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_DROPDOWNFORMEFIELDBUTTO_HXX + +#include "FormFieldButton.hxx" + +class SwEditWin; +class FloatingWindow; +namespace sw +{ +namespace mark +{ +class DropDownFieldmark; +} +} + +/** + * This button is shown when the cursor is on a drop-down form field. + * The user can select an item of the field using this button while filling in a form. + */ +class DropDownFormFieldButton : public FormFieldButton +{ +public: + DropDownFormFieldButton(SwEditWin* pEditWin, sw::mark::DropDownFieldmark& rFieldMark); + virtual ~DropDownFormFieldButton() override; + + virtual void InitPopup() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/inc/FormFieldButton.hxx b/sw/source/core/inc/FormFieldButton.hxx new file mode 100644 index 000000000..987d86d15 --- /dev/null +++ b/sw/source/core/inc/FormFieldButton.hxx @@ -0,0 +1,56 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_FORMEFIELDBUTTO_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_FORMEFIELDBUTTO_HXX + +#include <vcl/menubtn.hxx> +#include <swrect.hxx> + +class SwEditWin; +class FloatingWindow; +namespace sw +{ +namespace mark +{ +class Fieldmark; +} +} // namespace sw + +/** + * This button is shown when the cursor is on a form field with drop-down capability. + */ +class FormFieldButton : public MenuButton +{ +public: + FormFieldButton(SwEditWin* pEditWin, sw::mark::Fieldmark& rFieldMark); + virtual ~FormFieldButton() override; + virtual void dispose() override; + + void CalcPosAndSize(const SwRect& rPortionPaintArea); + + virtual void MouseButtonUp(const MouseEvent& rMEvt) override; + DECL_LINK(FieldPopupModeEndHdl, FloatingWindow*, void); + + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual WindowHitTest ImplHitTest(const Point& rFramePos) override; + + virtual void InitPopup() = 0; + +private: + tools::Rectangle m_aFieldFramePixel; + +protected: + sw::mark::Fieldmark& m_rFieldmark; + VclPtr<FloatingWindow> m_pFieldPopup; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/inc/GetMetricVal.hxx b/sw/source/core/inc/GetMetricVal.hxx new file mode 100644 index 000000000..9b17975d6 --- /dev/null +++ b/sw/source/core/inc/GetMetricVal.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_GETMETRICVAL_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_GETMETRICVAL_HXX + +#include <sal/types.h> + +#define CM_1 0 // 1 centimeter or 1/2 inch +#define CM_05 1 // 0.5 centimeter or 1/4 inch +#define CM_01 2 // 0.1 centimeter or 1/20 inch + +inline sal_uInt16 GetMetricVal( int n ) +{ + sal_uInt16 nVal = 567; // 1 cm + + if( CM_01 == n ) + nVal /= 10; + else if( CM_05 == n ) + nVal /= 2; + return nVal; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/MarkManager.hxx b/sw/source/core/inc/MarkManager.hxx new file mode 100644 index 000000000..a9457f916 --- /dev/null +++ b/sw/source/core/inc/MarkManager.hxx @@ -0,0 +1,150 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_MARKMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_MARKMANAGER_HXX + +#include <IMark.hxx> +#include <IDocumentMarkAccess.hxx> +#include <unordered_map> +#include <memory> + +class SwCursorShell; + +namespace sw { + namespace mark { + typedef std::unordered_map<OUString, sal_Int32> MarkBasenameMapUniqueOffset_t; + + class FieldmarkWithDropDownButton; + + class MarkManager + : virtual public IDocumentMarkAccess + { + public: + MarkManager(/*[in/out]*/ SwDoc& rDoc); + // IDocumentMarkAccess + virtual ::sw::mark::IMark* makeMark(const SwPaM& rPaM, + const OUString& rName, IDocumentMarkAccess::MarkType eMark, + sw::mark::InsertMode eMode, + SwPosition const* pSepPos = nullptr) override; + + virtual sw::mark::IFieldmark* makeFieldBookmark( const SwPaM& rPaM, + const OUString& rName, + const OUString& rType, + SwPosition const* pSepPos = nullptr) override; + virtual sw::mark::IFieldmark* makeNoTextFieldBookmark( const SwPaM& rPaM, + const OUString& rName, + const OUString& rType) override; + + virtual ::sw::mark::IMark* getMarkForTextNode(const SwTextNode& rTextNode, IDocumentMarkAccess::MarkType eMark) override; + + virtual sw::mark::IMark* makeAnnotationMark( + const SwPaM& rPaM, + const OUString& rName ) override; + + virtual void repositionMark(::sw::mark::IMark* io_pMark, const SwPaM& rPaM) override; + virtual bool renameMark(::sw::mark::IMark* io_pMark, const OUString& rNewName) override; + virtual void correctMarksAbsolute(const SwNodeIndex& rOldNode, const SwPosition& rNewPos, const sal_Int32 nOffset) override; + virtual void correctMarksRelative(const SwNodeIndex& rOldNode, const SwPosition& rNewPos, const sal_Int32 nOffset) override; + + virtual void deleteMarks(const SwNodeIndex& rStt, const SwNodeIndex& rEnd, std::vector< ::sw::mark::SaveBookmark>* pSaveBkmk, const SwIndex* pSttIdx, const SwIndex* pEndIdx) override; + + // deleters + virtual std::unique_ptr<ILazyDeleter> + deleteMark(const const_iterator_t& ppMark) override; + virtual void deleteMark(const ::sw::mark::IMark* const pMark) override; + virtual void clearAllMarks() override; + + // marks + virtual const_iterator_t getAllMarksBegin() const override; + virtual const_iterator_t getAllMarksEnd() const override; + virtual sal_Int32 getAllMarksCount() const override; + virtual const_iterator_t findMark(const OUString& rName) const override; + + // bookmarks + virtual bool isBookmarkDeleted(SwPaM const& rPaM) const override; + virtual const_iterator_t getBookmarksBegin() const override; + virtual const_iterator_t getBookmarksEnd() const override; + virtual sal_Int32 getBookmarksCount() const override; + virtual const_iterator_t findBookmark(const OUString& rName) const override; + virtual const_iterator_t findFirstBookmarkStartsAfter(const SwPosition& rPos) const override; + + // Fieldmarks + virtual ::sw::mark::IFieldmark* getFieldmarkAt(const SwPosition& rPos) const override; + virtual ::sw::mark::IFieldmark* getFieldmarkFor(const SwPosition& rPos) const override; + virtual ::sw::mark::IFieldmark* getFieldmarkBefore(const SwPosition& rPos) const override; + virtual ::sw::mark::IFieldmark* getFieldmarkAfter(const SwPosition& rPos) const override; + + virtual ::sw::mark::IFieldmark* getDropDownFor(const SwPosition &rPos) const override; + virtual std::vector< ::sw::mark::IFieldmark* > getDropDownsFor(const SwPaM &rPaM) const override; + + virtual void deleteFieldmarkAt(const SwPosition& rPos) override; + virtual ::sw::mark::IFieldmark* changeFormFieldmarkType(::sw::mark::IFieldmark* pFieldmark, const OUString& rNewType) override; + + virtual void NotifyCursorUpdate(const SwCursorShell& rCursorShell) override; + virtual void ClearFieldActivation() override; + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + // Annotation Marks + virtual const_iterator_t getAnnotationMarksBegin() const override; + virtual const_iterator_t getAnnotationMarksEnd() const override; + virtual sal_Int32 getAnnotationMarksCount() const override; + virtual const_iterator_t findAnnotationMark( const OUString& rName ) const override; + virtual sw::mark::IMark* getAnnotationMarkFor(const SwPosition& rPos) const override; + virtual const_iterator_t findFirstAnnotationStartsAfter(const SwPosition& rPos) const override; + + virtual void assureSortedMarkContainers() const override; + + typedef std::vector<sw::mark::MarkBase*> container_t; + + private: + + MarkManager(MarkManager const&) = delete; + MarkManager& operator=(MarkManager const&) = delete; + + // make names + OUString getUniqueMarkName(const OUString& rName) const; + + void sortSubsetMarks(); + void sortMarks(); + + // container for all marks, this container owns the objects it points to + container_t m_vAllMarks; + + // additional container for bookmarks + container_t m_vBookmarks; + // additional container for fieldmarks + container_t m_vFieldmarks; + + mutable MarkBasenameMapUniqueOffset_t m_aMarkBasenameMapUniqueOffset; + + // container for annotation marks + container_t m_vAnnotationMarks; + + SwDoc * const m_pDoc; + + sw::mark::FieldmarkWithDropDownButton* m_pLastActiveFieldmark; + }; + } // namespace mark +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwGrammarMarkUp.hxx b/sw/source/core/inc/SwGrammarMarkUp.hxx new file mode 100644 index 000000000..f37605556 --- /dev/null +++ b/sw/source/core/inc/SwGrammarMarkUp.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWGRAMMARMARKUP_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWGRAMMARMARKUP_HXX + +#include "wrong.hxx" +#include <vector> + +/* SwGrammarMarkUp extends the functionality of a "normal" SwWrongList by memorizing + the start positions of sentences in the paragraph + + The whole class is only a temporary solution without usage of virtual functions. + At the end the whole SwWrongList stuff should be reworked and replaced by interfaces + to deal with all the different wronglists like + spell, grammar, smarttag, sentence... + "MarkUpList" would be a better name than WrongList. +*/ + +class SwGrammarMarkUp : public SwWrongList +{ + std::vector< sal_Int32 > maSentence; + +public: + SwGrammarMarkUp() : SwWrongList( WRONGLIST_GRAMMAR ) {} + + virtual ~SwGrammarMarkUp() override; + virtual SwWrongList* Clone() override; + virtual void CopyFrom( const SwWrongList& rCopy ) override; + + /* SwWrongList::Move() + handling of maSentence */ + void MoveGrammar( sal_Int32 nPos, sal_Int32 nDiff ); + /* SwWrongList::SplitList() + handling of maSentence */ + SwGrammarMarkUp* SplitGrammarList( sal_Int32 nSplitPos ); + /* SwWrongList::JoinList() + handling of maSentence */ + void JoinGrammarList( SwGrammarMarkUp* pNext, sal_Int32 nInsertPos ); + /* SwWrongList::ClearList() + handling of maSentence */ + void ClearGrammarList( sal_Int32 nSentenceEnd = COMPLETE_STRING ); + /* setSentence to define the start position of a sentence, + at the moment the end position is given by the next start position */ + void setSentence( sal_Int32 nStart ); + /* getSentenceStart returns the last start position of a sentence + which is lower or equal to the given parameter */ + sal_Int32 getSentenceStart( sal_Int32 nPos ); + /* getSentenceEnd returns the first start position of a sentence + which is greater than the given parameter */ + sal_Int32 getSentenceEnd( sal_Int32 nPos ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwPortionHandler.hxx b/sw/source/core/inc/SwPortionHandler.hxx new file mode 100644 index 000000000..2036b0a66 --- /dev/null +++ b/sw/source/core/inc/SwPortionHandler.hxx @@ -0,0 +1,105 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWPORTIONHANDLER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWPORTIONHANDLER_HXX + +#include <swdllapi.h> +#include <rtl/ustring.hxx> +#include "TextFrameIndex.hxx" + +class SwFont; +enum class PortionType; + +/** The SwPortionHandler interface implements a visitor for the layout + * engine's text portions. This can be used to gather information of + * the on-screen representation of a single paragraph. + * + * For each text portion, one of the methods text(...) or special(...) + * is called, depending on whether it is a 'normal' run of text, or + * any other portion. Additionally, the linebreak() method is called + * once at the end of every on-screen line. + * + * All parameters relate to the 'view string', which is the text string + * held by the sequence of all corresponding SwTextFrames. + * + * The SwPortionHandler can be used with the + * SwTextFrame::VisitPortions(...) method. + */ +class SW_DLLPUBLIC SwPortionHandler +{ +public: + + SwPortionHandler() {} /// (empty) constructor + + virtual ~SwPortionHandler() {} /// (empty) destructor + + /** text portion. A run of nLength characters from the view + * string, that contains no special characters like embedded + * fields, etc. Thus, the on-screen text of this portion + * corresponds exactly to the corresponding characters in the + * view string. + */ + virtual void Text( + TextFrameIndex nLength, ///< length of this portion in the view string + PortionType nType, /// type of this portion + sal_Int32 nHeight = 0, /// height of this portion + sal_Int32 nWidth = 0 /// width of this portion + ) = 0; + + /** special portion. This method is called for every non-text + * portion. The parameters describe the length of the + * corresponding characters in the view string (often 0 or 1), + * the text which is displayed, and the type of the portion. + */ + virtual void Special( + TextFrameIndex nLength, ///< length of this portion in the view string + const OUString& rText, /// text which is painted on-screen + PortionType nType, /// type of this portion + sal_Int32 nHeight = 0, /// font height of the painted text + sal_Int32 nWidth = 0, /// width of this portion + const SwFont* pFont = nullptr /// font of this portion + ) = 0; + + /** line break. This method is called whenever a line break in the + * layout occurs. + */ + virtual void LineBreak(sal_Int32 nWidth) = 0; + + /** skip characters. The SwTextFrame may only display partially + * display a certain paragraph (e.g. when the paragraph is split + * across multiple pages). In this case, the Skip() method must be + * called to inform the portion handler to ignore a certain run of + * characters in the 'view string'. Skip(), if used at all, must + * be called before any of the other methods is called. Calling + * Skip() between portions is not allowed. + */ + virtual void Skip( + TextFrameIndex nLength /// number of 'view string' characters to be skipped + ) = 0; + + /** end of paragraph. This method is to be called when all the + * paragraph's portions have been processed. + */ + virtual void Finish() = 0; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwUndoFmt.hxx b/sw/source/core/inc/SwUndoFmt.hxx new file mode 100644 index 000000000..7ba6b1c34 --- /dev/null +++ b/sw/source/core/inc/SwUndoFmt.hxx @@ -0,0 +1,256 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWUNDOFMT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWUNDOFMT_HXX + +#include <undobj.hxx> +#include <swundo.hxx> +#include <numrule.hxx> + +class SwDoc; +class SwTextFormatColl; +class SwConditionTextFormatColl; +class SwRewriter; + +class SwUndoFormatCreate : public SwUndo +{ +protected: + SwFormat * m_pNew; + OUString m_sDerivedFrom; + SwDoc * m_pDoc; + mutable OUString m_sNewName; + SfxItemSet * m_pNewSet; + sal_uInt16 m_nId; // FormatId related + bool m_bAuto; + +public: + SwUndoFormatCreate(SwUndoId nUndoId, SwFormat * pNew, SwFormat const * pDerivedFrom, + SwDoc * pDoc); + virtual ~SwUndoFormatCreate() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; + + virtual SwFormat * Create(SwFormat * pDerivedFrom) = 0; + virtual void Delete() = 0; + virtual SwFormat * Find(const OUString & rName) const = 0; +}; + +class SwUndoFormatDelete : public SwUndo +{ +protected: + OUString m_sDerivedFrom; + SwDoc * m_pDoc; + OUString m_sOldName; + SfxItemSet m_aOldSet; + sal_uInt16 m_nId; // FormatId related + bool m_bAuto; + +public: + SwUndoFormatDelete(SwUndoId nUndoId, SwFormat const * pOld, SwDoc * pDoc); + virtual ~SwUndoFormatDelete() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; + + virtual SwFormat * Create(SwFormat * pDerivedFrom) = 0; + virtual void Delete(SwFormat * pFormat) = 0; + virtual SwFormat * Find(const OUString & rName) const = 0; +}; + +class SwUndoRenameFormat : public SwUndo +{ +protected: + OUString m_sOldName, m_sNewName; + SwDoc * m_pDoc; + +public: + SwUndoRenameFormat(SwUndoId nUndoId, const OUString & sOldName, + const OUString & sNewName, + SwDoc * pDoc); + virtual ~SwUndoRenameFormat() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + SwRewriter GetRewriter() const override; + + virtual SwFormat * Find(const OUString & rName) const = 0; +}; + +class SwUndoTextFormatCollCreate : public SwUndoFormatCreate +{ +public: + SwUndoTextFormatCollCreate(SwTextFormatColl * pNew, SwTextFormatColl const * pDerivedFrom, + SwDoc * pDoc); + + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; + virtual void Delete() override; + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoTextFormatCollDelete : public SwUndoFormatDelete +{ +public: + SwUndoTextFormatCollDelete(SwTextFormatColl const * pOld, SwDoc * pDoc); + + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; + virtual void Delete(SwFormat * pFormat) override; + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoCondTextFormatCollCreate : public SwUndoTextFormatCollCreate +{ +public: + SwUndoCondTextFormatCollCreate(SwConditionTextFormatColl * pNew, SwTextFormatColl const * pDerivedFrom, SwDoc * pDoc); + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; +}; + +class SwUndoCondTextFormatCollDelete : public SwUndoTextFormatCollDelete +{ +public: + SwUndoCondTextFormatCollDelete(SwTextFormatColl const * pOld, SwDoc * pDoc); + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; +}; + +class SwUndoRenameFormatColl : public SwUndoRenameFormat +{ +public: + SwUndoRenameFormatColl(const OUString & sOldName, + const OUString & sNewName, + SwDoc * pDoc); + + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoCharFormatCreate : public SwUndoFormatCreate +{ +public: + SwUndoCharFormatCreate(SwCharFormat * pNew, SwCharFormat const * pDerivedFrom, + SwDoc * pDoc); + + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; + virtual void Delete() override; + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoCharFormatDelete : public SwUndoFormatDelete +{ +public: + SwUndoCharFormatDelete(SwCharFormat const * pOld, SwDoc * pDoc); + + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; + virtual void Delete(SwFormat * pFormat) override; + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoRenameCharFormat : public SwUndoRenameFormat +{ +public: + SwUndoRenameCharFormat(const OUString & sOldName, + const OUString & sNewName, + SwDoc * pDoc); + + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoFrameFormatCreate : public SwUndoFormatCreate +{ +public: + SwUndoFrameFormatCreate(SwFrameFormat * pNew, SwFrameFormat const * pDerivedFrom, + SwDoc * pDoc); + + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; + virtual void Delete() override; + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoFrameFormatDelete : public SwUndoFormatDelete +{ +public: + SwUndoFrameFormatDelete(SwFrameFormat const * pOld, SwDoc * pDoc); + + virtual SwFormat * Create(SwFormat * pDerivedFrom) override; + virtual void Delete(SwFormat * pFormat) override; + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoRenameFrameFormat : public SwUndoRenameFormat +{ +public: + SwUndoRenameFrameFormat(const OUString & sOldName, + const OUString & sNewName, + SwDoc * pDoc); + + virtual SwFormat * Find(const OUString & rName) const override; +}; + +class SwUndoNumruleCreate : public SwUndo +{ + const SwNumRule * m_pNew; + mutable SwNumRule m_aNew; + SwDoc * m_pDoc; + mutable bool m_bInitialized; + +public: + SwUndoNumruleCreate(const SwNumRule * pNew, SwDoc * pDoc); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + SwRewriter GetRewriter() const override; +}; + +class SwUndoNumruleDelete : public SwUndo +{ + SwNumRule m_aOld; + SwDoc * m_pDoc; + +public: + SwUndoNumruleDelete(const SwNumRule & aRule, SwDoc * pDoc); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + SwRewriter GetRewriter() const override; +}; + +class SwUndoNumruleRename : public SwUndo +{ + OUString m_aOldName, m_aNewName; + SwDoc * m_pDoc; + + public: + SwUndoNumruleRename(const OUString & aOldName, const OUString & aNewName, + SwDoc * pDoc); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + SwRewriter GetRewriter() const override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_SWUNDOFMT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwUndoPageDesc.hxx b/sw/source/core/inc/SwUndoPageDesc.hxx new file mode 100644 index 000000000..acdaed7cc --- /dev/null +++ b/sw/source/core/inc/SwUndoPageDesc.hxx @@ -0,0 +1,86 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWUNDOPAGEDESC_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWUNDOPAGEDESC_HXX + +#include <undobj.hxx> +#include <pagedesc.hxx> + +class SwDoc; + +class SwUndoPageDesc : public SwUndo +{ + SwPageDescExt m_aOld, m_aNew; + SwDoc * m_pDoc; + bool m_bExchange; + + // To avoid duplication of (header/footer)content nodes for simple page desc changes + void ExchangeContentNodes( SwPageDesc& rSource, SwPageDesc &rDest ); + +public: + SwUndoPageDesc(const SwPageDesc & aOld, const SwPageDesc & aNew, + SwDoc * pDoc); + virtual ~SwUndoPageDesc() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoPageDescCreate : public SwUndo +{ + const SwPageDesc * m_pDesc; + SwPageDescExt m_aNew; + SwDoc * m_pDoc; + + void DoImpl(); + +public: + SwUndoPageDescCreate(const SwPageDesc * pNew, SwDoc * pDoc); + virtual ~SwUndoPageDescCreate() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoPageDescDelete : public SwUndo +{ + SwPageDescExt m_aOld; + SwDoc * m_pDoc; + + void DoImpl(); + +public: + SwUndoPageDescDelete(const SwPageDesc & aOld, SwDoc * pDoc); + virtual ~SwUndoPageDescDelete() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; +#endif // _SW_UNDO_PAGE_DESC_CHANGE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwUndoTOXChange.hxx b/sw/source/core/inc/SwUndoTOXChange.hxx new file mode 100644 index 000000000..a593bfd6d --- /dev/null +++ b/sw/source/core/inc/SwUndoTOXChange.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWUNDOTOXCHANGE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWUNDOTOXCHANGE_HXX + +#include <undobj.hxx> +#include <tox.hxx> + +class SwDoc; +class SwTOXBaseSection; + +class SwUndoTOXChange : public SwUndo +{ +private: + SwTOXBase m_Old; + SwTOXBase m_New; + + sal_uLong const m_nNodeIndex; + +public: + SwUndoTOXChange(const SwDoc* pDoc, SwTOXBaseSection const& rTOX, const SwTOXBase & rNew); + virtual ~SwUndoTOXChange() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_SWUNDOTOXCHANGE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwXMLBlockExport.hxx b/sw/source/core/inc/SwXMLBlockExport.hxx new file mode 100644 index 000000000..d858cfecf --- /dev/null +++ b/sw/source/core/inc/SwXMLBlockExport.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWXMLBLOCKEXPORT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWXMLBLOCKEXPORT_HXX + +#include <xmloff/xmlexp.hxx> + +class SwXMLTextBlocks; + +class SwXMLBlockListExport : public SvXMLExport +{ +private: + SwXMLTextBlocks &rBlockList; + +public: + SwXMLBlockListExport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + SwXMLTextBlocks & rBlocks, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc( enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID ) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +class SwXMLTextBlockExport : public SvXMLExport +{ +private: + SwXMLTextBlocks &rBlockList; + +public: + SwXMLTextBlockExport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + SwXMLTextBlocks & rBlocks, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum /*eClass*/) override { return ERRCODE_NONE; } + void exportDoc(const OUString & rText); + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwXMLBlockImport.hxx b/sw/source/core/inc/SwXMLBlockImport.hxx new file mode 100644 index 000000000..d58ce4bf1 --- /dev/null +++ b/sw/source/core/inc/SwXMLBlockImport.hxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWXMLBLOCKIMPORT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWXMLBLOCKIMPORT_HXX + +#include <xmloff/xmlimp.hxx> +#include <xmloff/xmlnmspe.hxx> +#include <com/sun/star/xml/sax/FastToken.hpp> +#include <sax/fastattribs.hxx> +#include <cppuhelper/implbase.hxx> + +namespace com::sun::star::xml::sax { class XFastTokenHandler; } + +using namespace css::xml::sax; +using namespace xmloff::token; + +class SwXMLTextBlocks; +class SwXMLBlockListImport : public SvXMLImport +{ +private: + SwXMLTextBlocks &rBlockList; + +protected: + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext* CreateFastContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + +public: + SwXMLBlockListImport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + SwXMLTextBlocks &rBlocks ); + + SwXMLTextBlocks& getBlockList() + { + return rBlockList; + } + virtual ~SwXMLBlockListImport() + throw() override; +}; + +class SwXMLTextBlockImport : public SvXMLImport +{ +protected: + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext* CreateFastContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + +public: + bool bTextOnly; + OUString &m_rText; + SwXMLTextBlockImport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + OUString &rNewText, bool bNewTextOnly ); + + virtual ~SwXMLTextBlockImport() + throw() override; + virtual void SAL_CALL endDocument() override; +}; + +enum SwXMLTextBlockToken : sal_Int32 +{ + OFFICE_BODY = FastToken::NAMESPACE | XML_NAMESPACE_OFFICE | XML_BODY, + OFFICE_TEXT = FastToken::NAMESPACE | XML_NAMESPACE_OFFICE | XML_TEXT, + OFFICE_DOCUMENT = FastToken::NAMESPACE | XML_NAMESPACE_OFFICE | XML_DOCUMENT, + OFFICE_DOCUMENT_CONTENT = FastToken::NAMESPACE | XML_NAMESPACE_OFFICE | XML_DOCUMENT_CONTENT, + TEXT_P = FastToken::NAMESPACE | XML_NAMESPACE_TEXT | XML_P +}; + +class SwXMLTextBlockTokenHandler : + public sax_fastparser::FastTokenHandlerBase +{ +public: + SwXMLTextBlockTokenHandler(); + virtual ~SwXMLTextBlockTokenHandler() override; + + //XFastTokenHandler + sal_Int32 SAL_CALL getTokenFromUTF8( const css::uno::Sequence< sal_Int8 >& Identifier ) override; + css::uno::Sequence< sal_Int8 > SAL_CALL getUTF8Identifier( sal_Int32 Token ) override; + + //Much fast direct C++ shortcut to the method that matters + virtual sal_Int32 getTokenDirect( const char *pTag, sal_Int32 nLength ) const override; +}; + +enum SwXMLBlockListToken : sal_Int32 +{ + ABBREVIATED_NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_ABBREVIATED_NAME, + BLOCK = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK, + BLOCK_LIST = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK_LIST, + LIST_NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_LIST_NAME, + NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_NAME, + PACKAGE_NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_PACKAGE_NAME, + UNFORMATTED_TEXT = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_UNFORMATTED_TEXT +}; + +class SwXMLBlockListTokenHandler : + public sax_fastparser::FastTokenHandlerBase +{ +public: + SwXMLBlockListTokenHandler(); + virtual ~SwXMLBlockListTokenHandler() override; + + //XFastTokenHandler + sal_Int32 SAL_CALL getTokenFromUTF8( const css::uno::Sequence< sal_Int8 >& Identifier ) override; + css::uno::Sequence< sal_Int8 > SAL_CALL getUTF8Identifier( sal_Int32 Token ) override; + + //Much fast direct C++ shortcut to the method that matters + virtual sal_Int32 getTokenDirect( const char *pTag, sal_Int32 nLength ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwXMLTextBlocks.hxx b/sw/source/core/inc/SwXMLTextBlocks.hxx new file mode 100644 index 000000000..3d1feb1ec --- /dev/null +++ b/sw/source/core/inc/SwXMLTextBlocks.hxx @@ -0,0 +1,96 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWXMLTEXTBLOCKS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWXMLTEXTBLOCKS_HXX + +#include <sfx2/objsh.hxx> +#include "swblocks.hxx" +#include <o3tl/typed_flags_set.hxx> + +class SwDoc; +class SvxMacroTableDtor; + +enum class SwXmlFlags { + NONE = 0x0000, + NoRootCommit = 0x0002, +}; +namespace o3tl { + template<> struct typed_flags<SwXmlFlags> : is_typed_flags<SwXmlFlags, 0x0002> {}; +} + +class SwXMLTextBlocks final : public SwImpBlocks +{ + SfxObjectShellRef xDocShellRef; + SwXmlFlags nFlags; + OUString aPackageName; + tools::SvRef<SfxMedium> xMedium; + + void ReadInfo(); + void WriteInfo(); + void InitBlockMode ( const css::uno::Reference < css::embed::XStorage >& rStorage ); + void ResetBlockMode(); + +public: + css::uno::Reference < css::embed::XStorage > xBlkRoot; + css::uno::Reference < css::embed::XStorage > xRoot; + SwXMLTextBlocks( const OUString& rFile ); + SwXMLTextBlocks( const css::uno::Reference < css::embed::XStorage >&, const OUString& rFile ); + void AddName( const OUString&, const OUString&, const OUString&, bool bOnlyText ); + virtual void AddName( const OUString&, const OUString&, bool bOnlyText = false ) override; + static OUString GeneratePackageName ( const OUString& rShort ); + virtual ~SwXMLTextBlocks() override; + virtual ErrCode Delete( sal_uInt16 ) override; + virtual ErrCode Rename( sal_uInt16, const OUString& ) override; + virtual void ClearDoc() override; + virtual ErrCode GetDoc( sal_uInt16 ) override; + virtual ErrCode BeginPutDoc( const OUString&, const OUString& ) override; + virtual ErrCode PutDoc() override; + virtual ErrCode PutText( const OUString&, const OUString&, const OUString& ) override; + virtual ErrCode MakeBlockList() override; + + virtual ErrCode OpenFile( bool bReadOnly = true ) override; + virtual void CloseFile() override; + + static bool IsFileUCBStorage( const OUString & rFileName); + + // Methods for the new Autocorrecter + ErrCode GetText( const OUString& rShort, OUString& ); + + virtual bool IsOnlyTextBlock( const OUString& rShort ) const override; + bool IsOnlyTextBlock( sal_uInt16 nIdx ) const; + void SetIsTextOnly( const OUString& rShort, bool bNewValue ); + + virtual ErrCode GetMacroTable( sal_uInt16, SvxMacroTableDtor& rMacroTable ) override; + virtual ErrCode SetMacroTable( sal_uInt16 nIdx, + const SvxMacroTableDtor& rMacroTable ) override; + virtual bool PutMuchEntries( bool bOn ) override; + + SwDoc* GetDoc() const { return m_xDoc.get(); } + //void SetDoc( SwDoc * pNewDoc); + ErrCode StartPutBlock( const OUString& rShort, const OUString& rPackageName ); + ErrCode PutBlock(); + ErrCode GetBlockText( const OUString& rShort, OUString& rText ); + ErrCode PutBlockText( const OUString& rShort, const OUString& rText, const OUString& rPackageName ); + void MakeBlockText( const OUString& rText ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/SwXTextDefaults.hxx b/sw/source/core/inc/SwXTextDefaults.hxx new file mode 100644 index 000000000..f989600c5 --- /dev/null +++ b/sw/source/core/inc/SwXTextDefaults.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWXTEXTDEFAULTS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWXTEXTDEFAULTS_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +class SwDoc; +class SfxItemPropertySet; + +class SwXTextDefaults : public cppu::WeakImplHelper + < + css::beans::XPropertyState, + css::beans::XPropertySet, + css::lang::XServiceInfo + > +{ + const SfxItemPropertySet* m_pPropSet; + SwDoc * m_pDoc; + +public: + SwXTextDefaults ( SwDoc * pNewDoc ); + virtual ~SwXTextDefaults () override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& rPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& rPropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& rPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& rPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& rPropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& rPropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + // XPropertyState + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& rPropertyName ) override; + virtual css::uno::Sequence< css::beans::PropertyState > SAL_CALL getPropertyStates( const css::uno::Sequence< OUString >& rPropertyNames ) override; + virtual void SAL_CALL setPropertyToDefault( const OUString& rPropertyName ) override; + virtual css::uno::Any SAL_CALL getPropertyDefault( const OUString& rPropertyName ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/TextFrameIndex.hxx b/sw/source/core/inc/TextFrameIndex.hxx new file mode 100644 index 000000000..3987bd590 --- /dev/null +++ b/sw/source/core/inc/TextFrameIndex.hxx @@ -0,0 +1,26 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TEXTFRAMEINDEX_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TEXTFRAMEINDEX_HXX + +#include <sal/types.h> +#include <o3tl/strong_int.hxx> + +/** + * Denotes a character index in a text frame at a layout level, after extent + * mapping from a text node at a document model level. + * + * @see SwTextFrame::MapViewToModelPos(). + */ +typedef o3tl::strong_int<sal_Int32, struct Tag_TextFrameIndex> TextFrameIndex; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_TEXTFRAMEINDEX_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/inc/UndoAttribute.hxx b/sw/source/core/inc/UndoAttribute.hxx new file mode 100644 index 000000000..cc73197fd --- /dev/null +++ b/sw/source/core/inc/UndoAttribute.hxx @@ -0,0 +1,257 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOATTRIBUTE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOATTRIBUTE_HXX + +#include <undobj.hxx> +#include <memory> +#include <rtl/ustring.hxx> +#include <svl/itemset.hxx> +#include <swtypes.hxx> +#include <calbck.hxx> +#include <set> + +class SvxTabStopItem; +class SwFormat; +class SwFootnoteInfo; +class SwEndNoteInfo; +class SwDoc; + +class SwUndoAttr : public SwUndo, private SwUndRng +{ + SfxItemSet m_AttrSet; // attributes for Redo + const std::unique_ptr<SwHistory> m_pHistory; // History for Undo + std::unique_ptr<SwRedlineData> m_pRedlineData; // Redlining + std::unique_ptr<SwRedlineSaveDatas> m_pRedlineSaveData; + sal_uLong m_nNodeIndex; // Offset: for Redlining + const SetAttrMode m_nInsertFlags; // insert flags + OUString m_aChrFormatName; + + void RemoveIdx( SwDoc& rDoc ); + +public: + SwUndoAttr( const SwPaM&, const SfxItemSet &, const SetAttrMode nFlags ); + SwUndoAttr( const SwPaM&, const SfxPoolItem&, const SetAttrMode nFlags ); + + virtual ~SwUndoAttr() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SaveRedlineData( const SwPaM& rPam, bool bInsContent ); + + SwHistory& GetHistory() { return *m_pHistory; } +}; + +class SwUndoResetAttr : public SwUndo, private SwUndRng +{ + const std::unique_ptr<SwHistory> m_pHistory; + std::set<sal_uInt16> m_Ids; + const sal_uInt16 m_nFormatId; // Format-Id for Redo + +public: + SwUndoResetAttr( const SwPaM&, sal_uInt16 nFormatId ); + SwUndoResetAttr( const SwPosition&, sal_uInt16 nFormatId ); + + virtual ~SwUndoResetAttr() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetAttrs( const std::set<sal_uInt16> &rAttrs ); + + SwHistory& GetHistory() { return *m_pHistory; } +}; + +class SwUndoFormatAttr : public SwUndo +{ + friend class SwUndoDefaultAttr; + OUString m_sFormatName; + std::unique_ptr<SfxItemSet> m_pOldSet; // old attributes + sal_uLong m_nNodeIndex; + const sal_uInt16 m_nFormatWhich; + const bool m_bSaveDrawPt; + + void SaveFlyAnchor( const SwFormat * pFormat, bool bSaveDrawPt = false ); + // #i35443# - Add return value, type <bool>. + // Return value indicates, if anchor attribute is restored. + // Notes: - If anchor attribute is restored, all other existing attributes + // are also restored. + // - Anchor attribute isn't restored successfully, if it contains + // an invalid anchor position and all other existing attributes + // aren't restored. + // This situation occurs for undo of styles. + bool RestoreFlyAnchor(::sw::UndoRedoContext & rContext); + // --> OD 2008-02-27 #refactorlists# - removed <rAffectedItemSet> + void Init( const SwFormat & rFormat ); + +public: + // register at the Format and save old attributes + // --> OD 2008-02-27 #refactorlists# - removed <rNewSet> + SwUndoFormatAttr( const SfxItemSet& rOldSet, + SwFormat& rFormat, + bool bSaveDrawPt ); + SwUndoFormatAttr( const SfxPoolItem& rItem, + SwFormat& rFormat, + bool bSaveDrawPt ); + + virtual ~SwUndoFormatAttr() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + virtual SwRewriter GetRewriter() const override; + + void PutAttr( const SfxPoolItem& rItem, const SwDoc& rDoc ); + SwFormat* GetFormat( const SwDoc& rDoc ); // checks if it is still in the Doc! +}; + +// --> OD 2008-02-12 #newlistlevelattrs# +class SwUndoFormatResetAttr : public SwUndo +{ + public: + SwUndoFormatResetAttr( SwFormat& rChangedFormat, + const sal_uInt16 nWhichId ); + virtual ~SwUndoFormatResetAttr() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + private: + // format at which a certain attribute is reset. + SwFormat * const m_pChangedFormat; + // which ID of the reset attribute + const sal_uInt16 m_nWhichId; + // old attribute which has been reset - needed for undo. + std::unique_ptr<SfxPoolItem> m_pOldItem; +}; + +class SwUndoDontExpandFormat : public SwUndo +{ + const sal_uLong m_nNodeIndex; + const sal_Int32 m_nContentIndex; + +public: + SwUndoDontExpandFormat( const SwPosition& rPos ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +// helper class to receive changed attribute sets +class SwUndoFormatAttrHelper : public SwClient +{ + std::unique_ptr<SwUndoFormatAttr> m_pUndo; + const bool m_bSaveDrawPt; + +public: + SwUndoFormatAttrHelper( SwFormat& rFormat, bool bSaveDrawPt = true ); + + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + + SwUndoFormatAttr* GetUndo() const { return m_pUndo.get(); } + // release the undo object (so it is not deleted here), and return it + std::unique_ptr<SwUndoFormatAttr> ReleaseUndo() { return std::move(m_pUndo); } +}; + +class SwUndoMoveLeftMargin : public SwUndo, private SwUndRng +{ + const std::unique_ptr<SwHistory> m_pHistory; + const bool m_bModulus; + +public: + SwUndoMoveLeftMargin( const SwPaM&, bool bRight, bool bModulus ); + + virtual ~SwUndoMoveLeftMargin() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + SwHistory& GetHistory() { return *m_pHistory; } + +}; + +class SwUndoDefaultAttr : public SwUndo +{ + std::unique_ptr<SfxItemSet> m_pOldSet; // the old attributes + std::unique_ptr<SvxTabStopItem> m_pTabStop; + +public: + // registers at the format and saves old attributes + SwUndoDefaultAttr( const SfxItemSet& rOldSet, const SwDoc* pDoc ); + + virtual ~SwUndoDefaultAttr() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +class SwUndoChangeFootNote : public SwUndo, private SwUndRng +{ + const std::unique_ptr<SwHistory> m_pHistory; + const OUString m_Text; + const bool m_bEndNote; + +public: + SwUndoChangeFootNote( const SwPaM& rRange, const OUString& rText, + bool bIsEndNote ); + virtual ~SwUndoChangeFootNote() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + SwHistory& GetHistory() { return *m_pHistory; } +}; + +class SwUndoFootNoteInfo : public SwUndo +{ + std::unique_ptr<SwFootnoteInfo> m_pFootNoteInfo; + +public: + SwUndoFootNoteInfo( const SwFootnoteInfo &rInfo, const SwDoc* pDoc ); + + virtual ~SwUndoFootNoteInfo() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +class SwUndoEndNoteInfo : public SwUndo +{ + std::unique_ptr<SwEndNoteInfo> m_pEndNoteInfo; + +public: + SwUndoEndNoteInfo( const SwEndNoteInfo &rInfo, const SwDoc* pDoc ); + + virtual ~SwUndoEndNoteInfo() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOATTRIBUTE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoBookmark.hxx b/sw/source/core/inc/UndoBookmark.hxx new file mode 100644 index 000000000..ebaac8cd1 --- /dev/null +++ b/sw/source/core/inc/UndoBookmark.hxx @@ -0,0 +1,157 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOBOOKMARK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOBOOKMARK_HXX + +#include <memory> +#include <undobj.hxx> + +class SwHistoryBookmark; +class SwHistoryNoTextFieldmark; +class SwHistoryTextFieldmark; + +namespace sw { + namespace mark { + class IMark; + class IFieldmark; + } +} + +class SwDoc; + +class SwUndoBookmark : public SwUndo +{ + const std::unique_ptr<SwHistoryBookmark> m_pHistoryBookmark; + +protected: + SwUndoBookmark( SwUndoId nUndoId, const ::sw::mark::IMark& ); + + void SetInDoc( SwDoc* ); + void ResetInDoc( SwDoc* ); + +public: + virtual ~SwUndoBookmark() override; + + /** + Returns the rewriter for this undo object. + + The rewriter contains the following rule: + + $1 -> <name of bookmark> + + <name of bookmark> is the name of the bookmark whose + insertion/deletion is recorded by this undo object. + + @return the rewriter for this undo object + */ + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoInsBookmark : public SwUndoBookmark +{ +public: + SwUndoInsBookmark( const ::sw::mark::IMark& ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +class SwUndoDeleteBookmark : public SwUndoBookmark +{ +public: + SwUndoDeleteBookmark( const ::sw::mark::IMark& ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +class SwUndoRenameBookmark : public SwUndo +{ + const OUString m_sOldName; + const OUString m_sNewName; + +public: + SwUndoRenameBookmark( const OUString& rOldName, const OUString& rNewName, const SwDoc* pDoc ); + virtual ~SwUndoRenameBookmark() override; + +private: + virtual SwRewriter GetRewriter() const override; + static void Rename( ::sw::UndoRedoContext const &, const OUString& sFrom, const OUString& sTo ); + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +/// Handling undo / redo of checkbox and drop-down form field insertion +class SwUndoInsNoTextFieldmark : public SwUndo +{ +private: + const std::unique_ptr<SwHistoryNoTextFieldmark> m_pHistoryNoTextFieldmark; + +public: + SwUndoInsNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +/// Handling undo / redo of checkbox and drop-down form field deletion +class SwUndoDelNoTextFieldmark : public SwUndo +{ +private: + const std::unique_ptr<SwHistoryNoTextFieldmark> m_pHistoryNoTextFieldmark; + +public: + SwUndoDelNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark); + ~SwUndoDelNoTextFieldmark(); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +/// Handling undo / redo of text form field insertion +class SwUndoInsTextFieldmark : public SwUndo +{ +private: + const std::unique_ptr<SwHistoryTextFieldmark> m_pHistoryTextFieldmark; + +public: + SwUndoInsTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +/// Handling undo / redo of text form field deletion +class SwUndoDelTextFieldmark : public SwUndo +{ +private: + const std::unique_ptr<SwHistoryTextFieldmark> m_pHistoryTextFieldmark; + +public: + SwUndoDelTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark); + ~SwUndoDelTextFieldmark(); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOBOOKMARK_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoCore.hxx b/sw/source/core/inc/UndoCore.hxx new file mode 100644 index 000000000..a86e30c83 --- /dev/null +++ b/sw/source/core/inc/UndoCore.hxx @@ -0,0 +1,273 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOCORE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOCORE_HXX + +#include <undobj.hxx> +#include <calbck.hxx> +#include <o3tl/deleter.hxx> +#include <rtl/ustring.hxx> +#include <redline.hxx> + +#include <memory> +#include <vector> + +class SfxItemSet; +class SwFormatColl; +class SwFormatAnchor; +class SdrMarkList; +class SwUndoDelete; + +namespace sw { + class UndoManager; + class IShellCursorSupplier; +} + +class SwRedlineSaveData: public SwUndRng, public SwRedlineData, private SwUndoSaveSection +{ +public: + SwRedlineSaveData( + SwComparePosition eCmpPos, + const SwPosition& rSttPos, + const SwPosition& rEndPos, + SwRangeRedline& rRedl, + bool bCopyNext ); + + ~SwRedlineSaveData(); + + void RedlineToDoc( SwPaM const & rPam ); + + SwNodeIndex* GetMvSttIdx() const + { + return SwUndoSaveSection::GetMvSttIdx(); + } + +#if OSL_DEBUG_LEVEL > 0 + sal_uInt16 m_nRedlineCount; +#endif +}; + +class SwRedlineSaveDatas { +private: + std::vector<std::unique_ptr<SwRedlineSaveData, o3tl::default_delete<SwRedlineSaveData>>> m_Data; + +public: + SwRedlineSaveDatas() : m_Data() {} + + void clear() { m_Data.clear(); } + bool empty() const { return m_Data.empty(); } + size_t size() const { return m_Data.size(); } + void push_back(std::unique_ptr<SwRedlineSaveData, o3tl::default_delete<SwRedlineSaveData>> pNew) { m_Data.push_back(std::move(pNew)); } + const SwRedlineSaveData& operator[](size_t const nIdx) const { return *m_Data[ nIdx ]; } + SwRedlineSaveData& operator[](size_t const nIdx) { return *m_Data[ nIdx ]; } +}; + +namespace sw { +class UndoRedoContext + : public SfxUndoContext +{ +public: + UndoRedoContext(SwDoc & rDoc, IShellCursorSupplier & rCursorSupplier) + : m_rDoc(rDoc) + , m_rCursorSupplier(rCursorSupplier) + , m_pSelFormat(nullptr) + , m_pMarkList(nullptr) + { } + + SwDoc & GetDoc() const { return m_rDoc; } + + IShellCursorSupplier & GetCursorSupplier() { return m_rCursorSupplier; } + + void SetSelections(SwFrameFormat *const pSelFormat, SdrMarkList *const pMarkList) + { + m_pSelFormat = pSelFormat; + m_pMarkList = pMarkList; + } + void GetSelections(SwFrameFormat *& o_rpSelFormat, SdrMarkList *& o_rpMarkList) + { + o_rpSelFormat = m_pSelFormat; + o_rpMarkList = m_pMarkList; + } + +private: + SwDoc & m_rDoc; + IShellCursorSupplier & m_rCursorSupplier; + SwFrameFormat * m_pSelFormat; + SdrMarkList * m_pMarkList; +}; + +class RepeatContext + : public SfxRepeatTarget +{ +public: + RepeatContext(SwDoc & rDoc, SwPaM & rPaM) + : m_rDoc(rDoc) + , m_pCurrentPaM(& rPaM) + , m_bDeleteRepeated(false) + { } + + SwDoc & GetDoc() const { return m_rDoc; } + + SwPaM & GetRepeatPaM() + { + return *m_pCurrentPaM; + } + +private: + friend class ::sw::UndoManager; + friend class ::SwUndoDelete; + + SwDoc & m_rDoc; + SwPaM * m_pCurrentPaM; + bool m_bDeleteRepeated; /// has a delete action been repeated? +}; + +} // namespace sw + +class SwUndoFormatColl : public SwUndo, private SwUndRng +{ + OUString maFormatName; + std::unique_ptr<SwHistory> mpHistory; + // for correct <ReDo(..)> and <Repeat(..)> + // boolean, which indicates that the attributes are reset at the nodes + // before the format has been applied. + const bool mbReset; + // boolean, which indicates that the list attributes had been reset at + // the nodes before the format has been applied. + const bool mbResetListAttrs; + + void DoSetFormatColl(SwDoc & rDoc, SwPaM const & rPaM); + +public: + SwUndoFormatColl( const SwPaM&, const SwFormatColl*, + const bool bReset, + const bool bResetListAttrs ); + virtual ~SwUndoFormatColl() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + /** + Returns the rewriter for this undo object. + + The rewriter contains one rule: + + $1 -> <name of format collection> + + <name of format collection> is the name of the format + collection that is applied by the action recorded by this undo + object. + + @return the rewriter for this undo object + */ + virtual SwRewriter GetRewriter() const override; + + SwHistory* GetHistory() { return mpHistory.get(); } + +}; + +class SwUndoSetFlyFormat : public SwUndo, public SwClient +{ + SwFrameFormat* m_pFrameFormat; // saved FlyFormat + const OUString m_DerivedFromFormatName; + const OUString m_NewFormatName; + std::unique_ptr<SfxItemSet> m_pItemSet; // the re-/ set attributes + sal_uLong m_nOldNode, m_nNewNode; + sal_Int32 m_nOldContent, m_nNewContent; + RndStdIds m_nOldAnchorType, m_nNewAnchorType; + bool m_bAnchorChanged; + + void PutAttr( sal_uInt16 nWhich, const SfxPoolItem* pItem ); + void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + void GetAnchor( SwFormatAnchor& rAnhor, sal_uLong nNode, sal_Int32 nContent ); + +public: + SwUndoSetFlyFormat( SwFrameFormat& rFlyFormat, const SwFrameFormat& rNewFrameFormat ); + virtual ~SwUndoSetFlyFormat() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoOutlineLeftRight : public SwUndo, private SwUndRng +{ + short m_nOffset; + +public: + SwUndoOutlineLeftRight( const SwPaM& rPam, short nOffset ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +const int nUndoStringLength = 20; + +/** + Shortens a string to a maximum length. + + @param rStr the string to be shortened + @param nLength the maximum length for rStr + @param rFillStr string to replace cut out characters with + + If rStr has less than nLength characters it will be returned unaltered. + + If rStr has more than nLength characters the following algorithm + generates the shortened string: + + frontLength = (nLength - length(rFillStr)) / 2 + rearLength = nLength - length(rFillStr) - frontLength + shortenedString = concat(<first frontLength characters of rStr, + rFillStr, + <last rearLength characters of rStr>) + + Preconditions: + - nLength - length(rFillStr) >= 2 + + @return the shortened string + */ +OUString +ShortenString(const OUString & rStr, sal_Int32 nLength, const OUString & rFillStr); +/** + Denotes special characters in a string. + + The rStr is split into parts containing special characters and + parts not containing special characters. In a part containing + special characters all characters are equal. These parts are + maximal. + + @param rStr the string to denote in + + The resulting string is generated by concatenating the found + parts. The parts without special characters are surrounded by + "'". The parts containing special characters are denoted as "n x", + where n is the length of the part and x is the representation of + the special character (i. e. "tab(s)"). + + @return the denoted string +*/ +OUString DenoteSpecialCharacters(const OUString & rStr); + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOCORE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoDelete.hxx b/sw/source/core/inc/UndoDelete.hxx new file mode 100644 index 000000000..8a0bdabb5 --- /dev/null +++ b/sw/source/core/inc/UndoDelete.hxx @@ -0,0 +1,106 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDODELETE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDODELETE_HXX + +#include <undobj.hxx> +#include <rtl/ustring.hxx> +#include <memory> +#include <optional> + +class SwRedlineSaveDatas; +class SwTextNode; + +namespace sfx2 { + class MetadatableUndo; +} + +class SwUndoDelete + : public SwUndo + , private SwUndRng + , private SwUndoSaveContent +{ + std::unique_ptr<SwNodeIndex> m_pMvStt; // Position of Nodes in UndoNodes-Array + std::optional<OUString> m_aSttStr, m_aEndStr; + std::unique_ptr<SwRedlineSaveDatas> m_pRedlSaveData; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd; + + OUString m_sTableName; + + sal_uLong m_nNode; + sal_uLong m_nNdDiff; // difference of Nodes before/after Delete + sal_uLong m_nSectDiff; // diff. of Nodes before/after Move w/ SectionNodes + sal_uLong m_nReplaceDummy; // diff. to a temporary dummy object + sal_uInt16 m_nSetPos; + + bool m_bGroup : 1; // TRUE: is already Grouped; see CanGrouping() + bool m_bBackSp : 1; // TRUE: if Grouped and preceding content deleted + bool m_bJoinNext: 1; // TRUE: if range is selected forwards + bool m_bTableDelLastNd : 1; // TRUE: TextNode following Table inserted/deleted + bool m_bDelFullPara : 1; // TRUE: entire Nodes were deleted + bool m_bResetPgDesc : 1; // TRUE: reset PgDsc on following node + bool m_bResetPgBrk : 1; // TRUE: reset PgBreak on following node + bool m_bFromTableCopy : 1; // TRUE: called by SwUndoTableCpyTable + + bool SaveContent( const SwPosition* pStt, const SwPosition* pEnd, + SwTextNode* pSttTextNd, SwTextNode* pEndTextNd ); + +public: + SwUndoDelete( + SwPaM&, + bool bFullPara = false, + bool bCalledByTableCpy = false ); + virtual ~SwUndoDelete() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + /** + Returns rewriter for this undo object. + + The rewriter consists of the following rule: + + $1 -> '<deleted text>' + + <deleted text> is shortened to nUndoStringLength characters. + + @return rewriter for this undo object + */ + virtual SwRewriter GetRewriter() const override; + + bool CanGrouping( SwDoc*, const SwPaM& ); + + void SetTableDelLastNd() { m_bTableDelLastNd = true; } + + // for PageDesc/PageBreak Attributes of a table + void SetPgBrkFlags( bool bPageBreak, bool bPageDesc ) + { m_bResetPgDesc = bPageDesc; m_bResetPgBrk = bPageBreak; } + + void SetTableName(const OUString & rName); + + // SwUndoTableCpyTable needs this information: + bool IsDelFullPara() const { return m_bDelFullPara; } + +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDODELETE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoDraw.hxx b/sw/source/core/inc/UndoDraw.hxx new file mode 100644 index 000000000..ee8f37ef2 --- /dev/null +++ b/sw/source/core/inc/UndoDraw.hxx @@ -0,0 +1,134 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDODRAW_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDODRAW_HXX + +#include <undobj.hxx> +#include <memory> + +struct SwUndoGroupObjImpl; +class SdrMark; +class SdrMarkList; +class SdrObject; +class SdrObjGroup; +class SdrUndoAction; +class SwDrawFrameFormat; +class SwDoc; + +// Undo for Draw Objects +class SwSdrUndo : public SwUndo +{ + std::unique_ptr<SdrUndoAction> m_pSdrUndo; + std::unique_ptr<SdrMarkList> m_pMarkList; // MarkList for all selected SdrObjects + +public: + SwSdrUndo( std::unique_ptr<SdrUndoAction> , const SdrMarkList* pMarkList, const SwDoc* pDoc ); + + virtual ~SwSdrUndo() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual OUString GetComment() const override; +}; + +class SwUndoDrawGroup : public SwUndo +{ + std::unique_ptr<SwUndoGroupObjImpl[]> m_pObjArray; + sal_uInt16 m_nSize; + bool m_bDeleteFormat; + +public: + SwUndoDrawGroup( sal_uInt16 nCnt, const SwDoc* pDoc ); + + virtual ~SwUndoDrawGroup() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void AddObj( sal_uInt16 nPos, SwDrawFrameFormat*, SdrObject* ); + void SetGroupFormat( SwDrawFrameFormat* ); +}; + +// Action "ungroup drawing object" is now split into three parts - see +// method <SwDoc::UnGroupSelection(..)>: +// - creation for <SwDrawFrameFormat> instances for the group members of the +// selected group objects +// - intrinsic ungroup of the selected group objects +// - creation of <SwDrawContact> instances for the former group members and +// connection to the Writer layout. +// Thus, two undo actions (instances of <SwUndo>) are needed: +// - Existing class <SwUndoDrawUnGroup> takes over the part for the formats. +// - New class <SwUndoDrawUnGroupConnectToLayout> takes over the part for +// contact object. +class SwUndoDrawUnGroup : public SwUndo +{ + std::unique_ptr<SwUndoGroupObjImpl[]> m_pObjArray; + sal_uInt16 m_nSize; + bool m_bDeleteFormat; + +public: + SwUndoDrawUnGroup( SdrObjGroup*, const SwDoc* pDoc ); + + virtual ~SwUndoDrawUnGroup() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void AddObj( sal_uInt16 nPos, SwDrawFrameFormat* ); +}; + +class SwUndoDrawUnGroupConnectToLayout : public SwUndo +{ +private: + std::vector< std::pair< SwDrawFrameFormat*, SdrObject* > > m_aDrawFormatsAndObjs; + +public: + SwUndoDrawUnGroupConnectToLayout(const SwDoc* pDoc); + + virtual ~SwUndoDrawUnGroupConnectToLayout() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void AddFormatAndObj( SwDrawFrameFormat* pDrawFrameFormat, + SdrObject* pDrawObject ); +}; + +class SwUndoDrawDelete : public SwUndo +{ + std::unique_ptr<SwUndoGroupObjImpl[]> m_pObjArray; + std::unique_ptr<SdrMarkList> m_pMarkList; // MarkList for all selected SdrObjects + bool m_bDeleteFormat; + +public: + SwUndoDrawDelete( sal_uInt16 nCnt, const SwDoc* pDoc ); + + virtual ~SwUndoDrawDelete() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void AddObj( SwDrawFrameFormat*, const SdrMark& ); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDODRAW_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoInsert.hxx b/sw/source/core/inc/UndoInsert.hxx new file mode 100644 index 000000000..801997d65 --- /dev/null +++ b/sw/source/core/inc/UndoInsert.hxx @@ -0,0 +1,223 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOINSERT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOINSERT_HXX + +#include <memory> + +#include <undobj.hxx> +#include <svx/svdtypes.hxx> +#include <rtl/ustring.hxx> +#include <swtypes.hxx> +#include <IDocumentContentOperations.hxx> +#include <optional> + +class Graphic; +class SwGrfNode; +class SwUndoDelete; +class SwUndoFormatAttr; +class SwDoc; +namespace sw { class DocumentContentOperationsManager; } +enum class MirrorGraph; + +class SwUndoInsert: public SwUndo, private SwUndoSaveContent +{ + /// start of Content in UndoNodes for Redo + std::unique_ptr<SwNodeIndex> m_pUndoNodeIndex; + std::optional<OUString> maText; + std::optional<OUString> maUndoText; + std::unique_ptr<SwRedlineData> m_pRedlData; + sal_uLong m_nNode; + sal_Int32 m_nContent, m_nLen; + bool m_bIsWordDelim : 1; + bool m_bIsAppend : 1; + bool m_bWithRsid : 1; + + const SwInsertFlags m_nInsertFlags; + + friend class ::sw::DocumentContentOperationsManager; // actually only DocumentContentOperationsManager::InsertString, because it uses CanGrouping + bool CanGrouping( sal_Unicode cIns ); + bool CanGrouping( const SwPosition& rPos ); + + SwDoc * m_pDoc; + + void Init(const SwNodeIndex & rNode); + std::optional<OUString> GetTextFromDoc() const; + +public: + SwUndoInsert( const SwNodeIndex& rNode, sal_Int32 nContent, sal_Int32 nLen, + const SwInsertFlags nInsertFlags, + bool bWDelim = true ); + SwUndoInsert( const SwNodeIndex& rNode ); + virtual ~SwUndoInsert() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + /** + Returns rewriter for this undo object. + + The returned rewriter has the following rule: + + $1 -> '<inserted text>' + + <inserted text> is shortened to a length of nUndoStringLength. + + @return rewriter for this undo object + */ + virtual SwRewriter GetRewriter() const override; + + void SetWithRsid() { m_bWithRsid = true; } +}; + +SwRewriter +MakeUndoReplaceRewriter(sal_uLong const occurrences, + OUString const& sOld, OUString const& sNew); + +class SwUndoReplace + : public SwUndo +{ +public: + SwUndoReplace(SwPaM const& rPam, + OUString const& rInsert, bool const bRegExp); + + virtual ~SwUndoReplace() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + /** + Returns the rewriter of this undo object. + + If this undo object represents several replacements the + rewriter contains the following rules: + + $1 -> <number of replacements> + $2 -> occurrences of + $3 -> <replaced text> + + If this undo object represents one replacement the rewriter + contains these rules: + + $1 -> <replaced text> + $2 -> "->" (STR_YIELDS) + $3 -> <replacing text> + + @return the rewriter of this undo object + */ + virtual SwRewriter GetRewriter() const override; + + void SetEnd( const SwPaM& rPam ); + +private: + class Impl; + std::unique_ptr<Impl> m_pImpl; +}; + +class SwUndoReRead : public SwUndo +{ + std::unique_ptr<Graphic> mpGraphic; + std::optional<OUString> maNm; + std::optional<OUString> maFltr; + sal_uLong mnPosition; + MirrorGraph mnMirror; + + void SaveGraphicData( const SwGrfNode& ); + void SetAndSave( ::sw::UndoRedoContext & ); + +public: + SwUndoReRead( const SwPaM& rPam, const SwGrfNode& pGrfNd ); + + virtual ~SwUndoReRead() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +class SwUndoInsertLabel : public SwUndo +{ + union { + struct { + // for NoTextFrames + SwUndoInsLayFormat* pUndoFly; + SwUndoFormatAttr* pUndoAttr; + } OBJECT; + struct { + // for tables or TextFrames + SwUndoDelete* pUndoInsNd; + sal_uLong nNode; + } NODE; + }; + + OUString m_sText; + // #i39983# the separator is drawn with a character style + OUString m_sSeparator; + OUString m_sNumberSeparator; + OUString m_sCharacterStyle; + // #i26791# - re-store of drawing object position no longer needed + sal_uInt16 m_nFieldId; + SwLabelType m_eType; + SdrLayerID m_nLayerId; // for character objects + bool m_bBefore :1; + bool m_bUndoKeep :1; + bool m_bCopyBorder :1; + +public: + SwUndoInsertLabel( const SwLabelType eTyp, const OUString &rText, + // #i39983# the separator is drawn with a character style + const OUString& rSeparator, + const OUString& rNumberSeparator, //#i61007# order of captions + const bool bBefore, const sal_uInt16 nId, + const OUString& rCharacterStyle, + const bool bCpyBrd, + const SwDoc* pDoc ); + virtual ~SwUndoInsertLabel() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + /** + Returns the rewriter of this undo object. + + The rewriter contains this rule: + + $1 -> '<text of inserted label>' + + <text of inserted label> is shortened to nUndoStringLength + characters. + + @return the rewriter of this undo object + */ + virtual SwRewriter GetRewriter() const override; + static SwRewriter CreateRewriter(const OUString &rStr); + + void SetNodePos( sal_uLong nNd ) + { if( SwLabelType::Object != m_eType ) NODE.nNode = nNd; } + + void SetUndoKeep() { m_bUndoKeep = true; } + void SetFlys( SwFrameFormat& rOldFly, SfxItemSet const & rChgSet, SwFrameFormat& rNewFly ); + void SetDrawObj( SdrLayerID nLayerId ); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOINSERT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoManager.hxx b/sw/source/core/inc/UndoManager.hxx new file mode 100644 index 000000000..fda9c734a --- /dev/null +++ b/sw/source/core/inc/UndoManager.hxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOMANAGER_HXX + +#include <IDocumentUndoRedo.hxx> +#include <svx/sdrundomanager.hxx> +#include <ndarr.hxx> +#include <memory> + +class IDocumentDrawModelAccess; +class IDocumentRedlineAccess; +class IDocumentState; +class SwDocShell; +class SwView; + +namespace sw { + +class SAL_DLLPUBLIC_RTTI UndoManager + : public IDocumentUndoRedo + , public SdrUndoManager +{ +public: + UndoManager(std::shared_ptr<SwNodes> const & pUndoNodes, + IDocumentDrawModelAccess & rDrawModelAccess, + IDocumentRedlineAccess & rRedlineAccess, + IDocumentState & rState); + + /** IDocumentUndoRedo */ + virtual void DoUndo(bool const bDoUndo) override; + virtual bool DoesUndo() const override; + virtual void DoGroupUndo(bool const bDoUndo) override; + virtual bool DoesGroupUndo() const override; + virtual void DoDrawUndo(bool const bDoUndo) override; + virtual bool DoesDrawUndo() const override; + void DoRepair(bool bRepair) override; + bool DoesRepair() const override; + virtual void SetUndoNoModifiedPosition() override; + virtual void LockUndoNoModifiedPosition() override; + virtual void UnLockUndoNoModifiedPosition() override; + virtual void SetUndoNoResetModified() override; + virtual bool IsUndoNoResetModified() const override; + + virtual SwUndoId StartUndo(SwUndoId const eUndoId, + SwRewriter const*const pRewriter) override; + virtual SwUndoId EndUndo(SwUndoId const eUndoId, + SwRewriter const*const pRewriter) override; + virtual void DelAllUndoObj() override; + virtual bool GetLastUndoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView = nullptr) const override; + virtual SwUndoComments_t GetUndoComments() const override; + virtual bool GetFirstRedoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView = nullptr) const override; + virtual SwUndoComments_t GetRedoComments() const override; + virtual bool Repeat(::sw::RepeatContext & rContext, + sal_uInt16 const nRepeatCnt) override; + virtual SwUndoId GetRepeatInfo(OUString *const o_pStr) const override; + virtual void AppendUndo(std::unique_ptr<SwUndo> pUndo) override; + virtual void ClearRedo() override; + virtual bool IsUndoNodes(SwNodes const& rNodes) const override; + virtual size_t GetUndoActionCount(const bool bCurrentLevel = true) const override; + size_t GetRedoActionCount(const bool bCurrentLevel = true) const override; + void SetView(SwView* pView) override; + + // SfxUndoManager + virtual void AddUndoAction(std::unique_ptr<SfxUndoAction> pAction, + bool bTryMerg = false) override; + virtual bool Undo() override; + virtual bool Redo() override; + + SwUndo * RemoveLastUndo(); + SwUndo * GetLastUndo(); + + SwNodes const& GetUndoNodes() const; + SwNodes & GetUndoNodes(); + void SetDocShell(SwDocShell* pDocShell); + +protected: + virtual void EmptyActionsChanged() override; + +private: + IDocumentDrawModelAccess & m_rDrawModelAccess; + IDocumentRedlineAccess & m_rRedlineAccess; + IDocumentState & m_rState; + + /// Undo nodes array: content not currently in document + std::shared_ptr<SwNodes> m_xUndoNodes; + + bool m_bGroupUndo : 1; // TRUE: Undo grouping enabled + bool m_bDrawUndo : 1; // TRUE: Draw Undo enabled + /// If true, then repair mode is enabled. + bool m_bRepair; + bool m_bLockUndoNoModifiedPosition : 1; + /// set the IgnoreRepeat flag on every added action + bool m_isAddWithIgnoreRepeat; + /// position in Undo-Array at which Doc was saved (and is not modified) + UndoStackMark m_UndoSaveMark; + SwDocShell* m_pDocShell; + SwView* m_pView; + + enum class UndoOrRedoType { Undo, Redo }; + bool impl_DoUndoRedo(UndoOrRedoType undoOrRedo); + + // UGLY: should not be called + using SdrUndoManager::Repeat; +}; + +} // namespace sw + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoNumbering.hxx b/sw/source/core/inc/UndoNumbering.hxx new file mode 100644 index 000000000..b83a78ec4 --- /dev/null +++ b/sw/source/core/inc/UndoNumbering.hxx @@ -0,0 +1,142 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDONUMBERING_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDONUMBERING_HXX + +#include <memory> +#include <vector> +#include <undobj.hxx> +#include <rtl/ustring.hxx> +#include <numrule.hxx> + +class SwUndoInsNum : public SwUndo, private SwUndRng +{ + SwNumRule m_aNumRule; + std::unique_ptr<SwHistory> m_pHistory; + std::unique_ptr<SwNumRule> m_pOldNumRule; + OUString m_sReplaceRule; + sal_uInt16 m_nLRSavePos; + +public: + SwUndoInsNum( const SwPaM& rPam, const SwNumRule& rRule ); + SwUndoInsNum( const SwNumRule& rOldRule, const SwNumRule& rNewRule, + const SwDoc* pDoc, SwUndoId nUndoId = SwUndoId::INSFMTATTR ); + SwUndoInsNum( const SwPosition& rPos, const SwNumRule& rRule, + const OUString& rReplaceRule ); + + virtual ~SwUndoInsNum() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + virtual SwRewriter GetRewriter() const override; + + SwHistory* GetHistory(); // will be created if necessary + void SaveOldNumRule( const SwNumRule& rOld ); + + void SetLRSpaceEndPos(); + +}; + +class SwUndoDelNum : public SwUndo, private SwUndRng +{ + struct NodeLevel + { + sal_uLong index; + int level; + NodeLevel(sal_uLong idx, int lvl) : index(idx), level(lvl) {}; + }; + std::vector<NodeLevel> m_aNodes; + std::unique_ptr<SwHistory> m_pHistory; + +public: + SwUndoDelNum( const SwPaM& rPam ); + + virtual ~SwUndoDelNum() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void AddNode( const SwTextNode& rNd ); + SwHistory* GetHistory() { return m_pHistory.get(); } +}; + +class SwUndoMoveNum : public SwUndo, private SwUndRng +{ + sal_uLong nNewStt; + long nOffset; + +public: + SwUndoMoveNum( const SwPaM& rPam, long nOffset, bool bIsOutlMv ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetStartNode( sal_uLong nValue ) { nNewStt = nValue; } +}; + +class SwUndoNumUpDown : public SwUndo, private SwUndRng +{ + short nOffset; + +public: + SwUndoNumUpDown( const SwPaM& rPam, short nOffset ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +class SwUndoNumOrNoNum : public SwUndo +{ + sal_uLong nIdx; + bool mbNewNum, mbOldNum; + +public: + SwUndoNumOrNoNum( const SwNodeIndex& rIdx, bool mbOldNum, + bool mbNewNum ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +class SwUndoNumRuleStart : public SwUndo +{ + sal_uLong nIdx; + sal_uInt16 nOldStt, nNewStt; + bool bSetSttValue : 1; + bool bFlag : 1; + +public: + SwUndoNumRuleStart( const SwPosition& rPos, bool bDelete ); + SwUndoNumRuleStart( const SwPosition& rPos, sal_uInt16 nStt ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDONUMBERING_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoOverwrite.hxx b/sw/source/core/inc/UndoOverwrite.hxx new file mode 100644 index 000000000..1a21a87a8 --- /dev/null +++ b/sw/source/core/inc/UndoOverwrite.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOOVERWRITE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOOVERWRITE_HXX + +#include <memory> +#include <rtl/ustring.hxx> +#include <undobj.hxx> + +class SwRedlineSaveDatas; +class SwTextNode; +enum class TransliterationFlags; +namespace utl { + class TransliterationWrapper; +} + +class SwUndoOverwrite: public SwUndo, private SwUndoSaveContent +{ + OUString aDelStr, aInsStr; + std::unique_ptr<SwRedlineSaveDatas> pRedlSaveData; + sal_uLong nSttNode; + sal_Int32 nSttContent; + bool bInsChar : 1; // no Overwrite, but Insert + bool bGroup : 1; // TRUE: is already grouped; evaluated in CanGrouping() + +public: + SwUndoOverwrite( SwDoc*, SwPosition&, sal_Unicode cIns ); + + virtual ~SwUndoOverwrite() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + /** + Returns the rewriter of this undo object. + + The rewriter contains the following rule: + + $1 -> '<overwritten text>' + + <overwritten text> is shortened to nUndoStringLength characters. + + @return the rewriter of this undo object + */ + virtual SwRewriter GetRewriter() const override; + + bool CanGrouping( SwDoc*, SwPosition&, sal_Unicode cIns ); +}; + +struct UndoTransliterate_Data; +class SwUndoTransliterate : public SwUndo, public SwUndRng +{ + std::vector< std::unique_ptr<UndoTransliterate_Data> > aChanges; + TransliterationFlags nType; + + void DoTransliterate(SwDoc & rDoc, SwPaM const & rPam); + +public: + SwUndoTransliterate( const SwPaM& rPam, + const utl::TransliterationWrapper& rTrans ); + + virtual ~SwUndoTransliterate() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void AddChanges( SwTextNode& rTNd, sal_Int32 nStart, sal_Int32 nLen, + css::uno::Sequence <sal_Int32> const & rOffsets ); + bool HasData() const { return aChanges.size() > 0; } +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOOVERWRITE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoRedline.hxx b/sw/source/core/inc/UndoRedline.hxx new file mode 100644 index 000000000..1688e2872 --- /dev/null +++ b/sw/source/core/inc/UndoRedline.hxx @@ -0,0 +1,137 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOREDLINE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOREDLINE_HXX + +#include <memory> +#include <undobj.hxx> + +struct SwSortOptions; +class SwRangeRedline; +class SwRedlineSaveDatas; +class SwUndoDelete; + +class SwUndoRedline : public SwUndo, public SwUndRng +{ +protected: + std::unique_ptr<SwRedlineData> mpRedlData; + std::unique_ptr<SwRedlineSaveDatas> mpRedlSaveData; + SwUndoId mnUserId; + bool mbHiddenRedlines; + + virtual void UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam); + virtual void RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam); + +public: + SwUndoRedline( SwUndoId nUserId, const SwPaM& rRange ); + + virtual ~SwUndoRedline() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + sal_uInt16 GetRedlSaveCount() const; +}; + +class SwUndoRedlineDelete : public SwUndoRedline +{ + bool m_bCanGroup : 1; + bool m_bIsDelim : 1; + bool m_bIsBackspace : 1; + + OUString m_sRedlineText; + + virtual void UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; + virtual void RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; + +public: + SwUndoRedlineDelete( const SwPaM& rRange, SwUndoId nUserId ); + virtual SwRewriter GetRewriter() const override; + + bool CanGrouping( const SwUndoRedlineDelete& rPrev ); + + // SwUndoTableCpyTable needs this information: + long NodeDiff() const { return m_nSttNode - m_nEndNode; } + sal_Int32 ContentStart() const { return m_nSttContent; } + + void SetRedlineText(const OUString & rText); +}; + +class SwUndoRedlineSort : public SwUndoRedline +{ + std::unique_ptr<SwSortOptions> m_pOpt; + sal_uLong m_nSaveEndNode; + sal_Int32 m_nSaveEndContent; + + virtual void UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; + virtual void RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; + +public: + SwUndoRedlineSort( const SwPaM& rRange, const SwSortOptions& rOpt ); + + virtual ~SwUndoRedlineSort() override; + + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetSaveRange( const SwPaM& rRange ); +}; + +class SwUndoAcceptRedline : public SwUndoRedline +{ +private: + virtual void RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; + +public: + SwUndoAcceptRedline( const SwPaM& rRange ); + + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +class SwUndoRejectRedline : public SwUndoRedline +{ +private: + virtual void RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; + +public: + SwUndoRejectRedline( const SwPaM& rRange ); + + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +class SwUndoCompDoc : public SwUndo, public SwUndRng +{ + std::unique_ptr<SwRedlineData> m_pRedlineData; + std::unique_ptr<SwUndoDelete> m_pUndoDelete, m_pUndoDelete2; + std::unique_ptr<SwRedlineSaveDatas> m_pRedlineSaveDatas; + bool m_bInsert; + +public: + SwUndoCompDoc( const SwPaM& rRg, bool bIns ); + SwUndoCompDoc( const SwRangeRedline& rRedl ); + + virtual ~SwUndoCompDoc() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOREDLINE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoSection.hxx b/sw/source/core/inc/UndoSection.hxx new file mode 100644 index 000000000..ac7858063 --- /dev/null +++ b/sw/source/core/inc/UndoSection.hxx @@ -0,0 +1,97 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOSECTION_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOSECTION_HXX + +#include <o3tl/deleter.hxx> +#include <undobj.hxx> +#include <memory> + +class SfxItemSet; +class SwTextNode; +class SwSectionData; +class SwSectionFormat; +class SwTOXBase; + +namespace sw { + enum class RedlineMode; +}; + +class SwUndoInsSection : public SwUndo, private SwUndRng +{ +private: + const std::unique_ptr<SwSectionData> m_pSectionData; + const std::unique_ptr<std::pair<SwTOXBase *, sw::RedlineMode>> m_pTOXBase; /// set iff section is TOX + const std::unique_ptr<SfxItemSet> m_pAttrSet; + std::unique_ptr<SwHistory> m_pHistory; + std::unique_ptr<SwRedlineData> m_pRedlData; + std::unique_ptr<SwRedlineSaveDatas> m_pRedlineSaveData; + sal_uLong m_nSectionNodePos; + bool m_bSplitAtStart : 1; + bool m_bSplitAtEnd : 1; + bool m_bUpdateFootnote : 1; + + void Join( SwDoc& rDoc, sal_uLong nNode ); + +public: + SwUndoInsSection(SwPaM const&, SwSectionData const&, + SfxItemSet const* pSet, + std::pair<SwTOXBase const*, sw::RedlineMode> const* pTOXBase); + + virtual ~SwUndoInsSection() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetSectNdPos(sal_uLong const nPos) { m_nSectionNodePos = nPos; } + void SaveSplitNode(SwTextNode *const pTextNd, bool const bAtStart); + void SetUpdateFootnoteFlag(bool const bFlag) { m_bUpdateFootnote = bFlag; } +}; + +std::unique_ptr<SwUndo> MakeUndoDelSection(SwSectionFormat const&); + +std::unique_ptr<SwUndo> MakeUndoUpdateSection(SwSectionFormat const&, bool const); + + +class SwTOXBaseSection; +class SwUndoDelSection; + +class SwUndoUpdateIndex : public SwUndo +{ +private: + std::unique_ptr<SwUndoDelSection> m_pTitleSectionUpdated; + std::unique_ptr<SwUndoSaveSection, o3tl::default_delete<SwUndoSaveSection>> const m_pSaveSectionOriginal; + std::unique_ptr<SwUndoSaveSection, o3tl::default_delete<SwUndoSaveSection>> const m_pSaveSectionUpdated; + sal_uLong const m_nStartIndex; + +public: + SwUndoUpdateIndex(SwTOXBaseSection &); + virtual ~SwUndoUpdateIndex() override; + + void TitleSectionInserted(SwSectionFormat & rSectionFormat); + + virtual void UndoImpl(::sw::UndoRedoContext &) override; + virtual void RedoImpl(::sw::UndoRedoContext &) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOSECTION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoSort.hxx b/sw/source/core/inc/UndoSort.hxx new file mode 100644 index 000000000..27f97ceaf --- /dev/null +++ b/sw/source/core/inc/UndoSort.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOSORT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOSORT_HXX + +#include <undobj.hxx> + +#include <rtl/ustring.hxx> + +#include <memory> +#include <vector> + +struct SwSortOptions; +class SwTableNode; +class SwUndoAttrTable; + +struct SwSortUndoElement +{ + union { + struct { + sal_uLong nID; + sal_uLong nSource, nTarget; + } TXT; + struct { + OUString *pSource, *pTarget; + } TBL; + } SORT_TXT_TBL; + + SwSortUndoElement( const OUString& aS, const OUString& aT ) + { + SORT_TXT_TBL.TBL.pSource = new OUString( aS ); + SORT_TXT_TBL.TBL.pTarget = new OUString( aT ); + } + SwSortUndoElement( sal_uLong nS, sal_uLong nT ) + { + SORT_TXT_TBL.TXT.nSource = nS; + SORT_TXT_TBL.TXT.nTarget = nT; + SORT_TXT_TBL.TXT.nID = 0xffffffff; + } + ~SwSortUndoElement(); +}; + +class SwUndoSort : public SwUndo, private SwUndRng +{ + std::unique_ptr<SwSortOptions> pSortOpt; + std::vector<std::unique_ptr<SwSortUndoElement>> m_SortList; + std::unique_ptr<SwUndoAttrTable> pUndoTableAttr; + sal_uLong nTableNd; + +public: + SwUndoSort( const SwPaM&, const SwSortOptions& ); + SwUndoSort( sal_uLong nStt, sal_uLong nEnd, const SwTableNode&, + const SwSortOptions&, bool bSaveTable ); + + virtual ~SwUndoSort() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void Insert( const OUString& rOrgPos, const OUString& rNewPos ); + void Insert( sal_uLong nOrgPos, sal_uLong nNewPos ); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOSORT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoSplitMove.hxx b/sw/source/core/inc/UndoSplitMove.hxx new file mode 100644 index 000000000..ad779ee44 --- /dev/null +++ b/sw/source/core/inc/UndoSplitMove.hxx @@ -0,0 +1,85 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOSPLITMOVE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOSPLITMOVE_HXX + +#include <undobj.hxx> + +class SwUndoSplitNode: public SwUndo +{ + std::unique_ptr<SwHistory> m_pHistory; + std::unique_ptr<SwRedlineData> pRedlData; + sal_uLong nNode; + sal_Int32 nContent; + bool bTableFlag : 1; + bool bChkTableStt : 1; + sal_uInt32 nParRsid; + +public: + SwUndoSplitNode( SwDoc* pDoc, const SwPosition& rPos, bool bChkTable ); + + virtual ~SwUndoSplitNode() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetTableFlag() { bTableFlag = true; } +}; + +class SwUndoMove : public SwUndo, private SwUndRng, private SwUndoSaveContent +{ + // nDest... - destination range of move (after move!) + // nIns... - source Position of move (after move!) + // nMv... - destination position of move (before move!); for REDO + sal_uLong m_nDestStartNode, m_nDestEndNode, m_nInsPosNode, m_nMoveDestNode; + sal_Int32 m_nDestStartContent, m_nDestEndContent, m_nInsPosContent, m_nMoveDestContent; + + sal_uInt16 m_nFootnoteStart; // StartPos of Footnotes in History + + bool m_bJoinNext : 1, + m_bMoveRange : 1; + + bool m_bMoveRedlines; // use DOC_MOVEREDLINES when calling SwDoc::Move + + void DelFootnote( const SwPaM& ); + +public: + SwUndoMove( const SwPaM&, const SwPosition& ); + SwUndoMove( SwDoc* pDoc, const SwNodeRange&, const SwNodeIndex& ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + /// set the destination range after the move + void SetDestRange( const SwNodeIndex& rStt, const SwNodeIndex& rEnd, + const SwNodeIndex& rInsPos ); + + bool IsMoveRange() const { return m_bMoveRange; } + sal_uLong GetEndNode() const { return m_nEndNode; } + sal_uLong GetDestSttNode() const { return m_nDestStartNode; } + sal_Int32 GetDestSttContent() const { return m_nDestStartContent; } + + void SetMoveRedlines( bool b ) { m_bMoveRedlines = b; } +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOSPLITMOVE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/UndoTable.hxx b/sw/source/core/inc/UndoTable.hxx new file mode 100644 index 000000000..88c3b10db --- /dev/null +++ b/sw/source/core/inc/UndoTable.hxx @@ -0,0 +1,418 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOTABLE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOTABLE_HXX + +#include <o3tl/deleter.hxx> +#include <ndarr.hxx> +#include <undobj.hxx> +#include <set> +#include <itabenum.hxx> +#include <tblenum.hxx> +#include <memory> +#include <vector> + +class SfxItemSet; + +struct SwSaveRowSpan; +class SaveTable; +class SwDDEFieldType; +class SwUndoDelete; +class SwSelBoxes; +class SwTable; +class SwTableBox; +class SwStartNode; +class SwTableNode; +class SwTableAutoFormat; +class SwTableSortBoxes; + +class SwUndoInsTable : public SwUndo +{ + OUString m_sTableName; + SwInsertTableOptions m_aInsTableOptions; + std::unique_ptr<SwDDEFieldType> m_pDDEFieldType; + std::unique_ptr<std::vector<sal_uInt16>> m_pColumnWidth; + std::unique_ptr<SwRedlineData> m_pRedlineData; + std::unique_ptr<SwTableAutoFormat> m_pAutoFormat; + sal_uLong m_nStartNode; + sal_uInt16 m_nRows, m_nColumns; + sal_uInt16 const m_nAdjust; + +public: + SwUndoInsTable( const SwPosition&, sal_uInt16 nCols, sal_uInt16 nRows, + sal_uInt16 eAdjust, const SwInsertTableOptions& rInsTableOpts, + const SwTableAutoFormat* pTAFormat, const std::vector<sal_uInt16> *pColArr, + const OUString & rName); + + virtual ~SwUndoInsTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoTextToTable : public SwUndo, public SwUndRng +{ + OUString m_sTableName; + SwInsertTableOptions m_aInsertTableOpts; + std::vector<sal_uLong> mvDelBoxes; + std::unique_ptr<SwTableAutoFormat> m_pAutoFormat; + SwHistory* m_pHistory; + sal_Unicode m_cSeparator; + sal_uInt16 m_nAdjust; + bool m_bSplitEnd : 1; + +public: + SwUndoTextToTable( const SwPaM&, const SwInsertTableOptions&, sal_Unicode, + sal_uInt16, + const SwTableAutoFormat* pAFormat ); + + virtual ~SwUndoTextToTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + SwHistory& GetHistory(); // will be created if necessary + void AddFillBox( const SwTableBox& rBox ); +}; + +class SwUndoTableToText : public SwUndo +{ + OUString m_sTableName; + std::unique_ptr<SwDDEFieldType> m_pDDEFieldType; + std::unique_ptr<SaveTable> m_pTableSave; + SwTableToTextSaves m_vBoxSaves; + std::unique_ptr<SwHistory> m_pHistory; + sal_uLong m_nStartNode, m_nEndNode; + sal_Unicode m_cSeparator; + sal_uInt16 m_nHeadlineRepeat; + bool m_bCheckNumFormat : 1; + +public: + SwUndoTableToText( const SwTable& rTable, sal_Unicode cCh ); + + virtual ~SwUndoTableToText() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetRange( const SwNodeRange& ); + void AddBoxPos( SwDoc& rDoc, sal_uLong nNdIdx, sal_uLong nEndIdx, + sal_Int32 nContentIdx = SAL_MAX_INT32); +}; + +class SwUndoAttrTable : public SwUndo +{ + sal_uLong m_nStartNode; + std::unique_ptr<SaveTable> m_pSaveTable; + bool m_bClearTableCol : 1; + +public: + SwUndoAttrTable( const SwTableNode& rTableNd, bool bClearTabCols = false ); + + virtual ~SwUndoAttrTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +class SwUndoTableNumFormat; + +class SwUndoTableAutoFormat : public SwUndo +{ + OUString m_TableStyleName; + sal_uLong m_nStartNode; + std::unique_ptr<SaveTable> m_pSaveTable; + std::vector< std::shared_ptr<SwUndoTableNumFormat> > m_Undos; + bool m_bSaveContentAttr; + sal_uInt16 m_nRepeatHeading; + + void UndoRedo(bool const bUndo, ::sw::UndoRedoContext & rContext); + +public: + SwUndoTableAutoFormat( const SwTableNode& rTableNd, const SwTableAutoFormat& ); + + virtual ~SwUndoTableAutoFormat() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void SaveBoxContent( const SwTableBox& rBox ); +}; + +using SwUndoSaveSections = std::vector<std::unique_ptr<SwUndoSaveSection, o3tl::default_delete<SwUndoSaveSection>>>; + +class SwUndoTableNdsChg : public SwUndo +{ + std::unique_ptr<SaveTable> m_pSaveTable; + std::set<sal_uLong> m_Boxes; + struct BoxMove + { + sal_uLong index; ///< Index of this box. + bool hasMoved; ///< Has this box been moved already. + BoxMove(sal_uLong idx, bool moved=false) : index(idx), hasMoved(moved) {}; + bool operator<(const BoxMove& other) const { return index < other.index; }; + }; + std::unique_ptr< std::set<BoxMove> > m_pNewSttNds; + std::unique_ptr<SwUndoSaveSections> m_pDelSects; + long m_nMin, m_nMax; // for redo of delete column + sal_uLong m_nSttNode; + sal_uInt16 m_nCount; + bool m_bFlag; + bool m_bSameHeight; // only used for SplitRow + + SwUndoTableNdsChg(SwUndoTableNdsChg const&) = delete; + SwUndoTableNdsChg& operator=(SwUndoTableNdsChg const&) = delete; + +public: + SwUndoTableNdsChg( SwUndoId UndoId, + const SwSelBoxes& rBoxes, + const SwTableNode& rTableNd, + long nMn, long nMx, + sal_uInt16 nCnt, bool bFlg, bool bSameHeight ); + + virtual ~SwUndoTableNdsChg() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void SaveNewBoxes( const SwTableNode& rTableNd, const SwTableSortBoxes& rOld ); + void SaveNewBoxes( const SwTableNode& rTableNd, const SwTableSortBoxes& rOld, + const SwSelBoxes& rBoxes, const std::vector<sal_uLong> &rNodeCnts ); + void SaveSection( SwStartNode* pSttNd ); + void ReNewBoxes( const SwSelBoxes& rBoxes ); + +}; + +class SwUndoMove; + +class SwUndoTableMerge : public SwUndo, private SwUndRng +{ + sal_uLong m_nTableNode; + std::unique_ptr<SaveTable> m_pSaveTable; + std::set<sal_uLong> m_Boxes; + std::vector<sal_uLong> m_aNewStartNodes; + std::vector<std::unique_ptr<SwUndoMove>> m_vMoves; + std::unique_ptr<SwHistory> m_pHistory; + +public: + SwUndoTableMerge( const SwPaM& rTableSel ); + + virtual ~SwUndoTableMerge() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void MoveBoxContent( SwDoc* pDoc, SwNodeRange& rRg, SwNodeIndex& rPos ); + + void SetSelBoxes( const SwSelBoxes& rBoxes ); + + void AddNewBox( sal_uLong nSttNdIdx ) + { m_aNewStartNodes.push_back( nSttNdIdx ); } + + void SaveCollection( const SwTableBox& rBox ); +}; + +class SwUndoTableNumFormat : public SwUndo +{ + std::unique_ptr<SfxItemSet> m_pBoxSet; + std::unique_ptr<SwHistory> m_pHistory; + OUString m_aStr, m_aNewFormula; + + sal_uLong m_nFormatIdx, m_nNewFormatIdx; + double m_fNum, m_fNewNum; + sal_uLong m_nNode; + sal_uLong m_nNodePos; + + bool m_bNewFormat : 1; + bool m_bNewFormula : 1; + bool m_bNewValue : 1; + +public: + SwUndoTableNumFormat( const SwTableBox& rBox, const SfxItemSet* pNewSet = nullptr ); + + virtual ~SwUndoTableNumFormat() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void SetNumFormat( sal_uLong nNewNumFormatIdx, const double& rNewNumber ) + { m_nFormatIdx = nNewNumFormatIdx; m_fNum = rNewNumber; } + void SetBox( const SwTableBox& rBox ); +}; + +struct UndoTableCpyTable_Entry; + +class SwUndoTableCpyTable : public SwUndo +{ + std::vector<std::unique_ptr<UndoTableCpyTable_Entry>> m_vArr; + std::unique_ptr<SwUndoTableNdsChg> m_pInsRowUndo; + + //b6341295: When redlining is active, PrepareRedline has to create the + //redlining attributes for the new and the old table cell content + static std::unique_ptr<SwUndo> PrepareRedline( SwDoc* pDoc, const SwTableBox& rBox, + const SwPosition& rPos, bool& rJoin, bool bRedo ); + +public: + SwUndoTableCpyTable(const SwDoc* pDoc); + + virtual ~SwUndoTableCpyTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void AddBoxBefore( const SwTableBox& rBox, bool bDelContent ); + void AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, + bool bDelContent ); + + bool IsEmpty() const; + bool InsertRow( SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ); +}; + +class SwUndoCpyTable : public SwUndo +{ + std::unique_ptr<SwUndoDelete> m_pDelete; + sal_uLong m_nTableNode; + +public: + SwUndoCpyTable(const SwDoc* pDoc); + + virtual ~SwUndoCpyTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + void SetTableSttIdx( sal_uLong nIdx ) { m_nTableNode = nIdx; } +}; + +class SwUndoSplitTable : public SwUndo +{ + sal_uLong m_nTableNode, m_nOffset; + std::unique_ptr<SwSaveRowSpan> mpSaveRowSpan; // stores row span values at the splitting row + std::unique_ptr<SaveTable> m_pSavedTable; + std::unique_ptr<SwHistory> m_pHistory; + SplitTable_HeadlineOption const m_nMode; + sal_uInt16 m_nFormulaEnd; + bool m_bCalcNewSize; + +public: + SwUndoSplitTable( const SwTableNode& rTableNd, std::unique_ptr<SwSaveRowSpan> pRowSp, + SplitTable_HeadlineOption nMode, bool bCalcNewSize ); + + virtual ~SwUndoSplitTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SetTableNodeOffset( sal_uLong nIdx ) { m_nOffset = nIdx - m_nTableNode; } + SwHistory* GetHistory() { return m_pHistory.get(); } + void SaveFormula( SwHistory& rHistory ); +}; + +class SwUndoMergeTable : public SwUndo +{ + OUString m_aName; + sal_uLong m_nTableNode; + std::unique_ptr<SaveTable> m_pSaveTable, m_pSaveHdl; + std::unique_ptr<SwHistory> m_pHistory; + sal_uInt16 m_nMode; + bool m_bWithPrev; + +public: + SwUndoMergeTable( const SwTableNode& rTableNd, const SwTableNode& rDelTableNd, + bool bWithPrev, sal_uInt16 nMode ); + + virtual ~SwUndoMergeTable() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; + + void SaveFormula( SwHistory& rHistory ); +}; + +class SwUndoTableHeadline : public SwUndo +{ + sal_uLong m_nTableNode; + sal_uInt16 m_nOldHeadline; + sal_uInt16 m_nNewHeadline; + +public: + SwUndoTableHeadline( const SwTable&, sal_uInt16 nOldHdl, sal_uInt16 nNewHdl ); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + virtual void RepeatImpl( ::sw::RepeatContext & ) override; +}; + +void InsertSort( std::vector<sal_uInt16>& rArr, sal_uInt16 nIdx ); + +class SwUndoTableStyleMake : public SwUndo +{ + OUString m_sName; + std::unique_ptr<SwTableAutoFormat> m_pAutoFormat; +public: + SwUndoTableStyleMake(const OUString& rName, const SwDoc* pDoc); + + virtual ~SwUndoTableStyleMake() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoTableStyleDelete : public SwUndo +{ + std::unique_ptr<SwTableAutoFormat> m_pAutoFormat; + std::vector<SwTable*> m_rAffectedTables; +public: + SwUndoTableStyleDelete(std::unique_ptr<SwTableAutoFormat> pAutoFormat, const std::vector<SwTable*>& rAffectedTables, const SwDoc* pDoc); + + virtual ~SwUndoTableStyleDelete() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +class SwUndoTableStyleUpdate : public SwUndo +{ + std::unique_ptr<SwTableAutoFormat> m_pOldFormat, m_pNewFormat; +public: + SwUndoTableStyleUpdate(const SwTableAutoFormat& rNewFormat, const SwTableAutoFormat& rOldFormat, const SwDoc* pDoc); + + virtual ~SwUndoTableStyleUpdate() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOTABLE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/acorrect.hxx b/sw/source/core/inc/acorrect.hxx new file mode 100644 index 000000000..1ab8f9411 --- /dev/null +++ b/sw/source/core/inc/acorrect.hxx @@ -0,0 +1,119 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ACORRECT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ACORRECT_HXX + +#include <memory> + +#include <svl/itemset.hxx> +#include <tools/solar.h> +#include <editeng/svxacorr.hxx> + +class SwEditShell; +class SwPaM; +class SwNodeIndex; +struct SwPosition; +class SfxItemSet; + +class SwDontExpandItem +{ + std::unique_ptr<SfxItemSet> m_pDontExpandItems; + +public: + SwDontExpandItem() {} + ~SwDontExpandItem(); + + void SaveDontExpandItems( const SwPosition& rPos ); + void RestoreDontExpandItems( const SwPosition& rPos ); + +}; + +class SwAutoCorrDoc : public SvxAutoCorrDoc +{ + SwEditShell& m_rEditSh; + SwPaM& m_rCursor; + std::unique_ptr<SwNodeIndex> m_pIndex; + int m_nEndUndoCounter; + bool m_bUndoIdInitialized; + + void DeleteSel( SwPaM& rDelPam ); + void DeleteSelImpl(SwPaM & rDelPam); + +public: + SwAutoCorrDoc( SwEditShell& rEditShell, SwPaM& rPam, sal_Unicode cIns = 0 ); + virtual ~SwAutoCorrDoc() override; + + virtual bool Delete( sal_Int32 nStt, sal_Int32 nEnd ) override; + virtual bool Insert( sal_Int32 nPos, const OUString& rText ) override; + virtual bool Replace( sal_Int32 nPos, const OUString& rText ) override; + virtual bool ReplaceRange( sal_Int32 nPos, sal_Int32 nLen, const OUString& rText ) override; + + virtual void SetAttr( sal_Int32 nStt, sal_Int32 nEnd, sal_uInt16 nSlotId, + SfxPoolItem& ) override; + + virtual bool SetINetAttr( sal_Int32 nStt, sal_Int32 nEnd, const OUString& rURL ) override; + + // return text of a previous paragraph + // If it does not exist or if there is nothing before, return blank. + // - true: paragraph before "normal" insertion position + // - false: paragraph in that the corrected word was inserted + // (does not need to be the same paragraph) + virtual OUString const* GetPrevPara(bool bAtNormalPos) override; + + virtual bool ChgAutoCorrWord( sal_Int32& rSttPos, sal_Int32 nEndPos, + SvxAutoCorrect& rACorrect, + OUString* pPara ) override; + virtual bool TransliterateRTLWord( sal_Int32& rSttPos, sal_Int32 nEndPos ) override; + + // Will be called after swapping characters by the functions + // - FnCapitalStartWord and + // - FnCapitalStartSentence. + // Afterwards the words can be added into exception list if needed. + virtual void SaveCpltSttWord( ACFlags nFlag, sal_Int32 nPos, + const OUString& rExceptWord, sal_Unicode cChar ) override; + virtual LanguageType GetLanguage( sal_Int32 nPos ) const override; +}; + +class SwAutoCorrExceptWord +{ + OUString m_sWord; + ACFlags m_nFlags; + sal_uLong m_nNode; + sal_Int32 m_nContent; + sal_Unicode m_cChar; + LanguageType m_eLanguage; + bool m_bDeleted; + +public: + SwAutoCorrExceptWord(ACFlags nAFlags, sal_uLong nNd, sal_Int32 nContent, + const OUString& rWord, sal_Unicode cChr, + LanguageType eLang) + : m_sWord(rWord), m_nFlags(nAFlags), m_nNode(nNd), m_nContent(nContent), + m_cChar(cChr), m_eLanguage(eLang), m_bDeleted(false) + {} + + bool IsDeleted() const { return m_bDeleted; } + void CheckChar(const SwPosition& rPos, sal_Unicode cChar); + bool CheckDelChar(const SwPosition& rPos); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/anchoredobjectposition.hxx b/sw/source/core/inc/anchoredobjectposition.hxx new file mode 100644 index 000000000..b2afe2c47 --- /dev/null +++ b/sw/source/core/inc/anchoredobjectposition.hxx @@ -0,0 +1,447 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ANCHOREDOBJECTPOSITION_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ANCHOREDOBJECTPOSITION_HXX + +#include <swtypes.hxx> +#include "frame.hxx" + +class SdrObject; +class SwFlyFrame; +class SwContact; +class SwFrameFormat; +class SwRect; +class SvxLRSpaceItem; +class SvxULSpaceItem; +class SwFormatHoriOrient; +class SwAnchoredObject; + +namespace objectpositioning +{ + class SwEnvironmentOfAnchoredObject; + + class SwAnchoredObjectPosition + { + private: + // object to be positioned + SdrObject& mrDrawObj; + + // does the object represents a Writer fly frame + bool mbIsObjFly; + // #i26791# - anchored object the object belongs to; + SwAnchoredObject* mpAnchoredObj; + // frame the object is anchored at + SwFrame* mpAnchorFrame; + // contact object + SwContact* mpContact; + // frame format + const SwFrameFormat* mpFrameFormat; + // #i62875# + bool mbFollowTextFlow; + // #i62875# + // for compatibility option <DoNotCaptureDrawObjsOnPage> + bool mbDoNotCaptureAnchoredObj; + + /** determine information about object + + member <mbIsObjFly>, <mpAnchoredObj>, <mpAnchorFrame>, <mpContact> + and <mpFrameFormat> are set + */ + void GetInfoAboutObj(); + + // #i62875# + // --> OD 2009-09-01 #mongolianlayout# - add parameter <bVertL2R> + SwTwips ImplAdjustVertRelPos( const SwTwips nTopOfAnch, + const bool bVert, + const bool bVertL2R, + const SwFrame& rPageAlignLayFrame, + const SwTwips nProposedRelPosY, + const bool bFollowTextFlow, + const bool bCheckBottom ) const; + SwTwips ImplAdjustHoriRelPos( const SwFrame& _rPageAlignLayFrame, + const SwTwips _nProposedRelPosX ) const; + + protected: + SwAnchoredObjectPosition( SdrObject& _rDrawObj ); + virtual ~SwAnchoredObjectPosition(); + + // accessors for object and its corresponding data/information + SdrObject& GetObject() const + { + return mrDrawObj; + } + bool IsObjFly() const + { + return mbIsObjFly; + } + SwAnchoredObject& GetAnchoredObj() const + { + return *mpAnchoredObj; + } + SwFrame& GetAnchorFrame() const + { + return *mpAnchorFrame; + } + const SwFrameFormat& GetFrameFormat() const + { + return *mpFrameFormat; + } + // #i62875# + bool DoesObjFollowsTextFlow() const + { + return mbFollowTextFlow; + } + + // virtual methods providing data for to character anchored objects. + virtual bool IsAnchoredToChar() const; + virtual const SwFrame* ToCharOrientFrame() const; + virtual const SwRect* ToCharRect() const; + // #i22341# + virtual SwTwips ToCharTopOfLine() const; + + /** helper method to determine top of a frame for the vertical object + positioning + + #i11860# + */ + SwTwips GetTopForObjPos( const SwFrame& _rFrame, + const SwRectFn& _fnRect, + const bool _bVert ) const; + + void GetVertAlignmentValues( const SwFrame& _rVertOrientFrame, + const SwFrame& _rPageAlignLayFrame, + const sal_Int16 _eRelOrient, + SwTwips& _orAlignAreaHeight, + SwTwips& _orAlignAreaOffset ) const; + + // #i26791# - add output parameter <_roVertOffsetToFrameAnchorPos> + SwTwips GetVertRelPos( const SwFrame& _rVertOrientFrame, + const SwFrame& _rPageAlignLayFrame, + const sal_Int16 _eVertOrient, + const sal_Int16 _eRelOrient, + const SwTwips _nVertPos, + const SvxLRSpaceItem& _rLRSpacing, + const SvxULSpaceItem& _rULSpacing, + SwTwips& _roVertOffsetToFrameAnchorPos ) const; + + /** adjust calculated vertical in order to keep object inside + 'page' alignment layout frame. + + #i31805# - add parameter <_bCheckBottom> + #i26945# - add parameter <_bFollowTextFlow> + #i62875# - made inline, intrinsic actions moved + to private method <ImplAdjustVertRelPos>, which is only + called, if <mbDoNotCaptureAnchoredObj> not set. + OD 2009-09-01 #mongolianlayout# - add parameter <bVertL2R> + + @param nTopOfAnch + input parameter - 'vertical' position, at which the relative + position of the object is calculated from. + + @param bVert + input parameter - boolean, indicating, if object is in vertical + layout. + + @param bVertL2R + input parameter - boolean, indicating, if object is in mongolian + layout (vertical left-to-right layout). + + @param rPageAlignLayFrame + input parameter - layout frame, which determines the 'page area' + the object has to be vertical positioned in. + + @param nProposedRelPosY + input parameter - proposed relative vertical position, which + will be adjusted. + + @param bFollowTextFlow + input parameter - value of attribute 'Follow text flow' of the + anchored object. + + @param bCheckBottom + input parameter - boolean indicating, if bottom of anchored + object has to be checked and thus, (if needed) the proposed + relative position has to be adjusted. default value <true> + */ + SwTwips AdjustVertRelPos( const SwTwips nTopOfAnch, + const bool bVert, + const bool bVertL2R, + const SwFrame& rPageAlignLayFrame, + const SwTwips nProposedRelPosY, + const bool bFollowTextFlow, + const bool bCheckBottom = true ) const + { + return !mbDoNotCaptureAnchoredObj + ? ImplAdjustVertRelPos( nTopOfAnch, bVert, bVertL2R, + rPageAlignLayFrame, + nProposedRelPosY, + bFollowTextFlow, + bCheckBottom ) + : nProposedRelPosY; + } + + /** calculate relative horizontal position + + #i26791# - add output parameter + <_roHoriOffsetToFrameAnchorPos> + + @param _rHoriOrientFrame + input parameter - frame the horizontal position of the object + is oriented at. + + @param _rEnvOfObj + input parameter - object instance to retrieve environment + information about the object + + @param _rHoriOrient + input parameter - horizontal positioning and alignment, for which + the relative position is calculated. + + @param _rLRSpacing + input parameter - left and right spacing of the object to the text + + @param _rULSpacing + input parameter - upper and lower spacing of the object to the text + + @param _bObjWrapThrough + input parameter - boolean indicating, if object has wrap mode + 'wrap through'. + + @param _nRelPosY + input parameter - relative vertical position + + @param _roHoriOffsetToFrameAnchorPos + output parameter - 'horizontal' offset to frame anchor position + according to the alignment + + @return relative horizontal position in SwTwips + */ + SwTwips CalcRelPosX( const SwFrame& _rHoriOrientFrame, + const SwEnvironmentOfAnchoredObject& _rEnvOfObj, + const SwFormatHoriOrient& _rHoriOrient, + const SvxLRSpaceItem& _rLRSpacing, + const SvxULSpaceItem& _rULSpacing, + const bool _bObjWrapThrough, + const SwTwips _nRelPosY, + SwTwips& _roHoriOffsetToFrameAnchorPos + ) const; + + /** adjust calculated horizontal in order to keep object inside + 'page' alignment layout frame for object type position TO_CNTNT + + #i62875# - made inline, intrinsic actions moved + to private method <ImplAdjustHoriRelPos>, which is only + called, if <mbDoNotCaptureAnchoredObj> not set. + + @param _rPageAlignLayFrame + input parameter - layout frame, which determines the 'page area' + the object has to be horizontal positioned in. + + @param _nProposedRelPosX + input parameter - proposed relative horizontal position, which + will be adjusted. + + @return adjusted relative horizontal position in SwTwips. + */ + SwTwips AdjustHoriRelPos( const SwFrame& _rPageAlignLayFrame, + const SwTwips _nProposedRelPosX ) const + { + return !mbDoNotCaptureAnchoredObj + ? ImplAdjustHoriRelPos( _rPageAlignLayFrame, _nProposedRelPosX ) + : _nProposedRelPosX; + } + + /** toggle given horizontal orientation and relative alignment + + @param _bToggleLeftRight + input parameter - boolean indicating, if horizontal orientation + and relative alignment has to be toggled. + + @param _ioeHoriOrient + input/output parameter - horizontal orientation, that is toggled, + if needed. + + @param _iopeRelOrient + optional input/output parameter (default value NULL) + - if set, relative alignment, that is toggled, if needed. + */ + static void ToggleHoriOrientAndAlign( const bool _bToggleLeftRight, + sal_Int16& _ioeHoriOrient, + sal_Int16& _iopeRelOrient + ); + + /** determine alignment values for horizontal position of object + + @param _rHoriOrientFrame + input parameter - frame the horizontal position of the object + is oriented at. + + @param _rPageAlignLayFrame + input parameter - layout frame, which determines the 'page area' + the object has to be horizontal positioned in. + + @param _eRelOrient + input parameter - horizontal relative alignment, for which + the relative position is calculated. + + @param _bToggleLeftRight + input parameter - boolean indicating, if left/right alignments + have to be toggled. + + @param _bObjWrapThrough + input parameter - boolean indicating, if object has wrap mode + 'wrap through'. + important note: value is only relevant, if _rHoriOrientFrame is + a text frame. + + @param _orAlignAreaWidth + output parameter - width in SwTwips of the area the horizontal + position is aligned to. + + @param _orAlignAreaOffset + output parameter - offset in SwTwips of the area the horizontal + position is aligned to. offset is given to the 'left' of the + anchor position. + + @param _obAlignedRelToPage + output parameter - boolean indicating, that object is aligned + to 'page area'. + */ + void GetHoriAlignmentValues( const SwFrame& _rHoriOrientFrame, + const SwFrame& _rPageAlignLayFrame, + const sal_Int16 _eRelOrient, + const bool _bObjWrapThrough, + SwTwips& _orAlignAreaWidth, + SwTwips& _orAlignAreaOffset, + bool& _obAlignedRelToPage ) const; + + /** adjust calculated horizontal position in order to draw object + aside other objects with same positioning + + @param _rHoriOrientFrame + input parameter - frame the horizontal position of the object + is oriented at. + + @param _nProposedRelPosX + input parameter - proposed relative horizontal position, which + will be adjusted. + + @param _nRelPosY + input parameter - relative vertical position + + @param _eHoriOrient + input parameter - horizontal position of object + + @param _eRelOrient + input parameter - alignment of object + + @param _rLRSpacing + input parameter - left and right spacing of the object to the text + + @param _rULSpacing + input parameter - upper and lower spacing of the object to the text + + @param _bEvenPage + input parameter - boolean indicating, if object is on an even page. + + @return adjusted relative horizontal position in SwTwips + */ + SwTwips AdjustHoriRelPosForDrawAside( const SwFrame& _rHoriOrientFrame, + const SwTwips _nProposedRelPosX, + const SwTwips _nRelPosY, + const sal_Int16 _eHoriOrient, + const sal_Int16 _eRelOrient, + const SvxLRSpaceItem& _rLRSpacing, + const SvxULSpaceItem& _rULSpacing, + const bool _bEvenPage + ) const; + + /** determine, if object has to draw aside given fly frame + + method used by <AdjustHoriRelPosForDrawAside(..)> + + @param _pFly + input parameter - fly frame the draw aside check is done for. + + @param _rObjRect + input parameter - proposed object rectangle + + @param _pObjContext + input parameter - context of the object + + @param _nObjIndex + input parameter - index of the anchor frame of the object + + @param _bEvenPage + input parameter - boolean indicating, if object is on an even page. + + @param _eHoriOrient + input parameter - horizontal position of object + + @param _eRelOrient + input parameter - alignment of object + + @return boolean indicating, if object has to be drawn aside + given fly frame. + */ + bool DrawAsideFly( const SwFlyFrame* _pFly, + const SwRect& _rObjRect, + const SwFrame* _pObjContext, + const sal_uLong _nObjIndex, + const bool _bEvenPage, + const sal_Int16 _eHoriOrient, + const sal_Int16 _eRelOrient + ) const; + + /** determine, if object has to draw aside another object + + the different alignments of the objects determines, if one has + to draw aside another one. Thus, the given alignment are checked + against each other, which one has to be drawn aside the other one. + depending on parameter _bLeft check is done for left or right + positioning. + method used by <DrawAsideFly(..)> + + @param _eRelOrient1 + input parameter - alignment 1 + + @param _eRelOrient2 + input parameter - alignment 2 + + @param _bLeft + input parameter - boolean indicating, if check is done for left + or for right positioning. + + @return boolean indicating, if an object with an alignment + <_eRelOrient1> has to be drawn aside an object with an + alignment <_eRelOrient2> + */ + static bool Minor_( sal_Int16 _eRelOrient1, + sal_Int16 _eRelOrient2, + bool _bLeft ); + + public: + virtual void CalcPosition() = 0; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/annotationmark.hxx b/sw/source/core/inc/annotationmark.hxx new file mode 100644 index 000000000..9fe8478e2 --- /dev/null +++ b/sw/source/core/inc/annotationmark.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ANNOTATIONMARK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ANNOTATIONMARK_HXX + +#include "bookmrk.hxx" +#include <rtl/ustring.hxx> + +class SwFormatField; + +namespace sw::mark +{ + class AnnotationMark : public MarkBase + { + public: + AnnotationMark( + const SwPaM& rPaM, + const OUString& rName ); + + virtual ~AnnotationMark() override; + + virtual void InitDoc(SwDoc* const io_Doc, sw::mark::InsertMode eMode, SwPosition const* pSepPos) override; + + const SwFormatField* GetAnnotationFormatField() const; + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/ascharanchoredobjectposition.hxx b/sw/source/core/inc/ascharanchoredobjectposition.hxx new file mode 100644 index 000000000..6bafb73d1 --- /dev/null +++ b/sw/source/core/inc/ascharanchoredobjectposition.hxx @@ -0,0 +1,160 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ASCHARANCHOREDOBJECTPOSITION_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ASCHARANCHOREDOBJECTPOSITION_HXX + +#include "anchoredobjectposition.hxx" +#include <swtypes.hxx> +#include <swrect.hxx> +#include <o3tl/typed_flags_set.hxx> + +class SwTextFrame; +class SwFormatVertOrient; + +// flags for positioning algorithm of as-character-anchored objects +enum class AsCharFlags { + None = 0x00, + Quick = 0x01, + UlSpace = 0x02, + Init = 0x04, + Rotate = 0x08, + Reverse = 0x10, + Bidi = 0x20, +}; +namespace o3tl { + template<> struct typed_flags<AsCharFlags> : is_typed_flags<AsCharFlags, 0x3f> {}; +}; + +namespace sw +{ + // TODO: merge/migrate this to com::sun::star::VertOrientation instead of duplicating? + enum class LineAlign + { + NONE, + TOP, + CENTER, + BOTTOM + }; +}; +namespace objectpositioning +{ + class SwAsCharAnchoredObjectPosition : public SwAnchoredObjectPosition + { + private: + // data to calculate object position + // Proposed anchor position, starting point for the calculation + // of the object position. + const Point& mrProposedAnchorPos; + // flags that influences the calculation of the anchor position + // AsCharFlags::Quick : quick formatting - calculated position not set at object + // AsCharFlags::UlSpace : consider upper/lower spacing - adjustment of anchor position + // AsCharFlags::Init : initial calculation + // AsCharFlags::Rotate : object is rotated by 90 degrees + // AsCharFlags::Reverse : object is reversed (rotated by 270 degrees) + // AsCharFlags::Bidi : object belongs to a BIDI-multi-portion + const AsCharFlags mnFlags; + // needed line values for the different alignments. + const SwTwips mnLineAscent; + const SwTwips mnLineDescent; + const SwTwips mnLineAscentInclObjs; + const SwTwips mnLineDescentInclObjs; + + // calculated data for object position + Point maAnchorPos; + SwTwips mnRelPos; + SwRect maObjBoundRect; + // line alignment relative to line height + sw::LineAlign mnLineAlignment; + + // method to cast <SwAnchoredObjectPosition::GetAnchorFrame()> + const SwTextFrame& GetAnchorTextFrame() const; + + /** determine the relative position to base line for object position + + @param _ObjBoundHeight + height including corresponding spacing of the object, for which + the Y-position has to be calculated. + + @param _rVert + given vertical positioning and alignment + + @return relative position to the base line + */ + SwTwips GetRelPosToBase( const SwTwips _nObjBoundHeight, + const SwFormatVertOrient& _rVert ); + + public: + /** constructor; provided object to be positioned and needed data + for calculation of the object position + + @param _rDrawObj + input parameter - object, that is be positioned. + + @param _rProposedAnchorPos + proposed anchor position; starting point for the calculation + of the anchor position + + @param _nFlags + flags that influences the calculation of the anchor position + AsCharFlags::Quick : quick formatting - calculated position not set at object + AsCharFlags::UlSpace : consider upper/lower spacing - adjustment of anchor position + AsCharFlags::Init : initial calculation + AsCharFlags::Rotate : object is rotated by 90 degrees + AsCharFlags::Reverse : object is reversed (rotated by 270 degrees) + AsCharFlags::Bidi : object belongs to a BIDI-multi-portion + + @param _nLineAscent, _nLineDescent, _nLineAscentInclObjs, + _nLineDescentInclObjs - needed line values for the different + alignments. + */ + SwAsCharAnchoredObjectPosition( SdrObject& _rDrawObj, + const Point& _rProposedAnchorPos, + const AsCharFlags _nFlags, + const SwTwips _nLineAscent, + const SwTwips _nLineDescent, + const SwTwips _nLineAscentInclObjs, + const SwTwips _nLineDescentInclObjs ); + virtual ~SwAsCharAnchoredObjectPosition() override; + + /** calculate position for object position + + members <maAnchorPos>, <mnRelPos>, <maObjBoundRect> and + <mnLineAlignment> are calculated. + calculated position is set at the given object. + */ + virtual void CalcPosition() override; + + // calculated anchored position for object position type AS_CHAR + const Point& GetAnchorPos() const { return maAnchorPos;} + + // calculated relative position to base line for object position type AS_CHAR + SwTwips GetRelPosY() const { return mnRelPos;} + + // determined object rectangle including spacing for object position type AS_CHAR + const SwRect& GetObjBoundRectInclSpacing() const { return maObjBoundRect;} + + // determined line alignment relative to line height + sw::LineAlign GetLineAlignment() const { return mnLineAlignment;} + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/attrhint.hxx b/sw/source/core/inc/attrhint.hxx new file mode 100644 index 000000000..8a3e86c5f --- /dev/null +++ b/sw/source/core/inc/attrhint.hxx @@ -0,0 +1,31 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ATTRHINT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ATTRHINT_HXX + +#include <svl/hint.hxx> + +class SwAttrHint : public SfxHint +{ +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/bodyfrm.hxx b/sw/source/core/inc/bodyfrm.hxx new file mode 100644 index 000000000..f6b046e9d --- /dev/null +++ b/sw/source/core/inc/bodyfrm.hxx @@ -0,0 +1,41 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_BODYFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_BODYFRM_HXX + +#include "layfrm.hxx" + +class SwBorderAttrs; + +/// Container of body content (i.e. not header or footer). Typical parent is an +/// SwPageFrame, typical lower is an SwTextFrame. +class SAL_DLLPUBLIC_RTTI SwBodyFrame: public SwLayoutFrame +{ +protected: + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + +public: + SwBodyFrame( SwFrameFormat*, SwFrame* ); + + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/bookmrk.hxx b/sw/source/core/inc/bookmrk.hxx new file mode 100644 index 000000000..27e4171be --- /dev/null +++ b/sw/source/core/inc/bookmrk.hxx @@ -0,0 +1,356 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_BOOKMRK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_BOOKMRK_HXX + +#include <cppuhelper/weakref.hxx> +#include <sfx2/Metadatable.hxx> +#include <vcl/keycod.hxx> +#include <memory> +#include <rtl/ustring.hxx> +#include <osl/diagnose.h> +#include <tools/ref.hxx> +#include <IMark.hxx> +#include <swrect.hxx> +#include "FormFieldButton.hxx" + +namespace com { + namespace sun { + namespace star { + namespace text { + class XTextContent; + } + } + } +} + +class SwDoc; +class SwEditWin; +class SwServerObject; +class SvNumberFormatter; + +namespace sw { + namespace mark { + class MarkBase + : virtual public IMark + { + public: + //getters + virtual SwPosition& GetMarkPos() const override + { return *m_pPos1; } + virtual const OUString& GetName() const override + { return m_aName; } + virtual SwPosition& GetOtherMarkPos() const override + { + OSL_PRECOND(IsExpanded(), "<SwPosition::GetOtherMarkPos(..)> - I have no other Pos set." ); + return *m_pPos2; + } + virtual SwPosition& GetMarkStart() const override + { + if( !IsExpanded() ) return GetMarkPos( ); + if ( GetMarkPos( ) < GetOtherMarkPos( ) ) + return GetMarkPos(); + else + return GetOtherMarkPos( ); + } + virtual SwPosition& GetMarkEnd() const override + { + if( !IsExpanded() ) return GetMarkPos(); + if ( GetMarkPos( ) >= GetOtherMarkPos( ) ) + return GetMarkPos( ); + else + return GetOtherMarkPos( ); + } + + virtual bool IsCoveringPosition(const SwPosition& rPos) const override; + virtual bool IsExpanded() const override + { return static_cast< bool >(m_pPos2); } + + void SetName(const OUString& rName) + { m_aName = rName; } + virtual void SetMarkPos(const SwPosition& rNewPos); + virtual void SetOtherMarkPos(const SwPosition& rNewPos); + virtual void ClearOtherMarkPos() + { m_pPos2.reset(); } + + virtual auto InvalidateFrames() -> void; + + virtual OUString ToString( ) const override; + virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override; + + void Swap() + { + if(m_pPos2) + m_pPos1.swap(m_pPos2); + } + + virtual void InitDoc(SwDoc* const, sw::mark::InsertMode, SwPosition const*) + { + } + + virtual ~MarkBase() override; + + const css::uno::WeakReference< css::text::XTextContent> & GetXBookmark() const + { return m_wXBookmark; } + void SetXBookmark(css::uno::Reference< css::text::XTextContent> const& xBkmk) + { m_wXBookmark = xBkmk; } + + protected: + // SwClient + virtual void Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) override; + + MarkBase(const SwPaM& rPaM, const OUString& rName); + std::unique_ptr<SwPosition> m_pPos1; + std::unique_ptr<SwPosition> m_pPos2; + OUString m_aName; + static OUString GenerateNewName(const OUString& rPrefix); + + css::uno::WeakReference< css::text::XTextContent> m_wXBookmark; + }; + + class NavigatorReminder + : public MarkBase + { + public: + NavigatorReminder(const SwPaM& rPaM); + }; + + class UnoMark + : public MarkBase + { + public: + UnoMark(const SwPaM& rPaM); + }; + + class DdeBookmark + : public MarkBase + { + public: + DdeBookmark(const SwPaM& rPaM); + + const SwServerObject* GetRefObject() const { return m_aRefObj.get(); } + SwServerObject* GetRefObject() { return m_aRefObj.get(); } + + bool IsServer() const { return m_aRefObj.is(); } + + void SetRefObject( SwServerObject* pObj ); + + virtual void DeregisterFromDoc(SwDoc* const pDoc); + virtual ~DdeBookmark() override; + + private: + tools::SvRef<SwServerObject> m_aRefObj; + }; + + class Bookmark + : virtual public IBookmark + , public DdeBookmark + , public ::sfx2::Metadatable + { + public: + Bookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName); + virtual void InitDoc(SwDoc* const io_Doc, sw::mark::InsertMode eMode, SwPosition const* pSepPos) override; + + virtual void DeregisterFromDoc(SwDoc* const io_pDoc) override; + + virtual auto InvalidateFrames() -> void override; + + virtual const OUString& GetShortName() const override + { return m_sShortName; } + virtual const vcl::KeyCode& GetKeyCode() const override + { return m_aCode; } + virtual void SetShortName(const OUString& rShortName) override + { m_sShortName = rShortName; } + virtual void SetKeyCode(const vcl::KeyCode& rCode) override + { m_aCode = rCode; } + virtual bool IsHidden() const override + { return m_bHidden; } + virtual const OUString& GetHideCondition() const override + { return m_sHideCondition; } + virtual void Hide(bool rHide) override; + virtual void SetHideCondition(const OUString& rHideCondition) override; + + // ::sfx2::Metadatable + virtual ::sfx2::IXmlIdRegistry& GetRegistry() override; + virtual bool IsInClipboard() const override; + virtual bool IsInUndo() const override; + virtual bool IsInContent() const override; + virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override; + + private: + vcl::KeyCode m_aCode; + OUString m_sShortName; + bool m_bHidden; + OUString m_sHideCondition; + }; + + class Fieldmark + : virtual public IFieldmark + , public MarkBase + { + public: + Fieldmark(const SwPaM& rPaM); + + virtual OUString GetFieldname() const override + { return m_aFieldname; } + virtual OUString GetFieldHelptext() const override + { return m_aFieldHelptext; } + + virtual IFieldmark::parameter_map_t* GetParameters() override + { return &m_vParams; } + + virtual const IFieldmark::parameter_map_t* GetParameters() const override + { return &m_vParams; } + + virtual void SetFieldname(const OUString& aFieldname) override + { m_aFieldname = aFieldname; } + virtual void SetFieldHelptext(const OUString& aFieldHelptext) override + { m_aFieldHelptext = aFieldHelptext; } + + virtual void ReleaseDoc(SwDoc* const) = 0; + + void SetMarkStartPos( const SwPosition& rNewStartPos ); + void SetMarkEndPos( const SwPosition& rNewEndPos ); + + virtual void Invalidate() override; + virtual OUString ToString() const override; + virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override; + + private: + OUString m_aFieldname; + OUString m_aFieldHelptext; + IFieldmark::parameter_map_t m_vParams; + }; + + class TextFieldmark + : public Fieldmark + { + public: + TextFieldmark(const SwPaM& rPaM, const OUString& rName); + virtual void InitDoc(SwDoc* const io_pDoc, sw::mark::InsertMode eMode, SwPosition const* pSepPos) override; + virtual void ReleaseDoc(SwDoc* const pDoc) override; + }; + + // Non text fieldmarks have no content between the start and end marks. + class NonTextFieldmark + : public Fieldmark + { + public: + NonTextFieldmark(const SwPaM& rPaM); + virtual void InitDoc(SwDoc* const io_pDoc, sw::mark::InsertMode eMode, SwPosition const* pSepPos) override; + virtual void ReleaseDoc(SwDoc* const pDoc) override; + }; + + /// Fieldmark representing a checkbox form field. + class CheckboxFieldmark + : virtual public ICheckboxFieldmark + , public NonTextFieldmark + { + public: + CheckboxFieldmark(const SwPaM& rPaM); + bool IsChecked() const override; + void SetChecked(bool checked) override; + }; + + /// Fieldmark with a drop down button (e.g. this button opens the date picker for a date field) + class FieldmarkWithDropDownButton + : public NonTextFieldmark + { + public: + FieldmarkWithDropDownButton(const SwPaM& rPaM); + virtual ~FieldmarkWithDropDownButton() override; + + virtual void ShowButton(SwEditWin* pEditWin) = 0; + virtual void HideButton(); + virtual void RemoveButton(); + + protected: + VclPtr<FormFieldButton> m_pButton; + }; + + /// Fieldmark representing a drop-down form field. + class DropDownFieldmark + : public FieldmarkWithDropDownButton + { + public: + DropDownFieldmark(const SwPaM& rPaM); + virtual ~DropDownFieldmark() override; + + virtual void ShowButton(SwEditWin* pEditWin) override; + virtual void HideButton() override; + virtual void RemoveButton() override; + + // This method should be called only by the portion so we can now the portion's painting area + void SetPortionPaintArea(const SwRect& rPortionPaintArea); + + void SendLOKMessage(const OString& sAction); + + private: + SwRect m_aPortionPaintArea; + OString m_sLastSentLOKMsg; + }; + + /// Fieldmark representing a date form field. + class DateFieldmark + : virtual public IDateFieldmark + , public FieldmarkWithDropDownButton + { + public: + DateFieldmark(const SwPaM& rPaM); + virtual ~DateFieldmark() override; + + virtual void InitDoc(SwDoc* const io_pDoc, sw::mark::InsertMode eMode, SwPosition const* pSepPos) override; + virtual void ReleaseDoc(SwDoc* const pDoc) override; + + virtual void ShowButton(SwEditWin* pEditWin) override; + + void SetPortionPaintAreaStart(const SwRect& rPortionPaintArea); + void SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea); + + virtual OUString GetContent() const override; + virtual void ReplaceContent(const OUString& sNewContent) override; + + virtual std::pair<bool, double> GetCurrentDate() const override; + virtual void SetCurrentDate(double fDate) override; + virtual OUString GetDateInStandardDateFormat(double fDate) const override; + + private: + OUString GetDateInCurrentDateFormat(double fDate) const; + std::pair<bool, double> ParseCurrentDateParam() const; + void InvalidateCurrentDateParam(); + + SvNumberFormatter* m_pNumberFormatter; + sw::DocumentContentOperationsManager* m_pDocumentContentOperationsManager; + SwRect m_aPaintAreaStart; + SwRect m_aPaintAreaEnd; + }; + + /// return position of the CH_TXT_ATR_FIELDSEP for rMark + SwPosition FindFieldSep(IFieldmark const& rMark); + + /// check if rPaM is valid range of new fieldmark + bool IsFieldmarkOverlap(SwPaM const& rPaM); + } +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/cellfrm.hxx b/sw/source/core/inc/cellfrm.hxx new file mode 100644 index 000000000..e98654ae4 --- /dev/null +++ b/sw/source/core/inc/cellfrm.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_CELLFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_CELLFRM_HXX + +#include "layfrm.hxx" + +class SwTableBox; +struct SwCursorMoveState; +class SwBorderAttrs; + +/// SwCellFrame is one table cell in the document layout. +class SwCellFrame: public SwLayoutFrame +{ + const SwTableBox* m_pTabBox; + + virtual void DestroyImpl() override; + virtual ~SwCellFrame() override; + +protected: + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + +public: + SwCellFrame( const SwTableBox &, SwFrame*, bool bInsertContent ); + + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override; + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + virtual void CheckDirection( bool bVert ) override; + + // #i103961# + virtual void Cut() override; + + const SwTableBox *GetTabBox() const { return m_pTabBox; } + + // used for breaking table rows: + SwCellFrame* GetFollowCell() const; + SwCellFrame* GetPreviousCell() const; + + virtual bool IsLeaveUpperAllowed() const override; + virtual bool IsCoveredCell() const override; + + // used for rowspan stuff: + const SwCellFrame& FindStartEndOfRowSpanCell( bool bStart ) const; + long GetLayoutRowSpan() const; + + void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/cntfrm.hxx b/sw/source/core/inc/cntfrm.hxx new file mode 100644 index 000000000..2daec8fdd --- /dev/null +++ b/sw/source/core/inc/cntfrm.hxx @@ -0,0 +1,125 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_CNTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_CNTFRM_HXX + +#include "frame.hxx" +#include "flowfrm.hxx" +#include <cshtyp.hxx> + +class SwLayoutFrame; +class SwContentNode; +class SwBorderAttrs; +class SwAttrSetChg; +class SwTextFrame; + +// implemented in cntfrm.cxx, used in cntfrm.cxx and crsrsh.cxx +extern bool GetFrameInPage( const SwContentFrame*, SwWhichPage, SwPosPage, SwPaM* ); + +class SAL_DLLPUBLIC_RTTI SwContentFrame: public SwFrame, public SwFlowFrame +{ + friend void MakeNxt( SwFrame *pFrame, SwFrame *pNxt ); // calls MakePrtArea + + // parameter <bObjsInNewUpper> indicates that objects exist in remaining + // area of new upper + bool WouldFit_( SwTwips nSpace, + SwLayoutFrame *pNewUpper, + bool bTstMove, + const bool bObjsInNewUpper ); + + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + + void UpdateAttr_( const SfxPoolItem*, const SfxPoolItem*, sal_uInt8 &, + SwAttrSetChg *pa = nullptr, SwAttrSetChg *pb = nullptr ); + + virtual bool ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool& ) override; + + const SwContentFrame* ImplGetNextContentFrame( bool bFwd ) const; + +protected: + void MakePrtArea( const SwBorderAttrs & ); + + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + virtual SwTwips ShrinkFrame( SwTwips, bool bTst = false, bool bInfo = false ) override; + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) override; + + SwContentFrame( SwContentNode * const, SwFrame* ); + + virtual void DestroyImpl() override; + virtual ~SwContentFrame() override; + +public: + + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; + + inline const SwContentFrame *GetFollow() const; + inline SwContentFrame *GetFollow(); + SwTextFrame* FindMaster() const; + + // layout dependent cursor travelling + virtual bool LeftMargin(SwPaM *) const = 0; + virtual bool RightMargin(SwPaM *, bool bAPI = false) const = 0; + virtual bool UnitUp( SwPaM *, const SwTwips nOffset, + bool bSetInReadOnly ) const; + virtual bool UnitDown( SwPaM *, const SwTwips nOffset, + bool bSetInReadOnly ) const; + + // nMaxHeight is the required height + // bSplit indicates that the paragraph has to be split + // bTst indicates that we are currently doing a test formatting + virtual bool WouldFit( SwTwips &nMaxHeight, bool &bSplit, bool bTst ); + + bool MoveFootnoteCntFwd( bool, SwFootnoteBossFrame* ); // called by MoveFwd if content + + inline SwContentFrame* GetNextContentFrame() const; + inline SwContentFrame* GetPrevContentFrame() const; + static bool CalcLowers(SwLayoutFrame & rLay, SwLayoutFrame const& rDontLeave, + long nBottom, bool bSkipRowSpanCells); +}; + +inline SwContentFrame* SwContentFrame::GetNextContentFrame() const +{ + if ( GetNext() && GetNext()->IsContentFrame() ) + return const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(GetNext())); + else + return const_cast<SwContentFrame*>(ImplGetNextContentFrame( true )); +} + +inline SwContentFrame* SwContentFrame::GetPrevContentFrame() const +{ + if ( GetPrev() && GetPrev()->IsContentFrame() ) + return const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(GetPrev())); + else + return const_cast<SwContentFrame*>(ImplGetNextContentFrame( false )); +} + +inline const SwContentFrame *SwContentFrame::GetFollow() const +{ + return static_cast<const SwContentFrame*>(SwFlowFrame::GetFollow()); +} +inline SwContentFrame *SwContentFrame::GetFollow() +{ + return static_cast<SwContentFrame*>(SwFlowFrame::GetFollow()); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/colfrm.hxx b/sw/source/core/inc/colfrm.hxx new file mode 100644 index 000000000..7c78661e7 --- /dev/null +++ b/sw/source/core/inc/colfrm.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_COLFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_COLFRM_HXX + +#include "ftnboss.hxx" + +class SwColumnFrame: public SwFootnoteBossFrame +{ +private: + virtual void DestroyImpl() override; + virtual ~SwColumnFrame() override; + +public: + SwColumnFrame( SwFrameFormat*, SwFrame* ); + + virtual void PaintBreak() const override; + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_COLFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/crossrefbookmark.hxx b/sw/source/core/inc/crossrefbookmark.hxx new file mode 100644 index 000000000..06116bdda --- /dev/null +++ b/sw/source/core/inc/crossrefbookmark.hxx @@ -0,0 +1,86 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_CROSSREFBOOKMARK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_CROSSREFBOOKMARK_HXX + +#include "bookmrk.hxx" +#include <rtl/ustring.hxx> +#include <vcl/keycod.hxx> + +namespace sw { + namespace mark { + class CrossRefBookmark + : public Bookmark + { + public: + CrossRefBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName, + const OUString& rPrefix); + + // getters + virtual SwPosition& GetOtherMarkPos() const override; + virtual SwPosition& GetMarkStart() const override + { return *m_pPos1; } + virtual SwPosition& GetMarkEnd() const override + { return *m_pPos1; } + virtual bool IsExpanded() const override + { return false; } + + virtual void SetMarkPos(const SwPosition& rNewPos) override; + virtual void SetOtherMarkPos(const SwPosition&) override + { + assert(false && + "<CrossRefBookmark::SetOtherMarkPos(..)>" + " - misusage of CrossRefBookmark: other bookmark position isn't allowed to be set." ); + } + virtual void ClearOtherMarkPos() override + { + assert(false && + "<SwCrossRefBookmark::ClearOtherMarkPos(..)>" + " - misusage of CrossRefBookmark: other bookmark position isn't allowed to be set or cleared." ); + } + }; + + class CrossRefHeadingBookmark + : public CrossRefBookmark + { + public: + CrossRefHeadingBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName); + static bool IsLegalName(const OUString& rName); + }; + + class CrossRefNumItemBookmark + : public CrossRefBookmark + { + public: + CrossRefNumItemBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName); + static bool IsLegalName(const OUString& rName); + }; + } +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/dbg_lay.hxx b/sw/source/core/inc/dbg_lay.hxx new file mode 100644 index 000000000..4f7348b3b --- /dev/null +++ b/sw/source/core/inc/dbg_lay.hxx @@ -0,0 +1,105 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DBG_LAY_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DBG_LAY_HXX + +#include <o3tl/typed_flags_set.hxx> +#include <memory> + +enum class PROT { + FileInit = 0x00000000, + Init = 0x00000001, + MakeAll = 0x00000002, + MoveFwd = 0x00000004, + MoveBack = 0x00000008, + Grow = 0x00000010, + Shrink = 0x00000020, + GrowTest = 0x00000040, + ShrinkTest = 0x00000080, + Size = 0x00000100, + PrintArea = 0x00000200, + AdjustN = 0x00000800, + Section = 0x00001000, + Cut = 0x00002000, + Paste = 0x00004000, + Leaf = 0x00008000, + TestFormat = 0x00010000, + FrmChanges = 0x00020000, +}; +namespace o3tl { + template<> struct typed_flags<PROT> : is_typed_flags<PROT, 0x0003fbff> {}; +} + +enum class DbgAction { + NONE, + Start, End, + CreateMaster, CreateFollow, + DelMaster, DelFollow, + Merge, + NextSect, PrevSect +}; + +#ifdef DBG_UTIL + +class SwImplProtocol; +class SwFrame; +class SwImplEnterLeave; + +class SwProtocol +{ + static PROT nRecord; + static SwImplProtocol* pImpl; + static bool Start() { return bool( PROT::Init & nRecord ); } + +public: + static PROT Record() { return nRecord; } + static void SetRecord( PROT nNew ) { nRecord = nNew; } + static bool Record( PROT nFunc ) { return bool(( nFunc | PROT::Init ) & nRecord); } + static void Record( const SwFrame* pFrame, PROT nFunction, DbgAction nAction, void* pParam ); + static void Init(); + static void Stop(); +}; + +class SwEnterLeave +{ + std::unique_ptr<SwImplEnterLeave> pImpl; +public: + SwEnterLeave( const SwFrame* pFrame, PROT nFunc, DbgAction nAct, void* pPar ); + ~SwEnterLeave(); +}; + +#define PROTOCOL( pFrame, nFunc, nAct, pPar ) { if( SwProtocol::Record( nFunc ) )\ + SwProtocol::Record( pFrame, nFunc, nAct, pPar ); } +#define PROTOCOL_INIT SwProtocol::Init(); +#define PROTOCOL_STOP SwProtocol::Stop(); +#define PROTOCOL_ENTER( pFrame, nFunc, nAct, pPar ) SwEnterLeave aEnter( pFrame, nFunc, nAct, pPar ); + +#else + +#define PROTOCOL( pFrame, nFunc, nAct, pPar ) +#define PROTOCOL_INIT +#define PROTOCOL_STOP +#define PROTOCOL_ENTER( pFrame, nFunc, nAct, pPar ) + +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/dflyobj.hxx b/sw/source/core/inc/dflyobj.hxx new file mode 100644 index 000000000..8da47dafd --- /dev/null +++ b/sw/source/core/inc/dflyobj.hxx @@ -0,0 +1,148 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DFLYOBJ_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DFLYOBJ_HXX + +#include <svx/svdovirt.hxx> +#include <svx/svdobj.hxx> + +namespace drawinglayer::geometry { class ViewInformation2D; } + +class SwFlyFrame; +class SwFrameFormat; + +const sal_uInt16 SwFlyDrawObjIdentifier = 0x0001; + +// DrawObjects for Flys +class SwFlyDrawObj : public SdrObject +{ +private: + virtual std::unique_ptr<sdr::properties::BaseProperties> CreateObjectSpecificProperties() override; + bool mbIsTextBox; + +protected: + // #i95264# SwFlyDrawObj needs an own VC since createViewIndependentPrimitive2DSequence() + // is called when RecalcBoundRect() is used + virtual std::unique_ptr<sdr::contact::ViewContact> CreateObjectSpecificViewContact() override; + + // protected destructor + virtual ~SwFlyDrawObj() override; + +public: + SwFlyDrawObj(SdrModel& rSdrModel); + + // for instantiation of this class while loading (via factory) + virtual SdrInventor GetObjInventor() const override; + virtual sal_uInt16 GetObjIdentifier() const override; + virtual bool IsTextBox() const override { return mbIsTextBox; } + void SetTextBox(bool bIsTextBox) { mbIsTextBox = bIsTextBox; } +}; + +// virtual objects for Flys +// Flys will always be shown with virtual objects. By doing that, they can be +// shown multiple times if needed (header/footer). +class SwVirtFlyDrawObj : public SdrVirtObj +{ +private: + SwFlyFrame *m_pFlyFrame; + + // RotGrfFlyFrame: Helper to access the rotation angle (in 10th degrees, left-handed) + // of a GraphicFrame + sal_uInt16 getPossibleRotationFromFraphicFrame(Size& rSize) const; + +protected: + // AW: Need own sdr::contact::ViewContact since AnchorPos from parent is + // not used but something own (top left of new SnapRect minus top left + // of original SnapRect) + virtual std::unique_ptr<sdr::contact::ViewContact> CreateObjectSpecificViewContact() override; + + // protected destructor + virtual ~SwVirtFlyDrawObj() override; + +public: + // for paints triggered form ExecutePrimitive + void wrap_DoPaintObject( + drawinglayer::geometry::ViewInformation2D const&) const; + + // for simple access to inner and outer bounds + basegfx::B2DRange getOuterBound() const; + basegfx::B2DRange getInnerBound() const; + + // RotGrfFlyFrame: Check if this is a SwGrfNode + bool ContainsSwGrfNode() const; + + SwVirtFlyDrawObj( + SdrModel& rSdrModel, + SdrObject& rNew, + SwFlyFrame* pFly); + + // override method of base class SdrVirtObj + virtual void TakeObjInfo( SdrObjTransformInfoRec& rInfo ) const override; + + // we treat the size calculation completely on ourself here + virtual const tools::Rectangle& GetCurrentBoundRect() const override; + virtual const tools::Rectangle& GetLastBoundRect() const override; + virtual long GetRotateAngle() const override; + virtual void RecalcBoundRect() override; + virtual void RecalcSnapRect() override; + virtual const tools::Rectangle& GetSnapRect() const override; + virtual void SetSnapRect(const tools::Rectangle& rRect) override; + virtual void NbcSetSnapRect(const tools::Rectangle& rRect) override; + virtual const tools::Rectangle& GetLogicRect() const override; + virtual void SetLogicRect(const tools::Rectangle& rRect) override; + virtual void NbcSetLogicRect(const tools::Rectangle& rRect) override; + virtual ::basegfx::B2DPolyPolygon TakeXorPoly() const override; + virtual void NbcMove (const Size& rSiz) override; + virtual void NbcResize(const Point& rRef, const Fraction& xFact, + const Fraction& yFact) override; + virtual void NbcCrop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) override; + virtual void Move (const Size& rSiz) override; + virtual void Resize(const Point& rRef, const Fraction& xFact, + const Fraction& yFact, bool bUnsetRelative = true) override; + virtual void Crop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) override; + virtual void addCropHandles(SdrHdlList& rTarget) const override; + virtual void Rotate(const Point& rRef, long nAngle, double sn, double cs) override; + + // FullDrag support + virtual SdrObjectUniquePtr getFullDragClone() const override; + + const SwFrameFormat *GetFormat() const; + SwFrameFormat *GetFormat(); + + // methods to get pointers for the Fly + SwFlyFrame* GetFlyFrame() { return m_pFlyFrame; } + const SwFlyFrame* GetFlyFrame() const { return m_pFlyFrame; } + + void SetRect() const; + + // if a URL is attached to a graphic than this is a macro object + virtual bool HasMacro() const override; + virtual SdrObject* CheckMacroHit (const SdrObjMacroHitRec& rRec) const override; + virtual PointerStyle GetMacroPointer (const SdrObjMacroHitRec& rRec) const override; + + // RotGrfFlyFrame: If true, this SdrObject supports only limited rotation. + virtual bool HasLimitedRotation() const override; + + virtual bool IsTextBox() const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/dialoghelp.hxx b/sw/source/core/inc/dialoghelp.hxx new file mode 100644 index 000000000..a54952365 --- /dev/null +++ b/sw/source/core/inc/dialoghelp.hxx @@ -0,0 +1,30 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DIALOGHELP_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DIALOGHELP_HXX + +class SwDoc; +class SwDocShell; +class SfxFrame; +class SfxMedium; + +namespace weld +{ +class Window; +} + +weld::Window* GetFrameWeld(const SfxFrame* pFrame); +weld::Window* GetFrameWeld(const SfxMedium* pMedium); +weld::Window* GetFrameWeld(SwDocShell* pDocSh); +weld::Window* GetFrameWeld(SwDoc* pDoc); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/inc/docedt.hxx b/sw/source/core/inc/docedt.hxx new file mode 100644 index 000000000..13ba7c8a8 --- /dev/null +++ b/sw/source/core/inc/docedt.hxx @@ -0,0 +1,31 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCEDT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCEDT_HXX + +class SwPaM; + +bool sw_JoinText( SwPaM& rPam, bool bJoinPrev ); + +void sw_GetJoinFlags( SwPaM& rPam, bool& rJoinText, bool& rJoinPrev ); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/docfld.hxx b/sw/source/core/inc/docfld.hxx new file mode 100644 index 000000000..36cf3d86e --- /dev/null +++ b/sw/source/core/inc/docfld.hxx @@ -0,0 +1,179 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCFLD_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCFLD_HXX + +#include <calc.hxx> +#include <doc.hxx> +#include <IDocumentTimerAccess.hxx> +#include <o3tl/sorted_vector.hxx> +#include <memory> + +class SwTextField; +class SwIndex; +class SwNodeIndex; +class SwContentFrame; +class SwSectionNode; +class SwSection; +class SwTextTOXMark; +class SwTableBox; +class SwTextINetFormat; +class SwFlyFrameFormat; +class SwNode; +struct SwPosition; +enum class SwFieldIds : sal_uInt16; + +// Update expression fields +class SetGetExpField +{ + sal_uLong m_nNode; + sal_Int32 m_nContent; + union { + const SwTextField* pTextField; + const SwSection* pSection; + const SwPosition* pPos; + const SwTextTOXMark* pTextTOX; + const SwTableBox* pTBox; + const SwTextINetFormat* pTextINet; + const SwFlyFrameFormat* pFlyFormat; + } m_CNTNT; + enum SetGetExpFieldType + { + TEXTFIELD, TEXTTOXMARK, SECTIONNODE, CRSRPOS, TABLEBOX, + TEXTINET, FLYFRAME + } m_eSetGetExpFieldType; + +public: + SetGetExpField( const SwNodeIndex& rNdIdx, const SwTextField* pField = nullptr, + const SwIndex* pIdx = nullptr ); + + SetGetExpField( const SwNodeIndex& rNdIdx, const SwTextINetFormat& rINet ); + + SetGetExpField( const SwSectionNode& rSectNode, + const SwPosition* pPos = nullptr ); + + SetGetExpField( const SwTableBox& rTableBox ); + + SetGetExpField( const SwNodeIndex& rNdIdx, const SwTextTOXMark& rTOX ); + + SetGetExpField( const SwPosition& rPos ); + + SetGetExpField( const SwFlyFrameFormat& rFlyFormat, const SwPosition* pPos ); + + bool operator==( const SetGetExpField& rField ) const; + bool operator<( const SetGetExpField& rField ) const; + + const SwTextField* GetTextField() const + { return TEXTFIELD == m_eSetGetExpFieldType ? m_CNTNT.pTextField : nullptr; } + const SwSection* GetSection() const + { return SECTIONNODE == m_eSetGetExpFieldType ? m_CNTNT.pSection : nullptr; } + const SwTextINetFormat* GetINetFormat() const + { return TEXTINET == m_eSetGetExpFieldType ? m_CNTNT.pTextINet : nullptr; } + const SwFlyFrameFormat* GetFlyFormat() const + { return FLYFRAME == m_eSetGetExpFieldType ? m_CNTNT.pFlyFormat : nullptr; } + + sal_uLong GetNode() const { return m_nNode; } + sal_Int32 GetContent() const { return m_nContent; } + const void* GetPointer() const { return m_CNTNT.pTextField; } + + void GetPosOfContent( SwPosition& rPos ) const; + + const SwNode* GetNodeFromContent() const; + sal_Int32 GetCntPosFromContent() const; + + void SetBodyPos( const SwContentFrame& rFrame ); +}; + +class SetGetExpFields : public o3tl::sorted_vector<std::unique_ptr<SetGetExpField>, o3tl::less_uniqueptr_to<SetGetExpField> > +{ +}; + +// struct for saving strings from the SetExp's string fields +struct HashStr : public SwHash +{ + OUString aSetStr; + HashStr( const OUString& rName, const OUString& rText, HashStr* ); +}; + +struct SwCalcFieldType : public SwHash +{ + const SwFieldType* pFieldType; + + SwCalcFieldType( const OUString& rStr, const SwFieldType* pFieldTyp ) + : SwHash( rStr ), pFieldType( pFieldTyp ) + {} +}; + +// search for the string that was saved under rName in the hash table +OUString LookString( SwHashTable<HashStr> const & rTable, const OUString& rName ); + +const int GETFLD_ALL = 3; // combine flags via OR +const int GETFLD_CALC = 1; +const int GETFLD_EXPAND = 2; + +class SwDocUpdateField +{ + std::unique_ptr<SetGetExpFields> m_pFieldSortList; ///< current field list for calculation + SwHashTable<SwCalcFieldType> m_FieldTypeTable; + + sal_uLong m_nNodes; ///< to check if the node count changed + int m_nFieldListGetMode; + SwDoc& m_rDoc; + + bool m_bInUpdateFields : 1; ///< currently in an UpdateFields call + bool m_bFieldsDirty : 1; ///< some fields are invalid + + void MakeFieldList_( SwDoc& pDoc, int eGetMode ); + void GetBodyNode( const SwTextField& , SwFieldIds nFieldWhich ); + void GetBodyNode( const SwSectionNode&); + +public: + SwDocUpdateField(SwDoc& rDocument); + ~SwDocUpdateField(); + + const SetGetExpFields* GetSortList() const { return m_pFieldSortList.get(); } + + void MakeFieldList( SwDoc& rDoc, bool bAll, int eGetMode ); + + void InsDelFieldInFieldLst( bool bIns, const SwTextField& rField ); + + void InsertFieldType( const SwFieldType& rType ); + void RemoveFieldType( const SwFieldType& rType ); + + bool IsInUpdateFields() const { return m_bInUpdateFields; } + void SetInUpdateFields( bool b ) { m_bInUpdateFields = b; } + + bool IsFieldsDirty() const { return m_bFieldsDirty; } + void SetFieldsDirty( bool b ) + { + m_bFieldsDirty = b; + + if (b) + { + m_rDoc.getIDocumentTimerAccess().StartIdling(); + } + } + + SwHashTable<SwCalcFieldType> const& GetFieldTypeTable() const { return m_FieldTypeTable; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/docredln.hxx b/sw/source/core/inc/docredln.hxx new file mode 100644 index 000000000..1a65c3540 --- /dev/null +++ b/sw/source/core/inc/docredln.hxx @@ -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/. + * + * 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCREDLN_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCREDLN_HXX + +#include <sal/config.h> + +#if defined DBG_UTIL + +class SwDoc; + +void sw_DebugRedline( const SwDoc* pDoc ); + +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/docsort.hxx b/sw/source/core/inc/docsort.hxx new file mode 100644 index 000000000..a660ba4cb --- /dev/null +++ b/sw/source/core/inc/docsort.hxx @@ -0,0 +1,160 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCSORT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCSORT_HXX + +#include <ndindex.hxx> + +#include <memory> +#include <vector> + +class SwDoc; +class SwTableBox; +class SwUndoSort; +class FlatFndBox; +struct SwSortOptions; +class FndBox_; +class FndLine_; +class CollatorWrapper; +class LocaleDataWrapper; + +namespace com { + namespace sun { + namespace star { + namespace lang { + struct Locale; + } + } + } +} + +class SwMovedBoxes +{ +private: + std::vector<const SwTableBox*> mBoxes; + +public: + void push_back(const SwTableBox* &rpTableBox) {mBoxes.push_back(rpTableBox);} + + sal_uInt16 GetPos(const SwTableBox* pTableBox) const; +}; + +// Functions for moving boxes +void MoveCol(SwDoc* pDoc, const FlatFndBox& rBox, + sal_uInt16 nS, sal_uInt16 nT, SwMovedBoxes& rMovedList, SwUndoSort* pUD); +void MoveRow(SwDoc* pDoc, const FlatFndBox& rBox, + sal_uInt16 nS, sal_uInt16 nT, SwMovedBoxes& rMovedList, SwUndoSort* pUD); +void MoveCell(SwDoc* pDoc, const SwTableBox* pSource, + const SwTableBox* pTar, bool bMovedBefore, SwUndoSort* pUD=nullptr); + +// Elements for sorting text and table content +struct SwSortElement +{ + static SwSortOptions* pOptions; + static SwDoc* pDoc; + static const FlatFndBox* pBox; + static CollatorWrapper* pSortCollator; + static css::lang::Locale* pLocale; + static OUString* pLastAlgorithm; + static LocaleDataWrapper* pLclData; + + static void Init( SwDoc*, const SwSortOptions& rOpt, FlatFndBox const * = nullptr ); + static void Finit(); + + SwSortElement() = default; + SwSortElement(SwSortElement const &) = default; + SwSortElement(SwSortElement &&) = default; + SwSortElement & operator =(SwSortElement const &) = default; + SwSortElement & operator =(SwSortElement &&) = default; + + virtual ~SwSortElement(); + + virtual OUString GetKey(sal_uInt16 nKey ) const = 0; + virtual double GetValue(sal_uInt16 nKey ) const; + + bool operator<(const SwSortElement& ) const; + + static double StrToDouble(const OUString& rStr); +private: + int keycompare(const SwSortElement& rCmp, sal_uInt16 nKey) const; +}; + +// sort text +struct SwSortTextElement : public SwSortElement +{ + sal_uLong nOrg; + SwNodeIndex aPos; + + SwSortTextElement( const SwNodeIndex& rPos ); + + virtual OUString GetKey( sal_uInt16 nKey ) const override; +}; + +// sort table +struct SwSortBoxElement : public SwSortElement +{ + sal_uInt16 nRow; + + SwSortBoxElement( sal_uInt16 nRC ); + + virtual OUString GetKey( sal_uInt16 nKey ) const override; + virtual double GetValue( sal_uInt16 nKey ) const override; +}; + +// two-dimensional array of FndBoxes +class FlatFndBox +{ +public: + FlatFndBox(SwDoc* pDocPtr, const FndBox_& rBox); + ~FlatFndBox(); + + bool IsSymmetric() const { return bSym; } + sal_uInt16 GetRows() const { return nRows; } + sal_uInt16 GetCols() const { return nCols; } + + const FndBox_* GetBox(sal_uInt16 nCol, sal_uInt16 nRow) const; + + inline bool HasItemSets() const; + const SfxItemSet* GetItemSet(sal_uInt16 nCol, sal_uInt16 nRow) const; + +private: + bool CheckLineSymmetry(const FndBox_& rBox); + bool CheckBoxSymmetry(const FndLine_& rLn); + sal_uInt16 GetColCount(const FndBox_& rBox); + sal_uInt16 GetRowCount(const FndBox_& rBox); + void FillFlat(const FndBox_&, bool bLastBox=false); + + SwDoc* pDoc; + std::unique_ptr<FndBox_ const *[]> pArr; + std::vector<std::unique_ptr<SfxItemSet>> ppItemSets; + + sal_uInt16 nRows; + sal_uInt16 nCols; + sal_uInt16 nRow; + sal_uInt16 nCol; + + bool bSym; +}; + +inline bool FlatFndBox::HasItemSets() const { return !ppItemSets.empty(); } + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/doctxm.hxx b/sw/source/core/inc/doctxm.hxx new file mode 100644 index 000000000..5c005b37f --- /dev/null +++ b/sw/source/core/inc/doctxm.hxx @@ -0,0 +1,108 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DOCTXM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DOCTXM_HXX + +#include <tools/gen.hxx> +#include <tox.hxx> +#include <section.hxx> + +class SwTOXInternational; +class SwPageDesc; +class SwTextNode; +class SwTextFormatColl; +struct SwPosition; +struct SwTOXSortTabBase; + +class SwTOXBaseSection : public SwTOXBase, public SwSection +{ + std::vector<std::unique_ptr<SwTOXSortTabBase>> m_aSortArr; + + void UpdateMarks( const SwTOXInternational& rIntl, + const SwTextNode* pOwnChapterNode, + SwRootFrame const* pLayout ); + void UpdateOutline( const SwTextNode* pOwnChapterNode, + SwRootFrame const* pLayout ); + void UpdateTemplate( const SwTextNode* pOwnChapterNode, + SwRootFrame const* pLayout ); + void UpdateContent( SwTOXElement eType, + const SwTextNode* pOwnChapterNode, + SwRootFrame const* pLayout ); + void UpdateTable( const SwTextNode* pOwnChapterNode, + SwRootFrame const* pLayout ); + void UpdateSequence( const SwTextNode* pOwnChapterNode, + SwRootFrame const* pLayout ); + void UpdateAuthorities( const SwTOXInternational& rIntl, + SwRootFrame const* pLayout ); + + // insert sorted into array for creation + void InsertSorted(std::unique_ptr<SwTOXSortTabBase> pBase); + + // insert alpha delimiter at creation + void InsertAlphaDelimiter( const SwTOXInternational& rIntl ); + + // replace page num placeholder with actual page number + void UpdatePageNum_( SwTextNode* pNd, + const std::vector<sal_uInt16>& rNums, + const std::vector<SwPageDesc*>& rDescs, + const std::vector<sal_uInt16>* pMainEntryNums, + const SwTOXInternational& rIntl ); + + // get section for entering keywords + Range GetKeyRange( const OUString& rStr, const OUString& rStrReading, + const SwTOXSortTabBase& rNew, sal_uInt16 nLevel, + const Range& rRange ); + + // return text collection via name/ from format pool + SwTextFormatColl* GetTextFormatColl( sal_uInt16 nLevel ); + +public: + SwTOXBaseSection(SwTOXBase const& rBase, SwSectionFormat & rFormat); + virtual ~SwTOXBaseSection() override; + + // <_bNewTOX> : distinguish between the creation of a new table-of-content + // (true) or an update of a table-of-content (false) + void Update( const SfxItemSet* pAttr = nullptr, + SwRootFrame const* pLayout = nullptr, + const bool _bNewTOX = false ); + void UpdatePageNum(); // insert page numbering + + bool SetPosAtStartEnd( SwPosition& rPos ) const; +}; + +struct SwDefTOXBase_Impl +{ + std::unique_ptr<SwTOXBase> pContBase; + std::unique_ptr<SwTOXBase> pIdxBase; + std::unique_ptr<SwTOXBase> pUserBase; + std::unique_ptr<SwTOXBase> pTableBase; + std::unique_ptr<SwTOXBase> pObjBase; + std::unique_ptr<SwTOXBase> pIllBase; + std::unique_ptr<SwTOXBase> pAuthBase; + std::unique_ptr<SwTOXBase> pBiblioBase; + + SwDefTOXBase_Impl() + { + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/drawfont.hxx b/sw/source/core/inc/drawfont.hxx new file mode 100644 index 000000000..410943c98 --- /dev/null +++ b/sw/source/core/inc/drawfont.hxx @@ -0,0 +1,597 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DRAWFONT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DRAWFONT_HXX + +#include <osl/diagnose.h> +#include <vcl/vclptr.hxx> +#include <vcl/outdev.hxx> +#include "TextFrameIndex.hxx" +#include <swdllapi.h> + +class SwTextFrame; +class SwViewShell; +class SwScriptInfo; +namespace sw { class WrongListIterator; } +class SwFont; +namespace vcl { + class Font; + class TextLayoutCache; + typedef OutputDevice RenderContext; +} +class SwUnderlineFont; + +// encapsulates information for drawing text +class SW_DLLPUBLIC SwDrawTextInfo +{ + const SwTextFrame* m_pFrame; + VclPtr<OutputDevice> m_pOut; + SwViewShell const * m_pSh; + const SwScriptInfo* m_pScriptInfo; + Point m_aPos; + vcl::TextLayoutCache const* m_pCachedVclData; + OUString m_aText; + sw::WrongListIterator* m_pWrong; + sw::WrongListIterator* m_pGrammarCheck; + sw::WrongListIterator* m_pSmartTags; + Size m_aSize; + SwFont *m_pFnt; + SwUnderlineFont* m_pUnderFnt; + TextFrameIndex* m_pHyphPos; + long m_nKanaDiff; + TextFrameIndex m_nIdx; + TextFrameIndex m_nLen; + /// this is not a string index + sal_Int32 m_nOfst; + sal_uInt16 m_nWidth; + sal_uInt16 m_nAscent; + sal_uInt16 m_nCompress; + long m_nSperren; + long m_nSpace; + long m_nKern; + TextFrameIndex m_nNumberOfBlanks; + sal_uInt8 m_nCursorBidiLevel; + bool m_bBullet : 1; + bool m_bUpper : 1; // for small caps: upper case flag + bool m_bDrawSpace : 1; // for small caps: underline/ line through + bool m_bGreyWave : 1; // grey wave line for extended text input + // For underlining we need to know, if a section is right in front of a + // whole block or a fix margin section. + bool m_bSpaceStop : 1; + bool m_bSnapToGrid : 1; // Does paragraph snap to grid? + // Paint text as if text has LTR direction, used for line numbering + bool m_bIgnoreFrameRTL : 1; + // GetModelPositionForViewPoint should not return the next position if screen position is + // inside second half of bound rect, used for Accessibility + bool m_bPosMatchesBounds :1; + +public: + +#ifdef DBG_UTIL + // These flags should control that the appropriate Set-function has been + // called before calling the Get-function of a member + bool m_bPos : 1; + bool m_bWrong : 1; + bool m_bGrammarCheck : 1; + bool m_bSize : 1; + bool m_bFnt : 1; + bool m_bHyph : 1; + bool m_bKana : 1; + bool m_bOfst : 1; + bool m_bAscent: 1; + bool m_bSperr : 1; + bool m_bSpace : 1; + bool m_bNumberOfBlanks : 1; + bool m_bUppr : 1; + bool m_bDrawSp: 1; +#endif + + /// constructor for simple strings + SwDrawTextInfo( SwViewShell const *pSh, OutputDevice &rOut, + const OUString &rText, sal_Int32 const nIdx, sal_Int32 const nLen, + sal_uInt16 nWidth = 0, bool bBullet = false) + : SwDrawTextInfo(pSh, rOut, nullptr, rText, TextFrameIndex(nIdx), TextFrameIndex(nLen), nWidth, bBullet) + {} + /// constructor for text frame contents + SwDrawTextInfo( SwViewShell const *pSh, OutputDevice &rOut, const SwScriptInfo* pSI, + const OUString &rText, TextFrameIndex const nIdx, TextFrameIndex const nLen, + sal_uInt16 nWidth = 0, bool bBullet = false, + vcl::TextLayoutCache const*const pCachedVclData = nullptr) + : m_pCachedVclData(pCachedVclData) + { + m_pFrame = nullptr; + m_pSh = pSh; + m_pOut = &rOut; + m_pScriptInfo = pSI; + m_aText = rText; + m_nIdx = nIdx; + m_nLen = nLen; + m_nKern = 0; + m_nCompress = 0; + m_nWidth = nWidth; + m_nNumberOfBlanks = TextFrameIndex(0); + m_nCursorBidiLevel = 0; + m_bBullet = bBullet; + m_pUnderFnt = nullptr; + m_bGreyWave = false; + m_bSpaceStop = false; + m_bSnapToGrid = false; + m_bIgnoreFrameRTL = false; + m_bPosMatchesBounds = false; + + // These values are initialized but have to be set explicitly via their + // Set-function before they may be accessed by their Get-function: + m_pWrong = nullptr; + m_pGrammarCheck = nullptr; + m_pSmartTags = nullptr; + m_pFnt = nullptr; + m_pHyphPos = nullptr; + m_nKanaDiff = 0; + m_nOfst = 0; + m_nAscent = 0; + m_nSperren = 0; + m_nSpace = 0; + m_bUpper = false; + m_bDrawSpace = false; + +#ifdef DBG_UTIL + // these flags control whether the matching member variables have been + // set by using the Set-function before they may be accessed by their + // Get-function: + m_bPos = m_bWrong = m_bGrammarCheck = m_bSize = m_bFnt = m_bAscent = + m_bSpace = m_bNumberOfBlanks = m_bUppr = + m_bDrawSp = m_bKana = m_bOfst = m_bHyph = + m_bSperr = false; +#endif + } + + const SwTextFrame* GetFrame() const + { + return m_pFrame; + } + + void SetFrame( const SwTextFrame* pNewFrame ) + { + m_pFrame = pNewFrame; + } + + SwViewShell const *GetShell() const + { + return m_pSh; + } + + vcl::RenderContext& GetOut() const + { + return *m_pOut; + } + + vcl::RenderContext *GetpOut() const + { + return m_pOut; + } + + const SwScriptInfo* GetScriptInfo() const + { + return m_pScriptInfo; + } + + const Point &GetPos() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bPos, "DrawTextInfo: Undefined Position" ); +#endif + return m_aPos; + } + + TextFrameIndex *GetHyphPos() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bHyph, "DrawTextInfo: Undefined Hyph Position" ); +#endif + return m_pHyphPos; + } + + vcl::TextLayoutCache const* GetVclCache() const + { + return m_pCachedVclData; + } + + const OUString &GetText() const + { + return m_aText; + } + + sw::WrongListIterator* GetWrong() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bWrong, "DrawTextInfo: Undefined WrongList" ); +#endif + return m_pWrong; + } + + sw::WrongListIterator* GetGrammarCheck() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bGrammarCheck, "DrawTextInfo: Undefined GrammarCheck List" ); +#endif + return m_pGrammarCheck; + } + + sw::WrongListIterator* GetSmartTags() const + { + return m_pSmartTags; + } + + const Size &GetSize() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bSize, "DrawTextInfo: Undefined Size" ); +#endif + return m_aSize; + } + + SwFont* GetFont() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bFnt, "DrawTextInfo: Undefined Font" ); +#endif + return m_pFnt; + } + + SwUnderlineFont* GetUnderFnt() const + { + return m_pUnderFnt; + } + + TextFrameIndex GetIdx() const + { + return m_nIdx; + } + + TextFrameIndex GetLen() const + { + return m_nLen; + } + + sal_Int32 GetOffset() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bOfst, "DrawTextInfo: Undefined Offset" ); +#endif + return m_nOfst; + } + + TextFrameIndex GetEnd() const + { + return m_nIdx + m_nLen; + } + + long GetKanaDiff() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bKana, "DrawTextInfo: Undefined kana difference" ); +#endif + return m_nKanaDiff; + } + + sal_uInt16 GetWidth() const + { + return m_nWidth; + } + + sal_uInt16 GetAscent() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bAscent, "DrawTextInfo: Undefined Ascent" ); +#endif + return m_nAscent; + } + + sal_uInt16 GetKanaComp() const + { + return m_nCompress; + } + + long GetSperren() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bSperr, "DrawTextInfo: Undefined >Sperren<" ); +#endif + return m_nSperren; + } + + long GetKern() const + { + return m_nKern; + } + + long GetSpace() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bSpace, "DrawTextInfo: Undefined Spacing" ); +#endif + return m_nSpace; + } + + TextFrameIndex GetNumberOfBlanks() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bNumberOfBlanks, "DrawTextInfo::Undefined NumberOfBlanks" ); +#endif + return m_nNumberOfBlanks; + } + + sal_uInt8 GetCursorBidiLevel() const + { + return m_nCursorBidiLevel; + } + + bool GetBullet() const + { + return m_bBullet; + } + + bool GetUpper() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bUppr, "DrawTextInfo: Undefined Upperflag" ); +#endif + return m_bUpper; + } + + bool GetDrawSpace() const + { +#ifdef DBG_UTIL + OSL_ENSURE( m_bDrawSp, "DrawTextInfo: Undefined DrawSpaceflag" ); +#endif + return m_bDrawSpace; + } + + bool GetGreyWave() const + { + return m_bGreyWave; + } + + bool IsSpaceStop() const + { + return m_bSpaceStop; + } + + bool SnapToGrid() const + { + return m_bSnapToGrid; + } + + bool IsIgnoreFrameRTL() const + { + return m_bIgnoreFrameRTL; + } + + bool IsPosMatchesBounds() const + { + return m_bPosMatchesBounds; + } + + void SetOut( OutputDevice &rNew ) + { + m_pOut = &rNew; + } + + void SetPos( const Point &rNew ) + { + m_aPos = rNew; +#ifdef DBG_UTIL + m_bPos = true; +#endif + } + + void SetHyphPos(TextFrameIndex *const pNew) + { + m_pHyphPos = pNew; +#ifdef DBG_UTIL + m_bHyph = true; +#endif + } + + void SetText( const OUString &rNew ) + { + m_aText = rNew; + m_pCachedVclData = nullptr; // would any case benefit from save/restore? + } + + void SetWrong(sw::WrongListIterator *const pNew) + { + m_pWrong = pNew; +#ifdef DBG_UTIL + m_bWrong = true; +#endif + } + + void SetGrammarCheck(sw::WrongListIterator *const pNew) + { + m_pGrammarCheck = pNew; +#ifdef DBG_UTIL + m_bGrammarCheck = true; +#endif + } + + void SetSmartTags(sw::WrongListIterator *const pNew) + { + m_pSmartTags = pNew; + } + + void SetSize( const Size &rNew ) + { + m_aSize = rNew; +#ifdef DBG_UTIL + m_bSize = true; +#endif + } + + void SetFont( SwFont* pNew ) + { + m_pFnt = pNew; +#ifdef DBG_UTIL + m_bFnt = true; +#endif + } + + void SetIdx(TextFrameIndex const nNew) + { + m_nIdx = nNew; + } + + void SetLen(TextFrameIndex const nNew) + { + m_nLen = nNew; + } + + void SetOffset( sal_Int32 nNew ) + { + m_nOfst = nNew; +#ifdef DBG_UTIL + m_bOfst = true; +#endif + } + + void SetKanaDiff( long nNew ) + { + m_nKanaDiff = nNew; +#ifdef DBG_UTIL + m_bKana = true; +#endif + } + + void SetWidth( sal_uInt16 nNew ) + { + m_nWidth = nNew; + } + + void SetAscent( sal_uInt16 nNew ) + { + m_nAscent = nNew; +#ifdef DBG_UTIL + m_bAscent = true; +#endif + } + + void SetKern( long nNew ) + { + m_nKern = nNew; + } + + void SetSpace( long nNew ) + { + if( nNew < 0 ) + { + m_nSperren = -nNew; + m_nSpace = 0; + } + else + { + m_nSpace = nNew; + m_nSperren = 0; + } +#ifdef DBG_UTIL + m_bSpace = true; + m_bSperr = true; +#endif + } + + void SetNumberOfBlanks( TextFrameIndex const nNew ) + { +#ifdef DBG_UTIL + m_bNumberOfBlanks = true; +#endif + m_nNumberOfBlanks = nNew; + } + + void SetCursorBidiLevel( sal_uInt8 nNew ) + { + m_nCursorBidiLevel = nNew; + } + + void SetKanaComp( short nNew ) + { + m_nCompress = nNew; + } + + void SetBullet( bool bNew ) + { + m_bBullet = bNew; + } + + void SetUnderFnt( SwUnderlineFont* pULFnt ) + { + m_pUnderFnt = pULFnt; + } + + void SetUpper( bool bNew ) + { + m_bUpper = bNew; +#ifdef DBG_UTIL + m_bUppr = true; +#endif + } + + void SetDrawSpace( bool bNew ) + { + m_bDrawSpace = bNew; +#ifdef DBG_UTIL + m_bDrawSp = true; +#endif + } + + void SetGreyWave( bool bNew ) + { + m_bGreyWave = bNew; + } + + void SetSpaceStop( bool bNew ) + { + m_bSpaceStop = bNew; + } + + void SetSnapToGrid( bool bNew ) + { + m_bSnapToGrid = bNew; + } + + void SetIgnoreFrameRTL( bool bNew ) + { + m_bIgnoreFrameRTL = bNew; + } + + void SetPosMatchesBounds( bool bNew ) + { + m_bPosMatchesBounds = bNew; + } + + void Shift( sal_uInt16 nDir ); + + // sets a new color at the output device if necessary if a font is passed + // as argument, the change if made to the font otherwise the font at the + // output device is changed returns if the font has been changed + bool ApplyAutoColor( vcl::Font* pFnt = nullptr ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/dumpfilter.hxx b/sw/source/core/inc/dumpfilter.hxx new file mode 100644 index 000000000..6ebc6e8c9 --- /dev/null +++ b/sw/source/core/inc/dumpfilter.hxx @@ -0,0 +1,59 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DUMPFILTER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DUMPFILTER_HXX + +#include <com/sun/star/document/XFilter.hpp> +#include <com/sun/star/document/XExporter.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/implbase.hxx> + +namespace sw { + + /** Implementation of UNO export service to dump the layout of the + document as XML. This filter should be mostly be used for testing + purpose. + */ + class LayoutDumpFilter final : public cppu::WeakImplHelper + < + css::document::XFilter, + css::document::XExporter, + css::lang::XInitialization, + css::lang::XServiceInfo + > + { + css::uno::Reference< css::lang::XComponent > m_xSrcDoc; + + public: + LayoutDumpFilter(); + virtual ~LayoutDumpFilter() override; + + // XFilter + virtual sal_Bool SAL_CALL filter( const css::uno::Sequence< css::beans::PropertyValue >& aDescriptor ) override; + virtual void SAL_CALL cancel( ) override; + + // XExporter + virtual void SAL_CALL setSourceDocument( const css::uno::Reference< css::lang::XComponent >& xDoc ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/dview.hxx b/sw/source/core/inc/dview.hxx new file mode 100644 index 000000000..2251efa48 --- /dev/null +++ b/sw/source/core/inc/dview.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_DVIEW_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_DVIEW_HXX + +#include <svx/fmview.hxx> + +class FmFormModel; +class OutputDevice; +class SwViewShellImp; +class SwFrame; +class SwFlyFrame; +class SwAnchoredObject; +class SdrUndoManager; + +class SwDrawView : public FmFormView +{ + Point m_aAnchorPoint; // anchor position + SwViewShellImp &m_rImp; // a view is always part of a shell + + const SwFrame *CalcAnchor(); + + /** determine maximal order number for a 'child' object of given 'parent' object + + The maximal order number will be determined on the current object + order hierarchy. It's the order number of the 'child' object with the + highest order number. The calculation can be influenced by parameter + <_pExclChildObj> - this 'child' object won't be considered. + + @param <_rParentObj> + input parameter - 'parent' object, for which the maximal order number + for its 'children' will be determined. + + @param <_pExclChildObj> + optional input parameter - 'child' object, which will not be considered + on the calculation of the maximal order number + */ + static sal_uInt32 GetMaxChildOrdNum( const SwFlyFrame& _rParentObj, + const SdrObject* _pExclChildObj = nullptr ); + + /** method to move 'repeated' objects of the given moved object to the + according level + + @param <_rMovedAnchoredObj> + input parameter - moved object, for which the 'repeated' ones have also + to be moved. + + @param <_rMovedChildrenObjs> + input parameter - data collection of moved 'child' objects - the 'repeated' + ones of these 'children' will also been moved. + */ + void MoveRepeatedObjs( const SwAnchoredObject& _rMovedAnchoredObj, + const std::vector<SdrObject*>& _rMovedChildObjs ) const; + +protected: + // add custom handles (used by other apps, e.g. AnchorPos) + virtual void AddCustomHdl() override; + + // override to allow extra handling when picking SwVirtFlyDrawObj's + using FmFormView::CheckSingleSdrObjectHit; + virtual SdrObject* CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObject* pObj, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay) const override; + + // support enhanced text edit for draw objects + virtual SdrUndoManager* getSdrUndoManagerForEnhancedTextEdit() const override; + +public: + SwDrawView( + SwViewShellImp &rI, + FmFormModel& rFmFormModel, + OutputDevice* pOutDev); + + // from base class + virtual SdrObject* GetMaxToTopObj(SdrObject* pObj) const override; + virtual SdrObject* GetMaxToBtmObj(SdrObject* pObj) const override; + virtual void MarkListHasChanged() override; + + // #i7672# + // Override to reuse edit background color in active text edit view (OutlinerView) + virtual void ModelHasChanged() override; + + virtual void ObjOrderChanged( SdrObject* pObj, size_t nOldPos, + size_t nNewPos ) override; + virtual bool TakeDragLimit(SdrDragMode eMode, tools::Rectangle& rRect) const override; + virtual void MakeVisible( const tools::Rectangle&, vcl::Window &rWin ) override; + virtual void CheckPossibilities() override; + + const SwViewShellImp &Imp() const { return m_rImp; } + SwViewShellImp &Imp() { return m_rImp; } + + // anchor and Xor for dragging + void ShowDragAnchor(); + + virtual void DeleteMarked() override; + + void ValidateMarkList() { FlushComeBackTimer(); } + + // #i99665# + bool IsAntiAliasing() const; + + // method to replace marked/selected <SwDrawVirtObj> + // by its reference object for delete of selection and group selection + static void ReplaceMarkedDrawVirtObjs( SdrMarkView& _rMarkView ); + + /// See SdrMarkView::GetSfxViewShell(). + SfxViewShell* GetSfxViewShell() const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/environmentofanchoredobject.hxx b/sw/source/core/inc/environmentofanchoredobject.hxx new file mode 100644 index 000000000..aa3d5a2a5 --- /dev/null +++ b/sw/source/core/inc/environmentofanchoredobject.hxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ENVIRONMENTOFANCHOREDOBJECT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ENVIRONMENTOFANCHOREDOBJECT_HXX + +class SwFrame; +class SwLayoutFrame; + +namespace objectpositioning +{ + class SwEnvironmentOfAnchoredObject + { + private: + const bool mbFollowTextFlow; + + public: + /** constructor + + @param _bFollowTextFlow + input parameter - indicates, if the anchored object, for which + this environment is instantiated, follow the text flow or not + */ + SwEnvironmentOfAnchoredObject( const bool _bFollowTextFlow ); + + /** destructor + */ + ~SwEnvironmentOfAnchoredObject(); + + /** determine environment layout frame for possible horizontal object + positions respectively for alignment to 'page areas' + + this is, if object has to follow the text flow: + - cell frame, if anchored inside a cell + - fly frame, if anchored inside a fly frame + otherwise it's the page frame + + this is, if object hasn't to follow the text flow: + - page frame. + - no exception any more. Thus remove + parameter <_bForPageAlignment> + + @param _rHoriOrientFrame + input parameter - frame, at which the horizontal position is + oriented at (typically it's the anchor frame). + starting point for the search of the layout frame. + + @return reference to the layout frame, which determines the + horizontal environment the object has to be positioned in. + */ + const SwLayoutFrame& GetHoriEnvironmentLayoutFrame( const SwFrame& _rHoriOrientFrame ) const; + + /** determine environment layout frame for possible vertical object + positions respectively for alignments to 'page areas' + + this is, if object has to follow the text flow: + - cell frame, if anchored inside a cell + - fly frame, if anchored inside a fly frame + - header/footer frame, if anchored inside page header/footer + - footnote frame, if anchored inside footnote + otherwise it's the document body frame + + this is, if object hasn't to follow the text flow: + - page frame. + - no exception any more. Thus remove + parameter <_bForPageAlignment> + + @param _rVertOrientFrame + input parameter - frame, at which the vertical position is + oriented at (typically it's the anchor frame). + starting point for the search of the layout frame. + + @return reference to the layout frame, which determines the + vertical environment the object has to be positioned in. + */ + const SwLayoutFrame& GetVertEnvironmentLayoutFrame( const SwFrame& _rVertOrientFrame ) const; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/fefly.hxx b/sw/source/core/inc/fefly.hxx new file mode 100644 index 000000000..07b56d5fc --- /dev/null +++ b/sw/source/core/inc/fefly.hxx @@ -0,0 +1,31 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FEFLY_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FEFLY_HXX + +class SwFlyFrame; +class SfxItemSet; + +bool sw_ChkAndSetNewAnchor( const SwFlyFrame& rFly, SfxItemSet& rSet ); + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/fieldhint.hxx b/sw/source/core/inc/fieldhint.hxx new file mode 100644 index 000000000..929d6ea19 --- /dev/null +++ b/sw/source/core/inc/fieldhint.hxx @@ -0,0 +1,43 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FIELDHINT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FIELDHINT_HXX + +#include <svl/hint.hxx> + +class SwPaM; +class SwRootFrame; + +class SwFieldHint : public SfxHint +{ +public: + SwPaM* m_pPaM; + SwRootFrame const* m_pLayout; + + SwFieldHint(SwPaM *const pPaM, SwRootFrame const*const pLayout) + : m_pPaM(pPaM) + , m_pLayout(pLayout) + {} + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/flowfrm.hxx b/sw/source/core/inc/flowfrm.hxx new file mode 100644 index 000000000..078ef9326 --- /dev/null +++ b/sw/source/core/inc/flowfrm.hxx @@ -0,0 +1,279 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FLOWFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FLOWFRM_HXX + +#include "frame.hxx" +#include "layfrm.hxx" +#include <swtypes.hxx> + +class SvxFormatKeepItem; +class SvxFormatBreakItem; +class SwPageFrame; +class SwRect; +class SwBorderAttrs; +class SwDoc; +class SwNodeIndex; + +/** Base class that provides the general functionalities for frames that are + allowed at page breaks (flow) and shall continue on the next page (can be + split), e.g. paragraphs (ContentFrame) or tables (TabFrame). + + Some parts of these functionalities are implemented in FlowFrame while the + specific ones are done in the corresponding Frame classes. The FlowFrame has to + be seen as a base class. As such it is no Frame by itself and thus no direct + instances of FlowFrame can exist. + + Actually it is not even a real Frame. The obvious implementation would be a + FlowFrame that is virtually inherited from SwFrame and that works with its own + member data. Further classes would need to inherit from FlowFrame and (via + multiple base classes since the class tree splits exactly at the branch + from SwFrame to SwContentFrame and SwLayoutFrame) also virtually from SwFrame as + well. Unfortunately, this leads - besides problems with compilers and + debugging programs - to high additional costs, that we IMHO are not able to + afford nowadays. + + Hence, we use another technique: A FlowFrame keeps a reference to a SwFrame + - which it is actually itself - and they are friends. As a result, the + FlowFrame can work with the reference to the SwFrame instead of working with + its own this-pointer. + */ +class SwFlowFrame +{ + // PrepareMake is allowed to lock/unlock (robustness) + friend inline void PrepareLock ( SwFlowFrame * ); + friend inline void PrepareUnlock( SwFlowFrame * ); + friend inline void TableSplitRecalcLock( SwFlowFrame * ); + friend inline void TableSplitRecalcUnlock( SwFlowFrame * ); + // #i44049# + friend class SwObjectFormatterTextFrame; + friend class FlowFrameJoinLockGuard; + + // TableSel is allowed to reset the follow-bit + friend inline void UnsetFollow( SwFlowFrame *pFlow ); + + friend void MakeFrames( SwDoc *, const SwNodeIndex &, const SwNodeIndex & ); + + friend class SwNode2LayImpl; + + SwFrame& m_rThis; + + // helper methods for MoveSubTree() + static SwLayoutFrame *CutTree( SwFrame* ); + static bool PasteTree( SwFrame *, SwLayoutFrame *, SwFrame *, SwFrame* ); + + /** indicates that a backward move was done over multiple pages + + Needed for the interaction of _GetPrevxxx and MoveBwd so that multiple + pages can be skipped at the same time. In addition, it is evaluated by + the MoveBwd() method in TabFrame. + */ + static bool m_bMoveBwdJump; + + /** helper method to determine previous frame for calculation of the + upper space + + #i11860# + + @param _pProposedPrevFrame + optional input parameter - pointer to frame, which should be used + instead of the direct previous frame. + */ + const SwFrame* GetPrevFrameForUpperSpaceCalc_( const SwFrame* _pProposedPrevFrame = nullptr ) const; + + /** method to determine the upper space amount, which is considered for + the previous frame + + #i11860# + */ + SwTwips GetUpperSpaceAmountConsideredForPrevFrame() const; + + /** method to determine the upper space amount, which is considered for + the page grid + + #i11860# + */ + SwTwips GetUpperSpaceAmountConsideredForPageGrid_( + const SwTwips _nUpperSpaceWithoutGrid ) const; + +protected: + SwFlowFrame *m_pFollow; + SwFlowFrame *m_pPrecede; + + bool m_bLockJoin :1; // if true than joins (and thus deletes) are prohibited! + bool m_bUndersized:1; // I am smaller than needed + bool m_bFlyLock :1; // stop positioning of at-character flyframes + + // checks if forward flow makes sense to prevent infinite moves + inline bool IsFwdMoveAllowed() const; + // #i44049# - method <CalcContent(..)> has to check this property. + friend void CalcContent( SwLayoutFrame *pLay, bool bNoColl ); + bool IsKeepFwdMoveAllowed( bool bIgnoreMyOwnKeepValue = false ); // like above, forward flow for Keep. + + /** method to determine overlapping of an object that requests floating + + 0: no overlapping + 1: objects that are anchored at the FlowFrame overlap + 2: objects that are anchored somewhere else overlap + 3: both types of objects overlap + */ + sal_uInt8 BwdMoveNecessary( const SwPageFrame *pPage, const SwRect &rRect ); + + void LockJoin() { m_bLockJoin = true; } + void UnlockJoin() { m_bLockJoin = false; } + + bool CheckMoveFwd( bool& rbMakePage, bool bKeep, bool bIgnoreMyOwnKeepValue ); + bool MoveFwd( bool bMakePage, bool bPageBreak, bool bMoveAlways = false ); + bool MoveBwd( bool &rbReformat ); + virtual bool ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool &rReformat )=0; + +public: + SwFlowFrame( SwFrame &rFrame ); + virtual ~SwFlowFrame(); + + const SwFrame& GetFrame() const { return m_rThis; } + SwFrame& GetFrame() { return m_rThis; } + + static bool IsMoveBwdJump() { return m_bMoveBwdJump; } + static void SetMoveBwdJump( bool bNew ){ m_bMoveBwdJump = bNew; } + + void SetUndersized( const bool bNew ) { m_bUndersized = bNew; } + bool IsUndersized() const { return m_bUndersized; } + + bool IsPrevObjMove() const; + + /** hook tree onto new parent with minimal operations and notifications */ + void MoveSubTree( SwLayoutFrame* pParent, SwFrame* pSibling = nullptr ); + + bool HasFollow() const { return m_pFollow != nullptr; } + bool IsFollow() const { return nullptr != m_pPrecede; } + bool IsAnFollow( const SwFlowFrame *pFlow ) const; + const SwFlowFrame *GetFollow() const { return m_pFollow; } + SwFlowFrame *GetFollow() { return m_pFollow; } + void SetFollow(SwFlowFrame *const pFollow); + + const SwFlowFrame *GetPrecede() const { return m_pPrecede; } + SwFlowFrame *GetPrecede() { return m_pPrecede; } + + bool IsJoinLocked() const { return m_bLockJoin; } + bool IsAnyJoinLocked() const { return m_bLockJoin || HasLockedFollow(); } + + bool IsPageBreak( bool bAct ) const; + bool IsColBreak( bool bAct ) const; + + /** method to determine if a Keep needs to be considered (Breaks!) */ + bool IsKeep(SvxFormatKeepItem const& rKeep, + SvxFormatBreakItem const& rBreak, + bool bBreakCheck = false ) const; + + bool HasLockedFollow() const; + + bool HasParaSpaceAtPages( bool bSct ) const; + + /** method to determine the upper space hold by the frame + + #i11860# + + @param _bConsiderGrid + optional input parameter - consider the page grid while calculating? + */ + SwTwips CalcUpperSpace( const SwBorderAttrs *pAttrs = nullptr, + const SwFrame* pPr = nullptr, + const bool _bConsiderGrid = true ) const; + + /** method to determine the upper space amount, which is considered for + the previous frame and the page grid, if option 'Use former object + positioning' is OFF + + #i11860# + */ + SwTwips GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid() const; + + /** calculation of lower space */ + SwTwips CalcLowerSpace( const SwBorderAttrs* _pAttrs = nullptr ) const; + + /** calculation of the additional space to be considered, if flow frame + is the last inside a table cell + + #i26250 + + @param _pAttrs + optional input parameter - border attributes of the flow frame. + Used for optimization, if caller has already determined the border + attributes. + + @return SwTwips + */ + SwTwips CalcAddLowerSpaceAsLastInTableCell( + const SwBorderAttrs* _pAttrs = nullptr ) const; + + void CheckKeep(); + + void SetFlyLock( bool bNew ){ m_bFlyLock = bNew; } + bool IsFlyLock() const { return m_bFlyLock; } + + bool ForbiddenForFootnoteCntFwd() const; + + // Casting of a Frame into a FlowFrame (if it is one, otherwise 0) + // These methods need to be customized in subclasses! + static SwFlowFrame *CastFlowFrame( SwFrame *pFrame ); + static const SwFlowFrame *CastFlowFrame( const SwFrame *pFrame ); +}; + +inline bool SwFlowFrame::IsFwdMoveAllowed() const +{ + return m_rThis.GetIndPrev() != nullptr; +} + +//use this to protect a SwLayoutFrame for a given scope from getting merged with +//its neighbour and thus deleted +class FlowFrameJoinLockGuard +{ +private: + SwFlowFrame *m_pFlow; + bool m_bOldJoinLocked; +public: + //JoinLock pParent for the lifetime of the Cut/Paste call, etc. to avoid + //SwSectionFrame::MergeNext removing the pParent we're trying to reparent + //into + FlowFrameJoinLockGuard(SwLayoutFrame* pFrame) + { + m_pFlow = SwFlowFrame::CastFlowFrame(pFrame); + if (m_pFlow) + { + m_bOldJoinLocked = m_pFlow->IsJoinLocked(); + m_pFlow->LockJoin(); + } + else + { + m_bOldJoinLocked = false; + } + } + + ~FlowFrameJoinLockGuard() + { + if (m_pFlow && !m_bOldJoinLocked) + m_pFlow->UnlockJoin(); + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/flyfrm.hxx b/sw/source/core/inc/flyfrm.hxx new file mode 100644 index 000000000..464eadd7e --- /dev/null +++ b/sw/source/core/inc/flyfrm.hxx @@ -0,0 +1,284 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FLYFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FLYFRM_HXX + +#include "layfrm.hxx" +#include <vector> +#include <frmfmt.hxx> +#include <anchoredobject.hxx> +#include <swdllapi.h> + +class SwFormatAnchor; +class SwPageFrame; +class SwFormatFrameSize; +struct SwCursorMoveState; +class SwBorderAttrs; +class SwVirtFlyDrawObj; +class SwAttrSetChg; +namespace tools { class PolyPolygon; } +class SwFormat; +class SwViewShell; +class SwFEShell; +class SwWrtShell; + + +/** search an anchor for paragraph bound frames starting from pOldAnch + + needed for dragging of objects bound to a paragraph for showing an anchor + indicator as well as for changing the anchor. + + implemented in layout/flycnt.cxx + */ +const SwContentFrame *FindAnchor( const SwFrame *pOldAnch, const Point &rNew, + const bool bBody = false ); + +/** calculate rectangle in that the object can be moved or rather be resized */ +bool CalcClipRect( const SdrObject *pSdrObj, SwRect &rRect, bool bMove = true ); + +/** general base class for all free-flowing frames + + #i26791# - inherit also from <SwAnchoredFlyFrame> +*/ +class SW_DLLPUBLIC SwFlyFrame : public SwLayoutFrame, public SwAnchoredObject +{ + // is allowed to lock, implemented in frmtool.cxx + friend void AppendObj(SwFrame *const pFrame, SwPageFrame *const pPage, SwFrameFormat *const pFormat, const SwFormatAnchor & rAnch); + friend void Notify( SwFlyFrame *, SwPageFrame *pOld, const SwRect &rOld, + const SwRect* pOldPrt ); + + void InitDrawObj(); // these to methods are called in the + void FinitDrawObj(); // constructors + + void UpdateAttr_( const SfxPoolItem*, const SfxPoolItem*, sal_uInt8 &, + SwAttrSetChg *pa = nullptr, SwAttrSetChg *pb = nullptr ); + + using SwLayoutFrame::CalcRel; + +protected: + // Predecessor/Successor for chaining with text flow + SwFlyFrame *m_pPrevLink, *m_pNextLink; + +private: + // It must be possible to block Content-bound flys so that they will be not + // formatted; in this case MakeAll() returns immediately. This is necessary + // for page changes during formatting. In addition, it is needed during + // the constructor call of the root object since otherwise the anchor will + // be formatted before the root is anchored correctly to a shell and + // because too much would be formatted as a result. + bool m_bLocked :1; + // true if the background of NotifyDTor needs to be notified at the end + // of a MakeAll() call. + bool m_bNotifyBack :1; + +protected: + // Pos, PrtArea or SSize have been invalidated - they will be evaluated + // again immediately because they have to be valid _at all time_. + // The invalidation is tracked here so that LayAction knows about it and + // can handle it properly. Exceptions prove the rule. + bool m_bInvalid :1; + + // true if the proposed height of an attribute is a minimal height + // (this means that the frame can grow higher if needed) + bool m_bMinHeight :1; + // true if the fly frame could not format position/size based on its + // attributes, e.g. because there was not enough space. + bool m_bHeightClipped :1; + bool m_bWidthClipped :1; + // If true then call only the format after adjusting the width (CheckClip); + // but the width will not be re-evaluated based on the attributes. + bool m_bFormatHeightOnly :1; + + bool m_bInCnt :1; ///< RndStdIds::FLY_AS_CHAR, anchored as character + bool m_bAtCnt :1; ///< RndStdIds::FLY_AT_PARA, anchored at paragraph + bool m_bLayout :1; ///< RndStdIds::FLY_AT_PAGE, RndStdIds::FLY_AT_FLY, at page or at frame + bool m_bAutoPosition :1; ///< RndStdIds::FLY_AT_CHAR, anchored at character + + friend class SwNoTextFrame; // is allowed to call NotifyBackground + + Point m_aContentPos; // content area's position relatively to Frame + bool m_bValidContentPos; + + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + void MakePrtArea( const SwBorderAttrs &rAttrs ); + void MakeContentPos( const SwBorderAttrs &rAttrs ); + + void Lock() { m_bLocked = true; } + void Unlock() { m_bLocked = false; } + + Size CalcRel( const SwFormatFrameSize &rSz ) const; + + SwFlyFrame( SwFlyFrameFormat*, SwFrame*, SwFrame *pAnchor ); + + virtual void DestroyImpl() override; + virtual ~SwFlyFrame() override; + + /** method to assure that anchored object is registered at the correct + page frame + */ + virtual void RegisterAtCorrectPage() override; + + virtual bool SetObjTop_( const SwTwips _nTop ) override; + virtual bool SetObjLeft_( const SwTwips _nLeft ) override; + + virtual SwRect GetObjBoundRect() const override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + virtual void SwClientNotify(const SwModify& rMod, const SfxHint& rHint) override; + + virtual const IDocumentDrawModelAccess& getIDocumentDrawModelAccess( ) override; + + SwTwips CalcContentHeight(const SwBorderAttrs *pAttrs, const SwTwips nMinHeight, const SwTwips nUL); + +public: + // #i26791# + + // get client information + virtual bool GetInfo( SfxPoolItem& ) const override; + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + virtual Size ChgSize( const Size& aNewSize ) override; + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, + SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override; + + virtual void CheckDirection( bool bVert ) override; + virtual void Cut() override; +#ifdef DBG_UTIL + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; +#endif + + SwTwips Shrink_( SwTwips, bool bTst ); + SwTwips Grow_ ( SwTwips, bool bTst ); + void Invalidate_( SwPageFrame const *pPage = nullptr ); + + bool FrameSizeChg( const SwFormatFrameSize & ); + + SwFlyFrame *GetPrevLink() const { return m_pPrevLink; } + SwFlyFrame *GetNextLink() const { return m_pNextLink; } + + static void ChainFrames( SwFlyFrame *pMaster, SwFlyFrame *pFollow ); + static void UnchainFrames( SwFlyFrame *pMaster, SwFlyFrame *pFollow ); + + SwFlyFrame *FindChainNeighbour( SwFrameFormat const &rFormat, SwFrame *pAnch = nullptr ); + + // #i26791# + const SwVirtFlyDrawObj* GetVirtDrawObj() const; + SwVirtFlyDrawObj *GetVirtDrawObj(); + void NotifyDrawObj(); + + void ChgRelPos( const Point &rAbsPos ); + bool IsInvalid() const { return m_bInvalid; } + void Invalidate() const { const_cast<SwFlyFrame*>(this)->m_bInvalid = true; } + void Validate() const { const_cast<SwFlyFrame*>(this)->m_bInvalid = false; } + + bool IsMinHeight() const { return m_bMinHeight; } + bool IsLocked() const { return m_bLocked; } + bool IsAutoPos() const { return m_bAutoPosition; } + bool IsFlyInContentFrame() const { return m_bInCnt; } + bool IsFlyFreeFrame() const { return m_bAtCnt || m_bLayout; } + bool IsFlyLayFrame() const { return m_bLayout; } + bool IsFlyAtContentFrame() const { return m_bAtCnt; } + + bool IsNotifyBack() const { return m_bNotifyBack; } + void SetNotifyBack() { m_bNotifyBack = true; } + void ResetNotifyBack() { m_bNotifyBack = false; } + + bool IsClipped() const { return m_bHeightClipped || m_bWidthClipped; } + bool IsHeightClipped() const { return m_bHeightClipped; } + + bool IsLowerOf( const SwLayoutFrame* pUpper ) const; + bool IsUpperOf( const SwFlyFrame& _rLower ) const + { + return _rLower.IsLowerOf( this ); + } + + SwFrame *FindLastLower(); + + // #i13147# - add parameter <_bForPaint> to avoid load of + // the graphic during paint. Default value: false + bool GetContour( tools::PolyPolygon& rContour, + const bool _bForPaint = false ) const; + + // Paint on this shell (consider Preview, print flag, etc. recursively)? + static bool IsPaint( SdrObject *pObj, const SwViewShell *pSh ); + + /** SwFlyFrame::IsBackgroundTransparent + + determines if background of fly frame has to be drawn transparently + + definition found in /core/layout/paintfrm.cxx + + @return true, if background color is transparent or an existing background + graphic is transparent. + */ + bool IsBackgroundTransparent() const; + + void Chain( SwFrame* _pAnchor ); + void Unchain(); + void InsertCnt(); + void DeleteCnt(); + void InsertColumns(); + + // #i26791# - pure virtual methods of base class <SwAnchoredObject> + virtual void MakeObjPos() override; + virtual void InvalidateObjPos() override; + + virtual SwFrameFormat& GetFrameFormat() override; + virtual const SwFrameFormat& GetFrameFormat() const override; + + virtual SwRect GetObjRect() const override; + + /** method to determine if a format on the Writer fly frame is possible + + #i28701# + refine 'IsFormatPossible'-conditions of method + <SwAnchoredObject::IsFormatPossible()> by: + format isn't possible, if Writer fly frame is locked resp. col-locked. + */ + virtual bool IsFormatPossible() const override; + static void GetAnchoredObjects( std::vector<SwAnchoredObject*>&, const SwFormat& rFormat ); + + // overwriting "SwFrameFormat *SwLayoutFrame::GetFormat" to provide the correct derived return type. + // (This is in order to skip on the otherwise necessary casting of the result to + // 'SwFlyFrameFormat *' after calls to this function. The casting is now done in this function.) + virtual const SwFlyFrameFormat *GetFormat() const override; + virtual SwFlyFrameFormat *GetFormat() override; + + virtual void dumpAsXml( xmlTextWriterPtr writer ) const override { SwLayoutFrame::dumpAsXml( writer ); }; + + virtual void Calc(vcl::RenderContext* pRenderContext) const override; + + const Point& ContentPos() const { return m_aContentPos; } + Point& ContentPos() { return m_aContentPos; } + + void InvalidateContentPos(); + + void SelectionHasChanged(SwFEShell* pShell); + bool IsShowUnfloatButton(SwWrtShell* pWrtSh) const; + + // For testing only (see uiwriter) + void ActiveUnfloatButton(SwWrtShell* pWrtSh); + +private: + void UpdateUnfloatButton(SwWrtShell* pWrtSh, bool bShow) const; + void PaintDecorators() const; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/flyfrms.hxx b/sw/source/core/inc/flyfrms.hxx new file mode 100644 index 000000000..4c6940c28 --- /dev/null +++ b/sw/source/core/inc/flyfrms.hxx @@ -0,0 +1,241 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FLYFRMS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FLYFRMS_HXX + +#include <sal/config.h> + +#include "flyfrm.hxx" + +class SwNoTextFrame; + +double getLocalFrameRotation_from_SwNoTextFrame(const SwNoTextFrame& rNoTextFrame); + +// Base class for those Flys that can "move freely" or better that are not +// bound in Content. +class SwFlyFreeFrame : public SwFlyFrame +{ +private: + // #i34753# - flag for at-page anchored Writer fly frames + // to prevent a positioning - call of method <MakeObjPos()> -, if Writer + // fly frame is already clipped during its format by the object formatter. + bool mbNoMakePos : 1; + + // #i37068# - flag to prevent move in method <CheckClip(..)> + bool mbNoMoveOnCheckClip : 1; + + SwRect maUnclippedFrame; + + // RotateFlyFrame3 add TransformableSwFrame + std::unique_ptr< TransformableSwFrame > mpTransformableSwFrame; + + void CheckClip( const SwFormatFrameSize &rSz ); //'Emergency' Clipping. + + /** determines, if direct environment of fly frame has 'auto' size + + #i17297# + start with anchor frame and search for a header, footer, row or fly frame + stopping at page frame. + return <true>, if such a frame is found and it has 'auto' size. + otherwise <false> is returned. + + @return boolean indicating, that direct environment has 'auto' size + */ + bool HasEnvironmentAutoSize() const; + + // RotateFlyFrame3 - Support for outer Frame of a SwGrfNode + // Only for local data extraction. To uniquely access information + // for local transformation, use getFrameArea(Print)Transformation + double getLocalFrameRotation() const; + +protected: + // #i28701# - new friend class <SwFlyNotify> for access to + // method <NotifyBackground> + friend class SwFlyNotify; + virtual void NotifyBackground( SwPageFrame *pPage, + const SwRect& rRect, PrepareHint eHint) override; + SwFlyFreeFrame( SwFlyFrameFormat*, SwFrame*, SwFrame *pAnchor ); + + virtual void DestroyImpl() override; + virtual ~SwFlyFreeFrame() override; + +public: + // #i28701# + + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + + // #i37068# - accessors for member <mbNoMoveOnCheckClip> + void SetNoMoveOnCheckClip( const bool _bNewNoMoveOnCheckClip ) + { + mbNoMoveOnCheckClip = _bNewNoMoveOnCheckClip; + } + bool IsNoMoveOnCheckClip() const + { + return mbNoMoveOnCheckClip; + } + // #i34753# - accessors for member <mbNoMakePos> + void SetNoMakePos( const bool _bNoMakePos ) + { + if ( IsFlyLayFrame() ) + { + mbNoMakePos = _bNoMakePos; + } + } + bool IsNoMakePos() const + { + if ( IsFlyLayFrame() ) + { + return mbNoMakePos; + } + else + { + return false; + } + } + + const SwRect& GetUnclippedFrame( ) const + { + if ( maUnclippedFrame.HasArea( ) ) + return maUnclippedFrame; + else + return getFrameArea(); + } + + /** method to determine, if a format on the Writer fly frame is possible + + #i28701# + refine 'IsFormatPossible'-conditions of method + <SwFlyFrame::IsFormatPossible()> by: + format isn't possible, if Writer fly frame isn't registered at a page frame + and its anchor frame isn't inside another Writer fly frame. + */ + virtual bool IsFormatPossible() const override; + + // RotateFlyFrame3 - Support for Transformations + bool isTransformableSwFrame() const { return bool(mpTransformableSwFrame); } + TransformableSwFrame* getTransformableSwFrame() { return mpTransformableSwFrame.get(); } + const TransformableSwFrame* getTransformableSwFrame() const { return mpTransformableSwFrame.get(); } + + // RotateFlyFrame3 - Support for AutoContour + bool supportsAutoContour() const; + + // RotateFlyFrame3 - Support for Transformations + virtual basegfx::B2DHomMatrix getFrameAreaTransformation() const override; + virtual basegfx::B2DHomMatrix getFramePrintAreaTransformation() const override; + + // RotateFlyFrame3 - Support for Transformations + virtual void transform_translate(const Point& rOffset) override; +}; + +// Flys that are bound to LayoutFrames and not to Content +class SwFlyLayFrame : public SwFlyFreeFrame +{ +public: + // #i28701# + + SwFlyLayFrame( SwFlyFrameFormat*, SwFrame*, SwFrame *pAnchor ); + +protected: + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; +}; + +// Flys that are bound to Content but not in Content +class SwFlyAtContentFrame : public SwFlyFreeFrame +{ +protected: + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + + // #i28701# + virtual bool InvalidationAllowed( const InvalidationType _nInvalid ) const override; + + /** method to assure that anchored object is registered at the correct + page frame + + #i28701# + */ + virtual void RegisterAtCorrectPage() override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + +public: + // #i28701# + + SwFlyAtContentFrame( SwFlyFrameFormat*, SwFrame*, SwFrame *pAnchor ); + + void SetAbsPos( const Point &rNew ); + + // #i26791# + virtual void MakeObjPos() override; + + /** method to determine, if a format on the Writer fly frame is possible + + #i28701# + refine 'IsFormatPossible'-conditions of method + <SwFlyFreeFrame::IsFormatPossible()> by: + format isn't possible, if method <MakeAll()> is already in progress. + */ + virtual bool IsFormatPossible() const override; +}; + +// Flys that are bound to a character in Content +class SwFlyInContentFrame : public SwFlyFrame +{ + Point aRef; // relative to this point AbsPos is being calculated + + virtual void DestroyImpl() override; + virtual ~SwFlyInContentFrame() override; + +protected: + virtual void NotifyBackground( SwPageFrame *pPage, + const SwRect& rRect, PrepareHint eHint) override; + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + +public: + // #i28701# + + SwFlyInContentFrame( SwFlyFrameFormat*, SwFrame*, SwFrame *pAnchor ); + + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + + void SetRefPoint( const Point& rPoint, const Point &rRelAttr, + const Point &rRelPos ); + const Point &GetRefPoint() const { return aRef; } + Point const & GetRelPos() const; + + // (26.11.93, see tabfrm.hxx, but might also be valid for others) + // For creation of a Fly after a FlyCnt was created _and_ inserted. + // Must be called by creator because can be pasted only after creation. + // Sometimes the page for registering the Flys is not visible until then + // as well. + void RegistFlys(); + + //see layact.cxx + void AddRefOfst( long nOfst ) { aRef.AdjustY( nOfst ); } + + // #i26791# + virtual void MakeObjPos() override; + + // invalidate anchor frame on invalidation of the position, because the + // position is calculated during the format of the anchor frame + virtual void ActionOnInvalidation( const InvalidationType _nInvalid ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/fntcache.hxx b/sw/source/core/inc/fntcache.hxx new file mode 100644 index 000000000..f908ed62e --- /dev/null +++ b/sw/source/core/inc/fntcache.hxx @@ -0,0 +1,164 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FNTCACHE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FNTCACHE_HXX + +#include <sal/config.h> + +#include <cstdint> +#include <map> + +#include <vcl/font.hxx> +#include <vcl/vclptr.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/outdev.hxx> +#include "swcache.hxx" +#include "TextFrameIndex.hxx" + +class FontMetric; +class SwFntObj; +class SwDrawTextInfo; +class SwViewShell; +class SwSubFont; +class MapMode; + +class SwFntCache : public SwCache +{ +public: + SwFntCache() : SwCache(50 +#ifdef DBG_UTIL + , OString(RTL_CONSTASCII_STRINGPARAM("Global Font-Cache pFntCache")) +#endif + ) {} + + inline SwFntObj *First( ); + static inline SwFntObj *Next( SwFntObj *pFntObj); + void Flush(); +}; + +/// Clears the pre-calculated text glyphs in all SwFntObj instances. +void SwClearFntCacheTextGlyphs(); + +// Font cache, global variable, created/destroyed in txtinit.cxx +extern SwFntCache *pFntCache; +extern SwFntObj *pLastFont; + +/** + * Defines a substring on a given output device, to be used as an std::map<> + * key. + */ +struct SwTextGlyphsKey +{ + VclPtr<OutputDevice> m_pOutputDevice; + OUString m_aText; + sal_Int32 m_nIndex; + sal_Int32 m_nLength; + +}; +bool operator<(const SwTextGlyphsKey& l, const SwTextGlyphsKey& r); + +class SwFntObj : public SwCacheObj +{ + friend class SwFntAccess; + friend void InitCore(); + friend void FinitCore(); + + vcl::Font m_aFont; + vcl::Font *m_pScrFont; + vcl::Font *m_pPrtFont; + VclPtr<OutputDevice> m_pPrinter; + sal_uInt16 m_nGuessedLeading; + sal_uInt16 m_nExtLeading; + sal_uInt16 m_nScrAscent; + sal_uInt16 m_nPrtAscent; + sal_uInt16 m_nScrHeight; + sal_uInt16 m_nPrtHeight; + sal_uInt16 m_nPropWidth; + sal_uInt16 m_nZoom; + bool m_bSymbol : 1; + bool m_bPaintBlank : 1; + + /// Cache of already calculated layout glyphs. + std::map<SwTextGlyphsKey, SalLayoutGlyphs> m_aTextGlyphs; + + static long nPixWidth; + static MapMode *pPixMap; + +public: + SwFntObj( const SwSubFont &rFont, std::uintptr_t nFontCacheId, + SwViewShell const *pSh ); + + virtual ~SwFntObj() override; + + vcl::Font *GetScrFont() { return m_pScrFont; } + vcl::Font& GetFont() { return m_aFont; } + const vcl::Font& GetFont() const { return m_aFont; } + + sal_uInt16 GetGuessedLeading() const { return m_nGuessedLeading; } + sal_uInt16 GetExternalLeading() const { return m_nExtLeading; } + + sal_uInt16 GetFontAscent( const SwViewShell *pSh, const OutputDevice& rOut ); + sal_uInt16 GetFontHeight( const SwViewShell *pSh, const OutputDevice& rOut ); + sal_uInt16 GetFontLeading( const SwViewShell *pSh, const OutputDevice& rOut ); + + void GuessLeading( const SwViewShell& rSh, const FontMetric& rMet ); + + void SetDevFont( const SwViewShell *pSh, OutputDevice& rOut ); + OutputDevice* GetPrt() const { return m_pPrinter; } + sal_uInt16 GetZoom() const { return m_nZoom; } + sal_uInt16 GetPropWidth() const { return m_nPropWidth; } + bool IsSymbol() const { return m_bSymbol; } + std::map<SwTextGlyphsKey, SalLayoutGlyphs>& GetTextGlyphs() { return m_aTextGlyphs; } + + void DrawText( SwDrawTextInfo &rInf ); + /// determine the TextSize (of the printer) + Size GetTextSize( SwDrawTextInfo &rInf ); + TextFrameIndex GetModelPositionForViewPoint(SwDrawTextInfo &rInf); + + void CreateScrFont( const SwViewShell& rSh, const OutputDevice& rOut ); + void CreatePrtFont( const OutputDevice& rOut ); +}; + +SwFntObj *SwFntCache::First( ) +{ + return static_cast<SwFntObj *>(SwCache::First()); +} + +SwFntObj *SwFntCache::Next( SwFntObj *pFntObj) +{ + return static_cast<SwFntObj *>(SwCache::Next( pFntObj )); +} + +class SwFntAccess : public SwCacheAccess +{ + SwViewShell const *m_pShell; +protected: + virtual SwCacheObj *NewObj( ) override; + +public: + SwFntAccess( const void*& rnFontCacheId, sal_uInt16 &rIndex, const void *pOwner, + SwViewShell const *pShell, + bool bCheck = false ); + SwFntObj* Get() { return static_cast<SwFntObj*>( SwCacheAccess::Get() ); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/fntcap.hxx b/sw/source/core/inc/fntcap.hxx new file mode 100644 index 000000000..0b1cee67c --- /dev/null +++ b/sw/source/core/inc/fntcap.hxx @@ -0,0 +1,37 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FNTCAP_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FNTCAP_HXX + +#include <rtl/ustring.hxx> +#include "TextFrameIndex.hxx" + +class SwFont; + +TextFrameIndex sw_CalcCaseMap( const SwFont& rFnt, + const OUString& rOrigString, + TextFrameIndex nOfst, + TextFrameIndex nLen, + TextFrameIndex nIdx ); + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/frame.hxx b/sw/source/core/inc/frame.hxx new file mode 100644 index 000000000..429746737 --- /dev/null +++ b/sw/source/core/inc/frame.hxx @@ -0,0 +1,1417 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FRAME_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FRAME_HXX + +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <editeng/borderline.hxx> +#include <svl/poolitem.hxx> +#include <swtypes.hxx> +#include <swrect.hxx> +#include <calbck.hxx> +#include <svl/SfxBroadcaster.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <com/sun/star/style/TabStop.hpp> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <vcl/outdev.hxx> + +#include <memory> + +namespace drawinglayer::processor2d { class BaseProcessor2D; } + +class SwLayoutFrame; +class SwRootFrame; +class SwPageFrame; +class SwBodyFrame; +class SwFlyFrame; +class SwSectionFrame; +class SwFootnoteFrame; +class SwFootnoteBossFrame; +class SwTabFrame; +class SwRowFrame; +class SwContentFrame; +class SwAttrSet; +class Color; +class SwBorderAttrs; +class SwCache; +class SvxBrushItem; +class SvxFormatBreakItem; +class SwFormatPageDesc; +class SwSelectionList; +struct SwPosition; +struct SwCursorMoveState; +class SwFormat; +class SwPrintData; +class SwSortedObjs; +class SwAnchoredObject; +enum class SvxFrameDirection; +class IDocumentDrawModelAccess; + +// Each FrameType is represented here as a bit. +// The bits must be set in a way that it can be determined with masking of +// which kind of FrameType an instance is _and_ from what classes it was derived. +// Each frame has in its base class a member that must be set by the +// constructors accordingly. +enum class SwFrameType +{ + None = 0x0000, + Root = 0x0001, + Page = 0x0002, + Column = 0x0004, + Header = 0x0008, + Footer = 0x0010, + FtnCont = 0x0020, + Ftn = 0x0040, + Body = 0x0080, + Fly = 0x0100, + Section = 0x0200, +// UNUSED 0x0400 + Tab = 0x0800, + Row = 0x1000, + Cell = 0x2000, + Txt = 0x4000, + NoTxt = 0x8000, +}; + +namespace o3tl +{ + template<> struct typed_flags<SwFrameType> : is_typed_flags<SwFrameType, 0xfbff> {}; +}; + +// for internal use some common combinations +#define FRM_LAYOUT SwFrameType(0x3bFF) +#define FRM_ALL SwFrameType(0xfbff) +#define FRM_CNTNT (SwFrameType::Txt | SwFrameType::NoTxt) +#define FRM_FTNBOSS (SwFrameType::Page | SwFrameType::Column) +#define FRM_ACCESSIBLE (SwFrameType::Root | SwFrameType::Page | SwFrameType::Header | SwFrameType::Footer | SwFrameType::Ftn | SwFrameType::Fly | SwFrameType::Tab | SwFrameType::Cell | SwFrameType::Txt) +#define FRM_NEIGHBOUR (SwFrameType::Column | SwFrameType::Cell) +#define FRM_NOTE_VERT (SwFrameType::FtnCont | SwFrameType::Ftn | SwFrameType::Section | SwFrameType::Tab | SwFrameType::Row | SwFrameType::Cell | SwFrameType::Txt) +#define FRM_HEADFOOT (SwFrameType::Header | SwFrameType::Footer) +#define FRM_BODYFTNC (SwFrameType::FtnCont | SwFrameType::Body) + +// for GetNextLeaf/GetPrevLeaf. +enum MakePageType +{ + MAKEPAGE_NONE, // do not create page/footnote + MAKEPAGE_APPEND, // only append page if needed + MAKEPAGE_INSERT, // add or append page if needed + MAKEPAGE_FTN, // add footnote if needed + MAKEPAGE_NOSECTION // Don't create section frames +}; + +namespace drawinglayer::attribute { + class SdrAllFillAttributesHelper; + typedef std::shared_ptr< SdrAllFillAttributesHelper > SdrAllFillAttributesHelperPtr; +} + +/// Helper class to isolate geometry-defining members of SwFrame +/// and to control their accesses. Moved to own class to have no +/// hidden accesses to 'private' members anymore. +/// +/// Added most important flags about the state of this geometric +/// information and if it is valid or not +class SAL_DLLPUBLIC_RTTI SwFrameAreaDefinition +{ +private: + friend void FriendHackInvalidateRowFrame(SwFrameAreaDefinition &); + + // The absolute position and size of the SwFrame in the document. + // This values are set by the layouter implementations + SwRect maFrameArea; + + // The 'inner' Frame Area defined by a SwRect relative to FrameArea: + // When identical to FrameArea, Pos() will be (0, 0) and Size identical. + SwRect maFramePrintArea; + + // bitfield + bool mbFrameAreaPositionValid : 1; + bool mbFrameAreaSizeValid : 1; + bool mbFramePrintAreaValid : 1; + + // #i65250# + // frame ID is now in general available - used for layout loop control + static sal_uInt32 mnLastFrameId; + const sal_uInt32 mnFrameId; + +protected: + // write access to mb*Valid flags + void setFrameAreaPositionValid(bool bNew); + void setFrameAreaSizeValid(bool bNew); + void setFramePrintAreaValid(bool bNew); + +public: + SwFrameAreaDefinition(); + virtual ~SwFrameAreaDefinition(); + + // read access to mb*Valid flags + bool isFrameAreaPositionValid() const { return mbFrameAreaPositionValid; } + bool isFrameAreaSizeValid() const { return mbFrameAreaSizeValid; } + bool isFramePrintAreaValid() const { return mbFramePrintAreaValid; } + + // syntactic sugar: test whole FrameAreaDefinition + bool isFrameAreaDefinitionValid() const { return isFrameAreaPositionValid() && isFrameAreaSizeValid() && isFramePrintAreaValid(); } + + // #i65250# + sal_uInt32 GetFrameId() const { return mnFrameId; } + + // read accesses to FrameArea definitions - only const access allowed. + // Do *not* const_cast results, it is necessary to track changes. use + // the below offered WriteAccess helper classes instead + const SwRect& getFrameArea() const { return maFrameArea; } + const SwRect& getFramePrintArea() const { return maFramePrintArea; } + + // helper class(es) for FrameArea manipulation. These + // have to be used to apply changes to FrameAreas. They hold a copy of the + // SwRect for manipulation. It gets written back at destruction. Thus, this + // mechanism depends on scope usage, take care. It prevents errors using + // different instances of SwFrame in get/set methods which is more safe + class FrameAreaWriteAccess : public SwRect + { + private: + SwFrameAreaDefinition& mrTarget; + + FrameAreaWriteAccess(const FrameAreaWriteAccess&) = delete; + FrameAreaWriteAccess& operator=(const FrameAreaWriteAccess&) = delete; + + public: + FrameAreaWriteAccess(SwFrameAreaDefinition& rTarget) : SwRect(rTarget.getFrameArea()), mrTarget(rTarget) {} + ~FrameAreaWriteAccess(); + void setSwRect(const SwRect& rNew) { *reinterpret_cast< SwRect* >(this) = rNew; } + }; + + // same helper for FramePrintArea + class FramePrintAreaWriteAccess : public SwRect + { + private: + SwFrameAreaDefinition& mrTarget; + + FramePrintAreaWriteAccess(const FramePrintAreaWriteAccess&) = delete; + FramePrintAreaWriteAccess& operator=(const FramePrintAreaWriteAccess&) = delete; + + public: + FramePrintAreaWriteAccess(SwFrameAreaDefinition& rTarget) : SwRect(rTarget.getFramePrintArea()), mrTarget(rTarget) {} + ~FramePrintAreaWriteAccess(); + void setSwRect(const SwRect& rNew) { *reinterpret_cast< SwRect* >(this) = rNew; } + }; + + // RotateFlyFrame3 - Support for Transformations + // Hand out the Transformations for the current FrameAreaDefinition + // for the FrameArea and FramePrintArea. + // FramePrintArea is not relative to FrameArea in this + // transformation representation (to make it easier to use and understand). + // There is no 'set' method since SwFrame is a layout object. For + // some cases rotation will be included (used for SwGrfNode in inner + // SwFrame of a SwFlyFrame) + virtual basegfx::B2DHomMatrix getFrameAreaTransformation() const; + virtual basegfx::B2DHomMatrix getFramePrintAreaTransformation() const; + + // RotateFlyFrame3 - Support for Transformations + // Modify current transformations by applying given translation + virtual void transform_translate(const Point& rOffset); +}; + +/// RotateFlyFrame3: Helper class when you want to make your SwFrame derivate +/// transformable. It provides some tooling to do so. To use, add as member +/// (see e.g. SwFlyFreeFrame which uses 'std::unique_ptr< TransformableSwFrame >') +class TransformableSwFrame +{ +private: + // The SwFrameAreaDefinition to work on + SwFrameAreaDefinition& mrSwFrameAreaDefinition; + + // FrameAreaTransformation and FramePrintAreaTransformation + // !identity when needed (translate/scale is used (e.g. rotation)) + basegfx::B2DHomMatrix maFrameAreaTransformation; + basegfx::B2DHomMatrix maFramePrintAreaTransformation; + +public: + TransformableSwFrame(SwFrameAreaDefinition& rSwFrameAreaDefinition) + : mrSwFrameAreaDefinition(rSwFrameAreaDefinition), + maFrameAreaTransformation(), + maFramePrintAreaTransformation() + { + } + + // get SwFrameArea in transformation form + const basegfx::B2DHomMatrix& getLocalFrameAreaTransformation() const + { + return maFrameAreaTransformation; + } + + // get SwFramePrintArea in transformation form + const basegfx::B2DHomMatrix& getLocalFramePrintAreaTransformation() const + { + return maFramePrintAreaTransformation; + } + + // Helpers to re-create the untransformed SwRect(s) originally + // in the SwFrameAreaDefinition, based on the current Transformations. + SwRect getUntransformedFrameArea() const; + SwRect getUntransformedFramePrintArea() const; + + // Helper method to re-create FrameAreaTransformations based on the + // current FrameAreaDefinition transformed by given rotation and Center + void createFrameAreaTransformations( + double fRotation, + const basegfx::B2DPoint& rCenter); + + // Tooling method to reset the SwRect(s) in the current + // SwFrameAreaDefinition which are already adapted to + // Transformation back to the untransformed state, using + // the getUntransformedFrame*Area calls above when needed. + // Only the SwRect(s) are changed back, not the transformations. + void restoreFrameAreas(); + + // Re-Creates the SwRect(s) as BoundAreas based on the current + // set Transformations. + void adaptFrameAreasToTransformations(); + + // Modify current definitions by applying the given transformation + void transform(const basegfx::B2DHomMatrix& aTransform); +}; + +/** + * Base class of the Writer layout elements. + * + * This includes not only fly frames, but everything down to the paragraph + * level: pages, headers, footers, etc. (Inside a paragraph SwLinePortion + * instances are used.) + */ +class SW_DLLPUBLIC SwFrame : public SwFrameAreaDefinition, public SwClient, public SfxBroadcaster +{ + // the hidden Frame + friend class SwFlowFrame; + friend class SwLayoutFrame; + friend class SwLooping; + friend class SwDeletionChecker; // for GetDep() + + // voids lower during creation of a column + friend SwFrame *SaveContent( SwLayoutFrame *, SwFrame* pStart ); + friend void RestoreContent( SwFrame *, SwLayoutFrame *, SwFrame *pSibling ); + + // for validating a mistakenly invalidated one in SwContentFrame::MakeAll + friend void ValidateSz( SwFrame *pFrame ); + // implemented in text/txtftn.cxx, prevents Footnote oscillation + friend void ValidateText( SwFrame *pFrame ); + + friend void MakeNxt( SwFrame *pFrame, SwFrame *pNxt ); + + // cache for (border) attributes + static SwCache *mpCache; + + SwRootFrame *mpRoot; + SwLayoutFrame *mpUpper; + SwFrame *mpNext; + SwFrame *mpPrev; + + // sw_redlinehide: hide these dangerous SwClient functions + using SwClient::GetRegisteredInNonConst; + using SwClient::GetRegisteredIn; + + SwFrame *FindNext_(); + SwFrame *FindPrev_(); + + /** method to determine next content frame in the same environment + for a flow frame (content frame, table frame, section frame) + + #i27138# - adding documentation: + Travelling downwards through the layout to determine the next content + frame in the same environment. There are several environments in a + document, which form a closed context regarding this function. These + environments are: + - Each page header + - Each page footer + - Each unlinked fly frame + - Each group of linked fly frames + - All footnotes + - All document body frames + #i27138# - adding parameter <_bInSameFootnote> + Its default value is <false>. If its value is <true>, the environment + 'All footnotes' is no longer treated. Instead each footnote is treated + as an own environment. + + @param _bInSameFootnote + input parameter - boolean indicating, that the found next content + frame has to be in the same footnote frame. This parameter is only + relevant for flow frames in footnotes. + + @return SwContentFrame* + pointer to the found next content frame. It's NULL, if none exists. + */ + SwContentFrame* FindNextCnt_( const bool _bInSameFootnote ); + + /** method to determine previous content frame in the same environment + for a flow frame (content frame, table frame, section frame) + + #i27138# + Travelling upwards through the layout to determine the previous content + frame in the same environment. There are several environments in a + document, which form a closed context regarding this function. These + environments are: + - Each page header + - Each page footer + - Each unlinked fly frame + - Each group of linked fly frames + - All footnotes + - All document body frames + #i27138# - adding parameter <_bInSameFootnote> + Its default value is <false>. If its value is <true>, the environment + 'All footnotes' is no longer treated. Instead each footnote is treated + as an own environment. + + The found previous content frame has to be in the same footnote frame. This is only + relevant for flow frames in footnotes. + + @return SwContentFrame* + pointer to the found previous content frame. It's NULL, if none exists. + */ + SwContentFrame* FindPrevCnt_(); + + void UpdateAttrFrame( const SfxPoolItem*, const SfxPoolItem*, sal_uInt8 & ); + SwFrame* GetIndNext_(); + void SetDirFlags( bool bVert ); + + const SwLayoutFrame* ImplGetNextLayoutLeaf( bool bFwd ) const; + + SwPageFrame* ImplFindPageFrame(); + +protected: + std::unique_ptr<SwSortedObjs> m_pDrawObjs; // draw objects, can be null + SwFrameType mnFrameType; //Who am I? + + bool mbInDtor : 1; + bool mbInvalidR2L : 1; + bool mbDerivedR2L : 1; + bool mbRightToLeft : 1; + bool mbInvalidVert : 1; + bool mbDerivedVert : 1; + bool mbVertical : 1; + + bool mbVertLR : 1; + bool mbVertLRBT : 1; + + bool mbValidLineNum : 1; + bool mbFixSize : 1; + + // if true, frame will be painted completely even content was changed + // only partially. For ContentFrames a border (from Action) will exclusively + // painted if <mbCompletePaint> is true. + bool mbCompletePaint : 1; + + bool mbRetouche : 1; // frame is responsible for retouching + + bool mbInfInvalid : 1; // InfoFlags are invalid + bool mbInfBody : 1; // Frame is in document body + bool mbInfTab : 1; // Frame is in a table + bool mbInfFly : 1; // Frame is in a Fly + bool mbInfFootnote : 1; // Frame is in a footnote + bool mbInfSct : 1; // Frame is in a section + bool mbColLocked : 1; // lock Grow/Shrink for column-wise section + // or fly frames, will be set in Format + bool m_isInDestroy : 1; + bool mbForbidDelete : 1; + + void ColLock() { mbColLocked = true; } + void ColUnlock() { mbColLocked = false; } + + virtual void DestroyImpl(); + virtual ~SwFrame() override; + + // Only used by SwRootFrame Ctor to get 'this' into mpRoot... + void setRootFrame( SwRootFrame* pRoot ) { mpRoot = pRoot; } + + SwPageFrame *InsertPage( SwPageFrame *pSibling, bool bFootnote ); + void PrepareMake(vcl::RenderContext* pRenderContext); + void OptPrepareMake(); + virtual void MakePos(); + // Format next frame of table frame to assure keeping attributes. + // In case of nested tables method <SwFrame::MakeAll()> is called to + // avoid formatting of superior table frame. + friend SwFrame* sw_FormatNextContentForKeep( SwTabFrame* pTabFrame ); + + virtual void MakeAll(vcl::RenderContext* pRenderContext) = 0; + // adjust frames of a page + SwTwips AdjustNeighbourhood( SwTwips nDiff, bool bTst = false ); + + // change only frame size not the size of PrtArea + virtual SwTwips ShrinkFrame( SwTwips, bool bTst = false, bool bInfo = false ) = 0; + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) = 0; + + /// use these so we can grep for SwFrame's GetRegisteredIn accesses + /// beware that SwTextFrame may return sw::WriterMultiListener + SwModify *GetDep() { return GetRegisteredInNonConst(); } + const SwModify *GetDep() const { return GetRegisteredIn(); } + + SwFrame( SwModify*, SwFrame* ); + + void CheckDir( SvxFrameDirection nDir, bool bVert, bool bOnlyBiDi, bool bBrowse ); + + /** enumeration for the different invalidations + #i28701# + */ + enum InvalidationType + { + INVALID_SIZE, INVALID_PRTAREA, INVALID_POS, INVALID_LINENUM, INVALID_ALL + }; + + /** method to determine, if an invalidation is allowed. + #i28701 + */ + virtual bool InvalidationAllowed( const InvalidationType _nInvalid ) const; + + /** method to perform additional actions on an invalidation + + #i28701# + Method has *only* to contain actions, which has to be performed on + *every* assignment of the corresponding flag to <false>. + */ + virtual void ActionOnInvalidation( const InvalidationType _nInvalid ); + + // draw shadow and borders + void PaintShadow( const SwRect&, SwRect&, const SwBorderAttrs& ) const; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + + virtual const IDocumentDrawModelAccess& getIDocumentDrawModelAccess( ); + +public: + virtual css::uno::Sequence< css::style::TabStop > GetTabStopInfo( SwTwips ) + { + return css::uno::Sequence< css::style::TabStop >(); + } + + + SwFrameType GetType() const { return mnFrameType; } + + static SwCache &GetCache() { return *mpCache; } + static SwCache *GetCachePtr() { return mpCache; } + static void SetCache( SwCache *pNew ) { mpCache = pNew; } + + // change PrtArea size and FrameSize + SwTwips Shrink( SwTwips, bool bTst = false, bool bInfo = false ); + SwTwips Grow ( SwTwips, bool bTst = false, bool bInfo = false ); + + // different methods for inserting in layout tree (for performance reasons) + + // insert before pBehind or at the end of the chain below mpUpper + void InsertBefore( SwLayoutFrame* pParent, SwFrame* pBehind ); + // insert after pBefore or at the beginning of the chain below mpUpper + void InsertBehind( SwLayoutFrame *pParent, SwFrame *pBefore ); + // insert before pBehind or at the end of the chain while considering + // the siblings of pSct + bool InsertGroupBefore( SwFrame* pParent, SwFrame* pWhere, SwFrame* pSct ); + void RemoveFromLayout(); + + // For internal use only - who ignores this will be put in a sack and has + // to stay there for two days + // Does special treatment for Get_[Next|Prev]Leaf() (for tables). + SwLayoutFrame *GetLeaf( MakePageType eMakePage, bool bFwd ); + SwLayoutFrame *GetNextLeaf ( MakePageType eMakePage ); + SwLayoutFrame *GetNextFootnoteLeaf( MakePageType eMakePage ); + SwLayoutFrame *GetNextSctLeaf( MakePageType eMakePage ); + SwLayoutFrame *GetNextCellLeaf(); + SwLayoutFrame *GetPrevLeaf (); + SwLayoutFrame *GetPrevFootnoteLeaf( MakePageType eMakeFootnote ); + SwLayoutFrame *GetPrevSctLeaf(); + SwLayoutFrame *GetPrevCellLeaf(); + const SwLayoutFrame *GetLeaf ( MakePageType eMakePage, bool bFwd, + const SwFrame *pAnch ) const; + + bool WrongPageDesc( SwPageFrame* pNew ); + + //#i28701# - new methods to append/remove drawing objects + void AppendDrawObj( SwAnchoredObject& _rNewObj ); + void RemoveDrawObj( SwAnchoredObject& _rToRemoveObj ); + + // work with chain of FlyFrames + void AppendFly( SwFlyFrame *pNew ); + void RemoveFly( SwFlyFrame *pToRemove ); + const SwSortedObjs *GetDrawObjs() const { return m_pDrawObjs.get(); } + SwSortedObjs *GetDrawObjs() { return m_pDrawObjs.get(); } + // #i28701# - change purpose of method and adjust its name + void InvalidateObjs( const bool _bNoInvaOfAsCharAnchoredObjs = true ); + + virtual void PaintSwFrameShadowAndBorder( + const SwRect&, + const SwPageFrame* pPage, + const SwBorderAttrs&) const; + void PaintBaBo( const SwRect&, const SwPageFrame *pPage, + const bool bOnlyTextBackground = false) const; + void PaintSwFrameBackground( const SwRect&, const SwPageFrame *pPage, + const SwBorderAttrs &, + const bool bLowerMode = false, + const bool bLowerBorder = false, + const bool bOnlyTextBackground = false ) const; + void PaintBorderLine( const SwRect&, const SwRect&, const SwPageFrame*, + const Color *pColor, + const SvxBorderLineStyle = SvxBorderLineStyle::SOLID ) const; + + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> CreateProcessor2D( ) const; + void ProcessPrimitives( const drawinglayer::primitive2d::Primitive2DContainer& rSequence ) const; + + // retouch, not in the area of the given Rect! + void Retouch( const SwPageFrame *pPage, const SwRect &rRect ) const; + + bool GetBackgroundBrush( + drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes, + const SvxBrushItem*& rpBrush, + const Color*& rpColor, + SwRect &rOrigRect, + bool bLowerMode, + bool bConsiderTextBox ) const; + + inline void SetCompletePaint() const; + inline void ResetCompletePaint() const; + bool IsCompletePaint() const { return mbCompletePaint; } + + inline void SetRetouche() const; + inline void ResetRetouche() const; + bool IsRetouche() const { return mbRetouche; } + + void SetInfFlags(); + void InvalidateInfFlags() { mbInfInvalid = true; } + inline bool IsInDocBody() const; // use InfoFlags, determine flags + inline bool IsInFootnote() const; // if necessary + inline bool IsInTab() const; + inline bool IsInFly() const; + inline bool IsInSct() const; + + // If frame is inside a split table row, this function returns + // the corresponding row frame in the follow table. + const SwRowFrame* IsInSplitTableRow() const; + + // If frame is inside a follow flow row, this function returns + // the corresponding row frame master table + const SwRowFrame* IsInFollowFlowRow() const; + + bool IsInBalancedSection() const; + + inline bool IsVertical() const; + inline bool IsVertLR() const; + inline bool IsVertLRBT() const; + + void SetDerivedVert( bool bNew ){ mbDerivedVert = bNew; } + void SetInvalidVert( bool bNew) { mbInvalidVert = bNew; } + inline bool IsRightToLeft() const; + void SetDerivedR2L( bool bNew ) { mbDerivedR2L = bNew; } + + void CheckDirChange(); + // returns upper left frame position for LTR and + // upper right frame position for Asian / RTL frames + Point GetFrameAnchorPos( bool bIgnoreFlysAnchoredAtThisFrame ) const; + + /** determine, if frame is moveable in given environment + + method replaced 'old' method <bool IsMoveable() const>. + Determines, if frame is moveable in given environment. if no environment + is given (parameter _pLayoutFrame == 0), the movability in the actual + environment (<GetUpper()) is checked. + + @param _pLayoutFrame + input parameter - given environment (layout frame), in which the movability + will be checked. If not set ( == 0 ), actual environment is taken. + + @return boolean, indicating, if frame is moveable in given environment + */ + bool IsMoveable( const SwLayoutFrame* _pLayoutFrame = nullptr ) const; + + // Is it permitted for the (Text)Frame to add a footnote in the current + // environment (not e.g. for repeating table headlines) + bool IsFootnoteAllowed() const; + + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ); + + virtual void CheckDirection( bool bVert ); + + void ReinitializeFrameSizeAttrFlags(); + + /// WARNING: this may not return correct RES_PAGEDESC/RES_BREAK items for + /// SwTextFrame, use GetBreakItem()/GetPageDescItem() instead + const SwAttrSet *GetAttrSet() const; + virtual const SvxFormatBreakItem& GetBreakItem() const; + virtual const SwFormatPageDesc& GetPageDescItem() const; + + bool HasFixSize() const { return mbFixSize; } + + // check all pages (starting from the given) and correct them if needed + static void CheckPageDescs( SwPageFrame *pStart, bool bNotifyFields = true, SwPageFrame** ppPrev = nullptr); + + // might return 0, with and without const + SwFrame *GetNext() { return mpNext; } + SwFrame *GetPrev() { return mpPrev; } + SwLayoutFrame *GetUpper() { return mpUpper; } + SwRootFrame *getRootFrame(){ return mpRoot; } + SwPageFrame *FindPageFrame() { return IsPageFrame() ? reinterpret_cast<SwPageFrame*>(this) : ImplFindPageFrame(); } + SwFrame *FindColFrame(); + SwRowFrame *FindRowFrame(); + SwFootnoteBossFrame *FindFootnoteBossFrame( bool bFootnotes = false ); + SwTabFrame *ImplFindTabFrame(); + SwFootnoteFrame *ImplFindFootnoteFrame(); + SwFlyFrame *ImplFindFlyFrame(); + SwSectionFrame *ImplFindSctFrame(); + const SwBodyFrame *ImplFindBodyFrame() const; + SwFrame *FindFooterOrHeader(); + SwFrame *GetLower(); + const SwFrame *GetNext() const { return mpNext; } + const SwFrame *GetPrev() const { return mpPrev; } + const SwLayoutFrame *GetUpper() const { return mpUpper; } + const SwRootFrame *getRootFrame() const { return mpRoot; } + inline SwTabFrame *FindTabFrame(); + inline SwFootnoteFrame *FindFootnoteFrame(); + inline SwFlyFrame *FindFlyFrame(); + inline SwSectionFrame *FindSctFrame(); + inline SwFrame *FindNext(); + // #i27138# - add parameter <_bInSameFootnote> + SwContentFrame* FindNextCnt( const bool _bInSameFootnote = false ); + inline SwFrame *FindPrev(); + inline const SwPageFrame *FindPageFrame() const; + inline const SwFootnoteBossFrame *FindFootnoteBossFrame( bool bFootnote = false ) const; + inline const SwFrame *FindColFrame() const; + inline const SwFrame *FindFooterOrHeader() const; + inline const SwTabFrame *FindTabFrame() const; + inline const SwFootnoteFrame *FindFootnoteFrame() const; + inline const SwFlyFrame *FindFlyFrame() const; + inline const SwSectionFrame *FindSctFrame() const; + inline const SwBodyFrame *FindBodyFrame() const; + inline const SwFrame *FindNext() const; + // #i27138# - add parameter <_bInSameFootnote> + const SwContentFrame* FindNextCnt( const bool _bInSameFootnote = false ) const; + inline const SwFrame *FindPrev() const; + const SwFrame *GetLower() const; + + SwContentFrame* FindPrevCnt(); + + const SwContentFrame* FindPrevCnt() const; + + // #i79774# + SwFrame* GetIndPrev_() const; + SwFrame* GetIndPrev() const + { return ( mpPrev || !IsInSct() ) ? mpPrev : GetIndPrev_(); } + + SwFrame* GetIndNext() + { return ( mpNext || !IsInSct() ) ? mpNext : GetIndNext_(); } + const SwFrame* GetIndNext() const { return const_cast<SwFrame*>(this)->GetIndNext(); } + + sal_uInt16 GetPhyPageNum() const; // page number without offset + sal_uInt16 GetVirtPageNum() const; // page number with offset + bool OnRightPage() const { return 0 != GetPhyPageNum() % 2; }; + bool WannaRightPage() const; + bool OnFirstPage() const; + + inline const SwLayoutFrame *GetPrevLayoutLeaf() const; + inline const SwLayoutFrame *GetNextLayoutLeaf() const; + inline SwLayoutFrame *GetPrevLayoutLeaf(); + inline SwLayoutFrame *GetNextLayoutLeaf(); + + virtual void Calc(vcl::RenderContext* pRenderContext) const; // here might be "formatted" + inline void OptCalc() const; // here we assume (for optimization) that + // the predecessors are already formatted + Point GetRelPos() const; + + // PaintArea is the area where content might be displayed. + // The margin of a page or the space between columns belongs to it. + SwRect GetPaintArea() const; + + // UnionFrame is the union of Frame- and PrtArea, normally identical + // to the FrameArea except in case of negative Prt margins. + SwRect UnionFrame( bool bBorder = false ) const; + + virtual Size ChgSize( const Size& aNewSize ); + + virtual void Cut() = 0; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) = 0; + + void ValidateLineNum() { mbValidLineNum = true; } + + bool GetValidLineNumFlag()const { return mbValidLineNum; } + + // Only invalidate Frame + // #i28701# - add call to method <ActionOnInvalidation(..)> + // for all invalidation methods. + // #i28701# - use method <InvalidationAllowed(..)> to + // decide, if invalidation will to be performed or not. + // #i26945# - no additional invalidation, if it's already + // invalidate. + void InvalidateSize_() + { + if ( isFrameAreaSizeValid() && InvalidationAllowed( INVALID_SIZE ) ) + { + setFrameAreaSizeValid(false); + ActionOnInvalidation( INVALID_SIZE ); + } + } + void InvalidatePrt_() + { + if ( isFramePrintAreaValid() && InvalidationAllowed( INVALID_PRTAREA ) ) + { + setFramePrintAreaValid(false); + ActionOnInvalidation( INVALID_PRTAREA ); + } + } + void InvalidatePos_() + { + if ( isFrameAreaPositionValid() && InvalidationAllowed( INVALID_POS ) ) + { + setFrameAreaPositionValid(false); + ActionOnInvalidation( INVALID_POS ); + } + } + void InvalidateLineNum_() + { + if ( mbValidLineNum && InvalidationAllowed( INVALID_LINENUM ) ) + { + mbValidLineNum = false; + ActionOnInvalidation( INVALID_LINENUM ); + } + } + void InvalidateAll_() + { + if ( ( isFrameAreaSizeValid() || isFramePrintAreaValid() || isFrameAreaPositionValid() ) && InvalidationAllowed( INVALID_ALL ) ) + { + setFrameAreaSizeValid(false); + setFrameAreaPositionValid(false); + setFramePrintAreaValid(false); + ActionOnInvalidation( INVALID_ALL ); + } + } + // also notify page at the same time + inline void InvalidateSize(); + inline void InvalidatePrt(); + inline void InvalidatePos(); + inline void InvalidateLineNum(); + inline void InvalidateAll(); + void ImplInvalidateSize(); + void ImplInvalidatePrt(); + void ImplInvalidatePos(); + void ImplInvalidateLineNum(); + + inline void InvalidateNextPos( bool bNoFootnote = false ); + void ImplInvalidateNextPos( bool bNoFootnote ); + + /** method to invalidate printing area of next frame + #i11859# + */ + void InvalidateNextPrtArea(); + + void InvalidatePage( const SwPageFrame *pPage = nullptr ) const; + + virtual bool FillSelection( SwSelectionList& rList, const SwRect& rRect ) const; + + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, + SwCursorMoveState* = nullptr, bool bTestBackground = false ) const; + virtual bool GetCharRect( SwRect &, const SwPosition&, + SwCursorMoveState* = nullptr, bool bAllowFarAway = true ) const; + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const; + + // HACK: shortcut between frame and formatting + // It's your own fault if you cast void* incorrectly! In any case check + // the void* for 0. + virtual bool Prepare( const PrepareHint ePrep = PrepareHint::Clear, + const void *pVoid = nullptr, bool bNotify = true ); + + // true if it is the correct class, false otherwise + inline bool IsLayoutFrame() const; + inline bool IsRootFrame() const; + inline bool IsPageFrame() const; + inline bool IsColumnFrame() const; + inline bool IsFootnoteBossFrame() const; // footnote bosses might be PageFrames or ColumnFrames + inline bool IsHeaderFrame() const; + inline bool IsFooterFrame() const; + inline bool IsFootnoteContFrame() const; + inline bool IsFootnoteFrame() const; + inline bool IsBodyFrame() const; + inline bool IsColBodyFrame() const; // implemented in layfrm.hxx, BodyFrame above ColumnFrame + inline bool IsPageBodyFrame() const; // implemented in layfrm.hxx, BodyFrame above PageFrame + inline bool IsFlyFrame() const; + inline bool IsSctFrame() const; + inline bool IsTabFrame() const; + inline bool IsRowFrame() const; + inline bool IsCellFrame() const; + inline bool IsContentFrame() const; + inline bool IsTextFrame() const; + inline bool IsNoTextFrame() const; + // Frames where its PrtArea depends on their neighbors and that are + // positioned in the content flow + inline bool IsFlowFrame() const; + // Frames that are capable of retouching or that might need to retouch behind + // themselves + inline bool IsRetoucheFrame() const; + inline bool IsAccessibleFrame() const; + + void PrepareCursor(); // CursorShell is allowed to call this + + // Is the Frame (or the section containing it) protected? Same for Fly in + // Fly in ... and footnotes + bool IsProtected() const; + + bool IsColLocked() const { return mbColLocked; } + virtual bool IsDeleteForbidden() const { return mbForbidDelete; } + + /// this is the only way to delete a SwFrame instance + static void DestroyFrame(SwFrame *const pFrame); + + bool IsInDtor() const { return mbInDtor; } + + // No inline cause we need the function pointers + long GetTopMargin() const; + long GetBottomMargin() const; + long GetLeftMargin() const; + long GetRightMargin() const; + void SetTopBottomMargins( long, long ); + void SetLeftRightMargins( long, long ); + void SetRightLeftMargins( long, long ); + long GetPrtLeft() const; + long GetPrtBottom() const; + long GetPrtRight() const; + long GetPrtTop() const; + bool SetMinLeft( long ); + bool SetMaxBottom( long ); + bool SetMaxRight( long ); + void MakeBelowPos( const SwFrame*, const SwFrame*, bool ); + void MakeLeftPos( const SwFrame*, const SwFrame*, bool ); + void MakeRightPos( const SwFrame*, const SwFrame*, bool ); + bool IsNeighbourFrame() const + { return bool(GetType() & FRM_NEIGHBOUR); } + + // NEW TABLES + // Some functions for covered/covering table cells. This way unnecessary + // includes can be avoided + virtual bool IsLeaveUpperAllowed() const; + virtual bool IsCoveredCell() const; + bool IsInCoveredCell() const; + + // #i81146# new loop control + bool KnowsFormat( const SwFormat& rFormat ) const; + void RegisterToFormat( SwFormat& rFormat ); + void ValidateThisAndAllLowers( const sal_uInt16 nStage ); + + void ForbidDelete() { mbForbidDelete = true; } + void AllowDelete() { mbForbidDelete = false; } + + drawinglayer::attribute::SdrAllFillAttributesHelperPtr getSdrAllFillAttributesHelper() const; + bool supportsFullDrawingLayerFillAttributeSet() const; + +public: + // if writer is NULL, dumps the layout structure as XML in layout.xml + virtual void dumpAsXml(xmlTextWriterPtr writer = nullptr) const; + void dumpTopMostAsXml(xmlTextWriterPtr writer = nullptr) const; + void dumpInfosAsXml(xmlTextWriterPtr writer) const; + virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const; + void dumpChildrenAsXml(xmlTextWriterPtr writer) const; + bool IsCollapse() const; +}; + +inline bool SwFrame::IsInDocBody() const +{ + if ( mbInfInvalid ) + const_cast<SwFrame*>(this)->SetInfFlags(); + return mbInfBody; +} +inline bool SwFrame::IsInFootnote() const +{ + if ( mbInfInvalid ) + const_cast<SwFrame*>(this)->SetInfFlags(); + return mbInfFootnote; +} +inline bool SwFrame::IsInTab() const +{ + if ( mbInfInvalid ) + const_cast<SwFrame*>(this)->SetInfFlags(); + return mbInfTab; +} +inline bool SwFrame::IsInFly() const +{ + if ( mbInfInvalid ) + const_cast<SwFrame*>(this)->SetInfFlags(); + return mbInfFly; +} +inline bool SwFrame::IsInSct() const +{ + if ( mbInfInvalid ) + const_cast<SwFrame*>(this)->SetInfFlags(); + return mbInfSct; +} +bool SwFrame::IsVertical() const +{ + if( mbInvalidVert ) + const_cast<SwFrame*>(this)->SetDirFlags( true ); + return mbVertical; +} +inline bool SwFrame::IsVertLR() const +{ + return mbVertLR; +} +inline bool SwFrame::IsVertLRBT() const +{ + return mbVertLRBT; +} +inline bool SwFrame::IsRightToLeft() const +{ + if( mbInvalidR2L ) + const_cast<SwFrame*>(this)->SetDirFlags( false ); + return mbRightToLeft; +} + +inline void SwFrame::SetCompletePaint() const +{ + const_cast<SwFrame*>(this)->mbCompletePaint = true; +} +inline void SwFrame::ResetCompletePaint() const +{ + const_cast<SwFrame*>(this)->mbCompletePaint = false; +} + +inline void SwFrame::SetRetouche() const +{ + const_cast<SwFrame*>(this)->mbRetouche = true; +} +inline void SwFrame::ResetRetouche() const +{ + const_cast<SwFrame*>(this)->mbRetouche = false; +} + +inline SwLayoutFrame *SwFrame::GetNextLayoutLeaf() +{ + return const_cast<SwLayoutFrame*>(static_cast<const SwFrame*>(this)->GetNextLayoutLeaf()); +} +inline SwLayoutFrame *SwFrame::GetPrevLayoutLeaf() +{ + return const_cast<SwLayoutFrame*>(static_cast<const SwFrame*>(this)->GetPrevLayoutLeaf()); +} +inline const SwLayoutFrame *SwFrame::GetNextLayoutLeaf() const +{ + return ImplGetNextLayoutLeaf( true ); +} +inline const SwLayoutFrame *SwFrame::GetPrevLayoutLeaf() const +{ + return ImplGetNextLayoutLeaf( false ); +} + +inline void SwFrame::InvalidateSize() +{ + if ( isFrameAreaSizeValid() ) + { + ImplInvalidateSize(); + } +} +inline void SwFrame::InvalidatePrt() +{ + if ( isFramePrintAreaValid() ) + { + ImplInvalidatePrt(); + } +} +inline void SwFrame::InvalidatePos() +{ + if ( isFrameAreaPositionValid() ) + { + ImplInvalidatePos(); + } +} +inline void SwFrame::InvalidateLineNum() +{ + if ( mbValidLineNum ) + ImplInvalidateLineNum(); +} +inline void SwFrame::InvalidateAll() +{ + if ( InvalidationAllowed( INVALID_ALL ) ) + { + if ( isFrameAreaDefinitionValid() ) + { + ImplInvalidatePos(); + } + + setFrameAreaSizeValid(false); + setFrameAreaPositionValid(false); + setFramePrintAreaValid(false); + + // #i28701# + ActionOnInvalidation( INVALID_ALL ); + } +} +inline void SwFrame::InvalidateNextPos( bool bNoFootnote ) +{ + if ( mpNext && !mpNext->IsSctFrame() ) + mpNext->InvalidatePos(); + else + ImplInvalidateNextPos( bNoFootnote ); +} + +inline void SwFrame::OptCalc() const +{ + if ( !isFrameAreaPositionValid() || !isFramePrintAreaValid() || !isFrameAreaSizeValid() ) + { + const_cast<SwFrame*>(this)->OptPrepareMake(); + } +} +inline const SwPageFrame *SwFrame::FindPageFrame() const +{ + return const_cast<SwFrame*>(this)->FindPageFrame(); +} +inline const SwFrame *SwFrame::FindColFrame() const +{ + return const_cast<SwFrame*>(this)->FindColFrame(); +} +inline const SwFrame *SwFrame::FindFooterOrHeader() const +{ + return const_cast<SwFrame*>(this)->FindFooterOrHeader(); +} +inline SwTabFrame *SwFrame::FindTabFrame() +{ + return IsInTab() ? ImplFindTabFrame() : nullptr; +} +inline const SwFootnoteBossFrame *SwFrame::FindFootnoteBossFrame( bool bFootnote ) const +{ + return const_cast<SwFrame*>(this)->FindFootnoteBossFrame( bFootnote ); +} +inline SwFootnoteFrame *SwFrame::FindFootnoteFrame() +{ + return IsInFootnote() ? ImplFindFootnoteFrame() : nullptr; +} +inline SwFlyFrame *SwFrame::FindFlyFrame() +{ + return IsInFly() ? ImplFindFlyFrame() : nullptr; +} +inline SwSectionFrame *SwFrame::FindSctFrame() +{ + return IsInSct() ? ImplFindSctFrame() : nullptr; +} + +inline const SwBodyFrame *SwFrame::FindBodyFrame() const +{ + return IsInDocBody() ? ImplFindBodyFrame() : nullptr; +} + +inline const SwTabFrame *SwFrame::FindTabFrame() const +{ + return IsInTab() ? const_cast<SwFrame*>(this)->ImplFindTabFrame() : nullptr; +} +inline const SwFootnoteFrame *SwFrame::FindFootnoteFrame() const +{ + return IsInFootnote() ? const_cast<SwFrame*>(this)->ImplFindFootnoteFrame() : nullptr; +} +inline const SwFlyFrame *SwFrame::FindFlyFrame() const +{ + return IsInFly() ? const_cast<SwFrame*>(this)->ImplFindFlyFrame() : nullptr; +} +inline const SwSectionFrame *SwFrame::FindSctFrame() const +{ + return IsInSct() ? const_cast<SwFrame*>(this)->ImplFindSctFrame() : nullptr; +} +inline SwFrame *SwFrame::FindNext() +{ + if ( mpNext ) + return mpNext; + else + return FindNext_(); +} +inline const SwFrame *SwFrame::FindNext() const +{ + if ( mpNext ) + return mpNext; + else + return const_cast<SwFrame*>(this)->FindNext_(); +} +inline SwFrame *SwFrame::FindPrev() +{ + if ( mpPrev && !mpPrev->IsSctFrame() ) + return mpPrev; + else + return FindPrev_(); +} +inline const SwFrame *SwFrame::FindPrev() const +{ + if ( mpPrev && !mpPrev->IsSctFrame() ) + return mpPrev; + else + return const_cast<SwFrame*>(this)->FindPrev_(); +} + +inline bool SwFrame::IsLayoutFrame() const +{ + return bool(GetType() & FRM_LAYOUT); +} +inline bool SwFrame::IsRootFrame() const +{ + return mnFrameType == SwFrameType::Root; +} +inline bool SwFrame::IsPageFrame() const +{ + return mnFrameType == SwFrameType::Page; +} +inline bool SwFrame::IsColumnFrame() const +{ + return mnFrameType == SwFrameType::Column; +} +inline bool SwFrame::IsFootnoteBossFrame() const +{ + return bool(GetType() & FRM_FTNBOSS); +} +inline bool SwFrame::IsHeaderFrame() const +{ + return mnFrameType == SwFrameType::Header; +} +inline bool SwFrame::IsFooterFrame() const +{ + return mnFrameType == SwFrameType::Footer; +} +inline bool SwFrame::IsFootnoteContFrame() const +{ + return mnFrameType == SwFrameType::FtnCont; +} +inline bool SwFrame::IsFootnoteFrame() const +{ + return mnFrameType == SwFrameType::Ftn; +} +inline bool SwFrame::IsBodyFrame() const +{ + return mnFrameType == SwFrameType::Body; +} +inline bool SwFrame::IsFlyFrame() const +{ + return mnFrameType == SwFrameType::Fly; +} +inline bool SwFrame::IsSctFrame() const +{ + return mnFrameType == SwFrameType::Section; +} +inline bool SwFrame::IsTabFrame() const +{ + return mnFrameType == SwFrameType::Tab; +} +inline bool SwFrame::IsRowFrame() const +{ + return mnFrameType == SwFrameType::Row; +} +inline bool SwFrame::IsCellFrame() const +{ + return mnFrameType == SwFrameType::Cell; +} +inline bool SwFrame::IsContentFrame() const +{ + return bool(GetType() & FRM_CNTNT); +} +inline bool SwFrame::IsTextFrame() const +{ + return mnFrameType == SwFrameType::Txt; +} +inline bool SwFrame::IsNoTextFrame() const +{ + return mnFrameType == SwFrameType::NoTxt; +} +inline bool SwFrame::IsFlowFrame() const +{ + return bool(GetType() & (FRM_CNTNT|SwFrameType::Tab|SwFrameType::Section)); +} +inline bool SwFrame::IsRetoucheFrame() const +{ + return bool(GetType() & (FRM_CNTNT|SwFrameType::Tab|SwFrameType::Section|SwFrameType::Ftn)); +} +inline bool SwFrame::IsAccessibleFrame() const +{ + return bool(GetType() & FRM_ACCESSIBLE); +} + +//use this to protect a SwFrame for a given scope from getting deleted +class SwFrameDeleteGuard +{ +private: + SwFrame *m_pForbidFrame; +public: + //Flag pFrame for SwFrameDeleteGuard lifetime that we shouldn't delete + //it in e.g. SwSectionFrame::MergeNext etc because we will need it + //again after the SwFrameDeleteGuard dtor + explicit SwFrameDeleteGuard(SwFrame* pFrame) + : m_pForbidFrame((pFrame && !pFrame->IsDeleteForbidden()) ? + pFrame : nullptr) + { + if (m_pForbidFrame) + m_pForbidFrame->ForbidDelete(); + } + + SwFrameDeleteGuard(const SwFrameDeleteGuard&) =delete; + + ~SwFrameDeleteGuard() + { + if (m_pForbidFrame) + m_pForbidFrame->AllowDelete(); + } + + SwFrameDeleteGuard& operator=(const SwFrameDeleteGuard&) =delete; +}; + +typedef long (SwFrame:: *SwFrameGet)() const; +typedef bool (SwFrame:: *SwFrameMax)( long ); +typedef void (SwFrame:: *SwFrameMakePos)( const SwFrame*, const SwFrame*, bool ); +typedef long (*SwOperator)( long, long ); +typedef void (SwFrame:: *SwFrameSet)( long, long ); + +struct SwRectFnCollection +{ + SwRectGet fnGetTop; + SwRectGet fnGetBottom; + SwRectGet fnGetLeft; + SwRectGet fnGetRight; + SwRectGet fnGetWidth; + SwRectGet fnGetHeight; + SwRectPoint fnGetPos; + SwRectSize fnGetSize; + + SwRectSet fnSetTop; + SwRectSet fnSetBottom; + SwRectSet fnSetLeft; + SwRectSet fnSetRight; + SwRectSet fnSetWidth; + SwRectSet fnSetHeight; + + SwRectSet fnSubTop; + SwRectSet fnAddBottom; + SwRectSet fnSubLeft; + SwRectSet fnAddRight; + SwRectSet fnAddWidth; + SwRectSet fnAddHeight; + + SwRectSet fnSetPosX; + SwRectSet fnSetPosY; + + SwFrameGet fnGetTopMargin; + SwFrameGet fnGetBottomMargin; + SwFrameGet fnGetLeftMargin; + SwFrameGet fnGetRightMargin; + SwFrameSet fnSetXMargins; + SwFrameSet fnSetYMargins; + SwFrameGet fnGetPrtTop; + SwFrameGet fnGetPrtBottom; + SwFrameGet fnGetPrtLeft; + SwFrameGet fnGetPrtRight; + SwRectDist fnTopDist; + SwRectDist fnBottomDist; + SwRectDist fnLeftDist; + SwRectDist fnRightDist; + SwFrameMax fnSetLimit; + SwRectMax fnOverStep; + + SwRectSetPos fnSetPos; + SwFrameMakePos fnMakePos; + SwOperator fnXDiff; + SwOperator fnYDiff; + SwOperator fnXInc; + SwOperator fnYInc; + + SwRectSetTwice fnSetLeftAndWidth; + SwRectSetTwice fnSetTopAndHeight; +}; + +typedef SwRectFnCollection* SwRectFn; + +// This class allows to use proper methods regardless of orientation (LTR/RTL, horizontal or vertical) +extern SwRectFn fnRectHori, fnRectVert, fnRectVertL2R, fnRectVertL2RB2T; +class SwRectFnSet { +public: + explicit SwRectFnSet(const SwFrame *pFrame) + : m_bVert(pFrame->IsVertical()) + , m_bVertL2R(pFrame->IsVertLR()) + , m_bVertL2RB2T(pFrame->IsVertLRBT()) + { + m_fnRect = m_bVert ? (m_bVertL2R ? (m_bVertL2RB2T ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert) : fnRectHori; + } + + void Refresh(const SwFrame *pFrame) + { + m_bVert = pFrame->IsVertical(); + m_bVertL2R = pFrame->IsVertLR(); + m_bVertL2RB2T = pFrame->IsVertLRBT(); + m_fnRect = m_bVert ? (m_bVertL2R ? (m_bVertL2RB2T ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert) : fnRectHori; + } + + bool IsVert() const { return m_bVert; } + bool IsVertL2R() const { return m_bVertL2R; } + SwRectFn FnRect() const { return m_fnRect; } + + bool PosDiff(const SwRect &rRect1, const SwRect &rRect2) const + { + return ((rRect1.*m_fnRect->fnGetTop)() != (rRect2.*m_fnRect->fnGetTop)() + || (rRect1.*m_fnRect->fnGetLeft)() != (rRect2.*m_fnRect->fnGetLeft)()); + } + + long GetTop (const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetTop) (); } + long GetBottom(const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetBottom)(); } + long GetLeft (const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetLeft) (); } + long GetRight (const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetRight) (); } + long GetWidth (const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetWidth) (); } + long GetHeight(const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetHeight)(); } + Point GetPos (const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetPos) (); } + Size GetSize (const SwRect& rRect) const { return (rRect.*m_fnRect->fnGetSize) (); } + + void SetTop (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetTop) (nNew); } + void SetBottom(SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetBottom)(nNew); } + void SetLeft (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetLeft) (nNew); } + void SetRight (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetRight) (nNew); } + void SetWidth (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetWidth) (nNew); } + void SetHeight(SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetHeight)(nNew); } + + void SubTop (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSubTop) (nNew); } + void AddBottom(SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnAddBottom)(nNew); } + void SubLeft (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSubLeft) (nNew); } + void AddRight (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnAddRight) (nNew); } + void AddWidth (SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnAddWidth) (nNew); } + void AddHeight(SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnAddHeight)(nNew); } + + void SetPosX(SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetPosX)(nNew); } + void SetPosY(SwRect& rRect, long nNew) const { (rRect.*m_fnRect->fnSetPosY)(nNew); } + + long GetTopMargin (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetTopMargin) (); } + long GetBottomMargin(const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetBottomMargin)(); } + long GetLeftMargin (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetLeftMargin) (); } + long GetRightMargin (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetRightMargin) (); } + void SetXMargins(SwFrame& rFrame, long nLeft, long nRight) const { (rFrame.*m_fnRect->fnSetXMargins)(nLeft, nRight); } + void SetYMargins(SwFrame& rFrame, long nTop, long nBottom) const { (rFrame.*m_fnRect->fnSetYMargins)(nTop, nBottom); } + long GetPrtTop (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetPrtTop) (); } + long GetPrtBottom (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetPrtBottom) (); } + long GetPrtLeft (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetPrtLeft) (); } + long GetPrtRight (const SwFrame& rFrame) const { return (rFrame.*m_fnRect->fnGetPrtRight) (); } + long TopDist (const SwRect& rRect, long nPos) const { return (rRect.*m_fnRect->fnTopDist) (nPos); } + long BottomDist(const SwRect& rRect, long nPos) const { return (rRect.*m_fnRect->fnBottomDist) (nPos); } + long LeftDist (const SwRect& rRect, long nPos) const { return (rRect.*m_fnRect->fnLeftDist) (nPos); } + long RightDist (const SwRect& rRect, long nPos) const { return (rRect.*m_fnRect->fnRightDist) (nPos); } + void SetLimit (SwFrame& rFrame, long nNew) const { (rFrame.*m_fnRect->fnSetLimit) (nNew); } + bool OverStep (const SwRect& rRect, long nPos) const { return (rRect.*m_fnRect->fnOverStep) (nPos); } + + void SetPos(SwRect& rRect, const Point& rNew) const { (rRect.*m_fnRect->fnSetPos)(rNew); } + void MakePos(SwFrame& rFrame, const SwFrame* pUp, const SwFrame* pPrv, bool bNotify) const { (rFrame.*m_fnRect->fnMakePos)(pUp, pPrv, bNotify); } + long XDiff(long n1, long n2) const { return (m_fnRect->fnXDiff) (n1, n2); } + long YDiff(long n1, long n2) const { return (m_fnRect->fnYDiff) (n1, n2); } + long XInc (long n1, long n2) const { return (m_fnRect->fnXInc) (n1, n2); } + long YInc (long n1, long n2) const { return (m_fnRect->fnYInc) (n1, n2); } + + void SetLeftAndWidth(SwRect& rRect, long nLeft, long nWidth) const { (rRect.*m_fnRect->fnSetLeftAndWidth)(nLeft, nWidth); } + void SetTopAndHeight(SwRect& rRect, long nTop, long nHeight) const { (rRect.*m_fnRect->fnSetTopAndHeight)(nTop, nHeight); } + +private: + bool m_bVert; + bool m_bVertL2R; + bool m_bVertL2RB2T; + SwRectFn m_fnRect; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/frminf.hxx b/sw/source/core/inc/frminf.hxx new file mode 100644 index 000000000..ab7ce6a5c --- /dev/null +++ b/sw/source/core/inc/frminf.hxx @@ -0,0 +1,75 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FRMINF_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FRMINF_HXX + +#include <swtypes.hxx> + +#include "TextFrameIndex.hxx" + +#include <vector> + +class SwTextFrame; +class SwTextCursor; + +class SwTextFrameInfo +{ + const SwTextFrame *pFrame; + + // Where does the text (w/o whitespaces) start (document is global!)? + static SwTwips GetLineStart( const SwTextCursor &rLine ); + +public: + SwTextFrameInfo( const SwTextFrame *pTextFrame ) : pFrame(pTextFrame) { } + + // Does the paragraph fit into a single line? + bool IsOneLine() const; + + // Is the line filled to X%? + bool IsFilled( const sal_uInt8 nPercent ) const; + + // Where does the text (w/o whitespaces) start (rel. in frame)? + SwTwips GetLineStart() const; + + // return center position of the next character + SwTwips GetCharPos(TextFrameIndex nChar, bool bCenter = true) const; + + // collect all whitespaces at the beginning and end of a line in Pam + void GetSpaces(std::vector<std::pair<TextFrameIndex, TextFrameIndex>> &, + bool bWithLineBreak) const; + + // Is a bullet point/symbol/etc. at the first text position? + bool IsBullet(TextFrameIndex nTextPos) const; + + // determine indentation for first line + SwTwips GetFirstIndent() const; + + const SwTextFrame* GetFrame() const { return pFrame; } + SwTextFrameInfo& SetFrame( const SwTextFrame* pNew ) + { pFrame = pNew; return *this; } + + // Is it a comparison? Returns position in frame. + sal_Int32 GetBigIndent( TextFrameIndex & rFndPos, + const SwTextFrame *pNextFrame ) const; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/frmtool.hxx b/sw/source/core/inc/frmtool.hxx new file mode 100644 index 000000000..f7f4a209b --- /dev/null +++ b/sw/source/core/inc/frmtool.hxx @@ -0,0 +1,610 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FRMTOOL_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FRMTOOL_HXX + +#include <swtypes.hxx> +#include "frame.hxx" +#include "txtfrm.hxx" +#include "swcache.hxx" +#include <swatrset.hxx> + +class SwLayoutFrame; +class SwFont; +class SwTextFrame; +class SwFormatAnchor; +class SwViewShell; +class SwPageFrame; +class SwFlyFrame; +class SwContentFrame; +class SwRootFrame; +class SwDoc; +class SdrObject; +class SvxBrushItem; +class SdrMarkList; +class SwNodeIndex; +class GraphicObject; +class GraphicAttr; +class SwPageDesc; +class SwFrameFormats; +class SwRegionRects; +class SwTextNode; +namespace sw { struct Extent; } +namespace basegfx::utils { class B2DClipState; } + +#define FAR_AWAY (SAL_MAX_INT32 - 20000) // initial position of a Fly +#define BROWSE_HEIGHT (56700L * 10L) // 10 Meters +#define GRFNUM_NO 0 +#define GRFNUM_YES 1 +#define GRFNUM_REPLACE 2 + +void AppendObjs( const SwFrameFormats *pTable, sal_uLong nIndex, + SwFrame *pFrame, SwPageFrame *pPage, SwDoc* doc ); + +void AppendObjsOfNode(SwFrameFormats const* pTable, sal_uLong nIndex, + SwFrame * pFrame, SwPageFrame * pPage, SwDoc * pDoc, + std::vector<sw::Extent>::const_iterator const* pIter, + std::vector<sw::Extent>::const_iterator const* pEnd, + SwTextNode const* pFirstNode, SwTextNode const* pLastNode); + +void RemoveHiddenObjsOfNode(SwTextNode const& rNode, + std::vector<sw::Extent>::const_iterator const* pIter, + std::vector<sw::Extent>::const_iterator const* pEnd, + SwTextNode const* pFirstNode, SwTextNode const* pLastNode); + +bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor); + +void AppendAllObjs(const SwFrameFormats* pTable, const SwFrame* pSib); + +// draw background with brush or graphics +// The 6th parameter indicates that the method should consider background +// transparency, saved in the color of the brush item. +void DrawGraphic( + const SvxBrushItem *, + vcl::RenderContext *, + const SwRect &rOrg, + const SwRect &rOut, + const sal_uInt8 nGrfNum = GRFNUM_NO, + const bool bConsiderBackgroundTransparency = false ); +bool DrawFillAttributes( + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes, + const SwRect& rOriginalLayoutRect, + const SwRegionRects& rPaintRegion, + const basegfx::utils::B2DClipState& rClipState, + vcl::RenderContext& rOut); + +// RotGrfFlyFrame: Adapted to rotation +void paintGraphicUsingPrimitivesHelper( + vcl::RenderContext & rOutputDevice, + GraphicObject const& rGraphicObj, + GraphicAttr const& rGraphicAttr, + const basegfx::B2DHomMatrix& rGraphicTransform, + const OUString& rName, + const OUString& rTitle, + const OUString& rDescription); + +// MM02 new VOC and primitive-based version +void paintGraphicUsingPrimitivesHelper( + vcl::RenderContext & rOutputDevice, + drawinglayer::primitive2d::Primitive2DContainer& rContent, + const basegfx::B2DHomMatrix& rGraphicTransform, + const OUString& rName, + const OUString& rTitle, + const OUString& rDescription); + +// method to align rectangle. +// Created declaration here to avoid <extern> declarations +void SwAlignRect( SwRect &rRect, const SwViewShell *pSh, const vcl::RenderContext* pRenderContext ); + +// method to align graphic rectangle +// Created declaration here to avoid <extern> declarations +void SwAlignGrfRect( SwRect *pGrfRect, const vcl::RenderContext &rOut ); + +/** + * Paint border around a run of characters using frame painting code. + * + * @param[in] rFont font object of actual text, which specify the border + * @param[in] rPaintArea rectangle area in which line portion takes place + * @param[in] bVerticalLayout corresponding text frame verticality + * @param[in] bVerticalLayoutLRBT corresponding text frame verticality (LRBT subset) + * @param[in] bJoinWithPrev leave border with which actual border joins to the previous portion + * @param[in] bJoinWithNext leave border with which actual border joins to the next portion +**/ +void PaintCharacterBorder(const SwFont& rFont, const SwRect& rPaintArea, const bool bVerticalLayout, + const bool bVerticalLayoutLRBT, const bool bJoinWithPrev, + const bool bJoinWithNext); + +// get Fly, if no List is given use the current shell +// Implementation in feshview.cxx +SwFlyFrame *GetFlyFromMarked( const SdrMarkList *pLst, SwViewShell *pSh ); + +SwFrame *SaveContent( SwLayoutFrame *pLay, SwFrame *pStart = nullptr ); +void RestoreContent( SwFrame *pSav, SwLayoutFrame *pParent, SwFrame *pSibling ); + +// Get ContentNodes, create ContentFrames, and add them to LayFrame. +void InsertCnt_( SwLayoutFrame *pLay, SwDoc *pDoc, sal_uLong nIndex, + bool bPages = false, sal_uLong nEndIndex = 0, + SwFrame *pPrv = nullptr, sw::FrameMode eMode = sw::FrameMode::New); + +// Creation of frames for a specific section (uses InsertCnt_) +void MakeFrames( SwDoc *pDoc, const SwNodeIndex &rSttIdx, + const SwNodeIndex &rEndIdx ); + +extern bool bObjsDirect; + +// prevent creation of Flys in InsertCnt_, e.g. for table headlines +extern bool bDontCreateObjects; + +// for FlyCnts, see SwFlyAtContentFrame::MakeAll() +extern bool bSetCompletePaintOnInvalidate; + +// for table settings via keyboard +SwTwips CalcRowRstHeight( SwLayoutFrame *pRow ); +long CalcHeightWithFlys( const SwFrame *pFrame ); + +namespace sw { + +bool IsRightPageByNumber(SwRootFrame const& rLayout, sal_uInt16 nPageNum); + +} // namespace sw + +SwPageFrame *InsertNewPage( SwPageDesc &rDesc, SwFrame *pUpper, + bool isRightPage, bool bFirst, bool bInsertEmpty, bool bFootnote, + SwFrame *pSibling ); + +// connect Flys with page +void RegistFlys( SwPageFrame*, const SwLayoutFrame* ); + +// notification of Fly's background if needed +void Notify( SwFlyFrame *pFly, SwPageFrame *pOld, const SwRect &rOld, + const SwRect* pOldRect = nullptr ); + +void Notify_Background( const SdrObject* pObj, + SwPageFrame* pPage, + const SwRect& rRect, + const PrepareHint eHint, + const bool bInva ); + +const SwFrame* GetVirtualUpper( const SwFrame* pFrame, const Point& rPos ); + +bool Is_Lower_Of( const SwFrame *pCurrFrame, const SdrObject* pObj ); + +// FIXME: EasyHack (refactoring): rename method and parameter name in all files +const SwFrame *FindContext( const SwFrame *pFrame, SwFrameType nAdditionalContextTyp ); + +bool IsFrameInSameContext( const SwFrame *pInnerFrame, const SwFrame *pFrame ); + +const SwFrame * FindPage( const SwRect &rRect, const SwFrame *pPage ); + +/** @see SwContentNode::getLayoutFrame() + @param pPos + Document model position; for a text frame, the returned frame will be + one containing this position. + @param pViewPosAndCalcFrame + First is a point in the document view; the returned frame will be the one + with the minimal distance to this point. To get the first frame in the + document, pass in a default-initialized Point with coordinates 0,0. + Second indicates whether the frames should be formatted before retrieving + their position for the test; this cannot be done by every caller so use + with care! + */ +SwFrame* GetFrameOfModify( const SwRootFrame* pLayout, + SwModify const&, + SwFrameType const nFrameType, + const SwPosition *pPos = nullptr, + std::pair<Point, bool> const* pViewPosAndCalcFrame = nullptr); + +// Should extra data (redline stroke, line numbers) be painted? +bool IsExtraData( const SwDoc *pDoc ); + +// #i11760# - method declaration <CalcContent(..)> +void CalcContent( SwLayoutFrame *pLay, bool bNoColl = false ); + +// Notify classes memorize the current sizes in their constructor and do +// the necessary notifications in their destructor if needed +class SwFrameNotify +{ +protected: + SwFrame *mpFrame; + const SwRect maFrame; + const SwRect maPrt; + SwTwips mnFlyAnchorOfst; + SwTwips mnFlyAnchorOfstNoWrap; + bool mbHadFollow; + bool mbInvaKeep; + bool mbValidSize; + +public: + SwFrameNotify( SwFrame *pFrame ); + ~SwFrameNotify() COVERITY_NOEXCEPT_FALSE; + + const SwRect &getFrameArea() const { return maFrame; } + void SetInvaKeep() { mbInvaKeep = true; } +}; + +class SwLayNotify : public SwFrameNotify +{ + bool m_bLowersComplete; + +public: + SwLayNotify( SwLayoutFrame *pLayFrame ); + ~SwLayNotify(); + + void SetLowersComplete( bool b ) { m_bLowersComplete = b; } + bool IsLowersComplete() const { return m_bLowersComplete; } +}; + +class SwFlyNotify : public SwLayNotify +{ + SwPageFrame *pOldPage; + const SwRect aFrameAndSpace; + +public: + SwFlyNotify( SwFlyFrame *pFlyFrame ); + ~SwFlyNotify(); +}; + +class SwContentNotify : public SwFrameNotify +{ +private: + // #i11859# + bool mbChkHeightOfLastLine; + SwTwips mnHeightOfLastLine; + + // #i25029# + bool mbInvalidatePrevPrtArea; + bool mbBordersJoinedWithPrev; + +public: + SwContentNotify( SwContentFrame *pContentFrame ); + ~SwContentNotify(); + + // #i25029# + void SetInvalidatePrevPrtArea() + { + mbInvalidatePrevPrtArea = true; + } + + void SetBordersJoinedWithPrev() + { + mbBordersJoinedWithPrev = true; + } +}; + +// SwBorderAttrs encapsulates the calculation for margin attributes including +// border. The whole class is cached. + +// WARNING! If more attributes should be cached also adjust the method +// Modify::Modify! +class SwBorderAttrs : public SwCacheObj +{ + const SwAttrSet &m_rAttrSet; + const SvxULSpaceItem &m_rUL; + // #i96772# + std::shared_ptr<SvxLRSpaceItem> m_rLR; + const SvxBoxItem &m_rBox; + const SvxShadowItem &m_rShadow; + const Size m_aFrameSize; + + // the following bool values set the cached values to INVALID - until they + // are calculated for the first time + bool m_bTopLine : 1; + bool m_bBottomLine : 1; + bool m_bLeftLine : 1; + bool m_bRightLine : 1; + bool m_bTop : 1; + bool m_bBottom : 1; + bool m_bLine : 1; + bool m_bLineSpacing : 1; + + bool m_bIsLine : 1; // border on at least one side? + + bool m_bCacheGetLine : 1; // cache GetTopLine(), GetBottomLine()? + bool m_bCachedGetTopLine : 1; // is GetTopLine() cached? + bool m_bCachedGetBottomLine : 1; // is GetBottomLine() cached? + // Booleans indicate that <m_bJoinedWithPrev> and <m_bJoinedWithNext> are + // cached and valid. + // Caching depends on value of <m_bCacheGetLine>. + mutable bool m_bCachedJoinedWithPrev : 1; + mutable bool m_bCachedJoinedWithNext : 1; + // Booleans indicate that borders are joined with previous/next frame. + bool m_bJoinedWithPrev :1; + bool m_bJoinedWithNext :1; + + // The cached values (un-defined until calculated for the first time) + sal_uInt16 m_nTopLine, + m_nBottomLine, + m_nLeftLine, + m_nRightLine, + m_nTop, + m_nBottom, + m_nGetTopLine, + m_nGetBottomLine, + m_nLineSpacing; + + // only calculate lines and shadow + void CalcTopLine_(); + void CalcBottomLine_(); + void CalcLeftLine_(); + void CalcRightLine_(); + + // lines + shadow + margin + void CalcTop_(); + void CalcBottom_(); + + void IsLine_(); + + // #i25029# - If <_pPrevFrame> is set, its value is taken for testing, if + // borders/shadow have to be joined with previous frame. + void GetTopLine_ ( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ); + void GetBottomLine_( const SwFrame& _rFrame ); + + // calculate cached values <m_bJoinedWithPrev> and <m_bJoinedWithNext> + // #i25029# - If <_pPrevFrame> is set, its value is taken for testing, if + // borders/shadow have to be joined with previous frame. + void CalcJoinedWithPrev( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ); + void CalcJoinedWithNext( const SwFrame& _rFrame ); + + // internal helper method for CalcJoinedWithPrev and CalcJoinedWithNext + bool JoinWithCmp( const SwFrame& _rCallerFrame, + const SwFrame& _rCmpFrame ) const; + + // Are the left and right line and the LRSpace equal? + bool CmpLeftRight( const SwBorderAttrs &rCmpAttrs, + const SwFrame *pCaller, + const SwFrame *pCmp ) const; + + // tdf#125300 line spacing before cell border + void CalcLineSpacing_(); + +public: + SwBorderAttrs( const SwModify *pOwner, const SwFrame *pConstructor ); + virtual ~SwBorderAttrs() override; + + const SwAttrSet &GetAttrSet() const { return m_rAttrSet; } + const SvxULSpaceItem &GetULSpace() const { return m_rUL; } + const SvxBoxItem &GetBox() const { return m_rBox; } + const SvxShadowItem &GetShadow() const { return m_rShadow; } + + inline sal_uInt16 CalcTopLine() const; + inline sal_uInt16 CalcBottomLine() const; + inline sal_uInt16 CalcLeftLine() const; + inline sal_uInt16 CalcRightLine() const; + inline sal_uInt16 CalcTop() const; + inline sal_uInt16 CalcBottom() const; + inline sal_uInt16 CalcLineSpacing() const; + long CalcLeft( const SwFrame *pCaller ) const; + long CalcRight( const SwFrame *pCaller ) const; + + inline bool IsLine() const; + + const Size &GetSize() const { return m_aFrameSize; } + + // Should upper (or lower) border be evaluated for this frame? + // #i25029# - If <_pPrevFrame> is set, its value is taken for testing, if + // borders/shadow have to be joined with previous frame. + inline sal_uInt16 GetTopLine ( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame = nullptr ) const; + inline sal_uInt16 GetBottomLine( const SwFrame& _rFrame ) const; + inline void SetGetCacheLine( bool bNew ) const; + + // Accessors for cached values <m_bJoinedWithPrev> and <m_bJoinedWithNext> + // #i25029# - If <_pPrevFrame> is set, its value is taken for testing, if + // borders/shadow have to be joined with previous frame. + bool JoinedWithPrev( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame = nullptr ) const; + bool JoinedWithNext( const SwFrame& _rFrame ) const; +}; + +class SwBorderAttrAccess : public SwCacheAccess +{ + const SwFrame *m_pConstructor; //opt: for passing on to SwBorderAttrs + +protected: + virtual SwCacheObj *NewObj() override; + +public: + SwBorderAttrAccess( SwCache &rCache, const SwFrame *pOwner ); + + SwBorderAttrs *Get(); +}; + +// Iterator for draw objects of a page. The objects will be iterated sorted by +// their Z-order. Iterating is not cheap since for each operation the _whole_ +// SortArray needs to be traversed. +class SwOrderIter +{ + const SwPageFrame *m_pPage; + const SdrObject *m_pCurrent; + +public: + SwOrderIter( const SwPageFrame *pPage ); + + void Current( const SdrObject *pNew ) { m_pCurrent = pNew; } + const SdrObject *operator()() const { return m_pCurrent; } + void Top(); + const SdrObject *Bottom(); + const SdrObject *Next(); + void Prev(); +}; + +class StackHack +{ + static sal_uInt8 nCnt; + static bool bLocked; + +public: + StackHack() + { + if ( ++StackHack::nCnt > 50 ) + StackHack::bLocked = true; + } + ~StackHack() + { + if ( --StackHack::nCnt < 5 ) + StackHack::bLocked = false; + } + + static bool IsLocked() { return StackHack::bLocked; } + static sal_uInt8 Count() { return StackHack::nCnt; } +}; + +// Should upper (or lower) border be evaluated for this frame? +// #i25029# - If <_pPrevFrame> is set, its value is taken for testing, if +// borders/shadow have to be joined with previous frame. +inline sal_uInt16 SwBorderAttrs::GetTopLine ( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) const +{ + if ( !m_bCachedGetTopLine || _pPrevFrame ) + { + const_cast<SwBorderAttrs*>(this)->GetTopLine_( _rFrame, _pPrevFrame ); + } + return m_nGetTopLine; +} +inline sal_uInt16 SwBorderAttrs::GetBottomLine( const SwFrame& _rFrame ) const +{ + if ( !m_bCachedGetBottomLine ) + const_cast<SwBorderAttrs*>(this)->GetBottomLine_( _rFrame ); + return m_nGetBottomLine; +} +inline void SwBorderAttrs::SetGetCacheLine( bool bNew ) const +{ + const_cast<SwBorderAttrs*>(this)->m_bCacheGetLine = bNew; + const_cast<SwBorderAttrs*>(this)->m_bCachedGetBottomLine = + const_cast<SwBorderAttrs*>(this)->m_bCachedGetTopLine = false; + // invalidate cache for values <m_bJoinedWithPrev> and <m_bJoinedWithNext> + m_bCachedJoinedWithPrev = false; + m_bCachedJoinedWithNext = false; +} + +inline sal_uInt16 SwBorderAttrs::CalcTopLine() const +{ + if ( m_bTopLine ) + const_cast<SwBorderAttrs*>(this)->CalcTopLine_(); + return m_nTopLine; +} +inline sal_uInt16 SwBorderAttrs::CalcBottomLine() const +{ + if ( m_bBottomLine ) + const_cast<SwBorderAttrs*>(this)->CalcBottomLine_(); + return m_nBottomLine; +} +inline sal_uInt16 SwBorderAttrs::CalcLeftLine() const +{ + if ( m_bLeftLine ) + const_cast<SwBorderAttrs*>(this)->CalcLeftLine_(); + return m_nLeftLine; +} +inline sal_uInt16 SwBorderAttrs::CalcRightLine() const +{ + if ( m_bRightLine ) + const_cast<SwBorderAttrs*>(this)->CalcRightLine_(); + return m_nRightLine; +} +inline sal_uInt16 SwBorderAttrs::CalcTop() const +{ + if ( m_bTop ) + const_cast<SwBorderAttrs*>(this)->CalcTop_(); + return m_nTop; +} +inline sal_uInt16 SwBorderAttrs::CalcBottom() const +{ + if ( m_bBottom ) + const_cast<SwBorderAttrs*>(this)->CalcBottom_(); + return m_nBottom; +} +inline sal_uInt16 SwBorderAttrs::CalcLineSpacing() const +{ + if ( m_bLineSpacing ) + const_cast<SwBorderAttrs*>(this)->CalcLineSpacing_(); + return m_nLineSpacing; +} +inline bool SwBorderAttrs::IsLine() const +{ + if ( m_bLine ) + const_cast<SwBorderAttrs*>(this)->IsLine_(); + return m_bIsLine; +} + +/** method to determine the spacing values of a frame + + #i28701# + Values only provided for flow frames (table, section or text frames) + Note: line spacing value is only determined for text frames + #i102458# + Add output parameter <obIsLineSpacingProportional> + + @param rFrame + input parameter - frame, for which the spacing values are determined. + + @param onPrevLowerSpacing + output parameter - lower spacing of the frame in SwTwips + + @param onPrevLineSpacing + output parameter - line spacing of the frame in SwTwips + + @param obIsLineSpacingProportional +*/ +void GetSpacingValuesOfFrame( const SwFrame& rFrame, + SwTwips& onLowerSpacing, + SwTwips& onLineSpacing, + bool& obIsLineSpacingProportional ); + +/** method to get the content of the table cell + + Content from any nested tables will be omitted. + Note: line spacing value is only determined for text frames + + @param rCell_ + input parameter - the cell which should be searched for content. + + return + pointer to the found content frame or 0 +*/ + +const SwContentFrame* GetCellContent( const SwLayoutFrame& rCell_ ); + +/** helper class to check if a frame has been deleted during an operation + * WARNING! This should only be used as a last and desperate means to make the + * code robust. + */ + +class SwDeletionChecker +{ +private: + const SwFrame* mpFrame; + const SwModify* mpRegIn; + +public: + SwDeletionChecker(const SwFrame* pFrame); + + /** + * return + * true if mpFrame != 0 and mpFrame is not client of pRegIn + * false otherwise + */ + bool HasBeenDeleted() const; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/ftnboss.hxx b/sw/source/core/inc/ftnboss.hxx new file mode 100644 index 000000000..3ee7859bf --- /dev/null +++ b/sw/source/core/inc/ftnboss.hxx @@ -0,0 +1,130 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FTNBOSS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FTNBOSS_HXX + +#include "layfrm.hxx" + +class SwFootnoteBossFrame; +class SwFootnoteContFrame; +class SwFootnoteFrame; +class SwTextFootnote; + +// Set max. footnote area. +// Restoration of the old value in DTor. Implementation in ftnfrm.cxx +class SwSaveFootnoteHeight +{ + SwFootnoteBossFrame *pBoss; + const SwTwips nOldHeight; + SwTwips nNewHeight; +public: + SwSaveFootnoteHeight( SwFootnoteBossFrame *pBs, const SwTwips nDeadLine ); + ~SwSaveFootnoteHeight(); +}; + +enum class SwNeighbourAdjust { + OnlyAdjust, GrowShrink, GrowAdjust, AdjustGrow +}; + +typedef std::vector<SwFootnoteFrame*> SwFootnoteFrames; + +class SAL_DLLPUBLIC_RTTI SwFootnoteBossFrame: public SwLayoutFrame +{ + // for private footnote operations + friend class SwFrame; + friend class SwSaveFootnoteHeight; + friend class SwPageFrame; // for setting of MaxFootnoteHeight + + // max. height of the footnote container on this page + SwTwips m_nMaxFootnoteHeight; + + SwFootnoteContFrame *MakeFootnoteCont(); + SwFootnoteFrame *FindFirstFootnote(); + SwNeighbourAdjust NeighbourhoodAdjustment_() const; + + static void CollectFootnotes_(const SwContentFrame*, SwFootnoteFrame*, + SwFootnoteFrames&, const SwFootnoteBossFrame*); + +protected: + void InsertFootnote( SwFootnoteFrame * ); + static void ResetFootnote( const SwFootnoteFrame *pAssumed ); + +public: + SwFootnoteBossFrame( SwFrameFormat* pFormat, SwFrame* pSib ) + : SwLayoutFrame( pFormat, pSib ) + , m_nMaxFootnoteHeight(0) + {} + + SwLayoutFrame *FindBodyCont(); + inline const SwLayoutFrame *FindBodyCont() const; + void SetMaxFootnoteHeight( const SwTwips nNewMax ) { m_nMaxFootnoteHeight = nNewMax; } + + // footnote interface + void AppendFootnote( SwContentFrame *, SwTextFootnote * ); + bool RemoveFootnote(const SwContentFrame *, const SwTextFootnote *, bool bPrep = true); + static SwFootnoteFrame *FindFootnote( const SwContentFrame *, const SwTextFootnote * ); + SwFootnoteContFrame *FindFootnoteCont(); + inline const SwFootnoteContFrame *FindFootnoteCont() const; + const SwFootnoteFrame *FindFirstFootnote( SwContentFrame const * ) const; + SwFootnoteContFrame *FindNearestFootnoteCont( bool bDontLeave = false ); + + static void ChangeFootnoteRef( const SwContentFrame *pOld, const SwTextFootnote *, + SwContentFrame *pNew ); + void RearrangeFootnotes( const SwTwips nDeadLine, const bool bLock, + const SwTextFootnote *pAttr = nullptr ); + + // Set DeadLine (in document coordinates) so that the text formatter can + // temporarily limit footnote height. + void SetFootnoteDeadLine( const SwTwips nDeadLine ); + SwTwips GetMaxFootnoteHeight() const { return m_nMaxFootnoteHeight; } + + // returns value for remaining space until the body reaches minimal height + SwTwips GetVarSpace() const; + + // methods needed for layouting + // The parameter <_bCollectOnlyPreviousFootnotes> controls if only footnotes + // that are positioned before the this footnote boss-frame have to be + // collected. + void CollectFootnotes( const SwContentFrame* _pRef, + SwFootnoteBossFrame* _pOld, + SwFootnoteFrames& _rFootnoteArr, + const bool _bCollectOnlyPreviousFootnotes = false ); + void MoveFootnotes_( SwFootnoteFrames &rFootnoteArr, bool bCalc = false ); + void MoveFootnotes( const SwContentFrame *pSrc, SwContentFrame *pDest, + SwTextFootnote const *pAttr ); + + // should AdjustNeighbourhood be called (or Grow/Shrink)? + SwNeighbourAdjust NeighbourhoodAdjustment() const + { return IsPageFrame() ? SwNeighbourAdjust::OnlyAdjust : NeighbourhoodAdjustment_(); } +}; + +inline const SwLayoutFrame *SwFootnoteBossFrame::FindBodyCont() const +{ + return const_cast<SwFootnoteBossFrame*>(this)->FindBodyCont(); +} + +inline const SwFootnoteContFrame *SwFootnoteBossFrame::FindFootnoteCont() const +{ + return const_cast<SwFootnoteBossFrame*>(this)->FindFootnoteCont(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/ftnfrm.hxx b/sw/source/core/inc/ftnfrm.hxx new file mode 100644 index 000000000..34dc89bc1 --- /dev/null +++ b/sw/source/core/inc/ftnfrm.hxx @@ -0,0 +1,165 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_FTNFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_FTNFRM_HXX + +#include "layfrm.hxx" + +class SwContentFrame; +class SwRootFrame; +class SwTextNode; +class SwTextFootnote; +class SwBorderAttrs; +class SwFootnoteFrame; + +void sw_RemoveFootnotes( SwFootnoteBossFrame* pBoss, bool bPageOnly, bool bEndNotes ); + +namespace sw { + +void RemoveFootnotesForNode( + SwRootFrame const& rLayout, SwTextNode const& rTextNode, + std::vector<std::pair<sal_Int32, sal_Int32>> const*const pExtents); + +} + +// There exists a special container frame on a page for footnotes. It's called +// SwFootnoteContFrame. Each footnote is separated by a SwFootnoteFrame which contains +// the text frames of a footnote. SwFootnoteFrame can be split and will then +// continue on another page. +class SwFootnoteContFrame: public SwLayoutFrame +{ + static SwFootnoteFrame* AddChained(bool bAppend, SwFrame *pNewUpper, bool bDefaultFormat); + +public: + SwFootnoteContFrame( SwFrameFormat*, SwFrame* ); + + const SwFootnoteFrame* FindFootNote() const; + + static inline SwFootnoteFrame* AppendChained(SwFrame* pThis, bool bDefaultFormat); + static inline SwFootnoteFrame* PrependChained(SwFrame* pThis, bool bDefaultFormat); + + virtual SwTwips ShrinkFrame( SwTwips, bool bTst = false, bool bInfo = false ) override; + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) override; + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual void PaintSwFrameShadowAndBorder( + const SwRect&, + const SwPageFrame* pPage, + const SwBorderAttrs&) const override; + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; + void PaintLine( const SwRect &, const SwPageFrame * ) const; +}; + +inline SwFootnoteFrame* SwFootnoteContFrame::AppendChained(SwFrame* pThis, bool bDefaultFormat) +{ + return AddChained(true, pThis, bDefaultFormat); +} + +inline SwFootnoteFrame* SwFootnoteContFrame::PrependChained(SwFrame* pThis, bool bDefaultFormat) +{ + return AddChained(false, pThis, bDefaultFormat); +} + +class SwFootnoteFrame: public SwLayoutFrame +{ + // Pointer to FootnoteFrame in which the footnote will be continued: + // - 0 no following existent + // - this for the last one + // - otherwise the following FootnoteFrame + SwFootnoteFrame *mpFollow; + SwFootnoteFrame *mpMaster; // FootnoteFrame from which I am the following + SwContentFrame *mpReference; // in this ContentFrame is the footnote reference + SwTextFootnote *mpAttribute; // footnote attribute (for recognition) + + // if true paragraphs in this footnote are NOT permitted to flow backwards + bool mbBackMoveLocked : 1; + // #i49383# - control unlock of position of lower anchored objects. + bool mbUnlockPosOfLowerObjs : 1; + +public: + SwFootnoteFrame( SwFrameFormat*, SwFrame*, SwContentFrame*, SwTextFootnote* ); + + virtual bool IsDeleteForbidden() const override; + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; + + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; + + bool operator<( const SwTextFootnote* pTextFootnote ) const; + +#ifdef DBG_UTIL + const SwContentFrame *GetRef() const; + SwContentFrame *GetRef(); +#else + const SwContentFrame *GetRef() const { return mpReference; } + SwContentFrame *GetRef() { return mpReference; } +#endif + const SwContentFrame *GetRefFromAttr() const; + SwContentFrame *GetRefFromAttr(); + + const SwFootnoteFrame *GetFollow() const { return mpFollow; } + SwFootnoteFrame *GetFollow() { return mpFollow; } + + const SwFootnoteFrame *GetMaster() const { return mpMaster; } + SwFootnoteFrame *GetMaster() { return mpMaster; } + + const SwTextFootnote *GetAttr() const { return mpAttribute; } + SwTextFootnote *GetAttr() { return mpAttribute; } + + void SetFollow( SwFootnoteFrame *pNew ) { mpFollow = pNew; } + void SetMaster( SwFootnoteFrame *pNew ) { mpMaster = pNew; } + void SetRef ( SwContentFrame *pNew ) { mpReference = pNew; } + + void InvalidateNxtFootnoteCnts( SwPageFrame const * pPage ); + + void LockBackMove() { mbBackMoveLocked = true; } + void UnlockBackMove() { mbBackMoveLocked = false;} + bool IsBackMoveLocked() const { return mbBackMoveLocked; } + + // prevents that the last content deletes the SwFootnoteFrame as well (Cut()) + void ColLock() { mbColLocked = true; } + void ColUnlock() { mbColLocked = false; } + + // #i49383# + void UnlockPosOfLowerObjs() + { + mbUnlockPosOfLowerObjs = true; + } + void KeepLockPosOfLowerObjs() + { + mbUnlockPosOfLowerObjs = false; + } + bool IsUnlockPosOfLowerObjs() const + { + return mbUnlockPosOfLowerObjs; + } + + /** search for last content in the current footnote frame + + OD 2005-12-02 #i27138# + + @return SwContentFrame* + pointer to found last content frame. NULL, if none is found. + */ + SwContentFrame* FindLastContent(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/hffrm.hxx b/sw/source/core/inc/hffrm.hxx new file mode 100644 index 000000000..7d070b683 --- /dev/null +++ b/sw/source/core/inc/hffrm.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_HFFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_HFFRM_HXX + +#include "layfrm.hxx" + +class SwHeadFootFrame : public SwLayoutFrame +{ +protected: + void FormatSize(SwTwips nUL, const SwBorderAttrs * pAttrs); + void FormatPrt(SwTwips & nUL, const SwBorderAttrs * pAttrs); + inline bool GetEatSpacing() const; // in hffrm.cxx + +public: + SwHeadFootFrame(SwFrameFormat * pFrame, SwFrame*, SwFrameType aType); + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual SwTwips GrowFrame( SwTwips, + bool bTst = false, bool bInfo = false ) override; + virtual SwTwips ShrinkFrame( SwTwips, + bool bTst = false, bool bInfo = false ) override; + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; +}; + +/// Header in the document layout, inside a page. +class SwHeaderFrame: public SwHeadFootFrame +{ +public: + SwHeaderFrame( SwFrameFormat* pFrame, SwFrame* pSib ) : SwHeadFootFrame(pFrame, pSib, SwFrameType::Header) {}; +}; + +/// Footer in the document layout, inside a page. +class SwFooterFrame: public SwHeadFootFrame +{ +public: + SwFooterFrame( SwFrameFormat* pFrame, SwFrame* pSib ) : SwHeadFootFrame(pFrame, pSib, SwFrameType::Footer) {}; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/ifinishedthreadlistener.hxx b/sw/source/core/inc/ifinishedthreadlistener.hxx new file mode 100644 index 000000000..2ff8197d2 --- /dev/null +++ b/sw/source/core/inc/ifinishedthreadlistener.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_IFINISHEDTHREADLISTENER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_IFINISHEDTHREADLISTENER_HXX + +#include <osl/interlck.h> + +/** interface class to listen on the finish of a thread + + OD 2007-03-30 #i73788# + Note: The thread provides its ThreadID on the finish notification +*/ +class IFinishedThreadListener +{ +public: + virtual ~IFinishedThreadListener() + { + }; + + virtual void NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ) = 0; + +protected: + IFinishedThreadListener() + { + }; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/layact.hxx b/sw/source/core/inc/layact.hxx new file mode 100644 index 000000000..990c0e4b8 --- /dev/null +++ b/sw/source/core/inc/layact.hxx @@ -0,0 +1,213 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_LAYACT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_LAYACT_HXX + +#include <sal/config.h> + +#include <vcl/inputtypes.hxx> +#include <vcl/TaskStopwatch.hxx> +#include <tools/color.hxx> + +#include <ctime> +#include <memory> + +#include <swrect.hxx> + +class OutputDevice; +class SwRootFrame; +class SwLayoutFrame; +class SwPageFrame; +class SwFlyFrame; +class SwContentFrame; +class SwTabFrame; +class SwViewShellImp; +class SwContentNode; +class SwWait; + +/** + * The usage of LayAction is always the same: + * + * 1. Generation of the LayAction object. + * 2. Specifying the wanted behaviour via the Set-methods + * 3. Calling Action() + * 4. Soon after that the destruction of the object + * + * The object registers at the SwViewShellImp in the ctor and deregisters not until + * the dtor! + * It's a typical stack object. + */ +class SwLayAction +{ + SwRootFrame *m_pRoot; + SwViewShellImp *m_pImp; // here the action logs in and off + TaskStopwatch* m_pWatch; + + // For the sake of optimization, so that the tables stick a bit better to + // the Cursor when hitting return/backspace in front of one. + // The first TabFrame that paints itself (per page) adds itself to the pointer. + // The ContentFrames beneath the page do not need to deregister at the Shell for + // painting. + const SwTabFrame *m_pOptTab; + + std::unique_ptr<SwWait> m_pWait; + + // If a paragraph (or anything else) moved more than one page when + // formatting, it adds its new page number here. + // The InternalAction can then take the appropriate steps. + sal_uInt16 m_nPreInvaPage; + + std::clock_t m_nStartTicks; // The Action's starting time; if too much time passes the + // WaitCursor can be enabled via CheckWaitCursor() + + sal_uInt16 m_nEndPage; // StatBar control + sal_uInt16 m_nCheckPageNum; // CheckPageDesc() was delayed if != USHRT_MAX + // check from this page onwards + + bool m_bPaint; // painting or only formatting? + bool m_bComplete; // Format everything or just the visible Area? + bool m_bCalcLayout; // Complete reformatting? + bool m_bAgain; // For the automatically repeated Action if Pages are deleted + bool m_bNextCycle; // Reset on the first invalid Page + bool m_bReschedule; // Call Reschedule depending on Progress? + bool m_bInterrupt; // For termination the layouting + bool m_bCheckPages; // Run CheckPageDescs() or delay it + bool m_bUpdateExpFields; // Is set if, after Formatting, we need to do another round for ExpField + bool m_bBrowseActionStop; // Terminate Action early (as per bInput) and leave the rest to the Idler + bool m_bWaitAllowed; // Waitcursor allowed? + bool m_bPaintExtraData; // Painting line numbers (or similar) enabled? + bool m_bActionInProgress; // Is set in Action() at the beginning and deleted at the end + + // OD 14.04.2003 #106346# - new flag for content formatting on interrupt. + bool mbFormatContentOnInterrupt; + + // for loop control by disabling in-row splitting within embedded tables + const SwPageFrame *m_pCurPage; + sal_uInt16 m_nTabLevel; // embedding level + sal_uInt32 m_nCallCount; // calling FormatLayoutTab on the same page + + void PaintContent( const SwContentFrame *, const SwPageFrame *, + const SwRect &rOldRect, long nOldBottom ); + bool PaintWithoutFlys( const SwRect &, const SwContentFrame *, + const SwPageFrame * ); + inline bool PaintContent_( const SwContentFrame *, const SwPageFrame *, + const SwRect & ); + + bool FormatLayout( OutputDevice* pRenderContext, SwLayoutFrame *, bool bAddRect = true ); + bool FormatLayoutTab( SwTabFrame *, bool bAddRect ); + bool FormatContent( const SwPageFrame* pPage ); + void FormatContent_( const SwContentFrame* pContent, + const SwPageFrame* pPage ); + bool IsShortCut( SwPageFrame *& ); + + bool TurboAction(); + bool TurboAction_( const SwContentFrame * ); + void InternalAction(OutputDevice* pRenderContext); + + static SwPageFrame *CheckFirstVisPage( SwPageFrame *pPage ); + + bool RemoveEmptyBrowserPages(); + +public: + SwLayAction(SwRootFrame *pRt, SwViewShellImp *pImp, TaskStopwatch* pWatch = nullptr); + ~SwLayAction(); + + void SetCheckPages ( bool bNew ) { m_bCheckPages = bNew; } + void SetBrowseActionStop( bool bNew ) { m_bBrowseActionStop = bNew; } + void SetNextCycle ( bool bNew ) { m_bNextCycle = bNew; } + + bool IsWaitAllowed() const { return m_bWaitAllowed; } + bool IsNextCycle() const { return m_bNextCycle; } + bool IsPaint() const { return m_bPaint; } + bool IsReschedule() const { return m_bReschedule; } + bool IsIdle() const { return m_pWatch != nullptr; } + bool IsPaintExtraData() const { return m_bPaintExtraData; } + bool IsInterrupt(); + + // adjusting Action to the wanted behaviour + void SetPaint ( bool bNew ) { m_bPaint = bNew; } + void SetComplete ( bool bNew ) { m_bComplete = bNew; } + void SetStatBar ( bool bNew ); + void SetCalcLayout ( bool bNew ) { m_bCalcLayout = bNew; } + void SetReschedule ( bool bNew ) { m_bReschedule = bNew; } + void SetWaitAllowed ( bool bNew ) { m_bWaitAllowed = bNew; } + + void SetAgain() { m_bAgain = true; } + void SetUpdateExpFields() {m_bUpdateExpFields = true; } + + inline void SetCheckPageNum( sal_uInt16 nNew ); + void SetCheckPageNumDirect( sal_uInt16 nNew ) { m_nCheckPageNum = nNew; } + + void Action(OutputDevice* pRenderContext); // here it begins + void Reset(); // back to CTor-defaults + + bool IsAgain() const { return m_bAgain; } + bool IsComplete() const { return m_bComplete; } + bool IsExpFields() const { return m_bUpdateExpFields; } + bool IsCalcLayout() const { return m_bCalcLayout; } + bool IsCheckPages() const { return m_bCheckPages; } + bool IsBrowseActionStop() const { return m_bBrowseActionStop; } + bool IsActionInProgress() const { return m_bActionInProgress; } + + sal_uInt16 GetCheckPageNum() const { return m_nCheckPageNum; } + + // others should be able to activate the WaitCursor, too + void CheckWaitCursor(); + + // #i28701# - method is now public; + // delete 2nd parameter, because it's not used; + void FormatLayoutFly( SwFlyFrame * ); + // #i28701# - method is now public + void FormatFlyContent( const SwFlyFrame * ); + +}; + +class SwLayIdle +{ + TaskStopwatch m_aWatch; + SwRootFrame *pRoot; + SwViewShellImp *pImp; // The Idler registers and deregisters here + SwContentNode *pContentNode; // The current cursor position is saved here + sal_Int32 nTextPos; + bool bPageValid; // Were we able to evaluate everything on the whole page? +#ifdef DBG_UTIL + bool m_bIndicator; + + void ShowIdle( Color eName ); +#endif + + bool IsInterrupt(); + enum IdleJobType{ ONLINE_SPELLING, AUTOCOMPLETE_WORDS, WORD_COUNT, SMART_TAGS }; + bool DoIdleJob_( const SwContentFrame*, IdleJobType ); + bool DoIdleJob( IdleJobType, bool bVisAreaOnly ); + +public: + SwLayIdle( SwRootFrame *pRt, SwViewShellImp *pImp ); + ~SwLayIdle(); +}; + +inline void SwLayAction::SetCheckPageNum( sal_uInt16 nNew ) +{ + if ( nNew < m_nCheckPageNum ) + m_nCheckPageNum = nNew; +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_LAYACT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/laycache.hxx b/sw/source/core/inc/laycache.hxx new file mode 100644 index 000000000..6f3a5a00d --- /dev/null +++ b/sw/source/core/inc/laycache.hxx @@ -0,0 +1,69 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_LAYCACHE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_LAYCACHE_HXX + +#include <sal/types.h> +#include <memory> + +class SwDoc; +class SwLayCacheImpl; +class SvStream; + +/* + * This class allows to save layout information in the file and it contains + * this information after loading of a file. + * Call Write(..) with a stream and the document to save and the page break + * information of the document will be written. + * Call Read(..) with a stream and the member pLayCacheImpl will + * read the information from the stream and store it in an internal structure. + * There's a simple locking mechanism at these classes, + * if somebody reads the information, he increments the lock count by 1, + * during the Read(..) function the lock count will set to $8000. + */ +class SwLayoutCache +{ + std::unique_ptr<SwLayCacheImpl> pImpl; + sal_uInt16 nLockCount; + +public: + SwLayoutCache(); + ~SwLayoutCache(); + + void Read( SvStream &rStream ); + static void Write( SvStream &rStream, const SwDoc& rDoc ); + + void ClearImpl(); + bool IsLocked() const { return nLockCount > 0; } + sal_uInt16& GetLockCount() { return nLockCount; } + SwLayCacheImpl *LockImpl() + { if( nLockCount & 0x8000 ) return nullptr; + if ( pImpl ) + ++nLockCount; + return pImpl.get(); } + void UnlockImpl() { --nLockCount; } + +#ifdef DBG_UTIL + bool CompareLayout( const SwDoc& rDoc ) const; +#endif +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/layfrm.hxx b/sw/source/core/inc/layfrm.hxx new file mode 100644 index 000000000..1a725dc34 --- /dev/null +++ b/sw/source/core/inc/layfrm.hxx @@ -0,0 +1,226 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_LAYFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_LAYFRM_HXX + +#include "frame.hxx" +#include <swdllapi.h> + +class SwAnchoredObject; +class SwContentFrame; +class SwFormatCol; +struct SwCursorMoveState; +class SwFrameFormat; +class SwBorderAttrs; +class SwFormatFrameSize; +class SwCellFrame; + +/// A layout frame is a frame that contains other frames (m_pLower), e.g. SwPageFrame or SwTabFrame. +class SW_DLLPUBLIC SwLayoutFrame: public SwFrame +{ + // The SwFrame in disguise + friend class SwFlowFrame; + friend class SwFrame; + + // Releases the Lower while restructuring columns + friend SwFrame* SaveContent( SwLayoutFrame *, SwFrame * ); + friend void RestoreContent( SwFrame *, SwLayoutFrame *, SwFrame *pSibling ); + +protected: + + virtual void DestroyImpl() override; + virtual ~SwLayoutFrame() override; + + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + + SwFrame * m_pLower; + std::vector<SwAnchoredObject*> m_VertPosOrientFramesFor; + + virtual SwTwips ShrinkFrame( SwTwips, bool bTst = false, bool bInfo = false ) override; + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) override; + + long CalcRel( const SwFormatFrameSize &rSz ) const; + +public: + // --> #i28701# + + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const; + void RefreshLaySubsidiary( const SwPageFrame*, const SwRect& ) const; + void RefreshExtraData( const SwRect & ) const; + + /// Change size of lowers proportionally + void ChgLowersProp( const Size& rOldSize ); + + void AdjustColumns( const SwFormatCol *pCol, bool bAdjustAttributes ); + + void ChgColumns( const SwFormatCol &rOld, const SwFormatCol &rNew, + const bool bChgFootnote = false ); + + /// Paints the column separation line for the inner columns + void PaintColLines( const SwRect &, const SwFormatCol &, + const SwPageFrame * ) const; + + virtual bool FillSelection( SwSelectionList& rList, const SwRect& rRect ) const override; + + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, + SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override; + + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; + + /** + * Finds the closest Content for the SPoint + * Is used for Pages, Flys and Cells if GetModelPositionForViewPoint failed + */ + const SwContentFrame* GetContentPos( Point &rPoint, const bool bDontLeave, + const bool bBodyOnly = false, + SwCursorMoveState *pCMS = nullptr, + const bool bDefaultExpand = true ) const; + + SwLayoutFrame( SwFrameFormat*, SwFrame* ); + + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + const SwFrame *Lower() const { return m_pLower; } + SwFrame *Lower() { return m_pLower; } + const SwContentFrame *ContainsContent() const; + inline SwContentFrame *ContainsContent(); + const SwCellFrame *FirstCell() const; + inline SwCellFrame *FirstCell(); + + /** + * Method <ContainsAny()> doesn't investigate content of footnotes by default. + * But under certain circumstances this investigation is intended. + * Thus, introduce new optional parameter <_bInvestigateFootnoteForSections>. + * It's default is <false>, still indicating that content of footnotes isn't + * investigated for sections. + */ + const SwFrame *ContainsAny( const bool _bInvestigateFootnoteForSections = false ) const; + inline SwFrame *ContainsAny( const bool _bInvestigateFootnoteForSections = false ); + bool IsAnLower( const SwFrame * ) const; + + virtual const SwFrameFormat *GetFormat() const; + virtual SwFrameFormat *GetFormat(); + void SetFrameFormat( SwFrameFormat* ); + + /** + * Moving the Footnotes of all Lowers - starting from StartContent + * + * @returns true if at least one Footnote was moved + * Calls the page number update if bFootnoteNums is set + */ + bool MoveLowerFootnotes( SwContentFrame *pStart, SwFootnoteBossFrame *pOldBoss, + SwFootnoteBossFrame *pNewBoss, const bool bFootnoteNums ); + + // --> #i28701# - change purpose of method and its name + // --> #i44016# - add parameter <_bUnlockPosOfObjs> to + /// force an unlockposition call for the lower objects. + void NotifyLowerObjs( const bool _bUnlockPosOfObjs = false ); + + /** + * Invalidates the inner Frames, whose width and/or height are + * calculated using percentages. + * Frames that are anchored to this or inner Frames, are also invalidated. + */ + void InvaPercentLowers( SwTwips nDiff = 0 ); + + /// Called by Format for Frames and Areas with columns + void FormatWidthCols( const SwBorderAttrs &, const SwTwips nBorder, + const SwTwips nMinHeight ); + + /** + * InnerHeight returns the height of the content and may be bigger or + * less than the PrtArea-Height of the layoutframe himself + */ + SwTwips InnerHeight() const; + + /** method to check relative position of layout frame to + a given layout frame. + + refactoring of pseudo-local method <lcl_Apres(..)> in + <txtftn.cxx> for #104840#. + + @param _aCheckRefLayFrame + constant reference of an instance of class <SwLayoutFrame> which + is used as the reference for the relative position check. + + @return true, if <this> is positioned before the layout frame <p> + */ + bool IsBefore( const SwLayoutFrame* _pCheckRefLayFrame ) const; + + const SwFrame* GetLastLower() const; + inline SwFrame* GetLastLower(); + + virtual void PaintBreak() const; + + void SetVertPosOrientFrameFor(SwAnchoredObject *pObj) + { + m_VertPosOrientFramesFor.push_back(pObj); + } + + void ClearVertPosOrientFrameFor(SwAnchoredObject *pObj) + { + m_VertPosOrientFramesFor.erase( + std::remove(m_VertPosOrientFramesFor.begin(), + m_VertPosOrientFramesFor.end(), pObj), + m_VertPosOrientFramesFor.end()); + } +}; + +/** + * In order to save us from duplicating implementations, we cast here + * a little. + */ +inline SwContentFrame* SwLayoutFrame::ContainsContent() +{ + return const_cast<SwContentFrame*>(static_cast<const SwLayoutFrame*>(this)->ContainsContent()); +} + +inline SwCellFrame* SwLayoutFrame::FirstCell() +{ + return const_cast<SwCellFrame*>(static_cast<const SwLayoutFrame*>(this)->FirstCell()); +} + +inline SwFrame* SwLayoutFrame::ContainsAny( const bool _bInvestigateFootnoteForSections ) +{ + return const_cast<SwFrame*>(static_cast<const SwLayoutFrame*>(this)->ContainsAny( _bInvestigateFootnoteForSections )); +} + +/** + * These SwFrame inlines are here, so that frame.hxx does not need to include layfrm.hxx + */ +inline bool SwFrame::IsColBodyFrame() const +{ + return mnFrameType == SwFrameType::Body && GetUpper()->IsColumnFrame(); +} + +inline bool SwFrame::IsPageBodyFrame() const +{ + return mnFrameType == SwFrameType::Body && GetUpper()->IsPageFrame(); +} + +inline SwFrame* SwLayoutFrame::GetLastLower() +{ + return const_cast<SwFrame*>(static_cast<const SwLayoutFrame*>(this)->GetLastLower()); +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_LAYFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/layouter.hxx b/sw/source/core/inc/layouter.hxx new file mode 100644 index 000000000..a3979cf46 --- /dev/null +++ b/sw/source/core/inc/layouter.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_LAYOUTER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_LAYOUTER_HXX + +#include <swtypes.hxx> +#include <unordered_map> +#include <memory> + +class SwEndnoter; +class SwDoc; +class SwSectionFrame; +class SwFootnoteFrame; +class SwPageFrame; +class SwLooping; + +class SwMovedFwdFramesByObjPos; +class SwTextFrame; +class SwRowFrame; +class SwObjsMarkedAsTmpConsiderWrapInfluence; +class SwAnchoredObject; +class SwFlowFrame; +class SwLayoutFrame; + +#define LOOP_PAGE 1 + +class SwLayouter +{ + std::unique_ptr<SwEndnoter> mpEndnoter; + std::unique_ptr<SwLooping> mpLooping; + void CollectEndnotes_( SwSectionFrame* pSect ); + bool StartLooping( SwPageFrame const * pPage ); + + // --> #i28701# + std::unique_ptr<SwMovedFwdFramesByObjPos> mpMovedFwdFrames; + // --> #i35911# + std::unique_ptr<SwObjsMarkedAsTmpConsiderWrapInfluence> mpObjsTmpConsiderWrapInfl; + +public: + // --> #i65250# + // - data structure to collect moving backward layout information + struct tMoveBwdLayoutInfoKey + { + // frame ID of flow frame + sal_uInt32 mnFrameId; + // position of new upper frame + SwTwips mnNewUpperPosX; + SwTwips mnNewUpperPosY; + // size of new upper frame + SwTwips mnNewUpperWidth; + SwTwips mnNewUpperHeight; + // free space in new upper frame + SwTwips mnFreeSpaceInNewUpper; + + }; +private: + struct fMoveBwdLayoutInfoKeyHash + { + size_t operator()( const tMoveBwdLayoutInfoKey& p_key ) const + { + return p_key.mnFrameId; + } + }; + struct fMoveBwdLayoutInfoKeyEq + { + bool operator()( const tMoveBwdLayoutInfoKey& p_key1, + const tMoveBwdLayoutInfoKey& p_key2 ) const + { + return p_key1.mnFrameId == p_key2.mnFrameId && + p_key1.mnNewUpperPosX == p_key2.mnNewUpperPosX && + p_key1.mnNewUpperPosY == p_key2.mnNewUpperPosY && + p_key1.mnNewUpperWidth == p_key2.mnNewUpperWidth && + p_key1.mnNewUpperHeight == p_key2.mnNewUpperHeight && + p_key1.mnFreeSpaceInNewUpper == p_key2.mnFreeSpaceInNewUpper; + } + }; + std::unordered_map< tMoveBwdLayoutInfoKey, sal_uInt16, + fMoveBwdLayoutInfoKeyHash, + fMoveBwdLayoutInfoKeyEq > maMoveBwdLayoutInfo; +public: + SwLayouter(); + ~SwLayouter(); + void InsertEndnotes( SwSectionFrame const * pSect ); + void CollectEndnote( SwFootnoteFrame* pFootnote ); + bool HasEndnotes() const; + + void LoopControl( SwPageFrame* pPage ); + void EndLoopControl(); + void LoopingLouieLight( const SwDoc& rDoc, const SwTextFrame& rFrame ); + + static void CollectEndnotes( SwDoc* pDoc, SwSectionFrame* pSect ); + static bool Collecting( SwDoc* pDoc, SwSectionFrame const * pSect, SwFootnoteFrame* pFootnote ); + static bool StartLoopControl( SwDoc* pDoc, SwPageFrame const *pPage ); + + // --> #i28701# + static void ClearMovedFwdFrames( const SwDoc& _rDoc ); + static void InsertMovedFwdFrame( const SwDoc& _rDoc, + const SwTextFrame& _rMovedFwdFrameByObjPos, + const sal_uInt32 _nToPageNum ); + static bool FrameMovedFwdByObjPos( const SwDoc& _rDoc, + const SwTextFrame& _rTextFrame, + sal_uInt32& _ornToPageNum ); + // --> #i40155# - unmark given frame as to be moved forward. + static void RemoveMovedFwdFrame( const SwDoc& _rDoc, + const SwTextFrame& _rTextFrame ); + // --> #i26945# + static bool DoesRowContainMovedFwdFrame( const SwDoc& _rDoc, + const SwRowFrame& _rRowFrame ); + + // --> #i35911# + static void ClearObjsTmpConsiderWrapInfluence( const SwDoc& _rDoc ); + static void InsertObjForTmpConsiderWrapInfluence( + const SwDoc& _rDoc, + SwAnchoredObject& _rAnchoredObj ); + static void RemoveObjForTmpConsiderWrapInfluence( + const SwDoc& _rDoc, + SwAnchoredObject& _rAnchoredObj ); + + // --> #i65250# + static bool MoveBwdSuppressed( const SwDoc& p_rDoc, + const SwFlowFrame& p_rFlowFrame, + const SwLayoutFrame& p_rNewUpperFrame ); + static void ClearMoveBwdLayoutInfo( const SwDoc& p_rDoc ); +}; + +extern void LOOPING_LOUIE_LIGHT( bool bCondition, const SwTextFrame& rTextFrame ); + +#endif // INCLUDED_SW_SOURCE_CORE_INC_LAYOUTER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/movedfwdfrmsbyobjpos.hxx b/sw/source/core/inc/movedfwdfrmsbyobjpos.hxx new file mode 100644 index 000000000..c42944060 --- /dev/null +++ b/sw/source/core/inc/movedfwdfrmsbyobjpos.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_MOVEDFWDFRMSBYOBJPOS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_MOVEDFWDFRMSBYOBJPOS_HXX + +#include <map> +#include <sal/types.h> + +class SwTextNode; +class SwTextFrame; +// --> #i26945# +class SwRowFrame; + +typedef std::map< const SwTextNode*, const sal_uInt32 > NodeMap; + +class SwMovedFwdFramesByObjPos +{ + private: + NodeMap maMovedFwdFrames; + + public: + SwMovedFwdFramesByObjPos(); + ~SwMovedFwdFramesByObjPos(); + + void Insert( const SwTextFrame& _rMovedFwdFrameByObjPos, + const sal_uInt32 _nToPageNum ); + + // --> #i40155# + void Remove( const SwTextFrame& _rTextFrame ); + + bool FrameMovedFwdByObjPos( const SwTextFrame& _rTextFrame, + sal_uInt32& _ornToPageNum ) const; + + // --> #i26945# + bool DoesRowContainMovedFwdFrame( const SwRowFrame& _rRowFrame ) const; + + void Clear() { maMovedFwdFrames.clear(); }; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/mvsave.hxx b/sw/source/core/inc/mvsave.hxx new file mode 100644 index 000000000..73e74d7d1 --- /dev/null +++ b/sw/source/core/inc/mvsave.hxx @@ -0,0 +1,199 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_MVSAVE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_MVSAVE_HXX + +#include <vcl/keycod.hxx> +#include <IDocumentMarkAccess.hxx> +#include <vector> +#include <deque> +#include <o3tl/typed_flags_set.hxx> + +namespace sfx2 { + class MetadatableUndo; +} + +class SvNumberFormatter; +class SwDoc; +class SwFormatAnchor; +class SwFrameFormat; +class SwIndex; +class SwNodeIndex; +class SwNodeRange; +class SwPaM; +class SwNode; +struct SwPosition; + +namespace sw::mark +{ + class IMark; + + class SaveBookmark + { + public: + SaveBookmark( + const ::sw::mark::IMark& rBkmk, + const SwNodeIndex& rMvPos, + const SwIndex* pIdx); + void SetInDoc(SwDoc* pDoc, + const SwNodeIndex&, + const SwIndex* pIdx =nullptr); + + private: + OUString m_aName; + OUString m_aShortName; + vcl::KeyCode m_aCode; + IDocumentMarkAccess::MarkType m_eOrigBkmType; + sal_uLong m_nNode1; + sal_uLong m_nNode2; + sal_Int32 m_nContent1; + sal_Int32 m_nContent2; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndo; + }; + + enum class RestoreMode { Flys = 1, NonFlys = 2, All = 3 }; + + /// Takes care of storing relevant attributes of an SwTextNode before split, then restore them on the new node. + class ContentIdxStore + { + public: + + virtual void Clear() =0; + virtual bool Empty() =0; + virtual void Save(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit=false) =0; + virtual void Restore(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nOffset=0, bool bAuto = false, RestoreMode = RestoreMode::All) =0; + virtual void Restore(SwNode& rNd, sal_Int32 nLen, sal_Int32 nCorrLen, RestoreMode = RestoreMode::All) =0; + virtual ~ContentIdxStore() {}; + static std::shared_ptr<ContentIdxStore> Create(); + }; +} + +namespace o3tl { + template<> struct typed_flags<sw::mark::RestoreMode> : is_typed_flags<sw::mark::RestoreMode, 3> {}; +} + +void DelBookmarks(const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + std::vector< ::sw::mark::SaveBookmark> * SaveBkmk =nullptr, + const SwIndex* pSttIdx =nullptr, + const SwIndex* pEndIdx =nullptr); + +/** data structure to temporarily hold fly anchor positions relative to some + * location. */ +struct SaveFly +{ + sal_uLong nNdDiff; /// relative node difference + sal_Int32 nContentIndex; ///< index in node + SwFrameFormat* pFrameFormat; /// the fly's frame format + bool isAtInsertNode; ///< if true, anchor _at_ insert node index + + SaveFly( sal_uLong nNodeDiff, sal_Int32 const nCntntIdx, SwFrameFormat* pFormat, bool bInsert ) + : nNdDiff(nNodeDiff) + , nContentIndex(nCntntIdx) + , pFrameFormat(pFormat) + , isAtInsertNode(bInsert) + { } +}; + +typedef std::deque< SaveFly > SaveFlyArr; + +void RestFlyInRange( SaveFlyArr& rArr, const SwPosition& rSttIdx, + const SwNodeIndex* pInsPos ); +void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ); +void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, + SaveFlyArr& rArr, bool bMoveAllFlys ); + +void DelFlyInRange( const SwNodeIndex& rMkNdIdx, + const SwNodeIndex& rPtNdIdx, + SwIndex const* pMkIdx = nullptr, + SwIndex const* pPtIdx = nullptr); + +class SwDataChanged +{ + const SwPaM* m_pPam; + const SwPosition* m_pPos; + SwDoc* m_pDoc; + sal_Int32 m_nContent; + +public: + SwDataChanged( const SwPaM& rPam ); + SwDataChanged( SwDoc* pDoc, const SwPosition& rPos ); + ~SwDataChanged(); + + sal_Int32 GetContent() const { return m_nContent; } +}; + +/** + * Function declarations so that everything below the CursorShell can + * move the Cursor once in a while. + * These functions do not call the SwDoc::Corr methods! + */ +void PaMCorrAbs( const SwPaM& rRange, + const SwPosition& rNewPos ); + +/// Sets all PaMs in OldNode to relative Pos +void PaMCorrRel( const SwNodeIndex &rOldNode, + const SwPosition &rNewPos, + const sal_Int32 nOffset = 0 ); + +/** + * Helper to copy paragraph-bound Flys. + * By sorting by order number, we try to retain the layout. + */ +class ZSortFly +{ + const SwFrameFormat* m_pFormat; + const SwFormatAnchor* m_pAnchor; + sal_uInt32 m_nOrdNum; + +public: + ZSortFly( const SwFrameFormat* pFrameFormat, const SwFormatAnchor* pFlyAnchor, + sal_uInt32 nArrOrdNum ); + + bool operator==( const ZSortFly& ) const { return false; } + bool operator<( const ZSortFly& rCmp ) const + { return m_nOrdNum < rCmp.m_nOrdNum; } + + const SwFrameFormat* GetFormat() const { return m_pFormat; } + const SwFormatAnchor* GetAnchor() const { return m_pAnchor; } +}; + +class SwTableNumFormatMerge +{ + SvNumberFormatter* pNFormat; +public: + SwTableNumFormatMerge( const SwDoc& rSrc, SwDoc& rDest ); + ~SwTableNumFormatMerge(); +}; + +class SaveRedlEndPosForRestore +{ + std::vector<SwPosition*> mvSavArr; + std::unique_ptr<SwNodeIndex> mpSaveIndex; + sal_Int32 mnSaveContent; + +public: + SaveRedlEndPosForRestore( const SwNodeIndex& rInsIdx, sal_Int32 nContent ); + ~SaveRedlEndPosForRestore(); + void Restore(); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_MVSAVE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/node2lay.hxx b/sw/source/core/inc/node2lay.hxx new file mode 100644 index 000000000..9faadd574 --- /dev/null +++ b/sw/source/core/inc/node2lay.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_NODE2LAY_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_NODE2LAY_HXX + +#include <tools/solar.h> +#include <memory> + +/** + * This class connects the Nodes with the Layouts. + * It provides an intelligent iterator over Frames belonging to the Node or + * Node Range. Depending on the purpose of iterating (e.g. to insert other + * Frames before or after the Frames) Master/Follows are recognized and only + * the relevant ones are returned. Repeated table headers are also taken + * into account. + * It's possible to iterate over SectionNodes that are either not directly + * assigned to a SectionFrame or to multiple ones due to nesting. + * + * This class is an interface between the method and a SwClientIter: it + * chooses the right SwModify depending on the task, creates a SwClientIter + * and filters its iterations depending on the task. + * The task is determined by the choice of class. + * + * 1. Collecting the UpperFrames (so that later RestoreUpperFrames can be called) + * is called by MakeFrames, if there's no PrevNext (before/after we can insert + * the Frames). + * 2. Inserting the Frames before/after which the new Frames of a Node need to + * be inserted, is also called by MakeFrames. + */ + +class SwNode2LayImpl; +class SwFrame; +class SwLayoutFrame; +class SwNode; +class SwNodes; +class Point; + +class SwNode2Layout +{ + std::unique_ptr<SwNode2LayImpl> pImpl; +public: + /// Use this ctor for inserting before/after rNd + /// @param nIdx is the index of the to-be-inserted Node + SwNode2Layout( const SwNode& rNd, sal_uLong nIdx ); + ~SwNode2Layout(); + SwFrame* NextFrame(); + SwLayoutFrame* UpperFrame( SwFrame* &rpFrame, const SwNode& rNode ); + + SwFrame *GetFrame( const Point* pDocPos ) const; +}; + +class SwNode2LayoutSaveUpperFrames +{ + std::unique_ptr<SwNode2LayImpl> pImpl; +public: + /// Use this ctor for collecting the UpperFrames + SwNode2LayoutSaveUpperFrames( const SwNode& rNd ); + ~SwNode2LayoutSaveUpperFrames(); + + void RestoreUpperFrames( SwNodes& rNds, sal_uLong nStt, sal_uLong nEnd ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/noteurl.hxx b/sw/source/core/inc/noteurl.hxx new file mode 100644 index 000000000..fd52fffb5 --- /dev/null +++ b/sw/source/core/inc/noteurl.hxx @@ -0,0 +1,32 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_NOTEURL_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_NOTEURL_HXX + +class SwNoteURL +{ +}; + +// globale Variable, in NoteURL.Cxx angelegt +extern SwNoteURL *pNoteURL; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/notxtfrm.hxx b/sw/source/core/inc/notxtfrm.hxx new file mode 100644 index 000000000..c958da594 --- /dev/null +++ b/sw/source/core/inc/notxtfrm.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_NOTXTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_NOTXTFRM_HXX + +#include "cntfrm.hxx" +#include <node.hxx> +// MM02 +#include <svx/sdr/contact/viewcontact.hxx> + +class SwNoTextNode; +class OutputDevice; +class SwBorderAttrs; +struct SwCursorMoveState; + +class SwNoTextFrame: public SwContentFrame +{ +private: + friend void FrameFinit(); + const Size& GetSize() const; + + void Format ( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + void PaintPicture( vcl::RenderContext*, const SwRect& ) const; + + virtual void DestroyImpl() override; + virtual ~SwNoTextFrame() override; + + // RotateFlyFrame3 add TransformableSwFrame + std::unique_ptr< TransformableSwFrame > mpTransformableSwFrame; + + // RotateFlyFrame3 - Support for inner frame of a SwGrfNode. + // Only for local data extraction. To uniquely access information + // for local transformation, use getFrameArea(Print)Transformation. + friend double getLocalFrameRotation_from_SwNoTextFrame(const SwNoTextFrame& rNoTextFrame); + double getLocalFrameRotation() const; + + void ClearCache(); + + // MM02 + std::unique_ptr<sdr::contact::ViewContact> mpViewContact; + sdr::contact::ViewContact& GetViewContact() const; + +protected: + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + +public: + SwNoTextFrame( SwNoTextNode * const, SwFrame* ); + + const SwContentNode *GetNode() const + { return static_cast<SwContentNode const*>(GetDep()); } + SwContentNode *GetNode() + { return static_cast<SwContentNode *>(GetDep()); } + + virtual bool LeftMargin(SwPaM *) const override; + virtual bool RightMargin(SwPaM *, bool bAPI = false) const override; + + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + virtual bool GetCharRect( SwRect &, const SwPosition&, + SwCursorMoveState* = nullptr, bool bAllowFarAway = true ) const override; + virtual bool GetModelPositionForViewPoint(SwPosition* pPos, Point& aPoint, + SwCursorMoveState* = nullptr, bool bTestBackground = false) const override; + + void GetGrfArea( SwRect &rRect, SwRect * ) const; + + bool IsTransparent() const; + + void StopAnimation( OutputDevice* = nullptr ) const; + bool HasAnimation() const; + + // RotateFlyFrame3 - Support for Transformations + bool isTransformableSwFrame() const { return bool(mpTransformableSwFrame); } + TransformableSwFrame* getTransformableSwFrame() { return mpTransformableSwFrame.get(); } + const TransformableSwFrame* getTransformableSwFrame() const { return mpTransformableSwFrame.get(); } + + // RotateFlyFrame3 - Support for Transformations + virtual basegfx::B2DHomMatrix getFrameAreaTransformation() const override; + virtual basegfx::B2DHomMatrix getFramePrintAreaTransformation() const override; + + // RotateFlyFrame3 - Support for Transformations + virtual void transform_translate(const Point& rOffset) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/objectformatter.hxx b/sw/source/core/inc/objectformatter.hxx new file mode 100644 index 000000000..99d8644f2 --- /dev/null +++ b/sw/source/core/inc/objectformatter.hxx @@ -0,0 +1,174 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_OBJECTFORMATTER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_OBJECTFORMATTER_HXX + +#include <sal/types.h> +#include <memory> + +class SwFrame; +// #i26945# +class SwTextFrame; +class SwLayoutFrame; +class SwPageFrame; +class SwAnchoredObject; +class SwLayAction; +// OD 2004-10-04 #i26945# +class SwPageNumAndTypeOfAnchors; + +// #i28701# +// Format floating screen objects, which are anchored at the given anchor frame +// and registered at the given page frame. + +class SwObjectFormatter +{ + private: + // page frame, at which the floating screen objects are registered. + const SwPageFrame& mrPageFrame; + + // value of document compatibility option 'Consider wrapping style on + // object positioning' + const bool mbConsiderWrapOnObjPos; + + // layout action calling the format of the floating screen objects + SwLayAction* mpLayAction; + + // data structure to collect page number of object's 'anchor' + // #i26945# + std::unique_ptr<SwPageNumAndTypeOfAnchors> mpPgNumAndTypeOfAnchors; + + /** helper method for method <FormatObj_(..)> - performs the intrinsic + format of the layout of the given layout frame and all its lower + layout frames. + + #i28701# + IMPORTANT NOTE: + Method corresponds to methods <SwLayAction::FormatLayoutFly(..)> and + <SwLayAction::FormatLayout(..)>. Thus, its code for the formatting have + to be synchronised. + */ + void FormatLayout_( SwLayoutFrame& _rLayoutFrame ); + + /** helper method for method <FormatObj_(..)> - performs the intrinsic + format of the content of the given floating screen object. + + #i28701# + */ + void FormatObjContent( SwAnchoredObject& _rAnchoredObj ); + + protected: + SwObjectFormatter( const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction, + const bool _bCollectPgNumOfAnchors = false ); + + static std::unique_ptr<SwObjectFormatter> CreateObjFormatter( SwFrame& _rAnchorFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); + + virtual SwFrame& GetAnchorFrame() = 0; + + const SwPageFrame& GetPageFrame() const + { + return mrPageFrame; + } + + bool ConsiderWrapOnObjPos() const + { + return mbConsiderWrapOnObjPos; + } + + SwLayAction* GetLayAction() + { + return mpLayAction; + } + + /** performs the intrinsic format of a given floating screen object and its content. + */ + void FormatObj_( SwAnchoredObject& _rAnchoredObj ); + + /** invokes the intrinsic format method for all floating screen objects, + anchored at anchor frame on the given page frame + + for format of floating screen objects for + follow text frames, the 'master' text frame is passed to the method. + Thus, the objects, whose anchor character is inside the follow text + frame can be formatted. + + @param _pMasterTextFrame + input parameter - pointer to 'master' text frame. default value: NULL + */ + bool FormatObjsAtFrame_( SwTextFrame* _pMasterTextFrame = nullptr ); + + /** accessor to collected anchored object + */ + SwAnchoredObject* GetCollectedObj( const sal_uInt32 _nIndex ); + + /** accessor to 'anchor' page number of collected anchored object + */ + sal_uInt32 GetPgNumOfCollected( const sal_uInt32 _nIndex ); + + /** accessor to 'anchor' type of collected anchored object + */ + bool IsCollectedAnchoredAtMaster( const sal_uInt32 _nIndex ); + + /** accessor to total number of collected anchored objects + */ + sal_uInt32 CountOfCollected(); + + public: + virtual ~SwObjectFormatter(); + + /** intrinsic method to format a certain floating screen object + + #i40147# - add parameter <_bCheckForMovedFwd> + + @param _rAnchoredObj + input parameter - anchored object, which have to be formatted. + + @param _bCheckForMovedFwd + input parameter - boolean indicating, that after a successful + format of the anchored object the anchor frame has to be checked, + if it would moved forward due to the positioning of the anchored object. + default value: false + value only considered, if wrapping style influence has to be + considered for the positioning of the anchored object. + */ + virtual bool DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd = false ) = 0; + + /** intrinsic method to format all floating screen objects + */ + virtual bool DoFormatObjs() = 0; + + /** method to format all floating screen objects at the given anchor frame + */ + static bool FormatObjsAtFrame( SwFrame& _rAnchorFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction = nullptr ); + + /** method to format a given floating screen object + */ + static bool FormatObj( SwAnchoredObject& _rAnchoredObj, + SwFrame* _pAnchorFrame = nullptr, + const SwPageFrame* _pPageFrame = nullptr ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/observablethread.hxx b/sw/source/core/inc/observablethread.hxx new file mode 100644 index 000000000..3d10a7f38 --- /dev/null +++ b/sw/source/core/inc/observablethread.hxx @@ -0,0 +1,87 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_OBSERVABLETHREAD_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_OBSERVABLETHREAD_HXX + +#include <osl/thread.hxx> +#include <osl/interlck.h> +#include <salhelper/simplereferenceobject.hxx> +#include <memory> + +class IFinishedThreadListener; + +/** class for an observable thread + + OD 2007-01-29 #i73788# + Note: A thread is ref-counted. Thus, an instance of a derived class has to + to be hold via a reference. The thread itself assures during its execution, + that the ref-count is increased. Its execution starts with its <run()> method + and ends with its <onTerminated()> method. + Note: A thread can be only observed by one or none thread observer in order + to notify, that the thread has finished its work. +*/ +class ObservableThread : public osl::Thread, + public salhelper::SimpleReferenceObject +{ + public: + + virtual ~ObservableThread() override; + + void SetListener( std::weak_ptr< IFinishedThreadListener > const & pThreadListener, + const oslInterlockedCount nThreadID ); + + // resolve ambiguity + using SimpleReferenceObject::operator new; + using SimpleReferenceObject::operator delete; + + protected: + + ObservableThread(); + + /** intrinsic function of the thread + + Important note: + Do not override this method again. Instead override <threadFunction()>. + Otherwise, it's not guaranteed, that its ref-count is increased + during the execution of the thread. + */ + virtual void SAL_CALL run() override; + + virtual void threadFunction() = 0; + + /** method called, when thread has finished its work + + Important note: + Do not override this method again. Instead override <threadFinished()>. + Otherwise, it's not guaranteed, that the ref-count is decreased at + the end of its execution and that the observer is notified, that + the thread has finished its work. + */ + virtual void SAL_CALL onTerminated() override; + + private: + + oslInterlockedCount mnThreadID; + + std::weak_ptr< IFinishedThreadListener > mpThreadListener; + +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/pagedeschint.hxx b/sw/source/core/inc/pagedeschint.hxx new file mode 100644 index 000000000..adee2319f --- /dev/null +++ b/sw/source/core/inc/pagedeschint.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_PAGEDESCHINT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_PAGEDESCHINT_HXX + +#include <svl/hint.hxx> + +class SwPageDesc; + +class SwPageDescHint : public SfxHint +{ + SwPageDesc* m_pPageDesc; +public: + SwPageDescHint( SwPageDesc* p ) + : m_pPageDesc(p) + {} + + SwPageDesc* GetPageDesc() const { return m_pPageDesc; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/pagefrm.hxx b/sw/source/core/inc/pagefrm.hxx new file mode 100644 index 000000000..3c7f29f52 --- /dev/null +++ b/sw/source/core/inc/pagefrm.hxx @@ -0,0 +1,443 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_PAGEFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_PAGEFRM_HXX + +#include <viewsh.hxx> +#include "ftnboss.hxx" +#include "hffrm.hxx" + +#include <SidebarWindowsTypes.hxx> + +class SwFlyFrame; +class SwFlyFrameFormat; +class SwPageDesc; +class SwContentFrame; +struct SwPosition; +struct SwCursorMoveState; +class SwAttrSetChg; +namespace vcl { class Font; } +class SwSortedObjs; +class SwAnchoredObject; + +/// A page of the document layout. Upper frame is expected to be an SwRootFrame +/// instance. At least an SwBodyFrame lower is expected. +class SAL_DLLPUBLIC_RTTI SwPageFrame: public SwFootnoteBossFrame +{ + friend class SwFrame; + + std::unique_ptr<SwSortedObjs> m_pSortedObjs; + + SwPageDesc *m_pDesc; //PageDesc that describes the Page + + /// Physical page number: index into list of SwRootFrame lowers + sal_uInt16 m_nPhyPageNum; + + bool m_bInvalidContent :1; + bool m_bInvalidLayout :1; + bool m_bInvalidFlyContent :1; + bool m_bInvalidFlyLayout :1; + bool m_bInvalidFlyInCnt :1; + bool m_bFootnotePage :1; // This Page is for document end footnotes + bool m_bEmptyPage :1; // This Page is an explicitly empty page + bool m_bEndNotePage :1; // 'Footnote page' for end notes + bool m_bInvalidSpelling :1; // We need online spelling + bool m_bInvalidSmartTags :1; // We need checking for smarttags + bool m_bInvalidAutoCmplWrds :1; // Update auto complete word list + bool m_bInvalidWordCount :1; + bool m_bHasGrid :1; // Grid for Asian layout + + static const sal_Int8 mnShadowPxWidth; + + void UpdateAttr_( const SfxPoolItem*, const SfxPoolItem*, sal_uInt8 &, + SwAttrSetChg *pa = nullptr, SwAttrSetChg *pb = nullptr ); + + /// Adapt the max. footnote height in each single column + void SetColMaxFootnoteHeight(); + + /** determine rectangle for horizontal page shadow + + #i9719# + + @param _rPageRect + input parameter - constant instance reference of the page rectangle. + Generally, it's the frame area of the page, but for empty pages in print + preview, this parameter is useful. + + @param _pViewShell + input parameter - instance of the view shell, for which the rectangle + has to be generated. + + @param _orBottomShadowRect + output parameter - instance reference of the bottom shadow rectangle for + the given page rectangle + */ + + static void GetHorizontalShadowRect( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + OutputDevice const * pRenderContext, + SwRect& _orBottomShadowRect, + bool bPaintLeftShadow, + bool bPaintRightShadow, + bool bRightSidebar ); + + virtual void DestroyImpl() override; + virtual ~SwPageFrame() override; + +protected: + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + virtual void SwClientNotify(const SwModify&, const SfxHint&) override; + + /// Calculate the content height of a page (without columns). + size_t GetContentHeight(const long nTop, const long nBottom) const; + +public: + SwPageFrame( SwFrameFormat*, SwFrame*, SwPageDesc* ); + + /// Make this public, so that the SwViewShell can access it when switching from browse mode + /// Add/remove header/footer + void PrepareHeader(); + void PrepareFooter(); + + const SwSortedObjs *GetSortedObjs() const { return m_pSortedObjs.get(); } + SwSortedObjs *GetSortedObjs() { return m_pSortedObjs.get(); } + + void AppendDrawObjToPage( SwAnchoredObject& _rNewObj ); + void RemoveDrawObjFromPage( SwAnchoredObject& _rToRemoveObj ); + + void AppendFlyToPage( SwFlyFrame *pNew ); + void RemoveFlyFromPage( SwFlyFrame *pToRemove ); + void MoveFly( SwFlyFrame *pToMove, SwPageFrame *pDest ); // Optimized Remove/Append + + void SetPageDesc( SwPageDesc *, SwFrameFormat * ); + SwPageDesc *GetPageDesc() { return m_pDesc; } + const SwPageDesc *GetPageDesc() const { return m_pDesc; } + SwPageDesc *FindPageDesc(); + + SwContentFrame *FindLastBodyContent(); + inline SwContentFrame *FindFirstBodyContent(); + inline const SwContentFrame *FindFirstBodyContent() const; + inline const SwContentFrame *FindLastBodyContent() const; + + SwRect GetBoundRect(OutputDevice const * pOutputDevice) const; + + // Specialized GetContentPos() for Field in Frames + void GetContentPosition( const Point &rPt, SwPosition &rPos ) const; + + bool IsEmptyPage() const { return m_bEmptyPage; } // Explicitly empty page + + void UpdateFootnoteNum(); + + /// Always call after Paste + /// Creates the page-bound frames and formats the generic content + void PreparePage( bool bFootnote ); + + // Sends a Prepare() to all ContentFrames caused by a changed register template + void PrepareRegisterChg(); + + // Appends a fly frame - the given one or a new one - at the page frame. + // Needed for <Modify> and <MakeFrames> + // - return value not needed any more + // - second parameter is of type <SwFlyFrameFormat*> + // - third parameter only needed for assertion, but calling method assures + // this assertion. Thus, delete it. + void PlaceFly( SwFlyFrame* pFly, SwFlyFrameFormat* pFormat ); + + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, + SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override; + /// Get info from Client + virtual bool GetInfo( SfxPoolItem& ) const override; + + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; + virtual void CheckDirection( bool bVert ) override; + void CheckGrid( bool bInvalidate ); + void PaintGrid( OutputDevice const * pOut, SwRect const &rRect ) const; + bool HasGrid() const { return m_bHasGrid; } + + void PaintDecorators( ) const; + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; + virtual void PaintBreak() const override; + + /// Paint line number etc. + void RefreshExtraData( const SwRect & ) const; + + /// Paint helper lines + void RefreshSubsidiary( const SwRect& ) const; + + /// Foot note interface + bool IsFootnotePage() const { return m_bFootnotePage; } + bool IsEndNotePage() const { return m_bEndNotePage; } + void SetFootnotePage( bool b ) { m_bFootnotePage = b; } + void SetEndNotePage( bool b ) { m_bEndNotePage = b; } + + sal_uInt16 GetPhyPageNum() const { return m_nPhyPageNum;} + void SetPhyPageNum( sal_uInt16 nNum ) { m_nPhyPageNum = nNum;} + + /// Validate, invalidate and query the Page status + /// Layout/Content and Fly/non-Fly respectively are inspected separately + inline void InvalidateFlyLayout() const; + inline void InvalidateFlyContent() const; + inline void InvalidateFlyInCnt() const; + inline void InvalidateLayout() const; + inline void InvalidateContent() const; + inline void InvalidateSpelling() const; + inline void InvalidateSmartTags() const; + inline void InvalidateAutoCompleteWords() const; + inline void InvalidateWordCount() const; + inline void ValidateFlyLayout() const; + inline void ValidateFlyContent() const; + inline void ValidateFlyInCnt() const; + inline void ValidateLayout() const; + inline void ValidateContent() const; + inline void ValidateSpelling() const; + inline void ValidateSmartTags() const; + inline void ValidateAutoCompleteWords() const; + inline void ValidateWordCount() const; + inline bool IsInvalid() const; + inline bool IsInvalidFly() const; + bool IsRightShadowNeeded() const; + bool IsLeftShadowNeeded() const; + bool IsInvalidFlyLayout() const { return m_bInvalidFlyLayout; } + bool IsInvalidFlyContent() const { return m_bInvalidFlyContent; } + bool IsInvalidFlyInCnt() const { return m_bInvalidFlyInCnt; } + bool IsInvalidLayout() const { return m_bInvalidLayout; } + bool IsInvalidContent() const { return (m_bInvalidContent || m_bInvalidFlyInCnt); } + bool IsInvalidSpelling() const { return m_bInvalidSpelling; } + bool IsInvalidSmartTags() const { return m_bInvalidSmartTags; } + bool IsInvalidAutoCompleteWords() const { return m_bInvalidAutoCmplWrds; } + bool IsInvalidWordCount() const { return m_bInvalidWordCount; } + + /** SwPageFrame::GetDrawBackgrdColor + + determine the color, that is respectively will be drawn as background + for the page frame. + + @return reference to an instance of class Color + */ + Color GetDrawBackgrdColor() const; + + /** paint margin area of a page + + implement paint of margin area; margin area will be painted for a + view shell with a window and if the document is not in online layout. + + @param _rOutputRect + input parameter - constant instance reference of the rectangle, for + which an output has to be generated. + + @param _pViewShell + input parameter - instance of the view shell, on which the output + has to be generated. + */ + void PaintMarginArea( const SwRect& _rOutputRect, + SwViewShell const * _pViewShell ) const; + + /** paint page border and shadow + + @param _rPageRect + input parameter - constant instance reference of the page rectangle. + Generally, it's the frame area of the page, but for empty pages in print + preview, this parameter is useful. + + @param _pViewShell + input parameter - instance of the view shell, on which the output + has to be generated. + + @param bPaintRightShadow + Should we paint shadow on the right or not (used in book mode) + + @param bFullBottomShadow + Should we have a bottom shadow of the same size as the pages or + not (for right pages in book mode in a LTR environment). + + @param bRightSidebar + Is the note sidebar on the right or not (used to adjust the + shadow with & position). + */ + static void PaintBorderAndShadow( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + bool bPaintLeftShadow, + bool bPaintRightShadow, + bool bRightSidebar ); + + /** get bound rectangle of border and shadow for repaints + + @param _rPageRect + input parameter - constant instance reference of the page rectangle. + Generally, it's the frame area of the page, but for empty pages in print + preview, this parameter is useful. + + @param _pViewShell + input parameter - instance of the view shell, for which the rectangle + has to be generated. + + @param _orBorderAndShadowBoundRect + output parameter - instance reference of the bounded border and shadow + rectangle for the given page rectangle + */ + static void GetBorderAndShadowBoundRect( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + OutputDevice const * pRenderContext, + SwRect& _orBorderAndShadowBoundRect, + const bool bLeftShadow, + const bool bRightShadow, + const bool bRightSidebar + ); + + static void PaintNotesSidebar(const SwRect& _rPageRect, SwViewShell* _pViewShell, sal_uInt16 nPageNum, bool bRight); + static void PaintNotesSidebarArrows(const Point &rMiddleFirst, const Point &rMiddleSecond, SwViewShell const * _pViewShell, const Color& rColorUp, const Color& rColorDown); + /** + asks the page on which side a margin should be shown, e.g for notes + returns true for left side, false for right side + */ + sw::sidebarwindows::SidebarPosition SidebarPosition() const; + + virtual bool FillSelection( SwSelectionList& rList, const SwRect& rRect ) const override; + + SwRect PrtWithoutHeaderAndFooter() const; + + // in case this is an empty page, this function returns the 'reference' page + const SwPageFrame& GetFormatPage() const; + + /// If in header or footer area, it also indicates the exact area in rControl. + /// Header or footer must be active, otherwise returns false. + bool IsOverHeaderFooterArea( const Point& rPt, FrameControlType &rControl ) const; + + // return font used to paint the "empty page" string + static const vcl::Font& GetEmptyPageFont(); + + static SwTwips GetSidebarBorderWidth( const SwViewShell* ); + + /// Is bottom-of-page-frame - bottom-of-text-frame difference valid in case whitespace is hidden? + /// If false is returned, then the caller should handle negative difference as (at least) zero difference instead. + bool CheckPageHeightValidForHideWhitespace(SwTwips nDiff); + + const SwFooterFrame* GetFooterFrame() const; +}; + +inline SwContentFrame *SwPageFrame::FindFirstBodyContent() +{ + SwLayoutFrame *pBody = FindBodyCont(); + return pBody ? pBody->ContainsContent() : nullptr; +} +inline const SwContentFrame *SwPageFrame::FindFirstBodyContent() const +{ + const SwLayoutFrame *pBody = FindBodyCont(); + return pBody ? pBody->ContainsContent() : nullptr; +} +inline const SwContentFrame *SwPageFrame::FindLastBodyContent() const +{ + return const_cast<SwPageFrame*>(this)->FindLastBodyContent(); +} +inline void SwPageFrame::InvalidateFlyLayout() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidFlyLayout = true; +} +inline void SwPageFrame::InvalidateFlyContent() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidFlyContent = true; +} +inline void SwPageFrame::InvalidateFlyInCnt() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidFlyInCnt = true; +} +inline void SwPageFrame::InvalidateLayout() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidLayout = true; +} +inline void SwPageFrame::InvalidateContent() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidContent = true; +} +inline void SwPageFrame::InvalidateSpelling() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidSpelling = true; +} + +inline void SwPageFrame::InvalidateSmartTags() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidSmartTags = true; +} +inline void SwPageFrame::InvalidateAutoCompleteWords() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidAutoCmplWrds = true; +} +inline void SwPageFrame::InvalidateWordCount() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidWordCount = true; +} +inline void SwPageFrame::ValidateFlyLayout() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidFlyLayout = false; +} +inline void SwPageFrame::ValidateFlyContent() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidFlyContent = false; +} +inline void SwPageFrame::ValidateFlyInCnt() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidFlyInCnt = false; +} +inline void SwPageFrame::ValidateLayout() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidLayout = false; +} +inline void SwPageFrame::ValidateContent() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidContent = false; +} +inline void SwPageFrame::ValidateSpelling() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidSpelling = false; +} + +inline void SwPageFrame::ValidateSmartTags() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidSmartTags = false; +} +inline void SwPageFrame::ValidateAutoCompleteWords() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidAutoCmplWrds = false; +} +inline void SwPageFrame::ValidateWordCount() const +{ + const_cast<SwPageFrame*>(this)->m_bInvalidWordCount = false; +} + +inline bool SwPageFrame::IsInvalid() const +{ + return (m_bInvalidContent || m_bInvalidLayout || m_bInvalidFlyInCnt); +} +inline bool SwPageFrame::IsInvalidFly() const +{ + return m_bInvalidFlyLayout || m_bInvalidFlyContent; +} + + +class SwTextGridItem; + +SwTextGridItem const* GetGridItem(SwPageFrame const*const); + +sal_uInt16 GetGridWidth(SwTextGridItem const&, SwDoc const&); + +#endif // INCLUDED_SW_SOURCE_CORE_INC_PAGEFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/paintfrm.hxx b/sw/source/core/inc/paintfrm.hxx new file mode 100644 index 000000000..b3d4812d6 --- /dev/null +++ b/sw/source/core/inc/paintfrm.hxx @@ -0,0 +1,36 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_PAINTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_PAINTFRM_HXX + +#include <tools/color.hxx> + +extern Color aGlobalRetoucheColor; + +class OutputDevice; +namespace vcl { + typedef OutputDevice RenderContext; +}; + +void SwCalcPixStatics( vcl::RenderContext const *pOut ); + +#endif // INCLUDED_SW_SOURCE_CORE_INC_PAINTFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/pamtyp.hxx b/sw/source/core/inc/pamtyp.hxx new file mode 100644 index 000000000..48dcce58a --- /dev/null +++ b/sw/source/core/inc/pamtyp.hxx @@ -0,0 +1,115 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_PAMTYP_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_PAMTYP_HXX + +#include <unotools/textsearch.hxx> +#include <swdllapi.h> + +#include <memory> + +class SwpHints; +struct SwPosition; +class SwPaM; +class SwTextAttr; +class SwFormat; +class SfxPoolItem; +class SwRootFrame; +class SwNode; +class SwNodeIndex; +class SwContentNode; +class SwIndex; +class SvxSearchItem; + +namespace i18nutil { + struct SearchOptions2; +} + +// function prototypes for the move/find methods of SwPaM + +void GoStartDoc( SwPosition*); +void GoEndDoc( SwPosition*); +void GoStartSection( SwPosition*); +void GoEndSection( SwPosition*); +const SwTextAttr* GetFrwrdTextHint( const SwpHints&, size_t&, sal_Int32 ); +const SwTextAttr* GetBkwrdTextHint( const SwpHints&, size_t&, sal_Int32 ); + +bool GoNext(SwNode* pNd, SwIndex * pIdx, sal_uInt16 nMode ); +bool GoPrevious(SwNode* pNd, SwIndex * pIdx, sal_uInt16 nMode ); +SwContentNode* GoNextNds( SwNodeIndex * pIdx, bool ); +SwContentNode* GoPreviousNds( SwNodeIndex * pIdx, bool ); + +// type definitions of functions +typedef bool (*GoNd)( SwNode*, SwIndex*, sal_uInt16 ); +typedef SwContentNode* (*GoNds)( SwNodeIndex*, bool ); +typedef void (*GoDoc)( SwPosition* ); +typedef void (*GoSection)( SwPosition* ); +typedef bool (SwPosition:: *CmpOp)( const SwPosition& ) const; +typedef const SwTextAttr* (*GetHint)( const SwpHints&, size_t&, sal_Int32 ); +typedef bool (utl::TextSearch:: *SearchText)( const OUString&, sal_Int32*, + sal_Int32*, css::util::SearchResult* ); +typedef void (*MvSection)( SwNodeIndex * ); + +struct SwMoveFnCollection +{ + GoNd fnNd; + GoNds fnNds; + GoDoc fnDoc; + GoSection fnSections; + CmpOp fnCmpOp; + GetHint fnGetHint; + SearchText fnSearch; + MvSection fnSection; +}; + +// function prototype for searching +SwContentNode* GetNode(SwPaM&, bool&, SwMoveFnCollection const &, + bool bInReadOnly = false, SwRootFrame const* pLayout = nullptr); + +namespace sw { + + std::unique_ptr<SwPaM> MakeRegion(SwMoveFnCollection const & fnMove, + const SwPaM & rOrigRg); + + /// Search. + bool FindTextImpl(SwPaM & rSearchPam, + const i18nutil::SearchOptions2& rSearchOpt, + bool bSearchInNotes, + utl::TextSearch& rSText, + SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + SwRootFrame const* pLayout, + std::unique_ptr<SvxSearchItem>& xSearchItem); + bool FindFormatImpl(SwPaM & rSearchPam, + const SwFormat& rFormat, + SwMoveFnCollection const & fnMove, + const SwPaM & rRegion, bool bInReadOnly, + SwRootFrame const* pLayout); + bool FindAttrImpl(SwPaM & rSearchPam, + const SfxPoolItem& rAttr, + SwMoveFnCollection const & fnMove, + const SwPaM & rPam, bool bInReadOnly, + SwRootFrame const* pLayout); + +} // namespace sw + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/prevwpage.hxx b/sw/source/core/inc/prevwpage.hxx new file mode 100644 index 000000000..d9c8d1df0 --- /dev/null +++ b/sw/source/core/inc/prevwpage.hxx @@ -0,0 +1,53 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_PREVWPAGE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_PREVWPAGE_HXX + +// classes <Point>, <Size> and <Rectangle> +#include <tools/gen.hxx> + +class SwPageFrame; + +/** data structure for a preview page in the current preview layout +*/ +struct PreviewPage +{ + const SwPageFrame* pPage; + bool bVisible; + Size aPageSize; + Point aPreviewWinPos; + Point aLogicPos; + Point aMapOffset; + + inline PreviewPage(); +}; + +inline PreviewPage::PreviewPage() + : pPage( nullptr ), + bVisible( false ), + aPageSize( Size(0,0) ), + aPreviewWinPos( Point(0,0) ), + aLogicPos( Point(0,0) ), + aMapOffset( Point(0,0) ) +{}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/ptqueue.hxx b/sw/source/core/inc/ptqueue.hxx new file mode 100644 index 000000000..04c62873f --- /dev/null +++ b/sw/source/core/inc/ptqueue.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_PTQUEUE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_PTQUEUE_HXX + +/** + * Unfortunately we have some problems with processing more than one Paint() + * at a time. This happens especially often during printing. + * + * SwRootFrame::PaintSwFrame() determines that it's called a second time and adds the + * rectangle and the corresponding Shell to the PaintCollector. + * The call sites that are causing the double Paint() only need to process the + * collected Paint()s at the right point in time. + * Doing this during printing (after having printed one page) is very suitable + * for doing that. + * + * Invalidating windows directly from the RootFrame::PaintSwFrame was not a successful + * approach, because the Paint()s arrive at a very unfavourable point in time. + * Triggering an update for all windows after printing each page does not seem + * appropriate either: on the one hand we don't have direct access to the edit + * windows and on the other hand the updates can become very costly on some + * platforms. + */ + +class SwQueuedPaint; +class SwViewShell; +class SwRect; + +class SwPaintQueue +{ +public: + static SwQueuedPaint *s_pPaintQueue; + + static void Add( SwViewShell *pSh, const SwRect &rNew ); + static void Remove( SwViewShell const *pSh ); + static void Repaint(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/retrievedinputstreamdata.hxx b/sw/source/core/inc/retrievedinputstreamdata.hxx new file mode 100644 index 000000000..505f18f40 --- /dev/null +++ b/sw/source/core/inc/retrievedinputstreamdata.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_RETRIEVEDINPUTSTREAMDATA_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_RETRIEVEDINPUTSTREAMDATA_HXX + +#include <tools/link.hxx> +#include <sal/types.h> +#include <osl/mutex.hxx> +#include <com/sun/star/uno/Reference.hxx> + +#include <map> +#include <memory> + +namespace com::sun::star::io { class XInputStream; } + +class SwAsyncRetrieveInputStreamThreadConsumer; + +/** Singleton class to manage retrieved input stream data in Writer + + OD 2007-01-29 #i73788# + The instance of this class provides data container for retrieved input + stream data. The data container is accessed via a key, which the data + manager provides on creation of the data container. + When a certain data container is filled with data, an user event is submitted + to trigger the processing of with data. +*/ +class SwRetrievedInputStreamDataManager +{ + public: + + typedef sal_uInt64 tDataKey; + + struct tData + { + std::weak_ptr< SwAsyncRetrieveInputStreamThreadConsumer > mpThreadConsumer; + css::uno::Reference<css::io::XInputStream> mxInputStream; + bool mbIsStreamReadOnly; + + tData() + : mpThreadConsumer(), + mbIsStreamReadOnly( false ) + {}; + + tData( std::weak_ptr< SwAsyncRetrieveInputStreamThreadConsumer > pThreadConsumer ) + : mpThreadConsumer( pThreadConsumer ), + mbIsStreamReadOnly( false ) + {}; + }; + + static SwRetrievedInputStreamDataManager& GetManager(); + + tDataKey ReserveData( std::weak_ptr< SwAsyncRetrieveInputStreamThreadConsumer > const & pThreadConsumer ); + + void PushData( const tDataKey nDataKey, + css::uno::Reference<css::io::XInputStream> const & xInputStream, + const bool bIsStreamReadOnly ); + + bool PopData( const tDataKey nDataKey, + tData& rData ); + + DECL_LINK( LinkedInputStreamReady, void*, void ); + + private: + + static tDataKey mnNextKeyValue; + + osl::Mutex maMutex; + + std::map< tDataKey, tData > maInputStreamData; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/retrieveinputstream.hxx b/sw/source/core/inc/retrieveinputstream.hxx new file mode 100644 index 000000000..579efb24f --- /dev/null +++ b/sw/source/core/inc/retrieveinputstream.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_RETRIEVEINPUTSTREAM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_RETRIEVEINPUTSTREAM_HXX + +#include "observablethread.hxx" +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include "retrievedinputstreamdata.hxx" + +/** class for a thread to retrieve an input stream given by a URL + + OD 2007-01-29 #i73788# +*/ +class SwAsyncRetrieveInputStreamThread : public ObservableThread +{ + public: + + static ::rtl::Reference< ObservableThread > createThread( + const SwRetrievedInputStreamDataManager::tDataKey nDataKey, + const OUString& rLinkedURL, const OUString& rReferer ); + + virtual ~SwAsyncRetrieveInputStreamThread() override; + + protected: + + virtual void threadFunction() override; + + private: + + SwAsyncRetrieveInputStreamThread( const SwRetrievedInputStreamDataManager::tDataKey nDataKey, + const OUString& rLinkedURL, + const OUString& rReferer ); + + const SwRetrievedInputStreamDataManager::tDataKey mnDataKey; + const OUString mrLinkedURL; + const OUString mrReferer; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/retrieveinputstreamconsumer.hxx b/sw/source/core/inc/retrieveinputstreamconsumer.hxx new file mode 100644 index 000000000..ba6424e9a --- /dev/null +++ b/sw/source/core/inc/retrieveinputstreamconsumer.hxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_RETRIEVEINPUTSTREAMCONSUMER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_RETRIEVEINPUTSTREAMCONSUMER_HXX + +#include <osl/interlck.h> +#include <rtl/ustring.hxx> + +namespace com::sun::star::io { class XInputStream; } +namespace com::sun::star::uno { template <typename > class Reference; } + +class SwGrfNode; + +/** class to provide creation of a thread to retrieve an input stream given by + a URL and to consume the retrieved input stream. + + #i73788# +*/ +class SwAsyncRetrieveInputStreamThreadConsumer +{ + public: + + SwAsyncRetrieveInputStreamThreadConsumer( SwGrfNode& rGrfNode ); + ~SwAsyncRetrieveInputStreamThreadConsumer() COVERITY_NOEXCEPT_FALSE; + + /** method to create thread + */ + void CreateThread( const OUString& rURL, const OUString& rReferer ); + + /** method called to provide the retrieved input stream to the thread Consumer + */ + void ApplyInputStream( + css::uno::Reference<css::io::XInputStream> const & xInputStream, + const bool bIsStreamReadOnly ); + + private: + + SwGrfNode& mrGrfNode; + oslInterlockedCount mnThreadID; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/rolbck.hxx b/sw/source/core/inc/rolbck.hxx new file mode 100644 index 000000000..d6cec5075 --- /dev/null +++ b/sw/source/core/inc/rolbck.hxx @@ -0,0 +1,435 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ROLBCK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ROLBCK_HXX + +#include <o3tl/deleter.hxx> +#include <svl/itemset.hxx> +#include <tools/solar.h> +#include <vcl/keycod.hxx> +#include <tox.hxx> + +#include <IDocumentMarkAccess.hxx> + +#include <memory> +#include <vector> +#include <set> + +namespace sfx2 { + class MetadatableUndo; +} + +class SwDoc; +class SwFormatColl; +class SwTextAttr; +class SfxPoolItem; +class SwUndoSaveSection; +class SwTextFootnote; +class SwUndoDelLayFormat; +class SwFlyFrameFormat; +class SwFormatField; +class SwTextField; +class SwFieldType; +class SwTextTOXMark; +class SwTextRefMark; +class SwFrameFormat; +class SwpHints; +class SwFormatChain; +class SwNode; +class SwCharFormat; +enum class SwFieldIds : sal_uInt16; + +enum HISTORY_HINT { + HSTRY_SETFMTHNT, + HSTRY_RESETFMTHNT, + HSTRY_SETTXTHNT, + HSTRY_SETTXTFLDHNT, + HSTRY_SETREFMARKHNT, + HSTRY_SETTOXMARKHNT, + HSTRY_RESETTXTHNT, + HSTRY_SETFTNHNT, + HSTRY_CHGFMTCOLL, + HSTRY_FLYCNT, + HSTRY_BOOKMARK, + HSTRY_SETATTRSET, + HSTRY_CHGFLYANCHOR, + HSTRY_CHGFLYCHAIN, + HSTRY_CHGCHARFMT, + HSTRY_NOTEXTFIELDMARK, + HSTRY_TEXTFIELDMARK, +}; + +class SwHistoryHint +{ + const HISTORY_HINT m_eWhichId; + +public: + SwHistoryHint( HISTORY_HINT eWhich ) : m_eWhichId( eWhich ) {} + virtual ~SwHistoryHint() {} + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) = 0; + HISTORY_HINT Which() const { return m_eWhichId; } + virtual OUString GetDescription() const; +}; + +class SwHistorySetFormat : public SwHistoryHint +{ + std::unique_ptr<SfxPoolItem> m_pAttr; + const sal_uLong m_nNodeIndex; + +public: + SwHistorySetFormat( const SfxPoolItem* pFormatHt, sal_uLong nNode ); + virtual ~SwHistorySetFormat() override; + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + virtual OUString GetDescription() const override; + +}; + +class SwHistoryResetFormat : public SwHistoryHint +{ + const sal_uLong m_nNodeIndex; + const sal_uInt16 m_nWhich; + +public: + SwHistoryResetFormat( const SfxPoolItem* pFormatHt, sal_uLong nNodeIdx ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + +}; + +class SwHistorySetText : public SwHistoryHint +{ + std::unique_ptr<SfxPoolItem> m_pAttr; + const sal_uLong m_nNodeIndex; + const sal_Int32 m_nStart; + const sal_Int32 m_nEnd; + bool m_bFormatIgnoreStart : 1; + bool m_bFormatIgnoreEnd : 1; + +public: + SwHistorySetText( SwTextAttr* pTextHt, sal_uLong nNode ); + virtual ~SwHistorySetText() override; + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + +}; + +class SwHistorySetTextField : public SwHistoryHint +{ + //!! beware of the order for the declaration of the unique_ptrs. + //!! If they get destroyed in the wrong order sw may crash (namely mail-merge as well) + std::unique_ptr<SwFieldType> m_pFieldType; + const std::unique_ptr<SwFormatField> m_pField; + + sal_uLong m_nNodeIndex; + sal_Int32 m_nPos; + SwFieldIds m_nFieldWhich; + +public: + SwHistorySetTextField( const SwTextField* pTextField, sal_uLong nNode ); + virtual ~SwHistorySetTextField() override; + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + + virtual OUString GetDescription() const override; + +}; + +class SwHistorySetRefMark : public SwHistoryHint +{ + const OUString m_RefName; + const sal_uLong m_nNodeIndex; + const sal_Int32 m_nStart; + const sal_Int32 m_nEnd; + +public: + SwHistorySetRefMark( const SwTextRefMark* pTextHt, sal_uLong nNode ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + +}; + +class SwHistorySetTOXMark : public SwHistoryHint +{ + SwTOXMark m_TOXMark; + const OUString m_TOXName; + const TOXTypes m_eTOXTypes; + const sal_uLong m_nNodeIndex; + const sal_Int32 m_nStart; + const sal_Int32 m_nEnd; + +public: + SwHistorySetTOXMark( const SwTextTOXMark* pTextHt, sal_uLong nNode ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + bool IsEqual( const SwTOXMark& rCmp ) const; + + static SwTOXType* GetSwTOXType(SwDoc& rDoc, TOXTypes eTOXTypes, const OUString& rTOXName); +}; + +class SwHistoryResetText : public SwHistoryHint +{ + const sal_uLong m_nNodeIndex; + const sal_Int32 m_nStart; + const sal_Int32 m_nEnd; + const sal_uInt16 m_nAttr; + +public: + SwHistoryResetText( sal_uInt16 nWhich, sal_Int32 nStt, sal_Int32 nEnd, + sal_uLong nNode ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + + sal_uInt16 GetWhich() const { return m_nAttr; } + sal_uLong GetNode() const { return m_nNodeIndex; } + sal_Int32 GetContent() const { return m_nStart; } + +}; + +class SwHistorySetFootnote : public SwHistoryHint +{ + const std::unique_ptr<SwUndoSaveSection, o3tl::default_delete<SwUndoSaveSection>> m_pUndo; + const OUString m_FootnoteNumber; + sal_uLong m_nNodeIndex; + const sal_Int32 m_nStart; + const bool m_bEndNote; + +public: + SwHistorySetFootnote( SwTextFootnote* pTextFootnote, sal_uLong nNode ); + SwHistorySetFootnote( const SwTextFootnote& ); + virtual ~SwHistorySetFootnote() override; + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + + virtual OUString GetDescription() const override; + +}; + +class SwHistoryChangeFormatColl : public SwHistoryHint +{ + SwFormatColl * const m_pColl; + const sal_uLong m_nNodeIndex; + const SwNodeType m_nNodeType; + +public: + SwHistoryChangeFormatColl( SwFormatColl* pColl, sal_uLong nNode, SwNodeType nNodeWhich ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + +}; + +class SwHistoryTextFlyCnt : public SwHistoryHint +{ + std::unique_ptr<SwUndoDelLayFormat> m_pUndo; + +public: + SwHistoryTextFlyCnt( SwFrameFormat* const pFlyFormat ); + virtual ~SwHistoryTextFlyCnt() override; + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + SwUndoDelLayFormat* GetUDelLFormat() { return m_pUndo.get(); } + +}; + +class SwHistoryBookmark : public SwHistoryHint +{ + public: + SwHistoryBookmark(const ::sw::mark::IMark& rBkmk, + bool bSavePos, bool bSaveOtherPos); + virtual void SetInDoc(SwDoc * pDoc, bool) override; + + bool IsEqualBookmark(const ::sw::mark::IMark& rBkmk); + const OUString& GetName() const { return m_aName;} + + private: + const OUString m_aName; + OUString m_aShortName; + vcl::KeyCode m_aKeycode; + const sal_uLong m_nNode; + const sal_uLong m_nOtherNode; + const sal_Int32 m_nContent; + const sal_Int32 m_nOtherContent; + const bool m_bSavePos; + const bool m_bSaveOtherPos; + const bool m_bHadOtherPos; + const IDocumentMarkAccess::MarkType m_eBkmkType; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndo; +}; + +/// History object containing all information used during undo / redo +/// of checkbox and drop-down form field insertion. +class SwHistoryNoTextFieldmark : public SwHistoryHint +{ + public: + SwHistoryNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldMark); + virtual void SetInDoc(SwDoc* pDoc, bool) override; + void ResetInDoc(SwDoc* pDoc); + + private: + const OUString m_sType; + const sal_uLong m_nNode; + const sal_Int32 m_nContent; +}; + +/// History object containing all information used during undo / redo +/// of text form field insertion. +class SwHistoryTextFieldmark : public SwHistoryHint +{ + public: + SwHistoryTextFieldmark(const ::sw::mark::IFieldmark& rFieldMark); + virtual void SetInDoc(SwDoc* pDoc, bool) override; + void ResetInDoc(SwDoc* pDoc); + + private: + const OUString m_sName; + const OUString m_sType; + const sal_uLong m_nStartNode; + const sal_Int32 m_nStartContent; + const sal_uLong m_nEndNode; + const sal_Int32 m_nEndContent; + /*const*/ sal_uLong m_nSepNode; + /*const*/ sal_Int32 m_nSepContent; +}; + +class SwHistorySetAttrSet : public SwHistoryHint +{ + SfxItemSet m_OldSet; + std::vector<sal_uInt16> m_ResetArray; + const sal_uLong m_nNodeIndex; + +public: + SwHistorySetAttrSet( const SfxItemSet& rSet, sal_uLong nNode, + const std::set<sal_uInt16> &rSetArr ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + +}; + +class SwHistoryChangeFlyAnchor : public SwHistoryHint +{ + SwFrameFormat & m_rFormat; + const sal_uLong m_nOldNodeIndex; + const sal_Int32 m_nOldContentIndex; + +public: + SwHistoryChangeFlyAnchor( SwFrameFormat& rFormat ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; +}; + +class SwHistoryChangeFlyChain : public SwHistoryHint +{ + SwFlyFrameFormat * const m_pPrevFormat; + SwFlyFrameFormat * const m_pNextFormat; + SwFlyFrameFormat * const m_pFlyFormat; + +public: + SwHistoryChangeFlyChain( SwFlyFrameFormat& rFormat, const SwFormatChain& rAttr ); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; +}; + +class SwHistoryChangeCharFormat : public SwHistoryHint +{ + const SfxItemSet m_OldSet; + const OUString m_Format; + +public: + SwHistoryChangeCharFormat( const SfxItemSet& rSet, const OUString & sFormat); + virtual void SetInDoc( SwDoc* pDoc, bool bTmpSet ) override; + +}; + +class SwHistory +{ + friend class SwDoc; // actually only SwDoc::DelUndoObj may access + friend class SwRegHistory; // for inserting History attributes + + std::vector<std::unique_ptr<SwHistoryHint>> m_SwpHstry; + sal_uInt16 m_nEndDiff; + +public: + SwHistory(); + ~SwHistory(); + + // call and delete all objects between nStart and array end + bool Rollback( SwDoc* pDoc, sal_uInt16 nStart = 0 ); + // call all objects between nStart and TmpEnd; store nStart as TmpEnd + bool TmpRollback( SwDoc* pDoc, sal_uInt16 nStart, bool ToFirst = true ); + + void Add( const SfxPoolItem* pOldValue, const SfxPoolItem* pNewValue, + sal_uLong nNodeIdx ); + void Add( SwTextAttr* pTextHt, sal_uLong nNodeIdx, bool bNewAttr ); + void Add( SwFormatColl*, sal_uLong nNodeIdx, SwNodeType nWhichNd ); + void Add( const ::sw::mark::IMark&, bool bSavePos, bool bSaveOtherPos ); + void AddChangeFlyAnchor( SwFrameFormat& rFormat ); + void AddDeleteFly( SwFrameFormat&, sal_uInt16& rSetPos ); + void Add( const SwTextFootnote& ); + void Add( const SfxItemSet & rSet, const SwCharFormat & rCharFormat); + + sal_uInt16 Count() const { return m_SwpHstry.size(); } + sal_uInt16 GetTmpEnd() const { return m_SwpHstry.size() - m_nEndDiff; } + sal_uInt16 SetTmpEnd( sal_uInt16 nTmpEnd ); // return previous value + SwHistoryHint * operator[]( sal_uInt16 nPos ) { return m_SwpHstry[nPos].get(); } + SwHistoryHint const* operator[]( sal_uInt16 nPos ) const + { return m_SwpHstry[nPos].get(); } + + // for SwUndoDelete::Undo/Redo + void Move( sal_uInt16 nPos, SwHistory *pIns, + sal_uInt16 const nStart = 0) + { + auto itSourceBegin = pIns->m_SwpHstry.begin() + nStart; + auto itSourceEnd = pIns->m_SwpHstry.end(); + if (itSourceBegin == itSourceEnd) + return; + m_SwpHstry.insert(m_SwpHstry.begin() + nPos, std::make_move_iterator(itSourceBegin), std::make_move_iterator(itSourceEnd)); + pIns->m_SwpHstry.erase( itSourceBegin, itSourceEnd ); + } + + // helper methods for recording attribute in History + // used by Undo classes (Delete/Overwrite/Inserts) + void CopyAttr( + SwpHints const * pHts, + const sal_uLong nNodeIdx, + const sal_Int32 nStart, + const sal_Int32 nEnd, + const bool bCopyFields ); + + void CopyFormatAttr( const SfxItemSet& rSet, sal_uLong nNodeIdx ); +}; + +class SwRegHistory : public SwClient +{ +private: + std::set<sal_uInt16> m_WhichIdSet; + SwHistory * const m_pHistory; + sal_uLong m_nNodeIndex; + + void MakeSetWhichIds(); + +protected: + virtual void Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) override; + +public: + SwRegHistory( SwHistory* pHst ); + SwRegHistory( const SwNode& rNd, SwHistory* pHst ); + SwRegHistory( SwModify* pRegIn, const SwNode& rNd, SwHistory* pHst ); + + /// @return true if at least 1 item was inserted + bool InsertItems( const SfxItemSet& rSet, + sal_Int32 const nStart, sal_Int32 const nEnd, + SetAttrMode const nFlags, + SwTextAttr **ppNewTextAttr ); + + void AddHint( SwTextAttr* pHt, const bool bNew ); + + void RegisterInModify( SwModify* pRegIn, const SwNode& rNd ); + void ChangeNodeIndex( sal_uLong nNew ) { m_nNodeIndex = nNew; } +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_ROLBCK_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/rootfrm.hxx b/sw/source/core/inc/rootfrm.hxx new file mode 100644 index 000000000..7a53fc1af --- /dev/null +++ b/sw/source/core/inc/rootfrm.hxx @@ -0,0 +1,462 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ROOTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ROOTFRM_HXX + +#include "layfrm.hxx" +#include <viewsh.hxx> +#include <doc.hxx> +#include <IDocumentTimerAccess.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <set> +#include <vector> + +class SwContentFrame; +class SdrPage; +class SwFrameFormat; +class SwPaM; +class SwCursor; +class SwShellCursor; +class SwTableCursor; +class SwLayVout; +class SwViewOption; +class SwSelectionList; +struct SwPosition; +struct SwCursorMoveState; + +namespace sw { + enum class RedlineMode + { + Shown, Hidden + }; +}; + +enum class SwInvalidateFlags +{ + Size = 0x01, + PrtArea = 0x02, + Pos = 0x04, + Table = 0x08, + Section = 0x10, + LineNum = 0x20, + Direction = 0x40, +}; + +namespace o3tl +{ + template<> struct typed_flags<SwInvalidateFlags> : is_typed_flags<SwInvalidateFlags, 0x7f> {}; +}; + +enum class SwRemoveResult +{ + Next, + Prev +}; + +using SwCurrShells = std::set<CurrShell*>; + +class SwSectionFrame; +using SwDestroyList = o3tl::sorted_vector<SwSectionFrame*>; + +/// The root element of a Writer document layout. Lower frames are expected to +/// be SwPageFrame instances. +class SAL_DLLPUBLIC_RTTI SwRootFrame: public SwLayoutFrame +{ + // Needs to disable the Superfluous temporarily + friend void AdjustSizeChgNotify( SwRootFrame *pRoot ); + + // Maintains the mpLastPage (Cut() and Paste() of SwPageFrame + friend inline void SetLastPage( SwPageFrame* ); + + // For creating and destroying of the virtual output device manager + friend void FrameInit(); // Creates s_pVout + friend void FrameFinit(); // Destroys s_pVout + + std::vector<SwRect> maPageRects;// returns the current rectangle for each page frame + // the rectangle is extended to the top/bottom/left/right + // for pages located at the outer margins + SwRect maPagesArea; // the area covered by the pages + long mnViewWidth; // the current page layout bases on this view width + sal_uInt16 mnColumns; // the current page layout bases on this number of columns + bool mbBookMode; // the current page layout is in book view + bool mbSidebarChanged; // the notes sidebar state has changed + + bool mbNeedGrammarCheck; // true when sth needs to be checked (not necessarily started yet!) + + static SwLayVout *s_pVout; + static bool s_isInPaint; // Protection against double Paints + static bool s_isNoVirDev;// No virt. Device for SystemPaints + + /// Width of the HTML / Web document if not defined otherwise: 20cm. + static constexpr sal_Int64 MIN_BROWSE_WIDTH = convertMm100ToTwip(20000); + + bool mbCheckSuperfluous :1; // Search for empty Pages? + bool mbIdleFormat :1; // Trigger Idle Formatter? + bool mbBrowseWidthValid :1; // Is mnBrowseWidth valid? + bool mbTurboAllowed :1; + bool mbAssertFlyPages :1; // Insert more Pages for Flys if needed? + bool mbIsVirtPageNum :1; // Do we have a virtual pagenumber? + bool mbIsNewLayout :1; // Layout loaded or newly created + bool mbCallbackActionEnabled:1; // No Action in Notification desired + // @see dcontact.cxx, ::Changed() + bool mbLayoutFreezed; + bool mbHideRedlines; + + /** + * For BrowseMode + * mnBrowseWidth is the outer margin of the object most to the right. + * The page's right edge should not be smaller than this value. + */ + long mnBrowseWidth; + + /// If we only have to format one ContentFrame, its in mpTurbo + const SwContentFrame *mpTurbo; + + /// We should not need to always struggle to find the last page, so store it here + SwPageFrame *mpLastPage; + + /** [ Comment from the original StarOffice checkin ]: + * The root takes care of the shell access. Via the document + * it should be possible to get at the root frame, and thus always + * have access to the shell. + * the pointer mpCurrShell is the pointer to any of the shells for + * the document. + * Because sometimes it matters which shell is used, it is necessary to + * know the active shell. + * this is approximated by setting the pointer mpCurrShell when a + * shell gets the focus (FEShell). Additionally the pointer will be + * set temporarily by SwCurrShell typically via SET_CURR_SHELL + * The macro and class can be found in the SwViewShell. These object can + * be created nested (also for different kinds of Shells). They are + * collected into the Array mpCurrShells. + * Furthermore it can happen that a shell is activated while a curshell + * object is still 'active'. This one will be entered into mpWaitingCurrShell + * and will be activated by the last d'tor of CurrShell. + * One other problem is the destruction of a shell while it is active. + * The pointer mpCurrShell is then reset to an arbitrary other shell. + * If at the time of the destruction of a shell, which is still referenced + * by a curshell object, that will be cleaned up as well. + */ + friend class CurrShell; + friend void SetShell( SwViewShell *pSh ); + friend void InitCurrShells( SwRootFrame *pRoot ); + SwViewShell *mpCurrShell; + SwViewShell *mpWaitingCurrShell; + std::unique_ptr<SwCurrShells> mpCurrShells; + + /// One Page per DrawModel per Document; is always the size of the Root + SdrPage *mpDrawPage; + + std::unique_ptr<SwDestroyList> mpDestroy; + + sal_uInt16 mnPhyPageNums; /// Page count + sal_uInt16 mnAccessibleShells; // Number of accessible shells + + void ImplCalcBrowseWidth(); + void ImplInvalidateBrowseWidth(); + + void DeleteEmptySct_(); // Destroys the registered SectionFrames + void RemoveFromList_( SwSectionFrame* pSct ); // Removes SectionFrames from the Delete List + + virtual void DestroyImpl() override; + virtual ~SwRootFrame() override; + +protected: + + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + +public: + + /// Remove MasterObjects from the Page (called by the ctors) + static void RemoveMasterObjs( SdrPage *pPg ); + + void AllCheckPageDescs() const; + void AllInvalidateAutoCompleteWords() const; + void AllAddPaintRect() const; + void AllRemoveFootnotes() ; + void AllInvalidateSmartTagsOrSpelling(bool bSmartTags) const; + + /// Output virtual Device (e.g. for animations) + static bool FlushVout(); + + /// Save Clipping if exactly the ClipRect is outputted + static bool HasSameRect( const SwRect& rRect ); + + SwRootFrame( SwFrameFormat*, SwViewShell* ); + void Init(SwFrameFormat*); + + SwViewShell *GetCurrShell() const { return mpCurrShell; } + void DeRegisterShell( SwViewShell *pSh ); + + /** + * Set up Start-/EndAction for all Shells on an as high as possible + * (Shell section) level. + * For the StarONE binding, which does not know the Shells directly. + * The ChangeLinkd of the CursorShell (UI notifications) is called + * automatically in the EndAllAction. + */ + void StartAllAction(); + void EndAllAction( bool bVirDev = false ); + + /** + * Certain UNO Actions (e.g. table cursor) require that all Actions are reset temporarily + * In order for that to work, every SwViewShell needs to remember its old Action counter + */ + void UnoRemoveAllActions(); + void UnoRestoreAllActions(); + + const SdrPage* GetDrawPage() const { return mpDrawPage; } + SdrPage* GetDrawPage() { return mpDrawPage; } + void SetDrawPage( SdrPage* pNew ){ mpDrawPage = pNew; } + + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, + SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override; + + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + virtual SwTwips ShrinkFrame( SwTwips, bool bTst = false, bool bInfo = false ) override; + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) override; +#ifdef DBG_UTIL + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; +#endif + + virtual bool FillSelection( SwSelectionList& rList, const SwRect& rRect ) const override; + + Point GetNextPrevContentPos( const Point &rPoint, bool bNext ) const; + + virtual Size ChgSize( const Size& aNewSize ) override; + + void SetIdleFlags() + { + mbIdleFormat = true; + + SwViewShell* pCurrShell = GetCurrShell(); + // May be NULL if called from SfxBaseModel::dispose + // (this happens in the build test 'rtfexport'). + if (pCurrShell != nullptr) + pCurrShell->GetDoc()->getIDocumentTimerAccess().StartIdling(); + } + bool IsIdleFormat() const { return mbIdleFormat; } + void ResetIdleFormat() { mbIdleFormat = false; } + + bool IsNeedGrammarCheck() const { return mbNeedGrammarCheck; } + void SetNeedGrammarCheck( bool bVal ) + { + mbNeedGrammarCheck = bVal; + + if ( bVal ) + { + SwViewShell* pCurrShell = GetCurrShell(); + // May be NULL if called from SfxBaseModel::dispose + // (this happens in the build test 'rtfexport'). + if (pCurrShell != nullptr) + pCurrShell->GetDoc()->getIDocumentTimerAccess().StartIdling(); + } + } + + /// Makes sure that all requested page-bound Flys find a Page + void SetAssertFlyPages() { mbAssertFlyPages = true; } + void AssertFlyPages(); + bool IsAssertFlyPages() const { return mbAssertFlyPages; } + + /** + * Makes sure that, starting from the passed Page, all page-bound Frames + * are on the right Page (pagenumber). + */ + static void AssertPageFlys( SwPageFrame * ); + + /// Invalidate all Content, Size or PrtArea + void InvalidateAllContent( SwInvalidateFlags nInvalidate ); + + /** + * Invalidate/re-calculate the position of all floating + * screen objects (Writer fly frames and drawing objects), which are + * anchored to paragraph or to character. + */ + void InvalidateAllObjPos(); + + /// Remove superfluous Pages + void SetSuperfluous() { mbCheckSuperfluous = true; } + bool IsSuperfluous() const { return mbCheckSuperfluous; } + void RemoveSuperfluous(); + + /** + * Query/set the current Page and the collective Page count + * We'll format as much as necessary + */ + sal_uInt16 GetCurrPage( const SwPaM* ) const; + sal_uInt16 SetCurrPage( SwCursor*, sal_uInt16 nPageNum ); + Point GetPagePos( sal_uInt16 nPageNum ) const; + sal_uInt16 GetPageNum() const { return mnPhyPageNums; } + void DecrPhyPageNums() { --mnPhyPageNums; } + void IncrPhyPageNums() { ++mnPhyPageNums; } + bool IsVirtPageNum() const { return mbIsVirtPageNum; } + inline void SetVirtPageNum( const bool bOf ) const; + bool IsDummyPage( sal_uInt16 nPageNum ) const; + + /** + * Point rPt: The point that should be used to find the page + * Size pSize: If given, we return the (first) page that overlaps with the + * rectangle defined by rPt and pSize + * bool bExtend: Extend each page to the left/right/top/bottom up to the + * next page margin + */ + const SwPageFrame* GetPageAtPos( const Point& rPt, const Size* pSize = nullptr, bool bExtend = false ) const; + + /** + * Point rPt: The point to test + * @returns true: if rPt is between top/bottom margins of two pages + * in hide-whitespace, rPt can be near the gap, but + * not strictly between pages (in a page) as gap is small. + * @returns false: if rPt is in a page or not strictly between two pages + */ + bool IsBetweenPages(const Point& rPt) const; + + void CalcFrameRects( SwShellCursor& ); + + /** + * Calculates the cells included from the current selection + * + * @returns false: There was no result because of an invalid layout + * @returns true: Everything worked fine. + */ + bool MakeTableCursors( SwTableCursor& ); + + void DisallowTurbo() const { const_cast<SwRootFrame*>(this)->mbTurboAllowed = false; } + void ResetTurboFlag() const { const_cast<SwRootFrame*>(this)->mbTurboAllowed = true; } + bool IsTurboAllowed() const { return mbTurboAllowed; } + void SetTurbo( const SwContentFrame *pContent ) { mpTurbo = pContent; } + void ResetTurbo() { mpTurbo = nullptr; } + const SwContentFrame *GetTurbo() const { return mpTurbo; } + + /// Update the footnote numbers of all Pages + void UpdateFootnoteNums(); // Only for page by page numbering! + + /// Remove all footnotes (but no references) + void RemoveFootnotes( SwPageFrame *pPage = nullptr, bool bPageOnly = false, + bool bEndNotes = false ); + void CheckFootnotePageDescs( bool bEndNote ); + + const SwPageFrame *GetLastPage() const { return mpLastPage; } + SwPageFrame *GetLastPage() { return mpLastPage; } + + static bool IsInPaint() { return s_isInPaint; } + + static void SetNoVirDev(const bool bNew) { s_isNoVirDev = bNew; } + + inline long GetBrowseWidth() const; + inline void InvalidateBrowseWidth(); + + bool IsNewLayout() const { return mbIsNewLayout; } + void ResetNewLayout() { mbIsNewLayout = false;} + + /** + * Empty SwSectionFrames are registered here for deletion and + * destroyed later on or deregistered. + */ + void InsertEmptySct( SwSectionFrame* pDel ); + void DeleteEmptySct() { if( mpDestroy ) DeleteEmptySct_(); } + void RemoveFromList( SwSectionFrame* pSct ) { if( mpDestroy ) RemoveFromList_( pSct ); } +#ifdef DBG_UTIL + bool IsInDelList( SwSectionFrame* pSct ) const; +#endif + + void SetCallbackActionEnabled( bool b ) { mbCallbackActionEnabled = b; } + bool IsCallbackActionEnabled() const { return mbCallbackActionEnabled; } + + bool IsAnyShellAccessible() const { return mnAccessibleShells > 0; } + void AddAccessibleShell() { ++mnAccessibleShells; } + void RemoveAccessibleShell() { --mnAccessibleShells; } + + /** + * Get page frame by physical page number + * looping through the lowers, which are page frame, in order to find the + * page frame with the given physical page number. + * if no page frame is found, 0 is returned. + * Note: Empty page frames are also returned. + * + * @param _nPageNum: physical page number of page frame to be searched and + * returned. + * + * @return pointer to the page frame with the given physical page number + */ + SwPageFrame* GetPageByPageNum( sal_uInt16 _nPageNum ) const; + + void CheckViewLayout( const SwViewOption* pViewOpt, const SwRect* pVisArea ); + bool IsLeftToRightViewLayout() const; + const SwRect& GetPagesArea() const { return maPagesArea; } + void SetSidebarChanged() { mbSidebarChanged = true; } + + bool IsLayoutFreezed() const { return mbLayoutFreezed; } + void FreezeLayout( bool freeze ) { mbLayoutFreezed = freeze; } + + void RemovePage( SwPageFrame **pDel, SwRemoveResult eResult ); + + /** + * Replacement for sw::DocumentRedlineManager::GetRedlineFlags() + * (this is layout-level redline hiding). + */ + bool IsHideRedlines() const { return mbHideRedlines; } + void SetHideRedlines(bool); +}; + +inline long SwRootFrame::GetBrowseWidth() const +{ + if ( !mbBrowseWidthValid ) + const_cast<SwRootFrame*>(this)->ImplCalcBrowseWidth(); + return mnBrowseWidth; +} + +inline void SwRootFrame::InvalidateBrowseWidth() +{ + if ( mbBrowseWidthValid ) + ImplInvalidateBrowseWidth(); +} + +inline void SwRootFrame::SetVirtPageNum( const bool bOf) const +{ + const_cast<SwRootFrame*>(this)->mbIsVirtPageNum = bOf; +} + +/// helper class to disable creation of an action by a callback event +/// in particular, change event from a drawing object (SwDrawContact::Changed()) +class DisableCallbackAction +{ + private: + SwRootFrame & m_rRootFrame; + bool m_bOldCallbackActionState; + + public: + explicit DisableCallbackAction(SwRootFrame & rRootFrame) + : m_rRootFrame(rRootFrame) + , m_bOldCallbackActionState(rRootFrame.IsCallbackActionEnabled()) + { + m_rRootFrame.SetCallbackActionEnabled(false); + } + + ~DisableCallbackAction() + { + m_rRootFrame.SetCallbackActionEnabled(m_bOldCallbackActionState); + } +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_ROOTFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/rowfrm.hxx b/sw/source/core/inc/rowfrm.hxx new file mode 100644 index 000000000..e131730c2 --- /dev/null +++ b/sw/source/core/inc/rowfrm.hxx @@ -0,0 +1,127 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_ROWFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_ROWFRM_HXX + +#include "layfrm.hxx" + +class SwTableLine; +class SwBorderAttrs; + +/// SwRowFrame is one table row in the document layout. +class SwRowFrame: public SwLayoutFrame +{ + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + /// Only change the Frame size, not the PrtArea SSize + virtual SwTwips ShrinkFrame( SwTwips, bool bTst = false, bool bInfo = false ) override; + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) override; + + const SwTableLine * m_pTabLine; + SwRowFrame * m_pFollowRow; ///< note: this is *only* set on old-style tables! + // #i29550# + sal_uInt16 mnTopMarginForLowers; + sal_uInt16 mnBottomMarginForLowers; + sal_uInt16 mnBottomLineSize; + // <-- collapsing + bool m_bIsFollowFlowRow; ///< note: this is *only* set on old-style tables! + bool m_bIsRepeatedHeadline; + bool m_bIsRowSpanLine; + + bool m_bForceRowSplitAllowed; + bool m_bIsInSplit; + + virtual void DestroyImpl() override; + virtual ~SwRowFrame() override; + +protected: + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + +public: + SwRowFrame( const SwTableLine &, SwFrame*, bool bInsertContent = true ); + + virtual void Cut() override; + + /** + * Register Flys after a line was created _AND_ inserted + * Must be called by the creator; the Fly is inserted _after_ it has + * been created; the same holds true for the Page at which the Flys + * are to be registered at. + */ + void RegistFlys( SwPageFrame *pPage = nullptr ); + + const SwTableLine *GetTabLine() const { return m_pTabLine; } + + /** + * Adapts the Cells to the current height; invalidates the Cells if + * the Direction does not match the height + */ + void AdjustCells( const SwTwips nHeight, const bool bHeight ); + + SwRowFrame* GetFollowRow() const { return m_pFollowRow; } + void SetFollowRow( SwRowFrame* pNew ) { m_pFollowRow = pNew; } + + // #i29550# + sal_uInt16 GetTopMarginForLowers() const { return mnTopMarginForLowers; } + void SetTopMarginForLowers( sal_uInt16 nNew ) { mnTopMarginForLowers = nNew; } + sal_uInt16 GetBottomMarginForLowers() const { return mnBottomMarginForLowers; } + void SetBottomMarginForLowers( sal_uInt16 nNew ) { mnBottomMarginForLowers = nNew; } + sal_uInt16 GetBottomLineSize() const { return mnBottomLineSize; } + void SetBottomLineSize( sal_uInt16 nNew ) { mnBottomLineSize = nNew; } + // <-- collapsing + + bool IsRepeatedHeadline() const { return m_bIsRepeatedHeadline; } + void SetRepeatedHeadline( bool bNew ) { m_bIsRepeatedHeadline = bNew; } + + // --> split table rows + bool IsRowSplitAllowed() const; + bool IsForceRowSplitAllowed() const { return m_bForceRowSplitAllowed; } + void SetForceRowSplitAllowed( bool bNew) { m_bForceRowSplitAllowed = bNew; }; + bool IsFollowFlowRow() const { return m_bIsFollowFlowRow; } + void SetFollowFlowRow( bool bNew ) { m_bIsFollowFlowRow = bNew; } + // <-- split table rows + + // #131283# Table row keep feature + bool ShouldRowKeepWithNext( const bool bCheckParents = true ) const; + + // #i4032# NEW TABLES + bool IsRowSpanLine() const { return m_bIsRowSpanLine; } + void SetRowSpanLine( bool bNew ) { m_bIsRowSpanLine = bNew; } + + // A row may only be split if the minimum height of the row frame + // fits into the vertical space left. + // The minimum height is found as maximum of two values: minimal + // contents of the row (e.g., height of first line of text, or an + // object, or lower table cell), and the minimum height setting. + // As the minimum height setting should not prevent the row to + // flow, (it only should ensure that *total* height is no less), we + // should not consider the setting when the split is performed + // (we should be able to keep on first page as little as required). + // When IsInSplit is true, lcl_CalcMinRowHeight will ignore the + // minimum height setting. It is set in lcl_RecalcSplitLine around + // lcl_RecalcRow and SwRowFrame::Calc that decide if it's possible + // to keep part of row's content on first page, and update table's + // height to fit the rest of space. + bool IsInSplit() const { return m_bIsInSplit; } + void SetInSplit(bool bNew = true) { m_bIsInSplit = bNew; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/scriptinfo.hxx b/sw/source/core/inc/scriptinfo.hxx new file mode 100644 index 000000000..ead531c5c --- /dev/null +++ b/sw/source/core/inc/scriptinfo.hxx @@ -0,0 +1,399 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SCRIPTINFO_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SCRIPTINFO_HXX + +#include <vector> +#include <deque> +#include <unordered_set> +#include <rtl/ustrbuf.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <i18nlangtag/lang.h> +#include "TextFrameIndex.hxx" + +class SwTextNode; +class SwTextFrame; +class Point; +class MultiSelection; +enum class SwFontScript; +namespace sw { struct MergedPara; } +namespace sw::mark { class IBookmark; } + +#define SPACING_PRECISION_FACTOR 100 + +// encapsulates information about script changes +class SwScriptInfo +{ +public: + enum CompType { KANA, SPECIAL_LEFT, SPECIAL_RIGHT, NONE, SPECIAL_MIDDLE}; + enum class MarkKind { Start = (1<<0), End = (1<<1), Point = (1<<2) }; + +private: + //! Records a single change in script type. + struct ScriptChangeInfo + { + TextFrameIndex position; //!< Character position at which we change script + sal_uInt8 type; //!< Script type (Latin/Asian/Complex) that we change to. + ScriptChangeInfo(TextFrameIndex pos, sal_uInt8 typ) : position(pos), type(typ) {}; + }; + //TODO - This is sorted, so should probably be a std::set rather than vector. + // But we also use random access (probably unnecessarily). + std::vector<ScriptChangeInfo> m_ScriptChanges; + //! Records a single change in direction. + struct DirectionChangeInfo + { + TextFrameIndex position; //!< Character position at which we change direction. + sal_uInt8 type; //!< Direction that we change to. + DirectionChangeInfo(TextFrameIndex pos, sal_uInt8 typ) : position(pos), type(typ) {}; + }; + std::vector<DirectionChangeInfo> m_DirectionChanges; + std::deque<TextFrameIndex> m_Kashida; + /// indexes into m_Kashida + std::unordered_set<size_t> m_KashidaInvalid; + std::deque<TextFrameIndex> m_NoKashidaLine; + std::deque<TextFrameIndex> m_NoKashidaLineEnd; + std::vector<TextFrameIndex> m_HiddenChg; + std::vector<std::pair<TextFrameIndex, MarkKind>> m_Bookmarks; + //! Records a single change in compression. + struct CompressionChangeInfo + { + TextFrameIndex position; //!< Character position where the change occurs. + TextFrameIndex length; //!< Length of the segment. + CompType type; //!< Type of compression that we change to. + CompressionChangeInfo(TextFrameIndex pos, TextFrameIndex len, CompType typ) : position(pos), length(len), type(typ) {}; + }; + std::vector<CompressionChangeInfo> m_CompressionChanges; +#ifdef DBG_UTIL + CompType DbgCompType(const TextFrameIndex nPos) const; +#endif + + TextFrameIndex m_nInvalidityPos; + sal_uInt8 m_nDefaultDir; + + void UpdateBidiInfo( const OUString& rText ); + bool IsKashidaValid(size_t nKashPos) const; + // returns true if nKashPos is newly marked invalid + bool MarkKashidaInvalid(size_t nKashPos); + void ClearKashidaInvalid(size_t nKashPos); + bool MarkOrClearKashidaInvalid(TextFrameIndex nStt, TextFrameIndex nLen, + bool bMark, sal_Int32 nMarkCount); + bool IsKashidaLine(TextFrameIndex nCharIdx) const; + // examines the range [ nStart, nStart + nEnd ] if there are kanas + // returns start index of kana entry in array, otherwise SAL_MAX_SIZE + size_t HasKana(TextFrameIndex nStart, TextFrameIndex nEnd) const; + +public: + + SwScriptInfo(); + ~SwScriptInfo(); + + // determines script changes + void InitScriptInfo(const SwTextNode& rNode, sw::MergedPara const* pMerged, bool bRTL); + void InitScriptInfo(const SwTextNode& rNode, sw::MergedPara const* pMerged); + + // set/get position from which data is invalid + void SetInvalidityA(const TextFrameIndex nPos) + { + if (nPos < m_nInvalidityPos) + m_nInvalidityPos = nPos; + } + TextFrameIndex GetInvalidityA() const + { + return m_nInvalidityPos; + } + + // get default direction for paragraph + sal_uInt8 GetDefaultDir() const { return m_nDefaultDir; }; + + // array operations, nCnt refers to array position + size_t CountScriptChg() const { return m_ScriptChanges.size(); } + TextFrameIndex GetScriptChg(const size_t nCnt) const + { + assert(nCnt < m_ScriptChanges.size()); + return m_ScriptChanges[nCnt].position; + } + sal_uInt8 GetScriptType( const size_t nCnt ) const + { + assert( nCnt < m_ScriptChanges.size()); + return m_ScriptChanges[nCnt].type; + } + + size_t CountDirChg() const { return m_DirectionChanges.size(); } + TextFrameIndex GetDirChg(const size_t nCnt) const + { + assert(nCnt < m_DirectionChanges.size()); + return m_DirectionChanges[ nCnt ].position; + } + sal_uInt8 GetDirType( const size_t nCnt ) const + { + assert(nCnt < m_DirectionChanges.size()); + return m_DirectionChanges[ nCnt ].type; + } + + size_t CountKashida() const + { + return m_Kashida.size(); + } + + TextFrameIndex GetKashida(const size_t nCnt) const + { + assert(nCnt < m_Kashida.size()); + return m_Kashida[nCnt]; + } + + size_t CountCompChg() const { return m_CompressionChanges.size(); }; + TextFrameIndex GetCompStart(const size_t nCnt) const + { + assert(nCnt < m_CompressionChanges.size()); + return m_CompressionChanges[ nCnt ].position; + } + TextFrameIndex GetCompLen(const size_t nCnt) const + { + assert(nCnt < m_CompressionChanges.size()); + return m_CompressionChanges[ nCnt ].length; + } + CompType GetCompType( const size_t nCnt ) const + { + assert(nCnt < m_CompressionChanges.size()); + return m_CompressionChanges[ nCnt ].type; + } + + size_t CountHiddenChg() const { return m_HiddenChg.size(); }; + TextFrameIndex GetHiddenChg(const size_t nCnt) const + { + assert(nCnt < m_HiddenChg.size()); + return m_HiddenChg[ nCnt ]; + } + TextFrameIndex NextHiddenChg(TextFrameIndex nPos) const; + TextFrameIndex NextBookmark(TextFrameIndex nPos) const; + MarkKind GetBookmark(TextFrameIndex nPos) const; + static void CalcHiddenRanges(const SwTextNode& rNode, + MultiSelection& rHiddenMulti, + std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> * pBookmarks); + static void selectHiddenTextProperty(const SwTextNode& rNode, + MultiSelection &rHiddenMulti, + std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> * pBookmarks); + static void selectRedLineDeleted(const SwTextNode& rNode, MultiSelection &rHiddenMulti, bool bSelect=true); + + // "high" level operations, nPos refers to string position + TextFrameIndex NextScriptChg(TextFrameIndex nPos) const; + sal_Int16 ScriptType(const TextFrameIndex nPos) const; + + // Returns the position of the next direction level change. + // If bLevel is set, the position of the next level which is smaller + // than the level at position nPos is returned. This is required to + // obtain the end of a SwBidiPortion + TextFrameIndex NextDirChg(const TextFrameIndex nPos, + const sal_uInt8* pLevel = nullptr) const; + sal_uInt8 DirType(const TextFrameIndex nPos) const; + + // HIDDEN TEXT STUFF START + +/** Hidden text range information - static and non-version + + @descr Determines if a given position is inside a hidden text range. The + static version tries to obtain a valid SwScriptInfo object + via the SwTextNode, otherwise it calculates the values from scratch. + The non-static version uses the internally cached information + for the calculation. + + @param rNode + The text node. + @param nPos + The given position that should be checked. + @param rnStartPos + Return parameter for the start position of the hidden range. + COMPLETE_STRING if nPos is not inside a hidden range. + @param rnEndPos + Return parameter for the end position of the hidden range. + 0 if nPos is not inside a hidden range. + @param rnEndPos + Return parameter that contains all the hidden text ranges. Optional. + @return + returns true if there are any hidden characters in this paragraph. + +*/ + static bool GetBoundsOfHiddenRange( const SwTextNode& rNode, sal_Int32 nPos, + sal_Int32& rnStartPos, sal_Int32& rnEndPos, + std::vector<sal_Int32>* pList = nullptr ); + bool GetBoundsOfHiddenRange(TextFrameIndex nPos, TextFrameIndex & rnStartPos, + TextFrameIndex & rnEndPos) const; + + static bool IsInHiddenRange( const SwTextNode& rNode, sal_Int32 nPos ); + +/** Hidden text attribute handling + + @descr Takes a string and either deletes the hidden ranges or sets + a given character in place of the hidden characters. + + @param rNode + The text node. + @param rText + The string to modify. + @param cChar + The character that should replace the hidden characters. + @param bDel + If set, the hidden ranges will be deleted from the text node. + */ + static sal_Int32 MaskHiddenRanges( + const SwTextNode& rNode, OUStringBuffer& rText, + const sal_Int32 nStt, const sal_Int32 nEnd, + const sal_Unicode cChar ); + +/** Hidden text attribute handling + + @descr Takes a SwTextNode and deletes the hidden ranges from the node. + + @param rNode + The text node. + */ + static void DeleteHiddenRanges( SwTextNode& rNode ); + + // HIDDEN TEXT STUFF END + + // modifies the kerning array according to a given compress value + long Compress( long* pKernArray, TextFrameIndex nIdx, TextFrameIndex nLen, + const sal_uInt16 nCompress, const sal_uInt16 nFontHeight, + const bool bCentered, + Point* pPoint = nullptr ) const; + +/** Performs a kashida justification on the kerning array + + @descr Add some extra space for kashida justification to the + positions in the kerning array. + @param pKernArray + The printers kerning array. Optional. + @param pScrArray + The screen kerning array. Optional. + @param nStt + Start referring to the paragraph. + @param nLen + The number of characters to be considered. + @param nSpaceAdd + The value which has to be added to a kashida opportunity. + @return The number of kashida opportunities in the given range +*/ + sal_Int32 KashidaJustify( long* pKernArray, long* pScrArray, + TextFrameIndex nStt, TextFrameIndex nLen, long nSpaceAdd = 0) const; + +/** Clears array of kashidas marked as invalid + */ + void ClearKashidaInvalid(TextFrameIndex const nStt, TextFrameIndex const nLen) + { + MarkOrClearKashidaInvalid(nStt, nLen, false, 0); + } + +/** Marks nCnt kashida positions as invalid + pKashidaPositions: array of char indices relative to the paragraph +*/ + void MarkKashidasInvalid(sal_Int32 nCnt, const TextFrameIndex* pKashidaPositions); + +/** Marks nCnt kashida positions as invalid + in the given text range + */ + bool MarkKashidasInvalid(sal_Int32 const nCnt, + TextFrameIndex const nStt, TextFrameIndex const nLen) + { + return MarkOrClearKashidaInvalid(nStt, nLen, true, nCnt); + } + +/** retrieves kashida opportunities for a given text range. + + rKashidaPositions: buffer to receive the char indices of the + kashida opportunities relative to the paragraph +*/ + void GetKashidaPositions(TextFrameIndex nStt, TextFrameIndex nLen, + std::vector<TextFrameIndex>& rKashidaPosition); + +/** Use regular blank justification instead of kashdida justification for the given line of text. + nStt Start char index of the line referring to the paragraph. + nLen Number of characters in the line +*/ + void SetNoKashidaLine(TextFrameIndex nStt, TextFrameIndex nLen); + +/** Clear forced blank justification for a given line. + nStt Start char index of the line referring to the paragraph. + nLen Number of characters in the line +*/ + void ClearNoKashidaLine(TextFrameIndex nStt, TextFrameIndex nLen); + +/** Checks if text is Arabic text. + + @descr Checks if text is Arabic text. + @param rText + The text to check + @param nStt + Start index of the text + @return Returns if the language is an Arabic language + */ + static bool IsArabicText(const OUString& rText, TextFrameIndex nStt, TextFrameIndex nLen); + +/** Performs a thai justification on the kerning array + + @descr Add some extra space for thai justification to the + positions in the kerning array. + @param rText + The String + @param pKernArray + The printers kerning array. Optional. + @param pScrArray + The screen kerning array. Optional. + @param nIdx + Start referring to the paragraph. + @param nLen + The number of characters to be considered. + @param nSpaceAdd + The value which has to be added to the cells. + @return The number of extra spaces in the given range +*/ + static TextFrameIndex ThaiJustify( const OUString& rText, long* pKernArray, + long* pScrArray, TextFrameIndex nIdx, + TextFrameIndex nLen, + TextFrameIndex nNumberOfBlanks = TextFrameIndex(0), + long nSpaceAdd = 0 ); + + static TextFrameIndex CountCJKCharacters(const OUString &rText, + TextFrameIndex nPos, TextFrameIndex nEnd, LanguageType aLang); + + static void CJKJustify( const OUString& rText, long* pKernArray, + long* pScrArray, TextFrameIndex nStt, + TextFrameIndex nLen, LanguageType aLang, + long nSpaceAdd, bool bIsSpaceStop ); + + /// return a frame for the node, ScriptInfo is its member... + /// (many clients need both frame and SI, and both have to match) + static SwScriptInfo* GetScriptInfo( const SwTextNode& rNode, + SwTextFrame const** o_pFrame = nullptr, + bool bAllowInvalid = false); + + SwFontScript WhichFont(TextFrameIndex nIdx) const; + static SwFontScript WhichFont(sal_Int32 nIdx, OUString const & rText); +}; + +namespace o3tl +{ + +template<> struct typed_flags<SwScriptInfo::MarkKind> : is_typed_flags<SwScriptInfo::MarkKind, 0x07> {}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/sectfrm.hxx b/sw/source/core/inc/sectfrm.hxx new file mode 100644 index 000000000..3b890b385 --- /dev/null +++ b/sw/source/core/inc/sectfrm.hxx @@ -0,0 +1,175 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SECTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SECTFRM_HXX + +#include "layfrm.hxx" +#include "flowfrm.hxx" + +class SwSection; +class SwSectionFormat; +class SwAttrSetChg; +class SwFootnoteContFrame; +class SwLayouter; + +enum class SwFindMode +{ + None = 0, EndNote = 1, LastCnt = 2, MyLast = 4 +}; + +class SwSectionFrame: public SwLayoutFrame, public SwFlowFrame +{ + SwSection* m_pSection; + bool m_bFootnoteAtEnd; // footnotes at the end of section + bool m_bEndnAtEnd; // endnotes at the end of section + bool m_bContentLock; // content locked + bool m_bOwnFootnoteNum; // special numbering of footnotes + bool m_bFootnoteLock; // ftn, don't leave this section bwd + + void UpdateAttr_( const SfxPoolItem*, const SfxPoolItem*, sal_uInt8 &, + SwAttrSetChg *pa = nullptr, SwAttrSetChg *pb = nullptr ); + void Cut_( bool bRemove ); + // Is there a FootnoteContainer? + // An empty sectionfrm without FootnoteCont is superfluous + bool IsSuperfluous() const { return !ContainsAny() && !ContainsFootnoteCont(); } + void CalcFootnoteAtEndFlag(); + void CalcEndAtEndFlag(); + const SwSectionFormat* GetEndSectFormat_() const; + bool IsEndnoteAtMyEnd() const; + + virtual void DestroyImpl() override; + virtual ~SwSectionFrame() override; + +protected: + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + virtual bool ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool &rReformat ) override; + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + virtual void SwClientNotify( const SwModify&, const SfxHint& ) override; + +public: + SwSectionFrame( SwSection &, SwFrame* ); // Content is not created! + SwSectionFrame( SwSectionFrame &, bool bMaster ); // _ONLY_ for creating Master/Follows! + + void Init(); + virtual void CheckDirection( bool bVert ) override; + + virtual void PaintSubsidiaryLines( const SwPageFrame*, const SwRect& ) const override; + + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; + + inline const SwSectionFrame *GetFollow() const; + inline SwSectionFrame *GetFollow(); + SwSectionFrame* FindMaster() const; + + SwContentFrame *FindLastContent( SwFindMode nMode = SwFindMode::None ); + inline const SwContentFrame *FindLastContent() const; + SwSection* GetSection() { return m_pSection; } + const SwSection* GetSection() const { return m_pSection; } + void ColLock() { mbColLocked = true; } + void ColUnlock() { mbColLocked = false; } + + void CalcFootnoteContent(); + void SimpleFormat(); + bool IsDescendantFrom( const SwSectionFormat* pSect ) const; + bool HasToBreak( const SwFrame* pFrame ) const; + void MergeNext( SwSectionFrame* pNxt ); + + /** + * Splits the SectionFrame surrounding the pFrame up in two parts: + * pFrame and the start of the 2nd part + */ + bool SplitSect( SwFrame* pFrame, bool bApres ); + void DelEmpty( bool bRemove ); // Like Cut(), except for that Follow chaining is maintained + SwFootnoteContFrame* ContainsFootnoteCont( const SwFootnoteContFrame* pCont = nullptr ) const; + bool Growable() const; + SwTwips Shrink_( SwTwips, bool bTst ); + SwTwips Grow_ ( SwTwips, bool bTst ); + + /** + * A sectionfrm has to maximize, if he has a follow or a ftncontainer at + * the end of the page. A superfluous follow will be ignored, + * if bCheckFollow is set. + */ + bool ToMaximize( bool bCheckFollow ) const; + bool ToMaximize_() const { + if( !m_pSection ) return false; + return ToMaximize( false ); + } + bool MoveAllowed( const SwFrame* ) const; + bool CalcMinDiff( SwTwips& rMinDiff ) const; + + /** + * Returns the size delta that the section would like to be + * greater if it has undersized TextFrames in it. + * + * The return value is > 0 for undersized Frames, or 0 otherwise. + * + * If necessary the undersized-flag is corrected. + * We need this in the FormatWidthCols to "deflate" columns there. + */ + SwTwips Undersize(); + SwTwips CalcUndersize() const; + + /// Adapt size to surroundings + void CheckClipping( bool bGrow, bool bMaximize ); + + void InvalidateFootnotePos(); + void CollectEndnotes( SwLayouter* pLayouter ); + const SwSectionFormat* GetEndSectFormat() const { + if( IsEndnAtEnd() ) return GetEndSectFormat_(); + return nullptr; + } + + static void MoveContentAndDelete( SwSectionFrame* pDel, bool bSave ); + + bool IsBalancedSection() const; + + virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override; + + bool IsFootnoteAtEnd() const { return m_bFootnoteAtEnd; } + bool IsEndnAtEnd() const { return m_bEndnAtEnd; } + bool IsAnyNoteAtEnd() const { return m_bFootnoteAtEnd || m_bEndnAtEnd; } + + void SetContentLock( bool bNew ) { m_bContentLock = bNew; } + bool IsContentLocked() const { return m_bContentLock; } + + bool IsOwnFootnoteNum() const { return m_bOwnFootnoteNum; } + + void SetFootnoteLock( bool bNew ) { m_bFootnoteLock = bNew; } + bool IsFootnoteLock() const { return m_bFootnoteLock; } +}; + +inline const SwSectionFrame *SwSectionFrame::GetFollow() const +{ + return static_cast<const SwSectionFrame*>(SwFlowFrame::GetFollow()); +} +inline SwSectionFrame *SwSectionFrame::GetFollow() +{ + return static_cast<SwSectionFrame*>(SwFlowFrame::GetFollow()); +} +inline const SwContentFrame *SwSectionFrame::FindLastContent() const +{ + return const_cast<SwSectionFrame*>(this)->FindLastContent(); +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_SECTFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/sortedobjs.hxx b/sw/source/core/inc/sortedobjs.hxx new file mode 100644 index 000000000..6d524f492 --- /dev/null +++ b/sw/source/core/inc/sortedobjs.hxx @@ -0,0 +1,100 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SORTEDOBJS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SORTEDOBJS_HXX + +#include <vector> +#include <swdllapi.h> + +class SwAnchoredObject; + +/** class for collecting anchored objects + + for #i28701# + Anchored objects can be inserted and deleted. The entries can be directly + accessed via index. + An anchored object is inserted sorted. The sort criteria are: + - anchor type + - order 1: to-page, 2: to-fly, 3: to-paragraph|to-character|as-character + - anchor node + - wrapping style (inclusive layer) + - order 1: wrapping style != css::text::WrapTextMode_THROUGH and not in hell layer, + 2: wrapping style = css::text::WrapTextMode_THROUGH or in hell layer + - wrapping style influence + - order 1: NONE_SUCCESSIVE_POSITIONED, 2: NONE_CONCURRENT_POSITIONED + - again anchor type + - order 1: to-paragraph, 2: to-character, 3: as-character + - anchor node position + - internal anchor order number + If one of the sort criteria attributes of an anchored object changes, + the sorting has to be updated - use method <Update(..)> +*/ +class SW_DLLPUBLIC SwSortedObjs +{ + private: + std::vector< SwAnchoredObject* > maSortedObjLst; + + public: + typedef std::vector<SwAnchoredObject*>::const_iterator const_iterator; + SwSortedObjs(); + ~SwSortedObjs(); + + size_t size() const; + + /** direct access to the entries + + @param _nIndex + input parameter - index of entry, valid value range [0..size()-1] + */ + SwAnchoredObject* operator[]( size_t _nIndex ) const; + const_iterator begin() const + { return maSortedObjLst.cbegin(); }; + const_iterator end() const + { return maSortedObjLst.cend(); }; + + bool Insert( SwAnchoredObject& _rAnchoredObj ); + + void Remove( SwAnchoredObject& _rAnchoredObj ); + + bool Contains( const SwAnchoredObject& _rAnchoredObj ) const; + + /** method to update the position of the given anchored object in the + sorted list + + @return boolean, indicating success of the update. + */ + void Update(SwAnchoredObject& _rAnchoredObj); + void UpdateAll(); + + /** Position of object <_rAnchoredObj> in sorted list + + Returns the number of the list position of object <_rAnchoredObj>. + Returns <size()>, if object isn't contained in list. + + @return size_t + Number of the list position of object <_rAnchoredObj> + */ + size_t ListPosOf( const SwAnchoredObject& _rAnchoredObj ) const; + + bool is_sorted() const; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swblocks.hxx b/sw/source/core/inc/swblocks.hxx new file mode 100644 index 000000000..3cc65c484 --- /dev/null +++ b/sw/source/core/inc/swblocks.hxx @@ -0,0 +1,128 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWBLOCKS_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWBLOCKS_HXX + +#include <tools/date.hxx> +#include <tools/time.hxx> +#include <o3tl/sorted_vector.hxx> +#include <vcl/errcode.hxx> +#include <rtl/ref.hxx> + +class SwPaM; +class SwDoc; +class SvxMacroTableDtor; + +// Name of a text block: + +class SwBlockName +{ + friend class SwImpBlocks; + sal_uInt16 nHashS, nHashL; // Hash codes for testing +public: + OUString aShort; /// Shortname + OUString aLong; /// Longname + OUString aPackageName; /// Package name + bool bIsOnlyTextFlagInit : 1; /// Is the Flag valid? + bool bIsOnlyText : 1; /// Unformatted text + + SwBlockName( const OUString& rShort, const OUString& rLong ); + SwBlockName( const OUString& rShort, const OUString& rLong, const OUString& rPackageName ); + + /// For sorting in the array + bool operator< ( const SwBlockName& r ) const { return aShort < r.aShort; } +}; + +class SwBlockNames : public o3tl::sorted_vector<std::unique_ptr<SwBlockName>, o3tl::less_uniqueptr_to<SwBlockName> > {}; + +class SwImpBlocks +{ + friend class SwTextBlocks; +protected: + OUString m_aFile; // Physical file name + OUString m_aName; // Logical file name + OUString m_aCurrentText; // Current text + OUString m_aShort, m_aLong; // Short- and longname (PutDoc) + OUString m_sBaseURL; // Base URL - has to be set at the Readers and Writers + SwBlockNames m_aNames; // List of all Blocks + Date m_aDateModified; // For aligning the Actions + tools::Time m_aTimeModified; + rtl::Reference<SwDoc> m_xDoc; // Document to be switched + sal_uInt16 m_nCurrentIndex; // Current Index + bool m_bReadOnly : 1; + bool m_bInPutMuchBlocks : 1; // Put several block entries + bool m_bInfoChanged : 1; // Whether any info of TextBlock changed + + explicit SwImpBlocks( const OUString& ); + + enum class FileType { + NoFile, // Not present + None, // No TB file + XML // XML Block List + }; + static FileType GetFileType( const OUString& ); + + virtual void ClearDoc(); // Delete Doc content + std::unique_ptr<SwPaM> MakePaM(); // Span PaM over Doc + virtual void AddName( const OUString&, const OUString&, bool bOnlyText = false ); + bool IsFileChanged() const; + void Touch(); + +public: + virtual ~SwImpBlocks(); + + static sal_uInt16 Hash( const OUString& ); /// Hashcode for Block names + sal_uInt16 GetCount() const; /// Get count of Text Blocks + sal_uInt16 GetIndex( const OUString& ) const; /// Index for shortnames + sal_uInt16 GetLongIndex( const OUString& ) const; /// Index for longnames + OUString GetShortName( sal_uInt16 ) const; /// Return shortname for index + OUString GetLongName( sal_uInt16 ) const; /// Return longname for index + OUString GetPackageName( sal_uInt16 ) const; /// Return packagename for index + + const OUString& GetFileName() const {return m_aFile;} /// Return physical file name + void SetName( const OUString& rName ) /// Logic name + { m_aName = rName; m_bInfoChanged = true; } + const OUString& GetName() const + { return m_aName; } + + const OUString& GetBaseURL() const { return m_sBaseURL;} + void SetBaseURL( const OUString& rURL ) { m_sBaseURL = rURL; } + + virtual ErrCode Delete( sal_uInt16 ) = 0; + virtual ErrCode Rename( sal_uInt16, const OUString& ) = 0; + virtual ErrCode GetDoc( sal_uInt16 ) = 0; + virtual ErrCode BeginPutDoc( const OUString&, const OUString& ) = 0; + virtual ErrCode PutDoc() = 0; + virtual ErrCode PutText( const OUString&, const OUString&, const OUString& ) = 0; + virtual ErrCode MakeBlockList() = 0; + + virtual ErrCode OpenFile( bool bReadOnly = true ) = 0; + virtual void CloseFile() = 0; + + virtual bool IsOnlyTextBlock( const OUString& rShort ) const; + + virtual ErrCode GetMacroTable( sal_uInt16 nIdx, SvxMacroTableDtor& rMacroTable ); + virtual ErrCode SetMacroTable( sal_uInt16 nIdx, + const SvxMacroTableDtor& rMacroTable ); + virtual bool PutMuchEntries( bool bOn ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swcache.hxx b/sw/source/core/inc/swcache.hxx new file mode 100644 index 000000000..92b678c07 --- /dev/null +++ b/sw/source/core/inc/swcache.hxx @@ -0,0 +1,265 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWCACHE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWCACHE_HXX + +/** + * Here, we manage pointers in a simple PtrArray to objects. + * These objects are created (using new) in cache access classes; they are + * destroyed by the cache. + * + * One can access these objects by array index or by searching in the array. + * If you access it by index, managing the index is the responsibility of + * the cache user. + * + * The cached objects are derived from the base class SwCacheObj. + * In it, the cache objects are doubly-linked which allows for the use of + * an LRU algorithm. + * + * The LRU algorithm can be changed in the base class, by setting a virtual + * First Pointer. It can be set to the first real one plus an offset. + * By doing so we can protect the start area of the cache and make sure we + * don't mess up the cache during some special operations. + * E.g.: the Idle Handler should not destroy the cache for the visible area. + * + * The cache can be grown and shrunk in size. + * E.g.: The cache for FormatInfo is grown for every new Shell and shrunk + * when destroying them. + */ + +#include <memory> +#include <vector> + +#include <rtl/string.hxx> + +class SwCacheObj; + +class SwCache +{ + std::vector<std::unique_ptr<SwCacheObj>> m_aCacheObjects; + std::vector<sal_uInt16> m_aFreePositions; /// Free positions for the Insert if the maximum has not been reached + /// Every time an object is deregistered, its position is added here + SwCacheObj *m_pRealFirst; /// _ALWAYS_ the real first LRU + SwCacheObj *m_pFirst; /// The virtual first + SwCacheObj *m_pLast; + + sal_uInt16 m_nCurMax; // Maximum of accepted objects + + void DeleteObj( SwCacheObj *pObj ); + +#ifdef DBG_UTIL + OString m_aName; + long m_nAppend; /// number of entries appended + long m_nInsertFree; /// number of entries inserted on freed position + long m_nReplace; /// number of LRU replacements + long m_nGetSuccess; + long m_nGetFail; + long m_nToTop; /// number of reordering (LRU) + long m_nDelete; /// number of explicit deletes + long m_nGetSeek; /// number of gets without index + long m_nAverageSeekCnt; /// number of seeks for all gets without index + long m_nFlushCnt; /// number of flush calls + long m_nFlushedObjects; + long m_nIncreaseMax; /// number of cache size increases + long m_nDecreaseMax; /// number of cache size decreases + + void Check(); +#endif + +public: + +// Only add sal_uInt8!!! +#ifdef DBG_UTIL + SwCache( const sal_uInt16 nInitSize, const OString &rNm ); +#else + SwCache( const sal_uInt16 nInitSize ); +#endif + /// The dtor will free all objects still in the vector + ~SwCache(); + + void Flush(); + + //bToTop == false -> No LRU resorting! + SwCacheObj *Get( const void *pOwner, const bool bToTop = true ); + SwCacheObj *Get( const void *pOwner, const sal_uInt16 nIndex, + const bool bToTop = true ); + void ToTop( SwCacheObj *pObj ); + + bool Insert(SwCacheObj *pNew, bool isDuplicateOwnerAllowed); + void Delete(const void * pOwner, sal_uInt16 nIndex); + void Delete( const void *pOwner ); + + void SetLRUOfst( const sal_uInt16 nOfst ); /// nOfst determines how many are not to be touched + void ResetLRUOfst() { m_pFirst = m_pRealFirst; } + + void IncreaseMax( const sal_uInt16 nAdd ); + void DecreaseMax( const sal_uInt16 nSub ); + sal_uInt16 GetCurMax() const { return m_nCurMax; } + SwCacheObj *First() { return m_pRealFirst; } + static inline SwCacheObj *Next( SwCacheObj *pCacheObj); + SwCacheObj* operator[](sal_uInt16 nIndex) { return m_aCacheObjects[nIndex].get(); } + sal_uInt16 size() { return m_aCacheObjects.size(); } +}; + +/// Try to prevent visible SwParaPortions from being deleted. +class SwSaveSetLRUOfst +{ +public: + SwSaveSetLRUOfst(); + ~SwSaveSetLRUOfst(); +}; + +/** + * The Cache object base class + * Users of the Cache must derive a class from the SwCacheObj and store + * their payload there + */ +class SwCacheObj +{ + friend class SwCache; /// Can do everything + + SwCacheObj *m_pNext; /// For the LRU chaining + SwCacheObj *m_pPrev; + + sal_uInt16 m_nCachePos; /// Position in the Cache array + + sal_uInt8 m_nLock; + + SwCacheObj *GetNext() { return m_pNext; } + SwCacheObj *GetPrev() { return m_pPrev; } + void SetNext( SwCacheObj *pNew ) { m_pNext = pNew; } + void SetPrev( SwCacheObj *pNew ) { m_pPrev = pNew; } + + void SetCachePos(const sal_uInt16 nNew) + { + if (m_nCachePos != nNew) + { + m_nCachePos = nNew; + UpdateCachePos(); + } + } + virtual void UpdateCachePos() { } + +protected: + const void *m_pOwner; + +public: + + SwCacheObj( const void *pOwner ); + virtual ~SwCacheObj(); + + const void *GetOwner() const { return m_pOwner; } + inline bool IsOwner( const void *pNew ) const; + + sal_uInt16 GetCachePos() const { return m_nCachePos; } + + bool IsLocked() const { return 0 != m_nLock; } + +#ifdef DBG_UTIL + void Lock(); + void Unlock(); +#else + void Lock() { ++m_nLock; } + void Unlock() { --m_nLock; } +#endif +}; + +/** + * Access class for the Cache + * + * The Cache object is created in the ctor. + * If the Cache does not return one, the member is set to 0 and one is + * created on the Get() and added to the Cache (if possible). + * Cache users must derive a class from SwCacheAccess in order to + * guarantee type safety. The base class should always be called for the + * Get(). A derived Get() should only ever guarantee type safety. + * Cache objects are always locked for the instance's life time. + */ +class SwCacheAccess +{ + SwCache &m_rCache; + + void Get_(bool isDuplicateOwnerAllowed); + +protected: + SwCacheObj *m_pObj; + const void *m_pOwner; /// Can be use in NewObj + + virtual SwCacheObj *NewObj() = 0; + + inline SwCacheObj *Get(bool isDuplicateOwnerAllowed); + + inline SwCacheAccess( SwCache &rCache, const void *pOwner, bool bSeek ); + inline SwCacheAccess( SwCache &rCache, const void* nCacheId, const sal_uInt16 nIndex ); + +public: + virtual ~SwCacheAccess(); +}; + + +inline bool SwCacheObj::IsOwner( const void *pNew ) const +{ + return m_pOwner && m_pOwner == pNew; +} + +inline SwCacheObj *SwCache::Next( SwCacheObj *pCacheObj) +{ + if ( pCacheObj ) + return pCacheObj->GetNext(); + else + return nullptr; +} + +inline SwCacheAccess::SwCacheAccess( SwCache &rC, const void *pOwn, bool bSeek ) : + m_rCache( rC ), + m_pObj( nullptr ), + m_pOwner( pOwn ) +{ + if ( bSeek && m_pOwner ) + { + m_pObj = m_rCache.Get( m_pOwner ); + if (m_pObj) + m_pObj->Lock(); + } +} + +inline SwCacheAccess::SwCacheAccess( SwCache &rC, const void* nCacheId, + const sal_uInt16 nIndex ) : + m_rCache( rC ), + m_pObj( nullptr ), + m_pOwner( nCacheId ) +{ + if ( m_pOwner ) + { + m_pObj = m_rCache.Get( m_pOwner, nIndex ); + if (m_pObj) + m_pObj->Lock(); + } +} + +inline SwCacheObj *SwCacheAccess::Get(bool const isDuplicateOwnerAllowed = true) +{ + if ( !m_pObj ) + Get_(isDuplicateOwnerAllowed); + return m_pObj; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swfntcch.hxx b/sw/source/core/inc/swfntcch.hxx new file mode 100644 index 000000000..ebe64d841 --- /dev/null +++ b/sw/source/core/inc/swfntcch.hxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWFNTCCH_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWFNTCCH_HXX + +#define NUM_DEFAULT_VALUES 39 + +#include "swcache.hxx" +#include "swfont.hxx" + +class SwViewShell; +class SfxPoolItem; + +class SwFontCache : public SwCache +{ +public: + + SwFontCache() : SwCache(50 +#ifdef DBG_UTIL + , "Global AttributSet/Font-Cache pSwFontCache" +#endif + ) {} + +}; + +// AttributSet/Font-Cache, globale Variable, in FontCache.Cxx angelegt +extern SwFontCache *pSwFontCache; + +class SwFontObj : public SwCacheObj +{ + friend class SwFontAccess; + +private: + SwFont m_aSwFont; + const SfxPoolItem* m_pDefaultArray[ NUM_DEFAULT_VALUES ]; + +public: + SwFontObj( const void* pOwner, SwViewShell *pSh ); + + virtual ~SwFontObj() override; + + SwFont& GetFont() { return m_aSwFont; } + const SwFont& GetFont() const { return m_aSwFont; } + const SfxPoolItem** GetDefault() { return m_pDefaultArray; } +}; + +class SwFontAccess : public SwCacheAccess +{ + SwViewShell *m_pShell; +protected: + virtual SwCacheObj *NewObj( ) override; + +public: + SwFontAccess( const void *pOwner, SwViewShell *pSh ); + SwFontObj *Get(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swfont.hxx b/sw/source/core/inc/swfont.hxx new file mode 100644 index 000000000..b9e367602 --- /dev/null +++ b/sw/source/core/inc/swfont.hxx @@ -0,0 +1,1000 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWFONT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWFONT_HXX + +#include <memory> +#include <i18nlangtag/lang.h> +#include <tools/color.hxx> +#include <tools/gen.hxx> +#include <svl/poolitem.hxx> +#include <editeng/svxfont.hxx> +#include <swtypes.hxx> +#include "drawfont.hxx" +#include <editeng/borderline.hxx> +#include <optional> +#include <o3tl/enumarray.hxx> + +class SfxItemSet; +class SwAttrSet; +class SwDoCapitals; // DoCapitals +class SwScriptInfo; // GetTextSize_ +class SwViewShell; +class IDocumentSettingAccess; +enum class SvxShadowItemSide; + +const sal_Unicode CH_BLANK = ' '; // ' ' blank spaces +const sal_Unicode CH_BREAK = 0x0A; +const sal_Unicode CH_TAB = '\t'; // \t +const sal_Unicode CH_PAR = 0xB6; // paragraph +const sal_Unicode CH_BULLET = 0xB7; // centered dot +const sal_Unicode CH_FULL_BLANK = 0x3000; +const sal_Unicode CH_NB_SPACE = 0xA0; +const sal_Unicode CH_SIX_PER_EM = 0x2006; // six-per-em space + +sal_uInt16 UnMapDirection( sal_uInt16 nDir, const bool bVertFormat, const bool bVertFormatLRBT ); + +class SwSubFont : public SvxFont +{ + friend class SwFont; + const void* m_nFontCacheId; // "MagicNumber" within the font cache + Size m_aSize; // foreigners only see this size + sal_uInt16 m_nFontIndex; // index in the font cache + sal_uInt16 m_nOrgHeight; // height including escapement/proportion + sal_uInt16 m_nOrgAscent; // ascent including escapement/proportion + sal_uInt16 m_nProportionalWidth; // proportional width + bool m_bSmallCapsPercentage66; + + sal_uInt16 CalcEscAscent( const sal_uInt16 nOldAscent ) const; + sal_uInt16 CalcEscHeight( const sal_uInt16 nOldHeight, + const sal_uInt16 nOldAscent ) const; + void CalcEsc( SwDrawTextInfo const & rInf, Point& rPos ); + + short CheckKerning_( ); + + bool ChgFnt( SwViewShell const *pSh, OutputDevice& rOut ); + bool IsSymbol( SwViewShell const *pSh ); + sal_uInt16 GetAscent( SwViewShell const *pSh, const OutputDevice& rOut ); + sal_uInt16 GetHeight( SwViewShell const *pSh, const OutputDevice& rOut ); + Size GetTextSize_( SwDrawTextInfo& rInf ); + Size GetCapitalSize( SwDrawTextInfo& rInf ); + void DrawText_( SwDrawTextInfo &rInf, const bool bGrey ); + void DrawCapital( SwDrawTextInfo &rInf ); + void DrawStretchCapital( SwDrawTextInfo &rInf ); + void DoOnCapitals( SwDoCapitals &rDo ); + void DrawStretchText_( SwDrawTextInfo &rInf ); + TextFrameIndex GetModelPositionForViewPoint_( SwDrawTextInfo& rInf ); + TextFrameIndex GetCapitalCursorOfst( SwDrawTextInfo& rInf ); + + inline void SetColor( const Color& rColor ); + inline void SetFillColor( const Color& rColor ); + inline void SetCharSet( const rtl_TextEncoding eCharSet ); + inline void SetPitch( const FontPitch ePitch ); + inline void SetAlign( const FontAlign eAlign ); + inline void SetUnderline( const FontLineStyle eUnderline ); + inline void SetOverline( const FontLineStyle eOverline ); + inline void SetStrikeout( const FontStrikeout eStrikeout ); + inline void SetItalic( const FontItalic eItalic ); + inline void SetOutline( const bool bOutline ); + inline void SetVertical( const sal_uInt16 nDir, const bool bVertFormat ); + inline void SetShadow( const bool bShadow ); + inline void SetAutoKern( FontKerning nAutoKern ); + inline void SetWordLineMode( const bool bWordLineMode ); + inline void SetEmphasisMark( const FontEmphasisMark eValue ); + inline void SetRelief( const FontRelief eNew ); + + // methods for sub-/superscript + inline void SetEscapement( const short nNewEsc ); + inline void SetProportion( const sal_uInt8 nNewPropr ); + + inline void SetFamily( const FontFamily eFamily ); + inline void SetName( const OUString& rName ); + inline void SetStyleName( const OUString& rStyleName ); + inline void SetSize( const Size& rSize ); + inline void SetWeight( const FontWeight eWeight ); + inline void SetLanguage( LanguageType eNewLang ); + short CheckKerning() + { return GetFixKerning() >= 0 ? GetFixKerning() : CheckKerning_( ); } + void SetPropWidth( const sal_uInt16 nNew ) + { m_nFontCacheId = nullptr; m_nProportionalWidth = nNew; } +public: + SwSubFont() : m_aSize(0,0) + { m_nFontCacheId = nullptr; m_nFontIndex = m_nOrgHeight = m_nOrgAscent = 0; m_nProportionalWidth =100; m_bSmallCapsPercentage66 = false; } + sal_uInt16 GetPropWidth() const { return m_nProportionalWidth; } +}; + +enum class SwFontScript +{ + Latin, CJK, CTL, LAST = CTL +}; + +// mostly used as a "unknown script" marker +#define SW_SCRIPTS (SwFontScript(int(SwFontScript::LAST)+1)) + +class SwFont +{ + // CJK == Chinese, Japanese, Korean + // CTL == Complex text layout ( Hebrew, Arabic ) + o3tl::enumarray<SwFontScript, SwSubFont> m_aSub; // Latin-, CJK- and CTL-font + + std::unique_ptr<Color> + m_pBackColor; // background color (i.e. at character styles) + Color m_aHighlightColor; // highlight color + Color m_aUnderColor; // color of the underlining + Color m_aOverColor; // color of the overlining + + // character borders + std::optional<editeng::SvxBorderLine> m_aTopBorder; + std::optional<editeng::SvxBorderLine> m_aBottomBorder; + std::optional<editeng::SvxBorderLine> m_aRightBorder; + std::optional<editeng::SvxBorderLine> m_aLeftBorder; + + // border distance + sal_uInt16 m_nTopBorderDist; + sal_uInt16 m_nBottomBorderDist; + sal_uInt16 m_nRightBorderDist; + sal_uInt16 m_nLeftBorderDist; + + Color m_aShadowColor; + sal_uInt16 m_nShadowWidth; + SvxShadowLocation m_aShadowLocation; + + sal_uInt8 m_nToxCount; // counts the nesting depth of the Tox + sal_uInt8 m_nRefCount; // counts the nesting depth of the Refs + sal_uInt8 m_nMetaCount; // count META/METAFIELD + sal_uInt8 m_nInputFieldCount; // count INPUTFIELD + + SwFontScript m_nActual; // actual font (Latin, CJK or CTL) + + // switch for the font-extensions + bool m_bPaintBlank :1; // blanks not with DrawRect + bool m_bFontChg :1; + bool m_bOrgChg :1; // nOrgHeight/Ascent are invalid + bool m_bGreyWave :1; // for the extended TextInput: gray waveline + +public: + SwFont( const SwAttrSet* pSet, const IDocumentSettingAccess* pIDocumentSettingAccess ); + SwFont( const SwFont& rFont ); + + void ChgFnt( SwViewShell const *pSh, OutputDevice& rOut ) + { m_bPaintBlank = m_aSub[m_nActual].ChgFnt( pSh, rOut ); } + + ~SwFont(); + + SwFont& operator=( const SwFont &rFont ); + + SwFontScript GetActual() const { return m_nActual; } + inline void SetActual( SwFontScript nNew ); + const SvxFont& GetActualFont() const { return m_aSub[m_nActual]; } + + // gets a font cache id via SwFntAccess + void AllocFontCacheId( SwViewShell const *pSh, SwFontScript nWhich ); + // set background color + void SetBackColor( Color* pNewColor ); + const Color* GetBackColor() const{ return m_pBackColor.get(); } + void SetHighlightColor( const Color& aNewColor ); + const Color& GetHighlightColor() const { return m_aHighlightColor; } + + void CheckFontCacheId( SwViewShell const *pSh, SwFontScript nWhich ) + { if( !m_aSub[ nWhich ].m_nFontCacheId ) AllocFontCacheId( pSh, nWhich ); } + void GetFontCacheId( const void* &rnFontCacheId, sal_uInt16 &rIdx, SwFontScript nWhich ) + { rnFontCacheId = m_aSub[nWhich].m_nFontCacheId; rIdx = m_aSub[nWhich].m_nFontIndex; } + void SetFontCacheId( const void* nNewFontCacheId, const sal_uInt16 nIdx, SwFontScript nWhich ) + { m_aSub[nWhich].m_nFontCacheId = nNewFontCacheId; m_aSub[nWhich].m_nFontIndex = nIdx; } + bool DifferentFontCacheId( const SwFont* pFnt, SwFontScript nWhich ) + { return m_aSub[nWhich].m_nFontCacheId != pFnt->m_aSub[nWhich].m_nFontCacheId || + !m_aSub[nWhich].m_nFontCacheId || !pFnt->m_aSub[nWhich].m_nFontCacheId; } + + const Size &GetSize( SwFontScript nWhich ) const + { return m_aSub[nWhich].m_aSize; } + bool IsFntChg() const { return m_bFontChg; } + void SetFntChg( const bool bNew ) { m_bFontChg = bNew; } + + // the encapsulated SV-Font-methods (set bFntChg to true) + inline void SetColor( const Color& rColor ); + inline void SetFillColor( const Color& rColor ); + inline void SetAlign( const FontAlign eAlign ); + inline void SetUnderline( const FontLineStyle eUnderline ); + void SetUnderColor( const Color &rColor ) { m_aUnderColor = rColor; } + inline void SetOverline( const FontLineStyle eOverline ); + void SetOverColor( const Color &rColor ) { m_aOverColor = rColor; } + inline void SetStrikeout( const FontStrikeout eStrikeout ); + inline void SetOutline( const bool bOutline ); + void SetVertical(sal_uInt16 nDir, const bool bVertLayout = false, + const bool bVertLayoutLRBT = false); + inline void SetShadow( const bool bShadow ); + inline void SetAutoKern( FontKerning nAutoKern ); + inline void SetTransparent( const bool bTrans ); + inline void SetWordLineMode( const bool bWordLineMode ); + inline void SetFixKerning( const short nNewKern ); + inline void SetCaseMap( const SvxCaseMap eNew ); + inline void SetEmphasisMark( const FontEmphasisMark eValue ); + + // methods for sub-/superscript + inline void SetEscapement( const short nNewEsc ); + inline void SetProportion( const sal_uInt8 nNewPropr ); + + inline void SetPropWidth( const sal_uInt16 nNew ); + + inline void SetFamily( const FontFamily eFamily, const SwFontScript nWhich ); + inline void SetName( const OUString& rName, const SwFontScript nWhich ); + inline void SetStyleName( const OUString& rStyleName, const SwFontScript nWhich ); + inline void SetSize( const Size& rSize, const SwFontScript nWhich ); + inline void SetWeight( const FontWeight eWeight, const SwFontScript nWhich ); + inline void SetItalic( const FontItalic eItalic, const SwFontScript nWhich ); + inline void SetLanguage( LanguageType eNewLang, const SwFontScript nWhich ); + inline void SetCharSet( const rtl_TextEncoding eCharSet, const SwFontScript nWhich ); + inline void SetPitch( const FontPitch ePitch, const SwFontScript nWhich ); + inline void SetRelief( const FontRelief eNew ); + + // Get/Set-methods for the current setting + sal_uInt8 &GetTox() { return m_nToxCount; } + bool IsTox() const { return ( 0 != m_nToxCount ); } + sal_uInt8 &GetRef() { return m_nRefCount; } + bool IsRef() const { return ( 0 != m_nRefCount ); } + sal_uInt8 &GetMeta() { return m_nMetaCount; } + bool IsMeta() const { return (0 != m_nMetaCount); } + sal_uInt8 &GetInputField() { return m_nInputFieldCount; } + bool IsInputField() const { return (0 != m_nInputFieldCount); } + inline void SetGreyWave( const bool bNew ); + bool IsGreyWave() const { return m_bGreyWave; } + bool IsPaintBlank() const { return m_bPaintBlank; } + + // setting of the base class font for SwTextCharFormat + void SetDiffFnt( const SfxItemSet* pSet, + const IDocumentSettingAccess* pIDocumentSettingAccess ); + + const SvxFont &GetFnt( const SwFontScript nWhich ) const + { return m_aSub[nWhich]; }; + + bool IsSymbol( SwViewShell const *pSh ) + { return m_aSub[m_nActual].IsSymbol( pSh ); } + FontLineStyle GetUnderline() const { return m_aSub[m_nActual].GetUnderline(); } + const Color& GetUnderColor() const { return m_aUnderColor; } + FontLineStyle GetOverline() const { return m_aSub[m_nActual].GetOverline(); } + const Color& GetOverColor() const { return m_aOverColor; } + FontStrikeout GetStrikeout() const { return m_aSub[m_nActual].GetStrikeout(); } + const Color& GetColor() const { return m_aSub[m_nActual].GetColor(); } + bool IsWordLineMode() const { return m_aSub[m_nActual].IsWordLineMode(); } + short GetEscapement() const { return m_aSub[m_nActual].GetEscapement(); } + SvxCaseMap GetCaseMap() const { return m_aSub[m_nActual].GetCaseMap(); } + sal_uInt8 GetPropr() const { return m_aSub[m_nActual].GetPropr(); } + FontItalic GetItalic() const { return m_aSub[m_nActual].GetItalic(); } + LanguageType GetLanguage() const { return m_aSub[m_nActual].GetLanguage(); } + long GetHeight() const { return m_aSub[m_nActual].GetFontSize().Height(); } + FontWeight GetWeight() const { return m_aSub[m_nActual].GetWeight(); } + FontEmphasisMark GetEmphasisMark() const + { return m_aSub[m_nActual].GetEmphasisMark(); } + sal_uInt16 GetOrientation(const bool bVertLayout = false, + const bool bVertFormatLRBT = false) const; + + const OUString& GetName( const SwFontScript nWhich ) const + { return m_aSub[nWhich].GetFamilyName(); } + LanguageType GetLanguage( const SwFontScript nWhich ) const + { return m_aSub[nWhich].GetLanguage(); } + rtl_TextEncoding GetCharSet( const SwFontScript nWhich ) const + { return m_aSub[nWhich].GetCharSet(); } + long GetHeight( const SwFontScript nWhich ) const + { return m_aSub[nWhich].GetFontSize().Height(); } + + // makes the logical font be effective in the OutputDevice + void ChgPhysFnt( SwViewShell const *pSh, OutputDevice& rOut ); + + TextFrameIndex GetCapitalBreak( SwViewShell const* pSh, const OutputDevice* pOut, + const SwScriptInfo* pScript, const OUString& rText, + long nTextWidth, TextFrameIndex nIdx, TextFrameIndex nLen); + + void DoOnCapitals( SwDoCapitals &rDo ) + { m_aSub[m_nActual].DoOnCapitals( rDo ); } + + Size GetTextSize_( SwDrawTextInfo& rInf ) + { rInf.SetFont( this ); return m_aSub[m_nActual].GetTextSize_( rInf ); } + + TextFrameIndex GetTextBreak( SwDrawTextInfo const & rInf, long nTextWidth ); + + TextFrameIndex GetModelPositionForViewPoint_( SwDrawTextInfo& rInf ) + { return m_aSub[m_nActual].GetModelPositionForViewPoint_( rInf ); } + + void DrawText_( SwDrawTextInfo &rInf ) + { m_aSub[m_nActual].DrawText_( rInf, IsGreyWave() ); } + + void DrawStretchText_( SwDrawTextInfo &rInf ) + { m_aSub[m_nActual].DrawStretchText_( rInf ); } + + short CheckKerning() + { return m_aSub[m_nActual].CheckKerning(); } + + sal_uInt16 GetAscent( SwViewShell const *pSh, const OutputDevice& rOut ) + { return m_aSub[m_nActual].GetAscent( pSh, rOut ); } + sal_uInt16 GetHeight( SwViewShell const *pSh, const OutputDevice& rOut ) + { return m_aSub[m_nActual].GetHeight( pSh, rOut ); } + + void Invalidate() + { m_bFontChg = m_bOrgChg = true; } + + void SetTopBorder( const editeng::SvxBorderLine* pTopBorder ); + void SetBottomBorder( const editeng::SvxBorderLine* pBottomBorder ); + void SetRightBorder( const editeng::SvxBorderLine* pRightBorder ); + void SetLeftBorder( const editeng::SvxBorderLine* pLeftBorder ); + + const std::optional<editeng::SvxBorderLine>& GetTopBorder() const { return m_aTopBorder; } + const std::optional<editeng::SvxBorderLine>& GetBottomBorder() const { return m_aBottomBorder; } + const std::optional<editeng::SvxBorderLine>& GetRightBorder() const { return m_aRightBorder; } + const std::optional<editeng::SvxBorderLine>& GetLeftBorder() const { return m_aLeftBorder; } + + // Get absolute border correspond to the layout verticality and orientation. + const std::optional<editeng::SvxBorderLine>& + GetAbsTopBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const; + const std::optional<editeng::SvxBorderLine>& + GetAbsBottomBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const; + const std::optional<editeng::SvxBorderLine>& + GetAbsRightBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const; + const std::optional<editeng::SvxBorderLine>& + GetAbsLeftBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const; + + void SetTopBorderDist( const sal_uInt16 nTopDist ); + void SetBottomBorderDist( const sal_uInt16 nBottomDist ); + void SetRightBorderDist( const sal_uInt16 nRightDist ); + void SetLeftBorderDist( const sal_uInt16 nLeftDist ); + + sal_uInt16 GetTopBorderDist() const { return m_nTopBorderDist; } + sal_uInt16 GetBottomBorderDist() const { return m_nBottomBorderDist; } + sal_uInt16 GetRightBorderDist() const { return m_nRightBorderDist; } + sal_uInt16 GetLeftBorderDist() const { return m_nLeftBorderDist; } + + // Return with the whole space which border holed (border width, spacing and shadow width) + sal_uInt16 GetTopBorderSpace() const; + sal_uInt16 GetBottomBorderSpace() const; + sal_uInt16 GetRightBorderSpace() const; + sal_uInt16 GetLeftBorderSpace() const; + + /// Check whether font has any border on any side + bool HasBorder() const; + + void SetShadowColor( const Color& rColor ); + void SetShadowWidth( const sal_uInt16 nWidth ); + void SetShadowLocation( const SvxShadowLocation aLocation ); + + const Color& GetShadowColor() const { return m_aShadowColor; } + sal_uInt16 GetShadowWidth() const { return m_nShadowWidth; } + SvxShadowLocation GetShadowLocation() const { return m_aShadowLocation; } + + /** + * Get the absolute shadow location dependent from orientation. + * + * @param[in] bVertLayout true, if the container layout is vertical + * false, otherwise + * @param[in] bVertLayoutLRBT true if the container layout is vertical + * (bottom to top, left to right), false otherwise + * @return absolute location + **/ + SvxShadowLocation GetAbsShadowLocation(const bool bVertLayout, + const bool bVertLayoutLRBT) const; + + /** + * Calculate the shadow space on the specified side dependent from + * the orientation and connection with neighbours. + * @see shaditem.hxx for integer constants of sides + * + * @param[in] nShadow specify the side + * @param[in] bVertLayout true, if the container layout is vertical + * false, otherwise + * @param[in] bVertLayoutLRBT true if the container layout is vertical + * (bottom to top, left to right), false otherwise + * @param[in] bSkipLeft relative left shadow space is skipped + * @param[in] bSkipRight relative right shadow space is skipped + * @return the shadow space + **/ + sal_uInt16 CalcShadowSpace( + const SvxShadowItemSide nShadow, const bool bVertLayout, const bool bVertLayoutLRBT, + const bool bSkipLeft, const bool bSkipRight ) const; + + void dumpAsXml( xmlTextWriterPtr writer ) const; +}; + +inline void SwFont::SetColor( const Color& rColor ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetColor( rColor ); + m_aSub[SwFontScript::CJK].SetColor( rColor ); + m_aSub[SwFontScript::CTL].SetColor( rColor ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetColor( const Color& rColor ) +{ + m_nFontCacheId = nullptr; + Font::SetColor( rColor ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetFillColor( const Color& rColor ) +{ + m_nFontCacheId = nullptr; + Font::SetFillColor( rColor ); +} + +inline void SwFont::SetFillColor( const Color& rColor ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetFillColor( rColor ); + m_aSub[SwFontScript::CJK].SetFillColor( rColor ); + m_aSub[SwFontScript::CTL].SetFillColor( rColor ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetFamily( const FontFamily eFamily ) +{ + m_nFontCacheId = nullptr; + Font::SetFamily( eFamily ); +} + +inline void SwFont::SetFamily( const FontFamily eFamily, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetFamily( eFamily ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetName( const OUString& rName ) +{ + m_nFontCacheId = nullptr; + Font::SetFamilyName( rName ); +} + +inline void SwFont::SetName( const OUString& rName, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetName( rName ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetStyleName( const OUString& rStyleName ) +{ + m_nFontCacheId = nullptr; + Font::SetStyleName( rStyleName ); +} + +inline void SwFont::SetStyleName( const OUString& rStyle, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetStyleName( rStyle ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetCharSet( const rtl_TextEncoding eCharSet ) +{ + m_nFontCacheId = nullptr; + Font::SetCharSet( eCharSet ); +} + +inline void SwFont::SetCharSet( const rtl_TextEncoding eCharSet, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetCharSet( eCharSet ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetPitch( const FontPitch ePitch ) +{ + m_nFontCacheId = nullptr; + Font::SetPitch( ePitch ); +} + +// encapsulated SV-Font-method +inline void SwFont::SetPitch( const FontPitch ePitch, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetPitch( ePitch ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetAlign( const FontAlign eAlign ) +{ + m_nFontCacheId = nullptr; + Font::SetAlignment( eAlign ); +} + +inline void SwFont::SetAlign( const FontAlign eAlign ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetAlign( eAlign ); + m_aSub[SwFontScript::CJK].SetAlign( eAlign ); + m_aSub[SwFontScript::CTL].SetAlign( eAlign ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetWeight( const FontWeight eWeight ) +{ + m_nFontCacheId = nullptr; + Font::SetWeight( eWeight ); +} + +inline void SwFont::SetWeight( const FontWeight eWeight, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetWeight( eWeight ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetUnderline( const FontLineStyle eUnderline ) +{ + m_nFontCacheId = nullptr; + Font::SetUnderline( eUnderline ); +} + +inline void SwFont::SetUnderline( const FontLineStyle eUnderline ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetUnderline( eUnderline ); + m_aSub[SwFontScript::CJK].SetUnderline( eUnderline ); + m_aSub[SwFontScript::CTL].SetUnderline( eUnderline ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetOverline( const FontLineStyle eOverline ) +{ + m_nFontCacheId = nullptr; + Font::SetOverline( eOverline ); +} + +inline void SwFont::SetOverline( const FontLineStyle eOverline ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetOverline( eOverline ); + m_aSub[SwFontScript::CJK].SetOverline( eOverline ); + m_aSub[SwFontScript::CTL].SetOverline( eOverline ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetStrikeout( const FontStrikeout eStrikeout ) +{ + m_nFontCacheId = nullptr; + Font::SetStrikeout( eStrikeout ); +} + +inline void SwFont::SetStrikeout( const FontStrikeout eStrikeout ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetStrikeout( eStrikeout ); + m_aSub[SwFontScript::CJK].SetStrikeout( eStrikeout ); + m_aSub[SwFontScript::CTL].SetStrikeout( eStrikeout ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetItalic( const FontItalic eItalic ) +{ + m_nFontCacheId = nullptr; + Font::SetItalic( eItalic ); +} + +inline void SwFont::SetItalic( const FontItalic eItalic, const SwFontScript nWhich ) +{ + m_bFontChg = true; + m_aSub[nWhich].SetItalic( eItalic ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetOutline( const bool bOutline ) +{ + m_nFontCacheId = nullptr; + Font::SetOutline( bOutline ); +} + +inline void SwFont::SetOutline( const bool bOutline ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetOutline( bOutline ); + m_aSub[SwFontScript::CJK].SetOutline( bOutline ); + m_aSub[SwFontScript::CTL].SetOutline( bOutline ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetShadow( const bool bShadow ) +{ + m_nFontCacheId = nullptr; + Font::SetShadow( bShadow ); +} + +inline void SwFont::SetShadow( const bool bShadow ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetShadow( bShadow ); + m_aSub[SwFontScript::CJK].SetShadow( bShadow ); + m_aSub[SwFontScript::CTL].SetShadow( bShadow ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetAutoKern( FontKerning nAutoKern ) +{ + m_nFontCacheId = nullptr; + Font::SetKerning( nAutoKern ); +} + +inline void SwFont::SetAutoKern( FontKerning nAutoKern ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::CJK].SetAutoKern( nAutoKern ); + if( nAutoKern != FontKerning::NONE ) + nAutoKern = FontKerning::FontSpecific; + m_aSub[SwFontScript::Latin].SetAutoKern( nAutoKern ); + m_aSub[SwFontScript::CTL].SetAutoKern( nAutoKern ); +} + +inline void SwFont::SetTransparent( const bool bTrans ) +{ + m_aSub[SwFontScript::Latin].SetTransparent( bTrans ); + m_aSub[SwFontScript::CJK].SetTransparent( bTrans ); + m_aSub[SwFontScript::CTL].SetTransparent( bTrans ); +} + +inline void SwFont::SetFixKerning( const short nNewKern ) +{ + m_aSub[SwFontScript::Latin].SetFixKerning( nNewKern ); + m_aSub[SwFontScript::CJK].SetFixKerning( nNewKern ); + m_aSub[SwFontScript::CTL].SetFixKerning( nNewKern ); +} + +inline void SwFont::SetCaseMap( const SvxCaseMap eNew ) +{ + m_aSub[SwFontScript::Latin].SetCaseMap( eNew ); + m_aSub[SwFontScript::CJK].SetCaseMap( eNew ); + m_aSub[SwFontScript::CTL].SetCaseMap( eNew ); +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetWordLineMode( const bool bWordLineMode ) +{ + m_nFontCacheId = nullptr; + Font::SetWordLineMode( bWordLineMode ); +} + +inline void SwFont::SetWordLineMode( const bool bWordLineMode ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetWordLineMode( bWordLineMode ); + m_aSub[SwFontScript::CJK].SetWordLineMode( bWordLineMode ); + m_aSub[SwFontScript::CTL].SetWordLineMode( bWordLineMode ); +} +// encapsulated SV-Font-method +inline void SwSubFont::SetEmphasisMark( const FontEmphasisMark eValue ) +{ + m_nFontCacheId = nullptr; + Font::SetEmphasisMark( eValue ); +} + +inline void SwFont::SetEmphasisMark( const FontEmphasisMark eValue ) +{ + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetEmphasisMark( eValue ); + m_aSub[SwFontScript::CJK].SetEmphasisMark( eValue ); + m_aSub[SwFontScript::CTL].SetEmphasisMark( eValue ); +} + +inline void SwFont::SetPropWidth( const sal_uInt16 nNew ) +{ + if( nNew != m_aSub[SwFontScript::Latin].GetPropWidth() ) + { + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetPropWidth( nNew ); + m_aSub[SwFontScript::CJK].SetPropWidth( nNew ); + m_aSub[SwFontScript::CTL].SetPropWidth( nNew ); + } +} + +// encapsulated SV-Font-method +inline void SwSubFont::SetRelief( const FontRelief eNew ) +{ + m_nFontCacheId = nullptr; + Font::SetRelief( eNew ); +} + +inline void SwFont::SetRelief( const FontRelief eNew ) +{ + if( eNew != m_aSub[SwFontScript::Latin].GetRelief() ) + { + m_bFontChg = true; + m_aSub[SwFontScript::Latin].SetRelief( eNew ); + m_aSub[SwFontScript::CJK].SetRelief( eNew ); + m_aSub[SwFontScript::CTL].SetRelief( eNew ); + } +} + +// overloaded font-method +inline void SwSubFont::SetSize( const Size& rSize ) +{ + m_aSize = rSize; + if ( GetPropr() == 100 ) + Font::SetFontSize( m_aSize ); + else + { + Font::SetFontSize( Size( + m_aSize.Width() * GetPropr() / 100, + m_aSize.Height() * GetPropr() / 100 ) ); + } + m_nFontCacheId = nullptr; +} + +inline void SwFont::SetSize( const Size& rSize, const SwFontScript nWhich ) +{ + if( m_aSub[nWhich].m_aSize != rSize ) + { + m_aSub[nWhich].SetSize( rSize ); + m_bFontChg = true; + m_bOrgChg = true; + } +} + +inline void SwFont::SetActual( SwFontScript nNew ) +{ + if ( m_nActual != nNew ) + { + m_bFontChg = true; + m_bOrgChg = true; + m_nActual = nNew; + } +} + +inline void SwSubFont::SetProportion( const sal_uInt8 nNewPropr ) +{ + m_nFontCacheId = nullptr; + Font::SetFontSize( Size( m_aSize.Width() * nNewPropr / 100, + m_aSize.Height() * nNewPropr / 100 ) ); + SvxFont::SetPropr( nNewPropr ); +} + +inline void SwFont::SetProportion( const sal_uInt8 nNewPropr ) +{ + if( nNewPropr != m_aSub[SwFontScript::Latin].GetPropr() ) + { + m_bFontChg = true; + m_bOrgChg = true; + + m_aSub[SwFontScript::Latin].SetProportion( nNewPropr ); + m_aSub[SwFontScript::CJK].SetProportion( nNewPropr ); + m_aSub[SwFontScript::CTL].SetProportion( nNewPropr ); + } +} + +inline void SwSubFont::SetEscapement( const short nNewEsc ) +{ + m_nFontCacheId = nullptr; + SvxFont::SetEscapement( nNewEsc ); +} + +inline void SwFont::SetEscapement( const short nNewEsc ) +{ + if( nNewEsc != m_aSub[SwFontScript::Latin].GetEscapement() ) + { + // these have to be set, otherwise nOrgHeight and nOrgAscent will not + // be calculated + m_bFontChg = true; + m_bOrgChg = true; + + m_aSub[SwFontScript::Latin].SetEscapement( nNewEsc ); + m_aSub[SwFontScript::CJK].SetEscapement( nNewEsc ); + m_aSub[SwFontScript::CTL].SetEscapement( nNewEsc ); + } +} + +inline void SwSubFont::SetLanguage( LanguageType eNewLang ) +{ + m_nFontCacheId = nullptr; + if( eNewLang == LANGUAGE_SYSTEM ) + eNewLang = GetAppLanguage(); + SvxFont::SetLanguage( eNewLang ); +} + +inline void SwFont::SetLanguage( const LanguageType eNewLang, const SwFontScript nWhich ) +{ + m_aSub[nWhich].SetLanguage( eNewLang ); + if( SwFontScript::CJK == nWhich ) + { + m_aSub[SwFontScript::Latin].SetCJKContextLanguage( eNewLang ); + m_aSub[SwFontScript::CJK].SetCJKContextLanguage( eNewLang ); + m_aSub[SwFontScript::CTL].SetCJKContextLanguage( eNewLang ); + } +} + +inline void SwFont::SetGreyWave( const bool bNew ) +{ + m_bGreyWave = bNew; +} + +inline void SwSubFont::SetVertical( const sal_uInt16 nDir, const bool bVertFormat ) +{ + m_nFontCacheId = nullptr; + Font::SetVertical( bVertFormat ); + Font::SetOrientation( nDir ); +} + +inline void SwFont::SetTopBorderDist( const sal_uInt16 nTopDist ) +{ + m_nTopBorderDist = nTopDist; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline void SwFont::SetBottomBorderDist( const sal_uInt16 nBottomDist ) +{ + m_nBottomBorderDist = nBottomDist; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline void SwFont::SetRightBorderDist( const sal_uInt16 nRightDist ) +{ + m_nRightBorderDist = nRightDist; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline void SwFont::SetLeftBorderDist( const sal_uInt16 nLeftDist ) +{ + m_nLeftBorderDist = nLeftDist; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline sal_uInt16 SwFont::GetTopBorderSpace() const +{ + sal_uInt16 nRet = 0; + if( m_aTopBorder ) + { + nRet += m_aTopBorder->GetScaledWidth() + m_nTopBorderDist; + } + if( m_aShadowLocation == SvxShadowLocation::TopLeft || + m_aShadowLocation == SvxShadowLocation::TopRight ) + { + nRet += m_nShadowWidth; + } + return nRet; +} + +inline sal_uInt16 SwFont::GetBottomBorderSpace() const +{ + sal_uInt16 nRet = 0; + if( m_aBottomBorder ) + { + nRet += m_aBottomBorder->GetScaledWidth() + m_nBottomBorderDist; + } + if( m_aShadowLocation == SvxShadowLocation::BottomLeft || + m_aShadowLocation == SvxShadowLocation::BottomRight ) + { + nRet += m_nShadowWidth; + } + return nRet; +} + +inline sal_uInt16 SwFont::GetRightBorderSpace() const +{ + sal_uInt16 nRet = 0; + if( m_aRightBorder ) + { + nRet += m_aRightBorder->GetScaledWidth() + m_nRightBorderDist; + } + if( m_aShadowLocation == SvxShadowLocation::TopRight || + m_aShadowLocation == SvxShadowLocation::BottomRight ) + { + nRet += m_nShadowWidth; + } + return nRet; +} + +inline sal_uInt16 SwFont::GetLeftBorderSpace() const +{ + sal_uInt16 nRet = 0; + if( m_aLeftBorder ) + { + nRet += m_aLeftBorder->GetScaledWidth() + m_nLeftBorderDist; + } + if( m_aShadowLocation == SvxShadowLocation::TopLeft || + m_aShadowLocation == SvxShadowLocation::BottomLeft ) + { + nRet += m_nShadowWidth; + } + return nRet; +} + +inline bool SwFont::HasBorder() const +{ + return m_aTopBorder || m_aBottomBorder || m_aLeftBorder || m_aRightBorder; +} + +inline void SwFont::SetShadowColor( const Color& rColor ) +{ + m_aShadowColor = rColor; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline void SwFont::SetShadowWidth( const sal_uInt16 nWidth ) +{ + m_nShadowWidth = nWidth; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline void SwFont::SetShadowLocation( const SvxShadowLocation aLocation ) +{ + m_aShadowLocation = aLocation; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +inline void SwFont::SetHighlightColor( const Color& aNewColor ) +{ + m_aHighlightColor = aNewColor; + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +// Used for the "continuous underline" feature. +class SwUnderlineFont +{ + Point m_aPos; + TextFrameIndex m_nEnd; + std::unique_ptr<SwFont> m_pFont; + +public: + // sets the font which should paint the common baseline, + // index where continuous underline ends, + // and the starting point of the common baseline + SwUnderlineFont(SwFont& rFnt, TextFrameIndex nEnd, const Point& rPoint); + ~SwUnderlineFont(); + + SwFont& GetFont() + { + OSL_ENSURE( m_pFont, "No underline font" ); + return *m_pFont; + } + const Point& GetPos() const { return m_aPos; } + TextFrameIndex GetEnd() const { return m_nEnd; } + // the x coordinate of the starting point has to be set for each portion + void SetPos( const Point& rPoint ) { m_aPos = rPoint; } +}; + +#ifdef DBG_UTIL + +class SvStatistics +{ +public: + sal_uInt16 nGetTextSize; + sal_uInt16 nDrawText; + sal_uInt16 nGetStretchTextSize; + sal_uInt16 nDrawStretchText; + sal_uInt16 nChangeFont; + + SvStatistics() + { nGetTextSize = nDrawText = nGetStretchTextSize = nDrawStretchText = nChangeFont = 0; } +}; + +// global variable, implemented in swfont.cxx +extern SvStatistics g_SvStat; + +#define SV_STAT(nWhich) ++(g_SvStat.nWhich); + + +#else +#define SV_STAT(nWhich) +#endif /* DBG_UTIL */ + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swselectionlist.hxx b/sw/source/core/inc/swselectionlist.hxx new file mode 100644 index 000000000..0fe63587b --- /dev/null +++ b/sw/source/core/inc/swselectionlist.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWSELECTIONLIST_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWSELECTIONLIST_HXX + +#include <list> + +class SwPaM; +class SwFrame; + +/** This class is used as parameter for creation of a block cursor selection + + This class will be created by a block cursor. Its responsibility is + to collect a group of selected text portions which are part of a common + context. + Definition of context: + A page header is a context. + A page footer is a context. + A footnote is a context. + Every fly frame builds a context together with its linked colleagues. + The content of the page bodies builds a context. +*/ + +class SwSelectionList +{ + std::list< SwPaM* > m_aList; // container for the selected text portions + const SwFrame* m_pContext; // the context of these text portions +public: + /** Ctor to create an empty list for a given context + + @param pInitCxt + The frame (normally a SwTextFrame) where the block cursor selection starts, + it will be used to get the allowed context for the text selections. + */ + explicit SwSelectionList( const SwFrame* pInitCxt ); + + /** Start of the container for the selected text portions + */ + std::list<SwPaM*>::iterator getStart() { return m_aList.begin(); } + + /** End of the container for the selected text portions + */ + std::list<SwPaM*>::iterator getEnd() { return m_aList.end(); } + + /** Adds a text portion to the selection list + + @param pPam + represents a text portion to select + */ + void insertPaM( SwPaM* pPam ) { m_aList.push_front( pPam ); } + + /** Reports if the list does not contain any text portion + + @return true, if list is empty + */ + bool isEmpty() const { return m_aList.empty(); } + + /** Checks if the context of the list is equal to the context of the frame + + If the list does not have already a context, the context of the frame + will define the list's context. + If the list has already a context, it will be compared to the context of + the given frame. + + @param pCheck + The frame to check + + @return true, if the context of the frame is equal to the one of the list + */ + bool checkContext( const SwFrame* pCheck ); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_SWSELECTIONLIST_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swthreadjoiner.hxx b/sw/source/core/inc/swthreadjoiner.hxx new file mode 100644 index 000000000..e22fb90ae --- /dev/null +++ b/sw/source/core/inc/swthreadjoiner.hxx @@ -0,0 +1,36 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWTHREADJOINER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWTHREADJOINER_HXX + +#include <sal/types.h> + +namespace com::sun::star::uno { template <class interface_type> class Reference; } +namespace com::sun::star::util { class XJobManager; } + +/** Testing */ +namespace SwThreadJoiner +{ + css::uno::Reference< css::util::XJobManager >& GetThreadJoiner(); + + void ReleaseThreadJoiner(); +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/swthreadmanager.hxx b/sw/source/core/inc/swthreadmanager.hxx new file mode 100644 index 000000000..28952683a --- /dev/null +++ b/sw/source/core/inc/swthreadmanager.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_SWTHREADMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_SWTHREADMANAGER_HXX + +#include <osl/interlck.h> + +#include <memory> + +namespace rtl { template <class reference_type> class Reference; } + +class ObservableThread; +class ThreadManager; + +/** class to manage threads in Writer using a <ThreadManager> instance + + #i73788# + Conforms the singleton pattern +*/ +class SwThreadManager +{ + public: + + static SwThreadManager& GetThreadManager(); + + static bool ExistsThreadManager(); + + // private: don't call! + SwThreadManager(); + // private: don't call! + ~SwThreadManager(); + + oslInterlockedCount AddThread( const rtl::Reference< ObservableThread >& rThread ); + + void RemoveThread( const oslInterlockedCount nThreadID ); + + /** suspend the starting of threads + + Suspending the starting of further threads is sensible during the + destruction of a Writer document. + */ + void SuspendStartingOfThreads(); + + /** continues the starting of threads after it has been suspended + */ + void ResumeStartingOfThreads(); + + bool StartingOfThreadsSuspended(); + + private: + + SwThreadManager(SwThreadManager const&) = delete; + SwThreadManager& operator=(SwThreadManager const&) = delete; + + static bool mbThreadManagerInstantiated; + + std::unique_ptr<ThreadManager> mpThreadManagerImpl; + +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/tabfrm.hxx b/sw/source/core/inc/tabfrm.hxx new file mode 100644 index 000000000..325515755 --- /dev/null +++ b/sw/source/core/inc/tabfrm.hxx @@ -0,0 +1,254 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX + +#include "layfrm.hxx" +#include "flowfrm.hxx" + +class SwTable; +class SwBorderAttrs; +class SwAttrSetChg; + +/// SwTabFrame is one table in the document layout, containing rows (which contain cells). +class SwTabFrame: public SwLayoutFrame, public SwFlowFrame +{ + friend void CalcContent( SwLayoutFrame *pLay, bool bNoColl ); + + // does the special treatment for Get_[Next|Prev]Leaf() + using SwFrame::GetLeaf; + + SwTable * m_pTable; + + bool m_bComplete :1; /// Set entries for Repaint without needing to + /// set the base class' CompletePaint + /// With that we would want to avoid unnecessary + /// table repaints + bool m_bCalcLowers :1; /// For stability of the content in MakeAll + bool m_bLowersFormatted :1; /// Communication between MakeAll and Layact + bool m_bLockBackMove :1; /// The Master took care of the BackMove test + bool m_bResizeHTMLTable :1; /// Call the Resize of the HTMLTableLayout in the MakeAll + /// This is an optimization, so that we don't have to call + /// it in ContentFrame::Grow; there it might be called for + /// _every_ Cell + + bool m_bONECalcLowers :1; /// Primarily for the StarONE SS + /// The Contents are formatted via Calc() on MakeAll in any + /// case. There are no further invalidations and that path can + /// hardly give any guarantees + + bool m_bHasFollowFlowLine :1; /// Means that the first line in the follow + /// is indented to contain content from a broken + /// cell + bool m_bIsRebuildLastLine :1; /// Means that currently the last line of the + /// TabFrame is rebuilt. In this case we do not + // want any notification to the master table + + bool m_bRestrictTableGrowth :1; // Usually, the table may grow infinitely, + // as the table can be split in SwTabFrame::MakeAll + // In MakeAll, this flag is set to indicate that + // the table may only grow inside its upper. This + // is necessary, in order to let the text flow into + // the FollowFlowLine + + bool m_bRemoveFollowFlowLinePending :1; + + // #i26945# + bool m_bConsiderObjsForMinCellHeight :1; // Usually, the floating screen objects + // are considered during the calculation + // for the minimal cell height. + // For the splitting table rows algorithm + // we need not to consider floating + // screen object for the preparation + // of the re-calculation of the + // last table row. + // #i26945# + bool m_bObjsDoesFit :1; // For splitting table rows algorithm, this boolean + // indicates, if the floating screen objects fits + + bool m_bInRecalcLowerRow : 1; + + bool m_bSplitRowDisabled : 1; // loop control + + /** + * Split() splits the Frame at the specified position: a Follow is + * created and constructed and inserted directly after this. + * Join() gets the Follow's content and destroys it. + */ + bool Split( const SwTwips nCutPos, bool bTryToSplit, bool bTableRowKeep ); + void Join(); + + void UpdateAttr_( + const SfxPoolItem*, + const SfxPoolItem*, sal_uInt8 &, + SwAttrSetChg *pa = nullptr, + SwAttrSetChg *pb = nullptr ); + + virtual bool ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool &rReformat ) override; + + virtual void DestroyImpl() override; + virtual ~SwTabFrame() override; + +protected: + virtual void MakeAll(vcl::RenderContext* pRenderContext) override; + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override; + // only changes the Framesize, not the PrtArea size + virtual SwTwips GrowFrame ( SwTwips, bool bTst = false, bool bInfo = false ) override; + +public: + SwTabFrame( SwTable &, SwFrame* ); // calling RegistFlys always after creation _and_pasting! + SwTabFrame( SwTabFrame & ); // _only_ for the creation of follows + + void JoinAndDelFollows(); // for DelFrames of the TableNodes! + + // calls thr RegistFlys of the rows + void RegistFlys(); + + inline const SwTabFrame *GetFollow() const; + inline SwTabFrame *GetFollow(); + SwTabFrame* FindMaster( bool bFirstMaster = false ) const; + + virtual bool GetInfo( SfxPoolItem &rHint ) const override; + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + virtual void CheckDirection( bool bVert ) override; + + virtual void Cut() override; + virtual void Paste( SwFrame* pParent, SwFrame* pSibling = nullptr ) override; + + virtual bool Prepare( const PrepareHint ePrep = PrepareHint::Clear, + const void *pVoid = nullptr, bool bNotify = true ) override; + + SwFrame *FindLastContentOrTable(); + inline const SwFrame *FindLastContentOrTable() const; + SwContentFrame *FindLastContent(); + inline const SwContentFrame *FindLastContent() const; + + const SwTable *GetTable() const { return m_pTable; } + SwTable *GetTable() { return m_pTable; } + + bool IsComplete() const { return m_bComplete; } + void SetComplete() { m_bComplete = true; } + void ResetComplete() { m_bComplete = false; } + + bool IsLowersFormatted() const { return m_bLowersFormatted; } + void SetLowersFormatted(bool b) { m_bLowersFormatted = b; } + + void SetCalcLowers() { m_bCalcLowers = true; } // use rarely + void SetResizeHTMLTable() { m_bResizeHTMLTable = true; } // same + void SetONECalcLowers() { m_bONECalcLowers = true; } + + // Start: New stuff for breaking table rows + + bool HasFollowFlowLine() const { return m_bHasFollowFlowLine; } + void SetFollowFlowLine(bool bNew) { m_bHasFollowFlowLine = bNew; } + + bool IsRebuildLastLine() const { return m_bIsRebuildLastLine; } + void SetRebuildLastLine(bool bNew) { m_bIsRebuildLastLine = bNew; } + + bool IsRestrictTableGrowth() const { return m_bRestrictTableGrowth; } + void SetRestrictTableGrowth( bool bNew ) { m_bRestrictTableGrowth = bNew; } + + bool IsRemoveFollowFlowLinePending() const { return m_bRemoveFollowFlowLinePending; } + void SetRemoveFollowFlowLinePending(bool bNew) { m_bRemoveFollowFlowLinePending = bNew; } + + bool IsInRecalcLowerRow() const + { + return m_bInRecalcLowerRow; + } + void SetInRecalcLowerRow( bool bNew ) + { + m_bInRecalcLowerRow = bNew; + } + bool IsSplitRowDisabled() const + { + return m_bSplitRowDisabled; + } + void SetSplitRowDisabled() + { + m_bSplitRowDisabled = true; + } + + // #i26945# + bool IsConsiderObjsForMinCellHeight() const + { + return m_bConsiderObjsForMinCellHeight; + } + void SetConsiderObjsForMinCellHeight(bool const bConsiderObjsForMinCellHeight) + { + m_bConsiderObjsForMinCellHeight = bConsiderObjsForMinCellHeight; + } + + // #i26945# + bool DoesObjsFit() const + { + return m_bObjsDoesFit; + } + void SetDoesObjsFit(bool const bObjsDoesFit) + { + m_bObjsDoesFit = bObjsDoesFit; + } + + bool RemoveFollowFlowLine(); + + // End: New stuff for breaking table rows + + bool CalcFlyOffsets( + SwTwips& rUpper, + long& rLeftOffset, + long& rRightOffset ) const; + + SwTwips CalcHeightOfFirstContentLine() const; + + bool IsInHeadline( const SwFrame& rFrame ) const; + SwRowFrame* GetFirstNonHeadlineRow() const; + + bool IsLayoutSplitAllowed() const; + + // #i29550# + bool IsCollapsingBorders() const; + + sal_uInt16 GetBottomLineSize() const; + + virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override; +}; + +inline const SwFrame *SwTabFrame::FindLastContentOrTable() const +{ + return const_cast<SwTabFrame*>(this)->FindLastContentOrTable(); +} + +inline const SwContentFrame *SwTabFrame::FindLastContent() const +{ + return const_cast<SwTabFrame*>(this)->FindLastContent(); +} + +inline const SwTabFrame *SwTabFrame::GetFollow() const +{ + return static_cast<const SwTabFrame*>(SwFlowFrame::GetFollow()); +} +inline SwTabFrame *SwTabFrame::GetFollow() +{ + return static_cast<SwTabFrame*>(SwFlowFrame::GetFollow()); +} + +#endif // INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/tblrwcl.hxx b/sw/source/core/inc/tblrwcl.hxx new file mode 100644 index 000000000..8d78bae00 --- /dev/null +++ b/sw/source/core/inc/tblrwcl.hxx @@ -0,0 +1,198 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TBLRWCL_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TBLRWCL_HXX + +#include <cstddef> +#include <memory> +#include <vector> + +#include <swtypes.hxx> +#include <swtable.hxx> + +namespace editeng { class SvxBorderLine; } + +class SwDoc; +class SwTableNode; +class SwTableBoxFormat; +class SwHistory; +class SwContentNode; +class SfxPoolItem; +class SwShareBoxFormats; +class SwFormatFrameSize; + +void sw_LineSetHeadCondColl( const SwTableLine* pLine ); + +#ifdef DBG_UTIL +void CheckBoxWidth( const SwTableLine& rLine, SwTwips nSize ); +#endif + +void InsTableBox( SwDoc* pDoc, SwTableNode* pTableNd, + SwTableLine* pLine, SwTableBoxFormat* pBoxFrameFormat, + SwTableBox* pBox, sal_uInt16 nInsPos, sal_uInt16 nCnt = 1 ); + +void DeleteBox_( SwTable& rTable, SwTableBox* pBox, SwUndo* pUndo, + bool bCalcNewSize, const bool bCorrBorder, + SwShareBoxFormats* pShareFormats = nullptr ); + +/** + * Class for SplitTable + * Collects the uppermost or lowermost Lines of a Box from a Line in an array. + * We also store their positions. + * + * @see implementation in ndtbl.cxx + */ +class SwCollectTableLineBoxes +{ + std::vector<sal_uInt16> aPosArr; + std::vector<SwTableBox*> m_Boxes; + SwHistory* pHst; + SplitTable_HeadlineOption nMode; + sal_uInt16 nWidth; + bool bGetFromTop : 1; + bool bGetValues : 1; + +public: + SwCollectTableLineBoxes( bool bTop, SplitTable_HeadlineOption nMd = SplitTable_HeadlineOption::NONE, SwHistory* pHist=nullptr ) + : + pHst( pHist ), nMode( nMd ), nWidth( 0 ), + bGetFromTop( bTop ), bGetValues( true ) + + {} + + void AddBox( const SwTableBox& rBox ); + const SwTableBox* GetBoxOfPos( const SwTableBox& rBox ); + void AddToUndoHistory( const SwContentNode& rNd ); + + size_t Count() const { return m_Boxes.size(); } + const SwTableBox& GetBox( std::size_t nPos, sal_uInt16* pWidth = nullptr ) const + { + // We need the EndPos of the column here! + if( pWidth ) + *pWidth = (nPos+1 == aPosArr.size()) ? nWidth + : aPosArr[ nPos+1 ]; + return *m_Boxes[ nPos ]; + } + + bool IsGetFromTop() const { return bGetFromTop; } + bool IsGetValues() const { return bGetValues; } + + SplitTable_HeadlineOption GetMode() const { return nMode; } + void SetValues( bool bFlag ) { bGetValues = false; nWidth = 0; + bGetFromTop = bFlag; } + bool Resize( sal_uInt16 nOffset, sal_uInt16 nWidth ); +}; + +void sw_Box_CollectBox( const SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ); +bool sw_Line_CollectBox( const SwTableLine*& rpLine, void* pPara ); + +void sw_BoxSetSplitBoxFormats( SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ); + +/** + * This structure is needed by Undo to restore row span attributes + * when a table has been split into two tables + */ +struct SwSaveRowSpan +{ + sal_uInt16 mnSplitLine; // the line number where the table has been split + std::vector< long > mnRowSpans; // the row span attributes in this line + SwSaveRowSpan( SwTableBoxes& rBoxes, sal_uInt16 nSplitLn ); +}; + +struct SwGCLineBorder +{ + const SwTableLines* pLines; + SwShareBoxFormats* pShareFormats; + sal_uInt16 nLinePos; + + SwGCLineBorder( const SwTable& rTable ) + : pLines( &rTable.GetTabLines() ), pShareFormats(nullptr), nLinePos( 0 ) {} + + SwGCLineBorder( const SwTableBox& rBox ) + : pLines( &rBox.GetTabLines() ), pShareFormats(nullptr), nLinePos( 0 ) {} + bool IsLastLine() const { return nLinePos + 1 >= static_cast<sal_uInt16>(pLines->size()); } +}; + +class SwGCBorder_BoxBrd +{ + const editeng::SvxBorderLine* pBrdLn; + bool bAnyBorderFnd; +public: + SwGCBorder_BoxBrd() : pBrdLn( nullptr ), bAnyBorderFnd( false ) {} + + void SetBorder( const editeng::SvxBorderLine& rBorderLine ) + { pBrdLn = &rBorderLine; bAnyBorderFnd = false; } + + /** + * Check whether the left Border is the same as the set one + * @returns false if no Border was set + */ + bool CheckLeftBorderOfFormat( const SwFrameFormat& rFormat ); + + bool IsAnyBorderFound() const { return bAnyBorderFnd; } +}; + +void sw_GC_Line_Border( const SwTableLine* pLine, SwGCLineBorder* pGCPara ); + +class SwShareBoxFormat +{ + const SwFrameFormat* pOldFormat; + std::vector<SwFrameFormat*> aNewFormats; + +public: + SwShareBoxFormat( const SwFrameFormat& rFormat ) + : pOldFormat( &rFormat ) + {} + + const SwFrameFormat& GetOldFormat() const { return *pOldFormat; } + + SwFrameFormat* GetFormat( long nWidth ) const; + SwFrameFormat* GetFormat( const SfxPoolItem& rItem ) const; + void AddFormat( SwFrameFormat& rFormat ); + /// @returns true, if we can delete + bool RemoveFormat( const SwFrameFormat& rFormat ); +}; + +class SwShareBoxFormats +{ + std::vector<std::unique_ptr<SwShareBoxFormat>> m_ShareArr; + + bool Seek_Entry( const SwFrameFormat& rFormat, sal_uInt16* pPos ) const; + + void ChangeFrameFormat( SwTableBox* pBox, SwTableLine* pLn, SwFrameFormat& rFormat ); + +public: + SwShareBoxFormats() {} + ~SwShareBoxFormats(); + + SwFrameFormat* GetFormat( const SwFrameFormat& rFormat, long nWidth ) const; + SwFrameFormat* GetFormat( const SwFrameFormat& rFormat, const SfxPoolItem& ) const; + + void AddFormat( const SwFrameFormat& rOld, SwFrameFormat& rNew ); + + void SetSize( SwTableBox& rBox, const SwFormatFrameSize& rSz ); + void SetAttr( SwTableBox& rBox, const SfxPoolItem& rItem ); + void SetAttr( SwTableLine& rLine, const SfxPoolItem& rItem ); + + void RemoveFormat( const SwFrameFormat& rFormat ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/textapi.hxx b/sw/source/core/inc/textapi.hxx new file mode 100644 index 000000000..3362621d7 --- /dev/null +++ b/sw/source/core/inc/textapi.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TEXTAPI_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TEXTAPI_HXX + +#include <editeng/unoedsrc.hxx> +#include <editeng/unotext.hxx> +#include <editeng/outlobj.hxx> + +class SwDoc; + +struct SwTextAPIEditSource_Impl; +class SwTextAPIEditSource : public SvxEditSource +{ + SwTextAPIEditSource_Impl* pImpl; + + virtual std::unique_ptr<SvxEditSource> Clone() const override; + virtual SvxTextForwarder* GetTextForwarder() override; + virtual void UpdateData() override; + explicit SwTextAPIEditSource( const SwTextAPIEditSource& rSource ); + +public: + SwTextAPIEditSource(SwDoc* pDoc); + virtual ~SwTextAPIEditSource() override; + + void Dispose(); + void SetText( OutlinerParaObject const & rText ); + void SetString( const OUString& rText ); + std::unique_ptr<OutlinerParaObject> CreateText(); + OUString GetText() const; +}; + +class SwTextAPIObject : public SvxUnoText +{ + std::unique_ptr<SwTextAPIEditSource> pSource; +public: + SwTextAPIObject( std::unique_ptr<SwTextAPIEditSource> p); + virtual ~SwTextAPIObject() throw() override; + void DisposeEditSource() { pSource->Dispose(); } + std::unique_ptr<OutlinerParaObject> CreateText() { return pSource->CreateText(); } + void SetString( const OUString& rText ) { pSource->SetString( rText ); } + void SetText( OutlinerParaObject const & rText ) { pSource->SetText( rText ); } + OUString GetText() const { return pSource->GetText(); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/threadlistener.hxx b/sw/source/core/inc/threadlistener.hxx new file mode 100644 index 000000000..4917296c6 --- /dev/null +++ b/sw/source/core/inc/threadlistener.hxx @@ -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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_THREADLISTENER_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_THREADLISTENER_HXX + +#include "ifinishedthreadlistener.hxx" + +class ThreadManager; +class ObservableThread; + +/** helper class to observe threads + + OD 2007-03-30 #i73788# + An instance of this class can be used to observe a thread in order to + be notified, if the thread has finished its work. The notification is + directly forward to its owner - an instance of ThreadManager + Note: + - A thread can only have one or none listener. + - The notification is performed via the ThreadID +*/ +class ThreadListener : public IFinishedThreadListener +{ + public: + + ThreadListener( ThreadManager& rThreadListenerOwner ); + virtual ~ThreadListener() override; + + void ListenToThread( const oslInterlockedCount nThreadID, + ObservableThread& rThread ); + + virtual void NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ) override; + + private: + + ThreadManager& mrThreadListenerOwner; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/tocntntanchoredobjectposition.hxx b/sw/source/core/inc/tocntntanchoredobjectposition.hxx new file mode 100644 index 000000000..07dd957cd --- /dev/null +++ b/sw/source/core/inc/tocntntanchoredobjectposition.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TOCNTNTANCHOREDOBJECTPOSITION_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TOCNTNTANCHOREDOBJECTPOSITION_HXX +#include "anchoredobjectposition.hxx" + +class SwFrame; +class SwTextFrame; +class SwLayoutFrame; +class SwRect; + +namespace objectpositioning +{ + class SwToContentAnchoredObjectPosition : public SwAnchoredObjectPosition + { + private: + // calculated data for object position + const SwLayoutFrame* mpVertPosOrientFrame; + // #i26791# + // determine offset to frame anchor position according to the + // positioning alignments + Point maOffsetToFrameAnchorPos; + + // data for calculation of position + bool mbAnchorToChar; + const SwFrame* mpToCharOrientFrame; + const SwRect* mpToCharRect; + SwTwips mnToCharTopOfLine; + + virtual bool IsAnchoredToChar() const override; + virtual const SwFrame* ToCharOrientFrame() const override; + virtual const SwRect* ToCharRect() const override; + // #i22341# + virtual SwTwips ToCharTopOfLine() const override; + + // method to cast <SwAnchoredObjectPosition::GetAnchorFrame()> to + // the needed type + SwTextFrame& GetAnchorTextFrame() const; + + /** determine frame for horizontal position + + if the given proposed frame is a content frame, the proposed + frame is returned. + otherwise (given proposed frame is a layout frame), + the lower content frames of the proposed frame are checked + for the first, that the anchor or a follow of the anchor. + If none is found, the proposed frame is returned. + + @param _pProposedFrame + input parameter - proposed frame for horizontal position + + @return constant reference to <SwFrame> object, at which the + horizontal position is determined. + */ + const SwFrame& GetHoriVirtualAnchor( const SwLayoutFrame& _pProposedFrame ) const; + + public: + SwToContentAnchoredObjectPosition( SdrObject& _rDrawObj ); + virtual ~SwToContentAnchoredObjectPosition() override; + + /** calculate position of object + */ + virtual void CalcPosition() override; + + /** frame, at which the vertical position is oriented at + */ + const SwLayoutFrame& GetVertPosOrientFrame() const { return *mpVertPosOrientFrame;} + + /// In case overlap is not allowed, re-position the current object. + void CalcOverlap(const SwTextFrame* pAnchorFrameForVertPos, Point& rRelPos, + const SwTwips nTopOfAnch); + }; +} // namespace objectpositioning + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/tolayoutanchoredobjectposition.hxx b/sw/source/core/inc/tolayoutanchoredobjectposition.hxx new file mode 100644 index 000000000..14bda973e --- /dev/null +++ b/sw/source/core/inc/tolayoutanchoredobjectposition.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TOLAYOUTANCHOREDOBJECTPOSITION_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TOLAYOUTANCHOREDOBJECTPOSITION_HXX +#include "anchoredobjectposition.hxx" + +namespace objectpositioning +{ + class SwToLayoutAnchoredObjectPosition : public SwAnchoredObjectPosition + { + private: + // calculated data for object position type TO_LAYOUT + Point maRelPos; + + // #i26791# + // determine offset to frame anchor position according to the + // positioning alignments + Point maOffsetToFrameAnchorPos; + + public: + SwToLayoutAnchoredObjectPosition( SdrObject& _rDrawObj ); + virtual ~SwToLayoutAnchoredObjectPosition() override; + + /** calculate position for object + */ + virtual void CalcPosition() override; + + /** calculated relative position for object + */ + const Point& GetRelPos() const { return maRelPos;} + }; +} // namespace objectpositioning + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/txmsrt.hxx b/sw/source/core/inc/txmsrt.hxx new file mode 100644 index 000000000..b7772a5b1 --- /dev/null +++ b/sw/source/core/inc/txmsrt.hxx @@ -0,0 +1,301 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TXMSRT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TXMSRT_HXX + +#include <i18nlangtag/lang.h> +#include <tox.hxx> + +#include <com/sun/star/lang/Locale.hpp> + +class CharClass; +class SwContentNode; +class SwTextNode; +class SwTextTOXMark; +class SwIndex; +class SwFormatField; +class SwRootFrame; +class IndexEntrySupplierWrapper; + +enum TOXSortType +{ + TOX_SORT_INDEX, + TOX_SORT_CUSTOM, + TOX_SORT_CONTENT, + TOX_SORT_PARA, + TOX_SORT_TABLE, + TOX_SORT_AUTHORITY +}; + +struct SwTOXSource +{ + const SwContentNode* pNd; + sal_Int32 nPos; + bool bMainEntry; + + SwTOXSource( const SwContentNode* pNode, sal_Int32 n, bool bMain ) + : pNd(pNode), nPos(n), bMainEntry(bMain) + { + } +}; + +struct TextAndReading +{ + OUString sText; + OUString sReading; + + TextAndReading() {} + + TextAndReading(const OUString& rText, const OUString& rReading) + : sText(rText) + , sReading(rReading) + {} +}; + +class SwTOXInternational +{ + std::unique_ptr<IndexEntrySupplierWrapper> m_pIndexWrapper; + std::unique_ptr<CharClass> m_pCharClass; + LanguageType m_eLang; + OUString m_sSortAlgorithm; + SwTOIOptions m_nOptions; + + void Init(); + +public: + SwTOXInternational( LanguageType nLang, SwTOIOptions nOptions, + const OUString& rSortAlgorithm ); + SwTOXInternational( const SwTOXInternational& ); + ~SwTOXInternational(); + + sal_Int32 Compare( const TextAndReading& rTaR1, + const css::lang::Locale& rLocale1, + const TextAndReading& rTaR2, + const css::lang::Locale& rLocale2 ) const; + + bool IsEqual( const TextAndReading& rTaR1, + const css::lang::Locale& rLocale1, + const TextAndReading& rTaR2, + const css::lang::Locale& rLocale2 ) const + { + return 0 == Compare( rTaR1, rLocale1, rTaR2, rLocale2 ); + } + + bool IsLess( const TextAndReading& rTaR1, + const css::lang::Locale& rLocale1, + const TextAndReading& rTaR2, + const css::lang::Locale& rLocale2 ) const + { + return -1 == Compare( rTaR1, rLocale1, rTaR2, rLocale2 ); + } + + OUString GetIndexKey( const TextAndReading& rTaR, + const css::lang::Locale& rLcl ) const; + + OUString GetFollowingText( bool bMorePages ) const; + + OUString ToUpper( const OUString& rStr, sal_Int32 nPos ) const; + inline bool IsNumeric( const OUString& rStr ) const; +}; + +/** + * Class for sorting directories + */ +struct SwTOXSortTabBase +{ + bool operator==(const SwTOXSortTabBase&) const = delete; + bool operator<(const SwTOXSortTabBase&) const = delete; + + std::vector<SwTOXSource> aTOXSources; + css::lang::Locale aLocale; + const SwTextNode* pTOXNd; + const SwTextTOXMark* pTextMark; + const SwTOXInternational* pTOXIntl; + sal_uLong nPos; + sal_Int32 nCntPos; + sal_uInt16 nType; + static SwTOIOptions nOpt; + + SwTOXSortTabBase( TOXSortType nType, + const SwContentNode* pTOXSrc, + const SwTextTOXMark* pTextMark, + const SwTOXInternational* pIntl, + const css::lang::Locale* pLocale = nullptr ); + virtual ~SwTOXSortTabBase() {} + + sal_uInt16 GetType() const { return nType; } + static SwTOIOptions GetOptions() { return nOpt; } + + virtual void FillText(SwTextNode& rNd, const SwIndex& rInsPos, + sal_uInt16 nAuthField, SwRootFrame const* pLayout) const; + virtual sal_uInt16 GetLevel() const = 0; + virtual bool equivalent( const SwTOXSortTabBase& ); + virtual bool sort_lt( const SwTOXSortTabBase& ); + + virtual OUString GetURL() const; + + virtual bool IsFullPara() const; + + // must be called + inline void InitText(SwRootFrame const*const pLayout); + inline TextAndReading const & GetText() const; + inline const css::lang::Locale& GetLocale() const; + +private: + bool m_bValidText; + TextAndReading m_aSort; + + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const = 0; +}; + +inline void SwTOXSortTabBase::InitText(SwRootFrame const*const pLayout) +{ + // 'this' is 'SwTOXSortTabBase const*', so the virtual + // mechanism will call the derived class' GetText_Impl + assert(!m_bValidText); + m_aSort = GetText_Impl(pLayout); + m_bValidText = true; +} + +inline TextAndReading const & SwTOXSortTabBase::GetText() const +{ + assert(m_bValidText); + return m_aSort; +} + +inline const css::lang::Locale& SwTOXSortTabBase::GetLocale() const +{ + return aLocale; +} + +/** + * For sorting by text + */ +struct SwTOXIndex : public SwTOXSortTabBase +{ + SwTOXIndex( const SwTextNode&, const SwTextTOXMark*, SwTOIOptions nOptions, sal_uInt8 nKeyLevel, + const SwTOXInternational& rIntl, + const css::lang::Locale& rLocale ); + + virtual void FillText(SwTextNode& rNd, const SwIndex& rInsPos, + sal_uInt16 nAuthField, SwRootFrame const* pLayout) const override; + virtual sal_uInt16 GetLevel() const override; + virtual bool equivalent( const SwTOXSortTabBase& ) override; + virtual bool sort_lt( const SwTOXSortTabBase& ) override; + +private: + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; + + sal_uInt8 nKeyLevel; +}; + +struct SwTOXCustom : public SwTOXSortTabBase +{ + SwTOXCustom( const TextAndReading& rKey, sal_uInt16 nLevel, + const SwTOXInternational& rIntl, + const css::lang::Locale& rLocale ); + + virtual sal_uInt16 GetLevel() const override; + virtual bool equivalent( const SwTOXSortTabBase& ) override; + virtual bool sort_lt( const SwTOXSortTabBase& ) override; + +private: + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; + + TextAndReading m_aKey; + sal_uInt16 nLev; +}; + +/** + * For sorting by position + */ +struct SwTOXContent : public SwTOXSortTabBase +{ + SwTOXContent( const SwTextNode&, const SwTextTOXMark*, + const SwTOXInternational& rIntl ); + + virtual void FillText(SwTextNode& rNd, const SwIndex& rInsPos, + sal_uInt16 nAuthField, SwRootFrame const* pLayout) const override; + virtual sal_uInt16 GetLevel() const override; +private: + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; + +}; + +struct SwTOXPara : public SwTOXSortTabBase +{ + SwTOXPara(SwContentNode&, SwTOXElement, + sal_uInt16 nLevel = FORM_ALPHA_DELIMITER, + const OUString& sSeqName = OUString()); + + void SetStartIndex(sal_Int32 nSet) { nStartIndex = nSet; } + void SetEndIndex(sal_Int32 nSet) { nEndIndex = nSet; } + + virtual void FillText(SwTextNode& rNd, const SwIndex& rInsPos, + sal_uInt16 nAuthField, SwRootFrame const* pLayout) const override; + virtual sal_uInt16 GetLevel() const override; + + virtual OUString GetURL() const override; + virtual bool IsFullPara() const override; +private: + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; + + SwTOXElement eType; + sal_uInt16 m_nLevel; + sal_Int32 nStartIndex; + sal_Int32 nEndIndex; + OUString m_sSequenceName; +}; + +struct SwTOXTable : public SwTOXSortTabBase +{ + SwTOXTable( const SwContentNode& rNd ); + + void SetLevel(sal_uInt16 nSet){nLevel = nSet;} + + virtual sal_uInt16 GetLevel() const override; + + virtual OUString GetURL() const override; +private: + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; + + sal_uInt16 nLevel; +}; + +struct SwTOXAuthority : public SwTOXSortTabBase +{ +private: + SwFormatField& m_rField; + virtual void FillText(SwTextNode& rNd, const SwIndex& rInsPos, + sal_uInt16 nAuthField, SwRootFrame const* pLayout) const override; + virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; + +public: + SwTOXAuthority( const SwContentNode& rNd, SwFormatField& rField, const SwTOXInternational& rIntl ); + + SwFormatField& GetFieldFormat() {return m_rField;} + + virtual bool equivalent( const SwTOXSortTabBase& ) override; + virtual bool sort_lt( const SwTOXSortTabBase& ) override; + virtual sal_uInt16 GetLevel() const override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_TXMSRT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/txtfly.hxx b/sw/source/core/inc/txtfly.hxx new file mode 100644 index 000000000..0f3c9e8c7 --- /dev/null +++ b/sw/source/core/inc/txtfly.hxx @@ -0,0 +1,378 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TXTFLY_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TXTFLY_HXX + +#include <editeng/txtrange.hxx> +#include <tools/solar.h> +#include <swtypes.hxx> +#include <swrect.hxx> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <memory> +#include <vector> + +class OutputDevice; +class SwPageFrame; +class SdrObject; +class SwFormat; +class SwAnchoredObject; +class SwTextFrame; +class SwDrawTextInfo; +class SwContourCache; + +typedef std::vector< SwAnchoredObject* > SwAnchoredObjList; + +/** Contour-cache global variable, initialized/destroyed in txtinit.cxx + and needed in txtfly.cxx by text wrapping. + */ +extern SwContourCache *pContourCache; + +#define POLY_CNT 20 +#define POLY_MIN 5 +#define POLY_MAX 4000 + +void ClrContourCache( const SdrObject *pObj ); + +class SwContourCache +{ + friend void ClrContourCache(); + struct CacheItem + { + const SdrObject *mpSdrObj; + std::unique_ptr<TextRanger> mxTextRanger; + }; + std::vector<CacheItem> mvItems; + long nPntCnt; + SwRect ContourRect( const SwFormat* pFormat, const SdrObject* pObj, + const SwTextFrame* pFrame, const SwRect &rLine, const long nXPos, + const bool bRight ); + +public: + SwContourCache(); + ~SwContourCache(); + const SdrObject* GetObject( sal_uInt16 nPos ) const{ return mvItems[ nPos ].mpSdrObj; } + sal_uInt16 GetCount() const { return mvItems.size(); } + void ClrObject( sal_uInt16 nPos ); + + /** + Computes the rectangle that will cover the object in the given line. + + For _non_ contour-flow objects, this is simply the overlap area of + BoundRect (including spacing), and the line, for contour-flow, + the tools::PolyPolygon of the object gets traversed + */ + static SwRect CalcBoundRect( const SwAnchoredObject* pAnchoredObj, + const SwRect &rLine, + const SwTextFrame* pFrame, + const long nXPos, + const bool bRight ); +}; + +/** + The purpose of this class is to be the universal interface between + formatting/text output and the possibly overlapping free-flying frames. + During formatting the formatter gets the information from SwTextFly, whether + a certain area is present by the attributes of an overlapping frame. + Such areas are represented by dummy portions. + + The whole text output and touch-up is, again, forwarded to a SwTextFly. + This one decides, whether parts of the text need to be clipped and splits + the areas for e.g. a DrawRect. + + Please note that all free-flying frames are located in a PtrArray, sorted + by TopLeft. + + Internally we always use document-global values. The IN and OUT parameters + are, however, adjusted to the needs of the LineIter most of the time. That + is: they are converted to frame- and window-local coordinates. + If multiple frames with wrap attributes are located on the same line, we get + the following settings for the text flow: + + L/R P L R N + P -P-P- -P-L -P R- -P N + L -L P- -L L -L R- -L N + R R-P- R-L R R- R N + N N P- N L N R- N N + + (P=parallel, L=left, R=right, N=no wrap) + + We can describe the behaviour as follows: + Every frame can push away text, with the restriction that it only has influence + until the next frame. + */ +class SwTextFly +{ + const SwPageFrame * pPage; + const SwAnchoredObject * mpCurrAnchoredObj; + const SwTextFrame * m_pCurrFrame; + const SwTextFrame * m_pMaster; + std::unique_ptr<SwAnchoredObjList> mpAnchoredObjList; + + long nMinBottom; + long nNextTop; /// Stores the upper edge of the "next" frame + sal_uLong m_nCurrFrameNodeIndex; + + bool bOn : 1; + bool bTopRule: 1; + bool mbIgnoreCurrentFrame: 1; + bool mbIgnoreContour: 1; + + /** boolean, indicating if objects in page header|footer are considered for + text frames not in page header|footer. + */ + bool mbIgnoreObjsInHeaderFooter: 1; + + /** + This method will be called during the LineIter formatting + \li to compute the position of the next \c FlyPortion + \li remember new overlappings after a change of the line height. + + \param[in] rPortion + Scope: document global. + */ + SwRect GetFrame_( const SwRect &rPortion ) const; + + SwAnchoredObjList* InitAnchoredObjList(); + + SwAnchoredObjList* GetAnchoredObjList() const; + + /** + Look for the first object which overlaps with the rectangle. + Iterates over the anchored object list mpAnchoredObjList. + */ + bool ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const; + + /** + \li There is less than 2cm space on both sides for the text: + no surround (css::text::WrapTextMode_NONE) + + \li There is more than 2cm space on only one side: + surround on that side (css::text::WrapTextMode_LEFT or css::text::WrapTextMode_RIGHT) + + \li There is more than 2cm space on both sides, the object is + larger than 1.5cm: surround on the wider side + (css::text::WrapTextMode_LEFT or css::text::WrapTextMode_RIGHT) + + \li There is more than 2cm space on both sides and the object + width is less than 1.5cm: both sides surround (css::text::WrapTextMode_PARALLEL) + */ + css::text::WrapTextMode GetSurroundForTextWrap( const SwAnchoredObject* pAnchoredObj ) const; + + /** + The right margin is the right margin or it is determined by the + next object standing on the line. + */ + void CalcRightMargin( SwRect &rFly, + SwAnchoredObjList::size_type nPos, + const SwRect &rLine ) const; + + /** + The left margin is the left margin of the current PrintArea or + it is determined by the last FlyFrame, which stands on the line. + */ + void CalcLeftMargin( SwRect &rFly, + SwAnchoredObjList::size_type nPos, + const SwRect &rLine ) const; + + /** + \return the position in sorted array + */ + SwAnchoredObjList::size_type GetPos( const SwAnchoredObject* pAnchoredObj ) const; + + bool GetTop( const SwAnchoredObject* _pAnchoredObj, + const bool bInFootnote, + const bool bInFooterOrHeader ); + + SwTwips CalcMinBottom() const; + + const SwTextFrame* GetMaster_(); + +public: + + SwTextFly(); + SwTextFly( const SwTextFrame *pFrame ); + SwTextFly( const SwTextFly& rTextFly ); + ~SwTextFly(); + + void CtorInitTextFly( const SwTextFrame *pFrame ); + + void SetTopRule(); + + SwRect GetFrame( const SwRect &rPortion ) const; + bool IsOn() const; + + /** + If there is no flying object frame standing in rRect (usually the current row), + then we are turning ourself off. + + \param rRect is global to the document! + */ + bool Relax( const SwRect &rRect ); + bool Relax(); + + SwTwips GetMinBottom() const; + const SwTextFrame* GetMaster() const; + + // This temporary variable needs to be manipulated in const methods + long GetNextTop() const; + void SetNextTop( long nNew ) const; + + /** + Determines the demanded rectangle for an anchored object, + considering its surround for text wrapping. + + \param pAnchoredObj the object for which to get the bounds + \param rLine the bounds of the line to format + + \return the flying object bounds + */ + SwRect AnchoredObjToRect( const SwAnchoredObject* pAnchoredObj, + const SwRect& rRect ) const; + + /** + This method is called by DrawText(). + + Ensures that the overlapping frames (except the transparent frames) won't + be scribbled by setting clip regions so that only the portions that are not + in the area of FlyFrames that are opaque and above the current frame will + be output. + + DrawText() takes over the on optimization! + */ + void DrawTextOpaque( SwDrawTextInfo &rInf ); + + /** + Two subtleties needs to be mentioned: + \li DrawRect() is allowed over the ClipRects + \li FlyToRect() returns bigger values than the frame data + + Ensure that the overlapping frames (except the transparent frames) + won't be scribbled + */ + void DrawFlyRect( OutputDevice* pOut, const SwRect &rRect ); + + /** + Used to switch off the SwTextFly when there is no overlapping object (Relax). + + \param[in] the line area + \return whether the line will be overlapped by a frame + */ + bool IsAnyFrame( const SwRect &rLine ) const; + + /** + Same as IsAnyFrame(const SwRect&), but uses the current frame print + area + */ + bool IsAnyFrame() const; + + /** + true when a frame or DrawObj must be taken in account. The optimizations + like Paint/FormatEmpty for empty sentences or the virtual OutputDevice can + be used only when false is returned. + + \param rRect + The rectangle can be empty, the current frame is then used. The value is + global to the document. + */ + bool IsAnyObj( const SwRect& rRect ) const; + + void SetIgnoreCurrentFrame( bool bNew ); + void SetIgnoreContour( bool bNew ); + + void SetIgnoreObjsInHeaderFooter( const bool bNew ); +}; + +inline SwAnchoredObjList* SwTextFly::GetAnchoredObjList() const +{ + return mpAnchoredObjList + ? mpAnchoredObjList.get() + : const_cast<SwTextFly*>(this)->InitAnchoredObjList(); +} + +inline void SwTextFly::SetTopRule() +{ + bTopRule = false; +} + +inline bool SwTextFly::IsOn() const +{ + return bOn; +} + +inline bool SwTextFly::Relax( const SwRect &rRect ) +{ + if (bOn) + { + bOn = IsAnyFrame( rRect ); + } + return bOn; +} + +inline bool SwTextFly::Relax() +{ + if (bOn) + { + bOn = IsAnyFrame(); + } + return bOn; +} + +inline SwTwips SwTextFly::GetMinBottom() const +{ + return mpAnchoredObjList ? nMinBottom : CalcMinBottom(); +} + +inline const SwTextFrame* SwTextFly::GetMaster() const +{ + return m_pMaster ? m_pMaster : const_cast<SwTextFly*>(this)->GetMaster_(); +} + +inline long SwTextFly::GetNextTop() const +{ + return nNextTop; +} + +inline void SwTextFly::SetNextTop( long nNew ) const +{ + const_cast<SwTextFly*>(this)->nNextTop = nNew; +} + +inline SwRect SwTextFly::GetFrame( const SwRect &rRect ) const +{ + return bOn ? GetFrame_( rRect ) : SwRect(); +} + +inline void SwTextFly::SetIgnoreCurrentFrame( bool bNew ) +{ + mbIgnoreCurrentFrame = bNew; +} + +inline void SwTextFly::SetIgnoreContour( bool bNew ) +{ + mbIgnoreContour = bNew; +} + +inline void SwTextFly::SetIgnoreObjsInHeaderFooter( const bool bNew ) +{ + mbIgnoreObjsInHeaderFooter = bNew; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx new file mode 100644 index 000000000..0f96e6676 --- /dev/null +++ b/sw/source/core/inc/txtfrm.hxx @@ -0,0 +1,1024 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TXTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TXTFRM_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include "cntfrm.hxx" +#include "TextFrameIndex.hxx" + +#include <set> + +namespace com::sun::star::linguistic2 { class XHyphenatedWord; } + +namespace sw::mark { class IMark; } +class SwCharRange; +class SwTextNode; +class SwTextAttrEnd; +class SwTextFormatter; +class SwTextFormatInfo; +class SwParaPortion; +class WidowsAndOrphans; +class SwTextFootnote; +class SwInterHyphInfo; // Hyphenate() +class SwCache; +class SwBorderAttrs; +class SwFrameFormat; +struct SwCursorMoveState; +struct SwFillData; +class SwPortionHandler; +class SwScriptInfo; +enum class ExpandMode; +class SwTextAttr; + +#define NON_PRINTING_CHARACTER_COLOR Color(0x26, 0x8b, 0xd2) + +/// a clone of SwInterHyphInfo, but with TextFrameIndex instead of node index +class SwInterHyphInfoTextFrame +{ +private: + /// output: hyphenated word + css::uno::Reference<css::linguistic2::XHyphenatedWord> m_xHyphWord; +public: + /// input: requested range to hyphenate + TextFrameIndex m_nStart; + TextFrameIndex m_nEnd; + /// output: found word + TextFrameIndex m_nWordStart; + TextFrameIndex m_nWordLen; + + SwInterHyphInfoTextFrame(SwTextFrame const& rFrame, + SwTextNode const& rNode, SwInterHyphInfo const& rHyphInfo); + void UpdateTextNodeHyphInfo(SwTextFrame const& rFrame, + SwTextNode const& rNode, SwInterHyphInfo & o_rHyphInfo); + + void SetHyphWord(const css::uno::Reference<css::linguistic2::XHyphenatedWord> &xHW) + { + m_xHyphWord = xHW; + } +}; + +namespace sw { + +/** + * Describes a part of a single text node, which will be part of a text frame, + * even when redlines are hidden at a layout level. + */ +struct Extent +{ + SwTextNode * /*const logically, but need assignment for std::vector*/ pNode; + sal_Int32 nStart; + sal_Int32 nEnd; + Extent(SwTextNode *const p, sal_Int32 const s, sal_Int32 const e) + : pNode(p), nStart(s), nEnd(e) + { + assert(pNode); + assert(nStart != nEnd); + } +}; + +struct MergedPara; + +std::pair<SwTextNode*, sal_Int32> MapViewToModel(MergedPara const&, TextFrameIndex nIndex); +TextFrameIndex MapModelToView(MergedPara const&, SwTextNode const* pNode, sal_Int32 nIndex); + +// warning: Existing must be used only once; a second use would delete the frame created by the first one... +enum class FrameMode { New, Existing }; +std::unique_ptr<sw::MergedPara> CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, FrameMode eMode); +SwTextFrame * MakeTextFrame(SwTextNode & rNode, SwFrame *, sw::FrameMode eMode); + +bool FrameContainsNode(SwContentFrame const& rFrame, sal_uLong nNodeIndex); +bool IsParaPropsNode(SwRootFrame const& rLayout, SwTextNode const& rNode); +SwTextNode * GetParaPropsNode(SwRootFrame const& rLayout, SwNodeIndex const& rNode); +SwPosition GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos); +std::pair<SwTextNode *, SwTextNode *> +GetFirstAndLastNode(SwRootFrame const& rLayout, SwNodeIndex const& rPos); + +SwTextNode const& GetAttrMerged(SfxItemSet & rFormatSet, + SwTextNode const& rNode, SwRootFrame const* pLayout); + +void GotoPrevLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const* pLayout); +void GotoNextLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const* pLayout); + +TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, + bool isRealDelete, + SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 nLen); + +void MoveMergedFlysAndFootnotes(std::vector<SwTextFrame*> const& rFrames, + SwTextNode const& rFirstNode, SwTextNode & rSecondNode, bool); + +void MoveDeletedPrevFrames(const SwTextNode & rDeletedPrev, SwTextNode & rNode); +enum class Recreate { No, ThisNode, Predecessor }; +void CheckResetRedlineMergeFlag(SwTextNode & rNode, Recreate eRecreateMerged); + +void UpdateFramesForAddDeleteRedline(SwDoc & rDoc, SwPaM const& rPam); +void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam); + +void AddRemoveFlysAnchoredToFrameStartingAtNode( + SwTextFrame & rFrame, SwTextNode & rTextNode, + std::set<sal_uLong> *pSkipped); + +OUString GetExpandTextMerged(SwRootFrame const* pLayout, + SwTextNode const& rNode, bool bWithNumber, + bool bWithSpacesForLevel, ExpandMode i_mode); + +bool IsMarkHidden(SwRootFrame const& rLayout, ::sw::mark::IMark const& rMark); +bool IsMarkHintHidden(SwRootFrame const& rLayout, + SwTextNode const& rNode, SwTextAttrEnd const& rHint); + +void RecreateStartTextFrames(SwTextNode & rNode); + +} // namespace sw + +/// Represents the visualization of a paragraph. Typical upper is an +/// SwBodyFrame. The first text portion of the first line is az SwParaPortion. +class SW_DLLPUBLIC SwTextFrame: public SwContentFrame +{ + friend class SwTextIter; + friend class SwTestFormat; + friend class WidowsAndOrphans; + friend class TextFrameLockGuard; // May Lock()/Unlock() + friend bool sw_ChangeOffset(SwTextFrame* pFrame, TextFrameIndex nNew); + + /// SwLineLayout cache: the lines are not actually owned by the SwTextFrame + /// but by this SwCache, so they will be deleted in large documents + /// if there are too many of them, but the "valid" flags of the frame + /// will still be set; GetFormatted() is the function that forces + /// recreation of the SwLineLayout by Format() if necessary. + static SwCache *s_pTextCache; + static constexpr long nMinPrtLine = 0; // This Line must not be underrun when printing + // Hack for table cells stretching multiple pages + + sal_uLong mnAllLines :24; // Line count for the Paint (including nThisLines) + sal_uLong mnThisLines :8; // Count of Lines of the Frame + + // The x position for flys anchored at this paragraph. + // These values are calculated in SwTextFrame::CalcBaseOfstForFly() + SwTwips mnFlyAnchorOfst; + // The x position for wrap-through flys anchored at this paragraph. + SwTwips mnFlyAnchorOfstNoWrap; + /// The y position for wrap-through flys anchored at this paragraph. + SwTwips mnFlyAnchorVertOfstNoWrap; + SwTwips mnFootnoteLine; + // OD 2004-03-17 #i11860# - re-factoring of #i11859# + // member for height of last line (value needed for proportional line spacing) + SwTwips mnHeightOfLastLine; + // member for the additional first line offset, which is caused by the list + // label alignment for list level position and space mode LABEL_ALIGNMENT. + // This additional first line offset is used for the text formatting. + // It is NOT used for the determination of printing area. + SwTwips mnAdditionalFirstLineOffset; + + /// redline merge data + std::unique_ptr<sw::MergedPara> m_pMergedPara; + + TextFrameIndex mnOffset; // Is the offset in the Content (character count) + + sal_uInt16 mnCacheIndex; // Index into the cache, USHRT_MAX if there's definitely no fitting object in the cache + + // Separates the Master and creates a Follow or adjusts the data in the Follow + void AdjustFollow_( SwTextFormatter &rLine, TextFrameIndex nOffset, + TextFrameIndex nStrEnd, const sal_uInt8 nMode ); + + // Iterates all Lines and sets the line spacing using the attribute + void CalcLineSpace(); + + // Only called in Format + void AdjustFrame( const SwTwips nChgHeight, bool bHasToFit = false ); + + // Evaluates the Preps in Format() + bool CalcPreps(); + void PrepWidows( const sal_uInt16 nNeed, bool bNotify ); + void InvalidateRange_( const SwCharRange &, const long = 0); + inline void InvalidateRange( const SwCharRange &, const long = 0); + + // WidowsAndOrphans, AdjustFrame, AdjustFollow + void FormatAdjust( SwTextFormatter &rLine, WidowsAndOrphans &rFrameBreak, + TextFrameIndex nStrLen, const bool bDummy ); + void ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew ); + + bool mbLocked : 1; // In the Format? + bool mbWidow : 1; // Is our follow a Widow? + bool mbJustWidow : 1; // Did we just request Widow flag on master? + bool mbEmpty : 1; // Are we an empty paragraph? + bool mbInFootnoteConnect : 1; // Is in Connect at the moment + bool mbFootnote : 1; // Has at least one footnote + bool mbRepaint : 1; // TextFrame: Repaint is ready to be fetched + /// Contains rotated portions. + bool mbHasRotatedPortions : 1; + bool mbFieldFollow : 1; // Start with Field rest of the Master + bool mbHasAnimation : 1; // Contains animated SwGrfNumPortion + bool mbIsSwapped : 1; // during text formatting we swap the + // width and height for vertical formatting + // OD 14.03.2003 #i11760# - flag to control, if follow is formatted in + // method <CalcFollow(..)>. + // E.g., avoid formatting of follow, if method <SwLayoutFrame::FormatWidthCols(..)> + // is running. + bool mbFollowFormatAllowed : 1; + + void ResetPreps(); + void Lock() { mbLocked = true; } + void Unlock() { mbLocked = false; } + void SetWidow( const bool bNew ) { mbWidow = bNew; } + void SetJustWidow( const bool bNew ) { mbJustWidow = bNew; } + void SetEmpty( const bool bNew ) { mbEmpty = bNew; } + void SetFieldFollow( const bool bNew ) { mbFieldFollow = bNew; } + + bool IsIdxInside(TextFrameIndex nPos, TextFrameIndex nLen) const; + + // Changes the Frame or not (cf. FlyCnt) + bool GetModelPositionForViewPoint_(SwPosition *pPos, const Point &rPoint, + const bool bChgFrame, SwCursorMoveState* = nullptr ) const; + void FillCursorPos( SwFillData &rFill ) const; + + // Format exactly one Line + bool FormatLine( SwTextFormatter &rLine, const bool bPrev ); + + // In order to safe stack space, we split this method: + // Format_ calls Format_ with parameters + void Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pPara ); + void Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf, + const bool bAdjust = false ); + void FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf ); + + // Formats the Follow and ensures disposing on orphans + bool CalcFollow(TextFrameIndex nTextOfst); + + virtual void MakePos() override; + + // Corrects the position from which we need to format + static TextFrameIndex FindBrk(const OUString &rText, TextFrameIndex nStart, + TextFrameIndex nEnd); + + // inline branch + SwTwips GetFootnoteFrameHeight_() const; + + // Outsourced to CalcPreps + bool CalcPrepFootnoteAdjust(); + + // For Footnote and WidOrp: Forced validation + void ValidateFrame(); + void ValidateBodyFrame(); + + bool GetDropRect_( SwRect &rRect ) const; + + void SetPara( SwParaPortion *pNew, bool bDelete = true ); + + bool IsFootnoteNumFrame_() const; + + // Refresh formatting information + bool FormatQuick( bool bForceQuickFormat ); + + // Opt: Format empty paragraphs + bool FormatEmpty(); + SwTwips EmptyHeight() const; + + // Opt: Paint empty paragraphs + bool PaintEmpty( const SwRect &, bool bCheck ) const; + + void ChgThisLines(); // Must always be called if the Line count could have changed + + // required for 'new' relative anchor position + void CalcBaseOfstForFly(); + + /** method to determine height of last line, needed for proportional line spacing + + OD 2004-03-17 #i11860# + OD 2005-05-20 #i47162# - introduce new optional parameter <_bUseFont> + in order to force the usage of the former algorithm to determine the + height of the last line, which uses the font. + + @param _bUseFont + optional input parameter - boolean indicating, if the font has to be + used to determine the height of the last line. default value: false + */ + void CalcHeightOfLastLine( const bool _bUseFont = false ); + + virtual void DestroyImpl() override; + virtual ~SwTextFrame() override; + +protected: + virtual void SwClientNotify(SwModify const& rModify, SfxHint const& rHint) override; + +public: + + virtual const SvxFormatBreakItem& GetBreakItem() const override; + virtual const SwFormatPageDesc& GetPageDescItem() const override; + + css::uno::Sequence< css::style::TabStop > GetTabStopInfo( SwTwips CurrentPos ) override; + + /** + * This is public, as it needs to be called by some methods in order to save the Prepare + * USE WITH CAUTION! + */ + void Init(); + + /// Is called by DoIdleJob_() and ExecSpellPopup() + SwRect AutoSpell_(SwTextNode &, sal_Int32); + + /// Is called by DoIdleJob_() + SwRect SmartTagScan(SwTextNode &); + + /// Is called by DoIdleJob_() + void CollectAutoCmplWrds(SwTextNode &, sal_Int32); + + /** + * Returns the screen position of rPos. The values are relative to the upper + * left position of the page frame. + * Additional information can be obtained by passing an SwCursorMoveState object. + * Returns false if rPos > number of character is string + */ + virtual bool GetCharRect( SwRect& rRect, const SwPosition& rPos, + SwCursorMoveState* pCMS = nullptr, bool bAllowFarAway = true ) const override; + + /// A slimmer version of GetCharRect for autopositioning Frames + bool GetAutoPos( SwRect &, const SwPosition& ) const; + + /** + * Determine top of line for given position in the text frame + * + * OD 11.11.2003 #i22341# + * Assumption: given position exists in the text frame or in a follow of it + * OD 2004-02-02 - adjustment + * Top of first paragraph line is the top of the paragraph. + * OD 2004-03-18 #i11860# - Consider upper space amount considered for + * previous frame and the page grid. + * + * @param _onTopOfLine + * output parameter - top of line, if the given position is found in the + * text frame. + * + * @param _rPos + * input parameter - reference to the position in the text frame + * + * @return boolean indicating, if the top of line for the given position + * has been determined or not. + */ + bool GetTopOfLine( SwTwips& _onTopOfLine, + const SwPosition& _rPos ) const; + + virtual bool FillSelection( SwSelectionList& rList, const SwRect& rRect ) const override; + + /** + * In nOffset returns the offset of the char within the set + * text buffer, which is closest to the position provided by + * aPoint within the layout's SSize. + * + * @returns false if the SPoint is outside of the SSize else + * returns true + */ + virtual bool GetModelPositionForViewPoint( SwPosition *, Point&, + SwCursorMoveState* = nullptr, bool bTestBackground = false ) const override; + + /** + * Makes sure that the Frame is not switched (e.g. switched for a + * character-bound Frame) + */ + bool GetKeyCursorOfst(SwPosition *pPos, const Point &rPoint ) const + { return GetModelPositionForViewPoint_( pPos, rPoint, false ); } + + void PaintExtraData( const SwRect & rRect ) const; /// Page number etc. + SwRect GetPaintSwRect(); + virtual void PaintSwFrame( vcl::RenderContext& rRenderContext, SwRect const&, + SwPrintData const*const pPrintData = nullptr ) const override; + virtual bool GetInfo( SfxPoolItem & ) const override; + + /** + * Layout oriented cursor travelling: + * Left border, right border, previous Line, following Line, + * same horizontal position + */ + virtual bool LeftMargin(SwPaM *) const override; + virtual bool RightMargin(SwPaM *, bool bAPI = false) const override; + + virtual bool UnitUp(SwPaM *, const SwTwips nOffset, + bool bSetInReadOnly ) const override; + virtual bool UnitDown(SwPaM *, const SwTwips nOffset, + bool bSetInReadOnly ) const override; + bool UnitUp_(SwPaM *, const SwTwips nOffset, + bool bSetInReadOnly ) const; + bool UnitDown_(SwPaM *, const SwTwips nOffset, + bool bSetInReadOnly ) const; + + /** + * Prepares the cursor position for a visual cursor move (BiDi). + * The behaviour is different for insert and overwrite cursors + */ + void PrepareVisualMove( TextFrameIndex& nPos, sal_uInt8& nCursorLevel, + bool& bRight, bool bInsertCursor ); + + /// Methods to manage the FollowFrame + void SplitFrame(TextFrameIndex nTextPos); + SwContentFrame *JoinFrame(); + TextFrameIndex GetOffset() const { return mnOffset; } + void SetOffset_(TextFrameIndex nNewOfst); + inline void SetOffset (TextFrameIndex nNewOfst); + void ManipOfst(TextFrameIndex const nNewOfst) { mnOffset = nNewOfst; } + SwTextFrame *GetFrameAtPos ( const SwPosition &rPos); + inline const SwTextFrame *GetFrameAtPos ( const SwPosition &rPos) const; + SwTextFrame& GetFrameAtOfst(TextFrameIndex nOfst); + /// If there's a Follow and we don't contain text ourselves + bool IsEmptyMaster() const + { return GetFollow() && !GetFollow()->GetOffset(); } + + void SetMergedPara(std::unique_ptr<sw::MergedPara> p); + sw::MergedPara * GetMergedPara() { return m_pMergedPara.get(); } + sw::MergedPara const* GetMergedPara() const { return m_pMergedPara.get(); } + + /// Returns the text portion we want to edit (for inline see underneath) + const OUString& GetText() const; + SwTextNode const* GetTextNodeForParaProps() const; + SwTextNode const* GetTextNodeForFirstText() const; + SwTextNode * GetTextNodeFirst() + { return const_cast<SwTextNode*>(const_cast<SwTextFrame const*>(this)->GetTextNodeFirst()); }; + SwTextNode const* GetTextNodeFirst() const; + SwDoc & GetDoc() + { return const_cast<SwDoc &>(const_cast<SwTextFrame const*>(this)->GetDoc()); } + SwDoc const& GetDoc() const; + + SwTextFrame(SwTextNode * const, SwFrame*, sw::FrameMode eMode); + + /** + * SwContentFrame: the shortcut for the Frames + * If the void* casts wrongly, it's its own fault! + * The void* must be checked for 0 in any case! + * + * return true if the Portion associated with this SwTextFrame was + * potentially destroyed and replaced by Prepare + */ + virtual bool Prepare( const PrepareHint ePrep = PrepareHint::Clear, + const void *pVoid = nullptr, bool bNotify = true ) override; + + /** + * nMaxHeight is the required height + * bSplit indicates, that the paragraph has to be split + * bTst indicates, that we are currently doing a test formatting + */ + virtual bool WouldFit( SwTwips &nMaxHeight, bool &bSplit, bool bTst ) override; + + /** + * The WouldFit equivalent for temporarily rewired TextFrames + * nMaxHeight returns the required size here too and bSplit + * determines whether the paragraph needs to be split. + * We pass the potential predecessor for the distance calculation + */ + bool TestFormat( const SwFrame* pPrv, SwTwips &nMaxHeight, bool &bSplit ); + + /** + * We format a Line for interactive hyphenation + * @return found + */ + bool Hyphenate(SwInterHyphInfoTextFrame & rInf); + + /// Test grow + inline SwTwips GrowTst( const SwTwips nGrow ); + + SwParaPortion *GetPara(); + inline const SwParaPortion *GetPara() const; + inline bool HasPara() const; + bool HasPara_() const; + + /// map position in potentially merged text frame to SwPosition + std::pair<SwTextNode*, sal_Int32> MapViewToModel(TextFrameIndex nIndex) const; + SwPosition MapViewToModelPos(TextFrameIndex nIndex) const; + TextFrameIndex MapModelToView(SwTextNode const* pNode, sal_Int32 nIndex) const; + TextFrameIndex MapModelToViewPos(SwPosition const& rPos) const; + + // If there are any hanging punctuation portions in the margin + // the offset will be returned. + SwTwips HangingMargin() const; + + // Locking + bool IsLocked() const { return mbLocked; } + + bool IsWidow() const { return mbWidow; } + bool IsJustWidow() const { return mbJustWidow; } + bool IsEmpty() const { return mbEmpty; } + bool HasFootnote() const { return mbFootnote; } + bool IsInFootnoteConnect()const { return mbInFootnoteConnect;} + bool IsFieldFollow() const { return mbFieldFollow;} + + inline void SetRepaint() const; + inline void ResetRepaint() const; + bool HasRepaint() const { return mbRepaint; } + void SetHasRotatedPortions(bool bHasRotatedPortions); + bool GetHasRotatedPortions() const { return mbHasRotatedPortions; } + void SetAnimation() const + { const_cast<SwTextFrame*>(this)->mbHasAnimation = true; } + bool HasAnimation() const { return mbHasAnimation; } + + bool IsSwapped() const { return mbIsSwapped; } + + /// Does the Frame have a local footnote (in this Frame or Follow)? +#ifdef DBG_UTIL + void CalcFootnoteFlag(TextFrameIndex nStop = TextFrameIndex(COMPLETE_STRING)); //For testing SplitFrame +#else + void CalcFootnoteFlag(); +#endif + + /// Hidden + bool IsHiddenNow() const; // bHidden && pOut == pPrt + void HideHidden(); // Remove appendage if Hidden + void HideFootnotes(TextFrameIndex nStart, TextFrameIndex nEnd); + + /** + * Hides respectively shows objects, which are anchored at paragraph, + * at/as a character of the paragraph, corresponding to the paragraph and + * paragraph portion visibility. + */ + void HideAndShowObjects(); + + /// Footnote + void RemoveFootnote(TextFrameIndex nStart, + TextFrameIndex nLen = TextFrameIndex(COMPLETE_STRING)); + inline SwTwips GetFootnoteFrameHeight() const; + SwTextFrame *FindFootnoteRef( const SwTextFootnote *pFootnote ); + const SwTextFrame *FindFootnoteRef( const SwTextFootnote *pFootnote ) const + { return const_cast<SwTextFrame *>(this)->FindFootnoteRef( pFootnote ); } + void ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDeadLine ); + + /** + * If we're a Footnote that grows towards its reference ... + * public, because it's needed by SwContentFrame::MakeAll + */ + SwTwips GetFootnoteLine( const SwTextFootnote *pFootnote ) const; + + TextFrameIndex GetDropLen(TextFrameIndex nWishLen) const; + + LanguageType GetLangOfChar(TextFrameIndex nIndex, sal_uInt16 nScript, + bool bNoChar = false) const; + + virtual void Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs = nullptr ) override; + virtual void CheckDirection( bool bVert ) override; + + /// Returns the sum of line height in pLine + sal_uInt16 GetParHeight() const; + + inline SwTextFrame *GetFollow(); + inline const SwTextFrame *GetFollow() const; + + /// Find the page number of ErgoSum and QuoVadis + SwTextFrame *FindQuoVadisFrame(); + + /** + * In case the SwLineLayout was cleared out of the s_pTextCache, recreate it + * + * #i29062# GetFormatted() can trigger a full formatting + * of the paragraph, causing other layout frames to become invalid. This + * has to be avoided during painting. Therefore we need to pass the + * information that we are currently in the paint process. + */ + SwTextFrame* GetFormatted( bool bForceQuickFormat = false ); + + /// Will be moved soon + void SetFootnote( const bool bNew ) { mbFootnote = bNew; } + + /// Respect the Follows + inline bool IsInside(TextFrameIndex nPos) const; + + /// DropCaps and selections + bool GetDropRect( SwRect &rRect ) const + { return HasPara() && GetDropRect_( rRect ); } + + static SwCache *GetTextCache() { return s_pTextCache; } + static void SetTextCache( SwCache *pNew ) { s_pTextCache = pNew; } + + static long GetMinPrtLine() { return nMinPrtLine; } + + sal_uInt16 GetCacheIdx() const { return mnCacheIndex; } + void SetCacheIdx( const sal_uInt16 nNew ) { mnCacheIndex = nNew; } + + /// Removes the Line information from the Cache but retains the entry itself + void ClearPara(); + /// Removes this frame completely from the Cache + void RemoveFromCache(); + + /// Am I a FootnoteFrame, with a number at the start of the paragraph? + bool IsFootnoteNumFrame() const + { return IsInFootnote() && !GetIndPrev() && IsFootnoteNumFrame_(); } + + /** + * Simulates a formatting as if there were not right margin or Flys or other + * obstacles and returns the width + */ + SwTwips CalcFitToContent(); + + /** + * Simulate format for a list item paragraph, whose list level attributes + * are in LABEL_ALIGNMENT mode, in order to determine additional first + * line offset for the real text formatting due to the value of label + * adjustment attribute of the list level. + */ + void CalcAdditionalFirstLineOffset(); + + SwTwips GetAdditionalFirstLineOffset() const + { + return mnAdditionalFirstLineOffset; + } + + /** + * Returns the additional line spacing for the next paragraph + * @param _bNoPropLineSpacing: control, whether the value of a + * proportional line spacing is returned or not + */ + long GetLineSpace( const bool _bNoPropLineSpacing = false ) const; + + /// Returns the first line height + sal_uInt16 FirstLineHeight() const; + + /// Rewires FlyInContentFrame, if nEnd > Index >= nStart + void MoveFlyInCnt(SwTextFrame *pNew, TextFrameIndex nStart, TextFrameIndex nEnd); + + /// Calculates the position of FlyInContentFrames + TextFrameIndex CalcFlyPos( SwFrameFormat const * pSearch ); + + /// Determines the start position and step size of the register + bool FillRegister( SwTwips& rRegStart, sal_uInt16& rRegDiff ); + + /// Determines the line count + sal_uInt16 GetLineCount(TextFrameIndex nPos); + + /// For displaying the line numbers + sal_uLong GetAllLines() const { return mnAllLines; } + sal_uLong GetThisLines() const { return mnThisLines;} + void RecalcAllLines(); + + /// Stops the animations within numberings + void StopAnimation( OutputDevice *pOut ); + + /// Visit all portions for Accessibility + void VisitPortions( SwPortionHandler& rPH ) const; + + /// Returns the script info stored at the paraportion + const SwScriptInfo* GetScriptInfo() const; + + /// Swaps width and height of the text frame + void SwapWidthAndHeight(); + + /** + * Calculates the coordinates of a rectangle when switching from + * horizontal to vertical layout + */ + void SwitchHorizontalToVertical( SwRect& rRect ) const; + + /** + * Calculates the coordinates of a point when switching from + * horizontal to vertical layout + */ + void SwitchHorizontalToVertical( Point& rPoint ) const; + + /** + * Calculates the limit value when switching from + * horizontal to vertical layout + */ + long SwitchHorizontalToVertical( long nLimit ) const; + + /** + * Calculates the coordinates of a rectangle when switching from + * vertical to horizontal layout + */ + void SwitchVerticalToHorizontal( SwRect& rRect ) const; + + /** + * Calculates the coordinates of a point when switching from + * vertical to horizontal layout + */ + void SwitchVerticalToHorizontal( Point& rPoint ) const; + + /** + * Calculates the a limit value when switching from + * vertical to horizontal layout + */ + long SwitchVerticalToHorizontal( long nLimit ) const; + + /** + * Calculates the coordinates of a rectangle when switching from + * LTR to RTL layout + */ + void SwitchLTRtoRTL( SwRect& rRect ) const; + + /** + * Calculates the coordinates of a point when switching from + * LTR to RTL layout + */ + void SwitchLTRtoRTL( Point& rPoint ) const; + + /** + * Calculates the coordinates of a rectangle when switching from + * RTL to LTR layout + */ + void SwitchRTLtoLTR( SwRect& rRect ) const { SwitchLTRtoRTL( rRect ); } + + /** + * Calculates the coordinates of a point when switching from + * RTL to LTR layout + */ + void SwitchRTLtoLTR( Point& rPoint ) const { SwitchLTRtoRTL( rPoint ); }; + + bool FollowFormatAllowed() const + { + return mbFollowFormatAllowed; + } + + void AllowFollowFormat() + { + mbFollowFormatAllowed = true; + } + + void ForbidFollowFormat() + { + mbFollowFormatAllowed = false; + } + + SwTwips GetBaseOffsetForFly( bool bIgnoreFlysAnchoredAtThisFrame ) const + { + return ( bIgnoreFlysAnchoredAtThisFrame ? + mnFlyAnchorOfst : + mnFlyAnchorOfstNoWrap ); + } + + SwTwips GetBaseVertOffsetForFly(bool bIgnoreFlysAnchoredAtThisFrame) const; + + SwTwips GetHeightOfLastLine() const + { + return mnHeightOfLastLine; + } + + static void repaintTextFrames( const SwTextNode& rNode ); + + void RegisterToNode(SwTextNode &, bool isForceNodeAsFirst = false); + + bool IsSymbolAt(TextFrameIndex) const; + OUString GetCurWord(SwPosition const&) const; + sal_uInt16 GetScalingOfSelectedText(TextFrameIndex nStt, TextFrameIndex nEnd); + + virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override; +}; + +//use this to protect a SwTextFrame for a given scope from getting merged with +//its neighbour and thus deleted +class TextFrameLockGuard +{ +private: + SwTextFrame *m_pTextFrame; + bool m_bOldLocked; +public: + //Lock pFrame for the lifetime of the Cut/Paste call, etc. to avoid + //SwTextFrame::AdjustFollow_ removing the pFrame we're trying to Make + TextFrameLockGuard(SwFrame* pFrame) + { + m_pTextFrame = pFrame->IsTextFrame() ? static_cast<SwTextFrame*>(pFrame) : nullptr; + if (m_pTextFrame) + { + m_bOldLocked = m_pTextFrame->IsLocked(); + m_pTextFrame->Lock(); + } + else + { + m_bOldLocked = false; + } + } + + ~TextFrameLockGuard() + { + if (m_pTextFrame && !m_bOldLocked) + m_pTextFrame->Unlock(); + } +}; + +inline const SwParaPortion *SwTextFrame::GetPara() const +{ + return const_cast<SwTextFrame*>(this)->GetPara(); +} + +inline bool SwTextFrame::HasPara() const +{ + return mnCacheIndex!=USHRT_MAX && HasPara_(); +} + +inline SwTwips SwTextFrame::GrowTst( const SwTwips nGrow ) +{ + return Grow( nGrow, true ); +} + +inline bool SwTextFrame::IsInside(TextFrameIndex const nPos) const +{ + bool bRet = true; + if( nPos < GetOffset() ) + bRet = false; + else + { + const SwTextFrame *pFoll = GetFollow(); + if( pFoll && nPos >= pFoll->GetOffset() ) + bRet = false; + } + return bRet; +} + +inline SwTwips SwTextFrame::GetFootnoteFrameHeight() const +{ + if( !IsFollow() && IsInFootnote() && HasPara() ) + return GetFootnoteFrameHeight_(); + else + return 0; +} + +inline const SwTextFrame *SwTextFrame::GetFollow() const +{ + return static_cast<const SwTextFrame*>(SwContentFrame::GetFollow()); +} +inline SwTextFrame *SwTextFrame::GetFollow() +{ + return static_cast<SwTextFrame*>(SwContentFrame::GetFollow()); +} + +inline const SwTextFrame *SwTextFrame::GetFrameAtPos( const SwPosition &rPos) const +{ + return const_cast<SwTextFrame*>(this)->GetFrameAtPos( rPos ); +} + +inline void SwTextFrame::SetOffset(TextFrameIndex const nNewOfst) +{ + if ( mnOffset != nNewOfst ) + SetOffset_( nNewOfst ); +} + +inline void SwTextFrame::SetRepaint() const +{ + const_cast<SwTextFrame*>(this)->mbRepaint = true; +} +inline void SwTextFrame::ResetRepaint() const +{ + const_cast<SwTextFrame*>(this)->mbRepaint = false; +} + +class TemporarySwap { +protected: + explicit TemporarySwap(SwTextFrame * frame, bool swap): + m_frame(frame), m_undo(false) + { + if (m_frame->IsVertical() && swap) { + m_undo = true; + m_frame->SwapWidthAndHeight(); + } + } + + ~TemporarySwap() { + if (m_undo) { + m_frame->SwapWidthAndHeight(); + } + } + +private: + TemporarySwap(TemporarySwap const &) = delete; + void operator =(TemporarySwap const &) = delete; + + SwTextFrame * m_frame; + bool m_undo; +}; + +class SwSwapIfSwapped: private TemporarySwap { +public: + explicit SwSwapIfSwapped(SwTextFrame* frame): + TemporarySwap(frame, frame->IsSwapped()) {} +}; + +class SwSwapIfNotSwapped: private TemporarySwap { +public: + explicit SwSwapIfNotSwapped(SwTextFrame* frame): + TemporarySwap(frame, !frame->IsSwapped()) {} +}; + +/** + * Helper class which can be used instead of the macros if a function + * has too many returns + */ +class SwFrameSwapper +{ + const SwTextFrame* pFrame; + bool bUndo; +public: + SwFrameSwapper( const SwTextFrame* pFrame, bool bSwapIfNotSwapped ); + ~SwFrameSwapper(); +}; + +class SwLayoutModeModifier +{ + const OutputDevice& m_rOut; + ComplexTextLayoutFlags m_nOldLayoutMode; +public: + SwLayoutModeModifier( const OutputDevice& rOutp ); + ~SwLayoutModeModifier(); + void Modify( bool bChgToRTL ); + void SetAuto(); +}; + +class SwDigitModeModifier +{ + const OutputDevice& rOut; + LanguageType nOldLanguageType; +public: + SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang ); + ~SwDigitModeModifier(); +}; + +namespace sw { + +/** + * Describes parts of multiple text nodes, which will form a text frame, even + * when redlines are hidden at a layout level. + */ +struct MergedPara +{ + sw::WriterMultiListener listener; + std::vector<Extent> extents; + /// note: cannot be const currently to avoid UB because SwTextGuess::Guess + /// const_casts it and modifies it (also, Update will modify it) + OUString mergedText; + /// most paragraph properties are taken from the first non-empty node + SwTextNode * pParaPropsNode; + /// except break attributes, those are taken from the first node + SwTextNode *const pFirstNode; + /// mainly for sanity checks + SwTextNode const* pLastNode; + MergedPara(SwTextFrame & rFrame, std::vector<Extent>&& rExtents, + OUString const& rText, + SwTextNode *const pProps, SwTextNode *const pFirst, + SwTextNode const*const pLast) + : listener(rFrame), extents(std::move(rExtents)), mergedText(rText) + , pParaPropsNode(pProps), pFirstNode(pFirst), pLastNode(pLast) + { + assert(pParaPropsNode); + assert(pFirstNode); + assert(pLastNode); + } +}; + +/// iterate SwTextAttr in potentially merged text frame +class MergedAttrIterBase +{ +protected: +#if BOOST_VERSION < 105600 + sw::MergedPara const* m_pMerged; + SwTextNode const* m_pNode; +#else + sw::MergedPara const*const m_pMerged; + SwTextNode const*const m_pNode; +#endif + size_t m_CurrentExtent; + size_t m_CurrentHint; + MergedAttrIterBase(SwTextFrame const& rFrame); +}; + +class MergedAttrIter + : public MergedAttrIterBase +{ +public: + MergedAttrIter(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {} + SwTextAttr const* NextAttr(SwTextNode const** ppNode = nullptr); +}; + +class MergedAttrIterByEnd +{ +private: + std::vector<std::pair<SwTextNode const*, SwTextAttr const*>> m_Hints; + SwTextNode const*const m_pNode; + size_t m_CurrentHint; +public: + MergedAttrIterByEnd(SwTextFrame const& rFrame); + SwTextAttr const* NextAttr(SwTextNode const*& rpNode); + void PrevAttr(); +}; + +class MergedAttrIterReverse + : public MergedAttrIterBase +{ +public: + MergedAttrIterReverse(SwTextFrame const& rFrame); + SwTextAttr const* PrevAttr(SwTextNode const** ppNode = nullptr); +}; + + +const SwTwips WIDOW_MAGIC = (SAL_MAX_INT32 - 1)/2; + +} // namespace sw + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/txttypes.hxx b/sw/source/core/inc/txttypes.hxx new file mode 100644 index 000000000..16f433e3b --- /dev/null +++ b/sw/source/core/inc/txttypes.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_TXTTYPES_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_TXTTYPES_HXX + +/// @see PORGRP_* masks in porlin.hxx for meaning of bits! +enum class PortionType +{ + NONE = 0x0000, + FlyCnt = 0x0001, + + Hole = 0x0080, + TempEnd = 0x0081, + Break = 0x0082, + Kern = 0x0083, + Arrow = 0x0084, + Multi = 0x0085, + HiddenText = 0x0086, + ControlChar = 0x0087, + Bookmark = 0x0088, + + Text = 0x8000, + Lay = 0x8001, + Para = 0x8002, + Hanging = 0x8004, + InputField = 0x8005, + FieldMark = 0x8006, + FieldFormCheckbox = 0x8007, + + Drop = 0x8080, + Tox = 0x8089, + IsoTox = 0x808a, + Ref = 0x808b, + IsoRef = 0x808c, + Meta = 0x808d, + + Expand = 0xc080, + Blank = 0xc081, + PostIts = 0xc082, + + Hyphen = 0xd080, + HyphenStr = 0xd081, + SoftHyphen = 0xd082, + SoftHyphenStr = 0xd083, + SoftHyphenComp = 0xd084, + + Field = 0xe080, + Hidden = 0xe081, + QuoVadis = 0xe082, + ErgoSum = 0xe083, + Combined = 0xe084, + Footnote = 0xe085, + + FootnoteNum = 0xe880, + Number = 0xe881, + Bullet = 0xe882, + GrfNum = 0xe883, + + Glue = 0x0480, + + Margin = 0x04c0, + + Fix = 0x06c0, + Fly = 0x06c1, + + Table = 0x0750, + + TabRight = 0x07d0, + TabCenter = 0x07d1, + TabDecimal = 0x07d2, + + TabLeft = 0x0740, +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_TXTTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/undoflystrattr.hxx b/sw/source/core/inc/undoflystrattr.hxx new file mode 100644 index 000000000..4110b91e6 --- /dev/null +++ b/sw/source/core/inc/undoflystrattr.hxx @@ -0,0 +1,49 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNDOFLYSTRATTR_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNDOFLYSTRATTR_HXX + +#include <undobj.hxx> +#include <swundo.hxx> + +class SwFlyFrameFormat; + +class SwUndoFlyStrAttr : public SwUndo +{ + public: + SwUndoFlyStrAttr( SwFlyFrameFormat& rFlyFrameFormat, + const SwUndoId eUndoId, + const OUString& sOldStr, + const OUString& sNewStr ); + virtual ~SwUndoFlyStrAttr() override; + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; + + virtual SwRewriter GetRewriter() const override; + + private: + SwFlyFrameFormat& mrFlyFrameFormat; + const OUString msOldStr; + const OUString msNewStr; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNDOFLYSTRATTR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unobookmark.hxx b/sw/source/core/inc/unobookmark.hxx new file mode 100644 index 000000000..f277748b5 --- /dev/null +++ b/sw/source/core/inc/unobookmark.hxx @@ -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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOBOOKMARK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOBOOKMARK_HXX + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XFormField.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <svl/listener.hxx> +#include <sfx2/Metadatable.hxx> + +#include <unobaseclass.hxx> +#include <IDocumentMarkAccess.hxx> + +class SwDoc; + +typedef ::cppu::ImplInheritanceHelper +< ::sfx2::MetadatableMixin +, css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::container::XNamed +, css::text::XTextContent +> SwXBookmark_Base; + +class SwXBookmark + : public SwXBookmark_Base +{ + +private: + + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + +protected: + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void attachToRangeEx( + const css::uno::Reference< css::text::XTextRange > & xTextRange, + IDocumentMarkAccess::MarkType eType); + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + virtual void attachToRange( + const css::uno::Reference< css::text::XTextRange > & xTextRange); + + ::sw::mark::IMark* GetBookmark() const; + + IDocumentMarkAccess* GetIDocumentMarkAccess(); + + void registerInMark( SwXBookmark& rXMark, ::sw::mark::IMark* const pMarkBase ); + + virtual ~SwXBookmark() override; + + SwXBookmark(SwDoc *const pDoc); + + /// descriptor + SwXBookmark(); + +public: + + static css::uno::Reference< css::text::XTextContent> + CreateXBookmark(SwDoc & rDoc, ::sw::mark::IMark * pBookmark); + + /// @return IMark for this, but only if it lives in pDoc + static ::sw::mark::IMark const* GetBookmarkInDoc(SwDoc const*const pDoc, + const css::uno::Reference<css::lang::XUnoTunnel> & xUT); + + // MetadatableMixin + virtual ::sfx2::Metadatable* GetCoreObject() override; + virtual css::uno::Reference< css::frame::XModel > GetModel() override; + + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< + css::beans::XVetoableChangeListener >& xListener) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName(const OUString& rName) override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + +}; + +class SwXFieldmarkParameters + : public ::cppu::WeakImplHelper< css::container::XNameContainer> + , public SvtListener +{ + private: + ::sw::mark::IFieldmark* m_pFieldmark; + /// @throws css::uno::RuntimeException + ::sw::mark::IFieldmark::parameter_map_t* getCoreParameters(); + public: + SwXFieldmarkParameters(::sw::mark::IFieldmark* const pFieldmark) + : m_pFieldmark(pFieldmark) + { + StartListening(pFieldmark->GetNotifier()); + } + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const css::uno::Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + virtual void Notify( const SfxHint& rHint ) override; +}; + +typedef cppu::ImplInheritanceHelper< SwXBookmark, + css::text::XFormField > SwXFieldmark_Base; + +class SwXFieldmark final + : public SwXFieldmark_Base +{ + ::sw::mark::ICheckboxFieldmark* getCheckboxFieldmark(); + bool m_bReplacementObject; + + SwXFieldmark(bool isReplacementObject, SwDoc* pDoc); + +public: + static css::uno::Reference<css::text::XTextContent> + CreateXFieldmark(SwDoc & rDoc, ::sw::mark::IMark * pMark, + bool isReplacementObject = false); + + virtual void attachToRange( + const css::uno::Reference<css::text::XTextRange > & xTextRange) override; + virtual OUString SAL_CALL getFieldType() override; + virtual void SAL_CALL setFieldType(const OUString& description ) override; + virtual css::uno::Reference< css::container::XNameContainer > SAL_CALL getParameters( ) override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOBOOKMARK_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unoevent.hxx b/sw/source/core/inc/unoevent.hxx new file mode 100644 index 000000000..6e533a079 --- /dev/null +++ b/sw/source/core/inc/unoevent.hxx @@ -0,0 +1,92 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOEVENT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOEVENT_HXX + +#include <svtools/unoevent.hxx> + +class SvxMacroItem; +class SwXFrame; +class SwXTextFrame; +class SwXTextGraphicObject; +class SwXTextEmbeddedObject; +class SwFormatINetFormat; +namespace sw { class ICoreFrameStyle; } + +class SwHyperlinkEventDescriptor : public SvDetachedEventDescriptor +{ + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; +protected: + virtual ~SwHyperlinkEventDescriptor() override; +public: + + SwHyperlinkEventDescriptor(); + + void copyMacrosFromINetFormat(const SwFormatINetFormat& aFormat); + void copyMacrosIntoINetFormat(SwFormatINetFormat& aFormat); + + void copyMacrosFromNameReplace( + css::uno::Reference<css::container::XNameReplace> const & xReplace); +}; + +// SwEventDescriptor for +// 1) SwXTextFrame +// 2) SwXGraphicObject +// 3) SwXEmbeddedObject +// All these objects are an SwXFrame, so they can use a common implementation +class SwFrameEventDescriptor : public SvEventDescriptor +{ + SwXFrame& rFrame; + +public: + SwFrameEventDescriptor( SwXTextFrame& rFrameRef ); + SwFrameEventDescriptor( SwXTextGraphicObject& rGraphicRef ); + SwFrameEventDescriptor( SwXTextEmbeddedObject& rObjectRef ); + + virtual ~SwFrameEventDescriptor() override; + + virtual OUString SAL_CALL getImplementationName() override; + +protected: + virtual void setMacroItem(const SvxMacroItem& rItem) override; + virtual const SvxMacroItem& getMacroItem() override; + virtual sal_uInt16 getMacroItemWhich() const override; +}; + +class SwFrameStyleEventDescriptor : public SvEventDescriptor +{ + sw::ICoreFrameStyle& m_rStyle; + +public: + SwFrameStyleEventDescriptor( sw::ICoreFrameStyle& rStyle ); + + virtual ~SwFrameStyleEventDescriptor() override; + + virtual OUString SAL_CALL getImplementationName() override; + +protected: + virtual void setMacroItem(const SvxMacroItem& rItem) override; + virtual const SvxMacroItem& getMacroItem() override; + virtual sal_uInt16 getMacroItemWhich() const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unofield.hxx b/sw/source/core/inc/unofield.hxx new file mode 100644 index 000000000..47f9f0c4d --- /dev/null +++ b/sw/source/core/inc/unofield.hxx @@ -0,0 +1,240 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOFIELD_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOFIELD_HXX + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/text/XDependentTextField.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <unobaseclass.hxx> +#include <unocoll.hxx> +#include <fldbas.hxx> + +class SwDoc; +class SwFormatField; +class SwSetExpField; + +typedef ::cppu::WeakImplHelper +< css::beans::XPropertySet +, css::lang::XServiceInfo +, css::lang::XUnoTunnel +, css::lang::XComponent +> SwXFieldMaster_Base; + +class SwXFieldMaster + : public SwXFieldMaster_Base +{ + +private: + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual ~SwXFieldMaster() override; + + SwXFieldMaster(SwFieldType& rType, SwDoc * pDoc); + + /// descriptor + SwXFieldMaster(SwDoc* pDoc, SwFieldIds nResId); + +public: + + static css::uno::Reference<css::beans::XPropertySet> + CreateXFieldMaster(SwDoc * pDoc, SwFieldType * pType, + SwFieldIds nResId = SwFieldIds::Unknown); + + static OUString GetProgrammaticName(const SwFieldType& rType, SwDoc& rDoc); + static OUString LocalizeFormula(const SwSetExpField& rField, const OUString& rFormula, bool bQuery); + + SwFieldType* GetFieldType(bool bDontCreate = false) const; + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + +}; + +typedef ::cppu::WeakImplHelper +< css::text::XDependentTextField +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::lang::XUnoTunnel +, css::util::XUpdatable +> SwXTextField_Base; + +class SwXTextField + : public SwXTextField_Base +{ + +private: + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual ~SwXTextField() override; + + SwXTextField(SwFormatField& rFormat, SwDoc & rDoc); + + /// descriptor + SwXTextField(SwServiceType nServiceId, SwDoc* pDoc); + +public: + SwServiceType GetServiceId() const; + + static void TransmuteLeadToInputField(SwSetExpField & rField); + + /// @return an SwXTextField, either an already existing one or a new one + static css::uno::Reference< css::text::XTextField> + CreateXTextField(SwDoc * pDoc, SwFormatField const* pFormat, + SwServiceType nServiceId = SwServiceType::Invalid); + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XUpdatable + virtual void SAL_CALL update() override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XTextField + virtual OUString SAL_CALL getPresentation(sal_Bool bShowCommand) override; + + // XDependentTextField + virtual void SAL_CALL attachTextFieldMaster( + const css::uno::Reference< css::beans::XPropertySet > & xFieldMaster) override; + virtual css::uno::Reference< css::beans::XPropertySet> SAL_CALL getTextFieldMaster() override; + +}; + +typedef ::cppu::WeakImplHelper +< css::container::XEnumeration +, css::lang::XServiceInfo +> SwXFieldEnumeration_Base; + +class SwXFieldEnumeration + : public SwXFieldEnumeration_Base +{ + +private: + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual ~SwXFieldEnumeration() override; + +public: + explicit SwXFieldEnumeration(SwDoc & rDoc); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual css::uno::Any SAL_CALL nextElement() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unoflatpara.hxx b/sw/source/core/inc/unoflatpara.hxx new file mode 100644 index 000000000..06f68aba4 --- /dev/null +++ b/sw/source/core/inc/unoflatpara.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOFLATPARA_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOFLATPARA_HXX + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/text/XFlatParagraph.hpp> +#include <com/sun/star/text/XFlatParagraphIterator.hpp> +#include <svl/listener.hxx> +#include "unotextmarkup.hxx" + +#include <set> + +namespace com::sun::star::container { class XStringKeyMap; } +namespace com::sun::star::text { class XTextRange; } +class SwTextNode; +class SwDoc; +class ModelToViewHelper; + +typedef ::cppu::ImplInheritanceHelper +< SwXTextMarkup +, css::beans::XPropertySet +, css::text::XFlatParagraph +, css::lang::XUnoTunnel +> SwXFlatParagraph_Base; + +class SwXFlatParagraph + : public SwXFlatParagraph_Base +{ +public: + SwXFlatParagraph( SwTextNode& rTextNode, const OUString& aExpandText, const ModelToViewHelper& rConversionMap ); + virtual ~SwXFlatParagraph() override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // text::XTextMarkup: + virtual css::uno::Reference< css::container::XStringKeyMap > SAL_CALL getMarkupInfoContainer() override; + + virtual void SAL_CALL commitStringMarkup(::sal_Int32 nType, const OUString & aIdentifier, ::sal_Int32 nStart, ::sal_Int32 nLength, + const css::uno::Reference< css::container::XStringKeyMap > & xMarkupInfoContainer) override; + + virtual void SAL_CALL commitTextRangeMarkup(::sal_Int32 nType, const OUString & aIdentifier, const css::uno::Reference< css::text::XTextRange> & xRange, + const css::uno::Reference< css::container::XStringKeyMap > & xMarkupInfoContainer) override; + + // text::XFlatParagraph: + virtual OUString SAL_CALL getText() override; + virtual sal_Bool SAL_CALL isModified() override; + virtual void SAL_CALL setChecked(::sal_Int32 nType, sal_Bool bVal) override; + virtual sal_Bool SAL_CALL isChecked(::sal_Int32 nType) override; + virtual css::lang::Locale SAL_CALL getLanguageOfText(::sal_Int32 nPos, ::sal_Int32 nLen) override; + virtual css::lang::Locale SAL_CALL getPrimaryLanguageOfText(::sal_Int32 nPos, ::sal_Int32 nLen) override; + virtual void SAL_CALL changeText(::sal_Int32 nPos, ::sal_Int32 nLen, const OUString & aNewText, const css::uno::Sequence< css::beans::PropertyValue > & aAttributes) override; + virtual void SAL_CALL changeAttributes(::sal_Int32 nPos, ::sal_Int32 nLen, const css::uno::Sequence< css::beans::PropertyValue > & aAttributes) override; + virtual css::uno::Sequence< ::sal_Int32 > SAL_CALL getLanguagePortions() override; + + using SwXTextMarkup::GetTextNode; + + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething(const css::uno::Sequence< sal_Int8 >& rId) override; + +private: + SwXFlatParagraph( const SwXFlatParagraph & ) = delete; + SwXFlatParagraph & operator = ( const SwXFlatParagraph & ) = delete; + + OUString maExpandText; +}; + +class SwXFlatParagraphIterator: + public ::cppu::WeakImplHelper + < + css::text::XFlatParagraphIterator + >, + public SvtListener +{ +public: + SwXFlatParagraphIterator( SwDoc& rDoc, sal_Int32 nType, bool bAutomatic ); + virtual ~SwXFlatParagraphIterator() override; + + // text::XFlatParagraphIterator: + virtual css::uno::Reference< css::text::XFlatParagraph > SAL_CALL getFirstPara() override; + virtual css::uno::Reference< css::text::XFlatParagraph > SAL_CALL getNextPara() override; + virtual css::uno::Reference< css::text::XFlatParagraph > SAL_CALL getLastPara() override; + virtual css::uno::Reference< css::text::XFlatParagraph > SAL_CALL getParaBefore(const css::uno::Reference< css::text::XFlatParagraph > & xPara) override; + virtual css::uno::Reference< css::text::XFlatParagraph > SAL_CALL getParaAfter(const css::uno::Reference< css::text::XFlatParagraph > & xPara) override; + + virtual void Notify( const SfxHint& ) override; + +private: + SwXFlatParagraphIterator( const SwXFlatParagraphIterator & ) = delete; + SwXFlatParagraphIterator & operator =(const SwXFlatParagraphIterator & ) = delete; + + // container to hold the 'hard' references as long as necessary and valid + std::set< css::uno::Reference< css::text::XFlatParagraph > > m_aFlatParaList; + + SwDoc* mpDoc; + const sal_Int32 mnType; + const bool mbAutomatic; + + sal_uLong mnCurrentNode; // used for non-automatic mode + sal_uLong mnEndNode; // used for non-automatic mode +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unofldmid.h b/sw/source/core/inc/unofldmid.h new file mode 100644 index 000000000..2bb13a66f --- /dev/null +++ b/sw/source/core/inc/unofldmid.h @@ -0,0 +1,52 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOFLDMID_H +#define INCLUDED_SW_SOURCE_CORE_INC_UNOFLDMID_H + +// mapping of the properties on the descriptor +#define FIELD_PROP_PAR1 10 +#define FIELD_PROP_PAR2 11 +#define FIELD_PROP_PAR3 12 +#define FIELD_PROP_FORMAT 13 +#define FIELD_PROP_SUBTYPE 14 +#define FIELD_PROP_BOOL1 15 +#define FIELD_PROP_BOOL2 16 +#define FIELD_PROP_DATE 17 +#define FIELD_PROP_USHORT1 18 +#define FIELD_PROP_USHORT2 19 +#define FIELD_PROP_BYTE1 20 +#define FIELD_PROP_DOUBLE 21 +#define FIELD_PROP_BOOL3 22 +#define FIELD_PROP_PAR4 23 +#define FIELD_PROP_SHORT1 24 +#define FIELD_PROP_DATE_TIME 25 +#define FIELD_PROP_PROP_SEQ 26 +#define FIELD_PROP_LOCALE 27 +#define FIELD_PROP_BOOL4 28 +#define FIELD_PROP_STRINGS 29 +#define FIELD_PROP_PAR5 30 + +#define FIELD_PROP_IS_FIELD_USED 32 +#define FIELD_PROP_IS_FIELD_DISPLAYED 33 + +#define FIELD_PROP_TEXT 34 + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unofootnote.hxx b/sw/source/core/inc/unofootnote.hxx new file mode 100644 index 000000000..79014ecba --- /dev/null +++ b/sw/source/core/inc/unofootnote.hxx @@ -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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOFOOTNOTE_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOFOOTNOTE_HXX + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/text/XFootnote.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <unotext.hxx> + +class SwDoc; +class SwFormatFootnote; + +typedef ::cppu::WeakImplHelper +< css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::container::XEnumerationAccess +, css::text::XFootnote +> SwXFootnote_Base; + +class SwXFootnote final + : public SwXFootnote_Base + , public SwXText +{ + friend class SwXFootnotes; + + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual const SwStartNode *GetStartNode() const override; + + virtual css::uno::Reference< css::text::XTextCursor > CreateCursor() override; + + virtual ~SwXFootnote() override; + + SwXFootnote(SwDoc & rDoc, SwFormatFootnote & rFormat); + SwXFootnote(const bool bEndnote); + +public: + + static css::uno::Reference<css::text::XFootnote> + CreateXFootnote(SwDoc & rDoc, SwFormatFootnote * pFootnoteFormat, + bool isEndnote = false); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& rType) override; + virtual void SAL_CALL acquire() throw() override { OWeakObject::acquire(); } + virtual void SAL_CALL release() throw() override { OWeakObject::release(); } + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > + SAL_CALL getTypes() override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XEnumerationAccess + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL + createEnumeration() override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XFootnote + virtual OUString SAL_CALL getLabel() override; + virtual void SAL_CALL setLabel(const OUString& rLabel) override; + + // XSimpleText + virtual css::uno::Reference< css::text::XTextCursor > SAL_CALL + createTextCursor() override; + virtual css::uno::Reference< css::text::XTextCursor > SAL_CALL + createTextCursorByRange( + const css::uno::Reference< css::text::XTextRange > & xTextPosition) override; + +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOFOOTNOTE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unofreg.hxx b/sw/source/core/inc/unofreg.hxx new file mode 100644 index 000000000..afb8e6dcd --- /dev/null +++ b/sw/source/core/inc/unofreg.hxx @@ -0,0 +1,38 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOFREG_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOFREG_HXX + +#include <sal/config.h> +#include <sfx2/sfxmodelfactory.hxx> + +#include <com/sun/star/uno/Sequence.hxx> + +namespace com::sun::star::lang { class XMultiServiceFactory; } + +// writer documents +css::uno::Sequence< OUString > SwTextDocument_getSupportedServiceNames() throw(); +OUString SwTextDocument_getImplementationName() throw(); +/// @throws css::uno::Exception +css::uno::Reference< css::uno::XInterface > SwTextDocument_createInstance( const css::uno::Reference< css::lang::XMultiServiceFactory > &rSMgr, SfxModelFlags _nCreationFlags ); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unoidx.hxx b/sw/source/core/inc/unoidx.hxx new file mode 100644 index 000000000..c87460a3d --- /dev/null +++ b/sw/source/core/inc/unoidx.hxx @@ -0,0 +1,229 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOIDX_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOIDX_HXX + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/util/XRefreshable.hpp> +#include <com/sun/star/text/XDocumentIndexMark.hpp> +#include <com/sun/star/text/XDocumentIndex.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <sfx2/Metadatable.hxx> + +#include <toxe.hxx> +#include <unobaseclass.hxx> + +class SwDoc; +class SwTOXBaseSection; +class SwTOXMark; +class SwTOXType; + +typedef ::cppu::ImplInheritanceHelper +< ::sfx2::MetadatableMixin +, css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::container::XNamed +, css::util::XRefreshable +, css::text::XDocumentIndex +> SwXDocumentIndex_Base; + +class SwXDocumentIndex + : public SwXDocumentIndex_Base +{ + +private: + + class StyleAccess_Impl; + class TokenAccess_Impl; + + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual ~SwXDocumentIndex() override; + + SwXDocumentIndex(SwTOXBaseSection &, SwDoc &); + + /// descriptor + SwXDocumentIndex(const TOXTypes eToxType, SwDoc& rDoc); + +public: + + static css::uno::Reference< css::text::XDocumentIndex> + CreateXDocumentIndex(SwDoc & rDoc, SwTOXBaseSection * pSection, + TOXTypes eTypes = TOX_INDEX); + + // MetadatableMixin + virtual ::sfx2::Metadatable* GetCoreObject() override; + virtual css::uno::Reference< css::frame::XModel > + GetModel() override; + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName(const OUString& rName) override; + + // XRefreshable + virtual void SAL_CALL refresh() override; + virtual void SAL_CALL addRefreshListener( + const css::uno::Reference< css::util::XRefreshListener>& xListener) override; + virtual void SAL_CALL removeRefreshListener( + const css::uno::Reference< css::util::XRefreshListener>& xListener) override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XDocumentIndex + virtual OUString SAL_CALL getServiceName() override; + virtual void SAL_CALL update() override; + +}; + +typedef ::cppu::WeakImplHelper +< css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::text::XDocumentIndexMark +> SwXDocumentIndexMark_Base; + +class SwXDocumentIndexMark + : public SwXDocumentIndexMark_Base +{ + +private: + + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual ~SwXDocumentIndexMark() override; + + SwXDocumentIndexMark(SwDoc & rDoc, + SwTOXType & rType, SwTOXMark & rMark); + + /// descriptor + SwXDocumentIndexMark(const TOXTypes eToxType); + +public: + + static css::uno::Reference< css::text::XDocumentIndexMark> + CreateXDocumentIndexMark(SwDoc & rDoc, + SwTOXMark * pMark, TOXTypes eType = TOX_INDEX); + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference<css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XDocumentIndexMark + virtual OUString SAL_CALL getMarkEntry() override; + virtual void SAL_CALL setMarkEntry(const OUString& rIndexEntry) override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unometa.hxx b/sw/source/core/inc/unometa.hxx new file mode 100644 index 000000000..87c351b5d --- /dev/null +++ b/sw/source/core/inc/unometa.hxx @@ -0,0 +1,270 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOMETA_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOMETA_HXX + +#include <memory> +#include <deque> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextField.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <sfx2/Metadatable.hxx> + +#include <unobaseclass.hxx> + +typedef std::deque< + css::uno::Reference< css::text::XTextRange > > + TextRangeList_t; + +class SwPaM; +class SwTextNode; + +namespace sw { + class Meta; +} + +typedef ::cppu::ImplInheritanceHelper +< ::sfx2::MetadatableMixin +, css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::container::XChild +, css::container::XEnumerationAccess +, css::text::XTextContent +, css::text::XText +> SwXMeta_Base; + +class SwXMeta + : public SwXMeta_Base +{ + +public: + + class Impl; + +protected: + + ::sw::UnoImplPtr<Impl> m_pImpl; + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void AttachImpl( + const css::uno::Reference< css::text::XTextRange > & xTextRange, + const sal_uInt16 nWhich); + + virtual ~SwXMeta() override; + + SwXMeta(SwXMeta const&) = delete; + SwXMeta& operator=(SwXMeta const&) = delete; + + /// @param pDoc and pMeta != 0, but not & because of ImplInheritanceHelper + SwXMeta(SwDoc *const pDoc, ::sw::Meta *const pMeta, + css::uno::Reference< css::text::XText> const& xParentText, + std::unique_ptr<TextRangeList_t const> pPortions); + + SwXMeta(SwDoc *const pDoc); + +public: + + static css::uno::Reference< css::rdf::XMetadatable > + CreateXMeta( + ::sw::Meta & rMeta, + css::uno::Reference< css::text::XText> const& xParentText = nullptr, + std::unique_ptr<TextRangeList_t const> && pPortions = std::unique_ptr<TextRangeList_t const>()); + + static css::uno::Reference<css::rdf::XMetadatable> + CreateXMeta(SwDoc & rDoc, bool isField); + + /// init params with position of the attribute content (w/out CH_TXTATR) + bool SetContentRange( SwTextNode *& rpNode, sal_Int32 & rStart, sal_Int32 & rEnd) const; + css::uno::Reference< css::text::XText > const & GetParentText() const; + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + bool CheckForOwnMemberMeta(const SwPaM & rPam, const bool bAbsorb); + + // MetadatableMixin + virtual ::sfx2::Metadatable * GetCoreObject() override; + virtual css::uno::Reference< css::frame::XModel > + GetModel() override; + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& Identifier ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XChild + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL + getParent() override; + virtual void SAL_CALL setParent( + css::uno::Reference< css::uno::XInterface> const& xParent) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XEnumerationAccess + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL + createEnumeration() override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XTextRange + virtual css::uno::Reference< css::text::XText > + SAL_CALL getText() override; + virtual css::uno::Reference< + css::text::XTextRange > SAL_CALL getStart() override; + virtual css::uno::Reference< + css::text::XTextRange > SAL_CALL getEnd() override; + virtual OUString SAL_CALL getString() override; + virtual void SAL_CALL setString(const OUString& rString) override; + + // XSimpleText + virtual css::uno::Reference< css::text::XTextCursor > SAL_CALL + createTextCursor() override; + virtual css::uno::Reference< css::text::XTextCursor > SAL_CALL + createTextCursorByRange( + const css::uno::Reference< css::text::XTextRange > & xTextPosition) override; + virtual void SAL_CALL insertString( + const css::uno::Reference< css::text::XTextRange > & xRange, + const OUString& aString, sal_Bool bAbsorb) override; + virtual void SAL_CALL insertControlCharacter( + const css::uno::Reference< css::text::XTextRange > & xRange, + sal_Int16 nControlCharacter, sal_Bool bAbsorb) override; + + // XText + virtual void SAL_CALL insertTextContent( + const css::uno::Reference< css::text::XTextRange > & xRange, + const css::uno::Reference< css::text::XTextContent > & xContent, + sal_Bool bAbsorb) override; + virtual void SAL_CALL removeTextContent( + const css::uno::Reference< css::text::XTextContent > & xContent) override; + +}; + +typedef ::cppu::ImplInheritanceHelper +< SwXMeta +, css::beans::XPropertySet +, css::text::XTextField +> SwXMetaField_Base; + +class SwXMetaField + : public SwXMetaField_Base +{ + +private: + + virtual ~SwXMetaField() override; + + friend css::uno::Reference< css::rdf::XMetadatable > + SwXMeta::CreateXMeta(::sw::Meta &, + css::uno::Reference< css::text::XText> const&, + std::unique_ptr<TextRangeList_t const> && pPortions); + + SwXMetaField(SwDoc *const pDoc, ::sw::Meta *const pMeta, + css::uno::Reference< css::text::XText> const& xParentText, + std::unique_ptr<TextRangeList_t const> pPortions); + + friend css::uno::Reference<css::rdf::XMetadatable> + SwXMeta::CreateXMeta(SwDoc &, bool); + + SwXMetaField(SwDoc *const pDoc); + +public: + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames( ) override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL + getPropertyValue(const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XTextField + virtual OUString SAL_CALL getPresentation(sal_Bool bShowCommand) override; + +}; + +/// get prefix/suffix from the RDF repository. @throws RuntimeException +void getPrefixAndSuffix( + const css::uno::Reference< css::frame::XModel>& xModel, + const css::uno::Reference< css::rdf::XMetadatable>& xMetaField, + OUString *const o_pPrefix, OUString *const o_pSuffix); + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOMETA_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unoparaframeenum.hxx b/sw/source/core/inc/unoparaframeenum.hxx new file mode 100644 index 000000000..e3712cf28 --- /dev/null +++ b/sw/source/core/inc/unoparaframeenum.hxx @@ -0,0 +1,77 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOPARAFRAMEENUM_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOPARAFRAMEENUM_HXX + +#include <deque> + +#include <calbck.hxx> +#include <unobaseclass.hxx> + + +class SwNodeIndex; +class SwPaM; +class SwFrameFormat; + +namespace sw +{ + struct FrameClient : public SwClient + { + FrameClient(SwModify* pModify) : SwClient(pModify) {}; + }; +} +struct FrameClientSortListEntry +{ + sal_Int32 nIndex; + sal_uInt32 nOrder; + std::shared_ptr<sw::FrameClient> pFrameClient; + + FrameClientSortListEntry (sal_Int32 const i_nIndex, + sal_uInt32 const i_nOrder, std::shared_ptr<sw::FrameClient> i_pClient) + : nIndex(i_nIndex), nOrder(i_nOrder), pFrameClient(std::move(i_pClient)) { } +}; + +typedef std::deque< FrameClientSortListEntry > + FrameClientSortList_t; + +typedef std::deque< std::shared_ptr<sw::FrameClient> > + FrameClientList_t; + +// #i28701# - adjust 4th parameter +void CollectFrameAtNode( const SwNodeIndex& rIdx, + FrameClientSortList_t& rFrames, + const bool bAtCharAnchoredObjs ); + +enum ParaFrameMode +{ + PARAFRAME_PORTION_PARAGRAPH, + PARAFRAME_PORTION_CHAR, + PARAFRAME_PORTION_TEXTRANGE, +}; + +struct SwXParaFrameEnumeration + : public SwSimpleEnumeration_Base +{ + static SwXParaFrameEnumeration* Create(const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, SwFrameFormat* const pFormat = nullptr); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOPARAFRAMEENUM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unoport.hxx b/sw/source/core/inc/unoport.hxx new file mode 100644 index 000000000..7817d7d16 --- /dev/null +++ b/sw/source/core/inc/unoport.hxx @@ -0,0 +1,307 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOPORT_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOPORT_HXX + +#include <memory> +#include <deque> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XTolerantMultiPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <svl/itemprop.hxx> +#include <svl/listener.hxx> + +#include <unocrsr.hxx> + +namespace com::sun::star::beans { struct PropertyValue; } +namespace com::sun::star::text { class XTextField; } +namespace com::sun::star::text { class XFootnote; } + +class SwFrameFormat; +class SwRangeRedline; +class SwTextRuby; + +typedef std::deque< + css::uno::Reference< css::text::XTextRange > > + TextRangeList_t; + +enum SwTextPortionType +{ + PORTION_TEXT, + PORTION_FIELD, + PORTION_FRAME, + PORTION_FOOTNOTE, + PORTION_REFMARK_START, + PORTION_REFMARK_END, + PORTION_TOXMARK_START, + PORTION_TOXMARK_END, + PORTION_BOOKMARK_START, + PORTION_BOOKMARK_END, + PORTION_REDLINE_START, + PORTION_REDLINE_END, + PORTION_RUBY_START, + PORTION_RUBY_END, + PORTION_SOFT_PAGEBREAK, + PORTION_META, + PORTION_FIELD_START, + PORTION_FIELD_SEP, + PORTION_FIELD_END, + PORTION_FIELD_START_END, + PORTION_ANNOTATION, + PORTION_ANNOTATION_END +}; + +class SwXTextPortion : public cppu::WeakImplHelper +< + css::beans::XTolerantMultiPropertySet, + css::beans::XMultiPropertySet, + css::beans::XPropertySet, + css::text::XTextRange, + css::beans::XPropertyState, + css::container::XContentEnumerationAccess, + css::lang::XUnoTunnel, + css::lang::XServiceInfo +>, + public SvtListener +{ +private: + + const SfxItemPropertySet * m_pPropSet; + const css::uno::Reference< css::text::XText > + m_xParentText; + css::uno::Reference< css::text::XTextContent > + m_xRefMark; + css::uno::Reference< css::text::XTextContent > + m_xTOXMark; + css::uno::Reference< css::text::XTextContent > + m_xBookmark; + css::uno::Reference< css::text::XFootnote > + m_xFootnote; + css::uno::Reference< css::text::XTextField > + m_xTextField; + css::uno::Reference< css::text::XTextContent > + m_xMeta; + std::unique_ptr< css::uno::Any > m_pRubyText; + std::unique_ptr< css::uno::Any > m_pRubyStyle; + std::unique_ptr< css::uno::Any > m_pRubyAdjust; + std::unique_ptr< css::uno::Any > m_pRubyIsAbove; + std::unique_ptr< css::uno::Any > m_pRubyPosition; + sw::UnoCursorPointer m_pUnoCursor; + + SwFrameFormat* m_pFrameFormat; + const SwTextPortionType m_ePortionType; + + bool m_bIsCollapsed; + + void init(const SwUnoCursor* pPortionCursor); + +protected: + /// @throws css::beans::UnknownPropertyException + /// @throws css::beans::PropertyVetoException + /// @throws css::lang::IllegalArgumentException + /// @throws css::lang::WrappedTargetException + /// @throws css::uno::RuntimeException + void SetPropertyValues_Impl( + const css::uno::Sequence< OUString >& aPropertyNames, + const css::uno::Sequence< css::uno::Any >& aValues ); + /// @throws css::beans::UnknownPropertyException + /// @throws css::lang::WrappedTargetException + /// @throws css::uno::RuntimeException + css::uno::Sequence< css::uno::Any > GetPropertyValues_Impl( + const css::uno::Sequence< OUString >& aPropertyNames ); + + void GetPropertyValue( css::uno::Any &rVal, + const SfxItemPropertySimpleEntry& rEntry, SwUnoCursor *pUnoCursor, std::unique_ptr<SfxItemSet> &pSet ); + + /// @throws css::uno::RuntimeException + css::uno::Sequence<css::beans::GetDirectPropertyTolerantResult> GetPropertyValuesTolerant_Impl( + const css::uno::Sequence< OUString >& rPropertyNames, bool bDirectValuesOnly ); + + virtual ~SwXTextPortion() override; + + virtual void Notify(const SfxHint& rHint) override; + +public: + SwXTextPortion(const SwUnoCursor* pPortionCursor, css::uno::Reference< css::text::XText > const& rParent, SwTextPortionType eType ); + SwXTextPortion(const SwUnoCursor* pPortionCursor, css::uno::Reference< css::text::XText > const& rParent, SwFrameFormat& rFormat ); + + // for Ruby + SwXTextPortion(const SwUnoCursor* pPortionCursor, + SwTextRuby const& rAttr, + css::uno::Reference< css::text::XText > const& xParent, + bool bIsEnd ); + + //XTextRange + virtual css::uno::Reference< css::text::XText > SAL_CALL getText() override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getStart() override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getEnd() override; + virtual OUString SAL_CALL getString() override; + virtual void SAL_CALL setString(const OUString& aString) override; + + //XTolerantMultiPropertySet + virtual css::uno::Sequence< css::beans::SetPropertyTolerantFailed > SAL_CALL setPropertyValuesTolerant( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ) override; + virtual css::uno::Sequence< css::beans::GetPropertyTolerantResult > SAL_CALL getPropertyValuesTolerant( const css::uno::Sequence< OUString >& aPropertyNames ) override; + virtual css::uno::Sequence< css::beans::GetDirectPropertyTolerantResult > SAL_CALL getDirectPropertyValuesTolerant( const css::uno::Sequence< OUString >& aPropertyNames ) override; + + //XMultiPropertySet + virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ) override; + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames ) override; + virtual void SAL_CALL addPropertiesChangeListener( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertiesChangeListener( const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener ) override; + virtual void SAL_CALL firePropertiesChangeEvent( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener ) override; + + //XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + //XPropertyState + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& PropertyName ) override; + virtual css::uno::Sequence< css::beans::PropertyState > SAL_CALL getPropertyStates( const css::uno::Sequence< OUString >& aPropertyName ) override; + virtual void SAL_CALL setPropertyToDefault( const OUString& PropertyName ) override; + virtual css::uno::Any SAL_CALL getPropertyDefault( const OUString& aPropertyName ) override; + + //XUnoTunnel + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + //XContentEnumerationAccess + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createContentEnumeration(const OUString& aServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getAvailableServiceNames() override; + + void SetRefMark( css::uno::Reference< css::text::XTextContent > const & xMark) + { m_xRefMark = xMark; } + + void SetTOXMark( css::uno::Reference< css::text::XTextContent > const & xMark) + { m_xTOXMark = xMark; } + + void SetBookmark( css::uno::Reference< css::text::XTextContent > const & xMark) + { m_xBookmark = xMark; } + + void SetFootnote( css::uno::Reference< css::text::XFootnote > const & xNote) + { m_xFootnote = xNote; } + + void SetTextField( css::uno::Reference< css::text::XTextField> const & xField) + { m_xTextField = xField; } + + void SetMeta( css::uno::Reference< css::text::XTextContent > const & xMeta) + { m_xMeta = xMeta; } + + void SetCollapsed(bool bSet) { m_bIsCollapsed = bSet;} + + SwTextPortionType GetTextPortionType() const { return m_ePortionType; } + + SwUnoCursor& GetCursor() const + { return *m_pUnoCursor; } +}; + +class SwXTextPortionEnumeration + : public ::cppu::WeakImplHelper + < css::container::XEnumeration + , css::lang::XServiceInfo + , css::lang::XUnoTunnel + > +{ + TextRangeList_t m_Portions; // contains all portions, filled by ctor + sw::UnoCursorPointer m_pUnoCursor; + +protected: + virtual ~SwXTextPortionEnumeration() override; + +public: + SwXTextPortionEnumeration(SwPaM& rParaCursor, + css::uno::Reference< css::text::XText > const & xParent, + const sal_Int32 nStart, const sal_Int32 nEnd ); + + SwXTextPortionEnumeration(SwPaM& rParaCursor, + TextRangeList_t const & rPortions ); + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + //XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; + + //XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual css::uno::Any SAL_CALL nextElement() override; + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; +}; + +class SwXRedlinePortion : public SwXTextPortion +{ +private: + SwRangeRedline const& m_rRedline; + + /// @throws css::uno::RuntimeException + void Validate(); + + using SwXTextPortion::GetPropertyValue; + + virtual ~SwXRedlinePortion() override; + +public: + SwXRedlinePortion( + SwRangeRedline const& rRedline, + SwUnoCursor const* pPortionCursor, + css::uno::Reference< css::text::XText > const& xParent, + bool const bIsStart); + + /// @throws std::exception + static css::uno::Any GetPropertyValue( + OUString const& PropertyName, SwRangeRedline const& rRedline); + /// @throws std::exception + static css::uno::Sequence< css::beans::PropertyValue > CreateRedlineProperties( + SwRangeRedline const& rRedline, bool const bIsStart); + + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + // XPropertySet + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unorefmark.hxx b/sw/source/core/inc/unorefmark.hxx new file mode 100644 index 000000000..c65b65009 --- /dev/null +++ b/sw/source/core/inc/unorefmark.hxx @@ -0,0 +1,116 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOREFMARK_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOREFMARK_HXX + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/text/XTextContent.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <unobaseclass.hxx> + +class SwDoc; +class SwFormatRefMark; + +typedef ::cppu::WeakImplHelper +< css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::container::XNamed +, css::text::XTextContent +> SwXReferenceMark_Base; + +class SwXReferenceMark + : public SwXReferenceMark_Base +{ + +private: + + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + virtual ~SwXReferenceMark() override; + + SwXReferenceMark(SwDoc *const pDoc, SwFormatRefMark *const pMark); + +public: + + static css::uno::Reference<css::text::XTextContent> + CreateXReferenceMark(SwDoc & rDoc, SwFormatRefMark * pMarkFormat); + + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName(const OUString& rName) override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOREFMARK_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unosection.hxx b/sw/source/core/inc/unosection.hxx new file mode 100644 index 000000000..370ed909f --- /dev/null +++ b/sw/source/core/inc/unosection.hxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOSECTION_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOSECTION_HXX + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/text/XTextSection.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <sfx2/Metadatable.hxx> + +#include <unobaseclass.hxx> + +class SwSectionFormat; + +typedef ::cppu::ImplInheritanceHelper +< ::sfx2::MetadatableMixin +, css::lang::XUnoTunnel +, css::lang::XServiceInfo +, css::beans::XPropertySet +, css::beans::XPropertyState +, css::beans::XMultiPropertySet +, css::container::XNamed +, css::text::XTextSection +> SwXTextSection_Base; + +class SwXTextSection + : public SwXTextSection_Base +{ + +private: + + class Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + + SwXTextSection(SwSectionFormat *const pFormat, const bool bIndexHeader); + + virtual ~SwXTextSection() override; + +public: + + SwSectionFormat* GetFormat() const; + + static css::uno::Reference< css::text::XTextSection > + CreateXTextSection(SwSectionFormat *const pFormat, + const bool bIndexHeader = false); + + // MetadatableMixin + virtual ::sfx2::Metadatable* GetCoreObject() override; + virtual css::uno::Reference< css::frame::XModel > + GetModel() override; + + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( + const OUString& rServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener > & xListener) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& rPropertyName, + const css::uno::Any& rValue) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& rPropertyName) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& rPropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) override; + + // XPropertyState + virtual css::beans::PropertyState SAL_CALL + getPropertyState(const OUString& rPropertyName) override; + virtual css::uno::Sequence< css::beans::PropertyState > SAL_CALL + getPropertyStates( + const css::uno::Sequence< OUString >& rPropertyNames) override; + virtual void SAL_CALL setPropertyToDefault( + const OUString& rPropertyName) override; + virtual css::uno::Any SAL_CALL getPropertyDefault( + const OUString& rPropertyName) override; + + // XMultiPropertySet + virtual void SAL_CALL setPropertyValues( + const css::uno::Sequence< OUString >& rPropertyNames, + const css::uno::Sequence< css::uno::Any >& rValues) override; + virtual css::uno::Sequence< css::uno::Any > + SAL_CALL getPropertyValues( + const css::uno::Sequence< OUString >& rPropertyNames) override; + virtual void SAL_CALL addPropertiesChangeListener( + const css::uno::Sequence< OUString >& rPropertyNames, + const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener) override; + virtual void SAL_CALL removePropertiesChangeListener( + const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener) override; + virtual void SAL_CALL firePropertiesChangeEvent( + const css::uno::Sequence< OUString >& rPropertyNames, + const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName(const OUString& rName) override; + + // XTextContent + virtual void SAL_CALL attach( + const css::uno::Reference< css::text::XTextRange > & xTextRange) override; + virtual css::uno::Reference< css::text::XTextRange > SAL_CALL getAnchor() override; + + // XTextSection + virtual css::uno::Reference< css::text::XTextSection > SAL_CALL + getParentSection() override; + virtual css::uno::Sequence< css::uno::Reference< css::text::XTextSection > > SAL_CALL + getChildSections() override; + +}; + +#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOSECTION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/unotextmarkup.hxx b/sw/source/core/inc/unotextmarkup.hxx new file mode 100644 index 000000000..bfbded136 --- /dev/null +++ b/sw/source/core/inc/unotextmarkup.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_UNOTEXTMARKUP_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_UNOTEXTMARKUP_HXX + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/text/XTextMarkup.hpp> +#include <com/sun/star/text/XMultiTextMarkup.hpp> + +#include <unobaseclass.hxx> + +#include <map> + +namespace com::sun::star::container { class XStringKeyMap; } +namespace com::sun::star::text { class XTextRange; } +class SwTextNode; +class ModelToViewHelper; + +/** Implementation of the css::text::XTextMarkup interface + */ +class SwXTextMarkup + : public ::cppu::WeakImplHelper + < css::text::XTextMarkup + , css::text::XMultiTextMarkup + > +{ +public: + SwXTextMarkup(SwTextNode *const rTextNode, + const ModelToViewHelper& rConversionMap); + virtual ~SwXTextMarkup() override; + + // css::text::XTextMarkup: + virtual css::uno::Reference< css::container::XStringKeyMap > SAL_CALL getMarkupInfoContainer() override; + + virtual void SAL_CALL commitStringMarkup(::sal_Int32 nType, const OUString & aIdentifier, ::sal_Int32 nStart, ::sal_Int32 nLength, + const css::uno::Reference< css::container::XStringKeyMap > & xMarkupInfoContainer) override; + + virtual void SAL_CALL commitTextRangeMarkup(::sal_Int32 nType, const OUString & aIdentifier, const css::uno::Reference< css::text::XTextRange> & xRange, + const css::uno::Reference< css::container::XStringKeyMap > & xMarkupInfoContainer) override; + + // css::text::XMultiTextMarkup: + virtual void SAL_CALL commitMultiTextMarkup( const css::uno::Sequence< css::text::TextMarkupDescriptor >& aMarkups ) override; + +private: + SwXTextMarkup( const SwXTextMarkup & ) = delete; + SwXTextMarkup & operator =( const SwXTextMarkup & ) = delete; + + struct Impl; + ::sw::UnoImplPtr<Impl> m_pImpl; + +protected: + SwTextNode* GetTextNode(); + void ClearTextNode(); + const ModelToViewHelper& GetConversionMap() const; +}; + +/** Implementation of the css::container::XStringKeyMap interface + */ +class SwXStringKeyMap: + public ::cppu::WeakImplHelper< + css::container::XStringKeyMap> +{ +public: + SwXStringKeyMap(); + + // css::container::XStringKeyMap: + virtual css::uno::Any SAL_CALL getValue(const OUString & aKey) override; + virtual sal_Bool SAL_CALL hasValue(const OUString & aKey) override; + virtual void SAL_CALL insertValue(const OUString & aKey, const css::uno::Any & aValue) override; + virtual ::sal_Int32 SAL_CALL getCount() override; + virtual OUString SAL_CALL getKeyByIndex(::sal_Int32 nIndex) override; + virtual css::uno::Any SAL_CALL getValueByIndex(::sal_Int32 nIndex) override; + +private: + SwXStringKeyMap(SwXStringKeyMap const &) = delete; + void operator =(SwXStringKeyMap const &) = delete; + + virtual ~SwXStringKeyMap() override {} + + std::map< OUString, css::uno::Any > maMap; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/viewimp.hxx b/sw/source/core/inc/viewimp.hxx new file mode 100644 index 000000000..aff1e7012 --- /dev/null +++ b/sw/source/core/inc/viewimp.hxx @@ -0,0 +1,307 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_INC_VIEWIMP_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_VIEWIMP_HXX + +#include <tools/color.hxx> +#include <svx/svdtypes.hxx> +#include <swrect.hxx> +#include <vector> +#include <memory> + +class OutputDevice; +class SwViewShell; +class SwFlyFrame; +class SwViewOption; +class SwRegionRects; +class SwFrame; +class SwLayAction; +class SwLayIdle; +class SwDrawView; +class SdrPageView; +class SwPageFrame; +class SwAccessibleMap; +class SdrObject; +class Fraction; +class SwPrintData; +class SwPagePreviewLayout; +struct PreviewPage; +class SwTextFrame; +// --> OD #i76669# +namespace sdr::contact { class ViewObjectContactRedirector; } +// <-- + +class SwViewShellImp +{ + friend class SwViewShell; + + friend class SwLayAction; // Lay- and IdleAction register and deregister + friend class SwLayIdle; + + // for paint of page preview + friend class SwPagePreviewLayout; + + SwViewShell *m_pShell; // If someone passes an Imp, but needs a SwViewShell, we + // keep a backlink here + + std::unique_ptr<SwDrawView> m_pDrawView; // Our DrawView + SdrPageView *m_pSdrPageView; // Exactly one Page for our DrawView + + SwPageFrame *m_pFirstVisiblePage; // Always points to the first visible Page + std::unique_ptr<SwRegionRects> m_pRegion; // Collector of Paintrects from the LayAction + + SwLayAction *m_pLayAction; // Is set if an Action object exists + // Is registered by the SwLayAction ctor and deregistered by the dtor + SwLayIdle *m_pIdleAct; // The same as SwLayAction for SwLayIdle + + /// note: the map is *uniquely* owned here - the shared_ptr is only + /// used so that SwAccessibleContext can check via weak_ptr that it's alive + std::shared_ptr<SwAccessibleMap> m_pAccessibleMap; + + bool m_bFirstPageInvalid : 1; // Pointer to the first Page invalid? + bool m_bResetHdlHiddenPaint : 1; // Ditto + bool m_bSmoothUpdate : 1; // For SmoothScroll + bool m_bStopSmooth : 1; + + sal_uInt16 m_nRestoreActions ; // Count for the Action that need to be restored (UNO) + SwRect m_aSmoothRect; + + std::unique_ptr<SwPagePreviewLayout> m_pPagePreviewLayout; + + void SetFirstVisPage(OutputDevice const * pRenderContext); // Recalculate the first visible Page + + void StartAction(); // Show handle and hide + void EndAction(); // Called by SwViewShell::ImplXXXAction + void LockPaint(); // Ditto; called by SwViewShell::ImplLockPaint + void UnlockPaint(); + +private: + + SwAccessibleMap *CreateAccessibleMap(); + + /** invalidate CONTENT_FLOWS_FROM/_TO relation for paragraphs + + #i27138# + implementation for wrapper method + <SwViewShell::InvalidateAccessibleParaFlowRelation(..)> + + @param _pFromTextFrame + input parameter - paragraph frame, for which the relation CONTENT_FLOWS_FROM + has to be invalidated. + If NULL, no CONTENT_FLOWS_FROM relation has to be invalidated + + @param _pToTextFrame + input parameter - paragraph frame, for which the relation CONTENT_FLOWS_TO + has to be invalidated. + If NULL, no CONTENT_FLOWS_TO relation has to be invalidated + */ + void InvalidateAccessibleParaFlowRelation_( const SwTextFrame* _pFromTextFrame, + const SwTextFrame* _pToTextFrame ); + + /** invalidate text selection for paragraphs + + #i27301# + implementation for wrapper method + <SwViewShell::InvalidateAccessibleParaTextSelection(..)> + */ + void InvalidateAccessibleParaTextSelection_(); + + /** invalidate attributes for paragraphs and paragraph's characters + + #i88069# + implementation for wrapper method + <SwViewShell::InvalidateAccessibleParaAttrs(..)> + */ + void InvalidateAccessibleParaAttrs_( const SwTextFrame& rTextFrame ); + +public: + SwViewShellImp( SwViewShell * ); + ~SwViewShellImp(); + void Init( const SwViewOption * ); /// Only for SwViewShell::Init() + + const SwViewShell *GetShell() const { return m_pShell; } + SwViewShell *GetShell() { return m_pShell; } + + Color GetRetoucheColor() const; + + /// Management of the first visible Page + const SwPageFrame *GetFirstVisPage(OutputDevice const * pRenderContext) const; + SwPageFrame *GetFirstVisPage(OutputDevice const * pRenderContext); + void SetFirstVisPageInvalid() { m_bFirstPageInvalid = true; } + + bool AddPaintRect( const SwRect &rRect ); + SwRegionRects *GetRegion() { return m_pRegion.get(); } + void DelRegion(); + + /// New Interface for StarView Drawing + bool HasDrawView() const { return nullptr != m_pDrawView; } + SwDrawView* GetDrawView() { return m_pDrawView.get(); } + const SwDrawView* GetDrawView() const { return m_pDrawView.get(); } + SdrPageView*GetPageView() { return m_pSdrPageView; } + const SdrPageView*GetPageView() const { return m_pSdrPageView; } + void MakeDrawView(); + + /** + * @param _pPageBackgrdColor for setting this color as the background color + * at the outliner of the draw view for painting layers "hell" and "heaven" + * + * @param _bIsPageRightToLeft for the horizontal text direction of the page + * in order to set the default horizontal text direction at the outliner of + * the draw view for painting layers "hell" and "heaven" + */ + void PaintLayer( const SdrLayerID _nLayerID, + SwPrintData const*const pPrintData, + SwPageFrame const& rPageFrame, + const SwRect& _rRect, + const Color* _pPageBackgrdColor, + const bool _bIsPageRightToLeft, + sdr::contact::ViewObjectContactRedirector* pRedirector ); + + /** + * Is passed to the DrawEngine as a Link and decides what is painted + * or not and in what way + */ + + // Interface Drawing + bool IsDragPossible( const Point &rPoint ); + void NotifySizeChg( const Size &rNewSz ); + + /// SS for the Lay-/IdleAction and relatives + bool IsAction() const { return m_pLayAction != nullptr; } + bool IsIdleAction() const { return m_pIdleAct != nullptr; } + SwLayAction &GetLayAction() { return *m_pLayAction; } + const SwLayAction &GetLayAction() const { return *m_pLayAction; } + + /** + * If an Action is running we ask it to check whether it's time + * to enable the WaitCursor + */ + void CheckWaitCursor(); + + /// Asks the LayAction if present + bool IsCalcLayoutProgress() const; + + /** + * @returns true if a LayAction is running + * + * There we also set the Flag for ExpressionFields + */ + bool IsUpdateExpFields(); + + void SetRestoreActions(sal_uInt16 nSet){m_nRestoreActions = nSet;} + sal_uInt16 GetRestoreActions() const{return m_nRestoreActions;} + + void InitPagePreviewLayout(); + + SwPagePreviewLayout* PagePreviewLayout() + { + return m_pPagePreviewLayout.get(); + } + + /// Is this view accessible? + bool IsAccessible() const { return m_pAccessibleMap != nullptr; } + + inline SwAccessibleMap& GetAccessibleMap(); + + /// Update (this) accessible view + void UpdateAccessible(); + + /// Remove a frame from the accessible view + void DisposeAccessible( const SwFrame *pFrame, const SdrObject *pObj, + bool bRecursive, bool bCanSkipInvisible ); + inline void DisposeAccessibleFrame( const SwFrame *pFrame, + bool bRecursive = false ); + inline void DisposeAccessibleObj( const SdrObject *pObj, bool bCanSkipInvisible ); + + /// Move a frame's position in the accessible view + void MoveAccessible( const SwFrame *pFrame, const SdrObject *pObj, + const SwRect& rOldFrame ); + inline void MoveAccessibleFrame( const SwFrame *pFrame, const SwRect& rOldFrame ); + + /// Add a frame in the accessible view + inline void AddAccessibleFrame( const SwFrame *pFrame ); + + inline void AddAccessibleObj( const SdrObject *pObj ); + + /// Invalidate accessible frame's content + void InvalidateAccessibleFrameContent( const SwFrame *pFrame ); + + /// Invalidate accessible frame's cursor position + void InvalidateAccessibleCursorPosition( const SwFrame *pFrame ); + + /// Invalidate editable state for all accessible frames + void InvalidateAccessibleEditableState( bool bAllShells, + const SwFrame *pFrame=nullptr ); + + /// Invalidate frame's relation set (for chained frames) + void InvalidateAccessibleRelationSet( const SwFlyFrame *pMaster, + const SwFlyFrame *pFollow ); + + /// update data for accessible preview + /// change method signature due to new page preview functionality + void UpdateAccessiblePreview( const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ); + + void InvalidateAccessiblePreviewSelection( sal_uInt16 nSelPage ); + + /// Fire all accessible events that have been collected so far + void FireAccessibleEvents(); +}; + +inline SwAccessibleMap& SwViewShellImp::GetAccessibleMap() +{ + if( !m_pAccessibleMap ) + CreateAccessibleMap(); + + return *m_pAccessibleMap; +} + +inline void SwViewShellImp::DisposeAccessibleFrame( const SwFrame *pFrame, + bool bRecursive ) +{ + DisposeAccessible( pFrame, nullptr, bRecursive, true ); +} + +inline void SwViewShellImp::DisposeAccessibleObj( const SdrObject *pObj, bool bCanSkipInvisible ) +{ + DisposeAccessible( nullptr, pObj, false, bCanSkipInvisible ); +} + +inline void SwViewShellImp::MoveAccessibleFrame( const SwFrame *pFrame, + const SwRect& rOldFrame ) +{ + MoveAccessible( pFrame, nullptr, rOldFrame ); +} + +inline void SwViewShellImp::AddAccessibleFrame( const SwFrame *pFrame ) +{ + SwRect aEmptyRect; + MoveAccessible( pFrame, nullptr, aEmptyRect ); +} + +inline void SwViewShellImp::AddAccessibleObj( const SdrObject *pObj ) +{ + SwRect aEmptyRect; + MoveAccessible( nullptr, pObj, aEmptyRect ); +} +#endif // INCLUDED_SW_SOURCE_CORE_INC_VIEWIMP_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/visiturl.hxx b/sw/source/core/inc/visiturl.hxx new file mode 100644 index 000000000..54f051247 --- /dev/null +++ b/sw/source/core/inc/visiturl.hxx @@ -0,0 +1,39 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_VISITURL_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_VISITURL_HXX + +#include <svl/lstner.hxx> + +class SwDoc; + +class SwURLStateChanged : public SfxListener +{ + SwDoc* pDoc; +public: + SwURLStateChanged( SwDoc* pD ); + virtual ~SwURLStateChanged() override; + + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/wrong.hxx b/sw/source/core/inc/wrong.hxx new file mode 100644 index 000000000..c5b4df884 --- /dev/null +++ b/sw/source/core/inc/wrong.hxx @@ -0,0 +1,417 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_INC_WRONG_HXX +#define INCLUDED_SW_SOURCE_CORE_INC_WRONG_HXX + +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XStringKeyMap.hpp> + +#include <com/sun/star/util/Color.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/uno/Any.hxx> + +#include <vector> + +#include <optional> + +#include <tools/color.hxx> +#include <swtypes.hxx> +#include <viewopt.hxx> +#include "TextFrameIndex.hxx" + +#if defined _MSC_VER +// For MSVC (without /vmg) SwTextNode must consistently be defined for +// WrongListIterator::m_pGetWrongList of pointer-to-SwTextNode-member type to consistently have the +// same size in all translation units that include this file: +#include <ndtxt.hxx> +#endif + +class SwWrongList; + +enum WrongAreaLineType +{ + WRONGAREA_NONE, + WRONGAREA_WAVE, + WRONGAREA_BOLDWAVE, + WRONGAREA_BOLD, + WRONGAREA_DASHED +}; + +enum WrongListType +{ + WRONGLIST_SPELL, + WRONGLIST_GRAMMAR, + WRONGLIST_SMARTTAG, + WRONGLIST_CHANGETRACKING +}; + +// ST2 +class SwWrongArea +{ +public: + OUString maType; + sal_Int32 mnPos; + sal_Int32 mnLen; + SwWrongList* mpSubList; + + Color mColor; + WrongAreaLineType mLineType; + + SwWrongArea( const OUString& rType, + WrongListType listType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nPos, + sal_Int32 nLen); + + SwWrongArea( const OUString& rType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nPos, + sal_Int32 nLen, + SwWrongList* pSubList); +private: + + static Color getGrammarColor ( css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag) + { + try + { + if (xPropertyBag.is()) + { + const OUString colorKey("LineColor"); + css::uno::Any aLineColor = xPropertyBag->getValue(colorKey); + css::util::Color lineColor = 0; + + if (aLineColor >>= lineColor) + { + return Color( lineColor ); + } + } + } + catch(const css::container::NoSuchElementException&) + { + } + catch(const css::uno::RuntimeException&) + { + } + + return COL_LIGHTBLUE; + } + + static WrongAreaLineType getGrammarLineType( css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag ) + { + try + { + if (xPropertyBag.is()) + { + const OUString typeKey("LineType"); + css::uno::Any aLineType = xPropertyBag->getValue(typeKey); + ::sal_Int16 lineType = 0; + + if (!(aLineType >>= lineType)) + { + return WRONGAREA_WAVE; + } + if (css::awt::FontUnderline::BOLDWAVE == lineType) + { + return WRONGAREA_BOLDWAVE; + } + if (css::awt::FontUnderline::BOLD == lineType) + { + return WRONGAREA_BOLD; + } + if (css::awt::FontUnderline::DASH == lineType) + { + return WRONGAREA_DASHED; + } + if (css::awt::FontUnderline::SMALLWAVE == lineType) + { + return WRONGAREA_WAVE; //Code draws wave height based on space that fits. + } + } + } + catch(const css::container::NoSuchElementException&) + { + } + catch(const css::uno::RuntimeException&) + { + } + + return WRONGAREA_WAVE; + } + + static Color getSmartColor ( css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag) + { + try + { + if (xPropertyBag.is()) + { + const OUString colorKey("LineColor"); + css::uno::Any aLineColor = xPropertyBag->getValue(colorKey); + css::util::Color lineColor = 0; + + if (aLineColor >>= lineColor) + { + return Color( lineColor ); + } + } + } + catch(const css::container::NoSuchElementException&) + { + } + catch(const css::uno::RuntimeException&) + { + } + + return SwViewOption::GetSmarttagColor( ); + } + + static WrongAreaLineType getSmartLineType( css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag ) + { + try + { + if (xPropertyBag.is()) + { + const OUString typeKey("LineType"); + css::uno::Any aLineType = xPropertyBag->getValue(typeKey); + ::sal_Int16 lineType = 0; + + if (!(aLineType >>= lineType)) + { + return WRONGAREA_DASHED; + } + if (css::awt::FontUnderline::WAVE == lineType) + { + return WRONGAREA_WAVE; + } + if (css::awt::FontUnderline::BOLDWAVE == lineType) + { + return WRONGAREA_BOLDWAVE; + } + if (css::awt::FontUnderline::BOLD == lineType) + { + return WRONGAREA_BOLD; + } + if (css::awt::FontUnderline::SMALLWAVE == lineType) + { + return WRONGAREA_WAVE; //Code draws wave height based on space that fits. + } + } + } + catch(const css::container::NoSuchElementException&) + { + } + catch(const css::uno::RuntimeException&) + { + } + + return WRONGAREA_DASHED; + } + + static Color getWrongAreaColor(WrongListType listType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag ) + { + if (WRONGLIST_SPELL == listType) + { + return SwViewOption::GetSpellColor(); + } + else if (WRONGLIST_GRAMMAR == listType) + { + return getGrammarColor(xPropertyBag); + } + else if (WRONGLIST_SMARTTAG == listType) + { + return getSmartColor(xPropertyBag); + } + + return SwViewOption::GetSpellColor(); + } + + static WrongAreaLineType getWrongAreaLineType(WrongListType listType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag ) + { + if (WRONGLIST_SPELL == listType) + { + return WRONGAREA_WAVE; + } + else if (WRONGLIST_GRAMMAR == listType) + { + return getGrammarLineType(xPropertyBag); + } + else if (WRONGLIST_SMARTTAG == listType) + { + return getSmartLineType(xPropertyBag); + } + + return WRONGAREA_WAVE; + } + +}; + +class SwWrongList +{ + std::vector<SwWrongArea> maList; + WrongListType meType; + + sal_Int32 mnBeginInvalid; // Start of the invalid range + sal_Int32 mnEndInvalid; // End of the invalid range + + static void ShiftLeft( sal_Int32 &rPos, sal_Int32 nStart, sal_Int32 nEnd ) + { if( rPos > nStart ) rPos = rPos > nEnd ? rPos - nEnd + nStart : nStart; } + void Invalidate_( sal_Int32 nBegin, sal_Int32 nEnd ); + + void Insert(sal_uInt16 nWhere, std::vector<SwWrongArea>::iterator startPos, std::vector<SwWrongArea>::iterator const & endPos); + void Remove( sal_uInt16 nIdx, sal_uInt16 nLen ); + + SwWrongList& operator= (const SwWrongList &) = delete; + SwWrongList( const SwWrongList& rCpy ) = delete; + +public: + SwWrongList( WrongListType eType ); + + virtual ~SwWrongList(); + virtual SwWrongList* Clone(); + virtual void CopyFrom( const SwWrongList& rCopy ); + + WrongListType GetWrongListType() const { return meType; } + sal_Int32 GetBeginInv() const { return mnBeginInvalid; } + sal_Int32 GetEndInv() const { return mnEndInvalid; } + void SetInvalid( sal_Int32 nBegin, sal_Int32 nEnd ); + void Validate(){ mnBeginInvalid = mnEndInvalid = COMPLETE_STRING; } + void Invalidate( sal_Int32 nBegin, sal_Int32 nEnd ); + bool InvalidateWrong(); + enum class FreshState { FRESH, CURSOR, NOTHING }; + FreshState Fresh( sal_Int32 &rStart, sal_Int32 &rEnd, sal_Int32 nPos, + sal_Int32 nLen, sal_uInt16 nIndex, sal_Int32 nCursorPos ); + sal_uInt16 GetWrongPos( sal_Int32 nValue ) const; + + bool Check( sal_Int32 &rChk, sal_Int32 &rLn ) const; + bool InWrongWord( sal_Int32 &rChk, sal_Int32 &rLn ) const; + sal_Int32 NextWrong( sal_Int32 nChk ) const; + + void Move( sal_Int32 nPos, sal_Int32 nDiff ); + void ClearList(); + + // Divide the list into two part, the wrong words until nSplitPos will be + // removed and transferred to a new SwWrongList. + SwWrongList* SplitList( sal_Int32 nSplitPos ); + // Join the next SwWrongList, nInsertPos is my own text length, where + // the other wrong list has to be inserted. + void JoinList( SwWrongList* pNext, sal_Int32 nInsertPos ); + + sal_Int32 Len( sal_uInt16 nIdx ) const + { + return nIdx < maList.size() ? maList[nIdx].mnLen : 0; + } + + sal_Int32 Pos( sal_uInt16 nIdx ) const + { + return nIdx < maList.size() ? maList[nIdx].mnPos : 0; + } + + sal_uInt16 Count() const { return static_cast<sal_uInt16>(maList.size()); } + + void Insert( const OUString& rType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nNewPos, sal_Int32 nNewLen, sal_uInt16 nWhere ) + { + std::vector<SwWrongArea>::iterator i = maList.begin(); + if ( nWhere >= maList.size() ) + i = maList.end(); // robust + else + i += nWhere; + + maList.insert(i, SwWrongArea( rType, meType, xPropertyBag, nNewPos, nNewLen) ); + } + + void Insert( const OUString& rType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nNewPos, sal_Int32 nNewLen ); + + SwWrongList* SubList( sal_uInt16 nIdx ) const + { + return nIdx < maList.size() ? maList[nIdx].mpSubList : nullptr; + } + + void InsertSubList( sal_Int32 nNewPos, sal_Int32 nNewLen, sal_uInt16 nWhere, SwWrongList* pSubList ); + + const SwWrongArea* GetElement( sal_uInt16 nIdx ) const + { + return nIdx < maList.size() ? &maList[nIdx] : nullptr; + } + void RemoveEntry( sal_Int32 nBegin, sal_Int32 nEnd ); + bool LookForEntry( sal_Int32 nBegin, sal_Int32 nEnd ); +}; + +class SwTextNode; +class SwTextFrame; + +namespace sw { + +struct MergedPara; + +class WrongListIteratorBase +{ +protected: + SwWrongList const* (SwTextNode::*const m_pGetWrongList)() const; + sw::MergedPara const*const m_pMergedPara; + size_t m_CurrentExtent; + TextFrameIndex m_CurrentIndex; + SwWrongList const*const m_pWrongList; + +public: + /// for the text frame + WrongListIteratorBase(SwTextFrame const& rFrame, + SwWrongList const* (SwTextNode::*pGetWrongList)() const); + /// for SwTextSlot + WrongListIteratorBase(SwWrongList const& rWrongList); +}; + +class WrongListIterator + : public WrongListIteratorBase +{ +public: + /// for the text frame + WrongListIterator(SwTextFrame const& rFrame, + SwWrongList const* (SwTextNode::*pGetWrongList)() const); + /// for SwTextSlot + WrongListIterator(SwWrongList const& rWrongList); + + bool Check(TextFrameIndex &rStart, TextFrameIndex &rLen); + const SwWrongArea* GetWrongElement(TextFrameIndex nStart); + + bool LooksUseful() { return m_pMergedPara || m_pWrongList; } +}; + +class WrongListIteratorCounter + : public WrongListIteratorBase +{ +public: + WrongListIteratorCounter(SwTextFrame const& rFrame, + SwWrongList const* (SwTextNode::*pGetWrongList)() const); + WrongListIteratorCounter(SwWrongList const& rWrongList); + + sal_uInt16 GetElementCount(); + std::optional<std::pair<TextFrameIndex, TextFrameIndex>> GetElementAt(sal_uInt16 nIndex); +}; + +} // namespace sw + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/anchoreddrawobject.cxx b/sw/source/core/layout/anchoreddrawobject.cxx new file mode 100644 index 000000000..a908300b0 --- /dev/null +++ b/sw/source/core/layout/anchoreddrawobject.cxx @@ -0,0 +1,918 @@ +/* -*- 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 <dcontact.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <tocntntanchoredobjectposition.hxx> +#include <tolayoutanchoredobjectposition.hxx> +#include <frmtool.hxx> +#include <fmtornt.hxx> +#include <txtfrm.hxx> +#include <vector> +#include <svx/svdogrp.hxx> +#include <tools/fract.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentState.hxx> +#include <txtfly.hxx> +#include <viewimp.hxx> +#include <textboxhelper.hxx> +#include <unomid.h> +#include <svx/svdoashp.hxx> + +using namespace ::com::sun::star; + +namespace { + +/// helper class for correct notification due to the positioning of +/// the anchored drawing object +class SwPosNotify +{ + private: + SwAnchoredDrawObject* mpAnchoredDrawObj; + SwRect maOldObjRect; + SwPageFrame* mpOldPageFrame; + + public: + explicit SwPosNotify( SwAnchoredDrawObject* _pAnchoredDrawObj ); + ~SwPosNotify() COVERITY_NOEXCEPT_FALSE; + // #i32795# + Point const & LastObjPos() const; +}; + +} + +SwPosNotify::SwPosNotify( SwAnchoredDrawObject* _pAnchoredDrawObj ) : + mpAnchoredDrawObj( _pAnchoredDrawObj ) +{ + maOldObjRect = mpAnchoredDrawObj->GetObjRect(); + // --> #i35640# - determine correct page frame + mpOldPageFrame = mpAnchoredDrawObj->GetPageFrame(); +} + +SwPosNotify::~SwPosNotify() COVERITY_NOEXCEPT_FALSE +{ + if ( maOldObjRect != mpAnchoredDrawObj->GetObjRect() ) + { + if( maOldObjRect.HasArea() && mpOldPageFrame ) + { + mpAnchoredDrawObj->NotifyBackground( mpOldPageFrame, maOldObjRect, + PrepareHint::FlyFrameLeave ); + } + SwRect aNewObjRect( mpAnchoredDrawObj->GetObjRect() ); + if( aNewObjRect.HasArea() ) + { + // --> #i35640# - determine correct page frame + SwPageFrame* pNewPageFrame = mpAnchoredDrawObj->GetPageFrame(); + if( pNewPageFrame ) + mpAnchoredDrawObj->NotifyBackground( pNewPageFrame, aNewObjRect, + PrepareHint::FlyFrameArrive ); + } + + ::ClrContourCache( mpAnchoredDrawObj->GetDrawObj() ); + + // --> #i35640# - additional notify anchor text frame + // Needed for negative positioned drawing objects + // --> #i43255# - refine condition to avoid unneeded + // invalidations: anchored object had to be on the page of its anchor + // text frame. + if ( mpAnchoredDrawObj->GetAnchorFrame()->IsTextFrame() && + mpOldPageFrame == mpAnchoredDrawObj->GetAnchorFrame()->FindPageFrame() ) + { + mpAnchoredDrawObj->AnchorFrame()->Prepare( PrepareHint::FlyFrameLeave ); + } + + // indicate a restart of the layout process + mpAnchoredDrawObj->SetRestartLayoutProcess( true ); + } + else + { + // lock position + mpAnchoredDrawObj->LockPosition(); + + if ( !mpAnchoredDrawObj->ConsiderForTextWrap() ) + { + // indicate that object has to be considered for text wrap + mpAnchoredDrawObj->SetConsiderForTextWrap( true ); + // invalidate 'background' in order to allow its 'background' + // to wrap around it. + mpAnchoredDrawObj->NotifyBackground( mpAnchoredDrawObj->GetPageFrame(), + mpAnchoredDrawObj->GetObjRectWithSpaces(), + PrepareHint::FlyFrameArrive ); + // invalidate position of anchor frame in order to force + // a re-format of the anchor frame, which also causes a + // re-format of the invalid previous frames of the anchor frame. + mpAnchoredDrawObj->AnchorFrame()->InvalidatePos(); + } + } + // tdf#101464 notify SwAccessibleMap about new drawing object position + if (mpOldPageFrame && mpOldPageFrame->getRootFrame()->IsAnyShellAccessible()) + { + mpOldPageFrame->getRootFrame()->GetCurrShell()->Imp()->MoveAccessible( + nullptr, mpAnchoredDrawObj->GetDrawObj(), maOldObjRect); + } +} + +// --> #i32795# +Point const & SwPosNotify::LastObjPos() const +{ + return maOldObjRect.Pos(); +} + +namespace { + +// #i32795# +/// helper class for oscillation control on object positioning +class SwObjPosOscillationControl +{ + private: + const SwAnchoredDrawObject* mpAnchoredDrawObj; + + std::vector<Point> maObjPositions; + + public: + explicit SwObjPosOscillationControl( const SwAnchoredDrawObject& _rAnchoredDrawObj ); + + bool OscillationDetected(); +}; + +} + +SwObjPosOscillationControl::SwObjPosOscillationControl( + const SwAnchoredDrawObject& _rAnchoredDrawObj ) + : mpAnchoredDrawObj( &_rAnchoredDrawObj ) +{ +} + +bool SwObjPosOscillationControl::OscillationDetected() +{ + bool bOscillationDetected = false; + + if ( maObjPositions.size() == 20 ) + { + // position stack is full -> oscillation + bOscillationDetected = true; + } + else + { + Point aNewObjPos = mpAnchoredDrawObj->GetObjRect().Pos(); + for ( auto const & pt : maObjPositions ) + { + if ( aNewObjPos == pt ) + { + // position already occurred -> oscillation + bOscillationDetected = true; + break; + } + } + if ( !bOscillationDetected ) + { + maObjPositions.push_back( aNewObjPos ); + } + } + + return bOscillationDetected; +} + + +SwAnchoredDrawObject::SwAnchoredDrawObject() : + SwAnchoredObject(), + mbValidPos( false ), + mbNotYetAttachedToAnchorFrame( true ), + // --> #i28749# + mbNotYetPositioned( true ), + // --> #i62875# + mbCaptureAfterLayoutDirChange( false ) +{ +} + +SwAnchoredDrawObject::~SwAnchoredDrawObject() +{ +} + +// --> #i62875# +void SwAnchoredDrawObject::UpdateLayoutDir() +{ + SwFrameFormat::tLayoutDir nOldLayoutDir( GetFrameFormat().GetLayoutDir() ); + + SwAnchoredObject::UpdateLayoutDir(); + + if ( !NotYetPositioned() && + GetFrameFormat().GetLayoutDir() != nOldLayoutDir && + GetFrameFormat().GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE) && + !IsOutsidePage() ) + { + mbCaptureAfterLayoutDirChange = true; + } +} + +// --> #i62875# +bool SwAnchoredDrawObject::IsOutsidePage() const +{ + bool bOutsidePage( false ); + + if ( !NotYetPositioned() && GetPageFrame() ) + { + SwRect aTmpRect( GetObjRect() ); + bOutsidePage = + ( aTmpRect.Intersection( GetPageFrame()->getFrameArea() ) != GetObjRect() ); + } + + return bOutsidePage; +} + +void SwAnchoredDrawObject::MakeObjPos() +{ + if ( IsPositioningInProgress() ) + { + // nothing to do - positioning already in progress + return; + } + + if ( mbValidPos ) + { + // nothing to do - position is valid + return; + } + + // --> #i28749# - anchored drawing object has to be attached + // to anchor frame + if ( mbNotYetAttachedToAnchorFrame ) + { + OSL_FAIL( "<SwAnchoredDrawObject::MakeObjPos() - drawing object not yet attached to anchor frame -> no positioning" ); + return; + } + + SwDrawContact* pDrawContact = + static_cast<SwDrawContact*>(::GetUserCall( GetDrawObj() )); + + // --> #i28749# - if anchored drawing object hasn't been yet + // positioned, convert its positioning attributes, if its positioning + // attributes are given in horizontal left-to-right layout. + // --> #i36010# - Note: horizontal left-to-right layout is made + // the default layout direction for <SwDrawFrameFormat> instances. Thus, it has + // to be adjusted manually, if no adjustment of the positioning attributes + // have to be performed here. + // --> #i35635# - additionally move drawing object to the visible layer. + if ( mbNotYetPositioned ) + { + // --> #i35635# + pDrawContact->MoveObjToVisibleLayer( DrawObj() ); + // --> perform conversion of positioning + // attributes only for 'master' drawing objects + // #i44334#, #i44681# - check, if positioning + // attributes already have been set. + if ( dynamic_cast< const SwDrawVirtObj* >(GetDrawObj()) == nullptr && + !static_cast<SwDrawFrameFormat&>(GetFrameFormat()).IsPosAttrSet() ) + { + SetPositioningAttr(); + } + // --> + // - reset internal flag after all needed actions are performed to + // avoid callbacks from drawing layer + mbNotYetPositioned = false; + } + + // indicate that positioning is in progress + { + SwObjPositioningInProgress aObjPosInProgress( *this ); + + // determine relative position of drawing object and set it + switch ( pDrawContact->GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + { + // indicate that position will be valid after positioning is performed + mbValidPos = true; + // nothing to do, because as-character anchored objects are positioned + // during the format of its anchor frame - see <SwFlyCntPortion::SetBase(..)> + } + break; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + { + // --> #i32795# - move intrinsic positioning to + // helper method <MakeObjPosAnchoredAtPara()> + MakeObjPosAnchoredAtPara(); + } + break; + case RndStdIds::FLY_AT_PAGE: + case RndStdIds::FLY_AT_FLY: + { + // --> #i32795# - move intrinsic positioning to + // helper method <MakeObjPosAnchoredAtLayout()> + MakeObjPosAnchoredAtLayout(); + } + break; + default: + { + assert(!"<SwAnchoredDrawObject::MakeObjPos()> - unknown anchor type."); + } + } + + // keep, current object rectangle + // --> #i34748# - use new method <SetLastObjRect(..)> + SetLastObjRect( GetObjRect().SVRect() ); + + // Assure for 'master' drawing object, that it's registered at the correct page. + // Perform check not for as-character anchored drawing objects and only if + // the anchor frame is valid. + if ( dynamic_cast< const SwDrawVirtObj* >(GetDrawObj()) == nullptr && + !pDrawContact->ObjAnchoredAsChar() && + GetAnchorFrame()->isFrameAreaDefinitionValid() ) + { + pDrawContact->ChkPage(); + } + } + + // --> #i62875# + if ( mbCaptureAfterLayoutDirChange && + GetPageFrame() ) + { + SwRect aPageRect( GetPageFrame()->getFrameArea() ); + SwRect aObjRect( GetObjRect() ); + if ( aObjRect.Right() >= aPageRect.Right() + 10 ) + { + Size aSize( aPageRect.Right() - aObjRect.Right(), 0 ); + DrawObj()->Move( aSize ); + aObjRect = GetObjRect(); + } + + if ( aObjRect.Left() + 10 <= aPageRect.Left() ) + { + Size aSize( aPageRect.Left() - aObjRect.Left(), 0 ); + DrawObj()->Move( aSize ); + } + + mbCaptureAfterLayoutDirChange = false; + } +} + +/** method for the intrinsic positioning of an at-paragraph|at-character + anchored drawing object + + #i32795# - helper method for method <MakeObjPos> +*/ +void SwAnchoredDrawObject::MakeObjPosAnchoredAtPara() +{ + // --> #i32795# - adopt positioning algorithm from Writer + // fly frames, which are anchored at paragraph|at character + + // Determine, if anchor frame can/has to be formatted. + // If yes, after each object positioning the anchor frame is formatted. + // If after the anchor frame format the object position isn't valid, the + // object is positioned again. + // --> #i43255# - refine condition: anchor frame format not + // allowed, if another anchored object, has to be consider its wrap influence + // --> #i50356# - format anchor frame containing the anchor + // position. E.g., for at-character anchored object this can be the follow + // frame of the anchor frame, which contains the anchor character. + bool bJoinLocked + = static_cast<const SwTextFrame*>(GetAnchorFrameContainingAnchPos())->IsAnyJoinLocked(); + const bool bFormatAnchor = !bJoinLocked && !ConsiderObjWrapInfluenceOnObjPos() + && !ConsiderObjWrapInfluenceOfOtherObjs(); + + // Format of anchor is needed for (vertical) fly offsets, otherwise the + // lack of fly portions will result in an incorrect 0 offset. + bool bAddVerticalFlyOffsets = GetFrameFormat().getIDocumentSettingAccess().get( + DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); + bool bFormatAnchorOnce = !bJoinLocked && bAddVerticalFlyOffsets; + + if (bFormatAnchor || bFormatAnchorOnce) + { + // --> #i50356# + GetAnchorFrameContainingAnchPos()->Calc(GetAnchorFrameContainingAnchPos()->getRootFrame()->GetCurrShell()->GetOut()); + } + + bool bOscillationDetected = false; + SwObjPosOscillationControl aObjPosOscCtrl( *this ); + // --> #i3317# - boolean, to apply temporarily the + // 'straightforward positioning process' for the frame due to its + // overlapping with a previous column. + bool bConsiderWrapInfluenceDueToOverlapPrevCol( false ); + do { + // indicate that position will be valid after positioning is performed + mbValidPos = true; + + // --> #i35640# - correct scope for <SwPosNotify> instance + { + // create instance of <SwPosNotify> for correct notification + SwPosNotify aPosNotify( this ); + + // determine and set position + objectpositioning::SwToContentAnchoredObjectPosition + aObjPositioning( *DrawObj() ); + aObjPositioning.CalcPosition(); + + // get further needed results of the positioning algorithm + SetVertPosOrientFrame ( aObjPositioning.GetVertPosOrientFrame() ); + SetDrawObjAnchor(); + + // check for object position oscillation, if position has changed. + if ( GetObjRect().Pos() != aPosNotify.LastObjPos() ) + { + bOscillationDetected = aObjPosOscCtrl.OscillationDetected(); + } + } + // format anchor frame, if requested. + // Note: the format of the anchor frame can cause the object position + // to be invalid. + if ( bFormatAnchor ) + { + // --> #i50356# + GetAnchorFrameContainingAnchPos()->Calc(GetAnchorFrameContainingAnchPos()->getRootFrame()->GetCurrShell()->GetOut()); + } + + // --> #i3317# + if ( !ConsiderObjWrapInfluenceOnObjPos() && + OverlapsPrevColumn() ) + { + bConsiderWrapInfluenceDueToOverlapPrevCol = true; + } + } while ( !mbValidPos && !bOscillationDetected && + !bConsiderWrapInfluenceDueToOverlapPrevCol ); + + // --> #i3317# - consider a detected oscillation and overlapping + // with previous column. + // temporarily consider the anchored objects wrapping style influence + if ( bOscillationDetected || bConsiderWrapInfluenceDueToOverlapPrevCol ) + { + SetTmpConsiderWrapInfluence( true ); + SetRestartLayoutProcess( true ); + } +} + +/** method for the intrinsic positioning of an at-page|at-frame anchored + drawing object + + #i32795# - helper method for method <MakeObjPos> +*/ +void SwAnchoredDrawObject::MakeObjPosAnchoredAtLayout() +{ + // indicate that position will be valid after positioning is performed + mbValidPos = true; + + // create instance of <SwPosNotify> for correct notification + SwPosNotify aPosNotify( this ); + + // determine position + objectpositioning::SwToLayoutAnchoredObjectPosition + aObjPositioning( *DrawObj() ); + aObjPositioning.CalcPosition(); + + // set position + + // --> #i31698# + // --> #i34995# - setting anchor position needed for filters, + // especially for the xml-filter to the OpenOffice.org file format + { + const Point aNewAnchorPos = + GetAnchorFrame()->GetFrameAnchorPos( ::HasWrap( GetDrawObj() ) ); + DrawObj()->SetAnchorPos( aNewAnchorPos ); + // --> #i70122# - missing invalidation + InvalidateObjRectWithSpaces(); + } + SetCurrRelPos( aObjPositioning.GetRelPos() ); + const SwFrame* pAnchorFrame = GetAnchorFrame(); + SwRectFnSet aRectFnSet(pAnchorFrame); + const Point aAnchPos( aRectFnSet.GetPos(pAnchorFrame->getFrameArea()) ); + SetObjLeft( aAnchPos.X() + GetCurrRelPos().X() ); + SetObjTop( aAnchPos.Y() + GetCurrRelPos().Y() ); +} + +void SwAnchoredDrawObject::SetDrawObjAnchor() +{ + // new anchor position + // --> #i31698# - + Point aNewAnchorPos = + GetAnchorFrame()->GetFrameAnchorPos( ::HasWrap( GetDrawObj() ) ); + Point aCurrAnchorPos = GetDrawObj()->GetAnchorPos(); + if ( aNewAnchorPos != aCurrAnchorPos ) + { + // determine movement to be applied after setting the new anchor position + Size aMove( aCurrAnchorPos.getX() - aNewAnchorPos.getX(), + aCurrAnchorPos.getY() - aNewAnchorPos.getY() ); + // set new anchor position + DrawObj()->SetAnchorPos( aNewAnchorPos ); + // correct object position, caused by setting new anchor position + DrawObj()->Move( aMove ); + // --> #i70122# - missing invalidation + InvalidateObjRectWithSpaces(); + } +} + +/** method to invalidate the given page frame + + #i28701# +*/ +void SwAnchoredDrawObject::InvalidatePage_( SwPageFrame* _pPageFrame ) +{ + if ( _pPageFrame && !_pPageFrame->GetFormat()->GetDoc()->IsInDtor() ) + { + if ( _pPageFrame->GetUpper() ) + { + // --> #i35007# - correct invalidation for as-character + // anchored objects. + if ( GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + _pPageFrame->InvalidateFlyInCnt(); + } + else + { + _pPageFrame->InvalidateFlyLayout(); + } + + SwRootFrame* pRootFrame = static_cast<SwRootFrame*>(_pPageFrame->GetUpper()); + pRootFrame->DisallowTurbo(); + if ( pRootFrame->GetTurbo() ) + { + const SwContentFrame* pTmpFrame = pRootFrame->GetTurbo(); + pRootFrame->ResetTurbo(); + pTmpFrame->InvalidatePage(); + } + pRootFrame->SetIdleFlags(); + } + } +} + +void SwAnchoredDrawObject::InvalidateObjPos() +{ + // --> #i28701# - check, if invalidation is allowed + if ( mbValidPos && + InvalidationOfPosAllowed() ) + { + mbValidPos = false; + // --> #i68520# + InvalidateObjRectWithSpaces(); + + // --> #i44339# - check, if anchor frame exists. + if ( GetAnchorFrame() ) + { + // --> #118547# - notify anchor frame of as-character + // anchored object, because its positioned by the format of its anchor frame. + // --> #i44559# - assure, that text hint is already + // existing in the text frame + if ( dynamic_cast< const SwTextFrame* >(GetAnchorFrame()) != nullptr && + (GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) ) + { + SwTextFrame* pAnchorTextFrame( static_cast<SwTextFrame*>(AnchorFrame()) ); + if (pAnchorTextFrame->CalcFlyPos(&GetFrameFormat()) != TextFrameIndex(COMPLETE_STRING)) + { + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged, &GetFrameFormat() ); + } + } + + SwPageFrame* pPageFrame = AnchorFrame()->FindPageFrame(); + InvalidatePage_( pPageFrame ); + + // --> #i32270# - also invalidate page frame, at which the + // drawing object is registered at. + SwPageFrame* pPageFrameRegisteredAt = GetPageFrame(); + if ( pPageFrameRegisteredAt && + pPageFrameRegisteredAt != pPageFrame ) + { + InvalidatePage_( pPageFrameRegisteredAt ); + } + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor && + pPageFrameOfAnchor != pPageFrame && + pPageFrameOfAnchor != pPageFrameRegisteredAt ) + { + InvalidatePage_( pPageFrameOfAnchor ); + } + } + } +} + +SwFrameFormat& SwAnchoredDrawObject::GetFrameFormat() +{ + assert(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); + return *(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); +} +const SwFrameFormat& SwAnchoredDrawObject::GetFrameFormat() const +{ + assert(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); + return *(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); +} + +SwRect SwAnchoredDrawObject::GetObjRect() const +{ + // use geometry of drawing object + //return GetDrawObj()->GetCurrentBoundRect(); + return GetDrawObj()->GetSnapRect(); +} + +namespace +{ + // Imagine an open book, inside margin is the one that is at the inner side of the pages, at the center of the book, + // outside margin is at the two opposite edges of the book. + // outside --text-- inside | inside --text-- outside + // With mirrored margins, when relating the size of an object from the inside margin for example, on the + // first page we calculate the new size of the object using the size of the right margin, + // on second page the left margin, third page right margin, etc. + long getInsideOutsideRelativeWidth(bool isOutside, const SwPageFrame* const pPageFrame) + { + // Alternating between the only two possible cases: inside and outside. + // Inside = false, Outside = true. + auto nPageNum = pPageFrame->GetPhyPageNum(); + if (nPageNum % 2 == (isOutside ? 0 : 1)) + return pPageFrame->GetRightMargin(); + else + return pPageFrame->GetLeftMargin(); + } +} + +// --> #i70122# +SwRect SwAnchoredDrawObject::GetObjBoundRect() const +{ + bool bGroupShape = dynamic_cast<const SdrObjGroup*>( GetDrawObj() ); + // Resize objects with relative width or height + if ( !bGroupShape && GetPageFrame( ) && ( GetDrawObj( )->GetRelativeWidth( ) || GetDrawObj()->GetRelativeHeight( ) ) ) + { + tools::Rectangle aCurrObjRect = GetDrawObj()->GetCurrentBoundRect(); + + long nTargetWidth = aCurrObjRect.GetWidth( ); + if ( GetDrawObj( )->GetRelativeWidth( ) ) + { + long nWidth = 0; + if (GetDrawObj()->GetRelativeWidthRelation() == text::RelOrientation::FRAME) + // Exclude margins. + nWidth = GetPageFrame()->getFramePrintArea().SVRect().GetWidth(); + // Here we handle the relative size of the width of some shape. + // The size of the shape's width is going to be relative to the size of the left margin. + // E.g.: (left margin = 8 && relative size = 150%) -> width of some shape = 12. + else if (GetDrawObj()->GetRelativeWidthRelation() == text::RelOrientation::PAGE_LEFT) + { + if (GetPageFrame()->GetPageDesc()->GetUseOn() == UseOnPage::Mirror) + // We want to get the width of whatever is going through here using the size of the + // outside margin. + nWidth = getInsideOutsideRelativeWidth(true, GetPageFrame()); + else + nWidth = GetPageFrame()->GetLeftMargin(); + } + // Same as the left margin above. + else if (GetDrawObj()->GetRelativeWidthRelation() == text::RelOrientation::PAGE_RIGHT) + if (GetPageFrame()->GetPageDesc()->GetUseOn() == UseOnPage::Mirror) + // We want to get the width of whatever is going through here using the size of the + // inside margin. + nWidth = getInsideOutsideRelativeWidth(false, GetPageFrame()); + else + nWidth = GetPageFrame()->GetRightMargin(); + else + nWidth = GetPageFrame( )->GetBoundRect( GetPageFrame()->getRootFrame()->GetCurrShell()->GetOut() ).SVRect().GetWidth(); + nTargetWidth = nWidth * (*GetDrawObj( )->GetRelativeWidth()); + } + + bool bCheck = GetDrawObj()->GetRelativeHeight(); + if (bCheck) + { + auto pObjCustomShape = dynamic_cast<const SdrObjCustomShape*>(GetDrawObj()); + bCheck = !pObjCustomShape || !pObjCustomShape->IsAutoGrowHeight(); + } + + long nTargetHeight = aCurrObjRect.GetHeight(); + if (bCheck) + { + long nHeight = 0; + if (GetDrawObj()->GetRelativeHeightRelation() == text::RelOrientation::FRAME) + // Exclude margins. + nHeight = GetPageFrame()->getFramePrintArea().SVRect().GetHeight(); + else if (GetDrawObj()->GetRelativeHeightRelation() == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + { + // count required height: print area bottom = bottom margin + footer + SwRect aFooterRect; + auto pFooterFrame = GetPageFrame()->GetFooterFrame(); + if (pFooterFrame) + aFooterRect = pFooterFrame->GetPaintArea(); + nHeight = GetPageFrame()->GetBottomMargin() + aFooterRect.Height(); + } + else + nHeight = GetPageFrame( )->GetBoundRect( GetPageFrame()->getRootFrame()->GetCurrShell()->GetOut() ).SVRect().GetHeight(); + nTargetHeight = nHeight * (*GetDrawObj()->GetRelativeHeight()); + } + + if ( nTargetWidth != aCurrObjRect.GetWidth( ) || nTargetHeight != aCurrObjRect.GetHeight( ) ) + { + SwDoc* pDoc = const_cast<SwDoc*>(GetPageFrame()->GetFormat()->GetDoc()); + + bool bEnableSetModified = pDoc->getIDocumentState().IsEnableSetModified(); + pDoc->getIDocumentState().SetEnableSetModified(false); + auto pObject = const_cast<SdrObject*>(GetDrawObj()); + pObject->Resize( aCurrObjRect.TopLeft(), + Fraction( nTargetWidth, aCurrObjRect.GetWidth() ), + Fraction( nTargetHeight, aCurrObjRect.GetHeight() ), false ); + + if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject)) + { + if (SwTextBoxHelper::isTextBox(pFrameFormat, RES_DRAWFRMFMT)) + { + // Shape has relative size and also a textbox, update its text area as well. + uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY); + SwTextBoxHelper::syncProperty(pFrameFormat, RES_FRM_SIZE, MID_FRMSIZE_SIZE, + uno::makeAny(xShape->getSize())); + } + } + + pDoc->getIDocumentState().SetEnableSetModified(bEnableSetModified); + } + } + return GetDrawObj()->GetCurrentBoundRect(); +} + +// --> #i68520# +bool SwAnchoredDrawObject::SetObjTop_( const SwTwips _nTop ) +{ + SwTwips nDiff = _nTop - GetObjRect().Top(); + DrawObj()->Move( Size( 0, nDiff ) ); + + return nDiff != 0; +} +bool SwAnchoredDrawObject::SetObjLeft_( const SwTwips _nLeft ) +{ + SwTwips nDiff = _nLeft - GetObjRect().Left(); + DrawObj()->Move( Size( nDiff, 0 ) ); + + return nDiff != 0; +} + +/** adjust positioning and alignment attributes for new anchor frame + + #i33313# - add second optional parameter <_pNewObjRect> +*/ +void SwAnchoredDrawObject::AdjustPositioningAttr( const SwFrame* _pNewAnchorFrame, + const SwRect* _pNewObjRect ) +{ + SwTwips nHoriRelPos = 0; + SwTwips nVertRelPos = 0; + const Point aAnchorPos = _pNewAnchorFrame->GetFrameAnchorPos( ::HasWrap( GetDrawObj() ) ); + // --> #i33313# + const SwRect aObjRect( _pNewObjRect ? *_pNewObjRect : GetObjRect() ); + const bool bVert = _pNewAnchorFrame->IsVertical(); + const bool bR2L = _pNewAnchorFrame->IsRightToLeft(); + if ( bVert ) + { + nHoriRelPos = aObjRect.Top() - aAnchorPos.Y(); + nVertRelPos = aAnchorPos.X() - aObjRect.Right(); + } + else if ( bR2L ) + { + nHoriRelPos = aAnchorPos.X() - aObjRect.Right(); + nVertRelPos = aObjRect.Top() - aAnchorPos.Y(); + } + else + { + nHoriRelPos = aObjRect.Left() - aAnchorPos.X(); + nVertRelPos = aObjRect.Top() - aAnchorPos.Y(); + } + + SwFormatHoriOrient hori(nHoriRelPos, text::HoriOrientation::NONE, text::RelOrientation::FRAME); + SwFormatVertOrient vert(nVertRelPos, text::VertOrientation::NONE, text::RelOrientation::FRAME); + SfxItemSet items(GetFrameFormat().GetDoc()->GetAttrPool(), svl::Items<RES_VERT_ORIENT, RES_HORI_ORIENT>()); + items.Put(hori); + items.Put(vert); + GetFrameFormat().GetDoc()->SetAttr(items, GetFrameFormat()); +} + +// --> #i34748# - change return type. +// If member <mpLastObjRect> is NULL, create one. +void SwAnchoredDrawObject::SetLastObjRect( const tools::Rectangle& _rNewLastRect ) +{ + maLastObjRect = _rNewLastRect; +} + +void SwAnchoredDrawObject::ObjectAttachedToAnchorFrame() +{ + // --> #i31698# + SwAnchoredObject::ObjectAttachedToAnchorFrame(); + + if ( mbNotYetAttachedToAnchorFrame ) + { + mbNotYetAttachedToAnchorFrame = false; + } +} + +/** method to set positioning attributes + + #i35798# + During load the positioning attributes aren't set. + Thus, the positioning attributes are set by the current object geometry. + This method is also used for the conversion for drawing objects + (not anchored as-character) imported from OpenOffice.org file format + once and directly before the first positioning. +*/ +void SwAnchoredDrawObject::SetPositioningAttr() +{ + SwDrawContact* pDrawContact = + static_cast<SwDrawContact*>(GetUserCall( GetDrawObj() )); + + if ( !pDrawContact->ObjAnchoredAsChar() ) + { + SwRect aObjRect( GetObjRect() ); + + SwTwips nHoriPos = aObjRect.Left(); + SwTwips nVertPos = aObjRect.Top(); + // #i44334#, #i44681# + // perform conversion only if position is in horizontal-left-to-right-layout. + if ( GetFrameFormat().GetPositionLayoutDir() == + text::PositionLayoutDir::PositionInHoriL2R ) + { + SwFrameFormat::tLayoutDir eLayoutDir = GetFrameFormat().GetLayoutDir(); + switch ( eLayoutDir ) + { + case SwFrameFormat::HORI_L2R: + { + // nothing to do + } + break; + case SwFrameFormat::HORI_R2L: + { + nHoriPos = -aObjRect.Left() - aObjRect.Width(); + } + break; + case SwFrameFormat::VERT_R2L: + { + nHoriPos = aObjRect.Top(); + nVertPos = -aObjRect.Left() - aObjRect.Width(); + } + break; + default: + { + assert(!"<SwAnchoredDrawObject::SetPositioningAttr()> - unsupported layout direction"); + } + } + } + + // --> #i71182# + // only change position - do not lose other attributes + + SwFormatHoriOrient aHori( GetFrameFormat().GetHoriOrient() ); + if (nHoriPos != aHori.GetPos()) { + aHori.SetPos( nHoriPos ); + InvalidateObjRectWithSpaces(); + GetFrameFormat().SetFormatAttr( aHori ); + } + + SwFormatVertOrient aVert( GetFrameFormat().GetVertOrient() ); + if (nVertPos != aVert.GetPos()) { + aVert.SetPos( nVertPos ); + InvalidateObjRectWithSpaces(); + GetFrameFormat().SetFormatAttr( aVert ); + } + + // --> #i36010# - set layout direction of the position + GetFrameFormat().SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + } + // --> #i65798# - also for as-character anchored objects + // --> #i45952# - indicate that position + // attributes are set now. + static_cast<SwDrawFrameFormat&>(GetFrameFormat()).PosAttrSet(); +} + +void SwAnchoredDrawObject::NotifyBackground( SwPageFrame* _pPageFrame, + const SwRect& _rRect, + PrepareHint _eHint ) +{ + ::Notify_Background( GetDrawObj(), _pPageFrame, _rRect, _eHint, true ); +} + +/** method to assure that anchored object is registered at the correct + page frame + + #i28701# +*/ +void SwAnchoredDrawObject::RegisterAtCorrectPage() +{ + SwPageFrame* pPageFrame( nullptr ); + if ( GetVertPosOrientFrame() ) + { + pPageFrame = const_cast<SwPageFrame*>(GetVertPosOrientFrame()->FindPageFrame()); + } + if ( pPageFrame && GetPageFrame() != pPageFrame ) + { + if ( GetPageFrame() ) + GetPageFrame()->RemoveDrawObjFromPage( *this ); + pPageFrame->AppendDrawObjToPage( *this ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/anchoredobject.cxx b/sw/source/core/layout/anchoredobject.cxx new file mode 100644 index 000000000..df9d0cb08 --- /dev/null +++ b/sw/source/core/layout/anchoredobject.cxx @@ -0,0 +1,900 @@ +/* -*- 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 <txtfrm.hxx> +#include <frmatr.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <fmtsrnd.hxx> +#include <dcontact.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <sortedobjs.hxx> +#include <pagefrm.hxx> +#include <layouter.hxx> + +using namespace ::com::sun::star; + +// --> #i28701# - +// implementation of helper class <SwObjPositioningInProgress> + +SwObjPositioningInProgress::SwObjPositioningInProgress( SdrObject& _rSdrObj ) : + mpAnchoredObj( nullptr ), + // --> #i52904# + mbOldObjPositioningInProgress( false ) +{ + mpAnchoredObj = ::GetUserCall( &_rSdrObj )->GetAnchoredObj( &_rSdrObj ); + // --> #i52904# + mbOldObjPositioningInProgress = mpAnchoredObj->IsPositioningInProgress(); + mpAnchoredObj->SetPositioningInProgress( true ); +} +SwObjPositioningInProgress::SwObjPositioningInProgress( SwAnchoredObject& _rAnchoredObj ) : + mpAnchoredObj( &_rAnchoredObj ), + // --> #i52904# + mbOldObjPositioningInProgress( false ) +{ + // --> #i52904# + mbOldObjPositioningInProgress = mpAnchoredObj->IsPositioningInProgress(); + mpAnchoredObj->SetPositioningInProgress( true ); +} + +SwObjPositioningInProgress::~SwObjPositioningInProgress() +{ + if ( mpAnchoredObj ) + { + // --> #i52904# + mpAnchoredObj->SetPositioningInProgress( mbOldObjPositioningInProgress ); + } +} + + +SwAnchoredObject::SwAnchoredObject() : + mpDrawObj( nullptr ), + mpAnchorFrame( nullptr ), + // --> #i28701# + mpPageFrame( nullptr ), + maRelPos(), + maLastCharRect(), + mnLastTopOfLine( 0 ), + mpVertPosOrientFrame( nullptr ), + // --> #i28701# + mbPositioningInProgress( false ), + mbConsiderForTextWrap( false ), + mbPositionLocked( false ), + // --> #i40147# + mbKeepPositionLockedForSection( false ), + mbRestartLayoutProcess( false ), + // --> #i35911# + mbClearedEnvironment( false ), + // --> #i3317# + mbTmpConsiderWrapInfluence( false ), + // --> #i68520# + maObjRectWithSpaces(), + mbObjRectWithSpacesValid( false ), + maLastObjRect() +{ +} + +void SwAnchoredObject::ClearVertPosOrientFrame() +{ + if (mpVertPosOrientFrame) + { + const_cast<SwLayoutFrame*>(mpVertPosOrientFrame)->ClearVertPosOrientFrameFor(this); + mpVertPosOrientFrame = nullptr; + } +} + +SwAnchoredObject::~SwAnchoredObject() +{ + ClearVertPosOrientFrame(); +} + +void SwAnchoredObject::SetDrawObj( SdrObject& _rDrawObj ) +{ + mpDrawObj = &_rDrawObj; +} + + +void SwAnchoredObject::ChgAnchorFrame( SwFrame* _pNewAnchorFrame ) +{ + mpAnchorFrame = _pNewAnchorFrame; + + if ( mpAnchorFrame ) + { + ObjectAttachedToAnchorFrame(); + } +} + +/** determine anchor frame containing the anchor position + + #i26945# + the anchor frame, which is determined, is <mpAnchorFrame> + for an at-page, at-frame or at-paragraph anchored object + and the anchor character frame for an at-character and as-character + anchored object. +*/ +SwFrame* SwAnchoredObject::GetAnchorFrameContainingAnchPos() +{ + SwFrame* pAnchorFrameContainingAnchPos = FindAnchorCharFrame(); + if ( !pAnchorFrameContainingAnchPos ) + { + pAnchorFrameContainingAnchPos = AnchorFrame(); + } + + return pAnchorFrameContainingAnchPos; +} + + +void SwAnchoredObject::SetPageFrame( SwPageFrame* _pNewPageFrame ) +{ + if ( mpPageFrame != _pNewPageFrame ) + { + // clear member, which denotes the layout frame at which the vertical + // position is oriented at, if it doesn't fit to the new page frame. + if ( GetVertPosOrientFrame() && + ( !_pNewPageFrame || + _pNewPageFrame != GetVertPosOrientFrame()->FindPageFrame() ) ) + { + ClearVertPosOrientFrame(); + } + + // assign new page frame + mpPageFrame = _pNewPageFrame; + } +} + + +SwTwips SwAnchoredObject::GetRelCharX( const SwFrame* pFrame ) const +{ + return maLastCharRect.Left() - pFrame->getFrameArea().Left(); +} + +SwTwips SwAnchoredObject::GetRelCharY( const SwFrame* pFrame ) const +{ + return maLastCharRect.Bottom() - pFrame->getFrameArea().Top(); +} + +void SwAnchoredObject::AddLastCharY( long nDiff ) +{ + maLastCharRect.Pos().AdjustY(nDiff ); +} + +void SwAnchoredObject::ResetLastCharRectHeight() +{ + maLastCharRect.Height( 0 ); +} + +void SwAnchoredObject::SetVertPosOrientFrame( const SwLayoutFrame& _rVertPosOrientFrame ) +{ + ClearVertPosOrientFrame(); + + mpVertPosOrientFrame = &_rVertPosOrientFrame; + const_cast<SwLayoutFrame*>(mpVertPosOrientFrame)->SetVertPosOrientFrameFor(this); + + // #i28701# - take over functionality of deleted method + // <SwFlyAtContentFrame::AssertPage()>: assure for at-paragraph and at-character + // an anchored object, that it is registered at the correct page frame + RegisterAtCorrectPage(); +} + + +// #i28701# - follow-up of #i22341# +void SwAnchoredObject::AddLastTopOfLineY( SwTwips _nDiff ) +{ + mnLastTopOfLine += _nDiff; +} + +/** check anchor character rectangle and top of line + + #i26791 + For to-character anchored Writer fly frames the members <maLastCharRect> + and <maLastTopOfLine> are updated. These are checked for change and + depending on the applied positioning, it's decided, if the Writer fly + frame has to be invalidated. + + add parameter <_bCheckForParaPorInf>, default value <true> +*/ +void SwAnchoredObject::CheckCharRectAndTopOfLine( + const bool _bCheckForParaPorInf ) +{ + if ( GetAnchorFrame() && + GetAnchorFrame()->IsTextFrame() ) + { + const SwFormatAnchor& rAnch = GetFrameFormat().GetAnchor(); + if ( (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) && + rAnch.GetContentAnchor() ) + { + // --> if requested, assure that anchor frame, + // which contains the anchor character, has a paragraph portion information. + // The paragraph portion information is needed to determine the + // anchor character rectangle respectively the top of the line. + // Thus, a format of this frame is avoided to determine the + // paragraph portion information. + // --> #i26945# - use new method <FindAnchorCharFrame()> + const SwTextFrame& aAnchorCharFrame = *(FindAnchorCharFrame()); + if ( !_bCheckForParaPorInf || aAnchorCharFrame.HasPara() ) + { + CheckCharRect( rAnch, aAnchorCharFrame ); + CheckTopOfLine( rAnch, aAnchorCharFrame ); + } + } + } +} + +/** check anchor character rectangle + + #i22341# + helper method for method <CheckCharRectAndTopOfLine()> + For to-character anchored Writer fly frames the member <maLastCharRect> + is updated. This is checked for change and depending on the applied + positioning, it's decided, if the Writer fly frame has to be invalidated. + + improvement - add second parameter <_rAnchorCharFrame> +*/ +void SwAnchoredObject::CheckCharRect( const SwFormatAnchor& _rAnch, + const SwTextFrame& _rAnchorCharFrame ) +{ + // determine rectangle of anchor character. If not exist, abort operation + SwRect aCharRect; + if ( !_rAnchorCharFrame.GetAutoPos( aCharRect, *_rAnch.GetContentAnchor() ) ) + { + return; + } + // check, if anchor character rectangle has changed + if ( aCharRect != maLastCharRect ) + { + // check positioning and alignment for invalidation of position + { + SwRectFnSet aRectFnSet(&_rAnchorCharFrame); + // determine positioning and alignment + SwFormatVertOrient aVert( GetFrameFormat().GetVertOrient() ); + SwFormatHoriOrient aHori( GetFrameFormat().GetHoriOrient() ); + // check for anchor character rectangle changes for certain + // positionings and alignments + // add condition to invalidate position, + // if vertical aligned at frame/page area and vertical position + // of anchor character has changed. + const sal_Int16 eVertRelOrient = aVert.GetRelationOrient(); + if ( ( aHori.GetRelationOrient() == text::RelOrientation::CHAR && + aRectFnSet.GetLeft(aCharRect) != aRectFnSet.GetLeft(maLastCharRect) ) || + ( eVertRelOrient == text::RelOrientation::CHAR && + ( aRectFnSet.GetTop(aCharRect) != aRectFnSet.GetTop(maLastCharRect) || + aRectFnSet.GetHeight(aCharRect) != aRectFnSet.GetHeight(maLastCharRect) ) ) || + ( ( ( eVertRelOrient == text::RelOrientation::FRAME ) || + ( eVertRelOrient == text::RelOrientation::PRINT_AREA ) || + ( eVertRelOrient == text::RelOrientation::PAGE_FRAME ) || + ( eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA ) ) && + ( aRectFnSet.GetTop(aCharRect) != aRectFnSet.GetTop(maLastCharRect) ) ) ) + { + // #i26945#, #i35911# - unlock position of + // anchored object, if it isn't registered at the page, + // where its anchor character frame is on. + if ( GetPageFrame() != _rAnchorCharFrame.FindPageFrame() ) + { + UnlockPosition(); + } + InvalidateObjPos(); + } + } + // keep new anchor character rectangle + maLastCharRect = aCharRect; + } +} + +/** check top of line + + #i22341# + helper method for method <CheckCharRectAndTopOfLine()> + For to-character anchored Writer fly frames the member <mnLastTopOfLine> + is updated. This is checked for change and depending on the applied + positioning, it's decided, if the Writer fly frame has to be invalidated. + + improvement - add second parameter <_rAnchorCharFrame> +*/ +void SwAnchoredObject::CheckTopOfLine( const SwFormatAnchor& _rAnch, + const SwTextFrame& _rAnchorCharFrame ) +{ + SwTwips nTopOfLine = 0; + if ( _rAnchorCharFrame.GetTopOfLine( nTopOfLine, *_rAnch.GetContentAnchor() ) ) + { + if ( nTopOfLine != mnLastTopOfLine ) + { + // check alignment for invalidation of position + if ( GetFrameFormat().GetVertOrient().GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + // #i26945#, #i35911# - unlock position of + // anchored object, if it isn't registered at the page, + // where its anchor character frame is on. + if ( GetPageFrame() != _rAnchorCharFrame.FindPageFrame() ) + { + UnlockPosition(); + } + InvalidateObjPos(); + } + // keep new top of line value + mnLastTopOfLine = nTopOfLine; + } + } +} + +void SwAnchoredObject::ClearCharRectAndTopOfLine() +{ + maLastCharRect.Clear(); + mnLastTopOfLine = 0; +} + +void SwAnchoredObject::SetCurrRelPos( Point _aRelPos ) +{ + maRelPos = _aRelPos; +} + +void SwAnchoredObject::ObjectAttachedToAnchorFrame() +{ + // default behaviour: + // update layout direction, the anchored object is assigned to + UpdateLayoutDir(); +} + +/** method update layout direction the layout direction, the anchored + object is in + + #i31698# + method has typically to be called, if the anchored object gets its + anchor frame assigned. +*/ +void SwAnchoredObject::UpdateLayoutDir() +{ + SwFrameFormat::tLayoutDir nLayoutDir = SwFrameFormat::HORI_L2R; + const SwFrame* pAnchorFrame = GetAnchorFrame(); + if ( pAnchorFrame ) + { + const bool bVert = pAnchorFrame->IsVertical(); + const bool bR2L = pAnchorFrame->IsRightToLeft(); + if ( bVert ) + { + nLayoutDir = SwFrameFormat::VERT_R2L; + } + else if ( bR2L ) + { + nLayoutDir = SwFrameFormat::HORI_R2L; + } + } + GetFrameFormat().SetLayoutDir( nLayoutDir ); +} + +/** method to perform necessary invalidations for the positioning of + objects, for whose the wrapping style influence has to be considered + on the object positioning. + + #i28701# +*/ +void SwAnchoredObject::InvalidateObjPosForConsiderWrapInfluence() +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + { + // indicate that object has not to be considered for text wrap + SetConsiderForTextWrap( false ); + // unlock position + UnlockPosition(); + // invalidate position + InvalidateObjPos(); + // invalidate 'background' + NotifyBackground( GetPageFrame(), GetObjRectWithSpaces(), PrepareHint::FlyFrameLeave ); + } +} + +/** method to determine, if wrapping style influence of the anchored + object has to be considered on the object positioning + + #i28701# + Note: result of this method also decides, if the booleans for the + layout process are of relevance. +*/ +bool SwAnchoredObject::ConsiderObjWrapInfluenceOnObjPos() const +{ + bool bRet( false ); + + const SwFrameFormat& rObjFormat = GetFrameFormat(); + + // --> #i3317# - add condition <IsTmpConsiderWrapInfluence()> + // --> #i55204# + // - correction: wrapping style influence has been considered, if condition + // <IsTmpConsiderWrapInfluence()> is hold, regardless of its anchor type + // or its wrapping style. + if ( IsTmpConsiderWrapInfluence() ) + { + bRet = true; + } + else if ( rObjFormat.getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + const SwFormatAnchor& rAnchor = rObjFormat.GetAnchor(); + if ( ((rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA)) && + rObjFormat.GetSurround().GetSurround() != css::text::WrapTextMode_THROUGH ) + { + // --> #i34520# - text also wraps around anchored + // objects in the layer Hell - see the text formatting. + // Thus, it hasn't to be checked here. + bRet = true; + } + } + + return bRet; +} + +/** method to determine, if other anchored objects, also attached at + to the anchor frame, have to consider its wrap influence. + + // --> #i43255# +*/ +bool SwAnchoredObject::ConsiderObjWrapInfluenceOfOtherObjs() const +{ + bool bRet( false ); + + const SwSortedObjs* pObjs = GetAnchorFrame()->GetDrawObjs(); + if ( pObjs->size() > 1 ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj != this && + pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + { + bRet = true; + break; + } + } + } + + return bRet; +} + +bool SwAnchoredObject::ConsiderForTextWrap() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbConsiderForTextWrap; + else + return true; +} + +void SwAnchoredObject::SetConsiderForTextWrap( const bool _bConsiderForTextWrap ) +{ + mbConsiderForTextWrap = _bConsiderForTextWrap; +} + +bool SwAnchoredObject::PositionLocked() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbPositionLocked; + else + return false; +} + +bool SwAnchoredObject::RestartLayoutProcess() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbRestartLayoutProcess; + else + return false; +} + +void SwAnchoredObject::SetRestartLayoutProcess( const bool _bRestartLayoutProcess ) +{ + mbRestartLayoutProcess = _bRestartLayoutProcess; +} + +// --> #i35911# +bool SwAnchoredObject::ClearedEnvironment() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbClearedEnvironment; + else + return false; +} +void SwAnchoredObject::SetClearedEnvironment( const bool _bClearedEnvironment ) +{ + mbClearedEnvironment = _bClearedEnvironment; +} + +/** method to determine, if due to anchored object size and wrapping + style, its layout environment is cleared. + + #i35911# +*/ +bool SwAnchoredObject::HasClearedEnvironment() const +{ + bool bHasClearedEnvironment( false ); + + // --> #i43913# - layout frame, vertical position is orient at, has to be set. + OSL_ENSURE( GetVertPosOrientFrame(), + "<SwAnchoredObject::HasClearedEnvironment()> - layout frame missing, at which the vertical position is oriented at." ); + if ( GetVertPosOrientFrame() && + GetAnchorFrame()->IsTextFrame() && + !static_cast<const SwTextFrame*>(GetAnchorFrame())->IsFollow() && + static_cast<const SwTextFrame*>(GetAnchorFrame())->FindPageFrame()->GetPhyPageNum() >= + GetPageFrame()->GetPhyPageNum() ) + { + const SwFrame* pTmpFrame = GetVertPosOrientFrame()->Lower(); + while ( pTmpFrame && pTmpFrame->IsLayoutFrame() && !pTmpFrame->IsTabFrame() ) + { + pTmpFrame = static_cast<const SwLayoutFrame*>(pTmpFrame)->Lower(); + } + if ( !pTmpFrame ) + { + bHasClearedEnvironment = true; + } + else if ( pTmpFrame->IsTextFrame() && !pTmpFrame->GetNext() ) + { + const SwTextFrame* pTmpTextFrame = static_cast<const SwTextFrame*>(pTmpFrame); + if ( pTmpTextFrame->IsUndersized() || + ( pTmpTextFrame->GetFollow() && + pTmpTextFrame->GetFollow()->GetOffset() == TextFrameIndex(0))) + { + bHasClearedEnvironment = true; + } + } + } + + return bHasClearedEnvironment; +} + +/** method to add spacing to object area + + #i28701# + #i68520# - return constant reference and use cache +*/ +const SwRect& SwAnchoredObject::GetObjRectWithSpaces() const +{ + if ( mbObjRectWithSpacesValid && + maLastObjRect != GetObjRect() ) + { + OSL_FAIL( "<SwAnchoredObject::GetObjRectWithSpaces> - cache for object rectangle inclusive spaces marked as valid, but it couldn't be. Missing invalidation of cache." ); + InvalidateObjRectWithSpaces(); + } + if ( !mbObjRectWithSpacesValid ) + { + maObjRectWithSpaces = GetObjBoundRect(); + const SwFrameFormat& rFormat = GetFrameFormat(); + const SvxULSpaceItem& rUL = rFormat.GetULSpace(); + const SvxLRSpaceItem& rLR = rFormat.GetLRSpace(); + { + maObjRectWithSpaces.Top ( std::max( maObjRectWithSpaces.Top() - long(rUL.GetUpper()), 0L )); + maObjRectWithSpaces.Left( std::max( maObjRectWithSpaces.Left()- rLR.GetLeft(), 0L )); + maObjRectWithSpaces.AddHeight(rUL.GetLower() ); + maObjRectWithSpaces.AddWidth(rLR.GetRight() ); + } + + mbObjRectWithSpacesValid = true; + maLastObjRect = GetObjRect(); + } + + return maObjRectWithSpaces; +} + +// --> #i68520# +void SwAnchoredObject::SetObjTop( const SwTwips _nTop) +{ + const bool bTopChanged( SetObjTop_( _nTop ) ); + if ( bTopChanged ) + { + mbObjRectWithSpacesValid = false; + } +} + +void SwAnchoredObject::SetObjLeft( const SwTwips _nLeft) +{ + const bool bLeftChanged( SetObjLeft_( _nLeft ) ); + if ( bLeftChanged ) + { + mbObjRectWithSpacesValid = false; + } +} + +/** method to update anchored object in the <SwSortedObjs> lists + + #i28701# + If document compatibility option 'Consider wrapping style influence + on object positioning' is ON, additionally all anchored objects + at the anchor frame and all following anchored objects on the page + frame are invalidated. +*/ +void SwAnchoredObject::UpdateObjInSortedList() +{ + if ( GetAnchorFrame() ) + { + if ( GetFrameFormat().getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + // invalidate position of all anchored objects at anchor frame + if ( GetAnchorFrame()->GetDrawObjs() ) + { + const SwSortedObjs* pObjs = GetAnchorFrame()->GetDrawObjs(); + // determine start index + for (auto it = pObjs->begin(); it != pObjs->end(); ++it) + { + SwAnchoredObject* pAnchoredObj = *it; + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + else + pAnchoredObj->InvalidateObjPos(); + } + } + // invalidate all following anchored objects on the page frame + if ( GetPageFrame() && GetPageFrame()->GetSortedObjs() ) + { + const SwSortedObjs* pObjs = GetPageFrame()->GetSortedObjs(); + // determine start index + for ( size_t i = pObjs->ListPosOf( *this ) + 1; i < pObjs->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + else + pAnchoredObj->InvalidateObjPos(); + } + } + } + // update its position in the sorted object list of its anchor frame + AnchorFrame()->GetDrawObjs()->Update( *this ); + // update its position in the sorted object list of its page frame + // note: as-character anchored object aren't registered at a page frame + if ( GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR ) + { + GetPageFrame()->GetSortedObjs()->Update( *this ); + } + } +} + +/** method to determine, if invalidation of position is allowed + + #i28701# +*/ +bool SwAnchoredObject::InvalidationOfPosAllowed() const +{ + // --> Check, if page frame layout is in progress, + // isn't needed, because of anchored object, whose are moved forward. + return !PositionLocked(); +} + +/** method to determine the page frame, on which the 'anchor' of + the given anchored object is. + + #i28701# + #i33751#, #i34060# + Adjust meaning of method and thus its name: If the anchored object + or its anchor isn't correctly inserted in the layout, no page frame + can be found. Thus, the return type changed to be a pointer and can + be NULL. +*/ +SwPageFrame* SwAnchoredObject::FindPageFrameOfAnchor() +{ + SwPageFrame* pRetPageFrame = nullptr; + + // --> #i44339# - check, if anchor frame exists. + if ( mpAnchorFrame ) + { + // --> #i26945# - use new method <GetAnchorFrameContainingAnchPos()> + pRetPageFrame = GetAnchorFrameContainingAnchPos()->FindPageFrame(); + } + + return pRetPageFrame; +} + +/** get frame, which contains the anchor character, if the object + is anchored at-character or as-character. + + #i26945# + + @return SwTextFrame* + text frame containing the anchor character. It's NULL, if the object + isn't anchored at-character resp. as-character. +*/ +SwTextFrame* SwAnchoredObject::FindAnchorCharFrame() +{ + SwTextFrame* pAnchorCharFrame( nullptr ); + + // --> #i44339# - check, if anchor frame exists. + if ( mpAnchorFrame ) + { + const SwFormatAnchor& rAnch = GetFrameFormat().GetAnchor(); + if ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR)) + { + SwTextFrame *const pFrame(static_cast<SwTextFrame*>(AnchorFrame())); + TextFrameIndex const nOffset(pFrame->MapModelToViewPos(*rAnch.GetContentAnchor())); + pAnchorCharFrame = &pFrame->GetFrameAtOfst(nOffset); + } + } + + return pAnchorCharFrame; +} + +/** method to determine, if a format on the anchored object is possible + + #i28701# + A format is possible, if anchored object is in an invisible layer. + Note: method is virtual to refine the conditions for the sub-classes. +*/ +bool SwAnchoredObject::IsFormatPossible() const +{ + return GetFrameFormat().GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetDrawObj()->GetLayer() ); +} + +// --> #i3317# +void SwAnchoredObject::SetTmpConsiderWrapInfluence( const bool _bTmpConsiderWrapInfluence ) +{ + mbTmpConsiderWrapInfluence = _bTmpConsiderWrapInfluence; + // --> #i35911# + if ( mbTmpConsiderWrapInfluence ) + { + SwLayouter::InsertObjForTmpConsiderWrapInfluence( *(GetFrameFormat().GetDoc()), + *this ); + } +} + +void SwAnchoredObject::ClearTmpConsiderWrapInfluence() +{ + mbTmpConsiderWrapInfluence = false; + mbClearedEnvironment = false; + SetClearedEnvironment( false ); + SwLayouter::RemoveObjForTmpConsiderWrapInfluence( *(GetFrameFormat().GetDoc()), + *this ); +} +void SwAnchoredObject::SetTmpConsiderWrapInfluenceOfOtherObjs() +{ + const SwSortedObjs* pObjs = GetAnchorFrame()->GetDrawObjs(); + if ( pObjs->size() > 1 ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj != this ) + { + pAnchoredObj->SetTmpConsiderWrapInfluence( true/*bTmpConsiderWrapInfluence*/ ); + } + } + } +} + +/** method to determine, if the anchored object is overlapping with a + previous column + + #i3317# + overlapping with a previous column means, that the object overlaps + with a column, which is a previous one of the column its anchor + frame is in. + Only applied for at-paragraph and at-character anchored objects. +*/ +bool SwAnchoredObject::OverlapsPrevColumn() const +{ + bool bOverlapsPrevColumn( false ); + + if ( mpAnchorFrame && mpAnchorFrame->IsTextFrame() ) + { + const SwFrame* pColFrame = mpAnchorFrame->FindColFrame(); + if ( pColFrame && pColFrame->GetPrev() ) + { + const SwFrame* pTmpColFrame = pColFrame->GetPrev(); + SwRect aChkRect; + while ( pTmpColFrame ) + { + aChkRect.Union( pTmpColFrame->getFrameArea() ); + pTmpColFrame = pTmpColFrame->GetPrev(); + } + bOverlapsPrevColumn = GetObjRect().IsOver( aChkRect ); + } + } + + return bOverlapsPrevColumn; +} + +/** method to determine position of anchored object relative to + anchor frame + + #i30669# + Usage: Needed layout information for WW8 export +*/ +Point SwAnchoredObject::GetRelPosToAnchorFrame() const +{ + Point aRelPos; + + assert(GetAnchorFrame()); + aRelPos = GetObjRect().Pos(); + aRelPos -= GetAnchorFrame()->getFrameArea().Pos(); + + return aRelPos; +} + +/** method to determine position of anchored object relative to + page frame + + #i30669# + Usage: Needed layout information for WW8 export + #i33818# - add parameters <_bFollowTextFlow> and + <_obRelToTableCell> + If <_bFollowTextFlow> is set and object is anchored inside table, + the position relative to the table cell is determined. Output + parameter <_obRelToTableCell> reflects this situation +*/ +Point SwAnchoredObject::GetRelPosToPageFrame( const bool _bFollowTextFlow, + bool& _obRelToTableCell ) const +{ + Point aRelPos; + _obRelToTableCell = false; + + assert(GetAnchorFrame()); + assert(GetAnchorFrame()->FindPageFrame()); + + aRelPos = GetObjRect().Pos(); + // --> #i33818# - search for cell frame, if object has to + // follow the text flow. + const SwFrame* pFrame( nullptr ); + if ( _bFollowTextFlow && !GetAnchorFrame()->IsPageFrame() ) + { + pFrame = GetAnchorFrame()->GetUpper(); + while ( !pFrame->IsCellFrame() && !pFrame->IsPageFrame() ) + { + pFrame = pFrame->GetUpper(); + } + } + else + { + pFrame = GetAnchorFrame()->FindPageFrame(); + } + if ( pFrame->IsCellFrame() ) + { + aRelPos -= pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); + _obRelToTableCell = true; + } + else + { + aRelPos -= pFrame->getFrameArea().Pos(); + } + + return aRelPos; +} + +/** method to determine position of anchored object relative to + anchor character + + #i30669# + Usage: Needed layout information for WW8 export +*/ +Point SwAnchoredObject::GetRelPosToChar() const +{ + Point aRelPos = GetObjRect().Pos(); + aRelPos -= GetLastCharRect().Pos(); + + return aRelPos; +} + +/** method to determine position of anchored object relative to + top of line + + #i30669# + Usage: Needed layout information for WW8 export +*/ +Point SwAnchoredObject::GetRelPosToLine() const +{ + Point aRelPos = GetObjRect().Pos(); + aRelPos.AdjustY( -(GetLastTopOfLine()) ); + + return aRelPos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/atrfrm.cxx b/sw/source/core/layout/atrfrm.cxx new file mode 100644 index 000000000..d9f37a669 --- /dev/null +++ b/sw/source/core/layout/atrfrm.cxx @@ -0,0 +1,3656 @@ +/* -*- 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 <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/text/TextGridMode.hpp> +#include <sal/log.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <svtools/unoimap.hxx> +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <unotools/intlwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <frmfmt.hxx> +#include <unocoll.hxx> +#include <unosett.hxx> +#include <fmtclds.hxx> +#include <fmtornt.hxx> +#include <fmthdft.hxx> +#include <fmtpdsc.hxx> +#include <fmtcntnt.hxx> +#include <fmtfsize.hxx> +#include <fmtfordr.hxx> +#include <fmtsrnd.hxx> +#include <fmtlsplt.hxx> +#include <fmtrowsplt.hxx> +#include <fmtftntx.hxx> +#include <fmteiro.hxx> +#include <fmturl.hxx> +#include <fmtcnct.hxx> +#include <section.hxx> +#include <fmtline.hxx> +#include <tgrditem.hxx> +#include <hfspacingitem.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <notxtfrm.hxx> +#include <txtfrm.hxx> +#include <crsrsh.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <frmtool.hxx> +#include <flyfrms.hxx> +#include <pagedesc.hxx> +#include <grfatr.hxx> +#include <ndnotxt.hxx> +#include <docary.hxx> +#include <node2lay.hxx> +#include <fmtclbl.hxx> +#include <swunohelper.hxx> +#include <unoframe.hxx> +#include <SwStyleNameMapper.hxx> +#include <editeng/brushitem.hxx> +#include <vcl/GraphicObject.hxx> +#include <unomid.h> +#include <strings.hrc> +#include <svx/svdundo.hxx> +#include <sortedobjs.hxx> +#include <HandleAnchorNodeChg.hxx> +#include <calbck.hxx> +#include <pagedeschint.hxx> +#include <drawdoc.hxx> +#include <hints.hxx> +#include <frameformats.hxx> + +#include <ndtxt.hxx> + +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <svl/itemiter.hxx> +#include <wrtsh.hxx> +#include <txtfld.hxx> + +using namespace ::com::sun::star; + + +namespace sw { + +bool GetAtPageRelOrientation(sal_Int16 & rOrientation, bool const isIgnorePrintArea) +{ + switch (rOrientation) + { + case text::RelOrientation::CHAR: + case text::RelOrientation::FRAME: + rOrientation = text::RelOrientation::PAGE_FRAME; + return true; + case text::RelOrientation::PRINT_AREA: + if (isIgnorePrintArea) + { + return false; + } + else + { + rOrientation = text::RelOrientation::PAGE_PRINT_AREA; + return true; + } + case text::RelOrientation::FRAME_LEFT: + rOrientation = text::RelOrientation::PAGE_LEFT; + return true; + case text::RelOrientation::FRAME_RIGHT: + rOrientation = text::RelOrientation::PAGE_RIGHT; + return true; + default: + return false; + } +} + +} // namespace sw + +SfxPoolItem* SwFormatLineNumber::CreateDefault() { return new SwFormatLineNumber; } + +static sal_Int16 lcl_IntToRelation(const uno::Any& rVal) +{ + sal_Int16 nVal = text::RelOrientation::FRAME; + if (!(rVal >>= nVal)) + SAL_WARN("sw.core", "lcl_IntToRelation: read from Any failed!"); + return nVal; +} + +static void lcl_DelHFFormat( SwClient *pToRemove, SwFrameFormat *pFormat ) +{ + //If the client is the last one who uses this format, then we have to delete + //it - before this is done, we may need to delete the content-section. + SwDoc* pDoc = pFormat->GetDoc(); + pFormat->Remove( pToRemove ); + if( pDoc->IsInDtor() ) + { + delete pFormat; + return; + } + + // Anything other than frames registered? + bool bDel = true; + { + // nested scope because DTOR of SwClientIter resets the flag bTreeChg. + // It's suboptimal if the format is deleted beforehand. + SwIterator<SwClient,SwFrameFormat> aIter(*pFormat); + for(SwClient* pLast = aIter.First(); bDel && pLast; pLast = aIter.Next()) + if (dynamic_cast<const SwFrame*>(pLast) == nullptr) + bDel = false; + } + + if ( bDel ) + { + // If there is a Cursor registered in one of the nodes, we need to call the + // ParkCursor in an (arbitrary) shell. + SwFormatContent& rCnt = const_cast<SwFormatContent&>(pFormat->GetContent()); + if ( rCnt.GetContentIdx() ) + { + SwNode *pNode = nullptr; + { + // #i92993# + // Begin with start node of page header/footer to assure that + // complete content is checked for cursors and the complete content + // is deleted on below made method call <pDoc->getIDocumentContentOperations().DeleteSection(pNode)> + SwNodeIndex aIdx( *rCnt.GetContentIdx(), 0 ); + // If there is a Cursor registered in one of the nodes, we need to call the + // ParkCursor in an (arbitrary) shell. + pNode = & aIdx.GetNode(); + sal_uInt32 nEnd = pNode->EndOfSectionIndex(); + while ( aIdx < nEnd ) + { + if ( pNode->IsContentNode() && + static_cast<SwContentNode*>(pNode)->HasWriterListeners() ) + { + SwCursorShell *pShell = SwIterator<SwCursorShell,SwContentNode>( *static_cast<SwContentNode*>(pNode) ).First(); + if( pShell ) + { + pShell->ParkCursor( aIdx ); + aIdx = nEnd-1; + } + } + ++aIdx; + pNode = & aIdx.GetNode(); + } + } + rCnt.SetNewContentIdx( nullptr ); + + // When deleting a header/footer-format, we ALWAYS need to disable + // the undo function (Bug 31069) + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + OSL_ENSURE( pNode, "A big problem." ); + pDoc->getIDocumentContentOperations().DeleteSection( pNode ); + } + delete pFormat; + } +} + +void SwFormatFrameSize::ScaleMetrics(long lMult, long lDiv) { + // Don't inherit the SvxSizeItem override (might or might not be relevant; added "just in case" + // when changing SwFormatFrameSize to derive from SvxSizeItem instead of directly from + // SfxPoolItem): + return SfxPoolItem::ScaleMetrics(lMult, lDiv); +} + +bool SwFormatFrameSize::HasMetrics() const { + // Don't inherit the SvxSizeItem override (might or might not be relevant; added "just in case" + // when changing SwFormatFrameSize to derive from SvxSizeItem instead of directly from + // SfxPoolItem): + return SfxPoolItem::HasMetrics(); +} + +// Partially implemented inline in hxx +SwFormatFrameSize::SwFormatFrameSize( SwFrameSize eSize, SwTwips nWidth, SwTwips nHeight ) + : SvxSizeItem( RES_FRM_SIZE, {nWidth, nHeight} ), + m_eFrameHeightType( eSize ), + m_eFrameWidthType( SwFrameSize::Fixed ) +{ + m_nWidthPercent = m_eWidthPercentRelation = m_nHeightPercent = m_eHeightPercentRelation = 0; +} + +bool SwFormatFrameSize::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return( m_eFrameHeightType == static_cast<const SwFormatFrameSize&>(rAttr).m_eFrameHeightType && + m_eFrameWidthType == static_cast<const SwFormatFrameSize&>(rAttr).m_eFrameWidthType && + SvxSizeItem::operator==(rAttr)&& + m_nWidthPercent == static_cast<const SwFormatFrameSize&>(rAttr).GetWidthPercent() && + m_eWidthPercentRelation == static_cast<const SwFormatFrameSize&>(rAttr).GetWidthPercentRelation() && + m_nHeightPercent == static_cast<const SwFormatFrameSize&>(rAttr).GetHeightPercent() && + m_eHeightPercentRelation == static_cast<const SwFormatFrameSize&>(rAttr).GetHeightPercentRelation() ); +} + +SwFormatFrameSize* SwFormatFrameSize::Clone( SfxItemPool* ) const +{ + return new SwFormatFrameSize( *this ); +} + +bool SwFormatFrameSize::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_FRMSIZE_SIZE: + { + awt::Size aTmp; + aTmp.Height = convertTwipToMm100(GetHeight()); + aTmp.Width = convertTwipToMm100(GetWidth()); + rVal <<= aTmp; + } + break; + case MID_FRMSIZE_REL_HEIGHT: + rVal <<= static_cast<sal_Int16>(GetHeightPercent() != SwFormatFrameSize::SYNCED ? GetHeightPercent() : 0); + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + rVal <<= GetHeightPercentRelation(); + break; + case MID_FRMSIZE_REL_WIDTH: + rVal <<= static_cast<sal_Int16>(GetWidthPercent() != SwFormatFrameSize::SYNCED ? GetWidthPercent() : 0); + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + rVal <<= GetWidthPercentRelation(); + break; + case MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH: + rVal <<= SwFormatFrameSize::SYNCED == GetHeightPercent(); + break; + case MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT: + rVal <<= SwFormatFrameSize::SYNCED == GetWidthPercent(); + break; + case MID_FRMSIZE_WIDTH : + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetWidth())); + break; + case MID_FRMSIZE_HEIGHT: + // #95848# returned size should never be zero. + // (there was a bug that allowed for setting height to 0. + // Thus there some documents existing with that not allowed + // attribute value which may cause problems on import.) + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetHeight() < MINLAY ? MINLAY : GetHeight() )); + break; + case MID_FRMSIZE_SIZE_TYPE: + rVal <<= static_cast<sal_Int16>(GetHeightSizeType()); + break; + case MID_FRMSIZE_IS_AUTO_HEIGHT: + rVal <<= SwFrameSize::Fixed != GetHeightSizeType(); + break; + case MID_FRMSIZE_WIDTH_TYPE: + rVal <<= static_cast<sal_Int16>(GetWidthSizeType()); + break; + } + return true; +} + +bool SwFormatFrameSize::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_FRMSIZE_SIZE: + { + awt::Size aVal; + if(!(rVal >>= aVal)) + bRet = false; + else + { + Size aTmp(aVal.Width, aVal.Height); + if(bConvert) + { + aTmp.setHeight( convertMm100ToTwip(aTmp.Height()) ); + aTmp.setWidth( convertMm100ToTwip(aTmp.Width()) ); + } + SetSize(aTmp); + } + } + break; + case MID_FRMSIZE_REL_HEIGHT: + { + sal_Int16 nSet = 0; + rVal >>= nSet; + if(nSet >= 0 && nSet < SwFormatFrameSize::SYNCED) + SetHeightPercent(static_cast<sal_uInt8>(nSet)); + else + bRet = false; + } + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + { + sal_Int16 eSet = 0; + rVal >>= eSet; + SetHeightPercentRelation(eSet); + } + break; + case MID_FRMSIZE_REL_WIDTH: + { + sal_Int16 nSet = 0; + rVal >>= nSet; + if(nSet >= 0 && nSet < SwFormatFrameSize::SYNCED) + SetWidthPercent(static_cast<sal_uInt8>(nSet)); + else + bRet = false; + } + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + { + sal_Int16 eSet = 0; + rVal >>= eSet; + SetWidthPercentRelation(eSet); + } + break; + case MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH: + { + bool bSet = *o3tl::doAccess<bool>(rVal); + if(bSet) + SetHeightPercent(SwFormatFrameSize::SYNCED); + else if( SwFormatFrameSize::SYNCED == GetHeightPercent() ) + SetHeightPercent( 0 ); + } + break; + case MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT: + { + bool bSet = *o3tl::doAccess<bool>(rVal); + if(bSet) + SetWidthPercent(SwFormatFrameSize::SYNCED); + else if( SwFormatFrameSize::SYNCED == GetWidthPercent() ) + SetWidthPercent(0); + } + break; + case MID_FRMSIZE_WIDTH : + { + sal_Int32 nWd = 0; + if(rVal >>= nWd) + { + if(bConvert) + nWd = convertMm100ToTwip(nWd); + if(nWd < MINLAY) + nWd = MINLAY; + SetWidth(nWd); + } + else + bRet = false; + } + break; + case MID_FRMSIZE_HEIGHT: + { + sal_Int32 nHg = 0; + if(rVal >>= nHg) + { + if(bConvert) + nHg = convertMm100ToTwip(nHg); + if(nHg < MINLAY) + nHg = MINLAY; + SetHeight(nHg); + } + else + bRet = false; + } + break; + case MID_FRMSIZE_SIZE_TYPE: + { + sal_Int16 nType = 0; + if((rVal >>= nType) && nType >= 0 && nType <= static_cast<int>(SwFrameSize::Minimum) ) + { + SetHeightSizeType(static_cast<SwFrameSize>(nType)); + } + else + bRet = false; + } + break; + case MID_FRMSIZE_IS_AUTO_HEIGHT: + { + bool bSet = *o3tl::doAccess<bool>(rVal); + SetHeightSizeType(bSet ? SwFrameSize::Variable : SwFrameSize::Fixed); + } + break; + case MID_FRMSIZE_WIDTH_TYPE: + { + sal_Int16 nType = 0; + if((rVal >>= nType) && nType >= 0 && nType <= static_cast<int>(SwFrameSize::Minimum) ) + { + SetWidthSizeType(static_cast<SwFrameSize>(nType)); + } + else + bRet = false; + } + break; + default: + bRet = false; + } + return bRet; +} + +void SwFormatFrameSize::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatFrameSize")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + + std::stringstream aSize; + aSize << GetSize(); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(aSize.str().c_str())); + + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eFrameHeightType"), BAD_CAST(OString::number(static_cast<int>(m_eFrameHeightType)).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eFrameWidthType"), BAD_CAST(OString::number(static_cast<int>(m_eFrameWidthType)).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidthPercent"), BAD_CAST(OString::number(m_nWidthPercent).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eWidthPercentRelation"), BAD_CAST(OString::number(m_eWidthPercentRelation).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nHeightPercent"), BAD_CAST(OString::number(m_nHeightPercent).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eHeightPercentRelation"), BAD_CAST(OString::number(m_eHeightPercentRelation).getStr())); + + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatFillOrder::SwFormatFillOrder( SwFillOrder nFO ) + : SfxEnumItem( RES_FILL_ORDER, nFO ) +{} + +SwFormatFillOrder* SwFormatFillOrder::Clone( SfxItemPool* ) const +{ + return new SwFormatFillOrder( GetValue() ); +} + +sal_uInt16 SwFormatFillOrder::GetValueCount() const +{ + return SW_FILL_ORDER_END - SW_FILL_ORDER_BEGIN; +} + +// Partially implemented inline in hxx +SwFormatHeader::SwFormatHeader( SwFrameFormat *pHeaderFormat ) + : SfxPoolItem( RES_HEADER ), + SwClient( pHeaderFormat ), + m_bActive( pHeaderFormat ) +{ +} + +SwFormatHeader::SwFormatHeader( const SwFormatHeader &rCpy ) + : SfxPoolItem( RES_HEADER ), + SwClient( const_cast<SwModify*>(rCpy.GetRegisteredIn()) ), + m_bActive( rCpy.IsActive() ) +{ +} + +SwFormatHeader::SwFormatHeader( bool bOn ) + : SfxPoolItem( RES_HEADER ), + SwClient( nullptr ), + m_bActive( bOn ) +{ +} + + SwFormatHeader::~SwFormatHeader() +{ + if ( GetHeaderFormat() ) + lcl_DelHFFormat( this, GetHeaderFormat() ); +} + +bool SwFormatHeader::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( GetRegisteredIn() == static_cast<const SwFormatHeader&>(rAttr).GetRegisteredIn() && + m_bActive == static_cast<const SwFormatHeader&>(rAttr).IsActive() ); +} + +SwFormatHeader* SwFormatHeader::Clone( SfxItemPool* ) const +{ + return new SwFormatHeader( *this ); +} + +void SwFormatHeader::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add(this); +} + +// Partially implemented inline in hxx +SwFormatFooter::SwFormatFooter( SwFrameFormat *pFooterFormat ) + : SfxPoolItem( RES_FOOTER ), + SwClient( pFooterFormat ), + m_bActive( pFooterFormat ) +{ +} + +SwFormatFooter::SwFormatFooter( const SwFormatFooter &rCpy ) + : SfxPoolItem( RES_FOOTER ), + SwClient( const_cast<SwModify*>(rCpy.GetRegisteredIn()) ), + m_bActive( rCpy.IsActive() ) +{ +} + +SwFormatFooter::SwFormatFooter( bool bOn ) + : SfxPoolItem( RES_FOOTER ), + SwClient( nullptr ), + m_bActive( bOn ) +{ +} + + SwFormatFooter::~SwFormatFooter() +{ + if ( GetFooterFormat() ) + lcl_DelHFFormat( this, GetFooterFormat() ); +} + +void SwFormatFooter::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add(this); +} + +bool SwFormatFooter::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( GetRegisteredIn() == static_cast<const SwFormatFooter&>(rAttr).GetRegisteredIn() && + m_bActive == static_cast<const SwFormatFooter&>(rAttr).IsActive() ); +} + +SwFormatFooter* SwFormatFooter::Clone( SfxItemPool* ) const +{ + return new SwFormatFooter( *this ); +} + +// Partially implemented inline in hxx +SwFormatContent::SwFormatContent( const SwFormatContent &rCpy ) + : SfxPoolItem( RES_CNTNT ) +{ + m_pStartNode.reset( rCpy.GetContentIdx() ? + new SwNodeIndex( *rCpy.GetContentIdx() ) : nullptr); +} + +SwFormatContent::SwFormatContent( const SwStartNode *pStartNd ) + : SfxPoolItem( RES_CNTNT ) +{ + m_pStartNode.reset( pStartNd ? new SwNodeIndex( *pStartNd ) : nullptr); +} + +SwFormatContent::~SwFormatContent() +{ +} + +void SwFormatContent::SetNewContentIdx( const SwNodeIndex *pIdx ) +{ + m_pStartNode.reset( pIdx ? new SwNodeIndex( *pIdx ) : nullptr ); +} + +bool SwFormatContent::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + if( static_cast<bool>(m_pStartNode) != static_cast<bool>(static_cast<const SwFormatContent&>(rAttr).m_pStartNode) ) + return false; + if( m_pStartNode ) + return ( *m_pStartNode == *static_cast<const SwFormatContent&>(rAttr).GetContentIdx() ); + return true; +} + +SwFormatContent* SwFormatContent::Clone( SfxItemPool* ) const +{ + return new SwFormatContent( *this ); +} + +void SwFormatContent::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatContent")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("startNode"), BAD_CAST(OString::number(m_pStartNode->GetNode().GetIndex()).getStr())); + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatPageDesc::SwFormatPageDesc( const SwFormatPageDesc &rCpy ) + : SfxPoolItem( RES_PAGEDESC ), + SwClient( const_cast<SwPageDesc*>(rCpy.GetPageDesc()) ), + m_oNumOffset( rCpy.m_oNumOffset ), + m_pDefinedIn( nullptr ) +{ +} + +SwFormatPageDesc::SwFormatPageDesc( const SwPageDesc *pDesc ) + : SfxPoolItem( RES_PAGEDESC ), + SwClient( const_cast<SwPageDesc*>(pDesc) ), + m_pDefinedIn( nullptr ) +{ +} + +SwFormatPageDesc &SwFormatPageDesc::operator=(const SwFormatPageDesc &rCpy) +{ + if(this == &rCpy) + return *this; + + if (rCpy.GetPageDesc()) + RegisterToPageDesc(*const_cast<SwPageDesc*>(rCpy.GetPageDesc())); + m_oNumOffset = rCpy.m_oNumOffset; + m_pDefinedIn = nullptr; + + return *this; +} + + SwFormatPageDesc::~SwFormatPageDesc() {} + +bool SwFormatPageDesc::KnowsPageDesc() const +{ + return (GetRegisteredIn() != nullptr); +} + +bool SwFormatPageDesc::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_pDefinedIn == static_cast<const SwFormatPageDesc&>(rAttr).m_pDefinedIn ) && + ( m_oNumOffset == static_cast<const SwFormatPageDesc&>(rAttr).m_oNumOffset ) && + ( GetPageDesc() == static_cast<const SwFormatPageDesc&>(rAttr).GetPageDesc() ); +} + +SwFormatPageDesc* SwFormatPageDesc::Clone( SfxItemPool* ) const +{ + return new SwFormatPageDesc( *this ); +} + +void SwFormatPageDesc::SwClientNotify( const SwModify& rModify, const SfxHint& rHint ) +{ + SwClient::SwClientNotify(rModify, rHint); + const SwPageDescHint* pHint = dynamic_cast<const SwPageDescHint*>(&rHint); + if ( pHint ) + { + // mba: shouldn't that be broadcasted also? + SwFormatPageDesc aDfltDesc( pHint->GetPageDesc() ); + SwPageDesc* pDesc = pHint->GetPageDesc(); + const SwModify* pMod = GetDefinedIn(); + if ( pMod ) + { + if( auto pContentNode = dynamic_cast<const SwContentNode*>( pMod) ) + const_cast<SwContentNode*>(pContentNode)->SetAttr( aDfltDesc ); + else if( auto pFormat = dynamic_cast<const SwFormat*>( pMod) ) + const_cast<SwFormat*>(pFormat)->SetFormatAttr( aDfltDesc ); + else + { + OSL_FAIL( "What kind of SwModify is this?" ); + RegisterToPageDesc( *pDesc ); + } + } + else + // there could be an Undo-copy + RegisterToPageDesc( *pDesc ); + } +} + +void SwFormatPageDesc::RegisterToPageDesc( SwPageDesc& rDesc ) +{ + rDesc.Add( this ); +} + +void SwFormatPageDesc::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( !m_pDefinedIn ) + return; + + const sal_uInt16 nWhichId = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhichId ) + { + case RES_OBJECTDYING: + //The Pagedesc where I'm registered dies, therefore I unregister + //from that format. During this I get deleted! + if( typeid(SwFormat) == typeid( m_pDefinedIn )) + { + bool const bResult = + static_cast<SwFormat*>(m_pDefinedIn)->ResetFormatAttr(RES_PAGEDESC); + OSL_ENSURE( bResult, "FormatPageDesc not deleted" ); + } + else if( typeid(SwContentNode) == typeid( m_pDefinedIn )) + { + bool const bResult = static_cast<SwContentNode*>(m_pDefinedIn) + ->ResetAttr(RES_PAGEDESC); + OSL_ENSURE( bResult, "FormatPageDesc not deleted" ); + } + break; + + default: + /* do nothing */; + } +} + +bool SwFormatPageDesc::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_PAGEDESC_PAGENUMOFFSET: + { + ::std::optional<sal_uInt16> oOffset = GetNumOffset(); + if (oOffset) + { + rVal <<= static_cast<sal_Int16>(*oOffset); + } + else + { + rVal.clear(); + } + } + break; + + case MID_PAGEDESC_PAGEDESCNAME: + { + const SwPageDesc* pDesc = GetPageDesc(); + if( pDesc ) + { + OUString aString; + SwStyleNameMapper::FillProgName(pDesc->GetName(), aString, SwGetPoolIdFromName::PageDesc); + rVal <<= aString; + } + else + rVal.clear(); + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatPageDesc::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_PAGEDESC_PAGENUMOFFSET: + { + sal_Int16 nOffset = 0; + if (!rVal.hasValue()) + { + SetNumOffset(std::nullopt); + } + else if (rVal >>= nOffset) + SetNumOffset( nOffset ); + else + bRet = false; + } + break; + + case MID_PAGEDESC_PAGEDESCNAME: + /* Doesn't work, because the attribute doesn't need the name but a + * pointer to the PageDesc (it's a client of it). The pointer can + * only be requested from the document using the name. + */ + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatPageDesc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatPageDesc")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + if (m_oNumOffset) + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("oNumOffset"), BAD_CAST(OString::number(*m_oNumOffset).getStr())); + else + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("oNumOffset"), BAD_CAST("none")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("pPageDesc"), "%p", GetPageDesc()); + if (const SwPageDesc* pPageDesc = GetPageDesc()) + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(pPageDesc->GetName().toUtf8().getStr())); + xmlTextWriterEndElement(pWriter); +} + +// class SwFormatCol +// Partially implemented inline in hxx + +SwColumn::SwColumn() : + m_nWish ( 0 ), + m_nLeft ( 0 ), + m_nRight( 0 ) +{ +} + +bool SwColumn::operator==( const SwColumn &rCmp ) const +{ + return m_nWish == rCmp.GetWishWidth() && + GetLeft() == rCmp.GetLeft() && + GetRight() == rCmp.GetRight(); +} + +void SwColumn::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwColumn")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWish"), BAD_CAST(OString::number(m_nWish).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUpper"), BAD_CAST(OString::number(0).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLower"), BAD_CAST(OString::number(0).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLeft"), BAD_CAST(OString::number(m_nLeft).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nRight"), BAD_CAST(OString::number(m_nRight).getStr())); + xmlTextWriterEndElement(pWriter); +} + +SwFormatCol::SwFormatCol( const SwFormatCol& rCpy ) + : SfxPoolItem( RES_COL ), + m_eLineStyle( rCpy.m_eLineStyle ), + m_nLineWidth( rCpy.m_nLineWidth), + m_aLineColor( rCpy.m_aLineColor), + m_nLineHeight( rCpy.GetLineHeight() ), + m_eAdj( rCpy.GetLineAdj() ), + m_nWidth( rCpy.GetWishWidth() ), + m_aWidthAdjustValue( rCpy.m_aWidthAdjustValue ), + m_bOrtho( rCpy.IsOrtho() ) +{ + m_aColumns.reserve(rCpy.GetNumCols()); + for ( sal_uInt16 i = 0; i < rCpy.GetNumCols(); ++i ) + { + m_aColumns.emplace_back(rCpy.GetColumns()[i] ); + } +} + +SwFormatCol::~SwFormatCol() {} + +SwFormatCol& SwFormatCol::operator=( const SwFormatCol& rCpy ) +{ + if (this != &rCpy) + { + m_eLineStyle = rCpy.m_eLineStyle; + m_nLineWidth = rCpy.m_nLineWidth; + m_aLineColor = rCpy.m_aLineColor; + m_nLineHeight = rCpy.GetLineHeight(); + m_eAdj = rCpy.GetLineAdj(); + m_nWidth = rCpy.GetWishWidth(); + m_aWidthAdjustValue = rCpy.m_aWidthAdjustValue; + m_bOrtho = rCpy.IsOrtho(); + + m_aColumns.clear(); + for ( sal_uInt16 i = 0; i < rCpy.GetNumCols(); ++i ) + { + m_aColumns.emplace_back(rCpy.GetColumns()[i] ); + } + } + return *this; +} + +SwFormatCol::SwFormatCol() + : SfxPoolItem( RES_COL ) + , m_eLineStyle( SvxBorderLineStyle::NONE) + , + m_nLineWidth(0), + m_nLineHeight( 100 ), + m_eAdj( COLADJ_NONE ), + m_nWidth( USHRT_MAX ), + m_aWidthAdjustValue( 0 ), + m_bOrtho( true ) +{ +} + +bool SwFormatCol::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SwFormatCol &rCmp = static_cast<const SwFormatCol&>(rAttr); + if( !(m_eLineStyle == rCmp.m_eLineStyle && + m_nLineWidth == rCmp.m_nLineWidth && + m_aLineColor == rCmp.m_aLineColor && + m_nLineHeight == rCmp.GetLineHeight() && + m_eAdj == rCmp.GetLineAdj() && + m_nWidth == rCmp.GetWishWidth() && + m_bOrtho == rCmp.IsOrtho() && + m_aColumns.size() == rCmp.GetNumCols() && + m_aWidthAdjustValue == rCmp.GetAdjustValue() + ) ) + return false; + + for ( size_t i = 0; i < m_aColumns.size(); ++i ) + if ( !(m_aColumns[i] == rCmp.GetColumns()[i]) ) + return false; + + return true; +} + +SwFormatCol* SwFormatCol::Clone( SfxItemPool* ) const +{ + return new SwFormatCol( *this ); +} + +sal_uInt16 SwFormatCol::GetGutterWidth( bool bMin ) const +{ + sal_uInt16 nRet = 0; + if ( m_aColumns.size() == 2 ) + nRet = m_aColumns[0].GetRight() + m_aColumns[1].GetLeft(); + else if ( m_aColumns.size() > 2 ) + { + bool bSet = false; + for ( size_t i = 1; i+1 < m_aColumns.size(); ++i ) + { + const sal_uInt16 nTmp = m_aColumns[i].GetRight() + m_aColumns[i+1].GetLeft(); + if ( bSet ) + { + if ( nTmp != nRet ) + { + if ( !bMin ) + return USHRT_MAX; + if ( nRet > nTmp ) + nRet = nTmp; + } + } + else + { + bSet = true; + nRet = nTmp; + } + } + } + return nRet; +} + +void SwFormatCol::SetGutterWidth( sal_uInt16 nNew, sal_uInt16 nAct ) +{ + if ( m_bOrtho ) + Calc( nNew, nAct ); + else + { + sal_uInt16 nHalf = nNew / 2; + for (size_t i = 0; i < m_aColumns.size(); ++i) + { + SwColumn &rCol = m_aColumns[i]; + rCol.SetLeft(nHalf); + rCol.SetRight(nHalf); + if ( i == 0 ) + rCol.SetLeft(0); + else if ( i+1 == m_aColumns.size() ) + rCol.SetRight(0); + } + } +} + +void SwFormatCol::Init( sal_uInt16 nNumCols, sal_uInt16 nGutterWidth, sal_uInt16 nAct ) +{ + // Deleting seems to be a bit radical on the first sight; but otherwise we + // have to initialize all values of the remaining SwColumns. + m_aColumns.clear(); + for ( sal_uInt16 i = 0; i < nNumCols; ++i ) + { + m_aColumns.emplace_back( ); + } + m_bOrtho = true; + m_nWidth = USHRT_MAX; + if( nNumCols ) + Calc( nGutterWidth, nAct ); +} + +void SwFormatCol::SetOrtho( bool bNew, sal_uInt16 nGutterWidth, sal_uInt16 nAct ) +{ + m_bOrtho = bNew; + if ( bNew && !m_aColumns.empty() ) + Calc( nGutterWidth, nAct ); +} + +sal_uInt16 SwFormatCol::CalcColWidth( sal_uInt16 nCol, sal_uInt16 nAct ) const +{ + assert(nCol < m_aColumns.size()); + if ( m_nWidth != nAct ) + { + long nW = m_aColumns[nCol].GetWishWidth(); + nW *= nAct; + nW /= m_nWidth; + return sal_uInt16(nW); + } + else + return m_aColumns[nCol].GetWishWidth(); +} + +sal_uInt16 SwFormatCol::CalcPrtColWidth( sal_uInt16 nCol, sal_uInt16 nAct ) const +{ + assert(nCol < m_aColumns.size()); + sal_uInt16 nRet = CalcColWidth( nCol, nAct ); + const SwColumn *pCol = &m_aColumns[nCol]; + nRet = nRet - pCol->GetLeft(); + nRet = nRet - pCol->GetRight(); + return nRet; +} + +void SwFormatCol::Calc( sal_uInt16 nGutterWidth, sal_uInt16 nAct ) +{ + if (!GetNumCols()) + return; + + //First set the column widths with the current width, then calculate the + //column's requested width using the requested total width. + const sal_uInt16 nGutterHalf = nGutterWidth ? nGutterWidth / 2 : 0; + + //Width of PrtAreas is totalwidth - spacings / count + sal_uInt16 nSpacings; + bool bFail = o3tl::checked_multiply<sal_uInt16>(GetNumCols() - 1, nGutterWidth, nSpacings); + if (bFail) + { + SAL_WARN("sw.core", "SwFormatVertOrient::Calc: overflow"); + return; + } + + const sal_uInt16 nPrtWidth = (nAct - nSpacings) / GetNumCols(); + sal_uInt16 nAvail = nAct; + + //The first column is PrtWidth + (gap width / 2) + const sal_uInt16 nLeftWidth = nPrtWidth + nGutterHalf; + SwColumn &rFirstCol = m_aColumns.front(); + rFirstCol.SetWishWidth(nLeftWidth); + rFirstCol.SetRight(nGutterHalf); + rFirstCol.SetLeft(0); + nAvail = nAvail - nLeftWidth; + + //Column 2 to n-1 is PrtWidth + gap width + const sal_uInt16 nMidWidth = nPrtWidth + nGutterWidth; + + for (sal_uInt16 i = 1; i < GetNumCols()-1; ++i) + { + SwColumn &rCol = m_aColumns[i]; + rCol.SetWishWidth(nMidWidth); + rCol.SetLeft(nGutterHalf); + rCol.SetRight(nGutterHalf); + nAvail = nAvail - nMidWidth; + } + + //The last column is equivalent to the first one - to compensate rounding + //errors we add the remaining space of the other columns to the last one. + SwColumn &rLastCol = m_aColumns.back(); + rLastCol.SetWishWidth(nAvail); + rLastCol.SetLeft(nGutterHalf); + rLastCol.SetRight(0); + + //Convert the current width to the requested width. + for (SwColumn &rCol: m_aColumns) + { + long nTmp = rCol.GetWishWidth(); + nTmp *= GetWishWidth(); + nTmp /= nAct; + rCol.SetWishWidth(sal_uInt16(nTmp)); + } +} + +bool SwFormatCol::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + if(MID_COLUMN_SEPARATOR_LINE == nMemberId) + { + OSL_FAIL("not implemented"); + } + else + { + uno::Reference< text::XTextColumns > xCols = new SwXTextColumns(*this); + rVal <<= xCols; + } + return true; +} + +bool SwFormatCol::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = false; + if(MID_COLUMN_SEPARATOR_LINE == nMemberId) + { + OSL_FAIL("not implemented"); + } + else + { + uno::Reference< text::XTextColumns > xCols; + rVal >>= xCols; + if(xCols.is()) + { + uno::Sequence<text::TextColumn> aSetColumns = xCols->getColumns(); + const text::TextColumn* pArray = aSetColumns.getConstArray(); + m_aColumns.clear(); + //max count is 64k here - this is something the array can't do + sal_uInt16 nCount = std::min( static_cast<sal_uInt16>(aSetColumns.getLength()), + sal_uInt16(0x3fff) ); + sal_uInt16 nWidthSum = 0; + // #101224# one column is no column + + if(nCount > 1) + for(sal_uInt16 i = 0; i < nCount; i++) + { + SwColumn aCol; + aCol.SetWishWidth(pArray[i].Width ); + nWidthSum = nWidthSum + pArray[i].Width; + aCol.SetLeft (convertMm100ToTwip(pArray[i].LeftMargin)); + aCol.SetRight(convertMm100ToTwip(pArray[i].RightMargin)); + m_aColumns.insert(m_aColumns.begin() + i, aCol); + } + bRet = true; + m_nWidth = nWidthSum; + m_bOrtho = false; + + auto pSwColums = comphelper::getUnoTunnelImplementation<SwXTextColumns>(xCols); + if(pSwColums) + { + m_bOrtho = pSwColums->IsAutomaticWidth(); + m_nLineWidth = pSwColums->GetSepLineWidth(); + m_aLineColor = pSwColums->GetSepLineColor(); + m_nLineHeight = pSwColums->GetSepLineHeightRelative(); + switch ( pSwColums->GetSepLineStyle() ) + { + default: + case 0: m_eLineStyle = SvxBorderLineStyle::NONE; break; + case 1: m_eLineStyle = SvxBorderLineStyle::SOLID; break; + case 2: m_eLineStyle = SvxBorderLineStyle::DOTTED; break; + case 3: m_eLineStyle = SvxBorderLineStyle::DASHED; break; + } + if(!pSwColums->GetSepLineIsOn()) + m_eAdj = COLADJ_NONE; + else switch(pSwColums->GetSepLineVertAlign()) + { + case style::VerticalAlignment_TOP: m_eAdj = COLADJ_TOP; break; + case style::VerticalAlignment_MIDDLE: m_eAdj = COLADJ_CENTER; break; + case style::VerticalAlignment_BOTTOM: m_eAdj = COLADJ_BOTTOM; break; + default: OSL_ENSURE( false, "unknown alignment" ); break; + } + } + } + } + return bRet; +} + +void SwFormatCol::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatCol")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eLineStyle"), BAD_CAST(OString::number(static_cast<sal_Int16>(m_eLineStyle)).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLineWidth"), BAD_CAST(OString::number(m_nLineWidth).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("aLineColor"), BAD_CAST(m_aLineColor.AsRGBHexString().toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLineHeight"), BAD_CAST(OString::number(m_nLineHeight).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eAdj"), BAD_CAST(OString::number(m_eAdj).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidth"), BAD_CAST(OString::number(m_nWidth).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidthAdjustValue"), BAD_CAST(OString::number(m_aWidthAdjustValue).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bOrtho"), BAD_CAST(OString::boolean(m_bOrtho).getStr())); + + xmlTextWriterStartElement(pWriter, BAD_CAST("aColumns")); + for (const SwColumn& rColumn : m_aColumns) + rColumn.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatSurround::SwFormatSurround( css::text::WrapTextMode eFly ) : + SfxEnumItem( RES_SURROUND, eFly ) +{ + m_bAnchorOnly = m_bContour = m_bOutside = false; +} + +bool SwFormatSurround::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( GetValue() == static_cast<const SwFormatSurround&>(rAttr).GetValue() && + m_bAnchorOnly== static_cast<const SwFormatSurround&>(rAttr).m_bAnchorOnly && + m_bContour== static_cast<const SwFormatSurround&>(rAttr).m_bContour && + m_bOutside== static_cast<const SwFormatSurround&>(rAttr).m_bOutside ); +} + +SwFormatSurround* SwFormatSurround::Clone( SfxItemPool* ) const +{ + return new SwFormatSurround( *this ); +} + +sal_uInt16 SwFormatSurround::GetValueCount() const +{ + return 6; +} + +bool SwFormatSurround::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_SURROUND_SURROUNDTYPE: + rVal <<= GetSurround(); + break; + case MID_SURROUND_ANCHORONLY: + rVal <<= IsAnchorOnly(); + break; + case MID_SURROUND_CONTOUR: + rVal <<= IsContour(); + break; + case MID_SURROUND_CONTOUROUTSIDE: + rVal <<= IsOutside(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatSurround::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_SURROUND_SURROUNDTYPE: + { + css::text::WrapTextMode eVal = static_cast<css::text::WrapTextMode>(SWUnoHelper::GetEnumAsInt32( rVal )); + if( eVal >= css::text::WrapTextMode_NONE && eVal <= css::text::WrapTextMode_RIGHT ) + SetValue( eVal ); + else { + //exception + ; + } + } + break; + + case MID_SURROUND_ANCHORONLY: + SetAnchorOnly( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_SURROUND_CONTOUR: + SetContour( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_SURROUND_CONTOUROUTSIDE: + SetOutside( *o3tl::doAccess<bool>(rVal) ); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatSurround::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatSurround")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(static_cast<sal_Int32>(GetValue())).getStr())); + + OUString aPresentation; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + GetPresentation(SfxItemPresentation::Nameless, MapUnit::Map100thMM, MapUnit::Map100thMM, aPresentation, aIntlWrapper); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(aPresentation.toUtf8().getStr())); + + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bAnchorOnly"), BAD_CAST(OString::boolean(m_bAnchorOnly).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bContour"), BAD_CAST(OString::boolean(m_bContour).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bOutside"), BAD_CAST(OString::boolean(m_bOutside).getStr())); + + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatVertOrient::SwFormatVertOrient( SwTwips nY, sal_Int16 eVert, + sal_Int16 eRel ) + : SfxPoolItem( RES_VERT_ORIENT ), + m_nYPos( nY ), + m_eOrient( eVert ), + m_eRelation( eRel ) +{} + +bool SwFormatVertOrient::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_nYPos == static_cast<const SwFormatVertOrient&>(rAttr).m_nYPos && + m_eOrient == static_cast<const SwFormatVertOrient&>(rAttr).m_eOrient && + m_eRelation == static_cast<const SwFormatVertOrient&>(rAttr).m_eRelation ); +} + +SwFormatVertOrient* SwFormatVertOrient::Clone( SfxItemPool* ) const +{ + return new SwFormatVertOrient( *this ); +} + +bool SwFormatVertOrient::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_VERTORIENT_ORIENT: + { + rVal <<= m_eOrient; + } + break; + case MID_VERTORIENT_RELATION: + rVal <<= m_eRelation; + break; + case MID_VERTORIENT_POSITION: + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetPos())); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatVertOrient::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_VERTORIENT_ORIENT: + { + sal_uInt16 nVal = text::VertOrientation::NONE; + rVal >>= nVal; + m_eOrient = nVal; + } + break; + case MID_VERTORIENT_RELATION: + { + m_eRelation = lcl_IntToRelation(rVal); + } + break; + case MID_VERTORIENT_POSITION: + { + sal_Int32 nVal = 0; + rVal >>= nVal; + if(bConvert) + nVal = convertMm100ToTwip(nVal); + SetPos( nVal ); + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatVertOrient::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatVertOrient")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nYPos"), BAD_CAST(OString::number(m_nYPos).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eOrient"), BAD_CAST(OString::number(m_eOrient).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eRelation"), BAD_CAST(OString::number(m_eRelation).getStr())); + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatHoriOrient::SwFormatHoriOrient( SwTwips nX, sal_Int16 eHori, + sal_Int16 eRel, bool bPos ) + : SfxPoolItem( RES_HORI_ORIENT ), + m_nXPos( nX ), + m_eOrient( eHori ), + m_eRelation( eRel ), + m_bPosToggle( bPos ) +{} + +bool SwFormatHoriOrient::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_nXPos == static_cast<const SwFormatHoriOrient&>(rAttr).m_nXPos && + m_eOrient == static_cast<const SwFormatHoriOrient&>(rAttr).m_eOrient && + m_eRelation == static_cast<const SwFormatHoriOrient&>(rAttr).m_eRelation && + m_bPosToggle == static_cast<const SwFormatHoriOrient&>(rAttr).m_bPosToggle ); +} + +SwFormatHoriOrient* SwFormatHoriOrient::Clone( SfxItemPool* ) const +{ + return new SwFormatHoriOrient( *this ); +} + +bool SwFormatHoriOrient::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_HORIORIENT_ORIENT: + { + rVal <<= m_eOrient; + } + break; + case MID_HORIORIENT_RELATION: + rVal <<= m_eRelation; + break; + case MID_HORIORIENT_POSITION: + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetPos())); + break; + case MID_HORIORIENT_PAGETOGGLE: + rVal <<= IsPosToggle(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatHoriOrient::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_HORIORIENT_ORIENT: + { + sal_Int16 nVal = text::HoriOrientation::NONE; + rVal >>= nVal; + m_eOrient = nVal; + } + break; + case MID_HORIORIENT_RELATION: + { + m_eRelation = lcl_IntToRelation(rVal); + } + break; + case MID_HORIORIENT_POSITION: + { + sal_Int32 nVal = 0; + if(!(rVal >>= nVal)) + bRet = false; + if(bConvert) + nVal = convertMm100ToTwip(nVal); + SetPos( nVal ); + } + break; + case MID_HORIORIENT_PAGETOGGLE: + SetPosToggle( *o3tl::doAccess<bool>(rVal)); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatHoriOrient::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatHoriOrient")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nXPos"), BAD_CAST(OString::number(m_nXPos).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eOrient"), BAD_CAST(OString::number(m_eOrient).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eRelation"), BAD_CAST(OString::number(m_eRelation).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bPosToggle"), BAD_CAST(OString::boolean(m_bPosToggle).getStr())); + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatAnchor::SwFormatAnchor( RndStdIds nRnd, sal_uInt16 nPage ) + : SfxPoolItem( RES_ANCHOR ), + m_eAnchorId( nRnd ), + m_nPageNumber( nPage ), + // OD 2004-05-05 #i28701# - get always new increased order number + m_nOrder( ++m_nOrderCounter ) +{} + +SwFormatAnchor::SwFormatAnchor( const SwFormatAnchor &rCpy ) + : SfxPoolItem( RES_ANCHOR ) + , m_pContentAnchor( (rCpy.GetContentAnchor()) + ? new SwPosition( *rCpy.GetContentAnchor() ) : nullptr ) + , m_eAnchorId( rCpy.GetAnchorId() ) + , m_nPageNumber( rCpy.GetPageNum() ) + // OD 2004-05-05 #i28701# - get always new increased order number + , m_nOrder( ++m_nOrderCounter ) +{ +} + +SwFormatAnchor::~SwFormatAnchor() +{ +} + +void SwFormatAnchor::SetAnchor( const SwPosition *pPos ) +{ + // anchor only to paragraphs, or start nodes in case of RndStdIds::FLY_AT_FLY + // also allow table node, this is used when a table is selected and is converted to a frame by the UI + assert(!pPos + || ((RndStdIds::FLY_AT_FLY == m_eAnchorId) && + dynamic_cast<SwStartNode*>(&pPos->nNode.GetNode())) + || (RndStdIds::FLY_AT_PARA == m_eAnchorId && dynamic_cast<SwTableNode*>(&pPos->nNode.GetNode())) + || dynamic_cast<SwTextNode*>(&pPos->nNode.GetNode())); + m_pContentAnchor .reset( pPos ? new SwPosition( *pPos ) : nullptr ); + // Flys anchored AT paragraph should not point into the paragraph content + if (m_pContentAnchor && + ((RndStdIds::FLY_AT_PARA == m_eAnchorId) || (RndStdIds::FLY_AT_FLY == m_eAnchorId))) + { + m_pContentAnchor->nContent.Assign( nullptr, 0 ); + } +} + +SwFormatAnchor& SwFormatAnchor::operator=(const SwFormatAnchor& rAnchor) +{ + if (this != &rAnchor) + { + m_eAnchorId = rAnchor.GetAnchorId(); + m_nPageNumber = rAnchor.GetPageNum(); + // OD 2004-05-05 #i28701# - get always new increased order number + m_nOrder = ++m_nOrderCounter; + + m_pContentAnchor.reset( (rAnchor.GetContentAnchor()) + ? new SwPosition(*(rAnchor.GetContentAnchor())) + : nullptr ); + } + return *this; +} + +bool SwFormatAnchor::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + SwFormatAnchor const& rFormatAnchor(static_cast<SwFormatAnchor const&>(rAttr)); + // OD 2004-05-05 #i28701# - Note: <mnOrder> hasn't to be considered. + return ( m_eAnchorId == rFormatAnchor.GetAnchorId() && + m_nPageNumber == rFormatAnchor.GetPageNum() && + // compare anchor: either both do not point into a textnode or + // both do (valid m_pContentAnchor) and the positions are equal + ((m_pContentAnchor.get() == rFormatAnchor.m_pContentAnchor.get()) || + (m_pContentAnchor && rFormatAnchor.GetContentAnchor() && + (*m_pContentAnchor == *rFormatAnchor.GetContentAnchor())))); +} + +SwFormatAnchor* SwFormatAnchor::Clone( SfxItemPool* ) const +{ + return new SwFormatAnchor( *this ); +} + +// OD 2004-05-05 #i28701# +sal_uInt32 SwFormatAnchor::m_nOrderCounter = 0; + +// OD 2004-05-05 #i28701# + +bool SwFormatAnchor::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_ANCHOR_ANCHORTYPE: + + text::TextContentAnchorType eRet; + switch (GetAnchorId()) + { + case RndStdIds::FLY_AT_CHAR: + eRet = text::TextContentAnchorType_AT_CHARACTER; + break; + case RndStdIds::FLY_AT_PAGE: + eRet = text::TextContentAnchorType_AT_PAGE; + break; + case RndStdIds::FLY_AT_FLY: + eRet = text::TextContentAnchorType_AT_FRAME; + break; + case RndStdIds::FLY_AS_CHAR: + eRet = text::TextContentAnchorType_AS_CHARACTER; + break; + //case RndStdIds::FLY_AT_PARA: + default: + eRet = text::TextContentAnchorType_AT_PARAGRAPH; + } + rVal <<= eRet; + break; + case MID_ANCHOR_PAGENUM: + rVal <<= static_cast<sal_Int16>(GetPageNum()); + break; + case MID_ANCHOR_ANCHORFRAME: + { + if (m_pContentAnchor && RndStdIds::FLY_AT_FLY == m_eAnchorId) + { + SwFrameFormat* pFormat = m_pContentAnchor->nNode.GetNode().GetFlyFormat(); + if(pFormat) + { + uno::Reference<text::XTextFrame> const xRet( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat)); + rVal <<= xRet; + } + } + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatAnchor::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_ANCHOR_ANCHORTYPE: + { + RndStdIds eAnchor; + switch( static_cast<text::TextContentAnchorType>(SWUnoHelper::GetEnumAsInt32( rVal )) ) + { + case text::TextContentAnchorType_AS_CHARACTER: + eAnchor = RndStdIds::FLY_AS_CHAR; + break; + case text::TextContentAnchorType_AT_PAGE: + eAnchor = RndStdIds::FLY_AT_PAGE; + if( GetPageNum() > 0 ) + { + // If the anchor type is page and a valid page number + // has been set, the content position isn't required + // any longer. + m_pContentAnchor.reset(); + } + break; + case text::TextContentAnchorType_AT_FRAME: + eAnchor = RndStdIds::FLY_AT_FLY; + break; + case text::TextContentAnchorType_AT_CHARACTER: + eAnchor = RndStdIds::FLY_AT_CHAR; + break; + //case text::TextContentAnchorType_AT_PARAGRAPH: + default: + eAnchor = RndStdIds::FLY_AT_PARA; + break; + } + SetType( eAnchor ); + } + break; + case MID_ANCHOR_PAGENUM: + { + sal_Int16 nVal = 0; + if((rVal >>= nVal) && nVal > 0) + { + SetPageNum( nVal ); + if (RndStdIds::FLY_AT_PAGE == GetAnchorId()) + { + // If the anchor type is page and a valid page number + // is set, the content position has to be deleted to not + // confuse the layout (frmtool.cxx). However, if the + // anchor type is not page, any content position will + // be kept. + m_pContentAnchor.reset(); + } + } + else + bRet = false; + } + break; + case MID_ANCHOR_ANCHORFRAME: + //no break here!; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatAnchor::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatAnchor")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + + if (m_pContentAnchor) + { + std::stringstream aContentAnchor; + aContentAnchor << *m_pContentAnchor; + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_pContentAnchor"), BAD_CAST(aContentAnchor.str().c_str())); + } + else + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pContentAnchor"), "%p", m_pContentAnchor.get()); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_eAnchorType"), BAD_CAST(OString::number(static_cast<int>(m_eAnchorId)).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nPageNumber"), BAD_CAST(OString::number(m_nPageNumber).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nOrder"), BAD_CAST(OString::number(m_nOrder).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nOrderCounter"), BAD_CAST(OString::number(m_nOrderCounter).getStr())); + OUString aPresentation; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + GetPresentation(SfxItemPresentation::Nameless, MapUnit::Map100thMM, MapUnit::Map100thMM, aPresentation, aIntlWrapper); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(aPresentation.toUtf8().getStr())); + + xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatURL::SwFormatURL() : + SfxPoolItem( RES_URL ), + m_bIsServerMap( false ) +{ +} + +SwFormatURL::SwFormatURL( const SwFormatURL &rURL) : + SfxPoolItem( RES_URL ), + m_sTargetFrameName( rURL.GetTargetFrameName() ), + m_sURL( rURL.GetURL() ), + m_sName( rURL.GetName() ), + m_bIsServerMap( rURL.IsServerMap() ) +{ + if (rURL.GetMap()) + m_pMap.reset( new ImageMap( *rURL.GetMap() ) ); +} + +SwFormatURL::~SwFormatURL() +{ +} + +bool SwFormatURL::operator==( const SfxPoolItem &rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SwFormatURL &rCmp = static_cast<const SwFormatURL&>(rAttr); + bool bRet = m_bIsServerMap == rCmp.IsServerMap() && + m_sURL == rCmp.GetURL() && + m_sTargetFrameName == rCmp.GetTargetFrameName() && + m_sName == rCmp.GetName(); + if ( bRet ) + { + if ( m_pMap && rCmp.GetMap() ) + bRet = *m_pMap == *rCmp.GetMap(); + else + bRet = m_pMap.get() == rCmp.GetMap(); + } + return bRet; +} + +SwFormatURL* SwFormatURL::Clone( SfxItemPool* ) const +{ + return new SwFormatURL( *this ); +} + +void SwFormatURL::SetURL(const OUString &rURL, bool bServerMap) +{ + m_sURL = rURL; + m_bIsServerMap = bServerMap; +} + +void SwFormatURL::SetMap( const ImageMap *pM ) +{ + m_pMap.reset( pM ? new ImageMap( *pM ) : nullptr); +} + +bool SwFormatURL::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_URL_URL: + rVal <<= GetURL(); + break; + case MID_URL_TARGET: + rVal <<= GetTargetFrameName(); + break; + case MID_URL_HYPERLINKNAME: + rVal <<= GetName(); + break; + case MID_URL_CLIENTMAP: + { + uno::Reference< uno::XInterface > xInt; + if(m_pMap) + { + xInt = SvUnoImageMap_createInstance( *m_pMap, sw_GetSupportedMacroItems() ); + } + else + { + ImageMap aEmptyMap; + xInt = SvUnoImageMap_createInstance( aEmptyMap, sw_GetSupportedMacroItems() ); + } + uno::Reference< container::XIndexContainer > xCont(xInt, uno::UNO_QUERY); + rVal <<= xCont; + } + break; + case MID_URL_SERVERMAP: + rVal <<= IsServerMap(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatURL::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_URL_URL: + { + OUString sTmp; + rVal >>= sTmp; + SetURL( sTmp, m_bIsServerMap ); + } + break; + case MID_URL_TARGET: + { + OUString sTmp; + rVal >>= sTmp; + SetTargetFrameName( sTmp ); + } + break; + case MID_URL_HYPERLINKNAME: + { + OUString sTmp; + rVal >>= sTmp; + SetName( sTmp ); + } + break; + case MID_URL_CLIENTMAP: + { + uno::Reference<container::XIndexContainer> xCont; + if(!rVal.hasValue()) + m_pMap.reset(); + else if(rVal >>= xCont) + { + if(!m_pMap) + m_pMap.reset(new ImageMap); + bRet = SvUnoImageMap_fillImageMap( xCont, *m_pMap ); + } + else + bRet = false; + } + break; + case MID_URL_SERVERMAP: + m_bIsServerMap = *o3tl::doAccess<bool>(rVal); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +SwFormatEditInReadonly* SwFormatEditInReadonly::Clone( SfxItemPool* ) const +{ + return new SwFormatEditInReadonly( *this ); +} + +SwFormatLayoutSplit* SwFormatLayoutSplit::Clone( SfxItemPool* ) const +{ + return new SwFormatLayoutSplit( *this ); +} + +SwFormatRowSplit* SwFormatRowSplit::Clone( SfxItemPool* ) const +{ + return new SwFormatRowSplit( *this ); +} + +SwFormatNoBalancedColumns* SwFormatNoBalancedColumns::Clone( SfxItemPool* ) const +{ + return new SwFormatNoBalancedColumns( *this ); +} + +void SwFormatNoBalancedColumns::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatNoBalancedColumns")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(GetValue()).getStr())); + xmlTextWriterEndElement(pWriter); +} + +// class SwFormatFootnoteEndAtTextEnd + +sal_uInt16 SwFormatFootnoteEndAtTextEnd::GetValueCount() const +{ + return sal_uInt16( FTNEND_ATTXTEND_END ); +} + +SwFormatFootnoteEndAtTextEnd& SwFormatFootnoteEndAtTextEnd::operator=( + const SwFormatFootnoteEndAtTextEnd& rAttr ) +{ + SfxEnumItem::SetValue( rAttr.GetValue() ); + m_aFormat = rAttr.m_aFormat; + m_nOffset = rAttr.m_nOffset; + m_sPrefix = rAttr.m_sPrefix; + m_sSuffix = rAttr.m_sSuffix; + return *this; +} + +bool SwFormatFootnoteEndAtTextEnd::operator==( const SfxPoolItem& rItem ) const +{ + const SwFormatFootnoteEndAtTextEnd& rAttr = static_cast<const SwFormatFootnoteEndAtTextEnd&>(rItem); + return SfxEnumItem::operator==( rAttr ) && + m_aFormat.GetNumberingType() == rAttr.m_aFormat.GetNumberingType() && + m_nOffset == rAttr.m_nOffset && + m_sPrefix == rAttr.m_sPrefix && + m_sSuffix == rAttr.m_sSuffix; +} + +bool SwFormatFootnoteEndAtTextEnd::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_COLLECT : + rVal <<= GetValue() >= FTNEND_ATTXTEND; + break; + case MID_RESTART_NUM : + rVal <<= GetValue() >= FTNEND_ATTXTEND_OWNNUMSEQ; + break; + case MID_NUM_START_AT: rVal <<= static_cast<sal_Int16>(m_nOffset); break; + case MID_OWN_NUM : + rVal <<= GetValue() >= FTNEND_ATTXTEND_OWNNUMANDFMT; + break; + case MID_NUM_TYPE : rVal <<= static_cast<sal_Int16>(m_aFormat.GetNumberingType()); break; + case MID_PREFIX : rVal <<= m_sPrefix; break; + case MID_SUFFIX : rVal <<= m_sSuffix; break; + default: return false; + } + return true; +} + +bool SwFormatFootnoteEndAtTextEnd::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = true; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_COLLECT : + { + bool bVal = *o3tl::doAccess<bool>(rVal); + if(!bVal && GetValue() >= FTNEND_ATTXTEND) + SetValue(FTNEND_ATPGORDOCEND); + else if(bVal && GetValue() < FTNEND_ATTXTEND) + SetValue(FTNEND_ATTXTEND); + } + break; + case MID_RESTART_NUM : + { + bool bVal = *o3tl::doAccess<bool>(rVal); + if(!bVal && GetValue() >= FTNEND_ATTXTEND_OWNNUMSEQ) + SetValue(FTNEND_ATTXTEND); + else if(bVal && GetValue() < FTNEND_ATTXTEND_OWNNUMSEQ) + SetValue(FTNEND_ATTXTEND_OWNNUMSEQ); + } + break; + case MID_NUM_START_AT: + { + sal_Int16 nVal = 0; + rVal >>= nVal; + if(nVal >= 0) + m_nOffset = nVal; + else + bRet = false; + } + break; + case MID_OWN_NUM : + { + bool bVal = *o3tl::doAccess<bool>(rVal); + if(!bVal && GetValue() >= FTNEND_ATTXTEND_OWNNUMANDFMT) + SetValue(FTNEND_ATTXTEND_OWNNUMSEQ); + else if(bVal && GetValue() < FTNEND_ATTXTEND_OWNNUMANDFMT) + SetValue(FTNEND_ATTXTEND_OWNNUMANDFMT); + } + break; + case MID_NUM_TYPE : + { + sal_Int16 nVal = 0; + rVal >>= nVal; + if(nVal >= 0 && + (nVal <= SVX_NUM_ARABIC || + SVX_NUM_CHARS_UPPER_LETTER_N == nVal || + SVX_NUM_CHARS_LOWER_LETTER_N == nVal )) + m_aFormat.SetNumberingType(static_cast<SvxNumType>(nVal)); + else + bRet = false; + } + break; + case MID_PREFIX : + { + OUString sVal; rVal >>= sVal; + m_sPrefix = sVal; + } + break; + case MID_SUFFIX : + { + OUString sVal; rVal >>= sVal; + m_sSuffix = sVal; + } + break; + default: bRet = false; + } + return bRet; +} + +// class SwFormatFootnoteAtTextEnd + +SwFormatFootnoteAtTextEnd* SwFormatFootnoteAtTextEnd::Clone( SfxItemPool* ) const +{ + return new SwFormatFootnoteAtTextEnd(*this); +} + +// class SwFormatEndAtTextEnd + +SwFormatEndAtTextEnd* SwFormatEndAtTextEnd::Clone( SfxItemPool* ) const +{ + return new SwFormatEndAtTextEnd(*this); +} + +//class SwFormatChain + +bool SwFormatChain::operator==( const SfxPoolItem &rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return GetPrev() == static_cast<const SwFormatChain&>(rAttr).GetPrev() && + GetNext() == static_cast<const SwFormatChain&>(rAttr).GetNext(); +} + +SwFormatChain::SwFormatChain( const SwFormatChain &rCpy ) : + SfxPoolItem( RES_CHAIN ) +{ + SetPrev( rCpy.GetPrev() ); + SetNext( rCpy.GetNext() ); +} + +SwFormatChain* SwFormatChain::Clone( SfxItemPool* ) const +{ + SwFormatChain *pRet = new SwFormatChain; + pRet->SetPrev( GetPrev() ); + pRet->SetNext( GetNext() ); + return pRet; +} + +void SwFormatChain::SetPrev( SwFlyFrameFormat *pFormat ) +{ + if ( pFormat ) + pFormat->Add( &m_aPrev ); + else + m_aPrev.EndListeningAll(); +} + +void SwFormatChain::SetNext( SwFlyFrameFormat *pFormat ) +{ + if ( pFormat ) + pFormat->Add( &m_aNext ); + else + m_aNext.EndListeningAll(); +} + +bool SwFormatChain::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + OUString aRet; + switch ( nMemberId ) + { + case MID_CHAIN_PREVNAME: + if ( GetPrev() ) + aRet = GetPrev()->GetName(); + break; + case MID_CHAIN_NEXTNAME: + if ( GetNext() ) + aRet = GetNext()->GetName(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + rVal <<= aRet; + return bRet; +} + +SwFormatLineNumber::SwFormatLineNumber() : + SfxPoolItem( RES_LINENUMBER ) +{ + m_nStartValue = 0; + m_bCountLines = true; +} + +SwFormatLineNumber::~SwFormatLineNumber() +{ +} + +bool SwFormatLineNumber::operator==( const SfxPoolItem &rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return m_nStartValue == static_cast<const SwFormatLineNumber&>(rAttr).GetStartValue() && + m_bCountLines == static_cast<const SwFormatLineNumber&>(rAttr).IsCount(); +} + +SwFormatLineNumber* SwFormatLineNumber::Clone( SfxItemPool* ) const +{ + return new SwFormatLineNumber( *this ); +} + +bool SwFormatLineNumber::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_LINENUMBER_COUNT: + rVal <<= IsCount(); + break; + case MID_LINENUMBER_STARTVALUE: + rVal <<= static_cast<sal_Int32>(GetStartValue()); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatLineNumber::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_LINENUMBER_COUNT: + SetCountLines( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_LINENUMBER_STARTVALUE: + { + sal_Int32 nVal = 0; + if(rVal >>= nVal) + SetStartValue( nVal ); + else + bRet = false; + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +SwTextGridItem::SwTextGridItem() + : SfxPoolItem( RES_TEXTGRID ), m_aColor( COL_LIGHTGRAY ), m_nLines( 20 ) + , m_nBaseHeight( 400 ), m_nRubyHeight( 200 ), m_eGridType( GRID_NONE ) + , m_bRubyTextBelow( false ), m_bPrintGrid( true ), m_bDisplayGrid( true ) + , m_nBaseWidth(400), m_bSnapToChars( true ), m_bSquaredMode(true) +{ +} + +SwTextGridItem::~SwTextGridItem() +{ +} + +bool SwTextGridItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + SwTextGridItem const& rOther(static_cast<SwTextGridItem const&>(rAttr)); + return m_eGridType == rOther.GetGridType() + && m_nLines == rOther.GetLines() + && m_nBaseHeight == rOther.GetBaseHeight() + && m_nRubyHeight == rOther.GetRubyHeight() + && m_bRubyTextBelow == rOther.GetRubyTextBelow() + && m_bDisplayGrid == rOther.GetDisplayGrid() + && m_bPrintGrid == rOther.GetPrintGrid() + && m_aColor == rOther.GetColor() + && m_nBaseWidth == rOther.GetBaseWidth() + && m_bSnapToChars == rOther.GetSnapToChars() + && m_bSquaredMode == rOther.GetSquaredMode(); +} + +SwTextGridItem* SwTextGridItem::Clone( SfxItemPool* ) const +{ + return new SwTextGridItem( *this ); +} + +bool SwTextGridItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bRet = true; + + switch( nMemberId & ~CONVERT_TWIPS ) + { + case MID_GRID_COLOR: + rVal <<= GetColor(); + break; + case MID_GRID_LINES: + rVal <<= GetLines(); + break; + case MID_GRID_RUBY_BELOW: + rVal <<= m_bRubyTextBelow; + break; + case MID_GRID_PRINT: + rVal <<= m_bPrintGrid; + break; + case MID_GRID_DISPLAY: + rVal <<= m_bDisplayGrid; + break; + case MID_GRID_BASEHEIGHT: + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(m_nBaseHeight)); + break; + case MID_GRID_BASEWIDTH: + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(m_nBaseWidth)); + break; + case MID_GRID_RUBYHEIGHT: + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(m_nRubyHeight)); + break; + case MID_GRID_TYPE: + switch( GetGridType() ) + { + case GRID_NONE: + rVal <<= text::TextGridMode::NONE; + break; + case GRID_LINES_ONLY: + rVal <<= text::TextGridMode::LINES; + break; + case GRID_LINES_CHARS: + rVal <<= text::TextGridMode::LINES_AND_CHARS; + break; + default: + OSL_FAIL("unknown SwTextGrid value"); + bRet = false; + break; + } + break; + case MID_GRID_SNAPTOCHARS: + rVal <<= m_bSnapToChars; + break; + case MID_GRID_STANDARD_MODE: + rVal <<= !m_bSquaredMode; + break; + default: + OSL_FAIL("Unknown SwTextGridItem member"); + bRet = false; + break; + } + + return bRet; +} + +bool SwTextGridItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = true; + switch( nMemberId & ~CONVERT_TWIPS ) + { + case MID_GRID_COLOR: + { + sal_Int32 nTmp = 0; + bRet = (rVal >>= nTmp); + if( bRet ) + SetColor( Color(nTmp) ); + } + break; + case MID_GRID_LINES: + { + sal_Int16 nTmp = 0; + bRet = (rVal >>= nTmp); + if( bRet && (nTmp >= 0) ) + SetLines( static_cast<sal_uInt16>(nTmp) ); + else + bRet = false; + } + break; + case MID_GRID_RUBY_BELOW: + SetRubyTextBelow( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_PRINT: + SetPrintGrid( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_DISPLAY: + SetDisplayGrid( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_BASEHEIGHT: + case MID_GRID_BASEWIDTH: + case MID_GRID_RUBYHEIGHT: + { + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + sal_Int32 nTmp = 0; + bRet = (rVal >>= nTmp); + nTmp = convertMm100ToTwip( nTmp ); + if( bRet && (nTmp >= 0) && ( nTmp <= SAL_MAX_UINT16) ) + { + // rhbz#1043551 round up to 5pt -- 0 causes divide-by-zero + // in layout; 1pt ties the painting code up in knots for + // minutes with bazillion lines... +#define MIN_TEXTGRID_SIZE 100 + if( (nMemberId & ~CONVERT_TWIPS) == MID_GRID_BASEHEIGHT ) + { + nTmp = std::max<sal_Int32>(nTmp, MIN_TEXTGRID_SIZE); + SetBaseHeight( static_cast<sal_uInt16>(nTmp) ); + } + else if( (nMemberId & ~CONVERT_TWIPS) == MID_GRID_BASEWIDTH ) + { + nTmp = std::max<sal_Int32>(nTmp, MIN_TEXTGRID_SIZE); + SetBaseWidth( static_cast<sal_uInt16>(nTmp) ); + } + else + SetRubyHeight( static_cast<sal_uInt16>(nTmp) ); + } + else + bRet = false; + } + break; + case MID_GRID_TYPE: + { + sal_Int16 nTmp = 0; + bRet = (rVal >>= nTmp); + if( bRet ) + { + switch( nTmp ) + { + case text::TextGridMode::NONE: + SetGridType( GRID_NONE ); + break; + case text::TextGridMode::LINES: + SetGridType( GRID_LINES_ONLY ); + break; + case text::TextGridMode::LINES_AND_CHARS: + SetGridType( GRID_LINES_CHARS ); + break; + default: + bRet = false; + break; + } + } + break; + } + case MID_GRID_SNAPTOCHARS: + SetSnapToChars( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_STANDARD_MODE: + { + bool bStandard = *o3tl::doAccess<bool>(rVal); + SetSquaredMode( !bStandard ); + break; + } + default: + OSL_FAIL("Unknown SwTextGridItem member"); + bRet = false; + } + + return bRet; +} + +void SwTextGridItem::SwitchPaperMode(bool bNew) +{ + if (bNew == m_bSquaredMode) + { + //same paper mode, not switch + return; + } + + // use default value when grid is disable + if (m_eGridType == GRID_NONE) + { + m_bSquaredMode = bNew; + Init(); + return; + } + + if (m_bSquaredMode) + { + //switch from "squared mode" to "standard mode" + m_nBaseWidth = m_nBaseHeight; + m_nBaseHeight = m_nBaseHeight + m_nRubyHeight; + m_nRubyHeight = 0; + } + else + { + //switch from "standard mode" to "squared mode" + m_nRubyHeight = m_nBaseHeight/3; + m_nBaseHeight = m_nBaseHeight - m_nRubyHeight; + m_nBaseWidth = m_nBaseHeight; + } + m_bSquaredMode = !m_bSquaredMode; +} + +void SwTextGridItem::Init() +{ + if (m_bSquaredMode) + { + m_nLines = 20; + m_nBaseHeight = 400; + m_nRubyHeight = 200; + m_eGridType = GRID_NONE; + m_bRubyTextBelow = false; + m_bPrintGrid = true; + m_bDisplayGrid = true; + m_bSnapToChars = true; + m_nBaseWidth = 400; + } + else + { + m_nLines = 44; + m_nBaseHeight = 312; + m_nRubyHeight = 0; + m_eGridType = GRID_NONE; + m_bRubyTextBelow = false; + m_bPrintGrid = true; + m_bDisplayGrid = true; + m_nBaseWidth = 210; + m_bSnapToChars = true; + } +} + +SwHeaderAndFooterEatSpacingItem* SwHeaderAndFooterEatSpacingItem::Clone( SfxItemPool* ) const +{ + return new SwHeaderAndFooterEatSpacingItem( Which(), GetValue() ); +} + +SwFrameFormat::SwFrameFormat( + SwAttrPool& rPool, + const char* pFormatNm, + SwFrameFormat *pDrvdFrame, + sal_uInt16 nFormatWhich, + const sal_uInt16* pWhichRange) +: SwFormat(rPool, pFormatNm, (pWhichRange ? pWhichRange : aFrameFormatSetRange), pDrvdFrame, nFormatWhich), + m_wXObject(), + maFillAttributes(), + m_ffList(nullptr), + m_pOtherTextBoxFormat(nullptr) +{ +} + +SwFrameFormat::SwFrameFormat( + SwAttrPool& rPool, + const OUString &rFormatNm, + SwFrameFormat *pDrvdFrame, + sal_uInt16 nFormatWhich, + const sal_uInt16* pWhichRange) +: SwFormat(rPool, rFormatNm, (pWhichRange ? pWhichRange : aFrameFormatSetRange), pDrvdFrame, nFormatWhich), + m_wXObject(), + maFillAttributes(), + m_ffList(nullptr), + m_pOtherTextBoxFormat(nullptr) +{ +} + +SwFrameFormat::~SwFrameFormat() +{ + if( !GetDoc()->IsInDtor()) + { + const SwFormatAnchor& rAnchor = GetAnchor(); + if (rAnchor.GetContentAnchor() != nullptr) + { + rAnchor.GetContentAnchor()->nNode.GetNode().RemoveAnchoredFly(this); + } + } + + if( nullptr != m_pOtherTextBoxFormat ) + { + m_pOtherTextBoxFormat->SetOtherTextBoxFormat( nullptr ); + m_pOtherTextBoxFormat = nullptr; + } +} + +void SwFrameFormat::SetName( const OUString& rNewName, bool bBroadcast ) +{ + if (m_ffList != nullptr) { + SwFrameFormats::iterator it = m_ffList->find( this ); + assert( m_ffList->end() != it ); + SAL_INFO_IF(m_aFormatName == rNewName, "sw.core", "SwFrmFmt not really renamed, as both names are equal"); + + SwStringMsgPoolItem aOld( RES_NAME_CHANGED, m_aFormatName ); + // As it's a non-unique list, rename should never fail! + bool const renamed = + m_ffList->m_PosIndex.modify( it, + change_name( rNewName ), change_name( m_aFormatName ) ); + assert(renamed); + (void)renamed; // unused in NDEBUG + if (bBroadcast) { + SwStringMsgPoolItem aNew( RES_NAME_CHANGED, rNewName ); + ModifyNotification( &aOld, &aNew ); + } + } + else + SwFormat::SetName( rNewName, bBroadcast ); +} + +void SwFrameFormat::SetOtherTextBoxFormat( SwFrameFormat *pFormat ) +{ + if( nullptr != pFormat ) + { + assert( (Which() == RES_DRAWFRMFMT && pFormat->Which() == RES_FLYFRMFMT) + || (Which() == RES_FLYFRMFMT && pFormat->Which() == RES_DRAWFRMFMT) ); + assert( nullptr == m_pOtherTextBoxFormat ); + } + else + { + assert( nullptr != m_pOtherTextBoxFormat ); + } + bool bChanged = m_pOtherTextBoxFormat != pFormat; + m_pOtherTextBoxFormat = pFormat; + + SdrObject* pObj = FindSdrObject(); + + if (pObj) + { + SwFlyDrawObj* pSwFlyDraw = dynamic_cast<SwFlyDrawObj*>(pObj); + + if (pSwFlyDraw) + pSwFlyDraw->SetTextBox(true); + } + + if (m_pOtherTextBoxFormat && bChanged && Which() == RES_DRAWFRMFMT) + { + // This is a shape of a shape+frame pair and my frame has changed. Make sure my content is + // in sync with the frame's content. + if (GetAttrSet().GetContent() != m_pOtherTextBoxFormat->GetAttrSet().GetContent()) + { + SwAttrSet aSet(GetAttrSet()); + SwFormatContent aContent(m_pOtherTextBoxFormat->GetAttrSet().GetContent()); + aSet.Put(aContent); + SetFormatAttr(aSet); + } + } +} + +bool SwFrameFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return true; +} + +void SwFrameFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if (pNew) + { + SwFormatHeader const *pH = nullptr; + SwFormatFooter const *pF = nullptr; + + const sal_uInt16 nWhich = pNew->Which(); + + if( RES_ATTRSET_CHG == nWhich ) + { + static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( + RES_HEADER, false, reinterpret_cast<const SfxPoolItem**>(&pH) ); + static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( + RES_FOOTER, false, reinterpret_cast<const SfxPoolItem**>(&pF) ); + + // reset fill information + if (maFillAttributes && supportsFullDrawingLayerFillAttributeSet()) + { + SfxItemIter aIter(*static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()); + bool bReset(false); + + for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem && !bReset; pItem = aIter.NextItem()) + { + bReset = !IsInvalidItem(pItem) && pItem->Which() >= XATTR_FILL_FIRST && pItem->Which() <= XATTR_FILL_LAST; + } + + if(bReset) + { + maFillAttributes.reset(); + } + } + } + else if(RES_FMT_CHG == nWhich) + { + // reset fill information on format change (e.g. style changed) + if (maFillAttributes && supportsFullDrawingLayerFillAttributeSet()) + { + maFillAttributes.reset(); + } + } + else if( RES_HEADER == nWhich ) + pH = static_cast<const SwFormatHeader*>(pNew); + else if( RES_FOOTER == nWhich ) + pF = static_cast<const SwFormatFooter*>(pNew); + + if( pH && pH->IsActive() && !pH->GetHeaderFormat() ) + { //If he doesn't have one, I'll add one + SwFrameFormat *pFormat = GetDoc()->getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::HEADER, nullptr ); + const_cast<SwFormatHeader *>(pH)->RegisterToFormat( *pFormat ); + } + + if( pF && pF->IsActive() && !pF->GetFooterFormat() ) + { //If he doesn't have one, I'll add one + SwFrameFormat *pFormat = GetDoc()->getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::FOOTER, nullptr ); + const_cast<SwFormatFooter *>(pF)->RegisterToFormat( *pFormat ); + } + } + + SwFormat::Modify( pOld, pNew ); + + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXObject(uno::Reference<uno::XInterface>(nullptr)); + } + + const SwPosition* oldAnchorPosition = nullptr; + const SwPosition* newAnchorPosition = nullptr; + if( pNew && pNew->Which() == RES_ATTRSET_CHG ) + { + const SfxPoolItem* tmp = nullptr; + static_cast< const SwAttrSetChg* >(pNew)->GetChgSet()->GetItemState( RES_ANCHOR, false, &tmp ); + if( tmp ) + { + newAnchorPosition = static_cast< const SwFormatAnchor* >( tmp )->GetContentAnchor(); + assert(newAnchorPosition == nullptr || // style's set must not contain position! + static_cast<SwAttrSetChg const*>(pNew)->GetTheChgdSet() == &m_aSet); + } + } + if( pNew && pNew->Which() == RES_ANCHOR ) + newAnchorPosition = static_cast< const SwFormatAnchor* >( pNew )->GetContentAnchor(); + if( pOld && pOld->Which() == RES_ATTRSET_CHG ) + { + const SfxPoolItem* tmp = nullptr; + static_cast< const SwAttrSetChg* >(pOld)->GetChgSet()->GetItemState( RES_ANCHOR, false, &tmp ); + if( tmp ) + { + oldAnchorPosition = static_cast< const SwFormatAnchor* >( tmp )->GetContentAnchor(); + assert(oldAnchorPosition == nullptr || // style's set must not contain position! + static_cast<SwAttrSetChg const*>(pOld)->GetTheChgdSet() == &m_aSet); + } + } + if( pOld && pOld->Which() == RES_ANCHOR ) + oldAnchorPosition = static_cast< const SwFormatAnchor* >( pOld )->GetContentAnchor(); + if( oldAnchorPosition != nullptr && ( newAnchorPosition == nullptr || oldAnchorPosition->nNode.GetIndex() != newAnchorPosition->nNode.GetIndex())) + { + oldAnchorPosition->nNode.GetNode().RemoveAnchoredFly(this); + } + if( newAnchorPosition != nullptr && ( oldAnchorPosition == nullptr || oldAnchorPosition->nNode.GetIndex() != newAnchorPosition->nNode.GetIndex())) + { + newAnchorPosition->nNode.GetNode().AddAnchoredFly(this); + } +} + +void SwFrameFormat::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +/// Delete all Frames that are registered in aDepend. +void SwFrameFormat::DelFrames() +{ + SwIterator<SwFrame,SwFormat> aIter( *this ); + SwFrame * pLast = aIter.First(); + if( pLast ) + do { + pLast->Cut(); + SwFrame::DestroyFrame(pLast); + } while( nullptr != ( pLast = aIter.Next() )); +} + +void SwFrameFormat::MakeFrames() +{ + assert(false); // unimplemented in base class +} + +SwRect SwFrameFormat::FindLayoutRect( const bool bPrtArea, const Point* pPoint ) const +{ + SwRect aRet; + SwFrame *pFrame = nullptr; + if( dynamic_cast<const SwSectionFormat*>( this ) != nullptr ) + { + // get the Frame using Node2Layout + const SwSectionNode* pSectNd = static_cast<const SwSectionFormat*>(this)->GetSectionNode(); + if( pSectNd ) + { + SwNode2Layout aTmp( *pSectNd, pSectNd->GetIndex() - 1 ); + pFrame = aTmp.NextFrame(); + + if( pFrame && !pFrame->KnowsFormat(*this) ) + { + // the Section doesn't have his own Frame, so if someone + // needs the real size, we have to implement this by requesting + // the matching Frame from the end. + // PROBLEM: what happens if SectionFrames overlaps multiple + // pages? + if( bPrtArea ) + aRet = pFrame->getFramePrintArea(); + else + { + aRet = pFrame->getFrameArea(); + aRet.Pos().AdjustY( -1 ); + } + pFrame = nullptr; // the rect is finished by now + } + } + } + else + { + const SwFrameType nFrameType = RES_FLYFRMFMT == Which() ? SwFrameType::Fly : FRM_ALL; + std::pair<Point, bool> tmp; + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = false; + } + pFrame = ::GetFrameOfModify(nullptr, *this, nFrameType, nullptr, pPoint ? &tmp : nullptr); + } + + if( pFrame ) + { + if( bPrtArea ) + aRet = pFrame->getFramePrintArea(); + else + aRet = pFrame->getFrameArea(); + } + return aRet; +} + +SdrObject* SwFrameFormat::FindRealSdrObject() +{ + if( RES_FLYFRMFMT == Which() ) + { + Point aNullPt; + std::pair<Point, bool> const tmp(aNullPt, false); + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(::GetFrameOfModify( nullptr, *this, SwFrameType::Fly, + nullptr, &tmp)); + return pFly ? pFly->GetVirtDrawObj() : nullptr; + } + return FindSdrObject(); +} + +bool SwFrameFormat::IsLowerOf( const SwFrameFormat& rFormat ) const +{ + //Also linking from inside to outside or from outside to inside is not + //allowed. + SwFlyFrame *pSFly = SwIterator<SwFlyFrame,SwFormat>(*this).First(); + if( pSFly ) + { + SwFlyFrame *pAskFly = SwIterator<SwFlyFrame,SwFormat>(rFormat).First(); + if( pAskFly ) + return pSFly->IsLowerOf( pAskFly ); + } + + // let's try it using the node positions + const SwFormatAnchor* pAnchor = &rFormat.GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) && pAnchor->GetContentAnchor()) + { + const SwFrameFormats& rFormats = *GetDoc()->GetSpzFrameFormats(); + const SwNode* pFlyNd = pAnchor->GetContentAnchor()->nNode.GetNode(). + FindFlyStartNode(); + while( pFlyNd ) + { + // then we walk up using the anchor + size_t n; + for( n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFormat = rFormats[ n ]; + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + { + if( pFormat == this ) + return true; + + pAnchor = &pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId()) || + !pAnchor->GetContentAnchor() ) + { + return false; + } + + pFlyNd = pAnchor->GetContentAnchor()->nNode.GetNode(). + FindFlyStartNode(); + break; + } + } + if( n >= rFormats.size() ) + { + OSL_ENSURE( false, "Fly section but no format found" ); + return false; + } + } + } + return false; +} + +// #i31698# +SwFrameFormat::tLayoutDir SwFrameFormat::GetLayoutDir() const +{ + return SwFrameFormat::HORI_L2R; +} + +void SwFrameFormat::SetLayoutDir( const SwFrameFormat::tLayoutDir ) +{ + // empty body, because default implementation does nothing +} + +// #i28749# +sal_Int16 SwFrameFormat::GetPositionLayoutDir() const +{ + return text::PositionLayoutDir::PositionInLayoutDirOfAnchor; +} +void SwFrameFormat::SetPositionLayoutDir( const sal_Int16 ) +{ + // empty body, because default implementation does nothing +} + +OUString SwFrameFormat::GetDescription() const +{ + return SwResId(STR_FRAME); +} + +void SwFrameFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFrameFormat")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + + const char* pWhich = nullptr; + switch (Which()) + { + case RES_FLYFRMFMT: + pWhich = "fly frame format"; + break; + case RES_DRAWFRMFMT: + pWhich = "draw frame format"; + break; + } + if (pWhich) + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("which"), BAD_CAST(pWhich)); + + if (m_pOtherTextBoxFormat) + { + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("OtherTextBoxFormat"), "%p", m_pOtherTextBoxFormat); + } + + GetAttrSet().dumpAsXml(pWriter); + + if (const SdrObject* pSdrObject = FindSdrObject()) + pSdrObject->dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +void SwFrameFormats::dumpAsXml(xmlTextWriterPtr pWriter, const char* pName) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); + for (const SwFrameFormat *pFormat : m_PosIndex) + pFormat->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + + +SwFlyFrameFormat::SwFlyFrameFormat( SwAttrPool& rPool, const OUString &rFormatNm, SwFrameFormat *pDrvdFrame ) + : SwFrameFormat( rPool, rFormatNm, pDrvdFrame, RES_FLYFRMFMT ) +{} + +SwFlyFrameFormat::~SwFlyFrameFormat() +{ + SwIterator<SwFlyFrame,SwFormat> aIter( *this ); + SwFlyFrame * pLast = aIter.First(); + if( pLast ) + do + { + SwFrame::DestroyFrame(pLast); + } while( nullptr != ( pLast = aIter.Next() )); + +} + +SwFlyDrawContact* SwFlyFrameFormat::GetOrCreateContact() +{ + if(!m_pContact) + { + SwDrawModel* pDrawModel(GetDoc()->getIDocumentDrawModelAccess().GetOrCreateDrawModel()); + m_pContact.reset(new SwFlyDrawContact(this, *pDrawModel)); + } + + return m_pContact.get(); +} + +/// Creates the Frames if the format describes a paragraph-bound frame. +/// MA: 1994-02-14: creates the Frames also for frames anchored at page. +void SwFlyFrameFormat::MakeFrames() +{ + // is there a layout? + if( !GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) + return; + + SwModify *pModify = nullptr; + // OD 24.07.2003 #111032# - create local copy of anchor attribute for possible changes. + SwFormatAnchor aAnchorAttr( GetAnchor() ); + switch( aAnchorAttr.GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + if( aAnchorAttr.GetContentAnchor() ) + { + pModify = aAnchorAttr.GetContentAnchor()->nNode.GetNode().GetContentNode(); + } + break; + + case RndStdIds::FLY_AT_FLY: + if( aAnchorAttr.GetContentAnchor() ) + { + //First search in the content because this is O(1) + //This can go wrong for linked frames because in this case it's + //possible, that no Frame exists for this content. + //In such a situation we also need to search from StartNode to + //FrameFormat. + SwNodeIndex aIdx( aAnchorAttr.GetContentAnchor()->nNode ); + SwContentNode *pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + // #i105535# + if ( pCNd == nullptr ) + { + pCNd = aAnchorAttr.GetContentAnchor()->nNode.GetNode().GetContentNode(); + } + if ( pCNd ) + { + if (SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*pCNd).First()) + { + pModify = pCNd; + } + } + // #i105535# + if ( pModify == nullptr ) + { + const SwNodeIndex &rIdx = aAnchorAttr.GetContentAnchor()->nNode; + SwFrameFormats& rFormats = *GetDoc()->GetSpzFrameFormats(); + for( size_t i = 0; i < rFormats.size(); ++i ) + { + SwFrameFormat* pFlyFormat = rFormats[i]; + if( pFlyFormat->GetContent().GetContentIdx() && + rIdx == *pFlyFormat->GetContent().GetContentIdx() ) + { + pModify = pFlyFormat; + break; + } + } + } + } + break; + + case RndStdIds::FLY_AT_PAGE: + { + sal_uInt16 nPgNum = aAnchorAttr.GetPageNum(); + SwPageFrame *pPage = static_cast<SwPageFrame*>(GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()->Lower()); + if( nPgNum == 0 && aAnchorAttr.GetContentAnchor() ) + { + SwContentNode *pCNd = aAnchorAttr.GetContentAnchor()->nNode.GetNode().GetContentNode(); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pCNd); + for ( SwFrame* pFrame = aIter.First(); pFrame != nullptr; pFrame = aIter.Next() ) + { + pPage = pFrame->FindPageFrame(); + if( pPage ) + { + nPgNum = pPage->GetPhyPageNum(); + aAnchorAttr.SetPageNum( nPgNum ); + aAnchorAttr.SetAnchor( nullptr ); + SetFormatAttr( aAnchorAttr ); + break; + } + } + } + while ( pPage ) + { + if ( pPage->GetPhyPageNum() == nPgNum ) + { + // #i50432# - adjust synopsis of <PlaceFly(..)> + pPage->PlaceFly( nullptr, this ); + break; + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + } + break; + default: + break; + } + + if( pModify ) + { + SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti> aIter(*pModify); + for( SwFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + bool bAdd = !pFrame->IsContentFrame() || + !static_cast<SwContentFrame*>(pFrame)->IsFollow(); + + if ( RndStdIds::FLY_AT_FLY == aAnchorAttr.GetAnchorId() && !pFrame->IsFlyFrame() ) + { + SwFrame* pFlyFrame = pFrame->FindFlyFrame(); + if ( pFlyFrame ) + { + pFrame = pFlyFrame; + } + else + { + aAnchorAttr.SetType( RndStdIds::FLY_AT_PARA ); + SetFormatAttr( aAnchorAttr ); + MakeFrames(); + return; + } + } + + if (bAdd) + { + switch (aAnchorAttr.GetAnchorId()) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + { + assert(pFrame->IsTextFrame()); + bAdd = IsAnchoredObjShown(*static_cast<SwTextFrame*>(pFrame), aAnchorAttr); + } + break; + default: + break; + } + } + + if (bAdd && pFrame->GetDrawObjs()) + { + // #i28701# - new type <SwSortedObjs> + SwSortedObjs &rObjs = *pFrame->GetDrawObjs(); + for(SwAnchoredObject* pObj : rObjs) + { + // #i28701# - consider changed type of + // <SwSortedObjs> entries. + if( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr && + (&pObj->GetFrameFormat()) == this ) + { + bAdd = false; + break; + } + } + } + + if( bAdd ) + { + SwFlyFrame *pFly = nullptr; // avoid warnings + switch( aAnchorAttr.GetAnchorId() ) + { + case RndStdIds::FLY_AT_FLY: + pFly = new SwFlyLayFrame( this, pFrame, pFrame ); + break; + + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + pFly = new SwFlyAtContentFrame( this, pFrame, pFrame ); + break; + + case RndStdIds::FLY_AS_CHAR: + pFly = new SwFlyInContentFrame( this, pFrame, pFrame ); + break; + + default: + assert(false && "New anchor type" ); + } + pFrame->AppendFly( pFly ); + pFly->GetFormat()->SetObjTitle(GetObjTitle()); + pFly->GetFormat()->SetObjDescription(GetObjDescription()); + SwPageFrame *pPage = pFly->FindPageFrame(); + if( pPage ) + ::RegistFlys( pPage, pFly ); + } + } + } +} + +SwFlyFrame* SwFlyFrameFormat::GetFrame( const Point* pPoint ) const +{ + std::pair<Point, bool> tmp; + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = false; + } + return static_cast<SwFlyFrame*>(::GetFrameOfModify( nullptr, *this, SwFrameType::Fly, + nullptr, &tmp)); +} + +SwAnchoredObject* SwFlyFrameFormat::GetAnchoredObj() const +{ + SwFlyFrame* pFlyFrame( GetFrame() ); + if ( pFlyFrame ) + { + return dynamic_cast<SwAnchoredObject*>(pFlyFrame); + } + else + { + return nullptr; + } +} + +bool SwFlyFrameFormat::GetInfo( SfxPoolItem& rInfo ) const +{ + bool bRet = true; + switch( rInfo.Which() ) + { + case RES_CONTENT_VISIBLE: + { + static_cast<SwPtrMsgPoolItem&>(rInfo).pObject = SwIterator<SwFrame,SwFormat>( *this ).First(); + } + bRet = false; + break; + + default: + bRet = SwFrameFormat::GetInfo( rInfo ); + break; + } + return bRet; +} + +// #i73249# +void SwFlyFrameFormat::SetObjTitle( const OUString& rTitle, bool bBroadcast ) +{ + SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::SetObjTitle(..)> - missing <SdrObject> instance" ); + msTitle = rTitle; + if ( !pMasterObject ) + { + return; + } + + if( bBroadcast ) + { + SwStringMsgPoolItem aOld( RES_TITLE_CHANGED, pMasterObject->GetTitle() ); + SwStringMsgPoolItem aNew( RES_TITLE_CHANGED, rTitle ); + pMasterObject->SetTitle( rTitle ); + ModifyNotification( &aOld, &aNew ); + } + else + { + pMasterObject->SetTitle( rTitle ); + } +} + +OUString SwFlyFrameFormat::GetObjTitle() const +{ + const SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::GetObjTitle(..)> - missing <SdrObject> instance" ); + if ( !pMasterObject ) + { + return msTitle; + } + if (!pMasterObject->GetTitle().isEmpty()) + return pMasterObject->GetTitle(); + else + return msTitle; +} + +void SwFlyFrameFormat::SetObjDescription( const OUString& rDescription, bool bBroadcast ) +{ + SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::SetDescription(..)> - missing <SdrObject> instance" ); + msDesc = rDescription; + if ( !pMasterObject ) + { + return; + } + + if( bBroadcast ) + { + SwStringMsgPoolItem aOld( RES_DESCRIPTION_CHANGED, pMasterObject->GetDescription() ); + SwStringMsgPoolItem aNew( RES_DESCRIPTION_CHANGED, rDescription ); + pMasterObject->SetDescription( rDescription ); + ModifyNotification( &aOld, &aNew ); + } + else + { + pMasterObject->SetDescription( rDescription ); + } +} + +OUString SwFlyFrameFormat::GetObjDescription() const +{ + const SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::GetDescription(..)> - missing <SdrObject> instance" ); + if ( !pMasterObject ) + { + return msDesc; + } + if (!pMasterObject->GetDescription().isEmpty()) + return pMasterObject->GetDescription(); + else + return msDesc; +} + +/** SwFlyFrameFormat::IsBackgroundTransparent - for #99657# + + OD 22.08.2002 - overriding virtual method and its default implementation, + because format of fly frame provides transparent backgrounds. + Method determines, if background of fly frame is transparent. + + @return true, if background color is transparent, but not "no fill" + or the transparency of an existing background graphic is set. +*/ +bool SwFlyFrameFormat::IsBackgroundTransparent() const +{ + if (supportsFullDrawingLayerFillAttributeSet() && getSdrAllFillAttributesHelper()) + { + return getSdrAllFillAttributesHelper()->isTransparent(); + } + + // NOTE: If background color is "no fill"/"auto fill" (COL_TRANSPARENT) + // and there is no background graphic, it "inherites" the background + // from its anchor. + std::unique_ptr<SvxBrushItem> aBackground(makeBackgroundBrushItem()); + if ( aBackground && + (aBackground->GetColor().GetTransparency() != 0) && + (aBackground->GetColor() != COL_TRANSPARENT) + ) + { + return true; + } + else + { + const GraphicObject *pTmpGrf = aBackground->GetGraphicObject(); + if ( pTmpGrf && + (pTmpGrf->GetAttr().GetTransparency() != 0) + ) + { + return true; + } + } + + return false; +} + +/** SwFlyFrameFormat::IsBackgroundBrushInherited - for #103898# + + OD 08.10.2002 - method to determine, if the brush for drawing the + background is "inherited" from its parent/grandparent. + This is the case, if no background graphic is set and the background + color is "no fill"/"auto fill" + NOTE: condition is "copied" from method <SwFrame::GetBackgroundBrush(..). + + @return true, if background brush is "inherited" from parent/grandparent +*/ +bool SwFlyFrameFormat::IsBackgroundBrushInherited() const +{ + if (supportsFullDrawingLayerFillAttributeSet() && getSdrAllFillAttributesHelper()) + { + return !getSdrAllFillAttributesHelper()->isUsed(); + } + else + { + std::unique_ptr<SvxBrushItem> aBackground(makeBackgroundBrushItem()); + if ( aBackground && + (aBackground->GetColor() == COL_TRANSPARENT) && + !(aBackground->GetGraphicObject()) ) + { + return true; + } + } + + return false; +} + +SwHandleAnchorNodeChg::SwHandleAnchorNodeChg( SwFlyFrameFormat& _rFlyFrameFormat, + const SwFormatAnchor& _rNewAnchorFormat, + SwFlyFrame const * _pKeepThisFlyFrame ) + : mrFlyFrameFormat( _rFlyFrameFormat ), + mbAnchorNodeChanged( false ), + mpWrtShell(nullptr) +{ + const SwFormatAnchor& aOldAnchorFormat(_rFlyFrameFormat.GetAnchor()); + const RndStdIds nNewAnchorType( _rNewAnchorFormat.GetAnchorId() ); + if ( ((nNewAnchorType == RndStdIds::FLY_AT_PARA) || + (nNewAnchorType == RndStdIds::FLY_AT_CHAR)) && + _rNewAnchorFormat.GetContentAnchor() && + _rNewAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode() ) + { + if ( aOldAnchorFormat.GetAnchorId() == nNewAnchorType && + aOldAnchorFormat.GetContentAnchor() && + aOldAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode() && + aOldAnchorFormat.GetContentAnchor()->nNode != + _rNewAnchorFormat.GetContentAnchor()->nNode ) + { + // determine 'old' number of anchor frames + sal_uInt32 nOldNumOfAnchFrame( 0 ); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aOldIter( + *(aOldAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode()) ); + for( SwFrame* pOld = aOldIter.First(); pOld; pOld = aOldIter.Next() ) + { + ++nOldNumOfAnchFrame; + } + // determine 'new' number of anchor frames + sal_uInt32 nNewNumOfAnchFrame( 0 ); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aNewIter( + *(_rNewAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode()) ); + for( SwFrame* pNew = aNewIter.First(); pNew; pNew = aNewIter.Next() ) + { + ++nNewNumOfAnchFrame; + } + if ( nOldNumOfAnchFrame != nNewNumOfAnchFrame ) + { + // delete existing fly frames except <_pKeepThisFlyFrame> + SwIterator<SwFrame,SwFormat> aIter( mrFlyFrameFormat ); + SwFrame* pFrame = aIter.First(); + if ( pFrame ) + { + do { + if ( pFrame != _pKeepThisFlyFrame ) + { + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } + } while( nullptr != ( pFrame = aIter.Next() )); + } + // indicate, that re-creation of fly frames necessary + mbAnchorNodeChanged = true; + } + } + } + + if (aOldAnchorFormat.GetContentAnchor() + && aOldAnchorFormat.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + mpCommentAnchor.reset(new SwPosition(*aOldAnchorFormat.GetContentAnchor())); + } + + if (_pKeepThisFlyFrame) + { + SwViewShell* pViewShell = _pKeepThisFlyFrame->getRootFrame()->GetCurrShell(); + mpWrtShell = dynamic_cast<SwWrtShell*>(pViewShell); + } +} + +SwHandleAnchorNodeChg::~SwHandleAnchorNodeChg() COVERITY_NOEXCEPT_FALSE +{ + if ( mbAnchorNodeChanged ) + { + mrFlyFrameFormat.MakeFrames(); + } + + // See if the fly frame had a comment: if so, move it to the new anchor as well. + if (!mpCommentAnchor) + { + return; + } + + SwTextNode* pTextNode = mpCommentAnchor->nNode.GetNode().GetTextNode(); + if (!pTextNode) + { + return; + } + + const SwTextField* pField = pTextNode->GetFieldTextAttrAt(mpCommentAnchor->nContent.GetIndex()); + if (!pField || pField->GetFormatField().GetField()->GetTyp()->Which() != SwFieldIds::Postit) + { + return; + } + + if (!mpWrtShell) + { + return; + } + + // Save current cursor position, so we can restore it later. + mpWrtShell->Push(); + + // Set up the source of the move: the old comment anchor. + { + SwPaM& rCursor = mpWrtShell->GetCurrentShellCursor(); + *rCursor.GetPoint() = *mpCommentAnchor; + rCursor.SetMark(); + *rCursor.GetMark() = *mpCommentAnchor; + ++rCursor.GetMark()->nContent; + } + + // Set up the target of the move: the new comment anchor. + const SwFormatAnchor& rNewAnchorFormat = mrFlyFrameFormat.GetAnchor(); + mpWrtShell->CreateCursor(); + *mpWrtShell->GetCurrentShellCursor().GetPoint() = *rNewAnchorFormat.GetContentAnchor(); + + // Move by copying and deleting. + mpWrtShell->SwEditShell::Copy(mpWrtShell); + mpWrtShell->DestroyCursor(); + + mpWrtShell->Delete(); + + mpWrtShell->Pop(SwCursorShell::PopMode::DeleteCurrent); +} + +namespace sw +{ + DrawFrameFormatHint::~DrawFrameFormatHint() {} + CheckDrawFrameFormatLayerHint::~CheckDrawFrameFormatLayerHint() {} + ContactChangedHint::~ContactChangedHint() {} + DrawFormatLayoutCopyHint::~DrawFormatLayoutCopyHint() {} + WW8AnchorConvHint::~WW8AnchorConvHint() {} + RestoreFlyAnchorHint::~RestoreFlyAnchorHint() {} + CreatePortionHint::~CreatePortionHint() {} + FindSdrObjectHint::~FindSdrObjectHint() {} + CollectTextObjectsHint::~CollectTextObjectsHint() {} + GetZOrderHint::~GetZOrderHint() {} + GetObjectConnectedHint::~GetObjectConnectedHint() {} +} + +SwDrawFrameFormat::~SwDrawFrameFormat() +{ + CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::DYING)); +} + +void SwDrawFrameFormat::MakeFrames() +{ + CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::MAKE_FRAMES)); +} + +void SwDrawFrameFormat::DelFrames() +{ + CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::DELETE_FRAMES)); +} + +// #i31698# +SwFrameFormat::tLayoutDir SwDrawFrameFormat::GetLayoutDir() const +{ + return meLayoutDir; +} + +void SwDrawFrameFormat::SetLayoutDir( const SwFrameFormat::tLayoutDir _eLayoutDir ) +{ + meLayoutDir = _eLayoutDir; +} + +// #i28749# +sal_Int16 SwDrawFrameFormat::GetPositionLayoutDir() const +{ + return mnPositionLayoutDir; +} +void SwDrawFrameFormat::SetPositionLayoutDir( const sal_Int16 _nPositionLayoutDir ) +{ + switch ( _nPositionLayoutDir ) + { + case text::PositionLayoutDir::PositionInHoriL2R: + case text::PositionLayoutDir::PositionInLayoutDirOfAnchor: + { + mnPositionLayoutDir = _nPositionLayoutDir; + } + break; + default: + { + OSL_FAIL( "<SwDrawFrameFormat::SetPositionLayoutDir(..)> - invalid attribute value." ); + } + } +} + +OUString SwDrawFrameFormat::GetDescription() const +{ + OUString aResult; + const SdrObject * pSdrObj = FindSdrObject(); + + if (pSdrObj) + { + if (pSdrObj != m_pSdrObjectCached) + { + m_sSdrObjectCachedComment = SdrUndoNewObj::GetComment(*pSdrObj); + m_pSdrObjectCached = pSdrObj; + } + + aResult = m_sSdrObjectCachedComment; + } + else + aResult = SwResId(STR_GRAPHIC); + + return aResult; +} + +IMapObject* SwFrameFormat::GetIMapObject( const Point& rPoint, + const SwFlyFrame *pFly ) const +{ + const SwFormatURL &rURL = GetURL(); + if( !rURL.GetMap() ) + return nullptr; + + if( !pFly ) + { + pFly = SwIterator<SwFlyFrame,SwFormat>( *this ).First(); + if( !pFly ) + return nullptr; + } + + //Original size for OLE and graphic is TwipSize, otherwise the size of + //FrameFormat of the Fly. + const SwFrame *pRef; + const SwNoTextNode *pNd = nullptr; + Size aOrigSz; + if( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + pRef = pFly->Lower(); + pNd = static_cast<const SwNoTextFrame*>(pRef)->GetNode()->GetNoTextNode(); + aOrigSz = pNd->GetTwipSize(); + } + else + { + pRef = pFly; + aOrigSz = pFly->GetFormat()->GetFrameSize().GetSize(); + } + + if( !aOrigSz.IsEmpty() ) + { + Point aPos( rPoint ); + Size aActSz ( pRef == pFly ? pFly->getFrameArea().SSize() : pRef->getFramePrintArea().SSize() ); + const MapMode aSrc ( MapUnit::MapTwip ); + const MapMode aDest( MapUnit::Map100thMM ); + aOrigSz = OutputDevice::LogicToLogic( aOrigSz, aSrc, aDest ); + aActSz = OutputDevice::LogicToLogic( aActSz, aSrc, aDest ); + aPos -= pRef->getFrameArea().Pos(); + aPos -= pRef->getFramePrintArea().Pos(); + aPos = OutputDevice::LogicToLogic( aPos, aSrc, aDest ); + sal_uInt32 nFlags = 0; + if ( pFly != pRef && pNd->IsGrfNode() ) + { + const MirrorGraph nMirror = pNd->GetSwAttrSet(). + GetMirrorGrf().GetValue(); + if ( MirrorGraph::Both == nMirror ) + nFlags = IMAP_MIRROR_HORZ | IMAP_MIRROR_VERT; + else if ( MirrorGraph::Vertical == nMirror ) + nFlags = IMAP_MIRROR_VERT; + else if ( MirrorGraph::Horizontal == nMirror ) + nFlags = IMAP_MIRROR_HORZ; + + } + return const_cast<ImageMap*>(rURL.GetMap())->GetHitIMapObject( aOrigSz, + aActSz, aPos, nFlags ); + } + + return nullptr; +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwFrameFormat::getSdrAllFillAttributesHelper() const +{ + if (supportsFullDrawingLayerFillAttributeSet()) + { + // create FillAttributes on demand + if(!maFillAttributes) + { + const_cast< SwFrameFormat* >(this)->maFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(GetAttrSet()); + } + } + else + { + // FALLBACKBREAKHERE assert wrong usage + OSL_ENSURE(false, "getSdrAllFillAttributesHelper() call only valid for RES_FLYFRMFMT and RES_FRMFMT (!)"); + } + + return maFillAttributes; +} + +namespace sw { + +bool IsFlyFrameFormatInHeader(const SwFrameFormat& rFormat) +{ + const SwFlyFrameFormat* pFlyFrameFormat = dynamic_cast<const SwFlyFrameFormat*>(&rFormat); + if (!pFlyFrameFormat) + return false; + SwFlyFrame* pFlyFrame = pFlyFrameFormat->GetFrame(); + if (!pFlyFrame) // fdo#54648: "hidden" drawing object has no layout frame + { + return false; + } + SwPageFrame* pPageFrame = pFlyFrame->FindPageFrameOfAnchor(); + SwFrame* pHeader = pPageFrame->Lower(); + if (pHeader->GetType() == SwFrameType::Header) + { + const SwFrame* pFrame = pFlyFrame->GetAnchorFrame(); + while (pFrame) + { + if (pFrame == pHeader) + return true; + pFrame = pFrame->GetUpper(); + } + } + return false; +} + +void CheckAnchoredFlyConsistency(SwDoc const& rDoc) +{ +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + SwNodes const& rNodes(rDoc.GetNodes()); + sal_uLong const count(rNodes.Count()); + for (sal_uLong i = 0; i != count; ++i) + { + SwNode const*const pNode(rNodes[i]); + std::vector<SwFrameFormat*> const*const pFlys(pNode->GetAnchoredFlys()); + if (pFlys) + { + for (const auto& rpFly : *pFlys) + { + SwFormatAnchor const& rAnchor((*rpFly).GetAnchor(false)); + assert(&rAnchor.GetContentAnchor()->nNode.GetNode() == pNode); + } + } + } + SwFrameFormats const*const pSpzFrameFormats(rDoc.GetSpzFrameFormats()); + if (pSpzFrameFormats) + { + for (auto it = pSpzFrameFormats->begin(); it != pSpzFrameFormats->end(); ++it) + { + SwFormatAnchor const& rAnchor((**it).GetAnchor(false)); + if (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) + { + assert(!rAnchor.GetContentAnchor() + // for invalid documents that lack text:anchor-page-number + // it may have an anchor before MakeFrames() is called + || (!SwIterator<SwFrame, SwFrameFormat>(**it).First())); + } + else + { + SwNode & rNode(rAnchor.GetContentAnchor()->nNode.GetNode()); + std::vector<SwFrameFormat*> const*const pFlys(rNode.GetAnchoredFlys()); + assert(std::find(pFlys->begin(), pFlys->end(), *it) != pFlys->end()); + switch (rAnchor.GetAnchorId()) + { + case RndStdIds::FLY_AT_FLY: + assert(rNode.IsStartNode()); + break; + case RndStdIds::FLY_AT_PARA: + assert(rNode.IsTextNode() || rNode.IsTableNode()); + break; + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + assert(rNode.IsTextNode()); + break; + default: + assert(false); + break; + } + } + } + } +#else + (void) rDoc; +#endif +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/calcmove.cxx b/sw/source/core/layout/calcmove.cxx new file mode 100644 index 000000000..6fb4ac868 --- /dev/null +++ b/sw/source/core/layout/calcmove.cxx @@ -0,0 +1,2232 @@ +/* -*- 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 <memory> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <ndtxt.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/keepitem.hxx> +#include <svx/sdtaitm.hxx> + +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <fmtclbl.hxx> + +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <dbg_lay.hxx> + +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include <flyfrms.hxx> + +#include <DocumentSettingManager.hxx> +#include <IDocumentLayoutAccess.hxx> + +// Move methods + +/// Return value tells whether the Frame should be moved. +bool SwContentFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool & ) +{ + if ( SwFlowFrame::IsMoveBwdJump() || !IsPrevObjMove() ) + { + // Floating back a frm uses a bit of time unfortunately. + // The most common case is the following: The Frame wants to float to + // somewhere where the FixSize is the same that the Frame itself has already. + // In that case it's pretty easy to check if the Frame has enough space + // for its VarSize. If this is NOT the case, we already know that + // we don't need to move. + // The Frame checks itself whether it has enough space - respecting the fact + // that it could possibly split itself if needed. + // If, however, the FixSize differs from the Frame or Flys are involved + // (either in the old or the new position), checking is pointless, + // and we have to move the Frame just to see what happens - if there's + // some space available to do it, that is. + + // The FixSize of the containers of Contents is always the width. + + // If we moved more than one sheet back (for example jumping over empty + // pages), we have to move either way. Otherwise, if the Frame doesn't fit + // into the page, empty pages wouldn't be respected anymore. + sal_uInt8 nMoveAnyway = 0; + SwPageFrame * const pNewPage = pNewUpper->FindPageFrame(); + SwPageFrame *pOldPage = FindPageFrame(); + + if ( SwFlowFrame::IsMoveBwdJump() ) + return true; + + if( IsInFootnote() && IsInSct() ) + { + SwFootnoteFrame* pFootnote = FindFootnoteFrame(); + SwSectionFrame* pMySect = pFootnote->FindSctFrame(); + if( pMySect && pMySect->IsFootnoteLock() ) + { + SwSectionFrame *pSect = pNewUpper->FindSctFrame(); + while( pSect && pSect->IsInFootnote() ) + pSect = pSect->GetUpper()->FindSctFrame(); + OSL_ENSURE( pSect, "Escaping footnote" ); + if( pSect != pMySect ) + return false; + } + } + SwRectFnSet aRectFnSet(this); + SwRectFnSet fnRectX(pNewUpper); + if( std::abs( fnRectX.GetWidth(pNewUpper->getFramePrintArea()) - + aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ) > 1 ) { + // In this case, only a WouldFit_ with test move is possible + nMoveAnyway = 2; + } + + // Do *not* move backward, if <nMoveAnyway> equals 3 and no space is left in new upper. + nMoveAnyway |= BwdMoveNecessary( pOldPage, getFrameArea() ); + { + const IDocumentSettingAccess& rIDSA = pNewPage->GetFormat()->getIDocumentSettingAccess(); + SwTwips nSpace = 0; + SwRect aRect( pNewUpper->getFramePrintArea() ); + aRect.Pos() += pNewUpper->getFrameArea().Pos(); + const SwFrame *pPrevFrame = pNewUpper->Lower(); + while ( pPrevFrame ) + { + SwTwips nNewTop = fnRectX.GetBottom(pPrevFrame->getFrameArea()); + // Consider lower spacing of last frame in a table cell + { + // Check if last frame is inside table and if it includes its lower spacing. + if ( !pPrevFrame->GetNext() && pPrevFrame->IsInTab() && + rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) ) + { + const SwFrame* pLastFrame = pPrevFrame; + // if last frame is a section, take its last content + if ( pPrevFrame->IsSctFrame() ) + { + pLastFrame = static_cast<const SwSectionFrame*>(pPrevFrame)->FindLastContent(); + if ( pLastFrame && + pLastFrame->FindTabFrame() != pPrevFrame->FindTabFrame() ) + { + pLastFrame = pLastFrame->FindTabFrame(); + } + } + + if ( pLastFrame ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pLastFrame ); + const SwBorderAttrs& rAttrs = *aAccess.Get(); + nNewTop -= rAttrs.GetULSpace().GetLower(); + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nNewTop -= rAttrs.CalcLineSpacing(); + } + } + } + } + fnRectX.SetTop( aRect, nNewTop ); + + pPrevFrame = pPrevFrame->GetNext(); + } + + nMoveAnyway |= BwdMoveNecessary( pNewPage, aRect); + + //determine space left in new upper frame + nSpace = fnRectX.GetHeight(aRect); + const SwViewShell *pSh = pNewUpper->getRootFrame()->GetCurrShell(); + if ( IsInFootnote() || + (pSh && pSh->GetViewOptions()->getBrowseMode()) || + pNewUpper->IsCellFrame() || + ( pNewUpper->IsInSct() && ( pNewUpper->IsSctFrame() || + ( pNewUpper->IsColBodyFrame() && + !pNewUpper->GetUpper()->GetPrev() && + !pNewUpper->GetUpper()->GetNext() ) ) ) ) + nSpace += pNewUpper->Grow( LONG_MAX, true ); + + if ( nMoveAnyway < 3 ) + { + if ( nSpace ) + { + // Do not notify footnotes which are stuck to the paragraph: + // This would require extremely confusing code, taking into + // account the widths + // and Flys, that in turn influence the footnotes, ... + + // WouldFit_ can only be used if the width is the same and + // ONLY self-anchored Flys are present. + + // WouldFit_ can also be used if ONLY Flys anchored + // somewhere else are present. + // In this case, the width doesn't even matter, + // because we're running a TestFormat in the new upper. + const sal_uInt8 nBwdMoveNecessaryResult = + BwdMoveNecessary( pNewPage, aRect); + const bool bObjsInNewUpper( nBwdMoveNecessaryResult == 2 || + nBwdMoveNecessaryResult == 3 ); + + return WouldFit_( nSpace, pNewUpper, nMoveAnyway == 2, + bObjsInNewUpper ); + } + // It's impossible for WouldFit_ to return a usable result if + // we have a fresh multi-column section - so we really have to + // float back unless there is no space. + return pNewUpper->IsInSct() && pNewUpper->IsColBodyFrame() && + !fnRectX.GetWidth(pNewUpper->getFramePrintArea()) && + ( pNewUpper->GetUpper()->GetPrev() || + pNewUpper->GetUpper()->GetNext() ); + } + + // Check for space left in new upper + return nSpace != 0; + } + } + return false; +} + +// Calc methods + +// Two little friendships form a secret society +inline void PrepareLock( SwFlowFrame *pTab ) +{ + pTab->LockJoin(); +} +inline void PrepareUnlock( SwFlowFrame *pTab ) +{ + pTab->UnlockJoin(); + +} + +// hopefully, one day this function simply will return 'false' +static bool lcl_IsCalcUpperAllowed( const SwFrame& rFrame ) +{ + return !rFrame.GetUpper()->IsSctFrame() && + !rFrame.GetUpper()->IsFooterFrame() && + // No format of upper Writer fly frame + !rFrame.GetUpper()->IsFlyFrame() && + !( rFrame.GetUpper()->IsTabFrame() && rFrame.GetUpper()->GetUpper()->IsInTab() ) && + !( rFrame.IsTabFrame() && rFrame.GetUpper()->IsInTab() ); +} + +/** Prepares the Frame for "formatting" (MakeAll()). + * + * This method serves to save stack space: To calculate the position of the Frame + * we have to make sure that the positions of Upper and Prev respectively are + * valid. This may require a recursive call (a loop would be quite expensive, + * as it's not required very often). + * + * Every call of MakeAll requires around 500 bytes on the stack - you easily + * see where this leads to. This method requires only a little bit of stack + * space, so the recursive call should not be a problem here. + * + * Another advantage is that one nice day, this method and with it the + * formatting of predecessors could be avoided. Then it could probably be + * possible to jump "quickly" to the document's end. + * + * @see MakeAll() + */ +void SwFrame::PrepareMake(vcl::RenderContext* pRenderContext) +{ + StackHack aHack; + if ( GetUpper() ) + { + SwFrameDeleteGuard aDeleteGuard(this); + if ( lcl_IsCalcUpperAllowed( *this ) ) + GetUpper()->Calc(pRenderContext); + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + + const bool bCnt = IsContentFrame(); + const bool bTab = IsTabFrame(); + bool bNoSect = IsInSct(); + bool bOldTabLock = false, bFoll = false; + SwFlowFrame* pThis = bCnt ? static_cast<SwContentFrame*>(this) : nullptr; + + if ( bTab ) + { + pThis = static_cast<SwTabFrame*>(this); + bOldTabLock = static_cast<SwTabFrame*>(this)->IsJoinLocked(); + ::PrepareLock( static_cast<SwTabFrame*>(this) ); + bFoll = pThis->IsFollow(); + } + else if( IsSctFrame() ) + { + pThis = static_cast<SwSectionFrame*>(this); + bFoll = pThis->IsFollow(); + bNoSect = false; + } + else if ( bCnt ) + { + bFoll = pThis->IsFollow(); + if ( bFoll && GetPrev() ) + { + // Do not follow the chain when we need only one instance + const SwTextFrame* pMaster = static_cast<SwContentFrame*>(this)->FindMaster(); + if ( pMaster && pMaster->IsLocked() ) + { + MakeAll(pRenderContext); + return; + } + } + } + + // There is no format of previous frame, if current frame is a table + // frame and its previous frame wants to keep with it. + const bool bFormatPrev = !bTab || + !GetPrev() || + !GetPrev()->GetAttrSet()->GetKeep().GetValue(); + if ( bFormatPrev ) + { + SwFrame *pFrame = GetUpper()->Lower(); + while ( pFrame != this ) + { + OSL_ENSURE( pFrame, ":-( Layout unstable (this not found)." ); + if ( !pFrame ) + return; //Oioioioi ... + + if ( !pFrame->isFrameAreaDefinitionValid() ) + { + // A small interference that hopefully improves on the stability: + // If I'm Follow AND neighbor of a Frame before me, it would delete + // me when formatting. This as you can see could easily become a + // confusing situation that we want to avoid. + if ( bFoll && pFrame->IsFlowFrame() && + SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( pThis ) ) + break; + + bool const isLast(pFrame->GetNext() == this); + // note: this seems obvious but does *not* hold, a MakeAll() + // could move more than 1 frame backwards! + // that's why FindNext() is used below + // assert(pFrame->GetUpper() == GetUpper()); + pFrame->MakeAll(pRenderContext); + if( IsSctFrame() && !static_cast<SwSectionFrame*>(this)->GetSection() ) + break; + if (isLast && pFrame->GetUpper() != GetUpper()) + { + assert(GetUpper()->Lower() == this + // empty section frames are created all the time... + || GetUpper()->Lower()->IsSctFrame() + // tab frame/section frame may split multiple times + || ( SwFlowFrame::CastFlowFrame(pFrame) + && SwFlowFrame::CastFlowFrame(GetUpper()->Lower()) + && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( + SwFlowFrame::CastFlowFrame(GetUpper()->Lower())) + && (GetUpper()->Lower()->GetNext() == this + // if it's more than 10 pages long... + || (SwFlowFrame::CastFlowFrame(GetUpper()->Lower())->GetFollow() + == SwFlowFrame::CastFlowFrame(GetUpper()->Lower()->GetNext()) + && GetUpper()->Lower()->GetNext()->GetNext() == this) + // pre-existing empty section frames may end up between them... + || GetUpper()->Lower()->GetNext()->IsSctFrame()))); + break; // tdf#119109 frame was moved backward, prevent + // FindNext() returning a frame inside this if + } // this is a table! + } + // With ContentFrames, the chain may be broken while walking through + // it. Therefore we have to figure out the next frame in a bit more + // complicated way. However, I'll HAVE to get back to myself + // sometime again. + pFrame = pFrame->FindNext(); + + // If we started out in a SectionFrame, it might have happened that + // we landed in a Section Follow via the MakeAll calls. + // FindNext only gives us the SectionFrame, not it's content - we + // won't find ourselves anymore! + if( bNoSect && pFrame && pFrame->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pCnt ) + pFrame = pCnt; + } + } + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone II)." ); + if ( !GetUpper() ) + return; + + if ( lcl_IsCalcUpperAllowed( *this ) ) + GetUpper()->Calc(pRenderContext); + + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone III)." ); + } + + if ( bTab && !bOldTabLock ) + ::PrepareUnlock( static_cast<SwTabFrame*>(this) ); + } + MakeAll(pRenderContext); +} + +void SwFrame::OptPrepareMake() +{ + // #i23129#, #i36347# - no format of upper Writer fly frame + if ( GetUpper() && !GetUpper()->IsFooterFrame() && + !GetUpper()->IsFlyFrame() ) + { + { + SwFrameDeleteGuard aDeleteGuard(this); + GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + } + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + } + if ( GetPrev() && !GetPrev()->isFrameAreaDefinitionValid() ) + { + PrepareMake(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + } + else + { + StackHack aHack; + MakeAll(IsRootFrame() ? nullptr : getRootFrame()->GetCurrShell()->GetOut()); + } +} + +void SwFrame::PrepareCursor() +{ + StackHack aHack; + if( GetUpper() && !GetUpper()->IsSctFrame() ) + { + const bool bCnt = IsContentFrame(); + const bool bTab = IsTabFrame(); + bool bNoSect = IsInSct(); + +#if BOOST_VERSION < 105600 + std::list<FlowFrameJoinLockGuard> tabGuard; + std::list<SwFrameDeleteGuard> rowGuard; +#else + std::optional<FlowFrameJoinLockGuard> tabGuard; + std::optional<SwFrameDeleteGuard> rowGuard; +#endif + SwFlowFrame* pThis = bCnt ? static_cast<SwContentFrame*>(this) : nullptr; + + if ( bTab ) + { +#if BOOST_VERSION < 105600 + tabGuard.emplace_back(static_cast<SwTabFrame*>(this)); // tdf#125741 +#else + tabGuard.emplace(static_cast<SwTabFrame*>(this)); // tdf#125741 +#endif + pThis = static_cast<SwTabFrame*>(this); + } + else if (IsRowFrame()) + { +#if BOOST_VERSION < 105600 + rowGuard.emplace_back(this); // tdf#125741 keep this alive +#else + rowGuard.emplace(this); // tdf#125741 keep this alive +#endif + } + else if( IsSctFrame() ) + { + pThis = static_cast<SwSectionFrame*>(this); + bNoSect = false; + } + + GetUpper()->PrepareCursor(); + GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + + bool const bFoll = pThis && pThis->IsFollow(); + + SwFrame *pFrame = GetUpper()->Lower(); + while ( pFrame != this ) + { + OSL_ENSURE( pFrame, ":-( Layout unstable (this not found)." ); + if ( !pFrame ) + return; //Oioioioi ... + + if ( !pFrame->isFrameAreaDefinitionValid() ) + { + // A small interference that hopefully improves on the stability: + // If I'm Follow AND neighbor of a Frame before me, it would delete + // me when formatting. This as you can see could easily become a + // confusing situation that we want to avoid. + if ( bFoll && pFrame->IsFlowFrame() && + SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( pThis ) ) + break; + + bool const isLast(pFrame->GetNext() == this); + pFrame->MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + if (isLast && pFrame->GetUpper() != GetUpper()) + { + assert(GetUpper()->Lower() == this + // empty section frames are created all the time... + || GetUpper()->Lower()->IsSctFrame() + // tab frame/section frame may split multiple times + || ( SwFlowFrame::CastFlowFrame(pFrame) + && SwFlowFrame::CastFlowFrame(GetUpper()->Lower()) + && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( + SwFlowFrame::CastFlowFrame(GetUpper()->Lower())) + && (GetUpper()->Lower()->GetNext() == this + // if it's more than 10 pages long... + || (SwFlowFrame::CastFlowFrame(GetUpper()->Lower())->GetFollow() + == SwFlowFrame::CastFlowFrame(GetUpper()->Lower()->GetNext()) + && GetUpper()->Lower()->GetNext()->GetNext() == this) + // pre-existing empty section frames may end up between them... + || GetUpper()->Lower()->GetNext()->IsSctFrame()))); + break; // tdf#119109 frame was moved backward, prevent + // FindNext() returning a frame inside this if + } // this is a table! + } + // With ContentFrames, the chain may be broken while walking through + // it. Therefore we have to figure out the next frame in a bit more + // complicated way. However, I'll HAVE to get back to myself + // sometime again. + pFrame = pFrame->FindNext(); + if( bNoSect && pFrame && pFrame->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pCnt ) + pFrame = pCnt; + } + } + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone II)." ); + if ( !GetUpper() ) + return; + + GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone III)." ); + } + Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); +} + +// Here we return GetPrev(); however we will ignore empty SectionFrames +static SwFrame* lcl_Prev( SwFrame* pFrame, bool bSectPrv = true ) +{ + SwFrame* pRet = pFrame->GetPrev(); + if( !pRet && pFrame->GetUpper() && pFrame->GetUpper()->IsSctFrame() && + bSectPrv && !pFrame->IsColumnFrame() ) + pRet = pFrame->GetUpper()->GetPrev(); + while( pRet && pRet->IsSctFrame() && + !static_cast<SwSectionFrame*>(pRet)->GetSection() ) + pRet = pRet->GetPrev(); + return pRet; +} + +static SwFrame* lcl_NotHiddenPrev( SwFrame* pFrame ) +{ + SwFrame *pRet = pFrame; + do + { + pRet = lcl_Prev( pRet ); + } while ( pRet && pRet->IsTextFrame() && static_cast<SwTextFrame*>(pRet)->IsHiddenNow() ); + return pRet; +} + +void SwFrame::MakePos() +{ + if ( !isFrameAreaPositionValid() ) + { + setFrameAreaPositionValid(true); + bool bUseUpper = false; + SwFrame* pPrv = lcl_Prev( this ); + if ( pPrv && + ( !pPrv->IsContentFrame() || + ( static_cast<SwContentFrame*>(pPrv)->GetFollow() != this ) ) + ) + { + if ( !StackHack::IsLocked() && + ( !IsInSct() || IsSctFrame() ) && + !pPrv->IsSctFrame() && + !pPrv->GetAttrSet()->GetKeep().GetValue() + ) + { + pPrv->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); // This may cause Prev to vanish! + } + else if ( pPrv->getFrameArea().Top() == 0 ) + { + bUseUpper = true; + } + } + + pPrv = lcl_Prev( this, false ); + const SwFrameType nMyType = GetType(); + SwRectFnSet aRectFnSet((IsCellFrame() && GetUpper() ? GetUpper() : this)); + if ( !bUseUpper && pPrv ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( pPrv->getFrameArea().Pos() ); + + if( FRM_NEIGHBOUR & nMyType ) + { + const bool bR2L = IsRightToLeft(); + + if( bR2L ) + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) - aRectFnSet.GetWidth(aFrm) ); + } + else + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) + aRectFnSet.GetWidth(pPrv->getFrameArea()) ); + } + + // cells may now leave their uppers + if( aRectFnSet.IsVert() && SwFrameType::Cell & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + pPrv->getFrameArea().Width()); + } + } + else if( aRectFnSet.IsVert() && FRM_NOTE_VERT & nMyType ) + { + if ( aRectFnSet.IsVertL2R() ) + { + aFrm.Pos().setX(aFrm.Pos().getX() + pPrv->getFrameArea().Width()); + } + else + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width()); + } + } + else + { + aFrm.Pos().setY(aFrm.Pos().getY() + pPrv->getFrameArea().Height()); + } + } + else if ( GetUpper() ) + { + // If parent frame is a footer frame and its <ColLocked()>, then + // do *not* calculate it. + // NOTE: Footer frame is <ColLocked()> during its + // <FormatSize(..)>, which is called from <Format(..)>, which + // is called from <MakeAll()>, which is called from <Calc()>. + // #i56850# + // - no format of upper Writer fly frame, which is anchored + // at-paragraph or at-character. + if ( !GetUpper()->IsTabFrame() && + !( IsTabFrame() && GetUpper()->IsInTab() ) && + !GetUpper()->IsSctFrame() && + !dynamic_cast<SwFlyAtContentFrame*>(GetUpper()) && + !( GetUpper()->IsFooterFrame() && + GetUpper()->IsColLocked() ) + ) + { + GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + pPrv = lcl_Prev( this, false ); + if ( !bUseUpper && pPrv ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( pPrv->getFrameArea().Pos() ); + + if( FRM_NEIGHBOUR & nMyType ) + { + const bool bR2L = IsRightToLeft(); + + if( bR2L ) + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) - aRectFnSet.GetWidth(aFrm) ); + } + else + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) + aRectFnSet.GetWidth(pPrv->getFrameArea()) ); + } + + // cells may now leave their uppers + if( aRectFnSet.IsVert() && SwFrameType::Cell & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + pPrv->getFrameArea().Width()); + } + } + else if( aRectFnSet.IsVert() && FRM_NOTE_VERT & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width()); + } + else + { + aFrm.Pos().setY(aFrm.Pos().getY() + pPrv->getFrameArea().Height()); + } + } + else + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( GetUpper()->getFrameArea().Pos() ); + + if( GetUpper()->IsFlyFrame() ) + { + aFrm.Pos() += static_cast<SwFlyFrame*>(GetUpper())->ContentPos(); + } + else + { + aFrm.Pos() += GetUpper()->getFramePrintArea().Pos(); + } + + if( FRM_NEIGHBOUR & nMyType && IsRightToLeft() ) + { + if( aRectFnSet.IsVert() ) + { + aFrm.Pos().setY(aFrm.Pos().getY() + GetUpper()->getFramePrintArea().Height() - aFrm.Height()); + } + else + { + aFrm.Pos().setX(aFrm.Pos().getX() + GetUpper()->getFramePrintArea().Width() - aFrm.Width()); + } + } + else if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() && FRM_NOTE_VERT & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + GetUpper()->getFramePrintArea().Width()); + } + } + } + else + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(0); + aFrm.Pos().setY(0); + } + + if( IsBodyFrame() && aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() && GetUpper() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(aFrm.Pos().getX() + GetUpper()->getFramePrintArea().Width() - aFrm.Width()); + } + + setFrameAreaPositionValid(true); + } +} + +// #i28701# - new type <SwSortedObjs> +static void lcl_CheckObjects(SwSortedObjs& rSortedObjs, const SwFrame* pFrame, long& rBot) +{ + // And then there can be paragraph anchored frames that sit below their paragraph. + long nMax = 0; + for (SwAnchoredObject* pObj : rSortedObjs) + { + // #i28701# - consider changed type of <SwSortedObjs> + // entries. + long nTmp = 0; + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pObj); + if( pFly->getFrameArea().Top() != FAR_AWAY && + ( pFrame->IsPageFrame() ? pFly->IsFlyLayFrame() : + ( pFly->IsFlyAtContentFrame() && + ( pFrame->IsBodyFrame() ? pFly->GetAnchorFrame()->IsInDocBody() : + pFly->GetAnchorFrame()->IsInFootnote() ) ) ) ) + { + nTmp = pFly->getFrameArea().Bottom(); + } + } + else + nTmp = pObj->GetObjRect().Bottom(); + nMax = std::max( nTmp, nMax ); + } + ++nMax; // Lower edge vs. height! + rBot = std::max( rBot, nMax ); +} + +size_t SwPageFrame::GetContentHeight(const long nTop, const long nBottom) const +{ + OSL_ENSURE(!(FindBodyCont() && FindBodyCont()->Lower() && FindBodyCont()->Lower()->IsColumnFrame()), + "SwPageFrame::GetContentHeight(): No support for columns."); + + // In pages without columns, the content defines the size. + long nBot = getFrameArea().Top() + nTop; + const SwFrame *pFrame = Lower(); + while (pFrame) + { + long nTmp = 0; + const SwFrame *pCnt = static_cast<const SwLayoutFrame*>(pFrame)->ContainsAny(); + while (pCnt && (pCnt->GetUpper() == pFrame || + static_cast<const SwLayoutFrame*>(pFrame)->IsAnLower(pCnt))) + { + nTmp += pCnt->getFrameArea().Height(); + if (pCnt->IsTextFrame() && + static_cast<const SwTextFrame*>(pCnt)->IsUndersized()) + { + // This TextFrame would like to be a bit bigger. + nTmp += static_cast<const SwTextFrame*>(pCnt)->GetParHeight() + - pCnt->getFramePrintArea().Height(); + } + else if (pCnt->IsSctFrame()) + { + // Grow if undersized, but don't shrink if oversized. + const auto delta = static_cast<const SwSectionFrame*>(pCnt)->CalcUndersize(); + if (delta > 0) + nTmp += delta; + } + + pCnt = pCnt->FindNext(); + } + // Consider invalid body frame properties + if (pFrame->IsBodyFrame() && + (!pFrame->isFrameAreaSizeValid() || + !pFrame->isFramePrintAreaValid()) && + (pFrame->getFrameArea().Height() < pFrame->getFramePrintArea().Height()) + ) + { + nTmp = std::min(nTmp, pFrame->getFrameArea().Height()); + } + else + { + // Assert invalid lower property + OSL_ENSURE(!(pFrame->getFrameArea().Height() < pFrame->getFramePrintArea().Height()), + "SwPageFrame::GetContentHeight(): Lower with frame height < printing height"); + nTmp += pFrame->getFrameArea().Height() - pFrame->getFramePrintArea().Height(); + } + if (!pFrame->IsBodyFrame()) + nTmp = std::min(nTmp, pFrame->getFrameArea().Height()); + nBot += nTmp; + // Here we check whether paragraph anchored objects + // protrude outside the Body/FootnoteCont. + if (m_pSortedObjs && !pFrame->IsHeaderFrame() && + !pFrame->IsFooterFrame()) + lcl_CheckObjects(*m_pSortedObjs, pFrame, nBot); + pFrame = pFrame->GetNext(); + } + nBot += nBottom; + // And the page anchored ones + if (m_pSortedObjs) + lcl_CheckObjects(*m_pSortedObjs, this, nBot); + nBot -= getFrameArea().Top(); + + return nBot; +} + +void SwPageFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + const SwRect aOldRect( getFrameArea() ); // Adjust root size + const SwLayNotify aNotify( this ); // takes care of the notification in the dtor + std::unique_ptr<SwBorderAttrAccess> pAccess; + const SwBorderAttrs*pAttrs = nullptr; + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( !isFrameAreaPositionValid() ) + { + setFrameAreaPositionValid(true); // positioning of the pages is taken care of by the root frame + } + + if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( IsEmptyPage() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( 0 ); + aFrm.Height( 0 ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( 0 ); + aPrt.Height( 0 ); + aPrt.Left( 0 ); + aPrt.Top( 0 ); + + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + else + { + if (!pAccess) + { + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + } + assert(pAttrs); + + SwRootFrame* pRootFrame = getRootFrame(); + SwViewShell* pSh = pRootFrame->GetCurrShell(); + if (pSh && pSh->GetViewOptions()->getBrowseMode()) + { + // In BrowseView, we use fixed settings + const Size aBorder = pRenderContext->PixelToLogic( pSh->GetBrowseBorder() ); + const long nTop = pAttrs->CalcTopLine() + aBorder.Height(); + const long nBottom = pAttrs->CalcBottomLine()+ aBorder.Height(); + + long nWidth = GetUpper() ? static_cast<SwRootFrame*>(GetUpper())->GetBrowseWidth() : 0; + const auto nDefWidth = pSh->GetBrowseWidth(); + if (nWidth < nDefWidth) + nWidth = nDefWidth; + nWidth += + 2 * aBorder.Width(); + nWidth = std::max( nWidth, 2L * aBorder.Width() + 4*MM50 ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( nWidth ); + + SwLayoutFrame *pBody = FindBodyCont(); + if ( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + // Columns have a fixed height + aFrm.Height( pAttrs->GetSize().Height() ); + } + else + { + // In pages without columns, the content defines the size. + long nBot = GetContentHeight(nTop, nBottom); + + // #i35143# - If second page frame + // exists, the first page doesn't have to fulfill the + // visible area. + if ( !GetPrev() && !GetNext() ) + { + nBot = std::max( nBot, pSh->VisArea().Height() ); + } + // #i35143# - Assure, that the page + // doesn't exceed the defined browse height. + aFrm.Height( std::min( nBot, BROWSE_HEIGHT ) ); + } + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left ( pAttrs->CalcLeftLine() + aBorder.Width() ); + aPrt.Top ( nTop ); + aPrt.Width( getFrameArea().Width() - ( aPrt.Left() + pAttrs->CalcRightLine() + aBorder.Width() ) ); + aPrt.Height( getFrameArea().Height() - (nTop + nBottom) ); + } + + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + continue; + } + else if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden() && pRootFrame->GetLastPage() != this) + { + long height = 0; + SwLayoutFrame *pBody = FindBodyCont(); + if ( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + // Columns have a fixed height + height = pAttrs->GetSize().Height(); + } + else + { + // No need for borders. + height = GetContentHeight(0, 0); + } + + if (height > 0) + { + ChgSize(Size(getFrameArea().Width(), height)); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top(0); + aPrt.Height(height); + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + continue; + } + + // Fallback to default formatting. Especially relevant + // when loading a doc when Hide Whitespace is enabled. + // Heights are zero initially. + } + + // Set FixSize. For pages, this is not done from Upper, but from + // the attribute. + //FIXME: This resets the size when (isFrameAreaSizeValid() && !isFramePrintAreaValid()). + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.SSize( pAttrs->GetSize() ); + } + Format( pRenderContext, pAttrs ); + } + } + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + + if ( getFrameArea() != aOldRect && GetUpper() ) + static_cast<SwRootFrame*>(GetUpper())->CheckViewLayout( nullptr, nullptr ); + + OSL_ENSURE( !GetUpper() || GetUpper()->getFramePrintArea().Width() >= getFrameArea().Width(), + "Upper (Root) must be wide enough to contain the widest page"); +} + +void SwLayoutFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + // takes care of the notification in the dtor + const SwLayNotify aNotify( this ); + bool bVert = IsVertical(); + + SwRectFn fnRect = ( IsNeighbourFrame() == bVert )? fnRectHori : ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + + std::unique_ptr<SwBorderAttrAccess> pAccess; + const SwBorderAttrs*pAttrs = nullptr; + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( !isFrameAreaPositionValid() ) + MakePos(); + + if ( GetUpper() ) + { + // NEW TABLES + if ( IsLeaveUpperAllowed() ) + { + if ( !isFrameAreaSizeValid() ) + { + setFramePrintAreaValid(false); + } + } + else + { + if ( !isFrameAreaSizeValid() ) + { + // Set FixSize; VarSize is set by Format() after calculating the PrtArea + setFramePrintAreaValid(false); + + SwTwips nPrtWidth = (GetUpper()->getFramePrintArea().*fnRect->fnGetWidth)(); + if( bVert && ( IsBodyFrame() || IsFootnoteContFrame() ) ) + { + SwFrame* pNxt = GetPrev(); + while( pNxt && !pNxt->IsHeaderFrame() ) + pNxt = pNxt->GetPrev(); + if( pNxt ) + nPrtWidth -= pNxt->getFrameArea().Height(); + pNxt = GetNext(); + while( pNxt && !pNxt->IsFooterFrame() ) + pNxt = pNxt->GetNext(); + if( pNxt ) + nPrtWidth -= pNxt->getFrameArea().Height(); + } + + const long nDiff = nPrtWidth - (getFrameArea().*fnRect->fnGetWidth)(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( IsNeighbourFrame() && IsRightToLeft() ) + { + (aFrm.*fnRect->fnSubLeft)( nDiff ); + } + else + { + (aFrm.*fnRect->fnAddRight)( nDiff ); + } + } + else + { + // Don't leave your upper + const SwTwips nDeadLine = (GetUpper()->*fnRect->fnGetPrtBottom)(); + if( (getFrameArea().*fnRect->fnOverStep)( nDeadLine ) ) + { + setFrameAreaSizeValid(false); + } + } + } + } + + if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( !pAccess ) + { + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + } + Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); + } + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) +} + +bool SwTextNode::IsCollapse() const +{ + if (GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA ) + && GetText().isEmpty()) + { + sal_uLong nIdx=GetIndex(); + const SwEndNode *pNdBefore=GetNodes()[nIdx-1]->GetEndNode(); + const SwEndNode *pNdAfter=GetNodes()[nIdx+1]->GetEndNode(); + + // The paragraph is collapsed only if the NdAfter is the end of a cell + bool bInTable = FindTableNode( ) != nullptr; + + SwSortedObjs* pObjs = getLayoutFrame( GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() )->GetDrawObjs( ); + const size_t nObjs = ( pObjs != nullptr ) ? pObjs->size( ) : 0; + + return pNdBefore!=nullptr && pNdAfter!=nullptr && nObjs == 0 && bInTable; + } + + return false; +} + +bool SwFrame::IsCollapse() const +{ + if (!IsTextFrame()) + return false; + + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame*>(this); + const SwTextNode *pTextNode = pTextFrame->GetTextNodeForParaProps(); + // TODO this SwTextNode function is pointless and should be merged in here + return pTextFrame->GetText().isEmpty() && pTextNode && pTextNode->IsCollapse(); +} + +void SwContentFrame::MakePrtArea( const SwBorderAttrs &rAttrs ) +{ + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + SwRectFnSet aRectFnSet(this); + const bool bTextFrame = IsTextFrame(); + SwTwips nUpper = 0; + if ( bTextFrame && static_cast<SwTextFrame*>(this)->IsHiddenNow() ) + { + if ( static_cast<SwTextFrame*>(this)->HasFollow() ) + static_cast<SwTextFrame*>(this)->JoinFrame(); + + if( aRectFnSet.GetHeight(getFramePrintArea()) ) + { + static_cast<SwTextFrame*>(this)->HideHidden(); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Pos().setX(0); + aPrt.Pos().setY(0); + aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(getFrameArea()) ); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + nUpper = -( aRectFnSet.GetHeight(getFrameArea()) ); + } + else + { + // Simplification: ContentFrames are always variable in height! + + // At the FixSize, the surrounding Frame enforces the size; + // the borders are simply subtracted. + const long nLeft = rAttrs.CalcLeft( this ); + const long nRight = rAttrs.CalcRight( this ); + aRectFnSet.SetXMargins( *this, nLeft, nRight ); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwTwips nWidthArea; + if( pSh && 0!=(nWidthArea=aRectFnSet.GetWidth(pSh->VisArea())) && + GetUpper()->IsPageBodyFrame() && // but not for BodyFrames in Columns + pSh->GetViewOptions()->getBrowseMode() ) + { + // Do not protrude the edge of the visible area. The page may be + // wider, because there may be objects with excess width + // (RootFrame::ImplCalcBrowseWidth()) + long nMinWidth = 0; + + for (size_t i = 0; GetDrawObjs() && i < GetDrawObjs()->size(); ++i) + { + // #i28701# - consider changed type of + // <SwSortedObjs> entries + SwAnchoredObject* pObj = (*GetDrawObjs())[i]; + const SwFrameFormat& rFormat = pObj->GetFrameFormat(); + const bool bFly = dynamic_cast<const SwFlyFrame*>( pObj) != nullptr; + if ((bFly && (FAR_AWAY == pObj->GetObjRect().Width())) + || rFormat.GetFrameSize().GetWidthPercent()) + { + continue; + } + + if ( RndStdIds::FLY_AS_CHAR == rFormat.GetAnchor().GetAnchorId() ) + { + nMinWidth = std::max( nMinWidth, + bFly ? rFormat.GetFrameSize().GetWidth() + : pObj->GetObjRect().Width() ); + } + } + + const Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); + long nWidth = nWidthArea - 2 * ( IsVertical() ? aBorder.Height() : aBorder.Width() ); + nWidth -= aRectFnSet.GetLeft(getFramePrintArea()); + nWidth -= rAttrs.CalcRightLine(); + nWidth = std::max( nMinWidth, nWidth ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, std::min( nWidth, aRectFnSet.GetWidth(aPrt) ) ); + } + + if ( aRectFnSet.GetWidth(getFramePrintArea()) <= MINLAY ) + { + // The PrtArea should already be at least MINLAY wide, matching the + // minimal values of the UI + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, std::min( long(MINLAY), aRectFnSet.GetWidth(getFrameArea()) ) ); + SwTwips nTmp = aRectFnSet.GetWidth(getFrameArea()) - aRectFnSet.GetWidth(aPrt); + + if( aRectFnSet.GetLeft(aPrt) > nTmp ) + { + aRectFnSet.SetLeft( aPrt, nTmp ); + } + } + + // The following rules apply for VarSize: + // 1. The first entry of a chain has no top border + // 2. There is never a bottom border + // 3. The top border is the maximum of the distance + // of Prev downwards and our own distance upwards + // Those three rules apply when calculating spacings + // that are given by UL- and LRSpace. There might be a spacing + // in all directions however; this may be caused by borders + // and / or shadows. + // 4. The spacing for TextFrames corresponds to the interline lead, + // at a minimum. + + nUpper = CalcUpperSpace( &rAttrs ); + + SwTwips nLower = CalcLowerSpace( &rAttrs ); + if (IsCollapse()) { + nUpper=0; + nLower=0; + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetPosY( aPrt, !aRectFnSet.IsVert() ? nUpper : nLower); + } + + nUpper += nLower; + nUpper -= aRectFnSet.GetHeight(getFrameArea()) - aRectFnSet.GetHeight(getFramePrintArea()); + } + // If there's a difference between old and new size, call Grow() or + // Shrink() respectively. + if ( nUpper ) + { + if ( nUpper > 0 ) + GrowFrame( nUpper ); + else + ShrinkFrame( -nUpper ); + } + } +} + +#define STOP_FLY_FORMAT 10 +// - loop prevention +const int cnStopFormat = 15; + +inline void ValidateSz( SwFrame *pFrame ) +{ + if ( pFrame ) + { + pFrame->setFrameAreaSizeValid(true); + pFrame->setFramePrintAreaValid(true); + } +} + +void SwContentFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + OSL_ENSURE( GetUpper(), "no Upper?" ); + OSL_ENSURE( IsTextFrame(), "MakeAll(), NoText" ); + + if ( !IsFollow() && StackHack::IsLocked() ) + return; + + if ( IsJoinLocked() ) + return; + + OSL_ENSURE( !static_cast<SwTextFrame*>(this)->IsSwapped(), "Calculation of a swapped frame" ); + + StackHack aHack; + + if ( static_cast<SwTextFrame*>(this)->IsLocked() ) + { + OSL_FAIL( "Format for locked TextFrame." ); + return; + } + + auto xDeleteGuard = std::make_unique<SwFrameDeleteGuard>(this); + LockJoin(); + long nFormatCount = 0; + // - loop prevention + int nConsecutiveFormatsWithoutChange = 0; + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + // takes care of the notification in the dtor + std::unique_ptr<SwContentNotify, o3tl::default_delete<SwContentNotify>> pNotify(new SwContentNotify( this )); + + // as long as bMakePage is true, a new page can be created (exactly once) + bool bMakePage = true; + // bMovedBwd gets set to true when the frame flows backwards + bool bMovedBwd = false; + // as long as bMovedFwd is false, the Frame may flow backwards (until + // it has been moved forward once) + bool bMovedFwd = false; + sal_Bool bFormatted = false; // For the widow/orphan rules, we encourage the + // last ContentFrame of a chain to format. This only + // needs to happen once. Every time the Frame is + // moved, the flag will have to be reset. + bool bMustFit = false; // Once the emergency brake is pulled, + // no other prepares will be triggered + bool bFitPromise = false; // If a paragraph didn't fit, but promises + // with WouldFit that it would adjust accordingly, + // this flag is set. If it turns out that it + // didn't keep it's promise, we can act in a + // controlled fashion. + const bool bFly = IsInFly(); + const bool bTab = IsInTab(); + const bool bFootnote = IsInFootnote(); + const bool bSct = IsInSct(); + Point aOldFramePos; // This is so we can compare with the last pos + Point aOldPrtPos; // and determine whether it makes sense to Prepare + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + if ( !IsFollow() && rAttrs.JoinedWithPrev( *(this) ) ) + { + pNotify->SetBordersJoinedWithPrev(); + } + + const bool bKeep = IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem()); + + std::unique_ptr<SwSaveFootnoteHeight> pSaveFootnote; + if ( bFootnote ) + { + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + SwSectionFrame* pSct = pFootnote->FindSctFrame(); + if ( !static_cast<SwTextFrame*>(pFootnote->GetRef())->IsLocked() ) + { + SwFootnoteBossFrame* pBoss = pFootnote->GetRef()->FindFootnoteBossFrame( + pFootnote->GetAttr()->GetFootnote().IsEndNote() ); + if( !pSct || pSct->IsColLocked() || !pSct->Growable() ) + pSaveFootnote.reset( new SwSaveFootnoteHeight( pBoss, + static_cast<SwTextFrame*>(pFootnote->GetRef())->GetFootnoteLine( pFootnote->GetAttr() ) ) ); + } + } + + if ( GetUpper()->IsSctFrame() && + HasFollow() && !GetFollow()->IsDeleteForbidden() && + &GetFollow()->GetFrame() == GetNext() ) + { + dynamic_cast<SwTextFrame&>(*this).JoinFrame(); + } + + // #i28701# - move master forward, if it has to move, + // because of its object positioning. + if ( !static_cast<SwTextFrame*>(this)->IsFollow() ) + { + sal_uInt32 nToPageNum = 0; + const bool bMoveFwdByObjPos = SwLayouter::FrameMovedFwdByObjPos( + *(GetAttrSet()->GetDoc()), + *static_cast<SwTextFrame*>(this), + nToPageNum ); + // #i58182# + // Also move a paragraph forward, which is the first one inside a table cell. + if ( bMoveFwdByObjPos && + FindPageFrame()->GetPhyPageNum() < nToPageNum && + ( lcl_Prev( this ) || + GetUpper()->IsCellFrame() || + ( GetUpper()->IsSctFrame() && + GetUpper()->GetUpper()->IsCellFrame() ) ) && + IsMoveable() ) + { + bMovedFwd = true; + MoveFwd( bMakePage, false ); + } + } + + // If a Follow sits next to its Master and doesn't fit, we know it can + // be moved right now. + if ( lcl_Prev( this ) && static_cast<SwTextFrame*>(this)->IsFollow() && IsMoveable() ) + { + bMovedFwd = true; + // If follow frame is in table, its master will be the last in the + // current table cell. Thus, invalidate the printing area of the master. + if ( IsInTab() ) + { + lcl_Prev( this )->InvalidatePrt(); + } + MoveFwd( bMakePage, false ); + } + + // Check footnote content for forward move. + // If a content of a footnote is on a prior page/column as its invalid + // reference, it can be moved forward. + if ( bFootnote && !isFrameAreaPositionValid() ) + { + SwFootnoteFrame* pFootnote = FindFootnoteFrame(); + SwContentFrame* pRefCnt = pFootnote ? pFootnote->GetRef() : nullptr; + + if ( pRefCnt && !pRefCnt->isFrameAreaDefinitionValid() ) + { + SwFootnoteBossFrame* pFootnoteBossOfFootnote = pFootnote->FindFootnoteBossFrame(); + SwFootnoteBossFrame* pFootnoteBossOfRef = pRefCnt->FindFootnoteBossFrame(); + //<loop of movefwd until condition held or no move> + if ( pFootnoteBossOfFootnote && pFootnoteBossOfRef && + pFootnoteBossOfFootnote != pFootnoteBossOfRef && + pFootnoteBossOfFootnote->IsBefore( pFootnoteBossOfRef ) ) + { + bMovedFwd = true; + MoveFwd( bMakePage, false ); + } + } + } + + SwRectFnSet aRectFnSet(this); + + SwFrame const* pMoveBwdPre(nullptr); + bool isMoveBwdPreValid(false); + + SwRect aOldFrame_StopFormat, aOldFrame_StopFormat2; + SwRect aOldPrt_StopFormat, aOldPrt_StopFormat2; + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + // - loop prevention + aOldFrame_StopFormat2 = aOldFrame_StopFormat; + aOldPrt_StopFormat2 = aOldPrt_StopFormat; + aOldFrame_StopFormat = getFrameArea(); + aOldPrt_StopFormat = getFramePrintArea(); + + bool bMoveable = IsMoveable(); + if (bMoveable) + { + SwFrame *pPre = GetIndPrev(); + if ( CheckMoveFwd( bMakePage, bKeep, bMovedBwd ) ) + { + aRectFnSet.Refresh(this); + bMovedFwd = true; + if ( bMovedBwd ) + { + // While flowing back, the Upper was encouraged to + // completely re-paint itself. We can skip this now after + // flowing back and forth. + GetUpper()->ResetCompletePaint(); + // The predecessor was invalidated, so this is obsolete as well now. + assert(pPre); + if ((pPre == pMoveBwdPre && isMoveBwdPreValid) && !pPre->IsSctFrame()) + ::ValidateSz( pPre ); + } + bMoveable = IsMoveable(); + } + } + + aOldFramePos = aRectFnSet.GetPos(getFrameArea()); + aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); + + if ( !isFrameAreaPositionValid() ) + MakePos(); + + //Set FixSize. VarSize is being adjusted by Format(). + if ( !isFrameAreaSizeValid() ) + { + // invalidate printing area flag, if the following conditions are hold: + // - current frame width is 0. + // - current printing area width is 0. + // - frame width is adjusted to a value greater than 0. + // - printing area flag is true. + // Thus, it's assured that the printing area is adjusted, if the + // frame area width changes its width from 0 to something greater + // than 0. + // Note: A text frame can be in such a situation, if the format is + // triggered by method call <SwCursorShell::SetCursor()> after + // loading the document. + const SwTwips nNewFrameWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + + if ( isFramePrintAreaValid() && + nNewFrameWidth > 0 && + aRectFnSet.GetWidth(getFrameArea()) == 0 && + aRectFnSet.GetWidth(getFramePrintArea()) == 0 ) + { + setFramePrintAreaValid(false); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, nNewFrameWidth ); + } + + // When a lower of a vertically aligned fly frame changes its size we need to recalculate content pos. + if( GetUpper() && GetUpper()->IsFlyFrame() && + GetUpper()->GetFormat()->GetTextVertAdjust().GetValue() != SDRTEXTVERTADJUST_TOP ) + { + static_cast<SwFlyFrame*>(GetUpper())->InvalidateContentPos(); + GetUpper()->SetCompletePaint(); + } + } + if ( !isFramePrintAreaValid() ) + { + const long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); + // #i34730# - keep current frame height + const SwTwips nOldH = aRectFnSet.GetHeight(getFrameArea()); + MakePrtArea( rAttrs ); + if ( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged ); + // #i34730# - check, if frame height has changed. + // If yes, send a PrepareHint::AdjustSizeWithoutFormatting and invalidate the size flag to + // force a format. The format will check in its method + // <SwTextFrame::CalcPreps()>, if the already formatted lines still + // fit and if not, performs necessary actions. + // #i40150# - no check, if frame is undersized. + if ( isFrameAreaSizeValid() && !IsUndersized() && nOldH != aRectFnSet.GetHeight(getFrameArea()) ) + { + // #115759# - no PrepareHint::AdjustSizeWithoutFormatting and size + // invalidation, if height decreases only by the additional + // lower space as last content of a table cell and an existing + // follow containing one line exists. + const SwTwips nHDiff = nOldH - aRectFnSet.GetHeight(getFrameArea()); + const bool bNoPrepAdjustFrame = + nHDiff > 0 && IsInTab() && GetFollow() && + (1 == static_cast<SwTextFrame*>(GetFollow())->GetLineCount(TextFrameIndex(COMPLETE_STRING)) + || aRectFnSet.GetWidth(static_cast<SwTextFrame*>(GetFollow())->getFrameArea()) < 0) && + GetFollow()->CalcAddLowerSpaceAsLastInTableCell() == nHDiff; + if ( !bNoPrepAdjustFrame ) + { + Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + setFrameAreaSizeValid(false); + } + } + } + + // To make the widow and orphan rules work, we need to notify the ContentFrame. + // Criteria: + // - It needs to be movable (otherwise, splitting doesn't make sense) + // - It needs to overlap with the lower edge of the PrtArea of the Upper + if ( !bMustFit ) + { + bool bWidow = true; + const SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + if( bMoveable && !bFormatted && + ( GetFollow() || aRectFnSet.OverStep( getFrameArea(), nDeadLine ) ) ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + setFrameAreaSizeValid(false); + bWidow = false; + } + if( aRectFnSet.GetPos(getFrameArea()) != aOldFramePos || + aRectFnSet.GetPos(getFramePrintArea()) != aOldPrtPos ) + { + // In this Prepare, an InvalidateSize_() might happen. + // isFrameAreaSizeValid() becomes false and Format() gets called. + Prepare( PrepareHint::FramePositionChanged, static_cast<const void*>(&bFormatted), false ); + if ( bWidow && GetFollow() ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + setFrameAreaSizeValid(false); + } + } + } + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + bFormatted = true; + ++nFormatCount; + if( nFormatCount > STOP_FLY_FORMAT ) + SetFlyLock( true ); + // - loop prevention + // No format any longer, if <cnStopFormat> consecutive formats + // without change occur. + if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) + { + Format(getRootFrame()->GetCurrShell()->GetOut()); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "debug assertion: <SwContentFrame::MakeAll()> - format of text frame suppressed by fix b6448963" ); + } +#endif + } + + // If this is the first one in a chain, check if this can flow + // backwards (if this is movable at all). + // To prevent oscillations/loops, check that this has not just + // flowed forwards. + bool bDummy; + auto const pTemp(GetIndPrev()); + auto const bTemp(pTemp && pTemp->isFrameAreaSizeValid() + && pTemp->isFramePrintAreaValid()); + if ( !lcl_Prev( this ) && + !bMovedFwd && + ( bMoveable || ( bFly && !bTab ) ) && + ( !bFootnote || !GetUpper()->FindFootnoteFrame()->GetPrev() ) + && MoveBwd( bDummy ) ) + { + aRectFnSet.Refresh(this); + pMoveBwdPre = pTemp; + isMoveBwdPreValid = bTemp; + bMovedBwd = true; + bFormatted = false; + if ( bKeep && bMoveable ) + { + if( CheckMoveFwd( bMakePage, false, bMovedBwd ) ) + { + bMovedFwd = true; + bMoveable = IsMoveable(); + aRectFnSet.Refresh(this); + } + Point aOldPos = aRectFnSet.GetPos(getFrameArea()); + MakePos(); + if( aOldPos != aRectFnSet.GetPos(getFrameArea()) ) + { + Prepare( PrepareHint::FramePositionChanged, static_cast<const void*>(&bFormatted), false ); + if ( !isFrameAreaSizeValid() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ); + } + + if ( !isFramePrintAreaValid() ) + { + const long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); + MakePrtArea( rAttrs ); + if( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged, nullptr, false ); + } + if( GetFollow() ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + } + + setFrameAreaSizeValid(true); + bFormatted = true; + Format(getRootFrame()->GetCurrShell()->GetOut()); + } + } + SwFrame *pNxt = HasFollow() ? nullptr : FindNext(); + while( pNxt && pNxt->IsSctFrame() ) + { // Leave empty sections out, go into the other ones. + if( static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + if( pTmp ) + { + pNxt = pTmp; + break; + } + } + pNxt = pNxt->FindNext(); + } + if ( pNxt ) + { + pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + if( isFrameAreaPositionValid() && !GetIndNext() ) + { + SwSectionFrame *pSct = FindSctFrame(); + if( pSct && !pSct->isFrameAreaSizeValid() ) + { + SwSectionFrame* pNxtSct = pNxt->FindSctFrame(); + if( pNxtSct && pSct->IsAnFollow( pNxtSct ) ) + { + setFrameAreaPositionValid(false); + } + } + else + { + setFrameAreaPositionValid(false); + } + } + } + } + } + + // In footnotes, the TextFrame may validate itself, which can lead to the + // situation that it's position is wrong despite being "valid". + if ( isFrameAreaPositionValid() ) + { + // #i59341# + // Workaround for inadequate layout algorithm: + // suppress invalidation and calculation of position, if paragraph + // has formatted itself at least STOP_FLY_FORMAT times and + // has anchored objects. + // Thus, the anchored objects get the possibility to format itself + // and this probably solve the layout loop. + if ( bFootnote && + nFormatCount <= STOP_FLY_FORMAT && + !GetDrawObjs() ) + { + setFrameAreaPositionValid(false); + MakePos(); + aOldFramePos = aRectFnSet.GetPos(getFrameArea()); + aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); + } + } + + // - loop prevention + { + if ( (aOldFrame_StopFormat == getFrameArea() || aOldFrame_StopFormat2 == getFrameArea() ) && + (aOldPrt_StopFormat == getFramePrintArea() || aOldPrt_StopFormat2 == getFramePrintArea())) + { + ++nConsecutiveFormatsWithoutChange; + } + else + { + nConsecutiveFormatsWithoutChange = 0; + } + } + + // Yet again an invalid value? Repeat from the start... + if ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + continue; + + // Done? + // Attention: because height == 0, it's better to use Top()+Height() instead of + // Bottom(). This might happen with undersized TextFrames on the lower edge of a + // multi-column section + const long nPrtBottom = aRectFnSet.GetPrtBottom(*GetUpper()); + long nBottomDist = aRectFnSet.BottomDist(getFrameArea(), nPrtBottom); + + // Hide whitespace may require not to insert a new page. + SwPageFrame* pPageFrame = FindPageFrame(); + const bool bHeightValid = pPageFrame->CheckPageHeightValidForHideWhitespace(nBottomDist); + if (!bHeightValid) + { + pPageFrame->InvalidateSize(); + nBottomDist = 0; + } + + if( nBottomDist >= 0 ) + { + if ( bKeep && bMoveable ) + { + // We make sure the successor will be formatted the same. + // This way, we keep control until (almost) everything is stable, + // allowing us to avoid endless loops caused by ever repeating + // retries. + + // bMoveFwdInvalid is required for #38407#. This was originally solved + // in flowfrm.cxx rev 1.38, but broke the above schema and + // preferred to play towers of hanoi (#43669#). + SwFrame *pNxt = HasFollow() ? nullptr : FindNext(); + // For sections we prefer the content, because it can change + // the page if required. + while( pNxt && pNxt->IsSctFrame() ) + { + if( static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + break; + } + pNxt = pNxt->FindNext(); + } + if ( pNxt ) + { + const bool bMoveFwdInvalid = nullptr != GetIndNext(); + const bool bNxtNew = + ( 0 == aRectFnSet.GetHeight(pNxt->getFramePrintArea()) ) && + (!pNxt->IsTextFrame() ||!static_cast<SwTextFrame*>(pNxt)->IsHiddenNow()); + + pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + if ( !bMovedBwd && + ((bMoveFwdInvalid && !GetIndNext()) || + bNxtNew) ) + { + if( bMovedFwd ) + pNotify->SetInvaKeep(); + bMovedFwd = false; + } + } + } + continue; + } + + // I don't fit into my parents, so it's time to make changes + // as constructively as possible. + + //If I'm NOT allowed to leave the parent Frame, I've got a problem. + // Following Arthur Dent, we do the only thing that you can do with + // an unsolvable problem: We ignore it with all our power. + if ( !bMoveable || IsUndersized() ) + { + if( !bMoveable && IsInTab() ) + { + long nDiff = -aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()) ); + long nReal = GetUpper()->Grow( nDiff ); + if( nReal ) + continue; + } + break; + } + + // If there's no way I can make myself fit into my Upper, the situation + // could still probably be mitigated by splitting up. + // This situation arises with freshly created Follows that had been moved + // to the next page but is still too big for it - ie. needs to be split + // as well. + + // If I'm unable to split (WouldFit()) and can't be fitted, I'm going + // to tell my TextFrame part that, if possible, we still need to split despite + // the "don't split" attribute. + bool bMoveOrFit = false; + bool bDontMoveMe = !GetIndPrev(); + if( bDontMoveMe && IsInSct() ) + { + SwFootnoteBossFrame* pBoss = FindFootnoteBossFrame(); + bDontMoveMe = !pBoss->IsInSct() || + ( !pBoss->Lower()->GetNext() && !pBoss->GetPrev() ); + } + + // Finally, we are able to split table rows. Therefore, bDontMoveMe + // can be set to false: + if( bDontMoveMe && IsInTab() && + nullptr != GetNextCellLeaf() ) + bDontMoveMe = false; + + assert(bMoveable); + + if ( bDontMoveMe && aRectFnSet.GetHeight(getFrameArea()) > + aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) ) + { + if ( !bFitPromise ) + { + SwTwips nTmp = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) - + aRectFnSet.GetTop(getFramePrintArea()); + bool bSplit = !IsFwdMoveAllowed(); + if ( nTmp > 0 && WouldFit( nTmp, bSplit, false ) ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + setFrameAreaSizeValid(false); + bFitPromise = true; + continue; + } + /* + * In earlier days, we never tried to fit TextFrames in + * frames and sections using bMoveOrFit by ignoring + * its attributes (Widows, Keep). + * This should have been done at least for column frames; + * as it must be tried anyway with linked frames and sections. + * Exception: If we sit in FormatWidthCols, we must not ignore + * the attributes. + */ + else if ( !bFootnote && + ( !bFly || !FindFlyFrame()->IsColLocked() ) && + ( !bSct || !FindSctFrame()->IsColLocked() ) ) + bMoveOrFit = true; + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "+TextFrame didn't respect WouldFit promise." ); + } +#endif + } + + // Let's see if I can find some space somewhere... + // footnotes in the neighbourhood are moved into _MoveFootnoteCntFwd + SwFrame *pPre = GetIndPrev(); + SwFrame *pOldUp = GetUpper(); + +/* MA 13. Oct. 98: What is this supposed to be!? + * AMA 14. Dec 98: If a column section can't find any space for its first ContentFrame, it should be + * moved not only to the next column, but probably even to the next page, creating + * a section-follow there. + */ + if( IsInSct() && bMovedFwd && bMakePage && pOldUp->IsColBodyFrame() && + pOldUp->GetUpper()->GetUpper()->IsSctFrame() && + ( pPre || pOldUp->GetUpper()->GetPrev() ) && + static_cast<SwSectionFrame*>(pOldUp->GetUpper()->GetUpper())->MoveAllowed(this) ) + { + bMovedFwd = false; + } + + const bool bCheckForGrownBody = pOldUp->IsBodyFrame(); + const long nOldBodyHeight = aRectFnSet.GetHeight(pOldUp->getFrameArea()); + + if ( !bMovedFwd && !MoveFwd( bMakePage, false ) ) + bMakePage = false; + aRectFnSet.Refresh(this); + if (!bMovedFwd && bFootnote && GetIndPrev() != pPre) + { // SwFlowFrame::CutTree() could have formatted and deleted pPre + auto const pPrevFootnoteFrame(static_cast<SwFootnoteFrame const*>( + FindFootnoteFrame())->GetMaster()); + bool bReset = true; + if (pPrevFootnoteFrame) + { // use GetIndNext() in case there are sections + for (auto p = pPrevFootnoteFrame->Lower(); p; p = p->GetIndNext()) + { + if (p == pPre) + { + bReset = false; + break; + } + } + } + if (bReset) + { + pPre = nullptr; + } + } + + // If MoveFwd moves the paragraph to the next page, a following + // paragraph, which contains footnotes can cause the old upper + // frame to grow. In this case we explicitly allow a new check + // for MoveBwd. Robust: We also check the bMovedBwd flag again. + // If pOldUp was a footnote frame, it has been deleted inside MoveFwd. + // Therefore we only check for growing body frames. + bMovedFwd = !bCheckForGrownBody || bMovedBwd || pOldUp == GetUpper() || + aRectFnSet.GetHeight(pOldUp->getFrameArea()) <= nOldBodyHeight; + + bFormatted = false; + if ( bMoveOrFit && GetUpper() == pOldUp ) + { + // FME 2007-08-30 #i81146# new loop control + if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) + { + Prepare( PrepareHint::MustFit, nullptr, false ); + setFrameAreaSizeValid(false); + bMustFit = true; + continue; + } + +#if OSL_DEBUG_LEVEL > 0 + OSL_FAIL( "LoopControl in SwContentFrame::MakeAll" ); +#endif + } + if ( bMovedBwd && GetUpper() ) + { // Retire invalidations that have become useless. + GetUpper()->ResetCompletePaint(); + if( pPre && !pPre->IsSctFrame() ) + ::ValidateSz( pPre ); + } + + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + + // NEW: Looping Louie (Light). Should not be applied in balanced sections. + // Should only be applied if there is no better solution! + LOOPING_LOUIE_LIGHT( bMovedFwd && bMovedBwd && !IsInBalancedSection() && + ( + + ( bFootnote && !FindFootnoteFrame()->GetRef()->IsInSct() ) || + + // #i33887# + ( IsInSct() && bKeep ) + + // ... add your conditions here ... + + ), + static_cast<SwTextFrame&>(*this) ); + + pSaveFootnote.reset(); + + UnlockJoin(); + xDeleteGuard.reset(); + if ( bMovedFwd || bMovedBwd ) + pNotify->SetInvaKeep(); + if ( bMovedFwd ) + { + pNotify->SetInvalidatePrevPrtArea(); + } + pNotify.reset(); + SetFlyLock( false ); +} + +void MakeNxt( SwFrame *pFrame, SwFrame *pNxt ) +{ + // fix(25455): Validate, otherwise this leads to a recursion. + // The first try, cancelling with pFrame = 0 if !Valid, leads to a problem, as + // the Keep may not be considered properly anymore (27417). + const bool bOldPos = pFrame->isFrameAreaPositionValid(); + const bool bOldSz = pFrame->isFrameAreaSizeValid(); + const bool bOldPrt = pFrame->isFramePrintAreaValid(); + pFrame->setFrameAreaPositionValid(true); + pFrame->setFrameAreaSizeValid(true); + pFrame->setFramePrintAreaValid(true); + + // fix(29272): Don't call MakeAll - there, pFrame might be invalidated again, and + // we recursively end up in here again. + if ( pNxt->IsContentFrame() ) + { + SwContentNotify aNotify( static_cast<SwContentFrame*>(pNxt) ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNxt ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + if ( !pNxt->isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNxt); + + if( pNxt->IsVertical() ) + { + aFrm.Height( pNxt->GetUpper()->getFramePrintArea().Height() ); + } + else + { + aFrm.Width( pNxt->GetUpper()->getFramePrintArea().Width() ); + } + } + static_cast<SwContentFrame*>(pNxt)->MakePrtArea( rAttrs ); + pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + else + { + SwLayNotify aNotify( static_cast<SwLayoutFrame*>(pNxt) ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNxt ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + if ( !pNxt->isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNxt); + + if( pNxt->IsVertical() ) + { + aFrm.Height( pNxt->GetUpper()->getFramePrintArea().Height() ); + } + else + { + aFrm.Width( pNxt->GetUpper()->getFramePrintArea().Width() ); + } + } + pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + + pFrame->setFrameAreaPositionValid(bOldPos); + pFrame->setFrameAreaSizeValid(bOldSz); + pFrame->setFramePrintAreaValid(bOldPrt); +} + +/// This routine checks whether there are no other FootnoteBosses +/// between the pFrame's FootnoteBoss and the pNxt's FootnoteBoss. +static bool lcl_IsNextFootnoteBoss( const SwFrame *pFrame, const SwFrame* pNxt ) +{ + assert(pFrame && pNxt && "lcl_IsNextFootnoteBoss: No Frames?"); + pFrame = pFrame->FindFootnoteBossFrame(); + pNxt = pNxt->FindFootnoteBossFrame(); + // If pFrame is a last column, we use the page instead. + while( pFrame && pFrame->IsColumnFrame() && !pFrame->GetNext() ) + pFrame = pFrame->GetUpper()->FindFootnoteBossFrame(); + // If pNxt is a first column, we use the page instead. + while( pNxt && pNxt->IsColumnFrame() && !pNxt->GetPrev() ) + pNxt = pNxt->GetUpper()->FindFootnoteBossFrame(); + // So... now pFrame and pNxt are either two adjacent pages or columns. + return pFrame && pNxt && pFrame->GetNext() == pNxt; +} + +bool SwContentFrame::WouldFit_( SwTwips nSpace, + SwLayoutFrame *pNewUpper, + bool bTstMove, + const bool bObjsInNewUpper ) +{ + // To have the footnote select its place carefully, it needs + // to be moved in any case if there is at least one page/column + // between the footnote and the new Upper. + SwFootnoteFrame* pFootnoteFrame = nullptr; + if ( IsInFootnote() ) + { + if( !lcl_IsNextFootnoteBoss( pNewUpper, this ) ) + return true; + pFootnoteFrame = FindFootnoteFrame(); + } + + bool bRet; + bool bSplit = !pNewUpper->Lower(); + SwContentFrame *pFrame = this; + const SwFrame *pTmpPrev = pNewUpper->Lower(); + if( pTmpPrev && pTmpPrev->IsFootnoteFrame() ) + pTmpPrev = static_cast<const SwFootnoteFrame*>(pTmpPrev)->Lower(); + while ( pTmpPrev && pTmpPrev->GetNext() ) + pTmpPrev = pTmpPrev->GetNext(); + do + { + // #i46181# + SwTwips nSecondCheck = 0; + SwTwips nOldSpace = nSpace; + bool bOldSplit = bSplit; + + if ( bTstMove || IsInFly() || ( IsInSct() && + ( pFrame->GetUpper()->IsColBodyFrame() || ( pFootnoteFrame && + pFootnoteFrame->GetUpper()->GetUpper()->IsColumnFrame() ) ) ) ) + { + // This is going to get a bit insidious now. If you're faint of heart, + // you'd better look away here. If a Fly contains columns, then the Contents + // are movable, except ones in the last column (see SwFrame::IsMoveable()). + // Of course they're allowed to float back. WouldFit() only returns a usable + // value if the Frame is movable. To fool WouldFit() into believing there's + // a movable Frame, I'm just going to hang it somewhere else for the time. + // The same procedure applies for column sections to make SwSectionFrame::Growable() + // return the proper value. + // Within footnotes, we may even need to put the SwFootnoteFrame somewhere else, if + // there's no SwFootnoteFrame there. + SwFrame* pTmpFrame = pFrame->IsInFootnote() && !pNewUpper->FindFootnoteFrame() ? + static_cast<SwFrame*>(pFrame->FindFootnoteFrame()) : pFrame; + SwLayoutFrame *pUp = pTmpFrame->GetUpper(); + SwFrame *pOldNext = pTmpFrame->GetNext(); + pTmpFrame->RemoveFromLayout(); + pTmpFrame->InsertBefore( pNewUpper, nullptr ); + // tdf#107126 for a section in a footnote, we have only inserted + // the SwTextFrame but no SwSectionFrame - reset mbInfSct flag + // to avoid crashing (but perhaps we should create a temp + // SwSectionFrame here because WidowsAndOrphans checks for that?) + pTmpFrame->InvalidateInfFlags(); + if ( pFrame->IsTextFrame() && + ( bTstMove || + static_cast<SwTextFrame*>(pFrame)->HasFollow() || + ( !static_cast<SwTextFrame*>(pFrame)->HasPara() && + !static_cast<SwTextFrame*>(pFrame)->IsEmpty() + ) + ) + ) + { + bTstMove = true; + bRet = static_cast<SwTextFrame*>(pFrame)->TestFormat( pTmpPrev, nSpace, bSplit ); + } + else + bRet = pFrame->WouldFit( nSpace, bSplit, false ); + + pTmpFrame->RemoveFromLayout(); + pTmpFrame->InsertBefore( pUp, pOldNext ); + pTmpFrame->InvalidateInfFlags(); // restore flags + } + else + { + bRet = pFrame->WouldFit( nSpace, bSplit, false ); + nSecondCheck = !bSplit ? 1 : 0; + } + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // Sad but true: We need to consider the spacing in our calculation. + // This already happened in TestFormat. + if ( bRet && !bTstMove ) + { + SwTwips nUpper; + + if ( pTmpPrev ) + { + nUpper = CalcUpperSpace( nullptr, pTmpPrev ); + + // in balanced columned section frames we do not want the + // common border + bool bCommonBorder = true; + if ( pFrame->IsInSct() && pFrame->GetUpper()->IsColBodyFrame() ) + { + const SwSectionFrame* pSct = pFrame->FindSctFrame(); + bCommonBorder = pSct->GetFormat()->GetBalancedColumns().GetValue(); + } + + // #i46181# + nSecondCheck = ( 1 == nSecondCheck && + pFrame == this && + IsTextFrame() && + bCommonBorder && + !static_cast<const SwTextFrame*>(this)->IsEmpty() ) ? + nUpper : + 0; + + nUpper += bCommonBorder ? + rAttrs.GetBottomLine( *pFrame ) : + rAttrs.CalcBottomLine(); + + } + else + { + // #i46181# + nSecondCheck = 0; + + if( pFrame->IsVertical() ) + nUpper = pFrame->getFrameArea().Width() - pFrame->getFramePrintArea().Width(); + else + nUpper = pFrame->getFrameArea().Height() - pFrame->getFramePrintArea().Height(); + } + + nSpace -= nUpper; + + if ( nSpace < 0 ) + { + bRet = false; + + // #i46181# + if ( nSecondCheck > 0 ) + { + // The following code is intended to solve a (rare) problem + // causing some frames not to move backward: + // SwTextFrame::WouldFit() claims that the whole paragraph + // fits into the given space and subtracts the height of + // all lines from nSpace. nSpace - nUpper is not a valid + // indicator if the frame should be allowed to move backward. + // We do a second check with the original remaining space + // reduced by the required upper space: + nOldSpace -= nSecondCheck; + const bool bSecondRet = nOldSpace >= 0 && pFrame->WouldFit( nOldSpace, bOldSplit, false ); + if ( bSecondRet && bOldSplit && nOldSpace >= 0 ) + { + bRet = true; + bSplit = true; + } + } + } + } + + // Also consider lower spacing in table cells + IDocumentSettingAccess const& rIDSA(pNewUpper->GetFormat()->getIDocumentSettingAccess()); + if ( bRet && IsInTab() && + rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS)) + { + nSpace -= rAttrs.GetULSpace().GetLower(); + + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nSpace -= rAttrs.CalcLineSpacing(); + } + if ( nSpace < 0 ) + { + bRet = false; + } + } + + if (bRet && !bSplit && pFrame->IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem())) + { + if( bTstMove ) + { + while( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->HasFollow() ) + { + pFrame = static_cast<SwTextFrame*>(pFrame)->GetFollow(); + } + // If last follow frame of <this> text frame isn't valid, + // a formatting of the next content frame doesn't makes sense. + // Thus, return true. + if ( IsAnFollow( pFrame ) && !pFrame->isFrameAreaDefinitionValid() ) + { + OSL_FAIL( "Only a warning for task 108824:/n<SwContentFrame::WouldFit_(..) - follow not valid!" ); + return true; + } + } + SwFrame *pNxt; + if( nullptr != (pNxt = pFrame->FindNext()) && pNxt->IsContentFrame() && + ( !pFootnoteFrame || ( pNxt->IsInFootnote() && + pNxt->FindFootnoteFrame()->GetAttr() == pFootnoteFrame->GetAttr() ) ) ) + { + // TestFormat(?) does not like paragraph- or character anchored objects. + + // current solution for the test formatting doesn't work, if + // objects are present in the remaining area of the new upper + if ( bTstMove && + ( pNxt->GetDrawObjs() || bObjsInNewUpper ) ) + { + return true; + } + + if ( !pNxt->isFrameAreaDefinitionValid() ) + { + MakeNxt( pFrame, pNxt ); + } + + // Little trick: if the next has a predecessor, then the paragraph + // spacing has been calculated already, and we don't need to re-calculate + // it in an expensive way. + if( lcl_NotHiddenPrev( pNxt ) ) + pTmpPrev = nullptr; + else + { + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsHiddenNow() ) + pTmpPrev = lcl_NotHiddenPrev( pFrame ); + else + pTmpPrev = pFrame; + } + pFrame = static_cast<SwContentFrame*>(pNxt); + } + else + pFrame = nullptr; + } + else + pFrame = nullptr; + + } while ( bRet && pFrame ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/colfrm.cxx b/sw/source/core/layout/colfrm.cxx new file mode 100644 index 000000000..dfb438953 --- /dev/null +++ b/sw/source/core/layout/colfrm.cxx @@ -0,0 +1,445 @@ +/* -*- 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 <editeng/ulspitem.hxx> +#include <fmtclds.hxx> +#include <fmtfordr.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <colfrm.hxx> +#include <pagefrm.hxx> +#include <bodyfrm.hxx> +#include <rootfrm.hxx> +#include <sectfrm.hxx> +#include <calbck.hxx> +#include <ftnfrm.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentUndoRedo.hxx> + +SwColumnFrame::SwColumnFrame( SwFrameFormat *pFormat, SwFrame* pSib ): + SwFootnoteBossFrame( pFormat, pSib ) +{ + mnFrameType = SwFrameType::Column; + SwBodyFrame* pColBody = new SwBodyFrame( pFormat->GetDoc()->GetDfltFrameFormat(), pSib ); + pColBody->InsertBehind( this, nullptr ); // ColumnFrames now with BodyFrame + SetMaxFootnoteHeight( LONG_MAX ); +} + +void SwColumnFrame::DestroyImpl() +{ + SwFrameFormat *pFormat = GetFormat(); + SwDoc *pDoc; + if ( !(pDoc = pFormat->GetDoc())->IsInDtor() && pFormat->HasOnlyOneListener() ) + { + //I'm the only one, delete the format. + //Get default format before, so the base class can cope with it. + pDoc->GetDfltFrameFormat()->Add( this ); + // tdf#134009, like #i32968# avoid SwUndoFrameFormatDelete creation, + // the format is owned by the SwColumnFrame, see lcl_AddColumns() + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->DelFrameFormat( pFormat ); + } + + SwFootnoteBossFrame::DestroyImpl(); +} + +SwColumnFrame::~SwColumnFrame() +{ +} + +static void lcl_RemoveColumns( SwLayoutFrame *pCont, sal_uInt16 nCnt ) +{ + OSL_ENSURE( pCont && pCont->Lower() && pCont->Lower()->IsColumnFrame(), + "no columns to remove." ); + + SwColumnFrame *pColumn = static_cast<SwColumnFrame*>(pCont->Lower()); + sw_RemoveFootnotes( pColumn, true, true ); + while ( pColumn->GetNext() ) + { + OSL_ENSURE( pColumn->GetNext()->IsColumnFrame(), + "neighbor of ColumnFrame is no ColumnFrame." ); + pColumn = static_cast<SwColumnFrame*>(pColumn->GetNext()); + } + for ( sal_uInt16 i = 0; i < nCnt; ++i ) + { + SwColumnFrame *pTmp = static_cast<SwColumnFrame*>(pColumn->GetPrev()); + pColumn->Cut(); + SwFrame::DestroyFrame(pColumn); //format is going to be destroyed in the DTor if needed. + pColumn = pTmp; + } +} + +static SwLayoutFrame * lcl_FindColumns( SwLayoutFrame *pLay, sal_uInt16 nCount ) +{ + SwFrame *pCol = pLay->Lower(); + if ( pLay->IsPageFrame() ) + pCol = static_cast<SwPageFrame*>(pLay)->FindBodyCont()->Lower(); + + if ( pCol && pCol->IsColumnFrame() ) + { + SwFrame *pTmp = pCol; + sal_uInt16 i; + for ( i = 0; pTmp; pTmp = pTmp->GetNext(), ++i ) + /* do nothing */; + return i == nCount ? static_cast<SwLayoutFrame*>(pCol) : nullptr; + } + return nullptr; +} + +static bool lcl_AddColumns( SwLayoutFrame *pCont, sal_uInt16 nCount ) +{ + SwDoc *pDoc = pCont->GetFormat()->GetDoc(); + const bool bMod = pDoc->getIDocumentState().IsModified(); + + //Formats should be shared whenever possible. If a neighbour already has + //the same column settings we can add them to the same format. + //The neighbour can be searched using the format, however the owner of the + //attribute depends on the frame type. + SwLayoutFrame *pAttrOwner = pCont; + if ( pCont->IsBodyFrame() ) + pAttrOwner = pCont->FindPageFrame(); + SwLayoutFrame *pNeighbourCol = nullptr; + SwIterator<SwLayoutFrame,SwFormat> aIter( *pAttrOwner->GetFormat() ); + SwLayoutFrame *pNeighbour = aIter.First(); + + sal_uInt16 nAdd = 0; + SwFrame *pCol = pCont->Lower(); + if ( pCol && pCol->IsColumnFrame() ) + for ( nAdd = 1; pCol; pCol = pCol->GetNext(), ++nAdd ) + /* do nothing */; + while ( pNeighbour ) + { + if ( nullptr != (pNeighbourCol = lcl_FindColumns( pNeighbour, nCount+nAdd )) && + pNeighbourCol != pCont ) + break; + pNeighbourCol = nullptr; + pNeighbour = aIter.Next(); + } + + bool bRet; + SwTwips nMax = pCont->IsPageBodyFrame() ? + pCont->FindPageFrame()->GetMaxFootnoteHeight() : LONG_MAX; + if ( pNeighbourCol ) + { + bRet = false; + SwFrame *pTmp = pCont->Lower(); + while ( pTmp ) + { + pTmp = pTmp->GetNext(); + pNeighbourCol = static_cast<SwLayoutFrame*>(pNeighbourCol->GetNext()); + } + for ( sal_uInt16 i = 0; i < nCount; ++i ) + { + SwColumnFrame *pTmpCol = new SwColumnFrame( pNeighbourCol->GetFormat(), pCont ); + pTmpCol->SetMaxFootnoteHeight( nMax ); + pTmpCol->InsertBefore( pCont, nullptr ); + pNeighbourCol = static_cast<SwLayoutFrame*>(pNeighbourCol->GetNext()); + } + } + else + { + bRet = true; + // tdf#103359, like #i32968# Inserting columns in the section causes MakeFrameFormat to put + // nCount objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + for ( sal_uInt16 i = 0; i < nCount; ++i ) + { + SwFrameFormat *pFormat = pDoc->MakeFrameFormat(OUString(), pDoc->GetDfltFrameFormat()); + SwColumnFrame *pTmp = new SwColumnFrame( pFormat, pCont ); + pTmp->SetMaxFootnoteHeight( nMax ); + pTmp->Paste( pCont ); + } + } + + if ( !bMod ) + pDoc->getIDocumentState().ResetModified(); + return bRet; +} + +/** add or remove columns from a layoutframe. + * + * Normally, a layoutframe with a column attribute of 1 or 0 columns contains + * no columnframe. However, a sectionframe with "footnotes at the end" needs + * a columnframe. + * + * @param rOld + * @param rNew + * @param bChgFootnote if true, the columnframe will be inserted or removed, if necessary. + */ +void SwLayoutFrame::ChgColumns( const SwFormatCol &rOld, const SwFormatCol &rNew, + const bool bChgFootnote ) +{ + if ( rOld.GetNumCols() <= 1 && rNew.GetNumCols() <= 1 && !bChgFootnote ) + return; + // #i97379# + // If current lower is a no text frame, then columns are not allowed + if ( Lower() && Lower()->IsNoTextFrame() && + rNew.GetNumCols() > 1 ) + { + return; + } + + sal_uInt16 nNewNum, nOldNum = 1; + if( Lower() && Lower()->IsColumnFrame() ) + { + SwFrame* pCol = Lower(); + while( nullptr != (pCol=pCol->GetNext()) ) + ++nOldNum; + } + nNewNum = rNew.GetNumCols(); + if( !nNewNum ) + ++nNewNum; + bool bAtEnd; + if( IsSctFrame() ) + bAtEnd = static_cast<SwSectionFrame*>(this)->IsAnyNoteAtEnd(); + else + bAtEnd = false; + + //Setting the column width is only needed for new formats. + bool bAdjustAttributes = nOldNum != rOld.GetNumCols(); + + //The content is saved and restored if the column count is different. + SwFrame *pSave = nullptr; + if( nOldNum != nNewNum || bChgFootnote ) + { + SwDoc *pDoc = GetFormat()->GetDoc(); + OSL_ENSURE( pDoc, "FrameFormat doesn't return a document." ); + // SaveContent would also suck up the content of the footnote container + // and store it within the normal text flow. + if( IsPageBodyFrame() ) + pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->RemoveFootnotes( static_cast<SwPageFrame*>(GetUpper()) ); + pSave = ::SaveContent( this ); + + //If columns exist, they get deleted if a column count of 0 or 1 is requested. + if ( nNewNum == 1 && !bAtEnd ) + { + ::lcl_RemoveColumns( this, nOldNum ); + if ( IsBodyFrame() ) + SetFrameFormat( pDoc->GetDfltFrameFormat() ); + else + GetFormat()->SetFormatAttr( SwFormatFillOrder() ); + if ( pSave ) + ::RestoreContent( pSave, this, nullptr ); + return; + } + if ( nOldNum == 1 ) + { + if ( IsBodyFrame() ) + SetFrameFormat( pDoc->GetColumnContFormat() ); + else + GetFormat()->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ) ); + if( !Lower() || !Lower()->IsColumnFrame() ) + --nOldNum; + } + if ( nOldNum > nNewNum ) + { + ::lcl_RemoveColumns( this, nOldNum - nNewNum ); + bAdjustAttributes = true; + } + else if( nOldNum < nNewNum ) + { + sal_uInt16 nAdd = nNewNum - nOldNum; + bAdjustAttributes = lcl_AddColumns( this, nAdd ); + } + } + + if ( !bAdjustAttributes ) + { + if ( rOld.GetLineWidth() != rNew.GetLineWidth() || + rOld.GetWishWidth() != rNew.GetWishWidth() || + rOld.IsOrtho() != rNew.IsOrtho() ) + bAdjustAttributes = true; + else + { + const size_t nCount = std::min( rNew.GetColumns().size(), rOld.GetColumns().size() ); + for ( size_t i = 0; i < nCount; ++i ) + if ( !(rOld.GetColumns()[i] == rNew.GetColumns()[i]) ) + { + bAdjustAttributes = true; + break; + } + } + } + + //The columns can now be easily adjusted. + AdjustColumns( &rNew, bAdjustAttributes ); + + //Don't restore the content before. An earlier restore would trigger useless + //actions during setup. + if ( pSave ) + { + OSL_ENSURE( Lower() && Lower()->IsLayoutFrame() && + static_cast<SwLayoutFrame*>(Lower())->Lower() && + static_cast<SwLayoutFrame*>(Lower())->Lower()->IsLayoutFrame(), + "no column body." ); // ColumnFrames contain BodyFrames + ::RestoreContent( pSave, static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(Lower())->Lower()), nullptr ); + } +} + +void SwLayoutFrame::AdjustColumns( const SwFormatCol *pAttr, bool bAdjustAttributes ) +{ + if( !Lower()->GetNext() ) + { + Lower()->ChgSize( getFramePrintArea().SSize() ); + return; + } + + const bool bVert = IsVertical(); + + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + //If we have a pointer or we have to configure an attribute, we set the + //column widths in any case. Otherwise we check if a configuration is needed. + if ( !pAttr ) + { + pAttr = &GetFormat()->GetCol(); + if ( !bAdjustAttributes ) + { + long nAvail = (getFramePrintArea().*fnRect->fnGetWidth)(); + for ( SwLayoutFrame *pCol = static_cast<SwLayoutFrame*>(Lower()); + pCol; + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()) ) + nAvail -= (pCol->getFrameArea().*fnRect->fnGetWidth)(); + if ( !nAvail ) + return; + } + } + + //The columns can now be easily adjusted. + //The widths get counted so we can give the reminder to the last one. + SwTwips nAvail = (getFramePrintArea().*fnRect->fnGetWidth)(); + const bool bLine = pAttr->GetLineAdj() != COLADJ_NONE; + const sal_uInt16 nMin = bLine ? sal_uInt16( 20 + ( pAttr->GetLineWidth() / 2) ) : 0; + + const bool bR2L = IsRightToLeft(); + SwFrame *pCol = bR2L ? GetLastLower() : Lower(); + + // #i27399# + // bOrtho means we have to adjust the column frames manually. Otherwise + // we may use the values returned by CalcColWidth: + const bool bOrtho = pAttr->IsOrtho() && pAttr->GetNumCols() > 0; + long nGutter = 0; + + for ( sal_uInt16 i = 0; i < pAttr->GetNumCols() && pCol; ++i ) //i118878, value returned by GetNumCols() can't be trusted + { + if( !bOrtho ) + { + const SwTwips nWidth = i == (pAttr->GetNumCols() - 1) ? + nAvail : + pAttr->CalcColWidth( i, sal_uInt16( (getFramePrintArea().*fnRect->fnGetWidth)() ) ); + + const Size aColSz = bVert ? + Size( getFramePrintArea().Width(), nWidth ) : + Size( nWidth, getFramePrintArea().Height() ); + + pCol->ChgSize( aColSz ); + + // With this, the ColumnBodyFrames from page columns gets adjusted and + // their bFixHeight flag is set so they won't shrink/grow. + // Don't use the flag with frame columns because BodyFrames in frame + // columns can grow/shrink. + if( IsBodyFrame() ) + static_cast<SwLayoutFrame*>(pCol)->Lower()->ChgSize( aColSz ); + + nAvail -= nWidth; + } + + if ( bOrtho || bAdjustAttributes ) + { + const SwColumn *pC = &pAttr->GetColumns()[i]; + const SwAttrSet* pSet = pCol->GetAttrSet(); + SvxLRSpaceItem aLR( pSet->GetLRSpace() ); + + //In order to have enough space for the separation lines, we have to + //take them into account here. Every time two columns meet we + //calculate a clearance of 20 + half the pen width on the left or + //right side, respectively. + const sal_uInt16 nLeft = pC->GetLeft(); + const sal_uInt16 nRight = pC->GetRight(); + + aLR.SetLeft ( nLeft ); + aLR.SetRight( nRight ); + + if ( bLine ) + { + if ( i == 0 ) + { + aLR.SetRight( std::max( nRight, nMin ) ); + } + else if ( i == pAttr->GetNumCols() - 1 ) + { + aLR.SetLeft ( std::max( nLeft, nMin ) ); + } + else + { + aLR.SetLeft ( std::max( nLeft, nMin ) ); + aLR.SetRight( std::max( nRight, nMin ) ); + } + } + + if ( bAdjustAttributes ) + { + SvxULSpaceItem aUL( pSet->GetULSpace() ); + aUL.SetUpper(0); + aUL.SetLower(0); + + static_cast<SwLayoutFrame*>(pCol)->GetFormat()->SetFormatAttr( aLR ); + static_cast<SwLayoutFrame*>(pCol)->GetFormat()->SetFormatAttr( aUL ); + } + + nGutter += aLR.GetLeft() + aLR.GetRight(); + } + + pCol = bR2L ? pCol->GetPrev() : pCol->GetNext(); + } + + if( bOrtho ) + { + long nInnerWidth = ( nAvail - nGutter ) / pAttr->GetNumCols(); + pCol = Lower(); + for( sal_uInt16 i = 0; i < pAttr->GetNumCols() && pCol; pCol = pCol->GetNext(), ++i ) //i118878, value returned by GetNumCols() can't be trusted + { + SwTwips nWidth; + if ( i == pAttr->GetNumCols() - 1 ) + nWidth = nAvail; + else + { + SvxLRSpaceItem aLR( pCol->GetAttrSet()->GetLRSpace() ); + nWidth = nInnerWidth + aLR.GetLeft() + aLR.GetRight(); + } + if( nWidth < 0 ) + nWidth = 0; + + const Size aColSz = bVert ? + Size( getFramePrintArea().Width(), nWidth ) : + Size( nWidth, getFramePrintArea().Height() ); + + pCol->ChgSize( aColSz ); + + if( IsBodyFrame() ) + static_cast<SwLayoutFrame*>(pCol)->Lower()->ChgSize( aColSz ); + + nAvail -= nWidth; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/dbg_lay.cxx b/sw/source/core/layout/dbg_lay.cxx new file mode 100644 index 000000000..f395efeae --- /dev/null +++ b/sw/source/core/layout/dbg_lay.cxx @@ -0,0 +1,923 @@ +/* -*- 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 . + */ + +#ifdef DBG_UTIL + +/* + * And here's the description: + * + * The PROTOCOL macros allow you to log events in frame methods. In places where + * logging is useful either one of the PROTOCOL(...) or PROTOCOL_ENTER(...) can + * be used. PROTOCOL_ENTER(...) additionally logs the leaving of a method. + * + * The PROTOCOL macros accept the following parameters: + * 1. A pointer to an SwFrame (usually "this" or "rThis") + * 2. The function group i.e. PROT::MakeAll. This is used to decide (inline) + * whether this event shall be logged at the current time. + * 3. The action, usually 0. For example DbgAction::Start indents output in the log + * file and DbgAction::End stops the indentation. This allows for example + * PROTOCOL_ENTER to indent at the beginning of a method and stop indenting + * when leaving the method. + * 4. The fourth parameter is a void pointer which allows to pass anything + * which can be used in the log. A good example is PROT::Grow: this requires + * a pointer to the value which defines how much to grow. + * + * The log file is called "dbg_lay.out", which is saved in the current (BIN-) + * directory. The file contains lines with FrameId, function group and additional + * information. + * + * What exactly is going to be logged, can be defined as follows: + * 1. The static variable SwProtocol::nRecord contains the function groups + * which shall be logged. + * A value of i.e. PROT::Grow causes calls to SwFrame::Grow to be + * logged; PROT::MakeAll logs the calls to xxx::MakeAll. + * The PROT_XY values can be combined using binary OR, the default value + * is null - no method calls are logged. + * 2. The SwImplProtocol class contains a filter for frame types, only method + * call of frame types which are defined there are logged. + * The member nTypes can be set to values like SwFrameType::Page or SwFrameType::Section and + * may be combined using binary OR. The default values is 0xFFFF - meaning + * all frame types. + * 3. The SwImplProtocol class contains an ArrayPointer to FrameIds which need to be + * tracked. If the pointer is null, all frames will be logged; otherwise + * only frames of linked from the array will be logged. + * + * Code changes are needed to start logging; either change the default of nRecord + * in SwProtocol::Init() or change the debugger. There are several possible + * places in the debugger: + * 1. Set a breakpoint in SwProtocol::Init() and manipulate nRecord there, set + * FrameIds accordingly then start logging during program start. + * 2. Set a breakpoint before any PROTOCOL or PROTOCOL_ENTER macro during + * program execution, then set the lowest bit (PROT::Init) of + * SwProtocol::nRecord. This activates the function group of the following + * macro and causes it to be logged in the future. + * 3. There's a special case for 2: If one uses 2. in SwRootFrame::PaintSwFrame(..), + * the log settings are taken from the file "dbg_lay.ini"! + * In this INI-file you can have comment lines starting with a '#'. + * The sections "[frmid]", "[frmtype]" and "[record]" are relevant. + * In the [frmid] section, you can put FrameIds of the Frames to be logged. + * If there are no entries in this section, all Frames will be logged. + * In the [frmtype] section, the frame types which should be logged are + * listed; default is USHRT_MAX which means that all types are logged. + * It's possible to remove types from the list using '!' in front of a + * value. The value !0xC000 would for example exclude SwContentFrames from + * logging. + * In the [record] section the functions group which should be logged are + * listed; default is 0 which means that none are logged. It's also + * possible to remove functions using '!'. + * An example INI file: + * #Functions: all(0x0007ffff), except PrintArea (0x200) + * [record] 524287 !512 + * [frmid] + * #the following FrameIds: + * 1 2 12 13 14 15 + * #no layout frames, except ColumnFrames + * [frmtype] !0x3FFF 0x4 + * + * As soon as the logging is in process, one can manipulate many things in + * SwImplProtocol::Record_(...) using a debugger, especially concerning + * frame types and FrameIds. + */ + +#include <dbg_lay.hxx> + +#include <txtfrm.hxx> +#include <fntcache.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <layfrm.hxx> +#include <frame.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <tools/stream.hxx> + +PROT SwProtocol::nRecord = PROT::FileInit; +SwImplProtocol* SwProtocol::pImpl = nullptr; + +static sal_uLong lcl_GetFrameId( const SwFrame* pFrame ) +{ +#if OSL_DEBUG_LEVEL > 1 + static bool bFrameId = false; + if( bFrameId ) + return pFrame->GetFrameId(); +#endif + if( pFrame ) + return pFrame->GetFrameId(); + return 0; +} + +class SwImplProtocol +{ + std::unique_ptr<SvFileStream> pStream; // output stream + std::unique_ptr<std::set<sal_uInt16>> pFrameIds; // which FrameIds shall be logged ( NULL == all) + std::vector<long> aVars; // variables + OStringBuffer aLayer; // indentation of output (" " per start/end) + SwFrameType nTypes; // which types shall be logged + sal_uInt16 nLineCount; // printed lines + sal_uInt16 nMaxLines; // max lines to be printed + sal_uInt8 nInitFile; // range (FrameId,FrameType,Record) during reading of the INI file + sal_uInt8 nTestMode; // special for test formatting, logging may only be done in test formatting. + void Record_( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ); + bool NewStream(); + void CheckLine( OString& rLine ); + static void SectFunc( OStringBuffer& rOut, DbgAction nAct, void const * pParam ); +public: + SwImplProtocol(); + ~SwImplProtocol(); + // logging + void Record( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ) + { if( pStream ) Record_( pFrame, nFunction, nAct, pParam ); } + void InsertFrame( sal_uInt16 nFrameId ); // take FrameId for logging + void DeleteFrame( sal_uInt16 nFrameId ); // remove FrameId; don't log him anymore + void FileInit(); // read the INI file + void ChkStream() { if( !pStream ) NewStream(); } +}; + +/* Through the PROTOCOL_ENTER macro a SwEnterLeave object gets created. If the + * current function should be logged a SwImplEnterLeace object gets created. + * The funny thing here is, that the Ctor of the Impl object is automatically + * called at the beginning of the function and the Dtor is automatically called + * when leaving the function. In the base implementation the Ctor calls only + * PROTOCOL(..) with DbgAction::Start and in the Dtor a PROTOCOL(..) with DbgAction::End. + * It's possible to derive from this class, for example to be able to document + * frame resize while leaving a function. To do this, one only needs to add the + * desired SwImplEnterLeave class in SwEnterLeave::Ctor(). + */ + +class SwImplEnterLeave +{ +protected: + const SwFrame* pFrame; // the frame + PROT nFunction; // the function + DbgAction nAction; // the action if needed + void* pParam; // further parameter +public: + SwImplEnterLeave( const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar ) + : pFrame( pF ), nFunction( nFunct ), nAction( nAct ), pParam( pPar ) {} + virtual ~SwImplEnterLeave() {} + virtual void Enter(); // message when entering + virtual void Leave(); // message when leaving +}; + +namespace { + +class SwSizeEnterLeave : public SwImplEnterLeave +{ + long nFrameHeight; +public: + SwSizeEnterLeave( const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar ) + : SwImplEnterLeave( pF, nFunct, nAct, pPar ), nFrameHeight( pF->getFrameArea().Height() ) {} + + virtual void Leave() override; // resize message +}; + +class SwUpperEnterLeave : public SwImplEnterLeave +{ + sal_uInt16 nFrameId; +public: + SwUpperEnterLeave( const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar ) + : SwImplEnterLeave( pF, nFunct, nAct, pPar ), nFrameId( 0 ) {} + + virtual void Enter() override; // message + virtual void Leave() override; // message of FrameId from upper +}; + +class SwFrameChangesLeave : public SwImplEnterLeave +{ + SwRect aFrame; +public: + SwFrameChangesLeave( const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar ) + : SwImplEnterLeave( pF, nFunct, nAct, pPar ), aFrame( pF->getFrameArea() ) {} + + virtual void Enter() override; // no message + virtual void Leave() override; // message when resizing the Frame area +}; + +} + +void SwProtocol::Record( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ) +{ + if( Start() ) + { // We reach this point if SwProtocol::nRecord is binary OR'd with PROT::Init(0x1) using the debugger + bool bFinit = false; // This gives the possibility to stop logging of this action in the debugger + if( bFinit ) + { + nRecord &= ~nFunction; // Don't log this function any longer + nRecord &= ~PROT::Init; // Always reset PROT::Init + return; + } + nRecord |= nFunction; // Activate logging of this function + nRecord &= ~PROT::Init; // Always reset PROT::Init + if( pImpl ) + pImpl->ChkStream(); + } + if( !pImpl ) // Create Impl object if needed + pImpl = new SwImplProtocol(); + pImpl->Record( pFrame, nFunction, nAct, pParam ); // ...and start logging +} + +// The following function gets called when pulling in the writer DLL through +// TextInit(..) and gives the possibility to release functions +// and/or FrameIds to the debugger + +void SwProtocol::Init() +{ + nRecord = PROT::FileInit; + OUString aName("dbg_lay.go"); + SvFileStream aStream( aName, StreamMode::READ ); + if( aStream.IsOpen() ) + { + pImpl = new SwImplProtocol(); + pImpl->FileInit(); + } + aStream.Close(); +} + +// End of logging + +void SwProtocol::Stop() +{ + if( pImpl ) + { + delete pImpl; + pImpl = nullptr; + if( pFntCache ) + pFntCache->Flush(); + } + nRecord = PROT::FileInit; +} + +SwImplProtocol::SwImplProtocol() + : nTypes( FRM_ALL ), + nLineCount( 0 ), nMaxLines( USHRT_MAX ), nTestMode( 0 ) +{ + NewStream(); +} + +bool SwImplProtocol::NewStream() +{ + OUString aName("dbg_lay.out"); + nLineCount = 0; + pStream.reset( new SvFileStream( aName, StreamMode::WRITE | StreamMode::TRUNC ) ); + if( pStream->GetError() ) + { + pStream.reset(); + } + return nullptr != pStream; +} + +SwImplProtocol::~SwImplProtocol() +{ + if( pStream ) + { + pStream->Close(); + pStream.reset(); + } + pFrameIds.reset(); + aVars.clear(); +} + +/// analyze a line in the INI file +void SwImplProtocol::CheckLine( OString& rLine ) +{ + rLine = rLine.toAsciiLowerCase(); // upper/lower case is the same + rLine = rLine.replace( '\t', ' ' ); + if( '#' == rLine[0] ) // comments start with '#' + return; + if( '[' == rLine[0] ) // section: FrameIds, type or function + { + OString aTmp = rLine.getToken(0, ']'); + if (aTmp == "[frmid") // section FrameIds + { + nInitFile = 1; + pFrameIds.reset(); // default: log all frames + } + else if (aTmp == "[frmtype")// section types + { + nInitFile = 2; + nTypes = FRM_ALL; // default: log all frame types + } + else if (aTmp == "[record")// section functions + { + nInitFile = 3; + SwProtocol::SetRecord( PROT::FileInit );// default: don't log any function + } + else if (aTmp == "[test")// section functions + { + nInitFile = 4; // default: + nTestMode = 0; // log outside of test formatting + } + else if (aTmp == "[max")// Max number of lines + { + nInitFile = 5; // default: + nMaxLines = USHRT_MAX; + } + else if (aTmp == "[var")// variables + { + nInitFile = 6; + } + else + nInitFile = 0; // oops: unknown section? + rLine = rLine.copy(aTmp.getLength() + 1); + } + + // spaces (or tabs) are the delimiter + sal_Int32 nIndex = 0; + do + { + OString aTok = rLine.getToken( 0, ' ', nIndex ); + bool bNo = false; + if( !aTok.isEmpty() && '!' == aTok[0] ) + { + bNo = true; // remove this function/type + aTok = aTok.copy(1); + } + if( !aTok.isEmpty() ) + { + sal_Int64 nVal = aTok.toInt64(); + switch ( nInitFile ) + { + case 1: InsertFrame( sal_uInt16( nVal ) ); // add FrameId + break; + case 2: { + SwFrameType nNew = static_cast<SwFrameType>(nVal); + if( bNo ) + nTypes &= ~nNew; // remove type + else + nTypes |= nNew; // add type + } + break; + case 3: { + PROT nOld = SwProtocol::Record(); + if( bNo ) + nOld &= ~PROT(nVal & o3tl::typed_flags<PROT>::mask); // remove function + else + nOld |= PROT(nVal & o3tl::typed_flags<PROT>::mask); // remove function + SwProtocol::SetRecord( nOld ); + } + break; + case 4: { + sal_uInt8 nNew = static_cast<sal_uInt8>(nVal); + if( bNo ) + nTestMode &= ~nNew; // reset test mode + else + nTestMode |= nNew; // set test mode + } + break; + case 5: nMaxLines = static_cast<sal_uInt16>(nVal); + break; + case 6: aVars.push_back( nVal ); + break; + } + } + } + while ( nIndex >= 0 ); +} + +/// read the file "dbg_lay.ini" in the current directory and evaluate it. +void SwImplProtocol::FileInit() +{ + OUString aName("dbg_lay.ini"); + SvFileStream aStream( aName, StreamMode::READ ); + if( aStream.IsOpen() ) + { + OString aLine; + nInitFile = 0; + while( aStream.good() ) + { + char c; + aStream.ReadChar( c ); + if( '\n' == c || '\r' == c ) // line ending + { + aLine = aLine.trim(); + if( !aLine.isEmpty() ) + CheckLine( aLine ); // evaluate line + aLine.clear(); + } + else + aLine += OString(c); + } + if( !aLine.isEmpty() ) + CheckLine( aLine ); // evaluate last line + } + aStream.Close(); +} + +/// enable indentation by two spaces during DbgAction::Start and disable it again at DbgAction::End. +static void lcl_Start(OStringBuffer& rOut, OStringBuffer& rLay, DbgAction nAction) +{ + if( nAction == DbgAction::Start ) + { + rLay.append(" "); + rOut.append(" On"); + } + else if( nAction == DbgAction::End ) + { + if( rLay.getLength() > 1 ) + { + rLay.remove(rLay.getLength() - 2, rLay.getLength()); + rOut.remove(0, 2); + } + rOut.append(" Off"); + } +} + +/// output the ValidSize-, ValidPos- and ValidPrtArea-Flag ("Sz","Ps","PA") +/// of the frame; "+" stands for valid, "-" stands for invalid. +static void lcl_Flags(OStringBuffer& rOut, const SwFrame* pFrame) +{ + rOut.append(" ValidSize"); + rOut.append(pFrame->isFrameAreaSizeValid() ? '+' : '-'); + rOut.append(" ValidPos"); + rOut.append(pFrame->isFrameAreaPositionValid() ? '+' : '-'); + rOut.append(" ValidPrtArea"); + rOut.append(pFrame->isFramePrintAreaValid() ? '+' : '-'); +} + +static void lcl_Padded(OStringBuffer& rOut, const OString& s, size_t length) +{ + if (sal_Int32(length) < s.getLength()) + length = s.getLength(); + rOut.append(s); + for (size_t i = 0; i < length - s.getLength(); i++) + { + rOut.append(" "); + } +} + +static void lcl_Padded(OStringBuffer& rOut, const long n, size_t length = 5) +{ + char sz[RTL_STR_MAX_VALUEOFINT64]; + rtl_str_valueOfInt64(sz, n, 10); + OString s(sz); + lcl_Padded(rOut, s, length); +} + +/// output the frame as plain text. +static void lcl_FrameRect(OStringBuffer& rOut, const char* hint, const SwRect& rect) +{ + rOut.append("["); + rOut.append(hint); + rOut.append(":X:"); + lcl_Padded(rOut, rect.Pos().X()); + rOut.append(", Y:"); + lcl_Padded(rOut, rect.Pos().Y()); + rOut.append(", Width:"); + lcl_Padded(rOut, rect.SSize().Width()); + rOut.append(", Height:"); + lcl_Padded(rOut, rect.SSize().Height()); + rOut.append("] "); +} + +static OString lcl_TableInfo(const SwTabFrame* pTabFrame) +{ + const SwTable* pTable = pTabFrame->GetTable(); + const SwModify* pModify = pTable->GetRegisteredIn(); + const SwFormat* pFormat = static_cast<const SwFormat*>(pModify); + const OUString& text = pFormat->GetName(); + return OUStringToOString(text, RTL_TEXTENCODING_ASCII_US); +} + +static OString lcl_RowInfo(const SwRowFrame* pFrame) +{ + // dummy, needs actual functionality... + if (pFrame == nullptr) + return ""; + const SwTableLine* pTabLine = pFrame->GetTabLine(); + if (pTabLine == nullptr) + return ""; + + return "RowInfo"; +} + +static OUString lcl_CellText(const SwCellFrame* pFrame) +{ + OUString result; + int n = 0; + + const SwStartNode* pStartNode = pFrame->GetTabBox()->GetSttNd(); + const SwEndNode* pEndNode = pStartNode->EndOfSectionNode(); + const SwNodes& nodes = pStartNode->GetNodes(); + + for (sal_uLong i = pStartNode->GetIndex(); i < nodes.Count(); i++) + { + SwNode* pNode = nodes[i]; + + if (pNode->IsEndNode()) + { + if (pNode->EndOfSectionNode() == pEndNode) + break; + } + else if (pNode->IsTextNode()) + { + n++; + result += "Para:" + OUString::number(10) + " " + + pNode->GetTextNode()->GetText(); + } + } + + return OUString::number(n) + " para(s):" + result; +} + +static OString lcl_CellInfo(const SwCellFrame* pFrame) +{ + const OUString text = "CellInfo: " + pFrame->GetTabBox()->GetName() + " Text: " + lcl_CellText(pFrame); + return OUStringToOString(text, RTL_TEXTENCODING_ASCII_US); +} + +/// output the type of the frame as plain text. +static void lcl_FrameType( OStringBuffer& rOut, const SwFrame* pFrame ) +{ + if( pFrame->IsTextFrame() ) + rOut.append("SwTextFrame "); + else if( pFrame->IsLayoutFrame() ) + { + if( pFrame->IsPageFrame() ) + rOut.append("SwPageFrame "); + else if( pFrame->IsColumnFrame() ) + rOut.append("SwColumnFrame "); + else if( pFrame->IsBodyFrame() ) + { + if( pFrame->GetUpper() && pFrame->IsColBodyFrame() ) + rOut.append("(Col)"); + rOut.append("SwBodyFrame "); + } + else if( pFrame->IsRootFrame() ) + rOut.append("SwRootFrame "); + else if( pFrame->IsCellFrame() ) + rOut.append("SwCellFrame "); + else if( pFrame->IsTabFrame() ) + rOut.append("SwTabFrame "); + else if( pFrame->IsRowFrame() ) + rOut.append("SwRowFrame "); + else if( pFrame->IsSctFrame() ) + rOut.append("SwSectionFrame "); + else if( pFrame->IsHeaderFrame() ) + rOut.append("SwHeaderFrame "); + else if( pFrame->IsFooterFrame() ) + rOut.append("SwFooterFrame "); + else if( pFrame->IsFootnoteFrame() ) + rOut.append("SwFootnoteFrame "); + else if( pFrame->IsFootnoteContFrame() ) + rOut.append("SwFootnoteContFrame "); + else if( pFrame->IsFlyFrame() ) + rOut.append("SwFlyFrame "); + else + rOut.append("SwLayoutFrame "); + } + else if( pFrame->IsNoTextFrame() ) + rOut.append("SwNoTextFrame"); + else + rOut.append("Not impl. "); +} + +/** + * Is only called if the PROTOCOL macro finds out, + * that this function should be recorded ( @see{SwProtocol::nRecord} ). + * + * In this method we also check if FrameId and frame type should be logged. + */ +void SwImplProtocol::Record_( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ) +{ + sal_uInt16 nSpecial = 0; + if( nSpecial ) // the possible debugger manipulations + { + sal_uInt16 nId = sal_uInt16(lcl_GetFrameId( pFrame )); + switch ( nSpecial ) + { + case 1: InsertFrame( nId ); break; + case 2: DeleteFrame( nId ); break; + case 3: pFrameIds.reset(); break; + case 4: pStream.reset(); break; + } + return; + } + if( !pStream && !NewStream() ) + return; // still no stream + + if( pFrameIds && !pFrameIds->count( sal_uInt16(lcl_GetFrameId( pFrame )) ) ) + return; // doesn't belong to the wished FrameIds + + if( !(pFrame->GetType() & nTypes) ) + return; // the type is unwanted + + if( 1 == nTestMode && nFunction != PROT::TestFormat ) + return; // we may only log inside a test formatting + bool bTmp = false; + OStringBuffer aOut(aLayer); + aOut.append(static_cast<sal_Int64>(lcl_GetFrameId(pFrame))); + aOut.append(' '); + lcl_FrameType( aOut, pFrame ); // then the frame type + switch ( nFunction ) // and the function + { + case PROT::MakeAll: aOut.append("SwFrame::MakeAll"); + lcl_Start( aOut, aLayer, nAct ); + if( nAct == DbgAction::Start ) + lcl_Flags( aOut, pFrame ); + break; + case PROT::MoveFwd: bTmp = true; + [[fallthrough]]; + case PROT::MoveBack: + if (nFunction == (bTmp ? PROT::Init : PROT::FileInit)) + aOut.append("SwFlowFrame::MoveFwd"); + else + aOut.append("SwFlowFrame::MoveBwd"); + lcl_Start( aOut, aLayer, nAct ); + if( pParam ) + { + aOut.append(' '); + aOut.append(static_cast<sal_Int32>(*static_cast<sal_uInt16*>(pParam))); + } + break; + case PROT::GrowTest: + aOut.append("SwFrame::Grow (test)"); + lcl_Start( aOut, aLayer, nAct ); + break; + case PROT::ShrinkTest: + aOut.append("SwFrame::Shrink (test)"); + lcl_Start( aOut, aLayer, nAct ); + break; + case PROT::AdjustN : + case PROT::Shrink: bTmp = true; + [[fallthrough]]; + case PROT::Grow: + if (!bTmp) + aOut.append("SwFrame::Grow"); + else + { + if (nFunction == PROT::Shrink) + aOut.append("SwFrame::Shrink"); + else + aOut.append("SwFrame::AdjustNeighbourhood"); + } + lcl_Start( aOut, aLayer, nAct ); + if( pParam ) + { + aOut.append(' '); + aOut.append(static_cast<sal_Int64>(*static_cast<long*>(pParam))); + } + break; + case PROT::PrintArea: aOut.append("PROT::PrintArea"); + lcl_Start( aOut, aLayer, nAct ); + break; + case PROT::Size: aOut.append("PROT::Size"); + lcl_Start( aOut, aLayer, nAct ); + aOut.append(' '); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Height())); + break; + case PROT::Leaf: aOut.append("SwFrame::GetPrev/NextSctLeaf"); + lcl_Start( aOut, aLayer, nAct ); + aOut.append(' '); + if( pParam ) + { + aOut.append(' '); + aOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame*>(pParam)))); + } + break; + case PROT::FileInit: FileInit(); + aOut.append("Initialize"); + break; + case PROT::Section: SectFunc(aOut, nAct, pParam); + break; + case PROT::Cut: bTmp = true; + [[fallthrough]]; + case PROT::Paste: + if (bTmp) + aOut.append("PROT::Cut from "); + else + aOut.append("PROT::Paste to "); + aOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame*>(pParam)))); + break; + case PROT::TestFormat: + aOut.append("SwTextFrame::TestFormat"); + lcl_Start( aOut, aLayer, nAct ); + if( DbgAction::Start == nAct ) + nTestMode |= 2; + else + nTestMode &= ~2; + break; + case PROT::FrmChanges: + { + SwRect& rFrame = *static_cast<SwRect*>(pParam); + if( pFrame->getFrameArea().Pos() != rFrame.Pos() ) + { + aOut.append("PosChg: ("); + aOut.append(static_cast<sal_Int64>(rFrame.Left())); + aOut.append(", "); + aOut.append(static_cast<sal_Int64>(rFrame.Top())); + aOut.append(") -> ("); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Left())); + aOut.append(", "); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Top())); + aOut.append(") "); + } + if( pFrame->getFrameArea().Height() != rFrame.Height() ) + { + aOut.append("Height: "); + aOut.append(static_cast<sal_Int64>(rFrame.Height())); + aOut.append(" -> "); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Height())); + aOut.append(" "); + } + if( pFrame->getFrameArea().Width() != rFrame.Width() ) + { + aOut.append("Width: "); + aOut.append(static_cast<sal_Int64>(rFrame.Width())); + aOut.append(" -> "); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Width())); + aOut.append(' '); + } + break; + } + default: break; + } + + aOut.append(" "); + while (aOut.getLength() < 40) aOut.append(" "); + lcl_FrameRect(aOut, "SwFrame", pFrame->getFrameArea()); + + aOut.append(" "); + while (aOut.getLength() < 90) aOut.append(" "); + lcl_FrameRect(aOut, "SwPrint", pFrame->getFramePrintArea()); + + if (pFrame->IsTextFrame()) + { + aOut.append(" "); + while (aOut.getLength() < 140) aOut.append(" "); + const OUString& text = static_cast<const SwTextFrame*>(pFrame)->GetText(); + OString o = OUStringToOString(text, RTL_TEXTENCODING_ASCII_US); + aOut.append(o); + } + else if (pFrame->IsTabFrame()) + { + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame); + aOut.append(lcl_TableInfo(pTabFrame)); + } + else if (pFrame->IsRowFrame()) + { + const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pFrame); + aOut.append(lcl_RowInfo(pRowFrame)); + + } + else if (pFrame->IsCellFrame()) + { + const SwCellFrame* pCellFrame = static_cast<const SwCellFrame*>(pFrame); + aOut.append(lcl_CellInfo(pCellFrame)); + } + + SAL_INFO("sw.layout.debug", aOut.getStr()); + pStream->WriteOString( aOut.makeStringAndClear() ); + (*pStream) << endl; // output + pStream->Flush(); // to the disk, so we can read it immediately + if( ++nLineCount >= nMaxLines ) // max number of lines reached? + { + SAL_WARN("sw.layout.debug", "max number of lines reached"); + SwProtocol::SetRecord( PROT::FileInit ); // => end f logging + } +} + +/// Handle the output of the SectionFrames. +void SwImplProtocol::SectFunc(OStringBuffer &rOut, DbgAction nAct, void const * pParam) +{ + bool bTmp = false; + switch( nAct ) + { + case DbgAction::Merge: rOut.append("Merge Section "); + rOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame const *>(pParam)))); + break; + case DbgAction::CreateMaster: bTmp = true; + [[fallthrough]]; + case DbgAction::CreateFollow: rOut.append("Create Section "); + if (bTmp) + rOut.append("Master to "); + else + rOut.append("Follow from "); + rOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame const *>(pParam)))); + break; + case DbgAction::DelMaster: bTmp = true; + [[fallthrough]]; + case DbgAction::DelFollow: rOut.append("Delete Section "); + if (bTmp) + rOut.append("Master to "); + else + rOut.append("Follow from "); + rOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame const *>(pParam)))); + break; + default: break; + } +} + +/** + * if pFrameIds==NULL all Frames will be logged. But as soon as pFrameIds are + * set, only the added FrameIds are being logged. + * + * @param nId new FrameId for logging + * @return TRUE if newly added, FALSE if FrameId is already under control + */ +void SwImplProtocol::InsertFrame( sal_uInt16 nId ) +{ + if( !pFrameIds ) + pFrameIds.reset( new std::set<sal_uInt16> ); + if( pFrameIds->count( nId ) ) + return; + pFrameIds->insert( nId ); +} + +/// Removes a FrameId from the pFrameIds array, so that it won't be logged anymore. +void SwImplProtocol::DeleteFrame( sal_uInt16 nId ) +{ + if( !pFrameIds ) + return; + pFrameIds->erase(nId); +} + +/* + * The task here is to find the right SwImplEnterLeave object based on the + * function; everything else is then done in his Ctor/Dtor. + */ +SwEnterLeave::SwEnterLeave( const SwFrame* pFrame, PROT nFunc, DbgAction nAct, void* pPar ) +{ + if( !SwProtocol::Record( nFunc ) ) + return; + switch( nFunc ) + { + case PROT::AdjustN : + case PROT::Grow: + case PROT::Shrink : pImpl.reset( new SwSizeEnterLeave( pFrame, nFunc, nAct, pPar ) ); break; + case PROT::MoveFwd: + case PROT::MoveBack : pImpl.reset( new SwUpperEnterLeave( pFrame, nFunc, nAct, pPar ) ); break; + case PROT::FrmChanges : pImpl.reset( new SwFrameChangesLeave( pFrame, nFunc, nAct, pPar ) ); break; + default: pImpl.reset( new SwImplEnterLeave( pFrame, nFunc, nAct, pPar ) ); break; + } + pImpl->Enter(); +} + +/* This is not inline because we don't want the SwImplEnterLeave definition inside + * dbg_lay.hxx. + */ +SwEnterLeave::~SwEnterLeave() +{ + if (pImpl) + pImpl->Leave(); +} + +void SwImplEnterLeave::Enter() +{ + SwProtocol::Record( pFrame, nFunction, DbgAction::Start, pParam ); +} + +void SwImplEnterLeave::Leave() +{ + SwProtocol::Record( pFrame, nFunction, DbgAction::End, pParam ); +} + +void SwSizeEnterLeave::Leave() +{ + nFrameHeight = pFrame->getFrameArea().Height() - nFrameHeight; + SwProtocol::Record( pFrame, nFunction, DbgAction::End, &nFrameHeight ); +} + +void SwUpperEnterLeave::Enter() +{ + nFrameId = pFrame->GetUpper() ? sal_uInt16(lcl_GetFrameId( pFrame->GetUpper() )) : 0; + SwProtocol::Record( pFrame, nFunction, DbgAction::Start, &nFrameId ); +} + +void SwUpperEnterLeave::Leave() +{ + nFrameId = pFrame->GetUpper() ? sal_uInt16(lcl_GetFrameId( pFrame->GetUpper() )) : 0; + SwProtocol::Record( pFrame, nFunction, DbgAction::End, &nFrameId ); +} + +void SwFrameChangesLeave::Enter() +{ +} + +void SwFrameChangesLeave::Leave() +{ + if( pFrame->getFrameArea() != aFrame ) + SwProtocol::Record( pFrame, PROT::FrmChanges, DbgAction::NONE, &aFrame ); +} + +#endif // DBG_UTIL + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/dumpfilter.cxx b/sw/source/core/layout/dumpfilter.cxx new file mode 100644 index 000000000..2ba71faf7 --- /dev/null +++ b/sw/source/core/layout/dumpfilter.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <dumpfilter.hxx> + +#include <wrtsh.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <docsh.hxx> +#include <rootfrm.hxx> +#include <unotxdoc.hxx> + +#include <unotools/mediadescriptor.hxx> + +#include <libxml/xmlwriter.h> + +using namespace ::com::sun::star; + +namespace +{ + int writeCallback( void* pContext, const char* sBuffer, int nLen ) + { + int written = nLen; + + // Actually write bytes to XOutputSream + try + { + uno::XInterface* pObj = static_cast<uno::XInterface*>(pContext); + uno::Reference< io::XOutputStream > xOut( pObj, uno::UNO_QUERY_THROW ); + + // Don't output the terminating \0 to the xml or the file will be invalid + uno::Sequence< sal_Int8 > seq( nLen ); + strncpy( reinterpret_cast<char *>(seq.getArray()), sBuffer, nLen ); + xOut->writeBytes( seq ); + } + catch (const uno::Exception&) + { + written = -1; + } + + return written; + } + + int closeCallback( void* pContext ) + { + int result = 0; + try + { + uno::XInterface* pObj = static_cast<uno::XInterface*>(pContext); + uno::Reference< io::XOutputStream > xOut( pObj, uno::UNO_QUERY_THROW ); + xOut->closeOutput( ); + } + catch (const uno::Exception&) + { + result = -1; + } + return result; + } +} + +namespace sw +{ + + LayoutDumpFilter::LayoutDumpFilter( ) + { + } + + LayoutDumpFilter::~LayoutDumpFilter( ) + { + } + + // XFilter + sal_Bool LayoutDumpFilter::filter( const uno::Sequence< beans::PropertyValue >& aDescriptor ) + { + bool bRet = false; + + utl::MediaDescriptor aMediaDesc = aDescriptor; + + // Get the output stream + uno::Reference< io::XOutputStream > xOut = aMediaDesc.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_OUTPUTSTREAM(), + uno::Reference< io::XOutputStream >() ); + + // Actually get the SwRootFrame to call dumpAsXml + auto pXDoc = comphelper::getUnoTunnelImplementation<SwXTextDocument>(m_xSrcDoc); + if ( pXDoc ) + { + SwRootFrame* pLayout = pXDoc->GetDocShell()->GetWrtShell()->GetLayout(); + + // Get sure that the whole layout is processed: set a visible area + // even though there isn't any need of it + pXDoc->GetDocShell()->GetWrtShell()->StartAction(); + tools::Rectangle aRect( 0, 0, 26000, 21000 ); + pXDoc->GetDocShell()->SetVisArea( aRect ); + pLayout->InvalidateAllContent( SwInvalidateFlags::Size ); + pXDoc->GetDocShell()->GetWrtShell()->EndAction(); + + // Dump the layout XML into the XOutputStream + xmlOutputBufferPtr outBuffer = xmlOutputBufferCreateIO( + writeCallback, closeCallback, static_cast<void*>(xOut.get()), nullptr ); + + xmlTextWriterPtr writer = xmlNewTextWriter( outBuffer ); + xmlTextWriterSetIndent(writer, 1); + xmlTextWriterStartDocument( writer, nullptr, nullptr, nullptr ); + + // TODO This doesn't export the whole XML file, whereas dumpAsXML() does it nicely + pLayout->dumpAsXml( writer ); + + xmlTextWriterEndDocument( writer ); + xmlFreeTextWriter( writer ); + + bRet = true; + } + + return bRet; + } + + void LayoutDumpFilter::cancel( ) + { + } + + // XExporter + void LayoutDumpFilter::setSourceDocument( const uno::Reference< lang::XComponent >& xDoc ) + { + m_xSrcDoc = xDoc; + } + + // XInitialization + void LayoutDumpFilter::initialize( const uno::Sequence< uno::Any >& ) + { + } + + // XServiceInfo + OUString LayoutDumpFilter::getImplementationName( ) + { + return "com.sun.star.comp.Writer.LayoutDump"; + } + + sal_Bool LayoutDumpFilter::supportsService( const OUString& rServiceName ) + { + return cppu::supportsService(this, rServiceName); + } + + uno::Sequence< OUString > LayoutDumpFilter::getSupportedServiceNames() + { + return { "com.sun.star.document.ExportFilter" }; + } + +} // Namespace sw + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_Writer_LayoutDump_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new sw::LayoutDumpFilter()); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/findfrm.cxx b/sw/source/core/layout/findfrm.cxx new file mode 100644 index 000000000..548399e40 --- /dev/null +++ b/sw/source/core/layout/findfrm.cxx @@ -0,0 +1,1827 @@ +/* -*- 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 <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cellfrm.hxx> +#include <rowfrm.hxx> +#include <swtable.hxx> +#include <notxtfrm.hxx> +#include <tabfrm.hxx> +#include <sectfrm.hxx> +#include <frmatr.hxx> +#include <flyfrm.hxx> +#include <ftnfrm.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtpdsc.hxx> +#include <txtfrm.hxx> +#include <bodyfrm.hxx> +#include <calbck.hxx> +#include <viewopt.hxx> +#include <ndtxt.hxx> +#include <sal/log.hxx> + +/// Searches the first ContentFrame in BodyText below the page. +SwLayoutFrame *SwFootnoteBossFrame::FindBodyCont() +{ + SwFrame *pLay = Lower(); + while ( pLay && !pLay->IsBodyFrame() ) + pLay = pLay->GetNext(); + return static_cast<SwLayoutFrame*>(pLay); +} + +/// Searches the last ContentFrame in BodyText below the page. +SwContentFrame *SwPageFrame::FindLastBodyContent() +{ + SwContentFrame *pRet = FindFirstBodyContent(); + SwContentFrame *pNxt = pRet; + while ( pNxt && pNxt->IsInDocBody() && IsAnLower( pNxt ) ) + { pRet = pNxt; + pNxt = pNxt->FindNextCnt(); + } + return pRet; +} + +/** + * Checks if the frame contains one or more ContentFrame's anywhere in his + * subsidiary structure; if so the first found ContentFrame is returned. + */ +const SwContentFrame *SwLayoutFrame::ContainsContent() const +{ + //Search downwards the layout leaf and if there is no content, jump to the + //next leaf until content is found or we leave "this". + //Sections: Content next to sections would not be found this way (empty + //sections directly next to ContentFrame) therefore we need to recursively + //search for them even if it's more complex. + + const SwLayoutFrame *pLayLeaf = this; + do + { + while ( (!pLayLeaf->IsSctFrame() || pLayLeaf == this ) && + pLayLeaf->Lower() && pLayLeaf->Lower()->IsLayoutFrame() ) + pLayLeaf = static_cast<const SwLayoutFrame*>(pLayLeaf->Lower()); + + if( pLayLeaf->IsSctFrame() && pLayLeaf != this ) + { + const SwContentFrame *pCnt = pLayLeaf->ContainsContent(); + if( pCnt ) + return pCnt; + if( pLayLeaf->GetNext() ) + { + if( pLayLeaf->GetNext()->IsLayoutFrame() ) + { + pLayLeaf = static_cast<const SwLayoutFrame*>(pLayLeaf->GetNext()); + continue; + } + else + return static_cast<const SwContentFrame*>(pLayLeaf->GetNext()); + } + } + else if ( pLayLeaf->Lower() ) + return static_cast<const SwContentFrame*>(pLayLeaf->Lower()); + + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + if( !IsAnLower( pLayLeaf) ) + return nullptr; + } while( pLayLeaf ); + return nullptr; +} + +/** + * Calls ContainsAny first to reach the innermost cell. From there we walk back + * up to the first SwCellFrame. Since we use SectionFrames, ContainsContent()->GetUpper() + * is not enough anymore. + */ +const SwCellFrame *SwLayoutFrame::FirstCell() const +{ + const SwFrame* pCnt = ContainsAny(); + while( pCnt && !pCnt->IsCellFrame() ) + pCnt = pCnt->GetUpper(); + return static_cast<const SwCellFrame*>(pCnt); +} + +/** return ContentFrames, sections, and tables. + * + * @param _bInvestigateFootnoteForSections controls investigation of content of footnotes for sections. + * @see ContainsContent + */ +const SwFrame *SwLayoutFrame::ContainsAny( const bool _bInvestigateFootnoteForSections ) const +{ + //Search downwards the layout leaf and if there is no content, jump to the + //next leaf until content is found, we leave "this" or until we found + //a SectionFrame or a TabFrame. + + const SwLayoutFrame *pLayLeaf = this; + const bool bNoFootnote = IsSctFrame() && !_bInvestigateFootnoteForSections; + do + { + while ( ( (!pLayLeaf->IsSctFrame() && !pLayLeaf->IsTabFrame()) + || pLayLeaf == this ) && + pLayLeaf->Lower() && pLayLeaf->Lower()->IsLayoutFrame() ) + pLayLeaf = static_cast<const SwLayoutFrame*>(pLayLeaf->Lower()); + + if( ( pLayLeaf->IsTabFrame() || pLayLeaf->IsSctFrame() ) + && pLayLeaf != this ) + { + // Now we also return "deleted" SectionFrames so they can be + // maintained on SaveContent and RestoreContent + return pLayLeaf; + } + else if ( pLayLeaf->Lower() ) + return static_cast<const SwContentFrame*>(pLayLeaf->Lower()); + + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + if( bNoFootnote && pLayLeaf && pLayLeaf->IsInFootnote() ) + { + do + { + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + } while( pLayLeaf && pLayLeaf->IsInFootnote() ); + } + if( !IsAnLower( pLayLeaf) ) + return nullptr; + } while( pLayLeaf ); + return nullptr; +} + +const SwFrame* SwFrame::GetLower() const +{ + return IsLayoutFrame() ? static_cast<const SwLayoutFrame*>(this)->Lower() : nullptr; +} + +SwFrame* SwFrame::GetLower() +{ + return IsLayoutFrame() ? static_cast<SwLayoutFrame*>(this)->Lower() : nullptr; +} + +SwContentFrame* SwFrame::FindPrevCnt( ) +{ + if ( GetPrev() && GetPrev()->IsContentFrame() ) + return static_cast<SwContentFrame*>(GetPrev()); + else + return FindPrevCnt_(); +} + +const SwContentFrame* SwFrame::FindPrevCnt() const +{ + if ( GetPrev() && GetPrev()->IsContentFrame() ) + return static_cast<const SwContentFrame*>(GetPrev()); + else + return const_cast<SwFrame*>(this)->FindPrevCnt_(); +} + +SwContentFrame *SwFrame::FindNextCnt( const bool _bInSameFootnote ) +{ + if ( mpNext && mpNext->IsContentFrame() ) + return static_cast<SwContentFrame*>(mpNext); + else + return FindNextCnt_( _bInSameFootnote ); +} + +const SwContentFrame *SwFrame::FindNextCnt( const bool _bInSameFootnote ) const +{ + if ( mpNext && mpNext->IsContentFrame() ) + return static_cast<SwContentFrame*>(mpNext); + else + return const_cast<SwFrame*>(this)->FindNextCnt_( _bInSameFootnote ); +} + +bool SwLayoutFrame::IsAnLower( const SwFrame *pAssumed ) const +{ + const SwFrame *pUp = pAssumed; + while ( pUp ) + { + if ( pUp == this ) + return true; + if ( pUp->IsFlyFrame() ) + pUp = static_cast<const SwFlyFrame*>(pUp)->GetAnchorFrame(); + else + pUp = pUp->GetUpper(); + } + return false; +} + +/** method to check relative position of layout frame to + a given layout frame. + + OD 08.11.2002 - refactoring of pseudo-local method <lcl_Apres(..)> in + <txtftn.cxx> for #104840#. + + @param _aCheckRefLayFrame + constant reference of an instance of class <SwLayoutFrame> which + is used as the reference for the relative position check. + + @return true, if <this> is positioned before the layout frame <p> +*/ +bool SwLayoutFrame::IsBefore( const SwLayoutFrame* _pCheckRefLayFrame ) const +{ + OSL_ENSURE( !IsRootFrame() , "<IsBefore> called at a <SwRootFrame>."); + OSL_ENSURE( !_pCheckRefLayFrame->IsRootFrame() , "<IsBefore> called with a <SwRootFrame>."); + + bool bReturn; + + // check, if on different pages + const SwPageFrame *pMyPage = FindPageFrame(); + const SwPageFrame *pCheckRefPage = _pCheckRefLayFrame->FindPageFrame(); + if( pMyPage != pCheckRefPage ) + { + // being on different page as check reference + bReturn = pMyPage->GetPhyPageNum() < pCheckRefPage->GetPhyPageNum(); + } + else + { + // being on same page as check reference + // --> search my supreme parent <pUp>, which doesn't contain check reference. + const SwLayoutFrame* pUp = this; + while ( pUp->GetUpper() && + !pUp->GetUpper()->IsAnLower( _pCheckRefLayFrame ) + ) + pUp = pUp->GetUpper(); + if( !pUp->GetUpper() ) + { + // can occur, if <this> is a fly frm + bReturn = false; + } + else + { + // travel through the next's of <pUp> and check if one of these + // contain the check reference. + const SwLayoutFrame* pUpNext = static_cast<const SwLayoutFrame*>(pUp->GetNext()); + while ( pUpNext && + !pUpNext->IsAnLower( _pCheckRefLayFrame ) ) + { + pUpNext = static_cast<const SwLayoutFrame*>(pUpNext->GetNext()); + } + bReturn = pUpNext != nullptr; + } + } + + return bReturn; +} + +// Local helper functions for GetNextLayoutLeaf + +static const SwFrame* lcl_FindLayoutFrame( const SwFrame* pFrame, bool bNext ) +{ + const SwFrame* pRet = nullptr; + if ( pFrame->IsFlyFrame() ) + pRet = bNext ? static_cast<const SwFlyFrame*>(pFrame)->GetNextLink() : static_cast<const SwFlyFrame*>(pFrame)->GetPrevLink(); + else + pRet = bNext ? pFrame->GetNext() : pFrame->GetPrev(); + + return pRet; +} + +static const SwFrame* lcl_GetLower( const SwFrame* pFrame, bool bFwd ) +{ + if ( !pFrame->IsLayoutFrame() ) + return nullptr; + + return bFwd ? + static_cast<const SwLayoutFrame*>(pFrame)->Lower() : + static_cast<const SwLayoutFrame*>(pFrame)->GetLastLower(); +} + +/** + * Finds the next layout leaf. This is a layout frame, which does not + * have a lower which is a LayoutFrame. That means, pLower can be 0 or a + * content frame. + * + * However, pLower may be a TabFrame + */ +const SwLayoutFrame *SwFrame::ImplGetNextLayoutLeaf( bool bFwd ) const +{ + const SwFrame *pFrame = this; + const SwLayoutFrame *pLayoutFrame = nullptr; + const SwFrame *p = nullptr; + bool bGoingUp = !bFwd; // false for forward, true for backward + do { + + bool bGoingFwdOrBwd = false; + + bool bGoingDown = !bGoingUp; + if (bGoingDown) + { + p = lcl_GetLower( pFrame, bFwd ); + bGoingDown = nullptr != p; + } + if ( !bGoingDown ) + { + // I cannot go down, because either I'm currently going up or + // because the is no lower. + // I'll try to go forward: + p = lcl_FindLayoutFrame( pFrame, bFwd ); + bGoingFwdOrBwd = nullptr != p; + if ( !bGoingFwdOrBwd ) + { + // I cannot go forward, because there is no next frame. + // I'll try to go up: + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + { + // I cannot go up, because there is no upper frame. + return nullptr; + } + } + } + + // If I could not go down or forward, I'll have to go up + bGoingUp = !bGoingFwdOrBwd && !bGoingDown; + + pFrame = p; + p = lcl_GetLower( pFrame, true ); + + } while( ( p && !p->IsFlowFrame() ) || + pFrame == this || + nullptr == ( pLayoutFrame = pFrame->IsLayoutFrame() ? static_cast<const SwLayoutFrame*>(pFrame) : nullptr ) || + pLayoutFrame->IsAnLower( this ) ); + + return pLayoutFrame; +} + +/** + * Walk back inside the tree: grab the subordinate Frame if one exists and the + * last step was not moving up a level (this would lead to an infinite up/down + * loop!). With this we ensure that during walking back we search through all + * sub trees. If we walked downwards we have to go to the end of the chain first + * because we go backwards from the last Frame inside another Frame. Walking + * forward works the same. + * + * @warning fixes here may also need to be applied to the @{lcl_NextFrame} method above + */ +const SwContentFrame* SwContentFrame::ImplGetNextContentFrame( bool bFwd ) const +{ + const SwFrame *pFrame = this; + const SwContentFrame *pContentFrame = nullptr; + bool bGoingUp = false; + do { + const SwFrame *p = nullptr; + bool bGoingFwdOrBwd = false; + + bool bGoingDown = !bGoingUp; + if (bGoingDown) + { + p = lcl_GetLower( pFrame, true ) ; + bGoingDown = nullptr != p; + } + if ( !bGoingDown ) + { + p = lcl_FindLayoutFrame( pFrame, bFwd ); + bGoingFwdOrBwd = nullptr != p; + if ( !bGoingFwdOrBwd ) + { + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + { + return nullptr; + } + } + } + + bGoingUp = !(bGoingFwdOrBwd || bGoingDown); + assert(p); + if (!bFwd && bGoingDown) + { + while ( p->GetNext() ) + p = p->GetNext(); + } + + pFrame = p; + } while ( nullptr == (pContentFrame = (pFrame->IsContentFrame() ? static_cast<const SwContentFrame*>(pFrame) : nullptr) )); + + return pContentFrame; +} + +SwPageFrame* SwFrame::ImplFindPageFrame() +{ + SwFrame *pRet = this; + while ( pRet && !pRet->IsPageFrame() ) + { + if ( pRet->GetUpper() ) + pRet = pRet->GetUpper(); + else if ( pRet->IsFlyFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + const auto pFly(static_cast<SwFlyFrame*>(pRet)); + pRet = pFly->GetPageFrame(); + if (pRet == nullptr) + pRet = pFly->AnchorFrame(); + } + else + return nullptr; + } + return static_cast<SwPageFrame*>(pRet); +} + +SwFootnoteBossFrame* SwFrame::FindFootnoteBossFrame( bool bFootnotes ) +{ + SwFrame *pRet = this; + // Footnote bosses can't exist inside a table; also sections with columns + // don't contain footnote texts there + if( pRet->IsInTab() ) + pRet = pRet->FindTabFrame(); + while ( pRet && !pRet->IsFootnoteBossFrame() ) + { + if ( pRet->GetUpper() ) + pRet = pRet->GetUpper(); + else if ( pRet->IsFlyFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + if ( static_cast<SwFlyFrame*>(pRet)->GetPageFrame() ) + pRet = static_cast<SwFlyFrame*>(pRet)->GetPageFrame(); + else + pRet = static_cast<SwFlyFrame*>(pRet)->AnchorFrame(); + } + else + return nullptr; + } + if( bFootnotes && pRet && pRet->IsColumnFrame() && + !pRet->GetNext() && !pRet->GetPrev() ) + { + SwSectionFrame* pSct = pRet->FindSctFrame(); + OSL_ENSURE( pSct, "FindFootnoteBossFrame: Single column outside section?" ); + if( !pSct->IsFootnoteAtEnd() ) + return pSct->FindFootnoteBossFrame( true ); + } + return static_cast<SwFootnoteBossFrame*>(pRet); +} + +SwTabFrame* SwFrame::ImplFindTabFrame() +{ + SwFrame *pRet = this; + while ( !pRet->IsTabFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<SwTabFrame*>(pRet); +} + +SwSectionFrame* SwFrame::ImplFindSctFrame() +{ + SwFrame *pRet = this; + while ( !pRet->IsSctFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<SwSectionFrame*>(pRet); +} + +const SwBodyFrame* SwFrame::ImplFindBodyFrame() const +{ + const SwFrame *pRet = this; + while ( !pRet->IsBodyFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<const SwBodyFrame*>(pRet); +} + +SwFootnoteFrame *SwFrame::ImplFindFootnoteFrame() +{ + SwFrame *pRet = this; + while ( !pRet->IsFootnoteFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<SwFootnoteFrame*>(pRet); +} + +SwFlyFrame *SwFrame::ImplFindFlyFrame() +{ + SwFrame *pRet = this; + do + { + if ( pRet->IsFlyFrame() ) + return static_cast<SwFlyFrame*>(pRet); + else + pRet = pRet->GetUpper(); + } while ( pRet ); + return nullptr; +} + +SwFrame *SwFrame::FindColFrame() +{ + SwFrame *pFrame = this; + do + { pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsColumnFrame() ); + return pFrame; +} + +SwRowFrame *SwFrame::FindRowFrame() +{ + SwFrame *pFrame = this; + do + { pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsRowFrame() ); + return dynamic_cast< SwRowFrame* >( pFrame ); +} + +SwFrame* SwFrame::FindFooterOrHeader() +{ + SwFrame* pRet = this; + do + { + if (pRet->GetType() & FRM_HEADFOOT) //header and footer + return pRet; + else if ( pRet->GetUpper() ) + pRet = pRet->GetUpper(); + else if ( pRet->IsFlyFrame() ) + pRet = static_cast<SwFlyFrame*>(pRet)->AnchorFrame(); + else + return nullptr; + } while ( pRet ); + return pRet; +} + +const SwFootnoteFrame* SwFootnoteContFrame::FindFootNote() const +{ + const SwFootnoteFrame* pRet = static_cast<const SwFootnoteFrame*>(Lower()); + if( pRet && !pRet->GetAttr()->GetFootnote().IsEndNote() ) + return pRet; + return nullptr; +} + +const SwPageFrame* SwRootFrame::GetPageAtPos( const Point& rPt, const Size* pSize, bool bExtend ) const +{ + const SwPageFrame* pRet = nullptr; + + SwRect aRect; + if ( pSize ) + { + aRect.Pos() = rPt; + aRect.SSize( *pSize ); + } + + const SwFrame* pPage = Lower(); + + if ( !bExtend ) + { + if( !getFrameArea().IsInside( rPt ) ) + return nullptr; + + // skip pages above point: + while( pPage && rPt.Y() > pPage->getFrameArea().Bottom() ) + pPage = pPage->GetNext(); + } + + OSL_ENSURE( GetPageNum() <= maPageRects.size(), "number of pages differs from page rect array size" ); + size_t nPageIdx = 0; + + while ( pPage && !pRet ) + { + const SwRect& rBoundRect = bExtend ? maPageRects[ nPageIdx++ ] : pPage->getFrameArea(); + + if ( (!pSize && rBoundRect.IsInside(rPt)) || + (pSize && rBoundRect.IsOver(aRect)) ) + { + pRet = static_cast<const SwPageFrame*>(pPage); + } + + pPage = pPage->GetNext(); + } + + return pRet; +} + +bool SwRootFrame::IsBetweenPages(const Point& rPt) const +{ + if (!getFrameArea().IsInside(rPt)) + return false; + + // top visible page + const SwFrame* pPage = Lower(); + if (pPage == nullptr) + return false; + + // skip pages above point: + while (pPage && rPt.Y() > pPage->getFrameArea().Bottom()) + pPage = pPage->GetNext(); + + if (pPage && + rPt.X() >= pPage->getFrameArea().Left() && + rPt.X() <= pPage->getFrameArea().Right()) + { + // Trivial case when we're right in between. + if (!pPage->getFrameArea().IsInside(rPt)) + return true; + + // In normal mode the gap is large enough and + // header/footer mouse interaction competes with + // handling hide-whitespace within them. + // In hide-whitespace, however, the gap is too small + // for convenience and there are no headers/footers. + const SwViewShell *pSh = GetCurrShell(); + if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden()) + { + // If we are really close to the bottom or top of a page. + const auto toEdge = std::min(std::abs(pPage->getFrameArea().Top() - rPt.Y()), + std::abs(pPage->getFrameArea().Bottom() - rPt.Y())); + return toEdge <= MmToTwips(2.0); + } + } + + return false; +} + +const SvxFormatBreakItem& SwFrame::GetBreakItem() const +{ + return GetAttrSet()->GetBreak(); +} + +const SwFormatPageDesc& SwFrame::GetPageDescItem() const +{ + return GetAttrSet()->GetPageDesc(); +} + +const SvxFormatBreakItem& SwTextFrame::GetBreakItem() const +{ + return GetTextNodeFirst()->GetSwAttrSet().GetBreak(); +} + +const SwFormatPageDesc& SwTextFrame::GetPageDescItem() const +{ + return GetTextNodeFirst()->GetSwAttrSet().GetPageDesc(); +} + +const SwAttrSet* SwFrame::GetAttrSet() const +{ + if (IsTextFrame()) + { + return &static_cast<const SwTextFrame*>(this)->GetTextNodeForParaProps()->GetSwAttrSet(); + } + else if (IsNoTextFrame()) + { + return &static_cast<const SwNoTextFrame*>(this)->GetNode()->GetSwAttrSet(); + } + else + { + assert(IsLayoutFrame()); + return &static_cast<const SwLayoutFrame*>(this)->GetFormat()->GetAttrSet(); + } +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwFrame::getSdrAllFillAttributesHelper() const +{ + if (IsTextFrame()) + { + return static_cast<const SwTextFrame*>(this)->GetTextNodeForParaProps()->getSdrAllFillAttributesHelper(); + } + else if (IsNoTextFrame()) + { + return static_cast<const SwNoTextFrame*>(this)->GetNode()->getSdrAllFillAttributesHelper(); + } + else + { + return static_cast< const SwLayoutFrame* >(this)->GetFormat()->getSdrAllFillAttributesHelper(); + } +} + +bool SwFrame::supportsFullDrawingLayerFillAttributeSet() const +{ + if (IsContentFrame()) + { + return true; + } + else + { + return static_cast< const SwLayoutFrame* >(this)->GetFormat()->supportsFullDrawingLayerFillAttributeSet(); + } +} + +/* + * SwFrame::FindNext_(), FindPrev_(), InvalidateNextPos() + * FindNextCnt_() visits tables and sections and only returns SwContentFrames. + * + * Description Invalidates the position of the next frame. + * This is the direct successor or in case of ContentFrames the next + * ContentFrame which sits in the same flow as I do: + * - body, + * - footnote, + * - in headers/footers the notification only needs to be forwarded + * inside the section + * - same for Flys + * - Contents in tabs remain only inside their cell + * - in principle tables behave exactly like the Contents + * - sections also + */ +// This helper function is an equivalent to the ImplGetNextContentFrame() method, +// besides ContentFrames this function also returns TabFrames and SectionFrames. +static SwFrame* lcl_NextFrame( SwFrame* pFrame ) +{ + SwFrame *pRet = nullptr; + bool bGoingUp = false; + do { + SwFrame *p = nullptr; + + bool bGoingFwd = false; + bool bGoingDown = !bGoingUp && pFrame->IsLayoutFrame(); + if (bGoingDown) + { + p = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + bGoingDown = nullptr != p; + } + if( !bGoingDown ) + { + p = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->GetNextLink() : pFrame->GetNext(); + bGoingFwd = nullptr != p; + if ( !bGoingFwd ) + { + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + { + return nullptr; + } + } + } + bGoingUp = !(bGoingFwd || bGoingDown); + pFrame = p; + } while ( nullptr == (pRet = ( ( pFrame->IsContentFrame() || ( !bGoingUp && + ( pFrame->IsTabFrame() || pFrame->IsSctFrame() ) ) )? pFrame : nullptr ) ) ); + return pRet; +} + +SwFrame *SwFrame::FindNext_() +{ + bool bIgnoreTab = false; + SwFrame *pThis = this; + + if ( IsTabFrame() ) + { + //The last Content of the table gets picked up and his follower is + //returned. To be able to deactivate the special case for tables + //(see below) bIgnoreTab will be set. + if ( static_cast<SwTabFrame*>(this)->GetFollow() ) + return static_cast<SwTabFrame*>(this)->GetFollow(); + + pThis = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + if ( !pThis ) + pThis = this; + bIgnoreTab = true; + } + else if ( IsSctFrame() ) + { + //The last Content of the section gets picked and his follower is returned. + if ( static_cast<SwSectionFrame*>(this)->GetFollow() ) + return static_cast<SwSectionFrame*>(this)->GetFollow(); + + pThis = static_cast<SwSectionFrame*>(this)->FindLastContent(); + if ( !pThis ) + pThis = this; + } + else if ( IsContentFrame() ) + { + if( static_cast<SwContentFrame*>(this)->GetFollow() ) + return static_cast<SwContentFrame*>(this)->GetFollow(); + } + else if ( IsRowFrame() ) + { + SwFrame* pMyUpper = GetUpper(); + if ( pMyUpper->IsTabFrame() && static_cast<SwTabFrame*>(pMyUpper)->GetFollow() ) + return static_cast<SwTabFrame*>(pMyUpper)->GetFollow()->GetLower(); + else return nullptr; + } + else + return nullptr; + + SwFrame* pRet = nullptr; + const bool bFootnote = pThis->IsInFootnote(); + if ( !bIgnoreTab && pThis->IsInTab() ) + { + SwLayoutFrame *pUp = pThis->GetUpper(); + while (pUp && !pUp->IsCellFrame()) + pUp = pUp->GetUpper(); + assert(pUp && "Content flag says it's in table but it's not in cell."); + SwFrame* pNxt = pUp ? static_cast<SwCellFrame*>(pUp)->GetFollowCell() : nullptr; + if ( pNxt ) + pNxt = static_cast<SwCellFrame*>(pNxt)->ContainsContent(); + if ( !pNxt ) + { + pNxt = lcl_NextFrame( pThis ); + if (pUp && pUp->IsAnLower(pNxt)) + pRet = pNxt; + } + else + pRet = pNxt; + } + else + { + const bool bBody = pThis->IsInDocBody(); + SwFrame *pNxtCnt = lcl_NextFrame( pThis ); + if ( pNxtCnt ) + { + if ( bBody || bFootnote ) + { + while ( pNxtCnt ) + { + // OD 02.04.2003 #108446# - check for endnote, only if found + // next content isn't contained in a section, that collect its + // endnotes at its end. + bool bEndn = IsInSct() && !IsSctFrame() && + ( !pNxtCnt->IsInSct() || + !pNxtCnt->FindSctFrame()->IsEndnAtEnd() + ); + if ( ( bBody && pNxtCnt->IsInDocBody() ) || + ( pNxtCnt->IsInFootnote() && + ( bFootnote || + ( bEndn && pNxtCnt->FindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() ) + ) + ) + ) + { + pRet = pNxtCnt->IsInTab() ? pNxtCnt->FindTabFrame() + : pNxtCnt; + break; + } + pNxtCnt = lcl_NextFrame( pNxtCnt ); + } + } + else if ( pThis->IsInFly() ) + { + pRet = pNxtCnt->IsInTab() ? pNxtCnt->FindTabFrame() + : pNxtCnt; + } + else //footer-/or header section + { + const SwFrame *pUp = pThis->GetUpper(); + const SwFrame *pCntUp = pNxtCnt->GetUpper(); + while ( pUp && pUp->GetUpper() && + !pUp->IsHeaderFrame() && !pUp->IsFooterFrame() ) + pUp = pUp->GetUpper(); + while ( pCntUp && pCntUp->GetUpper() && + !pCntUp->IsHeaderFrame() && !pCntUp->IsFooterFrame() ) + pCntUp = pCntUp->GetUpper(); + if ( pCntUp == pUp ) + { + pRet = pNxtCnt->IsInTab() ? pNxtCnt->FindTabFrame() + : pNxtCnt; + } + } + } + } + if( pRet && pRet->IsInSct() ) + { + SwSectionFrame* pSct = pRet->FindSctFrame(); + //Footnotes in frames with columns must not return the section which + //contains the footnote + if( !pSct->IsAnLower( this ) && + (!bFootnote || pSct->IsInFootnote() ) ) + return pSct; + } + return pRet; +} + +// #i27138# - add parameter <_bInSameFootnote> +SwContentFrame *SwFrame::FindNextCnt_( const bool _bInSameFootnote ) +{ + SwFrame *pThis = this; + + if ( IsTabFrame() ) + { + if ( static_cast<SwTabFrame*>(this)->GetFollow() ) + { + pThis = static_cast<SwTabFrame*>(this)->GetFollow()->ContainsContent(); + if( pThis ) + return static_cast<SwContentFrame*>(pThis); + } + pThis = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + if ( !pThis ) + return nullptr; + } + else if ( IsSctFrame() ) + { + if ( static_cast<SwSectionFrame*>(this)->GetFollow() ) + { + pThis = static_cast<SwSectionFrame*>(this)->GetFollow()->ContainsContent(); + if( pThis ) + return static_cast<SwContentFrame*>(pThis); + } + pThis = static_cast<SwSectionFrame*>(this)->FindLastContent(); + if ( !pThis ) + return nullptr; + } + else if ( IsContentFrame() && static_cast<SwContentFrame*>(this)->GetFollow() ) + return static_cast<SwContentFrame*>(this)->GetFollow(); + + if ( pThis->IsContentFrame() ) + { + const bool bBody = pThis->IsInDocBody(); + const bool bFootnote = pThis->IsInFootnote(); + SwContentFrame *pNxtCnt = static_cast<SwContentFrame*>(pThis)->GetNextContentFrame(); + if ( pNxtCnt ) + { + // #i27138# + if ( bBody || ( bFootnote && !_bInSameFootnote ) ) + { + // handling for environments 'footnotes' and 'document body frames': + while ( pNxtCnt ) + { + if ( (bBody && pNxtCnt->IsInDocBody()) || + (bFootnote && pNxtCnt->IsInFootnote()) ) + return pNxtCnt; + pNxtCnt = pNxtCnt->GetNextContentFrame(); + } + } + // #i27138# + else if ( bFootnote && _bInSameFootnote ) + { + // handling for environments 'each footnote': + // Assure that found next content frame belongs to the same footnotes + const SwFootnoteFrame* pFootnoteFrameOfNext( pNxtCnt->FindFootnoteFrame() ); + const SwFootnoteFrame* pFootnoteFrameOfCurr( pThis->FindFootnoteFrame() ); + OSL_ENSURE( pFootnoteFrameOfCurr, + "<SwFrame::FindNextCnt_() - unknown layout situation: current frame has to have an upper footnote frame." ); + if ( pFootnoteFrameOfNext == pFootnoteFrameOfCurr ) + { + return pNxtCnt; + } + else if ( pFootnoteFrameOfCurr->GetFollow() ) + { + // next content frame has to be the first content frame + // in the follow footnote, which contains a content frame. + SwFootnoteFrame* pFollowFootnoteFrameOfCurr( + const_cast<SwFootnoteFrame*>(pFootnoteFrameOfCurr) ); + pNxtCnt = nullptr; + do { + pFollowFootnoteFrameOfCurr = pFollowFootnoteFrameOfCurr->GetFollow(); + pNxtCnt = pFollowFootnoteFrameOfCurr->ContainsContent(); + } while ( !pNxtCnt && pFollowFootnoteFrameOfCurr->GetFollow() ); + return pNxtCnt; + } + else + { + // current content frame is the last content frame in the + // footnote - no next content frame exists. + return nullptr; + } + } + else if ( pThis->IsInFly() ) + // handling for environments 'unlinked fly frame' and + // 'group of linked fly frames': + return pNxtCnt; + else + { + // handling for environments 'page header' and 'page footer': + const SwFrame *pUp = pThis->GetUpper(); + const SwFrame *pCntUp = pNxtCnt->GetUpper(); + while ( pUp && pUp->GetUpper() && + !pUp->IsHeaderFrame() && !pUp->IsFooterFrame() ) + pUp = pUp->GetUpper(); + while ( pCntUp && pCntUp->GetUpper() && + !pCntUp->IsHeaderFrame() && !pCntUp->IsFooterFrame() ) + pCntUp = pCntUp->GetUpper(); + if ( pCntUp == pUp ) + return pNxtCnt; + } + } + } + return nullptr; +} + +/** method to determine previous content frame in the same environment + for a flow frame (content frame, table frame, section frame) + + OD 2005-11-30 #i27138# +*/ +SwContentFrame* SwFrame::FindPrevCnt_() +{ + if ( !IsFlowFrame() ) + { + // nothing to do, if current frame isn't a flow frame. + return nullptr; + } + + SwContentFrame* pPrevContentFrame( nullptr ); + + // Because method <SwContentFrame::GetPrevContentFrame()> is used to travel + // through the layout, a content frame, at which the travel starts, is needed. + SwContentFrame* pCurrContentFrame = dynamic_cast<SwContentFrame*>(this); + + // perform shortcut, if current frame is a follow, and + // determine <pCurrContentFrame>, if current frame is a table or section frame + if ( pCurrContentFrame && pCurrContentFrame->IsFollow() ) + { + // previous content frame is its master content frame + pPrevContentFrame = pCurrContentFrame->FindMaster(); + } + else if ( IsTabFrame() ) + { + SwTabFrame* pTabFrame( static_cast<SwTabFrame*>(this) ); + if ( pTabFrame->IsFollow() ) + { + // previous content frame is the last content of its master table frame + pPrevContentFrame = pTabFrame->FindMaster()->FindLastContent(); + } + else + { + // start content frame for the search is the first content frame of + // the table frame. + pCurrContentFrame = pTabFrame->ContainsContent(); + } + } + else if ( IsSctFrame() ) + { + SwSectionFrame* pSectFrame( static_cast<SwSectionFrame*>(this) ); + if ( pSectFrame->IsFollow() ) + { + // previous content frame is the last content of its master section frame + pPrevContentFrame = pSectFrame->FindMaster()->FindLastContent(); + } + else + { + // start content frame for the search is the first content frame of + // the section frame. + pCurrContentFrame = pSectFrame->ContainsContent(); + } + } + + // search for next content frame, depending on the environment, in which + // the current frame is in. + if ( !pPrevContentFrame && pCurrContentFrame ) + { + pPrevContentFrame = pCurrContentFrame->GetPrevContentFrame(); + if ( pPrevContentFrame ) + { + if ( pCurrContentFrame->IsInFly() ) + { + // handling for environments 'unlinked fly frame' and + // 'group of linked fly frames': + // Nothing to do, <pPrevContentFrame> is the one + } + else + { + const bool bInDocBody = pCurrContentFrame->IsInDocBody(); + const bool bInFootnote = pCurrContentFrame->IsInFootnote(); + if ( bInDocBody ) + { + // handling for environments 'footnotes' and 'document body frames': + // Assure that found previous frame is also in one of these + // environments. Otherwise, travel further + while ( pPrevContentFrame ) + { + if ( ( bInDocBody && pPrevContentFrame->IsInDocBody() ) || + ( bInFootnote && pPrevContentFrame->IsInFootnote() ) ) + { + break; + } + pPrevContentFrame = pPrevContentFrame->GetPrevContentFrame(); + } + } + else if ( bInFootnote ) + { + // handling for environments 'each footnote': + // Assure that found next content frame belongs to the same footnotes + const SwFootnoteFrame* pFootnoteFrameOfPrev( pPrevContentFrame->FindFootnoteFrame() ); + const SwFootnoteFrame* pFootnoteFrameOfCurr( pCurrContentFrame->FindFootnoteFrame() ); + if ( pFootnoteFrameOfPrev != pFootnoteFrameOfCurr ) + { + if ( pFootnoteFrameOfCurr->GetMaster() ) + { + SwFootnoteFrame* pMasterFootnoteFrameOfCurr( + const_cast<SwFootnoteFrame*>(pFootnoteFrameOfCurr) ); + pPrevContentFrame = nullptr; + // correct wrong loop-condition + do { + pMasterFootnoteFrameOfCurr = pMasterFootnoteFrameOfCurr->GetMaster(); + pPrevContentFrame = pMasterFootnoteFrameOfCurr->FindLastContent(); + } while ( !pPrevContentFrame && + pMasterFootnoteFrameOfCurr->GetMaster() ); + } + else + { + // current content frame is the first content in the + // footnote - no previous content exists. + pPrevContentFrame = nullptr; + } + } + } + else + { + // handling for environments 'page header' and 'page footer': + // Assure that found previous frame is also in the same + // page header respectively page footer as <pCurrContentFrame> + // Note: At this point it's clear that <pCurrContentFrame> has + // to be inside a page header or page footer and that + // neither <pCurrContentFrame> nor <pPrevContentFrame> are + // inside a fly frame. + // Thus, method <FindFooterOrHeader()> can be used. + OSL_ENSURE( pCurrContentFrame->FindFooterOrHeader(), + "<SwFrame::FindPrevCnt_()> - unknown layout situation: current frame should be in page header or page footer" ); + OSL_ENSURE( !pPrevContentFrame->IsInFly(), + "<SwFrame::FindPrevCnt_()> - unknown layout situation: found previous frame should *not* be inside a fly frame." ); + if ( pPrevContentFrame->FindFooterOrHeader() != + pCurrContentFrame->FindFooterOrHeader() ) + { + pPrevContentFrame = nullptr; + } + } + } + } + } + + return pPrevContentFrame; +} + +SwFrame *SwFrame::FindPrev_() +{ + bool bIgnoreTab = false; + SwFrame *pThis = this; + + if ( IsTabFrame() ) + { + //The first Content of the table gets picked up and his predecessor is + //returned. To be able to deactivate the special case for tables + //(see below) bIgnoreTab will be set. + if ( static_cast<SwTabFrame*>(this)->IsFollow() ) + return static_cast<SwTabFrame*>(this)->FindMaster(); + else + pThis = static_cast<SwTabFrame*>(this)->ContainsContent(); + bIgnoreTab = true; + } + + if ( pThis && pThis->IsContentFrame() ) + { + SwContentFrame *pPrvCnt = static_cast<SwContentFrame*>(pThis)->GetPrevContentFrame(); + if( !pPrvCnt ) + return nullptr; + if ( !bIgnoreTab && pThis->IsInTab() ) + { + SwLayoutFrame *pUp = pThis->GetUpper(); + while (pUp && !pUp->IsCellFrame()) + pUp = pUp->GetUpper(); + assert(pUp && "Content flag says it's in table but it's not in cell."); + if (pUp && pUp->IsAnLower(pPrvCnt)) + return pPrvCnt; + } + else + { + SwFrame* pRet; + const bool bBody = pThis->IsInDocBody(); + const bool bFootnote = !bBody && pThis->IsInFootnote(); + if ( bBody || bFootnote ) + { + while ( pPrvCnt ) + { + if ( (bBody && pPrvCnt->IsInDocBody()) || + (bFootnote && pPrvCnt->IsInFootnote()) ) + { + pRet = pPrvCnt->IsInTab() ? pPrvCnt->FindTabFrame() + : static_cast<SwFrame*>(pPrvCnt); + return pRet; + } + pPrvCnt = pPrvCnt->GetPrevContentFrame(); + } + } + else if ( pThis->IsInFly() ) + { + pRet = pPrvCnt->IsInTab() ? pPrvCnt->FindTabFrame() + : static_cast<SwFrame*>(pPrvCnt); + return pRet; + } + else // footer or header or Fly + { + const SwFrame *pUp = pThis->GetUpper(); + const SwFrame *pCntUp = pPrvCnt->GetUpper(); + while ( pUp && pUp->GetUpper() && + !pUp->IsHeaderFrame() && !pUp->IsFooterFrame() ) + pUp = pUp->GetUpper(); + while ( pCntUp && pCntUp->GetUpper() ) + pCntUp = pCntUp->GetUpper(); + if ( pCntUp == pUp ) + { + pRet = pPrvCnt->IsInTab() ? pPrvCnt->FindTabFrame() + : static_cast<SwFrame*>(pPrvCnt); + return pRet; + } + } + } + } + return nullptr; +} + +void SwFrame::ImplInvalidateNextPos( bool bNoFootnote ) +{ + SwFrame *pFrame; + if ( nullptr != (pFrame = FindNext_()) ) + { + if( pFrame->IsSctFrame() ) + { + while( pFrame && pFrame->IsSctFrame() ) + { + if( static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePos(); + else if( !bNoFootnote ) + static_cast<SwSectionFrame*>(pFrame)->InvalidateFootnotePos(); + if( !IsInSct() || FindSctFrame()->GetFollow() != pFrame ) + pFrame->InvalidatePos(); + return; + } + pFrame = pFrame->FindNext(); + } + if( pFrame ) + { + if ( pFrame->IsSctFrame()) + { + // We need to invalidate the section's content so it gets + // the chance to flow to a different page. + SwFrame* pTmp = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePos(); + if( !IsInSct() || FindSctFrame()->GetFollow() != pFrame ) + pFrame->InvalidatePos(); + } + else + pFrame->InvalidatePos(); + } + } + else + pFrame->InvalidatePos(); + } +} + +/** method to invalidate printing area of next frame + + OD 09.01.2004 #i11859# + + FME 2004-04-19 #i27145# Moved function from SwTextFrame to SwFrame +*/ +void SwFrame::InvalidateNextPrtArea() +{ + // determine next frame + SwFrame* pNextFrame = FindNext(); + // skip empty section frames and hidden text frames + { + while ( pNextFrame && + ( ( pNextFrame->IsSctFrame() && + !static_cast<SwSectionFrame*>(pNextFrame)->GetSection() ) || + ( pNextFrame->IsTextFrame() && + static_cast<SwTextFrame*>(pNextFrame)->IsHiddenNow() ) ) ) + { + pNextFrame = pNextFrame->FindNext(); + } + } + + // Invalidate printing area of found next frame + if ( pNextFrame ) + { + if ( pNextFrame->IsSctFrame() ) + { + // Invalidate printing area of found section frame, if + // (1) this text frame isn't in a section OR + // (2) found section frame isn't a follow of the section frame this + // text frame is in. + if ( !IsInSct() || FindSctFrame()->GetFollow() != pNextFrame ) + { + pNextFrame->InvalidatePrt(); + } + + // Invalidate printing area of first content in found section. + SwFrame* pFstContentOfSctFrame = + static_cast<SwSectionFrame*>(pNextFrame)->ContainsAny(); + if ( pFstContentOfSctFrame ) + { + pFstContentOfSctFrame->InvalidatePrt(); + } + } + else + { + pNextFrame->InvalidatePrt(); + } + } +} + +/// @returns true if the frame _directly_ sits in a section +/// but not if it sits in a table which itself sits in a section. +static bool lcl_IsInSectionDirectly( const SwFrame *pUp ) +{ + bool bSeenColumn = false; + + while( pUp ) + { + if( pUp->IsColumnFrame() ) + bSeenColumn = true; + else if( pUp->IsSctFrame() ) + { + auto pSection = static_cast<const SwSectionFrame*>(pUp); + const SwFrame* pHeaderFooter = pSection->FindFooterOrHeader(); + // When the section frame is not in header/footer: + // Allow move of frame in case our only column is not growable. + // Also allow if there is a previous section frame (to move back). + bool bAllowOutsideHeaderFooter = !pSection->Growable() || pSection->GetPrecede(); + return bSeenColumn || (!pHeaderFooter && bAllowOutsideHeaderFooter); + } + else if( pUp->IsTabFrame() ) + return false; + pUp = pUp->GetUpper(); + } + return false; +} + +/** determine, if frame is moveable in given environment + + OD 08.08.2003 #110978# + method replaced 'old' method <sal_Bool IsMoveable() const>. + Determines, if frame is moveable in given environment. if no environment + is given (parameter _pLayoutFrame == 0), the movability in the actual + environment (<GetUpper()) is checked. +*/ +bool SwFrame::IsMoveable( const SwLayoutFrame* _pLayoutFrame ) const +{ + bool bRetVal = false; + + if ( !_pLayoutFrame ) + { + _pLayoutFrame = GetUpper(); + } + + if ( _pLayoutFrame && IsFlowFrame() ) + { + if ( _pLayoutFrame->IsInSct() && lcl_IsInSectionDirectly( _pLayoutFrame ) ) + { + bRetVal = true; + } + else if ( _pLayoutFrame->IsInFly() || + _pLayoutFrame->IsInDocBody() || + _pLayoutFrame->IsInFootnote() ) + { + // If IsMovable() is called before a MoveFwd() the method + // may return false if there is no NextCellLeaf. If + // IsMovable() is called before a MoveBwd() the method may + // return false if there is no PrevCellLeaf. + if ( _pLayoutFrame->IsInTab() && !IsTabFrame() && + ( !IsContentFrame() || (!const_cast<SwFrame*>(this)->GetNextCellLeaf() + && !const_cast<SwFrame*>(this)->GetPrevCellLeaf()) ) + ) + { + bRetVal = false; + } + else + { + if ( _pLayoutFrame->IsInFly() ) + { + // if fly frame has a follow (next linked fly frame), + // frame is moveable. + if ( const_cast<SwLayoutFrame*>(_pLayoutFrame)->FindFlyFrame()->GetNextLink() ) + { + bRetVal = true; + } + else + { + // if environment is columned, frame is moveable, if + // it isn't in last column. + // search for column frame + const SwFrame* pCol = _pLayoutFrame; + while ( pCol && !pCol->IsColumnFrame() ) + { + pCol = pCol->GetUpper(); + } + // frame is moveable, if found column frame isn't last one. + if ( pCol && pCol->GetNext() ) + { + bRetVal = true; + } + } + } + else if (!(_pLayoutFrame->IsInFootnote() && (IsTabFrame() || IsInTab()))) + { + bRetVal = true; + } + } + } + } + + return bRetVal; +} + +void SwFrame::SetInfFlags() +{ + if ( !IsFlyFrame() && !GetUpper() ) //not yet pasted, no information available + return; + + mbInfInvalid = mbInfBody = mbInfTab = mbInfFly = mbInfFootnote = mbInfSct = false; + + SwFrame *pFrame = this; + if( IsFootnoteContFrame() ) + mbInfFootnote = true; + do + { + // mbInfBody is only set in the page body, but not in the column body + if ( pFrame->IsBodyFrame() && !mbInfFootnote && pFrame->GetUpper() + && pFrame->GetUpper()->IsPageFrame() ) + mbInfBody = true; + else if ( pFrame->IsTabFrame() || pFrame->IsCellFrame() ) + { + mbInfTab = true; + } + else if ( pFrame->IsFlyFrame() ) + mbInfFly = true; + else if ( pFrame->IsSctFrame() ) + mbInfSct = true; + else if ( pFrame->IsFootnoteFrame() ) + mbInfFootnote = true; + + pFrame = pFrame->GetUpper(); + + } while ( pFrame && !pFrame->IsPageFrame() ); //there is nothing above the page +} + +/** Updates the vertical or the righttoleft-flags. + * + * If the property is derived, it's from the upper or (for fly frames) from + * the anchor. Otherwise we've to call a virtual method to check the property. + */ +void SwFrame::SetDirFlags( bool bVert ) +{ + if( bVert ) + { + // OD 2004-01-21 #114969# - if derived, valid vertical flag only if + // vertical flag of upper/anchor is valid. + if( mbDerivedVert ) + { + const SwFrame* pAsk = IsFlyFrame() ? + static_cast<SwFlyFrame*>(this)->GetAnchorFrame() : GetUpper(); + + OSL_ENSURE( pAsk != this, "Autsch! Stack overflow is about to happen" ); + + if( pAsk ) + { + mbVertical = pAsk->IsVertical(); + mbVertLR = pAsk->IsVertLR(); + mbVertLRBT = pAsk->IsVertLRBT(); + + if ( !pAsk->mbInvalidVert ) + mbInvalidVert = false; + } + } + else + CheckDirection( bVert ); + } + else + { + bool bInv = false; + if( !mbDerivedR2L ) // CheckDirection is able to set bDerivedR2L! + CheckDirection( bVert ); + if( mbDerivedR2L ) + { + const SwFrame* pAsk = IsFlyFrame() ? + static_cast<SwFlyFrame*>(this)->GetAnchorFrame() : GetUpper(); + + OSL_ENSURE( pAsk != this, "Oops! Stack overflow is about to happen" ); + + if( pAsk ) + mbRightToLeft = pAsk->IsRightToLeft(); + if( !pAsk || pAsk->mbInvalidR2L ) + bInv = mbInvalidR2L; + } + mbInvalidR2L = bInv; + } +} + +SwLayoutFrame* SwFrame::GetNextCellLeaf() +{ + SwFrame* pTmpFrame = this; + while (pTmpFrame && !pTmpFrame->IsCellFrame()) + pTmpFrame = pTmpFrame->GetUpper(); + + SAL_WARN_IF(!pTmpFrame, "sw.core", "SwFrame::GetNextCellLeaf() without cell"); + return pTmpFrame ? static_cast<SwCellFrame*>(pTmpFrame)->GetFollowCell() : nullptr; +} + +SwLayoutFrame* SwFrame::GetPrevCellLeaf() +{ + SwFrame* pTmpFrame = this; + while (pTmpFrame && !pTmpFrame->IsCellFrame()) + pTmpFrame = pTmpFrame->GetUpper(); + + SAL_WARN_IF(!pTmpFrame, "sw.core", "SwFrame::GetNextPreviousLeaf() without cell"); + return pTmpFrame ? static_cast<SwCellFrame*>(pTmpFrame)->GetPreviousCell() : nullptr; +} + +static SwCellFrame* lcl_FindCorrespondingCellFrame( const SwRowFrame& rOrigRow, + const SwCellFrame& rOrigCell, + const SwRowFrame& rCorrRow, + bool bInFollow ) +{ + SwCellFrame* pRet = nullptr; + const SwCellFrame* pCell = static_cast<const SwCellFrame*>(rOrigRow.Lower()); + SwCellFrame* pCorrCell = const_cast<SwCellFrame*>(static_cast<const SwCellFrame*>(rCorrRow.Lower())); + + while ( pCell != &rOrigCell && !pCell->IsAnLower( &rOrigCell ) ) + { + pCell = static_cast<const SwCellFrame*>(pCell->GetNext()); + pCorrCell = static_cast<SwCellFrame*>(pCorrCell->GetNext()); + } + + assert(pCell && pCorrCell && "lcl_FindCorrespondingCellFrame does not work"); + + if ( pCell != &rOrigCell ) + { + // rOrigCell must be a lower of pCell. We need to recurse into the rows: + assert(pCell->Lower() && pCell->Lower()->IsRowFrame() && + "lcl_FindCorrespondingCellFrame does not work"); + + const SwRowFrame* pRow = static_cast<const SwRowFrame*>(pCell->Lower()); + while ( !pRow->IsAnLower( &rOrigCell ) ) + pRow = static_cast<const SwRowFrame*>(pRow->GetNext()); + + SwRowFrame* pCorrRow = nullptr; + if ( bInFollow ) + pCorrRow = pRow->GetFollowRow(); + else + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(pCorrCell->GetLastLower()); + + if ( pTmpRow && pTmpRow->GetFollowRow() == pRow ) + pCorrRow = pTmpRow; + } + + if ( pCorrRow ) + pRet = lcl_FindCorrespondingCellFrame( *pRow, rOrigCell, *pCorrRow, bInFollow ); + } + else + pRet = pCorrCell; + + return pRet; +} + +// VERSION OF GetFollowCell() that assumes that we always have a follow flow line: +SwCellFrame* SwCellFrame::GetFollowCell() const +{ + SwCellFrame* pRet = nullptr; + + // NEW TABLES + // Covered cells do not have follow cells! + const long nRowSpan = GetLayoutRowSpan(); + if ( nRowSpan < 1 ) + return nullptr; + + // find most upper row frame + const SwFrame* pRow = GetUpper(); + + while (pRow && (!pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame())) + pRow = pRow->GetUpper(); + + if (!pRow) + return nullptr; + + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pRow->GetUpper()); + if (!pTabFrame || !pTabFrame->GetFollow() || !pTabFrame->HasFollowFlowLine()) + return nullptr; + + const SwCellFrame* pThisCell = this; + + // Get last cell of the current table frame that belongs to the rowspan: + if ( nRowSpan > 1 ) + { + // optimization: Will end of row span be in last row or exceed row? + long nMax = 0; + while ( pRow->GetNext() && ++nMax < nRowSpan ) + pRow = pRow->GetNext(); + + if ( !pRow->GetNext() ) + { + pThisCell = &pThisCell->FindStartEndOfRowSpanCell( false ); + pRow = pThisCell->GetUpper(); + } + } + + const SwRowFrame* pFollowRow = nullptr; + if ( !pRow->GetNext() && + nullptr != ( pFollowRow = pRow->IsInSplitTableRow() ) && + ( !pFollowRow->IsRowSpanLine() || nRowSpan > 1 ) ) + pRet = lcl_FindCorrespondingCellFrame( *static_cast<const SwRowFrame*>(pRow), *pThisCell, *pFollowRow, true ); + + return pRet; +} + +// VERSION OF GetPreviousCell() THAT ASSUMES THAT WE ALWAYS HAVE A FFL +SwCellFrame* SwCellFrame::GetPreviousCell() const +{ + SwCellFrame* pRet = nullptr; + + // NEW TABLES + // Covered cells do not have previous cells! + if ( GetLayoutRowSpan() < 1 ) + return nullptr; + + // find most upper row frame + const SwFrame* pRow = GetUpper(); + while( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() ) + pRow = pRow->GetUpper(); + + OSL_ENSURE( pRow->GetUpper() && pRow->GetUpper()->IsTabFrame(), "GetPreviousCell without Table" ); + + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + if ( pTab->IsFollow() ) + { + const SwFrame* pTmp = pTab->GetFirstNonHeadlineRow(); + const bool bIsInFirstLine = ( pTmp == pRow ); + + if ( bIsInFirstLine ) + { + SwTabFrame *pMaster = pTab->FindMaster(); + if ( pMaster && pMaster->HasFollowFlowLine() ) + { + SwRowFrame* pMasterRow = static_cast<SwRowFrame*>(pMaster->GetLastLower()); + if ( pMasterRow ) + pRet = lcl_FindCorrespondingCellFrame( *static_cast<const SwRowFrame*>(pRow), *this, *pMasterRow, false ); + if ( pRet && pRet->GetTabBox()->getRowSpan() < 1 ) + pRet = &const_cast<SwCellFrame&>(pRet->FindStartEndOfRowSpanCell( true )); + } + } + } + + return pRet; +} + +// --> NEW TABLES +const SwCellFrame& SwCellFrame::FindStartEndOfRowSpanCell( bool bStart ) const +{ + const SwTabFrame* pTableFrame = dynamic_cast<const SwTabFrame*>(GetUpper()->GetUpper()); + + if ( !bStart && pTableFrame && pTableFrame->IsFollow() && pTableFrame->IsInHeadline( *this ) ) + return *this; + + OSL_ENSURE( pTableFrame && + ( (bStart && GetTabBox()->getRowSpan() < 1) || + (!bStart && GetLayoutRowSpan() > 1) ), + "SwCellFrame::FindStartRowSpanCell: No rowspan, no table, no cookies" ); + + if ( pTableFrame ) + { + const SwTable* pTable = pTableFrame->GetTable(); + + sal_uInt16 nMax = USHRT_MAX; + const SwFrame* pCurrentRow = GetUpper(); + const bool bDoNotEnterHeadline = bStart && pTableFrame->IsFollow() && + !pTableFrame->IsInHeadline( *pCurrentRow ); + + // check how many rows we are allowed to go up or down until we reach the end of + // the current table frame: + nMax = 0; + while ( bStart ? pCurrentRow->GetPrev() : pCurrentRow->GetNext() ) + { + if ( bStart ) + { + // do not enter a repeated headline: + if ( bDoNotEnterHeadline && pTableFrame->IsFollow() && + pTableFrame->IsInHeadline( *pCurrentRow->GetPrev() ) ) + break; + + pCurrentRow = pCurrentRow->GetPrev(); + } + else + pCurrentRow = pCurrentRow->GetNext(); + + ++nMax; + } + + // By passing the nMax value for Find*OfRowSpan (in case of bCurrentTableOnly + // is set) we assure that we find a rMasterBox that has a SwCellFrame in + // the current table frame: + const SwTableBox& rMasterBox = bStart ? + GetTabBox()->FindStartOfRowSpan( *pTable, nMax ) : + GetTabBox()->FindEndOfRowSpan( *pTable, nMax ); + + SwIterator<SwCellFrame,SwFormat> aIter( *rMasterBox.GetFrameFormat() ); + + for ( SwCellFrame* pMasterCell = aIter.First(); pMasterCell; pMasterCell = aIter.Next() ) + { + if ( pMasterCell->GetTabBox() == &rMasterBox ) + { + const SwTabFrame* pMasterTable = static_cast<const SwTabFrame*>(pMasterCell->GetUpper()->GetUpper()); + + if ( pMasterTable == pTableFrame ) + { + return *pMasterCell; + } + } + } + } + + SAL_WARN("sw.core", "SwCellFrame::FindStartRowSpanCell: No result"); + + return *this; +} + +// <-- NEW TABLES + +const SwRowFrame* SwFrame::IsInSplitTableRow() const +{ + OSL_ENSURE( IsInTab(), "IsInSplitTableRow should only be called for frames in tables" ); + + const SwFrame* pRow = this; + + // find most upper row frame + while( pRow && ( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() ) ) + pRow = pRow->GetUpper(); + + if ( !pRow ) return nullptr; + + OSL_ENSURE( pRow->GetUpper()->IsTabFrame(), "Confusion in table layout" ); + + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + // If most upper row frame is a headline row, the current frame + // can't be in a split table row. Thus, add corresponding condition. + if ( pRow->GetNext() || + pTab->GetTable()->IsHeadline( + *(static_cast<const SwRowFrame*>(pRow)->GetTabLine()) ) || + !pTab->HasFollowFlowLine() || + !pTab->GetFollow() ) + return nullptr; + + // skip headline + const SwRowFrame* pFollowRow = pTab->GetFollow()->GetFirstNonHeadlineRow(); + + OSL_ENSURE( pFollowRow, "SwFrame::IsInSplitTableRow() does not work" ); + + return pFollowRow; +} + +const SwRowFrame* SwFrame::IsInFollowFlowRow() const +{ + OSL_ENSURE( IsInTab(), "IsInSplitTableRow should only be called for frames in tables" ); + + // find most upper row frame + const SwFrame* pRow = this; + while( pRow && ( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() ) ) + pRow = pRow->GetUpper(); + + if ( !pRow ) return nullptr; + + OSL_ENSURE( pRow->GetUpper()->IsTabFrame(), "Confusion in table layout" ); + + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + const SwTabFrame* pMaster = pTab->IsFollow() ? pTab->FindMaster() : nullptr; + + if ( !pMaster || !pMaster->HasFollowFlowLine() ) + return nullptr; + + const SwFrame* pTmp = pTab->GetFirstNonHeadlineRow(); + const bool bIsInFirstLine = ( pTmp == pRow ); + + if ( !bIsInFirstLine ) + return nullptr; + + const SwRowFrame* pMasterRow = static_cast<const SwRowFrame*>(pMaster->GetLastLower()); + return pMasterRow; +} + +bool SwFrame::IsInBalancedSection() const +{ + bool bRet = false; + + if ( IsInSct() ) + { + const SwSectionFrame* pSectionFrame = FindSctFrame(); + if ( pSectionFrame ) + bRet = pSectionFrame->IsBalancedSection(); + } + return bRet; +} + +const SwFrame* SwLayoutFrame::GetLastLower() const +{ + const SwFrame* pRet = Lower(); + if ( !pRet ) + return nullptr; + while ( pRet->GetNext() ) + pRet = pRet->GetNext(); + return pRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flowfrm.cxx b/sw/source/core/layout/flowfrm.cxx new file mode 100644 index 000000000..576e32d90 --- /dev/null +++ b/sw/source/core/layout/flowfrm.cxx @@ -0,0 +1,2665 @@ +/* -*- 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 <memory> +#include <sal/config.h> +#include <sal/log.hxx> +#include <svx/svdobj.hxx> + +#include <anchoredobject.hxx> +#include <bodyfrm.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <fmtanchr.hxx> +#include <fmtsrnd.hxx> +#include <fmtpdsc.hxx> +#include <editeng/ulspitem.hxx> +#include <tgrditem.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <editeng/pgrditem.hxx> +#include <paratr.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <pagedesc.hxx> +#include <layact.hxx> +#include <flyfrm.hxx> +#include <sectfrm.hxx> +#include <section.hxx> +#include <dbg_lay.hxx> +#include <lineinfo.hxx> +#include <fmtclbl.hxx> +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include <fmtfollowtextflow.hxx> +#include <calbck.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> + +bool SwFlowFrame::m_bMoveBwdJump = false; + +SwFlowFrame::SwFlowFrame( SwFrame &rFrame ) : + m_rThis( rFrame ), + m_pFollow( nullptr ), + m_pPrecede( nullptr ), + m_bLockJoin( false ), + m_bUndersized( false ), + m_bFlyLock( false ) +{} + +SwFlowFrame::~SwFlowFrame() +{ + if (m_pFollow) + { + m_pFollow->m_pPrecede = nullptr; + } + if (m_pPrecede) + { + m_pPrecede->m_pFollow = nullptr; + } +} + +void SwFlowFrame::SetFollow(SwFlowFrame *const pFollow) +{ + if (m_pFollow) + { + assert(this == m_pFollow->m_pPrecede); + m_pFollow->m_pPrecede = nullptr; + } + m_pFollow = pFollow; + if (m_pFollow != nullptr) + { + if (m_pFollow->m_pPrecede) // re-chaining pFollow? + { + assert(m_pFollow == m_pFollow->m_pPrecede->m_pFollow); + m_pFollow->m_pPrecede->m_pFollow = nullptr; + } + m_pFollow->m_pPrecede = this; + } +} + +/// @return true if any follow has the JoinLocked flag +bool SwFlowFrame::HasLockedFollow() const +{ + const SwFlowFrame* pFrame = GetFollow(); + while( pFrame ) + { + if( pFrame->IsJoinLocked() ) + return true; + pFrame = pFrame->GetFollow(); + } + return false; +} + +bool SwFlowFrame::IsKeepFwdMoveAllowed( bool bIgnoreMyOwnKeepValue ) +{ + // If all the predecessors up to the first of the chain have + // the 'keep' attribute set, and the first of the chain's + // IsFwdMoveAllowed returns false, then we're not allowed to move. + SwFrame *pFrame = &m_rThis; + if ( !pFrame->IsInFootnote() ) { + if ( bIgnoreMyOwnKeepValue && pFrame->GetIndPrev() ) + pFrame = pFrame->GetIndPrev(); + do + { if ( pFrame->GetAttrSet()->GetKeep().GetValue() ) + pFrame = pFrame->GetIndPrev(); + else + return true; + } while ( pFrame ); + } + //See IsFwdMoveAllowed() + bool bRet = false; + if ( pFrame && pFrame->GetIndPrev() ) + bRet = true; + return bRet; +} + +void SwFlowFrame::CheckKeep() +{ + // Kick off the "last" predecessor with a 'keep' attribute, because + // it's possible for the whole troop to move back. + SwFrame *pPre = m_rThis.GetIndPrev(); + assert(pPre); + if( pPre->IsSctFrame() ) + { + SwFrame *pLast = static_cast<SwSectionFrame*>(pPre)->FindLastContent(); + if( pLast && pLast->FindSctFrame() == pPre ) + pPre = pLast; + else + return; + } + SwFrame* pTmp; + bool bKeep; + while ( (bKeep = pPre->GetAttrSet()->GetKeep().GetValue()) && + nullptr != ( pTmp = pPre->GetIndPrev() ) ) + { + if( pTmp->IsSctFrame() ) + { + SwFrame *pLast = static_cast<SwSectionFrame*>(pTmp)->FindLastContent(); + if( pLast && pLast->FindSctFrame() == pTmp ) + pTmp = pLast; + else + break; + } + pPre = pTmp; + } + if ( bKeep ) + pPre->InvalidatePos(); +} + +bool SwFlowFrame::IsKeep(SvxFormatKeepItem const& rKeep, + SvxFormatBreakItem const& rBreak, + bool const bCheckIfLastRowShouldKeep) const +{ + // 1. The keep attribute is ignored inside footnotes + // 2. For compatibility reasons, the keep attribute is + // ignored for frames inside table cells + // 3. If bBreakCheck is set to true, this function only checks + // if there are any break after attributes set at rAttrs + // or break before attributes set for the next content (or next table) + bool bKeep = bCheckIfLastRowShouldKeep || + ( !m_rThis.IsInFootnote() && + ( !m_rThis.IsInTab() || m_rThis.IsTabFrame() ) && + rKeep.GetValue() ); + + OSL_ENSURE( !bCheckIfLastRowShouldKeep || m_rThis.IsTabFrame(), + "IsKeep with bCheckIfLastRowShouldKeep should only be used for tabfrms" ); + + // Ignore keep attribute if there are break situations: + if ( bKeep ) + { + switch (rBreak.GetBreak()) + { + case SvxBreak::ColumnAfter: + case SvxBreak::ColumnBoth: + case SvxBreak::PageAfter: + case SvxBreak::PageBoth: + { + bKeep = false; + break; + } + default: break; + } + if ( bKeep ) + { + SwFrame *pNxt; + if( nullptr != (pNxt = m_rThis.FindNextCnt()) && + (!m_pFollow || pNxt != &m_pFollow->GetFrame())) + { + // The last row of a table only keeps with the next content + // it they are in the same section: + if ( bCheckIfLastRowShouldKeep ) + { + const SwSection* pThisSection = nullptr; + const SwSection* pNextSection = nullptr; + const SwSectionFrame* pThisSectionFrame = m_rThis.FindSctFrame(); + const SwSectionFrame* pNextSectionFrame = pNxt->FindSctFrame(); + + if ( pThisSectionFrame ) + pThisSection = pThisSectionFrame->GetSection(); + + if ( pNextSectionFrame ) + pNextSection = pNextSectionFrame->GetSection(); + + if ( pThisSection != pNextSection ) + bKeep = false; + } + + if ( bKeep ) + { + SvxFormatBreakItem const* pBreak; + SwFormatPageDesc const* pPageDesc; + SwTabFrame* pTab = pNxt->IsInTab() ? pNxt->FindTabFrame() : nullptr; + if (pTab && (!m_rThis.IsInTab() || m_rThis.FindTabFrame() != pTab)) + { + const SwAttrSet *const pSet = &pTab->GetFormat()->GetAttrSet(); + pBreak = &pSet->GetBreak(); + pPageDesc = &pSet->GetPageDesc(); + } + else + { + pBreak = &pNxt->GetBreakItem(); + pPageDesc = &pNxt->GetPageDescItem(); + } + + if (pPageDesc->GetPageDesc()) + bKeep = false; + else switch (pBreak->GetBreak()) + { + case SvxBreak::ColumnBefore: + case SvxBreak::ColumnBoth: + case SvxBreak::PageBefore: + case SvxBreak::PageBoth: + bKeep = false; + break; + default: break; + } + } + } + } + } + return bKeep; +} + +sal_uInt8 SwFlowFrame::BwdMoveNecessary( const SwPageFrame *pPage, const SwRect &rRect ) +{ + // The return value helps deciding whether we need to flow back (3), + // or whether we can use the good old WouldFit (0, 1), or if + // it's reasonable to relocate and test-format (2). + + // Bit 1 in this case means that there are objects anchored to myself, + // bit 2 means that I have to evade other objects. + + // If a SurroundObj that desires to be wrapped around overlaps with the + // Rect, it's required to flow (because we can't guess the relationships). + // However it's possible for a test formatting to happen. + // If the SurroundObj is a Fly and I'm a Lower, or the Fly is a Lower of + // mine, then it doesn't matter. + // If the SurroundObj is anchored in a character bound Fly, and I'm not + // a Lower of that character bound Fly myself, then the Fly doesn't matter. + + // If the object is anchored with me, i can ignore it, because + // it's likely that it will follow me with the flow. A test formatting is + // not allowed in that case, however! + sal_uInt8 nRet = 0; + SwFlowFrame *pTmp = this; + do + { // If there are objects hanging either on me or on a follow, we can't + // do a test formatting, because paragraph bound objects wouldn't + // be properly considered, and character bound objects shouldn't + // be test formatted at all. + if( pTmp->GetFrame().GetDrawObjs() ) + nRet = 1; + pTmp = pTmp->GetFollow(); + } while ( !nRet && pTmp ); + const SwSortedObjs *pObjs = pPage ? pPage->GetSortedObjs() : nullptr; + if (pObjs) + { + + const SwSortedObjs &rObjs = *pObjs; + sal_uLong nIndex = ULONG_MAX; + for ( size_t i = 0; nRet < 3 && i < rObjs.size(); ++i ) + { + + SwAnchoredObject* pObj = rObjs[i]; + const SwFrameFormat& rFormat = pObj->GetFrameFormat(); + const SwRect aRect( pObj->GetObjRect() ); + if ( aRect.IsOver( rRect ) && + rFormat.GetSurround().GetSurround() != css::text::WrapTextMode_THROUGH ) + { + if( m_rThis.IsLayoutFrame() && //Fly Lower of This? + Is_Lower_Of( &m_rThis, pObj->GetDrawObj() ) ) + continue; + if( auto pFly = dynamic_cast<const SwFlyFrame*>(pObj) ) + { + if ( pFly->IsAnLower( &m_rThis ) )//This Lower of Fly? + continue; + } + + const SwFrame* pAnchor = pObj->GetAnchorFrame(); + if ( pAnchor == &m_rThis ) + { + nRet |= 1; + continue; + } + + // Don't do this if the object is anchored behind me in the text + // flow, because then I wouldn't evade it. + if ( ::IsFrameInSameContext( pAnchor, &m_rThis ) ) + { + if ( rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PARA ) + { + // The index of the other one can be retrieved using the anchor attribute. + sal_uLong nTmpIndex = rFormat.GetAnchor().GetContentAnchor()->nNode.GetIndex(); + // Now we're going to check whether the current paragraph before + // the anchor of the displacing object sits in the text. If this + // is the case, we don't try to evade it. + // The index is being determined via SwFormatAnchor, because it's + // getting quite expensive otherwise. + if( ULONG_MAX == nIndex ) + { + const SwNode *pNode; + if (m_rThis.IsTextFrame()) + pNode = static_cast<SwTextFrame&>(m_rThis).GetTextNodeFirst(); + else if (m_rThis.IsNoTextFrame()) + pNode = static_cast<SwNoTextFrame&>(m_rThis).GetNode(); + else if( m_rThis.IsSctFrame() ) + pNode = static_cast<SwSectionFormat*>(static_cast<SwSectionFrame&>(m_rThis). + GetFormat())->GetSectionNode(); + else + { + assert(!m_rThis.IsContentFrame()); + OSL_ENSURE( m_rThis.IsTabFrame(), "new FowFrame?" ); + pNode = static_cast<SwTabFrame&>(m_rThis).GetTable()-> + GetTabSortBoxes()[0]->GetSttNd()->FindTableNode(); + } + nIndex = pNode->GetIndex(); + } + if (nIndex < nTmpIndex && + (!m_rThis.IsTextFrame() || + !FrameContainsNode(static_cast<SwTextFrame&>(m_rThis), nTmpIndex))) + { + continue; + } + } + } + else + continue; + + nRet |= 2; + } + } + } + return nRet; +} + +/// A specialized form of Cut(), which relocates a whole chain (this and the following, +/// in particular). During this process, only the minimum operations and notifications are done. +SwLayoutFrame *SwFlowFrame::CutTree( SwFrame *pStart ) +{ + // Cut the Start and all the neighbours; they are chained together and + // a handle to the first one is returned. Residuals are invalidated + // as appropriate. + + SwLayoutFrame *pLay = pStart->GetUpper(); + if ( pLay->IsInFootnote() ) + pLay = pLay->FindFootnoteFrame(); + + // i#58846 + // <pPrepare( PrepareHint::QuoVadis )> only for frames in footnotes + if( pStart->IsInFootnote() ) + { + SwFrame* pTmp = pStart->GetIndPrev(); + if( pTmp ) + pTmp->Prepare( PrepareHint::QuoVadis ); + } + + // Just cut quickly and take care that we don't cause problems with the + // left-behinds. The pointers of the chain being cut can point who-knows where. + if ( pStart == pStart->GetUpper()->Lower() ) + pStart->GetUpper()->m_pLower = nullptr; + if ( pStart->GetPrev() ) + { + pStart->GetPrev()->mpNext = nullptr; + pStart->mpPrev = nullptr; + } + + if ( pLay->IsFootnoteFrame() ) + { + if ( !pLay->Lower() && !pLay->IsColLocked() && + !static_cast<SwFootnoteFrame*>(pLay)->IsBackMoveLocked() ) + { + // tdf#101821 don't delete it while iterating over it + if (!pLay->IsDeleteForbidden()) + { + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } + // else: assume there is code on the stack to clean up empty + // footnote frames + // (don't go into the else branch below, it produces a disconnected + // footnote with null upper that can be returned by + // SwFootnoteBossFrame::FindFootnote() causing null pointer deref + // in SwTextFrame::ConnectFootnote() + } + else + { + bool bUnlock = !static_cast<SwFootnoteFrame*>(pLay)->IsBackMoveLocked(); + static_cast<SwFootnoteFrame*>(pLay)->LockBackMove(); + pLay->InvalidateSize(); + pLay->Calc(pLay->getRootFrame()->GetCurrShell()->GetOut()); + SwContentFrame *pCnt = pLay->ContainsContent(); + while ( pCnt && pLay->IsAnLower( pCnt ) ) + { + // It's possible for the ContentFrame to be locked, and we don't want + // to end up in an endless page migration, so we're not even + // going to call Calc! + OSL_ENSURE( pCnt->IsTextFrame(), "The Graphic has landed." ); + if ( static_cast<SwTextFrame*>(pCnt)->IsLocked() || + static_cast<SwTextFrame*>(pCnt)->GetFollow() == pStart ) + break; + pCnt->Calc(pCnt->getRootFrame()->GetCurrShell()->GetOut()); + pCnt = pCnt->GetNextContentFrame(); + } + if( bUnlock ) + static_cast<SwFootnoteFrame*>(pLay)->UnlockBackMove(); + } + pLay = nullptr; + } + return pLay; +} + +/// A specialized form of Paste(), which relocates a whole chain (this and the following, +/// in particular). During this process, only the minimum operations and notifications are done. +bool SwFlowFrame::PasteTree( SwFrame *pStart, SwLayoutFrame *pParent, SwFrame *pSibling, + SwFrame *pOldParent ) +{ + // returns true if there's a LayoutFrame in the chain. + bool bRet = false; + + // The chain beginning with pStart is inserted before pSibling + // under the parent. We take care to invalidate as required. + + // I'm receiving a finished chain. We need to update the pointers for + // the beginning of the chain, then all the uppers and finally the end. + // On the way there, we invalidate as required. + if ( pSibling ) + { + if ( nullptr != (pStart->mpPrev = pSibling->GetPrev()) ) + pStart->GetPrev()->mpNext = pStart; + else + pParent->m_pLower = pStart; + pSibling->InvalidatePos_(); + pSibling->InvalidatePrt_(); + } + else + { + if ( nullptr == (pStart->mpPrev = pParent->Lower()) ) + pParent->m_pLower = pStart; + else + //i#100782 + //If the pParent has more than 1 child nodes, former design will + //ignore them directly without any collection work. It will make some + //dangling pointers. This lead the crash... + //The new design will find the last child of pParent in loop way, and + //add the pStart after the last child. + // pParent->Lower()->pNext = pStart; + { + SwFrame* pTemp = pParent->m_pLower; + while (pTemp) + { + if (pTemp->mpNext) + pTemp = pTemp->mpNext; + else + { + pStart->mpPrev = pTemp; + pTemp->mpNext = pStart; + break; + } + } + } + + + // i#27145 + if ( pParent->IsSctFrame() ) + { + // We have no sibling because pParent is a section frame and + // has just been created to contain some content. The printing + // area of the frame behind pParent has to be invalidated, so + // that the correct distance between pParent and the next frame + // can be calculated. + pParent->InvalidateNextPrtArea(); + } + } + SwFrame *pFloat = pStart; + SwFrame *pLst = nullptr; + SwRectFnSet aRectFnSet(pParent); + SwTwips nGrowVal = 0; + do + { pFloat->mpUpper = pParent; + pFloat->InvalidateAll_(); + pFloat->CheckDirChange(); + + // I'm a friend of the TextFrame and thus am allowed to do many things. + // The CacheIdx idea seems to be a bit risky! + if ( pFloat->IsTextFrame() ) + { + if ( static_cast<SwTextFrame*>(pFloat)->GetCacheIdx() != USHRT_MAX ) + static_cast<SwTextFrame*>(pFloat)->Init(); // I'm his friend. + } + else + bRet = true; + + nGrowVal += aRectFnSet.GetHeight(pFloat->getFrameArea()); + if ( pFloat->GetNext() ) + pFloat = pFloat->GetNext(); + else + { + pLst = pFloat; + pFloat = nullptr; + } + } while ( pFloat ); + + if ( pSibling ) + { + pLst->mpNext = pSibling; + pSibling->mpPrev = pLst; + if( pSibling->IsInFootnote() ) + { + if( pSibling->IsSctFrame() ) + pSibling = static_cast<SwSectionFrame*>(pSibling)->ContainsAny(); + if( pSibling ) + pSibling->Prepare( PrepareHint::ErgoSum ); + } + } + if ( nGrowVal ) + { + if ( pOldParent && pOldParent->IsBodyFrame() ) // For variable page height while browsing + pOldParent->Shrink( nGrowVal ); + pParent->Grow( nGrowVal ); + } + + if ( pParent->IsFootnoteFrame() ) + static_cast<SwFootnoteFrame*>(pParent)->InvalidateNxtFootnoteCnts( pParent->FindPageFrame() ); + return bRet; +} + +void SwFlowFrame::MoveSubTree( SwLayoutFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "No parent given." ); + OSL_ENSURE( m_rThis.GetUpper(), "Where are we coming from?" ); + + // Be economical with notifications if an action is running. + SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + const SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + const bool bComplete = pImp && pImp->IsAction() && pImp->GetLayAction().IsComplete(); + + if ( !bComplete ) + { + SwFrame *pPre = m_rThis.GetIndPrev(); + if ( pPre ) + { + pPre->SetRetouche(); + // follow-up of i#26250 + // invalidate printing area of previous frame, if it's in a table + if ( pPre->GetUpper()->IsInTab() ) + { + pPre->InvalidatePrt_(); + } + pPre->InvalidatePage(); + } + else + { + m_rThis.GetUpper()->SetCompletePaint(); + m_rThis.GetUpper()->InvalidatePage(); + } + } + + SwPageFrame *pOldPage = m_rThis.FindPageFrame(); + + SwLayoutFrame *pOldParent; + bool bInvaLay; + + { + //JoinLock pParent for the lifetime of the Cut/Paste call to avoid + //SwSectionFrame::MergeNext removing the pParent we're trying to reparent + //into + FlowFrameJoinLockGuard aJoinGuard(pParent); + SwFrameDeleteGuard aDeleteGuard(pParent); + pOldParent = CutTree( &m_rThis ); + bInvaLay = PasteTree( &m_rThis, pParent, pSibling, pOldParent ); + } + + // If, by cutting & pasting, an empty SectionFrame came into existence, it should + // disappear automatically. + SwSectionFrame *pSct; + + if ( pOldParent && !pOldParent->Lower() && + ( pOldParent->IsInSct() && + !(pSct = pOldParent->FindSctFrame())->ContainsContent() && + !pSct->ContainsAny( true ) ) ) + { + pSct->DelEmpty( false ); + } + + // If we're in a column section, we'd rather not call Calc "from below" + if( !m_rThis.IsInSct() && + ( !m_rThis.IsInTab() || ( m_rThis.IsTabFrame() && !m_rThis.GetUpper()->IsInTab() ) ) ) + m_rThis.GetUpper()->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + else if( m_rThis.GetUpper()->IsSctFrame() ) + { + SwSectionFrame* pTmpSct = static_cast<SwSectionFrame*>(m_rThis.GetUpper()); + bool bOld = pTmpSct->IsContentLocked(); + pTmpSct->SetContentLock( true ); + pTmpSct->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + if( !bOld ) + pTmpSct->SetContentLock( false ); + } + SwPageFrame *pPage = m_rThis.FindPageFrame(); + + if ( pOldPage != pPage ) + { + m_rThis.InvalidatePage( pPage ); + if ( m_rThis.IsLayoutFrame() ) + { + SwContentFrame *pCnt = static_cast<SwLayoutFrame*>(&m_rThis)->ContainsContent(); + if ( pCnt ) + pCnt->InvalidatePage( pPage ); + } + else if ( pSh && pSh->GetDoc()->GetLineNumberInfo().IsRestartEachPage() + && pPage->FindFirstBodyContent() == &m_rThis ) + { + m_rThis.InvalidateLineNum_(); + } + } + if ( bInvaLay || (pSibling && pSibling->IsLayoutFrame()) ) + m_rThis.GetUpper()->InvalidatePage( pPage ); +} + +bool SwFlowFrame::IsAnFollow( const SwFlowFrame *pAssumed ) const +{ + const SwFlowFrame *pFoll = this; + do + { if ( pAssumed == pFoll ) + return true; + pFoll = pFoll->GetFollow(); + } while ( pFoll ); + return false; +} + +SwTextFrame* SwContentFrame::FindMaster() const +{ + OSL_ENSURE( IsFollow(), "SwContentFrame::FindMaster(): !IsFollow" ); + + const SwContentFrame* pPrec = static_cast<const SwContentFrame*>(SwFlowFrame::GetPrecede()); + + if ( pPrec && pPrec->HasFollow() && pPrec->GetFollow() == this ) + { + OSL_ENSURE( pPrec->IsTextFrame(), "NoTextFrame with follow found" ); + return const_cast<SwTextFrame*>(static_cast< const SwTextFrame* >(pPrec)); + } + + OSL_FAIL( "Follow is lost in Space." ); + return nullptr; +} + +SwSectionFrame* SwSectionFrame::FindMaster() const +{ + OSL_ENSURE( IsFollow(), "SwSectionFrame::FindMaster(): !IsFollow" ); + + if (!m_pSection) + return nullptr; + + SwIterator<SwSectionFrame,SwFormat> aIter( *m_pSection->GetFormat() ); + SwSectionFrame* pSect = aIter.First(); + while ( pSect ) + { + if (pSect->GetFollow() == this) + return pSect; + pSect = aIter.Next(); + } + + OSL_FAIL( "Follow is lost in Space." ); + return nullptr; +} + +SwTabFrame* SwTabFrame::FindMaster( bool bFirstMaster ) const +{ + OSL_ENSURE( IsFollow(), "SwTabFrame::FindMaster(): !IsFollow" ); + + SwIterator<SwTabFrame,SwFormat> aIter( *GetTable()->GetFrameFormat() ); + SwTabFrame* pTab = aIter.First(); + while ( pTab ) + { + if ( bFirstMaster ) + { + // Optimization. This makes code like this obsolete: + // while ( pTab->IsFollow() ) + // pTab = pTab->FindMaster(); + + if ( !pTab->IsFollow() ) + { + SwTabFrame* pNxt = pTab; + while ( pNxt ) + { + if ( pNxt->GetFollow() == this ) + return pTab; + pNxt = pNxt->GetFollow(); + } + } + } + else + { + if ( pTab->GetFollow() == this ) + return pTab; + } + + pTab = aIter.Next(); + } + + OSL_FAIL( "Follow is lost in Space." ); + return nullptr; +} + +/** + * Returns the next/previous Layout leaf that's NOT below this (or even is this itself). + * Also, that leaf must be in the same text flow as the pAnch origin frame (Body, Footnote) + */ +const SwLayoutFrame *SwFrame::GetLeaf( MakePageType eMakePage, bool bFwd, + const SwFrame *pAnch ) const +{ + // No flow, no joy... + if ( !(IsInDocBody() || IsInFootnote() || IsInFly()) ) + return nullptr; + + const SwFrame *pLeaf = this; + bool bFound = false; + + do + { pLeaf = const_cast<SwFrame*>(pLeaf)->GetLeaf( eMakePage, bFwd ); + + if ( pLeaf && + (!IsLayoutFrame() || !static_cast<const SwLayoutFrame*>(this)->IsAnLower( pLeaf ))) + { + if ( pAnch->IsInDocBody() == pLeaf->IsInDocBody() && + pAnch->IsInFootnote() == pLeaf->IsInFootnote() ) + { + bFound = true; + } + } + } while ( !bFound && pLeaf ); + + return static_cast<const SwLayoutFrame*>(pLeaf); +} + +SwLayoutFrame *SwFrame::GetLeaf( MakePageType eMakePage, bool bFwd ) +{ + if ( IsInFootnote() ) + return bFwd ? GetNextFootnoteLeaf( eMakePage ) : GetPrevFootnoteLeaf( eMakePage ); + + // i#53323 + // A frame could be inside a table AND inside a section. + // Thus, it has to be determined, which is the first parent. + bool bInTab( IsInTab() ); + bool bInSct( IsInSct() ); + if ( bInTab && bInSct ) + { + const SwFrame* pUpperFrame( GetUpper() ); + while ( pUpperFrame ) + { + if ( pUpperFrame->IsTabFrame() ) + { + // the table is the first. + bInSct = false; + break; + } + else if ( pUpperFrame->IsSctFrame() ) + { + // the section is the first. + bInTab = false; + break; + } + + pUpperFrame = pUpperFrame->GetUpper(); + } + } + + if ( bInTab && ( !IsTabFrame() || GetUpper()->IsCellFrame() ) ) // TABLE IN TABLE + return bFwd ? GetNextCellLeaf() : GetPrevCellLeaf(); + + if ( bInSct ) + return bFwd ? GetNextSctLeaf( eMakePage ) : GetPrevSctLeaf(); + + return bFwd ? GetNextLeaf( eMakePage ) : GetPrevLeaf(); +} + +bool SwFrame::WrongPageDesc( SwPageFrame* pNew ) +{ + // Now it's getting a bit complicated: + + // Maybe I'm bringing a Pagedesc myself; in that case, + // the pagedesc of the next page needs to correspond. + // Otherwise, I'll have to dig a bit deeper to see where + // the following Pagedesc is coming from. + // If the following page itself tells me that it's pagedesc + // is wrong, I can happily exchange it. + // If the page however thinks that it's pagedesc is correct, + // this doesn't mean it's useful to me: + // If the first BodyContent asks for a PageDesc or a PageBreak, + // I'll have to insert a new page - except the desired page is + // the correct one. + // If I inserted a new page, the problems only get started: + // because then it's likely for the next page to have been + // wrong and having been swapped because of that. + // This in turn means that I have a new (and correct) page, + // but the conditions to swap still apply. + // Way out of the situation: Try to preliminarily insert a + // new page once (empty pages are already inserted by InsertPage() + // if required) + + //My Pagedesc doesn't count if I'm a follow! + const SwPageDesc *pDesc = nullptr; + std::optional<sal_uInt16> oTmp; + SwFlowFrame *pFlow = SwFlowFrame::CastFlowFrame( this ); + if ( !pFlow || !pFlow->IsFollow() ) + { + const SwFormatPageDesc &rFormatDesc = GetPageDescItem(); + pDesc = rFormatDesc.GetPageDesc(); + if( pDesc ) + { + if( !pDesc->GetRightFormat() ) + oTmp = 2; + else if( !pDesc->GetLeftFormat() ) + oTmp = 1; + else if( rFormatDesc.GetNumOffset() ) + oTmp = rFormatDesc.GetNumOffset(); + } + } + + // Does the Content bring a Pagedesc or do we need the + // virtual page number of the new layout leaf? + // PageDesc isn't allowed with Follows + const bool isRightPage = oTmp ? sw::IsRightPageByNumber(*mpRoot, *oTmp) : pNew->OnRightPage(); + if ( !pDesc ) + pDesc = pNew->FindPageDesc(); + + bool bFirst = pNew->OnFirstPage(); + + const SwFlowFrame *pNewFlow = pNew->FindFirstBodyContent(); + // Did we find ourselves? + if( pNewFlow == pFlow ) + pNewFlow = nullptr; + if ( pNewFlow && pNewFlow->GetFrame().IsInTab() ) + pNewFlow = pNewFlow->GetFrame().FindTabFrame(); + const SwPageDesc *pNewDesc= ( pNewFlow && !pNewFlow->IsFollow() ) + ? pNewFlow->GetFrame().GetPageDescItem().GetPageDesc() + : nullptr; + + SAL_INFO( "sw.pageframe", "WrongPageDesc p: " << pNew << " phys: " << pNew->GetPhyPageNum() ); + SAL_INFO( "sw.pageframe", "WrongPageDesc " << pNew->GetPageDesc() << " " << pDesc ); + SAL_INFO( "sw.pageframe", "WrongPageDesc right: " << isRightPage + << " first: " << bFirst << " " << pNew->GetFormat() << " == " + << (isRightPage ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst)) << " " + << (isRightPage ? pDesc->GetLeftFormat(bFirst) : pDesc->GetRightFormat(bFirst)) ); + + return (pNew->GetPageDesc() != pDesc) // own desc ? + || (pNew->GetFormat() != + (isRightPage ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst))) + || (pNewDesc && pNewDesc == pDesc); +} + +/// Returns the next layout leaf in which we can move the frame. +SwLayoutFrame *SwFrame::GetNextLeaf( MakePageType eMakePage ) +{ + OSL_ENSURE( !IsInFootnote(), "GetNextLeaf(), don't call me for Footnote." ); + OSL_ENSURE( !IsInSct(), "GetNextLeaf(), don't call me for Sections." ); + + const bool bBody = IsInDocBody(); // If I'm coming from the DocBody, + // I want to end up in the body. + + // It doesn't make sense to insert pages, as we only want to search the + // chain. + if( IsInFly() ) + eMakePage = MAKEPAGE_NONE; + + // For tables, we just take the big leap. A simple GetNext would + // iterate through the first cells and, in turn, all other cells. + SwLayoutFrame *pLayLeaf = nullptr; + if ( IsTabFrame() ) + { + SwFrame *const pTmp = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + if ( pTmp ) + pLayLeaf = pTmp->GetUpper(); + } + if ( !pLayLeaf ) + pLayLeaf = GetNextLayoutLeaf(); + + SwLayoutFrame *pOldLayLeaf = nullptr; // Make sure that we don't have to + // start searching from top when we + // have a freshly created page. + bool bNewPg = false; // Only insert a new page once. + + while ( true ) + { + if ( pLayLeaf ) + { + // There's yet another LayoutFrame. Let's see if it's ready to host + // me as well. + // It only needs to be of the same kind like my starting point + // (DocBody or Footnote respectively) + if ( pLayLeaf->FindPageFrame()->IsFootnotePage() ) + { // If I ended up at the end note pages, we're done. + pLayLeaf = nullptr; + continue; + } + if ( (bBody && !pLayLeaf->IsInDocBody()) || pLayLeaf->IsInTab() + || pLayLeaf->IsInSct() ) + { + // They don't want me! Try again + pOldLayLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + continue; + } + + // I'm wanted, therefore I'm done. However, it may still be that, + // during a page break, the page type isn't the desired one. In that + // case we have to insert a page of the correct type. + + if( !IsFlowFrame() && ( eMakePage == MAKEPAGE_NONE || + eMakePage==MAKEPAGE_APPEND || eMakePage==MAKEPAGE_NOSECTION ) ) + return pLayLeaf; + + SwPageFrame *pNew = pLayLeaf->FindPageFrame(); + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + // The pagedesc check does not make sense for frames in fly frames + if ( pNew != FindPageFrame() && !bNewPg && !IsInFly() && + // i#46683 + // Do not consider page descriptions in browse mode (since + // MoveBwd ignored them) + !(pSh && pSh->GetViewOptions()->getBrowseMode() ) ) + { + if( WrongPageDesc( pNew ) ) + { + SwFootnoteContFrame *pCont = pNew->FindFootnoteCont(); + if( pCont ) + { + // If the reference of the first footnote of this page + // lies before the page, we'd rather not insert a new page. + + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + if( pFootnote && pFootnote->GetRef() ) + { + const sal_uInt16 nRefNum = pNew->GetPhyPageNum(); + if( pFootnote->GetRef()->GetPhyPageNum() < nRefNum ) + break; + } + } + //Gotcha! The following page is wrong, therefore we need to + //insert a new one. + if ( eMakePage == MAKEPAGE_INSERT ) + { + bNewPg = true; + + SwPageFrame *pPg = pOldLayLeaf ? + pOldLayLeaf->FindPageFrame() : nullptr; + if ( pPg && pPg->IsEmptyPage() ) + // Don't insert behind. Insert before the EmptyPage. + pPg = static_cast<SwPageFrame*>(pPg->GetPrev()); + + if ( !pPg || pPg == pNew ) + pPg = FindPageFrame(); + + InsertPage( pPg, false ); + pLayLeaf = GetNextLayoutLeaf(); + pOldLayLeaf = nullptr; + continue; + } + else + pLayLeaf = nullptr; + } + } + break; + } + else + { + // There's no other matching LayoutFrame, so we have to insert + // a new page. + if ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) + { + InsertPage( + pOldLayLeaf ? pOldLayLeaf->FindPageFrame() : FindPageFrame(), + false ); + + // And again from the start. + pLayLeaf = pOldLayLeaf ? pOldLayLeaf : GetNextLayoutLeaf(); + } + else + break; + } + } + return pLayLeaf; +} + +/// Returns the previous layout leaf where we can move the frame. +SwLayoutFrame *SwFrame::GetPrevLeaf() +{ + OSL_ENSURE( !IsInFootnote(), "GetPrevLeaf(), don't call me for Footnote." ); + + const bool bBody = IsInDocBody(); // If I'm coming from the DocBody, + // I want to end up in the body. + const bool bFly = IsInFly(); + + SwLayoutFrame *pLayLeaf = GetPrevLayoutLeaf(); + SwLayoutFrame *pPrevLeaf = nullptr; + + while ( pLayLeaf ) + { + if ( pLayLeaf->IsInTab() || // Never go into tables. + pLayLeaf->IsInSct() ) // Same goes for sections! + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + else if ( bBody && pLayLeaf->IsInDocBody() ) + { + if ( pLayLeaf->Lower() ) + break; + pPrevLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + if ( pLayLeaf ) + SwFlowFrame::SetMoveBwdJump( true ); + } + else if ( bFly ) + break; //Contents in Flys should accept any layout leaf. + else + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + } + return pLayLeaf ? pLayLeaf : pPrevLeaf; +} + +bool SwFlowFrame::IsPrevObjMove() const +{ + // true: The FlowFrame must respect the a border of the predecessor, also needs + // to insert a break if required. + + //!!!!!!!!!!!Hack!!!!!!!!!!! + const SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + return false; + + SwFrame *pPre = m_rThis.FindPrev(); + + if ( pPre && pPre->GetDrawObjs() ) + { + OSL_ENSURE( SwFlowFrame::CastFlowFrame( pPre ), "new flowfrm?" ); + if( SwFlowFrame::CastFlowFrame( pPre )->IsAnFollow( this ) ) + return false; + SwLayoutFrame* pPreUp = pPre->GetUpper(); + // If the upper is a SectionFrame, or a column of a SectionFrame, we're + // allowed to protrude out of it. However, we need to respect the + // Upper of the SectionFrame. + if( pPreUp->IsInSct() ) + { + if( pPreUp->IsSctFrame() ) + pPreUp = pPreUp->GetUpper(); + else if( pPreUp->IsColBodyFrame() && + pPreUp->GetUpper()->GetUpper()->IsSctFrame() ) + pPreUp = pPreUp->GetUpper()->GetUpper()->GetUpper(); + } + // i#26945 - re-factoring + // use <GetVertPosOrientFrame()> to determine, if object has followed the + // text flow to the next layout frame + for (SwAnchoredObject* pObj : *pPre->GetDrawObjs()) + { + + // Do not consider hidden objects + // i#26945 - do not consider object, which + // doesn't follow the text flow. + if ( pObj->GetFrameFormat().GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( + pObj->GetDrawObj()->GetLayer() ) && + pObj->GetFrameFormat().GetFollowTextFlow().GetValue() ) + { + const SwLayoutFrame* pVertPosOrientFrame = pObj->GetVertPosOrientFrame(); + if ( pVertPosOrientFrame && + pPreUp != pVertPosOrientFrame && + !pPreUp->IsAnLower( pVertPosOrientFrame ) ) + { + return true; + } + } + } + } + return false; +} + +/** +|* If there's a hard page break before the Frame AND there's a +|* predecessor on the same page, true is returned (we need to create a +|* new PageBreak). Otherwise, returns false. +|* If bAct is set to true, this function returns true if +|* there's a PageBreak. +|* Of course, we don't evaluate the hard page break for follows. +|* The page break is in its own FrameFormat (BEFORE) or in the FrameFormat of the +|* predecessor (AFTER). If there's no predecessor on the page, we don't +|* need to think further. +|* Also, a page break (or the need for one) is also present if +|* the FrameFormat contains a PageDesc. +|* The implementation works only on ContentFrames! - the definition +|* of the predecessor is not clear for LayoutFrames. +|*/ +bool SwFlowFrame::IsPageBreak( bool bAct ) const +{ + if ( !IsFollow() && m_rThis.IsInDocBody() && + ( !m_rThis.IsInTab() || ( m_rThis.IsTabFrame() && !m_rThis.GetUpper()->IsInTab() ) ) ) // i66968 + { + const SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + return false; + + // Determine predecessor + const SwFrame *pPrev = m_rThis.FindPrev(); + while ( pPrev && ( !pPrev->IsInDocBody() || + ( pPrev->IsTextFrame() && static_cast<const SwTextFrame*>(pPrev)->IsHiddenNow() ) ) ) + pPrev = pPrev->FindPrev(); + + if ( pPrev ) + { + OSL_ENSURE( pPrev->IsInDocBody(), "IsPageBreak: Not in DocBody?" ); + if ( bAct ) + { if ( m_rThis.FindPageFrame() == pPrev->FindPageFrame() ) + return false; + } + else + { if ( m_rThis.FindPageFrame() != pPrev->FindPageFrame() ) + return false; + } + + //for compatibility, also break at column break if no columns exist + const IDocumentSettingAccess& rIDSA = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess(); + const bool bTreatSingleColumnBreakAsPageBreak = rIDSA.get(DocumentSettingId::TREAT_SINGLE_COLUMN_BREAK_AS_PAGE_BREAK); + const SvxBreak eBreak = m_rThis.GetBreakItem().GetBreak(); + if ( eBreak == SvxBreak::PageBefore || + eBreak == SvxBreak::PageBoth || + ( bTreatSingleColumnBreakAsPageBreak && eBreak == SvxBreak::ColumnBefore && !m_rThis.FindColFrame() )) + return true; + else + { + const SvxBreak &ePrB = pPrev->GetBreakItem().GetBreak(); + if ( ePrB == SvxBreak::PageAfter || + ePrB == SvxBreak::PageBoth || + m_rThis.GetPageDescItem().GetPageDesc()) + { + return true; + } + } + } + } + return false; +} + +/** +|* If there's a hard column break before the Frame AND there is +|* a predecessor in the same column, we return true (we need to create +|* a ColBreak). Otherwise, we return false. +|* If bAct is set to true, we return true if there's a ColBreak. +|* Of course, we don't evaluate the hard column break for follows. +|* +|* The column break is in its own FrameFormat (BEFORE) or in the FrameFormat of the +|* predecessor (AFTER). If there's no predecessor in the column, we don't +|* need to think further. +|* The implementation works only on ContentFrames! - the definition +|* of the predecessor is not clear for LayoutFrames. +|*/ +bool SwFlowFrame::IsColBreak( bool bAct ) const +{ + if ( !IsFollow() && (m_rThis.IsMoveable() || bAct) ) + { + const SwFrame *pCol = m_rThis.FindColFrame(); + if ( pCol ) + { + // Determine predecessor + const SwFrame *pPrev = m_rThis.FindPrev(); + while( pPrev && ( ( !pPrev->IsInDocBody() && !m_rThis.IsInFly() && !m_rThis.FindFooterOrHeader() ) || + ( pPrev->IsTextFrame() && static_cast<const SwTextFrame*>(pPrev)->IsHiddenNow() ) ) ) + pPrev = pPrev->FindPrev(); + + if ( pPrev ) + { + if ( bAct ) + { if ( pCol == pPrev->FindColFrame() ) + return false; + } + else + { if ( pCol != pPrev->FindColFrame() ) + return false; + } + + const SvxBreak eBreak = m_rThis.GetBreakItem().GetBreak(); + if ( eBreak == SvxBreak::ColumnBefore || + eBreak == SvxBreak::ColumnBoth ) + return true; + else + { + const SvxBreak &ePrB = pPrev->GetBreakItem().GetBreak(); + if ( ePrB == SvxBreak::ColumnAfter || + ePrB == SvxBreak::ColumnBoth ) + return true; + } + } + } + } + return false; +} + +bool SwFlowFrame::HasParaSpaceAtPages( bool bSct ) const +{ + if( m_rThis.IsInSct() ) + { + const SwFrame* pTmp = m_rThis.GetUpper(); + while( pTmp ) + { + if( pTmp->IsCellFrame() || pTmp->IsFlyFrame() || + pTmp->IsFooterFrame() || pTmp->IsHeaderFrame() || + ( pTmp->IsFootnoteFrame() && !static_cast<const SwFootnoteFrame*>(pTmp)->GetMaster() ) ) + return true; + if( pTmp->IsPageFrame() ) + return !pTmp->GetPrev() || IsPageBreak(true); + if( pTmp->IsColumnFrame() && pTmp->GetPrev() ) + return IsColBreak( true ); + if( pTmp->IsSctFrame() && ( !bSct || pTmp->GetPrev() ) ) + return false; + pTmp = pTmp->GetUpper(); + } + OSL_FAIL( "HasParaSpaceAtPages: Where's my page?" ); + return false; + } + if( !m_rThis.IsInDocBody() || ( m_rThis.IsInTab() && !m_rThis.IsTabFrame()) || + IsPageBreak( true ) || ( m_rThis.FindColFrame() && IsColBreak( true ) ) ) + return true; + const SwFrame* pTmp = m_rThis.FindColFrame(); + if( pTmp ) + { + if( pTmp->GetPrev() ) + return false; + } + else + pTmp = &m_rThis; + pTmp = pTmp->FindPageFrame(); + return pTmp && !pTmp->GetPrev(); +} + +/** helper method to determine previous frame for calculation of the + upper space + + i#11860 +*/ +const SwFrame* SwFlowFrame::GetPrevFrameForUpperSpaceCalc_( const SwFrame* _pProposedPrevFrame ) const +{ + const SwFrame* pPrevFrame = _pProposedPrevFrame + ? _pProposedPrevFrame + : m_rThis.GetPrev(); + + // Skip hidden paragraphs and empty sections + while ( pPrevFrame && + ( ( pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) || + ( pPrevFrame->IsSctFrame() && + !static_cast<const SwSectionFrame*>(pPrevFrame)->GetSection() ) ) ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + + // Special case: no direct previous frame is found but frame is in footnote + // Search for a previous frame in previous footnote, + // if frame isn't in a section, which is also in the footnote + if ( !pPrevFrame && m_rThis.IsInFootnote() && + ( m_rThis.IsSctFrame() || + !m_rThis.IsInSct() || !m_rThis.FindSctFrame()->IsInFootnote() ) ) + { + const SwFootnoteFrame* pPrevFootnoteFrame = + static_cast<const SwFootnoteFrame*>(m_rThis.FindFootnoteFrame()->GetPrev()); + if ( pPrevFootnoteFrame ) + { + pPrevFrame = pPrevFootnoteFrame->GetLastLower(); + + // Skip hidden paragraphs and empty sections + while ( pPrevFrame && + ( ( pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) || + ( pPrevFrame->IsSctFrame() && + !static_cast<const SwSectionFrame*>(pPrevFrame)->GetSection() ) ) ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + } + } + // Special case: found previous frame is a section + // Search for the last content in the section + if( pPrevFrame && pPrevFrame->IsSctFrame() ) + { + const SwSectionFrame* pPrevSectFrame = + static_cast<const SwSectionFrame*>(pPrevFrame); + pPrevFrame = pPrevSectFrame->FindLastContent(); + // If the last content is in a table _inside_ the section, + // take the table herself. + // Correction: Check directly, if table is inside table, instead of indirectly + // by checking, if section isn't inside a table + if ( pPrevFrame && pPrevFrame->IsInTab() ) + { + const SwTabFrame* pTableFrame = pPrevFrame->FindTabFrame(); + if ( pPrevSectFrame->IsAnLower( pTableFrame ) ) + { + pPrevFrame = pTableFrame; + } + } + // Correction: skip hidden text frames + while ( pPrevFrame && + pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + } + + return pPrevFrame; +} + +/// Compare styles attached to these text frames. +static bool lcl_IdenticalStyles(const SwFrame* pPrevFrame, const SwFrame* pFrame) +{ + SwTextFormatColl *pPrevFormatColl = nullptr; + if (pPrevFrame && pPrevFrame->IsTextFrame()) + { + const SwTextFrame *pTextFrame = static_cast< const SwTextFrame * >( pPrevFrame ); + pPrevFormatColl = dynamic_cast<SwTextFormatColl*>( + pTextFrame->GetTextNodeForParaProps()->GetFormatColl()); + } + + bool bIdenticalStyles = false; + if (pFrame && pFrame->IsTextFrame()) + { + const SwTextFrame *pTextFrame = static_cast< const SwTextFrame * >( pFrame ); + SwTextFormatColl *const pFormatColl = dynamic_cast<SwTextFormatColl*>( + pTextFrame->GetTextNodeForParaProps()->GetFormatColl()); + bIdenticalStyles = pPrevFormatColl == pFormatColl; + } + return bIdenticalStyles; +} + +static bool lcl_getContextualSpacing(const SwFrame* pPrevFrame) +{ + bool bRet; + auto pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), pPrevFrame); + const SwBorderAttrs *pAttrs = pAccess->Get(); + + bRet = pAttrs->GetULSpace().GetContext(); + + return bRet; +} + + +SwTwips SwFlowFrame::CalcUpperSpace( const SwBorderAttrs *pAttrs, + const SwFrame* pPr, + const bool _bConsiderGrid ) const +{ + const SwFrame* pPrevFrame = GetPrevFrameForUpperSpaceCalc_( pPr ); + + std::unique_ptr<SwBorderAttrAccess> pAccess; + SwFrame* pOwn; + if( !pAttrs ) + { + if( m_rThis.IsSctFrame() ) + { + SwSectionFrame* pFoll = &static_cast<SwSectionFrame&>(m_rThis); + do + pOwn = pFoll->ContainsAny(); + while( !pOwn && nullptr != ( pFoll = pFoll->GetFollow() ) ); + if( !pOwn ) + return 0; + } + else + pOwn = &m_rThis; + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), pOwn); + pAttrs = pAccess->Get(); + } + else + { + pOwn = &m_rThis; + } + SwTwips nUpper = 0; + + { + const IDocumentSettingAccess& rIDSA = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess(); + if( pPrevFrame ) + { + const bool bUseFormerLineSpacing = rIDSA.get(DocumentSettingId::OLD_LINE_SPACING); + const bool bContextualSpacingThis = pAttrs->GetULSpace().GetContext(); + const bool bContextualSpacingPrev = lcl_getContextualSpacing(pPrevFrame); + + const bool bContextualSpacing = bContextualSpacingThis + && bContextualSpacingPrev + && lcl_IdenticalStyles(pPrevFrame, &m_rThis); + + // tdf#125893 always ignore own top margin setting of the actual paragraph + // with contextual spacing, if the previous paragraph is identical + const bool bHalfContextualSpacing = !bContextualSpacing + && bContextualSpacingThis + && !bContextualSpacingPrev + && lcl_IdenticalStyles(pPrevFrame, &m_rThis); + + // tdf#134463 always ignore own bottom margin setting of the previous paragraph + // with contextual spacing, if the actual paragraph is identical + const bool bHalfContextualSpacingPrev = !bContextualSpacing + && !bContextualSpacingThis + && bContextualSpacingPrev + && lcl_IdenticalStyles(pPrevFrame, &m_rThis); + + // i#11860 - use new method to determine needed spacing + // values of found previous frame and use these values. + SwTwips nPrevLowerSpace = 0; + SwTwips nPrevLineSpacing = 0; + // i#102458 + bool bPrevLineSpacingPorportional = false; + GetSpacingValuesOfFrame( (*pPrevFrame), + nPrevLowerSpace, nPrevLineSpacing, + bPrevLineSpacingPorportional ); + if( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) ) + { + // FIXME: apply bHalfContextualSpacing for better portability? + nUpper = bContextualSpacing ? 0 : nPrevLowerSpace + pAttrs->GetULSpace().GetUpper(); + SwTwips nAdd = nPrevLineSpacing; + // i#11859 - consideration of the line spacing + // for the upper spacing of a text frame + if ( bUseFormerLineSpacing ) + { + // former consideration + if ( pOwn->IsTextFrame() ) + { + nAdd = std::max( nAdd, static_cast<SwTextFrame*>(pOwn)->GetLineSpace() ); + } + nUpper += nAdd; + } + else + { + // new consideration: + // Only the proportional line spacing of the previous + // text frame is considered for the upper spacing and + // the line spacing values are add up instead of + // building its maximum. + if ( pOwn->IsTextFrame() ) + { + // i#102458 + // Correction: + // A proportional line spacing of the previous text frame + // is added up to an own leading line spacing. + // Otherwise, the maximum of the leading line spacing + // of the previous text frame and the own leading line + // spacing is built. + if ( bPrevLineSpacingPorportional ) + { + nAdd += static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true ); + } + else + { + nAdd = std::max( nAdd, static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true ) ); + } + } + nUpper += nAdd; + } + } + else + { + nUpper = bContextualSpacing ? 0 : std::max( + bHalfContextualSpacingPrev ? 0 : static_cast<long>(nPrevLowerSpace), + bHalfContextualSpacing ? 0 : static_cast<long>(pAttrs->GetULSpace().GetUpper()) ); + + // i#11859 - consideration of the line spacing + // for the upper spacing of a text frame + if ( bUseFormerLineSpacing ) + { + // former consideration + if ( pOwn->IsTextFrame() ) + nUpper = std::max( nUpper, static_cast<SwTextFrame*>(pOwn)->GetLineSpace() ); + if ( nPrevLineSpacing != 0 ) + { + nUpper = std::max( nUpper, nPrevLineSpacing ); + } + } + else + { + // new consideration: + // Only the proportional line spacing of the previous + // text frame is considered for the upper spacing and + // the line spacing values are add up and added to + // the paragraph spacing instead of building the + // maximum of the line spacings and the paragraph spacing. + SwTwips nAdd = nPrevLineSpacing; + if ( pOwn->IsTextFrame() ) + { + // i#102458 + // Correction: + // A proportional line spacing of the previous text frame + // is added up to an own leading line spacing. + // Otherwise, the maximum of the leading line spacing + // of the previous text frame and the own leading line + // spacing is built. + if ( bPrevLineSpacingPorportional ) + { + nAdd += static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true ); + } + else + { + nAdd = std::max( nAdd, static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true ) ); + } + } + nUpper += nAdd; + } + } + } + else if ( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES) && + CastFlowFrame( pOwn )->HasParaSpaceAtPages( m_rThis.IsSctFrame() ) ) + { + nUpper = pAttrs->GetULSpace().GetUpper(); + } + } + + // i#25029 - pass previous frame <pPrevFrame> + // to method <GetTopLine(..)>, if parameter <pPr> is set. + // Note: parameter <pPr> is set, if method is called from <SwTextFrame::WouldFit(..)> + nUpper += pAttrs->GetTopLine( m_rThis, (pPr ? pPrevFrame : nullptr) ); + + // i#11860 - consider value of new parameter <_bConsiderGrid> + // and use new method <GetUpperSpaceAmountConsideredForPageGrid(..)> + + //consider grid in square page mode + if ( _bConsiderGrid && m_rThis.GetUpper()->GetFormat()->GetDoc()->IsSquaredPageMode() ) + { + nUpper += GetUpperSpaceAmountConsideredForPageGrid_( nUpper ); + } + return nUpper; +} + +/** method to determine the upper space amount, which is considered for + the page grid + + i#11860 + Precondition: Position of frame is valid. +*/ +SwTwips SwFlowFrame::GetUpperSpaceAmountConsideredForPageGrid_( + const SwTwips _nUpperSpaceWithoutGrid ) const +{ + SwTwips nUpperSpaceAmountConsideredForPageGrid = 0; + + if ( m_rThis.IsInDocBody() && m_rThis.GetAttrSet()->GetParaGrid().GetValue() ) + { + const SwPageFrame* pPageFrame = m_rThis.FindPageFrame(); + SwTextGridItem const*const pGrid(GetGridItem(pPageFrame)); + if( pGrid ) + { + const SwFrame* pBodyFrame = pPageFrame->FindBodyCont(); + if ( pBodyFrame ) + { + const long nGridLineHeight = + pGrid->GetBaseHeight() + pGrid->GetRubyHeight(); + + SwRectFnSet aRectFnSet(&m_rThis); + const SwTwips nBodyPrtTop = aRectFnSet.GetPrtTop(*pBodyFrame); + const SwTwips nProposedPrtTop = + aRectFnSet.YInc( aRectFnSet.GetTop(m_rThis.getFrameArea()), + _nUpperSpaceWithoutGrid ); + + const SwTwips nSpaceAbovePrtTop = + aRectFnSet.YDiff( nProposedPrtTop, nBodyPrtTop ); + const SwTwips nSpaceOfCompleteLinesAbove = + nGridLineHeight * ( nSpaceAbovePrtTop / nGridLineHeight ); + SwTwips nNewPrtTop = + aRectFnSet.YInc( nBodyPrtTop, nSpaceOfCompleteLinesAbove ); + if ( aRectFnSet.YDiff( nProposedPrtTop, nNewPrtTop ) > 0 ) + { + nNewPrtTop = aRectFnSet.YInc( nNewPrtTop, nGridLineHeight ); + } + + const SwTwips nNewUpperSpace = + aRectFnSet.YDiff( nNewPrtTop, + aRectFnSet.GetTop(m_rThis.getFrameArea()) ); + + nUpperSpaceAmountConsideredForPageGrid = + nNewUpperSpace - _nUpperSpaceWithoutGrid; + + OSL_ENSURE( nUpperSpaceAmountConsideredForPageGrid >= 0, + "<SwFlowFrame::GetUpperSpaceAmountConsideredForPageGrid(..)> - negative space considered for page grid!" ); + } + } + } + return nUpperSpaceAmountConsideredForPageGrid; +} + +/** method to determine the upper space amount, which is considered for + the previous frame + + i#11860 +*/ +SwTwips SwFlowFrame::GetUpperSpaceAmountConsideredForPrevFrame() const +{ + SwTwips nUpperSpaceAmountOfPrevFrame = 0; + + const SwFrame* pPrevFrame = GetPrevFrameForUpperSpaceCalc_(); + if ( pPrevFrame ) + { + SwTwips nPrevLowerSpace = 0; + SwTwips nPrevLineSpacing = 0; + // i#102458 + bool bDummy = false; + GetSpacingValuesOfFrame( (*pPrevFrame), nPrevLowerSpace, nPrevLineSpacing, bDummy ); + if ( nPrevLowerSpace > 0 || nPrevLineSpacing > 0 ) + { + const IDocumentSettingAccess& rIDSA = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) || + !rIDSA.get(DocumentSettingId::OLD_LINE_SPACING) ) + { + nUpperSpaceAmountOfPrevFrame = nPrevLowerSpace + nPrevLineSpacing; + } + else + { + nUpperSpaceAmountOfPrevFrame = std::max( nPrevLowerSpace, nPrevLineSpacing ); + } + } + } + + return nUpperSpaceAmountOfPrevFrame; +} + +/** method to determine the upper space amount, which is considered for + the previous frame and the page grid, if option 'Use former object + positioning' is OFF + + i#11860 +*/ +SwTwips SwFlowFrame::GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid() const +{ + SwTwips nUpperSpaceAmountConsideredForPrevFrameAndPageGrid = 0; + + if ( !m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::USE_FORMER_OBJECT_POS) ) + { + nUpperSpaceAmountConsideredForPrevFrameAndPageGrid = + GetUpperSpaceAmountConsideredForPrevFrame() + + ( m_rThis.GetUpper()->GetFormat()->GetDoc()->IsSquaredPageMode() + ? GetUpperSpaceAmountConsideredForPageGrid_( CalcUpperSpace( nullptr, nullptr, false ) ) + : 0 ); + } + + return nUpperSpaceAmountConsideredForPrevFrameAndPageGrid; +} + +// Calculation of lower space + +SwTwips SwFlowFrame::CalcLowerSpace( const SwBorderAttrs* _pAttrs ) const +{ + SwTwips nLowerSpace = 0; + + std::unique_ptr<SwBorderAttrAccess> pAttrAccess; + if ( !_pAttrs ) + { + pAttrAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), &m_rThis); + _pAttrs = pAttrAccess->Get(); + } + + bool bCommonBorder = true; + if ( m_rThis.IsInSct() && m_rThis.GetUpper()->IsColBodyFrame() ) + { + const SwSectionFrame* pSectFrame = m_rThis.FindSctFrame(); + bCommonBorder = pSectFrame->GetFormat()->GetBalancedColumns().GetValue(); + } + nLowerSpace = bCommonBorder ? + _pAttrs->GetBottomLine( m_rThis ) : + _pAttrs->CalcBottomLine(); + + // i#26250 + // - correct consideration of table frames + // - use new method <CalcAddLowerSpaceAsLastInTableCell(..)> + if ( ( ( m_rThis.IsTabFrame() && m_rThis.GetUpper()->IsInTab() ) || + // No lower spacing, if frame has a follow + ( m_rThis.IsInTab() && !GetFollow() ) ) && + !m_rThis.GetIndNext() ) + { + nLowerSpace += CalcAddLowerSpaceAsLastInTableCell( _pAttrs ); + } + + // tdf#128195 Consider para spacing below last paragraph in header + bool bHasSpacingBelowPara = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess().get( + DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA); + if (bHasSpacingBelowPara && !m_rThis.IsInFly() && m_rThis.FindFooterOrHeader() && !GetFollow() + && !m_rThis.GetIndNext()) + nLowerSpace += _pAttrs->GetULSpace().GetLower() + _pAttrs->CalcLineSpacing(); + + return nLowerSpace; +} + +/** calculation of the additional space to be considered, if flow frame + is the last inside a table cell + + i#26250 +*/ +SwTwips SwFlowFrame::CalcAddLowerSpaceAsLastInTableCell( + const SwBorderAttrs* _pAttrs ) const +{ + SwTwips nAdditionalLowerSpace = 0; + + IDocumentSettingAccess const& rIDSA(m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess()); + if (rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS)) + { + const SwFrame* pFrame = &m_rThis; + if ( pFrame->IsSctFrame() ) + { + const SwSectionFrame* pSectFrame = static_cast<const SwSectionFrame*>(pFrame); + pFrame = pSectFrame->FindLastContent(); + if ( pFrame && pFrame->IsInTab() ) + { + const SwTabFrame* pTableFrame = pFrame->FindTabFrame(); + if ( pSectFrame->IsAnLower( pTableFrame ) ) + { + pFrame = pTableFrame; + } + } + } + + std::unique_ptr<SwBorderAttrAccess> pAttrAccess; + if (pFrame && (!_pAttrs || pFrame != &m_rThis)) + { + pAttrAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), pFrame); + _pAttrs = pAttrAccess->Get(); + } + + if (_pAttrs) + { + nAdditionalLowerSpace += _pAttrs->GetULSpace().GetLower(); + + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nAdditionalLowerSpace += _pAttrs->CalcLineSpacing(); + } + } + } + + return nAdditionalLowerSpace; +} + +/// Moves the Frame forward if it seems necessary regarding the current conditions and attributes. +bool SwFlowFrame::CheckMoveFwd( bool& rbMakePage, bool bKeep, bool bIgnoreMyOwnKeepValue ) +{ + const SwFrame* pNxt = m_rThis.GetIndNext(); + + if ( bKeep && //!bMovedBwd && + ( !pNxt || ( pNxt->IsTextFrame() && static_cast<const SwTextFrame*>(pNxt)->IsEmptyMaster() ) ) && + ( nullptr != (pNxt = m_rThis.FindNext()) ) && IsKeepFwdMoveAllowed(bIgnoreMyOwnKeepValue) ) + { + if( pNxt->IsSctFrame() ) + { // Don't get fooled by empty SectionFrames + const SwFrame* pTmp = nullptr; + while( pNxt && pNxt->IsSctFrame() && + ( !static_cast<const SwSectionFrame*>(pNxt)->GetSection() || + nullptr == ( pTmp = static_cast<const SwSectionFrame*>(pNxt)->ContainsAny() ) ) ) + { + pNxt = pNxt->FindNext(); + pTmp = nullptr; + } + if( pTmp ) + pNxt = pTmp; // the content of the next notempty sectionfrm + } + if( pNxt && pNxt->isFrameAreaPositionValid() ) + { + bool bMove = false; + const SwSectionFrame *pSct = m_rThis.FindSctFrame(); + if( pSct && !pSct->isFrameAreaSizeValid() ) + { + const SwSectionFrame* pNxtSct = pNxt->FindSctFrame(); + if( pNxtSct && pSct->IsAnFollow( pNxtSct ) ) + bMove = true; + } + else + bMove = true; + if( bMove ) + { + //Keep together with the following frame + MoveFwd( rbMakePage, false ); + return true; + } + } + } + + bool bMovedFwd = false; + + if ( m_rThis.GetIndPrev() ) + { + if ( IsPrevObjMove() ) // Should we care about objects of the Prev? + { + bMovedFwd = true; + if ( !MoveFwd( rbMakePage, false ) ) + rbMakePage = false; + } + else + { + if ( IsPageBreak( false ) ) + { + while ( MoveFwd( rbMakePage, true ) ) + /* do nothing */; + rbMakePage = false; + bMovedFwd = true; + } + else if ( IsColBreak ( false ) ) + { + const SwPageFrame *pPage = m_rThis.FindPageFrame(); + SwFrame *pCol = m_rThis.FindColFrame(); + do + { MoveFwd( rbMakePage, false ); + SwFrame *pTmp = m_rThis.FindColFrame(); + if( pTmp != pCol ) + { + bMovedFwd = true; + pCol = pTmp; + } + else + break; + } while ( IsColBreak( false ) ); + if ( pPage != m_rThis.FindPageFrame() ) + rbMakePage = false; + } + } + } + return bMovedFwd; +} + +bool SwFlowFrame::ForbiddenForFootnoteCntFwd() const +{ + return m_rThis.IsTabFrame() || m_rThis.IsInTab(); +} + +/// Return value guarantees that a new page was not created, +/// although false does not NECESSARILY indicate that a new page was created. +/// Either false or true(MoveFootnoteCntFwd) can be returned if no changes were made +bool SwFlowFrame::MoveFwd( bool bMakePage, bool bPageBreak, bool bMoveAlways ) +{ +//!!!!MoveFootnoteCntFwd might need to be updated as well. + SwFootnoteBossFrame *pOldBoss = m_rThis.FindFootnoteBossFrame(); + if (m_rThis.IsInFootnote()) + { + assert(!ForbiddenForFootnoteCntFwd()); // prevented by IsMoveable() + if (!m_rThis.IsContentFrame() || !pOldBoss) + { + SAL_WARN("sw.core", "Tables in footnotes are not truly supported"); + return false; + } + return static_cast<SwContentFrame&>(m_rThis).MoveFootnoteCntFwd( bMakePage, pOldBoss ); + } + + if( !IsFwdMoveAllowed() && !bMoveAlways ) + { + bool bNoFwd = true; + if( m_rThis.IsInSct() ) + { + SwFootnoteBossFrame* pBoss = m_rThis.FindFootnoteBossFrame(); + bNoFwd = !pBoss->IsInSct() || ( !pBoss->Lower()->GetNext() && + !pBoss->GetPrev() ); + } + + // Allow the MoveFwd even if we do not have an IndPrev in these cases: + if ( m_rThis.IsInTab() && + ( !m_rThis.IsTabFrame() || + ( m_rThis.GetUpper()->IsInTab() && + m_rThis.GetUpper()->FindTabFrame()->IsFwdMoveAllowed() ) ) && + nullptr != m_rThis.GetNextCellLeaf() ) + { + bNoFwd = false; + } + + if( bNoFwd ) + { + // It's allowed to move PageBreaks if the Frame isn't the first + // one on the page. + if ( !bPageBreak ) + return false; + + const SwFrame *pCol = m_rThis.FindColFrame(); + if ( !pCol || !pCol->GetPrev() ) + return false; + } + } + + std::unique_ptr<SwFrameDeleteGuard> xDeleteGuard(bMakePage ? new SwFrameDeleteGuard(pOldBoss) : nullptr); + + bool bSamePage = true; + SwLayoutFrame *pNewUpper = + m_rThis.GetLeaf( bMakePage ? MAKEPAGE_INSERT : MAKEPAGE_NONE, true ); + + if ( pNewUpper ) + { + PROTOCOL_ENTER( &m_rThis, PROT::MoveFwd, DbgAction::NONE, nullptr ); + SwPageFrame *pOldPage = pOldBoss->FindPageFrame(); + // We move ourself and all the direct successors before the + // first ContentFrame below the new Upper. + + // If our NewUpper lies in a SectionFrame, we need to make sure + // that it won't destroy itself in Calc. + SwSectionFrame* pSect = pNewUpper->FindSctFrame(); + if( pSect ) + { + // If we only switch column within our SectionFrame, we better don't + // call Calc, as this would format the SectionFrame, which in turn would + // call us again, etc. + if( pSect != m_rThis.FindSctFrame() ) + { + bool bUnlock = !pSect->IsColLocked(); + pSect->ColLock(); + pNewUpper->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + if( bUnlock ) + pSect->ColUnlock(); + } + } + // Do not calculate split cell frames. + else if ( !pNewUpper->IsCellFrame() || pNewUpper->Lower() ) + pNewUpper->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + + SwFootnoteBossFrame *pNewBoss = pNewUpper->FindFootnoteBossFrame(); + bool bBossChg = pNewBoss != pOldBoss; + pNewBoss = pNewBoss->FindFootnoteBossFrame( true ); + pOldBoss = pOldBoss->FindFootnoteBossFrame( true ); + SwPageFrame* pNewPage = pOldPage; + + xDeleteGuard.reset(); + + // First, we move the footnotes. + bool bFootnoteMoved = false; + + // i#26831 + // If pSect has just been created, the printing area of pSect has + // been calculated based on the first content of its follow. + // In this case we prefer to call a SimpleFormat for this new + // section after we inserted the contents. Otherwise the section + // frame will invalidate its lowers, if its printing area changes + // in SwSectionFrame::Format, which can cause loops. + const bool bForceSimpleFormat = pSect && pSect->HasFollow() && + !pSect->ContainsAny(); + + if ( pNewBoss != pOldBoss ) + { + pNewPage = pNewBoss->FindPageFrame(); + bSamePage = pNewPage == pOldPage; + // Set deadline, so the footnotes don't think up + // silly things... + SwRectFnSet aRectFnSet(pOldBoss); + SwSaveFootnoteHeight aHeight( pOldBoss, + aRectFnSet.GetBottom(pOldBoss->getFrameArea()) ); + SwContentFrame* pStart = m_rThis.IsContentFrame() ? + static_cast<SwContentFrame*>(&m_rThis) : static_cast<SwLayoutFrame&>(m_rThis).ContainsContent(); + OSL_ENSURE( pStart || ( m_rThis.IsTabFrame() && !static_cast<SwTabFrame&>(m_rThis).Lower() ), + "MoveFwd: Missing Content" ); + SwLayoutFrame* pBody = pStart ? ( pStart->IsTextFrame() ? + const_cast<SwBodyFrame *>(static_cast<SwTextFrame*>(pStart)->FindBodyFrame()) : nullptr ) : nullptr; + if( pBody ) + bFootnoteMoved = pBody->MoveLowerFootnotes( pStart, pOldBoss, pNewBoss, + false); + } + // It's possible when dealing with SectionFrames that we have been moved + // by pNewUpper->Calc(), for instance into the pNewUpper. + // MoveSubTree or PasteTree respectively is not prepared to handle such a + // situation. + if( pNewUpper != m_rThis.GetUpper() ) + { + // i#27145 + SwSectionFrame* pOldSct = nullptr; + if ( m_rThis.GetUpper()->IsSctFrame() ) + { + pOldSct = static_cast<SwSectionFrame*>(m_rThis.GetUpper()); + } + + MoveSubTree( pNewUpper, pNewUpper->Lower() ); + + // i#27145 + if ( pOldSct && pOldSct->GetSection() ) + { + // Prevent loops by setting the new height at + // the section frame if footnotes have been moved. + // Otherwise the call of SwLayNotify::~SwLayNotify() for + // the (invalid) section frame will invalidate the first + // lower of its follow, because it grows due to the removed + // footnotes. + // Note: If pOldSct has become empty during MoveSubTree, it + // has already been scheduled for removal. No SimpleFormat + // for these. + pOldSct->SimpleFormat(); + } + + // i#26831 + if ( bForceSimpleFormat ) + { + pSect->SimpleFormat(); + } + + if ( bFootnoteMoved && !bSamePage ) + { + pOldPage->UpdateFootnoteNum(); + pNewPage->UpdateFootnoteNum(); + } + + if( bBossChg ) + { + m_rThis.Prepare( PrepareHint::BossChanged, nullptr, false ); + if( !bSamePage ) + { + SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if ( pSh && !pSh->Imp()->IsUpdateExpFields() ) + pSh->GetDoc()->getIDocumentFieldsAccess().SetNewFieldLst(true); // Will be done by CalcLayout() later on! + + pNewPage->InvalidateSpelling(); + pNewPage->InvalidateSmartTags(); + pNewPage->InvalidateAutoCompleteWords(); + pNewPage->InvalidateWordCount(); + } + } + } + // No <CheckPageDesc(..)> in online layout + const SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + + if ( !( pSh && pSh->GetViewOptions()->getBrowseMode() ) ) + { + // i#106452 + // check page description not only in situation with sections. + if ( !bSamePage && + ( m_rThis.GetPageDescItem().GetPageDesc() || + pOldPage->GetPageDesc()->GetFollow() != pNewPage->GetPageDesc() ) ) + { + SwFrame::CheckPageDescs( pNewPage, false ); + } + } + } + return bSamePage; +} + +/** Return value tells whether any changes have been made. + * If true, the frame has moved backwards to an earlier column/section/frame/page etc. + * + * @note This should be called by derived classes. + * @note The actual moving must be implemented in the subclasses via Cut()/Paste(). + */ +bool SwFlowFrame::MoveBwd( bool &rbReformat ) +{ + SwFlowFrame::SetMoveBwdJump( false ); + + SwFootnoteFrame* pFootnote = m_rThis.FindFootnoteFrame(); + if ( pFootnote && pFootnote->IsBackMoveLocked() ) + return false; + + // Text frames, which are directly inside + // tables aren't allowed to move backward. + if ( m_rThis.IsTextFrame() && m_rThis.IsInTab() ) + { + const SwLayoutFrame* pUpperFrame = m_rThis.GetUpper(); + while ( pUpperFrame ) + { + if ( pUpperFrame->IsTabFrame() || pUpperFrame->IsRowFrame() ) + { + return false; + } + // If the text frame is a follow-section-in-table, that can move + // backward as well. + bool bIsFollowSection = pUpperFrame->IsSctFrame() && static_cast<const SwSectionFrame*>(pUpperFrame)->GetPrecede(); + + // If the text frame is a follow-in-table, that can move + // backward as well. + bool bIsFollow = const_cast<SwLayoutFrame*>(pUpperFrame)->GetPrevCellLeaf(); + + if ( ( pUpperFrame->IsColumnFrame() && pUpperFrame->IsInSct() ) || bIsFollowSection || bIsFollow ) + { + break; + } + pUpperFrame = pUpperFrame->GetUpper(); + } + } + + SwFootnoteBossFrame * pOldBoss = m_rThis.FindFootnoteBossFrame(); + if (!pOldBoss) + return false; + + SwPageFrame * const pOldPage = pOldBoss->FindPageFrame(); + SwLayoutFrame *pNewUpper = nullptr; + bool bCheckPageDescs = false; + bool bCheckPageDescOfNextPage = false; + + if ( pFootnote ) + { + // If the footnote already sits on the same page/column as the reference, + // we can't flow back. The breaks don't need to be checked for footnotes. + + // i#37084 FindLastContent does not necessarily + // have to have a result != 0 + SwFrame* pRef = nullptr; + const bool bEndnote = pFootnote->GetAttr()->GetFootnote().IsEndNote(); + const IDocumentSettingAccess& rSettings + = pFootnote->GetAttrSet()->GetDoc()->getIDocumentSettingAccess(); + if( bEndnote && pFootnote->IsInSct() ) + { + SwSectionFrame* pSect = pFootnote->FindSctFrame(); + if( pSect->IsEndnAtEnd() ) + // Endnotes at the end of the section. + pRef = pSect->FindLastContent( SwFindMode::LastCnt ); + } + else if (bEndnote && rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) + { + // Endnotes at the end of the document. + SwPageFrame* pPage = m_rThis.getRootFrame()->GetLastPage(); + pRef = pPage->FindLastBodyContent(); + } + if( !pRef ) + // Endnotes on a separate page. + pRef = pFootnote->GetRef(); + + OSL_ENSURE( pRef, "MoveBwd: Endnote for an empty section?" ); + + if( !bEndnote ) + pOldBoss = pOldBoss->FindFootnoteBossFrame( true ); + SwFootnoteBossFrame *pRefBoss = pRef->FindFootnoteBossFrame( !bEndnote ); + if ( pOldBoss != pRefBoss && + + ( !bEndnote || + pRefBoss->IsBefore( pOldBoss ) ) + ) + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_FTN, false ); + } + else if ( IsPageBreak( true ) ) // Do we have to respect a PageBreak? + { + // If the previous page doesn't have a Frame in the body, + // flowing back makes sense despite the PageBreak (otherwise, + // we'd get an empty page). + // Of course we need to overlook empty pages! + const SwFrame *pFlow = &m_rThis; + do + { + pFlow = pFlow->FindPrev(); + } while ( pFlow && + ( pFlow->FindPageFrame() == pOldPage || + !pFlow->IsInDocBody() ) ); + if ( pFlow ) + { + long nDiff = pOldPage->GetPhyPageNum() - pFlow->GetPhyPageNum(); + if ( nDiff > 1 ) + { + if ( static_cast<SwPageFrame*>(pOldPage->GetPrev())->IsEmptyPage() ) + nDiff -= 1; + if ( nDiff > 1 ) + { + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + // i#53139 + // Now <pNewUpper> is a previous layout frame, which contains + // content. But the new upper layout frame has to be the next one. + // Thus, hack for issue i14206 no longer needed, but fix for issue 114442 + // Correct fix for i53139 + // Check for wrong page description before using next new upper. + // i#66051 - further correction of fix for i53139 + // Check for correct type of new next upper layout frame + // Another correction of fix for i53139 + // Assumption, that in all cases <pNewUpper> is a previous + // layout frame, which contains content, is wrong. + // Another correction of fix for i53139 + // Beside type check, check also, if proposed new next upper + // frame is inside the same frame types. + // i#73194 - and yet another correction + // of fix for i53139: + // Assure that the new next upper layout frame doesn't + // equal the current one. + // E.g.: content is on page 3, on page 2 is only a 'ghost' + // section and on page 1 is normal content. Method <FindPrev(..)> + // will find the last content of page 1, but <GetLeaf(..)> + // returns new upper on page 2. + if (pNewUpper && pNewUpper->Lower()) + { + SwLayoutFrame* pNewNextUpper = pNewUpper->GetLeaf( MAKEPAGE_NONE, true ); + if ( pNewNextUpper && + pNewNextUpper != m_rThis.GetUpper() && + pNewNextUpper->GetType() == pNewUpper->GetType() && + pNewNextUpper->IsInDocBody() == pNewUpper->IsInDocBody() && + pNewNextUpper->IsInFootnote() == pNewUpper->IsInFootnote() && + pNewNextUpper->IsInTab() == pNewUpper->IsInTab() && + pNewNextUpper->IsInSct() == pNewUpper->IsInSct() && + !m_rThis.WrongPageDesc( pNewNextUpper->FindPageFrame() ) ) + { + pNewUpper = pNewNextUpper; + bCheckPageDescOfNextPage = true; + } + } + + bCheckPageDescs = true; + } + } + } + } + else if ( IsColBreak( true ) ) + { + // If the previous column doesn't contain a ContentFrame, flowing back + // makes sense despite the ColumnBreak, as otherwise we'd get + // an empty column. + if( m_rThis.IsInSct() ) + { + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + if( pNewUpper && !SwFlowFrame::IsMoveBwdJump() && + ( pNewUpper->ContainsContent() || + ( ( !pNewUpper->IsColBodyFrame() || + !pNewUpper->GetUpper()->GetPrev() ) && + !pNewUpper->FindSctFrame()->GetPrev() ) ) ) + { + pNewUpper = nullptr; + } + // i#53139 + // i#69409 - check <pNewUpper> + // i#71065 - check <SwFlowFrame::IsMoveBwdJump()> + else if ( pNewUpper && !SwFlowFrame::IsMoveBwdJump() ) + { + // Now <pNewUpper> is a previous layout frame, which + // contains content. But the new upper layout frame + // has to be the next one. + // Correct fix for i53139 + // Check for wrong page description before using next new upper. + // i#66051 - further correction of fix for i53139 + // Check for correct type of new next upper layout frame + // Another correction of fix for i53139 + // Beside type check, check also, if proposed new next upper + // frame is inside the same frame types. + SwLayoutFrame* pNewNextUpper = pNewUpper->GetLeaf( MAKEPAGE_NOSECTION, true ); + if ( pNewNextUpper && + pNewNextUpper->GetType() == pNewUpper->GetType() && + pNewNextUpper->IsInDocBody() == pNewUpper->IsInDocBody() && + pNewNextUpper->IsInFootnote() == pNewUpper->IsInFootnote() && + pNewNextUpper->IsInTab() == pNewUpper->IsInTab() && + pNewNextUpper->IsInSct() == pNewUpper->IsInSct() && + !m_rThis.WrongPageDesc( pNewNextUpper->FindPageFrame() ) ) + { + pNewUpper = pNewNextUpper; + } + } + } + else + { + const SwFrame *pCol = m_rThis.FindColFrame(); + bool bGoOn = true; + bool bJump = false; + do + { + if ( pCol->GetPrev() ) + pCol = pCol->GetPrev(); + else + { + bGoOn = false; + pCol = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + } + if ( pCol ) + { + // ColumnFrames now with BodyFrame + SwLayoutFrame* pColBody = pCol->IsColumnFrame() ? + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pCol)->Lower())) : + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pCol)); + if ( pColBody->ContainsContent() ) + { + bGoOn = false; // We have content here! we accept this + // only if GetLeaf() has set the MoveBwdJump. + if( SwFlowFrame::IsMoveBwdJump() ) + { + pNewUpper = pColBody; + // i#53139 + // Now <pNewUpper> is a previous layout frame, which + // contains content. But the new upper layout frame + // has to be the next one. + // Correct fix for i53139 + // Check for wrong page description before using next new upper. + // i#66051 - further correction of fix for i53139 + // Check for correct type of new next upper layout frame + // Another correction of fix for i53139 + // Beside type check, check also, if proposed new next upper + // frame is inside the same frame types. + // i#71065 + // Check that the proposed new next upper layout + // frame isn't the current one. + SwLayoutFrame* pNewNextUpper = pNewUpper->GetLeaf( MAKEPAGE_NONE, true ); + if ( pNewNextUpper && + pNewNextUpper != m_rThis.GetUpper() && + pNewNextUpper->GetType() == pNewUpper->GetType() && + pNewNextUpper->IsInDocBody() == pNewUpper->IsInDocBody() && + pNewNextUpper->IsInFootnote() == pNewUpper->IsInFootnote() && + pNewNextUpper->IsInTab() == pNewUpper->IsInTab() && + pNewNextUpper->IsInSct() == pNewUpper->IsInSct() && + !m_rThis.WrongPageDesc( pNewNextUpper->FindPageFrame() ) ) + { + pNewUpper = pNewNextUpper; + } + } + } + else + { + if( pNewUpper ) // We already had an empty column, in other + bJump = true; // words we skipped one. + pNewUpper = pColBody; // this empty column could be considered, + // but we continue searching nevertheless. + } + } + } while( bGoOn ); + if( bJump ) + SwFlowFrame::SetMoveBwdJump( true ); + } + } + else // No breaks - we can flow back. + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + + // i#27801 - no move backward of 'master' text frame, + // if - due to its object positioning - it isn't allowed to be on the new page frame + // i#44049 - add another condition for not moving backward + // If one of its objects has restarted the layout process, moving backward + // isn't sensible either. + // i#47697 - refine condition made for issue i44049 + // - allow move backward as long as the anchored object is only temporarily + // positions considering its wrapping style. + if ( pNewUpper && + m_rThis.IsTextFrame() && !IsFollow() ) + { + sal_uInt32 nToPageNum( 0 ); + const bool bMoveFwdByObjPos = SwLayouter::FrameMovedFwdByObjPos( + *(pOldPage->GetFormat()->GetDoc()), + static_cast<SwTextFrame&>(m_rThis), + nToPageNum ); + if ( bMoveFwdByObjPos && + pNewUpper->FindPageFrame()->GetPhyPageNum() < nToPageNum ) + { + pNewUpper = nullptr; + } + // i#44049 - check, if one of its anchored objects + // has restarted the layout process. + else if ( m_rThis.GetDrawObjs() ) + { + for (SwAnchoredObject* pAnchoredObj : *m_rThis.GetDrawObjs()) + { + // i#47697 - refine condition - see above + if ( pAnchoredObj->RestartLayoutProcess() && + !pAnchoredObj->IsTmpConsiderWrapInfluence() ) + { + pNewUpper = nullptr; + break; + } + } + } + } + + // With Follows, it's only allowed to flow back if there's no neighbor + // in the new environment (because that would be the Master). + // (6677) If however we skipped empty pages, we still have to move. + if ( pNewUpper && IsFollow() && pNewUpper->Lower() ) + { + // i#79774 + // neglect empty sections in proposed new upper frame + bool bProposedNewUpperContainsOnlyEmptySections( true ); + { + const SwFrame* pLower( pNewUpper->Lower() ); + while ( pLower ) + { + if ( pLower->IsSctFrame() && + !dynamic_cast<const SwSectionFrame*>(pLower)->GetSection() ) + { + pLower = pLower->GetNext(); + continue; + } + else + { + bProposedNewUpperContainsOnlyEmptySections = false; + break; + } + } + } + if ( !bProposedNewUpperContainsOnlyEmptySections ) + { + if ( SwFlowFrame::IsMoveBwdJump() ) + { + // Don't move after the Master, but into the next empty page. + SwFrame *pFrame = pNewUpper->Lower(); + while ( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); + pNewUpper = pFrame->GetLeaf( MAKEPAGE_INSERT, true ); + if( pNewUpper == m_rThis.GetUpper() ) // Did we end up in the same place? + pNewUpper = nullptr; // If so, moving is not needed. + } + else + pNewUpper = nullptr; + } + } + if ( pNewUpper && !ShouldBwdMoved( pNewUpper, rbReformat ) ) + { + if( !pNewUpper->Lower() ) + { + if( pNewUpper->IsFootnoteContFrame() ) + { + pNewUpper->Cut(); + SwFrame::DestroyFrame(pNewUpper); + } + else + { + SwSectionFrame* pSectFrame = pNewUpper->FindSctFrame(); + + if ( pSectFrame && !pSectFrame->IsColLocked() && + !pSectFrame->ContainsContent() && !pSectFrame->ContainsAny( true ) ) + { + pSectFrame->DelEmpty( true ); + SwFrame::DestroyFrame(pSectFrame); + m_rThis.setFrameAreaPositionValid(true); + } + } + } + pNewUpper = nullptr; + } + + // i#21478 - don't move backward, if flow frame wants to + // keep with next frame and next frame is locked. + // i#38232 - If next frame is a table, do *not* check, + // if it's locked. + if ( pNewUpper && !IsFollow() && + m_rThis.GetAttrSet()->GetKeep().GetValue() && m_rThis.GetIndNext() ) + { + SwFrame* pIndNext = m_rThis.GetIndNext(); + // i#38232 + if ( !pIndNext->IsTabFrame() ) + { + // get first content of section, while empty sections are skipped + while ( pIndNext && pIndNext->IsSctFrame() ) + { + if( static_cast<SwSectionFrame*>(pIndNext)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pIndNext)->ContainsAny(); + if ( pTmp ) + { + pIndNext = pTmp; + break; + } + } + pIndNext = pIndNext->GetIndNext(); + } + OSL_ENSURE( !pIndNext || dynamic_cast<const SwTextFrame*>( pIndNext) != nullptr, + "<SwFlowFrame::MovedBwd(..)> - incorrect next found." ); + if ( pIndNext && pIndNext->IsFlowFrame() && + SwFlowFrame::CastFlowFrame(pIndNext)->IsJoinLocked() ) + { + pNewUpper = nullptr; + } + } + } + + // i#65250 + // layout loop control for flowing content again and again moving + // backward under the same layout condition. + if ( pNewUpper && !IsFollow() && + pNewUpper != m_rThis.GetUpper() && + SwLayouter::MoveBwdSuppressed( *(pOldPage->GetFormat()->GetDoc()), + *this, *pNewUpper ) ) + { + SwLayoutFrame* pNextNewUpper = pNewUpper->GetLeaf( + ( !m_rThis.IsSctFrame() && m_rThis.IsInSct() ) + ? MAKEPAGE_NOSECTION + : MAKEPAGE_NONE, + true ); + // i#73194 - make code robust + OSL_ENSURE( pNextNewUpper, "<SwFlowFrame::MoveBwd(..)> - missing next new upper" ); + if ( pNextNewUpper && + ( pNextNewUpper == m_rThis.GetUpper() || + pNextNewUpper->GetType() != m_rThis.GetUpper()->GetType() ) ) + { + // tdf#107398 do not leave empty footnote container around + if (!pNewUpper->Lower() && pNewUpper->IsFootnoteContFrame()) + { + pNewUpper->Cut(); + SwFrame::DestroyFrame(pNewUpper); + } + pNewUpper = nullptr; + OSL_FAIL( "<SwFlowFrame::MoveBwd(..)> - layout loop control for layout action <Move Backward> applied!" ); + } + } + + OSL_ENSURE( pNewUpper != m_rThis.GetUpper(), + "<SwFlowFrame::MoveBwd(..)> - moving backward to the current upper frame!?" ); + if ( pNewUpper ) + { + PROTOCOL_ENTER( &m_rThis, PROT::MoveBack, DbgAction::NONE, nullptr ); + if ( pNewUpper->IsFootnoteContFrame() ) + { + // I may have gotten a Container + SwFootnoteFrame *pNew = SwFootnoteContFrame::PrependChained(&m_rThis, false); + pNew->Paste( pNewUpper ); + pNewUpper = pNew; + } + if( pNewUpper->IsFootnoteFrame() && m_rThis.IsInSct() ) + { + SwSectionFrame* pSct = m_rThis.FindSctFrame(); + // If we're in a section of a footnote, we may need to create + // a SwSectionFrame in the new upper + if( pSct->IsInFootnote() ) + { + SwFrame* pTmp = pNewUpper->Lower(); + if( pTmp ) + { + while( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + if( !pTmp->IsSctFrame() || + static_cast<SwSectionFrame*>(pTmp)->GetFollow() != pSct ) + pTmp = nullptr; + } + if( pTmp ) + pNewUpper = static_cast<SwSectionFrame*>(pTmp); + else + { + pSct = new SwSectionFrame( *pSct, true ); + pSct->Paste( pNewUpper ); + pSct->Init(); + pNewUpper = pSct; + pSct->SimpleFormat(); + } + } + } + bool bUnlock = false; + bool bFollow = false; + // Lock section. Otherwise, it could get destroyed if the only Content + // moves e.g. from the second into the first column. + SwSectionFrame* pSect = pNewUpper->FindSctFrame(); + if( pSect ) + { + bUnlock = !pSect->IsColLocked(); + pSect->ColLock(); + bFollow = pSect->HasFollow(); + } + + { + auto const pOld = m_rThis.GetUpper(); +#if BOOST_VERSION < 105600 + std::list<SwFrameDeleteGuard> g; +#else + ::std::optional<SwFrameDeleteGuard> g; +#endif + if (m_rThis.GetUpper()->IsCellFrame()) + { + // note: IsFollowFlowRow() is never set for new-style tables + SwTabFrame const*const pTabFrame(m_rThis.FindTabFrame()); + if ( pTabFrame->IsFollow() + && static_cast<SwTabFrame const*>(pTabFrame->GetPrecede())->HasFollowFlowLine() + && pTabFrame->GetFirstNonHeadlineRow() == m_rThis.GetUpper()->GetUpper()) + { + // lock follow-flow-row (similar to sections above) +#if BOOST_VERSION < 105600 + g.emplace_back(m_rThis.GetUpper()->GetUpper()); +#else + g.emplace(m_rThis.GetUpper()->GetUpper()); +#endif + assert(m_rThis.GetUpper()->GetUpper()->IsDeleteForbidden()); + } + } + pNewUpper->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + SAL_WARN_IF(pOld != m_rThis.GetUpper(), "sw.core", + "MoveBwd(): pNewUpper->Calc() moved this frame?"); + } + + m_rThis.Cut(); + + // optimization: format section, if its size is invalidated and if it's + // the new parent of moved backward frame. + bool bFormatSect( false ); + if( bUnlock ) + { + pSect->ColUnlock(); + if( pSect->HasFollow() != bFollow ) + { + pSect->InvalidateSize(); + // - optimization + if ( pSect == pNewUpper ) + bFormatSect = true; + } + } + + m_rThis.Paste( pNewUpper ); + // - optimization + if ( bFormatSect ) + pSect->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + + SwPageFrame *pNewPage = m_rThis.FindPageFrame(); + if( pNewPage != pOldPage ) + { + m_rThis.Prepare( PrepareHint::BossChanged, static_cast<const void*>(pOldPage), false ); + SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if ( pSh && !pSh->Imp()->IsUpdateExpFields() ) + pSh->GetDoc()->getIDocumentFieldsAccess().SetNewFieldLst(true); // Will be done by CalcLayout() later on + + pNewPage->InvalidateSpelling(); + pNewPage->InvalidateSmartTags(); + pNewPage->InvalidateAutoCompleteWords(); + pNewPage->InvalidateWordCount(); + + // No <CheckPageDesc(..)> in online layout + if ( !( pSh && pSh->GetViewOptions()->getBrowseMode() ) ) + { + if ( bCheckPageDescs && pNewPage->GetNext() ) + { + SwPageFrame* pStartPage = bCheckPageDescOfNextPage ? + pNewPage : + static_cast<SwPageFrame*>(pNewPage->GetNext()); + SwFrame::CheckPageDescs( pStartPage, false); + } + else if (m_rThis.GetPageDescItem().GetPageDesc()) + { + // First page could get empty for example by disabling + // a section + SwFrame::CheckPageDescs( pNewPage, false); + } + } + } + } + return pNewUpper != nullptr; +} + +SwFlowFrame *SwFlowFrame::CastFlowFrame( SwFrame *pFrame ) +{ + if ( pFrame->IsContentFrame() ) + return static_cast<SwContentFrame*>(pFrame); + if ( pFrame->IsTabFrame() ) + return static_cast<SwTabFrame*>(pFrame); + if ( pFrame->IsSctFrame() ) + return static_cast<SwSectionFrame*>(pFrame); + return nullptr; +} + +const SwFlowFrame *SwFlowFrame::CastFlowFrame( const SwFrame *pFrame ) +{ + if ( pFrame->IsContentFrame() ) + return static_cast<const SwContentFrame*>(pFrame); + if ( pFrame->IsTabFrame() ) + return static_cast<const SwTabFrame*>(pFrame); + if ( pFrame->IsSctFrame() ) + return static_cast<const SwSectionFrame*>(pFrame); + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx new file mode 100644 index 000000000..e3c52a035 --- /dev/null +++ b/sw/source/core/layout/fly.cxx @@ -0,0 +1,2917 @@ +/* -*- 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 <svl/itemiter.hxx> +#include <vcl/imap.hxx> +#include <tools/helpers.hxx> +#include <editeng/protitem.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/outlobj.hxx> +#include <fmtfsize.hxx> +#include <fmtclds.hxx> +#include <fmtcntnt.hxx> +#include <fmturl.hxx> +#include <fmtsrnd.hxx> +#include <fmtornt.hxx> +#include <fmtcnct.hxx> +#include <ndgrf.hxx> +#include <tolayoutanchoredobjectposition.hxx> +#include <fmtfollowtextflow.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <ndole.hxx> +#include <swtable.hxx> +#include <svx/svdoashp.hxx> +#include <layouter.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <dcontact.hxx> +#include <dflyobj.hxx> +#include <dview.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <hints.hxx> +#include <tabfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrms.hxx> +#include <sectfrm.hxx> +#include <vcl/svapp.hxx> +#include <calbck.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <textboxhelper.hxx> +#include <txtfly.hxx> +#include <ndindex.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <wrtsh.hxx> +#include <view.hxx> +#include <edtwin.hxx> +#include <bodyfrm.hxx> +#include <FrameControlsManager.hxx> +#include <ndtxt.hxx> + +using namespace ::com::sun::star; + +static SwTwips lcl_CalcAutoWidth( const SwLayoutFrame& rFrame ); + +SwFlyFrame::SwFlyFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwLayoutFrame( pFormat, pSib ), + SwAnchoredObject(), // #i26791# + m_pPrevLink( nullptr ), + m_pNextLink( nullptr ), + m_bInCnt( false ), + m_bAtCnt( false ), + m_bLayout( false ), + m_bAutoPosition( false ), + m_bValidContentPos( false ) +{ + mnFrameType = SwFrameType::Fly; + + m_bInvalid = m_bNotifyBack = true; + m_bLocked = m_bMinHeight = + m_bHeightClipped = m_bWidthClipped = m_bFormatHeightOnly = false; + + // Size setting: Fixed size is always the width + const SwFormatFrameSize &rFrameSize = pFormat->GetFrameSize(); + const SvxFrameDirection nDir = pFormat->GetFormatAttr( RES_FRAMEDIR ).GetValue(); + if( SvxFrameDirection::Environment == nDir ) + { + mbDerivedVert = true; + mbDerivedR2L = true; + } + else + { + mbInvalidVert = false; + mbDerivedVert = false; + mbDerivedR2L = false; + if( SvxFrameDirection::Horizontal_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + mbVertical = true; + + if ( SvxFrameDirection::Vertical_LR_TB == nDir ) + mbVertLR = true; + else if (nDir == SvxFrameDirection::Vertical_LR_BT) + { + mbVertLR = true; + mbVertLRBT = true; + } + else + mbVertLR = false; + } + } + + mbInvalidR2L = false; + if( SvxFrameDirection::Horizontal_RL_TB == nDir ) + mbRightToLeft = true; + else + mbRightToLeft = false; + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( rFrameSize.GetWidth() ); + aFrm.Height( rFrameSize.GetHeightSizeType() == SwFrameSize::Variable ? MINFLY : rFrameSize.GetHeight() ); + } + + // Fixed or variable Height? + if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum ) + m_bMinHeight = true; + else if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Fixed ) + mbFixSize = true; + + // insert columns, if necessary + InsertColumns(); + + // First the Init, then the Content: + // This is due to the fact that the Content may have Objects/Frames, + // which are then registered + InitDrawObj(); + + Chain( pAnch ); + + InsertCnt(); + + // Put it somewhere outside so that out document is not formatted unnecessarily often + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(FAR_AWAY); + aFrm.Pos().setY(FAR_AWAY); +} + +void SwFlyFrame::Chain( SwFrame* _pAnch ) +{ + // Connect to chain neighbours. + // No problem, if a neighbor doesn't exist - the construction of the + // neighbor will make the connection + const SwFormatChain& rChain = GetFormat()->GetChain(); + if ( rChain.GetPrev() || rChain.GetNext() ) + { + if ( rChain.GetNext() ) + { + SwFlyFrame* pFollow = FindChainNeighbour( *rChain.GetNext(), _pAnch ); + if ( pFollow ) + { + OSL_ENSURE( !pFollow->GetPrevLink(), "wrong chain detected" ); + if ( !pFollow->GetPrevLink() ) + SwFlyFrame::ChainFrames( this, pFollow ); + } + } + if ( rChain.GetPrev() ) + { + SwFlyFrame *pMaster = FindChainNeighbour( *rChain.GetPrev(), _pAnch ); + if ( pMaster ) + { + OSL_ENSURE( !pMaster->GetNextLink(), "wrong chain detected" ); + if ( !pMaster->GetNextLink() ) + SwFlyFrame::ChainFrames( pMaster, this ); + } + } + } +} + +void SwFlyFrame::InsertCnt() +{ + if ( !GetPrevLink() ) + { + const SwFormatContent& rContent = GetFormat()->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), ":-( no content prepared." ); + sal_uLong nIndex = rContent.GetContentIdx()->GetIndex(); + // Lower() means SwColumnFrame; the Content then needs to be inserted into the (Column)BodyFrame + ::InsertCnt_( Lower() ? static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(Lower())->Lower()) : static_cast<SwLayoutFrame*>(this), + GetFormat()->GetDoc(), nIndex ); + + // NoText always have a fixed height. + if ( Lower() && Lower()->IsNoTextFrame() ) + { + mbFixSize = true; + m_bMinHeight = false; + } + } +} + +void SwFlyFrame::InsertColumns() +{ + // #i97379# + // Check, if column are allowed. + // Columns are not allowed for fly frames, which represent graphics or embedded objects. + const SwFormatContent& rContent = GetFormat()->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "<SwFlyFrame::InsertColumns()> - no content prepared." ); + SwNodeIndex aFirstContent( *(rContent.GetContentIdx()), 1 ); + if ( aFirstContent.GetNode().IsNoTextNode() ) + { + return; + } + + const SwFormatCol &rCol = GetFormat()->GetCol(); + if ( rCol.GetNumCols() > 1 ) + { + // Start off PrtArea to be as large as Frame, so that we can put in the columns + // properly. It'll adjust later on. + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( getFrameArea().Width() ); + aPrt.Height( getFrameArea().Height() ); + } + + const SwFormatCol aOld; // ChgColumns() also needs an old value passed + ChgColumns( aOld, rCol ); + } +} + +void SwFlyFrame::DestroyImpl() +{ + // Accessible objects for fly frames will be destroyed in this destructor. + // For frames bound as char or frames that don't have an anchor we have + // to do that ourselves. For any other frame the call RemoveFly at the + // anchor will do that. + if( IsAccessibleFrame() && GetFormat() && (IsFlyInContentFrame() || !GetAnchorFrame()) ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell *pVSh = pRootFrame->GetCurrShell(); + if( pVSh && pVSh->Imp() ) + { + // Lowers aren't disposed already, so we have to do a recursive + // dispose + pVSh->Imp()->DisposeAccessibleFrame( this, true ); + } + } + } + + if( GetFormat() && !GetFormat()->GetDoc()->IsInDtor() ) + { + ClearTmpConsiderWrapInfluence(); // remove this from SwLayouter + + Unchain(); + + DeleteCnt(); + + if ( GetAnchorFrame() ) + AnchorFrame()->RemoveFly( this ); + } + + FinitDrawObj(); + + SwLayoutFrame::DestroyImpl(); + + SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(getRootFrame()->GetCurrShell()); + UpdateUnfloatButton(pWrtSh, false); +} + +SwFlyFrame::~SwFlyFrame() +{ +} + +const IDocumentDrawModelAccess& SwFlyFrame::getIDocumentDrawModelAccess() +{ + return GetFormat()->getIDocumentDrawModelAccess(); +} + +void SwFlyFrame::Unchain() +{ + if ( GetPrevLink() ) + UnchainFrames( GetPrevLink(), this ); + if ( GetNextLink() ) + UnchainFrames( this, GetNextLink() ); +} + +void SwFlyFrame::DeleteCnt() +{ + SwFrame* pFrame = m_pLower; + while ( pFrame ) + { + while ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) + { + SwAnchoredObject *pAnchoredObj = (*pFrame->GetDrawObjs())[0]; + if ( dynamic_cast<const SwFlyFrame*>( pAnchoredObj) != nullptr ) + { + SwFrame::DestroyFrame(static_cast<SwFlyFrame*>(pAnchoredObj)); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pAnchoredObj) != nullptr ) + { + // consider 'virtual' drawing objects + SdrObject* pObj = pAnchoredObj->DrawObj(); + if ( dynamic_cast<const SwDrawVirtObj*>( pObj) != nullptr ) + { + SwDrawVirtObj* pDrawVirtObj = static_cast<SwDrawVirtObj*>(pObj); + pDrawVirtObj->RemoveFromWriterLayout(); + pDrawVirtObj->RemoveFromDrawingPage(); + } + else + { + SwDrawContact* pContact = + static_cast<SwDrawContact*>(::GetUserCall( pObj )); + if ( pContact ) + { + pContact->DisconnectFromLayout(); + } + } + } + } + + pFrame->RemoveFromLayout(); + SwFrame::DestroyFrame(pFrame); + pFrame = m_pLower; + } + + InvalidatePage(); +} + +void SwFlyFrame::InitDrawObj() +{ + SetDrawObj(*SwFlyDrawContact::CreateNewRef(this, GetFormat())); + + // Set the right Layer + IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + SdrLayerID nHeavenId = rIDDMA.GetHeavenId(); + SdrLayerID nHellId = rIDDMA.GetHellId(); + GetVirtDrawObj()->SetLayer( GetFormat()->GetOpaque().GetValue() + ? nHeavenId + : nHellId ); +} + +static SwPosition ResolveFlyAnchor(SwFrameFormat const& rFlyFrame) +{ + SwFormatAnchor const& rAnch(rFlyFrame.GetAnchor()); + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { // arbitrarily pick last node + return SwPosition(SwNodeIndex(rFlyFrame.GetDoc()->GetNodes().GetEndOfContent(), -1)); + } + else + { + SwPosition const*const pPos(rAnch.GetContentAnchor()); + assert(pPos); + if (SwFrameFormat const*const pParent = pPos->nNode.GetNode().GetFlyFormat()) + { + return ResolveFlyAnchor(*pParent); + } + else if (pPos->nContent.GetIdxReg()) + { + return *pPos; + } + else + { + return SwPosition(*pPos->nNode.GetNode().GetContentNode(), 0); + } + } +} + +void SwFlyFrame::FinitDrawObj() +{ + if(!GetVirtDrawObj() ) + return; + SwFormat* pFormat = GetFormat(); + // Deregister from SdrPageViews if the Objects is still selected there. + if(!pFormat->GetDoc()->IsInDtor()) + { + SwViewShell* p1St = getRootFrame()->GetCurrShell(); + if(p1St) + { + for(SwViewShell& rCurrentShell : p1St->GetRingContainer()) + { // At the moment the Drawing can do just do an Unmark on everything, + // as the Object was already removed + if (rCurrentShell.HasDrawView() && + rCurrentShell.Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()) + { + if (SwFEShell *const pFEShell = dynamic_cast<SwFEShell*>(&rCurrentShell)) + { // tdf#131679 move any cursor out of fly + SwFlyFrame const*const pOldSelFly = ::GetFlyFromMarked(nullptr, pFEShell); + rCurrentShell.Imp()->GetDrawView()->UnmarkAll(); + if (pOldSelFly) + { + SwPosition const pos(ResolveFlyAnchor(*pOldSelFly->GetFormat())); + SwPaM const temp(pos); + pFEShell->SetSelection(temp); + // could also call SetCursor() like SwFEShell::SelectObj() + // does, but that would access layout a bit much... + } + } + else + { + rCurrentShell.Imp()->GetDrawView()->UnmarkAll(); + } + } + } + } + } + + // Else calls delete of the ContactObj + GetVirtDrawObj()->SetUserCall(nullptr); + + // Deregisters itself at the Master + // always use SdrObject::Free(...) for SdrObjects (!) + SdrObject* pTemp(GetVirtDrawObj()); + SdrObject::Free(pTemp); +} + +void SwFlyFrame::ChainFrames( SwFlyFrame *pMaster, SwFlyFrame *pFollow ) +{ + OSL_ENSURE( pMaster && pFollow, "incomplete chain" ); + OSL_ENSURE( !pMaster->GetNextLink(), "link can not be changed" ); + OSL_ENSURE( !pFollow->GetPrevLink(), "link can not be changed" ); + + pMaster->m_pNextLink = pFollow; + pFollow->m_pPrevLink = pMaster; + + if ( pMaster->ContainsContent() ) + { + // To get a text flow we need to invalidate + SwFrame *pInva = pMaster->FindLastLower(); + SwRectFnSet aRectFnSet(pMaster); + const long nBottom = aRectFnSet.GetPrtBottom(*pMaster); + while ( pInva ) + { + if( aRectFnSet.BottomDist( pInva->getFrameArea(), nBottom ) <= 0 ) + { + pInva->InvalidateSize(); + pInva->Prepare(); + pInva = pInva->FindPrev(); + } + else + pInva = nullptr; + } + } + + if ( pFollow->ContainsContent() ) + { + // There's only the content from the Masters left; the content from the Follow + // does not have any Frames left (should always be exactly one empty TextNode). + SwFrame *pFrame = pFollow->ContainsContent(); + OSL_ENSURE( !pFrame->IsTabFrame() && !pFrame->FindNext(), "follow in chain contains content" ); + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } + + // invalidate accessible relation set (accessibility wrapper) + SwViewShell* pSh = pMaster->getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = pMaster->getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleRelationSet( pMaster, pFollow ); + } +} + +void SwFlyFrame::UnchainFrames( SwFlyFrame *pMaster, SwFlyFrame *pFollow ) +{ + pMaster->m_pNextLink = nullptr; + pFollow->m_pPrevLink = nullptr; + + if ( pFollow->ContainsContent() ) + { + // The Master sucks up the content of the Follow + SwLayoutFrame *pUpper = pMaster; + if ( pUpper->Lower()->IsColumnFrame() ) + { + pUpper = static_cast<SwLayoutFrame*>(pUpper->GetLastLower()); + pUpper = static_cast<SwLayoutFrame*>(pUpper->Lower()); // The (Column)BodyFrame + OSL_ENSURE( pUpper && pUpper->IsColBodyFrame(), "Missing ColumnBody" ); + } + SwFlyFrame *pFoll = pFollow; + while ( pFoll ) + { + SwFrame *pTmp = ::SaveContent( pFoll ); + if ( pTmp ) + ::RestoreContent( pTmp, pUpper, pMaster->FindLastLower() ); + pFoll->SetCompletePaint(); + pFoll->InvalidateSize(); + pFoll = pFoll->GetNextLink(); + } + } + + // The Follow needs his own content to be served + const SwFormatContent &rContent = pFollow->GetFormat()->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), ":-( No content prepared." ); + sal_uLong nIndex = rContent.GetContentIdx()->GetIndex(); + // Lower() means SwColumnFrame: this one contains another SwBodyFrame + ::InsertCnt_( pFollow->Lower() ? const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pFollow->Lower())->Lower())) + : static_cast<SwLayoutFrame*>(pFollow), + pFollow->GetFormat()->GetDoc(), ++nIndex ); + + // invalidate accessible relation set (accessibility wrapper) + SwViewShell* pSh = pMaster->getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = pMaster->getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleRelationSet( pMaster, pFollow ); + } +} + +SwFlyFrame *SwFlyFrame::FindChainNeighbour( SwFrameFormat const &rChain, SwFrame *pAnch ) +{ + // We look for the Fly that's in the same Area. + // Areas can for now only be Head/Footer or Flys. + + if ( !pAnch ) // If an Anchor was passed along, that one counts (ctor!) + pAnch = AnchorFrame(); + + SwLayoutFrame *pLay; + if ( pAnch->IsInFly() ) + pLay = pAnch->FindFlyFrame(); + else + { + // FindFooterOrHeader is not appropriate here, as we may not have a + // connection to the Anchor yet. + pLay = pAnch->GetUpper(); + while ( pLay && !(pLay->GetType() & (SwFrameType::Header|SwFrameType::Footer)) ) + pLay = pLay->GetUpper(); + } + + SwIterator<SwFlyFrame,SwFormat> aIter( rChain ); + SwFlyFrame *pFly = aIter.First(); + if ( pLay ) + { + while ( pFly ) + { + if ( pFly->GetAnchorFrame() ) + { + if ( pFly->GetAnchorFrame()->IsInFly() ) + { + if ( pFly->AnchorFrame()->FindFlyFrame() == pLay ) + break; + } + else if ( pLay == pFly->FindFooterOrHeader() ) + break; + } + pFly = aIter.Next(); + } + } + else if ( pFly ) + { + OSL_ENSURE( !aIter.Next(), "chain with more than one instance" ); + } + return pFly; +} + +SwFrame *SwFlyFrame::FindLastLower() +{ + SwFrame *pRet = ContainsAny(); + if ( pRet && pRet->IsInTab() ) + pRet = pRet->FindTabFrame(); + SwFrame *pNxt = pRet; + while ( pNxt && IsAnLower( pNxt ) ) + { pRet = pNxt; + pNxt = pNxt->FindNext(); + } + return pRet; +} + +bool SwFlyFrame::FrameSizeChg( const SwFormatFrameSize &rFrameSize ) +{ + bool bRet = false; + SwTwips nDiffHeight = getFrameArea().Height(); + if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Variable ) + mbFixSize = m_bMinHeight = false; + else + { + if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Fixed ) + { + mbFixSize = true; + m_bMinHeight = false; + } + else if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum ) + { + mbFixSize = false; + m_bMinHeight = true; + } + nDiffHeight -= rFrameSize.GetHeight(); + } + // If the Fly contains columns, we already need to set the Fly + // and the Columns to the required value or else we run into problems. + if ( Lower() ) + { + if ( Lower()->IsColumnFrame() ) + { + const SwRect aOld( GetObjRectWithSpaces() ); + const Size aOldSz( getFramePrintArea().SSize() ); + const SwTwips nDiffWidth = getFrameArea().Width() - rFrameSize.GetWidth(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( aFrm.Height() - nDiffHeight ); + aFrm.Width ( aFrm.Width() - nDiffWidth ); + } + + // #i68520# + InvalidateObjRectWithSpaces(); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( aPrt.Height() - nDiffHeight ); + aPrt.Width ( aPrt.Width() - nDiffWidth ); + } + + ChgLowersProp( aOldSz ); + ::Notify( this, FindPageFrame(), aOld ); + setFrameAreaPositionValid(false); + bRet = true; + } + else if ( Lower()->IsNoTextFrame() ) + { + mbFixSize = true; + m_bMinHeight = false; + } + } + return bRet; +} + +void SwFlyFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + SwFrame::SwClientNotify(rMod, rHint); + if (auto pGetZOrdnerHint = dynamic_cast<const sw::GetZOrderHint*>(&rHint)) + { + const auto& rFormat(dynamic_cast<const SwFrameFormat&>(rMod)); + if (rFormat.Which() == RES_FLYFRMFMT && rFormat.getIDocumentLayoutAccess().GetCurrentViewShell()) // #i11176# + pGetZOrdnerHint->m_rnZOrder = GetVirtDrawObj()->GetOrdNum(); + } + else if (auto pConnectedHint = dynamic_cast<const sw::GetObjectConnectedHint*>(&rHint)) + { + const auto& rFormat(dynamic_cast<const SwFrameFormat&>(rMod)); + if (!pConnectedHint->m_risConnected && rFormat.Which() == RES_FLYFRMFMT && (!pConnectedHint->m_pRoot || pConnectedHint->m_pRoot == getRootFrame())) + pConnectedHint->m_risConnected = true; + } +} + +void SwFlyFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + sal_uInt8 nInvFlags = 0; + + if (pNew && pOld && RES_ATTRSET_CHG == pNew->Which()) + { + SfxItemIter aNIter( *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet() ); + SfxItemIter aOIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); + do + { + UpdateAttr_(pOItem, pNItem, nInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + if ( aOldSet.Count() || aNewSet.Count() ) + SwLayoutFrame::Modify( &aOldSet, &aNewSet ); + } + else + UpdateAttr_( pOld, pNew, nInvFlags ); + + if ( nInvFlags == 0 ) + return; + + Invalidate_(); + if ( nInvFlags & 0x01 ) + { + InvalidatePos_(); + // #i68520# + InvalidateObjRectWithSpaces(); + } + if ( nInvFlags & 0x02 ) + { + InvalidateSize_(); + // #i68520# + InvalidateObjRectWithSpaces(); + } + if ( nInvFlags & 0x04 ) + InvalidatePrt_(); + if ( nInvFlags & 0x08 ) + SetNotifyBack(); + if ( nInvFlags & 0x10 ) + SetCompletePaint(); + if ( ( nInvFlags & 0x40 ) && Lower() && Lower()->IsNoTextFrame() ) + ClrContourCache( GetVirtDrawObj() ); + SwRootFrame *pRoot; + if ( nInvFlags & 0x20 && nullptr != (pRoot = getRootFrame()) ) + pRoot->InvalidateBrowseWidth(); + // #i28701# + if ( nInvFlags & 0x80 ) + { + // update sorted object lists, the Writer fly frame is registered at. + UpdateObjInSortedList(); + } + + // #i87645# - reset flags for the layout process (only if something has been invalidated) + ResetLayoutProcessBools(); + +} + +void SwFlyFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + sal_uInt8 &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + switch( nWhich ) + { + case RES_VERT_ORIENT: + case RES_HORI_ORIENT: + // #i18732# - consider new option 'follow text flow' + case RES_FOLLOW_TEXT_FLOW: + { + // ATTENTION: Always also change Action in ChgRePos()! + rInvFlags |= 0x09; + } + break; + // #i28701# - consider new option 'wrap influence on position' + case RES_WRAP_INFLUENCE_ON_OBJPOS: + { + rInvFlags |= 0x89; + } + break; + case RES_SURROUND: + { + //#i28701# - invalidate position on change of + // wrapping style. + //rInvFlags |= 0x40; + rInvFlags |= 0x41; + // The background needs to be messaged and invalidated + const SwRect aTmp( GetObjRectWithSpaces() ); + NotifyBackground( FindPageFrame(), aTmp, PrepareHint::FlyFrameAttributesChanged ); + + // By changing the flow of frame-bound Frames, a vertical alignment + // can be activated/deactivated => MakeFlyPos + if( RndStdIds::FLY_AT_FLY == GetFormat()->GetAnchor().GetAnchorId() ) + rInvFlags |= 0x09; + + // Delete contour in the Node if necessary + if ( Lower() && Lower()->IsNoTextFrame() && + !GetFormat()->GetSurround().IsContour() ) + { + SwNoTextNode *pNd = static_cast<SwNoTextNode*>(static_cast<SwNoTextFrame*>(Lower())->GetNode()); + if ( pNd->HasContour() ) + pNd->SetContour( nullptr ); + } + // #i28701# - perform reorder of object lists + // at anchor frame and at page frame. + rInvFlags |= 0x80; + } + break; + + case RES_PROTECT: + if (pNew) + { + const SvxProtectItem *pP = static_cast<const SvxProtectItem*>(pNew); + GetVirtDrawObj()->SetMoveProtect( pP->IsPosProtected() ); + GetVirtDrawObj()->SetResizeProtect( pP->IsSizeProtected() ); + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleEditableState( true, this ); + } + } + break; + case RES_COL: + if (pOld && pNew) + { + ChgColumns( *static_cast<const SwFormatCol*>(pOld), *static_cast<const SwFormatCol*>(pNew) ); + const SwFormatFrameSize &rNew = GetFormat()->GetFrameSize(); + if ( FrameSizeChg( rNew ) ) + NotifyDrawObj(); + rInvFlags |= 0x1A; + } + break; + + case RES_FRM_SIZE: + case RES_FMT_CHG: + { + const SwFormatFrameSize &rNew = GetFormat()->GetFrameSize(); + if ( FrameSizeChg( rNew ) ) + NotifyDrawObj(); + rInvFlags |= 0x7F; + if (pOld && RES_FMT_CHG == nWhich) + { + SwRect aNew( GetObjRectWithSpaces() ); + SwRect aOld( getFrameArea() ); + const SvxULSpaceItem &rUL = static_cast<const SwFormatChg*>(pOld)->pChangedFormat->GetULSpace(); + aOld.Top( std::max( aOld.Top() - long(rUL.GetUpper()), 0L ) ); + aOld.AddHeight(rUL.GetLower() ); + const SvxLRSpaceItem &rLR = static_cast<const SwFormatChg*>(pOld)->pChangedFormat->GetLRSpace(); + aOld.Left ( std::max( aOld.Left() - rLR.GetLeft(), 0L ) ); + aOld.AddWidth(rLR.GetRight() ); + aNew.Union( aOld ); + NotifyBackground( FindPageFrame(), aNew, PrepareHint::Clear ); + + // Special case: + // When assigning a template we cannot rely on the old column + // attribute. As there need to be at least enough for ChgColumns, + // we need to create a temporary attribute. + SwFormatCol aCol; + if ( Lower() && Lower()->IsColumnFrame() ) + { + sal_uInt16 nCol = 0; + SwFrame *pTmp = Lower(); + do + { ++nCol; + pTmp = pTmp->GetNext(); + } while ( pTmp ); + aCol.Init( nCol, 0, 1000 ); + } + ChgColumns( aCol, GetFormat()->GetCol() ); + } + + SwFormatURL aURL( GetFormat()->GetURL() ); + + SwFormatFrameSize *pNewFormatFrameSize = nullptr; + SwFormatChg *pOldFormatChg = nullptr; + if (nWhich == RES_FRM_SIZE) + pNewFormatFrameSize = const_cast<SwFormatFrameSize*>(static_cast<const SwFormatFrameSize*>(pNew)); + else + pOldFormatChg = const_cast<SwFormatChg*>(static_cast<const SwFormatChg*>(pOld)); + + if (aURL.GetMap() && (pNewFormatFrameSize || pOldFormatChg)) + { + const SwFormatFrameSize &rOld = pNewFormatFrameSize ? + *pNewFormatFrameSize : + pOldFormatChg->pChangedFormat->GetFrameSize(); + //#35091# Can be "times zero", when loading the template + if ( rOld.GetWidth() && rOld.GetHeight() ) + { + + Fraction aScaleX( rOld.GetWidth(), rNew.GetWidth() ); + Fraction aScaleY( rOld.GetHeight(), rOld.GetHeight() ); + aURL.GetMap()->Scale( aScaleX, aScaleY ); + SwFrameFormat *pFormat = GetFormat(); + pFormat->LockModify(); + pFormat->SetFormatAttr( aURL ); + pFormat->UnlockModify(); + } + } + const SvxProtectItem &rP = GetFormat()->GetProtect(); + GetVirtDrawObj()->SetMoveProtect( rP.IsPosProtected() ); + GetVirtDrawObj()->SetResizeProtect( rP.IsSizeProtected() ); + + if ( pSh ) + pSh->InvalidateWindows( getFrameArea() ); + const IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + const SdrLayerID nId = GetFormat()->GetOpaque().GetValue() ? + rIDDMA.GetHeavenId() : + rIDDMA.GetHellId(); + GetVirtDrawObj()->SetLayer( nId ); + + if ( Lower() ) + { + // Delete contour in the Node if necessary + if( Lower()->IsNoTextFrame() && + !GetFormat()->GetSurround().IsContour() ) + { + SwNoTextNode *pNd = static_cast<SwNoTextNode*>(static_cast<SwNoTextFrame*>(Lower())->GetNode()); + if ( pNd->HasContour() ) + pNd->SetContour( nullptr ); + } + else if( !Lower()->IsColumnFrame() ) + { + SwFrame* pFrame = GetLastLower(); + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + } + + // #i28701# - perform reorder of object lists + // at anchor frame and at page frame. + rInvFlags |= 0x80; + + break; + } + case RES_UL_SPACE: + case RES_LR_SPACE: + { + rInvFlags |= 0x41; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + getRootFrame()->InvalidateBrowseWidth(); + SwRect aNew( GetObjRectWithSpaces() ); + SwRect aOld( getFrameArea() ); + if (pNew) + { + if ( RES_UL_SPACE == nWhich ) + { + const SvxULSpaceItem &rUL = *static_cast<const SvxULSpaceItem*>(pNew); + aOld.Top( std::max( aOld.Top() - long(rUL.GetUpper()), 0L ) ); + aOld.AddHeight(rUL.GetLower() ); + } + else + { + const SvxLRSpaceItem &rLR = *static_cast<const SvxLRSpaceItem*>(pNew); + aOld.Left ( std::max( aOld.Left() - rLR.GetLeft(), 0L ) ); + aOld.AddWidth(rLR.GetRight() ); + } + } + aNew.Union( aOld ); + NotifyBackground( FindPageFrame(), aNew, PrepareHint::Clear ); + } + break; + + case RES_TEXT_VERT_ADJUST: + { + InvalidateContentPos(); + rInvFlags |= 0x10; + } + break; + + case RES_BOX: + case RES_SHADOW: + rInvFlags |= 0x17; + break; + + case RES_FRAMEDIR : + SetDerivedVert( false ); + SetDerivedR2L( false ); + CheckDirChange(); + break; + + case RES_OPAQUE: + { + if ( pSh ) + pSh->InvalidateWindows( getFrameArea() ); + + const IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + const SdrLayerID nId = static_cast<const SvxOpaqueItem*>(pNew)->GetValue() ? + rIDDMA.GetHeavenId() : + rIDDMA.GetHellId(); + GetVirtDrawObj()->SetLayer( nId ); + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + { + pSh->Imp()->DisposeAccessibleFrame( this ); + pSh->Imp()->AddAccessibleFrame( this ); + } + } + // #i28701# - perform reorder of object lists + // at anchor frame and at page frame. + rInvFlags |= 0x80; + } + break; + + case RES_URL: + // The interface changes the frame size when interacting with text frames, + // the Map, however, needs to be relative to FrameSize(). + if ( (!Lower() || !Lower()->IsNoTextFrame()) && pNew && pOld && + static_cast<const SwFormatURL*>(pNew)->GetMap() && static_cast<const SwFormatURL*>(pOld)->GetMap() ) + { + const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); + if ( rSz.GetHeight() != getFrameArea().Height() || + rSz.GetWidth() != getFrameArea().Width() ) + { + SwFormatURL aURL( GetFormat()->GetURL() ); + Fraction aScaleX( getFrameArea().Width(), rSz.GetWidth() ); + Fraction aScaleY( getFrameArea().Height(), rSz.GetHeight() ); + aURL.GetMap()->Scale( aScaleX, aScaleY ); + SwFrameFormat *pFormat = GetFormat(); + pFormat->LockModify(); + pFormat->SetFormatAttr( aURL ); + pFormat->UnlockModify(); + } + } + // No invalidation necessary + break; + + case RES_CHAIN: + if (pNew) + { + const SwFormatChain *pChain = static_cast<const SwFormatChain*>(pNew); + if ( pChain->GetNext() ) + { + SwFlyFrame *pFollow = FindChainNeighbour( *pChain->GetNext() ); + if ( GetNextLink() && pFollow != GetNextLink() ) + SwFlyFrame::UnchainFrames( this, GetNextLink()); + if ( pFollow ) + { + if ( pFollow->GetPrevLink() && + pFollow->GetPrevLink() != this ) + SwFlyFrame::UnchainFrames( pFollow->GetPrevLink(), + pFollow ); + if ( !GetNextLink() ) + SwFlyFrame::ChainFrames( this, pFollow ); + } + } + else if ( GetNextLink() ) + SwFlyFrame::UnchainFrames( this, GetNextLink() ); + if ( pChain->GetPrev() ) + { + SwFlyFrame *pMaster = FindChainNeighbour( *pChain->GetPrev() ); + if ( GetPrevLink() && pMaster != GetPrevLink() ) + SwFlyFrame::UnchainFrames( GetPrevLink(), this ); + if ( pMaster ) + { + if ( pMaster->GetNextLink() && + pMaster->GetNextLink() != this ) + SwFlyFrame::UnchainFrames( pMaster, + pMaster->GetNextLink() ); + if ( !GetPrevLink() ) + SwFlyFrame::ChainFrames( pMaster, this ); + } + } + else if ( GetPrevLink() ) + SwFlyFrame::UnchainFrames( GetPrevLink(), this ); + } + [[fallthrough]]; + default: + bClear = false; + } + if ( bClear ) + { + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + SwLayoutFrame::Modify( pOld, pNew ); + } +} + +/// Gets information from the Modify +bool SwFlyFrame::GetInfo( SfxPoolItem & rInfo ) const +{ + if( RES_AUTOFMT_DOCNODE == rInfo.Which() ) + return false; // There's a FlyFrame, so use it + return true; // Continue searching +} + +void SwFlyFrame::Invalidate_( SwPageFrame const *pPage ) +{ + InvalidatePage( pPage ); + m_bNotifyBack = m_bInvalid = true; + + SwFlyFrame *pFrame; + if ( GetAnchorFrame() && nullptr != (pFrame = AnchorFrame()->FindFlyFrame()) ) + { + // Very bad case: If the Fly is bound within another Fly which + // contains columns, the Format should be from that one. + if ( !pFrame->IsLocked() && !pFrame->IsColLocked() && + pFrame->Lower() && pFrame->Lower()->IsColumnFrame() ) + pFrame->InvalidateSize(); + } + + // #i85216# + // if vertical position is oriented at a layout frame inside a ghost section, + // assure that the position is invalidated and that the information about + // the vertical position oriented frame is cleared + if ( GetVertPosOrientFrame() && GetVertPosOrientFrame()->IsLayoutFrame() ) + { + const SwSectionFrame* pSectFrame( GetVertPosOrientFrame()->FindSctFrame() ); + if ( pSectFrame && pSectFrame->GetSection() == nullptr ) + { + InvalidatePos(); + ClearVertPosOrientFrame(); + } + } +} + +/** Change the relative position + * + * The position will be Fix automatically and the attribute is changed accordingly. + */ +void SwFlyFrame::ChgRelPos( const Point &rNewPos ) +{ + if ( GetCurrRelPos() == rNewPos ) + return; + + SwFrameFormat *pFormat = GetFormat(); + const bool bVert = GetAnchorFrame()->IsVertical(); + const SwTwips nNewY = bVert ? rNewPos.X() : rNewPos.Y(); + SwTwips nTmpY = nNewY == LONG_MAX ? 0 : nNewY; + if( bVert ) + nTmpY = -nTmpY; + SfxItemSet aSet( pFormat->GetDoc()->GetAttrPool(), + svl::Items<RES_VERT_ORIENT, RES_HORI_ORIENT>{}); + + SwFormatVertOrient aVert( pFormat->GetVertOrient() ); + const SwTextFrame *pAutoFrame = nullptr; + // #i34948# - handle also at-page and at-fly anchored + // Writer fly frames + const RndStdIds eAnchorType = GetFrameFormat().GetAnchor().GetAnchorId(); + if ( eAnchorType == RndStdIds::FLY_AT_PAGE ) + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + aVert.SetRelationOrient( text::RelOrientation::PAGE_FRAME ); + } + else if ( eAnchorType == RndStdIds::FLY_AT_FLY ) + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + aVert.SetRelationOrient( text::RelOrientation::FRAME ); + } + else if ( IsFlyAtContentFrame() || text::VertOrientation::NONE != aVert.GetVertOrient() ) + { + if( text::RelOrientation::CHAR == aVert.GetRelationOrient() && IsAutoPos() ) + { + if( LONG_MAX != nNewY ) + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + assert(GetAnchorFrame()->IsTextFrame()); + pAutoFrame = static_cast<const SwTextFrame*>(GetAnchorFrame()); + TextFrameIndex const nOfs(pAutoFrame->MapModelToViewPos( + *pFormat->GetAnchor().GetContentAnchor())); + while( pAutoFrame->GetFollow() && + pAutoFrame->GetFollow()->GetOffset() <= nOfs ) + { + if( pAutoFrame == GetAnchorFrame() ) + nTmpY += pAutoFrame->GetRelPos().Y(); + nTmpY -= pAutoFrame->GetUpper()->getFramePrintArea().Height(); + pAutoFrame = pAutoFrame->GetFollow(); + } + nTmpY = static_cast<SwFlyAtContentFrame*>(this)->GetRelCharY(pAutoFrame)-nTmpY; + } + else + aVert.SetVertOrient( text::VertOrientation::CHAR_BOTTOM ); + } + else + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + aVert.SetRelationOrient( text::RelOrientation::FRAME ); + } + } + aVert.SetPos( nTmpY ); + aSet.Put( aVert ); + + // For Flys in the Cnt, the horizontal orientation is of no interest, + // as it's always 0 + if ( !IsFlyInContentFrame() ) + { + const SwTwips nNewX = bVert ? rNewPos.Y() : rNewPos.X(); + SwTwips nTmpX = nNewX == LONG_MAX ? 0 : nNewX; + SwFormatHoriOrient aHori( pFormat->GetHoriOrient() ); + // #i34948# - handle also at-page and at-fly anchored + // Writer fly frames + if ( eAnchorType == RndStdIds::FLY_AT_PAGE ) + { + aHori.SetHoriOrient( text::HoriOrientation::NONE ); + aHori.SetRelationOrient( text::RelOrientation::PAGE_FRAME ); + aHori.SetPosToggle( false ); + } + else if ( eAnchorType == RndStdIds::FLY_AT_FLY ) + { + aHori.SetHoriOrient( text::HoriOrientation::NONE ); + aHori.SetRelationOrient( text::RelOrientation::FRAME ); + aHori.SetPosToggle( false ); + } + else if ( IsFlyAtContentFrame() || text::HoriOrientation::NONE != aHori.GetHoriOrient() ) + { + aHori.SetHoriOrient( text::HoriOrientation::NONE ); + if( text::RelOrientation::CHAR == aHori.GetRelationOrient() && IsAutoPos() ) + { + if( LONG_MAX != nNewX ) + { + if( !pAutoFrame ) + { + assert(GetAnchorFrame()->IsTextFrame()); + pAutoFrame = static_cast<const SwTextFrame*>(GetAnchorFrame()); + TextFrameIndex const nOfs(pAutoFrame->MapModelToViewPos( + *pFormat->GetAnchor().GetContentAnchor())); + while( pAutoFrame->GetFollow() && + pAutoFrame->GetFollow()->GetOffset() <= nOfs ) + pAutoFrame = pAutoFrame->GetFollow(); + } + nTmpX -= static_cast<SwFlyAtContentFrame*>(this)->GetRelCharX(pAutoFrame); + } + } + else + aHori.SetRelationOrient( text::RelOrientation::FRAME ); + aHori.SetPosToggle( false ); + } + aHori.SetPos( nTmpX ); + aSet.Put( aHori ); + } + SetCurrRelPos( rNewPos ); + pFormat->GetDoc()->SetAttr( aSet, *pFormat ); + +} + +/** "Formats" the Frame; Frame and PrtArea. + * + * The FixSize is not inserted here. + */ +void SwFlyFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "FlyFrame::Format, pAttrs is 0." ); + + ColLock(); + + if ( !isFrameAreaSizeValid() ) + { + if ( getFrameArea().Top() == FAR_AWAY && getFrameArea().Left() == FAR_AWAY ) + { + // Remove safety switch (see SwFrame::CTor) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(0); + aFrm.Pos().setY(0); + } + + // #i68520# + InvalidateObjRectWithSpaces(); + } + + // Check column width and set it if needed + if ( Lower() && Lower()->IsColumnFrame() ) + AdjustColumns( nullptr, false ); + + setFrameAreaSizeValid(true); + + const SwTwips nUL = pAttrs->CalcTopLine() + pAttrs->CalcBottomLine(); + const SwTwips nLR = pAttrs->CalcLeftLine() + pAttrs->CalcRightLine(); + const SwFormatFrameSize &rFrameSz = GetFormat()->GetFrameSize(); + Size aRelSize( CalcRel( rFrameSz ) ); + + OSL_ENSURE( pAttrs->GetSize().Height() != 0 || rFrameSz.GetHeightPercent(), "FrameAttr height is 0." ); + OSL_ENSURE( pAttrs->GetSize().Width() != 0 || rFrameSz.GetWidthPercent(), "FrameAttr width is 0." ); + + SwRectFnSet aRectFnSet(this); + if( !HasFixSize() ) + { + long nMinHeight = 0; + if( IsMinHeight() ) + nMinHeight = aRectFnSet.IsVert() ? aRelSize.Width() : aRelSize.Height(); + + SwTwips nRemaining = CalcContentHeight(pAttrs, nMinHeight, nUL); + if( IsMinHeight() && (nRemaining + nUL) < nMinHeight ) + nRemaining = nMinHeight - nUL; + // Because the Grow/Shrink of the Flys does not directly + // set the size - only indirectly by triggering a Format() + // via Invalidate() - the sizes need to be set here. + // Notification is running along already. + // As we already got a lot of zeros per attribute, we block them + // from now on. + + if ( nRemaining < MINFLY ) + nRemaining = MINFLY; + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nRemaining ); + } + + nRemaining -= aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nRemaining + nUL ); + } + + // #i68520# + if ( nRemaining + nUL != 0 ) + { + InvalidateObjRectWithSpaces(); + } + + setFrameAreaSizeValid(true); + + if (SwFrameFormat* pShapeFormat = SwTextBoxHelper::getOtherTextBoxFormat(GetFormat(), RES_FLYFRMFMT)) + { + // This fly is a textbox of a draw shape. + SdrObject* pShape = pShapeFormat->FindSdrObject(); + if (SdrObjCustomShape* pCustomShape = dynamic_cast<SdrObjCustomShape*>( pShape) ) + { + // The shape is a customshape: then inform it about the calculated fly size. + Size aSize(getFrameArea().Width(), getFrameArea().Height()); + pCustomShape->SuggestTextFrameSize(aSize); + // Do the calculations normally done after touching editeng text of the shape. + pCustomShape->NbcSetOutlinerParaObjectForText(nullptr, nullptr); + } + } + } + else + { + // Fixed Frames do not Format itself + setFrameAreaSizeValid(true); + + // Flys set their size using the attr + SwTwips nNewSize = aRectFnSet.IsVert() ? aRelSize.Width() : aRelSize.Height(); + nNewSize -= nUL; + if( nNewSize < MINFLY ) + nNewSize = MINFLY; + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nNewSize ); + } + + nNewSize += nUL - aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nNewSize ); + } + + // #i68520# + if ( nNewSize != 0 ) + { + InvalidateObjRectWithSpaces(); + } + } + + if ( !m_bFormatHeightOnly ) + { + OSL_ENSURE( aRelSize == CalcRel( rFrameSz ), "SwFlyFrame::Format CalcRel problem" ); + SwTwips nNewSize = aRectFnSet.IsVert() ? aRelSize.Height() : aRelSize.Width(); + + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + // #i9046# Autowidth for fly frames + const SwTwips nAutoWidth = lcl_CalcAutoWidth( *this ); + if ( nAutoWidth ) + { + if( SwFrameSize::Minimum == rFrameSz.GetWidthSizeType() ) + nNewSize = std::max( nNewSize - nLR, nAutoWidth ); + else + nNewSize = nAutoWidth; + } + } + else + nNewSize -= nLR; + + if( nNewSize < MINFLY ) + nNewSize = MINFLY; + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, nNewSize ); + } + + nNewSize += nLR - aRectFnSet.GetWidth(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddRight( aFrm, nNewSize ); + } + + // #i68520# + if ( nNewSize != 0 ) + { + InvalidateObjRectWithSpaces(); + } + } + } + ColUnlock(); +} + +// #i11760# - change parameter <bNoColl>: type <bool>; +// add new parameter <bNoCalcFollow> with +// new parameter <bNoCalcFollow> was used by method +// <FormatWidthCols(..)> to avoid follow formatting +// for text frames. But, unformatted follows causes +// problems in method <SwContentFrame::WouldFit_(..)>, +// which assumes that the follows are formatted. +// Thus, <bNoCalcFollow> no longer used by <FormatWidthCols(..)>. +void CalcContent( SwLayoutFrame *pLay, bool bNoColl ) +{ + vcl::RenderContext* pRenderContext = pLay->getRootFrame()->GetCurrShell()->GetOut(); + SwSectionFrame* pSect; + bool bCollect = false; + if( pLay->IsSctFrame() ) + { + pSect = static_cast<SwSectionFrame*>(pLay); + if( pSect->IsEndnAtEnd() && !bNoColl ) + { + bCollect = true; + SwLayouter::CollectEndnotes( pLay->GetFormat()->GetDoc(), pSect ); + } + pSect->CalcFootnoteContent(); + } + else + pSect = nullptr; + SwFrame *pFrame = pLay->ContainsAny(); + if ( !pFrame ) + { + if( pSect ) + { + if( pSect->HasFollow() ) + pFrame = pSect->GetFollow()->ContainsAny(); + if( !pFrame ) + { + if( pSect->IsEndnAtEnd() ) + { + if( bCollect ) + pLay->GetFormat()->GetDoc()->getIDocumentLayoutAccess().GetLayouter()-> + InsertEndnotes( pSect ); + bool bLock = pSect->IsFootnoteLock(); + pSect->SetFootnoteLock( true ); + pSect->CalcFootnoteContent(); + pSect->CalcFootnoteContent(); + pSect->SetFootnoteLock( bLock ); + } + return; + } + pFrame->InvalidatePos_(); + } + else + return; + } + pFrame->InvalidatePage(); + + do + { + // local variables to avoid loops caused by anchored object positioning + SwAnchoredObject* pAgainObj1 = nullptr; + SwAnchoredObject* pAgainObj2 = nullptr; + + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 20; + const SwFrame* pLoopControlCond = nullptr; + + SwFrame* pLast; + do + { + pLast = pFrame; + if( pFrame->IsVertical() ? + ( pFrame->GetUpper()->getFramePrintArea().Height() != pFrame->getFrameArea().Height() ) + : ( pFrame->GetUpper()->getFramePrintArea().Width() != pFrame->getFrameArea().Width() ) ) + { + pFrame->Prepare( PrepareHint::FixSizeChanged ); + pFrame->InvalidateSize_(); + } + + if ( pFrame->IsTabFrame() ) + { + static_cast<SwTabFrame*>(pFrame)->m_bCalcLowers = true; + // #i18103# - lock move backward of follow table, + // if no section content is formatted or follow table belongs + // to the section, which content is formatted. + if ( static_cast<SwTabFrame*>(pFrame)->IsFollow() && + ( !pSect || pSect == pFrame->FindSctFrame() ) ) + { + static_cast<SwTabFrame*>(pFrame)->m_bLockBackMove = true; + } + } + + { + SwFrameDeleteGuard aDeletePageGuard(pSect ? pSect->FindPageFrame() : nullptr); + SwFrameDeleteGuard aDeleteGuard(pSect); + pFrame->Calc(pRenderContext); + } + + // #i11760# - reset control flag for follow format. + if ( pFrame->IsTextFrame() ) + { + static_cast<SwTextFrame*>(pFrame)->AllowFollowFormat(); + } + + // The keep-attribute can cause the position + // of the prev to be invalid: + // Do not consider invalid previous frame + // due to its keep-attribute, if current frame is a follow or is locked. + // #i44049# - do not consider invalid previous + // frame due to its keep-attribute, if it can't move forward. + // #i57765# - do not consider invalid previous + // frame, if current frame has a column/page break before attribute. + SwFrame* pTmpPrev = pFrame->FindPrev(); + SwFlowFrame* pTmpPrevFlowFrame = pTmpPrev && pTmpPrev->IsFlowFrame() ? SwFlowFrame::CastFlowFrame(pTmpPrev) : nullptr; + SwFlowFrame* pTmpFlowFrame = pFrame->IsFlowFrame() ? SwFlowFrame::CastFlowFrame(pFrame) : nullptr; + + bool bPrevInvalid = pTmpPrevFlowFrame && pTmpFlowFrame && + !pTmpFlowFrame->IsFollow() && + !StackHack::IsLocked() && // #i76382# + !pTmpFlowFrame->IsJoinLocked() && + !pTmpPrev->isFrameAreaPositionValid() && + pLay->IsAnLower( pTmpPrev ) && + pTmpPrevFlowFrame->IsKeep(pTmpPrev->GetAttrSet()->GetKeep(), pTmpPrev->GetBreakItem()) && + pTmpPrevFlowFrame->IsKeepFwdMoveAllowed(); + + // format floating screen objects anchored to the frame. + if ( !bPrevInvalid && pFrame->GetDrawObjs() && pLay->IsAnLower( pFrame ) ) + { + bool bAgain = false; + bool bRestartLayoutProcess = false; + SwPageFrame* pPageFrame = pFrame->FindPageFrame(); + size_t nCnt = pFrame->GetDrawObjs()->size(); + size_t i = 0; + while ( i < nCnt ) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + assert(pAnchoredObj); + + // determine if anchored object has to be + // formatted and, in case, format it + if ( !pAnchoredObj->PositionLocked() && pAnchoredObj->IsFormatPossible() ) + { + // #i43737# - no invalidation of + // anchored object needed - causes loops for as-character + // anchored objects. + //pAnchoredObj->InvalidateObjPos(); + SwRect aRect( pAnchoredObj->GetObjRect() ); + if ( !SwObjectFormatter::FormatObj( *pAnchoredObj, pFrame, pPageFrame ) ) + { + bRestartLayoutProcess = true; + break; + } + // #i3317# - restart layout process, + // if the position of the anchored object is locked now. + if ( pAnchoredObj->PositionLocked() ) + { + bRestartLayoutProcess = true; + break; + } + + if ( aRect != pAnchoredObj->GetObjRect() ) + { + bAgain = true; + if ( pAgainObj2 == pAnchoredObj ) + { + OSL_FAIL( "::CalcContent(..) - loop detected, perform attribute changes to avoid the loop" ); + // Prevent oscillation + SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + SwFormatSurround aAttr( rFormat.GetSurround() ); + if( css::text::WrapTextMode_THROUGH != aAttr.GetSurround() ) + { + // When on auto position, we can only set it to + // flow through + if ((rFormat.GetAnchor().GetAnchorId() == + RndStdIds::FLY_AT_CHAR) && + (css::text::WrapTextMode_PARALLEL == + aAttr.GetSurround())) + { + aAttr.SetSurround( css::text::WrapTextMode_THROUGH ); + } + else + { + aAttr.SetSurround( css::text::WrapTextMode_PARALLEL ); + } + rFormat.LockModify(); + rFormat.SetFormatAttr( aAttr ); + rFormat.UnlockModify(); + } + } + else + { + if ( pAgainObj1 == pAnchoredObj ) + pAgainObj2 = pAnchoredObj; + pAgainObj1 = pAnchoredObj; + } + } + + if ( !pFrame->GetDrawObjs() ) + break; + if ( pFrame->GetDrawObjs()->size() < nCnt ) + { + --nCnt; + // Do not increment index, in this case + continue; + } + } + ++i; + } + + // #i28701# - restart layout process, if + // requested by floating screen object formatting + if ( bRestartLayoutProcess ) + { + pFrame = pLay->ContainsAny(); + pAgainObj1 = nullptr; + pAgainObj2 = nullptr; + continue; + } + + // #i28701# - format anchor frame after its objects + // are formatted, if the wrapping style influence has to be considered. + if ( pLay->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + pFrame->Calc(pRenderContext); + } + + if ( bAgain ) + { + pFrame = pLay->ContainsContent(); + if ( pFrame && pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + if( pFrame && pFrame->IsInSct() ) + { + SwSectionFrame* pTmp = pFrame->FindSctFrame(); + if( pTmp != pLay && pLay->IsAnLower( pTmp ) ) + pFrame = pTmp; + } + + if ( pFrame == pLoopControlCond ) + ++nLoopControlRuns; + else + { + nLoopControlRuns = 0; + pLoopControlCond = pFrame; + } + + if ( nLoopControlRuns < nLoopControlMax ) + continue; + + OSL_FAIL( "LoopControl in CalcContent" ); + } + } + if ( pFrame->IsTabFrame() ) + { + if ( static_cast<SwTabFrame*>(pFrame)->IsFollow() ) + static_cast<SwTabFrame*>(pFrame)->m_bLockBackMove = false; + } + + pFrame = bPrevInvalid ? pTmpPrev : pFrame->FindNext(); + if( !bPrevInvalid && pFrame && pFrame->IsSctFrame() && pSect ) + { + // Empty SectionFrames could be present here + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + pFrame = pFrame->FindNext(); + + // If FindNext returns the Follow of the original Area, we want to + // continue with this content as long as it flows back. + if( pFrame && pFrame->IsSctFrame() && ( pFrame == pSect->GetFollow() || + static_cast<SwSectionFrame*>(pFrame)->IsAnFollow( pSect ) ) ) + { + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->InvalidatePos_(); + } + } + // Stay in the pLay + // Except for SectionFrames with Follow: the first ContentFrame of the Follow + // will be formatted, so that it gets a chance to load in the pLay. + // As long as these Frames are loading in pLay, we continue + } while ( pFrame && + ( pLay->IsAnLower( pFrame ) || + ( pSect && + ( ( pSect->HasFollow() && + ( pLay->IsAnLower( pLast ) || + ( pLast->IsInSct() && + pLast->FindSctFrame()->IsAnFollow(pSect) ) ) && + pSect->GetFollow()->IsAnLower( pFrame ) ) || + ( pFrame->IsInSct() && + pFrame->FindSctFrame()->IsAnFollow( pSect ) ) ) ) ) ); + if( pSect ) + { + if( bCollect ) + { + pLay->GetFormat()->GetDoc()->getIDocumentLayoutAccess().GetLayouter()->InsertEndnotes(pSect); + pSect->CalcFootnoteContent(); + } + if( pSect->HasFollow() ) + { + SwSectionFrame* pNxt = pSect->GetFollow(); + while( pNxt && !pNxt->ContainsContent() ) + pNxt = pNxt->GetFollow(); + if( pNxt ) + pNxt->CalcFootnoteContent(); + } + if( bCollect ) + { + pFrame = pLay->ContainsAny(); + bCollect = false; + if( pFrame ) + continue; + } + } + break; + } + while( true ); +} + +void SwFlyFrame::MakeObjPos() +{ + if ( !isFrameAreaPositionValid() ) + { + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + setFrameAreaPositionValid(true); + + // use new class to position object + GetAnchorFrame()->Calc(pRenderContext); + objectpositioning::SwToLayoutAnchoredObjectPosition + aObjPositioning( *GetVirtDrawObj() ); + aObjPositioning.CalcPosition(); + + // #i58280# + // update relative position + SetCurrRelPos( aObjPositioning.GetRelPos() ); + + { + SwRectFnSet aRectFnSet(GetAnchorFrame()); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( aObjPositioning.GetRelPos() ); + aFrm.Pos() += aRectFnSet.GetPos(GetAnchorFrame()->getFrameArea()); + } + + // #i69335# + InvalidateObjRectWithSpaces(); + } +} + +void SwFlyFrame::MakePrtArea( const SwBorderAttrs &rAttrs ) +{ + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + + // consider vertical layout + SwRectFnSet aRectFnSet(this); + aRectFnSet.SetXMargins( *this, rAttrs.CalcLeftLine(), + rAttrs.CalcRightLine() ); + aRectFnSet.SetYMargins( *this, rAttrs.CalcTopLine(), + rAttrs.CalcBottomLine() ); + } +} + +void SwFlyFrame::MakeContentPos( const SwBorderAttrs &rAttrs ) +{ + if ( m_bValidContentPos ) + return; + + m_bValidContentPos = true; + + const SwTwips nUL = rAttrs.CalcTopLine() + rAttrs.CalcBottomLine(); + Size aRelSize( CalcRel( GetFormat()->GetFrameSize() ) ); + + SwRectFnSet aRectFnSet(this); + long nMinHeight = 0; + if( IsMinHeight() ) + nMinHeight = aRectFnSet.IsVert() ? aRelSize.Width() : aRelSize.Height(); + + Point aNewContentPos = getFramePrintArea().Pos(); + const SdrTextVertAdjust nAdjust = GetFormat()->GetTextVertAdjust().GetValue(); + + if( nAdjust != SDRTEXTVERTADJUST_TOP ) + { + const SwTwips nContentHeight = CalcContentHeight(&rAttrs, nMinHeight, nUL); + SwTwips nDiff = 0; + + if( nContentHeight != 0) + nDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nContentHeight; + + if( nDiff > 0 ) + { + if( nAdjust == SDRTEXTVERTADJUST_CENTER ) + { + if( aRectFnSet.IsVertL2R() ) + aNewContentPos.setX(aNewContentPos.getX() + nDiff/2); + else if( aRectFnSet.IsVert() ) + aNewContentPos.setX(aNewContentPos.getX() - nDiff/2); + else + aNewContentPos.setY(aNewContentPos.getY() + nDiff/2); + } + else if( nAdjust == SDRTEXTVERTADJUST_BOTTOM ) + { + if( aRectFnSet.IsVertL2R() ) + aNewContentPos.setX(aNewContentPos.getX() + nDiff); + else if( aRectFnSet.IsVert() ) + aNewContentPos.setX(aNewContentPos.getX() - nDiff); + else + aNewContentPos.setY(aNewContentPos.getY() + nDiff); + } + } + } + if( aNewContentPos != ContentPos() ) + { + ContentPos() = aNewContentPos; + for( SwFrame *pFrame = Lower(); pFrame; pFrame = pFrame->GetNext()) + { + pFrame->InvalidatePos(); + } + } + +} + +void SwFlyFrame::InvalidateContentPos() +{ + m_bValidContentPos = false; + Invalidate_(); +} + +void SwFlyFrame::SelectionHasChanged(SwFEShell* pShell) +{ + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >(pShell); + if (pWrtSh == nullptr) + return; + + UpdateUnfloatButton(pWrtSh, IsShowUnfloatButton(pWrtSh)); +} + +bool SwFlyFrame::IsShowUnfloatButton(SwWrtShell* pWrtSh) const +{ + if (pWrtSh == nullptr) + return false; + + // In read only mode we don't allow unfloat operation + if (pWrtSh->GetViewOptions()->IsReadonly()) + return false; + + const SdrObject *pObj = GetFrameFormat().FindRealSdrObject(); + if (pObj == nullptr) + return false; + + // SwFlyFrame itself can mean images, ole objects, etc, but we interested in actual text frames + if (SwFEShell::GetObjCntType(*pObj) != OBJCNT_FLY) + return false; + + // We show the button only for the selected text frame + SwDrawView *pView = pWrtSh->Imp()->GetDrawView(); + if (pView == nullptr) + return false; + + // Fly frame can be selected only alone + if (pView->GetMarkedObjectList().GetMarkCount() != 1) + return false; + + if(!pView->IsObjMarked(pObj)) + return false; + + // A frame is a floating table if there is only one table (and maybe some whitespaces) inside it + int nTableCount = 0; + const SwFrame* pLower = GetLower(); + const SwTabFrame* pTable = nullptr; + while (pLower) + { + if (pLower->IsTabFrame()) + { + pTable = static_cast<const SwTabFrame*>(pLower); + ++nTableCount; + if (nTableCount > 1 || pTable == nullptr) + return false; + } + + if (pLower->IsTextFrame()) + { + const SwTextFrame* pTextFrame = static_cast<const SwTextFrame*>(pLower); + if (!pTextFrame->GetText().trim().isEmpty()) + return false; + } + pLower = pLower->GetNext(); + } + + if (nTableCount != 1 || pTable == nullptr) + return false; + + // Show the unfold button only for multipage tables + const SwBodyFrame *pBody = GetAnchorFrame()->FindBodyFrame(); + if (pBody == nullptr) + return false; + + long nBodyHeight = pBody->getFrameArea().Height(); + long nTableHeight = pTable->getFrameArea().Height(); + long nFrameOffset = std::abs(GetAnchorFrame()->getFrameArea().Top() - pBody->getFrameArea().Top()); + + return nBodyHeight < nTableHeight + nFrameOffset; +} + +void SwFlyFrame::ActiveUnfloatButton(SwWrtShell* pWrtSh) +{ + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + SwFrameControlPtr pControl = rMngr.GetControl(FrameControlType::FloatingTable, this); + if (pControl || pControl->GetWindow()) + { + pControl->GetWindow()->MouseButtonDown(MouseEvent()); + } +} + +void SwFlyFrame::UpdateUnfloatButton(SwWrtShell* pWrtSh, bool bShow) const +{ + if (pWrtSh == nullptr) + return; + + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + Point aTopRightPixel = rEditWin.LogicToPixel( getFrameArea().TopRight() ); + rMngr.SetUnfloatTableButton(this, bShow, aTopRightPixel); +} + +SwTwips SwFlyFrame::Grow_( SwTwips nDist, bool bTst ) +{ + SwRectFnSet aRectFnSet(this); + if ( Lower() && !IsColLocked() && !HasFixSize() ) + { + SwTwips nSize = aRectFnSet.GetHeight(getFrameArea()); + if( nSize > 0 && nDist > ( LONG_MAX - nSize ) ) + nDist = LONG_MAX - nSize; + + if ( nDist <= 0 ) + return 0; + + if ( Lower()->IsColumnFrame() ) + { // If it's a Column Frame, the Format takes control of the + // resizing (due to the adjustment). + if ( !bTst ) + { + // #i28701# - unlock position of Writer fly frame + UnlockPosition(); + InvalidatePos_(); + InvalidateSize(); + } + return 0; + } + + if ( !bTst ) + { + const SwRect aOld( GetObjRectWithSpaces() ); + InvalidateSize_(); + const bool bOldLock = m_bLocked; + Unlock(); + if ( IsFlyFreeFrame() ) + { + // #i37068# - no format of position here + // and prevent move in method <CheckClip(..)>. + // This is needed to prevent layout loop caused by nested + // Writer fly frames - inner Writer fly frames format its + // anchor, which grows/shrinks the outer Writer fly frame. + // Note: position will be invalidated below. + setFrameAreaPositionValid(true); + + // #i55416# + // Suppress format of width for autowidth frame, because the + // format of the width would call <SwTextFrame::CalcFitToContent()> + // for the lower frame, which initiated this grow. + const bool bOldFormatHeightOnly = m_bFormatHeightOnly; + const SwFormatFrameSize& rFrameSz = GetFormat()->GetFrameSize(); + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = true; + } + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if (pSh) + { + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( true ); + static_cast<SwFlyFreeFrame*>(this)->SwFlyFreeFrame::MakeAll(pSh->GetOut()); + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( false ); + } + // #i55416# + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = bOldFormatHeightOnly; + } + } + else + MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + InvalidateSize_(); + InvalidatePos(); + if ( bOldLock ) + Lock(); + const SwRect aNew( GetObjRectWithSpaces() ); + if ( aOld != aNew ) + ::Notify( this, FindPageFrame(), aOld ); + return aRectFnSet.GetHeight(aNew)-aRectFnSet.GetHeight(aOld); + } + return nDist; + } + return 0; +} + +SwTwips SwFlyFrame::Shrink_( SwTwips nDist, bool bTst ) +{ + if( Lower() && !IsColLocked() && !HasFixSize() ) + { + SwRectFnSet aRectFnSet(this); + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nDist > nHeight ) + nDist = nHeight; + + SwTwips nVal = nDist; + if ( IsMinHeight() ) + { + const SwFormatFrameSize& rFormatSize = GetFormat()->GetFrameSize(); + SwTwips nFormatHeight = aRectFnSet.IsVert() ? rFormatSize.GetWidth() : rFormatSize.GetHeight(); + + nVal = std::min( nDist, nHeight - nFormatHeight ); + } + + if ( nVal <= 0 ) + return 0; + + if ( Lower()->IsColumnFrame() ) + { // If it's a Column Frame, the Format takes control of the + // resizing (due to the adjustment). + if ( !bTst ) + { + SwRect aOld( GetObjRectWithSpaces() ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nHeight - nVal ); + } + + // #i68520# + if ( nHeight - nVal != 0 ) + { + InvalidateObjRectWithSpaces(); + } + + nHeight = aRectFnSet.GetHeight(getFramePrintArea()); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nHeight - nVal ); + } + + InvalidatePos_(); + InvalidateSize(); + ::Notify( this, FindPageFrame(), aOld ); + NotifyDrawObj(); + if ( GetAnchorFrame()->IsInFly() ) + AnchorFrame()->FindFlyFrame()->Shrink( nDist, bTst ); + } + return 0; + } + + if ( !bTst ) + { + const SwRect aOld( GetObjRectWithSpaces() ); + InvalidateSize_(); + const bool bOldLocked = m_bLocked; + Unlock(); + if ( IsFlyFreeFrame() ) + { + // #i37068# - no format of position here + // and prevent move in method <CheckClip(..)>. + // This is needed to prevent layout loop caused by nested + // Writer fly frames - inner Writer fly frames format its + // anchor, which grows/shrinks the outer Writer fly frame. + // Note: position will be invalidated below. + setFrameAreaPositionValid(true); + + // #i55416# + // Suppress format of width for autowidth frame, because the + // format of the width would call <SwTextFrame::CalcFitToContent()> + // for the lower frame, which initiated this shrink. + const bool bOldFormatHeightOnly = m_bFormatHeightOnly; + const SwFormatFrameSize& rFrameSz = GetFormat()->GetFrameSize(); + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = true; + } + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( true ); + static_cast<SwFlyFreeFrame*>(this)->SwFlyFreeFrame::MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( false ); + // #i55416# + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = bOldFormatHeightOnly; + } + } + else + MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + InvalidateSize_(); + InvalidatePos(); + if ( bOldLocked ) + Lock(); + const SwRect aNew( GetObjRectWithSpaces() ); + if ( aOld != aNew ) + { + ::Notify( this, FindPageFrame(), aOld ); + if ( GetAnchorFrame()->IsInFly() ) + AnchorFrame()->FindFlyFrame()->Shrink( nDist, bTst ); + } + return aRectFnSet.GetHeight(aOld) - + aRectFnSet.GetHeight(aNew); + } + return nVal; + } + return 0; +} + +Size SwFlyFrame::ChgSize( const Size& aNewSize ) +{ + // #i53298# + // If the fly frame anchored at-paragraph or at-character contains an OLE + // object, assure that the new size fits into the current clipping area + // of the fly frame + Size aAdjustedNewSize( aNewSize ); + { + if ( dynamic_cast<SwFlyAtContentFrame*>(this) && + Lower() && dynamic_cast<SwNoTextFrame*>(Lower()) && + static_cast<SwNoTextFrame*>(Lower())->GetNode()->GetOLENode() ) + { + SwRect aClipRect; + ::CalcClipRect( GetVirtDrawObj(), aClipRect, false ); + if ( aAdjustedNewSize.Width() > aClipRect.Width() ) + { + aAdjustedNewSize.setWidth( aClipRect.Width() ); + } + if ( aAdjustedNewSize.Height() > aClipRect.Height() ) + { + aAdjustedNewSize.setWidth( aClipRect.Height() ); + } + } + } + + if ( aAdjustedNewSize != getFrameArea().SSize() ) + { + SwFrameFormat *pFormat = GetFormat(); + SwFormatFrameSize aSz( pFormat->GetFrameSize() ); + aSz.SetWidth( aAdjustedNewSize.Width() ); + aSz.SetHeight( aAdjustedNewSize.Height() ); + // go via the Doc for UNDO + pFormat->GetDoc()->SetAttr( aSz, *pFormat ); + return aSz.GetSize(); + } + else + return getFrameArea().SSize(); +} + +bool SwFlyFrame::IsLowerOf( const SwLayoutFrame* pUpperFrame ) const +{ + OSL_ENSURE( GetAnchorFrame(), "8-( Fly is lost in Space." ); + const SwFrame* pFrame = GetAnchorFrame(); + do + { + if ( pFrame == pUpperFrame ) + return true; + pFrame = pFrame->IsFlyFrame() + ? static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame() + : pFrame->GetUpper(); + } while ( pFrame ); + return false; +} + +void SwFlyFrame::Cut() +{ +} + +void SwFrame::AppendFly( SwFlyFrame *pNew ) +{ + if (!m_pDrawObjs) + { + m_pDrawObjs.reset(new SwSortedObjs()); + } + m_pDrawObjs->Insert( *pNew ); + pNew->ChgAnchorFrame( this ); + + // Register at the page + // If there's none present, register via SwPageFrame::PreparePage + SwPageFrame* pPage = FindPageFrame(); + if ( pPage != nullptr ) + { + pPage->AppendFlyToPage( pNew ); + } +} + +void SwFrame::RemoveFly( SwFlyFrame *pToRemove ) +{ + // Deregister from the page + // Could already have happened, if the page was already destructed + SwPageFrame *pPage = pToRemove->FindPageFrame(); + if ( pPage && pPage->GetSortedObjs() ) + { + pPage->RemoveFlyFromPage( pToRemove ); + } + // #i73201# + else + { + if ( pToRemove->IsAccessibleFrame() && + pToRemove->GetFormat() && + !pToRemove->IsFlyInContentFrame() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell *pVSh = pRootFrame->GetCurrShell(); + if( pVSh && pVSh->Imp() ) + { + pVSh->Imp()->DisposeAccessibleFrame( pToRemove ); + } + } + } + } + + m_pDrawObjs->Remove(*pToRemove); + if (!m_pDrawObjs->size()) + { + m_pDrawObjs.reset(); + } + + pToRemove->ChgAnchorFrame( nullptr ); + + if ( !pToRemove->IsFlyInContentFrame() && GetUpper() && IsInTab() )//MA_FLY_HEIGHT + GetUpper()->InvalidateSize(); +} + +void SwFrame::AppendDrawObj( SwAnchoredObject& _rNewObj ) +{ + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); + + if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rNewObj) == nullptr ) + { + OSL_FAIL( "SwFrame::AppendDrawObj(..) - anchored object of unexpected type -> object not appended" ); + return; + } + + if ( dynamic_cast<const SwDrawVirtObj*>(_rNewObj.GetDrawObj()) == nullptr && + _rNewObj.GetAnchorFrame() && _rNewObj.GetAnchorFrame() != this ) + { + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); + // perform disconnect from layout, if 'master' drawing object is appended + // to a new frame. + static_cast<SwDrawContact*>(::GetUserCall( _rNewObj.GetDrawObj() ))-> + DisconnectFromLayout( false ); + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); + } + + if ( _rNewObj.GetAnchorFrame() != this ) + { + if (!m_pDrawObjs) + { + m_pDrawObjs.reset(new SwSortedObjs()); + } + m_pDrawObjs->Insert(_rNewObj); + _rNewObj.ChgAnchorFrame( this ); + } + + // #i113730# + // Assure the control objects and group objects containing controls are on the control layer + if ( ::CheckControlLayer( _rNewObj.DrawObj() ) ) + { + const IDocumentDrawModelAccess& rIDDMA = getIDocumentDrawModelAccess(); + const SdrLayerID aCurrentLayer(_rNewObj.DrawObj()->GetLayer()); + const SdrLayerID aControlLayerID(rIDDMA.GetControlsId()); + const SdrLayerID aInvisibleControlLayerID(rIDDMA.GetInvisibleControlsId()); + + if(aCurrentLayer != aControlLayerID && aCurrentLayer != aInvisibleControlLayerID) + { + if ( aCurrentLayer == rIDDMA.GetInvisibleHellId() || + aCurrentLayer == rIDDMA.GetInvisibleHeavenId() ) + { + _rNewObj.DrawObj()->SetLayer(aInvisibleControlLayerID); + } + else + { + _rNewObj.DrawObj()->SetLayer(aControlLayerID); + } + //The layer is part of the key used to sort the obj, so update + //its position if the layer changed. + m_pDrawObjs->Update(_rNewObj); + } + } + + // no direct positioning needed, but invalidate the drawing object position + _rNewObj.InvalidateObjPos(); + + // register at page frame + SwPageFrame* pPage = FindPageFrame(); + if ( pPage ) + { + pPage->AppendDrawObjToPage( _rNewObj ); + } + + // Notify accessible layout. + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + { + pSh->Imp()->AddAccessibleObj( _rNewObj.GetDrawObj() ); + } + } + + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); +} + +void SwFrame::RemoveDrawObj( SwAnchoredObject& _rToRemoveObj ) +{ + // Notify accessible layout. + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if (pLayout && pLayout->IsAnyShellAccessible()) + pSh->Imp()->DisposeAccessibleObj(_rToRemoveObj.GetDrawObj(), false); + } + + // deregister from page frame + SwPageFrame* pPage = _rToRemoveObj.GetPageFrame(); + if ( pPage && pPage->GetSortedObjs() ) + pPage->RemoveDrawObjFromPage( _rToRemoveObj ); + + m_pDrawObjs->Remove(_rToRemoveObj); + if (!m_pDrawObjs->size()) + { + m_pDrawObjs.reset(); + } + _rToRemoveObj.ChgAnchorFrame( nullptr ); + + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); +} + +void SwFrame::InvalidateObjs( const bool _bNoInvaOfAsCharAnchoredObjs ) +{ + if ( GetDrawObjs() ) + { + // #i26945# - determine page the frame is on, + // in order to check, if anchored object is registered at the same + // page. + const SwPageFrame* pPageFrame = FindPageFrame(); + // #i28701# - re-factoring + for (SwAnchoredObject* pAnchoredObj : *GetDrawObjs()) + { + if ( _bNoInvaOfAsCharAnchoredObjs && + (pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AS_CHAR) ) + { + continue; + } + // #i26945# - no invalidation, if anchored object + // isn't registered at the same page and instead is registered at + // the page, where its anchor character text frame is on. + if ( pAnchoredObj->GetPageFrame() && + pAnchoredObj->GetPageFrame() != pPageFrame ) + { + SwTextFrame* pAnchorCharFrame = pAnchoredObj->FindAnchorCharFrame(); + if ( pAnchorCharFrame && + pAnchoredObj->GetPageFrame() == pAnchorCharFrame->FindPageFrame() ) + { + continue; + } + // #115759# - unlock its position, if anchored + // object isn't registered at the page, where its anchor + // character text frame is on, respectively if it has no + // anchor character text frame. + else + { + pAnchoredObj->UnlockPosition(); + } + } + // #i51474# - reset flag, that anchored object + // has cleared environment, and unlock its position, if the anchored + // object is registered at the same page as the anchor frame is on. + if ( pAnchoredObj->ClearedEnvironment() && + pAnchoredObj->GetPageFrame() && + pAnchoredObj->GetPageFrame() == pPageFrame ) + { + pAnchoredObj->UnlockPosition(); + pAnchoredObj->SetClearedEnvironment( false ); + } + // distinguish between writer fly frames and drawing objects + if ( dynamic_cast<const SwFlyFrame*>( pAnchoredObj) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + pFly->Invalidate_(); + pFly->InvalidatePos_(); + } + else + { + pAnchoredObj->InvalidateObjPos(); + } + } // end of loop on objects, which are connected to the frame + } +} + +// #i26945# - correct check, if anchored object is a lower +// of the layout frame. E.g., anchor character text frame can be a follow text +// frame. +// #i44016# - add parameter <_bUnlockPosOfObjs> to +// force an unlockposition call for the lower objects. +void SwLayoutFrame::NotifyLowerObjs( const bool _bUnlockPosOfObjs ) +{ + // invalidate lower floating screen objects + SwPageFrame* pPageFrame = FindPageFrame(); + if ( pPageFrame && pPageFrame->GetSortedObjs() ) + { + SwSortedObjs& rObjs = *(pPageFrame->GetSortedObjs()); + for (SwAnchoredObject* pObj : rObjs) + { + // #i26945# - check, if anchored object is a lower + // of the layout frame is changed to check, if its anchor frame + // is a lower of the layout frame. + // determine the anchor frame - usually it's the anchor frame, + // for at-character/as-character anchored objects the anchor character + // text frame is taken. + const SwFrame* pAnchorFrame = pObj->GetAnchorFrameContainingAnchPos(); + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pObj); + + if ( pFly->getFrameArea().Left() == FAR_AWAY ) + continue; + + if ( pFly->IsAnLower( this ) ) + continue; + + // #i26945# - use <pAnchorFrame> to check, if + // fly frame is lower of layout frame resp. if fly frame is + // at a different page registered as its anchor frame is on. + const bool bLow = IsAnLower( pAnchorFrame ); + if ( bLow || pAnchorFrame->FindPageFrame() != pPageFrame ) + { + pFly->Invalidate_( pPageFrame ); + if ( !bLow || pFly->IsFlyAtContentFrame() ) + { + // #i44016# + if ( _bUnlockPosOfObjs ) + { + pFly->UnlockPosition(); + } + pFly->InvalidatePos_(); + } + else + pFly->InvalidatePrt_(); + } + } + else + { + OSL_ENSURE( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr, + "<SwLayoutFrame::NotifyFlys() - anchored object of unexpected type" ); + // #i26945# - use <pAnchorFrame> to check, if + // fly frame is lower of layout frame resp. if fly frame is + // at a different page registered as its anchor frame is on. + if ( IsAnLower( pAnchorFrame ) || + pAnchorFrame->FindPageFrame() != pPageFrame ) + { + // #i44016# + if ( _bUnlockPosOfObjs ) + { + pObj->UnlockPosition(); + } + pObj->InvalidateObjPos(); + } + } + } + } +} + +void SwFlyFrame::NotifyDrawObj() +{ + SwVirtFlyDrawObj* pObj = GetVirtDrawObj(); + pObj->SetRect(); + pObj->SetRectsDirty(); + pObj->SetChanged(); + pObj->BroadcastObjectChange(); + + if ( GetFormat()->GetSurround().IsContour() ) + { + ClrContourCache( pObj ); + } + else if(IsFlyFreeFrame() && static_cast< const SwFlyFreeFrame* >(this)->supportsAutoContour()) + { + // RotateFlyFrame3: Also need to clear when changes happen + // Caution: isTransformableSwFrame is already reset when resetting rotation, so + // *additionally* reset in SwFlyFreeFrame::MakeAll when no more rotation + ClrContourCache( pObj ); + } +} + +Size SwFlyFrame::CalcRel( const SwFormatFrameSize &rSz ) const +{ + Size aRet( rSz.GetSize() ); + + const SwFrame *pRel = IsFlyLayFrame() ? GetAnchorFrame() : GetAnchorFrame()->GetUpper(); + if( pRel ) // LAYER_IMPL + { + long nRelWidth = LONG_MAX, nRelHeight = LONG_MAX; + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( ( pRel->IsBodyFrame() || pRel->IsPageFrame() ) && + pSh && pSh->GetViewOptions()->getBrowseMode() && + pSh->VisArea().HasArea() ) + { + nRelWidth = pSh->GetBrowseWidth(); + nRelHeight = pSh->VisArea().Height(); + Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); + nRelWidth = std::min( nRelWidth, pRel->getFramePrintArea().Width() ); + nRelHeight -= 2*aBorder.Height(); + nRelHeight = std::min( nRelHeight, pRel->getFramePrintArea().Height() ); + } + + // At the moment only the "== PAGE_FRAME" and "!= PAGE_FRAME" cases are handled. + // When size is a relative to page size, ignore size of SwBodyFrame. + if (rSz.GetWidthPercentRelation() != text::RelOrientation::PAGE_FRAME) + nRelWidth = std::min( nRelWidth, pRel->getFramePrintArea().Width() ); + else if ( pRel->IsPageFrame() ) + nRelWidth = std::min( nRelWidth, pRel->getFrameArea().Width() ); + + if (rSz.GetHeightPercentRelation() != text::RelOrientation::PAGE_FRAME) + nRelHeight = std::min( nRelHeight, pRel->getFramePrintArea().Height() ); + else if ( pRel->IsPageFrame() ) + nRelHeight = std::min( nRelHeight, pRel->getFrameArea().Height() ); + + if( !pRel->IsPageFrame() ) + { + const SwPageFrame* pPage = FindPageFrame(); + if( pPage ) + { + if (rSz.GetWidthPercentRelation() == text::RelOrientation::PAGE_FRAME) + // Ignore margins of pPage. + nRelWidth = std::min( nRelWidth, pPage->getFrameArea().Width() ); + else + nRelWidth = std::min( nRelWidth, pPage->getFramePrintArea().Width() ); + if (rSz.GetHeightPercentRelation() == text::RelOrientation::PAGE_FRAME) + // Ignore margins of pPage. + nRelHeight = std::min( nRelHeight, pPage->getFrameArea().Height() ); + else + nRelHeight = std::min( nRelHeight, pPage->getFramePrintArea().Height() ); + } + } + + if ( rSz.GetWidthPercent() && rSz.GetWidthPercent() != SwFormatFrameSize::SYNCED ) + aRet.setWidth( nRelWidth * rSz.GetWidthPercent() / 100 ); + if ( rSz.GetHeightPercent() && rSz.GetHeightPercent() != SwFormatFrameSize::SYNCED ) + aRet.setHeight( nRelHeight * rSz.GetHeightPercent() / 100 ); + + if ( rSz.GetWidthPercent() == SwFormatFrameSize::SYNCED ) + { + aRet.setWidth( aRet.Width() * ( aRet.Height()) ); + aRet.setWidth( aRet.Width() / ( rSz.GetHeight()) ); + } + else if ( rSz.GetHeightPercent() == SwFormatFrameSize::SYNCED ) + { + aRet.setHeight( aRet.Height() * ( aRet.Width()) ); + aRet.setHeight( aRet.Height() / ( rSz.GetWidth()) ); + } + } + return aRet; +} + +static SwTwips lcl_CalcAutoWidth( const SwLayoutFrame& rFrame ) +{ + SwTwips nRet = 0; + SwTwips nMin = 0; + const SwFrame* pFrame = rFrame.Lower(); + + // No autowidth defined for columned frames + if ( !pFrame || pFrame->IsColumnFrame() ) + return nRet; + + while ( pFrame ) + { + if ( pFrame->IsSctFrame() ) + { + nMin = lcl_CalcAutoWidth( *static_cast<const SwSectionFrame*>(pFrame) ); + } + if ( pFrame->IsTextFrame() ) + { + nMin = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pFrame))->CalcFitToContent(); + const SvxLRSpaceItem &rSpace = + static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace(); + if (!static_cast<const SwTextFrame*>(pFrame)->IsLocked()) + nMin += rSpace.GetRight() + rSpace.GetTextLeft() + rSpace.GetTextFirstLineOffset(); + } + else if ( pFrame->IsTabFrame() ) + { + const SwFormatFrameSize& rTableFormatSz = static_cast<const SwTabFrame*>(pFrame)->GetTable()->GetFrameFormat()->GetFrameSize(); + if ( USHRT_MAX == rTableFormatSz.GetSize().Width() || + text::HoriOrientation::NONE == static_cast<const SwTabFrame*>(pFrame)->GetFormat()->GetHoriOrient().GetHoriOrient() ) + { + const SwPageFrame* pPage = rFrame.FindPageFrame(); + // auto width table + nMin = pFrame->GetUpper()->IsVertical() ? + pPage->getFramePrintArea().Height() : + pPage->getFramePrintArea().Width(); + } + else + { + nMin = rTableFormatSz.GetSize().Width(); + } + } + + if ( nMin > nRet ) + nRet = nMin; + + pFrame = pFrame->GetNext(); + } + + return nRet; +} + +/// #i13147# - If called for paint and the <SwNoTextFrame> contains +/// a graphic, load of intrinsic graphic has to be avoided. +bool SwFlyFrame::GetContour( tools::PolyPolygon& rContour, + const bool _bForPaint ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + bool bRet = false; + const bool bIsCandidate(Lower() && Lower()->IsNoTextFrame()); + + if(bIsCandidate) + { + if(GetFormat()->GetSurround().IsContour()) + { + SwNoTextNode *pNd = const_cast<SwNoTextNode*>(static_cast<const SwNoTextNode*>(static_cast<const SwNoTextFrame*>(Lower())->GetNode())); + // #i13147# - determine <GraphicObject> instead of <Graphic> + // in order to avoid load of graphic, if <SwNoTextNode> contains a graphic + // node and method is called for paint. + std::unique_ptr<GraphicObject> xTmpGrfObj; + const GraphicObject* pGrfObj = nullptr; + const SwGrfNode* pGrfNd = pNd->GetGrfNode(); + if ( pGrfNd && _bForPaint ) + { + pGrfObj = &(pGrfNd->GetGrfObj()); + } + else + { + xTmpGrfObj.reset(new GraphicObject(pNd->GetGraphic())); + pGrfObj = xTmpGrfObj.get(); + } + assert(pGrfObj && "SwFlyFrame::GetContour() - No Graphic/GraphicObject found at <SwNoTextNode>."); + if (pGrfObj->GetType() != GraphicType::NONE) + { + if( !pNd->HasContour() ) + { + //#i13147# - no <CreateContour> for a graphic + // during paint. Thus, return (value of <bRet> should be <false>). + if ( pGrfNd && _bForPaint ) + { + OSL_FAIL( "SwFlyFrame::GetContour() - No Contour found at <SwNoTextNode> during paint." ); + return bRet; + } + pNd->CreateContour(); + } + pNd->GetContour( rContour ); + // The Node holds the Polygon matching the original size of the graphic + // We need to include the scaling here + SwRect aClip; + SwRect aOrig; + Lower()->Calc(pRenderContext); + static_cast<const SwNoTextFrame*>(Lower())->GetGrfArea( aClip, &aOrig ); + // #i13147# - copy method code <SvxContourDlg::ScaleContour(..)> + // in order to avoid that graphic has to be loaded for contour scale. + //SvxContourDlg::ScaleContour( rContour, aGrf, MapUnit::MapTwip, aOrig.SSize() ); + { + OutputDevice* pOutDev = Application::GetDefaultDevice(); + const MapMode aDispMap( MapUnit::MapTwip ); + const MapMode aGrfMap( pGrfObj->GetPrefMapMode() ); + const Size aGrfSize( pGrfObj->GetPrefSize() ); + Size aOrgSize; + Point aNewPoint; + bool bPixelMap = aGrfMap.GetMapUnit() == MapUnit::MapPixel; + + if ( bPixelMap ) + aOrgSize = pOutDev->PixelToLogic( aGrfSize, aDispMap ); + else + aOrgSize = OutputDevice::LogicToLogic( aGrfSize, aGrfMap, aDispMap ); + + if ( aOrgSize.Width() && aOrgSize.Height() ) + { + double fScaleX = static_cast<double>(aOrig.Width()) / aOrgSize.Width(); + double fScaleY = static_cast<double>(aOrig.Height()) / aOrgSize.Height(); + + for ( sal_uInt16 j = 0, nPolyCount = rContour.Count(); j < nPolyCount; j++ ) + { + tools::Polygon& rPoly = rContour[ j ]; + + for ( sal_uInt16 i = 0, nCount = rPoly.GetSize(); i < nCount; i++ ) + { + if ( bPixelMap ) + aNewPoint = pOutDev->PixelToLogic( rPoly[ i ], aDispMap ); + else + aNewPoint = OutputDevice::LogicToLogic( rPoly[ i ], aGrfMap, aDispMap ); + + rPoly[ i ] = Point( FRound( aNewPoint.getX() * fScaleX ), FRound( aNewPoint.getY() * fScaleY ) ); + } + } + } + } + // destroy created <GraphicObject>. + xTmpGrfObj.reset(); + rContour.Move( aOrig.Left(), aOrig.Top() ); + if( !aClip.Width() ) + aClip.Width( 1 ); + if( !aClip.Height() ) + aClip.Height( 1 ); + rContour.Clip( aClip.SVRect() ); + rContour.Optimize(PolyOptimizeFlags::CLOSE); + bRet = true; + } + } + else + { + const SwFlyFreeFrame* pSwFlyFreeFrame(dynamic_cast< const SwFlyFreeFrame* >(this)); + + if(nullptr != pSwFlyFreeFrame && + pSwFlyFreeFrame->supportsAutoContour() && + // isTransformableSwFrame already used in supportsAutoContour(), but + // better check twice when it may get changed there... + pSwFlyFreeFrame->isTransformableSwFrame()) + { + // RotateFlyFrame: use untransformed SwFrame to allow text floating around. + // Will be transformed below + const TransformableSwFrame* pTransformableSwFrame(pSwFlyFreeFrame->getTransformableSwFrame()); + const SwRect aFrameArea(pTransformableSwFrame->getUntransformedFrameArea()); + rContour = tools::PolyPolygon(tools::Polygon(aFrameArea.SVRect())); + bRet = (0 != rContour.Count()); + } + } + + if(bRet && 0 != rContour.Count()) + { + const SwFlyFreeFrame* pSwFlyFreeFrame(dynamic_cast< const SwFlyFreeFrame* >(this)); + + if(nullptr != pSwFlyFreeFrame && pSwFlyFreeFrame->isTransformableSwFrame()) + { + // Need to adapt contour to transformation + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getFrameAreaTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + if(!basegfx::fTools::equalZero(fRotate)) + { + basegfx::B2DPolyPolygon aSource(rContour.getB2DPolyPolygon()); + const basegfx::B2DPoint aCenter(getFrameAreaTransformation() * basegfx::B2DPoint(0.5, 0.5)); + const basegfx::B2DHomMatrix aRotateAroundCenter( + basegfx::utils::createRotateAroundPoint( + aCenter.getX(), + aCenter.getY(), + fRotate)); + aSource.transform(aRotateAroundCenter); + rContour = tools::PolyPolygon(aSource); + } + } + } + } + + return bRet; +} + + +const SwVirtFlyDrawObj* SwFlyFrame::GetVirtDrawObj() const +{ + return static_cast<const SwVirtFlyDrawObj*>(GetDrawObj()); +} +SwVirtFlyDrawObj* SwFlyFrame::GetVirtDrawObj() +{ + return static_cast<SwVirtFlyDrawObj*>(DrawObj()); +} + +// implementation of pure virtual method declared in +// base class <SwAnchoredObject> + +void SwFlyFrame::InvalidateObjPos() +{ + InvalidatePos(); + // #i68520# + InvalidateObjRectWithSpaces(); +} + +SwFrameFormat& SwFlyFrame::GetFrameFormat() +{ + OSL_ENSURE( GetFormat(), + "<SwFlyFrame::GetFrameFormat()> - missing frame format -> crash." ); + return *GetFormat(); +} +const SwFrameFormat& SwFlyFrame::GetFrameFormat() const +{ + OSL_ENSURE( GetFormat(), + "<SwFlyFrame::GetFrameFormat()> - missing frame format -> crash." ); + return *GetFormat(); +} + +SwRect SwFlyFrame::GetObjRect() const +{ + return getFrameArea(); +} + +// #i70122# +// for Writer fly frames the bounding rectangle equals the object rectangles +SwRect SwFlyFrame::GetObjBoundRect() const +{ + return GetObjRect(); +} + +// #i68520# +bool SwFlyFrame::SetObjTop_( const SwTwips _nTop ) +{ + const bool bChanged( getFrameArea().Pos().getY() != _nTop ); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setY(_nTop); + + return bChanged; +} +bool SwFlyFrame::SetObjLeft_( const SwTwips _nLeft ) +{ + const bool bChanged( getFrameArea().Pos().getX() != _nLeft ); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(_nLeft); + + return bChanged; +} + +/** method to assure that anchored object is registered at the correct + page frame + + OD 2004-07-02 #i28701# +*/ +void SwFlyFrame::RegisterAtCorrectPage() +{ + // default behaviour is to do nothing. +} + +/** method to determine, if a <MakeAll()> on the Writer fly frame is possible + + OD 2004-05-11 #i28701# +*/ +bool SwFlyFrame::IsFormatPossible() const +{ + return SwAnchoredObject::IsFormatPossible() && + !IsLocked() && !IsColLocked(); +} + +void SwFlyFrame::GetAnchoredObjects( std::vector<SwAnchoredObject*>& aVector, const SwFormat& rFormat ) +{ + SwIterator<SwFlyFrame,SwFormat> aIter( rFormat ); + for( SwFlyFrame* pFlyFrame = aIter.First(); pFlyFrame; pFlyFrame = aIter.Next() ) + aVector.push_back( pFlyFrame ); +} + +const SwFlyFrameFormat * SwFlyFrame::GetFormat() const +{ + return static_cast< const SwFlyFrameFormat * >( GetDep() ); +} + +SwFlyFrameFormat * SwFlyFrame::GetFormat() +{ + return static_cast< SwFlyFrameFormat * >( GetDep() ); +} + +void SwFlyFrame::Calc(vcl::RenderContext* pRenderContext) const +{ + if ( !m_bValidContentPos ) + const_cast<SwFlyFrame*>(this)->PrepareMake(pRenderContext); + else + SwLayoutFrame::Calc(pRenderContext); +} + +SwTwips SwFlyFrame::CalcContentHeight(const SwBorderAttrs *pAttrs, const SwTwips nMinHeight, const SwTwips nUL) +{ + SwRectFnSet aRectFnSet(this); + SwTwips nHeight = 0; + if ( Lower() ) + { + if ( Lower()->IsColumnFrame() ) + { + FormatWidthCols( *pAttrs, nUL, nMinHeight ); + nHeight = aRectFnSet.GetHeight(Lower()->getFrameArea()); + } + else + { + SwFrame *pFrame = Lower(); + while ( pFrame ) + { + nHeight += aRectFnSet.GetHeight(pFrame->getFrameArea()); + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + // This TextFrame would like to be a bit larger + nHeight += static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - aRectFnSet.GetHeight(pFrame->getFramePrintArea()); + else if( pFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + nHeight += static_cast<SwSectionFrame*>(pFrame)->Undersize(); + pFrame = pFrame->GetNext(); + } + } + if ( GetDrawObjs() ) + { + const size_t nCnt = GetDrawObjs()->size(); + SwTwips nTop = aRectFnSet.GetTop(getFrameArea()); + SwTwips nBorder = aRectFnSet.GetHeight(getFrameArea()) - + aRectFnSet.GetHeight(getFramePrintArea()); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i]; + if ( dynamic_cast<const SwFlyFrame*>( pAnchoredObj) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + // consider only Writer fly frames, which follow the text flow. + if ( pFly->IsFlyLayFrame() && + pFly->getFrameArea().Top() != FAR_AWAY && + pFly->GetFormat()->GetFollowTextFlow().GetValue() ) + { + SwTwips nDist = -aRectFnSet.BottomDist( pFly->getFrameArea(), nTop ); + if( nDist > nBorder + nHeight ) + nHeight = nDist - nBorder; + } + } + } + } + } + return nHeight; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx new file mode 100644 index 000000000..71f6dff77 --- /dev/null +++ b/sw/source/core/layout/flycnt.cxx @@ -0,0 +1,1463 @@ +/* -*- 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 <sal/log.hxx> +#include <svx/swframetypes.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <frmtool.hxx> +#include <dflyobj.hxx> +#include <hints.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <txatbase.hxx> + +#include <tabfrm.hxx> +#include <flyfrms.hxx> +#include <crstate.hxx> +#include <sectfrm.hxx> + +#include <tocntntanchoredobjectposition.hxx> +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include "objectformattertxtfrm.hxx" +#include <HandleAnchorNodeChg.hxx> +#include <ndtxt.hxx> + +using namespace ::com::sun::star; + +namespace +{ + +SwTwips lcl_GetTopForObjPos(const SwContentFrame* pCnt, const bool bVert, const bool bVertL2R) +{ + if ( bVert ) + { + SwTwips aResult = pCnt->getFrameArea().Left(); + if ( bVertL2R ) + aResult += pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + else + aResult += pCnt->getFrameArea().Width() - pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + return aResult; + } + else + return pCnt->getFrameArea().Top() + pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); +} + +} + +SwFlyAtContentFrame::SwFlyAtContentFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwFlyFreeFrame( pFormat, pSib, pAnch ) +{ + m_bAtCnt = true; + m_bAutoPosition = (RndStdIds::FLY_AT_CHAR == pFormat->GetAnchor().GetAnchorId()); +} + +// #i28701# + +void SwFlyAtContentFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + const SwFormatAnchor *pAnch = nullptr; + + if (pNew) + { + const sal_uInt16 nWhich = pNew->Which(); + if( RES_ATTRSET_CHG == nWhich && SfxItemState::SET == + static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( RES_ANCHOR, false, + reinterpret_cast<const SfxPoolItem**>(&pAnch) )) + ; // The anchor pointer is set at GetItemState! + + else if( RES_ANCHOR == nWhich ) + { + //Change anchor, I move myself to a new place. + //The anchor type must not change, this is only possible using + //SwFEShell. + pAnch = static_cast<const SwFormatAnchor*>(pNew); + } + } + + if( pAnch ) + { + OSL_ENSURE( pAnch->GetAnchorId() == GetFormat()->GetAnchor().GetAnchorId(), + "Illegal change of anchor type. " ); + + //Unregister, get hold of a new anchor and attach it + SwRect aOld( GetObjRectWithSpaces() ); + SwPageFrame *pOldPage = FindPageFrame(); + const SwFrame *pOldAnchor = GetAnchorFrame(); + SwContentFrame *pContent = const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(GetAnchorFrame())); + AnchorFrame()->RemoveFly( this ); + + const bool bBodyFootnote = (pContent->IsInDocBody() || pContent->IsInFootnote()); + + // Search the new anchor using the NodeIdx; the relation between old + // and new NodeIdx determines the search direction + const SwNodeIndex aNewIdx( pAnch->GetContentAnchor()->nNode ); + SwNodeIndex aOldIdx( pContent->IsTextFrame() + // sw_redlinehide: can pick any node here, the compare with + // FrameContainsNode should catch it + ? *static_cast<SwTextFrame *>(pContent)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame *>(pContent)->GetNode() ); + + //fix: depending on which index was smaller, searching in the do-while + //loop previously was done forward or backwards respectively. This however + //could lead to an infinite loop. To at least avoid the loop, searching + //is now done in only one direction. Getting hold of a frame from the node + //is still possible if the new anchor could not be found. Chances are + //good that this will be the correct one. + // consider the case that at found anchor frame candidate already a + // fly frame of the given fly format is registered. + // consider, that <pContent> is the already + // the new anchor frame. + bool bFound( FrameContainsNode(*pContent, aNewIdx.GetIndex()) ); + const bool bNext = !bFound && aOldIdx < aNewIdx; + while ( pContent && !bFound ) + { + do + { + if ( bNext ) + pContent = pContent->GetNextContentFrame(); + else + pContent = pContent->GetPrevContentFrame(); + } while ( pContent && + ( bBodyFootnote != ( pContent->IsInDocBody() || + pContent->IsInFootnote() ) ) ); + if ( pContent ) + bFound = FrameContainsNode(*pContent, aNewIdx.GetIndex()); + + // check, if at found anchor frame candidate already a fly frame + // of the given fly frame format is registered. + if (bFound && pContent && pContent->GetDrawObjs()) + { + SwFrameFormat* pMyFlyFrameFormat( &GetFrameFormat() ); + SwSortedObjs &rObjs = *pContent->GetDrawObjs(); + for(SwAnchoredObject* rObj : rObjs) + { + SwFlyFrame* pFlyFrame = dynamic_cast<SwFlyFrame*>(rObj); + if ( pFlyFrame && + &(pFlyFrame->GetFrameFormat()) == pMyFlyFrameFormat ) + { + bFound = false; + break; + } + } + } + } + if ( !pContent ) + { + SwContentNode *pNode = aNewIdx.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(pOldAnchor->getFrameArea().Pos(), false); + pContent = pNode->getLayoutFrame(getRootFrame(), nullptr, &tmp); + OSL_ENSURE( pContent, "New anchor not found" ); + } + //Flys are never attached to a follow, but always on the master which + //we are going to search now. + SwContentFrame* pFlow = pContent; + while ( pFlow->IsFollow() ) + pFlow = pFlow->FindMaster(); + pContent = pFlow; + + //and *puff* it's attached... + pContent->AppendFly( this ); + if ( pOldPage && pOldPage != FindPageFrame() ) + NotifyBackground( pOldPage, aOld, PrepareHint::FlyFrameLeave ); + + //Fix(3495) + InvalidatePos_(); + InvalidatePage(); + SetNotifyBack(); + // #i28701# - reset member <maLastCharRect> and + // <mnLastTopOfLine> for to-character anchored objects. + ClearCharRectAndTopOfLine(); + } + else + SwFlyFrame::Modify( pOld, pNew ); +} + +//We need some helper classes to monitor the oscillation and a few functions +//to not get lost. + +namespace { + +// #i3317# - re-factoring of the position stack +class SwOszControl +{ + static const SwFlyFrame *pStack1; + static const SwFlyFrame *pStack2; + static const SwFlyFrame *pStack3; + static const SwFlyFrame *pStack4; + static const SwFlyFrame *pStack5; + + const SwFlyFrame *pFly; + std::vector<Point> maObjPositions; + +public: + explicit SwOszControl( const SwFlyFrame *pFrame ); + ~SwOszControl(); + bool ChkOsz(); + static bool IsInProgress( const SwFlyFrame *pFly ); +}; + +} + +const SwFlyFrame *SwOszControl::pStack1 = nullptr; +const SwFlyFrame *SwOszControl::pStack2 = nullptr; +const SwFlyFrame *SwOszControl::pStack3 = nullptr; +const SwFlyFrame *SwOszControl::pStack4 = nullptr; +const SwFlyFrame *SwOszControl::pStack5 = nullptr; + +SwOszControl::SwOszControl( const SwFlyFrame *pFrame ) + : pFly( pFrame ) +{ + if ( !SwOszControl::pStack1 ) + SwOszControl::pStack1 = pFly; + else if ( !SwOszControl::pStack2 ) + SwOszControl::pStack2 = pFly; + else if ( !SwOszControl::pStack3 ) + SwOszControl::pStack3 = pFly; + else if ( !SwOszControl::pStack4 ) + SwOszControl::pStack4 = pFly; + else if ( !SwOszControl::pStack5 ) + SwOszControl::pStack5 = pFly; +} + +SwOszControl::~SwOszControl() +{ + if ( SwOszControl::pStack1 == pFly ) + SwOszControl::pStack1 = nullptr; + else if ( SwOszControl::pStack2 == pFly ) + SwOszControl::pStack2 = nullptr; + else if ( SwOszControl::pStack3 == pFly ) + SwOszControl::pStack3 = nullptr; + else if ( SwOszControl::pStack4 == pFly ) + SwOszControl::pStack4 = nullptr; + else if ( SwOszControl::pStack5 == pFly ) + SwOszControl::pStack5 = nullptr; + // #i3317# + maObjPositions.clear(); +} + +bool SwOszControl::IsInProgress( const SwFlyFrame *pFly ) +{ + if ( SwOszControl::pStack1 && !pFly->IsLowerOf( SwOszControl::pStack1 ) ) + return true; + if ( SwOszControl::pStack2 && !pFly->IsLowerOf( SwOszControl::pStack2 ) ) + return true; + if ( SwOszControl::pStack3 && !pFly->IsLowerOf( SwOszControl::pStack3 ) ) + return true; + if ( SwOszControl::pStack4 && !pFly->IsLowerOf( SwOszControl::pStack4 ) ) + return true; + if ( SwOszControl::pStack5 && !pFly->IsLowerOf( SwOszControl::pStack5 ) ) + return true; + return false; +} + +bool SwOszControl::ChkOsz() +{ + bool bOscillationDetected = false; + + if ( maObjPositions.size() == 20 ) + { + // #i3317# position stack is full -> oscillation + bOscillationDetected = true; + } + else + { + Point aNewObjPos = pFly->GetObjRect().Pos(); + for ( auto const & pt : maObjPositions ) + { + if ( aNewObjPos == pt ) + { + // position already occurred -> oscillation + bOscillationDetected = true; + break; + } + } + if ( !bOscillationDetected ) + { + maObjPositions.push_back( aNewObjPos ); + } + } + + return bOscillationDetected; +} + +/** +|* With a paragraph-anchored fly it's absolutely possible that +|* the anchor reacts to changes of the fly. To this reaction the fly must +|* certainly react too. Sadly this can lead to oscillations; for example the +|* fly wants to go down therefore the content can go up - this leads to a +|* smaller TextFrame thus the fly needs to go up again whereby the text will +|* get pushed down... +|* To avoid such oscillations, a small position stack is built. If the fly +|* reaches a position which it already had once, the action is stopped. +|* To not run into problems, the stack is designed to hold five positions. +|* If the stack flows over, the action is stopped too. +|* Cancellation leads to the situation that the fly has a bad position in +|* the end. In case of cancellation, the frame is set to automatic top +|* alignment to not trigger a 'big oscillation' when calling from outside +|* again. +|*/ +void SwFlyAtContentFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( !GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ) + { + return; + } + + if ( !SwOszControl::IsInProgress( this ) && !IsLocked() && !IsColLocked() ) + { + // #i28701# - use new method <GetPageFrame()> + if( !GetPageFrame() && GetAnchorFrame() && GetAnchorFrame()->IsInFly() ) + { + SwFlyFrame* pFly = AnchorFrame()->FindFlyFrame(); + SwPageFrame *pTmpPage = pFly ? pFly->FindPageFrame() : nullptr; + if( pTmpPage ) + pTmpPage->AppendFlyToPage( this ); + } + // #i28701# - use new method <GetPageFrame()> + if( GetPageFrame() ) + { + bSetCompletePaintOnInvalidate = true; + { + SwFlyFrameFormat *pFormat = GetFormat(); + const SwFormatFrameSize &rFrameSz = GetFormat()->GetFrameSize(); + if( rFrameSz.GetHeightPercent() != SwFormatFrameSize::SYNCED && + rFrameSz.GetHeightPercent() >= 100 ) + { + pFormat->LockModify(); + SwFormatSurround aMain( pFormat->GetSurround() ); + if ( aMain.GetSurround() == css::text::WrapTextMode_NONE ) + { + aMain.SetSurround( css::text::WrapTextMode_THROUGH ); + pFormat->SetFormatAttr( aMain ); + } + pFormat->UnlockModify(); + } + } + + SwOszControl aOszCntrl( this ); + + // #i43255# + // #i50356# - format the anchor frame, which + // contains the anchor position. E.g., for at-character anchored + // object this can be the follow frame of the anchor frame. + const bool bFormatAnchor = + !static_cast<const SwTextFrame*>( GetAnchorFrameContainingAnchPos() )->IsAnyJoinLocked() && + !ConsiderObjWrapInfluenceOnObjPos() && + !ConsiderObjWrapInfluenceOfOtherObjs(); + + const SwFrame* pFooter = GetAnchorFrame()->FindFooterOrHeader(); + if( pFooter && !pFooter->IsFooterFrame() ) + pFooter = nullptr; + bool bOsz = false; + bool bExtra = Lower() && Lower()->IsColumnFrame(); + // #i3317# - boolean, to apply temporarily the + // 'straightforward positioning process' for the frame due to its + // overlapping with a previous column. + bool bConsiderWrapInfluenceDueToOverlapPrevCol( false ); + // #i35911# - boolean, to apply temporarily the + // 'straightforward positioning process' for the frame due to fact + // that it causes the complete content of its layout environment + // to move forward. + // #i40444# - extend usage of this boolean: + // apply temporarily the 'straightforward positioning process' for + // the frame due to the fact that the frame clears the area for + // the anchor frame, thus it has to move forward. + bool bConsiderWrapInfluenceDueToMovedFwdAnchor( false ); + do { + SwRectFnSet aRectFnSet(this); + Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); + SwFlyFreeFrame::MakeAll(pRenderContext); + const bool bPosChgDueToOwnFormat = + aOldPos != aRectFnSet.GetPos(getFrameArea()); + // #i3317# + if ( !ConsiderObjWrapInfluenceOnObjPos() && + OverlapsPrevColumn() ) + { + bConsiderWrapInfluenceDueToOverlapPrevCol = true; + } + // #i28701# - no format of anchor frame, if + // wrapping style influence is considered on object positioning + if ( bFormatAnchor ) + { + SwTextFrame& rAnchPosAnchorFrame = + dynamic_cast<SwTextFrame&>(*GetAnchorFrameContainingAnchPos()); + // #i58182# - For the usage of new method + // <SwObjectFormatterTextFrame::CheckMovedFwdCondition(..)> + // to check move forward of anchor frame due to the object + // positioning it's needed to know, if the object is anchored + // at the master frame before the anchor frame is formatted. + const bool bAnchoredAtMaster(!rAnchPosAnchorFrame.IsFollow()); + + // #i56300# + // perform complete format of anchor text frame and its + // previous frames, which have become invalid due to the + // fly frame format. + SwObjectFormatterTextFrame::FormatAnchorFrameAndItsPrevs( rAnchPosAnchorFrame ); + // #i35911# + // #i40444# + // #i58182# - usage of new method + // <SwObjectFormatterTextFrame::CheckMovedFwdCondition(..)> + sal_uInt32 nToPageNum( 0 ); + bool bDummy( false ); + if ( SwObjectFormatterTextFrame::CheckMovedFwdCondition( + *this, GetPageFrame()->GetPhyPageNum(), + bAnchoredAtMaster, nToPageNum, bDummy ) ) + { + bConsiderWrapInfluenceDueToMovedFwdAnchor = true; + // mark anchor text frame + // directly, that it is moved forward by object positioning. + SwTextFrame* pAnchorTextFrame( static_cast<SwTextFrame*>(AnchorFrame()) ); + bool bInsert( true ); + sal_uInt32 nAnchorFrameToPageNum( 0 ); + const SwDoc& rDoc = *(GetFrameFormat().GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, *pAnchorTextFrame, nAnchorFrameToPageNum ) ) + { + if ( nAnchorFrameToPageNum < nToPageNum ) + SwLayouter::RemoveMovedFwdFrame( rDoc, *pAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + SwLayouter::InsertMovedFwdFrame( rDoc, *pAnchorTextFrame, + nToPageNum ); + } + } + } + + if ( aOldPos != aRectFnSet.GetPos(getFrameArea()) || + ( !isFrameAreaPositionValid() && + ( pFooter || bPosChgDueToOwnFormat ) ) ) + { + bOsz = aOszCntrl.ChkOsz(); + + // special loop prevention for dedicated document: + if ( bOsz && + HasFixSize() && IsClipped() && + GetAnchorFrame()->GetUpper()->IsCellFrame() ) + { + SwFrameFormat* pFormat = GetFormat(); + const SwFormatFrameSize& rFrameSz = pFormat->GetFrameSize(); + if ( rFrameSz.GetWidthPercent() && + rFrameSz.GetHeightPercent() == SwFormatFrameSize::SYNCED ) + { + SwFormatSurround aSurround( pFormat->GetSurround() ); + if ( aSurround.GetSurround() == css::text::WrapTextMode_NONE ) + { + pFormat->LockModify(); + aSurround.SetSurround( css::text::WrapTextMode_THROUGH ); + pFormat->SetFormatAttr( aSurround ); + pFormat->UnlockModify(); + bOsz = false; + OSL_FAIL( "<SwFlyAtContentFrame::MakeAll()> - special loop prevention for dedicated document of b6403541 applied" ); + } + } + } + } + + if ( bExtra && Lower() && !Lower()->isFrameAreaPositionValid() ) + { + // If a multi column frame leaves invalid columns because of + // a position change, we loop once more and format + // our content using FormatWidthCols again. + InvalidateSize_(); + bExtra = false; // Ensure only one additional loop run + } + } while ( !isFrameAreaDefinitionValid() && !bOsz && + // #i3317# + !bConsiderWrapInfluenceDueToOverlapPrevCol && + // #i40444# + !bConsiderWrapInfluenceDueToMovedFwdAnchor && + GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ); + + // #i3317# - instead of attribute change apply + // temporarily the 'straightforward positioning process'. + // #i80924# + // handle special case during splitting of table rows + if ( bConsiderWrapInfluenceDueToMovedFwdAnchor && + GetAnchorFrame()->IsInTab() && + GetAnchorFrame()->IsInFollowFlowRow() ) + { + const SwFrame* pCellFrame = GetAnchorFrame(); + while ( pCellFrame && !pCellFrame->IsCellFrame() ) + { + pCellFrame = pCellFrame->GetUpper(); + } + if ( pCellFrame ) + { + SwRectFnSet aRectFnSet(pCellFrame); + if ( aRectFnSet.GetTop(pCellFrame->getFrameArea()) == 0 && + aRectFnSet.GetHeight(pCellFrame->getFrameArea()) == 0 ) + { + bConsiderWrapInfluenceDueToMovedFwdAnchor = false; + } + } + } + if ( bOsz || bConsiderWrapInfluenceDueToOverlapPrevCol || + // #i40444# + bConsiderWrapInfluenceDueToMovedFwdAnchor ) + { + SetTmpConsiderWrapInfluence( true ); + SetRestartLayoutProcess( true ); + SetTmpConsiderWrapInfluenceOfOtherObjs(); + } + bSetCompletePaintOnInvalidate = false; + } + } +} + +/** method to determine, if a <MakeAll()> on the Writer fly frame is possible + + #i28701# +*/ +bool SwFlyAtContentFrame::IsFormatPossible() const +{ + return SwFlyFreeFrame::IsFormatPossible() && + !SwOszControl::IsInProgress( this ); +} + +namespace { + +class SwDistance +{ +public: + SwTwips nMain, nSub; + SwDistance() : nMain(0), nSub(0) { } + bool operator<( const SwDistance& rTwo ) const + { return nMain < rTwo.nMain || ( nMain == rTwo.nMain && nSub && + rTwo.nSub && nSub < rTwo.nSub ); } + bool operator<=( const SwDistance& rTwo ) const + { return nMain < rTwo.nMain || ( nMain == rTwo.nMain && ( !nSub || + !rTwo.nSub || nSub <= rTwo.nSub ) ); } +}; + +} + +static const SwFrame * lcl_CalcDownDist( SwDistance &rRet, + const Point &rPt, + const SwContentFrame *pCnt ) +{ + rRet.nSub = 0; + //If the point stays inside the Cnt everything is clear already; the Content + //automatically has a distance of 0. + if ( pCnt->getFrameArea().IsInside( rPt ) ) + { + rRet.nMain = 0; + return pCnt; + } + else + { + const SwLayoutFrame *pUp = pCnt->IsInTab() ? pCnt->FindTabFrame()->GetUpper() : pCnt->GetUpper(); + // single column sections need to interconnect to their upper + while( pUp->IsSctFrame() ) + pUp = pUp->GetUpper(); + const bool bVert = pUp->IsVertical(); + + const bool bVertL2R = pUp->IsVertLR(); + + //Follow the text flow. + // #i70582# + // --> OD 2009-03-05 - adopted for Support for Classical Mongolian Script + const SwTwips nTopForObjPos = lcl_GetTopForObjPos(pCnt, bVert, bVertL2R); + if ( pUp->getFrameArea().IsInside( rPt ) ) + { + // <rPt> point is inside environment of given content frame + // #i70582# + if( bVert ) + { + if ( bVertL2R ) + rRet.nMain = rPt.X() - nTopForObjPos; + else + rRet.nMain = nTopForObjPos - rPt.X(); + } + else + rRet.nMain = rPt.Y() - nTopForObjPos; + return pCnt; + } + else if ( rPt.Y() <= pUp->getFrameArea().Top() ) + { + // <rPt> point is above environment of given content frame + // correct for vertical layout? + rRet.nMain = LONG_MAX; + } + else if( rPt.X() < pUp->getFrameArea().Left() && + rPt.Y() <= ( bVert ? pUp->getFrameArea().Top() : pUp->getFrameArea().Bottom() ) ) + { + // <rPt> point is left of environment of given content frame + // seems not to be correct for vertical layout!? + const SwFrame *pLay = pUp->GetLeaf( MAKEPAGE_NONE, false, pCnt ); + if( !pLay || + (bVert && (pLay->getFrameArea().Top() + pLay->getFramePrintArea().Bottom()) <rPt.Y())|| + (!bVert && (pLay->getFrameArea().Left() + pLay->getFramePrintArea().Right())<rPt.X()) ) + { + // <rPt> point is in left border of environment + // #i70582# + if( bVert ) + { + if ( bVertL2R ) + rRet.nMain = rPt.X() - nTopForObjPos; + else + rRet.nMain = nTopForObjPos - rPt.X(); + } + else + rRet.nMain = rPt.Y() - nTopForObjPos; + return pCnt; + } + else + rRet.nMain = LONG_MAX; + } + else + { + rRet.nMain = bVert + ? ( bVertL2R + ? ( (pUp->getFrameArea().Left() + pUp->getFramePrintArea().Right()) - nTopForObjPos ) + : ( nTopForObjPos - (pUp->getFrameArea().Left() + pUp->getFramePrintArea().Left() ) ) ) + : ( (pUp->getFrameArea().Top() + pUp->getFramePrintArea().Bottom()) - nTopForObjPos ); + + const SwFrame *pPre = pCnt; + const SwFrame *pLay = pUp->GetLeaf( MAKEPAGE_NONE, true, pCnt ); + SwTwips nFrameTop = 0; + SwTwips nPrtHeight = 0; + bool bSct = false; + const SwSectionFrame *pSect = pUp->FindSctFrame(); + if( pSect ) + { + rRet.nSub = rRet.nMain; + rRet.nMain = 0; + } + if( pSect && !pSect->IsAnLower( pLay ) ) + { + bSct = false; + const SwSectionFrame* pNxtSect = pLay ? pLay->FindSctFrame() : nullptr; + if (pSect->IsAnFollow(pNxtSect) && pLay) + { + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + nFrameTop = pLay->getFrameArea().Left(); + else + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + pSect = pNxtSect; + } + else + { + pLay = pSect->GetUpper(); + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pSect->getFrameArea().Right(); + nPrtHeight = pLay->getFrameArea().Left() + pLay->getFramePrintArea().Left() + + pLay->getFramePrintArea().Width() - pSect->getFrameArea().Left() + - pSect->getFrameArea().Width(); + } + else + { + nFrameTop = pSect->getFrameArea().Left(); + nPrtHeight = pSect->getFrameArea().Left() - pLay->getFrameArea().Left() + - pLay->getFramePrintArea().Left(); + } + } + else + { + nFrameTop = pSect->getFrameArea().Bottom(); + nPrtHeight = pLay->getFrameArea().Top() + pLay->getFramePrintArea().Top() + + pLay->getFramePrintArea().Height() - pSect->getFrameArea().Top() + - pSect->getFrameArea().Height(); + } + pSect = nullptr; + } + } + else if( pLay ) + { + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pLay->getFrameArea().Left(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + bSct = nullptr != pSect; + } + while ( pLay && !pLay->getFrameArea().IsInside( rPt ) && + ( pLay->getFrameArea().Top() <= rPt.Y() || pLay->IsInFly() || + ( pLay->IsInSct() && + pLay->FindSctFrame()->GetUpper()->getFrameArea().Top() <= rPt.Y())) ) + { + if ( pLay->IsFootnoteContFrame() ) + { + if ( !static_cast<const SwLayoutFrame*>(pLay)->Lower() ) + { + SwFrame *pDel = const_cast<SwFrame*>(pLay); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + return pPre; + } + return nullptr; + } + else + { + if( bSct || pSect ) + rRet.nSub += nPrtHeight; + else + rRet.nMain += nPrtHeight; + pPre = pLay; + pLay = pLay->GetLeaf( MAKEPAGE_NONE, true, pCnt ); + if( pSect && !pSect->IsAnLower( pLay ) ) + { // If we're leaving a SwSectionFrame, the next Leaf-Frame + // is the part of the upper below the SectionFrame. + const SwSectionFrame* pNxtSect = pLay ? + pLay->FindSctFrame() : nullptr; + bSct = false; + if (pLay && pSect->IsAnFollow(pNxtSect)) + { + pSect = pNxtSect; + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pLay->getFrameArea().Left(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + } + else + { + pLay = pSect->GetUpper(); + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pSect->getFrameArea().Right(); + nPrtHeight = pLay->getFrameArea().Left()+pLay->getFramePrintArea().Left() + + pLay->getFramePrintArea().Width() - pSect->getFrameArea().Left() + - pSect->getFrameArea().Width(); + } + else + { + nFrameTop = pSect->getFrameArea().Left(); + nPrtHeight = pSect->getFrameArea().Left() - + pLay->getFrameArea().Left() - pLay->getFramePrintArea().Left(); + } + } + else + { + nFrameTop = pSect->getFrameArea().Bottom(); + nPrtHeight = pLay->getFrameArea().Top()+pLay->getFramePrintArea().Top() + + pLay->getFramePrintArea().Height() - pSect->getFrameArea().Top() + - pSect->getFrameArea().Height(); + } + pSect = nullptr; + } + } + else if( pLay ) + { + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pLay->getFrameArea().Left(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + bSct = nullptr != pSect; + } + } + } + if ( pLay ) + { + if ( pLay->getFrameArea().IsInside( rPt ) ) + { + SwTwips nDiff = pLay->IsVertical() ? ( pLay->IsVertLR() ? ( rPt.X() - nFrameTop ) : ( nFrameTop - rPt.X() ) ) + : ( rPt.Y() - nFrameTop ); + if( bSct || pSect ) + rRet.nSub += nDiff; + else + rRet.nMain += nDiff; + } + if ( pLay->IsFootnoteContFrame() && !static_cast<const SwLayoutFrame*>(pLay)->Lower() ) + { + SwFrame *pDel = const_cast<SwFrame*>(pLay); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + return nullptr; + } + return pLay; + } + else + rRet.nMain = LONG_MAX; + } + } + return nullptr; +} + +static sal_uInt64 lcl_FindCntDiff( const Point &rPt, const SwLayoutFrame *pLay, + const SwContentFrame *& rpCnt, + const bool bBody, const bool bFootnote ) +{ + // Searches below pLay the nearest Cnt to the point. The reference point of + //the Contents is always the left upper corner. + //The Cnt should preferably be above the point. + + rpCnt = nullptr; + sal_uInt64 nDistance = SAL_MAX_UINT64; + sal_uInt64 nNearest = SAL_MAX_UINT64; + const SwContentFrame *pCnt = pLay ? pLay->ContainsContent() : nullptr; + + while ( pCnt && (bBody != pCnt->IsInDocBody() || bFootnote != pCnt->IsInFootnote())) + { + pCnt = pCnt->GetNextContentFrame(); + if ( !pLay->IsAnLower( pCnt ) ) + pCnt = nullptr; + } + const SwContentFrame *pNearest = pCnt; + if ( pCnt ) + { + do + { + //Calculate the distance between those two points. + //'delta' X^2 + 'delta' Y^2 = 'distance'^2 + sal_uInt64 dX = std::max( pCnt->getFrameArea().Left(), rPt.X() ) - + std::min( pCnt->getFrameArea().Left(), rPt.X() ), + dY = std::max( pCnt->getFrameArea().Top(), rPt.Y() ) - + std::min( pCnt->getFrameArea().Top(), rPt.Y() ); + // square of the difference will do fine here + const sal_uInt64 nDiff = (dX * dX) + (dY * dY); + if ( pCnt->getFrameArea().Top() <= rPt.Y() ) + { + if ( nDiff < nDistance ) + { + //This one is the nearer one + nDistance = nNearest = nDiff; + rpCnt = pNearest = pCnt; + } + } + else if ( nDiff < nNearest ) + { + nNearest = nDiff; + pNearest = pCnt; + } + pCnt = pCnt->GetNextContentFrame(); + while ( pCnt && + (bBody != pCnt->IsInDocBody() || bFootnote != pCnt->IsInFootnote())) + pCnt = pCnt->GetNextContentFrame(); + + } while ( pCnt && pLay->IsAnLower( pCnt ) ); + } + if (nDistance == SAL_MAX_UINT64) + { rpCnt = pNearest; + return nNearest; + } + return nDistance; +} + +static const SwContentFrame * lcl_FindCnt( const Point &rPt, const SwContentFrame *pCnt, + const bool bBody, const bool bFootnote ) +{ + //Starting from pCnt searches the ContentFrame whose left upper corner is the + //nearest to the point. + //Always returns a ContentFrame. + + //First the nearest Content inside the page which contains the Content is + //searched. Starting from this page the pages in both directions need to + //be considered. If possible a Content is returned whose Y-position is + //above the point. + const SwContentFrame *pRet, *pNew; + const SwLayoutFrame *pLay = pCnt->FindPageFrame(); + sal_uInt64 nDist; // not sure if a sal_Int32 would be enough? + + nDist = ::lcl_FindCntDiff( rPt, pLay, pNew, bBody, bFootnote ); + if ( pNew ) + pRet = pNew; + else + { pRet = pCnt; + nDist = SAL_MAX_UINT64; + } + const SwContentFrame *pNearest = pRet; + sal_uInt64 nNearest = nDist; + + if ( pLay ) + { + const SwLayoutFrame *pPge = pLay; + sal_uInt64 nOldNew = SAL_MAX_UINT64; + for ( int i = 0; pPge->GetPrev() && (i < 3); ++i ) + { + pPge = static_cast<const SwLayoutFrame*>(pPge->GetPrev()); + const sal_uInt64 nNew = ::lcl_FindCntDiff( rPt, pPge, pNew, bBody, bFootnote ); + if ( nNew < nDist ) + { + if ( pNew->getFrameArea().Top() <= rPt.Y() ) + { + pRet = pNearest = pNew; + nDist = nNearest = nNew; + } + else if ( nNew < nNearest ) + { + pNearest = pNew; + nNearest = nNew; + } + } + else if (nOldNew != SAL_MAX_UINT64 && nNew > nOldNew) + break; + else + nOldNew = nNew; + + } + pPge = pLay; + nOldNew = SAL_MAX_UINT64; + for ( int j = 0; pPge->GetNext() && (j < 3); ++j ) + { + pPge = static_cast<const SwLayoutFrame*>(pPge->GetNext()); + const sal_uInt64 nNew = ::lcl_FindCntDiff( rPt, pPge, pNew, bBody, bFootnote ); + if ( nNew < nDist ) + { + if ( pNew->getFrameArea().Top() <= rPt.Y() ) + { + pRet = pNearest = pNew; + nDist = nNearest = nNew; + } + else if ( nNew < nNearest ) + { + pNearest = pNew; + nNearest = nNew; + } + } + else if (nOldNew != SAL_MAX_UINT64 && nNew > nOldNew) + break; + else + nOldNew = nNew; + } + } + if ( pRet->getFrameArea().Top() > rPt.Y() ) + return pNearest; + else + return pRet; +} + +static void lcl_PointToPrt( Point &rPoint, const SwFrame *pFrame ) +{ + SwRect aTmp( pFrame->getFramePrintArea() ); + aTmp += pFrame->getFrameArea().Pos(); + if ( rPoint.getX() < aTmp.Left() ) + rPoint.setX(aTmp.Left()); + else if ( rPoint.getX() > aTmp.Right() ) + rPoint.setX(aTmp.Right()); + if ( rPoint.getY() < aTmp.Top() ) + rPoint.setY(aTmp.Top()); + else if ( rPoint.getY() > aTmp.Bottom() ) + rPoint.setY(aTmp.Bottom()); + +} + +/** Searches an anchor for paragraph bound objects starting from pOldAnch. + * + * This is used to show anchors as well as changing anchors + * when dragging paragraph bound objects. + */ +const SwContentFrame *FindAnchor( const SwFrame *pOldAnch, const Point &rNew, + const bool bBodyOnly ) +{ + //Search the nearest Cnt around the given document position in the text + //flow. The given anchor is the starting Frame. + const SwContentFrame* pCnt; + if ( pOldAnch->IsContentFrame() ) + { + pCnt = static_cast<const SwContentFrame*>(pOldAnch); + } + else + { + Point aTmp( rNew ); + const SwLayoutFrame *pTmpLay = static_cast<const SwLayoutFrame*>(pOldAnch); + if( pTmpLay->IsRootFrame() ) + { + SwRect aTmpRect( aTmp, Size(0,0) ); + pTmpLay = static_cast<const SwLayoutFrame*>(::FindPage( aTmpRect, pTmpLay->Lower() )); + } + pCnt = pTmpLay->GetContentPos( aTmp, false, bBodyOnly ); + } + + //Take care to use meaningful ranges during search. This means to not enter + //or leave header/footer in this case. + const bool bBody = pCnt->IsInDocBody() || bBodyOnly; + const bool bFootnote = !bBodyOnly && pCnt->IsInFootnote(); + + Point aNew( rNew ); + if ( bBody ) + { + //#38848 drag from page margin into the body. + const SwFrame *pPage = pCnt->FindPageFrame(); + ::lcl_PointToPrt( aNew, pPage->GetUpper() ); + SwRect aTmp( aNew, Size( 0, 0 ) ); + pPage = ::FindPage( aTmp, pPage ); + ::lcl_PointToPrt( aNew, pPage ); + } + + if ( pCnt->IsInDocBody() == bBody && pCnt->getFrameArea().IsInside( aNew ) ) + return pCnt; + else if ( pOldAnch->IsInDocBody() || pOldAnch->IsPageFrame() ) + { + // Maybe the selected anchor is on the same page as the current anchor. + // With this we won't run into problems with the columns. + Point aTmp( aNew ); + const SwContentFrame *pTmp = pCnt->FindPageFrame()-> + GetContentPos( aTmp, false, true ); + if ( pTmp && pTmp->getFrameArea().IsInside( aNew ) ) + return pTmp; + } + + //Starting from the anchor we now search in both directions until we found + //the nearest one respectively. + //Not the direct distance is relevant but the distance which needs to be + //traveled through the text flow. + const SwContentFrame *pUpLst; + const SwContentFrame *pUpFrame = pCnt; + SwDistance nUp, nUpLst; + ::lcl_CalcDownDist( nUp, aNew, pUpFrame ); + SwDistance nDown = nUp; + bool bNegAllowed = true;// Make it possible to leave the negative section once. + do + { + pUpLst = pUpFrame; nUpLst = nUp; + pUpFrame = pUpLst->GetPrevContentFrame(); + while ( pUpFrame && + (bBody != pUpFrame->IsInDocBody() || bFootnote != pUpFrame->IsInFootnote())) + pUpFrame = pUpFrame->GetPrevContentFrame(); + if ( pUpFrame ) + { + ::lcl_CalcDownDist( nUp, aNew, pUpFrame ); + //It makes sense to search further, if the distance grows inside + //a table. + if ( pUpLst->IsInTab() && pUpFrame->IsInTab() ) + { + while ( pUpFrame && ((nUpLst < nUp && pUpFrame->IsInTab()) || + bBody != pUpFrame->IsInDocBody()) ) + { + pUpFrame = pUpFrame->GetPrevContentFrame(); + if ( pUpFrame ) + ::lcl_CalcDownDist( nUp, aNew, pUpFrame ); + } + } + } + if ( !pUpFrame ) + nUp.nMain = LONG_MAX; + if ( nUp.nMain >= 0 && LONG_MAX != nUp.nMain ) + { + bNegAllowed = false; + if ( nUpLst.nMain < 0 ) //don't take the wrong one, if the value + //just changed from negative to positive. + { pUpLst = pUpFrame; + nUpLst = nUp; + } + } + } while ( pUpFrame && ( ( bNegAllowed && nUp.nMain < 0 ) || ( nUp <= nUpLst ) ) ); + + const SwContentFrame *pDownLst; + const SwContentFrame *pDownFrame = pCnt; + SwDistance nDownLst; + if ( nDown.nMain < 0 ) + nDown.nMain = LONG_MAX; + do + { + pDownLst = pDownFrame; nDownLst = nDown; + pDownFrame = pDownLst->GetNextContentFrame(); + while ( pDownFrame && + (bBody != pDownFrame->IsInDocBody() || bFootnote != pDownFrame->IsInFootnote())) + pDownFrame = pDownFrame->GetNextContentFrame(); + if ( pDownFrame ) + { + ::lcl_CalcDownDist( nDown, aNew, pDownFrame ); + if ( nDown.nMain < 0 ) + nDown.nMain = LONG_MAX; + //It makes sense to search further, if the distance grows inside + //a table. + if ( pDownLst->IsInTab() && pDownFrame->IsInTab() ) + { + while ( pDownFrame && ( ( nDown.nMain != LONG_MAX && pDownFrame->IsInTab()) || bBody != pDownFrame->IsInDocBody() ) ) + { + pDownFrame = pDownFrame->GetNextContentFrame(); + if ( pDownFrame ) + ::lcl_CalcDownDist( nDown, aNew, pDownFrame ); + if ( nDown.nMain < 0 ) + nDown.nMain = LONG_MAX; + } + } + } + if ( !pDownFrame ) + nDown.nMain = LONG_MAX; + + } while ( pDownFrame && nDown <= nDownLst && + nDown.nMain != LONG_MAX && nDownLst.nMain != LONG_MAX ); + + //If we couldn't find one in both directions, we'll search the Content whose + //left upper corner is the nearest to the point. Such a situation may + //happen, if the point doesn't lay in the text flow but in any margin. + if ( nDownLst.nMain == LONG_MAX && nUpLst.nMain == LONG_MAX ) + { + // If an OLE objects, which is contained in a fly frame + // is resized in inplace mode and the new Position is outside the + // fly frame, we do not want to leave our fly frame. + if ( pCnt->IsInFly() ) + return pCnt; + + return ::lcl_FindCnt( aNew, pCnt, bBody, bFootnote ); + } + else + return nDownLst < nUpLst ? pDownLst : pUpLst; +} + +void SwFlyAtContentFrame::SetAbsPos( const Point &rNew ) +{ + SwPageFrame *pOldPage = FindPageFrame(); + const SwRect aOld( GetObjRectWithSpaces() ); + Point aNew( rNew ); + + if( ( GetAnchorFrame()->IsVertical() && !GetAnchorFrame()->IsVertLR() ) || GetAnchorFrame()->IsRightToLeft() ) + aNew.setX(aNew.getX() + getFrameArea().Width()); + SwContentFrame *pCnt = const_cast<SwContentFrame*>(::FindAnchor( GetAnchorFrame(), aNew )); + if( pCnt->IsProtected() ) + pCnt = const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(GetAnchorFrame())); + + SwPageFrame *pTmpPage = nullptr; + const bool bVert = pCnt->IsVertical(); + + const bool bVertL2R = pCnt->IsVertLR(); + const bool bRTL = pCnt->IsRightToLeft(); + + if( ( !bVert != !GetAnchorFrame()->IsVertical() ) || + ( !bRTL != !GetAnchorFrame()->IsRightToLeft() ) ) + { + if( bVert || bRTL ) + aNew.setX(aNew.getX() + getFrameArea().Width()); + else + aNew.setX(aNew.getX() - getFrameArea().Width()); + } + + if ( pCnt->IsInDocBody() ) + { + //#38848 drag from page margin into the body. + pTmpPage = pCnt->FindPageFrame(); + ::lcl_PointToPrt( aNew, pTmpPage->GetUpper() ); + SwRect aTmp( aNew, Size( 0, 0 ) ); + pTmpPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(::FindPage( aTmp, pTmpPage ))); + ::lcl_PointToPrt( aNew, pTmpPage ); + } + + //Setup RelPos, only invalidate if requested. + //rNew is an absolute position. We need to calculate the distance from rNew + //to the anchor inside the text flow to correctly set RelPos. +//!!!!!We can optimize here: FindAnchor could also return RelPos! + const SwFrame *pFrame = nullptr; + SwTwips nY; + if ( pCnt->getFrameArea().IsInside( aNew ) ) + { + // #i70582# + if ( bVert ) + { + nY = pCnt->getFrameArea().Left() - rNew.X(); + if ( bVertL2R ) + nY = -nY; + else + nY += pCnt->getFrameArea().Width() - getFrameArea().Width(); + nY -= pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + } + else + nY = rNew.Y() - pCnt->getFrameArea().Top() - pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + } + else + { + SwDistance aDist; + pFrame = ::lcl_CalcDownDist( aDist, aNew, pCnt ); + nY = aDist.nMain + aDist.nSub; + } + + SwTwips nX = 0; + + if ( pCnt->IsFollow() ) + { + // Flys are never attached to the follow but always to the master, + // which we're going to search now. + const SwContentFrame *pOriginal = pCnt; + const SwContentFrame *pFollow = pCnt; + while ( pCnt->IsFollow() ) + { + do + { + SwContentFrame* pPrev = pCnt->GetPrevContentFrame(); + if (!pPrev) + { + SAL_WARN("sw.core", "very unexpected missing PrevContentFrame"); + break; + } + pCnt = pPrev; + } + while ( pCnt->GetFollow() != pFollow ); + pFollow = pCnt; + } + SwTwips nDiff = 0; + do + { const SwFrame *pUp = pFollow->GetUpper(); + if( pUp->IsVertical() ) + { + if ( pUp->IsVertLR() ) + nDiff += pUp->getFramePrintArea().Width() - pFollow->GetRelPos().getX(); + else + nDiff += pFollow->getFrameArea().Left() + pFollow->getFrameArea().Width() + - pUp->getFrameArea().Left() - pUp->getFramePrintArea().Left(); + } + else + nDiff += pUp->getFramePrintArea().Height() - pFollow->GetRelPos().Y(); + pFollow = pFollow->GetFollow(); + } while ( pFollow != pOriginal ); + nY += nDiff; + if( bVert ) + nX = pCnt->getFrameArea().Top() - pOriginal->getFrameArea().Top(); + else + nX = pCnt->getFrameArea().Left() - pOriginal->getFrameArea().Left(); + } + + if ( nY == LONG_MAX ) + { + // #i70582# + const SwTwips nTopForObjPos = lcl_GetTopForObjPos(pCnt, bVert, bVertL2R); + if( bVert ) + { + if ( bVertL2R ) + nY = rNew.X() - nTopForObjPos; + else + nY = nTopForObjPos - rNew.X(); + } + else + { + nY = rNew.Y() - nTopForObjPos; + } + } + + SwFlyFrameFormat *pFormat = GetFormat(); + + if( bVert ) + { + if( !pFrame ) + nX += rNew.Y() - pCnt->getFrameArea().Top(); + else + nX = rNew.Y() - pFrame->getFrameArea().Top(); + } + else + { + if( !pFrame ) + { + if ( pCnt->IsRightToLeft() ) + nX += pCnt->getFrameArea().Right() - rNew.X() - getFrameArea().Width(); + else + nX += rNew.X() - pCnt->getFrameArea().Left(); + } + else + { + if ( pFrame->IsRightToLeft() ) + nX += pFrame->getFrameArea().Right() - rNew.X() - getFrameArea().Width(); + else + nX = rNew.X() - pFrame->getFrameArea().Left(); + } + } + GetFormat()->GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + if( pCnt != GetAnchorFrame() || ( IsAutoPos() && pCnt->IsTextFrame() && + GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)) ) + { + //Set the anchor attribute according to the new Cnt. + SwFormatAnchor aAnch( pFormat->GetAnchor() ); + SwPosition pos = *aAnch.GetContentAnchor(); + if( IsAutoPos() && pCnt->IsTextFrame() ) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pCnt)); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + Point aPt( rNew ); + if( pCnt->GetModelPositionForViewPoint( &pos, aPt, &eTmpState ) + && FrameContainsNode(*pTextFrame, pos.nNode.GetIndex())) + { + const SwTextAttr *const pTextInputField = + pos.nNode.GetNode().GetTextNode()->GetTextAttrAt( + pos.nContent.GetIndex(), RES_TXTATR_INPUTFIELD, SwTextNode::PARENT ); + if (pTextInputField != nullptr) + { + pos.nContent = pTextInputField->GetStart(); + } + ResetLastCharRectHeight(); + if( text::RelOrientation::CHAR == pFormat->GetVertOrient().GetRelationOrient() ) + nY = LONG_MAX; + if( text::RelOrientation::CHAR == pFormat->GetHoriOrient().GetRelationOrient() ) + nX = LONG_MAX; + } + else + { + pos = pTextFrame->MapViewToModelPos(TextFrameIndex(0)); + } + } + else if (pCnt->IsTextFrame()) + { + pos = static_cast<SwTextFrame const*>(pCnt)->MapViewToModelPos(TextFrameIndex(0)); + } + else // is that even possible? maybe if there was a change of anchor type from AT_FLY or something? + { + assert(pCnt->IsNoTextFrame()); + pos.nNode = *static_cast<SwNoTextFrame*>(pCnt)->GetNode(); + pos.nContent.Assign(static_cast<SwNoTextFrame*>(pCnt)->GetNode(), 0); + } + aAnch.SetAnchor( &pos ); + + // handle change of anchor node: + // if count of the anchor frame also change, the fly frames have to be + // re-created. Thus, delete all fly frames except the <this> before the + // anchor attribute is change and re-create them afterwards. + { + SwHandleAnchorNodeChg aHandleAnchorNodeChg( *pFormat, aAnch, this ); + pFormat->GetDoc()->SetAttr( aAnch, *pFormat ); + } + } + else if ( pTmpPage && pTmpPage != GetPageFrame() ) + GetPageFrame()->MoveFly( this, pTmpPage ); + + const Point aRelPos = bVert ? Point( -nY, nX ) : Point( nX, nY ); + ChgRelPos( aRelPos ); + GetFormat()->GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + + if ( pOldPage != FindPageFrame() ) + ::Notify_Background( GetVirtDrawObj(), pOldPage, aOld, PrepareHint::FlyFrameLeave, false ); +} + +/** method to assure that anchored object is registered at the correct + page frame + + #i28701# + takes over functionality of deleted method <SwFlyAtContentFrame::AssertPage()> +*/ +void SwFlyAtContentFrame::RegisterAtCorrectPage() +{ + SwPageFrame* pPageFrame( nullptr ); + if ( GetVertPosOrientFrame() ) + { + pPageFrame = const_cast<SwPageFrame*>(GetVertPosOrientFrame()->FindPageFrame()); + } + if ( pPageFrame && GetPageFrame() != pPageFrame ) + { + if ( GetPageFrame() ) + GetPageFrame()->MoveFly( this, pPageFrame ); + else + pPageFrame->AppendFlyToPage( this ); + } +} + +// #i26791# +void SwFlyAtContentFrame::MakeObjPos() +{ + // if fly frame position is valid, nothing is to do. Thus, return + if ( isFrameAreaPositionValid() ) + { + return; + } + + // #i26791# - validate position flag here. + setFrameAreaPositionValid(true); + + // #i35911# - no calculation of new position, if + // anchored object is marked that it clears its environment and its + // environment is already cleared. + // before checking for cleared environment + // check, if member <mpVertPosOrientFrame> is set. + if ( GetVertPosOrientFrame() && + ClearedEnvironment() && HasClearedEnvironment() ) + { + return; + } + + // use new class to position object + objectpositioning::SwToContentAnchoredObjectPosition + aObjPositioning( *GetVirtDrawObj() ); + aObjPositioning.CalcPosition(); + + SetVertPosOrientFrame ( aObjPositioning.GetVertPosOrientFrame() ); +} + +// #i28701# +bool SwFlyAtContentFrame::InvalidationAllowed( const InvalidationType _nInvalid ) const +{ + bool bAllowed( SwFlyFreeFrame::InvalidationAllowed( _nInvalid ) ); + + // forbiddance of base instance can't be over ruled. + if ( bAllowed ) + { + if ( _nInvalid == INVALID_POS || + _nInvalid == INVALID_ALL ) + { + bAllowed = InvalidationOfPosAllowed(); + } + } + + return bAllowed; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flyincnt.cxx b/sw/source/core/layout/flyincnt.cxx new file mode 100644 index 000000000..4823aab29 --- /dev/null +++ b/sw/source/core/layout/flyincnt.cxx @@ -0,0 +1,278 @@ +/* -*- 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 <doc.hxx> +#include <frmtool.hxx> +#include <hints.hxx> +#include <fmtornt.hxx> +#include <rootfrm.hxx> +#include <flyfrms.hxx> +#include <dflyobj.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> + +SwFlyInContentFrame::SwFlyInContentFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwFlyFrame( pFormat, pSib, pAnch ) +{ + m_bInCnt = true; + SwTwips nRel = pFormat->GetVertOrient().GetPos(); + // OD 2004-05-27 #i26791# - member <aRelPos> moved to <SwAnchoredObject> + Point aRelPos; + if( pAnch && pAnch->IsVertical() ) + aRelPos.setX(-nRel); + else + aRelPos.setY(nRel); + SetCurrRelPos( aRelPos ); +} + +void SwFlyInContentFrame::DestroyImpl() +{ + if ( !GetFormat()->GetDoc()->IsInDtor() && GetAnchorFrame() ) + { + SwRect aTmp( GetObjRectWithSpaces() ); + SwFlyInContentFrame::NotifyBackground( FindPageFrame(), aTmp, PrepareHint::FlyFrameLeave ); + } + + SwFlyFrame::DestroyImpl(); +} + +SwFlyInContentFrame::~SwFlyInContentFrame() +{ +} + +// #i28701# + +void SwFlyInContentFrame::SetRefPoint( const Point& rPoint, + const Point& rRelAttr, + const Point& rRelPos ) +{ + // OD 2004-05-27 #i26791# - member <aRelPos> moved to <SwAnchoredObject> + OSL_ENSURE( rPoint != aRef || rRelAttr != GetCurrRelPos(), "SetRefPoint: no change" ); + std::unique_ptr<SwFlyNotify, o3tl::default_delete<SwFlyNotify>> xNotify; + // No notify at a locked fly frame, if a fly frame is locked, there's + // already a SwFlyNotify object on the stack (MakeAll). + if( !IsLocked() ) + xNotify.reset(new SwFlyNotify( this )); + aRef = rPoint; + SetCurrRelPos( rRelAttr ); + SwRectFnSet aRectFnSet(GetAnchorFrame()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetPos( aFrm, rPoint + rRelPos ); + } + + // #i68520# + InvalidateObjRectWithSpaces(); + if (xNotify) + { + InvalidatePage(); + setFrameAreaPositionValid(false); + m_bInvalid = true; + Calc(getRootFrame()->GetCurrShell()->GetOut()); + xNotify.reset(); + } +} + +void SwFlyInContentFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + bool bCallPrepare = false; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + if (RES_ATTRSET_CHG == nWhich && pNew) + { + if(pOld && + (SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()-> + GetItemState(RES_SURROUND, false) || + SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()-> + GetItemState(RES_FRMMACRO, false)) ) + { + SwAttrSetChg aOld( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNew( *static_cast<const SwAttrSetChg*>(pNew) ); + + aOld.ClearItem( RES_SURROUND ); + aNew.ClearItem( RES_SURROUND ); + aOld.ClearItem( RES_FRMMACRO ); + aNew.ClearItem( RES_FRMMACRO ); + if( aNew.Count() ) + { + SwFlyFrame::Modify( &aOld, &aNew ); + bCallPrepare = true; + } + } + else if( static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->Count()) + { + SwFlyFrame::Modify( pOld, pNew ); + bCallPrepare = true; + } + } + else if( nWhich != RES_SURROUND && RES_FRMMACRO != nWhich ) + { + SwFlyFrame::Modify( pOld, pNew ); + bCallPrepare = true; + } + + if ( bCallPrepare && GetAnchorFrame() ) + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged, GetFormat() ); +} + +/// Here the content gets formatted initially. +void SwFlyInContentFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs ) +{ + if ( !getFrameArea().Height() ) + { + Lock(); //don't format the anchor on the crook. + SwContentFrame *pContent = ContainsContent(); + while ( pContent ) + { pContent->Calc(pRenderContext); + pContent = pContent->GetNextContentFrame(); + } + Unlock(); + } + SwFlyFrame::Format( pRenderContext, pAttrs ); +} + +/** Calculate object position + * + * @note: In contrast to other Frames, we only calculate the relative position + * here. The absolute position is only calculated using SetAbsPos. + **/ +void SwFlyInContentFrame::MakeObjPos() +{ + if ( !isFrameAreaPositionValid() ) + { + setFrameAreaPositionValid(true); + SwFlyFrameFormat *pFormat = GetFormat(); + const SwFormatVertOrient &rVert = pFormat->GetVertOrient(); + //Update the current values in the format if needed, during this we of + //course must not send any Modify. + const bool bVert = GetAnchorFrame()->IsVertical(); + SwTwips nOld = rVert.GetPos(); + SwTwips nAct = bVert ? -GetCurrRelPos().X() : GetCurrRelPos().Y(); + if( nAct != nOld ) + { + SwFormatVertOrient aVert( rVert ); + aVert.SetPos( nAct ); + pFormat->LockModify(); + pFormat->SetFormatAttr( aVert ); + pFormat->UnlockModify(); + } + } +} + +void SwFlyInContentFrame::ActionOnInvalidation( const InvalidationType _nInvalid ) +{ + if ( INVALID_POS == _nInvalid || INVALID_ALL == _nInvalid ) + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged, &GetFrameFormat() ); +} + +void SwFlyInContentFrame::NotifyBackground( SwPageFrame *, const SwRect& rRect, + PrepareHint eHint) +{ + if ( eHint == PrepareHint::FlyFrameAttributesChanged ) + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged ); + else + AnchorFrame()->Prepare( eHint, static_cast<void const *>(&rRect) ); +} + +Point const & SwFlyInContentFrame::GetRelPos() const +{ + Calc(getRootFrame()->GetCurrShell()->GetOut()); + return GetCurrRelPos(); +} + +/// @see SwRowFrame::RegistFlys() +void SwFlyInContentFrame::RegistFlys() +{ + SwPageFrame *pPage = FindPageFrame(); + OSL_ENSURE( pPage, "Register Flys without pages?" ); + ::RegistFlys( pPage, this ); +} + +void SwFlyInContentFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + // OD 2004-01-19 #110582# + if ( !GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ) + { + return; + } + + if ( !GetAnchorFrame() || IsLocked() || IsColLocked() || !FindPageFrame() ) + return; + + Lock(); // The curtain falls + + //does the notification in the DTor + const SwFlyNotify aNotify( this ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + if ( IsClipped() ) + { + setFrameAreaSizeValid(false); + m_bHeightClipped = m_bWidthClipped = false; + } + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() || !m_bValidContentPos ) + { + //Only stop, if the flag is set!! + if ( !isFrameAreaSizeValid() ) + { + setFramePrintAreaValid(false); + } + + if ( !isFramePrintAreaValid() ) + { + MakePrtArea( rAttrs ); + m_bValidContentPos = false; + } + + if ( !isFrameAreaSizeValid() ) + { + Format( getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + + if ( !isFrameAreaPositionValid() ) + { + MakeObjPos(); + } + + if ( !m_bValidContentPos ) + MakeContentPos( rAttrs ); + + // re-activate clipping of as-character anchored Writer fly frames + // depending on compatibility option <ClipAsCharacterAnchoredWriterFlyFrames> + if ( isFrameAreaPositionValid() && + isFrameAreaSizeValid() && + GetFormat()->getIDocumentSettingAccess().get( DocumentSettingId::CLIP_AS_CHARACTER_ANCHORED_WRITER_FLY_FRAME ) ) + { + SwFrame* pFrame = AnchorFrame(); + if ( getFrameArea().Left() == (pFrame->getFrameArea().Left()+pFrame->getFramePrintArea().Left()) && + getFrameArea().Width() > pFrame->getFramePrintArea().Width() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( pFrame->getFramePrintArea().Width() ); + setFramePrintAreaValid(false); + m_bWidthClipped = true; + } + } + } + Unlock(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flylay.cxx b/sw/source/core/layout/flylay.cxx new file mode 100644 index 000000000..72d673b22 --- /dev/null +++ b/sw/source/core/layout/flylay.cxx @@ -0,0 +1,1489 @@ +/* -*- 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 <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <ftnfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <hints.hxx> +#include <sectfrm.hxx> +#include <notxtfrm.hxx> +#include <txtfly.hxx> + +#include <svx/svdpage.hxx> +#include <editeng/ulspitem.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <ndole.hxx> +#include <tabfrm.hxx> +#include <flyfrms.hxx> +#include <fmtfollowtextflow.hxx> +#include <environmentofanchoredobject.hxx> +#include <sortedobjs.hxx> +#include <viewimp.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <pam.hxx> +#include <ndindex.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> + +using namespace ::com::sun::star; + +SwFlyFreeFrame::SwFlyFreeFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) +: SwFlyFrame( pFormat, pSib, pAnch ), + // #i34753# + mbNoMakePos( false ), + // #i37068# + mbNoMoveOnCheckClip( false ), + maUnclippedFrame(), + // RotateFlyFrame3 + mpTransformableSwFrame() +{ +} + +void SwFlyFreeFrame::DestroyImpl() +{ + // #i28701# - use new method <GetPageFrame()> + if( GetPageFrame() ) + { + if( GetFormat()->GetDoc()->IsInDtor() ) + { + // #i29879# - remove also to-frame anchored Writer + // fly frame from page. + const bool bRemoveFromPage = + GetPageFrame()->GetSortedObjs() && + ( IsFlyAtContentFrame() || + ( GetAnchorFrame() && GetAnchorFrame()->IsFlyFrame() ) ); + if ( bRemoveFromPage ) + { + GetPageFrame()->GetSortedObjs()->Remove( *this ); + } + } + else + { + SwRect aTmp( GetObjRectWithSpaces() ); + SwFlyFreeFrame::NotifyBackground( GetPageFrame(), aTmp, PrepareHint::FlyFrameLeave ); + } + } + + SwFlyFrame::DestroyImpl(); +} + +SwFlyFreeFrame::~SwFlyFreeFrame() +{ +#if 0 + // we are possibly in ContourCache, make sure we vanish + ::ClrContourCache(GetVirtDrawObj()); +#endif +} + +// #i28701# +/** Notifies the background (all ContentFrames that currently are overlapping). + * + * Additionally, the window is also directly invalidated (especially where + * there are no overlapping ContentFrames). + * This also takes ContentFrames within other Flys into account. + */ +void SwFlyFreeFrame::NotifyBackground( SwPageFrame *pPageFrame, + const SwRect& rRect, PrepareHint eHint ) +{ + ::Notify_Background( GetVirtDrawObj(), pPageFrame, rRect, eHint, true ); +} + +void SwFlyFreeFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + if ( !GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ) + { + return; + } + + if ( !GetAnchorFrame() || IsLocked() || IsColLocked() ) + { + return; + } + + // #i28701# - use new method <GetPageFrame()> + if( !GetPageFrame() && GetAnchorFrame()->IsInFly() ) + { + SwFlyFrame* pFly = AnchorFrame()->FindFlyFrame(); + SwPageFrame *pPageFrame = pFly ? pFly->FindPageFrame() : nullptr; + if( pPageFrame ) + pPageFrame->AppendFlyToPage( this ); + } + + if( !GetPageFrame() ) + { + return; + } + + Lock(); // The curtain drops + + // takes care of the notification in the dtor + const SwFlyNotify aNotify( this ); + + if ( IsClipped() ) + { + setFrameAreaSizeValid(false); + m_bHeightClipped = m_bWidthClipped = false; + // no invalidation of position, + // if anchored object is anchored inside a Writer fly frame, + // its position is already locked, and it follows the text flow. + // #i34753# - add condition: + // no invalidation of position, if no direct move is requested in <CheckClip(..)> + if ( !IsNoMoveOnCheckClip() && + !( PositionLocked() && + GetAnchorFrame()->IsInFly() && + GetFrameFormat().GetFollowTextFlow().GetValue() ) ) + { + setFrameAreaPositionValid(false); + } + } + + // #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 10; + + // RotateFlyFrame3 - outer frame + const double fRotation(getLocalFrameRotation()); + const bool bRotated(!basegfx::fTools::equalZero(fRotation)); + + if(bRotated) + { + // Re-layout may be partially (see all isFrameAreaDefinitionValid() flags), + // so resetting the local SwFrame(s) in the local SwFrameAreaDefinition is + // needed. Reset to BoundAreas will be done below automatically + if(isTransformableSwFrame()) + { + getTransformableSwFrame()->restoreFrameAreas(); + } + } + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() || m_bFormatHeightOnly || !m_bValidContentPos ) + { + SwRectFnSet aRectFnSet(this); + const SwFormatFrameSize *pSz; + { // Additional scope, so aAccess will be destroyed before the check! + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + pSz = &rAttrs.GetAttrSet().GetFrameSize(); + + // Only set when the flag is set! + if ( !isFrameAreaSizeValid() ) + { + setFramePrintAreaValid(false); + } + + if ( !isFramePrintAreaValid() ) + { + MakePrtArea( rAttrs ); + m_bValidContentPos = false; + } + + if ( !isFrameAreaSizeValid() || m_bFormatHeightOnly ) + { + setFrameAreaSizeValid(false); + Format( getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + m_bFormatHeightOnly = false; + } + } + + if ( !isFrameAreaPositionValid() ) + { + const Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); + // #i26791# - use new method <MakeObjPos()> + // #i34753# - no positioning, if requested. + if ( IsNoMakePos() ) + { + setFrameAreaPositionValid(true); + } + else + // #i26791# - use new method <MakeObjPos()> + MakeObjPos(); + if( aOldPos == aRectFnSet.GetPos(getFrameArea()) ) + { + if( !isFrameAreaPositionValid() && GetAnchorFrame()->IsInSct() && + !GetAnchorFrame()->FindSctFrame()->isFrameAreaDefinitionValid() ) + { + setFrameAreaPositionValid(true); + } + } + else + { + setFrameAreaSizeValid(false); + } + } + + if ( !m_bValidContentPos ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + MakeContentPos( rAttrs ); + } + + if ( isFrameAreaPositionValid() && isFrameAreaSizeValid() ) + { + ++nLoopControlRuns; + + OSL_ENSURE( nLoopControlRuns < nLoopControlMax, "LoopControl in SwFlyFreeFrame::MakeAll" ); + + if ( nLoopControlRuns < nLoopControlMax ) + CheckClip( *pSz ); + } + else + nLoopControlRuns = 0; + } + + // RotateFlyFrame3 - outer frame + // Do not refresh transforms/Areas self here, this will be done + // when inner and outer frame are layouted, in SwNoTextFrame::MakeAll + if(bRotated) + { + // RotateFlyFrame3: Safe changes locally + // get center from outer frame (layout frame) to be on the safe side + const Point aCenter(getFrameArea().Center()); + const basegfx::B2DPoint aB2DCenter(aCenter.X(), aCenter.Y()); + + if(!mpTransformableSwFrame) + { + mpTransformableSwFrame.reset(new TransformableSwFrame(*this)); + } + + getTransformableSwFrame()->createFrameAreaTransformations( + fRotation, + aB2DCenter); + getTransformableSwFrame()->adaptFrameAreasToTransformations(); + } + else + { + // RotateFlyFrame3: Also need to clear ContourCache (if used), + // usually done in SwFlyFrame::NotifyDrawObj, but there relies on + // being in transform mode which is already reset then + if(isTransformableSwFrame()) + { + ::ClrContourCache(GetVirtDrawObj()); + } + + // reset transformations to show that they are not used + mpTransformableSwFrame.reset(); + } + + Unlock(); + +#if OSL_DEBUG_LEVEL > 0 + SwRectFnSet aRectFnSet(this); + OSL_ENSURE( m_bHeightClipped || ( aRectFnSet.GetHeight(getFrameArea()) > 0 && + aRectFnSet.GetHeight(getFramePrintArea()) > 0), + "SwFlyFreeFrame::Format(), flipping Fly." ); + +#endif +} + +bool SwFlyFreeFrame::supportsAutoContour() const +{ + static bool bOverrideHandleContourToAlwaysOff(true); // loplugin:constvars:ignore + + // RotateFlyFrameFix: For LO6.0 we need to deactivate the AutoContour feature again, it is simply + // not clear how/if to use and save/load it in ODF. This has to be discussed. + // The reason not to remove is that this may be used as-is now, using a new switch. + // Even when not, the detection if it is possible will be needed in any case later. + if(bOverrideHandleContourToAlwaysOff) + { + return false; + } + + if(!isTransformableSwFrame()) + { + // support only when transformed, else there is no free space + return false; + } + + // Check for Borders. If we have Borders, do (currently) not support, + // since borders do not transform with the object. + // (Will need to be enhanced to take into account if we have Borders and if these + // transform with the object) + SwBorderAttrAccess aAccess(SwFrame::GetCache(), this); + const SwBorderAttrs &rAttrs(*aAccess.Get()); + + if(rAttrs.IsLine()) + { + return false; + } + + // Check for Padding. Do not support when padding is used, this will + // produce a covered space around the object (filled with fill defines) + const SfxPoolItem* pItem(nullptr); + + if(GetFormat() && SfxItemState::SET == GetFormat()->GetItemState(RES_BOX, false, &pItem)) + { + const SvxBoxItem& rBox = *static_cast< const SvxBoxItem* >(pItem); + + if(rBox.HasBorder(/*bTreatPaddingAsBorder*/true)) + { + return false; + } + } + + // check for Fill - if we have fill, it will fill the gaps and we will not + // support AutoContour + if(GetFormat() && GetFormat()->supportsFullDrawingLayerFillAttributeSet()) + { + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes(GetFormat()->getSdrAllFillAttributesHelper()); + + if(aFillAttributes && aFillAttributes->isUsed()) + { + return false; + } + } + else + { + const std::unique_ptr<SvxBrushItem> aBack(GetFormat()->makeBackgroundBrushItem()); + + if(aBack && aBack->isUsed()) + { + return false; + } + } + + // else, support + return true; +} + +// RotateFlyFrame3 - Support for Transformations - outer frame +basegfx::B2DHomMatrix SwFlyFreeFrame::getFrameAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFrameAreaTransformation(); + } + + // call parent + return SwFlyFrame::getFrameAreaTransformation(); +} + +basegfx::B2DHomMatrix SwFlyFreeFrame::getFramePrintAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFramePrintAreaTransformation(); + } + + // call parent + return SwFlyFrame::getFramePrintAreaTransformation(); +} + +// RotateFlyFrame3 - Support for Transformations +void SwFlyFreeFrame::transform_translate(const Point& rOffset) +{ + // call parent - this will do the basic transform for SwRect(s) + // in the SwFrameAreaDefinition + SwFlyFrame::transform_translate(rOffset); + + // check if the Transformations need to be adapted + if(isTransformableSwFrame()) + { + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createTranslateB2DHomMatrix( + rOffset.X(), rOffset.Y())); + + // transform using TransformableSwFrame + getTransformableSwFrame()->transform(aTransform); + } +} + +// RotateFlyFrame3 - outer frame +double getLocalFrameRotation_from_SwNoTextFrame(const SwNoTextFrame& rNoTextFrame) +{ + return rNoTextFrame.getLocalFrameRotation(); +} + +double SwFlyFreeFrame::getLocalFrameRotation() const +{ + // SwLayoutFrame::Lower() != SwFrame::GetLower(), but SwFrame::GetLower() + // calls SwLayoutFrame::Lower() when it's a SwLayoutFrame - so use GetLower() + const SwNoTextFrame* pSwNoTextFrame(dynamic_cast< const SwNoTextFrame* >(GetLower())); + + if(nullptr != pSwNoTextFrame) + { + return getLocalFrameRotation_from_SwNoTextFrame(*pSwNoTextFrame); + } + + // no rotation + return 0.0; +} + +/** determines, if direct environment of fly frame has 'auto' size + + #i17297# + start with anchor frame and search via <GetUpper()> for a header, footer, + row or fly frame stopping at page frame. + return <true>, if such a frame is found and it has 'auto' size. + otherwise <false> is returned. + + @return boolean indicating, that direct environment has 'auto' size +*/ +bool SwFlyFreeFrame::HasEnvironmentAutoSize() const +{ + bool bRetVal = false; + + const SwFrame* pToBeCheckedFrame = GetAnchorFrame(); + while ( pToBeCheckedFrame && + !pToBeCheckedFrame->IsPageFrame() ) + { + if ( pToBeCheckedFrame->IsHeaderFrame() || + pToBeCheckedFrame->IsFooterFrame() || + pToBeCheckedFrame->IsRowFrame() || + pToBeCheckedFrame->IsFlyFrame() ) + { + bRetVal = SwFrameSize::Fixed != + pToBeCheckedFrame->GetAttrSet()->GetFrameSize().GetHeightSizeType(); + break; + } + else + { + pToBeCheckedFrame = pToBeCheckedFrame->GetUpper(); + } + } + + return bRetVal; +} + +void SwFlyFreeFrame::CheckClip( const SwFormatFrameSize &rSz ) +{ + // It's probably time now to take appropriate measures, if the Fly + // doesn't fit into its surrounding. + // First, the Fly gives up its position, then it's formatted. + // Only if it still doesn't fit after giving up its position, the + // width or height are given up as well. The frame will be squeezed + // as much as needed. + + const SwVirtFlyDrawObj *pObj = GetVirtDrawObj(); + SwRect aClip, aTmpStretch; + ::CalcClipRect( pObj, aClip ); + ::CalcClipRect( pObj, aTmpStretch, false ); + aClip.Intersection_( aTmpStretch ); + + const long nBot = getFrameArea().Top() + getFrameArea().Height(); + const long nRig = getFrameArea().Left() + getFrameArea().Width(); + const long nClipBot = aClip.Top() + aClip.Height(); + const long nClipRig = aClip.Left() + aClip.Width(); + + const bool bBot = nBot > nClipBot; + const bool bRig = nRig > nClipRig; + if ( bBot || bRig ) + { + bool bAgain = false; + // #i37068# - no move, if it's requested + if ( bBot && !IsNoMoveOnCheckClip() && + !GetDrawObjs() && !GetAnchorFrame()->IsInTab() ) + { + SwFrame* pHeader = FindFooterOrHeader(); + // In a header, correction of the position is no good idea. + // If the fly moves, some paragraphs have to be formatted, this + // could cause a change of the height of the headerframe, + // now the flyframe can change its position and so on ... + if ( !pHeader || !pHeader->IsHeaderFrame() ) + { + const long nOld = getFrameArea().Top(); + + // tdf#112443 disable positioning if content is completely off page + bool bDisableOffPagePositioning = GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + if ( !bDisableOffPagePositioning || nOld <= nClipBot) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setY( std::max( aClip.Top(), nClipBot - aFrm.Height() ) ); + } + + if ( getFrameArea().Top() != nOld ) + { + bAgain = true; + } + + m_bHeightClipped = true; + } + } + if ( bRig ) + { + const long nOld = getFrameArea().Left(); + + // tdf#112443 disable positioning if content is completely off page + bool bDisableOffPagePositioning = GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + if ( !bDisableOffPagePositioning || nOld <= nClipRig ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX( std::max( aClip.Left(), nClipRig - aFrm.Width() ) ); + } + + if ( getFrameArea().Left() != nOld ) + { + const SwFormatHoriOrient &rH = GetFormat()->GetHoriOrient(); + // Left-aligned ones may not be moved to the left when they + // are avoiding another one. + if( rH.GetHoriOrient() == text::HoriOrientation::LEFT ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX( nOld ); + } + else + { + bAgain = true; + } + } + m_bWidthClipped = true; + } + if ( bAgain ) + { + setFrameAreaSizeValid(false); + } + else + { + // If we reach this branch, the Frame protrudes into forbidden + // areas, and correcting the position is not allowed or not + // possible or not required. + + // For Flys with OLE objects as lower, we make sure that + // we always resize proportionally + Size aOldSize( getFrameArea().SSize() ); + + // First, setup the FrameRect, then transfer it to the Frame. + SwRect aFrameRect( getFrameArea() ); + + if ( bBot ) + { + long nDiff = nClipBot; + nDiff -= aFrameRect.Top(); // nDiff represents the available distance + nDiff = aFrameRect.Height() - nDiff; + aFrameRect.Height( aFrameRect.Height() - nDiff ); + m_bHeightClipped = true; + } + if ( bRig ) + { + long nDiff = nClipRig; + nDiff -= aFrameRect.Left();// nDiff represents the available distance + nDiff = aFrameRect.Width() - nDiff; + aFrameRect.Width( aFrameRect.Width() - nDiff ); + m_bWidthClipped = true; + } + + // #i17297# - no proportional + // scaling of graphics in environments, which determines its size + // by its content ('auto' size). Otherwise layout loops can occur and + // layout sizes of the environment can be incorrect. + // Such environment are: + // (1) header and footer frames with 'auto' size + // (2) table row frames with 'auto' size + // (3) fly frames with 'auto' size + // Note: section frames seems to be not critical - didn't found + // any critical layout situation so far. + if ( Lower() && Lower()->IsNoTextFrame() && + (static_cast<SwNoTextFrame*>(Lower())->GetNode()->GetOLENode() || + !HasEnvironmentAutoSize() ) ) + { + // If width and height got adjusted, then the bigger + // change is relevant. + if ( aFrameRect.Width() != aOldSize.Width() && + aFrameRect.Height()!= aOldSize.Height() ) + { + if ( (aOldSize.Width() - aFrameRect.Width()) > + (aOldSize.Height()- aFrameRect.Height()) ) + aFrameRect.Height( aOldSize.Height() ); + else + aFrameRect.Width( aOldSize.Width() ); + } + + // Adjusted the width? change height proportionally + if( aFrameRect.Width() != aOldSize.Width() ) + { + aFrameRect.Height( aFrameRect.Width() * aOldSize.Height() / + aOldSize.Width() ); + m_bHeightClipped = true; + } + // Adjusted the height? change width proportionally + else if( aFrameRect.Height() != aOldSize.Height() ) + { + aFrameRect.Width( aFrameRect.Height() * aOldSize.Width() / + aOldSize.Height() ); + m_bWidthClipped = true; + } + + // #i17297# - reactivate change + // of size attribute for fly frames containing an ole object. + + // Added the aFrameRect.HasArea() hack, because + // the environment of the ole object does not have to be valid + // at this moment, or even worse, it does not have to have a + // reasonable size. In this case we do not want to change to + // attributes permanently. Maybe one day somebody dares to remove + // this code. + if ( aFrameRect.HasArea() && + static_cast<SwNoTextFrame*>(Lower())->GetNode()->GetOLENode() && + ( m_bWidthClipped || m_bHeightClipped ) ) + { + SwFlyFrameFormat *pFormat = GetFormat(); + pFormat->LockModify(); + SwFormatFrameSize aFrameSize( rSz ); + aFrameSize.SetWidth( aFrameRect.Width() ); + aFrameSize.SetHeight( aFrameRect.Height() ); + pFormat->SetFormatAttr( aFrameSize ); + pFormat->UnlockModify(); + } + } + + // Now change the Frame; for columns, we put the new values into the attributes, + // otherwise we'll end up with unwanted side-effects/oscillations + const long nPrtHeightDiff = getFrameArea().Height() - getFramePrintArea().Height(); + const long nPrtWidthDiff = getFrameArea().Width() - getFramePrintArea().Width(); + maUnclippedFrame = getFrameArea(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( aFrameRect.Height() ); + aFrm.Width ( std::max( long(MINLAY), aFrameRect.Width() ) ); + } + + if ( Lower() && Lower()->IsColumnFrame() ) + { + ColLock(); //lock grow/shrink + const Size aTmpOldSize( getFramePrintArea().SSize() ); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( getFrameArea().Height() - nPrtHeightDiff ); + aPrt.Width ( getFrameArea().Width() - nPrtWidthDiff ); + } + + ChgLowersProp( aTmpOldSize ); + SwFrame *pLow = Lower(); + do + { + pLow->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // also calculate the (Column)BodyFrame + static_cast<SwLayoutFrame*>(pLow)->Lower()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pLow = pLow->GetNext(); + } while ( pLow ); + ::CalcContent( this ); + ColUnlock(); + + if ( !isFrameAreaSizeValid() && !m_bWidthClipped ) + { + setFrameAreaSizeValid(true); + m_bFormatHeightOnly = true; + } + } + else + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( getFrameArea().Height() - nPrtHeightDiff ); + aPrt.Width ( getFrameArea().Width() - nPrtWidthDiff ); + } + } + } + + // #i26945# + OSL_ENSURE( getFrameArea().Height() >= 0, + "<SwFlyFreeFrame::CheckClip(..)> - fly frame has negative height now." ); +} + +/** method to determine, if a <MakeAll()> on the Writer fly frame is possible + #i43771# +*/ +bool SwFlyFreeFrame::IsFormatPossible() const +{ + return SwFlyFrame::IsFormatPossible() && + ( GetPageFrame() || + ( GetAnchorFrame() && GetAnchorFrame()->IsInFly() ) ); +} + +SwFlyLayFrame::SwFlyLayFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwFlyFreeFrame( pFormat, pSib, pAnch ) +{ + m_bLayout = true; +} + +// #i28701# + +void SwFlyLayFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + const SwFormatAnchor *pAnch = nullptr; + + if (pNew) + { + const sal_uInt16 nWhich = pNew->Which(); + if( RES_ATTRSET_CHG == nWhich && SfxItemState::SET == + static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( RES_ANCHOR, false, + reinterpret_cast<const SfxPoolItem**>(&pAnch) )) + ; // GetItemState sets the anchor pointer! + + else if( RES_ANCHOR == nWhich ) + { + // Change of anchor. I'm attaching myself to the new place. + // It's not allowed to change the anchor type. This is only + // possible via SwFEShell. + pAnch = static_cast<const SwFormatAnchor*>(pNew); + } + } + + if( pAnch ) + { + OSL_ENSURE( pAnch->GetAnchorId() == + GetFormat()->GetAnchor().GetAnchorId(), + "8-) Invalid change of anchor type." ); + + // Unregister, get hold of the page, attach to the corresponding LayoutFrame. + SwRect aOld( GetObjRectWithSpaces() ); + // #i28701# - use new method <GetPageFrame()> + SwPageFrame *pOldPage = GetPageFrame(); + AnchorFrame()->RemoveFly( this ); + + if ( RndStdIds::FLY_AT_PAGE == pAnch->GetAnchorId() ) + { + sal_uInt16 nPgNum = pAnch->GetPageNum(); + SwRootFrame *pRoot = getRootFrame(); + SwPageFrame *pTmpPage = static_cast<SwPageFrame*>(pRoot->Lower()); + for ( sal_uInt16 i = 1; (i <= nPgNum) && pTmpPage; ++i, + pTmpPage = static_cast<SwPageFrame*>(pTmpPage->GetNext()) ) + { + if ( i == nPgNum ) + { + // #i50432# - adjust synopsis of <PlaceFly(..)> + pTmpPage->PlaceFly( this, nullptr ); + } + } + if( !pTmpPage ) + { + pRoot->SetAssertFlyPages(); + pRoot->AssertFlyPages(); + } + } + else + { + SwNodeIndex aIdx( pAnch->GetContentAnchor()->nNode ); + SwContentFrame *pContent = GetFormat()->GetDoc()->GetNodes().GoNext( &aIdx )-> + GetContentNode()->getLayoutFrame(getRootFrame(), nullptr, nullptr); + if( pContent ) + { + SwFlyFrame *pTmp = pContent->FindFlyFrame(); + if( pTmp ) + pTmp->AppendFly( this ); + } + } + // #i28701# - use new method <GetPageFrame()> + if ( pOldPage && pOldPage != GetPageFrame() ) + NotifyBackground( pOldPage, aOld, PrepareHint::FlyFrameLeave ); + SetCompletePaint(); + InvalidateAll(); + SetNotifyBack(); + } + else + SwFlyFrame::Modify( pOld, pNew ); +} + +void SwPageFrame::AppendFlyToPage( SwFlyFrame *pNew ) +{ + if ( !pNew->GetVirtDrawObj()->IsInserted() ) + getRootFrame()->GetDrawPage()->InsertObject( + static_cast<SdrObject*>(pNew->GetVirtDrawObj()), + pNew->GetVirtDrawObj()->GetReferencedObj().GetOrdNumDirect() ); + + InvalidateSpelling(); + InvalidateSmartTags(); + InvalidateAutoCompleteWords(); + InvalidateWordCount(); + + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->SetIdleFlags(); + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + + SdrObject* pObj = pNew->GetVirtDrawObj(); + OSL_ENSURE( pNew->GetAnchorFrame(), "Fly without Anchor" ); + SwFlyFrame* pFly = const_cast<SwFlyFrame*>(pNew->GetAnchorFrame()->FindFlyFrame()); + if ( pFly && pObj->GetOrdNum() < pFly->GetVirtDrawObj()->GetOrdNum() ) + { + //#i119945# set pFly's OrdNum to _rNewObj's. So when pFly is removed by Undo, the original OrdNum will not be changed. + sal_uInt32 nNewNum = pObj->GetOrdNumDirect(); + if ( pObj->getSdrPageFromSdrObject() ) + pObj->getSdrPageFromSdrObject()->SetObjectOrdNum( pFly->GetVirtDrawObj()->GetOrdNumDirect(), nNewNum ); + else + pFly->GetVirtDrawObj()->SetOrdNum( nNewNum ); + } + + // Don't look further at Flys that sit inside the Content. + if ( pNew->IsFlyInContentFrame() ) + InvalidateFlyInCnt(); + else + { + InvalidateFlyContent(); + + if ( !m_pSortedObjs ) + { + m_pSortedObjs.reset(new SwSortedObjs()); + } + + const bool bSuccessInserted = m_pSortedObjs->Insert( *pNew ); + OSL_ENSURE( bSuccessInserted, "Fly not inserted in Sorted." ); + + // #i87493# + OSL_ENSURE( pNew->GetPageFrame() == nullptr || pNew->GetPageFrame() == this, + "<SwPageFrame::AppendFlyToPage(..)> - anchored fly frame seems to be registered at another page frame. Serious defect." ); + // #i28701# - use new method <SetPageFrame(..)> + pNew->SetPageFrame( this ); + pNew->InvalidatePage( this ); + // #i28701# + pNew->UnlockPosition(); + // needed to reposition at-page anchored flys moved from different page + pNew->InvalidateObjPos(); + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->AddAccessibleFrame( pNew ); + } + } + + // #i28701# - correction: consider also drawing objects + if ( pNew->GetDrawObjs() ) + { + SwSortedObjs &rObjs = *pNew->GetDrawObjs(); + for (SwAnchoredObject* pTmpObj : rObjs) + { + if ( dynamic_cast<const SwFlyFrame*>( pTmpObj) != nullptr ) + { + SwFlyFrame* pTmpFly = static_cast<SwFlyFrame*>(pTmpObj); + // #i28701# - use new method <GetPageFrame()> + if ( pTmpFly->IsFlyFreeFrame() && !pTmpFly->GetPageFrame() ) + AppendFlyToPage( pTmpFly ); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pTmpObj) != nullptr ) + { + // #i87493# + if ( pTmpObj->GetPageFrame() != this ) + { + if ( pTmpObj->GetPageFrame() != nullptr ) + { + pTmpObj->GetPageFrame()->RemoveDrawObjFromPage( *pTmpObj ); + } + AppendDrawObjToPage( *pTmpObj ); + } + } + } + } +} + +void SwPageFrame::RemoveFlyFromPage( SwFlyFrame *pToRemove ) +{ + const sal_uInt32 nOrdNum = pToRemove->GetVirtDrawObj()->GetOrdNum(); + getRootFrame()->GetDrawPage()->RemoveObject( nOrdNum ); + pToRemove->GetVirtDrawObj()->ReferencedObj().SetOrdNum( nOrdNum ); + + if ( GetUpper() ) + { + if ( !pToRemove->IsFlyInContentFrame() ) + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + + // Don't look further at Flys that sit inside the Content. + if ( pToRemove->IsFlyInContentFrame() ) + return; + + // Don't delete collections just yet. This will happen at the end of the + // action in the RemoveSuperfluous of the page, kicked off by a method of + // the same name in the root. + // The FlyColl might be gone already, because the page's dtor is being + // executed. + // Remove it _before_ disposing accessible frames to avoid accesses to + // the Frame from event handlers. + if (m_pSortedObjs) + { + m_pSortedObjs->Remove(*pToRemove); + if (!m_pSortedObjs->size()) + { + m_pSortedObjs.reset(); + } + } + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->DisposeAccessibleFrame( pToRemove, true ); + } + + // #i28701# - use new method <SetPageFrame(..)> + pToRemove->SetPageFrame( nullptr ); +} + +void SwPageFrame::MoveFly( SwFlyFrame *pToMove, SwPageFrame *pDest ) +{ + // Invalidations + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->SetIdleFlags(); + if ( !pToMove->IsFlyInContentFrame() && pDest->GetPhyPageNum() < GetPhyPageNum() ) + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + } + + pDest->InvalidateSpelling(); + pDest->InvalidateSmartTags(); + pDest->InvalidateAutoCompleteWords(); + pDest->InvalidateWordCount(); + + if ( pToMove->IsFlyInContentFrame() ) + { + pDest->InvalidateFlyInCnt(); + return; + } + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->DisposeAccessibleFrame( pToMove, true ); + } + + // The FlyColl might be gone already, because the page's dtor is being executed. + if ( m_pSortedObjs ) + { + m_pSortedObjs->Remove( *pToMove ); + if ( !m_pSortedObjs->size() ) + { + m_pSortedObjs.reset(); + } + } + + // Register + if ( !pDest->GetSortedObjs() ) + pDest->m_pSortedObjs.reset(new SwSortedObjs()); + + const bool bSuccessInserted = pDest->GetSortedObjs()->Insert( *pToMove ); + OSL_ENSURE( bSuccessInserted, "Fly not inserted in Sorted." ); + + // #i28701# - use new method <SetPageFrame(..)> + pToMove->SetPageFrame( pDest ); + pToMove->InvalidatePage( pDest ); + pToMove->SetNotifyBack(); + pDest->InvalidateFlyContent(); + // #i28701# + pToMove->UnlockPosition(); + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->AddAccessibleFrame( pToMove ); + } + + // #i28701# - correction: move lowers of Writer fly frame + if ( pToMove->GetDrawObjs() ) + { + SwSortedObjs &rObjs = *pToMove->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pObj); + if ( pFly->IsFlyFreeFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + SwPageFrame* pPageFrame = pFly->GetPageFrame(); + if ( pPageFrame ) + pPageFrame->MoveFly( pFly, pDest ); + else + pDest->AppendFlyToPage( pFly ); + } + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + RemoveDrawObjFromPage( *pObj ); + pDest->AppendDrawObjToPage( *pObj ); + } + } + } +} + +void SwPageFrame::AppendDrawObjToPage( SwAnchoredObject& _rNewObj ) +{ + if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rNewObj) == nullptr ) + { + OSL_FAIL( "SwPageFrame::AppendDrawObjToPage(..) - anchored object of unexpected type -> object not appended" ); + return; + } + + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + + assert(_rNewObj.GetAnchorFrame()); + SwFlyFrame* pFlyFrame = const_cast<SwFlyFrame*>(_rNewObj.GetAnchorFrame()->FindFlyFrame()); + if ( pFlyFrame && + _rNewObj.GetDrawObj()->GetOrdNum() < pFlyFrame->GetVirtDrawObj()->GetOrdNum() ) + { + //#i119945# set pFly's OrdNum to _rNewObj's. So when pFly is removed by Undo, the original OrdNum will not be changed. + sal_uInt32 nNewNum = _rNewObj.GetDrawObj()->GetOrdNumDirect(); + if ( _rNewObj.GetDrawObj()->getSdrPageFromSdrObject() ) + _rNewObj.DrawObj()->getSdrPageFromSdrObject()->SetObjectOrdNum( pFlyFrame->GetVirtDrawObj()->GetOrdNumDirect(), nNewNum ); + else + pFlyFrame->GetVirtDrawObj()->SetOrdNum( nNewNum ); + } + + if ( RndStdIds::FLY_AS_CHAR == _rNewObj.GetFrameFormat().GetAnchor().GetAnchorId() ) + { + return; + } + + if ( !m_pSortedObjs ) + { + m_pSortedObjs.reset(new SwSortedObjs()); + } + if ( !m_pSortedObjs->Insert( _rNewObj ) ) + { + OSL_ENSURE( m_pSortedObjs->Contains( _rNewObj ), + "Drawing object not appended into list <pSortedObjs>." ); + } + // #i87493# + OSL_ENSURE( _rNewObj.GetPageFrame() == nullptr || _rNewObj.GetPageFrame() == this, + "<SwPageFrame::AppendDrawObjToPage(..)> - anchored draw object seems to be registered at another page frame. Serious defect." ); + _rNewObj.SetPageFrame( this ); + + // invalidate page in order to force a reformat of object layout of the page. + InvalidateFlyLayout(); +} + +void SwPageFrame::RemoveDrawObjFromPage( SwAnchoredObject& _rToRemoveObj ) +{ + if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rToRemoveObj) == nullptr ) + { + OSL_FAIL( "SwPageFrame::RemoveDrawObjFromPage(..) - anchored object of unexpected type -> object not removed" ); + return; + } + + if ( m_pSortedObjs ) + { + m_pSortedObjs->Remove( _rToRemoveObj ); + if ( !m_pSortedObjs->size() ) + { + m_pSortedObjs.reset(); + } + if ( GetUpper() ) + { + if (RndStdIds::FLY_AS_CHAR != + _rToRemoveObj.GetFrameFormat().GetAnchor().GetAnchorId()) + { + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + InvalidatePage(); + } + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + } + _rToRemoveObj.SetPageFrame( nullptr ); +} + +// #i50432# - adjust method description and synopsis. +void SwPageFrame::PlaceFly( SwFlyFrame* pFly, SwFlyFrameFormat* pFormat ) +{ + // #i50432# - consider the case that page is an empty page: + // In this case append the fly frame at the next page + OSL_ENSURE( !IsEmptyPage() || GetNext(), + "<SwPageFrame::PlaceFly(..)> - empty page with no next page! -> fly frame appended at empty page" ); + if ( IsEmptyPage() && GetNext() ) + { + static_cast<SwPageFrame*>(GetNext())->PlaceFly( pFly, pFormat ); + } + else + { + // If we received a Fly, we use that one. Otherwise, create a new + // one using the Format. + if ( pFly ) + AppendFly( pFly ); + else + { + OSL_ENSURE( pFormat, ":-( No Format given for Fly." ); + pFly = new SwFlyLayFrame( pFormat, this, this ); + AppendFly( pFly ); + ::RegistFlys( this, pFly ); + } + } +} + +// #i18732# - adjustments for following text flow or not +// AND alignment at 'page areas' for to paragraph/to character anchored objects +// #i22305# - adjustment for following text flow for to frame anchored objects +// #i29778# - Because calculating the floating screen object's position +// (Writer fly frame or drawing object) doesn't perform a calculation on its +// upper frames and its anchor frame, a calculation of the upper frames in this +// method is no longer sensible. +// #i28701# - if document compatibility option 'Consider wrapping style influence +// on object positioning' is ON, the clip area corresponds to the one as the +// object doesn't follow the text flow. +bool CalcClipRect( const SdrObject *pSdrObj, SwRect &rRect, bool bMove ) +{ + bool bRet = true; + if ( auto pVirtFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>(pSdrObj) ) + { + const SwFlyFrame* pFly = pVirtFlyDrawObj->GetFlyFrame(); + const bool bFollowTextFlow = pFly->GetFormat()->GetFollowTextFlow().GetValue(); + // #i28701# + const bool bConsiderWrapOnObjPos = + pFly->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION); + const SwFormatVertOrient &rV = pFly->GetFormat()->GetVertOrient(); + if( pFly->IsFlyLayFrame() ) + { + const SwFrame* pClip; + // #i22305# + // #i28701# + if ( !bFollowTextFlow || bConsiderWrapOnObjPos ) + { + pClip = pFly->GetAnchorFrame()->FindPageFrame(); + } + else + { + pClip = pFly->GetAnchorFrame(); + } + + rRect = pClip->getFrameArea(); + SwRectFnSet aRectFnSet(pClip); + + // vertical clipping: Top and Bottom, also to PrtArea if necessary + if( rV.GetVertOrient() != text::VertOrientation::NONE && + rV.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) + { + aRectFnSet.SetTop( rRect, aRectFnSet.GetPrtTop(*pClip) ); + aRectFnSet.SetBottom( rRect, aRectFnSet.GetPrtBottom(*pClip) ); + } + // horizontal clipping: Top and Bottom, also to PrtArea if necessary + const SwFormatHoriOrient &rH = pFly->GetFormat()->GetHoriOrient(); + if( rH.GetHoriOrient() != text::HoriOrientation::NONE && + rH.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) + { + aRectFnSet.SetLeft( rRect, aRectFnSet.GetPrtLeft(*pClip) ); + aRectFnSet.SetRight(rRect, aRectFnSet.GetPrtRight(*pClip)); + } + } + else if( pFly->IsFlyAtContentFrame() ) + { + // #i18732# - consider following text flow or not + // AND alignment at 'page areas' + const SwFrame* pVertPosOrientFrame = pFly->GetVertPosOrientFrame(); + if ( !pVertPosOrientFrame ) + { + OSL_FAIL( "::CalcClipRect(..) - frame, vertical position is oriented at, is missing ."); + pVertPosOrientFrame = pFly->GetAnchorFrame(); + } + + if ( !bFollowTextFlow || bConsiderWrapOnObjPos ) + { + const SwLayoutFrame* pClipFrame = pVertPosOrientFrame->FindPageFrame(); + if (!pClipFrame) + { + OSL_FAIL("!pClipFrame: " + "if you can reproduce this please file a bug"); + return false; + } + rRect = bMove ? pClipFrame->GetUpper()->getFrameArea() + : pClipFrame->getFrameArea(); + // #i26945# - consider that a table, during + // its format, can exceed its upper printing area bottom. + // Thus, enlarge the clip rectangle, if such a case occurred + if ( pFly->GetAnchorFrame()->IsInTab() ) + { + const SwTabFrame* pTabFrame = const_cast<SwFlyFrame*>(pFly) + ->GetAnchorFrameContainingAnchPos()->FindTabFrame(); + SwRect aTmp( pTabFrame->getFramePrintArea() ); + aTmp += pTabFrame->getFrameArea().Pos(); + rRect.Union( aTmp ); + // #i43913# - consider also the cell frame + const SwFrame* pCellFrame = const_cast<SwFlyFrame*>(pFly) + ->GetAnchorFrameContainingAnchPos()->GetUpper(); + while ( pCellFrame && !pCellFrame->IsCellFrame() ) + { + pCellFrame = pCellFrame->GetUpper(); + } + if ( pCellFrame ) + { + aTmp = pCellFrame->getFramePrintArea(); + aTmp += pCellFrame->getFrameArea().Pos(); + rRect.Union( aTmp ); + } + } + } + else if ( rV.GetRelationOrient() == text::RelOrientation::PAGE_FRAME || + rV.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) + { + // new class <SwEnvironmentOfAnchoredObject> + objectpositioning::SwEnvironmentOfAnchoredObject + aEnvOfObj( bFollowTextFlow ); + const SwLayoutFrame& rVertClipFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pVertPosOrientFrame ); + if ( rV.GetRelationOrient() == text::RelOrientation::PAGE_FRAME ) + { + rRect = rVertClipFrame.getFrameArea(); + } + else if ( rV.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) + { + if ( rVertClipFrame.IsPageFrame() ) + { + rRect = static_cast<const SwPageFrame&>(rVertClipFrame).PrtWithoutHeaderAndFooter(); + } + else + { + rRect = rVertClipFrame.getFrameArea(); + } + } + const SwLayoutFrame* pHoriClipFrame = + pFly->GetAnchorFrame()->FindPageFrame()->GetUpper(); + SwRectFnSet aRectFnSet(pFly->GetAnchorFrame()); + aRectFnSet.SetLeft( rRect, aRectFnSet.GetLeft(pHoriClipFrame->getFrameArea()) ); + aRectFnSet.SetRight(rRect, aRectFnSet.GetRight(pHoriClipFrame->getFrameArea())); + } + else + { + // #i26945# + const SwFrame *pClip = + const_cast<SwFlyFrame*>(pFly)->GetAnchorFrameContainingAnchPos(); + SwRectFnSet aRectFnSet(pClip); + const SwLayoutFrame *pUp = pClip->GetUpper(); + const SwFrame *pCell = pUp->IsCellFrame() ? pUp : nullptr; + const SwFrameType nType = bMove + ? SwFrameType::Root | SwFrameType::Fly | SwFrameType::Header | + SwFrameType::Footer | SwFrameType::Ftn + : SwFrameType::Body | SwFrameType::Fly | SwFrameType::Header | + SwFrameType::Footer | SwFrameType::Cell| SwFrameType::Ftn; + + while ( !(pUp->GetType() & nType) || pUp->IsColBodyFrame() ) + { + pUp = pUp->GetUpper(); + if ( !pCell && pUp->IsCellFrame() ) + pCell = pUp; + } + if ( bMove && pUp->IsRootFrame() ) + { + rRect = pUp->getFramePrintArea(); + rRect += pUp->getFrameArea().Pos(); + pUp = nullptr; + } + if ( pUp ) + { + if ( pUp->GetType() & SwFrameType::Body ) + { + const SwPageFrame *pPg; + if ( pUp->GetUpper() != (pPg = pFly->FindPageFrame()) ) + pUp = pPg->FindBodyCont(); + if (pUp) + { + rRect = pUp->GetUpper()->getFrameArea(); + aRectFnSet.SetTop( rRect, aRectFnSet.GetPrtTop(*pUp) ); + aRectFnSet.SetBottom(rRect, aRectFnSet.GetPrtBottom(*pUp)); + } + } + else + { + if( ( pUp->GetType() & (SwFrameType::Fly | SwFrameType::Ftn ) ) && + !pUp->getFrameArea().IsInside( pFly->getFrameArea().Pos() ) ) + { + if( pUp->IsFlyFrame() ) + { + const SwFlyFrame *pTmpFly = static_cast<const SwFlyFrame*>(pUp); + while( pTmpFly->GetNextLink() ) + { + pTmpFly = pTmpFly->GetNextLink(); + if( pTmpFly->getFrameArea().IsInside( pFly->getFrameArea().Pos() ) ) + break; + } + pUp = pTmpFly; + } + else if( pUp->IsInFootnote() ) + { + const SwFootnoteFrame *pTmp = pUp->FindFootnoteFrame(); + while( pTmp->GetFollow() ) + { + pTmp = pTmp->GetFollow(); + if( pTmp->getFrameArea().IsInside( pFly->getFrameArea().Pos() ) ) + break; + } + pUp = pTmp; + } + } + rRect = pUp->getFramePrintArea(); + rRect.Pos() += pUp->getFrameArea().Pos(); + if ( pUp->GetType() & (SwFrameType::Header | SwFrameType::Footer) ) + { + rRect.Left ( pUp->GetUpper()->getFrameArea().Left() ); + rRect.Width( pUp->GetUpper()->getFrameArea().Width()); + } + else if ( pUp->IsCellFrame() ) //MA_FLY_HEIGHT + { + const SwFrame *pTab = pUp->FindTabFrame(); + aRectFnSet.SetBottom( rRect, aRectFnSet.GetPrtBottom(*pTab->GetUpper()) ); + // expand to left and right cell border + rRect.Left ( pUp->getFrameArea().Left() ); + rRect.Width( pUp->getFrameArea().Width() ); + } + } + } + if ( pCell ) + { + // CellFrames might also sit in unallowed areas. In this case, + // the Fly is allowed to do so as well + SwRect aTmp( pCell->getFramePrintArea() ); + aTmp += pCell->getFrameArea().Pos(); + rRect.Union( aTmp ); + } + } + } + else + { + const SwFrame *pUp = pFly->GetAnchorFrame()->GetUpper(); + SwRectFnSet aRectFnSet(pFly->GetAnchorFrame()); + while( pUp->IsColumnFrame() || pUp->IsSctFrame() || pUp->IsColBodyFrame()) + pUp = pUp->GetUpper(); + rRect = pUp->getFrameArea(); + if( !pUp->IsBodyFrame() ) + { + rRect += pUp->getFramePrintArea().Pos(); + rRect.SSize( pUp->getFramePrintArea().SSize() ); + if ( pUp->IsCellFrame() ) + { + const SwFrame *pTab = pUp->FindTabFrame(); + aRectFnSet.SetBottom( rRect, aRectFnSet.GetPrtBottom(*pTab->GetUpper()) ); + } + } + else if ( pUp->GetUpper()->IsPageFrame() ) + { + // Objects anchored as character may exceed right margin + // of body frame: + aRectFnSet.SetRight( rRect, aRectFnSet.GetRight(pUp->GetUpper()->getFrameArea()) ); + } + long nHeight = (9*aRectFnSet.GetHeight(rRect))/10; + long nTop; + const SwFormat *pFormat = GetUserCall(pSdrObj)->GetFormat(); + const SvxULSpaceItem &rUL = pFormat->GetULSpace(); + if( bMove ) + { + nTop = aRectFnSet.IsVert() ? static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().X() : + static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().Y(); + nTop = aRectFnSet.YInc( nTop, -nHeight ); + long nWidth = aRectFnSet.GetWidth(pFly->getFrameArea()); + aRectFnSet.SetLeftAndWidth( rRect, aRectFnSet.IsVert() ? + static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().Y() : + static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().X(), nWidth ); + nHeight = 2*nHeight - rUL.GetLower() - rUL.GetUpper(); + } + else + { + nTop = aRectFnSet.YInc( aRectFnSet.GetBottom(pFly->getFrameArea()), + rUL.GetLower() - nHeight ); + nHeight = 2*nHeight - aRectFnSet.GetHeight(pFly->getFrameArea()) + - rUL.GetLower() - rUL.GetUpper(); + } + aRectFnSet.SetTopAndHeight( rRect, nTop, nHeight ); + } + } + else + { + const SwDrawContact *pC = static_cast<const SwDrawContact*>(GetUserCall(pSdrObj)); + const SwFrameFormat *pFormat = pC->GetFormat(); + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnch.GetAnchorId() ) + { + const SwFrame* pAnchorFrame = pC->GetAnchorFrame( pSdrObj ); + if( !pAnchorFrame ) + { + OSL_FAIL( "<::CalcClipRect(..)> - missing anchor frame." ); + const_cast<SwDrawContact*>(pC)->ConnectToLayout(); + pAnchorFrame = pC->GetAnchorFrame(); + } + const SwFrame* pUp = pAnchorFrame->GetUpper(); + rRect = pUp->getFramePrintArea(); + rRect += pUp->getFrameArea().Pos(); + SwRectFnSet aRectFnSet(pAnchorFrame); + long nHeight = (9*aRectFnSet.GetHeight(rRect))/10; + long nTop; + const SvxULSpaceItem &rUL = pFormat->GetULSpace(); + SwRect aSnapRect( pSdrObj->GetSnapRect() ); + long nTmpH = 0; + if( bMove ) + { + nTop = aRectFnSet.YInc( aRectFnSet.IsVert() ? pSdrObj->GetAnchorPos().X() : + pSdrObj->GetAnchorPos().Y(), -nHeight ); + long nWidth = aRectFnSet.GetWidth(aSnapRect); + aRectFnSet.SetLeftAndWidth( rRect, aRectFnSet.IsVert() ? + pSdrObj->GetAnchorPos().Y() : + pSdrObj->GetAnchorPos().X(), nWidth ); + } + else + { + // #i26791# - value of <nTmpH> is needed to + // calculate value of <nTop>. + nTmpH = aRectFnSet.IsVert() ? pSdrObj->GetCurrentBoundRect().GetWidth() : + pSdrObj->GetCurrentBoundRect().GetHeight(); + nTop = aRectFnSet.YInc( aRectFnSet.GetTop(aSnapRect), + rUL.GetLower() + nTmpH - nHeight ); + } + nHeight = 2*nHeight - nTmpH - rUL.GetLower() - rUL.GetUpper(); + aRectFnSet.SetTopAndHeight( rRect, nTop, nHeight ); + } + else + { + // restrict clip rectangle for drawing + // objects in header/footer to the page frame. + // #i26791# + const SwFrame* pAnchorFrame = pC->GetAnchorFrame( pSdrObj ); + if ( pAnchorFrame && pAnchorFrame->FindFooterOrHeader() ) + { + // clip frame is the page frame the header/footer is on. + const SwFrame* pClipFrame = pAnchorFrame->FindPageFrame(); + rRect = pClipFrame->getFrameArea(); + } + else + { + bRet = false; + } + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flypos.cxx b/sw/source/core/layout/flypos.cxx new file mode 100644 index 000000000..e7709204f --- /dev/null +++ b/sw/source/core/layout/flypos.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <fmtanchr.hxx> +#include <ndindex.hxx> +#include <frameformats.hxx> +#include <svx/swframetypes.hxx> + +bool SwPosFlyFrameCmp::operator()(const SwPosFlyFramePtr& rA, const SwPosFlyFramePtr& rB) const +{ + if(rA->GetNdIndex() == rB->GetNdIndex()) + { + // In this case, the order number decides! + return rA->GetOrdNum() < rB->GetOrdNum(); + } + + return rA->GetNdIndex() < rB->GetNdIndex(); +} + +SwPosFlyFrame::SwPosFlyFrame(const SwNodeIndex& rIdx, const SwFrameFormat* pFormat, sal_uInt16 nArrPos) + : m_pFrameFormat(pFormat), m_pNodeIndex(const_cast<SwNodeIndex*>(&rIdx)), m_nOrdNum(SAL_MAX_UINT32) +{ + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if(RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) + m_pNodeIndex = new SwNodeIndex(rIdx); + else if(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()) + pFormat->CallSwClientNotify(sw::GetZOrderHint(m_nOrdNum)); + if(m_nOrdNum == SAL_MAX_UINT32) + { + m_nOrdNum = pFormat->GetDoc()->GetSpzFrameFormats()->size(); + m_nOrdNum += nArrPos; + } +} + +SwPosFlyFrame::~SwPosFlyFrame() +{ + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + if (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) + { + delete m_pNodeIndex; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx new file mode 100644 index 000000000..295000c0a --- /dev/null +++ b/sw/source/core/layout/frmtool.cxx @@ -0,0 +1,3897 @@ +/* -*- 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 <svx/svdpage.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <sal/log.hxx> + +#include <drawdoc.hxx> +#include <fmtornt.hxx> +#include <fmthdft.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <docary.hxx> +#include <lineinfo.hxx> +#include <swmodule.hxx> +#include <pagefrm.hxx> +#include <colfrm.hxx> +#include <fesh.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrms.hxx> +#include <layact.hxx> +#include <pagedesc.hxx> +#include <section.hxx> +#include <sectfrm.hxx> +#include <node2lay.hxx> +#include <ndole.hxx> +#include <hints.hxx> +#include "layhelp.hxx" +#include <laycache.hxx> +#include <rootfrm.hxx> +#include <paratr.hxx> +#include <redline.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <calbck.hxx> +#include <ndtxt.hxx> +#include <undobj.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentTimerAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <frameformats.hxx> +#include <boost/circular_buffer.hpp> +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> + +using namespace ::com::sun::star; + +bool bObjsDirect = true; +bool bDontCreateObjects = false; +bool bSetCompletePaintOnInvalidate = false; + +sal_uInt8 StackHack::nCnt = 0; +bool StackHack::bLocked = false; + +SwFrameNotify::SwFrameNotify( SwFrame *pF ) : + mpFrame( pF ), + maFrame( pF->getFrameArea() ), + maPrt( pF->getFramePrintArea() ), + mbInvaKeep( false ), + mbValidSize( pF->isFrameAreaSizeValid() ) +{ + if ( pF->IsTextFrame() ) + { + mnFlyAnchorOfst = static_cast<SwTextFrame*>(pF)->GetBaseOffsetForFly( true ); + mnFlyAnchorOfstNoWrap = static_cast<SwTextFrame*>(pF)->GetBaseOffsetForFly( false ); + } + else + { + mnFlyAnchorOfst = 0; + mnFlyAnchorOfstNoWrap = 0; + } + + mbHadFollow = pF->IsContentFrame() && static_cast<SwContentFrame*>(pF)->GetFollow(); +} + +SwFrameNotify::~SwFrameNotify() COVERITY_NOEXCEPT_FALSE +{ + SwRectFnSet aRectFnSet(mpFrame); + const bool bAbsP = aRectFnSet.PosDiff(maFrame, mpFrame->getFrameArea()); + const bool bChgWidth = + aRectFnSet.GetWidth(maFrame) != aRectFnSet.GetWidth(mpFrame->getFrameArea()); + const bool bChgHeight = + aRectFnSet.GetHeight(maFrame)!=aRectFnSet.GetHeight(mpFrame->getFrameArea()); + const bool bChgFlyBasePos = mpFrame->IsTextFrame() && + ( ( mnFlyAnchorOfst != static_cast<SwTextFrame*>(mpFrame)->GetBaseOffsetForFly( true ) ) || + ( mnFlyAnchorOfstNoWrap != static_cast<SwTextFrame*>(mpFrame)->GetBaseOffsetForFly( false ) ) ); + + if ( mpFrame->IsFlowFrame() && !mpFrame->IsInFootnote() ) + { + SwFlowFrame *pFlow = SwFlowFrame::CastFlowFrame( mpFrame ); + + if ( !pFlow->IsFollow() ) + { + if ( !mpFrame->GetIndPrev() ) + { + if ( mbInvaKeep ) + { + SwFrame *pPre = mpFrame->FindPrev(); + if ( pPre && pPre->IsFlowFrame() ) + { + // 1. pPre wants to keep with me: + bool bInvalidPrePos = SwFlowFrame::CastFlowFrame(pPre)->IsKeep(pPre->GetAttrSet()->GetKeep(), pPre->GetBreakItem()) + && pPre->GetIndPrev(); + + // 2. pPre is a table and the last row wants to keep with me: + if ( !bInvalidPrePos && pPre->IsTabFrame() ) + { + SwTabFrame* pPreTab = static_cast<SwTabFrame*>(pPre); + if ( pPreTab->GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP) ) + { + SwRowFrame* pLastRow = static_cast<SwRowFrame*>(pPreTab->GetLastLower()); + if ( pLastRow && pLastRow->ShouldRowKeepWithNext() ) + bInvalidPrePos = true; + } + } + + if ( bInvalidPrePos ) + pPre->InvalidatePos(); + } + } + } + else if ( !pFlow->HasFollow() ) + { + long nOldHeight = aRectFnSet.GetHeight(maFrame); + long nNewHeight = aRectFnSet.GetHeight(mpFrame->getFrameArea()); + if( (nOldHeight > nNewHeight) || (!nOldHeight && nNewHeight) ) + pFlow->CheckKeep(); + } + } + } + + if ( bAbsP ) + { + mpFrame->SetCompletePaint(); + + SwFrame* pNxt = mpFrame->GetIndNext(); + // #121888# - skip empty section frames + while ( pNxt && + pNxt->IsSctFrame() && !static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = pNxt->GetIndNext(); + } + + if ( pNxt ) + pNxt->InvalidatePos(); + else + { + // #104100# - correct condition for setting retouche + // flag for vertical layout. + if( mpFrame->IsRetoucheFrame() && + aRectFnSet.TopDist( maFrame, aRectFnSet.GetTop(mpFrame->getFrameArea()) ) > 0 ) + { + mpFrame->SetRetouche(); + } + + // A fresh follow frame does not have to be invalidated, because + // it is already formatted: + if ( mbHadFollow || !mpFrame->IsContentFrame() || !static_cast<SwContentFrame*>(mpFrame)->GetFollow() ) + { + if ( !mpFrame->IsTabFrame() || !static_cast<SwTabFrame*>(mpFrame)->GetFollow() ) + mpFrame->InvalidateNextPos(); + } + } + } + + //For each resize of the background graphics is a repaint necessary. + const bool bPrtWidth = + aRectFnSet.GetWidth(maPrt) != aRectFnSet.GetWidth(mpFrame->getFramePrintArea()); + const bool bPrtHeight = + aRectFnSet.GetHeight(maPrt)!=aRectFnSet.GetHeight(mpFrame->getFramePrintArea()); + if ( bPrtWidth || bPrtHeight ) + { + bool bUseNewFillProperties(false); + if (mpFrame->supportsFullDrawingLayerFillAttributeSet()) + { + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes(mpFrame->getSdrAllFillAttributesHelper()); + if(aFillAttributes && aFillAttributes->isUsed()) + { + bUseNewFillProperties = true; + // use SetCompletePaint if needed + if(aFillAttributes->needCompleteRepaint()) + { + mpFrame->SetCompletePaint(); + } + } + } + if (!bUseNewFillProperties) + { + const SvxGraphicPosition ePos = mpFrame->GetAttrSet()->GetBackground().GetGraphicPos(); + if(GPOS_NONE != ePos && GPOS_TILED != ePos) + mpFrame->SetCompletePaint(); + } + } + else + { + // #97597# - consider case that *only* margins between + // frame and printing area has changed. Then, frame has to be repainted, + // in order to force paint of the margin areas. + if ( !bAbsP && (bChgWidth || bChgHeight) ) + { + mpFrame->SetCompletePaint(); + } + } + + const bool bPrtP = aRectFnSet.PosDiff( maPrt, mpFrame->getFramePrintArea() ); + if ( bAbsP || bPrtP || bChgWidth || bChgHeight || + bPrtWidth || bPrtHeight || bChgFlyBasePos ) + { + if( mpFrame->IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = mpFrame->getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( mpFrame, maFrame ); + } + } + + // Notification of anchored objects + if ( mpFrame->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *mpFrame->GetDrawObjs(); + SwPageFrame* pPageFrame = nullptr; + for (SwAnchoredObject* pObj : rObjs) + { + // OD 2004-03-31 #i26791# - no general distinction between + // Writer fly frames and drawing objects + bool bNotify = false; + bool bNotifySize = false; + SwContact* pContact = ::GetUserCall( pObj->GetDrawObj() ); + const bool bAnchoredAsChar = pContact->ObjAnchoredAsChar(); + if ( !bAnchoredAsChar ) + { + // Notify object, which aren't anchored as-character: + + // always notify objects, if frame position has changed + // or if the object is to-page|to-fly anchored. + if ( bAbsP || + pContact->ObjAnchoredAtPage() || + pContact->ObjAnchoredAtFly() ) + { + bNotify = true; + + // assure that to-fly anchored Writer fly frames are + // registered at the correct page frame, if frame + // position has changed. + if ( bAbsP && pContact->ObjAnchoredAtFly() && + dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + // determine to-fly anchored Writer fly frame + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pObj); + // determine page frame of to-fly anchored + // Writer fly frame + SwPageFrame* pFlyPageFrame = pFlyFrame->FindPageFrame(); + // determine page frame, if needed. + if ( !pPageFrame ) + { + pPageFrame = mpFrame->FindPageFrame(); + } + if ( pPageFrame != pFlyPageFrame ) + { + OSL_ENSURE( pFlyPageFrame, "~SwFrameNotify: Fly from Nowhere" ); + if( pFlyPageFrame ) + pFlyPageFrame->MoveFly( pFlyFrame, pPageFrame ); + else + pPageFrame->AppendFlyToPage( pFlyFrame ); + } + } + } + // otherwise the objects are notified in dependence to + // its positioning and alignment + else + { + const SwFormatVertOrient& rVert = + pContact->GetFormat()->GetVertOrient(); + if ( ( rVert.GetVertOrient() == text::VertOrientation::CENTER || + rVert.GetVertOrient() == text::VertOrientation::BOTTOM || + rVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) && + ( bChgHeight || bPrtHeight ) ) + { + bNotify = true; + } + if ( !bNotify ) + { + const SwFormatHoriOrient& rHori = + pContact->GetFormat()->GetHoriOrient(); + if ( ( rHori.GetHoriOrient() != text::HoriOrientation::NONE || + rHori.GetRelationOrient()== text::RelOrientation::PRINT_AREA || + rHori.GetRelationOrient()== text::RelOrientation::FRAME ) && + ( bChgWidth || bPrtWidth || bChgFlyBasePos ) ) + { + bNotify = true; + } + } + } + } + else if ( bPrtWidth ) + { + // Notify as-character anchored objects, if printing area + // width has changed. + bNotify = true; + bNotifySize = true; + } + + // perform notification via the corresponding invalidations + if ( bNotify ) + { + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pObj); + if ( bNotifySize ) + pFlyFrame->InvalidateSize_(); + // #115759# - no invalidation of + // position for as-character anchored objects. + if ( !bAnchoredAsChar ) + { + pFlyFrame->InvalidatePos_(); + } + pFlyFrame->Invalidate_(); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + // #115759# - no invalidation of + // position for as-character anchored objects. + if ( !bAnchoredAsChar ) + { + pObj->InvalidateObjPos(); + } + } + else + { + OSL_FAIL( "<SwContentNotify::~SwContentNotify()> - unknown anchored object type." ); + } + } + } + } + } + else if( mpFrame->IsTextFrame() && mbValidSize != mpFrame->isFrameAreaSizeValid() ) + { + SwRootFrame *pRootFrame = mpFrame->getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->InvalidateAccessibleFrameContent( mpFrame ); + } + } + + // #i9046# Automatic frame width + SwFlyFrame* pFly = nullptr; + // #i35879# Do not trust the inf flags. pFrame does not + // necessarily have to have an upper! + if ( !mpFrame->IsFlyFrame() && nullptr != ( pFly = mpFrame->ImplFindFlyFrame() ) ) + { + // #i61999# + // no invalidation of columned Writer fly frames, because automatic + // width doesn't make sense for such Writer fly frames. + if ( pFly->Lower() && !pFly->Lower()->IsColumnFrame() ) + { + const SwFormatFrameSize &rFrameSz = pFly->GetFormat()->GetFrameSize(); + + // This could be optimized. Basically the fly frame only has to + // be invalidated, if the first line of pFrame (if pFrame is a content + // frame, for other frame types it's the print area) has changed its + // size and pFrame was responsible for the current width of pFly. On + // the other hand, this is only rarely used and re-calculation of + // the fly frame does not cause too much trouble. So we keep it this + // way: + if ( SwFrameSize::Fixed != rFrameSz.GetWidthSizeType() ) + { + // #i50668#, #i50998# - invalidation of position + // of as-character anchored fly frames not needed and can cause + // layout loops + if ( dynamic_cast<const SwFlyInContentFrame*>( pFly) == nullptr ) + { + pFly->InvalidatePos(); + } + pFly->InvalidateSize(); + } + } + } +} + +SwLayNotify::SwLayNotify( SwLayoutFrame *pLayFrame ) : + SwFrameNotify( pLayFrame ), + m_bLowersComplete( false ) +{ +} + +// OD 2004-05-11 #i28701# - local method to invalidate the position of all +// frames inclusive its floating screen objects, which are lowers of the given +// layout frame +static void lcl_InvalidatePosOfLowers( SwLayoutFrame& _rLayoutFrame ) +{ + if( _rLayoutFrame.IsFlyFrame() && _rLayoutFrame.GetDrawObjs() ) + { + _rLayoutFrame.InvalidateObjs( false ); + } + + SwFrame* pLowerFrame = _rLayoutFrame.Lower(); + while ( pLowerFrame ) + { + pLowerFrame->InvalidatePos(); + if ( pLowerFrame->IsTextFrame() ) + { + static_cast<SwTextFrame*>(pLowerFrame)->Prepare( PrepareHint::FramePositionChanged ); + } + else if ( pLowerFrame->IsTabFrame() ) + { + pLowerFrame->InvalidatePrt(); + } + + pLowerFrame->InvalidateObjs( false ); + + pLowerFrame = pLowerFrame->GetNext(); + } +} + +SwLayNotify::~SwLayNotify() +{ + SwLayoutFrame *pLay = static_cast<SwLayoutFrame*>(mpFrame); + SwRectFnSet aRectFnSet(pLay); + bool bNotify = false; + if ( pLay->getFramePrintArea().SSize() != maPrt.SSize() ) + { + if ( !IsLowersComplete() ) + { + bool bInvaPercent; + + if ( pLay->IsRowFrame() ) + { + bInvaPercent = true; + long nNew = aRectFnSet.GetHeight(pLay->getFramePrintArea()); + if( nNew != aRectFnSet.GetHeight(maPrt) ) + static_cast<SwRowFrame*>(pLay)->AdjustCells( nNew, true); + if( aRectFnSet.GetWidth(pLay->getFramePrintArea()) + != aRectFnSet.GetWidth(maPrt) ) + static_cast<SwRowFrame*>(pLay)->AdjustCells( 0, false ); + } + else + { + //Proportional adoption of the internal. + //1. If the formatted is no Fly + //2. If he contains no columns + //3. If the Fly has a fixed height and the columns + // are next to be. + // Hoehe danebenliegen. + //4. Never at SectionFrames. + bool bLow; + if( pLay->IsFlyFrame() ) + { + if ( pLay->Lower() ) + { + bLow = !pLay->Lower()->IsColumnFrame() || + aRectFnSet.GetHeight(pLay->Lower()->getFrameArea()) + != aRectFnSet.GetHeight(pLay->getFramePrintArea()); + } + else + bLow = false; + } + else if( pLay->IsSctFrame() ) + { + if ( pLay->Lower() ) + { + if( pLay->Lower()->IsColumnFrame() && pLay->Lower()->GetNext() ) + bLow = pLay->Lower()->getFrameArea().Height() != pLay->getFramePrintArea().Height(); + else + bLow = pLay->getFramePrintArea().Width() != maPrt.Width(); + } + else + bLow = false; + } + else if( pLay->IsFooterFrame() && !pLay->HasFixSize() ) + bLow = pLay->getFramePrintArea().Width() != maPrt.Width(); + else + bLow = true; + bInvaPercent = bLow; + if ( bLow ) + { + pLay->ChgLowersProp( maPrt.SSize() ); + } + // If the PrtArea has been extended, it might be possible that the chain of parts + // can take another frame. As a result, the "possible right one" needs to be + // invalidated. This only pays off if this or its Uppers are moveable sections. + // A PrtArea has been extended if width or height are larger than before. + if ( (pLay->getFramePrintArea().Height() > maPrt.Height() || + pLay->getFramePrintArea().Width() > maPrt.Width()) && + (pLay->IsMoveable() || pLay->IsFlyFrame()) ) + { + SwFrame *pTmpFrame = pLay->Lower(); + if ( pTmpFrame && pTmpFrame->IsFlowFrame() ) + { + while ( pTmpFrame->GetNext() ) + pTmpFrame = pTmpFrame->GetNext(); + pTmpFrame->InvalidateNextPos(); + } + } + } + bNotify = true; + //EXPENSIVE!! But how we do it more elegant? + if( bInvaPercent ) + pLay->InvaPercentLowers( pLay->getFramePrintArea().Height() - maPrt.Height() ); + } + if ( pLay->IsTabFrame() ) + //So that _only_ the shadow is drawn while resizing. + static_cast<SwTabFrame*>(pLay)->SetComplete(); + else + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + if( !( pSh && pSh->GetViewOptions()->getBrowseMode() ) || + !(pLay->GetType() & (SwFrameType::Body | SwFrameType::Page)) ) + //Thereby the subordinates are retouched clean. + //Example problem: Take the Flys with the handles and downsize. + //Not for body and page, otherwise it flickers when loading HTML. + pLay->SetCompletePaint(); + } + } + //Notify Lower if the position has changed. + const bool bPrtPos = aRectFnSet.PosDiff( maPrt, pLay->getFramePrintArea() ); + const bool bPos = bPrtPos || aRectFnSet.PosDiff( maFrame, pLay->getFrameArea() ); + const bool bSize = pLay->getFrameArea().SSize() != maFrame.SSize(); + + if ( bPos && pLay->Lower() && !IsLowersComplete() ) + { + pLay->Lower()->InvalidatePos(); + SwFootnoteFrame* pFtnFrame = pLay->Lower()->IsFootnoteFrame() ? + static_cast<SwFootnoteFrame*>(pLay->Lower()) : nullptr; + SwFrame* pFtnLower = pFtnFrame ? pFtnFrame->Lower() : nullptr; + if (pFtnLower) + pFtnLower->InvalidatePos(); + } + + if ( bPrtPos ) + pLay->SetCompletePaint(); + + //Inform the Follower if the SSize has changed. + if ( bSize ) + { + if( pLay->GetNext() ) + { + if ( pLay->GetNext()->IsLayoutFrame() ) + pLay->GetNext()->InvalidatePos_(); + else + pLay->GetNext()->InvalidatePos(); + } + else if( pLay->IsSctFrame() ) + pLay->InvalidateNextPos(); + } + if ( !IsLowersComplete() && + !(pLay->GetType()&(SwFrameType::Fly|SwFrameType::Section) && + pLay->Lower() && pLay->Lower()->IsColumnFrame()) && + (bPos || bNotify) && + !(pLay->GetType() & (SwFrameType::Row|SwFrameType::Tab|SwFrameType::FtnCont|SwFrameType::Page|SwFrameType::Root))) + { + // #i44016# - force unlock of position of lower objects. + // #i43913# - no unlock of position of objects, + // if <pLay> is a cell frame, and its table frame resp. its parent table + // frame is locked. + // #i47458# - force unlock of position of lower objects, + // only if position of layout frame has changed. + bool bUnlockPosOfObjs( bPos ); + if ( bUnlockPosOfObjs && pLay->IsCellFrame() ) + { + SwTabFrame* pTabFrame( pLay->FindTabFrame() ); + if ( pTabFrame && + ( pTabFrame->IsJoinLocked() || + ( pTabFrame->IsFollow() && + pTabFrame->FindMaster()->IsJoinLocked() ) ) ) + { + bUnlockPosOfObjs = false; + } + } + // #i49383# - check for footnote frame, if unlock + // of position of lower objects is allowed. + else if ( bUnlockPosOfObjs && pLay->IsFootnoteFrame() ) + { + bUnlockPosOfObjs = static_cast<SwFootnoteFrame*>(pLay)->IsUnlockPosOfLowerObjs(); + } + // #i51303# - no unlock of object positions for sections + else if ( bUnlockPosOfObjs && pLay->IsSctFrame() ) + { + bUnlockPosOfObjs = false; + } + pLay->NotifyLowerObjs( bUnlockPosOfObjs ); + } + if ( bPos && pLay->IsFootnoteFrame() && pLay->Lower() ) + { + // OD 2004-05-11 #i28701# + ::lcl_InvalidatePosOfLowers( *pLay ); + } + if( ( bPos || bSize ) && pLay->IsFlyFrame() && static_cast<SwFlyFrame*>(pLay)->GetAnchorFrame() + && static_cast<SwFlyFrame*>(pLay)->GetAnchorFrame()->IsFlyFrame() ) + static_cast<SwFlyFrame*>(pLay)->AnchorFrame()->InvalidateSize(); +} + +SwFlyNotify::SwFlyNotify( SwFlyFrame *pFlyFrame ) : + SwLayNotify( pFlyFrame ), + // #115759# - keep correct page frame - the page frame + // the Writer fly frame is currently registered at. + pOldPage( pFlyFrame->GetPageFrame() ), + aFrameAndSpace( pFlyFrame->GetObjRectWithSpaces() ) +{ +} + +SwFlyNotify::~SwFlyNotify() +{ + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(mpFrame); + if ( pFly->IsNotifyBack() ) + { + SwViewShell *pSh = pFly->getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + if ( !pImp || !pImp->IsAction() || !pImp->GetLayAction().IsAgain() ) + { + //If in the LayAction the IsAgain is set it can be + //that the old page is destroyed in the meantime! + ::Notify( pFly, pOldPage, aFrameAndSpace, &maPrt ); + // #i35640# - additional notify anchor text frame, + // if Writer fly frame has changed its page + if ( pFly->GetAnchorFrame()->IsTextFrame() && + pFly->GetPageFrame() != pOldPage ) + { + pFly->AnchorFrame()->Prepare( PrepareHint::FlyFrameLeave ); + } + } + pFly->ResetNotifyBack(); + } + + //Have the size or the position changed, + //so should the view know this. + SwRectFnSet aRectFnSet(pFly); + const bool bPosChgd = aRectFnSet.PosDiff( maFrame, pFly->getFrameArea() ); + const bool bFrameChgd = pFly->getFrameArea().SSize() != maFrame.SSize(); + const bool bPrtChgd = maPrt != pFly->getFramePrintArea(); + if ( bPosChgd || bFrameChgd || bPrtChgd ) + { + pFly->NotifyDrawObj(); + } + if ( bPosChgd && maFrame.Pos().X() != FAR_AWAY ) + { + // OD 2004-05-10 #i28701# - no direct move of lower Writer fly frames. + // reason: New positioning and alignment (e.g. to-paragraph anchored, + // but aligned at page) are introduced. + // <SwLayNotify::~SwLayNotify()> takes care of invalidation of lower + // floating screen objects by calling method <SwLayoutFrame::NotifyLowerObjs()>. + + if ( pFly->IsFlyAtContentFrame() ) + { + SwFrame *pNxt = pFly->AnchorFrame()->FindNext(); + if ( pNxt ) + { + pNxt->InvalidatePos(); + } + } + + // #i26945# - notify anchor. + // Needed for negative positioned Writer fly frames + if ( pFly->GetAnchorFrame()->IsTextFrame() ) + { + pFly->AnchorFrame()->Prepare( PrepareHint::FlyFrameLeave ); + } + } + + // OD 2004-05-13 #i28701# + // #i45180# - no adjustment of layout process flags and + // further notifications/invalidations, if format is called by grow/shrink + if ( pFly->ConsiderObjWrapInfluenceOnObjPos() && + ( dynamic_cast<const SwFlyFreeFrame*>( pFly) == nullptr || + !static_cast<SwFlyFreeFrame*>(pFly)->IsNoMoveOnCheckClip() ) ) + { + // #i54138# - suppress restart of the layout process + // on changed frame height. + // Note: It doesn't seem to be necessary and can cause layout loops. + if ( bPosChgd ) + { + // indicate a restart of the layout process + pFly->SetRestartLayoutProcess( true ); + } + else + { + // lock position + pFly->LockPosition(); + } + + if ( !pFly->ConsiderForTextWrap() ) + { + // indicate that object has to be considered for text wrap + pFly->SetConsiderForTextWrap( true ); + // invalidate 'background' in order to allow its 'background' + // to wrap around it. + pFly->NotifyBackground( pFly->GetPageFrame(), + pFly->GetObjRectWithSpaces(), + PrepareHint::FlyFrameArrive ); + // invalidate position of anchor frame in order to force + // a re-format of the anchor frame, which also causes a + // re-format of the invalid previous frames of the anchor frame. + pFly->AnchorFrame()->InvalidatePos(); + } + } +} + +SwContentNotify::SwContentNotify( SwContentFrame *pContentFrame ) : + SwFrameNotify( pContentFrame ), + // OD 08.01.2004 #i11859# + mbChkHeightOfLastLine( false ), + mnHeightOfLastLine( 0 ), + // OD 2004-02-26 #i25029# + mbInvalidatePrevPrtArea( false ), + mbBordersJoinedWithPrev( false ) +{ + // OD 08.01.2004 #i11859# + if ( pContentFrame->IsTextFrame() ) + { + SwTextFrame* pTextFrame = static_cast<SwTextFrame*>(pContentFrame); + if (!pTextFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::OLD_LINE_SPACING)) + { + const SvxLineSpacingItem &rSpace = pTextFrame->GetAttrSet()->GetLineSpacing(); + if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + mbChkHeightOfLastLine = true; + mnHeightOfLastLine = pTextFrame->GetHeightOfLastLine(); + } + } + } +} + +SwContentNotify::~SwContentNotify() +{ + SwContentFrame *pCnt = static_cast<SwContentFrame*>(mpFrame); + if ( bSetCompletePaintOnInvalidate ) + pCnt->SetCompletePaint(); + + SwRectFnSet aRectFnSet(pCnt); + if ( pCnt->IsInTab() && ( aRectFnSet.PosDiff( pCnt->getFrameArea(), maFrame ) || + pCnt->getFrameArea().SSize() != maFrame.SSize())) + { + SwLayoutFrame* pCell = pCnt->GetUpper(); + while( !pCell->IsCellFrame() && pCell->GetUpper() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell->IsCellFrame(), "Where's my cell?" ); + if ( text::VertOrientation::NONE != pCell->GetFormat()->GetVertOrient().GetVertOrient() ) + pCell->InvalidatePrt(); //for the vertical align. + } + + // OD 2004-02-26 #i25029# + if ( mbInvalidatePrevPrtArea && mbBordersJoinedWithPrev && + pCnt->IsTextFrame() && + !pCnt->IsFollow() && !pCnt->GetIndPrev() ) + { + // determine previous frame + SwFrame* pPrevFrame = pCnt->FindPrev(); + // skip empty section frames and hidden text frames + { + while ( pPrevFrame && + ( ( pPrevFrame->IsSctFrame() && + !static_cast<SwSectionFrame*>(pPrevFrame)->GetSection() ) || + ( pPrevFrame->IsTextFrame() && + static_cast<SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) ) ) + { + pPrevFrame = pPrevFrame->FindPrev(); + } + } + + // Invalidate printing area of found previous frame + if ( pPrevFrame ) + { + if ( pPrevFrame->IsSctFrame() ) + { + if ( pCnt->IsInSct() ) + { + // Note: found previous frame is a section frame and + // <pCnt> is also inside a section. + // Thus due to <mbBordersJoinedWithPrev>, + // <pCnt> had joined its borders/shadow with the + // last content of the found section. + // Invalidate printing area of last content in found section. + SwFrame* pLstContentOfSctFrame = + static_cast<SwSectionFrame*>(pPrevFrame)->FindLastContent(); + if ( pLstContentOfSctFrame ) + { + pLstContentOfSctFrame->InvalidatePrt(); + } + } + } + else + { + pPrevFrame->InvalidatePrt(); + } + } + } + + const bool bFirst = aRectFnSet.GetWidth(maFrame) == 0; + + if ( pCnt->IsNoTextFrame() ) + { + //Active PlugIn's or OLE-Objects should know something of the change + //thereby they move their window appropriate. + SwViewShell *pSh = pCnt->getRootFrame()->GetCurrShell(); + if ( pSh ) + { + SwOLENode *const pNd(static_cast<SwNoTextFrame*>(pCnt)->GetNode()->GetOLENode()); + if (nullptr != pNd && + (pNd->GetOLEObj().IsOleRef() || + pNd->IsOLESizeInvalid()) ) + { + const bool bNoTextFramePrtAreaChanged = + ( maPrt.SSize().Width() != 0 && + maPrt.SSize().Height() != 0 ) && + maPrt.SSize() != pCnt->getFramePrintArea().SSize(); + OSL_ENSURE( pCnt->IsInFly(), "OLE not in FlyFrame" ); + SwFlyFrame *pFly = pCnt->FindFlyFrame(); + svt::EmbeddedObjectRef& xObj = pNd->GetOLEObj().GetObject(); + SwFEShell *pFESh = nullptr; + for(SwViewShell& rCurrentShell : pSh->GetRingContainer()) + { if ( dynamic_cast<const SwCursorShell*>( &rCurrentShell) != nullptr ) + { + pFESh = static_cast<SwFEShell*>(&rCurrentShell); + // #108369#: Here used to be the condition if (!bFirst). + // I think this should mean "do not call CalcAndSetScale" + // if the frame is formatted for the first time. + // Unfortunately this is not valid anymore since the + // SwNoTextFrame already gets a width during CalcLowerPreps. + // Nevertheless, the indention of !bFirst seemed to be + // to assure that the OLE objects have already been notified + // if necessary before calling CalcAndSetScale. + // So I replaced !bFirst by !IsOLESizeInvalid. There is + // one additional problem specific to the word import: + // The layout is calculated _before_ calling PrtOLENotify, + // and the OLE objects are not invalidated during import. + // Therefore I added the condition !IsUpdateExpField, + // have a look at the occurrence of CalcLayout in + // uiview/view.cxx. + if ( !pNd->IsOLESizeInvalid() && + !pSh->GetDoc()->getIDocumentState().IsUpdateExpField() ) + pFESh->CalcAndSetScale( xObj, + &pFly->getFramePrintArea(), &pFly->getFrameArea(), + bNoTextFramePrtAreaChanged ); + } + } + + if ( pFESh && pNd->IsOLESizeInvalid() ) + { + pNd->SetOLESizeInvalid( false ); + pFESh->CalcAndSetScale( xObj ); // create client + } + } + //dito animated graphics + if ( getFrameArea().HasArea() && static_cast<SwNoTextFrame*>(pCnt)->HasAnimation() ) + { + static_cast<SwNoTextFrame*>(pCnt)->StopAnimation(); + pSh->InvalidateWindows( getFrameArea() ); + } + } + } + + if ( bFirst ) + { + pCnt->SetRetouche(); //fix(13870) + + SwDoc *const pDoc = pCnt->IsTextFrame() + ? &static_cast<SwTextFrame*>(pCnt)->GetDoc() + : static_cast<SwNoTextFrame*>(pCnt)->GetNode()->GetDoc(); + if ( !pDoc->GetSpzFrameFormats()->empty() && + pDoc->DoesContainAtPageObjWithContentAnchor() && !pDoc->getIDocumentState().IsNewDoc() ) + { + // If certain import filters for foreign file format import + // AT_PAGE anchored objects, the corresponding page number is + // typically not known. In this case the content position is + // stored at which the anchored object is found in the + // imported document. + // When this content is formatted it is the time at which + // the page is known. Thus, this data can be corrected now. + + const SwPageFrame *pPage = nullptr; + SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + + for ( size_t i = 0; i < pTable->size(); ++i ) + { + SwFrameFormat *pFormat = (*pTable)[i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId() || + rAnch.GetContentAnchor() == nullptr ) + { + continue; + } + + if (FrameContainsNode(*pCnt, rAnch.GetContentAnchor()->nNode.GetIndex())) + { + OSL_FAIL( "<SwContentNotify::~SwContentNotify()> - to page anchored object with content position." ); + if ( !pPage ) + { + pPage = pCnt->FindPageFrame(); + } + SwFormatAnchor aAnch( rAnch ); + aAnch.SetAnchor( nullptr ); + aAnch.SetPageNum( pPage->GetPhyPageNum() ); + pFormat->SetFormatAttr( aAnch ); + if ( RES_DRAWFRMFMT != pFormat->Which() ) + { + pFormat->MakeFrames(); + } + } + } + } + } + + // OD 12.01.2004 #i11859# - invalidate printing area of following frame, + // if height of last line has changed. + if ( pCnt->IsTextFrame() && mbChkHeightOfLastLine ) + { + if ( mnHeightOfLastLine != static_cast<SwTextFrame*>(pCnt)->GetHeightOfLastLine() ) + { + pCnt->InvalidateNextPrtArea(); + } + } + + // #i44049# + if ( pCnt->IsTextFrame() && aRectFnSet.PosDiff( maFrame, pCnt->getFrameArea() ) ) + { + pCnt->InvalidateObjs(); + } + + // #i43255# - move code to invalidate at-character + // anchored objects due to a change of its anchor character from + // method <SwTextFrame::Format(..)>. + if ( pCnt->IsTextFrame() ) + { + SwTextFrame* pMasterFrame = pCnt->IsFollow() + ? static_cast<SwTextFrame*>(pCnt)->FindMaster() + : static_cast<SwTextFrame*>(pCnt); + if ( pMasterFrame && !pMasterFrame->IsFlyLock() && + pMasterFrame->GetDrawObjs() ) + { + SwSortedObjs* pObjs = pMasterFrame->GetDrawObjs(); + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AT_CHAR ) + { + pAnchoredObj->CheckCharRectAndTopOfLine( !pMasterFrame->IsEmpty() ); + } + } + } + } +} + +// note this *cannot* be static because it's a friend +void AppendObj(SwFrame *const pFrame, SwPageFrame *const pPage, SwFrameFormat *const pFormat, const SwFormatAnchor & rAnch) +{ + const bool bFlyAtFly = rAnch.GetAnchorId() == RndStdIds::FLY_AT_FLY; // LAYER_IMPL + //Is a frame or a SdrObject described? + const bool bSdrObj = RES_DRAWFRMFMT == pFormat->Which(); + // OD 23.06.2003 #108784# - append also drawing objects anchored + // as character. + const bool bDrawObjInContent = bSdrObj && + (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + + if( bFlyAtFly || + (rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) || + (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + bDrawObjInContent ) + { + SdrObject* pSdrObj = nullptr; + if ( bSdrObj && nullptr == (pSdrObj = pFormat->FindSdrObject()) ) + { + OSL_ENSURE( !bSdrObj, "DrawObject not found." ); + pFormat->GetDoc()->DelFrameFormat( pFormat ); + return; + } + if ( pSdrObj ) + { + if ( !pSdrObj->getSdrPageFromSdrObject() ) + { + pFormat->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0)-> + InsertObject(pSdrObj, pSdrObj->GetOrdNumDirect()); + } + + SwDrawContact* pNew = + static_cast<SwDrawContact*>(GetUserCall( pSdrObj )); + if ( !pNew->GetAnchorFrame() ) + { + pFrame->AppendDrawObj( *(pNew->GetAnchoredObj( nullptr )) ); + } + // OD 19.06.2003 #108784# - add 'virtual' drawing object, + // if necessary. But control objects have to be excluded. + else if ( !::CheckControlLayer( pSdrObj ) && + pNew->GetAnchorFrame() != pFrame && + !pNew->GetDrawObjectByAnchorFrame( *pFrame ) ) + { + SwDrawVirtObj* pDrawVirtObj = pNew->AddVirtObj(); + pFrame->AppendDrawObj( *(pNew->GetAnchoredObj( pDrawVirtObj )) ); + + pDrawVirtObj->ActionChanged(); + } + } + else + { + SwFlyFrame *pFly; + if( bFlyAtFly ) + pFly = new SwFlyLayFrame( static_cast<SwFlyFrameFormat*>(pFormat), pFrame, pFrame ); + else + pFly = new SwFlyAtContentFrame( static_cast<SwFlyFrameFormat*>(pFormat), pFrame, pFrame ); + pFly->Lock(); + pFrame->AppendFly( pFly ); + pFly->Unlock(); + if ( pPage ) + ::RegistFlys( pPage, pFly ); + } + } +} + +static bool IsShown(sal_uLong const nIndex, + const SwFormatAnchor & rAnch, + std::vector<sw::Extent>::const_iterator const*const pIter, + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ + assert(!pIter || *pIter == *pEnd || (*pIter)->pNode->GetIndex() == nIndex); + SwPosition const& rAnchor(*rAnch.GetContentAnchor()); + if (rAnchor.nNode.GetIndex() != nIndex) + { + return false; + } + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + return pIter == nullptr // not merged + || pIter != pEnd // at least one char visible in node + || !IsSelectFrameAnchoredAtPara(rAnchor, + SwPosition(const_cast<SwTextNode&>(*pFirstNode), 0), + SwPosition(const_cast<SwTextNode&>(*pLastNode), pLastNode->Len())); + } + if (pIter) + { + // note: frames are not sorted by anchor position. + assert(pEnd); + assert(pFirstNode); + assert(pLastNode); + assert(rAnch.GetAnchorId() != RndStdIds::FLY_AT_FLY); + for (auto iter = *pIter; iter != *pEnd; ++iter) + { + assert(iter->nStart != iter->nEnd); // TODO possible? + assert(iter->pNode->GetIndex() == nIndex); + if (rAnchor.nContent.GetIndex() < iter->nStart) + { + return false; + } + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + // if there is an extent then obviously the node was not + // deleted fully... + // show if start <= pos <= end + // *or* if first-node/0 *and* not StartOfSection + // *or* if last-node/Len *and* not EndOfSection + + // first determine the extent to compare to, then + // construct start/end positions for the deletion *before* the + // extent and compare once. + // the interesting corner cases are on the edge of the extent! + // no need to check for > the last extent because those + // are never visible. + if (rAnchor.nContent.GetIndex() <= iter->nEnd) + { + if (iter->nStart == 0) + { + return true; + } + else + { + SwPosition const start( + const_cast<SwTextNode&>( + iter == *pIter + ? *pFirstNode // simplification + : *iter->pNode), + iter == *pIter // first extent? + ? iter->pNode == pFirstNode + ? 0 // at start of 1st node + : pFirstNode->Len() // previous node; simplification but should get right result + : (iter-1)->nEnd); // previous extent + SwPosition const end(*iter->pNode, iter->nStart); + return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end); + } + } + else if (iter == *pEnd - 1) // special case: after last extent + { + if (iter->nEnd == iter->pNode->Len()) + { + return true; // special case: end of node + } + else + { + SwPosition const start(*iter->pNode, iter->nEnd); + SwPosition const end( + const_cast<SwTextNode&>(*pLastNode), // simplification + iter->pNode == pLastNode + ? iter->pNode->Len() + : 0); + return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end); + } + } + } + else + { + assert(rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + // for AS_CHAR obviously must be < + if (rAnchor.nContent.GetIndex() < iter->nEnd) + { + return true; + } + } + } + return false; + } + else + { + return true; + } +} + +void RemoveHiddenObjsOfNode(SwTextNode const& rNode, + std::vector<sw::Extent>::const_iterator const*const pIter, + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ + std::vector<SwFrameFormat*> const*const pFlys(rNode.GetAnchoredFlys()); + if (!pFlys) + { + return; + } + for (SwFrameFormat * pFrameFormat : *pFlys) + { + SwFormatAnchor const& rAnchor = pFrameFormat->GetAnchor(); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR + || (rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR + && RES_DRAWFRMFMT == pFrameFormat->Which())) + { + assert(rAnchor.GetContentAnchor()->nNode.GetIndex() == rNode.GetIndex()); + if (!IsShown(rNode.GetIndex(), rAnchor, pIter, pEnd, pFirstNode, pLastNode)) + { + pFrameFormat->DelFrames(); + } + } + } +} + +void AppendObjsOfNode(SwFrameFormats const*const pTable, sal_uLong const nIndex, + SwFrame *const pFrame, SwPageFrame *const pPage, SwDoc *const pDoc, + std::vector<sw::Extent>::const_iterator const*const pIter, + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ +#if OSL_DEBUG_LEVEL > 0 + std::vector<SwFrameFormat*> checkFormats; + for ( size_t i = 0; i < pTable->size(); ++i ) + { + SwFrameFormat *pFormat = (*pTable)[i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( rAnch.GetContentAnchor() && + IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode)) + { + checkFormats.push_back( pFormat ); + } + } +#else + (void)pTable; +#endif + + SwNode const& rNode(*pDoc->GetNodes()[nIndex]); + std::vector<SwFrameFormat*> const*const pFlys(rNode.GetAnchoredFlys()); + for (size_t it = 0; pFlys && it != pFlys->size(); ) + { + SwFrameFormat *const pFormat = (*pFlys)[it]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( rAnch.GetContentAnchor() && + IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode)) + { +#if OSL_DEBUG_LEVEL > 0 + std::vector<SwFrameFormat*>::iterator checkPos = std::find( checkFormats.begin(), checkFormats.end(), pFormat ); + assert( checkPos != checkFormats.end()); + checkFormats.erase( checkPos ); +#endif + AppendObj(pFrame, pPage, pFormat, rAnch); + } + ++it; + } + +#if OSL_DEBUG_LEVEL > 0 + assert( checkFormats.empty()); +#endif +} + + +void AppendObjs(const SwFrameFormats *const pTable, sal_uLong const nIndex, + SwFrame *const pFrame, SwPageFrame *const pPage, SwDoc *const pDoc) +{ + if (pFrame->IsTextFrame()) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pFrame)); + if (sw::MergedPara const*const pMerged = pTextFrame->GetMergedPara()) + { + std::vector<sw::Extent>::const_iterator iterFirst(pMerged->extents.begin()); + std::vector<sw::Extent>::const_iterator iter(iterFirst); + SwTextNode const* pNode(pMerged->pFirstNode); + for ( ; ; ++iter) + { + if (iter == pMerged->extents.end() + || iter->pNode != pNode) + { + AppendObjsOfNode(pTable, pNode->GetIndex(), pFrame, pPage, pDoc, + &iterFirst, &iter, pMerged->pFirstNode, pMerged->pLastNode); + sal_uLong const until = iter == pMerged->extents.end() + ? pMerged->pLastNode->GetIndex() + 1 + : iter->pNode->GetIndex(); + for (sal_uLong i = pNode->GetIndex() + 1; i < until; ++i) + { + // let's show at-para flys on nodes that contain start/end of + // redline too, even if there's no text there + SwNode const*const pTmp(pNode->GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + AppendObjsOfNode(pTable, pTmp->GetIndex(), pFrame, pPage, pDoc, &iter, &iter, pMerged->pFirstNode, pMerged->pLastNode); + } + } + if (iter == pMerged->extents.end()) + { + break; + } + pNode = iter->pNode; + iterFirst = iter; + } + } + } + else + { + return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr); + } + } + else + { + return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr); + } +} + +bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor) +{ + assert(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA || + rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR || + rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + bool ret(true); + if (auto const pMergedPara = rFrame.GetMergedPara()) + { + ret = false; + auto const pAnchor(rAnchor.GetContentAnchor()); + auto iterFirst(pMergedPara->extents.cbegin()); + if (iterFirst == pMergedPara->extents.end() + && (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)) + { + ret = (&pAnchor->nNode.GetNode() == pMergedPara->pFirstNode + && (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || pAnchor->nContent == 0)) + || (&pAnchor->nNode.GetNode() == pMergedPara->pLastNode + && (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || pAnchor->nContent == pMergedPara->pLastNode->Len())); + } + auto iter(iterFirst); + SwTextNode const* pNode(pMergedPara->pFirstNode); + for ( ; ; ++iter) + { + if (iter == pMergedPara->extents.end() + || iter->pNode != pNode) + { + assert(pNode->GetRedlineMergeFlag() != SwNode::Merge::Hidden); + if (pNode == &pAnchor->nNode.GetNode()) + { + ret = IsShown(pNode->GetIndex(), rAnchor, &iterFirst, &iter, + pMergedPara->pFirstNode, pMergedPara->pLastNode); + break; + } + if (iter == pMergedPara->extents.end()) + { + break; + } + pNode = iter->pNode; + if (pAnchor->nNode.GetIndex() < pNode->GetIndex()) + { + break; + } + iterFirst = iter; + } + } + } + return ret; +} + +void AppendAllObjs(const SwFrameFormats* pTable, const SwFrame* pSib) +{ + //Connecting of all Objects, which are described in the SpzTable with the + //layout. + + boost::circular_buffer<SwFrameFormat*> vFormatsToConnect(pTable->size()); + for(const auto& pFormat : *pTable) + { + const auto& rAnch = pFormat->GetAnchor(); + // Formats can still remain, because we neither use character bound + // frames nor objects which are anchored to character bounds. + if ((rAnch.GetAnchorId() != RndStdIds::FLY_AT_PAGE) && (rAnch.GetAnchorId() != RndStdIds::FLY_AS_CHAR)) + { + auto pContentAnchor = rAnch.GetContentAnchor(); + // formats in header/footer have no dependencies + if(pContentAnchor && pFormat->GetDoc()->IsInHeaderFooter(pContentAnchor->nNode)) + pFormat->MakeFrames(); + else + vFormatsToConnect.push_back(pFormat); + } + } + const SwRootFrame* pRoot = pSib ? pSib->getRootFrame() : nullptr; + const SwFrameFormat* pFirstRequeued(nullptr); + while(!vFormatsToConnect.empty()) + { + auto& pFormat = vFormatsToConnect.front(); + bool isConnected(false); + pFormat->CallSwClientNotify(sw::GetObjectConnectedHint(isConnected, pRoot)); + if(!isConnected) + { + pFormat->MakeFrames(); + pFormat->CallSwClientNotify(sw::GetObjectConnectedHint(isConnected, pRoot)); + } + // do this *before* push_back! the circular_buffer can be "full"! + vFormatsToConnect.pop_front(); + if (!isConnected) + { + if(pFirstRequeued == pFormat) + // If nothing happens anymore we can stop. + break; + if(!pFirstRequeued) + pFirstRequeued = pFormat; + assert(!vFormatsToConnect.full()); + vFormatsToConnect.push_back(pFormat); + } + else + { + pFirstRequeued = nullptr; + } + } +} + +namespace sw { + +void RecreateStartTextFrames(SwTextNode & rNode) +{ + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + auto eMode(sw::FrameMode::Existing); + for (SwTextFrame * pFrame : frames) + { + // SplitNode could have moved the original frame to the start node + // & created a new one on end, or could have created new frame on + // start node... grab start node's frame and recreate MergedPara. + SwTextNode & rFirstNode(pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pFirstNode + : rNode); + assert(rFirstNode.GetIndex() <= rNode.GetIndex()); + // clear old one first to avoid DelFrames confusing updates & asserts... + pFrame->SetMergedPara(nullptr); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + eMode = sw::FrameMode::New; // Existing is not idempotent! + // note: this may or may not delete frames on the end node + } +} + +} // namespace sw + +/** local method to set 'working' position for newly inserted frames + + OD 12.08.2003 #i17969# +*/ +static void lcl_SetPos( SwFrame& _rNewFrame, + const SwLayoutFrame& _rLayFrame ) +{ + SwRectFnSet aRectFnSet(&_rLayFrame); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(_rNewFrame); + aRectFnSet.SetPos( aFrm, aRectFnSet.GetPos(_rLayFrame.getFrameArea()) ); + + // move position by one SwTwip in text flow direction in order to get + // notifications for a new calculated position after its formatting. + if ( aRectFnSet.IsVert() ) + { + aFrm.Pos().AdjustX( -1 ); + } + else + { + aFrm.Pos().AdjustY(1 ); + } +} + +void InsertCnt_( SwLayoutFrame *pLay, SwDoc *pDoc, + sal_uLong nIndex, bool bPages, sal_uLong nEndIndex, + SwFrame *pPrv, sw::FrameMode const eMode ) +{ + pDoc->getIDocumentTimerAccess().BlockIdling(); + SwRootFrame* pLayout = pLay->getRootFrame(); + const bool bOldCallbackActionEnabled = pLayout && pLayout->IsCallbackActionEnabled(); + if( bOldCallbackActionEnabled ) + pLayout->SetCallbackActionEnabled( false ); + + //In the generation of the Layout bPages=true will be handed over. + //Then will be new pages generated all x paragraphs already times in advance. + //On breaks and/or pagedescriptorchanges the corresponding will be generated + //immediately. + //The advantage is, that on one hand already a nearly realistic number of + //pages are created, but above all there are no almost endless long chain + //of paragraphs, which must be moved expensively until it reaches a tolerable + //reduced level. + //We'd like to think that 20 Paragraphs fit on one page. + //So that it does not become in extreme situations so violent we calculate depending + //on the node something to it. + //If in the DocStatistic a usable given pagenumber + //(Will be cared for while writing), so it will be presumed that this will be + //number of pages. + const bool bStartPercent = bPages && !nEndIndex; + + SwPageFrame *pPage = pLay->FindPageFrame(); + const SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + SwFrame *pFrame = nullptr; + std::unique_ptr<SwActualSection> pActualSection; + std::unique_ptr<SwLayHelper> pPageMaker; + + //If the layout will be created (bPages == true) we do head on the progress + //Flys and DrawObjects are not connected immediately, this + //happens only at the end of the function. + if ( bPages ) + { + // Attention: the SwLayHelper class uses references to the content-, + // page-, layout-frame etc. and may change them! + pPageMaker.reset(new SwLayHelper( pDoc, pFrame, pPrv, pPage, pLay, + pActualSection, nIndex, 0 == nEndIndex )); + if( bStartPercent ) + { + const sal_uLong nPageCount = pPageMaker->CalcPageCount(); + if( nPageCount ) + bObjsDirect = false; + } + } + else + pPageMaker = nullptr; + + if( pLay->IsInSct() && + ( pLay->IsSctFrame() || pLay->GetUpper() ) ) // Hereby will newbies + // be intercepted, of which flags could not determined yet, + // for e.g. while inserting a table + { + SwSectionFrame* pSct = pLay->FindSctFrame(); + // If content will be inserted in a footnote, which in a column area, + // the column area it is not allowed to be broken up. + // Only if in the inner of the footnote lies an area, is this a candidate + // for pActualSection. + // The same applies for areas in tables, if inside the table will be + // something inserted, it's only allowed to break up areas, which + // lies in the inside also. + if( ( !pLay->IsInFootnote() || pSct->IsInFootnote() ) && + ( !pLay->IsInTab() || pSct->IsInTab() ) ) + { + pActualSection.reset(new SwActualSection(nullptr, pSct, pSct->GetSection()->GetFormat()->GetSectionNode())); + // tdf#132236 for SwUndoDelete: find outer sections whose start + // nodes aren't contained in the range but whose end nodes are, + // because section frames may need to be created for them + SwActualSection * pUpperSection(pActualSection.get()); + while (pUpperSection->GetSectionNode()->EndOfSectionIndex() < nEndIndex) + { + SwStartNode *const pStart(pUpperSection->GetSectionNode()->StartOfSectionNode()); + if (!pStart->IsSectionNode()) + { + break; + } + // note: these don't have a section frame, check it in EndNode case! + auto const pTmp(new SwActualSection(nullptr, nullptr, static_cast<SwSectionNode*>(pStart))); + pUpperSection->SetUpper(pTmp); + pUpperSection = pTmp; + } + OSL_ENSURE( !pLay->Lower() || !pLay->Lower()->IsColumnFrame(), + "InsertCnt_: Wrong Call" ); + } + } + + //If a section is "open", the pActualSection points to an SwActualSection. + //If the page breaks, for "open" sections a follow will created. + //For nested sections (which have, however, not a nested layout), + //the SwActualSection class has a member, which points to an upper(section). + //When the "inner" section finishes, the upper will used instead. + + // Do not consider the end node. The caller (Section/MakeFrames()) has to + // ensure that the end of this range is positioned before EndIndex! + for ( ; nEndIndex == 0 || nIndex < nEndIndex; ++nIndex) + { + SwNode *pNd = pDoc->GetNodes()[nIndex]; + if ( pNd->IsContentNode() ) + { + SwContentNode* pNode = static_cast<SwContentNode*>(pNd); + if (pLayout->IsHideRedlines() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + if (pNd->IsTextNode() + && pNd->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { // must have a frame already + assert(static_cast<SwTextFrame*>(pNode->getLayoutFrame(pLayout))->GetMergedPara()); + } + continue; // skip it + } + pFrame = pNode->IsTextNode() + ? sw::MakeTextFrame(*pNode->GetTextNode(), pLay, eMode) + : pNode->MakeFrame(pLay); + if( pPageMaker ) + pPageMaker->CheckInsert( nIndex ); + + pFrame->InsertBehind( pLay, pPrv ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + if ( pFrame->IsTextFrame() ) + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + // no notification, if <SwViewShell> is in construction + if ( pViewShell && !pViewShell->IsInConstructor() && + pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() && + pFrame->FindPageFrame() != nullptr) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pFrame->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pFrame->FindPrevCnt()) ); + // #i68958# + // The information flags of the text frame are validated + // in methods <FindNextCnt(..)> and <FindPrevCnt(..)>. + // The information flags have to be invalidated, because + // it is possible, that the one of its upper frames + // isn't inserted into the layout. + pFrame->InvalidateInfFlags(); + } + } + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + pPrv = pFrame; + + if ( !pTable->empty() && bObjsDirect && !bDontCreateObjects ) + AppendObjs( pTable, nIndex, pFrame, pPage, pDoc ); + } + else if ( pNd->IsTableNode() ) + { //Should we have encountered a table? + SwTableNode *pTableNode = static_cast<SwTableNode*>(pNd); + if (pLayout->IsHideRedlines()) + { + // in the problematic case, there can be only 1 redline... + SwPosition const tmp(*pNd); + SwRangeRedline const*const pRedline( + pDoc->getIDocumentRedlineAccess().GetRedline(tmp, nullptr)); + // pathology: redline that starts on a TableNode; cannot + // be created in UI but by import filters... + if (pRedline + && pRedline->GetType() == RedlineType::Delete + && &pRedline->Start()->nNode.GetNode() == pNd) + { + SAL_WARN("sw.pageframe", "skipping table frame creation on bizarre redline"); + while (true) + { + pTableNode->GetNodes()[nIndex]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + if (nIndex == pTableNode->EndOfSectionIndex()) + { + break; + } + ++nIndex; + } + continue; + } + } + if (pLayout->IsHideRedlines() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + nIndex = pTableNode->EndOfSectionIndex(); + continue; // skip it + } + + // #108116# loading may produce table structures that GCLines + // needs to clean up. To keep table formulas correct, change + // all table formulas to internal (BOXPTR) representation. + SwTableFormulaUpdate aMsgHint( &pTableNode->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + pDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + pTableNode->GetTable().GCLines(); + + pFrame = pTableNode->MakeFrame( pLay ); + + if( pPageMaker ) + pPageMaker->CheckInsert( nIndex ); + + pFrame->InsertBehind( pLay, pPrv ); + if (pPage) // would null in SwCellFrame ctor + { // tdf#134931 call ResetTurbo(); not sure if Paste() would be + pFrame->InvalidatePage(pPage); // better than InsertBehind()? + } + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + // no notification, if <SwViewShell> is in construction + if ( pViewShell && !pViewShell->IsInConstructor() && + pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() && + pFrame->FindPageFrame() != nullptr) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pFrame->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pFrame->FindPrevCnt()) ); + } + } + if ( bObjsDirect && !pTable->empty() ) + static_cast<SwTabFrame*>(pFrame)->RegistFlys(); + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + + pPrv = pFrame; + //Set the index to the endnode of the table section. + nIndex = pTableNode->EndOfSectionIndex(); + + SwTabFrame* pTmpFrame = static_cast<SwTabFrame*>(pFrame); + while ( pTmpFrame ) + { + pTmpFrame->CheckDirChange(); + pTmpFrame = pTmpFrame->IsFollow() ? pTmpFrame->FindMaster() : nullptr; + } + + } + else if ( pNd->IsSectionNode() ) + { + if (pLayout->IsHideRedlines() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + continue; // skip it + } + SwSectionNode *pNode = static_cast<SwSectionNode*>(pNd); + if( pNode->GetSection().CalcHiddenFlag() ) + // is hidden, skip the area + nIndex = pNode->EndOfSectionIndex(); + else + { + pFrame = pNode->MakeFrame( pLay ); + pActualSection.reset( new SwActualSection( pActualSection.release(), + static_cast<SwSectionFrame*>(pFrame), pNode ) ); + if ( pActualSection->GetUpper() ) + { + //Insert behind the Upper, the "Follow" of the Upper will be + //generated at the EndNode. + SwSectionFrame *pTmp = pActualSection->GetUpper()->GetSectionFrame(); + pFrame->InsertBehind( pTmp->GetUpper(), pTmp ); + // OD 25.03.2003 #108339# - direct initialization of section + // after insertion in the layout + static_cast<SwSectionFrame*>(pFrame)->Init(); + } + else + { + pFrame->InsertBehind( pLay, pPrv ); + // OD 25.03.2003 #108339# - direct initialization of section + // after insertion in the layout + static_cast<SwSectionFrame*>(pFrame)->Init(); + + // #i33963# + // Do not trust the IsInFootnote flag. If we are currently + // building up a table, the upper of pPrv may be a cell + // frame, but the cell frame does not have an upper yet. + if( pPrv && nullptr != pPrv->ImplFindFootnoteFrame() ) + { + if( pPrv->IsSctFrame() ) + pPrv = static_cast<SwSectionFrame*>(pPrv)->ContainsContent(); + if( pPrv && pPrv->IsTextFrame() ) + static_cast<SwTextFrame*>(pPrv)->Prepare( PrepareHint::QuoVadis, nullptr, false ); + } + } + if (nIndex + 1 == nEndIndex) + { // tdf#131684 tdf#132236 fix upper of frame moved in + // SwUndoDelete; can't be done there unfortunately + // because empty section frames are deleted here + SwFrame *const pNext( + // if there's a parent section, it has been split + // into 2 SwSectionFrame already :( + ( pFrame->GetNext() + && pFrame->GetNext()->IsSctFrame() + && pActualSection->GetUpper() + && pActualSection->GetUpper()->GetSectionNode() == + static_cast<SwSectionFrame const*>(pFrame->GetNext())->GetSection()->GetFormat()->GetSectionNode()) + ? static_cast<SwSectionFrame *>(pFrame->GetNext())->ContainsContent() + : pFrame->GetNext()); + if (pNext + && pNext->IsTextFrame() + && static_cast<SwTextFrame*>(pNext)->GetTextNodeFirst() == pDoc->GetNodes()[nEndIndex] + && (pNext->GetUpper() == pFrame->GetUpper() + || pFrame->GetNext()->IsSctFrame())) // checked above + { + pNext->Cut(); + pNext->InvalidateInfFlags(); // mbInfSct changed + // could have columns + SwSectionFrame *const pSection(static_cast<SwSectionFrame*>(pFrame)); + assert(!pSection->Lower() || pSection->Lower()->IsLayoutFrame()); + SwLayoutFrame *const pParent(pSection->Lower() ? pSection->GetNextLayoutLeaf() : pSection); + assert(!pParent->Lower()); + // paste invalidates, section could have indent... + pNext->Paste(pParent, nullptr); + } + } + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + // no notification, if <SwViewShell> is in construction + if ( pViewShell && !pViewShell->IsInConstructor() && + pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() && + pFrame->FindPageFrame() != nullptr) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pFrame->FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(pFrame->FindPrevCnt()) ); + } + } + pFrame->CheckDirChange(); + + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + + // OD 20.11.2002 #105405# - no page, no invalidate. + if ( pPage ) + { + // OD 18.09.2002 #100522# + // invalidate page in order to force format and paint of + // inserted section frame + pFrame->InvalidatePage( pPage ); + + // FME 10.11.2003 #112243# + // Invalidate fly content flag: + if ( pFrame->IsInFly() ) + pPage->InvalidateFlyContent(); + + // OD 14.11.2002 #104684# - invalidate page content in order to + // force format and paint of section content. + pPage->InvalidateContent(); + } + + pLay = static_cast<SwLayoutFrame*>(pFrame); + if ( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = pLay->GetNextLayoutLeaf(); + pPrv = nullptr; + } + } + else if ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ) + { + if (pLayout->IsHideRedlines() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + continue; // skip it + } + assert(pActualSection && "Section end without section start?"); + assert(pActualSection->GetSectionNode() == pNd->StartOfSectionNode()); + + //Close the section, where appropriate activate the surrounding + //section again. + pActualSection.reset(pActualSection->GetUpper()); + pLay = pLay->FindSctFrame(); + if ( pActualSection ) + { + //Could be, that the last SectionFrame remains empty. + //Then now is the time to remove them. + if ( !pLay->ContainsContent() ) + { + SwFrame *pTmpFrame = pLay; + pLay = pTmpFrame->GetUpper(); + pPrv = pTmpFrame->GetPrev(); + pTmpFrame->RemoveFromLayout(); + SwFrame::DestroyFrame(pTmpFrame); + } + else + { + pPrv = pLay; + pLay = pLay->GetUpper(); + } + + // new section frame + pFrame = pActualSection->GetSectionNode()->MakeFrame( pLay ); + pFrame->InsertBehind( pLay, pPrv ); + static_cast<SwSectionFrame*>(pFrame)->Init(); + + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + + SwSectionFrame* pOuterSectionFrame = pActualSection->GetSectionFrame(); + + // a follow has to be appended to the new section frame + SwSectionFrame* pFollow = pOuterSectionFrame ? pOuterSectionFrame->GetFollow() : nullptr; + if ( pFollow ) + { + pOuterSectionFrame->SetFollow( nullptr ); + pOuterSectionFrame->InvalidateSize(); + static_cast<SwSectionFrame*>(pFrame)->SetFollow( pFollow ); + } + + // We don't want to leave empty parts back. + if (pOuterSectionFrame && + ! pOuterSectionFrame->IsColLocked() && + ! pOuterSectionFrame->ContainsContent() ) + { + pOuterSectionFrame->DelEmpty( true ); + SwFrame::DestroyFrame(pOuterSectionFrame); + } + pActualSection->SetSectionFrame( static_cast<SwSectionFrame*>(pFrame) ); + + pLay = static_cast<SwLayoutFrame*>(pFrame); + if ( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = pLay->GetNextLayoutLeaf(); + pPrv = nullptr; + } + else + { + //Nothing more with sections, it goes on right behind + //the SectionFrame. + pPrv = pLay; + pLay = pLay->GetUpper(); + } + } + else if( pNd->IsStartNode() && + SwFlyStartNode == static_cast<SwStartNode*>(pNd)->GetStartNodeType() ) + { + if (pLayout->IsHideRedlines() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + assert(false); // actually a fly-section can't be deleted? + continue; // skip it + } + if ( !pTable->empty() && bObjsDirect && !bDontCreateObjects ) + { + SwFlyFrame* pFly = pLay->FindFlyFrame(); + if( pFly ) + AppendObjs( pTable, nIndex, pFly, pPage, pDoc ); + } + } + else + { + assert(!pLayout->IsHideRedlines() + || pNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden); + // Neither Content nor table nor section, so we are done. + break; + } + } + + if ( pActualSection ) + { + // Might happen that an empty (Follow-)Section is left over. + if ( !(pLay = pActualSection->GetSectionFrame())->ContainsContent() ) + { + pLay->RemoveFromLayout(); + SwFrame::DestroyFrame(pLay); + } + pActualSection.reset(); + } + + if ( bPages ) // let the Flys connect to each other + { + if ( !bDontCreateObjects ) + AppendAllObjs( pTable, pLayout ); + bObjsDirect = true; + } + + if( pPageMaker ) + { + pPageMaker->CheckFlyCache( pPage ); + pPageMaker.reset(); + if( pDoc->GetLayoutCache() ) + { +#ifdef DBG_UTIL + pDoc->GetLayoutCache()->CompareLayout( *pDoc ); +#endif + pDoc->GetLayoutCache()->ClearImpl(); + } + } + + pDoc->getIDocumentTimerAccess().UnblockIdling(); + if( bOldCallbackActionEnabled ) + pLayout->SetCallbackActionEnabled( bOldCallbackActionEnabled ); +} + +void MakeFrames( SwDoc *pDoc, const SwNodeIndex &rSttIdx, + const SwNodeIndex &rEndIdx ) +{ + bObjsDirect = false; + + SwNodeIndex aTmp( rSttIdx ); + sal_uLong nEndIdx = rEndIdx.GetIndex(); + SwNode* pNd = pDoc->GetNodes().FindPrvNxtFrameNode( aTmp, + pDoc->GetNodes()[ nEndIdx-1 ]); + if ( pNd ) + { + bool bApres = aTmp < rSttIdx; + SwNode2Layout aNode2Layout( *pNd, rSttIdx.GetIndex() ); + SwFrame* pFrame; + sw::FrameMode eMode = sw::FrameMode::Existing; + while( nullptr != (pFrame = aNode2Layout.NextFrame()) ) + { + SwLayoutFrame *pUpper = pFrame->GetUpper(); + SwFootnoteFrame* pFootnoteFrame = pUpper->FindFootnoteFrame(); + bool bOldLock, bOldFootnote; + if( pFootnoteFrame ) + { + bOldFootnote = pFootnoteFrame->IsColLocked(); + pFootnoteFrame->ColLock(); + } + else + bOldFootnote = true; + SwSectionFrame* pSct = pUpper->FindSctFrame(); + // Inside of footnotes only those areas are interesting that are inside of them. But + // not the ones (e.g. column areas) in which are the footnote containers positioned. + // #109767# Table frame is in section, insert section in cell frame. + if( pSct && ((pFootnoteFrame && !pSct->IsInFootnote()) || pUpper->IsCellFrame()) ) + pSct = nullptr; + if( pSct ) + { // to prevent pTmp->MoveFwd from destroying the SectionFrame + bOldLock = pSct->IsColLocked(); + pSct->ColLock(); + } + else + bOldLock = true; + + // If pFrame cannot be moved, it is not possible to move it to the next page. This applies + // also for frames (in the first column of a frame pFrame is moveable) and column + // sections of tables (also here pFrame is moveable). + bool bMoveNext = nEndIdx - rSttIdx.GetIndex() > 120; + bool bAllowMove = !pFrame->IsInFly() && pFrame->IsMoveable() && + (!pFrame->IsInTab() || pFrame->IsTabFrame() ); + if ( bMoveNext && bAllowMove ) + { + SwFrame *pMove = pFrame; + SwFrame *pPrev = pFrame->GetPrev(); + SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pMove ); + assert(pTmp); + + if ( bApres ) + { + // The rest of this page should be empty. Thus, the following one has to move to + // the next page (it might also be located in the following column). + assert(!pTmp->HasFollow() && "prev. node's frame is not last"); + pPrev = pFrame; + // If the surrounding SectionFrame has a "next" one, + // so this one needs to be moved as well. + pMove = pFrame->GetIndNext(); + SwColumnFrame* pCol = static_cast<SwColumnFrame*>(pFrame->FindColFrame()); + if( pCol ) + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + do + { + if( pCol && !pMove ) + { // No successor so far, look into the next column + pMove = pCol->ContainsAny(); + if( pCol->GetNext() ) + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + else if( pCol->IsInSct() ) + { // If there is no following column but we are in a column frame, + // there might be (page) columns outside of it. + pCol = static_cast<SwColumnFrame*>(pCol->FindSctFrame()->FindColFrame()); + if( pCol ) + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } + else + pCol = nullptr; + } + // skip invalid SectionFrames + while( pMove && pMove->IsSctFrame() && + !static_cast<SwSectionFrame*>(pMove)->GetSection() ) + pMove = pMove->GetNext(); + } while( !pMove && pCol ); + + if( pMove ) + { + if ( pMove->IsContentFrame() ) + pTmp = static_cast<SwContentFrame*>(pMove); + else if ( pMove->IsTabFrame() ) + pTmp = static_cast<SwTabFrame*>(pMove); + else if ( pMove->IsSctFrame() ) + { + pMove = static_cast<SwSectionFrame*>(pMove)->ContainsAny(); + if( pMove ) + pTmp = SwFlowFrame::CastFlowFrame( pMove ); + else + pTmp = nullptr; + } + } + else + pTmp = nullptr; + } + else + { + assert(!pTmp->IsFollow() && "next node's frame is not master"); + // move the _content_ of a section frame + if( pMove->IsSctFrame() ) + { + while( pMove && pMove->IsSctFrame() && + !static_cast<SwSectionFrame*>(pMove)->GetSection() ) + pMove = pMove->GetNext(); + if( pMove && pMove->IsSctFrame() ) + pMove = static_cast<SwSectionFrame*>(pMove)->ContainsAny(); + if( pMove ) + pTmp = SwFlowFrame::CastFlowFrame( pMove ); + else + pTmp = nullptr; + } + } + + if( pTmp ) + { + SwFrame* pOldUp = pTmp->GetFrame().GetUpper(); + // MoveFwd==true means that we are still on the same page. + // But since we want to move if possible! + bool bTmpOldLock = pTmp->IsJoinLocked(); + pTmp->LockJoin(); + while( pTmp->MoveFwd( true, false, true ) ) + { + if( pOldUp == pTmp->GetFrame().GetUpper() ) + break; + pOldUp = pTmp->GetFrame().GetUpper(); + } + if( !bTmpOldLock ) + pTmp->UnlockJoin(); + } + ::InsertCnt_( pUpper, pDoc, rSttIdx.GetIndex(), + pFrame->IsInDocBody(), nEndIdx, pPrev, eMode ); + } + else + { + bool bSplit; + SwFrame* pPrv = bApres ? pFrame : pFrame->GetPrev(); + // If the section frame is inserted into another one, it must be split. + if( pSct && rSttIdx.GetNode().IsSectionNode() ) + { + bSplit = pSct->SplitSect( pFrame, bApres ); + if( !bSplit && !bApres ) + { + pUpper = pSct->GetUpper(); + pPrv = pSct->GetPrev(); + } + } + else + bSplit = false; + + ::InsertCnt_( pUpper, pDoc, rSttIdx.GetIndex(), false, + nEndIdx, pPrv, eMode ); + // OD 23.06.2003 #108784# - correction: append objects doesn't + // depend on value of <bAllowMove> + if( !bDontCreateObjects ) + { + const SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + if( !pTable->empty() ) + AppendAllObjs( pTable, pUpper ); + } + + // If nothing was added (e.g. a hidden section), the split must be reversed. + if( bSplit && pSct && pSct->GetNext() + && pSct->GetNext()->IsSctFrame() ) + pSct->MergeNext( static_cast<SwSectionFrame*>(pSct->GetNext()) ); + if( pFrame->IsInFly() ) + pFrame->FindFlyFrame()->Invalidate_(); + if( pFrame->IsInTab() ) + pFrame->InvalidateSize(); + } + + SwPageFrame *pPage = pUpper->FindPageFrame(); + SwFrame::CheckPageDescs( pPage, false ); + if( !bOldFootnote ) + pFootnoteFrame->ColUnlock(); + if( !bOldLock ) + { + pSct->ColUnlock(); + // pSct might be empty (e.g. when inserting linked section containing further + // sections) and can be destroyed in such cases. + if( !pSct->ContainsContent() ) + { + pSct->DelEmpty( true ); + pUpper->getRootFrame()->RemoveFromList( pSct ); + SwFrame::DestroyFrame(pSct); + } + } + eMode = sw::FrameMode::New; // use Existing only once! + } + } + + bObjsDirect = true; +} + +SwBorderAttrs::SwBorderAttrs(const SwModify *pMod, const SwFrame *pConstructor) + : SwCacheObj(pMod) + , m_rAttrSet(pConstructor->IsContentFrame() + ? pConstructor->IsTextFrame() + ? static_cast<const SwTextFrame*>(pConstructor)->GetTextNodeForParaProps()->GetSwAttrSet() + : static_cast<const SwNoTextFrame*>(pConstructor)->GetNode()->GetSwAttrSet() + : static_cast<const SwLayoutFrame*>(pConstructor)->GetFormat()->GetAttrSet()) + , m_rUL(m_rAttrSet.GetULSpace()) + // #i96772# + // LRSpaceItem is copied due to the possibility that it is adjusted - see below + , m_rLR(m_rAttrSet.GetLRSpace().Clone()) + , m_rBox(m_rAttrSet.GetBox()) + , m_rShadow(m_rAttrSet.GetShadow()) + , m_aFrameSize(m_rAttrSet.GetFrameSize().GetSize()) + , m_bIsLine(false) + , m_bJoinedWithPrev(false) + , m_bJoinedWithNext(false) + , m_nTopLine(0) + , m_nBottomLine(0) + , m_nLeftLine(0) + , m_nRightLine(0) + , m_nTop(0) + , m_nBottom(0) + , m_nGetTopLine(0) + , m_nGetBottomLine(0) + , m_nLineSpacing(0) +{ + // #i96772# + const SwTextFrame* pTextFrame = dynamic_cast<const SwTextFrame*>(pConstructor); + if ( pTextFrame ) + { + pTextFrame->GetTextNodeForParaProps()->ClearLRSpaceItemDueToListLevelIndents( m_rLR ); + } + else if ( pConstructor->IsNoTextFrame() ) + { + m_rLR = std::make_shared<SvxLRSpaceItem>(RES_LR_SPACE); + } + + // Caution: The USHORTs for the cached values are not initialized by intention! + + // everything needs to be calculated at least once: + m_bTopLine = m_bBottomLine = m_bLeftLine = m_bRightLine = + m_bTop = m_bBottom = m_bLine = true; + + // except this one: calculate line spacing before cell border only for text frames + m_bLineSpacing = bool(pTextFrame); + + m_bCacheGetLine = m_bCachedGetTopLine = m_bCachedGetBottomLine = false; + // OD 21.05.2003 #108789# - init cache status for values <m_bJoinedWithPrev> + // and <m_bJoinedWithNext>, which aren't initialized by default. + m_bCachedJoinedWithPrev = false; + m_bCachedJoinedWithNext = false; +} + +SwBorderAttrs::~SwBorderAttrs() +{ + const_cast<SwModify *>(static_cast<SwModify const *>(m_pOwner))->SetInCache( false ); +} + +/* All calc methods calculate a safety distance in addition to the values given by the attributes. + * This safety distance is only added when working with borders and/or shadows to prevent that + * e.g. borders are painted over. + */ + +void SwBorderAttrs::CalcTop_() +{ + m_nTop = CalcTopLine() + m_rUL.GetUpper(); + m_bTop = false; +} + +void SwBorderAttrs::CalcBottom_() +{ + m_nBottom = CalcBottomLine() + m_rUL.GetLower(); + m_bBottom = false; +} + +long SwBorderAttrs::CalcRight( const SwFrame* pCaller ) const +{ + long nRight=0; + + if (!pCaller->IsTextFrame() || !static_cast<const SwTextFrame*>(pCaller)->GetDoc().GetDocumentSettingManager().get(DocumentSettingId::INVERT_BORDER_SPACING)) { + // OD 23.01.2003 #106895# - for cell frame in R2L text direction the left + // and right border are painted on the right respectively left. + if ( pCaller->IsCellFrame() && pCaller->IsRightToLeft() ) + nRight = CalcLeftLine(); + else + nRight = CalcRightLine(); + + } + // for paragraphs, "left" is "before text" and "right" is "after text" + if ( pCaller->IsTextFrame() && pCaller->IsRightToLeft() ) + nRight += m_rLR->GetLeft(); + else + nRight += m_rLR->GetRight(); + + // correction: retrieve left margin for numbering in R2L-layout + if ( pCaller->IsTextFrame() && pCaller->IsRightToLeft() ) + { + nRight += static_cast<const SwTextFrame*>(pCaller)->GetTextNodeForParaProps()->GetLeftMarginWithNum(); + } + + return nRight; +} + +/// Tries to detect if this paragraph has a floating table attached. +static bool lcl_hasTabFrame(const SwTextFrame* pTextFrame) +{ + if (pTextFrame->GetDrawObjs()) + { + const SwSortedObjs* pSortedObjs = pTextFrame->GetDrawObjs(); + if (pSortedObjs->size() > 0) + { + SwAnchoredObject* pObject = (*pSortedObjs)[0]; + if (dynamic_cast<const SwFlyFrame*>(pObject) != nullptr) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pObject); + if (pFly->Lower() && pFly->Lower()->IsTabFrame()) + return true; + } + } + } + return false; +} + +long SwBorderAttrs::CalcLeft( const SwFrame *pCaller ) const +{ + long nLeft=0; + + if (!pCaller->IsTextFrame() || !static_cast<const SwTextFrame*>(pCaller)->GetDoc().GetDocumentSettingManager().get(DocumentSettingId::INVERT_BORDER_SPACING)) + { + // OD 23.01.2003 #106895# - for cell frame in R2L text direction the left + // and right border are painted on the right respectively left. + if ( pCaller->IsCellFrame() && pCaller->IsRightToLeft() ) + nLeft = CalcRightLine(); + else + nLeft = CalcLeftLine(); + } + + // for paragraphs, "left" is "before text" and "right" is "after text" + if ( pCaller->IsTextFrame() && pCaller->IsRightToLeft() ) + nLeft += m_rLR->GetRight(); + else + { + bool bIgnoreMargin = false; + if (pCaller->IsTextFrame()) + { + const SwTextFrame* pTextFrame = static_cast<const SwTextFrame*>(pCaller); + if (pTextFrame->GetDoc().GetDocumentSettingManager().get(DocumentSettingId::FLOATTABLE_NOMARGINS)) + { + // If this is explicitly requested, ignore the margins next to the floating table. + if (lcl_hasTabFrame(pTextFrame)) + bIgnoreMargin = true; + // TODO here we only handle the first two paragraphs, would be nice to generalize this. + else if (pTextFrame->FindPrev() && pTextFrame->FindPrev()->IsTextFrame() && lcl_hasTabFrame(static_cast<const SwTextFrame*>(pTextFrame->FindPrev()))) + bIgnoreMargin = true; + } + } + if (!bIgnoreMargin) + nLeft += m_rLR->GetLeft(); + } + + // correction: do not retrieve left margin for numbering in R2L-layout + if ( pCaller->IsTextFrame() && !pCaller->IsRightToLeft() ) + { + nLeft += static_cast<const SwTextFrame*>(pCaller)->GetTextNodeForParaProps()->GetLeftMarginWithNum(); + } + + return nLeft; +} + +/* Calculated values for borders and shadows. + * It might be that a distance is wanted even without lines. This will be + * considered here and not by the attribute (e.g. bBorderDist for cells). + */ + +void SwBorderAttrs::CalcTopLine_() +{ + m_nTopLine = m_rBox.CalcLineSpace( SvxBoxItemLine::TOP, /*bEvenIfNoLine*/true ); + m_nTopLine = m_nTopLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::TOP); + m_bTopLine = false; +} + +void SwBorderAttrs::CalcBottomLine_() +{ + m_nBottomLine = m_rBox.CalcLineSpace( SvxBoxItemLine::BOTTOM, true ); + m_nBottomLine = m_nBottomLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::BOTTOM); + m_bBottomLine = false; +} + +void SwBorderAttrs::CalcLeftLine_() +{ + m_nLeftLine = m_rBox.CalcLineSpace( SvxBoxItemLine::LEFT, true); + m_nLeftLine = m_nLeftLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::LEFT); + m_bLeftLine = false; +} + +void SwBorderAttrs::CalcRightLine_() +{ + m_nRightLine = m_rBox.CalcLineSpace( SvxBoxItemLine::RIGHT, true ); + m_nRightLine = m_nRightLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::RIGHT); + m_bRightLine = false; +} + +void SwBorderAttrs::IsLine_() +{ + m_bIsLine = m_rBox.GetTop() || m_rBox.GetBottom() || + m_rBox.GetLeft()|| m_rBox.GetRight(); + m_bLine = false; +} + +/* The borders of neighboring paragraphs are condensed by following algorithm: + * + * 1. No top border if the predecessor has the same top border and (3) applies. + * In addition, the paragraph needs to have a border at least one side (left/right/bottom). + * 2. No bottom border if the successor has the same bottom border and (3) applies. + * In addition, the paragraph needs to have a border at least one side (left/right/top). + * 3. The borders on the left and right side are identical between the current and the + * pre-/succeeding paragraph. + */ + +static bool CmpLines( const editeng::SvxBorderLine *pL1, const editeng::SvxBorderLine *pL2 ) +{ + return ( ((pL1 && pL2) && (*pL1 == *pL2)) || (!pL1 && !pL2) ); +} + +// OD 21.05.2003 #108789# - change name of 1st parameter - "rAttrs" -> "rCmpAttrs" +// OD 21.05.2003 #108789# - compare <CalcRight()> and <rCmpAttrs.CalcRight()> +// instead of only the right LR-spacing, because R2L-layout has to be +// considered. +bool SwBorderAttrs::CmpLeftRight( const SwBorderAttrs &rCmpAttrs, + const SwFrame *pCaller, + const SwFrame *pCmp ) const +{ + return ( CmpLines( rCmpAttrs.GetBox().GetLeft(), GetBox().GetLeft() ) && + CmpLines( rCmpAttrs.GetBox().GetRight(),GetBox().GetRight() ) && + CalcLeft( pCaller ) == rCmpAttrs.CalcLeft( pCmp ) && + // OD 21.05.2003 #108789# - compare <CalcRight> with <rCmpAttrs.CalcRight>. + CalcRight( pCaller ) == rCmpAttrs.CalcRight( pCmp ) ); +} + +bool SwBorderAttrs::JoinWithCmp( const SwFrame& _rCallerFrame, + const SwFrame& _rCmpFrame ) const +{ + bool bReturnVal = false; + + SwBorderAttrAccess aCmpAccess( SwFrame::GetCache(), &_rCmpFrame ); + const SwBorderAttrs &rCmpAttrs = *aCmpAccess.Get(); + if ( m_rShadow == rCmpAttrs.GetShadow() && + CmpLines( m_rBox.GetTop(), rCmpAttrs.GetBox().GetTop() ) && + CmpLines( m_rBox.GetBottom(), rCmpAttrs.GetBox().GetBottom() ) && + CmpLeftRight( rCmpAttrs, &_rCallerFrame, &_rCmpFrame ) + ) + { + bReturnVal = true; + } + + return bReturnVal; +} + +// OD 21.05.2003 #108789# - method to determine, if borders are joined with +// previous frame. Calculated value saved in cached value <m_bJoinedWithPrev> +// OD 2004-02-26 #i25029# - add 2nd parameter <_pPrevFrame> +void SwBorderAttrs::CalcJoinedWithPrev( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) +{ + // set default + m_bJoinedWithPrev = false; + + if ( _rFrame.IsTextFrame() ) + { + // text frame can potentially join with previous text frame, if + // corresponding attribute set is set at previous text frame. + // OD 2004-02-26 #i25029# - If parameter <_pPrevFrame> is set, take this + // one as previous frame. + const SwFrame* pPrevFrame = _pPrevFrame ? _pPrevFrame : _rFrame.GetPrev(); + // OD 2004-02-13 #i25029# - skip hidden text frames. + while ( pPrevFrame && pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + if ( pPrevFrame && pPrevFrame->IsTextFrame() && + pPrevFrame->GetAttrSet()->GetParaConnectBorder().GetValue() + ) + { + m_bJoinedWithPrev = JoinWithCmp( _rFrame, *pPrevFrame ); + } + } + + // valid cache status, if demanded + // OD 2004-02-26 #i25029# - Do not validate cache, if parameter <_pPrevFrame> + // is set. + m_bCachedJoinedWithPrev = m_bCacheGetLine && !_pPrevFrame; +} + +// OD 21.05.2003 #108789# - method to determine, if borders are joined with +// next frame. Calculated value saved in cached value <m_bJoinedWithNext> +void SwBorderAttrs::CalcJoinedWithNext( const SwFrame& _rFrame ) +{ + // set default + m_bJoinedWithNext = false; + + if ( _rFrame.IsTextFrame() ) + { + // text frame can potentially join with next text frame, if + // corresponding attribute set is set at current text frame. + // OD 2004-02-13 #i25029# - get next frame, but skip hidden text frames. + const SwFrame* pNextFrame = _rFrame.GetNext(); + while ( pNextFrame && pNextFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pNextFrame)->IsHiddenNow() ) + { + pNextFrame = pNextFrame->GetNext(); + } + if ( pNextFrame && pNextFrame->IsTextFrame() && + _rFrame.GetAttrSet()->GetParaConnectBorder().GetValue() + ) + { + m_bJoinedWithNext = JoinWithCmp( _rFrame, *pNextFrame ); + } + } + + // valid cache status, if demanded + m_bCachedJoinedWithNext = m_bCacheGetLine; +} + +// OD 21.05.2003 #108789# - accessor for cached values <m_bJoinedWithPrev> +// OD 2004-02-26 #i25029# - add 2nd parameter <_pPrevFrame>, which is passed to +// method <_CalcJoindWithPrev(..)>. +bool SwBorderAttrs::JoinedWithPrev( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) const +{ + if ( !m_bCachedJoinedWithPrev || _pPrevFrame ) + { + // OD 2004-02-26 #i25029# - pass <_pPrevFrame> as 2nd parameter + const_cast<SwBorderAttrs*>(this)->CalcJoinedWithPrev( _rFrame, _pPrevFrame ); + } + + return m_bJoinedWithPrev; +} + +bool SwBorderAttrs::JoinedWithNext( const SwFrame& _rFrame ) const +{ + if ( !m_bCachedJoinedWithNext ) + { + const_cast<SwBorderAttrs*>(this)->CalcJoinedWithNext( _rFrame ); + } + + return m_bJoinedWithNext; +} + +// OD 2004-02-26 #i25029# - added 2nd parameter <_pPrevFrame>, which is passed to +// method <JoinedWithPrev> +void SwBorderAttrs::GetTopLine_( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) +{ + sal_uInt16 nRet = CalcTopLine(); + + // OD 21.05.2003 #108789# - use new method <JoinWithPrev()> + // OD 2004-02-26 #i25029# - add 2nd parameter + if ( JoinedWithPrev( _rFrame, _pPrevFrame ) ) + { + nRet = 0; + } + + m_bCachedGetTopLine = m_bCacheGetLine; + + m_nGetTopLine = nRet; +} + +void SwBorderAttrs::GetBottomLine_( const SwFrame& _rFrame ) +{ + sal_uInt16 nRet = CalcBottomLine(); + + // OD 21.05.2003 #108789# - use new method <JoinWithPrev()> + if ( JoinedWithNext( _rFrame ) ) + { + nRet = 0; + } + + m_bCachedGetBottomLine = m_bCacheGetLine; + + m_nGetBottomLine = nRet; +} + +void SwBorderAttrs::CalcLineSpacing_() +{ + // tdf#125300 compatibility option AddParaLineSpacingToTableCells needs also line spacing + const SvxLineSpacingItem &rSpace = m_rAttrSet.GetLineSpacing(); + if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop && rSpace.GetPropLineSpace() > 100 ) + { + sal_Int32 nFontSize = m_rAttrSet.Get(RES_CHRATR_FONTSIZE).GetHeight(); + m_nLineSpacing = nFontSize * (rSpace.GetPropLineSpace() - 100) * 1.15 / 100; + } + m_bLineSpacing = false; +} + +static SwModify const* GetCacheOwner(SwFrame const& rFrame) +{ + return rFrame.IsContentFrame() + ? static_cast<SwModify const*>(rFrame.IsTextFrame() + // sw_redlinehide: presumably this caches the border attrs at the model level and can be shared across different layouts so we want the ParaProps node here + ? static_cast<const SwTextFrame&>(rFrame).GetTextNodeForParaProps() + : static_cast<const SwNoTextFrame&>(rFrame).GetNode()) + : static_cast<SwModify const*>(static_cast<const SwLayoutFrame&>(rFrame).GetFormat()); +} + +SwBorderAttrAccess::SwBorderAttrAccess( SwCache &rCach, const SwFrame *pFrame ) : + SwCacheAccess( rCach, + static_cast<void const *>(GetCacheOwner(*pFrame)), + GetCacheOwner(*pFrame)->IsInCache()), + m_pConstructor( pFrame ) +{ +} + +SwCacheObj *SwBorderAttrAccess::NewObj() +{ + const_cast<SwModify *>(static_cast<SwModify const *>(m_pOwner))->SetInCache( true ); + return new SwBorderAttrs( static_cast<SwModify const *>(m_pOwner), m_pConstructor ); +} + +SwBorderAttrs *SwBorderAttrAccess::Get() +{ + return static_cast<SwBorderAttrs*>(SwCacheAccess::Get()); +} + +SwOrderIter::SwOrderIter( const SwPageFrame *pPg ) : + m_pPage( pPg ), + m_pCurrent( nullptr ) +{ +} + +void SwOrderIter::Top() +{ + m_pCurrent = nullptr; + if ( m_pPage->GetSortedObjs() ) + { + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( pObjs->size() ) + { + sal_uInt32 nTopOrd = 0; + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp >= nTopOrd ) + { + nTopOrd = nTmp; + m_pCurrent = pObj; + } + } + } + } +} + +const SdrObject *SwOrderIter::Bottom() +{ + m_pCurrent = nullptr; + if ( m_pPage->GetSortedObjs() ) + { + sal_uInt32 nBotOrd = USHRT_MAX; + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( pObjs->size() ) + { + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp < nBotOrd ) + { + nBotOrd = nTmp; + m_pCurrent = pObj; + } + } + } + } + return m_pCurrent; +} + +const SdrObject *SwOrderIter::Next() +{ + const sal_uInt32 nCurOrd = m_pCurrent ? m_pCurrent->GetOrdNumDirect() : 0; + m_pCurrent = nullptr; + if ( m_pPage->GetSortedObjs() ) + { + sal_uInt32 nOrd = USHRT_MAX; + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( pObjs->size() ) + { + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp > nCurOrd && nTmp < nOrd ) + { + nOrd = nTmp; + m_pCurrent = pObj; + } + } + } + } + return m_pCurrent; +} + +void SwOrderIter::Prev() +{ + const sal_uInt32 nCurOrd = m_pCurrent ? m_pCurrent->GetOrdNumDirect() : 0; + m_pCurrent = nullptr; + if ( m_pPage->GetSortedObjs() ) + { + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( pObjs->size() ) + { + sal_uInt32 nOrd = 0; + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp < nCurOrd && nTmp >= nOrd ) + { + nOrd = nTmp; + m_pCurrent = pObj; + } + } + } + } +} + +/// Keep and restore the substructure of a layout frame for an action. +// New algorithm: +// Do not look at each neighbor one by one to set all pointers correctly. +// It is sufficient to detach a part of a chain and check if another chain needs to be added +// when attaching it again. Only the pointers necessary for the chain connection need to be +// adjusted. The correction happens in RestoreContent(). In between all access is restricted. +// During this action, the Flys are detached from the page. + +// #115759# - 'remove' also drawing object from page and +// at-fly anchored objects from page +static void lcl_RemoveObjsFromPage( SwFrame* _pFrame ) +{ + OSL_ENSURE( _pFrame->GetDrawObjs(), "no DrawObjs in lcl_RemoveObjsFromPage." ); + SwSortedObjs &rObjs = *_pFrame->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + // #115759# - reset member, at which the anchored + // object orients its vertical position + pObj->ClearVertPosOrientFrame(); + // #i43913# + pObj->ResetLayoutProcessBools(); + // #115759# - remove also lower objects of as-character + // anchored Writer fly frames from page + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pObj); + + // #115759# - remove also direct lowers of Writer + // fly frame from page + if ( pFlyFrame->GetDrawObjs() ) + { + ::lcl_RemoveObjsFromPage( pFlyFrame ); + } + + SwContentFrame* pCnt = pFlyFrame->ContainsContent(); + while ( pCnt ) + { + if ( pCnt->GetDrawObjs() ) + ::lcl_RemoveObjsFromPage( pCnt ); + pCnt = pCnt->GetNextContentFrame(); + } + if ( pFlyFrame->IsFlyFreeFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + pFlyFrame->GetPageFrame()->RemoveFlyFromPage( pFlyFrame ); + } + } + // #115759# - remove also drawing objects from page + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + if (pObj->GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + { + if (SwPageFrame *pPg = pObj->GetPageFrame()) + pPg->RemoveDrawObjFromPage( + *static_cast<SwAnchoredDrawObject*>(pObj) ); + } + } + } +} + +SwFrame *SaveContent( SwLayoutFrame *pLay, SwFrame *pStart ) +{ + if( pLay->IsSctFrame() && pLay->Lower() && pLay->Lower()->IsColumnFrame() ) + sw_RemoveFootnotes( static_cast<SwColumnFrame*>(pLay->Lower()), true, true ); + + SwFrame *pSav; + if ( nullptr == (pSav = pLay->ContainsAny()) ) + return nullptr; + + if( pSav->IsInFootnote() && !pLay->IsInFootnote() ) + { + do + pSav = pSav->FindNext(); + while( pSav && pSav->IsInFootnote() ); + if( !pSav || !pLay->IsAnLower( pSav ) ) + return nullptr; + } + + // Tables should be saved as a whole, exception: + // The contents of a section or a cell inside a table should be saved + if ( pSav->IsInTab() && !( ( pLay->IsSctFrame() || pLay->IsCellFrame() ) && pLay->IsInTab() ) ) + while ( !pSav->IsTabFrame() ) + pSav = pSav->GetUpper(); + + if( pSav->IsInSct() ) + { // search the upmost section inside of pLay + SwFrame* pSect = pLay->FindSctFrame(); + SwFrame *pTmp = pSav; + do + { + pSav = pTmp; + pTmp = (pSav && pSav->GetUpper()) ? pSav->GetUpper()->FindSctFrame() : nullptr; + } while ( pTmp != pSect ); + } + + SwFrame *pFloat = pSav; + if( !pStart ) + pStart = pSav; + bool bGo = pStart == pSav; + do + { + if( bGo ) + pFloat->GetUpper()->m_pLower = nullptr; // detach the chain part + + // search the end of the chain part, remove Flys on the way + do + { + if( bGo ) + { + if ( pFloat->IsContentFrame() ) + { + if ( pFloat->GetDrawObjs() ) + ::lcl_RemoveObjsFromPage( static_cast<SwContentFrame*>(pFloat) ); + } + else if ( pFloat->IsTabFrame() || pFloat->IsSctFrame() ) + { + SwContentFrame *pCnt = static_cast<SwLayoutFrame*>(pFloat)->ContainsContent(); + if( pCnt ) + { + do + { if ( pCnt->GetDrawObjs() ) + ::lcl_RemoveObjsFromPage( pCnt ); + pCnt = pCnt->GetNextContentFrame(); + } while ( pCnt && static_cast<SwLayoutFrame*>(pFloat)->IsAnLower( pCnt ) ); + } + } + else { + OSL_ENSURE( !pFloat, "new FloatFrame?" ); + } + } + if ( pFloat->GetNext() ) + { + if( bGo ) + pFloat->mpUpper = nullptr; + pFloat = pFloat->GetNext(); + if( !bGo && pFloat == pStart ) + { + bGo = true; + pFloat->mpPrev->mpNext = nullptr; + pFloat->mpPrev = nullptr; + } + } + else + break; + + } while ( pFloat ); + + // search next chain part and connect both chains + SwFrame *pTmp = pFloat->FindNext(); + if( bGo ) + pFloat->mpUpper = nullptr; + + if( !pLay->IsInFootnote() ) + while( pTmp && pTmp->IsInFootnote() ) + pTmp = pTmp->FindNext(); + + if ( !pLay->IsAnLower( pTmp ) ) + pTmp = nullptr; + + if ( pTmp && bGo ) + { + pFloat->mpNext = pTmp; // connect both chains + pFloat->mpNext->mpPrev = pFloat; + } + pFloat = pTmp; + bGo = bGo || ( pStart == pFloat ); + } while ( pFloat ); + + return bGo ? pStart : nullptr; +} + +// #115759# - add also drawing objects to page and at-fly +// anchored objects to page +static void lcl_AddObjsToPage( SwFrame* _pFrame, SwPageFrame* _pPage ) +{ + OSL_ENSURE( _pFrame->GetDrawObjs(), "no DrawObjs in lcl_AddObjsToPage." ); + SwSortedObjs &rObjs = *_pFrame->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + // #115759# - unlock position of anchored object + // in order to get the object's position calculated. + pObj->UnlockPosition(); + // #115759# - add also lower objects of as-character + // anchored Writer fly frames from page + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pObj); + if ( dynamic_cast<const SwFlyFreeFrame*>( pObj) != nullptr ) + { + _pPage->AppendFlyToPage( pFlyFrame ); + } + pFlyFrame->InvalidatePos_(); + pFlyFrame->InvalidateSize_(); + pFlyFrame->InvalidatePage( _pPage ); + + // #115759# - add also at-fly anchored objects + // to page + if ( pFlyFrame->GetDrawObjs() ) + { + ::lcl_AddObjsToPage( pFlyFrame, _pPage ); + } + + SwContentFrame *pCnt = pFlyFrame->ContainsContent(); + while ( pCnt ) + { + if ( pCnt->GetDrawObjs() ) + ::lcl_AddObjsToPage( pCnt, _pPage ); + pCnt = pCnt->GetNextContentFrame(); + } + } + // #115759# - remove also drawing objects from page + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + if (pObj->GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + { + pObj->InvalidateObjPos(); + _pPage->AppendDrawObjToPage( + *static_cast<SwAnchoredDrawObject*>(pObj) ); + } + } + } +} + +void RestoreContent( SwFrame *pSav, SwLayoutFrame *pParent, SwFrame *pSibling ) +{ + OSL_ENSURE( pSav && pParent, "no Save or Parent provided for RestoreContent." ); + SwRectFnSet aRectFnSet(pParent); + + // If there are already FlowFrames below the new parent, so add the chain (starting with pSav) + // after the last one. The parts are inserted and invalidated if needed. + // On the way, the Flys of the ContentFrames are registered at the page. + + SwPageFrame *pPage = pParent->FindPageFrame(); + + if ( pPage ) + pPage->InvalidatePage( pPage ); + + // determine predecessor and establish connection or initialize + pSav->mpPrev = pSibling; + SwFrame* pNxt; + if ( pSibling ) + { + pNxt = pSibling->mpNext; + pSibling->mpNext = pSav; + pSibling->InvalidatePrt_(); + pSibling->InvalidatePage( pPage ); + SwFlowFrame *pFlowFrame = dynamic_cast<SwFlowFrame*>(pSibling); + if (pFlowFrame && pFlowFrame->GetFollow()) + pSibling->Prepare( PrepareHint::Clear, nullptr, false ); + } + else + { pNxt = pParent->m_pLower; + pParent->m_pLower = pSav; + pSav->mpUpper = pParent; // set here already, so that it is explicit when invalidating + + if ( pSav->IsContentFrame() ) + static_cast<SwContentFrame*>(pSav)->InvalidatePage( pPage ); + else + { // pSav might be an empty SectFrame + SwContentFrame* pCnt = pParent->ContainsContent(); + if( pCnt ) + pCnt->InvalidatePage( pPage ); + } + } + + // the parent needs to grow appropriately + SwTwips nGrowVal = 0; + SwFrame* pLast; + do + { pSav->mpUpper = pParent; + nGrowVal += aRectFnSet.GetHeight(pSav->getFrameArea()); + pSav->InvalidateAll_(); + + // register Flys, if TextFrames than also invalidate appropriately + if ( pSav->IsContentFrame() ) + { + if ( pSav->IsTextFrame() && + static_cast<SwTextFrame*>(pSav)->GetCacheIdx() != USHRT_MAX ) + static_cast<SwTextFrame*>(pSav)->Init(); // I am its friend + + if ( pPage && pSav->GetDrawObjs() ) + ::lcl_AddObjsToPage( static_cast<SwContentFrame*>(pSav), pPage ); + } + else + { SwContentFrame *pBlub = static_cast<SwLayoutFrame*>(pSav)->ContainsContent(); + if( pBlub ) + { + do + { if ( pPage && pBlub->GetDrawObjs() ) + ::lcl_AddObjsToPage( pBlub, pPage ); + if( pBlub->IsTextFrame() && static_cast<SwTextFrame*>(pBlub)->HasFootnote() && + static_cast<SwTextFrame*>(pBlub)->GetCacheIdx() != USHRT_MAX ) + static_cast<SwTextFrame*>(pBlub)->Init(); // I am its friend + pBlub = pBlub->GetNextContentFrame(); + } while ( pBlub && static_cast<SwLayoutFrame*>(pSav)->IsAnLower( pBlub )); + } + } + pLast = pSav; + pSav = pSav->GetNext(); + + } while ( pSav ); + + if( pNxt ) + { + pLast->mpNext = pNxt; + pNxt->mpPrev = pLast; + } + + pParent->Grow( nGrowVal ); +} + +namespace sw { + +bool IsRightPageByNumber(SwRootFrame const& rLayout, sal_uInt16 const nPageNum) +{ + assert(rLayout.GetLower()); + // unfortunately can only get SwPageDesc, not SwFormatPageDesc here... + auto const nFirstVirtPageNum(rLayout.GetLower()->GetVirtPageNum()); + bool const isFirstPageOfLayoutOdd(nFirstVirtPageNum % 2 == 1); + return ((nPageNum % 2) == 1) == isFirstPageOfLayoutOdd; +} + +} // namespace sw + +SwPageFrame * InsertNewPage( SwPageDesc &rDesc, SwFrame *pUpper, + bool const isRightPage, bool const bFirst, bool bInsertEmpty, + bool const bFootnote, + SwFrame *pSibling ) +{ + assert(pUpper); + assert(pUpper->IsRootFrame()); + assert(!pSibling || static_cast<SwLayoutFrame const*>(pUpper)->Lower() != pSibling); // currently no insert before 1st page + SwPageFrame *pRet; + SwDoc *pDoc = static_cast<SwLayoutFrame*>(pUpper)->GetFormat()->GetDoc(); + if (bFirst) + { + if (rDesc.IsFirstShared()) + { + // We need to fallback to left or right page format, decide it now. + // FIXME: is this still needed? + if (isRightPage) + { + rDesc.GetFirstMaster().SetFormatAttr( rDesc.GetMaster().GetHeader() ); + rDesc.GetFirstMaster().SetFormatAttr( rDesc.GetMaster().GetFooter() ); + // fdo#60250 copy margins for mirrored pages + rDesc.GetFirstMaster().SetFormatAttr( rDesc.GetMaster().GetLRSpace() ); + } + else + { + rDesc.GetFirstLeft().SetFormatAttr( rDesc.GetLeft().GetHeader() ); + rDesc.GetFirstLeft().SetFormatAttr( rDesc.GetLeft().GetFooter() ); + rDesc.GetFirstLeft().SetFormatAttr( rDesc.GetLeft().GetLRSpace() ); + } + } + } + SwFrameFormat *pFormat(isRightPage ? rDesc.GetRightFormat(bFirst) : rDesc.GetLeftFormat(bFirst)); + // If there is no FrameFormat for this page, add an empty page + if ( !pFormat ) + { + pFormat = isRightPage ? rDesc.GetLeftFormat() : rDesc.GetRightFormat(); + OSL_ENSURE( pFormat, "Descriptor without any format?!" ); + bInsertEmpty = !bInsertEmpty; + } + if( bInsertEmpty ) + { + SwPageDesc *pTmpDesc = pSibling && pSibling->GetPrev() ? + static_cast<SwPageFrame*>(pSibling->GetPrev())->GetPageDesc() : &rDesc; + pRet = new SwPageFrame( pDoc->GetEmptyPageFormat(), pUpper, pTmpDesc ); + SAL_INFO( "sw.pageframe", "InsertNewPage - insert empty p: " << pRet << " d: " << pTmpDesc ); + pRet->Paste( pUpper, pSibling ); + pRet->PreparePage( bFootnote ); + } + pRet = new SwPageFrame( pFormat, pUpper, &rDesc ); + SAL_INFO( "sw.pageframe", "InsertNewPage p: " << pRet << " d: " << &rDesc << " f: " << pFormat ); + pRet->Paste( pUpper, pSibling ); + pRet->PreparePage( bFootnote ); + if ( pRet->GetNext() ) + SwRootFrame::AssertPageFlys( pRet ); + return pRet; +} + +/* The following two methods search the layout structure recursively and + * register all Flys at the page that have a Frame in this structure as an anchor. + */ + +static void lcl_Regist( SwPageFrame *pPage, const SwFrame *pAnch ) +{ + SwSortedObjs *pObjs = const_cast<SwSortedObjs*>(pAnch->GetDrawObjs()); + for (SwAnchoredObject* pObj : *pObjs) + { + if (SwFlyFrame* pFly = dynamic_cast<SwFlyFrame*>(pObj)) + { + // register (not if already known) + // #i28701# - use new method <GetPageFrame()> + SwPageFrame *pPg = pFly->IsFlyFreeFrame() + ? pFly->GetPageFrame() : pFly->FindPageFrame(); + if ( pPg != pPage ) + { + if ( pPg ) + pPg->RemoveFlyFromPage( pFly ); + pPage->AppendFlyToPage( pFly ); + } + ::RegistFlys( pPage, pFly ); + } + else + { + // #i87493# + if ( pPage != pObj->GetPageFrame() ) + { + // #i28701# + if (SwPageFrame *pPg = pObj->GetPageFrame()) + pPg->RemoveDrawObjFromPage( *pObj ); + pPage->AppendDrawObjToPage( *pObj ); + } + } + + const SwFlyFrame* pFly = pAnch->FindFlyFrame(); + if ( pFly && + pObj->GetDrawObj()->GetOrdNum() < pFly->GetVirtDrawObj()->GetOrdNum() && + pObj->GetDrawObj()->getSdrPageFromSdrObject() ) + { + //#i119945# set pFly's OrdNum to pObj's. So when pFly is removed by Undo, the original OrdNum will not be changed. + pObj->DrawObj()->getSdrPageFromSdrObject()->SetObjectOrdNum( pFly->GetVirtDrawObj()->GetOrdNumDirect(), + pObj->GetDrawObj()->GetOrdNumDirect() ); + } + } +} + +void RegistFlys( SwPageFrame *pPage, const SwLayoutFrame *pLay ) +{ + if ( pLay->GetDrawObjs() ) + ::lcl_Regist( pPage, pLay ); + const SwFrame *pFrame = pLay->Lower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + ::RegistFlys( pPage, static_cast<const SwLayoutFrame*>(pFrame) ); + else if ( pFrame->GetDrawObjs() ) + ::lcl_Regist( pPage, pFrame ); + pFrame = pFrame->GetNext(); + } +} + +/// Notify the background based on the difference between old and new rectangle +void Notify( SwFlyFrame *pFly, SwPageFrame *pOld, const SwRect &rOld, + const SwRect* pOldPrt ) +{ + const SwRect aFrame( pFly->GetObjRectWithSpaces() ); + if ( rOld.Pos() != aFrame.Pos() ) + { // changed position, invalidate old and new area + if ( rOld.HasArea() && + rOld.Left()+pFly->GetFormat()->GetLRSpace().GetLeft() < FAR_AWAY ) + { + pFly->NotifyBackground( pOld, rOld, PrepareHint::FlyFrameLeave ); + } + pFly->NotifyBackground( pFly->FindPageFrame(), aFrame, PrepareHint::FlyFrameArrive ); + } + else if ( rOld.SSize() != aFrame.SSize() ) + { // changed size, invalidate the area that was left or is now overlapped + // For simplicity, we purposely invalidate a Twip even if not needed. + + SwViewShell *pSh = pFly->getRootFrame()->GetCurrShell(); + if( pSh && rOld.HasArea() ) + pSh->InvalidateWindows( rOld ); + + // #i51941# - consider case that fly frame isn't + // registered at the old page <pOld> + SwPageFrame* pPageFrame = pFly->FindPageFrame(); + if ( pOld != pPageFrame ) + { + pFly->NotifyBackground( pPageFrame, aFrame, PrepareHint::FlyFrameArrive ); + } + + if ( rOld.Left() != aFrame.Left() ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Left( std::min(aFrame.Left(), rOld.Left()) ); + aTmp.Right( std::max(aFrame.Left(), rOld.Left()) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + SwTwips nOld = rOld.Right(); + SwTwips nNew = aFrame.Right(); + if ( nOld != nNew ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Left( std::min(nNew, nOld) ); + aTmp.Right( std::max(nNew, nOld) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + if ( rOld.Top() != aFrame.Top() ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Top( std::min(aFrame.Top(), rOld.Top()) ); + aTmp.Bottom( std::max(aFrame.Top(), rOld.Top()) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + nOld = rOld.Bottom(); + nNew = aFrame.Bottom(); + if ( nOld != nNew ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Top( std::min(nNew, nOld) ); + aTmp.Bottom( std::max(nNew, nOld) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + } + else if(pOldPrt && *pOldPrt != pFly->getFramePrintArea()) + { + bool bNotifyBackground(pFly->GetFormat()->GetSurround().IsContour()); + + if(!bNotifyBackground && + pFly->IsFlyFreeFrame() && + static_cast< const SwFlyFreeFrame* >(pFly)->supportsAutoContour()) + { + // RotateFlyFrame3: Also notify for FlyFrames which allow AutoContour + bNotifyBackground = true; + } + + if(bNotifyBackground) + { + // #i24097# + pFly->NotifyBackground( pFly->FindPageFrame(), aFrame, PrepareHint::FlyFrameArrive ); + } + } +} + +static void lcl_CheckFlowBack( SwFrame* pFrame, const SwRect &rRect ) +{ + SwTwips nBottom = rRect.Bottom(); + while( pFrame ) + { + if( pFrame->IsLayoutFrame() ) + { + if( rRect.IsOver( pFrame->getFrameArea() ) ) + lcl_CheckFlowBack( static_cast<SwLayoutFrame*>(pFrame)->Lower(), rRect ); + } + else if( !pFrame->GetNext() && nBottom > pFrame->getFrameArea().Bottom() ) + { + if( pFrame->IsContentFrame() && static_cast<SwContentFrame*>(pFrame)->HasFollow() ) + pFrame->InvalidateSize(); + else + pFrame->InvalidateNextPos(); + } + pFrame = pFrame->GetNext(); + } +} + +static void lcl_NotifyContent( const SdrObject *pThis, SwContentFrame *pCnt, + const SwRect &rRect, const PrepareHint eHint ) +{ + if ( pCnt->IsTextFrame() ) + { + SwRect aCntPrt( pCnt->getFramePrintArea() ); + aCntPrt.Pos() += pCnt->getFrameArea().Pos(); + if ( eHint == PrepareHint::FlyFrameAttributesChanged ) + { + // #i35640# - use given rectangle <rRect> instead + // of current bound rectangle + if ( aCntPrt.IsOver( rRect ) ) + pCnt->Prepare( PrepareHint::FlyFrameAttributesChanged ); + } + // #i23129# - only invalidate, if the text frame + // printing area overlaps with the given rectangle. + else if ( aCntPrt.IsOver( rRect ) ) + pCnt->Prepare( eHint, static_cast<void*>(&aCntPrt.Intersection_( rRect )) ); + if ( pCnt->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( dynamic_cast<const SwFlyFrame*>( pObj) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pObj); + if ( pFly->IsFlyInContentFrame() ) + { + SwContentFrame *pContent = pFly->ContainsContent(); + while ( pContent ) + { + ::lcl_NotifyContent( pThis, pContent, rRect, eHint ); + pContent = pContent->GetNextContentFrame(); + } + } + } + } + } + } +} + +void Notify_Background( const SdrObject* pObj, + SwPageFrame* pPage, + const SwRect& rRect, + const PrepareHint eHint, + const bool bInva ) +{ + // If the frame was positioned correctly for the first time, do not inform the old area + if ( eHint == PrepareHint::FlyFrameLeave && rRect.Top() == FAR_AWAY ) + return; + + SwLayoutFrame* pArea; + SwFlyFrame *pFlyFrame = nullptr; + SwFrame* pAnchor; + if( auto pVirtFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>( pObj) ) + { + pFlyFrame = const_cast<SwVirtFlyDrawObj*>(pVirtFlyDrawObj)->GetFlyFrame(); + pAnchor = pFlyFrame->AnchorFrame(); + } + else + { + pFlyFrame = nullptr; + pAnchor = const_cast<SwFrame*>( + GetUserCall(pObj)->GetAnchoredObj( pObj )->GetAnchorFrame() ); + } + if( PrepareHint::FlyFrameLeave != eHint && pAnchor->IsInFly() ) + pArea = pAnchor->FindFlyFrame(); + else + pArea = pPage; + SwContentFrame *pCnt = nullptr; + if ( pArea ) + { + if( PrepareHint::FlyFrameArrive != eHint ) + lcl_CheckFlowBack( pArea, rRect ); + + // Only the Flys following this anchor are reacting. Thus, those do not + // need to be processed. + // An exception is LEAVE, since the Fly might come "from above". + // If the anchor is positioned on the previous page, the whole page + // needs to be processed (47722). + // OD 2004-05-13 #i28701# - If the wrapping style has to be considered + // on the object positioning, the complete area has to be processed, + // because content frames before the anchor frame also have to consider + // the object for the text wrapping. + // #i3317# - The complete area has always been + // processed. + { + pCnt = pArea->ContainsContent(); + } + } + SwFrame *pLastTab = nullptr; + + bool isValidTableBeforeAnchor(false); + while ( pCnt && pArea && pArea->IsAnLower( pCnt ) ) + { + ::lcl_NotifyContent( pObj, pCnt, rRect, eHint ); + if ( pCnt->IsInTab() ) + { + SwTabFrame *pTab = pCnt->FindTabFrame(); + if ( pTab != pLastTab ) + { + pLastTab = pTab; + isValidTableBeforeAnchor = false; + if (PrepareHint::FlyFrameArrive == eHint + && pFlyFrame // TODO: do it for draw objects too? + && pTab->IsFollow() // table starts on previous page? + // "through" means they will actually overlap anyway + && css::text::WrapTextMode_THROUGH != pFlyFrame->GetFormat()->GetSurround().GetSurround() + // if it's anchored in footer it can't move to other page + && !pAnchor->FindFooterOrHeader()) + { + SwFrame * pTmp(pAnchor->GetPrev()); + while (pTmp) + { + if (pTmp == pTab) + { + // tdf#99460 the table shouldn't be moved by the fly + isValidTableBeforeAnchor = true; + break; + } + pTmp = pTmp->GetPrev(); + } + } + // #i40606# - use <GetLastBoundRect()> + // instead of <GetCurrentBoundRect()>, because a recalculation + // of the bounding rectangle isn't intended here. + if (!isValidTableBeforeAnchor + && (pTab->getFrameArea().IsOver(pObj->GetLastBoundRect()) || + pTab->getFrameArea().IsOver(rRect))) + { + if ( !pFlyFrame || !pFlyFrame->IsLowerOf( pTab ) ) + pTab->InvalidatePrt(); + } + } + SwLayoutFrame* pCell = pCnt->GetUpper(); + // #i40606# - use <GetLastBoundRect()> + // instead of <GetCurrentBoundRect()>, because a recalculation + // of the bounding rectangle isn't intended here. + if (!isValidTableBeforeAnchor && pCell->IsCellFrame() && + ( pCell->getFrameArea().IsOver( pObj->GetLastBoundRect() ) || + pCell->getFrameArea().IsOver( rRect ) ) ) + { + const SwFormatVertOrient &rOri = pCell->GetFormat()->GetVertOrient(); + if ( text::VertOrientation::NONE != rOri.GetVertOrient() ) + pCell->InvalidatePrt(); + } + } + pCnt = pCnt->GetNextContentFrame(); + } + // #128702# - make code robust + if ( pPage && pPage->GetSortedObjs() ) + { + pObj->GetOrdNum(); + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( dynamic_cast<const SwFlyFrame*>( pAnchoredObj) != nullptr ) + { + if( pAnchoredObj->GetDrawObj() == pObj ) + continue; + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + if ( pFly->getFrameArea().Top() == FAR_AWAY ) + continue; + + if ( !pFlyFrame || + (!pFly->IsLowerOf( pFlyFrame ) && + pFly->GetVirtDrawObj()->GetOrdNumDirect() < pObj->GetOrdNumDirect())) + { + pCnt = pFly->ContainsContent(); + while ( pCnt ) + { + ::lcl_NotifyContent( pObj, pCnt, rRect, eHint ); + pCnt = pCnt->GetNextContentFrame(); + } + } + if( pFly->IsFlyLayFrame() ) + { + if( pFly->Lower() && pFly->Lower()->IsColumnFrame() && + pFly->getFrameArea().Bottom() >= rRect.Top() && + pFly->getFrameArea().Top() <= rRect.Bottom() && + pFly->getFrameArea().Right() >= rRect.Left() && + pFly->getFrameArea().Left() <= rRect.Right() ) + { + pFly->InvalidateSize(); + } + } + // Flys above myself might sidestep if they have an automatic + // alignment. This happens independently of my attributes since + // this might have been changed as well. + else if ( pFly->IsFlyAtContentFrame() && + pObj->GetOrdNumDirect() < + pFly->GetVirtDrawObj()->GetOrdNumDirect() && + pFlyFrame && !pFly->IsLowerOf( pFlyFrame ) ) + { + const SwFormatHoriOrient &rH = pFly->GetFormat()->GetHoriOrient(); + if ( text::HoriOrientation::NONE != rH.GetHoriOrient() && + text::HoriOrientation::CENTER != rH.GetHoriOrient() && + ( !pFly->IsAutoPos() || text::RelOrientation::CHAR != rH.GetRelationOrient() ) && + (pFly->getFrameArea().Bottom() >= rRect.Top() && + pFly->getFrameArea().Top() <= rRect.Bottom()) ) + pFly->InvalidatePos(); + } + } + } + } + if ( pFlyFrame && pAnchor->GetUpper() && pAnchor->IsInTab() )//MA_FLY_HEIGHT + pAnchor->GetUpper()->InvalidateSize(); + + // #i82258# - make code robust + SwViewShell* pSh = nullptr; + if ( bInva && pPage && + nullptr != (pSh = pPage->getRootFrame()->GetCurrShell()) ) + { + pSh->InvalidateWindows( rRect ); + } +} + +/// Provides the Upper of an anchor in paragraph-bound objects. If the latter +/// is a chained border or a footnote, the "virtual" Upper might be returned. +const SwFrame* GetVirtualUpper( const SwFrame* pFrame, const Point& rPos ) +{ + if( pFrame->IsTextFrame() ) + { + pFrame = pFrame->GetUpper(); + if( !pFrame->getFrameArea().IsInside( rPos ) ) + { + if( pFrame->IsFootnoteFrame() ) + { + const SwFootnoteFrame* pTmp = static_cast<const SwFootnoteFrame*>(pFrame)->GetFollow(); + while( pTmp ) + { + if( pTmp->getFrameArea().IsInside( rPos ) ) + return pTmp; + pTmp = pTmp->GetFollow(); + } + } + else + { + SwFlyFrame* pTmp = const_cast<SwFlyFrame*>(pFrame->FindFlyFrame()); + while( pTmp ) + { + if( pTmp->getFrameArea().IsInside( rPos ) ) + return pTmp; + pTmp = pTmp->GetNextLink(); + } + } + } + } + return pFrame; +} + +bool Is_Lower_Of(const SwFrame *pCurrFrame, const SdrObject* pObj) +{ + Point aPos; + const SwFrame* pFrame; + if (const SwVirtFlyDrawObj *pFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>(pObj)) + { + const SwFlyFrame* pFly = pFlyDrawObj->GetFlyFrame(); + pFrame = pFly->GetAnchorFrame(); + aPos = pFly->getFrameArea().Pos(); + } + else + { + pFrame = static_cast<SwDrawContact*>(GetUserCall(pObj))->GetAnchorFrame(pObj); + aPos = pObj->GetCurrentBoundRect().TopLeft(); + } + OSL_ENSURE( pFrame, "8-( Fly is lost in Space." ); + pFrame = GetVirtualUpper( pFrame, aPos ); + do + { if ( pFrame == pCurrFrame ) + return true; + if( pFrame->IsFlyFrame() ) + { + aPos = pFrame->getFrameArea().Pos(); + pFrame = GetVirtualUpper( static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(), aPos ); + } + else + pFrame = pFrame->GetUpper(); + } while ( pFrame ); + return false; +} + +/// provides the area of a frame in that no Fly from another area can overlap +const SwFrame *FindContext( const SwFrame *pFrame, SwFrameType nAdditionalContextType ) +{ + const SwFrameType nTyp = SwFrameType::Root | SwFrameType::Header | SwFrameType::Footer | SwFrameType::FtnCont | + SwFrameType::Ftn | SwFrameType::Fly | + SwFrameType::Tab | SwFrameType::Row | SwFrameType::Cell | + nAdditionalContextType; + do + { if ( pFrame->GetType() & nTyp ) + break; + pFrame = pFrame->GetUpper(); + } while( pFrame ); + return pFrame; +} + +bool IsFrameInSameContext( const SwFrame *pInnerFrame, const SwFrame *pFrame ) +{ + const SwFrame *pContext = FindContext( pInnerFrame, SwFrameType::None ); + + const SwFrameType nTyp = SwFrameType::Root | SwFrameType::Header | SwFrameType::Footer | SwFrameType::FtnCont | + SwFrameType::Ftn | SwFrameType::Fly | + SwFrameType::Tab | SwFrameType::Row | SwFrameType::Cell; + do + { if ( pFrame->GetType() & nTyp ) + { + if( pFrame == pContext ) + return true; + if( pFrame->IsCellFrame() ) + return false; + } + if( pFrame->IsFlyFrame() ) + { + Point aPos( pFrame->getFrameArea().Pos() ); + pFrame = GetVirtualUpper( static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(), aPos ); + } + else + pFrame = pFrame->GetUpper(); + } while( pFrame ); + + return false; +} + +static SwTwips lcl_CalcCellRstHeight( SwLayoutFrame *pCell ) +{ + SwFrame *pLow = pCell->Lower(); + if ( pLow && (pLow->IsContentFrame() || pLow->IsSctFrame()) ) + { + long nHeight = 0, nFlyAdd = 0; + do + { + long nLow = pLow->getFrameArea().Height(); + if( pLow->IsTextFrame() && static_cast<SwTextFrame*>(pLow)->IsUndersized() ) + nLow += static_cast<SwTextFrame*>(pLow)->GetParHeight()-pLow->getFramePrintArea().Height(); + else if( pLow->IsSctFrame() && static_cast<SwSectionFrame*>(pLow)->IsUndersized() ) + nLow += static_cast<SwSectionFrame*>(pLow)->Undersize(); + nFlyAdd = std::max( 0L, nFlyAdd - nLow ); + nFlyAdd = std::max( nFlyAdd, ::CalcHeightWithFlys( pLow ) ); + nHeight += nLow; + pLow = pLow->GetNext(); + } while ( pLow ); + if ( nFlyAdd ) + nHeight += nFlyAdd; + + // The border cannot be calculated based on PrtArea and Frame, since both can be invalid. + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nHeight += rAttrs.CalcTop() + rAttrs.CalcBottom(); + + return pCell->getFrameArea().Height() - nHeight; + } + else + { + long nRstHeight = 0; + while (pLow && pLow->IsLayoutFrame()) + { + nRstHeight += ::CalcRowRstHeight(static_cast<SwLayoutFrame*>(pLow)); + pLow = pLow->GetNext(); + } + return nRstHeight; + } +} + +SwTwips CalcRowRstHeight( SwLayoutFrame *pRow ) +{ + SwFrame *pLow = pRow->Lower(); + if (!(pLow && pLow->IsLayoutFrame())) + { + return 0; + } + SwTwips nRstHeight = LONG_MAX; + while (pLow && pLow->IsLayoutFrame()) + { + nRstHeight = std::min(nRstHeight, ::lcl_CalcCellRstHeight(static_cast<SwLayoutFrame*>(pLow))); + pLow = pLow->GetNext(); + } + return nRstHeight; +} + +const SwFrame* FindPage( const SwRect &rRect, const SwFrame *pPage ) +{ + if ( !rRect.IsOver( pPage->getFrameArea() ) ) + { + const SwRootFrame* pRootFrame = static_cast<const SwRootFrame*>(pPage->GetUpper()); + const SwFrame* pTmpPage = pRootFrame ? pRootFrame->GetPageAtPos( rRect.TopLeft(), &rRect.SSize(), true ) : nullptr; + if ( pTmpPage ) + pPage = pTmpPage; + } + + return pPage; +} + +namespace { + +class SwFrameHolder : private SfxListener +{ + SwFrame* pFrame; + bool bSet; + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; +public: + SwFrameHolder() : pFrame(nullptr), bSet(false) {} + void SetFrame( SwFrame* pHold ); + SwFrame* GetFrame() { return pFrame; } + void Reset(); + bool IsSet() const { return bSet; } +}; + +} + +void SwFrameHolder::SetFrame( SwFrame* pHold ) +{ + bSet = true; + if (pFrame != pHold) + { + if (pFrame) + EndListening(*pFrame); + StartListening(*pHold); + pFrame = pHold; + } +} + +void SwFrameHolder::Reset() +{ + if (pFrame) + EndListening(*pFrame); + bSet = false; + pFrame = nullptr; +} + +void SwFrameHolder::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying && &rBC == pFrame ) + { + pFrame = nullptr; + } +} + +SwFrame* GetFrameOfModify(SwRootFrame const*const pLayout, SwModify const& rMod, + SwFrameType const nFrameType, SwPosition const*const pPos, + std::pair<Point, bool> const*const pViewPosAndCalcFrame) +{ + SwFrame *pMinFrame = nullptr, *pTmpFrame; + SwFrameHolder aHolder; + SwRect aCalcRect; + bool bClientIterChanged = false; + + SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti> aIter(rMod); + do { + pMinFrame = nullptr; + aHolder.Reset(); + sal_uInt64 nMinDist = 0; + bClientIterChanged = false; + + for( pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() ) + { + if( pTmpFrame->GetType() & nFrameType && + ( !pLayout || pLayout == pTmpFrame->getRootFrame() ) && + (!pTmpFrame->IsFlowFrame() || + !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow() )) + { + if (pViewPosAndCalcFrame) + { + // watch for Frame being deleted + if ( pMinFrame ) + aHolder.SetFrame( pMinFrame ); + else + aHolder.Reset(); + + if (pViewPosAndCalcFrame->second) + { + // tdf#108118 prevent recursion + DisableCallbackAction a(*pTmpFrame->getRootFrame()); + // - format parent Writer + // fly frame, if it isn't been formatted yet. + // Note: The Writer fly frame could be the frame itself. + SwFlyFrame* pFlyFrame( pTmpFrame->FindFlyFrame() ); + if ( pFlyFrame && + pFlyFrame->getFrameArea().Pos().X() == FAR_AWAY && + pFlyFrame->getFrameArea().Pos().Y() == FAR_AWAY ) + { + SwObjectFormatter::FormatObj( *pFlyFrame ); + } + pTmpFrame->Calc(pLayout ? pLayout->GetCurrShell()->GetOut() : nullptr); + } + + // aIter.IsChanged checks if the current pTmpFrame has been deleted while + // it is the current iterator + // FrameHolder watches for deletion of the current pMinFrame + if( aIter.IsChanged() || ( aHolder.IsSet() && !aHolder.GetFrame() ) ) + { + // restart iteration + bClientIterChanged = true; + break; + } + + // for Flys go via the parent if the Fly is not yet "formatted" + if (!pViewPosAndCalcFrame->second && + pTmpFrame->GetType() & SwFrameType::Fly && + static_cast<SwFlyFrame*>(pTmpFrame)->GetAnchorFrame() && + FAR_AWAY == pTmpFrame->getFrameArea().Pos().getX() && + FAR_AWAY == pTmpFrame->getFrameArea().Pos().getY() ) + aCalcRect = static_cast<SwFlyFrame*>(pTmpFrame)->GetAnchorFrame()->getFrameArea(); + else + aCalcRect = pTmpFrame->getFrameArea(); + + if (aCalcRect.IsInside(pViewPosAndCalcFrame->first)) + { + pMinFrame = pTmpFrame; + break; + } + + // Point not in rectangle. Compare distances: + const Point aCalcRectCenter = aCalcRect.Center(); + const Point aDiff = aCalcRectCenter - pViewPosAndCalcFrame->first; + const sal_uInt64 nCurrentDist = sal_Int64(aDiff.getX()) * sal_Int64(aDiff.getX()) + sal_Int64(aDiff.getY()) * sal_Int64(aDiff.getY()); // opt: no sqrt + if ( !pMinFrame || nCurrentDist < nMinDist ) + { + pMinFrame = pTmpFrame; + nMinDist = nCurrentDist; + } + } + else + { + // if no pViewPosAndCalcFrame is provided, take the first one + pMinFrame = pTmpFrame; + break; + } + } + } + } while( bClientIterChanged ); + + if( pPos && pMinFrame && pMinFrame->IsTextFrame() ) + return static_cast<SwTextFrame*>(pMinFrame)->GetFrameAtPos( *pPos ); + + return pMinFrame; +} + +bool IsExtraData( const SwDoc *pDoc ) +{ + const SwLineNumberInfo &rInf = pDoc->GetLineNumberInfo(); + return rInf.IsPaintLineNumbers() || + rInf.IsCountInFlys() || + (static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos()) != text::HoriOrientation::NONE && + !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()); +} + +// OD 22.09.2003 #110978# +SwRect SwPageFrame::PrtWithoutHeaderAndFooter() const +{ + SwRect aPrtWithoutHeaderFooter( getFramePrintArea() ); + aPrtWithoutHeaderFooter.Pos() += getFrameArea().Pos(); + + const SwFrame* pLowerFrame = Lower(); + while ( pLowerFrame ) + { + // Note: independent on text direction page header and page footer are + // always at top respectively at bottom of the page frame. + if ( pLowerFrame->IsHeaderFrame() ) + { + aPrtWithoutHeaderFooter.AddTop( pLowerFrame->getFrameArea().Height() ); + } + if ( pLowerFrame->IsFooterFrame() ) + { + aPrtWithoutHeaderFooter.AddBottom( - pLowerFrame->getFrameArea().Height() ); + } + + pLowerFrame = pLowerFrame->GetNext(); + } + + return aPrtWithoutHeaderFooter; +} + +/** method to determine the spacing values of a frame + + OD 2004-03-10 #i28701# + OD 2009-08-28 #i102458# + Add output parameter <obIsLineSpacingProportional> +*/ +void GetSpacingValuesOfFrame( const SwFrame& rFrame, + SwTwips& onLowerSpacing, + SwTwips& onLineSpacing, + bool& obIsLineSpacingProportional ) +{ + if ( !rFrame.IsFlowFrame() ) + { + onLowerSpacing = 0; + onLineSpacing = 0; + } + else + { + const SvxULSpaceItem& rULSpace = rFrame.GetAttrSet()->GetULSpace(); + onLowerSpacing = rULSpace.GetLower(); + + onLineSpacing = 0; + obIsLineSpacingProportional = false; + if ( rFrame.IsTextFrame() ) + { + onLineSpacing = static_cast<const SwTextFrame&>(rFrame).GetLineSpace(); + obIsLineSpacingProportional = + onLineSpacing != 0 && + static_cast<const SwTextFrame&>(rFrame).GetLineSpace( true ) == 0; + } + + OSL_ENSURE( onLowerSpacing >= 0 && onLineSpacing >= 0, + "<GetSpacingValuesOfFrame(..)> - spacing values aren't positive!" ); + } +} + +/// get the content of the table cell, skipping content from nested tables +const SwContentFrame* GetCellContent( const SwLayoutFrame& rCell ) +{ + const SwContentFrame* pContent = rCell.ContainsContent(); + const SwTabFrame* pTab = rCell.FindTabFrame(); + + while ( pContent && rCell.IsAnLower( pContent ) ) + { + const SwTabFrame* pTmpTab = pContent->FindTabFrame(); + if ( pTmpTab != pTab ) + { + SwFrame const*const pTmp = pTmpTab->FindLastContentOrTable(); + if (pTmp) + { + pContent = pTmp->FindNextCnt(); + } + else + { + pContent = nullptr; + } + } + else + break; + } + return pContent; +} + +SwDeletionChecker::SwDeletionChecker(const SwFrame* pFrame) + : mpFrame( pFrame ) + , mpRegIn( pFrame + ? pFrame->IsTextFrame() + // sw_redlinehide: GetDep() may be a member of SwTextFrame! + ? static_cast<SwTextFrame const*>(pFrame)->GetTextNodeFirst() + : const_cast<SwFrame*>(pFrame)->GetDep() + : nullptr ) +{ +} + +/// Can be used to check if a frame has been deleted +bool SwDeletionChecker::HasBeenDeleted() const +{ + if ( !mpFrame || !mpRegIn ) + return false; + + SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti> aIter(*mpRegIn); + SwFrame* pLast = aIter.First(); + while ( pLast ) + { + if ( pLast == mpFrame ) + return false; + pLast = aIter.Next(); + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/ftnfrm.cxx b/sw/source/core/layout/ftnfrm.cxx new file mode 100644 index 000000000..8ddd9747f --- /dev/null +++ b/sw/source/core/layout/ftnfrm.cxx @@ -0,0 +1,2968 @@ +/* -*- 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 <txtftn.hxx> +#include <fmtftn.hxx> +#include <ftnidx.hxx> +#include <pagefrm.hxx> +#include <colfrm.hxx> +#include <rootfrm.hxx> +#include <frmtool.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <tabfrm.hxx> +#include <pagedesc.hxx> +#include <ftninfo.hxx> +#include <sectfrm.hxx> +#include <objectformatter.hxx> +#include <viewopt.hxx> +#include <calbck.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <sal/log.hxx> +#include <IDocumentSettingAccess.hxx> + +#define ENDNOTE 0x80000000 + +/// Search the position of an attribute in the FootnoteArray at the document, +/// because all footnotes are located there, ordered by their index. +static sal_uLong lcl_FindFootnotePos( const SwDoc *pDoc, const SwTextFootnote *pAttr ) +{ + const SwFootnoteIdxs &rFootnoteIdxs = pDoc->GetFootnoteIdxs(); + + SwTextFootnote* pBla = const_cast<SwTextFootnote*>(pAttr); + SwFootnoteIdxs::const_iterator it = rFootnoteIdxs.find( pBla ); + if ( it != rFootnoteIdxs.end() ) + { + sal_uLong nRet = it - rFootnoteIdxs.begin(); + if( pAttr->GetFootnote().IsEndNote() ) + return nRet + ENDNOTE; + return nRet; + } + OSL_ENSURE( !pDoc, "FootnotePos not found." ); + return 0; +} + +bool SwFootnoteFrame::operator<( const SwTextFootnote* pTextFootnote ) const +{ + const SwDoc* pDoc = GetFormat()->GetDoc(); + OSL_ENSURE( pDoc, "SwFootnoteFrame: Missing doc!" ); + return lcl_FindFootnotePos( pDoc, GetAttr() ) < + lcl_FindFootnotePos( pDoc, pTextFootnote ); +} + +/* +|* +|* bool lcl_NextFootnoteBoss( SwFootnoteBossFrame* pBoss, SwPageFrame* pPage) +|* sets pBoss on the next SwFootnoteBossFrame, which can either be a column +|* or a page (without columns). If the page changes meanwhile, +|* pPage contains the new page and this function returns true. +|* +|*/ + +static bool lcl_NextFootnoteBoss( SwFootnoteBossFrame* &rpBoss, SwPageFrame* &rpPage, + bool bDontLeave ) +{ + if( rpBoss->IsColumnFrame() ) + { + if( rpBoss->GetNext() ) + { + rpBoss = static_cast<SwFootnoteBossFrame*>(rpBoss->GetNext()); //next column + return false; + } + if( rpBoss->IsInSct() ) + { + SwSectionFrame* pSct = rpBoss->FindSctFrame()->GetFollow(); + if( pSct ) + { + OSL_ENSURE( pSct->Lower() && pSct->Lower()->IsColumnFrame(), + "Where's the column?" ); + rpBoss = static_cast<SwColumnFrame*>(pSct->Lower()); + SwPageFrame* pOld = rpPage; + rpPage = pSct->FindPageFrame(); + return pOld != rpPage; + } + else if( bDontLeave ) + { + rpPage = nullptr; + rpBoss = nullptr; + return false; + } + } + } + rpPage = static_cast<SwPageFrame*>(rpPage->GetNext()); // next page + rpBoss = rpPage; + if( rpPage ) + { + SwLayoutFrame* pBody = rpPage->FindBodyCont(); + if( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + rpBoss = static_cast<SwFootnoteBossFrame*>(pBody->Lower()); // first column + } + return true; +} + +/// @returns column number if pBoss is a column, otherwise 0. +static sal_uInt16 lcl_ColumnNum( const SwFrame* pBoss ) +{ + sal_uInt16 nRet = 0; + if( !pBoss->IsColumnFrame() ) + return 0; + const SwFrame* pCol; + if( pBoss->IsInSct() ) + { + pCol = pBoss->GetUpper()->FindColFrame(); + if( pBoss->GetNext() || pBoss->GetPrev() ) + { + while( pBoss ) + { + ++nRet; // Section columns + pBoss = pBoss->GetPrev(); + } + } + } + else + pCol = pBoss; + while( pCol ) + { + nRet += 256; // Page columns + pCol = pCol->GetPrev(); + } + return nRet; +} + +SwFootnoteContFrame::SwFootnoteContFrame( SwFrameFormat *pFormat, SwFrame* pSib ): + SwLayoutFrame( pFormat, pSib ) +{ + mnFrameType = SwFrameType::FtnCont; +} + +SwFootnoteFrame* SwFootnoteContFrame::AddChained(bool bAppend, SwFrame* pThis, bool bDefaultFormat) +{ + SwFootnoteFrame *pOld = pThis->FindFootnoteFrame(); + SwFrameFormat *pFormat = pOld->GetFormat(); + if (bDefaultFormat) + pFormat = pFormat->GetDoc()->GetDfltFrameFormat(); + + SwFootnoteFrame *pNew = new SwFootnoteFrame(pFormat, pOld, pOld->GetRef(), pOld->GetAttr()); + + if (bAppend) + { + if (pOld->GetFollow()) + { + pNew->SetFollow(pOld->GetFollow()); + pOld->GetFollow()->SetMaster(pNew); + } + pOld->SetFollow(pNew); + pNew->SetMaster(pOld); + } + else + { + if (pOld->GetMaster()) + { + pNew->SetMaster(pOld->GetMaster()); + pOld->GetMaster()->SetFollow(pNew); + } + pNew->SetFollow(pOld); + pOld->SetMaster(pNew); + } + + return pNew; +} + +// lcl_Undersize(..) walks over a SwFrame and its contents +// and returns the sum of all requested TextFrame magnifications. + +static long lcl_Undersize( const SwFrame* pFrame ) +{ + long nRet = 0; + SwRectFnSet aRectFnSet(pFrame); + if( pFrame->IsTextFrame() ) + { + if( static_cast<const SwTextFrame*>(pFrame)->IsUndersized() ) + { + // Does this TextFrame would like to be a little bit bigger? + nRet = static_cast<const SwTextFrame*>(pFrame)->GetParHeight() - + aRectFnSet.GetHeight(pFrame->getFramePrintArea()); + if( nRet < 0 ) + nRet = 0; + } + } + else if( pFrame->IsLayoutFrame() ) + { + const SwFrame* pNxt = static_cast<const SwLayoutFrame*>(pFrame)->Lower(); + while( pNxt ) + { + nRet += lcl_Undersize( pNxt ); + pNxt = pNxt->GetNext(); + } + } + return nRet; +} + +namespace sw { + +SwTwips FootnoteSeparatorHeight(SwPageFootnoteInfo const& rInf) +{ + return rInf.GetTopDist() + rInf.GetBottomDist() + rInf.GetLineWidth(); +} + +} // namespace sw + +/// "format" the frame (Fixsize is not set here). +void SwFootnoteContFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + // calculate total border, only one distance to the top + const SwPageFrame* pPage = FindPageFrame(); + const SwPageFootnoteInfo &rInf = pPage->GetPageDesc()->GetFootnoteInfo(); + const SwTwips nBorder = sw::FootnoteSeparatorHeight(rInf); + SwRectFnSet aRectFnSet(this); + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + aRectFnSet.SetTop( aPrt, nBorder ); + aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(getFrameArea()) ); + aRectFnSet.SetHeight(aPrt, aRectFnSet.GetHeight(getFrameArea()) - nBorder ); + + if( aRectFnSet.GetHeight(aPrt) < 0 && !pPage->IsFootnotePage() ) + { + setFrameAreaSizeValid(false); + } + } + + if ( !isFrameAreaSizeValid() ) + { + bool bGrow = pPage->IsFootnotePage(); + if( bGrow ) + { + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + bGrow = false; + } + if( bGrow ) + Grow( LONG_MAX ); + else + { + // VarSize is determined based on the content plus the borders + SwTwips nRemaining = 0; + SwFrame *pFrame = m_pLower; + while ( pFrame ) + { // lcl_Undersize(..) respects (recursively) TextFrames, which + // would like to be bigger. They are created especially in + // columnized borders, if these do not have their maximum + // size yet. + nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()) + lcl_Undersize( pFrame ); + pFrame = pFrame->GetNext(); + } + // add the own border + nRemaining += nBorder; + + SwTwips nDiff; + if( IsInSct() ) + { + nDiff = -aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()) ); + if( nDiff > 0 ) + { + if( nDiff > aRectFnSet.GetHeight(getFrameArea()) ) + { + nDiff = aRectFnSet.GetHeight(getFrameArea()); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, -nDiff ); + aRectFnSet.AddHeight( aFrm, -nDiff ); + } + } + nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; + if ( nDiff > 0 ) + Shrink( nDiff ); + else if ( nDiff < 0 ) + { + Grow( -nDiff ); + // It may happen that there is less space available, + // than what the border needs - the size of the PrtArea + // will then be negative. + SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( nPrtHeight < 0 ) + { + const SwTwips nTmpDiff = std::max( aRectFnSet.GetTop(getFramePrintArea()), -nPrtHeight ); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SubTop( aPrt, nTmpDiff ); + } + } + } + + setFrameAreaSizeValid(true); + } +} + +SwTwips SwFootnoteContFrame::GrowFrame( SwTwips nDist, bool bTst, bool ) +{ + // No check if FixSize since FootnoteContainer are variable up to their max. height. + // If the max. height is LONG_MAX, take as much space as needed. + // If the page is a special footnote page, take also as much as possible. + assert(GetUpper() && GetUpper()->IsFootnoteBossFrame()); + + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.GetHeight(getFrameArea()) > 0 && + nDist > ( LONG_MAX - aRectFnSet.GetHeight(getFrameArea()) ) ) + nDist = LONG_MAX - aRectFnSet.GetHeight(getFrameArea()); + + SwFootnoteBossFrame *pBoss = static_cast<SwFootnoteBossFrame*>(GetUpper()); + if( IsInSct() ) + { + SwSectionFrame* pSect = FindSctFrame(); + OSL_ENSURE( pSect, "GrowFrame: Missing SectFrame" ); + // In a section, which has to maximize, a footnotecontainer is allowed + // to grow, when the section can't grow anymore. + if( !bTst && !pSect->IsColLocked() && + pSect->ToMaximize( false ) && pSect->Growable() ) + { + pSect->InvalidateSize(); + return 0; + } + } + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwPageFrame *pPage = pBoss->FindPageFrame(); + if ( bBrowseMode || !pPage->IsFootnotePage() ) + { + if ( pBoss->GetMaxFootnoteHeight() != LONG_MAX ) + { + nDist = std::min( nDist, pBoss->GetMaxFootnoteHeight() + - aRectFnSet.GetHeight(getFrameArea()) ); + if ( nDist <= 0 ) + return 0; + } + // FootnoteBoss also influences the max value + if( !IsInSct() ) + { + const SwTwips nMax = pBoss->GetVarSpace(); + if ( nDist > nMax ) + nDist = nMax; + if ( nDist <= 0 ) + return 0; + } + } + else if( nDist > aRectFnSet.GetHeight(GetPrev()->getFrameArea()) ) + // do not use more space than the body has + nDist = aRectFnSet.GetHeight(GetPrev()->getFrameArea()); + + long nAvail = 0; + if ( bBrowseMode ) + { + nAvail = GetUpper()->getFramePrintArea().Height(); + const SwFrame *pAvail = GetUpper()->Lower(); + do + { nAvail -= pAvail->getFrameArea().Height(); + pAvail = pAvail->GetNext(); + } while ( pAvail ); + if ( nAvail > nDist ) + nAvail = nDist; + } + + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) + nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + } + long nGrow = nDist - nAvail, + nReal = 0; + if ( nGrow > 0 ) + { + SwNeighbourAdjust nAdjust = pBoss->NeighbourhoodAdjustment(); + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + nReal = AdjustNeighbourhood( nGrow, bTst ); + else + { + if( SwNeighbourAdjust::GrowAdjust == nAdjust ) + { + SwFrame* pFootnote = Lower(); + if( pFootnote ) + { + while( pFootnote->GetNext() ) + pFootnote = pFootnote->GetNext(); + if( static_cast<SwFootnoteFrame*>(pFootnote)->GetAttr()->GetFootnote().IsEndNote() ) + { + nReal = AdjustNeighbourhood( nGrow, bTst ); + nAdjust = SwNeighbourAdjust::GrowShrink; // no more AdjustNeighbourhood + } + } + } + nReal += pBoss->Grow( nGrow - nReal, bTst ); + if( ( SwNeighbourAdjust::GrowAdjust == nAdjust || SwNeighbourAdjust::AdjustGrow == nAdjust ) + && nReal < nGrow ) + nReal += AdjustNeighbourhood( nGrow - nReal, bTst ); + } + } + + nReal += nAvail; + + if ( !bTst ) + { + if ( nReal != nDist ) + { + nDist -= nReal; + + // We can only respect the boundless wish so much + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.AddHeight( -nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nDist ); + } + } + + // growing happens upwards, so successors to not need to be invalidated + if( nReal ) + { + InvalidateSize_(); + InvalidatePos_(); + InvalidatePage( pPage ); + } + } + return nReal; +} + +SwTwips SwFootnoteContFrame::ShrinkFrame( SwTwips nDiff, bool bTst, bool bInfo ) +{ + SwPageFrame *pPage = FindPageFrame(); + bool bShrink = false; + if ( pPage ) + { + if( !pPage->IsFootnotePage() ) + bShrink = true; + else + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + bShrink = true; + } + } + if( bShrink ) + { + SwTwips nRet = SwLayoutFrame::ShrinkFrame( nDiff, bTst, bInfo ); + if( IsInSct() && !bTst ) + FindSctFrame()->InvalidateNextPos(); + if ( !bTst && nRet ) + { + InvalidatePos_(); + InvalidatePage( pPage ); + } + return nRet; + } + return 0; +} + +SwFootnoteFrame::SwFootnoteFrame( SwFrameFormat *pFormat, SwFrame* pSib, SwContentFrame *pCnt, SwTextFootnote *pAt ): + SwLayoutFrame( pFormat, pSib ), + mpFollow( nullptr ), + mpMaster( nullptr ), + mpReference( pCnt ), + mpAttribute( pAt ), + mbBackMoveLocked( false ), + // #i49383# + mbUnlockPosOfLowerObjs( true ) +{ + mnFrameType = SwFrameType::Ftn; +} + +void SwFootnoteFrame::InvalidateNxtFootnoteCnts( SwPageFrame const *pPage ) +{ + if ( GetNext() ) + { + SwFrame *pCnt = static_cast<SwLayoutFrame*>(GetNext())->ContainsAny(); + if( pCnt ) + { + pCnt->InvalidatePage( pPage ); + pCnt->InvalidatePrt_(); + do + { pCnt->InvalidatePos_(); + if( pCnt->IsSctFrame() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePos_(); + } + pCnt->GetUpper()->InvalidateSize_(); + pCnt = pCnt->FindNext(); + } while ( pCnt && GetUpper()->IsAnLower( pCnt ) ); + } + } +} + +bool SwFootnoteFrame::IsDeleteForbidden() const +{ + if (SwLayoutFrame::IsDeleteForbidden()) + return true; + // needs to be in sync with the ::Cut logic + const SwLayoutFrame *pUp = GetUpper(); + if (pUp) + { + if (GetPrev()) + return false; + + // The last footnote takes its container along if it + // is deleted. Cut would put pUp->Lower() to the value + // of GetNext(), so if there is no GetNext then + // Cut would delete pUp. If that condition is true + // here then check if the container is delete-forbidden + return !GetNext() && pUp->IsDeleteForbidden(); + } + return false; +} + +void SwFootnoteFrame::Cut() +{ + if ( GetNext() ) + GetNext()->InvalidatePos(); + else if ( GetPrev() ) + GetPrev()->SetRetouche(); + + // first move then shrink Upper + SwLayoutFrame *pUp = GetUpper(); + + // correct chaining + SwFootnoteFrame *pFootnote = this; + if ( pFootnote->GetFollow() ) + pFootnote->GetFollow()->SetMaster( pFootnote->GetMaster() ); + if ( pFootnote->GetMaster() ) + pFootnote->GetMaster()->SetFollow( pFootnote->GetFollow() ); + pFootnote->SetFollow( nullptr ); + pFootnote->SetMaster( nullptr ); + + // cut all connections + RemoveFromLayout(); + + if ( pUp ) + { + // The last footnote takes its container along + if (!pUp->Lower()) + { + SwPageFrame *pPage = pUp->FindPageFrame(); + if ( pPage ) + { + SwLayoutFrame *pBody = pPage->FindBodyCont(); + if( pBody && !pBody->ContainsContent() ) + pPage->getRootFrame()->SetSuperfluous(); + } + SwSectionFrame* pSect = pUp->FindSctFrame(); + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + // If the last footnote container was removed from a column + // section without a Follow, then this section can be shrunk. + if( pSect && !pSect->ToMaximize( false ) && !pSect->IsColLocked() ) + pSect->InvalidateSize_(); + } + else + { if ( getFrameArea().Height() ) + pUp->Shrink( getFrameArea().Height() ); + pUp->SetCompletePaint(); + pUp->InvalidatePage(); + } + } +} + +void SwFootnoteFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "no parent in Paste." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I am my own parent." ); + OSL_ENSURE( pSibling != this, "I am my own sibling." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I am still somewhere registered." ); + + // insert into tree structure + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.GetWidth(getFrameArea())!=aRectFnSet.GetWidth(pParent->getFramePrintArea()) ) + InvalidateSize_(); + InvalidatePos_(); + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if ( GetNext() ) + GetNext()->InvalidatePos_(); + if( aRectFnSet.GetHeight(getFrameArea()) ) + pParent->Grow( aRectFnSet.GetHeight(getFrameArea()) ); + + // If the predecessor is the master and/or the successor is the Follow, + // then take their content and destroy them. + if ( GetPrev() && GetPrev() == GetMaster() ) + { + OSL_ENSURE( SwFlowFrame::CastFlowFrame( GetPrev()->GetLower() ), + "Footnote without content?" ); + SwFlowFrame::CastFlowFrame( GetPrev()->GetLower())-> + MoveSubTree( this, GetLower() ); + SwFrame *pDel = GetPrev(); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + if ( GetNext() && GetNext() == GetFollow() ) + { + OSL_ENSURE( SwFlowFrame::CastFlowFrame( GetNext()->GetLower() ), + "Footnote without content?" ); + SwFlowFrame::CastFlowFrame( GetNext()->GetLower() )->MoveSubTree( this ); + SwFrame *pDel = GetNext(); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } +#if OSL_DEBUG_LEVEL > 0 + SwDoc *pDoc = GetFormat()->GetDoc(); + if ( GetPrev() ) + { + OSL_ENSURE( lcl_FindFootnotePos( pDoc, static_cast<SwFootnoteFrame*>(GetPrev())->GetAttr() ) <= + lcl_FindFootnotePos( pDoc, GetAttr() ), "Prev is not FootnotePrev" ); + } + if ( GetNext() ) + { + OSL_ENSURE( lcl_FindFootnotePos( pDoc, GetAttr() ) <= + lcl_FindFootnotePos( pDoc, static_cast<SwFootnoteFrame*>(GetNext())->GetAttr() ), + "Next is not FootnoteNext" ); + } +#endif + InvalidateNxtFootnoteCnts( pPage ); +} + +/// Return the next layout leaf in that the frame can be moved. +/// New pages will only be created if specified by the parameter. +SwLayoutFrame *SwFrame::GetNextFootnoteLeaf( MakePageType eMakePage ) +{ + SwFootnoteBossFrame *pOldBoss = FindFootnoteBossFrame(); + SwPageFrame* pOldPage = pOldBoss->FindPageFrame(); + SwPageFrame* pPage; + SwFootnoteBossFrame *pBoss = pOldBoss->IsColumnFrame() ? + static_cast<SwFootnoteBossFrame*>(pOldBoss->GetNext()) : nullptr; // next column, if existing + if( pBoss ) + pPage = nullptr; + else + { + if( pOldBoss->GetUpper()->IsSctFrame() ) + { // this can only be in a column area + SwLayoutFrame* pNxt = pOldBoss->GetNextSctLeaf( eMakePage ); + if( pNxt ) + { + OSL_ENSURE( pNxt->IsColBodyFrame(), "GetNextFootnoteLeaf: Funny Leaf" ); + pBoss = static_cast<SwFootnoteBossFrame*>(pNxt->GetUpper()); + pPage = pBoss->FindPageFrame(); + } + else + return nullptr; + } + else + { + // next page + pPage = static_cast<SwPageFrame*>(pOldPage->GetNext()); + // skip empty pages + if( pPage && pPage->IsEmptyPage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pBoss = pPage; + } + } + // What do we have until here? + // pBoss != NULL, pPage==NULL => pBoss is the next column on the same page + // pBoss != NULL, pPage!=NULL => pBoss and pPage are the following page (empty pages skipped) + // pBoss == NULL => pPage == NULL, so there are no following pages + + // If the footnote has already a Follow we do not need to search. + // However, if there are unwanted empty columns/pages between Footnote and Follow, + // create another Follow on the next best column/page and the rest will sort itself out. + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + if ( pFootnote && pFootnote->GetFollow() ) + { + SwFootnoteBossFrame* pTmpBoss = pFootnote->GetFollow()->FindFootnoteBossFrame(); + // Following cases will be handled: + // 1. both "FootnoteBoss"es are neighboring columns/pages + // 2. the new one is the first column of a neighboring page + // 3. the new one is the first column in a section of the next page + while( pTmpBoss != pBoss && pTmpBoss && !pTmpBoss->GetPrev() ) + pTmpBoss = pTmpBoss->GetUpper()->FindFootnoteBossFrame(); + if( pTmpBoss == pBoss ) + return pFootnote->GetFollow(); + } + + // If no pBoss could be found or it is a "wrong" page, we need a new page. + if ( !pBoss || ( pPage && pPage->IsEndNotePage() && !pOldPage->IsEndNotePage() ) ) + { + if ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) + { + pBoss = InsertPage( pOldPage, pOldPage->IsFootnotePage() ); + static_cast<SwPageFrame*>(pBoss)->SetEndNotePage( pOldPage->IsEndNotePage() ); + } + else + return nullptr; + } + if( pBoss->IsPageFrame() ) + { + // If this page has columns, then go to the first one + SwLayoutFrame* pLay = pBoss->FindBodyCont(); + if( pLay && pLay->Lower() && pLay->Lower()->IsColumnFrame() ) + pBoss = static_cast<SwFootnoteBossFrame*>(pLay->Lower()); + } + // found column/page - add myself + SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont(); + if ( !pCont && pBoss->GetMaxFootnoteHeight() && + ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) ) + pCont = pBoss->MakeFootnoteCont(); + return pCont; +} + +/// Get the preceding layout leaf in that the frame can be moved. +SwLayoutFrame *SwFrame::GetPrevFootnoteLeaf( MakePageType eMakeFootnote ) +{ + // The predecessor of a footnote is (if possible) + // the master of the chain of the footnote. + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + SwLayoutFrame *pRet = pFootnote->GetMaster(); + + SwFootnoteBossFrame* pOldBoss = FindFootnoteBossFrame(); + SwPageFrame *pOldPage = pOldBoss->FindPageFrame(); + + if ( !pOldBoss->GetPrev() && !pOldPage->GetPrev() ) + return pRet; // there is neither a predecessor column nor page + + if ( !pRet ) + { + bool bEndn = pFootnote->GetAttr()->GetFootnote().IsEndNote(); + SwFrame* pTmpRef = nullptr; + const IDocumentSettingAccess& rSettings + = pFootnote->GetAttrSet()->GetDoc()->getIDocumentSettingAccess(); + if( bEndn && pFootnote->IsInSct() ) + { + SwSectionFrame* pSect = pFootnote->FindSctFrame(); + if( pSect->IsEndnAtEnd() ) + // Endnotes at the end of the section. + pTmpRef = pSect->FindLastContent( SwFindMode::LastCnt ); + } + else if (bEndn && rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) + { + // Endnotes at the end of the document. + SwPageFrame* pPage = getRootFrame()->GetLastPage(); + assert(pPage); + SwFrame* pPrevPage = pPage->GetPrev(); + if (pPrevPage) + { + // Have a last but one page, use that since we try to get a preceding frame. + assert(pPrevPage->IsPageFrame()); + pPage = static_cast<SwPageFrame*>(pPrevPage); + } + pTmpRef = pPage->FindLastBodyContent(); + } + if( !pTmpRef ) + // Endnotes on a separate page. + pTmpRef = pFootnote->GetRef(); + SwFootnoteBossFrame* pStop = pTmpRef->FindFootnoteBossFrame( !bEndn ); + + const sal_uInt16 nNum = pStop->GetPhyPageNum(); + + // Do not leave the corresponding page if the footnote should + // be shown at the document ending or the footnote is an endnote. + const bool bEndNote = pOldPage->IsEndNotePage(); + const bool bFootnoteEndDoc = pOldPage->IsFootnotePage(); + SwFootnoteBossFrame* pNxtBoss = pOldBoss; + SwSectionFrame *pSect = pNxtBoss->GetUpper()->IsSctFrame() ? + static_cast<SwSectionFrame*>(pNxtBoss->GetUpper()) : nullptr; + + do + { + if( pNxtBoss->IsColumnFrame() && pNxtBoss->GetPrev() ) + pNxtBoss = static_cast<SwFootnoteBossFrame*>(pNxtBoss->GetPrev()); // one column backwards + else // one page backwards + { + SwLayoutFrame* pBody = nullptr; + if( pSect ) + { + if( pSect->IsFootnoteLock() ) + { + if( pNxtBoss == pOldBoss ) + return nullptr; + pStop = pNxtBoss; + } + else + { + pSect = pSect->FindMaster(); + if( !pSect || !pSect->Lower() ) + return nullptr; + OSL_ENSURE( pSect->Lower()->IsColumnFrame(), + "GetPrevFootnoteLeaf: Where's the column?" ); + pNxtBoss = static_cast<SwFootnoteBossFrame*>(pSect->Lower()); + pBody = pSect; + } + } + else + { + SwPageFrame* pPage = static_cast<SwPageFrame*>(pNxtBoss->FindPageFrame()->GetPrev()); + if( !pPage || pPage->GetPhyPageNum() < nNum || + bEndNote != pPage->IsEndNotePage() || bFootnoteEndDoc != pPage->IsFootnotePage() ) + return nullptr; // no further pages found + pNxtBoss = pPage; + pBody = pPage->FindBodyCont(); + } + // We have the previous page, we might need to find the last column of it + if( pBody ) + { + if ( pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + pNxtBoss = static_cast<SwFootnoteBossFrame*>(pBody->GetLastLower()); + } + } + } + SwFootnoteContFrame *pCont = pNxtBoss->FindFootnoteCont(); + if ( pCont ) + { + pRet = pCont; + break; + } + if ( pStop == pNxtBoss ) + { + // Reached the column/page of the reference. + // Try to add a container and paste our content. + if ( eMakeFootnote == MAKEPAGE_FTN && pNxtBoss->GetMaxFootnoteHeight() ) + pRet = pNxtBoss->MakeFootnoteCont(); + break; + } + } while( !pRet ); + } + if ( pRet ) + { + const SwFootnoteBossFrame* pNewBoss = pRet->FindFootnoteBossFrame(); + bool bJump = false; + if( pOldBoss->IsColumnFrame() && pOldBoss->GetPrev() ) // a previous column exists + bJump = pOldBoss->GetPrev() != static_cast<SwFrame const *>(pNewBoss); // did we chose it? + else if( pNewBoss->IsColumnFrame() && pNewBoss->GetNext() ) + bJump = true; // there is another column after the boss (not the old boss) + else + { + // Will be reached only if old and new boss are both either pages or the last (new) + // or first (old) column of a page. In this case, check if pages were skipped. + const sal_uInt16 nDiff = pOldPage->GetPhyPageNum() - pRet->FindPageFrame()->GetPhyPageNum(); + if ( nDiff > 2 || + (nDiff > 1 && !static_cast<SwPageFrame*>(pOldPage->GetPrev())->IsEmptyPage()) ) + bJump = true; + } + if( bJump ) + SwFlowFrame::SetMoveBwdJump( true ); + } + return pRet; +} + +bool SwFrame::IsFootnoteAllowed() const +{ + if ( !IsInDocBody() ) + return false; + + if ( IsInTab() ) + { + // no footnotes in repeated headlines + const SwTabFrame *pTab = const_cast<SwFrame*>(this)->ImplFindTabFrame(); + assert(pTab); + if ( pTab->IsFollow() ) + return !pTab->IsInHeadline( *this ); + } + return true; +} + +void SwRootFrame::UpdateFootnoteNums() +{ + // page numbering only if set at the document + if ( GetFormat()->GetDoc()->GetFootnoteInfo().m_eNum == FTNNUM_PAGE ) + { + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && !pPage->IsFootnotePage() ) + { + pPage->UpdateFootnoteNum(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + } +} + +/// remove all footnotes (not the references) and all footnote pages +void sw_RemoveFootnotes( SwFootnoteBossFrame* pBoss, bool bPageOnly, bool bEndNotes ) +{ + do + { + SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont(); + if ( pCont ) + { + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + assert(pFootnote); + if ( bPageOnly ) + while ( pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + do + { + SwFootnoteFrame *pNxt = static_cast<SwFootnoteFrame*>(pFootnote->GetNext()); + if ( !pFootnote->GetAttr()->GetFootnote().IsEndNote() || + bEndNotes ) + { + pFootnote->GetRef()->Prepare( PrepareHint::FootnoteInvalidation, static_cast<void*>(pFootnote->GetAttr()) ); + if ( bPageOnly && !pNxt ) + pNxt = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + } + pFootnote = pNxt; + + } while ( pFootnote ); + } + if( !pBoss->IsInSct() ) + { + // A sectionframe with the Footnote/EndnAtEnd-flags may contain + // foot/endnotes. If the last lower frame of the bodyframe is + // a multicolumned sectionframe, it may contain footnotes, too. + SwLayoutFrame* pBody = pBoss->FindBodyCont(); + if( pBody && pBody->Lower() ) + { + SwFrame* pLow = pBody->Lower(); + while (pLow) + { + if( pLow->IsSctFrame() && ( !pLow->GetNext() || + static_cast<SwSectionFrame*>(pLow)->IsAnyNoteAtEnd() ) && + static_cast<SwSectionFrame*>(pLow)->Lower() && + static_cast<SwSectionFrame*>(pLow)->Lower()->IsColumnFrame() ) + sw_RemoveFootnotes( static_cast<SwColumnFrame*>(static_cast<SwSectionFrame*>(pLow)->Lower()), + bPageOnly, bEndNotes ); + pLow = pLow->GetNext(); + } + } + } + // is there another column? + pBoss = pBoss->IsColumnFrame() ? static_cast<SwColumnFrame*>(pBoss->GetNext()) : nullptr; + } while( pBoss ); +} + +void SwRootFrame::RemoveFootnotes( SwPageFrame *pPage, bool bPageOnly, bool bEndNotes ) +{ + if ( !pPage ) + pPage = static_cast<SwPageFrame*>(Lower()); + + do + { // On columned pages we have to clean up in all columns + SwFootnoteBossFrame* pBoss; + SwLayoutFrame* pBody = pPage->FindBodyCont(); + if( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + pBoss = static_cast<SwFootnoteBossFrame*>(pBody->Lower()); // the first column + else + pBoss = pPage; // no columns + sw_RemoveFootnotes( pBoss, bPageOnly, bEndNotes ); + if ( !bPageOnly ) + { + if ( pPage->IsFootnotePage() && + (!pPage->IsEndNotePage() || bEndNotes) ) + { + SwFrame *pDel = pPage; + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + else + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + else + break; + + } while ( pPage ); +} + +/// Change the page template of the footnote pages +void SwRootFrame::CheckFootnotePageDescs( bool bEndNote ) +{ + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && !pPage->IsFootnotePage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + while ( pPage && pPage->IsEndNotePage() != bEndNote ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + if ( pPage ) + SwFrame::CheckPageDescs( pPage, false ); +} + +/** Insert a footnote container + * + * A footnote container is always placed directly behind the body text. + * + * The frame format (FrameFormat) is always the default frame format. + * + * @return footnote container frame + */ +SwFootnoteContFrame *SwFootnoteBossFrame::MakeFootnoteCont() +{ + SAL_WARN_IF(FindFootnoteCont(), "sw.core", "footnote container exists already"); + + SwFootnoteContFrame *pNew = new SwFootnoteContFrame( GetFormat()->GetDoc()->GetDfltFrameFormat(), this ); + SwLayoutFrame *pLay = FindBodyCont(); + pNew->Paste( this, pLay->GetNext() ); + return pNew; +} + +SwFootnoteContFrame *SwFootnoteBossFrame::FindFootnoteCont() +{ + SwFrame *pFrame = Lower(); + while( pFrame && !pFrame->IsFootnoteContFrame() ) + pFrame = pFrame->GetNext(); + +#if OSL_DEBUG_LEVEL > 0 + if ( pFrame ) + { + SwFrame *pFootnote = pFrame->GetLower(); + assert(pFootnote); + while ( pFootnote ) + { + assert(pFootnote->IsFootnoteFrame() && "Neighbor of footnote must be a footnote"); + pFootnote = pFootnote->GetNext(); + } + } +#endif + + return static_cast<SwFootnoteContFrame*>(pFrame); +} + +/// Search the next available footnote container. +SwFootnoteContFrame *SwFootnoteBossFrame::FindNearestFootnoteCont( bool bDontLeave ) +{ + SwFootnoteContFrame *pCont = nullptr; + if ( !GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + { + pCont = FindFootnoteCont(); + if ( !pCont ) + { + SwPageFrame *pPage = FindPageFrame(); + SwFootnoteBossFrame* pBoss = this; + bool bEndNote = pPage->IsEndNotePage(); + do + { + bool bChgPage = lcl_NextFootnoteBoss( pBoss, pPage, bDontLeave ); + // Found another boss? When changing pages, also the endnote flag must match. + if( pBoss && ( !bChgPage || pPage->IsEndNotePage() == bEndNote ) ) + pCont = pBoss->FindFootnoteCont(); + } while ( !pCont && pPage ); + } + } + return pCont; +} + +SwFootnoteFrame *SwFootnoteBossFrame::FindFirstFootnote() +{ + // search for the nearest footnote container + SwFootnoteContFrame *pCont = FindNearestFootnoteCont(); + if ( !pCont ) + return nullptr; + + // Starting from the first footnote, search the first + // footnote that is referenced by the current column/page + + SwFootnoteFrame *pRet = static_cast<SwFootnoteFrame*>(pCont->Lower()); + const sal_uInt16 nRefNum = FindPageFrame()->GetPhyPageNum(); + const sal_uInt16 nRefCol = lcl_ColumnNum( this ); + sal_uInt16 nPgNum, nColNum; // page number, column number + SwFootnoteBossFrame* pBoss; + SwPageFrame* pPage; + if( pRet ) + { + pBoss = pRet->GetRef()->FindFootnoteBossFrame(); + OSL_ENSURE( pBoss, "FindFirstFootnote: No boss found" ); + if( !pBoss ) + return nullptr; // ?There must be a bug, but no GPF + pPage = pBoss->FindPageFrame(); + nPgNum = pPage->GetPhyPageNum(); + if ( nPgNum == nRefNum ) + { + nColNum = lcl_ColumnNum( pBoss ); + if( nColNum == nRefCol ) + return pRet; // found + else if( nColNum > nRefCol ) + return nullptr; // at least one column too far + } + else if ( nPgNum > nRefNum ) + return nullptr; // at least one column too far + } + else + return nullptr; + // Done if Ref is on a subsequent page or on the same page in a subsequent column + + do + { + while ( pRet->GetFollow() ) + pRet = pRet->GetFollow(); + + SwFootnoteFrame *pNxt = static_cast<SwFootnoteFrame*>(pRet->GetNext()); + if ( !pNxt ) + { + pBoss = pRet->FindFootnoteBossFrame(); + pPage = pBoss->FindPageFrame(); + lcl_NextFootnoteBoss( pBoss, pPage, false ); // next FootnoteBoss + pCont = pBoss ? pBoss->FindNearestFootnoteCont() : nullptr; + if ( pCont ) + pNxt = static_cast<SwFootnoteFrame*>(pCont->Lower()); + } + if ( pNxt ) + { + pRet = pNxt; + pBoss = pRet->GetRef()->FindFootnoteBossFrame(); + pPage = pBoss->FindPageFrame(); + nPgNum = pPage->GetPhyPageNum(); + if ( nPgNum == nRefNum ) + { + nColNum = lcl_ColumnNum( pBoss ); + if( nColNum == nRefCol ) + break; // found + else if( nColNum > nRefCol ) + pRet = nullptr; // at least one column too far + } + else if ( nPgNum > nRefNum ) + pRet = nullptr; // at least a page too far + } + else + pRet = nullptr; // there is none + } while( pRet ); + return pRet; +} + +/// Get the first footnote of a given content +const SwFootnoteFrame *SwFootnoteBossFrame::FindFirstFootnote( SwContentFrame const *pCnt ) const +{ + const SwFootnoteFrame *pRet = const_cast<SwFootnoteBossFrame*>(this)->FindFirstFootnote(); + if ( pRet ) + { + const sal_uInt16 nColNum = lcl_ColumnNum( this ); + const sal_uInt16 nPageNum = GetPhyPageNum(); + while ( pRet && (pRet->GetRef() != pCnt) ) + { + while ( pRet->GetFollow() ) + pRet = pRet->GetFollow(); + + if ( pRet->GetNext() ) + pRet = static_cast<const SwFootnoteFrame*>(pRet->GetNext()); + else + { SwFootnoteBossFrame *pBoss = const_cast<SwFootnoteBossFrame*>(pRet->FindFootnoteBossFrame()); + SwPageFrame *pPage = pBoss->FindPageFrame(); + lcl_NextFootnoteBoss( pBoss, pPage, false ); // next FootnoteBoss + SwFootnoteContFrame *pCont = pBoss ? pBoss->FindNearestFootnoteCont() : nullptr; + pRet = pCont ? static_cast<SwFootnoteFrame*>(pCont->Lower()) : nullptr; + } + if ( pRet ) + { + const SwFootnoteBossFrame* pBoss = pRet->GetRef()->FindFootnoteBossFrame(); + if( pBoss->GetPhyPageNum() != nPageNum || + nColNum != lcl_ColumnNum( pBoss ) ) + pRet = nullptr; + } + } + } + return pRet; +} + +void SwFootnoteBossFrame::ResetFootnote( const SwFootnoteFrame *pCheck ) +{ + // Destroy the incarnations of footnotes to an attribute, if they don't + // belong to pAssumed + OSL_ENSURE( !pCheck->GetMaster(), "given master is not a Master." ); + + SwNodeIndex aIdx( *pCheck->GetAttr()->GetStartNode(), 1 ); + SwContentNode *pNd = aIdx.GetNode().GetContentNode(); + if ( !pNd ) + pNd = pCheck->GetFormat()->GetDoc()-> + GetNodes().GoNextSection( &aIdx, true, false ); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd); + SwFrame* pFrame = aIter.First(); + while( pFrame ) + { + if( pFrame->getRootFrame() == pCheck->getRootFrame() ) + { + SwFrame *pTmp = pFrame->GetUpper(); + while ( pTmp && !pTmp->IsFootnoteFrame() ) + pTmp = pTmp->GetUpper(); + + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pTmp); + while ( pFootnote && pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + if ( pFootnote != pCheck ) + { + while (pFootnote && !pFootnote->IsDeleteForbidden()) + { + SwFootnoteFrame *pNxt = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + pFootnote = pNxt; + } + } + } + + pFrame = aIter.Next(); + } +} + +void SwFootnoteBossFrame::InsertFootnote( SwFootnoteFrame* pNew ) +{ + // Place the footnote in front of the footnote whose attribute + // is in front of the new one (get position via the Doc). + // If there is no footnote in this footnote-boss yet, create a new container. + // If there is a container but no footnote for this footnote-boss yet, place + // the footnote behind the last footnote of the closest previous column/page. + + ResetFootnote( pNew ); + SwFootnoteFrame *pSibling = FindFirstFootnote(); + bool bDontLeave = false; + + // Ok, a sibling has been found, but is the sibling in an acceptable + // environment? + if( IsInSct() ) + { + SwSectionFrame* pMySect = ImplFindSctFrame(); + bool bEndnt = pNew->GetAttr()->GetFootnote().IsEndNote(); + if( bEndnt ) + { + const SwSectionFormat* pEndFormat = pMySect->GetEndSectFormat(); + bDontLeave = nullptr != pEndFormat; + if( pSibling ) + { + if( pEndFormat ) + { + if( !pSibling->IsInSct() || + !pSibling->ImplFindSctFrame()->IsDescendantFrom( pEndFormat ) ) + pSibling = nullptr; + } + else if( pSibling->IsInSct() ) + pSibling = nullptr; + } + } + else + { + bDontLeave = pMySect->IsFootnoteAtEnd(); + if( pSibling ) + { + if( pMySect->IsFootnoteAtEnd() ) + { + if( !pSibling->IsInSct() || + !pMySect->IsAnFollow( pSibling->ImplFindSctFrame() ) ) + pSibling = nullptr; + } + else if( pSibling->IsInSct() ) + pSibling = nullptr; + } + } + } + + if( pSibling && pSibling->FindPageFrame()->IsEndNotePage() != + FindPageFrame()->IsEndNotePage() ) + pSibling = nullptr; + + // use the Doc to find out the position + SwDoc *pDoc = GetFormat()->GetDoc(); + const sal_uLong nStPos = ::lcl_FindFootnotePos( pDoc, pNew->GetAttr() ); + + sal_uLong nCmpPos = 0; + sal_uLong nLastPos = 0; + SwFootnoteContFrame *pParent = nullptr; + if( pSibling ) + { + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + if( nCmpPos > nStPos ) + pSibling = nullptr; + } + + if ( !pSibling ) + { pParent = FindFootnoteCont(); + if ( !pParent ) + { + // There is no footnote container yet. Before creating one, keep in mind that + // there might exist another following footnote that must be placed before the + // new inserted one e.g. because it was divided over multiple pages etc. + pParent = FindNearestFootnoteCont( bDontLeave ); + if ( pParent ) + { + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pParent->Lower()); + if ( pFootnote ) + { + + nCmpPos = ::lcl_FindFootnotePos( pDoc, pFootnote->GetAttr() ); + if ( nCmpPos > nStPos ) + pParent = nullptr; + } + else + pParent = nullptr; + } + } + if ( !pParent ) + // here, we are sure that we can create a footnote container + pParent = MakeFootnoteCont(); + else + { + // Based on the first footnote below the Parent, search for the first footnote whose + // index is after the index of the newly inserted, to place the new one correctly + pSibling = static_cast<SwFootnoteFrame*>(pParent->Lower()); + if ( !pSibling ) + { + OSL_ENSURE( false, "Could not find space for footnote."); + return; + } + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + + SwFootnoteBossFrame *pNxtB; // remember the last one to not + SwFootnoteFrame *pLastSib = nullptr; // go too far. + + while ( pSibling && nCmpPos <= nStPos ) + { + pLastSib = pSibling; // potential candidate + nLastPos = nCmpPos; + + while ( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + + if ( pSibling->GetNext() ) + { + pSibling = static_cast<SwFootnoteFrame*>(pSibling->GetNext()); + OSL_ENSURE( !pSibling->GetMaster() || ( ENDNOTE > nStPos && + pSibling->GetAttr()->GetFootnote().IsEndNote() ), + "InsertFootnote: Master expected I" ); + } + else + { + pNxtB = pSibling->FindFootnoteBossFrame(); + SwPageFrame *pSibPage = pNxtB->FindPageFrame(); + bool bEndNote = pSibPage->IsEndNotePage(); + bool bChgPage = lcl_NextFootnoteBoss( pNxtB, pSibPage, bDontLeave ); + // When changing pages, also the endnote flag must match. + SwFootnoteContFrame *pCont = pNxtB && ( !bChgPage || + pSibPage->IsEndNotePage() == bEndNote ) + ? pNxtB->FindNearestFootnoteCont( bDontLeave ) : nullptr; + if( pCont ) + pSibling = static_cast<SwFootnoteFrame*>(pCont->Lower()); + else // no further FootnoteContainer, insert after pSibling + break; + } + if ( pSibling ) + { + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + OSL_ENSURE( nCmpPos > nLastPos, "InsertFootnote: Order of FootnoteFrame's buggy" ); + } + } + // pLastSib is the last footnote before the new one and + // pSibling is empty or the first one after the new one + if ( pSibling && pLastSib && (pSibling != pLastSib) ) + { + // too far? + if ( nCmpPos > nStPos ) + pSibling = pLastSib; + } + else if ( !pSibling ) + { + // Last chance: Take the last footnote of the parent. + // Special case that happens e.g. when moving paragraphs with multiple footnotes. + // To keep the order, use the parent of the last inspected footnote. + pSibling = pLastSib; + while( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + OSL_ENSURE( !pSibling->GetNext(), "InsertFootnote: Who's that guy?" ); + } + } + } + else + { + // First footnote of the column/page found. Now search from there for the first one on the + // same column/page whose index is after the given one. The last one found is the predecessor. + SwFootnoteBossFrame* pBoss = pNew->GetRef()->FindFootnoteBossFrame( + !pNew->GetAttr()->GetFootnote().IsEndNote() ); + sal_uInt16 nRefNum = pBoss->GetPhyPageNum(); // page number of the new footnote + sal_uInt16 nRefCol = lcl_ColumnNum( pBoss ); // column number of the new footnote + bool bEnd = false; + SwFootnoteFrame *pLastSib = nullptr; + while ( pSibling && !bEnd && (nCmpPos <= nStPos) ) + { + pLastSib = pSibling; + nLastPos = nCmpPos; + + while ( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + + SwFootnoteFrame *pFoll = static_cast<SwFootnoteFrame*>(pSibling->GetNext()); + if ( pFoll ) + { + pBoss = pSibling->GetRef()->FindFootnoteBossFrame( !pSibling-> + GetAttr()->GetFootnote().IsEndNote() ); + sal_uInt16 nTmpRef; + if( nStPos >= ENDNOTE || + (nTmpRef = pBoss->GetPhyPageNum()) < nRefNum || + ( nTmpRef == nRefNum && lcl_ColumnNum( pBoss ) <= nRefCol )) + pSibling = pFoll; + else + bEnd = true; + } + else + { + SwFootnoteBossFrame* pNxtB = pSibling->FindFootnoteBossFrame(); + SwPageFrame *pSibPage = pNxtB->FindPageFrame(); + bool bEndNote = pSibPage->IsEndNotePage(); + bool bChgPage = lcl_NextFootnoteBoss( pNxtB, pSibPage, bDontLeave ); + // When changing pages, also the endnote flag must match. + SwFootnoteContFrame *pCont = pNxtB && ( !bChgPage || + pSibPage->IsEndNotePage() == bEndNote ) + ? pNxtB->FindNearestFootnoteCont( bDontLeave ) : nullptr; + if ( pCont ) + pSibling = static_cast<SwFootnoteFrame*>(pCont->Lower()); + else + bEnd = true; + } + if ( !bEnd && pSibling ) + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + if (pSibling && (pSibling != pLastSib)) + { + // too far? + if ( (nLastPos < nCmpPos) && (nCmpPos > nStPos) ) + { + pSibling = pLastSib; + bEnd = true; + } + } + } + } + if ( pSibling ) + { + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + if ( nCmpPos < nStPos ) + { + while ( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + pParent = static_cast<SwFootnoteContFrame*>(pSibling->GetUpper()); + pSibling = static_cast<SwFootnoteFrame*>(pSibling->GetNext()); + } + else + { + if( pSibling->GetMaster() ) + { + if( ENDNOTE > nCmpPos || nStPos >= ENDNOTE ) + { + OSL_FAIL( "InsertFootnote: Master expected II" ); + do + pSibling = pSibling->GetMaster(); + while ( pSibling->GetMaster() ); + } + } + pParent = static_cast<SwFootnoteContFrame*>(pSibling->GetUpper()); + } + } + OSL_ENSURE( pParent, "paste in space?" ); + pNew->Paste( pParent, pSibling ); +} + +static SwPageFrame* lcl_GetApproximateFootnotePage(const bool bEnd, const SwPageFrame* pPage, + const SwDoc *pDoc, const SwTextFootnote *pAttr) +{ + // We can at least search the approximately correct page + // to ensure that we will finish in finite time even if + // hundreds of footnotes exist. + const SwPageFrame *pNxt = static_cast<const SwPageFrame*>(pPage->GetNext()); + const sal_uLong nStPos = ::lcl_FindFootnotePos(pDoc, pAttr); + while (pNxt && (bEnd ? pNxt->IsEndNotePage() : pNxt->IsFootnotePage() && !pNxt->IsEndNotePage())) + { + const SwFootnoteContFrame *pCont = pNxt->FindFootnoteCont(); + if (pCont && pCont->Lower()) + { + OSL_ENSURE( pCont->Lower()->IsFootnoteFrame(), "no footnote in the container" ); + if (nStPos > ::lcl_FindFootnotePos(pDoc, + static_cast<const SwFootnoteFrame*>(pCont->Lower())->GetAttr())) + { + pPage = pNxt; + pNxt = static_cast<const SwPageFrame*>(pPage->GetNext()); + continue; + } + } + break; + } + return const_cast<SwPageFrame*>(pPage); +} + +void SwFootnoteBossFrame::AppendFootnote( SwContentFrame *pRef, SwTextFootnote *pAttr ) +{ + // If the footnote already exists, do nothing. + if ( FindFootnote( pRef, pAttr ) ) + return; + + // If footnotes are inserted at the end of the document, + // we only need to search from the relevant page on. + // If there is none yet, we need to create one. + // If it is an Endnote, we need to search for or create an + // Endnote page. + SwDoc *pDoc = GetFormat()->GetDoc(); + SwFootnoteBossFrame *pBoss = this; + SwPageFrame *pPage = FindPageFrame(); + SwPageFrame *pMyPage = pPage; + bool bChgPage = false; + const bool bEnd = pAttr->GetFootnote().IsEndNote(); + if (bEnd) + { + const IDocumentSettingAccess& rSettings = *pAttr->GetTextNode().getIDocumentSettingAccess(); + if( GetUpper()->IsSctFrame() && + static_cast<SwSectionFrame*>(GetUpper())->IsEndnAtEnd() ) + { + // Endnotes at the end of the section. + SwFrame* pLast = + static_cast<SwSectionFrame*>(GetUpper())->FindLastContent( SwFindMode::EndNote ); + if( pLast ) + { + pBoss = pLast->FindFootnoteBossFrame(); + pPage = pBoss->FindPageFrame(); + } + } + else if (rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) + { + // Endnotes at the end of the document. + pBoss = getRootFrame()->GetLastPage(); + pPage = pBoss->FindPageFrame(); + } + else + { + // Endnotes on a separate page. + while ( pPage->GetNext() && !pPage->IsEndNotePage() ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + bChgPage = true; + } + if ( !pPage->IsEndNotePage() ) + { + SwPageDesc *pDesc = pDoc->GetEndNoteInfo().GetPageDesc( *pDoc ); + pPage = ::InsertNewPage( *pDesc, pPage->GetUpper(), + !pPage->OnRightPage(), false, false, true, nullptr ); + pPage->SetEndNotePage( true ); + bChgPage = true; + } + else + pPage = lcl_GetApproximateFootnotePage(true, pPage, pDoc, pAttr); + } + } + else if( FTNPOS_CHAPTER == pDoc->GetFootnoteInfo().m_ePos && ( !GetUpper()-> + IsSctFrame() || !static_cast<SwSectionFrame*>(GetUpper())->IsFootnoteAtEnd() ) ) + { + while ( pPage->GetNext() && !pPage->IsFootnotePage() && + !static_cast<SwPageFrame*>(pPage->GetNext())->IsEndNotePage() ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + bChgPage = true; + } + + if ( !pPage->IsFootnotePage() ) + { + SwPageDesc *pDesc = pDoc->GetFootnoteInfo().GetPageDesc( *pDoc ); + pPage = ::InsertNewPage( *pDesc, pPage->GetUpper(), + !pPage->OnRightPage(), false, false, true, pPage->GetNext() ); + bChgPage = true; + } + else + pPage = lcl_GetApproximateFootnotePage(false, pPage, pDoc, pAttr); + } + + // For now, create a footnote and the corresponding content frames + if ( !pAttr->GetStartNode() ) + { + OSL_ENSURE( false, "no footnote content." ); + return; + } + + // If there is already a footnote content on the column/page, + // another one cannot be created in a column area. + if( pBoss->IsInSct() && pBoss->IsColumnFrame() && !pPage->IsFootnotePage() ) + { + SwSectionFrame* pSct = pBoss->FindSctFrame(); + if( bEnd ? !pSct->IsEndnAtEnd() : !pSct->IsFootnoteAtEnd() ) + { + SwFootnoteContFrame* pFootnoteCont = pSct->FindFootnoteBossFrame(!bEnd)->FindFootnoteCont(); + if( pFootnoteCont ) + { + SwFootnoteFrame* pTmp = static_cast<SwFootnoteFrame*>(pFootnoteCont->Lower()); + if( bEnd ) + while( pTmp && !pTmp->GetAttr()->GetFootnote().IsEndNote() ) + pTmp = static_cast<SwFootnoteFrame*>(pTmp->GetNext()); + if( pTmp && *pTmp < pAttr ) + return; + } + } + } + + SwFootnoteFrame *pNew = new SwFootnoteFrame( pDoc->GetDfltFrameFormat(), this, pRef, pAttr ); + { + SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 ); + ::InsertCnt_( pNew, pDoc, aIdx.GetIndex() ); + } + // If the page was changed or newly created, + // we need to place ourselves in the first column + if( bChgPage ) + { + SwLayoutFrame* pBody = pPage->FindBodyCont(); + OSL_ENSURE( pBody, "AppendFootnote: NoPageBody?" ); + if( pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + pBoss = static_cast<SwFootnoteBossFrame*>(pBody->Lower()); + else + pBoss = pPage; // page if no columns exist + } + pBoss->InsertFootnote( pNew ); + if ( pNew->GetUpper() ) // inserted or not? + { + ::RegistFlys( pNew->FindPageFrame(), pNew ); + SwSectionFrame* pSect = FindSctFrame(); + // The content of a FootnoteContainer in a (column) section only need to be calculated + // if the section stretches already to the bottom edge of the Upper. + if( pSect && !pSect->IsJoinLocked() && ( bEnd ? !pSect->IsEndnAtEnd() : + !pSect->IsFootnoteAtEnd() ) && pSect->Growable() ) + pSect->InvalidateSize(); + else + { + // #i49383# - disable unlock of position of + // lower objects during format of footnote content. + const bool bOldFootnoteFrameLocked( pNew->IsColLocked() ); + pNew->ColLock(); + pNew->KeepLockPosOfLowerObjs(); + // #i57914# - adjust fix #i49383# + SwContentFrame *pCnt = pNew->ContainsContent(); + while ( pCnt && pCnt->FindFootnoteFrame()->GetAttr() == pAttr ) + { + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pNew->ContainsContent(); + continue; + } + } + pCnt = pCnt->FindNextCnt(); + } + // #i49383# + if ( !bOldFootnoteFrameLocked ) + { + pNew->ColUnlock(); + } + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pNew->UnlockPosOfLowerObjs(); + pNew->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i57914# - adjust fix #i49383# + if ( !bOldFootnoteFrameLocked && !pNew->GetLower() && + !pNew->IsColLocked() && !pNew->IsBackMoveLocked() && + !pNew->IsDeleteForbidden() ) + { + pNew->Cut(); + SwFrame::DestroyFrame(pNew); + } + } + pMyPage->UpdateFootnoteNum(); + } + else + SwFrame::DestroyFrame(pNew); +} + +SwFootnoteFrame *SwFootnoteBossFrame::FindFootnote( const SwContentFrame *pRef, const SwTextFootnote *pAttr ) +{ + // the easiest and savest way goes via the attribute + OSL_ENSURE( pAttr->GetStartNode(), "FootnoteAtr without StartNode." ); + SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 ); + SwContentNode *pNd = aIdx.GetNode().GetContentNode(); + if ( !pNd ) + pNd = pRef->GetAttrSet()->GetDoc()-> + GetNodes().GoNextSection( &aIdx, true, false ); + if ( !pNd ) + return nullptr; + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd); + SwFrame* pFrame = aIter.First(); + if( pFrame ) + do + { + pFrame = pFrame->GetUpper(); + // #i28500#, #i27243# Due to the endnode collector, there are + // SwFootnoteFrames, which are not in the layout. Therefore the + // bInfFootnote flags are not set correctly, and a cell of FindFootnoteFrame + // would return 0. Therefore we better call ImplFindFootnoteFrame(). + SwFootnoteFrame *pFootnote = pFrame->ImplFindFootnoteFrame(); + if ( pFootnote && pFootnote->GetRef() == pRef ) + { + // The following condition becomes true, if the whole + // footnotecontent is a section. While no frames exist, + // the HiddenFlag of the section is set, this causes + // the GoNextSection-function leaves the footnote. + if( pFootnote->GetAttr() != pAttr ) + return nullptr; + while ( pFootnote && pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + return pFootnote; + } + + } while ( nullptr != (pFrame = aIter.Next()) ); + + return nullptr; +} + +bool SwFootnoteBossFrame::RemoveFootnote( + const SwContentFrame *const pRef, const SwTextFootnote *const pAttr, + bool bPrep ) +{ + bool ret(false); + SwFootnoteFrame *pFootnote = FindFootnote( pRef, pAttr ); + if( pFootnote ) + { + ret = true; + do + { + SwFootnoteFrame *pFoll = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + pFootnote = pFoll; + } while ( pFootnote ); + if( bPrep && pRef->IsFollow() ) + { + OSL_ENSURE( pRef->IsTextFrame(), "NoTextFrame has Footnote?" ); + SwTextFrame* pMaster = pRef->FindMaster(); + if( !pMaster->IsLocked() ) + pMaster->Prepare( PrepareHint::FootnoteInvalidationGone ); + } + } + FindPageFrame()->UpdateFootnoteNum(); + return ret; +} + +void SwFootnoteBossFrame::ChangeFootnoteRef( const SwContentFrame *pOld, const SwTextFootnote *pAttr, + SwContentFrame *pNew ) +{ + SwFootnoteFrame *pFootnote = FindFootnote( pOld, pAttr ); + while ( pFootnote ) + { + pFootnote->SetRef( pNew ); + pFootnote = pFootnote->GetFollow(); + } +} + +/// OD 03.04.2003 #108446# - add parameter <_bCollectOnlyPreviousFootnotes> in +/// order to control, if only footnotes, which are positioned before the +/// footnote boss frame <this> have to be collected. +void SwFootnoteBossFrame::CollectFootnotes( const SwContentFrame* _pRef, + SwFootnoteBossFrame* _pOld, + SwFootnoteFrames& _rFootnoteArr, + const bool _bCollectOnlyPreviousFootnotes ) +{ + SwFootnoteFrame *pFootnote = _pOld->FindFirstFootnote(); + while( !pFootnote ) + { + if( _pOld->IsColumnFrame() ) + { + // visit columns + while ( !pFootnote && _pOld->GetPrev() ) + { + // Still no problem if no footnote was found yet. The loop is needed to pick up + // following rows in tables. In all other cases it might correct bad contexts. + _pOld = static_cast<SwFootnoteBossFrame*>(_pOld->GetPrev()); + pFootnote = _pOld->FindFirstFootnote(); + } + } + if( !pFootnote ) + { + // previous page + SwPageFrame* pPg; + for ( SwFrame* pTmp = _pOld; + nullptr != ( pPg = static_cast<SwPageFrame*>(pTmp->FindPageFrame()->GetPrev())) + && pPg->IsEmptyPage() ; + ) + { + pTmp = pPg; + } + if( !pPg ) + return; + + SwLayoutFrame* pBody = pPg->FindBodyCont(); + if( pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + // multiple columns on one page => search last column + _pOld = static_cast<SwFootnoteBossFrame*>(pBody->GetLastLower()); + } + else + _pOld = pPg; // single column page + pFootnote = _pOld->FindFirstFootnote(); + } + } + + CollectFootnotes_(_pRef, pFootnote, _rFootnoteArr, _bCollectOnlyPreviousFootnotes ? this : nullptr); +} + +static void FootnoteInArr( SwFootnoteFrames& rFootnoteArr, SwFootnoteFrame* pFootnote ) +{ + if ( rFootnoteArr.end() == std::find( rFootnoteArr.begin(), rFootnoteArr.end(), pFootnote ) ) + rFootnoteArr.push_back( pFootnote ); +} + +void SwFootnoteBossFrame::CollectFootnotes_( const SwContentFrame* _pRef, + SwFootnoteFrame* _pFootnote, + SwFootnoteFrames& _rFootnoteArr, + const SwFootnoteBossFrame* _pRefFootnoteBossFrame) +{ + // Collect all footnotes referenced by pRef (attribute by attribute), combine them + // (the content might be divided over multiple pages) and cut them. + + // For robustness, we do not log the corresponding footnotes here. If a footnote + // is touched twice, there might be a crash. This allows this function here to + // also handle corrupt layouts in some degrees (without loops or even crashes). + SwFootnoteFrames aNotFootnoteArr; + + // here we have a footnote placed in front of the first one of the reference + OSL_ENSURE( !_pFootnote->GetMaster() || _pFootnote->GetRef() != _pRef, "move FollowFootnote?" ); + while ( _pFootnote->GetMaster() ) + _pFootnote = _pFootnote->GetMaster(); + + bool bFound = false; + + do + { + // Search for the next footnote in this column/page so that + // we do not start from zero again after cutting one footnote. + SwFootnoteFrame *pNxtFootnote = _pFootnote; + while ( pNxtFootnote->GetFollow() ) + pNxtFootnote = pNxtFootnote->GetFollow(); + pNxtFootnote = static_cast<SwFootnoteFrame*>(pNxtFootnote->GetNext()); + + if ( !pNxtFootnote ) + { + SwFootnoteBossFrame* pBoss = _pFootnote->FindFootnoteBossFrame(); + SwPageFrame* pPage = pBoss->FindPageFrame(); + do + { + lcl_NextFootnoteBoss( pBoss, pPage, false ); + if( pBoss ) + { + SwLayoutFrame* pCont = pBoss->FindFootnoteCont(); + if( pCont ) + { + pNxtFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + if( pNxtFootnote ) + { + while( pNxtFootnote->GetMaster() ) + pNxtFootnote = pNxtFootnote->GetMaster(); + if( pNxtFootnote == _pFootnote ) + pNxtFootnote = nullptr; + } + } + } + } while( !pNxtFootnote && pBoss ); + } + else if( !pNxtFootnote->GetAttr()->GetFootnote().IsEndNote() ) + { + OSL_ENSURE( !pNxtFootnote->GetMaster(), "_CollectFootnote: Master expected" ); + while ( pNxtFootnote->GetMaster() ) + pNxtFootnote = pNxtFootnote->GetMaster(); + } + if ( pNxtFootnote == _pFootnote ) + { + OSL_FAIL( "_CollectFootnote: Vicious circle" ); + pNxtFootnote = nullptr; + } + + // OD 03.04.2003 #108446# - determine, if found footnote has to be collected. + bool bCollectFoundFootnote = false; + // Ignore endnotes which are on a separate endnote page. + bool bEndNote = _pFootnote->GetAttr()->GetFootnote().IsEndNote(); + const IDocumentSettingAccess& rSettings + = _pFootnote->GetAttrSet()->GetDoc()->getIDocumentSettingAccess(); + bool bContinuousEndnotes = rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES); + if (_pFootnote->GetRef() == _pRef && (!bEndNote || bContinuousEndnotes)) + { + if (_pRefFootnoteBossFrame) + { + SwFootnoteBossFrame* pBossOfFoundFootnote = _pFootnote->FindFootnoteBossFrame( true ); + OSL_ENSURE( pBossOfFoundFootnote, + "<SwFootnoteBossFrame::CollectFootnotes_(..)> - footnote boss frame of found footnote frame missing.\nWrong layout!" ); + if ( !pBossOfFoundFootnote || // don't crash, if no footnote boss is found. + pBossOfFoundFootnote->IsBefore( _pRefFootnoteBossFrame ) + ) + { + bCollectFoundFootnote = true; + } + } + else + { + bCollectFoundFootnote = true; + } + } + + if ( bCollectFoundFootnote ) + { + OSL_ENSURE( !_pFootnote->GetMaster(), "move FollowFootnote?" ); + SwFootnoteFrame *pNxt = _pFootnote->GetFollow(); + while ( pNxt ) + { + SwFrame *pCnt = pNxt->ContainsAny(); + if ( pCnt ) + { + // destroy the follow on the way as it is empty + do + { SwFrame *pNxtCnt = pCnt->GetNext(); + pCnt->Cut(); + pCnt->Paste( _pFootnote ); + pCnt = pNxtCnt; + } while ( pCnt ); + } + else + { + OSL_ENSURE( !pNxt, "footnote without content?" ); + pNxt->Cut(); + SwFrame::DestroyFrame(pNxt); + } + pNxt = _pFootnote->GetFollow(); + } + _pFootnote->Cut(); + FootnoteInArr( _rFootnoteArr, _pFootnote ); + bFound = true; + } + else + { + FootnoteInArr( aNotFootnoteArr, _pFootnote ); + if( bFound ) + break; + } + if ( pNxtFootnote && + _rFootnoteArr.end() == std::find( _rFootnoteArr.begin(), _rFootnoteArr.end(), pNxtFootnote ) && + aNotFootnoteArr.end() == std::find( aNotFootnoteArr.begin(), aNotFootnoteArr.end(), pNxtFootnote ) ) + _pFootnote = pNxtFootnote; + else + break; + } + while ( _pFootnote ); +} + +void SwFootnoteBossFrame::MoveFootnotes_( SwFootnoteFrames &rFootnoteArr, bool bCalc ) +{ + // All footnotes referenced by pRef need to be moved + // to a new position (based on the new column/page) + const sal_uInt16 nMyNum = FindPageFrame()->GetPhyPageNum(); + const sal_uInt16 nMyCol = lcl_ColumnNum( this ); + SwRectFnSet aRectFnSet(this); + + // #i21478# - keep last inserted footnote in order to + // format the content of the following one. + SwFootnoteFrame* pLastInsertedFootnote = nullptr; + for (SwFootnoteFrame* pFootnote : rFootnoteArr) + { + SwFootnoteBossFrame* pRefBoss(pFootnote->GetRef()->FindFootnoteBossFrame( + !pFootnote->GetAttr()->GetFootnote().IsEndNote())); + if( pRefBoss != this ) + { + const sal_uInt16 nRefNum = pRefBoss->FindPageFrame()->GetPhyPageNum(); + const sal_uInt16 nRefCol = lcl_ColumnNum( this ); + if( nRefNum < nMyNum || ( nRefNum == nMyNum && nRefCol <= nMyCol ) ) + pRefBoss = this; + } + pRefBoss->InsertFootnote( pFootnote ); + + if ( pFootnote->GetUpper() ) // robust, e.g. with duplicates + { + // First condense the content so that footnote frames that do not fit on the page + // do not do too much harm (Loop 66312). So, the footnote content first grows as + // soon as the content gets formatted and it is sure that it fits on the page. + SwFrame *pCnt = pFootnote->ContainsAny(); + while( pCnt ) + { + if( pCnt->IsLayoutFrame() ) + { + SwFrame* pTmp = static_cast<SwLayoutFrame*>(pCnt)->ContainsAny(); + while( pTmp && static_cast<SwLayoutFrame*>(pCnt)->IsAnLower( pTmp ) ) + { + pTmp->Prepare( PrepareHint::FootnoteMove ); + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pTmp); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); + aRectFnSet.SetHeight(aPrt, 0); + + pTmp = pTmp->FindNext(); + } + } + else + { + pCnt->Prepare( PrepareHint::FootnoteMove ); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCnt); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pCnt); + aRectFnSet.SetHeight(aPrt, 0); + + pCnt = pCnt->GetNext(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFootnote); + aRectFnSet.SetHeight(aFrm, 0); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFootnote); + aRectFnSet.SetHeight(aPrt, 0); + } + + pFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pFootnote->GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + if( bCalc ) + { + SwTextFootnote *pAttr = pFootnote->GetAttr(); + pCnt = pFootnote->ContainsAny(); + bool bUnlock = !pFootnote->IsBackMoveLocked(); + pFootnote->LockBackMove(); + + // #i49383# - disable unlock of position of + // lower objects during format of footnote content. + pFootnote->KeepLockPosOfLowerObjs(); + // #i57914# - adjust fix #i49383# + + while ( pCnt && pCnt->FindFootnoteFrame()->GetAttr() == pAttr ) + { + pCnt->InvalidatePos_(); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pFootnote->ContainsAny(); + continue; + } + } + if( pCnt->IsSctFrame() ) + { + // If the area is not empty, iterate also over the content + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + pCnt = pTmp; + else + pCnt = pCnt->FindNext(); + } + else + pCnt = pCnt->FindNext(); + } + if( bUnlock ) + { + pFootnote->UnlockBackMove(); + if( !pFootnote->ContainsAny() && !pFootnote->IsColLocked() ) + { + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + // #i21478# + pFootnote = nullptr; + } + } + // #i49383# + if ( pFootnote ) + { + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pFootnote->UnlockPosOfLowerObjs(); + pFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + } + } + else + { + OSL_ENSURE( !pFootnote->GetMaster() && !pFootnote->GetFollow(), + "DelFootnote and Master/Follow?" ); + SwFrame::DestroyFrame(pFootnote); + // #i21478# + pFootnote = nullptr; + } + + // #i21478# + if ( pFootnote ) + { + pLastInsertedFootnote = pFootnote; + } + } + + // #i21478# - format content of footnote following + // the new inserted ones. + if ( bCalc && pLastInsertedFootnote ) + { + if ( pLastInsertedFootnote->GetNext() ) + { + SwFootnoteFrame* pNextFootnote = static_cast<SwFootnoteFrame*>(pLastInsertedFootnote->GetNext()); + SwTextFootnote* pAttr = pNextFootnote->GetAttr(); + SwFrame* pCnt = pNextFootnote->ContainsAny(); + + bool bUnlock = !pNextFootnote->IsBackMoveLocked(); + pNextFootnote->LockBackMove(); + // #i49383# - disable unlock of position of + // lower objects during format of footnote content. + pNextFootnote->KeepLockPosOfLowerObjs(); + // #i57914# - adjust fix #i49383# + + while ( pCnt && pCnt->FindFootnoteFrame()->GetAttr() == pAttr ) + { + pCnt->InvalidatePos_(); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pNextFootnote->ContainsAny(); + continue; + } + } + if( pCnt->IsSctFrame() ) + { + // If the area is not empty, iterate also over the content + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + pCnt = pTmp; + else + pCnt = pCnt->FindNext(); + } + else + pCnt = pCnt->FindNext(); + } + if( bUnlock ) + { + pNextFootnote->UnlockBackMove(); + } + // #i49383# + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pNextFootnote->UnlockPosOfLowerObjs(); + pNextFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + } +} + +void SwFootnoteBossFrame::MoveFootnotes( const SwContentFrame *pSrc, SwContentFrame *pDest, + SwTextFootnote const *pAttr ) +{ + if( ( GetFormat()->GetDoc()->GetFootnoteInfo().m_ePos == FTNPOS_CHAPTER && + (!GetUpper()->IsSctFrame() || !static_cast<SwSectionFrame*>(GetUpper())->IsFootnoteAtEnd())) + || pAttr->GetFootnote().IsEndNote() ) + return; + + OSL_ENSURE( this == pSrc->FindFootnoteBossFrame( true ), + "SwPageFrame::MoveFootnotes: source frame isn't on that FootnoteBoss" ); + + SwFootnoteFrame *pFootnote = FindFirstFootnote(); + if( pFootnote ) + { + ChangeFootnoteRef( pSrc, pAttr, pDest ); + SwFootnoteBossFrame *pDestBoss = pDest->FindFootnoteBossFrame( true ); + OSL_ENSURE( pDestBoss, "+SwPageFrame::MoveFootnotes: no destination boss" ); + if( pDestBoss ) // robust + { + SwFootnoteFrames aFootnoteArr; + SwFootnoteBossFrame::CollectFootnotes_(pDest, pFootnote, aFootnoteArr, nullptr); + if ( !aFootnoteArr.empty() ) + { + pDestBoss->MoveFootnotes_( aFootnoteArr, true ); + SwPageFrame* pSrcPage = FindPageFrame(); + SwPageFrame* pDestPage = pDestBoss->FindPageFrame(); + // update FootnoteNum only at page change + if( pSrcPage != pDestPage ) + { + if( pSrcPage->GetPhyPageNum() > pDestPage->GetPhyPageNum() ) + pSrcPage->UpdateFootnoteNum(); + pDestPage->UpdateFootnoteNum(); + } + } + } + } +} + +void SwFootnoteBossFrame::RearrangeFootnotes( const SwTwips nDeadLine, const bool bLock, + const SwTextFootnote *pAttr ) +{ + // Format all footnotes of a column/page so that they might change the column/page. + + SwSaveFootnoteHeight aSave( this, nDeadLine ); + SwFootnoteFrame *pFootnote = FindFirstFootnote(); + if( pFootnote && pFootnote->GetPrev() && bLock ) + { + SwFootnoteFrame* pFirst = static_cast<SwFootnoteFrame*>(pFootnote->GetUpper()->Lower()); + SwFrame* pContent = pFirst->ContainsAny(); + if( pContent ) + { + bool bUnlock = !pFirst->IsBackMoveLocked(); + pFirst->LockBackMove(); + pFirst->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pContent->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pContent->IsTextFrame() && pContent->isFrameAreaDefinitionValid() ) + { + SwObjectFormatter::FormatObjsAtFrame( *pContent, + *(pContent->FindPageFrame()) ); + } + if( bUnlock ) + pFirst->UnlockBackMove(); + } + pFootnote = FindFirstFootnote(); + } + SwDoc *pDoc = GetFormat()->GetDoc(); + const sal_uLong nFootnotePos = pAttr ? ::lcl_FindFootnotePos( pDoc, pAttr ) : 0; + SwFrame *pCnt = pFootnote ? pFootnote->ContainsAny() : nullptr; + if ( pCnt ) + { + bool bMore = true; + bool bStart = pAttr == nullptr; // If no attribute is given, process all + // #i49383# - disable unlock of position of + // lower objects during format of footnote and footnote content. + SwFootnoteFrame* pLastFootnoteFrame( nullptr ); + // footnote frame needs to be locked, if <bLock> isn't set. + bool bUnlockLastFootnoteFrame( false ); + do + { + if( !bStart ) + bStart = ::lcl_FindFootnotePos( pDoc, pCnt->FindFootnoteFrame()->GetAttr() ) + == nFootnotePos; + if( bStart ) + { + pCnt->InvalidatePos_(); + pCnt->InvalidateSize_(); + pCnt->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + SwFootnoteFrame* pFootnoteFrame = pCnt->FindFootnoteFrame(); + // #i49383# + if ( pFootnoteFrame != pLastFootnoteFrame ) + { + if ( pLastFootnoteFrame ) + { + if ( !bLock && bUnlockLastFootnoteFrame ) + { + pLastFootnoteFrame->ColUnlock(); + } + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pLastFootnoteFrame->UnlockPosOfLowerObjs(); + pLastFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + if ( !bLock && bUnlockLastFootnoteFrame && + !pLastFootnoteFrame->GetLower() && + !pLastFootnoteFrame->IsColLocked() && + !pLastFootnoteFrame->IsBackMoveLocked() && + !pLastFootnoteFrame->IsDeleteForbidden() ) + { + pLastFootnoteFrame->Cut(); + SwFrame::DestroyFrame(pLastFootnoteFrame); + pLastFootnoteFrame = nullptr; + } + } + if ( !bLock ) + { + bUnlockLastFootnoteFrame = !pFootnoteFrame->IsColLocked(); + pFootnoteFrame->ColLock(); + } + pFootnoteFrame->KeepLockPosOfLowerObjs(); + pLastFootnoteFrame = pFootnoteFrame; + } + // OD 30.10.2002 #97265# - invalidate position of footnote + // frame, if it's below its footnote container, in order to + // assure its correct position, probably calculating its previous + // footnote frames. + { + SwRectFnSet aRectFnSet(this); + SwFrame* pFootnoteContFrame = pFootnoteFrame->GetUpper(); + if ( aRectFnSet.TopDist(pFootnoteFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*pFootnoteContFrame)) > 0 ) + { + pFootnoteFrame->InvalidatePos_(); + } + } + if ( bLock ) + { + bool bUnlock = !pFootnoteFrame->IsBackMoveLocked(); + pFootnoteFrame->LockBackMove(); + pFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + SwFrameDeleteGuard aDeleteGuard(pFootnote); + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pFootnote ? pFootnote->ContainsAny() : nullptr; + if (!pCnt) + bMore = false; + continue; + } + } + if( bUnlock ) + { + pFootnoteFrame->UnlockBackMove(); + if( !pFootnoteFrame->Lower() && + !pFootnoteFrame->IsColLocked() ) + { + // #i49383# + OSL_ENSURE( pLastFootnoteFrame == pFootnoteFrame, + "<SwFootnoteBossFrame::RearrangeFootnotes(..)> - <pLastFootnoteFrame> != <pFootnoteFrame>" ); + pLastFootnoteFrame = nullptr; + pFootnoteFrame->Cut(); + SwFrame::DestroyFrame(pFootnoteFrame); + if (pFootnote == pFootnoteFrame) + pFootnote = nullptr; + } + } + } + else + { + pFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pFootnote->ContainsAny(); + continue; + } + } + } + } + SwSectionFrame *pDel = nullptr; + if( pCnt->IsSctFrame() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + { + pCnt = pTmp; + continue; + } + pDel = static_cast<SwSectionFrame*>(pCnt); + } + if ( pCnt->GetNext() ) + pCnt = pCnt->GetNext(); + else + { + pCnt = pCnt->FindNext(); + if ( pCnt ) + { + SwFootnoteFrame* pFootnoteFrame = pCnt->FindFootnoteFrame(); + if( pFootnoteFrame->GetRef()->FindFootnoteBossFrame( + pFootnoteFrame->GetAttr()->GetFootnote().IsEndNote() ) != this ) + bMore = false; + } + else + bMore = false; + } + if( pDel ) + { + bool bUnlockLastFootnoteFrameGuard = pLastFootnoteFrame && !pLastFootnoteFrame->IsColLocked(); + if (bUnlockLastFootnoteFrameGuard) + pLastFootnoteFrame->ColLock(); + pDel->Cut(); + if (bUnlockLastFootnoteFrameGuard) + pLastFootnoteFrame->ColUnlock(); + SwFrame::DestroyFrame(pDel); + } + if ( bMore ) + { + // Go not further than to the provided footnote (if given) + if ( pAttr && + (::lcl_FindFootnotePos( pDoc, + pCnt->FindFootnoteFrame()->GetAttr()) > nFootnotePos ) ) + bMore = false; + } + } while ( bMore ); + // #i49383# + if ( pLastFootnoteFrame ) + { + if ( !bLock && bUnlockLastFootnoteFrame ) + { + pLastFootnoteFrame->ColUnlock(); + } + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pLastFootnoteFrame->UnlockPosOfLowerObjs(); + pLastFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + if ( !bLock && bUnlockLastFootnoteFrame && + !pLastFootnoteFrame->GetLower() && + !pLastFootnoteFrame->IsColLocked() && + !pLastFootnoteFrame->IsBackMoveLocked() ) + { + pLastFootnoteFrame->Cut(); + SwFrame::DestroyFrame(pLastFootnoteFrame); + } + } + } +} + +void SwPageFrame::UpdateFootnoteNum() +{ + // page numbering only if set at the document + if ( GetFormat()->GetDoc()->GetFootnoteInfo().m_eNum != FTNNUM_PAGE ) + return; + + SwLayoutFrame* pBody = FindBodyCont(); + if( !pBody || !pBody->Lower() ) + return; + + SwContentFrame* pContent = pBody->ContainsContent(); + sal_uInt16 nNum = 0; + + while( pContent && pContent->FindPageFrame() == this ) + { + if( static_cast<SwTextFrame*>(pContent)->HasFootnote() ) + { + SwFootnoteBossFrame* pBoss = pContent->FindFootnoteBossFrame( true ); + if( pBoss->GetUpper()->IsSctFrame() && + static_cast<SwSectionFrame*>(pBoss->GetUpper())->IsOwnFootnoteNum() ) + pContent = static_cast<SwSectionFrame*>(pBoss->GetUpper())->FindLastContent(); + else + { + SwFootnoteFrame* pFootnote = const_cast<SwFootnoteFrame*>(pBoss->FindFirstFootnote( pContent )); + while( pFootnote ) + { + SwTextFootnote* pTextFootnote = pFootnote->GetAttr(); + if( !pTextFootnote->GetFootnote().IsEndNote() && + pTextFootnote->GetFootnote().GetNumStr().isEmpty() && + !pFootnote->GetMaster()) + { + // sw_redlinehide: the layout can only keep one number + // up to date; depending on its setting, this is either + // the non-hidden or the hidden number; the other + // number will simply be preserved as-is (so in case + // there are 2 layouts, maybe both can be updated...) + ++nNum; + sal_uInt16 const nOldNum(pTextFootnote->GetFootnote().GetNumber()); + sal_uInt16 const nOldNumRLHidden(pTextFootnote->GetFootnote().GetNumberRLHidden()); + if (getRootFrame()->IsHideRedlines()) + { + if (nNum != nOldNumRLHidden) + { + pTextFootnote->SetNumber(nOldNum, nNum, OUString()); + } + } + else + { + if (nNum != nOldNum) + { + pTextFootnote->SetNumber(nNum, nOldNumRLHidden, OUString()); + } + } + } + if ( pFootnote->GetNext() ) + pFootnote = static_cast<SwFootnoteFrame*>(pFootnote->GetNext()); + else + { + SwFootnoteBossFrame* pTmpBoss = pFootnote->FindFootnoteBossFrame( true ); + if( pTmpBoss ) + { + SwPageFrame* pPage = pTmpBoss->FindPageFrame(); + pFootnote = nullptr; + lcl_NextFootnoteBoss( pTmpBoss, pPage, false ); + SwFootnoteContFrame *pCont = pTmpBoss ? pTmpBoss->FindNearestFootnoteCont() : nullptr; + if ( pCont ) + pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + } + } + if( pFootnote && pFootnote->GetRef() != pContent ) + pFootnote = nullptr; + } + } + } + pContent = pContent->FindNextCnt(); + } +} + +void SwFootnoteBossFrame::SetFootnoteDeadLine( const SwTwips nDeadLine ) +{ + SwFrame *pBody = FindBodyCont(); + pBody->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + SwFrame *pCont = FindFootnoteCont(); + const SwTwips nMax = m_nMaxFootnoteHeight;// current should exceed MaxHeight + SwRectFnSet aRectFnSet(this); + if ( pCont ) + { + pCont->Calc(getRootFrame()->GetCurrShell()->GetOut()); + m_nMaxFootnoteHeight = -aRectFnSet.BottomDist( pCont->getFrameArea(), nDeadLine ); + } + else + m_nMaxFootnoteHeight = -aRectFnSet.BottomDist( pBody->getFrameArea(), nDeadLine ); + + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + m_nMaxFootnoteHeight += pBody->Grow( LONG_MAX, true ); + if ( IsInSct() ) + m_nMaxFootnoteHeight += FindSctFrame()->Grow( LONG_MAX, true ); + + if ( m_nMaxFootnoteHeight < 0 ) + m_nMaxFootnoteHeight = 0; + if ( nMax != LONG_MAX && m_nMaxFootnoteHeight > nMax ) + m_nMaxFootnoteHeight = nMax; +} + +SwTwips SwFootnoteBossFrame::GetVarSpace() const +{ + // To not fall below 20% of the page height + // (in contrast to MSOffice where footnotes can fill a whole column/page) + + const SwPageFrame* pPg = FindPageFrame(); + OSL_ENSURE( pPg || IsInSct(), "Footnote lost page" ); + + const SwFrame *pBody = FindBodyCont(); + SwTwips nRet; + if( pBody ) + { + SwRectFnSet aRectFnSet(this); + if( IsInSct() ) + { + nRet = 0; + SwTwips nTmp = aRectFnSet.YDiff( aRectFnSet.GetPrtTop(*pBody), + aRectFnSet.GetTop(getFrameArea()) ); + const SwSectionFrame* pSect = FindSctFrame(); + // Endnotes in a ftncontainer causes a deadline: + // the bottom of the last contentfrm + if( pSect->IsEndnAtEnd() ) // endnotes allowed? + { + OSL_ENSURE( !Lower() || !Lower()->GetNext() || Lower()->GetNext()-> + IsFootnoteContFrame(), "FootnoteContainer expected" ); + const SwFootnoteContFrame* pCont = Lower() ? + static_cast<const SwFootnoteContFrame*>(Lower()->GetNext()) : nullptr; + if( pCont ) + { + const SwFootnoteFrame* pFootnote = static_cast<const SwFootnoteFrame*>(pCont->Lower()); + while( pFootnote) + { + if( pFootnote->GetAttr()->GetFootnote().IsEndNote() ) + { // endnote found + const SwFrame* pFrame = static_cast<const SwLayoutFrame*>(Lower())->Lower(); + if( pFrame ) + { + while( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); // last cntntfrm + nTmp += aRectFnSet.YDiff( + aRectFnSet.GetTop(getFrameArea()), + aRectFnSet.GetBottom(pFrame->getFrameArea()) ); + } + break; + } + pFootnote = static_cast<const SwFootnoteFrame*>(pFootnote->GetNext()); + } + } + } + if( nTmp < nRet ) + nRet = nTmp; + } + else + nRet = - aRectFnSet.GetHeight(pPg->getFramePrintArea())/5; + nRet += aRectFnSet.GetHeight(pBody->getFrameArea()); + if( nRet < 0 ) + nRet = 0; + } + else + nRet = 0; + if ( IsPageFrame() ) + { + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + nRet += BROWSE_HEIGHT - getFrameArea().Height(); + } + return nRet; +} + +/** Obtain if pFrame's size adjustment should be processed + * + * For a page frame of columns directly below the page AdjustNeighbourhood() needs + * to be called, or Grow()/ Shrink() for frame columns respectively. + * + * A column section is special, since if there is a footnote container in a column + * and those footnotes are not collected, it is handled like a page frame. + * + * @see AdjustNeighbourhood() + * @see Grow() + * @see Shrink() + */ +SwNeighbourAdjust SwFootnoteBossFrame::NeighbourhoodAdjustment_() const +{ + SwNeighbourAdjust nRet = SwNeighbourAdjust::OnlyAdjust; + if( GetUpper() && !GetUpper()->IsPageBodyFrame() ) + { + // column sections need grow/shrink + if( GetUpper()->IsFlyFrame() ) + nRet = SwNeighbourAdjust::GrowShrink; + else + { + OSL_ENSURE( GetUpper()->IsSctFrame(), "NeighbourhoodAdjustment: Unexpected Upper" ); + if( !GetNext() && !GetPrev() ) + nRet = SwNeighbourAdjust::GrowAdjust; // section with a single column (FootnoteAtEnd) + else + { + const SwFrame* pTmp = Lower(); + OSL_ENSURE( pTmp, "NeighbourhoodAdjustment: Missing Lower()" ); + if( !pTmp->GetNext() ) + nRet = SwNeighbourAdjust::GrowShrink; + else if( !GetUpper()->IsColLocked() ) + nRet = SwNeighbourAdjust::AdjustGrow; + OSL_ENSURE( !pTmp->GetNext() || pTmp->GetNext()->IsFootnoteContFrame(), + "NeighbourhoodAdjustment: Who's that guy?" ); + } + } + } + return nRet; +} + +void SwPageFrame::SetColMaxFootnoteHeight() +{ + SwLayoutFrame *pBody = FindBodyCont(); + if( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + SwColumnFrame* pCol = static_cast<SwColumnFrame*>(pBody->Lower()); + do + { + pCol->SetMaxFootnoteHeight( GetMaxFootnoteHeight() ); + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } while ( pCol ); + } +} + +bool SwLayoutFrame::MoveLowerFootnotes( SwContentFrame *pStart, SwFootnoteBossFrame *pOldBoss, + SwFootnoteBossFrame *pNewBoss, const bool bFootnoteNums ) +{ + SwDoc *pDoc = GetFormat()->GetDoc(); + if ( pDoc->GetFootnoteIdxs().empty() ) + return false; + if( pDoc->GetFootnoteInfo().m_ePos == FTNPOS_CHAPTER && + ( !IsInSct() || !FindSctFrame()->IsFootnoteAtEnd() ) ) + return true; + + if ( !pNewBoss ) + pNewBoss = FindFootnoteBossFrame( true ); + if ( pNewBoss == pOldBoss ) + return false; + + bool bMoved = false; + if( !pStart ) + pStart = ContainsContent(); + + SwFootnoteFrames aFootnoteArr; + + while ( IsAnLower( pStart ) ) + { + if ( static_cast<SwTextFrame*>(pStart)->HasFootnote() ) + { + // OD 03.04.2003 #108446# - To avoid unnecessary moves of footnotes + // use new parameter <_bCollectOnlyPreviousFootnote> (4th parameter of + // method <SwFootnoteBossFrame::CollectFootnote(..)>) to control, that only + // footnotes have to be collected, that are positioned before the + // new dedicated footnote boss frame. + pNewBoss->CollectFootnotes( pStart, pOldBoss, aFootnoteArr, true ); + } + pStart = pStart->GetNextContentFrame(); + } + + OSL_ENSURE( pOldBoss->IsInSct() == pNewBoss->IsInSct(), + "MoveLowerFootnotes: Section confusion" ); + std::unique_ptr<SwFootnoteFrames> pFootnoteArr; + SwLayoutFrame* pNewChief = nullptr; + SwLayoutFrame* pOldChief = nullptr; + + bool bFoundCandidate = false; + if (pStart && pOldBoss->IsInSct()) + { + pOldChief = pOldBoss->FindSctFrame(); + pNewChief = pNewBoss->FindSctFrame(); + bFoundCandidate = pOldChief != pNewChief; + } + + if (bFoundCandidate) + { + pFootnoteArr.reset(new SwFootnoteFrames); + pOldChief = pOldBoss->FindFootnoteBossFrame( true ); + pNewChief = pNewBoss->FindFootnoteBossFrame( true ); + while( pOldChief->IsAnLower( pStart ) ) + { + if ( static_cast<SwTextFrame*>(pStart)->HasFootnote() ) + static_cast<SwFootnoteBossFrame*>(pNewChief)->CollectFootnotes( pStart, + pOldBoss, *pFootnoteArr ); + pStart = pStart->GetNextContentFrame(); + } + if( pFootnoteArr->empty() ) + { + pFootnoteArr.reset(); + } + } + else + pFootnoteArr = nullptr; + + if ( !aFootnoteArr.empty() || pFootnoteArr ) + { + if( !aFootnoteArr.empty() ) + pNewBoss->MoveFootnotes_( aFootnoteArr, true ); + if( pFootnoteArr ) + { + assert(pNewChief); + static_cast<SwFootnoteBossFrame*>(pNewChief)->MoveFootnotes_( *pFootnoteArr, true ); + pFootnoteArr.reset(); + } + bMoved = true; + + // update FootnoteNum only at page change + if ( bFootnoteNums ) + { + SwPageFrame* pOldPage = pOldBoss->FindPageFrame(); + SwPageFrame* pNewPage =pNewBoss->FindPageFrame(); + if( pOldPage != pNewPage ) + { + pOldPage->UpdateFootnoteNum(); + pNewPage->UpdateFootnoteNum(); + } + } + } + return bMoved; +} + +/// Return value guarantees that a new page was not created. See SwFlowFrame::MoveFwd. +bool SwContentFrame::MoveFootnoteCntFwd( bool bMakePage, SwFootnoteBossFrame *pOldBoss ) +{ + OSL_ENSURE( IsInFootnote(), "no footnote." ); + SwLayoutFrame *pFootnote = FindFootnoteFrame(); + + // The first paragraph in the first footnote in the first column in the + // sectionfrm at the top of the page has not to move forward, if the + // columnbody is empty. + if( pOldBoss->IsInSct() && !pOldBoss->GetIndPrev() && !GetIndPrev() && + !pFootnote->GetPrev() ) + { + SwLayoutFrame* pBody = pOldBoss->FindBodyCont(); + if( !pBody || !pBody->Lower() ) + return true; + } + + //fix(9538): if the footnote has neighbors behind itself, remove them temporarily + SwLayoutFrame *pNxt = static_cast<SwLayoutFrame*>(pFootnote->GetNext()); + SwLayoutFrame *pLst = nullptr; + while ( pNxt ) + { + while ( pNxt->GetNext() ) + pNxt = static_cast<SwLayoutFrame*>(pNxt->GetNext()); + if ( pNxt == pLst ) + pNxt = nullptr; + else + { pLst = pNxt; + SwContentFrame *pCnt = pNxt->ContainsContent(); + if( pCnt ) + pCnt->MoveFootnoteCntFwd( true, pOldBoss ); + pNxt = static_cast<SwLayoutFrame*>(pFootnote->GetNext()); + } + } + + bool bSamePage = true; + SwLayoutFrame *pNewUpper = + GetLeaf( bMakePage ? MAKEPAGE_INSERT : MAKEPAGE_NONE, true ); + + if ( pNewUpper ) + { + SwFootnoteBossFrame * const pNewBoss = pNewUpper->FindFootnoteBossFrame(); + // Are we changing the column/page? + bool bSameBoss = pNewBoss == pOldBoss; + if ( !bSameBoss ) + { + bSamePage = pOldBoss->FindPageFrame() == pNewBoss->FindPageFrame(); // page change? + pNewUpper->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + + // The layout leaf of the footnote is either a footnote container or a footnote. + // If it is a footnote and it has the same footnote reference like the old Upper, + // then move the content inside of it. + // If it is a container or the reference differs, create a new footnote and add + // it into the container. + // Create also a SectionFrame if currently in an area inside a footnote. + SwFootnoteFrame* pTmpFootnote = pNewUpper->IsFootnoteFrame() ? static_cast<SwFootnoteFrame*>(pNewUpper) : nullptr; + if (!pTmpFootnote && pNewUpper->IsFootnoteContFrame()) + { + SwFootnoteContFrame *pCont = static_cast<SwFootnoteContFrame*>(pNewUpper); + pTmpFootnote = SwFootnoteContFrame::AppendChained(this, true); + SwFrame* pNx = pCont->Lower(); + if( pNx && pTmpFootnote->GetAttr()->GetFootnote().IsEndNote() ) + while(pNx && !static_cast<SwFootnoteFrame*>(pNx)->GetAttr()->GetFootnote().IsEndNote()) + pNx = pNx->GetNext(); + pTmpFootnote->Paste( pCont, pNx ); + pTmpFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + OSL_ENSURE( pTmpFootnote->GetAttr() == FindFootnoteFrame()->GetAttr(), "Wrong Footnote!" ); + // areas inside of footnotes get a special treatment + SwLayoutFrame *pNewUp = pTmpFootnote; + if( IsInSct() ) + { + SwSectionFrame* pSect = FindSctFrame(); + // area inside of a footnote (or only footnote in an area)? + if( pSect->IsInFootnote() ) + { + if( pTmpFootnote->Lower() && pTmpFootnote->Lower()->IsSctFrame() && + pSect->GetFollow() == static_cast<SwSectionFrame*>(pTmpFootnote->Lower()) ) + pNewUp = static_cast<SwSectionFrame*>(pTmpFootnote->Lower()); + else + { + pNewUp = new SwSectionFrame( *pSect, false ); + pNewUp->InsertBefore( pTmpFootnote, pTmpFootnote->Lower() ); + static_cast<SwSectionFrame*>(pNewUp)->Init(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNewUp); + aFrm.Pos() = pTmpFootnote->getFrameArea().Pos(); + aFrm.Pos().AdjustY(1 ); // for notifications + } + + // If the section frame has a successor then the latter needs + // to be moved behind the new Follow of the section frame. + SwFrame* pTmp = pSect->GetNext(); + if( pTmp ) + { + SwFlowFrame* pTmpNxt; + if( pTmp->IsContentFrame() ) + pTmpNxt = static_cast<SwContentFrame*>(pTmp); + else if( pTmp->IsSctFrame() ) + pTmpNxt = static_cast<SwSectionFrame*>(pTmp); + else + { + OSL_ENSURE( pTmp->IsTabFrame(), "GetNextSctLeaf: Wrong Type" ); + pTmpNxt = static_cast<SwTabFrame*>(pTmp); + } + pTmpNxt->MoveSubTree( pTmpFootnote, pNewUp->GetNext() ); + } + } + } + } + + MoveSubTree( pNewUp, pNewUp->Lower() ); + + if( !bSameBoss ) + Prepare( PrepareHint::BossChanged ); + } + return bSamePage; +} + +SwSaveFootnoteHeight::SwSaveFootnoteHeight( SwFootnoteBossFrame *pBs, const SwTwips nDeadLine ) : + pBoss( pBs ), + nOldHeight( pBs->GetMaxFootnoteHeight() ) +{ + pBoss->SetFootnoteDeadLine( nDeadLine ); + nNewHeight = pBoss->GetMaxFootnoteHeight(); +} + +SwSaveFootnoteHeight::~SwSaveFootnoteHeight() +{ + // If somebody tweaked the deadline meanwhile, we let it happen + if ( nNewHeight == pBoss->GetMaxFootnoteHeight() ) + pBoss->m_nMaxFootnoteHeight = nOldHeight; +} + +#ifdef DBG_UTIL +//JP 15.10.2001: in a non pro version test if the attribute has the same +// meaning which his reference is + +// Normally, the pRef member and the GetRefFromAttr() result has to be +// identically. Sometimes footnote will be moved from a master to its follow, +// but the GetRef() is called first, so we have to ignore a master/follow +// mismatch. + +const SwContentFrame* SwFootnoteFrame::GetRef() const +{ + const SwContentFrame* pRefAttr = GetRefFromAttr(); + // check consistency: access to deleted frame? + assert(mpReference == pRefAttr || mpReference->IsAnFollow(pRefAttr) + || pRefAttr->IsAnFollow(mpReference)); + (void) pRefAttr; + return mpReference; +} + +SwContentFrame* SwFootnoteFrame::GetRef() +{ + const SwContentFrame* pRefAttr = GetRefFromAttr(); + // check consistency: access to deleted frame? + assert(mpReference == pRefAttr || mpReference->IsAnFollow(pRefAttr) + || pRefAttr->IsAnFollow(mpReference)); + (void) pRefAttr; + return mpReference; +} +#endif + +const SwContentFrame* SwFootnoteFrame::GetRefFromAttr() const +{ + SwFootnoteFrame* pThis = const_cast<SwFootnoteFrame*>(this); + return pThis->GetRefFromAttr(); +} + +SwContentFrame* SwFootnoteFrame::GetRefFromAttr() +{ + assert(mpAttribute && "invalid Attribute"); + SwTextNode& rTNd = const_cast<SwTextNode&>(mpAttribute->GetTextNode()); + SwPosition aPos( rTNd, SwIndex( &rTNd, mpAttribute->GetStart() )); + SwContentFrame* pCFrame = rTNd.getLayoutFrame(getRootFrame(), &aPos); + return pCFrame; +} + +/** search for last content in the current footnote frame + + OD 2005-12-02 #i27138# +*/ +SwContentFrame* SwFootnoteFrame::FindLastContent() +{ + SwContentFrame* pLastContentFrame( nullptr ); + + // find last lower, which is a content frame or contains content. + // hidden text frames, empty sections and empty tables have to be skipped. + SwFrame* pLastLowerOfFootnote( GetLower() ); + SwFrame* pTmpLastLower( pLastLowerOfFootnote ); + while ( pTmpLastLower && pTmpLastLower->GetNext() ) + { + pTmpLastLower = pTmpLastLower->GetNext(); + if ( ( pTmpLastLower->IsTextFrame() && + !static_cast<SwTextFrame*>(pTmpLastLower)->IsHiddenNow() ) || + ( pTmpLastLower->IsSctFrame() && + static_cast<SwSectionFrame*>(pTmpLastLower)->GetSection() && + static_cast<SwSectionFrame*>(pTmpLastLower)->ContainsContent() ) || + ( pTmpLastLower->IsTabFrame() && + static_cast<SwTabFrame*>(pTmpLastLower)->ContainsContent() ) ) + { + pLastLowerOfFootnote = pTmpLastLower; + } + } + + // determine last content frame depending on type of found last lower. + if ( pLastLowerOfFootnote && pLastLowerOfFootnote->IsTabFrame() ) + { + pLastContentFrame = static_cast<SwTabFrame*>(pLastLowerOfFootnote)->FindLastContent(); + } + else if ( pLastLowerOfFootnote && pLastLowerOfFootnote->IsSctFrame() ) + { + pLastContentFrame = static_cast<SwSectionFrame*>(pLastLowerOfFootnote)->FindLastContent(); + } + else + { + pLastContentFrame = dynamic_cast<SwContentFrame*>(pLastLowerOfFootnote); + } + + return pLastContentFrame; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/hffrm.cxx b/sw/source/core/layout/hffrm.cxx new file mode 100644 index 000000000..87d63ebb7 --- /dev/null +++ b/sw/source/core/layout/hffrm.cxx @@ -0,0 +1,767 @@ +/* -*- 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 <pagefrm.hxx> +#include <fmtcntnt.hxx> +#include <fmthdft.hxx> +#include <fmtfsize.hxx> +#include <viewopt.hxx> +#include <hffrm.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <flyfrm.hxx> +#include <frmtool.hxx> +#include <hfspacingitem.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <ndindex.hxx> +#include <sal/log.hxx> + +static SwTwips lcl_GetFrameMinHeight(const SwLayoutFrame & rFrame) +{ + const SwFormatFrameSize &rSz = rFrame.GetFormat()->GetFrameSize(); + SwTwips nMinHeight; + + switch (rSz.GetHeightSizeType()) + { + case SwFrameSize::Minimum: + nMinHeight = rSz.GetHeight(); + + break; + + default: + nMinHeight = 0; + } + + return nMinHeight; +} + +static SwTwips lcl_CalcContentHeight(SwLayoutFrame & frm) +{ + SwTwips nRemaining = 0; + SwFrame* pFrame = frm.Lower(); + + while ( pFrame ) + { + SwTwips nTmp; + + nTmp = pFrame->getFrameArea().Height(); + nRemaining += nTmp; + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + { + nTmp = static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - pFrame->getFramePrintArea().Height(); + // This TextFrame would like to be a bit bigger + nRemaining += nTmp; + } + else if( pFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + { + nTmp = static_cast<SwSectionFrame*>(pFrame)->Undersize(); + nRemaining += nTmp; + } + pFrame = pFrame->GetNext(); + } + + return nRemaining; +} + +static void lcl_LayoutFrameEnsureMinHeight(SwLayoutFrame & rFrame) +{ + SwTwips nMinHeight = lcl_GetFrameMinHeight(rFrame); + + if (rFrame.getFrameArea().Height() < nMinHeight) + { + rFrame.Grow(nMinHeight - rFrame.getFrameArea().Height()); + } +} + +SwHeadFootFrame::SwHeadFootFrame( SwFrameFormat * pFormat, SwFrame* pSib, SwFrameType nTypeIn) + : SwLayoutFrame( pFormat, pSib ) +{ + mnFrameType = nTypeIn; + SetDerivedVert( false ); + + const SwFormatContent &rCnt = pFormat->GetContent(); + + OSL_ENSURE( rCnt.GetContentIdx(), "No content for Header." ); + + // Have the objects created right now for header and footer + bool bOld = bObjsDirect; + bObjsDirect = true; + sal_uLong nIndex = rCnt.GetContentIdx()->GetIndex(); + ::InsertCnt_( this, pFormat->GetDoc(), ++nIndex ); + bObjsDirect = bOld; +} + +void SwHeadFootFrame::FormatPrt(SwTwips & nUL, const SwBorderAttrs * pAttrs) +{ + if (GetEatSpacing()) + { + /* The minimal height of the print area is the minimal height of the + frame without the height needed for borders and shadow. */ + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + + nMinHeight -= pAttrs->CalcTop(); + nMinHeight -= pAttrs->CalcBottom(); + + /* If the minimal height of the print area is negative, try to + compensate by overlapping */ + SwTwips nOverlap = 0; + if (nMinHeight < 0) + { + nOverlap = -nMinHeight; + nMinHeight = 0; + } + + /* Calculate desired height of content. The minimal height has to be + adhered. */ + SwTwips nHeight; + + if ( ! HasFixSize() ) + nHeight = lcl_CalcContentHeight(*this); + else + nHeight = nMinHeight; + + if (nHeight < nMinHeight) + nHeight = nMinHeight; + + /* calculate initial spacing/line space */ + SwTwips nSpace, nLine; + + if (IsHeaderFrame()) + { + nSpace = pAttrs->CalcBottom(); + nLine = pAttrs->CalcBottomLine(); + } + else + { + nSpace = pAttrs->CalcTop(); + nLine = pAttrs->CalcTopLine(); + } + + /* calculate overlap and correct spacing */ + nOverlap += nHeight - nMinHeight; + if (nOverlap < nSpace - nLine) + nSpace -= nOverlap; + else + nSpace = nLine; + + /* calculate real vertical space between frame and print area */ + if (IsHeaderFrame()) + nUL = pAttrs->CalcTop() + nSpace; + else + nUL = pAttrs->CalcBottom() + nSpace; + + /* set print area */ + SwTwips nLR = pAttrs->CalcLeft( this ) + pAttrs->CalcRight( this ); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + aPrt.Left(pAttrs->CalcLeft(this)); + + if (IsHeaderFrame()) + { + aPrt.Top(pAttrs->CalcTop()); + } + else + { + aPrt.Top(nSpace); + } + + aPrt.Width(getFrameArea().Width() - nLR); + + SwTwips nNewHeight; + + if (nUL < getFrameArea().Height()) + { + nNewHeight = getFrameArea().Height() - nUL; + } + else + { + nNewHeight = 0; + } + + aPrt.Height(nNewHeight); + } + else + { + // Set position + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( pAttrs->CalcLeft( this ) ); + aPrt.Top ( pAttrs->CalcTop() ); + + // Set sizes - the sizes are given by the surrounding Frame, just + // subtract the borders. + SwTwips nLR = pAttrs->CalcLeft( this ) + pAttrs->CalcRight( this ); + aPrt.Width ( getFrameArea().Width() - nLR ); + aPrt.Height( getFrameArea().Height()- nUL ); + } + + setFramePrintAreaValid(true); +} + +void SwHeadFootFrame::FormatSize(SwTwips nUL, const SwBorderAttrs * pAttrs) +{ + if ( !HasFixSize() ) + { + if( !IsColLocked() ) + { + setFramePrintAreaValid(true); + setFrameAreaSizeValid(true); + + const SwTwips nBorder = nUL; + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + nMinHeight -= pAttrs->CalcTop(); + nMinHeight -= pAttrs->CalcBottom(); + + if (nMinHeight < 0) + nMinHeight = 0; + + ColLock(); + + SwTwips nMaxHeight = LONG_MAX; + SwTwips nRemaining, nOldHeight; + // #i64301# + // use the position of the footer printing area to control invalidation + // of the first footer content. + Point aOldFooterPrtPos; + + do + { + nOldHeight = getFramePrintArea().Height(); + SwFrame* pFrame = Lower(); + // #i64301# + if ( pFrame && + aOldFooterPrtPos != ( getFrameArea().Pos() + getFramePrintArea().Pos() ) ) + { + pFrame->InvalidatePos_(); + aOldFooterPrtPos = getFrameArea().Pos() + getFramePrintArea().Pos(); + } + int nLoopControl = 0; + while( pFrame ) + { + pFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i43771# - format also object anchored + // at the frame + // #i46941# - frame has to be valid. + // Note: frame could be invalid after calling its format, + // if it's locked + OSL_ENSURE( StackHack::IsLocked() || !pFrame->IsTextFrame() || + pFrame->isFrameAreaDefinitionValid() || + static_cast<SwTextFrame*>(pFrame)->IsJoinLocked(), + "<SwHeadFootFrame::FormatSize(..)> - text frame invalid and not locked." ); + + if ( pFrame->IsTextFrame() && pFrame->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pFrame, + *(pFrame->FindPageFrame()) ) ) + { + if (nLoopControl++ < 20) + { + // restart format with first content + pFrame = Lower(); + continue; + } + else + SAL_WARN("sw", "SwHeadFootFrame::FormatSize: loop detection triggered"); + } + } + pFrame = pFrame->GetNext(); + } + nRemaining = 0; + pFrame = Lower(); + + while ( pFrame ) + { + nRemaining += pFrame->getFrameArea().Height(); + + if( pFrame->IsTextFrame() && + static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + // This TextFrame would like to be a bit bigger + nRemaining += static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - pFrame->getFramePrintArea().Height(); + else if( pFrame->IsSctFrame() && + static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + nRemaining += static_cast<SwSectionFrame*>(pFrame)->Undersize(); + pFrame = pFrame->GetNext(); + } + if ( nRemaining < nMinHeight ) + nRemaining = nMinHeight; + + SwTwips nDiff = nRemaining - nOldHeight; + + if( !nDiff ) + break; + if( nDiff < 0 ) + { + nMaxHeight = nOldHeight; + + if( nRemaining <= nMinHeight ) + nRemaining = ( nMaxHeight + nMinHeight + 1 ) / 2; + } + else + { + if (nOldHeight > nMinHeight) + nMinHeight = nOldHeight; + + if( nRemaining >= nMaxHeight ) + nRemaining = ( nMaxHeight + nMinHeight + 1 ) / 2; + } + + nDiff = nRemaining - nOldHeight; + + if ( nDiff ) + { + ColUnlock(); + if ( nDiff > 0 ) + { + if ( Grow( nDiff ) ) + { + pFrame = Lower(); + + while ( pFrame ) + { + if( pFrame->IsTextFrame()) + { + SwTextFrame * pTmpFrame = static_cast<SwTextFrame*>(pFrame); + if (pTmpFrame->IsUndersized() ) + { + pTmpFrame->InvalidateSize(); + pTmpFrame->Prepare(PrepareHint::AdjustSizeWithoutFormatting); + } + } + /* #i3568# Undersized sections need to be + invalidated too. */ + else if (pFrame->IsSctFrame()) + { + SwSectionFrame * pTmpFrame = + static_cast<SwSectionFrame*>(pFrame); + if (pTmpFrame->IsUndersized() ) + { + pTmpFrame->InvalidateSize(); + pTmpFrame->Prepare(PrepareHint::AdjustSizeWithoutFormatting); + } + } + pFrame = pFrame->GetNext(); + } + } + } + else + Shrink( -nDiff ); + // Quickly update the position + + MakePos(); + ColLock(); + } + else + break; + // Don't overwrite the lower edge of the upper + if ( GetUpper() && getFrameArea().Height() ) + { + const SwTwips nDeadLine = GetUpper()->getFrameArea().Top() + GetUpper()->getFramePrintArea().Bottom(); + const SwTwips nBot = getFrameArea().Bottom(); + + if ( nBot > nDeadLine ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Bottom( nDeadLine ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( getFrameArea().Height() - nBorder ); + } + } + + setFramePrintAreaValid(true); + setFrameAreaSizeValid(true); + } while( nRemaining<=nMaxHeight && nOldHeight!=getFramePrintArea().Height() ); + ColUnlock(); + } + + setFramePrintAreaValid(true); + setFrameAreaSizeValid(true); + } + else //if (GetType() & FRM_HEADFOOT) + { + do + { + if ( getFrameArea().Height() != pAttrs->GetSize().Height() ) + { + ChgSize( Size( getFrameArea().Width(), pAttrs->GetSize().Height())); + } + + setFrameAreaSizeValid(true); + MakePos(); + } while ( !isFrameAreaSizeValid() ); + } +} + +void SwHeadFootFrame::Format(vcl::RenderContext* pRenderContext, const SwBorderAttrs * pAttrs) +{ + OSL_ENSURE( pAttrs, "SwFooterFrame::Format, pAttrs is 0." ); + + if ( isFramePrintAreaValid() && isFrameAreaSizeValid() ) + return; + + if ( ! GetEatSpacing() && IsHeaderFrame()) + { + SwLayoutFrame::Format(pRenderContext, pAttrs); + } + else + { + lcl_LayoutFrameEnsureMinHeight(*this); + + long nUL = pAttrs->CalcTop() + pAttrs->CalcBottom(); + + if ( !isFramePrintAreaValid() ) + FormatPrt(nUL, pAttrs); + + if ( !isFrameAreaSizeValid() ) + FormatSize(nUL, pAttrs); + } +} + +SwTwips SwHeadFootFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwTwips nResult; + + if ( IsColLocked() ) + { + nResult = 0; + } + else if (!GetEatSpacing()) + { + nResult = SwLayoutFrame::GrowFrame(nDist, bTst, bInfo); + } + else + { + nResult = 0; + + auto pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + OSL_ENSURE(pAccess, "no border attributes"); + + SwBorderAttrs * pAttrs = pAccess->Get(); + + /* First assume the whole amount to grow can be provided by eating + spacing. */ + SwTwips nEat = nDist; + SwTwips nMaxEat; + + /* calculate maximum eatable spacing */ + if (IsHeaderFrame()) + nMaxEat = getFrameArea().Height() - getFramePrintArea().Top() - getFramePrintArea().Height() - pAttrs->CalcBottomLine(); + else + nMaxEat = getFramePrintArea().Top() - pAttrs->CalcTopLine(); + + if (nMaxEat < 0) + nMaxEat = 0; + + /* If the frame is too small, eat less spacing thus letting the frame + grow more. */ + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + SwTwips nFrameTooSmall = nMinHeight - getFrameArea().Height(); + + if (nFrameTooSmall > 0) + nEat -= nFrameTooSmall; + + /* No negative eating, not eating more than allowed. */ + if (nEat < 0) + nEat = 0; + else if (nEat > nMaxEat) + nEat = nMaxEat; + + // Notify fly frame, if header frame + // grows. Consider, that 'normal' grow of layout frame already notifies + // the fly frames. + bool bNotifyFlys = false; + if (nEat > 0) + { + if ( ! bTst) + { + if (! IsHeaderFrame()) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top(aPrt.Top() - nEat); + aPrt.Height(aPrt.Height() - nEat); + } + + InvalidateAll(); + } + + nResult += nEat; + // trigger fly frame notify. + if ( IsHeaderFrame() ) + { + bNotifyFlys = true; + } + } + + if (nDist - nEat > 0) + { + const SwTwips nFrameGrow = + SwLayoutFrame::GrowFrame( nDist - nEat, bTst, bInfo ); + + nResult += nFrameGrow; + if ( nFrameGrow > 0 ) + { + bNotifyFlys = false; + } + } + + // notify fly frames, if necessary and triggered. + if ( ( nResult > 0 ) && bNotifyFlys ) + { + NotifyLowerObjs(); + } + } + + if ( nResult && !bTst ) + SetCompletePaint(); + + return nResult; +} + +SwTwips SwHeadFootFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwTwips nResult; + + if ( IsColLocked() ) + { + nResult = 0; + } + else if (! GetEatSpacing()) + { + nResult = SwLayoutFrame::ShrinkFrame(nDist, bTst, bInfo); + } + else + { + nResult = 0; + + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + SwTwips nOldHeight = getFrameArea().Height(); + SwTwips nRest = 0; // Amount to shrink by spitting out spacing + + if ( nOldHeight >= nMinHeight ) + { + /* If the frame's height is bigger than its minimum height, shrink + the frame towards its minimum height. If this is not sufficient + to provide the shrinking requested provide the rest by spitting + out spacing. */ + + SwTwips nBiggerThanMin = nOldHeight - nMinHeight; + + if (nBiggerThanMin < nDist) + { + nRest = nDist - nBiggerThanMin; + } + /* info: declaration of nRest -> else nRest = 0 */ + } + else + /* The frame cannot shrink. Provide shrinking by spitting out + spacing. */ + nRest = nDist; + + // Notify fly frame, if header/footer frame shrinks. + // Consider, that 'normal' shrink of layout frame already notifies the fly frames. + bool bNotifyFlys = false; + if (nRest > 0) + { + auto pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + OSL_ENSURE(pAccess, "no border attributes"); + + SwBorderAttrs * pAttrs = pAccess->Get(); + + /* minimal height of print area */ + SwTwips nMinPrtHeight = nMinHeight + - pAttrs->CalcTop() + - pAttrs->CalcBottom(); + + if (nMinPrtHeight < 0) + nMinPrtHeight = 0; + + /* assume all shrinking can be provided */ + SwTwips nShrink = nRest; + + /* calculate maximum shrinking */ + SwTwips nMaxShrink = getFramePrintArea().Height() - nMinPrtHeight; + + /* shrink no more than maximum shrinking */ + if (nShrink > nMaxShrink) + { + //nRest -= nShrink - nMaxShrink; + nShrink = nMaxShrink; + } + + if (!bTst) + { + if (! IsHeaderFrame() ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top(aPrt.Top() + nShrink); + aPrt.Height(aPrt.Height() - nShrink); + } + + InvalidateAll(); + } + nResult += nShrink; + // Trigger fly frame notify. + if ( IsHeaderFrame() ) + { + bNotifyFlys = true; + } + } + + /* The shrinking not providable by spitting out spacing has to be done + by the frame. */ + if (nDist - nRest > 0) + { + SwTwips nShrinkAmount = SwLayoutFrame::ShrinkFrame( nDist - nRest, bTst, bInfo ); + nResult += nShrinkAmount; + if ( nShrinkAmount > 0 ) + { + bNotifyFlys = false; + } + } + + // Notify fly frames, if necessary. + if ( ( nResult > 0 ) && bNotifyFlys ) + { + NotifyLowerObjs(); + } + } + + return nResult; +} + +bool SwHeadFootFrame::GetEatSpacing() const +{ + const SwFrameFormat * pFormat = GetFormat(); + OSL_ENSURE(pFormat, "SwHeadFootFrame: no format?"); + + return pFormat->GetHeaderAndFooterEatSpacing().GetValue(); +} + +static void DelFlys( SwLayoutFrame const *pFrame, SwPageFrame *pPage ) +{ + size_t i = 0; + while ( pPage->GetSortedObjs() && + pPage->GetSortedObjs()->size() && + i < pPage->GetSortedObjs()->size() ) + { + SwAnchoredObject* pObj = (*pPage->GetSortedObjs())[i]; + if ( dynamic_cast< const SwFlyFrame *>( pObj ) != nullptr ) + { + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pObj); + if ( pFrame->IsAnLower( pFlyFrame ) ) + { + SwFrame::DestroyFrame(pFlyFrame); + // Do not increment index, in this case + continue; + } + } + ++i; + } +} + +/// Creates or removes headers +void SwPageFrame::PrepareHeader() +{ + SwLayoutFrame *pLay = static_cast<SwLayoutFrame*>(Lower()); + if ( !pLay ) + return; + + const SwFormatHeader &rH = static_cast<SwFrameFormat*>(GetDep())->GetHeader(); + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bOn = !(pSh && (pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsWhitespaceHidden())); + + if ( bOn && rH.IsActive() ) + { //Implant header, but remove first, if already present + OSL_ENSURE( rH.GetHeaderFormat(), "FrameFormat for Header not found." ); + + if ( pLay->GetFormat() == rH.GetHeaderFormat() ) + return; // Header is already the correct one. + + if ( pLay->IsHeaderFrame() ) + { SwLayoutFrame *pDel = pLay; + pLay = static_cast<SwLayoutFrame*>(pLay->GetNext()); + ::DelFlys( pDel, this ); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + OSL_ENSURE( pLay, "Where to with the Header?" ); + SwHeaderFrame *pH = new SwHeaderFrame( const_cast<SwFrameFormat*>(rH.GetHeaderFormat()), this ); + pH->Paste( this, pLay ); + if ( GetUpper() ) + ::RegistFlys( this, pH ); + } + else if (pLay->IsHeaderFrame()) + { // Remove header if present. + ::DelFlys( pLay, this ); + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } +} + +/// Creates or removes footer +void SwPageFrame::PrepareFooter() +{ + SwLayoutFrame *pLay = static_cast<SwLayoutFrame*>(Lower()); + if ( !pLay ) + return; + + const SwFormatFooter &rF = static_cast<SwFrameFormat*>(GetDep())->GetFooter(); + while ( pLay->GetNext() ) + pLay = static_cast<SwLayoutFrame*>(pLay->GetNext()); + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bOn = !(pSh && (pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsWhitespaceHidden())); + + if ( bOn && rF.IsActive() ) + { //Implant footer, but remove first, if already present + OSL_ENSURE( rF.GetFooterFormat(), "FrameFormat for Footer not found." ); + + if ( pLay->GetFormat() == rF.GetFooterFormat() ) + return; // Footer is already the correct one. + + if ( pLay->IsFooterFrame() ) + { + ::DelFlys( pLay, this ); + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } + SwFooterFrame *pF = new SwFooterFrame( const_cast<SwFrameFormat*>(rF.GetFooterFormat()), this ); + pF->Paste( this ); + if ( GetUpper() ) + ::RegistFlys( this, pF ); + } + else if ( pLay->IsFooterFrame() ) + { // Remove footer if already present + ::DelFlys( pLay, this ); + SwViewShell *pShell; + if ( pLay->GetPrev() && nullptr != (pShell = getRootFrame()->GetCurrShell()) && + pShell->VisArea().HasArea() ) + pShell->InvalidateWindows( pShell->VisArea() ); + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx new file mode 100644 index 000000000..7f913c8a0 --- /dev/null +++ b/sw/source/core/layout/layact.cxx @@ -0,0 +1,2344 @@ +/* -*- 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 <config_feature_desktop.h> + +#include <ctime> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewimp.hxx> +#include <crsrsh.hxx> +#include <dflyobj.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <dbg_lay.hxx> +#include <layouter.hxx> +#include <docstat.hxx> +#include <swevent.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentLayoutAccess.hxx> + +#include <sfx2/event.hxx> + +#include <ftnidx.hxx> +#include <editeng/opaqitem.hxx> +#include <SwSmartTagMgr.hxx> +#include <sal/log.hxx> + +#include <layact.hxx> +#include <swwait.hxx> +#include <fmtsrnd.hxx> +#include <docsh.hxx> + +#include <anchoreddrawobject.hxx> +#include <ndtxt.hxx> +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrms.hxx> +#include <mdiexp.hxx> +#include <sectfrm.hxx> +#include <acmplwrd.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <fntcache.hxx> +#include <vector> +#include <tools/diagnose_ex.h> + +// Save some typing work to avoid accessing destroyed pages. +#define XCHECKPAGE \ + { if ( IsAgain() ) \ + { \ + if( bNoLoop ) \ + rLayoutAccess.GetLayouter()->EndLoopControl(); \ + return; \ + } \ + } + +void SwLayAction::CheckWaitCursor() +{ + if (IsReschedule()) + { + ::RescheduleProgress(m_pImp->GetShell()->GetDoc()->GetDocShell()); + } + if ( !m_pWait && IsWaitAllowed() && IsPaint() && + ((std::clock() - m_nStartTicks) * 1000 / CLOCKS_PER_SEC >= CLOCKS_PER_SEC/2) ) + { + m_pWait.reset( new SwWait( *m_pRoot->GetFormat()->GetDoc()->GetDocShell(), true ) ); + } +} + +void SwLayAction::SetStatBar( bool bNew ) +{ + if ( bNew ) + { + m_nEndPage = m_pRoot->GetPageNum(); + m_nEndPage += m_nEndPage * 10 / 100; + } + else + m_nEndPage = USHRT_MAX; +} + +bool SwLayAction::PaintWithoutFlys( const SwRect &rRect, const SwContentFrame *pCnt, + const SwPageFrame *pPage ) +{ + SwRegionRects aTmp( rRect ); + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + const SwFlyFrame *pSelfFly = pCnt->FindFlyFrame(); + + for ( size_t i = 0; i < rObjs.size() && !aTmp.empty(); ++i ) + { + SdrObject *pO = rObjs[i]->DrawObj(); + if ( dynamic_cast< const SwVirtFlyDrawObj *>( pO ) == nullptr ) + continue; + + // do not consider invisible objects + const IDocumentDrawModelAccess& rIDDMA = pPage->GetFormat()->getIDocumentDrawModelAccess(); + if ( !rIDDMA.IsVisibleLayerId( pO->GetLayer() ) ) + { + continue; + } + + SwFlyFrame *pFly = static_cast<SwVirtFlyDrawObj*>(pO)->GetFlyFrame(); + + if ( pFly == pSelfFly || !rRect.IsOver( pFly->getFrameArea() ) ) + continue; + + if ( pSelfFly && pSelfFly->IsLowerOf( pFly ) ) + continue; + + if ( pFly->GetVirtDrawObj()->GetLayer() == rIDDMA.GetHellId() ) + continue; + + if ( pSelfFly ) + { + const SdrObject *pTmp = pSelfFly->GetVirtDrawObj(); + if ( pO->GetLayer() == pTmp->GetLayer() ) + { + if ( pO->GetOrdNumDirect() < pTmp->GetOrdNumDirect() ) + // Only look at things above us, if inside the same layer + continue; + } + else + { + const bool bLowerOfSelf = pFly->IsLowerOf( pSelfFly ); + if ( !bLowerOfSelf && !pFly->GetFormat()->GetOpaque().GetValue() ) + // Things from other layers are only interesting to us if + // they're not transparent or lie inwards + continue; + } + } + + // Fly frame without a lower have to be subtracted from paint region. + // For checking, if fly frame contains transparent graphic or + // has surrounded contour, assure that fly frame has a lower + if ( pFly->Lower() && + pFly->Lower()->IsNoTextFrame() && + ( static_cast<SwNoTextFrame*>(pFly->Lower())->IsTransparent() || + pFly->GetFormat()->GetSurround().IsContour() ) + ) + { + continue; + } + + // vcl::Region of a fly frame with transparent background or a transparent + // shadow have not to be subtracted from paint region + if ( pFly->IsBackgroundTransparent() ) + { + continue; + } + + aTmp -= pFly->getFrameArea(); + } + + bool bRetPaint = false; + for ( const auto& rRegionRect : aTmp ) + bRetPaint |= m_pImp->GetShell()->AddPaintRect( rRegionRect ); + return bRetPaint; +} + +inline bool SwLayAction::PaintContent_( const SwContentFrame *pContent, + const SwPageFrame *pPage, + const SwRect &rRect ) +{ + if ( rRect.HasArea() ) + { + if ( pPage->GetSortedObjs() ) + return PaintWithoutFlys( rRect, pContent, pPage ); + else + return m_pImp->GetShell()->AddPaintRect( rRect ); + } + return false; +} + +/** + * Depending of the type, the Content is output according to its changes, or the area + * to be outputted is registered with the region, respectively. + */ +void SwLayAction::PaintContent( const SwContentFrame *pCnt, + const SwPageFrame *pPage, + const SwRect &rOldRect, + long nOldBottom ) +{ + SwRectFnSet aRectFnSet(pCnt); + + if ( pCnt->IsCompletePaint() || !pCnt->IsTextFrame() ) + { + SwRect aPaint( pCnt->GetPaintArea() ); + if ( !PaintContent_( pCnt, pPage, aPaint ) ) + pCnt->ResetCompletePaint(); + } + else + { + // paint the area between printing bottom and frame bottom and + // the area left and right beside the frame, if its height changed. + long nOldHeight = aRectFnSet.GetHeight(rOldRect); + long nNewHeight = aRectFnSet.GetHeight(pCnt->getFrameArea()); + const bool bHeightDiff = nOldHeight != nNewHeight; + if( bHeightDiff ) + { + // consider whole potential paint area. + SwRect aDrawRect( pCnt->GetPaintArea() ); + if( nOldHeight > nNewHeight ) + nOldBottom = aRectFnSet.GetPrtBottom(*pCnt); + aRectFnSet.SetTop( aDrawRect, nOldBottom ); + PaintContent_( pCnt, pPage, aDrawRect ); + } + // paint content area + SwRect aPaintRect = static_cast<SwTextFrame*>(const_cast<SwContentFrame*>(pCnt))->GetPaintSwRect(); + PaintContent_( pCnt, pPage, aPaintRect ); + } + + if ( pCnt->IsRetouche() && !pCnt->GetNext() ) + { + const SwFrame *pTmp = pCnt; + if( pCnt->IsInSct() ) + { + const SwSectionFrame* pSct = pCnt->FindSctFrame(); + if( pSct->IsRetouche() && !pSct->GetNext() ) + pTmp = pSct; + } + SwRect aRect( pTmp->GetUpper()->GetPaintArea() ); + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTmp) ); + if ( !PaintContent_( pCnt, pPage, aRect ) ) + pCnt->ResetRetouche(); + } +} + +SwLayAction::SwLayAction(SwRootFrame *pRt, SwViewShellImp *pI, TaskStopwatch* pWatch) + : m_pRoot(pRt), + m_pImp( pI ), + m_pWatch(pWatch), + m_pOptTab( nullptr ), + m_nPreInvaPage( USHRT_MAX ), + m_nStartTicks( std::clock() ), + m_nEndPage( USHRT_MAX ), + m_nCheckPageNum( USHRT_MAX ), + m_pCurPage( nullptr ), + m_nTabLevel( 0 ), + m_nCallCount( 0 ) +{ + m_bPaintExtraData = ::IsExtraData( m_pImp->GetShell()->GetDoc() ); + m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true; + m_bInterrupt = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bReschedule = + m_bUpdateExpFields = m_bBrowseActionStop = m_bActionInProgress = false; + // init new flag <mbFormatContentOnInterrupt>. + mbFormatContentOnInterrupt = false; + + assert(!m_pImp->m_pLayAction); // there can be only one SwLayAction + m_pImp->m_pLayAction = this; // register there +} + +SwLayAction::~SwLayAction() +{ + OSL_ENSURE( !m_pWait, "Wait object not destroyed" ); + m_pImp->m_pLayAction = nullptr; // unregister +} + +bool SwLayAction::IsInterrupt() +{ + return m_bInterrupt || (m_pWatch && m_pWatch->exceededRuntime()); +} + +void SwLayAction::Reset() +{ + m_pOptTab = nullptr; + m_nStartTicks = std::clock(); + m_nEndPage = m_nPreInvaPage = m_nCheckPageNum = USHRT_MAX; + m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true; + m_bInterrupt = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bReschedule = + m_bUpdateExpFields = m_bBrowseActionStop = false; + m_pCurPage = nullptr; +} + +bool SwLayAction::RemoveEmptyBrowserPages() +{ + // switching from the normal to the browser mode, empty pages may be + // retained for an annoyingly long time, so delete them here + bool bRet = false; + const SwViewShell *pSh = m_pRoot->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + SwPageFrame *pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + do + { + if ( (pPage->GetSortedObjs() && pPage->GetSortedObjs()->size()) || + pPage->ContainsContent() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + else + { + bRet = true; + SwPageFrame *pDel = pPage; + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + } while ( pPage ); + } + return bRet; +} + +void SwLayAction::Action(OutputDevice* pRenderContext) +{ + m_bActionInProgress = true; + + //TurboMode? Hands-off during idle-format + if ( IsPaint() && !IsIdle() && TurboAction() ) + { + m_pWait.reset(); + m_pRoot->ResetTurboFlag(); + m_bActionInProgress = false; + m_pRoot->DeleteEmptySct(); + return; + } + else if ( m_pRoot->GetTurbo() ) + { + m_pRoot->DisallowTurbo(); + const SwFrame *pFrame = m_pRoot->GetTurbo(); + m_pRoot->ResetTurbo(); + pFrame->InvalidatePage(); + } + m_pRoot->DisallowTurbo(); + + if ( IsCalcLayout() ) + SetCheckPages( false ); + + InternalAction(pRenderContext); + m_bAgain |= RemoveEmptyBrowserPages(); + while ( IsAgain() ) + { + m_bAgain = m_bNextCycle = false; + InternalAction(pRenderContext); + m_bAgain |= RemoveEmptyBrowserPages(); + } + m_pRoot->DeleteEmptySct(); + + m_pWait.reset(); + + //Turbo-Action permitted again for all cases. + m_pRoot->ResetTurboFlag(); + m_pRoot->ResetTurbo(); + + SetCheckPages( true ); + + m_bActionInProgress = false; +} + +SwPageFrame* SwLayAction::CheckFirstVisPage( SwPageFrame *pPage ) +{ + SwContentFrame *pCnt = pPage->FindFirstBodyContent(); + SwContentFrame *pChk = pCnt; + bool bPageChgd = false; + while ( pCnt && pCnt->IsFollow() ) + pCnt = pCnt->FindMaster(); + if ( pCnt && pChk != pCnt ) + { bPageChgd = true; + pPage = pCnt->FindPageFrame(); + } + + if ( !pPage->GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + { + SwFootnoteContFrame *pCont = pPage->FindFootnoteCont(); + if ( pCont ) + { + pCnt = pCont->ContainsContent(); + pChk = pCnt; + while ( pCnt && pCnt->IsFollow() ) + pCnt = static_cast<SwContentFrame*>(pCnt->FindPrev()); + if ( pCnt && pCnt != pChk ) + { + if ( bPageChgd ) + { + // Use the 'topmost' page + SwPageFrame *pTmp = pCnt->FindPageFrame(); + if ( pPage->GetPhyPageNum() > pTmp->GetPhyPageNum() ) + pPage = pTmp; + } + else + pPage = pCnt->FindPageFrame(); + } + } + } + return pPage; +} + +// unlock position on start and end of page +// layout process. +static void unlockPositionOfObjects( SwPageFrame *pPageFrame ) +{ + assert( pPageFrame ); + + SwSortedObjs* pObjs = pPageFrame->GetSortedObjs(); + if ( pObjs ) + { + for (SwAnchoredObject* pObj : *pObjs) + { + pObj->UnlockPosition(); + } + } +} + +void SwLayAction::InternalAction(OutputDevice* pRenderContext) +{ + OSL_ENSURE( m_pRoot->Lower()->IsPageFrame(), ":-( No page below the root."); + + m_pRoot->Calc(pRenderContext); + + // Figure out the first invalid page or the first one to be formatted, + // respectively. A complete-action means the first invalid page. + // However, the first page to be formatted might be the one having the + // number 1. If we're doing a fake formatting, the number of the first + // page is the number of the first visible page. + SwPageFrame *pPage = IsComplete() ? static_cast<SwPageFrame*>(m_pRoot->Lower()) : + m_pImp->GetFirstVisPage(pRenderContext); + if ( !pPage ) + pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + + // If there's a first-flow-Content in the first visible page that's also a Follow, + // we switch the page back to the original master of that Content. + if ( !IsComplete() ) + pPage = CheckFirstVisPage( pPage ); + sal_uInt16 nFirstPageNum = pPage->GetPhyPageNum(); + + while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + IDocumentLayoutAccess& rLayoutAccess = m_pRoot->GetFormat()->getIDocumentLayoutAccess(); + bool bNoLoop = pPage && SwLayouter::StartLoopControl( m_pRoot->GetFormat()->GetDoc(), pPage ); + sal_uInt16 nPercentPageNum = 0; + while ((!IsInterrupt() && pPage) || (m_nCheckPageNum != USHRT_MAX)) + { + if (!pPage && m_nCheckPageNum != USHRT_MAX) + { + SwPageFrame *pPg = static_cast<SwPageFrame*>(m_pRoot->Lower()); + while (pPg && pPg->GetPhyPageNum() < m_nCheckPageNum) + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + if (pPg) + pPage = pPg; + if (!pPage) + break; + + SwPageFrame *pTmp = pPage->GetPrev() ? + static_cast<SwPageFrame*>(pPage->GetPrev()) : pPage; + SetCheckPages( true ); + SwFrame::CheckPageDescs( pPage, true, &pTmp ); + SetCheckPages( false ); + m_nCheckPageNum = USHRT_MAX; + pPage = pTmp; + continue; + } + + if ( m_nEndPage != USHRT_MAX && pPage->GetPhyPageNum() > nPercentPageNum ) + { + nPercentPageNum = pPage->GetPhyPageNum(); + ::SetProgressState( nPercentPageNum, m_pImp->GetShell()->GetDoc()->GetDocShell()); + } + m_pOptTab = nullptr; + + // No Shortcut for Idle or CalcLayout + if ( !IsIdle() && !IsComplete() && IsShortCut( pPage ) ) + { + m_pRoot->DeleteEmptySct(); + XCHECKPAGE; + if ( !IsInterrupt() && + (m_pRoot->IsSuperfluous() || m_pRoot->IsAssertFlyPages()) ) + { + if ( m_pRoot->IsAssertFlyPages() ) + m_pRoot->AssertFlyPages(); + if ( m_pRoot->IsSuperfluous() ) + { + bool bOld = IsAgain(); + m_pRoot->RemoveSuperfluous(); + m_bAgain = bOld; + } + if ( IsAgain() ) + { + if( bNoLoop ) + rLayoutAccess.GetLayouter()->EndLoopControl(); + return; + } + pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + while ( pPage && pPage->GetNext() && + pPage->GetPhyPageNum() < nFirstPageNum ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + continue; + } + break; + } + else + { + m_pRoot->DeleteEmptySct(); + XCHECKPAGE; + + while ( !IsInterrupt() && !IsNextCycle() && + ((pPage->GetSortedObjs() && pPage->IsInvalidFly()) || pPage->IsInvalid()) ) + { + unlockPositionOfObjects( pPage ); + + SwObjectFormatter::FormatObjsAtFrame( *pPage, *pPage, this ); + if ( !pPage->GetSortedObjs() ) + { + // If there are no (more) Flys, the flags are superfluous. + pPage->ValidateFlyLayout(); + pPage->ValidateFlyContent(); + } + // change condition + while ( !IsInterrupt() && !IsNextCycle() && + ( pPage->IsInvalid() || + (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) ) + { + PROTOCOL( pPage, PROT::FileInit, DbgAction::NONE, nullptr) + XCHECKPAGE; + + // new loop control + int nLoopControlRuns_1 = 0; + const int nLoopControlMax = 20; + + while ( !IsNextCycle() && pPage->IsInvalidLayout() ) + { + pPage->ValidateLayout(); + + if ( ++nLoopControlRuns_1 > nLoopControlMax ) + { + OSL_FAIL( "LoopControl_1 in SwLayAction::InternalAction" ); + break; + } + + FormatLayout( pRenderContext, pPage ); + XCHECKPAGE; + } + // change condition + if ( !IsNextCycle() && + ( pPage->IsInvalidContent() || + (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) ) + { + pPage->ValidateFlyInCnt(); + pPage->ValidateContent(); + pPage->ValidateFlyLayout(); + pPage->ValidateFlyContent(); + if ( !FormatContent( pPage ) ) + { + XCHECKPAGE; + pPage->InvalidateContent(); + pPage->InvalidateFlyInCnt(); + pPage->InvalidateFlyLayout(); + pPage->InvalidateFlyContent(); + if ( IsBrowseActionStop() ) + m_bInterrupt = true; + } + } + if( bNoLoop ) + rLayoutAccess.GetLayouter()->LoopControl( pPage ); + } + + unlockPositionOfObjects( pPage ); + } + + // A previous page may be invalid again. + XCHECKPAGE; + if ( !pPage->GetSortedObjs() ) + { + // If there are no (more) Flys, the flags are superfluous. + pPage->ValidateFlyLayout(); + pPage->ValidateFlyContent(); + } + + if (!IsInterrupt()) + { + SetNextCycle( false ); + + if ( m_nPreInvaPage != USHRT_MAX ) + { + if( !IsComplete() && m_nPreInvaPage + 2 < nFirstPageNum ) + { + m_pImp->SetFirstVisPageInvalid(); + SwPageFrame *pTmpPage = m_pImp->GetFirstVisPage(pRenderContext); + nFirstPageNum = pTmpPage->GetPhyPageNum(); + if( m_nPreInvaPage < nFirstPageNum ) + { + m_nPreInvaPage = nFirstPageNum; + pPage = pTmpPage; + } + } + while ( pPage->GetPrev() && pPage->GetPhyPageNum() > m_nPreInvaPage ) + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + m_nPreInvaPage = USHRT_MAX; + } + + while ( pPage->GetPrev() && + ( static_cast<SwPageFrame*>(pPage->GetPrev())->IsInvalid() || + ( static_cast<SwPageFrame*>(pPage->GetPrev())->GetSortedObjs() && + static_cast<SwPageFrame*>(pPage->GetPrev())->IsInvalidFly())) && + (static_cast<SwPageFrame*>(pPage->GetPrev())->GetPhyPageNum() >= + nFirstPageNum) ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + } + + // Continue to the next invalid page + while ( pPage && !pPage->IsInvalid() && + (!pPage->GetSortedObjs() || !pPage->IsInvalidFly()) ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + if( bNoLoop ) + rLayoutAccess.GetLayouter()->LoopControl( pPage ); + } + } + + if ( !pPage && !IsInterrupt() && + (m_pRoot->IsSuperfluous() || m_pRoot->IsAssertFlyPages()) ) + { + if ( m_pRoot->IsAssertFlyPages() ) + m_pRoot->AssertFlyPages(); + if ( m_pRoot->IsSuperfluous() ) + { + bool bOld = IsAgain(); + m_pRoot->RemoveSuperfluous(); + m_bAgain = bOld; + } + if ( IsAgain() ) + { + if( bNoLoop ) + rLayoutAccess.GetLayouter()->EndLoopControl(); + return; + } + pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + while ( pPage && pPage->GetNext() && + pPage->GetPhyPageNum() < nFirstPageNum ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + } + + if ( IsInterrupt() && pPage ) + { + // If we have input, we don't want to format content anymore, but + // we still should clean the layout. + // Otherwise, the following situation might arise: + // The user enters some text at the end of the paragraph of the last + // page, causing the paragraph to create a Follow for the next page. + // Meanwhile the user continues typing, so we have input while + // still formatting. + // The paragraph on the new page has already been partially formatted, + // and the new page has been fully formatted and is set to CompletePaint, + // but hasn't added itself to the area to be output. Then we paint, + // the CompletePaint of the page is reset because the new paragraph + // already added itself, but the borders of the page haven't been painted + // yet. + // Oh well, with the inevitable following LayAction, the page doesn't + // register itself, because it's (LayoutFrame) flags have been reset + // already - the border of the page will never be painted. + SwPageFrame *pPg = pPage; + XCHECKPAGE; + const SwRect &rVis = m_pImp->GetShell()->VisArea(); + + while( pPg && pPg->getFrameArea().Bottom() < rVis.Top() ) + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + if( pPg != pPage ) + pPg = pPg ? static_cast<SwPageFrame*>(pPg->GetPrev()) : pPage; + + // set flag for interrupt content formatting + mbFormatContentOnInterrupt = IsInterrupt(); + long nBottom = rVis.Bottom(); + // #i42586# - format current page, if idle action is active + // This is an optimization for the case that the interrupt is created by + // the move of a form control object, which is represented by a window. + while ( pPg && ( pPg->getFrameArea().Top() < nBottom || + ( IsIdle() && pPg == pPage ) ) ) + { + unlockPositionOfObjects( pPg ); + + XCHECKPAGE; + + // new loop control + int nLoopControlRuns_2 = 0; + const int nLoopControlMax = 20; + + // special case: interrupt content formatting + // conditions are incorrect and are too strict. + // adjust interrupt formatting to normal page formatting - see above. + while ( ( mbFormatContentOnInterrupt && + ( pPg->IsInvalid() || + ( pPg->GetSortedObjs() && pPg->IsInvalidFly() ) ) ) || + ( !mbFormatContentOnInterrupt && pPg->IsInvalidLayout() ) ) + { + XCHECKPAGE; + // format also at-page anchored objects + SwObjectFormatter::FormatObjsAtFrame( *pPg, *pPg, this ); + if ( !pPg->GetSortedObjs() ) + { + pPg->ValidateFlyLayout(); + pPg->ValidateFlyContent(); + } + + // new loop control + int nLoopControlRuns_3 = 0; + + while ( pPg->IsInvalidLayout() ) + { + pPg->ValidateLayout(); + + if ( ++nLoopControlRuns_3 > nLoopControlMax ) + { + OSL_FAIL( "LoopControl_3 in Interrupt formatting in SwLayAction::InternalAction" ); + break; + } + + FormatLayout( pRenderContext, pPg ); + XCHECKPAGE; + } + + if ( mbFormatContentOnInterrupt && + ( pPg->IsInvalidContent() || + ( pPg->GetSortedObjs() && pPg->IsInvalidFly() ) ) ) + { + pPg->ValidateFlyInCnt(); + pPg->ValidateContent(); + pPg->ValidateFlyLayout(); + pPg->ValidateFlyContent(); + + if ( ++nLoopControlRuns_2 > nLoopControlMax ) + { + OSL_FAIL( "LoopControl_2 in Interrupt formatting in SwLayAction::InternalAction" ); + break; + } + + if ( !FormatContent( pPg ) ) + { + XCHECKPAGE; + pPg->InvalidateContent(); + pPg->InvalidateFlyInCnt(); + pPg->InvalidateFlyLayout(); + pPg->InvalidateFlyContent(); + } + // we are satisfied if the content is formatted once complete. + else + { + break; + } + } + } + + unlockPositionOfObjects( pPg ); + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } + // reset flag for special interrupt content formatting. + mbFormatContentOnInterrupt = false; + } + m_pOptTab = nullptr; + if( bNoLoop ) + rLayoutAccess.GetLayouter()->EndLoopControl(); +} + +bool SwLayAction::TurboAction_( const SwContentFrame *pCnt ) +{ + const SwPageFrame *pPage = nullptr; + if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() || pCnt->IsRetouche() ) + { + const SwRect aOldRect( pCnt->UnionFrame( true ) ); + const long nOldBottom = pCnt->getFrameArea().Top() + pCnt->getFramePrintArea().Bottom(); + pCnt->Calc(m_pImp->GetShell()->GetOut()); + if ( pCnt->getFrameArea().Bottom() < aOldRect.Bottom() ) + pCnt->SetRetouche(); + + pPage = pCnt->FindPageFrame(); + PaintContent( pCnt, pPage, aOldRect, nOldBottom ); + + if ( !pCnt->GetValidLineNumFlag() && pCnt->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pCnt)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCnt))->RecalcAllLines(); + if ( nAllLines != static_cast<const SwTextFrame*>(pCnt)->GetAllLines() ) + { + if ( IsPaintExtraData() ) + m_pImp->GetShell()->AddPaintRect( pCnt->getFrameArea() ); + // This is to calculate the remaining LineNums on the page, + // and we don't stop processing here. To perform this inside RecalcAllLines + // would be expensive, because we would have to notify the page even + // in unnecessary cases (normal actions). + const SwContentFrame *pNxt = pCnt->GetNextContentFrame(); + while ( pNxt && + (pNxt->IsInTab() || pNxt->IsInDocBody() != pCnt->IsInDocBody()) ) + pNxt = pNxt->GetNextContentFrame(); + if ( pNxt ) + pNxt->InvalidatePage(); + } + return false; + } + + if ( pPage->IsInvalidLayout() || (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) + return false; + } + if ( !pPage ) + pPage = pCnt->FindPageFrame(); + + // format floating screen objects at content frame. + if ( pCnt->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pCnt), + *pPage, this ) ) + { + return false; + } + + if ( pPage->IsInvalidContent() ) + return false; + return true; +} + +bool SwLayAction::TurboAction() +{ + bool bRet = true; + + if ( m_pRoot->GetTurbo() ) + { + if ( !TurboAction_( m_pRoot->GetTurbo() ) ) + bRet = false; + m_pRoot->ResetTurbo(); + } + else + bRet = false; + return bRet; +} + +static bool lcl_IsInvaLay( const SwFrame *pFrame, long nBottom ) +{ + return !pFrame->isFrameAreaDefinitionValid() || + (pFrame->IsCompletePaint() && ( pFrame->getFrameArea().Top() < nBottom ) ); +} + +static const SwFrame *lcl_FindFirstInvaLay( const SwFrame *pFrame, long nBottom ) +{ + OSL_ENSURE( pFrame->IsLayoutFrame(), "FindFirstInvaLay, no LayFrame" ); + + if (lcl_IsInvaLay(pFrame, nBottom)) + return pFrame; + pFrame = static_cast<const SwLayoutFrame*>(pFrame)->Lower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + { + if (lcl_IsInvaLay(pFrame, nBottom)) + return pFrame; + const SwFrame *pTmp; + if ( nullptr != (pTmp = lcl_FindFirstInvaLay( pFrame, nBottom )) ) + return pTmp; + } + pFrame = pFrame->GetNext(); + } + return nullptr; +} + +static const SwFrame *lcl_FindFirstInvaContent( const SwLayoutFrame *pLay, long nBottom, + const SwContentFrame *pFirst ) +{ + const SwContentFrame *pCnt = pFirst ? pFirst->GetNextContentFrame() : + pLay->ContainsContent(); + while ( pCnt ) + { + if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() ) + { + if ( pCnt->getFrameArea().Top() <= nBottom ) + return pCnt; + } + + if ( pCnt->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pObj ) ) + { + if ( pFly->IsFlyInContentFrame() ) + { + if ( static_cast<const SwFlyInContentFrame*>(pFly)->IsInvalid() || + pFly->IsCompletePaint() ) + { + if ( pFly->getFrameArea().Top() <= nBottom ) + return pFly; + } + const SwFrame *pFrame = lcl_FindFirstInvaContent( pFly, nBottom, nullptr ); + if ( pFrame && pFrame->getFrameArea().Bottom() <= nBottom ) + return pFrame; + } + } + } + } + if ( pCnt->getFrameArea().Top() > nBottom && !pCnt->IsInTab() ) + return nullptr; + pCnt = pCnt->GetNextContentFrame(); + if ( !pLay->IsAnLower( pCnt ) ) + break; + } + return nullptr; +} + +// consider drawing objects +static const SwAnchoredObject* lcl_FindFirstInvaObj( const SwPageFrame* _pPage, + long _nBottom ) +{ + OSL_ENSURE( _pPage->GetSortedObjs(), "FindFirstInvaObj, no Objs" ); + + for (SwAnchoredObject* pObj : *_pPage->GetSortedObjs()) + { + if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pObj ) ) + { + if ( pFly->getFrameArea().Top() <= _nBottom ) + { + if ( pFly->IsInvalid() || pFly->IsCompletePaint() ) + return pFly; + + const SwFrame* pTmp; + if ( nullptr != (pTmp = lcl_FindFirstInvaContent( pFly, _nBottom, nullptr )) && + pTmp->getFrameArea().Top() <= _nBottom ) + return pFly; + } + } + else if ( auto pDrawObject = dynamic_cast< const SwAnchoredDrawObject *>( pObj ) ) + { + if ( !pDrawObject->IsValidPos() ) + { + return pObj; + } + } + } + return nullptr; +} + +/* Returns True if the page lies directly below or right of the visible area. + * + * It's possible for things to change in such a way that the processing + * (of the caller!) has to continue with the predecessor of the passed page. + * The parameter might therefore get modified! + * For BrowseMode, you may even activate the ShortCut if the invalid content + * of the page lies below the visible area. + */ +bool SwLayAction::IsShortCut( SwPageFrame *&prPage ) +{ + vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut(); + bool bRet = false; + const SwViewShell *pSh = m_pRoot->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + + // If the page is not valid, we quickly format it, otherwise + // there's gonna be no end of trouble + if ( !prPage->isFrameAreaDefinitionValid() ) + { + if ( bBrowse ) + { + // format complete page + // Thus, loop on all lowers of the page <prPage>, instead of only + // format its first lower. + // NOTE: In online layout (bBrowse == true) a page can contain + // a header frame and/or a footer frame beside the body frame. + prPage->Calc(pRenderContext); + SwFrame* pPageLowerFrame = prPage->Lower(); + while ( pPageLowerFrame ) + { + pPageLowerFrame->Calc(pRenderContext); + pPageLowerFrame = pPageLowerFrame->GetNext(); + } + } + else + FormatLayout( pSh ? pSh->GetOut() : nullptr, prPage ); + if ( IsAgain() ) + return false; + } + + const SwRect &rVis = m_pImp->GetShell()->VisArea(); + if ( (prPage->getFrameArea().Top() >= rVis.Bottom()) || + (prPage->getFrameArea().Left()>= rVis.Right()) ) + { + bRet = true; + + // This is going to be a bit nasty: The first ContentFrame of this + // page in the Body text needs formatting; if it changes the page during + // that process, I need to start over a page further back, because we + // have been processing a PageBreak. + // Even more uncomfortable: The next ContentFrame must be formatted, + // because it's possible for empty pages to exist temporarily (for example + // a paragraph across multiple pages gets deleted or reduced in size). + + // This is irrelevant for the browser, if the last Cnt above it + // isn't visible anymore. + + const SwPageFrame *p2ndPage = prPage; + const SwContentFrame *pContent; + const SwLayoutFrame* pBody = p2ndPage->FindBodyCont(); + if( p2ndPage->IsFootnotePage() && pBody ) + pBody = static_cast<const SwLayoutFrame*>(pBody->GetNext()); + pContent = pBody ? pBody->ContainsContent() : nullptr; + while ( p2ndPage && !pContent ) + { + p2ndPage = static_cast<const SwPageFrame*>(p2ndPage->GetNext()); + if( p2ndPage ) + { + pBody = p2ndPage->FindBodyCont(); + if( p2ndPage->IsFootnotePage() && pBody ) + pBody = static_cast<const SwLayoutFrame*>(pBody->GetNext()); + pContent = pBody ? pBody->ContainsContent() : nullptr; + } + } + if ( pContent ) + { + bool bTstCnt = true; + if ( bBrowse ) + { + // Is the Cnt before already invisible? + const SwFrame *pLst = pContent; + if ( pLst->IsInTab() ) + pLst = pContent->FindTabFrame(); + if ( pLst->IsInSct() ) + pLst = pContent->FindSctFrame(); + pLst = pLst->FindPrev(); + if ( pLst && + (pLst->getFrameArea().Top() >= rVis.Bottom() || + pLst->getFrameArea().Left()>= rVis.Right()) ) + { + bTstCnt = false; + } + } + + if ( bTstCnt ) + { + // check after each frame calculation, + // if the content frame has changed the page. If yes, no other + // frame calculation is performed + bool bPageChg = false; + + if ( pContent->IsInSct() ) + { + const SwSectionFrame *pSct = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindSctFrame(); + if ( !pSct->isFrameAreaDefinitionValid() ) + { + pSct->Calc(pRenderContext); + pSct->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + } + + if ( !bPageChg && !pContent->isFrameAreaDefinitionValid() ) + { + pContent->Calc(pRenderContext); + pContent->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + + if ( !bPageChg && pContent->IsInTab() ) + { + const SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindTabFrame(); + if ( !pTab->isFrameAreaDefinitionValid() ) + { + pTab->Calc(pRenderContext); + pTab->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + } + + if ( !bPageChg && pContent->IsInSct() ) + { + const SwSectionFrame *pSct = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindSctFrame(); + if ( !pSct->isFrameAreaDefinitionValid() ) + { + pSct->Calc(pRenderContext); + pSct->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + } + + if ( bPageChg ) + { + bRet = false; + const SwPageFrame* pTmp = pContent->FindPageFrame(); + if ( pTmp->GetPhyPageNum() < prPage->GetPhyPageNum() && + pTmp->IsInvalid() ) + { + prPage = const_cast<SwPageFrame*>(pTmp); + } + else + { + prPage = static_cast<SwPageFrame*>(prPage->GetPrev()); + } + } + // no shortcut, if at previous page + // an anchored object is registered, whose anchor is <pContent>. + else if ( prPage->GetPrev() ) + { + SwSortedObjs* pObjs = + static_cast<SwPageFrame*>(prPage->GetPrev())->GetSortedObjs(); + if ( pObjs ) + { + for (SwAnchoredObject* pObj : *pObjs) + { + if ( pObj->GetAnchorFrameContainingAnchPos() == pContent ) + { + bRet = false; + break; + } + } + } + } + } + } + } + + if ( !bRet && bBrowse ) + { + const long nBottom = rVis.Bottom(); + const SwAnchoredObject* pObj( nullptr ); + if ( prPage->GetSortedObjs() && + (prPage->IsInvalidFlyLayout() || prPage->IsInvalidFlyContent()) && + nullptr != (pObj = lcl_FindFirstInvaObj( prPage, nBottom )) && + pObj->GetObjRect().Top() <= nBottom ) + { + return false; + } + const SwFrame* pFrame( nullptr ); + if ( prPage->IsInvalidLayout() && + nullptr != (pFrame = lcl_FindFirstInvaLay( prPage, nBottom )) && + pFrame->getFrameArea().Top() <= nBottom ) + { + return false; + } + if ( (prPage->IsInvalidContent() || prPage->IsInvalidFlyInCnt()) && + nullptr != (pFrame = lcl_FindFirstInvaContent( prPage, nBottom, nullptr )) && + pFrame->getFrameArea().Top() <= nBottom ) + { + return false; + } + bRet = true; + } + return bRet; +} + +// introduce support for vertical layout +bool SwLayAction::FormatLayout( OutputDevice *pRenderContext, SwLayoutFrame *pLay, bool bAddRect ) +{ + // save page for loop control + if( pLay->IsPageFrame() && static_cast<SwPageFrame*>(pLay) != m_pCurPage ) + { + m_nCallCount = 0; + m_pCurPage = static_cast<SwPageFrame*>(pLay); + } + OSL_ENSURE( !IsAgain(), "Attention to the invalid page." ); + if ( IsAgain() ) + return false; + + bool bChanged = false; + bool bAlreadyPainted = false; + // remember frame at complete paint + SwRect aFrameAtCompletePaint; + + if ( !pLay->isFrameAreaDefinitionValid() || pLay->IsCompletePaint() ) + { + if ( pLay->GetPrev() && !pLay->GetPrev()->isFrameAreaDefinitionValid() ) + pLay->GetPrev()->SetCompletePaint(); + + SwRect aOldFrame( pLay->getFrameArea() ); + SwRect aOldRect( aOldFrame ); + if( pLay->IsPageFrame() ) + { + aOldRect = static_cast<SwPageFrame*>(pLay)->GetBoundRect(pRenderContext); + } + + { + SwFrameDeleteGuard aDeleteGuard(pLay); + pLay->Calc(pRenderContext); + } + + if ( aOldFrame != pLay->getFrameArea() ) + bChanged = true; + + bool bNoPaint = false; + if ( pLay->IsPageBodyFrame() && + pLay->getFrameArea().Pos() == aOldRect.Pos() && + pLay->Lower() ) + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + // Limitations because of headers / footers + if( pSh && pSh->GetViewOptions()->getBrowseMode() && + !( pLay->IsCompletePaint() && pLay->FindPageFrame()->FindFootnoteCont() ) ) + bNoPaint = true; + } + + if ( !bNoPaint && IsPaint() && bAddRect && (pLay->IsCompletePaint() || bChanged) ) + { + SwRect aPaint( pLay->getFrameArea() ); + // consider border and shadow for + // page frames -> enlarge paint rectangle correspondingly. + if ( pLay->IsPageFrame() ) + { + SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(pLay); + aPaint = pPageFrame->GetBoundRect(pRenderContext); + } + + bool bPageInBrowseMode = pLay->IsPageFrame(); + if( bPageInBrowseMode ) + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + if( !pSh || !pSh->GetViewOptions()->getBrowseMode() ) + bPageInBrowseMode = false; + } + if( bPageInBrowseMode ) + { + // NOTE: no vertical layout in online layout + // Is the change even visible? + if ( pLay->IsCompletePaint() ) + { + m_pImp->GetShell()->AddPaintRect( aPaint ); + bAddRect = false; + } + else + { + SwRegionRects aRegion( aOldRect ); + aRegion -= aPaint; + for ( size_t i = 0; i < aRegion.size(); ++i ) + m_pImp->GetShell()->AddPaintRect( aRegion[i] ); + aRegion.ChangeOrigin( aPaint ); + aRegion.clear(); + aRegion.push_back( aPaint ); + aRegion -= aOldRect; + for ( size_t i = 0; i < aRegion.size(); ++i ) + m_pImp->GetShell()->AddPaintRect( aRegion[i] ); + } + } + else + { + m_pImp->GetShell()->AddPaintRect( aPaint ); + bAlreadyPainted = true; + // remember frame at complete paint + aFrameAtCompletePaint = pLay->getFrameArea(); + } + + // provide paint of spacing + // between pages (not only for in online mode). + if ( pLay->IsPageFrame() ) + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + const SwTwips nHalfDocBorder = pSh ? pSh->GetViewOptions()->GetGapBetweenPages() + : SwViewOption::defGapBetweenPages; + const bool bLeftToRightViewLayout = m_pRoot->IsLeftToRightViewLayout(); + const bool bPrev = bLeftToRightViewLayout ? pLay->GetPrev() : pLay->GetNext(); + const bool bNext = bLeftToRightViewLayout ? pLay->GetNext() : pLay->GetPrev(); + SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(pLay); + SwRect aPageRect( pLay->getFrameArea() ); + + if(pSh) + { + SwPageFrame::GetBorderAndShadowBoundRect(aPageRect, pSh, + pRenderContext, + aPageRect, pPageFrame->IsLeftShadowNeeded(), pPageFrame->IsRightShadowNeeded(), + pPageFrame->SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT); + } + + if ( bPrev ) + { + // top + SwRect aSpaceToPrevPage( aPageRect ); + aSpaceToPrevPage.Top( aSpaceToPrevPage.Top() - nHalfDocBorder ); + aSpaceToPrevPage.Bottom( pLay->getFrameArea().Top() ); + if(!aSpaceToPrevPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToPrevPage ); + + // left + aSpaceToPrevPage = aPageRect; + aSpaceToPrevPage.Left( aSpaceToPrevPage.Left() - nHalfDocBorder ); + aSpaceToPrevPage.Right( pLay->getFrameArea().Left() ); + if(!aSpaceToPrevPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToPrevPage ); + } + if ( bNext ) + { + // bottom + SwRect aSpaceToNextPage( aPageRect ); + aSpaceToNextPage.Bottom( aSpaceToNextPage.Bottom() + nHalfDocBorder ); + aSpaceToNextPage.Top( pLay->getFrameArea().Bottom() ); + if(!aSpaceToNextPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToNextPage ); + + // right + aSpaceToNextPage = aPageRect; + aSpaceToNextPage.Right( aSpaceToNextPage.Right() + nHalfDocBorder ); + aSpaceToNextPage.Left( pLay->getFrameArea().Right() ); + if(!aSpaceToNextPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToNextPage ); + } + } + } + pLay->ResetCompletePaint(); + } + + if ( IsPaint() && bAddRect && + !pLay->GetNext() && pLay->IsRetoucheFrame() && pLay->IsRetouche() ) + { + // vertical layout support + SwRectFnSet aRectFnSet(pLay); + SwRect aRect( pLay->GetUpper()->GetPaintArea() ); + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pLay) ); + if ( !m_pImp->GetShell()->AddPaintRect( aRect ) ) + pLay->ResetRetouche(); + } + + if( bAlreadyPainted ) + bAddRect = false; + + CheckWaitCursor(); + + if ( IsAgain() ) + return false; + + // Now, deal with the lowers that are LayoutFrames + + if ( pLay->IsFootnoteFrame() ) // no LayFrames as Lower + return bChanged; + + SwFrame *pLow = pLay->Lower(); + bool bTabChanged = false; + while ( pLow && pLow->GetUpper() == pLay ) + { + SwFrame* pNext = nullptr; + if ( pLow->IsLayoutFrame() ) + { + if ( pLow->IsTabFrame() ) + { + // loop control for embedded tables + if ( m_nTabLevel > 0 && ++m_nCallCount > 50 ) { + static_cast<SwTabFrame*>(pLow)->SetSplitRowDisabled(); + } + + ++m_nTabLevel; + + // Remember what was the next of the lower. Formatting may move it to the previous + // page, in which case it looses its next. + pNext = pLow->GetNext(); + + if (pNext && pNext->IsTabFrame()) + { + auto pTab = static_cast<SwTabFrame*>(pNext); + if (pTab->IsFollow()) + { + // The next frame is a follow of the previous frame, SwTabFrame::Join() will + // delete this one as part of formatting, so forget about it. + pNext = nullptr; + } + } + + bTabChanged |= FormatLayoutTab( static_cast<SwTabFrame*>(pLow), bAddRect ); + --m_nTabLevel; + } + // Skip the ones already registered for deletion + else if( !pLow->IsSctFrame() || static_cast<SwSectionFrame*>(pLow)->GetSection() ) + bChanged |= FormatLayout( pRenderContext, static_cast<SwLayoutFrame*>(pLow), bAddRect ); + } + else if ( m_pImp->GetShell()->IsPaintLocked() ) + // Shortcut to minimize the cycles. With Lock, the + // paint is coming either way (primarily for browse) + pLow->OptCalc(); + + if ( IsAgain() ) + return false; + if (!pNext) + { + pNext = pLow->GetNext(); + } + pLow = pNext; + } + // add complete frame area as paint area, if frame + // area has been already added and after formatting its lowers the frame area + // is enlarged. + SwRect aBoundRect(pLay->IsPageFrame() ? static_cast<SwPageFrame*>(pLay)->GetBoundRect(pRenderContext) : pLay->getFrameArea() ); + + if ( bAlreadyPainted && + ( aBoundRect.Width() > aFrameAtCompletePaint.Width() || + aBoundRect.Height() > aFrameAtCompletePaint.Height() ) + ) + { + m_pImp->GetShell()->AddPaintRect( aBoundRect ); + } + return bChanged || bTabChanged; +} + +void SwLayAction::FormatLayoutFly( SwFlyFrame* pFly ) +{ + vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut(); + OSL_ENSURE( !IsAgain(), "Attention to the invalid page." ); + if ( IsAgain() ) + return; + + bool bChanged = false; + bool bAddRect = true; + + if ( !pFly->isFrameAreaDefinitionValid() || pFly->IsCompletePaint() || pFly->IsInvalid() ) + { + // The Frame has changed, now it's getting formatted. + const SwRect aOldRect( pFly->getFrameArea() ); + pFly->Calc(pRenderContext); + bChanged = aOldRect != pFly->getFrameArea(); + + if ( IsPaint() && (pFly->IsCompletePaint() || bChanged) && + pFly->getFrameArea().Top() > 0 && pFly->getFrameArea().Left() > 0 ) + m_pImp->GetShell()->AddPaintRect( pFly->getFrameArea() ); + + if ( bChanged ) + pFly->Invalidate(); + else + pFly->Validate(); + + bAddRect = false; + pFly->ResetCompletePaint(); + } + + if ( IsAgain() ) + return; + + // Now, deal with the lowers that are LayoutFrames + bool bTabChanged = false; + SwFrame *pLow = pFly->Lower(); + while ( pLow ) + { + if ( pLow->IsLayoutFrame() ) + { + if ( pLow->IsTabFrame() ) + bTabChanged |= FormatLayoutTab( static_cast<SwTabFrame*>(pLow), bAddRect ); + else + bChanged |= FormatLayout( m_pImp->GetShell()->GetOut(), static_cast<SwLayoutFrame*>(pLow), bAddRect ); + } + pLow = pLow->GetNext(); + } +} + +// Implement vertical layout support +bool SwLayAction::FormatLayoutTab( SwTabFrame *pTab, bool bAddRect ) +{ + OSL_ENSURE( !IsAgain(), "8-) Attention to the invalid page." ); + if ( IsAgain() || !pTab->Lower() ) + return false; + + vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut(); + IDocumentTimerAccess& rTimerAccess = m_pRoot->GetFormat()->getIDocumentTimerAccess(); + rTimerAccess.BlockIdling(); + + bool bChanged = false; + bool bPainted = false; + + const SwPageFrame *pOldPage = pTab->FindPageFrame(); + + // vertical layout support + SwRectFnSet aRectFnSet(pTab); + + if ( !pTab->isFrameAreaDefinitionValid() || pTab->IsCompletePaint() || pTab->IsComplete() ) + { + if ( pTab->GetPrev() && !pTab->GetPrev()->isFrameAreaDefinitionValid() ) + { + pTab->GetPrev()->SetCompletePaint(); + } + + const SwRect aOldRect( pTab->getFrameArea() ); + pTab->SetLowersFormatted( false ); + pTab->Calc(pRenderContext); + if ( aOldRect != pTab->getFrameArea() ) + { + bChanged = true; + } + const SwRect aPaintFrame = pTab->GetPaintArea(); + + if ( IsPaint() && bAddRect ) + { + // add condition <pTab->getFrameArea().HasArea()> + if ( !pTab->IsCompletePaint() && + pTab->IsComplete() && + ( pTab->getFrameArea().SSize() != pTab->getFramePrintArea().SSize() || + // vertical layout support + aRectFnSet.GetLeftMargin(*pTab) ) && + pTab->getFrameArea().HasArea() + ) + { + // re-implement calculation of margin rectangles. + SwRect aMarginRect; + + SwTwips nLeftMargin = aRectFnSet.GetLeftMargin(*pTab); + if ( nLeftMargin > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetWidth( aMarginRect, nLeftMargin ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + + if ( aRectFnSet.GetRightMargin(*pTab) > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetLeft( aMarginRect, aRectFnSet.GetPrtRight(*pTab) ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + + SwTwips nTopMargin = aRectFnSet.GetTopMargin(*pTab); + if ( nTopMargin > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetHeight( aMarginRect, nTopMargin ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + + if ( aRectFnSet.GetBottomMargin(*pTab) > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetTop( aMarginRect, aRectFnSet.GetPrtBottom(*pTab) ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + } + else if ( pTab->IsCompletePaint() ) + { + m_pImp->GetShell()->AddPaintRect( aPaintFrame ); + bAddRect = false; + bPainted = true; + } + + if ( pTab->IsRetouche() && !pTab->GetNext() ) + { + SwRect aRect( pTab->GetUpper()->GetPaintArea() ); + // vertical layout support + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTab) ); + if ( !m_pImp->GetShell()->AddPaintRect( aRect ) ) + pTab->ResetRetouche(); + } + } + else + bAddRect = false; + + if ( pTab->IsCompletePaint() && !m_pOptTab ) + m_pOptTab = pTab; + pTab->ResetCompletePaint(); + } + if ( IsPaint() && bAddRect && pTab->IsRetouche() && !pTab->GetNext() ) + { + // set correct rectangle for retouche: area between bottom of table frame + // and bottom of paint area of the upper frame. + SwRect aRect( pTab->GetUpper()->GetPaintArea() ); + // vertical layout support + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTab) ); + if ( !m_pImp->GetShell()->AddPaintRect( aRect ) ) + pTab->ResetRetouche(); + } + + CheckWaitCursor(); + + rTimerAccess.UnblockIdling(); + + // Ugly shortcut! + if ( pTab->IsLowersFormatted() && + (bPainted || !m_pImp->GetShell()->VisArea().IsOver( pTab->getFrameArea())) ) + return false; + + // Now, deal with the lowers + if ( IsAgain() ) + return false; + + // for safety reasons: + // check page number before formatting lowers. + if ( pOldPage->GetPhyPageNum() > (pTab->FindPageFrame()->GetPhyPageNum() + 1) ) + SetNextCycle( true ); + + // format lowers, only if table frame is valid + if ( pTab->isFrameAreaDefinitionValid() ) + { + FlowFrameJoinLockGuard tabG(pTab); // tdf#124675 prevent Join() if pTab becomes empty + SwLayoutFrame *pLow = static_cast<SwLayoutFrame*>(pTab->Lower()); + while ( pLow ) + { + SwFrameDeleteGuard rowG(pLow); // tdf#124675 prevent RemoveFollowFlowLine() + bChanged |= FormatLayout( m_pImp->GetShell()->GetOut(), pLow, bAddRect ); + if ( IsAgain() ) + return false; + pLow = static_cast<SwLayoutFrame*>(pLow->GetNext()); + } + } + + return bChanged; +} + +bool SwLayAction::FormatContent( const SwPageFrame *pPage ) +{ + const SwContentFrame *pContent = pPage->ContainsContent(); + const SwViewShell *pSh = m_pRoot->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + + while ( pContent && pPage->IsAnLower( pContent ) ) + { + // If the content didn't change, we can use a few shortcuts. + const bool bFull = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint() || + pContent->IsRetouche() || pContent->GetDrawObjs(); + if ( bFull ) + { + // We do this so we don't have to search later on. + const bool bNxtCnt = IsCalcLayout() && !pContent->GetFollow(); + const SwContentFrame *pContentNext = bNxtCnt ? pContent->GetNextContentFrame() : nullptr; + const SwContentFrame *pContentPrev = pContent->GetPrev() ? pContent->GetPrevContentFrame() : nullptr; + + const SwLayoutFrame*pOldUpper = pContent->GetUpper(); + const SwTabFrame *pTab = pContent->FindTabFrame(); + const bool bInValid = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint(); + const bool bOldPaint = IsPaint(); + m_bPaint = bOldPaint && !(pTab && pTab == m_pOptTab); + FormatContent_( pContent, pPage ); + // reset <bPaint> before format objects + m_bPaint = bOldPaint; + + // format floating screen object at content frame. + // No format, if action flag <bAgain> is set or action is interrupted. + // allow format on interruption of action, if + // it's the format for this interrupt + // pass correct page frame + // to the object formatter. + if ( !IsAgain() && + ( !IsInterrupt() || mbFormatContentOnInterrupt ) && + pContent->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pContent), + *(pContent->FindPageFrame()), this ) ) + { + return false; + } + + if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines(); + if ( IsPaintExtraData() && IsPaint() && + nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() ) + m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() ); + } + + if ( IsAgain() ) + return false; + + // Temporarily interrupt processing if layout or Flys become invalid again. + // However not for the BrowseView: The layout is getting invalid + // all the time because the page height gets adjusted. + // The same applies if the user wants to continue working and at least one + // paragraph has been processed. + if (!pTab || !bInValid) + { + // consider interrupt formatting. + if ( ( IsInterrupt() && !mbFormatContentOnInterrupt ) || + ( !bBrowse && pPage->IsInvalidLayout() ) || + // consider interrupt formatting + ( pPage->GetSortedObjs() && pPage->IsInvalidFly() && !mbFormatContentOnInterrupt ) + ) + return false; + } + if ( pOldUpper != pContent->GetUpper() ) + { + const sal_uInt16 nCurNum = pContent->FindPageFrame()->GetPhyPageNum(); + if ( nCurNum < pPage->GetPhyPageNum() ) + m_nPreInvaPage = nCurNum; + + // If the frame flowed backwards more than one page, we need to + // start over again from the beginning, so nothing gets left out. + if ( !IsCalcLayout() && pPage->GetPhyPageNum() > nCurNum+1 ) + { + SetNextCycle( true ); + // consider interrupt formatting + if ( !mbFormatContentOnInterrupt ) + { + return false; + } + } + } + // If the frame moved forwards to the next page, we re-run through + // the predecessor. + // This way, we catch predecessors which are now responsible for + // retouching, but the footers will be touched also. + bool bSetContent = true; + if ( pContentPrev ) + { + if ( !pContentPrev->isFrameAreaDefinitionValid() && pPage->IsAnLower( pContentPrev ) ) + { + pPage->InvalidateContent(); + } + + if ( pOldUpper != pContent->GetUpper() && + pPage->GetPhyPageNum() < pContent->FindPageFrame()->GetPhyPageNum() ) + { + pContent = pContentPrev; + bSetContent = false; + } + } + if ( bSetContent ) + { + if ( bBrowse && !IsIdle() && !IsCalcLayout() && !IsComplete() && + pContent->getFrameArea().Top() > m_pImp->GetShell()->VisArea().Bottom()) + { + const long nBottom = m_pImp->GetShell()->VisArea().Bottom(); + const SwFrame *pTmp = lcl_FindFirstInvaContent( pPage, + nBottom, pContent ); + if ( !pTmp ) + { + if ( (!(pPage->GetSortedObjs() && pPage->IsInvalidFly()) || + !lcl_FindFirstInvaObj( pPage, nBottom )) && + (!pPage->IsInvalidLayout() || + !lcl_FindFirstInvaLay( pPage, nBottom ))) + SetBrowseActionStop( true ); + // consider interrupt formatting. + if ( !mbFormatContentOnInterrupt ) + { + return false; + } + } + } + pContent = bNxtCnt ? pContentNext : pContent->GetNextContentFrame(); + } + + if (IsReschedule()) + { + ::RescheduleProgress(m_pImp->GetShell()->GetDoc()->GetDocShell()); + } + } + else + { + if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines(); + if ( IsPaintExtraData() && IsPaint() && + nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() ) + m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() ); + } + + // Do this if the frame has been formatted before. + if ( pContent->IsTextFrame() && static_cast<const SwTextFrame*>(pContent)->HasRepaint() && + IsPaint() ) + PaintContent( pContent, pPage, pContent->getFrameArea(), pContent->getFrameArea().Bottom()); + if ( IsIdle() ) + { + // consider interrupt formatting. + if ( IsInterrupt() && !mbFormatContentOnInterrupt ) + return false; + } + if ( bBrowse && !IsIdle() && !IsCalcLayout() && !IsComplete() && + pContent->getFrameArea().Top() > m_pImp->GetShell()->VisArea().Bottom()) + { + const long nBottom = m_pImp->GetShell()->VisArea().Bottom(); + const SwFrame *pTmp = lcl_FindFirstInvaContent( pPage, + nBottom, pContent ); + if ( !pTmp ) + { + if ( (!(pPage->GetSortedObjs() && pPage->IsInvalidFly()) || + !lcl_FindFirstInvaObj( pPage, nBottom )) && + (!pPage->IsInvalidLayout() || + !lcl_FindFirstInvaLay( pPage, nBottom ))) + SetBrowseActionStop( true ); + // consider interrupt formatting. + if ( !mbFormatContentOnInterrupt ) + { + return false; + } + } + } + pContent = pContent->GetNextContentFrame(); + } + } + CheckWaitCursor(); + // consider interrupt formatting. + return !IsInterrupt() || mbFormatContentOnInterrupt; +} + +void SwLayAction::FormatContent_( const SwContentFrame *pContent, const SwPageFrame *pPage ) +{ + // We probably only ended up here because the Content holds DrawObjects. + const bool bDrawObjsOnly = pContent->isFrameAreaDefinitionValid() && !pContent->IsCompletePaint() && !pContent->IsRetouche(); + SwRectFnSet aRectFnSet(pContent); + if ( !bDrawObjsOnly && IsPaint() ) + { + const SwRect aOldRect( pContent->UnionFrame() ); + const long nOldBottom = aRectFnSet.GetPrtBottom(*pContent); + pContent->OptCalc(); + if( IsAgain() ) + return; + if( aRectFnSet.YDiff( aRectFnSet.GetBottom(pContent->getFrameArea()), + aRectFnSet.GetBottom(aOldRect) ) < 0 ) + { + pContent->SetRetouche(); + } + PaintContent( pContent, pContent->FindPageFrame(), aOldRect, nOldBottom); + } + else + { + if ( IsPaint() && pContent->IsTextFrame() && static_cast<const SwTextFrame*>(pContent)->HasRepaint() ) + PaintContent( pContent, pPage, pContent->getFrameArea(), + aRectFnSet.GetBottom(pContent->getFrameArea()) ); + pContent->OptCalc(); + } +} + +void SwLayAction::FormatFlyContent( const SwFlyFrame *pFly ) +{ + const SwContentFrame *pContent = pFly->ContainsContent(); + + while ( pContent ) + { + FormatContent_( pContent, pContent->FindPageFrame() ); + + // format floating screen objects at content text frame + // pass correct page frame to the object formatter. + if ( pContent->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( + *const_cast<SwContentFrame*>(pContent), + *(pContent->FindPageFrame()), this ) ) + { + // restart format with first content + pContent = pFly->ContainsContent(); + continue; + } + + if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines(); + if ( IsPaintExtraData() && IsPaint() && + nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() ) + m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() ); + } + + if ( IsAgain() ) + return; + + // If there's input, we interrupt processing. + if ( !pFly->IsFlyInContentFrame() ) + { + // consider interrupt formatting. + if ( IsInterrupt() && !mbFormatContentOnInterrupt ) + return; + } + pContent = pContent->GetNextContentFrame(); + } + CheckWaitCursor(); +} + +bool SwLayIdle::IsInterrupt() +{ + return m_aWatch.exceededRuntime(); +} + +bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob ) +{ + OSL_ENSURE( pCnt->IsTextFrame(), "NoText neighbour of Text" ); + // robust against misuse by e.g. #i52542# + if( !pCnt->IsTextFrame() ) + return false; + + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pCnt)); + // sw_redlinehide: spell check only the nodes with visible content? + SwTextNode* pTextNode = const_cast<SwTextNode*>(pTextFrame->GetTextNodeForParaProps()); + + bool bProcess = false; + for (size_t i = 0; pTextNode; ) + { + switch ( eJob ) + { + case ONLINE_SPELLING : + bProcess = pTextNode->IsWrongDirty(); break; + case AUTOCOMPLETE_WORDS : + bProcess = pTextNode->IsAutoCompleteWordDirty(); break; + case WORD_COUNT : + bProcess = pTextNode->IsWordCountDirty(); break; + case SMART_TAGS : + bProcess = pTextNode->IsSmartTagDirty(); break; + } + if (bProcess) + { + break; + } + if (sw::MergedPara const* pMerged = pTextFrame->GetMergedPara()) + { + while (true) + { + ++i; + if (i < pMerged->extents.size()) + { + if (pMerged->extents[i].pNode != pTextNode) + { + pTextNode = pMerged->extents[i].pNode; + break; + } + } + else + { + pTextNode = nullptr; + break; + } + } + } + else + pTextNode = nullptr; + } + + if( bProcess ) + { + assert(pTextNode); + SwViewShell *pSh = pImp->GetShell(); + if( COMPLETE_STRING == nTextPos ) + { + --nTextPos; + if( dynamic_cast< const SwCursorShell *>( pSh ) != nullptr && !static_cast<SwCursorShell*>(pSh)->IsTableMode() ) + { + SwPaM *pCursor = static_cast<SwCursorShell*>(pSh)->GetCursor(); + if( !pCursor->HasMark() && !pCursor->IsMultiSelection() ) + { + pContentNode = pCursor->GetContentNode(); + nTextPos = pCursor->GetPoint()->nContent.GetIndex(); + } + } + } + sal_Int32 const nPos((pContentNode && pTextNode == pContentNode) + ? nTextPos + : COMPLETE_STRING); + + switch ( eJob ) + { + case ONLINE_SPELLING : + { + SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->AutoSpell_(*pTextNode, nPos) ); + // PENDING should stop idle spell checking + bPageValid = bPageValid && (SwTextNode::WrongState::TODO != pTextNode->GetWrongDirty()); + if ( aRepaint.HasArea() ) + pImp->GetShell()->InvalidateWindows( aRepaint ); + if (IsInterrupt()) + return true; + break; + } + case AUTOCOMPLETE_WORDS : + const_cast<SwTextFrame*>(pTextFrame)->CollectAutoCmplWrds(*pTextNode, nPos); + // note: bPageValid remains true here even if the cursor + // position is skipped, so no PENDING state needed currently + if (IsInterrupt()) + return true; + break; + case WORD_COUNT : + { + const sal_Int32 nEnd = pTextNode->GetText().getLength(); + SwDocStat aStat; + pTextNode->CountWords( aStat, 0, nEnd ); + if (IsInterrupt()) + return true; + break; + } + case SMART_TAGS : + { + try { + const SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->SmartTagScan(*pTextNode) ); + bPageValid = bPageValid && !pTextNode->IsSmartTagDirty(); + if ( aRepaint.HasArea() ) + pImp->GetShell()->InvalidateWindows( aRepaint ); + } catch( const css::uno::RuntimeException&) { + // handle smarttag problems gracefully and provide diagnostics + TOOLS_WARN_EXCEPTION( "sw.core", "SMART_TAGS"); + } + if (IsInterrupt()) + return true; + break; + } + } + } + + // The Flys that are anchored to the paragraph need to be considered too. + if ( pCnt->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( dynamic_cast< const SwFlyFrame *>( pObj ) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pObj); + if ( pFly->IsFlyInContentFrame() ) + { + const SwContentFrame *pC = pFly->ContainsContent(); + while( pC ) + { + if ( pC->IsTextFrame() ) + { + if ( DoIdleJob_( pC, eJob ) ) + return true; + } + pC = pC->GetNextContentFrame(); + } + } + } + } + } + return false; +} + +bool SwLayIdle::DoIdleJob( IdleJobType eJob, bool bVisAreaOnly ) +{ + // Spellcheck all contents of the pages. Either only the + // visible ones or all of them. + const SwViewShell* pViewShell = pImp->GetShell(); + const SwViewOption* pViewOptions = pViewShell->GetViewOptions(); + const SwDoc* pDoc = pViewShell->GetDoc(); + + switch ( eJob ) + { + case ONLINE_SPELLING : + if( !pViewOptions->IsOnlineSpell() ) + return false; + break; + case AUTOCOMPLETE_WORDS : + if( !SwViewOption::IsAutoCompleteWords() || + SwDoc::GetAutoCompleteWords().IsLockWordLstLocked()) + return false; + break; + case WORD_COUNT : + if ( !pViewShell->getIDocumentStatistics().GetDocStat().bModified ) + return false; + break; + case SMART_TAGS : + if ( pDoc->GetDocShell()->IsHelpDocument() || + pDoc->isXForms() || + !SwSmartTagMgr::Get().IsSmartTagsEnabled() ) + return false; + break; + default: OSL_FAIL( "Unknown idle job type" ); + } + + SwPageFrame *pPage; + if ( bVisAreaOnly ) + pPage = pImp->GetFirstVisPage(pViewShell->GetOut()); + else + pPage = static_cast<SwPageFrame*>(pRoot->Lower()); + + pContentNode = nullptr; + nTextPos = COMPLETE_STRING; + + while ( pPage ) + { + bPageValid = true; + const SwContentFrame *pCnt = pPage->ContainsContent(); + while( pCnt && pPage->IsAnLower( pCnt ) ) + { + if ( DoIdleJob_( pCnt, eJob ) ) + return true; + pCnt = pCnt->GetNextContentFrame(); + } + if ( pPage->GetSortedObjs() ) + { + for ( size_t i = 0; pPage->GetSortedObjs() && + i < pPage->GetSortedObjs()->size(); ++i ) + { + const SwAnchoredObject* pObj = (*pPage->GetSortedObjs())[i]; + if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pObj ) ) + { + const SwContentFrame *pC = pFly->ContainsContent(); + while( pC ) + { + if ( pC->IsTextFrame() ) + { + if ( DoIdleJob_( pC, eJob ) ) + return true; + } + pC = pC->GetNextContentFrame(); + } + } + } + } + + if( bPageValid ) + { + switch ( eJob ) + { + case ONLINE_SPELLING : pPage->ValidateSpelling(); break; + case AUTOCOMPLETE_WORDS : pPage->ValidateAutoCompleteWords(); break; + case WORD_COUNT : pPage->ValidateWordCount(); break; + case SMART_TAGS : pPage->ValidateSmartTags(); break; + } + } + + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + if ( pPage && bVisAreaOnly && + !pPage->getFrameArea().IsOver( pImp->GetShell()->VisArea())) + break; + } + return false; +} + +#if HAVE_FEATURE_DESKTOP && defined DBG_UTIL +void SwLayIdle::ShowIdle( Color eColor ) +{ + if ( !m_bIndicator ) + { + m_bIndicator = true; + vcl::Window *pWin = pImp->GetShell()->GetWin(); + if (pWin && !pWin->SupportsDoubleBuffering()) // FIXME make this work with double-buffering + { + tools::Rectangle aRect( 0, 0, 5, 5 ); + aRect = pWin->PixelToLogic( aRect ); + // Depending on if idle layout is in progress or not, draw a "red square" or a "green square". + pWin->Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + pWin->SetFillColor( eColor ); + pWin->SetLineColor(); + pWin->DrawRect( aRect ); + pWin->Pop(); + } + } +} +#define SHOW_IDLE( Color ) ShowIdle( Color ) +#else +#define SHOW_IDLE( Color ) +#endif // DBG_UTIL + +SwLayIdle::SwLayIdle( SwRootFrame *pRt, SwViewShellImp *pI ) : + pRoot( pRt ), + pImp( pI ) +#ifdef DBG_UTIL + , m_bIndicator( false ) +#endif +{ + SAL_INFO("sw.idle", "SwLayIdle() entry"); + + pImp->m_pIdleAct = this; + + SHOW_IDLE( COL_LIGHTRED ); + + pImp->GetShell()->EnableSmooth( false ); + + // First, spellcheck the visible area. Only if there's nothing + // to do there, we trigger the IdleFormat. + if ( !DoIdleJob( SMART_TAGS, true ) && + !DoIdleJob( ONLINE_SPELLING, true ) && + !DoIdleJob( AUTOCOMPLETE_WORDS, true ) ) + { + // Format, then register repaint rectangles with the SwViewShell if necessary. + // This requires running artificial actions, so we don't get undesired + // effects when for instance the page count gets changed. + // We remember the shells where the cursor is visible, so we can make + // it visible again if needed after a document change. + std::vector<bool> aBools; + for(SwViewShell& rSh : pImp->GetShell()->GetRingContainer()) + { + ++rSh.mnStartAction; + bool bVis = false; + if ( dynamic_cast<const SwCursorShell*>( &rSh) != nullptr ) + { + bVis = static_cast<SwCursorShell*>(&rSh)->GetCharRect().IsOver(rSh.VisArea()); + } + aBools.push_back( bVis ); + } + + bool bInterrupt(false); + { + SwLayAction aAction(pRoot, pImp, &m_aWatch); + aAction.SetWaitAllowed( false ); + aAction.Action(pImp->GetShell()->GetOut()); + bInterrupt = aAction.IsInterrupt(); + } + + // Further start/end actions only happen if there were paints started + // somewhere or if the visibility of the CharRects has changed. + bool bActions = false; + size_t nBoolIdx = 0; + for(SwViewShell& rSh : pImp->GetShell()->GetRingContainer()) + { + --rSh.mnStartAction; + + if ( rSh.Imp()->GetRegion() ) + bActions = true; + else + { + SwRect aTmp( rSh.VisArea() ); + rSh.UISizeNotify(); + + // Are we supposed to crash if rSh isn't a cursor shell?! + // bActions |= aTmp != rSh.VisArea() || + // aBools[nBoolIdx] != ((SwCursorShell*)&rSh)->GetCharRect().IsOver( rSh.VisArea() ); + + // aBools[ i ] is true, if the i-th shell is a cursor shell (!!!) + // and the cursor is visible. + bActions |= aTmp != rSh.VisArea(); + if ( aTmp == rSh.VisArea() && dynamic_cast<const SwCursorShell*>( &rSh) != nullptr ) + { + bActions |= aBools[nBoolIdx] != + static_cast<SwCursorShell*>(&rSh)->GetCharRect().IsOver( rSh.VisArea() ); + } + } + + ++nBoolIdx; + } + + if ( bActions ) + { + // Prepare start/end actions via CursorShell, so the cursor, selection + // and VisArea can be set correctly. + nBoolIdx = 0; + for(SwViewShell& rSh : pImp->GetShell()->GetRingContainer()) + { + SwCursorShell* pCursorShell = nullptr; + if(dynamic_cast<const SwCursorShell*>( &rSh) != nullptr) + pCursorShell = static_cast<SwCursorShell*>(&rSh); + + if ( pCursorShell ) + pCursorShell->SttCursorMove(); + + // If there are accrued paints, it's best to simply invalidate + // the whole window. Otherwise there would arise paint problems whose + // solution would be disproportionally expensive. + SwViewShellImp *pViewImp = rSh.Imp(); + bool bUnlock = false; + if ( pViewImp->GetRegion() ) + { + pViewImp->DelRegion(); + + // Cause a repaint with virtual device. + rSh.LockPaint(); + bUnlock = true; + } + + if ( pCursorShell ) + // If the Cursor was visible, we need to make it visible again. + // Otherwise, EndCursorMove with true for IdleEnd + pCursorShell->EndCursorMove( !aBools[nBoolIdx] ); + if( bUnlock ) + { + if( pCursorShell ) + { + // UnlockPaint overwrite the selection from the + // CursorShell and calls the virtual method paint + // to fill the virtual device. This fill don't have + // paint the selection! -> Set the focus flag at + // CursorShell and it doesn't paint the selection. + pCursorShell->ShellLoseFocus(); + pCursorShell->UnlockPaint( true ); + pCursorShell->ShellGetFocus(); + } + else + rSh.UnlockPaint( true ); + } + ++nBoolIdx; + + } + } + + if (!bInterrupt) + { + if ( !DoIdleJob( WORD_COUNT, false ) ) + if ( !DoIdleJob( SMART_TAGS, false ) ) + if ( !DoIdleJob( ONLINE_SPELLING, false ) ) + DoIdleJob( AUTOCOMPLETE_WORDS, false ); + } + + bool bInValid = false; + const SwViewOption& rVOpt = *pImp->GetShell()->GetViewOptions(); + const SwViewShell* pViewShell = pImp->GetShell(); + // See conditions in DoIdleJob() + const bool bSpell = rVOpt.IsOnlineSpell(); + const bool bACmplWrd = SwViewOption::IsAutoCompleteWords(); + const bool bWordCount = pViewShell->getIDocumentStatistics().GetDocStat().bModified; + const bool bSmartTags = !pViewShell->GetDoc()->GetDocShell()->IsHelpDocument() && + !pViewShell->GetDoc()->isXForms() && + SwSmartTagMgr::Get().IsSmartTagsEnabled(); + + SwPageFrame *pPg = static_cast<SwPageFrame*>(pRoot->Lower()); + do + { + bInValid = pPg->IsInvalidContent() || pPg->IsInvalidLayout() || + pPg->IsInvalidFlyContent() || pPg->IsInvalidFlyLayout() || + pPg->IsInvalidFlyInCnt() || + (bSpell && pPg->IsInvalidSpelling()) || + (bACmplWrd && pPg->IsInvalidAutoCompleteWords()) || + (bWordCount && pPg->IsInvalidWordCount()) || + (bSmartTags && pPg->IsInvalidSmartTags()); + + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + + } while ( pPg && !bInValid ); + + if ( !bInValid ) + { + pRoot->ResetIdleFormat(); + SfxObjectShell* pDocShell = pImp->GetShell()->GetDoc()->GetDocShell(); + pDocShell->Broadcast( SfxEventHint( SfxEventHintId::SwEventLayoutFinished, SwDocShell::GetEventName(STR_SW_EVENT_LAYOUT_FINISHED), pDocShell ) ); + // Limit lifetime of the text glyphs cache to a single run of the + // layout. + SwClearFntCacheTextGlyphs(); + } + } + + pImp->GetShell()->EnableSmooth( true ); + + if( pImp->IsAccessible() ) + pImp->FireAccessibleEvents(); + + SAL_INFO("sw.idle", "SwLayIdle() return"); + +#ifdef DBG_UTIL + if ( m_bIndicator && pImp->GetShell()->GetWin() ) + { + // Do not invalidate indicator, this may cause an endless loop. Instead, just repaint it + // This should be replaced by an overlay object in the future, anyways. Since it's only for debug + // purposes, it is not urgent. + m_bIndicator = false; SHOW_IDLE( COL_LIGHTGREEN ); + } +#endif +} + +SwLayIdle::~SwLayIdle() +{ + pImp->m_pIdleAct = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/laycache.cxx b/sw/source/core/layout/laycache.cxx new file mode 100644 index 000000000..d7c9cc72f --- /dev/null +++ b/sw/source/core/layout/laycache.cxx @@ -0,0 +1,1205 @@ +/* -*- 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 <editeng/formatbreakitem.hxx> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <doc.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docstat.hxx> +#include <docary.hxx> +#include <fmtpdsc.hxx> +#include <laycache.hxx> +#include "layhelp.hxx" +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <swtable.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <sectfrm.hxx> +#include <fmtcntnt.hxx> +#include <pagedesc.hxx> +#include <frmtool.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <viewopt.hxx> +#include <flyfrm.hxx> +#include <sortedobjs.hxx> +#include <ndindex.hxx> +#include <node.hxx> +#include <ndtxt.hxx> +#include <frameformats.hxx> + +#include <limits> +#include <set> + +using namespace ::com::sun::star; + +SwLayoutCache::SwLayoutCache() : nLockCount( 0 ) {} + +/* + * Reading and writing of the layout cache. + * The layout cache is not necessary, but it improves + * the performance and reduces the text flow during + * the formatting. + * The layout cache contains the index of the paragraphs/tables + * at the top of every page, so it's possible to create + * the right count of pages and to distribute the document content + * to this pages before the formatting starts. + */ + +void SwLayoutCache::Read( SvStream &rStream ) +{ + if( !pImpl ) + { + pImpl.reset( new SwLayCacheImpl ); + if( !pImpl->Read( rStream ) ) + { + pImpl.reset(); + } + } +} + +void SwLayCacheImpl::Insert( sal_uInt16 nType, sal_uLong nIndex, sal_Int32 nOffset ) +{ + aType.push_back( nType ); + mIndices.push_back( nIndex ); + aOffset.push_back( nOffset ); +} + +bool SwLayCacheImpl::Read( SvStream& rStream ) +{ + SwLayCacheIoImpl aIo( rStream, false ); + if( aIo.GetMajorVersion() > SW_LAYCACHE_IO_VERSION_MAJOR ) + return false; + + // Due to an evil bug in the layout cache (#102759#), we cannot trust the + // sizes of fly frames which have been written using the "old" layout cache. + // This flag should indicate that we do not want to trust the width and + // height of fly frames + bUseFlyCache = aIo.GetMinorVersion() >= 1; + + aIo.OpenRec( SW_LAYCACHE_IO_REC_PAGES ); + aIo.OpenFlagRec(); + aIo.CloseFlagRec(); + while( aIo.BytesLeft() && !aIo.HasError() ) + { + sal_uInt32 nIndex(0), nOffset(0); + + switch( aIo.Peek() ) + { + case SW_LAYCACHE_IO_REC_PARA: + { + aIo.OpenRec( SW_LAYCACHE_IO_REC_PARA ); + sal_uInt8 cFlags = aIo.OpenFlagRec(); + aIo.GetStream().ReadUInt32( nIndex ); + if( (cFlags & 0x01) != 0 ) + aIo.GetStream().ReadUInt32( nOffset ); + else + nOffset = COMPLETE_STRING; + aIo.CloseFlagRec(); + Insert( SW_LAYCACHE_IO_REC_PARA, nIndex, static_cast<sal_Int32>(nOffset) ); + aIo.CloseRec(); + break; + } + case SW_LAYCACHE_IO_REC_TABLE: + aIo.OpenRec( SW_LAYCACHE_IO_REC_TABLE ); + aIo.OpenFlagRec(); + aIo.GetStream().ReadUInt32( nIndex ) + .ReadUInt32( nOffset ); + Insert( SW_LAYCACHE_IO_REC_TABLE, nIndex, static_cast<sal_Int32>(nOffset) ); + aIo.CloseFlagRec(); + aIo.CloseRec(); + break; + case SW_LAYCACHE_IO_REC_FLY: + { + aIo.OpenRec( SW_LAYCACHE_IO_REC_FLY ); + aIo.OpenFlagRec(); + aIo.CloseFlagRec(); + sal_Int32 nX(0), nY(0), nW(0), nH(0); + sal_uInt16 nPgNum(0); + aIo.GetStream().ReadUInt16( nPgNum ).ReadUInt32( nIndex ) + .ReadInt32( nX ).ReadInt32( nY ).ReadInt32( nW ).ReadInt32( nH ); + m_FlyCache.emplace_back( nPgNum, nIndex, nX, nY, nW, nH ); + aIo.CloseRec(); + break; + } + default: + aIo.SkipRec(); + break; + } + } + aIo.CloseRec(); + + return !aIo.HasError(); +} + +/** writes the index (more precise: the difference between + * the index and the first index of the document content) + * of the first paragraph/table at the top of every page. + * If at the top of a page is the rest of a paragraph/table + * from the bottom of the previous page, the character/row + * number is stored, too. + * The position, size and page number of the text frames + * are stored, too + */ +void SwLayoutCache::Write( SvStream &rStream, const SwDoc& rDoc ) +{ + if( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ) // the layout itself .. + { + SwLayCacheIoImpl aIo( rStream, true ); + // We want to save the relative index, so we need the index + // of the first content + sal_uLong nStartOfContent = rDoc.GetNodes().GetEndOfContent(). + StartOfSectionNode()->GetIndex(); + // The first page... + SwPageFrame* pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->Lower())); + + aIo.OpenRec( SW_LAYCACHE_IO_REC_PAGES ); + aIo.OpenFlagRec( 0, 0 ); + aIo.CloseFlagRec(); + while( pPage ) + { + if( pPage->GetPrev() ) + { + SwLayoutFrame* pLay = pPage->FindBodyCont(); + SwFrame* pTmp = pLay ? pLay->ContainsAny() : nullptr; + // We are only interested in paragraph or table frames, + // a section frames contains paragraphs/tables. + if( pTmp && pTmp->IsSctFrame() ) + pTmp = static_cast<SwSectionFrame*>(pTmp)->ContainsAny(); + + if( pTmp ) // any content + { + if( pTmp->IsTextFrame() ) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTmp)); + assert(!pFrame->GetMergedPara()); + sal_uLong nNdIdx = pFrame->GetTextNodeFirst()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + /* Open Paragraph Record */ + aIo.OpenRec( SW_LAYCACHE_IO_REC_PARA ); + bool bFollow = static_cast<SwTextFrame*>(pTmp)->IsFollow(); + aIo.OpenFlagRec( bFollow ? 0x01 : 0x00, + bFollow ? 8 : 4 ); + nNdIdx -= nStartOfContent; + aIo.GetStream().WriteUInt32( nNdIdx ); + if( bFollow ) + aIo.GetStream().WriteUInt32( sal_Int32(static_cast<SwTextFrame*>(pTmp)->GetOffset()) ); + aIo.CloseFlagRec(); + /* Close Paragraph Record */ + aIo.CloseRec(); + } + } + else if( pTmp->IsTabFrame() ) + { + SwTabFrame* pTab = static_cast<SwTabFrame*>(pTmp); + sal_uLong nOfst = COMPLETE_STRING; + if( pTab->IsFollow() ) + { + // If the table is a follow, we have to look for the + // master and to count all rows to get the row number + nOfst = 0; + if( pTab->IsFollow() ) + pTab = pTab->FindMaster( true ); + while( pTab != pTmp ) + { + SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + assert(pTab && "Table follow without master"); + } + } + while (true) + { + sal_uLong nNdIdx = + pTab->GetTable()->GetTableNode()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + /* Open Table Record */ + aIo.OpenRec( SW_LAYCACHE_IO_REC_TABLE ); + aIo.OpenFlagRec( 0, 8 ); + nNdIdx -= nStartOfContent; + aIo.GetStream().WriteUInt32( nNdIdx ) + .WriteUInt32( nOfst ); + aIo.CloseFlagRec(); + /* Close Table Record */ + aIo.CloseRec(); + } + // If the table has a follow on the next page, + // we know already the row number and store this + // immediately. + if( pTab->GetFollow() ) + { + if( nOfst == sal_uLong(COMPLETE_STRING) ) + nOfst = 0; + do + { + SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + SwPageFrame *pTabPage = pTab->FindPageFrame(); + if( pTabPage != pPage ) + { + OSL_ENSURE( pPage->GetPhyPageNum() < + pTabPage->GetPhyPageNum(), + "Looping Tableframes" ); + pPage = pTabPage; + break; + } + } while ( pTab->GetFollow() ); + } + else + break; + } + } + } + } + if( pPage->GetSortedObjs() ) + { + SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if (SwFlyFrame *pFly = dynamic_cast<SwFlyFrame*>(pAnchoredObj)) + { + if( pFly->getFrameArea().Left() != FAR_AWAY && + !pFly->GetAnchorFrame()->FindFooterOrHeader() ) + { + const SwContact *pC = + ::GetUserCall(pAnchoredObj->GetDrawObj()); + if( pC ) + { + sal_uInt32 nOrdNum = pAnchoredObj->GetDrawObj()->GetOrdNum(); + sal_uInt16 nPageNum = pPage->GetPhyPageNum(); + /* Open Fly Record */ + aIo.OpenRec( SW_LAYCACHE_IO_REC_FLY ); + aIo.OpenFlagRec( 0, 0 ); + aIo.CloseFlagRec(); + const SwRect& rRct = pFly->getFrameArea(); + sal_Int32 nX = rRct.Left() - pPage->getFrameArea().Left(); + sal_Int32 nY = rRct.Top() - pPage->getFrameArea().Top(); + aIo.GetStream().WriteUInt16( nPageNum ).WriteUInt32( nOrdNum ) + .WriteInt32( nX ).WriteInt32( nY ) + .WriteInt32( rRct.Width() ) + .WriteInt32( rRct.Height() ); + /* Close Fly Record */ + aIo.CloseRec(); + } + } + } + } + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + aIo.CloseRec(); + } +} + +#ifdef DBG_UTIL +bool SwLayoutCache::CompareLayout( const SwDoc& rDoc ) const +{ + if( !pImpl ) + return true; + const SwRootFrame *pRootFrame = rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pRootFrame ) + { + size_t nIndex = 0; + sal_uLong nStartOfContent = rDoc.GetNodes().GetEndOfContent(). + StartOfSectionNode()->GetIndex(); + const SwPageFrame* pPage = static_cast<const SwPageFrame*>(pRootFrame->Lower()); + if( pPage ) + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + while( pPage ) + { + if( nIndex >= pImpl->size() ) + return false; + + const SwLayoutFrame* pLay = pPage->FindBodyCont(); + const SwFrame* pTmp = pLay ? pLay->ContainsAny() : nullptr; + if( pTmp && pTmp->IsSctFrame() ) + pTmp = static_cast<const SwSectionFrame*>(pTmp)->ContainsAny(); + if( pTmp ) + { + if( pTmp->IsTextFrame() ) + { + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTmp)); + assert(!pFrame->GetMergedPara()); + sal_uLong nNdIdx = pFrame->GetTextNodeFirst()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + bool bFollow = static_cast<const SwTextFrame*>(pTmp)->IsFollow(); + nNdIdx -= nStartOfContent; + if( pImpl->GetBreakIndex( nIndex ) != nNdIdx || + SW_LAYCACHE_IO_REC_PARA != + pImpl->GetBreakType( nIndex ) || + (bFollow + ? sal_Int32(static_cast<const SwTextFrame*>(pTmp)->GetOffset()) + : COMPLETE_STRING) != pImpl->GetBreakOfst(nIndex)) + { + return false; + } + ++nIndex; + } + } + else if( pTmp->IsTabFrame() ) + { + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pTmp); + sal_Int32 nOfst = COMPLETE_STRING; + if( pTab->IsFollow() ) + { + nOfst = 0; + if( pTab->IsFollow() ) + pTab = pTab->FindMaster( true ); + while( pTab != pTmp ) + { + const SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + } + } + do + { + sal_uLong nNdIdx = + pTab->GetTable()->GetTableNode()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + nNdIdx -= nStartOfContent; + if( pImpl->GetBreakIndex( nIndex ) != nNdIdx || + SW_LAYCACHE_IO_REC_TABLE != + pImpl->GetBreakType( nIndex ) || + nOfst != pImpl->GetBreakOfst( nIndex ) ) + { + return false; + } + ++nIndex; + } + if( pTab->GetFollow() ) + { + if( nOfst == COMPLETE_STRING ) + nOfst = 0; + do + { + const SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + const SwPageFrame *pTabPage = pTab->FindPageFrame(); + if( pTabPage != pPage ) + { + pPage = pTabPage; + break; + } + } while ( pTab->GetFollow() ); + } + else + break; + } while( pTab ); + } + } + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + } + return true; +} +#endif + +void SwLayoutCache::ClearImpl() +{ + if( !IsLocked() ) + { + pImpl.reset(); + } +} + +SwLayoutCache::~SwLayoutCache() +{ + OSL_ENSURE( !nLockCount, "Deleting a locked SwLayoutCache!?" ); +} + +/// helper class to create not nested section frames for nested sections. +SwActualSection::SwActualSection( SwActualSection *pUp, + SwSectionFrame *pSect, + SwSectionNode *pNd ) : + pUpper( pUp ), + pSectFrame( pSect ), + pSectNode( pNd ) +{ + if ( !pSectNode ) + { + const SwNodeIndex *pIndex = pSect->GetFormat()->GetContent().GetContentIdx(); + pSectNode = pIndex->GetNode().FindSectionNode(); + } +} + +namespace { + +bool sanityCheckLayoutCache(SwLayCacheImpl const& rCache, + SwNodes const& rNodes, sal_uLong nNodeIndex) +{ + auto const nStartOfContent(rNodes.GetEndOfContent().StartOfSectionNode()->GetIndex()); + nNodeIndex -= nStartOfContent; + auto const nMaxIndex(rNodes.GetEndOfContent().GetIndex() - nStartOfContent); + for (size_t nIndex = 0; nIndex < rCache.size(); ++nIndex) + { + auto const nBreakIndex(rCache.GetBreakIndex(nIndex)); + if (nBreakIndex < nNodeIndex || nMaxIndex <= nBreakIndex) + { + SAL_WARN("sw.layout", + "invalid node index in layout-cache: " << nBreakIndex); + return false; + } + auto const nBreakType(rCache.GetBreakType(nIndex)); + switch (nBreakType) + { + case SW_LAYCACHE_IO_REC_PARA: + if (!rNodes[nBreakIndex + nStartOfContent]->IsTextNode()) + { + SAL_WARN("sw.layout", + "invalid node of type 'P' in layout-cache"); + return false; + } + break; + case SW_LAYCACHE_IO_REC_TABLE: + if (!rNodes[nBreakIndex + nStartOfContent]->IsTableNode()) + { + SAL_WARN("sw.layout", + "invalid node of type 'T' in layout-cache"); + return false; + } + break; + default: + assert(false); // Read shouldn't have inserted that + } + } + return true; +} + +} // namespace + +/** helper class, which utilizes the layout cache information + * to distribute the document content to the right pages. + * It's used by the InsertCnt_(..)-function. + * If there's no layout cache, the distribution to the pages is more + * a guess, but a guess with statistical background. + */ +SwLayHelper::SwLayHelper( SwDoc *pD, SwFrame* &rpF, SwFrame* &rpP, SwPageFrame* &rpPg, + SwLayoutFrame* &rpL, std::unique_ptr<SwActualSection> &rpA, + sal_uLong nNodeIndex, bool bCache ) + : mrpFrame( rpF ) + , mrpPrv( rpP ) + , mrpPage( rpPg ) + , mrpLay( rpL ) + , mrpActualSection( rpA ) + , mbBreakAfter(false) + , mpDoc(pD) + , mnMaxParaPerPage( 25 ) + , mnParagraphCnt( bCache ? 0 : USHRT_MAX ) + , mnFlyIdx( 0 ) + , mbFirst( bCache ) +{ + mpImpl = mpDoc->GetLayoutCache() ? mpDoc->GetLayoutCache()->LockImpl() : nullptr; + if( mpImpl ) + { + SwNodes const& rNodes(mpDoc->GetNodes()); + if (sanityCheckLayoutCache(*mpImpl, rNodes, nNodeIndex)) + { + mnIndex = 0; + mnStartOfContent = rNodes.GetEndOfContent().StartOfSectionNode()->GetIndex(); + mnMaxParaPerPage = 1000; + } + else + { + mpDoc->GetLayoutCache()->UnlockImpl(); + mpImpl = nullptr; + mnIndex = std::numeric_limits<size_t>::max(); + mnStartOfContent = USHRT_MAX; + } + } + else + { + mnIndex = std::numeric_limits<size_t>::max(); + mnStartOfContent = ULONG_MAX; + } +} + +SwLayHelper::~SwLayHelper() +{ + if( mpImpl ) + { + OSL_ENSURE( mpDoc && mpDoc->GetLayoutCache(), "Missing layoutcache" ); + mpDoc->GetLayoutCache()->UnlockImpl(); + } +} + +/** Does NOT really calculate the page count, + * it returns the page count value from the layout cache, if available, + * otherwise it estimates the page count. + */ +sal_uLong SwLayHelper::CalcPageCount() +{ + sal_uLong nPgCount; + SwLayCacheImpl *pCache = mpDoc->GetLayoutCache() ? + mpDoc->GetLayoutCache()->LockImpl() : nullptr; + if( pCache ) + { + nPgCount = pCache->size() + 1; + mpDoc->GetLayoutCache()->UnlockImpl(); + } + else + { + nPgCount = mpDoc->getIDocumentStatistics().GetDocStat().nPage; + if ( nPgCount <= 10 ) // no page insertion for less than 10 pages + nPgCount = 0; + sal_uLong nNdCount = mpDoc->getIDocumentStatistics().GetDocStat().nPara; + if ( nNdCount <= 1 ) + { + //Estimates the number of paragraphs. + sal_uLong nTmp = mpDoc->GetNodes().GetEndOfContent().GetIndex() - + mpDoc->GetNodes().GetEndOfExtras().GetIndex(); + //Tables have a little overhead... + nTmp -= mpDoc->GetTableFrameFormats()->size() * 25; + //Fly frames, too .. + nTmp -= (mpDoc->GetNodes().GetEndOfAutotext().GetIndex() - + mpDoc->GetNodes().GetEndOfInserts().GetIndex()) / 3 * 5; + if ( nTmp > 0 ) + nNdCount = nTmp; + } + if ( nNdCount > 100 ) // no estimation below this value + { + if ( nPgCount > 0 ) + { // tdf#129529 avoid 0... + mnMaxParaPerPage = std::max<sal_uLong>(3, nNdCount / nPgCount); + } + else + { + mnMaxParaPerPage = std::max( sal_uLong(20), + sal_uLong(20 + nNdCount / 1000 * 3) ); + const sal_uLong nMax = 53; + mnMaxParaPerPage = std::min( mnMaxParaPerPage, nMax ); + nPgCount = nNdCount / mnMaxParaPerPage; + } + if ( nNdCount < 1000 ) + nPgCount = 0;// no progress bar for small documents + SwViewShell *pSh = nullptr; + if( mrpLay && mrpLay->getRootFrame() ) + pSh = mrpLay->getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + mnMaxParaPerPage *= 6; + } + } + return nPgCount; +} + +/** + * inserts a page and return true, if + * - the break after flag is set + * - the actual content wants a break before + * - the maximum count of paragraph/rows is reached + * + * The break after flag is set, if the actual content + * wants a break after. + */ +bool SwLayHelper::CheckInsertPage() +{ + bool bEnd = nullptr == mrpPage->GetNext(); + const SvxFormatBreakItem& rBrk = mrpFrame->GetBreakItem(); + const SwFormatPageDesc& rDesc = mrpFrame->GetPageDescItem(); + // #118195# Do not evaluate page description if frame + // is a follow frame! + const SwPageDesc* pDesc = mrpFrame->IsFlowFrame() && + SwFlowFrame::CastFlowFrame( mrpFrame )->IsFollow() ? + nullptr : + rDesc.GetPageDesc(); + + bool bBrk = mnParagraphCnt > mnMaxParaPerPage || mbBreakAfter; + mbBreakAfter = rBrk.GetBreak() == SvxBreak::PageAfter || + rBrk.GetBreak() == SvxBreak::PageBoth; + if ( !bBrk ) + bBrk = rBrk.GetBreak() == SvxBreak::PageBefore || + rBrk.GetBreak() == SvxBreak::PageBoth; + + if ( bBrk || pDesc ) + { + ::std::optional<sal_uInt16> oPgNum; + if ( !pDesc ) + { + pDesc = mrpPage->GetPageDesc()->GetFollow(); + } + else + { + oPgNum = rDesc.GetNumOffset(); + if ( oPgNum ) + static_cast<SwRootFrame*>(mrpPage->GetUpper())->SetVirtPageNum(true); + } + bool bNextPageRight = !mrpPage->OnRightPage(); + bool bInsertEmpty = false; + assert(mrpPage->GetUpper()->GetLower()); + if (oPgNum && bNextPageRight != IsRightPageByNumber( + *static_cast<SwRootFrame*>(mrpPage->GetUpper()), *oPgNum)) + { + bNextPageRight = !bNextPageRight; + bInsertEmpty = true; + } + // If the page style is changing, we'll have a first page. + bool bNextPageFirst = pDesc != mrpPage->GetPageDesc(); + ::InsertNewPage( const_cast<SwPageDesc&>(*pDesc), mrpPage->GetUpper(), + bNextPageRight, bNextPageFirst, bInsertEmpty, false, mrpPage->GetNext()); + if ( bEnd ) + { + OSL_ENSURE( mrpPage->GetNext(), "No new page?" ); + do + { mrpPage = static_cast<SwPageFrame*>(mrpPage->GetNext()); + } while ( mrpPage->GetNext() ); + } + else + { + OSL_ENSURE( mrpPage->GetNext(), "No new page?" ); + mrpPage = static_cast<SwPageFrame*>(mrpPage->GetNext()); + if ( mrpPage->IsEmptyPage() ) + { + OSL_ENSURE( mrpPage->GetNext(), "No new page?" ); + mrpPage = static_cast<SwPageFrame*>(mrpPage->GetNext()); + } + } + mrpLay = mrpPage->FindBodyCont(); + while( mrpLay->Lower() ) + mrpLay = static_cast<SwLayoutFrame*>(mrpLay->Lower()); + return true; + } + return false; +} + +/** entry point for the InsertCnt_-function. + * The document content index is checked either it is + * in the layout cache either it's time to insert a page + * cause the maximal estimation of content per page is reached. + * A really big table or long paragraph may contains more than + * one page, in this case the needed count of pages will inserted. + */ +bool SwLayHelper::CheckInsert( sal_uLong nNodeIndex ) +{ + bool bRet = false; + bool bLongTab = false; + sal_uLong nMaxRowPerPage( 0 ); + nNodeIndex -= mnStartOfContent; + sal_uInt16 nRows( 0 ); + if( mrpFrame->IsTabFrame() ) + { + //Inside a table counts every row as a paragraph + SwFrame *pLow = static_cast<SwTabFrame*>(mrpFrame)->Lower(); + nRows = 0; + do + { + ++nRows; + pLow = pLow->GetNext(); + } while ( pLow ); + mnParagraphCnt += nRows; + if( !mpImpl && mnParagraphCnt > mnMaxParaPerPage + 10 ) + { + // OD 09.04.2003 #108698# - improve heuristics: + // Assume that a table, which has more than three times the quantity + // of maximal paragraphs per page rows, consists of rows, which have + // the height of a normal paragraph. Thus, allow as much rows per page + // as much paragraphs are allowed. + if ( nRows > ( 3*mnMaxParaPerPage ) ) + { + nMaxRowPerPage = mnMaxParaPerPage; + } + else + { + SwFrame *pTmp = static_cast<SwTabFrame*>(mrpFrame)->Lower(); + if( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + pTmp = static_cast<SwRowFrame*>(pTmp)->Lower(); + sal_uInt16 nCnt = 0; + do + { + ++nCnt; + pTmp = pTmp->GetNext(); + } while( pTmp ); + nMaxRowPerPage = std::max( sal_uLong(2), mnMaxParaPerPage / nCnt ); + } + bLongTab = true; + } + } + else + ++mnParagraphCnt; + if( mbFirst && mpImpl && mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex && + ( mpImpl->GetBreakOfst( mnIndex ) < COMPLETE_STRING || + ( ++mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex ) ) ) + mbFirst = false; + // OD 09.04.2003 #108698# - always split a big tables. + if ( !mbFirst || + ( mrpFrame->IsTabFrame() && bLongTab ) + ) + { + sal_Int32 nRowCount = 0; + do + { + if( mpImpl || bLongTab ) + { + sal_Int32 nOfst = COMPLETE_STRING; + sal_uInt16 nType = SW_LAYCACHE_IO_REC_PAGES; + if( bLongTab ) + { + mbBreakAfter = true; + nOfst = static_cast<sal_Int32>(nRowCount + nMaxRowPerPage); + } + else + { + while( mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex(mnIndex) < nNodeIndex) + ++mnIndex; + if( mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex(mnIndex) == nNodeIndex ) + { + nType = mpImpl->GetBreakType( mnIndex ); + nOfst = mpImpl->GetBreakOfst( mnIndex++ ); + mbBreakAfter = true; + } + } + + if( nOfst < COMPLETE_STRING ) + { + bool bSplit = false; + sal_uInt16 nRepeat( 0 ); + if( !bLongTab && mrpFrame->IsTextFrame() && + SW_LAYCACHE_IO_REC_PARA == nType && + nOfst < static_cast<SwTextFrame*>(mrpFrame)->GetText().getLength()) + bSplit = true; + else if( mrpFrame->IsTabFrame() && nRowCount < nOfst && + ( bLongTab || SW_LAYCACHE_IO_REC_TABLE == nType ) ) + { + nRepeat = static_cast<SwTabFrame*>(mrpFrame)-> + GetTable()->GetRowsToRepeat(); + bSplit = nOfst < nRows && nRowCount + nRepeat < nOfst; + bLongTab = bLongTab && bSplit; + } + if( bSplit ) + { + mrpFrame->InsertBehind( mrpLay, mrpPrv ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mrpFrame); + aFrm.Pos() = mrpLay->getFrameArea().Pos(); + aFrm.Pos().AdjustY(1 ); + } + + mrpPrv = mrpFrame; + if( mrpFrame->IsTabFrame() ) + { + SwTabFrame* pTab = static_cast<SwTabFrame*>(mrpFrame); + // #i33629#, #i29955# + ::RegistFlys( pTab->FindPageFrame(), pTab ); + SwFrame *pRow = pTab->Lower(); + SwTabFrame *pFoll = new SwTabFrame( *pTab ); + + SwFrame *pPrv; + if( nRepeat > 0 ) + { + bDontCreateObjects = true; //frmtool + + // Insert new headlines: + sal_uInt16 nRowIdx = 0; + SwRowFrame* pHeadline = nullptr; + while( nRowIdx < nRepeat ) + { + OSL_ENSURE( pTab->GetTable()->GetTabLines()[ nRowIdx ], "Table without rows?" ); + pHeadline = + new SwRowFrame( *pTab->GetTable()->GetTabLines()[ nRowIdx ], pTab ); + pHeadline->SetRepeatedHeadline( true ); + pHeadline->InsertBefore( pFoll, nullptr ); + pHeadline->RegistFlys(); + + ++nRowIdx; + } + + bDontCreateObjects = false; + pPrv = pHeadline; + nRows = nRows + nRepeat; + } + else + pPrv = nullptr; + while( pRow && nRowCount < nOfst ) + { + pRow = pRow->GetNext(); + ++nRowCount; + } + while ( pRow ) + { + SwFrame* pNxt = pRow->GetNext(); + pRow->RemoveFromLayout(); + pRow->InsertBehind( pFoll, pPrv ); + pPrv = pRow; + pRow = pNxt; + } + mrpFrame = pFoll; + } + else + { + SwTextFrame *const pNew = static_cast<SwTextFrame*>( + static_cast<SwTextFrame*>(mrpFrame) + ->GetTextNodeFirst()->MakeFrame(mrpFrame)); + pNew->ManipOfst( TextFrameIndex(nOfst) ); + pNew->SetFollow( static_cast<SwTextFrame*>(mrpFrame)->GetFollow() ); + static_cast<SwTextFrame*>(mrpFrame)->SetFollow( pNew ); + mrpFrame = pNew; + } + } + } + } + + SwPageFrame* pLastPage = mrpPage; + if( CheckInsertPage() ) + { + CheckFlyCache_( pLastPage ); + if( mrpPrv && mrpPrv->IsTextFrame() && !mrpPrv->isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mrpPrv); + aFrm.Height( mrpPrv->GetUpper()->getFramePrintArea().Height() ); + } + + bRet = true; + mrpPrv = nullptr; + mnParagraphCnt = 0; + + if ( mrpActualSection ) + { + //Did the SectionFrame even have a content? If not, we can + //directly put it somewhere else + SwSectionFrame *pSct; + bool bInit = false; + if ( !mrpActualSection->GetSectionFrame()->ContainsContent()) + { + pSct = mrpActualSection->GetSectionFrame(); + pSct->RemoveFromLayout(); + } + else + { + pSct = new SwSectionFrame( + *mrpActualSection->GetSectionFrame(), false ); + mrpActualSection->GetSectionFrame()->SimpleFormat(); + bInit = true; + } + mrpActualSection->SetSectionFrame( pSct ); + pSct->InsertBehind( mrpLay, nullptr ); + + if( bInit ) + { + pSct->Init(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pSct); + aFrm.Pos() = mrpLay->getFrameArea().Pos(); + aFrm.Pos().AdjustY(1 ); //because of the notifications + } + + mrpLay = pSct; + if ( mrpLay->Lower() && mrpLay->Lower()->IsLayoutFrame() ) + mrpLay = mrpLay->GetNextLayoutLeaf(); + } + } + } while( bLongTab || ( mpImpl && mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex ) ); + } + mbFirst = false; + return bRet; +} + +namespace { + +struct SdrObjectCompare +{ + bool operator()( const SdrObject* pF1, const SdrObject* pF2 ) const + { + return pF1->GetOrdNum() < pF2->GetOrdNum(); + } +}; + +struct FlyCacheCompare +{ + bool operator()( const SwFlyCache* pC1, const SwFlyCache* pC2 ) const + { + return pC1->nOrdNum < pC2->nOrdNum; + } +}; + +} + +/** + * If a new page is inserted, the last page is analysed. + * If there are text frames with default position, the fly cache + * is checked, if these frames are stored in the cache. + */ +void SwLayHelper::CheckFlyCache_( SwPageFrame* pPage ) +{ + if( !mpImpl || !pPage ) + return; + const size_t nFlyCount = mpImpl->GetFlyCount(); + // Any text frames at the page, fly cache available? + if( pPage->GetSortedObjs() && mnFlyIdx < nFlyCount ) + { + SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + sal_uInt16 nPgNum = pPage->GetPhyPageNum(); + + // NOTE: Here we do not use the absolute ordnums but + // relative ordnums for the objects on this page. + + // skip fly frames from pages before the current page + while( mnFlyIdx < nFlyCount && + mpImpl->GetFlyCache(mnFlyIdx).nPageNum < nPgNum ) + ++mnFlyIdx; + + // sort cached objects on this page by ordnum + std::set< const SwFlyCache*, FlyCacheCompare > aFlyCacheSet; + size_t nIdx = mnFlyIdx; + + SwFlyCache* pFlyC; + while( nIdx < nFlyCount && + ( pFlyC = &mpImpl->GetFlyCache( nIdx ) )->nPageNum == nPgNum ) + { + aFlyCacheSet.insert( pFlyC ); + ++nIdx; + } + + // sort objects on this page by ordnum + std::set< const SdrObject*, SdrObjectCompare > aFlySet; + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if (SwFlyFrame *pFly = dynamic_cast<SwFlyFrame*>(pAnchoredObj)) // a text frame? + { + if( pFly->GetAnchorFrame() && + !pFly->GetAnchorFrame()->FindFooterOrHeader() ) + { + const SwContact *pC = ::GetUserCall( pAnchoredObj->GetDrawObj() ); + if( pC ) + { + aFlySet.insert( pAnchoredObj->GetDrawObj() ); + } + } + } + } + + if ( aFlyCacheSet.size() == aFlySet.size() ) + { + std::set< const SdrObject*, SdrObjectCompare >::iterator aFlySetIt = + aFlySet.begin(); + + for ( const SwFlyCache* pFlyCache : aFlyCacheSet ) + { + SwFlyFrame* pFly = const_cast<SwVirtFlyDrawObj*>(static_cast<const SwVirtFlyDrawObj*>(*aFlySetIt))->GetFlyFrame(); + + if ( pFly->getFrameArea().Left() == FAR_AWAY ) + { + // we get the stored information + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly); + aFrm.Pos().setX( pFlyCache->Left() + pPage->getFrameArea().Left() ); + aFrm.Pos().setY( pFlyCache->Top() + pPage->getFrameArea().Top() ); + + if ( mpImpl->IsUseFlyCache() ) + { + aFrm.Width( pFlyCache->Width() ); + aFrm.Height( pFlyCache->Height() ); + } + } + + ++aFlySetIt; + } + } + } +} + +SwLayCacheIoImpl::SwLayCacheIoImpl( SvStream& rStrm, bool bWrtMd ) : + pStream( &rStrm ), + nFlagRecEnd ( 0 ), + nMajorVersion(SW_LAYCACHE_IO_VERSION_MAJOR), + nMinorVersion(SW_LAYCACHE_IO_VERSION_MINOR), + bWriteMode( bWrtMd ), + bError( false ) +{ + if( bWriteMode ) + pStream->WriteUInt16( nMajorVersion ) + .WriteUInt16( nMinorVersion ); + + else + pStream->ReadUInt16( nMajorVersion ) + .ReadUInt16( nMinorVersion ); +} + +void SwLayCacheIoImpl::OpenRec( sal_uInt8 cType ) +{ + sal_uInt32 nPos = pStream->Tell(); + if( bWriteMode ) + { + aRecords.emplace_back(cType, nPos ); + pStream->WriteUInt32( 0 ); + } + else + { + sal_uInt32 nVal(0); + pStream->ReadUInt32( nVal ); + sal_uInt8 cRecTyp = static_cast<sal_uInt8>(nVal); + if (!nVal || cRecTyp != cType || !pStream->good()) + { + OSL_ENSURE( nVal, "OpenRec: Record-Header is 0" ); + OSL_ENSURE( cRecTyp == cType, "OpenRec: Wrong Record Type" ); + aRecords.emplace_back(0, pStream->Tell() ); + bError = true; + } + else + { + sal_uInt32 nSize = nVal >> 8; + aRecords.emplace_back(cRecTyp, nPos+nSize ); + } + } +} + +// Close record +void SwLayCacheIoImpl::CloseRec() +{ + bool bRes = true; + OSL_ENSURE( !aRecords.empty(), "CloseRec: no levels" ); + if( !aRecords.empty() ) + { + sal_uInt32 nPos = pStream->Tell(); + if( bWriteMode ) + { + sal_uInt32 nBgn = aRecords.back().size; + pStream->Seek( nBgn ); + sal_uInt32 nSize = nPos - nBgn; + sal_uInt32 nVal = ( nSize << 8 ) | aRecords.back().type; + pStream->WriteUInt32( nVal ); + pStream->Seek( nPos ); + if( pStream->GetError() != ERRCODE_NONE ) + bRes = false; + } + else + { + sal_uInt32 n = aRecords.back().size; + OSL_ENSURE( n >= nPos, "CloseRec: too much data read" ); + if( n != nPos ) + { + pStream->Seek( n ); + if( n < nPos ) + bRes = false; + } + if( pStream->GetErrorCode() != ERRCODE_NONE ) + bRes = false; + } + aRecords.pop_back(); + } + + if( !bRes ) + bError = true; +} + +sal_uInt32 SwLayCacheIoImpl::BytesLeft() +{ + sal_uInt32 n = 0; + if( !bError && !aRecords.empty() ) + { + sal_uInt32 nEndPos = aRecords.back().size; + sal_uInt32 nPos = pStream->Tell(); + if( nEndPos > nPos ) + n = nEndPos - nPos; + } + return n; +} + +sal_uInt8 SwLayCacheIoImpl::Peek() +{ + sal_uInt8 c(0); + if( !bError ) + { + sal_uInt32 nPos = pStream->Tell(); + pStream->ReadUChar( c ); + pStream->Seek( nPos ); + if( pStream->GetErrorCode() != ERRCODE_NONE ) + { + c = 0; + bError = true; + } + } + return c; +} + +void SwLayCacheIoImpl::SkipRec() +{ + sal_uInt8 c = Peek(); + OpenRec( c ); + pStream->Seek( aRecords.back().size ); + CloseRec(); +} + +sal_uInt8 SwLayCacheIoImpl::OpenFlagRec() +{ + OSL_ENSURE( !bWriteMode, "OpenFlagRec illegal in write mode" ); + sal_uInt8 cFlags(0); + pStream->ReadUChar( cFlags ); + nFlagRecEnd = pStream->Tell() + ( cFlags & 0x0F ); + return (cFlags >> 4); +} + +void SwLayCacheIoImpl::OpenFlagRec( sal_uInt8 nFlags, sal_uInt8 nLen ) +{ + OSL_ENSURE( bWriteMode, "OpenFlagRec illegal in read mode" ); + OSL_ENSURE( (nFlags & 0xF0) == 0, "illegal flags set" ); + OSL_ENSURE( nLen < 16, "wrong flag record length" ); + sal_uInt8 cFlags = (nFlags << 4) + nLen; + pStream->WriteUChar( cFlags ); + nFlagRecEnd = pStream->Tell() + nLen; +} + +void SwLayCacheIoImpl::CloseFlagRec() +{ + if( bWriteMode ) + { + OSL_ENSURE( pStream->Tell() == nFlagRecEnd, "Wrong amount of data written" ); + } + else + { + OSL_ENSURE( pStream->Tell() <= nFlagRecEnd, "Too many data read" ); + if( pStream->Tell() != nFlagRecEnd ) + pStream->Seek( nFlagRecEnd ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layhelp.hxx b/sw/source/core/layout/layhelp.hxx new file mode 100644 index 000000000..b4d21a3fd --- /dev/null +++ b/sw/source/core/layout/layhelp.hxx @@ -0,0 +1,217 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_LAYHELP_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_LAYHELP_HXX + +#include <swrect.hxx> + +#include <tools/solar.h> + +#include <memory> +#include <vector> +#include <deque> + +class SwDoc; +class SwFrame; +class SwLayoutFrame; +class SwPageFrame; +class SwSectionFrame; +class SwSectionNode; +class SvStream; + +/* + * Contains the page break information and the text frame positions + * of the document (after loading) + * and is used inside the constructor of the layout rootframe to + * insert content and text frames at the right pages. + * For every page of the main text (body content, no footnotes, text frames etc.) + * we have the nodeindex of the first content at the page, + * the type of content ( table or paragraph ) + * and if it's not the first part of the table/paragraph, + * the row/character-offset inside the table/paragraph. + * The text frame positions are stored in the SwPageFlyCache array. + */ + +class SwFlyCache; +typedef std::vector<SwFlyCache> SwPageFlyCache; + +class SwLayCacheImpl +{ + std::vector<sal_uLong> mIndices; + /// either a textframe character offset, or a row index inside a table + std::deque<sal_Int32> aOffset; + std::vector<sal_uInt16> aType; + SwPageFlyCache m_FlyCache; + bool bUseFlyCache; + void Insert( sal_uInt16 nType, sal_uLong nIndex, sal_Int32 nOffset ); + +public: + SwLayCacheImpl() : bUseFlyCache(false) {} + + size_t size() const { return mIndices.size(); } + + bool Read( SvStream& rStream ); + + sal_uLong GetBreakIndex( size_t nIdx ) const { return mIndices[ nIdx ]; } + sal_Int32 GetBreakOfst( size_t nIdx ) const { return aOffset[ nIdx ]; } + sal_uInt16 GetBreakType( size_t nIdx ) const { return aType[ nIdx ]; } + + size_t GetFlyCount() const { return m_FlyCache.size(); } + SwFlyCache& GetFlyCache( size_t nIdx ) { return m_FlyCache[ nIdx ]; } + + bool IsUseFlyCache() const { return bUseFlyCache; } +}; + +// Helps to create the sectionframes during the InsertCnt_-function +// by controlling nested sections. +class SwActualSection +{ + SwActualSection *pUpper; + SwSectionFrame *pSectFrame; + SwSectionNode *pSectNode; +public: + SwActualSection( SwActualSection *pUpper, + SwSectionFrame *pSect, + SwSectionNode *pNd ); + + SwSectionFrame *GetSectionFrame() { return pSectFrame; } + void SetSectionFrame( SwSectionFrame *p ) { pSectFrame = p; } + SwSectionNode *GetSectionNode() { return pSectNode;} + void SetUpper(SwActualSection *p) { pUpper = p; } + SwActualSection *GetUpper() { return pUpper; } +}; + +/// Helps during the InsertCnt_ function to create new pages. +/// If there's a layout cache available, this information is used. +class SwLayHelper +{ + SwFrame* &mrpFrame; + SwFrame* &mrpPrv; + SwPageFrame* &mrpPage; + SwLayoutFrame* &mrpLay; + std::unique_ptr<SwActualSection> &mrpActualSection; + bool mbBreakAfter; + SwDoc* mpDoc; + SwLayCacheImpl* mpImpl; + sal_uLong mnMaxParaPerPage; + sal_uLong mnParagraphCnt; + sal_uLong mnStartOfContent; + size_t mnIndex; ///< the index in the page break array + size_t mnFlyIdx; ///< the index in the fly cache array + bool mbFirst : 1; + void CheckFlyCache_( SwPageFrame* pPage ); +public: + SwLayHelper( SwDoc *pD, SwFrame* &rpF, SwFrame* &rpP, SwPageFrame* &rpPg, + SwLayoutFrame* &rpL, std::unique_ptr<SwActualSection> &rpA, + sal_uLong nNodeIndex, bool bCache ); + ~SwLayHelper(); + sal_uLong CalcPageCount(); + bool CheckInsert( sal_uLong nNodeIndex ); + + bool CheckInsertPage(); + + /// Look for fresh text frames at this (new) page and set them to the right + /// position, if they are in the fly cache. + void CheckFlyCache( SwPageFrame* pPage ) + { if( mpImpl && mnFlyIdx < mpImpl->GetFlyCount() ) CheckFlyCache_( pPage ); } +}; + +// Contains the data structures that are required to read and write a layout cache. +#define SW_LAYCACHE_IO_REC_PAGES 'p' +#define SW_LAYCACHE_IO_REC_PARA 'P' +#define SW_LAYCACHE_IO_REC_TABLE 'T' +#define SW_LAYCACHE_IO_REC_FLY 'F' + +#define SW_LAYCACHE_IO_VERSION_MAJOR 1 +#define SW_LAYCACHE_IO_VERSION_MINOR 1 + +class SwLayCacheIoImpl +{ +private: + struct RecTypeSize { + sal_uInt8 type; + sal_uLong size; + RecTypeSize(sal_uInt8 typ, sal_uLong siz) : type(typ), size(siz) {} + }; + std::vector<RecTypeSize> aRecords; + + SvStream *pStream; + + sal_uLong nFlagRecEnd; + + sal_uInt16 nMajorVersion; + sal_uInt16 nMinorVersion; + + bool bWriteMode : 1; + bool bError : 1; + +public: + SwLayCacheIoImpl( SvStream& rStrm, bool bWrtMd ); + + /// Get input or output stream + SvStream& GetStream() const { return *pStream; } + + /// Open a record of type "nType" + void OpenRec( sal_uInt8 nType ); + + /// Close a record. This skips any unread data that + /// remains in the record. + void CloseRec(); + + /// Return the number of bytes contained in the current record that + /// haven't been read by now. + sal_uInt32 BytesLeft(); + + /// Return the current record's type + sal_uInt8 Peek(); + + /// Skip the current record + void SkipRec(); + + /// Open a flag record for reading. The uppermost four bits are flags, + /// while the lowermost are the flag record's size. Flag records cannot + /// be nested. + sal_uInt8 OpenFlagRec(); + + /// Open flag record for writing; + void OpenFlagRec( sal_uInt8 nFlags, sal_uInt8 nLen ); + + /// Close a flag record. Any bytes left are skipped. + void CloseFlagRec(); + + bool HasError() const { return bError; } + + sal_uInt16 GetMajorVersion() const { return nMajorVersion; } + sal_uInt16 GetMinorVersion() const { return nMinorVersion; } +}; + +// Stored information about text frames: +class SwFlyCache : public SwRect // position and size +{ +public: + sal_uLong nOrdNum; ///< Id to recognize text frames + sal_uInt16 nPageNum; ///< page number + SwFlyCache( sal_uInt16 nP, sal_uLong nO, long nXL, long nYL, long nWL, long nHL ) : + SwRect( nXL, nYL, nWL, nHL ), nOrdNum( nO ), nPageNum( nP ){} +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layouter.cxx b/sw/source/core/layout/layouter.cxx new file mode 100644 index 000000000..0ae658570 --- /dev/null +++ b/sw/source/core/layout/layouter.cxx @@ -0,0 +1,482 @@ +/* -*- 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 <memory> +#include <layouter.hxx> +#include <doc.hxx> +#include <sectfrm.hxx> +#include <pagefrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <IDocumentLayoutAccess.hxx> + +#include <movedfwdfrmsbyobjpos.hxx> +#include "objstmpconsiderwrapinfl.hxx" + +#define LOOP_DETECT 250 + +class SwLooping +{ + sal_uInt16 nMinPage; + sal_uInt16 nMaxPage; + sal_uInt16 nCount; + sal_uInt16 mnLoopControlStage; +public: + explicit SwLooping( SwPageFrame const * pPage ); + void Control( SwPageFrame* pPage ); + void Drastic( SwFrame* pFrame ); + bool IsLoopingLouieLight() const { return nCount > LOOP_DETECT - 30; }; +}; + +class SwEndnoter +{ + SwLayouter* pMaster; + SwSectionFrame* pSect; + std::unique_ptr<SwFootnoteFrames> pEndArr; +public: + explicit SwEndnoter( SwLayouter* pLay ) + : pMaster( pLay ), pSect( nullptr ) {} + void CollectEndnotes( SwSectionFrame* pSct ); + void CollectEndnote( SwFootnoteFrame* pFootnote ); + const SwSectionFrame* GetSect() const { return pSect; } + void InsertEndnotes(); + bool HasEndnotes() const { return pEndArr && !pEndArr->empty(); } +}; + +void SwEndnoter::CollectEndnotes( SwSectionFrame* pSct ) +{ + OSL_ENSURE( pSct, "CollectEndnotes: Which section?" ); + if( !pSect ) + pSect = pSct; + else if( pSct != pSect ) + return; + pSect->CollectEndnotes( pMaster ); +} + +void SwEndnoter::CollectEndnote( SwFootnoteFrame* pFootnote ) +{ + if( pEndArr && pEndArr->end() != std::find( pEndArr->begin(), pEndArr->end(), pFootnote ) ) + return; + + if( pFootnote->GetUpper() ) + { + // pFootnote is the master, he incorporates its follows + SwFootnoteFrame *pNxt = pFootnote->GetFollow(); + while ( pNxt ) + { + SwFrame *pCnt = pNxt->ContainsAny(); + if ( pCnt ) + { + do + { SwFrame *pNxtCnt = pCnt->GetNext(); + pCnt->Cut(); + pCnt->Paste( pFootnote ); + pCnt = pNxtCnt; + } while ( pCnt ); + } + else + { + OSL_ENSURE( pNxt->Lower() && pNxt->Lower()->IsSctFrame(), + "Endnote without content?" ); + pNxt->Cut(); + SwFrame::DestroyFrame(pNxt); + } + pNxt = pFootnote->GetFollow(); + } + if( pFootnote->GetMaster() ) + return; + pFootnote->Cut(); + } + else if( pEndArr ) + { + for (SwFootnoteFrame* pEndFootnote : *pEndArr) + { + if( pEndFootnote->GetAttr() == pFootnote->GetAttr() ) + { + SwFrame::DestroyFrame(pFootnote); + return; + } + } + } + if( !pEndArr ) + pEndArr.reset( new SwFootnoteFrames ); // deleted from the SwLayouter + pEndArr->push_back( pFootnote ); +} + +void SwEndnoter::InsertEndnotes() +{ + if( !pSect ) + return; + if( !pEndArr || pEndArr->empty() ) + { + pSect = nullptr; + return; + } + OSL_ENSURE( pSect->Lower() && pSect->Lower()->IsFootnoteBossFrame(), + "InsertEndnotes: Where's my column?" ); + SwFrame* pRef = pSect->FindLastContent( SwFindMode::MyLast ); + SwFootnoteBossFrame *pBoss = pRef ? pRef->FindFootnoteBossFrame() + : static_cast<SwFootnoteBossFrame*>(pSect->Lower()); + pBoss->MoveFootnotes_( *pEndArr ); + pEndArr.reset(); + pSect = nullptr; +} + +SwLooping::SwLooping( SwPageFrame const * pPage ) +{ + OSL_ENSURE( pPage, "Where's my page?" ); + nMinPage = pPage->GetPhyPageNum(); + nMaxPage = nMinPage; + nCount = 0; + mnLoopControlStage = 0; +} + +void SwLooping::Drastic( SwFrame* pFrame ) +{ + while( pFrame ) + { + pFrame->ValidateThisAndAllLowers( mnLoopControlStage ); + pFrame = pFrame->GetNext(); + } +} + +void SwLooping::Control( SwPageFrame* pPage ) +{ + if( !pPage ) + return; + const sal_uInt16 nNew = pPage->GetPhyPageNum(); + if( nNew > nMaxPage ) + nMaxPage = nNew; + if( nNew < nMinPage ) + { + nMinPage = nNew; + nMaxPage = nNew; + nCount = 0; + mnLoopControlStage = 0; + } + else if( nNew > nMinPage + 2 ) + { + nMinPage = nNew - 2; + nMaxPage = nNew; + nCount = 0; + mnLoopControlStage = 0; + } + else if( ++nCount > LOOP_DETECT ) + { +#if OSL_DEBUG_LEVEL > 1 + static bool bNoLouie = false; + if( bNoLouie ) + return; + + // FME 2007-08-30 #i81146# new loop control + OSL_ENSURE( 0 != mnLoopControlStage, "Looping Louie: Stage 1!" ); + OSL_ENSURE( 1 != mnLoopControlStage, "Looping Louie: Stage 2!!" ); + OSL_ENSURE( 2 > mnLoopControlStage, "Looping Louie: Stage 3!!!" ); +#endif + + Drastic( pPage->Lower() ); + if( nNew > nMinPage && pPage->GetPrev() ) + Drastic( static_cast<SwPageFrame*>(pPage->GetPrev())->Lower() ); + if( nNew < nMaxPage && pPage->GetNext() ) + Drastic( static_cast<SwPageFrame*>(pPage->GetNext())->Lower() ); + + ++mnLoopControlStage; + nCount = 0; + } +} + +SwLayouter::SwLayouter() +{ +} + +SwLayouter::~SwLayouter() +{ +} + +void SwLayouter::CollectEndnotes_( SwSectionFrame* pSect ) +{ + if( !mpEndnoter ) + mpEndnoter.reset(new SwEndnoter( this )); + mpEndnoter->CollectEndnotes( pSect ); +} + +bool SwLayouter::HasEndnotes() const +{ + return mpEndnoter->HasEndnotes(); +} + +void SwLayouter::CollectEndnote( SwFootnoteFrame* pFootnote ) +{ + mpEndnoter->CollectEndnote( pFootnote ); +} + +void SwLayouter::InsertEndnotes( SwSectionFrame const * pSect ) +{ + if( !mpEndnoter || mpEndnoter->GetSect() != pSect ) + return; + mpEndnoter->InsertEndnotes(); +} + +void SwLayouter::LoopControl( SwPageFrame* pPage ) +{ + OSL_ENSURE( mpLooping, "Looping: Lost control" ); + mpLooping->Control( pPage ); +} + +void SwLayouter::LoopingLouieLight( const SwDoc& rDoc, const SwTextFrame& rFrame ) +{ + if ( mpLooping && mpLooping->IsLoopingLouieLight() ) + { +#if OSL_DEBUG_LEVEL > 1 + OSL_FAIL( "Looping Louie (Light): Fixating fractious frame" ); +#endif + SwLayouter::InsertMovedFwdFrame( rDoc, rFrame, rFrame.FindPageFrame()->GetPhyPageNum() ); + } +} + +bool SwLayouter::StartLooping( SwPageFrame const * pPage ) +{ + if( mpLooping ) + return false; + mpLooping.reset(new SwLooping( pPage )); + return true; +} + +void SwLayouter::EndLoopControl() +{ + mpLooping.reset(); +} + +void SwLayouter::CollectEndnotes( SwDoc* pDoc, SwSectionFrame* pSect ) +{ + assert(pDoc && "No doc, no fun"); + if( !pDoc->getIDocumentLayoutAccess().GetLayouter() ) + pDoc->getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + pDoc->getIDocumentLayoutAccess().GetLayouter()->CollectEndnotes_( pSect ); +} + +bool SwLayouter::Collecting( SwDoc* pDoc, SwSectionFrame const * pSect, SwFootnoteFrame* pFootnote ) +{ + if( !pDoc->getIDocumentLayoutAccess().GetLayouter() ) + return false; + SwLayouter *pLayouter = pDoc->getIDocumentLayoutAccess().GetLayouter(); + if( pLayouter->mpEndnoter && pLayouter->mpEndnoter->GetSect() && pSect && + ( pLayouter->mpEndnoter->GetSect()->IsAnFollow( pSect ) || + pSect->IsAnFollow( pLayouter->mpEndnoter->GetSect() ) ) ) + { + if( pFootnote ) + pLayouter->CollectEndnote( pFootnote ); + return true; + } + return false; +} + +bool SwLayouter::StartLoopControl( SwDoc* pDoc, SwPageFrame const *pPage ) +{ + OSL_ENSURE( pDoc, "No doc, no fun" ); + if( !pDoc->getIDocumentLayoutAccess().GetLayouter() ) + pDoc->getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + return !pDoc->getIDocumentLayoutAccess().GetLayouter()->mpLooping && + pDoc->getIDocumentLayoutAccess().GetLayouter()->StartLooping( pPage ); +} + +// #i28701# +// methods to manage text frames, which are moved forward by the positioning +// of its anchored objects +void SwLayouter::ClearMovedFwdFrames( const SwDoc& _rDoc ) +{ + if ( _rDoc.getIDocumentLayoutAccess().GetLayouter() && + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames->Clear(); + } +} + +void SwLayouter::InsertMovedFwdFrame( const SwDoc& _rDoc, + const SwTextFrame& _rMovedFwdFrameByObjPos, + const sal_uInt32 _nToPageNum ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + } + + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames.reset( + new SwMovedFwdFramesByObjPos()); + } + + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames->Insert( _rMovedFwdFrameByObjPos, + _nToPageNum ); +} + +// #i40155# +void SwLayouter::RemoveMovedFwdFrame( const SwDoc& _rDoc, + const SwTextFrame& _rTextFrame ) +{ + sal_uInt32 nDummy; + if ( SwLayouter::FrameMovedFwdByObjPos( _rDoc, _rTextFrame, nDummy ) ) + { + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames->Remove( _rTextFrame ); + } +} + +bool SwLayouter::FrameMovedFwdByObjPos( const SwDoc& _rDoc, + const SwTextFrame& _rTextFrame, + sal_uInt32& _ornToPageNum ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + _ornToPageNum = 0; + return false; + } + else if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + _ornToPageNum = 0; + return false; + } + else + { + return _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames-> + FrameMovedFwdByObjPos( _rTextFrame, _ornToPageNum ); + } +} + +// #i26945# +bool SwLayouter::DoesRowContainMovedFwdFrame( const SwDoc& _rDoc, + const SwRowFrame& _rRowFrame ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + return false; + } + else if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + return false; + } + else + { + return _rDoc.getIDocumentLayoutAccess().GetLayouter()-> + mpMovedFwdFrames->DoesRowContainMovedFwdFrame( _rRowFrame ); + } +} + +// #i35911# +void SwLayouter::ClearObjsTmpConsiderWrapInfluence( const SwDoc& _rDoc ) +{ + if ( _rDoc.getIDocumentLayoutAccess().GetLayouter() && + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl ) + { + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl->Clear(); + } +} + +void SwLayouter::InsertObjForTmpConsiderWrapInfluence( + const SwDoc& _rDoc, + SwAnchoredObject& _rAnchoredObj ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + } + + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl.reset( + new SwObjsMarkedAsTmpConsiderWrapInfluence()); + } + + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl->Insert( _rAnchoredObj ); +} + +void SwLayouter::RemoveObjForTmpConsiderWrapInfluence( + const SwDoc& _rDoc, + SwAnchoredObject& _rAnchoredObj ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + return; + + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl ) + return; + + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl->Remove( _rAnchoredObj ); +} + + +void LOOPING_LOUIE_LIGHT( bool bCondition, const SwTextFrame& rTextFrame ) +{ + if ( bCondition ) + { + const SwDoc& rDoc = *rTextFrame.GetAttrSet()->GetDoc(); + if ( rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(rDoc).getIDocumentLayoutAccess().GetLayouter()->LoopingLouieLight( rDoc, rTextFrame ); + } + } +} + +// #i65250# +bool SwLayouter::MoveBwdSuppressed( const SwDoc& p_rDoc, + const SwFlowFrame& p_rFlowFrame, + const SwLayoutFrame& p_rNewUpperFrame ) +{ + bool bMoveBwdSuppressed( false ); + + if ( !p_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(p_rDoc).getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + } + + // create hash map key + tMoveBwdLayoutInfoKey aMoveBwdLayoutInfo; + aMoveBwdLayoutInfo.mnFrameId = p_rFlowFrame.GetFrame().GetFrameId(); + aMoveBwdLayoutInfo.mnNewUpperPosX = p_rNewUpperFrame.getFrameArea().Pos().X(); + aMoveBwdLayoutInfo.mnNewUpperPosY = p_rNewUpperFrame.getFrameArea().Pos().Y(); + aMoveBwdLayoutInfo.mnNewUpperWidth = p_rNewUpperFrame.getFrameArea().Width(); + aMoveBwdLayoutInfo.mnNewUpperHeight = p_rNewUpperFrame.getFrameArea().Height(); + SwRectFnSet aRectFnSet(&p_rNewUpperFrame); + const SwFrame* pLastLower( p_rNewUpperFrame.Lower() ); + while ( pLastLower && pLastLower->GetNext() ) + { + pLastLower = pLastLower->GetNext(); + } + aMoveBwdLayoutInfo.mnFreeSpaceInNewUpper = + pLastLower + ? aRectFnSet.BottomDist( pLastLower->getFrameArea(), aRectFnSet.GetPrtBottom(p_rNewUpperFrame) ) + : aRectFnSet.GetHeight(p_rNewUpperFrame.getFrameArea()); + + // check for moving backward suppress threshold + const sal_uInt16 cMoveBwdCountSuppressThreshold = 20; + if ( ++const_cast<SwDoc&>(p_rDoc).getIDocumentLayoutAccess().GetLayouter()->maMoveBwdLayoutInfo[ aMoveBwdLayoutInfo ] > + cMoveBwdCountSuppressThreshold ) + { + bMoveBwdSuppressed = true; + } + + return bMoveBwdSuppressed; +} + +void SwLayouter::ClearMoveBwdLayoutInfo( const SwDoc& _rDoc ) +{ + if ( _rDoc.getIDocumentLayoutAccess().GetLayouter() ) + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().GetLayouter()->maMoveBwdLayoutInfo.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/legacyitem.cxx b/sw/source/core/layout/legacyitem.cxx new file mode 100644 index 000000000..932afa918 --- /dev/null +++ b/sw/source/core/layout/legacyitem.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <legacyitem.hxx> +#include <tools/stream.hxx> +#include <sal/log.hxx> +#include <fmtornt.hxx> + +namespace legacy::SwFormatVert +{ + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SwFormatVertOrient& rItem, SvStream& rStrm, sal_uInt16 nVersionAbusedAsSize) + { + SwTwips yPos(0); + sal_Int16 orient(0); + sal_Int16 relation(0); + + switch (nVersionAbusedAsSize) + { + // compatibility hack for Table Auto Format: SwTwips is "long" :( + // (this means that the file format is platform dependent) + case 14: + { + sal_Int64 n(0); + rStrm.ReadInt64(n); + yPos = n; + break; + } + case 10: + { + sal_Int32 n(0); + rStrm.ReadInt32(n); + yPos = n; + break; + } + default: + SAL_WARN("sw.core", "SwFormatVertOrient::Create: unknown size"); + } + + rStrm.ReadInt16( orient ).ReadInt16( relation ); + + rItem.SetPos(yPos); + rItem.SetVertOrient(orient); + rItem.SetRelationOrient(relation); + } + + SvStream& Store(const SwFormatVertOrient& rItem, SvStream& rStrm, sal_uInt16) + { +#if SAL_TYPES_SIZEOFLONG == 8 + rStrm.WriteInt64(rItem.GetPos()); +#else + rStrm.WriteInt32(rItem.GetPos()); +#endif + rStrm.WriteInt16(rItem.GetVertOrient()).WriteInt16(rItem.GetRelationOrient()); + return rStrm; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/movedfwdfrmsbyobjpos.cxx b/sw/source/core/layout/movedfwdfrmsbyobjpos.cxx new file mode 100644 index 000000000..7aba4b74a --- /dev/null +++ b/sw/source/core/layout/movedfwdfrmsbyobjpos.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 <movedfwdfrmsbyobjpos.hxx> +#include <txtfrm.hxx> +#include <rowfrm.hxx> +#include <pagefrm.hxx> +#include <calbck.hxx> +#include <ndtxt.hxx> + +SwMovedFwdFramesByObjPos::SwMovedFwdFramesByObjPos() +{ +} + +SwMovedFwdFramesByObjPos::~SwMovedFwdFramesByObjPos() +{ + Clear(); +} + +void SwMovedFwdFramesByObjPos::Insert( const SwTextFrame& _rMovedFwdFrameByObjPos, + const sal_uInt32 _nToPageNum ) +{ + maMovedFwdFrames.emplace(_rMovedFwdFrameByObjPos.GetTextNodeFirst(), _nToPageNum); +} + +void SwMovedFwdFramesByObjPos::Remove( const SwTextFrame& _rTextFrame ) +{ + maMovedFwdFrames.erase(_rTextFrame.GetTextNodeFirst()); +} + +bool SwMovedFwdFramesByObjPos::FrameMovedFwdByObjPos( const SwTextFrame& _rTextFrame, + sal_uInt32& _ornToPageNum ) const +{ + // sw_redlinehide: assumption: this wants to uniquely identify all + // SwTextFrame belonging to the same paragraph, so just use first one as key + auto aIter = maMovedFwdFrames.find( _rTextFrame.GetTextNodeFirst() ); + if ( maMovedFwdFrames.end() != aIter ) + { + _ornToPageNum = (*aIter).second; + return true; + } + + return false; +} + +// #i26945# +bool SwMovedFwdFramesByObjPos::DoesRowContainMovedFwdFrame( const SwRowFrame& _rRowFrame ) const +{ + bool bDoesRowContainMovedFwdFrame( false ); + + const sal_uInt32 nPageNumOfRow = _rRowFrame.FindPageFrame()->GetPhyPageNum(); + + for ( const auto & rEntry : maMovedFwdFrames ) + { + if ( rEntry.second >= nPageNumOfRow ) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aFrameIter(*rEntry.first); + for( SwTextFrame* pTextFrame = aFrameIter.First(); pTextFrame; pTextFrame = aFrameIter.Next() ) + { + // #115759# - assure that found text frame + // is the first one. + if ( _rRowFrame.IsAnLower( pTextFrame ) && !pTextFrame->GetIndPrev() ) + { + bDoesRowContainMovedFwdFrame = true; + break; + } + } + } + } + + return bDoesRowContainMovedFwdFrame; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/newfrm.cxx b/sw/source/core/layout/newfrm.cxx new file mode 100644 index 000000000..07ba49b7b --- /dev/null +++ b/sw/source/core/layout/newfrm.cxx @@ -0,0 +1,613 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <svx/svdpage.hxx> +#include <drawdoc.hxx> +#include <fmtpdsc.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <dflyobj.hxx> +#include <frmtool.hxx> +#include "virtoutp.hxx" +#include <notxtfrm.hxx> +#include <pagedesc.hxx> +#include <viewimp.hxx> +#include <hints.hxx> +#include <viewopt.hxx> +#include <set> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <DocumentLayoutManager.hxx> +#include <DocumentRedlineManager.hxx> +#include <ndindex.hxx> + +SwLayVout *SwRootFrame::s_pVout = nullptr; +bool SwRootFrame::s_isInPaint = false; +bool SwRootFrame::s_isNoVirDev = false; + +SwCache *SwFrame::mpCache = nullptr; + +static long FirstMinusSecond( long nFirst, long nSecond ) + { return nFirst - nSecond; } +static long SecondMinusFirst( long nFirst, long nSecond ) + { return nSecond - nFirst; } +static long SwIncrement( long nA, long nAdd ) + { return nA + nAdd; } +static long SwDecrement( long nA, long nSub ) + { return nA - nSub; } + +static SwRectFnCollection aHorizontal = { + /*.fnGetTop =*/&SwRect::Top_, + /*.fnGetBottom =*/&SwRect::Bottom_, + /*.fnGetLeft =*/&SwRect::Left_, + /*.fnGetRight =*/&SwRect::Right_, + /*.fnGetWidth =*/&SwRect::Width_, + /*.fnGetHeight =*/&SwRect::Height_, + /*.fnGetPos =*/&SwRect::TopLeft, + /*.fnGetSize =*/&SwRect::Size_, + + /*.fnSetTop =*/&SwRect::Top_, + /*.fnSetBottom =*/&SwRect::Bottom_, + /*.fnSetLeft =*/&SwRect::Left_, + /*.fnSetRight =*/&SwRect::Right_, + /*.fnSetWidth =*/&SwRect::Width_, + /*.fnSetHeight =*/&SwRect::Height_, + + /*.fnSubTop =*/&SwRect::SubTop, + /*.fnAddBottom =*/&SwRect::AddBottom, + /*.fnSubLeft =*/&SwRect::SubLeft, + /*.fnAddRight =*/&SwRect::AddRight, + /*.fnAddWidth =*/&SwRect::AddWidth, + /*.fnAddHeight =*/&SwRect::AddHeight, + + /*.fnSetPosX =*/&SwRect::SetPosX, + /*.fnSetPosY =*/&SwRect::SetPosY, + + /*.fnGetTopMargin =*/&SwFrame::GetTopMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetBottomMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetRightMargin =*/&SwFrame::GetRightMargin, + /*.fnSetXMargins =*/&SwFrame::SetLeftRightMargins, + /*.fnSetYMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtTop, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtBottom, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtRight =*/&SwFrame::GetPrtRight, + /*.fnTopDist =*/&SwRect::GetTopDistance, + /*.fnBottomDist =*/&SwRect::GetBottomDistance, + /*.fnLeftDist =*/&SwRect::GetLeftDistance, + /*.fnRightDist =*/&SwRect::GetRightDistance, + /*.fnSetLimit =*/&SwFrame::SetMaxBottom, + /*.fnOverStep =*/&SwRect::OverStepBottom, + + /*.fnSetPos =*/&SwRect::SetUpperLeftCorner, + /*.fnMakePos =*/&SwFrame::MakeBelowPos, + /*.fnXDiff =*/&FirstMinusSecond, + /*.fnYDiff =*/&FirstMinusSecond, + /*.fnXInc =*/&SwIncrement, + /*.fnYInc =*/&o3tl::saturating_add<long>, + + /*.fnSetLeftAndWidth =*/&SwRect::SetLeftAndWidth, + /*.fnSetTopAndHeight =*/&SwRect::SetTopAndHeight +}; + +static SwRectFnCollection aVertical = { + /*.fnGetTop =*/&SwRect::Right_, + /*.fnGetBottom =*/&SwRect::Left_, + /*.fnGetLeft =*/&SwRect::Top_, + /*.fnGetRight =*/&SwRect::Bottom_, + /*.fnGetWidth =*/&SwRect::Height_, + /*.fnGetHeight =*/&SwRect::Width_, + /*.fnGetPos =*/&SwRect::TopRight, + /*.fnGetSize =*/&SwRect::SwappedSize, + + /*.fnSetTop =*/&SwRect::Right_, + /*.fnSetBottom =*/&SwRect::Left_, + /*.fnSetLeft =*/&SwRect::Top_, + /*.fnSetRight =*/&SwRect::Bottom_, + /*.fnSetWidth =*/&SwRect::Height_, + /*.fnSetHeight =*/&SwRect::Width_, + + /*.fnSubTop =*/&SwRect::AddRight, + /*.fnAddBottom =*/&SwRect::SubLeft, + /*.fnSubLeft =*/&SwRect::SubTop, + /*.fnAddRight =*/&SwRect::AddBottom, + /*.fnAddWidth =*/&SwRect::AddHeight, + /*.fnAddHeight =*/&SwRect::AddWidth, + + /*.fnSetPosX =*/&SwRect::SetPosY, + /*.fnSetPosY =*/&SwRect::SetPosX, + + /*.fnGetTopMargin =*/&SwFrame::GetRightMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetTopMargin, + /*.fnGetRightMargin =*/&SwFrame::GetBottomMargin, + /*.fnSetXMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnSetYMargins =*/&SwFrame::SetRightLeftMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtRight, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtTop, + /*.fnGetPrtRight =*/&SwFrame::GetPrtBottom, + /*.fnTopDist =*/&SwRect::GetRightDistance, + /*.fnBottomDist =*/&SwRect::GetLeftDistance, + /*.fnLeftDist =*/&SwRect::GetTopDistance, + /*.fnRightDist =*/&SwRect::GetBottomDistance, + /*.fnSetLimit =*/&SwFrame::SetMinLeft, + /*.fnOverStep =*/&SwRect::OverStepLeft, + + /*.fnSetPos =*/&SwRect::SetUpperRightCorner, + /*.fnMakePos =*/&SwFrame::MakeLeftPos, + /*.fnXDiff =*/&FirstMinusSecond, + /*.fnYDiff =*/&SecondMinusFirst, + /*.fnXInc =*/&SwIncrement, + /*.fnYInc =*/&SwDecrement, + + /*.fnSetLeftAndWidth =*/&SwRect::SetTopAndHeight, + /*.fnSetTopAndHeight =*/&SwRect::SetRightAndWidth +}; + +static SwRectFnCollection aVerticalLeftToRight = { + /*.fnGetTop =*/&SwRect::Left_, + /*.fnGetBottom =*/&SwRect::Right_, + /*.fnGetLeft =*/&SwRect::Top_, + /*.fnGetRight =*/&SwRect::Bottom_, + /*.fnGetWidth =*/&SwRect::Height_, + /*.fnGetHeight =*/&SwRect::Width_, + /*.fnGetPos =*/&SwRect::TopLeft, + /*.fnGetSize =*/&SwRect::SwappedSize, + + /*.fnSetTop =*/&SwRect::Left_, + /*.fnSetBottom =*/&SwRect::Right_, + /*.fnSetLeft =*/&SwRect::Top_, + /*.fnSetRight =*/&SwRect::Bottom_, + /*.fnSetWidth =*/&SwRect::Height_, + /*.fnSetHeight =*/&SwRect::Width_, + + /*.fnSubTop =*/&SwRect::SubLeft, + /*.fnAddBottom =*/&SwRect::AddRight, + /*.fnSubLeft =*/&SwRect::SubTop, + /*.fnAddRight =*/&SwRect::AddBottom, + /*.fnAddWidth =*/&SwRect::AddHeight, + /*.fnAddHeight =*/&SwRect::AddWidth, + + /*.fnSetPosX =*/&SwRect::SetPosY, + /*.fnSetPosY =*/&SwRect::SetPosX, + + /*.fnGetTopMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetRightMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetTopMargin, + /*.fnGetRightMargin =*/&SwFrame::GetBottomMargin, + /*.fnSetXMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnSetYMargins =*/&SwFrame::SetLeftRightMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtRight, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtTop, + /*.fnGetPrtRight =*/&SwFrame::GetPrtBottom, + /*.fnTopDist =*/&SwRect::GetLeftDistance, + /*.fnBottomDist =*/&SwRect::GetRightDistance, + /*.fnLeftDist =*/&SwRect::GetTopDistance, + /*.fnRightDist =*/&SwRect::GetBottomDistance, + /*.fnSetLimit =*/&SwFrame::SetMaxRight, + /*.fnOverStep =*/&SwRect::OverStepRight, + + /*.fnSetPos =*/&SwRect::SetUpperLeftCorner, + /*.fnMakePos =*/&SwFrame::MakeRightPos, + /*.fnXDiff =*/&FirstMinusSecond, + /*.fnYDiff =*/&FirstMinusSecond, + /*.fnXInc =*/&SwIncrement, + /*.fnYInc =*/&SwIncrement, + + /*.fnSetLeftAndWidth =*/&SwRect::SetTopAndHeight, + /*.fnSetTopAndHeight =*/&SwRect::SetLeftAndWidth +}; + +/** + * This is the same as horizontal, but rotated counter-clockwise by 90 degrees. + * This means logical top is physical left, bottom is right, left is bottom, + * finally right is top. Values map from logical to physical. + */ +static SwRectFnCollection aVerticalLeftToRightBottomToTop = { + /*.fnGetTop =*/&SwRect::Left_, + /*.fnGetBottom =*/&SwRect::Right_, + /*.fnGetLeft =*/&SwRect::Bottom_, + /*.fnGetRight =*/&SwRect::Top_, + /*.fnGetWidth =*/&SwRect::Height_, + /*.fnGetHeight =*/&SwRect::Width_, + /*.fnGetPos =*/&SwRect::BottomLeft, + /*.fnGetSize =*/&SwRect::SwappedSize, + + /*.fnSetTop =*/&SwRect::Left_, + /*.fnSetBottom =*/&SwRect::Right_, + /*.fnSetLeft =*/&SwRect::Bottom_, + /*.fnSetRight =*/&SwRect::Top_, + /*.fnSetWidth =*/&SwRect::Height_, + /*.fnSetHeight =*/&SwRect::Width_, + + /*.fnSubTop =*/&SwRect::SubLeft, + /*.fnAddBottom =*/&SwRect::AddRight, + /*.fnSubLeft =*/&SwRect::AddBottom, + /*.fnAddRight =*/&SwRect::SubTop, + /*.fnAddWidth =*/&SwRect::AddHeight, + /*.fnAddHeight =*/&SwRect::AddWidth, + + /*.fnSetPosX =*/&SwRect::SetPosY, + /*.fnSetPosY =*/&SwRect::SetPosX, + + /*.fnGetTopMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetRightMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetBottomMargin, + /*.fnGetRightMargin =*/&SwFrame::GetTopMargin, + /*.fnSetXMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnSetYMargins =*/&SwFrame::SetLeftRightMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtRight, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtBottom, + /*.fnGetPrtRight =*/&SwFrame::GetPrtTop, + /*.fnTopDist =*/&SwRect::GetLeftDistance, + /*.fnBottomDist =*/&SwRect::GetRightDistance, + /*.fnLeftDist =*/&SwRect::GetBottomDistance, + /*.fnRightDist =*/&SwRect::GetTopDistance, + /*.fnSetLimit =*/&SwFrame::SetMaxRight, + /*.fnOverStep =*/&SwRect::OverStepRight, + + /*.fnSetPos =*/&SwRect::SetLowerLeftCorner, + /*.fnMakePos =*/&SwFrame::MakeRightPos, + /*.fnXDiff =*/&SecondMinusFirst, + /*.fnYDiff =*/&FirstMinusSecond, + /*.fnXInc =*/&SwDecrement, + /*.fnYInc =*/&SwIncrement, + + /*.fnSetLeftAndWidth =*/&SwRect::SetBottomAndHeight, + /*.fnSetTopAndHeight =*/&SwRect::SetLeftAndWidth +}; + +SwRectFn fnRectHori = &aHorizontal; +SwRectFn fnRectVert = &aVertical; +SwRectFn fnRectVertL2R = &aVerticalLeftToRight; +SwRectFn fnRectVertL2RB2T = &aVerticalLeftToRightBottomToTop; + +// #i65250# +sal_uInt32 SwFrameAreaDefinition::mnLastFrameId=0; + + +void FrameInit() +{ + SwRootFrame::s_pVout = new SwLayVout(); + SwCache *pNew = new SwCache( 100 +#ifdef DBG_UTIL + , "static SwBorderAttrs::pCache" +#endif + ); + SwFrame::SetCache( pNew ); +} + +void FrameFinit() +{ +#if OSL_DEBUG_LEVEL > 0 + // The cache may only contain null pointers at this time. + for( size_t n = SwFrame::GetCachePtr()->size(); n; ) + if( (*SwFrame::GetCachePtr())[ --n ] ) + { + SwCacheObj* pObj = (*SwFrame::GetCachePtr())[ n ]; + OSL_ENSURE( !pObj, "Who didn't deregister?"); + } +#endif + delete SwRootFrame::s_pVout; + delete SwFrame::GetCachePtr(); +} + +// RootFrame::Everything that belongs to CurrShell + +CurrShell::CurrShell( SwViewShell *pNew ) +{ + OSL_ENSURE( pNew, "insert 0-Shell?" ); + pRoot = pNew->GetLayout(); + if ( pRoot ) + { + pPrev = pRoot->mpCurrShell; + pRoot->mpCurrShell = pNew; + pRoot->mpCurrShells->insert( this ); + } + else + pPrev = nullptr; +} + +CurrShell::~CurrShell() +{ + if ( pRoot ) + { + pRoot->mpCurrShells->erase( this ); + if ( pPrev ) + pRoot->mpCurrShell = pPrev; + if ( pRoot->mpCurrShells->empty() && pRoot->mpWaitingCurrShell ) + { + pRoot->mpCurrShell = pRoot->mpWaitingCurrShell; + pRoot->mpWaitingCurrShell = nullptr; + } + } +} + +void SetShell( SwViewShell *pSh ) +{ + SwRootFrame *pRoot = pSh->GetLayout(); + if ( pRoot->mpCurrShells->empty() ) + pRoot->mpCurrShell = pSh; + else + pRoot->mpWaitingCurrShell = pSh; +} + +void SwRootFrame::DeRegisterShell( SwViewShell *pSh ) +{ + // Activate some shell if possible + if ( mpCurrShell == pSh ) + { + mpCurrShell = nullptr; + for(SwViewShell& rShell : pSh->GetRingContainer()) + { + if(&rShell != pSh) + { + mpCurrShell = &rShell; + break; + } + } + } + + // Doesn't matter anymore + if ( mpWaitingCurrShell == pSh ) + mpWaitingCurrShell = nullptr; + + // Remove references + for ( CurrShell *pC : *mpCurrShells ) + { + if (pC->pPrev == pSh) + pC->pPrev = nullptr; + } +} + +void InitCurrShells( SwRootFrame *pRoot ) +{ + pRoot->mpCurrShells.reset( new SwCurrShells ); +} + +/* +|* The RootFrame requests an own FrameFormat from the document, which it is +|* going to delete again in the dtor. The own FrameFormat is derived from +|* the passed FrameFormat. +|*/ +SwRootFrame::SwRootFrame( SwFrameFormat *pFormat, SwViewShell * pSh ) : + SwLayoutFrame( pFormat->GetDoc()->MakeFrameFormat( + "Root", pFormat ), nullptr ), + maPagesArea(), + mnViewWidth( -1 ), + mnColumns( 0 ), + mbBookMode( false ), + mbSidebarChanged( false ), + mbNeedGrammarCheck( false ), + mbCheckSuperfluous( false ), + mbIdleFormat( true ), + mbBrowseWidthValid( false ), + mbTurboAllowed( true ), + mbAssertFlyPages( true ), + mbIsVirtPageNum( false ), + mbIsNewLayout( true ), + mbCallbackActionEnabled ( false ), + mbLayoutFreezed ( false ), + mbHideRedlines(pFormat->GetDoc()->GetDocumentRedlineManager().IsHideRedlines()), + mnBrowseWidth(MIN_BROWSE_WIDTH), + mpTurbo( nullptr ), + mpLastPage( nullptr ), + mpCurrShell( pSh ), + mpWaitingCurrShell( nullptr ), + mpDrawPage( nullptr ), + mnPhyPageNums( 0 ), + mnAccessibleShells( 0 ) +{ + mnFrameType = SwFrameType::Root; + setRootFrame( this ); +} + +void SwRootFrame::Init( SwFrameFormat* pFormat ) +{ + InitCurrShells( this ); + + IDocumentTimerAccess& rTimerAccess = pFormat->getIDocumentTimerAccess(); + IDocumentLayoutAccess& rLayoutAccess = pFormat->getIDocumentLayoutAccess(); + IDocumentFieldsAccess& rFieldsAccess = pFormat->getIDocumentFieldsAccess(); + const IDocumentSettingAccess& rSettingAccess = pFormat->getIDocumentSettingAccess(); + rTimerAccess.StopIdling(); + // For creating the Flys by MakeFrames() + rLayoutAccess.SetCurrentViewShell( GetCurrShell() ); + mbCallbackActionEnabled = false; // needs to be set to true before leaving! + + SwDrawModel* pMd = pFormat->getIDocumentDrawModelAccess().GetDrawModel(); + if ( pMd ) + { + // Disable "multiple layout" + mpDrawPage = pMd->GetPage(0); + + mpDrawPage->SetSize( getFrameArea().SSize() ); + } + + // Initialize the layout: create pages, link content with Content etc. + // First, initialize some stuff, then get hold of the first + // node (which will be needed for the PageDesc). + + SwDoc* pDoc = pFormat->GetDoc(); + SwNodeIndex aIndex( *pDoc->GetNodes().GetEndOfContent().StartOfSectionNode() ); + SwContentNode *pNode = pDoc->GetNodes().GoNextSection( &aIndex, true, false ); + // #123067# pNode = 0 can really happen + SwTableNode *pTableNd= pNode ? pNode->FindTableNode() : nullptr; + + // Get hold of PageDesc (either via FrameFormat of the first node or the initial one). + SwPageDesc *pDesc = nullptr; + ::std::optional<sal_uInt16> oPgNum; + + if ( pTableNd ) + { + const SwFormatPageDesc &rDesc = pTableNd->GetTable().GetFrameFormat()->GetPageDesc(); + pDesc = const_cast<SwPageDesc*>(rDesc.GetPageDesc()); + //#19104# respect the page number offset!! + oPgNum = rDesc.GetNumOffset(); + if (oPgNum) + mbIsVirtPageNum = true; + } + else if ( pNode ) + { + const SwFormatPageDesc &rDesc = pNode->GetSwAttrSet().GetPageDesc(); + pDesc = const_cast<SwPageDesc*>(rDesc.GetPageDesc()); + //#19104# respect the page number offset!! + oPgNum = rDesc.GetNumOffset(); + if (oPgNum) + mbIsVirtPageNum = true; + } + else + mbIsVirtPageNum = false; + if ( !pDesc ) + pDesc = &pDoc->GetPageDesc( 0 ); + + // Create a page and put it in the layout + // The first page is always a right-page and always a first-page + SwPageFrame *pPage = ::InsertNewPage(*pDesc, this, true, true, false, false, nullptr); + + // Find the first page in the Bodytext section. + SwLayoutFrame *pLay = pPage->FindBodyCont(); + while( pLay->Lower() ) + pLay = static_cast<SwLayoutFrame*>(pLay->Lower()); + + SwNodeIndex aTmp( *pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), 1 ); + ::InsertCnt_( pLay, pDoc, aTmp.GetIndex(), true ); + //Remove masters that haven't been replaced yet from the list. + RemoveMasterObjs( mpDrawPage ); + if( rSettingAccess.get(DocumentSettingId::GLOBAL_DOCUMENT) ) + rFieldsAccess.UpdateRefFields(); + //b6433357: Update page fields after loading + if ( !mpCurrShell || !mpCurrShell->Imp()->IsUpdateExpFields() ) + { + SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); + rFieldsAccess.UpdatePageFields( &aMsgHint ); + } + + rTimerAccess.StartIdling(); + mbCallbackActionEnabled = true; + + SwViewShell *pViewSh = GetCurrShell(); + if (pViewSh) + mbNeedGrammarCheck = pViewSh->GetViewOptions()->IsOnlineSpell(); +} + +void SwRootFrame::DestroyImpl() +{ + mbTurboAllowed = false; + mpTurbo = nullptr; + + SwFrameFormat *pRegisteredInNonConst = static_cast<SwFrameFormat*>(GetDep()); + if ( pRegisteredInNonConst ) + { + SwDoc *pDoc = pRegisteredInNonConst->GetDoc(); + pDoc->DelFrameFormat( pRegisteredInNonConst ); + // do this before calling RemoveFootnotes() because footnotes + // can contain anchored objects + pDoc->GetDocumentLayoutManager().ClearSwLayouterEntries(); + } + + mpDestroy.reset(); + + // Remove references + for ( auto& rpCurrShell : *mpCurrShells ) + rpCurrShell->pRoot = nullptr; + + mpCurrShells.reset(); + + // Some accessible shells are left => problems on second SwFrame::Destroy call + assert(0 == mnAccessibleShells); + + // fdo#39510 crash on document close with footnotes + // Object ownership in writer and esp. in layout are a mess: Before the + // document/layout split SwDoc and SwRootFrame were essentially one object + // and magically/uncleanly worked around their common destruction by call + // to SwDoc::IsInDtor() -- even from the layout. As of now destruction of + // the layout proceeds forward through the frames. Since SwTextFootnote::DelFrames + // also searches backwards to find the master of footnotes, they must be + // considered to be owned by the SwRootFrame and also be destroyed here, + // before tearing down the (now footnote free) rest of the layout. + RemoveFootnotes(nullptr, false, true); + + SwLayoutFrame::DestroyImpl(); +} + +SwRootFrame::~SwRootFrame() +{ +} + +void SwRootFrame::RemoveMasterObjs( SdrPage *pPg ) +{ + // Remove all master objects from the Page. But don't delete! + for( size_t i = pPg ? pPg->GetObjCount() : 0; i; ) + { + SdrObject* pObj = pPg->GetObj( --i ); + if( dynamic_cast< const SwFlyDrawObj *>( pObj ) != nullptr ) + pPg->RemoveObject( i ); + } +} + +void SwRootFrame::AllCheckPageDescs() const +{ + if ( !IsLayoutFreezed() ) + CheckPageDescs( const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())) ); +} + +void SwRootFrame::AllInvalidateAutoCompleteWords() const +{ + SwPageFrame *pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())); + while ( pPage ) + { + pPage->InvalidateAutoCompleteWords(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } +} + +void SwRootFrame::AllAddPaintRect() const +{ + GetCurrShell()->AddPaintRect( getFrameArea() ); +} + +void SwRootFrame::AllRemoveFootnotes() +{ + RemoveFootnotes(); +} + +void SwRootFrame::AllInvalidateSmartTagsOrSpelling(bool bSmartTags) const +{ + SwPageFrame *pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())); + while ( pPage ) + { + if ( bSmartTags ) + pPage->InvalidateSmartTags(); + + pPage->InvalidateSpelling(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformatter.cxx b/sw/source/core/layout/objectformatter.cxx new file mode 100644 index 000000000..e48439a2b --- /dev/null +++ b/sw/source/core/layout/objectformatter.cxx @@ -0,0 +1,478 @@ +/* -*- 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 "objectformattertxtfrm.hxx" +#include "objectformatterlayfrm.hxx" +#include <anchoreddrawobject.hxx> +#include <sortedobjs.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <flyfrms.hxx> +#include <txtfrm.hxx> +#include <layact.hxx> +#include <IDocumentSettingAccess.hxx> + +#include <vector> + +// --> #i26945# - Additionally the type of the anchor text frame +// is collected - by type is meant 'master' or 'follow'. +class SwPageNumAndTypeOfAnchors +{ + private: + struct tEntry + { + SwAnchoredObject* mpAnchoredObj; + sal_uInt32 mnPageNumOfAnchor; + bool mbAnchoredAtMaster; + }; + + std::vector< tEntry > maObjList; + + public: + SwPageNumAndTypeOfAnchors() + { + } + + void Collect( SwAnchoredObject& _rAnchoredObj ) + { + tEntry aNewEntry; + aNewEntry.mpAnchoredObj = &_rAnchoredObj; + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = _rAnchoredObj.FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor ) + { + aNewEntry.mnPageNumOfAnchor = pPageFrameOfAnchor->GetPhyPageNum(); + } + else + { + aNewEntry.mnPageNumOfAnchor = 0; + } + // --> #i26945# - collect type of anchor + SwTextFrame* pAnchorCharFrame = _rAnchoredObj.FindAnchorCharFrame(); + if ( pAnchorCharFrame ) + { + aNewEntry.mbAnchoredAtMaster = !pAnchorCharFrame->IsFollow(); + } + else + { + aNewEntry.mbAnchoredAtMaster = true; + } + maObjList.push_back( aNewEntry ); + } + + SwAnchoredObject* operator[]( sal_uInt32 _nIndex ) + { + return maObjList[_nIndex].mpAnchoredObj; + } + + sal_uInt32 GetPageNum( sal_uInt32 _nIndex ) const + { + return maObjList[_nIndex].mnPageNumOfAnchor; + } + + // --> #i26945# + bool AnchoredAtMaster( sal_uInt32 _nIndex ) + { + return maObjList[_nIndex].mbAnchoredAtMaster; + } + + sal_uInt32 Count() const + { + return maObjList.size(); + } +}; + +SwObjectFormatter::SwObjectFormatter( const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction, + const bool _bCollectPgNumOfAnchors ) + : mrPageFrame( _rPageFrame ), + mbConsiderWrapOnObjPos( _rPageFrame.GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ), + mpLayAction( _pLayAction ), + // --> #i26945# + mpPgNumAndTypeOfAnchors( _bCollectPgNumOfAnchors ? new SwPageNumAndTypeOfAnchors() : nullptr ) +{ +} + +SwObjectFormatter::~SwObjectFormatter() +{ +} + +std::unique_ptr<SwObjectFormatter> SwObjectFormatter::CreateObjFormatter( + SwFrame& _rAnchorFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + std::unique_ptr<SwObjectFormatter> pObjFormatter; + if ( _rAnchorFrame.IsTextFrame() ) + { + pObjFormatter = SwObjectFormatterTextFrame::CreateObjFormatter( + static_cast<SwTextFrame&>(_rAnchorFrame), + _rPageFrame, _pLayAction ); + } + else if ( _rAnchorFrame.IsLayoutFrame() ) + { + pObjFormatter = SwObjectFormatterLayFrame::CreateObjFormatter( + static_cast<SwLayoutFrame&>(_rAnchorFrame), + _rPageFrame, _pLayAction ); + } + else + { + OSL_FAIL( "<SwObjectFormatter::CreateObjFormatter(..)> - unexpected type of anchor frame" ); + } + + return pObjFormatter; +} + +/** method to format all floating screen objects at the given anchor frame +*/ +bool SwObjectFormatter::FormatObjsAtFrame( SwFrame& _rAnchorFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + bool bSuccess( true ); + + // create corresponding object formatter + std::unique_ptr<SwObjectFormatter> pObjFormatter = + SwObjectFormatter::CreateObjFormatter( _rAnchorFrame, _rPageFrame, _pLayAction ); + + if ( pObjFormatter ) + { + // format anchored floating screen objects + bSuccess = pObjFormatter->DoFormatObjs(); + } + + return bSuccess; +} + +/** method to format a given floating screen object +*/ +bool SwObjectFormatter::FormatObj( SwAnchoredObject& _rAnchoredObj, + SwFrame* _pAnchorFrame, + const SwPageFrame* _pPageFrame ) +{ + bool bSuccess( true ); + + OSL_ENSURE( _pAnchorFrame || _rAnchoredObj.GetAnchorFrame(), + "<SwObjectFormatter::FormatObj(..)> - missing anchor frame" ); + SwFrame& rAnchorFrame = _pAnchorFrame ? *_pAnchorFrame : *(_rAnchoredObj.AnchorFrame()); + + OSL_ENSURE( _pPageFrame || rAnchorFrame.FindPageFrame(), + "<SwObjectFormatter::FormatObj(..)> - missing page frame" ); + const SwPageFrame& rPageFrame = _pPageFrame ? *_pPageFrame : *(rAnchorFrame.FindPageFrame()); + + // create corresponding object formatter + std::unique_ptr<SwObjectFormatter> pObjFormatter = + SwObjectFormatter::CreateObjFormatter( rAnchorFrame, rPageFrame, nullptr/*_pLayAction*/ ); + + if ( pObjFormatter ) + { + // format given floating screen object + // --> #i40147# - check for moved forward anchor frame + bSuccess = pObjFormatter->DoFormatObj( _rAnchoredObj, true ); + } + + return bSuccess; +} + +/** helper method for method <FormatObj_(..)> - performs the intrinsic format + of the layout of the given layout frame and all its lower layout frames. + + #i28701# + IMPORTANT NOTE: + Method corresponds to methods <SwLayAction::FormatLayoutFly(..)> and + <SwLayAction::FormatLayout(..)>. Thus, its code for the formatting have + to be synchronised. +*/ +void SwObjectFormatter::FormatLayout_( SwLayoutFrame& _rLayoutFrame ) +{ + _rLayoutFrame.Calc(_rLayoutFrame.getRootFrame()->GetCurrShell()->GetOut()); + + SwFrame* pLowerFrame = _rLayoutFrame.Lower(); + while ( pLowerFrame ) + { + if ( pLowerFrame->IsLayoutFrame() ) + { + FormatLayout_( *static_cast<SwLayoutFrame*>(pLowerFrame) ); + } + pLowerFrame = pLowerFrame->GetNext(); + } +} + +/** helper method for method <FormatObj_(..)> - performs the intrinsic + format of the content of the given floating screen object. + + #i28701# +*/ +void SwObjectFormatter::FormatObjContent( SwAnchoredObject& _rAnchoredObj ) +{ + if ( dynamic_cast<const SwFlyFrame*>( &_rAnchoredObj) == nullptr ) + { + // only Writer fly frames have content + return; + } + + SwFlyFrame& rFlyFrame = static_cast<SwFlyFrame&>(_rAnchoredObj); + SwContentFrame* pContent = rFlyFrame.ContainsContent(); + + while ( pContent ) + { + // format content + pContent->OptCalc(); + + // format floating screen objects at content text frame + // #i23129#, #i36347# - pass correct page frame to + // the object formatter + if ( pContent->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( *pContent, + *(pContent->FindPageFrame()), + GetLayAction() ) ) + { + // restart format with first content + pContent = rFlyFrame.ContainsContent(); + continue; + } + + // continue with next content + pContent = pContent->GetNextContentFrame(); + } +} + +/** performs the intrinsic format of a given floating screen object and its content. + + #i28701# +*/ +void SwObjectFormatter::FormatObj_( SwAnchoredObject& _rAnchoredObj ) +{ + // collect anchor object and its 'anchor' page number, if requested + if ( mpPgNumAndTypeOfAnchors ) + { + mpPgNumAndTypeOfAnchors->Collect( _rAnchoredObj ); + } + + if ( dynamic_cast<const SwFlyFrame*>( &_rAnchoredObj) != nullptr ) + { + SwFlyFrame& rFlyFrame = static_cast<SwFlyFrame&>(_rAnchoredObj); + // --> #i34753# - reset flag, which prevents a positioning + if ( rFlyFrame.IsFlyLayFrame() ) + { + static_cast<SwFlyLayFrame&>(rFlyFrame).SetNoMakePos( false ); + } + + // #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 15; + + do { + if ( mpLayAction ) + { + mpLayAction->FormatLayoutFly( &rFlyFrame ); + // --> consider, if the layout action + // has to be restarted due to a delete of a page frame. + if ( mpLayAction->IsAgain() ) + { + break; + } + } + else + { + FormatLayout_( rFlyFrame ); + } + // --> #i34753# - prevent further positioning, if + // to-page|to-fly anchored Writer fly frame is already clipped. + if ( rFlyFrame.IsFlyLayFrame() && rFlyFrame.IsClipped() ) + { + static_cast<SwFlyLayFrame&>(rFlyFrame).SetNoMakePos( true ); + } + // #i23129#, #i36347# - pass correct page frame + // to the object formatter + SwObjectFormatter::FormatObjsAtFrame( rFlyFrame, + *(rFlyFrame.FindPageFrame()), + mpLayAction ); + if ( mpLayAction ) + { + mpLayAction->FormatFlyContent( &rFlyFrame ); + // --> consider, if the layout action + // has to be restarted due to a delete of a page frame. + if ( mpLayAction->IsAgain() ) + { + break; + } + } + else + { + FormatObjContent( rFlyFrame ); + } + + if ( ++nLoopControlRuns >= nLoopControlMax ) + { + OSL_FAIL( "LoopControl in SwObjectFormatter::FormatObj_: Stage 3!!!" ); + rFlyFrame.ValidateThisAndAllLowers( 2 ); + nLoopControlRuns = 0; + } + + // --> #i57917# + // stop formatting of anchored object, if restart of layout process is requested. + } while ( !rFlyFrame.isFrameAreaDefinitionValid() && + !_rAnchoredObj.RestartLayoutProcess() && + rFlyFrame.GetAnchorFrame() == &GetAnchorFrame() ); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rAnchoredObj) != nullptr ) + { + _rAnchoredObj.MakeObjPos(); + } +} + +/** invokes the intrinsic format method for all floating screen objects, + anchored at anchor frame on the given page frame + + #i28701# + #i26945# - for format of floating screen objects for + follow text frames, the 'master' text frame is passed to the method. + Thus, the objects, whose anchor character is inside the follow text + frame can be formatted. +*/ +bool SwObjectFormatter::FormatObjsAtFrame_( SwTextFrame* _pMasterTextFrame ) +{ + // --> #i26945# + SwFrame* pAnchorFrame( nullptr ); + if ( GetAnchorFrame().IsTextFrame() && + static_cast<SwTextFrame&>(GetAnchorFrame()).IsFollow() && + _pMasterTextFrame ) + { + pAnchorFrame = _pMasterTextFrame; + } + else + { + pAnchorFrame = &GetAnchorFrame(); + } + if ( !pAnchorFrame->GetDrawObjs() ) + { + // nothing to do, if no floating screen object is registered at the anchor frame. + return true; + } + + bool bSuccess( true ); + + for ( size_t i = 0; i < pAnchorFrame->GetDrawObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pAnchorFrame->GetDrawObjs())[i]; + + // check, if object's anchor is on the given page frame or + // object is registered at the given page frame. + // --> #i26945# - check, if the anchor character of the + // anchored object is located in a follow text frame. If this anchor + // follow text frame differs from the given anchor frame, the given + // anchor frame is a 'master' text frame of the anchor follow text frame. + // If the anchor follow text frame is in the same body as its 'master' + // text frame, do not format the anchored object. + // E.g., this situation can occur during the table row splitting algorithm. + SwTextFrame* pAnchorCharFrame = pAnchoredObj->FindAnchorCharFrame(); + const bool bAnchoredAtFollowInSameBodyAsMaster = + pAnchorCharFrame && pAnchorCharFrame->IsFollow() && + pAnchorCharFrame != pAnchoredObj->GetAnchorFrame() && + pAnchorCharFrame->FindBodyFrame() == + static_cast<SwTextFrame*>(pAnchoredObj->AnchorFrame())->FindBodyFrame(); + if ( bAnchoredAtFollowInSameBodyAsMaster ) + { + continue; + } + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = pAnchoredObj->FindPageFrameOfAnchor(); + OSL_ENSURE( pPageFrameOfAnchor, + "<SwObjectFormatter::FormatObjsAtFrame_()> - missing page frame." ); + // --> #i26945# + if ( pPageFrameOfAnchor && pPageFrameOfAnchor == &mrPageFrame ) + { + // if format of object fails, stop formatting and pass fail to + // calling method via the return value. + if ( !DoFormatObj( *pAnchoredObj ) ) + { + bSuccess = false; + break; + } + + // considering changes at <pAnchorFrame->GetDrawObjs()> during + // format of the object. + if ( !pAnchorFrame->GetDrawObjs() || + i > pAnchorFrame->GetDrawObjs()->size() ) + { + break; + } + else + { + const size_t nActPosOfObj = + pAnchorFrame->GetDrawObjs()->ListPosOf( *pAnchoredObj ); + if ( nActPosOfObj == pAnchorFrame->GetDrawObjs()->size() || + nActPosOfObj > i ) + { + --i; + } + else if ( nActPosOfObj < i ) + { + i = nActPosOfObj; + } + } + } + } // end of loop on <pAnchorFrame->.GetDrawObjs()> + + return bSuccess; +} + +/** accessor to collected anchored object + + #i28701# +*/ +SwAnchoredObject* SwObjectFormatter::GetCollectedObj( const sal_uInt32 _nIndex ) +{ + return mpPgNumAndTypeOfAnchors ? (*mpPgNumAndTypeOfAnchors)[_nIndex] : nullptr; +} + +/** accessor to 'anchor' page number of collected anchored object + + #i28701# +*/ +sal_uInt32 SwObjectFormatter::GetPgNumOfCollected( const sal_uInt32 _nIndex ) +{ + return mpPgNumAndTypeOfAnchors ? mpPgNumAndTypeOfAnchors->GetPageNum(_nIndex) : 0; +} + +/** accessor to 'anchor' type of collected anchored object + + #i26945# +*/ +bool SwObjectFormatter::IsCollectedAnchoredAtMaster( const sal_uInt32 _nIndex ) +{ + return mpPgNumAndTypeOfAnchors == nullptr + || mpPgNumAndTypeOfAnchors->AnchoredAtMaster(_nIndex); +} + +/** accessor to total number of collected anchored objects + + #i28701# +*/ +sal_uInt32 SwObjectFormatter::CountOfCollected() +{ + return mpPgNumAndTypeOfAnchors ? mpPgNumAndTypeOfAnchors->Count() : 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformatterlayfrm.cxx b/sw/source/core/layout/objectformatterlayfrm.cxx new file mode 100644 index 000000000..cc9f33216 --- /dev/null +++ b/sw/source/core/layout/objectformatterlayfrm.cxx @@ -0,0 +1,187 @@ +/* -*- 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 "objectformatterlayfrm.hxx" +#include <anchoredobject.hxx> +#include <sortedobjs.hxx> +#include <pagefrm.hxx> + +#include <layact.hxx> + +SwObjectFormatterLayFrame::SwObjectFormatterLayFrame( SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) + : SwObjectFormatter( _rPageFrame, _pLayAction ), + mrAnchorLayFrame( _rAnchorLayFrame ) +{ +} + +SwObjectFormatterLayFrame::~SwObjectFormatterLayFrame() +{ +} + +std::unique_ptr<SwObjectFormatterLayFrame> SwObjectFormatterLayFrame::CreateObjFormatter( + SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + if ( !_rAnchorLayFrame.IsPageFrame() && + !_rAnchorLayFrame.IsFlyFrame() ) + { + OSL_FAIL( "<SwObjectFormatterLayFrame::CreateObjFormatter(..)> - unexpected type of anchor frame " ); + return nullptr; + } + + std::unique_ptr<SwObjectFormatterLayFrame> pObjFormatter; + + // create object formatter, if floating screen objects are registered at + // given anchor layout frame. + if ( _rAnchorLayFrame.GetDrawObjs() || + ( _rAnchorLayFrame.IsPageFrame() && + static_cast<SwPageFrame&>(_rAnchorLayFrame).GetSortedObjs() ) ) + { + pObjFormatter.reset( + new SwObjectFormatterLayFrame( _rAnchorLayFrame, _rPageFrame, _pLayAction )); + } + + return pObjFormatter; +} + +SwFrame& SwObjectFormatterLayFrame::GetAnchorFrame() +{ + return mrAnchorLayFrame; +} + +// #i40147# - add parameter <_bCheckForMovedFwd>. +// Not relevant for objects anchored at layout frame. +bool SwObjectFormatterLayFrame::DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool ) +{ + FormatObj_( _rAnchoredObj ); + + // #124218# - consider that the layout action has to be + // restarted due to a deleted page frame. + return GetLayAction() == nullptr || !GetLayAction()->IsAgain(); +} + +bool SwObjectFormatterLayFrame::DoFormatObjs() +{ + bool bSuccess = FormatObjsAtFrame_(); + + if ( bSuccess && GetAnchorFrame().IsPageFrame() ) + { + // anchor layout frame is a page frame. + // Thus, format also all anchored objects, which are registered at + // this page frame, whose 'anchor' isn't on this page frame and whose + // anchor frame is valid. + bSuccess = AdditionalFormatObjsOnPage(); + } + + return bSuccess; +} + +/** method to format all anchored objects, which are registered at + the page frame, whose 'anchor' isn't on this page frame and whose + anchor frame is valid. + + OD 2004-07-02 #i28701# +*/ +bool SwObjectFormatterLayFrame::AdditionalFormatObjsOnPage() +{ + if ( !GetAnchorFrame().IsPageFrame() ) + { + OSL_FAIL( "<SwObjectFormatterLayFrame::AdditionalFormatObjsOnPage()> - mis-usage of method, call only for anchor frames of type page frame" ); + return true; + } + + // #124218# - consider, if the layout action + // has to be restarted due to a delete of a page frame. + if ( GetLayAction() && GetLayAction()->IsAgain() ) + { + return false; + } + + SwPageFrame& rPageFrame = static_cast<SwPageFrame&>(GetAnchorFrame()); + + if ( !rPageFrame.GetSortedObjs() ) + { + // nothing to do, if no floating screen object is registered at the anchor frame. + return true; + } + + bool bSuccess( true ); + + for ( size_t i = 0; i < rPageFrame.GetSortedObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*rPageFrame.GetSortedObjs())[i]; + + // #i51941# - do not format object, which are anchored + // inside or at fly frame. + if ( pAnchoredObj->GetAnchorFrame()->FindFlyFrame() ) + { + continue; + } + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = pAnchoredObj->FindPageFrameOfAnchor(); + // #i26945# - check, if the page frame of the + // object's anchor frame isn't the given page frame + OSL_ENSURE( pPageFrameOfAnchor, + "<SwObjectFormatterLayFrame::AdditionalFormatObjsOnPage()> - missing page frame" ); + if ( pPageFrameOfAnchor && + // #i35911# + pPageFrameOfAnchor->GetPhyPageNum() < rPageFrame.GetPhyPageNum() ) + { + // if format of object fails, stop formatting and pass fail to + // calling method via the return value. + if ( !DoFormatObj( *pAnchoredObj ) ) + { + bSuccess = false; + break; + } + + // considering changes at <GetAnchorFrame().GetDrawObjs()> during + // format of the object. + if ( !rPageFrame.GetSortedObjs() || + i > rPageFrame.GetSortedObjs()->size() ) + { + break; + } + else + { + const size_t nActPosOfObj = + rPageFrame.GetSortedObjs()->ListPosOf( *pAnchoredObj ); + if ( nActPosOfObj == rPageFrame.GetSortedObjs()->size() || + nActPosOfObj > i ) + { + --i; + } + else if ( nActPosOfObj < i ) + { + i = nActPosOfObj; + } + } + } + } // end of loop on <rPageFrame.GetSortedObjs()> + + return bSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformatterlayfrm.hxx b/sw/source/core/layout/objectformatterlayfrm.hxx new file mode 100644 index 000000000..9ece6f281 --- /dev/null +++ b/sw/source/core/layout/objectformatterlayfrm.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERLAYFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERLAYFRM_HXX + +#include <objectformatter.hxx> + +class SwLayoutFrame; + +// Format floating screen objects, which are anchored at a given anchor text frame +// and registered at the given page frame. +class SwObjectFormatterLayFrame : public SwObjectFormatter +{ + private: + // anchor layout frame + SwLayoutFrame& mrAnchorLayFrame; + + SwObjectFormatterLayFrame( SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); + + /** method to format all anchored objects, which are registered at + the page frame, whose 'anchor' isn't on this page frame and whose + anchor frame is valid. + + OD 2004-07-02 #i28701# + + @return boolean + indicates, if format was successful + */ + bool AdditionalFormatObjsOnPage(); + + protected: + + virtual SwFrame& GetAnchorFrame() override; + + public: + virtual ~SwObjectFormatterLayFrame() override; + + // #i40147# - add parameter <_bCheckForMovedFwd>. + // Not relevant for objects anchored at layout frame. + virtual bool DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd = false ) override; + virtual bool DoFormatObjs() override; + + static std::unique_ptr<SwObjectFormatterLayFrame> CreateObjFormatter( + SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformattertxtfrm.cxx b/sw/source/core/layout/objectformattertxtfrm.cxx new file mode 100644 index 000000000..c8d64cf44 --- /dev/null +++ b/sw/source/core/layout/objectformattertxtfrm.cxx @@ -0,0 +1,807 @@ +/* -*- 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 "objectformattertxtfrm.hxx" +#include <sortedobjs.hxx> +#include <rootfrm.hxx> +#include <anchoredobject.hxx> +#include <txtfrm.hxx> +#include <pagefrm.hxx> +#include <rowfrm.hxx> +#include <layouter.hxx> +#include <fmtanchr.hxx> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <fmtfollowtextflow.hxx> +#include <layact.hxx> +#include <ftnfrm.hxx> + +using namespace ::com::sun::star; + +namespace { + +// little helper class to forbid follow formatting for the given text frame +class SwForbidFollowFormat +{ +private: + SwTextFrame& mrTextFrame; + const bool bOldFollowFormatAllowed; + +public: + explicit SwForbidFollowFormat( SwTextFrame& _rTextFrame ) + : mrTextFrame( _rTextFrame ), + bOldFollowFormatAllowed( _rTextFrame.FollowFormatAllowed() ) + { + mrTextFrame.ForbidFollowFormat(); + } + + ~SwForbidFollowFormat() + { + if ( bOldFollowFormatAllowed ) + { + mrTextFrame.AllowFollowFormat(); + } + } +}; + +} + +SwObjectFormatterTextFrame::SwObjectFormatterTextFrame( SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwTextFrame* _pMasterAnchorTextFrame, + SwLayAction* _pLayAction ) + : SwObjectFormatter( _rPageFrame, _pLayAction, true ), + mrAnchorTextFrame( _rAnchorTextFrame ), + mpMasterAnchorTextFrame( _pMasterAnchorTextFrame ) +{ +} + +SwObjectFormatterTextFrame::~SwObjectFormatterTextFrame() +{ +} + +std::unique_ptr<SwObjectFormatterTextFrame> SwObjectFormatterTextFrame::CreateObjFormatter( + SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + std::unique_ptr<SwObjectFormatterTextFrame> pObjFormatter; + + // determine 'master' of <_rAnchorTextFrame>, if anchor frame is a follow text frame. + SwTextFrame* pMasterOfAnchorFrame = nullptr; + if ( _rAnchorTextFrame.IsFollow() ) + { + pMasterOfAnchorFrame = _rAnchorTextFrame.FindMaster(); + while ( pMasterOfAnchorFrame && pMasterOfAnchorFrame->IsFollow() ) + { + pMasterOfAnchorFrame = pMasterOfAnchorFrame->FindMaster(); + } + } + + // create object formatter, if floating screen objects are registered + // at anchor frame (or at 'master' anchor frame) + if ( _rAnchorTextFrame.GetDrawObjs() || + ( pMasterOfAnchorFrame && pMasterOfAnchorFrame->GetDrawObjs() ) ) + { + pObjFormatter.reset( + new SwObjectFormatterTextFrame( _rAnchorTextFrame, _rPageFrame, + pMasterOfAnchorFrame, _pLayAction )); + } + + return pObjFormatter; +} + +SwFrame& SwObjectFormatterTextFrame::GetAnchorFrame() +{ + return mrAnchorTextFrame; +} + +// #i40147# - add parameter <_bCheckForMovedFwd>. +bool SwObjectFormatterTextFrame::DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd ) +{ + // consider, if the layout action has to be + // restarted due to a delete of a page frame. + if ( GetLayAction() && GetLayAction()->IsAgain() ) + { + return false; + } + + bool bSuccess( true ); + + if ( _rAnchoredObj.IsFormatPossible() ) + { + _rAnchoredObj.SetRestartLayoutProcess( false ); + + FormatObj_( _rAnchoredObj ); + // consider, if the layout action has to be + // restarted due to a delete of a page frame. + if ( GetLayAction() && GetLayAction()->IsAgain() ) + { + return false; + } + + // check, if layout process has to be restarted. + // if yes, perform needed invalidations. + + // no restart of layout process, + // if anchored object is anchored inside a Writer fly frame, + // its position is already locked, and it follows the text flow. + const bool bRestart = + _rAnchoredObj.RestartLayoutProcess() && + !( _rAnchoredObj.PositionLocked() && + _rAnchoredObj.GetAnchorFrame()->IsInFly() && + _rAnchoredObj.GetFrameFormat().GetFollowTextFlow().GetValue() ); + if ( bRestart ) + { + bSuccess = false; + InvalidatePrevObjs( _rAnchoredObj ); + InvalidateFollowObjs( _rAnchoredObj ); + } + + // format anchor text frame, if wrapping style influence of the object + // has to be considered and it's <NONE_SUCCESSIVE_POSITIONED> + // #i3317# - consider also anchored objects, whose + // wrapping style influence is temporarily considered. + // #i40147# - consider also anchored objects, for + // whose the check of a moved forward anchor frame is requested. + // revise decision made for i3317: + // anchored objects, whose wrapping style influence is temporarily considered, + // have to be considered in method <SwObjectFormatterTextFrame::DoFormatObjs()> + if ( bSuccess && + _rAnchoredObj.ConsiderObjWrapInfluenceOnObjPos() && + ( _bCheckForMovedFwd || + _rAnchoredObj.GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE ) ) + { + // #i26945# - check conditions for move forward of + // anchor text frame + // determine, if anchor text frame has previous frame + const bool bDoesAnchorHadPrev = ( mrAnchorTextFrame.GetIndPrev() != nullptr ); + + // #i40141# - use new method - it also formats the + // section the anchor frame is in. + FormatAnchorFrameForCheckMoveFwd(); + + // #i35911# + if ( _rAnchoredObj.HasClearedEnvironment() ) + { + _rAnchoredObj.SetClearedEnvironment( true ); + // #i44049# - consider, that anchor frame + // could already been marked to move forward. + SwPageFrame* pAnchorPageFrame( mrAnchorTextFrame.FindPageFrame() ); + if ( pAnchorPageFrame != _rAnchoredObj.GetPageFrame() ) + { + bool bInsert( true ); + sal_uInt32 nToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nToPageNum ) ) + { + if ( nToPageNum < pAnchorPageFrame->GetPhyPageNum() ) + SwLayouter::RemoveMovedFwdFrame( rDoc, mrAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + SwLayouter::InsertMovedFwdFrame( rDoc, mrAnchorTextFrame, + pAnchorPageFrame->GetPhyPageNum() ); + mrAnchorTextFrame.InvalidatePos(); + bSuccess = false; + InvalidatePrevObjs( _rAnchoredObj ); + InvalidateFollowObjs( _rAnchoredObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObj(..)> - anchor frame not marked to move forward" ); + } + } + } + else if ( !mrAnchorTextFrame.IsFollow() && bDoesAnchorHadPrev ) + { + // index of anchored object in collection of page numbers and + // anchor types + sal_uInt32 nIdx( CountOfCollected() ); + OSL_ENSURE( nIdx > 0, + "<SwObjectFormatterTextFrame::DoFormatObj(..)> - anchored object not collected!?" ); + --nIdx; + + sal_uInt32 nToPageNum( 0 ); + // #i43913# + bool bDummy( false ); + // #i58182# - consider new method signature + if ( SwObjectFormatterTextFrame::CheckMovedFwdCondition( *GetCollectedObj( nIdx ), + GetPgNumOfCollected( nIdx ), + IsCollectedAnchoredAtMaster( nIdx ), + nToPageNum, bDummy ) ) + { + // #i49987# - consider, that anchor frame + // could already been marked to move forward. + bool bInsert( true ); + sal_uInt32 nMovedFwdToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nMovedFwdToPageNum ) ) + { + if ( nMovedFwdToPageNum < nToPageNum ) + SwLayouter::RemoveMovedFwdFrame( rDoc, mrAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + // Indicate that anchor text frame has to move forward and + // invalidate its position to force a re-format. + SwLayouter::InsertMovedFwdFrame( rDoc, mrAnchorTextFrame, + nToPageNum ); + mrAnchorTextFrame.InvalidatePos(); + + // Indicate restart of the layout process + bSuccess = false; + + // If needed, invalidate previous objects anchored at same anchor + // text frame. + InvalidatePrevObjs( _rAnchoredObj ); + + // Invalidate object and following objects for the restart of the + // layout process + InvalidateFollowObjs( _rAnchoredObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObj(..)> - anchor frame not marked to move forward" ); + } + } + } + // i40155# - mark anchor frame not to wrap around + // objects under the condition, that its follow contains all its text. + else if ( !mrAnchorTextFrame.IsFollow() && + mrAnchorTextFrame.GetFollow() && + mrAnchorTextFrame.GetFollow()->GetOffset() == TextFrameIndex(0)) + { + SwLayouter::RemoveMovedFwdFrame( + *(mrAnchorTextFrame.FindPageFrame()->GetFormat()->GetDoc()), + mrAnchorTextFrame ); + } + } + } + + return bSuccess; +} + +bool SwObjectFormatterTextFrame::DoFormatObjs() +{ + if ( !mrAnchorTextFrame.isFrameAreaDefinitionValid() ) + { + if ( GetLayAction() && + mrAnchorTextFrame.FindPageFrame() != &GetPageFrame() ) + { + // notify layout action, thus is can restart the layout process on + // a previous page. + GetLayAction()->SetAgain(); + } + else + { + // the anchor text frame has to be valid, thus assert. + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObjs()> called for invalidate anchor text frame." ); + } + + return false; + } + + bool bSuccess( true ); + + if ( mrAnchorTextFrame.IsFollow() ) + { + // Only floating screen objects anchored as-character are directly + // registered at a follow text frame. The other floating screen objects + // are registered at the 'master' anchor text frame. + // Thus, format the other floating screen objects through the 'master' + // anchor text frame + OSL_ENSURE( mpMasterAnchorTextFrame, + "SwObjectFormatterTextFrame::DoFormatObjs() - missing 'master' anchor text frame" ); + bSuccess = FormatObjsAtFrame_( mpMasterAnchorTextFrame ); + + if ( bSuccess ) + { + // format of as-character anchored floating screen objects - no failure + // expected on the format of these objects. + bSuccess = FormatObjsAtFrame_(); + } + } + else + { + bSuccess = FormatObjsAtFrame_(); + } + + // consider anchored objects, whose wrapping style influence are temporarily + // considered. + if ( bSuccess && + ( ConsiderWrapOnObjPos() || + ( !mrAnchorTextFrame.IsFollow() && + AtLeastOneObjIsTmpConsiderWrapInfluence() ) ) ) + { + const bool bDoesAnchorHadPrev = ( mrAnchorTextFrame.GetIndPrev() != nullptr ); + + // Format anchor text frame after its objects are formatted. + // Note: The format of the anchor frame also formats the invalid + // previous frames of the anchor frame. The format of the previous + // frames is needed to get a correct result of format of the + // anchor frame for the following check for moved forward anchors + // #i40141# - use new method - it also formats the + // section the anchor frame is in. + FormatAnchorFrameForCheckMoveFwd(); + + sal_uInt32 nToPageNum( 0 ); + // #i43913# + bool bInFollow( false ); + SwAnchoredObject* pObj = nullptr; + if ( !mrAnchorTextFrame.IsFollow() ) + { + pObj = GetFirstObjWithMovedFwdAnchor( + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_CONCURRENT, + nToPageNum, bInFollow ); + } + // #i35911# + if ( pObj && pObj->HasClearedEnvironment() ) + { + pObj->SetClearedEnvironment( true ); + // #i44049# - consider, that anchor frame + // could already been marked to move forward. + SwPageFrame* pAnchorPageFrame( mrAnchorTextFrame.FindPageFrame() ); + // #i43913# - consider, that anchor frame + // is a follow or is in a follow row, which will move forward. + if ( pAnchorPageFrame != pObj->GetPageFrame() || + bInFollow ) + { + bool bInsert( true ); + sal_uInt32 nTmpToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nTmpToPageNum ) ) + { + if ( nTmpToPageNum < pAnchorPageFrame->GetPhyPageNum() ) + SwLayouter::RemoveMovedFwdFrame( rDoc, mrAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + SwLayouter::InsertMovedFwdFrame( rDoc, mrAnchorTextFrame, + pAnchorPageFrame->GetPhyPageNum() ); + mrAnchorTextFrame.InvalidatePos(); + bSuccess = false; + InvalidatePrevObjs( *pObj ); + InvalidateFollowObjs( *pObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObjs(..)> - anchor frame not marked to move forward" ); + } + } + } + else if ( pObj && bDoesAnchorHadPrev ) + { + // Object found, whose anchor is moved forward + + // #i49987# - consider, that anchor frame + // could already been marked to move forward. + bool bInsert( true ); + sal_uInt32 nMovedFwdToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nMovedFwdToPageNum ) ) + { + if ( nMovedFwdToPageNum < nToPageNum ) + SwLayouter::RemoveMovedFwdFrame( rDoc, mrAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + // Indicate that anchor text frame has to move forward and + // invalidate its position to force a re-format. + SwLayouter::InsertMovedFwdFrame( rDoc, mrAnchorTextFrame, nToPageNum ); + mrAnchorTextFrame.InvalidatePos(); + + // Indicate restart of the layout process + bSuccess = false; + + // If needed, invalidate previous objects anchored at same anchor + // text frame. + InvalidatePrevObjs( *pObj ); + + // Invalidate object and following objects for the restart of the + // layout process + InvalidateFollowObjs( *pObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObjs(..)> - anchor frame not marked to move forward" ); + } + } + // #i40155# - mark anchor frame not to wrap around + // objects under the condition, that its follow contains all its text. + else if ( !mrAnchorTextFrame.IsFollow() && + mrAnchorTextFrame.GetFollow() && + mrAnchorTextFrame.GetFollow()->GetOffset() == TextFrameIndex(0)) + { + SwLayouter::RemoveMovedFwdFrame( + *(mrAnchorTextFrame.FindPageFrame()->GetFormat()->GetDoc()), + mrAnchorTextFrame ); + } + } + + return bSuccess; +} + +void SwObjectFormatterTextFrame::InvalidatePrevObjs( SwAnchoredObject& _rAnchoredObj ) +{ + // invalidate all previous objects, whose wrapping influence on the object + // positioning is <NONE_CONCURRENT_POSITIONED>. + // Note: list of objects at anchor frame is sorted by this property. + if ( _rAnchoredObj.GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_CONCURRENT ) + { + const SwSortedObjs* pObjs = GetAnchorFrame().GetDrawObjs(); + if ( pObjs ) + { + // determine start index + size_t i = pObjs->ListPosOf( _rAnchoredObj ); + while (i > 0) + { + --i; + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if ( pAnchoredObj->GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_CONCURRENT ) + { + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + } + } + } + } +} + +void SwObjectFormatterTextFrame::InvalidateFollowObjs( SwAnchoredObject& _rAnchoredObj ) +{ + _rAnchoredObj.InvalidateObjPosForConsiderWrapInfluence(); + + const SwSortedObjs* pObjs = GetPageFrame().GetSortedObjs(); + if ( pObjs ) + { + // determine start index + for ( size_t i = pObjs->ListPosOf( _rAnchoredObj ) + 1; i < pObjs->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + } + } +} + +SwAnchoredObject* SwObjectFormatterTextFrame::GetFirstObjWithMovedFwdAnchor( + const sal_Int16 _nWrapInfluenceOnPosition, + sal_uInt32& _noToPageNum, + bool& _boInFollow ) +{ + // #i35017# - constant names have changed + OSL_ENSURE( _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE || + _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ONCE_CONCURRENT, + "<SwObjectFormatterTextFrame::GetFirstObjWithMovedFwdAnchor(..)> - invalid value for parameter <_nWrapInfluenceOnPosition>" ); + + SwAnchoredObject* pRetAnchoredObj = nullptr; + + sal_uInt32 i = 0; + for ( ; i < CountOfCollected(); ++i ) + { + SwAnchoredObject* pAnchoredObj = GetCollectedObj(i); + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() && + pAnchoredObj->GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == _nWrapInfluenceOnPosition ) + { + // #i26945# - use new method <_CheckMovedFwdCondition(..)> + // #i43913# + // #i58182# - consider new method signature + if ( SwObjectFormatterTextFrame::CheckMovedFwdCondition( *GetCollectedObj( i ), + GetPgNumOfCollected( i ), + IsCollectedAnchoredAtMaster( i ), + _noToPageNum, _boInFollow ) ) + { + pRetAnchoredObj = pAnchoredObj; + break; + } + } + } + + return pRetAnchoredObj; +} + +// #i58182# +// - replace private method by corresponding static public method +bool SwObjectFormatterTextFrame::CheckMovedFwdCondition( + SwAnchoredObject& _rAnchoredObj, + const sal_uInt32 _nFromPageNum, + const bool _bAnchoredAtMasterBeforeFormatAnchor, + sal_uInt32& _noToPageNum, + bool& _boInFollow ) +{ + bool bAnchorIsMovedForward( false ); + + SwPageFrame* pPageFrameOfAnchor = _rAnchoredObj.FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor ) + { + const sal_uInt32 nPageNum = pPageFrameOfAnchor->GetPhyPageNum(); + if ( nPageNum > _nFromPageNum ) + { + _noToPageNum = nPageNum; + // Handling of special case: + // If anchor frame is move forward into a follow flow row, + // <_noToPageNum> is set to <_nFromPageNum + 1>, because it is + // possible that the anchor page frame isn't valid, because the + // page distance between master row and follow flow row is greater + // than 1. + if ( _noToPageNum > (_nFromPageNum + 1) ) + { + SwFrame* pAnchorFrame = _rAnchoredObj.GetAnchorFrameContainingAnchPos(); + if ( pAnchorFrame->IsInTab() && + pAnchorFrame->IsInFollowFlowRow() ) + { + _noToPageNum = _nFromPageNum + 1; + } + } + bAnchorIsMovedForward = true; + } + } + // #i26945# - check, if an at-paragraph|at-character + // anchored object is now anchored at a follow text frame, which will be + // on the next page. Also check, if an at-character anchored object + // is now anchored at a text frame, which is in a follow flow row, + // which will be on the next page. + if ( !bAnchorIsMovedForward && + _bAnchoredAtMasterBeforeFormatAnchor && + ((_rAnchoredObj.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + (_rAnchoredObj.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PARA))) + { + SwFrame* pAnchorFrame = _rAnchoredObj.GetAnchorFrameContainingAnchPos(); + OSL_ENSURE( pAnchorFrame->IsTextFrame(), + "<SwObjectFormatterTextFrame::CheckMovedFwdCondition(..) - wrong type of anchor frame>" ); + SwTextFrame* pAnchorTextFrame = static_cast<SwTextFrame*>(pAnchorFrame); + bool bCheck( false ); + if ( pAnchorTextFrame->IsFollow() ) + { + bCheck = true; + } + else if( pAnchorTextFrame->IsInTab() ) + { + const SwRowFrame* pMasterRow = pAnchorTextFrame->IsInFollowFlowRow(); + if ( pMasterRow && + pMasterRow->FindPageFrame() == pPageFrameOfAnchor ) + { + bCheck = true; + } + } + if ( bCheck ) + { + // check, if found text frame will be on the next page + // by checking, if it's in a column, which has no next. + SwFrame* pColFrame = pAnchorTextFrame->FindColFrame(); + while ( pColFrame && !pColFrame->GetNext() ) + { + pColFrame = pColFrame->FindColFrame(); + } + if ( !pColFrame || !pColFrame->GetNext() ) + { + _noToPageNum = _nFromPageNum + 1; + bAnchorIsMovedForward = true; + // #i43913# + _boInFollow = true; + } + } + } + + return bAnchorIsMovedForward; +} + +// #i40140# - helper method to format layout frames used by +// method <SwObjectFormatterTextFrame::FormatAnchorFrameForCheckMoveFwd()> +// #i44049# - format till a certain lower frame, if provided. +static void lcl_FormatContentOfLayoutFrame( SwLayoutFrame* pLayFrame, + SwFrame* pLastLowerFrame = nullptr ) +{ + SwFrame* pLowerFrame = pLayFrame->GetLower(); + while ( pLowerFrame ) + { + // #i44049# + if ( pLastLowerFrame && pLowerFrame == pLastLowerFrame ) + { + break; + } + if ( pLowerFrame->IsLayoutFrame() ) + { + SwFrameDeleteGuard aCrudeHack(pLowerFrame); // ??? any issue setting this for non-footnote frames? + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pLowerFrame), + pLastLowerFrame ); + } + else + pLowerFrame->Calc(pLowerFrame->getRootFrame()->GetCurrShell()->GetOut()); + + // Calc on a SwTextFrame in a footnote can move it to the next page - + // deletion of the SwFootnoteFrame was disabled with SwFrameDeleteGuard + // but now we have to clean up empty footnote frames to prevent crashes. + // Note: check it at this level, not lower: both container and footnote + // can be deleted at the same time! + SwFrame *const pNext = pLowerFrame->GetNext(); + if (pLowerFrame->IsFootnoteContFrame()) + { + for (SwFrame * pFootnote = pLowerFrame->GetLower(); pFootnote; ) + { + assert(pFootnote->IsFootnoteFrame()); + SwFrame *const pNextNote = pFootnote->GetNext(); + if (!pFootnote->IsDeleteForbidden() && !pFootnote->GetLower() && !pFootnote->IsColLocked() && + !static_cast<SwFootnoteFrame*>(pFootnote)->IsBackMoveLocked()) + { + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + } + pFootnote = pNextNote; + } + } + pLowerFrame = pNext; + } +} + +/** method to format given anchor text frame and its previous frames + + #i56300# + Usage: Needed to check, if the anchor text frame is moved forward + due to the positioning and wrapping of its anchored objects, and + to format the frames, which have become invalid due to the anchored + object formatting in the iterative object positioning algorithm +*/ +void SwObjectFormatterTextFrame::FormatAnchorFrameAndItsPrevs( SwTextFrame& _rAnchorTextFrame ) +{ + // #i47014# - no format of section and previous columns + // for follow text frames. + if ( !_rAnchorTextFrame.IsFollow() ) + { + // if anchor frame is directly inside a section, format this section and + // its previous frames. + // Note: It's a very simple format without formatting objects. + if ( _rAnchorTextFrame.IsInSct() ) + { + SwFrame* pSectFrame = _rAnchorTextFrame.GetUpper(); + while ( pSectFrame ) + { + if ( pSectFrame->IsSctFrame() || pSectFrame->IsCellFrame() ) + { + break; + } + pSectFrame = pSectFrame->GetUpper(); + } + if ( pSectFrame && pSectFrame->IsSctFrame() ) + { + SwFrameDeleteGuard aDeleteGuard(&_rAnchorTextFrame); + // #i44049# + _rAnchorTextFrame.LockJoin(); + SwFrame* pFrame = pSectFrame->GetUpper()->GetLower(); + // #i49605# - section frame could move forward + // by the format of its previous frame. + // Thus, check for valid <pFrame>. + while ( pFrame && pFrame != pSectFrame ) + { + if ( pFrame->IsLayoutFrame() ) + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pFrame) ); + else + pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + + pFrame = pFrame->GetNext(); + } + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pSectFrame), + &_rAnchorTextFrame ); + // #i44049# + _rAnchorTextFrame.UnlockJoin(); + } + } + + // #i40140# - if anchor frame is inside a column, + // format the content of the previous columns. + // Note: It's a very simple format without formatting objects. + SwFrame* pColFrameOfAnchor = _rAnchorTextFrame.FindColFrame(); + if ( pColFrameOfAnchor ) + { + // #i44049# + _rAnchorTextFrame.LockJoin(); + SwFrame* pColFrame = pColFrameOfAnchor->GetUpper()->GetLower(); + while ( pColFrame != pColFrameOfAnchor ) + { + SwFrame* pFrame = pColFrame->GetLower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pFrame) ); + else + pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + + pFrame = pFrame->GetNext(); + } + + pColFrame = pColFrame->GetNext(); + } + // #i44049# + _rAnchorTextFrame.UnlockJoin(); + } + } + + // format anchor frame - format of its follow not needed + // #i43255# - forbid follow format, only if anchor text + // frame is in table + if ( _rAnchorTextFrame.IsInTab() ) + { + SwForbidFollowFormat aForbidFollowFormat( _rAnchorTextFrame ); + _rAnchorTextFrame.Calc(_rAnchorTextFrame.getRootFrame()->GetCurrShell()->GetOut()); + } + else + { + _rAnchorTextFrame.Calc(_rAnchorTextFrame.getRootFrame()->GetCurrShell()->GetOut()); + } +} + +/** method to format the anchor frame for checking of the move forward condition + + #i40141# +*/ +void SwObjectFormatterTextFrame::FormatAnchorFrameForCheckMoveFwd() +{ + SwObjectFormatterTextFrame::FormatAnchorFrameAndItsPrevs( mrAnchorTextFrame ); +} + +/** method to determine if at least one anchored object has state + <temporarily consider wrapping style influence> set. +*/ +bool SwObjectFormatterTextFrame::AtLeastOneObjIsTmpConsiderWrapInfluence() +{ + bool bRet( false ); + + const SwSortedObjs* pObjs = GetAnchorFrame().GetDrawObjs(); + if ( pObjs && pObjs->size() > 1 ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + { + bRet = true; + break; + } + } + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformattertxtfrm.hxx b/sw/source/core/layout/objectformattertxtfrm.hxx new file mode 100644 index 000000000..15667b9ea --- /dev/null +++ b/sw/source/core/layout/objectformattertxtfrm.hxx @@ -0,0 +1,185 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERTXTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERTXTFRM_HXX + +#include <objectformatter.hxx> +#include <sal/types.h> + +class SwTextFrame; + +// #i28701# +// Format floating screen objects, which are anchored at a given anchor text frame +// and registered at the given page frame. +class SwObjectFormatterTextFrame : public SwObjectFormatter +{ + private: + // anchor text frame + SwTextFrame& mrAnchorTextFrame; + + // 'master' anchor text frame + SwTextFrame* mpMasterAnchorTextFrame; + + SwObjectFormatterTextFrame( SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwTextFrame* _pMasterAnchorTextFrame, + SwLayAction* _pLayAction ); + + /** method to invalidate objects, anchored previous to given object at + the anchor text frame + + @param _rAnchoredObj + reference to anchored object - objects, anchored previous to + this one will be invalidated. + */ + void InvalidatePrevObjs( SwAnchoredObject& _rAnchoredObj ); + + /** method to invalidate objects, anchored after the given object at + the page frame + + @param _rAnchoredObj + reference to anchored object - objects, anchored after this one will + be invalidated. + */ + void InvalidateFollowObjs( SwAnchoredObject& _rAnchoredObj ); + + /** method to determine first anchored object, whose 'anchor is moved + forward'. + + 'anchor (of an object) is moved forward', if the anchor frame + respectively the anchor character of the object isn't on the + proposed page frame. Instead its on a following page + + #i26945# - For at-character anchored objects, + it has also to be checked, if the anchor character is in a follow + text frame, which would move to the next page. + + #i43913# - add output parameter <_boInFollow> + + @param _nWrapInfluenceOnPosition + input parameter - only object with this given wrapping style + influence are investigated. + + @param _nFromPageNum + input parameter - number of page frame, the 'anchor' should be + + @param _noToPageNum + output parameter - number of page frame, the 'anchor' of the returned + anchored object is. + + @param _boInFollow + output parameter - boolean, indicating that anchor text frame is + currently on the same page, but it's a follow of in a follow row, + which will move forward. value only relevant, if method returns + an anchored object + + @return SwAnchoredObject* + anchored object with a 'moved forward anchor'. If NULL, no such + anchored object is found. + */ + SwAnchoredObject* GetFirstObjWithMovedFwdAnchor( + const sal_Int16 _nWrapInfluenceOnPosition, + sal_uInt32& _noToPageNum, + bool& _boInFollow ); + + /** method to format the anchor frame for checking of the move forward condition + + #i40141# + */ + void FormatAnchorFrameForCheckMoveFwd(); + + /** method to determine if at least one anchored object has state + <temporarily consider wrapping style influence> set. + */ + bool AtLeastOneObjIsTmpConsiderWrapInfluence(); + + protected: + + virtual SwFrame& GetAnchorFrame() override; + + public: + virtual ~SwObjectFormatterTextFrame() override; + + // #i40147# - add parameter <_bCheckForMovedFwd>. + virtual bool DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd = false ) override; + virtual bool DoFormatObjs() override; + + /** method to create an instance of <SwObjectFormatterTextFrame> is + necessary. + */ + static std::unique_ptr<SwObjectFormatterTextFrame> CreateObjFormatter( + SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); + + /** method to format given anchor text frame and its previous frames + + #i56300# + Usage: Needed to check, if the anchor text frame is moved forward + due to the positioning and wrapping of its anchored objects, and + to format the frames, which have become invalid due to the anchored + object formatting in the iterative object positioning algorithm + + @param _rAnchorTextFrame + input parameter - reference to anchor text frame, which has to be + formatted including its previous frames of the page. + */ + static void FormatAnchorFrameAndItsPrevs( SwTextFrame& _rAnchorTextFrame ); + + /** method to check the conditions, if 'anchor is moved forward' + + #i26945# + #i43913# - add output parameter <_boInFollow> + #i58182# - replace method by a corresponding static + method, because it's needed for the iterative positioning algorithm. + + @param _rAnchoredObj + input parameter - anchored object, for which the condition has to checked. + + @param _nFromPageNum + input parameter - number of the page, on which the check is performed + + @param _bAnchoredAtMasterBeforeFormatAnchor + input parameter - boolean indicating, that the given anchored object + was anchored at the master frame before the anchor frame has been + formatted. + + @param _noToPageNum + output parameter - number of page frame, the 'anchor' of the returned + anchored object is. + + @param _boInFollow + output parameter - boolean, indicating that anchor text frame is + currently on the same page, but it's a follow of in a follow row, + which will move forward. value only relevant, if method return <true>. + + @return boolean + indicating, if 'anchor is moved forward' + */ + static bool CheckMovedFwdCondition( SwAnchoredObject& _rAnchoredObj, + const sal_uInt32 _nFromPageNum, + const bool _bAnchoredAtMasterBeforeFormatAnchor, + sal_uInt32& _noToPageNum, + bool& _boInFollow ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objstmpconsiderwrapinfl.cxx b/sw/source/core/layout/objstmpconsiderwrapinfl.cxx new file mode 100644 index 000000000..e35336b9f --- /dev/null +++ b/sw/source/core/layout/objstmpconsiderwrapinfl.cxx @@ -0,0 +1,60 @@ +/* -*- 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 "objstmpconsiderwrapinfl.hxx" +#include <anchoredobject.hxx> + +SwObjsMarkedAsTmpConsiderWrapInfluence::SwObjsMarkedAsTmpConsiderWrapInfluence() +{ +} + +SwObjsMarkedAsTmpConsiderWrapInfluence::~SwObjsMarkedAsTmpConsiderWrapInfluence() +{ + Clear(); +} + +void SwObjsMarkedAsTmpConsiderWrapInfluence::Insert( SwAnchoredObject& _rAnchoredObj ) +{ + auto it = std::find(maObjsTmpConsiderWrapInfl.begin(), maObjsTmpConsiderWrapInfl.end(), &_rAnchoredObj); + if (it != maObjsTmpConsiderWrapInfl.end()) + return; + maObjsTmpConsiderWrapInfl.push_back( &_rAnchoredObj ); +} + +void SwObjsMarkedAsTmpConsiderWrapInfluence::Remove( SwAnchoredObject& _rAnchoredObj ) +{ + auto it = std::find(maObjsTmpConsiderWrapInfl.begin(), maObjsTmpConsiderWrapInfl.end(), &_rAnchoredObj); + if (it == maObjsTmpConsiderWrapInfl.end()) + return; + maObjsTmpConsiderWrapInfl.erase(it); +} + +void SwObjsMarkedAsTmpConsiderWrapInfluence::Clear() +{ + while ( !maObjsTmpConsiderWrapInfl.empty() ) + { + SwAnchoredObject* pAnchoredObj = maObjsTmpConsiderWrapInfl.back(); + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + pAnchoredObj->SetClearedEnvironment( false ); + + maObjsTmpConsiderWrapInfl.pop_back(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objstmpconsiderwrapinfl.hxx b/sw/source/core/layout/objstmpconsiderwrapinfl.hxx new file mode 100644 index 000000000..28b6acf9d --- /dev/null +++ b/sw/source/core/layout/objstmpconsiderwrapinfl.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJSTMPCONSIDERWRAPINFL_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJSTMPCONSIDERWRAPINFL_HXX + +#include <vector> + +class SwAnchoredObject; + +class SwObjsMarkedAsTmpConsiderWrapInfluence +{ + private: + std::vector< SwAnchoredObject* > maObjsTmpConsiderWrapInfl; + + public: + SwObjsMarkedAsTmpConsiderWrapInfluence(); + ~SwObjsMarkedAsTmpConsiderWrapInfluence(); + + void Insert( SwAnchoredObject& _rAnchoredObj ); + void Remove( SwAnchoredObject& _rAnchoredObj ); + void Clear(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/pagechg.cxx b/sw/source/core/layout/pagechg.cxx new file mode 100644 index 000000000..a3188eb2a --- /dev/null +++ b/sw/source/core/layout/pagechg.cxx @@ -0,0 +1,2546 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <ndole.hxx> +#include <sal/log.hxx> +#include <svl/itemiter.hxx> +#include <fmtfsize.hxx> +#include <fmthdft.hxx> +#include <fmtclds.hxx> +#include <fmtpdsc.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <ftninfo.hxx> +#include <frmtool.hxx> +#include <tgrditem.hxx> +#include <viewopt.hxx> +#include <docsh.hxx> +#include <wrtsh.hxx> +#include <view.hxx> +#include <edtwin.hxx> +#include <docary.hxx> +#include <frameformats.hxx> + +#include <viewimp.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <dcontact.hxx> +#include <hints.hxx> +#include <FrameControlsManager.hxx> + +#include <ftnidx.hxx> +#include <bodyfrm.hxx> +#include <ftnfrm.hxx> +#include <tabfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <layact.hxx> +#include <flyfrms.hxx> +#include <htmltbl.hxx> +#include <pagedesc.hxx> +#include <editeng/frmdiritem.hxx> +#include <sortedobjs.hxx> +#include <calbck.hxx> +#include <txtfly.hxx> + +using namespace ::com::sun::star; + +SwBodyFrame::SwBodyFrame( SwFrameFormat *pFormat, SwFrame* pSib ): + SwLayoutFrame( pFormat, pSib ) +{ + mnFrameType = SwFrameType::Body; +} + +void SwBodyFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + // Formatting of the body is too simple, thus, it gets its own format method. + // Borders etc. are not taken into account here. + // Width is taken from the PrtArea of the Upper. Height is the height of the + // PrtArea of the Upper minus any neighbors (for robustness). + // The PrtArea has always the size of the frame. + + if ( !isFrameAreaSizeValid() ) + { + SwTwips nHeight = GetUpper()->getFramePrintArea().Height(); + SwTwips nWidth = GetUpper()->getFramePrintArea().Width(); + const SwFrame *pFrame = GetUpper()->Lower(); + do + { + if ( pFrame != this ) + { + if( pFrame->IsVertical() ) + nWidth -= pFrame->getFrameArea().Width(); + else + nHeight -= pFrame->getFrameArea().Height(); + } + pFrame = pFrame->GetNext(); + } while ( pFrame ); + + if ( nHeight < 0 ) + { + nHeight = 0; + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( nHeight ); + + if( IsVertical() && !IsVertLR() && nWidth != aFrm.Width() ) + { + aFrm.Pos().setX(aFrm.Pos().getX() + aFrm.Width() - nWidth); + } + + aFrm.Width( nWidth ); + } + + bool bNoGrid = true; + if( GetUpper()->IsPageFrame() && static_cast<SwPageFrame*>(GetUpper())->HasGrid() ) + { + SwTextGridItem const*const pGrid( + GetGridItem(static_cast<SwPageFrame*>(GetUpper()))); + if( pGrid ) + { + bNoGrid = false; + long nSum = pGrid->GetBaseHeight() + pGrid->GetRubyHeight(); + SwRectFnSet aRectFnSet(this); + long nSize = aRectFnSet.GetWidth(getFrameArea()); + long nBorder = 0; + if( GRID_LINES_CHARS == pGrid->GetGridType() ) + { + //for textgrid refactor + SwDoc *pDoc = GetFormat()->GetDoc(); + nBorder = nSize % (GetGridWidth(*pGrid, *pDoc)); + nSize -= nBorder; + nBorder /= 2; + } + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetPosX( aPrt, nBorder ); + aRectFnSet.SetWidth( aPrt, nSize ); + + // Height of body frame: + nBorder = aRectFnSet.GetHeight(getFrameArea()); + + // Number of possible lines in area of body frame: + long nNumberOfLines = nBorder / nSum; + if( nNumberOfLines > pGrid->GetLines() ) + nNumberOfLines = pGrid->GetLines(); + + // Space required for nNumberOfLines lines: + nSize = nNumberOfLines * nSum; + nBorder -= nSize; + nBorder /= 2; + + // #i21774# Footnotes and centering the grid does not work together: + const bool bAdjust = static_cast<SwPageFrame*>(GetUpper())->GetFormat()->GetDoc()-> + GetFootnoteIdxs().empty(); + + aRectFnSet.SetPosY( aPrt, bAdjust ? nBorder : 0 ); + aRectFnSet.SetHeight( aPrt, nSize ); + } + } + + if( bNoGrid ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Pos().setX(0); + aPrt.Pos().setY(0); + aPrt.Height( getFrameArea().Height() ); + aPrt.Width( getFrameArea().Width() ); + } + + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); +} + +SwPageFrame::SwPageFrame( SwFrameFormat *pFormat, SwFrame* pSib, SwPageDesc *pPgDsc ) : + SwFootnoteBossFrame( pFormat, pSib ), + m_pDesc( pPgDsc ), + m_nPhyPageNum( 0 ) +{ + SetDerivedVert( false ); + SetDerivedR2L( false ); + if( m_pDesc ) + { + m_bHasGrid = true; + SwTextGridItem const*const pGrid(GetGridItem(this)); + if( !pGrid ) + m_bHasGrid = false; + } + else + m_bHasGrid = false; + SetMaxFootnoteHeight( pPgDsc->GetFootnoteInfo().GetHeight() ? + pPgDsc->GetFootnoteInfo().GetHeight() : LONG_MAX ); + mnFrameType = SwFrameType::Page; + m_bInvalidLayout = m_bInvalidContent = m_bInvalidSpelling = m_bInvalidSmartTags = m_bInvalidAutoCmplWrds = m_bInvalidWordCount = true; + m_bInvalidFlyLayout = m_bInvalidFlyContent = m_bInvalidFlyInCnt = m_bFootnotePage = m_bEndNotePage = false; + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + vcl::RenderContext* pRenderContext = pSh ? pSh->GetOut() : nullptr; + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if ( bBrowseMode ) + { + aFrm.Height( 0 ); + long nWidth = pSh->VisArea().Width(); + + if ( !nWidth ) + { + nWidth = 5000; // changes anyway + } + + aFrm.Width ( nWidth ); + } + else + { + aFrm.SSize( pFormat->GetFrameSize().GetSize() ); + } + } + + // create and insert body area if it is not a blank page + SwDoc* pDoc(pFormat->GetDoc()); + m_bEmptyPage = (pFormat == pDoc->GetEmptyPageFormat()); + + if(m_bEmptyPage) + { + return; + } + + Calc(pRenderContext); // so that the PrtArea is correct + SwBodyFrame *pBodyFrame = new SwBodyFrame( pDoc->GetDfltFrameFormat(), this ); + pBodyFrame->ChgSize( getFramePrintArea().SSize() ); + pBodyFrame->Paste( this ); + pBodyFrame->Calc(pRenderContext); // so that the columns can be inserted correctly + pBodyFrame->InvalidatePos(); + + if ( bBrowseMode ) + InvalidateSize_(); + + // insert header/footer,, but only if active. + if ( pFormat->GetHeader().IsActive() ) + PrepareHeader(); + if ( pFormat->GetFooter().IsActive() ) + PrepareFooter(); + + const SwFormatCol &rCol = pFormat->GetCol(); + if ( rCol.GetNumCols() > 1 ) + { + const SwFormatCol aOld; //ChgColumns() needs an old value + pBodyFrame->ChgColumns( aOld, rCol ); + } + +} + +void SwPageFrame::DestroyImpl() +{ + // Cleanup the header-footer controls in the SwEditWin + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( pSh ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + rEditWin.GetFrameControlsManager( ).RemoveControls( this ); + } + + // empty FlyContainer, deletion of the Flys is done by the anchor (in base class SwFrame) + if (m_pSortedObjs) + { + // Objects can be anchored at pages that are before their anchors (why ever...). + // In such cases, we would access already freed memory. + for (SwAnchoredObject* pAnchoredObj : *m_pSortedObjs) + { + pAnchoredObj->SetPageFrame( nullptr ); + } + m_pSortedObjs.reset(); // reset to zero to prevent problems when detaching the Flys + } + + // prevent access to destroyed pages + SwDoc *pDoc = GetFormat() ? GetFormat()->GetDoc() : nullptr; + if( pDoc && !pDoc->IsInDtor() ) + { + if ( pSh ) + { + SwViewShellImp *pImp = pSh->Imp(); + pImp->SetFirstVisPageInvalid(); + if ( pImp->IsAction() ) + pImp->GetLayAction().SetAgain(); + // #i9719# - retouche area of page + // including border and shadow area. + const bool bRightSidebar = (SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT); + SwRect aRetoucheRect; + SwPageFrame::GetBorderAndShadowBoundRect( getFrameArea(), pSh, pSh->GetOut(), aRetoucheRect, IsLeftShadowNeeded(), IsRightShadowNeeded(), bRightSidebar ); + pSh->AddPaintRect( aRetoucheRect ); + } + } + + SwFootnoteBossFrame::DestroyImpl(); +} + +SwPageFrame::~SwPageFrame() +{ +} + +void SwPageFrame::CheckGrid( bool bInvalidate ) +{ + bool bOld = m_bHasGrid; + m_bHasGrid = true; + SwTextGridItem const*const pGrid(GetGridItem(this)); + m_bHasGrid = nullptr != pGrid; + if( bInvalidate || bOld != m_bHasGrid ) + { + SwLayoutFrame* pBody = FindBodyCont(); + if( pBody ) + { + pBody->InvalidatePrt(); + SwContentFrame* pFrame = pBody->ContainsContent(); + while( pBody->IsAnLower( pFrame ) ) + { + static_cast<SwTextFrame*>(pFrame)->Prepare(); + pFrame = pFrame->GetNextContentFrame(); + } + } + SetCompletePaint(); + } +} + +void SwPageFrame::CheckDirection( bool bVert ) +{ + SvxFrameDirection nDir = GetFormat()->GetFormatAttr( RES_FRAMEDIR ).GetValue(); + if( bVert ) + { + if( SvxFrameDirection::Horizontal_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + mbVertical = true; + + if(SvxFrameDirection::Vertical_RL_TB == nDir) + mbVertLR = false; + else if(SvxFrameDirection::Vertical_LR_TB==nDir) + mbVertLR = true; + } + } + + mbInvalidVert = false; + } + else + { + if( SvxFrameDirection::Horizontal_RL_TB == nDir ) + mbRightToLeft = true; + else + mbRightToLeft = false; + mbInvalidR2L = false; + } +} + +/// create specific Flys for this page and format generic content +static void lcl_FormatLay( SwLayoutFrame *pLay ) +{ + vcl::RenderContext* pRenderContext = pLay->getRootFrame()->GetCurrShell()->GetOut(); + // format all LayoutFrames - no tables, Flys etc. + + SwFrame *pTmp = pLay->Lower(); + // first the low-level ones + while ( pTmp ) + { + const SwFrameType nTypes = SwFrameType::Root | SwFrameType::Page | SwFrameType::Column + | SwFrameType::Header | SwFrameType::Footer | SwFrameType::FtnCont + | SwFrameType::Ftn | SwFrameType::Body; + if ( pTmp->GetType() & nTypes ) + ::lcl_FormatLay( static_cast<SwLayoutFrame*>(pTmp) ); + pTmp = pTmp->GetNext(); + } + pLay->Calc(pRenderContext); +} + +/// Create Flys or register draw objects +static void lcl_MakeObjs( const SwFrameFormats &rTable, SwPageFrame *pPage ) +{ + // formats are in the special table of the document + + for ( size_t i = 0; i < rTable.size(); ++i ) + { + SwFrameFormat *pFormat = rTable[i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( rAnch.GetPageNum() == pPage->GetPhyPageNum() ) + { + if( rAnch.GetContentAnchor() ) + { + if (RndStdIds::FLY_AT_PAGE == rAnch.GetAnchorId()) + { + SwFormatAnchor aAnch( rAnch ); + aAnch.SetAnchor( nullptr ); + pFormat->SetFormatAttr( aAnch ); + } + else + continue; + } + + // is it a border or a SdrObject? + bool bSdrObj = RES_DRAWFRMFMT == pFormat->Which(); + SdrObject *pSdrObj = nullptr; + if ( bSdrObj && nullptr == (pSdrObj = pFormat->FindSdrObject()) ) + { + OSL_FAIL( "DrawObject not found." ); + pFormat->GetDoc()->DelFrameFormat( pFormat ); + --i; + continue; + } + // The object might be anchored to another page, e.g. when inserting + // a new page due to a page descriptor change. In such cases, the + // object needs to be moved. + // In some cases the object is already anchored to the correct page. + // This will be handled here and does not need to be coded extra. + SwPageFrame *pPg = pPage->IsEmptyPage() ? static_cast<SwPageFrame*>(pPage->GetNext()) : pPage; + if ( bSdrObj ) + { + // OD 23.06.2003 #108784# - consider 'virtual' drawing objects + SwDrawContact *pContact = + static_cast<SwDrawContact*>(::GetUserCall(pSdrObj)); + if ( dynamic_cast< const SwDrawVirtObj *>( pSdrObj ) != nullptr ) + { + SwDrawVirtObj* pDrawVirtObj = static_cast<SwDrawVirtObj*>(pSdrObj); + if ( pContact ) + { + pDrawVirtObj->RemoveFromWriterLayout(); + pDrawVirtObj->RemoveFromDrawingPage(); + pPg->AppendDrawObj( *(pContact->GetAnchoredObj( pDrawVirtObj )) ); + } + } + else + { + if ( pContact->GetAnchorFrame() ) + pContact->DisconnectFromLayout( false ); + pPg->AppendDrawObj( *(pContact->GetAnchoredObj( pSdrObj )) ); + } + } + else + { + SwIterator<SwFlyFrame,SwFormat> aIter( *pFormat ); + SwFlyFrame *pFly = aIter.First(); + if ( pFly) + { + if( pFly->GetAnchorFrame() ) + pFly->AnchorFrame()->RemoveFly( pFly ); + } + else + pFly = new SwFlyLayFrame( static_cast<SwFlyFrameFormat*>(pFormat), pPg, pPg ); + pPg->AppendFly( pFly ); + ::RegistFlys( pPg, pFly ); + } + } + } +} + +void SwPageFrame::PreparePage( bool bFootnote ) +{ + SetFootnotePage( bFootnote ); + + // #i82258# + // Due to made change on OOo 2.0 code line, method <::lcl_FormatLay(..)> has + // the side effect, that the content of page header and footer are formatted. + // For this formatting it is needed that the anchored objects are registered + // at the <SwPageFrame> instance. + // Thus, first calling <::RegistFlys(..)>, then call <::lcl_FormatLay(..)> + ::RegistFlys( this, this ); + + if ( Lower() ) + { + ::lcl_FormatLay( this ); + } + + // Flys and draw objects that are still attached to the document. + // Footnote pages do not have page-bound Flys! + // There might be Flys or draw objects that want to be placed on + // empty pages, however, the empty pages ignore that and the following + // pages take care of them. + if ( !bFootnote && !IsEmptyPage() ) + { + SwDoc *pDoc = GetFormat()->GetDoc(); + + if ( GetPrev() && static_cast<SwPageFrame*>(GetPrev())->IsEmptyPage() ) + lcl_MakeObjs( *pDoc->GetSpzFrameFormats(), static_cast<SwPageFrame*>(GetPrev()) ); + lcl_MakeObjs( *pDoc->GetSpzFrameFormats(), this ); + } +} + +void SwPageFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( pSh ) + pSh->SetFirstVisPageInvalid(); + sal_uInt8 nInvFlags = 0; + + if( pNew && RES_ATTRSET_CHG == pNew->Which() ) + { + SfxItemIter aNIter( *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet() ); + SfxItemIter aOIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); + do + { + UpdateAttr_(pOItem, pNItem, nInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + if ( aOldSet.Count() || aNewSet.Count() ) + SwLayoutFrame::Modify( &aOldSet, &aNewSet ); + } + else + UpdateAttr_( pOld, pNew, nInvFlags ); + + if ( nInvFlags != 0 ) + { + InvalidatePage( this ); + if ( nInvFlags & 0x01 ) + InvalidatePrt_(); + if ( nInvFlags & 0x02 ) + SetCompletePaint(); + if ( nInvFlags & 0x04 && GetNext() ) + GetNext()->InvalidatePos(); + if ( nInvFlags & 0x08 ) + PrepareHeader(); + if ( nInvFlags & 0x10 ) + PrepareFooter(); + if ( nInvFlags & 0x20 ) + CheckGrid( nInvFlags & 0x40 ); + } +} + + +void SwPageFrame::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if(typeid(sw::PageFootnoteHint) == typeid(rHint)) + { + // currently the savest way: + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + SetMaxFootnoteHeight(m_pDesc->GetFootnoteInfo().GetHeight()); + if(!GetMaxFootnoteHeight()) + SetMaxFootnoteHeight(LONG_MAX); + SetColMaxFootnoteHeight(); + // here, the page might be destroyed: + static_cast<SwRootFrame*>(GetUpper())->RemoveFootnotes(nullptr, false, true); + } + else + SwFrame::SwClientNotify(rModify, rHint); +} + +void SwPageFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + sal_uInt8 &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_FMT_CHG: + { + // state of m_bEmptyPage needs to be determined newly + const bool bNewState(GetFormat() == GetFormat()->GetDoc()->GetEmptyPageFormat()); + + if(m_bEmptyPage != bNewState) + { + // copy new state + m_bEmptyPage = bNewState; + + if(nullptr == GetLower()) + { + // if we were an empty page before there is not yet a BodyArea in the + // form of a SwBodyFrame, see constructor + SwViewShell* pSh(getRootFrame()->GetCurrShell()); + vcl::RenderContext* pRenderContext(pSh ? pSh->GetOut() : nullptr); + Calc(pRenderContext); // so that the PrtArea is correct + SwBodyFrame* pBodyFrame = new SwBodyFrame(GetFormat(), this); + pBodyFrame->ChgSize(getFramePrintArea().SSize()); + pBodyFrame->Paste(this); + pBodyFrame->InvalidatePos(); + } + } + + // If the frame format is changed, several things might also change: + // 1. columns: + assert(pOld && pNew); //FMT_CHG Missing Format + const SwFormat *const pOldFormat = static_cast<const SwFormatChg*>(pOld)->pChangedFormat; + const SwFormat *const pNewFormat = static_cast<const SwFormatChg*>(pNew)->pChangedFormat; + assert(pOldFormat && pNewFormat); //FMT_CHG Missing Format + const SwFormatCol &rOldCol = pOldFormat->GetCol(); + const SwFormatCol &rNewCol = pNewFormat->GetCol(); + if( rOldCol != rNewCol ) + { + SwLayoutFrame *pB = FindBodyCont(); + assert(pB && "Page without Body."); + pB->ChgColumns( rOldCol, rNewCol ); + rInvFlags |= 0x20; + } + + // 2. header and footer: + const SwFormatHeader &rOldH = pOldFormat->GetHeader(); + const SwFormatHeader &rNewH = pNewFormat->GetHeader(); + if( rOldH != rNewH ) + rInvFlags |= 0x08; + + const SwFormatFooter &rOldF = pOldFormat->GetFooter(); + const SwFormatFooter &rNewF = pNewFormat->GetFooter(); + if( rOldF != rNewF ) + rInvFlags |= 0x10; + CheckDirChange(); + + [[fallthrough]]; + } + case RES_FRM_SIZE: + { + const SwRect aOldPageFrameRect( getFrameArea() ); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + setFrameAreaSizeValid(false); + // OD 28.10.2002 #97265# - Don't call <SwPageFrame::MakeAll()> + // Calculation of the page is not necessary, because its size is + // invalidated here and further invalidation is done in the + // calling method <SwPageFrame::Modify(..)> and probably by calling + // <SwLayoutFrame::Modify(..)> at the end. + // It can also causes inconsistences, because the lowers are + // adjusted, but not calculated, and a <SwPageFrame::MakeAll()> of + // a next page is called. This is performed on the switch to the + // online layout. + //MakeAll(); + } + else if (pNew) + { + const SwFormatFrameSize &rSz = nWhich == RES_FMT_CHG ? + static_cast<const SwFormatChg*>(pNew)->pChangedFormat->GetFrameSize() : + static_cast<const SwFormatFrameSize&>(*pNew); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( std::max( rSz.GetHeight(), long(MINLAY) ) ); + aFrm.Width ( std::max( rSz.GetWidth(), long(MINLAY) ) ); + } + + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->CheckViewLayout( nullptr, nullptr ); + } + } + // cleanup Window + if( pSh && pSh->GetWin() && aOldPageFrameRect.HasArea() ) + { + // #i9719# - consider border and shadow of + // page frame for determine 'old' rectangle - it's used for invalidating. + const bool bRightSidebar = (SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT); + SwRect aOldRectWithBorderAndShadow; + SwPageFrame::GetBorderAndShadowBoundRect( aOldPageFrameRect, pSh, pSh->GetOut(), aOldRectWithBorderAndShadow, + IsLeftShadowNeeded(), IsRightShadowNeeded(), bRightSidebar ); + pSh->InvalidateWindows( aOldRectWithBorderAndShadow ); + } + rInvFlags |= 0x03; + if ( aOldPageFrameRect.Height() != getFrameArea().Height() ) + rInvFlags |= 0x04; + } + break; + + case RES_COL: + assert(pOld && pNew); //COL Missing Format + if (pOld && pNew) + { + SwLayoutFrame *pB = FindBodyCont(); + assert(pB); //page without body + pB->ChgColumns( *static_cast<const SwFormatCol*>(pOld), *static_cast<const SwFormatCol*>(pNew) ); + rInvFlags |= 0x22; + } + break; + + case RES_HEADER: + rInvFlags |= 0x08; + break; + + case RES_FOOTER: + rInvFlags |= 0x10; + break; + case RES_TEXTGRID: + rInvFlags |= 0x60; + break; + case RES_FRAMEDIR : + CheckDirChange(); + break; + + default: + bClear = false; + } + if ( bClear ) + { + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + SwLayoutFrame::Modify( pOld, pNew ); + } +} + +/// get information from Modify +bool SwPageFrame::GetInfo( SfxPoolItem & rInfo ) const +{ + if( RES_AUTOFMT_DOCNODE == rInfo.Which() ) + { + // a page frame exists, so use this one + return false; + } + return true; // continue searching +} + +void SwPageFrame::SetPageDesc( SwPageDesc *pNew, SwFrameFormat *pFormat ) +{ + m_pDesc = pNew; + if ( pFormat ) + SetFrameFormat( pFormat ); +} + +/* determine the right PageDesc: + * 0. from the document for footnote and endnote pages + * 1. from the first BodyContent below a page + * 2. from PageDesc of the predecessor page + * 3. from PageDesc of the previous page if blank page + * 3.1 from PageDesc of the next page if no predecessor exists + * 4. default PageDesc + * 5. In BrowseMode use the first paragraph or default PageDesc. + */ +SwPageDesc *SwPageFrame::FindPageDesc() +{ + // 0. + if ( IsFootnotePage() ) + { + SwDoc *pDoc = GetFormat()->GetDoc(); + if ( IsEndNotePage() ) + return pDoc->GetEndNoteInfo().GetPageDesc( *pDoc ); + else + return pDoc->GetFootnoteInfo().GetPageDesc( *pDoc ); + } + + SwPageDesc *pRet = nullptr; + + //5. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + SwContentFrame *pFrame = GetUpper()->ContainsContent(); + while (pFrame && !pFrame->IsInDocBody()) + pFrame = pFrame->GetNextContentFrame(); + if (pFrame) + { + SwFrame *pFlow = pFrame; + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + pRet = const_cast<SwPageDesc*>(pFlow->GetPageDescItem().GetPageDesc()); + } + if ( !pRet ) + pRet = &GetFormat()->GetDoc()->GetPageDesc( 0 ); + return pRet; + } + + SwFrame *pFlow = FindFirstBodyContent(); + if ( pFlow && pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + + //1. + if ( pFlow ) + { + SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pFlow ); + if ( !pTmp->IsFollow() ) + pRet = const_cast<SwPageDesc*>(pFlow->GetPageDescItem().GetPageDesc()); + } + + //3. and 3.1 + if ( !pRet && IsEmptyPage() ) + // FME 2008-03-03 #i81544# lijian/fme: an empty page should have + // the same page description as its prev, just like after construction + // of the empty page. + pRet = GetPrev() ? static_cast<SwPageFrame*>(GetPrev())->GetPageDesc() : + GetNext() ? static_cast<SwPageFrame*>(GetNext())->GetPageDesc() : nullptr; + + //2. + if ( !pRet ) + pRet = GetPrev() ? + static_cast<SwPageFrame*>(GetPrev())->GetPageDesc()->GetFollow() : nullptr; + + //4. + if ( !pRet ) + pRet = &GetFormat()->GetDoc()->GetPageDesc( 0 ); + + OSL_ENSURE( pRet, "could not find page descriptor." ); + return pRet; +} + +// Notify if the RootFrame changes its size +void AdjustSizeChgNotify( SwRootFrame *pRoot ) +{ + const bool bOld = pRoot->IsSuperfluous(); + pRoot->mbCheckSuperfluous = false; + if ( pRoot->GetCurrShell() ) + { + for(SwViewShell& rSh : pRoot->GetCurrShell()->GetRingContainer()) + { + if( pRoot == rSh.GetLayout() ) + { + rSh.SizeChgNotify(); + if ( rSh.Imp() ) + rSh.Imp()->NotifySizeChg( pRoot->getFrameArea().SSize() ); + } + } + } + pRoot->mbCheckSuperfluous = bOld; +} + +inline void SetLastPage( SwPageFrame *pPage ) +{ + static_cast<SwRootFrame*>(pPage->GetUpper())->mpLastPage = pPage; +} + +void SwPageFrame::Cut() +{ + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( !IsEmptyPage() ) + { + if ( GetNext() ) + GetNext()->InvalidatePos(); + + // move Flys whose anchor is on a different page (draw objects are not relevant here) + if ( GetSortedObjs() ) + { + size_t i = 0; + while ( GetSortedObjs() && i < GetSortedObjs()->size() ) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = (*GetSortedObjs())[i]; + + if ( dynamic_cast< const SwFlyAtContentFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyAtContentFrame*>(pAnchoredObj); + SwPageFrame *pAnchPage = pFly->GetAnchorFrame() ? + pFly->AnchorFrame()->FindPageFrame() : nullptr; + if ( pAnchPage && (pAnchPage != this) ) + { + MoveFly( pFly, pAnchPage ); + pFly->InvalidateSize(); + pFly->InvalidatePos_(); + // Do not increment index, in this case + continue; + } + } + ++i; + } + } + // cleanup Window + if ( pSh && pSh->GetWin() ) + pSh->InvalidateWindows( getFrameArea() ); + } + + // decrease the root's page number + static_cast<SwRootFrame*>(GetUpper())->DecrPhyPageNums(); + SwPageFrame *pPg = static_cast<SwPageFrame*>(GetNext()); + if ( pPg ) + { + while ( pPg ) + { + --pPg->m_nPhyPageNum; + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } + } + else + ::SetLastPage( static_cast<SwPageFrame*>(GetPrev()) ); + + SwFrame* pRootFrame = GetUpper(); + + // cut all connections + RemoveFromLayout(); + + if ( pRootFrame ) + static_cast<SwRootFrame*>(pRootFrame)->CheckViewLayout( nullptr, nullptr ); +} + +void SwPageFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent->IsRootFrame(), "Parent is no Root." ); + OSL_ENSURE( pParent, "No parent for Paste()." ); + OSL_ENSURE( pParent != this, "I'm my own parent." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I am still registered somewhere." ); + + // insert into tree structure + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + // increase the root's page number + static_cast<SwRootFrame*>(GetUpper())->IncrPhyPageNums(); + if( GetPrev() ) + SetPhyPageNum( static_cast<SwPageFrame*>(GetPrev())->GetPhyPageNum() + 1 ); + else + SetPhyPageNum( 1 ); + SwPageFrame *pPg = static_cast<SwPageFrame*>(GetNext()); + if ( pPg ) + { + while ( pPg ) + { + ++pPg->m_nPhyPageNum; + pPg->InvalidatePos_(); + pPg->InvalidateLayout(); + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } + } + else + ::SetLastPage( this ); + + if( getFrameArea().Width() != pParent->getFramePrintArea().Width() ) + InvalidateSize_(); + + InvalidatePos(); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( pSh ) + pSh->SetFirstVisPageInvalid(); + + getRootFrame()->CheckViewLayout( nullptr, nullptr ); +} + +static void lcl_PrepFlyInCntRegister( SwContentFrame *pFrame ) +{ + pFrame->Prepare( PrepareHint::Register ); + if( pFrame->GetDrawObjs() ) + { + for(SwAnchoredObject* pAnchoredObj : *pFrame->GetDrawObjs()) + { + // #i28701# + if ( dynamic_cast< const SwFlyInContentFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyInContentFrame*>(pAnchoredObj); + SwContentFrame *pCnt = pFly->ContainsContent(); + while ( pCnt ) + { + lcl_PrepFlyInCntRegister( pCnt ); + pCnt = pCnt->GetNextContentFrame(); + } + } + } + } +} + +void SwPageFrame::PrepareRegisterChg() +{ + SwContentFrame *pFrame = FindFirstBodyContent(); + while( pFrame ) + { + lcl_PrepFlyInCntRegister( pFrame ); + pFrame = pFrame->GetNextContentFrame(); + if( !IsAnLower( pFrame ) ) + break; + } + if( GetSortedObjs() ) + { + for(SwAnchoredObject* pAnchoredObj : *GetSortedObjs()) + { + // #i28701# + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + pFrame = pFly->ContainsContent(); + while ( pFrame ) + { + ::lcl_PrepFlyInCntRegister( pFrame ); + pFrame = pFrame->GetNextContentFrame(); + } + } + } + } +} + +//FIXME: provide missing documentation +/** Check all pages (starting from the given one) if they use the appropriate frame format. + * + * If "wrong" pages are found, try to fix this as simple as possible. + * + * @param pStart the page from where to start searching + * @param bNotifyFields + * @param ppPrev + */ +void SwFrame::CheckPageDescs( SwPageFrame *pStart, bool bNotifyFields, SwPageFrame** ppPrev ) +{ + SAL_INFO( "sw.pageframe", "(CheckPageDescs in phy: " << pStart->GetPhyPageNum() ); + assert(pStart && "no starting page."); + + SwViewShell *pSh = pStart->getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + + if ( pImp && pImp->IsAction() && !pImp->GetLayAction().IsCheckPages() ) + { + pImp->GetLayAction().SetCheckPageNum( pStart->GetPhyPageNum() ); + SAL_INFO( "sw.pageframe", "CheckPageDescs out fast - via SetCheckPageNum: " + << pStart->GetPhyPageNum() << ")" ); + return; + } + + // For the update of page numbering fields, nDocPos provides + // the page position from where invalidation should start. + SwTwips nDocPos = LONG_MAX; + + SwRootFrame *pRoot = static_cast<SwRootFrame*>(pStart->GetUpper()); + SwDoc* pDoc = pStart->GetFormat()->GetDoc(); + const bool bFootnotes = !pDoc->GetFootnoteIdxs().empty(); + + SwPageFrame *pPage = pStart; + if( pPage->GetPrev() && static_cast<SwPageFrame*>(pPage->GetPrev())->IsEmptyPage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + while ( pPage ) + { + SwPageFrame *pPrevPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + SwPageFrame *pNextPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + SwPageDesc *pDesc = pPage->FindPageDesc(); + bool bIsEmpty = pPage->IsEmptyPage(); + bool bIsOdd = pPage->OnRightPage(); + bool bWantOdd = pPage->WannaRightPage(); + bool bFirst = pPage->OnFirstPage(); + SwFrameFormat *pFormatWish = bWantOdd + ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst); + + if ( bIsOdd != bWantOdd || + pDesc != pPage->GetPageDesc() || // wrong Desc + ( pFormatWish != pPage->GetFormat() && // wrong format and + ( !bIsEmpty || pFormatWish ) // not blank /empty + ) + ) + { + // Updating a page might take a while, so check the WaitCursor + if( pImp ) + pImp->CheckWaitCursor(); + + // invalidate the field, starting from here + if ( nDocPos == LONG_MAX ) + nDocPos = pPrevPage ? pPrevPage->getFrameArea().Top() : pPage->getFrameArea().Top(); + + // Cases: + // 1. Empty page should be "normal" page -> remove empty page and take next one + // 2. Empty page should have different descriptor -> change + // 3. Normal page should be empty -> insert empty page if previous page + // is not empty, otherwise see (6). + // 4. Normal page should have different descriptor -> change + // 5. Normal page should have different format -> change + // 6. No "wish" format provided -> take the "other" format (left/right) of the PageDesc + + if ( bIsEmpty && ( pFormatWish || //1. + ( !bWantOdd && !pPrevPage ) ) ) + { + // Check all cases for the next page, so we don't oscillate empty pages + // Skip case 1 and 2, as we require a non-empty next page to save the empty page + // Case 3 is the one we actually want to predict and skip + // We can skip the empty check of case 3, as we just work on an existing next page + bool bNextWantOdd; + SwPageDesc *pNextDesc; + if ( pNextPage && !pNextPage->IsEmptyPage() && //3. + pNextPage->OnRightPage() == (bNextWantOdd = pNextPage->WannaRightPage()) && + pNextPage->GetPageDesc() == (pNextDesc = pNextPage->FindPageDesc()) ) //4. + { + bool bNextFirst = pNextPage->OnFirstPage(); + SwFrameFormat *pNextFormatWish = bNextWantOdd ? //5. + pNextDesc->GetRightFormat(bNextFirst) : pNextDesc->GetLeftFormat(bNextFirst); + if ( !pNextFormatWish ) // 6. + pNextFormatWish = bNextWantOdd ? pNextDesc->GetLeftFormat() : pNextDesc->GetRightFormat(); + if ( pNextFormatWish && pNextPage->GetFormat() == pNextFormatWish ) + { + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 1+3 - skip next page of p: " << pPage ); + if (pPrevPage && pPage->GetPageDesc() != pPrevPage->GetPageDesc()) + pPage->SetPageDesc( pPrevPage->GetPageDesc(), nullptr ); + // We can skip the next page, as all checks were already done! + pPage = static_cast<SwPageFrame*>(pNextPage->GetNext()); + continue; + } + } + + pPage->Cut(); + bool bUpdatePrev = false; + if (ppPrev && *ppPrev == pPage) + bUpdatePrev = true; + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 1 - destroy p: " << pPage ); + SwFrame::DestroyFrame(pPage); + if ( pStart == pPage ) + pStart = pNextPage; + pPage = pNextPage; + if (bUpdatePrev) + *ppPrev = pNextPage; + continue; + } + else if ( bIsEmpty && !pFormatWish && //2. + pDesc != pPage->GetPageDesc() ) + { + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 2 - set desc p: " << pPage << " d: " << pDesc ); + pPage->SetPageDesc( pDesc, nullptr ); + } + else if ( !bIsEmpty && //3. + bIsOdd != bWantOdd && + ( ( !pPrevPage && !bWantOdd ) || + ( pPrevPage && !pPrevPage->IsEmptyPage() ) + ) + ) + { + if ( pPrevPage ) + pDesc = pPrevPage->GetPageDesc(); + SwPageFrame *pTmp = new SwPageFrame( pDoc->GetEmptyPageFormat(), pRoot, pDesc ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 3 - insert empty p: " << pTmp << " d: " << pDesc ); + pTmp->Paste( pRoot, pPage ); + pTmp->PreparePage( false ); + pPage = pTmp; + } + else if ( pPage->GetPageDesc() != pDesc ) //4. + { + SwPageDesc *pOld = pPage->GetPageDesc(); + pPage->SetPageDesc( pDesc, pFormatWish ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 4 - set desc + format p: " << pPage + << " d: " << pDesc << " f: " << pFormatWish ); + if ( bFootnotes ) + { + // If specific values of the FootnoteInfo are changed, something has to happen. + // We try to limit the damage... + // If the page has no FootnoteCont it might be problematic. + // Let's hope that invalidation is enough. + SwFootnoteContFrame *pCont = pPage->FindFootnoteCont(); + if ( pCont && !(pOld->GetFootnoteInfo() == pDesc->GetFootnoteInfo()) ) + pCont->InvalidateAll_(); + } + } + else if ( pFormatWish && pPage->GetFormat() != pFormatWish ) //5. + { + pPage->SetFrameFormat( pFormatWish ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 5 - set format p: " << pPage << " f: " << pFormatWish ); + } + else if ( !pFormatWish ) //6. + { + // get format with inverted logic + pFormatWish = bWantOdd ? pDesc->GetLeftFormat() : pDesc->GetRightFormat(); + if ( pFormatWish && pPage->GetFormat() != pFormatWish ) + { + pPage->SetFrameFormat( pFormatWish ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 6 - set format p: " << pPage << " f: " << pFormatWish ); + } + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "CheckPageDescs, missing solution" ); + } +#endif + } + if ( bIsEmpty ) + { + // It also might be that an empty page is not needed at all. + // However, the algorithm above cannot determine that. It is not needed if the following + // page can live without it. Do obtain that information, we need to dig deeper... + SwPageFrame *pPg = static_cast<SwPageFrame*>(pPage->GetNext()); + if( !pPg || pPage->OnRightPage() == pPg->WannaRightPage() ) + { + // The following page can find a FrameFormat or has no successor -> empty page not needed + SwPageFrame *pTmp = static_cast<SwPageFrame*>(pPage->GetNext()); + pPage->Cut(); + bool bUpdatePrev = false; + if (ppPrev && *ppPrev == pPage) + bUpdatePrev = true; + SwFrame::DestroyFrame(pPage); + SAL_INFO( "sw.pageframe", "CheckPageDescs - handle bIsEmpty - destroy p: " << pPage ); + if ( pStart == pPage ) + pStart = pTmp; + pPage = pTmp; + if (bUpdatePrev) + *ppPrev = pTmp; + continue; + } + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + pRoot->SetAssertFlyPages(); + SwRootFrame::AssertPageFlys( pStart ); + + if ( bNotifyFields && (!pImp || !pImp->IsUpdateExpFields()) ) + { + SwDocPosUpdate aMsgHint( nDocPos ); + pDoc->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + +#if OSL_DEBUG_LEVEL > 0 + //1. check if two empty pages are behind one another + bool bEmpty = false; + SwPageFrame *pPg = pStart; + while ( pPg ) + { + if ( pPg->IsEmptyPage() ) + { + if ( bEmpty ) + { + OSL_FAIL( "double empty pages." ); + break; // once is enough + } + bEmpty = true; + } + else + bEmpty = false; + + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } +#endif + SAL_INFO( "sw.pageframe", "CheckPageDescs out)" ); +} + +namespace +{ + bool isDeleteForbidden(const SwPageFrame *pDel) + { + if (pDel->IsDeleteForbidden()) + return true; + const SwLayoutFrame* pBody = pDel->FindBodyCont(); + const SwFrame* pBodyContent = pBody ? pBody->Lower() : nullptr; + return pBodyContent && pBodyContent->IsDeleteForbidden(); + } + + bool doInsertPage( SwRootFrame *pRoot, SwPageFrame **pRefSibling, + SwFrameFormat *pFormat, SwPageDesc *pDesc, + bool bFootnote, SwPageFrame **pRefPage ) + { + SwPageFrame *pPage = new SwPageFrame(pFormat, pRoot, pDesc); + SwPageFrame *pSibling = *pRefSibling; + if ( pRefPage ) + { + *pRefPage = pPage; + SAL_INFO( "sw.pageframe", "doInsertPage p: " << pPage + << " d: " << pDesc << " f: " << pFormat ); + } + else + SAL_INFO( "sw.pageframe", "doInsertPage - insert empty p: " + << pPage << " d: " << pDesc ); + pPage->Paste( pRoot, pSibling ); + + SwViewShell* pViewShell = pRoot->GetCurrShell(); + if (pViewShell && pViewShell->GetViewOptions()->IsHideWhitespaceMode()) + { + // Hide-whitespace mode does not shrink the last page, so resize the page that used to + // be the last one. + if (SwFrame* pPrevPage = pPage->GetPrev()) + { + pPrevPage->InvalidateSize(); + } + } + + pPage->PreparePage( bFootnote ); + // If the sibling has no body text, destroy it as long as it is no footnote page. + if ( pSibling && !pSibling->IsFootnotePage() && + !pSibling->FindFirstBodyContent() && + (!pRefPage || !isDeleteForbidden(pSibling)) ) + { + pRoot->RemovePage( pRefSibling, SwRemoveResult::Next ) ; + return false; + } + return true; + } +} + +SwPageFrame *SwFrame::InsertPage( SwPageFrame *pPrevPage, bool bFootnote ) +{ + SwRootFrame *pRoot = static_cast<SwRootFrame*>(pPrevPage->GetUpper()); + SwPageFrame *pSibling = static_cast<SwPageFrame*>(pPrevPage->GetNext()); + SwPageDesc *pDesc = nullptr; + + // insert right (odd) or left (even) page? + bool bNextRightPage = !pPrevPage->OnRightPage(); + bool bWishedRightPage = bNextRightPage; + + // Which PageDesc is relevant? + // For ContentFrame take the one from format if provided, + // otherwise from the Follow of the PrevPage + if ( IsFlowFrame() && !SwFlowFrame::CastFlowFrame( this )->IsFollow() ) + { + SwFormatPageDesc &rDesc = const_cast<SwFormatPageDesc&>(GetPageDescItem()); + pDesc = rDesc.GetPageDesc(); + if ( rDesc.GetNumOffset() ) + { + ::std::optional<sal_uInt16> oNumOffset = rDesc.GetNumOffset(); + bWishedRightPage = sw::IsRightPageByNumber(*pRoot, *oNumOffset); + // use the opportunity to set the flag at root + pRoot->SetVirtPageNum( true ); + } + } + if ( !pDesc ) + pDesc = pPrevPage->GetPageDesc()->GetFollow(); + + assert(pDesc && "Missing PageDesc"); + if( !(bWishedRightPage ? pDesc->GetRightFormat() : pDesc->GetLeftFormat()) ) + bWishedRightPage = !bWishedRightPage; + bool const bWishedFirst = pDesc != pPrevPage->GetPageDesc(); + + SwDoc *pDoc = pPrevPage->GetFormat()->GetDoc(); + bool bCheckPages = false; + // If there is no FrameFormat for this page, create an empty page. + if (bWishedRightPage != bNextRightPage) + { + if( doInsertPage( pRoot, &pSibling, pDoc->GetEmptyPageFormat(), + pPrevPage->GetPageDesc(), bFootnote, nullptr ) ) + bCheckPages = true; + } + SwFrameFormat *const pFormat( bWishedRightPage + ? pDesc->GetRightFormat(bWishedFirst) + : pDesc->GetLeftFormat(bWishedFirst) ); + assert(pFormat); + SwPageFrame *pPage = nullptr; + if( doInsertPage( pRoot, &pSibling, pFormat, pDesc, bFootnote, &pPage ) ) + bCheckPages = true; + + if ( pSibling ) + { + if ( bCheckPages ) + { + CheckPageDescs( pSibling, false ); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + if ( pImp && pImp->IsAction() && !pImp->GetLayAction().IsCheckPages() ) + { + const sal_uInt16 nNum = pImp->GetLayAction().GetCheckPageNum(); + if ( nNum == pPrevPage->GetPhyPageNum() + 1 ) + { + pImp->GetLayAction().SetCheckPageNumDirect( + pSibling->GetPhyPageNum() ); + SAL_INFO( "sw.pageframe", "InsertPage - SetCheckPageNumDirect: " + << pSibling->GetPhyPageNum() ); + } + return pPage; + } + } + else + SwRootFrame::AssertPageFlys( pSibling ); + } + + // For the update of page numbering fields, nDocPos provides + // the page position from where invalidation should start. + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( !pSh || !pSh->Imp()->IsUpdateExpFields() ) + { + SwDocPosUpdate aMsgHint( pPrevPage->getFrameArea().Top() ); + pDoc->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + return pPage; +} + +sw::sidebarwindows::SidebarPosition SwPageFrame::SidebarPosition() const +{ + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( !pSh || pSh->GetViewOptions()->getBrowseMode() ) + { + return sw::sidebarwindows::SidebarPosition::RIGHT; + } + else + { + const bool bLTR = getRootFrame()->IsLeftToRightViewLayout(); + const bool bBookMode = pSh->GetViewOptions()->IsViewLayoutBookMode(); + const bool bRightSidebar = bLTR ? (!bBookMode || OnRightPage()) : (bBookMode && !OnRightPage()); + + return bRightSidebar + ? sw::sidebarwindows::SidebarPosition::RIGHT + : sw::sidebarwindows::SidebarPosition::LEFT; + } +} + +SwTwips SwRootFrame::GrowFrame( SwTwips nDist, bool bTst, bool ) +{ + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.AddHeight(nDist ); + } + + return nDist; +} + +SwTwips SwRootFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool ) +{ + OSL_ENSURE( nDist >= 0, "nDist < 0." ); + OSL_ENSURE( nDist <= getFrameArea().Height(), "nDist greater than current size." ); + + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.AddHeight( -nDist ); + } + + return nDist; +} + +void SwRootFrame::RemovePage( SwPageFrame **pDelRef, SwRemoveResult eResult ) +{ + SwPageFrame *pDel = *pDelRef; + (*pDelRef) = static_cast<SwPageFrame*>( + eResult == SwRemoveResult::Next ? pDel->GetNext() : pDel->GetPrev() ); + if ( !GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + RemoveFootnotes( pDel, true ); + pDel->Cut(); + SwFrame::DestroyFrame( pDel ); +} + +/// remove pages that are not needed at all +void SwRootFrame::RemoveSuperfluous() +{ + // A page is empty if the body text area has no ContentFrame, but not if there + // is at least one Fly or one footnote attached to the page. Two runs are + // needed: one for endnote pages and one for the pages of the body text. + + if ( !IsSuperfluous() ) + return; + mbCheckSuperfluous = false; + + SwPageFrame *pPage = GetLastPage(); + long nDocPos = LONG_MAX; + + // Check the corresponding last page if it is empty and stop loop at the last non-empty page. + do + { + bool bExistEssentialObjs = ( nullptr != pPage->GetSortedObjs() ); + if ( bExistEssentialObjs ) + { + // Only because the page has Flys does not mean that it is needed. If all Flys are + // attached to generic content it is also superfluous (checking DocBody should be enough) + // OD 19.06.2003 #108784# - consider that drawing objects in + // header/footer are supported now. + bool bOnlySuperfluosObjs = true; + SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for ( size_t i = 0; bOnlySuperfluosObjs && i < rObjs.size(); ++i ) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = rObjs[i]; + // OD 2004-01-19 #110582# - do not consider hidden objects + if ( pPage->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( + pAnchoredObj->GetDrawObj()->GetLayer() ) && + !pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) + { + bOnlySuperfluosObjs = false; + } + } + bExistEssentialObjs = !bOnlySuperfluosObjs; + } + + // OD 19.06.2003 #108784# - optimization: check first, if essential objects + // exists. + const SwLayoutFrame* pBody = nullptr; + if ( bExistEssentialObjs || + pPage->FindFootnoteCont() || + ( nullptr != ( pBody = pPage->FindBodyCont() ) && + ( pBody->ContainsContent() || + // #i47580# + // Do not delete page if there's an empty tabframe + // left. I think it might be correct to use ContainsAny() + // instead of ContainsContent() to cover the empty-table-case, + // but I'm not fully sure, since ContainsAny() also returns + // SectionFrames. Therefore I prefer to do it the safe way: + ( pBody->Lower() && pBody->Lower()->IsTabFrame() ) ) ) ) + { + if ( pPage->IsFootnotePage() ) + { + while ( pPage->IsFootnotePage() ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + OSL_ENSURE( pPage, "only endnote pages remain." ); + } + continue; + } + else + pPage = nullptr; + } + + if ( pPage ) + { + SAL_INFO( "sw.pageframe", "RemoveSuperfluous - DestroyFrm p: " << pPage ); + RemovePage( &pPage, SwRemoveResult::Prev ); + nDocPos = pPage ? pPage->getFrameArea().Top() : 0; + } + } while ( pPage ); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( nDocPos != LONG_MAX && + (!pSh || !pSh->Imp()->IsUpdateExpFields()) ) + { + SwDocPosUpdate aMsgHint( nDocPos ); + GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } +} + +/// Ensures that enough pages exist, so that all page bound frames and draw objects can be placed +void SwRootFrame::AssertFlyPages() +{ + if ( !IsAssertFlyPages() ) + return; + mbAssertFlyPages = false; + + SwDoc *pDoc = GetFormat()->GetDoc(); + const SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + + // what page targets the "last" Fly? + // note the needed pages in a set + sal_uInt16 nMaxPg(0); + std::set< sal_uInt16 > neededPages; + + for ( size_t i = 0; i < pTable->size(); ++i ) + { + const SwFormatAnchor &rAnch = (*pTable)[i]->GetAnchor(); + if(!rAnch.GetContentAnchor()) + { + const sal_uInt16 nPageNum(rAnch.GetPageNum()); + + // calc MaxPage (as before) + nMaxPg = std::max(nMaxPg, nPageNum); + + // note as needed page + neededPages.insert(nPageNum); + } + } + + // How many pages exist at the moment? + // And are there EmptyPages that are needed? + SwPageFrame* pPage(static_cast<SwPageFrame*>(Lower())); + SwPageFrame* pPrevPage(nullptr); + SwPageFrame* pFirstRevivedEmptyPage(nullptr); + + while(pPage) // moved two while-conditions to break-statements (see below) + { + const sal_uInt16 nPageNum(pPage->GetPhyPageNum()); + + if(pPage->IsEmptyPage() && + nullptr != pPrevPage && + neededPages.find(nPageNum) != neededPages.end()) + { + // This is an empty page, but it *is* needed since a SwFrame + // is anchored at it directly. Initially these SwFrames are + // not fully initialized. Need to change the format of this SwFrame + // and let the ::Notify mechanism newly evaluate + // m_bEmptyPage (see SwPageFrame::UpdateAttr_). Code is taken and + // adapted from ::InsertPage (used below), this needs previous page + bool bWishedRightPage(!pPrevPage->OnRightPage()); + SwPageDesc* pDesc(pPrevPage->GetPageDesc()->GetFollow()); + assert(pDesc && "Missing PageDesc"); + + if (!(bWishedRightPage ? pDesc->GetRightFormat() : pDesc->GetLeftFormat())) + { + bWishedRightPage = !bWishedRightPage; + } + + bool const bWishedFirst(pDesc != pPrevPage->GetPageDesc()); + SwFrameFormat* pFormat(bWishedRightPage ? pDesc->GetRightFormat(bWishedFirst) : pDesc->GetLeftFormat(bWishedFirst)); + + // set SwFrameFormat, this will trigger SwPageFrame::UpdateAttr_ and re-evaluate + // m_bEmptyPage, too + pPage->SetFrameFormat(pFormat); + + if(nullptr == pFirstRevivedEmptyPage) + { + // remember first (lowest) SwPageFrame which needed correction + pFirstRevivedEmptyPage = pPage; + } + } + + // original while-condition II + if(nullptr == pPage->GetNext()) + { + break; + } + + // original while-condition III + if(static_cast< SwPageFrame* >(pPage->GetNext())->IsFootnotePage()) + { + break; + } + + pPrevPage = pPage; + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + if ( nMaxPg > pPage->GetPhyPageNum() ) + { + for ( sal_uInt16 i = pPage->GetPhyPageNum(); i < nMaxPg; ++i ) + pPage = InsertPage( pPage, false ); + + // If the endnote pages are now corrupt, destroy them. + if ( !pDoc->GetFootnoteIdxs().empty() ) + { + pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && !pPage->IsFootnotePage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + if ( pPage ) + { + SwPageDesc *pTmpDesc = pPage->FindPageDesc(); + bool isRightPage = pPage->OnRightPage(); + if ( pPage->GetFormat() != + (isRightPage ? pTmpDesc->GetRightFormat() : pTmpDesc->GetLeftFormat()) ) + RemoveFootnotes( pPage, false, true ); + } + } + } + + // if we corrected SwFrameFormat and changed one (or more) m_bEmptyPage + // flags, we need to correct evtl. currently wrong positioned SwFrame(s) + // which did think until now that these Page(s) are empty. + // After trying to correct myself I found SwRootFrame::AssertPageFlys + // directly below that already does that, so use it. + if(nullptr != pFirstRevivedEmptyPage) + { + AssertPageFlys(pFirstRevivedEmptyPage); + } + +#if OSL_DEBUG_LEVEL > 0 + pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && pPage->GetNext() && + !static_cast<SwPageFrame*>(pPage->GetNext())->IsFootnotePage() ) + { + SAL_INFO( "sw.pageframe", "AssertFlyPages p: " << pPage << " d: " << pPage->GetPageDesc() + << " f: " << pPage->GetFormat() << " virt: " << pPage->GetVirtPageNum() + << " phys: " << pPage->GetPhyPageNum() << " empty: " << pPage->IsEmptyPage() ); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + SAL_INFO( "sw.pageframe", "AssertFlyPages p: " << pPage << " d: " << pPage->GetPageDesc() + << " f: " << pPage->GetFormat() << " virt: " << pPage->GetVirtPageNum() + << " phys: " << pPage->GetPhyPageNum() << " empty: " << pPage->IsEmptyPage() ); +#endif +} + +/// Ensure that after the given page all page-bound objects are located on the correct page +void SwRootFrame::AssertPageFlys( SwPageFrame *pPage ) +{ + SAL_INFO( "sw.pageframe", "(AssertPageFlys in" ); + while ( pPage ) + { + if (pPage->GetSortedObjs()) + { + size_t i = 0; + while ( pPage->GetSortedObjs() && i< pPage->GetSortedObjs()->size() ) + { + // #i28701# + SwFrameFormat& rFormat = (*pPage->GetSortedObjs())[i]->GetFrameFormat(); + const SwFormatAnchor &rAnch = rFormat.GetAnchor(); + const sal_uInt16 nPg = rAnch.GetPageNum(); + if ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) && + nPg != pPage->GetPhyPageNum() ) + { + SAL_INFO( "sw.pageframe", nPg << " " << pPage->GetPhyPageNum() ); + // If on the wrong page, check if previous page is empty + if( nPg && !(pPage->GetPhyPageNum()-1 == nPg && + static_cast<SwPageFrame*>(pPage->GetPrev())->IsEmptyPage()) ) + { + // It can move by itself. Just send a modify to its anchor attribute. +#if OSL_DEBUG_LEVEL > 1 + const size_t nCnt = pPage->GetSortedObjs()->size(); + rFormat.NotifyClients( nullptr, &rAnch ); + OSL_ENSURE( !pPage->GetSortedObjs() || + nCnt != pPage->GetSortedObjs()->size(), + "Object couldn't be reattached!" ); +#else + rFormat.NotifyClients( nullptr, &rAnch ); +#endif + // Do not increment index, in this case + continue; + } + } + ++i; + } + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + SAL_INFO( "sw.pageframe", "AssertPageFlys out)" ); +} + +Size SwRootFrame::ChgSize( const Size& aNewSize ) +{ + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.SSize(aNewSize); + } + + InvalidatePrt_(); + mbFixSize = false; + return getFrameArea().SSize(); +} + +void SwRootFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + if ( !isFrameAreaPositionValid() ) + { + setFrameAreaPositionValid(true); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(DOCUMENTBORDER); + aFrm.Pos().setY(DOCUMENTBORDER); + } + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Pos().setX(0); + aPrt.Pos().setY(0); + aPrt.SSize( getFrameArea().SSize() ); + } + + if ( !isFrameAreaSizeValid() ) + { + // SSize is set by the pages (Cut/Paste). + setFrameAreaSizeValid(true); + } +} + +void SwRootFrame::ImplInvalidateBrowseWidth() +{ + mbBrowseWidthValid = false; + SwFrame *pPg = Lower(); + while ( pPg ) + { + pPg->InvalidateSize(); + pPg = pPg->GetNext(); + } +} + +void SwRootFrame::ImplCalcBrowseWidth() +{ + OSL_ENSURE( GetCurrShell() && GetCurrShell()->GetViewOptions()->getBrowseMode(), + "CalcBrowseWidth and not in BrowseView" ); + + // The (minimal) with is determined from borders, tables and paint objects. + // It is calculated based on the attributes. Thus, it is not relevant how wide they are + // currently but only how wide they want to be. + // Frames and paint objects inside other objects (frames, tables) do not count. + // Borders and columns are not taken into account. + + SwFrame *pFrame = ContainsContent(); + while ( pFrame && !pFrame->IsInDocBody() ) + pFrame = static_cast<SwContentFrame*>(pFrame)->GetNextContentFrame(); + if ( !pFrame ) + return; + + mbBrowseWidthValid = true; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + mnBrowseWidth = (!comphelper::LibreOfficeKit::isActive() && pSh)? MINLAY + 2 * pSh->GetOut()-> PixelToLogic( pSh->GetBrowseBorder() ).Width(): MIN_BROWSE_WIDTH; + + do + { + if ( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + + if ( pFrame->IsTabFrame() && + !static_cast<SwLayoutFrame*>(pFrame)->GetFormat()->GetFrameSize().GetWidthPercent() ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + const SwFormatHoriOrient &rHori = rAttrs.GetAttrSet().GetHoriOrient(); + long nWidth = rAttrs.GetSize().Width(); + if ( nWidth < int(USHRT_MAX)-2000 && //-2k, because USHRT_MAX gets missing while trying to resize! (and cast to int to avoid -Wsign-compare due to broken USHRT_MAX on Android) + text::HoriOrientation::FULL != rHori.GetHoriOrient() ) + { + const SwHTMLTableLayout *pLayoutInfo = + static_cast<const SwTabFrame *>(pFrame)->GetTable() + ->GetHTMLTableLayout(); + if ( pLayoutInfo ) + nWidth = std::min( nWidth, pLayoutInfo->GetBrowseWidthMin() ); + + switch ( rHori.GetHoriOrient() ) + { + case text::HoriOrientation::NONE: + // OD 23.01.2003 #106895# - add 1st param to <SwBorderAttrs::CalcRight(..)> + nWidth += rAttrs.CalcLeft( pFrame ) + rAttrs.CalcRight( pFrame ); + break; + case text::HoriOrientation::LEFT_AND_WIDTH: + nWidth += rAttrs.CalcLeft( pFrame ); + break; + default: + break; + } + mnBrowseWidth = std::max( mnBrowseWidth, nWidth ); + } + } + else if ( pFrame->GetDrawObjs() ) + { + for ( size_t i = 0; i < pFrame->GetDrawObjs()->size(); ++i ) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + const SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + const bool bFly = dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr; + if ((bFly && (FAR_AWAY == pAnchoredObj->GetObjRect().Width())) + || rFormat.GetFrameSize().GetWidthPercent()) + { + continue; + } + + long nWidth = 0; + switch ( rFormat.GetAnchor().GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + nWidth = bFly ? rFormat.GetFrameSize().GetWidth() : + pAnchoredObj->GetObjRect().Width(); + break; + case RndStdIds::FLY_AT_PARA: + { + // #i33170# + // Reactivated old code because + // nWidth = pAnchoredObj->GetObjRect().Right() + // gives wrong results for objects that are still + // at position FAR_AWAY. + if ( bFly ) + { + nWidth = rFormat.GetFrameSize().GetWidth(); + const SwFormatHoriOrient &rHori = rFormat.GetHoriOrient(); + switch ( rHori.GetHoriOrient() ) + { + case text::HoriOrientation::NONE: + nWidth += rHori.GetPos(); + break; + case text::HoriOrientation::INSIDE: + case text::HoriOrientation::LEFT: + if ( text::RelOrientation::PRINT_AREA == rHori.GetRelationOrient() ) + nWidth += pFrame->getFramePrintArea().Left(); + break; + default: + break; + } + } + else + // Paint objects to not have attributes and + // are defined by their current size + nWidth = pAnchoredObj->GetObjRect().Right() - + pAnchoredObj->GetDrawObj()->GetAnchorPos().X(); + } + break; + default: /* do nothing */; + } + mnBrowseWidth = std::max( mnBrowseWidth, nWidth ); + } + } + pFrame = pFrame->FindNextCnt(); + } while ( pFrame ); +} + +void SwRootFrame::StartAllAction() +{ + if ( GetCurrShell() ) + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + if ( dynamic_cast<const SwCursorShell*>( &rSh) != nullptr ) + static_cast<SwCursorShell*>(&rSh)->StartAction(); + else + rSh.StartAction(); + } +} + +void SwRootFrame::EndAllAction( bool bVirDev ) +{ + if ( GetCurrShell() ) + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + const bool bOldEndActionByVirDev = rSh.IsEndActionByVirDev(); + rSh.SetEndActionByVirDev( bVirDev ); + if ( dynamic_cast<const SwCursorShell*>( &rSh) != nullptr ) + { + static_cast<SwCursorShell*>(&rSh)->EndAction(); + static_cast<SwCursorShell*>(&rSh)->CallChgLnk(); + if ( dynamic_cast<const SwFEShell*>( &rSh) != nullptr ) + static_cast<SwFEShell*>(&rSh)->SetChainMarker(); + } + else + rSh.EndAction(); + rSh.SetEndActionByVirDev( bOldEndActionByVirDev ); + } +} + +void SwRootFrame::UnoRemoveAllActions() +{ + if ( GetCurrShell() ) + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + // #i84729# + // No end action, if <SwViewShell> instance is currently in its end action. + // Recursives calls to <::EndAction()> are not allowed. + if ( !rSh.IsInEndAction() ) + { + OSL_ENSURE(!rSh.GetRestoreActions(), "Restore action count is already set!"); + bool bCursor = dynamic_cast<const SwCursorShell*>( &rSh) != nullptr; + bool bFE = dynamic_cast<const SwFEShell*>( &rSh) != nullptr; + sal_uInt16 nRestore = 0; + while( rSh.ActionCount() ) + { + if( bCursor ) + { + static_cast<SwCursorShell*>(&rSh)->EndAction(); + static_cast<SwCursorShell*>(&rSh)->CallChgLnk(); + if ( bFE ) + static_cast<SwFEShell*>(&rSh)->SetChainMarker(); + } + else + rSh.EndAction(); + nRestore++; + } + rSh.SetRestoreActions(nRestore); + } + rSh.LockView(true); + } +} + +void SwRootFrame::UnoRestoreAllActions() +{ + if ( GetCurrShell() ) + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + sal_uInt16 nActions = rSh.GetRestoreActions(); + while( nActions-- ) + { + if ( dynamic_cast<const SwCursorShell*>( &rSh) != nullptr ) + static_cast<SwCursorShell*>(&rSh)->StartAction(); + else + rSh.StartAction(); + } + rSh.SetRestoreActions(0); + rSh.LockView(false); + } +} + +// Helper functions for SwRootFrame::CheckViewLayout +static void lcl_MoveAllLowers( SwFrame* pFrame, const Point& rOffset ); + +static void lcl_MoveAllLowerObjs( SwFrame* pFrame, const Point& rOffset ) +{ + const bool bPage = pFrame->IsPageFrame(); + const SwSortedObjs* pSortedObj = bPage + ? static_cast<SwPageFrame*>(pFrame)->GetSortedObjs() + : pFrame->GetDrawObjs(); + if (pSortedObj == nullptr) + return; + + // note: pSortedObj elements may be removed and inserted from + // MoveObjectIfActive(), invalidating iterators + // DO NOT CONVERT THIS TO A C++11 FOR LOOP, IT DID NOT WORK THE LAST 2 TIMES + for (size_t i = 0; i < pSortedObj->size(); ++i) + { + SwAnchoredObject *const pAnchoredObj = (*pSortedObj)[i]; + const SwFrameFormat& rObjFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatAnchor& rAnchor = rObjFormat.GetAnchor(); + + // all except from the as character anchored objects are moved + // when processing the page frame: + if ( !bPage && (rAnchor.GetAnchorId() != RndStdIds::FLY_AS_CHAR) ) + continue; + + SwObjPositioningInProgress aPosInProgress( *pAnchoredObj ); + + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame* pFlyFrame( static_cast<SwFlyFrame*>(pAnchoredObj) ); + lcl_MoveAllLowers( pFlyFrame, rOffset ); + pFlyFrame->NotifyDrawObj(); + // --> let the active embedded object be moved + SwFrame* pLower = pFlyFrame->Lower(); + if ( pLower && pLower->IsNoTextFrame() ) + { + SwRootFrame* pRoot = pLower->getRootFrame(); + SwViewShell *pSh = pRoot ? pRoot->GetCurrShell() : nullptr; + if ( pSh ) + { + SwNoTextFrame *const pContentFrame = static_cast<SwNoTextFrame*>(pLower); + SwOLENode* pNode = pContentFrame->GetNode()->GetOLENode(); + if ( pNode ) + { + svt::EmbeddedObjectRef& xObj = pNode->GetOLEObj().GetObject(); + if ( xObj.is() ) + { + for(SwViewShell& rSh : pSh->GetRingContainer()) + { + SwFEShell* pFEShell = dynamic_cast< SwFEShell* >( &rSh ); + if ( pFEShell ) + pFEShell->MoveObjectIfActive( xObj, rOffset ); + } + } + } + } + } + } + else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr ) + { + SwAnchoredDrawObject* pAnchoredDrawObj( static_cast<SwAnchoredDrawObject*>(pAnchoredObj) ); + + // don't touch objects that are not yet positioned: + if ( pAnchoredDrawObj->NotYetPositioned() ) + continue; + + const Point& aCurrAnchorPos = pAnchoredDrawObj->GetDrawObj()->GetAnchorPos(); + const Point aNewAnchorPos( aCurrAnchorPos + rOffset ); + pAnchoredDrawObj->DrawObj()->SetAnchorPos( aNewAnchorPos ); + pAnchoredDrawObj->SetLastObjRect( pAnchoredDrawObj->GetObjRect().SVRect() ); + + // clear contour cache + if ( pAnchoredDrawObj->GetFrameFormat().GetSurround().IsContour() ) + ClrContourCache( pAnchoredDrawObj->GetDrawObj() ); + } + // #i92511# + // cache for object rectangle inclusive spaces has to be invalidated. + pAnchoredObj->InvalidateObjRectWithSpaces(); + } +} + +static void lcl_MoveAllLowers( SwFrame* pFrame, const Point& rOffset ) +{ + const SwRect aFrame( pFrame->getFrameArea() ); + + // first move the current frame + // RotateFlyFrame3: moved to transform_translate instead of + // direct modification to allow the SwFrame evtl. needed own reactions + pFrame->transform_translate(rOffset); + + // Don't forget accessibility: + if( pFrame->IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = pFrame->getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( pFrame, aFrame ); + } + } + + // the move any objects + lcl_MoveAllLowerObjs( pFrame, rOffset ); + + // finally, for layout frames we have to call this function recursively: + if ( dynamic_cast< const SwLayoutFrame *>( pFrame ) != nullptr ) + { + SwFrame* pLowerFrame = pFrame->GetLower(); + while ( pLowerFrame ) + { + lcl_MoveAllLowers( pLowerFrame, rOffset ); + pLowerFrame = pLowerFrame->GetNext(); + } + } +} + +// Calculate how the pages have to be positioned +void SwRootFrame::CheckViewLayout( const SwViewOption* pViewOpt, const SwRect* pVisArea ) +{ + SwViewShell* pSh = GetCurrShell(); + vcl::RenderContext* pRenderContext = pSh ? pSh->GetOut() : nullptr; + // #i91432# + // No calculation of page positions, if only an empty page is present. + // This situation occurs when <SwRootFrame> instance is in construction + // and the document contains only left pages. + if ( Lower()->GetNext() == nullptr && + static_cast<SwPageFrame*>(Lower())->IsEmptyPage() ) + { + return; + } + + if ( !pVisArea ) + { + // no early return for bNewPage + if ( mnViewWidth < 0 ) + mnViewWidth = 0; + } + else + { + assert(pViewOpt && "CheckViewLayout required ViewOptions"); + + const sal_uInt16 nColumns = pViewOpt->GetViewLayoutColumns(); + const bool bBookMode = pViewOpt->IsViewLayoutBookMode(); + + if ( nColumns == mnColumns && bBookMode == mbBookMode && pVisArea->Width() == mnViewWidth && !mbSidebarChanged ) + return; + + mnColumns = nColumns; + mbBookMode = bBookMode; + mnViewWidth = pVisArea->Width(); + mbSidebarChanged = false; + } + + if( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE ) ) + { + mnColumns = 1; + mbBookMode = false; + } + + Calc(pRenderContext); + + const bool bOldCallbackActionEnabled = IsCallbackActionEnabled(); + SetCallbackActionEnabled( false ); + + maPageRects.clear(); + + const long nBorder = getFrameArea().Pos().getX(); + const long nVisWidth = mnViewWidth - 2 * nBorder; + const long nGapBetweenPages = pViewOpt ? pViewOpt->GetGapBetweenPages() + : (pSh ? pSh->GetViewOptions()->GetGapBetweenPages() + : SwViewOption::defGapBetweenPages); + + // check how many pages fit into the first page layout row: + SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(Lower()); + + // will contain the number of pages per row. 0 means that + // the page does not fit. + long nWidthRemain = nVisWidth; + + // after one row has been processed, these variables contain + // the width of the row and the maximum of the page heights + long nCurrentRowHeight = 0; + long nCurrentRowWidth = 0; + + // these variables are used to finally set the size of the + // root frame + long nSumRowHeight = 0; + SwTwips nMinPageLeft = TWIPS_MAX; + SwTwips nMaxPageRight = 0; + SwPageFrame* pStartOfRow = pPageFrame; + sal_uInt16 nNumberOfPagesInRow = mbBookMode ? 1 : 0; // in book view, start with right page + bool bFirstRow = true; + + bool bPageChanged = false; + const bool bRTL = !IsLeftToRightViewLayout(); + const SwTwips nSidebarWidth = SwPageFrame::GetSidebarBorderWidth( pSh ); + + while ( pPageFrame ) + { + // we consider the current page to be "start of row" if + // 1. it is the first page in the current row or + // 2. it is the second page in the row and the first page is an empty page in non-book view: + const bool bStartOfRow = pPageFrame == pStartOfRow || + ( pStartOfRow->IsEmptyPage() && pPageFrame == pStartOfRow->GetNext() && !mbBookMode ); + + const bool bEmptyPage = pPageFrame->IsEmptyPage() && !mbBookMode; + + // no half doc border space for first page in each row and + long nPageWidth = 0; + long nPageHeight = 0; + + if ( mbBookMode ) + { + const SwFrame& rFormatPage = pPageFrame->GetFormatPage(); + + nPageWidth = rFormatPage.getFrameArea().Width() + nSidebarWidth + ((bStartOfRow || 1 == (pPageFrame->GetPhyPageNum()%2)) ? 0 : nGapBetweenPages); + nPageHeight = rFormatPage.getFrameArea().Height() + nGapBetweenPages; + } + else + { + if ( !pPageFrame->IsEmptyPage() ) + { + nPageWidth = pPageFrame->getFrameArea().Width() + nSidebarWidth + (bStartOfRow ? 0 : nGapBetweenPages); + nPageHeight = pPageFrame->getFrameArea().Height() + nGapBetweenPages; + } + } + + if ( !bEmptyPage ) + ++nNumberOfPagesInRow; + + // finish current row if + // 1. in dynamic mode the current page does not fit anymore or + // 2. the current page exceeds the maximum number of columns + bool bRowFinished = (0 == mnColumns && nWidthRemain < nPageWidth ) || + (0 != mnColumns && mnColumns < nNumberOfPagesInRow); + + // make sure that at least one page goes to the current row: + if ( !bRowFinished || bStartOfRow ) + { + // current page is allowed to be in current row + nWidthRemain = nWidthRemain - nPageWidth; + + nCurrentRowWidth = nCurrentRowWidth + nPageWidth; + nCurrentRowHeight = std::max( nCurrentRowHeight, nPageHeight ); + + pPageFrame = static_cast<SwPageFrame*>(pPageFrame->GetNext()); + + if ( !pPageFrame ) + bRowFinished = true; + } + + if ( bRowFinished ) + { + // pPageFrame now points to the first page in the new row or null + // pStartOfRow points to the first page in the current row + + // special centering for last row. pretend to fill the last row with virtual copies of the last page before centering: + if ( !pPageFrame && nWidthRemain > 0 ) + { + // find last page in current row: + const SwPageFrame* pLastPageInCurrentRow = pStartOfRow; + while( pLastPageInCurrentRow->GetNext() ) + pLastPageInCurrentRow = static_cast<const SwPageFrame*>(pLastPageInCurrentRow->GetNext()); + + if ( pLastPageInCurrentRow->IsEmptyPage() ) + pLastPageInCurrentRow = static_cast<const SwPageFrame*>(pLastPageInCurrentRow->GetPrev()); + + // check how many times the last page would still fit into the remaining space: + sal_uInt16 nNumberOfVirtualPages = 0; + const sal_uInt16 nMaxNumberOfVirtualPages = mnColumns > 0 ? mnColumns - nNumberOfPagesInRow : USHRT_MAX; + SwTwips nRemain = nWidthRemain; + SwTwips nVirtualPagesWidth = 0; + SwTwips nLastPageWidth = pLastPageInCurrentRow->getFrameArea().Width() + nSidebarWidth; + + while ( ( mnColumns > 0 || nRemain > 0 ) && nNumberOfVirtualPages < nMaxNumberOfVirtualPages ) + { + SwTwips nLastPageWidthWithGap = nLastPageWidth; + if ( !mbBookMode || ( 0 == (nNumberOfVirtualPages + nNumberOfPagesInRow) %2) ) + nLastPageWidthWithGap += nGapBetweenPages; + + if ( mnColumns > 0 || nLastPageWidthWithGap < nRemain ) + { + ++nNumberOfVirtualPages; + nVirtualPagesWidth += nLastPageWidthWithGap; + } + nRemain = nRemain - nLastPageWidthWithGap; + } + + nCurrentRowWidth = nCurrentRowWidth + nVirtualPagesWidth; + } + + // first page in book mode is always special: + if ( bFirstRow && mbBookMode ) + { + // #i88036# + nCurrentRowWidth += + pStartOfRow->GetFormatPage().getFrameArea().Width() + nSidebarWidth; + } + + // center page if possible + long nSizeDiff = 0; + if (nVisWidth > nCurrentRowWidth && !comphelper::LibreOfficeKit::isActive()) + nSizeDiff = ( nVisWidth - nCurrentRowWidth ) / 2; + + // adjust positions of pages in current row + long nX = nSizeDiff; + + const long nRowStart = nBorder + nSizeDiff; + const long nRowEnd = nRowStart + nCurrentRowWidth; + + if ( bFirstRow && mbBookMode ) + { + // #i88036# + nX += pStartOfRow->GetFormatPage().getFrameArea().Width() + nSidebarWidth; + } + + SwPageFrame* pEndOfRow = pPageFrame; + SwPageFrame* pPageToAdjust = pStartOfRow; + + do + { + const SwPageFrame* pFormatPage = pPageToAdjust; + if ( mbBookMode ) + pFormatPage = &pPageToAdjust->GetFormatPage(); + + const SwTwips nCurrentPageWidth = pFormatPage->getFrameArea().Width() + (pFormatPage->IsEmptyPage() ? 0 : nSidebarWidth); + const Point aOldPagePos = pPageToAdjust->getFrameArea().Pos(); + const bool bLeftSidebar = pPageToAdjust->SidebarPosition() == sw::sidebarwindows::SidebarPosition::LEFT; + const SwTwips nLeftPageAddOffset = bLeftSidebar ? + nSidebarWidth : + 0; + + Point aNewPagePos( nBorder + nX, nBorder + nSumRowHeight ); + Point aNewPagePosWithLeftOffset( nBorder + nX + nLeftPageAddOffset, nBorder + nSumRowHeight ); + + // RTL view layout: Calculate mirrored page position + if ( bRTL ) + { + const long nXOffsetInRow = aNewPagePos.getX() - nRowStart; + aNewPagePos.setX(nRowEnd - nXOffsetInRow - nCurrentPageWidth); + aNewPagePosWithLeftOffset = aNewPagePos; + aNewPagePosWithLeftOffset.setX(aNewPagePosWithLeftOffset.getX() + nLeftPageAddOffset); + } + + if ( aNewPagePosWithLeftOffset != aOldPagePos ) + { + lcl_MoveAllLowers( pPageToAdjust, aNewPagePosWithLeftOffset - aOldPagePos ); + pPageToAdjust->SetCompletePaint(); + bPageChanged = true; + } + + // calculate area covered by the current page and store to + // maPageRects. This is used e.g., for cursor setting + const bool bFirstColumn = pPageToAdjust == pStartOfRow; + const bool bLastColumn = pPageToAdjust->GetNext() == pEndOfRow; + const bool bLastRow = !pEndOfRow; + + nMinPageLeft = std::min( nMinPageLeft, aNewPagePos.getX() ); + nMaxPageRight = std::max( nMaxPageRight, aNewPagePos.getX() + nCurrentPageWidth); + + // border of nGapBetweenPages around the current page: + SwRect aPageRectWithBorders( aNewPagePos.getX() - nGapBetweenPages, + aNewPagePos.getY(), + pPageToAdjust->getFrameArea().SSize().Width() + nGapBetweenPages + nSidebarWidth, + nCurrentRowHeight ); + + static const long nOuterClickDiff = 1000000; + + // adjust borders for these special cases: + if ( (bFirstColumn && !bRTL) || (bLastColumn && bRTL) ) + aPageRectWithBorders.SubLeft( nOuterClickDiff ); + if ( (bLastColumn && !bRTL) || (bFirstColumn && bRTL) ) + aPageRectWithBorders.AddRight( nOuterClickDiff ); + if ( bFirstRow ) + aPageRectWithBorders.SubTop( nOuterClickDiff ); + if ( bLastRow ) + aPageRectWithBorders.AddBottom( nOuterClickDiff ); + + maPageRects.push_back( aPageRectWithBorders ); + + nX = nX + nCurrentPageWidth; + pPageToAdjust = static_cast<SwPageFrame*>(pPageToAdjust->GetNext()); + + // distance to next page + if ( pPageToAdjust && pPageToAdjust != pEndOfRow ) + { + // in book view, we add the x gap before left (even) pages: + if ( mbBookMode ) + { + if ( 0 == (pPageToAdjust->GetPhyPageNum()%2) ) + nX = nX + nGapBetweenPages; + } + else + { + // in non-book view, don't add x gap before + // 1. the last empty page in a row + // 2. after an empty page + const bool bDontAddGap = ( pPageToAdjust->IsEmptyPage() && pPageToAdjust->GetNext() == pEndOfRow ) || + ( static_cast<SwPageFrame*>(pPageToAdjust->GetPrev())->IsEmptyPage() ); + + if ( !bDontAddGap ) + nX = nX + nGapBetweenPages; + } + } + } + while (pPageToAdjust && pPageToAdjust != pEndOfRow); + + // adjust values for root frame size + nSumRowHeight = nSumRowHeight + nCurrentRowHeight; + + // start new row: + nCurrentRowHeight = 0; + nCurrentRowWidth = 0; + pStartOfRow = pEndOfRow; + nWidthRemain = nVisWidth; + nNumberOfPagesInRow = 0; + bFirstRow = false; + } // end row finished + } // end while + + // set size of root frame: + const Size aOldSize( getFrameArea().SSize() ); + const Size aNewSize( nMaxPageRight - nBorder, nSumRowHeight - nGapBetweenPages ); + + if ( bPageChanged || aNewSize != aOldSize ) + { + ChgSize( aNewSize ); + ::AdjustSizeChgNotify( this ); + Calc(pRenderContext); + + if ( pSh && pSh->GetDoc()->GetDocShell() ) + { + pSh->SetFirstVisPageInvalid(); + if (bOldCallbackActionEnabled) + { + pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) ); + pSh->GetDoc()->GetDocShell()->Broadcast(SfxHint(SfxHintId::DocChanged)); + } + } + } + + maPagesArea.Pos( getFrameArea().Pos() ); + maPagesArea.SSize( aNewSize ); + if ( TWIPS_MAX != nMinPageLeft ) + maPagesArea.Left_( nMinPageLeft ); + + SetCallbackActionEnabled( bOldCallbackActionEnabled ); +} + +bool SwRootFrame::IsLeftToRightViewLayout() const +{ + // Layout direction determined by layout direction of the first page. + // #i88036# + // Only ask a non-empty page frame for its layout direction + assert(dynamic_cast<const SwPageFrame *>(Lower()) != nullptr); + const SwPageFrame& rPage = static_cast<const SwPageFrame&>(*Lower()).GetFormatPage(); + return !rPage.IsRightToLeft() && !rPage.IsVertical(); +} + +const SwPageFrame& SwPageFrame::GetFormatPage() const +{ + const SwPageFrame* pRet = this; + if ( IsEmptyPage() ) + { + pRet = static_cast<const SwPageFrame*>( OnRightPage() ? GetNext() : GetPrev() ); + // #i88035# + // Typically a right empty page frame has a next non-empty page frame and + // a left empty page frame has a previous non-empty page frame. + // But under certain circumstances this assumption is not true - + // e.g. during insertion of a left page at the end of the document right + // after a left page in an intermediate state a right empty page does not + // have a next page frame. + if ( pRet == nullptr ) + { + if ( OnRightPage() ) + { + pRet = static_cast<const SwPageFrame*>( GetPrev() ); + } + else + { + pRet = static_cast<const SwPageFrame*>( GetNext() ); + } + } + assert(pRet && + "<SwPageFrame::GetFormatPage()> - inconsistent layout: empty page without previous and next page frame --> crash."); + } + return *pRet; +} + +bool SwPageFrame::IsOverHeaderFooterArea( const Point& rPt, FrameControlType &rControl ) const +{ + long nUpperLimit = 0; + long nLowerLimit = 0; + const SwFrame* pFrame = Lower(); + while ( pFrame ) + { + if ( pFrame->IsBodyFrame() ) + { + nUpperLimit = pFrame->getFrameArea().Top(); + nLowerLimit = pFrame->getFrameArea().Bottom(); + } + else if ( pFrame->IsFootnoteContFrame() ) + nLowerLimit = pFrame->getFrameArea().Bottom(); + + pFrame = pFrame->GetNext(); + } + + SwRect aHeaderArea( getFrameArea().TopLeft(), + Size( getFrameArea().Width(), nUpperLimit - getFrameArea().Top() ) ); + + SwViewShell* pViewShell = getRootFrame()->GetCurrShell(); + const bool bHideWhitespaceMode = pViewShell->GetViewOptions()->IsHideWhitespaceMode(); + if ( aHeaderArea.IsInside( rPt ) ) + { + if (!bHideWhitespaceMode || static_cast<const SwFrameFormat*>(GetDep())->GetHeader().IsActive()) + { + rControl = FrameControlType::Header; + return true; + } + } + else + { + SwRect aFooterArea( Point( getFrameArea().Left(), nLowerLimit ), + Size( getFrameArea().Width(), getFrameArea().Bottom() - nLowerLimit ) ); + + if ( aFooterArea.IsInside( rPt ) && + (!bHideWhitespaceMode || static_cast<const SwFrameFormat*>(GetDep())->GetFooter().IsActive()) ) + { + rControl = FrameControlType::Footer; + return true; + } + } + + return false; +} + +bool SwPageFrame::CheckPageHeightValidForHideWhitespace(SwTwips nDiff) +{ + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + if (pShell && pShell->GetViewOptions()->IsWhitespaceHidden()) + { + // When whitespace is hidden, the page frame has two heights: the + // nominal (defined by the frame format), and the actual (which is + // at most the nominal height, but can be smaller in case there is + // no content for the whole page). + // The layout size is the actual one, but we want to move the + // content frame to a new page only in case it doesn't fit the + // nominal size. + if (nDiff < 0) + { + // Content frame doesn't fit the actual size, check if it fits the nominal one. + const SwFrameFormat* pPageFormat = static_cast<const SwFrameFormat*>(GetDep()); + const Size& rPageSize = pPageFormat->GetFrameSize().GetSize(); + long nWhitespace = rPageSize.getHeight() - getFrameArea().Height(); + if (nWhitespace > -nDiff) + { + // It does: don't move it and invalidate our page frame so + // that it gets a larger height. + return false; + } + } + } + + return true; +} + +const SwFooterFrame* SwPageFrame::GetFooterFrame() const +{ + const SwFrame* pLowerFrame = Lower(); + while (pLowerFrame) + { + if (pLowerFrame->IsFooterFrame()) + return dynamic_cast<const SwFooterFrame*>(pLowerFrame); + pLowerFrame = pLowerFrame->GetNext(); + } + return nullptr; +} + +SwTextGridItem const* GetGridItem(SwPageFrame const*const pPage) +{ + if (pPage && pPage->HasGrid()) + { + SwTextGridItem const& rGridItem( + pPage->GetPageDesc()->GetMaster().GetTextGrid()); + if (GRID_NONE != rGridItem.GetGridType()) + { + return &rGridItem; + } + } + return nullptr; +} + +sal_uInt16 GetGridWidth(SwTextGridItem const& rG, SwDoc const& rDoc) +{ + return (rDoc.IsSquaredPageMode()) ? rG.GetBaseHeight() : rG.GetBaseWidth(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/pagedesc.cxx b/sw/source/core/layout/pagedesc.cxx new file mode 100644 index 000000000..c9c242435 --- /dev/null +++ b/sw/source/core/layout/pagedesc.cxx @@ -0,0 +1,633 @@ +/* -*- 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 <libxml/xmlwriter.h> + +#include <editeng/pbinitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/frmdiritem.hxx> +#include <sal/log.hxx> +#include <fmtclds.hxx> +#include <fmtfsize.hxx> +#include <pagefrm.hxx> +#include <pagedesc.hxx> +#include <swtable.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <doc.hxx> +#include <node.hxx> +#include <strings.hrc> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <poolfmt.hxx> +#include <calbck.hxx> +#include <hints.hxx> + +SwPageDesc::SwPageDesc(const OUString& rName, SwFrameFormat *pFormat, SwDoc *const pDoc) + : SwModify() + , m_StyleName( rName ) + , m_Master( pDoc->GetAttrPool(), rName, pFormat ) + , m_Left( pDoc->GetAttrPool(), rName, pFormat ) + , m_FirstMaster( pDoc->GetAttrPool(), rName, pFormat ) + , m_FirstLeft( pDoc->GetAttrPool(), rName, pFormat ) + , m_aDepends(*this) + , m_pTextFormatColl(nullptr) + , m_pFollow( this ) + , m_nRegHeight( 0 ) + , m_nRegAscent( 0 ) + , m_nVerticalAdjustment( drawing::TextVerticalAdjust_TOP ) + , m_eUse( UseOnPage::All | UseOnPage::HeaderShare | UseOnPage::FooterShare | UseOnPage::FirstShare ) + , m_IsLandscape( false ) + , m_IsHidden( false ) + , m_pdList( nullptr ) +{ +} + +SwPageDesc::SwPageDesc( const SwPageDesc &rCpy ) + : SwModify() + , BroadcasterMixin() + , m_StyleName( rCpy.GetName() ) + , m_NumType( rCpy.GetNumType() ) + , m_Master( rCpy.GetMaster() ) + , m_Left( rCpy.GetLeft() ) + , m_FirstMaster( rCpy.GetFirstMaster() ) + , m_FirstLeft( rCpy.GetFirstLeft() ) + , m_aDepends(*this) + , m_pTextFormatColl(nullptr) + , m_pFollow( rCpy.m_pFollow ) + , m_nRegHeight( rCpy.GetRegHeight() ) + , m_nRegAscent( rCpy.GetRegAscent() ) + , m_nVerticalAdjustment( rCpy.GetVerticalAdjustment() ) + , m_eUse( rCpy.ReadUseOn() ) + , m_IsLandscape( rCpy.GetLandscape() ) + , m_IsHidden( rCpy.IsHidden() ) + , m_FootnoteInfo( rCpy.GetFootnoteInfo() ) + , m_pdList( nullptr ) +{ + if (rCpy.m_pTextFormatColl && rCpy.m_aDepends.IsListeningTo(rCpy.m_pTextFormatColl)) + { + m_pTextFormatColl = rCpy.m_pTextFormatColl; + m_aDepends.StartListening(const_cast<SwTextFormatColl*>(m_pTextFormatColl)); + } +} + +SwPageDesc & SwPageDesc::operator = (const SwPageDesc & rSrc) +{ + if(this == &rSrc) + return *this; + + m_StyleName = rSrc.m_StyleName; + m_NumType = rSrc.m_NumType; + m_Master = rSrc.m_Master; + m_Left = rSrc.m_Left; + m_FirstMaster = rSrc.m_FirstMaster; + m_FirstLeft = rSrc.m_FirstLeft; + m_aDepends.EndListeningAll(); + if (rSrc.m_pTextFormatColl && rSrc.m_aDepends.IsListeningTo(rSrc.m_pTextFormatColl)) + { + m_pTextFormatColl = rSrc.m_pTextFormatColl; + m_aDepends.StartListening(const_cast<SwTextFormatColl*>(m_pTextFormatColl)); + } + else + m_pTextFormatColl = nullptr; + + if (rSrc.m_pFollow == &rSrc) + m_pFollow = this; + else + m_pFollow = rSrc.m_pFollow; + + m_nRegHeight = rSrc.m_nRegHeight; + m_nRegAscent = rSrc.m_nRegAscent; + m_nVerticalAdjustment = rSrc.m_nVerticalAdjustment; + m_eUse = rSrc.m_eUse; + m_IsLandscape = rSrc.m_IsLandscape; + return *this; +} + +SwPageDesc::~SwPageDesc() +{ +} + +bool SwPageDesc::SetName( const OUString& rNewName ) +{ + bool renamed = true; + if (m_pdList) { + SwPageDescs::iterator it = m_pdList->find_( m_StyleName ); + if( m_pdList->end() == it ) { + SAL_WARN( "sw", "SwPageDesc not found in expected m_pdList" ); + return false; + } + renamed = m_pdList->m_PosIndex.modify( it, + change_name( rNewName ), change_name( m_StyleName ) ); + } + else + m_StyleName = rNewName; + return renamed; +} + +/// Only the margin is mirrored. +/// Attributes like borders and so on are copied 1:1. +void SwPageDesc::Mirror() +{ + //Only the margins are mirrored, all other values are just copied. + SvxLRSpaceItem aLR( RES_LR_SPACE ); + const SvxLRSpaceItem &rLR = m_Master.GetLRSpace(); + aLR.SetLeft( rLR.GetRight() ); + aLR.SetRight( rLR.GetLeft() ); + + SfxItemSet aSet( *m_Master.GetAttrSet().GetPool(), + m_Master.GetAttrSet().GetRanges() ); + aSet.Put( aLR ); + aSet.Put( m_Master.GetFrameSize() ); + aSet.Put( m_Master.GetPaperBin() ); + aSet.Put( m_Master.GetULSpace() ); + aSet.Put( m_Master.GetBox() ); + aSet.Put( *m_Master.makeBackgroundBrushItem() ); + aSet.Put( m_Master.GetShadow() ); + aSet.Put( m_Master.GetCol() ); + aSet.Put( m_Master.GetFrameDir() ); + m_Left.SetFormatAttr( aSet ); +} + +void SwPageDesc::ResetAllAttr() +{ + SwFrameFormat& rFormat = GetMaster(); + + // #i73790# - method renamed + rFormat.ResetAllFormatAttr(); + rFormat.SetFormatAttr( SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR) ); +} + +// gets information from Modify +bool SwPageDesc::GetInfo( SfxPoolItem & rInfo ) const +{ + if (!m_Master.GetInfo(rInfo)) + return false; // found + if (!m_Left.GetInfo(rInfo)) + return false ; + if ( !m_FirstMaster.GetInfo( rInfo ) ) + return false; + return m_FirstLeft.GetInfo( rInfo ); +} + +/// set the style for the grid alignment +void SwPageDesc::SetRegisterFormatColl(const SwTextFormatColl* pFormat) +{ + if(pFormat != m_pTextFormatColl) + { + m_aDepends.EndListeningAll(); + m_pTextFormatColl = pFormat; + m_aDepends.StartListening(const_cast<SwTextFormatColl*>(m_pTextFormatColl)); + RegisterChange(); + } +} + +/// retrieve the style for the grid alignment +const SwTextFormatColl* SwPageDesc::GetRegisterFormatColl() const +{ + if (!m_aDepends.IsListeningTo(m_pTextFormatColl)) + m_pTextFormatColl = nullptr; + return m_pTextFormatColl; +} + +/// notify all affected page frames +void SwPageDesc::RegisterChange() +{ + // #117072# - During destruction of the document <SwDoc> + // the page description is modified. Thus, do nothing, if the document + // is in destruction respectively if no viewshell exists. + SwDoc* pDoc = GetMaster().GetDoc(); + if ( !pDoc || pDoc->IsInDtor() ) + { + return; + } + SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( !pSh ) + { + return; + } + + m_nRegHeight = 0; + { + SwIterator<SwFrame,SwFormat> aIter( GetMaster() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } + { + SwIterator<SwFrame,SwFormat> aIter( GetLeft() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } + { + SwIterator<SwFrame,SwFormat> aIter( GetFirstMaster() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } + { + SwIterator<SwFrame,SwFormat> aIter( GetFirstLeft() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } +} + +/// special handling if the style of the grid alignment changes +void SwPageDesc::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if(auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + const sal_uInt16 nWhich = pLegacyHint->m_pOld + ? pLegacyHint->m_pOld->Which() + : pLegacyHint->m_pNew + ? pLegacyHint->m_pNew->Which() + : 0; + NotifyClients(pLegacyHint->m_pOld, pLegacyHint->m_pNew); + if((RES_ATTRSET_CHG == nWhich) + || (RES_FMT_CHG == nWhich) + || isCHRATR(nWhich) + || (RES_PARATR_LINESPACING == nWhich)) + RegisterChange(); + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + if(m_pTextFormatColl == &rModify) + m_pTextFormatColl = static_cast<const SwTextFormatColl*>(pModifyChangedHint->m_pNew); + else + assert(false); + } +} + +static const SwFrame* lcl_GetFrameOfNode( const SwNode& rNd ) +{ + const SwModify* pMod; + SwFrameType nFrameType = FRM_CNTNT; + + if( rNd.IsContentNode() ) + { + pMod = &static_cast<const SwContentNode&>(rNd); + } + else if( rNd.IsTableNode() ) + { + pMod = static_cast<const SwTableNode&>(rNd).GetTable().GetFrameFormat(); + nFrameType = SwFrameType::Tab; + } + else + pMod = nullptr; + + Point aNullPt; + std::pair<Point, bool> const tmp(aNullPt, false); + return pMod ? ::GetFrameOfModify(nullptr, *pMod, nFrameType, nullptr, &tmp) + : nullptr; +} + +const SwPageDesc* SwPageDesc::GetPageDescOfNode(const SwNode& rNd) +{ + const SwPageDesc* pRet = nullptr; + const SwFrame* pChkFrame = lcl_GetFrameOfNode( rNd ); + if (pChkFrame && nullptr != (pChkFrame = pChkFrame->FindPageFrame())) + pRet = static_cast<const SwPageFrame*>(pChkFrame)->GetPageDesc(); + return pRet; +} + +const SwFrameFormat* SwPageDesc::GetPageFormatOfNode( const SwNode& rNd, + bool bCheckForThisPgDc ) const +{ + // which PageDescFormat is valid for this node? + const SwFrameFormat* pRet; + const SwFrame* pChkFrame = lcl_GetFrameOfNode( rNd ); + + if( pChkFrame && nullptr != ( pChkFrame = pChkFrame->FindPageFrame() )) + { + const SwPageDesc* pPd = bCheckForThisPgDc ? this : + static_cast<const SwPageFrame*>(pChkFrame)->GetPageDesc(); + pRet = &pPd->GetMaster(); + OSL_ENSURE( static_cast<const SwPageFrame*>(pChkFrame)->GetPageDesc() == pPd, "Wrong node for detection of page format!" ); + // this page is assigned to which format? + if( !pChkFrame->KnowsFormat(*pRet) ) + { + pRet = &pPd->GetLeft(); + OSL_ENSURE( pChkFrame->KnowsFormat(*pRet), "Wrong node for detection of page format!" ); + } + } + else + pRet = &GetMaster(); + return pRet; +} + +bool SwPageDesc::IsFollowNextPageOfNode( const SwNode& rNd ) const +{ + bool bRet = false; + if( GetFollow() && this != GetFollow() ) + { + const SwFrame* pChkFrame = lcl_GetFrameOfNode( rNd ); + if( pChkFrame && nullptr != ( pChkFrame = pChkFrame->FindPageFrame() ) && + pChkFrame->IsPageFrame() && + ( !pChkFrame->GetNext() || GetFollow() == + static_cast<const SwPageFrame*>(pChkFrame->GetNext())->GetPageDesc() )) + // the page on which the follow points was found + bRet = true; + } + return bRet; +} + +SwFrameFormat *SwPageDesc::GetLeftFormat(bool const bFirst) +{ + return (UseOnPage::Left & m_eUse) + ? (bFirst ? &m_FirstLeft : &m_Left) + : nullptr; +} + +SwFrameFormat *SwPageDesc::GetRightFormat(bool const bFirst) +{ + return (UseOnPage::Right & m_eUse) + ? (bFirst ? &m_FirstMaster : &m_Master) + : nullptr; +} + +bool SwPageDesc::IsFirstShared() const +{ + return bool(m_eUse & UseOnPage::FirstShare); +} + +void SwPageDesc::ChgFirstShare( bool bNew ) +{ + if ( bNew ) + m_eUse |= UseOnPage::FirstShare; + else + m_eUse &= UseOnPage::NoFirstShare; +} + +// Page styles +static const char* STR_POOLPAGE[] = +{ + STR_POOLPAGE_STANDARD, + STR_POOLPAGE_FIRST, + STR_POOLPAGE_LEFT, + STR_POOLPAGE_RIGHT, + STR_POOLPAGE_JAKET, + STR_POOLPAGE_REGISTER, + STR_POOLPAGE_HTML, + STR_POOLPAGE_FOOTNOTE, + STR_POOLPAGE_ENDNOTE, + STR_POOLPAGE_LANDSCAPE +}; + +SwPageDesc* SwPageDesc::GetByName(SwDoc& rDoc, const OUString& rName) +{ + const size_t nDCount = rDoc.GetPageDescCnt(); + + for( size_t i = 0; i < nDCount; i++ ) + { + SwPageDesc* pDsc = &rDoc.GetPageDesc( i ); + if(pDsc->GetName() == rName) + { + return pDsc; + } + } + + for (size_t i = 0; i < SAL_N_ELEMENTS(STR_POOLPAGE); ++i) + { + if (rName == SwResId(STR_POOLPAGE[i])) + { + return rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool( static_cast< sal_uInt16 >( + i + RES_POOLPAGE_BEGIN) ); + } + } + + return nullptr; +} + +void SwPageDesc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPageDesc")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_StyleName"), "%s", + BAD_CAST(m_StyleName.toUtf8().getStr())); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pFollow"), "%p", m_pFollow); + xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("m_eUse"), "0x%s", + BAD_CAST(OString::number(static_cast<int>(m_eUse), 16).getStr())); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_Master")); + m_Master.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_Left")); + m_Left.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_FirstMaster")); + m_FirstMaster.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_FirstLeft")); + m_FirstLeft.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +SwPageFootnoteInfo::SwPageFootnoteInfo() + : m_nMaxHeight( 0 ) + , m_nLineWidth(10) + , m_eLineStyle( SvxBorderLineStyle::SOLID ) + , m_Width( 25, 100 ) + , m_nTopDist( 57 ) //1mm + , m_nBottomDist( 57 ) +{ + m_eAdjust = SvxFrameDirection::Horizontal_RL_TB == GetDefaultFrameDirection(GetAppLanguage()) ? + css::text::HorizontalAdjust_RIGHT : + css::text::HorizontalAdjust_LEFT; +} + +SwPageFootnoteInfo::SwPageFootnoteInfo( const SwPageFootnoteInfo &rCpy ) + : m_nMaxHeight(rCpy.GetHeight()) + , m_nLineWidth(rCpy.m_nLineWidth) + , m_eLineStyle(rCpy.m_eLineStyle) + , m_LineColor(rCpy.m_LineColor) + , m_Width(rCpy.GetWidth()) + , m_eAdjust(rCpy.GetAdj()) + , m_nTopDist(rCpy.GetTopDist()) + , m_nBottomDist(rCpy.GetBottomDist()) +{ +} + +SwPageFootnoteInfo &SwPageFootnoteInfo::operator=( const SwPageFootnoteInfo& rCpy ) +{ + m_nMaxHeight = rCpy.GetHeight(); + m_nLineWidth = rCpy.m_nLineWidth; + m_eLineStyle = rCpy.m_eLineStyle; + m_LineColor = rCpy.m_LineColor; + m_Width = rCpy.GetWidth(); + m_eAdjust = rCpy.GetAdj(); + m_nTopDist = rCpy.GetTopDist(); + m_nBottomDist = rCpy.GetBottomDist(); + return *this; +} + +bool SwPageFootnoteInfo::operator==( const SwPageFootnoteInfo& rCmp ) const +{ + return m_nMaxHeight == rCmp.GetHeight() + && m_nLineWidth == rCmp.m_nLineWidth + && m_eLineStyle == rCmp.m_eLineStyle + && m_LineColor == rCmp.m_LineColor + && m_Width == rCmp.GetWidth() + && m_eAdjust == rCmp.GetAdj() + && m_nTopDist == rCmp.GetTopDist() + && m_nBottomDist== rCmp.GetBottomDist(); +} + +SwPageDescExt::SwPageDescExt(const SwPageDesc & rPageDesc, SwDoc *const pDoc) + : m_PageDesc(rPageDesc) + , m_pDoc(pDoc) +{ + SetPageDesc(rPageDesc); +} + +SwPageDescExt::SwPageDescExt(const SwPageDescExt & rSrc) + : m_PageDesc(rSrc.m_PageDesc) + , m_pDoc(rSrc.m_pDoc) +{ + SetPageDesc(rSrc.m_PageDesc); +} + +SwPageDescExt::~SwPageDescExt() +{ +} + +OUString const & SwPageDescExt::GetName() const +{ + return m_PageDesc.GetName(); +} + +void SwPageDescExt::SetPageDesc(const SwPageDesc & rPageDesc) +{ + m_PageDesc = rPageDesc; + + if (m_PageDesc.GetFollow()) + m_sFollow = m_PageDesc.GetFollow()->GetName(); +} + +SwPageDescExt & SwPageDescExt::operator = (const SwPageDesc & rSrc) +{ + SetPageDesc(rSrc); + + return *this; +} + +SwPageDescExt & SwPageDescExt::operator = (const SwPageDescExt & rSrc) +{ + operator=(rSrc.m_PageDesc); + return *this; +} + +SwPageDescExt::operator SwPageDesc() const +{ + SwPageDesc aResult(m_PageDesc); + + SwPageDesc * pPageDesc = m_pDoc->FindPageDesc(m_sFollow); + + if ( nullptr != pPageDesc ) + aResult.SetFollow(pPageDesc); + + return aResult; +} + +SwPageDescs::SwPageDescs() + : m_PosIndex( m_Array.get<0>() ) + , m_NameIndex( m_Array.get<1>() ) +{ +} + +SwPageDescs::~SwPageDescs() +{ + for(const_iterator it = begin(); it != end(); ++it) + delete *it; +} + +SwPageDescs::iterator SwPageDescs::find_(const OUString &name) const +{ + ByName::iterator it = m_NameIndex.find( name ); + return m_Array.iterator_to( *it ); +} + +std::pair<SwPageDescs::const_iterator,bool> SwPageDescs::push_back( const value_type& x ) +{ + // SwPageDesc is not already in a SwPageDescs list! + assert( x->m_pdList == nullptr ); + + std::pair<iterator,bool> res = m_PosIndex.push_back( x ); + if( res.second ) + x->m_pdList = this; + return res; +} + +void SwPageDescs::erase( const value_type& x ) +{ + // SwPageDesc is not in this SwPageDescs list! + assert( x->m_pdList == this ); + + iterator const ret = find_( x->GetName() ); + if (ret != end()) + m_PosIndex.erase( ret ); + else + SAL_WARN( "sw", "SwPageDesc is not in SwPageDescs m_pdList!" ); + x->m_pdList = nullptr; +} + +void SwPageDescs::erase( const_iterator const& position ) +{ + // SwPageDesc is not in this SwPageDescs list! + assert( (*position)->m_pdList == this ); + + (*position)->m_pdList = nullptr; + m_PosIndex.erase( position ); +} + +void SwPageDescs::erase( size_type index_ ) +{ + erase( begin() + index_ ); +} + +void SwPageDescs::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPageDescs")); + + for (const auto& pPageDesc : m_PosIndex) + { + pPageDesc->dumpAsXml(pWriter); + } + + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/paintfrm.cxx b/sw/source/core/layout/paintfrm.cxx new file mode 100644 index 000000000..7e3986006 --- /dev/null +++ b/sw/source/core/layout/paintfrm.cxx @@ -0,0 +1,7495 @@ +/* -*- 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 <vcl/lazydelete.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/progress.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/prntitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <svx/framelink.hxx> +#include <drawdoc.hxx> +#include <tgrditem.hxx> +#include <calbck.hxx> +#include <fmtsrnd.hxx> +#include <fmtclds.hxx> +#include <strings.hrc> +#include <swmodule.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <section.hxx> +#include <sectfrm.hxx> +#include <viewimp.hxx> +#include <dflyobj.hxx> +#include <flyfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <dview.hxx> +#include <dcontact.hxx> +#include <txtfrm.hxx> +#include <ftnfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <notxtfrm.hxx> +#include <layact.hxx> +#include <pagedesc.hxx> +#include <ptqueue.hxx> +#include <noteurl.hxx> +#include "virtoutp.hxx" +#include <lineinfo.hxx> +#include <dbg_lay.hxx> +#include <docsh.hxx> +#include <svx/svdogrp.hxx> +#include <sortedobjs.hxx> +#include <EnhancedPDFExportHelper.hxx> +#include <bodyfrm.hxx> +#include <hffrm.hxx> +#include <colfrm.hxx> +#include <sw_primitivetypes2d.hxx> +#include <swfont.hxx> + +#include <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx> +#include <svx/sdr/contact/viewobjectcontactredirector.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> + +#include <ndole.hxx> +#include <PostItMgr.hxx> +#include <FrameControlsManager.hxx> +#include <vcl/settings.hxx> + +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> + +#include <svtools/borderhelper.hxx> + +#include <bitmaps.hlst> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/discreteshadowprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <svx/unoapi.hxx> +#include <svx/svdpagv.hxx> +#include <svx/xfillit0.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/color/bcolortools.hxx> +#include <basegfx/utils/b2dclipstate.hxx> +#include <sal/log.hxx> + +#include <memory> +#include <vector> +#include <algorithm> +#include <wrtsh.hxx> +#include <edtwin.hxx> +#include <view.hxx> +#include <paintfrm.hxx> +#include <textboxhelper.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include <vcl/BitmapTools.hxx> +#include <comphelper/lok.hxx> + +#define COL_NOTES_SIDEPANE Color(230,230,230) +#define COL_NOTES_SIDEPANE_BORDER Color(200,200,200) +#define COL_NOTES_SIDEPANE_SCROLLAREA Color(230,230,220) + +using namespace ::editeng; +using namespace ::com::sun::star; + +namespace { + +struct SwPaintProperties; + +//Class declaration; here because they are only used in this file +enum class SubColFlags { + Page = 0x01, //Helplines of the page + Tab = 0x08, //Helplines inside tables + Fly = 0x10, //Helplines inside fly frames + Sect = 0x20, //Helplines inside sections +}; + +} + +namespace o3tl { + template<> struct typed_flags<SubColFlags> : is_typed_flags<SubColFlags, 0x39> {}; +} + +namespace { + +// Classes collecting the border lines and help lines +class SwLineRect : public SwRect +{ + Color aColor; + SvxBorderLineStyle nStyle; + const SwTabFrame *pTab; + SubColFlags nSubColor; //colorize subsidiary lines + bool bPainted; //already painted? + sal_uInt8 nLock; //To distinguish the line and the hell layer. +public: + SwLineRect( const SwRect &rRect, const Color *pCol, const SvxBorderLineStyle nStyle, + const SwTabFrame *pT , const SubColFlags nSCol ); + + const Color& GetColor() const { return aColor;} + SvxBorderLineStyle GetStyle() const { return nStyle; } + const SwTabFrame *GetTab() const { return pTab; } + void SetPainted() { bPainted = true; } + void Lock( bool bLock ) { if ( bLock ) + ++nLock; + else if ( nLock ) + --nLock; + } + bool IsPainted() const { return bPainted; } + bool IsLocked() const { return nLock != 0; } + SubColFlags GetSubColor() const { return nSubColor;} + + bool MakeUnion( const SwRect &rRect, SwPaintProperties const &properties ); +}; + +} + +#ifdef IOS +static void dummy_function() +{ + pid_t pid = getpid(); + (void) pid; +} +#endif + +namespace { + +class SwLineRects +{ +public: + std::vector< SwLineRect > aLineRects; + typedef std::vector< SwLineRect >::const_iterator const_iterator; + typedef std::vector< SwLineRect >::iterator iterator; + typedef std::vector< SwLineRect >::reverse_iterator reverse_iterator; + typedef std::vector< SwLineRect >::size_type size_type; + size_t nLastCount; //avoid unnecessary cycles in PaintLines + SwLineRects() : nLastCount( 0 ) + { +#ifdef IOS + // Work around what is either a compiler bug in Xcode 5.1.1, + // or some unknown problem in this file. If I ifdef out this + // call, I get a crash in SwSubsRects::PaintSubsidiary: the + // address of the rLi reference variable is claimed to be + // 0x4000000! + dummy_function(); +#endif + } + void AddLineRect( const SwRect& rRect, const Color *pColor, const SvxBorderLineStyle nStyle, + const SwTabFrame *pTab, const SubColFlags nSCol, SwPaintProperties const &properties ); + void ConnectEdges( OutputDevice const *pOut, SwPaintProperties const &properties ); + void PaintLines ( OutputDevice *pOut, SwPaintProperties const &properties ); + void LockLines( bool bLock ); + + //Limit lines to 100 + bool isFull() const { return aLineRects.size()>100; } +}; + +class SwSubsRects : public SwLineRects +{ + void RemoveSuperfluousSubsidiaryLines( const SwLineRects &rRects, SwPaintProperties const &properties ); +public: + void PaintSubsidiary( OutputDevice *pOut, const SwLineRects *pRects, SwPaintProperties const &properties ); +}; + +class BorderLines +{ + drawinglayer::primitive2d::Primitive2DContainer m_Lines; +public: + void AddBorderLines(const drawinglayer::primitive2d::Primitive2DContainer& rContainer); + drawinglayer::primitive2d::Primitive2DContainer GetBorderLines_Clear() + { + drawinglayer::primitive2d::Primitive2DContainer lines; + lines.swap(m_Lines); + return lines; + } +}; + +} + +// Default zoom factor +const static double aEdgeScale = 0.5; + +//To optimize the expensive RetouchColor determination +Color aGlobalRetoucheColor; + +namespace sw +{ +Color* GetActiveRetoucheColor() +{ + return &aGlobalRetoucheColor; +} +} + +namespace { + +/** + * Container for static properties + */ +struct SwPaintProperties { + // Only repaint the Fly content as well as the background of the Fly content if + // a metafile is taken of the Fly. + bool bSFlyMetafile; + VclPtr<OutputDevice> pSFlyMetafileOut; + SwViewShell *pSGlobalShell; + + // Retouch for transparent Flys is done by the background of the Flys. + // The Fly itself should certainly not be spared out. See PaintSwFrameBackground and + // lcl_SubtractFlys() + SwFlyFrame *pSRetoucheFly; + SwFlyFrame *pSRetoucheFly2; + SwFlyFrame *pSFlyOnlyDraw; + + // The borders will be collected in pSLines during the Paint and later + // possibly merge them. + // The help lines will be collected and merged in gProp.pSSubsLines. These will + // be compared with pSLines before the work in order to avoid help lines + // to hide borders. + std::unique_ptr<BorderLines> pBLines; + std::unique_ptr<SwLineRects> pSLines; + std::unique_ptr<SwSubsRects> pSSubsLines; + + // global variable for sub-lines of body, header, footer, section and footnote frames. + std::unique_ptr<SwSubsRects> pSSpecSubsLines; + SfxProgress *pSProgress; + + // Sizes of a pixel and the corresponding halves. Will be reset when + // entering SwRootFrame::PaintSwFrame + long nSPixelSzW; + long nSPixelSzH; + long nSHalfPixelSzW; + long nSHalfPixelSzH; + long nSMinDistPixelW; + long nSMinDistPixelH; + + Color aSGlobalRetoucheColor; + + // Current zoom factor + double aSScaleX; + double aSScaleY; + + SwPaintProperties() + : bSFlyMetafile(false) + , pSFlyMetafileOut(nullptr) + , pSGlobalShell(nullptr) + , pSRetoucheFly(nullptr) + , pSRetoucheFly2(nullptr) + , pSFlyOnlyDraw(nullptr) + , pSProgress(nullptr) + , nSPixelSzW(0) + , nSPixelSzH(0) + , nSHalfPixelSzW(0) + , nSHalfPixelSzH(0) + , nSMinDistPixelW(0) + , nSMinDistPixelH(0) + , aSScaleX(1) + , aSScaleY(1) + { + } + +}; + +} + +static SwPaintProperties gProp; + +static bool isSubsidiaryLinesFlysEnabled() +{ + return !gProp.pSGlobalShell->GetViewOptions()->IsPagePreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->IsFormView() && + SwViewOption::IsObjectBoundaries(); +} +//other subsidiary lines enabled? +static bool isSubsidiaryLinesEnabled() +{ + return !gProp.pSGlobalShell->GetViewOptions()->IsPagePreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->IsFormView() && + !gProp.pSGlobalShell->GetViewOptions()->IsWhitespaceHidden() && + SwViewOption::IsDocBoundaries(); +} +//subsidiary lines for sections +static bool isSubsidiaryLinesForSectionsEnabled() +{ + return !gProp.pSGlobalShell->GetViewOptions()->IsPagePreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->IsFormView() && + SwViewOption::IsSectionBoundaries(); +} + + +namespace { + +bool isTableBoundariesEnabled() +{ + if (!gProp.pSGlobalShell->GetViewOptions()->IsTable()) + return false; + + if (gProp.pSGlobalShell->GetViewOptions()->IsPagePreview()) + return false; + + if (gProp.pSGlobalShell->GetViewOptions()->IsReadonly()) + return false; + + if (gProp.pSGlobalShell->GetViewOptions()->IsFormView()) + return false; + + return SwViewOption::IsTableBoundaries(); +} + +} + +/** + * Set borders alignment statics + * Adjustment for 'small' twip-to-pixel relations: + * For 'small' twip-to-pixel relations (less than 2:1) + * values of <gProp.nSHalfPixelSzW> and <gProp.nSHalfPixelSzH> are set to ZERO + */ +void SwCalcPixStatics( vcl::RenderContext const *pOut ) +{ + // determine 'small' twip-to-pixel relation + bool bSmallTwipToPxRelW = false; + bool bSmallTwipToPxRelH = false; + { + Size aCheckTwipToPxRelSz( pOut->PixelToLogic( Size( 100, 100 )) ); + if ( (aCheckTwipToPxRelSz.Width()/100.0) < 2.0 ) + { + bSmallTwipToPxRelW = true; + } + if ( (aCheckTwipToPxRelSz.Height()/100.0) < 2.0 ) + { + bSmallTwipToPxRelH = true; + } + } + + Size aSz( pOut->PixelToLogic( Size( 1,1 )) ); + + gProp.nSPixelSzW = aSz.Width(); + if( !gProp.nSPixelSzW ) + gProp.nSPixelSzW = 1; + gProp.nSPixelSzH = aSz.Height(); + if( !gProp.nSPixelSzH ) + gProp.nSPixelSzH = 1; + + // consider 'small' twip-to-pixel relations + if ( !bSmallTwipToPxRelW ) + { + gProp.nSHalfPixelSzW = gProp.nSPixelSzW / 2 + 1; + } + else + { + gProp.nSHalfPixelSzW = 0; + } + // consider 'small' twip-to-pixel relations + if ( !bSmallTwipToPxRelH ) + { + gProp.nSHalfPixelSzH = gProp.nSPixelSzH / 2 + 1; + } + else + { + gProp.nSHalfPixelSzH = 0; + } + + gProp.nSMinDistPixelW = gProp.nSPixelSzW * 2 + 1; + gProp.nSMinDistPixelH = gProp.nSPixelSzH * 2 + 1; + + const MapMode &rMap = pOut->GetMapMode(); + gProp.aSScaleX = double(rMap.GetScaleX()); + gProp.aSScaleY = double(rMap.GetScaleY()); +} + +namespace { + +/** + * To be able to save the statics so the paint is more or less reentrant + */ +class SwSavePaintStatics : public SwPaintProperties +{ +public: + SwSavePaintStatics(); + ~SwSavePaintStatics(); +}; + +} + +SwSavePaintStatics::SwSavePaintStatics() +{ + // Saving globales + bSFlyMetafile = gProp.bSFlyMetafile; + pSGlobalShell = gProp.pSGlobalShell; + pSFlyMetafileOut = gProp.pSFlyMetafileOut; + pSRetoucheFly = gProp.pSRetoucheFly; + pSRetoucheFly2 = gProp.pSRetoucheFly2; + pSFlyOnlyDraw = gProp.pSFlyOnlyDraw; + pBLines = std::move(gProp.pBLines); + pSLines = std::move(gProp.pSLines); + pSSubsLines = std::move(gProp.pSSubsLines); + pSSpecSubsLines = std::move(gProp.pSSpecSubsLines); + pSProgress = gProp.pSProgress; + nSPixelSzW = gProp.nSPixelSzW; + nSPixelSzH = gProp.nSPixelSzH; + nSHalfPixelSzW = gProp.nSHalfPixelSzW; + nSHalfPixelSzH = gProp.nSHalfPixelSzH; + nSMinDistPixelW = gProp.nSMinDistPixelW; + nSMinDistPixelH = gProp.nSMinDistPixelH ; + aSGlobalRetoucheColor = aGlobalRetoucheColor; + aSScaleX = gProp.aSScaleX; + aSScaleY = gProp.aSScaleY; + + // Restoring globales to default + gProp.bSFlyMetafile = false; + gProp.pSFlyMetafileOut = nullptr; + gProp.pSRetoucheFly = nullptr; + gProp.pSRetoucheFly2 = nullptr; + gProp.nSPixelSzW = gProp.nSPixelSzH = + gProp.nSHalfPixelSzW = gProp.nSHalfPixelSzH = + gProp.nSMinDistPixelW = gProp.nSMinDistPixelH = 0; + gProp.aSScaleX = gProp.aSScaleY = 1.0; + gProp.pSProgress = nullptr; +} + +SwSavePaintStatics::~SwSavePaintStatics() +{ + // Restoring globales to saved one + gProp.pSGlobalShell = pSGlobalShell; + gProp.bSFlyMetafile = bSFlyMetafile; + gProp.pSFlyMetafileOut = pSFlyMetafileOut; + gProp.pSRetoucheFly = pSRetoucheFly; + gProp.pSRetoucheFly2 = pSRetoucheFly2; + gProp.pSFlyOnlyDraw = pSFlyOnlyDraw; + gProp.pBLines = std::move(pBLines); + gProp.pSLines = std::move(pSLines); + gProp.pSSubsLines = std::move(pSSubsLines); + gProp.pSSpecSubsLines = std::move(pSSpecSubsLines); + gProp.pSProgress = pSProgress; + gProp.nSPixelSzW = nSPixelSzW; + gProp.nSPixelSzH = nSPixelSzH; + gProp.nSHalfPixelSzW = nSHalfPixelSzW; + gProp.nSHalfPixelSzH = nSHalfPixelSzH; + gProp.nSMinDistPixelW = nSMinDistPixelW; + gProp.nSMinDistPixelH = nSMinDistPixelH; + aGlobalRetoucheColor = aSGlobalRetoucheColor; + gProp.aSScaleX = aSScaleX; + gProp.aSScaleY = aSScaleY; +} + +void BorderLines::AddBorderLines(const drawinglayer::primitive2d::Primitive2DContainer& rContainer) +{ + if(!rContainer.empty()) + { + m_Lines.append(rContainer); + } +} + +SwLineRect::SwLineRect( const SwRect &rRect, const Color *pCol, const SvxBorderLineStyle nStyl, + const SwTabFrame *pT, const SubColFlags nSCol ) : + SwRect( rRect ), + nStyle( nStyl ), + pTab( pT ), + nSubColor( nSCol ), + bPainted( false ), + nLock( 0 ) +{ + if ( pCol != nullptr ) + aColor = *pCol; +} + +bool SwLineRect::MakeUnion( const SwRect &rRect, SwPaintProperties const & properties) +{ + // It has already been tested outside, whether the rectangles have + // the same orientation (horizontal or vertical), color, etc. + if ( Height() > Width() ) //Vertical line + { + if ( Left() == rRect.Left() && Width() == rRect.Width() ) + { + // Merge when there is no gap between the lines + const long nAdd = properties.nSPixelSzW + properties.nSHalfPixelSzW; + if ( Bottom() + nAdd >= rRect.Top() && + Top() - nAdd <= rRect.Bottom() ) + { + Bottom( std::max( Bottom(), rRect.Bottom() ) ); + Top ( std::min( Top(), rRect.Top() ) ); + return true; + } + } + } + else + { + if ( Top() == rRect.Top() && Height() == rRect.Height() ) + { + // Merge when there is no gap between the lines + const long nAdd = properties.nSPixelSzW + properties.nSHalfPixelSzW; + if ( Right() + nAdd >= rRect.Left() && + Left() - nAdd <= rRect.Right() ) + { + Right( std::max( Right(), rRect.Right() ) ); + Left ( std::min( Left(), rRect.Left() ) ); + return true; + } + } + } + return false; +} + +void SwLineRects::AddLineRect( const SwRect &rRect, const Color *pCol, const SvxBorderLineStyle nStyle, + const SwTabFrame *pTab, const SubColFlags nSCol, SwPaintProperties const & properties ) +{ + // Loop backwards because lines which can be combined, can usually be painted + // in the same context + for (reverse_iterator it = aLineRects.rbegin(); it != aLineRects.rend(); + ++it) + { + SwLineRect &rLRect = *it; + // Test for the orientation, color, table + if ( rLRect.GetTab() == pTab && + !rLRect.IsPainted() && rLRect.GetSubColor() == nSCol && + (rLRect.Height() > rLRect.Width()) == (rRect.Height() > rRect.Width()) && + (pCol && rLRect.GetColor() == *pCol) ) + { + if ( rLRect.MakeUnion( rRect, properties ) ) + return; + } + } + aLineRects.emplace_back( rRect, pCol, nStyle, pTab, nSCol ); +} + +void SwLineRects::ConnectEdges( OutputDevice const *pOut, SwPaintProperties const & properties ) +{ + if ( pOut->GetOutDevType() != OUTDEV_PRINTER ) + { + // I'm not doing anything for a too small zoom + if ( properties.aSScaleX < aEdgeScale || properties.aSScaleY < aEdgeScale ) + return; + } + + static const long nAdd = 20; + + std::vector<SwLineRect*> aCheck; + + for (size_t i = 0; i < aLineRects.size(); ++i) + { + SwLineRect &rL1 = aLineRects[i]; + if ( !rL1.GetTab() || rL1.IsPainted() || rL1.IsLocked() ) + continue; + + aCheck.clear(); + + const bool bVert = rL1.Height() > rL1.Width(); + long nL1a, nL1b, nL1c, nL1d; + + if ( bVert ) + { + nL1a = rL1.Top(); nL1b = rL1.Left(); + nL1c = rL1.Right(); nL1d = rL1.Bottom(); + } + else + { + nL1a = rL1.Left(); nL1b = rL1.Top(); + nL1c = rL1.Bottom(); nL1d = rL1.Right(); + } + + // Collect all lines to possibly link with i1 + for (iterator it2 = aLineRects.begin(); it2 != aLineRects.end(); ++it2) + { + SwLineRect &rL2 = *it2; + if ( rL2.GetTab() != rL1.GetTab() || + rL2.IsPainted() || + rL2.IsLocked() || + (bVert == (rL2.Height() > rL2.Width())) ) + continue; + + long nL2a, nL2b, nL2c, nL2d; + if ( bVert ) + { + nL2a = rL2.Top(); nL2b = rL2.Left(); + nL2c = rL2.Right(); nL2d = rL2.Bottom(); + } + else + { + nL2a = rL2.Left(); nL2b = rL2.Top(); + nL2c = rL2.Bottom(); nL2d = rL2.Right(); + } + + if ( (nL1a - nAdd < nL2d && nL1d + nAdd > nL2a) && + ((nL1b > nL2b && nL1c < nL2c) || + (nL1c >= nL2c && nL1b - nAdd < nL2c) || + (nL1b <= nL2b && nL1c + nAdd > nL2b)) ) + { + aCheck.push_back( &rL2 ); + } + } + if ( aCheck.size() < 2 ) + continue; + + bool bRemove = false; + + // For each line test all following ones. + for ( size_t k = 0; !bRemove && k < aCheck.size(); ++k ) + { + SwLineRect &rR1 = *aCheck[k]; + + for ( size_t k2 = k+1; !bRemove && k2 < aCheck.size(); ++k2 ) + { + SwLineRect &rR2 = *aCheck[k2]; + if ( bVert ) + { + SwLineRect *pLA = nullptr; + SwLineRect *pLB = nullptr; + if ( rR1.Top() < rR2.Top() ) + { + pLA = &rR1; pLB = &rR2; + } + else if ( rR1.Top() > rR2.Top() ) + { + pLA = &rR2; pLB = &rR1; + } + // are k1 and k2 describing a double line? + if ( pLA && pLA->Bottom() + 60 > pLB->Top() ) + { + if ( rL1.Top() < pLA->Top() ) + { + if ( rL1.Bottom() == pLA->Bottom() ) + continue; //Small mistake (where?) + + SwRect aIns( rL1 ); + aIns.Bottom( pLA->Bottom() ); + if ( !rL1.IsInside( aIns ) ) + continue; + aLineRects.emplace_back( aIns, &rL1.GetColor(), + SvxBorderLineStyle::SOLID, + rL1.GetTab(), SubColFlags::Tab ); + if ( isFull() ) + { + --i; + k = aCheck.size(); + break; + } + } + + if ( rL1.Bottom() > pLB->Bottom() ) + rL1.Top( pLB->Top() ); // extend i1 on the top + else + bRemove = true; //stopping, remove i1 + } + } + else + { + SwLineRect *pLA = nullptr; + SwLineRect *pLB = nullptr; + if ( rR1.Left() < rR2.Left() ) + { + pLA = &rR1; pLB = &rR2; + } + else if ( rR1.Left() > rR2.Left() ) + { + pLA = &rR2; pLB = &rR1; + } + // Is it double line? + if ( pLA && pLA->Right() + 60 > pLB->Left() ) + { + if ( rL1.Left() < pLA->Left() ) + { + if ( rL1.Right() == pLA->Right() ) + continue; //small error + + SwRect aIns( rL1 ); + aIns.Right( pLA->Right() ); + if ( !rL1.IsInside( aIns ) ) + continue; + aLineRects.emplace_back( aIns, &rL1.GetColor(), + SvxBorderLineStyle::SOLID, + rL1.GetTab(), SubColFlags::Tab ); + if ( isFull() ) + { + --i; + k = aCheck.size(); + break; + } + } + if ( rL1.Right() > pLB->Right() ) + rL1.Left( pLB->Left() ); + else + bRemove = true; + } + } + } + } + if ( bRemove ) + { + aLineRects.erase(aLineRects.begin() + i); + --i; + } + } +} + +void SwSubsRects::RemoveSuperfluousSubsidiaryLines( const SwLineRects &rRects, SwPaintProperties const & properties ) +{ + // All help lines that are covered by any border will be removed or split + for (size_t i = 0; i < aLineRects.size(); ++i) + { + // get a copy instead of a reference, because an <insert> may destroy + // the object due to a necessary array resize. + const SwLineRect aSubsLineRect(aLineRects[i]); + + // add condition <aSubsLineRect.IsLocked()> in order to consider only + // border lines, which are *not* locked. + if ( aSubsLineRect.IsPainted() || + aSubsLineRect.IsLocked() ) + continue; + + const bool bVerticalSubs = aSubsLineRect.Height() > aSubsLineRect.Width(); + SwRect aSubsRect( aSubsLineRect ); + if ( bVerticalSubs ) + { + aSubsRect.AddLeft ( - (properties.nSPixelSzW+properties.nSHalfPixelSzW) ); + aSubsRect.AddRight ( properties.nSPixelSzW+properties.nSHalfPixelSzW ); + } + else + { + aSubsRect.AddTop ( - (properties.nSPixelSzH+properties.nSHalfPixelSzH) ); + aSubsRect.AddBottom( properties.nSPixelSzH+properties.nSHalfPixelSzH ); + } + for (const_iterator itK = rRects.aLineRects.begin(); itK != rRects.aLineRects.end(); ++itK) + { + const SwLineRect &rLine = *itK; + + // do *not* consider painted or locked border lines. + // #i1837# - locked border lines have to be considered. + if ( rLine.IsLocked () ) + continue; + + if ( !bVerticalSubs == ( rLine.Height() > rLine.Width() ) ) //same direction? + continue; + + if ( aSubsRect.IsOver( rLine ) ) + { + if ( bVerticalSubs ) // Vertical? + { + if ( aSubsRect.Left() <= rLine.Right() && + aSubsRect.Right() >= rLine.Left() ) + { + long nTmp = rLine.Top()-(properties.nSPixelSzH+1); + if ( aSubsLineRect.Top() < nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Bottom( nTmp ); + aLineRects.emplace_back( aNewSubsRect, nullptr, aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor() ); + } + nTmp = rLine.Bottom()+properties.nSPixelSzH+1; + if ( aSubsLineRect.Bottom() > nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Top( nTmp ); + aLineRects.emplace_back( aNewSubsRect, nullptr, aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor() ); + } + aLineRects.erase(aLineRects.begin() + i); + --i; + break; + } + } + else // Horizontal + { + if ( aSubsRect.Top() <= rLine.Bottom() && + aSubsRect.Bottom() >= rLine.Top() ) + { + long nTmp = rLine.Left()-(properties.nSPixelSzW+1); + if ( aSubsLineRect.Left() < nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Right( nTmp ); + aLineRects.emplace_back( aNewSubsRect, nullptr, aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor() ); + } + nTmp = rLine.Right()+properties.nSPixelSzW+1; + if ( aSubsLineRect.Right() > nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Left( nTmp ); + aLineRects.emplace_back( aNewSubsRect, nullptr, aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor() ); + } + aLineRects.erase(aLineRects.begin() + i); + --i; + break; + } + } + } + } + } +} + +void SwLineRects::LockLines( bool bLock ) +{ + for (SwLineRect& rLRect : aLineRects) + rLRect.Lock( bLock ); +} + +static void lcl_DrawDashedRect( OutputDevice * pOut, SwLineRect const & rLRect ) +{ + long startX = rLRect.Left( ), endX; + long startY = rLRect.Top( ), endY; + + // Discriminate vertically stretched rect from horizontally stretched + // and restrict minimum nHalfLWidth to 1 + long nHalfLWidth = std::max( static_cast<long>(std::min( rLRect.Width( ), rLRect.Height( ) ) / 2), 1L ); + + if ( rLRect.Height( ) > rLRect.Width( ) ) + { + startX += nHalfLWidth; + endX = startX; + endY = startY + rLRect.Height( ); + } + else + { + startY += nHalfLWidth; + endY = startY; + endX = startX + rLRect.Width( ); + } + + svtools::DrawLine( *pOut, Point( startX, startY ), Point( endX, endY ), + sal_uInt32( nHalfLWidth * 2 ), rLRect.GetStyle( ) ); +} + +void SwLineRects::PaintLines( OutputDevice *pOut, SwPaintProperties const &properties ) +{ + // Paint the borders. Sadly two passes are needed. + // Once for the inside and once for the outside edges of tables + if ( aLineRects.size() == nLastCount ) + return; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pOut ); + + pOut->Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + pOut->SetFillColor(); + pOut->SetLineColor(); + ConnectEdges( pOut, properties ); + const Color *pLast = nullptr; + + bool bPaint2nd = false; + size_t nMinCount = aLineRects.size(); + + for ( size_t i = 0; i < aLineRects.size(); ++i ) + { + SwLineRect &rLRect = aLineRects[i]; + + if ( rLRect.IsPainted() ) + continue; + + if ( rLRect.IsLocked() ) + { + nMinCount = std::min( nMinCount, i ); + continue; + } + + // Paint it now or in the second pass? + bool bPaint = true; + if ( rLRect.GetTab() ) + { + if ( rLRect.Height() > rLRect.Width() ) + { + // Vertical edge, overlapping with the table edge? + SwTwips nLLeft = rLRect.Left() - 30, + nLRight = rLRect.Right() + 30, + nTLeft = rLRect.GetTab()->getFrameArea().Left() + rLRect.GetTab()->getFramePrintArea().Left(), + nTRight = rLRect.GetTab()->getFrameArea().Left() + rLRect.GetTab()->getFramePrintArea().Right(); + if ( (nTLeft >= nLLeft && nTLeft <= nLRight) || + (nTRight>= nLLeft && nTRight<= nLRight) ) + bPaint = false; + } + else + { + // Horizontal edge, overlapping with the table edge? + SwTwips nLTop = rLRect.Top() - 30, + nLBottom = rLRect.Bottom() + 30, + nTTop = rLRect.GetTab()->getFrameArea().Top() + rLRect.GetTab()->getFramePrintArea().Top(), + nTBottom = rLRect.GetTab()->getFrameArea().Top() + rLRect.GetTab()->getFramePrintArea().Bottom(); + if ( (nTTop >= nLTop && nTTop <= nLBottom) || + (nTBottom >= nLTop && nTBottom <= nLBottom) ) + bPaint = false; + } + } + if ( bPaint ) + { + if ( !pLast || *pLast != rLRect.GetColor() ) + { + pLast = &rLRect.GetColor(); + + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + if( properties.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + pOut->SetDrawMode( DrawModeFlags::Default ); + + pOut->SetLineColor( *pLast ); + pOut->SetFillColor( *pLast ); + pOut->SetDrawMode( nOldDrawMode ); + } + + if( !rLRect.IsEmpty() ) + lcl_DrawDashedRect( pOut, rLRect ); + rLRect.SetPainted(); + } + else + bPaint2nd = true; + } + if ( bPaint2nd ) + { + for ( size_t i = 0; i < aLineRects.size(); ++i ) + { + SwLineRect &rLRect = aLineRects[i]; + if ( rLRect.IsPainted() ) + continue; + + if ( rLRect.IsLocked() ) + { + nMinCount = std::min( nMinCount, i ); + continue; + } + + if ( !pLast || *pLast != rLRect.GetColor() ) + { + pLast = &rLRect.GetColor(); + + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + if( properties.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pOut->SetDrawMode( DrawModeFlags::Default ); + } + + pOut->SetFillColor( *pLast ); + pOut->SetDrawMode( nOldDrawMode ); + } + if( !rLRect.IsEmpty() ) + lcl_DrawDashedRect( pOut, rLRect ); + rLRect.SetPainted(); + } + } + nLastCount = nMinCount; + pOut->Pop(); + +} + +void SwSubsRects::PaintSubsidiary( OutputDevice *pOut, + const SwLineRects *pRects, + SwPaintProperties const & properties ) +{ + if ( !aLineRects.empty() ) + { + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pOut ); + + // Remove all help line that are almost covered (tables) + for (size_type i = 0; i != aLineRects.size(); ++i) + { + SwLineRect &rLi = aLineRects[i]; + const bool bVerticalSubs = rLi.Height() > rLi.Width(); + + for (size_type k = i + 1; k != aLineRects.size(); ++k) + { + SwLineRect &rLk = aLineRects[k]; + if ( rLi.SSize() == rLk.SSize() ) + { + if ( bVerticalSubs == ( rLk.Height() > rLk.Width() ) ) + { + if ( bVerticalSubs ) + { + long nLi = rLi.Right(); + long nLk = rLk.Right(); + if ( rLi.Top() == rLk.Top() && + ((nLi < rLk.Left() && nLi+21 > rLk.Left()) || + (nLk < rLi.Left() && nLk+21 > rLi.Left()))) + { + aLineRects.erase(aLineRects.begin() + i); + // don't continue with inner loop any more: + // the array may shrink! + --i; + break; + } + } + else + { + long nLi = rLi.Bottom(); + long nLk = rLk.Bottom(); + if ( rLi.Left() == rLk.Left() && + ((nLi < rLk.Top() && nLi+21 > rLk.Top()) || + (nLk < rLi.Top() && nLk+21 > rLi.Top()))) + { + aLineRects.erase(aLineRects.begin() + i); + // don't continue with inner loop any more: + // the array may shrink! + --i; + break; + } + } + } + } + } + } + + if ( pRects && (!pRects->aLineRects.empty()) ) + RemoveSuperfluousSubsidiaryLines( *pRects, properties ); + + if ( !aLineRects.empty() ) + { + pOut->Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + pOut->SetLineColor(); + + // Reset draw mode in high contrast mode in order to get fill color + // set at output device. Recover draw mode after draw of lines. + // Necessary for the subsidiary lines painted by the fly frames. + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + if( gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pOut->SetDrawMode( DrawModeFlags::Default ); + } + + for (SwLineRect& rLRect : aLineRects) + { + // Add condition <!rLRect.IsLocked()> to prevent paint of locked subsidiary lines. + if ( !rLRect.IsPainted() && + !rLRect.IsLocked() ) + { + const Color *pCol = nullptr; + switch ( rLRect.GetSubColor() ) + { + case SubColFlags::Page: pCol = &SwViewOption::GetDocBoundariesColor(); break; + case SubColFlags::Fly: pCol = &SwViewOption::GetObjectBoundariesColor(); break; + case SubColFlags::Tab: pCol = &SwViewOption::GetTableBoundariesColor(); break; + case SubColFlags::Sect: pCol = &SwViewOption::GetSectionBoundColor(); break; + } + + if (pCol && pOut->GetFillColor() != *pCol) + pOut->SetFillColor( *pCol ); + pOut->DrawRect( rLRect.SVRect() ); + + rLRect.SetPainted(); + } + } + + pOut->SetDrawMode( nOldDrawMode ); + + pOut->Pop(); + } + } +} + +// Various functions that are use in this file. + +/** + * Function <SwAlignRect(..)> is also used outside this file + * + * Correction: adjust rectangle on pixel level in order to make sure, + * that the border "leaves its original pixel", if it has to + * No prior adjustments for odd relation between pixel and twip + */ +void SwAlignRect( SwRect &rRect, const SwViewShell *pSh, const vcl::RenderContext* pRenderContext ) +{ + if( !rRect.HasArea() ) + return; + + // Make sure that view shell (parameter <pSh>) exists, if the output device + // is taken from this view shell --> no output device, no alignment + // Output device taken from view shell <pSh>, if <gProp.bSFlyMetafile> not set + if ( !gProp.bSFlyMetafile && !pSh ) + { + return; + } + + const vcl::RenderContext *pOut = gProp.bSFlyMetafile ? + gProp.pSFlyMetafileOut.get() : pRenderContext; + + // Hold original rectangle in pixel + const tools::Rectangle aOrgPxRect = pOut->LogicToPixel( rRect.SVRect() ); + // Determine pixel-center rectangle in twip + const SwRect aPxCenterRect( pOut->PixelToLogic( aOrgPxRect ) ); + + // Perform adjustments on pixel level. + SwRect aAlignedPxRect( aOrgPxRect ); + if ( rRect.Top() > aPxCenterRect.Top() ) + { + // 'leave pixel overlapping on top' + aAlignedPxRect.AddTop( 1 ); + } + + if ( rRect.Bottom() < aPxCenterRect.Bottom() ) + { + // 'leave pixel overlapping on bottom' + aAlignedPxRect.AddBottom( - 1 ); + } + + if ( rRect.Left() > aPxCenterRect.Left() ) + { + // 'leave pixel overlapping on left' + aAlignedPxRect.AddLeft( 1 ); + } + + if ( rRect.Right() < aPxCenterRect.Right() ) + { + // 'leave pixel overlapping on right' + aAlignedPxRect.AddRight( - 1 ); + } + + // Consider negative width/height check, if aligned SwRect has negative width/height. + // If Yes, adjust it to width/height = 0 twip. + // NOTE: A SwRect with negative width/height can occur, if the width/height + // of the given SwRect in twip was less than a pixel in twip and that + // the alignment calculates that the aligned SwRect should not contain + // the pixels the width/height is on. + if ( aAlignedPxRect.Width() < 0 ) + { + aAlignedPxRect.Width(0); + } + if ( aAlignedPxRect.Height() < 0 ) + { + aAlignedPxRect.Height(0); + } + // Consider zero width/height for converting a rectangle from + // pixel to logic it needs a width/height. Thus, set width/height + // to one, if it's zero and correct this on the twip level after the conversion. + bool bZeroWidth = false; + if ( aAlignedPxRect.Width() == 0 ) + { + aAlignedPxRect.Width(1); + bZeroWidth = true; + } + bool bZeroHeight = false; + if ( aAlignedPxRect.Height() == 0 ) + { + aAlignedPxRect.Height(1); + bZeroHeight = true; + } + + rRect = pOut->PixelToLogic( aAlignedPxRect.SVRect() ); + + // Consider zero width/height and adjust calculated aligned twip rectangle. + // Reset width/height to zero; previous negative width/height haven't to be considered. + if ( bZeroWidth ) + { + rRect.Width(0); + } + if ( bZeroHeight ) + { + rRect.Height(0); + } +} + +/** + * Method to pixel-align rectangle for drawing graphic object + * + * Because we are drawing graphics from the left-top-corner in conjunction + * with size coordinates, these coordinates have to be calculated at a pixel + * level. + * Thus, we convert the rectangle to pixel and then convert to left-top-corner + * and then get size of pixel rectangle back to logic. + * This calculation is necessary, because there's a different between + * the conversion from logic to pixel of a normal rectangle with its left-top- + * and right-bottom-corner and the same conversion of the same rectangle + * with left-top-corner and size. + * + * NOTE: Call this method before each <GraphicObject.Draw(...)> +*/ +void SwAlignGrfRect( SwRect *pGrfRect, const vcl::RenderContext &rOut ) +{ + tools::Rectangle aPxRect = rOut.LogicToPixel( pGrfRect->SVRect() ); + pGrfRect->Pos( rOut.PixelToLogic( aPxRect.TopLeft() ) ); + pGrfRect->SSize( rOut.PixelToLogic( aPxRect.GetSize() ) ); +} + +static long lcl_AlignWidth( const long nWidth, SwPaintProperties const & properties ) +{ + if ( nWidth ) + { + const long nW = nWidth % properties.nSPixelSzW; + + if ( !nW || nW > properties.nSHalfPixelSzW ) + return std::max(1L, nWidth - properties.nSHalfPixelSzW); + } + return nWidth; +} + +static long lcl_AlignHeight( const long nHeight, SwPaintProperties const & properties ) +{ + if ( nHeight ) + { + const long nH = nHeight % properties.nSPixelSzH; + + if ( !nH || nH > properties.nSHalfPixelSzH ) + return std::max(1L, nHeight - properties.nSHalfPixelSzH); + } + return nHeight; +} + +/** + * Calculate PrtArea plus surrounding plus shadow + */ +static void lcl_CalcBorderRect( SwRect &rRect, const SwFrame *pFrame, + const SwBorderAttrs &rAttrs, + const bool bShadow, + SwPaintProperties const & properties) +{ + // Special handling for cell frames. + // The printing area of a cell frame is completely enclosed in the frame area + // and a cell frame has no shadow. Thus, for cell frames the calculated + // area equals the frame area. + // Notes: Borders of cell frames in R2L text direction will switch its side + // - left border is painted on the right; right border on the left. + // See <lcl_PaintLeftLine> and <lcl_PaintRightLine>. + if( pFrame->IsSctFrame() ) + { + rRect = pFrame->getFramePrintArea(); + rRect.Pos() += pFrame->getFrameArea().Pos(); + } + else if ( pFrame->IsCellFrame() ) + rRect = pFrame->getFrameArea(); + else + { + rRect = pFrame->getFramePrintArea(); + rRect.Pos() += pFrame->getFrameArea().Pos(); + + SwRectFn fnRect = pFrame->IsVertical() ? ( pFrame->IsVertLR() ? (pFrame->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + const SvxBoxItem &rBox = rAttrs.GetBox(); + const bool bTop = 0 != (pFrame->*fnRect->fnGetTopMargin)(); + if ( bTop ) + { + SwTwips nDiff = rBox.GetTop() ? + rBox.CalcLineSpace( SvxBoxItemLine::TOP ) : + rBox.GetDistance( SvxBoxItemLine::TOP ); + if( nDiff ) + (rRect.*fnRect->fnSubTop)( nDiff ); + } + + const bool bBottom = 0 != (pFrame->*fnRect->fnGetBottomMargin)(); + if ( bBottom ) + { + SwTwips nDiff = 0; + // #i29550# + if ( pFrame->IsTabFrame() && + static_cast<const SwTabFrame*>(pFrame)->IsCollapsingBorders() ) + { + // For collapsing borders, we have to add the height of + // the height of the last line + nDiff = static_cast<const SwTabFrame*>(pFrame)->GetBottomLineSize(); + } + else + { + nDiff = rBox.GetBottom() ? + rBox.CalcLineSpace( SvxBoxItemLine::BOTTOM ) : + rBox.GetDistance( SvxBoxItemLine::BOTTOM ); + } + if( nDiff ) + (rRect.*fnRect->fnAddBottom)( nDiff ); + } + + if ( rBox.GetLeft() ) + (rRect.*fnRect->fnSubLeft)( rBox.CalcLineSpace( SvxBoxItemLine::LEFT ) ); + else + (rRect.*fnRect->fnSubLeft)( rBox.GetDistance( SvxBoxItemLine::LEFT ) ); + + if ( rBox.GetRight() ) + (rRect.*fnRect->fnAddRight)( rBox.CalcLineSpace( SvxBoxItemLine::RIGHT ) ); + else + (rRect.*fnRect->fnAddRight)( rBox.GetDistance( SvxBoxItemLine::RIGHT ) ); + + if ( bShadow && rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE ) + { + const SvxShadowItem &rShadow = rAttrs.GetShadow(); + if ( bTop ) + (rRect.*fnRect->fnSubTop)(rShadow.CalcShadowSpace(SvxShadowItemSide::TOP)); + (rRect.*fnRect->fnSubLeft)(rShadow.CalcShadowSpace(SvxShadowItemSide::LEFT)); + if ( bBottom ) + (rRect.*fnRect->fnAddBottom) + (rShadow.CalcShadowSpace( SvxShadowItemSide::BOTTOM )); + (rRect.*fnRect->fnAddRight)(rShadow.CalcShadowSpace(SvxShadowItemSide::RIGHT)); + } + } + + ::SwAlignRect( rRect, properties.pSGlobalShell, properties.pSGlobalShell ? properties.pSGlobalShell->GetOut() : nullptr ); +} + +/** + * Extend left/right border/shadow rectangle to bottom of previous frame/to + * top of next frame, if border/shadow is joined with previous/next frame + */ +static void lcl_ExtendLeftAndRight( SwRect& _rRect, + const SwFrame& _rFrame, + const SwBorderAttrs& _rAttrs, + const SwRectFn& _rRectFn ) +{ + if ( _rAttrs.JoinedWithPrev( _rFrame ) ) + { + const SwFrame* pPrevFrame = _rFrame.GetPrev(); + (_rRect.*_rRectFn->fnSetTop)( (pPrevFrame->*_rRectFn->fnGetPrtBottom)() ); + } + if ( _rAttrs.JoinedWithNext( _rFrame ) ) + { + const SwFrame* pNextFrame = _rFrame.GetNext(); + (_rRect.*_rRectFn->fnSetBottom)( (pNextFrame->*_rRectFn->fnGetPrtTop)() ); + } +} + +/// Returns a range suitable for subtraction when lcl_SubtractFlys() is used. +/// Otherwise DrawFillAttributes() expands the clip path itself. +static basegfx::B2DRange lcl_ShrinkFly(const SwRect& rRect) +{ + static MapMode aMapMode(MapUnit::MapTwip); + static const Size aSingleUnit = Application::GetDefaultDevice()->PixelToLogic(Size(1, 1), aMapMode); + + double x1 = rRect.Left() + aSingleUnit.getWidth(); + double y1 = rRect.Top() + aSingleUnit.getHeight(); + double x2 = rRect.Right() - aSingleUnit.getWidth(); + double y2 = rRect.Bottom() - aSingleUnit.getHeight(); + + return basegfx::B2DRange(x1, y1, x2, y2); +} + +static void lcl_SubtractFlys( const SwFrame *pFrame, const SwPageFrame *pPage, + const SwRect &rRect, SwRegionRects &rRegion, basegfx::utils::B2DClipState& rClipState, SwPaintProperties const & rProperties) +{ + const SwSortedObjs& rObjs = *pPage->GetSortedObjs(); + const SwFlyFrame* pSelfFly = pFrame->IsInFly() ? pFrame->FindFlyFrame() : gProp.pSRetoucheFly2; + if (!gProp.pSRetoucheFly) + gProp.pSRetoucheFly = gProp.pSRetoucheFly2; + + for (size_t j = 0; (j < rObjs.size()) && !rRegion.empty(); ++j) + { + const SwAnchoredObject* pAnchoredObj = rObjs[j]; + const SdrObject* pSdrObj = pAnchoredObj->GetDrawObj(); + + // Do not consider invisible objects + if (!pPage->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId(pSdrObj->GetLayer())) + continue; + + if (dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr) + continue; + + const SwFlyFrame *pFly = static_cast<const SwFlyFrame*>(pAnchoredObj); + + if (pSelfFly == pFly || gProp.pSRetoucheFly == pFly || !rRect.IsOver(pFly->getFrameArea())) + continue; + + if (!pFly->GetFormat()->GetPrint().GetValue() && + (OUTDEV_PRINTER == gProp.pSGlobalShell->GetOut()->GetOutDevType() || + gProp.pSGlobalShell->IsPreview())) + continue; + + const bool bLowerOfSelf = pSelfFly && pFly->IsLowerOf( pSelfFly ); + + //For character bound Flys only examine those Flys in which it is not + //anchored itself. + //Why only for character bound ones you may ask? It never makes sense to + //subtract frames in which it is anchored itself right? + if (pSelfFly && pSelfFly->IsLowerOf(pFly)) + continue; + + //Any why does it not apply for the RetoucheFly too? + if (gProp.pSRetoucheFly && gProp.pSRetoucheFly->IsLowerOf(pFly)) + continue; + +#if OSL_DEBUG_LEVEL > 0 + //Flys who are anchored inside their own one, must have a bigger OrdNum + //or be character bound. + if (pSelfFly && bLowerOfSelf) + { + OSL_ENSURE( pFly->IsFlyInContentFrame() || + pSdrObj->GetOrdNumDirect() > pSelfFly->GetVirtDrawObj()->GetOrdNumDirect(), + "Fly with wrong z-Order" ); + } +#endif + + bool bStopOnHell = true; + if (pSelfFly) + { + const SdrObject *pTmp = pSelfFly->GetVirtDrawObj(); + if (pSdrObj->GetLayer() == pTmp->GetLayer()) + { + if (pSdrObj->GetOrdNumDirect() < pTmp->GetOrdNumDirect()) + //In the same layer we only observe those that are above. + continue; + } + else + { + if (!bLowerOfSelf && !pFly->GetFormat()->GetOpaque().GetValue()) + //From other layers we are only interested in non + //transparent ones or those that are internal + continue; + bStopOnHell = false; + } + } + if (gProp.pSRetoucheFly) + { + const SdrObject *pTmp = gProp.pSRetoucheFly->GetVirtDrawObj(); + if ( pSdrObj->GetLayer() == pTmp->GetLayer() ) + { + if ( pSdrObj->GetOrdNumDirect() < pTmp->GetOrdNumDirect() ) + //In the same layer we only observe those that are above. + continue; + } + else + { + if (!pFly->IsLowerOf( gProp.pSRetoucheFly ) && !pFly->GetFormat()->GetOpaque().GetValue()) + //From other layers we are only interested in non + //transparent ones or those that are internal + continue; + bStopOnHell = false; + } + } + + //If the content of the Fly is transparent, we subtract it only if it's + //contained in the hell layer. + const IDocumentDrawModelAccess& rIDDMA = pFly->GetFormat()->getIDocumentDrawModelAccess(); + bool bHell = pSdrObj->GetLayer() == rIDDMA.GetHellId(); + if ( (bStopOnHell && bHell) || + /// Change internal order of condition + /// first check "!bHell", then "..->Lower()" and "..->IsNoTextFrame()" + /// have not to be performed, if frame is in "Hell" + ( !bHell && pFly->Lower() && pFly->Lower()->IsNoTextFrame() && + (static_cast<SwNoTextFrame const*>(pFly->Lower())->IsTransparent() || + static_cast<SwNoTextFrame const*>(pFly->Lower())->HasAnimation() || + pFly->GetFormat()->GetSurround().IsContour() + ) + ) + ) + continue; + + // Own if-statements for transparent background/shadow of fly frames + // in order to handle special conditions. + if (pFly->IsBackgroundTransparent()) + { + // Background <pFly> is transparent drawn. Thus normally, its region + // have not to be subtracted from given region. + // But, if method is called for a fly frame and + // <pFly> is a direct lower of this fly frame and + // <pFly> inherites its transparent background brush from its parent, + // then <pFly> frame area have to be subtracted from given region. + // NOTE: Because in Status Quo transparent backgrounds can only be + // assigned to fly frames, the handle of this special case + // avoids drawing of transparent areas more than once, if + // a fly frame inherites a transparent background from its + // parent fly frame. + if (pFrame->IsFlyFrame() && + (pFly->GetAnchorFrame()->FindFlyFrame() == pFrame) && + pFly->GetFormat()->IsBackgroundBrushInherited() + ) + { + SwRect aRect; + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(pFly) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + ::lcl_CalcBorderRect( aRect, pFly, rAttrs, true, rProperties ); + rRegion -= aRect; + rClipState.subtractRange(lcl_ShrinkFly(aRect)); + continue; + } + else + { + continue; + } + } + + if (bHell && pFly->GetAnchorFrame()->IsInFly()) + { + //So the border won't get dismantled by the background of the other + //Fly. + SwRect aRect; + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(pFly) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + ::lcl_CalcBorderRect( aRect, pFly, rAttrs, true, rProperties ); + rRegion -= aRect; + rClipState.subtractRange(lcl_ShrinkFly(aRect)); + } + else + { + SwRect aRect( pFly->getFramePrintArea() ); + aRect += pFly->getFrameArea().Pos(); + rRegion -= aRect; + rClipState.subtractRange(lcl_ShrinkFly(aRect)); + } + } + if (gProp.pSRetoucheFly == gProp.pSRetoucheFly2) + gProp.pSRetoucheFly = nullptr; +} + +static void lcl_implDrawGraphicBackgrd( const SvxBrushItem& _rBackgrdBrush, + vcl::RenderContext* _pOut, + const SwRect& _rAlignedPaintRect, + const GraphicObject& _rGraphicObj, + SwPaintProperties const & properties) +{ + /// determine color of background + /// If color of background brush is not "no fill"/"auto fill" or + /// <SwPaintProperties.bSFlyMetafile> is set, use color of background brush, otherwise + /// use global retouche color. + const Color aColor( ( (_rBackgrdBrush.GetColor() != COL_TRANSPARENT) || properties.bSFlyMetafile ) + ? _rBackgrdBrush.GetColor() + : aGlobalRetoucheColor ); + + /// determine, if background color have to be drawn transparent + /// and calculate transparency percent value + sal_Int8 nTransparencyPercent = 0; + bool bDrawTransparent = false; + if ( aColor.GetTransparency() != 0 ) + /// background color is transparent --> draw transparent. + { + bDrawTransparent = true; + nTransparencyPercent = (aColor.GetTransparency()*100 + 0x7F)/0xFF; + } + else if ( (_rGraphicObj.GetAttr().GetTransparency() != 0) && + (_rBackgrdBrush.GetColor() == COL_TRANSPARENT) ) + /// graphic is drawn transparent and background color is + /// "no fill"/"auto fill" --> draw transparent + { + bDrawTransparent = true; + nTransparencyPercent = (_rGraphicObj.GetAttr().GetTransparency()*100 + 0x7F)/0xFF; + } + + if ( bDrawTransparent ) + { + /// draw background transparent + if( _pOut->GetFillColor() != aColor.GetRGBColor() ) + _pOut->SetFillColor( aColor.GetRGBColor() ); + tools::PolyPolygon aPoly( _rAlignedPaintRect.SVRect() ); + _pOut->DrawTransparent( aPoly, nTransparencyPercent ); + } + else + { + /// draw background opaque + if ( _pOut->GetFillColor() != aColor ) + _pOut->SetFillColor( aColor ); + _pOut->DrawRect( _rAlignedPaintRect.SVRect() ); + } +} + +/** + * This is a local help method to draw a background for a graphic + * + * Under certain circumstances we have to draw a background for a graphic. + * This method takes care of the conditions and draws the background with the + * corresponding color. + * Method introduced for bug fix #103876# in order to optimize drawing tiled + * background graphics. Previously, this code was integrated in method + * <lcl_DrawGraphic>. + * Method implemented as an inline, checking the conditions and calling method + * method <lcl_implDrawGraphicBackgrd(..)> for the intrinsic drawing. + * + * @param _rBackgrdBrush + * background brush contain the color the background has to be drawn. + * + * @param _pOut + * output device the background has to be drawn in. + * + * @param _rAlignedPaintRect + * paint rectangle in the output device, which has to be drawn with the background. + * rectangle have to be aligned by method ::SwAlignRect + * + * @param _rGraphicObj + * graphic object, for which the background has to be drawn. Used for checking + * the transparency of its bitmap, its type and if the graphic is drawn transparent + * + * @param _bNumberingGraphic + * boolean indicating that graphic is used as a numbering. + * + * @param _bBackgrdAlreadyDrawn + * boolean (optional; default: false) indicating, if the background is already drawn. +*/ +static void lcl_DrawGraphicBackgrd( const SvxBrushItem& _rBackgrdBrush, + OutputDevice* _pOut, + const SwRect& _rAlignedPaintRect, + const GraphicObject& _rGraphicObj, + bool _bNumberingGraphic, + SwPaintProperties const & properties, + bool _bBackgrdAlreadyDrawn = false) +{ + // draw background with background color, if + // (1) graphic is not used as a numbering AND + // (2) background is not already drawn AND + // (3) intrinsic graphic is transparent OR intrinsic graphic doesn't exists + if ( !_bNumberingGraphic && + !_bBackgrdAlreadyDrawn && + ( _rGraphicObj.IsTransparent() || _rGraphicObj.GetType() == GraphicType::NONE ) + ) + { + lcl_implDrawGraphicBackgrd( _rBackgrdBrush, _pOut, _rAlignedPaintRect, _rGraphicObj, properties ); + } +} + +/** + * NNOTE: the transparency of the background graphic is saved in + * SvxBrushItem.GetGraphicObject(<shell>).GetAttr().Set/GetTransparency() + * and is considered in the drawing of the graphic + * + * Thus, to provide transparent background graphic for text frames nothing + * has to be coded + * + * Use align rectangle for drawing graphic Pixel-align coordinates for + * drawing graphic + * Outsource code for drawing background of the graphic + * with a background color in method <lcl_DrawGraphicBackgrd> + * + * Also, change type of <bGrfNum> and <bClip> from <bool> to <bool> + */ +static void lcl_DrawGraphic( const SvxBrushItem& rBrush, vcl::RenderContext *pOut, + SwViewShell &rSh, const SwRect &rGrf, const SwRect &rOut, + bool bGrfNum, + SwPaintProperties const & properties, + bool bBackgrdAlreadyDrawn ) + // add parameter <bBackgrdAlreadyDrawn> to indicate + // that the background is already drawn. +{ + // Calculate align rectangle from parameter <rGrf> and use aligned + // rectangle <aAlignedGrfRect> in the following code + SwRect aAlignedGrfRect = rGrf; + ::SwAlignRect( aAlignedGrfRect, &rSh, pOut ); + + // Change type from <bool> to <bool>. + const bool bNotInside = !rOut.IsInside( aAlignedGrfRect ); + if ( bNotInside ) + { + pOut->Push( PushFlags::CLIPREGION ); + pOut->IntersectClipRegion( rOut.SVRect() ); + } + + GraphicObject *pGrf = const_cast<GraphicObject*>(rBrush.GetGraphicObject()); + + // Outsource drawing of background with a background color + ::lcl_DrawGraphicBackgrd( rBrush, pOut, aAlignedGrfRect, *pGrf, bGrfNum, properties, bBackgrdAlreadyDrawn ); + + // Because for drawing a graphic left-top-corner and size coordinates are + // used, these coordinates have to be determined on pixel level. + ::SwAlignGrfRect( &aAlignedGrfRect, *pOut ); + + const basegfx::B2DHomMatrix aGraphicTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aAlignedGrfRect.Width(), aAlignedGrfRect.Height(), + aAlignedGrfRect.Left(), aAlignedGrfRect.Top())); + + paintGraphicUsingPrimitivesHelper( + *pOut, + *pGrf, + pGrf->GetAttr(), + aGraphicTransform, + OUString(), + OUString(), + OUString()); + + if ( bNotInside ) + pOut->Pop(); +} + +bool DrawFillAttributes( + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes, + const SwRect& rOriginalLayoutRect, + const SwRegionRects& rPaintRegion, + const basegfx::utils::B2DClipState& rClipState, + vcl::RenderContext& rOut) +{ + if(rFillAttributes && rFillAttributes->isUsed()) + { + basegfx::B2DRange aPaintRange( + rPaintRegion.GetOrigin().Left(), + rPaintRegion.GetOrigin().Top(), + rPaintRegion.GetOrigin().Right(), + rPaintRegion.GetOrigin().Bottom()); + + if (!aPaintRange.isEmpty() && + !rPaintRegion.empty() && + !basegfx::fTools::equalZero(aPaintRange.getWidth()) && + !basegfx::fTools::equalZero(aPaintRange.getHeight())) + { + const SvtOptionsDrawinglayer aSvtOptionsDrawinglayer; + + // need to expand for correct AAed and non-AAed visualization as primitive. + // This must probably be removed again when we will be able to get all Writer visualization + // as primitives and Writer prepares all it's stuff in high precision coordinates (also + // needs to avoid moving boundaries around to better show overlapping stuff...) + if(aSvtOptionsDrawinglayer.IsAntiAliasing()) + { + // if AAed in principle expand by 0.5 in all directions. Since painting edges of + // AAed regions does not add to no transparence (0.5 opacity covered by 0.5 opacity + // is not full opacity but 0.75 opacity) we need some overlap here to avoid paint + // artifacts. Checked experimentally - a little bit more in Y is needed, probably + // due to still existing integer alignment and crunching in writer. + static const double fExpandX = 0.55; + static const double fExpandY = 0.70; + const basegfx::B2DVector aSingleUnit(rOut.GetInverseViewTransformation() * basegfx::B2DVector(fExpandX, fExpandY)); + + aPaintRange.expand(aPaintRange.getMinimum() - aSingleUnit); + aPaintRange.expand(aPaintRange.getMaximum() + aSingleUnit); + } + else + { + // if not AAed expand by one unit to bottom right due to the missing unit + // from SwRect/Rectangle integer handling + const basegfx::B2DVector aSingleUnit(rOut.GetInverseViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + + aPaintRange.expand(aPaintRange.getMaximum() + aSingleUnit); + } + + const basegfx::B2DRange aDefineRange( + rOriginalLayoutRect.Left(), + rOriginalLayoutRect.Top(), + rOriginalLayoutRect.Right(), + rOriginalLayoutRect.Bottom()); + + const drawinglayer::primitive2d::Primitive2DContainer& rSequence = rFillAttributes->getPrimitive2DSequence( + aPaintRange, + aDefineRange); + + if(rSequence.size()) + { + drawinglayer::primitive2d::Primitive2DContainer const* + pPrimitives(&rSequence); + drawinglayer::primitive2d::Primitive2DContainer primitives; + // tdf#86578 the awful lcl_SubtractFlys hack + if (rPaintRegion.size() > 1 || rPaintRegion[0] != rPaintRegion.GetOrigin()) + { + basegfx::B2DPolyPolygon const& maskRegion(rClipState.getClipPoly()); + primitives.resize(1); + primitives[0] = new drawinglayer::primitive2d::MaskPrimitive2D( + maskRegion, rSequence); + pPrimitives = &primitives; + } + assert(pPrimitives && pPrimitives->size()); + + const drawinglayer::geometry::ViewInformation2D aViewInformation2D( + basegfx::B2DHomMatrix(), + rOut.GetViewTransformation(), + aPaintRange, + nullptr, + 0.0, + uno::Sequence< beans::PropertyValue >()); + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor(drawinglayer::processor2d::createProcessor2DFromOutputDevice( + rOut, + aViewInformation2D) ); + if(pProcessor) + { + pProcessor->process(*pPrimitives); + return true; + } + } + } + } + + return false; +} + +void DrawGraphic( + const SvxBrushItem *pBrush, + vcl::RenderContext *pOutDev, + const SwRect &rOrg, + const SwRect &rOut, + const sal_uInt8 nGrfNum, + const bool bConsiderBackgroundTransparency ) + // Add 6th parameter to indicate that method should + // consider background transparency, saved in the color of the brush item +{ + SwViewShell &rSh = *gProp.pSGlobalShell; + bool bReplaceGrfNum = GRFNUM_REPLACE == nGrfNum; + bool bGrfNum = GRFNUM_NO != nGrfNum; + Size aGrfSize; + SvxGraphicPosition ePos = GPOS_NONE; + if( pBrush && !bReplaceGrfNum ) + { + if( rSh.GetViewOptions()->IsGraphic() ) + { + OUString referer; + SfxObjectShell * sh = rSh.GetDoc()->GetPersist(); + if (sh != nullptr && sh->HasName()) { + referer = sh->GetMedium()->GetName(); + } + const Graphic* pGrf = pBrush->GetGraphic(referer); + if( pGrf && GraphicType::NONE != pGrf->GetType() ) + { + ePos = pBrush->GetGraphicPos(); + if( pGrf->IsSupportedGraphic() ) + // don't the use the specific output device! Bug 94802 + aGrfSize = ::GetGraphicSizeTwip( *pGrf, nullptr ); + } + } + else + bReplaceGrfNum = bGrfNum; + } + + SwRect aGrf; + aGrf.SSize( aGrfSize ); + bool bDraw = true; + bool bRetouche = true; + switch ( ePos ) + { + case GPOS_LT: + aGrf.Pos() = rOrg.Pos(); + break; + + case GPOS_MT: + aGrf.Pos().setY( rOrg.Top() ); + aGrf.Pos().setX( rOrg.Left() + rOrg.Width()/2 - aGrfSize.Width()/2 ); + break; + + case GPOS_RT: + aGrf.Pos().setY( rOrg.Top() ); + aGrf.Pos().setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_LM: + aGrf.Pos().setY( rOrg.Top() + rOrg.Height()/2 - aGrfSize.Height()/2 ); + aGrf.Pos().setX( rOrg.Left() ); + break; + + case GPOS_MM: + aGrf.Pos().setY( rOrg.Top() + rOrg.Height()/2 - aGrfSize.Height()/2 ); + aGrf.Pos().setX( rOrg.Left() + rOrg.Width()/2 - aGrfSize.Width()/2 ); + break; + + case GPOS_RM: + aGrf.Pos().setY( rOrg.Top() + rOrg.Height()/2 - aGrfSize.Height()/2 ); + aGrf.Pos().setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_LB: + aGrf.Pos().setY( rOrg.Bottom() - aGrfSize.Height() ); + aGrf.Pos().setX( rOrg.Left() ); + break; + + case GPOS_MB: + aGrf.Pos().setY( rOrg.Bottom() - aGrfSize.Height() ); + aGrf.Pos().setX( rOrg.Left() + rOrg.Width()/2 - aGrfSize.Width()/2 ); + break; + + case GPOS_RB: + aGrf.Pos().setY( rOrg.Bottom() - aGrfSize.Height() ); + aGrf.Pos().setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_AREA: + aGrf = rOrg; + // Despite the fact that the background graphic has to fill the complete + // area, we already checked, whether the graphic will completely fill out + // the region the <rOut> that is to be painted. Thus, nothing has to be + // touched again. + // E.g. this is the case for a Fly Frame without a background + // brush positioned on the border of the page which inherited the background + // brush from the page. + bRetouche = !rOut.IsInside( aGrf ); + break; + + case GPOS_TILED: + { + // draw background of tiled graphic before drawing tiled graphic in loop + // determine graphic object + GraphicObject* pGraphicObj = const_cast< GraphicObject* >(pBrush->GetGraphicObject()); + // calculate aligned paint rectangle + SwRect aAlignedPaintRect = rOut; + ::SwAlignRect( aAlignedPaintRect, &rSh, pOutDev ); + // draw background color for aligned paint rectangle + lcl_DrawGraphicBackgrd( *pBrush, pOutDev, aAlignedPaintRect, *pGraphicObj, bGrfNum, gProp ); + + // set left-top-corner of background graphic to left-top-corner of the + // area, from which the background brush is determined. + aGrf.Pos() = rOrg.Pos(); + // setup clipping at output device + pOutDev->Push( PushFlags::CLIPREGION ); + pOutDev->IntersectClipRegion( rOut.SVRect() ); + // use new method <GraphicObject::DrawTiled(::)> + { + // calculate paint offset + Point aPaintOffset( aAlignedPaintRect.Pos() - aGrf.Pos() ); + // draw background graphic tiled for aligned paint rectangle + // #i42643# + // For PDF export, every draw operation for bitmaps takes a + // noticeable amount of place (~50 characters). Thus, optimize + // between tile bitmap size and number of drawing operations here. + + // A_out + // n_chars = k1 * ---------- + k2 * A_bitmap + // A_bitmap + + // minimum n_chars is obtained for (derive for A_bitmap, + // set to 0, take positive solution): + // k1 + // A_bitmap = Sqrt( ---- A_out ) + // k2 + + // where k1 is the number of chars per draw operation, and + // k2 is the number of chars per bitmap pixel. + // This is approximately 50 and 7 for current PDF writer, respectively. + + const double k1( 50 ); + const double k2( 7 ); + const Size aSize( aAlignedPaintRect.SSize() ); + const double Abitmap( k1/k2 * static_cast<double>(aSize.Width())*aSize.Height() ); + + pGraphicObj->DrawTiled( pOutDev, + aAlignedPaintRect.SVRect(), + aGrf.SSize(), + Size( aPaintOffset.X(), aPaintOffset.Y() ), + std::max( 128, static_cast<int>( sqrt(sqrt( Abitmap)) + .5 ) ) ); + } + // reset clipping at output device + pOutDev->Pop(); + // set <bDraw> and <bRetouche> to false, indicating that background + // graphic and background are already drawn. + bDraw = bRetouche = false; + } + break; + + case GPOS_NONE: + bDraw = false; + break; + + default: OSL_ENSURE( !pOutDev, "new Graphic position?" ); + } + + /// init variable <bGrfBackgrdAlreadDrawn> to indicate, if background of + /// graphic is already drawn or not. + bool bGrfBackgrdAlreadyDrawn = false; + if ( bRetouche ) + { + pOutDev->Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + pOutDev->SetLineColor(); + + // check, if an existing background graphic (not filling the complete + // background) is transparent drawn and the background color is + // "no fill" respectively "auto fill", if background transparency + // has to be considered. + // If YES, memorize transparency of background graphic. + // check also, if background graphic bitmap is transparent. + bool bTransparentGrfWithNoFillBackgrd = false; + sal_Int32 nGrfTransparency = 0; + bool bGrfIsTransparent = false; + if ( (ePos != GPOS_NONE) && + (ePos != GPOS_TILED) && (ePos != GPOS_AREA) + ) + { + GraphicObject *pGrf = const_cast<GraphicObject*>(pBrush->GetGraphicObject()); + if ( bConsiderBackgroundTransparency ) + { + GraphicAttr aGrfAttr = pGrf->GetAttr(); + if ( (aGrfAttr.GetTransparency() != 0) && + (pBrush->GetColor() == COL_TRANSPARENT) + ) + { + bTransparentGrfWithNoFillBackgrd = true; + nGrfTransparency = aGrfAttr.GetTransparency(); + } + } + if ( pGrf->IsTransparent() ) + { + bGrfIsTransparent = true; + } + } + + // to get color of brush, check background color against COL_TRANSPARENT ("no fill"/"auto fill") + // instead of checking, if transparency is not set. + const Color aColor( pBrush && + ( (pBrush->GetColor() != COL_TRANSPARENT) || + gProp.bSFlyMetafile ) + ? pBrush->GetColor() + : aGlobalRetoucheColor ); + + // determine, if background region have to be + // drawn transparent. + // background region has to be drawn transparent, if + // background transparency have to be considered + // AND + // ( background color is transparent OR + // background graphic is transparent and background color is "no fill" + // ) + + enum DrawStyle { + Default, + Transparent, + } eDrawStyle = Default; + + if (bConsiderBackgroundTransparency && + ( ( aColor.GetTransparency() != 0) || + bTransparentGrfWithNoFillBackgrd ) ) + { + eDrawStyle = Transparent; + } + + // #i75614# reset draw mode in high contrast mode in order to get fill color set + const DrawModeFlags nOldDrawMode = pOutDev->GetDrawMode(); + if ( gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pOutDev->SetDrawMode( DrawModeFlags::Default ); + } + + // If background region has to be drawn transparent, set only the RGB values of the background color as + // the fill color for the output device. + switch (eDrawStyle) + { + case Transparent: + { + if( pOutDev->GetFillColor() != aColor.GetRGBColor() ) + pOutDev->SetFillColor( aColor.GetRGBColor() ); + break; + } + default: + { + if( pOutDev->GetFillColor() != aColor ) + pOutDev->SetFillColor( aColor ); + break; + } + } + + // #i75614# + // restore draw mode + pOutDev->SetDrawMode( nOldDrawMode ); + + switch (eDrawStyle) + { + case Transparent: + { + // background region have to be drawn transparent. + // Thus, create a poly-polygon from the region and draw it with + // the corresponding transparency percent. + tools::PolyPolygon aDrawPoly( rOut.SVRect() ); + if ( aGrf.HasArea() ) + { + if ( !bGrfIsTransparent ) + { + // subtract area of background graphic from draw area + // Consider only that part of the graphic area that is overlapping with draw area. + SwRect aTmpGrf = aGrf; + aTmpGrf.Intersection( rOut ); + if ( aTmpGrf.HasArea() ) + { + tools::Polygon aGrfPoly( aTmpGrf.SVRect() ); + aDrawPoly.Insert( aGrfPoly ); + } + } + else + bGrfBackgrdAlreadyDrawn = true; + } + // calculate transparency percent: + // ( <transparency value[0x01..0xFF]>*100 + 0x7F ) / 0xFF + // If there is a background graphic with a background color "no fill"/"auto fill", + // the transparency value is taken from the background graphic, + // otherwise take the transparency value from the color. + sal_Int8 nTransparencyPercent = static_cast<sal_Int8>( + (( bTransparentGrfWithNoFillBackgrd ? nGrfTransparency : aColor.GetTransparency() + )*100 + 0x7F)/0xFF); + // draw poly-polygon transparent + pOutDev->DrawTransparent( aDrawPoly, nTransparencyPercent ); + + break; + } + case Default: + default: + { + SwRegionRects aRegion( rOut, 4 ); + if ( !bGrfIsTransparent ) + aRegion -= aGrf; + else + bGrfBackgrdAlreadyDrawn = true; + // loop rectangles of background region, which has to be drawn + for( size_t i = 0; i < aRegion.size(); ++i ) + { + pOutDev->DrawRect( aRegion[i].SVRect() ); + } + } + } + pOutDev ->Pop(); + } + + if( bDraw && aGrf.IsOver( rOut ) ) + lcl_DrawGraphic( *pBrush, pOutDev, rSh, aGrf, rOut, bGrfNum, gProp, + bGrfBackgrdAlreadyDrawn ); + + if( bReplaceGrfNum ) + { + const BitmapEx& rBmp = rSh.GetReplacementBitmap(false); + vcl::Font aTmp( pOutDev->GetFont() ); + Graphic::DrawEx(pOutDev, OUString(), aTmp, rBmp, rOrg.Pos(), rOrg.SSize()); + } +} + +/** + * Local helper for SwRootFrame::PaintSwFrame(..) - Adjust given rectangle to pixel size + * + * By OD at 27.09.2002 for #103636# + * In order to avoid paint errors caused by multiple alignments (e.g. ::SwAlignRect(..)) + * and other changes to the to be painted rectangle, this method is called for the + * rectangle to be painted in order to adjust it to the pixel it is overlapping +*/ +static void lcl_AdjustRectToPixelSize( SwRect& io_aSwRect, const vcl::RenderContext &aOut ) +{ + // local constant object of class <Size> to determine number of Twips + // representing a pixel. + const Size aTwipToPxSize( aOut.PixelToLogic( Size( 1,1 )) ); + + // local object of class <Rectangle> in Twip coordinates + // calculated from given rectangle aligned to pixel centers. + const tools::Rectangle aPxCenterRect = aOut.PixelToLogic( + aOut.LogicToPixel( io_aSwRect.SVRect() ) ); + + // local constant object of class <Rectangle> representing given rectangle + // in pixel. + const tools::Rectangle aOrgPxRect = aOut.LogicToPixel( io_aSwRect.SVRect() ); + + // calculate adjusted rectangle from pixel centered rectangle. + // Due to rounding differences <aPxCenterRect> doesn't exactly represents + // the Twip-centers. Thus, adjust borders by half of pixel width/height plus 1. + // Afterwards, adjust calculated Twip-positions of the all borders. + tools::Rectangle aSizedRect = aPxCenterRect; + aSizedRect.AdjustLeft( -(aTwipToPxSize.Width()/2 + 1) ); + aSizedRect.AdjustRight( aTwipToPxSize.Width()/2 + 1 ); + aSizedRect.AdjustTop( -(aTwipToPxSize.Height()/2 + 1) ); + aSizedRect.AdjustBottom(aTwipToPxSize.Height()/2 + 1); + + // adjust left() + while ( aOut.LogicToPixel(aSizedRect).Left() < aOrgPxRect.Left() ) + { + aSizedRect.AdjustLeft( 1 ); + } + // adjust right() + while ( aOut.LogicToPixel(aSizedRect).Right() > aOrgPxRect.Right() ) + { + aSizedRect.AdjustRight( -1 ); + } + // adjust top() + while ( aOut.LogicToPixel(aSizedRect).Top() < aOrgPxRect.Top() ) + { + aSizedRect.AdjustTop( 1 ); + } + // adjust bottom() + while ( aOut.LogicToPixel(aSizedRect).Bottom() > aOrgPxRect.Bottom() ) + { + aSizedRect.AdjustBottom( -1 ); + } + + io_aSwRect = SwRect( aSizedRect ); + +#if OSL_DEBUG_LEVEL > 0 + tools::Rectangle aTestOrgPxRect = aOut.LogicToPixel( io_aSwRect.SVRect() ); + tools::Rectangle aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect == aTestNewPxRect, + "Error in lcl_AlignRectToPixelSize(..): Adjusted rectangle has incorrect position or size"); + // check Left() + aSizedRect.AdjustLeft( -1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Left() >= (aTestNewPxRect.Left()+1), + "Error in lcl_AlignRectToPixelSize(..): Left() not correct adjusted"); + aSizedRect.AdjustLeft( 1 ); + // check Right() + aSizedRect.AdjustRight( 1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Right() <= (aTestNewPxRect.Right()-1), + "Error in lcl_AlignRectToPixelSize(..): Right() not correct adjusted"); + aSizedRect.AdjustRight( -1 ); + // check Top() + aSizedRect.AdjustTop( -1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Top() >= (aTestNewPxRect.Top()+1), + "Error in lcl_AlignRectToPixelSize(..): Top() not correct adjusted"); + aSizedRect.AdjustTop( 1 ); + // check Bottom() + aSizedRect.AdjustBottom( 1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Bottom() <= (aTestNewPxRect.Bottom()-1), + "Error in lcl_AlignRectToPixelSize(..): Bottom() not correct adjusted"); + aSizedRect.AdjustBottom( -1 ); +#endif +} + +// FUNCTIONS USED FOR COLLAPSING TABLE BORDER LINES START + +namespace { + +struct SwLineEntry +{ + SwTwips mnKey; + SwTwips mnStartPos; + SwTwips mnEndPos; + + svx::frame::Style maAttribute; + + enum OverlapType { NO_OVERLAP, OVERLAP1, OVERLAP2, OVERLAP3 }; + +public: + SwLineEntry( SwTwips nKey, + SwTwips nStartPos, + SwTwips nEndPos, + const svx::frame::Style& rAttribute ); + + OverlapType Overlaps( const SwLineEntry& rComp ) const; +}; + +} + +SwLineEntry::SwLineEntry( SwTwips nKey, + SwTwips nStartPos, + SwTwips nEndPos, + const svx::frame::Style& rAttribute ) + : mnKey( nKey ), + mnStartPos( nStartPos ), + mnEndPos( nEndPos ), + maAttribute( rAttribute ) +{ +} + +/* + + 1. ---------- rOld + ---------- rNew + + 2. ---------- rOld + ------------- rNew + + 3. ------- rOld + ------------- rNew + + 4. ------------- rOld + ---------- rNew + + 5. ---------- rOld + ---- rNew + + 6. ---------- rOld + ---------- rNew + + 7. ------------- rOld + ---------- rNew + + 8. ---------- rOld + ------------- rNew + + 9. ---------- rOld + ---------- rNew +*/ + +SwLineEntry::OverlapType SwLineEntry::Overlaps( const SwLineEntry& rNew ) const +{ + SwLineEntry::OverlapType eRet = OVERLAP3; + + if ( mnStartPos >= rNew.mnEndPos || mnEndPos <= rNew.mnStartPos ) + eRet = NO_OVERLAP; + + // 1, 2, 3 + else if ( mnEndPos < rNew.mnEndPos ) + eRet = OVERLAP1; + + // 4, 5, 6, 7 + else if (mnStartPos <= rNew.mnStartPos) + eRet = OVERLAP2; + + // 8, 9 + return eRet; +} + +namespace { + +struct lt_SwLineEntry +{ + bool operator()( const SwLineEntry& e1, const SwLineEntry& e2 ) const + { + return e1.mnStartPos < e2.mnStartPos; + } +}; + +} + +typedef std::set< SwLineEntry, lt_SwLineEntry > SwLineEntrySet; +typedef std::map< SwTwips, SwLineEntrySet > SwLineEntryMap; + +namespace { + +class SwTabFramePainter +{ + SwLineEntryMap maVertLines; + SwLineEntryMap maHoriLines; + const SwTabFrame& mrTabFrame; + + void Insert( SwLineEntry&, bool bHori ); + void Insert(const SwFrame& rFrame, const SvxBoxItem& rBoxItem, const SwRect &rPaintArea); + void HandleFrame(const SwLayoutFrame& rFrame, const SwRect& rPaintArea); + void FindStylesForLine( const Point&, + const Point&, + svx::frame::Style*, + bool bHori ) const; + +public: + explicit SwTabFramePainter( const SwTabFrame& rTabFrame ); + + void PaintLines( OutputDevice& rDev, const SwRect& rRect ) const; +}; + +} + +SwTabFramePainter::SwTabFramePainter( const SwTabFrame& rTabFrame ) + : mrTabFrame( rTabFrame ) +{ + SwRect aPaintArea = rTabFrame.GetUpper()->GetPaintArea(); + HandleFrame(rTabFrame, aPaintArea); +} + +void SwTabFramePainter::HandleFrame(const SwLayoutFrame& rLayoutFrame, const SwRect& rPaintArea) +{ + // Add border lines of cell frames. Skip covered cells. Skip cells + // in special row span row, which do not have a negative row span: + if ( rLayoutFrame.IsCellFrame() && !rLayoutFrame.IsCoveredCell() ) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(&rLayoutFrame); + const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pThisCell->GetUpper()); + const long nRowSpan = pThisCell->GetTabBox()->getRowSpan(); + if ( !pRowFrame->IsRowSpanLine() || nRowSpan > 1 || nRowSpan < -1 ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), &rLayoutFrame ); + const SwBorderAttrs& rAttrs = *aAccess.Get(); + const SvxBoxItem& rBox = rAttrs.GetBox(); + Insert(rLayoutFrame, rBox, rPaintArea); + } + } + + // Recurse into lower layout frames, but do not recurse into lower tabframes. + const SwFrame* pLower = rLayoutFrame.Lower(); + while ( pLower ) + { + const SwLayoutFrame* pLowerLayFrame = dynamic_cast<const SwLayoutFrame*>(pLower); + if ( pLowerLayFrame && !pLowerLayFrame->IsTabFrame() ) + HandleFrame(*pLowerLayFrame, rPaintArea); + + pLower = pLower->GetNext(); + } +} + +void SwTabFramePainter::PaintLines(OutputDevice& rDev, const SwRect& rRect) const +{ + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, rDev ); + + SwLineEntryMap::const_iterator aIter = maHoriLines.begin(); + bool bHori = true; + + // color for subsidiary lines: + const Color& rCol( SwViewOption::GetTableBoundariesColor() ); + + // high contrast mode: + // overrides the color of non-subsidiary lines. + const Color* pHCColor = nullptr; + DrawModeFlags nOldDrawMode = rDev.GetDrawMode(); + if( gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pHCColor = &SwViewOption::GetFontColor(); + rDev.SetDrawMode( DrawModeFlags::Default ); + } + + const SwFrame* pUpper = mrTabFrame.GetUpper(); + SwRect aUpper( pUpper->getFramePrintArea() ); + aUpper.Pos() += pUpper->getFrameArea().Pos(); + SwRect aUpperAligned( aUpper ); + ::SwAlignRect( aUpperAligned, gProp.pSGlobalShell, &rDev ); + + // prepare SdrFrameBorderDataVector + std::shared_ptr<drawinglayer::primitive2d::SdrFrameBorderDataVector> aData( + std::make_shared<drawinglayer::primitive2d::SdrFrameBorderDataVector>()); + + while ( true ) + { + if ( bHori && aIter == maHoriLines.end() ) + { + aIter = maVertLines.begin(); + bHori = false; + } + + if ( !bHori && aIter == maVertLines.end() ) + break; + + const SwLineEntrySet& rEntrySet = (*aIter).second; + for (const SwLineEntry& rEntry : rEntrySet) + { + const svx::frame::Style& rEntryStyle( rEntry.maAttribute ); + + Point aStart, aEnd; + if ( bHori ) + { + aStart.setX( rEntry.mnStartPos ); + aStart.setY( rEntry.mnKey ); + aEnd.setX( rEntry.mnEndPos ); + aEnd.setY( rEntry.mnKey ); + } + else + { + aStart.setX( rEntry.mnKey ); + aStart.setY( rEntry.mnStartPos ); + aEnd.setX( rEntry.mnKey ); + aEnd.setY( rEntry.mnEndPos ); + } + + svx::frame::Style aStyles[ 7 ]; + aStyles[ 0 ] = rEntryStyle; + FindStylesForLine( aStart, aEnd, aStyles, bHori ); + SwRect aRepaintRect( aStart, aEnd ); + + // the repaint rectangle has to be moved a bit for the centered lines: + SwTwips nRepaintRectSize = !rEntryStyle.GetWidth() ? 1 : rEntryStyle.GetWidth(); + if ( bHori ) + { + aRepaintRect.Height( 2 * nRepaintRectSize ); + aRepaintRect.Pos().AdjustY( -nRepaintRectSize ); + + // To decide on visibility it is also necessary to expand the RepaintRect + // to left/right according existing BorderLine overlap matchings, else there + // will be repaint errors when scrolling in e.t TripleLine BorderLines. + // aStyles[1] == aLFromT, aStyles[3] == aLFromB, aStyles[4] == aRFromT, aStyles[6] == aRFromB + if(aStyles[1].IsUsed() || aStyles[3].IsUsed() || aStyles[4].IsUsed() || aStyles[6].IsUsed()) + { + const double fLineWidthMaxLeft(std::max(aStyles[1].GetWidth(), aStyles[3].GetWidth())); + const double fLineWidthMaxRight(std::max(aStyles[4].GetWidth(), aStyles[6].GetWidth())); + aRepaintRect.Width(aRepaintRect.Width() + (fLineWidthMaxLeft + fLineWidthMaxRight)); + aRepaintRect.Pos().AdjustX( -fLineWidthMaxLeft ); + } + } + else + { + aRepaintRect.Width( 2 * nRepaintRectSize ); + aRepaintRect.Pos().AdjustX( -nRepaintRectSize ); + + // Accordingly to horizontal case, but for top/bottom + // aStyles[3] == aTFromR, aStyles[1] == aTFromL, aStyles[6] == aBFromR, aStyles[4] == aBFromL + if(aStyles[3].IsUsed() || aStyles[1].IsUsed() || aStyles[6].IsUsed() || aStyles[4].IsUsed()) + { + const double fLineWidthMaxTop(std::max(aStyles[3].GetWidth(), aStyles[1].GetWidth())); + const double fLineWidthMaxBottom(std::max(aStyles[6].GetWidth(), aStyles[4].GetWidth())); + aRepaintRect.Height(aRepaintRect.Height() + (fLineWidthMaxTop + fLineWidthMaxBottom)); + aRepaintRect.Pos().AdjustY( -fLineWidthMaxTop ); + } + } + + if (!rRect.IsOver(aRepaintRect)) + { + continue; + } + + // subsidiary lines + const Color* pTmpColor = nullptr; + if (0 == aStyles[ 0 ].GetWidth()) + { + if (isTableBoundariesEnabled() && gProp.pSGlobalShell->GetWin()) + aStyles[ 0 ].Set( rCol, rCol, rCol, false, 1, 0, 0 ); + else + aStyles[0].SetType(SvxBorderLineStyle::NONE); + } + else + pTmpColor = pHCColor; + + // The (twip) positions will be adjusted to meet these requirements: + // 1. The y coordinates are located in the middle of the pixel grid + // 2. The x coordinated are located at the beginning of the pixel grid + // This is done, because the horizontal lines are painted "at + // beginning", whereas the vertical lines are painted "centered". + // By making the line sizes a multiple of one pixel size, we can + // assure that all lines having the same twip size have the same + // pixel size, independent of their position on the screen. + Point aPaintStart = rDev.PixelToLogic( rDev.LogicToPixel(aStart) ); + Point aPaintEnd = rDev.PixelToLogic( rDev.LogicToPixel(aEnd) ); + + if (gProp.pSGlobalShell->GetWin()) + { + // The table borders do not use SwAlignRect, but all the other frames do. + // Therefore we tweak the outer borders a bit to achieve that the outer + // borders match the subsidiary lines of the upper: + if (aStart.X() == aUpper.Left()) + aPaintStart.setX( aUpperAligned.Left() ); + else if (aStart.X() == aUpper.Right_()) + aPaintStart.setX( aUpperAligned.Right_() ); + if (aStart.Y() == aUpper.Top()) + aPaintStart.setY( aUpperAligned.Top() ); + else if (aStart.Y() == aUpper.Bottom_()) + aPaintStart.setY( aUpperAligned.Bottom_() ); + + if (aEnd.X() == aUpper.Left()) + aPaintEnd.setX( aUpperAligned.Left() ); + else if (aEnd.X() == aUpper.Right_()) + aPaintEnd.setX( aUpperAligned.Right_() ); + if (aEnd.Y() == aUpper.Top()) + aPaintEnd.setY( aUpperAligned.Top() ); + else if (aEnd.Y() == aUpper.Bottom_()) + aPaintEnd.setY( aUpperAligned.Bottom_() ); + } + + if(aStyles[0].IsUsed()) + { + if (bHori) + { + const basegfx::B2DPoint aOrigin(aPaintStart.X(), aPaintStart.Y()); + const basegfx::B2DVector aX(basegfx::B2DPoint(aPaintEnd.X(), aPaintEnd.Y()) - aOrigin); + + if(!aX.equalZero()) + { + const basegfx::B2DVector aY(basegfx::getNormalizedPerpendicular(aX)); + aData->emplace_back( + aOrigin, + aX, + aStyles[0], + pTmpColor); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + rInstance.addSdrConnectStyleData(true, aStyles[1], -aY, true); // aLFromT + rInstance.addSdrConnectStyleData(true, aStyles[2], -aX, true); // aLFromL + rInstance.addSdrConnectStyleData(true, aStyles[3], aY, false); // aLFromB + + rInstance.addSdrConnectStyleData(false, aStyles[4], -aY, true); // aRFromT + rInstance.addSdrConnectStyleData(false, aStyles[5], aX, false); // aRFromR + rInstance.addSdrConnectStyleData(false, aStyles[6], aY, false); // aRFromB + } + } + else // vertical + { + const basegfx::B2DPoint aOrigin(aPaintStart.X(), aPaintStart.Y()); + const basegfx::B2DVector aX(basegfx::B2DPoint(aPaintEnd.X(), aPaintEnd.Y()) - aOrigin); + + if(!aX.equalZero()) + { + const basegfx::B2DVector aY(basegfx::getNormalizedPerpendicular(aX)); + aData->emplace_back( + aOrigin, + aX, + aStyles[0], + pTmpColor); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + rInstance.addSdrConnectStyleData(true, aStyles[3], -aY, false); // aTFromR + rInstance.addSdrConnectStyleData(true, aStyles[2], -aX, true); // aTFromT + rInstance.addSdrConnectStyleData(true, aStyles[1], aY, true); // aTFromL + + rInstance.addSdrConnectStyleData(false, aStyles[6], -aY, false); // aBFromR + rInstance.addSdrConnectStyleData(false, aStyles[5], aX, false); // aBFromB + rInstance.addSdrConnectStyleData(false, aStyles[4], aY, true); // aBFromL + } + } + } + } + ++aIter; + } + + // create instance of SdrFrameBorderPrimitive2D if + // SdrFrameBorderDataVector is used + if(!aData->empty()) + { + drawinglayer::primitive2d::Primitive2DContainer aSequence; + aSequence.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SdrFrameBorderPrimitive2D( + aData, + true))); // force visualization to minimal one discrete unit (pixel) + // paint + mrTabFrame.ProcessPrimitives(aSequence); + } + + // restore output device: + rDev.SetDrawMode( nOldDrawMode ); +} + +/** + * Finds the lines that join the line defined by (StartPoint, EndPoint) in either + * StartPoint or Endpoint. The styles of these lines are required for DR's magic + * line painting functions + */ +void SwTabFramePainter::FindStylesForLine( const Point& rStartPoint, + const Point& rEndPoint, + svx::frame::Style* pStyles, + bool bHori ) const +{ + // pStyles[ 1 ] = bHori ? aLFromT : TFromL + // pStyles[ 2 ] = bHori ? aLFromL : TFromT, + // pStyles[ 3 ] = bHori ? aLFromB : TFromR, + // pStyles[ 4 ] = bHori ? aRFromT : BFromL, + // pStyles[ 5 ] = bHori ? aRFromR : BFromB, + // pStyles[ 6 ] = bHori ? aRFromB : BFromR, + + SwLineEntryMap::const_iterator aMapIter = maVertLines.find( rStartPoint.X() ); + OSL_ENSURE( aMapIter != maVertLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rVertSet = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rVertSet ) + { + if ( bHori ) + { + if ( rStartPoint.Y() == rEntry.mnStartPos ) + pStyles[ 3 ] = rEntry.maAttribute; + else if ( rStartPoint.Y() == rEntry.mnEndPos ) + pStyles[ 1 ] = rEntry.maAttribute; + } + else + { + if ( rStartPoint.Y() == rEntry.mnEndPos ) + pStyles[ 2 ] = rEntry.maAttribute; + else if ( rEndPoint.Y() == rEntry.mnStartPos ) + pStyles[ 5 ] = rEntry.maAttribute; + } + } + + aMapIter = maHoriLines.find( rStartPoint.Y() ); + OSL_ENSURE( aMapIter != maHoriLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rHoriSet = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rHoriSet ) + { + if ( bHori ) + { + if ( rStartPoint.X() == rEntry.mnEndPos ) + pStyles[ 2 ] = rEntry.maAttribute; + else if ( rEndPoint.X() == rEntry.mnStartPos ) + pStyles[ 5 ] = rEntry.maAttribute; + } + else + { + if ( rStartPoint.X() == rEntry.mnEndPos ) + pStyles[ 1 ] = rEntry.maAttribute; + else if ( rStartPoint.X() == rEntry.mnStartPos ) + pStyles[ 3 ] = rEntry.maAttribute; + } + } + + if ( bHori ) + { + aMapIter = maVertLines.find( rEndPoint.X() ); + OSL_ENSURE( aMapIter != maVertLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rVertSet2 = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rVertSet2 ) + { + if ( rEndPoint.Y() == rEntry.mnStartPos ) + pStyles[ 6 ] = rEntry.maAttribute; + else if ( rEndPoint.Y() == rEntry.mnEndPos ) + pStyles[ 4 ] = rEntry.maAttribute; + } + } + else + { + aMapIter = maHoriLines.find( rEndPoint.Y() ); + OSL_ENSURE( aMapIter != maHoriLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rHoriSet2 = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rHoriSet2 ) + { + if ( rEndPoint.X() == rEntry.mnEndPos ) + pStyles[ 4 ] = rEntry.maAttribute; + else if ( rEndPoint.X() == rEntry.mnStartPos ) + pStyles[ 6 ] = rEntry.maAttribute; + } + } +} + +/** + * Special case: #i9860# + * first line in follow table without repeated headlines + */ +static bool lcl_IsFirstRowInFollowTableWithoutRepeatedHeadlines( + SwTabFrame const& rTabFrame, SwFrame const& rFrame, SvxBoxItem const& rBoxItem) +{ + SwRowFrame const*const pThisRowFrame = + dynamic_cast<const SwRowFrame*>(rFrame.GetUpper()); + return (pThisRowFrame + && (pThisRowFrame->GetUpper() == &rTabFrame) + && rTabFrame.IsFollow() + && !rTabFrame.GetTable()->GetRowsToRepeat() + && ( !pThisRowFrame->GetPrev() + || static_cast<const SwRowFrame*>(pThisRowFrame->GetPrev()) + ->IsRowSpanLine()) + && !rBoxItem.GetTop() + && rBoxItem.GetBottom()); +} + +void SwTabFramePainter::Insert(const SwFrame& rFrame, const SvxBoxItem& rBoxItem, const SwRect& rPaintArea) +{ + // build 4 line entries for the 4 borders: + SwRect aBorderRect = rFrame.getFrameArea(); + + aBorderRect.Intersection(rPaintArea); + + bool const bBottomAsTop(lcl_IsFirstRowInFollowTableWithoutRepeatedHeadlines( + mrTabFrame, rFrame, rBoxItem)); + bool const bVert = mrTabFrame.IsVertical(); + bool const bR2L = mrTabFrame.IsRightToLeft(); + + bool bWordTableCell = false; + SwViewShell* pShell = rFrame.getRootFrame()->GetCurrShell(); + if (pShell) + { + const IDocumentSettingAccess& rIDSA = pShell->GetDoc()->getIDocumentSettingAccess(); + bWordTableCell = rIDSA.get(DocumentSettingId::TABLE_ROW_KEEP); + } + + // no scaling needed, it's all in the primitives and the target device + svx::frame::Style aL(rBoxItem.GetLeft(), 1.0); + aL.SetWordTableCell(bWordTableCell); + svx::frame::Style aR(rBoxItem.GetRight(), 1.0); + aR.SetWordTableCell(bWordTableCell); + svx::frame::Style aT(rBoxItem.GetTop(), 1.0); + aT.SetWordTableCell(bWordTableCell); + svx::frame::Style aB(rBoxItem.GetBottom(), 1.0); + aB.SetWordTableCell(bWordTableCell); + + aR.MirrorSelf(); + aB.MirrorSelf(); + + const SwTwips nLeft = aBorderRect.Left_(); + const SwTwips nRight = aBorderRect.Right_(); + const SwTwips nTop = aBorderRect.Top_(); + const SwTwips nBottom = aBorderRect.Bottom_(); + + aL.SetRefMode( svx::frame::RefMode::Centered ); + aR.SetRefMode( svx::frame::RefMode::Centered ); + aT.SetRefMode( !bVert ? svx::frame::RefMode::Begin : svx::frame::RefMode::End ); + aB.SetRefMode( !bVert ? svx::frame::RefMode::Begin : svx::frame::RefMode::End ); + + SwLineEntry aLeft (nLeft, nTop, nBottom, + bVert ? aB : (bR2L ? aR : aL)); + SwLineEntry aRight (nRight, nTop, nBottom, + bVert ? (bBottomAsTop ? aB : aT) : (bR2L ? aL : aR)); + SwLineEntry aTop (nTop, nLeft, nRight, + bVert ? aL : (bBottomAsTop ? aB : aT)); + SwLineEntry aBottom(nBottom, nLeft, nRight, + bVert ? aR : aB); + + Insert( aLeft, false ); + Insert( aRight, false ); + Insert( aTop, true ); + Insert( aBottom, true ); +} + +void SwTabFramePainter::Insert( SwLineEntry& rNew, bool bHori ) +{ + // get all lines from structure, that have key entry of pLE + SwLineEntryMap* pLine2 = bHori ? &maHoriLines : &maVertLines; + const SwTwips nKey = rNew.mnKey; + SwLineEntryMap::iterator aMapIter = pLine2->find( nKey ); + + SwLineEntrySet* pLineSet = aMapIter != pLine2->end() ? &((*aMapIter).second) : nullptr; + if ( !pLineSet ) + { + SwLineEntrySet aNewSet; + (*pLine2)[ nKey ] = aNewSet; + pLineSet = &(*pLine2)[ nKey ]; + } + SwLineEntrySet::iterator aIter = pLineSet->begin(); + + while ( aIter != pLineSet->end() && rNew.mnStartPos < rNew.mnEndPos ) + { + const SwLineEntry& rOld = *aIter; + const SwLineEntry::OverlapType nOverlapType = rOld.Overlaps( rNew ); + + const svx::frame::Style& rOldAttr = rOld.maAttribute; + const svx::frame::Style& rNewAttr = rNew.maAttribute; + const svx::frame::Style& rCmpAttr = std::max(rNewAttr, rOldAttr); + + if ( SwLineEntry::OVERLAP1 == nOverlapType ) + { + OSL_ENSURE( rNew.mnStartPos >= rOld.mnStartPos, "Overlap type 3? How this?" ); + + // new left segment + const SwLineEntry aLeft( nKey, rOld.mnStartPos, rNew.mnStartPos, rOldAttr ); + + // new middle segment + const SwLineEntry aMiddle( nKey, rNew.mnStartPos, rOld.mnEndPos, rCmpAttr ); + + // new right segment + rNew.mnStartPos = rOld.mnEndPos; + + // update current lines set + pLineSet->erase( aIter ); + if ( aLeft.mnStartPos < aLeft.mnEndPos ) pLineSet->insert( aLeft ); + if ( aMiddle.mnStartPos < aMiddle.mnEndPos ) pLineSet->insert( aMiddle ); + + aIter = pLineSet->begin(); + + continue; // start over + } + else if ( SwLineEntry::OVERLAP2 == nOverlapType ) + { + // new left segment + const SwLineEntry aLeft( nKey, rOld.mnStartPos, rNew.mnStartPos, rOldAttr ); + + // new middle segment + const SwLineEntry aMiddle( nKey, rNew.mnStartPos, rNew.mnEndPos, rCmpAttr ); + + // new right segment + const SwLineEntry aRight( nKey, rNew.mnEndPos, rOld.mnEndPos, rOldAttr ); + + // update current lines set + pLineSet->erase( aIter ); + if ( aLeft.mnStartPos < aLeft.mnEndPos ) pLineSet->insert( aLeft ); + if ( aMiddle.mnStartPos < aMiddle.mnEndPos ) pLineSet->insert( aMiddle ); + if ( aRight.mnStartPos < aRight.mnEndPos ) pLineSet->insert( aRight ); + + rNew.mnStartPos = rNew.mnEndPos; // rNew should not be inserted! + + break; // we are finished + } + else if ( SwLineEntry::OVERLAP3 == nOverlapType ) + { + // new left segment + const SwLineEntry aLeft( nKey, rNew.mnStartPos, rOld.mnStartPos, rNewAttr ); + + // new middle segment + const SwLineEntry aMiddle( nKey, rOld.mnStartPos, rNew.mnEndPos, rCmpAttr ); + + // new right segment + const SwLineEntry aRight( nKey, rNew.mnEndPos, rOld.mnEndPos, rOldAttr ); + + // update current lines set + pLineSet->erase( aIter ); + if ( aLeft.mnStartPos < aLeft.mnEndPos ) pLineSet->insert( aLeft ); + if ( aMiddle.mnStartPos < aMiddle.mnEndPos ) pLineSet->insert( aMiddle ); + if ( aRight.mnStartPos < aRight.mnEndPos ) pLineSet->insert( aRight ); + + rNew.mnStartPos = rNew.mnEndPos; // rNew should not be inserted! + + break; // we are finished + } + + ++aIter; + } + + if ( rNew.mnStartPos < rNew.mnEndPos ) // insert rest + pLineSet->insert( rNew ); +} + +/** + * FUNCTIONS USED FOR COLLAPSING TABLE BORDER LINES END + * --> OD #i76669# + */ +namespace +{ + class SwViewObjectContactRedirector : public sdr::contact::ViewObjectContactRedirector + { + private: + const SwViewShell& mrViewShell; + + public: + explicit SwViewObjectContactRedirector( const SwViewShell& rSh ) + : mrViewShell( rSh ) + {}; + + virtual drawinglayer::primitive2d::Primitive2DContainer createRedirectedPrimitive2DSequence( + const sdr::contact::ViewObjectContact& rOriginal, + const sdr::contact::DisplayInfo& rDisplayInfo) override + { + bool bPaint( true ); + + SdrObject* pObj = rOriginal.GetViewContact().TryToGetSdrObject(); + if ( pObj ) + { + bPaint = SwFlyFrame::IsPaint( pObj, &mrViewShell ); + } + + if ( !bPaint ) + { + return drawinglayer::primitive2d::Primitive2DContainer(); + } + + return sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence( + rOriginal, rDisplayInfo ); + } + }; + +} // end of anonymous namespace +// <-- + +/** + * Paint once for every visible page which is touched by Rect + * + * 1. Paint borders and backgrounds + * 2. Paint the draw layer (frames and drawing objects) that is + * below the document (hell) + * 3. Paint the document content (text) + * 4. Paint the draw layer that is above the document +|*/ +void SwRootFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const pPrintData) const +{ + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "Lower of root is no page." ); + + PROTOCOL( this, PROT::FileInit, DbgAction::NONE, nullptr) + + bool bResetRootPaint = false; + SwViewShell *pSh = mpCurrShell; + + if ( pSh->GetWin() ) + { + if ( pSh->GetOut() == pSh->GetWin() && !pSh->GetWin()->IsVisible() ) + { + return; + } + if (SwRootFrame::s_isInPaint) + { + SwPaintQueue::Add( pSh, rRect ); + return; + } + } + else + SwRootFrame::s_isInPaint = bResetRootPaint = true; + + std::unique_ptr<SwSavePaintStatics> pStatics; + if ( gProp.pSGlobalShell ) + pStatics.reset(new SwSavePaintStatics()); + gProp.pSGlobalShell = pSh; + + if( !pSh->GetWin() ) + gProp.pSProgress = SfxProgress::GetActiveProgress( static_cast<SfxObjectShell*>(pSh->GetDoc()->GetDocShell()) ); + + ::SwCalcPixStatics( pSh->GetOut() ); + aGlobalRetoucheColor = pSh->Imp()->GetRetoucheColor(); + + // Copy rRect; for one, rRect could become dangling during the below action, and for another it + // needs to be copied to aRect anyway as that is modified further down below: + SwRect aRect( rRect ); + + //Trigger an action to clear things up if needed. + //Using this trick we can ensure that all values are valid in all paints - + //no problems, no special case(s). + // #i92745# + // Extend check on certain states of the 'current' <SwViewShell> instance to + // all existing <SwViewShell> instances. + bool bPerformLayoutAction( true ); + { + for(SwViewShell& rTmpViewShell : pSh->GetRingContainer()) + { + if ( rTmpViewShell.IsInEndAction() || + rTmpViewShell.IsPaintInProgress() || + ( rTmpViewShell.Imp()->IsAction() && + rTmpViewShell.Imp()->GetLayAction().IsActionInProgress() ) ) + { + bPerformLayoutAction = false; + } + + if(!bPerformLayoutAction) + break; + } + } + if ( bPerformLayoutAction ) + { + const_cast<SwRootFrame*>(this)->ResetTurbo(); + SwLayAction aAction( const_cast<SwRootFrame*>(this), pSh->Imp() ); + aAction.SetPaint( false ); + aAction.SetComplete( false ); + aAction.SetReschedule( gProp.pSProgress != nullptr ); + aAction.Action(&rRenderContext); + ResetTurboFlag(); + if ( !pSh->ActionPend() ) + pSh->Imp()->DelRegion(); + } + + aRect.Intersection( pSh->VisArea() ); + + const bool bExtraData = ::IsExtraData( GetFormat()->GetDoc() ); + + gProp.pSLines.reset(new SwLineRects); // Container for borders. + + // #104289#. During painting, something (OLE) can + // load the linguistic, which in turn can cause a reformat + // of the document. Dangerous! We better set this flag to + // avoid the reformat. + const bool bOldAction = IsCallbackActionEnabled(); + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( false ); + + const SwPageFrame *pPage = pSh->Imp()->GetFirstVisPage(&rRenderContext); + + // #126222. The positions of headers and footers of the previous + // pages have to be updated, else these headers and footers could + // get visible at a wrong position. + const SwPageFrame *pPageDeco = static_cast<const SwPageFrame*>(pPage->GetPrev()); + while (pPageDeco) + { + pPageDeco->PaintDecorators(); + OSL_ENSURE(!pPageDeco->GetPrev() || pPageDeco->GetPrev()->IsPageFrame(), + "Neighbour of page is not a page."); + pPageDeco = static_cast<const SwPageFrame*>(pPageDeco->GetPrev()); + } + + const bool bBookMode = gProp.pSGlobalShell->GetViewOptions()->IsViewLayoutBookMode(); + if ( bBookMode && pPage->GetPrev() && static_cast<const SwPageFrame*>(pPage->GetPrev())->IsEmptyPage() ) + pPage = static_cast<const SwPageFrame*>(pPage->GetPrev()); + + // #i68597# + const bool bGridPainting(pSh->GetWin() && pSh->Imp()->HasDrawView() && pSh->Imp()->GetDrawView()->IsGridVisible()); + + // Hide all page break controls before showing them again + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + const SwPageFrame* pHiddenPage = pPage; + while ( pHiddenPage->GetPrev() != nullptr ) + { + pHiddenPage = static_cast< const SwPageFrame* >( pHiddenPage->GetPrev() ); + SwFrameControlPtr pControl = rMngr.GetControl( FrameControlType::PageBreak, pHiddenPage ); + if ( pControl ) + pControl->ShowAll( false ); + } + } + + // #i76669# + SwViewObjectContactRedirector aSwRedirector( *pSh ); + + while ( pPage ) + { + const bool bPaintRightShadow = pPage->IsRightShadowNeeded(); + const bool bPaintLeftShadow = pPage->IsLeftShadowNeeded(); + const bool bRightSidebar = pPage->SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT; + + if ( !pPage->IsEmptyPage() ) + { + SwRect aPaintRect; + SwPageFrame::GetBorderAndShadowBoundRect( pPage->getFrameArea(), pSh, &rRenderContext, aPaintRect, + bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + + if ( aRect.IsOver( aPaintRect ) ) + { + if ( pSh->GetWin() ) + { + gProp.pSSubsLines.reset(new SwSubsRects); + gProp.pSSpecSubsLines.reset(new SwSubsRects); + } + gProp.pBLines.reset(new BorderLines); + + aPaintRect.Intersection_( aRect ); + + if ( bExtraData && + pSh->GetWin() && pSh->IsInEndAction() ) + { + // enlarge paint rectangle to complete page width, subtract + // current paint area and invalidate the resulting region. + SwRectFnSet aRectFnSet(pPage); + SwRect aPageRectTemp( aPaintRect ); + aRectFnSet.SetLeftAndWidth( aPageRectTemp, + aRectFnSet.GetLeft(pPage->getFrameArea()), + aRectFnSet.GetWidth(pPage->getFrameArea()) ); + aPageRectTemp.Intersection_( pSh->VisArea() ); + vcl::Region aPageRectRegion( aPageRectTemp.SVRect() ); + aPageRectRegion.Exclude( aPaintRect.SVRect() ); + pSh->GetWin()->Invalidate( aPageRectRegion, InvalidateFlags::Children ); + } + + // #i80793# + // enlarge paint rectangle for objects overlapping the same pixel + // in all cases and before the DrawingLayer overlay is initialized. + lcl_AdjustRectToPixelSize( aPaintRect, *(pSh->GetOut()) ); + + // #i68597# + // moved paint pre-process for DrawingLayer overlay here since the above + // code dependent from bExtraData may expand the PaintRect + { + // #i75172# if called from SwViewShell::ImplEndAction it should no longer + // really be used but handled by SwViewShell::ImplEndAction already + const vcl::Region aDLRegion(aPaintRect.SVRect()); + pSh->DLPrePaint2(aDLRegion); + } + + if(OUTDEV_WINDOW == gProp.pSGlobalShell->GetOut()->GetOutDevType()) + { + // changed method SwLayVout::Enter(..) + // 2nd parameter is no longer <const> and will be set to the + // rectangle the virtual output device is calculated from <aPaintRect>, + // if the virtual output is used. + s_pVout->Enter(pSh, aPaintRect, !s_isNoVirDev); + + // Adjust paint rectangle to pixel size + // Thus, all objects overlapping on pixel level with the unadjusted + // paint rectangle will be considered in the paint. + lcl_AdjustRectToPixelSize( aPaintRect, *(pSh->GetOut()) ); + } + + // maybe this can be put in the above scope. Since we are not sure, just leave it ATM + s_pVout->SetOrgRect( aPaintRect ); + + // determine background color of page for <PaintLayer> method + // calls, paint <hell> or <heaven> + const Color aPageBackgrdColor(pPage->GetDrawBackgrdColor()); + + pPage->PaintBaBo( aPaintRect, pPage ); + + if ( pSh->Imp()->HasDrawView() ) + { + gProp.pSLines->LockLines( true ); + const IDocumentDrawModelAccess& rIDDMA = pSh->getIDocumentDrawModelAccess(); + pSh->Imp()->PaintLayer( rIDDMA.GetHellId(), + pPrintData, + *pPage, pPage->getFrameArea(), + &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + gProp.pSLines->PaintLines( pSh->GetOut(), gProp ); + gProp.pSLines->LockLines( false ); + } + + if ( pSh->GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::BACKGROUND_PARA_OVER_DRAWINGS ) ) + pPage->PaintBaBo( aPaintRect, pPage, /*bOnlyTextBackground=*/true ); + + if( pSh->GetWin() ) + { + // collect sub-lines + pPage->RefreshSubsidiary( aPaintRect ); + // paint special sub-lines + gProp.pSSpecSubsLines->PaintSubsidiary( pSh->GetOut(), nullptr, gProp ); + } + + pPage->PaintSwFrame( rRenderContext, aPaintRect ); + + // no paint of page border and shadow, if writer is in place mode. + if( pSh->GetWin() && pSh->GetDoc()->GetDocShell() && + !pSh->GetDoc()->GetDocShell()->IsInPlaceActive() ) + { + SwPageFrame::PaintBorderAndShadow( pPage->getFrameArea(), pSh, bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + SwPageFrame::PaintNotesSidebar( pPage->getFrameArea(), pSh, pPage->GetPhyPageNum(), bRightSidebar); + } + + gProp.pSLines->PaintLines( pSh->GetOut(), gProp ); + if ( pSh->GetWin() ) + { + gProp.pSSubsLines->PaintSubsidiary( pSh->GetOut(), gProp.pSLines.get(), gProp ); + gProp.pSSubsLines.reset(); + gProp.pSSpecSubsLines.reset(); + } + // fdo#42750: delay painting these until after subsidiary lines + // fdo#45562: delay painting these until after hell layer + // fdo#47717: but do it before heaven layer + ProcessPrimitives(gProp.pBLines->GetBorderLines_Clear()); + + if ( pSh->Imp()->HasDrawView() ) + { + pSh->Imp()->PaintLayer( pSh->GetDoc()->getIDocumentDrawModelAccess().GetHeavenId(), + pPrintData, + *pPage, pPage->getFrameArea(), + &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + } + + if ( bExtraData ) + pPage->RefreshExtraData( aPaintRect ); + + gProp.pBLines.reset(); + s_pVout->Leave(); + + // #i68597# + // needed to move grid painting inside Begin/EndDrawLayer bounds and to change + // output rect for it accordingly + if(bGridPainting) + { + SdrPaintView* pPaintView = pSh->Imp()->GetDrawView(); + SdrPageView* pPageView = pPaintView->GetSdrPageView(); + pPageView->DrawPageViewGrid(*pSh->GetOut(), aPaintRect.SVRect(), SwViewOption::GetTextGridColor() ); + } + + // #i68597# + // moved paint post-process for DrawingLayer overlay here, see above + { + pSh->DLPostPaint2(true); + } + } + + pPage->PaintDecorators( ); + pPage->PaintBreak(); + } + else if ( bBookMode && pSh->GetWin() && !pSh->GetDoc()->GetDocShell()->IsInPlaceActive() ) + { + // paint empty page + SwRect aPaintRect; + SwRect aEmptyPageRect( pPage->getFrameArea() ); + + // code from vprint.cxx + const SwPageFrame& rFormatPage = pPage->GetFormatPage(); + aEmptyPageRect.SSize( rFormatPage.getFrameArea().SSize() ); + + SwPageFrame::GetBorderAndShadowBoundRect( aEmptyPageRect, pSh, &rRenderContext, aPaintRect, + bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + aPaintRect.Intersection_( aRect ); + + if ( aRect.IsOver( aEmptyPageRect ) ) + { + // #i75172# if called from SwViewShell::ImplEndAction it should no longer + // really be used but handled by SwViewShell::ImplEndAction already + { + const vcl::Region aDLRegion(aPaintRect.SVRect()); + pSh->DLPrePaint2(aDLRegion); + } + + if( pSh->GetOut()->GetFillColor() != aGlobalRetoucheColor ) + pSh->GetOut()->SetFillColor( aGlobalRetoucheColor ); + // No line color + pSh->GetOut()->SetLineColor(); + // Use aligned page rectangle + { + SwRect aTmpPageRect( aEmptyPageRect ); + ::SwAlignRect( aTmpPageRect, pSh, &rRenderContext ); + aEmptyPageRect = aTmpPageRect; + } + + pSh->GetOut()->DrawRect( aEmptyPageRect.SVRect() ); + + // paint empty page text + const vcl::Font& rEmptyPageFont = SwPageFrame::GetEmptyPageFont(); + const vcl::Font aOldFont( pSh->GetOut()->GetFont() ); + + pSh->GetOut()->SetFont( rEmptyPageFont ); + pSh->GetOut()->DrawText( aEmptyPageRect.SVRect(), SwResId( STR_EMPTYPAGE ), + DrawTextFlags::VCenter | + DrawTextFlags::Center | + DrawTextFlags::Clip ); + + pSh->GetOut()->SetFont( aOldFont ); + // paint shadow and border for empty page + SwPageFrame::PaintBorderAndShadow( aEmptyPageRect, pSh, bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + SwPageFrame::PaintNotesSidebar( aEmptyPageRect, pSh, pPage->GetPhyPageNum(), bRightSidebar); + + { + pSh->DLPostPaint2(true); + } + } + } + + OSL_ENSURE( !pPage->GetNext() || pPage->GetNext()->IsPageFrame(), + "Neighbour of page is not a page." ); + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + + gProp.pSLines.reset(); + + if ( bResetRootPaint ) + SwRootFrame::s_isInPaint = false; + if ( pStatics ) + pStatics.reset(); + else + { + gProp.pSProgress = nullptr; + gProp.pSGlobalShell = nullptr; + } + + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( bOldAction ); +} + +static void lcl_EmergencyFormatFootnoteCont( SwFootnoteContFrame *pCont ) +{ + vcl::RenderContext* pRenderContext = pCont->getRootFrame()->GetCurrShell()->GetOut(); + + //It's possible that the Cont will get destroyed. + SwContentFrame *pCnt = pCont->ContainsContent(); + while ( pCnt && pCnt->IsInFootnote() ) + { + pCnt->Calc(pRenderContext); + pCnt = pCnt->GetNextContentFrame(); + } +} + +namespace { + +class SwShortCut +{ + SwRectDist fnCheck; + long nLimit; +public: + SwShortCut( const SwFrame& rFrame, const SwRect& rRect ); + bool Stop( const SwRect& rRect ) const + { return (rRect.*fnCheck)( nLimit ) > 0; } +}; + +} + +SwShortCut::SwShortCut( const SwFrame& rFrame, const SwRect& rRect ) +{ + bool bVert = rFrame.IsVertical(); + bool bR2L = rFrame.IsRightToLeft(); + if( rFrame.IsNeighbourFrame() && bVert == bR2L ) + { + if( bVert ) + { + fnCheck = &SwRect::GetBottomDistance; + nLimit = rRect.Top(); + } + else + { + fnCheck = &SwRect::GetLeftDistance; + nLimit = rRect.Left() + rRect.Width(); + } + } + else if( bVert == rFrame.IsNeighbourFrame() ) + { + fnCheck = &SwRect::GetTopDistance; + nLimit = rRect.Top() + rRect.Height(); + } + else + { + if ( rFrame.IsVertLR() ) + { + fnCheck = &SwRect::GetLeftDistance; + nLimit = rRect.Right(); + } + else + { + fnCheck = &SwRect::GetRightDistance; + nLimit = rRect.Left(); + } + } +} + +void SwLayoutFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + // #i16816# tagged pdf support + Frame_Info aFrameInfo( *this ); + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, &aFrameInfo, nullptr, rRenderContext ); + + const SwFrame *pFrame = Lower(); + if ( !pFrame ) + return; + + SwFrameDeleteGuard g(const_cast<SwLayoutFrame*>(this)); // lock because Calc() and recursion + SwShortCut aShortCut( *pFrame, rRect ); + bool bCnt = pFrame->IsContentFrame(); + if ( bCnt ) + pFrame->Calc(&rRenderContext); + + if ( pFrame->IsFootnoteContFrame() ) + { + ::lcl_EmergencyFormatFootnoteCont( const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pFrame)) ); + pFrame = Lower(); + } + + const SwPageFrame *pPage = nullptr; + bool bWin = gProp.pSGlobalShell->GetWin() != nullptr; + if (comphelper::LibreOfficeKit::isTiledPainting()) + // Tiled rendering is similar to printing in this case: painting transparently multiple + // times will result in darker colors: avoid that. + bWin = false; + + while ( IsAnLower( pFrame ) ) + { + SwRect aPaintRect( pFrame->GetPaintArea() ); + if( aShortCut.Stop( aPaintRect ) ) + break; + if ( bCnt && gProp.pSProgress ) + SfxProgress::Reschedule(); + + //We need to retouch if a frame explicitly requests it. + //First do the retouch, because this could flatten the borders. + if ( pFrame->IsRetouche() ) + { + if ( pFrame->IsRetoucheFrame() && bWin && !pFrame->GetNext() ) + { + if ( !pPage ) + pPage = FindPageFrame(); + pFrame->Retouch( pPage, rRect ); + } + pFrame->ResetRetouche(); + } + + if ( rRect.IsOver( aPaintRect ) ) + { + if ( bCnt && pFrame->IsCompletePaint() && + !rRect.IsInside( aPaintRect ) && Application::AnyInput( VclInputFlags::KEYBOARD ) ) + { + //fix(8104): It may happen, that the processing wasn't complete + //but some parts of the paragraph were still repainted. + //This could lead to the situation, that other parts of the + //paragraph won't be repainted at all. The only solution seems + //to be an invalidation of the window. + //To not make it too severe the rectangle is limited by + //painting the desired part and only invalidating the + //remaining paragraph parts. + if ( aPaintRect.Left() == rRect.Left() && + aPaintRect.Right() == rRect.Right() ) + { + aPaintRect.Bottom( rRect.Top() - 1 ); + if ( aPaintRect.Height() > 0 ) + gProp.pSGlobalShell->InvalidateWindows(aPaintRect); + aPaintRect.Top( rRect.Bottom() + 1 ); + aPaintRect.Bottom( pFrame->getFrameArea().Bottom() ); + if ( aPaintRect.Height() > 0 ) + gProp.pSGlobalShell->InvalidateWindows(aPaintRect); + aPaintRect.Top( pFrame->getFrameArea().Top() ); + aPaintRect.Bottom( pFrame->getFrameArea().Bottom() ); + } + else + { + gProp.pSGlobalShell->InvalidateWindows( aPaintRect ); + pFrame = pFrame->GetNext(); + if ( pFrame ) + { + bCnt = pFrame->IsContentFrame(); + if ( bCnt ) + pFrame->Calc(&rRenderContext); + } + continue; + } + } + pFrame->ResetCompletePaint(); + aPaintRect.Intersection_( rRect ); + + pFrame->PaintSwFrame( rRenderContext, aPaintRect ); + + if ( Lower() && Lower()->IsColumnFrame() ) + { + //Paint the column separator line if needed. The page is + //responsible for the page frame - not the upper. + const SwFrameFormat *pFormat = GetUpper() && GetUpper()->IsPageFrame() + ? GetUpper()->GetFormat() + : GetFormat(); + const SwFormatCol &rCol = pFormat->GetCol(); + if ( rCol.GetLineAdj() != COLADJ_NONE ) + { + if ( !pPage ) + pPage = pFrame->FindPageFrame(); + + PaintColLines( aPaintRect, rCol, pPage ); + } + } + } + if ( !bCnt && pFrame->GetNext() && pFrame->GetNext()->IsFootnoteContFrame() ) + ::lcl_EmergencyFormatFootnoteCont( const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pFrame->GetNext())) ); + + pFrame = pFrame->GetNext(); + + if ( pFrame ) + { + bCnt = pFrame->IsContentFrame(); + if ( bCnt ) + pFrame->Calc(&rRenderContext); + } + } +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreateDashedIndicatorPrimitive( + const basegfx::B2DPoint& rStart, const basegfx::B2DPoint& rEnd, + basegfx::BColor aColor ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 1 ); + + std::vector< double > aStrokePattern; + basegfx::B2DPolygon aLinePolygon; + aLinePolygon.append(rStart); + aLinePolygon.append(rEnd); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if ( rSettings.GetHighContrastMode( ) ) + { + // Only a solid line in high contrast mode + aColor = rSettings.GetDialogTextColor().getBColor(); + } + else + { + // Get a color for the contrast + basegfx::BColor aHslLine = basegfx::utils::rgb2hsl( aColor ); + double nLuminance = aHslLine.getZ() * 2.5; + if ( nLuminance == 0 ) + nLuminance = 0.5; + else if ( nLuminance >= 1.0 ) + nLuminance = aHslLine.getZ() * 0.4; + aHslLine.setZ( nLuminance ); + const basegfx::BColor aOtherColor = basegfx::utils::hsl2rgb( aHslLine ); + + // Compute the plain line + drawinglayer::primitive2d::PolygonHairlinePrimitive2D * pPlainLine = + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aLinePolygon, aOtherColor ); + + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference( pPlainLine ); + + // Dashed line in twips + aStrokePattern.push_back( 40 ); + aStrokePattern.push_back( 40 ); + + aSeq.resize( 2 ); + } + + // Compute the dashed line primitive + drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D * pLine = + new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D ( + basegfx::B2DPolyPolygon( aLinePolygon ), + drawinglayer::attribute::LineAttribute( aColor ), + drawinglayer::attribute::StrokeAttribute( aStrokePattern ) ); + + aSeq[ aSeq.size( ) - 1 ] = drawinglayer::primitive2d::Primitive2DReference( pLine ); + + return aSeq; +} + +void SwPageFrame::PaintBreak( ) const +{ + if ( gProp.pSGlobalShell->GetOut()->GetOutDevType() != OUTDEV_PRINTER && + !gProp.pSGlobalShell->GetViewOptions()->IsPDFExport() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->IsPreview() ) + { + const SwFrame* pBodyFrame = Lower(); + while ( pBodyFrame && !pBodyFrame->IsBodyFrame() ) + pBodyFrame = pBodyFrame->GetNext(); + + if ( pBodyFrame ) + { + const SwLayoutFrame* pLayBody = static_cast< const SwLayoutFrame* >( pBodyFrame ); + const SwFlowFrame *pFlowFrame = pLayBody->ContainsContent(); + + // Test if the first node is a table + const SwFrame* pFirstFrame = pLayBody->Lower(); + if ( pFirstFrame && pFirstFrame->IsTabFrame() ) + pFlowFrame = static_cast< const SwTabFrame* >( pFirstFrame ); + + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + + if ( pFlowFrame && pFlowFrame->IsPageBreak( true ) ) + rMngr.SetPageBreakControl( this ); + else + rMngr.RemoveControlsByType( FrameControlType::PageBreak, this ); + } + } + SwLayoutFrame::PaintBreak( ); + } +} + +void SwColumnFrame::PaintBreak( ) const +{ + if ( gProp.pSGlobalShell->GetOut()->GetOutDevType() != OUTDEV_PRINTER && + !gProp.pSGlobalShell->GetViewOptions()->IsPDFExport() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->IsPreview() ) + { + const SwFrame* pBodyFrame = Lower(); + while ( pBodyFrame && !pBodyFrame->IsBodyFrame() ) + pBodyFrame = pBodyFrame->GetNext(); + + if ( pBodyFrame ) + { + const SwContentFrame *pCnt = static_cast< const SwLayoutFrame* >( pBodyFrame )->ContainsContent(); + if ( pCnt && pCnt->IsColBreak( true ) ) + { + // Paint the break only if: + // * Not in header footer edition, to avoid conflicts with the + // header/footer marker + // * Non-printing characters are shown, as this is more consistent + // with other formatting marks + if ( !gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Header ) && + !gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Footer ) && + gProp.pSGlobalShell->GetViewOptions()->IsLineBreak() ) + { + SwRect aRect( pCnt->getFramePrintArea() ); + aRect.Pos() += pCnt->getFrameArea().Pos(); + + // Draw the line + basegfx::B2DPoint aStart( double( aRect.Left() ), aRect.Top() ); + basegfx::B2DPoint aEnd( double( aRect.Right() ), aRect.Top() ); + double nWidth = aRect.Width(); + if ( IsVertical( ) ) + { + aStart = basegfx::B2DPoint( double( aRect.Right() ), double( aRect.Top() ) ); + aEnd = basegfx::B2DPoint( double( aRect.Right() ), double( aRect.Bottom() ) ); + nWidth = aRect.Height(); + } + + basegfx::BColor aLineColor = SwViewOption::GetPageBreakColor().getBColor(); + + drawinglayer::primitive2d::Primitive2DContainer aSeq = + lcl_CreateDashedIndicatorPrimitive( aStart, aEnd, aLineColor ); + + // Add the text above + OUString aBreakText = SwResId(STR_COLUMN_BREAK); + + basegfx::B2DVector aFontSize; + OutputDevice* pOut = gProp.pSGlobalShell->GetOut(); + vcl::Font aFont = pOut->GetSettings().GetStyleSettings().GetToolFont(); + aFont.SetFontHeight( 8 * 20 ); + pOut->SetFont( aFont ); + drawinglayer::attribute::FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontSize, aFont, IsRightToLeft(), false ); + + tools::Rectangle aTextRect; + pOut->GetTextBoundRect( aTextRect, aBreakText ); + long nTextOff = ( nWidth - aTextRect.GetWidth() ) / 2; + + basegfx::B2DHomMatrix aTextMatrix( basegfx::utils::createScaleTranslateB2DHomMatrix( + aFontSize.getX(), aFontSize.getY(), + aRect.Left() + nTextOff, aRect.Top() ) ); + if ( IsVertical() ) + { + aTextMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix ( + aFontSize.getX(), aFontSize.getY(), 0.0, M_PI_2, + aRect.Right(), aRect.Top() + nTextOff ); + } + + drawinglayer::primitive2d::TextSimplePortionPrimitive2D * pText = + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, + aBreakText, 0, aBreakText.getLength(), + std::vector< double >(), + aFontAttr, + lang::Locale(), + aLineColor ); + aSeq.push_back( drawinglayer::primitive2d::Primitive2DReference( pText ) ); + + ProcessPrimitives( aSeq ); + } + } + } + } +} + +void SwLayoutFrame::PaintBreak( ) const +{ + const SwFrame* pFrame = Lower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + static_cast< const SwLayoutFrame*>( pFrame )->PaintBreak( ); + pFrame = pFrame->GetNext(); + } +} + +void SwPageFrame::PaintDecorators( ) const +{ + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + + const SwLayoutFrame* pBody = FindBodyCont(); + if ( pBody ) + { + SwRect aBodyRect( pBody->getFrameArea() ); + + if ( gProp.pSGlobalShell->GetOut()->GetOutDevType() != OUTDEV_PRINTER && + !gProp.pSGlobalShell->GetViewOptions()->IsPDFExport() && + !gProp.pSGlobalShell->IsPreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->getBrowseMode() && + ( gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Header ) || + gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Footer ) ) ) + { + bool bRtl = AllSettings::GetLayoutRTL(); + const SwRect& rVisArea = gProp.pSGlobalShell->VisArea(); + long nXOff = std::min( aBodyRect.Right(), rVisArea.Right() ); + if ( bRtl ) + nXOff = std::max( aBodyRect.Left(), rVisArea.Left() ); + + // Header + if ( gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Header ) ) + { + const SwFrame* pHeaderFrame = Lower(); + if ( !pHeaderFrame->IsHeaderFrame() ) + pHeaderFrame = nullptr; + + long nHeaderYOff = aBodyRect.Top(); + Point nOutputOff = rEditWin.LogicToPixel( Point( nXOff, nHeaderYOff ) ); + rEditWin.GetFrameControlsManager().SetHeaderFooterControl( this, FrameControlType::Header, nOutputOff ); + } + + // Footer + if ( gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Footer ) ) + { + const SwFrame* pFootnoteContFrame = Lower(); + while ( pFootnoteContFrame ) + { + if ( pFootnoteContFrame->IsFootnoteContFrame() ) + aBodyRect.AddBottom( pFootnoteContFrame->getFrameArea().Bottom() - aBodyRect.Bottom() ); + pFootnoteContFrame = pFootnoteContFrame->GetNext(); + } + + long nFooterYOff = aBodyRect.Bottom(); + Point nOutputOff = rEditWin.LogicToPixel( Point( nXOff, nFooterYOff ) ); + rEditWin.GetFrameControlsManager().SetHeaderFooterControl( this, FrameControlType::Footer, nOutputOff ); + } + } + } + } +} + +/** + * For feature #99657# + * + * OD 12.08.2002 + * determines, if background of fly frame has to be drawn transparent + * declaration found in /core/inc/flyfrm.cxx + * + * OD 08.10.2002 #103898# - If the background of the fly frame itself is not + * transparent and the background is inherited from its parent/grandparent, + * the background brush, used for drawing, has to be investigated for transparency. + * + * @return true, if background is transparent drawn +*/ +bool SwFlyFrame::IsBackgroundTransparent() const +{ + bool bBackgroundTransparent = GetFormat()->IsBackgroundTransparent(); + if ( !bBackgroundTransparent && + GetFormat()->IsBackgroundBrushInherited() ) + { + const SvxBrushItem* pBackgrdBrush = nullptr; + const Color* pSectionTOXColor = nullptr; + SwRect aDummyRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( GetBackgroundBrush( aFillAttributes, pBackgrdBrush, pSectionTOXColor, aDummyRect, false, /*bConsiderTextBox=*/false) ) + { + if ( pSectionTOXColor && + (pSectionTOXColor->GetTransparency() != 0) && + (*pSectionTOXColor != COL_TRANSPARENT) ) + { + bBackgroundTransparent = true; + } + else if(aFillAttributes && aFillAttributes->isUsed()) + { + bBackgroundTransparent = aFillAttributes->isTransparent(); + } + else if ( pBackgrdBrush ) + { + if ( (pBackgrdBrush->GetColor().GetTransparency() != 0) && + (pBackgrdBrush->GetColor() != COL_TRANSPARENT) ) + { + bBackgroundTransparent = true; + } + else + { + const GraphicObject *pTmpGrf = + pBackgrdBrush->GetGraphicObject(); + if ( pTmpGrf && + (pTmpGrf->GetAttr().GetTransparency() != 0) + ) + { + bBackgroundTransparent = true; + } + } + } + } + } + + return bBackgroundTransparent; +}; + +bool SwFlyFrame::IsPaint( SdrObject *pObj, const SwViewShell *pSh ) +{ + SdrObjUserCall *pUserCall; + + if ( nullptr == ( pUserCall = GetUserCall(pObj) ) ) + return true; + + //Attribute dependent, don't paint for printer or Preview + bool bPaint = gProp.pSFlyOnlyDraw || + static_cast<SwContact*>(pUserCall)->GetFormat()->GetPrint().GetValue(); + if ( !bPaint ) + bPaint = pSh->GetWin() && !pSh->IsPreview(); + + if ( bPaint ) + { + //The paint may be prevented by the superior Flys. + SwFrame *pAnch = nullptr; + if ( dynamic_cast< const SwFlyDrawObj *>( pObj ) != nullptr ) // i#117962# + { + bPaint = false; + } + if ( dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwVirtFlyDrawObj*>(pObj)->GetFlyFrame(); + if ( gProp.pSFlyOnlyDraw && gProp.pSFlyOnlyDraw == pFly ) + return true; + + //Try to avoid displaying the intermediate stage, Flys which don't + //overlap with the page on which they are anchored won't be + //painted. + //HACK: exception: printing of frames in tables, those can overlap + //a page once in a while when dealing with oversized tables (HTML). + SwPageFrame *pPage = pFly->FindPageFrame(); + if ( pPage && pPage->getFrameArea().IsOver( pFly->getFrameArea() ) ) + { + pAnch = pFly->AnchorFrame(); + } + + } + else + { + // Consider 'virtual' drawing objects + SwDrawContact* pDrawContact = dynamic_cast<SwDrawContact*>(pUserCall); + pAnch = pDrawContact ? pDrawContact->GetAnchorFrame(pObj) : nullptr; + if ( pAnch ) + { + if ( !pAnch->isFrameAreaPositionValid() ) + pAnch = nullptr; + else if ( pSh->GetOut() == pSh->getIDocumentDeviceAccess().getPrinter( false )) + { + //HACK: we have to omit some of the objects for printing, + //otherwise they would be printed twice. + //The objects should get printed if the TableHack is active + //right now. Afterwards they must not be printed if the + //page over which they float position wise gets printed. + const SwPageFrame *pPage = pAnch->FindPageFrame(); + if ( !pPage->getFrameArea().IsOver( pObj->GetCurrentBoundRect() ) ) + pAnch = nullptr; + } + } + else + { + if ( dynamic_cast< const SdrObjGroup *>( pObj ) == nullptr ) + { + OSL_FAIL( "<SwFlyFrame::IsPaint(..)> - paint of drawing object without anchor frame!?" ); + } + } + } + if ( pAnch ) + { + if ( pAnch->IsInFly() ) + bPaint = SwFlyFrame::IsPaint( pAnch->FindFlyFrame()->GetVirtDrawObj(), + pSh ); + else if ( gProp.pSFlyOnlyDraw ) + bPaint = false; + } + else + bPaint = false; + } + return bPaint; +} + +void SwCellFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + if ( GetLayoutRowSpan() >= 1 ) + SwLayoutFrame::PaintSwFrame( rRenderContext, rRect ); +} + +namespace { + +struct BorderLinesGuard +{ + explicit BorderLinesGuard() : m_pBorderLines(std::move(gProp.pBLines)) + { + gProp.pBLines.reset(new BorderLines); + } + ~BorderLinesGuard() + { + gProp.pBLines = std::move(m_pBorderLines); + } +private: + std::unique_ptr<BorderLines> m_pBorderLines; +}; + +} + +void SwFlyFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + //optimize thumbnail generation and store procedure to improve odt saving performance, #i120030# + SwViewShell *pShell = getRootFrame()->GetCurrShell(); + if (pShell && pShell->GetDoc() && pShell->GetDoc()->GetDocShell()) + { + bool bInGenerateThumbnail = pShell->GetDoc()->GetDocShell()->IsInGenerateAndStoreThumbnail(); + if (bInGenerateThumbnail) + { + const SwRect& aVisRect = pShell->VisArea(); + if (!aVisRect.IsOver(getFrameArea())) + return; + } + } + + //because of the overlapping of frames and drawing objects the flys have to + //paint their borders (and those of the internal ones) directly. + //e.g. #33066# + gProp.pSLines->LockLines(true); + BorderLinesGuard blg; // this should not paint borders added from PaintBaBo + + SwRect aRect( rRect ); + aRect.Intersection_( getFrameArea() ); + + rRenderContext.Push( PushFlags::CLIPREGION ); + rRenderContext.SetClipRegion(); + const SwPageFrame* pPage = FindPageFrame(); + + const SwNoTextFrame *pNoText = Lower() && Lower()->IsNoTextFrame() + ? static_cast<const SwNoTextFrame*>(Lower()) : nullptr; + + bool bIsChart = false; //#i102950# don't paint additional borders for charts + //check whether we have a chart + if(pNoText) + { + const SwNoTextNode* pNoTNd = dynamic_cast<const SwNoTextNode*>(pNoText->GetNode()); + if( pNoTNd ) + { + SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTNd->GetOLENode()); + if( pOLENd && pOLENd->GetOLEObj().GetObject().IsChart() ) + bIsChart = true; + } + } + + { + bool bContour = GetFormat()->GetSurround().IsContour(); + tools::PolyPolygon aPoly; + if ( bContour ) + { + // add 2nd parameter with value <true> + // to indicate that method is called for paint in order to avoid + // load of the intrinsic graphic. + bContour = GetContour( aPoly, true ); + } + + // #i47804# - distinguish complete background paint + // and margin paint. + // paint complete background for Writer text fly frames + bool bPaintCompleteBack( !pNoText ); + // paint complete background for transparent graphic and contour, + // if own background color exists. + const bool bIsGraphicTransparent = pNoText && pNoText->IsTransparent(); + if ( !bPaintCompleteBack && + ( bIsGraphicTransparent|| bContour ) ) + { + const SwFrameFormat* pSwFrameFormat = dynamic_cast< const SwFrameFormat* >(GetFormat()); + + if (pSwFrameFormat && pSwFrameFormat->supportsFullDrawingLayerFillAttributeSet()) + { + // check for transparency + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes(pSwFrameFormat->getSdrAllFillAttributesHelper()); + + // check if the new fill attributes are used + if(aFillAttributes && aFillAttributes->isUsed()) + { + bPaintCompleteBack = true; + } + } + else + { + std::unique_ptr<SvxBrushItem> aBack = GetFormat()->makeBackgroundBrushItem(); + // to determine, if background has to be painted, by checking, if + // background color is not COL_TRANSPARENT ("no fill"/"auto fill") + // or a background graphic exists. + bPaintCompleteBack = aBack && + ((aBack->GetColor() != COL_TRANSPARENT) || + aBack->GetGraphicPos() != GPOS_NONE); + } + } + // paint of margin needed. + const bool bPaintMarginOnly( !bPaintCompleteBack && + getFramePrintArea().SSize() != getFrameArea().SSize() ); + + // #i47804# - paint background of parent fly frame + // for transparent graphics in layer Hell, if parent fly frame isn't + // in layer Hell. It's only painted the intersection between the + // parent fly frame area and the paint area <aRect> + const IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + + if (bIsGraphicTransparent && + GetFormat()->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::SUBTRACT_FLYS) && + GetVirtDrawObj()->GetLayer() == rIDDMA.GetHellId() && + GetAnchorFrame()->FindFlyFrame() ) + { + const SwFlyFrame* pParentFlyFrame = GetAnchorFrame()->FindFlyFrame(); + if ( pParentFlyFrame->GetDrawObj()->GetLayer() != + rIDDMA.GetHellId() ) + { + SwFlyFrame* pOldRet = gProp.pSRetoucheFly2; + gProp.pSRetoucheFly2 = const_cast<SwFlyFrame*>(this); + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pParentFlyFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + SwRect aPaintRect( aRect ); + aPaintRect.Intersection_( pParentFlyFrame->getFrameArea() ); + pParentFlyFrame->PaintSwFrameBackground( aPaintRect, pPage, rAttrs ); + + gProp.pSRetoucheFly2 = pOldRet; + } + } + + if ( bPaintCompleteBack || bPaintMarginOnly ) + { + //#24926# JP 01.02.96, PaintBaBo is here partially so PaintSwFrameShadowAndBorder + //receives the original Rect but PaintSwFrameBackground only the limited + //one. + + rRenderContext.Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + rRenderContext.SetLineColor(); + + pPage = FindPageFrame(); + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(this) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // paint background + { + SwRegionRects aRegion( aRect ); + // #i80822# + // suppress painting of background in printing area for + // non-transparent graphics. + if ( bPaintMarginOnly || + ( pNoText && !bIsGraphicTransparent ) ) + { + //What we actually want to paint is the small stripe between + //PrtArea and outer border. + SwRect aTmp( getFramePrintArea() ); aTmp += getFrameArea().Pos(); + aRegion -= aTmp; + } + if ( bContour ) + { + rRenderContext.Push(); + // #i80822# + // apply clip region under the same conditions, which are + // used in <SwNoTextFrame::PaintSwFrame(..)> to set the clip region + // for painting the graphic/OLE. Thus, the clip region is + // also applied for the PDF export. + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + if ( !rRenderContext.GetConnectMetaFile() || !pSh || !pSh->GetWin() ) + { + rRenderContext.SetClipRegion(vcl::Region(aPoly)); + } + + for ( size_t i = 0; i < aRegion.size(); ++i ) + { + PaintSwFrameBackground( aRegion[i], pPage, rAttrs, false, true ); + } + + rRenderContext.Pop(); + } + else + { + for ( size_t i = 0; i < aRegion.size(); ++i ) + { + PaintSwFrameBackground( aRegion[i], pPage, rAttrs, false, true ); + } + } + } + + // paint border before painting background + PaintSwFrameShadowAndBorder(rRect, pPage, rAttrs); + + rRenderContext.Pop(); + } + } + + // fly frame will paint it's subsidiary lines and + // the subsidiary lines of its lowers on its own, due to overlapping with + // other fly frames or other objects. + if( gProp.pSGlobalShell->GetWin() + && !bIsChart ) //#i102950# don't paint additional borders for charts + { + bool bSubsLineRectsCreated; + if ( gProp.pSSubsLines ) + { + // Lock already existing subsidiary lines + gProp.pSSubsLines->LockLines( true ); + bSubsLineRectsCreated = false; + } + else + { + // create new subsidiary lines + gProp.pSSubsLines.reset(new SwSubsRects); + bSubsLineRectsCreated = true; + } + + bool bSpecSubsLineRectsCreated; + if ( gProp.pSSpecSubsLines ) + { + // Lock already existing special subsidiary lines + gProp.pSSpecSubsLines->LockLines( true ); + bSpecSubsLineRectsCreated = false; + } + else + { + // create new special subsidiary lines + gProp.pSSpecSubsLines.reset(new SwSubsRects); + bSpecSubsLineRectsCreated = true; + } + // Add subsidiary lines of fly frame and its lowers + RefreshLaySubsidiary( pPage, aRect ); + // paint subsidiary lines of fly frame and its lowers + gProp.pSSpecSubsLines->PaintSubsidiary( &rRenderContext, nullptr, gProp ); + gProp.pSSubsLines->PaintSubsidiary(&rRenderContext, gProp.pSLines.get(), gProp); + if ( !bSubsLineRectsCreated ) + // unlock subsidiary lines + gProp.pSSubsLines->LockLines( false ); + else + { + // delete created subsidiary lines container + gProp.pSSubsLines.reset(); + } + + if ( !bSpecSubsLineRectsCreated ) + // unlock special subsidiary lines + gProp.pSSpecSubsLines->LockLines( false ); + else + { + // delete created special subsidiary lines container + gProp.pSSpecSubsLines.reset(); + } + } + + SwLayoutFrame::PaintSwFrame( rRenderContext, aRect ); + + Validate(); + + // first paint lines added by fly frame paint + // and then unlock other lines. + gProp.pSLines->PaintLines( &rRenderContext, gProp ); + gProp.pSLines->LockLines( false ); + // have to paint frame borders added in heaven layer here... + ProcessPrimitives(gProp.pBLines->GetBorderLines_Clear()); + + PaintDecorators(); + + rRenderContext.Pop(); + + if ( gProp.pSProgress && pNoText ) + SfxProgress::Reschedule(); +} + +void SwFlyFrame::PaintDecorators() const +{ + // Show the un-float button + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + UpdateUnfloatButton(pWrtSh, IsShowUnfloatButton(pWrtSh)); + } +} + +void SwTabFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + const SwViewOption* pViewOption = gProp.pSGlobalShell->GetViewOptions(); + if (pViewOption->IsTable()) + { + // #i29550# + if ( IsCollapsingBorders() ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(this) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // paint shadow + if ( rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE ) + { + SwRect aRect; + ::lcl_CalcBorderRect( aRect, this, rAttrs, true, gProp ); + PaintShadow( rRect, aRect, rAttrs ); + } + + SwTabFramePainter aHelper(*this); + aHelper.PaintLines(rRenderContext, rRect); + } + + SwLayoutFrame::PaintSwFrame( rRenderContext, rRect ); + } + // #i6467# - no light grey rectangle for page preview + else if ( gProp.pSGlobalShell->GetWin() && !gProp.pSGlobalShell->IsPreview() ) + { + // #i6467# - intersect output rectangle with table frame + SwRect aTabRect( getFramePrintArea() ); + aTabRect.Pos() += getFrameArea().Pos(); + SwRect aTabOutRect( rRect ); + aTabOutRect.Intersection( aTabRect ); + SwViewOption::DrawRect( &rRenderContext, aTabOutRect, COL_LIGHTGRAY ); + } + const_cast<SwTabFrame*>(this)->ResetComplete(); +} + +/** + * Paint border shadow + * + * @param[in] rRect aligned rect to clip the result + * @param[in,out] rOutRect full painting area as input + * painting area reduced by shadow space for border and background as output + * @param[in] rShadow includes shadow attributes + * @param[in] bDrawFullShadowRectangle paint full rect of shadow + * @param[in] bTop paint top part of the shadow + * @param[in] bBottom paint bottom part of the shadow + * @param[in] bLeft paint left part of the shadow + * @param[in] bRight paint right part of the shadow +**/ +static void lcl_PaintShadow( const SwRect& rRect, SwRect& rOutRect, + const SvxShadowItem& rShadow, const bool bDrawFullShadowRectangle, + const bool bTop, const bool bBottom, + const bool bLeft, const bool bRight, + SwPaintProperties const & properties) +{ + const long nWidth = ::lcl_AlignWidth ( rShadow.GetWidth(), properties ); + const long nHeight = ::lcl_AlignHeight( rShadow.GetWidth(), properties ); + + SwRects aRegion; + SwRect aOut( rOutRect ); + + switch ( rShadow.GetLocation() ) + { + case SvxShadowLocation::BottomRight: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Top( rOutRect.Top() + nHeight ); + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bBottom ) + { + aOut.Top( rOutRect.Bottom() - nHeight ); + if( bLeft ) + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + if( bRight ) + { + aOut.Left( rOutRect.Right() - nWidth ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + else + aOut.Top( rOutRect.Top() ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bRight ) + rOutRect.AddRight(- nWidth ); + if( bBottom ) + rOutRect.AddBottom(- nHeight ); + } + break; + case SvxShadowLocation::TopLeft: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Bottom( rOutRect.Bottom() - nHeight ); + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bTop ) + { + aOut.Bottom( rOutRect.Top() + nHeight ); + if( bRight ) + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + if( bLeft ) + { + aOut.Right( rOutRect.Left() + nWidth ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + else + aOut.Bottom( rOutRect.Bottom() ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bLeft ) + rOutRect.AddLeft( nWidth ); + if( bTop ) + rOutRect.AddTop( nHeight ); + } + break; + case SvxShadowLocation::TopRight: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Bottom( rOutRect.Bottom() - nHeight); + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bTop ) + { + aOut.Bottom( rOutRect.Top() + nHeight ); + if( bLeft ) + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + if( bRight ) + { + aOut.Left( rOutRect.Right() - nWidth ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + else + aOut.Bottom( rOutRect.Bottom() ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bRight ) + rOutRect.AddRight( - nWidth ); + if( bTop ) + rOutRect.AddTop( nHeight ); + } + break; + case SvxShadowLocation::BottomLeft: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Top( rOutRect.Top() + nHeight ); + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bBottom ) + { + aOut.Top( rOutRect.Bottom()- nHeight ); + if( bRight ) + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + if( bLeft ) + { + aOut.Right( rOutRect.Left() + nWidth ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + else + aOut.Top( rOutRect.Top() ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bLeft ) + rOutRect.AddLeft( nWidth ); + if( bBottom ) + rOutRect.AddBottom( - nHeight ); + } + break; + default: + assert(false); + break; + } + + vcl::RenderContext *pOut = properties.pSGlobalShell->GetOut(); + + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + Color aShadowColor( rShadow.GetColor().GetRGBColor() ); + if( !aRegion.empty() && properties.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + // In high contrast mode, the output device has already set the + // DrawModeFlags::SettingsFill flag. This causes the SetFillColor function + // to ignore the setting of a new color. Therefore we have to reset + // the drawing mode + pOut->SetDrawMode( DrawModeFlags::Default ); + aShadowColor = SwViewOption::GetFontColor(); + } + + if ( pOut->GetFillColor() != aShadowColor ) + pOut->SetFillColor( aShadowColor ); + + pOut->SetLineColor(); + + pOut->SetDrawMode( nOldDrawMode ); + + for (const SwRect & rOut : aRegion) + { + aOut = rOut; + if ( rRect.IsOver( aOut ) && aOut.Height() > 0 && aOut.Width() > 0 ) + { + aOut.Intersection_( rRect ); + pOut->DrawRect( aOut.SVRect() ); + } + } +} + +/** + * Paints a shadow if the format requests so. + * + * The shadow is always painted on the outer edge of the OutRect. + * If needed, the OutRect is shrunk so the painting of the border can be + * done on it. + * + * @note: draw full shadow rectangle for frames with transparent drawn backgrounds (OD 23.08.2002 #99657#) + */ +void SwFrame::PaintShadow( const SwRect& rRect, SwRect& rOutRect, + const SwBorderAttrs &rAttrs ) const +{ + SvxShadowItem rShadow = rAttrs.GetShadow(); + + const bool bCnt = IsContentFrame(); + const bool bTop = !bCnt || rAttrs.GetTopLine ( *(this) ); + const bool bBottom = !bCnt || rAttrs.GetBottomLine( *(this) ); + + if( IsVertical() ) + { + switch( rShadow.GetLocation() ) + { + case SvxShadowLocation::BottomRight: rShadow.SetLocation(SvxShadowLocation::BottomLeft); break; + case SvxShadowLocation::TopLeft: rShadow.SetLocation(SvxShadowLocation::TopRight); break; + case SvxShadowLocation::TopRight: rShadow.SetLocation(SvxShadowLocation::BottomRight); break; + case SvxShadowLocation::BottomLeft: rShadow.SetLocation(SvxShadowLocation::TopLeft); break; + default: break; + } + } + + // determine, if full shadow rectangle have to be drawn or only two shadow rectangles beside the frame. + // draw full shadow rectangle, if frame background is drawn transparent. + // Status Quo: + // SwLayoutFrame can have transparent drawn backgrounds. Thus, + // "asked" their frame format. + const bool bDrawFullShadowRectangle = + ( IsLayoutFrame() && + static_cast<const SwLayoutFrame*>(this)->GetFormat()->IsBackgroundTransparent() + ); + + SwRectFnSet aRectFnSet(this); + ::lcl_ExtendLeftAndRight( rOutRect, *(this), rAttrs, aRectFnSet.FnRect() ); + + lcl_PaintShadow(rRect, rOutRect, rShadow, bDrawFullShadowRectangle, bTop, bBottom, true, true, gProp); +} + +void SwFrame::PaintBorderLine( const SwRect& rRect, + const SwRect& rOutRect, + const SwPageFrame * pPage, + const Color *pColor, + const SvxBorderLineStyle nStyle ) const +{ + if ( !rOutRect.IsOver( rRect ) ) + return; + + SwRect aOut( rOutRect ); + aOut.Intersection_( rRect ); + + const SwTabFrame *pTab = IsCellFrame() ? FindTabFrame() : nullptr; + SubColFlags nSubCol = ( IsCellFrame() || IsRowFrame() ) + ? SubColFlags::Tab + : ( IsInSct() + ? SubColFlags::Sect + : ( IsInFly() ? SubColFlags::Fly : SubColFlags::Page ) ); + if( pColor && gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pColor = &SwViewOption::GetFontColor(); + } + + if (pPage->GetSortedObjs() && + pPage->GetFormat()->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::SUBTRACT_FLYS)) + { + SwRegionRects aRegion( aOut, 4 ); + basegfx::utils::B2DClipState aClipState; + ::lcl_SubtractFlys( this, pPage, aOut, aRegion, aClipState, gProp ); + for ( size_t i = 0; i < aRegion.size(); ++i ) + gProp.pSLines->AddLineRect( aRegion[i], pColor, nStyle, pTab, nSubCol, gProp ); + } + else + gProp.pSLines->AddLineRect( aOut, pColor, nStyle, pTab, nSubCol, gProp ); +} + +namespace drawinglayer::primitive2d +{ + namespace { + + class SwBorderRectanglePrimitive2D : public BufferedDecompositionPrimitive2D + { + private: + /// the transformation defining the geometry of this BorderRectangle + basegfx::B2DHomMatrix maB2DHomMatrix; + + /// the four styles to be used + svx::frame::Style maStyleTop; + svx::frame::Style maStyleRight; + svx::frame::Style maStyleBottom; + svx::frame::Style maStyleLeft; + + protected: + /// local decomposition. + virtual void create2DDecomposition( + Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& rViewInformation) const override; + + public: + /// constructor + SwBorderRectanglePrimitive2D( + const basegfx::B2DHomMatrix& rB2DHomMatrix, + const svx::frame::Style& rStyleTop, + const svx::frame::Style& rStyleRight, + const svx::frame::Style& rStyleBottom, + const svx::frame::Style& rStyleLeft); + + /// data read access + const basegfx::B2DHomMatrix& getB2DHomMatrix() const { return maB2DHomMatrix; } + const svx::frame::Style& getStyleTop() const { return maStyleTop; } + const svx::frame::Style& getStyleRight() const { return maStyleRight; } + const svx::frame::Style& getStyleBottom() const { return maStyleBottom; } + const svx::frame::Style& getStyleLeft() const { return maStyleLeft; } + + /// compare operator + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + + /// get range + virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override; + + /// provide unique ID + virtual sal_uInt32 getPrimitive2DID() const override; + }; + + } + + void SwBorderRectanglePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& /*rViewInformation*/) const + { + basegfx::B2DPoint aTopLeft(getB2DHomMatrix() * basegfx::B2DPoint(0.0, 0.0)); + basegfx::B2DPoint aTopRight(getB2DHomMatrix() * basegfx::B2DPoint(1.0, 0.0)); + basegfx::B2DPoint aBottomLeft(getB2DHomMatrix() * basegfx::B2DPoint(0.0, 1.0)); + basegfx::B2DPoint aBottomRight(getB2DHomMatrix() * basegfx::B2DPoint(1.0, 1.0)); + + // prepare SdrFrameBorderDataVector + std::shared_ptr<drawinglayer::primitive2d::SdrFrameBorderDataVector> aData( + std::make_shared<drawinglayer::primitive2d::SdrFrameBorderDataVector>()); + + if(getStyleTop().IsUsed()) + { + // move top left/right inwards half border width + basegfx::B2DVector aDown(getB2DHomMatrix() * basegfx::B2DVector(0.0, 1.0)); + aDown.setLength(getStyleTop().GetWidth() * 0.5); + aTopLeft += aDown; + aTopRight += aDown; + } + + if(getStyleBottom().IsUsed()) + { + // move bottom left/right inwards half border width + basegfx::B2DVector aUp(getB2DHomMatrix() * basegfx::B2DVector(0.0, -1.0)); + aUp.setLength(getStyleBottom().GetWidth() * 0.5); + aBottomLeft += aUp; + aBottomRight += aUp; + } + + if(getStyleLeft().IsUsed()) + { + // move left top/bottom inwards half border width + basegfx::B2DVector aRight(getB2DHomMatrix() * basegfx::B2DVector(1.0, 0.0)); + aRight.setLength(getStyleLeft().GetWidth() * 0.5); + aTopLeft += aRight; + aBottomLeft += aRight; + } + + if(getStyleRight().IsUsed()) + { + // move right top/bottom inwards half border width + basegfx::B2DVector aLeft(getB2DHomMatrix() * basegfx::B2DVector(-1.0, 0.0)); + aLeft.setLength(getStyleRight().GetWidth() * 0.5); + aTopRight += aLeft; + aBottomRight += aLeft; + } + + // go round-robin, from TopLeft to TopRight, down, left and back up. That + // way, the borders will not need to be mirrored in any way + if(getStyleTop().IsUsed()) + { + // create BorderPrimitive(s) for top border + const basegfx::B2DVector aVector(aTopRight - aTopLeft); + aData->emplace_back( + aTopLeft, + aVector, + getStyleTop(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleLeft().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleLeft(), basegfx::B2DVector(aBottomLeft - aTopLeft), false); + } + + if(getStyleRight().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleRight(), basegfx::B2DVector(aBottomRight - aTopRight), false); + } + } + + if(getStyleRight().IsUsed()) + { + // create BorderPrimitive(s) for right border + const basegfx::B2DVector aVector(aBottomRight - aTopRight); + aData->emplace_back( + aTopRight, + aVector, + getStyleRight(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleTop().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleTop(), basegfx::B2DVector(aTopLeft - aTopRight), false); + } + + if(getStyleBottom().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleBottom(), basegfx::B2DVector(aBottomLeft - aBottomRight), false); + } + } + + if(getStyleBottom().IsUsed()) + { + // create BorderPrimitive(s) for bottom border + const basegfx::B2DVector aVector(aBottomLeft - aBottomRight); + aData->emplace_back( + aBottomRight, + aVector, + getStyleBottom(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleRight().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleRight(), basegfx::B2DVector(aTopRight - aBottomRight), false); + } + + if(getStyleLeft().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleLeft(), basegfx::B2DVector(aTopLeft - aBottomLeft), false); + } + } + + if(getStyleLeft().IsUsed()) + { + // create BorderPrimitive(s) for left border + const basegfx::B2DVector aVector(aTopLeft - aBottomLeft); + aData->emplace_back( + aBottomLeft, + aVector, + getStyleLeft(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleBottom().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleBottom(), basegfx::B2DVector(aBottomRight - aBottomLeft), false); + } + + if(getStyleTop().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleTop(), basegfx::B2DVector(aTopRight - aTopLeft), false); + } + } + + // create instance of SdrFrameBorderPrimitive2D if + // SdrFrameBorderDataVector is used + if(!aData->empty()) + { + rContainer.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SdrFrameBorderPrimitive2D( + aData, + true))); // force visualization to minimal one discrete unit (pixel) + } + } + + SwBorderRectanglePrimitive2D::SwBorderRectanglePrimitive2D( + const basegfx::B2DHomMatrix& rB2DHomMatrix, + const svx::frame::Style& rStyleTop, + const svx::frame::Style& rStyleRight, + const svx::frame::Style& rStyleBottom, + const svx::frame::Style& rStyleLeft) + : BufferedDecompositionPrimitive2D(), + maB2DHomMatrix(rB2DHomMatrix), + maStyleTop(rStyleTop), + maStyleRight(rStyleRight), + maStyleBottom(rStyleBottom), + maStyleLeft(rStyleLeft) + { + } + + bool SwBorderRectanglePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const SwBorderRectanglePrimitive2D& rCompare = static_cast<const SwBorderRectanglePrimitive2D&>(rPrimitive); + + return (getB2DHomMatrix() == rCompare.getB2DHomMatrix() && + getStyleTop() == rCompare.getStyleTop() && + getStyleRight() == rCompare.getStyleRight() && + getStyleBottom() == rCompare.getStyleBottom() && + getStyleLeft() == rCompare.getStyleLeft()); + } + + return false; + } + + basegfx::B2DRange SwBorderRectanglePrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + + aRetval.transform(getB2DHomMatrix()); + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(SwBorderRectanglePrimitive2D, PRIMITIVE2D_ID_SWBORDERRECTANGLERIMITIVE) + +} // end of namespace drawinglayer::primitive2d + +namespace { + +editeng::SvxBorderLine const * get_ptr(std::optional<editeng::SvxBorderLine> const & opt) { + return opt ? &*opt : nullptr; +} + +} + +void PaintCharacterBorder( + const SwFont& rFont, + const SwRect& rPaintArea, + const bool bVerticalLayout, + const bool bVerticalLayoutLRBT, + const bool bJoinWithPrev, + const bool bJoinWithNext ) +{ + SwRect aAlignedRect(rPaintArea); + SwAlignRect(aAlignedRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut()); + + bool bTop = true; + bool bBottom = true; + bool bLeft = true; + bool bRight = true; + + switch (rFont.GetOrientation(bVerticalLayout, bVerticalLayoutLRBT)) + { + case 0 : + bLeft = !bJoinWithPrev; + bRight = !bJoinWithNext; + break; + case 900 : + bBottom = !bJoinWithPrev; + bTop = !bJoinWithNext; + break; + case 1800 : + bRight = !bJoinWithPrev; + bLeft = !bJoinWithNext; + break; + case 2700 : + bTop = !bJoinWithPrev; + bBottom = !bJoinWithNext; + break; + } + + // Paint shadow (reduce painting rect) + { + const SvxShadowItem aShadow( + 0, &rFont.GetShadowColor(), rFont.GetShadowWidth(), + rFont.GetAbsShadowLocation(bVerticalLayout, bVerticalLayoutLRBT)); + + if( aShadow.GetLocation() != SvxShadowLocation::NONE ) + { + lcl_PaintShadow( rPaintArea, aAlignedRect, aShadow, + false, bTop, bBottom, bLeft, bRight, gProp); + } + } + + const basegfx::B2DHomMatrix aBorderTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aAlignedRect.Width(), aAlignedRect.Height(), + aAlignedRect.Left(), aAlignedRect.Top())); + const svx::frame::Style aStyleTop( + bTop ? get_ptr(rFont.GetAbsTopBorder(bVerticalLayout, bVerticalLayoutLRBT)) : nullptr, + 1.0); + const svx::frame::Style aStyleRight( + bRight ? get_ptr(rFont.GetAbsRightBorder(bVerticalLayout, bVerticalLayoutLRBT)) : nullptr, + 1.0); + const svx::frame::Style aStyleBottom( + bBottom ? get_ptr(rFont.GetAbsBottomBorder(bVerticalLayout, bVerticalLayoutLRBT)) + : nullptr, + 1.0); + const svx::frame::Style aStyleLeft( + bLeft ? get_ptr(rFont.GetAbsLeftBorder(bVerticalLayout, bVerticalLayoutLRBT)) : nullptr, + 1.0); + drawinglayer::primitive2d::Primitive2DContainer aBorderLineTarget; + + aBorderLineTarget.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SwBorderRectanglePrimitive2D( + aBorderTransform, + aStyleTop, + aStyleRight, + aStyleBottom, + aStyleLeft))); + gProp.pBLines->AddBorderLines(aBorderLineTarget); +} + +/// #i15844# +static const SwFrame* lcl_HasNextCell( const SwFrame& rFrame ) +{ + OSL_ENSURE( rFrame.IsCellFrame(), + "lcl_HasNextCell( const SwFrame& rFrame ) should be called with SwCellFrame" ); + + const SwFrame* pTmpFrame = &rFrame; + do + { + if ( pTmpFrame->GetNext() ) + return pTmpFrame->GetNext(); + + pTmpFrame = pTmpFrame->GetUpper()->GetUpper(); + } + while ( pTmpFrame->IsCellFrame() ); + + return nullptr; +} + +/** + * Determine cell frame, from which the border attributes + * for paint of top/bottom border has to be used. + * + * OD 21.02.2003 #b4779636#, #107692# + * + * @param _pCellFrame + * input parameter - constant pointer to cell frame for which the cell frame + * for the border attributes has to be determined. + * + * @param _rCellBorderAttrs + * input parameter - constant reference to the border attributes of cell frame + * <_pCellFrame>. + * + * @param _bTop + * input parameter - boolean, that controls, if cell frame for top border or + * for bottom border has to be determined. + * + * @return constant pointer to cell frame, for which the border attributes has + * to be used + */ +static const SwFrame* lcl_GetCellFrameForBorderAttrs( const SwFrame* _pCellFrame, + const SwBorderAttrs& _rCellBorderAttrs, + const bool _bTop ) +{ + OSL_ENSURE( _pCellFrame, "No cell frame available, dying soon" ); + + // determine, if cell frame is at bottom/top border of a table frame and + // the table frame has/is a follow. + const SwFrame* pTmpFrame = _pCellFrame; + bool bCellAtBorder = true; + bool bCellAtLeftBorder = !_pCellFrame->GetPrev(); + bool bCellAtRightBorder = !_pCellFrame->GetNext(); + while( !pTmpFrame->IsRowFrame() || !pTmpFrame->GetUpper()->IsTabFrame() ) + { + pTmpFrame = pTmpFrame->GetUpper(); + if ( pTmpFrame->IsRowFrame() && + (_bTop ? pTmpFrame->GetPrev() : pTmpFrame->GetNext()) + ) + { + bCellAtBorder = false; + } + if ( pTmpFrame->IsCellFrame() ) + { + if ( pTmpFrame->GetPrev() ) + { + bCellAtLeftBorder = false; + } + if ( pTmpFrame->GetNext() ) + { + bCellAtRightBorder = false; + } + } + } + OSL_ENSURE( pTmpFrame && pTmpFrame->IsRowFrame(), "No RowFrame available" ); + + const SwLayoutFrame* pParentRowFrame = static_cast<const SwLayoutFrame*>(pTmpFrame); + const SwTabFrame* pParentTabFrame = + static_cast<const SwTabFrame*>(pParentRowFrame->GetUpper()); + + const bool bCellNeedsAttribute = bCellAtBorder && + ( _bTop ? + // bCellInFirstRowWithMaster + ( !pParentRowFrame->GetPrev() && + pParentTabFrame->IsFollow() && + 0 == pParentTabFrame->GetTable()->GetRowsToRepeat() ) : + // bCellInLastRowWithFollow + ( !pParentRowFrame->GetNext() && + pParentTabFrame->GetFollow() ) + ); + + const SwFrame* pRet = _pCellFrame; + if ( bCellNeedsAttribute ) + { + // determine, if cell frame has no borders inside the table. + const SwFrame* pNextCell = nullptr; + bool bNoBordersInside = false; + + if ( bCellAtLeftBorder && ( nullptr != ( pNextCell = lcl_HasNextCell( *_pCellFrame ) ) ) ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNextCell ); + const SwBorderAttrs &rBorderAttrs = *aAccess.Get(); + const SvxBoxItem& rBorderBox = rBorderAttrs.GetBox(); + bCellAtRightBorder = !lcl_HasNextCell( *pNextCell ); + bNoBordersInside = + ( !rBorderBox.GetTop() || !pParentRowFrame->GetPrev() ) && + !rBorderBox.GetLeft() && + ( !rBorderBox.GetRight() || bCellAtRightBorder ) && + ( !rBorderBox.GetBottom() || !pParentRowFrame->GetNext() ); + } + else + { + const SvxBoxItem& rBorderBox = _rCellBorderAttrs.GetBox(); + bNoBordersInside = + ( !rBorderBox.GetTop() || !pParentRowFrame->GetPrev() ) && + ( !rBorderBox.GetLeft() || bCellAtLeftBorder ) && + ( !rBorderBox.GetRight() || bCellAtRightBorder ) && + ( !rBorderBox.GetBottom() || !pParentRowFrame->GetNext() ); + } + + if ( bNoBordersInside ) + { + if ( _bTop && !_rCellBorderAttrs.GetBox().GetTop() ) + { + //-hack + // Cell frame has no top border and no border inside the table, but + // it is at the top border of a table frame, which is a follow. + // Thus, use border attributes of cell frame in first row of complete table. + // First, determine first table frame of complete table. + SwTabFrame* pMasterTabFrame = pParentTabFrame->FindMaster( true ); + // determine first row of complete table. + const SwFrame* pFirstRow = pMasterTabFrame->GetLower(); + // return first cell in first row + SwFrame* pLowerCell = const_cast<SwFrame*>(pFirstRow->GetLower()); + while ( !pLowerCell->IsCellFrame() || + ( pLowerCell->GetLower() && pLowerCell->GetLower()->IsRowFrame() ) + ) + { + pLowerCell = pLowerCell->GetLower(); + } + OSL_ENSURE( pLowerCell && pLowerCell->IsCellFrame(), "No CellFrame available" ); + pRet = pLowerCell; + } + else if ( !_bTop && !_rCellBorderAttrs.GetBox().GetBottom() ) + { + //-hack + // Cell frame has no bottom border and no border inside the table, + // but it is at the bottom border of a table frame, which has a follow. + // Thus, use border attributes of cell frame in last row of complete table. + // First, determine last table frame of complete table. + SwTabFrame* pLastTabFrame = const_cast<SwTabFrame*>(pParentTabFrame->GetFollow()); + while ( pLastTabFrame->GetFollow() ) + { + pLastTabFrame = pLastTabFrame->GetFollow(); + } + // determine last row of complete table. + SwFrame* pLastRow = pLastTabFrame->GetLastLower(); + // return first bottom border cell in last row + SwFrame* pLowerCell = pLastRow->GetLower(); + while ( !pLowerCell->IsCellFrame() || + ( pLowerCell->GetLower() && pLowerCell->GetLower()->IsRowFrame() ) + ) + { + if ( pLowerCell->IsRowFrame() ) + { + while ( pLowerCell->GetNext() ) + { + pLowerCell = pLowerCell->GetNext(); + } + } + pLowerCell = pLowerCell->GetLower(); + } + OSL_ENSURE( pLowerCell && pLowerCell->IsCellFrame(), "No CellFrame available" ); + pRet = pLowerCell; + } + } + } + + return pRet; +} + +std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> SwFrame::CreateProcessor2D( ) const +{ + basegfx::B2DRange aViewRange; + + SdrPage *pDrawPage = getRootFrame()->GetCurrShell()->Imp()->GetPageView()->GetPage(); + const drawinglayer::geometry::ViewInformation2D aNewViewInfos( + basegfx::B2DHomMatrix( ), + getRootFrame()->GetCurrShell()->GetOut()->GetViewTransformation(), + aViewRange, + GetXDrawPageForSdrPage( pDrawPage ), + 0.0, + uno::Sequence< beans::PropertyValue >() ); + + return drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice( + *getRootFrame()->GetCurrShell()->GetOut(), + aNewViewInfos ); +} + +void SwFrame::ProcessPrimitives( const drawinglayer::primitive2d::Primitive2DContainer& rSequence ) const +{ + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D = CreateProcessor2D(); + if ( pProcessor2D ) + { + pProcessor2D->process( rSequence ); + } +} + +/// Paints shadows and borders +void SwFrame::PaintSwFrameShadowAndBorder( + const SwRect& rRect, + const SwPageFrame* /*pPage*/, + const SwBorderAttrs& rAttrs) const +{ + // There's nothing (Row,Body,Footnote,Root,Column,NoText) need to do here + if (GetType() & (SwFrameType::NoTxt|SwFrameType::Row|SwFrameType::Body|SwFrameType::Ftn|SwFrameType::Column|SwFrameType::Root)) + return; + + if (IsCellFrame() && !gProp.pSGlobalShell->GetViewOptions()->IsTable()) + return; + + // #i29550# + if ( IsTabFrame() || IsCellFrame() || IsRowFrame() ) + { + const SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->IsCollapsingBorders() ) + return; + + if ( pTabFrame->GetTable()->IsNewModel() && ( !IsCellFrame() || IsCoveredCell() ) ) + return; + } + + const bool bLine = rAttrs.IsLine(); + const bool bShadow = rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE; + + // - flag to control, + //-hack has to be used. + const bool bb4779636HackActive = true; + + const SwFrame* pCellFrameForBottomBorderAttrs = nullptr; + const SwFrame* pCellFrameForTopBorderAttrs = nullptr; + bool bFoundCellForTopOrBorderAttrs = false; + if ( bb4779636HackActive && IsCellFrame() ) + { + pCellFrameForBottomBorderAttrs = lcl_GetCellFrameForBorderAttrs( this, rAttrs, false ); + if ( pCellFrameForBottomBorderAttrs != this ) + bFoundCellForTopOrBorderAttrs = true; + pCellFrameForTopBorderAttrs = lcl_GetCellFrameForBorderAttrs( this, rAttrs, true ); + if ( pCellFrameForTopBorderAttrs != this ) + bFoundCellForTopOrBorderAttrs = true; + } + + // - add condition <bFoundCellForTopOrBorderAttrs> + //-hack + if ( !(bLine || bShadow || bFoundCellForTopOrBorderAttrs) ) + return; + + //If the rectangle is completely inside the PrtArea, no border needs to + //be painted. + //For the PrtArea the aligned value needs to be used, otherwise it could + //happen, that some parts won't be processed. + SwRect aRect( getFramePrintArea() ); + aRect += getFrameArea().Pos(); + ::SwAlignRect( aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + // new local boolean variable in order to + // suspend border paint under special cases - see below. + // NOTE: This is a fix for the implementation of feature #99657#. + bool bDrawOnlyShadowForTransparentFrame = false; + if ( aRect.IsInside( rRect ) ) + { + // paint shadow, if background is transparent. + // Because of introduced transparent background for fly frame #99657#, + // the shadow have to be drawn if the background is transparent, + // in spite the fact that the paint rectangle <rRect> lies fully + // in the printing area. + // NOTE to chosen solution: + // On transparent background, continue processing, but suspend + // drawing of border by setting <bDrawOnlyShadowForTransparentFrame> + // to true. + if ( IsLayoutFrame() && + static_cast<const SwLayoutFrame*>(this)->GetFormat()->IsBackgroundTransparent() ) + { + bDrawOnlyShadowForTransparentFrame = true; + } + else + { + return; + } + } + + ::lcl_CalcBorderRect( aRect, this, rAttrs, true, gProp ); + rAttrs.SetGetCacheLine( true ); + + if(bShadow) + { + PaintShadow(rRect, aRect, rAttrs); + } + + // suspend drawing of border + // add condition < NOT bDrawOnlyShadowForTransparentFrame > - see above + // - add condition <bFoundCellForTopOrBorderAttrs> + //-hack. + if((bLine || bFoundCellForTopOrBorderAttrs) && !bDrawOnlyShadowForTransparentFrame) + { + // define SvxBorderLine(s) to use + const SvxBoxItem& rBox(rAttrs.GetBox()); + const SvxBorderLine* pLeftBorder(rBox.GetLeft()); + const SvxBorderLine* pRightBorder(rBox.GetRight()); + const SvxBorderLine* pTopBorder(rBox.GetTop()); + const SvxBorderLine* pBottomBorder(rBox.GetBottom()); + + // if R2L, exchange Right/Left + const bool bR2L(IsCellFrame() && IsRightToLeft()); + + if(bR2L) + { + std::swap(pLeftBorder, pRightBorder); + } + + // if ContentFrame and joined Prev/Next, reset top/bottom as needed + if(IsContentFrame()) + { + const SwFrame* pDirRefFrame(IsCellFrame() ? FindTabFrame() : this); + const SwRectFnSet aRectFnSet(pDirRefFrame); + const SwRectFn& _rRectFn(aRectFnSet.FnRect()); + + if(rAttrs.JoinedWithPrev(*this)) + { + // tdf#115296 re-add adaptation of vert distance to close the evtl. + // existing gap to previous frame + const SwFrame* pPrevFrame(GetPrev()); + (aRect.*_rRectFn->fnSetTop)( (pPrevFrame->*_rRectFn->fnGetPrtBottom)() ); + + // ...and disable top border paint/creation + pTopBorder = nullptr; + } + + if(rAttrs.JoinedWithNext(*this)) + { + // tdf#115296 re-add adaptation of vert distance to close the evtl. + // existing gap to next frame + const SwFrame* pNextFrame(GetNext()); + (aRect.*_rRectFn->fnSetBottom)( (pNextFrame->*_rRectFn->fnGetPrtTop)() ); + + // ...and disable bottom border paint/creation + pBottomBorder = nullptr; + } + } + + // necessary to replace TopBorder? + if((!IsContentFrame() || rAttrs.GetTopLine(*this)) && IsCellFrame() && pCellFrameForTopBorderAttrs != this) + { + SwBorderAttrAccess aAccess(SwFrame::GetCache(), pCellFrameForTopBorderAttrs); + pTopBorder = aAccess.Get()->GetBox().GetTop(); + } + + // necessary to replace BottomBorder? + if((!IsContentFrame() || rAttrs.GetBottomLine(*this)) && IsCellFrame() && pCellFrameForBottomBorderAttrs != this) + { + SwBorderAttrAccess aAccess(SwFrame::GetCache(), pCellFrameForBottomBorderAttrs); + pBottomBorder = aAccess.Get()->GetBox().GetBottom(); + } + + if(nullptr != pLeftBorder || nullptr != pRightBorder || nullptr != pTopBorder || nullptr != pBottomBorder) + { + // now we have all SvxBorderLine(s) sorted out, create geometry + const basegfx::B2DHomMatrix aBorderTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aRect.Width(), aRect.Height(), + aRect.Left(), aRect.Top())); + const svx::frame::Style aStyleTop(pTopBorder, 1.0); + const svx::frame::Style aStyleRight(pRightBorder, 1.0); + const svx::frame::Style aStyleBottom(pBottomBorder, 1.0); + const svx::frame::Style aStyleLeft(pLeftBorder, 1.0); + drawinglayer::primitive2d::Primitive2DContainer aBorderLineTarget; + + aBorderLineTarget.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SwBorderRectanglePrimitive2D( + aBorderTransform, + aStyleTop, + aStyleRight, + aStyleBottom, + aStyleLeft))); + gProp.pBLines->AddBorderLines(aBorderLineTarget); + } + } + + rAttrs.SetGetCacheLine( false ); +} + +/** + * Special implementation because of the footnote line + * + * Currently only the top frame needs to be taken into account + * Other lines and shadows are set aside + */ +void SwFootnoteContFrame::PaintSwFrameShadowAndBorder( + const SwRect& rRect, + const SwPageFrame* pPage, + const SwBorderAttrs&) const +{ + //If the rectangle is completely inside the PrtArea, no border needs to + //be painted. + SwRect aRect( getFramePrintArea() ); + aRect.Pos() += getFrameArea().Pos(); + if ( !aRect.IsInside( rRect ) ) + PaintLine( rRect, pPage ); +} + +/// Paint footnote lines. +void SwFootnoteContFrame::PaintLine( const SwRect& rRect, + const SwPageFrame *pPage ) const +{ + //The length of the line is derived from the percentual indication on the + //PageDesc. The position is also stated on the PageDesc. + //The pen can directly be taken from the PageDesc. + + if ( !pPage ) + pPage = FindPageFrame(); + const SwPageFootnoteInfo &rInf = pPage->GetPageDesc()->GetFootnoteInfo(); + + SwRectFnSet aRectFnSet(this); + SwTwips nPrtWidth = aRectFnSet.GetWidth(getFramePrintArea()); + Fraction aFract( nPrtWidth, 1 ); + aFract *= rInf.GetWidth(); + const SwTwips nWidth = static_cast<long>(aFract); + + SwTwips nX = aRectFnSet.GetPrtLeft(*this); + switch ( rInf.GetAdj() ) + { + case css::text::HorizontalAdjust_CENTER: + nX += nPrtWidth/2 - nWidth/2; break; + case css::text::HorizontalAdjust_RIGHT: + nX += nPrtWidth - nWidth; break; + case css::text::HorizontalAdjust_LEFT: + /* do nothing */; break; + default: + SAL_WARN("sw.core", "New adjustment for footnote lines?"); + assert(false); + } + SwTwips nLineWidth = rInf.GetLineWidth(); + const SwRect aLineRect = aRectFnSet.IsVert() ? + SwRect( Point(getFrameArea().Left()+getFrameArea().Width()-rInf.GetTopDist()-nLineWidth, + nX), Size( nLineWidth, nWidth ) ) + : SwRect( Point( nX, getFrameArea().Pos().Y() + rInf.GetTopDist() ), + Size( nWidth, rInf.GetLineWidth())); + if ( aLineRect.HasArea() && rInf.GetLineStyle() != SvxBorderLineStyle::NONE) + PaintBorderLine( rRect, aLineRect , pPage, &rInf.GetLineColor(), + rInf.GetLineStyle() ); +} + +/// Paints the separator line for inside columns +void SwLayoutFrame::PaintColLines( const SwRect &rRect, const SwFormatCol &rFormatCol, + const SwPageFrame *pPage ) const +{ + const SwFrame *pCol = Lower(); + if ( !pCol || !pCol->IsColumnFrame() ) + return; + + SwRectFn fnRect = pCol->IsVertical() ? ( pCol->IsVertLR() ? (pCol->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + SwRect aLineRect = getFramePrintArea(); + aLineRect += getFrameArea().Pos(); + + SwTwips nTop = ((aLineRect.*fnRect->fnGetHeight)()*rFormatCol.GetLineHeight()) + / 100 - (aLineRect.*fnRect->fnGetHeight)(); + SwTwips nBottom = 0; + + switch ( rFormatCol.GetLineAdj() ) + { + case COLADJ_CENTER: + nBottom = nTop / 2; nTop -= nBottom; break; + case COLADJ_TOP: + nBottom = nTop; nTop = 0; break; + case COLADJ_BOTTOM: + break; + default: + OSL_ENSURE( false, "New adjustment for column lines?" ); + } + + if( nTop ) + (aLineRect.*fnRect->fnSubTop)( nTop ); + if( nBottom ) + (aLineRect.*fnRect->fnAddBottom)( nBottom ); + + SwTwips nPenHalf = rFormatCol.GetLineWidth(); + (aLineRect.*fnRect->fnSetWidth)( nPenHalf ); + nPenHalf /= 2; + + //We need to be a bit generous here, to not lose something. + SwRect aRect( rRect ); + (aRect.*fnRect->fnSubLeft)( nPenHalf + gProp.nSPixelSzW ); + (aRect.*fnRect->fnAddRight)( nPenHalf + gProp.nSPixelSzW ); + SwRectGet fnGetX = IsRightToLeft() ? fnRect->fnGetLeft : fnRect->fnGetRight; + while ( pCol->GetNext() ) + { + (aLineRect.*fnRect->fnSetPosX) + ( (pCol->getFrameArea().*fnGetX)() - nPenHalf ); + if ( aRect.IsOver( aLineRect ) ) + PaintBorderLine( aRect, aLineRect , pPage, &rFormatCol.GetLineColor(), + rFormatCol.GetLineStyle() ); + pCol = pCol->GetNext(); + } +} + +void SwPageFrame::PaintGrid( OutputDevice const * pOut, SwRect const &rRect ) const +{ + if( !m_bHasGrid || gProp.pSRetoucheFly || gProp.pSRetoucheFly2 ) + return; + SwTextGridItem const*const pGrid(GetGridItem(this)); + if( pGrid && ( OUTDEV_PRINTER != pOut->GetOutDevType() ? + pGrid->GetDisplayGrid() : pGrid->GetPrintGrid() ) ) + { + const SwLayoutFrame* pBody = FindBodyCont(); + if( pBody ) + { + SwRect aGrid( pBody->getFramePrintArea() ); + aGrid += pBody->getFrameArea().Pos(); + + SwRect aInter( aGrid ); + aInter.Intersection( rRect ); + if( aInter.HasArea() ) + { + bool bGrid = pGrid->GetRubyTextBelow(); + bool bCell = GRID_LINES_CHARS == pGrid->GetGridType(); + long nGrid = pGrid->GetBaseHeight(); + const SwDoc* pDoc = GetFormat()->GetDoc(); + long nGridWidth = GetGridWidth(*pGrid, *pDoc); + long nRuby = pGrid->GetRubyHeight(); + long nSum = nGrid + nRuby; + const Color *pCol = &pGrid->GetColor(); + + SwTwips nRight = aInter.Left() + aInter.Width(); + SwTwips nBottom = aInter.Top() + aInter.Height(); + if( IsVertical() ) + { + SwTwips nOrig = aGrid.Left() + aGrid.Width(); + SwTwips nY = nOrig + nSum * + ( ( nOrig - aInter.Left() ) / nSum ); + SwRect aTmp( Point( nY, aInter.Top() ), + Size( 1, aInter.Height() ) ); + SwTwips nX = aGrid.Top() + nGrid * + ( ( aInter.Top() - aGrid.Top() )/ nGrid ); + if( nX < aInter.Top() ) + nX += nGrid; + SwTwips nGridBottom = aGrid.Top() + aGrid.Height(); + bool bLeft = aGrid.Top() >= aInter.Top(); + bool bRight = nGridBottom <= nBottom; + bool bBorder = bLeft || bRight; + while( nY > nRight ) + { + aTmp.Pos().setX( nY ); + if( bGrid ) + { + nY -= nGrid; + SwTwips nPosY = std::max( aInter.Left(), nY ); + SwTwips nHeight = std::min(nRight, aTmp.Pos().X())-nPosY; + if( nHeight > 0 ) + { + if( bCell ) + { + SwRect aVert( Point( nPosY, nX ), + Size( nHeight, 1 ) ); + while( aVert.Top() <= nBottom ) + { + PaintBorderLine(rRect,aVert,this,pCol); + aVert.Pos().AdjustY(nGrid ); + } + } + else if( bBorder ) + { + SwRect aVert( Point( nPosY, aGrid.Top() ), + Size( nHeight, 1 ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY -= nRuby; + if( bBorder ) + { + SwTwips nPos = std::max( aInter.Left(), nY ); + SwTwips nW = std::min(nRight, aTmp.Pos().X()) - nPos; + SwRect aVert( Point( nPos, aGrid.Top() ), + Size( nW, 1 ) ); + if( nW > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + while( nY >= aInter.Left() ) + { + aTmp.Pos().setX( nY ); + PaintBorderLine( rRect, aTmp, this, pCol); + if( bGrid ) + { + nY -= nGrid; + SwTwips nHeight = aTmp.Pos().X() + - std::max(aInter.Left(), nY ); + if( nHeight > 0 ) + { + if( bCell ) + { + SwRect aVert( Point(aTmp.Pos().X()-nHeight, + nX ), Size( nHeight, 1 ) ); + while( aVert.Top() <= nBottom ) + { + PaintBorderLine(rRect,aVert,this,pCol); + aVert.Pos().AdjustY(nGrid ); + } + } + else if( bBorder ) + { + SwRect aVert( Point(aTmp.Pos().X()-nHeight, + aGrid.Top() ), Size( nHeight, 1 ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY -= nRuby; + if( bBorder ) + { + SwTwips nPos = std::max( aInter.Left(), nY ); + SwTwips nW = std::min(nRight, aTmp.Pos().X()) - nPos; + SwRect aVert( Point( nPos, aGrid.Top() ), + Size( nW, 1 ) ); + if( nW > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + } + else + { + SwTwips nOrig = aGrid.Top(); + SwTwips nY = nOrig + nSum *( (aInter.Top()-nOrig)/nSum ); + SwRect aTmp( Point( aInter.Left(), nY ), + Size( aInter.Width(), 1 ) ); + //for textgrid refactor + SwTwips nX = aGrid.Left() + nGridWidth * + ( ( aInter.Left() - aGrid.Left() )/ nGridWidth ); + if( nX < aInter.Left() ) + nX += nGridWidth; + SwTwips nGridRight = aGrid.Left() + aGrid.Width(); + bool bLeft = aGrid.Left() >= aInter.Left(); + bool bRight = nGridRight <= nRight; + bool bBorder = bLeft || bRight; + while( nY < aInter.Top() ) + { + aTmp.Pos().setY(nY); + if( bGrid ) + { + nY += nGrid; + SwTwips nPosY = std::max( aInter.Top(), aTmp.Pos().getY() ); + SwTwips nHeight = std::min(nBottom, nY ) - nPosY; + if( nHeight ) + { + if( bCell ) + { + SwRect aVert( Point( nX, nPosY ), + Size( 1, nHeight ) ); + while( aVert.Left() <= nRight ) + { + PaintBorderLine(rRect,aVert,this,pCol); + aVert.Pos().AdjustX(nGridWidth ); //for textgrid refactor + } + } + else if ( bBorder ) + { + SwRect aVert( Point( aGrid.Left(), nPosY ), + Size( 1, nHeight ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX( nGridRight ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY += nRuby; + if( bBorder ) + { + SwTwips nPos = std::max(aInter.Top(),aTmp.Pos().getY()); + SwTwips nH = std::min( nBottom, nY ) - nPos; + SwRect aVert( Point( aGrid.Left(), nPos ), + Size( 1, nH ) ); + if( nH > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX(nGridRight); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + while( nY <= nBottom ) + { + aTmp.Pos().setY(nY); + PaintBorderLine( rRect, aTmp, this, pCol); + if( bGrid ) + { + nY += nGrid; + SwTwips nHeight = std::min(nBottom, nY) - aTmp.Pos().getY(); + if( nHeight ) + { + if( bCell ) + { + SwRect aVert( Point( nX, aTmp.Pos().getY() ), + Size( 1, nHeight ) ); + while( aVert.Left() <= nRight ) + { + PaintBorderLine( rRect, aVert, this, pCol); + aVert.Pos().setX(aVert.Pos().getX() + nGridWidth); //for textgrid refactor + } + } + else if( bBorder ) + { + SwRect aVert( Point( aGrid.Left(), + aTmp.Pos().getY() ), Size( 1, nHeight ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX(nGridRight); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY += nRuby; + if( bBorder ) + { + SwTwips nPos = std::max(aInter.Top(),aTmp.Pos().Y()); + SwTwips nH = std::min( nBottom, nY ) - nPos; + SwRect aVert( Point( aGrid.Left(), nPos ), + Size( 1, nH ) ); + if( nH > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX(nGridRight); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + } + } + } + } +} + +/** + * Paint margin area of a page + * + * OD 20.11.2002 for #104598#: + * implement paint of margin area; margin area will be painted for a + * view shell with a window and if the document is not in online layout. + * + * @param _rOutputRect + * input parameter - constant instance reference of the rectangle, for + * which an output has to be generated. + * + * @param _pViewShell + * input parameter - instance of the view shell, on which the output + * has to be generated. + */ +void SwPageFrame::PaintMarginArea( const SwRect& _rOutputRect, + SwViewShell const * _pViewShell ) const +{ + if ( _pViewShell->GetWin() && !_pViewShell->GetViewOptions()->getBrowseMode() ) + { + // Simplified paint with DrawingLayer FillStyle + SwRect aPgRect = getFrameArea(); + aPgRect.Intersection_( _rOutputRect ); + + if(!aPgRect.IsEmpty()) + { + OutputDevice *pOut = _pViewShell->GetOut(); + + if(pOut->GetFillColor() != aGlobalRetoucheColor) + { + pOut->SetFillColor(aGlobalRetoucheColor); + } + + pOut->DrawRect(aPgRect.SVRect()); + } + } +} + +const sal_Int8 SwPageFrame::mnShadowPxWidth = 9; + +bool SwPageFrame::IsRightShadowNeeded() const +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bIsLTR = getRootFrame()->IsLeftToRightViewLayout(); + + // We paint the right shadow if we're not in book mode + // or if we've no sibling or are the last page of the "row" + return !pSh || (!pSh->GetViewOptions()->IsViewLayoutBookMode()) || !GetNext() + || (this == Lower()) || (bIsLTR && OnRightPage()) + || (!bIsLTR && !OnRightPage()); + +} + +bool SwPageFrame::IsLeftShadowNeeded() const +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bIsLTR = getRootFrame()->IsLeftToRightViewLayout(); + + // We paint the left shadow if we're not in book mode + // or if we've no sibling or are the last page of the "row" + return !pSh || (!pSh->GetViewOptions()->IsViewLayoutBookMode()) || !GetPrev() + || (bIsLTR && !OnRightPage()) + || (!bIsLTR && OnRightPage()); +} + +/** + * Determine rectangle for bottom page shadow + * for #i9719# + */ +/*static*/ void SwPageFrame::GetHorizontalShadowRect( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + OutputDevice const * pRenderContext, + SwRect& _orHorizontalShadowRect, + bool bPaintLeftShadow, + bool bPaintRightShadow, + bool bRightSidebar ) +{ + const SwPostItMgr *pMgr = _pViewShell->GetPostItMgr(); + SwRect aAlignedPageRect( _rPageRect ); + ::SwAlignRect( aAlignedPageRect, _pViewShell, pRenderContext ); + SwRect aPagePxRect = pRenderContext->LogicToPixel( aAlignedPageRect.SVRect() ); + + long lShadowAdjustment = mnShadowPxWidth - 1; // TODO: extract this + + _orHorizontalShadowRect.Chg( + Point( aPagePxRect.Left() + (bPaintLeftShadow ? lShadowAdjustment : 0), 0 ), + Size( aPagePxRect.Width() - ( (bPaintLeftShadow ? lShadowAdjustment : 0) + (bPaintRightShadow ? lShadowAdjustment : 0) ), + mnShadowPxWidth ) ); + + if(pMgr && pMgr->ShowNotes() && pMgr->HasNotes()) + { + // Notes are displayed, we've to extend borders + SwTwips aSidebarTotalWidth = pMgr->GetSidebarWidth(true) + pMgr->GetSidebarBorderWidth(true); + if(bRightSidebar) + _orHorizontalShadowRect.AddRight( aSidebarTotalWidth ); + else + _orHorizontalShadowRect.AddLeft( - aSidebarTotalWidth ); + } +} + +namespace { + +enum PaintArea {LEFT, RIGHT, TOP, BOTTOM}; + +} + +#define BORDER_TILE_SIZE 512 + +/// Wrapper around pOut->DrawBitmapEx. +static void lcl_paintBitmapExToRect(vcl::RenderContext *pOut, const Point& aPoint, const Size& aSize, const BitmapEx& rBitmapEx, PaintArea eArea) +{ + // The problem is that if we get called multiple times and the color is + // partly transparent, then the result will get darker and darker. To avoid + // this, always paint the background color before doing the real paint. + tools::Rectangle aRect(aPoint, aSize); + + if (!aRect.IsEmpty()) + { + switch (eArea) + { + case LEFT: aRect.SetLeft( aRect.Right() - 1 ); break; + case RIGHT: aRect.SetRight( aRect.Left() + 1 ); break; + case TOP: aRect.SetTop( aRect.Bottom() - 1 ); break; + case BOTTOM: aRect.SetBottom( aRect.Top() + 1 ); break; + } + } + + pOut->SetFillColor(SwViewOption::GetAppBackgroundColor()); + pOut->SetLineColor(); + pOut->DrawRect(pOut->PixelToLogic(aRect)); + + // Tiled render if necessary + tools::Rectangle aComplete(aPoint, aSize); + Size aTileSize(BORDER_TILE_SIZE, BORDER_TILE_SIZE); + + long iterX = eArea != RIGHT && eArea != LEFT ? BORDER_TILE_SIZE : 0; + long iterY = eArea == RIGHT || eArea == LEFT ? BORDER_TILE_SIZE : 0; + + for (tools::Rectangle aTile(aPoint, aTileSize); true; aTile.Move(iterX, iterY)) + { + tools::Rectangle aRender = aComplete.GetIntersection(aTile); + if (aRender.IsEmpty()) + break; + pOut->DrawBitmapEx(pOut->PixelToLogic(aRender.TopLeft()), + pOut->PixelToLogic(aRender.GetSize()), + Point(0, 0), aRender.GetSize(), + rBitmapEx); + } + +} + +/** + * Paint page border and shadow + * + * for #i9719# + * implement paint of page border and shadow +*/ +/*static*/ void SwPageFrame::PaintBorderAndShadow( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + bool bPaintLeftShadow, + bool bPaintRightShadow, + bool bRightSidebar ) +{ + // No shadow in prefs + if (!SwViewOption::IsShadow()) + return; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *_pViewShell->GetOut() ); + + static vcl::DeleteOnDeinit<drawinglayer::primitive2d::DiscreteShadow> shadowMaskObj( + new drawinglayer::primitive2d::DiscreteShadow( + vcl::bitmap::loadFromName(BMP_PAGE_SHADOW_MASK, + ImageLoadFlags::IgnoreDarkTheme | ImageLoadFlags::IgnoreScalingFactor))); + + drawinglayer::primitive2d::DiscreteShadow& shadowMask = *shadowMaskObj.get(); + static vcl::DeleteOnDeinit< BitmapEx > aPageTopRightShadowObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageBottomRightShadowObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageBottomLeftShadowObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageBottomShadowBaseObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageRightShadowBaseObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageTopShadowBaseObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageTopLeftShadowObj( new BitmapEx ); + static vcl::DeleteOnDeinit< BitmapEx > aPageLeftShadowBaseObj( new BitmapEx ); + BitmapEx& aPageTopRightShadow = *aPageTopRightShadowObj.get(); + BitmapEx& aPageBottomRightShadow = *aPageBottomRightShadowObj.get(); + BitmapEx& aPageBottomLeftShadow = *aPageBottomLeftShadowObj.get(); + BitmapEx& aPageBottomShadow = *aPageBottomShadowBaseObj.get(); + BitmapEx& aPageRightShadow = *aPageRightShadowBaseObj.get(); + BitmapEx& aPageTopShadow = *aPageTopShadowBaseObj.get(); + BitmapEx& aPageTopLeftShadow = *aPageTopLeftShadowObj.get(); + BitmapEx& aPageLeftShadow = *aPageLeftShadowBaseObj.get(); + static Color aShadowColor( COL_AUTO ); + + SwRect aAlignedPageRect( _rPageRect ); + ::SwAlignRect( aAlignedPageRect, _pViewShell, _pViewShell->GetOut() ); + SwRect aPagePxRect = _pViewShell->GetOut()->LogicToPixel( aAlignedPageRect.SVRect() ); + + if (aShadowColor != SwViewOption::GetShadowColor()) + { + aShadowColor = SwViewOption::GetShadowColor(); + + AlphaMask aMask( shadowMask.getBottomRight().GetBitmap() ); + Bitmap aFilledSquare( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageBottomRightShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getBottomLeft().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageBottomLeftShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getBottom().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageBottomShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getTop().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageTopShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getTopRight().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageTopRightShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getRight().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageRightShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getTopLeft().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageTopLeftShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getLeft().GetBitmap() ); + aFilledSquare = Bitmap( aMask.GetSizePixel(), 24 ); + aFilledSquare.Erase( aShadowColor ); + aPageLeftShadow = BitmapEx( aFilledSquare, aMask ); + } + + SwRect aPaintRect; + OutputDevice *pOut = _pViewShell->GetOut(); + + SwPageFrame::GetHorizontalShadowRect( _rPageRect, _pViewShell, pOut, aPaintRect, bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + + // Right shadow & corners + if ( bPaintRightShadow ) + { + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( aPaintRect.Right(), aPagePxRect.Bottom() + 1 - (aPageBottomRightShadow.GetSizePixel().Height() - mnShadowPxWidth) ) ), + aPageBottomRightShadow ); + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( aPaintRect.Right(), aPagePxRect.Top() - mnShadowPxWidth ) ), + aPageTopRightShadow ); + + if (aPagePxRect.Height() > 2 * mnShadowPxWidth) + { + const long nWidth = aPageRightShadow.GetSizePixel().Width(); + const long nHeight = aPagePxRect.Height() - 2 * (mnShadowPxWidth - 1); + if (aPageRightShadow.GetSizePixel().Height() < BORDER_TILE_SIZE) + aPageRightShadow.Scale(Size(nWidth, BORDER_TILE_SIZE), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(aPaintRect.Right() + mnShadowPxWidth, aPagePxRect.Top() + mnShadowPxWidth - 1), + Size(nWidth, nHeight), + aPageRightShadow, RIGHT); + } + } + + // Left shadows and corners + if(bPaintLeftShadow) + { + const long lLeft = aPaintRect.Left() - aPageBottomLeftShadow.GetSizePixel().Width(); + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( lLeft, + aPagePxRect.Bottom() + 1 + mnShadowPxWidth - aPageBottomLeftShadow.GetSizePixel().Height() ) ), aPageBottomLeftShadow ); + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( lLeft, aPagePxRect.Top() - mnShadowPxWidth ) ), aPageTopLeftShadow ); + if (aPagePxRect.Height() > 2 * mnShadowPxWidth) + { + const long nWidth = aPageLeftShadow.GetSizePixel().Width(); + const long nHeight = aPagePxRect.Height() - 2 * (mnShadowPxWidth - 1); + if (aPageLeftShadow.GetSizePixel().Height() < BORDER_TILE_SIZE) + aPageLeftShadow.Scale(Size(nWidth, BORDER_TILE_SIZE), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(lLeft, aPagePxRect.Top() + mnShadowPxWidth - 1), + Size(nWidth, nHeight), + aPageLeftShadow, LEFT); + } + } + + // Bottom shadow + const long nBottomHeight = aPageBottomShadow.GetSizePixel().Height(); + if (aPageBottomShadow.GetSizePixel().Width() < BORDER_TILE_SIZE) + aPageBottomShadow.Scale(Size(BORDER_TILE_SIZE, nBottomHeight), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(aPaintRect.Left(), aPagePxRect.Bottom() + 2), + Size(aPaintRect.Width(), nBottomHeight), + aPageBottomShadow, BOTTOM); + + // Top shadow + const long nTopHeight = aPageTopShadow.GetSizePixel().Height(); + if (aPageTopShadow.GetSizePixel().Width() < BORDER_TILE_SIZE) + aPageTopShadow.Scale(Size(BORDER_TILE_SIZE, nTopHeight), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(aPaintRect.Left(), aPagePxRect.Top() - mnShadowPxWidth), + Size(aPaintRect.Width(), nTopHeight), + aPageTopShadow, TOP); +} + +/** + * mod #i6193# paint sidebar for notes + * IMPORTANT: if you change the rects here, also change SwPostItMgr::ScrollbarHit + */ +/*static*/void SwPageFrame::PaintNotesSidebar(const SwRect& _rPageRect, SwViewShell* _pViewShell, sal_uInt16 nPageNum, bool bRight) +{ + //TODO: cut out scrollbar area and arrows out of sidepane rect, otherwise it could flicker when pressing arrow buttons + if (!_pViewShell ) + return; + + SwRect aPageRect( _rPageRect ); + SwAlignRect( aPageRect, _pViewShell, _pViewShell->GetOut() ); + + const SwPostItMgr *pMgr = _pViewShell->GetPostItMgr(); + if (pMgr && pMgr->ShowNotes() && pMgr->HasNotes()) // do not show anything in print preview + { + sal_Int32 nScrollerHeight = pMgr->GetSidebarScrollerHeight(); + const tools::Rectangle &aVisRect = _pViewShell->VisArea().SVRect(); + //draw border and sidepane + _pViewShell->GetOut()->SetLineColor(); + if (!bRight) + { + _pViewShell->GetOut()->SetFillColor(COL_NOTES_SIDEPANE_BORDER); + _pViewShell->GetOut()->DrawRect(tools::Rectangle(Point(aPageRect.Left()-pMgr->GetSidebarBorderWidth(),aPageRect.Top()),Size(pMgr->GetSidebarBorderWidth(),aPageRect.Height()))) ; + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + else + _pViewShell->GetOut()->SetFillColor(COL_NOTES_SIDEPANE); + _pViewShell->GetOut()->DrawRect(tools::Rectangle(Point(aPageRect.Left()-pMgr->GetSidebarWidth()-pMgr->GetSidebarBorderWidth(),aPageRect.Top()),Size(pMgr->GetSidebarWidth(),aPageRect.Height()))) ; + } + else + { + _pViewShell->GetOut()->SetFillColor(COL_NOTES_SIDEPANE_BORDER); + SwRect aSidebarBorder(aPageRect.TopRight(),Size(pMgr->GetSidebarBorderWidth(),aPageRect.Height())); + _pViewShell->GetOut()->DrawRect(aSidebarBorder.SVRect()); + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + else + _pViewShell->GetOut()->SetFillColor(COL_NOTES_SIDEPANE); + SwRect aSidebar(Point(aPageRect.Right()+pMgr->GetSidebarBorderWidth(),aPageRect.Top()),Size(pMgr->GetSidebarWidth(),aPageRect.Height())); + _pViewShell->GetOut()->DrawRect(aSidebar.SVRect()); + } + if (pMgr->ShowScrollbar(nPageNum)) + { + // draw scrollbar area and arrows + Point aPointBottom; + Point aPointTop; + aPointBottom = !bRight ? Point(aPageRect.Left() - pMgr->GetSidebarWidth() - pMgr->GetSidebarBorderWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- _pViewShell->GetOut()->PixelToLogic(Size(0,2+pMgr->GetSidebarScrollerHeight())).Height()) : + Point(aPageRect.Right() + pMgr->GetSidebarBorderWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- _pViewShell->GetOut()->PixelToLogic(Size(0,2+pMgr->GetSidebarScrollerHeight())).Height()); + aPointTop = !bRight ? Point(aPageRect.Left() - pMgr->GetSidebarWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + _pViewShell->GetOut()->PixelToLogic(Size(0,2)).Height()) : + Point(aPageRect.Right() + pMgr->GetSidebarBorderWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + _pViewShell->GetOut()->PixelToLogic(Size(0,2)).Height()); + Size aSize(pMgr->GetSidebarWidth() - _pViewShell->GetOut()->PixelToLogic(Size(4,0)).Width(), _pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()) ; + tools::Rectangle aRectBottom(aPointBottom,aSize); + tools::Rectangle aRectTop(aPointTop,aSize); + + if (aRectBottom.IsOver(aVisRect)) + { + + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + _pViewShell->GetOut()->SetLineColor(COL_WHITE); + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + } + else + { + _pViewShell->GetOut()->SetLineColor(COL_BLACK); + _pViewShell->GetOut()->SetFillColor(COL_NOTES_SIDEPANE_SCROLLAREA); + } + _pViewShell->GetOut()->DrawRect(aRectBottom); + _pViewShell->GetOut()->DrawLine(aPointBottom + Point(pMgr->GetSidebarWidth()/3,0), aPointBottom + Point(pMgr->GetSidebarWidth()/3 , _pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height())); + + _pViewShell->GetOut()->SetLineColor(); + Point aMiddleFirst(aPointBottom + Point(pMgr->GetSidebarWidth()/6,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + Point aMiddleSecond(aPointBottom + Point(pMgr->GetSidebarWidth()/3*2,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + PaintNotesSidebarArrows(aMiddleFirst,aMiddleSecond,_pViewShell,pMgr->GetArrowColor(KEY_PAGEUP,nPageNum), pMgr->GetArrowColor(KEY_PAGEDOWN,nPageNum)); + } + if (aRectTop.IsOver(aVisRect)) + { + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + _pViewShell->GetOut()->SetLineColor(COL_WHITE); + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + } + else + { + _pViewShell->GetOut()->SetLineColor(COL_BLACK); + _pViewShell->GetOut()->SetFillColor(COL_NOTES_SIDEPANE_SCROLLAREA); + } + _pViewShell->GetOut()->DrawRect(aRectTop); + _pViewShell->GetOut()->DrawLine(aPointTop + Point(pMgr->GetSidebarWidth()/3*2,0), aPointTop + Point(pMgr->GetSidebarWidth()/3*2 , _pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height())); + + _pViewShell->GetOut()->SetLineColor(); + Point aMiddleFirst(aPointTop + Point(pMgr->GetSidebarWidth()/3,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + Point aMiddleSecond(aPointTop + Point(pMgr->GetSidebarWidth()/6*5,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + PaintNotesSidebarArrows(aMiddleFirst,aMiddleSecond,_pViewShell, pMgr->GetArrowColor(KEY_PAGEUP,nPageNum), pMgr->GetArrowColor(KEY_PAGEDOWN,nPageNum)); + } + } + } +} + +/*static*/ void SwPageFrame::PaintNotesSidebarArrows(const Point &aMiddleFirst, const Point &aMiddleSecond, SwViewShell const * _pViewShell, const Color& rColorUp, const Color& rColorDown) +{ + tools::Polygon aTriangleUp(3); + tools::Polygon aTriangleDown(3); + + aTriangleUp.SetPoint(aMiddleFirst + Point(0,_pViewShell->GetOut()->PixelToLogic(Size(0,-3)).Height()),0); + aTriangleUp.SetPoint(aMiddleFirst + Point(_pViewShell->GetOut()->PixelToLogic(Size(-3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,3)).Height()),1); + aTriangleUp.SetPoint(aMiddleFirst + Point(_pViewShell->GetOut()->PixelToLogic(Size(3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,3)).Height()),2); + + aTriangleDown.SetPoint(aMiddleSecond + Point(_pViewShell->GetOut()->PixelToLogic(Size(-3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,-3)).Height()),0); + aTriangleDown.SetPoint(aMiddleSecond + Point(_pViewShell->GetOut()->PixelToLogic(Size(+3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,-3)).Height()),1); + aTriangleDown.SetPoint(aMiddleSecond + Point(0,_pViewShell->GetOut()->PixelToLogic(Size(0,3)).Height()),2); + + _pViewShell->GetOut()->SetFillColor(rColorUp); + _pViewShell->GetOut()->DrawPolygon(aTriangleUp); + _pViewShell->GetOut()->SetFillColor(rColorDown); + _pViewShell->GetOut()->DrawPolygon(aTriangleDown); +} + +/** + * Get bound rectangle of border and shadow for repaints + * + * for #i9719# + */ +/*static*/ void SwPageFrame::GetBorderAndShadowBoundRect( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + OutputDevice const * pRenderContext, + SwRect& _orBorderAndShadowBoundRect, + bool bLeftShadow, + bool bRightShadow, + bool bRightSidebar + ) +{ + SwRect aAlignedPageRect( _rPageRect ); + ::SwAlignRect( aAlignedPageRect, _pViewShell, pRenderContext ); + SwRect aPagePxRect = pRenderContext->LogicToPixel( aAlignedPageRect.SVRect() ); + aPagePxRect.AddBottom( mnShadowPxWidth + 1 ); + aPagePxRect.AddTop( - mnShadowPxWidth - 1 ); + + SwRect aTmpRect; + + // Always ask for full shadow since we want a bounding rect + // including at least the page frame + SwPageFrame::GetHorizontalShadowRect( _rPageRect, _pViewShell, pRenderContext, aTmpRect, false, false, bRightSidebar ); + + if(bLeftShadow) aPagePxRect.Left( aTmpRect.Left() - mnShadowPxWidth - 1); + if(bRightShadow) aPagePxRect.Right( aTmpRect.Right() + mnShadowPxWidth + 1); + + _orBorderAndShadowBoundRect = pRenderContext->PixelToLogic( aPagePxRect.SVRect() ); +} + +SwRect SwPageFrame::GetBoundRect(OutputDevice const * pOutputDevice) const +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwRect aPageRect( getFrameArea() ); + SwRect aResult; + + if(!pSh) { + return SwRect( Point(0, 0), Size(0, 0) ); + } + + SwPageFrame::GetBorderAndShadowBoundRect( aPageRect, pSh, pOutputDevice, aResult, + IsLeftShadowNeeded(), IsRightShadowNeeded(), SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT ); + return aResult; +} + +/*static*/ SwTwips SwPageFrame::GetSidebarBorderWidth( const SwViewShell* _pViewShell ) +{ + const SwPostItMgr* pPostItMgr = _pViewShell ? _pViewShell->GetPostItMgr() : nullptr; + const SwTwips nRet = pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ? pPostItMgr->GetSidebarWidth() + pPostItMgr->GetSidebarBorderWidth() : 0; + return nRet; +} + +void SwFrame::PaintBaBo( const SwRect& rRect, const SwPageFrame *pPage, + const bool bOnlyTextBackground ) const +{ + if ( !pPage ) + pPage = FindPageFrame(); + + OutputDevice *pOut = gProp.pSGlobalShell->GetOut(); + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pOut ); + + pOut->Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + pOut->SetLineColor(); + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // take care of page margin area + // Note: code move from <SwFrame::PaintSwFrameBackground(..)> to new method + // <SwPageFrame::Paintmargin(..)>. + if ( IsPageFrame() && !bOnlyTextBackground) + { + static_cast<const SwPageFrame*>(this)->PaintMarginArea( rRect, gProp.pSGlobalShell ); + } + + // paint background + { + PaintSwFrameBackground( rRect, pPage, rAttrs, false, true/*bLowerBorder*/, bOnlyTextBackground ); + } + + // paint border before painting background + // paint grid for page frame and paint border + if (!bOnlyTextBackground) + { + SwRect aRect( rRect ); + + if( IsPageFrame() ) + { + static_cast<const SwPageFrame*>(this)->PaintGrid( pOut, aRect ); + } + + PaintSwFrameShadowAndBorder(aRect, pPage, rAttrs); + } + + pOut->Pop(); +} + +static bool lcl_compareFillAttributes(const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& pA, const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& pB) +{ + if (pA == pB) + return true; + if (!pA || !pB) + return false; + return pA->getFillAttribute() == pB->getFillAttribute(); +} + +/// Do not paint background for fly frames without a background brush by +/// calling <PaintBaBo> at the page or at the fly frame its anchored +void SwFrame::PaintSwFrameBackground( const SwRect &rRect, const SwPageFrame *pPage, + const SwBorderAttrs & rAttrs, + const bool bLowerMode, + const bool bLowerBorder, + const bool bOnlyTextBackground ) const +{ + // #i1837# - no paint of table background, if corresponding option is *not* set. + if( IsTabFrame() && + !gProp.pSGlobalShell->GetViewOptions()->IsTable() ) + { + return; + } + + // nothing to do for covered table cells: + if( IsCellFrame() && IsCoveredCell() ) + return; + + SwViewShell *pSh = gProp.pSGlobalShell; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pSh->GetOut() ); + + const SvxBrushItem* pItem; + // temporary background brush for a fly frame without a background brush + std::unique_ptr<SvxBrushItem> pTmpBackBrush; + const Color* pCol; + SwRect aOrigBackRect; + const bool bPageFrame = IsPageFrame(); + bool bLowMode = true; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + bool bBack = GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, bLowerMode, /*bConsiderTextBox=*/false ); + //- Output if a separate background is used. + bool bNoFlyBackground = !gProp.bSFlyMetafile && !bBack && IsFlyFrame(); + if ( bNoFlyBackground ) + { + // Fly frame has no background. + // Try to find background brush at parents, if previous call of + // <GetBackgroundBrush> disabled this option with the parameter <bLowerMode> + if ( bLowerMode ) + { + bBack = GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/false ); + } + // If still no background found for the fly frame, initialize the + // background brush <pItem> with global retouche color and set <bBack> + // to true, that fly frame will paint its background using this color. + if ( !bBack ) + { + // #i6467# - on print output, pdf output and in embedded mode not editing color COL_WHITE is used + // instead of the global retouche color. + if ( pSh->GetOut()->GetOutDevType() == OUTDEV_PRINTER || + pSh->GetViewOptions()->IsPDFExport() || + ( pSh->GetDoc()->GetDocShell()->GetCreateMode() == SfxObjectCreateMode::EMBEDDED && + !pSh->GetDoc()->GetDocShell()->IsInPlaceActive() + ) + ) + { + pTmpBackBrush.reset(new SvxBrushItem( COL_WHITE, RES_BACKGROUND )); + + //UUU + aFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(COL_WHITE); + } + else + { + pTmpBackBrush.reset(new SvxBrushItem( aGlobalRetoucheColor, RES_BACKGROUND)); + + //UUU + aFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(aGlobalRetoucheColor); + } + + pItem = pTmpBackBrush.get(); + bBack = true; + } + } + + SwRect aPaintRect( getFrameArea() ); + if( IsTextFrame() || IsSctFrame() ) + aPaintRect = UnionFrame( true ); + + // bOnlyTextBackground means background that's on top of background shapes, + // this includes both text and cell frames. + if ( (!bOnlyTextBackground || IsTextFrame() || IsCellFrame()) && aPaintRect.IsOver( rRect ) ) + { + if ( bBack || bPageFrame || !bLowerMode ) + { + const bool bBrowse = pSh->GetViewOptions()->getBrowseMode(); + SwRect aRect; + if ( (bPageFrame && bBrowse) || + (IsTextFrame() && getFramePrintArea().SSize() == getFrameArea().SSize()) ) + { + aRect = getFrameArea(); + ::SwAlignRect( aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + } + else + { + if ( bPageFrame ) + { + aRect = getFrameArea(); + } + else + { + ::lcl_CalcBorderRect( aRect, this, rAttrs, false, gProp); + } + + if ( (IsTextFrame() || IsTabFrame()) && GetPrev() ) + { + if ( GetPrev()->GetAttrSet()->GetBackground() == GetAttrSet()->GetBackground() && + lcl_compareFillAttributes(GetPrev()->getSdrAllFillAttributesHelper(), getSdrAllFillAttributesHelper())) + { + aRect.Top( getFrameArea().Top() ); + } + } + } + aRect.Intersection( rRect ); + + OutputDevice *pOut = pSh->GetOut(); + + if ( aRect.HasArea() ) + { + std::unique_ptr<SvxBrushItem> pNewItem; + + if( pCol ) + { + pNewItem.reset(new SvxBrushItem( *pCol, RES_BACKGROUND )); + pItem = pNewItem.get(); + aFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(*pCol); + } + + SwRegionRects aRegion( aRect ); + basegfx::B2DPolygon aB2DPolygon{tools::Polygon(aRect.SVRect()).getB2DPolygon()}; + basegfx::utils::B2DClipState aClipState{basegfx::B2DPolyPolygon(aB2DPolygon)}; + if (pPage->GetSortedObjs() && + pSh->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::SUBTRACT_FLYS)) + { + ::lcl_SubtractFlys( this, pPage, aRect, aRegion, aClipState, gProp ); + } + + // Determine, if background transparency + // have to be considered for drawing. + // Status Quo: background transparency have to be + // considered for fly frames + const bool bConsiderBackgroundTransparency = IsFlyFrame(); + bool bDone(false); + + // #i125189# We are also done when the new DrawingLayer FillAttributes are used + // or the FillStyle is set (different from drawing::FillStyle_NONE) + if(pOut && aFillAttributes) + { + if(aFillAttributes->isUsed()) + { + // check if really something is painted + bDone = DrawFillAttributes(aFillAttributes, aOrigBackRect, aRegion, aClipState, *pOut); + } + + if(!bDone) + { + // if not, still a FillStyle could be set but the transparency is at 100%, + // thus need to check the model data itself for FillStyle (do not rely on + // SdrAllFillAttributesHelper since it already contains optimized information, + // e.g. transparency leads to no fill) + const drawing::FillStyle eFillStyle(GetAttrSet()->Get(XATTR_FILLSTYLE).GetValue()); + + if(drawing::FillStyle_NONE != eFillStyle) + { + bDone = true; + } + } + } + + if(!bDone) + { + for (size_t i = 0; i < aRegion.size(); ++i) + { + if (1 < aRegion.size()) + { + ::SwAlignRect( aRegion[i], gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + if( !aRegion[i].HasArea() ) + continue; + } + // add 6th parameter to indicate, if background transparency have to be considered + // Set missing 5th parameter to the default value GRFNUM_NO + // - see declaration in /core/inc/frmtool.hxx. + ::DrawGraphic( + pItem, + pOut, + aOrigBackRect, + aRegion[i], + GRFNUM_NO, + bConsiderBackgroundTransparency ); + } + } + } + } + else + bLowMode = bLowerMode; + } + + // delete temporary background brush. + pTmpBackBrush.reset(); + + //Now process lower and his neighbour. + //We end this as soon as a Frame leaves the chain and therefore is not a lower + //of me anymore + const SwFrame *pFrame = GetLower(); + if ( pFrame ) + { + SwRect aFrameRect; + SwRect aRect( GetPaintArea() ); + aRect.Intersection_( rRect ); + SwRect aBorderRect( aRect ); + SwShortCut aShortCut( *pFrame, aBorderRect ); + do + { if ( gProp.pSProgress ) + SfxProgress::Reschedule(); + + aFrameRect = pFrame->GetPaintArea(); + if ( aFrameRect.IsOver( aBorderRect ) ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rTmpAttrs = *aAccess.Get(); + if ( ( pFrame->IsLayoutFrame() && bLowerBorder ) || aFrameRect.IsOver( aRect ) ) + { + pFrame->PaintSwFrameBackground( aRect, pPage, rTmpAttrs, bLowMode, + bLowerBorder, bOnlyTextBackground ); + } + + if ( bLowerBorder ) + { + pFrame->PaintSwFrameShadowAndBorder( aBorderRect, pPage, rTmpAttrs ); + } + } + pFrame = pFrame->GetNext(); + } while ( pFrame && pFrame->GetUpper() == this && + !aShortCut.Stop( aFrameRect ) ); + } +} + +/// Refreshes all subsidiary lines of a page. +void SwPageFrame::RefreshSubsidiary( const SwRect &rRect ) const +{ + if ( isSubsidiaryLinesEnabled() || isTableBoundariesEnabled() + || isSubsidiaryLinesForSectionsEnabled() || isSubsidiaryLinesFlysEnabled() ) + { + if ( rRect.HasArea() ) + { + //During paint using the root, the array is controlled from there. + //Otherwise we'll handle it for our self. + bool bDelSubs = false; + if ( !gProp.pSSubsLines ) + { + gProp.pSSubsLines.reset(new SwSubsRects); + // create container for special subsidiary lines + gProp.pSSpecSubsLines.reset(new SwSubsRects); + bDelSubs = true; + } + + RefreshLaySubsidiary( this, rRect ); + + if ( bDelSubs ) + { + // paint special subsidiary lines and delete its container + gProp.pSSpecSubsLines->PaintSubsidiary( gProp.pSGlobalShell->GetOut(), nullptr, gProp ); + gProp.pSSpecSubsLines.reset(); + + gProp.pSSubsLines->PaintSubsidiary(gProp.pSGlobalShell->GetOut(), gProp.pSLines.get(), gProp); + gProp.pSSubsLines.reset(); + } + } + } +} + +void SwLayoutFrame::RefreshLaySubsidiary( const SwPageFrame *pPage, + const SwRect &rRect ) const +{ + const bool bSubsOpt = isSubsidiaryLinesEnabled(); + if ( bSubsOpt ) + PaintSubsidiaryLines( pPage, rRect ); + + const SwFrame *pLow = Lower(); + if( !pLow ) + return; + SwShortCut aShortCut( *pLow, rRect ); + while( pLow && !aShortCut.Stop( pLow->getFrameArea() ) ) + { + if ( pLow->getFrameArea().IsOver( rRect ) && pLow->getFrameArea().HasArea() ) + { + if ( pLow->IsLayoutFrame() ) + static_cast<const SwLayoutFrame*>(pLow)->RefreshLaySubsidiary( pPage, rRect); + else if ( pLow->GetDrawObjs() ) + { + const SwSortedObjs& rObjs = *(pLow->GetDrawObjs()); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( pPage->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( + pAnchoredObj->GetDrawObj()->GetLayer() ) && + dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + const SwFlyFrame *pFly = + static_cast<const SwFlyFrame*>(pAnchoredObj); + if ( pFly->IsFlyInContentFrame() && pFly->getFrameArea().IsOver( rRect ) ) + { + if ( !pFly->Lower() || !pFly->Lower()->IsNoTextFrame() || + !static_cast<const SwNoTextFrame*>(pFly->Lower())->HasAnimation()) + pFly->RefreshLaySubsidiary( pPage, rRect ); + } + } + } + } + } + pLow = pLow->GetNext(); + } +} + +/** + * Subsidiary lines to paint the PrtAreas + * Only the LayoutFrames which directly contain Content + * Paints the desired line and pays attention to not overpaint any flys + */ +static void lcl_RefreshLine( const SwLayoutFrame *pLay, + const SwPageFrame *pPage, + const Point &rP1, + const Point &rP2, + const SubColFlags nSubColor, + SwLineRects* pSubsLines ) +{ + //In which direction do we loop? Can only be horizontal or vertical. + OSL_ENSURE( ((rP1.X() == rP2.X()) || (rP1.Y() == rP2.Y())), + "Sloped subsidiary lines are not allowed." ); + + const bool bHori = rP1.Y() == rP2.Y(); + + // use pointers to member function in order to unify flow + typedef long (Point:: *pmfPtGet)() const; + typedef void (Point:: *pmfPtSet)(long); + const pmfPtGet pDirPtX = &Point::X; + const pmfPtGet pDirPtY = &Point::Y; + const pmfPtGet pDirPt = bHori ? pDirPtX : pDirPtY; + const pmfPtSet pDirPtSetX = &Point::setX; + const pmfPtSet pDirPtSetY = &Point::setY; + const pmfPtSet pDirPtSet = bHori ? pDirPtSetX : pDirPtSetY; + + Point aP1( rP1 ); + Point aP2( rP2 ); + + while ( (aP1.*pDirPt)() < (aP2.*pDirPt)() ) + { + //If the starting point lies in a fly, it is directly set behind the + //fly. + //The end point moves to the start if the end point lies in a fly or we + //have a fly between starting point and end point. + // In this way, every position is output one by one. + + //If I'm a fly I'll only avoid those flys which are places 'above' me; + //this means those who are behind me in the array. + //Even if I'm inside a fly or inside a fly inside a fly a.s.o I won't + //avoid any of those flys. + SwOrderIter aIter( pPage ); + const SwFlyFrame *pMyFly = pLay->FindFlyFrame(); + if ( pMyFly ) + { + aIter.Current( pMyFly->GetVirtDrawObj() ); + while ( nullptr != (pMyFly = pMyFly->GetAnchorFrame()->FindFlyFrame()) ) + { + if ( aIter()->GetOrdNum() > pMyFly->GetVirtDrawObj()->GetOrdNum() ) + aIter.Current( pMyFly->GetVirtDrawObj() ); + } + } + else + aIter.Bottom(); + + while ( aIter() ) + { + const SwVirtFlyDrawObj *pObj = static_cast<const SwVirtFlyDrawObj*>(aIter()); + const SwFlyFrame *pFly = pObj ? pObj->GetFlyFrame() : nullptr; + + //I certainly won't avoid myself, even if I'm placed _inside_ the + //fly I won't avoid it. + if ( !pFly || (pFly == pLay || pFly->IsAnLower( pLay )) ) + { + aIter.Next(); + continue; + } + + // do *not* consider fly frames with a transparent background. + // do *not* consider fly frame, which belongs to an invisible layer + if ( pFly->IsBackgroundTransparent() || + !pFly->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( pObj->GetLayer() ) ) + { + aIter.Next(); + continue; + } + + //Is the Obj placed on the line + const long nP1OthPt = !bHori ? rP1.X() : rP1.Y(); + const tools::Rectangle &rBound = pObj->GetCurrentBoundRect(); + const Point aDrPt( rBound.TopLeft() ); + const long nDrOthPt = !bHori ? aDrPt.X() : aDrPt.Y(); + const Size aDrSz( rBound.GetSize() ); + const long nDrOthSz = !bHori ? aDrSz.Width() : aDrSz.Height(); + + if ( nP1OthPt >= nDrOthPt && nP1OthPt <= nDrOthPt + nDrOthSz ) + { + const long nDrDirPt = bHori ? aDrPt.X() : aDrPt.Y(); + const long nDrDirSz = bHori ? aDrSz.Width() : aDrSz.Height(); + + if ( (aP1.*pDirPt)() >= nDrDirPt && (aP1.*pDirPt)() <= nDrDirPt + nDrDirSz ) + (aP1.*pDirPtSet)( nDrDirPt + nDrDirSz ); + + if ( (aP2.*pDirPt)() >= nDrDirPt && (aP1.*pDirPt)() < (nDrDirPt - 1) ) + (aP2.*pDirPtSet)( nDrDirPt - 1 ); + } + aIter.Next(); + } + + if ( (aP1.*pDirPt)() < (aP2.*pDirPt)() ) + { + SwRect aRect( aP1, aP2 ); + // use parameter <pSubsLines> instead of global variable <gProp.pSSubsLines>. + pSubsLines->AddLineRect( aRect, nullptr, SvxBorderLineStyle::SOLID, + nullptr, nSubColor, gProp ); + } + aP1 = aP2; + (aP1.*pDirPtSet)( (aP1.*pDirPt)() + 1 ); + aP2 = rP2; + } +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreatePageAreaDelimiterPrimitives( + const SwRect& rRect ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 4 ); + + basegfx::BColor aLineColor = SwViewOption::GetDocBoundariesColor().getBColor(); + double nLineLength = 200.0; // in Twips + + Point aPoints[] = { rRect.TopLeft(), rRect.TopRight(), rRect.BottomRight(), rRect.BottomLeft() }; + double const aXOffDirs[] = { -1.0, 1.0, 1.0, -1.0 }; + double const aYOffDirs[] = { -1.0, -1.0, 1.0, 1.0 }; + + // Actually loop over the corners to create the two lines + for ( int i = 0; i < 4; i++ ) + { + basegfx::B2DVector aHorizVector( aXOffDirs[i], 0.0 ); + basegfx::B2DVector aVertVector( 0.0, aYOffDirs[i] ); + + basegfx::B2DPoint aBPoint( aPoints[i].getX(), aPoints[i].getY() ); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( aBPoint + aHorizVector * nLineLength ); + aPolygon.append( aBPoint ); + aPolygon.append( aBPoint + aVertVector * nLineLength ); + + drawinglayer::primitive2d::PolygonHairlinePrimitive2D* pLine = + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aLineColor ); + aSeq[i] = drawinglayer::primitive2d::Primitive2DReference( pLine ); + } + + return aSeq; +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreateRectangleDelimiterPrimitives ( + const SwRect& rRect ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 1 ); + basegfx::BColor aLineColor = SwViewOption::GetDocBoundariesColor().getBColor(); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( basegfx::B2DPoint( rRect.Left(), rRect.Top() ) ); + aPolygon.append( basegfx::B2DPoint( rRect.Right(), rRect.Top() ) ); + aPolygon.append( basegfx::B2DPoint( rRect.Right(), rRect.Bottom() ) ); + aPolygon.append( basegfx::B2DPoint( rRect.Left(), rRect.Bottom() ) ); + aPolygon.setClosed( true ); + + drawinglayer::primitive2d::PolygonHairlinePrimitive2D* pLine = + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aLineColor ); + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference( pLine ); + + return aSeq; +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreateColumnAreaDelimiterPrimitives( + const SwRect& rRect ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 4 ); + + basegfx::BColor aLineColor = SwViewOption::GetDocBoundariesColor().getBColor(); + double nLineLength = 100.0; // in Twips + + Point aPoints[] = { rRect.TopLeft(), rRect.TopRight(), rRect.BottomRight(), rRect.BottomLeft() }; + double const aXOffDirs[] = { 1.0, -1.0, -1.0, 1.0 }; + double const aYOffDirs[] = { 1.0, 1.0, -1.0, -1.0 }; + + // Actually loop over the corners to create the two lines + for ( int i = 0; i < 4; i++ ) + { + basegfx::B2DVector aHorizVector( aXOffDirs[i], 0.0 ); + basegfx::B2DVector aVertVector( 0.0, aYOffDirs[i] ); + + basegfx::B2DPoint aBPoint( aPoints[i].getX(), aPoints[i].getY() ); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( aBPoint + aHorizVector * nLineLength ); + aPolygon.append( aBPoint ); + aPolygon.append( aBPoint + aVertVector * nLineLength ); + + drawinglayer::primitive2d::PolygonHairlinePrimitive2D* pLine = + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aLineColor ); + aSeq[i] = drawinglayer::primitive2d::Primitive2DReference( pLine ); + } + + return aSeq; +} + +void SwPageFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ + if ( !gProp.pSGlobalShell->IsHeaderFooterEdit() ) + { + const SwFrame* pLay = Lower(); + const SwFrame* pFootnoteCont = nullptr; + const SwFrame* pPageBody = nullptr; + while ( pLay && !( pFootnoteCont && pPageBody ) ) + { + if ( pLay->IsFootnoteContFrame( ) ) + pFootnoteCont = pLay; + if ( pLay->IsBodyFrame() ) + pPageBody = pLay; + pLay = pLay->GetNext(); + } + + SwRect aArea( pPageBody->getFrameArea() ); + if ( pFootnoteCont ) + aArea.AddBottom( pFootnoteCont->getFrameArea().Bottom() - aArea.Bottom() ); + + if ( !gProp.pSGlobalShell->GetViewOptions()->IsViewMetaChars( ) ) + ProcessPrimitives( lcl_CreatePageAreaDelimiterPrimitives( aArea ) ); + else + ProcessPrimitives( lcl_CreateRectangleDelimiterPrimitives( aArea ) ); + } +} + +void SwColumnFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ + const SwFrame* pLay = Lower(); + const SwFrame* pFootnoteCont = nullptr; + const SwFrame* pColBody = nullptr; + while ( pLay && !( pFootnoteCont && pColBody ) ) + { + if ( pLay->IsFootnoteContFrame( ) ) + pFootnoteCont = pLay; + if ( pLay->IsBodyFrame() ) + pColBody = pLay; + pLay = pLay->GetNext(); + } + + SwRect aArea( pColBody->getFrameArea() ); + + // #i3662# - enlarge top of column body frame's printing area + // in sections to top of section frame. + const bool bColInSection = GetUpper()->IsSctFrame(); + if ( bColInSection ) + { + if ( IsVertical() ) + aArea.Right( GetUpper()->getFrameArea().Right() ); + else + aArea.Top( GetUpper()->getFrameArea().Top() ); + } + + if ( pFootnoteCont ) + aArea.AddBottom( pFootnoteCont->getFrameArea().Bottom() - aArea.Bottom() ); + + ::SwAlignRect( aArea, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + + if ( !gProp.pSGlobalShell->GetViewOptions()->IsViewMetaChars( ) ) + ProcessPrimitives( lcl_CreateColumnAreaDelimiterPrimitives( aArea ) ); + else + ProcessPrimitives( lcl_CreateRectangleDelimiterPrimitives( aArea ) ); +} + +void SwSectionFrame::PaintSubsidiaryLines( const SwPageFrame * pPage, + const SwRect & rRect ) const +{ + const bool bNoLowerColumn = !Lower() || !Lower()->IsColumnFrame(); + if ( bNoLowerColumn ) + { + SwLayoutFrame::PaintSubsidiaryLines( pPage, rRect ); + } +} + +/** + * The SwBodyFrame doesn't print any subsidiary line: it's bounds are painted + * either by the parent page or the parent column frame. + */ +void SwBodyFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ +} + +void SwHeadFootFrame::PaintSubsidiaryLines( const SwPageFrame *, const SwRect & ) const +{ + if ( gProp.pSGlobalShell->IsHeaderFooterEdit() ) + { + SwRect aArea( getFramePrintArea() ); + aArea.Pos() += getFrameArea().Pos(); + if ( !gProp.pSGlobalShell->GetViewOptions()->IsViewMetaChars( ) ) + ProcessPrimitives( lcl_CreatePageAreaDelimiterPrimitives( aArea ) ); + else + ProcessPrimitives( lcl_CreateRectangleDelimiterPrimitives( aArea ) ); + } +} + +/** + * This method is overridden in order to have no subsidiary lines + * around the footnotes. + */ +void SwFootnoteFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ +} + +/** + * This method is overridden in order to have no subsidiary lines + * around the footnotes containers. + */ +void SwFootnoteContFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ +} + +void SwLayoutFrame::PaintSubsidiaryLines( const SwPageFrame *pPage, + const SwRect &rRect ) const +{ + bool bNewTableModel = false; + + // #i29550# + if ( IsTabFrame() || IsCellFrame() || IsRowFrame() ) + { + const SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->IsCollapsingBorders() ) + return; + + bNewTableModel = pTabFrame->GetTable()->IsNewModel(); + // in the new table model, we have an early return for all cell-related + // frames, except from non-covered table cells + if ( bNewTableModel ) + if ( IsTabFrame() || + IsRowFrame() || + ( IsCellFrame() && IsCoveredCell() ) ) + return; + } + + const bool bFlys = pPage->GetSortedObjs() != nullptr; + + const bool bCell = IsCellFrame(); + // #i3662# - use frame area for cells for section use also frame area + const bool bUseFrameArea = bCell || IsSctFrame(); + SwRect aOriginal( bUseFrameArea ? getFrameArea() : getFramePrintArea() ); + if ( !bUseFrameArea ) + aOriginal.Pos() += getFrameArea().Pos(); + + ::SwAlignRect( aOriginal, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + + if ( !aOriginal.IsOver( rRect ) ) + return; + + SwRect aOut( aOriginal ); + aOut.Intersection_( rRect ); + + const SwTwips nRight = aOut.Right(); + const SwTwips nBottom= aOut.Bottom(); + + const Point aRT( nRight, aOut.Top() ); + const Point aRB( nRight, nBottom ); + const Point aLB( aOut.Left(), nBottom ); + + SubColFlags nSubColor = ( bCell || IsRowFrame() ) + ? SubColFlags::Tab + : ( IsInSct() + ? SubColFlags::Sect + : ( IsInFly() ? SubColFlags::Fly : SubColFlags::Page ) ); + + // collect body, header, footer, footnote and section + // sub-lines in <pSpecSubsLine> array. + const bool bSpecialSublines = IsBodyFrame() || IsHeaderFrame() || IsFooterFrame() || + IsFootnoteFrame() || IsSctFrame(); + SwLineRects *const pUsedSubsLines = bSpecialSublines + ? gProp.pSSpecSubsLines.get() : gProp.pSSubsLines.get(); + + // NOTE: for cell frames only left and right (horizontal layout) respectively + // top and bottom (vertical layout) lines painted. + // NOTE2: this does not hold for the new table model!!! We paint the top border + // of each non-covered table cell. + const bool bVert = IsVertical(); + if ( bFlys ) + { + // add control for drawing left and right lines + if ( !bCell || bNewTableModel || !bVert ) + { + if ( aOriginal.Left() == aOut.Left() ) + ::lcl_RefreshLine( this, pPage, aOut.Pos(), aLB, nSubColor, pUsedSubsLines ); + // in vertical layout set page/column break at right + if ( aOriginal.Right() == nRight ) + ::lcl_RefreshLine( this, pPage, aRT, aRB, nSubColor, pUsedSubsLines ); + } + // adjust control for drawing top and bottom lines + if ( !bCell || bNewTableModel || bVert ) + { + if ( aOriginal.Top() == aOut.Top() ) + // in horizontal layout set page/column break at top + ::lcl_RefreshLine( this, pPage, aOut.Pos(), aRT, nSubColor, pUsedSubsLines ); + if ( aOriginal.Bottom() == nBottom ) + ::lcl_RefreshLine( this, pPage, aLB, aRB, nSubColor, + pUsedSubsLines ); + } + } + else + { + // add control for drawing left and right lines + if ( !bCell || bNewTableModel || !bVert ) + { + if ( aOriginal.Left() == aOut.Left() ) + { + const SwRect aRect( aOut.Pos(), aLB ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + // in vertical layout set page/column break at right + if ( aOriginal.Right() == nRight ) + { + const SwRect aRect( aRT, aRB ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + } + // adjust control for drawing top and bottom lines + if ( !bCell || bNewTableModel || bVert ) + { + if ( aOriginal.Top() == aOut.Top() ) + { + // in horizontal layout set page/column break at top + const SwRect aRect( aOut.Pos(), aRT ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + if ( aOriginal.Bottom() == nBottom ) + { + const SwRect aRect( aLB, aRB ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + } + } +} + +/** + * Refreshes all extra data (line breaks a.s.o) of the page. Basically only those objects + * are considered which horizontally overlap the Rect. + */ +void SwPageFrame::RefreshExtraData( const SwRect &rRect ) const +{ + const SwLineNumberInfo &rInfo = GetFormat()->GetDoc()->GetLineNumberInfo(); + bool bLineInFly = (rInfo.IsPaintLineNumbers() && rInfo.IsCountInFlys()) + || static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos()) != text::HoriOrientation::NONE; + + SwRect aRect( rRect ); + ::SwAlignRect( aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + if ( aRect.HasArea() ) + { + SwLayoutFrame::RefreshExtraData( aRect ); + + if ( bLineInFly && GetSortedObjs() ) + for (SwAnchoredObject* pAnchoredObj : *GetSortedObjs()) + { + if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) ) + { + if ( pFly->getFrameArea().Top() <= aRect.Bottom() && + pFly->getFrameArea().Bottom() >= aRect.Top() ) + pFly->RefreshExtraData( aRect ); + } + } + } +} + +void SwLayoutFrame::RefreshExtraData( const SwRect &rRect ) const +{ + + const SwLineNumberInfo &rInfo = GetFormat()->GetDoc()->GetLineNumberInfo(); + bool bLineInBody = rInfo.IsPaintLineNumbers(), + bLineInFly = bLineInBody && rInfo.IsCountInFlys(), + bRedLine = static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos())!=text::HoriOrientation::NONE; + + const SwContentFrame *pCnt = ContainsContent(); + while ( pCnt && IsAnLower( pCnt ) ) + { + if ( pCnt->IsTextFrame() && ( bRedLine || + ( !pCnt->IsInTab() && + ((bLineInBody && pCnt->IsInDocBody()) || + (bLineInFly && pCnt->IsInFly())) ) ) && + pCnt->getFrameArea().Top() <= rRect.Bottom() && + pCnt->getFrameArea().Bottom() >= rRect.Top() ) + { + static_cast<const SwTextFrame*>(pCnt)->PaintExtraData( rRect ); + } + if ( bLineInFly && pCnt->GetDrawObjs() ) + for (SwAnchoredObject* pAnchoredObj : *pCnt->GetDrawObjs()) + { + if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) ) + { + if ( pFly->IsFlyInContentFrame() && + pFly->getFrameArea().Top() <= rRect.Bottom() && + pFly->getFrameArea().Bottom() >= rRect.Top() ) + pFly->RefreshExtraData( rRect ); + } + } + pCnt = pCnt->GetNextContentFrame(); + } +} + +/** + * For #102450# + * Determine the color, that is respectively will be drawn as background + * for the page frame. + * Using existing method SwFrame::GetBackgroundBrush to determine the color + * that is set at the page frame respectively is parent. If none is found + * return the global retouche color + * + * @return Color + */ +Color SwPageFrame::GetDrawBackgrdColor() const +{ + const SvxBrushItem* pBrushItem; + const Color* pDummyColor; + SwRect aDummyRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( GetBackgroundBrush( aFillAttributes, pBrushItem, pDummyColor, aDummyRect, true, /*bConsiderTextBox=*/false) ) + { + if(aFillAttributes && aFillAttributes->isUsed()) + { + // let SdrAllFillAttributesHelper do the average color calculation + return Color(aFillAttributes->getAverageColor(aGlobalRetoucheColor.getBColor())); + } + else if(pBrushItem) + { + OUString referer; + SwViewShell * sh1 = getRootFrame()->GetCurrShell(); + if (sh1 != nullptr) { + SfxObjectShell * sh2 = sh1->GetDoc()->GetPersist(); + if (sh2 != nullptr && sh2->HasName()) { + referer = sh2->GetMedium()->GetName(); + } + } + const Graphic* pGraphic = pBrushItem->GetGraphic(referer); + + if(pGraphic) + { + // #29105# when a graphic is set, it may be possible to calculate a single + // color which looks good in all places of the graphic. Since it is + // planned to have text edit on the overlay one day and the fallback + // to aGlobalRetoucheColor returns something useful, just use that + // for now. + } + else + { + // not a graphic, use (hopefully) initialized color + return pBrushItem->GetColor(); + } + } + } + + return aGlobalRetoucheColor; +} + +/// create/return font used to paint the "empty page" string +const vcl::Font& SwPageFrame::GetEmptyPageFont() +{ + static vcl::Font aEmptyPgFont = [&]() + { + vcl::Font tmp; + tmp.SetFontSize( Size( 0, 80 * 20 )); // == 80 pt + tmp.SetWeight( WEIGHT_BOLD ); + tmp.SetStyleName(OUString()); + tmp.SetFamilyName("Helvetica"); + tmp.SetFamily( FAMILY_SWISS ); + tmp.SetTransparent( true ); + tmp.SetColor( COL_GRAY ); + return tmp; + }(); + + return aEmptyPgFont; +} + +/** + * Retouch for a section + * + * Retouch will only be done, if the Frame is the last one in his chain. + * The whole area of the upper which is located below the Frame will be + * cleared using PaintSwFrameBackground. + */ +void SwFrame::Retouch( const SwPageFrame * pPage, const SwRect &rRect ) const +{ + if ( gProp.bSFlyMetafile ) + return; + + OSL_ENSURE( GetUpper(), "Retouche try without Upper." ); + OSL_ENSURE( getRootFrame()->GetCurrShell() && gProp.pSGlobalShell->GetWin(), "Retouche on a printer?" ); + + SwRect aRetouche( GetUpper()->GetPaintArea() ); + aRetouche.Top( getFrameArea().Top() + getFrameArea().Height() ); + aRetouche.Intersection( gProp.pSGlobalShell->VisArea() ); + + if ( aRetouche.HasArea() ) + { + //Omit the passed Rect. To do this, we unfortunately need a region to + //cut out. + SwRegionRects aRegion( aRetouche ); + aRegion -= rRect; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pSh->GetOut() ); + + for ( size_t i = 0; i < aRegion.size(); ++i ) + { + const SwRect &rRetouche = aRegion[i]; + + GetUpper()->PaintBaBo( rRetouche, pPage ); + + //Hell and Heaven need to be refreshed too. + //To avoid recursion my retouch flag needs to be reset first! + ResetRetouche(); + if ( rRetouche.HasArea() ) + { + const Color aPageBackgrdColor(pPage->GetDrawBackgrdColor()); + const IDocumentDrawModelAccess& rIDDMA = pSh->getIDocumentDrawModelAccess(); + // --> OD #i76669# + SwViewObjectContactRedirector aSwRedirector( *pSh ); + // <-- + + pSh->Imp()->PaintLayer( rIDDMA.GetHellId(), nullptr, + *pPage, rRetouche, &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + pSh->Imp()->PaintLayer( rIDDMA.GetHeavenId(), nullptr, + *pPage, rRetouche, &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + } + + SetRetouche(); + + //Because we leave all paint areas, we need to refresh the + //subsidiary lines. + pPage->RefreshSubsidiary( rRetouche ); + } + } + if ( SwViewShell::IsLstEndAction() ) + ResetRetouche(); +} + +/** + * Determine the background brush for the frame: + * the background brush is taken from it-self or from its parent (anchor/upper). + * Normally, the background brush is taken, which has no transparent color or + * which has a background graphic. But there are some special cases: + * (1) No background brush is taken from a page frame, if view option "IsPageBack" + * isn't set. + * (2) Background brush from an index section is taken under special conditions. + * In this case parameter <rpCol> is set to the index shading color. + * (3) New (OD 20.08.2002) - Background brush is taken, if on background drawing + * of the frame transparency is considered and its color is not "no fill"/"auto fill" + * + * Old description in German: + * Returns the Backgroundbrush for the area of the Frame. + * The Brush is defined by the Frame or by an upper, the first Brush is + * used. If no Brush is defined for a Frame, false is returned. + * + * @param rpBrush + * output parameter - constant reference pointer the found background brush + * + * @param rpFillStyle + * output parameter - constant reference pointer the found background fill style + * + * @param rpFillGradient + * output parameter - constant reference pointer the found background fill gradient + * + * @param rpCol + * output parameter - constant reference pointer to the color of the index shading + * set under special conditions, if background brush is taken from an index section. + * + * @param rOrigRect + * in-/output parameter - reference to the rectangle the background brush is + * considered for - adjusted to the frame, from which the background brush is + * taken. + * + * @parem bLowerMode + * input parameter - boolean indicating, if background brush should *not* be + * taken from parent. + * + * @param bConsiderTextBox + * consider the TextBox of this fly frame (if there is any) when determining + * the background color, useful for automatic font color. + * + * @return true, if a background brush for the frame is found + */ +bool SwFrame::GetBackgroundBrush( + drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes, + const SvxBrushItem* & rpBrush, + const Color*& rpCol, + SwRect &rOrigRect, + bool bLowerMode, + bool bConsiderTextBox ) const +{ + const SwFrame *pFrame = this; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const SwViewOption *pOpt = pSh->GetViewOptions(); + rpBrush = nullptr; + rpCol = nullptr; + do + { + if ( pFrame->IsPageFrame() && !pOpt->IsPageBack() ) + return false; + + if (pFrame->supportsFullDrawingLayerFillAttributeSet()) + { + bool bHandledTextBox = false; + if (pFrame->IsFlyFrame() && bConsiderTextBox) + { + const SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrame*>(pFrame); + SwFrameFormat* pShape + = SwTextBoxHelper::getOtherTextBoxFormat(pFlyFrame->GetFormat(), RES_FLYFRMFMT); + if (pShape) + { + SdrObject* pObject = pShape->FindRealSdrObject(); + if (pObject) + { + // Work with the fill attributes of the shape of the fly frame. + rFillAttributes = + std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>( + pObject->GetMergedItemSet()); + bHandledTextBox = true; + } + } + } + + if (!bHandledTextBox) + rFillAttributes = pFrame->getSdrAllFillAttributesHelper(); + } + const SvxBrushItem &rBack = pFrame->GetAttrSet()->GetBackground(); + + if( pFrame->IsSctFrame() ) + { + const SwSection* pSection = static_cast<const SwSectionFrame*>(pFrame)->GetSection(); + // Note: If frame <pFrame> is a section of the index and + // it its background color is "no fill"/"auto fill" and + // it has no background graphic and + // we are not in the page preview and + // we are not in read-only mode and + // option "index shadings" is set and + // the output is not the printer + // then set <rpCol> to the color of the index shading + if( pSection && ( SectionType::ToxHeader == pSection->GetType() || + SectionType::ToxContent == pSection->GetType() ) && + (rBack.GetColor() == COL_TRANSPARENT) && + rBack.GetGraphicPos() == GPOS_NONE && + !pOpt->IsPagePreview() && + !pOpt->IsReadonly() && + // #114856# Form view + !pOpt->IsFormView() && + SwViewOption::IsIndexShadings() && + !pOpt->IsPDFExport() && + pSh->GetOut()->GetOutDevType() != OUTDEV_PRINTER ) + { + rpCol = &SwViewOption::GetIndexShadingsColor(); + } + } + + // determine, if background draw of frame <pFrame> considers transparency + // Status Quo: background transparency have to be + // considered for fly frames + const bool bConsiderBackgroundTransparency = pFrame->IsFlyFrame(); + + // #i125189# Do not base the decision for using the parent's fill style for this + // frame when the new DrawingLayer FillAttributes are used on the SdrAllFillAttributesHelper + // information. There the data is already optimized to no fill in the case that the + // transparence is at 100% while no fill is the criteria for derivation + bool bNewDrawingLayerFillStyleIsUsedAndNotNoFill(false); + + if(rFillAttributes) + { + // the new DrawingLayer FillStyle is used + if(rFillAttributes->isUsed()) + { + // it's not drawing::FillStyle_NONE + bNewDrawingLayerFillStyleIsUsedAndNotNoFill = true; + } + else + { + // maybe optimized already when 100% transparency is used somewhere, need to test + // XFillStyleItem directly from the model data + const drawing::FillStyle eFillStyle(pFrame->GetAttrSet()->Get(XATTR_FILLSTYLE).GetValue()); + + if(drawing::FillStyle_NONE != eFillStyle) + { + bNewDrawingLayerFillStyleIsUsedAndNotNoFill = true; + } + } + } + + // add condition: + // If <bConsiderBackgroundTransparency> is set - see above -, + // return brush of frame <pFrame>, if its color is *not* "no fill"/"auto fill" + if ( + // #i125189# Done when the new DrawingLayer FillAttributes are used and + // not drawing::FillStyle_NONE (see above) + bNewDrawingLayerFillStyleIsUsedAndNotNoFill || + + // done when SvxBrushItem is used + !rBack.GetColor().GetTransparency() || rBack.GetGraphicPos() != GPOS_NONE || + + // done when direct color is forced + rpCol || + + // done when consider BG transparency and color is not completely transparent + (bConsiderBackgroundTransparency && (rBack.GetColor() != COL_TRANSPARENT)) + ) + { + rpBrush = &rBack; + if ( pFrame->IsPageFrame() && pSh->GetViewOptions()->getBrowseMode() ) + { + rOrigRect = pFrame->getFrameArea(); + } + else + { + if ( pFrame->getFrameArea().SSize() != pFrame->getFramePrintArea().SSize() ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + ::lcl_CalcBorderRect( rOrigRect, pFrame, rAttrs, false, gProp ); + } + else + { + rOrigRect = pFrame->getFramePrintArea(); + rOrigRect += pFrame->getFrameArea().Pos(); + } + } + + return true; + } + + if ( bLowerMode ) + { + // Do not try to get background brush from parent (anchor/upper) + return false; + } + + // get parent frame - anchor or upper - for next loop + if ( pFrame->IsFlyFrame() ) + { + pFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + } + else + { + pFrame = pFrame->GetUpper(); + } + } while ( pFrame ); + + return false; +} + +void SetOutDevAndWin( SwViewShell *pSh, OutputDevice *pO, + vcl::Window *pW, sal_uInt16 nZoom ) +{ + pSh->mpOut = pO; + pSh->mpWin = pW; + pSh->mpOpt->SetZoom( nZoom ); +} + +Graphic SwFrameFormat::MakeGraphic( ImageMap* ) +{ + return Graphic(); +} + +Graphic SwFlyFrameFormat::MakeGraphic( ImageMap* pMap ) +{ + Graphic aRet; + //search any Fly! + SwIterator<SwFrame,SwFormat> aIter( *this ); + SwFrame *pFirst = aIter.First(); + SwViewShell *const pSh = + pFirst ? pFirst->getRootFrame()->GetCurrShell() : nullptr; + if (nullptr != pSh) + { + SwViewShell *pOldGlobal = gProp.pSGlobalShell; + gProp.pSGlobalShell = pSh; + + bool bNoteURL = pMap && + SfxItemState::SET != GetAttrSet().GetItemState( RES_URL ); + if( bNoteURL ) + { + OSL_ENSURE( !pNoteURL, "MakeGraphic: pNoteURL already used? " ); + pNoteURL = new SwNoteURL; + } + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pFirst); + + OutputDevice *pOld = pSh->GetOut(); + ScopedVclPtrInstance< VirtualDevice > pDev( *pOld ); + pDev->EnableOutput( false ); + + GDIMetaFile aMet; + MapMode aMap( pOld->GetMapMode().GetMapUnit() ); + pDev->SetMapMode( aMap ); + aMet.SetPrefMapMode( aMap ); + + ::SwCalcPixStatics( pSh->GetOut() ); + aMet.SetPrefSize( pFly->getFrameArea().SSize() ); + + aMet.Record( pDev.get() ); + pDev->SetLineColor(); + pDev->SetFillColor(); + pDev->SetFont( pOld->GetFont() ); + + //Enlarge the rectangle if needed, so the border is painted too. + SwRect aOut( pFly->getFrameArea() ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFly ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + if ( rAttrs.CalcRightLine() ) + aOut.AddWidth(2*gProp.nSPixelSzW ); + if ( rAttrs.CalcBottomLine() ) + aOut.AddHeight(2*gProp.nSPixelSzH ); + + // #i92711# start Pre/PostPaint encapsulation before pOut is changed to the buffering VDev + const vcl::Region aRepaintRegion(aOut.SVRect()); + pSh->DLPrePaint2(aRepaintRegion); + + vcl::Window *pWin = pSh->GetWin(); + sal_uInt16 nZoom = pSh->GetViewOptions()->GetZoom(); + ::SetOutDevAndWin( pSh, pDev, nullptr, 100 ); + gProp.bSFlyMetafile = true; + gProp.pSFlyMetafileOut = pWin; + + SwViewShellImp *pImp = pSh->Imp(); + gProp.pSFlyOnlyDraw = pFly; + gProp.pSLines.reset(new SwLineRects); + + // determine page, fly frame is on + const SwPageFrame* pFlyPage = pFly->FindPageFrame(); + const Color aPageBackgrdColor(pFlyPage->GetDrawBackgrdColor()); + const IDocumentDrawModelAccess& rIDDMA = pSh->getIDocumentDrawModelAccess(); + // --> OD #i76669# + SwViewObjectContactRedirector aSwRedirector( *pSh ); + // <-- + pImp->PaintLayer( rIDDMA.GetHellId(), nullptr, + *pFlyPage, aOut, &aPageBackgrdColor, + pFlyPage->IsRightToLeft(), + &aSwRedirector ); + gProp.pSLines->PaintLines( pDev, gProp ); + if ( pFly->IsFlyInContentFrame() ) + pFly->PaintSwFrame( *pDev, aOut ); + gProp.pSLines->PaintLines( pDev, gProp ); + pImp->PaintLayer( rIDDMA.GetHeavenId(), nullptr, + *pFlyPage, aOut, &aPageBackgrdColor, + pFlyPage->IsRightToLeft(), + &aSwRedirector ); + gProp.pSLines->PaintLines( pDev, gProp ); + gProp.pSLines.reset(); + gProp.pSFlyOnlyDraw = nullptr; + + gProp.pSFlyMetafileOut = nullptr; + gProp.bSFlyMetafile = false; + ::SetOutDevAndWin( pSh, pOld, pWin, nZoom ); + + // #i92711# end Pre/PostPaint encapsulation when pOut is back and content is painted + pSh->DLPostPaint2(true); + + aMet.Stop(); + aMet.Move( -pFly->getFrameArea().Left(), -pFly->getFrameArea().Top() ); + aRet = Graphic( aMet ); + + if( bNoteURL ) + { + OSL_ENSURE( pNoteURL, "MakeGraphic: Good Bye, NoteURL." ); + delete pNoteURL; + pNoteURL = nullptr; + } + gProp.pSGlobalShell = pOldGlobal; + } + return aRet; +} + +Graphic SwDrawFrameFormat::MakeGraphic( ImageMap* ) +{ + Graphic aRet; + SwDrawModel* pMod = getIDocumentDrawModelAccess().GetDrawModel(); + if ( pMod ) + { + SdrObject *pObj = FindSdrObject(); + std::unique_ptr<SdrView> pView( new SdrView( *pMod ) ); + SdrPageView *pPgView = pView->ShowSdrPage(pView->GetModel()->GetPage(0)); + pView->MarkObj( pObj, pPgView ); + aRet = pView->GetMarkedObjBitmapEx(); + pView->HideSdrPage(); + } + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/sectfrm.cxx b/sw/source/core/layout/sectfrm.cxx new file mode 100644 index 000000000..2ad9d79e5 --- /dev/null +++ b/sw/source/core/layout/sectfrm.cxx @@ -0,0 +1,2916 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <o3tl/safeint.hxx> +#include <svl/itemiter.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtclbl.hxx> +#include <sectfrm.hxx> +#include <cellfrm.hxx> +#include <section.hxx> +#include <IDocumentSettingAccess.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <fmtclds.hxx> +#include <colfrm.hxx> +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <layouter.hxx> +#include <dbg_lay.hxx> +#include <viewopt.hxx> +#include <viewimp.hxx> +#include <editeng/brushitem.hxx> +#include <fmtftntx.hxx> +#include <flyfrm.hxx> +#include <sortedobjs.hxx> +#include <hints.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> + +namespace +{ +/** + * Performs the correct type of position invalidation depending on if we're in + * CalcContent(). + */ +void InvalidateFramePos(SwFrame* pFrame, bool bInCalcContent) +{ + if (bInCalcContent) + pFrame->InvalidatePos_(); + else + pFrame->InvalidatePos(); +} +} + +SwSectionFrame::SwSectionFrame( SwSection &rSect, SwFrame* pSib ) + : SwLayoutFrame( rSect.GetFormat(), pSib ) + , SwFlowFrame( static_cast<SwFrame&>(*this) ) + , m_pSection( &rSect ) + , m_bFootnoteAtEnd(false) + , m_bEndnAtEnd(false) + , m_bContentLock(false) + , m_bOwnFootnoteNum(false) + , m_bFootnoteLock(false) +{ + mnFrameType = SwFrameType::Section; + + CalcFootnoteAtEndFlag(); + CalcEndAtEndFlag(); +} + +SwSectionFrame::SwSectionFrame( SwSectionFrame &rSect, bool bMaster ) : + SwLayoutFrame( rSect.GetFormat(), rSect.getRootFrame() ), + SwFlowFrame( static_cast<SwFrame&>(*this) ), + m_pSection( rSect.GetSection() ), + m_bFootnoteAtEnd( rSect.IsFootnoteAtEnd() ), + m_bEndnAtEnd( rSect.IsEndnAtEnd() ), + m_bContentLock( false ), + m_bOwnFootnoteNum( false ), + m_bFootnoteLock( false ) +{ + mnFrameType = SwFrameType::Section; + + PROTOCOL( this, PROT::Section, bMaster ? DbgAction::CreateMaster : DbgAction::CreateFollow, &rSect ) + + if( bMaster ) + { + SwSectionFrame* pMaster = rSect.IsFollow() ? rSect.FindMaster() : nullptr; + if (pMaster) + pMaster->SetFollow( this ); + SetFollow( &rSect ); + } + else + { + SetFollow( rSect.GetFollow() ); + rSect.SetFollow( this ); + if( !GetFollow() ) + rSect.SimpleFormat(); + if( !rSect.IsColLocked() ) + rSect.InvalidateSize(); + } +} + +// NOTE: call <SwSectionFrame::Init()> directly after creation of a new section +// frame and its insert in the layout. +void SwSectionFrame::Init() +{ + assert(GetUpper() && "SwSectionFrame::Init before insertion?!"); + SwRectFnSet aRectFnSet(this); + long nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, nWidth ); + aRectFnSet.SetHeight( aFrm, 0 ); + } + + // #109700# LRSpace for sections + const SvxLRSpaceItem& rLRSpace = GetFormat()->GetLRSpace(); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetLeft( aPrt, rLRSpace.GetLeft() ); + aRectFnSet.SetWidth( aPrt, nWidth - rLRSpace.GetLeft() - rLRSpace.GetRight() ); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + const SwFormatCol &rCol = GetFormat()->GetCol(); + if( ( rCol.GetNumCols() > 1 || IsAnyNoteAtEnd() ) && !IsInFootnote() ) + { + const SwFormatCol *pOld = Lower() ? &rCol : new SwFormatCol; + ChgColumns( *pOld, rCol, IsAnyNoteAtEnd() ); + if( pOld != &rCol ) + delete pOld; + } +} + +void SwSectionFrame::DestroyImpl() +{ + if( GetFormat() && !GetFormat()->GetDoc()->IsInDtor() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame ) + pRootFrame->RemoveFromList( this ); + if( IsFollow() ) + { + SwSectionFrame *pMaster = FindMaster(); + if( pMaster ) + { + PROTOCOL( this, PROT::Section, DbgAction::DelFollow, pMaster ) + pMaster->SetFollow( GetFollow() ); + // A Master always grabs the space until the lower edge of his + // Upper. If he doesn't have a Follow anymore, he can + // release it, which is why the Size of the Master is + // invalidated. + if( !GetFollow() ) + pMaster->InvalidateSize(); + } + } +#if defined DBG_UTIL + else if( HasFollow() ) + { + PROTOCOL( this, PROT::Section, DbgAction::DelMaster, GetFollow() ) + } +#endif + } + + SwLayoutFrame::DestroyImpl(); +} + +SwSectionFrame::~SwSectionFrame() +{ +} + +void SwSectionFrame::DelEmpty( bool bRemove ) +{ + if( IsColLocked() ) + { + OSL_ENSURE( !bRemove, "Don't delete locked SectionFrames" ); + return; + } + SwFrame* pUp = GetUpper(); + if( pUp ) + { + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. + { + SwViewShell* pViewShell( getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(FindNextCnt( true )), + dynamic_cast<SwTextFrame*>(FindPrevCnt()) ); + } + } + Cut_( bRemove ); + } + SwSectionFrame *pMaster = IsFollow() ? FindMaster() : nullptr; + if (pMaster) + { + pMaster->SetFollow( GetFollow() ); + // A Master always grabs the space until the lower edge of his + // Upper. If he doesn't have a Follow anymore, he can + // release it, which is why the Size of the Master is + // invalidated. + if( !GetFollow() && !pMaster->IsColLocked() ) + pMaster->InvalidateSize(); + } + SetFollow(nullptr); + if( pUp ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( 0 ); + } + + // If we are destroyed immediately anyway, we don't need + // to put us into the list + if( bRemove ) + { // If we already were half dead before this DelEmpty, + // we are likely in the list and have to remove us from + // it + if( !m_pSection && getRootFrame() ) + getRootFrame()->RemoveFromList( this ); + } + else if( getRootFrame() ) + { + getRootFrame()->InsertEmptySct( this ); + } + + m_pSection = nullptr; // like this a reanimation is virtually impossible though + } +} + +void SwSectionFrame::Cut() +{ + Cut_( true ); +} + +void SwSectionFrame::Cut_( bool bRemove ) +{ + OSL_ENSURE( GetUpper(), "Cut without Upper()." ); + + PROTOCOL( this, PROT::Cut, DbgAction::NONE, GetUpper() ) + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + SwFrame *pFrame = GetNext(); + SwFrame* pPrepFrame = nullptr; + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + pFrame = pFrame->GetNext(); + if( pFrame ) + { // The former successor might have calculated a gap to the predecessor + // which is now obsolete since he becomes the first + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if ( pFrame && pFrame->IsContentFrame() ) + { + pFrame->InvalidatePage( pPage ); + if( IsInFootnote() && !GetIndPrev() ) + pPrepFrame = pFrame; + } + } + else + { + InvalidateNextPos(); + // Someone has to take over the retouching: predecessor or Upper + if ( nullptr != (pFrame = GetPrev()) ) + { pFrame->SetRetouche(); + pFrame->Prepare( PrepareHint::WidowsOrphans ); + if ( pFrame->IsContentFrame() ) + pFrame->InvalidatePage( pPage ); + } + // If I am (was) the only FlowFrame in my Upper, then he has to take over + // the retouching. + // Furthermore a blank page could have emerged + else + { SwRootFrame *pRoot = static_cast<SwRootFrame*>(pPage->GetUpper()); + pRoot->SetSuperfluous(); + GetUpper()->SetCompletePaint(); + } + } + // First remove, then shrink Upper + SwLayoutFrame *pUp = GetUpper(); + if( bRemove ) + { + RemoveFromLayout(); + if( pUp && !pUp->Lower() && pUp->IsFootnoteFrame() && !pUp->IsColLocked() && + pUp->GetUpper() ) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + pUp = nullptr; + } + } + if( pPrepFrame ) + pPrepFrame->Prepare( PrepareHint::FootnoteInvalidation ); + if ( pUp ) + { + SwRectFnSet aRectFnSet(this); + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight > 0 ) + { + if( !bRemove ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, 0 ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + pUp->Shrink( nFrameHeight ); + } + } +} + +void SwSectionFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "No parent for Paste()." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm my own parent." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetUpper(), + "I am still registered somewhere." ); + + PROTOCOL( this, PROT::Paste, DbgAction::NONE, GetUpper() ) + + // Add to the tree + SwSectionFrame* pSect = pParent->FindSctFrame(); + // Assure that parent is not inside a table frame, which is inside the found section frame. + if ( pSect ) + { + SwTabFrame* pTableFrame = pParent->FindTabFrame(); + if ( pTableFrame && + pSect->IsAnLower( pTableFrame ) ) + { + pSect = nullptr; + } + } + + SwRectFnSet aRectFnSet(pParent); + if( pSect && HasToBreak( pSect ) ) + { + if( pParent->IsColBodyFrame() ) // dealing with a single-column area + { + // If we are coincidentally at the end of a column, pSibling + // has to point to the first frame of the next column in order + // for the content of the next column to be moved correctly to the + // newly created pSect by the InsertGroup + SwColumnFrame *pCol = static_cast<SwColumnFrame*>(pParent->GetUpper()); + while( !pSibling && nullptr != ( pCol = static_cast<SwColumnFrame*>(pCol->GetNext()) ) ) + pSibling = static_cast<SwLayoutFrame*>(pCol->Lower())->Lower(); + if( pSibling ) + { + // Even worse: every following column content has to + // be attached to the pSibling-chain in order to be + // taken along + SwFrame *pTmp = pSibling; + while ( nullptr != ( pCol = static_cast<SwColumnFrame*>(pCol->GetNext()) ) ) + { + while ( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + SwFrame* pSave = ::SaveContent( pCol ); + if (pSave) + ::RestoreContent( pSave, pSibling->GetUpper(), pTmp ); + } + } + } + pParent = pSect; + pSect = new SwSectionFrame( *static_cast<SwSectionFrame*>(pParent)->GetSection(), pParent ); + // if pParent is decomposed into two parts, its Follow has to be attached + // to the new second part + pSect->SetFollow( static_cast<SwSectionFrame*>(pParent)->GetFollow() ); + static_cast<SwSectionFrame*>(pParent)->SetFollow( nullptr ); + if( pSect->GetFollow() ) + pParent->InvalidateSize_(); + + const bool bInserted = InsertGroupBefore( pParent, pSibling, pSect ); + if (bInserted) + { + pSect->Init(); + aRectFnSet.MakePos( *pSect, pSect->GetUpper(), pSect->GetPrev(), true); + } + if( !static_cast<SwLayoutFrame*>(pParent)->Lower() ) + { + SwSectionFrame::MoveContentAndDelete( static_cast<SwSectionFrame*>(pParent), false ); + pParent = this; + } + } + else + InsertGroupBefore( pParent, pSibling, nullptr ); + + InvalidateAll_(); + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + + if ( pSibling ) + { + pSibling->InvalidatePos_(); + pSibling->InvalidatePrt_(); + if ( pSibling->IsContentFrame() ) + pSibling->InvalidatePage( pPage ); + } + + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight ) + pParent->Grow( nFrameHeight ); + + if ( GetPrev() && !IsFollow() ) + { + GetPrev()->InvalidateSize(); + if ( GetPrev()->IsContentFrame() ) + GetPrev()->InvalidatePage( pPage ); + } +} + +/** +|* Here it's decided whether the this-SectionFrame should break up +|* the passed (Section)frm (or not). +|* Initially, all superior sections are broken up. Later on that could +|* be made configurable. +|*/ +bool SwSectionFrame::HasToBreak( const SwFrame* pFrame ) const +{ + if( !pFrame->IsSctFrame() ) + return false; + + const SwSectionFormat *pTmp = static_cast<const SwSectionFormat*>(GetFormat()); + + const SwFrameFormat *pOtherFormat = static_cast<const SwSectionFrame*>(pFrame)->GetFormat(); + do + { + pTmp = pTmp->GetParent(); + if( !pTmp ) + return false; + if( pTmp == pOtherFormat ) + return true; + } while( true ); // ( pTmp->GetSect().GetValue() ); +} + +/** +|* Merges two SectionFrames, in case it's about the same section. +|* This can be necessary when a (sub)section is deleted that had +|* divided another part into two. +|*/ +void SwSectionFrame::MergeNext( SwSectionFrame* pNxt ) +{ + if (pNxt->IsDeleteForbidden()) + return; + + if (!pNxt->IsJoinLocked() && GetSection() == pNxt->GetSection()) + { + PROTOCOL( this, PROT::Section, DbgAction::Merge, pNxt ) + + SwFrame* pTmp = ::SaveContent( pNxt ); + if( pTmp ) + { + SwFrame* pLast = Lower(); + SwLayoutFrame* pLay = this; + if( pLast ) + { + while( pLast->GetNext() ) + pLast = pLast->GetNext(); + if( pLast->IsColumnFrame() ) + { // Columns now with BodyFrame + pLay = static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pLast)->Lower()); + pLast = pLay->Lower(); + if( pLast ) + while( pLast->GetNext() ) + pLast = pLast->GetNext(); + } + } + ::RestoreContent( pTmp, pLay, pLast ); + } + SetFollow( pNxt->GetFollow() ); + pNxt->SetFollow( nullptr ); + pNxt->Cut(); + SwFrame::DestroyFrame(pNxt); + InvalidateSize(); + } +} + +/** +|* Divides a SectionFrame into two parts. The second one starts with the +|* passed frame. +|* This is required when inserting an inner section, because the MoveFwd +|* cannot have the desired effect within a frame or a table cell. +|*/ +bool SwSectionFrame::SplitSect( SwFrame* pFrame, bool bApres ) +{ + assert(pFrame && "SplitSect: Why?"); + SwFrame* pOther = bApres ? pFrame->FindNext() : pFrame->FindPrev(); + if( !pOther ) + return false; + SwSectionFrame* pSect = pOther->FindSctFrame(); + if( pSect != this ) + return false; + // Put the content aside + SwFrame* pSav = ::SaveContent( this, bApres ? pOther : pFrame ); + OSL_ENSURE( pSav, "SplitSect: What's on?" ); + if( pSav ) // be robust + { // Create a new SctFrame, not as a Follower/master + SwSectionFrame* pNew = new SwSectionFrame( *pSect->GetSection(), pSect ); + pNew->InsertBehind( pSect->GetUpper(), pSect ); + pNew->Init(); + SwRectFnSet aRectFnSet(this); + aRectFnSet.MakePos( *pNew, nullptr, pSect, true ); + // OD 25.03.2003 #108339# - restore content: + // determine layout frame for restoring content after the initialization + // of the section frame. In the section initialization the columns are + // created. + { + SwLayoutFrame* pLay = pNew; + // Search for last layout frame, e.g. for columned sections. + while( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = static_cast<SwLayoutFrame*>(pLay->Lower()); + ::RestoreContent( pSav, pLay, nullptr ); + } + InvalidateSize_(); + if( HasFollow() ) + { + pNew->SetFollow( GetFollow() ); + SetFollow( nullptr ); + } + return true; + } + return false; +} + +/** +|* MoveContent is called for destroying a SectionFrames, due to +|* the cancellation or hiding of a section, to handle the content. +|* If the SectionFrame hasn't broken up another one, then the content +|* is moved to the Upper. Otherwise the content is moved to another +|* SectionFrame, which has to be potentially merged. +|*/ +// If a multi-column section is cancelled, the ContentFrames have to be +// invalidated +static void lcl_InvalidateInfFlags( SwFrame* pFrame, bool bInva ) +{ + while ( pFrame ) + { + pFrame->InvalidateInfFlags(); + if( bInva ) + { + pFrame->InvalidatePos_(); + pFrame->InvalidateSize_(); + pFrame->InvalidatePrt_(); + } + if( pFrame->IsLayoutFrame() ) + lcl_InvalidateInfFlags( static_cast<SwLayoutFrame*>(pFrame)->GetLower(), false ); + pFrame = pFrame->GetNext(); + } +} + +// Works like SwContentFrame::ImplGetNextContentFrame, but starts with a LayoutFrame +static SwContentFrame* lcl_GetNextContentFrame( const SwLayoutFrame* pLay, bool bFwd ) +{ + if ( bFwd ) + { + if ( pLay->GetNext() && pLay->GetNext()->IsContentFrame() ) + return const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(pLay->GetNext())); + } + else + { + if ( pLay->GetPrev() && pLay->GetPrev()->IsContentFrame() ) + return const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(pLay->GetPrev())); + } + + const SwFrame* pFrame = pLay; + SwContentFrame *pContentFrame = nullptr; + bool bGoingUp = true; + do { + const SwFrame *p = nullptr; + bool bGoingFwdOrBwd = false; + + bool bGoingDown = !bGoingUp && pFrame->IsLayoutFrame(); + if (bGoingDown) + { + p = static_cast<const SwLayoutFrame*>(pFrame)->Lower(); + bGoingDown = nullptr != p; + } + if ( !bGoingDown ) + { + p = pFrame->IsFlyFrame() ? + ( bFwd ? static_cast<const SwFlyFrame*>(pFrame)->GetNextLink() : static_cast<const SwFlyFrame*>(pFrame)->GetPrevLink() ) : + ( bFwd ? pFrame->GetNext() :pFrame->GetPrev() ); + bGoingFwdOrBwd = nullptr != p; + if ( !bGoingFwdOrBwd ) + { + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + return nullptr; + } + } + + bGoingUp = !( bGoingFwdOrBwd || bGoingDown ); + assert(p); + if (!bFwd && bGoingDown) + while ( p->GetNext() ) + p = p->GetNext(); + + pFrame = p; + } while ( nullptr == (pContentFrame = (pFrame->IsContentFrame() ? const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(pFrame)) : nullptr) )); + + return pContentFrame; +} + +namespace +{ + SwLayoutFrame* FirstLeaf(SwSectionFrame* pLayFrame) + { + if (pLayFrame->Lower() && pLayFrame->Lower()->IsColumnFrame()) + return pLayFrame->GetNextLayoutLeaf(); + return pLayFrame; + } + + /// Checks if pFrame has a parent that can contain a split section frame. + bool CanContainSplitSection(const SwFrame* pFrame) + { + if (!pFrame->IsInTab()) + return true; + + // The frame is in a table, see if the table is in a section. + bool bRet = !pFrame->FindTabFrame()->IsInSct(); + + if (bRet) + { + // Don't try to split if the frame itself is a section frame with + // multiple columns. + if (pFrame->IsSctFrame()) + { + const SwFrame* pLower = pFrame->GetLower(); + if (pLower && pLower->IsColumnFrame()) + bRet = false; + } + } + + return bRet; + } +} + +void SwSectionFrame::MoveContentAndDelete( SwSectionFrame* pDel, bool bSave ) +{ + bool bSize = pDel->Lower() && pDel->Lower()->IsColumnFrame(); + SwFrame* pPrv = pDel->GetPrev(); + SwLayoutFrame* pUp = pDel->GetUpper(); + // OD 27.03.2003 #i12711# - initialize local pointer variables. + SwSectionFrame* pPrvSct = nullptr; + SwSectionFrame* pNxtSct = nullptr; + SwSectionFormat* pParent = static_cast<SwSectionFormat*>(pDel->GetFormat())->GetParent(); + if( pDel->IsInTab() && pParent ) + { + SwTabFrame *pTab = pDel->FindTabFrame(); + // If we are within a table, we can only have broken up sections that + // are inside as well, but not a section that contains the whole table. + if( pTab->IsInSct() && pParent == pTab->FindSctFrame()->GetFormat() ) + pParent = nullptr; + } + // If our Format has a parent, we have probably broken up another + // SectionFrame, which has to be checked. To do so we first acquire the + // succeeding and the preceding ContentFrame, let's see if they + // lay in the SectionFrames. + // OD 27.03.2003 #i12711# - check, if previous and next section belonging + // together and can be joined, *not* only if deleted section contains content. + if ( pParent ) + { + SwFrame* pPrvContent = lcl_GetNextContentFrame( pDel, false ); + pPrvSct = pPrvContent ? pPrvContent->FindSctFrame() : nullptr; + SwFrame* pNxtContent = lcl_GetNextContentFrame( pDel, true ); + pNxtSct = pNxtContent ? pNxtContent->FindSctFrame() : nullptr; + } + else + { + pParent = nullptr; + pPrvSct = pNxtSct = nullptr; + } + + // Now the content is put aside and the frame is destroyed + SwFrame *pSave = bSave ? ::SaveContent( pDel ) : nullptr; + bool bOldFootnote = true; + if( pSave && pUp->IsFootnoteFrame() ) + { + bOldFootnote = static_cast<SwFootnoteFrame*>(pUp)->IsColLocked(); + static_cast<SwFootnoteFrame*>(pUp)->ColLock(); + } + pDel->DelEmpty( true ); + SwFrame::DestroyFrame(pDel); + if( pParent ) + { // Search for the appropriate insert position + if( pNxtSct && pNxtSct->GetFormat() == pParent ) + { // Here we can insert ourselves at the beginning + pUp = FirstLeaf( pNxtSct ); + pPrv = nullptr; + if( pPrvSct && ( pPrvSct->GetFormat() != pParent ) ) + pPrvSct = nullptr; // In order that nothing is merged + } + else if( pPrvSct && pPrvSct->GetFormat() == pParent ) + { // Wonderful, here we can insert ourselves at the end + pUp = pPrvSct; + if( pUp->Lower() && pUp->Lower()->IsColumnFrame() ) + { + pUp = static_cast<SwLayoutFrame*>(pUp->GetLastLower()); + // The body of the last column + pUp = static_cast<SwLayoutFrame*>(pUp->Lower()); + } + // In order to perform the insertion after the last one + pPrv = pUp->GetLastLower(); + pPrvSct = nullptr; // Such that nothing is merged + } + else + { + if( pSave ) + { // Following situations: before and after the section-to-be + // deleted there is the section boundary of the enclosing + // section, or another (sibling) section connects subsequently, + // that derives from the same Parent. + // In that case, there's not (yet) a part of our parent available + // that can store the content, so we create it here. + pPrvSct = new SwSectionFrame( *pParent->GetSection(), pUp ); + pPrvSct->InsertBehind( pUp, pPrv ); + pPrvSct->Init(); + SwRectFnSet aRectFnSet(pUp); + aRectFnSet.MakePos( *pPrvSct, pUp, pPrv, true ); + pUp = FirstLeaf( pPrvSct ); + pPrv = nullptr; + } + pPrvSct = nullptr; // Such that nothing will be merged + } + } + // The content is going to be inserted... + if( pSave ) + { + lcl_InvalidateInfFlags( pSave, bSize ); + ::RestoreContent( pSave, pUp, pPrv ); + pUp->FindPageFrame()->InvalidateContent(); + if( !bOldFootnote ) + static_cast<SwFootnoteFrame*>(pUp)->ColUnlock(); + } + // Now two parts of the superior section could possibly be merged + if( pPrvSct && !pPrvSct->IsJoinLocked() ) + { + OSL_ENSURE( pNxtSct, "MoveContent: No Merge" ); + pPrvSct->MergeNext( pNxtSct ); + } +} + +void SwSectionFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( IsJoinLocked() || IsColLocked() || StackHack::IsLocked() || StackHack::Count() > 50 ) + return; + if( !m_pSection ) // Via DelEmpty + { +#ifdef DBG_UTIL + OSL_ENSURE( getRootFrame()->IsInDelList( this ), "SectionFrame without Section" ); +#endif + if( !isFrameAreaPositionValid() ) + { + if( GetUpper() ) + { + SwRectFnSet aRectFnSet(GetUpper()); + aRectFnSet.MakePos( *this, GetUpper(), GetPrev(), false ); + } + + if (getFrameArea().Height() == 0) + { + // SwLayoutFrame::MakeAll() is not called for to-be-deleted + // section frames (which would invalidate the position of the + // next frame via the SwLayNotify dtor), so call it manually. + if (SwFrame* pNext = GetNext()) + pNext->InvalidatePos(); + } + } + + setFrameAreaPositionValid(true); + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + return; + } + LockJoin(); // I don't let myself to be destroyed on the way + + while( GetNext() && GetNext() == GetFollow() ) + { + const SwFrame* pFoll = GetFollow(); + MergeNext( static_cast<SwSectionFrame*>(GetNext()) ); + if( pFoll == GetFollow() ) + break; + } + + // OD 2004-03-15 #116561# - In online layout join the follows, if section + // can grow. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + // Split sections inside table cells: need to merge all follows of the + // section here, as later we won't attempt doing so. + bool bCanContainSplitSection = false; + if (IsInTab() && GetUpper()) + bCanContainSplitSection = CanContainSplitSection(GetUpper()); + + if( pSh && (pSh->GetViewOptions()->getBrowseMode() || bCanContainSplitSection) && + ( Grow( LONG_MAX, true ) > 0 ) ) + { + while( GetFollow() ) + { + const SwFrame* pFoll = GetFollow(); + MergeNext( GetFollow() ); + if( pFoll == GetFollow() ) + break; + } + } + + // A section with Follow uses all the space until the lower edge of the + // Upper. If it moves, its size can grow or decrease... + if( !isFrameAreaPositionValid() && ToMaximize( false ) ) + { + setFrameAreaSizeValid(false); + } + + SwLayoutFrame::MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + + if (IsInTab()) + { + // In case the section is in a table, then calculate the lower right + // now. Just setting the valid size flag of the lower to false may not + // be enough, as lcl_RecalcRow() can call + // SwFrame::ValidateThisAndAllLowers(), and then we don't attempt + // calculating the proper position of the lower. + SwFrame* pLower = Lower(); + if (pLower && !pLower->isFrameAreaPositionValid()) + pLower->Calc(pRenderContext); + } + + UnlockJoin(); + if( m_pSection && IsSuperfluous() ) + DelEmpty( false ); +} + +bool SwSectionFrame::ShouldBwdMoved( SwLayoutFrame *, bool & ) +{ + OSL_FAIL( "Oops, where is my tinfoil hat?" ); + return false; +} + +const SwSectionFormat* SwSectionFrame::GetEndSectFormat_() const +{ + const SwSectionFormat *pFormat = m_pSection->GetFormat(); + while( !pFormat->GetEndAtTextEnd().IsAtEnd() ) + { + if( dynamic_cast< const SwSectionFormat *>( pFormat->GetRegisteredIn()) != nullptr ) + pFormat = static_cast<const SwSectionFormat*>(pFormat->GetRegisteredIn()); + else + return nullptr; + } + return pFormat; +} + +static void lcl_FindContentFrame( SwContentFrame* &rpContentFrame, SwFootnoteFrame* &rpFootnoteFrame, + SwFrame* pFrame, bool &rbChkFootnote ) +{ + if( pFrame ) + { + while( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); + while( !rpContentFrame && pFrame ) + { + if( pFrame->IsContentFrame() ) + rpContentFrame = static_cast<SwContentFrame*>(pFrame); + else if( pFrame->IsLayoutFrame() ) + { + if( pFrame->IsFootnoteFrame() ) + { + if( rbChkFootnote ) + { + rpFootnoteFrame = static_cast<SwFootnoteFrame*>(pFrame); + rbChkFootnote = rpFootnoteFrame->GetAttr()->GetFootnote().IsEndNote(); + } + } + else + lcl_FindContentFrame( rpContentFrame, rpFootnoteFrame, + static_cast<SwLayoutFrame*>(pFrame)->Lower(), rbChkFootnote ); + } + pFrame = pFrame->GetPrev(); + } + } +} + +SwContentFrame *SwSectionFrame::FindLastContent( SwFindMode nMode ) +{ + SwContentFrame *pRet = nullptr; + SwFootnoteFrame *pFootnoteFrame = nullptr; + SwSectionFrame *pSect = this; + if( nMode != SwFindMode::None ) + { + const SwSectionFormat *pFormat = IsEndnAtEnd() ? GetEndSectFormat() : + m_pSection->GetFormat(); + do { + while( pSect->HasFollow() ) + pSect = pSect->GetFollow(); + SwFrame* pTmp = pSect->FindNext(); + while( pTmp && pTmp->IsSctFrame() && + !static_cast<SwSectionFrame*>(pTmp)->GetSection() ) + pTmp = pTmp->FindNext(); + if( pTmp && pTmp->IsSctFrame() && + static_cast<SwSectionFrame*>(pTmp)->IsDescendantFrom( pFormat ) ) + pSect = static_cast<SwSectionFrame*>(pTmp); + else + break; + } while( true ); + } + bool bFootnoteFound = nMode == SwFindMode::EndNote; + do + { + lcl_FindContentFrame( pRet, pFootnoteFrame, pSect->Lower(), bFootnoteFound ); + if( pRet || !pSect->IsFollow() || nMode == SwFindMode::None || + ( SwFindMode::MyLast == nMode && this == pSect ) ) + break; + pSect = pSect->FindMaster(); + } while( pSect ); + if( ( nMode == SwFindMode::EndNote ) && pFootnoteFrame ) + pRet = pFootnoteFrame->ContainsContent(); + return pRet; +} + +bool SwSectionFrame::CalcMinDiff( SwTwips& rMinDiff ) const +{ + if( ToMaximize( true ) ) + { + SwRectFnSet aRectFnSet(this); + rMinDiff = aRectFnSet.GetPrtBottom(*GetUpper()); + rMinDiff = aRectFnSet.BottomDist( getFrameArea(), rMinDiff ); + return true; + } + return false; +} + +/** + * CollectEndnotes looks for endnotes in the sectionfrm and his follows, + * the endnotes will cut off the layout and put into the array. + * If the first endnote is not a master-SwFootnoteFrame, the whole sectionfrm + * contains only endnotes and it is not necessary to collect them. + */ +static SwFootnoteFrame* lcl_FindEndnote( SwSectionFrame* &rpSect, bool &rbEmpty, + SwLayouter *pLayouter ) +{ + // if rEmpty is set, the rpSect is already searched + SwSectionFrame* pSect = rbEmpty ? rpSect->GetFollow() : rpSect; + while( pSect ) + { + OSL_ENSURE( (pSect->Lower() && pSect->Lower()->IsColumnFrame()) || pSect->GetUpper()->IsFootnoteFrame(), + "InsertEndnotes: Where's my column?" ); + + // i73332: Columned section in endnote + SwColumnFrame* pCol = nullptr; + if(pSect->Lower() && pSect->Lower()->IsColumnFrame()) + pCol = static_cast<SwColumnFrame*>(pSect->Lower()); + + while( pCol ) // check all columns + { + SwFootnoteContFrame* pFootnoteCont = pCol->FindFootnoteCont(); + if( pFootnoteCont ) + { + SwFootnoteFrame* pRet = static_cast<SwFootnoteFrame*>(pFootnoteCont->Lower()); + while( pRet ) // look for endnotes + { + /* CollectEndNode can destroy pRet so we need to get the + next early + */ + SwFootnoteFrame* pRetNext = static_cast<SwFootnoteFrame*>(pRet->GetNext()); + if( pRet->GetAttr()->GetFootnote().IsEndNote() ) + { + if( pRet->GetMaster() ) + { + if( pLayouter ) + pLayouter->CollectEndnote( pRet ); + else + return nullptr; + } + else + return pRet; // Found + } + pRet = pRetNext; + } + } + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } + rpSect = pSect; + pSect = pLayouter ? pSect->GetFollow() : nullptr; + rbEmpty = true; + } + return nullptr; +} + +static void lcl_ColumnRefresh( SwSectionFrame* pSect, bool bFollow ) +{ + vcl::RenderContext* pRenderContext = pSect->getRootFrame()->GetCurrShell()->GetOut(); + while( pSect ) + { + bool bOldLock = pSect->IsColLocked(); + pSect->ColLock(); + if( pSect->Lower() && pSect->Lower()->IsColumnFrame() ) + { + SwColumnFrame *pCol = static_cast<SwColumnFrame*>(pSect->Lower()); + do + { pCol->InvalidateSize_(); + pCol->InvalidatePos_(); + static_cast<SwLayoutFrame*>(pCol)->Lower()->InvalidateSize_(); + pCol->Calc(pRenderContext); // calculation of column and + static_cast<SwLayoutFrame*>(pCol)->Lower()->Calc(pRenderContext); // body + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } while ( pCol ); + } + if( !bOldLock ) + pSect->ColUnlock(); + if( bFollow ) + pSect = pSect->GetFollow(); + else + pSect = nullptr; + } +} + +void SwSectionFrame::CollectEndnotes( SwLayouter* pLayouter ) +{ + OSL_ENSURE( IsColLocked(), "CollectEndnotes: You love the risk?" ); + // i73332: Section in footnode does not have columns! + OSL_ENSURE( (Lower() && Lower()->IsColumnFrame()) || GetUpper()->IsFootnoteFrame(), "Where's my column?" ); + + SwSectionFrame* pSect = this; + SwFootnoteFrame* pFootnote; + bool bEmpty = false; + // pSect is the last sectionfrm without endnotes or the this-pointer + // the first sectionfrm with endnotes may be destroyed, when the endnotes + // is cutted + while( nullptr != (pFootnote = lcl_FindEndnote( pSect, bEmpty, pLayouter )) ) + pLayouter->CollectEndnote( pFootnote ); + if( pLayouter->HasEndnotes() ) + lcl_ColumnRefresh( this, true ); +} + +/** Fits the size to the surroundings. +|* +|* Those that have a Follow or foot notes, have to extend until +|* the lower edge of a upper (bMaximize) +|* They must not extend above the Upper, as the case may be one can +|* try to grow its upper (bGrow) +|* If the size had to be changed, the content is calculated. +|* +|* @note: perform calculation of content, only if height has changed (OD 18.09.2002 #100522#) +|*/ +void SwSectionFrame::CheckClipping( bool bGrow, bool bMaximize ) +{ + SwRectFnSet aRectFnSet(this); + long nDiff; + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + if( bGrow && ( !IsInFly() || !GetUpper()->IsColBodyFrame() || + !FindFlyFrame()->IsLocked() ) ) + { + nDiff = -aRectFnSet.BottomDist( getFrameArea(), nDeadLine ); + if( !bMaximize ) + nDiff += Undersize(); + if( nDiff > 0 ) + { + long nAdd = GetUpper()->Grow( nDiff ); + if( aRectFnSet.IsVert() ) + nDeadLine -= nAdd; + else + nDeadLine += nAdd; + } + } + nDiff = -aRectFnSet.BottomDist( getFrameArea(), nDeadLine ); + SetUndersized( !bMaximize && nDiff >= 0 ); + const bool bCalc = ( IsUndersized() || bMaximize ) && + ( nDiff || + aRectFnSet.GetTop(getFramePrintArea()) > aRectFnSet.GetHeight(getFrameArea()) ); + // OD 03.11.2003 #i19737# - introduce local variable <bExtraCalc> to indicate + // that a calculation has to be done beside the value of <bCalc>. + bool bExtraCalc = false; + if( !bCalc && !bGrow && IsAnyNoteAtEnd() && !IsInFootnote() ) + { + SwSectionFrame *pSect = this; + bool bEmpty = false; + SwLayoutFrame* pFootnote = IsEndnAtEnd() ? + lcl_FindEndnote( pSect, bEmpty, nullptr ) : nullptr; + if( pFootnote ) + { + pFootnote = pFootnote->FindFootnoteBossFrame(); + SwFrame* pTmp = FindLastContent( SwFindMode::LastCnt ); + // OD 08.11.2002 #104840# - use <SwLayoutFrame::IsBefore(..)> + if ( pTmp && pFootnote->IsBefore( pTmp->FindFootnoteBossFrame() ) ) + bExtraCalc = true; + } + else if( GetFollow() && !GetFollow()->ContainsAny() ) + bExtraCalc = true; + } + if ( bCalc || bExtraCalc ) + { + nDiff = aRectFnSet.YDiff( nDeadLine, aRectFnSet.GetTop(getFrameArea()) ); + if( nDiff < 0 ) + nDeadLine = aRectFnSet.GetTop(getFrameArea()); + const Size aOldSz( getFramePrintArea().SSize() ); + long nTop = aRectFnSet.GetTopMargin(*this); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetBottom( aFrm, nDeadLine ); + } + + nDiff = aRectFnSet.GetHeight(getFrameArea()); + if( nTop > nDiff ) + nTop = nDiff; + aRectFnSet.SetYMargins( *this, nTop, 0 ); + + // OD 18.09.2002 #100522# + // Determine, if height has changed. + // Note: In vertical layout the height equals the width value. + bool bHeightChanged = aRectFnSet.IsVert() ? + (aOldSz.Width() != getFramePrintArea().Width()) : + (aOldSz.Height() != getFramePrintArea().Height()); + // Last but not least we have changed the height again, thus the inner + // layout (columns) is calculated and the content as well. + // OD 18.09.2002 #100522# + // calculate content, only if height has changed. + // OD 03.11.2003 #i19737# - restriction of content calculation too strong. + // If an endnote has an incorrect position or a follow section contains + // no content except footnotes/endnotes, the content has also been calculated. + if ( ( bHeightChanged || bExtraCalc ) && Lower() ) + { + if( Lower()->IsColumnFrame() ) + { + lcl_ColumnRefresh( this, false ); + ::CalcContent( this ); + } + else + { + ChgLowersProp( aOldSz ); + if( !bMaximize && !IsContentLocked() ) + ::CalcContent( this ); + } + } + } +} + +void SwSectionFrame::SimpleFormat() +{ + if ( IsJoinLocked() || IsColLocked() ) + return; + LockJoin(); + SwRectFnSet aRectFnSet(this); + if( GetPrev() || GetUpper() ) + { + // assure notifications on position changes. + const SwLayNotify aNotify( this ); + aRectFnSet.MakePos( *this, GetUpper(), GetPrev(), false ); + setFrameAreaPositionValid(true); + } + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + // OD 22.10.2002 #97265# - call always method <lcl_ColumnRefresh(..)>, in + // order to get calculated lowers, not only if there space left in its upper. + if( aRectFnSet.BottomDist( getFrameArea(), nDeadLine ) >= 0 ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetBottom( aFrm, nDeadLine ); + } + + long nHeight = aRectFnSet.GetHeight(getFrameArea()); + long nTop = CalcUpperSpace(); + if( nTop > nHeight ) + nTop = nHeight; + aRectFnSet.SetYMargins( *this, nTop, 0 ); + } + lcl_ColumnRefresh( this, false ); + UnlockJoin(); +} + +namespace { + +// #i40147# - helper class to perform extra section format +// to position anchored objects and to keep the position of whose objects locked. +class ExtraFormatToPositionObjs +{ + private: + SwSectionFrame* mpSectFrame; + bool mbExtraFormatPerformed; + + public: + explicit ExtraFormatToPositionObjs( SwSectionFrame& _rSectFrame) + : mpSectFrame( &_rSectFrame ), + mbExtraFormatPerformed( false ) + {} + + ~ExtraFormatToPositionObjs() + { + if ( mbExtraFormatPerformed ) + { + // release keep locked position of lower floating screen objects + SwPageFrame* pPageFrame = mpSectFrame->FindPageFrame(); + SwSortedObjs* pObjs = pPageFrame ? pPageFrame->GetSortedObjs() : nullptr; + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( mpSectFrame->IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetKeepPosLocked( false ); + } + } + } + } + } + + // #i81555# + void InitObjs( SwFrame& rFrame ) + { + SwSortedObjs* pObjs = rFrame.GetDrawObjs(); + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + pAnchoredObj->UnlockPosition(); + pAnchoredObj->SetClearedEnvironment( false ); + } + } + SwLayoutFrame* pLayoutFrame = dynamic_cast<SwLayoutFrame*>(&rFrame); + if ( pLayoutFrame != nullptr ) + { + SwFrame* pLowerFrame = pLayoutFrame->GetLower(); + while ( pLowerFrame != nullptr ) + { + InitObjs( *pLowerFrame ); + + pLowerFrame = pLowerFrame->GetNext(); + } + } + } + + void FormatSectionToPositionObjs() + { + vcl::RenderContext* pRenderContext = mpSectFrame->getRootFrame()->GetCurrShell()->GetOut(); + // perform extra format for multi-columned section. + if ( !(mpSectFrame->Lower() && mpSectFrame->Lower()->IsColumnFrame() && + mpSectFrame->Lower()->GetNext()) ) + return; + + // grow section till bottom of printing area of upper frame + SwRectFnSet aRectFnSet(mpSectFrame); + SwTwips nTopMargin = aRectFnSet.GetTopMargin(*mpSectFrame); + Size aOldSectPrtSize( mpSectFrame->getFramePrintArea().SSize() ); + SwTwips nDiff = aRectFnSet.BottomDist( mpSectFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*mpSectFrame->GetUpper()) ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mpSectFrame); + aRectFnSet.AddBottom( aFrm, nDiff ); + } + + aRectFnSet.SetYMargins( *mpSectFrame, nTopMargin, 0 ); + // #i59789# + // suppress formatting, if printing area of section is too narrow + if ( aRectFnSet.GetHeight(mpSectFrame->getFramePrintArea()) <= 0 ) + { + return; + } + mpSectFrame->ChgLowersProp( aOldSectPrtSize ); + + // format column frames and its body and footnote container + SwColumnFrame* pColFrame = static_cast<SwColumnFrame*>(mpSectFrame->Lower()); + while ( pColFrame ) + { + pColFrame->Calc(pRenderContext); + pColFrame->Lower()->Calc(pRenderContext); + if ( pColFrame->Lower()->GetNext() ) + { + pColFrame->Lower()->GetNext()->Calc(pRenderContext); + } + + pColFrame = static_cast<SwColumnFrame*>(pColFrame->GetNext()); + } + + // unlock position of lower floating screen objects for the extra format + // #i81555# + // Section frame can already have changed the page and its content + // can still be on the former page. + // Thus, initialize objects via lower-relationship + InitObjs( *mpSectFrame ); + + // format content - first with collecting its foot-/endnotes before content + // format, second without collecting its foot-/endnotes. + ::CalcContent( mpSectFrame ); + ::CalcContent( mpSectFrame, true ); + + // keep locked position of lower floating screen objects + SwPageFrame* pPageFrame = mpSectFrame->FindPageFrame(); + SwSortedObjs* pObjs = pPageFrame ? pPageFrame->GetSortedObjs() : nullptr; + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( mpSectFrame->IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetKeepPosLocked( true ); + } + } + } + + mbExtraFormatPerformed = true; + + } +}; + +} + +/// "formats" the frame; Frame and PrtArea +void SwSectionFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttr ) +{ + if( !m_pSection ) // via DelEmpty + { +#ifdef DBG_UTIL + OSL_ENSURE( getRootFrame()->IsInDelList( this ), "SectionFrame without Section" ); +#endif + setFrameAreaPositionValid(true); + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + return; + } + + SwRectFnSet aRectFnSet(this); + + if ( !isFramePrintAreaValid() ) + { + PROTOCOL( this, PROT::PrintArea, DbgAction::NONE, nullptr ) + setFramePrintAreaValid(true); + SwTwips nUpper = CalcUpperSpace(); + + // #109700# LRSpace for sections + const SvxLRSpaceItem& rLRSpace = GetFormat()->GetLRSpace(); + aRectFnSet.SetXMargins( *this, rLRSpace.GetLeft(), rLRSpace.GetRight() ); + + if( nUpper != aRectFnSet.GetTopMargin(*this) ) + { + setFrameAreaSizeValid(false); + SwFrame* pOwn = ContainsAny(); + if( pOwn ) + pOwn->InvalidatePos_(); + } + aRectFnSet.SetYMargins( *this, nUpper, 0 ); + } + + if ( isFrameAreaSizeValid() ) + return; + + PROTOCOL_ENTER( this, PROT::Size, DbgAction::NONE, nullptr ) + const long nOldHeight = aRectFnSet.GetHeight(getFrameArea()); + bool bOldLock = IsColLocked(); + ColLock(); + + setFrameAreaSizeValid(true); + + // The size is only determined by the content, if the SectFrame does not have a + // Follow. Otherwise it fills (occupies) the Upper down to the lower edge. + // It is not responsible for the text flow, but the content is. + bool bMaximize = ToMaximize( false ); + + // OD 2004-05-17 #i28701# - If the wrapping style has to be considered + // on object positioning, an extra formatting has to be performed + // to determine the correct positions the floating screen objects. + // #i40147# + // use new helper class <ExtraFormatToPositionObjs>. + // This class additionally keep the locked position of the objects + // and releases this position lock keeping on destruction. + ExtraFormatToPositionObjs aExtraFormatToPosObjs( *this ); + if ( !bMaximize && + GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) && + !GetFormat()->GetBalancedColumns().GetValue() ) + { + aExtraFormatToPosObjs.FormatSectionToPositionObjs(); + } + + // Column widths have to be adjusted before calling CheckClipping. + // CheckClipping can cause the formatting of the lower frames + // which still have a width of 0. + const bool bHasColumns = Lower() && Lower()->IsColumnFrame(); + if ( bHasColumns && Lower()->GetNext() ) + AdjustColumns( nullptr, false ); + + if( GetUpper() ) + { + const long nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, nWidth ); + } + + // #109700# LRSpace for sections + { + const SvxLRSpaceItem& rLRSpace = GetFormat()->GetLRSpace(); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, nWidth - rLRSpace.GetLeft() - rLRSpace.GetRight() ); + } + + // OD 15.10.2002 #103517# - allow grow in online layout + // Thus, set <..IsBrowseMode()> as parameter <bGrow> on calling + // method <CheckClipping(..)>. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + CheckClipping( pSh && pSh->GetViewOptions()->getBrowseMode(), bMaximize ); + bMaximize = ToMaximize( false ); + setFrameAreaSizeValid(true); + } + + // Check the width of the columns and adjust if necessary + if ( bHasColumns && ! Lower()->GetNext() && bMaximize ) + static_cast<SwColumnFrame*>(Lower())->Lower()->Calc(pRenderContext); + + if ( !bMaximize ) + { + SwTwips nRemaining = aRectFnSet.GetTopMargin(*this); + SwFrame *pFrame = m_pLower; + if( pFrame ) + { + if( pFrame->IsColumnFrame() && pFrame->GetNext() ) + { + // #i61435# + // suppress formatting, if upper frame has height <= 0 + if ( aRectFnSet.GetHeight(GetUpper()->getFrameArea()) > 0 ) + { + FormatWidthCols( *pAttr, nRemaining, MINLAY ); + } + // #126020# - adjust check for empty section + // #130797# - correct fix #126020# + while( HasFollow() && !GetFollow()->ContainsContent() && + !GetFollow()->ContainsAny( true ) ) + { + SwFrame* pOld = GetFollow(); + GetFollow()->DelEmpty( false ); + if( pOld == GetFollow() ) + break; + } + bMaximize = ToMaximize( false ); + nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()); + } + else + { + if( pFrame->IsColumnFrame() ) + { + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwColumnFrame*>(pFrame)->Lower(); + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + CalcFootnoteContent(); + } + // If we are in a columned frame which calls a CalcContent + // in the FormatWidthCols, the content might need calculating + if( pFrame && !pFrame->isFrameAreaDefinitionValid() && IsInFly() && + FindFlyFrame()->IsColLocked() ) + ::CalcContent( this ); + nRemaining += InnerHeight(); + bMaximize = HasFollow(); + } + } + + SwTwips nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; + if( nDiff < 0) + { + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + { + long nBottom = aRectFnSet.GetBottom(getFrameArea()); + nBottom = aRectFnSet.YInc( nBottom, -nDiff ); + long nTmpDiff = aRectFnSet.YDiff( nBottom, nDeadLine ); + if( nTmpDiff > 0 ) + { + nTmpDiff = GetUpper()->Grow( nTmpDiff, true ); + nDeadLine = aRectFnSet.YInc( nDeadLine, nTmpDiff ); + nTmpDiff = aRectFnSet.YDiff( nBottom, nDeadLine ); + if( nTmpDiff > 0 ) + nDiff += nTmpDiff; + if( nDiff > 0 ) + nDiff = 0; + } + } + } + if( nDiff ) + { + long nTmp = nRemaining - aRectFnSet.GetHeight(getFrameArea()); + long nTop = aRectFnSet.GetTopMargin(*this); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nTmp ); + } + + aRectFnSet.SetYMargins( *this, nTop, 0 ); + InvalidateNextPos(); + + if (m_pLower && (!m_pLower->IsColumnFrame() || !m_pLower->GetNext())) + { + // If a single-column section just created the space that + // was requested by the "undersized" paragraphs, then they + // have to be invalidated and calculated, so they fully cover it + pFrame = m_pLower; + if( pFrame->IsColumnFrame() ) + { + pFrame->InvalidateSize_(); + pFrame->InvalidatePos_(); + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwColumnFrame*>(pFrame)->Lower(); + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + CalcFootnoteContent(); + } + bool bUnderSz = false; + while( pFrame ) + { + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + { + pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + bUnderSz = true; + } + pFrame = pFrame->GetNext(); + } + if( bUnderSz && !IsContentLocked() ) + ::CalcContent( this ); + } + } + } + + // Do not exceed the lower edge of the Upper. + // Do not extend below the lower edge with Sections with Follows + if ( GetUpper() ) + CheckClipping( true, bMaximize ); + if( !bOldLock ) + ColUnlock(); + long nDiff = nOldHeight - aRectFnSet.GetHeight(getFrameArea()); + + if( nDiff > 0 ) + { + if( !GetNext() ) + SetRetouche(); // Take over the retouching ourselves + if( GetUpper() && !GetUpper()->IsFooterFrame() ) + GetUpper()->Shrink( nDiff ); + } + + if( IsUndersized() ) + { + setFramePrintAreaValid(true); + } + +} + +/// Returns the next layout sheet where the frame can be moved in. +/// New pages are created only if specified by the parameter. +SwLayoutFrame *SwFrame::GetNextSctLeaf( MakePageType eMakePage ) +{ + // Attention: Nested sections are currently not supported + + PROTOCOL_ENTER( this, PROT::Leaf, DbgAction::NextSect, GetUpper()->FindSctFrame() ) + + // Shortcuts for "columned" sections, if we're not in the last column + // Can we slide to the next column of the section? + if( IsColBodyFrame() && GetUpper()->GetNext() ) + return static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(GetUpper()->GetNext())->Lower()); + if( GetUpper()->IsColBodyFrame() && GetUpper()->GetUpper()->GetNext() ) + return static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(GetUpper()->GetUpper()->GetNext())->Lower()); + // Inside a table-in-section, or sections of headers/footers, there can be only + // one column shift be made, one of the above shortcuts should have applied! + if( !CanContainSplitSection(GetUpper()) || FindFooterOrHeader() ) + return nullptr; + + SwSectionFrame *pSect = FindSctFrame(); + bool bWrongPage = false; + assert(pSect && "GetNextSctLeaf: Missing SectionFrame"); + + // Shortcut for sections with Follows. That's ok, + // if no columns or pages (except dummy pages) lie in between. + // In case of linked frames and in footnotes the shortcut would get + // even more costly + if( pSect->HasFollow() && pSect->IsInDocBody() && !pSect->IsInTab() ) + { + if( pSect->GetFollow() == pSect->GetNext() ) + { + SwPageFrame *pPg = pSect->GetFollow()->FindPageFrame(); + if( WrongPageDesc( pPg ) ) + bWrongPage = true; + else + return FirstLeaf( pSect->GetFollow() ); + } + else + { + SwFrame* pTmp; + if( !pSect->GetUpper()->IsColBodyFrame() || + nullptr == ( pTmp = pSect->GetUpper()->GetUpper()->GetNext() ) ) + pTmp = pSect->FindPageFrame()->GetNext(); + if( pTmp ) // is now the next column or page + { + SwFrame* pTmpX = pTmp; + if( pTmp->IsPageFrame() && static_cast<SwPageFrame*>(pTmp)->IsEmptyPage() ) + pTmp = pTmp->GetNext(); // skip dummy pages + SwFrame *pUp = pSect->GetFollow()->GetUpper(); + // pUp becomes the next column if the Follow lies in a column + // that is not a "not first" one, otherwise the page + if( !pUp->IsColBodyFrame() || + !( pUp = pUp->GetUpper() )->GetPrev() ) + pUp = pUp->FindPageFrame(); + // Now pUp and pTmp have to be the same page/column, otherwise + // pages or columns lie between Master and Follow + if( pUp == pTmp || pUp->GetNext() == pTmpX ) + { + SwPageFrame* pNxtPg = pUp->IsPageFrame() ? + static_cast<SwPageFrame*>(pUp) : pUp->FindPageFrame(); + if( WrongPageDesc( pNxtPg ) ) + bWrongPage = true; + else + return FirstLeaf( pSect->GetFollow() ); + } + } + } + } + +#ifndef NDEBUG + std::vector<SwFrame *> parents; + for (SwFrame * pTmp = GetUpper(); pTmp && !pTmp->IsPageFrame(); pTmp = pTmp->GetUpper()) + { + parents.push_back(pTmp); + } +#endif + + // Always end up in the same section: Body again inside Body etc. + const bool bBody = IsInDocBody(); + const bool bFootnotePage = FindPageFrame()->IsFootnotePage(); + + // The "pLayLeaf is in a table" case is rejected by default, so that it + // can't happen that we try to move a table to one of its own cells. + bool bLayLeafTableAllowed = false; + SwLayoutFrame *pLayLeaf; + + SwLayoutFrame* pCellLeaf = nullptr; + if (GetUpper()->IsInTab()) + { + if (IsTabFrame()) + { + return nullptr; // table in section in table: split disabled for now + } + // We are *in* a table (not an outermost SwTabFrame), see if there + // is a follow cell frame created already. + pCellLeaf = GetNextCellLeaf(); + if (!pCellLeaf) + { + SAL_WARN("sw.layout", "section is in table, but the table is not split"); + return nullptr; + } + } + + // A shortcut for TabFrames such that not all cells need to be visited + if( bWrongPage ) + pLayLeaf = nullptr; + else if( IsTabFrame() ) + { + SwFrame *const pTmpCnt = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + pLayLeaf = pTmpCnt ? pTmpCnt->GetUpper() : nullptr; + } + else if (pCellLeaf && CanContainSplitSection(this)) + { + // This frame is in a table-not-in-section, its follow should be + // inserted under the follow of the frame's cell. + pLayLeaf = pCellLeaf; + if (pLayLeaf->FindTabFrame() == FindTabFrame()) + SAL_WARN("sw.layout", "my table frame and my follow's table frame is the same"); + // In this case pLayLeaf pointing to an in-table frame is OK. + bLayLeafTableAllowed = true; + } + else + { + pLayLeaf = GetNextLayoutLeaf(); + if( IsColumnFrame() ) + { + while( pLayLeaf && static_cast<SwColumnFrame*>(this)->IsAnLower( pLayLeaf ) ) + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + } + } + + SwLayoutFrame *pOldLayLeaf = nullptr; // Such that in case of newly + // created pages, the search is + // not started over at the beginning + + while( true ) + { + if( pLayLeaf ) + { + // A layout leaf was found, let's see whether it can store me or + // another SectionFrame can be inserted here, or we have to continue + // searching + SwPageFrame* pNxtPg = pLayLeaf->FindPageFrame(); + if ( !bFootnotePage && pNxtPg->IsFootnotePage() ) + { // If I reached the end note pages it's over + pLayLeaf = nullptr; + continue; + } + // Once inBody always inBody, don't step into tables-in-sections and not into other sections + if ( (bBody && !pLayLeaf->IsInDocBody()) || + (IsInFootnote() != pLayLeaf->IsInFootnote() ) || + (pLayLeaf->IsInTab() && !bLayLeafTableAllowed) || + ( pLayLeaf->IsInSct() && ( !pSect->HasFollow() + || pSect->GetFollow() != pLayLeaf->FindSctFrame() ) ) ) + { + // Rejected - try again. + pOldLayLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + continue; + } + // Page desc is never wrong in case of sections-in-tables: in that + // case pLayLeaf points to our section's cell's follow, which is + // fine to be on the same page. New page creation is handled when + // creating / moving the cell frame. + if( WrongPageDesc( pNxtPg ) && !bLayLeafTableAllowed ) + { + if( bWrongPage ) + break; // there's a column between me and my right page + pLayLeaf = nullptr; + bWrongPage = true; + pOldLayLeaf = nullptr; + continue; + } + } + // There is no further LayoutFrame that fits, so a new page + // has to be created, although new pages are worthless within a frame + else if( !pSect->IsInFly() && + ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) ) + { + InsertPage(pOldLayLeaf ? pOldLayLeaf->FindPageFrame() : FindPageFrame(), + false ); + // and again the whole thing + if (pCellLeaf && CanContainSplitSection(this)) + // GetNextLayoutLeaf() would refer to the next cell in the same + // row, avoid that. pCellLeaf points to the correct cell in the + // follow table, and in the next round it'll be used, as we now + // have a next page. + pLayLeaf = pCellLeaf; + else + pLayLeaf = pOldLayLeaf ? pOldLayLeaf : GetNextLayoutLeaf(); + continue; + } + break; + } + + if( pLayLeaf ) + { + // We have found the suitable layout sheet. If there (in the sheet) is + // already a Follow of our section, we take its first layout sheet, + // otherwise it is time to create a section follow + SwSectionFrame* pNew = nullptr; + + // This can be omitted if existing Follows were cut short + SwFrame* pFirst = pLayLeaf->Lower(); + // Here SectionFrames that are to be deleted must be ignored + while( pFirst && pFirst->IsSctFrame() && !static_cast<SwSectionFrame*>(pFirst)->GetSection() ) + pFirst = pFirst->GetNext(); + if( pFirst && pFirst->IsSctFrame() && pSect->GetFollow() == pFirst ) + pNew = pSect->GetFollow(); + else if( MAKEPAGE_NOSECTION == eMakePage ) + return pLayLeaf; + else if (pSect->GetSection()) + { + pNew = new SwSectionFrame( *pSect, false ); + pNew->InsertBefore( pLayLeaf, pLayLeaf->Lower() ); + pNew->Init(); + SwRectFnSet aRectFnSet(pNew); + aRectFnSet.MakePos( *pNew, pLayLeaf, nullptr, true ); + +#ifndef NDEBUG + { // sanity check the parents of the new frame vs. the old frame + SwFrame * pTmp = pNew; + auto iter(parents.begin()); + if (parents.size() >= 2 && + parents[0]->IsBodyFrame() && parents[1]->IsColumnFrame()) + { // this only inserts section frame - remove column + assert(parents[2]->IsSctFrame() || IsSctFrame()); + if (parents[2]->IsSctFrame()) + std::advance(iter, +2); + else + pTmp = pTmp->GetUpper(); + } + else if (IsBodyFrame() && parents.size() >= 1 + && parents[0]->IsColumnFrame()) + { // same as above, special case: "this" is the body frame + assert(parents[1]->IsSctFrame()); + std::advance(iter, +1); + } + else if (IsSctFrame()) // special case: "this" is the section + { + pTmp = pTmp->GetUpper(); + } + + for ( ; iter != parents.end(); ++iter) + { + if (pTmp->IsPageFrame()) + { + if ((*iter)->IsColumnFrame() && + (iter + 1) != parents.end() && (*(iter + 1))->IsBodyFrame()) + { // page style has columns - evidently these are + break; // added later? + } + assert(!pTmp->IsPageFrame()); + } + assert(pTmp->GetType() == (*iter)->GetType()); + // for cell frames and table frames: + // 1) there may be multiple follow frames of the old one + // 2) the new frame may be identical to the old one + // (not sure if this is allowed, but it happens now + // for the outer table of a nested table) + if (pTmp->IsCellFrame()) + { + SwCellFrame const*const pNewF(static_cast<SwCellFrame*>(pTmp)); + SwCellFrame const*const pOldF(static_cast<SwCellFrame*>(*iter)); + bool bFollowFound(false); + for (SwCellFrame const* pOldIter = pOldF; + pOldIter; pOldIter = pOldIter->GetFollowCell()) + { + if (pOldIter == pNewF) + { + bFollowFound = true; + break; + } + } + assert(bFollowFound); + } + else if (pTmp->IsFlowFrame()) + { + SwFlowFrame const*const pNewF(SwFlowFrame::CastFlowFrame(pTmp)); + SwFlowFrame const*const pOldF(SwFlowFrame::CastFlowFrame(*iter)); + bool bFollowFound(false); + for (SwFlowFrame const* pOldIter = pOldF; + pOldIter; pOldIter = pOldIter->GetFollow()) + { + if (pOldIter == pNewF) + { + bFollowFound = true; + break; + } + } + assert(bFollowFound); + } + pTmp = pTmp->GetUpper(); + } + assert(pTmp == nullptr /* SwFlyAtContentFrame case */ + || pTmp->IsPageFrame() // usual case + // the new page has columns, but the old page did not + || (pTmp->IsColumnFrame() && pTmp->GetUpper()->IsBodyFrame() + && pTmp->GetUpper()->GetUpper()->IsPageFrame())); + } +#endif + + // If our section frame has a successor then that has to be + // moved behind the new Follow of the section frames + SwFrame* pTmp = pSect->GetNext(); + if( pTmp && pTmp != pSect->GetFollow() ) + { + SwFlowFrame* pNxt; + SwContentFrame* pNxtContent = nullptr; + if( pTmp->IsContentFrame() ) + { + pNxt = static_cast<SwContentFrame*>(pTmp); + pNxtContent = static_cast<SwContentFrame*>(pTmp); + } + else + { + pNxtContent = static_cast<SwLayoutFrame*>(pTmp)->ContainsContent(); + if( pTmp->IsSctFrame() ) + pNxt = static_cast<SwSectionFrame*>(pTmp); + else + { + assert(pTmp->IsTabFrame()); + pNxt = static_cast<SwTabFrame*>(pTmp); + } + while( !pNxtContent && nullptr != ( pTmp = pTmp->GetNext() ) ) + { + if( pTmp->IsContentFrame() ) + pNxtContent = static_cast<SwContentFrame*>(pTmp); + else + pNxtContent = static_cast<SwLayoutFrame*>(pTmp)->ContainsContent(); + } + } + if( pNxtContent ) + { + SwFootnoteBossFrame* pOldBoss = pSect->FindFootnoteBossFrame( true ); + if( pOldBoss == pNxtContent->FindFootnoteBossFrame( true ) ) + { + SwSaveFootnoteHeight aHeight( pOldBoss, + pOldBoss->getFrameArea().Top() + pOldBoss->getFrameArea().Height() ); + pSect->GetUpper()->MoveLowerFootnotes( pNxtContent, pOldBoss, + pLayLeaf->FindFootnoteBossFrame( true ), false ); + } + } + pNxt->MoveSubTree( pLayLeaf, pNew->GetNext() ); + } + if( pNew->GetFollow() ) + pNew->SimpleFormat(); + } + // The wanted layout sheet is now the first of the determined SctFrames: + pLayLeaf = pNew ? FirstLeaf(pNew) : nullptr; + } + return pLayLeaf; +} + +/// Returns the preceding layout sheet where the frame can be moved into +SwLayoutFrame *SwFrame::GetPrevSctLeaf() +{ + PROTOCOL_ENTER( this, PROT::Leaf, DbgAction::PrevSect, GetUpper()->FindSctFrame() ) + + SwLayoutFrame* pCol; + // ColumnFrame always contain a BodyFrame now + if( IsColBodyFrame() ) + pCol = GetUpper(); + else if( GetUpper()->IsColBodyFrame() ) + pCol = GetUpper()->GetUpper(); + else + pCol = nullptr; + bool bJump = false; + if( pCol ) + { + if( pCol->GetPrev() ) + { + do + { + pCol = static_cast<SwLayoutFrame*>(pCol->GetPrev()); + // Is there any content? + if( static_cast<SwLayoutFrame*>(pCol->Lower())->Lower() ) + { + if( bJump ) // Did we skip a blank page? + SwFlowFrame::SetMoveBwdJump( true ); + return static_cast<SwLayoutFrame*>(pCol->Lower()); // The columnm body + } + bJump = true; + } while( pCol->GetPrev() ); + + // We get here when all columns are empty, pCol is now the + // first column, we need the body though + pCol = static_cast<SwLayoutFrame*>(pCol->Lower()); + } + else + pCol = nullptr; + } + + if( bJump ) // Did we skip a blank page? + SwFlowFrame::SetMoveBwdJump( true ); + + SwSectionFrame *pSect = FindSctFrame(); + if (!pCol && pSect && IsInTab() && CanContainSplitSection(this)) + { + // We don't have a previous section yet, and we're in a + // section-in-table. + if (SwFlowFrame* pPrecede = pSect->GetPrecede()) + { + // Our section has a precede, work with that. + if (pPrecede->GetFrame().IsLayoutFrame()) + pCol = static_cast<SwLayoutFrame*>(&pPrecede->GetFrame()); + } + } + + // Within sections in tables or section in headers/footers there can + // be only one column change be made, one of the above shortcuts should + // have applied, also when the section has a pPrev. + // Now we even consider an empty column... + OSL_ENSURE( pSect, "GetNextSctLeaf: Missing SectionFrame" ); + if (!pSect || (IsInTab() && !IsTabFrame()) || FindFooterOrHeader()) + return pCol; + + // === IMPORTANT === + // Precondition, which needs to be hold, is that the <this> frame can be + // inside a table, but then the found section frame <pSect> is also inside + // this table. + + // #i95698# + // A table cell containing directly a section does not break - see lcl_FindSectionsInRow(..) + // Thus, a table inside a section, which is inside another table can only + // flow backward in the columns of its section. + // Note: The table cell, which contains the section, can not have a master table cell. + if ( IsTabFrame() && pSect->IsInTab() ) + { + return pCol; + } + + { + if (SwFrame *pPrv = pSect->GetIndPrev()) + { + // Mooching, half dead SectionFrames shouldn't confuse us + while( pPrv && pPrv->IsSctFrame() && !static_cast<SwSectionFrame*>(pPrv)->GetSection() ) + pPrv = pPrv->GetPrev(); + if( pPrv ) + return pCol; + } + } + + const bool bBody = IsInDocBody(); + const bool bFly = IsInFly(); + + SwLayoutFrame *pLayLeaf = GetPrevLayoutLeaf(); + SwLayoutFrame *pPrevLeaf = nullptr; + + while ( pLayLeaf ) + { + // Never step into tables or sections + if ( pLayLeaf->IsInTab() || pLayLeaf->IsInSct() ) + { + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + } + else if ( bBody && pLayLeaf->IsInDocBody() ) + { + // If there is a pLayLeaf has a lower pLayLeaf is the frame we are looking for. + // Exception: pLayLeaf->Lower() is a zombie section frame + const SwFrame* pTmp = pLayLeaf->Lower(); + // OD 11.04.2003 #108824# - consider, that the zombie section frame + // can have frame below it in the found layout leaf. + // Thus, skipping zombie section frame, if possible. + while ( pTmp && pTmp->IsSctFrame() && + !( static_cast<const SwSectionFrame*>(pTmp)->GetSection() ) && + pTmp->GetNext() + ) + { + pTmp = pTmp->GetNext(); + } + if ( pTmp && + ( !pTmp->IsSctFrame() || + ( static_cast<const SwSectionFrame*>(pTmp)->GetSection() ) + ) + ) + { + break; + } + pPrevLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + if ( pLayLeaf ) + SwFlowFrame::SetMoveBwdJump( true ); + } + else if ( bFly ) + break; // Contents in Flys every layout sheet should be right. Why? + else + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + } + if( !pLayLeaf ) + { + if( !pPrevLeaf ) + return pCol; + pLayLeaf = pPrevLeaf; + } + + SwSectionFrame* pNew = nullptr; + // At first go to the end of the layout sheet + SwFrame *pTmp = pLayLeaf->Lower(); + if( pTmp ) + { + while( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + if( pTmp->IsSctFrame() ) + { + // Half dead ones only interfere here + while( !static_cast<SwSectionFrame*>(pTmp)->GetSection() && pTmp->GetPrev() && + pTmp->GetPrev()->IsSctFrame() ) + pTmp = pTmp->GetPrev(); + if( static_cast<SwSectionFrame*>(pTmp)->GetFollow() == pSect ) + pNew = static_cast<SwSectionFrame*>(pTmp); + } + } + if( !pNew ) + { + pNew = new SwSectionFrame( *pSect, true ); + pNew->InsertBefore( pLayLeaf, nullptr ); + pNew->Init(); + SwRectFnSet aRectFnSet(pNew); + aRectFnSet.MakePos( *pNew, pLayLeaf, pNew->GetPrev(), true ); + + pLayLeaf = FirstLeaf( pNew ); + if( !pNew->Lower() ) // Format single column sections + { + pNew->MakePos(); + pLayLeaf->Format(getRootFrame()->GetCurrShell()->GetOut()); // In order that the PrtArea is correct for the MoveBwd + } + else + pNew->SimpleFormat(); + } + else + { + pLayLeaf = FirstLeaf( pNew ); + if( pLayLeaf->IsColBodyFrame() ) + { + // In existent section columns we're looking for the last not empty + // column. + SwLayoutFrame *pTmpLay = pLayLeaf; + while( pLayLeaf->GetUpper()->GetNext() ) + { + pLayLeaf = static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pLayLeaf->GetUpper()->GetNext())->Lower()); + if( pLayLeaf->Lower() ) + pTmpLay = pLayLeaf; + } + // If we skipped an empty column, we've to set the jump-flag + if( pLayLeaf != pTmpLay ) + { + pLayLeaf = pTmpLay; + SwFlowFrame::SetMoveBwdJump( true ); + } + } + } + return pLayLeaf; +} + +static SwTwips lcl_DeadLine( const SwFrame* pFrame ) +{ + const SwLayoutFrame* pUp = pFrame->GetUpper(); + while( pUp && pUp->IsInSct() ) + { + if( pUp->IsSctFrame() ) + pUp = pUp->GetUpper(); + // Columns now with BodyFrame + else if( pUp->IsColBodyFrame() && pUp->GetUpper()->GetUpper()->IsSctFrame() ) + pUp = pUp->GetUpper()->GetUpper(); + else + break; + } + SwRectFnSet aRectFnSet(pFrame); + return pUp ? aRectFnSet.GetPrtBottom(*pUp) : + aRectFnSet.GetBottom(pFrame->getFrameArea()); +} + +/// checks whether the SectionFrame is still able to grow, as case may be the environment has to be asked +bool SwSectionFrame::Growable() const +{ + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.YDiff( lcl_DeadLine( this ), + aRectFnSet.GetBottom(getFrameArea()) ) > 0 ) + return true; + + return ( GetUpper() && const_cast<SwFrame*>(static_cast<SwFrame const *>(GetUpper()))->Grow( LONG_MAX, true ) ); +} + +SwTwips SwSectionFrame::Grow_( SwTwips nDist, bool bTst ) +{ + if ( !IsColLocked() && !HasFixSize() ) + { + SwRectFnSet aRectFnSet(this); + long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight > 0 && nDist > (LONG_MAX - nFrameHeight) ) + nDist = LONG_MAX - nFrameHeight; + + if ( nDist <= 0 ) + return 0; + + bool bInCalcContent = GetUpper() && IsInFly() && FindFlyFrame()->IsLocked(); + // OD 2004-03-15 #116561# - allow grow in online layout + bool bGrow = !Lower() || !Lower()->IsColumnFrame() || !Lower()->GetNext(); + if (!bGrow) + { + SwSection* pSection = GetSection(); + bGrow = pSection && pSection->GetFormat()->GetBalancedColumns().GetValue(); + } + if( !bGrow ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + bGrow = pSh && pSh->GetViewOptions()->getBrowseMode(); + } + if( bGrow ) + { + SwTwips nGrow; + if( IsInFootnote() ) + nGrow = 0; + else + { + nGrow = lcl_DeadLine( this ); + nGrow = aRectFnSet.YDiff( nGrow, aRectFnSet.GetBottom(getFrameArea()) ); + } + SwTwips nSpace = nGrow; + if( !bInCalcContent && nGrow < nDist && GetUpper() ) + nGrow = o3tl::saturating_add( + nGrow, GetUpper()->Grow( LONG_MAX, true )); + + if( nGrow > nDist ) + nGrow = nDist; + if( nGrow <= 0 ) + { + nGrow = 0; + if (!bTst) + { + if( bInCalcContent ) + InvalidateSize_(); + else + InvalidateSize(); + } + } + else if( !bTst ) + { + if( bInCalcContent ) + InvalidateSize_(); + else if( nSpace < nGrow && nDist != nSpace + GetUpper()-> + Grow( nGrow - nSpace ) ) + InvalidateSize(); + else + { + const SvxGraphicPosition ePos = + GetAttrSet()->GetBackground().GetGraphicPos(); + if ( GPOS_RT < ePos && GPOS_TILED != ePos ) + { + SetCompletePaint(); + InvalidatePage(); + } + if( GetUpper() && GetUpper()->IsHeaderFrame() ) + GetUpper()->InvalidateSize(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nGrow ); + } + + { + const long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()) + nGrow; + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight ); + } + + if( Lower() && Lower()->IsColumnFrame() && Lower()->GetNext() ) + { + SwFrame* pTmp = Lower(); + do + { + pTmp->InvalidateSize_(); + pTmp = pTmp->GetNext(); + } while ( pTmp ); + InvalidateSize_(); + } + if( GetNext() ) + { + // Own height changed, need to invalidate the position of + // next frames. + SwFrame *pFrame = GetNext(); + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + { + // Invalidate all in-between frames, otherwise position + // calculation (which only looks back to one relative + // frame) will have an incorrect result. + InvalidateFramePos(pFrame, bInCalcContent); + pFrame = pFrame->GetNext(); + } + if( pFrame ) + { + InvalidateFramePos(pFrame, bInCalcContent); + } + } + // #i28701# - Due to the new object positioning + // the frame on the next page/column can flow backward (e.g. it + // was moved forward due to the positioning of its objects ). + // Thus, invalivate this next frame, if document compatibility + // option 'Consider wrapping style influence on object positioning' is ON. + else if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + } + return nGrow; + } + if ( !bTst ) + { + if( bInCalcContent ) + InvalidateSize_(); + else + InvalidateSize(); + } + } + return 0; +} + +SwTwips SwSectionFrame::Shrink_( SwTwips nDist, bool bTst ) +{ + if ( Lower() && !IsColLocked() && !HasFixSize() ) + { + if( ToMaximize( false ) ) + { + if( !bTst ) + InvalidateSize(); + } + else + { + SwRectFnSet aRectFnSet(this); + long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nDist > nFrameHeight ) + nDist = nFrameHeight; + + if ( Lower()->IsColumnFrame() && Lower()->GetNext() && // FootnoteAtEnd + !GetSection()->GetFormat()->GetBalancedColumns().GetValue() ) + { // With column bases the format takes over the control of the + // growth (because of the balance) + if ( !bTst ) + InvalidateSize(); + return nDist; + } + else if( !bTst ) + { + const SvxGraphicPosition ePos = + GetAttrSet()->GetBackground().GetGraphicPos(); + if ( GPOS_RT < ePos && GPOS_TILED != ePos ) + { + SetCompletePaint(); + InvalidatePage(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, -nDist ); + } + + { + const long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()) - nDist; + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight ); + } + + // We do not allow a section frame to shrink the its upper + // footer frame. This is because in the calculation of a + // footer frame, the content of the section frame is _not_ + // calculated. If there is a fly frame overlapping with the + // footer frame, the section frame is not affected by this + // during the calculation of the footer frame size. + // The footer frame does not grow in its FormatSize function + // but during the calculation of the content of the section + // frame. The section frame grows until some of its text is + // located on top of the fly frame. The next call of CalcContent + // tries to shrink the section and here it would also shrink + // the footer. This may not happen, because shrinking the footer + // would cause the top of the section frame to overlap with the + // fly frame again, this would result in a perfect loop. + if( GetUpper() && !GetUpper()->IsFooterFrame() ) + GetUpper()->Shrink( nDist, bTst ); + + if( Lower() && Lower()->IsColumnFrame() && Lower()->GetNext() ) + { + SwFrame* pTmp = Lower(); + do + { + pTmp->InvalidateSize_(); + pTmp = pTmp->GetNext(); + } while ( pTmp ); + } + if( GetNext() ) + { + SwFrame* pFrame = GetNext(); + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + pFrame = pFrame->GetNext(); + if( pFrame ) + pFrame->InvalidatePos(); + else + SetRetouche(); + } + else + SetRetouche(); + return nDist; + } + } + } + return 0; +} + +/* +|* When are Frames within a SectionFrames moveable? +|* If they are not in the last column of a SectionFrames yet, +|* if there is no Follow, +|* if the SectionFrame cannot grow anymore, then it gets more complicated, +|* in that case it depends on whether the SectionFrame can find a next +|* layout sheet. In (column based/chained) Flys this is checked via +|* GetNextLayout, in tables and headers/footers there is none, however in the +|* DocBody and in foot notes there is always one. +|* +|* This routine is used in the TextFormatter to decided whether it's allowed to +|* create a (paragraph-)Follow or whether the paragraph has to stick together +|*/ +bool SwSectionFrame::MoveAllowed( const SwFrame* pFrame) const +{ + // Is there a Follow or is the Frame not in the last column? + if( HasFollow() || ( pFrame->GetUpper()->IsColBodyFrame() && + pFrame->GetUpper()->GetUpper()->GetNext() ) ) + return true; + if( pFrame->IsInFootnote() ) + { + if( IsInFootnote() ) + { + if( GetUpper()->IsInSct() ) + { + if( Growable() ) + return false; + return GetUpper()->FindSctFrame()->MoveAllowed( this ); + } + else + return true; + } + // The content of footnote inside a columned sectionfrm is moveable + // except in the last column + const SwLayoutFrame *pLay = pFrame->FindFootnoteFrame()->GetUpper()->GetUpper(); + if( pLay->IsColumnFrame() && pLay->GetNext() ) + { + // The first paragraph in the first footnote in the first column + // in the sectionfrm at the top of the page is not moveable, + // if the columnbody is empty. + bool bRet = false; + if( pLay->GetIndPrev() || pFrame->GetIndPrev() || + pFrame->FindFootnoteFrame()->GetPrev() ) + bRet = true; + else + { + const SwLayoutFrame* pBody = static_cast<const SwColumnFrame*>(pLay)->FindBodyCont(); + if( pBody && pBody->Lower() ) + bRet = true; + } + if( bRet && ( IsFootnoteAtEnd() || !Growable() ) ) + return true; + } + } + // Or can the section still grow? + if( !IsColLocked() && Growable() ) + return false; + // Now it has to be examined whether there is a layout sheet wherein + // a section Follow can be created + if( !CanContainSplitSection(this) || ( !IsInDocBody() && FindFooterOrHeader() ) ) + return false; // It doesn't work in table-in-sections/nested tables/headers/footers + if( IsInFly() ) // In column based or chained frames + return nullptr != const_cast<SwFrame*>(static_cast<SwFrame const *>(GetUpper()))->GetNextLeaf( MAKEPAGE_NONE ); + return true; +} + +/** Called for a frame inside a section with no direct previous frame (or only + previous empty section frames) the previous frame of the outer section is + returned, if the frame is the first flowing content of this section. + + Note: For a frame inside a table frame, which is inside a section frame, + NULL is returned. +*/ +SwFrame* SwFrame::GetIndPrev_() const +{ + SwFrame *pRet = nullptr; + // #i79774# + // Do not assert, if the frame has a direct previous frame, because it + // could be an empty section frame. The caller has to assure, that the + // frame has no direct previous frame or only empty section frames as + // previous frames. + OSL_ENSURE( /*!pPrev &&*/ IsInSct(), "Why?" ); + const SwFrame* pSct = GetUpper(); + if( !pSct ) + return nullptr; + if( pSct->IsSctFrame() ) + pRet = pSct->GetIndPrev(); + else if( pSct->IsColBodyFrame() && (pSct = pSct->GetUpper()->GetUpper())->IsSctFrame() ) + { + // Do not return the previous frame of the outer section, if in one + // of the previous columns is content. + const SwFrame* pCol = GetUpper()->GetUpper()->GetPrev(); + while( pCol ) + { + OSL_ENSURE( pCol->IsColumnFrame(), "GetIndPrev(): ColumnFrame expected" ); + OSL_ENSURE( pCol->GetLower() && pCol->GetLower()->IsBodyFrame(), + "GetIndPrev(): Where's the body?"); + if( static_cast<const SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pCol)->Lower())->Lower() ) + return nullptr; + pCol = pCol->GetPrev(); + } + pRet = pSct->GetIndPrev(); + } + + // skip empty section frames + while( pRet && pRet->IsSctFrame() && !static_cast<SwSectionFrame*>(pRet)->GetSection() ) + pRet = pRet->GetIndPrev(); + return pRet; +} + +SwFrame* SwFrame::GetIndNext_() +{ + OSL_ENSURE( !mpNext && IsInSct(), "Why?" ); + SwFrame* pSct = GetUpper(); + if( !pSct ) + return nullptr; + if( pSct->IsSctFrame() ) + return pSct->GetIndNext(); + if( pSct->IsColBodyFrame() && (pSct = pSct->GetUpper()->GetUpper())->IsSctFrame() ) + { // We can only return the successor of the SectionFrames if there is no + // content in the successive columns + SwFrame* pCol = GetUpper()->GetUpper()->GetNext(); + while( pCol ) + { + OSL_ENSURE( pCol->IsColumnFrame(), "GetIndNext(): ColumnFrame expected" ); + OSL_ENSURE( pCol->GetLower() && pCol->GetLower()->IsBodyFrame(), + "GetIndNext(): Where's the body?"); + if( static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pCol)->Lower())->Lower() ) + return nullptr; + pCol = pCol->GetNext(); + } + return pSct->GetIndNext(); + } + return nullptr; +} + +bool SwSectionFrame::IsDescendantFrom( const SwSectionFormat* pFormat ) const +{ + if( !m_pSection || !pFormat ) + return false; + const SwSectionFormat *pMyFormat = m_pSection->GetFormat(); + while( pFormat != pMyFormat ) + { + if( dynamic_cast< const SwSectionFormat *>( pMyFormat->GetRegisteredIn()) != nullptr ) + pMyFormat = static_cast<const SwSectionFormat*>(pMyFormat->GetRegisteredIn()); + else + return false; + } + return true; +} + +void SwSectionFrame::CalcFootnoteAtEndFlag() +{ + SwSectionFormat *pFormat = GetSection()->GetFormat(); + sal_uInt16 nVal = pFormat->GetFootnoteAtTextEnd( false ).GetValue(); + m_bFootnoteAtEnd = FTNEND_ATPGORDOCEND != nVal; + m_bOwnFootnoteNum = FTNEND_ATTXTEND_OWNNUMSEQ == nVal || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal; + while( !m_bFootnoteAtEnd && !m_bOwnFootnoteNum ) + { + if( dynamic_cast< const SwSectionFormat *>( pFormat->GetRegisteredIn()) != nullptr ) + pFormat = static_cast<SwSectionFormat*>(pFormat->GetRegisteredIn()); + else + break; + nVal = pFormat->GetFootnoteAtTextEnd( false ).GetValue(); + if( FTNEND_ATPGORDOCEND != nVal ) + { + m_bFootnoteAtEnd = true; + m_bOwnFootnoteNum = m_bOwnFootnoteNum ||FTNEND_ATTXTEND_OWNNUMSEQ == nVal || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal; + } + } +} + +bool SwSectionFrame::IsEndnoteAtMyEnd() const +{ + return m_pSection->GetFormat()->GetEndAtTextEnd( false ).IsAtEnd(); +} + +void SwSectionFrame::CalcEndAtEndFlag() +{ + SwSectionFormat *pFormat = GetSection()->GetFormat(); + m_bEndnAtEnd = pFormat->GetEndAtTextEnd( false ).IsAtEnd(); + while( !m_bEndnAtEnd ) + { + if( dynamic_cast< const SwSectionFormat *>( pFormat->GetRegisteredIn()) != nullptr ) + pFormat = static_cast<SwSectionFormat*>(pFormat->GetRegisteredIn()); + else + break; + m_bEndnAtEnd = pFormat->GetEndAtTextEnd( false ).IsAtEnd(); + } +} + +void SwSectionFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + sal_uInt8 nInvFlags = 0; + + if( pNew && RES_ATTRSET_CHG == pNew->Which() ) + { + SfxItemIter aNIter( *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet() ); + SfxItemIter aOIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); + do + { + UpdateAttr_(pOItem, pNItem, nInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + if ( aOldSet.Count() || aNewSet.Count() ) + SwLayoutFrame::Modify( &aOldSet, &aNewSet ); + } + else + UpdateAttr_( pOld, pNew, nInvFlags ); + + if ( nInvFlags != 0 ) + { + if ( nInvFlags & 0x01 ) + InvalidateSize(); + if ( nInvFlags & 0x10 ) + SetCompletePaint(); + } +} + +void SwSectionFrame::SwClientNotify( const SwModify& rMod, const SfxHint& rHint ) +{ + SwFrame::SwClientNotify(rMod, rHint); + // #i117863# + if(&rMod != GetDep()) + return; + const auto pHint = dynamic_cast<const SwSectionFrameMoveAndDeleteHint*>(&rHint); + if(!pHint) + return; + SwSectionFrame::MoveContentAndDelete( this, pHint->IsSaveContent() ); +} + +void SwSectionFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + sal_uInt8 &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { // Suppress multi columns in foot notes + case RES_FMT_CHG: + { + const SwFormatCol& rNewCol = GetFormat()->GetCol(); + if( !IsInFootnote() ) + { + // Nasty case. When allocating a template we can not count + // on the old column attribute. We're left with creating a + // temporary attribute here. + SwFormatCol aCol; + if ( Lower() && Lower()->IsColumnFrame() ) + { + sal_uInt16 nCol = 0; + SwFrame *pTmp = Lower(); + do + { ++nCol; + pTmp = pTmp->GetNext(); + } while ( pTmp ); + aCol.Init( nCol, 0, 1000 ); + } + bool bChgFootnote = IsFootnoteAtEnd(); + bool const bChgEndn = IsEndnAtEnd(); + bool const bChgMyEndn = IsEndnoteAtMyEnd(); + CalcFootnoteAtEndFlag(); + CalcEndAtEndFlag(); + bChgFootnote = ( bChgFootnote != IsFootnoteAtEnd() ) || + ( bChgEndn != IsEndnAtEnd() ) || + ( bChgMyEndn != IsEndnoteAtMyEnd() ); + ChgColumns( aCol, rNewCol, bChgFootnote ); + rInvFlags |= 0x10; + } + rInvFlags |= 0x01; + bClear = false; + } + break; + + case RES_COL: + if( !IsInFootnote() ) + { + assert(pOld && pNew); + if (pOld && pNew) + { + ChgColumns( *static_cast<const SwFormatCol*>(pOld), *static_cast<const SwFormatCol*>(pNew) ); + rInvFlags |= 0x11; + } + } + break; + + case RES_FTN_AT_TXTEND: + if( !IsInFootnote() ) + { + bool const bOld = IsFootnoteAtEnd(); + CalcFootnoteAtEndFlag(); + if (bOld != IsFootnoteAtEnd()) + { + const SwFormatCol& rNewCol = GetFormat()->GetCol(); + ChgColumns( rNewCol, rNewCol, true ); + rInvFlags |= 0x01; + } + } + break; + + case RES_END_AT_TXTEND: + if( !IsInFootnote() ) + { + bool const bOld = IsEndnAtEnd(); + bool const bMyOld = IsEndnoteAtMyEnd(); + CalcEndAtEndFlag(); + if (bOld != IsEndnAtEnd() || bMyOld != IsEndnoteAtMyEnd()) + { + const SwFormatCol& rNewCol = GetFormat()->GetCol(); + ChgColumns( rNewCol, rNewCol, true ); + rInvFlags |= 0x01; + } + } + break; + case RES_COLUMNBALANCE: + rInvFlags |= 0x01; + break; + + case RES_FRAMEDIR : + SetDerivedR2L( false ); + CheckDirChange(); + break; + + case RES_PROTECT: + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetLayout()->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleEditableState( true, this ); + } + break; + + default: + bClear = false; + } + if ( bClear ) + { + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + SwLayoutFrame::Modify( pOld, pNew ); + } +} + +/// A follow or a ftncontainer at the end of the page causes a maximal Size of the sectionframe. +bool SwSectionFrame::ToMaximize( bool bCheckFollow ) const +{ + if( HasFollow() ) + { + if( !bCheckFollow ) // Don't check superfluous follows + return true; + const SwSectionFrame* pFoll = GetFollow(); + while( pFoll && pFoll->IsSuperfluous() ) + pFoll = pFoll->GetFollow(); + if( pFoll ) + return true; + } + if( IsFootnoteAtEnd() ) + return false; + const SwFootnoteContFrame* pCont = ContainsFootnoteCont(); + if( !IsEndnAtEnd() ) + return nullptr != pCont; + bool bRet = false; + while( pCont && !bRet ) + { + if( pCont->FindFootNote() ) + bRet = true; + else + pCont = ContainsFootnoteCont( pCont ); + } + return bRet; +} + +/// Check every Column for FootnoteContFrames. +SwFootnoteContFrame* SwSectionFrame::ContainsFootnoteCont( const SwFootnoteContFrame* pCont ) const +{ + SwFootnoteContFrame* pRet = nullptr; + const SwLayoutFrame* pLay; + if( pCont ) + { + pLay = pCont->FindFootnoteBossFrame(); + OSL_ENSURE( IsAnLower( pLay ), "ConatainsFootnoteCont: Wrong FootnoteContainer" ); + pLay = static_cast<const SwLayoutFrame*>(pLay->GetNext()); + } + else if( Lower() && Lower()->IsColumnFrame() ) + pLay = static_cast<const SwLayoutFrame*>(Lower()); + else + pLay = nullptr; + while ( !pRet && pLay ) + { + if( pLay->Lower() && pLay->Lower()->GetNext() ) + { + OSL_ENSURE( pLay->Lower()->GetNext()->IsFootnoteContFrame(), + "ToMaximize: Unexpected Frame" ); + pRet = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pLay->Lower()->GetNext())); + } + OSL_ENSURE( !pLay->GetNext() || pLay->GetNext()->IsLayoutFrame(), + "ToMaximize: ColFrame expected" ); + pLay = static_cast<const SwLayoutFrame*>(pLay->GetNext()); + } + return pRet; +} + +void SwSectionFrame::InvalidateFootnotePos() +{ + SwFootnoteContFrame* pCont = ContainsFootnoteCont(); + if( pCont ) + { + SwFrame *pTmp = pCont->ContainsContent(); + if( pTmp ) + pTmp->InvalidatePos_(); + } +} + +SwTwips SwSectionFrame::CalcUndersize() const +{ + SwRectFnSet aRectFnSet(this); + return InnerHeight() - aRectFnSet.GetHeight(getFramePrintArea()); +} + +SwTwips SwSectionFrame::Undersize() +{ + const auto nRet = CalcUndersize(); + m_bUndersized = (nRet > 0); + return nRet <= 0 ? 0 : nRet; +} + +void SwSectionFrame::CalcFootnoteContent() +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + SwFootnoteContFrame* pCont = ContainsFootnoteCont(); + if( pCont ) + { + SwFrame* pFrame = pCont->ContainsAny(); + if( pFrame ) + pCont->Calc(pRenderContext); + while( pFrame && IsAnLower( pFrame ) ) + { + SwFootnoteFrame* pFootnote = pFrame->FindFootnoteFrame(); + if( pFootnote ) + pFootnote->Calc(pRenderContext); + pFrame->Calc(pRenderContext); + if( pFrame->IsSctFrame() ) + { + SwFrame *pTmp = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pTmp ) + { + pFrame = pTmp; + continue; + } + } + pFrame = pFrame->FindNext(); + } + } +} + +/* + * If a SectionFrame gets empty, e.g. because its content changes the page/column, + * it is not destroyed immediately (there could be a pointer left to it on the + * stack), instead it puts itself in a list at the RootFrame, which is processed + * later on (in Layaction::Action among others). Its size is set to Null and + * the pointer to its page as well. Such SectionFrames that are to be deleted + * must be ignored by the layout/during formatting. + * + * With InsertEmptySct the RootFrame stores a SectionFrame in the list, + * with RemoveFromList it can be removed from the list (Dtor), + * with DeleteEmptySct the list is processed and the SectionFrames are destroyed. + */ +void SwRootFrame::InsertEmptySct( SwSectionFrame* pDel ) +{ + if( !mpDestroy ) + mpDestroy.reset( new SwDestroyList ); + mpDestroy->insert( pDel ); +} + +void SwRootFrame::DeleteEmptySct_() +{ + assert(mpDestroy); + while( !mpDestroy->empty() ) + { + SwSectionFrame* pSect = *mpDestroy->begin(); + mpDestroy->erase( mpDestroy->begin() ); + OSL_ENSURE( !pSect->IsColLocked() && !pSect->IsJoinLocked(), + "DeleteEmptySct: Locked SectionFrame" ); + if( !pSect->getFrameArea().HasArea() && !pSect->ContainsContent() ) + { + SwLayoutFrame* pUp = pSect->GetUpper(); + pSect->RemoveFromLayout(); + SwFrame::DestroyFrame(pSect); + if( pUp && !pUp->Lower() ) + { + if( pUp->IsPageBodyFrame() ) + pUp->getRootFrame()->SetSuperfluous(); + else if( pUp->IsFootnoteFrame() && !pUp->IsColLocked() && + pUp->GetUpper() ) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + } + } + } + else { + OSL_ENSURE( pSect->GetSection(), "DeleteEmptySct: Half-dead SectionFrame?!" ); + } + } +} + +void SwRootFrame::RemoveFromList_( SwSectionFrame* pSct ) +{ + assert(mpDestroy && "Where's my list?"); + mpDestroy->erase( pSct ); +} + +#ifdef DBG_UTIL +bool SwRootFrame::IsInDelList( SwSectionFrame* pSct ) const +{ + return mpDestroy && mpDestroy->find( pSct ) != mpDestroy->end(); +} +#endif + +bool SwSectionFrame::IsBalancedSection() const +{ + bool bRet = false; + if ( GetSection() && Lower() && Lower()->IsColumnFrame() && Lower()->GetNext() ) + { + bRet = !GetSection()->GetFormat()->GetBalancedColumns().GetValue(); + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/softpagebreak.cxx b/sw/source/core/layout/softpagebreak.cxx new file mode 100644 index 000000000..87ba7c24e --- /dev/null +++ b/sw/source/core/layout/softpagebreak.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <txtfrm.hxx> +#include <pagefrm.hxx> +#include <swtable.hxx> +#include <frmfmt.hxx> +#include <rowfrm.hxx> +#include <tabfrm.hxx> +#include <calbck.hxx> +#include <ndtxt.hxx> + +void SwTextNode::fillSoftPageBreakList( SwSoftPageBreakList& rBreak ) const +{ + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for( const SwTextFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + // No soft page break in header or footer + if( pFrame->FindFooterOrHeader() || pFrame->IsInFly() ) + return; + // No soft page break if I'm not the first frame in my layout frame + if( pFrame->GetIndPrev() ) + continue; + const SwPageFrame* pPage = pFrame->FindPageFrame(); + // No soft page break at the first page + if( pPage && pPage->GetPrev() ) + { + const SwContentFrame* pFirst2 = pPage->FindFirstBodyContent(); + // Special handling for content frame in table frames + if( pFrame->IsInTab() ) + { + // No soft page break if I'm in a table but the first content frame + // at my page is not in a table + if( !pFirst2 || !pFirst2->IsInTab() ) + continue; + const SwLayoutFrame *pRow = pFrame->GetUpper(); + // Looking for the "most upper" row frame, + // skipping sub tables and/or table in table + while( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() || + pRow->GetUpper()->GetUpper()->IsInTab() ) + pRow = pRow->GetUpper(); + const SwTabFrame *pTab = pRow->FindTabFrame(); + // For master tables the soft page break will exported at the table row, + // not at the content frame. + // If the first content is outside my table frame, no soft page break. + if( !pTab->IsFollow() || !pTab->IsAnLower( pFirst2 ) ) + continue; + // Only content of non-heading-rows can get a soft page break + const SwFrame* pFirstRow = pTab->GetFirstNonHeadlineRow(); + // If there's no follow flow line, the soft page break will be + // exported at the row, not at the content. + if( pRow == pFirstRow && + pTab->FindMaster()->HasFollowFlowLine() ) + { + // Now we have the row which causes a new page, + // this row is a follow flow line and therefore cannot get + // the soft page break itself. + // Every first content frame of every cell frame in this row + // will get the soft page break + const SwFrame* pCell = pRow->Lower(); + while( pCell ) + { + pFirst2 = static_cast<const SwLayoutFrame*>(pCell)->ContainsContent(); + if( pFirst2 == pFrame ) + { // Here we are: a first content inside a cell + // inside the split row => soft page break + auto const pos(pFrame->MapViewToModel(pFrame->GetOffset())); + if (pos.first == this) + { + rBreak.insert(pos.second); + } + break; + } + pCell = pCell->GetNext(); + } + } + } + else // No soft page break if there's a "hard" page break attribute + if( pFirst2 == pFrame && !pFrame->IsPageBreak( true ) ) + { + auto const pos(pFrame->MapViewToModel(pFrame->GetOffset())); + if (pos.first == this) + { // in the !Show case, we have to iterate over the merged + // SwTextFrame for every node + rBreak.insert(pos.second); + } + } + } + } +} + +bool SwTableLine::hasSoftPageBreak() const +{ + // No soft page break for sub tables + if( GetUpper() || !GetFrameFormat() ) + return false; + SwIterator<SwRowFrame,SwFormat> aIter( *GetFrameFormat() ); + for( SwRowFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->GetTabLine() == this ) + { + const SwTabFrame* pTab = pLast->FindTabFrame(); + // No soft page break for + // tables with prevs, i.e. if the frame is not the first in its layout frame + // tables in footer or header + // tables in flies + // inner tables of nested tables + // master table frames with "hard" page break attribute + if( pTab->GetIndPrev() || pTab->FindFooterOrHeader() + || pTab->IsInFly() || pTab->GetUpper()->IsInTab() || + ( !pTab->IsFollow() && pTab->IsPageBreak( true ) ) ) + return false; + const SwPageFrame* pPage = pTab->FindPageFrame(); + // No soft page break at the first page of the document + if( pPage && !pPage->GetPrev() ) + return false; + const SwContentFrame* pFirst = pPage ? pPage->FindFirstBodyContent() : nullptr; + // No soft page break for + // tables which does not contain the first body content of the page + if( !pFirst || !pTab->IsAnLower( pFirst->FindTabFrame() ) ) + return false; + // The row which could get a soft page break must be either the first + // row of a master table frame or the first "non-headline-row" of a + // follow table frame... + const SwFrame* pRow = pTab->IsFollow() ? + pTab->GetFirstNonHeadlineRow() : pTab->Lower(); + if( pRow == pLast ) + { + // The last check: no soft page break for "follow" table lines + return !pTab->IsFollow() || !pTab->FindMaster()->HasFollowFlowLine(); + } + return false; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/sortedobjs.cxx b/sw/source/core/layout/sortedobjs.cxx new file mode 100644 index 000000000..2e3df1910 --- /dev/null +++ b/sw/source/core/layout/sortedobjs.cxx @@ -0,0 +1,292 @@ +/* -*- 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 <sortedobjs.hxx> + +#include <algorithm> +#include <anchoredobject.hxx> +#include <fmtanchr.hxx> +#include <fmtsrnd.hxx> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <frmfmt.hxx> +#include <pam.hxx> +#include <svx/svdobj.hxx> +#include <IDocumentDrawModelAccess.hxx> + +using namespace ::com::sun::star; + +SwSortedObjs::SwSortedObjs() +{ +} + +SwSortedObjs::~SwSortedObjs() +{ +} + +size_t SwSortedObjs::size() const +{ + return maSortedObjLst.size(); +} + +SwAnchoredObject* SwSortedObjs::operator[]( size_t _nIndex ) const +{ + SwAnchoredObject* pAnchoredObj = nullptr; + + if ( _nIndex >= size() ) + { + OSL_FAIL( "<SwSortedObjs::operator[]> - index out of range" ); + } + else + { + pAnchoredObj = maSortedObjLst[ _nIndex ]; + } + + return pAnchoredObj; +} + +namespace +{ + int GetAnchorWeight(RndStdIds eAnchor) + { + if (eAnchor == RndStdIds::FLY_AT_CHAR) + return 0; + if (eAnchor == RndStdIds::FLY_AS_CHAR) + return 1; + return 2; + } + +struct ObjAnchorOrder +{ + bool operator()( const SwAnchoredObject* _pListedAnchoredObj, + const SwAnchoredObject* _pNewAnchoredObj ) + { + // get attributes of listed object + const SwFrameFormat& rFormatListed = _pListedAnchoredObj->GetFrameFormat(); + const SwFormatAnchor* pAnchorListed = &(rFormatListed.GetAnchor()); + + // get attributes of new object + const SwFrameFormat& rFormatNew = _pNewAnchoredObj->GetFrameFormat(); + const SwFormatAnchor* pAnchorNew = &(rFormatNew.GetAnchor()); + + // check for to-page anchored objects + if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_PAGE) && + (pAnchorNew ->GetAnchorId() != RndStdIds::FLY_AT_PAGE)) + { + return true; + } + else if ((pAnchorListed->GetAnchorId() != RndStdIds::FLY_AT_PAGE) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_PAGE)) + { + return false; + } + else if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_PAGE) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_PAGE)) + { + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } + + // Both objects aren't anchored to page. + // Thus, check for to-fly anchored objects + if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_FLY) && + (pAnchorNew ->GetAnchorId() != RndStdIds::FLY_AT_FLY)) + { + return true; + } + else if ((pAnchorListed->GetAnchorId() != RndStdIds::FLY_AT_FLY) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_FLY)) + { + return false; + } + else if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_FLY) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_FLY)) + { + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } + + // Both objects aren't anchor to page or to fly + // Thus, compare content anchor nodes, if existing. + const SwPosition* pContentAnchorListed = pAnchorListed->GetContentAnchor(); + const SwPosition* pContentAnchorNew = pAnchorNew->GetContentAnchor(); + if ( pContentAnchorListed && pContentAnchorNew && + pContentAnchorListed->nNode != pContentAnchorNew->nNode ) + { + return pContentAnchorListed->nNode < pContentAnchorNew->nNode; + } + + // objects anchored at the same content. + // --> OD 2006-11-29 #???# - objects have to be ordered by anchor node position + // Thus, compare content anchor node positions and anchor type, + // if not anchored at-paragraph + if (pContentAnchorListed && pContentAnchorNew) + { + sal_Int32 nListedIndex = pAnchorListed->GetAnchorId() != RndStdIds::FLY_AT_PARA ? + pContentAnchorListed->nContent.GetIndex() : 0; + sal_Int32 nNewIndex = pAnchorNew->GetAnchorId() != RndStdIds::FLY_AT_PARA ? + pContentAnchorNew->nContent.GetIndex() : 0; + if (nListedIndex != nNewIndex) + { + return nListedIndex < nNewIndex; + } + } + + int nAnchorListedWeight = GetAnchorWeight(pAnchorListed->GetAnchorId()); + int nAnchorNewWeight = GetAnchorWeight(pAnchorNew->GetAnchorId()); + if (nAnchorListedWeight != nAnchorNewWeight) + { + return nAnchorListedWeight < nAnchorNewWeight; + } + + // objects anchored at the same content and at the same content anchor + // node position with the same anchor type + // Thus, compare its wrapping style including its layer + const IDocumentDrawModelAccess& rIDDMA = rFormatListed.getIDocumentDrawModelAccess(); + const SdrLayerID nHellId = rIDDMA.GetHellId(); + const SdrLayerID nInvisibleHellId = rIDDMA.GetInvisibleHellId(); + const bool bWrapThroughOrHellListed = + rFormatListed.GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH || + _pListedAnchoredObj->GetDrawObj()->GetLayer() == nHellId || + _pListedAnchoredObj->GetDrawObj()->GetLayer() == nInvisibleHellId; + const bool bWrapThroughOrHellNew = + rFormatNew.GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH || + _pNewAnchoredObj->GetDrawObj()->GetLayer() == nHellId || + _pNewAnchoredObj->GetDrawObj()->GetLayer() == nInvisibleHellId; + if ( bWrapThroughOrHellListed != bWrapThroughOrHellNew ) + { + return !bWrapThroughOrHellListed; + } + else if ( bWrapThroughOrHellListed && bWrapThroughOrHellNew ) + { + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } + + // objects anchored at the same content with a set text wrapping + // Thus, compare wrap influences on object position + const SwFormatWrapInfluenceOnObjPos* pWrapInfluenceOnObjPosListed = + &(rFormatListed.GetWrapInfluenceOnObjPos()); + const SwFormatWrapInfluenceOnObjPos* pWrapInfluenceOnObjPosNew = + &(rFormatNew.GetWrapInfluenceOnObjPos()); + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + if ( pWrapInfluenceOnObjPosListed->GetWrapInfluenceOnObjPos( true ) != + pWrapInfluenceOnObjPosNew->GetWrapInfluenceOnObjPos( true ) ) + { + // #i35017# - constant name has changed + return pWrapInfluenceOnObjPosListed->GetWrapInfluenceOnObjPos( true ) + == text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE; + } + + // objects anchored at the same content position/page/fly with same + // wrap influence. + // Thus, compare anchor order number + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } +}; + +} + +bool SwSortedObjs::is_sorted() const +{ + return std::is_sorted(maSortedObjLst.begin(), maSortedObjLst.end(), ObjAnchorOrder()); +} + +bool SwSortedObjs::Insert( SwAnchoredObject& _rAnchoredObj ) +{ + // #i51941# + if ( Contains( _rAnchoredObj ) ) + { + // list already contains object + OSL_FAIL( "<SwSortedObjs::Insert()> - already contains object" ); + return true; + } + + // find insert position + std::vector< SwAnchoredObject* >::iterator aInsPosIter = + std::lower_bound( maSortedObjLst.begin(), maSortedObjLst.end(), + &_rAnchoredObj, ObjAnchorOrder() ); + + // insert object into list + maSortedObjLst.insert( aInsPosIter, &_rAnchoredObj ); + + return Contains( _rAnchoredObj ); +} + +void SwSortedObjs::Remove( SwAnchoredObject& _rAnchoredObj ) +{ + std::vector< SwAnchoredObject* >::iterator aDelPosIter = + std::find( maSortedObjLst.begin(), maSortedObjLst.end(), &_rAnchoredObj ); + + if ( aDelPosIter == maSortedObjLst.end() ) + { + // object not found. + OSL_FAIL( "<SwSortedObjs::Remove()> - object not found" ); + } + else + { + maSortedObjLst.erase( aDelPosIter ); + } +} + +bool SwSortedObjs::Contains( const SwAnchoredObject& _rAnchoredObj ) const +{ + std::vector< SwAnchoredObject* >::const_iterator aIter = + std::find( maSortedObjLst.begin(), maSortedObjLst.end(), &_rAnchoredObj ); + + return aIter != maSortedObjLst.end(); +} + +void SwSortedObjs::Update( SwAnchoredObject& _rAnchoredObj ) +{ + if ( !Contains( _rAnchoredObj ) ) + { + // given anchored object not found in list + OSL_FAIL( "<SwSortedObjs::Update(..) - sorted list doesn't contain given anchored object" ); + return; + } + + if ( size() == 1 ) + { + // given anchored object is the only one in the list. + return; + } + + Remove( _rAnchoredObj ); + Insert( _rAnchoredObj ); +} + +void SwSortedObjs::UpdateAll() +{ + std::stable_sort(maSortedObjLst.begin(), maSortedObjLst.end(), ObjAnchorOrder()); +} + +size_t SwSortedObjs::ListPosOf( const SwAnchoredObject& _rAnchoredObj ) const +{ + std::vector< SwAnchoredObject* >::const_iterator aIter = + std::find( maSortedObjLst.begin(), maSortedObjLst.end(), &_rAnchoredObj ); + + if ( aIter != maSortedObjLst.end() ) + { + // #i51941# + std::vector< SwAnchoredObject* >::difference_type nPos = + aIter - maSortedObjLst.begin(); + return static_cast<size_t>( nPos ); + } + + return size(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/ssfrm.cxx b/sw/source/core/layout/ssfrm.cxx new file mode 100644 index 000000000..1a92144a5 --- /dev/null +++ b/sw/source/core/layout/ssfrm.cxx @@ -0,0 +1,739 @@ +/* -*- 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 <pagefrm.hxx> +#include <rootfrm.hxx> +#include <dcontact.hxx> +#include <flyfrm.hxx> +#include <txtfrm.hxx> +#include <cellfrm.hxx> +#include <swtable.hxx> +#include <fmtfsize.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <fmtclds.hxx> +#include <viewimp.hxx> +#include <sortedobjs.hxx> +#include <hints.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> + + // No inline cause we need the function pointers +long SwFrame::GetTopMargin() const + { return getFramePrintArea().Top(); } +long SwFrame::GetBottomMargin() const + { return getFrameArea().Height() -getFramePrintArea().Height() -getFramePrintArea().Top(); } +long SwFrame::GetLeftMargin() const + { return getFramePrintArea().Left(); } +long SwFrame::GetRightMargin() const + { return getFrameArea().Width() - getFramePrintArea().Width() - getFramePrintArea().Left(); } +long SwFrame::GetPrtLeft() const + { return getFrameArea().Left() + getFramePrintArea().Left(); } +long SwFrame::GetPrtBottom() const + { return getFrameArea().Top() + getFramePrintArea().Height() + getFramePrintArea().Top(); } +long SwFrame::GetPrtRight() const + { return getFrameArea().Left() + getFramePrintArea().Width() + getFramePrintArea().Left(); } +long SwFrame::GetPrtTop() const + { return getFrameArea().Top() + getFramePrintArea().Top(); } + +bool SwFrame::SetMinLeft( long nDeadline ) +{ + SwTwips nDiff = nDeadline - getFrameArea().Left(); + if( nDiff > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Left( nDeadline ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( aPrt.Width() - nDiff ); + + return true; + } + return false; +} + +bool SwFrame::SetMaxBottom( long nDeadline ) +{ + SwTwips nDiff = getFrameArea().Top() + getFrameArea().Height() - nDeadline; + if( nDiff > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( aFrm.Height() - nDiff ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( aPrt.Height() - nDiff ); + + return true; + } + return false; +} + +bool SwFrame::SetMaxRight( long nDeadline ) +{ + SwTwips nDiff = getFrameArea().Left() + getFrameArea().Width() - nDeadline; + if( nDiff > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( aFrm.Width() - nDiff ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( aPrt.Width() - nDiff ); + + return true; + } + return false; +} + +void SwFrame::MakeBelowPos( const SwFrame* pUp, const SwFrame* pPrv, bool bNotify ) +{ + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( pPrv ) + { + aFrm.Pos( pPrv->getFrameArea().Pos() ); + aFrm.Pos().AdjustY(pPrv->getFrameArea().Height() ); + } + else + { + aFrm.Pos( pUp->getFrameArea().Pos() ); + aFrm.Pos() += pUp->getFramePrintArea().Pos(); + } + + if( bNotify ) + { + aFrm.Pos().AdjustY(1 ); + } +} + +void SwFrame::MakeLeftPos( const SwFrame* pUp, const SwFrame* pPrv, bool bNotify ) +{ + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( pPrv ) + { + aFrm.Pos( pPrv->getFrameArea().Pos() ); + aFrm.Pos().AdjustX( -(aFrm.Width()) ); + } + else + { + aFrm.Pos( pUp->getFrameArea().Pos() ); + aFrm.Pos() += pUp->getFramePrintArea().Pos(); + aFrm.Pos().AdjustX(pUp->getFramePrintArea().Width() - aFrm.Width() ); + } + + if( bNotify ) + { + aFrm.Pos().AdjustX( -1 ); + } +} + +void SwFrame::MakeRightPos( const SwFrame* pUp, const SwFrame* pPrv, bool bNotify ) +{ + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( pPrv ) + { + aFrm.Pos( pPrv->getFrameArea().Pos() ); + aFrm.Pos().AdjustX(pPrv->getFrameArea().Width() ); + } + else + { + aFrm.Pos( pUp->getFrameArea().Pos() ); + aFrm.Pos() += pUp->getFramePrintArea().Pos(); + } + + if( bNotify ) + { + aFrm.Pos().AdjustX(1 ); + } +} + +void SwFrame::SetTopBottomMargins( long nTop, long nBot ) +{ + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top( nTop ); + aPrt.Height( getFrameArea().Height() - nTop - nBot ); +} + +void SwFrame::SetLeftRightMargins( long nLeft, long nRight) +{ + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( nLeft ); + aPrt.Width( getFrameArea().Width() - nLeft - nRight ); +} + +void SwFrame::SetRightLeftMargins( long nRight, long nLeft) +{ + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( nLeft ); + aPrt.Width( getFrameArea().Width() - nLeft - nRight ); +} + +/// checks the layout direction and invalidates the lower frames recursively, if necessary. +void SwFrame::CheckDirChange() +{ + bool bOldVert = mbVertical; + bool bOldR2L = mbRightToLeft; + SetInvalidVert( true ); + mbInvalidR2L = true; + bool bChg = bOldR2L != IsRightToLeft(); + bool bOldVertL2R = IsVertLR(); + if( ( IsVertical() != bOldVert ) || bChg || bOldVertL2R != IsVertLR() ) + { + InvalidateAll(); + if( IsLayoutFrame() ) + { + // set minimum row height for vertical cells in horizontal table: + if ( IsCellFrame() && GetUpper() ) + { + if ( IsVertical() != GetUpper()->IsVertical() && + static_cast<SwCellFrame*>(this)->GetTabBox()->getRowSpan() == 1 ) + { + enum { + MIN_VERT_CELL_HEIGHT = 1135 + }; + + SwTableLine* pLine = const_cast<SwTableLine*>(static_cast<SwCellFrame*>(this)->GetTabBox()->GetUpper()); + SwFrameFormat* pFrameFormat = pLine->GetFrameFormat(); + SwFormatFrameSize aNew( pFrameFormat->GetFrameSize() ); + if ( SwFrameSize::Fixed != aNew.GetHeightSizeType() ) + aNew.SetHeightSizeType( SwFrameSize::Minimum ); + if ( aNew.GetHeight() < MIN_VERT_CELL_HEIGHT ) + aNew.SetHeight( MIN_VERT_CELL_HEIGHT ); + SwDoc* pDoc = pFrameFormat->GetDoc(); + pDoc->SetAttr( aNew, *pLine->ClaimFrameFormat() ); + } + } + + SwFrame* pFrame = static_cast<SwLayoutFrame*>(this)->Lower(); + const SwFormatCol* pCol = nullptr; + SwLayoutFrame* pBody = nullptr; + if( pFrame ) + { + if( IsPageFrame() ) + { + // If we're a page frame and we change our layout direction, + // we have to look for columns and rearrange them. + pBody = static_cast<SwPageFrame*>(this)->FindBodyCont(); + if(pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame()) + pCol = &static_cast<SwPageFrame*>(this)->GetFormat()->GetCol(); + } + else if( pFrame->IsColumnFrame() ) + { + pBody = static_cast<SwLayoutFrame*>(this); + const SwFrameFormat *pFormat = pBody->GetFormat(); + if( pFormat ) + pCol = &pFormat->GetCol(); + } + } + while( pFrame ) + { + pFrame->CheckDirChange(); + pFrame = pFrame->GetNext(); + } + if( pCol ) + pBody->AdjustColumns( pCol, true ); + } + else if( IsTextFrame() ) + static_cast<SwTextFrame*>(this)->Prepare(); + + // #i31698# - notify anchored objects also for page frames. + // Remove code above for special handling of page frames + if ( GetDrawObjs() ) + { + const SwSortedObjs *pObjs = GetDrawObjs(); + const size_t nCnt = pObjs->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + static_cast<SwFlyFrame*>(pAnchoredObj)->CheckDirChange(); + else + { + // OD 2004-04-06 #i26791# - direct object + // positioning no longer needed. Instead + // invalidate + pAnchoredObj->InvalidateObjPos(); + } + // #i31698# - update layout direction of + // anchored object + { + ::setContextWritingMode( pAnchoredObj->DrawObj(), pAnchoredObj->GetAnchorFrameContainingAnchPos() ); + pAnchoredObj->UpdateLayoutDir(); + } + } + } + } +} + +/// returns the position for anchors based on frame direction +// OD 2004-03-10 #i11860# - consider lower space and line spacing of +// previous frame according to new option 'Use former object positioning' +Point SwFrame::GetFrameAnchorPos( bool bIgnoreFlysAnchoredAtThisFrame ) const +{ + Point aAnchor = getFrameArea().Pos(); + + if ( ( IsVertical() && !IsVertLR() ) || IsRightToLeft() ) + aAnchor.AdjustX(getFrameArea().Width() ); + + if ( IsTextFrame() ) + { + SwTwips nBaseOfstForFly = + static_cast<const SwTextFrame*>(this)->GetBaseOffsetForFly( bIgnoreFlysAnchoredAtThisFrame ); + if ( IsVertical() ) + aAnchor.AdjustY(nBaseOfstForFly ); + else + aAnchor.AdjustX(nBaseOfstForFly ); + + // OD 2004-03-10 #i11860# - if option 'Use former object positioning' + // is OFF, consider the lower space and the line spacing of the + // previous frame and the spacing considered for the page grid + const SwTextFrame* pThisTextFrame = static_cast<const SwTextFrame*>(this); + const SwTwips nUpperSpaceAmountConsideredForPrevFrameAndPageGrid = + pThisTextFrame->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + if ( IsVertical() ) + { + aAnchor.AdjustX( -nUpperSpaceAmountConsideredForPrevFrameAndPageGrid ); + } + else + { + aAnchor.AdjustY(nUpperSpaceAmountConsideredForPrevFrameAndPageGrid ); + } + } + + return aAnchor; +} + +void SwFrame::DestroyImpl() +{ + mbInDtor = true; + + // accessible objects for fly and cell frames have been already disposed + // by the destructors of the derived classes. + if (IsAccessibleFrame() && !(IsFlyFrame() || IsCellFrame()) + && (GetDep() || IsTextFrame())) // sw_redlinehide: text frame may not have Dep! + { + assert(!IsTextFrame() || GetDep() || static_cast<SwTextFrame*>(this)->GetMergedPara()); + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell *pVSh = pRootFrame->GetCurrShell(); + if( pVSh && pVSh->Imp() ) + { + OSL_ENSURE( !GetLower(), "Lowers should be dispose already!" ); + pVSh->Imp()->DisposeAccessibleFrame( this ); + } + } + } + + if (m_pDrawObjs) + { + for (size_t i = m_pDrawObjs->size(); i; ) + { + SwAnchoredObject* pAnchoredObj = (*m_pDrawObjs)[--i]; + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFrame::DestroyFrame(static_cast<SwFlyFrame*>(pAnchoredObj)); + } + else + { + SdrObject* pSdrObj = pAnchoredObj->DrawObj(); + SwDrawContact* pContact = + static_cast<SwDrawContact*>(pSdrObj->GetUserCall()); + OSL_ENSURE( pContact, + "<SwFrame::~SwFrame> - missing contact for drawing object" ); + if ( pContact ) + { + pContact->DisconnectObjFromLayout( pSdrObj ); + } + } + } + m_pDrawObjs.reset(); + } +} + +SwFrame::~SwFrame() +{ + assert(m_isInDestroy); // check that only DestroySwFrame does "delete" + assert(!IsDeleteForbidden()); // check that it's not deleted while deletes are forbidden +#if OSL_DEBUG_LEVEL > 0 + // JP 15.10.2001: for detection of access to deleted frames + mpRoot = reinterpret_cast<SwRootFrame*>(0x33333333); +#endif +} + +void SwFrame::DestroyFrame(SwFrame *const pFrame) +{ + if (pFrame) + { + pFrame->m_isInDestroy = true; + pFrame->DestroyImpl(); + assert(pFrame->mbInDtor); // check that nobody forgot to call base class + delete pFrame; + } +} + +const SwFrameFormat * SwLayoutFrame::GetFormat() const +{ + return static_cast< const SwFrameFormat * >( GetDep() ); +} + +SwFrameFormat * SwLayoutFrame::GetFormat() +{ + return static_cast< SwFrameFormat * >( GetDep() ); +} + +void SwLayoutFrame::SetFrameFormat( SwFrameFormat *pNew ) +{ + if ( pNew != GetFormat() ) + { + SwFormatChg aOldFormat( GetFormat() ); + pNew->Add( this ); + SwFormatChg aNewFormat( pNew ); + ModifyNotification( &aOldFormat, &aNewFormat ); + } +} + +SwContentFrame::SwContentFrame( SwContentNode * const pContent, SwFrame* pSib ) : + SwFrame( pContent, pSib ), + SwFlowFrame( static_cast<SwFrame&>(*this) ) +{ + assert(!getRootFrame()->IsHideRedlines() || pContent->IsCreateFrameWhenHidingRedlines()); +} + +void SwContentFrame::DestroyImpl() +{ + const SwContentNode* pCNd(dynamic_cast<SwContentNode*>(GetDep())); + if (nullptr == pCNd && IsTextFrame()) + { + pCNd = static_cast<SwTextFrame*>(this)->GetTextNodeFirst(); + } + // IsInDtor shouldn't be happening with ViewShell owning layout + assert(nullptr == pCNd || !pCNd->GetDoc()->IsInDtor()); + if (nullptr != pCNd && !pCNd->GetDoc()->IsInDtor()) + { + //Unregister from root if I'm still in turbo there. + SwRootFrame *pRoot = getRootFrame(); + if( pRoot && pRoot->GetTurbo() == this ) + { + pRoot->DisallowTurbo(); + pRoot->ResetTurbo(); + } + } + + SwFrame::DestroyImpl(); +} + +SwContentFrame::~SwContentFrame() +{ +} + +void SwTextFrame::RegisterToNode(SwTextNode & rNode, bool const isForceNodeAsFirst) +{ + if (isForceNodeAsFirst && m_pMergedPara) + { // nothing registered here, in particular no delete redlines (insert + // redline might end on empty node where delete rl starts, should be ok) + assert(m_pMergedPara->pFirstNode->GetIndex() + 1 == rNode.GetIndex()); + assert(rNode.GetDoc()->getIDocumentRedlineAccess().GetRedlinePos( + *m_pMergedPara->pFirstNode, RedlineType::Delete) == SwRedlineTable::npos); + } + assert(&rNode != GetDep()); + assert(!m_pMergedPara + || (m_pMergedPara->pFirstNode->GetIndex() < rNode.GetIndex()) + || (rNode.GetIndex() + 1 == m_pMergedPara->pFirstNode->GetIndex())); + SwTextNode & rFirstNode( + (!isForceNodeAsFirst && m_pMergedPara && m_pMergedPara->pFirstNode->GetIndex() < rNode.GetIndex()) + ? *m_pMergedPara->pFirstNode + : rNode); + // sw_redlinehide: use New here, because the only caller also calls lcl_ChangeFootnoteRef + m_pMergedPara = sw::CheckParaRedlineMerge(*this, rFirstNode, sw::FrameMode::New); + if (!m_pMergedPara) + { + rNode.Add(this); + } +} + +void SwLayoutFrame::DestroyImpl() +{ + while (!m_VertPosOrientFramesFor.empty()) + { + SwAnchoredObject *pObj = *m_VertPosOrientFramesFor.begin(); + pObj->ClearVertPosOrientFrame(); + } + + assert(m_VertPosOrientFramesFor.empty()); + + SwFrame *pFrame = m_pLower; + + if( GetFormat() && !GetFormat()->GetDoc()->IsInDtor() ) + { + while ( pFrame ) + { + //First delete the Objs of the Frame because they can't unregister + //from the page after remove. + //We don't want to create an endless loop only because one couldn't + //unregister. + + while ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) + { + const size_t nCnt = pFrame->GetDrawObjs()->size(); + // #i28701# + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[0]; + if (SwFlyFrame* pFlyFrame = dynamic_cast<SwFlyFrame*>(pAnchoredObj)) + { + SwFrame::DestroyFrame(pFlyFrame); + assert(!pFrame->GetDrawObjs() || nCnt > pFrame->GetDrawObjs()->size()); + } + else + { + pAnchoredObj->ClearTmpConsiderWrapInfluence(); + SdrObject* pSdrObj = pAnchoredObj->DrawObj(); + SwDrawContact* pContact = + static_cast<SwDrawContact*>(pSdrObj->GetUserCall()); + OSL_ENSURE( pContact, + "<SwFrame::~SwFrame> - missing contact for drawing object" ); + if ( pContact ) + { + pContact->DisconnectObjFromLayout( pSdrObj ); + } + + if ( pFrame->GetDrawObjs() && + nCnt == pFrame->GetDrawObjs()->size() ) + { + pFrame->GetDrawObjs()->Remove( *pAnchoredObj ); + } + } + } + pFrame->RemoveFromLayout(); + SwFrame::DestroyFrame(pFrame); + pFrame = m_pLower; + } + //Delete the Flys, the last one also deletes the array. + while ( GetDrawObjs() && GetDrawObjs()->size() ) + { + const size_t nCnt = GetDrawObjs()->size(); + + // #i28701# + SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[0]; + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFrame::DestroyFrame(static_cast<SwFlyFrame*>(pAnchoredObj)); + assert(!GetDrawObjs() || nCnt > GetDrawObjs()->size()); + } + else + { + SdrObject* pSdrObj = pAnchoredObj->DrawObj(); + SwDrawContact* pContact = + static_cast<SwDrawContact*>(pSdrObj->GetUserCall()); + OSL_ENSURE( pContact, + "<SwFrame::~SwFrame> - missing contact for drawing object" ); + if ( pContact ) + { + pContact->DisconnectObjFromLayout( pSdrObj ); + } + + if ( GetDrawObjs() && nCnt == GetDrawObjs()->size() ) + { + GetDrawObjs()->Remove( *pAnchoredObj ); + } + } + } + } + else + { + while( pFrame ) + { + SwFrame *pNxt = pFrame->GetNext(); + SwFrame::DestroyFrame(pFrame); + pFrame = pNxt; + } + } + + SwFrame::DestroyImpl(); +} + +SwLayoutFrame::~SwLayoutFrame() +{ +} + +/** +|* The paintarea is the area, in which the content of a frame is allowed +|* to be displayed. This region could be larger than the printarea (getFramePrintArea()) +|* of the upper, it includes e.g. often the margin of the page. +|*/ +SwRect SwFrame::GetPaintArea() const +{ + // NEW TABLES + // Cell frames may not leave their upper: + SwRect aRect = IsRowFrame() ? GetUpper()->getFrameArea() : getFrameArea(); + const bool bVert = IsVertical(); + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + SwRectFnSet aRectFnSet(this); + long nRight = (aRect.*fnRect->fnGetRight)(); + long nLeft = (aRect.*fnRect->fnGetLeft)(); + const SwFrame* pTmp = this; + bool bLeft = true; + bool bRight = true; + long nRowSpan = 0; + while( pTmp ) + { + if( pTmp->IsCellFrame() && pTmp->GetUpper() && + pTmp->GetUpper()->IsVertical() != pTmp->IsVertical() ) + nRowSpan = static_cast<const SwCellFrame*>(pTmp)->GetTabBox()->getRowSpan(); + long nTmpRight = (pTmp->getFrameArea().*fnRect->fnGetRight)(); + long nTmpLeft = (pTmp->getFrameArea().*fnRect->fnGetLeft)(); + if( pTmp->IsRowFrame() && nRowSpan > 1 ) + { + const SwFrame* pNxt = pTmp; + while( --nRowSpan > 0 && pNxt->GetNext() ) + pNxt = pNxt->GetNext(); + if( pTmp->IsVertical() ) + nTmpLeft = (pNxt->getFrameArea().*fnRect->fnGetLeft)(); + else + { + // pTmp is a row frame, but it's not vertical. + if (IsVertLRBT()) + { + // This frame cell is OK to expand towards the physical down direction. + // Physical down is left. + nTmpLeft = (pNxt->getFrameArea().*fnRect->fnGetLeft)(); + } + else + { + nTmpRight = (pNxt->getFrameArea().*fnRect->fnGetRight)(); + } + } + } + OSL_ENSURE( pTmp, "GetPaintArea lost in time and space" ); + if( pTmp->IsPageFrame() || pTmp->IsFlyFrame() || + pTmp->IsCellFrame() || pTmp->IsRowFrame() || //nobody leaves a table! + pTmp->IsRootFrame() ) + { + if( bLeft || aRectFnSet.XDiff(nTmpLeft, nLeft) > 0 ) + nLeft = nTmpLeft; + if( bRight || aRectFnSet.XDiff(nRight, nTmpRight) > 0 ) + nRight = nTmpRight; + if( pTmp->IsPageFrame() || pTmp->IsFlyFrame() || pTmp->IsRootFrame() ) + break; + bLeft = false; + bRight = false; + } + else if( pTmp->IsColumnFrame() ) // nobody enters neighbour columns + { + bool bR2L = pTmp->IsRightToLeft(); + // the first column has _no_ influence to the left range + if( bR2L ? pTmp->GetNext() : pTmp->GetPrev() ) + { + if( bLeft || aRectFnSet.XDiff(nTmpLeft, nLeft) > 0 ) + nLeft = nTmpLeft; + bLeft = false; + } + // the last column has _no_ influence to the right range + if( bR2L ? pTmp->GetPrev() : pTmp->GetNext() ) + { + if( bRight || aRectFnSet.XDiff(nRight, nTmpRight) > 0 ) + nRight = nTmpRight; + bRight = false; + } + } + else if( bVert && pTmp->IsBodyFrame() ) + { + // Header and footer frames have always horizontal direction and + // limit the body frame. + // A previous frame of a body frame must be a header, + // the next frame of a body frame may be a footnotecontainer or + // a footer. The footnotecontainer has the same direction like + // the body frame. + if( pTmp->GetPrev() && ( bLeft || aRectFnSet.XDiff(nTmpLeft, nLeft) > 0 ) ) + { + nLeft = nTmpLeft; + bLeft = false; + } + if( pTmp->GetNext() && + ( pTmp->GetNext()->IsFooterFrame() || pTmp->GetNext()->GetNext() ) + && ( bRight || aRectFnSet.XDiff(nRight, nTmpRight) > 0 ) ) + { + nRight = nTmpRight; + bRight = false; + } + } + pTmp = pTmp->GetUpper(); + } + (aRect.*fnRect->fnSetLeft)( nLeft ); + (aRect.*fnRect->fnSetRight)( nRight ); + return aRect; +} + +/** +|* The unionframe is the framearea (getFrameArea()) of a frame expanded by the +|* printarea, if there's a negative margin at the left or right side. +|*/ +SwRect SwFrame::UnionFrame( bool bBorder ) const +{ + bool bVert = IsVertical(); + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + long nLeft = (getFrameArea().*fnRect->fnGetLeft)(); + long nWidth = (getFrameArea().*fnRect->fnGetWidth)(); + long nPrtLeft = (getFramePrintArea().*fnRect->fnGetLeft)(); + long nPrtWidth = (getFramePrintArea().*fnRect->fnGetWidth)(); + SwRectFnSet aRectFnSet(this); + if (aRectFnSet.XInc(nPrtLeft, nPrtWidth) > nWidth) + nWidth = nPrtLeft + nPrtWidth; + if( nPrtLeft < 0 ) + { + nLeft += nPrtLeft; + nWidth -= nPrtLeft; + } + SwTwips nRight = aRectFnSet.XInc(nLeft, nWidth); + long nAdd = 0; + if( bBorder ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + const SvxBoxItem &rBox = rAttrs.GetBox(); + if ( rBox.GetLeft() ) + nLeft -= rBox.CalcLineSpace( SvxBoxItemLine::LEFT ); + else + nLeft -= rBox.GetDistance( SvxBoxItemLine::LEFT ) + 1; + if ( rBox.GetRight() ) + nAdd += rBox.CalcLineSpace( SvxBoxItemLine::RIGHT ); + else + nAdd += rBox.GetDistance( SvxBoxItemLine::RIGHT ) + 1; + if( rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE ) + { + const SvxShadowItem &rShadow = rAttrs.GetShadow(); + nLeft -= rShadow.CalcShadowSpace( SvxShadowItemSide::LEFT ); + nAdd += rShadow.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + } + } + if( IsTextFrame() && static_cast<const SwTextFrame*>(this)->HasPara() ) + { + long nTmp = static_cast<const SwTextFrame*>(this)->HangingMargin(); + if( nTmp > nAdd ) + nAdd = nTmp; + } + nWidth = aRectFnSet.XDiff(aRectFnSet.XInc(nRight, nAdd), nLeft); + SwRect aRet( getFrameArea() ); + (aRet.*fnRect->fnSetLeft)(nLeft); + (aRet.*fnRect->fnSetWidth)( nWidth ); + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/swselectionlist.cxx b/sw/source/core/layout/swselectionlist.cxx new file mode 100644 index 000000000..b7628cbac --- /dev/null +++ b/sw/source/core/layout/swselectionlist.cxx @@ -0,0 +1,83 @@ +/* -*- 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 <swselectionlist.hxx> +#include <flyfrm.hxx> +#include <ftnfrm.hxx> + +/** This class is used as parameter for functions to create a rectangular text selection +*/ + +namespace { + + /** Find the context of a given frame + + A context is the environment where text is allowed to flow. + The context is represented by + - the SwRootFrame if the frame is part of a page body + - the SwHeaderFrame if the frame is part of a page header + - the SwFooterFrame if the frame is part of a page footer + - the (master) SwFootnoteFrame if the frame is part of footnote + - the (first) SwFlyFrame if the frame is part of a (linked) fly frame + + @param pFrame + the given frame + + @return the context of the frame, represented by a SwFrame* + */ + const SwFrame* getContext( const SwFrame* pFrame ) + { + while( pFrame ) + { + if( pFrame->IsRootFrame() || pFrame->IsHeaderFrame() || pFrame->IsFooterFrame() ) + break; + if( pFrame->IsFlyFrame() ) + { + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>( pFrame ); + while( pFly->GetPrevLink() ) + pFly = pFly->GetPrevLink(); + break; + } + if( pFrame->IsFootnoteFrame() ) + { + const SwFootnoteFrame* pFootnote = static_cast<const SwFootnoteFrame*>( pFrame ); + while( pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + break; + } + pFrame = pFrame->GetUpper(); + } + return pFrame; + } +} + +SwSelectionList::SwSelectionList( const SwFrame* pInitCxt ) : + m_pContext( getContext( pInitCxt ) ) +{ +} + +bool SwSelectionList::checkContext( const SwFrame* pCheck ) +{ + pCheck = getContext( pCheck ); + if( !m_pContext ) + m_pContext = pCheck; + return m_pContext == pCheck; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx new file mode 100644 index 000000000..009099250 --- /dev/null +++ b/sw/source/core/layout/tabfrm.cxx @@ -0,0 +1,5851 @@ +/* -*- 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 <pagefrm.hxx> +#include <rootfrm.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <viewimp.hxx> +#include <fesh.hxx> +#include <swtable.hxx> +#include <dflyobj.hxx> +#include <anchoreddrawobject.hxx> +#include <fmtanchr.hxx> +#include <viewopt.hxx> +#include <hints.hxx> +#include <dbg_lay.hxx> +#include <ftnidx.hxx> +#include <svl/itemiter.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/boxitem.hxx> +#include <basegfx/range/b1drange.hxx> +#include <fmtlsplt.hxx> +#include <fmtrowsplt.hxx> +#include <fmtsrnd.hxx> +#include <fmtornt.hxx> +#include <fmtpdsc.hxx> +#include <fmtfsize.hxx> +#include <swtblfmt.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <flyfrms.hxx> +#include <txtfrm.hxx> +#include <ftnfrm.hxx> +#include <notxtfrm.hxx> +#include <htmltbl.hxx> +#include <sectfrm.hxx> +#include <fmtfollowtextflow.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <layouter.hxx> +#include <calbck.hxx> +#include <DocumentSettingManager.hxx> +#include <docary.hxx> +#include <sal/log.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <frameformats.hxx> + +using namespace ::com::sun::star; + +SwTabFrame::SwTabFrame( SwTable &rTab, SwFrame* pSib ) + : SwLayoutFrame( rTab.GetFrameFormat(), pSib ) + , SwFlowFrame( static_cast<SwFrame&>(*this) ) + , m_pTable( &rTab ) + , m_bComplete(false) + , m_bCalcLowers(false) + , m_bLowersFormatted(false) + , m_bLockBackMove(false) + , m_bResizeHTMLTable(false) + , m_bONECalcLowers(false) + , m_bHasFollowFlowLine(false) + , m_bIsRebuildLastLine(false) + , m_bRestrictTableGrowth(false) + , m_bRemoveFollowFlowLinePending(false) + , m_bConsiderObjsForMinCellHeight(true) + , m_bObjsDoesFit(true) + , m_bInRecalcLowerRow(false) + , m_bSplitRowDisabled(false) +{ + mbFixSize = false; //Don't fall for import filter again. + mnFrameType = SwFrameType::Tab; + + //Create the lines and insert them. + const SwTableLines &rLines = rTab.GetTabLines(); + SwFrame *pTmpPrev = nullptr; + for ( size_t i = 0; i < rLines.size(); ++i ) + { + SwRowFrame *pNew = new SwRowFrame( *rLines[i], this ); + if( pNew->Lower() ) + { + pNew->InsertBehind( this, pTmpPrev ); + pTmpPrev = pNew; + } + else + SwFrame::DestroyFrame(pNew); + } + OSL_ENSURE( Lower() && Lower()->IsRowFrame(), "SwTabFrame::SwTabFrame: No rows." ); +} + +SwTabFrame::SwTabFrame( SwTabFrame &rTab ) + : SwLayoutFrame( rTab.GetFormat(), &rTab ) + , SwFlowFrame( static_cast<SwFrame&>(*this) ) + , m_pTable( rTab.GetTable() ) + , m_bComplete(false) + , m_bCalcLowers(false) + , m_bLowersFormatted(false) + , m_bLockBackMove(false) + , m_bResizeHTMLTable(false) + , m_bONECalcLowers(false) + , m_bHasFollowFlowLine(false) + , m_bIsRebuildLastLine(false) + , m_bRestrictTableGrowth(false) + , m_bRemoveFollowFlowLinePending(false) + , m_bConsiderObjsForMinCellHeight(true) + , m_bObjsDoesFit(true) + , m_bInRecalcLowerRow(false) + , m_bSplitRowDisabled(false) +{ + mbFixSize = false; //Don't fall for import filter again. + mnFrameType = SwFrameType::Tab; + + SetFollow( rTab.GetFollow() ); + rTab.SetFollow( this ); +} + +void SwTabFrame::DestroyImpl() +{ + // There is some terrible code in fetab.cxx, that + // caches pointers to SwTabFrames. + ::ClearFEShellTabCols(*GetFormat()->GetDoc(), this); + + SwLayoutFrame::DestroyImpl(); +} + +SwTabFrame::~SwTabFrame() +{ +} + +void SwTabFrame::JoinAndDelFollows() +{ + SwTabFrame *pFoll = GetFollow(); + if ( pFoll->HasFollow() ) + pFoll->JoinAndDelFollows(); + pFoll->Cut(); + SetFollow( pFoll->GetFollow() ); + SwFrame::DestroyFrame(pFoll); +} + +void SwTabFrame::RegistFlys() +{ + OSL_ENSURE( Lower() && Lower()->IsRowFrame(), "No rows." ); + + SwPageFrame *pPage = FindPageFrame(); + if ( pPage ) + { + SwRowFrame *pRow = static_cast<SwRowFrame*>(Lower()); + do + { + pRow->RegistFlys( pPage ); + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + } while ( pRow ); + } +} + +static void SwInvalidateAll( SwFrame *pFrame, long nBottom ); +static void lcl_RecalcRow( SwRowFrame& rRow, long nBottom ); +static bool lcl_ArrangeLowers( SwLayoutFrame *pLay, long lYStart, bool bInva ); +// #i26945# - add parameter <_bOnlyRowsAndCells> to control +// that only row and cell frames are formatted. +static bool lcl_InnerCalcLayout( SwFrame *pFrame, + long nBottom, + bool _bOnlyRowsAndCells = false ); +// OD 2004-02-18 #106629# - correct type of 1st parameter +// #i26945# - add parameter <_bConsiderObjs> in order to +// control, if floating screen objects have to be considered for the minimal +// cell height. +static SwTwips lcl_CalcMinRowHeight( const SwRowFrame *pRow, + const bool _bConsiderObjs ); +static SwTwips lcl_CalcTopAndBottomMargin( const SwLayoutFrame&, const SwBorderAttrs& ); + +static SwTwips lcl_calcHeightOfRowBeforeThisFrame(const SwRowFrame& rRow); + +static SwTwips lcl_GetHeightOfRows( const SwFrame* pStart, long nCount ) +{ + if ( !nCount || !pStart) + return 0; + + SwTwips nRet = 0; + SwRectFnSet aRectFnSet(pStart); + while ( pStart && nCount > 0 ) + { + nRet += aRectFnSet.GetHeight(pStart->getFrameArea()); + pStart = pStart->GetNext(); + --nCount; + } + + return nRet; +} + +// Local helper function to insert a new follow flow line +static SwRowFrame* lcl_InsertNewFollowFlowLine( SwTabFrame& rTab, const SwFrame& rTmpRow, bool bRowSpanLine ) +{ + OSL_ENSURE( rTmpRow.IsRowFrame(), "No row frame to copy for FollowFlowLine" ); + const SwRowFrame& rRow = static_cast<const SwRowFrame&>(rTmpRow); + + rTab.SetFollowFlowLine( true ); + SwRowFrame *pFollowFlowLine = new SwRowFrame(*rRow.GetTabLine(), &rTab, false ); + pFollowFlowLine->SetRowSpanLine( bRowSpanLine ); + SwFrame* pFirstRow = rTab.GetFollow()->GetFirstNonHeadlineRow(); + pFollowFlowLine->InsertBefore( rTab.GetFollow(), pFirstRow ); + return pFollowFlowLine; +} + +// #i26945# - local helper function to invalidate all lower +// objects. By parameter <_bMoveObjsOutOfRange> it can be controlled, if +// additionally the objects are moved 'out of range'. +static void lcl_InvalidateLowerObjs( SwLayoutFrame& _rLayoutFrame, + const bool _bMoveObjsOutOfRange = false, + SwPageFrame* _pPageFrame = nullptr ) +{ + // determine page frame, if needed + if ( !_pPageFrame ) + { + _pPageFrame = _rLayoutFrame.FindPageFrame(); + OSL_ENSURE( _pPageFrame, + "<lcl_InvalidateLowerObjs(..)> - missing page frame -> no move of lower objects out of range" ); + if ( !_pPageFrame ) + { + return; + } + } + + // loop on lower frames + SwFrame* pLowerFrame = _rLayoutFrame.Lower(); + while ( pLowerFrame ) + { + if ( pLowerFrame->IsLayoutFrame() ) + { + ::lcl_InvalidateLowerObjs( *static_cast<SwLayoutFrame*>(pLowerFrame), + _bMoveObjsOutOfRange, _pPageFrame ); + } + if ( pLowerFrame->GetDrawObjs() ) + { + for (SwAnchoredObject* pAnchoredObj : *pLowerFrame->GetDrawObjs()) + { + // invalidate position of anchored object + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + pAnchoredObj->SetConsiderForTextWrap( false ); + pAnchoredObj->UnlockPosition(); + pAnchoredObj->InvalidateObjPos(); + + SwFlyFrame *pFly = dynamic_cast<SwFlyFrame*>(pAnchoredObj); + + // move anchored object 'out of range' + if ( _bMoveObjsOutOfRange ) + { + // indicate, that positioning is progress to avoid + // modification of the anchored object resp. it's attributes + // due to the movement + SwObjPositioningInProgress aObjPosInProgress( *pAnchoredObj ); + pAnchoredObj->SetObjLeft( _pPageFrame->getFrameArea().Right() ); + // #115759# - reset character rectangle, + // top of line and relative position in order to assure, + // that anchored object is correctly positioned. + pAnchoredObj->ClearCharRectAndTopOfLine(); + pAnchoredObj->SetCurrRelPos( Point( 0, 0 ) ); + if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AS_CHAR ) + { + pAnchoredObj->AnchorFrame() + ->Prepare( PrepareHint::FlyFrameAttributesChanged, + &(pAnchoredObj->GetFrameFormat()) ); + } + if ( pFly != nullptr ) + { + pFly->GetVirtDrawObj()->SetRectsDirty(); + pFly->GetVirtDrawObj()->SetChanged(); + } + } + + // If anchored object is a fly frame, invalidate its lower objects + if ( pFly != nullptr ) + { + ::lcl_InvalidateLowerObjs( *pFly, _bMoveObjsOutOfRange, _pPageFrame ); + } + } + } + pLowerFrame = pLowerFrame->GetNext(); + } +} + +// Local helper function to shrink all lowers of pRow to 0 height +static void lcl_ShrinkCellsAndAllContent( SwRowFrame& rRow ) +{ + SwCellFrame* pCurrMasterCell = static_cast<SwCellFrame*>(rRow.Lower()); + SwRectFnSet aRectFnSet(pCurrMasterCell); + + bool bAllCellsCollapsed = true; + while ( pCurrMasterCell ) + { + // NEW TABLES + SwCellFrame& rToAdjust = pCurrMasterCell->GetTabBox()->getRowSpan() < 1 ? + const_cast<SwCellFrame&>(pCurrMasterCell->FindStartEndOfRowSpanCell( true )) : + *pCurrMasterCell; + + // #i26945# + // all lowers should have the correct position + lcl_ArrangeLowers( &rToAdjust, + aRectFnSet.GetPrtTop(rToAdjust), + false ); + // TODO: Optimize number of frames which are set to 0 height + // we have to start with the last lower frame, otherwise + // the shrink will not shrink the current cell + SwFrame* pTmp = rToAdjust.GetLastLower(); + bool bAllLowersCollapsed = true; + + if ( pTmp && pTmp->IsRowFrame() ) + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(pTmp); + lcl_ShrinkCellsAndAllContent( *pTmpRow ); + } + else + { + // TODO: Optimize number of frames which are set to 0 height + while ( pTmp ) + { + // the frames have to be shrunk + if ( pTmp->IsTabFrame() ) + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(static_cast<SwTabFrame*>(pTmp)->Lower()); + bool bAllRowsCollapsed = true; + + while ( pTmpRow ) + { + lcl_ShrinkCellsAndAllContent( *pTmpRow ); + + if (aRectFnSet.GetHeight(pTmpRow->getFrameArea()) > 0) + bAllRowsCollapsed = false; + + pTmpRow = static_cast<SwRowFrame*>(pTmpRow->GetNext()); + } + + if (bAllRowsCollapsed) + { + // All rows of this table have 0 height -> set height of the table itself as well. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pTmp); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } + else + bAllLowersCollapsed = false; + } + else + { + pTmp->Shrink(aRectFnSet.GetHeight(pTmp->getFrameArea())); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + + if (aRectFnSet.GetHeight(pTmp->getFrameArea()) > 0) + { + bAllLowersCollapsed = false; + } + } + + pTmp = pTmp->GetPrev(); + } + + // all lowers should have the correct position + lcl_ArrangeLowers( &rToAdjust, + aRectFnSet.GetPrtTop(rToAdjust), + false ); + } + + if (bAllLowersCollapsed) + { + // All lower frame of this cell have 0 height -> set height of the cell itself as well. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCurrMasterCell); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pCurrMasterCell); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } + else + bAllCellsCollapsed = false; + + pCurrMasterCell = static_cast<SwCellFrame*>(pCurrMasterCell->GetNext()); + } + + if (bAllCellsCollapsed) + { + // All cells have 0 height -> set height of row as well. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(rRow); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(rRow); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } +} + +// Local helper function to move the content from rSourceLine to rDestLine +// The content is inserted behind the last content in the corresponding +// cell in rDestLine. +static void lcl_MoveRowContent( SwRowFrame& rSourceLine, SwRowFrame& rDestLine ) +{ + SwCellFrame* pCurrDestCell = static_cast<SwCellFrame*>(rDestLine.Lower()); + SwCellFrame* pCurrSourceCell = static_cast<SwCellFrame*>(rSourceLine.Lower()); + + // Move content of follow cells into master cells + while ( pCurrSourceCell ) + { + if ( pCurrSourceCell->Lower() && pCurrSourceCell->Lower()->IsRowFrame() ) + { + SwRowFrame* pTmpSourceRow = static_cast<SwRowFrame*>(pCurrSourceCell->Lower()); + while ( pTmpSourceRow ) + { + // #125926# Attention! It is possible, + // that pTmpSourceRow->IsFollowFlowRow() but pTmpDestRow + // cannot be found. In this case, we have to move the complete + // row. + SwRowFrame* pTmpDestRow = static_cast<SwRowFrame*>(pCurrDestCell->Lower()); + + if ( pTmpSourceRow->IsFollowFlowRow() && pTmpDestRow ) + { + // move content from follow flow row to pTmpDestRow: + while ( pTmpDestRow->GetNext() ) + pTmpDestRow = static_cast<SwRowFrame*>(pTmpDestRow->GetNext()); + + assert(pTmpDestRow->GetFollowRow() == pTmpSourceRow); + + lcl_MoveRowContent( *pTmpSourceRow, *pTmpDestRow ); + pTmpDestRow->SetFollowRow( pTmpSourceRow->GetFollowRow() ); + pTmpSourceRow->RemoveFromLayout(); + SwFrame::DestroyFrame(pTmpSourceRow); + } + else + { + // move complete row: + pTmpSourceRow->RemoveFromLayout(); + pTmpSourceRow->InsertBefore( pCurrDestCell, nullptr ); + } + + pTmpSourceRow = static_cast<SwRowFrame*>(pCurrSourceCell->Lower()); + } + } + else + { + SwFrame *pTmp = ::SaveContent( pCurrSourceCell ); + if ( pTmp ) + { + // NEW TABLES + SwCellFrame* pDestCell = pCurrDestCell; + if ( pDestCell->GetTabBox()->getRowSpan() < 1 ) + pDestCell = & const_cast<SwCellFrame&>(pDestCell->FindStartEndOfRowSpanCell( true )); + + // Find last content + SwFrame* pFrame = pDestCell->GetLastLower(); + ::RestoreContent( pTmp, pDestCell, pFrame ); + } + } + pCurrDestCell = static_cast<SwCellFrame*>(pCurrDestCell->GetNext()); + pCurrSourceCell = static_cast<SwCellFrame*>(pCurrSourceCell->GetNext()); + } +} + +// Local helper function to move all footnotes in rRowFrame from +// the footnote boss of rSource to the footnote boss of rDest. +static void lcl_MoveFootnotes( SwTabFrame& rSource, SwTabFrame& rDest, SwLayoutFrame& rRowFrame ) +{ + if ( !rSource.GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + { + SwFootnoteBossFrame* pOldBoss = rSource.FindFootnoteBossFrame( true ); + SwFootnoteBossFrame* pNewBoss = rDest.FindFootnoteBossFrame( true ); + rRowFrame.MoveLowerFootnotes( nullptr, pOldBoss, pNewBoss, true ); + } +} + +// Local helper function to handle nested table cells before the split process +static void lcl_PreprocessRowsInCells( SwTabFrame& rTab, SwRowFrame& rLastLine, + SwRowFrame& rFollowFlowLine, SwTwips nRemain ) +{ + SwCellFrame* pCurrLastLineCell = static_cast<SwCellFrame*>(rLastLine.Lower()); + SwCellFrame* pCurrFollowFlowLineCell = static_cast<SwCellFrame*>(rFollowFlowLine.Lower()); + + SwRectFnSet aRectFnSet(pCurrLastLineCell); + + // Move content of follow cells into master cells + while ( pCurrLastLineCell ) + { + if ( pCurrLastLineCell->Lower() && pCurrLastLineCell->Lower()->IsRowFrame() ) + { + SwTwips nTmpCut = nRemain; + SwRowFrame* pTmpLastLineRow = static_cast<SwRowFrame*>(pCurrLastLineCell->Lower()); + + // #i26945# + SwTwips nCurrentHeight = + lcl_CalcMinRowHeight( pTmpLastLineRow, + rTab.IsConsiderObjsForMinCellHeight() ); + while ( pTmpLastLineRow->GetNext() && nTmpCut > nCurrentHeight ) + { + nTmpCut -= nCurrentHeight; + pTmpLastLineRow = static_cast<SwRowFrame*>(pTmpLastLineRow->GetNext()); + // #i26945# + nCurrentHeight = + lcl_CalcMinRowHeight( pTmpLastLineRow, + rTab.IsConsiderObjsForMinCellHeight() ); + } + + // pTmpLastLineRow does not fit to the line or it is the last line + // Check if we can move pTmpLastLineRow to the follow table, + // or if we have to split the line: + bool bTableLayoutTooComplex = false; + long nMinHeight = 0; + + // We have to take into account: + // 1. The fixed height of the row + // 2. The borders of the cells inside the row + // 3. The minimum height of the row + if ( pTmpLastLineRow->HasFixSize() ) + nMinHeight = aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()); + else + { + { + const SwFormatFrameSize &rSz = pTmpLastLineRow->GetFormat()->GetFrameSize(); + if ( rSz.GetHeightSizeType() == SwFrameSize::Minimum ) + nMinHeight = rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*pTmpLastLineRow); + } + + SwFrame* pCell = pTmpLastLineRow->Lower(); + while ( pCell ) + { + if ( static_cast<SwCellFrame*>(pCell)->Lower() && + static_cast<SwCellFrame*>(pCell)->Lower()->IsRowFrame() ) + { + bTableLayoutTooComplex = true; + break; + } + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nMinHeight = std::max( nMinHeight, lcl_CalcTopAndBottomMargin( *static_cast<SwLayoutFrame*>(pCell), rAttrs ) ); + pCell = pCell->GetNext(); + } + } + + // 1. Case: + // The line completely fits into the master table. + // Nevertheless, we build a follow (otherwise painting problems + // with empty cell). + + // 2. Case: + // The line has to be split, the minimum height still fits into + // the master table, and the table structure is not too complex. + if ( nTmpCut > nCurrentHeight || + ( pTmpLastLineRow->IsRowSplitAllowed() && + !bTableLayoutTooComplex && nMinHeight < nTmpCut ) ) + { + // The line has to be split: + SwRowFrame* pNewRow = new SwRowFrame( *pTmpLastLineRow->GetTabLine(), &rTab, false ); + pNewRow->SetFollowFlowRow( true ); + pNewRow->SetFollowRow( pTmpLastLineRow->GetFollowRow() ); + pTmpLastLineRow->SetFollowRow( pNewRow ); + pNewRow->InsertBehind( pCurrFollowFlowLineCell, nullptr ); + pTmpLastLineRow = static_cast<SwRowFrame*>(pTmpLastLineRow->GetNext()); + } + + // The following lines have to be moved: + while ( pTmpLastLineRow ) + { + SwRowFrame* pTmp = static_cast<SwRowFrame*>(pTmpLastLineRow->GetNext()); + lcl_MoveFootnotes( rTab, *rTab.GetFollow(), *pTmpLastLineRow ); + pTmpLastLineRow->RemoveFromLayout(); + pTmpLastLineRow->InsertBefore( pCurrFollowFlowLineCell, nullptr ); + pTmpLastLineRow->Shrink( aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()) ); + pCurrFollowFlowLineCell->Grow( aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()) ); + pTmpLastLineRow = pTmp; + } + } + + pCurrLastLineCell = static_cast<SwCellFrame*>(pCurrLastLineCell->GetNext()); + pCurrFollowFlowLineCell = static_cast<SwCellFrame*>(pCurrFollowFlowLineCell->GetNext()); + } +} + +// Local helper function to handle nested table cells after the split process +static void lcl_PostprocessRowsInCells( SwTabFrame& rTab, SwRowFrame& rLastLine ) +{ + SwCellFrame* pCurrMasterCell = static_cast<SwCellFrame*>(rLastLine.Lower()); + while ( pCurrMasterCell ) + { + if ( pCurrMasterCell->Lower() && + pCurrMasterCell->Lower()->IsRowFrame() ) + { + SwRowFrame* pRowFrame = static_cast<SwRowFrame*>(pCurrMasterCell->GetLastLower()); + + if ( nullptr != pRowFrame->GetPrev() && !pRowFrame->ContainsContent() ) + { + OSL_ENSURE( pRowFrame->GetFollowRow(), "Deleting row frame without follow" ); + + // The footnotes have to be moved: + lcl_MoveFootnotes( rTab, *rTab.GetFollow(), *pRowFrame ); + pRowFrame->Cut(); + SwRowFrame* pFollowRow = pRowFrame->GetFollowRow(); + pRowFrame->Paste( pFollowRow->GetUpper(), pFollowRow ); + pRowFrame->SetFollowRow( pFollowRow->GetFollowRow() ); + lcl_MoveRowContent( *pFollowRow, *pRowFrame ); + pFollowRow->Cut(); + SwFrame::DestroyFrame(pFollowRow); + ::SwInvalidateAll( pCurrMasterCell, LONG_MAX ); + } + } + + pCurrMasterCell = static_cast<SwCellFrame*>(pCurrMasterCell->GetNext()); + } +} + +// Local helper function to re-calculate the split line. +inline void TableSplitRecalcLock( SwFlowFrame *pTab ) { pTab->LockJoin(); } +inline void TableSplitRecalcUnlock( SwFlowFrame *pTab ) { pTab->UnlockJoin(); } + +static bool lcl_RecalcSplitLine( SwRowFrame& rLastLine, SwRowFrame& rFollowLine, + SwTwips nRemainingSpaceForLastRow, SwTwips nAlreadyFree ) +{ + bool bRet = true; + + vcl::RenderContext* pRenderContext = rLastLine.getRootFrame()->GetCurrShell()->GetOut(); + SwTabFrame& rTab = static_cast<SwTabFrame&>(*rLastLine.GetUpper()); + SwRectFnSet aRectFnSet(rTab.GetUpper()); + SwTwips nCurLastLineHeight = aRectFnSet.GetHeight(rLastLine.getFrameArea()); + + // If there are nested cells in rLastLine, the recalculation of the last + // line needs some preprocessing. + lcl_PreprocessRowsInCells( rTab, rLastLine, rFollowLine, nRemainingSpaceForLastRow ); + + // Here the recalculation process starts: + rTab.SetRebuildLastLine( true ); + // #i26945# + rTab.SetDoesObjsFit( true ); + + // #i26945# - invalidate and move floating screen + // objects 'out of range' + ::lcl_InvalidateLowerObjs( rLastLine, true ); + + // manipulate row and cell sizes + + // #i26945# - Do *not* consider floating screen objects + // for the minimal cell height. + rTab.SetConsiderObjsForMinCellHeight( false ); + ::lcl_ShrinkCellsAndAllContent( rLastLine ); + rTab.SetConsiderObjsForMinCellHeight( true ); + + // invalidate last line + ::SwInvalidateAll( &rLastLine, LONG_MAX ); + + // Shrink the table to account for the shrunk last row, as well as lower rows + // that had been moved to follow table in SwTabFrame::Split. + // It will grow later when last line will recalc its height. + rTab.Shrink(nAlreadyFree + nCurLastLineHeight - nRemainingSpaceForLastRow + 1); + + // Lock this tab frame and its follow + bool bUnlockMaster = false; + SwFlowFrame * pFollow = nullptr; + SwTabFrame* pMaster = rTab.IsFollow() ? rTab.FindMaster() : nullptr; + if ( pMaster && !pMaster->IsJoinLocked() ) + { + bUnlockMaster = true; + ::TableSplitRecalcLock( pMaster ); + } + if ( !rTab.GetFollow()->IsJoinLocked() ) + { + pFollow = rTab.GetFollow(); + ::TableSplitRecalcLock( pFollow ); + } + + bool bInSplit = rLastLine.IsInSplit(); + rLastLine.SetInSplit(); + + // Do the recalculation + lcl_RecalcRow( rLastLine, LONG_MAX ); + // #115759# - force a format of the last line in order to + // get the correct height. + rLastLine.InvalidateSize(); + rLastLine.Calc(pRenderContext); + + rLastLine.SetInSplit(bInSplit); + + // Unlock this tab frame and its follow + if ( pFollow ) + ::TableSplitRecalcUnlock( pFollow ); + if ( bUnlockMaster ) + ::TableSplitRecalcUnlock( pMaster ); + + // If there are nested cells in rLastLine, the recalculation of the last + // line needs some postprocessing. + lcl_PostprocessRowsInCells( rTab, rLastLine ); + + // Do a couple of checks on the current situation. + + // If we are not happy with the current situation we return false. + // This will start a new try to split the table, this time we do not + // try to split the table rows. + + // 1. Check if table fits to its upper. + // #i26945# - include check, if objects fit + const SwTwips nDistanceToUpperPrtBottom = + aRectFnSet.BottomDist(rTab.getFrameArea(), aRectFnSet.GetPrtBottom(*rTab.GetUpper())); + // tdf#125685 ignore footnotes that are anchored in follow-table of this + // table - if split is successful they move to the next page/column anyway + assert(rTab.GetFollow() == rFollowLine.GetUpper()); + SwTwips nFollowFootnotes(0); + // actually there should always be a boss frame, except if "this" isn't + // connected to a page yet; not sure if that can happen + if (SwFootnoteBossFrame const*const pBoss = rTab.FindFootnoteBossFrame()) + { + if (SwFootnoteContFrame const*const pCont = pBoss->FindFootnoteCont()) + { + for (SwFootnoteFrame const* pFootnote = static_cast<SwFootnoteFrame const*>(pCont->Lower()); + pFootnote != nullptr; + pFootnote = static_cast<SwFootnoteFrame const*>(pFootnote->GetNext())) + { + SwContentFrame const*const pAnchor = pFootnote->GetRef(); + SwTabFrame const* pTab = pAnchor->FindTabFrame(); + if (pTab) + { + while (pTab->GetUpper()->IsInTab()) + { + pTab = pTab->GetUpper()->FindTabFrame(); + } + // TODO currently do this only for top-level tables? + // otherwise would need to check rTab's follow and any upper table's follow? + if (pTab == rTab.GetFollow()) + { + nFollowFootnotes += aRectFnSet.GetHeight(pFootnote->getFrameArea()); + } + } + } + } + } + if (nDistanceToUpperPrtBottom + nFollowFootnotes < 0 || !rTab.DoesObjsFit()) + bRet = false; + + // 2. Check if each cell in the last line has at least one content frame. + + // Note: a FollowFlowRow may contains empty cells! + if ( bRet ) + { + if ( !rLastLine.IsInFollowFlowRow() ) + { + SwCellFrame* pCurrMasterCell = static_cast<SwCellFrame*>(rLastLine.Lower()); + while ( pCurrMasterCell ) + { + if ( !pCurrMasterCell->ContainsContent() && pCurrMasterCell->GetTabBox()->getRowSpan() >= 1 ) + { + bRet = false; + break; + } + pCurrMasterCell = static_cast<SwCellFrame*>(pCurrMasterCell->GetNext()); + } + } + } + + // 3. Check if last line does not contain any content: + if ( bRet ) + { + if ( !rLastLine.ContainsContent() ) + { + bRet = false; + } + } + + // 4. Check if follow flow line does not contain content: + if ( bRet ) + { + if ( !rFollowLine.IsRowSpanLine() && !rFollowLine.ContainsContent() ) + { + bRet = false; + } + } + + if ( bRet ) + { + // Everything looks fine. Splitting seems to be successful. We invalidate + // rFollowLine to force a new formatting. + ::SwInvalidateAll( &rFollowLine, LONG_MAX ); + } + else + { + // Splitting the table row gave us an unexpected result. + // Everything has to be prepared for a second try to split + // the table, this time without splitting the row. + ::SwInvalidateAll( &rLastLine, LONG_MAX ); + } + + rTab.SetRebuildLastLine( false ); + // #i26945# + rTab.SetDoesObjsFit( true ); + + return bRet; +} + +// Sets the correct height for all spanned cells +static void lcl_AdjustRowSpanCells( SwRowFrame* pRow ) +{ + SwRectFnSet aRectFnSet(pRow); + SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pRow->GetLower()); + while ( pCellFrame ) + { + const long nLayoutRowSpan = pCellFrame->GetLayoutRowSpan(); + if ( nLayoutRowSpan > 1 ) + { + // calculate height of cell: + const long nNewCellHeight = lcl_GetHeightOfRows( pRow, nLayoutRowSpan ); + const long nDiff = nNewCellHeight - aRectFnSet.GetHeight(pCellFrame->getFrameArea()); + + if ( nDiff ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCellFrame); + aRectFnSet.AddBottom(aFrm, nDiff); + } + } + + pCellFrame = static_cast<SwCellFrame*>(pCellFrame->GetNext()); + } +} + +// Returns the maximum layout row span of the row +// Looking for the next row that contains no covered cells: +static long lcl_GetMaximumLayoutRowSpan( const SwRowFrame& rRow ) +{ + long nRet = 1; + + const SwRowFrame* pCurrentRowFrame = static_cast<const SwRowFrame*>(rRow.GetNext()); + bool bNextRow = false; + + while ( pCurrentRowFrame ) + { + // if there is any covered cell, we proceed to the next row frame + const SwCellFrame* pLower = static_cast<const SwCellFrame*>( pCurrentRowFrame->Lower()); + while ( pLower ) + { + if ( pLower->GetTabBox()->getRowSpan() < 0 ) + { + ++nRet; + bNextRow = true; + break; + } + pLower = static_cast<const SwCellFrame*>(pLower->GetNext()); + } + pCurrentRowFrame = bNextRow ? + static_cast<const SwRowFrame*>(pCurrentRowFrame->GetNext() ) : + nullptr; + } + + return nRet; +} + +// Function to remove the FollowFlowLine of rTab. +// The content of the FollowFlowLine is moved to the associated line in the +// master table. +bool SwTabFrame::RemoveFollowFlowLine() +{ + // find FollowFlowLine + SwTabFrame *pFoll = GetFollow(); + SwRowFrame* pFollowFlowLine = pFoll ? pFoll->GetFirstNonHeadlineRow() : nullptr; + + // find last row in master + SwFrame* pLastLine = GetLastLower(); + + OSL_ENSURE( HasFollowFlowLine() && + pFollowFlowLine && + pLastLine, "There should be a flowline in the follow" ); + + // #140081# Make code robust. + if ( !pFollowFlowLine || !pLastLine ) + return true; + if (pFollowFlowLine->IsDeleteForbidden()) + { + SAL_WARN("sw.layout", "Cannot remove in-use Follow Flow Line"); + return false; + } + + // We have to reset the flag here, because lcl_MoveRowContent + // calls a GrowFrame(), which has a different behavior if + // this flag is set. + SetFollowFlowLine( false ); + + // Move content + lcl_MoveRowContent( *pFollowFlowLine, *static_cast<SwRowFrame*>(pLastLine) ); + + // NEW TABLES + // If a row span follow flow line is removed, we want to move the whole span + // to the master: + long nRowsToMove = lcl_GetMaximumLayoutRowSpan( *pFollowFlowLine ); + + if ( nRowsToMove > 1 ) + { + SwRectFnSet aRectFnSet(this); + SwFrame* pRow = pFollowFlowLine->GetNext(); + SwFrame* pInsertBehind = GetLastLower(); + SwTwips nGrow = 0; + + while ( pRow && nRowsToMove-- > 1 ) + { + SwFrame* pNxt = pRow->GetNext(); + nGrow += aRectFnSet.GetHeight(pRow->getFrameArea()); + + // The footnotes have to be moved: + lcl_MoveFootnotes( *GetFollow(), *this, static_cast<SwRowFrame&>(*pRow) ); + + pRow->RemoveFromLayout(); + pRow->InsertBehind( this, pInsertBehind ); + pRow->InvalidateAll_(); + pRow->CheckDirChange(); + pInsertBehind = pRow; + pRow = pNxt; + } + + SwFrame* pFirstRow = Lower(); + while ( pFirstRow ) + { + lcl_AdjustRowSpanCells( static_cast<SwRowFrame*>(pFirstRow) ); + pFirstRow = pFirstRow->GetNext(); + } + + Grow( nGrow ); + GetFollow()->Shrink( nGrow ); + } + + bool bJoin = !pFollowFlowLine->GetNext(); + pFollowFlowLine->Cut(); + SwFrame::DestroyFrame(pFollowFlowLine); + + return bJoin; +} + +// #i26945# - Floating screen objects are no longer searched. +static bool lcl_FindSectionsInRow( const SwRowFrame& rRow ) +{ + bool bRet = false; + const SwCellFrame* pLower = static_cast<const SwCellFrame*>(rRow.Lower()); + while ( pLower ) + { + if ( pLower->IsVertical() != rRow.IsVertical() ) + return true; + + const SwFrame* pTmpFrame = pLower->Lower(); + while ( pTmpFrame ) + { + if ( pTmpFrame->IsRowFrame() ) + { + bRet = lcl_FindSectionsInRow( *static_cast<const SwRowFrame*>(pTmpFrame) ); + } + else + { + // #i26945# - search only for sections + if (pTmpFrame->IsSctFrame()) + { + bRet = true; + + if (!rRow.IsInSct()) + { + // This row is not in a section. + if (const SwFrame* pSectionLower = pTmpFrame->GetLower()) + { + if (!pSectionLower->IsColumnFrame()) + { + // Section has a single column only, try to + // split that. + bRet = false; + + for (const SwFrame* pFrame = pSectionLower; pFrame; pFrame = pFrame->GetNext()) + { + if (pFrame->IsTabFrame()) + { + // Section contains a table, no split in that case. + bRet = true; + break; + } + } + } + } + } + } + } + + if ( bRet ) + return true; + pTmpFrame = pTmpFrame->GetNext(); + } + + pLower = static_cast<const SwCellFrame*>(pLower->GetNext()); + } + return bRet; +} + +bool SwTabFrame::Split( const SwTwips nCutPos, bool bTryToSplit, bool bTableRowKeep ) +{ + bool bRet = true; + + SwRectFnSet aRectFnSet(this); + + // #i26745# - format row and cell frames of table + { + Lower()->InvalidatePos_(); + // #i43913# - correction + // call method <lcl_InnerCalcLayout> with first lower. + lcl_InnerCalcLayout( Lower(), LONG_MAX, true ); + } + + //In order to be able to compare the positions of the cells with CutPos, + //they have to be calculated consecutively starting from the table. + //They can definitely be invalid because of position changes of the table. + SwRowFrame *pRow = static_cast<SwRowFrame*>(Lower()); + if( !pRow ) + return bRet; + + const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + sal_uInt16 nRowCount = 0; // pRow currently points to the first row + + SwTwips nRemainingSpaceForLastRow = + aRectFnSet.YDiff(nCutPos, aRectFnSet.GetTop(getFrameArea())); + nRemainingSpaceForLastRow -= aRectFnSet.GetTopMargin(*this); + + // Make pRow point to the line that does not fit anymore: + while( pRow->GetNext() && + nRemainingSpaceForLastRow >= ( aRectFnSet.GetHeight(pRow->getFrameArea()) + + (IsCollapsingBorders() ? + pRow->GetBottomLineSize() : + 0 ) ) ) + { + if( bTryToSplit || !pRow->IsRowSpanLine() || + 0 != aRectFnSet.GetHeight(pRow->getFrameArea()) ) + ++nRowCount; + nRemainingSpaceForLastRow -= aRectFnSet.GetHeight(pRow->getFrameArea()); + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + } + + // bSplitRowAllowed: Row may be split according to its attributes. + // bTryToSplit: Row will never be split if bTryToSplit = false. + // This can either be passed as a parameter, indicating + // that we are currently doing the second try to split the + // table, or it will be set to false under certain + // conditions that are not suitable for splitting + // the row. + bool bSplitRowAllowed = !IsSplitRowDisabled(); + if ( bSplitRowAllowed && !pRow->IsRowSplitAllowed() ) + { + // A row larger than the entire page ought to be allowed to split regardless of setting, + // otherwise it has hidden content and that makes no sense + if ( pRow->getFrameArea().Height() > FindPageFrame()->getFramePrintArea().Height() ) + pRow->SetForceRowSplitAllowed( true ); + else + bSplitRowAllowed = false; + } + // #i29438# + // #i26945# - Floating screen objects no longer forbid + // a splitting of the table row. + // Special DoNotSplit case 1: + // Search for sections inside pRow: + if ( lcl_FindSectionsInRow( *pRow ) ) + { + bTryToSplit = false; + } + + // #i29771# + // To avoid loops, we do some checks before actually trying to split + // the row. Maybe we should keep the next row in this table. + // Note: This is only done if we are at the beginning of our upper + bool bKeepNextRow = false; + if ( nRowCount < nRepeat ) + { + // First case: One of the repeated headline does not fit to the page anymore. + // tdf#88496 Disable repeated headline (like for #i44910#) to avoid loops and + // to fix interoperability problems (very long tables only with headline) + OSL_ENSURE( !GetIndPrev(), "Table is supposed to be at beginning" ); + m_pTable->SetRowsToRepeat(0); + return false; + } + else if ( !GetIndPrev() && nRepeat == nRowCount ) + { + // Second case: The first non-headline row does not fit to the page. + // If it is not allowed to be split, or it contains a sub-row that + // is not allowed to be split, we keep the row in this table: + if ( bTryToSplit && bSplitRowAllowed ) + { + // Check if there are (first) rows inside this row, + // which are not allowed to be split. + SwCellFrame* pLowerCell = static_cast<SwCellFrame*>(pRow->Lower()); + while ( pLowerCell ) + { + if ( pLowerCell->Lower() && pLowerCell->Lower()->IsRowFrame() ) + { + const SwRowFrame* pLowerRow = static_cast<SwRowFrame*>(pLowerCell->Lower()); + if ( !pLowerRow->IsRowSplitAllowed() && + aRectFnSet.GetHeight(pLowerRow->getFrameArea()) > nRemainingSpaceForLastRow ) + { + bKeepNextRow = true; + break; + } + } + pLowerCell = static_cast<SwCellFrame*>(pLowerCell->GetNext()); + } + } + else + bKeepNextRow = true; + } + + // Better keep the next row in this table: + if ( bKeepNextRow ) + { + pRow = GetFirstNonHeadlineRow(); + if ( pRow && pRow->IsRowSpanLine() && 0 == aRectFnSet.GetHeight(pRow->getFrameArea()) ) + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + if ( pRow ) + { + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + ++nRowCount; + } + } + + // No more row to split or to move to follow table: + if ( !pRow ) + return bRet; + + // We try to split the row if + // - the attributes of the row are set accordingly and + // - we are allowed to do so + // - it should not be kept with the next row + bSplitRowAllowed = bSplitRowAllowed && bTryToSplit && + ( !bTableRowKeep || + !pRow->ShouldRowKeepWithNext() ); + + // Adjust pRow according to the keep-with-next attribute: + if ( !bSplitRowAllowed && bTableRowKeep ) + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(pRow->GetPrev()); + SwRowFrame* pOldRow = pRow; + while ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() && + nRowCount > nRepeat ) + { + pRow = pTmpRow; + --nRowCount; + pTmpRow = static_cast<SwRowFrame*>(pTmpRow->GetPrev()); + } + + // loop prevention + if ( nRowCount == nRepeat && !GetIndPrev()) + { + pRow = pOldRow; + } + } + + // If we do not intend to split pRow, we check if we are + // allowed to move pRow to a follow. Otherwise we return + // false, indicating an error + if ( !bSplitRowAllowed ) + { + SwRowFrame* pFirstNonHeadlineRow = GetFirstNonHeadlineRow(); + if ( pRow == pFirstNonHeadlineRow ) + return false; + + // #i91764# + // Ignore row span lines + SwRowFrame* pTmpRow = pFirstNonHeadlineRow; + while ( pTmpRow && pTmpRow->IsRowSpanLine() ) + { + pTmpRow = static_cast<SwRowFrame*>(pTmpRow->GetNext()); + } + if ( !pTmpRow || pRow == pTmpRow ) + { + return false; + } + } + + // Build follow table if not already done: + bool bNewFollow; + SwTabFrame *pFoll; + if ( GetFollow() ) + { + pFoll = GetFollow(); + bNewFollow = false; + } + else + { + bNewFollow = true; + pFoll = new SwTabFrame( *this ); + + // We give the follow table an initial width. + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFoll); + aRectFnSet.AddWidth(aFrm, aRectFnSet.GetWidth(getFrameArea())); + aRectFnSet.SetLeft(aFrm, aRectFnSet.GetLeft(getFrameArea())); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFoll); + aRectFnSet.AddWidth(aPrt, aRectFnSet.GetWidth(getFramePrintArea())); + } + + // Insert the new follow table + pFoll->InsertBehind( GetUpper(), this ); + + // Repeat the headlines. + for ( nRowCount = 0; nRowCount < nRepeat; ++nRowCount ) + { + // Insert new headlines: + bDontCreateObjects = true; //frmtool + SwRowFrame* pHeadline = new SwRowFrame( + *GetTable()->GetTabLines()[ nRowCount ], this ); + pHeadline->SetRepeatedHeadline( true ); + bDontCreateObjects = false; + pHeadline->InsertBefore( pFoll, nullptr ); + + SwPageFrame *pPage = pHeadline->FindPageFrame(); + const SwFrameFormats *pTable = GetFormat()->GetDoc()->GetSpzFrameFormats(); + if( !pTable->empty() ) + { + sal_uLong nIndex; + SwContentFrame* pFrame = pHeadline->ContainsContent(); + while( pFrame ) + { + // sw_redlinehide: the implementation of AppendObjs + // takes care of iterating merged SwTextFrame + nIndex = pFrame->IsTextFrame() + ? static_cast<SwTextFrame*>(pFrame)->GetTextNodeFirst()->GetIndex() + : static_cast<SwNoTextFrame*>(pFrame)->GetNode()->GetIndex(); + AppendObjs( pTable, nIndex, pFrame, pPage, GetFormat()->GetDoc()); + pFrame = pFrame->GetNextContentFrame(); + if( !pHeadline->IsAnLower( pFrame ) ) + break; + } + } + } + } + + SwRowFrame* pLastRow = nullptr; // points to the last remaining line in master + SwRowFrame* pFollowRow = nullptr; // points to either the follow flow line or the + // first regular line in the follow + + if ( bSplitRowAllowed ) + { + // If the row that does not fit anymore is allowed + // to be split, the next row has to be moved to the follow table. + pLastRow = pRow; + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + + // new follow flow line for last row of master table + pFollowRow = lcl_InsertNewFollowFlowLine( *this, *pLastRow, false ); + } + else + { + pFollowRow = pRow; + + // NEW TABLES + // check if we will break a row span by moving pFollowRow to the follow: + // In this case we want to reformat the last line. + const SwCellFrame* pCellFrame = static_cast<const SwCellFrame*>(pFollowRow->GetLower()); + while ( pCellFrame ) + { + if ( pCellFrame->GetTabBox()->getRowSpan() < 1 ) + { + pLastRow = static_cast<SwRowFrame*>(pRow->GetPrev()); + break; + } + + pCellFrame = static_cast<const SwCellFrame*>(pCellFrame->GetNext()); + } + + // new follow flow line for last row of master table + if ( pLastRow ) + pFollowRow = lcl_InsertNewFollowFlowLine( *this, *pLastRow, true ); + } + + SwTwips nShrink = 0; + + //Optimization: There is no paste needed for the new Follow and the + //optimized insert can be used (large numbers of rows luckily only occur in + //such situations). + if ( bNewFollow ) + { + SwFrame* pInsertBehind = pFoll->GetLastLower(); + + while ( pRow ) + { + SwFrame* pNxt = pRow->GetNext(); + nShrink += aRectFnSet.GetHeight(pRow->getFrameArea()); + // The footnotes do not have to be moved, this is done in the + // MoveFwd of the follow table!!! + pRow->RemoveFromLayout(); + pRow->InsertBehind( pFoll, pInsertBehind ); + pRow->InvalidateAll_(); + pInsertBehind = pRow; + pRow = static_cast<SwRowFrame*>(pNxt); + } + } + else + { + SwFrame* pPasteBefore = HasFollowFlowLine() ? + pFollowRow->GetNext() : + pFoll->GetFirstNonHeadlineRow(); + + while ( pRow ) + { + SwFrame* pNxt = pRow->GetNext(); + nShrink += aRectFnSet.GetHeight(pRow->getFrameArea()); + + // The footnotes have to be moved: + lcl_MoveFootnotes( *this, *GetFollow(), *pRow ); + + pRow->RemoveFromLayout(); + pRow->Paste( pFoll, pPasteBefore ); + + pRow->CheckDirChange(); + pRow = static_cast<SwRowFrame*>(pNxt); + } + } + + if ( !pLastRow ) + Shrink( nShrink ); + else + { + // we rebuild the last line to assure that it will be fully formatted + // we also don't shrink here, because we will be doing that in lcl_RecalcSplitLine + + // recalculate the split line + bRet = lcl_RecalcSplitLine( *pLastRow, *pFollowRow, nRemainingSpaceForLastRow, nShrink ); + + // RecalcSplitLine did not work. In this case we conceal the split error: + if (!bRet && !bSplitRowAllowed) + { + bRet = true; + } + + // NEW TABLES + // check if each cell in the row span line has a good height + if ( bRet && pFollowRow->IsRowSpanLine() ) + lcl_AdjustRowSpanCells( pFollowRow ); + } + + return bRet; +} + +void SwTabFrame::Join() +{ + OSL_ENSURE( !HasFollowFlowLine(), "Joining follow flow line" ); + + SwTabFrame *pFoll = GetFollow(); + + if (pFoll && !pFoll->IsJoinLocked()) + { + SwRectFnSet aRectFnSet(this); + pFoll->Cut(); //Cut out first to avoid unnecessary notifications. + + SwFrame *pRow = pFoll->GetFirstNonHeadlineRow(), + *pNxt; + + SwFrame* pPrv = GetLastLower(); + + SwTwips nHeight = 0; //Total height of the inserted rows as return value. + + while ( pRow ) + { + pNxt = pRow->GetNext(); + nHeight += aRectFnSet.GetHeight(pRow->getFrameArea()); + pRow->RemoveFromLayout(); + pRow->InvalidateAll_(); + pRow->InsertBehind( this, pPrv ); + pRow->CheckDirChange(); + pPrv = pRow; + pRow = pNxt; + } + + SetFollow( pFoll->GetFollow() ); + SetFollowFlowLine( pFoll->HasFollowFlowLine() ); + SwFrame::DestroyFrame(pFoll); + + Grow( nHeight ); + } +} + +static void SwInvalidatePositions( SwFrame *pFrame, long nBottom ) +{ + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + SwRectFnSet aRectFnSet(pFrame); + do + { pFrame->InvalidatePos_(); + pFrame->InvalidateSize_(); + if( pFrame->IsLayoutFrame() ) + { + if ( static_cast<SwLayoutFrame*>(pFrame)->Lower() ) + { + ::SwInvalidatePositions( static_cast<SwLayoutFrame*>(pFrame)->Lower(), nBottom); + // #i26945# + ::lcl_InvalidateLowerObjs( *static_cast<SwLayoutFrame*>(pFrame) ); + } + } + else + pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pFrame = pFrame->GetNext(); + } while ( pFrame && + ( bAll || + aRectFnSet.YDiff( aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom ) < 0 ) ); +} + +void SwInvalidateAll( SwFrame *pFrame, long nBottom ) +{ + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + SwRectFnSet aRectFnSet(pFrame); + do + { + pFrame->InvalidatePos_(); + pFrame->InvalidateSize_(); + pFrame->InvalidatePrt_(); + if( pFrame->IsLayoutFrame() ) + { + // NEW TABLES + SwLayoutFrame* pToInvalidate = static_cast<SwLayoutFrame*>(pFrame); + SwCellFrame* pThisCell = dynamic_cast<SwCellFrame*>(pFrame); + if ( pThisCell && pThisCell->GetTabBox()->getRowSpan() < 1 ) + { + pToInvalidate = & const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( true )); + pToInvalidate->InvalidatePos_(); + pToInvalidate->InvalidateSize_(); + pToInvalidate->InvalidatePrt_(); + } + + if ( pToInvalidate->Lower() ) + ::SwInvalidateAll( pToInvalidate->Lower(), nBottom); + } + else + pFrame->Prepare(); + + pFrame = pFrame->GetNext(); + } while ( pFrame && + ( bAll || + aRectFnSet.YDiff( aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom ) < 0 ) ); +} + +// #i29550# +static void lcl_InvalidateAllLowersPrt( SwLayoutFrame* pLayFrame ) +{ + pLayFrame->InvalidatePrt_(); + pLayFrame->InvalidateSize_(); + pLayFrame->SetCompletePaint(); + + SwFrame* pFrame = pLayFrame->Lower(); + + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + lcl_InvalidateAllLowersPrt( static_cast<SwLayoutFrame*>(pFrame) ); + else + { + pFrame->InvalidatePrt_(); + pFrame->InvalidateSize_(); + pFrame->SetCompletePaint(); + } + + pFrame = pFrame->GetNext(); + } +} + +bool SwContentFrame::CalcLowers(SwLayoutFrame & rLay, SwLayoutFrame const& rDontLeave, + long nBottom, bool bSkipRowSpanCells ) +{ + vcl::RenderContext* pRenderContext = rLay.getRootFrame()->GetCurrShell()->GetOut(); + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + bool bRet = false; + SwContentFrame *pCnt = rLay.ContainsContent(); + SwRectFnSet aRectFnSet(&rLay); + + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 10; + const SwModify* pLoopControlCond = nullptr; + + while (pCnt && rDontLeave.IsAnLower(pCnt)) + { + // #115759# - check, if a format of content frame is + // possible. Thus, 'copy' conditions, found at the beginning of + // <SwContentFrame::MakeAll(..)>, and check these. + const bool bFormatPossible = !pCnt->IsJoinLocked() && + ( !pCnt->IsTextFrame() || + !static_cast<SwTextFrame*>(pCnt)->IsLocked() ) && + ( pCnt->IsFollow() || !StackHack::IsLocked() ); + + // NEW TABLES + bool bSkipContent = false; + if ( bSkipRowSpanCells && pCnt->IsInTab() ) + { + const SwFrame* pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + if ( pCell && 1 != static_cast<const SwCellFrame*>( pCell )->GetLayoutRowSpan() ) + bSkipContent = true; + } + + if ( bFormatPossible && !bSkipContent ) + { + bRet |= !pCnt->isFrameAreaDefinitionValid(); + // #i26945# - no extra invalidation of floating + // screen objects needed. + // Thus, delete call of method <SwFrame::InvalidateObjs( true )> + pCnt->Calc(pRenderContext); + // #i46941# - frame has to be valid + // Note: frame could be invalid after calling its format, if it's locked. + OSL_ENSURE( !pCnt->IsTextFrame() || + pCnt->isFrameAreaDefinitionValid() || + static_cast<SwTextFrame*>(pCnt)->IsJoinLocked(), + "<SwContentFrame::CalcLowers(..)> - text frame invalid and not locked." ); + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + // #i23129#, #i36347# - pass correct page frame to + // the object formatter + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + SwTextNode const*const pTextNode( + static_cast<SwTextFrame*>(pCnt)->GetTextNodeFirst()); + if (pTextNode == pLoopControlCond) + ++nLoopControlRuns; + else + { + nLoopControlRuns = 0; + pLoopControlCond = pTextNode; + } + + if ( nLoopControlRuns < nLoopControlMax ) + { + // restart format with first content + pCnt = rLay.ContainsContent(); + continue; + } + +#if OSL_DEBUG_LEVEL > 1 + OSL_FAIL( "LoopControl in SwContentFrame::CalcLowers" ); +#endif + } + } + if (!rDontLeave.IsAnLower(pCnt)) // moved backward? + { + pCnt = rLay.ContainsContent(); + continue; // avoid formatting new upper on different page + } + pCnt->GetUpper()->Calc(pRenderContext); + } + if( ! bAll && aRectFnSet.YDiff(aRectFnSet.GetTop(pCnt->getFrameArea()), nBottom) > 0 ) + break; + pCnt = pCnt->GetNextContentFrame(); + } + return bRet; +} + +// #i26945# - add parameter <_bOnlyRowsAndCells> to control +// that only row and cell frames are formatted. +static bool lcl_InnerCalcLayout( SwFrame *pFrame, + long nBottom, + bool _bOnlyRowsAndCells ) +{ + vcl::RenderContext* pRenderContext = pFrame->getRootFrame()->GetCurrShell() ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr; + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + bool bRet = false; + const SwFrame* pOldUp = pFrame->GetUpper(); + SwRectFnSet aRectFnSet(pFrame); + do + { + // #i26945# - parameter <_bOnlyRowsAndCells> controls, + // if only row and cell frames are formatted. + if ( pFrame->IsLayoutFrame() && + ( !_bOnlyRowsAndCells || pFrame->IsRowFrame() || pFrame->IsCellFrame() ) ) + { + // #130744# An invalid locked table frame will + // not be calculated => It will not become valid => + // Loop in lcl_RecalcRow(). Therefore we do not consider them for bRet. + bRet |= !pFrame->isFrameAreaDefinitionValid() && ( !pFrame->IsTabFrame() || !static_cast<SwTabFrame*>(pFrame)->IsJoinLocked() ); + pFrame->Calc(pRenderContext); + if( static_cast<SwLayoutFrame*>(pFrame)->Lower() ) + bRet |= lcl_InnerCalcLayout( static_cast<SwLayoutFrame*>(pFrame)->Lower(), nBottom); + + // NEW TABLES + SwCellFrame* pThisCell = dynamic_cast<SwCellFrame*>(pFrame); + if ( pThisCell && pThisCell->GetTabBox()->getRowSpan() < 1 ) + { + SwCellFrame& rToCalc = const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( true )); + bRet |= !rToCalc.isFrameAreaDefinitionValid(); + rToCalc.Calc(pRenderContext); + if ( rToCalc.Lower() ) + bRet |= lcl_InnerCalcLayout( rToCalc.Lower(), nBottom); + } + } + pFrame = pFrame->GetNext(); + } while( pFrame && + ( bAll || + aRectFnSet.YDiff(aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom) < 0 ) + && pFrame->GetUpper() == pOldUp ); + return bRet; +} + +static void lcl_RecalcRow(SwRowFrame & rRow, long const nBottom) +{ + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns_1 = 0; + sal_uInt16 nLoopControlStage_1 = 0; + const int nLoopControlMax = 10; + + bool bCheck = true; + do + { + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns_2 = 0; + sal_uInt16 nLoopControlStage_2 = 0; + + while (lcl_InnerCalcLayout(&rRow, nBottom)) + { + if ( ++nLoopControlRuns_2 > nLoopControlMax ) + { + SAL_WARN_IF(nLoopControlStage_2 == 0, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 1!"); + SAL_WARN_IF(nLoopControlStage_2 == 1, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 2!!"); + SAL_WARN_IF(nLoopControlStage_2 >= 2, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 3!!!"); + rRow.ValidateThisAndAllLowers( nLoopControlStage_2++ ); + nLoopControlRuns_2 = 0; + if( nLoopControlStage_2 > 2 ) + break; + } + + bCheck = true; + } + + if( bCheck ) + { + // #115759# - force another format of the + // lowers, if at least one of it was invalid. + bCheck = SwContentFrame::CalcLowers(rRow, *rRow.GetUpper(), nBottom, true); + + // NEW TABLES + // First we calculate the cells with row span of < 1, afterwards + // all cells with row span of > 1: + for ( int i = 0; i < 2; ++i ) + { + SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(rRow.Lower()); + while ( pCellFrame ) + { + const bool bCalc = 0 == i ? + pCellFrame->GetLayoutRowSpan() < 1 : + pCellFrame->GetLayoutRowSpan() > 1; + + if ( bCalc ) + { + SwCellFrame& rToRecalc = 0 == i ? + const_cast<SwCellFrame&>(pCellFrame->FindStartEndOfRowSpanCell( true )) : + *pCellFrame; + bCheck |= SwContentFrame::CalcLowers(rToRecalc, rToRecalc, nBottom, false); + } + + pCellFrame = static_cast<SwCellFrame*>(pCellFrame->GetNext()); + } + } + + if ( bCheck ) + { + if ( ++nLoopControlRuns_1 > nLoopControlMax ) + { + SAL_WARN_IF(nLoopControlStage_1 == 0, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 1!"); + SAL_WARN_IF(nLoopControlStage_1 == 1, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 2!!"); + SAL_WARN_IF(nLoopControlStage_1 >= 2, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 3!!!"); + rRow.ValidateThisAndAllLowers( nLoopControlStage_1++ ); + nLoopControlRuns_1 = 0; + if( nLoopControlStage_1 > 2 ) + break; + } + + continue; + } + } + break; + } while( true ); +} + +static void lcl_RecalcTable( SwTabFrame& rTab, + SwLayoutFrame *pFirstRow, + SwLayNotify &rNotify ) +{ + if ( rTab.Lower() ) + { + if ( !pFirstRow ) + { + pFirstRow = static_cast<SwLayoutFrame*>(rTab.Lower()); + rNotify.SetLowersComplete( true ); + } + ::SwInvalidatePositions( pFirstRow, LONG_MAX ); + lcl_RecalcRow( *static_cast<SwRowFrame*>(pFirstRow), LONG_MAX ); + } +} + +// This is a new function to check the first condition whether +// a tab frame may move backward. It replaces the formerly used +// GetIndPrev(), which did not work correctly for #i5947# +static bool lcl_NoPrev( const SwFrame& rFrame ) +{ + // #i79774# + // skip empty sections on investigation of direct previous frame. + // use information, that at least one empty section is skipped in the following code. + bool bSkippedDirectPrevEmptySection( false ); + if ( rFrame.GetPrev() ) + { + const SwFrame* pPrev( rFrame.GetPrev() ); + while ( pPrev && + pPrev->IsSctFrame() && + !dynamic_cast<const SwSectionFrame&>(*pPrev).GetSection() ) + { + pPrev = pPrev->GetPrev(); + bSkippedDirectPrevEmptySection = true; + } + if ( pPrev ) + { + return false; + } + } + + if ( ( !bSkippedDirectPrevEmptySection && !rFrame.GetIndPrev() ) || + ( bSkippedDirectPrevEmptySection && + ( !rFrame.IsInSct() || !rFrame.GetIndPrev_() ) ) ) + { + return true; + } + + // I do not have a direct prev, but I have an indirect prev. + // In section frames I have to check if I'm located inside + // the first column: + if ( rFrame.IsInSct() ) + { + const SwFrame* pSct = rFrame.GetUpper(); + if ( pSct && pSct->IsColBodyFrame() && + pSct->GetUpper()->GetUpper()->IsSctFrame() ) + { + const SwFrame* pPrevCol = rFrame.GetUpper()->GetUpper()->GetPrev(); + if ( pPrevCol ) + // I'm not inside the first column and do not have a direct + // prev. I can try to go backward. + return true; + } + } + + return false; +} + +#define KEEPTAB ( !GetFollow() && !IsFollow() ) + +// - helper method to find next content frame of +// a table frame and format it to assure keep attribute. +// method return true, if a next content frame is formatted. +// Precondition: The given table frame hasn't a follow and isn't a follow. +SwFrame* sw_FormatNextContentForKeep( SwTabFrame* pTabFrame ) +{ + vcl::RenderContext* pRenderContext = pTabFrame->getRootFrame()->GetCurrShell()->GetOut(); + // find next content, table or section + SwFrame* pNxt = pTabFrame->FindNext(); + + // skip empty sections + while ( pNxt && pNxt->IsSctFrame() && + !static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = pNxt->FindNext(); + } + + // if found next frame is a section, get its first content. + if ( pNxt && pNxt->IsSctFrame() ) + { + pNxt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + } + + // format found next frame. + // if table frame is inside another table, method <SwFrame::MakeAll()> is + // called to avoid that the superior table frame is formatted. + if ( pNxt ) + { + if ( pTabFrame->GetUpper()->IsInTab() ) + pNxt->MakeAll(pNxt->getRootFrame()->GetCurrShell()->GetOut()); + else + pNxt->Calc(pRenderContext); + } + + return pNxt; +} + +namespace { + bool AreAllRowsKeepWithNext( const SwRowFrame* pFirstRowFrame, const bool bCheckParents = true ) + { + bool bRet = pFirstRowFrame != nullptr && + pFirstRowFrame->ShouldRowKeepWithNext( bCheckParents ); + + while ( bRet && pFirstRowFrame->GetNext() != nullptr ) + { + pFirstRowFrame = dynamic_cast<const SwRowFrame*>(pFirstRowFrame->GetNext()); + bRet = pFirstRowFrame != nullptr && + pFirstRowFrame->ShouldRowKeepWithNext( bCheckParents ); + } + + return bRet; + } +} + +// extern because static can't be friend +void FriendHackInvalidateRowFrame(SwFrameAreaDefinition & rRowFrame) +{ + // hilariously static_cast<SwTabFrame*>(GetLower()) would not require friend declaration, but it's UB... + rRowFrame.setFrameAreaPositionValid(false); +} + +static void InvalidateFramePositions(SwFrame * pFrame) +{ + while (pFrame) + { + if (pFrame->IsLayoutFrame()) + { + InvalidateFramePositions(pFrame->GetLower()); + } + else if (pFrame->IsTextFrame()) + { + pFrame->Prepare(PrepareHint::FramePositionChanged); + } + pFrame = pFrame->GetNext(); + } +} + +void SwTabFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( IsJoinLocked() || StackHack::IsLocked() || StackHack::Count() > 50 ) + return; + + if ( HasFollow() ) + { + SwTabFrame* pFollowFrame = GetFollow(); + OSL_ENSURE( !pFollowFrame->IsJoinLocked() || !pFollowFrame->IsRebuildLastLine(), + "SwTabFrame::MakeAll for master while follow is in RebuildLastLine()" ); + if ( pFollowFrame->IsJoinLocked() && pFollowFrame->IsRebuildLastLine() ) + return; + } + + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + LockJoin(); //I don't want to be destroyed on the way. + SwLayNotify aNotify( this ); //does the notification in the DTor + // If pos is invalid, we have to call a SetInvaKeep at aNotify. + // Otherwise the keep attribute would not work in front of a table. + const bool bOldValidPos = isFrameAreaPositionValid(); + + //If my neighbour is my Follow at the same time, I'll swallow it up. + // OD 09.04.2003 #108698# - join all follows, which are placed on the + // same page/column. + // OD 29.04.2003 #109213# - join follow, only if join for the follow + // is not locked. Otherwise, join will not be performed and this loop + // will be endless. + while ( GetNext() && GetNext() == GetFollow() && + !GetFollow()->IsJoinLocked() + ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + + // The bRemoveFollowFlowLinePending is set if the split attribute of the + // last line is set: + if ( IsRemoveFollowFlowLinePending() && HasFollowFlowLine() ) + { + if ( RemoveFollowFlowLine() ) + Join(); + SetRemoveFollowFlowLinePending( false ); + } + + if (m_bResizeHTMLTable) //Optimized interplay with grow/shrink of the content + { + m_bResizeHTMLTable = false; + SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); + if ( pLayout ) + m_bCalcLowers = pLayout->Resize( + pLayout->GetBrowseWidthByTabFrame( *this ) ); + } + + // as long as bMakePage is true, a new page can be created (exactly once) + bool bMakePage = true; + // bMovedBwd gets set to true when the frame flows backwards + bool bMovedBwd = false; + // as long as bMovedFwd is false, the Frame may flow backwards (until + // it has been moved forward once) + bool bMovedFwd = false; + // gets set to true when the Frame is split + bool bSplit = false; + const bool bFootnotesInDoc = !GetFormat()->GetDoc()->GetFootnoteIdxs().empty(); + const bool bFly = IsInFly(); + + auto pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + const SwBorderAttrs *pAttrs = pAccess->Get(); + + // All rows should keep together + const bool bDontSplit = !IsFollow() && + ( !GetFormat()->GetLayoutSplit().GetValue() ); + + // The number of repeated headlines + const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + + // This flag indicates that we are allowed to try to split the + // table rows. + bool bTryToSplit = true; + + // Indicates that two individual rows may keep together, based on the keep + // attribute set at the first paragraph in the first cell. + const bool bTableRowKeep = !bDontSplit && GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP); + + // The Magic Move: Used for the table row keep feature. + // If only the last row of the table wants to keep (implicitly by setting + // keep for the first paragraph in the first cell), and this table does + // not have a next, the last line will be cut. Loop prevention: Only + // one try. + // WHAT IS THIS??? It "magically" hides last line (paragraph) in a table, + // if first is set to keep with next??? + bool bLastRowHasToMoveToFollow = false; + bool bLastRowMoveNoMoreTries = false; + + const bool bLargeTable = GetTable()->GetTabLines().size() > 64; //arbitrary value, virtually guaranteed to be larger than one page. + const bool bEmulateTableKeep = !bLargeTable && bTableRowKeep && AreAllRowsKeepWithNext( GetFirstNonHeadlineRow(), /*bCheckParents=*/false ); + // The beloved keep attribute + const bool bKeep = IsKeep(pAttrs->GetAttrSet().GetKeep(), GetBreakItem(), bEmulateTableKeep); + + // Join follow table, if this table is not allowed to split: + if ( bDontSplit ) + { + while ( GetFollow() && !GetFollow()->IsJoinLocked() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + } + + // Join follow table, if this does not have enough (repeated) lines: + if ( nRepeat ) + { + if( GetFollow() && !GetFollow()->IsJoinLocked() && + nullptr == GetFirstNonHeadlineRow() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + } + + // Join follow table, if last row of this table should keep: + if ( bTableRowKeep && GetFollow() && !GetFollow()->IsJoinLocked() ) + { + const SwRowFrame* pTmpRow = static_cast<const SwRowFrame*>(GetLastLower()); + if ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + } + + // a new one is moved forwards immediately + if ( !getFrameArea().Top() && IsFollow() ) + { + SwFrame *pPre = GetPrev(); + if ( pPre && pPre->IsTabFrame() && static_cast<SwTabFrame*>(pPre)->GetFollow() == this) + { + // don't make the effort to move fwd if its known + // conditions that are known not to work + if (IsInFootnote() && ForbiddenForFootnoteCntFwd()) + bMakePage = false; + else if (!MoveFwd(bMakePage, false)) + bMakePage = false; + bMovedFwd = true; + } + } + + int nUnSplitted = 5; // Just another loop control :-( + int nThrowAwayValidLayoutLimit = 5; // And another one :-( + SwRectFnSet aRectFnSet(this); + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + const bool bMoveable = IsMoveable(); + if (bMoveable && + !(bMovedFwd && bEmulateTableKeep) ) + if ( CheckMoveFwd( bMakePage, bKeep && KEEPTAB, bEmulateTableKeep ) ) + { + bMovedFwd = true; + m_bCalcLowers = true; + // #i99267# + // reset <bSplit> after forward move to assure that follows + // can be joined, if further space is available. + bSplit = false; + } + + Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); + MakePos(); + + if ( aOldPos != aRectFnSet.GetPos(getFrameArea()) ) + { + if ( aOldPos.Y() != aRectFnSet.GetTop(getFrameArea()) ) + { + SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); + if( pLayout ) + { + pAccess.reset(); + m_bCalcLowers |= pLayout->Resize( + pLayout->GetBrowseWidthByTabFrame( *this ) ); + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + } + + setFramePrintAreaValid(false); + aNotify.SetLowersComplete( false ); + } + SwFrame *pPre; + if ( bKeep || (nullptr != (pPre = FindPrev()) && + pPre->GetAttrSet()->GetKeep().GetValue()) ) + { + m_bCalcLowers = true; + } + if (GetLower()) + { // it's possible that the rows already have valid pos - but it is surely wrong if the table's pos changed! + FriendHackInvalidateRowFrame(*GetLower()); + // invalidate text frames to get rid of their SwFlyPortions + InvalidateFramePositions(GetLower()); + } + } + + //We need to know the height of the first row, because the master needs + //to be invalidated if it shrinks and then absorb the row if possible. + long n1StLineHeight = 0; + if ( IsFollow() ) + { + SwFrame* pFrame = GetFirstNonHeadlineRow(); + if ( pFrame ) + n1StLineHeight = aRectFnSet.GetHeight(pFrame->getFrameArea()); + } + + if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + const long nOldPrtWidth = aRectFnSet.GetWidth(getFramePrintArea()); + const long nOldFrameWidth = aRectFnSet.GetWidth(getFrameArea()); + const Point aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); + Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); + + SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); + if ( pLayout && + (aRectFnSet.GetWidth(getFramePrintArea()) != nOldPrtWidth || + aRectFnSet.GetWidth(getFrameArea()) != nOldFrameWidth) ) + { + pAccess.reset(); + m_bCalcLowers |= pLayout->Resize( + pLayout->GetBrowseWidthByTabFrame( *this ) ); + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + } + if ( aOldPrtPos != aRectFnSet.GetPos(getFramePrintArea()) ) + aNotify.SetLowersComplete( false ); + } + + // If this is the first one in a chain, check if this can flow + // backwards (if this is movable at all). + // To prevent oscillations/loops, check that this has not just + // flowed forwards. + if ( !bMovedFwd && (bMoveable || bFly) && lcl_NoPrev( *this ) ) + { + // for Follows notify Master. + // only move Follow if it has to skip empty pages. + if ( IsFollow() ) + { + // Only if the height of the first line got smaller. + SwFrame *pFrame = GetFirstNonHeadlineRow(); + if( pFrame && n1StLineHeight >aRectFnSet.GetHeight(pFrame->getFrameArea()) ) + { + SwTabFrame *pMaster = FindMaster(); + bool bDummy; + if ( ShouldBwdMoved( pMaster->GetUpper(), bDummy ) ) + pMaster->InvalidatePos(); + } + } + SwFootnoteBossFrame *pOldBoss = bFootnotesInDoc ? FindFootnoteBossFrame( true ) : nullptr; + bool bReformat; + if ( MoveBwd( bReformat ) ) + { + aRectFnSet.Refresh(this); + bMovedBwd = true; + aNotify.SetLowersComplete( false ); + if ( bFootnotesInDoc ) + MoveLowerFootnotes( nullptr, pOldBoss, nullptr, true ); + if ( bReformat || bKeep ) + { + long nOldTop = aRectFnSet.GetTop(getFrameArea()); + MakePos(); + if( nOldTop != aRectFnSet.GetTop(getFrameArea()) ) + { + SwHTMLTableLayout *pHTMLLayout = + GetTable()->GetHTMLTableLayout(); + if( pHTMLLayout ) + { + pAccess.reset(); + m_bCalcLowers |= pHTMLLayout->Resize( + pHTMLLayout->GetBrowseWidthByTabFrame( *this ) ); + + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + } + + setFramePrintAreaValid(false); + Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); + } + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + if ( bKeep && KEEPTAB ) + { + + // Consider case that table is inside another table, + // because it has to be avoided, that superior table + // is formatted. + // Thus, find next content, table or section + // and, if a section is found, get its first + // content. + if ( nullptr != sw_FormatNextContentForKeep( this ) && !GetNext() ) + { + setFrameAreaPositionValid(false); + } + } + } + } + } + + //Again an invalid value? - do it again... + if ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + continue; + + // check, if calculation of table frame is ready. + + // Local variable <nDistanceToUpperPrtBottom> + // Introduce local variable and init it with the distance from the + // table frame bottom to the bottom of the upper printing area. + // Note: negative values denotes the situation that table frame doesn't fit in its upper. + SwTwips nDistanceToUpperPrtBottom = + aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + + /// In online layout try to grow upper of table frame, if table frame doesn't fit in its upper. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + if ( nDistanceToUpperPrtBottom < 0 && bBrowseMode ) + { + if ( GetUpper()->Grow( -nDistanceToUpperPrtBottom ) ) + { + // upper is grown --> recalculate <nDistanceToUpperPrtBottom> + nDistanceToUpperPrtBottom = aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + } + } + + // If there is still some space left in the upper, we check if we + // can join some rows of the follow. + // Setting bLastRowHasToMoveToFollow to true means we want to force + // the table to be split! Only skip this if condition once. + if( nDistanceToUpperPrtBottom >= 0 && !bLastRowHasToMoveToFollow ) + { + // If there is space left in the upper printing area, join as for trial + // at least one further row of an existing follow. + if ( !bSplit && GetFollow() ) + { + bool bDummy; + if ( GetFollow()->ShouldBwdMoved( GetUpper(), bDummy ) ) + { + SwFrame *pTmp = GetUpper(); + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*pTmp); + if ( bBrowseMode ) + nDeadLine += pTmp->Grow( LONG_MAX, true ); + bool bFits = aRectFnSet.BottomDist(getFrameArea(), nDeadLine) > 0; + if (!bFits && aRectFnSet.GetHeight(GetFollow()->getFrameArea()) == 0) + // The follow should move backwards, so allow the case + // when the upper has no space, but the follow is + // empty. + bFits = aRectFnSet.BottomDist(getFrameArea(), nDeadLine) >= 0; + if (bFits) + { + // First, we remove an existing follow flow line. + if ( HasFollowFlowLine() ) + { + SwFrame* pLastLine = GetLastLower(); + RemoveFollowFlowLine(); + // invalidate and rebuild last row + if ( pLastLine ) + { + ::SwInvalidateAll( pLastLine, LONG_MAX ); + SetRebuildLastLine( true ); + lcl_RecalcRow(*static_cast<SwRowFrame*>(pLastLine), LONG_MAX); + SetRebuildLastLine( false ); + } + + SwFrame* pRow = GetFollow()->GetFirstNonHeadlineRow(); + + if ( !pRow || !pRow->GetNext() ) + // The follow became empty and hence useless + Join(); + + continue; + } + + // If there is no follow flow line, we move the first + // row in the follow table to the master table. + SwRowFrame *pRow = GetFollow()->GetFirstNonHeadlineRow(); + + // The follow became empty and hence useless + if ( !pRow ) + { + Join(); + continue; + } + + const SwTwips nOld = aRectFnSet.GetHeight(getFrameArea()); + long nRowsToMove = lcl_GetMaximumLayoutRowSpan( *pRow ); + SwFrame* pRowToMove = pRow; + + while ( pRowToMove && nRowsToMove-- > 0 ) + { + const bool bMoveFootnotes = bFootnotesInDoc && !GetFollow()->IsJoinLocked(); + + SwFootnoteBossFrame *pOldBoss = nullptr; + if ( bMoveFootnotes ) + pOldBoss = pRowToMove->FindFootnoteBossFrame( true ); + + SwFrame* pNextRow = pRowToMove->GetNext(); + + if ( !pNextRow ) + { + // The follow became empty and hence useless + Join(); + } + else + { + pRowToMove->Cut(); + pRowToMove->Paste( this ); + } + + // Move the footnotes! + if ( bMoveFootnotes ) + if ( static_cast<SwLayoutFrame*>(pRowToMove)->MoveLowerFootnotes( nullptr, pOldBoss, FindFootnoteBossFrame( true ), true ) ) + GetUpper()->Calc(pRenderContext); + + pRowToMove = pNextRow; + } + + if ( nOld != aRectFnSet.GetHeight(getFrameArea()) ) + lcl_RecalcTable( *this, static_cast<SwLayoutFrame*>(pRow), aNotify ); + + continue; + } + } + } + else if ( KEEPTAB ) + { + bool bFormat = false; + if ( bKeep ) + bFormat = true; + else if ( bTableRowKeep && !bLastRowMoveNoMoreTries ) + { + // We only want to give the last row one chance to move + // to the follow table. Set the flag as early as possible: + bLastRowMoveNoMoreTries = true; + + // The last line of the table has to be cut off if: + // 1. The table does not want to keep with its next + // 2. The compatibility option is set and the table is allowed to split + // 3. We did not already cut off the last row + // 4. There is not break after attribute set at the table + // 5. There is no break before attribute set behind the table + // 6. There is no section change behind the table (see IsKeep) + // 7. The last table row wants to keep with its next. + const SwRowFrame* pLastRow = static_cast<const SwRowFrame*>(GetLastLower()); + if (pLastRow + && IsKeep(pAttrs->GetAttrSet().GetKeep(), GetBreakItem(), true) + && pLastRow->ShouldRowKeepWithNext()) + { + bFormat = true; + } + } + + if ( bFormat ) + { + pAccess.reset(); + + // Consider case that table is inside another table, because + // it has to be avoided, that superior table is formatted. + // Thus, find next content, table or section and, if a section + // is found, get its first content. + const SwFrame* pTmpNxt = sw_FormatNextContentForKeep( this ); + + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + + // The last row wants to keep with the frame behind the table. + // Check if the next frame is on a different page and valid. + // In this case we do a magic trick: + if ( !bKeep && !GetNext() && pTmpNxt && pTmpNxt->isFrameAreaDefinitionValid() ) + { + setFrameAreaPositionValid(false); + bLastRowHasToMoveToFollow = true; + } + } + } + + if ( isFrameAreaDefinitionValid() ) + { + if (m_bCalcLowers) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + } + else if (m_bONECalcLowers) + { + lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), LONG_MAX); + m_bONECalcLowers = false; + } + } + continue; + } + + // I don't fit in the upper Frame anymore, therefore it's the + // right moment to do some preferably constructive changes. + + // If I'm NOT allowed to leave the upper Frame, I've got a problem. + // Following Arthur Dent, we do the only thing that you can do with + // an unsolvable problem: We ignore it with all our power. + if ( !bMoveable ) + { + if (m_bCalcLowers && isFrameAreaDefinitionValid()) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + } + else if (m_bONECalcLowers) + { + lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), LONG_MAX); + m_bONECalcLowers = false; + } + + // It does not make sense to cut off the last line if we are + // not moveable: + bLastRowHasToMoveToFollow = false; + + continue; + } + + if (m_bCalcLowers && isFrameAreaDefinitionValid()) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + if( !isFrameAreaDefinitionValid() ) + continue; + } + + // First try to split the table. Condition: + // 1. We have at least one non headline row + // 2. If this row wants to keep, we need an additional row + // 3. The table is allowed to split or we do not have a pIndPrev: + SwFrame* pIndPrev = GetIndPrev(); + const SwRowFrame* pFirstNonHeadlineRow = GetFirstNonHeadlineRow(); + // #i120016# if this row wants to keep, allow split in case that all rows want to keep with next, + // the table can not move forward as it is the first one and a split is in general allowed. + const bool bAllowSplitOfRow = bTableRowKeep && !pIndPrev && AreAllRowsKeepWithNext(pFirstNonHeadlineRow); + // tdf91083 MSCompat: this extends bAllowSplitOfRow (and perhaps should just replace it). + // If the kept-together items cannot move to a new page, a table split is in general allowed. + const bool bEmulateTableKeepSplitAllowed = bEmulateTableKeep && !IsKeepFwdMoveAllowed(/*IgnoreMyOwnKeepValue=*/true); + + if ( pFirstNonHeadlineRow && nUnSplitted > 0 && + ( bEmulateTableKeepSplitAllowed || bAllowSplitOfRow || + ( ( !bTableRowKeep || pFirstNonHeadlineRow->GetNext() || + !pFirstNonHeadlineRow->ShouldRowKeepWithNext() + ) && ( !bDontSplit || !pIndPrev ) + ) ) ) + { + // #i29438# + // Special DoNotSplit cases: + // We better avoid splitting of a row frame if we are inside a columned + // section which has a height of 0, because this is not growable and thus + // all kinds of unexpected things could happen. + if ( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() && + 0 == aRectFnSet.GetHeight(GetUpper()->getFrameArea()) + ) + { + bTryToSplit = false; + } + + // 1. Try: bTryToSplit = true => Try to split the row. + // 2. Try: bTryToSplit = false => Split the table between the rows. + if ( pFirstNonHeadlineRow->GetNext() || bTryToSplit ) + { + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + if( IsInSct() || GetUpper()->IsInTab() ) // TABLE IN TABLE) + nDeadLine = aRectFnSet.YInc( nDeadLine, + GetUpper()->Grow( LONG_MAX, true ) ); + + { + SwFrameDeleteGuard g(Lower()); // tdf#134965 prevent RemoveFollowFlowLine() + SetInRecalcLowerRow( true ); + ::lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), nDeadLine); + SetInRecalcLowerRow( false ); + } + m_bLowersFormatted = true; + aNotify.SetLowersComplete( true ); + + // One more check if it's really necessary to split the table. + // 1. The table either has to exceed the deadline or + // 2. We explicitly want to cut off the last row. + if( aRectFnSet.BottomDist( getFrameArea(), nDeadLine ) > 0 && !bLastRowHasToMoveToFollow ) + { + continue; + } + + // Set to false again as early as possible. + bLastRowHasToMoveToFollow = false; + + // #i52781# + // YaSC - Yet another special case: + // If our upper is inside a table cell which is not allowed + // to split, we do not try to split: + if ( GetUpper()->IsInTab() ) + { + const SwFrame* pTmpRow = GetUpper(); + while ( pTmpRow && !pTmpRow->IsRowFrame() ) + pTmpRow = pTmpRow->GetUpper(); + if ( pTmpRow && !static_cast<const SwRowFrame*>(pTmpRow)->IsRowSplitAllowed() ) + continue; + } + + sal_uInt16 nMinNumOfLines = nRepeat; + + if ( bTableRowKeep ) + { + const SwRowFrame* pTmpRow = GetFirstNonHeadlineRow(); + while ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() ) + { + ++nMinNumOfLines; + pTmpRow = static_cast<const SwRowFrame*>(pTmpRow->GetNext()); + } + } + + if ( !bTryToSplit ) + ++nMinNumOfLines; + + const SwTwips nBreakLine = aRectFnSet.YInc( + aRectFnSet.GetTop(getFrameArea()), + aRectFnSet.GetTopMargin(*this) + + lcl_GetHeightOfRows( GetLower(), nMinNumOfLines ) ); + + // Some more checks if we want to call the split algorithm or not: + // The repeating lines / keeping lines still fit into the upper or + // if we do not have an (in)direct Prev, we split anyway. + if( aRectFnSet.YDiff(nDeadLine, nBreakLine) >=0 + || !pIndPrev || bEmulateTableKeepSplitAllowed ) + { + aNotify.SetLowersComplete( false ); + bSplit = true; + + // An existing follow flow line has to be removed. + if ( HasFollowFlowLine() ) + { + if (!nThrowAwayValidLayoutLimit) + continue; + const bool bInitialLoopEndCondition(isFrameAreaDefinitionValid()); + RemoveFollowFlowLine(); + const bool bFinalLoopEndCondition(isFrameAreaDefinitionValid()); + + if (bInitialLoopEndCondition && !bFinalLoopEndCondition) + { + --nThrowAwayValidLayoutLimit; + } + } + + const bool bSplitError = !Split( nDeadLine, bTryToSplit, ( bTableRowKeep && !(bAllowSplitOfRow || bEmulateTableKeepSplitAllowed) ) ); + if (!bTryToSplit && !bSplitError) + { + --nUnSplitted; + } + + // #i29771# Two tries to split the table + // If an error occurred during splitting. We start a second + // try, this time without splitting of table rows. + if ( bSplitError && HasFollowFlowLine() ) + RemoveFollowFlowLine(); + + // If splitting the table was successful or not, + // we do not want to have 'empty' follow tables. + if ( GetFollow() && !GetFollow()->GetFirstNonHeadlineRow() ) + Join(); + + // We want to restore the situation before the failed + // split operation as good as possible. Therefore we + // do some more calculations. Note: Restricting this + // to nDeadLine may not be enough. + if ( bSplitError && bTryToSplit ) // no restart if we did not try to split: i72847, i79426 + { + lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), LONG_MAX); + setFrameAreaPositionValid(false); + bTryToSplit = false; + continue; + } + + bTryToSplit = !bSplitError; + + //To avoid oscillations the Follow must become valid now + if ( GetFollow() ) + { + // #i80924# + // After a successful split assure that the first row + // is invalid. When graphics are present, this isn't hold. + // Note: defect i80924 could also be fixed, if it is + // assured, that <SwLayNotify::bLowersComplete> is only + // set, if all lower are valid *and* are correct laid out. + if ( !bSplitError && GetFollow()->GetLower() ) + { + GetFollow()->GetLower()->InvalidatePos(); + } + SwRectFnSet fnRectX(GetFollow()); + + static sal_uInt8 nStack = 0; + if ( !StackHack::IsLocked() && nStack < 4 ) + { + ++nStack; + StackHack aHack; + pAccess.reset(); + + GetFollow()->MakeAll(pRenderContext); + + pAccess = std::make_unique<SwBorderAttrAccess>(SwFrame::GetCache(), this); + pAttrs = pAccess->Get(); + + GetFollow()->SetLowersFormatted(false); + // #i43913# - lock follow table + // to avoid its formatting during the format of + // its content. + const bool bOldJoinLock = GetFollow()->IsJoinLocked(); + GetFollow()->LockJoin(); + ::lcl_RecalcRow(*static_cast<SwRowFrame*>(GetFollow()->Lower()), + fnRectX.GetBottom(GetFollow()->GetUpper()->getFrameArea()) ); + // #i43913# + // #i63632# Do not unlock the + // follow if it wasn't locked before. + if ( !bOldJoinLock ) + GetFollow()->UnlockJoin(); + + if ( !GetFollow()->GetFollow() ) + { + SwFrame* pNxt = static_cast<SwFrame*>(GetFollow())->FindNext(); + if ( pNxt ) + { + // #i18103# - no formatting of found next + // frame, if it's a follow section of the + // 'ColLocked' section, the follow table is + // in. + bool bCalcNxt = true; + if ( GetFollow()->IsInSct() && pNxt->IsSctFrame() ) + { + SwSectionFrame* pSct = GetFollow()->FindSctFrame(); + if ( pSct->IsColLocked() && + pSct->GetFollow() == pNxt ) + { + bCalcNxt = false; + } + } + if ( bCalcNxt ) + { + // tdf#119109 follow was just formatted, + // don't do it again now + FlowFrameJoinLockGuard g(GetFollow()); + pNxt->Calc(pRenderContext); + } + } + } + --nStack; + } + else if ( GetFollow() == GetNext() ) + GetFollow()->MoveFwd( true, false ); + } + continue; + } + } + } + + // Set to false again as early as possible. + bLastRowHasToMoveToFollow = false; + + if( IsInSct() && bMovedFwd && bMakePage && GetUpper()->IsColBodyFrame() && + GetUpper()->GetUpper()->GetUpper()->IsSctFrame() && + ( GetUpper()->GetUpper()->GetPrev() || GetIndPrev() ) && + static_cast<SwSectionFrame*>(GetUpper()->GetUpper()->GetUpper())->MoveAllowed(this) ) + { + bMovedFwd = false; + } + + // #i29771# Reset bTryToSplit flag on change of upper + const SwFrame* pOldUpper = GetUpper(); + + //Let's see if we find some place anywhere... + if (!bMovedFwd) + { + // don't make the effort to move fwd if its known + // conditions that are known not to work + if (IsInFootnote() && ForbiddenForFootnoteCntFwd()) + bMakePage = false; + else if (!MoveFwd(bMakePage, false)) + bMakePage = false; + } + + // #i29771# Reset bSplitError flag on change of upper + if ( GetUpper() != pOldUpper ) + { + bTryToSplit = true; + nUnSplitted = 5; + } + + aRectFnSet.Refresh(this); + m_bCalcLowers = true; + bMovedFwd = true; + aNotify.SetLowersComplete( false ); + if ( IsFollow() ) + { + // To avoid oscillations, master should not remain invalid + SwTabFrame *pTab = FindMaster(); + if ( pTab->GetUpper() ) + pTab->GetUpper()->Calc(pRenderContext); + pTab->Calc(pRenderContext); + pTab->SetLowersFormatted( false ); + } + + //If my neighbour is my Follow at the same time, I'll swallow it up. + if ( ( GetNext() && GetNext() == GetFollow() ) || !GetLower() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + if ( GetFollow() ) + Join(); + } + + if ( bMovedBwd && GetUpper() ) + { + //During flowing back the upper was animated to do a full repaint, + //we can now skip this after the whole flowing back and forth. + GetUpper()->ResetCompletePaint(); + } + + if (m_bCalcLowers && isFrameAreaDefinitionValid()) + { + // #i44910# - format of lower frames unnecessary + // and can cause layout loops, if table doesn't fit and isn't + // allowed to split. + SwTwips nDistToUpperPrtBottom = + aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + if ( nDistToUpperPrtBottom >= 0 || bTryToSplit ) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + if (!isFramePrintAreaValid()) + m_pTable->SetRowsToRepeat(1); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "debug assertion: <SwTabFrame::MakeAll()> - format of table lowers suppressed by fix i44910" ); + } +#endif + } + + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + + //If my direct predecessor is my master now, it can destroy me during the + //next best opportunity. + if ( IsFollow() ) + { + SwFrame *pPre = GetPrev(); + if ( pPre && pPre->IsTabFrame() && static_cast<SwTabFrame*>(pPre)->GetFollow() == this) + pPre->InvalidatePos(); + } + + m_bCalcLowers = m_bONECalcLowers = false; + pAccess.reset(); + UnlockJoin(); + if ( bMovedFwd || bMovedBwd || !bOldValidPos ) + aNotify.SetInvaKeep(); +} + +static bool IsNextOnSamePage(SwPageFrame const& rPage, + SwTabFrame const& rTabFrame, SwTextFrame const& rAnchorFrame) +{ + for (SwContentFrame const* pContentFrame = rTabFrame.FindNextCnt(); + pContentFrame && pContentFrame->FindPageFrame() == &rPage; + pContentFrame = pContentFrame->FindNextCnt()) + { + if (pContentFrame == &rAnchorFrame) + { + return true; + } + } + return false; +} + +/// Calculate the offsets arising because of FlyFrames +bool SwTabFrame::CalcFlyOffsets( SwTwips& rUpper, + long& rLeftOffset, + long& rRightOffset ) const +{ + bool bInvalidatePrtArea = false; + const SwPageFrame *pPage = FindPageFrame(); + const SwFlyFrame* pMyFly = FindFlyFrame(); + + // --> #108724# Page header/footer content doesn't have to wrap around + // floating screen objects + + const IDocumentSettingAccess& rIDSA = GetFormat()->getIDocumentSettingAccess(); + const bool bWrapAllowed = rIDSA.get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) || + ( !IsInFootnote() && nullptr == FindFooterOrHeader() ); + + if ( pPage->GetSortedObjs() && bWrapAllowed ) + { + SwRectFnSet aRectFnSet(this); + const bool bConsiderWrapOnObjPos = rIDSA.get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION); + long nPrtPos = aRectFnSet.GetTop(getFrameArea()); + nPrtPos = aRectFnSet.YInc( nPrtPos, rUpper ); + SwRect aRect( getFrameArea() ); + long nYDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(getFramePrintArea()), rUpper ); + if( nYDiff > 0 ) + aRectFnSet.AddBottom( aRect, -nYDiff ); + + bool bAddVerticalFlyOffsets = rIDSA.get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); + + for ( size_t i = 0; i < pPage->GetSortedObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pPage->GetSortedObjs())[i]; + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + const SwRect aFlyRect = pFly->GetObjRectWithSpaces(); + // #i26945# - correction of conditions, + // if Writer fly frame has to be considered: + // - no need to check, if top of Writer fly frame differs + // from FAR_AWAY, because it's also checked, if the Writer + // fly frame rectangle overlaps with <aRect> + // - no check, if bottom of anchor frame is prior the top of + // the table, because Writer fly frames can be negative positioned. + // - correct check, if the Writer fly frame is a lower of the + // table, because table lines/rows can split and an at-character + // anchored Writer fly frame could be positioned in the follow + // flow line. + // - add condition, that an existing anchor character text frame + // has to be on the same page as the table. + // E.g., it could happen, that the fly frame is still registered + // at the page frame, the table is on, but it's anchor character + // text frame has already changed its page. + const SwTextFrame* pAnchorCharFrame = pFly->FindAnchorCharFrame(); + bool bConsiderFly = + // #i46807# - do not consider invalid + // Writer fly frames. + (pFly->isFrameAreaDefinitionValid() || bAddVerticalFlyOffsets) && + // fly anchored at character + pFly->IsFlyAtContentFrame() && + // fly overlaps with corresponding table rectangle + aFlyRect.IsOver( aRect ) && + // fly isn't lower of table and + // anchor character frame of fly isn't lower of table + ( !IsAnLower( pFly ) && + ( !pAnchorCharFrame || !IsAnLower( pAnchorCharFrame ) ) ) && + // table isn't lower of fly + !pFly->IsAnLower( this ) && + // fly is lower of fly, the table is in + // #123274# - correction + // assure that fly isn't a lower of a fly, the table isn't in. + // E.g., a table in the body doesn't wrap around a graphic, + // which is inside a frame. + ( ( !pMyFly || + pMyFly->IsAnLower( pFly ) ) && + pMyFly == pFly->GetAnchorFrameContainingAnchPos()->FindFlyFrame() ) && + // anchor frame not on following page + pPage->GetPhyPageNum() >= + pFly->GetAnchorFrame()->FindPageFrame()->GetPhyPageNum() && + // anchor character text frame on same page + ( !pAnchorCharFrame || + pAnchorCharFrame->FindPageFrame()->GetPhyPageNum() == + pPage->GetPhyPageNum() ); + + if ( bConsiderFly ) + { + const SwFrame* pFlyHeaderFooterFrame = pFly->GetAnchorFrame()->FindFooterOrHeader(); + const SwFrame* pThisHeaderFooterFrame = FindFooterOrHeader(); + + if ( pFlyHeaderFooterFrame != pThisHeaderFooterFrame && + // #148493# If bConsiderWrapOnObjPos is set, + // we want to consider the fly if it is located in the header and + // the table is located in the body: + ( !bConsiderWrapOnObjPos || nullptr != pThisHeaderFooterFrame || !pFlyHeaderFooterFrame->IsHeaderFrame() ) ) + bConsiderFly = false; + } + + if ( bConsiderFly ) + { + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + const SwFormatHoriOrient &rHori= pFly->GetFormat()->GetHoriOrient(); + bool bShiftDown = css::text::WrapTextMode_NONE == rSur.GetSurround(); + if (!bShiftDown && bAddVerticalFlyOffsets) + { + if (rSur.GetSurround() == text::WrapTextMode_PARALLEL + && rHori.GetHoriOrient() == text::HoriOrientation::NONE) + { + // We know that wrapping was requested and the table frame overlaps with + // the fly frame. Check if the print area overlaps with the fly frame as + // well (in case the table does not use all the available width). + basegfx::B1DRange aTabRange( + aRectFnSet.GetLeft(aRect) + aRectFnSet.GetLeft(getFramePrintArea()), + aRectFnSet.GetLeft(aRect) + aRectFnSet.GetLeft(getFramePrintArea()) + + aRectFnSet.GetWidth(getFramePrintArea())); + + // Ignore spacing when determining the left/right edge of the fly, like + // Word does. + const SwRect aFlyRectWithoutSpaces = pFly->GetObjRect(); + basegfx::B1DRange aFlyRange(aRectFnSet.GetLeft(aFlyRectWithoutSpaces), + aRectFnSet.GetRight(aFlyRectWithoutSpaces)); + + // If it does, shift the table down. Do this only in the compat case, + // normally an SwFlyPortion is created instead that increases the height + // of the first table row. + bShiftDown = aTabRange.overlaps(aFlyRange); + } + } + + if (bShiftDown) + { + // possible cases: + // both in body + // both in same fly + // any comb. of body, footnote, header/footer + // to keep it safe, check only in doc body vs page margin for now + long nBottom = aRectFnSet.GetBottom(aFlyRect); + // tdf#138039 don't grow beyond the page body + // if the fly is anchored below the table; the fly + // must move with its anchor frame to the next page + SwRectFnSet fnPage(pPage); + if (!IsInDocBody() // TODO + || fnPage.YDiff(fnPage.GetBottom(aFlyRect), fnPage.GetPrtBottom(*pPage)) <= 0 + || !IsNextOnSamePage(*pPage, *this, + *static_cast<SwTextFrame*>(pFly->GetAnchorFrameContainingAnchPos()))) + { + if (aRectFnSet.YDiff( nPrtPos, nBottom ) < 0) + nPrtPos = nBottom; + bInvalidatePrtArea = true; + } + } + if ( (css::text::WrapTextMode_RIGHT == rSur.GetSurround() || + css::text::WrapTextMode_PARALLEL == rSur.GetSurround())&& + text::HoriOrientation::LEFT == rHori.GetHoriOrient() ) + { + const long nWidth = aRectFnSet.XDiff( + aRectFnSet.GetRight(aFlyRect), + aRectFnSet.GetLeft(pFly->GetAnchorFrame()->getFrameArea()) ); + rLeftOffset = std::max( rLeftOffset, nWidth ); + bInvalidatePrtArea = true; + } + if ( (css::text::WrapTextMode_LEFT == rSur.GetSurround() || + css::text::WrapTextMode_PARALLEL == rSur.GetSurround())&& + text::HoriOrientation::RIGHT == rHori.GetHoriOrient() ) + { + const long nWidth = aRectFnSet.XDiff( + aRectFnSet.GetRight(pFly->GetAnchorFrame()->getFrameArea()), + aRectFnSet.GetLeft(aFlyRect) ); + rRightOffset = std::max( rRightOffset, nWidth ); + bInvalidatePrtArea = true; + } + } + } + } + rUpper = aRectFnSet.YDiff( nPrtPos, aRectFnSet.GetTop(getFrameArea()) ); + } + + return bInvalidatePrtArea; +} + +/// "Formats" the frame; Frame and PrtArea. +/// The fixed size is not adjusted here. +void SwTabFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "TabFrame::Format, pAttrs is 0." ); + + SwRectFnSet aRectFnSet(this); + if ( !isFrameAreaSizeValid() ) + { + long nDiff = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) - + aRectFnSet.GetWidth(getFrameArea()); + if( nDiff ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddRight( aFrm, nDiff ); + } + } + + //VarSize is always the height. + //For the upper/lower margins the same rules apply as for ContentFrames (see + //MakePrtArea() of those). + + SwTwips nUpper = CalcUpperSpace( pAttrs ); + + // We want to dodge the flys. Two possibilities: + // 1. There are flys with SurroundNone, dodge them completely + // 2. There are flys which only wrap on the right or the left side and + // those are right or left aligned, those set the minimum for the margins + long nTmpRight = -1000000, + nLeftOffset = 0; + if( CalcFlyOffsets( nUpper, nLeftOffset, nTmpRight ) ) + { + setFramePrintAreaValid(false); + } + + long nRightOffset = std::max( 0L, nTmpRight ); + + SwTwips nLower = pAttrs->CalcBottomLine(); + // #i29550# + if ( IsCollapsingBorders() ) + nLower += GetBottomLineSize(); + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + + // The width of the PrintArea is given by the FrameFormat, the margins + // have to be set accordingly. + // Minimum margins are determined depending on borders and shadows. + // The margins are set so that the PrintArea is aligned into the + // Frame according to the adjustment. + // If the adjustment is 0, the margins are set according to the border + // attributes. + + const SwTwips nOldHeight = aRectFnSet.GetHeight(getFramePrintArea()); + const SwTwips nMax = aRectFnSet.GetWidth(getFrameArea()); + + // OD 14.03.2003 #i9040# - adjust variable names. + const SwTwips nLeftLine = pAttrs->CalcLeftLine(); + const SwTwips nRightLine = pAttrs->CalcRightLine(); + + // The width possibly is a percentage value. If the table is inside + // something else, the value refers to the environment. If it's in the + // body then in the BrowseView the value refers to the screen width. + const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); + // OD 14.03.2003 #i9040# - adjust variable name. + const SwTwips nWishedTableWidth = CalcRel( rSz ); + + bool bCheckBrowseWidth = false; + + // OD 14.03.2003 #i9040# - insert new variables for left/right spacing. + SwTwips nLeftSpacing = 0; + SwTwips nRightSpacing = 0; + switch ( GetFormat()->GetHoriOrient().GetHoriOrient() ) + { + case text::HoriOrientation::LEFT: + { + // left indent: + nLeftSpacing = nLeftLine + nLeftOffset; + // OD 06.03.2003 #i9040# - correct calculation of right indent: + // - Consider right indent given by right line attributes. + // - Consider negative right indent. + // wished right indent determined by wished table width and + // left offset given by surround fly frames on the left: + const SwTwips nWishRight = nMax - nWishedTableWidth - nLeftOffset; + if ( nRightOffset > 0 ) + { + // surrounding fly frames on the right + // -> right indent is maximum of given right offset + // and wished right offset. + nRightSpacing = nRightLine + std::max( nRightOffset, nWishRight ); + } + else + { + // no surrounding fly frames on the right + // If intrinsic right indent (intrinsic means not considering + // determined left indent) is negative, + // then hold this intrinsic indent, + // otherwise non negative wished right indent is hold. + nRightSpacing = nRightLine + + ( ( (nWishRight+nLeftOffset) < 0 ) ? + (nWishRight+nLeftOffset) : + std::max( 0L, nWishRight ) ); + } + } + break; + case text::HoriOrientation::RIGHT: + { + // right indent: + nRightSpacing = nRightLine + nRightOffset; + // OD 06.03.2003 #i9040# - correct calculation of left indent: + // - Consider left indent given by left line attributes. + // - Consider negative left indent. + // wished left indent determined by wished table width and + // right offset given by surrounding fly frames on the right: + const SwTwips nWishLeft = nMax - nWishedTableWidth - nRightOffset; + if ( nLeftOffset > 0 ) + { + // surrounding fly frames on the left + // -> right indent is maximum of given left offset + // and wished left offset. + nLeftSpacing = nLeftLine + std::max( nLeftOffset, nWishLeft ); + } + else + { + // no surrounding fly frames on the left + // If intrinsic left indent (intrinsic = not considering + // determined right indent) is negative, + // then hold this intrinsic indent, + // otherwise non negative wished left indent is hold. + nLeftSpacing = nLeftLine + + ( ( (nWishLeft+nRightOffset) < 0 ) ? + (nWishLeft+nRightOffset) : + std::max( 0L, nWishLeft ) ); + } + } + break; + case text::HoriOrientation::CENTER: + { + // OD 07.03.2003 #i9040# - consider left/right line attribute. + const SwTwips nCenterSpacing = ( nMax - nWishedTableWidth ) / 2; + nLeftSpacing = nLeftLine + + ( (nLeftOffset > 0) ? + std::max( nCenterSpacing, nLeftOffset ) : + nCenterSpacing ); + nRightSpacing = nRightLine + + ( (nRightOffset > 0) ? + std::max( nCenterSpacing, nRightOffset ) : + nCenterSpacing ); + } + break; + case text::HoriOrientation::FULL: + //This things grows over the whole width. + //Only the free space needed for the border is taken into + //account. The attribute values of LRSpace are ignored + //intentionally. + bCheckBrowseWidth = true; + nLeftSpacing = nLeftLine + nLeftOffset; + nRightSpacing = nRightLine + nRightOffset; + break; + case text::HoriOrientation::NONE: + { + // The margins are defined by the LRSpace attribute. + nLeftSpacing = pAttrs->CalcLeft( this ); + if( nLeftOffset ) + { + // OD 07.03.2003 #i9040# - surround fly frames only, if + // they overlap with the table. + // Thus, take maximum of left spacing and left offset. + // OD 10.03.2003 #i9040# - consider left line attribute. + nLeftSpacing = std::max( nLeftSpacing, ( nLeftOffset + nLeftLine ) ); + } + // OD 23.01.2003 #106895# - add 1st param to <SwBorderAttrs::CalcRight(..)> + nRightSpacing = pAttrs->CalcRight( this ); + if( nRightOffset ) + { + // OD 07.03.2003 #i9040# - surround fly frames only, if + // they overlap with the table. + // Thus, take maximum of right spacing and right offset. + // OD 10.03.2003 #i9040# - consider right line attribute. + nRightSpacing = std::max( nRightSpacing, ( nRightOffset + nRightLine ) ); + } + } + break; + case text::HoriOrientation::LEFT_AND_WIDTH: + { + // count left border and width (Word specialty) + // OD 10.03.2003 #i9040# - no width alignment in online mode. + //bCheckBrowseWidth = true; + nLeftSpacing = pAttrs->CalcLeft( this ); + if( nLeftOffset ) + { + // OD 10.03.2003 #i9040# - surround fly frames only, if + // they overlap with the table. + // Thus, take maximum of right spacing and right offset. + // OD 10.03.2003 #i9040# - consider left line attribute. + nLeftSpacing = std::max( nLeftSpacing, ( pAttrs->CalcLeftLine() + nLeftOffset ) ); + } + // OD 10.03.2003 #i9040# - consider right and left line attribute. + const SwTwips nWishRight = + nMax - (nLeftSpacing-pAttrs->CalcLeftLine()) - nWishedTableWidth; + nRightSpacing = nRightLine + + ( (nRightOffset > 0) ? + std::max( nWishRight, nRightOffset ) : + nWishRight ); + } + break; + default: + OSL_FAIL( "Invalid orientation for table." ); + } + + // #i26250# - extend bottom printing area, if table + // is last content inside a table cell. + if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) && + GetUpper()->IsInTab() && !GetIndNext() ) + { + nLower += pAttrs->GetULSpace().GetLower(); + } + aRectFnSet.SetYMargins( *this, nUpper, nLower ); + if( (nMax - MINLAY) < (nLeftSpacing + nRightSpacing) ) + aRectFnSet.SetXMargins( *this, 0, 0 ); + else + aRectFnSet.SetXMargins( *this, nLeftSpacing, nRightSpacing ); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( bCheckBrowseWidth && + pSh && pSh->GetViewOptions()->getBrowseMode() && + GetUpper()->IsPageBodyFrame() && // only PageBodyFrames and not ColBodyFrames + pSh->VisArea().Width() ) + { + //Don't go beyond the edge of the visible area. + //The page width can be bigger because objects with + //"over-size" are possible (RootFrame::ImplCalcBrowseWidth()) + long nWidth = pSh->GetBrowseWidth(); + nWidth -= getFramePrintArea().Left(); + nWidth -= pAttrs->CalcRightLine(); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( std::min( nWidth, aPrt.Width() ) ); + } + + if ( nOldHeight != aRectFnSet.GetHeight(getFramePrintArea()) ) + { + setFrameAreaSizeValid(false); + } + } + + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + + // The size is defined by the content plus the margins. + SwTwips nRemaining = 0, nDiff; + SwFrame *pFrame = m_pLower; + while ( pFrame ) + { + nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + // And now add the margins + nRemaining += nUpper + nLower; + + nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; + if ( nDiff > 0 ) + Shrink( nDiff ); + else if ( nDiff < 0 ) + Grow( -nDiff ); + } +} + +SwTwips SwTabFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nHeight > 0 && nDist > ( LONG_MAX - nHeight ) ) + nDist = LONG_MAX - nHeight; + + if ( bTst && !IsRestrictTableGrowth() ) + return nDist; + + if ( GetUpper() ) + { + SwRect aOldFrame( getFrameArea() ); + + //The upper only grows as far as needed. nReal provides the distance + //which is already available. + SwTwips nReal = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); + SwFrame *pFrame = GetUpper()->Lower(); + while ( pFrame && GetFollow() != pFrame ) + { + nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + + if ( nReal < nDist ) + { + long nTmp = GetUpper()->Grow( nDist - std::max<long>(nReal, 0), bTst, bInfo ); + + if ( IsRestrictTableGrowth() ) + { + nTmp = std::min( nDist, nReal + nTmp ); + nDist = nTmp < 0 ? 0 : nTmp; + } + } + + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nDist ); + } + + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); + } + } + } + + if ( !bTst && ( nDist || IsRestrictTableGrowth() ) ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + } + // #i28701# - Due to the new object positioning the + // frame on the next page/column can flow backward (e.g. it was moved + // forward due to the positioning of its objects ). Thus, invalivate this + // next frame, if document compatibility option 'Consider wrapping style + // influence on object positioning' is ON. + else if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + InvalidateAll_(); + InvalidatePage( pPage ); + SetComplete(); + + std::unique_ptr<SvxBrushItem> aBack = GetFormat()->makeBackgroundBrushItem(); + const SvxGraphicPosition ePos = aBack ? aBack->GetGraphicPos() : GPOS_NONE; + if ( GPOS_NONE != ePos && GPOS_TILED != ePos ) + SetCompletePaint(); + } + + return nDist; +} + +void SwTabFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + sal_uInt8 nInvFlags = 0; + bool bAttrSetChg = pNew && RES_ATTRSET_CHG == pNew->Which(); + + if( bAttrSetChg ) + { + SfxItemIter aNIter( *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet() ); + SfxItemIter aOIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); + do + { + UpdateAttr_(pOItem, pNItem, nInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + if ( aOldSet.Count() || aNewSet.Count() ) + SwLayoutFrame::Modify( &aOldSet, &aNewSet ); + } + else + UpdateAttr_( pOld, pNew, nInvFlags ); + + if ( nInvFlags != 0 ) + { + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if ( nInvFlags & 0x02 ) + InvalidatePrt_(); + if ( nInvFlags & 0x40 ) + InvalidatePos_(); + SwFrame *pTmp; + if ( nullptr != (pTmp = GetIndNext()) ) + { + if ( nInvFlags & 0x04 ) + { + pTmp->InvalidatePrt_(); + if ( pTmp->IsContentFrame() ) + pTmp->InvalidatePage( pPage ); + } + if ( nInvFlags & 0x10 ) + pTmp->SetCompletePaint(); + } + if ( nInvFlags & 0x08 && nullptr != (pTmp = GetPrev()) ) + { + pTmp->InvalidatePrt_(); + if ( pTmp->IsContentFrame() ) + pTmp->InvalidatePage( pPage ); + } + if ( nInvFlags & 0x20 ) + { + if ( pPage && pPage->GetUpper() && !IsFollow() ) + static_cast<SwRootFrame*>(pPage->GetUpper())->InvalidateBrowseWidth(); + } + if ( nInvFlags & 0x80 ) + InvalidateNextPos(); + } +} + +void SwTabFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + sal_uInt8 &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_TBLHEADLINECHG: + if ( IsFollow() ) + { + // Delete remaining headlines: + SwRowFrame* pLowerRow = nullptr; + while ( nullptr != ( pLowerRow = static_cast<SwRowFrame*>(Lower()) ) && pLowerRow->IsRepeatedHeadline() ) + { + pLowerRow->Cut(); + SwFrame::DestroyFrame(pLowerRow); + } + + // insert new headlines + const sal_uInt16 nNewRepeat = GetTable()->GetRowsToRepeat(); + for ( sal_uInt16 nIdx = 0; nIdx < nNewRepeat; ++nIdx ) + { + bDontCreateObjects = true; //frmtool + SwRowFrame* pHeadline = new SwRowFrame( *GetTable()->GetTabLines()[ nIdx ], this ); + pHeadline->SetRepeatedHeadline( true ); + bDontCreateObjects = false; + pHeadline->Paste( this, pLowerRow ); + } + } + rInvFlags |= 0x02; + break; + + case RES_FRM_SIZE: + case RES_HORI_ORIENT: + rInvFlags |= 0x22; + break; + + case RES_PAGEDESC: //Attribute changes (on/off) + if ( IsInDocBody() ) + { + rInvFlags |= 0x40; + SwPageFrame *pPage = FindPageFrame(); + if (pPage) + { + if ( !GetPrev() ) + CheckPageDescs( pPage ); + if (GetFormat()->GetPageDesc().GetNumOffset()) + static_cast<SwRootFrame*>(pPage->GetUpper())->SetVirtPageNum( true ); + SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); + GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + } + break; + + case RES_BREAK: + rInvFlags |= 0xC0; + break; + + case RES_LAYOUT_SPLIT: + if ( !IsFollow() ) + rInvFlags |= 0x40; + break; + case RES_FRAMEDIR : + SetDerivedR2L( false ); + CheckDirChange(); + break; + case RES_COLLAPSING_BORDERS : + rInvFlags |= 0x02; + lcl_InvalidateAllLowersPrt( this ); + break; + case RES_UL_SPACE: + rInvFlags |= 0x1C; + [[fallthrough]]; + + default: + bClear = false; + } + if ( bClear ) + { + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + SwLayoutFrame::Modify( pOld, pNew ); + } +} + +bool SwTabFrame::GetInfo( SfxPoolItem &rHint ) const +{ + if ( RES_VIRTPAGENUM_INFO == rHint.Which() && IsInDocBody() && !IsFollow() ) + { + SwVirtPageNumInfo &rInfo = static_cast<SwVirtPageNumInfo&>(rHint); + const SwPageFrame *pPage = FindPageFrame(); + if ( pPage ) + { + if ( pPage == rInfo.GetOrigPage() && !GetPrev() ) + { + // Should be the one (can temporarily be different, should we be + // concerned about this possibility?) + rInfo.SetInfo( pPage, this ); + return false; + } + if ( pPage->GetPhyPageNum() < rInfo.GetOrigPage()->GetPhyPageNum() && + (!rInfo.GetPage() || pPage->GetPhyPageNum() > rInfo.GetPage()->GetPhyPageNum())) + { + //This could be the one. + rInfo.SetInfo( pPage, this ); + } + } + } + return true; +} + +SwFrame *SwTabFrame::FindLastContentOrTable() +{ + SwFrame *pRet = m_pLower; + + while ( pRet && !pRet->IsContentFrame() ) + { + SwFrame *pOld = pRet; + + SwFrame *pTmp = pRet; // To skip empty section frames + while ( pRet->GetNext() ) + { + pRet = pRet->GetNext(); + if( !pRet->IsSctFrame() || static_cast<SwSectionFrame*>(pRet)->GetSection() ) + pTmp = pRet; + } + pRet = pTmp; + + if ( pRet->GetLower() ) + pRet = pRet->GetLower(); + if ( pRet == pOld ) + { + // Check all other columns if there is a column based section with + // an empty last column at the end of the last cell - this is done + // by SwSectionFrame::FindLastContent + if( pRet->IsColBodyFrame() ) + { +#if OSL_DEBUG_LEVEL > 0 + SwSectionFrame* pSect = pRet->FindSctFrame(); + OSL_ENSURE( pSect, "Where does this column come from?"); + OSL_ENSURE( IsAnLower( pSect ), "Split cell?" ); +#endif + return pRet->FindSctFrame()->FindLastContent(); + } + + // pRet may be a cell frame without a lower (cell has been split). + // We have to find the last content the hard way: + + OSL_ENSURE( pRet->IsCellFrame(), "SwTabFrame::FindLastContent failed" ); + const SwFrame* pRow = pRet->GetUpper(); + while ( pRow && !pRow->GetUpper()->IsTabFrame() ) + pRow = pRow->GetUpper(); + const SwContentFrame* pContentFrame = pRow ? static_cast<const SwLayoutFrame*>(pRow)->ContainsContent() : nullptr; + pRet = nullptr; + + while ( pContentFrame && static_cast<const SwLayoutFrame*>(pRow)->IsAnLower( pContentFrame ) ) + { + pRet = const_cast<SwContentFrame*>(pContentFrame); + pContentFrame = pContentFrame->GetNextContentFrame(); + } + } + } + + // #112929# There actually is a situation, which results in pRet = 0: + // Insert frame, insert table via text <-> table. This gives you a frame + // containing a table without any other content frames. Split the table + // and undo the splitting. This operation gives us a table frame without + // a lower. + if ( pRet ) + { + while ( pRet->GetNext() ) + pRet = pRet->GetNext(); + + if (pRet->IsSctFrame()) + pRet = static_cast<SwSectionFrame*>(pRet)->FindLastContent(); + } + + assert(pRet == nullptr || dynamic_cast<SwContentFrame*>(pRet) || dynamic_cast<SwTabFrame*>(pRet)); + return pRet; +} + +SwContentFrame *SwTabFrame::FindLastContent() +{ + SwFrame * pRet(FindLastContentOrTable()); + + while (pRet && pRet->IsTabFrame()) // possibly there's only tables here! + { // tdf#126138 skip table, don't look inside + pRet = pRet->GetPrev(); + } + + assert(pRet == nullptr || dynamic_cast<SwContentFrame*>(pRet)); + return static_cast<SwContentFrame*>(pRet); +} + +/// Return value defines if the frm needs to be relocated +bool SwTabFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool &rReformat ) +{ + rReformat = false; + if ( SwFlowFrame::IsMoveBwdJump() || !IsPrevObjMove() ) + { + //Flowing back Frames is quite time consuming unfortunately. + //Most often the location where the Frame wants to flow to has the same + //FixSize as the Frame itself. In such a situation it's easy to check if + //the Frame will find enough space for its VarSize, if this is not the + //case, the relocation can be skipped. + //Checking if the Frame will find enough space is done by the Frame itself, + //this also takes the possibility of splitting the Frame into account. + //If the FixSize is different or Flys are involved (at the old or the + //new position) the checks are pointless, the Frame then + //needs to be relocated tentatively (if a bit of space is available). + + //The FixSize of the environments which contain tables is always the + //width. + + SwPageFrame *pOldPage = FindPageFrame(), + *pNewPage = pNewUpper->FindPageFrame(); + bool bMoveAnyway = false; + SwTwips nSpace = 0; + + SwRectFnSet aRectFnSet(this); + if ( !SwFlowFrame::IsMoveBwdJump() ) + { + + long nOldWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + SwRectFnSet fnRectX(pNewUpper); + long nNewWidth = fnRectX.GetWidth(pNewUpper->getFramePrintArea()); + if( std::abs( nNewWidth - nOldWidth ) < 2 ) + { + bMoveAnyway = BwdMoveNecessary( pOldPage, getFrameArea() ) > 1; + if( !bMoveAnyway ) + { + SwRect aRect( pNewUpper->getFramePrintArea() ); + aRect.Pos() += pNewUpper->getFrameArea().Pos(); + const SwFrame *pPrevFrame = pNewUpper->Lower(); + while ( pPrevFrame && pPrevFrame != this ) + { + fnRectX.SetTop( aRect, fnRectX.GetBottom(pPrevFrame->getFrameArea()) ); + pPrevFrame = pPrevFrame->GetNext(); + } + bMoveAnyway = BwdMoveNecessary( pNewPage, aRect) > 1; + + // #i54861# Due to changes made in PrepareMake, + // the tabfrm may not have a correct position. Therefore + // it is possible that pNewUpper->getFramePrintArea().Height == 0. In this + // case the above calculation of nSpace might give wrong + // results and we really do not want to MoveBackward into a + // 0 height frame. If nTmpSpace is already <= 0, we take this + // value: + const SwTwips nTmpSpace = fnRectX.GetHeight(aRect); + if ( fnRectX.GetHeight(pNewUpper->getFramePrintArea()) > 0 || nTmpSpace <= 0 ) + nSpace = nTmpSpace; + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + nSpace += pNewUpper->Grow( LONG_MAX, true ); + } + } + else if (!m_bLockBackMove) + bMoveAnyway = true; + } + else if (!m_bLockBackMove) + bMoveAnyway = true; + + if ( bMoveAnyway ) + { + rReformat = true; + return true; + } + + bool bFits = nSpace > 0; + if (!bFits && aRectFnSet.GetHeight(getFrameArea()) == 0) + // This frame fits into pNewUpper in case it has no space, but this + // frame is empty. + bFits = nSpace >= 0; + if (!m_bLockBackMove && bFits) + { + // #i26945# - check, if follow flow line + // contains frame, which are moved forward due to its object + // positioning. + const SwRowFrame* pFirstRow = GetFirstNonHeadlineRow(); + if ( pFirstRow && pFirstRow->IsInFollowFlowRow() && + SwLayouter::DoesRowContainMovedFwdFrame( + *(pFirstRow->GetFormat()->GetDoc()), + *pFirstRow ) ) + { + return false; + } + SwTwips nTmpHeight = CalcHeightOfFirstContentLine(); + + // For some mysterious reason, I changed the good old + // 'return nHeight <= nSpace' to 'return nTmpHeight < nSpace'. + // This obviously results in problems with table frames in + // sections. Remember: Every twip is sacred. + return nTmpHeight <= nSpace; + } + } + return false; +} + +void SwTabFrame::Cut() +{ + OSL_ENSURE( GetUpper(), "Cut without Upper()." ); + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + SwFrame *pFrame = GetNext(); + if( pFrame ) + { + // Possibly the old follow calculated a spacing to the predecessor + // which is obsolete now when it becomes the first frame + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + if ( pFrame->IsContentFrame() ) + pFrame->InvalidatePage( pPage ); + if( IsInSct() && !GetPrev() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + } + else + { + InvalidateNextPos(); + //Someone has to do the retouch: predecessor or upper + if ( nullptr != (pFrame = GetPrev()) ) + { pFrame->SetRetouche(); + pFrame->Prepare( PrepareHint::WidowsOrphans ); + pFrame->InvalidatePos_(); + if ( pFrame->IsContentFrame() ) + pFrame->InvalidatePage( pPage ); + } + //If I am (was) the only FlowFrame in my own upper, it has to do + //the retouch. Moreover a new empty page might be created. + else + { SwRootFrame *pRoot = static_cast<SwRootFrame*>(pPage->GetUpper()); + pRoot->SetSuperfluous(); + GetUpper()->SetCompletePaint(); + if( IsInSct() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + } + } + + //First remove, then shrink the upper. + SwLayoutFrame *pUp = GetUpper(); + SwRectFnSet aRectFnSet(this); + RemoveFromLayout(); + if ( pUp ) + { + OSL_ENSURE( !pUp->IsFootnoteFrame(), "Table in Footnote." ); + SwSectionFrame *pSct = nullptr; + // #126020# - adjust check for empty section + // #130797# - correct fix #126020# + if ( !pUp->Lower() && pUp->IsInSct() && + !(pSct = pUp->FindSctFrame())->ContainsContent() && + !pSct->ContainsAny( true ) ) + { + if ( pUp->GetUpper() ) + { + pSct->DelEmpty( false ); + pSct->InvalidateSize_(); + } + } + // table-in-footnote: delete empty footnote frames (like SwContentFrame::Cut) + else if (!pUp->Lower() && pUp->IsFootnoteFrame() && !pUp->IsColLocked()) + { + if (pUp->GetNext() && !pUp->GetPrev()) + { + if (SwFrame *const pTmp = static_cast<SwLayoutFrame*>(pUp->GetNext())->ContainsAny()) + { + pTmp->InvalidatePrt_(); + } + } + if (!pUp->IsDeleteForbidden()) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + } + } + else if( aRectFnSet.GetHeight(getFrameArea()) ) + { + // OD 26.08.2003 #i18103# - *no* 'ColUnlock' of section - + // undo changes of fix for #104992# + pUp->Shrink( getFrameArea().Height() ); + } + } + + + if ( pPage && !IsFollow() && pPage->GetUpper() ) + static_cast<SwRootFrame*>(pPage->GetUpper())->InvalidateBrowseWidth(); +} + +void SwTabFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "No parent for pasting." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm the parent myself." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I'm still registered somewhere." ); + + //Insert in the tree. + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + InvalidateAll_(); + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + GetNext()->InvalidatePrt_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + } + + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.GetHeight(getFrameArea()) ) + pParent->Grow( aRectFnSet.GetHeight(getFrameArea()) ); + + if( aRectFnSet.GetWidth(getFrameArea()) != aRectFnSet.GetWidth(pParent->getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged ); + if ( GetPrev() ) + { + if ( !IsFollow() ) + { + GetPrev()->InvalidateSize(); + if ( GetPrev()->IsContentFrame() ) + GetPrev()->InvalidatePage( pPage ); + } + } + else if ( GetNext() ) + // Take the spacing into account when dealing with ContentFrames. + // There are two situations (both always happen at the same time): + // a) The Content becomes the first in a chain + // b) The new follower was previously the first in a chain + GetNext()->InvalidatePrt_(); + + if ( pPage && !IsFollow() ) + { + if ( pPage->GetUpper() ) + static_cast<SwRootFrame*>(pPage->GetUpper())->InvalidateBrowseWidth(); + + if ( !GetPrev() )//At least needed for HTML with a table at the beginning. + { + const SwPageDesc *pDesc = GetFormat()->GetPageDesc().GetPageDesc(); + if ( (pDesc && pDesc != pPage->GetPageDesc()) || + (!pDesc && pPage->GetPageDesc() != &GetFormat()->GetDoc()->GetPageDesc(0)) ) + CheckPageDescs( pPage ); + } + } +} + +bool SwTabFrame::Prepare( const PrepareHint eHint, const void *, bool ) +{ + if( PrepareHint::BossChanged == eHint ) + CheckDirChange(); + return false; +} + +SwRowFrame::SwRowFrame(const SwTableLine &rLine, SwFrame* pSib, bool bInsertContent) + : SwLayoutFrame( rLine.GetFrameFormat(), pSib ) + , m_pTabLine( &rLine ) + , m_pFollowRow( nullptr ) + // #i29550# + , mnTopMarginForLowers( 0 ) + , mnBottomMarginForLowers( 0 ) + , mnBottomLineSize( 0 ) + // --> split table rows + , m_bIsFollowFlowRow( false ) + // <-- split table rows + , m_bIsRepeatedHeadline( false ) + , m_bIsRowSpanLine( false ) + , m_bForceRowSplitAllowed( false ) + , m_bIsInSplit( false ) +{ + mnFrameType = SwFrameType::Row; + + //Create the boxes and insert them. + const SwTableBoxes &rBoxes = rLine.GetTabBoxes(); + SwFrame *pTmpPrev = nullptr; + for ( size_t i = 0; i < rBoxes.size(); ++i ) + { + SwCellFrame *pNew = new SwCellFrame( *rBoxes[i], this, bInsertContent ); + pNew->InsertBehind( this, pTmpPrev ); + pTmpPrev = pNew; + } +} + +void SwRowFrame::DestroyImpl() +{ + SwModify* pMod = GetFormat(); + if( pMod ) + { + pMod->Remove( this ); + if( !pMod->HasWriterListeners() ) + delete pMod; + } + + SwLayoutFrame::DestroyImpl(); +} + +SwRowFrame::~SwRowFrame() +{ +} + +void SwRowFrame::RegistFlys( SwPageFrame *pPage ) +{ + ::RegistFlys( pPage ? pPage : FindPageFrame(), this ); +} + +void SwRowFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + bool bAttrSetChg = pNew && RES_ATTRSET_CHG == pNew->Which(); + const SfxPoolItem *pItem = nullptr; + + if( bAttrSetChg ) + { + const SwAttrSet* pChgSet = static_cast<const SwAttrSetChg*>(pNew)->GetChgSet(); + pChgSet->GetItemState( RES_FRM_SIZE, false, &pItem); + if ( !pItem ) + pChgSet->GetItemState( RES_ROW_SPLIT, false, &pItem); + } + else if (pNew && (RES_FRM_SIZE == pNew->Which() || RES_ROW_SPLIT == pNew->Which())) + pItem = pNew; + + if ( pItem ) + { + SwTabFrame *pTab = FindTabFrame(); + if ( pTab ) + { + const bool bInFirstNonHeadlineRow = pTab->IsFollow() && + this == pTab->GetFirstNonHeadlineRow(); + // #i35063# + // Invalidation required is pRow is last row + if ( bInFirstNonHeadlineRow || !GetNext() ) + { + if ( bInFirstNonHeadlineRow ) + pTab = pTab->FindMaster(); + pTab->InvalidatePos(); + } + } + } + + SwLayoutFrame::Modify( pOld, pNew ); +} + +void SwRowFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( !GetNext() ) + { + setFrameAreaSizeValid(false); + } + + SwLayoutFrame::MakeAll(pRenderContext); +} + +long CalcHeightWithFlys( const SwFrame *pFrame ) +{ + SwRectFnSet aRectFnSet(pFrame); + long nHeight = 0; + const SwFrame* pTmp = pFrame->IsSctFrame() ? + static_cast<const SwSectionFrame*>(pFrame)->ContainsContent() : pFrame; + while( pTmp ) + { + // #i26945# - consider follow text frames + const SwSortedObjs* pObjs( nullptr ); + bool bIsFollow( false ); + if ( pTmp->IsTextFrame() && static_cast<const SwTextFrame*>(pTmp)->IsFollow() ) + { + const SwFrame* pMaster; + // #i46450# Master does not necessarily have + // to exist if this function is called from JoinFrame() -> + // Cut() -> Shrink() + const SwTextFrame* pTmpFrame = static_cast<const SwTextFrame*>(pTmp); + if ( pTmpFrame->GetPrev() && pTmpFrame->GetPrev()->IsTextFrame() && + static_cast<const SwTextFrame*>(pTmpFrame->GetPrev())->GetFollow() && + static_cast<const SwTextFrame*>(pTmpFrame->GetPrev())->GetFollow() != pTmp ) + pMaster = nullptr; + else + pMaster = pTmpFrame->FindMaster(); + + if ( pMaster ) + { + pObjs = static_cast<const SwTextFrame*>(pTmp)->FindMaster()->GetDrawObjs(); + bIsFollow = true; + } + } + else + { + pObjs = pTmp->GetDrawObjs(); + } + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + // #i26945# - if <pTmp> is follow, the + // anchor character frame has to be <pTmp>. + if ( bIsFollow && + pAnchoredObj->FindAnchorCharFrame() != pTmp ) + { + continue; + } + // #i26945# - consider also drawing objects + { + // OD 30.09.2003 #i18732# - only objects, which follow + // the text flow have to be considered. + const SwFrameFormat& rFrameFormat = pAnchoredObj->GetFrameFormat(); + bool bFollowTextFlow = rFrameFormat.GetFollowTextFlow().GetValue(); + bool bIsFarAway = pAnchoredObj->GetObjRect().Top() != FAR_AWAY; + const SwPageFrame* pPageFrm = pTmp->FindPageFrame(); + bool bIsAnchoredToTmpFrm = false; + if ( pPageFrm && pPageFrm->IsPageFrame() && pAnchoredObj->GetPageFrame()) + bIsAnchoredToTmpFrm = pAnchoredObj->GetPageFrame() == pPageFrm || + (pPageFrm->GetFormatPage().GetPhyPageNum() == pAnchoredObj->GetPageFrame()->GetFormatPage().GetPhyPageNum() + 1); + const bool bConsiderObj = + (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) && + bIsFarAway && + bFollowTextFlow && bIsAnchoredToTmpFrm; + bool bWrapThrough = rFrameFormat.GetSurround().GetValue() == text::WrapTextMode_THROUGH; + if (pFrame->IsInTab() && bFollowTextFlow && bWrapThrough) + { + // Ignore wrap-through objects when determining the cell height. + // Normally FollowTextFlow requires a resize of the cell, but not in case of + // wrap-through. + continue; + } + + if ( bConsiderObj ) + { + const SwFormatFrameSize &rSz = rFrameFormat.GetFrameSize(); + if( !rSz.GetHeightPercent() ) + { + const SwTwips nDistOfFlyBottomToAnchorTop = + aRectFnSet.GetHeight(pAnchoredObj->GetObjRect()) + + ( aRectFnSet.IsVert() ? + pAnchoredObj->GetCurrRelPos().X() : + pAnchoredObj->GetCurrRelPos().Y() ); + + const SwTwips nFrameDiff = + aRectFnSet.YDiff( + aRectFnSet.GetTop(pTmp->getFrameArea()), + aRectFnSet.GetTop(pFrame->getFrameArea()) ); + + nHeight = std::max( nHeight, nDistOfFlyBottomToAnchorTop + nFrameDiff - + aRectFnSet.GetHeight(pFrame->getFrameArea()) ); + + // #i56115# The first height calculation + // gives wrong results if pFrame->getFramePrintArea().Y() > 0. We do + // a second calculation based on the actual rectangles of + // pFrame and pAnchoredObj, and use the maximum of the results. + // I do not want to remove the first calculation because + // if clipping has been applied, using the GetCurrRelPos + // might be the better option to calculate nHeight. + const SwTwips nDistOfFlyBottomToAnchorTop2 = aRectFnSet.YDiff( + aRectFnSet.GetBottom(pAnchoredObj->GetObjRect()), + aRectFnSet.GetBottom(pFrame->getFrameArea()) ); + + nHeight = std::max( nHeight, nDistOfFlyBottomToAnchorTop2 ); + } + } + } + } + } + if( !pFrame->IsSctFrame() ) + break; + pTmp = pTmp->FindNextCnt(); + if( !static_cast<const SwSectionFrame*>(pFrame)->IsAnLower( pTmp ) ) + break; + } + return nHeight; +} + +static SwTwips lcl_CalcTopAndBottomMargin( const SwLayoutFrame& rCell, const SwBorderAttrs& rAttrs ) +{ + const SwTabFrame* pTab = rCell.FindTabFrame(); + SwTwips nTopSpace = 0; + SwTwips nBottomSpace = 0; + + // #i29550# + if ( pTab->IsCollapsingBorders() && rCell.Lower() && !rCell.Lower()->IsRowFrame() ) + { + nTopSpace = static_cast<const SwRowFrame*>(rCell.GetUpper())->GetTopMarginForLowers(); + nBottomSpace = static_cast<const SwRowFrame*>(rCell.GetUpper())->GetBottomMarginForLowers(); + } + else + { + if ( pTab->IsVertical() != rCell.IsVertical() ) + { + nTopSpace = rAttrs.CalcLeft( &rCell ); + nBottomSpace = rAttrs.CalcRight( &rCell ); + } + else + { + nTopSpace = rAttrs.CalcTop(); + nBottomSpace = rAttrs.CalcBottom(); + } + } + + return nTopSpace + nBottomSpace; +} + +// #i26945# - add parameter <_bConsiderObjs> in order to +// control, if floating screen objects have to be considered for the minimal +// cell height. +static SwTwips lcl_CalcMinCellHeight( const SwLayoutFrame *_pCell, + const bool _bConsiderObjs, + const SwBorderAttrs *pAttrs = nullptr ) +{ + SwRectFnSet aRectFnSet(_pCell); + SwTwips nHeight = 0; + const SwFrame* pLow = _pCell->Lower(); + if ( pLow ) + { + long nFlyAdd = 0; + while ( pLow ) + { + if ( pLow->IsRowFrame() ) + { + // #i26945# + nHeight += ::lcl_CalcMinRowHeight( static_cast<const SwRowFrame*>(pLow), + _bConsiderObjs ); + } + else + { + long nLowHeight = aRectFnSet.GetHeight(pLow->getFrameArea()); + nHeight += nLowHeight; + // #i26945# + if ( _bConsiderObjs ) + { + nFlyAdd = std::max( 0L, nFlyAdd - nLowHeight ); + nFlyAdd = std::max( nFlyAdd, ::CalcHeightWithFlys( pLow ) ); + } + } + + pLow = pLow->GetNext(); + } + if ( nFlyAdd ) + nHeight += nFlyAdd; + } + // The border/margin needs to be considered too, unfortunately it can't be + // calculated using PrintArea and FrameArea because any or all of those + // may be invalid. + if ( _pCell->Lower() ) + { + if ( pAttrs ) + nHeight += lcl_CalcTopAndBottomMargin( *_pCell, *pAttrs ); + else + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), _pCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nHeight += lcl_CalcTopAndBottomMargin( *_pCell, rAttrs ); + } + } + return nHeight; +} + +// #i26945# - add parameter <_bConsiderObjs> in order to control, +// if floating screen objects have to be considered for the minimal cell height +static SwTwips lcl_CalcMinRowHeight( const SwRowFrame* _pRow, + const bool _bConsiderObjs ) +{ + SwTwips nHeight = 0; + if ( !_pRow->IsRowSpanLine() ) + { + const SwFormatFrameSize &rSz = _pRow->GetFormat()->GetFrameSize(); + if ( _pRow->HasFixSize() ) + { + OSL_ENSURE(SwFrameSize::Fixed == rSz.GetHeightSizeType(), "pRow claims to have fixed size"); + return rSz.GetHeight(); + } + // If this row frame is being split, then row's minimal height shouldn't restrict + // this frame's minimal height, because the rest will go to follow frame. + else if ( !_pRow->IsInSplit() && rSz.GetHeightSizeType() == SwFrameSize::Minimum ) + { + nHeight = rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*_pRow); + } + } + + SwRectFnSet aRectFnSet(_pRow); + const SwCellFrame* pLow = static_cast<const SwCellFrame*>(_pRow->Lower()); + while ( pLow ) + { + SwTwips nTmp = 0; + const long nRowSpan = pLow->GetLayoutRowSpan(); + // --> NEW TABLES + // Consider height of + // 1. current cell if RowSpan == 1 + // 2. current cell if cell is "follow" cell of a cell with RowSpan == -1 + // 3. master cell if RowSpan == -1 + if ( 1 == nRowSpan ) + { + nTmp = ::lcl_CalcMinCellHeight( pLow, _bConsiderObjs ); + } + else if ( -1 == nRowSpan ) + { + // Height of the last cell of a row span is height of master cell + // minus the height of the other rows which are covered by the master + // cell: + const SwCellFrame& rMaster = pLow->FindStartEndOfRowSpanCell( true ); + nTmp = ::lcl_CalcMinCellHeight( &rMaster, _bConsiderObjs ); + const SwFrame* pMasterRow = rMaster.GetUpper(); + while ( pMasterRow && pMasterRow != _pRow ) + { + nTmp -= aRectFnSet.GetHeight(pMasterRow->getFrameArea()); + pMasterRow = pMasterRow->GetNext(); + } + } + // <-- NEW TABLES + + // Do not consider rotated cells: + if ( pLow->IsVertical() == aRectFnSet.IsVert() && nTmp > nHeight ) + nHeight = nTmp; + + pLow = static_cast<const SwCellFrame*>(pLow->GetNext()); + } + + return nHeight; +} + +// #i29550# + +// Calculate the maximum of (TopLineSize + TopLineDist) over all lowers: +static sal_uInt16 lcl_GetTopSpace( const SwRowFrame& rRow ) +{ + sal_uInt16 nTopSpace = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpTopSpace = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + nTmpTopSpace = lcl_GetTopSpace( *static_cast<const SwRowFrame*>(pCurrLower->Lower()) ); + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpTopSpace = rBoxItem.CalcLineSpace( SvxBoxItemLine::TOP, true ); + } + nTopSpace = std::max( nTopSpace, nTmpTopSpace ); + } + return nTopSpace; +} + +// Calculate the maximum of TopLineDist over all lowers: +static sal_uInt16 lcl_GetTopLineDist( const SwRowFrame& rRow ) +{ + sal_uInt16 nTopLineDist = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpTopLineDist = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + nTmpTopLineDist = lcl_GetTopLineDist( *static_cast<const SwRowFrame*>(pCurrLower->Lower()) ); + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpTopLineDist = rBoxItem.GetDistance( SvxBoxItemLine::TOP ); + } + nTopLineDist = std::max( nTopLineDist, nTmpTopLineDist ); + } + return nTopLineDist; +} + +// Calculate the maximum of BottomLineSize over all lowers: +static sal_uInt16 lcl_GetBottomLineSize( const SwRowFrame& rRow ) +{ + sal_uInt16 nBottomLineSize = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpBottomLineSize = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + { + const SwFrame* pRow = pCurrLower->GetLastLower(); + nTmpBottomLineSize = lcl_GetBottomLineSize( *static_cast<const SwRowFrame*>(pRow) ); + } + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpBottomLineSize = rBoxItem.CalcLineSpace( SvxBoxItemLine::BOTTOM, true ) - + rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ); + } + nBottomLineSize = std::max( nBottomLineSize, nTmpBottomLineSize ); + } + return nBottomLineSize; +} + +// Calculate the maximum of BottomLineDist over all lowers: +static sal_uInt16 lcl_GetBottomLineDist( const SwRowFrame& rRow ) +{ + sal_uInt16 nBottomLineDist = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpBottomLineDist = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + { + const SwFrame* pRow = pCurrLower->GetLastLower(); + nTmpBottomLineDist = lcl_GetBottomLineDist( *static_cast<const SwRowFrame*>(pRow) ); + } + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpBottomLineDist = rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ); + } + nBottomLineDist = std::max( nBottomLineDist, nTmpBottomLineDist ); + } + return nBottomLineDist; +} + +// tdf#104425: calculate the height of all row frames, +// for which this frame is a follow. +// When a row has fixed/minimum height, it may span over +// several pages. The minimal height on this page should +// take into account the sum of all the heights of previous +// frames that constitute the table row on previous pages. +// Otherwise, trying to split a too high row frame will +// result in loop trying to create that too high row +// on each following page +static SwTwips lcl_calcHeightOfRowBeforeThisFrame(const SwRowFrame& rRow) +{ + // We don't need to account for previous instances of repeated headlines + if (rRow.IsRepeatedHeadline()) + return 0; + SwRectFnSet aRectFnSet(&rRow); + const SwTableLine* pLine = rRow.GetTabLine(); + const SwTabFrame* pTab = rRow.FindTabFrame(); + if (!pLine || !pTab || !pTab->IsFollow()) + return 0; + SwTwips nResult = 0; + SwIterator<SwRowFrame, SwFormat> aIter(*pLine->GetFrameFormat()); + for (const SwRowFrame* pCurRow = aIter.First(); pCurRow; pCurRow = aIter.Next()) + { + if (pCurRow != &rRow && pCurRow->GetTabLine() == pLine) + { + // We've found another row frame that is part of the same table row + const SwTabFrame* pCurTab = pCurRow->FindTabFrame(); + // A row frame may not belong to a table frame, when it is being cut, e.g., in + // lcl_PostprocessRowsInCells(). + // Its SwRowFrame::Cut() has been called; it in turn called SwLayoutFrame::Cut(), + // which nullified row's upper in RemoveFromLayout(), and then called Shrink() + // for its former upper. + // Regardless of whether it will be pasted back, or destroyed, currently it's not + // part of layout, and its height does not count + if (pCurTab && pCurTab->IsAnFollow(pTab)) + { + // The found row frame belongs to a table frame that precedes + // (above) this one in chain. So, include it in the sum + nResult += aRectFnSet.GetHeight(pCurRow->getFrameArea()); + } + } + } + return nResult; +} + +void SwRowFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + SwRectFnSet aRectFnSet(this); + OSL_ENSURE( pAttrs, "SwRowFrame::Format without Attrs." ); + + const bool bFix = mbFixSize; + + if ( !isFramePrintAreaValid() ) + { + // RowFrames don't have borders/margins therefore the PrintArea always + // matches the FrameArea. + setFramePrintAreaValid(true); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( 0 ); + aPrt.Top( 0 ); + aPrt.Width ( getFrameArea().Width() ); + aPrt.Height( getFrameArea().Height() ); + } + + // #i29550# + // Here we calculate the top-printing area for the lower cell frames + SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->IsCollapsingBorders() ) + { + const sal_uInt16 nTopSpace = lcl_GetTopSpace( *this ); + const sal_uInt16 nTopLineDist = lcl_GetTopLineDist( *this ); + const sal_uInt16 nBottomLineSize = lcl_GetBottomLineSize( *this ); + const sal_uInt16 nBottomLineDist = lcl_GetBottomLineDist( *this ); + + const SwRowFrame* pPreviousRow = nullptr; + + // #i32456# + // In order to calculate the top printing area for the lower cell + // frames, we have to find the 'previous' row frame and compare + // the bottom values of the 'previous' row with the 'top' values + // of this row. The best way to find the 'previous' row is to + // use the table structure: + const SwTable* pTable = pTabFrame->GetTable(); + const SwTableLine* pPrevTabLine = nullptr; + const SwRowFrame* pTmpRow = this; + + while ( pTmpRow && !pPrevTabLine ) + { + size_t nIdx = 0; + const SwTableLines& rLines = pTmpRow->GetTabLine()->GetUpper() ? + pTmpRow->GetTabLine()->GetUpper()->GetTabLines() : + pTable->GetTabLines(); + + while ( rLines[ nIdx ] != pTmpRow->GetTabLine() ) + ++nIdx; + + if ( nIdx > 0 ) + { + // pTmpRow has a 'previous' row in the table structure: + pPrevTabLine = rLines[ nIdx - 1 ]; + } + else + { + // pTmpRow is a first row in the table structure. + // We go up in the table structure: + pTmpRow = pTmpRow->GetUpper()->GetUpper() && + pTmpRow->GetUpper()->GetUpper()->IsRowFrame() ? + static_cast<const SwRowFrame*>( pTmpRow->GetUpper()->GetUpper() ) : + nullptr; + } + } + + // If we found a 'previous' row, we look for the appropriate row frame: + if ( pPrevTabLine ) + { + SwIterator<SwRowFrame,SwFormat> aIter( *pPrevTabLine->GetFrameFormat() ); + for ( SwRowFrame* pRow = aIter.First(); pRow; pRow = aIter.Next() ) + { + // #115759# - do *not* take repeated + // headlines, because during split of table it can be + // invalid and thus can't provide correct border values. + if ( pRow->GetTabLine() == pPrevTabLine && + !pRow->IsRepeatedHeadline() ) + { + pPreviousRow = pRow; + break; + } + } + } + + sal_uInt16 nTopPrtMargin = nTopSpace; + if ( pPreviousRow ) + { + const sal_uInt16 nTmpPrtMargin = pPreviousRow->GetBottomLineSize() + nTopLineDist; + if ( nTmpPrtMargin > nTopPrtMargin ) + nTopPrtMargin = nTmpPrtMargin; + } + + // table has to be notified if it has to change its lower + // margin due to changes of nBottomLineSize: + if ( !GetNext() && nBottomLineSize != GetBottomLineSize() ) + pTabFrame->InvalidatePrt_(); + + // If there are rows nested inside this row, the nested rows + // may not have been calculated yet. Therefore the + // ::lcl_CalcMinRowHeight( this ) operation later in this + // function cannot consider the correct border values. We + // have to trigger the invalidation of the outer row frame + // manually: + // Note: If any further invalidations should be necessary, we + // should consider moving the invalidation stuff to the + // appropriate SwNotify object. + if ( GetUpper()->GetUpper()->IsRowFrame() && + ( nBottomLineDist != GetBottomMarginForLowers() || + nTopPrtMargin != GetTopMarginForLowers() ) ) + GetUpper()->GetUpper()->InvalidateSize_(); + + SetBottomMarginForLowers( nBottomLineDist ); // 3. + SetBottomLineSize( nBottomLineSize ); // 4. + SetTopMarginForLowers( nTopPrtMargin ); // 5. + + } + } + + while ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + +#if OSL_DEBUG_LEVEL > 0 + if ( HasFixSize() ) + { + const SwFormatFrameSize &rFrameSize = GetFormat()->GetFrameSize(); + OSL_ENSURE( rFrameSize.GetSize().Height() > 0, "Has it" ); + } +#endif + const SwTwips nDiff = aRectFnSet.GetHeight(getFrameArea()) - + ( HasFixSize() && !IsRowSpanLine() + ? pAttrs->GetSize().Height() + // #i26945# + : ::lcl_CalcMinRowHeight( this, + FindTabFrame()->IsConsiderObjsForMinCellHeight() ) ); + if ( nDiff ) + { + mbFixSize = false; + if ( nDiff > 0 ) + Shrink( nDiff, false, true ); + else if ( nDiff < 0 ) + Grow( -nDiff ); + mbFixSize = bFix; + } + } + + // last row will fill the space in its upper. + if ( !GetNext() ) + { + //The last fills the remaining space in the upper. + SwTwips nDiff = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); + SwFrame *pSibling = GetUpper()->Lower(); + do + { nDiff -= aRectFnSet.GetHeight(pSibling->getFrameArea()); + pSibling = pSibling->GetNext(); + } while ( pSibling ); + if ( nDiff > 0 ) + { + mbFixSize = false; + Grow( nDiff ); + mbFixSize = bFix; + setFrameAreaSizeValid(true); + } + } +} + +void SwRowFrame::AdjustCells( const SwTwips nHeight, const bool bHeight ) +{ + SwFrame *pFrame = Lower(); + if ( bHeight ) + { + SwRootFrame *pRootFrame = getRootFrame(); + SwRectFnSet aRectFnSet(this); + SwRect aOldFrame; + + while ( pFrame ) + { + SwFrame* pNotify = nullptr; + + SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pFrame); + + // NEW TABLES + // Which cells need to be adjusted if the current row changes + // its height? + + // Current frame is a covered frame: + // Set new height for covered cell and adjust master cell: + if ( pCellFrame->GetTabBox()->getRowSpan() < 1 ) + { + // Set height of current (covered) cell to new line height. + const long nDiff = nHeight - aRectFnSet.GetHeight(pCellFrame->getFrameArea()); + if ( nDiff ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCellFrame); + aRectFnSet.AddBottom( aFrm, nDiff ); + } + + pCellFrame->InvalidatePrt_(); + } + } + + SwCellFrame* pToAdjust = nullptr; + SwFrame* pToAdjustRow = nullptr; + + // If current frame is covered frame, we still want to adjust the + // height of the cell starting the row span + if ( pCellFrame->GetLayoutRowSpan() < 1 ) + { + pToAdjust = const_cast< SwCellFrame*>(&pCellFrame->FindStartEndOfRowSpanCell( true )); + pToAdjustRow = pToAdjust->GetUpper(); + } + else + { + pToAdjust = pCellFrame; + pToAdjustRow = this; + } + + // Set height of master cell to height of all lines spanned by this line. + long nRowSpan = pToAdjust->GetLayoutRowSpan(); + SwTwips nSumRowHeight = 0; + while ( pToAdjustRow ) + { + // Use new height for the current row: + nSumRowHeight += pToAdjustRow == this ? + nHeight : + aRectFnSet.GetHeight(pToAdjustRow->getFrameArea()); + + if ( nRowSpan-- == 1 ) + break; + + pToAdjustRow = pToAdjustRow->GetNext(); + } + + if ( pToAdjustRow && pToAdjustRow != this ) + pToAdjustRow->InvalidateSize_(); + + const long nDiff = nSumRowHeight - aRectFnSet.GetHeight(pToAdjust->getFrameArea()); + if ( nDiff ) + { + aOldFrame = pToAdjust->getFrameArea(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pToAdjust); + aRectFnSet.AddBottom( aFrm, nDiff ); + pNotify = pToAdjust; + } + + if ( pNotify ) + { + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && pRootFrame->GetCurrShell() ) + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( pNotify, aOldFrame ); + + pNotify->InvalidatePrt_(); + } + + pFrame = pFrame->GetNext(); + } + } + else + { while ( pFrame ) + { + pFrame->InvalidateAll_(); + pFrame = pFrame->GetNext(); + } + } + InvalidatePage(); +} + +void SwRowFrame::Cut() +{ + SwTabFrame *pTab = FindTabFrame(); + if ( pTab && pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow() ) + { + pTab->FindMaster()->InvalidatePos(); + } + + SwLayoutFrame::Cut(); +} + +SwTwips SwRowFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwTwips nReal = 0; + + SwTabFrame* pTab = FindTabFrame(); + SwRectFnSet aRectFnSet(pTab); + + bool bRestrictTableGrowth; + bool bHasFollowFlowLine = pTab->HasFollowFlowLine(); + + if ( GetUpper()->IsTabFrame() ) + { + const SwRowFrame* pFollowFlowRow = IsInSplitTableRow(); + bRestrictTableGrowth = pFollowFlowRow && !pFollowFlowRow->IsRowSpanLine(); + } + else + { + OSL_ENSURE( GetUpper()->IsCellFrame(), "RowFrame->GetUpper neither table nor cell" ); + bRestrictTableGrowth = GetFollowRow() && bHasFollowFlowLine; + OSL_ENSURE( !bRestrictTableGrowth || !GetNext(), + "GetFollowRow for row frame that has a Next" ); + + // There may still be some space left in my direct upper: + const SwTwips nAdditionalSpace = + aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()->GetUpper()) ); + if ( bRestrictTableGrowth && nAdditionalSpace > 0 ) + { + nReal = std::min( nAdditionalSpace, nDist ); + nDist -= nReal; + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nReal ); + } + } + } + + if ( bRestrictTableGrowth ) + pTab->SetRestrictTableGrowth( true ); + else + { + // Ok, this looks like a hack, indeed, it is a hack. + // If the current row frame is inside another cell frame, + // and the current row frame has no follow, it should not + // be allowed to grow. In fact, setting bRestrictTableGrowth + // to 'false' does not work, because the surrounding RowFrame + // would set this to 'true'. + pTab->SetFollowFlowLine( false ); + } + + nReal += SwLayoutFrame::GrowFrame( nDist, bTst, bInfo); + + pTab->SetRestrictTableGrowth( false ); + pTab->SetFollowFlowLine( bHasFollowFlowLine ); + + //Update the height of the cells to the newest value. + if ( !bTst ) + { + SwRectFnSet fnRectX(this); + AdjustCells( fnRectX.GetHeight(getFramePrintArea()) + nReal, true ); + if ( nReal ) + SetCompletePaint(); + } + + return nReal; +} + +SwTwips SwRowFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + if( HasFixSize() ) + { + AdjustCells( aRectFnSet.GetHeight(getFramePrintArea()), true ); + return 0; + } + + // bInfo may be set to true by SwRowFrame::Format; we need to handle this + // here accordingly + const bool bShrinkAnyway = bInfo; + + //Only shrink as much as the content of the biggest cell allows. + SwTwips nRealDist = nDist; + SwFormat* pMod = GetFormat(); + if (pMod) + { + const SwFormatFrameSize &rSz = pMod->GetFrameSize(); + SwTwips nMinHeight = 0; + if (rSz.GetHeightSizeType() == SwFrameSize::Minimum) + nMinHeight = std::max(rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*this), + 0L); + + // Only necessary to calculate minimal row height if height + // of pRow is at least nMinHeight. Otherwise nMinHeight is the + // minimum height. + if( nMinHeight < aRectFnSet.GetHeight(getFrameArea()) ) + { + // #i26945# + OSL_ENSURE( FindTabFrame(), "<SwRowFrame::ShrinkFrame(..)> - no table frame -> crash." ); + const bool bConsiderObjs( FindTabFrame()->IsConsiderObjsForMinCellHeight() ); + nMinHeight = lcl_CalcMinRowHeight( this, bConsiderObjs ); + } + + if ( (aRectFnSet.GetHeight(getFrameArea()) - nRealDist) < nMinHeight ) + nRealDist = aRectFnSet.GetHeight(getFrameArea()) - nMinHeight; + } + if ( nRealDist < 0 ) + nRealDist = 0; + + SwTwips nReal = nRealDist; + if ( nReal ) + { + if ( !bTst ) + { + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nHeight - nReal ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nReal ); + } + } + + SwLayoutFrame* pFrame = GetUpper(); + SwTwips nTmp = pFrame ? pFrame->Shrink(nReal, bTst) : 0; + if ( !bShrinkAnyway && !GetNext() && nTmp != nReal ) + { + //The last one gets the leftover in the upper and therefore takes + //care (otherwise: endless loop) + if ( !bTst ) + { + nReal -= nTmp; + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nHeight + nReal ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nReal ); + } + } + nReal = nTmp; + } + } + + // Invalidate appropriately and update the height to the newest value. + if ( !bTst ) + { + if ( nReal ) + { + if ( GetNext() ) + GetNext()->InvalidatePos_(); + InvalidateAll_(); + SetCompletePaint(); + + SwTabFrame *pTab = FindTabFrame(); + if ( !pTab->IsRebuildLastLine() + && pTab->IsFollow() + && this == pTab->GetFirstNonHeadlineRow() + && !pTab->IsInRecalcLowerRow() ) + { + SwTabFrame* pMasterTab = pTab->FindMaster(); + pMasterTab->InvalidatePos(); + } + } + AdjustCells( aRectFnSet.GetHeight(getFramePrintArea()) - nReal, true ); + } + return nReal; +} + +bool SwRowFrame::IsRowSplitAllowed() const +{ + // Fixed size rows are never allowed to split: + if ( HasFixSize() ) + { + OSL_ENSURE( SwFrameSize::Fixed == GetFormat()->GetFrameSize().GetHeightSizeType(), "pRow claims to have fixed size" ); + return false; + } + + // Repeated headlines are never allowed to split: + const SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->GetTable()->GetRowsToRepeat() > 0 && + pTabFrame->IsInHeadline( *this ) ) + return false; + + if ( IsForceRowSplitAllowed() ) + return true; + + const SwTableLineFormat* pFrameFormat = static_cast<SwTableLineFormat*>(GetTabLine()->GetFrameFormat()); + const SwFormatRowSplit& rLP = pFrameFormat->GetRowSplit(); + return rLP.GetValue(); +} + +bool SwRowFrame::ShouldRowKeepWithNext( const bool bCheckParents ) const +{ + // No KeepWithNext if nested in another table + if ( GetUpper()->GetUpper()->IsCellFrame() ) + return false; + + const SwCellFrame* pCell = static_cast<const SwCellFrame*>(Lower()); + const SwFrame* pText = pCell->Lower(); + + return pText && pText->IsTextFrame() && + static_cast<const SwTextFrame*>(pText)->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep(bCheckParents).GetValue(); +} + +SwCellFrame::SwCellFrame(const SwTableBox &rBox, SwFrame* pSib, bool bInsertContent) + : SwLayoutFrame( rBox.GetFrameFormat(), pSib ) + , m_pTabBox( &rBox ) +{ + mnFrameType = SwFrameType::Cell; + + if ( !bInsertContent ) + return; + + //If a StartIdx is available, ContentFrames are added in the cell, otherwise + //Rows have to be present and those are added. + if ( rBox.GetSttIdx() ) + { + sal_uLong nIndex = rBox.GetSttIdx(); + ::InsertCnt_( this, rBox.GetFrameFormat()->GetDoc(), ++nIndex ); + } + else + { + const SwTableLines &rLines = rBox.GetTabLines(); + SwFrame *pTmpPrev = nullptr; + for ( size_t i = 0; i < rLines.size(); ++i ) + { + SwRowFrame *pNew = new SwRowFrame( *rLines[i], this, bInsertContent ); + pNew->InsertBehind( this, pTmpPrev ); + pTmpPrev = pNew; + } + } +} + +void SwCellFrame::DestroyImpl() +{ + SwModify* pMod = GetFormat(); + if( pMod ) + { + // At this stage the lower frames aren't destroyed already, + // therefore we have to do a recursive dispose. + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->DisposeAccessibleFrame( this, true ); + } + + pMod->Remove( this ); + if( !pMod->HasWriterListeners() ) + delete pMod; + } + + SwLayoutFrame::DestroyImpl(); +} + +SwCellFrame::~SwCellFrame() +{ +} + +static bool lcl_ArrangeLowers( SwLayoutFrame *pLay, long lYStart, bool bInva ) +{ + bool bRet = false; + SwFrame *pFrame = pLay->Lower(); + SwRectFnSet aRectFnSet(pLay); + while ( pFrame ) + { + long nFrameTop = aRectFnSet.GetTop(pFrame->getFrameArea()); + if( nFrameTop != lYStart ) + { + bRet = true; + const long lDiff = aRectFnSet.YDiff( lYStart, nFrameTop ); + const long lDiffX = lYStart - nFrameTop; + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); + aRectFnSet.SubTop( aFrm, -lDiff ); + aRectFnSet.AddBottom( aFrm, lDiff ); + } + + pFrame->SetCompletePaint(); + + if ( !pFrame->GetNext() ) + pFrame->SetRetouche(); + if( bInva ) + pFrame->Prepare( PrepareHint::FramePositionChanged ); + if ( pFrame->IsLayoutFrame() && static_cast<SwLayoutFrame*>(pFrame)->Lower() ) + lcl_ArrangeLowers( static_cast<SwLayoutFrame*>(pFrame), + aRectFnSet.GetTop(static_cast<SwLayoutFrame*>(pFrame)->Lower()->getFrameArea()) + + lDiffX, bInva ); + if ( pFrame->GetDrawObjs() ) + { + for ( size_t i = 0; i < pFrame->GetDrawObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + // #i26945# - check, if anchored object + // is lower of layout frame by checking, if the anchor + // frame, which contains the anchor position, is a lower + // of the layout frame. + if ( !pLay->IsAnLower( pAnchoredObj->GetAnchorFrameContainingAnchPos() ) ) + { + continue; + } + // #i52904# - distinguish between anchored + // objects, whose vertical position depends on its anchor + // frame and whose vertical position is independent + // from its anchor frame. + bool bVertPosDepOnAnchor( true ); + { + SwFormatVertOrient aVert( pAnchoredObj->GetFrameFormat().GetVertOrient() ); + switch ( aVert.GetRelationOrient() ) + { + case text::RelOrientation::PAGE_FRAME: + case text::RelOrientation::PAGE_PRINT_AREA: + bVertPosDepOnAnchor = false; + break; + default: break; + } + } + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + + // OD 2004-05-18 #i28701# - no direct move of objects, + // which are anchored to-paragraph/to-character, if + // the wrapping style influence has to be considered + // on the object positioning. + // #i52904# - no direct move of objects, + // whose vertical position doesn't depend on anchor frame. + const bool bDirectMove = + FAR_AWAY != pFly->getFrameArea().Top() && + bVertPosDepOnAnchor && + !pFly->ConsiderObjWrapInfluenceOnObjPos(); + if ( bDirectMove ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly); + aRectFnSet.SubTop( aFrm, -lDiff ); + aRectFnSet.AddBottom( aFrm, lDiff ); + } + + pFly->GetVirtDrawObj()->SetRectsDirty(); + // --> OD 2004-08-17 - also notify view of <SdrObject> + // instance, which represents the Writer fly frame in + // the drawing layer + pFly->GetVirtDrawObj()->SetChanged(); + // #i58280# + pFly->InvalidateObjRectWithSpaces(); + } + + if ( pFly->IsFlyInContentFrame() ) + { + static_cast<SwFlyInContentFrame*>(pFly)->AddRefOfst( lDiff ); + // #115759# - reset current relative + // position to get re-positioned, if not directly moved. + if ( !bDirectMove ) + { + pAnchoredObj->SetCurrRelPos( Point( 0, 0 ) ); + } + } + else if( pFly->IsAutoPos() ) + { + pFly->AddLastCharY( lDiff ); + // OD 2004-05-18 #i28701# - follow-up of #i22341# + // <mnLastTopOfLine> has also been adjusted. + pFly->AddLastTopOfLineY( lDiff ); + } + // #i26945# - re-registration at + // page frame of anchor frame, if table frame isn't + // a follow table and table frame isn't in its + // rebuild of last line. + const SwTabFrame* pTabFrame = pLay->FindTabFrame(); + // - save: check, if table frame is found. + if ( pTabFrame && + !( pTabFrame->IsFollow() && + pTabFrame->FindMaster()->IsRebuildLastLine() ) && + pFly->IsFlyFreeFrame() ) + { + SwPageFrame* pPageFrame = pFly->GetPageFrame(); + SwPageFrame* pPageOfAnchor = pFrame->FindPageFrame(); + if ( pPageFrame != pPageOfAnchor ) + { + pFly->InvalidatePos(); + if ( pPageFrame ) + pPageFrame->MoveFly( pFly, pPageOfAnchor ); + else + pPageOfAnchor->AppendFlyToPage( pFly ); + } + } + // OD 2004-05-11 #i28701# - Because of the introduction + // of new positionings and alignments (e.g. aligned at + // page area, but anchored at-character), the position + // of the Writer fly frame has to be invalidated. + pFly->InvalidatePos(); + + // #i26945# - follow-up of #i3317# + // No arrangement of lowers, if Writer fly frame isn't + // moved + if ( bDirectMove && + ::lcl_ArrangeLowers( pFly, + aRectFnSet.GetPrtTop(*pFly), + bInva ) ) + { + pFly->SetCompletePaint(); + } + } + else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr ) + { + // #i26945# + const SwTabFrame* pTabFrame = pLay->FindTabFrame(); + if ( pTabFrame && + !( pTabFrame->IsFollow() && + pTabFrame->FindMaster()->IsRebuildLastLine() ) && + (pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + != RndStdIds::FLY_AS_CHAR)) + { + SwPageFrame* pPageFrame = pAnchoredObj->GetPageFrame(); + SwPageFrame* pPageOfAnchor = pFrame->FindPageFrame(); + if ( pPageFrame != pPageOfAnchor ) + { + pAnchoredObj->InvalidateObjPos(); + if ( pPageFrame ) + { + pPageFrame->RemoveDrawObjFromPage( *pAnchoredObj ); + } + pPageOfAnchor->AppendDrawObjToPage( *pAnchoredObj ); + } + } + // #i28701# - adjust last character + // rectangle and last top of line. + pAnchoredObj->AddLastCharY( lDiff ); + pAnchoredObj->AddLastTopOfLineY( lDiff ); + // #i52904# - re-introduce direct move + // of drawing objects + const bool bDirectMove = + static_cast<const SwDrawFrameFormat&>(pAnchoredObj->GetFrameFormat()).IsPosAttrSet() && + bVertPosDepOnAnchor && + !pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos(); + if ( bDirectMove ) + { + SwObjPositioningInProgress aObjPosInProgress( *pAnchoredObj ); + if ( aRectFnSet.IsVert() ) + { + pAnchoredObj->DrawObj()->Move( Size( lDiff, 0 ) ); + } + else + { + pAnchoredObj->DrawObj()->Move( Size( 0, lDiff ) ); + } + // #i58280# + pAnchoredObj->InvalidateObjRectWithSpaces(); + } + pAnchoredObj->InvalidateObjPos(); + } + else + { + OSL_FAIL( "<lcl_ArrangeLowers(..)> - unknown type of anchored object!" ); + } + } + } + } + // Columns and cells are ordered horizontal, not vertical + if( !pFrame->IsColumnFrame() && !pFrame->IsCellFrame() ) + lYStart = aRectFnSet.YInc( lYStart, + aRectFnSet.GetHeight(pFrame->getFrameArea()) ); + + // Nowadays, the content inside a cell can flow into the follow table. + // Thus, the cell may only grow up to the end of the environment. + // So the content may have grown, but the cell could not grow. + // Therefore we have to trigger a formatting for the frames, which do + // not fit into the cell anymore: + SwTwips nDistanceToUpperPrtBottom = + aRectFnSet.BottomDist( pFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*pLay) ); + // #i56146# - Revise fix of issue #i26945# + // do *not* consider content inside fly frames, if it's an undersized paragraph. + // #i26945# - consider content inside fly frames + if ( nDistanceToUpperPrtBottom < 0 && + ( ( pFrame->IsInFly() && + ( !pFrame->IsTextFrame() || + !static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) ) || + pFrame->IsInSplitTableRow() ) ) + { + pFrame->InvalidatePos(); + } + + pFrame = pFrame->GetNext(); + } + return bRet; +} + +void SwCellFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "CellFrame::Format, pAttrs is 0." ); + const SwTabFrame* pTab = FindTabFrame(); + SwRectFnSet aRectFnSet(pTab); + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + + //Adjust position. + if ( Lower() ) + { + SwTwips nTopSpace, nBottomSpace, nLeftSpace, nRightSpace; + // #i29550# + if ( pTab->IsCollapsingBorders() && !Lower()->IsRowFrame() ) + { + const SvxBoxItem& rBoxItem = pAttrs->GetBox(); + nLeftSpace = rBoxItem.GetDistance( SvxBoxItemLine::LEFT ); + nRightSpace = rBoxItem.GetDistance( SvxBoxItemLine::RIGHT ); + nTopSpace = static_cast<SwRowFrame*>(GetUpper())->GetTopMarginForLowers(); + nBottomSpace = static_cast<SwRowFrame*>(GetUpper())->GetBottomMarginForLowers(); + } + else + { + // OD 23.01.2003 #106895# - add 1st param to <SwBorderAttrs::CalcRight(..)> + nLeftSpace = pAttrs->CalcLeft( this ); + nRightSpace = pAttrs->CalcRight( this ); + nTopSpace = pAttrs->CalcTop(); + nBottomSpace = pAttrs->CalcBottom(); + } + aRectFnSet.SetXMargins( *this, nLeftSpace, nRightSpace ); + aRectFnSet.SetYMargins( *this, nTopSpace, nBottomSpace ); + } + } + // #i26945# + long nRemaining = GetTabBox()->getRowSpan() >= 1 ? + ::lcl_CalcMinCellHeight( this, pTab->IsConsiderObjsForMinCellHeight(), pAttrs ) : + 0; + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + + //The VarSize of the CellFrames is always the width. + //The width is not variable though, it is defined by the format. + //This predefined value however does not necessary match the actual + //width. The width is calculated based on the attribute, the value in + //the attribute matches the desired value of the TabFrame. Changes which + //were done there are taken into account here proportionately. + //If the cell doesn't have a neighbour anymore, it does not take the + //attribute into account and takes the rest of the upper instead. + SwTwips nWidth; + if ( GetNext() ) + { + const SwTwips nWish = pTab->GetFormat()->GetFrameSize().GetWidth(); + nWidth = pAttrs->GetSize().Width(); + + OSL_ENSURE( nWish, "Table without width?" ); + OSL_ENSURE( nWidth <= nWish, "Width of cell larger than table." ); + OSL_ENSURE( nWidth > 0, "Box without width" ); + + const long nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + if ( nWish != nPrtWidth ) + { + // Avoid rounding problems, at least for the new table model + if ( pTab->GetTable()->IsNewModel() ) + { + // 1. sum of widths of cells up to this cell (in model) + const SwTableLine* pTabLine = GetTabBox()->GetUpper(); + const SwTableBoxes& rBoxes = pTabLine->GetTabBoxes(); + const SwTableBox* pTmpBox = nullptr; + + SwTwips nSumWidth = 0; + size_t i = 0; + do + { + pTmpBox = rBoxes[ i++ ]; + nSumWidth += pTmpBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + while ( pTmpBox != GetTabBox() ); + + // 2. calculate actual width of cells up to this one + double nTmpWidth = nSumWidth; + nTmpWidth *= nPrtWidth; + nTmpWidth /= nWish; + nWidth = static_cast<SwTwips>(nTmpWidth); + + // 3. calculate frame widths of cells up to this one: + const SwFrame* pTmpCell = static_cast<const SwLayoutFrame*>(GetUpper())->Lower(); + SwTwips nSumFrameWidths = 0; + while ( pTmpCell != this ) + { + nSumFrameWidths += aRectFnSet.GetWidth(pTmpCell->getFrameArea()); + pTmpCell = pTmpCell->GetNext(); + } + + nWidth = nWidth - nSumFrameWidths; + } + else + { + // #i12092# use double instead of long, + // otherwise this could lead to overflows + double nTmpWidth = nWidth; + nTmpWidth *= nPrtWidth; + nTmpWidth /= nWish; + nWidth = static_cast<SwTwips>(nTmpWidth); + } + } + } + else + { + OSL_ENSURE( pAttrs->GetSize().Width() > 0, "Box without width" ); + nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + SwFrame *pPre = GetUpper()->Lower(); + while ( pPre != this ) + { + nWidth -= aRectFnSet.GetWidth(pPre->getFrameArea()); + pPre = pPre->GetNext(); + } + } + + const long nDiff = nWidth - aRectFnSet.GetWidth(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( IsNeighbourFrame() && IsRightToLeft() ) + { + aRectFnSet.SubLeft( aFrm, nDiff ); + } + else + { + aRectFnSet.AddRight( aFrm, nDiff ); + } + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.AddRight( aPrt, nDiff ); + } + + //Adjust the height, it's defined through the content and the margins. + const long nDiffHeight = nRemaining - aRectFnSet.GetHeight(getFrameArea()); + if ( nDiffHeight ) + { + if ( nDiffHeight > 0 ) + { + //Validate again if no growth happened. Invalidation is done + //through AdjustCells of the row. + if ( !Grow( nDiffHeight ) ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + } + else + { + // Only keep invalidated if shrinking was actually done; the + // attempt can be ignored because all horizontally adjoined + // cells have to be the same height. + if ( !Shrink( -nDiffHeight ) ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + } + } + } + const SwFormatVertOrient &rOri = pAttrs->GetAttrSet().GetVertOrient(); + + if ( !Lower() ) + return; + + // From now on, all operations are related to the table cell. + aRectFnSet.Refresh(this); + + SwPageFrame* pPg = nullptr; + if ( !FindTabFrame()->IsRebuildLastLine() && text::VertOrientation::NONE != rOri.GetVertOrient() && + // #158225# no vertical alignment of covered cells + !IsCoveredCell() && + (pPg = FindPageFrame())!=nullptr ) + { + if ( !Lower()->IsContentFrame() && !Lower()->IsSctFrame() && !Lower()->IsTabFrame() ) + { + // OSL_ENSURE(for HTML-import! + OSL_ENSURE( false, "VAlign to cell without content" ); + return; + } + bool bVertDir = true; + // #i43913# - no vertical alignment, if wrapping + // style influence is considered on object positioning and + // an object is anchored inside the cell. + const bool bConsiderWrapOnObjPos( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ); + // No alignment if fly with wrap overlaps the cell. + if ( pPg->GetSortedObjs() ) + { + SwRect aRect( getFramePrintArea() ); aRect += getFrameArea().Pos(); + for (SwAnchoredObject* pAnchoredObj : *pPg->GetSortedObjs()) + { + SwRect aTmp( pAnchoredObj->GetObjRect() ); + const SwFrame* pAnch = pAnchoredObj->GetAnchorFrame(); + if ( (bConsiderWrapOnObjPos && IsAnLower( pAnch )) || (!bConsiderWrapOnObjPos && aTmp.IsOver( aRect )) ) + { + const SwFrameFormat& rAnchoredObjFrameFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatSurround &rSur = rAnchoredObjFrameFormat.GetSurround(); + + if ( bConsiderWrapOnObjPos || css::text::WrapTextMode_THROUGH != rSur.GetSurround() ) + { + // frames, which the cell is a lower of, aren't relevant + if ( auto pFly = dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) ) + { + if ( pFly->IsAnLower( this ) ) + continue; + } + + // #i43913# + // #i52904# - no vertical alignment, + // if object, anchored inside cell, has temporarily + // consider its wrapping style on object positioning. + // #i58806# - no vertical alignment + // if object does not follow the text flow. + if ( bConsiderWrapOnObjPos || + !IsAnLower( pAnch ) || + pAnchoredObj->IsTmpConsiderWrapInfluence() || + !rAnchoredObjFrameFormat.GetFollowTextFlow().GetValue() ) + { + bVertDir = false; + break; + } + } + } + } + } + + long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( ( bVertDir && ( nRemaining -= lcl_CalcTopAndBottomMargin( *this, *pAttrs ) ) < nPrtHeight ) || + aRectFnSet.GetTop(Lower()->getFrameArea()) != aRectFnSet.GetPrtTop(*this) ) + { + long nDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nRemaining; + if ( nDiff >= 0 ) + { + long lTopOfst = 0; + if ( bVertDir ) + { + switch ( rOri.GetVertOrient() ) + { + case text::VertOrientation::CENTER: lTopOfst = nDiff / 2; break; + case text::VertOrientation::BOTTOM: lTopOfst = nDiff; break; + default: break; + } + } + long nTmp = aRectFnSet.YInc( + aRectFnSet.GetPrtTop(*this), lTopOfst ); + if ( lcl_ArrangeLowers( this, nTmp, !bVertDir ) ) + SetCompletePaint(); + } + } + } + else + { + //Was an old alignment taken into account? + if ( Lower()->IsContentFrame() ) + { + const long lYStart = aRectFnSet.GetPrtTop(*this); + lcl_ArrangeLowers( this, lYStart, true ); + } + } + + // Handle rotated portions of lowers: it's possible that we have changed amount of vertical + // space since the last format, and this affects how many rotated portions we need. So throw + // away the current portions to build them using the new line width. + for (SwFrame* pFrame = Lower(); pFrame; pFrame = pFrame->GetNext()) + { + if (!pFrame->IsTextFrame()) + { + continue; + } + + auto pTextFrame = static_cast<SwTextFrame*>(pFrame); + if (!pTextFrame->GetHasRotatedPortions()) + { + continue; + } + + pTextFrame->Prepare(); + } +} + +void SwCellFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + bool bAttrSetChg = pNew && RES_ATTRSET_CHG == pNew->Which(); + const SfxPoolItem *pItem = nullptr; + + if( bAttrSetChg ) + static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( RES_VERT_ORIENT, false, &pItem); + else if (pNew && RES_VERT_ORIENT == pNew->Which()) + pItem = pNew; + + if ( pItem ) + { + bool bInva = true; + if ( text::VertOrientation::NONE == static_cast<const SwFormatVertOrient*>(pItem)->GetVertOrient() && + // OD 04.11.2003 #112910# + Lower() && Lower()->IsContentFrame() ) + { + SwRectFnSet aRectFnSet(this); + const long lYStart = aRectFnSet.GetPrtTop(*this); + bInva = lcl_ArrangeLowers( this, lYStart, false ); + } + if ( bInva ) + { + SetCompletePaint(); + InvalidatePrt(); + } + } + + if ( ( bAttrSetChg && + SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( RES_PROTECT, false ) ) || + ( pNew && RES_PROTECT == pNew->Which()) ) + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetLayout()->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleEditableState( true, this ); + } + + if ( bAttrSetChg && + SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( RES_FRAMEDIR, false, &pItem ) ) + { + SetDerivedVert( false ); + CheckDirChange(); + } + + // #i29550# + if ( bAttrSetChg && + SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( RES_BOX, false, &pItem ) ) + { + SwFrame* pTmpUpper = GetUpper(); + while ( pTmpUpper->GetUpper() && !pTmpUpper->GetUpper()->IsTabFrame() ) + pTmpUpper = pTmpUpper->GetUpper(); + + SwTabFrame* pTabFrame = static_cast<SwTabFrame*>(pTmpUpper->GetUpper()); + if ( pTabFrame->IsCollapsingBorders() ) + { + // Invalidate lowers of this and next row: + lcl_InvalidateAllLowersPrt( static_cast<SwRowFrame*>(pTmpUpper) ); + pTmpUpper = pTmpUpper->GetNext(); + if ( pTmpUpper ) + lcl_InvalidateAllLowersPrt( static_cast<SwRowFrame*>(pTmpUpper) ); + else + pTabFrame->InvalidatePrt(); + } + } + + SwLayoutFrame::Modify( pOld, pNew ); +} + +long SwCellFrame::GetLayoutRowSpan() const +{ + long nRet = GetTabBox()->getRowSpan(); + if ( nRet < 1 ) + { + const SwFrame* pRow = GetUpper(); + const SwTabFrame* pTab = pRow ? static_cast<const SwTabFrame*>(pRow->GetUpper()) : nullptr; + + if ( pTab && pTab->IsFollow() && pRow == pTab->GetFirstNonHeadlineRow() ) + nRet = -nRet; + } + return nRet; +} + +void SwCellFrame::dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const +{ + SwFrame::dumpAsXmlAttributes(pWriter); + if (SwCellFrame* pFollow = GetFollowCell()) + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("follow"), "%" SAL_PRIuUINT32, pFollow->GetFrameId()); + + if (SwCellFrame* pPrevious = GetPreviousCell()) + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("precede"), "%" SAL_PRIuUINT32, pPrevious->GetFrameId()); +} + +// #i103961# +void SwCellFrame::Cut() +{ + // notification for accessibility + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell* pVSh = pRootFrame->GetCurrShell(); + if ( pVSh && pVSh->Imp() ) + { + pVSh->Imp()->DisposeAccessibleFrame( this ); + } + } + } + + SwLayoutFrame::Cut(); +} + +// Helper functions for repeated headlines: + +bool SwTabFrame::IsInHeadline( const SwFrame& rFrame ) const +{ + OSL_ENSURE( IsAnLower( &rFrame ) && rFrame.IsInTab(), + "SwTabFrame::IsInHeadline called for frame not lower of table" ); + + const SwFrame* pTmp = &rFrame; + while ( !pTmp->GetUpper()->IsTabFrame() ) + pTmp = pTmp->GetUpper(); + + return GetTable()->IsHeadline( *static_cast<const SwRowFrame*>(pTmp)->GetTabLine() ); +} + +/* + * If this is a master table, we can may assume, that there are at least + * nRepeat lines in the table. + * If this is a follow table, there are intermediate states for the table + * layout, e.g., during deletion of rows, which makes it necessary to find + * the first non-headline row by evaluating the headline flag at the row frame. + */ +SwRowFrame* SwTabFrame::GetFirstNonHeadlineRow() const +{ + SwRowFrame* pRet = const_cast<SwRowFrame*>(static_cast<const SwRowFrame*>(Lower())); + if ( pRet ) + { + if ( IsFollow() ) + { + while ( pRet && pRet->IsRepeatedHeadline() ) + pRet = static_cast<SwRowFrame*>(pRet->GetNext()); + } + else + { + sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + while ( pRet && nRepeat > 0 ) + { + pRet = static_cast<SwRowFrame*>(pRet->GetNext()); + --nRepeat; + } + } + } + + return pRet; +} + +bool SwTable::IsHeadline( const SwTableLine& rLine ) const +{ + for ( sal_uInt16 i = 0; i < GetRowsToRepeat(); ++i ) + if ( GetTabLines()[ i ] == &rLine ) + return true; + + return false; +} + +bool SwTabFrame::IsLayoutSplitAllowed() const +{ + return GetFormat()->GetLayoutSplit().GetValue(); +} + +// #i29550# + +sal_uInt16 SwTabFrame::GetBottomLineSize() const +{ + OSL_ENSURE( IsCollapsingBorders(), + "BottomLineSize only required for collapsing borders" ); + + OSL_ENSURE( Lower(), "Warning! Trying to prevent a crash" ); + + const SwFrame* pTmp = GetLastLower(); + + // #124755# Try to make code robust + if ( !pTmp ) return 0; + + return static_cast<const SwRowFrame*>(pTmp)->GetBottomLineSize(); +} + +bool SwTabFrame::IsCollapsingBorders() const +{ + return GetFormat()->GetAttrSet().Get( RES_COLLAPSING_BORDERS ).GetValue(); +} + +/// Local helper function to calculate height of first text row +static SwTwips lcl_CalcHeightOfFirstContentLine( const SwRowFrame& rSourceLine ) +{ + // Find corresponding split line in master table + const SwTabFrame* pTab = rSourceLine.FindTabFrame(); + SwRectFnSet aRectFnSet(pTab); + const SwCellFrame* pCurrSourceCell = static_cast<const SwCellFrame*>(rSourceLine.Lower()); + + // 1. Case: rSourceLine is a follow flow line. + // In this case we have to return the minimum of the heights + // of the first lines in rSourceLine. + + // 2. Case: rSourceLine is not a follow flow line. + // In this case we have to return the maximum of the heights + // of the first lines in rSourceLine. + bool bIsInFollowFlowLine = rSourceLine.IsInFollowFlowRow(); + SwTwips nHeight = bIsInFollowFlowLine ? LONG_MAX : 0; + + while ( pCurrSourceCell ) + { + // NEW TABLES + // Skip cells which are not responsible for the height of + // the follow flow line: + if ( bIsInFollowFlowLine && pCurrSourceCell->GetLayoutRowSpan() > 1 ) + { + pCurrSourceCell = static_cast<const SwCellFrame*>(pCurrSourceCell->GetNext()); + continue; + } + + const SwFrame *pTmp = pCurrSourceCell->Lower(); + if ( pTmp ) + { + SwTwips nTmpHeight = USHRT_MAX; + // #i32456# Consider lower row frames + if ( pTmp->IsRowFrame() ) + { + const SwRowFrame* pTmpSourceRow = static_cast<const SwRowFrame*>(pCurrSourceCell->Lower()); + nTmpHeight = lcl_CalcHeightOfFirstContentLine( *pTmpSourceRow ); + } + else if ( pTmp->IsTabFrame() ) + { + nTmpHeight = static_cast<const SwTabFrame*>(pTmp)->CalcHeightOfFirstContentLine(); + } + else if (pTmp->IsTextFrame() || (pTmp->IsSctFrame() && pTmp->GetLower() && pTmp->GetLower()->IsTextFrame())) + { + // Section frames don't influence the size/position of text + // frames, so 'text frame' and 'text frame in section frame' is + // the same case. + SwTextFrame* pTextFrame = nullptr; + if (pTmp->IsTextFrame()) + pTextFrame = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pTmp)); + else + pTextFrame = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pTmp->GetLower())); + pTextFrame->GetFormatted(); + nTmpHeight = pTextFrame->FirstLineHeight(); + } + + if ( USHRT_MAX != nTmpHeight ) + { + const SwCellFrame* pPrevCell = pCurrSourceCell->GetPreviousCell(); + if ( pPrevCell ) + { + // If we are in a split row, there may be some space + // left in the cell frame of the master row. + // We look for the minimum of all first line heights; + SwTwips nReal = aRectFnSet.GetHeight(pPrevCell->getFramePrintArea()); + const SwFrame* pFrame = pPrevCell->Lower(); + const SwFrame* pLast = pFrame; + while ( pFrame ) + { + nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); + pLast = pFrame; + pFrame = pFrame->GetNext(); + } + + // #i26831#, #i26520# + // The additional lower space of the current last. + // #115759# - do *not* consider the + // additional lower space for 'master' text frames + if ( pLast && pLast->IsFlowFrame() && + ( !pLast->IsTextFrame() || + !static_cast<const SwTextFrame*>(pLast)->GetFollow() ) ) + { + nReal += SwFlowFrame::CastFlowFrame(pLast)->CalcAddLowerSpaceAsLastInTableCell(); + } + // Don't forget the upper space and lower space, + // #115759# - do *not* consider the upper + // and the lower space for follow text frames. + if ( pTmp->IsFlowFrame() && + ( !pTmp->IsTextFrame() || + !static_cast<const SwTextFrame*>(pTmp)->IsFollow() ) ) + { + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcUpperSpace( nullptr, pLast); + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcLowerSpace(); + } + // #115759# - consider additional lower + // space of <pTmp>, if contains only one line. + // In this case it would be the new last text frame, which + // would have no follow and thus would add this space. + if ( pTmp->IsTextFrame() && + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pTmp)) + ->GetLineCount(TextFrameIndex(COMPLETE_STRING)) == 1) + { + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp) + ->CalcAddLowerSpaceAsLastInTableCell(); + } + if ( nReal > 0 ) + nTmpHeight -= nReal; + } + else + { + // pFirstRow is not a FollowFlowRow. In this case, + // we look for the maximum of all first line heights: + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCurrSourceCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nTmpHeight += rAttrs.CalcTop() + rAttrs.CalcBottom(); + // #i26250# + // Don't forget the upper space and lower space, + if ( pTmp->IsFlowFrame() ) + { + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcUpperSpace(); + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcLowerSpace(); + } + } + } + + if ( bIsInFollowFlowLine ) + { + // minimum + if ( nTmpHeight < nHeight ) + nHeight = nTmpHeight; + } + else + { + // maximum + if ( nTmpHeight > nHeight && USHRT_MAX != nTmpHeight ) + nHeight = nTmpHeight; + } + } + + pCurrSourceCell = static_cast<const SwCellFrame*>(pCurrSourceCell->GetNext()); + } + + return ( LONG_MAX == nHeight ) ? 0 : nHeight; +} + +/// Function to calculate height of first text row +SwTwips SwTabFrame::CalcHeightOfFirstContentLine() const +{ + SwRectFnSet aRectFnSet(this); + + const bool bDontSplit = !IsFollow() && !GetFormat()->GetLayoutSplit().GetValue(); + + if ( bDontSplit ) + { + // Table is not allowed to split: Take the whole height, that's all + return aRectFnSet.GetHeight(getFrameArea()); + } + + SwTwips nTmpHeight = 0; + + const SwRowFrame* pFirstRow = GetFirstNonHeadlineRow(); + OSL_ENSURE( !IsFollow() || pFirstRow, "FollowTable without Lower" ); + + // NEW TABLES + if ( pFirstRow && pFirstRow->IsRowSpanLine() && pFirstRow->GetNext() ) + pFirstRow = static_cast<const SwRowFrame*>(pFirstRow->GetNext()); + + // Calculate the height of the headlines: + const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + SwTwips nRepeatHeight = nRepeat ? lcl_GetHeightOfRows( GetLower(), nRepeat ) : 0; + + // Calculate the height of the keeping lines + // (headlines + following keeping lines): + SwTwips nKeepHeight = nRepeatHeight; + if ( GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP) ) + { + sal_uInt16 nKeepRows = nRepeat; + + // Check how many rows want to keep together + while ( pFirstRow && pFirstRow->ShouldRowKeepWithNext() ) + { + ++nKeepRows; + pFirstRow = static_cast<const SwRowFrame*>(pFirstRow->GetNext()); + } + + if ( nKeepRows > nRepeat ) + nKeepHeight = lcl_GetHeightOfRows( GetLower(), nKeepRows ); + } + + // For master tables, the height of the headlines + the height of the + // keeping lines (if any) has to be considered. For follow tables, we + // only consider the height of the keeping rows without the repeated lines: + if ( !IsFollow() ) + { + nTmpHeight = nKeepHeight; + } + else + { + nTmpHeight = nKeepHeight - nRepeatHeight; + } + + // pFirstRow row is the first non-heading row. + // nTmpHeight is the height of the heading row if we are a follow. + if ( pFirstRow ) + { + const bool bSplittable = pFirstRow->IsRowSplitAllowed(); + const SwTwips nFirstLineHeight = aRectFnSet.GetHeight(pFirstRow->getFrameArea()); + + if ( !bSplittable ) + { + // pFirstRow is not splittable, but it is still possible that the line height of pFirstRow + // actually is determined by a lower cell with rowspan = -1. In this case we should not + // just return the height of the first line. Basically we need to get the height of the + // line as it would be on the last page. Since this is quite complicated to calculate, + // we only calculate the height of the first line. + SwFormatFrameSize const& rFrameSize(pFirstRow->GetAttrSet()->GetFrameSize()); + if ( pFirstRow->GetPrev() && + static_cast<const SwRowFrame*>(pFirstRow->GetPrev())->IsRowSpanLine() + && rFrameSize.GetHeightSizeType() != SwFrameSize::Fixed) + { + // Calculate maximum height of all cells with rowspan = 1: + SwTwips nMaxHeight = rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum + ? rFrameSize.GetHeight() + : 0; + const SwCellFrame* pLower2 = static_cast<const SwCellFrame*>(pFirstRow->Lower()); + while ( pLower2 ) + { + if ( 1 == pLower2->GetTabBox()->getRowSpan() ) + { + const SwTwips nCellHeight = lcl_CalcMinCellHeight( pLower2, true ); + nMaxHeight = std::max( nCellHeight, nMaxHeight ); + } + pLower2 = static_cast<const SwCellFrame*>(pLower2->GetNext()); + } + nTmpHeight += nMaxHeight; + } + else + { + nTmpHeight += nFirstLineHeight; + } + } + + // Optimization: lcl_CalcHeightOfFirstContentLine actually can trigger + // a formatting of the row frame (via the GetFormatted()). We don't + // want this formatting if the row does not have a height. + else if ( 0 != nFirstLineHeight ) + { + const bool bOldJoinLock = IsJoinLocked(); + const_cast<SwTabFrame*>(this)->LockJoin(); + const SwTwips nHeightOfFirstContentLine = lcl_CalcHeightOfFirstContentLine( *pFirstRow ); + + // Consider minimum row height: + const SwFormatFrameSize &rSz = pFirstRow->GetFormat()->GetFrameSize(); + + SwTwips nMinRowHeight = 0; + if (rSz.GetHeightSizeType() == SwFrameSize::Minimum) + { + nMinRowHeight = std::max(rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*pFirstRow), + 0L); + } + + nTmpHeight += std::max( nHeightOfFirstContentLine, nMinRowHeight ); + + if ( !bOldJoinLock ) + const_cast<SwTabFrame*>(this)->UnlockJoin(); + } + } + + return nTmpHeight; +} + +// Some more functions for covered/covering cells. This way inclusion of +// SwCellFrame can be avoided + +bool SwFrame::IsLeaveUpperAllowed() const +{ + return false; +} + +bool SwCellFrame::IsLeaveUpperAllowed() const +{ + return GetLayoutRowSpan() > 1; +} + +bool SwFrame::IsCoveredCell() const +{ + return false; +} + +bool SwCellFrame::IsCoveredCell() const +{ + return GetLayoutRowSpan() < 1; +} + +bool SwFrame::IsInCoveredCell() const +{ + bool bRet = false; + + const SwFrame* pThis = this; + while ( pThis && !pThis->IsCellFrame() ) + pThis = pThis->GetUpper(); + + if ( pThis ) + bRet = pThis->IsCoveredCell(); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/trvlfrm.cxx b/sw/source/core/layout/trvlfrm.cxx new file mode 100644 index 000000000..7ec33dcea --- /dev/null +++ b/sw/source/core/layout/trvlfrm.cxx @@ -0,0 +1,2638 @@ +/* -*- 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 <hints.hxx> +#include <comphelper/flagguard.hxx> +#include <tools/line.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/protitem.hxx> +#include <vcl/settings.hxx> +#include <fmtpdsc.hxx> +#include <fmtsrnd.hxx> +#include <pagedesc.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <ftnfrm.hxx> +#include <flyfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <viewopt.hxx> +#include <DocumentSettingManager.hxx> +#include <viscrs.hxx> +#include <dflyobj.hxx> +#include <crstate.hxx> +#include <dcontact.hxx> +#include <sortedobjs.hxx> +#include <txatbase.hxx> +#include <fmtfld.hxx> +#include <fldbas.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <undobj.hxx> + +#include <swselectionlist.hxx> +#include <comphelper/lok.hxx> + +namespace { + bool lcl_GetModelPositionForViewPoint_Objects( const SwPageFrame* pPageFrame, bool bSearchBackground, + SwPosition *pPos, Point const & rPoint, SwCursorMoveState* pCMS ) + { + bool bRet = false; + Point aPoint( rPoint ); + SwOrderIter aIter( pPageFrame ); + aIter.Top(); + while ( aIter() ) + { + const SwVirtFlyDrawObj* pObj = + static_cast<const SwVirtFlyDrawObj*>(aIter()); + const SwAnchoredObject* pAnchoredObj = GetUserCall( aIter() )->GetAnchoredObj( aIter() ); + const SwFormatSurround& rSurround = pAnchoredObj->GetFrameFormat().GetSurround(); + const SvxOpaqueItem& rOpaque = pAnchoredObj->GetFrameFormat().GetOpaque(); + bool bInBackground = ( rSurround.GetSurround() == css::text::WrapTextMode_THROUGH ) && !rOpaque.GetValue(); + + bool bBackgroundMatches = bInBackground == bSearchBackground; + + const SwFlyFrame* pFly = pObj ? pObj->GetFlyFrame() : nullptr; + if ( pFly && bBackgroundMatches && + ( ( pCMS && pCMS->m_bSetInReadOnly ) || + !pFly->IsProtected() ) && + pFly->GetModelPositionForViewPoint( pPos, aPoint, pCMS ) ) + { + bRet = true; + break; + } + + if ( pCMS && pCMS->m_bStop ) + return false; + aIter.Prev(); + } + return bRet; + } + + double lcl_getDistance( const SwRect& rRect, const Point& rPoint ) + { + double nDist = 0.0; + + // If the point is inside the rectangle, then distance is 0 + // Otherwise, compute the distance to the center of the rectangle. + if ( !rRect.IsInside( rPoint ) ) + { + tools::Line aLine( rPoint, rRect.Center( ) ); + nDist = aLine.GetLength( ); + } + + return nDist; + } +} + +namespace { + +//For SwFlyFrame::GetModelPositionForViewPoint +class SwCursorOszControl +{ +public: + // So the compiler can initialize the class already. No DTOR and member + // as public members + const SwFlyFrame *pEntry; + const SwFlyFrame *pStack1; + const SwFlyFrame *pStack2; + + bool ChkOsz( const SwFlyFrame *pFly ) + { + bool bRet = true; + if ( pFly != pStack1 && pFly != pStack2 ) + { + pStack1 = pStack2; + pStack2 = pFly; + bRet = false; + } + return bRet; + } + + void Entry( const SwFlyFrame *pFly ) + { + if ( !pEntry ) + pEntry = pStack1 = pFly; + } + + void Exit( const SwFlyFrame *pFly ) + { + if ( pFly == pEntry ) + pEntry = pStack1 = pStack2 = nullptr; + } +}; + +} + +static SwCursorOszControl g_OszCtrl = { nullptr, nullptr, nullptr }; + +/** Searches the ContentFrame owning the PrtArea containing the point. */ +bool SwLayoutFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + bool bRet = false; + const SwFrame *pFrame = Lower(); + while ( !bRet && pFrame ) + { + pFrame->Calc(pRenderContext); + + // #i43742# New function + const bool bContentCheck = pFrame->IsTextFrame() && pCMS && pCMS->m_bContentCheck; + const SwRect aPaintRect( bContentCheck ? + pFrame->UnionFrame() : + pFrame->GetPaintArea() ); + + if ( aPaintRect.IsInside( rPoint ) && + ( bContentCheck || pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ) ) ) + bRet = true; + else + pFrame = pFrame->GetNext(); + if ( pCMS && pCMS->m_bStop ) + return false; + } + return bRet; +} + +/** Searches the page containing the searched point. */ + +bool SwPageFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool bTestBackground ) const +{ + Point aPoint( rPoint ); + + // check, if we have to adjust the point + if ( !getFrameArea().IsInside( aPoint ) ) + { + aPoint.setX( std::max( aPoint.X(), getFrameArea().Left() ) ); + aPoint.setX( std::min( aPoint.X(), getFrameArea().Right() ) ); + aPoint.setY( std::max( aPoint.Y(), getFrameArea().Top() ) ); + aPoint.setY( std::min( aPoint.Y(), getFrameArea().Bottom() ) ); + } + + bool bRet = false; + //Could it be a free flying one? + //If his content should be protected, we can't set the Cursor in it, thus + //all changes should be impossible. + if ( GetSortedObjs() ) + { + bRet = lcl_GetModelPositionForViewPoint_Objects( this, false, pPos, rPoint, pCMS ); + } + + if ( !bRet ) + { + SwPosition aBackPos( *pPos ); + SwPosition aTextPos( *pPos ); + + //We fix the StartPoint if no Content below the page 'answers' and then + //start all over again one page before the current one. + //However we can't use Flys in such a case. + if (!SwLayoutFrame::GetModelPositionForViewPoint(&aTextPos, aPoint, pCMS)) + { + if ( pCMS && (pCMS->m_bStop || pCMS->m_bExactOnly) ) + { + pCMS->m_bStop = true; + return false; + } + + const SwContentFrame *pCnt = GetContentPos( aPoint, false, false, pCMS, false ); + // GetContentPos may have modified pCMS + if ( pCMS && pCMS->m_bStop ) + return false; + + bool bTextRet = false; + + OSL_ENSURE( pCnt, "Cursor is gone to a Black hole" ); + if( pCMS && pCMS->m_pFill && pCnt->IsTextFrame() ) + bTextRet = pCnt->GetModelPositionForViewPoint( &aTextPos, rPoint, pCMS ); + else + bTextRet = pCnt->GetModelPositionForViewPoint( &aTextPos, aPoint, pCMS ); + + if ( !bTextRet ) + { + // Set point to pCnt, delete mark + // this may happen, if pCnt is hidden + if (pCnt->IsTextFrame()) + { + aTextPos = static_cast<SwTextFrame const*>(pCnt)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + assert(pCnt->IsNoTextFrame()); + aTextPos = SwPosition( *static_cast<SwNoTextFrame const*>(pCnt)->GetNode() ); + } + } + } + + SwContentNode* pContentNode = aTextPos.nNode.GetNode().GetContentNode(); + bool bConsiderBackground = true; + // If the text position is a clickable field, then that should have priority. + if (pContentNode && pContentNode->IsTextNode()) + { + SwTextNode* pTextNd = pContentNode->GetTextNode(); + SwTextAttr* pTextAttr = pTextNd->GetTextAttrForCharAt(aTextPos.nContent.GetIndex(), RES_TXTATR_FIELD); + if (pTextAttr) + { + const SwField* pField = pTextAttr->GetFormatField().GetField(); + if (pField->IsClickable()) + bConsiderBackground = false; + } + } + + bool bBackRet = false; + // Check objects in the background if nothing else matched + if ( GetSortedObjs() ) + { + bBackRet = lcl_GetModelPositionForViewPoint_Objects( this, true, &aBackPos, rPoint, pCMS ); + } + + if (bConsiderBackground && bTestBackground && bBackRet) + { + (*pPos) = aBackPos; + } + else if (!bBackRet) + { + (*pPos) = aTextPos; + } + else // bBackRet && !(bConsiderBackground && bTestBackground) + { + /* In order to provide a selection as accurate as possible when we have both + * text and background object, then we compute the distance between both + * would-be positions and the click point. The shortest distance wins. + */ + double nTextDistance = 0; + bool bValidTextDistance = false; + if (pContentNode) + { + SwContentFrame* pTextFrame = pContentNode->getLayoutFrame( getRootFrame( ) ); + + // try this again but prefer the "previous" position + SwCursorMoveState aMoveState; + SwCursorMoveState *const pState(pCMS ? pCMS : &aMoveState); + comphelper::FlagRestorationGuard g( + pState->m_bPosMatchesBounds, true); + SwPosition prevTextPos(*pPos); + if (SwLayoutFrame::GetModelPositionForViewPoint(&prevTextPos, aPoint, pState)) + { + SwRect aTextRect; + pTextFrame->GetCharRect(aTextRect, prevTextPos); + + if (prevTextPos.nContent < pContentNode->Len()) + { + // aRextRect is just a line on the left edge of the + // previous character; to get a better measure from + // lcl_getDistance, extend that to a rectangle over + // the entire character. + SwPosition const nextTextPos(prevTextPos.nNode, + SwIndex(prevTextPos.nContent, +1)); + SwRect nextTextRect; + pTextFrame->GetCharRect(nextTextRect, nextTextPos); + SwRectFnSet aRectFnSet(pTextFrame); + if (aRectFnSet.GetTop(aTextRect) == + aRectFnSet.GetTop(nextTextRect)) // same line? + { + // need to handle mixed RTL/LTR portions somehow + if (aRectFnSet.GetLeft(aTextRect) < + aRectFnSet.GetLeft(nextTextRect)) + { + aRectFnSet.SetRight( aTextRect, + aRectFnSet.GetLeft(nextTextRect)); + } + else // RTL + { + aRectFnSet.SetLeft( aTextRect, + aRectFnSet.GetLeft(nextTextRect)); + } + } + } + + nTextDistance = lcl_getDistance(aTextRect, rPoint); + bValidTextDistance = true; + } + } + + double nBackDistance = 0; + bool bValidBackDistance = false; + SwContentNode* pBackNd = aBackPos.nNode.GetNode( ).GetContentNode( ); + if ( pBackNd && bConsiderBackground) + { + // FIXME There are still cases were we don't have the proper node here. + SwContentFrame* pBackFrame = pBackNd->getLayoutFrame( getRootFrame( ) ); + SwRect rBackRect; + if (pBackFrame) + { + pBackFrame->GetCharRect( rBackRect, aBackPos ); + + nBackDistance = lcl_getDistance( rBackRect, rPoint ); + bValidBackDistance = true; + } + } + + if ( bValidTextDistance && bValidBackDistance && basegfx::fTools::more( nTextDistance, nBackDistance ) ) + { + (*pPos) = aBackPos; + } + else + { + (*pPos) = aTextPos; + } + } + } + + rPoint = aPoint; + return true; +} + +bool SwLayoutFrame::FillSelection( SwSelectionList& rList, const SwRect& rRect ) const +{ + if( rRect.IsOver(GetPaintArea()) ) + { + const SwFrame* pFrame = Lower(); + while( pFrame ) + { + pFrame->FillSelection( rList, rRect ); + pFrame = pFrame->GetNext(); + } + } + return false; +} + +bool SwPageFrame::FillSelection( SwSelectionList& rList, const SwRect& rRect ) const +{ + bool bRet = false; + if( rRect.IsOver(GetPaintArea()) ) + { + bRet = SwLayoutFrame::FillSelection( rList, rRect ); + if( GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr ) + continue; + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pAnchoredObj); + if( pFly->FillSelection( rList, rRect ) ) + bRet = true; + } + } + } + return bRet; +} + +bool SwRootFrame::FillSelection( SwSelectionList& aSelList, const SwRect& rRect) const +{ + const SwFrame *pPage = Lower(); + const long nBottom = rRect.Bottom(); + while( pPage ) + { + if( pPage->getFrameArea().Top() < nBottom ) + { + if( pPage->getFrameArea().Bottom() > rRect.Top() ) + pPage->FillSelection( aSelList, rRect ); + pPage = pPage->GetNext(); + } + else + pPage = nullptr; + } + return !aSelList.isEmpty(); +} + +/** Primary passes the call to the first page. + * + * @return false, if the passed Point gets changed + */ +bool SwRootFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool bTestBackground ) const +{ + const bool bOldAction = IsCallbackActionEnabled(); + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( false ); + OSL_ENSURE( (Lower() && Lower()->IsPageFrame()), "No PageFrame found." ); + if( pCMS && pCMS->m_pFill ) + pCMS->m_bFillRet = false; + Point aOldPoint = rPoint; + + // search for page containing rPoint. The borders around the pages are considered + const SwPageFrame* pPage = GetPageAtPos( rPoint, nullptr, true ); + + // #i95626# + // special handling for <rPoint> beyond root frames area + if ( !pPage && + rPoint.X() > getFrameArea().Right() && + rPoint.Y() > getFrameArea().Bottom() ) + { + pPage = dynamic_cast<const SwPageFrame*>(Lower()); + while ( pPage && pPage->GetNext() ) + { + pPage = dynamic_cast<const SwPageFrame*>(pPage->GetNext()); + } + } + if ( pPage ) + { + pPage->SwPageFrame::GetModelPositionForViewPoint( pPos, rPoint, pCMS, bTestBackground ); + } + + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( bOldAction ); + if( pCMS ) + { + if( pCMS->m_bStop ) + return false; + if( pCMS->m_pFill ) + return pCMS->m_bFillRet; + } + return aOldPoint == rPoint; +} + +/** + * If this is about a Content-carrying cell the Cursor will be force inserted into one of the ContentFrames + * if there are no other options. + * + * There is no entry for protected cells. + */ +bool SwCellFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + // cell frame does not necessarily have a lower (split table cell) + if ( !Lower() ) + return false; + + if ( !(pCMS && pCMS->m_bSetInReadOnly) && + GetFormat()->GetProtect().IsContentProtected() ) + return false; + + if ( pCMS && pCMS->m_eState == CursorMoveState::TableSel ) + { + const SwTabFrame *pTab = FindTabFrame(); + if ( pTab->IsFollow() && pTab->IsInHeadline( *this ) ) + { + pCMS->m_bStop = true; + return false; + } + } + + if ( Lower() ) + { + if ( Lower()->IsLayoutFrame() ) + return SwLayoutFrame::GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + else + { + Calc(pRenderContext); + bool bRet = false; + + const SwFrame *pFrame = Lower(); + while ( pFrame && !bRet ) + { + pFrame->Calc(pRenderContext); + if ( pFrame->getFrameArea().IsInside( rPoint ) ) + { + bRet = pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + } + pFrame = pFrame->GetNext(); + } + if ( !bRet ) + { + const bool bFill = pCMS && pCMS->m_pFill; + Point aPoint( rPoint ); + const SwContentFrame *pCnt = GetContentPos( rPoint, true ); + if( bFill && pCnt->IsTextFrame() ) + { + rPoint = aPoint; + } + pCnt->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + } + return true; + } + } + + return false; +} + +//Problem: If two Flys have the same size and share the same position then +//they end inside each other. +//Because we recursively check if a Point doesn't randomly lie inside another +//fly which lies completely inside the current Fly we could trigger an endless +//loop with the mentioned situation above. +//Using the helper class SwCursorOszControl we prevent the recursion. During +//a recursion GetModelPositionForViewPoint picks the one which lies on top. +bool SwFlyFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + g_OszCtrl.Entry( this ); + + //If the Points lies inside the Fly, we try hard to set the Cursor inside it. + //However if the Point sits inside a Fly which is completely located inside + //the current one, we call GetModelPositionForViewPoint for it. + Calc(pRenderContext); + bool bInside = getFrameArea().IsInside( rPoint ) && Lower(); + bool bRet = false; + + //If a Frame contains a graphic, but only text was requested, it basically + //won't accept the Cursor. + if ( bInside && pCMS && pCMS->m_eState == CursorMoveState::SetOnlyText && + (!Lower() || Lower()->IsNoTextFrame()) ) + bInside = false; + + const SwPageFrame *pPage = FindPageFrame(); + if ( bInside && pPage && pPage->GetSortedObjs() ) + { + SwOrderIter aIter( pPage ); + aIter.Top(); + while ( aIter() && !bRet ) + { + const SwVirtFlyDrawObj* pObj = static_cast<const SwVirtFlyDrawObj*>(aIter()); + const SwFlyFrame* pFly = pObj ? pObj->GetFlyFrame() : nullptr; + if ( pFly && pFly->getFrameArea().IsInside( rPoint ) && + getFrameArea().IsInside( pFly->getFrameArea() ) ) + { + if (g_OszCtrl.ChkOsz(pFly)) + break; + bRet = pFly->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( bRet ) + break; + if ( pCMS && pCMS->m_bStop ) + return false; + } + aIter.Next(); + } + } + + while ( bInside && !bRet ) + { + const SwFrame *pFrame = Lower(); + while ( pFrame && !bRet ) + { + pFrame->Calc(pRenderContext); + if ( pFrame->getFrameArea().IsInside( rPoint ) ) + { + bRet = pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + } + pFrame = pFrame->GetNext(); + } + if ( !bRet ) + { + const bool bFill = pCMS && pCMS->m_pFill; + Point aPoint( rPoint ); + const SwContentFrame *pCnt = GetContentPos( rPoint, true, false, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + if( bFill && pCnt->IsTextFrame() ) + { + rPoint = aPoint; + } + pCnt->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + bRet = true; + } + } + g_OszCtrl.Exit( this ); + return bRet; +} + +/** Layout dependent cursor travelling */ +bool SwNoTextFrame::LeftMargin(SwPaM *pPam) const +{ + if( &pPam->GetNode() != GetNode() ) + return false; + const_cast<SwContentNode*>(GetNode())-> + MakeStartIndex(&pPam->GetPoint()->nContent); + return true; +} + +bool SwNoTextFrame::RightMargin(SwPaM *pPam, bool) const +{ + if( &pPam->GetNode() != GetNode() ) + return false; + const_cast<SwContentNode*>(GetNode())-> + MakeEndIndex(&pPam->GetPoint()->nContent); + return true; +} + +static const SwContentFrame *lcl_GetNxtCnt( const SwContentFrame* pCnt ) +{ + return pCnt->GetNextContentFrame(); +} + +static const SwContentFrame *lcl_GetPrvCnt( const SwContentFrame* pCnt ) +{ + return pCnt->GetPrevContentFrame(); +} + +typedef const SwContentFrame *(*GetNxtPrvCnt)( const SwContentFrame* ); + +/// Frame in repeated headline? +static bool lcl_IsInRepeatedHeadline( const SwFrame *pFrame, + const SwTabFrame** ppTFrame = nullptr ) +{ + const SwTabFrame *pTab = pFrame->FindTabFrame(); + if( ppTFrame ) + *ppTFrame = pTab; + return pTab && pTab->IsFollow() && pTab->IsInHeadline( *pFrame ); +} + +/// Skip protected table cells. Optionally also skip repeated headlines. +//MA 1998-01-26: Chg also skip other protected areas +//FME: Skip follow flow cells +static const SwContentFrame * lcl_MissProtectedFrames( const SwContentFrame *pCnt, + GetNxtPrvCnt fnNxtPrv, + bool bMissHeadline, + bool bInReadOnly, + bool bMissFollowFlowLine ) +{ + if ( pCnt && pCnt->IsInTab() ) + { + bool bProtect = true; + while ( pCnt && bProtect ) + { + const SwLayoutFrame *pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + if ( !pCell || + ( ( bInReadOnly || !pCell->GetFormat()->GetProtect().IsContentProtected() ) && + ( !bMissHeadline || !lcl_IsInRepeatedHeadline( pCell ) ) && + ( !bMissFollowFlowLine || !pCell->IsInFollowFlowRow() ) && + !pCell->IsCoveredCell() ) ) + bProtect = false; + else + pCnt = (*fnNxtPrv)( pCnt ); + } + } + else if ( !bInReadOnly ) + while ( pCnt && pCnt->IsProtected() ) + pCnt = (*fnNxtPrv)( pCnt ); + + return pCnt; +} + +static bool lcl_UpDown( SwPaM *pPam, const SwContentFrame *pStart, + GetNxtPrvCnt fnNxtPrv, bool bInReadOnly ) +{ + OSL_ENSURE( FrameContainsNode(*pStart, pPam->GetNode().GetIndex()), + "lcl_UpDown doesn't work for others." ); + + const SwContentFrame *pCnt = nullptr; + + //We have to cheat a little bit during a table selection: Go to the + //beginning of the cell while going up and go to the end of the cell while + //going down. + bool bTableSel = false; + if ( pStart->IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + bTableSel = true; + const SwLayoutFrame *pCell = pStart->GetUpper(); + while ( !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + + // Check, if cell has a Prev/Follow cell: + const bool bFwd = ( fnNxtPrv == lcl_GetNxtCnt ); + const SwLayoutFrame* pTmpCell = bFwd ? + static_cast<const SwCellFrame*>(pCell)->GetFollowCell() : + static_cast<const SwCellFrame*>(pCell)->GetPreviousCell(); + + const SwContentFrame* pTmpStart = pStart; + while ( pTmpCell && nullptr != ( pTmpStart = pTmpCell->ContainsContent() ) ) + { + pCell = pTmpCell; + pTmpCell = bFwd ? + static_cast<const SwCellFrame*>(pCell)->GetFollowCell() : + static_cast<const SwCellFrame*>(pCell)->GetPreviousCell(); + } + const SwContentFrame *pNxt = pCnt = pTmpStart; + + while ( pCell->IsAnLower( pNxt ) ) + { + pCnt = pNxt; + pNxt = (*fnNxtPrv)( pNxt ); + } + } + + pCnt = (*fnNxtPrv)( pCnt ? pCnt : pStart ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + + const SwTabFrame *pStTab = pStart->FindTabFrame(); + const SwTabFrame *pTable = nullptr; + const bool bTab = pStTab || (pCnt && pCnt->IsInTab()); + bool bEnd = !bTab; + + const SwFrame* pVertRefFrame = pStart; + if ( bTableSel && pStTab ) + pVertRefFrame = pStTab; + SwRectFnSet aRectFnSet(pVertRefFrame); + + SwTwips nX = 0; + if ( bTab ) + { + // pStart or pCnt is inside a table. nX will be used for travelling: + SwRect aRect( pStart->getFrameArea() ); + pStart->GetCharRect( aRect, *pPam->GetPoint() ); + Point aCenter = aRect.Center(); + nX = aRectFnSet.IsVert() ? aCenter.Y() : aCenter.X(); + + pTable = pCnt ? pCnt->FindTabFrame() : nullptr; + if ( !pTable ) + pTable = pStTab; + + if ( pStTab && + !pStTab->GetUpper()->IsInTab() && + !pTable->GetUpper()->IsInTab() ) + { + const SwFrame *pCell = pStart->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "could not find the cell" ); + nX = aRectFnSet.XInc(aRectFnSet.GetLeft(pCell->getFrameArea()), + aRectFnSet.GetWidth(pCell->getFrameArea()) / 2); + + //The flow leads from one table to the next. The X-value needs to be + //corrected based on the middle of the starting cell by the amount + //of the offset of the tables. + if ( pStTab != pTable ) + { + nX += aRectFnSet.GetLeft(pTable->getFrameArea()) - + aRectFnSet.GetLeft(pStTab->getFrameArea()); + } + } + + // Restrict nX to the left and right borders of pTab: + // (is this really necessary?) + if (pTable && !pTable->GetUpper()->IsInTab()) + { + const bool bRTL = pTable->IsRightToLeft(); + const long nPrtLeft = bRTL ? + aRectFnSet.GetPrtRight(*pTable) : + aRectFnSet.GetPrtLeft(*pTable); + if (bRTL != (aRectFnSet.XDiff(nPrtLeft, nX) > 0)) + nX = nPrtLeft; + else + { + const long nPrtRight = bRTL ? + aRectFnSet.GetPrtLeft(*pTable) : + aRectFnSet.GetPrtRight(*pTable); + if (bRTL != (aRectFnSet.XDiff(nX, nPrtRight) > 0)) + nX = nPrtRight; + } + } + } + + do + { + //If I'm in the DocumentBody, I want to stay there. + if ( pStart->IsInDocBody() ) + { + while ( pCnt && (!pCnt->IsInDocBody() || + (pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow()))) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //If I'm in the FootNoteArea, I try to reach the next FootNoteArea in + //case of necessity. + else if ( pStart->IsInFootnote() ) + { + while ( pCnt && (!pCnt->IsInFootnote() || + (pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow()))) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //In Flys we can go ahead blindly as long as we find a Content. + else if ( pStart->IsInFly() ) + { + if ( pCnt && pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow() ) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //Otherwise I'll just refuse to leave to current area. + else if ( pCnt ) + { + const SwFrame *pUp = pStart->GetUpper(); + while (pUp && pUp->GetUpper() && !(pUp->GetType() & FRM_HEADFOOT)) + pUp = pUp->GetUpper(); + bool bSame = false; + const SwFrame *pCntUp = pCnt->GetUpper(); + while ( pCntUp && !bSame ) + { + if ( pUp == pCntUp ) + bSame = true; + else + pCntUp = pCntUp->GetUpper(); + } + if ( !bSame ) + pCnt = nullptr; + else if (pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow()) // i73332 + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + if ( bTab ) + { + if ( !pCnt ) + bEnd = true; + else + { + const SwTabFrame *pTab = pCnt->FindTabFrame(); + if( !pTab ) + bEnd = true; + else + { + if ( pTab != pTable ) + { + //The flow leads from one table to the next. The X-value + //needs to be corrected by the amount of the offset of + //the tables + if ( pTable && + !pTab->GetUpper()->IsInTab() && + !pTable->GetUpper()->IsInTab() ) + nX += pTab->getFrameArea().Left() - pTable->getFrameArea().Left(); + pTable = pTab; + } + const SwLayoutFrame *pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + + Point aInsideCell; + Point aInsideCnt; + if ( pCell ) + { + long nTmpTop = aRectFnSet.GetTop(pCell->getFrameArea()); + if ( aRectFnSet.IsVert() ) + { + if ( nTmpTop ) + nTmpTop = aRectFnSet.XInc(nTmpTop, -1); + + aInsideCell = Point( nTmpTop, nX ); + } + else + aInsideCell = Point( nX, nTmpTop ); + } + + long nTmpTop = aRectFnSet.GetTop(pCnt->getFrameArea()); + if ( aRectFnSet.IsVert() ) + { + if ( nTmpTop ) + nTmpTop = aRectFnSet.XInc(nTmpTop, -1); + + aInsideCnt = Point( nTmpTop, nX ); + } + else + aInsideCnt = Point( nX, nTmpTop ); + + if ( pCell && pCell->getFrameArea().IsInside( aInsideCell ) ) + { + bEnd = true; + //Get the right Content out of the cell. + if ( !pCnt->getFrameArea().IsInside( aInsideCnt ) ) + { + pCnt = pCell->ContainsContent(); + if ( fnNxtPrv == lcl_GetPrvCnt ) + while ( pCell->IsAnLower(pCnt->GetNextContentFrame()) ) + pCnt = pCnt->GetNextContentFrame(); + } + } + else if ( pCnt->getFrameArea().IsInside( aInsideCnt ) ) + bEnd = true; + } + } + if ( !bEnd ) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + } while ( !bEnd || + (pCnt && pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow())); + + if (pCnt == nullptr) + { + return false; + } + if (pCnt->IsTextFrame()) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pCnt)); + *pPam->GetPoint() = pFrame->MapViewToModelPos(TextFrameIndex( + fnNxtPrv == lcl_GetPrvCnt + ? pFrame->GetText().getLength() + : 0)); + } + else + { // set the Point on the Content-Node + assert(pCnt->IsNoTextFrame()); + SwContentNode *const pCNd = const_cast<SwContentNode*>(static_cast<SwNoTextFrame const*>(pCnt)->GetNode()); + pPam->GetPoint()->nNode = *pCNd; + if ( fnNxtPrv == lcl_GetPrvCnt ) + pCNd->MakeEndIndex( &pPam->GetPoint()->nContent ); + else + pCNd->MakeStartIndex( &pPam->GetPoint()->nContent ); + } + return true; +} + +bool SwContentFrame::UnitUp( SwPaM* pPam, const SwTwips, bool bInReadOnly ) const +{ + return ::lcl_UpDown( pPam, this, lcl_GetPrvCnt, bInReadOnly ); +} + +bool SwContentFrame::UnitDown( SwPaM* pPam, const SwTwips, bool bInReadOnly ) const +{ + return ::lcl_UpDown( pPam, this, lcl_GetNxtCnt, bInReadOnly ); +} + +/** Returns the number of the current page. + * + * If the method gets a PaM then the current page is the one in which the PaM sits. Otherwise the + * current page is the first one inside the VisibleArea. We only work on available pages! + */ +sal_uInt16 SwRootFrame::GetCurrPage( const SwPaM *pActualCursor ) const +{ + OSL_ENSURE( pActualCursor, "got no page cursor" ); + SwFrame const*const pActFrame = pActualCursor->GetPoint()->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, + pActualCursor->GetPoint()); + return pActFrame->FindPageFrame()->GetPhyPageNum(); +} + +/** Returns a PaM which sits at the beginning of the requested page. + * + * Formatting is done as far as necessary. + * The PaM sits on the last page, if the page number was chosen too big. + * + * @return Null, if the operation was not possible. + */ +sal_uInt16 SwRootFrame::SetCurrPage( SwCursor* pToSet, sal_uInt16 nPageNum ) +{ + vcl::RenderContext* pRenderContext = GetCurrShell() ? GetCurrShell()->GetOut() : nullptr; + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "No page available." ); + + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + bool bEnd =false; + while ( !bEnd && pPage->GetPhyPageNum() != nPageNum ) + { if ( pPage->GetNext() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + else + { //Search the first ContentFrame and format until a new page is started + //or until the ContentFrame are all done. + const SwContentFrame *pContent = pPage->ContainsContent(); + while ( pContent && pPage->IsAnLower( pContent ) ) + { + pContent->Calc(pRenderContext); + pContent = pContent->GetNextContentFrame(); + } + //Either this is a new page or we found the last page. + if ( pPage->GetNext() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + else + bEnd = true; + } + } + //pPage now points to the 'requested' page. Now we have to create the PaM + //on the beginning of the first ContentFrame in the body-text. + //If this is a footnote-page, the PaM will be set in the first footnote. + const SwContentFrame *pContent = pPage->ContainsContent(); + if ( pPage->IsFootnotePage() ) + while ( pContent && !pContent->IsInFootnote() ) + pContent = pContent->GetNextContentFrame(); + else + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + if ( pContent ) + { + assert(pContent->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast<const SwTextFrame*>(pContent)); + *pToSet->GetPoint() = pFrame->MapViewToModelPos(pFrame->GetOffset()); + + SwShellCursor* pSCursor = dynamic_cast<SwShellCursor*>(pToSet); + if( pSCursor ) + { + Point &rPt = pSCursor->GetPtPos(); + rPt = pContent->getFrameArea().Pos(); + rPt += pContent->getFramePrintArea().Pos(); + } + return pPage->GetPhyPageNum(); + } + return 0; +} + +SwContentFrame *GetFirstSub( const SwLayoutFrame *pLayout ) +{ + return const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pLayout))->FindFirstBodyContent(); +} + +SwContentFrame *GetLastSub( const SwLayoutFrame *pLayout ) +{ + return const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pLayout))->FindLastBodyContent(); +} + +SwLayoutFrame *GetNextFrame( const SwLayoutFrame *pFrame ) +{ + SwLayoutFrame *pNext = + (pFrame->GetNext() && pFrame->GetNext()->IsLayoutFrame()) ? + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pFrame->GetNext())) : nullptr; + // #i39402# in case of an empty page + if(pNext && !pNext->ContainsContent()) + pNext = (pNext->GetNext() && pNext->GetNext()->IsLayoutFrame()) ? + static_cast<SwLayoutFrame*>(pNext->GetNext()) : nullptr; + return pNext; +} + +SwLayoutFrame *GetThisFrame( const SwLayoutFrame *pFrame ) +{ + return const_cast<SwLayoutFrame*>(pFrame); +} + +SwLayoutFrame *GetPrevFrame( const SwLayoutFrame *pFrame ) +{ + SwLayoutFrame *pPrev = + (pFrame->GetPrev() && pFrame->GetPrev()->IsLayoutFrame()) ? + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pFrame->GetPrev())) : nullptr; + // #i39402# in case of an empty page + if(pPrev && !pPrev->ContainsContent()) + pPrev = (pPrev->GetPrev() && pPrev->GetPrev()->IsLayoutFrame()) ? + static_cast<SwLayoutFrame*>(pPrev->GetPrev()) : nullptr; + return pPrev; +} + +/** + * Returns the first/last Contentframe (controlled using the parameter fnPosPage) + * of the current/previous/next page (controlled using the parameter fnWhichPage). + */ +bool GetFrameInPage( const SwContentFrame *pCnt, SwWhichPage fnWhichPage, + SwPosPage fnPosPage, SwPaM *pPam ) +{ + //First find the requested page, at first the current, then the one which + //was requests through fnWichPage. + const SwLayoutFrame *pLayoutFrame = pCnt->FindPageFrame(); + if ( !pLayoutFrame || (nullptr == (pLayoutFrame = (*fnWhichPage)(pLayoutFrame))) ) + return false; + + //Now the desired ContentFrame below the page + if( nullptr == (pCnt = (*fnPosPage)(pLayoutFrame)) ) + return false; + else + { + // repeated headlines in tables + if ( pCnt->IsInTab() && fnPosPage == GetFirstSub ) + { + const SwTabFrame* pTab = pCnt->FindTabFrame(); + if ( pTab->IsFollow() ) + { + if ( pTab->IsInHeadline( *pCnt ) ) + { + SwLayoutFrame* pRow = pTab->GetFirstNonHeadlineRow(); + if ( pRow ) + { + // We are in the first line of a follow table + // with repeated headings. + // To actually make a "real" move we take the first content + // of the next row + pCnt = pRow->ContainsContent(); + if ( ! pCnt ) + return false; + } + } + } + } + + assert(pCnt->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast<const SwTextFrame*>(pCnt)); + TextFrameIndex const nIdx((fnPosPage == GetFirstSub) + ? pFrame->GetOffset() + : (pFrame->GetFollow()) + ? pFrame->GetFollow()->GetOffset() - TextFrameIndex(1) + : TextFrameIndex(pFrame->GetText().getLength())); + *pPam->GetPoint() = pFrame->MapViewToModelPos(nIdx); + return true; + } +} + +static sal_uInt64 CalcDiff(const Point &rPt1, const Point &rPt2) +{ + //Calculate the distance between the two points. + //'delta' X^2 + 'delta'Y^2 = 'distance'^2 + sal_uInt64 dX = std::max( rPt1.X(), rPt2.X() ) - + std::min( rPt1.X(), rPt2.X() ), + dY = std::max( rPt1.Y(), rPt2.Y() ) - + std::min( rPt1.Y(), rPt2.Y() ); + return (dX * dX) + (dY * dY); +} + +/** Check if the point lies inside the page part in which also the ContentFrame lies. + * + * In this context header, page body, footer and footnote-container count as page part. + * This will suit the purpose that the ContentFrame which lies in the "right" page part will be + * accepted instead of one which doesn't lie there although his distance to the point is shorter. + */ +static const SwLayoutFrame* lcl_Inside( const SwContentFrame *pCnt, Point const & rPt ) +{ + const SwLayoutFrame* pUp = pCnt->GetUpper(); + while( pUp ) + { + if( pUp->IsPageBodyFrame() || pUp->IsFooterFrame() || pUp->IsHeaderFrame() ) + { + if( rPt.Y() >= pUp->getFrameArea().Top() && rPt.Y() <= pUp->getFrameArea().Bottom() ) + return pUp; + return nullptr; + } + if( pUp->IsFootnoteContFrame() ) + return pUp->getFrameArea().IsInside( rPt ) ? pUp : nullptr; + pUp = pUp->GetUpper(); + } + return nullptr; +} + +/** Search for the nearest Content to pass. + * + * Considers the previous, the current and the next page. + * If no content is found, the area gets expanded until one is found. + * + * @return The 'semantically correct' position inside the PrtArea of the found ContentFrame. + */ +const SwContentFrame *SwLayoutFrame::GetContentPos( Point& rPoint, + const bool bDontLeave, + const bool bBodyOnly, + SwCursorMoveState *pCMS, + const bool bDefaultExpand ) const +{ + //Determine the first ContentFrame. + const SwLayoutFrame *pStart = (!bDontLeave && bDefaultExpand && GetPrev()) ? + static_cast<const SwLayoutFrame*>(GetPrev()) : this; + const SwContentFrame *pContent = pStart->ContainsContent(); + + if ( !pContent && (GetPrev() && !bDontLeave) ) + pContent = ContainsContent(); + + if ( bBodyOnly && pContent && !pContent->IsInDocBody() ) + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + + const SwContentFrame *pActual= pContent; + const SwLayoutFrame *pInside = nullptr; + sal_uInt16 nMaxPage = GetPhyPageNum() + (bDefaultExpand ? 1 : 0); + Point aPoint = rPoint; + sal_uInt64 nDistance = SAL_MAX_UINT64; + + while ( true ) //A loop to be sure we always find one. + { + while ( pContent && + ((!bDontLeave || IsAnLower( pContent )) && + (pContent->GetPhyPageNum() <= nMaxPage)) ) + { + if ( pContent->getFrameArea().Width() && + ( !bBodyOnly || pContent->IsInDocBody() ) ) + { + //If the Content lies in a protected area (cell, Footnote, section), + //we search the next Content which is not protected. + const SwContentFrame *pComp = pContent; + pContent = ::lcl_MissProtectedFrames( pContent, lcl_GetNxtCnt, false, + pCMS && pCMS->m_bSetInReadOnly, false ); + if ( pComp != pContent ) + continue; + + if ( !pContent->IsTextFrame() || !static_cast<const SwTextFrame*>(pContent)->IsHiddenNow() ) + { + SwRect aContentFrame( pContent->UnionFrame() ); + if ( aContentFrame.IsInside( rPoint ) ) + { + pActual = pContent; + aPoint = rPoint; + break; + } + //The distance from rPoint to the nearest Point of pContent + //will now be calculated. + Point aContentPoint( rPoint ); + + //First set the vertical position + if ( aContentFrame.Top() > aContentPoint.Y() ) + aContentPoint.setY( aContentFrame.Top() ); + else if ( aContentFrame.Bottom() < aContentPoint.Y() ) + aContentPoint.setY( aContentFrame.Bottom() ); + + //Now the horizontal position + if ( aContentFrame.Left() > aContentPoint.X() ) + aContentPoint.setX( aContentFrame.Left() ); + else if ( aContentFrame.Right() < aContentPoint.X() ) + aContentPoint.setX( aContentFrame.Right() ); + + // pInside is a page area in which the point lies. As soon + // as pInside != 0 only frames are accepted which are + // placed inside. + if( !pInside || ( pInside->IsAnLower( pContent ) && + ( !pContent->IsInFootnote() || pInside->IsFootnoteContFrame() ) ) ) + { + const sal_uInt64 nDiff = ::CalcDiff(aContentPoint, rPoint); + bool bBetter = nDiff < nDistance; // This one is nearer + if( !pInside ) + { + pInside = lcl_Inside( pContent, rPoint ); + if( pInside ) // In the "right" page area + bBetter = true; + } + if( bBetter ) + { + aPoint = aContentPoint; + nDistance = nDiff; + pActual = pContent; + } + } + } + } + pContent = pContent->GetNextContentFrame(); + if ( bBodyOnly ) + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + } + if ( !pActual ) + { //If we not yet found one we have to expand the searched + //area, sometime we will find one! + //MA 1997-01-09: Opt for many empty pages - if we only search inside + //the body, we can expand the searched area sufficiently in one step. + if ( bBodyOnly ) + { + while ( !pContent && pStart->GetPrev() ) + { + ++nMaxPage; + if( !pStart->GetPrev()->IsLayoutFrame() ) + return nullptr; + pStart = static_cast<const SwLayoutFrame*>(pStart->GetPrev()); + pContent = pStart->IsInDocBody() + ? pStart->ContainsContent() + : pStart->FindPageFrame()->FindFirstBodyContent(); + } + if ( !pContent ) // Somewhere down the road we have to start with one! + { + pContent = pStart->FindPageFrame()->GetUpper()->ContainsContent(); + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + if ( !pContent ) + return nullptr; // There is no document content yet! + } + } + else + { + ++nMaxPage; + if ( pStart->GetPrev() ) + { + if( !pStart->GetPrev()->IsLayoutFrame() ) + return nullptr; + pStart = static_cast<const SwLayoutFrame*>(pStart->GetPrev()); + pContent = pStart->ContainsContent(); + } + else // Somewhere down the road we have to start with one! + pContent = pStart->FindPageFrame()->GetUpper()->ContainsContent(); + } + pActual = pContent; + } + else + break; + } + + OSL_ENSURE( pActual, "no Content found." ); + OSL_ENSURE( !bBodyOnly || pActual->IsInDocBody(), "Content not in Body." ); + + //Special case for selecting tables not in repeated TableHeadlines. + if ( pActual->IsInTab() && pCMS && pCMS->m_eState == CursorMoveState::TableSel ) + { + const SwTabFrame *pTab = pActual->FindTabFrame(); + if ( pTab->IsFollow() && pTab->IsInHeadline( *pActual ) ) + { + pCMS->m_bStop = true; + return nullptr; + } + } + + //A small correction at the first/last + Size aActualSize( pActual->getFramePrintArea().SSize() ); + if ( aActualSize.Height() > pActual->GetUpper()->getFramePrintArea().Height() ) + aActualSize.setHeight( pActual->GetUpper()->getFramePrintArea().Height() ); + + SwRectFnSet aRectFnSet(pActual); + if ( !pActual->GetPrev() && + aRectFnSet.YDiff( aRectFnSet.GetPrtTop(*pActual), + aRectFnSet.IsVert() ? rPoint.X() : rPoint.Y() ) > 0 ) + { + aPoint.setY( pActual->getFrameArea().Top() + pActual->getFramePrintArea().Top() ); + aPoint.setX( pActual->getFrameArea().Left() + + ( pActual->IsRightToLeft() || aRectFnSet.IsVert() ? + pActual->getFramePrintArea().Right() : + pActual->getFramePrintArea().Left() ) ); + } + else if ( !pActual->GetNext() && + aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pActual), + aRectFnSet.IsVert() ? rPoint.X() : rPoint.Y() ) < 0 ) + { + aPoint.setY( pActual->getFrameArea().Top() + pActual->getFramePrintArea().Bottom() ); + aPoint.setX( pActual->getFrameArea().Left() + + ( pActual->IsRightToLeft() || aRectFnSet.IsVert() ? + pActual->getFramePrintArea().Left() : + pActual->getFramePrintArea().Right() ) ); + } + + //Bring the Point into the PrtArea + const SwRect aRect( pActual->getFrameArea().Pos() + pActual->getFramePrintArea().Pos(), + aActualSize ); + if ( aPoint.Y() < aRect.Top() ) + aPoint.setY( aRect.Top() ); + else if ( aPoint.Y() > aRect.Bottom() ) + aPoint.setY( aRect.Bottom() ); + if ( aPoint.X() < aRect.Left() ) + aPoint.setX( aRect.Left() ); + else if ( aPoint.X() > aRect.Right() ) + aPoint.setX( aRect.Right() ); + rPoint = aPoint; + return pActual; +} + +/** Same as SwLayoutFrame::GetContentPos(). Specialized for fields and border. */ +void SwPageFrame::GetContentPosition( const Point &rPt, SwPosition &rPos ) const +{ + //Determine the first ContentFrame. + const SwContentFrame *pContent = ContainsContent(); + if ( pContent ) + { + //Look back one more (if possible). + const SwContentFrame *pTmp = pContent->GetPrevContentFrame(); + while ( pTmp && !pTmp->IsInDocBody() ) + pTmp = pTmp->GetPrevContentFrame(); + if ( pTmp ) + pContent = pTmp; + } + else + pContent = GetUpper()->ContainsContent(); + + const SwContentFrame *pAct = pContent; + Point aAct = rPt; + sal_uInt64 nDist = SAL_MAX_UINT64; + + while ( pContent ) + { + SwRect aContentFrame( pContent->UnionFrame() ); + if ( aContentFrame.IsInside( rPt ) ) + { + //This is the nearest one. + pAct = pContent; + break; + } + + //Calculate the distance from rPt to the nearest point of pContent. + Point aPoint( rPt ); + + //Calculate the vertical position first + if ( aContentFrame.Top() > rPt.Y() ) + aPoint.setY( aContentFrame.Top() ); + else if ( aContentFrame.Bottom() < rPt.Y() ) + aPoint.setY( aContentFrame.Bottom() ); + + //And now the horizontal position + if ( aContentFrame.Left() > rPt.X() ) + aPoint.setX( aContentFrame.Left() ); + else if ( aContentFrame.Right() < rPt.X() ) + aPoint.setX( aContentFrame.Right() ); + + const sal_uInt64 nDiff = ::CalcDiff( aPoint, rPt ); + if ( nDiff < nDist ) + { + aAct = aPoint; + nDist = nDiff; + pAct = pContent; + } + else if ( aContentFrame.Top() > getFrameArea().Bottom() ) + //In terms of fields, it's not possible to be closer any more! + break; + + pContent = pContent->GetNextContentFrame(); + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + } + + //Bring the point into the PrtArea. + const SwRect aRect( pAct->getFrameArea().Pos() + pAct->getFramePrintArea().Pos(), pAct->getFramePrintArea().SSize() ); + if ( aAct.Y() < aRect.Top() ) + aAct.setY( aRect.Top() ); + else if ( aAct.Y() > aRect.Bottom() ) + aAct.setY( aRect.Bottom() ); + if ( aAct.X() < aRect.Left() ) + aAct.setX( aRect.Left() ); + else if ( aAct.X() > aRect.Right() ) + aAct.setX( aRect.Right() ); + + if (!pAct->isFrameAreaDefinitionValid() || + (pAct->IsTextFrame() && !static_cast<SwTextFrame const*>(pAct)->HasPara())) + { + // ContentFrame not formatted -> always on node-beginning + // tdf#100635 also if the SwTextFrame would require reformatting, + // which is unwanted in case this is called from text formatting code + rPos = static_cast<SwTextFrame const*>(pAct)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + pAct->GetModelPositionForViewPoint( &rPos, aAct, &aTmpState ); + } +} + +/** Search the nearest Content to the passed point. + * + * Only search inside the BodyText. + * @note Only the nearest vertically one will be searched. + * @note JP 11.10.2001: only in tables we try to find the right column - Bug 72294 + */ +Point SwRootFrame::GetNextPrevContentPos( const Point& rPoint, bool bNext ) const +{ + vcl::RenderContext* pRenderContext = GetCurrShell() ? GetCurrShell()->GetOut() : nullptr; + // #123110# - disable creation of an action by a callback + // event during processing of this method. Needed because formatting is + // triggered by this method. + DisableCallbackAction aDisableCallbackAction(const_cast<SwRootFrame&>(*this)); + //Search the first ContentFrame and his successor in the body area. + //To be efficient (and not formatting too much) we'll start at the correct + //page. + const SwLayoutFrame *pPage = static_cast<const SwLayoutFrame*>(Lower()); + if( pPage ) + while( pPage->GetNext() && pPage->getFrameArea().Bottom() < rPoint.Y() ) + pPage = static_cast<const SwLayoutFrame*>(pPage->GetNext()); + + const SwContentFrame *pCnt = pPage ? pPage->ContainsContent() : ContainsContent(); + while ( pCnt && !pCnt->IsInDocBody() ) + pCnt = pCnt->GetNextContentFrame(); + + if ( !pCnt ) + return Point( 0, 0 ); + + pCnt->Calc(pRenderContext); + if( !bNext ) + { + // As long as the point lies before the first ContentFrame and there are + // still precedent pages I'll go to the next page. + while ( rPoint.Y() < pCnt->getFrameArea().Top() && pPage->GetPrev() ) + { + pPage = static_cast<const SwLayoutFrame*>(pPage->GetPrev()); + pCnt = pPage->ContainsContent(); + while ( !pCnt ) + { + pPage = static_cast<const SwLayoutFrame*>(pPage->GetPrev()); + if ( pPage ) + pCnt = pPage->ContainsContent(); + else + return ContainsContent()->UnionFrame().Pos(); + } + pCnt->Calc(pRenderContext); + } + } + + //Does the point lie above the first ContentFrame? + if ( rPoint.Y() < pCnt->getFrameArea().Top() && !lcl_IsInRepeatedHeadline( pCnt ) ) + return pCnt->UnionFrame().Pos(); + + Point aRet(0, 0); + do + { + //Does the point lie in the current ContentFrame? + SwRect aContentFrame( pCnt->UnionFrame() ); + if ( aContentFrame.IsInside( rPoint ) && !lcl_IsInRepeatedHeadline( pCnt )) + { + aRet = rPoint; + break; + } + + //Is the current one the last ContentFrame? + //If the next ContentFrame lies behind the point, then the current on is the + //one we searched. + const SwContentFrame *pNxt = pCnt->GetNextContentFrame(); + while ( pNxt && !pNxt->IsInDocBody() ) + pNxt = pNxt->GetNextContentFrame(); + + //Does the point lie behind the last ContentFrame? + if ( !pNxt ) + { + aRet = Point( aContentFrame.Right(), aContentFrame.Bottom() ); + break; + } + + //If the next ContentFrame lies behind the point then it is the one we + //searched. + const SwTabFrame* pTFrame; + pNxt->Calc(pRenderContext); + if( pNxt->getFrameArea().Top() > rPoint.Y() && + !lcl_IsInRepeatedHeadline( pCnt, &pTFrame ) && + ( !pTFrame || pNxt->getFrameArea().Left() > rPoint.X() )) + { + if (bNext) + aRet = pNxt->getFrameArea().Pos(); + else + aRet = Point( aContentFrame.Right(), aContentFrame.Bottom() ); + break; + } + pCnt = pNxt; + } + while (pCnt); + return aRet; +} + +/** Returns the absolute document position of the desired page. + * + * Formatting is done only as far as needed and only if bFormat=true. + * Pos is set to the one of the last page, if the page number was chosen too big. + * + * @return Null, if the operation failed. + */ +Point SwRootFrame::GetPagePos( sal_uInt16 nPageNum ) const +{ + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "No page available." ); + + const SwPageFrame *pPage = static_cast<const SwPageFrame*>(Lower()); + while ( true ) + { + if ( pPage->GetPhyPageNum() >= nPageNum || !pPage->GetNext() ) + break; + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + return pPage->getFrameArea().Pos(); +} + +/** get page frame by physical page number + * + * @return pointer to the page frame with the given physical page number + */ +SwPageFrame* SwRootFrame::GetPageByPageNum( sal_uInt16 _nPageNum ) const +{ + const SwPageFrame* pPageFrame = static_cast<const SwPageFrame*>( Lower() ); + while ( pPageFrame && pPageFrame->GetPhyPageNum() < _nPageNum ) + { + pPageFrame = static_cast<const SwPageFrame*>( pPageFrame->GetNext() ); + } + + if ( pPageFrame && pPageFrame->GetPhyPageNum() == _nPageNum ) + { + return const_cast<SwPageFrame*>( pPageFrame ); + } + else + { + return nullptr; + } +} + +/** + * @return true, when the given physical pagenumber doesn't exist or this page is an empty page. + */ +bool SwRootFrame::IsDummyPage( sal_uInt16 nPageNum ) const +{ + if( !Lower() || !nPageNum || nPageNum > GetPageNum() ) + return true; + + const SwPageFrame *pPage = static_cast<const SwPageFrame*>(Lower()); + while( pPage && nPageNum < pPage->GetPhyPageNum() ) + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + return !pPage || pPage->IsEmptyPage(); +} + +/** Is the Frame or rather the Section in which it lies protected? + * + * Also Fly in Fly in ... and Footnotes + */ +bool SwFrame::IsProtected() const +{ + if (IsTextFrame()) + { + const SwDoc *pDoc = &static_cast<const SwTextFrame*>(this)->GetDoc(); + bool isFormProtected=pDoc->GetDocumentSettingManager().get(DocumentSettingId::PROTECT_FORM ); + if (isFormProtected) + { + return false; // TODO a hack for now, well deal with it later, I we return true here we have a "double" locking + } + } + //The Frame can be protected in borders, cells or sections. + //Also goes up FlyFrames recursive and from footnote to anchor. + const SwFrame *pFrame = this; + do + { + if (pFrame->IsTextFrame()) + { // sw_redlinehide: redlines can't overlap section nodes, so any node will do + if (static_cast<SwTextFrame const*>(pFrame)->GetTextNodeFirst()->IsInProtectSect()) + { + return true; + } + } + else if ( pFrame->IsContentFrame() ) + { + assert(pFrame->IsNoTextFrame()); + if (static_cast<const SwNoTextFrame*>(pFrame)->GetNode() && + static_cast<const SwNoTextFrame*>(pFrame)->GetNode()->IsInProtectSect()) + { + return true; + } + } + else + { + if ( static_cast<const SwLayoutFrame*>(pFrame)->GetFormat() && + static_cast<const SwLayoutFrame*>(pFrame)->GetFormat()-> + GetProtect().IsContentProtected() ) + return true; + if ( pFrame->IsCoveredCell() ) + return true; + } + if ( pFrame->IsFlyFrame() ) + { + //In a chain the protection of the content can be specified by the + //master of the chain. + if ( static_cast<const SwFlyFrame*>(pFrame)->GetPrevLink() ) + { + const SwFlyFrame *pMaster = static_cast<const SwFlyFrame*>(pFrame); + do + { pMaster = pMaster->GetPrevLink(); + } while ( pMaster->GetPrevLink() ); + if ( pMaster->IsProtected() ) + return true; + } + pFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + } + else if ( pFrame->IsFootnoteFrame() ) + pFrame = static_cast<const SwFootnoteFrame*>(pFrame)->GetRef(); + else + pFrame = pFrame->GetUpper(); + + } while ( pFrame ); + + return false; +} + +/** @return the physical page number */ +sal_uInt16 SwFrame::GetPhyPageNum() const +{ + const SwPageFrame *pPage = FindPageFrame(); + return pPage ? pPage->GetPhyPageNum() : 0; +} + +/** Decides if the page want to be a right page or not. + * + * If the first content of the page has a page descriptor, we take the follow + * of the page descriptor of the last not empty page. If this descriptor allows + * only right(left) pages and the page isn't an empty page then it wants to be + * such right(left) page. If the descriptor allows right and left pages, we + * look for a number offset in the first content. If there is one, odd number + * results right pages (or left pages if document starts with even number), + * even number results left pages (or right pages if document starts with even + * number). + * If there is no number offset, we take the physical page number instead, + * but a previous empty page doesn't count. + */ +bool SwFrame::WannaRightPage() const +{ + const SwPageFrame *pPage = FindPageFrame(); + if ( !pPage || !pPage->GetUpper() ) + return true; + + const SwFrame *pFlow = pPage->FindFirstBodyContent(); + const SwPageDesc *pDesc = nullptr; + ::std::optional<sal_uInt16> oPgNum; + if ( pFlow ) + { + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + const SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pFlow ); + if ( !pTmp->IsFollow() ) + { + const SwFormatPageDesc& rPgDesc = pFlow->GetPageDescItem(); + pDesc = rPgDesc.GetPageDesc(); + oPgNum = rPgDesc.GetNumOffset(); + } + } + if ( !pDesc ) + { + SwPageFrame *pPrv = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pPage->GetPrev())); + if( pPrv && pPrv->IsEmptyPage() ) + pPrv = static_cast<SwPageFrame*>(pPrv->GetPrev()); + if( pPrv ) + pDesc = pPrv->GetPageDesc()->GetFollow(); + else + { + const SwDoc* pDoc = pPage->GetFormat()->GetDoc(); + pDesc = &pDoc->GetPageDesc( 0 ); + } + } + OSL_ENSURE( pDesc, "No pagedescriptor" ); + bool isRightPage; + if( oPgNum ) + isRightPage = sw::IsRightPageByNumber(*mpRoot, *oPgNum); + else + { + isRightPage = pPage->OnRightPage(); + if( pPage->GetPrev() && static_cast<const SwPageFrame*>(pPage->GetPrev())->IsEmptyPage() ) + isRightPage = !isRightPage; + } + if( !pPage->IsEmptyPage() ) + { + if( !pDesc->GetRightFormat() ) + isRightPage = false; + else if( !pDesc->GetLeftFormat() ) + isRightPage = true; + } + return isRightPage; +} + +bool SwFrame::OnFirstPage() const +{ + bool bRet = false; + const SwPageFrame *pPage = FindPageFrame(); + + if (pPage) + { + const SwPageFrame* pPrevFrame = dynamic_cast<const SwPageFrame*>(pPage->GetPrev()); + if (pPrevFrame) + { + // first page of layout may be empty page, but only if it starts with "Left Page" style + const SwPageDesc* pDesc = pPage->GetPageDesc(); + bRet = pPrevFrame->GetPageDesc() != pDesc; + } + else + bRet = true; + } + return bRet; +} + +void SwFrame::Calc(vcl::RenderContext* pRenderContext) const +{ + if ( !isFrameAreaPositionValid() || !isFramePrintAreaValid() || !isFrameAreaSizeValid() ) + { + const_cast<SwFrame*>(this)->PrepareMake(pRenderContext); + } +} + +Point SwFrame::GetRelPos() const +{ + Point aRet( getFrameArea().Pos() ); + // here we cast since SwLayoutFrame is declared only as forwarded + aRet -= GetUpper()->getFramePrintArea().Pos(); + aRet -= GetUpper()->getFrameArea().Pos(); + return aRet; +} + +/** @return the virtual page number with the offset. */ +sal_uInt16 SwFrame::GetVirtPageNum() const +{ + const SwPageFrame *pPage = FindPageFrame(); + if ( !pPage || !pPage->GetUpper() ) + return 0; + + sal_uInt16 nPhyPage = pPage->GetPhyPageNum(); + if ( !static_cast<const SwRootFrame*>(pPage->GetUpper())->IsVirtPageNum() ) + return nPhyPage; + + //Search the nearest section using the virtual page number. + //Because searching backwards needs a lot of time we search specific using + //the dependencies. From the PageDescs we get the attributes and from the + //attributes we get the sections. + const SwPageFrame *pVirtPage = nullptr; + const SwFrame *pFrame = nullptr; + const SfxItemPool &rPool = pPage->GetFormat()->GetDoc()->GetAttrPool(); + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(RES_PAGEDESC)) + { + const SwFormatPageDesc *pDesc = dynamic_cast<const SwFormatPageDesc*>(pItem); + if ( !pDesc ) + continue; + + if ( pDesc->GetNumOffset() && pDesc->GetDefinedIn() ) + { + const SwModify *pMod = pDesc->GetDefinedIn(); + SwVirtPageNumInfo aInfo( pPage ); + pMod->GetInfo( aInfo ); + if ( aInfo.GetPage() ) + { + if( !pVirtPage || aInfo.GetPage()->GetPhyPageNum() > pVirtPage->GetPhyPageNum() ) + { + pVirtPage = aInfo.GetPage(); + pFrame = aInfo.GetFrame(); + } + } + } + } + if ( pFrame ) + { + ::std::optional<sal_uInt16> oNumOffset = pFrame->GetPageDescItem().GetNumOffset(); + if (oNumOffset) + { + return nPhyPage - pFrame->GetPhyPageNum() + *oNumOffset; + } + else + { + return nPhyPage - pFrame->GetPhyPageNum(); + } + } + return nPhyPage; +} + +/** Determines and sets those cells which are enclosed by the selection. */ +bool SwRootFrame::MakeTableCursors( SwTableCursor& rTableCursor ) +{ + //Find Union-Rects and tables (Follows) of the selection. + OSL_ENSURE( rTableCursor.GetContentNode() && rTableCursor.GetContentNode( false ), + "Tabselection not on Cnt." ); + + bool bRet = false; + + // For new table models there's no need to ask the layout... + if( rTableCursor.NewTableSelection() ) + return true; + + Point aPtPt, aMkPt; + { + SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rTableCursor); + + if( pShCursor ) + { + aPtPt = pShCursor->GetPtPos(); + aMkPt = pShCursor->GetMkPos(); + } + } + + // #151012# Made code robust here + const SwContentNode* pTmpStartNode = rTableCursor.GetContentNode(); + const SwContentNode* pTmpEndNode = rTableCursor.GetContentNode(false); + + std::pair<Point, bool> tmp(aPtPt, false); + const SwFrame *const pTmpStartFrame = pTmpStartNode ? pTmpStartNode->getLayoutFrame(this, nullptr, &tmp) : nullptr; + tmp.first = aMkPt; + const SwFrame *const pTmpEndFrame = pTmpEndNode ? pTmpEndNode->getLayoutFrame(this, nullptr, &tmp) : nullptr; + + const SwLayoutFrame* pStart = pTmpStartFrame ? pTmpStartFrame->GetUpper() : nullptr; + const SwLayoutFrame* pEnd = pTmpEndFrame ? pTmpEndFrame->GetUpper() : nullptr; + + OSL_ENSURE( pStart && pEnd, "MakeTableCursors: Good to have the code robust here!" ); + + /* #109590# Only change table boxes if the frames are + valid. Needed because otherwise the table cursor after moving + table cells by dnd resulted in an empty tables cursor. */ + if ( pStart && pEnd && pStart->isFrameAreaDefinitionValid() && pEnd->isFrameAreaDefinitionValid()) + { + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + SwSelBoxes aNew; + + const bool bReadOnlyAvailable = rTableCursor.IsReadOnlyAvailable(); + + for (SwSelUnion & rUnion : aUnions) + { + const SwTabFrame *pTable = rUnion.GetTable(); + + // Skip any repeated headlines in the follow: + SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pTable->Lower())); + + while ( pRow ) + { + if ( pRow->getFrameArea().IsOver( rUnion.GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while ( pCell && pRow->IsAnLower( pCell ) ) + { + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + if( IsFrameInTableSel( rUnion.GetUnion(), pCell ) && + (bReadOnlyAvailable || + !pCell->GetFormat()->GetProtect().IsContentProtected())) + { + SwTableBox* pInsBox = const_cast<SwTableBox*>( + static_cast<const SwCellFrame*>(pCell)->GetTabBox()); + aNew.insert( pInsBox ); + } + if ( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + { + const SwLayoutFrame* pLastCell = pCell; + do + { + pCell = pCell->GetNextLayoutLeaf(); + } while ( pCell && pLastCell->IsAnLower( pCell ) ); + // For sections with columns + if( pCell && pCell->IsInTab() ) + { + while( !pCell->IsCellFrame() ) + { + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "Where's my cell?" ); + } + } + } + } + } + pRow = static_cast<SwLayoutFrame*>(pRow->GetNext()); + } + } + + rTableCursor.ActualizeSelection( aNew ); + bRet = true; + } + + return bRet; +} + +static void Sub( SwRegionRects& rRegion, const SwRect& rRect ) +{ + if( rRect.Width() > 1 && rRect.Height() > 1 && + rRect.IsOver( rRegion.GetOrigin() )) + rRegion -= rRect; +} + +static void Add( SwRegionRects& rRegion, const SwRect& rRect ) +{ + if( rRect.Width() > 1 && rRect.Height() > 1 ) + rRegion += rRect; +} + +/* + * The following situations can happen: + * 1. Start and end lie in one screen-row and in the same node + * -> one rectangle out of start and end; and we're okay + * 2. Start and end lie in one frame (therefore in the same node!) + * -> expand start to the right, end to the left and if more than two + * screen-rows are involved - calculate the in-between + * 3. Start and end lie in different frames + * -> expand start to the right until frame-end, calculate Rect + * expand end to the left until frame-start, calculate Rect + * and if more than two frames are involved add the PrtArea of all + * frames which lie in between + * + * Big reorganization because of the FlyFrame - those need to be locked out. + * Exceptions: - The Fly in which the selection took place (if it took place + * in a Fly) + * - The Flys which are underrun by the text + * - The Flys which are anchored to somewhere inside the selection. + * Functioning: First a SwRegion with a root gets initialized. + * Out of the region the inverted sections are cut out. The + * section gets compressed and finally inverted and thereby the + * inverted rectangles are available. + * In the end the Flys are cut out of the section. + */ +void SwRootFrame::CalcFrameRects(SwShellCursor &rCursor) +{ + SwPosition *pStartPos = rCursor.Start(), + *pEndPos = rCursor.GetPoint() == pStartPos ? rCursor.GetMark() : rCursor.GetPoint(); + + SwViewShell *pSh = GetCurrShell(); + + bool bIgnoreVisArea = true; + if (pSh) + bIgnoreVisArea = pSh->GetViewOptions()->IsPDFExport() || comphelper::LibreOfficeKit::isActive(); + + // #i12836# enhanced pdf + SwRegionRects aRegion( !bIgnoreVisArea ? + pSh->VisArea() : + getFrameArea() ); + if( !pStartPos->nNode.GetNode().IsContentNode() || + !pStartPos->nNode.GetNode().GetContentNode()->getLayoutFrame(this) || + ( pStartPos->nNode != pEndPos->nNode && + ( !pEndPos->nNode.GetNode().IsContentNode() || + !pEndPos->nNode.GetNode().GetContentNode()->getLayoutFrame(this) ) ) ) + { + return; + } + + DisableCallbackAction a(*this); // the GetCharRect below may format + + //First obtain the ContentFrames for the start and the end - those are needed + //anyway. + std::pair<Point, bool> tmp(rCursor.GetSttPos(), true); + SwContentFrame* pStartFrame = pStartPos->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, pStartPos, &tmp); + + tmp.first = rCursor.GetEndPos(); + SwContentFrame* pEndFrame = pEndPos->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, pEndPos, &tmp); + + assert(pStartFrame && pEndFrame && "No ContentFrames found."); + //tdf#119224 start and end are expected to exist for the scope of this function + SwFrameDeleteGuard aStartFrameGuard(pStartFrame), aEndFrameGuard(pEndFrame); + + //Do not subtract the FlyFrames in which selected Frames lie. + SwSortedObjs aSortObjs; + if ( pStartFrame->IsInFly() ) + { + const SwAnchoredObject* pObj = pStartFrame->FindFlyFrame(); + OSL_ENSURE( pObj, "No Start Object." ); + if (pObj) aSortObjs.Insert( *const_cast<SwAnchoredObject*>(pObj) ); + const SwAnchoredObject* pObj2 = pEndFrame->FindFlyFrame(); + OSL_ENSURE( pObj2, "SwRootFrame::CalcFrameRects(..) - FlyFrame missing - looks like an invalid selection" ); + if ( pObj2 != nullptr && pObj2 != pObj ) + { + aSortObjs.Insert( *const_cast<SwAnchoredObject*>(pObj2) ); + } + } + + // if a selection which is not allowed exists, we correct what is not + // allowed (header/footer/table-headline) for two pages. + do { // middle check loop + const SwLayoutFrame* pSttLFrame = pStartFrame->GetUpper(); + const SwFrameType cHdFtTableHd = SwFrameType::Header | SwFrameType::Footer | SwFrameType::Tab; + while( pSttLFrame && + ! (cHdFtTableHd & pSttLFrame->GetType() )) + pSttLFrame = pSttLFrame->GetUpper(); + if( !pSttLFrame ) + break; + const SwLayoutFrame* pEndLFrame = pEndFrame->GetUpper(); + while( pEndLFrame && + ! (cHdFtTableHd & pEndLFrame->GetType() )) + pEndLFrame = pEndLFrame->GetUpper(); + if( !pEndLFrame ) + break; + + OSL_ENSURE( pEndLFrame->GetType() == pSttLFrame->GetType(), + "Selection over different content" ); + switch( pSttLFrame->GetType() ) + { + case SwFrameType::Header: + case SwFrameType::Footer: + // On different pages? Then always on the start-page + if( pEndLFrame->FindPageFrame() != pSttLFrame->FindPageFrame() ) + { + // Set end- to the start-ContentFrame + if( pStartPos == rCursor.GetPoint() ) + pEndFrame = pStartFrame; + else + pStartFrame = pEndFrame; + } + break; + case SwFrameType::Tab: + // On different pages? Then check for table-headline + { + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pSttLFrame); + if( ( pTabFrame->GetFollow() || + static_cast<const SwTabFrame*>(pEndLFrame)->GetFollow() ) && + pTabFrame->GetTable()->GetRowsToRepeat() > 0 && + pTabFrame->GetLower() != static_cast<const SwTabFrame*>(pEndLFrame)->GetLower() && + ( lcl_IsInRepeatedHeadline( pStartFrame ) || + lcl_IsInRepeatedHeadline( pEndFrame ) ) ) + { + // Set end- to the start-ContentFrame + if( pStartPos == rCursor.GetPoint() ) + pEndFrame = pStartFrame; + else + pStartFrame = pEndFrame; + } + } + break; + default: break; + } + } while( false ); + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_b2Lines = true; + aTmpState.m_bNoScroll = true; + aTmpState.m_nCursorBidiLevel = pStartFrame->IsRightToLeft() ? 1 : 0; + + //ContentRects to Start- and EndFrames. + SwRect aStRect, aEndRect; + pStartFrame->GetCharRect( aStRect, *pStartPos, &aTmpState ); + std::unique_ptr<Sw2LinesPos> pSt2Pos = std::move(aTmpState.m_p2Lines); + aTmpState.m_nCursorBidiLevel = pEndFrame->IsRightToLeft() ? 1 : 0; + + pEndFrame->GetCharRect( aEndRect, *pEndPos, &aTmpState ); + std::unique_ptr<Sw2LinesPos> pEnd2Pos = std::move(aTmpState.m_p2Lines); + + SwRect aStFrame ( pStartFrame->UnionFrame( true ) ); + aStFrame.Intersection( pStartFrame->GetPaintArea() ); + SwRect aEndFrame( pStartFrame == pEndFrame ? aStFrame : pEndFrame->UnionFrame( true ) ); + if( pStartFrame != pEndFrame ) + { + aEndFrame.Intersection( pEndFrame->GetPaintArea() ); + } + SwRectFnSet aRectFnSet(pStartFrame); + const bool bR2L = pStartFrame->IsRightToLeft(); + const bool bEndR2L = pEndFrame->IsRightToLeft(); + const bool bB2T = pStartFrame->IsVertLRBT(); + + // If there's no doubleline portion involved or start and end are both + // in the same doubleline portion, all works fine, but otherwise + // we need the following... + if( pSt2Pos != pEnd2Pos && ( !pSt2Pos || !pEnd2Pos || + pSt2Pos->aPortion != pEnd2Pos->aPortion ) ) + { + // If we have a start(end) position inside a doubleline portion + // the surrounded part of the doubleline portion is subtracted + // from the region and the aStRect(aEndRect) is set to the + // end(start) of the doubleline portion. + if( pSt2Pos ) + { + SwRect aTmp( aStRect ); + + // BiDi-Portions are swimming against the current. + const bool bPorR2L = ( MultiPortionType::BIDI == pSt2Pos->nMultiType ) ? + ! bR2L : + bR2L; + + if( MultiPortionType::BIDI == pSt2Pos->nMultiType && + aRectFnSet.GetWidth(pSt2Pos->aPortion2) ) + { + // nested bidi portion + long nRightAbs = aRectFnSet.GetRight(pSt2Pos->aPortion); + nRightAbs -= aRectFnSet.GetLeft(pSt2Pos->aPortion2); + long nLeftAbs = nRightAbs - aRectFnSet.GetWidth(pSt2Pos->aPortion2); + + aRectFnSet.SetRight( aTmp, nRightAbs ); + + if ( ! pEnd2Pos || pEnd2Pos->aPortion != pSt2Pos->aPortion ) + { + SwRect aTmp2( pSt2Pos->aPortion ); + aRectFnSet.SetRight( aTmp2, nLeftAbs ); + aTmp2.Intersection( aEndFrame ); + Sub( aRegion, aTmp2 ); + } + } + else + { + if( bPorR2L ) + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(pSt2Pos->aPortion) ); + else + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(pSt2Pos->aPortion) ); + } + + if( MultiPortionType::ROT_90 == pSt2Pos->nMultiType || + aRectFnSet.GetTop(pSt2Pos->aPortion) == + aRectFnSet.GetTop(aTmp) ) + { + aRectFnSet.SetTop( aTmp, aRectFnSet.GetTop(pSt2Pos->aLine) ); + } + + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + + SwTwips nTmp = aRectFnSet.GetBottom(pSt2Pos->aLine); + if( MultiPortionType::ROT_90 != pSt2Pos->nMultiType && + aRectFnSet.BottomDist( aStRect, nTmp ) > 0 ) + { + aRectFnSet.SetTop( aTmp, aRectFnSet.GetBottom(aTmp) ); + aRectFnSet.SetBottom( aTmp, nTmp ); + if( aRectFnSet.BottomDist( aStRect, aRectFnSet.GetBottom(pSt2Pos->aPortion) ) > 0 ) + { + if( bPorR2L ) + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(pSt2Pos->aPortion) ); + else + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(pSt2Pos->aPortion) ); + } + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + } + + aStRect = pSt2Pos->aLine; + aRectFnSet.SetLeft( aStRect, bR2L ? + aRectFnSet.GetLeft(pSt2Pos->aPortion) : + aRectFnSet.GetRight(pSt2Pos->aPortion) ); + aRectFnSet.SetWidth( aStRect, 1 ); + } + + if( pEnd2Pos ) + { + SwRectFnSet fnRectX(pEndFrame); + SwRect aTmp( aEndRect ); + + // BiDi-Portions are swimming against the current. + const bool bPorR2L = ( MultiPortionType::BIDI == pEnd2Pos->nMultiType ) ? + ! bEndR2L : + bEndR2L; + + if( MultiPortionType::BIDI == pEnd2Pos->nMultiType && + fnRectX.GetWidth(pEnd2Pos->aPortion2) ) + { + // nested bidi portion + long nRightAbs = fnRectX.GetRight(pEnd2Pos->aPortion); + nRightAbs = nRightAbs - fnRectX.GetLeft(pEnd2Pos->aPortion2); + long nLeftAbs = nRightAbs - fnRectX.GetWidth(pEnd2Pos->aPortion2); + + fnRectX.SetLeft( aTmp, nLeftAbs ); + + if ( ! pSt2Pos || pSt2Pos->aPortion != pEnd2Pos->aPortion ) + { + SwRect aTmp2( pEnd2Pos->aPortion ); + fnRectX.SetLeft( aTmp2, nRightAbs ); + aTmp2.Intersection( aEndFrame ); + Sub( aRegion, aTmp2 ); + } + } + else + { + if ( bPorR2L ) + fnRectX.SetRight( aTmp, fnRectX.GetRight(pEnd2Pos->aPortion) ); + else + fnRectX.SetLeft( aTmp, fnRectX.GetLeft(pEnd2Pos->aPortion) ); + } + + if( MultiPortionType::ROT_90 == pEnd2Pos->nMultiType || + fnRectX.GetBottom(pEnd2Pos->aPortion) == + fnRectX.GetBottom(aEndRect) ) + { + fnRectX.SetBottom( aTmp, fnRectX.GetBottom(pEnd2Pos->aLine) ); + } + + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + + // The next statement means neither ruby nor rotate(90): + if( MultiPortionType::RUBY != pEnd2Pos->nMultiType && MultiPortionType::ROT_90 != pEnd2Pos->nMultiType ) + { + SwTwips nTmp = fnRectX.GetTop(pEnd2Pos->aLine); + if( fnRectX.GetTop(aEndRect) != nTmp ) + { + fnRectX.SetBottom( aTmp, fnRectX.GetTop(aTmp) ); + fnRectX.SetTop( aTmp, nTmp ); + if( fnRectX.GetTop(aEndRect) != + fnRectX.GetTop(pEnd2Pos->aPortion) ) + { + if( bPorR2L ) + fnRectX.SetLeft( aTmp, fnRectX.GetLeft(pEnd2Pos->aPortion) ); + else + fnRectX.SetRight( aTmp, fnRectX.GetRight(pEnd2Pos->aPortion) ); + } + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + } + } + + aEndRect = pEnd2Pos->aLine; + fnRectX.SetLeft( aEndRect, bEndR2L ? + fnRectX.GetRight(pEnd2Pos->aPortion) : + fnRectX.GetLeft(pEnd2Pos->aPortion) ); + fnRectX.SetWidth( aEndRect, 1 ); + } + } + else if( pSt2Pos && pEnd2Pos && + MultiPortionType::BIDI == pSt2Pos->nMultiType && + MultiPortionType::BIDI == pEnd2Pos->nMultiType && + pSt2Pos->aPortion == pEnd2Pos->aPortion && + pSt2Pos->aPortion2 != pEnd2Pos->aPortion2 ) + { + // This is the ugly special case, where the selection starts and + // ends in the same bidi portion but one start or end is inside a + // nested bidi portion. + + if ( aRectFnSet.GetWidth(pSt2Pos->aPortion2) ) + { + SwRect aTmp( aStRect ); + long nRightAbs = aRectFnSet.GetRight(pSt2Pos->aPortion); + nRightAbs -= aRectFnSet.GetLeft(pSt2Pos->aPortion2); + long nLeftAbs = nRightAbs - aRectFnSet.GetWidth(pSt2Pos->aPortion2); + + aRectFnSet.SetRight( aTmp, nRightAbs ); + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + + aStRect = pSt2Pos->aLine; + aRectFnSet.SetLeft( aStRect, bR2L ? nRightAbs : nLeftAbs ); + aRectFnSet.SetWidth( aStRect, 1 ); + } + + SwRectFnSet fnRectX(pEndFrame); + if ( fnRectX.GetWidth(pEnd2Pos->aPortion2) ) + { + SwRect aTmp( aEndRect ); + long nRightAbs = fnRectX.GetRight(pEnd2Pos->aPortion); + nRightAbs -= fnRectX.GetLeft(pEnd2Pos->aPortion2); + long nLeftAbs = nRightAbs - fnRectX.GetWidth(pEnd2Pos->aPortion2); + + fnRectX.SetLeft( aTmp, nLeftAbs ); + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + + aEndRect = pEnd2Pos->aLine; + fnRectX.SetLeft( aEndRect, bEndR2L ? nLeftAbs : nRightAbs ); + fnRectX.SetWidth( aEndRect, 1 ); + } + } + + // The charrect may be outside the paintarea (for cursortravelling) + // but the selection has to be restricted to the paintarea + if( aStRect.Left() < aStFrame.Left() ) + aStRect.Left( aStFrame.Left() ); + else if( aStRect.Left() > aStFrame.Right() ) + aStRect.Left( aStFrame.Right() ); + SwTwips nTmp = aStRect.Right(); + if( nTmp < aStFrame.Left() ) + aStRect.Right( aStFrame.Left() ); + else if( nTmp > aStFrame.Right() ) + aStRect.Right( aStFrame.Right() ); + if( aEndRect.Left() < aEndFrame.Left() ) + aEndRect.Left( aEndFrame.Left() ); + else if( aEndRect.Left() > aEndFrame.Right() ) + aEndRect.Left( aEndFrame.Right() ); + nTmp = aEndRect.Right(); + if( nTmp < aEndFrame.Left() ) + aEndRect.Right( aEndFrame.Left() ); + else if( nTmp > aEndFrame.Right() ) + aEndRect.Right( aEndFrame.Right() ); + + if( pStartFrame == pEndFrame ) + { + bool bSameRotatedOrBidi = pSt2Pos && pEnd2Pos && + ( MultiPortionType::BIDI == pSt2Pos->nMultiType || + MultiPortionType::ROT_270 == pSt2Pos->nMultiType || + MultiPortionType::ROT_90 == pSt2Pos->nMultiType ) && + pSt2Pos->aPortion == pEnd2Pos->aPortion; + //case 1: (Same frame and same row) + if( bSameRotatedOrBidi || + aRectFnSet.GetTop(aStRect) == aRectFnSet.GetTop(aEndRect) ) + { + Point aTmpSt( aStRect.Pos() ); + Point aTmpEnd( aEndRect.Right(), aEndRect.Bottom() ); + if (bSameRotatedOrBidi || bR2L || bB2T) + { + if( aTmpSt.Y() > aTmpEnd.Y() ) + { + long nTmpY = aTmpEnd.Y(); + aTmpEnd.setY( aTmpSt.Y() ); + aTmpSt.setY( nTmpY ); + } + if( aTmpSt.X() > aTmpEnd.X() ) + { + long nTmpX = aTmpEnd.X(); + aTmpEnd.setX( aTmpSt.X() ); + aTmpSt.setX( nTmpX ); + } + } + + SwRect aTmp( aTmpSt, aTmpEnd ); + // Bug 34888: If content is selected which doesn't take space + // away (i.e. PostIts, RefMarks, TOXMarks), then at + // least set the width of the Cursor. + if( 1 == aRectFnSet.GetWidth(aTmp) && + pStartPos->nContent.GetIndex() != + pEndPos->nContent.GetIndex() ) + { + OutputDevice* pOut = pSh->GetOut(); + long nCursorWidth = pOut->GetSettings().GetStyleSettings(). + GetCursorSize(); + aRectFnSet.SetWidth( aTmp, pOut->PixelToLogic( + Size( nCursorWidth, 0 ) ).Width() ); + } + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + } + //case 2: (Same frame, but not the same line) + else + { + SwTwips lLeft, lRight; + if( pSt2Pos && pEnd2Pos && pSt2Pos->aPortion == pEnd2Pos->aPortion ) + { + lLeft = aRectFnSet.GetLeft(pSt2Pos->aPortion); + lRight = aRectFnSet.GetRight(pSt2Pos->aPortion); + } + else + { + lLeft = aRectFnSet.GetLeft(pStartFrame->getFrameArea()) + + aRectFnSet.GetLeft(pStartFrame->getFramePrintArea()); + lRight = aRectFnSet.GetRight(aEndFrame); + } + if( lLeft < aRectFnSet.GetLeft(aStFrame) ) + lLeft = aRectFnSet.GetLeft(aStFrame); + if( lRight > aRectFnSet.GetRight(aStFrame) ) + lRight = aRectFnSet.GetRight(aStFrame); + SwRect aSubRect( aStRect ); + //First line + if( bR2L ) + aRectFnSet.SetLeft( aSubRect, lLeft ); + else + aRectFnSet.SetRight( aSubRect, lRight ); + Sub( aRegion, aSubRect ); + + //If there's at least a twips between start- and endline, + //so the whole area between will be added. + SwTwips aTmpBottom = aRectFnSet.GetBottom(aStRect); + SwTwips aTmpTop = aRectFnSet.GetTop(aEndRect); + if( aTmpBottom != aTmpTop ) + { + aRectFnSet.SetLeft( aSubRect, lLeft ); + aRectFnSet.SetRight( aSubRect, lRight ); + aRectFnSet.SetTop( aSubRect, aTmpBottom ); + aRectFnSet.SetBottom( aSubRect, aTmpTop ); + Sub( aRegion, aSubRect ); + } + //and the last line + aSubRect = aEndRect; + if( bR2L ) + aRectFnSet.SetRight( aSubRect, lRight ); + else + aRectFnSet.SetLeft( aSubRect, lLeft ); + Sub( aRegion, aSubRect ); + } + } + //case 3: (Different frames, maybe with other frames between) + else + { + //The startframe first... + SwRect aSubRect( aStRect ); + if( bR2L ) + aRectFnSet.SetLeft( aSubRect, aRectFnSet.GetLeft(aStFrame)); + else + aRectFnSet.SetRight( aSubRect, aRectFnSet.GetRight(aStFrame)); + Sub( aRegion, aSubRect ); + SwTwips nTmpTwips = aRectFnSet.GetBottom(aStRect); + if( aRectFnSet.GetBottom(aStFrame) != nTmpTwips ) + { + aSubRect = aStFrame; + aRectFnSet.SetTop( aSubRect, nTmpTwips ); + Sub( aRegion, aSubRect ); + } + + //Now the frames between, if there are any + bool const bBody = pStartFrame->IsInDocBody(); + const SwTableBox* pCellBox = pStartFrame->GetUpper()->IsCellFrame() ? + static_cast<const SwCellFrame*>(pStartFrame->GetUpper())->GetTabBox() : nullptr; + if (pSh->IsSelectAll()) + pCellBox = nullptr; + + const SwContentFrame *pContent = pStartFrame->GetNextContentFrame(); + SwRect aPrvRect; + + OSL_ENSURE( pContent, + "<SwRootFrame::CalcFrameRects(..)> - no content frame. This is a serious defect" ); + while ( pContent && pContent != pEndFrame ) + { + if ( pContent->IsInFly() ) + { + const SwAnchoredObject* pObj = pContent->FindFlyFrame(); + if (!aSortObjs.Contains(*pObj)) + { // is this even possible, assuming valid cursor pos.? + aSortObjs.Insert( *const_cast<SwAnchoredObject*>(pObj) ); + } + } + + // Consider only frames which have the same IsInDocBody value like pStartFrame + // If pStartFrame is inside a SwCellFrame, consider only frames which are inside the + // same cell frame (or its follow cell) + const SwTableBox* pTmpCellBox = pContent->GetUpper()->IsCellFrame() ? + static_cast<const SwCellFrame*>(pContent->GetUpper())->GetTabBox() : nullptr; + if (pSh->IsSelectAll()) + pTmpCellBox = nullptr; + if ( bBody == pContent->IsInDocBody() && + ( !pCellBox || pCellBox == pTmpCellBox ) ) + { + SwRect aCRect( pContent->UnionFrame( true ) ); + aCRect.Intersection( pContent->GetPaintArea() ); + if( aCRect.IsOver( aRegion.GetOrigin() )) + { + SwRect aTmp( aPrvRect ); + aTmp.Union( aCRect ); + if ( (aPrvRect.Height() * aPrvRect.Width() + + aCRect.Height() * aCRect.Width()) == + (aTmp.Height() * aTmp.Width()) ) + { + aPrvRect.Union( aCRect ); + } + else + { + if ( aPrvRect.HasArea() ) + Sub( aRegion, aPrvRect ); + aPrvRect = aCRect; + } + } + } + pContent = pContent->GetNextContentFrame(); + OSL_ENSURE( pContent, + "<SwRootFrame::CalcFrameRects(..)> - no content frame. This is a serious defect!" ); + } + if ( aPrvRect.HasArea() ) + Sub( aRegion, aPrvRect ); + + //At least the endframe... + aRectFnSet.Refresh(pEndFrame); + nTmpTwips = aRectFnSet.GetTop(aEndRect); + if( aRectFnSet.GetTop(aEndFrame) != nTmpTwips ) + { + aSubRect = aEndFrame; + aRectFnSet.SetBottom( aSubRect, nTmpTwips ); + Sub( aRegion, aSubRect ); + } + aSubRect = aEndRect; + if( bEndR2L ) + aRectFnSet.SetRight(aSubRect, aRectFnSet.GetRight(aEndFrame)); + else + aRectFnSet.SetLeft( aSubRect, aRectFnSet.GetLeft(aEndFrame) ); + Sub( aRegion, aSubRect ); + } + + aRegion.Invert(); + pSt2Pos.reset(); + pEnd2Pos.reset(); + + // Cut out Flys during loop. We don't cut out Flys when: + // - the Lower is StartFrame/EndFrame (FlyInCnt and all other Flys which again + // sit in it) + // - if in the Z-order we have Flys above those in which the StartFrame is + // placed + // - if they are anchored to inside the selection and thus part of it + const SwPageFrame *pPage = pStartFrame->FindPageFrame(); + const SwPageFrame *pEndPage = pEndFrame->FindPageFrame(); + + while ( pPage ) + { + if ( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr ) + continue; + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pAnchoredObj); + const SwVirtFlyDrawObj* pObj = pFly->GetVirtDrawObj(); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat().GetAnchor()); + const SwPosition* anchoredAt = rAnchor.GetContentAnchor(); + bool inSelection = ( + anchoredAt != nullptr + && ( (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR + && IsDestroyFrameAnchoredAtChar(*anchoredAt, *pStartPos, *pEndPos)) + || (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + && IsSelectFrameAnchoredAtPara(*anchoredAt, *pStartPos, *pEndPos)))); + if( inSelection ) + Add( aRegion, pFly->getFrameArea() ); + else if ( !pFly->IsAnLower( pStartFrame ) && + (rSur.GetSurround() != css::text::WrapTextMode_THROUGH && + !rSur.IsContour()) ) + { + if ( aSortObjs.Contains( *pAnchoredObj ) ) + continue; + + bool bSub = true; + const sal_uInt32 nPos = pObj->GetOrdNum(); + for ( size_t k = 0; bSub && k < aSortObjs.size(); ++k ) + { + OSL_ENSURE( dynamic_cast< const SwFlyFrame *>( aSortObjs[k] ) != nullptr, + "<SwRootFrame::CalcFrameRects(..)> - object in <aSortObjs> of unexpected type" ); + const SwFlyFrame* pTmp = static_cast<SwFlyFrame*>(aSortObjs[k]); + do + { + if ( nPos < pTmp->GetVirtDrawObj()->GetOrdNumDirect() ) + { + bSub = false; + } + else + { + pTmp = pTmp->GetAnchorFrame()->FindFlyFrame(); + } + } while ( bSub && pTmp ); + } + if ( bSub ) + Sub( aRegion, pFly->getFrameArea() ); + } + } + } + if ( pPage == pEndPage ) + break; + else + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + + //Because it looks better, we close the DropCaps. + SwRect aDropRect; + if ( pStartFrame->IsTextFrame() ) + { + if ( static_cast<const SwTextFrame*>(pStartFrame)->GetDropRect( aDropRect ) ) + Sub( aRegion, aDropRect ); + } + if ( pEndFrame != pStartFrame && pEndFrame->IsTextFrame() ) + { + if ( static_cast<const SwTextFrame*>(pEndFrame)->GetDropRect( aDropRect ) ) + Sub( aRegion, aDropRect ); + } + + rCursor.assign( aRegion.begin(), aRegion.end() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/unusedf.cxx b/sw/source/core/layout/unusedf.cxx new file mode 100644 index 000000000..fea1c0551 --- /dev/null +++ b/sw/source/core/layout/unusedf.cxx @@ -0,0 +1,78 @@ +/* -*- 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 <rootfrm.hxx> +#include <cntfrm.hxx> +#include <flyfrm.hxx> + +void SwFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + OSL_FAIL( "Format() of the base class called." ); +} + +void SwFrame::PaintSwFrame(vcl::RenderContext&, SwRect const&, SwPrintData const*const) const +{ + OSL_FAIL( "PaintSwFrame() of the base class called." ); +} + +bool SwContentFrame::WouldFit( SwTwips &, bool&, bool ) +{ + OSL_FAIL( "WouldFit of ContentFrame called." ); + return false; +} + +bool SwFrame::FillSelection( SwSelectionList& , const SwRect& ) const +{ + OSL_FAIL( "Don't call this function at the base class!" ); + return false; +} + +bool SwFrame::GetModelPositionForViewPoint( SwPosition *, Point&, SwCursorMoveState*, bool ) const +{ + OSL_FAIL( "GetModelPositionForViewPoint of the base class, hi!" ); + return false; +} + +#ifdef DBG_UTIL + +void SwRootFrame::Cut() +{ + OSL_FAIL( "Cut() of RootFrame called." ); +} + +void SwRootFrame::Paste( SwFrame *, SwFrame * ) +{ + OSL_FAIL( "Paste() of RootFrame called." ); +} + +void SwFlyFrame::Paste( SwFrame *, SwFrame * ) +{ + OSL_FAIL( "Paste() of FlyFrame called." ); +} + +#endif + +bool SwFrame::GetCharRect( SwRect&, const SwPosition&, + SwCursorMoveState*, bool ) const +{ + OSL_FAIL( "GetCharRect() of the base called." ); + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/virtoutp.cxx b/sw/source/core/layout/virtoutp.cxx new file mode 100644 index 000000000..ace9cb283 --- /dev/null +++ b/sw/source/core/layout/virtoutp.cxx @@ -0,0 +1,188 @@ +/* -*- 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 "virtoutp.hxx" +#include <viewopt.hxx> +#include <rootfrm.hxx> + +/* The SWLayVout class manages the virtual output devices. + * RootFrame has a static member of this class which is created in FrameInit + * and destroyed in FrameFinit. + * */ + +bool SwRootFrame::FlushVout() +{ + if (SwRootFrame::s_pVout->IsFlushable()) + { + SwRootFrame::s_pVout->Flush_(); + return true; + } + return false; +} + +bool SwRootFrame::HasSameRect( const SwRect& rRect ) +{ + if (SwRootFrame::s_pVout->IsFlushable()) + return ( rRect == SwRootFrame::s_pVout->GetOrgRect() ); + return false; +} + +/** method to set mapping/pixel offset for virtual output device + + OD 12.11.2002 #96272# - method implements two solutions for the mapping of + the virtual output device: + The old solution set the origin of the mapping mode, which will be used in + the virtual output device. This causes several paint errors, because of the + different roundings in the virtual output device and the original output device. + The new solution avoids the rounding differences between virtual and original + output device by setting a pixel offset at the virtual output device. + A define controls, which solution is used, in order to switch in escalation + back to old solution. + + @param _pOrgOutDev + input parameter - constant instance of the original output device, for which + the virtual output device is created. + + @param _pVirDev + input/output parameter - instance of the virtual output device. + + @param _rNewOrigin + input parameter - constant instance of the origin, which will be used in + the virtual output device +*/ +// define to control, if old or new solution for setting the mapping for +// a virtual output device is used. +static void SetMappingForVirtDev( const Point& _rNewOrigin, + const vcl::RenderContext* _pOrgOutDev, + vcl::RenderContext* _pVirDev ) +{ + // new solution: set pixel offset at virtual output device + Point aPixelOffset = _pOrgOutDev->LogicToPixel( _rNewOrigin ); + _pVirDev->SetPixelOffset( Size( -aPixelOffset.X(), -aPixelOffset.Y() ) ); +} + +// rSize must be pixel coordinates! +bool SwLayVout::DoesFit( const Size &rNew ) +{ + if( rNew.Height() > VIRTUALHEIGHT ) + return false; + if( rNew.IsEmpty() ) + return false; + if( rNew.Width() <= aSize.Width() ) + return true; + if( !pVirDev ) + { + pVirDev = VclPtr<VirtualDevice>::Create(); + pVirDev->SetLineColor(); + if( pOut ) + { + if( pVirDev->GetFillColor() != pOut->GetFillColor() ) + pVirDev->SetFillColor( pOut->GetFillColor() ); + } + } + + if( rNew.Width() > aSize.Width() ) + { + aSize.setWidth( rNew.Width() ); + if( !pVirDev->SetOutputSizePixel( aSize ) ) + { + pVirDev.disposeAndClear(); + aSize.setWidth( 0 ); + return false; + } + } + return true; +} + +/// change 2nd parameter <rRect> - no longer <const> +/// in order to return value of class member variable <aRect>, if virtual +/// output is used. +/// <aRect> contains the rectangle that represents the area the virtual +/// output device is used for and that is flushed at the end. +void SwLayVout::Enter( SwViewShell *pShell, SwRect &rRect, bool bOn ) +{ + Flush(); + +#ifdef DBG_UTIL + if( pShell->GetViewOptions()->IsTest3() ) + { + ++nCount; + return; + } +#endif + + bOn = bOn && !nCount && rRect.HasArea() && pShell->GetWin(); + ++nCount; + if( !bOn ) + return; + + pSh = pShell; + pOut = nullptr; + OutputDevice *pO = pSh->GetOut(); +// We don't cheat on printers or virtual output devices... + if( OUTDEV_WINDOW != pO->GetOutDevType() ) + return; + + pOut = pO; + Size aPixSz( pOut->PixelToLogic( Size( 1,1 )) ); + SwRect aTmp( rRect ); + aTmp.AddWidth(aPixSz.Width()/2 + 1 ); + aTmp.AddHeight(aPixSz.Height()/2 + 1 ); + tools::Rectangle aTmpRect( pO->LogicToPixel( aTmp.SVRect() ) ); + + OSL_ENSURE( !pSh->GetWin()->IsReallyVisible() || + aTmpRect.GetWidth() <= pSh->GetWin()->GetOutputSizePixel().Width() + 2, + "Paintwidth bigger than visarea?" ); + // Does the rectangle fit in our buffer? + if( !DoesFit( aTmpRect.GetSize() ) ) + { + pOut = nullptr; + return; + } + + aRect = SwRect( pO->PixelToLogic( aTmpRect ) ); + + SetOutDev( pSh, pVirDev ); + + if( pVirDev->GetFillColor() != pOut->GetFillColor() ) + pVirDev->SetFillColor( pOut->GetFillColor() ); + + MapMode aMapMode( pOut->GetMapMode() ); + // use method to set mapping + //aMapMode.SetOrigin( Point(0,0) - aRect.Pos() ); + ::SetMappingForVirtDev( aRect.Pos(), pOut, pVirDev ); + + if( aMapMode != pVirDev->GetMapMode() ) + pVirDev->SetMapMode( aMapMode ); + + // set value of parameter <rRect> + rRect = aRect; + +} + +void SwLayVout::Flush_() +{ + OSL_ENSURE( pVirDev, "SwLayVout::DrawOut: nothing left Toulouse" ); + pOut->DrawOutDev( aRect.Pos(), aRect.SSize(), + aRect.Pos(), aRect.SSize(), *pVirDev ); + SetOutDev( pSh, pOut ); + pOut = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/virtoutp.hxx b/sw/source/core/layout/virtoutp.hxx new file mode 100644 index 000000000..c120f8cc6 --- /dev/null +++ b/sw/source/core/layout/virtoutp.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_VIRTOUTP_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_VIRTOUTP_HXX + +#include <vcl/virdev.hxx> + +#include <swrect.hxx> + +class SwViewShell; +#define VIRTUALHEIGHT 64 + +class SwLayVout +{ + friend void FrameFinit(); //deletes Vout +private: + SwViewShell* pSh; + VclPtr<OutputDevice> pOut; + VclPtr<VirtualDevice> pVirDev; + SwRect aRect; + SwRect aOrgRect; + Size aSize; + sal_uInt16 nCount; + + bool DoesFit( const Size &rOut ); + +public: + SwLayVout() : pSh(nullptr), pOut(nullptr), pVirDev(nullptr), aSize(0, VIRTUALHEIGHT), nCount(0) {} + ~SwLayVout() { pVirDev.disposeAndClear(); } + + /// OD 27.09.2002 #103636# - change 2nd parameter <rRect> - no longer <const> + void Enter( SwViewShell *pShell, SwRect &rRect, bool bOn ); + void Leave() { --nCount; Flush(); } + + void SetOrgRect( SwRect const &rRect ) { aOrgRect = rRect; } + const SwRect& GetOrgRect() const { return aOrgRect; } + + bool IsFlushable() const { return bool(pOut); } + void Flush_(); + void Flush() { if( pOut ) Flush_(); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/wsfrm.cxx b/sw/source/core/layout/wsfrm.cxx new file mode 100644 index 000000000..57db1547a --- /dev/null +++ b/sw/source/core/layout/wsfrm.cxx @@ -0,0 +1,4602 @@ +/* -*- 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 <hints.hxx> +#include <o3tl/safeint.hxx> +#include <svl/itemiter.hxx> +#include <editeng/brushitem.hxx> +#include <fmtornt.hxx> +#include <pagefrm.hxx> +#include <section.hxx> +#include <rootfrm.hxx> +#include <anchoreddrawobject.hxx> +#include <fmtanchr.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <redline.hxx> +#include <docsh.hxx> +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <fmtclbl.hxx> +#include <fmtfsize.hxx> +#include <fmtpdsc.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtsrnd.hxx> +#include <fmtcntnt.hxx> +#include <ftnfrm.hxx> +#include <tabfrm.hxx> +#include <flyfrm.hxx> +#include <sectfrm.hxx> +#include <fmtclds.hxx> +#include <txtfrm.hxx> +#include <bodyfrm.hxx> +#include <cellfrm.hxx> +#include <dbg_lay.hxx> +#include <editeng/frmdiritem.hxx> +#include <sortedobjs.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> + +// RotateFlyFrame3 +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +using namespace ::com::sun::star; + +SwFrameAreaDefinition::SwFrameAreaDefinition() +: maFrameArea(), + maFramePrintArea(), + mbFrameAreaPositionValid(false), + mbFrameAreaSizeValid(false), + mbFramePrintAreaValid(false), + mnFrameId(SwFrameAreaDefinition::mnLastFrameId++) +{ +} + +SwFrameAreaDefinition::~SwFrameAreaDefinition() +{ +} + +void SwFrameAreaDefinition::setFrameAreaPositionValid(bool bNew) +{ + if(mbFrameAreaPositionValid != bNew) + { + mbFrameAreaPositionValid = bNew; + } +} + +void SwFrameAreaDefinition::setFrameAreaSizeValid(bool bNew) +{ + if(mbFrameAreaSizeValid != bNew) + { + mbFrameAreaSizeValid = bNew; + } +} + +void SwFrameAreaDefinition::setFramePrintAreaValid(bool bNew) +{ + if(mbFramePrintAreaValid != bNew) + { + mbFramePrintAreaValid = bNew; + } +} + +SwFrameAreaDefinition::FrameAreaWriteAccess::~FrameAreaWriteAccess() +{ + if(mrTarget.maFrameArea != *this) + { + mrTarget.maFrameArea = *this; + } +} + +SwFrameAreaDefinition::FramePrintAreaWriteAccess::~FramePrintAreaWriteAccess() +{ + if(mrTarget.maFramePrintArea != *this) + { + mrTarget.maFramePrintArea = *this; + } +} + +// RotateFlyFrame3 - Support for Transformations +basegfx::B2DHomMatrix SwFrameAreaDefinition::getFrameAreaTransformation() const +{ + // default implementation hands out FrameArea (outer frame) + const SwRect& rFrameArea(getFrameArea()); + + return basegfx::utils::createScaleTranslateB2DHomMatrix( + rFrameArea.Width(), rFrameArea.Height(), + rFrameArea.Left(), rFrameArea.Top()); +} + +basegfx::B2DHomMatrix SwFrameAreaDefinition::getFramePrintAreaTransformation() const +{ + // default implementation hands out FramePrintArea (outer frame) + // Take into account that FramePrintArea is relative to FrameArea + const SwRect& rFrameArea(getFrameArea()); + const SwRect& rFramePrintArea(getFramePrintArea()); + + return basegfx::utils::createScaleTranslateB2DHomMatrix( + rFramePrintArea.Width(), rFramePrintArea.Height(), + rFramePrintArea.Left() + rFrameArea.Left(), + rFramePrintArea.Top() + rFrameArea.Top()); +} + +void SwFrameAreaDefinition::transform_translate(const Point& rOffset) +{ + // RotateFlyFrame3: default is to change the FrameArea, FramePrintArea needs no + // change since it is relative to FrameArea + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if (aFrm.Pos().X() != FAR_AWAY) + { + aFrm.Pos().AdjustX(rOffset.X() ); + } + + if (aFrm.Pos().Y() != FAR_AWAY) + { + aFrm.Pos().AdjustY(rOffset.Y() ); + } +} + +SwRect TransformableSwFrame::getUntransformedFrameArea() const +{ + const basegfx::B2DHomMatrix& rSource(getLocalFrameAreaTransformation()); + + if(rSource.isIdentity()) + { + return mrSwFrameAreaDefinition.getFrameArea(); + } + else + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSource.decompose(aScale, aTranslate, fRotate, fShearX); + const basegfx::B2DPoint aCenter(rSource * basegfx::B2DPoint(0.5, 0.5)); + const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale)); + + return SwRect( + basegfx::fround(aCenter.getX() - (0.5 * aAbsScale.getX())), + basegfx::fround(aCenter.getY() - (0.5 * aAbsScale.getY())), + basegfx::fround(aAbsScale.getX()), + basegfx::fround(aAbsScale.getY())); + } +} + +SwRect TransformableSwFrame::getUntransformedFramePrintArea() const +{ + const basegfx::B2DHomMatrix& rSource(getLocalFramePrintAreaTransformation()); + + if(rSource.isIdentity()) + { + return mrSwFrameAreaDefinition.getFramePrintArea(); + } + else + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSource.decompose(aScale, aTranslate, fRotate, fShearX); + const basegfx::B2DPoint aCenter(rSource * basegfx::B2DPoint(0.5, 0.5)); + const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale)); + const SwRect aUntransformedFrameArea(getUntransformedFrameArea()); + + return SwRect( + basegfx::fround(aCenter.getX() - (0.5 * aAbsScale.getX())) - aUntransformedFrameArea.Left(), + basegfx::fround(aCenter.getY() - (0.5 * aAbsScale.getY())) - aUntransformedFrameArea.Top(), + basegfx::fround(aAbsScale.getX()), + basegfx::fround(aAbsScale.getY())); + } +} + +void TransformableSwFrame::createFrameAreaTransformations( + double fRotation, + const basegfx::B2DPoint& rCenter) +{ + const basegfx::B2DHomMatrix aRotateAroundCenter( + basegfx::utils::createRotateAroundPoint( + rCenter.getX(), + rCenter.getY(), + fRotation)); + const SwRect& rFrameArea(mrSwFrameAreaDefinition.getFrameArea()); + const SwRect& rFramePrintArea(mrSwFrameAreaDefinition.getFramePrintArea()); + + maFrameAreaTransformation = aRotateAroundCenter * basegfx::utils::createScaleTranslateB2DHomMatrix( + rFrameArea.Width(), rFrameArea.Height(), + rFrameArea.Left(), rFrameArea.Top()); + maFramePrintAreaTransformation = aRotateAroundCenter * basegfx::utils::createScaleTranslateB2DHomMatrix( + rFramePrintArea.Width(), rFramePrintArea.Height(), + rFramePrintArea.Left() + rFrameArea.Left(), rFramePrintArea.Top() + rFrameArea.Top()); +} + +void TransformableSwFrame::adaptFrameAreasToTransformations() +{ + if(!getLocalFrameAreaTransformation().isIdentity()) + { + basegfx::B2DRange aRangeFrameArea(0.0, 0.0, 1.0, 1.0); + aRangeFrameArea.transform(getLocalFrameAreaTransformation()); + const SwRect aNewFrm( + basegfx::fround(aRangeFrameArea.getMinX()), basegfx::fround(aRangeFrameArea.getMinY()), + basegfx::fround(aRangeFrameArea.getWidth()), basegfx::fround(aRangeFrameArea.getHeight())); + + if(aNewFrm != mrSwFrameAreaDefinition.getFrameArea()) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(mrSwFrameAreaDefinition); + aFrm.setSwRect(aNewFrm); + } + } + + if(!getLocalFramePrintAreaTransformation().isIdentity()) + { + basegfx::B2DRange aRangeFramePrintArea(0.0, 0.0, 1.0, 1.0); + aRangeFramePrintArea.transform(getLocalFramePrintAreaTransformation()); + const SwRect aNewPrt( + basegfx::fround(aRangeFramePrintArea.getMinX()) - mrSwFrameAreaDefinition.getFrameArea().Left(), + basegfx::fround(aRangeFramePrintArea.getMinY()) - mrSwFrameAreaDefinition.getFrameArea().Top(), + basegfx::fround(aRangeFramePrintArea.getWidth()), + basegfx::fround(aRangeFramePrintArea.getHeight())); + + if(aNewPrt != mrSwFrameAreaDefinition.getFramePrintArea()) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(mrSwFrameAreaDefinition); + aPrt.setSwRect(aNewPrt); + } + } +} + +void TransformableSwFrame::restoreFrameAreas() +{ + // This can be done fully based on the Transformations currently + // set, so use this. Only needed when transformation *is* used + if(!getLocalFrameAreaTransformation().isIdentity()) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(mrSwFrameAreaDefinition); + aFrm.setSwRect(getUntransformedFrameArea()); + } + + if(!getLocalFramePrintAreaTransformation().isIdentity()) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(mrSwFrameAreaDefinition); + aPrt.setSwRect(getUntransformedFramePrintArea()); + } +} + +// transform by given B2DHomMatrix +void TransformableSwFrame::transform(const basegfx::B2DHomMatrix& aTransform) +{ + maFrameAreaTransformation *= aTransform; + maFramePrintAreaTransformation *= aTransform; +} + +SwFrame::SwFrame( SwModify *pMod, SwFrame* pSib ) +: SwFrameAreaDefinition(), + SwClient( pMod ), + SfxBroadcaster(), + mpRoot( pSib ? pSib->getRootFrame() : nullptr ), + mpUpper(nullptr), + mpNext(nullptr), + mpPrev(nullptr), + mnFrameType(SwFrameType::None), + mbInDtor(false), + mbInvalidR2L(true), + mbDerivedR2L(false), + mbRightToLeft(false), + mbInvalidVert(true), + mbDerivedVert(false), + mbVertical(false), + mbVertLR(false), + mbVertLRBT(false), + mbValidLineNum(false), + mbFixSize(false), + mbCompletePaint(true), + mbRetouche(false), + mbInfInvalid(true), + mbInfBody( false ), + mbInfTab ( false ), + mbInfFly ( false ), + mbInfFootnote ( false ), + mbInfSct ( false ), + mbColLocked(false), + m_isInDestroy(false), + mbForbidDelete(false) +{ + OSL_ENSURE( pMod, "No frame format given." ); +} + +const IDocumentDrawModelAccess& SwFrame::getIDocumentDrawModelAccess() +{ + return GetUpper()->GetFormat()->getIDocumentDrawModelAccess(); +} + +bool SwFrame::KnowsFormat( const SwFormat& rFormat ) const +{ + return GetRegisteredIn() == &rFormat; +} + +void SwFrame::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +void SwFrame::CheckDir( SvxFrameDirection nDir, bool bVert, bool bOnlyBiDi, bool bBrowse ) +{ + if( SvxFrameDirection::Environment == nDir || ( bVert && bOnlyBiDi ) ) + { + mbDerivedVert = true; + if( SvxFrameDirection::Environment == nDir ) + mbDerivedR2L = true; + SetDirFlags( bVert ); + } + else if( bVert ) + { + mbInvalidVert = false; + if( SvxFrameDirection::Horizontal_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir + || bBrowse ) + { + mbVertical = false; + mbVertLR = false; + mbVertLRBT = false; + } + else + { + mbVertical = true; + if(SvxFrameDirection::Vertical_RL_TB == nDir) + { + mbVertLR = false; + mbVertLRBT = false; + } + else if(SvxFrameDirection::Vertical_LR_TB==nDir) + { + mbVertLR = true; + mbVertLRBT = false; + } + else if (nDir == SvxFrameDirection::Vertical_LR_BT) + { + mbVertLR = true; + mbVertLRBT = true; + } + } + } + else + { + mbInvalidR2L = false; + if( SvxFrameDirection::Horizontal_RL_TB == nDir ) + mbRightToLeft = true; + else + mbRightToLeft = false; + } +} + +void SwFrame::CheckDirection( bool bVert ) +{ + if( bVert ) + { + if( !IsHeaderFrame() && !IsFooterFrame() ) + { + mbDerivedVert = true; + SetDirFlags( bVert ); + } + } + else + { + mbDerivedR2L = true; + SetDirFlags( bVert ); + } +} + +void SwSectionFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + if( pFormat ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(pFormat->GetFormatAttr(RES_FRAMEDIR).GetValue(), + bVert, true, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwFlyFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + if( pFormat ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(pFormat->GetFormatAttr(RES_FRAMEDIR).GetValue(), + bVert, false, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwTabFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + if( pFormat ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(pFormat->GetFormatAttr(RES_FRAMEDIR).GetValue(), + bVert, true, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwCellFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + const SfxPoolItem* pItem; + // Check if the item is set, before actually + // using it. Otherwise the dynamic pool default is used, which may be set + // to LTR in case of OOo 1.0 documents. + if( pFormat && SfxItemState::SET == pFormat->GetItemState( RES_FRAMEDIR, true, &pItem ) ) + { + const SvxFrameDirectionItem* pFrameDirItem = static_cast<const SvxFrameDirectionItem*>(pItem); + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir( pFrameDirItem->GetValue(), bVert, false, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwTextFrame::CheckDirection( bool bVert ) +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(GetTextNodeForParaProps()->GetSwAttrSet().GetFrameDir().GetValue(), + bVert, true, bBrowseMode); +} + +void SwFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + sal_uInt8 nInvFlags = 0; + + if( pOld && pNew && RES_ATTRSET_CHG == pNew->Which() ) + { + SfxItemIter aNIter( *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet() ); + SfxItemIter aOIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + do + { + UpdateAttrFrame(pOItem, pNItem, nInvFlags); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + } + else + UpdateAttrFrame( pOld, pNew, nInvFlags ); + + if ( nInvFlags != 0 ) + { + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if ( nInvFlags & 0x01 ) + { + InvalidatePrt_(); + if( !GetPrev() && IsTabFrame() && IsInSct() ) + FindSctFrame()->InvalidatePrt_(); + } + if ( nInvFlags & 0x02 ) + InvalidateSize_(); + if ( nInvFlags & 0x04 ) + InvalidatePos_(); + if ( nInvFlags & 0x08 ) + SetCompletePaint(); + SwFrame *pNxt; + if ( nInvFlags & 0x30 && nullptr != (pNxt = GetNext()) ) + { + pNxt->InvalidatePage( pPage ); + if ( nInvFlags & 0x10 ) + pNxt->InvalidatePos_(); + if ( nInvFlags & 0x20 ) + pNxt->SetCompletePaint(); + } + } +} + +void SwFrame::UpdateAttrFrame( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + sal_uInt8 &rInvFlags ) +{ + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_BOX: + case RES_SHADOW: + Prepare( PrepareHint::FixSizeChanged ); + [[fallthrough]]; + case RES_LR_SPACE: + case RES_UL_SPACE: + rInvFlags |= 0x0B; + break; + + case RES_HEADER_FOOTER_EAT_SPACING: + rInvFlags |= 0x03; + break; + + case RES_BACKGROUND: + rInvFlags |= 0x28; + break; + + case RES_KEEP: + rInvFlags |= 0x04; + break; + + case RES_FRM_SIZE: + ReinitializeFrameSizeAttrFlags(); + rInvFlags |= 0x13; + break; + + case RES_FMT_CHG: + rInvFlags |= 0x0F; + break; + + case RES_ROW_SPLIT: + { + if ( IsRowFrame() ) + { + bool bInFollowFlowRow = nullptr != IsInFollowFlowRow(); + if ( bInFollowFlowRow || nullptr != IsInSplitTableRow() ) + { + SwTabFrame* pTab = FindTabFrame(); + if ( bInFollowFlowRow ) + pTab = pTab->FindMaster(); + pTab->SetRemoveFollowFlowLinePending( true ); + } + } + break; + } + case RES_COL: + OSL_FAIL( "Columns for new FrameType?" ); + break; + + default: + // the new FillStyle has to do the same as previous RES_BACKGROUND + if(nWhich >= XATTR_FILL_FIRST && nWhich <= XATTR_FILL_LAST) + { + rInvFlags |= 0x28; + } + /* do Nothing */; + } +} + +bool SwFrame::Prepare( const PrepareHint, const void *, bool ) +{ + /* Do nothing */ + return false; +} + +/** + * Invalidates the page in which the Frame is currently placed. + * The page is invalidated depending on the type (Layout, Content, FlyFrame) + */ +void SwFrame::InvalidatePage( const SwPageFrame *pPage ) const +{ + if ( !pPage ) + { + pPage = FindPageFrame(); + // #i28701# - for at-character and as-character + // anchored Writer fly frames additionally invalidate also page frame + // its 'anchor character' is on. + if ( pPage && pPage->GetUpper() && IsFlyFrame() ) + { + const SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrame*>(this); + if ( pFlyFrame->IsAutoPos() || pFlyFrame->IsFlyInContentFrame() ) + { + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = + const_cast<SwFlyFrame*>(pFlyFrame)->FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor && pPageFrameOfAnchor != pPage ) + { + InvalidatePage( pPageFrameOfAnchor ); + } + } + } + } + + if ( pPage && pPage->GetUpper() ) + { + if ( pPage->GetFormat()->GetDoc()->IsInDtor() ) + return; + + SwRootFrame *pRoot = const_cast<SwRootFrame*>(static_cast<const SwRootFrame*>(pPage->GetUpper())); + const SwFlyFrame *pFly = FindFlyFrame(); + if ( IsContentFrame() ) + { + if ( pRoot->IsTurboAllowed() ) + { + // If a ContentFrame wants to register for a second time, make it a TurboAction. + if ( !pRoot->GetTurbo() || this == pRoot->GetTurbo() ) + pRoot->SetTurbo( static_cast<const SwContentFrame*>(this) ); + else + { + pRoot->DisallowTurbo(); + //The page of the Turbo could be a different one then mine, + //therefore we have to invalidate it. + const SwFrame *pTmp = pRoot->GetTurbo(); + pRoot->ResetTurbo(); + pTmp->InvalidatePage(); + } + } + if ( !pRoot->GetTurbo() ) + { + if ( pFly ) + { if( !pFly->IsLocked() ) + { + if ( pFly->IsFlyInContentFrame() ) + { pPage->InvalidateFlyInCnt(); + pFly->GetAnchorFrame()->InvalidatePage(); + } + else + pPage->InvalidateFlyContent(); + } + } + else + pPage->InvalidateContent(); + } + } + else + { + pRoot->DisallowTurbo(); + if ( pFly ) + { + if ( !pFly->IsLocked() ) + { + if ( pFly->IsFlyInContentFrame() ) + { + pPage->InvalidateFlyInCnt(); + pFly->GetAnchorFrame()->InvalidatePage(); + } + else + pPage->InvalidateFlyLayout(); + } + } + else + pPage->InvalidateLayout(); + + if ( pRoot->GetTurbo() ) + { const SwFrame *pTmp = pRoot->GetTurbo(); + pRoot->ResetTurbo(); + pTmp->InvalidatePage(); + } + } + pRoot->SetIdleFlags(); + + if (IsTextFrame()) + { + SwTextFrame const*const pText(static_cast<SwTextFrame const*>(this)); + if (sw::MergedPara const*const pMergedPara = pText->GetMergedPara()) + { + SwTextNode const* pNode(nullptr); + for (auto const& e : pMergedPara->extents) + { + if (e.pNode != pNode) + { + pNode = e.pNode; + if (pNode->IsGrammarCheckDirty()) + { + pRoot->SetNeedGrammarCheck( true ); + break; + } + } + } + } + else + { + if (pText->GetTextNodeFirst()->IsGrammarCheckDirty()) + { + pRoot->SetNeedGrammarCheck( true ); + } + } + } + } +} + +Size SwFrame::ChgSize( const Size& aNewSize ) +{ + mbFixSize = true; + const Size aOldSize( getFrameArea().SSize() ); + if ( aNewSize == aOldSize ) + return aOldSize; + + if ( GetUpper() ) + { + bool bNeighb = IsNeighbourFrame(); + SwRectFn fnRect = IsVertical() == bNeighb ? fnRectHori : ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + SwRect aNew( Point(0,0), aNewSize ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetWidth)( (aNew.*fnRect->fnGetWidth)() ); + } + + long nNew = (aNew.*fnRect->fnGetHeight)(); + long nDiff = nNew - (getFrameArea().*fnRect->fnGetHeight)(); + + if( nDiff ) + { + if ( GetUpper()->IsFootnoteBossFrame() && HasFixSize() && + SwNeighbourAdjust::GrowShrink != + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetHeight)( nNew ); + } + + SwTwips nReal = static_cast<SwLayoutFrame*>(this)->AdjustNeighbourhood(nDiff); + + if ( nReal != nDiff ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetHeight)( nNew - nDiff + nReal ); + } + } + else + { + // OD 24.10.2002 #97265# - grow/shrink not for neighbour frames + // NOTE: neighbour frames are cell and column frames. + if ( !bNeighb ) + { + if ( nDiff > 0 ) + Grow( nDiff ); + else + Shrink( -nDiff ); + + if ( GetUpper() && (getFrameArea().*fnRect->fnGetHeight)() != nNew ) + { + GetUpper()->InvalidateSize_(); + } + } + + // Even if grow/shrink did not yet set the desired width, for + // example when called by ChgColumns to set the column width, we + // set the right width now. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetHeight)( nNew ); + } + } + } + else + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.SSize( aNewSize ); + } + + if ( getFrameArea().SSize() != aOldSize ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + GetNext()->InvalidatePage( pPage ); + } + if( IsLayoutFrame() ) + { + if( IsRightToLeft() ) + InvalidatePos_(); + if( static_cast<SwLayoutFrame*>(this)->Lower() ) + static_cast<SwLayoutFrame*>(this)->Lower()->InvalidateSize_(); + } + InvalidatePrt_(); + InvalidateSize_(); + InvalidatePage( pPage ); + } + + return getFrameArea().SSize(); +} + +/** Insert SwFrame into existing structure. + * + * Insertion is done below the parent either before pBehind or + * at the end of the chain if pBehind is empty. + */ +void SwFrame::InsertBefore( SwLayoutFrame* pParent, SwFrame* pBehind ) +{ + OSL_ENSURE( pParent, "No parent for insert." ); + OSL_ENSURE( (!pBehind || pParent == pBehind->GetUpper()), + "Frame tree is inconsistent." ); + + mpUpper = pParent; + mpNext = pBehind; + if( pBehind ) + { //Insert before pBehind. + if( nullptr != (mpPrev = pBehind->mpPrev) ) + mpPrev->mpNext = this; + else + mpUpper->m_pLower = this; + pBehind->mpPrev = this; + } + else + { //Insert at the end, or as first node in the sub tree + mpPrev = mpUpper->Lower(); + if ( mpPrev ) + { + while( mpPrev->mpNext ) + mpPrev = mpPrev->mpNext; + mpPrev->mpNext = this; + } + else + mpUpper->m_pLower = this; + } +} + +/** Insert SwFrame into existing structure. + * + * Insertion is done below the parent either after pBehind or + * at the beginning of the chain if pBehind is empty. + */ +void SwFrame::InsertBehind( SwLayoutFrame *pParent, SwFrame *pBefore ) +{ + OSL_ENSURE( pParent, "No Parent for Insert." ); + OSL_ENSURE( (!pBefore || pParent == pBefore->GetUpper()), + "Frame tree is inconsistent." ); + + mpUpper = pParent; + mpPrev = pBefore; + if ( pBefore ) + { + //Insert after pBefore + if ( nullptr != (mpNext = pBefore->mpNext) ) + mpNext->mpPrev = this; + pBefore->mpNext = this; + } + else + { + //Insert at the beginning of the chain + mpNext = pParent->Lower(); + if ( pParent->Lower() ) + pParent->Lower()->mpPrev = this; + pParent->m_pLower = this; + } +} + +/** Insert a chain of SwFrames into an existing structure + * + * Currently, this method is used to insert a SectionFrame (which may have some siblings) into an + * existing structure. If the third parameter is NULL, this method is (besides handling the + * siblings) equal to SwFrame::InsertBefore(..). + * + * If the third parameter is passed, the following happens: + * - this becomes mpNext of pParent + * - pSct becomes mpNext of the last one in the this-chain + * - pBehind is reconnected from pParent to pSct + * The purpose is: a SectionFrame (this) won't become a child of another SectionFrame (pParent), but + * pParent gets split into two siblings (pParent+pSect) and this is inserted between. + */ +bool SwFrame::InsertGroupBefore( SwFrame* pParent, SwFrame* pBehind, SwFrame* pSct ) +{ + OSL_ENSURE( pParent, "No parent for insert." ); + OSL_ENSURE( (!pBehind || ( (pBehind && (pParent == pBehind->GetUpper())) + || ((pParent->IsSctFrame() && pBehind->GetUpper()->IsColBodyFrame())) ) ), + "Frame tree inconsistent." ); + if( pSct ) + { + mpUpper = pParent->GetUpper(); + SwFrame *pLast = this; + while( pLast->GetNext() ) + { + pLast = pLast->GetNext(); + pLast->mpUpper = GetUpper(); + } + if( pBehind ) + { + pLast->mpNext = pSct; + pSct->mpPrev = pLast; + pSct->mpNext = pParent->GetNext(); + } + else + { + pLast->mpNext = pParent->GetNext(); + if( pLast->GetNext() ) + pLast->GetNext()->mpPrev = pLast; + } + pParent->mpNext = this; + mpPrev = pParent; + if( pSct->GetNext() ) + pSct->GetNext()->mpPrev = pSct; + while( pLast->GetNext() ) + { + pLast = pLast->GetNext(); + pLast->mpUpper = GetUpper(); + } + if( pBehind ) + { // Insert before pBehind. + if( pBehind->GetPrev() ) + pBehind->GetPrev()->mpNext = nullptr; + else + pBehind->GetUpper()->m_pLower = nullptr; + pBehind->mpPrev = nullptr; + SwLayoutFrame* pTmp = static_cast<SwLayoutFrame*>(pSct); + if( pTmp->Lower() ) + { + OSL_ENSURE( pTmp->Lower()->IsColumnFrame(), "InsertGrp: Used SectionFrame" ); + pTmp = static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pTmp->Lower())->Lower()); + OSL_ENSURE( pTmp, "InsertGrp: Missing ColBody" ); + } + pBehind->mpUpper = pTmp; + pBehind->GetUpper()->m_pLower = pBehind; + pLast = pBehind->GetNext(); + while ( pLast ) + { + pLast->mpUpper = pBehind->GetUpper(); + pLast = pLast->GetNext(); + } + } + else + { + OSL_ENSURE( pSct->IsSctFrame(), "InsertGroup: For SectionFrames only" ); + SwFrame::DestroyFrame(pSct); + return false; + } + } + else + { + mpUpper = static_cast<SwLayoutFrame*>(pParent); + SwFrame *pLast = this; + while( pLast->GetNext() ) + { + pLast = pLast->GetNext(); + pLast->mpUpper = GetUpper(); + } + pLast->mpNext = pBehind; + if( pBehind ) + { // Insert before pBehind. + if( nullptr != (mpPrev = pBehind->mpPrev) ) + mpPrev->mpNext = this; + else + mpUpper->m_pLower = this; + pBehind->mpPrev = pLast; + } + else + { + //Insert at the end, or ... the first node in the subtree + mpPrev = mpUpper->Lower(); + if ( mpPrev ) + { + while( mpPrev->mpNext ) + mpPrev = mpPrev->mpNext; + mpPrev->mpNext = this; + } + else + mpUpper->m_pLower = this; + } + } + return true; +} + +void SwFrame::RemoveFromLayout() +{ + OSL_ENSURE( mpUpper, "Remove without upper?" ); + + if (mpPrev) + // one out of the middle is removed + mpPrev->mpNext = mpNext; + else if (mpUpper) + { // the first in a list is removed //TODO + OSL_ENSURE( mpUpper->m_pLower == this, "Layout is inconsistent." ); + mpUpper->m_pLower = mpNext; + } + if( mpNext ) + mpNext->mpPrev = mpPrev; + + // Remove link + mpNext = mpPrev = nullptr; + mpUpper = nullptr; +} + +void SwContentFrame::Paste( SwFrame* pParent, SwFrame* pSibling) +{ + OSL_ENSURE( pParent, "No parent for pasting." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm the parent." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I'm still registered somewhere" ); + OSL_ENSURE( !pSibling || pSibling->IsFlowFrame(), + "<SwContentFrame::Paste(..)> - sibling not of expected type." ); + + //Insert in the tree. + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + SwPageFrame *pPage = FindPageFrame(); + InvalidateAll_(); + InvalidatePage( pPage ); + + if( pPage ) + { + pPage->InvalidateSpelling(); + pPage->InvalidateSmartTags(); + pPage->InvalidateAutoCompleteWords(); + pPage->InvalidateWordCount(); + } + + if ( GetNext() ) + { + SwFrame* pNxt = GetNext(); + pNxt->InvalidatePrt_(); + pNxt->InvalidatePos_(); + pNxt->InvalidatePage( pPage ); + if( pNxt->IsSctFrame() ) + pNxt = static_cast<SwSectionFrame*>(pNxt)->ContainsContent(); + if( pNxt && pNxt->IsTextFrame() && pNxt->IsInFootnote() ) + pNxt->Prepare( PrepareHint::FootnoteInvalidation, nullptr, false ); + } + + if ( getFrameArea().Height() ) + pParent->Grow( getFrameArea().Height() ); + + if ( getFrameArea().Width() != pParent->getFramePrintArea().Width() ) + Prepare( PrepareHint::FixSizeChanged ); + + if ( GetPrev() ) + { + if ( IsFollow() ) + //I'm a direct follower of my master now + static_cast<SwContentFrame*>(GetPrev())->Prepare( PrepareHint::FollowFollows ); + else + { + if ( GetPrev()->getFrameArea().Height() != + GetPrev()->getFramePrintArea().Height() + GetPrev()->getFramePrintArea().Top() ) + { + // Take the border into account? + GetPrev()->InvalidatePrt_(); + } + // OD 18.02.2003 #104989# - force complete paint of previous frame, + // if frame is inserted at the end of a section frame, in order to + // get subsidiary lines repainted for the section. + if ( pParent->IsSctFrame() && !GetNext() ) + { + // force complete paint of previous frame, if new inserted frame + // in the section is the last one. + GetPrev()->SetCompletePaint(); + } + GetPrev()->InvalidatePage( pPage ); + } + } + if ( IsInFootnote() ) + { + SwFrame* pFrame = GetIndPrev(); + if( pFrame && pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->Prepare( PrepareHint::QuoVadis, nullptr, false ); + if( !GetNext() ) + { + pFrame = FindFootnoteFrame()->GetNext(); + if( pFrame && nullptr != (pFrame=static_cast<SwLayoutFrame*>(pFrame)->ContainsAny()) ) + pFrame->InvalidatePrt_(); + } + } + + InvalidateLineNum_(); + SwFrame *pNxt = FindNextCnt(); + if ( pNxt ) + { + while ( pNxt && pNxt->IsInTab() ) + { + if( nullptr != (pNxt = pNxt->FindTabFrame()) ) + pNxt = pNxt->FindNextCnt(); + } + if ( pNxt ) + { + pNxt->InvalidateLineNum_(); + if ( pNxt != GetNext() ) + pNxt->InvalidatePage(); + } + } +} + +void SwContentFrame::Cut() +{ + OSL_ENSURE( GetUpper(), "Cut without Upper()." ); + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + SwFrame *pFrame = GetIndPrev(); + if( pFrame ) + { + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if ( pFrame && pFrame->IsContentFrame() ) + { + pFrame->InvalidatePrt_(); + if( IsInFootnote() ) + pFrame->Prepare( PrepareHint::QuoVadis, nullptr, false ); + } + // #i26250# - invalidate printing area of previous + // table frame. + else if ( pFrame && pFrame->IsTabFrame() ) + { + pFrame->InvalidatePrt(); + } + } + + SwFrame *pNxt = FindNextCnt(); + if ( pNxt ) + { + while ( pNxt && pNxt->IsInTab() ) + { + if( nullptr != (pNxt = pNxt->FindTabFrame()) ) + pNxt = pNxt->FindNextCnt(); + } + if ( pNxt ) + { + pNxt->InvalidateLineNum_(); + if ( pNxt != GetNext() ) + pNxt->InvalidatePage(); + } + } + + if( nullptr != (pFrame = GetIndNext()) ) + { + // The old follow may have calculated a gap to the predecessor which + // now becomes obsolete or different as it becomes the first one itself + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + pFrame->InvalidatePage( pPage ); + if( pFrame->IsSctFrame() ) + { + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + { + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + pFrame->InvalidatePage( pPage ); + } + } + if( pFrame && IsInFootnote() ) + pFrame->Prepare( PrepareHint::ErgoSum, nullptr, false ); + if( IsInSct() && !GetPrev() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + } + else + { + InvalidateNextPos(); + //Someone needs to do the retouching: predecessor or upper + if ( nullptr != (pFrame = GetPrev()) ) + { pFrame->SetRetouche(); + pFrame->Prepare( PrepareHint::WidowsOrphans ); + pFrame->InvalidatePos_(); + pFrame->InvalidatePage( pPage ); + } + // If I'm (was) the only ContentFrame in my upper, it has to do the + // retouching. Also, perhaps a page became empty. + else + { SwRootFrame *pRoot = getRootFrame(); + if ( pRoot ) + { + pRoot->SetSuperfluous(); + GetUpper()->SetCompletePaint(); + GetUpper()->InvalidatePage( pPage ); + } + if( IsInSct() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + // #i52253# The master table should take care + // of removing the follow flow line. + if ( IsInTab() ) + { + SwTabFrame* pThisTab = FindTabFrame(); + SwTabFrame* pMasterTab = pThisTab && pThisTab->IsFollow() ? pThisTab->FindMaster() : nullptr; + if ( pMasterTab ) + { + pMasterTab->InvalidatePos_(); + pMasterTab->SetRemoveFollowFlowLinePending( true ); + } + } + } + } + //Remove first, then shrink the upper. + SwLayoutFrame *pUp = GetUpper(); + RemoveFromLayout(); + if ( pUp ) + { + SwSectionFrame *pSct = nullptr; + if ( !pUp->Lower() && + ( ( pUp->IsFootnoteFrame() && !pUp->IsColLocked() ) || + ( pUp->IsInSct() && + // #i29438# + // We have to consider the case that the section may be "empty" + // except from a temporary empty table frame. + // This can happen due to the new cell split feature. + !pUp->IsCellFrame() && + // #126020# - adjust check for empty section + // #130797# - correct fix #126020# + !(pSct = pUp->FindSctFrame())->ContainsContent() && + !pSct->ContainsAny( true ) ) ) ) + { + if ( pUp->GetUpper() ) + { + + // prevent delete of <ColLocked> footnote frame + if ( pUp->IsFootnoteFrame() && !pUp->IsColLocked()) + { + if( pUp->GetNext() && !pUp->GetPrev() ) + { + SwFrame* pTmp = static_cast<SwLayoutFrame*>(pUp->GetNext())->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePrt_(); + } + if (!pUp->IsDeleteForbidden()) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + } + } + else + { + + if ( pSct->IsColLocked() || !pSct->IsInFootnote() || + ( pUp->IsFootnoteFrame() && pUp->IsColLocked() ) ) + { + pSct->DelEmpty( false ); + // If a locked section may not be deleted then at least + // its size became invalid after removing its last + // content. + pSct->InvalidateSize_(); + } + else + { + pSct->DelEmpty( true ); + SwFrame::DestroyFrame(pSct); + } + } + } + } + else + { + SwRectFnSet aRectFnSet(this); + long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight ) + pUp->Shrink( nFrameHeight ); + } + } +} + +void SwLayoutFrame::Paste( SwFrame* pParent, SwFrame* pSibling) +{ + OSL_ENSURE( pParent, "No parent for pasting." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm the parent oneself." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I'm still registered somewhere." ); + + //Insert in the tree. + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + // OD 24.10.2002 #103517# - correct setting of variable <fnRect> + // <fnRect> is used for the following: + // (1) To invalidate the frame's size, if its size, which has to be the + // same as its upper/parent, differs from its upper's/parent's. + // (2) To adjust/grow the frame's upper/parent, if it has a dimension in its + // size, which is not determined by its upper/parent. + // Which size is which depends on the frame type and the layout direction + // (vertical or horizontal). + // There are the following cases: + // (A) Header and footer frames both in vertical and in horizontal layout + // have to size the width to the upper/parent. A dimension in the height + // has to cause an adjustment/grow of the upper/parent. + // --> <fnRect> = fnRectHori + // (B) Cell and column frames in vertical layout, the width has to be the + // same as upper/parent and a dimension in height causes adjustment/grow + // of the upper/parent. + // --> <fnRect> = fnRectHori + // in horizontal layout the other way around + // --> <fnRect> = fnRectVert + // (C) Other frames in vertical layout, the height has to be the + // same as upper/parent and a dimension in width causes adjustment/grow + // of the upper/parent. + // --> <fnRect> = fnRectVert + // in horizontal layout the other way around + // --> <fnRect> = fnRectHori + //SwRectFn fnRect = IsVertical() ? fnRectHori : fnRectVert; + SwRectFn fnRect; + if ( IsHeaderFrame() || IsFooterFrame() ) + fnRect = fnRectHori; + else if ( IsCellFrame() || IsColumnFrame() ) + fnRect = GetUpper()->IsVertical() ? fnRectHori : ( GetUpper()->IsVertLR() ? (GetUpper()->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + else + fnRect = GetUpper()->IsVertical() ? ( GetUpper()->IsVertLR() ? (GetUpper()->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + if( (getFrameArea().*fnRect->fnGetWidth)() != (pParent->getFramePrintArea().*fnRect->fnGetWidth)()) + InvalidateSize_(); + InvalidatePos_(); + const SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if( !IsColumnFrame() ) + { + SwFrame *pFrame = GetIndNext(); + if( nullptr != pFrame ) + { + pFrame->InvalidatePos_(); + if( IsInFootnote() ) + { + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->Prepare( PrepareHint::ErgoSum, nullptr, false ); + } + } + if ( IsInFootnote() && nullptr != ( pFrame = GetIndPrev() ) ) + { + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->Prepare( PrepareHint::QuoVadis, nullptr, false ); + } + } + + if( (getFrameArea().*fnRect->fnGetHeight)() ) + { + // AdjustNeighbourhood is now also called in columns which are not + // placed inside a frame + SwNeighbourAdjust nAdjust = GetUpper()->IsFootnoteBossFrame() ? + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() + : SwNeighbourAdjust::GrowShrink; + SwTwips nGrow = (getFrameArea().*fnRect->fnGetHeight)(); + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + AdjustNeighbourhood( nGrow ); + else + { + SwTwips nReal = 0; + if( SwNeighbourAdjust::AdjustGrow == nAdjust ) + nReal = AdjustNeighbourhood( nGrow ); + if( nReal < nGrow ) + nReal += pParent->Grow( nGrow - nReal ); + if( SwNeighbourAdjust::GrowAdjust == nAdjust && nReal < nGrow ) + AdjustNeighbourhood( nGrow - nReal ); + } + } +} + +void SwLayoutFrame::Cut() +{ + if ( GetNext() ) + GetNext()->InvalidatePos_(); + + SwRectFnSet aRectFnSet(this); + SwTwips nShrink = aRectFnSet.GetHeight(getFrameArea()); + + // Remove first, then shrink upper. + SwLayoutFrame *pUp = GetUpper(); + + // AdjustNeighbourhood is now also called in columns which are not + // placed inside a frame. + + // Remove must not be called before an AdjustNeighbourhood, but it has to + // be called before the upper-shrink-call, if the upper-shrink takes care + // of its content. + if ( pUp && nShrink ) + { + if( pUp->IsFootnoteBossFrame() ) + { + SwNeighbourAdjust nAdjust= static_cast<SwFootnoteBossFrame*>(pUp)->NeighbourhoodAdjustment(); + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + AdjustNeighbourhood( -nShrink ); + else + { + SwTwips nReal = 0; + if( SwNeighbourAdjust::AdjustGrow == nAdjust ) + nReal = -AdjustNeighbourhood( -nShrink ); + if( nReal < nShrink ) + { + const SwTwips nOldHeight = aRectFnSet.GetHeight(getFrameArea()); + + // seems as if this needs to be forwarded to the SwFrame already here, + // changing to zero seems temporary anyways + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, 0 ); + } + + nReal += pUp->Shrink( nShrink - nReal ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nOldHeight ); + } + } + + if( SwNeighbourAdjust::GrowAdjust == nAdjust && nReal < nShrink ) + AdjustNeighbourhood( nReal - nShrink ); + } + RemoveFromLayout(); + } + else + { + RemoveFromLayout(); + pUp->Shrink( nShrink ); + } + } + else + RemoveFromLayout(); + + if( pUp && !pUp->Lower() ) + { + pUp->SetCompletePaint(); + pUp->InvalidatePage(); + } +} + +SwTwips SwFrame::Grow( SwTwips nDist, bool bTst, bool bInfo ) +{ + OSL_ENSURE( nDist >= 0, "Negative growth?" ); + + PROTOCOL_ENTER( this, bTst ? PROT::GrowTest : PROT::Grow, DbgAction::NONE, &nDist ) + + if ( nDist ) + { + SwRectFnSet aRectFnSet(this); + + SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( nPrtHeight > 0 && nDist > (LONG_MAX - nPrtHeight) ) + nDist = LONG_MAX - nPrtHeight; + + if ( IsFlyFrame() ) + return static_cast<SwFlyFrame*>(this)->Grow_( nDist, bTst ); + else if( IsSctFrame() ) + return static_cast<SwSectionFrame*>(this)->Grow_( nDist, bTst ); + else + { + const SwCellFrame* pThisCell = dynamic_cast<const SwCellFrame*>(this); + if ( pThisCell ) + { + const SwTabFrame* pTab = FindTabFrame(); + + // NEW TABLES + if ( pTab->IsVertical() != IsVertical() || + pThisCell->GetLayoutRowSpan() < 1 ) + return 0; + } + + const SwTwips nReal = GrowFrame( nDist, bTst, bInfo ); + if( !bTst ) + { + nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight + ( IsContentFrame() ? nDist : nReal ) ); + } + return nReal; + } + } + return 0; +} + +SwTwips SwFrame::Shrink( SwTwips nDist, bool bTst, bool bInfo ) +{ + OSL_ENSURE( nDist >= 0, "Negative reduction?" ); + + PROTOCOL_ENTER( this, bTst ? PROT::ShrinkTest : PROT::Shrink, DbgAction::NONE, &nDist ) + + if ( nDist ) + { + if ( IsFlyFrame() ) + return static_cast<SwFlyFrame*>(this)->Shrink_( nDist, bTst ); + else if( IsSctFrame() ) + return static_cast<SwSectionFrame*>(this)->Shrink_( nDist, bTst ); + else + { + const SwCellFrame* pThisCell = dynamic_cast<const SwCellFrame*>(this); + if ( pThisCell ) + { + const SwTabFrame* pTab = FindTabFrame(); + + // NEW TABLES + if ( (pTab && pTab->IsVertical() != IsVertical()) || + pThisCell->GetLayoutRowSpan() < 1 ) + return 0; + } + + SwRectFnSet aRectFnSet(this); + SwTwips nReal = aRectFnSet.GetHeight(getFrameArea()); + ShrinkFrame( nDist, bTst, bInfo ); + nReal -= aRectFnSet.GetHeight(getFrameArea()); + if( !bTst ) + { + const SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight - ( IsContentFrame() ? nDist : nReal ) ); + } + return nReal; + } + } + return 0; +} + +/** Adjust surrounding neighbourhood after insertion + * + * A Frame needs "normalization" if it is directly placed below a footnote boss (page/column) and its + * size changes. There is always a frame that takes the maximum possible space (the frame that + * contains the Body text) and zero or more frames which only take the space needed (header/footer + * area, footnote container). If one of these frames changes, the body-text-frame has to grow or + * shrink accordingly, even though it's fixed. + * + * !! Is it possible to do this in a generic way and not restrict it to the page and a distinct + * frame which takes the maximum space (controlled using the FrameSize attribute)? + * Problems: + * - What if multiple frames taking the maximum space are placed next to each other? + * - How is the maximum space calculated? + * - How small can those frames become? + * + * In any case, only a certain amount of space is allowed, so we never go below a minimum value for + * the height of the body. + * + * @param nDiff the value around which the space has to be allocated + */ +SwTwips SwFrame::AdjustNeighbourhood( SwTwips nDiff, bool bTst ) +{ + PROTOCOL_ENTER( this, PROT::AdjustN, DbgAction::NONE, &nDiff ); + + if ( !nDiff || !GetUpper()->IsFootnoteBossFrame() ) // only inside pages/columns + return 0; + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + + //The (Page-)Body only changes in BrowseMode, but only if it does not + //contain columns. + if ( IsPageBodyFrame() && (!bBrowse || + (static_cast<SwLayoutFrame*>(this)->Lower() && + static_cast<SwLayoutFrame*>(this)->Lower()->IsColumnFrame())) ) + return 0; + + //In BrowseView mode the PageFrame can handle some of the requests. + long nBrowseAdd = 0; + if ( bBrowse && GetUpper()->IsPageFrame() ) // only (Page-)BodyFrames + { + SwViewShell *pViewShell = getRootFrame()->GetCurrShell(); + SwLayoutFrame *pUp = GetUpper(); + long nChg; + const long nUpPrtBottom = pUp->getFrameArea().Height() - + pUp->getFramePrintArea().Height() - pUp->getFramePrintArea().Top(); + SwRect aInva( pUp->getFrameArea() ); + if ( pViewShell ) + { + aInva.Pos().setX( pViewShell->VisArea().Left() ); + aInva.Width( pViewShell->VisArea().Width() ); + } + if ( nDiff > 0 ) + { + nChg = BROWSE_HEIGHT - pUp->getFrameArea().Height(); + nChg = std::min( nDiff, nChg ); + + if ( !IsBodyFrame() ) + { + SetCompletePaint(); + if ( !pViewShell || pViewShell->VisArea().Height() >= pUp->getFrameArea().Height() ) + { + //First minimize Body, it will grow again later. + SwFrame *pBody = static_cast<SwFootnoteBossFrame*>(pUp)->FindBodyCont(); + const long nTmp = nChg - pBody->getFramePrintArea().Height(); + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pBody); + aFrm.Height(std::max( 0L, aFrm.Height() - nChg )); + } + + pBody->InvalidatePrt_(); + pBody->InvalidateSize_(); + if ( pBody->GetNext() ) + pBody->GetNext()->InvalidatePos_(); + if ( !IsHeaderFrame() ) + pBody->SetCompletePaint(); + } + nChg = nTmp <= 0 ? 0 : nTmp; + } + } + + const long nTmp = nUpPrtBottom + 20; + aInva.Top( aInva.Bottom() - nTmp ); + aInva.Height( nChg + nTmp ); + } + else + { + //The page can shrink to 0. The first page keeps the same size like + //VisArea. + nChg = nDiff; + long nInvaAdd = 0; + if ( pViewShell && !pUp->GetPrev() && + pUp->getFrameArea().Height() + nDiff < pViewShell->VisArea().Height() ) + { + // This means that we have to invalidate adequately. + nChg = pViewShell->VisArea().Height() - pUp->getFrameArea().Height(); + nInvaAdd = -(nDiff - nChg); + } + + //Invalidate including bottom border. + long nBorder = nUpPrtBottom + 20; + nBorder -= nChg; + aInva.Top( aInva.Bottom() - (nBorder+nInvaAdd) ); + if ( !IsBodyFrame() ) + { + SetCompletePaint(); + if ( !IsHeaderFrame() ) + static_cast<SwFootnoteBossFrame*>(pUp)->FindBodyCont()->SetCompletePaint(); + } + //Invalidate the page because of the frames. Thereby the page becomes + //the right size again if a frame didn't fit. This only works + //randomly for paragraph bound frames otherwise (NotifyFlys). + pUp->InvalidateSize(); + } + if ( !bTst ) + { + //Independent from nChg + if ( pViewShell && aInva.HasArea() && pUp->GetUpper() ) + pViewShell->InvalidateWindows( aInva ); + } + if ( !bTst && nChg ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pUp); + aFrm.AddHeight(nChg ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pUp); + aPrt.AddHeight(nChg ); + } + + if ( pViewShell ) + pViewShell->Imp()->SetFirstVisPageInvalid(); + + if ( GetNext() ) + GetNext()->InvalidatePos_(); + + //Trigger a repaint if necessary. + std::unique_ptr<SvxBrushItem> aBack(pUp->GetFormat()->makeBackgroundBrushItem()); + const SvxGraphicPosition ePos = aBack ? aBack->GetGraphicPos() : GPOS_NONE; + if ( ePos != GPOS_NONE && ePos != GPOS_TILED ) + pViewShell->InvalidateWindows( pUp->getFrameArea() ); + + if ( pUp->GetUpper() ) + { + if ( pUp->GetNext() ) + pUp->GetNext()->InvalidatePos(); + + //Sad but true: during notify on ViewImp a Calc on the page and + //its Lower may be called. The values should not be changed + //because the caller takes care of the adjustment of Frame and + //Prt. + const long nOldFrameHeight = getFrameArea().Height(); + const long nOldPrtHeight = getFramePrintArea().Height(); + const bool bOldComplete = IsCompletePaint(); + + if ( IsBodyFrame() ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( nOldFrameHeight ); + } + + if ( pUp->GetUpper() ) + { + static_cast<SwRootFrame*>(pUp->GetUpper())->CheckViewLayout( nullptr, nullptr ); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( nOldFrameHeight ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( nOldPrtHeight ); + + mbCompletePaint = bOldComplete; + } + if ( !IsBodyFrame() ) + pUp->InvalidateSize_(); + InvalidatePage( static_cast<SwPageFrame*>(pUp) ); + } + nDiff -= nChg; + if ( !nDiff ) + return nChg; + else + nBrowseAdd = nChg; + } + + const SwFootnoteBossFrame *pBoss = static_cast<SwFootnoteBossFrame*>(GetUpper()); + + SwTwips nReal = 0, + nAdd = 0; + SwFrame *pFrame = nullptr; + SwRectFnSet aRectFnSet(this); + + if( IsBodyFrame() ) + { + if( IsInSct() ) + { + SwSectionFrame *pSect = FindSctFrame(); + if( nDiff > 0 && pSect->IsEndnAtEnd() && GetNext() && + GetNext()->IsFootnoteContFrame() ) + { + SwFootnoteContFrame* pCont = static_cast<SwFootnoteContFrame*>(GetNext()); + SwTwips nMinH = 0; + SwFootnoteFrame* pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + bool bFootnote = false; + while( pFootnote ) + { + if( !pFootnote->GetAttr()->GetFootnote().IsEndNote() ) + { + nMinH += aRectFnSet.GetHeight(pFootnote->getFrameArea()); + bFootnote = true; + } + pFootnote = static_cast<SwFootnoteFrame*>(pFootnote->GetNext()); + } + if( bFootnote ) + nMinH += aRectFnSet.GetTop(pCont->getFramePrintArea()); + nReal = aRectFnSet.GetHeight(pCont->getFrameArea()) - nMinH; + if( nReal > nDiff ) + nReal = nDiff; + if( nReal > 0 ) + pFrame = GetNext(); + else + nReal = 0; + } + if( !bTst && !pSect->IsColLocked() ) + pSect->InvalidateSize(); + } + if( !pFrame ) + return nBrowseAdd; + } + else + { + const bool bFootnotePage = pBoss->IsPageFrame() && static_cast<const SwPageFrame*>(pBoss)->IsFootnotePage(); + if ( bFootnotePage && !IsFootnoteContFrame() ) + pFrame = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoss->FindFootnoteCont())); + if ( !pFrame ) + pFrame = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoss->FindBodyCont())); + + if ( !pFrame ) + return 0; + + //If not one is found, everything else is solved. + nReal = aRectFnSet.GetHeight(pFrame->getFrameArea()); + if( nReal > nDiff ) + nReal = nDiff; + if( !bFootnotePage ) + { + //Respect the minimal boundary! + if( nReal ) + { + const SwTwips nMax = pBoss->GetVarSpace(); + if ( nReal > nMax ) + nReal = nMax; + } + if( !IsFootnoteContFrame() && nDiff > nReal && + pFrame->GetNext() && pFrame->GetNext()->IsFootnoteContFrame() + && ( pFrame->GetNext()->IsVertical() == IsVertical() ) + ) + { + //If the Body doesn't return enough, we look for a footnote, if + //there is one, we steal there accordingly. + const SwTwips nAddMax = aRectFnSet.GetHeight(pFrame->GetNext()->getFrameArea()); + nAdd = nDiff - nReal; + if ( nAdd > nAddMax ) + nAdd = nAddMax; + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame->GetNext()); + aRectFnSet.SetHeight(aFrm, nAddMax-nAdd); + + if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + aFrm.Pos().AdjustX(nAdd ); + } + } + + pFrame->GetNext()->InvalidatePrt(); + + if ( pFrame->GetNext()->GetNext() ) + { + pFrame->GetNext()->GetNext()->InvalidatePos_(); + } + } + } + } + } + + if ( !bTst && nReal ) + { + SwTwips nTmp = aRectFnSet.GetHeight(pFrame->getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); + aRectFnSet.SetHeight( aFrm, nTmp - nReal ); + + if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + aFrm.Pos().AdjustX(nReal ); + } + } + + pFrame->InvalidatePrt(); + + if ( pFrame->GetNext() ) + pFrame->GetNext()->InvalidatePos_(); + + if( nReal < 0 && pFrame->IsInSct() ) + { + SwLayoutFrame* pUp = pFrame->GetUpper(); + if( pUp && nullptr != ( pUp = pUp->GetUpper() ) && pUp->IsSctFrame() && + !pUp->IsColLocked() ) + pUp->InvalidateSize(); + } + if( ( IsHeaderFrame() || IsFooterFrame() ) && pBoss->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pBoss->GetDrawObjs(); + OSL_ENSURE( pBoss->IsPageFrame(), "Header/Footer out of page?" ); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + OSL_ENSURE( !pFly->IsFlyInContentFrame(), "FlyInCnt at Page?" ); + const SwFormatVertOrient &rVert = + pFly->GetFormat()->GetVertOrient(); + // When do we have to invalidate? + // If a frame is aligned on a PageTextArea and the header + // changes a TOP, MIDDLE or NONE aligned frame needs to + // recalculate it's position; if the footer changes a BOTTOM + // or MIDDLE aligned frame needs to recalculate it's + // position. + if( ( rVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA || + rVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) && + ((IsHeaderFrame() && rVert.GetVertOrient()!=text::VertOrientation::BOTTOM) || + (IsFooterFrame() && rVert.GetVertOrient()!=text::VertOrientation::NONE && + rVert.GetVertOrient() != text::VertOrientation::TOP)) ) + { + pFly->InvalidatePos_(); + pFly->Invalidate_(); + } + } + } + } + } + return (nBrowseAdd + nReal + nAdd); +} + +/** method to perform additional actions on an invalidation (2004-05-19 #i28701#) */ +void SwFrame::ActionOnInvalidation( const InvalidationType ) +{ + // default behaviour is to perform no additional action +} + +/** method to determine, if an invalidation is allowed (2004-05-19 #i28701#) */ +bool SwFrame::InvalidationAllowed( const InvalidationType ) const +{ + // default behaviour is to allow invalidation + return true; +} + +void SwFrame::ImplInvalidateSize() +{ + if ( InvalidationAllowed( INVALID_SIZE ) ) + { + setFrameAreaSizeValid(false); + + if ( IsFlyFrame() ) + static_cast<SwFlyFrame*>(this)->Invalidate_(); + else + InvalidatePage(); + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_SIZE ); + } +} + +void SwFrame::ImplInvalidatePrt() +{ + if ( InvalidationAllowed( INVALID_PRTAREA ) ) + { + setFramePrintAreaValid(false); + + if ( IsFlyFrame() ) + static_cast<SwFlyFrame*>(this)->Invalidate_(); + else + InvalidatePage(); + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_PRTAREA ); + } +} + +void SwFrame::ImplInvalidatePos() +{ + if ( InvalidationAllowed( INVALID_POS ) ) + { + setFrameAreaPositionValid(false); + + if ( IsFlyFrame() ) + { + static_cast<SwFlyFrame*>(this)->Invalidate_(); + } + else + { + InvalidatePage(); + } + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_POS ); + } +} + +void SwFrame::ImplInvalidateLineNum() +{ + if ( InvalidationAllowed( INVALID_LINENUM ) ) + { + mbValidLineNum = false; + OSL_ENSURE( IsTextFrame(), "line numbers are implemented for text only" ); + InvalidatePage(); + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_LINENUM ); + } +} + +void SwFrame::ReinitializeFrameSizeAttrFlags() +{ + const SwFormatFrameSize &rFormatSize = GetAttrSet()->GetFrameSize(); + if ( SwFrameSize::Variable == rFormatSize.GetHeightSizeType() || + SwFrameSize::Minimum == rFormatSize.GetHeightSizeType()) + { + mbFixSize = false; + if ( GetType() & (SwFrameType::Header | SwFrameType::Footer | SwFrameType::Row) ) + { + SwFrame *pFrame = static_cast<SwLayoutFrame*>(this)->Lower(); + while ( pFrame ) + { pFrame->InvalidateSize_(); + pFrame->InvalidatePrt_(); + pFrame = pFrame->GetNext(); + } + SwContentFrame *pCnt = static_cast<SwLayoutFrame*>(this)->ContainsContent(); + // #i36991# - be save. + // E.g., a row can contain *no* content. + if ( pCnt ) + { + pCnt->InvalidatePage(); + do + { + pCnt->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pCnt->InvalidateSize_(); + pCnt = pCnt->GetNextContentFrame(); + } while ( static_cast<SwLayoutFrame*>(this)->IsAnLower( pCnt ) ); + } + } + } + else if ( rFormatSize.GetHeightSizeType() == SwFrameSize::Fixed ) + { + if( IsVertical() ) + ChgSize( Size( rFormatSize.GetWidth(), getFrameArea().Height())); + else + ChgSize( Size( getFrameArea().Width(), rFormatSize.GetHeight())); + } +} + +void SwFrame::ValidateThisAndAllLowers( const sal_uInt16 nStage ) +{ + // Stage 0: Only validate frames. Do not process any objects. + // Stage 1: Only validate fly frames and all of their contents. + // Stage 2: Validate all. + + const bool bOnlyObject = 1 == nStage; + const bool bIncludeObjects = 1 <= nStage; + + if ( !bOnlyObject || dynamic_cast< const SwFlyFrame *>( this ) != nullptr ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + setFrameAreaPositionValid(true); + } + + if ( bIncludeObjects ) + { + const SwSortedObjs* pObjs = GetDrawObjs(); + if ( pObjs ) + { + const size_t nCnt = pObjs->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchObj = (*pObjs)[i]; + if ( dynamic_cast< const SwFlyFrame *>( pAnchObj ) != nullptr ) + static_cast<SwFlyFrame*>(pAnchObj)->ValidateThisAndAllLowers( 2 ); + else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchObj ) != nullptr ) + static_cast<SwAnchoredDrawObject*>(pAnchObj)->ValidateThis(); + } + } + } + + if ( IsLayoutFrame() ) + { + SwFrame* pLower = static_cast<SwLayoutFrame*>(this)->Lower(); + while ( pLower ) + { + pLower->ValidateThisAndAllLowers( nStage ); + pLower = pLower->GetNext(); + } + } +} + +SwTwips SwContentFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight > 0 && + nDist > (LONG_MAX - nFrameHeight ) ) + nDist = LONG_MAX - nFrameHeight; + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwFrameType nTmpType = SwFrameType::Cell | SwFrameType::Column; + if (bBrowse) + nTmpType |= SwFrameType::Body; + if( !(GetUpper()->GetType() & nTmpType) && GetUpper()->HasFixSize() ) + { + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight + nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + } + + if ( GetNext() ) + { + GetNext()->InvalidatePos(); + } + // #i28701# - Due to the new object positioning the + // frame on the next page/column can flow backward (e.g. it was moved forward + // due to the positioning of its objects ). Thus, invalivate this next frame, + // if document compatibility option 'Consider wrapping style influence on + // object positioning' is ON. + else if ( GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + } + return 0; + } + + SwTwips nReal = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); + SwFrame *pFrame = GetUpper()->Lower(); + while( pFrame && nReal > 0 ) + { nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + + if ( !bTst ) + { + //Contents are always resized to the wished value. + long nOld = aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + aRectFnSet.SetHeight( aFrm, nOld + nDist ); + + if( IsVertical()&& !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + } + + SwTabFrame *pTab = (nOld && IsInTab()) ? FindTabFrame() : nullptr; + if (pTab) + { + if ( pTab->GetTable()->GetHTMLTableLayout() && + !pTab->IsJoinLocked() && + !pTab->GetFormat()->GetDoc()->GetDocShell()->IsReadOnly() ) + { + pTab->InvalidatePos(); + pTab->SetResizeHTMLTable(); + } + } + } + + //Only grow Upper if necessary. + if ( nReal < nDist ) + { + if( GetUpper() ) + { + if( bTst || !GetUpper()->IsFooterFrame() ) + nReal = GetUpper()->Grow( nDist - std::max<long>(nReal, 0), + bTst, bInfo ); + else + { + nReal = 0; + GetUpper()->InvalidateSize(); + } + } + else + nReal = 0; + } + else + nReal = nDist; + + // #i28701# - Due to the new object positioning the + // frame on the next page/column can flow backward (e.g. it was moved forward + // due to the positioning of its objects ). Thus, invalivate this next frame, + // if document compatibility option 'Consider wrapping style influence on + // object positioning' is ON. + if ( !bTst ) + { + if ( GetNext() ) + { + GetNext()->InvalidatePos(); + } + else if ( GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + } + + return nReal; +} + +SwTwips SwContentFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + OSL_ENSURE( nDist >= 0, "nDist < 0" ); + OSL_ENSURE( nDist <= aRectFnSet.GetHeight(getFrameArea()), + "nDist > than current size." ); + + if ( !bTst ) + { + SwTwips nRstHeight; + if( GetUpper() ) + nRstHeight = aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()) ); + else + nRstHeight = 0; + if( nRstHeight < 0 ) + { + SwTwips nNextHeight = 0; + if( GetUpper()->IsSctFrame() && nDist > LONG_MAX/2 ) + { + SwFrame *pNxt = GetNext(); + while( pNxt ) + { + nNextHeight += aRectFnSet.GetHeight(pNxt->getFrameArea()); + pNxt = pNxt->GetNext(); + } + } + nRstHeight = nDist + nRstHeight - nNextHeight; + } + else + { + nRstHeight = nDist; + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) - nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nDist ); + } + } + + nDist = nRstHeight; + SwTabFrame *pTab = IsInTab() ? FindTabFrame() : nullptr; + if (pTab) + { + if ( pTab->GetTable()->GetHTMLTableLayout() && + !pTab->IsJoinLocked() && + !pTab->GetFormat()->GetDoc()->GetDocShell()->IsReadOnly() ) + { + pTab->InvalidatePos(); + pTab->SetResizeHTMLTable(); + } + } + } + + SwTwips nReal; + if( GetUpper() && nDist > 0 ) + { + if( bTst || !GetUpper()->IsFooterFrame() ) + nReal = GetUpper()->Shrink( nDist, bTst, bInfo ); + else + { + nReal = 0; + + // #108745# Sorry, dear old footer friend, I'm not gonna invalidate you, + // if there are any objects anchored inside your content, which + // overlap with the shrinking frame. + // This may lead to a footer frame that is too big, but this is better + // than looping. + // #109722# : The fix for #108745# was too strict. + + bool bInvalidate = true; + const SwRect aRect( getFrameArea() ); + const SwPageFrame* pPage = FindPageFrame(); + const SwSortedObjs* pSorted = pPage ? pPage->GetSortedObjs() : nullptr; + if( pSorted ) + { + for (SwAnchoredObject* pAnchoredObj : *pSorted) + { + const SwRect aBound( pAnchoredObj->GetObjRectWithSpaces() ); + + if( aBound.Left() > aRect.Right() ) + continue; + + if( aBound.IsOver( aRect ) ) + { + const SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + if( css::text::WrapTextMode_THROUGH != rFormat.GetSurround().GetSurround() ) + { + const SwFrame* pAnchor = pAnchoredObj->GetAnchorFrame(); + if ( pAnchor && pAnchor->FindFooterOrHeader() == GetUpper() ) + { + bInvalidate = false; + break; + } + } + } + } + } + + if ( bInvalidate ) + GetUpper()->InvalidateSize(); + } + } + else + nReal = 0; + + if ( !bTst ) + { + //The position of the next Frame changes for sure. + InvalidateNextPos(); + + //If I don't have a successor I have to do the retouch by myself. + if ( !GetNext() ) + SetRetouche(); + } + return nReal; +} + +void SwContentFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem * pNew ) +{ + sal_uInt8 nInvFlags = 0; + + if( pNew && RES_ATTRSET_CHG == pNew->Which() && pOld ) + { + SfxItemIter aNIter( *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet() ); + SfxItemIter aOIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); + do + { + UpdateAttr_(pOItem, pNItem, nInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + if ( aOldSet.Count() || aNewSet.Count() ) + SwFrame::Modify( &aOldSet, &aNewSet ); + } + else + UpdateAttr_( pOld, pNew, nInvFlags ); + + if ( nInvFlags == 0 ) + return; + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if ( nInvFlags & 0x01 ) + SetCompletePaint(); + if ( nInvFlags & 0x02 ) + InvalidatePos_(); + if ( nInvFlags & 0x04 ) + InvalidateSize_(); + if ( nInvFlags & 0x88 ) + { + if( IsInSct() && !GetPrev() ) + { + SwSectionFrame *pSect = FindSctFrame(); + if( pSect->ContainsAny() == this ) + { + pSect->InvalidatePrt_(); + pSect->InvalidatePage( pPage ); + } + } + InvalidatePrt_(); + } + SwFrame* pNextFrame = GetIndNext(); + if ( pNextFrame && nInvFlags & 0x10) + { + pNextFrame->InvalidatePrt_(); + pNextFrame->InvalidatePage( pPage ); + } + if ( pNextFrame && nInvFlags & 0x80 ) + { + pNextFrame->SetCompletePaint(); + } + if ( nInvFlags & 0x20 ) + { + SwFrame* pPrevFrame = GetPrev(); + if ( pPrevFrame ) + { + pPrevFrame->InvalidatePrt_(); + pPrevFrame->InvalidatePage( pPage ); + } + } + if ( nInvFlags & 0x40 ) + InvalidateNextPos(); + +} + +void SwContentFrame::UpdateAttr_( const SfxPoolItem* pOld, const SfxPoolItem* pNew, + sal_uInt8 &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch ( nWhich ) + { + case RES_FMT_CHG: + rInvFlags = 0xFF; + [[fallthrough]]; + + case RES_PAGEDESC: //attribute changes (on/off) + if ( IsInDocBody() && !IsInTab() ) + { + rInvFlags |= 0x02; + SwPageFrame *pPage = FindPageFrame(); + if ( !GetPrev() ) + CheckPageDescs( pPage ); + if (GetPageDescItem().GetNumOffset()) + static_cast<SwRootFrame*>(pPage->GetUpper())->SetVirtPageNum( true ); + SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); + pPage->GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + break; + + case RES_UL_SPACE: + { + // OD 2004-02-18 #106629# - correction + // Invalidation of the printing area of next frame, not only + // for footnote content. + if ( !GetIndNext() ) + { + SwFrame* pNxt = FindNext(); + if ( pNxt ) + { + SwPageFrame* pPg = pNxt->FindPageFrame(); + pNxt->InvalidatePage( pPg ); + pNxt->InvalidatePrt_(); + if( pNxt->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + if( pCnt ) + { + pCnt->InvalidatePrt_(); + pCnt->InvalidatePage( pPg ); + } + } + pNxt->SetCompletePaint(); + } + } + // OD 2004-03-17 #i11860# + if ( GetIndNext() && + !GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::USE_FORMER_OBJECT_POS) ) + { + // OD 2004-07-01 #i28701# - use new method <InvalidateObjs(..)> + GetIndNext()->InvalidateObjs(); + } + Prepare( PrepareHint::ULSpaceChanged ); //TextFrame has to correct line spacing. + rInvFlags |= 0x80; + [[fallthrough]]; + } + case RES_LR_SPACE: + case RES_BOX: + case RES_SHADOW: + Prepare( PrepareHint::FixSizeChanged ); + SwFrame::Modify( pOld, pNew ); + rInvFlags |= 0x30; + break; + + case RES_BREAK: + { + rInvFlags |= 0x42; + const IDocumentSettingAccess& rIDSA = GetUpper()->GetFormat()->getIDocumentSettingAccess(); + if( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) || + rIDSA.get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES) ) + { + rInvFlags |= 0x1; + SwFrame* pNxt = FindNext(); + if( pNxt ) + { + SwPageFrame* pPg = pNxt->FindPageFrame(); + pNxt->InvalidatePage( pPg ); + pNxt->InvalidatePrt_(); + if( pNxt->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + if( pCnt ) + { + pCnt->InvalidatePrt_(); + pCnt->InvalidatePage( pPg ); + } + } + pNxt->SetCompletePaint(); + } + } + } + break; + + // OD 2004-02-26 #i25029# + case RES_PARATR_CONNECT_BORDER: + { + rInvFlags |= 0x01; + if ( IsTextFrame() ) + { + InvalidateNextPrtArea(); + } + if ( !GetIndNext() && IsInTab() && IsInSplitTableRow() ) + { + FindTabFrame()->InvalidateSize(); + } + } + break; + + case RES_PARATR_TABSTOP: + case RES_CHRATR_SHADOWED: + case RES_CHRATR_AUTOKERN: + case RES_CHRATR_UNDERLINE: + case RES_CHRATR_OVERLINE: + case RES_CHRATR_KERNING: + case RES_CHRATR_FONT: + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_ESCAPEMENT: + case RES_CHRATR_CONTOUR: + case RES_PARATR_NUMRULE: + rInvFlags |= 0x01; + break; + + case RES_FRM_SIZE: + rInvFlags |= 0x01; + [[fallthrough]]; + + default: + bClear = false; + } + if ( bClear ) + { + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + SwFrame::Modify( pOld, pNew ); + } +} + +SwLayoutFrame::SwLayoutFrame(SwFrameFormat *const pFormat, SwFrame *const pSib) + : SwFrame(pFormat, pSib) + , m_pLower(nullptr) +{ + const SwFormatFrameSize &rFormatSize = pFormat->GetFrameSize(); + if ( rFormatSize.GetHeightSizeType() == SwFrameSize::Fixed ) + mbFixSize = true; +} + +// #i28701# + +SwTwips SwLayoutFrame::InnerHeight() const +{ + const SwFrame* pCnt = Lower(); + if (!pCnt) + return 0; + + SwRectFnSet aRectFnSet(this); + SwTwips nRet = 0; + if( pCnt->IsColumnFrame() || pCnt->IsCellFrame() ) + { + do + { + SwTwips nTmp = static_cast<const SwLayoutFrame*>(pCnt)->InnerHeight(); + if( pCnt->isFramePrintAreaValid() ) + nTmp += aRectFnSet.GetHeight(pCnt->getFrameArea()) - + aRectFnSet.GetHeight(pCnt->getFramePrintArea()); + if( nRet < nTmp ) + nRet = nTmp; + pCnt = pCnt->GetNext(); + } while ( pCnt ); + } + else + { + do + { + nRet += aRectFnSet.GetHeight(pCnt->getFrameArea()); + if( pCnt->IsContentFrame() && static_cast<const SwTextFrame*>(pCnt)->IsUndersized() ) + nRet += static_cast<const SwTextFrame*>(pCnt)->GetParHeight() - + aRectFnSet.GetHeight(pCnt->getFramePrintArea()); + if( pCnt->IsLayoutFrame() && !pCnt->IsTabFrame() ) + nRet += static_cast<const SwLayoutFrame*>(pCnt)->InnerHeight() - + aRectFnSet.GetHeight(pCnt->getFramePrintArea()); + pCnt = pCnt->GetNext(); + } while( pCnt ); + + } + return nRet; +} + +SwTwips SwLayoutFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwFrameType nTmpType = SwFrameType::Cell | SwFrameType::Column; + if (bBrowse) + nTmpType |= SwFrameType::Body; + if( !(GetType() & nTmpType) && HasFixSize() ) + return 0; + + SwRectFnSet aRectFnSet(this); + const SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + const SwTwips nFramePos = getFrameArea().Pos().X(); + + if ( nFrameHeight > 0 && nDist > (LONG_MAX - nFrameHeight) ) + nDist = LONG_MAX - nFrameHeight; + + SwTwips nMin = 0; + if ( GetUpper() && !IsCellFrame() ) + { + SwFrame *pFrame = GetUpper()->Lower(); + while( pFrame ) + { nMin += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + nMin = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) - nMin; + if ( nMin < 0 ) + nMin = 0; + } + + SwRect aOldFrame( getFrameArea() ); + bool bMoveAccFrame = false; + + bool bChgPos = IsVertical(); + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight + nDist ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + + bMoveAccFrame = true; + } + + SwTwips nReal = nDist - nMin; + if ( nReal > 0 ) + { + if ( GetUpper() ) + { // AdjustNeighbourhood now only for the columns (but not in frames) + SwNeighbourAdjust nAdjust = GetUpper()->IsFootnoteBossFrame() ? + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() + : SwNeighbourAdjust::GrowShrink; + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + nReal = AdjustNeighbourhood( nReal, bTst ); + else + { + if( SwNeighbourAdjust::AdjustGrow == nAdjust ) + nReal += AdjustNeighbourhood( nReal, bTst ); + + SwTwips nGrow = 0; + if( 0 < nReal ) + { + SwFrame* pToGrow = GetUpper(); + // NEW TABLES + // A cell with a row span of > 1 is allowed to grow the + // line containing the end of the row span if it is + // located in the same table frame: + const SwCellFrame* pThisCell = dynamic_cast<const SwCellFrame*>(this); + if ( pThisCell && pThisCell->GetLayoutRowSpan() > 1 ) + { + SwCellFrame& rEndCell = const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( false )); + if ( -1 == rEndCell.GetTabBox()->getRowSpan() ) + pToGrow = rEndCell.GetUpper(); + else + pToGrow = nullptr; + } + + nGrow = pToGrow ? pToGrow->Grow( nReal, bTst, bInfo ) : 0; + } + + if( SwNeighbourAdjust::GrowAdjust == nAdjust && nGrow < nReal ) + nReal = o3tl::saturating_add(nReal, AdjustNeighbourhood( nReal - nGrow, bTst )); + + if ( IsFootnoteFrame() && (nGrow != nReal) && GetNext() ) + { + //Footnotes can replace their successor. + SwTwips nSpace = bTst ? 0 : -nDist; + const SwFrame *pFrame = GetUpper()->Lower(); + do + { nSpace += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } while ( pFrame != GetNext() ); + nSpace = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) -nSpace; + if ( nSpace < 0 ) + nSpace = 0; + nSpace += nGrow; + if ( nReal > nSpace ) + nReal = nSpace; + if ( nReal && !bTst ) + static_cast<SwFootnoteFrame*>(this)->InvalidateNxtFootnoteCnts( FindPageFrame() ); + } + else + nReal = nGrow; + } + } + else + nReal = 0; + + nReal += nMin; + } + else + nReal = nDist; + + if ( !bTst ) + { + if( nReal != nDist && + // NEW TABLES + ( !IsCellFrame() || static_cast<SwCellFrame*>(this)->GetLayoutRowSpan() > 1 ) ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight + nReal ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().setX( nFramePos - nReal ); + } + + bMoveAccFrame = true; + } + + if ( nReal ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + } + if ( !IsPageBodyFrame() ) + { + InvalidateAll_(); + InvalidatePage( pPage ); + } + if (!(GetType() & (SwFrameType::Row|SwFrameType::Tab|SwFrameType::FtnCont|SwFrameType::Page|SwFrameType::Root))) + NotifyLowerObjs(); + + if( IsCellFrame() ) + InvaPercentLowers( nReal ); + + std::unique_ptr<SvxBrushItem> aBack(GetFormat()->makeBackgroundBrushItem()); + const SvxGraphicPosition ePos = aBack ? aBack->GetGraphicPos() : GPOS_NONE; + if ( GPOS_NONE != ePos && GPOS_TILED != ePos ) + SetCompletePaint(); + } + } + + if( bMoveAccFrame && IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); + } + } + return nReal; +} + +SwTwips SwLayoutFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwFrameType nTmpType = SwFrameType::Cell | SwFrameType::Column; + if (bBrowse) + nTmpType |= SwFrameType::Body; + + if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden()) + { + if (IsBodyFrame()) + { + // Whitespace is hidden and this body frame will not shrink, as it + // has a fix size. + // Invalidate the page frame size, so in case the reason for the + // shrink was that there is more whitespace on this page, the size + // without whitespace will be recalculated correctly. + SwPageFrame* pPageFrame = FindPageFrame(); + pPageFrame->InvalidateSize(); + } + } + + if( !(GetType() & nTmpType) && HasFixSize() ) + return 0; + + OSL_ENSURE( nDist >= 0, "nDist < 0" ); + SwRectFnSet aRectFnSet(this); + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nDist > nFrameHeight ) + nDist = nFrameHeight; + + SwTwips nMin = 0; + bool bChgPos = IsVertical(); + if ( Lower() ) + { + if( !Lower()->IsNeighbourFrame() ) + { const SwFrame *pFrame = Lower(); + const long nTmp = aRectFnSet.GetHeight(getFramePrintArea()); + while( pFrame && nMin < nTmp ) + { nMin += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + } + } + SwTwips nReal = nDist; + SwTwips nMinDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nMin; + if( nReal > nMinDiff ) + nReal = nMinDiff; + if( nReal <= 0 ) + return nDist; + + SwRect aOldFrame( getFrameArea() ); + bool bMoveAccFrame = false; + + SwTwips nRealDist = nReal; + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight - nReal ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nReal ); + } + + bMoveAccFrame = true; + } + + SwNeighbourAdjust nAdjust = GetUpper() && GetUpper()->IsFootnoteBossFrame() ? + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() + : SwNeighbourAdjust::GrowShrink; + + // AdjustNeighbourhood also in columns (but not in frames) + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + { + if ( IsPageBodyFrame() && !bBrowse ) + nReal = nDist; + else + { nReal = AdjustNeighbourhood( -nReal, bTst ); + nReal *= -1; + if ( !bTst && IsBodyFrame() && nReal < nRealDist ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) + nRealDist - nReal ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nRealDist - nReal ); + } + + OSL_ENSURE( !IsAccessibleFrame(), "bMoveAccFrame has to be set!" ); + } + } + } + else if( IsColumnFrame() || IsColBodyFrame() ) + { + SwTwips nTmp = GetUpper()->Shrink( nReal, bTst, bInfo ); + if ( nTmp != nReal ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) + nReal - nTmp ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nTmp - nReal ); + } + + OSL_ENSURE( !IsAccessibleFrame(), "bMoveAccFrame has to be set!" ); + nReal = nTmp; + } + } + else + { + SwTwips nShrink = nReal; + SwFrame* pToShrink = GetUpper(); + const SwCellFrame* pThisCell = dynamic_cast<const SwCellFrame*>(this); + // NEW TABLES + if ( pThisCell && pThisCell->GetLayoutRowSpan() > 1 ) + { + SwCellFrame& rEndCell = const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( false )); + pToShrink = rEndCell.GetUpper(); + } + + nReal = pToShrink ? pToShrink->Shrink( nShrink, bTst, bInfo ) : 0; + if( ( SwNeighbourAdjust::GrowAdjust == nAdjust || SwNeighbourAdjust::AdjustGrow == nAdjust ) + && nReal < nShrink ) + AdjustNeighbourhood( nReal - nShrink ); + } + + if( bMoveAccFrame && IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); + } + } + if ( !bTst && (IsCellFrame() || IsColumnFrame() ? nReal : nRealDist) ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + if ( IsTabFrame() ) + static_cast<SwTabFrame*>(this)->SetComplete(); + } + else + { if ( IsRetoucheFrame() ) + SetRetouche(); + if ( IsTabFrame() ) + { + static_cast<SwTabFrame*>(this)->SetComplete(); + if ( Lower() ) // Can also be in the Join and be empty! + InvalidateNextPos(); + } + } + if ( !IsBodyFrame() ) + { + InvalidateAll_(); + InvalidatePage( pPage ); + bool bCompletePaint = true; + const SwFrameFormat* pFormat = GetFormat(); + if (pFormat) + { + std::unique_ptr<SvxBrushItem> aBack(pFormat->makeBackgroundBrushItem()); + const SvxGraphicPosition ePos = aBack ? aBack->GetGraphicPos() : GPOS_NONE; + if ( GPOS_NONE == ePos || GPOS_TILED == ePos ) + bCompletePaint = false; + } + if (bCompletePaint) + SetCompletePaint(); + } + + if (!(GetType() & (SwFrameType::Row|SwFrameType::Tab|SwFrameType::FtnCont|SwFrameType::Page|SwFrameType::Root))) + NotifyLowerObjs(); + + if( IsCellFrame() ) + InvaPercentLowers( nReal ); + + SwContentFrame *pCnt; + if( IsFootnoteFrame() && !static_cast<SwFootnoteFrame*>(this)->GetAttr()->GetFootnote().IsEndNote() && + ( GetFormat()->GetDoc()->GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER || + ( IsInSct() && FindSctFrame()->IsFootnoteAtEnd() ) ) && + nullptr != (pCnt = static_cast<SwFootnoteFrame*>(this)->GetRefFromAttr() ) ) + { + if ( pCnt->IsFollow() ) + { // If we are in another column/page than the frame with the + // reference, we don't need to invalidate its master. + SwFrame *pTmp = pCnt->FindFootnoteBossFrame(true) == FindFootnoteBossFrame(true) + ? &pCnt->FindMaster()->GetFrame() : pCnt; + pTmp->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pTmp->InvalidateSize(); + } + else + pCnt->InvalidatePos(); + } + } + return nReal; +} + +/** + * Changes the size of the directly subsidiary Frame's that have a fixed size, proportionally to the + * size change of the PrtArea of the Frame's. + * + * The variable Frames are also proportionally adapted; they will grow/shrink again by themselves. + */ +void SwLayoutFrame::ChgLowersProp( const Size& rOldSize ) +{ + // no change of lower properties for root frame or if no lower exists. + if ( IsRootFrame() || !Lower() ) + return; + + // declare and init <SwFrame* pLowerFrame> with first lower + SwFrame *pLowerFrame = Lower(); + + // declare and init const booleans <bHeightChgd> and <bWidthChg> + const bool bHeightChgd = rOldSize.Height() != getFramePrintArea().Height(); + const bool bWidthChgd = rOldSize.Width() != getFramePrintArea().Width(); + + SwRectFnSet aRectFnSet(this); + + // This shortcut basically tries to handle only lower frames that + // are affected by the size change. Otherwise much more lower frames + // are invalidated. + if ( !( aRectFnSet.IsVert() ? bHeightChgd : bWidthChgd ) && + ! Lower()->IsColumnFrame() && + ( ( IsBodyFrame() && IsInDocBody() && ( !IsInSct() || !FindSctFrame()->IsColLocked() ) ) || + // #i10826# Section frames without columns should not + // invalidate all lowers! + IsSctFrame() ) ) + { + // Determine page frame the body frame resp. the section frame belongs to. + SwPageFrame *pPage = FindPageFrame(); + // Determine last lower by traveling through them using <GetNext()>. + // During travel check each section frame, if it will be sized to + // maximum. If Yes, invalidate size of section frame and set + // corresponding flags at the page. + do + { + if( pLowerFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pLowerFrame)->ToMaximize_() ) + { + pLowerFrame->InvalidateSize_(); + pLowerFrame->InvalidatePage( pPage ); + } + if( pLowerFrame->GetNext() ) + pLowerFrame = pLowerFrame->GetNext(); + else + break; + } while( true ); + // If found last lower is a section frame containing no section + // (section frame isn't valid and will be deleted in the future), + // travel backwards. + while( pLowerFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pLowerFrame)->GetSection() && + pLowerFrame->GetPrev() ) + pLowerFrame = pLowerFrame->GetPrev(); + // If found last lower is a section frame, set <pLowerFrame> to its last + // content, if the section frame is valid and is not sized to maximum. + // Otherwise set <pLowerFrame> to NULL - In this case body frame only + // contains invalid section frames. + if( pLowerFrame->IsSctFrame() ) + pLowerFrame = static_cast<SwSectionFrame*>(pLowerFrame)->GetSection() && + !static_cast<SwSectionFrame*>(pLowerFrame)->ToMaximize( false ) ? + static_cast<SwSectionFrame*>(pLowerFrame)->FindLastContent() : nullptr; + + // continue with found last lower, probably the last content of a section + if ( pLowerFrame ) + { + // If <pLowerFrame> is in a table frame, set <pLowerFrame> to this table + // frame and continue. + if ( pLowerFrame->IsInTab() ) + { + // OD 28.10.2002 #97265# - safeguard for setting <pLowerFrame> to + // its table frame - check, if the table frame is also a lower + // of the body frame, in order to assure that <pLowerFrame> is not + // set to a frame, which is an *upper* of the body frame. + SwFrame* pTableFrame = pLowerFrame->FindTabFrame(); + if ( IsAnLower( pTableFrame ) ) + { + pLowerFrame = pTableFrame; + } + } + // Check, if variable size of body frame resp. section frame has grown + // OD 28.10.2002 #97265# - correct check, if variable size has grown. + SwTwips nOldHeight = aRectFnSet.IsVert() ? rOldSize.Width() : rOldSize.Height(); + if( nOldHeight < aRectFnSet.GetHeight(getFramePrintArea()) ) + { + // If variable size of body|section frame has grown, only found + // last lower and the position of the its next have to be invalidated. + pLowerFrame->InvalidateAll_(); + pLowerFrame->InvalidatePage( pPage ); + if( !pLowerFrame->IsFlowFrame() || + !SwFlowFrame::CastFlowFrame( pLowerFrame )->HasFollow() ) + pLowerFrame->InvalidateNextPos( true ); + if ( pLowerFrame->IsTextFrame() ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + else + { + // variable size of body|section frame has shrunk. Thus, + // invalidate all lowers not matching the new body|section size + // and the dedicated new last lower. + if( aRectFnSet.IsVert() ) + { + SwTwips nBot = getFrameArea().Left() + getFramePrintArea().Left(); + while ( pLowerFrame && pLowerFrame->GetPrev() && pLowerFrame->getFrameArea().Left() < nBot ) + { + pLowerFrame->InvalidateAll_(); + pLowerFrame->InvalidatePage( pPage ); + pLowerFrame = pLowerFrame->GetPrev(); + } + } + else + { + SwTwips nBot = getFrameArea().Top() + getFramePrintArea().Bottom(); + while ( pLowerFrame && pLowerFrame->GetPrev() && pLowerFrame->getFrameArea().Top() > nBot ) + { + pLowerFrame->InvalidateAll_(); + pLowerFrame->InvalidatePage( pPage ); + pLowerFrame = pLowerFrame->GetPrev(); + } + } + if ( pLowerFrame ) + { + pLowerFrame->InvalidateSize_(); + pLowerFrame->InvalidatePage( pPage ); + if ( pLowerFrame->IsTextFrame() ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + } + // #i41694# - improvement by removing duplicates + if ( pLowerFrame ) + { + if ( pLowerFrame->IsInSct() ) + { + // #i41694# - follow-up of issue #i10826# + // No invalidation of section frame, if it's the this. + SwFrame* pSectFrame = pLowerFrame->FindSctFrame(); + if( pSectFrame != this && IsAnLower( pSectFrame ) ) + { + pSectFrame->InvalidateSize_(); + pSectFrame->InvalidatePage( pPage ); + } + } + } + } + return; + } // end of { special case } + + // Invalidate page for content only once. + bool bInvaPageForContent = true; + + // Declare booleans <bFixChgd> and <bVarChgd>, indicating for text frame + // adjustment, if fixed/variable size has changed. + bool bFixChgd, bVarChgd; + if( aRectFnSet.IsVert() == pLowerFrame->IsNeighbourFrame() ) + { + bFixChgd = bWidthChgd; + bVarChgd = bHeightChgd; + } + else + { + bFixChgd = bHeightChgd; + bVarChgd = bWidthChgd; + } + + // Declare const unsigned short <nFixWidth> and init it this frame types + // which has fixed width in vertical respectively horizontal layout. + // In vertical layout these are neighbour frames (cell and column frames), + // header frames and footer frames. + // In horizontal layout these are all frames, which aren't neighbour frames. + const SwFrameType nFixWidth = aRectFnSet.IsVert() ? (FRM_NEIGHBOUR | FRM_HEADFOOT) + : ~SwFrameType(FRM_NEIGHBOUR); + + // Declare const unsigned short <nFixHeight> and init it this frame types + // which has fixed height in vertical respectively horizontal layout. + // In vertical layout these are all frames, which aren't neighbour frames, + // header frames, footer frames, body frames or foot note container frames. + // In horizontal layout these are neighbour frames. + const SwFrameType nFixHeight = aRectFnSet.IsVert() ? ~SwFrameType(FRM_NEIGHBOUR | FRM_HEADFOOT | FRM_BODYFTNC) + : FRM_NEIGHBOUR; + + // Travel through all lowers using <GetNext()> + while ( pLowerFrame ) + { + if ( pLowerFrame->IsTextFrame() ) + { + // Text frames will only be invalidated - prepare invalidation + if ( bFixChgd ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::FixSizeChanged ); + if ( bVarChgd ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + else + { + // If lower isn't a table, row, cell or section frame, adjust its + // frame size. + const SwFrameType nLowerType = pLowerFrame->GetType(); + if ( !(nLowerType & (SwFrameType::Tab|SwFrameType::Row|SwFrameType::Cell|SwFrameType::Section)) ) + { + if ( bWidthChgd ) + { + if( nLowerType & nFixWidth ) + { + // Considering previous conditions: + // In vertical layout set width of column, header and + // footer frames to its upper width. + // In horizontal layout set width of header, footer, + // foot note container, foot note, body and no-text + // frames to its upper width. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Width( getFramePrintArea().Width() ); + } + else if( rOldSize.Width() && !pLowerFrame->IsFootnoteFrame() ) + { + // Adjust frame width proportional, if lower isn't a + // foot note frame and condition <nLowerType & nFixWidth> + // isn't true. + // Considering previous conditions: + // In vertical layout these are foot note container, + // body and no-text frames. + // In horizontal layout these are column and no-text frames. + // OD 24.10.2002 #97265# - <double> calculation + // Perform <double> calculation of new width, if + // one of the coefficients is greater than 50000 + SwTwips nNewWidth; + if ( (pLowerFrame->getFrameArea().Width() > 50000) || + (getFramePrintArea().Width() > 50000) ) + { + double nNewWidthTmp = + ( double(pLowerFrame->getFrameArea().Width()) + * double(getFramePrintArea().Width()) ) + / double(rOldSize.Width()); + nNewWidth = SwTwips(nNewWidthTmp); + } + else + { + nNewWidth = + (pLowerFrame->getFrameArea().Width() * getFramePrintArea().Width()) / rOldSize.Width(); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Width( nNewWidth ); + } + } + if ( bHeightChgd ) + { + if( nLowerType & nFixHeight ) + { + // Considering previous conditions: + // In vertical layout set height of foot note and + // no-text frames to its upper height. + // In horizontal layout set height of column frames + // to its upper height. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Height( getFramePrintArea().Height() ); + } + // OD 01.10.2002 #102211# + // add conditions <!pLowerFrame->IsHeaderFrame()> and + // <!pLowerFrame->IsFooterFrame()> in order to avoid that + // the <Grow> of header or footer are overwritten. + // NOTE: Height of header/footer frame is determined by contents. + else if ( rOldSize.Height() && + !pLowerFrame->IsFootnoteFrame() && + !pLowerFrame->IsHeaderFrame() && + !pLowerFrame->IsFooterFrame() + ) + { + // Adjust frame height proportional, if lower isn't a + // foot note, a header or a footer frame and + // condition <nLowerType & nFixHeight> isn't true. + // Considering previous conditions: + // In vertical layout these are column, foot note container, + // body and no-text frames. + // In horizontal layout these are column, foot note + // container, body and no-text frames. + + // OD 29.10.2002 #97265# - special case for page lowers + // The page lowers that have to be adjusted on page height + // change are the body frame and the foot note container + // frame. + // In vertical layout the height of both is directly + // adjusted to the page height change. + // In horizontal layout the height of the body frame is + // directly adjusted to the page height change and the + // foot note frame height isn't touched, because its + // determined by its content. + // OD 31.03.2003 #108446# - apply special case for page + // lowers - see description above - also for section columns. + if ( IsPageFrame() || + ( IsColumnFrame() && IsInSct() ) + ) + { + OSL_ENSURE( pLowerFrame->IsBodyFrame() || pLowerFrame->IsFootnoteContFrame(), + "ChgLowersProp - only for body or foot note container" ); + if ( pLowerFrame->IsBodyFrame() || pLowerFrame->IsFootnoteContFrame() ) + { + if ( IsVertical() || pLowerFrame->IsBodyFrame() ) + { + SwTwips nNewHeight = + pLowerFrame->getFrameArea().Height() + + ( getFramePrintArea().Height() - rOldSize.Height() ); + if ( nNewHeight < 0) + { + // OD 01.04.2003 #108446# - adjust assertion condition and text + OSL_ENSURE( !( IsPageFrame() && + (pLowerFrame->getFrameArea().Height()>0) && + (pLowerFrame->isFrameAreaDefinitionValid()) ), + "ChgLowersProg - negative height for lower."); + nNewHeight = 0; + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Height( nNewHeight ); + } + } + } + else + { + SwTwips nNewHeight; + // OD 24.10.2002 #97265# - <double> calculation + // Perform <double> calculation of new height, if + // one of the coefficients is greater than 50000 + if ( (pLowerFrame->getFrameArea().Height() > 50000) || + (getFramePrintArea().Height() > 50000) ) + { + double nNewHeightTmp = + ( double(pLowerFrame->getFrameArea().Height()) + * double(getFramePrintArea().Height()) ) + / double(rOldSize.Height()); + nNewHeight = SwTwips(nNewHeightTmp); + } + else + { + nNewHeight = ( pLowerFrame->getFrameArea().Height() + * getFramePrintArea().Height() ) / rOldSize.Height(); + } + if( !pLowerFrame->GetNext() ) + { + SwTwips nSum = getFramePrintArea().Height(); + SwFrame* pTmp = Lower(); + while( pTmp->GetNext() ) + { + if( !pTmp->IsFootnoteContFrame() || !pTmp->IsVertical() ) + nSum -= pTmp->getFrameArea().Height(); + pTmp = pTmp->GetNext(); + } + if( nSum - nNewHeight == 1 && + nSum == pLowerFrame->getFrameArea().Height() ) + nNewHeight = nSum; + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Height( nNewHeight ); + } + } + } + } + } // end of else { NOT text frame } + + pLowerFrame->InvalidateAll_(); + if ( bInvaPageForContent && pLowerFrame->IsContentFrame() ) + { + pLowerFrame->InvalidatePage(); + bInvaPageForContent = false; + } + + if ( !pLowerFrame->GetNext() && pLowerFrame->IsRetoucheFrame() ) + { + //If a growth took place and the subordinate elements can retouch + //itself (currently Tabs, Sections and Content) we trigger it. + if ( rOldSize.Height() < getFramePrintArea().SSize().Height() || + rOldSize.Width() < getFramePrintArea().SSize().Width() ) + pLowerFrame->SetRetouche(); + } + pLowerFrame = pLowerFrame->GetNext(); + } + + // Finally adjust the columns if width is set to auto + // Possible optimization: execute this code earlier in this function and + // return??? + if ( ( (aRectFnSet.IsVert() && bHeightChgd) || (! aRectFnSet.IsVert() && bWidthChgd) ) && + Lower()->IsColumnFrame() ) + { + // get column attribute + const SwFormatCol* pColAttr = nullptr; + if ( IsPageBodyFrame() ) + { + OSL_ENSURE( GetUpper()->IsPageFrame(), "Upper is not page frame" ); + pColAttr = &GetUpper()->GetFormat()->GetCol(); + } + else + { + OSL_ENSURE( IsFlyFrame() || IsSctFrame(), "Columns not in fly or section" ); + pColAttr = &GetFormat()->GetCol(); + } + + if ( pColAttr->IsOrtho() && pColAttr->GetNumCols() > 1 ) + AdjustColumns( pColAttr, false ); + } +} + +/** "Formats" the Frame; Frame and PrtArea. + * + * The Fixsize is not set here. + */ +void SwLayoutFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "LayoutFrame::Format, pAttrs is 0." ); + + if ( isFramePrintAreaValid() && isFrameAreaSizeValid() ) + return; + + bool bHideWhitespace = false; + if (IsPageFrame()) + { + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + if (pShell && pShell->GetViewOptions()->IsWhitespaceHidden()) + { + // This is needed so that no space is reserved for the margin on + // the last page of the document. Other pages would have no margin + // set even without this, as their frame height is the content + // height already. + bHideWhitespace = true; + } + } + + const sal_uInt16 nLeft = static_cast<sal_uInt16>(pAttrs->CalcLeft(this)); + const sal_uInt16 nUpper = bHideWhitespace ? 0 : pAttrs->CalcTop(); + + const sal_uInt16 nRight = static_cast<sal_uInt16>(pAttrs->CalcRight(this)); + const sal_uInt16 nLower = bHideWhitespace ? 0 : pAttrs->CalcBottom(); + + const bool bVert = IsVertical() && !IsPageFrame(); + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + (this->*fnRect->fnSetXMargins)( nLeft, nRight ); + (this->*fnRect->fnSetYMargins)( nUpper, nLower ); + } + + if ( !isFrameAreaSizeValid() ) + { + if ( !HasFixSize() ) + { + const SwTwips nBorder = nUpper + nLower; + const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); + SwTwips nMinHeight = rSz.GetHeightSizeType() == SwFrameSize::Minimum ? rSz.GetHeight() : 0; + do + { + setFrameAreaSizeValid(true); + + //The size in VarSize is calculated using the content plus the + // borders. + SwTwips nRemaining = 0; + SwFrame *pFrame = Lower(); + while ( pFrame ) + { nRemaining += (pFrame->getFrameArea().*fnRect->fnGetHeight)(); + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + // This TextFrame would like to be a bit bigger + nRemaining += static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - (pFrame->getFramePrintArea().*fnRect->fnGetHeight)(); + else if( pFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + nRemaining += static_cast<SwSectionFrame*>(pFrame)->Undersize(); + pFrame = pFrame->GetNext(); + } + nRemaining += nBorder; + nRemaining = std::max( nRemaining, nMinHeight ); + const SwTwips nDiff = nRemaining-(getFrameArea().*fnRect->fnGetHeight)(); + const long nOldLeft = (getFrameArea().*fnRect->fnGetLeft)(); + const long nOldTop = (getFrameArea().*fnRect->fnGetTop)(); + if ( nDiff ) + { + if ( nDiff > 0 ) + Grow( nDiff ); + else + Shrink( -nDiff ); + //Updates the positions using the fast channel. + MakePos(); + } + //Don't exceed the bottom edge of the Upper. + if ( GetUpper() && (getFrameArea().*fnRect->fnGetHeight)() ) + { + const SwTwips nLimit = (GetUpper()->*fnRect->fnGetPrtBottom)(); + if( (this->*fnRect->fnSetLimit)( nLimit ) && + nOldLeft == (getFrameArea().*fnRect->fnGetLeft)() && + nOldTop == (getFrameArea().*fnRect->fnGetTop)() ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + } + } while ( !isFrameAreaSizeValid() ); + } + else if (GetType() & FRM_HEADFOOT) + { + do + { if ( getFrameArea().Height() != pAttrs->GetSize().Height() ) + { + ChgSize( Size( getFrameArea().Width(), pAttrs->GetSize().Height())); + } + + setFrameAreaSizeValid(true); + MakePos(); + } while ( !isFrameAreaSizeValid() ); + } + else + { + setFrameAreaSizeValid(true); + } + + // While updating the size, PrtArea might be invalidated. + if (!isFramePrintAreaValid()) + { + setFramePrintAreaValid(true); + (this->*fnRect->fnSetXMargins)(nLeft, nRight); + (this->*fnRect->fnSetYMargins)(nUpper, nLower); + } + } +} + +static void InvaPercentFlys( SwFrame *pFrame, SwTwips nDiff ) +{ + OSL_ENSURE( pFrame->GetDrawObjs(), "Can't find any Objects" ); + for (SwAnchoredObject* pAnchoredObj : *pFrame->GetDrawObjs()) + { + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + const SwFormatFrameSize &rSz = pFly->GetFormat()->GetFrameSize(); + if ( rSz.GetWidthPercent() || rSz.GetHeightPercent() ) + { + bool bNotify = true; + // If we've a fly with more than 90% relative height... + if( rSz.GetHeightPercent() > 90 && pFly->GetAnchorFrame() && + rSz.GetHeightPercent() != SwFormatFrameSize::SYNCED && nDiff ) + { + const SwFrame *pRel = pFly->IsFlyLayFrame() ? pFly->GetAnchorFrame(): + pFly->GetAnchorFrame()->GetUpper(); + // ... and we have already more than 90% height and we + // not allow the text to go through... + // then a notification could cause an endless loop, e.g. + // 100% height and no text wrap inside a cell of a table. + if( pFly->getFrameArea().Height()*10 > + ( nDiff + pRel->getFramePrintArea().Height() )*9 && + pFly->GetFormat()->GetSurround().GetSurround() != + css::text::WrapTextMode_THROUGH ) + bNotify = false; + } + if( bNotify ) + pFly->InvalidateSize(); + } + } + } +} + +void SwLayoutFrame::InvaPercentLowers( SwTwips nDiff ) +{ + if ( GetDrawObjs() ) + ::InvaPercentFlys( this, nDiff ); + + SwFrame *pFrame = ContainsContent(); + if ( pFrame ) + do + { + if ( pFrame->IsInTab() && !IsTabFrame() ) + { + SwFrame *pTmp = pFrame->FindTabFrame(); + OSL_ENSURE( pTmp, "Where's my TabFrame?" ); + if( IsAnLower( pTmp ) ) + pFrame = pTmp; + } + + if ( pFrame->IsTabFrame() ) + { + const SwFormatFrameSize &rSz = static_cast<SwLayoutFrame*>(pFrame)->GetFormat()->GetFrameSize(); + if ( rSz.GetWidthPercent() || rSz.GetHeightPercent() ) + pFrame->InvalidatePrt(); + } + else if ( pFrame->GetDrawObjs() ) + ::InvaPercentFlys( pFrame, nDiff ); + pFrame = pFrame->FindNextCnt(); + } while ( pFrame && IsAnLower( pFrame ) ) ; +} + +long SwLayoutFrame::CalcRel( const SwFormatFrameSize &rSz ) const +{ + long nRet = rSz.GetWidth(), + nPercent = rSz.GetWidthPercent(); + + if ( nPercent ) + { + const SwFrame *pRel = GetUpper(); + long nRel = LONG_MAX; + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + if( pRel->IsPageBodyFrame() && pSh && bBrowseMode && pSh->VisArea().Width() ) + { + nRel = pSh->GetBrowseWidth(); + long nDiff = nRel - pRel->getFramePrintArea().Width(); + if ( nDiff > 0 ) + nRel -= nDiff; + } + nRel = std::min( nRel, pRel->getFramePrintArea().Width() ); + nRet = nRel * nPercent / 100; + } + return nRet; +} + +// Local helpers for SwLayoutFrame::FormatWidthCols() + +static long lcl_CalcMinColDiff( SwLayoutFrame *pLayFrame ) +{ + long nDiff = 0, nFirstDiff = 0; + SwLayoutFrame *pCol = static_cast<SwLayoutFrame*>(pLayFrame->Lower()); + OSL_ENSURE( pCol, "Where's the columnframe?" ); + SwFrame *pFrame = pCol->Lower(); + do + { + if( pFrame && pFrame->IsBodyFrame() ) + pFrame = static_cast<SwBodyFrame*>(pFrame)->Lower(); + if ( pFrame && pFrame->IsTextFrame() ) + { + const long nTmp = static_cast<SwTextFrame*>(pFrame)->FirstLineHeight(); + if ( nTmp != USHRT_MAX ) + { + if ( pCol == pLayFrame->Lower() ) + nFirstDiff = nTmp; + else + nDiff = nDiff ? std::min( nDiff, nTmp ) : nTmp; + } + } + //Skip empty columns! + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + while ( pCol && nullptr == (pFrame = pCol->Lower()) ) + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + + } while ( pFrame && pCol ); + + return nDiff ? nDiff : nFirstDiff ? nFirstDiff : 240; +} + +static bool lcl_IsFlyHeightClipped( SwLayoutFrame *pLay ) +{ + SwFrame *pFrame = pLay->ContainsContent(); + while ( pFrame ) + { + if ( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + + if ( pFrame->GetDrawObjs() ) + { + const size_t nCnt = pFrame->GetDrawObjs()->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + if ( pFly->IsHeightClipped() && + ( !pFly->IsFlyFreeFrame() || pFly->GetPageFrame() ) ) + return true; + } + } + } + pFrame = pFrame->FindNextCnt(); + } + return false; +} + +void SwLayoutFrame::FormatWidthCols( const SwBorderAttrs &rAttrs, + const SwTwips nBorder, const SwTwips nMinHeight ) +{ + //If there are columns involved, the size is adjusted using the last column. + //1. Format content. + //2. Calculate height of the last column: if it's too big, the Fly has to + // grow. The amount by which the Fly grows is not the amount of the + // overhang because we have to act on the assumption that some text flows + // back which will generate some more space. + // The amount which we grow by equals the overhang + // divided by the amount of columns or the overhang itself if it's smaller + // than the amount of columns. + //3. Go back to 1. until everything is stable. + + const SwFormatCol &rCol = rAttrs.GetAttrSet().GetCol(); + const sal_uInt16 nNumCols = rCol.GetNumCols(); + + bool bEnd = false; + bool bBackLock = false; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + vcl::RenderContext* pRenderContext = pSh ? pSh->GetOut() : nullptr; + { + // Underlying algorithm + // We try to find the optimal height for the column. + // nMinimum starts with the passed minimum height and is then remembered + // as the maximum height on which column content still juts out of a + // column. + // nMaximum starts with LONG_MAX and is then remembered as the minimum + // width on which the content fitted. + // In column based sections nMaximum starts at the maximum value which + // the surrounding defines, this can certainly be a value on which + // content still juts out. + // The columns are formatted. If content still juts out, nMinimum is + // adjusted accordingly, then we grow, at least by uMinDiff but not + // over a certain nMaximum. If no content juts out but there is still + // some space left in the column, shrinking is done accordingly, at + // least by nMindIff but not below the nMinimum. + // Cancel as soon as no content juts out and the difference from minimum + // to maximum is less than MinDiff or the maximum which was defined by + // the surrounding is reached even if some content still juts out. + + // Criticism of this implementation + // 1. Theoretically situations are possible in which the content fits in + // a lower height but not in a higher height. To ensure that the code + // handles such situations the code contains a few checks concerning + // minimum and maximum which probably are never triggered. + // 2. We use the same nMinDiff for shrinking and growing, but nMinDiff + // is more or less the smallest first line height and doesn't seem ideal + // as minimum value. + + long nMinimum = nMinHeight; + long nMaximum; + bool bNoBalance = false; + SwRectFnSet aRectFnSet(this); + if( IsSctFrame() ) + { + nMaximum = aRectFnSet.GetHeight(getFrameArea()) - nBorder + + aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + nMaximum += GetUpper()->Grow( LONG_MAX, true ); + if( nMaximum < nMinimum ) + { + if( nMaximum < 0 ) + nMinimum = nMaximum = 0; + else + nMinimum = nMaximum; + } + if( nMaximum > BROWSE_HEIGHT ) + nMaximum = BROWSE_HEIGHT; + + bNoBalance = static_cast<SwSectionFrame*>(this)->GetSection()->GetFormat()-> + GetBalancedColumns().GetValue(); + SwFrame* pAny = ContainsAny(); + if( bNoBalance || + ( !aRectFnSet.GetHeight(getFrameArea()) && pAny ) ) + { + long nTop = aRectFnSet.GetTopMargin(*this); + // #i23129# - correction + // to the calculated maximum height. + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nMaximum - aRectFnSet.GetHeight(getFrameArea()) ); + } + + if( nTop > nMaximum ) + nTop = nMaximum; + aRectFnSet.SetYMargins( *this, nTop, 0 ); + } + if( !pAny && !static_cast<SwSectionFrame*>(this)->IsFootnoteLock() ) + { + SwFootnoteContFrame* pFootnoteCont = static_cast<SwSectionFrame*>(this)->ContainsFootnoteCont(); + if( pFootnoteCont ) + { + SwFrame* pFootnoteAny = pFootnoteCont->ContainsAny(); + if( pFootnoteAny && pFootnoteAny->isFrameAreaDefinitionValid() ) + { + bBackLock = true; + static_cast<SwSectionFrame*>(this)->SetFootnoteLock( true ); + } + } + } + } + else + nMaximum = LONG_MAX; + + // #i3317# - reset temporarily consideration + // of wrapping style influence + SwPageFrame* pPageFrame = FindPageFrame(); + SwSortedObjs* pObjs = pPageFrame ? pPageFrame->GetSortedObjs() : nullptr; + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + } + } + } + do + { + //Could take a while therefore check for Waitcrsr here. + if ( pImp ) + pImp->CheckWaitCursor(); + + setFrameAreaSizeValid(true); + //First format the column as this will relieve the stack a bit. + //Also set width and height of the column (if they are wrong) + //while we are at it. + SwLayoutFrame *pCol = static_cast<SwLayoutFrame*>(Lower()); + + // #i27399# + // Simply setting the column width based on the values returned by + // CalcColWidth does not work for automatic column width. + AdjustColumns( &rCol, false ); + + for ( sal_uInt16 i = 0; i < nNumCols; ++i ) + { + pCol->Calc(pRenderContext); + // ColumnFrames have a BodyFrame now, which needs to be calculated + pCol->Lower()->Calc(pRenderContext); + if( pCol->Lower()->GetNext() ) + pCol->Lower()->GetNext()->Calc(pRenderContext); // SwFootnoteCont + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + + ::CalcContent( this ); + + pCol = static_cast<SwLayoutFrame*>(Lower()); + OSL_ENSURE( pCol && pCol->GetNext(), ":-( column making holidays?"); + // set bMinDiff if no empty columns exist + bool bMinDiff = true; + // OD 28.03.2003 #108446# - check for all column content and all columns + while ( bMinDiff && pCol ) + { + bMinDiff = nullptr != pCol->ContainsContent(); + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + pCol = static_cast<SwLayoutFrame*>(Lower()); + // OD 28.03.2003 #108446# - initialize local variable + SwTwips nDiff = 0; + SwTwips nMaxFree = 0; + SwTwips nAllFree = LONG_MAX; + // set bFoundLower if there is at least one non-empty column + bool bFoundLower = false; + while( pCol ) + { + SwLayoutFrame* pLay = static_cast<SwLayoutFrame*>(pCol->Lower()); + SwTwips nInnerHeight = aRectFnSet.GetHeight(pLay->getFrameArea()) - + aRectFnSet.GetHeight(pLay->getFramePrintArea()); + if( pLay->Lower() ) + { + bFoundLower = true; + nInnerHeight += pLay->InnerHeight(); + } + else if( nInnerHeight < 0 ) + nInnerHeight = 0; + + if( pLay->GetNext() ) + { + bFoundLower = true; + pLay = static_cast<SwLayoutFrame*>(pLay->GetNext()); + OSL_ENSURE( pLay->IsFootnoteContFrame(),"FootnoteContainer expected" ); + nInnerHeight += pLay->InnerHeight(); + nInnerHeight += aRectFnSet.GetHeight(pLay->getFrameArea()) - + aRectFnSet.GetHeight(pLay->getFramePrintArea()); + } + nInnerHeight -= aRectFnSet.GetHeight(pCol->getFramePrintArea()); + if( nInnerHeight > nDiff ) + { + nDiff = nInnerHeight; + nAllFree = 0; + } + else + { + if( nMaxFree < -nInnerHeight ) + nMaxFree = -nInnerHeight; + if( nAllFree > -nInnerHeight ) + nAllFree = -nInnerHeight; + } + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + + if ( bFoundLower || ( IsSctFrame() && static_cast<SwSectionFrame*>(this)->HasFollow() ) ) + { + SwTwips nMinDiff = ::lcl_CalcMinColDiff( this ); + // Here we decide if growing is needed - this is the case, if + // column content (nDiff) or a Fly juts over. + // In sections with columns we take into account to set the size + // when having a non-empty Follow. + if ( nDiff || ::lcl_IsFlyHeightClipped( this ) || + ( IsSctFrame() && static_cast<SwSectionFrame*>(this)->CalcMinDiff( nMinDiff ) ) ) + { + long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + // The minimum must not be smaller than our PrtHeight as + // long as something juts over. + if( nMinimum < nPrtHeight ) + nMinimum = nPrtHeight; + // The maximum must not be smaller than PrtHeight if + // something still juts over. + if( nMaximum < nPrtHeight ) + nMaximum = nPrtHeight; // Robust, but will this ever happen? + if( !nDiff ) // If only Flys jut over, we grow by nMinDiff + nDiff = nMinDiff; + // If we should grow more than by nMinDiff we split it over + // the columns + if ( std::abs(nDiff - nMinDiff) > nNumCols && nDiff > static_cast<long>(nNumCols) ) + nDiff /= nNumCols; + + if ( bMinDiff ) + { // If no empty column exists, we want to grow at least + // by nMinDiff. Special case: If we are smaller than the + // minimal FrameHeight and PrtHeight is smaller than + // nMindiff we grow in a way that PrtHeight is exactly + // nMinDiff afterwards. + long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nFrameHeight > nMinHeight || nPrtHeight >= nMinDiff ) + nDiff = std::max( nDiff, nMinDiff ); + else if( nDiff < nMinDiff ) + nDiff = nMinDiff - nPrtHeight + 1; + } + // nMaximum has a size which fits the content or the + // requested value from the surrounding therefore we don't + // need to exceed this value. + if( nDiff + nPrtHeight > nMaximum ) + nDiff = nMaximum - nPrtHeight; + } + else if( nMaximum > nMinimum ) // We fit, do we still have some margin? + { + long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if ( nMaximum < nPrtHeight ) + nDiff = nMaximum - nPrtHeight; // We grew over a working + // height and shrink back to it, but will this ever + // happen? + else + { // We have a new maximum, a size which fits for the content. + nMaximum = nPrtHeight; + // If the margin in the column is bigger than nMinDiff + // and we therefore drop under the minimum, we deflate + // a bit. + if ( !bNoBalance && + // #i23129# - <nMinDiff> can be + // big, because of an object at the beginning of + // a column. Thus, decrease optimization here. + //nMaxFree >= nMinDiff && + nMaxFree > 0 && + ( !nAllFree || + nMinimum < nPrtHeight - nMinDiff ) ) + { + nMaxFree /= nNumCols; // disperse over the columns + nDiff = nMaxFree < nMinDiff ? -nMinDiff : -nMaxFree; // min nMinDiff + if( nPrtHeight + nDiff <= nMinimum ) // below the minimum? + nDiff = ( nMinimum - nMaximum ) / 2; // Take the center + } + else if( nAllFree ) + { + nDiff = -nAllFree; + if( nPrtHeight + nDiff <= nMinimum ) // Less than minimum? + nDiff = ( nMinimum - nMaximum ) / 2; // Take the center + } + } + } + if( nDiff ) // now we shrink or grow... + { + Size aOldSz( getFramePrintArea().SSize() ); + long nTop = aRectFnSet.GetTopMargin(*this); + nDiff = aRectFnSet.GetHeight(getFramePrintArea()) + nDiff + nBorder - aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nDiff ); + } + + // #i68520# + SwFlyFrame *pFlyFrame = dynamic_cast<SwFlyFrame*>(this); + if (pFlyFrame) + { + pFlyFrame->InvalidateObjRectWithSpaces(); + } + aRectFnSet.SetYMargins( *this, nTop, nBorder - nTop ); + ChgLowersProp( aOldSz ); + NotifyLowerObjs(); + + // #i3317# - reset temporarily consideration + // of wrapping style influence + SwPageFrame* pTmpPageFrame = FindPageFrame(); + SwSortedObjs* pTmpObjs = pTmpPageFrame ? pTmpPageFrame->GetSortedObjs() : nullptr; + if ( pTmpObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pTmpObjs) + { + if ( IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + } + } + } + //Invalidate suitable to nicely balance the Frames. + //- Every first one after the second column gets a + // InvalidatePos(); + pCol = static_cast<SwLayoutFrame*>(Lower()->GetNext()); + while ( pCol ) + { + SwFrame *pLow = pCol->Lower(); + if ( pLow ) + pLow->InvalidatePos_(); + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + if( IsSctFrame() && static_cast<SwSectionFrame*>(this)->HasFollow() ) + { + // If we created a Follow, we need to give its content + // the opportunity to flow back inside the CalcContent + SwContentFrame* pTmpContent = + static_cast<SwSectionFrame*>(this)->GetFollow()->ContainsContent(); + if( pTmpContent ) + pTmpContent->InvalidatePos_(); + } + } + else + bEnd = true; + } + else + bEnd = true; + + } while ( !bEnd || !isFrameAreaSizeValid() ); + } + // OD 01.04.2003 #108446# - Don't collect endnotes for sections. Thus, set + // 2nd parameter to <true>. + ::CalcContent( this, true ); + if( IsSctFrame() ) + { + // OD 14.03.2003 #i11760# - adjust 2nd parameter - sal_True --> true + ::CalcContent( this, true ); + if( bBackLock ) + static_cast<SwSectionFrame*>(this)->SetFootnoteLock( false ); + } +} + +static SwContentFrame* lcl_InvalidateSection( SwFrame *pCnt, SwInvalidateFlags nInv ) +{ + SwSectionFrame* pSect = pCnt->FindSctFrame(); + // If our ContentFrame is placed inside a table or a footnote, only sections + // which are also placed inside are meant. + // Exception: If a table is directly passed. + if( ( ( pCnt->IsInTab() && !pSect->IsInTab() ) || + ( pCnt->IsInFootnote() && !pSect->IsInFootnote() ) ) && !pCnt->IsTabFrame() ) + return nullptr; + if( nInv & SwInvalidateFlags::Size ) + pSect->InvalidateSize_(); + if( nInv & SwInvalidateFlags::Pos ) + pSect->InvalidatePos_(); + if( nInv & SwInvalidateFlags::PrtArea ) + pSect->InvalidatePrt_(); + SwFlowFrame *pFoll = pSect->GetFollow(); + // Temporary separation from follow + pSect->SetFollow( nullptr ); + SwContentFrame* pRet = pSect->FindLastContent(); + pSect->SetFollow( pFoll ); + return pRet; +} + +static SwContentFrame* lcl_InvalidateTable( SwTabFrame *pTable, SwInvalidateFlags nInv ) +{ + if( ( nInv & SwInvalidateFlags::Section ) && pTable->IsInSct() ) + lcl_InvalidateSection( pTable, nInv ); + if( nInv & SwInvalidateFlags::Size ) + pTable->InvalidateSize_(); + if( nInv & SwInvalidateFlags::Pos ) + pTable->InvalidatePos_(); + if( nInv & SwInvalidateFlags::PrtArea ) + pTable->InvalidatePrt_(); + return pTable->FindLastContent(); +} + +static void lcl_InvalidateAllContent( SwContentFrame *pCnt, SwInvalidateFlags nInv ); + +static void lcl_InvalidateContent( SwContentFrame *pCnt, SwInvalidateFlags nInv ) +{ + SwContentFrame *pLastTabCnt = nullptr; + SwContentFrame *pLastSctCnt = nullptr; + while ( pCnt ) + { + if( nInv & SwInvalidateFlags::Section ) + { + if( pCnt->IsInSct() ) + { + // See above at tables + if( !pLastSctCnt ) + pLastSctCnt = lcl_InvalidateSection( pCnt, nInv ); + if( pLastSctCnt == pCnt ) + pLastSctCnt = nullptr; + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_ENSURE( !pLastSctCnt, "Where's the last SctContent?" ); +#endif + } + if( nInv & SwInvalidateFlags::Table ) + { + if( pCnt->IsInTab() ) + { + // To not call FindTabFrame() for each ContentFrame of a table and + // then invalidate the table, we remember the last ContentFrame of + // the table and ignore IsInTab() until we are past it. + // When entering the table, LastSctCnt is set to null, so + // sections inside the table are correctly invalidated. + // If the table itself is in a section the + // invalidation is done three times, which is acceptable. + if( !pLastTabCnt ) + { + pLastTabCnt = lcl_InvalidateTable( pCnt->FindTabFrame(), nInv ); + pLastSctCnt = nullptr; + } + if( pLastTabCnt == pCnt ) + { + pLastTabCnt = nullptr; + pLastSctCnt = nullptr; + } + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_ENSURE( !pLastTabCnt, "Where's the last TabContent?" ); +#endif + } + + if( nInv & SwInvalidateFlags::Size ) + pCnt->Prepare( PrepareHint::Clear, nullptr, false ); + if( nInv & SwInvalidateFlags::Pos ) + pCnt->InvalidatePos_(); + if( nInv & SwInvalidateFlags::PrtArea ) + pCnt->InvalidatePrt_(); + if ( nInv & SwInvalidateFlags::LineNum ) + pCnt->InvalidateLineNum(); + if ( pCnt->GetDrawObjs() ) + lcl_InvalidateAllContent( pCnt, nInv ); + pCnt = pCnt->GetNextContentFrame(); + } +} + +static void lcl_InvalidateAllContent( SwContentFrame *pCnt, SwInvalidateFlags nInv ) +{ + SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + if ( pFly->IsFlyInContentFrame() ) + { + ::lcl_InvalidateContent( pFly->ContainsContent(), nInv ); + if( nInv & SwInvalidateFlags::Direction ) + pFly->CheckDirChange(); + } + } + } +} + +void SwRootFrame::InvalidateAllContent( SwInvalidateFlags nInv ) +{ + // First process all page bound FlyFrames. + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + while( pPage ) + { + pPage->InvalidateFlyLayout(); + pPage->InvalidateFlyContent(); + pPage->InvalidateFlyInCnt(); + pPage->InvalidateLayout(); + pPage->InvalidateContent(); + pPage->InvalidatePage( pPage ); // So even the Turbo disappears if applicable + + if ( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + ::lcl_InvalidateContent( pFly->ContainsContent(), nInv ); + if ( nInv & SwInvalidateFlags::Direction ) + pFly->CheckDirChange(); + } + } + } + if( nInv & SwInvalidateFlags::Direction ) + pPage->CheckDirChange(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + //Invalidate the whole document content and the character bound Flys here. + ::lcl_InvalidateContent( ContainsContent(), nInv ); + + if( nInv & SwInvalidateFlags::PrtArea ) + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh ) + pSh->InvalidateWindows( getFrameArea() ); + } +} + +/** + * Invalidate/re-calculate the position of all floating screen objects (Writer fly frames and + * drawing objects), that are anchored to paragraph or to character. (2004-03-16 #i11860#) + */ +void SwRootFrame::InvalidateAllObjPos() +{ + const SwPageFrame* pPageFrame = static_cast<const SwPageFrame*>(Lower()); + while( pPageFrame ) + { + pPageFrame->InvalidateFlyLayout(); + + if ( pPageFrame->GetSortedObjs() ) + { + const SwSortedObjs& rObjs = *(pPageFrame->GetSortedObjs()); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + const SwFormatAnchor& rAnch = pAnchoredObj->GetFrameFormat().GetAnchor(); + if ((rAnch.GetAnchorId() != RndStdIds::FLY_AT_PARA) && + (rAnch.GetAnchorId() != RndStdIds::FLY_AT_CHAR)) + { + // only to paragraph and to character anchored objects are considered. + continue; + } + // #i28701# - special invalidation for anchored + // objects, whose wrapping style influence has to be considered. + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + else + pAnchoredObj->InvalidateObjPos(); + } + } + + pPageFrame = static_cast<const SwPageFrame*>(pPageFrame->GetNext()); + } +} + +static void AddRemoveFlysForNode( + SwTextFrame & rFrame, SwTextNode & rTextNode, + std::set<sal_uLong> *const pSkipped, + SwFrameFormats & rTable, + SwPageFrame *const pPage, + SwTextNode const*const pNode, + std::vector<sw::Extent>::const_iterator & rIterFirst, + std::vector<sw::Extent>::const_iterator const& rIterEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ + if (pNode == &rTextNode) + { // remove existing hidden at-char anchored flys + RemoveHiddenObjsOfNode(rTextNode, &rIterFirst, &rIterEnd, pFirstNode, pLastNode); + } + else if (rTextNode.GetIndex() < pNode->GetIndex()) + { + // pNode's frame has been deleted by CheckParaRedlineMerge() + AppendObjsOfNode(&rTable, + pNode->GetIndex(), &rFrame, pPage, rTextNode.GetDoc(), + &rIterFirst, &rIterEnd, pFirstNode, pLastNode); + if (pSkipped) + { + // if a fly has been added by AppendObjsOfNode, it must be skipped; if not, then it doesn't matter if it's skipped or not because it has no frames and because of that it would be skipped anyway + if (auto const pFlys = pNode->GetAnchoredFlys()) + { + for (auto const pFly : *pFlys) + { + if (pFly->Which() != RES_DRAWFRMFMT) + { + pSkipped->insert(pFly->GetContent().GetContentIdx()->GetIndex()); + } + } + } + } + } +} + +namespace sw { + +/// rTextNode is the first one of the "new" merge - if rTextNode isn't the same +/// as MergedPara::pFirstNode, then nodes before rTextNode have their flys +/// already properly attached, so only the other nodes need handling here. +void AddRemoveFlysAnchoredToFrameStartingAtNode( + SwTextFrame & rFrame, SwTextNode & rTextNode, + std::set<sal_uLong> *const pSkipped) +{ + auto const pMerged(rFrame.GetMergedPara()); + if (pMerged + // do this only *once*, for the *last* frame + // otherwise AppendObj would create multiple frames for fly-frames! + && !rFrame.GetFollow()) + { + assert(pMerged->pFirstNode->GetIndex() <= rTextNode.GetIndex() + && rTextNode.GetIndex() <= pMerged->pLastNode->GetIndex()); + // add visible flys in non-first node to merged frame + // (hidden flys remain and are deleted via DelFrames()) + SwFrameFormats& rTable(*rTextNode.GetDoc()->GetSpzFrameFormats()); + SwPageFrame *const pPage(rFrame.FindPageFrame()); + std::vector<sw::Extent>::const_iterator iterFirst(pMerged->extents.begin()); + std::vector<sw::Extent>::const_iterator iter(iterFirst); + SwTextNode const* pNode(pMerged->pFirstNode); + for ( ; ; ++iter) + { + if (iter == pMerged->extents.end() + || iter->pNode != pNode) + { + AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, rTable, pPage, + pNode, iterFirst, iter, + pMerged->pFirstNode, pMerged->pLastNode); + sal_uLong const until = iter == pMerged->extents.end() + ? pMerged->pLastNode->GetIndex() + 1 + : iter->pNode->GetIndex(); + for (sal_uLong i = pNode->GetIndex() + 1; i < until; ++i) + { + // let's show at-para flys on nodes that contain start/end of + // redline too, even if there's no text there + SwNode const*const pTmp(pNode->GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, + rTable, pPage, pTmp->GetTextNode(), iter, iter, + pMerged->pFirstNode, pMerged->pLastNode); + } + } + if (iter == pMerged->extents.end()) + { + break; + } + pNode = iter->pNode; + iterFirst = iter; + } + } + } +} + +} // namespace sw + +static void UnHideRedlines(SwRootFrame & rLayout, + SwNodes & rNodes, SwNode const& rEndOfSectionNode, + std::set<sal_uLong> *const pSkipped) +{ + assert(rEndOfSectionNode.IsEndNode()); + assert(rNodes[rEndOfSectionNode.StartOfSectionNode()->GetIndex() + 1]->IsCreateFrameWhenHidingRedlines()); // first node is never hidden + for (sal_uLong i = rEndOfSectionNode.StartOfSectionNode()->GetIndex() + 1; + i < rEndOfSectionNode.GetIndex(); ++i) + { + SwNode & rNode(*rNodes[i]); + if (rNode.IsTextNode()) // only text nodes are 1st node of a merge + { + SwTextNode & rTextNode(*rNode.GetTextNode()); + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTextNode); + std::vector<SwTextFrame*> frames; + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame() == &rLayout) + { + if (pFrame->IsFollow()) + { + frames.push_back(pFrame); + } // when hiding, the loop must remove the anchored flys + else // *before* resetting SetMergedPara anywhere - else + { // the fly deletion code will access multiple of the + // frames with inconsistent MergedPara and assert + frames.insert(frames.begin(), pFrame); + } + } + } + // this messes with pRegisteredIn so do it outside SwIterator + auto eMode(sw::FrameMode::Existing); + for (SwTextFrame * pFrame : frames) + { + if (rLayout.IsHideRedlines()) + { + assert(!pFrame->GetMergedPara() || + !rNode.IsCreateFrameWhenHidingRedlines()); + if (rNode.IsCreateFrameWhenHidingRedlines()) + { + { + auto pMerged(CheckParaRedlineMerge(*pFrame, + rTextNode, eMode)); + pFrame->SetMergedPara(std::move(pMerged)); + } + auto const pMerged(pFrame->GetMergedPara()); + if (pMerged) + { + // invalidate SwInvalidateFlags::Size + pFrame->Prepare(PrepareHint::Clear, nullptr, false); + pFrame->InvalidatePage(); + if (auto const pObjs = pFrame->GetDrawObjs()) + { // also invalidate position of existing flys + // because they may need to be moved + for (auto const pObject : *pObjs) + { + pObject->InvalidateObjPos(); + } + } + } + sw::AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, rTextNode, pSkipped); + // only *first* frame of node gets Existing because it + eMode = sw::FrameMode::New; // is not idempotent! + } + } + else + { + if (auto const& pMergedPara = pFrame->GetMergedPara()) + { + // invalidate SwInvalidateFlags::Size + pFrame->Prepare(PrepareHint::Clear, nullptr, false); + pFrame->InvalidatePage(); + if (auto const pObjs = pFrame->GetDrawObjs()) + { // also invalidate position of existing flys + for (auto const pObject : *pObjs) + { + pObject->InvalidateObjPos(); + } + } + // SwFlyAtContentFrame::Modify() always appends to + // the master frame, so do the same here. + // (RemoveFootnotesForNode must be called at least once) + if (!pFrame->IsFollow()) + { + // the new text frames don't exist yet, so at this point + // we can only delete the footnote frames so they don't + // point to the merged SwTextFrame any more... + assert(&rTextNode == pMergedPara->pFirstNode); + // iterate over nodes, not extents: if a node has + // no extents now but did have extents initially, + // its flys need their frames deleted too! + for (sal_uLong j = rTextNode.GetIndex() + 1; + j <= pMergedPara->pLastNode->GetIndex(); ++j) + { + SwNode *const pNode(rTextNode.GetNodes()[j]); + assert(!pNode->IsEndNode()); + if (pNode->IsStartNode()) + { + j = pNode->EndOfSectionIndex(); + } + else if (pNode->IsTextNode()) + { + sw::RemoveFootnotesForNode(rLayout, *pNode->GetTextNode(), nullptr); + // similarly, remove the anchored flys + if (auto const pFlys = pNode->GetAnchoredFlys()) + { + for (SwFrameFormat * pFormat : *pFlys) + { + pFormat->DelFrames(/*&rLayout*/); + } + } + } + } + // rely on AppendAllObjs call at the end to add + // all flys in first node that are hidden + } + pFrame->SetMergedPara(nullptr); + } + } + pFrame->Broadcast(SfxHint()); // notify SwAccessibleParagraph + } + // all nodes, not just merged ones! it may be in the same list as + if (rTextNode.IsNumbered(nullptr)) // a preceding merged one... + { // notify frames so they reformat numbering portions + rTextNode.NumRuleChgd(); + } + } + else if (rNode.IsTableNode() && rLayout.IsHideRedlines()) + { + SwPosition const tmp(rNode); + SwRangeRedline const*const pRedline( + rLayout.GetFormat()->GetDoc()->getIDocumentRedlineAccess().GetRedline(tmp, nullptr)); + // pathology: redline that starts on a TableNode; cannot + // be created in UI but by import filters... + if (pRedline + && pRedline->GetType() == RedlineType::Delete + && &pRedline->Start()->nNode.GetNode() == &rNode) + { + for (sal_uLong j = rNode.GetIndex(); j <= rNode.EndOfSectionIndex(); ++j) + { + rNode.GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + } + rNode.GetTableNode()->DelFrames(&rLayout); + } + } + if (!rNode.IsCreateFrameWhenHidingRedlines()) + { + if (rLayout.IsHideRedlines()) + { + if (rNode.IsContentNode()) + { + // note: nothing to do here, already done +#ifndef NDEBUG + auto const pFrame(static_cast<SwContentNode&>(rNode).getLayoutFrame(&rLayout)); + assert(!pFrame || static_cast<SwTextFrame*>(pFrame)->GetMergedPara()->pFirstNode != &rNode); +#endif + } + } + else + { + assert(!rNode.IsContentNode() || !rNode.GetContentNode()->getLayoutFrame(&rLayout)); + sal_uLong j = i + 1; + for ( ; j < rEndOfSectionNode.GetIndex(); ++j) + { + if (rNodes[j]->IsCreateFrameWhenHidingRedlines()) + { + break; + } + } + // call MakeFrames once, because sections/tables + // InsertCnt_ also checks for hidden sections + SwNodeIndex const start(rNodes, i); + SwNodeIndex const end(rNodes, j); + assert(!bDontCreateObjects); + bDontCreateObjects = true; // suppress here, to be called once + ::MakeFrames(rLayout.GetFormat()->GetDoc(), start, end); + bDontCreateObjects = false; + i = j - 1; // will be incremented again + } + } + } +} + +static void UnHideRedlinesExtras(SwRootFrame & rLayout, + SwNodes & rNodes, SwNode const& rEndOfExtraSectionNode, + std::set<sal_uLong> *const pSkipped) +{ + assert(rEndOfExtraSectionNode.IsEndNode()); + for (sal_uLong i = rEndOfExtraSectionNode.StartOfSectionNode()->GetIndex() + + 1; i < rEndOfExtraSectionNode.GetIndex(); ++i) + { + SwNode const& rStartNode(*rNodes[i]); + assert(rStartNode.IsStartNode()); + assert(rStartNode.GetRedlineMergeFlag() == SwNode::Merge::None); + SwNode const& rEndNode(*rStartNode.EndOfSectionNode()); + bool bSkip(pSkipped && pSkipped->find(i) != pSkipped->end()); + i = rEndNode.GetIndex(); + for (sal_uLong j = rStartNode.GetIndex() + 1; j < i; ++j) + { + // note: SwStartNode has no way to access the frames, so check + // whether the first content-node inside the section has frames + SwNode const& rNode(*rNodes[j]); + if (rNode.IsSectionNode() && + static_cast<SwSectionNode const&>(rNode).GetSection().IsHiddenFlag()) + { // skip hidden sections - they can be inserted in fly-frames :( + j = rNode.EndOfSectionNode()->GetIndex(); + continue; + } + if (rNode.IsContentNode()) + { + SwContentNode const& rCNode(static_cast<SwContentNode const&>(rNode)); + if (!rCNode.getLayoutFrame(&rLayout)) + { // ignore footnote/fly/header/footer with no layout frame + bSkip = true; // they will be created from scratch later if needed + } + break; + } + } + if (!bSkip) + { + UnHideRedlines(rLayout, rNodes, rEndNode, pSkipped); + } + } +} + +void SwRootFrame::SetHideRedlines(bool const bHideRedlines) +{ + if (bHideRedlines == mbHideRedlines) + { + return; + } + mbHideRedlines = bHideRedlines; + assert(GetCurrShell()->ActionPend()); // tdf#125754 avoid recursive layout + SwDoc & rDoc(*GetFormat()->GetDoc()); + // don't do early return if there are no redlines: + // Show->Hide must init hidden number trees + // Hide->Show may be called after all redlines have been deleted but there + // may still be MergedParas because those aren't deleted yet... +#if 0 + if (!bHideRedlines + && rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + return; + } +#endif + // Hide->Show: clear MergedPara, create frames + // Show->Hide: call CheckParaRedlineMerge, delete frames + // Traverse the document via the nodes-array; traversing via the layout + // wouldn't find the nodes that don't have frames in the ->Show case. + // In-order traversal of each nodes array section should init the flags + // in nodes before they are iterated. + // Actual creation of frames should be done with existing functions + // if possible, particularly InsertCnt_() or its wrapper ::MakeFrames(). + SwNodes /*const*/& rNodes(rDoc.GetNodes()); + // Flys/footnotes: must iterate and find all the ones that already exist + // with frames and have redlines inside them; if any don't have frames at + // all, they will be created (if necessary) from scratch and completely by + // MakeFrames(). + // + // Flys before footnotes: because footnotes may contain flys but not + // vice-versa; alas flys may contain flys, so we skip some of them + // if they have already been created from scratch via their anchor flys. + std::set<sal_uLong> skippedFlys; + UnHideRedlinesExtras(*this, rNodes, rNodes.GetEndOfAutotext(), + // when un-hiding, delay all fly frame creation to AppendAllObjs below + IsHideRedlines() ? &skippedFlys : nullptr); + // Footnotes are created automatically (after invalidation etc.) by + // ConnectFootnote(), but need to be deleted manually. Footnotes do not + // occur in flys or headers/footers. + UnHideRedlinesExtras(*this, rNodes, rNodes.GetEndOfInserts(), nullptr); + UnHideRedlines(*this, rNodes, rNodes.GetEndOfContent(), nullptr); + + if (!IsHideRedlines()) + { // create all previously hidden flys at once: + // * Flys on first node of pre-existing merged frames that are hidden + // (in delete redline), to be added to the existing frame + // * Flys on non-first (hidden/merged) nodes of pre-existing merged + // frames, to be added to the new frame of their node + // * Flys anchored in other flys that are hidden + AppendAllObjs(rDoc.GetSpzFrameFormats(), this); + } + + for (auto const pRedline : rDoc.getIDocumentRedlineAccess().GetRedlineTable()) + { // DELETE are handled by the code above; for other types, need to + // trigger repaint of text frames to add/remove the redline color font + if (pRedline->GetType() != RedlineType::Delete) + { + pRedline->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + } + + SwFootnoteIdxs & rFootnotes(rDoc.GetFootnoteIdxs()); + if (rDoc.GetFootnoteInfo().m_eNum == FTNNUM_CHAPTER) + { + // sadly determining which node is outline node requires hidden layout + rFootnotes.UpdateAllFootnote(); + } + // invalidate all footnotes to reformat their numbers + for (SwTextFootnote *const pFootnote : rFootnotes) + { + SwFormatFootnote const& rFootnote(pFootnote->GetFootnote()); + if (rFootnote.GetNumber() != rFootnote.GetNumberRLHidden() + && rFootnote.GetNumStr().isEmpty()) + { + pFootnote->InvalidateNumberInLayout(); + } + } + // update various fields to re-expand them with the new layout + IDocumentFieldsAccess & rIDFA(rDoc.getIDocumentFieldsAccess()); + auto const pAuthType(rIDFA.GetFieldType( + SwFieldIds::TableOfAuthorities, OUString(), false)); + if (pAuthType) // created on demand... + { // calling DelSequenceArray() should be unnecessary here since the + // sequence doesn't depend on frames + pAuthType->UpdateFields(); + } + rIDFA.GetFieldType(SwFieldIds::RefPageGet, OUString(), false)->UpdateFields(); + rIDFA.GetSysFieldType(SwFieldIds::Chapter)->UpdateFields(); + rIDFA.UpdateExpFields(nullptr, false); + rIDFA.UpdateRefFields(); + + // update SwPostItMgr / notes in the margin + // note: as long as all shells share layout, broadcast to all shells! + rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(nullptr, bHideRedlines + ? SwFormatFieldHintWhich::REMOVED + : SwFormatFieldHintWhich::INSERTED) ); + + +// InvalidateAllContent(SwInvalidateFlags::Size); // ??? TODO what to invalidate? this is the big hammer +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/objectpositioning/anchoredobjectposition.cxx b/sw/source/core/objectpositioning/anchoredobjectposition.cxx new file mode 100644 index 000000000..5ac2514bb --- /dev/null +++ b/sw/source/core/objectpositioning/anchoredobjectposition.cxx @@ -0,0 +1,1132 @@ +/* -*- 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 <anchoredobjectposition.hxx> +#include <environmentofanchoredobject.hxx> +#include <flyfrm.hxx> +#include <flyfrms.hxx> +#include <txtfrm.hxx> +#include <pagefrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <svx/svdobj.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <frmfmt.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmtfollowtextflow.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <ndtxt.hxx> +#include <IDocumentSettingAccess.hxx> +#include <textboxhelper.hxx> +#include <fmtsrnd.hxx> + +using namespace ::com::sun::star; +using namespace objectpositioning; + +SwAnchoredObjectPosition::SwAnchoredObjectPosition( SdrObject& _rDrawObj ) + : mrDrawObj( _rDrawObj ), + mbIsObjFly( false ), + mpAnchoredObj( nullptr ), + mpAnchorFrame( nullptr ), + mpContact( nullptr ), + // #i62875# + mbFollowTextFlow( false ), + mbDoNotCaptureAnchoredObj( false ) +{ +#if OSL_DEBUG_LEVEL > 0 + // assert, if object isn't of expected type + const bool bObjOfExceptedType = + dynamic_cast<const SwVirtFlyDrawObj*>( &mrDrawObj) != nullptr || // object representing fly frame + dynamic_cast<const SwDrawVirtObj*>( &mrDrawObj) != nullptr || // 'virtual' drawing object + ( dynamic_cast<const SdrVirtObj*>( &mrDrawObj) == nullptr && // 'master' drawing object + dynamic_cast<const SwFlyDrawObj*>( &mrDrawObj) == nullptr ); // - indirectly checked + OSL_ENSURE( bObjOfExceptedType, + "SwAnchoredObjectPosition(..) - object of unexpected type!" ); +#endif + + GetInfoAboutObj(); +} + +/** determine information about object + + members <mbIsObjFly>, <mpFrameOfObj>, <mpAnchorFrame>, <mpContact>, + <mbFollowTextFlow> and <mbDoNotCaptureAnchoredObj> are set +*/ +void SwAnchoredObjectPosition::GetInfoAboutObj() +{ + // determine, if object represents a fly frame + { + mbIsObjFly = dynamic_cast<const SwVirtFlyDrawObj*>( &mrDrawObj) != nullptr; + } + + // determine contact object + { + mpContact = GetUserCall( &mrDrawObj ); + assert(mpContact && + "SwAnchoredObjectPosition::GetInfoAboutObj() - missing SwContact-object."); + } + + // determine anchored object, the object belongs to + { + // #i26791# + mpAnchoredObj = mpContact->GetAnchoredObj( &mrDrawObj ); + assert(mpAnchoredObj && + "SwAnchoredObjectPosition::GetInfoAboutObj() - missing anchored object."); + } + + // determine frame, the object is anchored at + { + // #i26791# + mpAnchorFrame = mpAnchoredObj->AnchorFrame(); + OSL_ENSURE( mpAnchorFrame, + "SwAnchoredObjectPosition::GetInfoAboutObj() - missing anchor frame." ); + } + + // determine format the object belongs to + { + // #i28701# + mpFrameFormat = &mpAnchoredObj->GetFrameFormat(); + assert(mpFrameFormat && + "<SwAnchoredObjectPosition::GetInfoAboutObj() - missing frame format."); + } + + // #i62875# - determine attribute value of <Follow-Text-Flow> + { + mbFollowTextFlow = mpFrameFormat->GetFollowTextFlow().GetValue(); + } + + // determine, if anchored object has not to be captured on the page. + // the following conditions must be hold to *not* capture it: + // - corresponding document compatibility flag is set + // - it's a drawing object or it's a non-textbox wrap-though fly frame + // - it doesn't follow the text flow + { + bool bTextBox = SwTextBoxHelper::isTextBox(mpFrameFormat, RES_FLYFRMFMT); + bool bWrapThrough = mpFrameFormat->GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH; + mbDoNotCaptureAnchoredObj = (!mbIsObjFly || (!bTextBox && bWrapThrough)) && !mbFollowTextFlow && + mpFrameFormat->getIDocumentSettingAccess().get(DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE); + } +} + +SwAnchoredObjectPosition::~SwAnchoredObjectPosition() +{} + +bool SwAnchoredObjectPosition::IsAnchoredToChar() const +{ + return false; +} + +const SwFrame* SwAnchoredObjectPosition::ToCharOrientFrame() const +{ + return nullptr; +} + +const SwRect* SwAnchoredObjectPosition::ToCharRect() const +{ + return nullptr; +} + +// #i22341# +SwTwips SwAnchoredObjectPosition::ToCharTopOfLine() const +{ + return 0; +} + +/** helper method to determine top of a frame for the vertical + object positioning + + #i11860# +*/ +SwTwips SwAnchoredObjectPosition::GetTopForObjPos( const SwFrame& _rFrame, + const SwRectFn& _fnRect, + const bool _bVert ) const +{ + SwTwips nTopOfFrameForObjPos = (_rFrame.getFrameArea().*_fnRect->fnGetTop)(); + + if ( _rFrame.IsTextFrame() ) + { + const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(_rFrame); + if ( _bVert ) + { + nTopOfFrameForObjPos -= + rTextFrame.GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + } + else + { + nTopOfFrameForObjPos += + rTextFrame.GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + + const SwFormatSurround& rSurround = mpFrameFormat->GetSurround(); + bool bWrapThrough = rSurround.GetSurround() == css::text::WrapTextMode_THROUGH; + // If the frame format is a TextBox of a draw shape, then use the + // surround of the original shape. + SwTextBoxHelper::getShapeWrapThrough(mpFrameFormat, bWrapThrough); + + // Get the offset between the top of the text frame and the top of + // the first line inside the frame that has more than just fly + // portions. + nTopOfFrameForObjPos += rTextFrame.GetBaseVertOffsetForFly(!bWrapThrough); + } + } + + return nTopOfFrameForObjPos; +} + +void SwAnchoredObjectPosition::GetVertAlignmentValues( + const SwFrame& _rVertOrientFrame, + const SwFrame& _rPageAlignLayFrame, + const sal_Int16 _eRelOrient, + SwTwips& _orAlignAreaHeight, + SwTwips& _orAlignAreaOffset ) const +{ + SwTwips nHeight = 0; + SwTwips nOffset = 0; + SwRectFnSet aRectFnSet(&_rVertOrientFrame); + // #i11860# - top of <_rVertOrientFrame> for object positioning + const SwTwips nVertOrientTop = GetTopForObjPos( _rVertOrientFrame, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + // #i11860# - upper space amount of <_rVertOrientFrame> considered + // for previous frame + const SwTwips nVertOrientUpperSpaceForPrevFrameAndPageGrid = + _rVertOrientFrame.IsTextFrame() + ? static_cast<const SwTextFrame&>(_rVertOrientFrame). + GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid() + : 0; + switch ( _eRelOrient ) + { + case text::RelOrientation::FRAME: + { + // #i11860# - consider upper space of previous frame + nHeight = aRectFnSet.GetHeight(_rVertOrientFrame.getFrameArea()) - + nVertOrientUpperSpaceForPrevFrameAndPageGrid; + nOffset = 0; + } + break; + case text::RelOrientation::PRINT_AREA: + { + nHeight = aRectFnSet.GetHeight(_rVertOrientFrame.getFramePrintArea()); + // #i11860# - consider upper space of previous frame + nOffset = aRectFnSet.GetTopMargin(_rVertOrientFrame) - + nVertOrientUpperSpaceForPrevFrameAndPageGrid; + // if aligned to page in horizontal layout, consider header and + // footer frame height appropriately. + if( _rVertOrientFrame.IsPageFrame() && !aRectFnSet.IsVert() ) + { + const SwFrame* pPrtFrame = + static_cast<const SwPageFrame&>(_rVertOrientFrame).Lower(); + while( pPrtFrame ) + { + if( pPrtFrame->IsHeaderFrame() ) + { + nHeight -= pPrtFrame->getFrameArea().Height(); + nOffset += pPrtFrame->getFrameArea().Height(); + } + else if( pPrtFrame->IsFooterFrame() ) + { + nHeight -= pPrtFrame->getFrameArea().Height(); + } + pPrtFrame = pPrtFrame->GetNext(); + } + } + } + break; + case text::RelOrientation::PAGE_FRAME: + { + nHeight = aRectFnSet.GetHeight(_rPageAlignLayFrame.getFrameArea()); + nOffset = aRectFnSet.YDiff( + aRectFnSet.GetTop(_rPageAlignLayFrame.getFrameArea()), + nVertOrientTop ); + } + break; + case text::RelOrientation::PAGE_PRINT_AREA: + { + nHeight = aRectFnSet.GetHeight(_rPageAlignLayFrame.getFramePrintArea()); + nOffset = aRectFnSet.GetTopMargin(_rPageAlignLayFrame) + + aRectFnSet.YDiff( + aRectFnSet.GetTop(_rPageAlignLayFrame.getFrameArea()), + nVertOrientTop ); + // if aligned to page in horizontal layout, consider header and + // footer frame height appropriately. + if( _rPageAlignLayFrame.IsPageFrame() && !aRectFnSet.IsVert() ) + { + const SwFrame* pPrtFrame = + static_cast<const SwPageFrame&>(_rPageAlignLayFrame).Lower(); + while( pPrtFrame ) + { + if( pPrtFrame->IsHeaderFrame() ) + { + nHeight -= pPrtFrame->getFrameArea().Height(); + nOffset += pPrtFrame->getFrameArea().Height(); + } + else if( pPrtFrame->IsFooterFrame() ) + { + nHeight -= pPrtFrame->getFrameArea().Height(); + } + pPrtFrame = pPrtFrame->GetNext(); + } + } + } + break; + // #i22341# - vertical alignment at top of line + case text::RelOrientation::TEXT_LINE: + { + if ( IsAnchoredToChar() ) + { + nHeight = 0; + nOffset = aRectFnSet.YDiff( ToCharTopOfLine(), nVertOrientTop ); + } + else + { + OSL_FAIL( "<SwAnchoredObjectPosition::GetVertAlignmentValues(..)> - invalid relative alignment" ); + } + } + break; + case text::RelOrientation::CHAR: + { + if ( IsAnchoredToChar() ) + { + nHeight = aRectFnSet.GetHeight(*ToCharRect()); + nOffset = aRectFnSet.YDiff( aRectFnSet.GetTop(*ToCharRect()), + nVertOrientTop ); + } + else + { + OSL_FAIL( "<SwAnchoredObjectPosition::GetVertAlignmentValues(..)> - invalid relative alignment" ); + } + } + break; + // no break here, because text::RelOrientation::CHAR is invalid, if !mbAnchorToChar + default: + { + OSL_FAIL( "<SwAnchoredObjectPosition::GetVertAlignmentValues(..)> - invalid relative alignment" ); + } + } + + _orAlignAreaHeight = nHeight; + _orAlignAreaOffset = nOffset; +} + +// #i26791# - add output parameter <_roVertOffsetToFrameAnchorPos> +SwTwips SwAnchoredObjectPosition::GetVertRelPos( + const SwFrame& _rVertOrientFrame, + const SwFrame& _rPageAlignLayFrame, + const sal_Int16 _eVertOrient, + const sal_Int16 _eRelOrient, + const SwTwips _nVertPos, + const SvxLRSpaceItem& _rLRSpacing, + const SvxULSpaceItem& _rULSpacing, + SwTwips& _roVertOffsetToFrameAnchorPos ) const +{ + SwTwips nRelPosY = 0; + SwRectFnSet aRectFnSet(&_rVertOrientFrame); + + SwTwips nAlignAreaHeight; + SwTwips nAlignAreaOffset; + GetVertAlignmentValues( _rVertOrientFrame, _rPageAlignLayFrame, + _eRelOrient, nAlignAreaHeight, nAlignAreaOffset ); + + nRelPosY = nAlignAreaOffset; + const SwRect aObjBoundRect( GetAnchoredObj().GetObjRect() ); + const SwTwips nObjHeight = aRectFnSet.GetHeight(aObjBoundRect); + + switch ( _eVertOrient ) + { + case text::VertOrientation::NONE: + { + // 'manual' vertical position + nRelPosY += _nVertPos; + } + break; + case text::VertOrientation::TOP: + { + nRelPosY += aRectFnSet.IsVert() + ? ( aRectFnSet.IsVertL2R() + ? _rLRSpacing.GetLeft() + : _rLRSpacing.GetRight() ) + : _rULSpacing.GetUpper(); + } + break; + case text::VertOrientation::CENTER: + { + nRelPosY += (nAlignAreaHeight / 2) - (nObjHeight / 2); + } + break; + case text::VertOrientation::BOTTOM: + { + nRelPosY += nAlignAreaHeight - + ( nObjHeight + ( aRectFnSet.IsVert() + ? ( aRectFnSet.IsVertL2R() + ? _rLRSpacing.GetRight() + : _rLRSpacing.GetLeft() ) + : _rULSpacing.GetLower() ) ); + } + break; + default: + { + OSL_FAIL( "<SwAnchoredObjectPosition::GetVertRelPos(..) - invalid vertical positioning" ); + } + } + + // #i26791# + _roVertOffsetToFrameAnchorPos = nAlignAreaOffset; + + return nRelPosY; +} + +/** adjust calculated vertical in order to keep object inside + 'page' alignment layout frame. + + #i28701# - parameter <_nTopOfAnch> and <_bVert> added + #i31805# - add parameter <_bCheckBottom> + #i26945# - add parameter <_bFollowTextFlow> + #i62875# - method now private and renamed. + OD 2009-09-01 #mongolianlayout# - add parameter <bVertL2R> +*/ +SwTwips SwAnchoredObjectPosition::ImplAdjustVertRelPos( const SwTwips nTopOfAnch, + const bool bVert, + const bool bVertL2R, + const SwFrame& rPageAlignLayFrame, + const SwTwips nProposedRelPosY, + const bool bFollowTextFlow, + const bool bCheckBottom ) const +{ + SwTwips nAdjustedRelPosY = nProposedRelPosY; + + const Size aObjSize( GetAnchoredObj().GetObjRect().SSize() ); + + // determine the area of 'page' alignment frame, to which the vertical + // position is restricted. + // #i28701# - Extend restricted area for the vertical + // position to area of the page frame, if wrapping style influence is + // considered on object positioning. Needed to avoid layout loops in the + // object positioning algorithm considering the wrapping style influence + // caused by objects, which follow the text flow and thus are restricted + // to its environment (e.g. page header/footer). + SwRect aPgAlignArea; + { + // #i26945# - no extension of restricted area, if + // object's attribute follow text flow is set and its inside a table + if ( GetFrameFormat().getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) && + ( !bFollowTextFlow || + !GetAnchoredObj().GetAnchorFrame()->IsInTab() ) ) + { + aPgAlignArea = rPageAlignLayFrame.FindPageFrame()->getFrameArea(); + } + else + { + aPgAlignArea = rPageAlignLayFrame.getFrameArea(); + } + } + + if ( bVert ) + { + // #i31805# - consider value of <_bCheckBottom> + if ( !bVertL2R ) + { + if ( bCheckBottom && + nTopOfAnch - nAdjustedRelPosY - aObjSize.Width() < + aPgAlignArea.Left() ) + { + nAdjustedRelPosY = aPgAlignArea.Left() + + nTopOfAnch - + aObjSize.Width(); + } + // #i32964# - correction + if ( nTopOfAnch - nAdjustedRelPosY > aPgAlignArea.Right() ) + { + nAdjustedRelPosY = nTopOfAnch - aPgAlignArea.Right(); + } + } + else + { + // tdf#112443 if position is completely off-page + // return the proposed position and do not adjust it... + // tdf#120839 .. unless anchored to char (anchor can jump on other page) + bool bDisablePositioning = mpFrameFormat->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + + if ( bDisablePositioning && !IsAnchoredToChar() && nTopOfAnch + nAdjustedRelPosY > aPgAlignArea.Right() ) + { + return nProposedRelPosY; + } + + if ( bCheckBottom && + nTopOfAnch + nAdjustedRelPosY + aObjSize.Width() > + aPgAlignArea.Right() ) + { + nAdjustedRelPosY = aPgAlignArea.Right() - + nTopOfAnch - + aObjSize.Width(); + } + if ( nTopOfAnch + nAdjustedRelPosY < aPgAlignArea.Left() ) + { + nAdjustedRelPosY = aPgAlignArea.Left() - nTopOfAnch; + } + } + } + else + { + // tdf#112443 if position is completely off-page + // return the proposed position and do not adjust it... + const bool bDisablePositioning = mpFrameFormat->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + + // tdf#123002 disable the positioning in header and footer only + // we should limit this since anchors of body frames may appear on other pages + const bool bIsFooterOrHeader = GetAnchorFrame().GetUpper() + && (GetAnchorFrame().GetUpper()->IsFooterFrame() || GetAnchorFrame().GetUpper()->IsHeaderFrame() ); + + if ( bDisablePositioning && bIsFooterOrHeader && nTopOfAnch + nAdjustedRelPosY > aPgAlignArea.Bottom() ) + { + return nProposedRelPosY; + } + + // #i31805# - consider value of <bCheckBottom> + if ( bCheckBottom && + nTopOfAnch + nAdjustedRelPosY + aObjSize.Height() > + aPgAlignArea.Top() + aPgAlignArea.Height() ) + { + nAdjustedRelPosY = aPgAlignArea.Top() + aPgAlignArea.Height() - + nTopOfAnch - + aObjSize.Height(); + } + if ( nTopOfAnch + nAdjustedRelPosY < aPgAlignArea.Top() ) + { + nAdjustedRelPosY = aPgAlignArea.Top() - nTopOfAnch; + } + + // tdf#91260 - allow textboxes extending beyond the page bottom + // tdf#101627 - the patch a4dee94afed9ade6ac50237c8d99a6e49d3bebc1 + // for tdf#91260 causes problems if the textbox + // is anchored in the footer, so exclude this case + if ( !( GetAnchorFrame().GetUpper() && GetAnchorFrame().GetUpper()->IsFooterFrame() ) + && nAdjustedRelPosY < nProposedRelPosY ) + { + const SwFrameFormat* pFormat = &(GetFrameFormat()); + if ( GetObject().IsTextBox() ) + { + // shrink textboxes to extend beyond the page bottom + SwFrameFormat* pFrameFormat = ::FindFrameFormat(&GetObject()); + SwFormatFrameSize aSize(pFormat->GetFrameSize()); + SwTwips nShrinked = aSize.GetHeight() - (nProposedRelPosY - nAdjustedRelPosY); + if (nShrinked >= 0) { + aSize.SetHeight( nShrinked ); + pFrameFormat->SetFormatAttr(aSize); + } + nAdjustedRelPosY = nProposedRelPosY; + } else if ( SwTextBoxHelper::isTextBox(pFormat, RES_DRAWFRMFMT) ) + // when the shape has a textbox, use only the proposed vertical position + nAdjustedRelPosY = nProposedRelPosY; + } + } + return nAdjustedRelPosY; +} + +/** adjust calculated horizontal in order to keep object inside + 'page' alignment layout frame. + + #i62875# - method now private and renamed. +*/ +SwTwips SwAnchoredObjectPosition::ImplAdjustHoriRelPos( + const SwFrame& _rPageAlignLayFrame, + const SwTwips _nProposedRelPosX ) const +{ + SwTwips nAdjustedRelPosX = _nProposedRelPosX; + + const SwFrame& rAnchorFrame = GetAnchorFrame(); + const bool bVert = rAnchorFrame.IsVertical(); + + const Size aObjSize( GetAnchoredObj().GetObjRect().SSize() ); + + if( bVert ) + { + if ( rAnchorFrame.getFrameArea().Top() + nAdjustedRelPosX + aObjSize.Height() > + _rPageAlignLayFrame.getFrameArea().Bottom() ) + { + nAdjustedRelPosX = _rPageAlignLayFrame.getFrameArea().Bottom() - + rAnchorFrame.getFrameArea().Top() - + aObjSize.Height(); + } + if ( rAnchorFrame.getFrameArea().Top() + nAdjustedRelPosX < + _rPageAlignLayFrame.getFrameArea().Top() ) + { + nAdjustedRelPosX = _rPageAlignLayFrame.getFrameArea().Top() - + rAnchorFrame.getFrameArea().Top(); + } + } + else + { + if ( rAnchorFrame.getFrameArea().Left() + nAdjustedRelPosX + aObjSize.Width() > + _rPageAlignLayFrame.getFrameArea().Right() ) + { + nAdjustedRelPosX = _rPageAlignLayFrame.getFrameArea().Right() - + rAnchorFrame.getFrameArea().Left() - + aObjSize.Width(); + } + if ( rAnchorFrame.getFrameArea().Left() + nAdjustedRelPosX < + _rPageAlignLayFrame.getFrameArea().Left() ) + { + nAdjustedRelPosX = _rPageAlignLayFrame.getFrameArea().Left() - + rAnchorFrame.getFrameArea().Left(); + } + } + + return nAdjustedRelPosX; +} + +/** determine alignment value for horizontal position of object */ +void SwAnchoredObjectPosition::GetHoriAlignmentValues( const SwFrame& _rHoriOrientFrame, + const SwFrame& _rPageAlignLayFrame, + const sal_Int16 _eRelOrient, + const bool _bObjWrapThrough, + SwTwips& _orAlignAreaWidth, + SwTwips& _orAlignAreaOffset, + bool& _obAlignedRelToPage ) const +{ + SwTwips nWidth = 0; + SwTwips nOffset = 0; + SwRectFnSet aRectFnSet(&_rHoriOrientFrame); + switch ( _eRelOrient ) + { + case text::RelOrientation::PRINT_AREA: + { + nWidth = aRectFnSet.GetWidth(_rHoriOrientFrame.getFramePrintArea()); + nOffset = aRectFnSet.GetLeftMargin(_rHoriOrientFrame); + if ( _rHoriOrientFrame.IsTextFrame() ) + { + // consider movement of text frame left + nOffset += static_cast<const SwTextFrame&>(_rHoriOrientFrame).GetBaseOffsetForFly( !_bObjWrapThrough ); + } + else if ( _rHoriOrientFrame.IsPageFrame() && aRectFnSet.IsVert() ) + { + // for to-page anchored objects, consider header/footer frame + // in vertical layout + const SwFrame* pPrtFrame = + static_cast<const SwPageFrame&>(_rHoriOrientFrame).Lower(); + while( pPrtFrame ) + { + if( pPrtFrame->IsHeaderFrame() ) + { + nWidth -= pPrtFrame->getFrameArea().Height(); + nOffset += pPrtFrame->getFrameArea().Height(); + } + else if( pPrtFrame->IsFooterFrame() ) + { + nWidth -= pPrtFrame->getFrameArea().Height(); + } + pPrtFrame = pPrtFrame->GetNext(); + } + } + break; + } + case text::RelOrientation::PAGE_LEFT: + { + // align at left border of page frame/fly frame/cell frame + nWidth = aRectFnSet.GetLeftMargin(_rPageAlignLayFrame); + nOffset = aRectFnSet.XDiff( + aRectFnSet.GetLeft(_rPageAlignLayFrame.getFrameArea()), + aRectFnSet.GetLeft(_rHoriOrientFrame.getFrameArea()) ); + _obAlignedRelToPage = true; + } + break; + case text::RelOrientation::PAGE_RIGHT: + { + // align at right border of page frame/fly frame/cell frame + nWidth = aRectFnSet.GetRightMargin(_rPageAlignLayFrame); + nOffset = aRectFnSet.XDiff( + aRectFnSet.GetPrtRight(_rPageAlignLayFrame), + aRectFnSet.GetLeft(_rHoriOrientFrame.getFrameArea()) ); + _obAlignedRelToPage = true; + } + break; + case text::RelOrientation::FRAME_LEFT: + { + // align at left border of anchor frame + nWidth = aRectFnSet.GetLeftMargin(_rHoriOrientFrame); + nOffset = 0; + } + break; + case text::RelOrientation::FRAME_RIGHT: + { + // align at right border of anchor frame + // Unify and simplify + nWidth = aRectFnSet.GetRightMargin(_rHoriOrientFrame); + nOffset = aRectFnSet.GetRight(_rHoriOrientFrame.getFramePrintArea()); + } + break; + case text::RelOrientation::CHAR: + { + // alignment relative to character - assure, that corresponding + // character rectangle is set. + if ( IsAnchoredToChar() ) + { + nWidth = 0; + nOffset = aRectFnSet.XDiff( + aRectFnSet.GetLeft(*ToCharRect()), + aRectFnSet.GetLeft(ToCharOrientFrame()->getFrameArea()) ); + break; + } + [[fallthrough]]; + } + case text::RelOrientation::PAGE_PRINT_AREA: + { + nWidth = aRectFnSet.GetWidth(_rPageAlignLayFrame.getFramePrintArea()); + nOffset = aRectFnSet.XDiff( + aRectFnSet.GetPrtLeft(_rPageAlignLayFrame), + aRectFnSet.GetLeft(_rHoriOrientFrame.getFrameArea()) ); + if ( _rHoriOrientFrame.IsPageFrame() && aRectFnSet.IsVert() ) + { + // for to-page anchored objects, consider header/footer frame + // in vertical layout + const SwFrame* pPrtFrame = + static_cast<const SwPageFrame&>(_rHoriOrientFrame).Lower(); + while( pPrtFrame ) + { + if( pPrtFrame->IsHeaderFrame() ) + { + nWidth -= pPrtFrame->getFrameArea().Height(); + nOffset += pPrtFrame->getFrameArea().Height(); + } + else if( pPrtFrame->IsFooterFrame() ) + { + nWidth -= pPrtFrame->getFrameArea().Height(); + } + pPrtFrame = pPrtFrame->GetNext(); + } + } + _obAlignedRelToPage = true; + break; + } + case text::RelOrientation::PAGE_FRAME: + { + nWidth = aRectFnSet.GetWidth(_rPageAlignLayFrame.getFrameArea()); + nOffset = aRectFnSet.XDiff( + aRectFnSet.GetLeft(_rPageAlignLayFrame.getFrameArea()), + aRectFnSet.GetLeft(_rHoriOrientFrame.getFrameArea()) ); + _obAlignedRelToPage = true; + break; + } + default: + { + nWidth = aRectFnSet.GetWidth(_rHoriOrientFrame.getFrameArea()); + + bool bWrapThrough = _bObjWrapThrough; + // If the frame format is a TextBox of a draw shape, then use the + // surround of the original shape. + SwTextBoxHelper::getShapeWrapThrough(mpFrameFormat, bWrapThrough); + + bool bIgnoreFlysAnchoredAtFrame = !bWrapThrough; + nOffset = _rHoriOrientFrame.IsTextFrame() ? + static_cast<const SwTextFrame&>(_rHoriOrientFrame).GetBaseOffsetForFly( bIgnoreFlysAnchoredAtFrame ) : + 0; + break; + } + } + + _orAlignAreaWidth = nWidth; + _orAlignAreaOffset = nOffset; +} + +/** toggle given horizontal orientation and relative alignment */ +void SwAnchoredObjectPosition::ToggleHoriOrientAndAlign( + const bool _bToggleLeftRight, + sal_Int16& _ioeHoriOrient, + sal_Int16& _iopeRelOrient + ) +{ + if( _bToggleLeftRight ) + { + // toggle orientation + switch ( _ioeHoriOrient ) + { + case text::HoriOrientation::RIGHT : + { + _ioeHoriOrient = text::HoriOrientation::LEFT; + } + break; + case text::HoriOrientation::LEFT : + { + _ioeHoriOrient = text::HoriOrientation::RIGHT; + } + break; + default: + break; + } + + // toggle relative alignment + switch ( _iopeRelOrient ) + { + case text::RelOrientation::PAGE_RIGHT : + { + _iopeRelOrient = text::RelOrientation::PAGE_LEFT; + } + break; + case text::RelOrientation::PAGE_LEFT : + { + _iopeRelOrient = text::RelOrientation::PAGE_RIGHT; + } + break; + case text::RelOrientation::FRAME_RIGHT : + { + _iopeRelOrient = text::RelOrientation::FRAME_LEFT; + } + break; + case text::RelOrientation::FRAME_LEFT : + { + _iopeRelOrient = text::RelOrientation::FRAME_RIGHT; + } + break; + default: + break; + } + } +} + +/** calculate relative horizontal position */ +SwTwips SwAnchoredObjectPosition::CalcRelPosX( + const SwFrame& _rHoriOrientFrame, + const SwEnvironmentOfAnchoredObject& _rEnvOfObj, + const SwFormatHoriOrient& _rHoriOrient, + const SvxLRSpaceItem& _rLRSpacing, + const SvxULSpaceItem& _rULSpacing, + const bool _bObjWrapThrough, + const SwTwips _nRelPosY, + SwTwips& _roHoriOffsetToFrameAnchorPos + ) const +{ + // determine 'page' alignment layout frame + const SwFrame& rPageAlignLayFrame = + _rEnvOfObj.GetHoriEnvironmentLayoutFrame( _rHoriOrientFrame ); + + const bool bEvenPage = !rPageAlignLayFrame.OnRightPage(); + const bool bToggle = _rHoriOrient.IsPosToggle() && bEvenPage; + + // determine orientation and relative alignment + sal_Int16 eHoriOrient = _rHoriOrient.GetHoriOrient(); + sal_Int16 eRelOrient = _rHoriOrient.GetRelationOrient(); + // toggle orientation and relative alignment + ToggleHoriOrientAndAlign( bToggle, eHoriOrient, eRelOrient ); + + // determine alignment parameter + // <nWidth>: 'width' of alignment area + // <nOffset>: offset of alignment area, relative to 'left' of anchor frame + SwTwips nWidth = 0; + SwTwips nOffset = 0; + bool bAlignedRelToPage = false; + GetHoriAlignmentValues( _rHoriOrientFrame, rPageAlignLayFrame, + eRelOrient, _bObjWrapThrough, + nWidth, nOffset, bAlignedRelToPage ); + + const SwFrame& rAnchorFrame = GetAnchorFrame(); + SwRectFnSet aRectFnSet(&_rHoriOrientFrame); + SwTwips nObjWidth = aRectFnSet.GetWidth(GetAnchoredObj().GetObjRect()); + SwTwips nRelPosX = nOffset; + if ( _rHoriOrient.GetHoriOrient() == text::HoriOrientation::NONE ) + { + // 'manual' horizontal position + const bool bR2L = rAnchorFrame.IsRightToLeft(); + if( IsAnchoredToChar() && text::RelOrientation::CHAR == eRelOrient ) + { + if( bR2L ) + nRelPosX -= _rHoriOrient.GetPos(); + else + nRelPosX += _rHoriOrient.GetPos(); + } + else if ( bToggle || ( !_rHoriOrient.IsPosToggle() && bR2L ) ) + { + // Correction: consider <nOffset> also for + // toggling from left to right. + nRelPosX += nWidth - nObjWidth - _rHoriOrient.GetPos(); + } + else + { + nRelPosX += _rHoriOrient.GetPos(); + } + } + else if ( text::HoriOrientation::CENTER == eHoriOrient ) + nRelPosX += (nWidth / 2) - (nObjWidth / 2); + else if ( text::HoriOrientation::RIGHT == eHoriOrient ) + nRelPosX += nWidth - + ( nObjWidth + + ( aRectFnSet.IsVert() ? _rULSpacing.GetLower() : _rLRSpacing.GetRight() ) ); + else + nRelPosX += aRectFnSet.IsVert() ? _rULSpacing.GetUpper() : _rLRSpacing.GetLeft(); + + // adjust relative position by distance between anchor frame and + // the frame, the object is oriented at. + if ( &rAnchorFrame != &_rHoriOrientFrame ) + { + SwTwips nLeftOrient = aRectFnSet.GetLeft(_rHoriOrientFrame.getFrameArea()); + SwTwips nLeftAnchor = aRectFnSet.GetLeft(rAnchorFrame.getFrameArea()); + nRelPosX += aRectFnSet.XDiff( nLeftOrient, nLeftAnchor ); + } + + // adjust calculated relative horizontal position, in order to + // keep object inside 'page' alignment layout frame + const SwFrame& rEnvironmentLayFrame = + _rEnvOfObj.GetHoriEnvironmentLayoutFrame( _rHoriOrientFrame ); + bool bFollowTextFlow = GetFrameFormat().GetFollowTextFlow().GetValue(); + bool bWrapThrough = GetFrameFormat().GetSurround().GetSurround() != text::WrapTextMode_THROUGH; + // Don't try to keep wrap-though objects inside the cell, even if they are following text flow. + if (!rEnvironmentLayFrame.IsInTab() || !bFollowTextFlow || bWrapThrough) + { + nRelPosX = AdjustHoriRelPos( rEnvironmentLayFrame, nRelPosX ); + } + + // if object is a Writer fly frame and it's anchored to a content and + // it is horizontal positioned left or right, but not relative to character, + // it has to be drawn aside another object, which have the same horizontal + // position and lay below it. + if ( dynamic_cast<const SwFlyFrame*>( &GetAnchoredObj() ) != nullptr && + ( mpContact->ObjAnchoredAtPara() || mpContact->ObjAnchoredAtChar() ) && + ( eHoriOrient == text::HoriOrientation::LEFT || eHoriOrient == text::HoriOrientation::RIGHT ) && + eRelOrient != text::RelOrientation::CHAR ) + { + nRelPosX = AdjustHoriRelPosForDrawAside( _rHoriOrientFrame, + nRelPosX, _nRelPosY, + eHoriOrient, eRelOrient, + _rLRSpacing, _rULSpacing, + bEvenPage ); + } + + // #i26791# + _roHoriOffsetToFrameAnchorPos = nOffset; + + return nRelPosX; +} + +// method incl. helper methods for adjusting proposed horizontal position, +// if object has to draw aside another object. +/** adjust calculated horizontal position in order to draw object + aside other objects with same positioning +*/ +SwTwips SwAnchoredObjectPosition::AdjustHoriRelPosForDrawAside( + const SwFrame& _rHoriOrientFrame, + const SwTwips _nProposedRelPosX, + const SwTwips _nRelPosY, + const sal_Int16 _eHoriOrient, + const sal_Int16 _eRelOrient, + const SvxLRSpaceItem& _rLRSpacing, + const SvxULSpaceItem& _rULSpacing, + const bool _bEvenPage + ) const +{ + // #i26791# + if ( dynamic_cast<const SwTextFrame*>( &GetAnchorFrame() ) == nullptr || + dynamic_cast<const SwFlyAtContentFrame*>( &GetAnchoredObj() ) == nullptr ) + { + OSL_FAIL( "<SwAnchoredObjectPosition::AdjustHoriRelPosForDrawAside(..) - usage for wrong anchor type" ); + return _nProposedRelPosX; + } + + const SwTextFrame& rAnchorTextFrame = static_cast<const SwTextFrame&>(GetAnchorFrame()); + // #i26791# + const SwFlyAtContentFrame& rFlyAtContentFrame = + static_cast<const SwFlyAtContentFrame&>(GetAnchoredObj()); + const SwRect aObjBoundRect( GetAnchoredObj().GetObjRect() ); + SwRectFnSet aRectFnSet(&_rHoriOrientFrame); + + SwTwips nAdjustedRelPosX = _nProposedRelPosX; + + // determine proposed object bound rectangle + Point aTmpPos = aRectFnSet.GetPos(rAnchorTextFrame.getFrameArea()); + if( aRectFnSet.IsVert() ) + { + aTmpPos.AdjustX( -(_nRelPosY + aObjBoundRect.Width()) ); + aTmpPos.AdjustY(nAdjustedRelPosX ); + } + else + { + aTmpPos.AdjustX(nAdjustedRelPosX ); + aTmpPos.AdjustY(_nRelPosY ); + } + SwRect aTmpObjRect( aTmpPos, aObjBoundRect.SSize() ); + + const sal_uInt32 nObjOrdNum = GetObject().GetOrdNum(); + const SwPageFrame* pObjPage = rFlyAtContentFrame.FindPageFrame(); + const SwFrame* pObjContext = ::FindContext( &rAnchorTextFrame, SwFrameType::Column ); + sal_uLong nObjIndex = rAnchorTextFrame.GetTextNodeFirst()->GetIndex(); + SwOrderIter aIter( pObjPage ); + const SwFlyFrame* pFly = static_cast<const SwVirtFlyDrawObj*>(aIter.Bottom())->GetFlyFrame(); + while ( pFly && nObjOrdNum > pFly->GetVirtDrawObj()->GetOrdNumDirect() ) + { + if ( DrawAsideFly( pFly, aTmpObjRect, pObjContext, nObjIndex, + _bEvenPage, _eHoriOrient, _eRelOrient ) ) + { + if( aRectFnSet.IsVert() ) + { + const SvxULSpaceItem& rOtherUL = pFly->GetFormat()->GetULSpace(); + const SwTwips nOtherTop = pFly->getFrameArea().Top() - rOtherUL.GetUpper(); + const SwTwips nOtherBot = pFly->getFrameArea().Bottom() + rOtherUL.GetLower(); + if ( nOtherTop <= aTmpObjRect.Bottom() + _rULSpacing.GetLower() && + nOtherBot >= aTmpObjRect.Top() - _rULSpacing.GetUpper() ) + { + if ( _eHoriOrient == text::HoriOrientation::LEFT ) + { + SwTwips nTmp = nOtherBot + 1 + _rULSpacing.GetUpper() - + rAnchorTextFrame.getFrameArea().Top(); + if ( nTmp > nAdjustedRelPosX && + rAnchorTextFrame.getFrameArea().Top() + nTmp + + aObjBoundRect.Height() + _rULSpacing.GetLower() + <= pObjPage->getFrameArea().Height() + pObjPage->getFrameArea().Top() ) + { + nAdjustedRelPosX = nTmp; + } + } + else if ( _eHoriOrient == text::HoriOrientation::RIGHT ) + { + SwTwips nTmp = nOtherTop - 1 - _rULSpacing.GetLower() - + aObjBoundRect.Height() - + rAnchorTextFrame.getFrameArea().Top(); + if ( nTmp < nAdjustedRelPosX && + rAnchorTextFrame.getFrameArea().Top() + nTmp - _rULSpacing.GetUpper() + >= pObjPage->getFrameArea().Top() ) + { + nAdjustedRelPosX = nTmp; + } + } + aTmpObjRect.Pos().setY( rAnchorTextFrame.getFrameArea().Top() + + nAdjustedRelPosX ); + } + } + else + { + const SvxLRSpaceItem& rOtherLR = pFly->GetFormat()->GetLRSpace(); + const SwTwips nOtherLeft = pFly->getFrameArea().Left() - rOtherLR.GetLeft(); + const SwTwips nOtherRight = pFly->getFrameArea().Right() + rOtherLR.GetRight(); + if( nOtherLeft <= aTmpObjRect.Right() + _rLRSpacing.GetRight() && + nOtherRight >= aTmpObjRect.Left() - _rLRSpacing.GetLeft() ) + { + if ( _eHoriOrient == text::HoriOrientation::LEFT ) + { + SwTwips nTmp = nOtherRight + 1 + _rLRSpacing.GetLeft() - + rAnchorTextFrame.getFrameArea().Left(); + if ( nTmp > nAdjustedRelPosX && + rAnchorTextFrame.getFrameArea().Left() + nTmp + + aObjBoundRect.Width() + _rLRSpacing.GetRight() + <= pObjPage->getFrameArea().Width() + pObjPage->getFrameArea().Left() ) + { + nAdjustedRelPosX = nTmp; + } + } + else if ( _eHoriOrient == text::HoriOrientation::RIGHT ) + { + SwTwips nTmp = nOtherLeft - 1 - _rLRSpacing.GetRight() - + aObjBoundRect.Width() - + rAnchorTextFrame.getFrameArea().Left(); + if ( nTmp < nAdjustedRelPosX && + rAnchorTextFrame.getFrameArea().Left() + nTmp - _rLRSpacing.GetLeft() + >= pObjPage->getFrameArea().Left() ) + { + nAdjustedRelPosX = nTmp; + } + } + aTmpObjRect.Pos().setX( rAnchorTextFrame.getFrameArea().Left() + + nAdjustedRelPosX ); + } + } // end of <if (bVert)> + } // end of <if DrawAsideFly(..)> + + pFly = static_cast<const SwVirtFlyDrawObj*>(aIter.Next())->GetFlyFrame(); + } // end of <loop on fly frames + + return nAdjustedRelPosX; +} + +/** determine, if object has to draw aside given fly frame + + method used by <AdjustHoriRelPosForDrawAside(..)> +*/ +bool SwAnchoredObjectPosition::DrawAsideFly( const SwFlyFrame* _pFly, + const SwRect& _rObjRect, + const SwFrame* _pObjContext, + const sal_uLong _nObjIndex, + const bool _bEvenPage, + const sal_Int16 _eHoriOrient, + const sal_Int16 _eRelOrient + ) const +{ + bool bRetVal = false; + + SwRectFnSet aRectFnSet(&GetAnchorFrame()); + + if ( _pFly->IsFlyAtContentFrame() && + aRectFnSet.BottomDist( _pFly->getFrameArea(), aRectFnSet.GetTop(_rObjRect) ) < 0 && + aRectFnSet.BottomDist( _rObjRect, aRectFnSet.GetTop(_pFly->getFrameArea()) ) < 0 && + ::FindContext( _pFly->GetAnchorFrame(), SwFrameType::Column ) == _pObjContext ) + { + sal_uLong nOtherIndex = + static_cast<const SwTextFrame*>(_pFly->GetAnchorFrame())->GetTextNodeFirst()->GetIndex(); + if (sw::FrameContainsNode(static_cast<SwTextFrame const&>(*_pFly->GetAnchorFrame()), _nObjIndex) + || nOtherIndex < _nObjIndex) + { + const SwFormatHoriOrient& rHori = _pFly->GetFormat()->GetHoriOrient(); + sal_Int16 eOtherRelOrient = rHori.GetRelationOrient(); + if( text::RelOrientation::CHAR != eOtherRelOrient ) + { + sal_Int16 eOtherHoriOrient = rHori.GetHoriOrient(); + ToggleHoriOrientAndAlign( _bEvenPage && rHori.IsPosToggle(), + eOtherHoriOrient, + eOtherRelOrient ); + if ( eOtherHoriOrient == _eHoriOrient && + Minor_( _eRelOrient, eOtherRelOrient, text::HoriOrientation::LEFT == _eHoriOrient ) ) + { + bRetVal = true; + } + } + } + } + + return bRetVal; +} + +/** determine, if object has to draw aside another object + + the different alignments of the objects determines, if one has + to draw aside another one. Thus, the given alignment are checked + against each other, which one has to be drawn aside the other one. + depending on parameter _bLeft check is done for left or right + positioning. + method used by <DrawAsideFly(..)> +*/ +bool SwAnchoredObjectPosition::Minor_( sal_Int16 _eRelOrient1, + sal_Int16 _eRelOrient2, + bool _bLeft ) +{ + bool bRetVal; + + // draw aside order for left horizontal position + //! one array entry for each value in text::RelOrientation + static sal_uInt16 const aLeft[ 10 ] = + { 5, 6, 0, 1, 8, 4, 7, 2, 3, 9 }; + // draw aside order for right horizontal position + //! one array entry for each value in text::RelOrientation + static sal_uInt16 const aRight[ 10 ] = + { 5, 6, 0, 8, 1, 7, 4, 2, 3, 9 }; + + // decide depending on given order, which frame has to draw aside another frame + if( _bLeft ) + bRetVal = aLeft[ _eRelOrient1 ] >= aLeft[ _eRelOrient2 ]; + else + bRetVal = aRight[ _eRelOrient1 ] >= aRight[ _eRelOrient2 ]; + + return bRetVal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/objectpositioning/ascharanchoredobjectposition.cxx b/sw/source/core/objectpositioning/ascharanchoredobjectposition.cxx new file mode 100644 index 000000000..ffe9a3682 --- /dev/null +++ b/sw/source/core/objectpositioning/ascharanchoredobjectposition.cxx @@ -0,0 +1,395 @@ +/* -*- 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 <anchoreddrawobject.hxx> +#include <ascharanchoredobjectposition.hxx> +#include <frame.hxx> +#include <txtfrm.hxx> +#include <flyfrms.hxx> +#include <svx/svdobj.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <fmtornt.hxx> + + +using namespace ::com::sun::star; +using namespace objectpositioning; + +/** constructor */ +SwAsCharAnchoredObjectPosition::SwAsCharAnchoredObjectPosition( + SdrObject& _rDrawObj, + const Point& _rProposedAnchorPos, + const AsCharFlags _nFlags, + const SwTwips _nLineAscent, + const SwTwips _nLineDescent, + const SwTwips _nLineAscentInclObjs, + const SwTwips _nLineDescentInclObjs ) + : SwAnchoredObjectPosition( _rDrawObj ), + mrProposedAnchorPos( _rProposedAnchorPos ), + mnFlags( _nFlags ), + mnLineAscent( _nLineAscent ), + mnLineDescent( _nLineDescent ), + mnLineAscentInclObjs( _nLineAscentInclObjs ), + mnLineDescentInclObjs( _nLineDescentInclObjs ), + maAnchorPos ( Point() ), + mnRelPos ( 0 ), + maObjBoundRect ( SwRect() ), + mnLineAlignment ( sw::LineAlign::NONE ) +{} + +/** destructor */ +SwAsCharAnchoredObjectPosition::~SwAsCharAnchoredObjectPosition() +{} + +/** method to cast <SwAnchoredObjectPosition::GetAnchorFrame()> to needed type */ +const SwTextFrame& SwAsCharAnchoredObjectPosition::GetAnchorTextFrame() const +{ + OSL_ENSURE( dynamic_cast<const SwTextFrame*>( &GetAnchorFrame() ) != nullptr, + "SwAsCharAnchoredObjectPosition::GetAnchorTextFrame() - wrong anchor frame type" ); + + return static_cast<const SwTextFrame&>(GetAnchorFrame()); +} + +/** calculate position for object + + OD 30.07.2003 #110978# + members <maAnchorPos>, <mnRelPos>, <maObjBoundRect> and + <mnLineAlignment> are calculated. + calculated position is set at the given object. +*/ +void SwAsCharAnchoredObjectPosition::CalcPosition() +{ + const SwTextFrame& rAnchorFrame = GetAnchorTextFrame(); + // swap anchor frame, if swapped. Note: destructor takes care of the 'undo' + SwFrameSwapper aFrameSwapper( &rAnchorFrame, false ); + + SwRectFnSet aRectFnSet(&rAnchorFrame); + + Point aAnchorPos( mrProposedAnchorPos ); + + const SwFrameFormat& rFrameFormat = GetFrameFormat(); + + SwRect aObjBoundRect( GetAnchoredObj().GetObjRect() ); + SwTwips nObjWidth = aRectFnSet.GetWidth(aObjBoundRect); + + // determine spacing values considering layout-/text-direction + const SvxLRSpaceItem& rLRSpace = rFrameFormat.GetLRSpace(); + const SvxULSpaceItem& rULSpace = rFrameFormat.GetULSpace(); + SwTwips nLRSpaceLeft, nLRSpaceRight, nULSpaceUpper, nULSpaceLower; + { + if ( rAnchorFrame.IsVertical() ) + { + // Seems to be easier to do it all the horizontal way + // So, from now on think horizontal. + rAnchorFrame.SwitchVerticalToHorizontal( aObjBoundRect ); + rAnchorFrame.SwitchVerticalToHorizontal( aAnchorPos ); + + // convert the spacing values + nLRSpaceLeft = rULSpace.GetUpper(); + nLRSpaceRight = rULSpace.GetLower(); + nULSpaceUpper = rLRSpace.GetRight(); + nULSpaceLower = rLRSpace.GetLeft(); + } + else + { + if ( rAnchorFrame.IsRightToLeft() ) + { + nLRSpaceLeft = rLRSpace.GetRight(); + nLRSpaceRight = rLRSpace.GetLeft(); + } + else + { + nLRSpaceLeft = rLRSpace.GetLeft(); + nLRSpaceRight = rLRSpace.GetRight(); + } + + nULSpaceUpper = rULSpace.GetUpper(); + nULSpaceLower = rULSpace.GetLower(); + } + } + + // consider left and upper spacing by adjusting anchor position. + // left spacing is only considered, if requested. + if( mnFlags & AsCharFlags::UlSpace ) + { + aAnchorPos.AdjustX(nLRSpaceLeft ); + } + aAnchorPos.AdjustY(nULSpaceUpper ); + + // for drawing objects: consider difference between its bounding rectangle + // and its snapping rectangle by adjusting anchor position. + // left difference is only considered, if requested. + if( !IsObjFly() ) + { + SwRect aSnapRect = GetObject().GetSnapRect(); + if ( rAnchorFrame.IsVertical() ) + { + rAnchorFrame.SwitchVerticalToHorizontal( aSnapRect ); + } + + if( mnFlags & AsCharFlags::UlSpace ) + { + aAnchorPos.AdjustX(aSnapRect.Left() - aObjBoundRect.Left() ); + } + aAnchorPos.AdjustY(aSnapRect.Top() - aObjBoundRect.Top() ); + } + + // enlarge bounding rectangle of object by its spacing. + aObjBoundRect.AddLeft( - nLRSpaceLeft ); + aObjBoundRect.AddWidth( nLRSpaceRight ); + aObjBoundRect.AddTop( - nULSpaceUpper ); + aObjBoundRect.AddHeight( nULSpaceLower ); + + // calculate relative position to given base line. + const SwFormatVertOrient& rVert = rFrameFormat.GetVertOrient(); + const SwTwips nObjBoundHeight = ( mnFlags & AsCharFlags::Rotate ) + ? aObjBoundRect.Width() + : aObjBoundRect.Height(); + const SwTwips nRelPos = GetRelPosToBase( nObjBoundHeight, rVert ); + + // for initial positioning: + // adjust the proposed anchor position by difference between + // calculated relative position to base line and current maximal line ascent. + // Note: In the following line formatting the base line will be adjusted + // by the same difference. + if( mnFlags & AsCharFlags::Init && nRelPos < 0 && mnLineAscentInclObjs < -nRelPos ) + { + if( mnFlags & AsCharFlags::Rotate ) + aAnchorPos.AdjustX( -(mnLineAscentInclObjs + nRelPos) ); + else + aAnchorPos.AdjustY( -(mnLineAscentInclObjs + nRelPos) ); + } + + // consider BIDI-multiportion by adjusting proposed anchor position + if( mnFlags & AsCharFlags::Bidi ) + aAnchorPos.AdjustX( -(aObjBoundRect.Width()) ); + + // calculate relative position considering rotation and inside rotation + // reverse direction. + Point aRelPos; + { + if( mnFlags & AsCharFlags::Rotate ) + { + if( mnFlags & AsCharFlags::Reverse ) + aRelPos.setX( -nRelPos - aObjBoundRect.Width() ); + else + { + aRelPos.setX( nRelPos ); + aRelPos.setY( -aObjBoundRect.Height() ); + } + } + else + aRelPos.setY( nRelPos ); + } + + if( !IsObjFly() ) + { + if( !( mnFlags & AsCharFlags::Quick ) ) + { + // save calculated Y-position value for 'automatic' vertical positioning, + // in order to avoid a switch to 'manual' vertical positioning in + // <SwDrawContact::Changed_(..)>. + const sal_Int16 eVertOrient = rVert.GetVertOrient(); + if( rVert.GetPos() != nRelPos && eVertOrient != text::VertOrientation::NONE ) + { + SwFormatVertOrient aVert( rVert ); + aVert.SetPos( nRelPos ); + const_cast<SwFrameFormat&>(rFrameFormat).LockModify(); + const_cast<SwFrameFormat&>(rFrameFormat).SetFormatAttr( aVert ); + const_cast<SwFrameFormat&>(rFrameFormat).UnlockModify(); + } + + // determine absolute anchor position considering layout directions. + // Note: Use copy of <aAnchorPos>, because it's needed for + // setting relative position. + Point aAbsAnchorPos( aAnchorPos ); + if ( rAnchorFrame.IsRightToLeft() ) + { + rAnchorFrame.SwitchLTRtoRTL( aAbsAnchorPos ); + aAbsAnchorPos.AdjustX( -nObjWidth ); + } + if ( rAnchorFrame.IsVertical() ) + rAnchorFrame.SwitchHorizontalToVertical( aAbsAnchorPos ); + + // set proposed anchor position at the drawing object. + // OD 2004-04-06 #i26791# - distinction between 'master' drawing + // object and 'virtual' drawing object no longer needed. + GetObject().SetAnchorPos( aAbsAnchorPos ); + + // move drawing object to set its correct relative position. + { + SwRect aSnapRect = GetObject().GetSnapRect(); + if ( rAnchorFrame.IsVertical() ) + rAnchorFrame.SwitchVerticalToHorizontal( aSnapRect ); + + Point aDiff; + if ( rAnchorFrame.IsRightToLeft() ) + aDiff = aRelPos + aAbsAnchorPos - aSnapRect.TopLeft(); + else + aDiff = aRelPos + aAnchorPos - aSnapRect.TopLeft(); + + if ( rAnchorFrame.IsVertical() ) + aDiff = Point( -aDiff.Y(), aDiff.X() ); + + // OD 2004-04-06 #i26791# - distinction between 'master' drawing + // object and 'virtual' drawing object no longer needed. + GetObject().Move( Size( aDiff.X(), aDiff.Y() ) ); + } + } + + // switch horizontal, LTR anchor position to absolute values. + if ( rAnchorFrame.IsRightToLeft() ) + { + rAnchorFrame.SwitchLTRtoRTL( aAnchorPos ); + aAnchorPos.AdjustX( -nObjWidth ); + } + if ( rAnchorFrame.IsVertical() ) + rAnchorFrame.SwitchHorizontalToVertical( aAnchorPos ); + + // #i44347# - keep last object rectangle at anchored object + OSL_ENSURE( dynamic_cast<const SwAnchoredDrawObject*>( &GetAnchoredObj() ) != nullptr, + "<SwAsCharAnchoredObjectPosition::CalcPosition()> - wrong type of anchored object." ); + SwAnchoredDrawObject& rAnchoredDrawObj = + static_cast<SwAnchoredDrawObject&>( GetAnchoredObj() ); + rAnchoredDrawObj.SetLastObjRect( rAnchoredDrawObj.GetObjRect().SVRect() ); + } + else + { + // determine absolute anchor position and calculate corresponding + // relative position and its relative position attribute. + // Note: The relative position contains the spacing values. + Point aRelAttr; + if ( rAnchorFrame.IsRightToLeft() ) + { + rAnchorFrame.SwitchLTRtoRTL( aAnchorPos ); + aAnchorPos.AdjustX( -nObjWidth ); + } + if ( rAnchorFrame.IsVertical() ) + { + rAnchorFrame.SwitchHorizontalToVertical( aAnchorPos ); + aRelAttr = Point( -nRelPos, 0 ); + aRelPos = Point( -aRelPos.Y(), aRelPos.X() ); + } + else + aRelAttr = Point( 0, nRelPos ); + + // OD 2004-03-23 #i26791# + OSL_ENSURE( dynamic_cast<const SwFlyInContentFrame*>( &GetAnchoredObj()) != nullptr, + "<SwAsCharAnchoredObjectPosition::CalcPosition()> - wrong anchored object." ); + const SwFlyInContentFrame& rFlyInContentFrame = + static_cast<const SwFlyInContentFrame&>(GetAnchoredObj()); + if ( !(mnFlags & AsCharFlags::Quick) && + ( aAnchorPos != rFlyInContentFrame.GetRefPoint() || + aRelAttr != rFlyInContentFrame.GetCurrRelPos() ) ) + { + // set new anchor position and relative position + SwFlyInContentFrame* pFlyInContentFrame = &const_cast<SwFlyInContentFrame&>(rFlyInContentFrame); + pFlyInContentFrame->SetRefPoint( aAnchorPos, aRelAttr, aRelPos ); + if( nObjWidth != aRectFnSet.GetWidth(pFlyInContentFrame->getFrameArea()) ) + { + // recalculate object bound rectangle, if object width has changed. + aObjBoundRect = GetAnchoredObj().GetObjRect(); + aObjBoundRect.AddLeft( - rLRSpace.GetLeft() ); + aObjBoundRect.AddWidth( rLRSpace.GetRight() ); + aObjBoundRect.AddTop( - rULSpace.GetUpper() ); + aObjBoundRect.AddHeight( rULSpace.GetLower() ); + } + } + OSL_ENSURE( aRectFnSet.GetHeight(rFlyInContentFrame.getFrameArea()), + "SwAnchoredObjectPosition::CalcPosition(..) - fly frame has an invalid height" ); + } + + // keep calculated values + maAnchorPos = aAnchorPos; + mnRelPos = nRelPos; + maObjBoundRect = aObjBoundRect; +} + +/** determine the relative position to base line for object position type AS_CHAR + + OD 29.07.2003 #110978# + Note about values set at member <mnLineAlignment> - + value gives feedback for the line formatting. + 0 - no feedback; 1|2|3 - proposed formatting of characters + at top|at center|at bottom of line. +*/ +SwTwips SwAsCharAnchoredObjectPosition::GetRelPosToBase( + const SwTwips _nObjBoundHeight, + const SwFormatVertOrient& _rVert ) +{ + SwTwips nRelPosToBase = 0; + + mnLineAlignment = sw::LineAlign::NONE; + + const sal_Int16 eVertOrient = _rVert.GetVertOrient(); + + if ( eVertOrient == text::VertOrientation::NONE ) + nRelPosToBase = _rVert.GetPos(); + else + { + if ( eVertOrient == text::VertOrientation::CENTER ) + nRelPosToBase -= _nObjBoundHeight / 2; + else if ( eVertOrient == text::VertOrientation::TOP ) + nRelPosToBase -= _nObjBoundHeight; + else if ( eVertOrient == text::VertOrientation::BOTTOM ) + nRelPosToBase = 0; + else if ( eVertOrient == text::VertOrientation::CHAR_CENTER ) + nRelPosToBase -= ( _nObjBoundHeight + mnLineAscent - mnLineDescent ) / 2; + else if ( eVertOrient == text::VertOrientation::CHAR_TOP ) + nRelPosToBase -= mnLineAscent; + else if ( eVertOrient == text::VertOrientation::CHAR_BOTTOM ) + nRelPosToBase += mnLineDescent - _nObjBoundHeight; + else + { + if( _nObjBoundHeight >= mnLineAscentInclObjs + mnLineDescentInclObjs ) + { + // object is at least as high as the line. Thus, no more is + // positioning necessary. Also, the max. ascent isn't changed. + nRelPosToBase -= mnLineAscentInclObjs; + if ( eVertOrient == text::VertOrientation::LINE_CENTER ) + mnLineAlignment = sw::LineAlign::CENTER; + else if ( eVertOrient == text::VertOrientation::LINE_TOP ) + mnLineAlignment = sw::LineAlign::TOP; + else if ( eVertOrient == text::VertOrientation::LINE_BOTTOM ) + mnLineAlignment = sw::LineAlign::BOTTOM; + } + else if ( eVertOrient == text::VertOrientation::LINE_CENTER ) + { + nRelPosToBase -= ( _nObjBoundHeight + mnLineAscentInclObjs - mnLineDescentInclObjs ) / 2; + mnLineAlignment = sw::LineAlign::CENTER; + } + else if ( eVertOrient == text::VertOrientation::LINE_TOP ) + { + nRelPosToBase -= mnLineAscentInclObjs; + mnLineAlignment = sw::LineAlign::TOP; + } + else if ( eVertOrient == text::VertOrientation::LINE_BOTTOM ) + { + nRelPosToBase += mnLineDescentInclObjs - _nObjBoundHeight; + mnLineAlignment = sw::LineAlign::BOTTOM; + } + } + } + + return nRelPosToBase; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/objectpositioning/environmentofanchoredobject.cxx b/sw/source/core/objectpositioning/environmentofanchoredobject.cxx new file mode 100644 index 000000000..b8fb4f02a --- /dev/null +++ b/sw/source/core/objectpositioning/environmentofanchoredobject.cxx @@ -0,0 +1,98 @@ +/* -*- 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 <environmentofanchoredobject.hxx> +#include <frame.hxx> +#include <pagefrm.hxx> + +using namespace objectpositioning; + +SwEnvironmentOfAnchoredObject::SwEnvironmentOfAnchoredObject( + const bool _bFollowTextFlow ) + : mbFollowTextFlow( _bFollowTextFlow ) +{} + +SwEnvironmentOfAnchoredObject::~SwEnvironmentOfAnchoredObject() +{} + +/** determine environment layout frame for possible horizontal object positions */ +const SwLayoutFrame& SwEnvironmentOfAnchoredObject::GetHoriEnvironmentLayoutFrame( + const SwFrame& _rHoriOrientFrame ) const +{ + const SwFrame* pHoriEnvironmentLayFrame = &_rHoriOrientFrame; + + if ( !mbFollowTextFlow ) + { + // No exception any more for page alignment. + // the page frame determines the horizontal layout environment. + pHoriEnvironmentLayFrame = _rHoriOrientFrame.FindPageFrame(); + } + else + { + while ( !pHoriEnvironmentLayFrame->IsCellFrame() && + !pHoriEnvironmentLayFrame->IsFlyFrame() && + !pHoriEnvironmentLayFrame->IsPageFrame() ) + { + pHoriEnvironmentLayFrame = pHoriEnvironmentLayFrame->GetUpper(); + OSL_ENSURE( pHoriEnvironmentLayFrame, + "SwEnvironmentOfAnchoredObject::GetHoriEnvironmentLayoutFrame(..) - no page|fly|cell frame found" ); + } + } + + OSL_ENSURE( dynamic_cast< const SwLayoutFrame *>( pHoriEnvironmentLayFrame ) != nullptr, + "SwEnvironmentOfAnchoredObject::GetHoriEnvironmentLayoutFrame(..) - found frame isn't a layout frame" ); + + return static_cast<const SwLayoutFrame&>(*pHoriEnvironmentLayFrame); +} + +/** determine environment layout frame for possible vertical object positions */ +const SwLayoutFrame& SwEnvironmentOfAnchoredObject::GetVertEnvironmentLayoutFrame( + const SwFrame& _rVertOrientFrame ) const +{ + const SwFrame* pVertEnvironmentLayFrame = &_rVertOrientFrame; + + if ( !mbFollowTextFlow ) + { + // No exception any more for page alignment. + // the page frame determines the vertical layout environment. + pVertEnvironmentLayFrame = _rVertOrientFrame.FindPageFrame(); + } + else + { + while ( !pVertEnvironmentLayFrame->IsCellFrame() && + !pVertEnvironmentLayFrame->IsFlyFrame() && + !pVertEnvironmentLayFrame->IsHeaderFrame() && + !pVertEnvironmentLayFrame->IsFooterFrame() && + !pVertEnvironmentLayFrame->IsFootnoteFrame() && + !pVertEnvironmentLayFrame->IsPageBodyFrame() && + !pVertEnvironmentLayFrame->IsPageFrame() ) + { + pVertEnvironmentLayFrame = pVertEnvironmentLayFrame->GetUpper(); + OSL_ENSURE( pVertEnvironmentLayFrame, + "SwEnvironmentOfAnchoredObject::GetVertEnvironmentLayoutFrame(..) - proposed frame not found" ); + } + } + + OSL_ENSURE( dynamic_cast< const SwLayoutFrame *>( pVertEnvironmentLayFrame ) != nullptr, + "SwEnvironmentOfAnchoredObject::GetVertEnvironmentLayoutFrame(..) - found frame isn't a layout frame" ); + + return static_cast<const SwLayoutFrame&>(*pVertEnvironmentLayFrame); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx new file mode 100644 index 000000000..95331da7f --- /dev/null +++ b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx @@ -0,0 +1,1202 @@ +/* -*- 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 <tocntntanchoredobjectposition.hxx> +#include <anchoredobject.hxx> +#include <frame.hxx> +#include <txtfrm.hxx> +#include <pagefrm.hxx> +#include <sectfrm.hxx> +#include <tabfrm.hxx> +#include <rootfrm.hxx> +#include <viewopt.hxx> +#include <viewsh.hxx> +#include <frmfmt.hxx> +#include <fmtsrnd.hxx> +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <svx/svdobj.hxx> +#include <environmentofanchoredobject.hxx> +#include <frmatr.hxx> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <sortedobjs.hxx> +#include <textboxhelper.hxx> + +using namespace objectpositioning; +using namespace ::com::sun::star; + +SwToContentAnchoredObjectPosition::SwToContentAnchoredObjectPosition( SdrObject& _rDrawObj ) + : SwAnchoredObjectPosition ( _rDrawObj ), + mpVertPosOrientFrame( nullptr ), + // #i26791# + maOffsetToFrameAnchorPos( Point() ), + mbAnchorToChar ( false ), + mpToCharOrientFrame( nullptr ), + mpToCharRect( nullptr ), + // #i22341# + mnToCharTopOfLine( 0 ) +{} + +SwToContentAnchoredObjectPosition::~SwToContentAnchoredObjectPosition() +{} + +bool SwToContentAnchoredObjectPosition::IsAnchoredToChar() const +{ + return mbAnchorToChar; +} + +const SwFrame* SwToContentAnchoredObjectPosition::ToCharOrientFrame() const +{ + return mpToCharOrientFrame; +} + +const SwRect* SwToContentAnchoredObjectPosition::ToCharRect() const +{ + return mpToCharRect; +} + +// #i22341# +SwTwips SwToContentAnchoredObjectPosition::ToCharTopOfLine() const +{ + return mnToCharTopOfLine; +} + +SwTextFrame& SwToContentAnchoredObjectPosition::GetAnchorTextFrame() const +{ + OSL_ENSURE( dynamic_cast<const SwTextFrame*>( &GetAnchorFrame()) != nullptr , + "SwToContentAnchoredObjectPosition::GetAnchorTextFrame() - wrong anchor frame type" ); + + return static_cast<SwTextFrame&>(GetAnchorFrame()); +} + +// #i23512# +static bool lcl_DoesVertPosFits( const SwTwips _nRelPosY, + const SwTwips _nAvail, + const SwLayoutFrame* _pUpperOfOrientFrame, + const bool _bBrowse, + const bool _bGrowInTable, + SwLayoutFrame*& _orpLayoutFrameToGrow ) +{ + bool bVertPosFits = false; + + if ( _nRelPosY <= _nAvail ) + { + bVertPosFits = true; + } + else if ( _bBrowse ) + { + if ( _pUpperOfOrientFrame->IsInSct() ) + { + SwSectionFrame* pSctFrame = + const_cast<SwSectionFrame*>(_pUpperOfOrientFrame->FindSctFrame()); + bVertPosFits = pSctFrame->GetUpper()->Grow( _nRelPosY - _nAvail, true ) > 0; + // Note: do not provide a layout frame for a grow. + } + else + { + bVertPosFits = const_cast<SwLayoutFrame*>(_pUpperOfOrientFrame)-> + Grow( _nRelPosY - _nAvail, true ) > 0; + if ( bVertPosFits ) + _orpLayoutFrameToGrow = const_cast<SwLayoutFrame*>(_pUpperOfOrientFrame); + } + } + else if ( _pUpperOfOrientFrame->IsInTab() && _bGrowInTable ) + { + // #i45085# - check, if upper frame would grow the + // expected amount of twips. + const SwTwips nTwipsGrown = const_cast<SwLayoutFrame*>(_pUpperOfOrientFrame)-> + Grow( _nRelPosY - _nAvail, true ); + bVertPosFits = ( nTwipsGrown == ( _nRelPosY - _nAvail ) ); + if ( bVertPosFits ) + _orpLayoutFrameToGrow = const_cast<SwLayoutFrame*>(_pUpperOfOrientFrame); + } + + return bVertPosFits; +} + +void SwToContentAnchoredObjectPosition::CalcPosition() +{ + // get format of object + const SwFrameFormat& rFrameFormat = GetFrameFormat(); + + // declare and set <pFooter> to footer frame, if object is anchored + // at a frame belonging to the footer. + const SwFrame* pFooter = GetAnchorFrame().FindFooterOrHeader(); + if ( pFooter && !pFooter->IsFooterFrame() ) + pFooter = nullptr; + + // declare and set <bBrowse> to true, if document is in browser mode and + // object is anchored at the body, but not at frame belonging to a table. + bool bBrowse = GetAnchorFrame().IsInDocBody() && !GetAnchorFrame().IsInTab(); + if( bBrowse ) + { + const SwViewShell *pSh = GetAnchorFrame().getRootFrame()->GetCurrShell(); + if( !pSh || !pSh->GetViewOptions()->getBrowseMode() ) + bBrowse = false; + } + + // determine left/right and its upper/lower spacing. + const SvxLRSpaceItem &rLR = rFrameFormat.GetLRSpace(); + const SvxULSpaceItem &rUL = rFrameFormat.GetULSpace(); + + // determine, if object has no surrounding. + const SwFormatSurround& rSurround = rFrameFormat.GetSurround(); + const bool bNoSurround = rSurround.GetSurround() == css::text::WrapTextMode_NONE; + const bool bWrapThrough = rSurround.GetSurround() == css::text::WrapTextMode_THROUGH; + + // new class <SwEnvironmentOfAnchoredObject> + SwEnvironmentOfAnchoredObject aEnvOfObj( DoesObjFollowsTextFlow() ); + + // #i18732# - grow only, if object has to follow the text flow + const bool bGrow = DoesObjFollowsTextFlow() && + ( !GetAnchorFrame().IsInTab() || + !rFrameFormat.GetFrameSize().GetHeightPercent() ); + + // get text frame the object is anchored at + const SwTextFrame& rAnchorTextFrame = GetAnchorTextFrame(); + SwRectFnSet aRectFnSet(&rAnchorTextFrame); + + const SwRect aObjBoundRect( GetAnchoredObj().GetObjRect() ); + + // local variable keeping the calculated relative position; initialized with + // current relative position. + // #i26791# - use new object instance of <SwAnchoredObject> + Point aRelPos( GetAnchoredObj().GetCurrRelPos() ); + + SwTwips nRelDiff = 0; + + bool bMoveable = rAnchorTextFrame.IsMoveable(); + + // determine frame the object position has to be oriented at. + const SwTextFrame* pOrientFrame = &rAnchorTextFrame; + const SwTextFrame* pAnchorFrameForVertPos; + { + // if object is at-character anchored, determine character-rectangle + // and frame, position has to be oriented at. + mbAnchorToChar = (RndStdIds::FLY_AT_CHAR == rFrameFormat.GetAnchor().GetAnchorId()); + if ( mbAnchorToChar ) + { + const SwFormatAnchor& rAnch = rFrameFormat.GetAnchor(); + // #i26791# - use new object instance of <SwAnchoredObject> + // Due to table break algorithm the character + // rectangle can have no height. Thus, check also the width + if ( ( !GetAnchoredObj().GetLastCharRect().Height() && + !GetAnchoredObj().GetLastCharRect().Width() ) || + !GetAnchoredObj().GetLastTopOfLine() ) + { + GetAnchoredObj().CheckCharRectAndTopOfLine( false ); + // Due to table break algorithm the character + // rectangle can have no height. Thus, check also the width + if ( ( !GetAnchoredObj().GetLastCharRect().Height() && + !GetAnchoredObj().GetLastCharRect().Width() ) || + !GetAnchoredObj().GetLastTopOfLine() ) + { + // Get default for <mpVertPosOrientFrame>, if it's not set. + if ( !mpVertPosOrientFrame ) + { + mpVertPosOrientFrame = rAnchorTextFrame.GetUpper(); + } + return; + } + } + mpToCharRect = &(GetAnchoredObj().GetLastCharRect()); + // #i22341# - get top of line, in which the anchor character is. + mnToCharTopOfLine = GetAnchoredObj().GetLastTopOfLine(); + pOrientFrame = &(const_cast<SwTextFrame&>(rAnchorTextFrame).GetFrameAtOfst( + rAnchorTextFrame.MapModelToViewPos(*rAnch.GetContentAnchor()))); + mpToCharOrientFrame = pOrientFrame; + } + } + aRectFnSet.Refresh(pOrientFrame); + + // determine vertical position + { + + // determine vertical positioning and alignment attributes + SwFormatVertOrient aVert( rFrameFormat.GetVertOrient() ); + + // #i18732# - determine layout frame for vertical + // positions aligned to 'page areas'. + const SwLayoutFrame& rPageAlignLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pOrientFrame ); + + if ( aVert.GetVertOrient() != text::VertOrientation::NONE ) + { + // #i18732# - adjustments for follow text flow or not + // AND vertical alignment at 'page areas'. + SwTwips nAlignAreaHeight; + SwTwips nAlignAreaOffset; + GetVertAlignmentValues( *pOrientFrame, rPageAlignLayFrame, + aVert.GetRelationOrient(), + nAlignAreaHeight, nAlignAreaOffset ); + + // determine relative vertical position + SwTwips nRelPosY = nAlignAreaOffset; + const SwTwips nObjHeight = aRectFnSet.GetHeight(aObjBoundRect); + const SwTwips nUpperSpace = aRectFnSet.IsVert() + ? ( aRectFnSet.IsVertL2R() + ? rLR.GetLeft() + : rLR.GetRight() ) + : rUL.GetUpper(); + // --> OD 2009-08-31 #monglianlayout# + const SwTwips nLowerSpace = aRectFnSet.IsVert() + ? ( aRectFnSet.IsVertL2R() + ? rLR.GetLeft() + : rLR.GetRight() ) + : rUL.GetLower(); + switch ( aVert.GetVertOrient() ) + { + case text::VertOrientation::CHAR_BOTTOM: + { + if ( mbAnchorToChar ) + { + // bottom (to character anchored) + nRelPosY += nAlignAreaHeight + nUpperSpace; + if ( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + nRelPosY += nObjHeight; + } + break; + } + [[fallthrough]]; + } + case text::VertOrientation::TOP: + { + // #i22341# - special case for vertical + // alignment at top of line + if ( mbAnchorToChar && + aVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + nRelPosY -= (nObjHeight + nLowerSpace); + } + else + { + nRelPosY += nUpperSpace; + } + } + break; + // #i22341# + case text::VertOrientation::LINE_TOP: + { + if ( mbAnchorToChar && + aVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + nRelPosY -= (nObjHeight + nLowerSpace); + } + else + { + OSL_FAIL( "<SwToContentAnchoredObjectPosition::CalcPosition()> - unknown combination of vertical position and vertical alignment." ); + } + } + break; + case text::VertOrientation::CENTER: + { + nRelPosY += (nAlignAreaHeight / 2) - (nObjHeight / 2); + } + break; + // #i22341# + case text::VertOrientation::LINE_CENTER: + { + if ( mbAnchorToChar && + aVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + nRelPosY += (nAlignAreaHeight / 2) - (nObjHeight / 2); + } + else + { + OSL_FAIL( "<SwToContentAnchoredObjectPosition::CalcPosition()> - unknown combination of vertical position and vertical alignment." ); + } + } + break; + case text::VertOrientation::BOTTOM: + { + if ( ( aVert.GetRelationOrient() == text::RelOrientation::FRAME || + aVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) && + bNoSurround ) + { + // bottom (aligned to 'paragraph areas') + nRelPosY += nAlignAreaHeight + nUpperSpace; + } + else + { + // #i22341# - special case for vertical + // alignment at top of line + if ( mbAnchorToChar && + aVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + nRelPosY += nUpperSpace; + } + else + { + nRelPosY += nAlignAreaHeight - + ( nObjHeight + nLowerSpace ); + } + } + } + break; + // #i22341# + case text::VertOrientation::LINE_BOTTOM: + { + if ( mbAnchorToChar && + aVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + nRelPosY += nUpperSpace; + } + else + { + OSL_FAIL( "<SwToContentAnchoredObjectPosition::CalcPosition()> - unknown combination of vertical position and vertical alignment." ); + } + } + break; + default: + break; + } + + // adjust relative position by distance between anchor frame and + // the frame, the object is oriented at. + // #i28701# - correction: adjust relative position, + // only if the floating screen object has to follow the text flow. + if ( DoesObjFollowsTextFlow() && pOrientFrame != &rAnchorTextFrame ) + { + // #i11860# - use new method <GetTopForObjPos> + // to get top of frame for object positioning. + const SwTwips nTopOfOrient = GetTopForObjPos( *pOrientFrame, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + nRelPosY += aRectFnSet.YDiff( nTopOfOrient, + GetTopForObjPos( rAnchorTextFrame, aRectFnSet.FnRect(), aRectFnSet.IsVert() ) ); + } + + // #i42124# - capture object inside vertical + // layout environment. + { + const SwTwips nTopOfAnch = + GetTopForObjPos( *pOrientFrame, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + const SwLayoutFrame& rVertEnvironLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( + *(pOrientFrame->GetUpper()) ); + const bool bCheckBottom = !DoesObjFollowsTextFlow(); + nRelPosY = AdjustVertRelPos( nTopOfAnch, aRectFnSet.IsVert(), aRectFnSet.IsVertL2R(), + rVertEnvironLayFrame, nRelPosY, + DoesObjFollowsTextFlow(), + bCheckBottom ); + } + + // keep calculated relative vertical position - needed for filters + // (including the xml-filter) + { + // determine position + SwTwips nAttrRelPosY = nRelPosY - nAlignAreaOffset; + // set + if ( nAttrRelPosY != aVert.GetPos() ) + { + aVert.SetPos( nAttrRelPosY ); + const_cast<SwFrameFormat&>(rFrameFormat).LockModify(); + const_cast<SwFrameFormat&>(rFrameFormat).SetFormatAttr( aVert ); + const_cast<SwFrameFormat&>(rFrameFormat).UnlockModify(); + } + } + + // determine absolute 'vertical' position, depending on layout-direction + // #i26791# - determine offset to 'vertical' frame + // anchor position, depending on layout-direction + if ( aRectFnSet.IsVert() ) + { + aRelPos.setX( nRelPosY ); + maOffsetToFrameAnchorPos.setX( nAlignAreaOffset ); + } + else + { + aRelPos.setY( nRelPosY ); + maOffsetToFrameAnchorPos.setY( nAlignAreaOffset ); + } + } + + // Determine upper of frame vertical position is oriented at. + // #i28701# - determine 'virtual' anchor frame. + // This frame is used in the following instead of the 'real' anchor + // frame <rAnchorTextFrame> for the 'vertical' position in all cases. + const SwLayoutFrame* pUpperOfOrientFrame = nullptr; + { + // #i28701# - As long as the anchor frame is on the + // same page as <pOrientFrame> and the vertical position isn't aligned + // automatic at the anchor character or the top of the line of the + // anchor character, the anchor frame determines the vertical position. + if ( &rAnchorTextFrame == pOrientFrame || + ( rAnchorTextFrame.FindPageFrame() == pOrientFrame->FindPageFrame() && + aVert.GetVertOrient() == text::VertOrientation::NONE && + aVert.GetRelationOrient() != text::RelOrientation::CHAR && + aVert.GetRelationOrient() != text::RelOrientation::TEXT_LINE ) ) + { + pUpperOfOrientFrame = rAnchorTextFrame.GetUpper(); + pAnchorFrameForVertPos = &rAnchorTextFrame; + } + else + { + pUpperOfOrientFrame = pOrientFrame->GetUpper(); + pAnchorFrameForVertPos = pOrientFrame; + } + } + + // ignore one-column sections. + // #i23512# - correction: also ignore one-columned + // sections with footnotes/endnotes + if ( pUpperOfOrientFrame->IsInSct() ) + { + const SwSectionFrame* pSctFrame = pUpperOfOrientFrame->FindSctFrame(); + const bool bIgnoreSection = pUpperOfOrientFrame->IsSctFrame() || + ( pSctFrame->Lower()->IsColumnFrame() && + !pSctFrame->Lower()->GetNext() ); + if ( bIgnoreSection ) + pUpperOfOrientFrame = pSctFrame->GetUpper(); + } + + if ( aVert.GetVertOrient() == text::VertOrientation::NONE ) + { + // local variable <nRelPosY> for calculation of relative vertical + // distance to anchor. + SwTwips nRelPosY = 0; + // #i26791# - local variable <nVertOffsetToFrameAnchorPos> + // for determination of the 'vertical' offset to the frame anchor + // position + SwTwips nVertOffsetToFrameAnchorPos( 0 ); + // #i22341# - add special case for vertical alignment + // at top of line. + if ( mbAnchorToChar && + ( aVert.GetRelationOrient() == text::RelOrientation::CHAR || + aVert.GetRelationOrient() == text::RelOrientation::TEXT_LINE ) ) + { + // #i11860# - use new method <GetTopForObjPos> + // to get top of frame for object positioning. + SwTwips nTopOfOrient = GetTopForObjPos( *pOrientFrame, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + if ( aVert.GetRelationOrient() == text::RelOrientation::CHAR ) + { + nVertOffsetToFrameAnchorPos = aRectFnSet.YDiff( + aRectFnSet.GetBottom(*ToCharRect()), + nTopOfOrient ); + } + else + { + nVertOffsetToFrameAnchorPos = aRectFnSet.YDiff( ToCharTopOfLine(), + nTopOfOrient ); + } + nRelPosY = nVertOffsetToFrameAnchorPos - aVert.GetPos(); + } + else + { + // #i28701# - correction: use <pAnchorFrameForVertPos> + // instead of <pOrientFrame> and do not adjust relative position + // to get correct vertical position. + nVertOffsetToFrameAnchorPos = 0; + // #i11860# - use new method <GetTopForObjPos> + // to get top of frame for object positioning. + const SwTwips nTopOfOrient = + GetTopForObjPos( *pAnchorFrameForVertPos, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + // Increase <nRelPosY> by margin height, + // if position is vertical aligned to "paragraph text area" + if ( aVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) + { + // #i11860# - consider upper space amount of previous frame + SwTwips nTopMargin = aRectFnSet.GetTopMargin(*pAnchorFrameForVertPos); + if ( pAnchorFrameForVertPos->IsTextFrame() ) + { + nTopMargin -= pAnchorFrameForVertPos-> + GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + } + nVertOffsetToFrameAnchorPos += nTopMargin; + } + // #i18732# - adjust <nRelPosY> by difference + // between 'page area' and 'anchor' frame, if position is + // vertical aligned to 'page areas' + else if ( aVert.GetRelationOrient() == text::RelOrientation::PAGE_FRAME ) + { + nVertOffsetToFrameAnchorPos += aRectFnSet.YDiff( + aRectFnSet.GetTop(rPageAlignLayFrame.getFrameArea()), + nTopOfOrient ); + } + else if ( aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) + { + SwRect aPgPrtRect( rPageAlignLayFrame.getFrameArea() ); + if ( rPageAlignLayFrame.IsPageFrame() ) + { + aPgPrtRect = + static_cast<const SwPageFrame&>(rPageAlignLayFrame).PrtWithoutHeaderAndFooter(); + } + nVertOffsetToFrameAnchorPos += aRectFnSet.YDiff( + aRectFnSet.GetTop(aPgPrtRect), + nTopOfOrient ); + } + else if (aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + { + // The anchored object is relative from the bottom of the page's print area. + SwRect aPgPrtRect(rPageAlignLayFrame.getFrameArea()); + if (rPageAlignLayFrame.IsPageFrame()) + { + auto& rPageFrame = static_cast<const SwPageFrame&>(rPageAlignLayFrame); + aPgPrtRect = rPageFrame.PrtWithoutHeaderAndFooter(); + } + SwTwips nPageBottom = aRectFnSet.GetBottom(aPgPrtRect); + nVertOffsetToFrameAnchorPos += aRectFnSet.YDiff(nPageBottom, nTopOfOrient); + } + nRelPosY = nVertOffsetToFrameAnchorPos + aVert.GetPos(); + } + + // <pUpperOfOrientFrame>: layout frame, at which the position has to + // is oriented at + // <nRelPosY>: rest of the relative distance in the current + // layout frame + // <nAvail>: space, which is available in the current + // layout frame + + // #i26791# - determine offset to 'vertical' + // frame anchor position, depending on layout-direction + if ( aRectFnSet.IsVert() ) + maOffsetToFrameAnchorPos.setX( nVertOffsetToFrameAnchorPos ); + else + maOffsetToFrameAnchorPos.setY( nVertOffsetToFrameAnchorPos ); + // #i11860# - use new method <GetTopForObjPos> + // to get top of frame for object positioning. + const SwTwips nTopOfAnch = GetTopForObjPos( *pAnchorFrameForVertPos, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + if( nRelPosY <= 0 ) + { + // Allow negative position, but keep it + // inside environment layout frame. + const SwLayoutFrame& rVertEnvironLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pUpperOfOrientFrame ); + // #i31805# - do not check, if bottom of + // anchored object would fit into environment layout frame, if + // anchored object has to follow the text flow. + const bool bCheckBottom = !DoesObjFollowsTextFlow(); + nRelPosY = AdjustVertRelPos( nTopOfAnch, aRectFnSet.IsVert(), aRectFnSet.IsVertL2R(), + rVertEnvironLayFrame, nRelPosY, + DoesObjFollowsTextFlow(), + bCheckBottom ); + if ( aRectFnSet.IsVert() ) + aRelPos.setX( nRelPosY ); + else + aRelPos.setY( nRelPosY ); + } + else + { + aRectFnSet.Refresh(pAnchorFrameForVertPos); + SwTwips nAvail = + aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame), + nTopOfAnch ); + const bool bInFootnote = pAnchorFrameForVertPos->IsInFootnote(); + while ( nRelPosY ) + { + // #i23512# - correction: + // consider section frame for grow in online layout. + // use new local method <lcl_DoesVertPosFits(..)> + SwLayoutFrame* pLayoutFrameToGrow = nullptr; + const bool bDoesVertPosFits = lcl_DoesVertPosFits( + nRelPosY, nAvail, pUpperOfOrientFrame, bBrowse, + bGrow, pLayoutFrameToGrow ); + + if ( bDoesVertPosFits ) + { + SwTwips nTmpRelPosY = + aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame), + nTopOfAnch ) - + nAvail + nRelPosY; + // #i28701# - adjust calculated + // relative vertical position to object's environment. + const SwFrame& rVertEnvironLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pUpperOfOrientFrame ); + // Do not check, if bottom of + // anchored object would fit into environment layout + // frame, if anchored object has to follow the text flow. + const bool bCheckBottom = !DoesObjFollowsTextFlow(); + nTmpRelPosY = AdjustVertRelPos( nTopOfAnch, aRectFnSet.IsVert(), aRectFnSet.IsVertL2R(), + rVertEnvironLayFrame, + nTmpRelPosY, + DoesObjFollowsTextFlow(), + bCheckBottom ); + if ( aRectFnSet.IsVert() ) + aRelPos.setX( nTmpRelPosY ); + else + aRelPos.setY( nTmpRelPosY ); + + // #i23512# - use local variable + // <pLayoutFrameToGrow> provided by new method + // <lcl_DoesVertPosFits(..)>. + if ( pLayoutFrameToGrow ) + { + // No need to grow the anchor cell in case the follow-text-flow object + // is wrap-though. + if (!GetAnchorFrame().IsInTab() || !DoesObjFollowsTextFlow() || !bWrapThrough) + { + pLayoutFrameToGrow->Grow( nRelPosY - nAvail ); + } + } + nRelPosY = 0; + } + else + { + // #i26495# - floating screen objects, + // which are anchored inside a table, doesn't follow + // the text flow. + if ( DoesObjFollowsTextFlow() && + !( aVert.GetRelationOrient() == text::RelOrientation::PAGE_FRAME || + aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) && + !GetAnchorFrame().IsInTab() ) + { + if ( bMoveable ) + { + // follow the text flow + nRelPosY -= nAvail; + MakePageType eMakePage = bInFootnote ? MAKEPAGE_NONE + : MAKEPAGE_APPEND; + const bool bInSct = pUpperOfOrientFrame->IsInSct(); + if( bInSct ) + eMakePage = MAKEPAGE_NOSECTION; + + const SwLayoutFrame* pTmp = + pUpperOfOrientFrame->GetLeaf( eMakePage, true, &rAnchorTextFrame ); + if ( pTmp && + ( !bInSct || + pUpperOfOrientFrame->FindSctFrame()->IsAnFollow( pTmp->FindSctFrame() ) ) ) + { + pUpperOfOrientFrame = pTmp; + bMoveable = rAnchorTextFrame.IsMoveable( pUpperOfOrientFrame ); + aRectFnSet.Refresh(pUpperOfOrientFrame); + nAvail = aRectFnSet.GetHeight(pUpperOfOrientFrame->getFramePrintArea()); + } + else + { + // if there isn't enough space in the (columned) + // section, leave it and set available space <nAvail> + // to the space below the section. + // if the new available space isn't also enough, + // new pages can be created. + if( bInSct ) + { + const SwFrame* pSct = pUpperOfOrientFrame->FindSctFrame(); + pUpperOfOrientFrame = pSct->GetUpper(); + nAvail = aRectFnSet.YDiff( + aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame), + aRectFnSet.GetPrtBottom(*pSct) ); + } + else + { +#if OSL_DEBUG_LEVEL > 1 + OSL_FAIL( "<SwToContentAnchoredObjectPosition::CalcPosition()> - !bInSct" ); +#endif + nRelDiff = nRelPosY; + nRelPosY = 0; + } + } + } + else + { + nRelPosY = 0; + } + } + else + { + // #i18732# - do not follow text flow respectively + // align at 'page areas', but stay inside given environment + const SwFrame& rVertEnvironLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pUpperOfOrientFrame ); + nRelPosY = AdjustVertRelPos( nTopOfAnch, aRectFnSet.IsVert(), aRectFnSet.IsVertL2R(), + rVertEnvironLayFrame, + nRelPosY, + DoesObjFollowsTextFlow() ); + if( aRectFnSet.IsVert() ) + aRelPos.setX( nRelPosY ); + else + aRelPos.setY( nRelPosY ); + nRelPosY = 0; + } + } + } // end of <while ( nRelPosY )> + } // end of else <nRelPosY <= 0> + } // end of <aVert.GetVertOrient() == text::VertOrientation::NONE> + + // We need to calculate the part's absolute position, in order for + // it to be put onto the right page and to be pulled into the + // LayLeaf's PrtArea + const SwTwips nTopOfAnch = GetTopForObjPos( *pAnchorFrameForVertPos, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + if( aRectFnSet.IsVert() ) + { + // --> OD 2009-08-31 #monglianlayout# + if ( !aRectFnSet.IsVertL2R() ) + { + GetAnchoredObj().SetObjLeft( nTopOfAnch - + ( aRelPos.X() - nRelDiff ) - + aObjBoundRect.Width() ); + } + else + { + GetAnchoredObj().SetObjLeft( nTopOfAnch + + ( aRelPos.X() - nRelDiff ) ); + } + } + else + { + GetAnchoredObj().SetObjTop( nTopOfAnch + + ( aRelPos.Y() - nRelDiff ) ); + } + + // grow environment under certain conditions + // ignore one-column sections. + // #i23512# - correction: also ignore one-columned + // sections with footnotes/endnotes + if ( pUpperOfOrientFrame->IsInSct() ) + { + const SwSectionFrame* pSctFrame = pUpperOfOrientFrame->FindSctFrame(); + const bool bIgnoreSection = pUpperOfOrientFrame->IsSctFrame() || + ( pSctFrame->Lower()->IsColumnFrame() && + !pSctFrame->Lower()->GetNext() ); + if ( bIgnoreSection ) + pUpperOfOrientFrame = pSctFrame->GetUpper(); + } + SwTwips nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame) ); + if( nDist < 0 ) + { + // #i23512# - correction: + // consider section frame for grow in online layout and + // consider page alignment for grow in table. + SwLayoutFrame* pLayoutFrameToGrow = nullptr; + if ( bBrowse && rAnchorTextFrame.IsMoveable() ) + { + if ( pUpperOfOrientFrame->IsInSct() ) + { + pLayoutFrameToGrow = const_cast<SwLayoutFrame*>( + pUpperOfOrientFrame->FindSctFrame()->GetUpper()); + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pLayoutFrameToGrow) ); + if ( nDist >= 0 ) + { + pLayoutFrameToGrow = nullptr; + } + } + else + { + pLayoutFrameToGrow = + const_cast<SwLayoutFrame*>(pUpperOfOrientFrame); + } + } + else if ( rAnchorTextFrame.IsInTab() && bGrow ) + { + pLayoutFrameToGrow = const_cast<SwLayoutFrame*>(pUpperOfOrientFrame); + } + if ( pLayoutFrameToGrow ) + { + // No need to grow the anchor cell in case the follow-text-flow object + // is wrap-though. + if (!GetAnchorFrame().IsInTab() || !DoesObjFollowsTextFlow() || !bWrapThrough) + { + pLayoutFrameToGrow->Grow( -nDist ); + } + } + } + + if ( DoesObjFollowsTextFlow() && + !( aVert.GetRelationOrient() == text::RelOrientation::PAGE_FRAME || + aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) ) + { + + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame) ); + // #i26945# - floating screen objects, which are + // anchored inside a table, doesn't follow the text flow. But, they + // have to stay inside its layout environment. + if ( nDist < 0 && pOrientFrame->IsInTab() ) + { + // If the anchor frame is the first content of the table cell + // and has no follow, the table frame is notified, + // that the object doesn't fit into the table cell. + // Adjustment of position isn't needed in this case. + if ( pOrientFrame == &rAnchorTextFrame && + !pOrientFrame->GetFollow() && + !pOrientFrame->GetIndPrev() ) + { + const_cast<SwTabFrame*>(pOrientFrame->FindTabFrame()) + ->SetDoesObjsFit( false ); + } + else + { + SwTwips nTmpRelPosY( 0 ); + if ( aRectFnSet.IsVert() ) + nTmpRelPosY = aRelPos.X() - nDist; + else + nTmpRelPosY = aRelPos.Y() + nDist; + const SwLayoutFrame& rVertEnvironLayFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pUpperOfOrientFrame ); + nTmpRelPosY = AdjustVertRelPos( nTopOfAnch, aRectFnSet.IsVert(), aRectFnSet.IsVertL2R(), + rVertEnvironLayFrame, + nTmpRelPosY, + DoesObjFollowsTextFlow(), + false ); + if ( aRectFnSet.IsVert() ) + { + aRelPos.setX( nTmpRelPosY ); + // --> OD 2009-08-31 #mongolianlayout# + if ( !aRectFnSet.IsVertL2R() ) + { + GetAnchoredObj().SetObjLeft( nTopOfAnch - + aRelPos.X() - + aObjBoundRect.Width() ); + } + else + { + GetAnchoredObj().SetObjLeft( nTopOfAnch + aRelPos.X() ); + } + } + else + { + aRelPos.setY( nTmpRelPosY ); + GetAnchoredObj().SetObjTop( nTopOfAnch + aRelPos.Y() ); + } + // If the anchor frame is the first content of the table cell + // and the object still doesn't fit, the table frame is notified, + // that the object doesn't fit into the table cell. + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame) ); + if ( nDist < 0 && + pOrientFrame == &rAnchorTextFrame && !pOrientFrame->GetIndPrev() ) + { + const_cast<SwTabFrame*>(pOrientFrame->FindTabFrame()) + ->SetDoesObjsFit( false ); + } + } + } + else + { + // follow text flow + const bool bInFootnote = rAnchorTextFrame.IsInFootnote(); + while( bMoveable && nDist < 0 ) + { + bool bInSct = pUpperOfOrientFrame->IsInSct(); + if ( bInSct ) + { + const SwLayoutFrame* pTmp = pUpperOfOrientFrame->FindSctFrame()->GetUpper(); + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pTmp) ); + // #i23129# - Try to flow into next + // section|section column. Thus, do *not* leave section + // area, if anchored object doesn't fit into upper of section. + // But the anchored object is allowed to overlap bottom + // section|section column. + if ( nDist >= 0 ) + { + break; + } + } + if ( !bInSct && + aRectFnSet.GetTop(GetAnchoredObj().GetObjRect()) == + aRectFnSet.GetPrtTop(*pUpperOfOrientFrame) ) + // It doesn't fit, moving it would not help either anymore + break; + + const SwLayoutFrame* pNextLay = pUpperOfOrientFrame->GetLeaf( + ( bInSct + ? MAKEPAGE_NOSECTION + : ( bInFootnote ? MAKEPAGE_NONE : MAKEPAGE_APPEND ) ), + true, &rAnchorTextFrame ); + // correction: + // If anchor is in footnote and proposed next layout environment + // isn't a footnote frame, object can't follow the text flow + if ( bInFootnote && pNextLay && !pNextLay->IsFootnoteFrame() ) + { + pNextLay = nullptr; + } + if ( pNextLay ) + { + SwRectFnSet fnRectX(pNextLay); + if ( !bInSct || + ( pUpperOfOrientFrame->FindSctFrame()->IsAnFollow( pNextLay->FindSctFrame() ) && + fnRectX.GetHeight(pNextLay->getFramePrintArea()) ) ) + { + SwTwips nTmpRelPosY = + aRectFnSet.YDiff( aRectFnSet.GetPrtTop(*pNextLay), + nTopOfAnch ); + if ( aRectFnSet.IsVert() ) + aRelPos.setX( nTmpRelPosY ); + else + aRelPos.setY( nTmpRelPosY ); + pUpperOfOrientFrame = pNextLay; + aRectFnSet.Refresh(pUpperOfOrientFrame); + bMoveable = rAnchorTextFrame.IsMoveable( pUpperOfOrientFrame ); + if( fnRectX.IsVert() ) + { + // --> OD 2009-08-31 #mongolianlayout# + if ( !aRectFnSet.IsVertL2R() ) + { + GetAnchoredObj().SetObjLeft( nTopOfAnch - + aRelPos.X() - + aObjBoundRect.Width() ); + } + else + { + GetAnchoredObj().SetObjLeft( nTopOfAnch + + aRelPos.X() ); + } + } + else + GetAnchoredObj().SetObjTop( nTopOfAnch + + aRelPos.Y() ); + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame) ); + } + // #i23129# - leave section area + else if ( bInSct ) + { + const SwLayoutFrame* pTmp = pUpperOfOrientFrame->FindSctFrame()->GetUpper(); + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pTmp) ); + if( nDist < 0 ) + pUpperOfOrientFrame = pTmp; + else + break; + } + } + else if ( bInSct ) + { + // If we don't have enough room within the Area, we take a look at + // the Page + const SwLayoutFrame* pTmp = pUpperOfOrientFrame->FindSctFrame()->GetUpper(); + nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), + aRectFnSet.GetPrtBottom(*pTmp) ); + if( nDist < 0 ) + pUpperOfOrientFrame = pTmp; + else + break; + } + else + bMoveable = false; + } + } + } + + // keep layout frame vertical position is oriented at. + mpVertPosOrientFrame = pUpperOfOrientFrame; + + // If it was requested to not overlap with already formatted objects, take care of that + // here. + CalcOverlap(pAnchorFrameForVertPos, aRelPos, nTopOfAnch); + } + + // determine 'horizontal' position + { + // determine horizontal positioning and alignment attributes + SwFormatHoriOrient aHori( rFrameFormat.GetHoriOrient() ); + + // set calculated vertical position in order to determine correct + // frame, the horizontal position is oriented at. + const SwTwips nTopOfAnch = GetTopForObjPos( *pAnchorFrameForVertPos, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + if( aRectFnSet.IsVert() ) + { + // --> OD 2009-08-31 #mongolianlayout# + if ( !aRectFnSet.IsVertL2R() ) + { + GetAnchoredObj().SetObjLeft( nTopOfAnch - + aRelPos.X() - aObjBoundRect.Width() ); + } + else + { + GetAnchoredObj().SetObjLeft( nTopOfAnch + aRelPos.X() ); + } + } + else + GetAnchoredObj().SetObjTop( nTopOfAnch + aRelPos.Y() ); + + // determine frame, horizontal position is oriented at. + // #i28701# - If floating screen object doesn't follow + // the text flow, its horizontal position is oriented at <pOrientFrame>. + const SwFrame* pHoriOrientFrame = DoesObjFollowsTextFlow() + ? &GetHoriVirtualAnchor( *mpVertPosOrientFrame ) + : pOrientFrame; + + // #i26791# - get 'horizontal' offset to frame anchor position. + SwTwips nHoriOffsetToFrameAnchorPos( 0 ); + SwTwips nRelPosX = CalcRelPosX( *pHoriOrientFrame, aEnvOfObj, + aHori, rLR, rUL, bWrapThrough, + ( aRectFnSet.IsVert() ? aRelPos.X() : aRelPos.Y() ), + nHoriOffsetToFrameAnchorPos ); + + // #i26791# - determine offset to 'horizontal' frame + // anchor position, depending on layout-direction + if ( aRectFnSet.IsVert() ) + { + aRelPos.setY( nRelPosX ); + maOffsetToFrameAnchorPos.setY( nHoriOffsetToFrameAnchorPos ); + } + else + { + aRelPos.setX( nRelPosX ); + maOffsetToFrameAnchorPos.setX( nHoriOffsetToFrameAnchorPos ); + } + + // save calculated horizontal position - needed for filters + // (including the xml-filter) + { + SwTwips nAttrRelPosX = nRelPosX - nHoriOffsetToFrameAnchorPos; + if ( aHori.GetHoriOrient() != text::HoriOrientation::NONE && + aHori.GetPos() != nAttrRelPosX ) + { + aHori.SetPos( nAttrRelPosX ); + const_cast<SwFrameFormat&>(rFrameFormat).LockModify(); + const_cast<SwFrameFormat&>(rFrameFormat).SetFormatAttr( aHori ); + const_cast<SwFrameFormat&>(rFrameFormat).UnlockModify(); + } + } + } + + // set absolute position at object + const SwTwips nTopOfAnch = GetTopForObjPos( *pAnchorFrameForVertPos, aRectFnSet.FnRect(), aRectFnSet.IsVert() ); + if( aRectFnSet.IsVert() ) + { + // --> OD 2009-08-31 #mongolianlayout# + if ( !aRectFnSet.IsVertL2R() ) + { + GetAnchoredObj().SetObjLeft( nTopOfAnch - + aRelPos.X() - aObjBoundRect.Width() ); + } + else + { + GetAnchoredObj().SetObjLeft( nTopOfAnch + aRelPos.X() ); + } + GetAnchoredObj().SetObjTop( rAnchorTextFrame.getFrameArea().Top() + + aRelPos.Y() ); + } + else + { + GetAnchoredObj().SetObjLeft( rAnchorTextFrame.getFrameArea().Left() + + aRelPos.X() ); + GetAnchoredObj().SetObjTop( nTopOfAnch + aRelPos.Y() ); + } + + // set relative position at object + GetAnchoredObj().SetCurrRelPos( aRelPos ); +} + +void SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFrameForVertPos, + Point& rRelPos, const SwTwips nTopOfAnch) +{ + const SwFrameFormat& rFrameFormat = GetFrameFormat(); + bool bAllowOverlap = rFrameFormat.GetWrapInfluenceOnObjPos().GetAllowOverlap(); + if (bAllowOverlap) + { + return; + } + + if (rFrameFormat.GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH) + { + // This is explicit wrap through: allowed to overlap. + return; + } + + if (SwTextBoxHelper::isTextBox(&rFrameFormat, RES_FLYFRMFMT)) + { + // This is the frame part of a textbox, just take the offset from the textbox's shape part. + SwFrameFormat* pShapeOfTextBox + = SwTextBoxHelper::getOtherTextBoxFormat(&rFrameFormat, RES_FLYFRMFMT); + if (pShapeOfTextBox) + { + SwTwips nYDiff = pShapeOfTextBox->GetWrapInfluenceOnObjPos().GetOverlapVertOffset(); + if (nYDiff > 0) + { + rRelPos.setY(rRelPos.getY() + nYDiff + 1); + GetAnchoredObj().SetObjTop(nTopOfAnch + rRelPos.Y()); + } + } + return; + } + + // Get the list of objects. + auto pSortedObjs = pAnchorFrameForVertPos->GetDrawObjs(); + if (!pSortedObjs) + { + return; + } + + for (const auto& pAnchoredObj : *pSortedObjs) + { + if (pAnchoredObj == &GetAnchoredObj()) + { + // We found ourselves, stop iterating. + break; + } + + if (SwTextBoxHelper::isTextBox(&pAnchoredObj->GetFrameFormat(), RES_FLYFRMFMT)) + { + // Overlapping with the frame of a textbox is fine. + continue; + } + + css::text::WrapTextMode eWrap = pAnchoredObj->GetFrameFormat().GetSurround().GetSurround(); + if (eWrap == css::text::WrapTextMode_THROUGH) + { + // The other object is wrap through: allowed to overlap. + continue; + } + + if (!GetAnchoredObj().GetObjRect().IsOver(pAnchoredObj->GetObjRect())) + { + // Found an already positioned object, but it doesn't overlap, ignore. + continue; + } + + // Already formatted, overlaps: resolve the conflict by shifting ourselves down. + SwTwips nYDiff = pAnchoredObj->GetObjRect().Bottom() - GetAnchoredObj().GetObjRect().Top(); + rRelPos.setY(rRelPos.getY() + nYDiff + 1); + GetAnchoredObj().SetObjTop(nTopOfAnch + rRelPos.Y()); + + // Store our offset that avoids the overlap. If this is a shape of a textbox, then the frame + // of the textbox will use it. + SwFormatWrapInfluenceOnObjPos aInfluence(rFrameFormat.GetWrapInfluenceOnObjPos()); + aInfluence.SetOverlapVertOffset(nYDiff); + const_cast<SwFrameFormat&>(rFrameFormat).LockModify(); + const_cast<SwFrameFormat&>(rFrameFormat).SetFormatAttr(aInfluence); + const_cast<SwFrameFormat&>(rFrameFormat).UnlockModify(); + } +} + +/** + * Determine frame for horizontal position + */ +const SwFrame& SwToContentAnchoredObjectPosition::GetHoriVirtualAnchor( + const SwLayoutFrame& _rProposedFrame ) const +{ + const SwFrame* pHoriVirtAnchFrame = &_rProposedFrame; + + // Search for first lower content frame, which is the anchor or a follow + // of the anchor (Note: <Anchor.IsAnFollow( Anchor )> is true) + // If none found, <_rProposedFrame> is returned. + const SwFrame* pFrame = _rProposedFrame.Lower(); + while ( pFrame ) + { + if ( pFrame->IsContentFrame() && + GetAnchorTextFrame().IsAnFollow( static_cast<const SwContentFrame*>(pFrame) ) ) + { + pHoriVirtAnchFrame = pFrame; + break; + } + pFrame = pFrame->GetNext(); + } + + return *pHoriVirtAnchFrame; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/objectpositioning/tolayoutanchoredobjectposition.cxx b/sw/source/core/objectpositioning/tolayoutanchoredobjectposition.cxx new file mode 100644 index 000000000..19ad5ac61 --- /dev/null +++ b/sw/source/core/objectpositioning/tolayoutanchoredobjectposition.cxx @@ -0,0 +1,226 @@ +/* -*- 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 <tolayoutanchoredobjectposition.hxx> +#include <anchoredobject.hxx> +#include <frame.hxx> +#include <pagefrm.hxx> +#include <svx/svdobj.hxx> +#include <frmfmt.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <frmatr.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <rootfrm.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> + +using namespace objectpositioning; +using namespace ::com::sun::star; + +SwToLayoutAnchoredObjectPosition::SwToLayoutAnchoredObjectPosition( SdrObject& _rDrawObj ) + : SwAnchoredObjectPosition( _rDrawObj ), + maRelPos( Point() ), + // #i26791# + maOffsetToFrameAnchorPos( Point() ) +{} + +SwToLayoutAnchoredObjectPosition::~SwToLayoutAnchoredObjectPosition() +{} + +/** calculate position for object position type TO_LAYOUT */ +void SwToLayoutAnchoredObjectPosition::CalcPosition() +{ + const SwRect aObjBoundRect( GetAnchoredObj().GetObjRect() ); + + SwRectFnSet aRectFnSet(&GetAnchorFrame()); + + const SwFrameFormat& rFrameFormat = GetFrameFormat(); + const SvxLRSpaceItem &rLR = rFrameFormat.GetLRSpace(); + const SvxULSpaceItem &rUL = rFrameFormat.GetULSpace(); + + const bool bFlyAtFly = RndStdIds::FLY_AT_FLY == rFrameFormat.GetAnchor().GetAnchorId(); + + // determine position. + // 'vertical' and 'horizontal' position are calculated separately + Point aRelPos; + + // calculate 'vertical' position + SwFormatVertOrient aVert( rFrameFormat.GetVertOrient() ); + { + // to-frame anchored objects are *only* vertical positioned centered or + // bottom, if its wrap mode is 'through' and its anchor frame has fixed + // size. Otherwise, it's positioned top. + sal_Int16 eVertOrient = aVert.GetVertOrient(); + if ( bFlyAtFly && + ( eVertOrient == text::VertOrientation::CENTER || + eVertOrient == text::VertOrientation::BOTTOM ) && + css::text::WrapTextMode_THROUGH != rFrameFormat.GetSurround().GetSurround() && + !GetAnchorFrame().HasFixSize() ) + { + eVertOrient = text::VertOrientation::TOP; + } + // #i26791# - get vertical offset to frame anchor position. + SwTwips nVertOffsetToFrameAnchorPos( 0 ); + SwTwips nRelPosY = + GetVertRelPos( GetAnchorFrame(), GetAnchorFrame(), eVertOrient, + aVert.GetRelationOrient(), aVert.GetPos(), + rLR, rUL, nVertOffsetToFrameAnchorPos ); + + // keep the calculated relative vertical position - needed for filters + // (including the xml-filter) + { + SwTwips nAttrRelPosY = nRelPosY - nVertOffsetToFrameAnchorPos; + if ( aVert.GetVertOrient() != text::VertOrientation::NONE && + aVert.GetPos() != nAttrRelPosY ) + { + aVert.SetPos( nAttrRelPosY ); + const_cast<SwFrameFormat&>(rFrameFormat).LockModify(); + const_cast<SwFrameFormat&>(rFrameFormat).SetFormatAttr( aVert ); + const_cast<SwFrameFormat&>(rFrameFormat).UnlockModify(); + } + } + + // determine absolute 'vertical' position, depending on layout-direction + // #i26791# - determine offset to 'vertical' frame + // anchor position, depending on layout-direction + if( aRectFnSet.IsVert() ) + { + if ( aRectFnSet.IsVertL2R() ) + aRelPos.setX( nRelPosY ); + else + aRelPos.setX( -nRelPosY - aObjBoundRect.Width() ); + maOffsetToFrameAnchorPos.setX( nVertOffsetToFrameAnchorPos ); + } + else + { + aRelPos.setY( nRelPosY ); + maOffsetToFrameAnchorPos.setY( nVertOffsetToFrameAnchorPos ); + } + + // if in online-layout the bottom of to-page anchored object is beyond + // the page bottom, the page frame has to grow by growing its body frame. + const SwViewShell *pSh = GetAnchorFrame().getRootFrame()->GetCurrShell(); + if ( !bFlyAtFly && GetAnchorFrame().IsPageFrame() && + pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + const long nAnchorBottom = GetAnchorFrame().getFrameArea().Bottom(); + const long nBottom = GetAnchorFrame().getFrameArea().Top() + + aRelPos.Y() + aObjBoundRect.Height(); + if ( nAnchorBottom < nBottom ) + { + static_cast<SwPageFrame&>(GetAnchorFrame()). + FindBodyCont()->Grow( nBottom - nAnchorBottom ); + } + } + } // end of determination of vertical position + + // calculate 'horizontal' position + SwFormatHoriOrient aHori( rFrameFormat.GetHoriOrient() ); + { + // consider toggle of horizontal position for even pages. + const bool bToggle = aHori.IsPosToggle() && + !GetAnchorFrame().FindPageFrame()->OnRightPage(); + sal_Int16 eHoriOrient = aHori.GetHoriOrient(); + sal_Int16 eRelOrient = aHori.GetRelationOrient(); + // toggle orientation + ToggleHoriOrientAndAlign( bToggle, eHoriOrient, eRelOrient ); + + // determine alignment values: + // <nWidth>: 'width' of the alignment area + // <nOffset>: offset of alignment area, relative to 'left' of + // frame anchor position + SwTwips nWidth, nOffset; + { + bool bDummy; // in this context irrelevant output parameter + GetHoriAlignmentValues( GetAnchorFrame(), GetAnchorFrame(), + eRelOrient, false, + nWidth, nOffset, bDummy ); + } + + SwTwips nObjWidth = aRectFnSet.GetWidth(aObjBoundRect); + + // determine relative horizontal position + SwTwips nRelPosX; + if ( text::HoriOrientation::NONE == eHoriOrient ) + { + if( bToggle || + ( !aHori.IsPosToggle() && GetAnchorFrame().IsRightToLeft() ) ) + { + nRelPosX = nWidth - nObjWidth - aHori.GetPos(); + } + else + { + nRelPosX = aHori.GetPos(); + } + } + else if ( text::HoriOrientation::CENTER == eHoriOrient ) + nRelPosX = (nWidth / 2) - (nObjWidth / 2); + else if ( text::HoriOrientation::RIGHT == eHoriOrient ) + nRelPosX = nWidth - ( nObjWidth + + ( aRectFnSet.IsVert() ? rUL.GetLower() : rLR.GetRight() ) ); + else + nRelPosX = aRectFnSet.IsVert() ? rUL.GetUpper() : rLR.GetLeft(); + nRelPosX += nOffset; + + // no 'negative' relative horizontal position + // OD 06.11.2003 #FollowTextFlowAtFrame# - negative positions allow for + // to frame anchored objects. + if ( !bFlyAtFly && nRelPosX < 0 ) + { + nRelPosX = 0; + } + + // determine absolute 'horizontal' position, depending on layout-direction + // #i26791# - determine offset to 'horizontal' frame + // anchor position, depending on layout-direction + if( aRectFnSet.IsVert() || aRectFnSet.IsVertL2R() ) + { + + aRelPos.setY( nRelPosX ); + maOffsetToFrameAnchorPos.setY( nOffset ); + } + else + { + aRelPos.setX( nRelPosX ); + maOffsetToFrameAnchorPos.setX( nOffset ); + } + + // keep the calculated relative horizontal position - needed for filters + // (including the xml-filter) + { + SwTwips nAttrRelPosX = nRelPosX - nOffset; + if ( text::HoriOrientation::NONE != aHori.GetHoriOrient() && + aHori.GetPos() != nAttrRelPosX ) + { + aHori.SetPos( nAttrRelPosX ); + const_cast<SwFrameFormat&>(rFrameFormat).LockModify(); + const_cast<SwFrameFormat&>(rFrameFormat).SetFormatAttr( aHori ); + const_cast<SwFrameFormat&>(rFrameFormat).UnlockModify(); + } + } + } // end of determination of horizontal position + + // keep calculate relative position + maRelPos = aRelPos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/ole/ndole.cxx b/sw/source/core/ole/ndole.cxx new file mode 100644 index 000000000..1927872ed --- /dev/null +++ b/sw/source/core/ole/ndole.cxx @@ -0,0 +1,1246 @@ +/* -*- 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 <com/sun/star/container/XChild.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XEmbedPersist.hpp> +#include <com/sun/star/embed/XLinkageSupport.hpp> +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <cppuhelper/implbase.hxx> + +#include <sot/exchange.hxx> +#include <tools/globname.hxx> +#include <sfx2/linkmgr.hxx> +#include <unotools/configitem.hxx> +#include <vcl/outdev.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <pam.hxx> +#include <section.hxx> +#include <cntfrm.hxx> +#include <ndole.hxx> +#include <viewsh.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <comphelper/classids.hxx> +#include <vcl/graph.hxx> +#include <sot/formats.hxx> +#include <vcl/svapp.hxx> +#include <strings.hrc> +#include <svx/charthelper.hxx> +#include <comphelper/threadpool.hxx> +#include <atomic> +#include <deque> +#include <libxml/xmlwriter.h> + +using namespace utl; +using namespace com::sun::star::uno; +using namespace com::sun::star; + +namespace { + +class SwOLELRUCache + : private utl::ConfigItem +{ +private: + typedef std::deque<SwOLEObj *> OleObjects_t; + OleObjects_t m_OleObjects; + sal_Int32 m_nLRU_InitSize; + static uno::Sequence< OUString > GetPropertyNames(); + + virtual void ImplCommit() override; + +public: + SwOLELRUCache(); + + virtual void Notify( const uno::Sequence< + OUString>& aPropertyNames ) override; + void Load(); + + void InsertObj( SwOLEObj& rObj ); + void RemoveObj( SwOLEObj& rObj ); +}; + +} + +static std::shared_ptr<SwOLELRUCache> g_pOLELRU_Cache; + +class SwOLEListener_Impl : public ::cppu::WeakImplHelper< embed::XStateChangeListener > +{ + SwOLEObj* mpObj; +public: + explicit SwOLEListener_Impl( SwOLEObj* pObj ); + void dispose(); + virtual void SAL_CALL changingState( const lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL stateChanged( const lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL disposing( const lang::EventObject& aEvent ) override; +}; + +SwOLEListener_Impl::SwOLEListener_Impl( SwOLEObj* pObj ) +: mpObj( pObj ) +{ + if ( mpObj->IsOleRef() && mpObj->GetOleRef()->getCurrentState() == embed::EmbedStates::RUNNING ) + { + g_pOLELRU_Cache->InsertObj( *mpObj ); + } +} + +void SAL_CALL SwOLEListener_Impl::changingState( const lang::EventObject&, ::sal_Int32 , ::sal_Int32 ) +{ +} + +void SAL_CALL SwOLEListener_Impl::stateChanged( const lang::EventObject&, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) +{ + if ( mpObj && nOldState == embed::EmbedStates::LOADED && nNewState == embed::EmbedStates::RUNNING ) + { + if (!g_pOLELRU_Cache) + g_pOLELRU_Cache = std::make_shared<SwOLELRUCache>(); + g_pOLELRU_Cache->InsertObj( *mpObj ); + } + else if ( mpObj && nNewState == embed::EmbedStates::LOADED && nOldState == embed::EmbedStates::RUNNING ) + { + if (g_pOLELRU_Cache) + g_pOLELRU_Cache->RemoveObj( *mpObj ); + } + else if(mpObj && nNewState == embed::EmbedStates::RUNNING) + { + mpObj->resetBufferedData(); + } +} + +void SwOLEListener_Impl::dispose() +{ + if (mpObj && g_pOLELRU_Cache) + g_pOLELRU_Cache->RemoveObj( *mpObj ); + mpObj = nullptr; +} + +void SAL_CALL SwOLEListener_Impl::disposing( const lang::EventObject& ) +{ + if (mpObj && g_pOLELRU_Cache) + g_pOLELRU_Cache->RemoveObj( *mpObj ); +} + +// TODO/LATER: actually SwEmbedObjectLink should be used here, but because different objects are used to control +// embedded object different link objects with the same functionality had to be implemented + +class SwEmbedObjectLink : public sfx2::SvBaseLink +{ + SwOLENode* pOleNode; + +public: + explicit SwEmbedObjectLink(SwOLENode* pNode); + + virtual void Closed() override; + virtual ::sfx2::SvBaseLink::UpdateResult DataChanged( + const OUString& rMimeType, const css::uno::Any & rValue ) override; + + void Connect() { GetRealObject(); } +}; + +SwEmbedObjectLink::SwEmbedObjectLink(SwOLENode* pNode): + ::sfx2::SvBaseLink( ::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SVXB ), + pOleNode(pNode) +{ + SetSynchron( false ); +} + +::sfx2::SvBaseLink::UpdateResult SwEmbedObjectLink::DataChanged( + const OUString&, const uno::Any& ) +{ + if ( !pOleNode->UpdateLinkURL_Impl() ) + { + // the link URL was not changed + uno::Reference< embed::XEmbeddedObject > xObject = pOleNode->GetOLEObj().GetOleRef(); + OSL_ENSURE( xObject.is(), "The object must exist always!" ); + if ( xObject.is() ) + { + // let the object reload the link + // TODO/LATER: reload call could be used for this case + + try + { + sal_Int32 nState = xObject->getCurrentState(); + if ( nState != embed::EmbedStates::LOADED ) + { + // in some cases the linked file probably is not locked so it could be changed + xObject->changeState( embed::EmbedStates::LOADED ); + xObject->changeState( nState ); + } + } + catch (const uno::Exception&) + { + } + } + } + + pOleNode->GetNewReplacement(); + pOleNode->SetChanged(); + + return SUCCESS; +} + +void SwEmbedObjectLink::Closed() +{ + pOleNode->BreakFileLink_Impl(); + SvBaseLink::Closed(); +} + +SwOLENode::SwOLENode( const SwNodeIndex &rWhere, + const svt::EmbeddedObjectRef& xObj, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwNoTextNode( rWhere, SwNodeType::Ole, pGrfColl, pAutoAttr ), + maOLEObj( xObj ), + mbOLESizeInvalid( false ), + mpObjectLink( nullptr ) +{ + maOLEObj.SetNode( this ); +} + +SwOLENode::SwOLENode( const SwNodeIndex &rWhere, + const OUString &rString, + sal_Int64 nAspect, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwNoTextNode( rWhere, SwNodeType::Ole, pGrfColl, pAutoAttr ), + maOLEObj( rString, nAspect ), + mbOLESizeInvalid( false ), + mpObjectLink( nullptr ) +{ + maOLEObj.SetNode( this ); +} + +SwOLENode::~SwOLENode() +{ + DisconnectFileLink_Impl(); +} + +const Graphic* SwOLENode::GetGraphic() +{ + if ( maOLEObj.GetOleRef().is() ) + return maOLEObj.m_xOLERef.GetGraphic(); + return nullptr; +} + +/** + * Loading an OLE object that has been moved to the Undo Area + */ +bool SwOLENode::RestorePersistentData() +{ + OSL_ENSURE( maOLEObj.GetOleRef().is(), "No object to restore!" ); + if ( maOLEObj.m_xOLERef.is() ) + { + // If a SvPersist instance already exists, we use it + SfxObjectShell* p = GetDoc()->GetPersist(); + if( !p ) + { + // TODO/LATER: Isn't an EmbeddedObjectContainer sufficient here? + // What happens to this document? + OSL_ENSURE( false, "Why are we creating a DocShell here?" ); + p = new SwDocShell( GetDoc(), SfxObjectCreateMode::INTERNAL ); + p->DoInitNew(); + } + + uno::Reference < container::XChild > xChild( maOLEObj.m_xOLERef.GetObject(), uno::UNO_QUERY ); + if ( xChild.is() ) + xChild->setParent( p->GetModel() ); + + OSL_ENSURE( !maOLEObj.m_aName.isEmpty(), "No object name!" ); + OUString aObjName; + if ( !p->GetEmbeddedObjectContainer().InsertEmbeddedObject( maOLEObj.m_xOLERef.GetObject(), aObjName ) ) + { + if ( xChild.is() ) + xChild->setParent( nullptr ); + OSL_FAIL( "InsertObject failed" ); + } + else + { + maOLEObj.m_aName = aObjName; + maOLEObj.m_xOLERef.AssignToContainer( &p->GetEmbeddedObjectContainer(), aObjName ); + CheckFileLink_Impl(); + } + } + + return true; +} + +void SwOLENode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwOLENode")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(GetIndex()).getStr())); + + GetOLEObj().dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +/** + * OLE object is transported into UNDO area + */ +bool SwOLENode::SavePersistentData() +{ + if( maOLEObj.m_xOLERef.is() ) + { + comphelper::EmbeddedObjectContainer* pCnt = maOLEObj.m_xOLERef.GetContainer(); + +#if OSL_DEBUG_LEVEL > 0 + SfxObjectShell* p = GetDoc()->GetPersist(); + OSL_ENSURE( p, "No document!" ); + if( p ) + { + comphelper::EmbeddedObjectContainer& rCnt = p->GetEmbeddedObjectContainer(); + OSL_ENSURE( !pCnt || &rCnt == pCnt, "The helper is assigned to unexpected container!" ); + } +#endif + + if ( pCnt && pCnt->HasEmbeddedObject( maOLEObj.m_aName ) ) + { + uno::Reference < container::XChild > xChild( maOLEObj.m_xOLERef.GetObject(), uno::UNO_QUERY ); + if ( xChild.is() ) + xChild->setParent( nullptr ); + + /* + #i119941 + When cut or move the chart, SwUndoFlyBase::DelFly will call SaveSection + to store the content to storage. In this step, chart filter functions + will be called. And chart filter will call chart core functions to create + the chart again. Then chart core function will call the class + ExplicitCategoryProvider to create data source. In this step, when SW data + source provider create the data source, a UnoActionRemoveContext + will mess with the layout and create a new SwFlyFrame. + But later in SwUndoFlyBase::DelFly, it will clear anchor related attributes + of SwFlyFrame. Then finally null pointer occur. + Resolution: + In pCnt->RemoveEmbeddedObject in SaveSection process of table chart, + only remove the object from the object container, without removing it's + storage and graphic stream. The chart already removed from formatter. + */ + bool bKeepObjectToTempStorage = true; + uno::Reference < embed::XEmbeddedObject > xIP = GetOLEObj().GetOleRef(); + if (IsChart() && !msChartTableName.isEmpty() + && svt::EmbeddedObjectRef::TryRunningState(xIP)) + { + uno::Reference< chart2::XChartDocument > xChart( xIP->getComponent(), UNO_QUERY ); + if (xChart.is() && !xChart->hasInternalDataProvider()) + { + bKeepObjectToTempStorage = false; + } + } + + pCnt->RemoveEmbeddedObject( maOLEObj.m_aName, bKeepObjectToTempStorage ); + + // TODO/LATER: aOLEObj.aName has no meaning here, since the undo container contains the object + // by different name, in future it might makes sense that the name is transported here. + maOLEObj.m_xOLERef.AssignToContainer( nullptr, maOLEObj.m_aName ); + try + { + // "unload" object + maOLEObj.m_xOLERef->changeState( embed::EmbedStates::LOADED ); + } + catch (const uno::Exception&) + { + } + } + } + + DisconnectFileLink_Impl(); + + return true; +} + +SwOLENode * SwNodes::MakeOLENode( const SwNodeIndex & rWhere, + const svt::EmbeddedObjectRef& xObj, + SwGrfFormatColl* pGrfColl ) +{ + OSL_ENSURE( pGrfColl,"SwNodes::MakeOLENode: Formatpointer is 0." ); + + SwOLENode *pNode = + new SwOLENode( rWhere, xObj, pGrfColl, nullptr ); + + // set parent if XChild is supported + //!! needed to supply Math objects with a valid reference device + uno::Reference< container::XChild > xChild( pNode->GetOLEObj().GetObject().GetObject(), UNO_QUERY ); + if (xChild.is()) + { + SwDocShell *pDocSh = GetDoc()->GetDocShell(); + if (pDocSh) + xChild->setParent( pDocSh->GetModel() ); + } + + return pNode; +} + +SwOLENode * SwNodes::MakeOLENode( const SwNodeIndex & rWhere, + const OUString &rName, sal_Int64 nAspect, SwGrfFormatColl* pGrfColl, SwAttrSet const * pAutoAttr ) +{ + OSL_ENSURE( pGrfColl,"SwNodes::MakeOLENode: Formatpointer is 0." ); + + SwOLENode *pNode = + new SwOLENode( rWhere, rName, nAspect, pGrfColl, pAutoAttr ); + + // set parent if XChild is supported + //!! needed to supply Math objects with a valid reference device + uno::Reference< container::XChild > xChild( pNode->GetOLEObj().GetObject().GetObject(), UNO_QUERY ); + if (xChild.is()) + { + SwDocShell *pDocSh= GetDoc()->GetDocShell(); + if (pDocSh) + xChild->setParent( pDocSh->GetModel() ); + } + + return pNode; +} + +Size SwOLENode::GetTwipSize() const +{ + MapMode aMapMode( MapUnit::MapTwip ); + return const_cast<SwOLENode*>(this)->maOLEObj.GetObject().GetSize( &aMapMode ); +} + +SwContentNode* SwOLENode::MakeCopy( SwDoc* pDoc, const SwNodeIndex& rIdx, bool) const +{ + // If there's already a SvPersist instance, we use it + SfxObjectShell* pPersistShell = pDoc->GetPersist(); + if( !pPersistShell ) + { + // TODO/LATER: is EmbeddedObjectContainer not enough? + // the created document will be closed by pDoc ( should use SfxObjectShellLock ) + pPersistShell = new SwDocShell( pDoc, SfxObjectCreateMode::INTERNAL ); + pDoc->SetTmpDocShell( pPersistShell ); + pPersistShell->DoInitNew(); + } + + // We insert it at SvPersist level + // TODO/LATER: check if using the same naming scheme for all apps works here + OUString aNewName/*( Sw3Io::UniqueName( p->GetStorage(), "Obj" ) )*/; + SfxObjectShell* pSrc = GetDoc()->GetPersist(); + + pPersistShell->GetEmbeddedObjectContainer().CopyAndGetEmbeddedObject( + pSrc->GetEmbeddedObjectContainer(), + pSrc->GetEmbeddedObjectContainer().GetEmbeddedObject( maOLEObj.m_aName ), + aNewName, + pSrc->getDocumentBaseURL(), + pPersistShell->getDocumentBaseURL()); + + SwOLENode* pOLENd = pDoc->GetNodes().MakeOLENode( rIdx, aNewName, GetAspect(), + pDoc->GetDfltGrfFormatColl(), + GetpSwAttrSet() ); + + pOLENd->SetChartTableName( GetChartTableName() ); + pOLENd->SetTitle( GetTitle() ); + pOLENd->SetDescription( GetDescription() ); + pOLENd->SetContour( HasContour(), HasAutomaticContour() ); + pOLENd->SetAspect( GetAspect() ); // the replacement image must be already copied + + pOLENd->SetOLESizeInvalid( true ); + pDoc->SetOLEPrtNotifyPending(); + + return pOLENd; +} + +bool SwOLENode::IsInGlobalDocSection() const +{ + // Find the "Body Anchor" + sal_uLong nEndExtraIdx = GetNodes().GetEndOfExtras().GetIndex(); + const SwNode* pAnchorNd = this; + do { + SwFrameFormat* pFlyFormat = pAnchorNd->GetFlyFormat(); + if( !pFlyFormat ) + return false; + + const SwFormatAnchor& rAnchor = pFlyFormat->GetAnchor(); + if( !rAnchor.GetContentAnchor() ) + return false; + + pAnchorNd = &rAnchor.GetContentAnchor()->nNode.GetNode(); + } while( pAnchorNd->GetIndex() < nEndExtraIdx ); + + const SwSectionNode* pSectNd = pAnchorNd->FindSectionNode(); + if( !pSectNd ) + return false; + + while( pSectNd ) + { + pAnchorNd = pSectNd; + pSectNd = pAnchorNd->StartOfSectionNode()->FindSectionNode(); + } + + // pAnchorNd contains the most recently found Section Node, which + // now must fulfill the prerequisites for the GlobalDoc + pSectNd = static_cast<const SwSectionNode*>(pAnchorNd); + return SectionType::FileLink == pSectNd->GetSection().GetType() && + pSectNd->GetIndex() > nEndExtraIdx; +} + +bool SwOLENode::IsOLEObjectDeleted() const +{ + if( maOLEObj.m_xOLERef.is() ) + { + SfxObjectShell* p = GetDoc()->GetPersist(); + if( p ) // Must be there + { + return !p->GetEmbeddedObjectContainer().HasEmbeddedObject( maOLEObj.m_aName ); + } + } + return false; +} + +void SwOLENode::GetNewReplacement() +{ + if ( maOLEObj.m_xOLERef.is() ) + maOLEObj.m_xOLERef.UpdateReplacement(); +} + +bool SwOLENode::UpdateLinkURL_Impl() +{ + bool bResult = false; + + if ( mpObjectLink ) + { + OUString aNewLinkURL; + sfx2::LinkManager::GetDisplayNames( mpObjectLink, nullptr, &aNewLinkURL ); + if ( !aNewLinkURL.equalsIgnoreAsciiCase( maLinkURL ) ) + { + if ( !maOLEObj.m_xOLERef.is() ) + maOLEObj.GetOleRef(); + + uno::Reference< embed::XEmbeddedObject > xObj = maOLEObj.m_xOLERef.GetObject(); + uno::Reference< embed::XCommonEmbedPersist > xPersObj( xObj, uno::UNO_QUERY ); + OSL_ENSURE( xPersObj.is(), "The object must exist!" ); + if ( xPersObj.is() ) + { + try + { + sal_Int32 nCurState = xObj->getCurrentState(); + if ( nCurState != embed::EmbedStates::LOADED ) + xObj->changeState( embed::EmbedStates::LOADED ); + + // TODO/LATER: there should be possible to get current mediadescriptor settings from the object + uno::Sequence< beans::PropertyValue > aArgs( 1 ); + aArgs[0].Name = "URL"; + aArgs[0].Value <<= aNewLinkURL; + xPersObj->reload( aArgs, uno::Sequence< beans::PropertyValue >() ); + + maLinkURL = aNewLinkURL; + bResult = true; + + if ( nCurState != embed::EmbedStates::LOADED ) + xObj->changeState( nCurState ); + } + catch (const uno::Exception&) + { + } + } + + if ( !bResult ) + { + // TODO/LATER: return the old name to the link manager, is it possible? + } + } + } + + return bResult; +} + +void SwOLENode::BreakFileLink_Impl() +{ + SfxObjectShell* pPers = GetDoc()->GetPersist(); + + if ( pPers ) + { + uno::Reference< embed::XStorage > xStorage = pPers->GetStorage(); + if ( xStorage.is() ) + { + try + { + uno::Reference< embed::XLinkageSupport > xLinkSupport( maOLEObj.GetOleRef(), uno::UNO_QUERY_THROW ); + xLinkSupport->breakLink( xStorage, maOLEObj.GetCurrentPersistName() ); + DisconnectFileLink_Impl(); + maLinkURL.clear(); + } + catch( uno::Exception& ) + { + } + } + } +} + +void SwOLENode::DisconnectFileLink_Impl() +{ + if ( mpObjectLink ) + { + GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( mpObjectLink ); + mpObjectLink = nullptr; + } +} + +void SwOLENode::CheckFileLink_Impl() +{ + if ( maOLEObj.m_xOLERef.GetObject().is() && !mpObjectLink ) + { + try + { + uno::Reference< embed::XLinkageSupport > xLinkSupport( maOLEObj.m_xOLERef.GetObject(), uno::UNO_QUERY_THROW ); + if ( xLinkSupport->isLink() ) + { + const OUString aLinkURL = xLinkSupport->getLinkURL(); + if ( !aLinkURL.isEmpty() ) + { + // this is a file link so the model link manager should handle it + mpObjectLink = new SwEmbedObjectLink( this ); + maLinkURL = aLinkURL; + GetDoc()->getIDocumentLinksAdministration().GetLinkManager().InsertFileLink( *mpObjectLink, sfx2::SvBaseLinkObjectType::ClientOle, aLinkURL ); + mpObjectLink->Connect(); + } + } + } + catch( uno::Exception& ) + { + } + } +} + +// #i99665# +bool SwOLENode::IsChart() const +{ + bool bIsChart( false ); + + const uno::Reference< embed::XEmbeddedObject > xEmbObj = + const_cast<SwOLEObj&>(GetOLEObj()).GetOleRef(); + if ( xEmbObj.is() ) + { + SvGlobalName aClassID( xEmbObj->getClassID() ); + bIsChart = SotExchange::IsChart( aClassID ); + } + + return bIsChart; +} + +// react on visual change (invalidate) +void SwOLENode::SetChanged() +{ + SwFrame* pFrame(getLayoutFrame(nullptr)); + + if(nullptr == pFrame) + { + return; + } + + const SwRect aFrameArea(pFrame->getFrameArea()); + SwViewShell* pVSh(GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()); + + if(nullptr == pVSh) + { + return; + } + + for(SwViewShell& rShell : pVSh->GetRingContainer()) + { + SET_CURR_SHELL(&rShell); + + if(rShell.VisArea().IsOver(aFrameArea) && OUTDEV_WINDOW == rShell.GetOut()->GetOutDevType()) + { + // invalidate instead of painting + rShell.GetWin()->Invalidate(aFrameArea.SVRect()); + } + } +} + +namespace { class DeflateThread; } + +/// Holder for local data for a parallel-executed task to load a chart model +class DeflateData +{ +private: + friend DeflateThread; + friend class SwOLEObj; + + uno::Reference< frame::XModel > maXModel; + drawinglayer::primitive2d::Primitive2DContainer maPrimitive2DSequence; + basegfx::B2DRange maRange; + + // evtl.set from the SwOLEObj destructor when a WorkerThread is still active + // since it is not possible to kill it - let it terminate and delete the + // data working on itself + std::atomic< bool> mbKilled; + + std::shared_ptr<comphelper::ThreadTaskTag> mpTag; + +public: + explicit DeflateData(const uno::Reference< frame::XModel >& rXModel) + : maXModel(rXModel), + maPrimitive2DSequence(), + maRange(), + mbKilled(false), + mpTag( comphelper::ThreadPool::createThreadTaskTag() ) + { + } + + const drawinglayer::primitive2d::Primitive2DContainer& getSequence() const + { + return maPrimitive2DSequence; + } + + const basegfx::B2DRange& getRange() const + { + return maRange; + } + + bool isFinished() const + { + return comphelper::ThreadPool::isTaskTagDone(mpTag); + } + + void waitFinished() + { + // need to wait until the load in progress is finished. + // WorkerThreads need the SolarMutex to be able to continue + // and finish the running import. + SolarMutexReleaser aReleaser; + comphelper::ThreadPool::getSharedOptimalPool().waitUntilDone(mpTag); + } +}; + +namespace { + +/// Task for parallelly-executed task to load a chart model +class DeflateThread : public comphelper::ThreadTask +{ + // the data to work on + DeflateData& mrDeflateData; + +public: + explicit DeflateThread(DeflateData& rDeflateData) + : comphelper::ThreadTask(rDeflateData.mpTag), mrDeflateData(rDeflateData) + { + } + +private: + virtual void doWork() override + { + try + { + // load the chart data and get the primitives + mrDeflateData.maPrimitive2DSequence = ChartHelper::tryToGetChartContentAsPrimitive2DSequence( + mrDeflateData.maXModel, + mrDeflateData.maRange); + + // model no longer needed and done + mrDeflateData.maXModel.clear(); + } + catch (const uno::Exception&) + { + } + + if(mrDeflateData.mbKilled) + { + // need to cleanup myself - data will not be used + delete &mrDeflateData; + } + } +}; + +} + +////////////////////////////////////////////////////////////////////////////// + +SwOLEObj::SwOLEObj( const svt::EmbeddedObjectRef& xObj ) : + m_pOLENode( nullptr ), + m_xOLERef( xObj ), + m_aPrimitive2DSequence(), + m_aRange() +{ + m_xOLERef.Lock(); + if ( xObj.is() ) + { + m_xListener = new SwOLEListener_Impl( this ); + xObj->addStateChangeListener( m_xListener.get() ); + } +} + +SwOLEObj::SwOLEObj( const OUString &rString, sal_Int64 nAspect ) : + m_pOLENode( nullptr ), + m_aName( rString ), + m_aPrimitive2DSequence(), + m_aRange() +{ + m_xOLERef.Lock(); + m_xOLERef.SetViewAspect( nAspect ); +} + +SwOLEObj::~SwOLEObj() COVERITY_NOEXCEPT_FALSE +{ + if(m_pDeflateData) + { + // set flag so that the worker thread will delete m_pDeflateData + // when finished and forget about it + m_pDeflateData->mbKilled = true; + m_pDeflateData = nullptr; + } + + if( m_xListener ) + { + if ( m_xOLERef.is() ) + m_xOLERef->removeStateChangeListener( m_xListener.get() ); + m_xListener->dispose(); + m_xListener.clear(); + } + + if( m_pOLENode && !m_pOLENode->GetDoc()->IsInDtor() ) + { + // if the model is not currently in destruction it means that this object should be removed from the model + comphelper::EmbeddedObjectContainer* pCnt = m_xOLERef.GetContainer(); + +#if OSL_DEBUG_LEVEL > 0 + SfxObjectShell* p = m_pOLENode->GetDoc()->GetPersist(); + OSL_ENSURE( p, "No document!" ); + if( p ) + { + comphelper::EmbeddedObjectContainer& rCnt = p->GetEmbeddedObjectContainer(); + OSL_ENSURE( !pCnt || &rCnt == pCnt, "The helper is assigned to unexpected container!" ); + } +#endif + + if ( pCnt && pCnt->HasEmbeddedObject( m_aName ) ) + { + uno::Reference < container::XChild > xChild( m_xOLERef.GetObject(), uno::UNO_QUERY ); + if ( xChild.is() ) + xChild->setParent( nullptr ); + + // not already removed by deleting the object + m_xOLERef.AssignToContainer( nullptr, m_aName ); + + // unlock object so that object can be closed in RemoveEmbeddedObject + // successful closing of the object will automatically clear the reference then + m_xOLERef.Lock(false); + + // Always remove object from container it is connected to + try + { + // remove object from container but don't close it + pCnt->RemoveEmbeddedObject( m_aName ); + } + catch ( uno::Exception& ) + { + } + } + + } + + if ( m_xOLERef.is() ) + // in case the object wasn't closed: release it + // in case the object was not in the container: it's still locked, try to close + m_xOLERef.Clear(); +} + +void SwOLEObj::SetNode( SwOLENode* pNode ) +{ + m_pOLENode = pNode; + if ( m_aName.isEmpty() ) + { + SwDoc* pDoc = pNode->GetDoc(); + + // If there's already a SvPersist instance, we use it + SfxObjectShell* p = pDoc->GetPersist(); + if( !p ) + { + // TODO/LATER: Isn't an EmbeddedObjectContainer sufficient here? + // What happens to the document? + OSL_ENSURE( false, "Why are we creating a DocShell here??" ); + p = new SwDocShell( pDoc, SfxObjectCreateMode::INTERNAL ); + p->DoInitNew(); + } + + OUString aObjName; + uno::Reference < container::XChild > xChild( m_xOLERef.GetObject(), uno::UNO_QUERY ); + if ( xChild.is() && xChild->getParent() != p->GetModel() ) + // it is possible that the parent was set already + xChild->setParent( p->GetModel() ); + if (!p->GetEmbeddedObjectContainer().InsertEmbeddedObject( m_xOLERef.GetObject(), aObjName ) ) + { + OSL_FAIL( "InsertObject failed" ); + if ( xChild.is() ) + xChild->setParent( nullptr ); + } + else + m_xOLERef.AssignToContainer( &p->GetEmbeddedObjectContainer(), aObjName ); + + const_cast<SwOLENode*>(m_pOLENode)->CheckFileLink_Impl(); // for this notification nonconst access is required + + m_aName = aObjName; + } +} + +OUString SwOLEObj::GetStyleString() +{ + OUString strStyle; + if (m_xOLERef.is() && m_xOLERef.IsChart()) + strStyle = m_xOLERef.GetChartType(); + return strStyle; +} + +bool SwOLEObj::IsOleRef() const +{ + return m_xOLERef.is(); +} + +uno::Reference < embed::XEmbeddedObject > const & SwOLEObj::GetOleRef() +{ + if( !m_xOLERef.is() ) + { + SfxObjectShell* p = m_pOLENode->GetDoc()->GetPersist(); + assert(p && "No SvPersist present"); + + OUString sDocumentBaseURL = p->getDocumentBaseURL(); + uno::Reference < embed::XEmbeddedObject > xObj = p->GetEmbeddedObjectContainer().GetEmbeddedObject(m_aName, &sDocumentBaseURL); + OSL_ENSURE( !m_xOLERef.is(), "Calling GetOleRef() recursively is not permitted" ); + + if ( !xObj.is() ) + { + // We could not load this part (probably broken) + tools::Rectangle aArea; + SwFrame *pFrame = m_pOLENode->getLayoutFrame(nullptr); + if ( pFrame ) + { + Size aSz( pFrame->getFrameArea().SSize() ); + const MapMode aSrc ( MapUnit::MapTwip ); + const MapMode aDest( MapUnit::Map100thMM ); + aSz = OutputDevice::LogicToLogic( aSz, aSrc, aDest ); + aArea.SetSize( aSz ); + } + else + aArea.SetSize( Size( 5000, 5000 ) ); + // TODO/LATER: set replacement graphic for dead object + // It looks as if it should work even without the object, because the replace will be generated automatically + OUString aTmpName; + xObj = p->GetEmbeddedObjectContainer().CreateEmbeddedObject( SvGlobalName( SO3_DUMMY_CLASSID ).GetByteSequence(), aTmpName ); + } + if (xObj.is()) + { + m_xOLERef.Assign( xObj, m_xOLERef.GetViewAspect() ); + m_xOLERef.AssignToContainer( &p->GetEmbeddedObjectContainer(), m_aName ); + m_xListener = new SwOLEListener_Impl( this ); + xObj->addStateChangeListener( m_xListener.get() ); + } + + const_cast<SwOLENode*>(m_pOLENode)->CheckFileLink_Impl(); // for this notification nonconst access is required + } + else if ( m_xOLERef->getCurrentState() == embed::EmbedStates::RUNNING ) + { + // move object to first position in cache + if (!g_pOLELRU_Cache) + g_pOLELRU_Cache = std::make_shared<SwOLELRUCache>(); + g_pOLELRU_Cache->InsertObj( *this ); + } + + return m_xOLERef.GetObject(); +} + +svt::EmbeddedObjectRef& SwOLEObj::GetObject() +{ + GetOleRef(); + return m_xOLERef; +} + +bool SwOLEObj::UnloadObject() +{ + bool bRet = true; + if ( m_pOLENode ) + { + const SwDoc* pDoc = m_pOLENode->GetDoc(); + bRet = UnloadObject( m_xOLERef.GetObject(), pDoc, m_xOLERef.GetViewAspect() ); + } + + return bRet; +} + +PurgeGuard::PurgeGuard(const SwDoc& rDoc) + : m_rManager(const_cast<SwDoc&>(rDoc).GetDocumentSettingManager()) + , m_bOrigPurgeOle(m_rManager.get(DocumentSettingId::PURGE_OLE)) +{ + m_rManager.set(DocumentSettingId::PURGE_OLE, false); +} + +PurgeGuard::~PurgeGuard() +{ + m_rManager.set(DocumentSettingId::PURGE_OLE, m_bOrigPurgeOle); +} + +bool SwOLEObj::UnloadObject( uno::Reference< embed::XEmbeddedObject > const & xObj, const SwDoc* pDoc, sal_Int64 nAspect ) +{ + if ( !pDoc ) + return false; + + bool bRet = true; + sal_Int32 nState = xObj.is() ? xObj->getCurrentState() : embed::EmbedStates::LOADED; + bool bIsActive = ( nState != embed::EmbedStates::LOADED && nState != embed::EmbedStates::RUNNING ); + sal_Int64 nMiscStatus = xObj->getStatus( nAspect ); + + if( nState != embed::EmbedStates::LOADED && !pDoc->IsInDtor() && !bIsActive && + embed::EmbedMisc::MS_EMBED_ALWAYSRUN != ( nMiscStatus & embed::EmbedMisc::MS_EMBED_ALWAYSRUN ) && + embed::EmbedMisc::EMBED_ACTIVATEIMMEDIATELY != ( nMiscStatus & embed::EmbedMisc::EMBED_ACTIVATEIMMEDIATELY ) ) + { + SfxObjectShell* p = pDoc->GetPersist(); + if( p ) + { + if( pDoc->GetDocumentSettingManager().get(DocumentSettingId::PURGE_OLE) ) + { + try + { + uno::Reference < util::XModifiable > xMod( xObj->getComponent(), uno::UNO_QUERY ); + if( xMod.is() && xMod->isModified() ) + { + uno::Reference < embed::XEmbedPersist > xPers( xObj, uno::UNO_QUERY ); + assert(xPers.is() && "Modified object without persistence in cache!"); + + PurgeGuard aGuard(*pDoc); + xPers->storeOwn(); + } + + // setting object to loaded state will remove it from cache + xObj->changeState( embed::EmbedStates::LOADED ); + } + catch (const uno::Exception&) + { + bRet = false; + } + } + else + bRet = false; + } + } + + return bRet; +} + +OUString SwOLEObj::GetDescription() +{ + uno::Reference< embed::XEmbeddedObject > xEmbObj = GetOleRef(); + if ( !xEmbObj.is() ) + return OUString(); + + SvGlobalName aClassID( xEmbObj->getClassID() ); + if ( SotExchange::IsMath( aClassID ) ) + return SwResId(STR_MATH_FORMULA); + + if ( SotExchange::IsChart( aClassID ) ) + return SwResId(STR_CHART); + + return SwResId(STR_OLE); +} + +drawinglayer::primitive2d::Primitive2DContainer const & SwOLEObj::tryToGetChartContentAsPrimitive2DSequence( + basegfx::B2DRange& rRange, + bool bSynchron) +{ + if(m_pDeflateData) + { + if(bSynchron) + { + // data in high quality is requested, wait until the data is available + // since a WorkerThread was already started to load it + m_pDeflateData->waitFinished(); + } + + if(m_pDeflateData->isFinished()) + { + // copy the result data and cleanup + m_aPrimitive2DSequence = m_pDeflateData->getSequence(); + m_aRange = m_pDeflateData->getRange(); + m_pDeflateData.reset(); + } + } + + if(m_aPrimitive2DSequence.empty() && m_aRange.isEmpty() && m_xOLERef.is() && m_xOLERef.IsChart()) + { + const uno::Reference< frame::XModel > aXModel(m_xOLERef->getComponent(), uno::UNO_QUERY); + + if(aXModel.is()) + { + // disabled for now, need to check deeper + static bool bAsynchronousLoadingAllowed = false; // loplugin:constvars:ignore + + if(bSynchron || + !bAsynchronousLoadingAllowed) + { + // load chart synchron in this Thread + m_aPrimitive2DSequence = ChartHelper::tryToGetChartContentAsPrimitive2DSequence( + aXModel, + m_aRange); + } + else + { + // if not yet setup, initiate and start a WorkerThread to load the chart + // and it's primitives asynchron. If it already works, returning nothing + // is okay (preview will be reused) + if(!m_pDeflateData) + { + m_pDeflateData.reset( new DeflateData(aXModel) ); + std::unique_ptr<DeflateThread> pNew( new DeflateThread(*m_pDeflateData) ); + comphelper::ThreadPool::getSharedOptimalPool().pushTask(std::move(pNew)); + } + } + } + } + + if(!m_aPrimitive2DSequence.empty() && !m_aRange.isEmpty()) + { + // when we have data, also copy the buffered Range data as output + rRange = m_aRange; + } + + return m_aPrimitive2DSequence; +} + +void SwOLEObj::resetBufferedData() +{ + m_aPrimitive2DSequence = drawinglayer::primitive2d::Primitive2DContainer(); + m_aRange.reset(); + + if(m_pDeflateData) + { + // load is in progress, wait until finished and cleanup without using it + m_pDeflateData->waitFinished(); + m_pDeflateData.reset(); + } +} + +void SwOLEObj::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwOLEObj")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_xOLERef")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), + BAD_CAST(typeid(*m_xOLERef.GetObject()).name())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +SwOLELRUCache::SwOLELRUCache() + : utl::ConfigItem("Office.Common/Cache") + , m_nLRU_InitSize( 20 ) +{ + EnableNotification( GetPropertyNames() ); + Load(); +} + +uno::Sequence< OUString > SwOLELRUCache::GetPropertyNames() +{ + Sequence< OUString > aNames { "Writer/OLE_Objects" }; + return aNames; +} + +void SwOLELRUCache::Notify( const uno::Sequence< OUString>& ) +{ + Load(); +} + +void SwOLELRUCache::ImplCommit() +{ +} + +void SwOLELRUCache::Load() +{ + Sequence< OUString > aNames( GetPropertyNames() ); + Sequence< Any > aValues = GetProperties( aNames ); + const Any* pValues = aValues.getConstArray(); + OSL_ENSURE( aValues.getLength() == aNames.getLength(), "GetProperties failed" ); + if (aValues.getLength() != aNames.getLength() || !pValues->hasValue()) + return; + + sal_Int32 nVal = 0; + *pValues >>= nVal; + + if (nVal < m_nLRU_InitSize) + { + std::shared_ptr<SwOLELRUCache> xKeepAlive(g_pOLELRU_Cache); // prevent delete this + // size of cache has been changed + sal_Int32 nCount = m_OleObjects.size(); + sal_Int32 nPos = nCount; + + // try to remove the last entries until new maximum size is reached + while( nCount > nVal ) + { + SwOLEObj *const pObj = m_OleObjects[ --nPos ]; + if ( pObj->UnloadObject() ) + nCount--; + if ( !nPos ) + break; + } + } + + m_nLRU_InitSize = nVal; +} + +void SwOLELRUCache::InsertObj( SwOLEObj& rObj ) +{ + SwOLEObj* pObj = &rObj; + OleObjects_t::iterator it = + std::find(m_OleObjects.begin(), m_OleObjects.end(), pObj); + if (it != m_OleObjects.end() && it != m_OleObjects.begin()) + { + // object in cache but is currently not the first in cache + m_OleObjects.erase(it); + it = m_OleObjects.end(); + } + if (it == m_OleObjects.end()) + { + std::shared_ptr<SwOLELRUCache> xKeepAlive(g_pOLELRU_Cache); // prevent delete this + // try to remove objects if necessary + sal_Int32 nCount = m_OleObjects.size(); + sal_Int32 nPos = nCount-1; + while (nPos >= 0 && nCount >= m_nLRU_InitSize) + { + pObj = m_OleObjects[ nPos-- ]; + if ( pObj->UnloadObject() ) + nCount--; + } + m_OleObjects.push_front(&rObj); + } +} + +void SwOLELRUCache::RemoveObj( SwOLEObj& rObj ) +{ + OleObjects_t::iterator const it = + std::find(m_OleObjects.begin(), m_OleObjects.end(), &rObj); + if (it != m_OleObjects.end()) + { + m_OleObjects.erase(it); + } + if (m_OleObjects.empty()) + { + if (g_pOLELRU_Cache.use_count() == 1) // test that we're not in InsertObj() + { + g_pOLELRU_Cache.reset(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/para/paratr.cxx b/sw/source/core/para/paratr.cxx new file mode 100644 index 000000000..e12a71805 --- /dev/null +++ b/sw/source/core/para/paratr.cxx @@ -0,0 +1,236 @@ +/* -*- 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 <hintids.hxx> +#include <unomid.h> +#include <com/sun/star/style/DropCapFormat.hpp> +#include <o3tl/any.hxx> +#include <SwStyleNameMapper.hxx> +#include <paratr.hxx> +#include <charfmt.hxx> +#include <libxml/xmlwriter.h> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + + +SfxPoolItem* SwFormatDrop::CreateDefault() { return new SwFormatDrop; } +SfxPoolItem* SwRegisterItem::CreateDefault() { return new SwRegisterItem; } +SfxPoolItem* SwNumRuleItem::CreateDefault() { return new SwNumRuleItem; } + +SwFormatDrop::SwFormatDrop() + : SfxPoolItem( RES_PARATR_DROP ), + SwClient( nullptr ), + m_pDefinedIn( nullptr ), + m_nDistance( 0 ), + m_nLines( 0 ), + m_nChars( 0 ), + m_bWholeWord( false ) +{ +} + +SwFormatDrop::SwFormatDrop( const SwFormatDrop &rCpy ) + : SfxPoolItem( RES_PARATR_DROP ), + SwClient( rCpy.GetRegisteredInNonConst() ), + m_pDefinedIn( nullptr ), + m_nDistance( rCpy.GetDistance() ), + m_nLines( rCpy.GetLines() ), + m_nChars( rCpy.GetChars() ), + m_bWholeWord( rCpy.GetWholeWord() ) +{ +} + +SwFormatDrop::~SwFormatDrop() +{ +} + +void SwFormatDrop::SetCharFormat( SwCharFormat *pNew ) +{ + assert(!pNew || !pNew->IsDefault()); // expose cases that lead to use-after-free + // Rewire + EndListeningAll(); + if(pNew) + pNew->Add( this ); +} + +void SwFormatDrop::Modify( const SfxPoolItem*, const SfxPoolItem * ) +{ + if( m_pDefinedIn ) + { + if( dynamic_cast< const SwFormat *>( m_pDefinedIn ) == nullptr) + m_pDefinedIn->ModifyNotification( this, this ); + else if( m_pDefinedIn->HasWriterListeners() && + !m_pDefinedIn->IsModifyLocked() ) + { + // Notify those who are dependent on the format on our own. + // The format itself wouldn't pass on the notify as it does not get past the check. + m_pDefinedIn->ModifyBroadcast( this, this ); + } + } +} + +bool SwFormatDrop::GetInfo( SfxPoolItem& ) const +{ + return true; // Continue +} + +bool SwFormatDrop::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_nLines == static_cast<const SwFormatDrop&>(rAttr).GetLines() && + m_nChars == static_cast<const SwFormatDrop&>(rAttr).GetChars() && + m_nDistance == static_cast<const SwFormatDrop&>(rAttr).GetDistance() && + m_bWholeWord == static_cast<const SwFormatDrop&>(rAttr).GetWholeWord() && + GetCharFormat() == static_cast<const SwFormatDrop&>(rAttr).GetCharFormat() && + m_pDefinedIn == static_cast<const SwFormatDrop&>(rAttr).m_pDefinedIn ); +} + +SwFormatDrop* SwFormatDrop::Clone( SfxItemPool* ) const +{ + return new SwFormatDrop( *this ); +} + +bool SwFormatDrop::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + switch(nMemberId&~CONVERT_TWIPS) + { + case MID_DROPCAP_LINES : rVal <<= static_cast<sal_Int16>(m_nLines); break; + case MID_DROPCAP_COUNT : rVal <<= static_cast<sal_Int16>(m_nChars); break; + case MID_DROPCAP_DISTANCE : rVal <<= static_cast<sal_Int16>(convertTwipToMm100(m_nDistance)); break; + case MID_DROPCAP_FORMAT: + { + style::DropCapFormat aDrop; + aDrop.Lines = m_nLines ; + aDrop.Count = m_nChars ; + aDrop.Distance = convertTwipToMm100(m_nDistance); + rVal <<= aDrop; + } + break; + case MID_DROPCAP_WHOLE_WORD: + rVal <<= m_bWholeWord; + break; + case MID_DROPCAP_CHAR_STYLE_NAME : + { + OUString sName; + if(GetCharFormat()) + sName = SwStyleNameMapper::GetProgName( + GetCharFormat()->GetName(), SwGetPoolIdFromName::ChrFmt ); + rVal <<= sName; + } + break; + } + return true; +} + +bool SwFormatDrop::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + switch(nMemberId&~CONVERT_TWIPS) + { + case MID_DROPCAP_LINES : + { + sal_Int8 nTemp = 0; + rVal >>= nTemp; + if(nTemp >=1 && nTemp < 0x7f) + m_nLines = static_cast<sal_uInt8>(nTemp); + } + break; + case MID_DROPCAP_COUNT : + { + sal_Int16 nTemp = 0; + rVal >>= nTemp; + if(nTemp >=1 && nTemp < 0x7f) + m_nChars = static_cast<sal_uInt8>(nTemp); + } + break; + case MID_DROPCAP_DISTANCE : + { + sal_Int16 nVal = 0; + if ( rVal >>= nVal ) + m_nDistance = static_cast<sal_Int16>(convertMm100ToTwip(static_cast<sal_Int32>(nVal))); + else + return false; + break; + } + case MID_DROPCAP_FORMAT: + { + if(rVal.getValueType() == ::cppu::UnoType<style::DropCapFormat>::get()) + { + auto pDrop = o3tl::doAccess<style::DropCapFormat>(rVal); + m_nLines = pDrop->Lines; + m_nChars = pDrop->Count; + m_nDistance = convertMm100ToTwip(pDrop->Distance); + } + } + break; + case MID_DROPCAP_WHOLE_WORD: + m_bWholeWord = *o3tl::doAccess<bool>(rVal); + break; + case MID_DROPCAP_CHAR_STYLE_NAME : + OSL_FAIL("char format cannot be set in PutValue()!"); + break; + } + return true; +} + +SwRegisterItem* SwRegisterItem::Clone( SfxItemPool * ) const +{ + return new SwRegisterItem( *this ); +} + +SwNumRuleItem* SwNumRuleItem::Clone( SfxItemPool * ) const +{ + return new SwNumRuleItem( *this ); +} + +bool SwNumRuleItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return GetValue() == static_cast<const SwNumRuleItem&>(rAttr).GetValue(); +} + +bool SwNumRuleItem::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + OUString sRet = SwStyleNameMapper::GetProgName(GetValue(), SwGetPoolIdFromName::NumRule ); + rVal <<= sRet; + return true; +} + +bool SwNumRuleItem::PutValue( const uno::Any& rVal, sal_uInt8 ) +{ + OUString uName; + rVal >>= uName; + SetValue(SwStyleNameMapper::GetUIName(uName, SwGetPoolIdFromName::NumRule)); + return true; +} + +void SwNumRuleItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRuleItem")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(GetValue().toUtf8().getStr())); + xmlTextWriterEndElement(pWriter); +} + +SwParaConnectBorderItem* SwParaConnectBorderItem::Clone( SfxItemPool * ) const +{ + return new SwParaConnectBorderItem( *this ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/sw3io/swacorr.cxx b/sw/source/core/sw3io/swacorr.cxx new file mode 100644 index 000000000..748f764a3 --- /dev/null +++ b/sw/source/core/sw3io/swacorr.cxx @@ -0,0 +1,99 @@ +/* -*- 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 <swacorr.hxx> +#include <swblocks.hxx> +#include <SwXMLTextBlocks.hxx> +#include <docsh.hxx> +#include <editsh.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +/** + * Returns the replacement text + * + * Only for the SWG format, all others can be extracted from the word list + * + * @param rShort - the stream name (encrypted) + */ +bool SwAutoCorrect::GetLongText( const OUString& rShort, OUString& rLong ) +{ + ErrCode nRet = ERRCODE_NONE; + assert( m_pTextBlocks ); + nRet = m_pTextBlocks->GetText( rShort, rLong ); + return !nRet.IsError() && !rLong.isEmpty(); +} + +void SwAutoCorrect::refreshBlockList( const uno::Reference< embed::XStorage >& rStg ) +{ + if (rStg.is()) + { + // mba: relative URLs don't make sense here + m_pTextBlocks.reset( new SwXMLTextBlocks( rStg, OUString() ) ); + } + else { + OSL_ENSURE( rStg.is(), "Someone passed SwAutoCorrect::refreshBlockList a dud storage!"); + } +} + +/** + * Text with attributes + * + * Only for SWG format + * + * @param rShort - the stream name (encrypted) + */ +bool SwAutoCorrect::PutText( const uno::Reference < embed::XStorage >& rStg, + const OUString& rFileName, const OUString& rShort, + SfxObjectShell& rObjSh, OUString& rLong ) +{ + if( nullptr == dynamic_cast<const SwDocShell*>( &rObjSh) ) + return false; + + SwDocShell& rDShell = static_cast<SwDocShell&>(rObjSh); + ErrCode nRet = ERRCODE_NONE; + + // mba: relative URLs don't make sense here + SwXMLTextBlocks aBlk( rStg, rFileName ); + SwDoc* pDoc = aBlk.GetDoc(); + + nRet = aBlk.BeginPutDoc( rShort, rShort ); + if( ! nRet.IsError() ) + { + rDShell.GetEditShell()->CopySelToDoc( pDoc ); + nRet = aBlk.PutDoc(); + aBlk.AddName ( rShort, rShort ); + if( ! nRet.IsError() ) + nRet = aBlk.GetText( rShort, rLong ); + } + return ! nRet.IsError(); +} + +SwAutoCorrect::SwAutoCorrect( const SvxAutoCorrect& rACorr ) + : SvxAutoCorrect( rACorr ) +{ + SwEditShell::SetAutoFormatFlags(&GetSwFlags()); +} + +SwAutoCorrect::~SwAutoCorrect() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/swg/BlockListTokens.txt b/sw/source/core/swg/BlockListTokens.txt new file mode 100644 index 000000000..0b5a64607 --- /dev/null +++ b/sw/source/core/swg/BlockListTokens.txt @@ -0,0 +1,7 @@ +abbreviated-name +block +block-list +list-name +name +package-name +unformatted-text diff --git a/sw/source/core/swg/SwXMLBlockExport.cxx b/sw/source/core/swg/SwXMLBlockExport.cxx new file mode 100644 index 000000000..ae8d81ca2 --- /dev/null +++ b/sw/source/core/swg/SwXMLBlockExport.cxx @@ -0,0 +1,136 @@ +/* -*- 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 <SwXMLBlockExport.hxx> +#include <SwXMLTextBlocks.hxx> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <xmloff/nmspmap.hxx> +#include <xmloff/xmlnmspe.hxx> +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +SwXMLBlockListExport::SwXMLBlockListExport( + const uno::Reference< uno::XComponentContext >& rContext, + SwXMLTextBlocks & rBlocks, + const OUString &rFileName, + uno::Reference< xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( rContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + rBlockList(rBlocks) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST ), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SwXMLBlockListExport::exportDoc(enum XMLTokenEnum ) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_LIST_NAME, + rBlockList.GetName()); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + sal_uInt16 nBlocks= rBlockList.GetCount(); + for ( sal_uInt16 i = 0; i < nBlocks; i++) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + rBlockList.GetShortName(i)); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_PACKAGE_NAME, + rBlockList.GetPackageName(i)); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_NAME, + rBlockList.GetLongName(i)); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_UNFORMATTED_TEXT, + rBlockList.IsOnlyTextBlock(i) ? XML_TRUE : XML_FALSE ); + + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +SwXMLTextBlockExport::SwXMLTextBlockExport( + const uno::Reference< uno::XComponentContext >& rContext, + SwXMLTextBlocks & rBlocks, + const OUString &rFileName, + uno::Reference< xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( rContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + rBlockList(rBlocks) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST ), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_OFFICE ), + GetXMLToken(XML_N_OFFICE_OOO), + XML_NAMESPACE_OFFICE ); + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_TEXT ), + GetXMLToken(XML_N_TEXT_OOO), + XML_NAMESPACE_TEXT ); +} + +void SwXMLTextBlockExport::exportDoc(const OUString &rText) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_TEXT ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_TEXT ) ); + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_OFFICE ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_OFFICE ) ); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_LIST_NAME, + rBlockList.GetName()); + { + SvXMLElementExport aDocument (*this, XML_NAMESPACE_OFFICE, XML_DOCUMENT, true, true); + { + SvXMLElementExport aBody (*this, XML_NAMESPACE_OFFICE, XML_BODY, true, true); + { + sal_Int32 nPos = 0; + do + { + OUString sTemp ( rText.getToken( 0, '\015', nPos ) ); + SvXMLElementExport aPara (*this, XML_NAMESPACE_TEXT, XML_P, true, false); + GetDocHandler()->characters(sTemp); + } while (-1 != nPos ); + } + + } + } + GetDocHandler()->endDocument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/swg/SwXMLBlockImport.cxx b/sw/source/core/swg/SwXMLBlockImport.cxx new file mode 100644 index 000000000..ab5519e27 --- /dev/null +++ b/sw/source/core/swg/SwXMLBlockImport.cxx @@ -0,0 +1,359 @@ +/* -*- 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 <SwXMLBlockImport.hxx> +#include <SwXMLTextBlocks.hxx> +#include <xmloff/xmlictxt.hxx> +#include <unotools/charclass.hxx> +#include <swtypes.hxx> + +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-register" +#endif +#endif +#include <tokens.cxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic pop +#endif +#endif + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace css::xml::sax; + +class SwXMLBlockListImport; +class SwXMLTextBlockImport; + +namespace { + +class SwXMLBlockListContext : public SvXMLImportContext +{ +private: + SwXMLBlockListImport & rLocalRef; + +public: + SwXMLBlockListContext( SwXMLBlockListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; +}; + +class SwXMLBlockContext : public SvXMLImportContext +{ +public: + SwXMLBlockContext( SwXMLBlockListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} +}; + +class SwXMLTextBlockDocumentContext : public SvXMLImportContext +{ +private: + SwXMLTextBlockImport & rLocalRef; + +public: + SwXMLTextBlockDocumentContext( SwXMLTextBlockImport& rImport ); + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; +}; + +class SwXMLTextBlockBodyContext : public SvXMLImportContext +{ +private: + SwXMLTextBlockImport & rLocalRef; + +public: + SwXMLTextBlockBodyContext( SwXMLTextBlockImport& rImport ); + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; +}; + +class SwXMLTextBlockTextContext : public SvXMLImportContext +{ +private: + SwXMLTextBlockImport & rLocalRef; + +public: + SwXMLTextBlockTextContext( SwXMLTextBlockImport& rImport ); + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; +}; + +class SwXMLTextBlockParContext : public SvXMLImportContext +{ +private: + SwXMLTextBlockImport & rLocalRef; + +public: + SwXMLTextBlockParContext( SwXMLTextBlockImport & rImport ); + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual void SAL_CALL characters( const OUString & aChars ) override; + + virtual ~SwXMLTextBlockParContext() override; +}; + +} + +SwXMLTextBlockTokenHandler::SwXMLTextBlockTokenHandler() +{ +} + +SwXMLTextBlockTokenHandler::~SwXMLTextBlockTokenHandler() +{ +} + +sal_Int32 SAL_CALL SwXMLTextBlockTokenHandler::getTokenFromUTF8( const Sequence< sal_Int8 >& Identifier ) +{ + return getTokenDirect( reinterpret_cast< const char* >( Identifier.getConstArray() ), Identifier.getLength() ); +} + +Sequence< sal_Int8 > SAL_CALL SwXMLTextBlockTokenHandler::getUTF8Identifier( sal_Int32 ) +{ + return Sequence< sal_Int8 >(); +} + +sal_Int32 SwXMLTextBlockTokenHandler::getTokenDirect( const char *pTag, sal_Int32 nLength ) const +{ + if( !nLength ) + nLength = strlen( pTag ); + const struct xmltoken* pToken = TextBlockTokens::in_word_set( pTag, nLength ); + return pToken ? pToken->nToken : XML_TOKEN_INVALID; +} + +SwXMLBlockListTokenHandler::SwXMLBlockListTokenHandler() +{ +} + +SwXMLBlockListTokenHandler::~SwXMLBlockListTokenHandler() +{ +} + +sal_Int32 SAL_CALL SwXMLBlockListTokenHandler::getTokenFromUTF8( const Sequence< sal_Int8 >& Identifier ) +{ + return getTokenDirect( reinterpret_cast< const char* >( Identifier.getConstArray() ), Identifier.getLength() ); +} + +Sequence< sal_Int8 > SAL_CALL SwXMLBlockListTokenHandler::getUTF8Identifier( sal_Int32 ) +{ + return Sequence< sal_Int8 >(); +} + +sal_Int32 SwXMLBlockListTokenHandler::getTokenDirect( const char *pTag, sal_Int32 nLength ) const +{ + if( !nLength ) + nLength = strlen( pTag ); + const struct xmltoken* pToken = BlockListTokens::in_word_set( pTag, nLength ); + return pToken ? pToken->nToken : XML_TOKEN_INVALID; +} + +SwXMLBlockListContext::SwXMLBlockListContext( + SwXMLBlockListImport& rImport, + const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext( rImport ), + rLocalRef( rImport ) +{ + if( xAttrList.is() && xAttrList->hasAttribute( SwXMLBlockListToken::LIST_NAME ) ) + rImport.getBlockList().SetName( xAttrList->getValue( SwXMLBlockListToken::LIST_NAME ) ); +} + +uno::Reference< ::xml::sax::XFastContextHandler > SAL_CALL +SwXMLBlockListContext::createFastChildContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SwXMLBlockListToken::BLOCK ) + return new SwXMLBlockContext( rLocalRef, xAttrList ); + return nullptr; +} + +SwXMLBlockContext::SwXMLBlockContext( + SwXMLBlockListImport& rImport, + const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext( rImport ) +{ + static const CharClass & rCC = GetAppCharClass(); + OUString aShort, aLong, aPackageName; + bool bTextOnly = false; + if( xAttrList.is() ) + { + if( xAttrList->hasAttribute( SwXMLBlockListToken::ABBREVIATED_NAME ) ) + aShort = rCC.uppercase( xAttrList->getValue( SwXMLBlockListToken::ABBREVIATED_NAME ) ); + if( xAttrList->hasAttribute( SwXMLBlockListToken::NAME ) ) + aLong = xAttrList->getValue( SwXMLBlockListToken::NAME ); + if( xAttrList->hasAttribute( SwXMLBlockListToken::PACKAGE_NAME ) ) + aPackageName = xAttrList->getValue( SwXMLBlockListToken::PACKAGE_NAME ); + if( xAttrList->hasAttribute( SwXMLBlockListToken::UNFORMATTED_TEXT ) ) + { + OUString rAttrValue( xAttrList->getValue( SwXMLBlockListToken::UNFORMATTED_TEXT ) ); + if( IsXMLToken( rAttrValue, XML_TRUE ) ) + bTextOnly = true; + } + } + if (aShort.isEmpty() || aLong.isEmpty() || aPackageName.isEmpty()) + return; + rImport.getBlockList().AddName( aShort, aLong, aPackageName, bTextOnly); +} + +SwXMLTextBlockDocumentContext::SwXMLTextBlockDocumentContext( + SwXMLTextBlockImport& rImport ) : + SvXMLImportContext( rImport ), + rLocalRef(rImport) +{ +} + +uno::Reference< ::xml::sax::XFastContextHandler > SAL_CALL +SwXMLTextBlockDocumentContext::createFastChildContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if ( Element == SwXMLTextBlockToken::OFFICE_BODY ) + return new SwXMLTextBlockBodyContext( rLocalRef ); + return nullptr; +} + +SwXMLTextBlockTextContext::SwXMLTextBlockTextContext( + SwXMLTextBlockImport& rImport) : + SvXMLImportContext ( rImport ), + rLocalRef( rImport ) +{ +} + +uno::Reference< xml::sax::XFastContextHandler > SAL_CALL +SwXMLTextBlockTextContext::createFastChildContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if ( Element == SwXMLTextBlockToken::TEXT_P ) + return new SwXMLTextBlockParContext( rLocalRef ); + return nullptr; +} + +SwXMLTextBlockBodyContext::SwXMLTextBlockBodyContext( + SwXMLTextBlockImport& rImport ) : + SvXMLImportContext( rImport ), + rLocalRef(rImport) +{ +} + +uno::Reference < xml::sax::XFastContextHandler > SAL_CALL +SwXMLTextBlockBodyContext::createFastChildContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SwXMLTextBlockToken::OFFICE_TEXT ) + return new SwXMLTextBlockTextContext( rLocalRef ); + else if( Element == SwXMLTextBlockToken::TEXT_P ) + return new SwXMLTextBlockParContext( rLocalRef ); + return nullptr; +} + +SwXMLTextBlockParContext::SwXMLTextBlockParContext( + SwXMLTextBlockImport& rImport ) : + SvXMLImportContext( rImport ), + rLocalRef( rImport ) +{ +} + +void SAL_CALL SwXMLTextBlockParContext::characters( const OUString & aChars ) +{ + rLocalRef.m_rText += aChars; +} + +SwXMLTextBlockParContext::~SwXMLTextBlockParContext() +{ + if (rLocalRef.bTextOnly) + rLocalRef.m_rText += "\015"; + else + { + if (!rLocalRef.m_rText.endsWith( " " )) + rLocalRef.m_rText += " "; + } +} + +// SwXMLBlockListImport ////////////////////////////// +SwXMLBlockListImport::SwXMLBlockListImport( + const uno::Reference< uno::XComponentContext >& rContext, + SwXMLTextBlocks &rBlocks ) +: SvXMLImport( rContext, "", SvXMLImportFlags::NONE ), + rBlockList (rBlocks) +{ +} + +SwXMLBlockListImport::~SwXMLBlockListImport() + throw () +{ +} + +SvXMLImportContext* SwXMLBlockListImport::CreateFastContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if( Element == SwXMLBlockListToken::BLOCK_LIST ) + return new SwXMLBlockListContext( *this, xAttrList ); + return nullptr; +} + +SwXMLTextBlockImport::SwXMLTextBlockImport( + const uno::Reference< uno::XComponentContext >& rContext, + OUString & rNewText, + bool bNewTextOnly ) +: SvXMLImport(rContext, "", SvXMLImportFlags::ALL ), + bTextOnly ( bNewTextOnly ), + m_rText ( rNewText ) +{ +} + +SwXMLTextBlockImport::~SwXMLTextBlockImport() + throw() +{ +} + +SvXMLImportContext* SwXMLTextBlockImport::CreateFastContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SwXMLTextBlockToken::OFFICE_DOCUMENT || + Element == SwXMLTextBlockToken::OFFICE_DOCUMENT_CONTENT ) + return new SwXMLTextBlockDocumentContext( *this ); + return nullptr; +} + +void SAL_CALL SwXMLTextBlockImport::endDocument() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/swg/SwXMLSectionList.cxx b/sw/source/core/swg/SwXMLSectionList.cxx new file mode 100644 index 000000000..aa6c63b8b --- /dev/null +++ b/sw/source/core/swg/SwXMLSectionList.cxx @@ -0,0 +1,137 @@ +/* -*- 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 <SwXMLSectionList.hxx> +#include <xmloff/xmlictxt.hxx> +#include <xmloff/nmspmap.hxx> +#include <xmloff/xmlnmspe.hxx> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +namespace { + +class SvXMLSectionListContext : public SvXMLImportContext +{ +private: + SwXMLSectionList & GetImport() { return static_cast<SwXMLSectionList&>(SvXMLImportContext::GetImport()); } + +public: + SvXMLSectionListContext(SwXMLSectionList& rImport); + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; +}; + +class SwXMLParentContext : public SvXMLImportContext +{ +private: + SwXMLSectionList & GetImport() { return static_cast<SwXMLSectionList&>(SvXMLImportContext::GetImport()); } + +public: + SwXMLParentContext(SwXMLSectionList& rImport) + : SvXMLImportContext(rImport) + { + } + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) override {} + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & /*xAttrList*/ ) override + { + if (Element == XML_ELEMENT(OFFICE, XML_BODY) || + Element == XML_ELEMENT(OFFICE_OOO, XML_BODY)) + { + return new SvXMLSectionListContext(GetImport()); + } + if (IsTokenInNamespace(Element, XML_NAMESPACE_TEXT) || + IsTokenInNamespace(Element, XML_NAMESPACE_TEXT_OOO)) + { + auto nToken = Element & TOKEN_MASK; + if (nToken == XML_P || + nToken == XML_H || + nToken == XML_A || + nToken == XML_SPAN || + nToken == XML_SECTION || + nToken == XML_INDEX_BODY || + nToken == XML_INDEX_TITLE || + nToken == XML_INSERTION || + nToken == XML_DELETION) + return new SvXMLSectionListContext(GetImport()); + } + return new SwXMLParentContext(GetImport()); + } +}; + +} + +SwXMLSectionList::SwXMLSectionList(const css::uno::Reference< css::uno::XComponentContext >& rContext, std::vector<OUString> &rNewSectionList) +: SvXMLImport(rContext, "") +, m_rSectionList(rNewSectionList) +{ +} + +SwXMLSectionList::~SwXMLSectionList() + throw() +{ +} + +SvXMLImportContext * SwXMLSectionList::CreateFastContext( + sal_Int32 /*Element*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + return new SwXMLParentContext(*this); +} + +SvXMLSectionListContext::SvXMLSectionListContext( SwXMLSectionList& rImport ) + : SvXMLImportContext ( rImport ) +{ +} + +css::uno::Reference<css::xml::sax::XFastContextHandler> SvXMLSectionListContext::createFastChildContext( + sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) +{ + SvXMLImportContext *pContext = nullptr; + + if (Element == XML_ELEMENT(TEXT, XML_SECTION ) || + Element == XML_ELEMENT(TEXT, XML_BOOKMARK) || + Element == XML_ELEMENT(TEXT_OOO, XML_SECTION ) || + Element == XML_ELEMENT(TEXT_OOO, XML_BOOKMARK) ) + { + OUString sName; + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + if (aIter.getToken() == XML_ELEMENT(TEXT, XML_NAME) || + aIter.getToken() == XML_ELEMENT(TEXT_OOO, XML_NAME)) + sName = aIter.toString(); + if ( !sName.isEmpty() ) + GetImport().m_rSectionList.push_back(sName); + } + + pContext = new SvXMLSectionListContext(GetImport()); + return pContext; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/swg/SwXMLTextBlocks.cxx b/sw/source/core/swg/SwXMLTextBlocks.cxx new file mode 100644 index 000000000..943cb8c41 --- /dev/null +++ b/sw/source/core/swg/SwXMLTextBlocks.cxx @@ -0,0 +1,517 @@ +/* -*- 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 <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <osl/file.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sot/exchange.hxx> +#include <sot/stg.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <unotools/ucbstreamhelper.hxx> + +#include <comphelper/storagehelper.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docsh.hxx> +#include <pam.hxx> +#include <swblocks.hxx> +#include <ndtxt.hxx> +#include <shellio.hxx> +#include <poolfmt.hxx> +#include <SwXMLTextBlocks.hxx> +#include <swerror.h> + +using namespace ::com::sun::star; + +void SwXMLTextBlocks::InitBlockMode ( const uno::Reference < embed::XStorage >& rStorage ) +{ + xBlkRoot = rStorage; + xRoot = nullptr; +} + +void SwXMLTextBlocks::ResetBlockMode ( ) +{ + xBlkRoot = nullptr; + xRoot = nullptr; +} + +SwXMLTextBlocks::SwXMLTextBlocks( const OUString& rFile ) + : SwImpBlocks(rFile) + , nFlags(SwXmlFlags::NONE) +{ + SwDocShell* pDocSh = new SwDocShell ( SfxObjectCreateMode::INTERNAL ); + if( !pDocSh->DoInitNew() ) + return; + m_bReadOnly = true; + m_xDoc = pDocSh->GetDoc(); + xDocShellRef = pDocSh; + m_xDoc->SetOle2Link( Link<bool,void>() ); + m_xDoc->GetIDocumentUndoRedo().DoUndo(false); + uno::Reference< embed::XStorage > refStg; + if( !m_aDateModified.GetDate() || !m_aTimeModified.GetTime() ) + Touch(); // If it's created anew -> get a new timestamp + + try + { + refStg = comphelper::OStorageHelper::GetStorageFromURL( rFile, embed::ElementModes::READWRITE ); + m_bReadOnly = false; + } + catch(const uno::Exception&) + { + //FIXME: couldn't open the file - maybe it's readonly + } + if( !refStg.is()) + { + try + { + refStg = comphelper::OStorageHelper::GetStorageFromURL( rFile, embed::ElementModes::READ ); + } + catch(const uno::Exception&) + { + OSL_FAIL("exception while creating AutoText storage"); + } + } + InitBlockMode ( refStg ); + ReadInfo(); + ResetBlockMode (); + m_bInfoChanged = false; +} + +SwXMLTextBlocks::SwXMLTextBlocks( const uno::Reference < embed::XStorage >& rStg, const OUString& rName ) + : SwImpBlocks( rName ) + , nFlags(SwXmlFlags::NONE) +{ + SwDocShell* pDocSh = new SwDocShell ( SfxObjectCreateMode::INTERNAL ); + if( !pDocSh->DoInitNew() ) + return; + m_bReadOnly = false; + m_xDoc = pDocSh->GetDoc(); + xDocShellRef = pDocSh; + m_xDoc->SetOle2Link( Link<bool,void>() ); + m_xDoc->GetIDocumentUndoRedo().DoUndo(false); + + InitBlockMode ( rStg ); + ReadInfo(); + m_bInfoChanged = false; +} + +SwXMLTextBlocks::~SwXMLTextBlocks() +{ + if ( m_bInfoChanged ) + WriteInfo(); + ResetBlockMode (); + if(xDocShellRef.is()) + xDocShellRef->DoClose(); + xDocShellRef = nullptr; +} + +void SwXMLTextBlocks::ClearDoc() +{ + SwDocShell * pDocShell = m_xDoc->GetDocShell(); + pDocShell->InvalidateModel(); + pDocShell->ReactivateModel(); + + m_xDoc->ClearDoc(); + pDocShell->ClearEmbeddedObjects(); +} + +void SwXMLTextBlocks::AddName( const OUString& rShort, const OUString& rLong, bool bOnlyText ) +{ + aPackageName = GeneratePackageName( rShort ); + AddName(rShort, rLong, aPackageName, bOnlyText); +} + +void SwXMLTextBlocks::AddName( const OUString& rShort, const OUString& rLong, + const OUString& rPackageName, bool bOnlyText ) +{ + sal_uInt16 nIdx = GetIndex( rShort ); + if (nIdx != USHRT_MAX) + { + m_aNames.erase( m_aNames.begin() + nIdx ); + } + std::unique_ptr<SwBlockName> pNew(new SwBlockName( rShort, rLong, rPackageName )); + pNew->bIsOnlyTextFlagInit = true; + pNew->bIsOnlyText = bOnlyText; + m_aNames.insert( std::move(pNew) ); + m_bInfoChanged = true; +} + +ErrCode SwXMLTextBlocks::Delete( sal_uInt16 n ) +{ + const OUString aPckName (m_aNames[n]->aPackageName); + if ( xBlkRoot.is() && + xBlkRoot->hasByName( aPckName ) && xBlkRoot->isStreamElement( aPckName ) ) + { + try + { + xBlkRoot->removeElement ( aPckName ); + uno::Reference < embed::XTransactedObject > xTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + return ERRCODE_NONE; + } + catch (const uno::Exception&) + { + return ERR_SWG_WRITE_ERROR; + } + } + return ERRCODE_NONE; +} + +ErrCode SwXMLTextBlocks::Rename( sal_uInt16 nIdx, const OUString& rNewShort ) +{ + OSL_ENSURE( xBlkRoot.is(), "No storage set" ); + if(!xBlkRoot.is()) + return ERRCODE_NONE; + OUString aOldName (m_aNames[nIdx]->aPackageName); + m_aShort = rNewShort; + aPackageName = GeneratePackageName( m_aShort ); + + if(aOldName != aPackageName) + { + if (IsOnlyTextBlock ( nIdx ) ) + { + OUString sExt(".xml"); + OUString aOldStreamName = aOldName + sExt; + OUString aNewStreamName = aPackageName + sExt; + + xRoot = xBlkRoot->openStorageElement( aOldName, embed::ElementModes::READWRITE ); + try + { + xRoot->renameElement ( aOldStreamName, aNewStreamName ); + } + catch(const container::ElementExistException&) + { + SAL_WARN("sw", "Couldn't rename " << aOldStreamName << " to " << aNewStreamName); + } + uno::Reference < embed::XTransactedObject > xTrans( xRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + xRoot = nullptr; + } + + try + { + xBlkRoot->renameElement ( aOldName, aPackageName ); + } + catch(const container::ElementExistException&) + { + SAL_WARN("sw", "Couldn't rename " << aOldName << " to " << aPackageName); + } + } + uno::Reference < embed::XTransactedObject > xTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + // No need to commit xBlkRoot here as SwTextBlocks::Rename calls + // WriteInfo which does the commit + return ERRCODE_NONE; +} + +ErrCode SwXMLTextBlocks::StartPutBlock( const OUString& rShort, const OUString& rPackageName ) +{ + OSL_ENSURE( xBlkRoot.is(), "No storage set" ); + if(!xBlkRoot.is()) + return ERRCODE_NONE; + GetIndex ( rShort ); + try + { + xRoot = xBlkRoot->openStorageElement( rPackageName, embed::ElementModes::READWRITE ); + + uno::Reference< beans::XPropertySet > xRootProps( xRoot, uno::UNO_QUERY_THROW ); + OUString aMime( SotExchange::GetFormatMimeType( SotClipboardFormatId::STARWRITER_8 ) ); + xRootProps->setPropertyValue( "MediaType", uno::makeAny( aMime ) ); + } + catch (const uno::Exception&) + { + } + return ERRCODE_NONE; +} + +ErrCode SwXMLTextBlocks::BeginPutDoc( const OUString& rShort, const OUString& rLong ) +{ + // Store in base class + m_aShort = rShort; + m_aLong = rLong; + aPackageName = GeneratePackageName( rShort ); + SetIsTextOnly( rShort, false); + return StartPutBlock (rShort, aPackageName); +} + +ErrCode SwXMLTextBlocks::PutBlock() +{ + ErrCode nRes = ERRCODE_NONE; // dead variable, this always returns 0 + SwXmlFlags nCommitFlags = nFlags; + + WriterRef xWrt; + ::GetXMLWriter ( OUString(), GetBaseURL(), xWrt); + SwWriter aWriter (xRoot, *m_xDoc ); + + xWrt->m_bBlock = true; + nRes = aWriter.Write ( xWrt ); + xWrt->m_bBlock = false; + // Save OLE objects if there are some + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + + bool bHasChildren = pDocSh && pDocSh->GetEmbeddedObjectContainer().HasEmbeddedObjects(); + if( !nRes && bHasChildren ) + { + // we have to write to the temporary storage first, since the used below functions are optimized + // TODO/LATER: it is only a temporary solution, that should be changed soon, the used methods should be + // called without optimization + bool bOK = false; + + if ( xRoot.is() ) + { + std::unique_ptr<SfxMedium> pTmpMedium; + try + { + uno::Reference< embed::XStorage > xTempStorage = + ::comphelper::OStorageHelper::GetTemporaryStorage(); + + xRoot->copyToStorage( xTempStorage ); + + // TODO/LATER: no progress bar?! + // TODO/MBA: strange construct + pTmpMedium.reset(new SfxMedium(xTempStorage, GetBaseURL())); + bool bTmpOK = pDocSh->SaveAsChildren( *pTmpMedium ); + if( bTmpOK ) + bTmpOK = pDocSh->SaveCompletedChildren(); + + xTempStorage->copyToStorage( xRoot ); + bOK = bTmpOK; + } + catch(const uno::Exception&) + { + } + } + + if( !bOK ) + nRes = ERR_SWG_WRITE_ERROR; + } + + try + { + uno::Reference < embed::XTransactedObject > xTrans( xRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + xRoot = nullptr; + if ( nCommitFlags == SwXmlFlags::NONE ) + { + uno::Reference < embed::XTransactedObject > xTmpTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTmpTrans.is() ) + xTmpTrans->commit(); + } + } + catch (const uno::Exception&) + { + } + + //TODO/LATER: error handling + return ERRCODE_NONE; +} + +ErrCode SwXMLTextBlocks::PutDoc() +{ + std::unique_ptr<SwPaM> pPaM = MakePaM(); + ErrCode nErr = PutBlock(); + return nErr; +} + +ErrCode SwXMLTextBlocks::GetText( const OUString& rShort, OUString& rText ) +{ + return GetBlockText( rShort, rText ); +} + +ErrCode SwXMLTextBlocks::MakeBlockList() +{ + WriteInfo(); + return ERRCODE_NONE; +} + +bool SwXMLTextBlocks::PutMuchEntries( bool bOn ) +{ + bool bRet = false; + if( bOn ) + { + if( m_bInPutMuchBlocks ) + { + OSL_ENSURE( false, "Nested calls are not allowed"); + } + else if( !IsFileChanged() ) + { + bRet = ERRCODE_NONE == OpenFile( false ); + if( bRet ) + { + nFlags |= SwXmlFlags::NoRootCommit; + m_bInPutMuchBlocks = true; + } + } + } + else if( m_bInPutMuchBlocks ) + { + nFlags &= ~SwXmlFlags::NoRootCommit; + if( xBlkRoot.is() ) + { + try + { + uno::Reference < embed::XTransactedObject > xTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + MakeBlockList(); + CloseFile(); + Touch(); + m_bInPutMuchBlocks = false; + bRet = true; + } + catch (const uno::Exception&) + { + } + } + } + return bRet; +} + +ErrCode SwXMLTextBlocks::OpenFile( bool bRdOnly ) +{ + ErrCode nRet = ERRCODE_NONE; + try + { + uno::Reference < embed::XStorage > refStg = comphelper::OStorageHelper::GetStorageFromURL( m_aFile, + bRdOnly ? embed::ElementModes::READ : embed::ElementModes::READWRITE ); + InitBlockMode ( refStg ); + } + catch (const uno::Exception&) + { + //TODO/LATER: error handling + nRet = ErrCode(1); + } + + return nRet; +} + +void SwXMLTextBlocks::CloseFile() +{ + if (m_bInfoChanged) + WriteInfo(); + ResetBlockMode(); +} + +void SwXMLTextBlocks::SetIsTextOnly( const OUString& rShort, bool bNewValue ) +{ + sal_uInt16 nIdx = GetIndex ( rShort ); + if (nIdx != USHRT_MAX) + m_aNames[nIdx]->bIsOnlyText = bNewValue; +} + +bool SwXMLTextBlocks::IsOnlyTextBlock( const OUString& rShort ) const +{ + sal_uInt16 nIdx = GetIndex ( rShort ); + bool bRet = false; + if (nIdx != USHRT_MAX) + { + bRet = m_aNames[nIdx]->bIsOnlyText; + } + return bRet; +} +bool SwXMLTextBlocks::IsOnlyTextBlock( sal_uInt16 nIdx ) const +{ + return m_aNames[nIdx]->bIsOnlyText; +} + +bool SwXMLTextBlocks::IsFileUCBStorage( const OUString & rFileName) +{ + OUString aName( rFileName ); + INetURLObject aObj( aName ); + if ( aObj.GetProtocol() == INetProtocol::NotValid ) + { + OUString aURL; + osl::FileBase::getFileURLFromSystemPath( aName, aURL ); + aObj.SetURL( aURL ); + aName = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + std::unique_ptr<SvStream> pStm = ::utl::UcbStreamHelper::CreateStream( aName, StreamMode::STD_READ ); + bool bRet = UCBStorage::IsStorageFile( pStm.get() ); + return bRet; +} + +OUString SwXMLTextBlocks::GeneratePackageName ( const OUString& rShort ) +{ + OString sByte(OUStringToOString(rShort, RTL_TEXTENCODING_UTF7)); + OUStringBuffer aBuf(OStringToOUString(sByte, RTL_TEXTENCODING_ASCII_US)); + const sal_Int32 nLen = aBuf.getLength(); + for (sal_Int32 nPos=0; nPos<nLen; ++nPos) + { + switch (aBuf[nPos]) + { + case '!': + case '/': + case ':': + case '.': + case '\\': + aBuf[nPos] = '_'; + break; + default: + break; + } + } + return aBuf.makeStringAndClear(); +} + +ErrCode SwXMLTextBlocks::PutText( const OUString& rShort, const OUString& rName, + const OUString& rText ) +{ + ErrCode nRes = ERRCODE_NONE; + m_aShort = rShort; + m_aLong = rName; + m_aCurrentText = rText; + SetIsTextOnly( m_aShort, true ); + aPackageName = GeneratePackageName( rShort ); + ClearDoc(); + nRes = PutBlockText( rShort, rText, aPackageName ); + return nRes; +} + +void SwXMLTextBlocks::MakeBlockText( const OUString& rText ) +{ + SwTextNode* pTextNode = m_xDoc->GetNodes()[ m_xDoc->GetNodes().GetEndOfContent(). + GetIndex() - 1 ]->GetTextNode(); + if( pTextNode->GetTextColl() == m_xDoc->GetDfltTextFormatColl() ) + pTextNode->ChgFormatColl( m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + + sal_Int32 nPos = 0; + do + { + if ( nPos ) + { + pTextNode = static_cast<SwTextNode*>(pTextNode->AppendNode( SwPosition( *pTextNode ) )); + } + SwIndex aIdx( pTextNode ); + pTextNode->InsertText( rText.getToken( 0, '\015', nPos ), aIdx ); + } while ( -1 != nPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/swg/SwXMLTextBlocks1.cxx b/sw/source/core/swg/SwXMLTextBlocks1.cxx new file mode 100644 index 000000000..9995d3c1f --- /dev/null +++ b/sw/source/core/swg/SwXMLTextBlocks1.cxx @@ -0,0 +1,566 @@ +/* -*- 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 <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <osl/diagnose.h> +#include <svl/macitem.hxx> +#include <svtools/unoevent.hxx> +#include <sfx2/docfile.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/fileformat.h> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/FastToken.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <doc.hxx> +#include <docsh.hxx> +#include <shellio.hxx> +#include <SwXMLTextBlocks.hxx> +#include <SwXMLBlockImport.hxx> +#include <SwXMLBlockExport.hxx> +#include <xmloff/xmlnmspe.hxx> +#include <sfx2/event.hxx> +#include <swerror.h> + +const char XMLN_BLOCKLIST[] = "BlockList.xml"; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace css::xml::sax; +using namespace xmloff::token; + +using ::xmloff::token::XML_BLOCK_LIST; +using ::xmloff::token::XML_UNFORMATTED_TEXT; +using ::xmloff::token::GetXMLToken; + +ErrCode SwXMLTextBlocks::GetDoc( sal_uInt16 nIdx ) +{ + OUString aFolderName ( GetPackageName ( nIdx ) ); + + if (!IsOnlyTextBlock ( nIdx ) ) + { + try + { + xRoot = xBlkRoot->openStorageElement( aFolderName, embed::ElementModes::READ ); + xMedium = new SfxMedium( xRoot, GetBaseURL(), "writer8" ); + SwReader aReader( *xMedium, aFolderName, m_xDoc.get() ); + ReadXML->SetBlockMode( true ); + aReader.Read( *ReadXML ); + ReadXML->SetBlockMode( false ); + // Ole objects fail to display when inserted into the document, as + // the ObjectReplacement folder and contents are missing + OUString sObjReplacements( "ObjectReplacements" ); + if ( xRoot->hasByName( sObjReplacements ) ) + { + uno::Reference< document::XStorageBasedDocument > xDocStor( m_xDoc->GetDocShell()->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< embed::XStorage > xStr( xDocStor->getDocumentStorage() ); + if ( xStr.is() ) + { + xRoot->copyElementTo( sObjReplacements, xStr, sObjReplacements ); + uno::Reference< embed::XTransactedObject > xTrans( xStr, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + } + } + } + catch( uno::Exception& ) + { + } + + xRoot = nullptr; + } + else + { + OUString aStreamName = aFolderName + ".xml"; + try + { + xRoot = xBlkRoot->openStorageElement( aFolderName, embed::ElementModes::READ ); + uno::Reference < io::XStream > xStream = xRoot->openStreamElement( aStreamName, embed::ElementModes::READ ); + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = m_aNames[nIdx]->aPackageName; + + aParserInput.aInputStream = xStream->getInputStream(); + + // get filter + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SwXMLTextBlockImport( xContext, m_aCurrentText, true ); + uno::Reference< xml::sax::XFastTokenHandler > xTokenHandler = new SwXMLTextBlockTokenHandler(); + + // connect parser and filter + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); + xParser->setFastDocumentHandler( xFilter ); + xParser->setTokenHandler( xTokenHandler ); + + xParser->registerNamespace( "http://openoffice.org/2000/text", FastToken::NAMESPACE | XML_NAMESPACE_TEXT ); + xParser->registerNamespace( "http://openoffice.org/2000/office", FastToken::NAMESPACE | XML_NAMESPACE_OFFICE ); + + // parse + try + { + xParser->parseStream( aParserInput ); + } + catch( xml::sax::SAXParseException& ) + { + // re throw ? + } + catch( xml::sax::SAXException& ) + { + // re throw ? + } + catch( io::IOException& ) + { + // re throw ? + } + + m_bInfoChanged = false; + MakeBlockText(m_aCurrentText); + } + catch( uno::Exception& ) + { + } + + xRoot = nullptr; + } + return ERRCODE_NONE; +} + +// event description for autotext events; this constant should really be +// taken from unocore/unoevents.cxx or ui/unotxt.cxx +const struct SvEventDescription aAutotextEvents[] = +{ + { SvMacroItemId::SwStartInsGlossary, "OnInsertStart" }, + { SvMacroItemId::SwEndInsGlossary, "OnInsertDone" }, + { SvMacroItemId::NONE, nullptr } +}; + +ErrCode SwXMLTextBlocks::GetMacroTable( sal_uInt16 nIdx, + SvxMacroTableDtor& rMacroTable ) +{ + // set current auto text + m_aShort = m_aNames[nIdx]->aShort; + m_aLong = m_aNames[nIdx]->aLong; + aPackageName = m_aNames[nIdx]->aPackageName; + + // open stream in proper sub-storage + CloseFile(); + if ( OpenFile() != ERRCODE_NONE ) + return ERR_SWG_READ_ERROR; + + try + { + xRoot = xBlkRoot->openStorageElement( aPackageName, embed::ElementModes::READ ); + bool bOasis = SotStorage::GetVersion( xRoot ) > SOFFICE_FILEFORMAT_60; + + uno::Reference < io::XStream > xDocStream = xRoot->openStreamElement( + "atevent.xml", embed::ElementModes::READ ); + OSL_ENSURE(xDocStream.is(), "Can't create stream"); + if ( !xDocStream.is() ) + return ERR_SWG_READ_ERROR; + + uno::Reference<io::XInputStream> xInputStream = xDocStream->getInputStream(); + + // prepare ParserInputSrouce + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = m_aName; + aParserInput.aInputStream = xInputStream; + + // get service factory + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + // create descriptor and reference to it. Either + // both or neither must be kept because of the + // reference counting! + SvMacroTableEventDescriptor* pDescriptor = + new SvMacroTableEventDescriptor(aAutotextEvents); + uno::Reference<XNameReplace> xReplace = pDescriptor; + Sequence<Any> aFilterArguments( 1 ); + aFilterArguments[0] <<= xReplace; + + // get filter + OUString sFilterComponent = bOasis + ? OUString("com.sun.star.comp.Writer.XMLOasisAutotextEventsImporter") + : OUString("com.sun.star.comp.Writer.XMLAutotextEventsImporter"); + uno::Reference< xml::sax::XFastParser > xFilter( + xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + sFilterComponent, aFilterArguments, xContext), + UNO_QUERY_THROW ); + + // parse the stream + try + { + xFilter->parseStream( aParserInput ); + } + catch( xml::sax::SAXParseException& ) + { + // workaround for #83452#: SetSize doesn't work + // nRet = ERR_SWG_READ_ERROR; + } + catch( xml::sax::SAXException& ) + { + TOOLS_WARN_EXCEPTION("sw", ""); + return ERR_SWG_READ_ERROR; + } + catch( io::IOException& ) + { + TOOLS_WARN_EXCEPTION("sw", ""); + return ERR_SWG_READ_ERROR; + } + + // and finally, copy macro into table + pDescriptor->copyMacrosIntoTable(rMacroTable); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("sw", ""); + return ERR_SWG_READ_ERROR; + } + + // success! + return ERRCODE_NONE; +} + +ErrCode SwXMLTextBlocks::GetBlockText( const OUString& rShort, OUString& rText ) +{ + OUString aFolderName = GeneratePackageName ( rShort ); + OUString aStreamName = aFolderName + ".xml"; + rText.clear(); + + try + { + bool bTextOnly = true; + + xRoot = xBlkRoot->openStorageElement( aFolderName, embed::ElementModes::READ ); + if ( !xRoot->hasByName( aStreamName ) || !xRoot->isStreamElement( aStreamName ) ) + { + bTextOnly = false; + aStreamName = "content.xml"; + } + + uno::Reference < io::XStream > xContents = xRoot->openStreamElement( aStreamName, embed::ElementModes::READ ); + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = m_aName; + aParserInput.aInputStream = xContents->getInputStream(); + + // get filter + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SwXMLTextBlockImport( xContext, rText, bTextOnly ); + uno::Reference< xml::sax::XFastTokenHandler > xTokenHandler = new SwXMLTextBlockTokenHandler(); + + // connect parser and filter + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); + xParser->setFastDocumentHandler( xFilter ); + xParser->setTokenHandler( xTokenHandler ); + + xParser->registerNamespace( "urn:oasis:names:tc:opendocument:xmlns:office:1.0", FastToken::NAMESPACE | XML_NAMESPACE_OFFICE ); + xParser->registerNamespace( "urn:oasis:names:tc:opendocument:xmlns:text:1.0", FastToken::NAMESPACE | XML_NAMESPACE_TEXT ); + + // parse + try + { + xParser->parseStream( aParserInput ); + } + catch( xml::sax::SAXParseException& ) + { + // re throw ? + } + catch( xml::sax::SAXException& ) + { + // re throw ? + } + catch( io::IOException& ) + { + // re throw ? + } + + xRoot = nullptr; + } + catch ( uno::Exception& ) + { + OSL_FAIL( "Tried to open non-existent folder or stream!"); + } + + return ERRCODE_NONE; +} + +ErrCode SwXMLTextBlocks::PutBlockText( const OUString& rShort, + const OUString& rText, const OUString& rPackageName ) +{ + GetIndex ( rShort ); + /* + if (xBlkRoot->IsContained ( rPackageName ) ) + { + xBlkRoot->Remove ( rPackageName ); + xBlkRoot->Commit ( ); + } + */ + OUString aStreamName = rPackageName + ".xml"; + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + ErrCode nRes = ERRCODE_NONE; + + try + { + xRoot = xBlkRoot->openStorageElement( rPackageName, embed::ElementModes::WRITE ); + uno::Reference < io::XStream > xDocStream = xRoot->openStreamElement( aStreamName, + embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ); + + uno::Reference < beans::XPropertySet > xSet( xDocStream, uno::UNO_QUERY ); + xSet->setPropertyValue("MediaType", Any(OUString( "text/xml" )) ); + uno::Reference < io::XOutputStream > xOut = xDocStream->getOutputStream(); + xWriter->setOutputStream(xOut); + + rtl::Reference<SwXMLTextBlockExport> xExp( new SwXMLTextBlockExport( xContext, *this, GetXMLToken ( XML_UNFORMATTED_TEXT ), xWriter) ); + + xExp->exportDoc( rText ); + + uno::Reference < embed::XTransactedObject > xTrans( xRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + + if (! (nFlags & SwXmlFlags::NoRootCommit) ) + { + uno::Reference < embed::XTransactedObject > xTmpTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTmpTrans.is() ) + xTmpTrans->commit(); + } + } + catch ( uno::Exception& ) + { + nRes = ERR_SWG_WRITE_ERROR; + } + + xRoot = nullptr; + + //TODO/LATER: error handling + /* + sal_uLong nErr = xBlkRoot->GetError(); + sal_uLong nRes = 0; + if( nErr == SVSTREAM_DISK_FULL ) + nRes = ERR_W4W_WRITE_FULL; + else if( nErr != ERRCODE_NONE ) + nRes = ERR_SWG_WRITE_ERROR; + */ + if( !nRes ) // So that we can access the Doc via GetText & nCur + MakeBlockText( rText ); + + return nRes; +} + +void SwXMLTextBlocks::ReadInfo() +{ + const OUString sDocName( XMLN_BLOCKLIST ); + try + { + if ( !xBlkRoot.is() || !xBlkRoot->hasByName( sDocName ) || !xBlkRoot->isStreamElement( sDocName ) ) + return; + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = sDocName; + + uno::Reference < io::XStream > xDocStream = xBlkRoot->openStreamElement( sDocName, embed::ElementModes::READ ); + aParserInput.aInputStream = xDocStream->getInputStream(); + + // get filter + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SwXMLBlockListImport( xContext, *this ); + uno::Reference< xml::sax::XFastTokenHandler > xTokenHandler = new SwXMLBlockListTokenHandler(); + + // connect parser and filter + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST ); + xParser->setTokenHandler( xTokenHandler ); + + // parse + xParser->parseStream( aParserInput ); + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("sw", "when loading " << sDocName); + // re throw ? + } +} +void SwXMLTextBlocks::WriteInfo() +{ + if ( xBlkRoot.is() || ERRCODE_NONE == OpenFile ( false ) ) + { + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + OUString sDocName( XMLN_BLOCKLIST ); + + /* + if ( xBlkRoot->IsContained( sDocName) ) + { + xBlkRoot->Remove ( sDocName ); + xBlkRoot->Commit(); + } + */ + + try + { + uno::Reference < io::XStream > xDocStream = xBlkRoot->openStreamElement( sDocName, + embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ); + + uno::Reference < beans::XPropertySet > xSet( xDocStream, uno::UNO_QUERY ); + xSet->setPropertyValue("MediaType", Any(OUString( "text/xml" )) ); + uno::Reference < io::XOutputStream > xOut = xDocStream->getOutputStream(); + xWriter->setOutputStream(xOut); + + rtl::Reference<SwXMLBlockListExport> xExp(new SwXMLBlockListExport( xContext, *this, XMLN_BLOCKLIST, xWriter) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + uno::Reference < embed::XTransactedObject > xTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + } + catch ( uno::Exception& ) + { + } + + m_bInfoChanged = false; + return; + } +} + +ErrCode SwXMLTextBlocks::SetMacroTable( + sal_uInt16 nIdx, + const SvxMacroTableDtor& rMacroTable ) +{ + // set current autotext + m_aShort = m_aNames[nIdx]->aShort; + m_aLong = m_aNames[nIdx]->aLong; + aPackageName = m_aNames[nIdx]->aPackageName; + + // start XML autotext event export + ErrCode nRes = ERRCODE_NONE; + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + // Get model + uno::Reference< lang::XComponent > xModelComp = + m_xDoc->GetDocShell()->GetModel(); + OSL_ENSURE( xModelComp.is(), "XMLWriter::Write: got no model" ); + if( !xModelComp.is() ) + return ERR_SWG_WRITE_ERROR; + + // open stream in proper sub-storage + CloseFile(); // close (it may be open in read-only-mode) + nRes = OpenFile ( false ); + + if ( ERRCODE_NONE == nRes ) + { + try + { + xRoot = xBlkRoot->openStorageElement( aPackageName, embed::ElementModes::WRITE ); + bool bOasis = SotStorage::GetVersion( xRoot ) > SOFFICE_FILEFORMAT_60; + + uno::Reference < io::XStream > xDocStream = xRoot->openStreamElement( "atevent.xml", + embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ); + + uno::Reference < beans::XPropertySet > xSet( xDocStream, uno::UNO_QUERY ); + xSet->setPropertyValue("MediaType", Any(OUString( "text/xml" )) ); + uno::Reference < io::XOutputStream > xOutputStream = xDocStream->getOutputStream(); + + // get XML writer + uno::Reference< xml::sax::XWriter > xSaxWriter = + xml::sax::Writer::create( xContext ); + + // connect XML writer to output stream + xSaxWriter->setOutputStream( xOutputStream ); + + // construct events object + uno::Reference<XNameAccess> xEvents = + new SvMacroTableEventDescriptor(rMacroTable,aAutotextEvents); + + // prepare arguments (prepend doc handler to given arguments) + Sequence<Any> aParams(2); + aParams[0] <<= xSaxWriter; + aParams[1] <<= xEvents; + + // get filter component + OUString sFilterComponent = bOasis + ? OUString("com.sun.star.comp.Writer.XMLOasisAutotextEventsExporter") + : OUString("com.sun.star.comp.Writer.XMLAutotextEventsExporter"); + uno::Reference< document::XExporter > xExporter( + xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + sFilterComponent, aParams, xContext), UNO_QUERY); + OSL_ENSURE( xExporter.is(), + "can't instantiate export filter component" ); + if( xExporter.is() ) + { + // connect model and filter + xExporter->setSourceDocument( xModelComp ); + + // filter! + Sequence<beans::PropertyValue> aFilterProps( 0 ); + uno::Reference < document::XFilter > xFilter( xExporter, + UNO_QUERY ); + xFilter->filter( aFilterProps ); + } + else + nRes = ERR_SWG_WRITE_ERROR; + + // finally, commit stream, sub-storage and storage + uno::Reference < embed::XTransactedObject > xTmpTrans( xRoot, uno::UNO_QUERY ); + if ( xTmpTrans.is() ) + xTmpTrans->commit(); + + uno::Reference < embed::XTransactedObject > xTrans( xBlkRoot, uno::UNO_QUERY ); + if ( xTrans.is() ) + xTrans->commit(); + + xRoot = nullptr; + } + catch ( uno::Exception& ) + { + nRes = ERR_SWG_WRITE_ERROR; + } + + CloseFile(); + } + else + nRes = ERR_SWG_WRITE_ERROR; + + return nRes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/swg/TextBlockTokens.txt b/sw/source/core/swg/TextBlockTokens.txt new file mode 100644 index 000000000..8698704fc --- /dev/null +++ b/sw/source/core/swg/TextBlockTokens.txt @@ -0,0 +1,5 @@ +body +text +document +document-content +p diff --git a/sw/source/core/swg/swblocks.cxx b/sw/source/core/swg/swblocks.cxx new file mode 100644 index 000000000..803c1bb0b --- /dev/null +++ b/sw/source/core/swg/swblocks.cxx @@ -0,0 +1,569 @@ +/* -*- 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 <algorithm> + +#include <osl/diagnose.h> +#include <tools/urlobj.hxx> +#include <svl/fstathelper.hxx> +#include <svl/macitem.hxx> +#include <unotools/charclass.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <shellio.hxx> +#include <swblocks.hxx> +#include <SwXMLTextBlocks.hxx> + +#include <swerror.h> + +/** + * Calculate hash code (is not guaranteed to be unique) + */ +sal_uInt16 SwImpBlocks::Hash( const OUString& r ) +{ + sal_uInt16 n = 0; + // std::min requires an explicit cast to sal_Int32 on 32bit platforms + const sal_Int32 nLen = std::min(r.getLength(), static_cast<sal_Int32>(8)); + for (sal_Int32 i=0; i<nLen; ++i) + { + n = ( n << 1 ) + r[i]; + } + return n; +} + +SwBlockName::SwBlockName( const OUString& rShort, const OUString& rLong ) + : aShort( rShort ), aLong( rLong ), aPackageName (rShort), + bIsOnlyTextFlagInit( false ), bIsOnlyText( false ) +{ + nHashS = SwImpBlocks::Hash( rShort ); + nHashL = SwImpBlocks::Hash( rLong ); +} + +SwBlockName::SwBlockName( const OUString& rShort, const OUString& rLong, const OUString& rPackageName) + : aShort( rShort ), aLong( rLong ), aPackageName (rPackageName), + bIsOnlyTextFlagInit( false ), bIsOnlyText( false ) +{ + nHashS = SwImpBlocks::Hash( rShort ); + nHashL = SwImpBlocks::Hash( rLong ); +} + +/** + * Is the provided file a storage or doesn't it exist? + */ +SwImpBlocks::FileType SwImpBlocks::GetFileType( const OUString& rFile ) +{ + if( !FStatHelper::IsDocument( rFile ) ) + return FileType::NoFile; + if( SwXMLTextBlocks::IsFileUCBStorage( rFile ) ) + return FileType::XML; + //otherwise return NONE + return FileType::None; +} + +SwImpBlocks::SwImpBlocks( const OUString& rFile ) + : m_aFile( rFile ), + m_aDateModified( Date::EMPTY ), + m_aTimeModified( tools::Time::EMPTY ), + m_nCurrentIndex( USHRT_MAX ), + m_bReadOnly( true ), m_bInPutMuchBlocks( false ), + m_bInfoChanged(false) +{ + FStatHelper::GetModifiedDateTimeOfFile( rFile, + &m_aDateModified, &m_aTimeModified ); + INetURLObject aObj(rFile); + aObj.setExtension( OUString() ); + m_aName = aObj.GetBase(); +} + +SwImpBlocks::~SwImpBlocks() +{ +} + +/** + * Delete the document's content + */ +void SwImpBlocks::ClearDoc() +{ + m_xDoc->ClearDoc(); +} + +/** + * Creating a PaM, that spans the whole document + */ +std::unique_ptr<SwPaM> SwImpBlocks::MakePaM() +{ + std::unique_ptr<SwPaM> pPam(new SwPaM( m_xDoc->GetNodes().GetEndOfContent() )); + pPam->Move( fnMoveBackward, GoInDoc ); + pPam->SetMark(); + pPam->Move( fnMoveForward, GoInDoc ); + pPam->Exchange(); + return pPam; +} + +sal_uInt16 SwImpBlocks::GetCount() const +{ + return m_aNames.size(); +} + +/** + * Case Insensitive + */ +sal_uInt16 SwImpBlocks::GetIndex( const OUString& rShort ) const +{ + const OUString s( GetAppCharClass().uppercase( rShort ) ); + const sal_uInt16 nHash = Hash( s ); + for( size_t i = 0; i < m_aNames.size(); i++ ) + { + const SwBlockName* pName = m_aNames[ i ].get(); + if( pName->nHashS == nHash + && pName->aShort == s ) + return i; + } + return USHRT_MAX; +} + +sal_uInt16 SwImpBlocks::GetLongIndex( const OUString& rLong ) const +{ + sal_uInt16 nHash = Hash( rLong ); + for( size_t i = 0; i < m_aNames.size(); i++ ) + { + const SwBlockName* pName = m_aNames[ i ].get(); + if( pName->nHashL == nHash + && pName->aLong == rLong ) + return i; + } + return USHRT_MAX; +} + +OUString SwImpBlocks::GetShortName( sal_uInt16 n ) const +{ + if( n < m_aNames.size() ) + return m_aNames[n]->aShort; + return OUString(); +} + +OUString SwImpBlocks::GetLongName( sal_uInt16 n ) const +{ + if( n < m_aNames.size() ) + return m_aNames[n]->aLong; + return OUString(); +} + +OUString SwImpBlocks::GetPackageName( sal_uInt16 n ) const +{ + if( n < m_aNames.size() ) + return m_aNames[n]->aPackageName; + return OUString(); +} + +void SwImpBlocks::AddName( const OUString& rShort, const OUString& rLong, + bool bOnlyText ) +{ + sal_uInt16 nIdx = GetIndex( rShort ); + if( nIdx != USHRT_MAX ) + { + m_aNames.erase( m_aNames.begin() + nIdx ); + } + std::unique_ptr<SwBlockName> pNew(new SwBlockName( rShort, rLong )); + pNew->bIsOnlyTextFlagInit = true; + pNew->bIsOnlyText = bOnlyText; + m_aNames.insert( std::move(pNew) ); +} + +bool SwImpBlocks::IsFileChanged() const +{ + Date aTempDateModified( m_aDateModified ); + tools::Time aTempTimeModified( m_aTimeModified ); + return FStatHelper::GetModifiedDateTimeOfFile( m_aFile, &aTempDateModified, &aTempTimeModified ) && + ( m_aDateModified != aTempDateModified || + m_aTimeModified != aTempTimeModified ); +} + +void SwImpBlocks::Touch() +{ + FStatHelper::GetModifiedDateTimeOfFile( m_aFile, &m_aDateModified, &m_aTimeModified ); +} + +bool SwImpBlocks::IsOnlyTextBlock( const OUString& ) const +{ + return false; +} + +ErrCode SwImpBlocks::GetMacroTable( sal_uInt16, SvxMacroTableDtor& ) +{ + return ERRCODE_NONE; +} + +ErrCode SwImpBlocks::SetMacroTable( sal_uInt16 , const SvxMacroTableDtor& ) +{ + return ERRCODE_NONE; +} + +bool SwImpBlocks::PutMuchEntries( bool ) +{ + return false; +} + +SwTextBlocks::SwTextBlocks( const OUString& rFile ) + : m_nErr( 0 ) +{ + INetURLObject aObj(rFile); + const OUString sFileName = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + switch( SwImpBlocks::GetFileType( rFile ) ) + { + case SwImpBlocks::FileType::XML: m_pImp.reset( new SwXMLTextBlocks( sFileName ) ); break; + case SwImpBlocks::FileType::NoFile: m_pImp.reset( new SwXMLTextBlocks( sFileName ) ); break; + default: break; + } + if( !m_pImp ) + m_nErr = ERR_SWG_FILE_FORMAT_ERROR; +} + +SwTextBlocks::~SwTextBlocks() +{ +} + +OUString SwTextBlocks::GetName() const +{ + return m_pImp ? m_pImp->m_aName : OUString(); +} + +void SwTextBlocks::SetName( const OUString& r ) +{ + if( m_pImp ) + m_pImp->SetName( r ); +} + +sal_uInt16 SwTextBlocks::GetCount() const +{ + return m_pImp ? m_pImp->GetCount() : 0; +} + +sal_uInt16 SwTextBlocks::GetIndex( const OUString& r ) const +{ + return m_pImp ? m_pImp->GetIndex( r ) : USHRT_MAX; +} + +sal_uInt16 SwTextBlocks::GetLongIndex( const OUString& r ) const +{ + return m_pImp ? m_pImp->GetLongIndex( r ) : USHRT_MAX; +} + +OUString SwTextBlocks::GetShortName( sal_uInt16 n ) const +{ + if( m_pImp ) + return m_pImp->GetShortName( n ); + return OUString(); +} + +OUString SwTextBlocks::GetLongName( sal_uInt16 n ) const +{ + if( m_pImp ) + return m_pImp->GetLongName( n ); + return OUString(); +} + +bool SwTextBlocks::Delete( sal_uInt16 n ) +{ + if( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + { + if( m_pImp->IsFileChanged() ) + m_nErr = ERR_TXTBLOCK_NEWFILE_ERROR; + else if( ERRCODE_NONE == (m_nErr = m_pImp->OpenFile( false ) )) + { + m_nErr = m_pImp->Delete( n ); + if( !m_nErr ) + { + m_pImp->m_aNames.erase( m_pImp->m_aNames.begin() + n ); + } + if( n == m_pImp->m_nCurrentIndex ) + m_pImp->m_nCurrentIndex = USHRT_MAX; + if( !m_nErr ) + m_nErr = m_pImp->MakeBlockList(); + } + m_pImp->CloseFile(); + m_pImp->Touch(); + + return ( m_nErr == ERRCODE_NONE ); + } + return false; +} + +void SwTextBlocks::Rename( sal_uInt16 n, const OUString* s, const OUString* l ) +{ + if( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + { + m_pImp->m_nCurrentIndex = USHRT_MAX; + OUString aNew; + OUString aLong; + if( s ) + aNew = aLong = *s; + if( l ) + aLong = *l; + if( aNew.isEmpty() ) + { + OSL_ENSURE( false, "No short name provided in the rename" ); + m_nErr = ERR_SWG_INTERNAL_ERROR; + return; + } + + if( m_pImp->IsFileChanged() ) + m_nErr = ERR_TXTBLOCK_NEWFILE_ERROR; + else if( ERRCODE_NONE == ( m_nErr = m_pImp->OpenFile( false ))) + { + // Set the new entry in the list before we do that! + aNew = GetAppCharClass().uppercase( aNew ); + m_nErr = m_pImp->Rename( n, aNew ); + if( !m_nErr ) + { + bool bOnlyText = m_pImp->m_aNames[ n ]->bIsOnlyText; + m_pImp->m_aNames.erase( m_pImp->m_aNames.begin() + n ); + m_pImp->AddName( aNew, aLong, bOnlyText ); + m_nErr = m_pImp->MakeBlockList(); + } + } + m_pImp->CloseFile(); + m_pImp->Touch(); + } +} + +bool SwTextBlocks::BeginGetDoc( sal_uInt16 n ) +{ + if( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + { + if( m_pImp->IsFileChanged() ) + m_nErr = ERR_TXTBLOCK_NEWFILE_ERROR; + else if( ERRCODE_NONE == ( m_nErr = m_pImp->OpenFile())) + { + m_pImp->ClearDoc(); + m_nErr = m_pImp->GetDoc( n ); + if( m_nErr ) + m_pImp->m_nCurrentIndex = USHRT_MAX; + else + m_pImp->m_nCurrentIndex = n; + } + return ( m_nErr == ERRCODE_NONE ); + } + return false; +} + +void SwTextBlocks::EndGetDoc() +{ + if( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + m_pImp->CloseFile(); +} + +bool SwTextBlocks::BeginPutDoc( const OUString& s, const OUString& l ) +{ + if( m_pImp ) + { + bool bOk = m_pImp->m_bInPutMuchBlocks; + if( !bOk ) + { + if( m_pImp->IsFileChanged() ) + m_nErr = ERR_TXTBLOCK_NEWFILE_ERROR; + else + m_nErr = m_pImp->OpenFile( false ); + bOk = ERRCODE_NONE == m_nErr; + } + if( bOk ) + { + const OUString aNew = GetAppCharClass().uppercase(s); + m_nErr = m_pImp->BeginPutDoc( aNew, l ); + } + if( m_nErr ) + m_pImp->CloseFile(); + } + return ERRCODE_NONE == m_nErr; +} + +sal_uInt16 SwTextBlocks::PutDoc() +{ + sal_uInt16 nIdx = USHRT_MAX; + if( m_pImp ) + { + m_nErr = m_pImp->PutDoc(); + if( !m_nErr ) + { + m_pImp->m_nCurrentIndex = GetIndex( m_pImp->m_aShort ); + if( m_pImp->m_nCurrentIndex != USHRT_MAX ) + m_pImp->m_aNames[ m_pImp->m_nCurrentIndex ]->aLong = m_pImp->m_aLong; + else + { + m_pImp->AddName( m_pImp->m_aShort, m_pImp->m_aLong ); + m_pImp->m_nCurrentIndex = m_pImp->GetIndex( m_pImp->m_aShort ); + } + if( !m_pImp->m_bInPutMuchBlocks ) + m_nErr = m_pImp->MakeBlockList(); + } + if( !m_pImp->m_bInPutMuchBlocks ) + { + m_pImp->CloseFile(); + m_pImp->Touch(); + } + nIdx = m_pImp->m_nCurrentIndex; + } + return nIdx; +} + +sal_uInt16 SwTextBlocks::PutText( const OUString& rShort, const OUString& rName, + const OUString& rText ) +{ + sal_uInt16 nIdx = USHRT_MAX; + if( m_pImp ) + { + bool bOk = m_pImp->m_bInPutMuchBlocks; + if( !bOk ) + { + if( m_pImp->IsFileChanged() ) + m_nErr = ERR_TXTBLOCK_NEWFILE_ERROR; + else + m_nErr = m_pImp->OpenFile( false ); + bOk = ERRCODE_NONE == m_nErr; + } + if( bOk ) + { + OUString aNew = GetAppCharClass().uppercase( rShort ); + m_nErr = m_pImp->PutText( aNew, rName, rText ); + m_pImp->m_nCurrentIndex = USHRT_MAX; + if( !m_nErr ) + { + nIdx = GetIndex( m_pImp->m_aShort ); + if( nIdx != USHRT_MAX ) + m_pImp->m_aNames[ nIdx ]->aLong = rName; + else + { + m_pImp->AddName( m_pImp->m_aShort, rName, true ); + nIdx = m_pImp->GetIndex( m_pImp->m_aShort ); + } + if( !m_pImp->m_bInPutMuchBlocks ) + m_nErr = m_pImp->MakeBlockList(); + } + } + if( !m_pImp->m_bInPutMuchBlocks ) + { + m_pImp->CloseFile(); + m_pImp->Touch(); + } + } + return nIdx; +} + +SwDoc* SwTextBlocks::GetDoc() +{ + if( m_pImp ) + return m_pImp->m_xDoc.get(); + return nullptr; +} + +void SwTextBlocks::ClearDoc() +{ + if( m_pImp ) + { + m_pImp->ClearDoc(); + m_pImp->m_nCurrentIndex = USHRT_MAX; + } +} + +OUString const & SwTextBlocks::GetFileName() const +{ + return m_pImp->GetFileName(); +} + +bool SwTextBlocks::IsReadOnly() const +{ + return m_pImp->m_bReadOnly; +} + +bool SwTextBlocks::IsOnlyTextBlock( sal_uInt16 nIdx ) const +{ + bool bRet = false; + if( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + { + SwBlockName* pBlkNm = m_pImp->m_aNames[ nIdx ].get(); + if( !pBlkNm->bIsOnlyTextFlagInit && + !m_pImp->IsFileChanged() && !m_pImp->OpenFile() ) + { + pBlkNm->bIsOnlyText = m_pImp->IsOnlyTextBlock( pBlkNm->aShort ); + pBlkNm->bIsOnlyTextFlagInit = true; + m_pImp->CloseFile(); + } + bRet = pBlkNm->bIsOnlyText; + } + return bRet; +} + +bool SwTextBlocks::IsOnlyTextBlock( const OUString& rShort ) const +{ + sal_uInt16 nIdx = m_pImp->GetIndex( rShort ); + if( USHRT_MAX != nIdx ) + { + if( m_pImp->m_aNames[ nIdx ]->bIsOnlyTextFlagInit ) + return m_pImp->m_aNames[ nIdx ]->bIsOnlyText; + return IsOnlyTextBlock( nIdx ); + } + + OSL_ENSURE( false, "Invalid name" ); + return false; +} + +bool SwTextBlocks::GetMacroTable( sal_uInt16 nIdx, SvxMacroTableDtor& rMacroTable ) +{ + bool bRet = true; + if ( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + bRet = ( ERRCODE_NONE == m_pImp->GetMacroTable( nIdx, rMacroTable ) ); + return bRet; +} + +bool SwTextBlocks::SetMacroTable( sal_uInt16 nIdx, const SvxMacroTableDtor& rMacroTable ) +{ + bool bRet = true; + if ( m_pImp && !m_pImp->m_bInPutMuchBlocks ) + bRet = ( ERRCODE_NONE == m_pImp->SetMacroTable( nIdx, rMacroTable ) ); + return bRet; +} + +bool SwTextBlocks::StartPutMuchBlockEntries() +{ + bool bRet = false; + if( m_pImp ) + bRet = m_pImp->PutMuchEntries( true ); + return bRet; +} + +void SwTextBlocks::EndPutMuchBlockEntries() +{ + if( m_pImp ) + m_pImp->PutMuchEntries( false ); +} + +OUString SwTextBlocks::GetBaseURL() const +{ + if(m_pImp) + return m_pImp->GetBaseURL(); + return OUString(); +} + +void SwTextBlocks::SetBaseURL( const OUString& rURL ) +{ + if(m_pImp) + m_pImp->SetBaseURL(rURL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/table/swnewtable.cxx b/sw/source/core/table/swnewtable.cxx new file mode 100644 index 000000000..d1e724936 --- /dev/null +++ b/sw/source/core/table/swnewtable.cxx @@ -0,0 +1,2199 @@ +/* -*- 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 <swtable.hxx> +#include <tblsel.hxx> +#include <tblrwcl.hxx> +#include <ndtxt.hxx> +#include <node.hxx> +#include <UndoTable.hxx> +#include <pam.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <fmtfsize.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <cstdlib> +#include <vector> +#include <set> +#include <list> +#include <memory> +#include <editeng/boxitem.hxx> +#include <editeng/protitem.hxx> +#include <swtblfmt.hxx> +#include <calbck.hxx> +#include <sal/log.hxx> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +/** SwBoxSelection is a small helperclass (structure) to handle selections + of cells (boxes) between table functions + + It contains an "array" of table boxes, a rectangulare selection of table boxes. + To be more specific, it contains a vector of box selections, + every box selection (SwSelBoxes) contains the selected boxes inside one row. + The member mnMergeWidth contains the width of the selected boxes +*/ + +class SwBoxSelection +{ +public: + std::vector<SwSelBoxes> maBoxes; + long mnMergeWidth; + SwBoxSelection() : mnMergeWidth(0) {} + bool isEmpty() const { return maBoxes.empty(); } + void push_back(const SwSelBoxes& rNew) { maBoxes.push_back(rNew); } +}; + +/** NewMerge(..) removes the superfluous cells after cell merge + +SwTable::NewMerge(..) does some cleaning up, +it simply deletes the superfluous cells ("cell span") +and notifies the Undo about it. +The main work has been done by SwTable::PrepareMerge(..) already. + +@param rBoxes +the boxes to remove + +@param pUndo +the undo object to notify, maybe empty + +@return true for compatibility reasons with OldMerge(..) +*/ + +bool SwTable::NewMerge( SwDoc* pDoc, const SwSelBoxes& rBoxes, + const SwSelBoxes& rMerged, SwUndoTableMerge* pUndo ) +{ + if( pUndo ) + pUndo->SetSelBoxes( rBoxes ); + DeleteSel( pDoc, rBoxes, &rMerged, nullptr, true, true ); + + CHECK_TABLE( *this ) + return true; +} + +/** lcl_CheckMinMax helps evaluating (horizontal) min/max of boxes + +lcl_CheckMinMax(..) compares the left border and the right border +of a given cell with the given range and sets it accordingly. + +@param rMin +will be decremented if necessary to the left border of the cell + +@param rMax +will be incremented if necessary to the right border of the cell + +@param rLine +the row (table line) of the interesting box + +@param nCheck +the index of the box in the table box array of the given row + +@param bSet +if bSet is false, rMin and rMax will be manipulated if necessary +if bSet is true, rMin and rMax will be set to the left and right border of the box + +*/ + +static void lcl_CheckMinMax( long& rMin, long& rMax, const SwTableLine& rLine, size_t nCheck, bool bSet ) +{ + ++nCheck; + if( rLine.GetTabBoxes().size() < nCheck ) + { // robust + OSL_FAIL( "Box out of table line" ); + nCheck = rLine.GetTabBoxes().size(); + } + + long nNew = 0; // will be the right border of the current box + long nWidth = 0; // the width of the current box + for( size_t nCurrBox = 0; nCurrBox < nCheck; ++nCurrBox ) + { + SwTableBox* pBox = rLine.GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + nWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nNew += nWidth; + } + // nNew is the right border of the wished box + if( bSet || nNew > rMax ) + rMax = nNew; + nNew -= nWidth; // nNew becomes the left border of the wished box + if( bSet || nNew < rMin ) + rMin = nNew; +} + +/** lcl_Box2LeftBorder(..) delivers the left (logical) border of a table box + +The left logical border of a table box is the sum of the cell width before this +box. + +@param rBox +is the requested table box + +@return is the left logical border (long, even it cannot be negative) + +*/ + +static long lcl_Box2LeftBorder( const SwTableBox& rBox ) +{ + if( !rBox.GetUpper() ) + return 0; + long nLeft = 0; + const SwTableLine &rLine = *rBox.GetUpper(); + const size_t nCount = rLine.GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = rLine.GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox == &rBox ) + return nLeft; + nLeft += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + OSL_FAIL( "Box not found in own upper?" ); + return nLeft; +} + +/** lcl_LeftBorder2Box delivers the box to a given left border + +It's used to find the master/follow table boxes in previous/next rows. +Don't call this function to check if there is such a box, +call it if you know there has to be such box. + +@param nLeft +the left border (logical x-value) of the demanded box + +@param rLine +the row (table line) to be scanned + +@return a pointer to the table box inside the given row with the wished left border + +*/ + +static SwTableBox* lcl_LeftBorder2Box( long nLeft, const SwTableLine* pLine ) +{ + if( !pLine ) + return nullptr; + long nCurrLeft = 0; + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox->GetFrameFormat()->GetFrameSize().GetWidth() ) + { + if( nCurrLeft == nLeft ) + return pBox; + // HACK: It appears that rounding errors may result in positions not matching + // exactly, so allow a little tolerance. This happens at least with merged cells + // in the doc from fdo#38414 . + if( std::abs( nCurrLeft - nLeft ) <= ( nLeft / 1000 )) + return pBox; + if( nCurrLeft >= nLeft ) + { + SAL_WARN( "sw.core", "Possibly wrong box found" ); + return pBox; + } + } + nCurrLeft += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + OSL_FAIL( "Didn't find wished box" ); + return nullptr; +} + +/** lcl_ChangeRowSpan corrects row span after insertion/deletion of rows + +lcl_ChangeRowSpan(..) has to be called after an insertion or deletion of rows +to adjust the row spans of previous rows accordingly. +If rows are deleted, the previous rows with row spans into the deleted area +have to be decremented by the number of _overlapped_ inserted rows. +If rows are inserted, the previous rows with row span into the inserted area +have to be incremented by the number of inserted rows. +For those row spans which ends exactly above the inserted area it has to be +decided by the parameter bSingle if they have to be expanded or not. + +@param rTable +the table to manipulate (has to be a new model table) + +@param nDiff +the number of rows which has been inserted (nDiff > 0) or deleted (nDiff < 0) + +@param nRowIdx +the index of the first row which has to be checked + +@param bSingle +true if the new inserted row should not extend row spans which ends in the row above +this is for rows inserted by UI "insert row" +false if all cells of an inserted row has to be overlapped by the previous row +this is for rows inserted by "split row" +false is also needed for deleted rows + +*/ + +static void lcl_ChangeRowSpan( const SwTable& rTable, const long nDiff, + sal_uInt16 nRowIdx, const bool bSingle ) +{ + if( !nDiff || nRowIdx >= rTable.GetTabLines().size() ) + return; + OSL_ENSURE( !bSingle || nDiff > 0, "Don't set bSingle when deleting lines!" ); + bool bGoOn; + // nDistance is the distance between the current row and the critical row, + // e.g. the deleted rows or the inserted rows. + // If the row span is lower than the distance there is nothing to do + // because the row span ends before the critical area. + // When the inserted rows should not be overlapped by row spans which ends + // exactly in the row above, the trick is to start with a distance of 1. + long nDistance = bSingle ? 1 : 0; + do + { + bGoOn = false; // will be set to true if we found a non-master cell + // which has to be manipulated => we have to check the previous row, too. + const SwTableLine* pLine = rTable.GetTabLines()[ nRowIdx ]; + const size_t nBoxCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nBoxCount; ++nCurrBox ) + { + long nRowSpan = pLine->GetTabBoxes()[nCurrBox]->getRowSpan(); + long nAbsSpan = nRowSpan > 0 ? nRowSpan : -nRowSpan; + // Check if the last overlapped cell is above or below + // the critical area + if( nAbsSpan > nDistance ) + { + if( nDiff > 0 ) + { + if( nRowSpan > 0 ) + nRowSpan += nDiff; // increment row span of master cell + else + { + nRowSpan -= nDiff; // increment row span of non-master cell + bGoOn = true; + } + } + else + { + if( nRowSpan > 0 ) + { // A master cell + // end of row span behind the deleted area .. + if( nRowSpan - nDistance > -nDiff ) + nRowSpan += nDiff; + else // .. or inside the deleted area + nRowSpan = nDistance + 1; + } + else + { // Same for a non-master cell + if( nRowSpan + nDistance < nDiff ) + nRowSpan -= nDiff; + else + nRowSpan = -nDistance - 1; + bGoOn = true; // We have to continue + } + } + pLine->GetTabBoxes()[ nCurrBox ]->setRowSpan( nRowSpan ); + } + } + ++nDistance; + if( nRowIdx ) + --nRowIdx; + else + bGoOn = false; //robust + } while( bGoOn ); +} + +/** CollectBoxSelection(..) create a rectangulare selection based on the given SwPaM + and prepares the selected cells for merging +*/ + +std::unique_ptr<SwBoxSelection> SwTable::CollectBoxSelection( const SwPaM& rPam ) const +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + if( m_aLines.empty() ) + return nullptr; + const SwNode* pStartNd = rPam.Start()->nNode.GetNode().FindTableBoxStartNode(); + const SwNode* pEndNd = rPam.End()->nNode.GetNode().FindTableBoxStartNode(); + if( !pStartNd || !pEndNd || pStartNd == pEndNd ) + return nullptr; + + const size_t nLines = m_aLines.size(); + size_t nTop = 0; + size_t nBottom = 0; + long nMin = 0, nMax = 0; + int nFound = 0; + for( size_t nRow = 0; nFound < 2 && nRow < nLines; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + OSL_ENSURE( pBox, "Missing table box" ); + if( nFound ) + { + if( pBox->GetSttNd() == pEndNd ) + { + nBottom = nRow; + lcl_CheckMinMax( nMin, nMax, *pLine, nCol, false ); + ++nFound; + break; + } + } + else if( pBox->GetSttNd() == pStartNd ) + { + nTop = nRow; + lcl_CheckMinMax( nMin, nMax, *pLine, nCol, true ); + ++nFound; + } + } + } + if( nFound < 2 ) + return nullptr; + + bool bOkay = true; + long nMid = ( nMin + nMax ) / 2; + + auto pRet(std::make_unique<SwBoxSelection>()); + std::vector< std::pair< SwTableBox*, long > > aNewWidthVector; + size_t nCheckBottom = nBottom; + long nLeftSpan = 0; + long nRightSpan = 0; + long nLeftSpanCnt = 0; + long nRightSpanCnt = 0; + for( size_t nRow = nTop; nRow <= nBottom && bOkay && nRow < nLines; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + SwSelBoxes aBoxes; + long nRight = 0; + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + long nLeft = nRight; + nRight += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + long nRowSpan = pBox->getRowSpan(); + if( nRight <= nMin ) + { + if( nRight == nMin && nLeftSpanCnt ) + bOkay = false; + continue; + } + SwTableBox* pInnerBox = nullptr; + SwTableBox* pLeftBox = nullptr; + SwTableBox* pRightBox = nullptr; + long nDiff = 0; + long nDiff2 = 0; + if( nLeft < nMin ) + { + if( nRight >= nMid || nRight + nLeft >= nMin + nMin ) + { + if( nCurrBox ) + { + aBoxes.insert(pBox); + pInnerBox = pBox; + pLeftBox = pLine->GetTabBoxes()[nCurrBox-1]; + nDiff = nMin - nLeft; + if( nRight > nMax ) + { + if( nCurrBox+1 < nCount ) + { + pRightBox = pLine->GetTabBoxes()[nCurrBox+1]; + nDiff2 = nRight - nMax; + } + else + bOkay = false; + } + else if( nRightSpanCnt && nRight == nMax ) + bOkay = false; + } + else + bOkay = false; + } + else if( nCurrBox+1 < nCount ) + { + pLeftBox = pBox; + pInnerBox = pLine->GetTabBoxes()[nCurrBox+1]; + nDiff = nMin - nRight; + } + else + bOkay = false; + } + else if( nRight <= nMax ) + { + aBoxes.insert(pBox); + if( nRow == nTop && nRowSpan < 0 ) + { + bOkay = false; + break; + } + if( nRowSpan > 1 && nRow + nRowSpan - 1 > nBottom ) + nBottom = nRow + nRowSpan - 1; + if( nRowSpan < -1 && nRow - nRowSpan - 1 > nBottom ) + nBottom = nRow - nRowSpan - 1; + if( nRightSpanCnt && nRight == nMax ) + bOkay = false; + } + else if( nLeft < nMax ) + { + if( nLeft <= nMid || nRight + nLeft <= nMax ) + { + if( nCurrBox+1 < nCount ) + { + aBoxes.insert(pBox); + pInnerBox = pBox; + pRightBox = pLine->GetTabBoxes()[nCurrBox+1]; + nDiff = nRight - nMax; + } + else + bOkay = false; + } + else if( nCurrBox ) + { + pRightBox = pBox; + pInnerBox = pLine->GetTabBoxes()[nCurrBox-1]; + nDiff = nLeft - nMax; + } + else + bOkay = false; + } + else + break; + if( pInnerBox ) + { + if( nRow == nBottom ) + { + long nTmpSpan = pInnerBox->getRowSpan(); + if( nTmpSpan > 1 ) + nBottom += nTmpSpan - 1; + else if( nTmpSpan < -1 ) + nBottom -= nTmpSpan + 1; + } + SwTableBox* pOuterBox = pLeftBox; + do + { + if( pOuterBox ) + { + long nOutSpan = pOuterBox->getRowSpan(); + if( nOutSpan != 1 ) + { + size_t nCheck = nRow; + if( nOutSpan < 0 ) + { + const SwTableBox& rBox = + pOuterBox->FindStartOfRowSpan( *this ); + nOutSpan = rBox.getRowSpan(); + const SwTableLine* pTmpL = rBox.GetUpper(); + nCheck = GetTabLines().GetPos( pTmpL ); + if( nCheck < nTop ) + bOkay = false; + if( pOuterBox == pLeftBox ) + { + if( !nLeftSpanCnt || nMin - nDiff != nLeftSpan ) + bOkay = false; + } + else + { + if( !nRightSpanCnt || nMax + nDiff != nRightSpan ) + bOkay = false; + } + } + else + { + if( pOuterBox == pLeftBox ) + { + if( nLeftSpanCnt ) + bOkay = false; + nLeftSpan = nMin - nDiff; + nLeftSpanCnt = nOutSpan; + } + else + { + if( nRightSpanCnt ) + bOkay = false; + nRightSpan = nMax + nDiff; + nRightSpanCnt = nOutSpan; + } + } + nCheck += nOutSpan - 1; + if( nCheck > nCheckBottom ) + nCheckBottom = nCheck; + } + else if( ( nLeftSpanCnt && pLeftBox == pOuterBox ) || + ( nRightSpanCnt && pRightBox == pOuterBox ) ) + bOkay = false; + std::pair< SwTableBox*, long > aTmp; + aTmp.first = pInnerBox; + aTmp.second = -nDiff; + aNewWidthVector.push_back(aTmp); + aTmp.first = pOuterBox; + aTmp.second = nDiff; + aNewWidthVector.push_back(aTmp); + } + pOuterBox = pOuterBox == pRightBox ? nullptr : pRightBox; + if( nDiff2 ) + nDiff = nDiff2; + } while( pOuterBox ); + } + } + if( nLeftSpanCnt ) + --nLeftSpanCnt; + if( nRightSpanCnt ) + --nRightSpanCnt; + pRet->push_back(aBoxes); + } + if( nCheckBottom > nBottom ) + bOkay = false; + if( bOkay ) + { + pRet->mnMergeWidth = nMax - nMin; + for (auto const& newWidth : aNewWidthVector) + { + SwFrameFormat* pFormat = newWidth.first->ClaimFrameFormat(); + long nNewWidth = pFormat->GetFrameSize().GetWidth() + newWidth.second; + pFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nNewWidth, 0 ) ); + } + } + else + pRet.reset(); + + return pRet; +} + +/** lcl_InvalidateCellFrame(..) invalidates all layout representations of a given cell + to initiate a reformatting +*/ + +static void lcl_InvalidateCellFrame( const SwTableBox& rBox ) +{ + SwIterator<SwCellFrame,SwFormat> aIter( *rBox.GetFrameFormat() ); + for( SwCellFrame* pCell = aIter.First(); pCell; pCell = aIter.Next() ) + { + if( pCell->GetTabBox() == &rBox ) + { + pCell->InvalidateSize(); + SwFrame* pLower = pCell->GetLower(); + if( pLower ) + pLower->InvalidateSize_(); + } + } +} + +/** lcl_InsertPosition(..) evaluates the insert positions in every table line, + when a selection of cells is given and returns the average cell widths +*/ + +static long lcl_InsertPosition( SwTable &rTable, std::vector<sal_uInt16>& rInsPos, + const SwSelBoxes& rBoxes, bool bBehind ) +{ + sal_Int32 nAddWidth = 0; + long nCount = 0; + for (size_t j = 0; j < rBoxes.size(); ++j) + { + SwTableBox *pBox = rBoxes[j]; + SwTableLine* pLine = pBox->GetUpper(); + long nWidth = rBoxes[j]->GetFrameFormat()->GetFrameSize().GetWidth(); + nAddWidth += nWidth; + sal_uInt16 nCurrBox = pLine->GetBoxPos( pBox ); + sal_uInt16 nCurrLine = rTable.GetTabLines().GetPos( pLine ); + OSL_ENSURE( nCurrLine != USHRT_MAX, "Time to say Good-Bye.." ); + if( rInsPos[ nCurrLine ] == USHRT_MAX ) + { + rInsPos[ nCurrLine ] = nCurrBox; + ++nCount; + } + else if( ( rInsPos[ nCurrLine ] > nCurrBox ) == !bBehind ) + rInsPos[ nCurrLine ] = nCurrBox; + } + if( nCount ) + nAddWidth /= nCount; + return nAddWidth; +} + +/** SwTable::NewInsertCol(..) insert new column(s) into a table + +@param pDoc +the document + +@param rBoxes +the selected boxes + +@param nCnt +the number of columns to insert + +@param bBehind +insertion behind (true) or before (false) the selected boxes + +@return true, if any insertion has been done successfully + +*/ + +bool SwTable::NewInsertCol( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind ) +{ + if( m_aLines.empty() || !nCnt ) + return false; + + CHECK_TABLE( *this ) + long nNewBoxWidth = 0; + std::vector< sal_uInt16 > aInsPos( m_aLines.size(), USHRT_MAX ); + { // Calculation of the insert positions and the width of the new boxes + sal_uInt64 nTableWidth = 0; + for( size_t i = 0; i < m_aLines[0]->GetTabBoxes().size(); ++i ) + nTableWidth += m_aLines[0]->GetTabBoxes()[i]->GetFrameFormat()->GetFrameSize().GetWidth(); + + // Fill the vector of insert positions and the (average) width to insert + sal_uInt64 nAddWidth = lcl_InsertPosition( *this, aInsPos, rBoxes, bBehind ); + + // Given is the (average) width of the selected boxes, if we would + // insert nCnt of columns the table would grow + // So we will shrink the table first, then insert the new boxes and + // get a table with the same width than before. + // But we will not shrink the table by the full already calculated value, + // we will reduce this value proportional to the old table width + nAddWidth *= nCnt; // we have to insert nCnt boxes per line + sal_uInt64 nResultingWidth = nAddWidth + nTableWidth; + if( !nResultingWidth ) + return false; + nAddWidth = (nAddWidth * nTableWidth) / nResultingWidth; + nNewBoxWidth = long( nAddWidth / nCnt ); // Rounding + nAddWidth = nNewBoxWidth * nCnt; // Rounding + if( !nAddWidth || nAddWidth >= nTableWidth ) + return false; + AdjustWidths( static_cast< long >(nTableWidth), static_cast< long >(nTableWidth - nAddWidth) ); + } + + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + + SwTableNode* pTableNd = GetTableNode(); + std::vector<SwTableBoxFormat*> aInsFormat( nCnt, nullptr ); + size_t nLastLine = SAL_MAX_SIZE; + long nLastRowSpan = 1; + + for( size_t i = 0; i < m_aLines.size(); ++i ) + { + SwTableLine* pLine = m_aLines[ i ]; + sal_uInt16 nInsPos = aInsPos[i]; + assert(nInsPos != USHRT_MAX); // didn't find insert position + SwTableBox* pBox = pLine->GetTabBoxes()[ nInsPos ]; + if( bBehind ) + ++nInsPos; + SwTableBoxFormat* pBoxFrameFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + ::InsTableBox( pDoc, pTableNd, pLine, pBoxFrameFormat, pBox, nInsPos, nCnt ); + long nRowSpan = pBox->getRowSpan(); + long nDiff = i - nLastLine; + bool bNewSpan = false; + if( nLastLine != SAL_MAX_SIZE && nDiff <= nLastRowSpan && + nRowSpan != nDiff - nLastRowSpan ) + { + bNewSpan = true; + while( nLastLine < i ) + { + SwTableLine* pTmpLine = m_aLines[ nLastLine ]; + sal_uInt16 nTmpPos = aInsPos[nLastLine]; + if( bBehind ) + ++nTmpPos; + for( sal_uInt16 j = 0; j < nCnt; ++j ) + pTmpLine->GetTabBoxes()[nTmpPos+j]->setRowSpan( nDiff ); + if( nDiff > 0 ) + nDiff = -nDiff; + ++nDiff; + ++nLastLine; + } + } + if( nRowSpan > 0 ) + bNewSpan = true; + if( bNewSpan ) + { + nLastLine = i; + if( nRowSpan < 0 ) + nLastRowSpan = -nRowSpan; + else + nLastRowSpan = nRowSpan; + } + const SvxBoxItem& aSelBoxItem = pBoxFrameFormat->GetBox(); + std::unique_ptr<SvxBoxItem> pNoRightBorder; + if( aSelBoxItem.GetRight() ) + { + pNoRightBorder.reset( new SvxBoxItem( aSelBoxItem )); + pNoRightBorder->SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + for( sal_uInt16 j = 0; j < nCnt; ++j ) + { + SwTableBox *pCurrBox = pLine->GetTabBoxes()[nInsPos+j]; + if( bNewSpan ) + { + pCurrBox->setRowSpan( nLastRowSpan ); + SwFrameFormat* pFrameFormat = pCurrBox->ClaimFrameFormat(); + SwFormatFrameSize aFrameSz( pFrameFormat->GetFrameSize() ); + aFrameSz.SetWidth( nNewBoxWidth ); + pFrameFormat->SetFormatAttr( aFrameSz ); + if( pNoRightBorder && ( !bBehind || j+1 < nCnt ) ) + pFrameFormat->SetFormatAttr( *pNoRightBorder ); + aInsFormat[j] = static_cast<SwTableBoxFormat*>(pFrameFormat); + } + else + pCurrBox->ChgFrameFormat( aInsFormat[j] ); + } + if( bBehind && pNoRightBorder ) + { + SwFrameFormat* pFrameFormat = pBox->ClaimFrameFormat(); + pFrameFormat->SetFormatAttr( *pNoRightBorder ); + } + } + + aFndBox.MakeFrames( *this ); +#if OSL_DEBUG_LEVEL > 0 + { + const SwTableBoxes &rTabBoxes = m_aLines[0]->GetTabBoxes(); + long nNewWidth = 0; + for( size_t i = 0; i < rTabBoxes.size(); ++i ) + nNewWidth += rTabBoxes[i]->GetFrameFormat()->GetFrameSize().GetWidth(); + OSL_ENSURE( nNewWidth > 0, "Very small" ); + } +#endif + CHECK_TABLE( *this ) + + return true; +} + +/** SwTable::PrepareMerge(..) some preparation for the coming Merge(..) + +For the old table model, ::GetMergeSel(..) is called only, +for the new table model, PrepareMerge does the main work. +It modifies all cells to merge (width, border, rowspan etc.) and collects +the cells which have to be deleted by Merge(..) afterwards. +If there are superfluous rows, these cells are put into the deletion list as well. + +@param rPam +the selection to merge + +@param rBoxes +should be empty at the beginning, at the end it is filled with boxes to delete. + +@param ppMergeBox +will be set to the master cell box + +@param pUndo +the undo object to record all changes +can be Null, e.g. when called by Redo(..) + +@return + +*/ + +bool SwTable::PrepareMerge( const SwPaM& rPam, SwSelBoxes& rBoxes, + SwSelBoxes& rMerged, SwTableBox** ppMergeBox, SwUndoTableMerge* pUndo ) +{ + if( !m_bNewModel ) + { + ::GetMergeSel( rPam, rBoxes, ppMergeBox, pUndo ); + return rBoxes.size() > 1; + } + CHECK_TABLE( *this ) + // We have to assert a "rectangular" box selection before we start to merge + std::unique_ptr< SwBoxSelection > pSel( CollectBoxSelection( rPam ) ); + if (!pSel || pSel->isEmpty()) + return false; + // Now we should have a rectangle of boxes, + // i.e. contiguous cells in contiguous rows + bool bMerge = false; // will be set if any content is transferred from + // a "not already overlapped" cell into the new master cell. + const SwSelBoxes& rFirstBoxes = pSel->maBoxes[0]; + if (rFirstBoxes.empty()) + return false; + SwTableBox *pMergeBox = rFirstBoxes[0]; // the master cell box + if( !pMergeBox ) + return false; + (*ppMergeBox) = pMergeBox; + // The new master box will get the left and the top border of the top-left + // box of the selection and because the new master cell _is_ the top-left + // box, the left and right border does not need to be changed. + // The right and bottom border instead has to be derived from the right- + // bottom box of the selection. If this is an overlapped cell, + // the appropriate master box. + SwTableBox* pLastBox = nullptr; // the right-bottom (master) cell + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + SwPosition aInsPos( *pMergeBox->GetSttNd()->EndOfSectionNode() ); + SwPaM aChkPam( aInsPos ); + // The number of lines in the selection rectangle: nLineCount + const size_t nLineCount = pSel->maBoxes.size(); + // BTW: nLineCount is the rowspan of the new master cell + long nRowSpan = static_cast<long>(nLineCount); + // We will need the first and last line of the selection + // to check if there any superfluous row after merging + SwTableLine* pFirstLn = nullptr; + SwTableLine* pLastLn = nullptr; + // Iteration over the lines of the selection... + for( size_t nCurrLine = 0; nCurrLine < nLineCount; ++nCurrLine ) + { + // The selected boxes in the current line + const SwSelBoxes& rLineBoxes = pSel->maBoxes[nCurrLine]; + size_t nColCount = rLineBoxes.size(); + // Iteration over the selected cell in the current row + for (size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol) + { + SwTableBox* pBox = rLineBoxes[nCurrCol]; + rMerged.insert( pBox ); + // Only the first selected cell in every row will be alive, + // the other will be deleted => put into rBoxes + if( nCurrCol ) + rBoxes.insert( pBox ); + else + { + if( nCurrLine == 1 ) + pFirstLn = pBox->GetUpper(); // we need this line later on + if( nCurrLine + 1 == nLineCount ) + pLastLn = pBox->GetUpper(); // and this one, too. + } + // A box has to be merged if it's not the master box itself, + // but an already overlapped cell must not be merged as well. + bool bDoMerge = pBox != pMergeBox && pBox->getRowSpan() > 0; + // The last box has to be in the last "column" of the selection + // and it has to be a master cell + if( nCurrCol+1 == nColCount && pBox->getRowSpan() > 0 ) + pLastBox = pBox; + if( bDoMerge ) + { + bMerge = true; + // If the cell to merge contains only one empty paragraph, + // we do not transfer this paragraph. + if( !IsEmptyBox( *pBox, aChkPam ) ) + { + SwNodeIndex& rInsPosNd = aInsPos.nNode; + SwPaM aPam( aInsPos ); + aPam.GetPoint()->nNode.Assign( *pBox->GetSttNd()->EndOfSectionNode(), -1 ); + SwContentNode* pCNd = aPam.GetContentNode(); + aPam.GetPoint()->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + SwNodeIndex aSttNdIdx( *pBox->GetSttNd(), 1 ); + bool const bUndo = pDoc->GetIDocumentUndoRedo().DoesUndo(); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().DoUndo(false); + } + pDoc->getIDocumentContentOperations().AppendTextNode( *aPam.GetPoint() ); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().DoUndo(bUndo); + } + SwNodeRange aRg( aSttNdIdx, aPam.GetPoint()->nNode ); + if( pUndo ) + pUndo->MoveBoxContent( pDoc, aRg, rInsPosNd ); + else + { + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, rInsPosNd, + SwMoveFlags::NO_DELFRMS ); + } + } + } + // Only the cell of the first selected column will stay alive + // and got a new row span + if( !nCurrCol ) + pBox->setRowSpan( nRowSpan ); + } + if( nRowSpan > 0 ) // the master cell is done, from now on we set + nRowSpan = -nRowSpan; // negative row spans + ++nRowSpan; // ... -3, -2, -1 + } + if( bMerge ) + { + // A row containing overlapped cells is superfluous, + // these cells can be put into rBoxes for deletion + FindSuperfluousRows_( rBoxes, pFirstLn, pLastLn ); + // pNewFormat will be set to the new master box and the overlapped cells + SwFrameFormat* pNewFormat = pMergeBox->ClaimFrameFormat(); + pNewFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, pSel->mnMergeWidth, 0 ) ); + for( size_t nCurrLine = 0; nCurrLine < nLineCount; ++nCurrLine ) + { + const SwSelBoxes& rLineBoxes = pSel->maBoxes[nCurrLine]; + size_t nColCount = rLineBoxes.size(); + for (size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol) + { + SwTableBox* pBox = rLineBoxes[nCurrCol]; + if( nCurrCol ) + { + // Even this box will be deleted soon, + // we have to correct the width to avoid side effects + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + pFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, 0, 0 ) ); + } + else + { + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pNewFormat) ); + // remove numbering from cells that will be disabled in the merge + if( nCurrLine ) + { + SwPaM aPam( *pBox->GetSttNd(), 0 ); + aPam.GetPoint()->nNode++; + SwTextNode* pNd = aPam.GetNode().GetTextNode(); + while( pNd ) + { + pNd->SetCountedInList( false ); + + aPam.GetPoint()->nNode++; + pNd = aPam.GetNode().GetTextNode(); + } + } + } + } + } + if( pLastBox ) // Robust + { + // The new borders of the master cell... + SvxBoxItem aBox( pMergeBox->GetFrameFormat()->GetBox() ); + bool bOld = aBox.GetRight() || aBox.GetBottom(); + const SvxBoxItem& rBox = pLastBox->GetFrameFormat()->GetBox(); + aBox.SetLine( rBox.GetRight(), SvxBoxItemLine::RIGHT ); + aBox.SetLine( rBox.GetBottom(), SvxBoxItemLine::BOTTOM ); + if( bOld || aBox.GetLeft() || aBox.GetTop() || aBox.GetRight() || aBox.GetBottom() ) + (*ppMergeBox)->GetFrameFormat()->SetFormatAttr( aBox ); + } + + if( pUndo ) + pUndo->AddNewBox( pMergeBox->GetSttIdx() ); + } + return bMerge; +} + +/** SwTable::FindSuperfluousRows_(..) is looking for superfluous rows, i.e. rows + containing overlapped cells only. +*/ + +void SwTable::FindSuperfluousRows_( SwSelBoxes& rBoxes, + SwTableLine* pFirstLn, SwTableLine* pLastLn ) +{ + if( !pFirstLn || !pLastLn ) + { + if( rBoxes.empty() ) + return; + pFirstLn = rBoxes[0]->GetUpper(); + pLastLn = rBoxes.back()->GetUpper(); + } + sal_uInt16 nFirstLn = GetTabLines().GetPos( pFirstLn ); + sal_uInt16 nLastLn = GetTabLines().GetPos( pLastLn ); + for( sal_uInt16 nRow = nFirstLn; nRow <= nLastLn; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + bool bSuperfl = true; + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox *pBox = pLine->GetTabBoxes()[nCol]; + if( pBox->getRowSpan() > 0 && + rBoxes.end() == rBoxes.find( pBox ) ) + { + bSuperfl = false; + break; + } + } + if( bSuperfl ) + { + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + rBoxes.insert( pBox ); + } + } + } +} + +/** SwTableBox::FindStartOfRowSpan(..) returns the "master" cell, the cell which + overlaps the given cell, it maybe the cell itself. +*/ + +SwTableBox& SwTableBox::FindStartOfRowSpan( const SwTable& rTable, sal_uInt16 nMaxStep ) +{ + if( getRowSpan() > 0 || !nMaxStep ) + return *this; + + long nLeftBorder = lcl_Box2LeftBorder( *this ); + SwTableBox* pBox = this; + const SwTableLine* pMyUpper = GetUpper(); + sal_uInt16 nLine = rTable.GetTabLines().GetPos( pMyUpper ); + if( nLine && nLine < rTable.GetTabLines().size() ) + { + SwTableBox* pNext; + do + { + pNext = lcl_LeftBorder2Box( nLeftBorder, rTable.GetTabLines()[--nLine] ); + if( pNext ) + pBox = pNext; + } while( nLine && --nMaxStep && pNext && pBox->getRowSpan() < 1 ); + } + + return *pBox; +} + +/** SwTableBox::FindEndOfRowSpan(..) returns the last overlapped cell if there is + any. Otherwise the cell itself will returned. +*/ + +SwTableBox& SwTableBox::FindEndOfRowSpan( const SwTable& rTable, sal_uInt16 nMaxStep ) +{ + long nAbsSpan = getRowSpan(); + if( nAbsSpan < 0 ) + nAbsSpan = -nAbsSpan; + if( nAbsSpan == 1 || !nMaxStep ) + return *this; + + if( nMaxStep > --nAbsSpan ) + nMaxStep = static_cast<sal_uInt16>(nAbsSpan); + const SwTableLine* pMyUpper = GetUpper(); + sal_uInt16 nLine = rTable.GetTabLines().GetPos( pMyUpper ); + nMaxStep = nLine + nMaxStep; + if( nMaxStep >= rTable.GetTabLines().size() ) + nMaxStep = rTable.GetTabLines().size() - 1; + long nLeftBorder = lcl_Box2LeftBorder( *this ); + SwTableBox* pBox = + lcl_LeftBorder2Box( nLeftBorder, rTable.GetTabLines()[ nMaxStep ] ); + if ( !pBox ) + pBox = this; + + return *pBox; +} + +/** lcl_getAllMergedBoxes(..) collects all overlapped boxes to a given (master) box +*/ + +static void lcl_getAllMergedBoxes( const SwTable& rTable, SwSelBoxes& rBoxes, SwTableBox& rBox ) +{ + SwTableBox* pBox = &rBox; + OSL_ENSURE( pBox == &rBox.FindStartOfRowSpan( rTable ), "Not a master box" ); + rBoxes.insert( pBox ); + if( pBox->getRowSpan() == 1 ) + return; + const SwTableLine* pMyUpper = pBox->GetUpper(); + sal_uInt16 nLine = rTable.GetTabLines().GetPos( pMyUpper ); + long nLeftBorder = lcl_Box2LeftBorder( *pBox ); + sal_uInt16 nCount = rTable.GetTabLines().size(); + while( ++nLine < nCount && pBox && pBox->getRowSpan() != -1 ) + { + pBox = lcl_LeftBorder2Box( nLeftBorder, rTable.GetTabLines()[nLine] ); + if( pBox ) + rBoxes.insert( pBox ); + } +} + +/** lcl_UnMerge(..) manipulates the row span attribute of a given master cell + and its overlapped cells to split them into several pieces. +*/ + +static void lcl_UnMerge( const SwTable& rTable, SwTableBox& rBox, size_t nCnt, + bool bSameHeight ) +{ + SwSelBoxes aBoxes; + lcl_getAllMergedBoxes( rTable, aBoxes, rBox ); + size_t const nCount = aBoxes.size(); + if( nCount < 2 ) + return; + if( nCnt > nCount ) + nCnt = nCount; + std::unique_ptr<size_t[]> const pSplitIdx(new size_t[nCnt]); + if( bSameHeight ) + { + std::unique_ptr<SwTwips[]> const pHeights(new SwTwips[nCount]); + SwTwips nHeight = 0; + for (size_t i = 0; i < nCount; ++i) + { + SwTableLine* pLine = aBoxes[ i ]->GetUpper(); + SwFrameFormat *pRowFormat = pLine->GetFrameFormat(); + pHeights[ i ] = pRowFormat->GetFrameSize().GetHeight(); + nHeight += pHeights[ i ]; + } + SwTwips nSumH = 0; + size_t nIdx = 0; + for (size_t i = 1; i <= nCnt; ++i) + { + SwTwips nSplit = ( i * nHeight ) / nCnt; + while( nSumH < nSplit && nIdx < nCount ) + nSumH += pHeights[ nIdx++ ]; + pSplitIdx[ i - 1 ] = nIdx; + } + } + else + { + for (size_t i = 1; i <= nCnt; ++i) + { + pSplitIdx[ i - 1 ] = ( i * nCount ) / nCnt; + } + } + size_t nIdx = 0; + for (size_t i = 0; i < nCnt; ++i) + { + size_t nNextIdx = pSplitIdx[ i ]; + aBoxes[ nIdx ]->setRowSpan( nNextIdx - nIdx ); + lcl_InvalidateCellFrame( *aBoxes[ nIdx ] ); + while( ++nIdx < nNextIdx ) + aBoxes[ nIdx ]->setRowSpan( nIdx - nNextIdx ); + } +} + +/** lcl_FillSelBoxes(..) puts all boxes of a given line into the selection structure +*/ + +static void lcl_FillSelBoxes( SwSelBoxes &rBoxes, SwTableLine &rLine ) +{ + const size_t nBoxCount = rLine.GetTabBoxes().size(); + for( size_t i = 0; i < nBoxCount; ++i ) + rBoxes.insert( rLine.GetTabBoxes()[i] ); +} + +/** SwTable::InsertSpannedRow(..) inserts "superfluous" rows, i.e. rows containing + overlapped cells only. This is a preparation for an upcoming split. +*/ + +void SwTable::InsertSpannedRow( SwDoc* pDoc, sal_uInt16 nRowIdx, sal_uInt16 nCnt ) +{ + CHECK_TABLE( *this ) + OSL_ENSURE( nCnt && nRowIdx < GetTabLines().size(), "Wrong call of InsertSpannedRow" ); + SwSelBoxes aBoxes; + SwTableLine& rLine = *GetTabLines()[ nRowIdx ]; + lcl_FillSelBoxes( aBoxes, rLine ); + SwFormatFrameSize aFSz( rLine.GetFrameFormat()->GetFrameSize() ); + if( SwFrameSize::Variable != aFSz.GetHeightSizeType() ) + { + SwFrameFormat* pFrameFormat = rLine.ClaimFrameFormat(); + long nNewHeight = aFSz.GetHeight() / ( nCnt + 1 ); + if( !nNewHeight ) + ++nNewHeight; + aFSz.SetHeight( nNewHeight ); + pFrameFormat->SetFormatAttr( aFSz ); + } + InsertRow_( pDoc, aBoxes, nCnt, true ); + const size_t nBoxCount = rLine.GetTabBoxes().size(); + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwTableLine *pNewLine = GetTabLines()[ nRowIdx + nCnt - n ]; + for( size_t nCurrBox = 0; nCurrBox < nBoxCount; ++nCurrBox ) + { + long nRowSpan = rLine.GetTabBoxes()[nCurrBox]->getRowSpan(); + if( nRowSpan > 0 ) + nRowSpan = - nRowSpan; + pNewLine->GetTabBoxes()[ nCurrBox ]->setRowSpan( nRowSpan - n ); + } + } + lcl_ChangeRowSpan( *this, nCnt, nRowIdx, false ); + CHECK_TABLE( *this ) +} + +typedef std::pair< sal_uInt16, sal_uInt16 > SwLineOffset; +typedef std::vector< SwLineOffset > SwLineOffsetArray; + +/* +* When a couple of table boxes has to be split, +* lcl_SophisticatedFillLineIndices delivers the information where and how many +* rows have to be inserted. +* Input +* rTable: the table to manipulate +* rBoxes: an array of boxes to split +* nCnt: how many parts are wanted +* Output +* rArr: a list of pairs ( line index, number of lines to insert ) +*/ +static void lcl_SophisticatedFillLineIndices( SwLineOffsetArray &rArr, + const SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ) +{ + std::list< SwLineOffset > aBoxes; + SwLineOffset aLnOfs( USHRT_MAX, USHRT_MAX ); + for (size_t i = 0; i < rBoxes.size(); ++i) + { // Collect all end line indices and the row spans + const SwTableBox &rBox = rBoxes[ i ]->FindStartOfRowSpan( rTable ); + OSL_ENSURE( rBox.getRowSpan() > 0, "Didn't I say 'StartOfRowSpan' ??" ); + if( nCnt > rBox.getRowSpan() ) + { + const SwTableLine *pLine = rBox.GetUpper(); + const sal_uInt16 nEnd = sal_uInt16( rBox.getRowSpan() + + rTable.GetTabLines().GetPos( pLine ) ); + // The next if statement is a small optimization + if( aLnOfs.first != nEnd || aLnOfs.second != rBox.getRowSpan() ) + { + aLnOfs.first = nEnd; // ok, this is the line behind the box + aLnOfs.second = sal_uInt16( rBox.getRowSpan() ); // the row span + aBoxes.insert( aBoxes.end(), aLnOfs ); + } + } + } + // As I said, I noted the line index _behind_ the last line of the boxes + // in the resulting array the index has to be _on_ the line + // nSum is to evaluate the wished value + sal_uInt16 nSum = 1; + while( !aBoxes.empty() ) + { + // I. step: + // Looking for the "smallest" line end with the smallest row span + std::list< SwLineOffset >::iterator pCurr = aBoxes.begin(); + aLnOfs = *pCurr; // the line end and row span of the first box + while( ++pCurr != aBoxes.end() ) + { + if( aLnOfs.first > pCurr->first ) + { // Found a smaller line end + aLnOfs.first = pCurr->first; + aLnOfs.second = pCurr->second; // row span + } + else if( aLnOfs.first == pCurr->first && + aLnOfs.second < pCurr->second ) + aLnOfs.second = pCurr->second; // Found a smaller row span + } + OSL_ENSURE( aLnOfs.second < nCnt, "Clean-up failed" ); + aLnOfs.second = nCnt - aLnOfs.second; // the number of rows to insert + rArr.emplace_back( aLnOfs.first - nSum, aLnOfs.second ); + // the correction has to be incremented because in the following + // loops the line ends were manipulated + nSum = nSum + aLnOfs.second; + + pCurr = aBoxes.begin(); + while( pCurr != aBoxes.end() ) + { + if( pCurr->first == aLnOfs.first ) + { // These boxes can be removed because the last insertion + // of rows will expand their row span above the needed value + pCurr = aBoxes.erase(pCurr); + } + else + { + bool bBefore = ( pCurr->first - pCurr->second < aLnOfs.first ); + // Manipulation of the end line indices as if the rows are + // already inserted + pCurr->first = pCurr->first + aLnOfs.second; + if( bBefore ) + { // If the insertion is inside the box, + // its row span has to be incremented + pCurr->second = pCurr->second + aLnOfs.second; + if( pCurr->second >= nCnt ) + { // if the row span is bigger than the split factor + // this box is done + pCurr = aBoxes.erase(pCurr); + } + else + ++pCurr; + } + else + ++pCurr; + } + } + } +} + +typedef std::set< SwTwips > SwSplitLines; + +/** lcl_CalculateSplitLineHeights(..) delivers all y-positions where table rows have + to be split to fulfill the requested "split same height" +*/ + +static sal_uInt16 lcl_CalculateSplitLineHeights( SwSplitLines &rCurr, SwSplitLines &rNew, + const SwTable& rTable, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ) +{ + if( nCnt < 2 ) + return 0; + std::vector< SwLineOffset > aBoxes; + SwLineOffset aLnOfs( USHRT_MAX, USHRT_MAX ); + sal_uInt16 nFirst = USHRT_MAX; // becomes the index of the first line + sal_uInt16 nLast = 0; // becomes the index of the last line of the splitting + for (size_t i = 0; i < rBoxes.size(); ++i) + { // Collect all pairs (start+end) of line indices to split + const SwTableBox &rBox = rBoxes[ i ]->FindStartOfRowSpan( rTable ); + OSL_ENSURE( rBox.getRowSpan() > 0, "Didn't I say 'StartOfRowSpan' ??" ); + const SwTableLine *pLine = rBox.GetUpper(); + const sal_uInt16 nStart = rTable.GetTabLines().GetPos( pLine ); + const sal_uInt16 nEnd = sal_uInt16( rBox.getRowSpan() + nStart - 1 ); + // The next if statement is a small optimization + if( aLnOfs.first != nStart || aLnOfs.second != nEnd ) + { + aLnOfs.first = nStart; + aLnOfs.second = nEnd; + aBoxes.push_back( aLnOfs ); + if( nStart < nFirst ) + nFirst = nStart; + if( nEnd > nLast ) + nLast = nEnd; + } + } + + if (nFirst == USHRT_MAX) + { + assert(aBoxes.empty()); + return 0; + } + + SwTwips nHeight = 0; + std::unique_ptr<SwTwips[]> pLines(new SwTwips[ nLast + 1 - nFirst ]); + for( sal_uInt16 i = nFirst; i <= nLast; ++i ) + { + bool bLayoutAvailable = false; + nHeight += rTable.GetTabLines()[ i ]->GetTableLineHeight( bLayoutAvailable ); + rCurr.insert( rCurr.end(), nHeight ); + pLines[ i - nFirst ] = nHeight; + } + for( const auto& rSplit : aBoxes ) + { + SwTwips nBase = rSplit.first <= nFirst ? 0 : + pLines[ rSplit.first - nFirst - 1 ]; + SwTwips nDiff = pLines[ rSplit.second - nFirst ] - nBase; + for( sal_uInt16 i = 1; i < nCnt; ++i ) + { + SwTwips nSplit = nBase + ( i * nDiff ) / nCnt; + rNew.insert( nSplit ); + } + } + return nFirst; +} + +/** lcl_LineIndex(..) delivers the line index of the line behind or above + the box selection. +*/ + +static sal_uInt16 lcl_LineIndex( const SwTable& rTable, const SwSelBoxes& rBoxes, + bool bBehind ) +{ + sal_uInt16 nDirect = USHRT_MAX; + sal_uInt16 nSpan = USHRT_MAX; + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableBox *pBox = rBoxes[i]; + const SwTableLine* pLine = rBoxes[i]->GetUpper(); + sal_uInt16 nPos = rTable.GetTabLines().GetPos( pLine ); + if( USHRT_MAX != nPos ) + { + if( bBehind ) + { + if( nPos > nDirect || nDirect == USHRT_MAX ) + nDirect = nPos; + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan < 2 ) + nSpan = 0; + else if( nSpan ) + { + sal_uInt16 nEndOfRowSpan = static_cast<sal_uInt16>(nPos + nRowSpan - 1); + if( nEndOfRowSpan > nSpan || nSpan == USHRT_MAX ) + nSpan = nEndOfRowSpan; + } + } + else if( nPos < nDirect ) + nDirect = nPos; + } + } + if( nSpan && nSpan < USHRT_MAX ) + return nSpan; + return nDirect; +} + +/** SwTable::NewSplitRow(..) splits all selected boxes horizontally. +*/ + +bool SwTable::NewSplitRow( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, + bool bSameHeight ) +{ + CHECK_TABLE( *this ) + ++nCnt; + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + + if( bSameHeight && pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + SwSplitLines aRowLines; + SwSplitLines aSplitLines; + sal_uInt16 nFirst = lcl_CalculateSplitLineHeights( aRowLines, aSplitLines, + *this, rBoxes, nCnt ); + aFndBox.DelFrames( *this ); + SwTwips nLast = 0; + SwSplitLines::iterator pSplit = aSplitLines.begin(); + for( const auto& rCurr : aRowLines ) + { + while( pSplit != aSplitLines.end() && *pSplit < rCurr ) + { + InsertSpannedRow( pDoc, nFirst, 1 ); + SwTableLine* pRow = GetTabLines()[ nFirst ]; + SwFrameFormat* pRowFormat = pRow->ClaimFrameFormat(); + SwFormatFrameSize aFSz( pRowFormat->GetFrameSize() ); + aFSz.SetHeightSizeType( SwFrameSize::Minimum ); + aFSz.SetHeight( *pSplit - nLast ); + pRowFormat->SetFormatAttr( aFSz ); + nLast = *pSplit; + ++pSplit; + ++nFirst; + } + if( pSplit != aSplitLines.end() && rCurr == *pSplit ) + ++pSplit; + SwTableLine* pRow = GetTabLines()[ nFirst ]; + SwFrameFormat* pRowFormat = pRow->ClaimFrameFormat(); + SwFormatFrameSize aFSz( pRowFormat->GetFrameSize() ); + aFSz.SetHeightSizeType( SwFrameSize::Minimum ); + aFSz.SetHeight( rCurr - nLast ); + pRowFormat->SetFormatAttr( aFSz ); + nLast = rCurr; + ++nFirst; + } + } + else + { + aFndBox.DelFrames( *this ); + bSameHeight = false; + } + if( !bSameHeight ) + { + SwLineOffsetArray aLineOffs; + lcl_SophisticatedFillLineIndices( aLineOffs, *this, rBoxes, nCnt ); + SwLineOffsetArray::reverse_iterator pCurr( aLineOffs.rbegin() ); + while( pCurr != aLineOffs.rend() ) + { + InsertSpannedRow( pDoc, pCurr->first, pCurr->second ); + ++pCurr; + } + } + + std::set<size_t> aIndices; + for (size_t i = 0; i < rBoxes.size(); ++i) + { + OSL_ENSURE( rBoxes[i]->getRowSpan() != 1, "Forgot to split?" ); + if( rBoxes[i]->getRowSpan() > 1 ) + aIndices.insert( i ); + } + + for( const auto& rCurrBox : aIndices ) + lcl_UnMerge( *this, *rBoxes[rCurrBox], nCnt, bSameHeight ); + + CHECK_TABLE( *this ) + // update the layout + aFndBox.MakeFrames( *this ); + + return true; +} + +/** SwTable::InsertRow(..) inserts one or more rows before or behind the selected + boxes. +*/ + +bool SwTable::InsertRow( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind ) +{ + bool bRet = false; + if( IsNewModel() ) + { + CHECK_TABLE( *this ) + sal_uInt16 nRowIdx = lcl_LineIndex( *this, rBoxes, bBehind ); + if( nRowIdx < USHRT_MAX ) + { + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + + bRet = true; + SwTableLine *pLine = GetTabLines()[ nRowIdx ]; + SwSelBoxes aLineBoxes; + lcl_FillSelBoxes( aLineBoxes, *pLine ); + InsertRow_( pDoc, aLineBoxes, nCnt, bBehind ); + const size_t nBoxCount = pLine->GetTabBoxes().size(); + sal_uInt16 nOfs = bBehind ? 0 : 1; + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwTableLine *pNewLine = GetTabLines()[ nRowIdx+nCnt-n-nOfs]; + for( size_t nCurrBox = 0; nCurrBox < nBoxCount; ++nCurrBox ) + { + long nRowSpan = pLine->GetTabBoxes()[nCurrBox]->getRowSpan(); + if( bBehind ) + { + if( nRowSpan == 1 || nRowSpan == -1 ) + nRowSpan = n + 1; + else if( nRowSpan > 1 ) + { + nRowSpan = - nRowSpan; + + // tdf#123102 disable numbering of the new hidden + // paragraph in merged cells to avoid of bad + // renumbering of next list elements + SwTableBox* pBox = pNewLine->GetTabBoxes()[nCurrBox]; + SwNodeIndex aIdx( *pBox->GetSttNd(), +1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( pCNd && pCNd->IsTextNode() && pCNd->GetTextNode()->GetNumRule() ) + { + SwPosition aPos( *pCNd->GetTextNode() ); + SwPaM aPam( aPos, aPos ); + pDoc->DelNumRules( aPam ); + } + } + } + else + { + if( nRowSpan > 0 ) + nRowSpan = n + 1; + else + --nRowSpan; + } + pNewLine->GetTabBoxes()[ nCurrBox ]->setRowSpan( nRowSpan - n ); + } + } + if( bBehind ) + ++nRowIdx; + if( nRowIdx ) + lcl_ChangeRowSpan( *this, nCnt, --nRowIdx, true ); + // update the layout + aFndBox.MakeFrames( *this ); + } + CHECK_TABLE( *this ) + } + else + bRet = InsertRow_( pDoc, rBoxes, nCnt, bBehind ); + return bRet; +} + +/** SwTable::PrepareDelBoxes(..) adjusts the row span attributes for an upcoming + deletion of table cells and invalidates the layout of these cells. +*/ + +void SwTable::PrepareDelBoxes( const SwSelBoxes& rBoxes ) +{ + if( IsNewModel() ) + { + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableBox* pBox = rBoxes[i]; + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan != 1 && pBox->GetFrameFormat()->GetFrameSize().GetWidth() ) + { + long nLeft = lcl_Box2LeftBorder( *pBox ); + SwTableLine *pLine = pBox->GetUpper(); + sal_uInt16 nLinePos = GetTabLines().GetPos( pLine); + OSL_ENSURE( nLinePos < USHRT_MAX, "Box/table mismatch" ); + if( nRowSpan > 1 ) + { + if( ++nLinePos < GetTabLines().size() ) + { + pLine = GetTabLines()[ nLinePos ]; + pBox = lcl_LeftBorder2Box( nLeft, pLine ); + OSL_ENSURE( pBox, "RowSpan irritation I" ); + if( pBox ) + pBox->setRowSpan( --nRowSpan ); + } + } + else if( nLinePos > 0 ) + { + do + { + pLine = GetTabLines()[ --nLinePos ]; + pBox = lcl_LeftBorder2Box( nLeft, pLine ); + OSL_ENSURE( pBox, "RowSpan irritation II" ); + if( pBox ) + { + nRowSpan = pBox->getRowSpan(); + if( nRowSpan > 1 ) + { + lcl_InvalidateCellFrame( *pBox ); + --nRowSpan; + } + else + ++nRowSpan; + pBox->setRowSpan( nRowSpan ); + } + else + nRowSpan = 1; + } + while( nRowSpan < 0 && nLinePos > 0 ); + } + } + } + } +} + +/** lcl_SearchSelBox(..) adds cells of a given table row to the selection structure + if it overlaps with the given x-position range +*/ + +static void lcl_SearchSelBox( const SwTable &rTable, SwSelBoxes& rBoxes, long nMin, long nMax, + SwTableLine& rLine, bool bChkProtected, bool bColumn ) +{ + long nLeft = 0; + long nRight = 0; + long nMid = ( nMax + nMin )/ 2; + const size_t nCount = rLine.GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = rLine.GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + long nWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nRight += nWidth; + if( nRight > nMin ) + { + bool bAdd = false; + if( nRight <= nMax ) + bAdd = nLeft >= nMin || nRight >= nMid || + nRight - nMin > nMin - nLeft; + else + bAdd = nLeft <= nMid || nRight - nMax < nMax - nLeft; + long nRowSpan = pBox->getRowSpan(); + if( bAdd && + ( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) ) + { + size_t const nOldCnt = rBoxes.size(); + rBoxes.insert( pBox ); + if( bColumn && nRowSpan != 1 && nOldCnt < rBoxes.size() ) + { + SwTableBox *pMasterBox = pBox->getRowSpan() > 0 ? pBox + : &pBox->FindStartOfRowSpan( rTable ); + lcl_getAllMergedBoxes( rTable, rBoxes, *pMasterBox ); + } + } + } + if( nRight >= nMax ) + break; + nLeft = nRight; + } +} + +/** void SwTable::CreateSelection(..) fills the selection structure with table cells + for a given SwPaM, ie. start and end position inside a table +*/ + +void SwTable::CreateSelection( const SwPaM& rPam, SwSelBoxes& rBoxes, + const SearchType eSearch, bool bChkProtected ) const +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + if( m_aLines.empty() ) + return; + const SwNode* pStartNd = rPam.GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + const SwNode* pEndNd = rPam.GetMark()->nNode.GetNode().FindTableBoxStartNode(); + if( !pStartNd || !pEndNd ) + return; + CreateSelection( pStartNd, pEndNd, rBoxes, eSearch, bChkProtected ); +} + +/** void SwTable::CreateSelection(..) fills the selection structure with table cells + for given start and end nodes inside a table +*/ +void SwTable::CreateSelection( const SwNode* pStartNd, const SwNode* pEndNd, + SwSelBoxes& rBoxes, const SearchType eSearch, bool bChkProtected ) const +{ + rBoxes.clear(); + // Looking for start and end of the selection given by SwNode-pointer + const size_t nLines = m_aLines.size(); + // nTop becomes the line number of the upper box + // nBottom becomes the line number of the lower box + size_t nTop = 0; + size_t nBottom = 0; + // nUpperMin becomes the left border value of the upper box + // nUpperMax becomes the right border of the upper box + // nLowerMin and nLowerMax the borders of the lower box + long nUpperMin = 0, nUpperMax = 0; + long nLowerMin = 0, nLowerMax = 0; + // nFound will incremented if a box is found + // 0 => no box found; 1 => the upper box has been found; 2 => both found + int nFound = 0; + for( size_t nRow = 0; nFound < 2 && nRow < nLines; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox->GetSttNd() == pEndNd || pBox->GetSttNd() == pStartNd ) + { + if( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + rBoxes.insert( pBox ); + if( nFound ) + { + nBottom = nRow; + lcl_CheckMinMax( nLowerMin, nLowerMax, *pLine, nCol, true ); + ++nFound; + break; + } + else + { + nTop = nRow; + lcl_CheckMinMax( nUpperMin, nUpperMax, *pLine, nCol, true ); + ++nFound; + // If start and end node are identical, we're nearly done... + if( pEndNd == pStartNd ) + { + nBottom = nTop; + nLowerMin = nUpperMin; + nLowerMax = nUpperMax; + ++nFound; + } + } + } + } + } + if( nFound < 2 ) + return; // At least one node was not a part of the given table + if( eSearch == SEARCH_ROW ) + { + // Selection of a row is quiet easy: + // every (unprotected) box between start and end line + // with a positive row span will be collected + for( size_t nRow = nTop; nRow <= nBottom; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox->getRowSpan() > 0 && ( !bChkProtected || + !pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) ) + rBoxes.insert( pBox ); + } + } + return; + } + bool bCombine = nTop == nBottom; + if( !bCombine ) + { + long nMinWidth = nUpperMax - nUpperMin; + long nTmp = nLowerMax - nLowerMin; + if( nMinWidth > nTmp ) + nMinWidth = nTmp; + nTmp = std::min(nLowerMax, nUpperMax); + nTmp -= ( nLowerMin < nUpperMin ) ? nUpperMin : nLowerMin; + // If the overlapping between upper and lower box is less than half + // of the width (of the smaller cell), bCombine is set, + // e.g. if upper and lower cell are in different columns + bCombine = ( nTmp + nTmp < nMinWidth ); + } + if( bCombine ) + { + if( nUpperMin < nLowerMin ) + nLowerMin = nUpperMin; + else + nUpperMin = nLowerMin; + if( nUpperMax > nLowerMax ) + nLowerMax = nUpperMax; + else + nUpperMax = nLowerMax; + } + const bool bColumn = eSearch == SEARCH_COL; + if( bColumn ) + { + for( size_t i = 0; i < nTop; ++i ) + lcl_SearchSelBox( *this, rBoxes, nUpperMin, nUpperMax, + *m_aLines[i], bChkProtected, bColumn ); + } + + { + long nMin = std::min(nUpperMin, nLowerMin); + long nMax = nUpperMax < nLowerMax ? nLowerMax : nUpperMax; + for( size_t i = nTop; i <= nBottom; ++i ) + lcl_SearchSelBox( *this, rBoxes, nMin, nMax, *m_aLines[i], + bChkProtected, bColumn ); + } + if( bColumn ) + { + for( size_t i = nBottom + 1; i < nLines; ++i ) + lcl_SearchSelBox( *this, rBoxes, nLowerMin, nLowerMax, *m_aLines[i], + bChkProtected, true ); + } +} + +/** void SwTable::ExpandColumnSelection(..) adds cell to the give selection to + assure that at least one cell of every row is part of the selection. +*/ + +void SwTable::ExpandColumnSelection( SwSelBoxes& rBoxes, long &rMin, long &rMax ) const +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + rMin = 0; + rMax = 0; + if( m_aLines.empty() || rBoxes.empty() ) + return; + + const size_t nLineCnt = m_aLines.size(); + const size_t nBoxCnt = rBoxes.size(); + size_t nBox = 0; + for( size_t nRow = 0; nRow < nLineCnt && nBox < nBoxCnt; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + OSL_ENSURE( pLine, "Missing table line" ); + const size_t nCols = pLine->GetTabBoxes().size(); + for( size_t nCol = 0; nCol < nCols; ++nCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCol]; + OSL_ENSURE( pBox, "Missing table box" ); + if( pBox == rBoxes[nBox] ) + { + lcl_CheckMinMax( rMin, rMax, *pLine, nCol, nBox == 0 ); + if( ++nBox >= nBoxCnt ) + break; + } + } + } + for( size_t nRow = 0; nRow < nLineCnt; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + const size_t nCols = pLine->GetTabBoxes().size(); + long nRight = 0; + for( size_t nCurrBox = 0; nCurrBox < nCols; ++nCurrBox ) + { + long nLeft = nRight; + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + nRight += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + if( nLeft >= rMin && nRight <= rMax ) + rBoxes.insert( pBox ); + } + } +} + +/** SwTable::PrepareDeleteCol(..) adjusts the widths of the neighbour cells of + a cell selection for an upcoming (column) deletion +*/ +void SwTable::PrepareDeleteCol( long nMin, long nMax ) +{ + OSL_ENSURE( m_bNewModel, "Don't call me for old tables" ); + if( m_aLines.empty() || nMax < nMin ) + return; + long nMid = nMin ? ( nMin + nMax ) / 2 : 0; + const SwTwips nTabSize = GetFrameFormat()->GetFrameSize().GetWidth(); + if( nTabSize == nMax ) + nMid = nMax; + const size_t nLineCnt = m_aLines.size(); + for( size_t nRow = 0; nRow < nLineCnt; ++nRow ) + { + SwTableLine* pLine = m_aLines[nRow]; + const size_t nCols = pLine->GetTabBoxes().size(); + long nRight = 0; + for( size_t nCurrBox = 0; nCurrBox < nCols; ++nCurrBox ) + { + long nLeft = nRight; + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + nRight += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + if( nRight < nMin ) + continue; + if( nLeft > nMax ) + break; + long nNewWidth = -1; + if( nLeft < nMin ) + { + if( nRight <= nMax ) + nNewWidth = nMid - nLeft; + } + else if( nRight > nMax ) + nNewWidth = nRight - nMid; + else + nNewWidth = 0; + if( nNewWidth >= 0 ) + { + SwFrameFormat* pFrameFormat = pBox->ClaimFrameFormat(); + SwFormatFrameSize aFrameSz( pFrameFormat->GetFrameSize() ); + aFrameSz.SetWidth( nNewWidth ); + pFrameFormat->SetFormatAttr( aFrameSz ); + } + } + } +} + +/** SwTable::ExpandSelection(..) adds all boxes to the box selections which are + overlapped by it. +*/ + +void SwTable::ExpandSelection( SwSelBoxes& rBoxes ) const +{ + for (size_t i = 0; i < rBoxes.size(); ++i) + { + SwTableBox *pBox = rBoxes[i]; + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan != 1 ) + { + SwTableBox *pMasterBox = nRowSpan > 0 ? pBox + : &pBox->FindStartOfRowSpan( *this ); + lcl_getAllMergedBoxes( *this, rBoxes, *pMasterBox ); + } + } +} + +/** SwTable::CheckRowSpan(..) looks for the next line without an overlapping to + the previous line. +*/ + +void SwTable::CheckRowSpan( SwTableLine* &rpLine, bool bUp ) const +{ + OSL_ENSURE( IsNewModel(), "Don't call me for old tables" ); + sal_uInt16 nLineIdx = GetTabLines().GetPos( rpLine ); + OSL_ENSURE( nLineIdx < GetTabLines().size(), "Start line out of range" ); + bool bChange = true; + if( bUp ) + { + while( bChange ) + { + bChange = false; + rpLine = GetTabLines()[ nLineIdx ]; + const size_t nCols = rpLine->GetTabBoxes().size(); + for( size_t nCol = 0; !bChange && nCol < nCols; ++nCol ) + { + SwTableBox* pBox = rpLine->GetTabBoxes()[nCol]; + if( pBox->getRowSpan() > 1 || pBox->getRowSpan() < -1 ) + bChange = true; + } + if( bChange ) + { + if( nLineIdx ) + --nLineIdx; + else + { + bChange = false; + rpLine = nullptr; + } + } + } + } + else + { + const size_t nMaxLine = GetTabLines().size(); + while( bChange ) + { + bChange = false; + rpLine = GetTabLines()[ nLineIdx ]; + const size_t nCols = rpLine->GetTabBoxes().size(); + for( size_t nCol = 0; !bChange && nCol < nCols; ++nCol ) + { + SwTableBox* pBox = rpLine->GetTabBoxes()[nCol]; + if( pBox->getRowSpan() < 0 ) + bChange = true; + } + if( bChange ) + { + ++nLineIdx; + if( nLineIdx >= nMaxLine ) + { + bChange = false; + rpLine = nullptr; + } + } + } + } +} + +// This structure corrects the row span attributes for a top line of a table +// In a top line no negative row span is allowed, so these have to be corrected. +// If there has been at least one correction, all values are stored +// and can be used by undo of table split +SwSaveRowSpan::SwSaveRowSpan( SwTableBoxes& rBoxes, sal_uInt16 nSplitLn ) + : mnSplitLine( nSplitLn ) +{ + bool bDontSave = true; // nothing changed, nothing to save + const size_t nColCount = rBoxes.size(); + OSL_ENSURE( nColCount, "Empty Table Line" ); + mnRowSpans.resize( nColCount ); + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = rBoxes[nCurrCol]; + OSL_ENSURE( pBox, "Missing Table Box" ); + long nRowSp = pBox->getRowSpan(); + mnRowSpans[ nCurrCol ] = nRowSp; + if( nRowSp < 0 ) + { + bDontSave = false; + nRowSp = -nRowSp; + pBox->setRowSpan( nRowSp ); // correction needed + } + } + if( bDontSave ) + mnRowSpans.clear(); +} + +// This function is called by undo of table split to restore the old row span +// values at the split line +void SwTable::RestoreRowSpan( const SwSaveRowSpan& rSave ) +{ + if( !IsNewModel() ) // for new model only + return; + sal_uInt16 nLineCount = GetTabLines().size(); + OSL_ENSURE( rSave.mnSplitLine < nLineCount, "Restore behind last line?" ); + if( rSave.mnSplitLine < nLineCount ) + { + SwTableLine* pLine = GetTabLines()[rSave.mnSplitLine]; + const size_t nColCount = pLine->GetTabBoxes().size(); + OSL_ENSURE( nColCount, "Empty Table Line" ); + OSL_ENSURE( nColCount == rSave.mnRowSpans.size(), "Wrong row span store" ); + if( nColCount == rSave.mnRowSpans.size() ) + { + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrCol]; + OSL_ENSURE( pBox, "Missing Table Box" ); + long nRowSp = pBox->getRowSpan(); + if( nRowSp != rSave.mnRowSpans[ nCurrCol ] ) + { + OSL_ENSURE( -nRowSp == rSave.mnRowSpans[ nCurrCol ], "Pardon me?!" ); + OSL_ENSURE( rSave.mnRowSpans[ nCurrCol ] < 0, "Pardon me?!" ); + pBox->setRowSpan( -nRowSp ); + + sal_uInt16 nLine = rSave.mnSplitLine; + if( nLine ) + { + long nLeftBorder = lcl_Box2LeftBorder( *pBox ); + SwTableBox* pNext; + do + { + pNext = lcl_LeftBorder2Box( nLeftBorder, GetTabLines()[--nLine] ); + if( pNext ) + { + pBox = pNext; + long nNewSpan = pBox->getRowSpan(); + if( pBox->getRowSpan() < 1 ) + nNewSpan -= nRowSp; + else + { + nNewSpan += nRowSp; + pNext = nullptr; + } + pBox->setRowSpan( nNewSpan ); + } + } while( nLine && pNext ); + } + } + } + } + } +} + +std::unique_ptr<SwSaveRowSpan> SwTable::CleanUpTopRowSpan( sal_uInt16 nSplitLine ) +{ + if( !IsNewModel() ) + return nullptr; + std::unique_ptr<SwSaveRowSpan> pRet(new SwSaveRowSpan( GetTabLines()[0]->GetTabBoxes(), nSplitLine )); + if( pRet->mnRowSpans.empty() ) + return nullptr; + return pRet; +} + +void SwTable::CleanUpBottomRowSpan( sal_uInt16 nDelLines ) +{ + if( !IsNewModel() ) + return; + const size_t nLastLine = GetTabLines().size()-1; + SwTableLine* pLine = GetTabLines()[nLastLine]; + const size_t nColCount = pLine->GetTabBoxes().size(); + OSL_ENSURE( nColCount, "Empty Table Line" ); + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrCol]; + OSL_ENSURE( pBox, "Missing Table Box" ); + long nRowSp = pBox->getRowSpan(); + if( nRowSp < 0 ) + nRowSp = -nRowSp; + if( nRowSp > 1 ) + { + lcl_ChangeRowSpan( *this, -static_cast<long>(nDelLines), + static_cast<sal_uInt16>(nLastLine), false ); + break; + } + } +} + +#ifdef DBG_UTIL + +namespace { + +struct RowSpanCheck +{ + long nRowSpan; + SwTwips nLeft; + SwTwips nRight; +}; + +} + +void SwTable::CheckConsistency() const +{ + if( !IsNewModel() ) + return; + const size_t nLineCount = GetTabLines().size(); + const SwTwips nTabSize = GetFrameFormat()->GetFrameSize().GetWidth(); + SwTwips nLineWidth = 0; + std::list< RowSpanCheck > aRowSpanCells; + std::list< RowSpanCheck >::iterator aIter = aRowSpanCells.end(); + SwNodeIndex index(*GetTableNode()); + ++index; + for( size_t nCurrLine = 0; nCurrLine < nLineCount; ++nCurrLine ) + { + SwTwips nWidth = 0; + SwTableLine* pLine = GetTabLines()[nCurrLine]; + SAL_WARN_IF( !pLine, "sw.core", "Missing Table Line" ); + const size_t nColCount = pLine->GetTabBoxes().size(); + SAL_WARN_IF( !nColCount, "sw.core", "Empty Table Line" ); + for( size_t nCurrCol = 0; nCurrCol < nColCount; ++nCurrCol ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrCol]; + assert(pBox); + SAL_WARN_IF(GetTableNode()->EndOfSectionIndex() <= index.GetIndex(), "sw.core", "Box not in table nodes"); + SAL_WARN_IF(!index.GetNode().IsStartNode(), "sw.core", "No box start node"); + index = *index.GetNode().EndOfSectionNode(); + ++index; + SwTwips nNewWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth() + nWidth; + long nRowSp = pBox->getRowSpan(); + if( nRowSp < 0 ) + { + SAL_WARN_IF( aIter == aRowSpanCells.end(), + "sw.core", "Missing master box"); + if (aIter != aRowSpanCells.end()) + { + SAL_WARN_IF( aIter->nLeft != nWidth || aIter->nRight != nNewWidth, + "sw.core", "Wrong position/size of overlapped table box"); + --(aIter->nRowSpan); + SAL_WARN_IF( aIter->nRowSpan != -nRowSp, "sw.core", + "Wrong row span value" ); + if( nRowSp == -1 ) + { + aIter = aRowSpanCells.erase(aIter); + } + else + ++aIter; + } + } + else if( nRowSp != 1 ) + { + SAL_WARN_IF( !nRowSp, "sw.core", "Zero row span?!" ); + RowSpanCheck aEntry; + aEntry.nLeft = nWidth; + aEntry.nRight = nNewWidth; + aEntry.nRowSpan = nRowSp; + aRowSpanCells.insert( aIter, aEntry ); + } + nWidth = nNewWidth; + } + if( !nCurrLine ) + nLineWidth = nWidth; + SAL_WARN_IF( nWidth != nLineWidth, "sw.core", + "Different Line Widths: first: " << nLineWidth + << " current [" << nCurrLine << "]: " << nWidth); + SAL_WARN_IF( std::abs(nWidth - nTabSize) > 1 /* how tolerant? */, "sw.core", + "Line width differs from table width: " << nTabSize + << " current [" << nCurrLine << "]: " << nWidth); + SAL_WARN_IF( nWidth < 0 || nWidth > USHRT_MAX, "sw.core", + "Width out of range [" << nCurrLine << "]: " << nWidth); + SAL_WARN_IF( aIter != aRowSpanCells.end(), "sw.core", + "Missing overlapped box" ); + aIter = aRowSpanCells.begin(); + } + bool bEmpty = aRowSpanCells.empty(); + SAL_WARN_IF( !bEmpty, "sw.core", "Open row span detected" ); + SAL_WARN_IF(GetTableNode()->EndOfSectionNode() != &index.GetNode(), "sw.core", "table end node not found"); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/table/swtable.cxx b/sw/source/core/table/swtable.cxx new file mode 100644 index 000000000..e3e083127 --- /dev/null +++ b/sw/source/core/table/swtable.cxx @@ -0,0 +1,2771 @@ +/* -*- 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 <float.h> +#include <hintids.hxx> +#include <hints.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/colritem.hxx> +#include <sfx2/linkmgr.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtpdsc.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <frmatr.hxx> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <frame.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <tabcol.hxx> +#include <tabfrm.hxx> +#include <cellfrm.hxx> +#include <rowfrm.hxx> +#include <swserv.hxx> +#include <expfld.hxx> +#include <mdiexp.hxx> +#include <cellatr.hxx> +#include <txatbase.hxx> +#include <htmltbl.hxx> +#include <swtblfmt.hxx> +#include <ndindex.hxx> +#include <tblrwcl.hxx> +#include <shellres.hxx> +#include <viewsh.hxx> +#include <redline.hxx> +#include <list> +#include <calbck.hxx> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +using namespace com::sun::star; + + +#define COLFUZZY 20 + +static void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, + bool bChgAlign, sal_uLong nNdPos ); + +inline const Color* SwTableBox::GetSaveUserColor() const +{ + return mpUserColor.get(); +} + +inline const Color* SwTableBox::GetSaveNumFormatColor() const +{ + return mpNumFormatColor.get(); +} + +inline void SwTableBox::SetSaveUserColor(const Color* p ) +{ + if (p) + mpUserColor.reset(new Color(*p)); + else + mpUserColor.reset(); +} + +inline void SwTableBox::SetSaveNumFormatColor( const Color* p ) +{ + if (p) + mpNumFormatColor.reset(new Color(*p)); + else + mpNumFormatColor.reset(); +} + +long SwTableBox::getRowSpan() const +{ + return mnRowSpan; +} + +void SwTableBox::setRowSpan( long nNewRowSpan ) +{ + mnRowSpan = nNewRowSpan; +} + +bool SwTableBox::getDummyFlag() const +{ + return mbDummyFlag; +} + +void SwTableBox::setDummyFlag( bool bDummy ) +{ + mbDummyFlag = bDummy; +} + +//JP 15.09.98: Bug 55741 - Keep tabs (front and rear) +static OUString& lcl_TabToBlankAtSttEnd( OUString& rText ) +{ + sal_Unicode c; + sal_Int32 n; + + for( n = 0; n < rText.getLength() && ' ' >= ( c = rText[n] ); ++n ) + if( '\x9' == c ) + rText = rText.replaceAt( n, 1, " " ); + for( n = rText.getLength(); n && ' ' >= ( c = rText[--n] ); ) + if( '\x9' == c ) + rText = rText.replaceAt( n, 1, " " ); + return rText; +} + +static OUString& lcl_DelTabsAtSttEnd( OUString& rText ) +{ + sal_Unicode c; + sal_Int32 n; + OUStringBuffer sBuff(rText); + + for( n = 0; n < sBuff.getLength() && ' ' >= ( c = sBuff[ n ]); ++n ) + { + if( '\x9' == c ) + sBuff.remove( n--, 1 ); + } + for( n = sBuff.getLength(); n && ' ' >= ( c = sBuff[ --n ]); ) + { + if( '\x9' == c ) + sBuff.remove( n, 1 ); + } + rText = sBuff.makeStringAndClear(); + return rText; +} + +void InsTableBox( SwDoc* pDoc, SwTableNode* pTableNd, + SwTableLine* pLine, SwTableBoxFormat* pBoxFrameFormat, + SwTableBox* pBox, + sal_uInt16 nInsPos, sal_uInt16 nCnt ) +{ + OSL_ENSURE( pBox->GetSttNd(), "Box with no start node" ); + SwNodeIndex aIdx( *pBox->GetSttNd(), +1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &aIdx ); + OSL_ENSURE( pCNd, "Box with no content node" ); + + if( pCNd->IsTextNode() ) + { + if( pBox->GetSaveNumFormatColor() && pCNd->GetpSwAttrSet() ) + { + SwAttrSet aAttrSet( *pCNd->GetpSwAttrSet() ); + if( pBox->GetSaveUserColor() ) + aAttrSet.Put( SvxColorItem( *pBox->GetSaveUserColor(), RES_CHRATR_COLOR )); + else + aAttrSet.ClearItem( RES_CHRATR_COLOR ); + pDoc->GetNodes().InsBoxen( pTableNd, pLine, pBoxFrameFormat, + static_cast<SwTextNode*>(pCNd)->GetTextColl(), + &aAttrSet, nInsPos, nCnt ); + } + else + pDoc->GetNodes().InsBoxen( pTableNd, pLine, pBoxFrameFormat, + static_cast<SwTextNode*>(pCNd)->GetTextColl(), + pCNd->GetpSwAttrSet(), + nInsPos, nCnt ); + } + else + pDoc->GetNodes().InsBoxen( pTableNd, pLine, pBoxFrameFormat, + pDoc->GetDfltTextFormatColl(), nullptr, + nInsPos, nCnt ); + + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan != 1 ) + { + SwTableBoxes& rTableBoxes = pLine->GetTabBoxes(); + for( sal_uInt16 i = 0; i < nCnt; ++i ) + { + pBox = rTableBoxes[ i + nInsPos ]; + pBox->setRowSpan( nRowSpan ); + } + } +} + +SwTable::SwTable() + : SwClient( nullptr ), + m_pTableNode( nullptr ), + m_nGraphicsThatResize( 0 ), + m_nRowsToRepeat( 1 ), + m_bModifyLocked( false ), + m_bNewModel( true ) +{ + // default value set in the options + m_eTableChgMode = GetTableChgDefaultMode(); +} + +SwTable::SwTable( const SwTable& rTable ) + : SwClient( rTable.GetFrameFormat() ), + m_pTableNode( nullptr ), + m_eTableChgMode( rTable.m_eTableChgMode ), + m_nGraphicsThatResize( 0 ), + m_nRowsToRepeat( rTable.GetRowsToRepeat() ), + maTableStyleName(rTable.maTableStyleName), + m_bModifyLocked( false ), + m_bNewModel( rTable.m_bNewModel ) +{ +} + +void DelBoxNode( SwTableSortBoxes const & rSortCntBoxes ) +{ + for (size_t n = 0; n < rSortCntBoxes.size(); ++n) + { + rSortCntBoxes[ n ]->m_pStartNode = nullptr; + } +} + +SwTable::~SwTable() +{ + if( m_xRefObj.is() ) + { + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + if( !pDoc->IsInDtor() ) // then remove from the list + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( m_xRefObj.get() ); + + m_xRefObj->Closed(); + } + + // the table can be deleted if it's the last client of the FrameFormat + SwTableFormat* pFormat = GetFrameFormat(); + pFormat->Remove( this ); // remove + + if( !pFormat->HasWriterListeners() ) + pFormat->GetDoc()->DelTableFrameFormat( pFormat ); // and delete + + // Delete the pointers from the SortArray of the boxes. The objects + // are preserved and are deleted by the lines/boxes arrays dtor. + // Note: unfortunately not enough, pointers to the StartNode of the + // section need deletion. + DelBoxNode(m_TabSortContentBoxes); + m_TabSortContentBoxes.clear(); +} + +namespace +{ + +template<class T> +T lcl_MulDiv64(sal_uInt64 nA, sal_uInt64 nM, sal_uInt64 nD) +{ + return static_cast<T>((nA*nM)/nD); +} + +} + +static void FormatInArr( std::vector<SwFormat*>& rFormatArr, SwFormat* pBoxFormat ) +{ + std::vector<SwFormat*>::const_iterator it = std::find( rFormatArr.begin(), rFormatArr.end(), pBoxFormat ); + if ( it == rFormatArr.end() ) + rFormatArr.push_back( pBoxFormat ); +} + +static void lcl_ModifyBoxes( SwTableBoxes &rBoxes, const long nOld, + const long nNew, std::vector<SwFormat*>& rFormatArr ); + +static void lcl_ModifyLines( SwTableLines &rLines, const long nOld, + const long nNew, std::vector<SwFormat*>& rFormatArr, const bool bCheckSum ) +{ + for ( size_t i = 0; i < rLines.size(); ++i ) + ::lcl_ModifyBoxes( rLines[i]->GetTabBoxes(), nOld, nNew, rFormatArr ); + if( bCheckSum ) + { + for(SwFormat* pFormat : rFormatArr) + { + const SwTwips nBox = lcl_MulDiv64<SwTwips>(pFormat->GetFrameSize().GetWidth(), nNew, nOld); + SwFormatFrameSize aNewBox( SwFrameSize::Variable, nBox, 0 ); + pFormat->LockModify(); + pFormat->SetFormatAttr( aNewBox ); + pFormat->UnlockModify(); + } + } +} + +static void lcl_ModifyBoxes( SwTableBoxes &rBoxes, const long nOld, + const long nNew, std::vector<SwFormat*>& rFormatArr ) +{ + sal_uInt64 nSum = 0; // To avoid rounding errors we summarize all box widths + sal_uInt64 nOriginalSum = 0; // Sum of original widths + for ( size_t i = 0; i < rBoxes.size(); ++i ) + { + SwTableBox &rBox = *rBoxes[i]; + if ( !rBox.GetTabLines().empty() ) + { + // For SubTables the rounding problem will not be solved :-( + ::lcl_ModifyLines( rBox.GetTabLines(), nOld, nNew, rFormatArr, false ); + } + // Adjust the box + SwFrameFormat *pFormat = rBox.GetFrameFormat(); + sal_uInt64 nBox = pFormat->GetFrameSize().GetWidth(); + nOriginalSum += nBox; + nBox *= nNew; + nBox /= nOld; + const sal_uInt64 nWishedSum = lcl_MulDiv64<sal_uInt64>(nOriginalSum, nNew, nOld) - nSum; + if( nWishedSum > 0 ) + { + if( nBox == nWishedSum ) + FormatInArr( rFormatArr, pFormat ); + else + { + nBox = nWishedSum; + pFormat = rBox.ClaimFrameFormat(); + SwFormatFrameSize aNewBox( SwFrameSize::Variable, static_cast< SwTwips >(nBox), 0 ); + pFormat->LockModify(); + pFormat->SetFormatAttr( aNewBox ); + pFormat->UnlockModify(); + } + } + else { + OSL_FAIL( "Rounding error" ); + } + nSum += nBox; + } +} + +void SwTable::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + // catch SSize changes, to adjust the lines/boxes + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0 ; + const SwFormatFrameSize* pNewSize = nullptr, *pOldSize = nullptr; + + if( RES_ATTRSET_CHG == nWhich ) + { + if (pOld && pNew && SfxItemState::SET == static_cast<const SwAttrSetChg*>(pNew)->GetChgSet()->GetItemState( + RES_FRM_SIZE, false, reinterpret_cast<const SfxPoolItem**>(&pNewSize))) + { + pOldSize = &static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->GetFrameSize(); + } + } + else if( RES_FRM_SIZE == nWhich ) + { + pOldSize = static_cast<const SwFormatFrameSize*>(pOld); + pNewSize = static_cast<const SwFormatFrameSize*>(pNew); + } + else + CheckRegistration( pOld ); + + if (pOldSize && pNewSize && !m_bModifyLocked) + AdjustWidths( pOldSize->GetWidth(), pNewSize->GetWidth() ); +} + +void SwTable::AdjustWidths( const long nOld, const long nNew ) +{ + std::vector<SwFormat*> aFormatArr; + aFormatArr.reserve( m_aLines[0]->GetTabBoxes().size() ); + ::lcl_ModifyLines( m_aLines, nOld, nNew, aFormatArr, true ); +} + +static void lcl_RefreshHidden( SwTabCols &rToFill, size_t nPos ) +{ + for ( size_t i = 0; i < rToFill.Count(); ++i ) + { + if ( std::abs(static_cast<long>(nPos) - rToFill[i]) <= COLFUZZY ) + { + rToFill.SetHidden( i, false ); + break; + } + } +} + +static void lcl_SortedTabColInsert( SwTabCols &rToFill, const SwTableBox *pBox, + const SwFrameFormat *pTabFormat, const bool bHidden, + const bool bRefreshHidden ) +{ + const long nWish = pTabFormat->GetFrameSize().GetWidth(); + OSL_ENSURE(nWish, "weird <= 0 width frmfrm"); + + // The value for the left edge of the box is calculated from the + // widths of the previous boxes. + long nPos = 0; + long nLeftMin = 0; + long nRightMax = 0; + if (nWish != 0) //fdo#33012 0 width frmfmt + { + SwTwips nSum = 0; + const SwTableBox *pCur = pBox; + const SwTableLine *pLine = pBox->GetUpper(); + const long nAct = rToFill.GetRight() - rToFill.GetLeft(); // +1 why? + + while ( pLine ) + { + const SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( size_t i = 0; i < rBoxes.size(); ++i ) + { + const SwTwips nWidth = rBoxes[i]->GetFrameFormat()->GetFrameSize().GetWidth(); + nSum += nWidth; + const long nTmp = lcl_MulDiv64<long>(nSum, nAct, nWish); + + if (rBoxes[i] != pCur) + { + if ( pLine == pBox->GetUpper() || 0 == nLeftMin ) + nLeftMin = nTmp - nPos; + nPos = nTmp; + } + else + { + nSum -= nWidth; + if ( 0 == nRightMax ) + nRightMax = nTmp - nPos; + break; + } + } + pCur = pLine->GetUpper(); + pLine = pCur ? pCur->GetUpper() : nullptr; + } + } + + bool bInsert = !bRefreshHidden; + for ( size_t j = 0; bInsert && (j < rToFill.Count()); ++j ) + { + long nCmp = rToFill[j]; + if ( (nPos >= ((nCmp >= COLFUZZY) ? nCmp - COLFUZZY : nCmp)) && + (nPos <= (nCmp + COLFUZZY)) ) + { + bInsert = false; // Already has it. + } + else if ( nPos < nCmp ) + { + bInsert = false; + rToFill.Insert( nPos, bHidden, j ); + } + } + if ( bInsert ) + rToFill.Insert( nPos, bHidden, rToFill.Count() ); + else if ( bRefreshHidden ) + ::lcl_RefreshHidden( rToFill, nPos ); + + if ( bHidden && !bRefreshHidden ) + { + // calculate minimum/maximum values for the existing entries: + nLeftMin = nPos - nLeftMin; + nRightMax = nPos + nRightMax; + + // check if nPos is entry: + bool bFoundPos = false; + bool bFoundMax = false; + for ( size_t j = 0; !(bFoundPos && bFoundMax ) && j < rToFill.Count(); ++j ) + { + SwTabColsEntry& rEntry = rToFill.GetEntry( j ); + long nCmp = rToFill[j]; + + if ( (nPos >= ((nCmp >= COLFUZZY) ? nCmp - COLFUZZY : nCmp)) && + (nPos <= (nCmp + COLFUZZY)) ) + { + // check if nLeftMin is > old minimum for entry nPos: + const long nOldMin = rEntry.nMin; + if ( nLeftMin > nOldMin ) + rEntry.nMin = nLeftMin; + // check if nRightMin is < old maximum for entry nPos: + const long nOldMax = rEntry.nMax; + if ( nRightMax < nOldMax ) + rEntry.nMax = nRightMax; + + bFoundPos = true; + } + else if ( (nRightMax >= ((nCmp >= COLFUZZY) ? nCmp - COLFUZZY : nCmp)) && + (nRightMax <= (nCmp + COLFUZZY)) ) + { + // check if nPos is > old minimum for entry nRightMax: + const long nOldMin = rEntry.nMin; + if ( nPos > nOldMin ) + rEntry.nMin = nPos; + + bFoundMax = true; + } + } + } +} + +static void lcl_ProcessBoxGet( const SwTableBox *pBox, SwTabCols &rToFill, + const SwFrameFormat *pTabFormat, bool bRefreshHidden ) +{ + if ( !pBox->GetTabLines().empty() ) + { + const SwTableLines &rLines = pBox->GetTabLines(); + for ( size_t i = 0; i < rLines.size(); ++i ) + { + const SwTableBoxes &rBoxes = rLines[i]->GetTabBoxes(); + for ( size_t j = 0; j < rBoxes.size(); ++j ) + ::lcl_ProcessBoxGet( rBoxes[j], rToFill, pTabFormat, bRefreshHidden); + } + } + else + ::lcl_SortedTabColInsert( rToFill, pBox, pTabFormat, false, bRefreshHidden ); +} + +static void lcl_ProcessLineGet( const SwTableLine *pLine, SwTabCols &rToFill, + const SwFrameFormat *pTabFormat ) +{ + for ( size_t i = 0; i < pLine->GetTabBoxes().size(); ++i ) + { + const SwTableBox *pBox = pLine->GetTabBoxes()[i]; + if ( pBox->GetSttNd() ) + ::lcl_SortedTabColInsert( rToFill, pBox, pTabFormat, true, false ); + else + for ( size_t j = 0; j < pBox->GetTabLines().size(); ++j ) + ::lcl_ProcessLineGet( pBox->GetTabLines()[j], rToFill, pTabFormat ); + } +} + +void SwTable::GetTabCols( SwTabCols &rToFill, const SwTableBox *pStart, + bool bRefreshHidden, bool bCurRowOnly ) const +{ + // Optimization: if bHidden is set, we only update the Hidden Array. + if ( bRefreshHidden ) + { + // remove corrections + for ( size_t i = 0; i < rToFill.Count(); ++i ) + { + SwTabColsEntry& rEntry = rToFill.GetEntry( i ); + rEntry.nPos -= rToFill.GetLeft(); + rEntry.nMin -= rToFill.GetLeft(); + rEntry.nMax -= rToFill.GetLeft(); + } + + // All are hidden, so add the visible ones. + for ( size_t i = 0; i < rToFill.Count(); ++i ) + rToFill.SetHidden( i, true ); + } + else + { + rToFill.Remove( 0, rToFill.Count() ); + } + + // Insertion cases: + // 1. All boxes which are inferior to Line which is superior to the Start, + // as well as their inferior boxes if present. + // 2. Starting from the Line, the superior box plus its neighbours; but no inferiors. + // 3. Apply 2. to the Line superior to the chain of boxes, + // until the Line's superior is not a box but the table. + // Only those boxes are inserted that don't contain further rows. The insertion + // function takes care to avoid duplicates. In order to achieve this, we work + // with some degree of fuzzyness (to avoid rounding errors). + // Only the left edge of the boxes are inserted. + // Finally, the first entry is removed again, because it's already + // covered by the border. + // 4. Scan the table again and insert _all_ boxes, this time as hidden. + + const SwFrameFormat *pTabFormat = GetFrameFormat(); + + // 1. + const SwTableBoxes &rBoxes = pStart->GetUpper()->GetTabBoxes(); + + for ( size_t i = 0; i < rBoxes.size(); ++i ) + ::lcl_ProcessBoxGet( rBoxes[i], rToFill, pTabFormat, bRefreshHidden ); + + // 2. and 3. + const SwTableLine *pLine = pStart->GetUpper()->GetUpper() ? + pStart->GetUpper()->GetUpper()->GetUpper() : nullptr; + while ( pLine ) + { + const SwTableBoxes &rBoxes2 = pLine->GetTabBoxes(); + for ( size_t k = 0; k < rBoxes2.size(); ++k ) + ::lcl_SortedTabColInsert( rToFill, rBoxes2[k], + pTabFormat, false, bRefreshHidden ); + pLine = pLine->GetUpper() ? pLine->GetUpper()->GetUpper() : nullptr; + } + + if ( !bRefreshHidden ) + { + // 4. + if ( !bCurRowOnly ) + { + for ( size_t i = 0; i < m_aLines.size(); ++i ) + ::lcl_ProcessLineGet( m_aLines[i], rToFill, pTabFormat ); + } + + rToFill.Remove( 0 ); + } + + // Now the coordinates are relative to the left table border - i.e. + // relative to SwTabCols.nLeft. However, they are expected + // relative to the left document border, i.e. SwTabCols.nLeftMin. + // So all values need to be extended by nLeft. + for ( size_t i = 0; i < rToFill.Count(); ++i ) + { + SwTabColsEntry& rEntry = rToFill.GetEntry( i ); + rEntry.nPos += rToFill.GetLeft(); + rEntry.nMin += rToFill.GetLeft(); + rEntry.nMax += rToFill.GetLeft(); + } +} + +// Structure for parameter passing +struct Parm +{ + const SwTabCols &rNew; + const SwTabCols &rOld; + long nNewWish, + nOldWish; + std::deque<SwTableBox*> aBoxArr; + SwShareBoxFormats aShareFormats; + + Parm( const SwTabCols &rN, const SwTabCols &rO ) + : rNew( rN ), rOld( rO ), nNewWish(0), nOldWish(0) + {} +}; + +static void lcl_ProcessBoxSet( SwTableBox *pBox, Parm &rParm ); + +static void lcl_ProcessLine( SwTableLine *pLine, Parm &rParm ) +{ + SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( size_t i = rBoxes.size(); i > 0; ) + { + --i; + ::lcl_ProcessBoxSet( rBoxes[i], rParm ); + } +} + +static void lcl_ProcessBoxSet( SwTableBox *pBox, Parm &rParm ) +{ + if ( !pBox->GetTabLines().empty() ) + { + SwTableLines &rLines = pBox->GetTabLines(); + for ( size_t i = rLines.size(); i > 0; ) + { + --i; + lcl_ProcessLine( rLines[i], rParm ); + } + } + else + { + // Search the old TabCols for the current position (calculate from + // left and right edge). Adjust the box if the values differ from + // the new TabCols. If the adjusted edge has no neighbour we also + // adjust all superior boxes. + + const long nOldAct = rParm.rOld.GetRight() - + rParm.rOld.GetLeft(); // +1 why? + + // The value for the left edge of the box is calculated from the + // widths of the previous boxes plus the left edge. + long nLeft = rParm.rOld.GetLeft(); + const SwTableBox *pCur = pBox; + const SwTableLine *pLine = pBox->GetUpper(); + + while ( pLine ) + { + const SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( size_t i = 0; (i < rBoxes.size()) && (rBoxes[i] != pCur); ++i) + { + nLeft += lcl_MulDiv64<long>( + rBoxes[i]->GetFrameFormat()->GetFrameSize().GetWidth(), + nOldAct, rParm.nOldWish); + } + pCur = pLine->GetUpper(); + pLine = pCur ? pCur->GetUpper() : nullptr; + } + long nLeftDiff = 0; + long nRightDiff = 0; + if ( nLeft != rParm.rOld.GetLeft() ) // There are still boxes before this. + { + // Right edge is left edge plus width. + const long nWidth = lcl_MulDiv64<long>( + pBox->GetFrameFormat()->GetFrameSize().GetWidth(), + nOldAct, rParm.nOldWish); + const long nRight = nLeft + nWidth; + size_t nLeftPos = 0; + size_t nRightPos = 0; + bool bFoundLeftPos = false; + bool bFoundRightPos = false; + for ( size_t i = 0; i < rParm.rOld.Count(); ++i ) + { + if ( nLeft >= (rParm.rOld[i] - COLFUZZY) && + nLeft <= (rParm.rOld[i] + COLFUZZY) ) + { + nLeftPos = i; + bFoundLeftPos = true; + } + else if ( nRight >= (rParm.rOld[i] - COLFUZZY) && + nRight <= (rParm.rOld[i] + COLFUZZY) ) + { + nRightPos = i; + bFoundRightPos = true; + } + } + nLeftDiff = bFoundLeftPos ? + rParm.rOld[nLeftPos] - rParm.rNew[nLeftPos] : 0; + nRightDiff= bFoundRightPos ? + rParm.rNew[nRightPos] - rParm.rOld[nRightPos] : 0; + } + else // The first box. + { + nLeftDiff = rParm.rOld.GetLeft() - rParm.rNew.GetLeft(); + if ( rParm.rOld.Count() ) + { + // Calculate the difference to the edge touching the first box. + const long nWidth = lcl_MulDiv64<long>( + pBox->GetFrameFormat()->GetFrameSize().GetWidth(), + nOldAct, rParm.nOldWish); + const long nTmp = nWidth + rParm.rOld.GetLeft(); + for ( size_t i = 0; i < rParm.rOld.Count(); ++i ) + { + if ( nTmp >= (rParm.rOld[i] - COLFUZZY) && + nTmp <= (rParm.rOld[i] + COLFUZZY) ) + { + nRightDiff = rParm.rNew[i] - rParm.rOld[i]; + break; + } + } + } + } + + if( pBox->getRowSpan() == 1 ) + { + const sal_uInt16 nPos = pBox->GetUpper()->GetBoxPos( pBox ); + SwTableBoxes& rTableBoxes = pBox->GetUpper()->GetTabBoxes(); + if( nPos && rTableBoxes[ nPos - 1 ]->getRowSpan() != 1 ) + nLeftDiff = 0; + if( nPos + 1 < static_cast<sal_uInt16>(rTableBoxes.size()) && + rTableBoxes[ nPos + 1 ]->getRowSpan() != 1 ) + nRightDiff = 0; + } + else + nLeftDiff = nRightDiff = 0; + + if ( nLeftDiff || nRightDiff ) + { + // The difference is the actual difference amount. For stretched + // tables, it does not make sense to adjust the attributes of the + // boxes by this amount. The difference amount needs to be converted + // accordingly. + long nTmp = rParm.rNew.GetRight() - rParm.rNew.GetLeft(); // +1 why? + nLeftDiff *= rParm.nNewWish; + nLeftDiff /= nTmp; + nRightDiff *= rParm.nNewWish; + nRightDiff /= nTmp; + long nDiff = nLeftDiff + nRightDiff; + + // Adjust the box and all superiors by the difference amount. + while ( pBox ) + { + SwFormatFrameSize aFormatFrameSize( pBox->GetFrameFormat()->GetFrameSize() ); + aFormatFrameSize.SetWidth( aFormatFrameSize.GetWidth() + nDiff ); + if ( aFormatFrameSize.GetWidth() < 0 ) + aFormatFrameSize.SetWidth( -aFormatFrameSize.GetWidth() ); + rParm.aShareFormats.SetSize( *pBox, aFormatFrameSize ); + + // The outer cells of the last row are responsible to adjust a surrounding cell. + // Last line check: + if ( pBox->GetUpper()->GetUpper() && + pBox->GetUpper() != pBox->GetUpper()->GetUpper()->GetTabLines().back()) + { + pBox = nullptr; + } + else + { + // Middle cell check: + if ( pBox != pBox->GetUpper()->GetTabBoxes().front() ) + nDiff = nRightDiff; + + if ( pBox != pBox->GetUpper()->GetTabBoxes().back() ) + nDiff -= nRightDiff; + + pBox = nDiff ? pBox->GetUpper()->GetUpper() : nullptr; + } + } + } + } +} + +static void lcl_ProcessBoxPtr( SwTableBox *pBox, std::deque<SwTableBox*> &rBoxArr, + bool bBefore ) +{ + if ( !pBox->GetTabLines().empty() ) + { + const SwTableLines &rLines = pBox->GetTabLines(); + for ( size_t i = 0; i < rLines.size(); ++i ) + { + const SwTableBoxes &rBoxes = rLines[i]->GetTabBoxes(); + for ( size_t j = 0; j < rBoxes.size(); ++j ) + ::lcl_ProcessBoxPtr( rBoxes[j], rBoxArr, bBefore ); + } + } + else if ( bBefore ) + rBoxArr.push_front( pBox ); + else + rBoxArr.push_back( pBox ); +} + +static void lcl_AdjustBox( SwTableBox *pBox, const long nDiff, Parm &rParm ); + +static void lcl_AdjustLines( SwTableLines &rLines, const long nDiff, Parm &rParm ) +{ + for ( size_t i = 0; i < rLines.size(); ++i ) + { + SwTableBox *pBox = rLines[i]->GetTabBoxes() + [rLines[i]->GetTabBoxes().size()-1]; + lcl_AdjustBox( pBox, nDiff, rParm ); + } +} + +static void lcl_AdjustBox( SwTableBox *pBox, const long nDiff, Parm &rParm ) +{ + if ( !pBox->GetTabLines().empty() ) + ::lcl_AdjustLines( pBox->GetTabLines(), nDiff, rParm ); + + // Adjust the size of the box. + SwFormatFrameSize aFormatFrameSize( pBox->GetFrameFormat()->GetFrameSize() ); + aFormatFrameSize.SetWidth( aFormatFrameSize.GetWidth() + nDiff ); + + rParm.aShareFormats.SetSize( *pBox, aFormatFrameSize ); +} + +void SwTable::SetTabCols( const SwTabCols &rNew, const SwTabCols &rOld, + const SwTableBox *pStart, bool bCurRowOnly ) +{ + CHECK_TABLE( *this ) + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // delete HTML-Layout + + // FME: Made rOld const. The caller is responsible for passing correct + // values of rOld. Therefore we do not have to call GetTabCols anymore: + //GetTabCols( rOld, pStart ); + + Parm aParm( rNew, rOld ); + + OSL_ENSURE( rOld.Count() == rNew.Count(), "Number of columns changed."); + + // Convert the edges. We need to adjust the size of the table and some boxes. + // For the size adjustment, we must not make use of the Modify, since that'd + // adjust all boxes, which we really don't want. + SwFrameFormat *pFormat = GetFrameFormat(); + aParm.nOldWish = aParm.nNewWish = pFormat->GetFrameSize().GetWidth(); + if ( (rOld.GetLeft() != rNew.GetLeft()) || + (rOld.GetRight()!= rNew.GetRight()) ) + { + LockModify(); + { + SvxLRSpaceItem aLR( pFormat->GetLRSpace() ); + SvxShadowItem aSh( pFormat->GetShadow() ); + + SwTwips nShRight = aSh.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + SwTwips nShLeft = aSh.CalcShadowSpace( SvxShadowItemSide::LEFT ); + + aLR.SetLeft ( rNew.GetLeft() - nShLeft ); + aLR.SetRight( rNew.GetRightMax() - rNew.GetRight() - nShRight ); + pFormat->SetFormatAttr( aLR ); + + // The alignment of the table needs to be adjusted accordingly. + // This is done by preserving the exact positions that have been + // set by the user. + SwFormatHoriOrient aOri( pFormat->GetHoriOrient() ); + if(text::HoriOrientation::NONE != aOri.GetHoriOrient()) + { + const bool bLeftDist = rNew.GetLeft() != nShLeft; + const bool bRightDist = rNew.GetRight() + nShRight != rNew.GetRightMax(); + if(!bLeftDist && !bRightDist) + aOri.SetHoriOrient( text::HoriOrientation::FULL ); + else if(!bRightDist && rNew.GetLeft() > nShLeft ) + aOri.SetHoriOrient( text::HoriOrientation::RIGHT ); + else if(!bLeftDist && rNew.GetRight() + nShRight < rNew.GetRightMax()) + aOri.SetHoriOrient( text::HoriOrientation::LEFT ); + else + aOri.SetHoriOrient( text::HoriOrientation::LEFT_AND_WIDTH ); + } + pFormat->SetFormatAttr( aOri ); + } + const long nAct = rOld.GetRight() - rOld.GetLeft(); // +1 why? + long nTabDiff = 0; + + if ( rOld.GetLeft() != rNew.GetLeft() ) + { + nTabDiff = rOld.GetLeft() - rNew.GetLeft(); + nTabDiff *= aParm.nOldWish; + nTabDiff /= nAct; + } + if ( rOld.GetRight() != rNew.GetRight() ) + { + long nDiff = rNew.GetRight() - rOld.GetRight(); + nDiff *= aParm.nOldWish; + nDiff /= nAct; + nTabDiff += nDiff; + if( !IsNewModel() ) + ::lcl_AdjustLines( GetTabLines(), nDiff, aParm ); + } + + // Adjust the size of the table, watch out for stretched tables. + if ( nTabDiff ) + { + aParm.nNewWish += nTabDiff; + if ( aParm.nNewWish < 0 ) + aParm.nNewWish = USHRT_MAX; // Oops! Have to roll back. + SwFormatFrameSize aSz( pFormat->GetFrameSize() ); + if ( aSz.GetWidth() != aParm.nNewWish ) + { + aSz.SetWidth( aParm.nNewWish ); + aSz.SetWidthPercent( 0 ); + pFormat->SetFormatAttr( aSz ); + } + } + UnlockModify(); + } + + if( IsNewModel() ) + NewSetTabCols( aParm, rNew, rOld, pStart, bCurRowOnly ); + else + { + if ( bCurRowOnly ) + { + // To adjust the current row, we need to process all its boxes, + // similar to the filling of the TabCols (see GetTabCols()). + // Unfortunately we again have to take care to adjust the boxes + // from back to front, respectively from outer to inner. + // The best way to achieve this is probably to track the boxes + // in a PtrArray. + const SwTableBoxes &rBoxes = pStart->GetUpper()->GetTabBoxes(); + for ( size_t i = 0; i < rBoxes.size(); ++i ) + ::lcl_ProcessBoxPtr( rBoxes[i], aParm.aBoxArr, false ); + + const SwTableLine *pLine = pStart->GetUpper()->GetUpper() ? + pStart->GetUpper()->GetUpper()->GetUpper() : nullptr; + const SwTableBox *pExcl = pStart->GetUpper()->GetUpper(); + while ( pLine ) + { + const SwTableBoxes &rBoxes2 = pLine->GetTabBoxes(); + bool bBefore = true; + for ( size_t i = 0; i < rBoxes2.size(); ++i ) + { + if ( rBoxes2[i] != pExcl ) + ::lcl_ProcessBoxPtr( rBoxes2[i], aParm.aBoxArr, bBefore ); + else + bBefore = false; + } + pExcl = pLine->GetUpper(); + pLine = pLine->GetUpper() ? pLine->GetUpper()->GetUpper() : nullptr; + } + // After we've inserted a bunch of boxes (hopefully all and in + // correct order), we just need to process them in reverse order. + for ( int j = aParm.aBoxArr.size()-1; j >= 0; --j ) + { + SwTableBox *pBox = aParm.aBoxArr[j]; + ::lcl_ProcessBoxSet( pBox, aParm ); + } + } + else + { + // Adjusting the entire table is 'easy'. All boxes without lines are + // adjusted, as are their superiors. Of course we need to process + // in reverse order to prevent fooling ourselves! + SwTableLines &rLines = GetTabLines(); + for ( size_t i = rLines.size(); i > 0; ) + { + --i; + ::lcl_ProcessLine( rLines[i], aParm ); + } + } + } + +#ifdef DBG_UTIL + { + // do some checking for correct table widths + SwTwips nSize = GetFrameFormat()->GetFrameSize().GetWidth(); + for (size_t n = 0; n < m_aLines.size(); ++n) + { + CheckBoxWidth( *m_aLines[ n ], nSize ); + } + } +#endif +} + +typedef std::pair<sal_uInt16, sal_uInt16> ColChange; +typedef std::list< ColChange > ChangeList; + +static void lcl_AdjustWidthsInLine( SwTableLine* pLine, ChangeList& rOldNew, + Parm& rParm, sal_uInt16 nColFuzzy ) +{ + ChangeList::iterator pCurr = rOldNew.begin(); + if( pCurr == rOldNew.end() ) + return; + const size_t nCount = pLine->GetTabBoxes().size(); + SwTwips nBorder = 0; + SwTwips nRest = 0; + for( size_t i = 0; i < nCount; ++i ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[i]; + SwTwips nWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + SwTwips nNewWidth = nWidth - nRest; + nRest = 0; + nBorder += nWidth; + if( pCurr != rOldNew.end() && nBorder + nColFuzzy >= pCurr->first ) + { + nBorder -= nColFuzzy; + while( pCurr != rOldNew.end() && nBorder > pCurr->first ) + ++pCurr; + if( pCurr != rOldNew.end() ) + { + nBorder += nColFuzzy; + if( nBorder + nColFuzzy >= pCurr->first ) + { + if( pCurr->second == pCurr->first ) + nRest = 0; + else + nRest = pCurr->second - nBorder; + nNewWidth += nRest; + ++pCurr; + } + } + } + if( nNewWidth != nWidth ) + { + if( nNewWidth < 0 ) + { + nRest += 1 - nNewWidth; + nNewWidth = 1; + } + SwFormatFrameSize aFormatFrameSize( pBox->GetFrameFormat()->GetFrameSize() ); + aFormatFrameSize.SetWidth( nNewWidth ); + rParm.aShareFormats.SetSize( *pBox, aFormatFrameSize ); + } + } +} + +static void lcl_CalcNewWidths( std::list<sal_uInt16> &rSpanPos, ChangeList& rChanges, + SwTableLine* pLine, long nWish, long nWidth, bool bTop ) +{ + if( rChanges.empty() ) + { + rSpanPos.clear(); + return; + } + if( rSpanPos.empty() ) + { + rChanges.clear(); + return; + } + std::list<sal_uInt16> aNewSpanPos; + ChangeList aNewChanges; + ChangeList::iterator pCurr = rChanges.begin(); + aNewChanges.push_back( *pCurr ); // Nullposition + std::list<sal_uInt16>::iterator pSpan = rSpanPos.begin(); + sal_uInt16 nCurr = 0; + SwTwips nOrgSum = 0; + bool bRowSpan = false; + sal_uInt16 nRowSpanCount = 0; + const size_t nCount = pLine->GetTabBoxes().size(); + for( size_t nCurrBox = 0; nCurrBox < nCount; ++nCurrBox ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nCurrBox]; + SwTwips nCurrWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + const long nRowSpan = pBox->getRowSpan(); + const bool bCurrRowSpan = bTop ? nRowSpan < 0 : + ( nRowSpan > 1 || nRowSpan < -1 ); + if( bRowSpan || bCurrRowSpan ) + aNewSpanPos.push_back( nRowSpanCount ); + bRowSpan = bCurrRowSpan; + nOrgSum += nCurrWidth; + const sal_uInt16 nPos = lcl_MulDiv64<sal_uInt16>( + lcl_MulDiv64<sal_uInt64>(nOrgSum, nWidth, nWish), + nWish, nWidth); + while( pCurr != rChanges.end() && pCurr->first < nPos ) + { + ++nCurr; + ++pCurr; + } + bool bNew = true; + if( pCurr != rChanges.end() && pCurr->first <= nPos && + pCurr->first != pCurr->second ) + { + pSpan = std::find_if(pSpan, rSpanPos.end(), + [nCurr](const sal_uInt16 nSpan) { return nSpan >= nCurr; }); + if( pSpan != rSpanPos.end() && *pSpan == nCurr ) + { + aNewChanges.push_back( *pCurr ); + ++nRowSpanCount; + bNew = false; + } + } + if( bNew ) + { + ColChange aTmp( nPos, nPos ); + aNewChanges.push_back( aTmp ); + ++nRowSpanCount; + } + } + + pCurr = aNewChanges.begin(); + ChangeList::iterator pLast = pCurr; + ChangeList::iterator pLeftMove = pCurr; + while( pCurr != aNewChanges.end() ) + { + if( pLeftMove == pCurr ) + { + while( ++pLeftMove != aNewChanges.end() && pLeftMove->first <= pLeftMove->second ) + ; + } + if( pCurr->second == pCurr->first ) + { + if( pLeftMove != aNewChanges.end() && pCurr->second > pLeftMove->second ) + { + if( pLeftMove->first == pLast->first ) + pCurr->second = pLeftMove->second; + else + { + pCurr->second = lcl_MulDiv64<sal_uInt16>( + pCurr->first - pLast->first, + pLeftMove->second - pLast->second, + pLeftMove->first - pLast->first) + pLast->second; + } + } + pLast = pCurr; + ++pCurr; + } + else if( pCurr->second > pCurr->first ) + { + pLast = pCurr; + ++pCurr; + ChangeList::iterator pNext = pCurr; + while( pNext != pLeftMove && pNext->second == pNext->first && + pNext->second < pLast->second ) + ++pNext; + while( pCurr != pNext ) + { + if( pNext == aNewChanges.end() || pNext->first == pLast->first ) + pCurr->second = pLast->second; + else + { + pCurr->second = lcl_MulDiv64<sal_uInt16>( + pCurr->first - pLast->first, + pNext->second - pLast->second, + pNext->first - pLast->first) + pLast->second; + } + ++pCurr; + } + pLast = pCurr; + } + else + { + pLast = pCurr; + ++pCurr; + } + } + + rChanges.swap(aNewChanges); + rSpanPos.swap(aNewSpanPos); +} + +void SwTable::NewSetTabCols( Parm &rParm, const SwTabCols &rNew, + const SwTabCols &rOld, const SwTableBox *pStart, bool bCurRowOnly ) +{ +#if OSL_DEBUG_LEVEL > 1 + static int nCallCount = 0; + ++nCallCount; +#endif + // First step: evaluate which lines have been moved/which widths changed + ChangeList aOldNew; + const long nNewWidth = rParm.rNew.GetRight() - rParm.rNew.GetLeft(); + const long nOldWidth = rParm.rOld.GetRight() - rParm.rOld.GetLeft(); + if( nNewWidth < 1 || nOldWidth < 1 ) + return; + for( size_t i = 0; i <= rOld.Count(); ++i ) + { + long nNewPos; + long nOldPos; + if( i == rOld.Count() ) + { + nOldPos = rParm.rOld.GetRight() - rParm.rOld.GetLeft(); + nNewPos = rParm.rNew.GetRight() - rParm.rNew.GetLeft(); + } + else + { + nOldPos = rOld[i] - rParm.rOld.GetLeft(); + nNewPos = rNew[i] - rParm.rNew.GetLeft(); + } + nNewPos = lcl_MulDiv64<long>(nNewPos, rParm.nNewWish, nNewWidth); + nOldPos = lcl_MulDiv64<long>(nOldPos, rParm.nOldWish, nOldWidth); + if( nOldPos != nNewPos && nNewPos > 0 && nOldPos > 0 ) + { + ColChange aChg( static_cast<sal_uInt16>(nOldPos), static_cast<sal_uInt16>(nNewPos) ); + aOldNew.push_back( aChg ); + } + } + // Finished first step + int nCount = aOldNew.size(); + if( !nCount ) + return; // no change, nothing to do + SwTableLines &rLines = GetTabLines(); + if( bCurRowOnly ) + { + const SwTableLine* pCurrLine = pStart->GetUpper(); + sal_uInt16 nCurr = rLines.GetPos( pCurrLine ); + if( nCurr >= USHRT_MAX ) + return; + + ColChange aChg( 0, 0 ); + aOldNew.push_front( aChg ); + std::list<sal_uInt16> aRowSpanPos; + if( nCurr ) + { + ChangeList aCopy; + sal_uInt16 nPos = 0; + for( const auto& rCop : aOldNew ) + { + aCopy.push_back( rCop ); + aRowSpanPos.push_back( nPos++ ); + } + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[nCurr], + rParm.nOldWish, nOldWidth, true ); + bool bGoOn = !aRowSpanPos.empty(); + sal_uInt16 j = nCurr; + while( bGoOn ) + { + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[--j], + rParm.nOldWish, nOldWidth, true ); + lcl_AdjustWidthsInLine( rLines[j], aCopy, rParm, 0 ); + bGoOn = !aRowSpanPos.empty() && j > 0; + } + aRowSpanPos.clear(); + } + if( nCurr+1 < static_cast<sal_uInt16>(rLines.size()) ) + { + ChangeList aCopy; + sal_uInt16 nPos = 0; + for( const auto& rCop : aOldNew ) + { + aCopy.push_back( rCop ); + aRowSpanPos.push_back( nPos++ ); + } + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[nCurr], + rParm.nOldWish, nOldWidth, false ); + bool bGoOn = !aRowSpanPos.empty(); + sal_uInt16 j = nCurr; + while( bGoOn ) + { + lcl_CalcNewWidths( aRowSpanPos, aCopy, rLines[++j], + rParm.nOldWish, nOldWidth, false ); + lcl_AdjustWidthsInLine( rLines[j], aCopy, rParm, 0 ); + bGoOn = !aRowSpanPos.empty() && j+1 < static_cast<sal_uInt16>(rLines.size()); + } + } + ::lcl_AdjustWidthsInLine( rLines[nCurr], aOldNew, rParm, COLFUZZY ); + } + else + { + for( size_t i = 0; i < rLines.size(); ++i ) + ::lcl_AdjustWidthsInLine( rLines[i], aOldNew, rParm, COLFUZZY ); + } + CHECK_TABLE( *this ) +} + +// return the pointer of the box specified. +static bool lcl_IsValidRowName( const OUString& rStr ) +{ + bool bIsValid = true; + sal_Int32 nLen = rStr.getLength(); + for( sal_Int32 i = 0; i < nLen && bIsValid; ++i ) + { + const sal_Unicode cChar = rStr[i]; + if (cChar < '0' || cChar > '9') + bIsValid = false; + } + return bIsValid; +} + +// #i80314# +// add 3rd parameter and its handling +sal_uInt16 SwTable::GetBoxNum( OUString& rStr, bool bFirstPart, + const bool bPerformValidCheck ) +{ + sal_uInt16 nRet = 0; + if( bFirstPart ) // true == column; false == row + { + sal_Int32 nPos = 0; + // the first one uses letters for addressing! + bool bFirst = true; + sal_uInt32 num = 0; + bool overflow = false; + while (nPos<rStr.getLength()) + { + sal_Unicode cChar = rStr[nPos]; + if ((cChar<'A' || cChar>'Z') && (cChar<'a' || cChar>'z')) + break; + if( (cChar -= 'A') >= 26 ) + cChar -= 'a' - '['; + if( bFirst ) + bFirst = false; + else + ++num; + num = num * 52 + cChar; + if (num > SAL_MAX_UINT16) { + overflow = true; + } + ++nPos; + } + nRet = overflow ? SAL_MAX_UINT16 : num; + rStr = rStr.copy( nPos ); // Remove char from String + } + else + { + const sal_Int32 nPos = rStr.indexOf( "." ); + if ( nPos<0 ) + { + nRet = 0; + if ( !bPerformValidCheck || lcl_IsValidRowName( rStr ) ) + { + nRet = static_cast<sal_uInt16>(rStr.toInt32()); + } + rStr.clear(); + } + else + { + nRet = 0; + const OUString aText( rStr.copy( 0, nPos ) ); + if ( !bPerformValidCheck || lcl_IsValidRowName( aText ) ) + { + nRet = static_cast<sal_uInt16>(aText.toInt32()); + } + rStr = rStr.copy( nPos+1 ); + } + } + return nRet; +} + +// #i80314# +// add 2nd parameter and its handling +const SwTableBox* SwTable::GetTableBox( const OUString& rName, + const bool bPerformValidCheck ) const +{ + const SwTableBox* pBox = nullptr; + const SwTableLine* pLine; + const SwTableLines* pLines; + + sal_uInt16 nLine, nBox; + OUString aNm( rName ); + while( !aNm.isEmpty() ) + { + nBox = SwTable::GetBoxNum( aNm, nullptr == pBox, bPerformValidCheck ); + // first box ? + if( !pBox ) + pLines = &GetTabLines(); + else + { + pLines = &pBox->GetTabLines(); + if( nBox ) + --nBox; + } + + nLine = SwTable::GetBoxNum( aNm, false, bPerformValidCheck ); + + // determine line + if( !nLine || nLine > pLines->size() ) + return nullptr; + pLine = (*pLines)[ nLine-1 ]; + + // determine box + const SwTableBoxes* pBoxes = &pLine->GetTabBoxes(); + if( nBox >= pBoxes->size() ) + return nullptr; + pBox = (*pBoxes)[ nBox ]; + } + + // check if the box found has any contents + if( pBox && !pBox->GetSttNd() ) + { + OSL_FAIL( "Box without content, looking for the next one!" ); + // "drop this" until the first box + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + } + return pBox; +} + +SwTableBox* SwTable::GetTableBox( sal_uLong nSttIdx ) +{ + // For optimizations, don't always process the entire SortArray. + // Converting text to table, tries certain conditions + // to ask for a table box of a table that is not yet having a format + if(!GetFrameFormat()) + return nullptr; + SwTableBox* pRet = nullptr; + SwNodes& rNds = GetFrameFormat()->GetDoc()->GetNodes(); + sal_uLong nIndex = nSttIdx + 1; + SwContentNode* pCNd = nullptr; + SwTableNode* pTableNd = nullptr; + + while ( nIndex < rNds.Count() ) + { + pTableNd = rNds[ nIndex ]->GetTableNode(); + if ( pTableNd ) + break; + + pCNd = rNds[ nIndex ]->GetContentNode(); + if ( pCNd ) + break; + + ++nIndex; + } + + if ( pCNd || pTableNd ) + { + SwModify* pModify = pCNd; + // #144862# Better handling of table in table + if ( pTableNd && pTableNd->GetTable().GetFrameFormat() ) + pModify = pTableNd->GetTable().GetFrameFormat(); + + SwFrame* pFrame = pModify ? SwIterator<SwFrame,SwModify>(*pModify).First() : nullptr; + while ( pFrame && !pFrame->IsCellFrame() ) + pFrame = pFrame->GetUpper(); + if ( pFrame ) + pRet = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + } + + // In case the layout doesn't exist yet or anything else goes wrong. + if ( !pRet ) + { + for (size_t n = m_TabSortContentBoxes.size(); n; ) + { + if (m_TabSortContentBoxes[ --n ]->GetSttIdx() == nSttIdx) + { + return m_TabSortContentBoxes[ n ]; + } + } + } + return pRet; +} + +bool SwTable::IsTableComplex() const +{ + // Returns true for complex tables, i.e. tables that contain nestings, + // like containing boxes not part of the first line, e.g. results of + // splits/merges which lead to more complex structures. + for (size_t n = 0; n < m_TabSortContentBoxes.size(); ++n) + { + if (m_TabSortContentBoxes[ n ]->GetUpper()->GetUpper()) + { + return true; + } + } + return false; +} + +SwTableLine::SwTableLine( SwTableLineFormat *pFormat, sal_uInt16 nBoxes, + SwTableBox *pUp ) + : SwClient( pFormat ), + m_aBoxes(), + m_pUpper( pUp ) +{ + m_aBoxes.reserve( nBoxes ); +} + +SwTableLine::~SwTableLine() +{ + for (size_t i = 0; i < m_aBoxes.size(); ++i) + { + delete m_aBoxes[i]; + } + // the TabelleLine can be deleted if it's the last client of the FrameFormat + SwModify* pMod = GetFrameFormat(); + pMod->Remove( this ); // remove, + if( !pMod->HasWriterListeners() ) + delete pMod; // and delete +} + +SwFrameFormat* SwTableLine::ClaimFrameFormat() +{ + // This method makes sure that this object is an exclusive SwTableLine client + // of an SwTableLineFormat object + // If other SwTableLine objects currently listen to the same SwTableLineFormat as + // this one, something needs to be done + SwTableLineFormat *pRet = static_cast<SwTableLineFormat*>(GetFrameFormat()); + SwIterator<SwTableLine,SwFormat> aIter( *pRet ); + for( SwTableLine* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if ( pLast != this ) + { + // found another SwTableLine that is a client of the current Format + // create a new Format as a copy and use it for this object + SwTableLineFormat *pNewFormat = pRet->GetDoc()->MakeTableLineFormat(); + *pNewFormat = *pRet; + + // register SwRowFrames that know me as clients at the new Format + SwIterator<SwRowFrame,SwFormat> aFrameIter( *pRet ); + for( SwRowFrame* pFrame = aFrameIter.First(); pFrame; pFrame = aFrameIter.Next() ) + if( pFrame->GetTabLine() == this ) + pFrame->RegisterToFormat( *pNewFormat ); + + // register myself + pNewFormat->Add( this ); + pRet = pNewFormat; + break; + } + } + + return pRet; +} + +void SwTableLine::ChgFrameFormat( SwTableLineFormat *pNewFormat ) +{ + SwFrameFormat *pOld = GetFrameFormat(); + SwIterator<SwRowFrame,SwFormat> aIter( *pOld ); + + // First, re-register the Frames. + for( SwRowFrame* pRow = aIter.First(); pRow; pRow = aIter.Next() ) + { + if( pRow->GetTabLine() == this ) + { + pRow->RegisterToFormat( *pNewFormat ); + + pRow->InvalidateSize(); + pRow->InvalidatePrt_(); + pRow->SetCompletePaint(); + pRow->ReinitializeFrameSizeAttrFlags(); + + // #i35063# + // consider 'split row allowed' attribute + SwTabFrame* pTab = pRow->FindTabFrame(); + bool bInFollowFlowRow = false; + const bool bInFirstNonHeadlineRow = pTab->IsFollow() && + pRow == pTab->GetFirstNonHeadlineRow(); + if ( bInFirstNonHeadlineRow || + !pRow->GetNext() || + ( bInFollowFlowRow = pRow->IsInFollowFlowRow() ) || + nullptr != pRow->IsInSplitTableRow() ) + { + if ( bInFirstNonHeadlineRow || bInFollowFlowRow ) + pTab = pTab->FindMaster(); + + pTab->SetRemoveFollowFlowLinePending( true ); + pTab->InvalidatePos(); + } + } + } + + // Now, re-register self. + pNewFormat->Add( this ); + + if ( !pOld->HasWriterListeners() ) + delete pOld; +} + +SwTwips SwTableLine::GetTableLineHeight( bool& bLayoutAvailable ) const +{ + SwTwips nRet = 0; + bLayoutAvailable = false; + SwIterator<SwRowFrame,SwFormat> aIter( *GetFrameFormat() ); + // A row could appear several times in headers/footers so only one chain of master/follow tables + // will be accepted... + const SwTabFrame* pChain = nullptr; // My chain + for( SwRowFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->GetTabLine() == this ) + { + const SwTabFrame* pTab = pLast->FindTabFrame(); + bLayoutAvailable = ( pTab && pTab->IsVertical() ) ? + ( 0 < pTab->getFrameArea().Height() ) : + ( 0 < pTab->getFrameArea().Width() ); + + // The first one defines the chain, if a chain is defined, only members of the chain + // will be added. + if (pTab && (!pChain || pChain->IsAnFollow( pTab ) || pTab->IsAnFollow(pChain))) + { + pChain = pTab; // defines my chain (even it is already) + if( pTab->IsVertical() ) + nRet += pLast->getFrameArea().Width(); + else + nRet += pLast->getFrameArea().Height(); + // Optimization, if there are no master/follows in my chain, nothing more to add + if( !pTab->HasFollow() && !pTab->IsFollow() ) + break; + // This is not an optimization, this is necessary to avoid double additions of + // repeating rows + if( pTab->IsInHeadline(*pLast) ) + break; + } + } + } + return nRet; +} + +SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, sal_uInt16 nLines, SwTableLine *pUp ) + : SwClient(nullptr) + , m_aLines() + , m_pStartNode(nullptr) + , m_pUpper(pUp) + , mnRowSpan(1) + , mbDummyFlag(false) + , mbDirectFormatting(false) +{ + m_aLines.reserve( nLines ); + CheckBoxFormat( pFormat )->Add( this ); +} + +SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, const SwNodeIndex &rIdx, + SwTableLine *pUp ) + : SwClient(nullptr) + , m_aLines() + , m_pUpper(pUp) + , mnRowSpan(1) + , mbDummyFlag(false) + , mbDirectFormatting(false) +{ + CheckBoxFormat( pFormat )->Add( this ); + + m_pStartNode = rIdx.GetNode().GetStartNode(); + + // insert into the table + const SwTableNode* pTableNd = m_pStartNode->FindTableNode(); + assert(pTableNd && "In which table is that box?"); + SwTableSortBoxes& rSrtArr = const_cast<SwTableSortBoxes&>(pTableNd->GetTable(). + GetTabSortBoxes()); + SwTableBox* p = this; // error: &this + rSrtArr.insert( p ); // insert +} + +SwTableBox::SwTableBox( SwTableBoxFormat* pFormat, const SwStartNode& rSttNd, SwTableLine *pUp ) + : SwClient(nullptr) + , m_aLines() + , m_pStartNode(&rSttNd) + , m_pUpper(pUp) + , mnRowSpan(1) + , mbDummyFlag(false) + , mbDirectFormatting(false) +{ + CheckBoxFormat( pFormat )->Add( this ); + + // insert into the table + const SwTableNode* pTableNd = m_pStartNode->FindTableNode(); + OSL_ENSURE( pTableNd, "In which table is the box?" ); + SwTableSortBoxes& rSrtArr = const_cast<SwTableSortBoxes&>(pTableNd->GetTable(). + GetTabSortBoxes()); + SwTableBox* p = this; // error: &this + rSrtArr.insert( p ); // insert +} + +void SwTableBox::RemoveFromTable() +{ + if (m_pStartNode) // box containing contents? + { + // remove from table + const SwTableNode* pTableNd = m_pStartNode->FindTableNode(); + assert(pTableNd && "In which table is that box?"); + SwTableSortBoxes& rSrtArr = const_cast<SwTableSortBoxes&>(pTableNd->GetTable(). + GetTabSortBoxes()); + SwTableBox *p = this; // error: &this + rSrtArr.erase( p ); // remove + m_pStartNode = nullptr; // clear it so this is only run once + } +} + +SwTableBox::~SwTableBox() +{ + if (!GetFrameFormat()->GetDoc()->IsInDtor()) + { + RemoveFromTable(); + } + + // the TabelleBox can be deleted if it's the last client of the FrameFormat + SwModify* pMod = GetFrameFormat(); + pMod->Remove( this ); // remove, + if( !pMod->HasWriterListeners() ) + delete pMod; // and delete +} + +SwTableBoxFormat* SwTableBox::CheckBoxFormat( SwTableBoxFormat* pFormat ) +{ + // We might need to create a new format here, because the box must be + // added to the format solely if pFormat has a value or form. + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE, false ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA, false ) ) + { + SwTableBox* pOther = SwIterator<SwTableBox,SwFormat>( *pFormat ).First(); + if( pOther ) + { + SwTableBoxFormat* pNewFormat = pFormat->GetDoc()->MakeTableBoxFormat(); + pNewFormat->LockModify(); + *pNewFormat = *pFormat; + + // Remove values and formulas + pNewFormat->ResetFormatAttr( RES_BOXATR_FORMULA, RES_BOXATR_VALUE ); + pNewFormat->UnlockModify(); + + pFormat = pNewFormat; + } + } + return pFormat; +} + +SwFrameFormat* SwTableBox::ClaimFrameFormat() +{ + // This method makes sure that this object is an exclusive SwTableBox client + // of an SwTableBoxFormat object + // If other SwTableBox objects currently listen to the same SwTableBoxFormat as + // this one, something needs to be done + SwTableBoxFormat *pRet = static_cast<SwTableBoxFormat*>(GetFrameFormat()); + SwIterator<SwTableBox,SwFormat> aIter( *pRet ); + for( SwTableBox* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if ( pLast != this ) + { + // Found another SwTableBox object + // create a new Format as a copy and assign me to it + // don't copy values and formulas + SwTableBoxFormat* pNewFormat = pRet->GetDoc()->MakeTableBoxFormat(); + pNewFormat->LockModify(); + *pNewFormat = *pRet; + pNewFormat->ResetFormatAttr( RES_BOXATR_FORMULA, RES_BOXATR_VALUE ); + pNewFormat->UnlockModify(); + + // re-register SwCellFrame objects that know me + SwIterator<SwCellFrame,SwFormat> aFrameIter( *pRet ); + for( SwCellFrame* pCell = aFrameIter.First(); pCell; pCell = aFrameIter.Next() ) + if( pCell->GetTabBox() == this ) + pCell->RegisterToFormat( *pNewFormat ); + + // re-register myself + pNewFormat->Add( this ); + pRet = pNewFormat; + break; + } + } + return pRet; +} + +void SwTableBox::ChgFrameFormat( SwTableBoxFormat* pNewFormat, bool bNeedToReregister ) +{ + SwFrameFormat *pOld = GetFrameFormat(); + SwIterator<SwCellFrame,SwFormat> aIter( *pOld ); + + // tdf#84635 We set bNeedToReregister=false to avoid a quadratic slowdown on loading large tables, + // and since we are creating the table for the first time, no re-registration is necessary. + + // First, re-register the Frames. + if (bNeedToReregister) + for( SwCellFrame* pCell = aIter.First(); pCell; pCell = aIter.Next() ) + { + if( pCell->GetTabBox() == this ) + { + pCell->RegisterToFormat( *pNewFormat ); + pCell->InvalidateSize(); + pCell->InvalidatePrt_(); + pCell->SetCompletePaint(); + pCell->SetDerivedVert( false ); + pCell->CheckDirChange(); + + // #i47489# + // make sure that the row will be formatted, in order + // to have the correct Get(Top|Bottom)MarginForLowers values + // set at the row. + const SwTabFrame* pTab = pCell->FindTabFrame(); + if ( pTab && pTab->IsCollapsingBorders() ) + { + SwFrame* pRow = pCell->GetUpper(); + pRow->InvalidateSize_(); + pRow->InvalidatePrt_(); + } + } + } + + // Now, re-register self. + pNewFormat->Add( this ); + + if( !pOld->HasWriterListeners() ) + delete pOld; +} + +// Return the name of this box. This is determined dynamically +// resulting from the position in the lines/boxes/tables. +void sw_GetTableBoxColStr( sal_uInt16 nCol, OUString& rNm ) +{ + const sal_uInt16 coDiff = 52; // 'A'-'Z' 'a' - 'z' + + do { + const sal_uInt16 nCalc = nCol % coDiff; + if( nCalc >= 26 ) + rNm = OUStringChar( sal_Unicode('a' - 26 + nCalc) ) + rNm; + else + rNm = OUStringChar( sal_Unicode('A' + nCalc) ) + rNm; + + if( 0 == (nCol = nCol - nCalc) ) + break; + nCol /= coDiff; + --nCol; + } while( true ); +} + +Point SwTableBox::GetCoordinates() const +{ + if( !m_pStartNode ) // box without content? + { + // search for the next first box? + return Point( 0, 0 ); + } + + const SwTable& rTable = m_pStartNode->FindTableNode()->GetTable(); + sal_uInt16 nX, nY; + const SwTableBox* pBox = this; + do { + const SwTableLine* pLine = pBox->GetUpper(); + // at the first level? + const SwTableLines* pLines = pLine->GetUpper() + ? &pLine->GetUpper()->GetTabLines() : &rTable.GetTabLines(); + + nY = pLines->GetPos( pLine ) + 1 ; + nX = pBox->GetUpper()->GetBoxPos( pBox ) + 1; + pBox = pLine->GetUpper(); + } while( pBox ); + return Point( nX, nY ); +} + +OUString SwTableBox::GetName() const +{ + if( !m_pStartNode ) // box without content? + { + // search for the next first box? + return OUString(); + } + + const SwTable& rTable = m_pStartNode->FindTableNode()->GetTable(); + sal_uInt16 nPos; + OUString sNm, sTmp; + const SwTableBox* pBox = this; + do { + const SwTableLine* pLine = pBox->GetUpper(); + // at the first level? + const SwTableLines* pLines = pLine->GetUpper() + ? &pLine->GetUpper()->GetTabLines() : &rTable.GetTabLines(); + + nPos = pLines->GetPos( pLine ) + 1; + sTmp = OUString::number( nPos ); + if( !sNm.isEmpty() ) + sNm = sTmp + "." + sNm; + else + sNm = sTmp; + + nPos = pBox->GetUpper()->GetBoxPos( pBox ); + sTmp = OUString::number(nPos + 1); + if( nullptr != ( pBox = pLine->GetUpper()) ) + sNm = sTmp + "." + sNm; + else + sw_GetTableBoxColStr( nPos, sNm ); + + } while( pBox ); + return sNm; +} + +bool SwTableBox::IsInHeadline( const SwTable* pTable ) const +{ + if( !GetUpper() ) // should only happen upon merge. + return false; + + if( !pTable ) + pTable = &m_pStartNode->FindTableNode()->GetTable(); + + const SwTableLine* pLine = GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // Headerline? + return pTable->GetTabLines()[ 0 ] == pLine; +} + +sal_uLong SwTableBox::GetSttIdx() const +{ + return m_pStartNode ? m_pStartNode->GetIndex() : 0; +} + + // retrieve information from the client +bool SwTable::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_AUTOFMT_DOCNODE: + { + const SwTableNode* pNode = GetTableNode(); + if (pNode && &pNode->GetNodes() == static_cast<SwAutoFormatGetDocNode&>(rInfo).pNodes) + { + if (!m_TabSortContentBoxes.empty()) + { + SwNodeIndex aIdx( *m_TabSortContentBoxes[0]->GetSttNd() ); + GetFrameFormat()->GetDoc()->GetNodes().GoNext( &aIdx ); + } + return false; + } + break; + } + case RES_FINDNEARESTNODE: + if( GetFrameFormat() && + GetFrameFormat()->GetFormatAttr( RES_PAGEDESC ).GetPageDesc() && + !m_TabSortContentBoxes.empty() && + m_TabSortContentBoxes[0]->GetSttNd()->GetNodes().IsDocNodes() ) + static_cast<SwFindNearestNode&>(rInfo).CheckNode( * + m_TabSortContentBoxes[0]->GetSttNd()->FindTableNode() ); + break; + + case RES_CONTENT_VISIBLE: + static_cast<SwPtrMsgPoolItem&>(rInfo).pObject = SwIterator<SwFrame,SwFormat>( *GetFrameFormat() ).First(); + return false; + } + return true; +} + +SwTable * SwTable::FindTable( SwFrameFormat const*const pFormat ) +{ + return pFormat + ? SwIterator<SwTable,SwFormat>(*pFormat).First() + : nullptr; +} + +SwTableNode* SwTable::GetTableNode() const +{ + return !GetTabSortBoxes().empty() ? + const_cast<SwTableNode*>(GetTabSortBoxes()[ 0 ]->GetSttNd()->FindTableNode()) : + m_pTableNode; +} + +void SwTable::SetRefObject( SwServerObject* pObj ) +{ + if( m_xRefObj.is() ) + m_xRefObj->Closed(); + + m_xRefObj = pObj; +} + +void SwTable::SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout> const& r) +{ + m_xHTMLLayout = r; +} + +static void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, + bool bChgAlign ) +{ + sal_uLong nNdPos = rBox.IsValidNumTextNd(); + ChgTextToNum( rBox,rText,pCol,bChgAlign,nNdPos); +} +void ChgTextToNum( SwTableBox& rBox, const OUString& rText, const Color* pCol, + bool bChgAlign,sal_uLong nNdPos ) +{ + + if( ULONG_MAX == nNdPos ) + return; + + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + SwTextNode* pTNd = pDoc->GetNodes()[ nNdPos ]->GetTextNode(); + const SfxPoolItem* pItem; + + // assign adjustment + if( bChgAlign ) + { + pItem = &pTNd->SwContentNode::GetAttr( RES_PARATR_ADJUST ); + SvxAdjust eAdjust = static_cast<const SvxAdjustItem*>(pItem)->GetAdjust(); + if( SvxAdjust::Left == eAdjust || SvxAdjust::Block == eAdjust ) + { + SvxAdjustItem aAdjust( *static_cast<const SvxAdjustItem*>(pItem) ); + aAdjust.SetAdjust( SvxAdjust::Right ); + pTNd->SetAttr( aAdjust ); + } + } + + // assign color or save "user color" + if( !pTNd->GetpSwAttrSet() || SfxItemState::SET != pTNd->GetpSwAttrSet()-> + GetItemState( RES_CHRATR_COLOR, false, &pItem )) + pItem = nullptr; + + const Color* pOldNumFormatColor = rBox.GetSaveNumFormatColor(); + const Color* pNewUserColor = pItem ? &static_cast<const SvxColorItem*>(pItem)->GetValue() : nullptr; + + if( ( pNewUserColor && pOldNumFormatColor && + *pNewUserColor == *pOldNumFormatColor ) || + ( !pNewUserColor && !pOldNumFormatColor )) + { + // Keep the user color, set updated values, delete old NumFormatColor if needed + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + else if( pItem ) + { + pNewUserColor = rBox.GetSaveUserColor(); + if( pNewUserColor ) + pTNd->SetAttr( SvxColorItem( *pNewUserColor, RES_CHRATR_COLOR )); + else + pTNd->ResetAttr( RES_CHRATR_COLOR ); + } + } + else + { + // Save user color, set NumFormat color if needed, but never reset the color + rBox.SetSaveUserColor( pNewUserColor ); + + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + + } + rBox.SetSaveNumFormatColor( pCol ); + + if( pTNd->GetText() != rText ) + { + // Exchange text. Bugfix to keep Tabs (front and back!) and annotations (inword comment anchors) + const OUString& rOrig = pTNd->GetText(); + sal_Int32 n; + + for( n = 0; n < rOrig.getLength() && ('\x9' == rOrig[n] || CH_TXTATR_INWORD == rOrig[n]); ++n ) + ; + for( ; n < rOrig.getLength() && '\x01' == rOrig[n]; ++n ) + ; + SwIndex aIdx( pTNd, n ); + for( n = rOrig.getLength(); n && ('\x9' == rOrig[--n] || CH_TXTATR_INWORD == rOrig[n]); ) + ; + sal_Int32 nEndPos = n; + n -= aIdx.GetIndex() - 1; + + // Reset DontExpand-Flags before exchange, to retrigger expansion + { + SwIndex aResetIdx( aIdx, n ); + pTNd->DontExpandFormat( aResetIdx, false, false ); + } + + if( !pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aTemp(*pTNd, 0, *pTNd, rOrig.getLength()); + pDoc->getIDocumentRedlineAccess().DeleteRedline(aTemp, true, RedlineType::Any); + } + + // preserve comments inside of the number by deleting number portions starting from the back + sal_Int32 nCommentPos = pTNd->GetText().lastIndexOf( CH_TXTATR_INWORD, nEndPos ); + while( nCommentPos > aIdx.GetIndex() ) + { + pTNd->EraseText( SwIndex(pTNd, nCommentPos+1), nEndPos - nCommentPos, SwInsertFlags::EMPTYEXPAND ); + // find the next non-sequential comment anchor + do + { + nEndPos = nCommentPos; + n = nEndPos - aIdx.GetIndex(); + nCommentPos = pTNd->GetText().lastIndexOf( CH_TXTATR_INWORD, nEndPos ); + --nEndPos; + } + while( nCommentPos > aIdx.GetIndex() && nCommentPos == nEndPos ); + } + + pTNd->EraseText( aIdx, n, SwInsertFlags::EMPTYEXPAND ); + pTNd->InsertText( rText, aIdx, SwInsertFlags::EMPTYEXPAND ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPaM aTemp(*pTNd, 0, *pTNd, rText.getLength()); + pDoc->getIDocumentRedlineAccess().AppendRedline(new SwRangeRedline(RedlineType::Insert, aTemp), true); + } + } + + // assign vertical orientation + if( bChgAlign && + ( SfxItemState::SET != rBox.GetFrameFormat()->GetItemState( + RES_VERT_ORIENT, true, &pItem ) || + text::VertOrientation::TOP == static_cast<const SwFormatVertOrient*>(pItem)->GetVertOrient() )) + { + rBox.GetFrameFormat()->SetFormatAttr( SwFormatVertOrient( 0, text::VertOrientation::BOTTOM )); + } + +} + +static void ChgNumToText( SwTableBox& rBox, sal_uLong nFormat ) +{ + sal_uLong nNdPos = rBox.IsValidNumTextNd( false ); + if( ULONG_MAX == nNdPos ) + return; + + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + SwTextNode* pTNd = pDoc->GetNodes()[ nNdPos ]->GetTextNode(); + bool bChgAlign = pDoc->IsInsTableAlignNum(); + const SfxPoolItem* pItem; + + Color* pCol = nullptr; + if( getSwDefaultTextFormat() != nFormat ) + { + // special text format: + OUString sTmp; + const OUString sText( pTNd->GetText() ); + pDoc->GetNumberFormatter()->GetOutputString( sText, nFormat, sTmp, &pCol ); + if( sText != sTmp ) + { + // exchange text + SwIndex aIdx( pTNd, sText.getLength() ); + // Reset DontExpand-Flags before exchange, to retrigger expansion + pTNd->DontExpandFormat( aIdx, false, false ); + aIdx = 0; + pTNd->EraseText( aIdx, SAL_MAX_INT32, SwInsertFlags::EMPTYEXPAND ); + pTNd->InsertText( sTmp, aIdx, SwInsertFlags::EMPTYEXPAND ); + } + } + + const SfxItemSet* pAttrSet = pTNd->GetpSwAttrSet(); + + // assign adjustment + if( bChgAlign && pAttrSet && SfxItemState::SET == pAttrSet->GetItemState( + RES_PARATR_ADJUST, false, &pItem ) && + SvxAdjust::Right == static_cast<const SvxAdjustItem*>(pItem)->GetAdjust() ) + { + pTNd->SetAttr( SvxAdjustItem( SvxAdjust::Left, RES_PARATR_ADJUST ) ); + } + + // assign color or save "user color" + if( !pAttrSet || SfxItemState::SET != pAttrSet-> + GetItemState( RES_CHRATR_COLOR, false, &pItem )) + pItem = nullptr; + + const Color* pOldNumFormatColor = rBox.GetSaveNumFormatColor(); + const Color* pNewUserColor = pItem ? &static_cast<const SvxColorItem*>(pItem)->GetValue() : nullptr; + + if( ( pNewUserColor && pOldNumFormatColor && + *pNewUserColor == *pOldNumFormatColor ) || + ( !pNewUserColor && !pOldNumFormatColor )) + { + // Keep the user color, set updated values, delete old NumFormatColor if needed + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + else if( pItem ) + { + pNewUserColor = rBox.GetSaveUserColor(); + if( pNewUserColor ) + pTNd->SetAttr( SvxColorItem( *pNewUserColor, RES_CHRATR_COLOR )); + else + pTNd->ResetAttr( RES_CHRATR_COLOR ); + } + } + else + { + // Save user color, set NumFormat color if needed, but never reset the color + rBox.SetSaveUserColor( pNewUserColor ); + + if( pCol ) + // if needed, set the color + pTNd->SetAttr( SvxColorItem( *pCol, RES_CHRATR_COLOR )); + + } + rBox.SetSaveNumFormatColor( pCol ); + + // assign vertical orientation + if( bChgAlign && + SfxItemState::SET == rBox.GetFrameFormat()->GetItemState( + RES_VERT_ORIENT, false, &pItem ) && + text::VertOrientation::BOTTOM == static_cast<const SwFormatVertOrient*>(pItem)->GetVertOrient() ) + { + rBox.GetFrameFormat()->SetFormatAttr( SwFormatVertOrient( 0, text::VertOrientation::TOP )); + } + +} + +// for detection of modifications (mainly TableBoxAttribute) +void SwTableBoxFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( !IsModifyLocked() && GetDoc() && !GetDoc()->IsInDtor()) + { + const SwTableBoxNumFormat *pNewFormat = nullptr; + const SwTableBoxFormula *pNewFormula = nullptr; + const SwTableBoxValue *pNewVal = nullptr; + sal_uLong nOldFormat = getSwDefaultTextFormat(); + + switch( pNew ? pNew->Which() : 0 ) + { + case RES_ATTRSET_CHG: + { + const SfxItemSet& rSet = *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet(); + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMAT, + false, reinterpret_cast<const SfxPoolItem**>(&pNewFormat) ) ) + nOldFormat = static_cast<const SwAttrSetChg*>(pOld)-> + GetChgSet()->Get( RES_BOXATR_FORMAT ).GetValue(); + rSet.GetItemState( RES_BOXATR_FORMULA, false, + reinterpret_cast<const SfxPoolItem**>(&pNewFormula) ); + rSet.GetItemState( RES_BOXATR_VALUE, false, + reinterpret_cast<const SfxPoolItem**>(&pNewVal) ); + break; + } + case RES_BOXATR_FORMAT: + pNewFormat = static_cast<const SwTableBoxNumFormat*>(pNew); + nOldFormat = static_cast<const SwTableBoxNumFormat*>(pOld)->GetValue(); + break; + case RES_BOXATR_FORMULA: + pNewFormula = static_cast<const SwTableBoxFormula*>(pNew); + break; + case RES_BOXATR_VALUE: + pNewVal = static_cast<const SwTableBoxValue*>(pNew); + break; + } + + // something changed and some BoxAttribut remained in the set! + if( pNewFormat || pNewFormula || pNewVal ) + { + GetDoc()->getIDocumentFieldsAccess().SetFieldsDirty(true, nullptr, 0); + + if( SfxItemState::SET == GetItemState( RES_BOXATR_FORMAT, false ) || + SfxItemState::SET == GetItemState( RES_BOXATR_VALUE, false ) || + SfxItemState::SET == GetItemState( RES_BOXATR_FORMULA, false ) ) + { + // fetch the box + SwIterator<SwTableBox,SwFormat> aIter( *this ); + SwTableBox* pBox = aIter.First(); + if( pBox ) + { + OSL_ENSURE( !aIter.Next(), "zero or more than one box at format" ); + + sal_uLong nNewFormat; + if( pNewFormat ) + { + nNewFormat = pNewFormat->GetValue(); + // new formatting + // is it newer or has the current been removed? + if( SfxItemState::SET != GetItemState( RES_BOXATR_VALUE, false )) + pNewFormat = nullptr; + } + else + { + // fetch the current Item + (void)GetItemState(RES_BOXATR_FORMAT, false, reinterpret_cast<const SfxPoolItem**>(&pNewFormat)); + nOldFormat = GetTableBoxNumFormat().GetValue(); + nNewFormat = pNewFormat ? pNewFormat->GetValue() : nOldFormat; + } + + // is it newer or has the current been removed? + if( pNewVal ) + { + if( GetDoc()->GetNumberFormatter()->IsTextFormat(nNewFormat) ) + nOldFormat = 0; + else + { + if( SfxItemState::SET == GetItemState( RES_BOXATR_VALUE, false )) + nOldFormat = getSwDefaultTextFormat(); + else + nNewFormat = getSwDefaultTextFormat(); + } + } + + // Logic: + // Value change: -> "simulate" a format change! + // Format change: + // Text -> !Text or format change: + // - align right for horizontal alignment, if LEFT or JUSTIFIED + // - align bottom for vertical alignment, if TOP is set, or default + // - replace text (color? negative numbers RED?) + // !Text -> Text: + // - align left for horizontal alignment, if RIGHT + // - align top for vertical alignment, if BOTTOM is set + SvNumberFormatter* pNumFormatr = GetDoc()->GetNumberFormatter(); + bool bNewIsTextFormat = pNumFormatr->IsTextFormat( nNewFormat ); + + if( (!bNewIsTextFormat && nOldFormat != nNewFormat) || pNewFormula ) + { + bool bIsNumFormat = false; + OUString aOrigText; + bool bChgText = true; + double fVal = 0; + if( !pNewVal && SfxItemState::SET != GetItemState( + RES_BOXATR_VALUE, false, reinterpret_cast<const SfxPoolItem**>(&pNewVal) )) + { + // so far, no value has been set, so try to evaluate the content + sal_uLong nNdPos = pBox->IsValidNumTextNd(); + if( ULONG_MAX != nNdPos ) + { + sal_uInt32 nTmpFormatIdx = nNewFormat; + OUString aText( GetDoc()->GetNodes()[ nNdPos ] + ->GetTextNode()->GetRedlineText()); + aOrigText = aText; + if( aText.isEmpty() ) + bChgText = false; + else + { + // Keep Tabs + lcl_TabToBlankAtSttEnd( aText ); + + // JP 22.04.98: Bug 49659 - + // Special casing for percent + if( SvNumFormatType::PERCENT == + pNumFormatr->GetType( nNewFormat )) + { + sal_uInt32 nTmpFormat = 0; + if( GetDoc()->IsNumberFormat( + aText, nTmpFormat, fVal )) + { + if( SvNumFormatType::NUMBER == + pNumFormatr->GetType( nTmpFormat )) + aText += "%"; + + bIsNumFormat = GetDoc()->IsNumberFormat( + aText, nTmpFormatIdx, fVal ); + } + } + else + bIsNumFormat = GetDoc()->IsNumberFormat( + aText, nTmpFormatIdx, fVal ); + + if( bIsNumFormat ) + { + // directly assign value - without Modify + bool bIsLockMod = IsModifyLocked(); + LockModify(); + SetFormatAttr( SwTableBoxValue( fVal )); + if( !bIsLockMod ) + UnlockModify(); + } + } + } + } + else + { + fVal = pNewVal->GetValue(); + bIsNumFormat = true; + } + + // format contents with the new value assigned and write to paragraph + Color* pCol = nullptr; + OUString sNewText; + if( DBL_MAX == fVal ) + { + sNewText = SwViewShell::GetShellRes()->aCalc_Error; + } + else + { + if (bIsNumFormat) + pNumFormatr->GetOutputString( fVal, nNewFormat, sNewText, &pCol ); + else + { + // Original text could not be parsed as + // number/date/time/..., so keep the text. +#if 0 + // Actually the text should be formatted + // according to the format, which may include + // additional text from the format, for example + // in {0;-0;"BAD: "@}. But other places when + // entering a new value or changing text or + // changing to a different format of type Text + // don't do this (yet?). + pNumFormatr->GetOutputString( aOrigText, nNewFormat, sNewText, &pCol ); +#else + sNewText = aOrigText; +#endif + } + + if( !bChgText ) + { + sNewText.clear(); + } + } + + // across all boxes + ChgTextToNum( *pBox, sNewText, pCol, + GetDoc()->IsInsTableAlignNum() ); + + } + else if( bNewIsTextFormat && nOldFormat != nNewFormat ) + { + ChgNumToText( *pBox, nNewFormat ); + } + } + } + } + } + // call base class + SwFrameFormat::Modify( pOld, pNew ); +} + +bool SwTableBoxFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +bool SwTableFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +bool SwTableLineFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +bool SwTableBox::HasNumContent( double& rNum, sal_uInt32& rFormatIndex, + bool& rIsEmptyTextNd ) const +{ + bool bRet = false; + sal_uLong nNdPos = IsValidNumTextNd(); + if( ULONG_MAX != nNdPos ) + { + OUString aText( m_pStartNode->GetNodes()[ nNdPos ]->GetTextNode()->GetRedlineText() ); + // Keep Tabs + lcl_TabToBlankAtSttEnd( aText ); + rIsEmptyTextNd = aText.isEmpty(); + SvNumberFormatter* pNumFormatr = GetFrameFormat()->GetDoc()->GetNumberFormatter(); + + const SfxPoolItem* pItem; + if( SfxItemState::SET == GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT, false, &pItem )) + { + rFormatIndex = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + // Special casing for percent + if( !rIsEmptyTextNd && SvNumFormatType::PERCENT == pNumFormatr->GetType( rFormatIndex )) + { + sal_uInt32 nTmpFormat = 0; + if( GetFrameFormat()->GetDoc()->IsNumberFormat( aText, nTmpFormat, rNum ) && + SvNumFormatType::NUMBER == pNumFormatr->GetType( nTmpFormat )) + aText += "%"; + } + } + else + rFormatIndex = 0; + + bRet = GetFrameFormat()->GetDoc()->IsNumberFormat( aText, rFormatIndex, rNum ); + } + else + rIsEmptyTextNd = false; + return bRet; +} + +bool SwTableBox::IsNumberChanged() const +{ + bool bRet = true; + + if( SfxItemState::SET == GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA, false )) + { + const SwTableBoxNumFormat *pNumFormat; + const SwTableBoxValue *pValue; + + if( SfxItemState::SET != GetFrameFormat()->GetItemState( RES_BOXATR_VALUE, false, + reinterpret_cast<const SfxPoolItem**>(&pValue) )) + pValue = nullptr; + if( SfxItemState::SET != GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT, false, + reinterpret_cast<const SfxPoolItem**>(&pNumFormat) )) + pNumFormat = nullptr; + + sal_uLong nNdPos; + if( pNumFormat && pValue && ULONG_MAX != ( nNdPos = IsValidNumTextNd() ) ) + { + OUString sNewText, sOldText( m_pStartNode->GetNodes()[ nNdPos ]-> + GetTextNode()->GetRedlineText() ); + lcl_DelTabsAtSttEnd( sOldText ); + + Color* pCol = nullptr; + GetFrameFormat()->GetDoc()->GetNumberFormatter()->GetOutputString( + pValue->GetValue(), pNumFormat->GetValue(), sNewText, &pCol ); + + bRet = sNewText != sOldText || + !( ( !pCol && !GetSaveNumFormatColor() ) || + ( pCol && GetSaveNumFormatColor() && + *pCol == *GetSaveNumFormatColor() )); + } + } + return bRet; +} + +sal_uLong SwTableBox::IsValidNumTextNd( bool bCheckAttr ) const +{ + sal_uLong nPos = ULONG_MAX; + if( m_pStartNode ) + { + SwNodeIndex aIdx( *m_pStartNode ); + sal_uLong nIndex = aIdx.GetIndex(); + const sal_uLong nIndexEnd = m_pStartNode->GetNodes()[ nIndex ]->EndOfSectionIndex(); + const SwTextNode *pTextNode = nullptr; + while( ++nIndex < nIndexEnd ) + { + const SwNode* pNode = m_pStartNode->GetNodes()[nIndex]; + if( pNode->IsTableNode() ) + { + pTextNode = nullptr; + break; + } + if( pNode->IsTextNode() ) + { + if( pTextNode ) + { + pTextNode = nullptr; + break; + } + else + { + pTextNode = pNode->GetTextNode(); + nPos = nIndex; + } + } + } + if( pTextNode ) + { + if( bCheckAttr ) + { + const SwpHints* pHts = pTextNode->GetpSwpHints(); + // do some tests if there's only text in the node! + // Flys/fields/... + if( pHts ) + { + sal_Int32 nNextSetField = 0; + for( size_t n = 0; n < pHts->Count(); ++n ) + { + const SwTextAttr* pAttr = pHts->Get(n); + if( RES_TXTATR_NOEND_BEGIN <= pAttr->Which() ) + { + if ( (pAttr->GetStart() == nNextSetField) + && (pAttr->Which() == RES_TXTATR_FIELD)) + { + // #i104949# hideous hack for report builder: + // it inserts hidden variable-set fields at + // the beginning of para in cell, but they + // should not turn cell into text cell + const SwField* pField = pAttr->GetFormatField().GetField(); + if (pField && + (pField->GetTypeId() == SwFieldTypesEnum::Set) && + (0 != (static_cast<SwSetExpField const*> + (pField)->GetSubType() & + nsSwExtendedSubType::SUB_INVISIBLE))) + { + nNextSetField = pAttr->GetStart() + 1; + continue; + } + } + else if( RES_TXTATR_ANNOTATION == pAttr->Which() ) + { + continue; + } + nPos = ULONG_MAX; + break; + } + } + } + } + } + else + nPos = ULONG_MAX; + } + return nPos; +} + +// is this a Formula box or one with numeric content (AutoSum) +sal_uInt16 SwTableBox::IsFormulaOrValueBox() const +{ + sal_uInt16 nWhich = 0; + const SwTextNode* pTNd; + SwFrameFormat* pFormat = GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA, false )) + nWhich = RES_BOXATR_FORMULA; + else if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE, false ) && + !pFormat->GetDoc()->GetNumberFormatter()->IsTextFormat( + pFormat->GetTableBoxNumFormat().GetValue() )) + nWhich = RES_BOXATR_VALUE; + else if( m_pStartNode && m_pStartNode->GetIndex() + 2 == m_pStartNode->EndOfSectionIndex() + && nullptr != ( pTNd = m_pStartNode->GetNodes()[ m_pStartNode->GetIndex() + 1 ] + ->GetTextNode() ) && pTNd->GetText().isEmpty()) + nWhich = USHRT_MAX; + + return nWhich; +} + +void SwTableBox::ActualiseValueBox() +{ + const SfxPoolItem *pFormatItem, *pValItem; + SwFrameFormat* pFormat = GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMAT, true, &pFormatItem ) + && SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE, true, &pValItem )) + { + const sal_uLong nFormatId = static_cast<const SwTableBoxNumFormat*>(pFormatItem)->GetValue(); + sal_uLong nNdPos = ULONG_MAX; + SvNumberFormatter* pNumFormatr = pFormat->GetDoc()->GetNumberFormatter(); + + if( !pNumFormatr->IsTextFormat( nFormatId ) && + ULONG_MAX != (nNdPos = IsValidNumTextNd()) ) + { + double fVal = static_cast<const SwTableBoxValue*>(pValItem)->GetValue(); + Color* pCol = nullptr; + OUString sNewText; + pNumFormatr->GetOutputString( fVal, nFormatId, sNewText, &pCol ); + + const OUString& rText = m_pStartNode->GetNodes()[ nNdPos ]->GetTextNode()->GetText(); + if( rText != sNewText ) + ChgTextToNum( *this, sNewText, pCol, false ,nNdPos); + } + } +} + +struct SwTableCellInfo::Impl +{ + const SwTable * m_pTable; + const SwCellFrame * m_pCellFrame; + const SwTabFrame * m_pTabFrame; + typedef o3tl::sorted_vector<const SwTableBox *> TableBoxes_t; + TableBoxes_t m_HandledTableBoxes; + +public: + Impl() + : m_pTable(nullptr), m_pCellFrame(nullptr), m_pTabFrame(nullptr) + { + } + + void setTable(const SwTable * pTable) + { + m_pTable = pTable; + SwFrameFormat * pFrameFormat = m_pTable->GetFrameFormat(); + m_pTabFrame = SwIterator<SwTabFrame,SwFormat>(*pFrameFormat).First(); + if (m_pTabFrame && m_pTabFrame->IsFollow()) + m_pTabFrame = m_pTabFrame->FindMaster(true); + } + + const SwCellFrame * getCellFrame() const { return m_pCellFrame; } + + const SwFrame * getNextFrameInTable(const SwFrame * pFrame); + const SwCellFrame * getNextCellFrame(const SwFrame * pFrame); + const SwCellFrame * getNextTableBoxsCellFrame(const SwFrame * pFrame); + bool getNext(); +}; + +const SwFrame * SwTableCellInfo::Impl::getNextFrameInTable(const SwFrame * pFrame) +{ + const SwFrame * pResult = nullptr; + + if (((! pFrame->IsTabFrame()) || pFrame == m_pTabFrame) && pFrame->GetLower()) + pResult = pFrame->GetLower(); + else if (pFrame->GetNext()) + pResult = pFrame->GetNext(); + else + { + while (pFrame->GetUpper() != nullptr) + { + pFrame = pFrame->GetUpper(); + + if (pFrame->IsTabFrame()) + { + m_pTabFrame = static_cast<const SwTabFrame *>(pFrame)->GetFollow(); + pResult = m_pTabFrame; + break; + } + else if (pFrame->GetNext()) + { + pResult = pFrame->GetNext(); + break; + } + } + } + + return pResult; +} + +const SwCellFrame * SwTableCellInfo::Impl::getNextCellFrame(const SwFrame * pFrame) +{ + const SwCellFrame * pResult = nullptr; + + while ((pFrame = getNextFrameInTable(pFrame)) != nullptr) + { + if (pFrame->IsCellFrame()) + { + pResult = static_cast<const SwCellFrame *>(pFrame); + break; + } + } + + return pResult; +} + +const SwCellFrame * SwTableCellInfo::Impl::getNextTableBoxsCellFrame(const SwFrame * pFrame) +{ + const SwCellFrame * pResult = nullptr; + + while ((pFrame = getNextCellFrame(pFrame)) != nullptr) + { + const SwCellFrame * pCellFrame = static_cast<const SwCellFrame *>(pFrame); + const SwTableBox * pTabBox = pCellFrame->GetTabBox(); + auto aIt = m_HandledTableBoxes.insert(pTabBox); + if (aIt.second) + { + pResult = pCellFrame; + break; + } + } + + return pResult; +} + +const SwCellFrame * SwTableCellInfo::getCellFrame() const +{ + return m_pImpl->getCellFrame(); +} + +bool SwTableCellInfo::Impl::getNext() +{ + if (m_pCellFrame == nullptr) + { + if (m_pTabFrame != nullptr) + m_pCellFrame = Impl::getNextTableBoxsCellFrame(m_pTabFrame); + } + else + m_pCellFrame = Impl::getNextTableBoxsCellFrame(m_pCellFrame); + + return m_pCellFrame != nullptr; +} + +SwTableCellInfo::SwTableCellInfo(const SwTable * pTable) + : m_pImpl(std::make_unique<Impl>()) +{ + m_pImpl->setTable(pTable); +} + +SwTableCellInfo::~SwTableCellInfo() +{ +} + +bool SwTableCellInfo::getNext() +{ + return m_pImpl->getNext(); +} + +SwRect SwTableCellInfo::getRect() const +{ + SwRect aRet; + + if (getCellFrame() != nullptr) + aRet = getCellFrame()->getFrameArea(); + + return aRet; +} + +const SwTableBox * SwTableCellInfo::getTableBox() const +{ + const SwTableBox * pRet = nullptr; + + if (getCellFrame() != nullptr) + pRet = getCellFrame()->GetTabBox(); + + return pRet; +} + +void SwTable::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +bool SwTable::HasLayout() const +{ + const SwFrameFormat* pFrameFormat = GetFrameFormat(); + //a table in a clipboard document doesn't have any layout information + return pFrameFormat && SwIterator<SwTabFrame,SwFormat>(*pFrameFormat).First(); +} + +void SwTableLine::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +void SwTableBox::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +// free's any remaining child objects +SwTableLines::~SwTableLines() +{ + for ( const_iterator it = begin(); it != end(); ++it ) + delete *it; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx new file mode 100644 index 000000000..d570a8015 --- /dev/null +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -0,0 +1,2280 @@ +/* -*- 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 <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <EnhancedPDFExportHelper.hxx> +#include <hintids.hxx> + +#include <sot/exchange.hxx> +#include <vcl/outdev.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <tools/multisel.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/langitem.hxx> +#include <tools/urlobj.hxx> +#include <svl/languageoptions.hxx> +#include <svl/zforlist.hxx> +#include <swatrset.hxx> +#include <frmatr.hxx> +#include <paratr.hxx> +#include <ndtxt.hxx> +#include <ndole.hxx> +#include <section.hxx> +#include <tox.hxx> +#include <fmtfld.hxx> +#include <txtinet.hxx> +#include <fmtinfmt.hxx> +#include <fchrfmt.hxx> +#include <charfmt.hxx> +#include <fmtanchr.hxx> +#include <fmturl.hxx> +#include <editsh.hxx> +#include <viscrs.hxx> +#include <txtfld.hxx> +#include <reffld.hxx> +#include <doc.hxx> +#include <IDocumentOutlineNodes.hxx> +#include <docary.hxx> +#include <mdiexp.hxx> +#include <docufld.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <sectfrm.hxx> +#include <flyfrm.hxx> +#include <notxtfrm.hxx> +#include "porfld.hxx" +#include <SwStyleNameMapper.hxx> +#include "itrpaint.hxx" +#include <i18nlangtag/languagetag.hxx> +#include <IMark.hxx> +#include <printdata.hxx> +#include <SwNodeNum.hxx> +#include <calbck.hxx> +#include <stack> +#include <frmtool.hxx> +#include <strings.hrc> +#include <frameformats.hxx> + +#include <tools/globname.hxx> +#include <svx/svdobj.hxx> + +using namespace ::com::sun::star; + +// Some static data structures + +TableColumnsMap SwEnhancedPDFExportHelper::aTableColumnsMap; +LinkIdMap SwEnhancedPDFExportHelper::aLinkIdMap; +NumListIdMap SwEnhancedPDFExportHelper::aNumListIdMap; +NumListBodyIdMap SwEnhancedPDFExportHelper::aNumListBodyIdMap; +FrameTagIdMap SwEnhancedPDFExportHelper::aFrameTagIdMap; + +LanguageType SwEnhancedPDFExportHelper::eLanguageDefault = LANGUAGE_SYSTEM; + +#if OSL_DEBUG_LEVEL > 1 + +static std::vector< sal_uInt16 > aStructStack; + +void lcl_DBGCheckStack() +{ + /* NonStructElement = 0 Document = 1 Part = 2 + * Article = 3 Section = 4 Division = 5 + * BlockQuote = 6 Caption = 7 TOC = 8 + * TOCI = 9 Index = 10 Paragraph = 11 + * Heading = 12 H1-6 = 13 - 18 List = 19 + * ListItem = 20 LILabel = 21 LIBody = 22 + * Table = 23 TableRow = 24 TableHeader = 25 + * TableData = 26 Span = 27 Quote = 28 + * Note = 29 Reference = 30 BibEntry = 31 + * Code = 32 Link = 33 Figure = 34 + * Formula = 35 Form = 36 Continued frame = 99 + */ + + sal_uInt16 nElement; + for ( const auto& rItem : aStructStack ) + { + nElement = rItem; + } + (void)nElement; +}; + +#endif + +namespace +{ +// ODF Style Names: +const char aTableHeadingName[] = "Table Heading"; +const char aQuotations[] = "Quotations"; +const char aCaption[] = "Caption"; +const char aHeading[] = "Heading"; +const char aQuotation[] = "Quotation"; +const char aSourceText[] = "Source Text"; + +// PDF Tag Names: +const char aDocumentString[] = "Document"; +const char aDivString[] = "Div"; +const char aSectString[] = "Sect"; +const char aHString[] = "H"; +const char aH1String[] = "H1"; +const char aH2String[] = "H2"; +const char aH3String[] = "H3"; +const char aH4String[] = "H4"; +const char aH5String[] = "H5"; +const char aH6String[] = "H6"; +const char aListString[] = "L"; +const char aListItemString[] = "LI"; +const char aListBodyString[] = "LBody"; +const char aBlockQuoteString[] = "BlockQuote"; +const char aCaptionString[] = "Caption"; +const char aIndexString[] = "Index"; +const char aTOCString[] = "TOC"; +const char aTOCIString[] = "TOCI"; +const char aTableString[] = "Table"; +const char aTRString[] = "TR"; +const char aTDString[] = "TD"; +const char aTHString[] = "TH"; +const char aBibEntryString[] = "BibEntry"; +const char aQuoteString[] = "Quote"; +const char aSpanString[] = "Span"; +const char aCodeString[] = "Code"; +const char aFigureString[] = "Figure"; +const char aFormulaString[] = "Formula"; +const char aLinkString[] = "Link"; +const char aNoteString[] = "Note"; + +// returns true if first paragraph in cell frame has 'table heading' style +bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) +{ + bool bRet = false; + + const SwContentFrame *pCnt = rCellFrame.ContainsContent(); + if ( pCnt && pCnt->IsTextFrame() ) + { + SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps(); + const SwFormat* pTextFormat = pTextNode->GetFormatColl(); + + OUString sStyleName; + SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); + bRet = sStyleName == aTableHeadingName; + } + + return bRet; +} + +// List all frames for which the NonStructElement tag is set: +bool lcl_IsInNonStructEnv( const SwFrame& rFrame ) +{ + bool bRet = false; + + if ( nullptr != rFrame.FindFooterOrHeader() && + !rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() ) + { + bRet = true; + } + else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() ) + { + const SwTabFrame* pTabFrame = rFrame.FindTabFrame(); + if ( rFrame.GetUpper() != pTabFrame && + pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) ) + bRet = true; + } + + return bRet; +} + +// Generate key from frame for reopening tags: +void* lcl_GetKeyFromFrame( const SwFrame& rFrame ) +{ + void* pKey = nullptr; + + if ( rFrame.IsPageFrame() ) + pKey = const_cast<void*>(static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()))); + else if ( rFrame.IsTextFrame() ) + pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst())); + else if ( rFrame.IsSctFrame() ) + pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection())); + else if ( rFrame.IsTabFrame() ) + pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable())); + else if ( rFrame.IsRowFrame() ) + pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine())); + else if ( rFrame.IsCellFrame() ) + { + const SwTabFrame* pTabFrame = rFrame.FindTabFrame(); + const SwTable* pTable = pTabFrame->GetTable(); + pKey = const_cast<void*>(static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan( *pTable ))); + } + + return pKey; +} + +bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode) +{ + bool bRet = false; + SwNodeIndex aIdx( rNode ); + const SwDoc* pDoc = rNode.GetDoc(); + const SwNodes& rNodes = pDoc->GetNodes(); + const SwNode* pNode = &rNode; + const SwNumRule* pNumRule = rNode.GetNumRule(); + + while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) ) + { + sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame()); + + if (aIdx.GetNode().IsTextNode()) + { + const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode( + *rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode()); + const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule(); + + // We find the previous text node. Now check, if the previous text node + // has the same numrule like rNode: + if ( (pPrevNumRule == pNumRule) && + (!pPrevTextNd->IsOutline() == !rNode.IsOutline())) + bRet = true; + + break; + } + + pNode = &aIdx.GetNode(); + } + return bRet; +} + +bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, SwFormatField& rField) +{ + // 1. Check if the whole paragraph is hidden + // 2. Move to the field + // 3. Check for hidden text attribute + if(rNd.IsHidden()) + return false; + if(!rShell.GotoFormatField(rField) || rShell.SelectHiddenRange()) + { + rShell.SwCursorShell::ClearMark(); + return false; + } + return true; +}; + +} // end namespace + +SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo, + const Frame_Info* pFrameInfo, + const Por_Info* pPorInfo, + OutputDevice const & rOut ) + : nEndStructureElement( 0 ), + nRestoreCurrentTag( -1 ), + mpNumInfo( pNumInfo ), + mpFrameInfo( pFrameInfo ), + mpPorInfo( pPorInfo ) +{ + mpPDFExtOutDevData = + dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() ); + + if ( mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF() ) + { +#if OSL_DEBUG_LEVEL > 1 + sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); +#endif + if ( mpNumInfo ) + BeginNumberedListStructureElements(); + else if ( mpFrameInfo ) + BeginBlockStructureElements(); + else if ( mpPorInfo ) + BeginInlineStructureElements(); + else + BeginTag( vcl::PDFWriter::NonStructElement, OUString() ); + +#if OSL_DEBUG_LEVEL > 1 + nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); + (void)nCurrentStruct; +#endif + } +} + +SwTaggedPDFHelper::~SwTaggedPDFHelper() +{ + if ( mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF() ) + { +#if OSL_DEBUG_LEVEL > 1 + sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); +#endif + EndStructureElements(); + +#if OSL_DEBUG_LEVEL > 1 + nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); + (void)nCurrentStruct; +#endif + + } +} + +bool SwTaggedPDFHelper::CheckReopenTag() +{ + bool bRet = false; + sal_Int32 nReopenTag = -1; + bool bContinue = false; // in some cases we just have to reopen a tag without early returning + + if ( mpFrameInfo ) + { + const SwFrame& rFrame = mpFrameInfo->mrFrame; + const SwFrame* pKeyFrame = nullptr; + + // Reopen an existing structure element if + // - rFrame is not the first page frame (reopen Document tag) + // - rFrame is a follow frame (reopen Master tag) + // - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag) + // - rFrame is a fly frame anchored at page (reopen Document tag) + // - rFrame is a follow flow row (reopen TableRow tag) + // - rFrame is a cell frame in a follow flow row (reopen TableData tag) + if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) || + ( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) || + ( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) || + ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) ) + { + pKeyFrame = &rFrame; + } + else if ( rFrame.IsFlyFrame() ) + { + const SwFormatAnchor& rAnchor = + static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId())) + { + pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame(); + bContinue = true; + } + } + + if ( pKeyFrame ) + { + void* pKey = lcl_GetKeyFromFrame( *pKeyFrame ); + + if ( pKey ) + { + FrameTagIdMap& rFrameTagIdMap = SwEnhancedPDFExportHelper::GetFrameTagIdMap(); + const FrameTagIdMap::const_iterator aIter = rFrameTagIdMap.find( pKey ); + if ( aIter != rFrameTagIdMap.end() ) + nReopenTag = (*aIter).second; + } + } + } + + if ( -1 != nReopenTag ) + { + nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement(); + const bool bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag ); + OSL_ENSURE( bSuccess, "Failed to reopen tag" ); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.push_back( 99 ); +#endif + + bRet = bSuccess; + } + + return bRet && !bContinue; +} + +void SwTaggedPDFHelper::CheckRestoreTag() const +{ + if ( nRestoreCurrentTag != -1 ) + { + const bool bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement( nRestoreCurrentTag ); + OSL_ENSURE( bSuccess, "Failed to restore reopened tag" ); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.pop_back(); +#endif + } +} + +void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString ) +{ + // write new tag + const sal_Int32 nId = mpPDFExtOutDevData->BeginStructureElement( eType, rString ); + ++nEndStructureElement; + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.push_back( static_cast<sal_uInt16>(eType) ); +#endif + + // Store the id of the current structure element if + // - it is a list structure element + // - it is a list body element with children + // - rFrame is the first page frame + // - rFrame is a master frame + // - rFrame has objects anchored to it + // - rFrame is a row frame or cell frame in a split table row + + if ( mpNumInfo ) + { + const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(mpNumInfo->mrFrame); + SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps(); + const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame()); + + if ( vcl::PDFWriter::List == eType ) + { + NumListIdMap& rNumListIdMap = SwEnhancedPDFExportHelper::GetNumListIdMap(); + rNumListIdMap[ pNodeNum ] = nId; + } + else if ( vcl::PDFWriter::LIBody == eType ) + { + NumListBodyIdMap& rNumListBodyIdMap = SwEnhancedPDFExportHelper::GetNumListBodyIdMap(); + rNumListBodyIdMap[ pNodeNum ] = nId; + } + } + else if ( mpFrameInfo ) + { + const SwFrame& rFrame = mpFrameInfo->mrFrame; + + if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) || + ( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) || + ( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) || + ( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) || + ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) ) + { + const void* pKey = lcl_GetKeyFromFrame( rFrame ); + + if ( pKey ) + { + FrameTagIdMap& rFrameTagIdMap = SwEnhancedPDFExportHelper::GetFrameTagIdMap(); + rFrameTagIdMap[ pKey ] = nId; + } + } + } + + SetAttributes( eType ); +} + +void SwTaggedPDFHelper::EndTag() +{ + mpPDFExtOutDevData->EndStructureElement(); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.pop_back(); +#endif +} + +// Sets the attributes according to the structure type. +void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) +{ + vcl::PDFWriter::StructAttributeValue eVal; + sal_Int32 nVal; + + /* + * ATTRIBUTES FOR BLSE + */ + if ( mpFrameInfo ) + { + const SwFrame* pFrame = &mpFrameInfo->mrFrame; + SwRectFnSet aRectFnSet(pFrame); + + bool bPlacement = false; + bool bWritingMode = false; + bool bSpaceBefore = false; + bool bSpaceAfter = false; + bool bStartIndent = false; + bool bEndIndent = false; + bool bTextIndent = false; + bool bTextAlign = false; + bool bWidth = false; + bool bHeight = false; + bool bBox = false; + bool bRowSpan = false; + + // Check which attributes to set: + + switch ( eType ) + { + case vcl::PDFWriter::Document : + bWritingMode = true; + break; + + case vcl::PDFWriter::Table : + bPlacement = + bWritingMode = + bSpaceBefore = + bSpaceAfter = + bStartIndent = + bEndIndent = + bWidth = + bHeight = + bBox = true; + break; + + case vcl::PDFWriter::TableRow : + bPlacement = + bWritingMode = true; + break; + + case vcl::PDFWriter::TableHeader : + case vcl::PDFWriter::TableData : + bPlacement = + bWritingMode = + bWidth = + bHeight = + bRowSpan = true; + break; + + case vcl::PDFWriter::H1 : + case vcl::PDFWriter::H2 : + case vcl::PDFWriter::H3 : + case vcl::PDFWriter::H4 : + case vcl::PDFWriter::H5 : + case vcl::PDFWriter::H6 : + case vcl::PDFWriter::Paragraph : + case vcl::PDFWriter::Heading : + case vcl::PDFWriter::Caption : + case vcl::PDFWriter::BlockQuote : + + bPlacement = + bWritingMode = + bSpaceBefore = + bSpaceAfter = + bStartIndent = + bEndIndent = + bTextIndent = + bTextAlign = true; + break; + + case vcl::PDFWriter::Formula : + case vcl::PDFWriter::Figure : + bPlacement = + bWidth = + bHeight = + bBox = true; + break; + default : + break; + } + + // Set the attributes: + + if ( bPlacement ) + { + eVal = vcl::PDFWriter::TableHeader == eType || + vcl::PDFWriter::TableData == eType ? + vcl::PDFWriter::Inline : + vcl::PDFWriter::Block; + + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal ); + } + + if ( bWritingMode ) + { + eVal = pFrame->IsVertical() ? + vcl::PDFWriter::TbRl : + pFrame->IsRightToLeft() ? + vcl::PDFWriter::RlTb : + vcl::PDFWriter::LrTb; + + if ( vcl::PDFWriter::LrTb != eVal ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::WritingMode, eVal ); + } + + if ( bSpaceBefore ) + { + nVal = aRectFnSet.GetTopMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceBefore, nVal ); + } + + if ( bSpaceAfter ) + { + nVal = aRectFnSet.GetBottomMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceAfter, nVal ); + } + + if ( bStartIndent ) + { + nVal = aRectFnSet.GetLeftMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::StartIndent, nVal ); + } + + if ( bEndIndent ) + { + nVal = aRectFnSet.GetRightMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal ); + } + + if ( bTextIndent ) + { + OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" ); + const SvxLRSpaceItem &rSpace = + static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace(); + nVal = rSpace.GetTextFirstLineOffset(); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal ); + } + + if ( bTextAlign ) + { + OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" ); + const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet(); + const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust(); + if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust || + ( (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) || + (!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) ) + { + eVal = SvxAdjust::Block == nAdjust ? + vcl::PDFWriter::Justify : + SvxAdjust::Center == nAdjust ? + vcl::PDFWriter::Center : + vcl::PDFWriter::End; + + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextAlign, eVal ); + } + } + + // Formally here bAlternateText was triggered for PDF export, but this + // was moved for more general use to primitives and usage in + // VclMetafileProcessor2D (see processGraphicPrimitive2D). + + if ( bWidth ) + { + nVal = aRectFnSet.GetWidth(pFrame->getFrameArea()); + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal ); + } + + if ( bHeight ) + { + nVal = aRectFnSet.GetHeight(pFrame->getFrameArea()); + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Height, nVal ); + } + + if ( bBox ) + { + // BBox only for non-split tables: + if ( vcl::PDFWriter::Table != eType || + ( pFrame->IsTabFrame() && + !static_cast<const SwTabFrame*>(pFrame)->IsFollow() && + !static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) ) + { + mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect()); + } + } + + if ( bRowSpan ) + { + const SwCellFrame* pThisCell = dynamic_cast<const SwCellFrame*>(pFrame); + if ( pThisCell ) + { + nVal = pThisCell->GetTabBox()->getRowSpan(); + if ( nVal > 1 ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal ); + + // calculate colspan: + const SwTabFrame* pTabFrame = pThisCell->FindTabFrame(); + const SwTable* pTable = pTabFrame->GetTable(); + + SwRectFnSet fnRectX(pTabFrame); + + const TableColumnsMapEntry& rCols = SwEnhancedPDFExportHelper::GetTableColumnsMap()[ pTable ]; + + const long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea()); + const long nRight = fnRectX.GetRight(pThisCell->getFrameArea()); + const TableColumnsMapEntry::const_iterator aLeftIter = rCols.find( nLeft ); + const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight ); + + OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" ); + if ( aLeftIter != rCols.end() && aRightIter != rCols.end() ) + { + nVal = std::distance( aLeftIter, aRightIter ); + if ( nVal > 1 ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal ); + } + } + } + } + + /* + * ATTRIBUTES FOR ILSE + */ + else if ( mpPorInfo ) + { + const SwLinePortion* pPor = &mpPorInfo->mrPor; + const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo(); + + bool bActualText = false; + bool bBaselineShift = false; + bool bTextDecorationType = false; + bool bLinkAttribute = false; + bool bLanguage = false; + + // Check which attributes to set: + + switch ( eType ) + { + case vcl::PDFWriter::Span : + case vcl::PDFWriter::Quote : + case vcl::PDFWriter::Code : + if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() || + PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() ) + bActualText = true; + else + { + bBaselineShift = + bTextDecorationType = + bLanguage = true; + } + break; + + case vcl::PDFWriter::Link : + bTextDecorationType = + bBaselineShift = + bLinkAttribute = + bLanguage = true; + break; + + default: + break; + } + + if ( bActualText ) + { + OUString aActualText; + if (pPor->GetWhichPor() == PortionType::SoftHyphen || pPor->GetWhichPor() == PortionType::Hyphen) + aActualText = OUString(u'\x00ad'); // soft hyphen + else + aActualText = rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(pPor->GetLen())); + mpPDFExtOutDevData->SetActualText( aActualText ); + } + + if ( bBaselineShift ) + { + // TODO: Calculate correct values! + nVal = rInf.GetFont()->GetEscapement(); + if ( nVal > 0 ) nVal = 33; + else if ( nVal < 0 ) nVal = -33; + + if ( 0 != nVal ) + { + nVal = nVal * pPor->Height() / 100; + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::BaselineShift, nVal ); + } + } + + if ( bTextDecorationType ) + { + if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Underline ); + if ( LINESTYLE_NONE != rInf.GetFont()->GetOverline() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline ); + if ( STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::LineThrough ); + if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline ); + } + + if ( bLanguage ) + { + + const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); + const LanguageType nDefaultLang = SwEnhancedPDFExportHelper::GetDefaultLanguage(); + + if ( nDefaultLang != nCurrentLanguage ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) ); + } + + if ( bLinkAttribute ) + { + const LinkIdMap& rLinkIdMap = SwEnhancedPDFExportHelper::GetLinkIdMap(); + SwRect aPorRect; + rInf.CalcRect( *pPor, &aPorRect ); + const Point aPorCenter = aPorRect.Center(); + auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(), + [&aPorCenter](const IdMapEntry& rEntry) { return rEntry.first.IsInside(aPorCenter); }); + if (aIter != rLinkIdMap.end()) + { + sal_Int32 nLinkId = (*aIter).second; + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::LinkAnnotation, nLinkId ); + } + } + } +} + +void SwTaggedPDFHelper::BeginNumberedListStructureElements() +{ + OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" ); + if ( !mpNumInfo ) + return; + + const SwFrame& rFrame = mpNumInfo->mrFrame; + OSL_ENSURE( rFrame.IsTextFrame(), "numbered only for text frames" ); + const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame); + + // Lowers of NonStructureElements should not be considered: + + if ( lcl_IsInNonStructEnv( rTextFrame ) || rTextFrame.IsFollow() ) + return; + + const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps(); + const SwNumRule* pNumRule = pTextNd->GetNumRule(); + const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame()); + + const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule; + + // Check, if we have to reopen a list or a list body: + // First condition: + // Paragraph is numbered/bulleted + if ( !bNumbered ) + return; + + const SwNumberTreeNode* pParent = pNodeNum->GetParent(); + const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd); + + // Second condition: current numbering is not 'interrupted' + if ( bSameNumbering ) + { + sal_Int32 nReopenTag = -1; + + // Two cases: + // 1. We have to reopen an existing list body tag: + // - If the current node is either the first child of its parent + // and its level > 1 or + // - Numbering should restart at the current node and its level > 1 + // - The current item has no label + const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() ); + const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart(); + if ( bNewSubListStart || bNoLabel ) + { + // Fine, we try to reopen the appropriate list body + NumListBodyIdMap& rNumListBodyIdMap = SwEnhancedPDFExportHelper::GetNumListBodyIdMap(); + + if ( bNewSubListStart ) + { + // The list body tag associated with the parent has to be reopened + // to start a new list inside the list body + NumListBodyIdMap::const_iterator aIter; + + do + aIter = rNumListBodyIdMap.find( pParent ); + while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) ); + + if ( aIter != rNumListBodyIdMap.end() ) + nReopenTag = (*aIter).second; + } + else // if(bNoLabel) + { + // The list body tag of a 'counted' predecessor has to be reopened + const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true); + while ( pPrevious ) + { + if ( pPrevious->IsCounted()) + { + // get id of list body tag + const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious ); + if ( aIter != rNumListBodyIdMap.end() ) + { + nReopenTag = (*aIter).second; + break; + } + } + pPrevious = pPrevious->GetPred(true); + } + } + } + // 2. We have to reopen an existing list tag: + else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() ) + { + // any other than the first node in a list level has to reopen the current + // list. The current list is associated in a map with the first child of the list: + NumListIdMap& rNumListIdMap = SwEnhancedPDFExportHelper::GetNumListIdMap(); + + // Search backwards and check if any of the previous nodes has a list associated with it: + const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true); + while ( pPrevious ) + { + // get id of list tag + const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious ); + if ( aIter != rNumListIdMap.end() ) + { + nReopenTag = (*aIter).second; + break; + } + + pPrevious = pPrevious->GetPred(true); + } + } + + if ( -1 != nReopenTag ) + { + nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement(); + mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag ); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.push_back( 99 ); +#endif + } + } + else + { + // clear list maps in case a list has been interrupted + NumListIdMap& rNumListIdMap = SwEnhancedPDFExportHelper::GetNumListIdMap(); + rNumListIdMap.clear(); + NumListBodyIdMap& rNumListBodyIdMap = SwEnhancedPDFExportHelper::GetNumListBodyIdMap(); + rNumListBodyIdMap.clear(); + } + + // New tags: + const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering); + const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item: + + if ( bNewListTag ) + BeginTag( vcl::PDFWriter::List, aListString ); + + if ( bNewItemTag ) + { + BeginTag( vcl::PDFWriter::ListItem, aListItemString ); + BeginTag( vcl::PDFWriter::LIBody, aListBodyString ); + } +} + +void SwTaggedPDFHelper::BeginBlockStructureElements() +{ + const SwFrame* pFrame = &mpFrameInfo->mrFrame; + + // Lowers of NonStructureElements should not be considered: + + if ( lcl_IsInNonStructEnv( *pFrame ) ) + return; + + // Check if we have to reopen an existing structure element. + // This has to be done e.g., if pFrame is a follow frame. + if ( CheckReopenTag() ) + return; + + sal_uInt16 nPDFType = USHRT_MAX; + OUString aPDFType; + + switch ( pFrame->GetType() ) + { + /* + * GROUPING ELEMENTS + */ + + case SwFrameType::Page : + + // Document: Document + + nPDFType = vcl::PDFWriter::Document; + aPDFType = aDocumentString; + break; + + case SwFrameType::Header : + case SwFrameType::Footer : + + // Header, Footer: NonStructElement + + nPDFType = vcl::PDFWriter::NonStructElement; + break; + + case SwFrameType::FtnCont : + + // Footnote container: Division + + nPDFType = vcl::PDFWriter::Division; + aPDFType = aDivString; + break; + + case SwFrameType::Ftn : + + // Footnote frame: Note + + // Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless + // we treat it like a grouping element! + nPDFType = vcl::PDFWriter::Note; + aPDFType = aNoteString; + break; + + case SwFrameType::Section : + + // Section: TOX, Index, or Sect + + { + const SwSection* pSection = + static_cast<const SwSectionFrame*>(pFrame)->GetSection(); + if ( SectionType::ToxContent == pSection->GetType() ) + { + const SwTOXBase* pTOXBase = pSection->GetTOXBase(); + if ( pTOXBase ) + { + if ( TOX_INDEX == pTOXBase->GetType() ) + { + nPDFType = vcl::PDFWriter::Index; + aPDFType = aIndexString; + } + else + { + nPDFType = vcl::PDFWriter::TOC; + aPDFType = aTOCString; + } + } + } + else if ( SectionType::Content == pSection->GetType() ) + { + nPDFType = vcl::PDFWriter::Section; + aPDFType = aSectString; + } + } + break; + + /* + * BLOCK-LEVEL STRUCTURE ELEMENTS + */ + + case SwFrameType::Txt : + { + const SwTextNode* pTextNd = + static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps(); + + const SwFormat* pTextFormat = pTextNd->GetFormatColl(); + const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr; + + OUString sStyleName; + OUString sParentStyleName; + + if ( pTextFormat) + SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); + if ( pParentTextFormat) + SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl ); + + // This is the default. If the paragraph could not be mapped to + // any of the standard pdf tags, we write a user defined tag + // <stylename> with role = P + nPDFType = vcl::PDFWriter::Paragraph; + aPDFType = sStyleName; + + // Quotations: BlockQuote + + if (sStyleName == aQuotations) + { + nPDFType = vcl::PDFWriter::BlockQuote; + aPDFType = aBlockQuoteString; + } + + // Caption: Caption + + else if (sStyleName == aCaption) + { + nPDFType = vcl::PDFWriter::Caption; + aPDFType = aCaptionString; + } + + // Caption: Caption + + else if (sParentStyleName == aCaption) + { + nPDFType = vcl::PDFWriter::Caption; + aPDFType = sStyleName + aCaptionString; + } + + // Heading: H + + else if (sStyleName == aHeading) + { + nPDFType = vcl::PDFWriter::Heading; + aPDFType = aHString; + } + + // Heading: H1 - H6 + + if (pTextNd->IsOutline() + && sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd)) + { + int nRealLevel = pTextNd->GetAttrOutlineLevel()-1; + nRealLevel = std::min(nRealLevel, 5); + + nPDFType = static_cast<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel); + switch(nRealLevel) + { + case 0 : + aPDFType = aH1String; + break; + case 1 : + aPDFType = aH2String; + break; + case 2 : + aPDFType = aH3String; + break; + case 3 : + aPDFType = aH4String; + break; + case 4 : + aPDFType = aH5String; + break; + default: + aPDFType = aH6String; + break; + } + } + + // Section: TOCI + + else if ( pFrame->IsInSct() ) + { + const SwSectionFrame* pSctFrame = pFrame->FindSctFrame(); + const SwSection* pSection = pSctFrame->GetSection(); + + if ( SectionType::ToxContent == pSection->GetType() ) + { + const SwTOXBase* pTOXBase = pSection->GetTOXBase(); + if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() ) + { + // Special case: Open additional TOCI tag: + BeginTag( vcl::PDFWriter::TOCI, aTOCIString ); + } + } + } + } + break; + + case SwFrameType::Tab : + + // TabFrame: Table + + nPDFType = vcl::PDFWriter::Table; + aPDFType = aTableString; + + { + // set up table column data: + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame); + const SwTable* pTable = pTabFrame->GetTable(); + + TableColumnsMap& rTableColumnsMap = SwEnhancedPDFExportHelper::GetTableColumnsMap(); + const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable ); + + if ( aIter == rTableColumnsMap.end() ) + { + SwRectFnSet aRectFnSet(pTabFrame); + TableColumnsMapEntry& rCols = rTableColumnsMap[ pTable ]; + + const SwTabFrame* pMasterFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame; + + while ( pMasterFrame ) + { + const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pMasterFrame->GetLower()); + + while ( pRowFrame ) + { + const SwFrame* pCellFrame = pRowFrame->GetLower(); + + const long nLeft = aRectFnSet.GetLeft(pCellFrame->getFrameArea()); + rCols.insert( nLeft ); + + while ( pCellFrame ) + { + const long nRight = aRectFnSet.GetRight(pCellFrame->getFrameArea()); + rCols.insert( nRight ); + pCellFrame = pCellFrame->GetNext(); + } + pRowFrame = static_cast<const SwRowFrame*>(pRowFrame->GetNext()); + } + pMasterFrame = pMasterFrame->GetFollow(); + } + } + } + + break; + + /* + * TABLE ELEMENTS + */ + + case SwFrameType::Row : + + // RowFrame: TR + + if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() ) + { + nPDFType = vcl::PDFWriter::TableRow; + aPDFType = aTRString; + } + else + { + nPDFType = vcl::PDFWriter::NonStructElement; + } + break; + + case SwFrameType::Cell : + + // CellFrame: TH, TD + + { + const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame(); + if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) ) + { + nPDFType = vcl::PDFWriter::TableHeader; + aPDFType = aTHString; + } + else + { + nPDFType = vcl::PDFWriter::TableData; + aPDFType = aTDString; + } + } + break; + + /* + * ILLUSTRATION + */ + + case SwFrameType::Fly : + + // FlyFrame: Figure, Formula, Control + // fly in content or fly at page + { + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pFrame); + if ( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + bool bFormula = false; + + const SwNoTextFrame* pNoTextFrame = static_cast<const SwNoTextFrame*>(pFly->Lower()); + SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTextFrame->GetNode()->GetOLENode()); + if ( pOLENd ) + { + SwOLEObj& aOLEObj = pOLENd->GetOLEObj(); + uno::Reference< embed::XEmbeddedObject > aRef = aOLEObj.GetOleRef(); + if ( aRef.is() ) + { + bFormula = 0 != SotExchange::IsMath( SvGlobalName( aRef->getClassID() ) ); + } + } + if ( bFormula ) + { + nPDFType = vcl::PDFWriter::Formula; + aPDFType = aFormulaString; + } + else + { + nPDFType = vcl::PDFWriter::Figure; + aPDFType = aFigureString; + } + } + else + { + nPDFType = vcl::PDFWriter::Division; + aPDFType = aDivString; + } + } + break; + + default: break; + } + + if ( USHRT_MAX != nPDFType ) + { + BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType ); + } +} + +void SwTaggedPDFHelper::EndStructureElements() +{ + while ( nEndStructureElement > 0 ) + { + EndTag(); + --nEndStructureElement; + } + + CheckRestoreTag(); +} + +void SwTaggedPDFHelper::BeginInlineStructureElements() +{ + const SwLinePortion* pPor = &mpPorInfo->mrPor; + const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo(); + const SwTextFrame* pFrame = rInf.GetTextFrame(); + + // Lowers of NonStructureElements should not be considered: + + if ( lcl_IsInNonStructEnv( *pFrame ) ) + return; + + sal_uInt16 nPDFType = USHRT_MAX; + OUString aPDFType; + + switch ( pPor->GetWhichPor() ) + { + case PortionType::Hyphen : + case PortionType::SoftHyphen : + // Check for alternative spelling: + case PortionType::HyphenStr : + case PortionType::SoftHyphenStr : + nPDFType = vcl::PDFWriter::Span; + aPDFType = aSpanString; + break; + + case PortionType::Lay : + case PortionType::Text : + case PortionType::Para : + { + std::pair<SwTextNode const*, sal_Int32> const pos( + pFrame->MapViewToModel(rInf.GetIdx())); + SwTextAttr const*const pInetFormatAttr = + pos.first->GetTextAttrAt(pos.second, RES_TXTATR_INETFMT); + + OUString sStyleName; + if ( !pInetFormatAttr ) + { + std::vector<SwTextAttr *> const charAttrs( + pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT)); + // TODO: handle more than 1 char style? + const SwCharFormat* pCharFormat = (charAttrs.size()) + ? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr; + if ( pCharFormat ) + SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); + } + + // Check for Link: + if( pInetFormatAttr ) + { + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + } + // Check for Quote/Code character style: + else if (sStyleName == aQuotation) + { + nPDFType = vcl::PDFWriter::Quote; + aPDFType = aQuoteString; + } + else if (sStyleName == aSourceText) + { + nPDFType = vcl::PDFWriter::Code; + aPDFType = aCodeString; + } + else + { + const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); + const SwFontScript nFont = rInf.GetFont()->GetActual(); + const LanguageType nDefaultLang = SwEnhancedPDFExportHelper::GetDefaultLanguage(); + + if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() || + LINESTYLE_NONE != rInf.GetFont()->GetOverline() || + STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() || + FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() || + 0 != rInf.GetFont()->GetEscapement() || + SwFontScript::Latin != nFont || + nCurrentLanguage != nDefaultLang || + !sStyleName.isEmpty()) + { + nPDFType = vcl::PDFWriter::Span; + if (!sStyleName.isEmpty()) + aPDFType = sStyleName; + else + aPDFType = aSpanString; + } + } + } + break; + + case PortionType::Footnote : + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + break; + + case PortionType::Field : + { + // check field type: + TextFrameIndex const nIdx = static_cast<const SwFieldPortion*>(pPor)->IsFollow() + ? rInf.GetIdx() - TextFrameIndex(1) + : rInf.GetIdx(); + const SwTextAttr* pHint = mpPorInfo->mrTextPainter.GetAttr( nIdx ); + if ( pHint && RES_TXTATR_FIELD == pHint->Which() ) + { + const SwField* pField = pHint->GetFormatField().GetField(); + if ( SwFieldIds::GetRef == pField->Which() ) + { + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + } + else if ( SwFieldIds::TableOfAuthorities == pField->Which() ) + { + nPDFType = vcl::PDFWriter::BibEntry; + aPDFType = aBibEntryString; + } + } + } + break; + + case PortionType::Table : + case PortionType::TabRight : + case PortionType::TabCenter : + case PortionType::TabDecimal : + nPDFType = vcl::PDFWriter::NonStructElement; + break; + default: break; + } + + if ( USHRT_MAX != nPDFType ) + { + BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType ); + } +} + +bool SwTaggedPDFHelper::IsExportTaggedPDF( const OutputDevice& rOut ) +{ + vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() ); + return pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF(); +} + +SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh, + OutputDevice& rOut, + const OUString& rPageRange, + bool bSkipEmptyPages, + bool bEditEngineOnly, + const SwPrintData& rPrintData ) + : mrSh( rSh ), + mrOut( rOut ), + mbSkipEmptyPages( bSkipEmptyPages ), + mbEditEngineOnly( bEditEngineOnly ), + mrPrintData( rPrintData ) +{ + if ( !rPageRange.isEmpty() ) + mpRangeEnum.reset( new StringRangeEnumerator( rPageRange, 0, mrSh.GetPageCount()-1 ) ); + + if ( mbSkipEmptyPages ) + { + maPageNumberMap.resize( mrSh.GetPageCount() ); + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + sal_Int32 nPageNumber = 0; + for ( size_t i = 0, n = maPageNumberMap.size(); i < n && pCurrPage; ++i ) + { + if ( pCurrPage->IsEmptyPage() ) + maPageNumberMap[i] = -1; + else + maPageNumberMap[i] = nPageNumber++; + + pCurrPage = static_cast<const SwPageFrame*>( pCurrPage->GetNext() ); + } + } + + aTableColumnsMap.clear(); + aLinkIdMap.clear(); + aNumListIdMap.clear(); + aNumListBodyIdMap.clear(); + aFrameTagIdMap.clear(); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.clear(); +#endif + + const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + sal_uInt16 nLangRes = RES_CHRATR_LANGUAGE; + + if ( i18n::ScriptType::ASIAN == nScript ) + nLangRes = RES_CHRATR_CJK_LANGUAGE; + else if ( i18n::ScriptType::COMPLEX == nScript ) + nLangRes = RES_CHRATR_CTL_LANGUAGE; + + eLanguageDefault = static_cast<const SvxLanguageItem*>(&mrSh.GetDoc()->GetDefault( nLangRes ))->GetLanguage(); + + EnhancedPDFExport(); +} + +SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper() +{ +} + +tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage, + const tools::Rectangle& rRectangle) const +{ + SwPostItMode nPostItMode = mrPrintData.GetPrintPostIts(); + if (nPostItMode != SwPostItMode::InMargins) + return rRectangle; + //the page has been scaled by 75% and vertically centered, so adjust these + //rectangles equivalently + tools::Rectangle aRect(rRectangle); + Size aRectSize(aRect.GetSize()); + double fScale = 0.75; + aRectSize.setWidth( aRectSize.Width() * fScale ); + aRectSize.setHeight( aRectSize.Height() * fScale ); + long nOrigHeight = pCurrPage->getFrameArea().Height(); + long nNewHeight = nOrigHeight*fScale; + long nShiftY = (nOrigHeight-nNewHeight)/2; + aRect.SetLeft( aRect.Left() * fScale ); + aRect.SetTop( aRect.Top() * fScale ); + aRect.Move(0, nShiftY); + aRect.SetSize(aRectSize); + return aRect; +} + +void SwEnhancedPDFExportHelper::EnhancedPDFExport() +{ + vcl::PDFExtOutDevData* pPDFExtOutDevData = + dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() ); + + if ( !pPDFExtOutDevData ) + return; + + // set the document locale + + css::lang::Locale aDocLocale( LanguageTag( SwEnhancedPDFExportHelper::GetDefaultLanguage() ).getLocale() ); + pPDFExtOutDevData->SetDocumentLocale( aDocLocale ); + + // Prepare the output device: + + mrOut.Push( PushFlags::MAPMODE ); + MapMode aMapMode( mrOut.GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + mrOut.SetMapMode( aMapMode ); + + // Create new cursor and lock the view: + + SwDoc* pDoc = mrSh.GetDoc(); + mrSh.SwCursorShell::Push(); + mrSh.SwCursorShell::ClearMark(); + const bool bOldLockView = mrSh.IsViewLocked(); + mrSh.LockView( true ); + + if ( !mbEditEngineOnly ) + { + + // POSTITS + + if ( pPDFExtOutDevData->GetIsExportNotes() ) + { + std::vector<SwFormatField*> vpFields; + mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields); + for(auto pFormatField : vpFields) + { + const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode(); + OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing"); + if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField)) + continue; + // Link Rectangle + const SwRect& rNoteRect = mrSh.GetCharRect(); + const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower()); + + // Link PageNums + std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect); + for (sal_Int32 aNotePageNum : aNotePageNums) + { + + // Use the NumberFormatter to get the date string: + const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField()); + SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter(); + const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate()); + const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage()); + OUString sDate; + Color* pColor; + pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor); + + vcl::PDFNote aNote; + // The title should consist of the author and the date: + aNote.Title = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : ""); + // Guess what the contents contains... + aNote.Contents = pField->GetText(); + + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect())); + pPDFExtOutDevData->CreateNote(aRect, aNote, aNotePageNum); + } + mrSh.SwCursorShell::ClearMark(); + } + } + + // HYPERLINKS + + SwGetINetAttrs aArr; + mrSh.GetINetAttrs( aArr ); + for( auto &rAttr : aArr ) + { + SwGetINetAttr* p = &rAttr; + OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" ); + + const SwTextNode* pTNd = p->rINetAttr.GetpTextNode(); + OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + + // 1. Check if the whole paragraph is hidden + // 2. Move to the hyperlink + // 3. Check for hidden text attribute + if ( !pTNd->IsHidden() && + mrSh.GotoINetAttr( p->rINetAttr ) && + !mrSh.SelectHiddenRange() ) + { + // Select the hyperlink: + mrSh.SwCursorShell::Right( 1, CRSR_SKIP_CHARS ); + if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) ) + { + // First, we create the destination, because there may be more + // than one link to this destination: + OUString aURL( INetURLObject::decode( + p->rINetAttr.GetINetFormat().GetValue(), + INetURLObject::DecodeMechanism::Unambiguous ) ); + + // We have to distinguish between intern and real URLs + const bool bIntern = '#' == aURL[0]; + + // GetCursor_() is a SwShellCursor, which is derived from + // SwSelPaintRects, therefore the rectangles of the current + // selection can be easily obtained: + // Note: We make a copy of the rectangles, because they may + // be deleted again in JumpToSwMark. + SwRects aTmp; + aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() ); + OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); + + const SwPageFrame* pSelectionPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Create the destination for internal links: + sal_Int32 nDestId = -1; + if ( bIntern ) + { + aURL = aURL.copy( 1 ); + mrSh.SwCursorShell::ClearMark(); + if (! JumpToSwMark( &mrSh, aURL )) + { + continue; // target deleted + } + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + } + } + + if ( !bIntern || -1 != nDestId ) + { + // #i44368# Links in Header/Footer + const SwPosition aPos( *pTNd ); + const bool bHeaderFooter = pDoc->IsInHeaderFooter( aPos.nNode ); + + // Create links for all selected rectangles: + const size_t nNumOfRects = aTmp.size(); + for ( size_t i = 0; i < nNumOfRects; ++i ) + { + // Link Rectangle + const SwRect& rLinkRect( aTmp[ i ] ); + + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect ); + + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect())); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry( rLinkRect, nLinkId ); + aLinkIdMap.push_back( aLinkEntry ); + + // Connect Link and Destination: + if ( bIntern ) + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + else + pPDFExtOutDevData->SetLinkURL( nLinkId, aURL ); + + // #i44368# Links in Header/Footer + if ( bHeaderFooter ) + MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bIntern ); + } + } + } + } + } + mrSh.SwCursorShell::ClearMark(); + } + + // HYPERLINKS (Graphics, Frames, OLEs ) + + SwFrameFormats* pTable = pDoc->GetSpzFrameFormats(); + const size_t nSpzFrameFormatsCount = pTable->size(); + for( size_t n = 0; n < nSpzFrameFormatsCount; ++n ) + { + SwFrameFormat* pFrameFormat = (*pTable)[n]; + const SfxPoolItem* pItem; + if ( RES_DRAWFRMFMT != pFrameFormat->Which() && + GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) && + SfxItemState::SET == pFrameFormat->GetAttrSet().GetItemState( RES_URL, true, &pItem ) ) + { + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + OUString aURL( static_cast<const SwFormatURL*>(pItem)->GetURL() ); + const bool bIntern = !aURL.isEmpty() && '#' == aURL[0]; + + // Create the destination for internal links: + sal_Int32 nDestId = -1; + if ( bIntern ) + { + aURL = aURL.copy( 1 ); + mrSh.SwCursorShell::ClearMark(); + if (! JumpToSwMark( &mrSh, aURL )) + { + continue; // target deleted + } + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + } + } + + if ( !bIntern || -1 != nDestId ) + { + Point aNullPt; + const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt ); + + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect ); + + // Link Export + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + + // Connect Link and Destination: + if ( bIntern ) + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + else + pPDFExtOutDevData->SetLinkURL( nLinkId, aURL ); + + // #i44368# Links in Header/Footer + const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor(); + if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) + { + const SwPosition* pPosition = rAnch.GetContentAnchor(); + if ( pPosition && pDoc->IsInHeaderFooter( pPosition->nNode ) ) + { + const SwTextNode* pTNd = pPosition->nNode.GetNode().GetTextNode(); + if ( pTNd ) + MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bIntern ); + } + } + } + } + } + else if (pFrameFormat->Which() == RES_DRAWFRMFMT) + { + // Turn media shapes into Screen annotations. + if (SdrObject* pObject = pFrameFormat->FindRealSdrObject()) + { + SwRect aSnapRect = pObject->GetSnapRect(); + std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect); + if (aScreenPageNums.empty()) + continue; + + uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY); + if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape") + { + uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY); + OUString aMediaURL; + xShapePropSet->getPropertyValue("MediaURL") >>= aMediaURL; + if (!aMediaURL.isEmpty()) + { + const SwPageFrame* pCurrPage = mrSh.GetLayout()->GetPageAtPos(aSnapRect.Center()); + tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect())); + for (sal_Int32 nScreenPageNum : aScreenPageNums) + { + sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, nScreenPageNum); + if (aMediaURL.startsWith("vnd.sun.star.Package:")) + { + // Embedded media. + OUString aTempFileURL; + xShapePropSet->getPropertyValue("PrivateTempFileURL") >>= aTempFileURL; + pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL); + } + else + // Linked media. + pPDFExtOutDevData->SetScreenURL(nScreenId, aMediaURL); + } + } + } + } + } + mrSh.SwCursorShell::ClearMark(); + } + + // REFERENCES + + std::vector<SwFormatField*> vpFields; + mrSh.GetFieldType( SwFieldIds::GetRef, OUString() )->GatherFields(vpFields); + for(auto pFormatField : vpFields ) + { + if( pFormatField->GetTextField() && pFormatField->IsFieldInDoc() ) + { + const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode(); + OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField)) + continue; + // Select the field: + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right( 1, CRSR_SKIP_CHARS ); + + // Link Rectangles + SwRects aTmp; + aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() ); + OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); + + mrSh.SwCursorShell::ClearMark(); + + // Destination Rectangle + const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField()); + const OUString& rRefName = pField->GetSetRefName(); + mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo() ); + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + if ( -1 != nDestPageNum ) + { + // Destination Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + + // #i44368# Links in Header/Footer + const SwPosition aPos( *pTNd ); + const bool bHeaderFooter = pDoc->IsInHeaderFooter( aPos.nNode ); + + // Create links for all selected rectangles: + const size_t nNumOfRects = aTmp.size(); + for ( size_t i = 0; i < nNumOfRects; ++i ) + { + // Link rectangle + const SwRect& rLinkRect( aTmp[ i ] ); + + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect ); + + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + // Link Export + aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry( rLinkRect, nLinkId ); + aLinkIdMap.push_back( aLinkEntry ); + + // Connect Link and Destination: + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + + // #i44368# Links in Header/Footer + if ( bHeaderFooter ) + { + const OUString aDummy; + MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aDummy, true ); + } + } + } + } + } + mrSh.SwCursorShell::ClearMark(); + } + + // FOOTNOTES + + const size_t nFootnoteCount = pDoc->GetFootnoteIdxs().size(); + for ( size_t nIdx = 0; nIdx < nFootnoteCount; ++nIdx ) + { + // Set cursor to text node that contains the footnote: + const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ]; + SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode()); + + mrSh.GetCursor_()->GetPoint()->nNode = rTNd; + mrSh.GetCursor_()->GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() ); + + // 1. Check if the whole paragraph is hidden + // 2. Check for hidden text attribute + if (rTNd.GetTextNode()->IsHidden() || mrSh.SelectHiddenRange() + || (mrSh.GetLayout()->IsHideRedlines() + && sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote))) + { + continue; + } + + SwCursorSaveState aSaveState( *mrSh.GetCursor_() ); + + // Select the footnote: + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right( 1, CRSR_SKIP_CHARS ); + + // Link Rectangle + SwRects aTmp; + aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() ); + OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); + + mrSh.GetCursor_()->RestoreSavePos(); + mrSh.SwCursorShell::ClearMark(); + + if (aTmp.empty()) + continue; + + const SwRect aLinkRect( aTmp[ 0 ] ); + + // Goto footnote text: + if ( mrSh.GotoFootnoteText() ) + { + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect ); + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry( aLinkRect, nLinkId ); + aLinkIdMap.push_back( aLinkEntry ); + + if ( -1 != nDestPageNum ) + { + aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect()); + // Destination Export + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(rDestRect.SVRect(), nDestPageNum); + + // Connect Link and Destination: + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + } + } + } + } + + // OUTLINE + + if( pPDFExtOutDevData->GetIsExportBookmarks() ) + { + typedef std::pair< sal_Int8, sal_Int32 > StackEntry; + std::stack< StackEntry > aOutlineStack; + aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value + + const SwOutlineNodes::size_type nOutlineCount = + mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); + for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i ) + { + // Check if outline is hidden + const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode(); + OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + + if ( pTNd->IsHidden() || + !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) || + // #i40292# Skip empty outlines: + pTNd->GetText().isEmpty()) + continue; + + // Get parent id from stack: + const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i )); + sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first; + while ( nLevelOnTopOfStack >= nLevel && + nLevelOnTopOfStack != -1 ) + { + aOutlineStack.pop(); + nLevelOnTopOfStack = aOutlineStack.top().first; + } + const sal_Int32 nParent = aOutlineStack.top().second; + + // Destination rectangle + mrSh.GotoOutline(i); + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + if ( -1 != nDestPageNum ) + { + // Destination Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + const sal_Int32 nDestId = + pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + + // Outline entry text + const OUString& rEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText( + i, mrSh.GetLayout(), true, false, false ); + + // Create a new outline item: + const sal_Int32 nOutlineId = + pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, nDestId ); + + // Push current level and nOutlineId on stack: + aOutlineStack.push( StackEntry( nLevel, nOutlineId ) ); + } + } + } + + if( pPDFExtOutDevData->GetIsExportNamedDestinations() ) + { + // #i56629# the iteration to convert the OOo bookmark (#bookmark) + // into PDF named destination, see section 8.2.1 in PDF 1.4 spec + // We need: + // 1. a name for the destination, formed from the standard OOo bookmark name + // 2. the destination, obtained from where the bookmark destination lies + IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess(); + for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin(); + ppMark != pMarkAccess->getBookmarksEnd(); + ++ppMark) + { + //get the name + const ::sw::mark::IMark* pBkmk = *ppMark; + mrSh.SwCursorShell::ClearMark(); + const OUString& sBkName = pBkmk->GetName(); + + //jump to it + if (! JumpToSwMark( &mrSh, sBkName )) + { + continue; + } + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum); + } + } + mrSh.SwCursorShell::ClearMark(); + //<--- i56629 + } + } + else + { + + // LINKS FROM EDITENGINE + + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); + for ( const auto& rBookmark : rBookmarks ) + { + OUString aBookmarkName( rBookmark.aBookmark ); + const bool bIntern = '#' == aBookmarkName[0]; + if ( bIntern ) + { + aBookmarkName = aBookmarkName.copy( 1 ); + JumpToSwMark( &mrSh, aBookmarkName ); + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + if ( rBookmark.nLinkId != -1 ) + { + // Destination Export + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + + // Connect Link and Destination: + pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId ); + } + else + { + pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum); + } + } + } + else + pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName ); + } + rBookmarks.clear(); + } + + // Restore view, cursor, and outdev: + mrSh.LockView( bOldLockView ); + mrSh.SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent); + mrOut.Pop(); +} + +// Returns the page number in the output pdf on which the given rect is located. +// If this page is duplicated, method will return first occurrence of it. +sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const +{ + std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect ); + if ( !aPageNums.empty() ) + return aPageNums[0]; + return -1; +} + +// Returns a vector of the page numbers in the output pdf on which the given +// rect is located. There can be many such pages since StringRangeEnumerator +// allows duplication of its entries. +std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums( + const SwRect& rRect ) const +{ + std::vector< sal_Int32 > aPageNums; + + // Document page number. + sal_Int32 nPageNumOfRect = mrSh.GetPageNumAndSetOffsetForPDF( mrOut, rRect ); + if ( nPageNumOfRect < 0 ) + return aPageNums; + + // What will be the page numbers of page nPageNumOfRect in the output pdf? + if ( mpRangeEnum ) + { + if ( mbSkipEmptyPages ) + // Map the page number to the range without empty pages. + nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ]; + + if ( mpRangeEnum->hasValue( nPageNumOfRect ) ) + { + sal_Int32 nOutputPageNum = 0; + StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin(); + StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end(); + for ( ; aIter != aEnd; ++aIter ) + { + if ( *aIter == nPageNumOfRect ) + aPageNums.push_back( nOutputPageNum ); + ++nOutputPageNum; + } + } + } + else + { + if ( mbSkipEmptyPages ) + { + sal_Int32 nOutputPageNum = 0; + for ( size_t i = 0; i < maPageNumberMap.size(); ++i ) + { + if ( maPageNumberMap[i] >= 0 ) // is not empty? + { + if ( i == static_cast<size_t>( nPageNumOfRect ) ) + { + aPageNums.push_back( nOutputPageNum ); + break; + } + ++nOutputPageNum; + } + } + } + else + aPageNums.push_back( nPageNumOfRect ); + } + + return aPageNums; +} + +void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData, + const SwTextNode& rTNd, + const SwRect& rLinkRect, + sal_Int32 nDestId, + const OUString& rURL, + bool bIntern ) const +{ + // We assume, that the primary link has just been exported. Therefore + // the offset of the link rectangle calculates as follows: + const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin(); + + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd); + for ( SwTextFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() ) + { + // Add offset to current page: + const SwPageFrame* pPageFrame = pTmpFrame->FindPageFrame(); + SwRect aHFLinkRect( rLinkRect ); + aHFLinkRect.Pos() = pPageFrame->getFrameArea().Pos() + aOffset; + + // #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong + // fool it by comparing the position only (the width and height are the + // same anyway) + if ( aHFLinkRect.Pos() != rLinkRect.Pos() ) + { + // Link PageNums + std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect ); + + for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums) + { + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect())); + const sal_Int32 nHFLinkId = + rPDFExtOutDevData.CreateLink(aRect, aHFLinkPageNum); + + // Connect Link and Destination: + if ( bIntern ) + rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId ); + else + rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/SwGrammarMarkUp.cxx b/sw/source/core/text/SwGrammarMarkUp.cxx new file mode 100644 index 000000000..3a007ce05 --- /dev/null +++ b/sw/source/core/text/SwGrammarMarkUp.cxx @@ -0,0 +1,145 @@ +/* -*- 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 <SwGrammarMarkUp.hxx> + +SwGrammarMarkUp::~SwGrammarMarkUp() +{ +} + +SwWrongList* SwGrammarMarkUp::Clone() +{ + SwWrongList* pClone = new SwGrammarMarkUp(); + pClone->CopyFrom( *this ); + return pClone; +} + +void SwGrammarMarkUp::CopyFrom( const SwWrongList& rCopy ) +{ + maSentence = static_cast<const SwGrammarMarkUp&>(rCopy).maSentence; + SwWrongList::CopyFrom( rCopy ); +} + +void SwGrammarMarkUp::MoveGrammar( sal_Int32 nPos, sal_Int32 nDiff ) +{ + Move( nPos, nDiff ); + if( maSentence.empty() ) + return; + auto pIter = std::find_if(maSentence.begin(), maSentence.end(), + [nPos](const sal_Int32& rPos) { return rPos >= nPos; }); + const sal_Int32 nEnd = nDiff < 0 ? nPos-nDiff : nPos; + while( pIter != maSentence.end() ) + { + if( *pIter >= nEnd ) + *pIter += nDiff; + else + *pIter = nPos; + ++pIter; + } +} + +SwGrammarMarkUp* SwGrammarMarkUp::SplitGrammarList( sal_Int32 nSplitPos ) +{ + SwGrammarMarkUp* pNew = static_cast<SwGrammarMarkUp*>(SplitList( nSplitPos )); + if( maSentence.empty() ) + return pNew; + auto pIter = std::find_if(maSentence.begin(), maSentence.end(), + [nSplitPos](const sal_Int32& rPos) { return rPos >= nSplitPos; }); + if( pIter != maSentence.begin() ) + { + if( !pNew ) { + pNew = new SwGrammarMarkUp(); + pNew->SetInvalid( 0, COMPLETE_STRING ); + } + pNew->maSentence.insert( pNew->maSentence.begin(), maSentence.begin(), pIter ); + maSentence.erase( maSentence.begin(), pIter ); + } + return pNew; +} + +void SwGrammarMarkUp::JoinGrammarList( SwGrammarMarkUp* pNext, sal_Int32 nInsertPos ) +{ + JoinList( pNext, nInsertPos ); + if (pNext) + { + if( pNext->maSentence.empty() ) + return; + for( auto& rPos : pNext->maSentence ) + { + rPos += nInsertPos; + } + maSentence.insert( maSentence.end(), pNext->maSentence.begin(), pNext->maSentence.end() ); + } +} + +void SwGrammarMarkUp::ClearGrammarList( sal_Int32 nSentenceEnd ) +{ + if( COMPLETE_STRING == nSentenceEnd ) { + ClearList(); + maSentence.clear(); + Validate(); + } else if( GetBeginInv() <= nSentenceEnd ) { + std::vector< sal_Int32 >::iterator pIter = maSentence.begin(); + sal_Int32 nStart = 0; + while( pIter != maSentence.end() && *pIter < GetBeginInv() ) + { + nStart = *pIter; + ++pIter; + } + auto pLast = std::find_if(pIter, maSentence.end(), + [nSentenceEnd](const sal_Int32& rPos) { return rPos > nSentenceEnd; }); + maSentence.erase( pIter, pLast ); + RemoveEntry( nStart, nSentenceEnd ); + SetInvalid( nSentenceEnd + 1, COMPLETE_STRING ); + } +} + +void SwGrammarMarkUp::setSentence( sal_Int32 nStart ) +{ + auto pIter = std::find_if(maSentence.begin(), maSentence.end(), + [nStart](const sal_Int32& rPos) { return rPos >= nStart; }); + if( pIter == maSentence.end() || *pIter > nStart ) + maSentence.insert( pIter, nStart ); +} + +sal_Int32 SwGrammarMarkUp::getSentenceStart( sal_Int32 nPos ) +{ + if( maSentence.empty() ) + return 0; + auto pIter = std::find_if(maSentence.begin(), maSentence.end(), + [nPos](const sal_Int32& rPos) { return rPos >= nPos; }); + if( pIter != maSentence.begin() ) + --pIter; + if( pIter != maSentence.end() && *pIter < nPos ) + return *pIter; + return 0; +} + +sal_Int32 SwGrammarMarkUp::getSentenceEnd( sal_Int32 nPos ) +{ + if( maSentence.empty() ) + return COMPLETE_STRING; + auto pIter = std::find_if(maSentence.begin(), maSentence.end(), + [nPos](const sal_Int32& rPos) { return rPos > nPos; }); + if( pIter != maSentence.end() ) + return *pIter; + return COMPLETE_STRING; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/atrhndl.hxx b/sw/source/core/text/atrhndl.hxx new file mode 100644 index 000000000..e391b0825 --- /dev/null +++ b/sw/source/core/text/atrhndl.hxx @@ -0,0 +1,121 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_ATRHNDL_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_ATRHNDL_HXX + +#define NUM_ATTRIBUTE_STACKS 44 + +#include <memory> +#include <vector> +#include <swfntcch.hxx> + +class SwTextAttr; +class SwAttrSet; +class IDocumentSettingAccess; +class SwViewShell; +class SfxPoolItem; +extern const sal_uInt8 StackPos[]; + +/** + * Used by Attribute Iterators to organize attributes on stacks to + * find the valid attribute in each category + */ +class SwAttrHandler +{ +private: + std::vector<const SwTextAttr*> m_aAttrStack[NUM_ATTRIBUTE_STACKS]; // stack collection + const SfxPoolItem* m_pDefaultArray[ NUM_DEFAULT_VALUES ]; + const IDocumentSettingAccess* m_pIDocumentSettingAccess; + const SwViewShell* m_pShell; + + // This is the base font for the paragraph. It is stored in order to have + // a template, if we have to restart the attribute evaluation + std::unique_ptr<SwFont> m_pFnt; + + bool m_bVertLayout; + bool m_bVertLayoutLRBT; + + const SwTextAttr* GetTop(sal_uInt16 nStack); + void RemoveFromStack(sal_uInt16 nWhich, const SwTextAttr& rAttr); + + // change font according to pool item + void FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ); + + // push attribute to specified stack, returns true, if attribute has + // been pushed on top of stack (important for stacks containing different + // attributes with different priority and redlining) + bool Push( const SwTextAttr& rAttr, const SfxPoolItem& rItem ); + + // apply top attribute on stack to font + void ActivateTop( SwFont& rFnt, sal_uInt16 nStackPos ); + +public: + // Ctor + SwAttrHandler(); + ~SwAttrHandler(); + + // set default attributes to values in rAttrSet or from cache + void Init( const SwAttrSet& rAttrSet, + const IDocumentSettingAccess& rIDocumentSettingAccess ); + void Init( const SfxPoolItem** pPoolItem, const SwAttrSet* pAttrSet, + const IDocumentSettingAccess& rIDocumentSettingAccess, + const SwViewShell* pShell, SwFont& rFnt, + bool bVertLayout, bool bVertLayoutLRBT ); + + bool IsVertLayout() const { return m_bVertLayout; } + + // remove everything from internal stacks, keep default data + void Reset( ); + + // insert specified attribute and change font + void PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt ); + + // remove specified attribute and reset font + void PopAndChg( const SwTextAttr& rAttr, SwFont& rFnt ); + void Pop( const SwTextAttr& rAttr ); + + // apply script dependent attributes + // void ChangeScript( SwFont& rFnt, const sal_uInt8 nScr ); + + // do not call these if you only used the small init function + inline void ResetFont( SwFont& rFnt ) const; + inline const SwFont* GetFont() const; + + void GetDefaultAscentAndHeight(SwViewShell const * pShell, + OutputDevice const & rOut, + sal_uInt16& nAscent, + sal_uInt16& nHeight) const; +}; + +inline void SwAttrHandler::ResetFont( SwFont& rFnt ) const +{ + OSL_ENSURE(m_pFnt, "ResetFont without a font"); + if (m_pFnt) + rFnt = *m_pFnt; +}; + +inline const SwFont* SwAttrHandler::GetFont() const +{ + return m_pFnt.get(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/atrstck.cxx b/sw/source/core/text/atrstck.cxx new file mode 100644 index 000000000..aa1b9f67e --- /dev/null +++ b/sw/source/core/text/atrstck.cxx @@ -0,0 +1,846 @@ +/* -*- 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 "atrhndl.hxx" +#include <svl/itemiter.hxx> +#include <vcl/outdev.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/charhiddenitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <viewopt.hxx> +#include <charfmt.hxx> +#include <fchrfmt.hxx> +#include <fmtautofmt.hxx> +#include <editeng/brushitem.hxx> +#include <fmtinfmt.hxx> +#include <txtinet.hxx> +#include <IDocumentSettingAccess.hxx> +#include <viewsh.hxx> + +/** + * Attribute to Stack Mapping + * + * Attributes applied to a text are pushed on different stacks. For each + * stack, the top most attribute on the stack is valid. Because some + * kinds of attributes have to be pushed to the same stacks we map their + * ids to stack ids + * Attention: The first NUM_DEFAULT_VALUES ( defined in swfntcch.hxx ) + * are stored in the defaultitem-cache, if you add one, you have to increase + * NUM_DEFAULT_VALUES. + * Also adjust NUM_ATTRIBUTE_STACKS in atrhndl.hxx. + */ +const sal_uInt8 StackPos[ RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN + 1 ] = +{ + 0, // // 0 + 1, // RES_CHRATR_CASEMAP = RES_CHRATR_BEGIN // 1 + 0, // RES_CHRATR_CHARSETCOLOR, // 2 + 2, // RES_CHRATR_COLOR, // 3 + 3, // RES_CHRATR_CONTOUR, // 4 + 4, // RES_CHRATR_CROSSEDOUT, // 5 + 5, // RES_CHRATR_ESCAPEMENT, // 6 + 6, // RES_CHRATR_FONT, // 7 + 7, // RES_CHRATR_FONTSIZE, // 8 + 8, // RES_CHRATR_KERNING, // 9 + 9, // RES_CHRATR_LANGUAGE, // 10 + 10, // RES_CHRATR_POSTURE, // 11 + 0, // RES_CHRATR_UNUSED1, // 12 + 11, // RES_CHRATR_SHADOWED, // 13 + 12, // RES_CHRATR_UNDERLINE, // 14 + 13, // RES_CHRATR_WEIGHT, // 15 + 14, // RES_CHRATR_WORDLINEMODE, // 16 + 15, // RES_CHRATR_AUTOKERN, // 17 + 16, // RES_CHRATR_BLINK, // 18 + 17, // RES_CHRATR_NOHYPHEN, // 19 + 0, // RES_CHRATR_UNUSED2, // 20 + 18, // RES_CHRATR_BACKGROUND, // 21 + 19, // RES_CHRATR_CJK_FONT, // 22 + 20, // RES_CHRATR_CJK_FONTSIZE, // 23 + 21, // RES_CHRATR_CJK_LANGUAGE, // 24 + 22, // RES_CHRATR_CJK_POSTURE, // 25 + 23, // RES_CHRATR_CJK_WEIGHT, // 26 + 24, // RES_CHRATR_CTL_FONT, // 27 + 25, // RES_CHRATR_CTL_FONTSIZE, // 28 + 26, // RES_CHRATR_CTL_LANGUAGE, // 29 + 27, // RES_CHRATR_CTL_POSTURE, // 30 + 28, // RES_CHRATR_CTL_WEIGHT, // 31 + 29, // RES_CHRATR_ROTATE, // 32 + 30, // RES_CHRATR_EMPHASIS_MARK, // 33 + 31, // RES_CHRATR_TWO_LINES, // 34 + 32, // RES_CHRATR_SCALEW, // 35 + 33, // RES_CHRATR_RELIEF, // 36 + 34, // RES_CHRATR_HIDDEN, // 37 + 35, // RES_CHRATR_OVERLINE, // 38 + 0, // RES_CHRATR_RSID, // 39 + 36, // RES_CHRATR_BOX, // 40 + 37, // RES_CHRATR_SHADOW, // 41 + 38, // RES_CHRATR_HIGHLIGHT, // 42 + 0, // RES_CHRATR_GRABBAG, // 43 + 0, // RES_CHRATR_BIDIRTL, // 44 + 0, // RES_CHRATR_IDCTHINT, // 45 + 39, // RES_TXTATR_REFMARK, // 46 + 40, // RES_TXTATR_TOXMARK, // 47 + 41, // RES_TXTATR_META, // 48 + 41, // RES_TXTATR_METAFIELD, // 49 + 0, // RES_TXTATR_AUTOFMT, // 50 + 0, // RES_TXTATR_INETFMT // 51 + 0, // RES_TXTATR_CHARFMT, // 52 + 42, // RES_TXTATR_CJK_RUBY, // 53 + 0, // RES_TXTATR_UNKNOWN_CONTAINER, // 54 + 43, // RES_TXTATR_INPUTFIELD // 55 +}; + +namespace CharFormat +{ + +/// Returns the item set associated with a character/inet/auto style +const SfxItemSet* GetItemSet( const SfxPoolItem& rAttr ) +{ + const SfxItemSet* pSet = nullptr; + + if ( RES_TXTATR_AUTOFMT == rAttr.Which() ) + { + pSet = static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle().get(); + } + else + { + // Get the attributes from the template + const SwCharFormat* pFormat = RES_TXTATR_INETFMT == rAttr.Which() ? + static_cast<const SwFormatINetFormat&>(rAttr).GetTextINetFormat()->GetCharFormat() : + static_cast<const SwFormatCharFormat&>(rAttr).GetCharFormat(); + if( pFormat ) + { + pSet = &pFormat->GetAttrSet(); + } + } + + return pSet; +} + +/// Extracts pool item of type nWhich from rAttr +const SfxPoolItem* GetItem( const SwTextAttr& rAttr, sal_uInt16 nWhich ) +{ + if ( RES_TXTATR_INETFMT == rAttr.Which() || + RES_TXTATR_CHARFMT == rAttr.Which() || + RES_TXTATR_AUTOFMT == rAttr.Which() ) + { + const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( !pSet ) return nullptr; + + bool bInParent = RES_TXTATR_AUTOFMT != rAttr.Which(); + const SfxPoolItem* pItem; + bool bRet = SfxItemState::SET == pSet->GetItemState( nWhich, bInParent, &pItem ); + + return bRet ? pItem : nullptr; + } + + return ( nWhich == rAttr.Which() ) ? &rAttr.GetAttr() : nullptr; +} + +/// Checks if item is included in character/inet/auto style +bool IsItemIncluded( const sal_uInt16 nWhich, const SwTextAttr *pAttr ) +{ + bool bRet = false; + + const SfxItemSet* pItemSet = CharFormat::GetItemSet( pAttr->GetAttr() ); + if ( pItemSet ) + bRet = SfxItemState::SET == pItemSet->GetItemState( nWhich ); + + return bRet; +} +} + +/** + * The color of hyperlinks is taken from the associated character attribute, + * depending on its 'visited' state. There are actually two cases, which + * should override the colors from the character attribute: + * 1. We never take the 'visited' color during printing/pdf export/preview + * 2. The user has chosen to override these colors in the view options + */ +static bool lcl_ChgHyperLinkColor( const SwTextAttr& rAttr, + const SfxPoolItem& rItem, + const SwViewShell* pShell, + Color* pColor ) +{ + if ( !pShell || + RES_TXTATR_INETFMT != rAttr.Which() || + RES_CHRATR_COLOR != rItem.Which() ) + return false; + + // #i15455# + // 1. case: + // We do not want to show visited links: + // (printing, pdf export, page preview) + + SwTextINetFormat & rINetAttr(const_cast<SwTextINetFormat&>( + static_txtattr_cast<SwTextINetFormat const&>(rAttr))); + if ( pShell->GetOut()->GetOutDevType() == OUTDEV_PRINTER || + pShell->GetViewOptions()->IsPDFExport() || + pShell->GetViewOptions()->IsPagePreview() ) + { + if (rINetAttr.IsVisited()) + { + if ( pColor ) + { + // take color from character format 'unvisited link' + rINetAttr.SetVisited(false); + const SwCharFormat* pTmpFormat = rINetAttr.GetCharFormat(); + const SfxPoolItem* pItem; + if (SfxItemState::SET == pTmpFormat->GetItemState(RES_CHRATR_COLOR, true, &pItem)) + *pColor = static_cast<const SvxColorItem*>(pItem)->GetValue(); + rINetAttr.SetVisited(true); + } + return true; + } + + return false; + } + + // 2. case: + // We do not want to apply the color set in the hyperlink + // attribute, instead we take the colors from the view options: + + if ( pShell->GetWin() && + ( + (rINetAttr.IsVisited() && SwViewOption::IsVisitedLinks()) || + (!rINetAttr.IsVisited() && SwViewOption::IsLinks()) + ) + ) + { + if ( pColor ) + { + if (rINetAttr.IsVisited()) + { + // take color from view option 'visited link color' + *pColor = SwViewOption::GetVisitedLinksColor(); + } + else + { + // take color from view option 'unvisited link color' + *pColor = SwViewOption::GetLinksColor(); + } + } + return true; + } + + return false; +} + +SwAttrHandler::SwAttrHandler() + : m_pIDocumentSettingAccess(nullptr) + , m_pShell(nullptr) + , m_bVertLayout(false) + , m_bVertLayoutLRBT(false) +{ + memset( m_pDefaultArray, 0, NUM_DEFAULT_VALUES * sizeof(SfxPoolItem*) ); +} + +SwAttrHandler::~SwAttrHandler() +{ +} + +void SwAttrHandler::Init( const SwAttrSet& rAttrSet, + const IDocumentSettingAccess& rIDocumentSettingAcces ) +{ + m_pIDocumentSettingAccess = &rIDocumentSettingAcces; + m_pShell = nullptr; + + for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++ ) + m_pDefaultArray[ StackPos[ i ] ] = &rAttrSet.Get( i ); +} + +void SwAttrHandler::Init( const SfxPoolItem** pPoolItem, const SwAttrSet* pAS, + const IDocumentSettingAccess& rIDocumentSettingAcces, + const SwViewShell* pSh, + SwFont& rFnt, bool bVL, bool bVertLayoutLRBT ) +{ + // initialize default array + memcpy( m_pDefaultArray, pPoolItem, + NUM_DEFAULT_VALUES * sizeof(SfxPoolItem*) ); + + m_pIDocumentSettingAccess = &rIDocumentSettingAcces; + m_pShell = pSh; + + // do we have to apply additional paragraph attributes? + m_bVertLayout = bVL; + m_bVertLayoutLRBT = bVertLayoutLRBT; + + if ( pAS && pAS->Count() ) + { + SfxItemIter aIter( *pAS ); + sal_uInt16 nWhich; + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + nWhich = pItem->Which(); + if (isCHRATR(nWhich)) + { + m_pDefaultArray[ StackPos[ nWhich ] ] = pItem; + FontChg( *pItem, rFnt, true ); + } + + pItem = aIter.NextItem(); + } while (pItem); + } + + // It is possible, that Init is called more than once, e.g., in a + // SwTextFrame::FormatOnceMore situation or (since sw_redlinehide) + // from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt + // is an alias of m_pFnt so it must not be deleted! + if (m_pFnt) + { + *m_pFnt = rFnt; + } + else + { + m_pFnt.reset(new SwFont(rFnt)); + } +} + +void SwAttrHandler::Reset( ) +{ + for (auto& i : m_aAttrStack) + i.clear(); +} + +void SwAttrHandler::PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) +{ + // these special attributes in fact represent a collection of attributes + // they have to be pushed to each stack they belong to + if ( RES_TXTATR_INETFMT == rAttr.Which() || + RES_TXTATR_CHARFMT == rAttr.Which() || + RES_TXTATR_AUTOFMT == rAttr.Which() ) + { + const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( !pSet ) return; + + for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++) + { + const SfxPoolItem* pItem; + bool bRet = SfxItemState::SET == pSet->GetItemState( i, rAttr.Which() != RES_TXTATR_AUTOFMT, &pItem ); + + if ( bRet ) + { + // we push rAttr onto the appropriate stack + if ( Push( rAttr, *pItem ) ) + { + // we let pItem change rFnt + Color aColor; + if (lcl_ChgHyperLinkColor(rAttr, *pItem, m_pShell, &aColor)) + { + SvxColorItem aItemNext( aColor, RES_CHRATR_COLOR ); + FontChg( aItemNext, rFnt, true ); + } + else + FontChg( *pItem, rFnt, true ); + } + } + } + } + // this is the usual case, we have a basic attribute, push it onto the + // stack and change the font + else + { + if ( Push( rAttr, rAttr.GetAttr() ) ) + // we let pItem change rFnt + FontChg( rAttr.GetAttr(), rFnt, true ); + } +} + +const SwTextAttr* SwAttrHandler::GetTop(sal_uInt16 nStack) +{ + return m_aAttrStack[nStack].empty() ? nullptr : m_aAttrStack[nStack].back(); +} + +bool SwAttrHandler::Push( const SwTextAttr& rAttr, const SfxPoolItem& rItem ) +{ + OSL_ENSURE( rItem.Which() < RES_TXTATR_WITHEND_END, + "I do not want this attribute, nWhich >= RES_TXTATR_WITHEND_END" ); + + // robust + if ( RES_TXTATR_WITHEND_END <= rItem.Which() ) + return false; + + const sal_uInt16 nStack = StackPos[ rItem.Which() ]; + + // attributes originating from redlining have highest priority + // second priority are hyperlink attributes, which have a color replacement + const SwTextAttr* pTopAttr = GetTop(nStack); + if ( !pTopAttr + || rAttr.IsPriorityAttr() + || ( !pTopAttr->IsPriorityAttr() + && !lcl_ChgHyperLinkColor(*pTopAttr, rItem, m_pShell, nullptr))) + { + m_aAttrStack[nStack].push_back(&rAttr); + return true; + } + + const auto it = m_aAttrStack[nStack].end() - 1; + m_aAttrStack[nStack].insert(it, &rAttr); + return false; +} + +void SwAttrHandler::RemoveFromStack(sal_uInt16 nWhich, const SwTextAttr& rAttr) +{ + auto& rStack = m_aAttrStack[StackPos[nWhich]]; + const auto it = std::find(rStack.begin(), rStack.end(), &rAttr); + if (it != rStack.end()) + rStack.erase(it); +} + +void SwAttrHandler::PopAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) +{ + if ( RES_TXTATR_WITHEND_END <= rAttr.Which() ) + return; // robust + + // these special attributes in fact represent a collection of attributes + // they have to be removed from each stack they belong to + if ( RES_TXTATR_INETFMT == rAttr.Which() || + RES_TXTATR_CHARFMT == rAttr.Which() || + RES_TXTATR_AUTOFMT == rAttr.Which() ) + { + const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); + if ( !pSet ) return; + + for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++) + { + const SfxPoolItem* pItem; + bool bRet = SfxItemState::SET == pSet->GetItemState( i, RES_TXTATR_AUTOFMT != rAttr.Which(), &pItem ); + if ( bRet ) + { + // we remove rAttr from the appropriate stack + RemoveFromStack(i, rAttr); + // reset font according to attribute on top of stack + // or default value + ActivateTop( rFnt, i ); + } + } + } + // this is the usual case, we have a basic attribute, remove it from the + // stack and reset the font + else + { + RemoveFromStack(rAttr.Which(), rAttr); + // reset font according to attribute on top of stack + // or default value + ActivateTop( rFnt, rAttr.Which() ); + } +} + +/// Only used during redlining +void SwAttrHandler::Pop( const SwTextAttr& rAttr ) +{ + OSL_ENSURE( rAttr.Which() < RES_TXTATR_WITHEND_END, + "I do not have this attribute, nWhich >= RES_TXTATR_WITHEND_END" ); + + if ( rAttr.Which() < RES_TXTATR_WITHEND_END ) + { + RemoveFromStack(rAttr.Which(), rAttr); + } +} + +void SwAttrHandler::ActivateTop( SwFont& rFnt, const sal_uInt16 nAttr ) +{ + OSL_ENSURE( nAttr < RES_TXTATR_WITHEND_END, + "I cannot activate this attribute, nWhich >= RES_TXTATR_WITHEND_END" ); + + const sal_uInt16 nStackPos = StackPos[ nAttr ]; + const SwTextAttr* pTopAt = GetTop(nStackPos); + if ( pTopAt ) + { + const SfxPoolItem* pItemNext(nullptr); + + // check if top attribute is collection of attributes + if ( RES_TXTATR_INETFMT == pTopAt->Which() || + RES_TXTATR_CHARFMT == pTopAt->Which() || + RES_TXTATR_AUTOFMT == pTopAt->Which() ) + { + const SfxItemSet* pSet = CharFormat::GetItemSet( pTopAt->GetAttr() ); + if (pSet) + pSet->GetItemState( nAttr, RES_TXTATR_AUTOFMT != pTopAt->Which(), &pItemNext ); + } + + if (pItemNext) + { + Color aColor; + if (lcl_ChgHyperLinkColor(*pTopAt, *pItemNext, m_pShell, &aColor)) + { + SvxColorItem aItemNext( aColor, RES_CHRATR_COLOR ); + FontChg( aItemNext, rFnt, false ); + } + else + FontChg( *pItemNext, rFnt, false ); + } + else + FontChg( pTopAt->GetAttr(), rFnt, false ); + } + + // default value has to be set, we only have default values for char attribs + else if ( nStackPos < NUM_DEFAULT_VALUES ) + FontChg( *m_pDefaultArray[ nStackPos ], rFnt, false ); + else if ( RES_TXTATR_REFMARK == nAttr ) + rFnt.GetRef()--; + else if ( RES_TXTATR_TOXMARK == nAttr ) + rFnt.GetTox()--; + else if ( (RES_TXTATR_META == nAttr) || (RES_TXTATR_METAFIELD == nAttr) ) + { + rFnt.GetMeta()--; + } + else if ( RES_TXTATR_CJK_RUBY == nAttr ) + { + // ruby stack has no more attributes + // check, if a rotation attribute has to be applied + const sal_uInt16 nTwoLineStack = StackPos[ RES_CHRATR_TWO_LINES ]; + bool bTwoLineAct = false; + const SwTextAttr* pTwoLineAttr = GetTop(nTwoLineStack); + + if ( pTwoLineAttr ) + { + const SfxPoolItem* pTwoLineItem = CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES ); + bTwoLineAct = static_cast<const SvxTwoLinesItem*>(pTwoLineItem)->GetValue(); + } + else + bTwoLineAct = + static_cast<const SvxTwoLinesItem*>(m_pDefaultArray[ nTwoLineStack ])->GetValue(); + + if ( bTwoLineAct ) + return; + + // eventually, a rotate attribute has to be activated + const sal_uInt16 nRotateStack = StackPos[ RES_CHRATR_ROTATE ]; + const SwTextAttr* pRotateAttr = GetTop(nRotateStack); + + if ( pRotateAttr ) + { + const SfxPoolItem* pRotateItem = CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE ); + rFnt.SetVertical( static_cast<const SvxCharRotateItem*>(pRotateItem)->GetValue(), + m_bVertLayout ); + } + else + rFnt.SetVertical( + static_cast<const SvxCharRotateItem*>(m_pDefaultArray[ nRotateStack ])->GetValue(), + m_bVertLayout + ); + } + else if ( RES_TXTATR_INPUTFIELD == nAttr ) + rFnt.GetInputField()--; +} + +/** + * When popping an attribute from the stack, the top more remaining + * attribute in the stack becomes valid. The following function change + * a font depending on the stack id. + */ +void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) +{ + switch ( rItem.Which() ) + { + case RES_CHRATR_CASEMAP : + rFnt.SetCaseMap( static_cast<const SvxCaseMapItem&>(rItem).GetCaseMap() ); + break; + case RES_CHRATR_COLOR : + rFnt.SetColor( static_cast<const SvxColorItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_CONTOUR : + rFnt.SetOutline( static_cast<const SvxContourItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_CROSSEDOUT : + rFnt.SetStrikeout( static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout() ); + break; + case RES_CHRATR_ESCAPEMENT : + rFnt.SetEscapement( static_cast<const SvxEscapementItem&>(rItem).GetEsc() ); + rFnt.SetProportion( static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight() ); + break; + case RES_CHRATR_FONT : + rFnt.SetName( static_cast<const SvxFontItem&>(rItem).GetFamilyName(), SwFontScript::Latin ); + rFnt.SetStyleName( static_cast<const SvxFontItem&>(rItem).GetStyleName(), SwFontScript::Latin ); + rFnt.SetFamily( static_cast<const SvxFontItem&>(rItem).GetFamily(), SwFontScript::Latin ); + rFnt.SetPitch( static_cast<const SvxFontItem&>(rItem).GetPitch(), SwFontScript::Latin ); + rFnt.SetCharSet( static_cast<const SvxFontItem&>(rItem).GetCharSet(), SwFontScript::Latin ); + break; + case RES_CHRATR_FONTSIZE : + rFnt.SetSize(Size(0,static_cast<const SvxFontHeightItem&>(rItem).GetHeight() ), SwFontScript::Latin ); + break; + case RES_CHRATR_KERNING : + rFnt.SetFixKerning( static_cast<const SvxKerningItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_LANGUAGE : + rFnt.SetLanguage( static_cast<const SvxLanguageItem&>(rItem).GetLanguage(), SwFontScript::Latin ); + break; + case RES_CHRATR_POSTURE : + rFnt.SetItalic( static_cast<const SvxPostureItem&>(rItem).GetPosture(), SwFontScript::Latin ); + break; + case RES_CHRATR_SHADOWED : + rFnt.SetShadow( static_cast<const SvxShadowedItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_UNDERLINE : + { + const sal_uInt16 nStackPos = StackPos[ RES_CHRATR_HIDDEN ]; + const SwTextAttr* pTopAt = GetTop(nStackPos); + + const SfxPoolItem* pTmpItem = pTopAt ? + CharFormat::GetItem( *pTopAt, RES_CHRATR_HIDDEN ) : + m_pDefaultArray[ nStackPos ]; + + if ((m_pShell && !m_pShell->GetWin()) || + (pTmpItem && !static_cast<const SvxCharHiddenItem*>(pTmpItem)->GetValue()) ) + { + rFnt.SetUnderline( static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle() ); + rFnt.SetUnderColor( static_cast<const SvxUnderlineItem&>(rItem).GetColor() ); + } + break; + } + case RES_CHRATR_BOX: + { + const SvxBoxItem& aBoxItem = static_cast<const SvxBoxItem&>(rItem); + rFnt.SetTopBorder( aBoxItem.GetTop() ); + rFnt.SetBottomBorder( aBoxItem.GetBottom() ); + rFnt.SetRightBorder( aBoxItem.GetRight() ); + rFnt.SetLeftBorder( aBoxItem.GetLeft() ); + rFnt.SetTopBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::TOP) ); + rFnt.SetBottomBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::BOTTOM) ); + rFnt.SetRightBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::RIGHT) ); + rFnt.SetLeftBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::LEFT) ); + break; + } + case RES_CHRATR_SHADOW: + { + const SvxShadowItem& aShadowItem = static_cast<const SvxShadowItem&>(rItem); + rFnt.SetShadowColor( aShadowItem.GetColor() ); + rFnt.SetShadowWidth( aShadowItem.GetWidth() ); + rFnt.SetShadowLocation( aShadowItem.GetLocation() ); + break; + } + case RES_CHRATR_OVERLINE : + rFnt.SetOverline( static_cast<const SvxOverlineItem&>(rItem).GetLineStyle() ); + rFnt.SetOverColor( static_cast<const SvxOverlineItem&>(rItem).GetColor() ); + break; + case RES_CHRATR_WEIGHT : + rFnt.SetWeight( static_cast<const SvxWeightItem&>(rItem).GetWeight(), SwFontScript::Latin ); + break; + case RES_CHRATR_WORDLINEMODE : + rFnt.SetWordLineMode( static_cast<const SvxWordLineModeItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_AUTOKERN : + if( static_cast<const SvxAutoKernItem&>(rItem).GetValue() ) + { + rFnt.SetAutoKern( (!m_pIDocumentSettingAccess || + !m_pIDocumentSettingAccess->get(DocumentSettingId::KERN_ASIAN_PUNCTUATION)) ? + FontKerning::FontSpecific : + FontKerning::Asian ); + } + else + rFnt.SetAutoKern( FontKerning::NONE ); + break; + case RES_CHRATR_BACKGROUND : + rFnt.SetBackColor(new Color( static_cast<const SvxBrushItem&>(rItem).GetColor() ) ); + break; + case RES_CHRATR_HIGHLIGHT : + rFnt.SetHighlightColor( static_cast<const SvxBrushItem&>(rItem).GetColor() ); + break; + case RES_CHRATR_CJK_FONT : + rFnt.SetName( static_cast<const SvxFontItem&>(rItem).GetFamilyName(), SwFontScript::CJK ); + rFnt.SetStyleName( static_cast<const SvxFontItem&>(rItem).GetStyleName(), SwFontScript::CJK ); + rFnt.SetFamily( static_cast<const SvxFontItem&>(rItem).GetFamily(), SwFontScript::CJK ); + rFnt.SetPitch( static_cast<const SvxFontItem&>(rItem).GetPitch(), SwFontScript::CJK ); + rFnt.SetCharSet( static_cast<const SvxFontItem&>(rItem).GetCharSet(), SwFontScript::CJK ); + break; + case RES_CHRATR_CJK_FONTSIZE : + rFnt.SetSize(Size( 0, static_cast<const SvxFontHeightItem&>(rItem).GetHeight()), SwFontScript::CJK); + break; + case RES_CHRATR_CJK_LANGUAGE : + rFnt.SetLanguage( static_cast<const SvxLanguageItem&>(rItem).GetLanguage(), SwFontScript::CJK ); + break; + case RES_CHRATR_CJK_POSTURE : + rFnt.SetItalic( static_cast<const SvxPostureItem&>(rItem).GetPosture(), SwFontScript::CJK ); + break; + case RES_CHRATR_CJK_WEIGHT : + rFnt.SetWeight( static_cast<const SvxWeightItem&>(rItem).GetWeight(), SwFontScript::CJK ); + break; + case RES_CHRATR_CTL_FONT : + rFnt.SetName( static_cast<const SvxFontItem&>(rItem).GetFamilyName(), SwFontScript::CTL ); + rFnt.SetStyleName( static_cast<const SvxFontItem&>(rItem).GetStyleName(), SwFontScript::CTL ); + rFnt.SetFamily( static_cast<const SvxFontItem&>(rItem).GetFamily(), SwFontScript::CTL ); + rFnt.SetPitch( static_cast<const SvxFontItem&>(rItem).GetPitch(), SwFontScript::CTL ); + rFnt.SetCharSet( static_cast<const SvxFontItem&>(rItem).GetCharSet(), SwFontScript::CTL ); + break; + case RES_CHRATR_CTL_FONTSIZE : + rFnt.SetSize(Size(0, static_cast<const SvxFontHeightItem&>(rItem).GetHeight() ), SwFontScript::CTL); + break; + case RES_CHRATR_CTL_LANGUAGE : + rFnt.SetLanguage( static_cast<const SvxLanguageItem&>(rItem).GetLanguage(), SwFontScript::CTL ); + break; + case RES_CHRATR_CTL_POSTURE : + rFnt.SetItalic( static_cast<const SvxPostureItem&>(rItem).GetPosture(), SwFontScript::CTL ); + break; + case RES_CHRATR_CTL_WEIGHT : + rFnt.SetWeight( static_cast<const SvxWeightItem&>(rItem).GetWeight(), SwFontScript::CTL ); + break; + case RES_CHRATR_EMPHASIS_MARK : + rFnt.SetEmphasisMark( + static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark() + ); + break; + case RES_CHRATR_SCALEW : + rFnt.SetPropWidth( static_cast<const SvxCharScaleWidthItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_RELIEF : + rFnt.SetRelief( static_cast<const SvxCharReliefItem&>(rItem).GetValue() ); + break; + case RES_CHRATR_HIDDEN : + if (m_pShell && m_pShell->GetWin()) + { + if ( static_cast<const SvxCharHiddenItem&>(rItem).GetValue() ) + rFnt.SetUnderline( LINESTYLE_DOTTED ); + else + ActivateTop( rFnt, RES_CHRATR_UNDERLINE ); + } + break; + case RES_CHRATR_ROTATE : + { + // rotate attribute is applied, when: + // 1. ruby stack is empty and + // 2. top of two line stack ( or default attribute )is an + // deactivated two line attribute + const bool bRuby = + 0 != m_aAttrStack[ StackPos[ RES_TXTATR_CJK_RUBY ] ].size(); + + if ( bRuby ) + break; + + const sal_uInt16 nTwoLineStack = StackPos[ RES_CHRATR_TWO_LINES ]; + bool bTwoLineAct = false; + const SwTextAttr* pTwoLineAttr = GetTop(nTwoLineStack); + + if ( pTwoLineAttr ) + { + const SfxPoolItem* pTwoLineItem = CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES ); + bTwoLineAct = static_cast<const SvxTwoLinesItem*>(pTwoLineItem)->GetValue(); + } + else + bTwoLineAct = + static_cast<const SvxTwoLinesItem*>(m_pDefaultArray[ nTwoLineStack ])->GetValue(); + + if ( !bTwoLineAct ) + rFnt.SetVertical( static_cast<const SvxCharRotateItem&>(rItem).GetValue(), + m_bVertLayout, m_bVertLayoutLRBT ); + + break; + } + case RES_CHRATR_TWO_LINES : + { + bool bRuby = 0 != + m_aAttrStack[ StackPos[ RES_TXTATR_CJK_RUBY ] ].size(); + + // two line is activated, if + // 1. no ruby attribute is set and + // 2. attribute is active + if ( !bRuby && static_cast<const SvxTwoLinesItem&>(rItem).GetValue() ) + { + rFnt.SetVertical( 0, m_bVertLayout ); + break; + } + + // a deactivating two line attribute is on top of stack, + // check if rotate attribute has to be enabled + if ( bRuby ) + break; + + const sal_uInt16 nRotateStack = StackPos[ RES_CHRATR_ROTATE ]; + const SwTextAttr* pRotateAttr = GetTop(nRotateStack); + + if ( pRotateAttr ) + { + const SfxPoolItem* pRotateItem = CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE ); + rFnt.SetVertical( static_cast<const SvxCharRotateItem*>(pRotateItem)->GetValue(), + m_bVertLayout ); + } + else + rFnt.SetVertical( + static_cast<const SvxCharRotateItem*>(m_pDefaultArray[ nRotateStack ])->GetValue(), + m_bVertLayout + ); + break; + } + case RES_TXTATR_CJK_RUBY : + rFnt.SetVertical( 0, m_bVertLayout ); + break; + case RES_TXTATR_REFMARK : + if ( bPush ) + rFnt.GetRef()++; + else + rFnt.GetRef()--; + break; + case RES_TXTATR_TOXMARK : + if ( bPush ) + rFnt.GetTox()++; + else + rFnt.GetTox()--; + break; + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + if ( bPush ) + rFnt.GetMeta()++; + else + rFnt.GetMeta()--; + break; + case RES_TXTATR_INPUTFIELD : + if ( bPush ) + rFnt.GetInputField()++; + else + rFnt.GetInputField()--; + break; + } +} + +/// Takes the default font and calculated the ascent and height +void SwAttrHandler::GetDefaultAscentAndHeight( SwViewShell const * pShell, OutputDevice const & rOut, + sal_uInt16& nAscent, sal_uInt16& nHeight ) const +{ + OSL_ENSURE(m_pFnt, "No font available for GetDefaultAscentAndHeight"); + + if (m_pFnt) + { + SwFont aFont( *m_pFnt ); + nHeight = aFont.GetHeight( pShell, rOut ); + nAscent = aFont.GetAscent( pShell, rOut ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/frmcrsr.cxx b/sw/source/core/text/frmcrsr.cxx new file mode 100644 index 000000000..9fd60520c --- /dev/null +++ b/sw/source/core/text/frmcrsr.cxx @@ -0,0 +1,1685 @@ +/* -*- 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 <ndtxt.hxx> +#include <pam.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <paratr.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <colfrm.hxx> +#include <swtypes.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lspcitem.hxx> +#include "pormulti.hxx" +#include <doc.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <sortedobjs.hxx> + +#include <unicode/ubidi.h> + +#include <txtfrm.hxx> +#include "inftxt.hxx" +#include "itrtxt.hxx" +#include <crstate.hxx> +#include <viewsh.hxx> +#include <swfntcch.hxx> +#include <flyfrm.hxx> + +#define MIN_OFFSET_STEP 10 + +using namespace ::com::sun::star; + +/* + * - SurvivalKit: For how long do we get past the last char of the line. + * - RightMargin abstains from adjusting position with -1 + * - GetCharRect returns a GetEndCharRect for CursorMoveState::RightMargin + * - GetEndCharRect sets bRightMargin to true + * - SwTextCursor::bRightMargin is set to false by CharCursorToLine + */ + +namespace +{ + +SwTextFrame *GetAdjFrameAtPos( SwTextFrame *pFrame, const SwPosition &rPos, + const bool bRightMargin, const bool bNoScroll = true ) +{ + // RightMargin in the last master line + TextFrameIndex const nOffset = pFrame->MapModelToViewPos(rPos); + SwTextFrame *pFrameAtPos = pFrame; + if( !bNoScroll || pFrame->GetFollow() ) + { + pFrameAtPos = pFrame->GetFrameAtPos( rPos ); + if (nOffset < pFrameAtPos->GetOffset() && + !pFrameAtPos->IsFollow() ) + { + assert(pFrameAtPos->MapModelToViewPos(rPos) == nOffset); + TextFrameIndex nNew(nOffset); + if (nNew < TextFrameIndex(MIN_OFFSET_STEP)) + nNew = TextFrameIndex(0); + else + nNew -= TextFrameIndex(MIN_OFFSET_STEP); + sw_ChangeOffset( pFrameAtPos, nNew ); + } + } + while( pFrame != pFrameAtPos ) + { + pFrame = pFrameAtPos; + pFrame->GetFormatted(); + pFrameAtPos = pFrame->GetFrameAtPos( rPos ); + } + + if( nOffset && bRightMargin ) + { + while (pFrameAtPos && + pFrameAtPos->MapViewToModelPos(pFrameAtPos->GetOffset()) == rPos && + pFrameAtPos->IsFollow() ) + { + pFrameAtPos->GetFormatted(); + pFrameAtPos = pFrameAtPos->FindMaster(); + } + OSL_ENSURE( pFrameAtPos, "+GetCharRect: no frame with my rightmargin" ); + } + return pFrameAtPos ? pFrameAtPos : pFrame; +} + +} + +bool sw_ChangeOffset(SwTextFrame* pFrame, TextFrameIndex nNew) +{ + // Do not scroll in areas and outside of flies + OSL_ENSURE( !pFrame->IsFollow(), "Illegal Scrolling by Follow!" ); + if( pFrame->GetOffset() != nNew && !pFrame->IsInSct() ) + { + SwFlyFrame *pFly = pFrame->FindFlyFrame(); + // Attention: if e.g. in a column frame the size is still invalid + // we must not scroll around just like that + if ( ( pFly && pFly->isFrameAreaDefinitionValid() && + !pFly->GetNextLink() && !pFly->GetPrevLink() ) || + ( !pFly && pFrame->IsInTab() ) ) + { + SwViewShell* pVsh = pFrame->getRootFrame()->GetCurrShell(); + if( pVsh ) + { + if( pVsh->GetRingContainer().size() > 1 || + ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) ) + { + if( !pFrame->GetOffset() ) + return false; + nNew = TextFrameIndex(0); + } + pFrame->SetOffset( nNew ); + pFrame->SetPara( nullptr ); + pFrame->GetFormatted(); + if( pFrame->getFrameArea().HasArea() ) + pFrame->getRootFrame()->GetCurrShell()->InvalidateWindows( pFrame->getFrameArea() ); + return true; + } + } + } + return false; +} + +SwTextFrame& SwTextFrame::GetFrameAtOfst(TextFrameIndex const nWhere) +{ + SwTextFrame* pRet = this; + while( pRet->HasFollow() && nWhere >= pRet->GetFollow()->GetOffset() ) + pRet = pRet->GetFollow(); + return *pRet; +} + +SwTextFrame *SwTextFrame::GetFrameAtPos( const SwPosition &rPos ) +{ + TextFrameIndex const nPos(MapModelToViewPos(rPos)); + SwTextFrame *pFoll = this; + while( pFoll->GetFollow() ) + { + if (nPos > pFoll->GetFollow()->GetOffset()) + pFoll = pFoll->GetFollow(); + else + { + if (nPos == pFoll->GetFollow()->GetOffset() + && !SwTextCursor::IsRightMargin() ) + pFoll = pFoll->GetFollow(); + else + break; + } + } + return pFoll; +} + +/* + * GetCharRect() returns the char's char line described by aPos. + * GetModelPositionForViewPoint() does the reverse: It goes from a document coordinate to + * a Pam. + * Both are virtual in the frame base class and thus are redefined here. + */ + +bool SwTextFrame::GetCharRect( SwRect& rOrig, const SwPosition &rPos, + SwCursorMoveState *pCMS, bool bAllowFarAway ) const +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::GetCharRect with swapped frame" ); + + if( IsLocked() || IsHiddenNow() ) + return false; + + // Find the right frame first. We need to keep in mind that: + // - the cached information could be invalid (GetPara() == 0) + // - we could have a Follow + // - the Follow chain grows dynamically; the one we end up in + // needs to be formatted + + // Optimisation: reading ahead saves us a GetAdjFrameAtPos + const bool bRightMargin = pCMS && ( CursorMoveState::RightMargin == pCMS->m_eState ); + const bool bNoScroll = pCMS && pCMS->m_bNoScroll; + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), rPos, bRightMargin, + bNoScroll ); + pFrame->GetFormatted(); + + const SwFrame* pTmpFrame = pFrame->GetUpper(); + if (pTmpFrame->getFrameArea().Top() == FAR_AWAY && !bAllowFarAway) + return false; + + SwRectFnSet aRectFnSet(pFrame); + const SwTwips nUpperMaxY = aRectFnSet.GetPrtBottom(*pTmpFrame); + const SwTwips nFrameMaxY = aRectFnSet.GetPrtBottom(*pFrame); + + // nMaxY is an absolute value + SwTwips nMaxY = aRectFnSet.IsVert() ? + ( aRectFnSet.IsVertL2R() ? std::min( nFrameMaxY, nUpperMaxY ) : std::max( nFrameMaxY, nUpperMaxY ) ) : + std::min( nFrameMaxY, nUpperMaxY ); + + bool bRet = false; + + if ( pFrame->IsEmpty() || ! aRectFnSet.GetHeight(pFrame->getFramePrintArea()) ) + { + Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); + SwTextNode const*const pTextNd(GetTextNodeForParaProps()); + short nFirstOffset; + pTextNd->GetFirstLineOfsWithNum( nFirstOffset ); + + Point aPnt2; + if ( aRectFnSet.IsVert() ) + { + if( nFirstOffset > 0 ) + aPnt1.AdjustY(nFirstOffset ); + if ( aPnt1.X() < nMaxY && !aRectFnSet.IsVertL2R() ) + aPnt1.setX( nMaxY ); + aPnt2.setX( aPnt1.X() + pFrame->getFramePrintArea().Width() ); + aPnt2.setY( aPnt1.Y() ); + if( aPnt2.X() < nMaxY ) + aPnt2.setX( nMaxY ); + } + else + { + if( nFirstOffset > 0 ) + aPnt1.AdjustX(nFirstOffset ); + + if( aPnt1.Y() > nMaxY ) + aPnt1.setY( nMaxY ); + aPnt2.setX( aPnt1.X() ); + aPnt2.setY( aPnt1.Y() + pFrame->getFramePrintArea().Height() ); + if( aPnt2.Y() > nMaxY ) + aPnt2.setY( nMaxY ); + } + + rOrig = SwRect( aPnt1, aPnt2 ); + + if ( pCMS ) + { + pCMS->m_aRealHeight.setX( 0 ); + pCMS->m_aRealHeight.setY( aRectFnSet.IsVert() ? -rOrig.Width() : rOrig.Height() ); + } + + if ( pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( rOrig ); + + bRet = true; + } + else + { + if( !pFrame->HasPara() ) + return false; + + SwFrameSwapper aSwapper( pFrame, true ); + if ( aRectFnSet.IsVert() ) + nMaxY = pFrame->SwitchVerticalToHorizontal( nMaxY ); + + bool bGoOn = true; + TextFrameIndex const nOffset = MapModelToViewPos(rPos); + assert(nOffset != TextFrameIndex(COMPLETE_STRING)); // not going to end well + TextFrameIndex nNextOfst; + + do + { + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + nNextOfst = aLine.GetEnd(); + // See comment in AdjustFrame + // Include the line's last char? + if (bRightMargin) + aLine.GetEndCharRect( &rOrig, nOffset, pCMS, nMaxY ); + else + aLine.GetCharRect( &rOrig, nOffset, pCMS, nMaxY ); + bRet = true; + } + + if ( pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( rOrig ); + + if ( aRectFnSet.IsVert() ) + pFrame->SwitchHorizontalToVertical( rOrig ); + + if( pFrame->IsUndersized() && pCMS && !pFrame->GetNext() && + aRectFnSet.GetBottom(rOrig) == nUpperMaxY && + pFrame->GetOffset() < nOffset && + !pFrame->IsFollow() && !bNoScroll && + TextFrameIndex(pFrame->GetText().getLength()) != nNextOfst) + { + bGoOn = sw_ChangeOffset( pFrame, nNextOfst ); + } + else + bGoOn = false; + } while ( bGoOn ); + + if ( pCMS ) + { + if ( pFrame->IsRightToLeft() ) + { + if( pCMS->m_b2Lines && pCMS->m_p2Lines) + { + pFrame->SwitchLTRtoRTL( pCMS->m_p2Lines->aLine ); + pFrame->SwitchLTRtoRTL( pCMS->m_p2Lines->aPortion ); + } + } + + if ( aRectFnSet.IsVert() ) + { + if ( pCMS->m_bRealHeight ) + { + pCMS->m_aRealHeight.setY( -pCMS->m_aRealHeight.Y() ); + if ( pCMS->m_aRealHeight.Y() < 0 ) + { + // writing direction is from top to bottom + pCMS->m_aRealHeight.setX( rOrig.Width() - + pCMS->m_aRealHeight.X() + + pCMS->m_aRealHeight.Y() ); + } + } + if( pCMS->m_b2Lines && pCMS->m_p2Lines) + { + pFrame->SwitchHorizontalToVertical( pCMS->m_p2Lines->aLine ); + pFrame->SwitchHorizontalToVertical( pCMS->m_p2Lines->aPortion ); + } + } + + } + } + if( bRet ) + { + SwPageFrame *pPage = pFrame->FindPageFrame(); + OSL_ENSURE( pPage, "Text escaped from page?" ); + const SwTwips nOrigTop = aRectFnSet.GetTop(rOrig); + const SwTwips nPageTop = aRectFnSet.GetTop(pPage->getFrameArea()); + const SwTwips nPageBott = aRectFnSet.GetBottom(pPage->getFrameArea()); + + // We have the following situation: if the frame is in an invalid + // sectionframe, it's possible that the frame is outside the page. + // If we restrict the cursor position to the page area, we enforce + // the formatting of the page, of the section frame and the frame itself. + if( aRectFnSet.YDiff( nPageTop, nOrigTop ) > 0 ) + aRectFnSet.SetTop( rOrig, nPageTop ); + + if ( aRectFnSet.YDiff( nOrigTop, nPageBott ) > 0 ) + aRectFnSet.SetTop( rOrig, nPageBott ); + } + + return bRet; +} + +/* + * GetAutoPos() looks up the char's char line which is described by rPos + * and is used by the auto-positioned frame. + */ + +bool SwTextFrame::GetAutoPos( SwRect& rOrig, const SwPosition &rPos ) const +{ + if( IsHiddenNow() ) + return false; + + TextFrameIndex const nOffset = MapModelToViewPos(rPos); + SwTextFrame* pFrame = &(const_cast<SwTextFrame*>(this)->GetFrameAtOfst( nOffset )); + + pFrame->GetFormatted(); + const SwFrame* pTmpFrame = pFrame->GetUpper(); + + SwRectFnSet aRectFnSet(pTmpFrame); + SwTwips nUpperMaxY = aRectFnSet.GetPrtBottom(*pTmpFrame); + + // nMaxY is in absolute value + SwTwips nMaxY; + if ( aRectFnSet.IsVert() ) + { + if ( aRectFnSet.IsVertL2R() ) + nMaxY = std::min( aRectFnSet.GetPrtBottom(*pFrame), nUpperMaxY ); + else + nMaxY = std::max( aRectFnSet.GetPrtBottom(*pFrame), nUpperMaxY ); + } + else + nMaxY = std::min( aRectFnSet.GetPrtBottom(*pFrame), nUpperMaxY ); + if ( pFrame->IsEmpty() || ! aRectFnSet.GetHeight(pFrame->getFramePrintArea()) ) + { + Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); + Point aPnt2; + if ( aRectFnSet.IsVert() ) + { + if ( aPnt1.X() < nMaxY && !aRectFnSet.IsVertL2R() ) + aPnt1.setX( nMaxY ); + + aPnt2.setX( aPnt1.X() + pFrame->getFramePrintArea().Width() ); + aPnt2.setY( aPnt1.Y() ); + if( aPnt2.X() < nMaxY ) + aPnt2.setX( nMaxY ); + } + else + { + if( aPnt1.Y() > nMaxY ) + aPnt1.setY( nMaxY ); + aPnt2.setX( aPnt1.X() ); + aPnt2.setY( aPnt1.Y() + pFrame->getFramePrintArea().Height() ); + if( aPnt2.Y() > nMaxY ) + aPnt2.setY( nMaxY ); + } + rOrig = SwRect( aPnt1, aPnt2 ); + return true; + } + else + { + if( !pFrame->HasPara() ) + return false; + + SwFrameSwapper aSwapper( pFrame, true ); + if ( aRectFnSet.IsVert() ) + nMaxY = pFrame->SwitchVerticalToHorizontal( nMaxY ); + + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + aTmpState.m_bRealHeight = true; + aLine.GetCharRect( &rOrig, nOffset, &aTmpState, nMaxY ); + if( aTmpState.m_aRealHeight.X() >= 0 ) + { + rOrig.Pos().AdjustY(aTmpState.m_aRealHeight.X() ); + rOrig.Height( aTmpState.m_aRealHeight.Y() ); + } + + if ( pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( rOrig ); + + if ( aRectFnSet.IsVert() ) + pFrame->SwitchHorizontalToVertical( rOrig ); + + return true; + } +} + +/** determine top of line for given position in the text frame + + - Top of first paragraph line is the top of the printing area of the text frame + - If a proportional line spacing is applied use top of anchor character as + top of the line. +*/ +bool SwTextFrame::GetTopOfLine( SwTwips& _onTopOfLine, + const SwPosition& _rPos ) const +{ + bool bRet = true; + + // get position offset + TextFrameIndex const nOffset = MapModelToViewPos(_rPos); + + if (TextFrameIndex(GetText().getLength()) < nOffset) + { + bRet = false; + } + else + { + SwRectFnSet aRectFnSet(this); + if ( IsEmpty() || !aRectFnSet.GetHeight(getFramePrintArea()) ) + { + // consider upper space amount considered + // for previous frame and the page grid. + _onTopOfLine = aRectFnSet.GetPrtTop(*this); + } + else + { + // determine formatted text frame that contains the requested position + SwTextFrame* pFrame = &(const_cast<SwTextFrame*>(this)->GetFrameAtOfst( nOffset )); + pFrame->GetFormatted(); + aRectFnSet.Refresh(pFrame); + // If proportional line spacing is applied + // to the text frame, the top of the anchor character is also the + // top of the line. + // Otherwise the line layout determines the top of the line + const SvxLineSpacingItem& rSpace = GetAttrSet()->GetLineSpacing(); + if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + SwRect aCharRect; + if ( GetAutoPos( aCharRect, _rPos ) ) + { + _onTopOfLine = aRectFnSet.GetTop(aCharRect); + } + else + { + bRet = false; + } + } + else + { + // assure that text frame is in a horizontal layout + SwFrameSwapper aSwapper( pFrame, true ); + // determine text line that contains the requested position + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + aLine.CharCursorToLine( nOffset ); + // determine top of line + _onTopOfLine = aLine.Y(); + if ( aRectFnSet.IsVert() ) + { + _onTopOfLine = pFrame->SwitchHorizontalToVertical( _onTopOfLine ); + } + } + } + } + + return bRet; +} + +// Minimum distance of non-empty lines is a little less than 2 cm +#define FILL_MIN_DIST 1100 + +struct SwFillData +{ + SwRect aFrame; + const SwCursorMoveState *pCMS; + SwPosition* pPos; + const Point& rPoint; + SwTwips nLineWidth; + bool bFirstLine : 1; + bool bInner : 1; + bool bColumn : 1; + bool bEmpty : 1; + SwFillData( const SwCursorMoveState *pC, SwPosition* pP, const SwRect& rR, + const Point& rPt ) : aFrame( rR ), pCMS( pC ), pPos( pP ), rPoint( rPt ), + nLineWidth( 0 ), bFirstLine( true ), bInner( false ), bColumn( false ), + bEmpty( true ){} + SwFillMode Mode() const { return pCMS->m_pFill->eMode; } + long X() const { return rPoint.X(); } + long Y() const { return rPoint.Y(); } + long Left() const { return aFrame.Left(); } + long Right() const { return aFrame.Right(); } + long Bottom() const { return aFrame.Bottom(); } + SwFillCursorPos &Fill() const { return *pCMS->m_pFill; } + void SetTab( sal_uInt16 nNew ) { pCMS->m_pFill->nTabCnt = nNew; } + void SetSpace( sal_uInt16 nNew ) { pCMS->m_pFill->nSpaceCnt = nNew; } + void SetSpaceOnly( sal_uInt16 nNew ) { pCMS->m_pFill->nSpaceOnlyCnt = nNew; } + void SetOrient( const sal_Int16 eNew ){ pCMS->m_pFill->eOrient = eNew; } +}; + +bool SwTextFrame::GetModelPositionForViewPoint_(SwPosition* pPos, const Point& rPoint, + const bool bChgFrame, SwCursorMoveState* pCMS ) const +{ + // GetModelPositionForViewPoint_ is called by GetModelPositionForViewPoint and GetKeyCursorOfst. + // Never just a return false. + + if( IsLocked() || IsHiddenNow() ) + return false; + + const_cast<SwTextFrame*>(this)->GetFormatted(); + + Point aOldPoint( rPoint ); + + if ( IsVertical() ) + { + SwitchVerticalToHorizontal( const_cast<Point&>(rPoint) ); + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + } + + if ( IsRightToLeft() ) + SwitchRTLtoLTR( const_cast<Point&>(rPoint) ); + + std::unique_ptr<SwFillData> pFillData; + if ( pCMS && pCMS->m_pFill ) + pFillData.reset(new SwFillData( pCMS, pPos, getFrameArea(), rPoint )); + + if ( IsEmpty() ) + { + *pPos = MapViewToModelPos(TextFrameIndex(0)); + if( pCMS && pCMS->m_bFieldInfo ) + { + SwTwips nDiff = rPoint.X() - getFrameArea().Left() - getFramePrintArea().Left(); + if( nDiff > 50 || nDiff < 0 ) + pCMS->m_bPosCorr = true; + } + } + else + { + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf ); + + // See comment in AdjustFrame() + SwTwips nMaxY = getFrameArea().Top() + getFramePrintArea().Top() + getFramePrintArea().Height(); + aLine.TwipsToLine( rPoint.Y() ); + while( aLine.Y() + aLine.GetLineHeight() > nMaxY ) + { + if( !aLine.Prev() ) + break; + } + + if( aLine.GetDropLines() >= aLine.GetLineNr() && 1 != aLine.GetLineNr() + && rPoint.X() < aLine.FirstLeft() + aLine.GetDropLeft() ) + while( aLine.GetLineNr() > 1 ) + aLine.Prev(); + + TextFrameIndex nOffset = aLine.GetModelPositionForViewPoint(pPos, rPoint, bChgFrame, pCMS); + + if( pCMS && pCMS->m_eState == CursorMoveState::NONE && aLine.GetEnd() == nOffset ) + pCMS->m_eState = CursorMoveState::RightMargin; + + // pPos is a pure IN parameter and must not be evaluated. + // pIter->GetModelPositionForViewPoint returns from a nesting with COMPLETE_STRING. + // If SwTextIter::GetModelPositionForViewPoint calls GetModelPositionForViewPoint further by itself + // nNode changes the position. + // In such cases, pPos must not be calculated. + if (TextFrameIndex(COMPLETE_STRING) != nOffset) + { + *pPos = MapViewToModelPos(nOffset); + if( pFillData ) + { + if (TextFrameIndex(GetText().getLength()) > nOffset || + rPoint.Y() < getFrameArea().Top() ) + pFillData->bInner = true; + pFillData->bFirstLine = aLine.GetLineNr() < 2; + if (GetText().getLength()) + { + pFillData->bEmpty = false; + pFillData->nLineWidth = aLine.GetCurr()->Width(); + } + } + } + } + bool bChgFillData = false; + if( pFillData && FindPageFrame()->getFrameArea().IsInside( aOldPoint ) ) + { + FillCursorPos( *pFillData ); + bChgFillData = true; + } + + if ( IsVertical() ) + { + if ( bChgFillData ) + SwitchHorizontalToVertical( pFillData->Fill().aCursor.Pos() ); + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + } + + if ( IsRightToLeft() && bChgFillData ) + { + SwitchLTRtoRTL( pFillData->Fill().aCursor.Pos() ); + const sal_Int16 eOrient = pFillData->pCMS->m_pFill->eOrient; + + if ( text::HoriOrientation::LEFT == eOrient ) + pFillData->SetOrient( text::HoriOrientation::RIGHT ); + else if ( text::HoriOrientation::RIGHT == eOrient ) + pFillData->SetOrient( text::HoriOrientation::LEFT ); + } + + const_cast<Point&>(rPoint) = aOldPoint; + + return true; +} + +bool SwTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + const bool bChgFrame = !(pCMS && CursorMoveState::UpDown == pCMS->m_eState); + return GetModelPositionForViewPoint_( pPos, rPoint, bChgFrame, pCMS ); +} + +/* + * Layout-oriented cursor movement to the line start. + */ + +bool SwTextFrame::LeftMargin(SwPaM *pPam) const +{ + assert(GetMergedPara() || &pPam->GetNode() == static_cast<SwContentNode const*>(GetDep())); + + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), + SwTextCursor::IsRightMargin() ); + pFrame->GetFormatted(); + TextFrameIndex nIndx; + if ( pFrame->IsEmpty() ) + nIndx = TextFrameIndex(0); + else + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + + aLine.CharCursorToLine(pFrame->MapModelToViewPos(*pPam->GetPoint())); + nIndx = aLine.GetStart(); + if( pFrame->GetOffset() && !pFrame->IsFollow() && !aLine.GetPrev() ) + { + sw_ChangeOffset(pFrame, TextFrameIndex(0)); + nIndx = TextFrameIndex(0); + } + } + *pPam->GetPoint() = pFrame->MapViewToModelPos(nIndx); + SwTextCursor::SetRightMargin( false ); + return true; +} + +/* + * To the line end: That's the position before the last char of the line. + * Exception: In the last line, it should be able to place the cursor after + * the last char in order to append text. + */ + +bool SwTextFrame::RightMargin(SwPaM *pPam, bool bAPI) const +{ + assert(GetMergedPara() || &pPam->GetNode() == static_cast<SwContentNode const*>(GetDep())); + + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), + SwTextCursor::IsRightMargin() ); + pFrame->GetFormatted(); + TextFrameIndex nRightMargin(0); + if (!IsEmpty()) + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + + aLine.CharCursorToLine(MapModelToViewPos(*pPam->GetPoint())); + nRightMargin = aLine.GetStart() + aLine.GetCurr()->GetLen(); + + // We skip hard line brakes + if( aLine.GetCurr()->GetLen() && + CH_BREAK == aInf.GetText()[sal_Int32(nRightMargin) - 1]) + --nRightMargin; + else if( !bAPI && (aLine.GetNext() || pFrame->GetFollow()) ) + { + while( nRightMargin > aLine.GetStart() && + ' ' == aInf.GetText()[sal_Int32(nRightMargin) - 1]) + --nRightMargin; + } + } + *pPam->GetPoint() = pFrame->MapViewToModelPos(nRightMargin); + SwTextCursor::SetRightMargin( !bAPI ); + return true; +} + +// The following two methods try to put the Cursor into the next/successive +// line. If we do not have a preceding/successive line we forward the call +// to the base class. +// The Cursor's horizontal justification is done afterwards by the CursorShell. + +namespace { + +class SwSetToRightMargin +{ + bool bRight; +public: + SwSetToRightMargin() : bRight( false ) { } + ~SwSetToRightMargin() { SwTextCursor::SetRightMargin( bRight ); } + void SetRight( const bool bNew ) { bRight = bNew; } +}; + +} + +bool SwTextFrame::UnitUp_( SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + // Set the RightMargin if needed + SwSetToRightMargin aSet; + + if( IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + // If the PaM is located within different boxes, we have a table selection, + // which is handled by the base class. + return SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly ); + } + + const_cast<SwTextFrame*>(this)->GetFormatted(); + const TextFrameIndex nPos = MapModelToViewPos(*pPam->GetPoint()); + SwRect aCharBox; + + if( !IsEmpty() && !IsHiddenNow() ) + { + TextFrameIndex nFormat(COMPLETE_STRING); + do + { + if (nFormat != TextFrameIndex(COMPLETE_STRING) && !IsFollow()) + sw_ChangeOffset( const_cast<SwTextFrame*>(this), nFormat ); + + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf ); + + // Optimize away flys with no flow and IsDummy() + if( nPos ) + aLine.CharCursorToLine( nPos ); + else + aLine.Top(); + + const SwLineLayout *pPrevLine = aLine.GetPrevLine(); + const TextFrameIndex nStart = aLine.GetStart(); + aLine.GetCharRect( &aCharBox, nPos ); + + bool bSecondOfDouble = ( aInf.IsMulti() && ! aInf.IsFirstMulti() ); + bool bPrevLine = ( pPrevLine && pPrevLine != aLine.GetCurr() ); + + if( !pPrevLine && !bSecondOfDouble && GetOffset() && !IsFollow() ) + { + nFormat = GetOffset(); + TextFrameIndex nDiff = aLine.GetLength(); + if( !nDiff ) + nDiff = TextFrameIndex(MIN_OFFSET_STEP); + if( nFormat > nDiff ) + nFormat = nFormat - nDiff; + else + nFormat = TextFrameIndex(0); + continue; + } + + // We select the target line for the cursor, in case we are in a + // double line portion, prev line = curr line + if( bPrevLine && !bSecondOfDouble ) + { + aLine.PrevLine(); + while ( aLine.GetStart() == nStart && + nullptr != ( pPrevLine = aLine.GetPrevLine() ) && + pPrevLine != aLine.GetCurr() ) + aLine.PrevLine(); + } + + if ( bPrevLine || bSecondOfDouble ) + { + aCharBox.Width( aCharBox.SSize().Width() / 2 ); + aCharBox.Pos().setX( aCharBox.Pos().X() - 150 ); + + // See comment in SwTextFrame::GetModelPositionForViewPoint() +#if OSL_DEBUG_LEVEL > 0 + const sal_uLong nOldNode = pPam->GetPoint()->nNode.GetIndex(); +#endif + // The node should not be changed + TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint(pPam->GetPoint(), + aCharBox.Pos(), false ); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( nOldNode == pPam->GetPoint()->nNode.GetIndex(), + "SwTextFrame::UnitUp: illegal node change" ); +#endif + + // We make sure that we move up. + if( nTmpOfst >= nStart && nStart && !bSecondOfDouble ) + { + nTmpOfst = nStart; + aSet.SetRight( true ); + } + *pPam->GetPoint() = MapViewToModelPos(nTmpOfst); + return true; + } + + if ( IsFollow() ) + { + aLine.GetCharRect( &aCharBox, nPos ); + aCharBox.Width( aCharBox.SSize().Width() / 2 ); + } + break; + } while ( true ); + } + /* If 'this' is a follow and a prev failed, we need to go to the + * last line of the master, which is us. + * Or: If we are a follow with follow, we need to get the master. + */ + if ( IsFollow() ) + { + const SwTextFrame *pTmpPrev = FindMaster(); + TextFrameIndex nOffs = GetOffset(); + if( pTmpPrev ) + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bProtectedAllowed = pSh && pSh->GetViewOptions()->IsCursorInProtectedArea(); + const SwTextFrame *pPrevPrev = pTmpPrev; + // We skip protected frames and frames without content here + while( pPrevPrev && ( pPrevPrev->GetOffset() == nOffs || + ( !bProtectedAllowed && pPrevPrev->IsProtected() ) ) ) + { + pTmpPrev = pPrevPrev; + nOffs = pTmpPrev->GetOffset(); + if ( pPrevPrev->IsFollow() ) + pPrevPrev = pTmpPrev->FindMaster(); + else + pPrevPrev = nullptr; + } + if ( !pPrevPrev ) + return pTmpPrev->SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly ); + aCharBox.Pos().setY( pPrevPrev->getFrameArea().Bottom() - 1 ); + return pPrevPrev->GetKeyCursorOfst( pPam->GetPoint(), aCharBox.Pos() ); + } + } + return SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly ); +} + +// Used for Bidi. nPos is the logical position in the string, bLeft indicates +// if left arrow or right arrow was pressed. The return values are: +// nPos: the new visual position +// bLeft: whether the break iterator has to add or subtract from the +// current position +static void lcl_VisualMoveRecursion(const SwLineLayout& rCurrLine, TextFrameIndex nIdx, + TextFrameIndex & nPos, bool& bRight, + sal_uInt8& nCursorLevel, sal_uInt8 nDefaultDir ) +{ + const SwLinePortion* pPor = rCurrLine.GetFirstPortion(); + const SwLinePortion* pLast = nullptr; + + // What's the current portion? + while ( pPor && nIdx + pPor->GetLen() <= nPos ) + { + nIdx = nIdx + pPor->GetLen(); + pLast = pPor; + pPor = pPor->GetNextPortion(); + } + + if ( bRight ) + { + bool bRecurse = pPor && pPor->IsMultiPortion() && + static_cast<const SwMultiPortion*>(pPor)->IsBidi(); + + // 1. special case: at beginning of bidi portion + if ( bRecurse && nIdx == nPos ) + { + nPos = nPos + pPor->GetLen(); + + // leave bidi portion + if ( nCursorLevel != nDefaultDir ) + { + bRecurse = false; + } + else + // special case: + // buffer: abcXYZ123 in LTR paragraph + // view: abc123ZYX + // cursor is between c and X in the buffer and cursor level = 0 + nCursorLevel++; + } + + // 2. special case: at beginning of portion after bidi portion + else if ( pLast && pLast->IsMultiPortion() && + static_cast<const SwMultiPortion*>(pLast)->IsBidi() && nIdx == nPos ) + { + // enter bidi portion + if ( nCursorLevel != nDefaultDir ) + { + bRecurse = true; + nIdx = nIdx - pLast->GetLen(); + pPor = pLast; + } + } + + // Recursion + if ( bRecurse ) + { + const SwLineLayout& rLine = static_cast<const SwMultiPortion*>(pPor)->GetRoot(); + TextFrameIndex nTmpPos = nPos - nIdx; + bool bTmpForward = ! bRight; + sal_uInt8 nTmpCursorLevel = nCursorLevel; + lcl_VisualMoveRecursion(rLine, TextFrameIndex(0), nTmpPos, bTmpForward, + nTmpCursorLevel, nDefaultDir + 1 ); + + nPos = nTmpPos + nIdx; + bRight = bTmpForward; + nCursorLevel = nTmpCursorLevel; + } + + // go forward + else + { + bRight = true; + nCursorLevel = nDefaultDir; + } + + } + else + { + bool bRecurse = pPor && pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsBidi(); + + // 1. special case: at beginning of bidi portion + if ( bRecurse && nIdx == nPos ) + { + // leave bidi portion + if ( nCursorLevel == nDefaultDir ) + { + bRecurse = false; + } + } + + // 2. special case: at beginning of portion after bidi portion + else if ( pLast && pLast->IsMultiPortion() && + static_cast<const SwMultiPortion*>(pLast)->IsBidi() && nIdx == nPos ) + { + nPos = nPos - pLast->GetLen(); + + // enter bidi portion + if ( nCursorLevel % 2 == nDefaultDir % 2 ) + { + bRecurse = true; + nIdx = nIdx - pLast->GetLen(); + pPor = pLast; + + // special case: + // buffer: abcXYZ123 in LTR paragraph + // view: abc123ZYX + // cursor is behind 3 in the buffer and cursor level = 2 + if ( nDefaultDir + 2 == nCursorLevel ) + nPos = nPos + pLast->GetLen(); + } + } + + // go forward + if ( bRecurse ) + { + const SwLineLayout& rLine = static_cast<const SwMultiPortion*>(pPor)->GetRoot(); + TextFrameIndex nTmpPos = nPos - nIdx; + bool bTmpForward = ! bRight; + sal_uInt8 nTmpCursorLevel = nCursorLevel; + lcl_VisualMoveRecursion(rLine, TextFrameIndex(0), nTmpPos, bTmpForward, + nTmpCursorLevel, nDefaultDir + 1 ); + + // special case: + // buffer: abcXYZ123 in LTR paragraph + // view: abc123ZYX + // cursor is between Z and 1 in the buffer and cursor level = 2 + if ( nTmpPos == pPor->GetLen() && nTmpCursorLevel == nDefaultDir + 1 ) + { + nTmpPos = nTmpPos - pPor->GetLen(); + nTmpCursorLevel = nDefaultDir; + bTmpForward = ! bTmpForward; + } + + nPos = nTmpPos + nIdx; + bRight = bTmpForward; + nCursorLevel = nTmpCursorLevel; + } + + // go backward + else + { + bRight = false; + nCursorLevel = nDefaultDir; + } + } +} + +void SwTextFrame::PrepareVisualMove(TextFrameIndex & nPos, sal_uInt8& nCursorLevel, + bool& bForward, bool bInsertCursor ) +{ + if( IsEmpty() || IsHiddenNow() ) + return; + + GetFormatted(); + + SwTextSizeInfo aInf(this); + SwTextCursor aLine(this, &aInf); + + if( nPos ) + aLine.CharCursorToLine( nPos ); + else + aLine.Top(); + + const SwLineLayout* pLine = aLine.GetCurr(); + const TextFrameIndex nStt = aLine.GetStart(); + const TextFrameIndex nLen = pLine->GetLen(); + + // We have to distinguish between an insert and overwrite cursor: + // The insert cursor position depends on the cursor level: + // buffer: abcXYZdef in LTR paragraph + // display: abcZYXdef + // If cursor is between c and X in the buffer and cursor level is 0, + // the cursor blinks between c and Z and -> sets the cursor between Z and Y. + // If the cursor level is 1, the cursor blinks between X and d and + // -> sets the cursor between d and e. + // The overwrite cursor simply travels to the next visual character. + if ( bInsertCursor ) + { + lcl_VisualMoveRecursion( *pLine, nStt, nPos, bForward, + nCursorLevel, IsRightToLeft() ? 1 : 0 ); + return; + } + + const sal_uInt8 nDefaultDir = static_cast<sal_uInt8>(IsRightToLeft() ? UBIDI_RTL : UBIDI_LTR); + const bool bVisualRight = ( nDefaultDir == UBIDI_LTR && bForward ) || + ( nDefaultDir == UBIDI_RTL && ! bForward ); + + // Bidi functions from icu 2.0 + + const sal_Unicode* pLineString = GetText().getStr(); + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( sal_Int32(nLen), 0, &nError ); + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(pLineString), + sal_Int32(nLen), nDefaultDir, nullptr, &nError ); + + TextFrameIndex nTmpPos(0); + bool bOutOfBounds = false; + + if ( nPos < nStt + nLen ) + { + nTmpPos = TextFrameIndex(ubidi_getVisualIndex( pBidi, sal_Int32(nPos), &nError )); + + // visual indices are always LTR aligned + if ( bVisualRight ) + { + if (nTmpPos + TextFrameIndex(1) < nStt + nLen) + ++nTmpPos; + else + { + nPos = nDefaultDir == UBIDI_RTL ? TextFrameIndex(0) : nStt + nLen; + bOutOfBounds = true; + } + } + else + { + if ( nTmpPos ) + --nTmpPos; + else + { + nPos = nDefaultDir == UBIDI_RTL ? nStt + nLen : TextFrameIndex(0); + bOutOfBounds = true; + } + } + } + else + { + nTmpPos = nDefaultDir == UBIDI_LTR ? nPos - TextFrameIndex(1) : TextFrameIndex(0); + } + + if ( ! bOutOfBounds ) + { + nPos = TextFrameIndex(ubidi_getLogicalIndex( pBidi, sal_Int32(nTmpPos), &nError )); + + if ( bForward ) + { + if ( nPos ) + --nPos; + else + { + ++nPos; + bForward = ! bForward; + } + } + else + ++nPos; + } + + ubidi_close( pBidi ); +} + +bool SwTextFrame::UnitDown_(SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + + if ( IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + // If the PaM is located within different boxes, we have a table selection, + // which is handled by the base class. + return SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly ); + } + const_cast<SwTextFrame*>(this)->GetFormatted(); + const TextFrameIndex nPos = MapModelToViewPos(*pPam->GetPoint()); + SwRect aCharBox; + const SwContentFrame *pTmpFollow = nullptr; + + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + + if ( !IsEmpty() && !IsHiddenNow() ) + { + TextFrameIndex nFormat(COMPLETE_STRING); + do + { + if (nFormat != TextFrameIndex(COMPLETE_STRING) && !IsFollow() && + !sw_ChangeOffset( const_cast<SwTextFrame*>(this), nFormat ) ) + break; + + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf ); + nFormat = aLine.GetEnd(); + + aLine.CharCursorToLine( nPos ); + + const SwLineLayout* pNextLine = aLine.GetNextLine(); + const TextFrameIndex nStart = aLine.GetStart(); + aLine.GetCharRect( &aCharBox, nPos ); + + bool bFirstOfDouble = ( aInf.IsMulti() && aInf.IsFirstMulti() ); + + if( pNextLine || bFirstOfDouble ) + { + aCharBox.Width( aCharBox.SSize().Width() / 2 ); +#if OSL_DEBUG_LEVEL > 0 + // See comment in SwTextFrame::GetModelPositionForViewPoint() + const sal_uLong nOldNode = pPam->GetPoint()->nNode.GetIndex(); +#endif + if ( pNextLine && ! bFirstOfDouble ) + aLine.NextLine(); + + TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint( pPam->GetPoint(), + aCharBox.Pos(), false ); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( nOldNode == pPam->GetPoint()->nNode.GetIndex(), + "SwTextFrame::UnitDown: illegal node change" ); +#endif + + // We make sure that we move down. + if( nTmpOfst <= nStart && ! bFirstOfDouble ) + nTmpOfst = nStart + TextFrameIndex(1); + *pPam->GetPoint() = MapViewToModelPos(nTmpOfst); + + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + + return true; + } + if( nullptr != ( pTmpFollow = GetFollow() ) ) + { // Skip protected follows + const SwContentFrame* pTmp = pTmpFollow; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( !pSh || !pSh->GetViewOptions()->IsCursorInProtectedArea() ) + { + while( pTmpFollow && pTmpFollow->IsProtected() ) + { + pTmp = pTmpFollow; + pTmpFollow = pTmpFollow->GetFollow(); + } + } + if( !pTmpFollow ) // Only protected ones left + { + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + return pTmp->SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly ); + } + + aLine.GetCharRect( &aCharBox, nPos ); + aCharBox.Width( aCharBox.SSize().Width() / 2 ); + } + else if( !IsFollow() ) + { + TextFrameIndex nTmpLen(aInf.GetText().getLength()); + if( aLine.GetEnd() < nTmpLen ) + { + if( nFormat <= GetOffset() ) + { + nFormat = std::min(GetOffset() + TextFrameIndex(MIN_OFFSET_STEP), + nTmpLen ); + if( nFormat <= GetOffset() ) + break; + } + continue; + } + } + break; + } while( true ); + } + else + pTmpFollow = GetFollow(); + + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + + // We take a shortcut for follows + if( pTmpFollow ) + { + aCharBox.Pos().setY( pTmpFollow->getFrameArea().Top() + 1 ); + return static_cast<const SwTextFrame*>(pTmpFollow)->GetKeyCursorOfst( pPam->GetPoint(), + aCharBox.Pos() ); + } + return SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly ); +} + +bool SwTextFrame::UnitUp(SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + /* We call ContentNode::GertFrame() in CursorSh::Up(). + * This _always returns the master. + * In order to not mess with cursor travelling, we correct here + * in SwTextFrame. + * We calculate UnitUp for pFrame. pFrame is either a master (= this) or a + * follow (!= this). + */ + const SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *(pPam->GetPoint()), + SwTextCursor::IsRightMargin() ); + const bool bRet = pFrame->UnitUp_( pPam, nOffset, bSetInReadOnly ); + + // No SwTextCursor::SetRightMargin( false ); + // Instead we have a SwSetToRightMargin in UnitUp_ + return bRet; +} + +bool SwTextFrame::UnitDown(SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + const SwTextFrame *pFrame = GetAdjFrameAtPos(const_cast<SwTextFrame*>(this), *(pPam->GetPoint()), + SwTextCursor::IsRightMargin() ); + const bool bRet = pFrame->UnitDown_( pPam, nOffset, bSetInReadOnly ); + SwTextCursor::SetRightMargin( false ); + return bRet; +} + +void SwTextFrame::FillCursorPos( SwFillData& rFill ) const +{ + if( !rFill.bColumn && GetUpper()->IsColBodyFrame() ) // ColumnFrames now with BodyFrame + { + const SwColumnFrame* pTmp = + static_cast<const SwColumnFrame*>(GetUpper()->GetUpper()->GetUpper()->Lower()); // The 1st column + // The first SwFrame in BodyFrame of the first column + const SwFrame* pFrame = static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower(); + sal_uInt16 nNextCol = 0; + // In which column do we end up in? + while( rFill.X() > pTmp->getFrameArea().Right() && pTmp->GetNext() ) + { + pTmp = static_cast<const SwColumnFrame*>(pTmp->GetNext()); + if( static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower() ) // ColumnFrames now with BodyFrame + { + pFrame = static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower(); + nNextCol = 0; + } + else + ++nNextCol; // Empty columns require column brakes + } + if( pTmp != GetUpper()->GetUpper() ) // Did we end up in another column? + { + if( !pFrame ) + return; + if( nNextCol ) + { + while( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); + } + else + { + while( pFrame->GetNext() && pFrame->getFrameArea().Bottom() < rFill.Y() ) + pFrame = pFrame->GetNext(); + } + // No filling, if the last frame in the targeted column does + // not contain a paragraph, but e.g. a table + if( pFrame->IsTextFrame() ) + { + rFill.Fill().nColumnCnt = nNextCol; + rFill.bColumn = true; + if( rFill.pPos ) + { + SwTextFrame const*const pTextFrame(static_cast<const SwTextFrame*>(pFrame)); + *rFill.pPos = pTextFrame->MapViewToModelPos( + TextFrameIndex(pTextFrame->GetText().getLength())); + } + if( nNextCol ) + { + rFill.aFrame = pTmp->getFramePrintArea(); + rFill.aFrame += pTmp->getFrameArea().Pos(); + } + else + rFill.aFrame = pFrame->getFrameArea(); + static_cast<const SwTextFrame*>(pFrame)->FillCursorPos( rFill ); + } + return; + } + } + std::unique_ptr<SwFont> pFnt; + SwTextFormatColl* pColl = GetTextNodeForParaProps()->GetTextColl(); + SwTwips nFirst = GetTextNodeForParaProps()->GetSwAttrSet().GetULSpace().GetLower(); + SwTwips nDiff = rFill.Y() - getFrameArea().Bottom(); + if( nDiff < nFirst ) + nDiff = -1; + else + pColl = &pColl->GetNextTextFormatColl(); + SwAttrSet aSet(const_cast<SwDoc&>(GetDoc()).GetAttrPool(), aTextFormatCollSetRange ); + const SwAttrSet* pSet = &pColl->GetAttrSet(); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if (GetTextNodeForParaProps()->HasSwAttrSet()) + { + // sw_redlinehide: pSet is mostly used for para props, but there are + // accesses to char props via pFnt - why does it use only the node's + // props for this, and not hints? + aSet.Put( *GetTextNodeForParaProps()->GetpSwAttrSet() ); + aSet.SetParent( pSet ); + pSet = &aSet; + pFnt.reset(new SwFont( pSet, &GetDoc().getIDocumentSettingAccess() )); + } + else + { + SwFontAccess aFontAccess( pColl, pSh ); + pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() )); + pFnt->CheckFontCacheId( pSh, pFnt->GetActual() ); + } + OutputDevice* pOut = pSh->GetOut(); + if( !pSh->GetViewOptions()->getBrowseMode() || pSh->GetViewOptions()->IsPrtFormat() ) + pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true ); + + pFnt->SetFntChg( true ); + pFnt->ChgPhysFnt( pSh, *pOut ); + + SwTwips nLineHeight = pFnt->GetHeight( pSh, *pOut ); + + bool bFill = false; + if( nLineHeight ) + { + bFill = true; + const SvxULSpaceItem &rUL = pSet->GetULSpace(); + SwTwips nDist = std::max( rUL.GetLower(), rUL.GetUpper() ); + if( rFill.Fill().nColumnCnt ) + { + rFill.aFrame.Height( nLineHeight ); + nDiff = rFill.Y() - rFill.Bottom(); + nFirst = 0; + } + else if( nDist < nFirst ) + nFirst = nFirst - nDist; + else + nFirst = 0; + nDist = std::max( nDist, GetLineSpace() ); + nDist += nLineHeight; + nDiff -= nFirst; + + if( nDiff > 0 ) + { + nDiff /= nDist; + rFill.Fill().nParaCnt = static_cast<sal_uInt16>(nDiff + 1); + rFill.nLineWidth = 0; + rFill.bInner = false; + rFill.bEmpty = true; + rFill.SetOrient( text::HoriOrientation::LEFT ); + } + else + nDiff = -1; + if( rFill.bInner ) + bFill = false; + else + { + const SvxTabStopItem &rRuler = pSet->GetTabStops(); + const SvxLRSpaceItem &rLRSpace = pSet->GetLRSpace(); + + SwRect &rRect = rFill.Fill().aCursor; + rRect.Top( rFill.Bottom() + (nDiff+1) * nDist - nLineHeight ); + if( nFirst && nDiff > -1 ) + rRect.Top( rRect.Top() + nFirst ); + rRect.Height( nLineHeight ); + SwTwips nLeft = rFill.Left() + rLRSpace.GetLeft() + + GetTextNodeForParaProps()->GetLeftMarginWithNum(); + SwTwips nRight = rFill.Right() - rLRSpace.GetRight(); + SwTwips nCenter = ( nLeft + nRight ) / 2; + rRect.Left( nLeft ); + if( SwFillMode::Margin == rFill.Mode() ) + { + if( rFill.bEmpty ) + { + rFill.SetOrient( text::HoriOrientation::LEFT ); + if( rFill.X() < nCenter ) + { + if( rFill.X() > ( nLeft + 2 * nCenter ) / 3 ) + { + rFill.SetOrient( text::HoriOrientation::CENTER ); + rRect.Left( nCenter ); + } + } + else if( rFill.X() > ( nRight + 2 * nCenter ) / 3 ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + } + else + { + rFill.SetOrient( text::HoriOrientation::CENTER ); + rRect.Left( nCenter ); + } + } + else + bFill = false; + } + else + { + SwTwips nSpace = 0; + if( SwFillMode::Tab != rFill.Mode() ) + { + const OUString aTmp(" "); + SwDrawTextInfo aDrawInf( pSh, *pOut, aTmp, 0, 2 ); + nSpace = pFnt->GetTextSize_( aDrawInf ).Width()/2; + } + if( rFill.X() >= nRight ) + { + if( SwFillMode::Indent != rFill.Mode() && ( rFill.bEmpty || + rFill.X() > rFill.nLineWidth + FILL_MIN_DIST ) ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + } + else + bFill = false; + } + else if( SwFillMode::Indent == rFill.Mode() ) + { + SwTwips nIndent = rFill.X(); + if( !rFill.bEmpty || nIndent > nRight ) + bFill = false; + else + { + nIndent -= rFill.Left(); + if( nIndent >= 0 && nSpace ) + { + nIndent /= nSpace; + nIndent *= nSpace; + rFill.SetTab( sal_uInt16( nIndent ) ); + rRect.Left( nIndent + rFill.Left() ); + } + else + bFill = false; + } + } + else if( rFill.X() > nLeft ) + { + SwTwips nTextLeft = rFill.Left() + rLRSpace.GetTextLeft() + + GetTextNodeForParaProps()->GetLeftMarginWithNum(true); + rFill.nLineWidth += rFill.bFirstLine ? nLeft : nTextLeft; + SwTwips nLeftTab; + SwTwips nRightTab = nLeft; + sal_uInt16 nSpaceCnt = 0; + sal_uInt16 nSpaceOnlyCnt = 0; + sal_uInt16 nTabCnt = 0; + sal_uInt16 nIdx = 0; + do + { + nLeftTab = nRightTab; + if( nIdx < rRuler.Count() ) + { + const SvxTabStop &rTabStop = rRuler.operator[](nIdx); + nRightTab = nTextLeft + rTabStop.GetTabPos(); + if( nLeftTab < nTextLeft && nRightTab > nTextLeft ) + nRightTab = nTextLeft; + else + ++nIdx; + if( nRightTab > rFill.nLineWidth ) + ++nTabCnt; + } + else + { + const SvxTabStopItem& rTab = + pSet->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP ); + const SwTwips nDefTabDist = rTab[0].GetTabPos(); + nRightTab = nLeftTab - nTextLeft; + nRightTab /= nDefTabDist; + nRightTab = nRightTab * nDefTabDist + nTextLeft; + while ( nRightTab <= nLeftTab ) + nRightTab += nDefTabDist; + if( nRightTab > rFill.nLineWidth ) + ++nTabCnt; + while ( nRightTab < rFill.X() ) + { + nRightTab += nDefTabDist; + if( nRightTab > rFill.nLineWidth ) + ++nTabCnt; + } + if( nLeftTab < nRightTab - nDefTabDist ) + nLeftTab = nRightTab - nDefTabDist; + } + if( nRightTab > nRight ) + nRightTab = nRight; + } + while( rFill.X() > nRightTab ); + --nTabCnt; + if( SwFillMode::TabSpace == rFill.Mode() ) + { + if( nSpace > 0 ) + { + if( !nTabCnt ) + nLeftTab = rFill.nLineWidth; + while( nLeftTab < rFill.X() ) + { + nLeftTab += nSpace; + ++nSpaceCnt; + } + if( nSpaceCnt ) + { + nLeftTab -= nSpace; + --nSpaceCnt; + } + if( rFill.X() - nLeftTab > nRightTab - rFill.X() ) + { + nSpaceCnt = 0; + ++nTabCnt; + rRect.Left( nRightTab ); + } + else + { + if( rFill.X() - nLeftTab > nSpace/2 ) + { + ++nSpaceCnt; + rRect.Left( nLeftTab + nSpace ); + } + else + rRect.Left( nLeftTab ); + } + } + else if( rFill.X() - nLeftTab < nRightTab - rFill.X() ) + rRect.Left( nLeftTab ); + else + { + if( nRightTab >= nRight ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + nTabCnt = 0; + nSpaceCnt = 0; + } + else + { + rRect.Left( nRightTab ); + ++nTabCnt; + } + } + } + else if( SwFillMode::Space == rFill.Mode() ) + { + SwTwips nLeftSpace = nLeft; + while( nLeftSpace < rFill.X() ) + { + nLeftSpace += nSpace; + ++nSpaceOnlyCnt; + } + rRect.Left( nLeftSpace ); + } + else + { + if( rFill.X() - nLeftTab < nRightTab - rFill.X() ) + rRect.Left( nLeftTab ); + else + { + if( nRightTab >= nRight ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + nTabCnt = 0; + nSpaceCnt = 0; + } + else + { + rRect.Left( nRightTab ); + ++nTabCnt; + } + } + } + rFill.SetTab( nTabCnt ); + rFill.SetSpace( nSpaceCnt ); + rFill.SetSpaceOnly( nSpaceOnlyCnt ); + if( bFill ) + { + if( std::abs( rFill.X() - nCenter ) <= + std::abs( rFill.X() - rRect.Left() ) ) + { + rFill.SetOrient( text::HoriOrientation::CENTER ); + rFill.SetTab( 0 ); + rFill.SetSpace( 0 ); + rFill.SetSpaceOnly( 0 ); + rRect.Left( nCenter ); + } + if( !rFill.bEmpty ) + rFill.nLineWidth += FILL_MIN_DIST; + if( rRect.Left() < rFill.nLineWidth ) + bFill = false; + } + } + } + // Do we extend over the page's/column's/etc. lower edge? + const SwFrame* pUp = GetUpper(); + if( pUp->IsInSct() ) + { + if( pUp->IsSctFrame() ) + pUp = pUp->GetUpper(); + else if( pUp->IsColBodyFrame() && + pUp->GetUpper()->GetUpper()->IsSctFrame() ) + pUp = pUp->GetUpper()->GetUpper()->GetUpper(); + } + SwRectFnSet aRectFnSet(this); + SwTwips nLimit = aRectFnSet.GetPrtBottom(*pUp); + SwTwips nRectBottom = rRect.Bottom(); + if ( aRectFnSet.IsVert() ) + nRectBottom = SwitchHorizontalToVertical( nRectBottom ); + + if( aRectFnSet.YDiff( nLimit, nRectBottom ) < 0 ) + bFill = false; + else + rRect.Width( 1 ); + } + } + const_cast<SwCursorMoveState*>(rFill.pCMS)->m_bFillRet = bFill; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx new file mode 100644 index 000000000..71e417c3a --- /dev/null +++ b/sw/source/core/text/frmform.cxx @@ -0,0 +1,2061 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <anchoredobject.hxx> +#include <bodyfrm.hxx> +#include <hintids.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <pagefrm.hxx> +#include <ndtxt.hxx> +#include <ftnfrm.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <paratr.hxx> +#include <viewopt.hxx> +#include <viewsh.hxx> +#include <frmatr.hxx> +#include <pam.hxx> +#include <fmtanchr.hxx> +#include "itrform2.hxx" +#include "widorp.hxx" +#include "txtcache.hxx" +#include <sectfrm.hxx> +#include <rootfrm.hxx> +#include <frmfmt.hxx> +#include <sortedobjs.hxx> +#include <editeng/tstpitem.hxx> +#include <redline.hxx> +#include <comphelper/lok.hxx> + +// Tolerance in formatting and text output +#define SLOPPY_TWIPS 5 + +namespace { + +class FormatLevel +{ + static sal_uInt16 nLevel; +public: + FormatLevel() { ++nLevel; } + ~FormatLevel() { --nLevel; } + static sal_uInt16 GetLevel() { return nLevel; } + static bool LastLevel() { return 10 < nLevel; } +}; + +} + +sal_uInt16 FormatLevel::nLevel = 0; + +void ValidateText( SwFrame *pFrame ) // Friend of frame +{ + if ( ( ! pFrame->IsVertical() && + pFrame->getFrameArea().Width() == pFrame->GetUpper()->getFramePrintArea().Width() ) || + ( pFrame->IsVertical() && + pFrame->getFrameArea().Height() == pFrame->GetUpper()->getFramePrintArea().Height() ) ) + { + pFrame->setFrameAreaSizeValid(true); + } +} + +void SwTextFrame::ValidateFrame() +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + // Validate surroundings to avoid oscillation + SwSwapIfSwapped swap( this ); + + if ( !IsInFly() && !IsInTab() ) + { // Only validate 'this' when inside a fly, the rest should actually only be + // needed for footnotes, which do not exist in flys. + SwSectionFrame* pSct = FindSctFrame(); + if( pSct ) + { + if( !pSct->IsColLocked() ) + pSct->ColLock(); + else + pSct = nullptr; + } + + SwFrame *pUp = GetUpper(); + pUp->Calc(pRenderContext); + if( pSct ) + pSct->ColUnlock(); + } + ValidateText( this ); + + // We at least have to save the MustFit flag! + assert(HasPara() && "ResetPreps(), missing ParaPortion, SwCache bug?"); + SwParaPortion *pPara = GetPara(); + const bool bMustFit = pPara->IsPrepMustFit(); + ResetPreps(); + pPara->SetPrepMustFit( bMustFit ); +} + +// After a RemoveFootnote the BodyFrame and all Frames contained within it, need to be +// recalculated, so that the DeadLine is right. +// First we search outwards, on the way back we calculate everything. +static void ValidateBodyFrame_( SwFrame *pFrame ) +{ + vcl::RenderContext* pRenderContext = pFrame ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr; + if( pFrame && !pFrame->IsCellFrame() ) + { + if( !pFrame->IsBodyFrame() && pFrame->GetUpper() ) + ValidateBodyFrame_( pFrame->GetUpper() ); + if( !pFrame->IsSctFrame() ) + pFrame->Calc(pRenderContext); + else + { + const bool bOld = static_cast<SwSectionFrame*>(pFrame)->IsContentLocked(); + static_cast<SwSectionFrame*>(pFrame)->SetContentLock( true ); + pFrame->Calc(pRenderContext); + if( !bOld ) + static_cast<SwSectionFrame*>(pFrame)->SetContentLock( false ); + } + } +} + +void SwTextFrame::ValidateBodyFrame() +{ + SwSwapIfSwapped swap( this ); + + // See comment in ValidateFrame() + if ( !IsInFly() && !IsInTab() && + !( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() ) ) + ValidateBodyFrame_( GetUpper() ); +} + +bool SwTextFrame::GetDropRect_( SwRect &rRect ) const +{ + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + + OSL_ENSURE( HasPara(), "SwTextFrame::GetDropRect_: try again next year." ); + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextMargin aLine( const_cast<SwTextFrame*>(this), &aInf ); + if( aLine.GetDropLines() ) + { + rRect.Top( aLine.Y() ); + rRect.Left( aLine.GetLineStart() ); + rRect.Height( aLine.GetDropHeight() ); + rRect.Width( aLine.GetDropLeft() ); + + if ( IsRightToLeft() ) + SwitchLTRtoRTL( rRect ); + + if ( IsVertical() ) + SwitchHorizontalToVertical( rRect ); + return true; + } + + return false; +} + +bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + SwSwapIfSwapped swap( this ); + + OSL_ENSURE( HasFollow(), "CalcFollow: missing Follow." ); + + SwTextFrame* pMyFollow = GetFollow(); + + SwParaPortion *pPara = GetPara(); + const bool bFollowField = pPara && pPara->IsFollowField(); + + if( !pMyFollow->GetOffset() || pMyFollow->GetOffset() != nTextOfst || + bFollowField || pMyFollow->IsFieldFollow() || + ( pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Width() ) || + ( ! pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Height() ) ) + { +#if OSL_DEBUG_LEVEL > 0 + const SwFrame *pOldUp = GetUpper(); +#endif + + SwRectFnSet aRectFnSet(this); + SwTwips nOldBottom = aRectFnSet.GetBottom(GetUpper()->getFrameArea()); + SwTwips nMyPos = aRectFnSet.GetTop(getFrameArea()); + + const SwPageFrame *pPage = nullptr; + bool bOldInvaContent = true; + if ( !IsInFly() && GetNext() ) + { + pPage = FindPageFrame(); + // Minimize (reset if possible) invalidations: see below + bOldInvaContent = pPage->IsInvalidContent(); + } + + pMyFollow->SetOffset_( nTextOfst ); + pMyFollow->SetFieldFollow( bFollowField ); + if( HasFootnote() || pMyFollow->HasFootnote() ) + { + ValidateFrame(); + ValidateBodyFrame(); + if( pPara ) + { + pPara->GetReformat() = SwCharRange(); + pPara->GetDelta() = 0; + } + } + + // The footnote area must not get larger + SwSaveFootnoteHeight aSave( FindFootnoteBossFrame( true ), LONG_MAX ); + + pMyFollow->CalcFootnoteFlag(); + if ( !pMyFollow->GetNext() && !pMyFollow->HasFootnote() ) + nOldBottom = aRectFnSet.IsVert() ? 0 : LONG_MAX; + + // tdf#122892 check flag: + // 1. WidowsAndOrphans::FindWidows() determines follow is a widow + // 2. SwTextFrame::PrepWidows() calls SetPrepWidows() on master; + // if it can spare lines, master truncates one line + // 3. SwTextFrame::CalcPreps() on master (below); + // unless IsPrepMustFit(), if master hasn't shrunk via 2., it will SetWidow() + // 4. loop must exit then, because the follow didn't grow so nothing will ever change + while (!IsWidow()) + { + if( !FormatLevel::LastLevel() ) + { + // If the follow is contained within a column section or column + // frame, we need to calculate that first. This is because the + // FormatWidthCols() does not work if it is called from MakeAll + // of the _locked_ follow. + SwSectionFrame* pSct = pMyFollow->FindSctFrame(); + if( pSct && !pSct->IsAnLower( this ) ) + { + if( pSct->GetFollow() ) + pSct->SimpleFormat(); + else if( ( pSct->IsVertical() && !pSct->getFrameArea().Width() ) || + ( ! pSct->IsVertical() && !pSct->getFrameArea().Height() ) ) + break; + } + // i#11760 - Intrinsic format of follow is controlled. + if ( FollowFormatAllowed() ) + { + // i#11760 - No nested format of follows, if + // text frame is contained in a column frame. + // Thus, forbid intrinsic format of follow. + { + bool bIsFollowInColumn = false; + SwFrame* pFollowUpper = pMyFollow->GetUpper(); + while ( pFollowUpper ) + { + if ( pFollowUpper->IsColumnFrame() ) + { + bIsFollowInColumn = true; + break; + } + if ( pFollowUpper->IsPageFrame() || + pFollowUpper->IsFlyFrame() ) + { + break; + } + pFollowUpper = pFollowUpper->GetUpper(); + } + if ( bIsFollowInColumn ) + { + pMyFollow->ForbidFollowFormat(); + } + } + + pMyFollow->Calc(pRenderContext); + // The Follow can tell from its getFrameArea().Height() that something went wrong + OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: cheesy follow" ); + if( pMyFollow->GetPrev() ) + { + pMyFollow->Prepare(); + pMyFollow->Calc(pRenderContext); + OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: very cheesy follow" ); + } + + // i#11760 - Reset control flag for follow format. + pMyFollow->AllowFollowFormat(); + } + + // Make sure that the Follow gets painted + pMyFollow->SetCompletePaint(); + } + + pPara = GetPara(); + // As long as the Follow requests lines due to Orphans, it is + // passed these and is formatted again if possible + if( pPara && pPara->IsPrepWidows() ) + CalcPreps(); + else + break; + } + + if( HasFootnote() || pMyFollow->HasFootnote() ) + { + ValidateBodyFrame(); + ValidateFrame(); + if( pPara ) + { + pPara->GetReformat() = SwCharRange(); + pPara->GetDelta() = 0; + } + } + + if ( pPage && !bOldInvaContent ) + pPage->ValidateContent(); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( pOldUp == GetUpper(), "SwTextFrame::CalcFollow: heavy follow" ); +#endif + + const long nRemaining = + - aRectFnSet.BottomDist( GetUpper()->getFrameArea(), nOldBottom ); + if ( nRemaining > 0 && !GetUpper()->IsSctFrame() && + nRemaining != ( aRectFnSet.IsVert() ? + nMyPos - getFrameArea().Right() : + getFrameArea().Top() - nMyPos ) ) + { + return true; + } + } + + return false; +} + +void SwTextFrame::MakePos() +{ + SwFrame::MakePos(); + // Inform LOK clients about change in position of redlines (if any) + if(comphelper::LibreOfficeKit::isActive()) + { + SwTextNode const* pTextNode = GetTextNodeFirst(); + const SwRedlineTable& rTable = pTextNode->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos) + { + SwRangeRedline* pRedln = rTable[nRedlnPos]; + if (pTextNode->GetIndex() == pRedln->GetPoint()->nNode.GetNode().GetIndex()) + { + pRedln->MaybeNotifyRedlinePositionModification(getFrameArea().Top()); + if (GetMergedPara() + && pRedln->GetType() == RedlineType::Delete + && pRedln->GetPoint()->nNode != pRedln->GetMark()->nNode) + { + pTextNode = pRedln->End()->nNode.GetNode().GetTextNode(); + } + } + } + } +} + +void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit ) +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + if( IsUndersized() ) + { + if( GetOffset() && !IsFollow() ) // A scrolled paragraph (undersized) + return; + SetUndersized( nChgHght == 0 || bHasToFit ); + } + + // AdjustFrame is called with a swapped frame during + // formatting but the frame is not swapped during FormatEmpty + SwSwapIfSwapped swap( this ); + SwRectFnSet aRectFnSet(this); + + // The Frame's size variable is incremented by Grow or decremented by Shrink. + // If the size cannot change, nothing should happen! + if( nChgHght >= 0) + { + SwTwips nChgHeight = nChgHght; + if( nChgHght && !bHasToFit ) + { + if( IsInFootnote() && !IsInSct() ) + { + SwTwips nReal = Grow( nChgHght, true ); + if( nReal < nChgHght ) + { + SwTwips nBot = aRectFnSet.YInc( aRectFnSet.GetBottom(getFrameArea()), + nChgHght - nReal ); + SwFrame* pCont = FindFootnoteFrame()->GetUpper(); + + if( aRectFnSet.BottomDist( pCont->getFrameArea(), nBot ) > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nChgHght ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + if( aRectFnSet.IsVert() ) + { + aPrt.AddWidth(nChgHght ); + } + else + { + aPrt.AddHeight(nChgHght ); + } + + return; + } + } + } + + Grow( nChgHght ); + + if ( IsInFly() ) + { + // If one of the Upper is a Fly, it's very likely that this fly changes its + // position by the Grow. Therefore, my position has to be corrected also or + // the check further down is not meaningful. + // The predecessors need to be calculated, so that the position can be + // calculated correctly. + if ( GetPrev() ) + { + SwFrame *pPre = GetUpper()->Lower(); + do + { pPre->Calc(pRenderContext); + pPre = pPre->GetNext(); + } while ( pPre && pPre != this ); + } + const Point aOldPos( getFrameArea().Pos() ); + MakePos(); + if ( aOldPos != getFrameArea().Pos() ) + { + InvalidateObjs(false); + } + } + nChgHeight = 0; + } + // A Grow() is always accepted by the Layout, even if the + // FixSize of the surrounding layout frame should not allow it. + // We text for this case and correct the values. + // The Frame must NOT be shrunk further than its size permits + // even in the case of an emergency. + SwTwips nRstHeight; + if ( IsVertical() ) + { + OSL_ENSURE( ! IsSwapped(),"Swapped frame while calculating nRstHeight" ); + + if ( IsVertLR() ) + nRstHeight = GetUpper()->getFrameArea().Left() + + GetUpper()->getFramePrintArea().Left() + + GetUpper()->getFramePrintArea().Width() + - getFrameArea().Left(); + else + nRstHeight = getFrameArea().Left() + getFrameArea().Width() - + ( GetUpper()->getFrameArea().Left() + GetUpper()->getFramePrintArea().Left() ); + } + else + nRstHeight = GetUpper()->getFrameArea().Top() + + GetUpper()->getFramePrintArea().Top() + + GetUpper()->getFramePrintArea().Height() + - getFrameArea().Top(); + + // We can get a bit of space in table cells, because there could be some + // left through a vertical alignment to the top. + // Assure that first lower in upper is the current one or is valid. + if ( IsInTab() && + ( GetUpper()->Lower() == this || + GetUpper()->Lower()->isFrameAreaDefinitionValid() ) ) + { + long nAdd = aRectFnSet.YDiff( aRectFnSet.GetTop(GetUpper()->Lower()->getFrameArea()), + aRectFnSet.GetPrtTop(*GetUpper()) ); + OSL_ENSURE( nAdd >= 0, "Ey" ); + nRstHeight += nAdd; + } + + // nRstHeight < 0 means that the TextFrame is located completely outside of its Upper. + // This can happen, if it's located within a FlyAtContentFrame, which changed sides by a + // Grow(). In such a case, it's wrong to execute the following Grow(). + // In the case of a bug, we end up with an infinite loop. + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + + if( nRstHeight < nFrameHeight ) + { + // It can be that I have the right size, but the Upper is too small and can get me some room + if( ( nRstHeight >= 0 || ( IsInFootnote() && IsInSct() ) ) && !bHasToFit ) + nRstHeight += GetUpper()->Grow( nFrameHeight - nRstHeight ); + // In column sections we do not want to get too big or else more areas are created by + // GetNextSctLeaf. Instead, we shrink and remember bUndersized, so that FormatWidthCols + // can calculate the right column size. + if ( nRstHeight < nFrameHeight ) + { + if( bHasToFit || !IsMoveable() || + ( IsInSct() && !FindSctFrame()->MoveAllowed(this) ) ) + { + SetUndersized( true ); + Shrink( std::min( ( nFrameHeight - nRstHeight), nPrtHeight ) ); + } + else + SetUndersized( false ); + } + } + else if( nChgHeight ) + { + if( nRstHeight - nFrameHeight < nChgHeight ) + nChgHeight = nRstHeight - nFrameHeight; + if( nChgHeight ) + Grow( nChgHeight ); + } + } + else + Shrink( -nChgHght ); +} + +css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips CurrentPos ) +{ + css::uno::Sequence< css::style::TabStop > tabs(1); + css::style::TabStop ts; + + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); + SwTextFormatter aLine( this, &aInf ); + SwTextCursor TextCursor( this, &aInf ); + const Point aCharPos( TextCursor.GetTopLeft() ); + + SwTwips nRight = aLine.Right(); + CurrentPos -= aCharPos.X(); + + // get current tab stop information stored in the Frame + const SvxTabStop *pTS = aLine.GetLineInfo().GetTabStop( CurrentPos, nRight ); + + if( !pTS ) + { + return css::uno::Sequence< css::style::TabStop >(); + } + + // copy tab stop information into a Sequence, which only contains one element. + ts.Position = pTS->GetTabPos(); + ts.DecimalChar = pTS->GetDecimal(); + ts.FillChar = pTS->GetFill(); + switch( pTS->GetAdjustment() ) + { + case SvxTabAdjust::Left : ts.Alignment = css::style::TabAlign_LEFT; break; + case SvxTabAdjust::Center : ts.Alignment = css::style::TabAlign_CENTER; break; + case SvxTabAdjust::Right : ts.Alignment = css::style::TabAlign_RIGHT; break; + case SvxTabAdjust::Decimal: ts.Alignment = css::style::TabAlign_DECIMAL; break; + case SvxTabAdjust::Default: ts.Alignment = css::style::TabAlign_DEFAULT; break; + default: break; // prevent warning + } + + tabs[0] = ts; + return tabs; +} + +// AdjustFollow expects the following situation: +// The SwTextIter points to the lower end of the Master, the Offset is set in the Follow. +// nOffset holds the Offset in the text string, from which the Master closes +// and the Follow starts. +// If it's 0, the FollowFrame is deleted. +void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine, + const TextFrameIndex nOffset, const TextFrameIndex nEnd, + const sal_uInt8 nMode ) +{ + SwFrameSwapper aSwapper( this, false ); + + // We got the rest of the text mass: Delete all Follows + // DummyPortions() are a special case. + // Special cases are controlled by parameter <nMode>. + if( HasFollow() && !(nMode & 1) && nOffset == nEnd ) + { + while( GetFollow() ) + { + if( GetFollow()->IsLocked() ) + { + OSL_FAIL( "+SwTextFrame::JoinFrame: Follow is locked." ); + return; + } + if (GetFollow()->IsDeleteForbidden()) + return; + JoinFrame(); + } + + return; + } + + // Dancing on the volcano: We'll just format the last line quickly + // for the QuoVadis stuff. + // The Offset can move of course: + const TextFrameIndex nNewOfst = (IsInFootnote() && (!GetIndNext() || HasFollow())) + ? rLine.FormatQuoVadis(nOffset) : nOffset; + + if( !(nMode & 1) ) + { + // We steal text mass from our Follows + // It can happen that we have to join some of them + while( GetFollow() && GetFollow()->GetFollow() && + nNewOfst >= GetFollow()->GetFollow()->GetOffset() ) + { + JoinFrame(); + } + } + + // The Offset moved + if( GetFollow() ) + { + if ( nMode ) + GetFollow()->ManipOfst(TextFrameIndex(0)); + + if ( CalcFollow( nNewOfst ) ) // CalcFollow only at the end, we do a SetOffset there + rLine.SetOnceMore( true ); + } +} + +SwContentFrame *SwTextFrame::JoinFrame() +{ + OSL_ENSURE( GetFollow(), "+SwTextFrame::JoinFrame: no follow" ); + SwTextFrame *pFoll = GetFollow(); + + SwTextFrame *pNxt = pFoll->GetFollow(); + + // All footnotes of the to-be-destroyed Follow are relocated to us + TextFrameIndex nStart = pFoll->GetOffset(); + if ( pFoll->HasFootnote() ) + { + SwFootnoteBossFrame *pFootnoteBoss = nullptr; + SwFootnoteBossFrame *pEndBoss = nullptr; + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*pFoll); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if (RES_TXTATR_FTN == pHt->Which() + && nStart <= pFoll->MapModelToView(pNode, pHt->GetStart())) + { + if (pHt->GetFootnote().IsEndNote()) + { + if (!pEndBoss) + pEndBoss = pFoll->FindFootnoteBossFrame(); + SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this ); + } + else + { + if (!pFootnoteBoss) + pFootnoteBoss = pFoll->FindFootnoteBossFrame( true ); + SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this ); + } + SetFootnote( true ); + } + } + } + +#ifdef DBG_UTIL + else if ( pFoll->isFramePrintAreaValid() || + pFoll->isFrameAreaSizeValid() ) + { + pFoll->CalcFootnoteFlag(); + OSL_ENSURE( !pFoll->HasFootnote(), "Missing FootnoteFlag." ); + } +#endif + + pFoll->MoveFlyInCnt( this, nStart, TextFrameIndex(COMPLETE_STRING) ); + pFoll->SetFootnote( false ); + // i#27138 + // Notify accessibility paragraphs objects about changed CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph, which + // is <this>, will change. + { + SwViewShell* pViewShell( pFoll->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pFoll->FindNextCnt( true )), + this ); + } + } + pFoll->Cut(); + SetFollow(pNxt); + SwFrame::DestroyFrame(pFoll); + return pNxt; +} + +void SwTextFrame::SplitFrame(TextFrameIndex const nTextPos) +{ + SwSwapIfSwapped swap( this ); + + // The Paste sends a Modify() to me + // I lock myself, so that my data does not disappear + TextFrameLockGuard aLock( this ); + SwTextFrame *const pNew = static_cast<SwTextFrame *>(GetTextNodeFirst()->MakeFrame(this)); + + pNew->SetFollow( GetFollow() ); + SetFollow( pNew ); + + pNew->Paste( GetUpper(), GetNext() ); + // i#27138 + // notify accessibility paragraphs objects about changed CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph, which + // is <this>, will change. + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + pViewShell->InvalidateAccessibleParaFlowRelation( + dynamic_cast<SwTextFrame*>(pNew->FindNextCnt( true )), + this ); + } + } + + // If footnotes end up in pNew bz our actions, we need + // to re-register them + if ( HasFootnote() ) + { + SwFootnoteBossFrame *pFootnoteBoss = nullptr; + SwFootnoteBossFrame *pEndBoss = nullptr; + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*this); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if (RES_TXTATR_FTN == pHt->Which() + && nTextPos <= MapModelToView(pNode, pHt->GetStart())) + { + if (pHt->GetFootnote().IsEndNote()) + { + if (!pEndBoss) + pEndBoss = FindFootnoteBossFrame(); + SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew ); + } + else + { + if (!pFootnoteBoss) + pFootnoteBoss = FindFootnoteBossFrame( true ); + SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew ); + } + pNew->SetFootnote( true ); + } + } + } + +#ifdef DBG_UTIL + else + { + CalcFootnoteFlag( nTextPos - TextFrameIndex(1) ); + OSL_ENSURE( !HasFootnote(), "Missing FootnoteFlag." ); + } +#endif + + MoveFlyInCnt( pNew, nTextPos, TextFrameIndex(COMPLETE_STRING) ); + + // No SetOffset or CalcFollow, because an AdjustFollow follows immediately anyways + + pNew->ManipOfst( nTextPos ); +} + +void SwTextFrame::SetOffset_(TextFrameIndex const nNewOfst) +{ + // We do not need to invalidate out Follow. + // We are a Follow, get formatted right away and call + // SetOffset() from there + mnOffset = nNewOfst; + SwParaPortion *pPara = GetPara(); + if( pPara ) + { + SwCharRange &rReformat = pPara->GetReformat(); + rReformat.Start() = TextFrameIndex(0); + rReformat.Len() = TextFrameIndex(GetText().getLength()); + pPara->GetDelta() = sal_Int32(rReformat.Len()); + } + InvalidateSize(); +} + +bool SwTextFrame::CalcPreps() +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), "SwTextFrame::CalcPreps with swapped frame" ); + SwRectFnSet aRectFnSet(this); + + SwParaPortion *pPara = GetPara(); + if ( !pPara ) + return false; + const bool bPrep = pPara->IsPrep(); + const bool bPrepWidows = pPara->IsPrepWidows(); + const bool bPrepAdjust = pPara->IsPrepAdjust(); + const bool bPrepMustFit = pPara->IsPrepMustFit(); + ResetPreps(); + + bool bRet = false; + if( bPrep && !pPara->GetReformat().Len() ) + { + // PrepareHint::Widows means that the orphans rule got activated in the Follow. + // In unfortunate cases we could also have a PrepAdjust! + if( bPrepWidows ) + { + if( !GetFollow() ) + { + OSL_ENSURE( GetFollow(), "+SwTextFrame::CalcPreps: no credits" ); + return false; + } + + // We need to prepare for two cases: + // We were able to hand over a few lines to the Follow + // -> we need to shrink + // or we need to go on the next page + // -> we let our Frame become too big + + SwTwips nChgHeight = GetParHeight(); + if( nChgHeight >= aRectFnSet.GetHeight(getFramePrintArea()) ) + { + if( bPrepMustFit ) + { + GetFollow()->SetJustWidow( true ); + GetFollow()->Prepare(); + } + else if ( aRectFnSet.IsVert() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( aFrm.Width() + aFrm.Left() ); + aFrm.Left( 0 ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( aPrt.Width() + getFrameArea().Left() ); + } + + SetWidow( true ); + } + else + { + // nTmp should be very large, but not so large as to cause overflow later (e.g., + // GetFrameOfModify in sw/source/core/layout/frmtool.cxx calculates nCurrentDist + // from, among others, the square of aDiff.getY(), which can be close to nTmp); + // the previously used value TWIPS_MAX/2 (i.e., (LONG_MAX - 1)/2) depended on + // the range of 'long', while the value (SAL_MAX_INT32 - 1)/2 (which matches the + // old value on platforms where 'long' is 'sal_Int32') is empirically shown to + // be large enough in practice even on platforms where 'long' is 'sal_Int64': + SwTwips const nTmp = sw::WIDOW_MAGIC - (getFrameArea().Top()+10000); + SwTwips nDiff = nTmp - getFrameArea().Height(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( nTmp ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( aPrt.Height() + nDiff ); + } + + SetWidow( true ); + } + } + else + { + OSL_ENSURE( nChgHeight < aRectFnSet.GetHeight(getFramePrintArea()), + "+SwTextFrame::CalcPrep: want to shrink" ); + + nChgHeight = aRectFnSet.GetHeight(getFramePrintArea()) - nChgHeight; + + GetFollow()->SetJustWidow( true ); + GetFollow()->Prepare(); + Shrink( nChgHeight ); + SwRect &rRepaint = pPara->GetRepaint(); + + if ( aRectFnSet.IsVert() ) + { + SwRect aRepaint( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + SwitchVerticalToHorizontal( aRepaint ); + rRepaint.Chg( aRepaint.Pos(), aRepaint.SSize() ); + } + else + rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + + if( 0 >= rRepaint.Width() ) + rRepaint.Width(1); + } + bRet = true; + } + else if ( bPrepAdjust ) + { + if ( HasFootnote() ) + { + if( !CalcPrepFootnoteAdjust() ) + { + if( bPrepMustFit ) + { + SwTextLineAccess aAccess( this ); + aAccess.GetPara()->SetPrepMustFit(true); + } + return false; + } + } + + { + SwSwapIfNotSwapped swap( this ); + + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); + SwTextFormatter aLine( this, &aInf ); + + WidowsAndOrphans aFrameBreak( this ); + // Whatever the attributes say: we split the paragraph in + // MustFit case if necessary + if( bPrepMustFit ) + { + aFrameBreak.SetKeep( false ); + aFrameBreak.ClrOrphLines(); + } + // Before calling FormatAdjust, we need to make sure + // that the lines protruding at the bottom get indeed + // truncated + bool bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine ); + bRet = true; + while( !bBreak && aLine.Next() ) + { + bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine ); + } + if( bBreak ) + { + // We run into troubles: when TruncLines is called, the + // conditions in IsInside change immediately such that + // IsBreakNow can return different results. + // For this reason, we tell rFrameBreak that the + // end is reached at the location of rLine. + // Let's see if it works ... + aLine.TruncLines(); + aFrameBreak.SetRstHeight( aLine ); + FormatAdjust( aLine, aFrameBreak, TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() ); + } + else + { + if( !GetFollow() ) + { + FormatAdjust( aLine, aFrameBreak, + TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() ); + } + else if ( !aFrameBreak.IsKeepAlways() ) + { + // We delete a line before the Master, because the Follow + // could hand over a line + const SwCharRange aFollowRg(GetFollow()->GetOffset(), TextFrameIndex(1)); + pPara->GetReformat() += aFollowRg; + // We should continue! + bRet = false; + } + } + } + + // A final check, if FormatAdjust() didn't help we need to + // truncate + if( bPrepMustFit ) + { + const SwTwips nMust = aRectFnSet.GetPrtBottom(*GetUpper()); + const SwTwips nIs = aRectFnSet.GetBottom(getFrameArea()); + + if( aRectFnSet.IsVert() && nIs < nMust ) + { + Shrink( nMust - nIs ); + + if( getFramePrintArea().Width() < 0 ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( 0 ); + } + + SetUndersized( true ); + } + else if ( ! aRectFnSet.IsVert() && nIs > nMust ) + { + Shrink( nIs - nMust ); + + if( getFramePrintArea().Height() < 0 ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( 0 ); + } + + SetUndersized( true ); + } + } + } + } + pPara->SetPrepMustFit( bPrepMustFit ); + return bRet; +} + +// We rewire the footnotes and the character bound objects +void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew ) +{ + if( pFrame->GetOffset() < nNew ) + pFrame->MoveFlyInCnt( this, TextFrameIndex(0), nNew ); + else if( pFrame->GetOffset() > nNew ) + MoveFlyInCnt( pFrame, nNew, TextFrameIndex(COMPLETE_STRING) ); +} + +void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, + WidowsAndOrphans &rFrameBreak, + TextFrameIndex const nStrLen, + const bool bDummy ) +{ + SwSwapIfNotSwapped swap( this ); + + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + + TextFrameIndex nEnd = rLine.GetStart(); + + const bool bHasToFit = pPara->IsPrepMustFit(); + + // The StopFlag is set by footnotes which want to go onto the next page + // Call base class method <SwTextFrameBreak::IsBreakNow(..)> + // instead of method <WidowsAndOrphans::IsBreakNow(..)> to get a break, + // even if due to widow rule no enough lines exists. + sal_uInt8 nNew = ( !GetFollow() && + nEnd < nStrLen && + ( rLine.IsStop() || + ( bHasToFit + ? ( rLine.GetLineNr() > 1 && + !rFrameBreak.IsInside( rLine ) ) + : rFrameBreak.IsBreakNow( rLine ) ) ) ) + ? 1 : 0; + // i#84870 + // no split of text frame, which only contains an as-character anchored object + bool bOnlyContainsAsCharAnchoredObj = + !IsFollow() && nStrLen == TextFrameIndex(1) && + GetDrawObjs() && GetDrawObjs()->size() == 1 && + (*GetDrawObjs())[0]->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR; + + // Still try split text frame if we have columns. + if (FindColFrame()) + bOnlyContainsAsCharAnchoredObj = false; + + if ( nNew && bOnlyContainsAsCharAnchoredObj ) + { + nNew = 0; + } + + if ( nNew ) + { + SplitFrame( nEnd ); + } + + const SwFrame *pBodyFrame = FindBodyFrame(); + + const long nBodyHeight = pBodyFrame ? ( IsVertical() ? + pBodyFrame->getFrameArea().Width() : + pBodyFrame->getFrameArea().Height() ) : 0; + + // If the current values have been calculated, show that they + // are valid now + pPara->GetReformat() = SwCharRange(); + bool bDelta = pPara->GetDelta() != 0; + pPara->GetDelta() = 0; + + if( rLine.IsStop() ) + { + rLine.TruncLines( true ); + nNew = 1; + } + + // FindBreak truncates the last line + if( !rFrameBreak.FindBreak( this, rLine, bHasToFit ) ) + { + // If we're done formatting, we set nEnd to the end. + // AdjustFollow might execute JoinFrame() because of this. + // Else, nEnd is the end of the last line in the Master. + TextFrameIndex nOld = nEnd; + nEnd = rLine.GetEnd(); + if( GetFollow() ) + { + if( nNew && nOld < nEnd ) + RemoveFootnote( nOld, nEnd - nOld ); + ChangeOffset( GetFollow(), nEnd ); + if( !bDelta ) + GetFollow()->ManipOfst( nEnd ); + } + } + else + { // If we pass over lines, we must not call Join in Follows, instead we even + // need to create a Follow. + // We also need to do this if the whole mass of text remains in the Master, + // because a hard line break could necessitate another line (without text mass)! + nEnd = rLine.GetEnd(); + if( GetFollow() ) + { + // Another case for not joining the follow: + // Text frame has no content, but a numbering. Then, do *not* join. + // Example of this case: When an empty, but numbered paragraph + // at the end of page is completely displaced by a fly frame. + // Thus, the text frame introduced a follow by a + // <SwTextFrame::SplitFrame(..)> - see below. The follow then shows + // the numbering and must stay. + if ( GetFollow()->GetOffset() != nEnd || + GetFollow()->IsFieldFollow() || + (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule())) + { + nNew |= 3; + } + else if (FindTabFrame() && nEnd > TextFrameIndex(0) && + rLine.GetInfo().GetChar(nEnd - TextFrameIndex(1)) == CH_BREAK) + { + // We are in a table, the paragraph has a follow and the text + // ends with a hard line break. Don't join the follow just + // because the follow would have no content, we may still need it + // for the paragraph mark. + nNew |= 1; + } + ChangeOffset( GetFollow(), nEnd ); + GetFollow()->ManipOfst( nEnd ); + } + else + { + // Only split frame, if the frame contains + // content or contains no content, but has a numbering. + // i#84870 - No split, if text frame only contains one + // as-character anchored object. + if ( !bOnlyContainsAsCharAnchoredObj && + (nStrLen > TextFrameIndex(0) || + (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule())) + ) + { + SplitFrame( nEnd ); + nNew |= 3; + } + } + // If the remaining height changed e.g by RemoveFootnote() we need to + // fill up in order to avoid oscillation. + if( bDummy && pBodyFrame && + nBodyHeight < ( IsVertical() ? + pBodyFrame->getFrameArea().Width() : + pBodyFrame->getFrameArea().Height() ) ) + rLine.MakeDummyLine(); + } + + // In AdjustFrame() we set ourselves via Grow/Shrink + // In AdjustFollow() we set our FollowFrame + + const SwTwips nDocPrtTop = getFrameArea().Top() + getFramePrintArea().Top(); + const SwTwips nOldHeight = getFramePrintArea().SSize().Height(); + SwTwips nChg = rLine.CalcBottomLine() - nDocPrtTop - nOldHeight; + + //#i84870# - no shrink of text frame, if it only contains one as-character anchored object. + if ( nChg < 0 && !bDelta && bOnlyContainsAsCharAnchoredObj ) + { + nChg = 0; + } + + // Vertical Formatting: + // The (rotated) repaint rectangle's x coordinate refers to the frame. + // If the frame grows (or shirks) the repaint rectangle cannot simply + // be rotated back after formatting, because we use the upper left point + // of the frame for rotation. This point changes when growing/shrinking. + + if ( IsVertical() && !IsVertLR() && nChg ) + { + SwRect &rRepaint = pPara->GetRepaint(); + rRepaint.Left( rRepaint.Left() - nChg ); + rRepaint.Width( rRepaint.Width() - nChg ); + } + + AdjustFrame( nChg, bHasToFit ); + + if( HasFollow() || IsInFootnote() ) + AdjustFollow_( rLine, nEnd, nStrLen, nNew ); + + pPara->SetPrepMustFit( false ); +} + +// bPrev is set whether Reformat.Start() was called because of Prev(). +// Else, wo don't know whether we can limit the repaint or not. +bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev ) +{ + OSL_ENSURE( ! IsVertical() || IsSwapped(), + "SwTextFrame::FormatLine( rLine, bPrev) with unswapped frame" ); + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + const SwLineLayout *pOldCur = rLine.GetCurr(); + const TextFrameIndex nOldLen = pOldCur->GetLen(); + const sal_uInt16 nOldAscent = pOldCur->GetAscent(); + const sal_uInt16 nOldHeight = pOldCur->Height(); + const SwTwips nOldWidth = pOldCur->Width() + pOldCur->GetHangingMargin(); + const bool bOldHyph = pOldCur->IsEndHyph(); + SwTwips nOldTop = 0; + SwTwips nOldBottom = 0; + if( rLine.GetCurr()->IsClipping() ) + rLine.CalcUnclipped( nOldTop, nOldBottom ); + + TextFrameIndex const nNewStart = rLine.FormatLine( rLine.GetStart() ); + + OSL_ENSURE( getFrameArea().Pos().Y() + getFramePrintArea().Pos().Y() == rLine.GetFirstPos(), + "SwTextFrame::FormatLine: frame leaves orbit." ); + OSL_ENSURE( rLine.GetCurr()->Height(), + "SwTextFrame::FormatLine: line height is zero" ); + + // The current line break object + const SwLineLayout *pNew = rLine.GetCurr(); + + bool bUnChg = nOldLen == pNew->GetLen() && + bOldHyph == pNew->IsEndHyph(); + if ( bUnChg && !bPrev ) + { + const long nWidthDiff = nOldWidth > pNew->Width() + ? nOldWidth - pNew->Width() + : pNew->Width() - nOldWidth; + + // we only declare a line as unchanged, if its main values have not + // changed and it is not the last line (!paragraph end symbol!) + bUnChg = nOldHeight == pNew->Height() && + nOldAscent == pNew->GetAscent() && + nWidthDiff <= SLOPPY_TWIPS && + pOldCur->GetNext(); + } + + // Calculate rRepaint + const SwTwips nBottom = rLine.Y() + rLine.GetLineHeight(); + SwRepaint &rRepaint = pPara->GetRepaint(); + if( bUnChg && rRepaint.Top() == rLine.Y() + && (bPrev || nNewStart <= pPara->GetReformat().Start()) + && (nNewStart < TextFrameIndex(GetText().getLength()))) + { + rRepaint.Top( nBottom ); + rRepaint.Height( 0 ); + } + else + { + if( nOldTop ) + { + if( nOldTop < rRepaint.Top() ) + rRepaint.Top( nOldTop ); + if( !rLine.IsUnclipped() || nOldBottom > rRepaint.Bottom() ) + { + rRepaint.Bottom( nOldBottom - 1 ); + rLine.SetUnclipped( true ); + } + } + if( rLine.GetCurr()->IsClipping() && rLine.IsFlyInCntBase() ) + { + SwTwips nTmpTop, nTmpBottom; + rLine.CalcUnclipped( nTmpTop, nTmpBottom ); + if( nTmpTop < rRepaint.Top() ) + rRepaint.Top( nTmpTop ); + if( !rLine.IsUnclipped() || nTmpBottom > rRepaint.Bottom() ) + { + rRepaint.Bottom( nTmpBottom - 1 ); + rLine.SetUnclipped( true ); + } + } + else + { + if( !rLine.IsUnclipped() || nBottom > rRepaint.Bottom() ) + { + rRepaint.Bottom( nBottom - 1 ); + rLine.SetUnclipped( false ); + } + } + SwTwips nRght = std::max( nOldWidth, pNew->Width() + + pNew->GetHangingMargin() ); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const SwViewOption *pOpt = pSh ? pSh->GetViewOptions() : nullptr; + if( pOpt && (pOpt->IsParagraph() || pOpt->IsLineBreak()) ) + nRght += ( std::max( nOldAscent, pNew->GetAscent() ) ); + else + nRght += ( std::max( nOldAscent, pNew->GetAscent() ) / 4); + nRght += rLine.GetLeftMargin(); + if( rRepaint.GetOffset() || rRepaint.GetRightOfst() < nRght ) + rRepaint.SetRightOfst( nRght ); + + // Finally we enlarge the repaint rectangle if we found an underscore + // within our line. 40 Twips should be enough + const bool bHasUnderscore = + ( rLine.GetInfo().GetUnderScorePos() < nNewStart ); + if ( bHasUnderscore || rLine.GetCurr()->HasUnderscore() ) + rRepaint.Bottom( rRepaint.Bottom() + 40 ); + + const_cast<SwLineLayout*>(rLine.GetCurr())->SetUnderscore( bHasUnderscore ); + } + + // Calculating the good ol' nDelta + pPara->GetDelta() -= sal_Int32(pNew->GetLen()) - sal_Int32(nOldLen); + + // Stop! + if( rLine.IsStop() ) + return false; + + // Absolutely another line + if( rLine.IsNewLine() ) + return true; + + // Until the String's end? + if (nNewStart >= TextFrameIndex(GetText().getLength())) + return false; + + if( rLine.GetInfo().IsShift() ) + return true; + + // Reached the Reformat's end? + const TextFrameIndex nEnd = pPara->GetReformat().Start() + + pPara->GetReformat().Len(); + + if( nNewStart <= nEnd ) + return true; + + return 0 != pPara->GetDelta(); +} + +void SwTextFrame::Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf, + const bool bAdjust ) +{ + OSL_ENSURE( ! IsVertical() || IsSwapped(),"SwTextFrame::Format_ with unswapped frame" ); + + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + rLine.SetUnclipped( false ); + + const OUString & rString = GetText(); + const TextFrameIndex nStrLen(rString.getLength()); + + SwCharRange &rReformat = pPara->GetReformat(); + SwRepaint &rRepaint = pPara->GetRepaint(); + std::unique_ptr<SwRepaint> pFreeze; + + // Due to performance reasons we set rReformat to COMPLETE_STRING in Init() + // In this case we adjust rReformat + if( rReformat.Len() > nStrLen ) + rReformat.Len() = nStrLen; + + if( rReformat.Start() + rReformat.Len() > nStrLen ) + rReformat.Len() = nStrLen - rReformat.Start(); + + SwTwips nOldBottom; + if( GetOffset() && !IsFollow() ) + { + rLine.Bottom(); + nOldBottom = rLine.Y(); + rLine.Top(); + } + else + nOldBottom = 0; + rLine.CharToLine( rReformat.Start() ); + + // When inserting or removing a Space, words can be moved out of the edited + // line and into the preceding line, hence the preceding line must be + // formatted as well. + // Optimization: If rReformat starts after the first word of the line, + // this line cannot possibly influence the previous one. + // ...Turns out that unfortunately it can: Text size changes + FlyFrames; + // the feedback can affect multiple lines (Frames!)! + + // i#46560 + // FME: Yes, consider this case: "(word )" has to go to the next line + // because ")" is a forbidden character at the beginning of a line although + // "(word" would still fit on the previous line. Adding text right in front + // of ")" would not trigger a reformatting of the previous line. Adding 1 + // to the result of FindBrk() does not solve the problem in all cases, + // nevertheless it should be sufficient. + bool bPrev = rLine.GetPrev() && + (FindBrk(rString, rLine.GetStart(), rReformat.Start() + TextFrameIndex(1)) + // i#46560 + + TextFrameIndex(1) + >= rReformat.Start() || + rLine.GetCurr()->IsRest() ); + if( bPrev ) + { + while( rLine.Prev() ) + if( rLine.GetCurr()->GetLen() && !rLine.GetCurr()->IsRest() ) + { + if( !rLine.GetStart() ) + rLine.Top(); // So that NumDone doesn't get confused + break; + } + TextFrameIndex nNew = rLine.GetStart() + rLine.GetLength(); + if( nNew ) + { + --nNew; + if (CH_BREAK == rString[sal_Int32(nNew)]) + { + ++nNew; + rLine.Next(); + bPrev = false; + } + } + rReformat.Len() += rReformat.Start() - nNew; + rReformat.Start() = nNew; + } + + rRepaint.SetOffset( 0 ); + rRepaint.SetRightOfst( 0 ); + rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + if( pPara->IsMargin() ) + rRepaint.Width( rRepaint.Width() + pPara->GetHangingMargin() ); + rRepaint.Top( rLine.Y() ); + if( 0 >= rRepaint.Width() ) + rRepaint.Width(1); + WidowsAndOrphans aFrameBreak( this, rInf.IsTest() ? 1 : 0 ); + + // rLine is now set to the first line which needs formatting. + // The bFirst flag makes sure that Next() is not called. + // The whole thing looks weird, but we need to make sure that + // rLine stops at the last non-fitting line when calling IsBreakNow. + bool bFirst = true; + bool bFormat = true; + + // The CharToLine() can also get us into the danger zone. + // In that case we need to walk back until rLine is set + // to the non-fitting line. Or else the mass of text is lost, + // because the Ofst was set wrongly in the Follow. + + bool bBreak = ( !pPara->IsPrepMustFit() || rLine.GetLineNr() > 1 ) + && aFrameBreak.IsBreakNowWidAndOrp( rLine ); + if( bBreak ) + { + bool bPrevDone = nullptr != rLine.Prev(); + while( bPrevDone && aFrameBreak.IsBreakNowWidAndOrp(rLine) ) + bPrevDone = nullptr != rLine.Prev(); + if( bPrevDone ) + { + aFrameBreak.SetKeep( false ); + rLine.Next(); + } + rLine.TruncLines(); + + // Play it safe + aFrameBreak.IsBreakNowWidAndOrp(rLine); + } + + /* Meaning if the following flags are set: + + Watch(End/Mid)Hyph: we need to format if we have a break at + the line end/Fly, as long as MaxHyph is reached + + Jump(End/Mid)Flag: the next line which has no break (line end/Fly), + needs to be formatted, because we could wrap now. This might have been + forbidden earlier by MaxHyph + + Watch(End/Mid)Hyph: if the last formatted line got a cutoff point, but + didn't have one before + + Jump(End/Mid)Hyph: if a cutoff point disappears + */ + bool bJumpEndHyph = false; + bool bWatchEndHyph = false; + bool bJumpMidHyph = false; + bool bWatchMidHyph = false; + + const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet(); + rInf.MaxHyph() = rAttrSet.GetHyphenZone().GetMaxHyphens(); + bool bMaxHyph = 0 != rInf.MaxHyph(); + if ( bMaxHyph ) + rLine.InitCntHyph(); + + if( IsFollow() && IsFieldFollow() && rLine.GetStart() == GetOffset() ) + { + SwTextFrame *pMaster = FindMaster(); + OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" ); + const SwLineLayout* pLine=nullptr; + if (pMaster) + { + if (!pMaster->HasPara()) + { // master could be locked because it's being formatted upstack + SAL_WARN("sw", "SwTextFrame::Format_: master not formatted!"); + } + else + { + SwTextSizeInfo aInf( pMaster ); + SwTextIter aMasterLine( pMaster, &aInf ); + aMasterLine.Bottom(); + pLine = aMasterLine.GetCurr(); + assert(aMasterLine.GetEnd() == GetOffset()); + } + } + SwLinePortion* pRest = pLine ? + rLine.MakeRestPortion(pLine, GetOffset()) : nullptr; + if( pRest ) + rInf.SetRest( pRest ); + else + SetFieldFollow( false ); + } + + /* Ad cancel criterion: + * In order to recognize, whether a line does not fit onto the page + * anymore, we need to format it. This overflow is removed again in + * e.g. AdjustFollow. + * Another complication: if we are the Master, we need to traverse + * the lines, because it could happen that one line can overflow + * from the Follow to the Master. + */ + do + { + if( bFirst ) + bFirst = false; + else + { + if ( bMaxHyph ) + { + if ( rLine.GetCurr()->IsEndHyph() ) + rLine.CntEndHyph()++; + else + rLine.CntEndHyph() = 0; + if ( rLine.GetCurr()->IsMidHyph() ) + rLine.CntMidHyph()++; + else + rLine.CntMidHyph() = 0; + } + if( !rLine.Next() ) + { + if( !bFormat ) + { + SwLinePortion* pRest = + rLine.MakeRestPortion( rLine.GetCurr(), rLine.GetEnd() ); + if( pRest ) + rInf.SetRest( pRest ); + } + rLine.Insert( new SwLineLayout() ); + rLine.Next(); + bFormat = true; + } + } + if ( !bFormat && bMaxHyph && + (bWatchEndHyph || bJumpEndHyph || bWatchMidHyph || bJumpMidHyph) ) + { + if ( rLine.GetCurr()->IsEndHyph() ) + { + if ( bWatchEndHyph ) + bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() ); + } + else + { + bFormat = bJumpEndHyph; + bWatchEndHyph = false; + bJumpEndHyph = false; + } + if ( rLine.GetCurr()->IsMidHyph() ) + { + if ( bWatchMidHyph && !bFormat ) + bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() ); + } + else + { + bFormat |= bJumpMidHyph; + bWatchMidHyph = false; + bJumpMidHyph = false; + } + } + if( bFormat ) + { + const bool bOldEndHyph = rLine.GetCurr()->IsEndHyph(); + const bool bOldMidHyph = rLine.GetCurr()->IsMidHyph(); + bFormat = FormatLine( rLine, bPrev ); + // There can only be one bPrev ... (???) + bPrev = false; + if ( bMaxHyph ) + { + if ( rLine.GetCurr()->IsEndHyph() != bOldEndHyph ) + { + bWatchEndHyph = !bOldEndHyph; + bJumpEndHyph = bOldEndHyph; + } + if ( rLine.GetCurr()->IsMidHyph() != bOldMidHyph ) + { + bWatchMidHyph = !bOldMidHyph; + bJumpMidHyph = bOldMidHyph; + } + } + } + + if( !rInf.IsNewLine() ) + { + if( !bFormat ) + bFormat = nullptr != rInf.GetRest(); + if( rInf.IsStop() || rInf.GetIdx() >= nStrLen ) + break; + if( !bFormat && ( !bMaxHyph || ( !bWatchEndHyph && + !bJumpEndHyph && !bWatchMidHyph && !bJumpMidHyph ) ) ) + { + if( GetFollow() ) + { + while( rLine.Next() ) + ; //Nothing + pFreeze.reset(new SwRepaint( rRepaint )); // to minimize painting + } + else + break; + } + } + bBreak = aFrameBreak.IsBreakNowWidAndOrp(rLine); + }while( !bBreak ); + + if( pFreeze ) + { + rRepaint = *pFreeze; + pFreeze.reset(); + } + + if( !rLine.IsStop() ) + { + // If we're finished formatting the text and we still + // have other line objects left, these are superfluous + // now because the text has gotten shorter. + if( rLine.GetStart() + rLine.GetLength() >= nStrLen && + rLine.GetCurr()->GetNext() ) + { + rLine.TruncLines(); + rLine.SetTruncLines( true ); + } + } + + if( !rInf.IsTest() ) + { + // FormatAdjust does not pay off at OnceMore + if( bAdjust || !rLine.GetDropFormat() || !rLine.CalcOnceMore() ) + { + FormatAdjust( rLine, aFrameBreak, nStrLen, rInf.IsStop() ); + } + if( rRepaint.HasArea() ) + SetRepaint(); + rLine.SetTruncLines( false ); + if( nOldBottom ) // We check whether paragraphs that need scrolling can + // be shrunk, so that they don't need scrolling anymore + { + rLine.Bottom(); + SwTwips nNewBottom = rLine.Y(); + if( nNewBottom < nOldBottom ) + SetOffset_(TextFrameIndex(0)); + } + } +} + +void SwTextFrame::FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) +{ + OSL_ENSURE( ! IsVertical() || IsSwapped(), + "A frame is not swapped in SwTextFrame::FormatOnceMore" ); + + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + if( !pPara ) + return; + + // If necessary the pPara + sal_uInt16 nOld = static_cast<const SwTextMargin&>(rLine).GetDropHeight(); + bool bShrink = false; + bool bGrow = false; + bool bGoOn = rLine.IsOnceMore(); + sal_uInt8 nGo = 0; + while( bGoOn ) + { + ++nGo; + rInf.Init(); + rLine.Top(); + if( !rLine.GetDropFormat() ) + rLine.SetOnceMore( false ); + SwCharRange aRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength())); + pPara->GetReformat() = aRange; + Format_( rLine, rInf ); + + bGoOn = rLine.IsOnceMore(); + if( bGoOn ) + { + const sal_uInt16 nNew = static_cast<const SwTextMargin&>(rLine).GetDropHeight(); + if( nOld == nNew ) + bGoOn = false; + else + { + if( nOld > nNew ) + bShrink = true; + else + bGrow = true; + + if( bShrink == bGrow || 5 < nGo ) + bGoOn = false; + + nOld = nNew; + } + + // If something went wrong, we need to reformat again + if( !bGoOn ) + { + rInf.CtorInitTextFormatInfo( getRootFrame()->GetCurrShell()->GetOut(), this ); + rLine.CtorInitTextFormatter( this, &rInf ); + rLine.SetDropLines( 1 ); + rLine.CalcDropHeight( 1 ); + SwCharRange aTmpRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength())); + pPara->GetReformat() = aTmpRange; + Format_( rLine, rInf, true ); + // We paint everything ... + SetCompletePaint(); + } + } + } +} + +void SwTextFrame::Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pPara ) +{ + const bool bIsEmpty = GetText().isEmpty(); + + if ( bIsEmpty ) + { + // Empty lines do not get tortured for very long: + // pPara is cleared, which is the same as: + // *pPara = SwParaPortion; + const bool bMustFit = pPara->IsPrepMustFit(); + pPara->Truncate(); + pPara->FormatReset(); + + // delete pSpaceAdd and pKanaComp + pPara->FinishSpaceAdd(); + pPara->FinishKanaComp(); + pPara->ResetFlags(); + pPara->SetPrepMustFit( bMustFit ); + } + + OSL_ENSURE( ! IsSwapped(), "A frame is swapped before Format_" ); + + if ( IsVertical() ) + SwapWidthAndHeight(); + + SwTextFormatInfo aInf( pRenderContext, this ); + SwTextFormatter aLine( this, &aInf ); + + HideAndShowObjects(); + + Format_( aLine, aInf ); + + if( aLine.IsOnceMore() ) + FormatOnceMore( aLine, aInf ); + + if ( IsVertical() ) + SwapWidthAndHeight(); + + OSL_ENSURE( ! IsSwapped(), "A frame is swapped after Format_" ); + + if( 1 < aLine.GetDropLines() ) + { + if( SvxAdjust::Left != aLine.GetAdjust() && + SvxAdjust::Block != aLine.GetAdjust() ) + { + aLine.CalcDropAdjust(); + aLine.SetPaintDrop( true ); + } + + if( aLine.IsPaintDrop() ) + { + aLine.CalcDropRepaint(); + aLine.SetPaintDrop( false ); + } + } +} + +// We calculate the text frame's size and send a notification. +// Shrink() or Grow() to adjust the frame's size to the changed required space. +void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs * ) +{ + SwRectFnSet aRectFnSet(this); + + CalcAdditionalFirstLineOffset(); + + // The range autopilot or the BASIC interface pass us TextFrames with + // a width <= 0 from time to time + if( aRectFnSet.GetWidth(getFramePrintArea()) <= 0 ) + { + // If MustFit is set, we shrink to the Upper's bottom edge if needed. + // Else we just take a standard size of 12 Pt. (240 twip). + SwTextLineAccess aAccess( this ); + long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + + if( aAccess.GetPara()->IsPrepMustFit() ) + { + const SwTwips nLimit = aRectFnSet.GetPrtBottom(*GetUpper()); + const SwTwips nDiff = - aRectFnSet.BottomDist( getFrameArea(), nLimit ); + if( nDiff > 0 ) + Shrink( nDiff ); + } + else if( 240 < nFrameHeight ) + { + Shrink( nFrameHeight - 240 ); + } + else if( 240 > nFrameHeight ) + { + Grow( 240 - nFrameHeight ); + } + + nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + const long nTop = aRectFnSet.GetTopMargin(*this); + + if( nTop > nFrameHeight ) + { + aRectFnSet.SetYMargins( *this, nFrameHeight, 0 ); + } + else if( aRectFnSet.GetHeight(getFramePrintArea()) < 0 ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + return; + } + + const TextFrameIndex nStrLen(GetText().getLength()); + if ( nStrLen || !FormatEmpty() ) + { + + SetEmpty( false ); + // In order to not get confused by nested Formats + FormatLevel aLevel; + if( 12 == FormatLevel::GetLevel() ) + return; + + // We could be possibly not allowed to alter the format information + if( IsLocked() ) + return; + + // Attention: Format() could be triggered by GetFormatted() + if( IsHiddenNow() ) + { + long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( nPrtHeight ) + { + HideHidden(); + Shrink( nPrtHeight ); + } + else + { + // Assure that objects anchored + // at paragraph resp. at/as character inside paragraph + // are hidden. + HideAndShowObjects(); + } + ChgThisLines(); + return; + } + + // We do not want to be interrupted during formatting + TextFrameLockGuard aLock(this); + + // this is to ensure that the similar code in SwTextFrame::Format_ + // finds the master formatted in case it's needed + if (IsFollow() && IsFieldFollow()) + { + SwTextFrame *pMaster = FindMaster(); + assert(pMaster); + if (!pMaster->HasPara()) + { + pMaster->GetFormatted(); + } + if (!pMaster->HasPara()) + { // master could be locked because it's being formatted upstack + SAL_WARN("sw", "SwTextFrame::Format: failed to format master!"); + } + else + { + SwTextSizeInfo aInf( pMaster ); + SwTextIter aMasterLine( pMaster, &aInf ); + aMasterLine.Bottom(); + SetOffset(aMasterLine.GetEnd()); + } + } + + SwTextLineAccess aAccess( this ); + const bool bNew = !aAccess.IsAvailable(); + const bool bSetOffset = + (GetOffset() && GetOffset() > TextFrameIndex(GetText().getLength())); + + if( CalcPreps() ) + ; // nothing + // We return if already formatted, but if the TextFrame was just created + // and does not have any format information + else if( !bNew && !aAccess.GetPara()->GetReformat().Len() ) + { + if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue()) + { + aAccess.GetPara()->SetPrepAdjust(); + aAccess.GetPara()->SetPrep(); + CalcPreps(); + } + SetWidow( false ); + } + else if( bSetOffset && IsFollow() ) + { + SwTextFrame *pMaster = FindMaster(); + OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" ); + if( pMaster ) + pMaster->Prepare( PrepareHint::FollowFollows ); + SwTwips nMaxY = aRectFnSet.GetPrtBottom(*GetUpper()); + + if( aRectFnSet.OverStep( getFrameArea(), nMaxY ) ) + { + aRectFnSet.SetLimit( *this, nMaxY ); + } + else if( aRectFnSet.BottomDist( getFrameArea(), nMaxY ) < 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, -aRectFnSet.GetHeight(aFrm) ); + } + } + else + { + // bSetOffset here means that we have the "red arrow situation" + if ( bSetOffset ) + SetOffset_(TextFrameIndex(0)); + + const bool bOrphan = IsWidow(); + const SwFootnoteBossFrame* pFootnoteBoss = HasFootnote() ? FindFootnoteBossFrame() : nullptr; + SwTwips nFootnoteHeight = 0; + if( pFootnoteBoss ) + { + const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont(); + nFootnoteHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0; + } + do + { + Format_( pRenderContext, aAccess.GetPara() ); + if( pFootnoteBoss && nFootnoteHeight ) + { + const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont(); + SwTwips nNewHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0; + // If we lost some footnotes, we may have more space + // for our main text, so we have to format again ... + if( nNewHeight < nFootnoteHeight ) + nFootnoteHeight = nNewHeight; + else + break; + } + else + break; + } while ( pFootnoteBoss ); + if( bOrphan ) + { + ValidateFrame(); + SetWidow( false ); + } + } + if( IsEmptyMaster() ) + { + SwFrame* pPre = GetPrev(); + if( pPre && + // i#10826 It's the first, it cannot keep! + pPre->GetIndPrev() && + pPre->GetAttrSet()->GetKeep().GetValue() ) + { + pPre->InvalidatePos(); + } + } + } + + ChgThisLines(); + + // the PrepMustFit should not survive a Format operation + SwParaPortion *pPara = GetPara(); + if ( pPara ) + pPara->SetPrepMustFit( false ); + + CalcBaseOfstForFly(); + CalcHeightOfLastLine(); // i#11860 - Adjust spacing implementation for + // object positioning - Compatibility to MS Word + // tdf#117982 -- Fix cell spacing hides content + // Check if the cell's content has greater size than the row height + if (IsInTab() && GetUpper() && ((GetUpper()->getFramePrintArea().Height() < getFramePrintArea().Height()) + || (getFramePrintArea().Height() <= 0))) + { + SAL_INFO("sw.core", "Warn: Cell content has greater size than cell height!"); + //get font size... + SwTwips aTmpHeight = getFrameArea().Height(); + //...and push it into the text frame + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + //if only bottom margin what we have: + if (GetTopMargin() == 0) + //set the frame to its original location + aPrt.SetTopAndHeight(0, aTmpHeight); + } +} + +// bForceQuickFormat is set if GetFormatted() has been called during the +// painting process. Actually I cannot imagine a situation which requires +// a full formatting of the paragraph during painting, on the other hand +// a full formatting can cause the invalidation of other layout frames, +// e.g., if there are footnotes in this paragraph, and invalid layout +// frames will not calculated during the painting. So I actually want to +// avoid a formatting during painting, but since I'm a coward, I'll only +// force the quick formatting in the situation of issue i29062. +bool SwTextFrame::FormatQuick( bool bForceQuickFormat ) +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), + "SwTextFrame::FormatQuick with swapped frame" ); + + if( IsEmpty() && FormatEmpty() ) + return true; + + // We're very picky: + if( HasPara() || IsWidow() || IsLocked() + || !isFrameAreaSizeValid() || + ( ( IsVertical() ? getFramePrintArea().Width() : getFramePrintArea().Height() ) && IsHiddenNow() ) ) + return false; + + SwTextLineAccess aAccess( this ); + SwParaPortion *pPara = aAccess.GetPara(); + if( !pPara ) + return false; + + SwFrameSwapper aSwapper( this, true ); + + TextFrameLockGuard aLock(this); + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true ); + if( 0 != aInf.MaxHyph() ) // Respect MaxHyphen! + return false; + + SwTextFormatter aLine( this, &aInf ); + + // DropCaps are too complicated ... + if( aLine.GetDropFormat() ) + return false; + + TextFrameIndex nStart = GetOffset(); + const TextFrameIndex nEnd = GetFollow() + ? GetFollow()->GetOffset() + : TextFrameIndex(aInf.GetText().getLength()); + + int nLoopProtection = 0; + do + { + TextFrameIndex nNewStart = aLine.FormatLine(nStart); + if (nNewStart == nStart) + ++nLoopProtection; + else + nLoopProtection = 0; + nStart = nNewStart; + const bool bWillEndlessInsert = nLoopProtection > 250; + SAL_WARN_IF(bWillEndlessInsert, "sw", "loop detection triggered"); + if ((!bWillEndlessInsert) // Check for special case: line is invisible, + // like in too thin table cell: tdf#66141 + && (aInf.IsNewLine() || (!aInf.IsStop() && nStart < nEnd))) + aLine.Insert( new SwLineLayout() ); + } while( aLine.Next() ); + + // Last exit: the heights need to match + Point aTopLeft( getFrameArea().Pos() ); + aTopLeft += getFramePrintArea().Pos(); + const SwTwips nNewHeight = aLine.Y() + aLine.GetLineHeight(); + const SwTwips nOldHeight = aTopLeft.Y() + getFramePrintArea().Height(); + + if( !bForceQuickFormat && nNewHeight != nOldHeight && !IsUndersized() ) + { + // Attention: This situation can occur due to FormatLevel==12. Don't panic! + TextFrameIndex const nStrt = GetOffset(); + InvalidateRange_( SwCharRange( nStrt, nEnd - nStrt) ); + return false; + } + + if (m_pFollow && nStart != static_cast<SwTextFrame*>(m_pFollow)->GetOffset()) + return false; // can be caused by e.g. Orphans + + // We made it! + + // Set repaint + pPara->GetRepaint().Pos( aTopLeft ); + pPara->GetRepaint().SSize( getFramePrintArea().SSize() ); + + // Delete reformat + pPara->GetReformat() = SwCharRange(); + pPara->GetDelta() = 0; + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/frminf.cxx b/sw/source/core/text/frminf.cxx new file mode 100644 index 000000000..f42fef695 --- /dev/null +++ b/sw/source/core/text/frminf.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 <sal/config.h> + +#include <o3tl/safeint.hxx> + +#include <frminf.hxx> +#include "itrtxt.hxx" + +TextFrameIndex SwTextMargin::GetTextStart() const +{ + const OUString &rText = GetInfo().GetText(); + const TextFrameIndex nEnd = m_nStart + m_pCurr->GetLen(); + + for (TextFrameIndex i = m_nStart; i < nEnd; ++i) + { + const sal_Unicode aChar = rText[sal_Int32(i)]; + if( CH_TAB != aChar && ' ' != aChar ) + return i; + } + return nEnd; +} + +TextFrameIndex SwTextMargin::GetTextEnd() const +{ + const OUString &rText = GetInfo().GetText(); + const TextFrameIndex nEnd = m_nStart + m_pCurr->GetLen(); + for (TextFrameIndex i = nEnd - TextFrameIndex(1); i >= m_nStart; --i) + { + const sal_Unicode aChar = rText[sal_Int32(i)]; + if( CH_TAB != aChar && CH_BREAK != aChar && ' ' != aChar ) + return i + TextFrameIndex(1); + } + return m_nStart; +} + +// Does the paragraph fit into one line? +bool SwTextFrameInfo::IsOneLine() const +{ + const SwLineLayout *pLay = pFrame->GetPara(); + if( !pLay ) + return false; + + // For follows false of course + if( pFrame->GetFollow() ) + return false; + + pLay = pLay->GetNext(); + while( pLay ) + { + if( pLay->GetLen() ) + return false; + pLay = pLay->GetNext(); + } + return true; +} + +// Is the line filled for X percent? +bool SwTextFrameInfo::IsFilled( const sal_uInt8 nPercent ) const +{ + const SwLineLayout *pLay = pFrame->GetPara(); + if( !pLay ) + return false; + + long nWidth = pFrame->getFramePrintArea().Width(); + nWidth *= nPercent; + nWidth /= 100; + return o3tl::make_unsigned(nWidth) <= pLay->Width(); +} + +// Where does the text start (without whitespace)? (document global) +SwTwips SwTextFrameInfo::GetLineStart( const SwTextCursor &rLine ) +{ + const TextFrameIndex nTextStart = rLine.GetTextStart(); + if( rLine.GetStart() == nTextStart ) + return rLine.GetLineStart(); + + SwRect aRect; + const_cast<SwTextCursor&>(rLine).GetCharRect( &aRect, nTextStart ); + return aRect.Left(); +} + +// Where does the text start (without whitespace)? (relative in the Frame) +SwTwips SwTextFrameInfo::GetLineStart() const +{ + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(pFrame) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(pFrame), &aInf ); + return GetLineStart( aLine ) - pFrame->getFrameArea().Left() - pFrame->getFramePrintArea().Left(); +} + +// Calculates the character's position and returns the middle position +SwTwips SwTextFrameInfo::GetCharPos(TextFrameIndex const nChar, bool bCenter) const +{ + SwRectFnSet aRectFnSet(pFrame); + SwFrameSwapper aSwapper( pFrame, true ); + + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(pFrame) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(pFrame), &aInf ); + + SwTwips nStt, nNext; + SwRect aRect; + aLine.GetCharRect( &aRect, nChar ); + if ( aRectFnSet.IsVert() ) + pFrame->SwitchHorizontalToVertical( aRect ); + + nStt = aRectFnSet.GetLeft(aRect); + + if( !bCenter ) + return nStt - aRectFnSet.GetLeft(pFrame->getFrameArea()); + + aLine.GetCharRect( &aRect, nChar + TextFrameIndex(1) ); + if ( aRectFnSet.IsVert() ) + pFrame->SwitchHorizontalToVertical( aRect ); + + nNext = aRectFnSet.GetLeft(aRect); + + return (( nNext + nStt ) / 2 ) - aRectFnSet.GetLeft(pFrame->getFrameArea()); +} + +static void +AddRange(std::vector<std::pair<TextFrameIndex, TextFrameIndex>> & rRanges, + TextFrameIndex const nPos, TextFrameIndex const nLen) +{ + assert(rRanges.empty() || rRanges.back().second <= nPos); + if( nLen ) + { + if (!rRanges.empty() && nPos == rRanges.back().second) + { + rRanges.back().second += nLen; + } + else + { + rRanges.emplace_back(nPos, nPos + nLen); + } + } +} + +// Accumulates the whitespace at line start and end in the vector +void SwTextFrameInfo::GetSpaces( + std::vector<std::pair<TextFrameIndex, TextFrameIndex>> & rRanges, + bool const bWithLineBreak) const +{ + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(pFrame) ); + SwTextMargin aLine( const_cast<SwTextFrame*>(pFrame), &aInf ); + bool bFirstLine = true; + do { + + if( aLine.GetCurr()->GetLen() ) + { + TextFrameIndex nPos = aLine.GetTextStart(); + // Do NOT include the blanks/tabs from the first line + // in the selection + if( !bFirstLine && nPos > aLine.GetStart() ) + AddRange( rRanges, aLine.GetStart(), nPos - aLine.GetStart() ); + + // Do NOT include the blanks/tabs from the last line + // in the selection + if( aLine.GetNext() ) + { + nPos = aLine.GetTextEnd(); + + if( nPos < aLine.GetEnd() ) + { + TextFrameIndex const nOff( !bWithLineBreak && CH_BREAK == + aLine.GetInfo().GetChar(aLine.GetEnd() - TextFrameIndex(1)) + ? 1 : 0 ); + AddRange( rRanges, nPos, aLine.GetEnd() - nPos - nOff ); + } + } + } + bFirstLine = false; + } + while( aLine.Next() ); +} + +// Is there a bullet/symbol etc. at the text position? +// Fonts: CharSet, SYMBOL and DONTKNOW +bool SwTextFrameInfo::IsBullet(TextFrameIndex const nTextStart) const +{ + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(pFrame) ); + SwTextMargin aLine( const_cast<SwTextFrame*>(pFrame), &aInf ); + aInf.SetIdx( nTextStart ); + return aLine.IsSymbol( nTextStart ); +} + +// Get first line indent +// The precondition for a positive or negative first line indent: +// All lines (except for the first one) have the same left margin. +// We do not want to be so picky and work with a tolerance of TOLERANCE twips. +SwTwips SwTextFrameInfo::GetFirstIndent() const +{ + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(pFrame) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(pFrame), &aInf ); + const SwTwips nFirst = GetLineStart( aLine ); + const SwTwips TOLERANCE = 20; + + if( !aLine.Next() ) + return 0; + + SwTwips nLeft = GetLineStart( aLine ); + while( aLine.Next() ) + { + if( aLine.GetCurr()->GetLen() ) + { + const SwTwips nCurrLeft = GetLineStart( aLine ); + if( nLeft + TOLERANCE < nCurrLeft || + nLeft - TOLERANCE > nCurrLeft ) + return 0; + } + } + + // At first we only return +1, -1 and 0 + if( nLeft == nFirst ) + return 0; + + if( nLeft > nFirst ) + return -1; + + return 1; +} + +sal_Int32 SwTextFrameInfo::GetBigIndent(TextFrameIndex& rFndPos, + const SwTextFrame *pNextFrame ) const +{ + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(pFrame) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(pFrame), &aInf ); + SwTwips nNextIndent = 0; + + if( pNextFrame ) + { + // I'm a single line + SwTextSizeInfo aNxtInf( const_cast<SwTextFrame*>(pNextFrame) ); + SwTextCursor aNxtLine( const_cast<SwTextFrame*>(pNextFrame), &aNxtInf ); + nNextIndent = GetLineStart( aNxtLine ); + } + else + { + // I'm multi-line + if( aLine.Next() ) + { + nNextIndent = GetLineStart( aLine ); + aLine.Prev(); + } + } + + if( nNextIndent <= GetLineStart( aLine ) ) + return 0; + + const Point aPoint( nNextIndent, aLine.Y() ); + rFndPos = aLine.GetModelPositionForViewPoint( nullptr, aPoint, false ); + if (TextFrameIndex(1) >= rFndPos) + return 0; + + // Is on front of a non-space + const OUString& rText = aInf.GetText(); + sal_Unicode aChar = rText[sal_Int32(rFndPos)]; + if( CH_TAB == aChar || CH_BREAK == aChar || ' ' == aChar || + (( CH_TXTATR_BREAKWORD == aChar || CH_TXTATR_INWORD == aChar ) && + aInf.HasHint( rFndPos ) ) ) + return 0; + + // and after a space + aChar = rText[sal_Int32(rFndPos) - 1]; + if( CH_TAB != aChar && CH_BREAK != aChar && + ( ( CH_TXTATR_BREAKWORD != aChar && CH_TXTATR_INWORD != aChar ) || + !aInf.HasHint(rFndPos - TextFrameIndex(1))) && + // More than two Blanks! + (' ' != aChar || ' ' != rText[sal_Int32(rFndPos) - 2])) + return 0; + + SwRect aRect; + aLine.GetCharRect( &aRect, rFndPos ); + return static_cast<sal_Int32>(aRect.Left() - pFrame->getFrameArea().Left() - pFrame->getFramePrintArea().Left()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx new file mode 100644 index 000000000..ec3fd196d --- /dev/null +++ b/sw/source/core/text/frmpaint.cxx @@ -0,0 +1,714 @@ +/* -*- 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 <memory> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <editeng/pgrditem.hxx> +#include <editeng/lrspitem.hxx> +#include <tgrditem.hxx> +#include <paratr.hxx> + +#include <fmtline.hxx> +#include <lineinfo.hxx> +#include <charfmt.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <txtfrm.hxx> +#include "itrpaint.hxx" +#include "txtpaint.hxx" +#include "txtcache.hxx" +#include <flyfrm.hxx> +#include "redlnitr.hxx" +#include <swmodule.hxx> +#include <tabfrm.hxx> +#include <numrule.hxx> +#include <wrong.hxx> + +#include <EnhancedPDFExportHelper.hxx> + +#include <IDocumentStylePoolAccess.hxx> + +#define REDLINE_DISTANCE 567/4 +#define REDLINE_MINDIST 567/10 + +using namespace ::com::sun::star; + +static bool bInitFont = true; + +namespace { + +class SwExtraPainter +{ + SwSaveClip m_aClip; + SwRect m_aRect; + const SwTextFrame* m_pTextFrame; + SwViewShell *m_pSh; + std::unique_ptr<SwFont> m_pFnt; + const SwLineNumberInfo &m_rLineInf; + SwTwips m_nX; + SwTwips m_nRedX; + sal_uLong m_nLineNr; + sal_uInt16 m_nDivider; + bool m_bGoLeft; + bool IsClipChg() const { return m_aClip.IsChg(); } + + SwExtraPainter(const SwExtraPainter&) = delete; + SwExtraPainter& operator=(const SwExtraPainter&) = delete; + +public: + SwExtraPainter( const SwTextFrame *pFrame, SwViewShell *pVwSh, + const SwLineNumberInfo &rLnInf, const SwRect &rRct, + sal_Int16 eHor, bool bLnNm ); + SwFont* GetFont() const { return m_pFnt.get(); } + void IncLineNr() { ++m_nLineNr; } + bool HasNumber() const { return !( m_nLineNr % m_rLineInf.GetCountBy() ); } + bool HasDivider() const { + if( !m_nDivider ) return false; + return !(m_nLineNr % m_rLineInf.GetDividerCountBy()); + } + + void PaintExtra( SwTwips nY, long nAsc, long nMax, bool bRed ); + void PaintRedline( SwTwips nY, long nMax ); +}; + +} + +SwExtraPainter::SwExtraPainter( const SwTextFrame *pFrame, SwViewShell *pVwSh, + const SwLineNumberInfo &rLnInf, const SwRect &rRct, + sal_Int16 eHor, bool bLineNum ) + : m_aClip( pVwSh->GetWin() || pFrame->IsUndersized() ? pVwSh->GetOut() : nullptr ) + , m_aRect( rRct ) + , m_pTextFrame( pFrame ) + , m_pSh( pVwSh ) + , m_rLineInf( rLnInf ) + , m_nX(0) + , m_nRedX(0) + , m_nLineNr( 1 ) + , m_nDivider(0) + , m_bGoLeft(false) +{ + if( pFrame->IsUndersized() ) + { + SwTwips nBottom = pFrame->getFrameArea().Bottom(); + if( m_aRect.Bottom() > nBottom ) + m_aRect.Bottom( nBottom ); + } + std::optional<bool> oIsRightPage; + if( bLineNum ) + { + /* Initializes the Members necessary for line numbering: + + nDivider, how often do we want a substring; 0 == never + nX, line number's x position + pFnt, line number's font + nLineNr, the first line number + bLineNum is set back to false if the numbering is completely + outside of the paint rect + */ + m_nDivider = !m_rLineInf.GetDivider().isEmpty() ? m_rLineInf.GetDividerCountBy() : 0; + m_nX = pFrame->getFrameArea().Left(); + SwCharFormat* pFormat = m_rLineInf.GetCharFormat( const_cast<IDocumentStylePoolAccess&>(pFrame->GetDoc().getIDocumentStylePoolAccess()) ); + OSL_ENSURE( pFormat, "PaintExtraData without CharFormat" ); + m_pFnt.reset( new SwFont(&pFormat->GetAttrSet(), &pFrame->GetDoc().getIDocumentSettingAccess()) ); + m_pFnt->Invalidate(); + m_pFnt->ChgPhysFnt( m_pSh, *m_pSh->GetOut() ); + m_pFnt->SetVertical( 0, pFrame->IsVertical() ); + m_nLineNr += pFrame->GetAllLines() - pFrame->GetThisLines(); + LineNumberPosition ePos = m_rLineInf.GetPos(); + if( ePos != LINENUMBER_POS_LEFT && ePos != LINENUMBER_POS_RIGHT ) + { + if( pFrame->FindPageFrame()->OnRightPage() ) + { + oIsRightPage = true; + ePos = ePos == LINENUMBER_POS_INSIDE ? + LINENUMBER_POS_LEFT : LINENUMBER_POS_RIGHT; + } + else + { + oIsRightPage = false; + ePos = ePos == LINENUMBER_POS_OUTSIDE ? + LINENUMBER_POS_LEFT : LINENUMBER_POS_RIGHT; + } + } + if( LINENUMBER_POS_LEFT == ePos ) + { + m_bGoLeft = true; + m_nX -= m_rLineInf.GetPosFromLeft(); + } + else + { + m_bGoLeft = false; + m_nX += pFrame->getFrameArea().Width() + m_rLineInf.GetPosFromLeft(); + } + } + if( eHor != text::HoriOrientation::NONE ) + { + if( text::HoriOrientation::INSIDE == eHor || text::HoriOrientation::OUTSIDE == eHor ) + { + if (!oIsRightPage) + oIsRightPage = pFrame->FindPageFrame()->OnRightPage(); + if (*oIsRightPage) + eHor = eHor == text::HoriOrientation::INSIDE ? text::HoriOrientation::LEFT : text::HoriOrientation::RIGHT; + else + eHor = eHor == text::HoriOrientation::OUTSIDE ? text::HoriOrientation::LEFT : text::HoriOrientation::RIGHT; + } + const SwFrame* pTmpFrame = pFrame->FindTabFrame(); + if( !pTmpFrame ) + pTmpFrame = pFrame; + m_nRedX = text::HoriOrientation::LEFT == eHor ? pTmpFrame->getFrameArea().Left() - REDLINE_DISTANCE : + pTmpFrame->getFrameArea().Right() + REDLINE_DISTANCE; + } +} + +void SwExtraPainter::PaintExtra( SwTwips nY, long nAsc, long nMax, bool bRed ) +{ + // Line number is stronger than the divider + const OUString aTmp( HasNumber() ? m_rLineInf.GetNumType().GetNumStr( m_nLineNr ) + : m_rLineInf.GetDivider() ); + + // Get script type of line numbering: + m_pFnt->SetActual( SwScriptInfo::WhichFont(0, aTmp) ); + + SwDrawTextInfo aDrawInf( m_pSh, *m_pSh->GetOut(), aTmp, 0, aTmp.getLength() ); + aDrawInf.SetSpace( 0 ); + aDrawInf.SetWrong( nullptr ); + aDrawInf.SetGrammarCheck( nullptr ); + aDrawInf.SetSmartTags( nullptr ); + aDrawInf.SetFrame( m_pTextFrame ); + aDrawInf.SetFont( m_pFnt.get() ); + aDrawInf.SetSnapToGrid( false ); + aDrawInf.SetIgnoreFrameRTL( true ); + + bool bTooBig = m_pFnt->GetSize( m_pFnt->GetActual() ).Height() > nMax && + m_pFnt->GetHeight( m_pSh, *m_pSh->GetOut() ) > nMax; + SwFont* pTmpFnt; + if( bTooBig ) + { + pTmpFnt = new SwFont( *GetFont() ); + if( nMax >= 20 ) + { + nMax *= 17; + nMax /= 20; + } + pTmpFnt->SetSize( Size( 0, nMax ), pTmpFnt->GetActual() ); + } + else + pTmpFnt = GetFont(); + Point aTmpPos( m_nX, nY ); + aTmpPos.AdjustY(nAsc ); + bool bPaint = true; + if( !IsClipChg() ) + { + Size aSize = pTmpFnt->GetTextSize_( aDrawInf ); + if( m_bGoLeft ) + aTmpPos.AdjustX( -(aSize.Width()) ); + // calculate rectangle containing the line number + SwRect aRct( Point( aTmpPos.X(), + aTmpPos.Y() - pTmpFnt->GetAscent( m_pSh, *m_pSh->GetOut() ) + ), aSize ); + if( !m_aRect.IsInside( aRct ) ) + { + if( aRct.Intersection( m_aRect ).IsEmpty() ) + bPaint = false; + else + m_aClip.ChgClip( m_aRect, m_pTextFrame ); + } + } + else if( m_bGoLeft ) + aTmpPos.AdjustX( -(pTmpFnt->GetTextSize_( aDrawInf ).Width()) ); + aDrawInf.SetPos( aTmpPos ); + if( bPaint ) + pTmpFnt->DrawText_( aDrawInf ); + + if( bTooBig ) + delete pTmpFnt; + if( bRed ) + { + long nDiff = m_bGoLeft ? m_nRedX - m_nX : m_nX - m_nRedX; + if( nDiff > REDLINE_MINDIST ) + PaintRedline( nY, nMax ); + } +} + +void SwExtraPainter::PaintRedline( SwTwips nY, long nMax ) +{ + Point aStart( m_nRedX, nY ); + Point aEnd( m_nRedX, nY + nMax ); + + if( !IsClipChg() ) + { + SwRect aRct( aStart, aEnd ); + if( !m_aRect.IsInside( aRct ) ) + { + if( aRct.Intersection( m_aRect ).IsEmpty() ) + return; + m_aClip.ChgClip( m_aRect, m_pTextFrame ); + } + } + const Color aOldCol( m_pSh->GetOut()->GetLineColor() ); + m_pSh->GetOut()->SetLineColor( SW_MOD()->GetRedlineMarkColor() ); + + if ( m_pTextFrame->IsVertical() ) + { + m_pTextFrame->SwitchHorizontalToVertical( aStart ); + m_pTextFrame->SwitchHorizontalToVertical( aEnd ); + } + + m_pSh->GetOut()->DrawLine( aStart, aEnd ); + m_pSh->GetOut()->SetLineColor( aOldCol ); +} + +void SwTextFrame::PaintExtraData( const SwRect &rRect ) const +{ + if( getFrameArea().Top() > rRect.Bottom() || getFrameArea().Bottom() < rRect.Top() ) + return; + + SwDoc const& rDoc(GetDoc()); + const IDocumentRedlineAccess& rIDRA = rDoc.getIDocumentRedlineAccess(); + const SwLineNumberInfo &rLineInf = rDoc.GetLineNumberInfo(); + const SwFormatLineNumber &rLineNum = GetAttrSet()->GetLineNumber(); + bool bLineNum = !IsInTab() && rLineInf.IsPaintLineNumbers() && + ( !IsInFly() || rLineInf.IsCountInFlys() ) && rLineNum.IsCount(); + sal_Int16 eHor = static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos()); + if (eHor != text::HoriOrientation::NONE + && (!IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags()) + || getRootFrame()->IsHideRedlines())) + { + eHor = text::HoriOrientation::NONE; + } + bool bRedLine = eHor != text::HoriOrientation::NONE; + if ( !bLineNum && !bRedLine ) + return; + + if( IsLocked() || IsHiddenNow() || !getFramePrintArea().Height() ) + return; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + SwRect rOldRect( rRect ); + + if ( IsVertical() ) + SwitchVerticalToHorizontal( const_cast<SwRect&>(rRect) ); + + SwLayoutModeModifier aLayoutModeModifier( *pSh->GetOut() ); + aLayoutModeModifier.Modify( false ); + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pSh->GetOut() ); + + SwExtraPainter aExtra( this, pSh, rLineInf, rRect, eHor, bLineNum ); + + if( HasPara() ) + { + TextFrameLockGuard aLock(const_cast<SwTextFrame*>(this)); + + SwTextLineAccess aAccess( this ); + aAccess.GetPara(); + + SwTextPaintInfo aInf( const_cast<SwTextFrame*>(this), rRect ); + + aLayoutModeModifier.Modify( false ); + + SwTextPainter aLine( const_cast<SwTextFrame*>(this), &aInf ); + bool bNoDummy = !aLine.GetNext(); // Only one empty line! + + while( aLine.Y() + aLine.GetLineHeight() <= rRect.Top() ) + { + if( !aLine.GetCurr()->IsDummy() && + ( rLineInf.IsCountBlankLines() || + aLine.GetCurr()->HasContent() ) ) + aExtra.IncLineNr(); + if( !aLine.Next() ) + { + const_cast<SwRect&>(rRect) = rOldRect; + return; + } + } + + long nBottom = rRect.Bottom(); + + bool bNoPrtLine = 0 == GetMinPrtLine(); + if( !bNoPrtLine ) + { + while ( aLine.Y() < GetMinPrtLine() ) + { + if( ( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() ) + && !aLine.GetCurr()->IsDummy() ) + aExtra.IncLineNr(); + if( !aLine.Next() ) + break; + } + bNoPrtLine = aLine.Y() >= GetMinPrtLine(); + } + if( bNoPrtLine ) + { + do + { + if( bNoDummy || !aLine.GetCurr()->IsDummy() ) + { + bool bRed = bRedLine && aLine.GetCurr()->HasRedline(); + if( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() ) + { + if( bLineNum && + ( aExtra.HasNumber() || aExtra.HasDivider() ) ) + { + sal_uInt16 nTmpHeight, nTmpAscent; + aLine.CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + aExtra.PaintExtra( aLine.Y(), nTmpAscent, + nTmpHeight, bRed ); + bRed = false; + } + aExtra.IncLineNr(); + } + if( bRed ) + aExtra.PaintRedline( aLine.Y(), aLine.GetLineHeight() ); + } + } while( aLine.Next() && aLine.Y() <= nBottom ); + } + } + else + { + if (!GetMergedPara() && + SwRedlineTable::npos == rIDRA.GetRedlinePos(*GetTextNodeFirst(), RedlineType::Any)) + { + bRedLine = false; + } + + if( bLineNum && rLineInf.IsCountBlankLines() && + ( aExtra.HasNumber() || aExtra.HasDivider() ) ) + { + aExtra.PaintExtra( getFrameArea().Top()+getFramePrintArea().Top(), aExtra.GetFont() + ->GetAscent( pSh, *pSh->GetOut() ), getFramePrintArea().Height(), bRedLine ); + } + else if( bRedLine ) + aExtra.PaintRedline( getFrameArea().Top()+getFramePrintArea().Top(), getFramePrintArea().Height() ); + } + + const_cast<SwRect&>(rRect) = rOldRect; + +} + +SwRect SwTextFrame::GetPaintSwRect() +{ + // finger layout + OSL_ENSURE( isFrameAreaPositionValid(), "+SwTextFrame::GetPaintSwRect: no Calc()" ); + + SwRect aRet( getFramePrintArea() ); + if ( IsEmpty() || !HasPara() ) + aRet += getFrameArea().Pos(); + else + { + // We return the right paint rect. Use the calculated PaintOfst as the + // left margin + SwRepaint& rRepaint = GetPara()->GetRepaint(); + long l; + + if ( IsVertLR() && !IsVertLRBT()) // mba: the following line was added, but we don't need it for the existing directions; kept for IsVertLR(), but should be checked + rRepaint.Chg( GetUpper()->getFrameArea().Pos() + GetUpper()->getFramePrintArea().Pos(), GetUpper()->getFramePrintArea().SSize() ); + + if( rRepaint.GetOffset() ) + rRepaint.Left( rRepaint.GetOffset() ); + + l = rRepaint.GetRightOfst(); + if( l && l > rRepaint.Right() ) + rRepaint.Right( l ); + rRepaint.SetOffset( 0 ); + aRet = rRepaint; + + // In case our left edge is the same as the body frame's left edge, + // then extend the rectangle to include the page margin as well, + // otherwise some font will be clipped. + SwLayoutFrame* pBodyFrame = GetUpper(); + if (pBodyFrame->IsBodyFrame() && aRet.Left() == (pBodyFrame->getFrameArea().Left() + pBodyFrame->getFramePrintArea().Left())) + if (SwLayoutFrame* pPageFrame = pBodyFrame->GetUpper()) + aRet.Left(pPageFrame->getFrameArea().Left()); + + if ( IsRightToLeft() ) + SwitchLTRtoRTL( aRet ); + + if ( IsVertical() ) + SwitchHorizontalToVertical( aRet ); + } + ResetRepaint(); + + return aRet; +} + +bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const +{ + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && ( pSh->GetViewOptions()->IsParagraph() || bInitFont ) ) + { + bInitFont = false; + SwTextFly aTextFly( this ); + aTextFly.SetTopRule(); + SwRect aRect; + if( bCheck && aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) ) + return false; + else if( pSh->GetWin() ) + { + std::unique_ptr<SwFont> pFnt; + const SwTextNode& rTextNode = *GetTextNodeForParaProps(); + if ( rTextNode.HasSwAttrSet() ) + { + const SwAttrSet *pAttrSet = &( rTextNode.GetSwAttrSet() ); + pFnt.reset(new SwFont( pAttrSet, rTextNode.getIDocumentSettingAccess() )); + } + else + { + SwFontAccess aFontAccess( &rTextNode.GetAnyFormatColl(), pSh ); + pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() )); + } + + const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess(); + if (IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags()) + && !getRootFrame()->IsHideRedlines()) + { + const SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwAttrHandler aAttrHandler; + aAttrHandler.Init( rTextNode.GetSwAttrSet(), + *rTextNode.getIDocumentSettingAccess() ); + SwRedlineItr aRedln(rTextNode, *pFnt, aAttrHandler, nRedlPos, SwRedlineItr::Mode::Show); + } + } + + if( pSh->GetViewOptions()->IsParagraph() && getFramePrintArea().Height() ) + { + if( RTL_TEXTENCODING_SYMBOL == pFnt->GetCharSet( SwFontScript::Latin ) && + pFnt->GetName( SwFontScript::Latin ) != numfunc::GetDefBulletFontname() ) + { + pFnt->SetFamily( FAMILY_DONTKNOW, SwFontScript::Latin ); + pFnt->SetName( numfunc::GetDefBulletFontname(), SwFontScript::Latin ); + pFnt->SetStyleName(OUString(), SwFontScript::Latin); + pFnt->SetCharSet( RTL_TEXTENCODING_SYMBOL, SwFontScript::Latin ); + } + pFnt->SetVertical( 0, IsVertical() ); + SwFrameSwapper aSwapper( this, true ); + SwLayoutModeModifier aLayoutModeModifier( *pSh->GetOut() ); + aLayoutModeModifier.Modify( IsRightToLeft() ); + + pFnt->Invalidate(); + pFnt->ChgPhysFnt( pSh, *pSh->GetOut() ); + Point aPos = getFrameArea().Pos() + getFramePrintArea().Pos(); + + const SvxLRSpaceItem &rSpace = + GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace(); + + if ( rSpace.GetTextFirstLineOffset() > 0 ) + aPos.AdjustX(rSpace.GetTextFirstLineOffset() ); + + std::unique_ptr<SwSaveClip> pClip; + if( IsUndersized() ) + { + pClip.reset(new SwSaveClip( pSh->GetOut() )); + pClip->ChgClip( rRect ); + } + + aPos.AdjustY(pFnt->GetAscent( pSh, *pSh->GetOut() ) ); + + if (GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue() && + IsInDocBody() ) + { + SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame())); + if ( pGrid ) + { + // center character in grid line + aPos.AdjustY(( pGrid->GetBaseHeight() - + pFnt->GetHeight( pSh, *pSh->GetOut() ) ) / 2 ); + + if ( ! pGrid->GetRubyTextBelow() ) + aPos.AdjustY(pGrid->GetRubyHeight() ); + } + } + + // Don't show the paragraph mark for collapsed paragraphs, when they are hidden + if ( EmptyHeight( ) > 1 ) + { + const OUString aTmp( CH_PAR ); + SwDrawTextInfo aDrawInf( pSh, *pSh->GetOut(), aTmp, 0, 1 ); + aDrawInf.SetPos( aPos ); + aDrawInf.SetSpace( 0 ); + aDrawInf.SetKanaComp( 0 ); + aDrawInf.SetWrong( nullptr ); + aDrawInf.SetGrammarCheck( nullptr ); + aDrawInf.SetSmartTags( nullptr ); + aDrawInf.SetFrame( this ); + aDrawInf.SetFont( pFnt.get() ); + aDrawInf.SetSnapToGrid( false ); + + pFnt->SetColor(NON_PRINTING_CHARACTER_COLOR); + pFnt->DrawText_( aDrawInf ); + } + } + return true; + } + } + else + return true; + return false; +} + +void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + ResetRepaint(); + + // #i16816# tagged pdf support + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + Num_Info aNumInfo( *this ); + SwTaggedPDFHelper aTaggedPDFHelperNumbering( &aNumInfo, nullptr, nullptr, rRenderContext ); + + Frame_Info aFrameInfo( *this ); + SwTaggedPDFHelper aTaggedPDFHelperParagraph( nullptr, &aFrameInfo, nullptr, rRenderContext ); + + if( IsEmpty() && PaintEmpty( rRect, true ) ) + return; + + if( IsLocked() || IsHiddenNow() || ! getFramePrintArea().HasArea() ) + return; + + // It can happen that the IdleCollector withdrew my cached information + if( !HasPara() ) + { + OSL_ENSURE( isFrameAreaPositionValid(), "+SwTextFrame::PaintSwFrame: no Calc()" ); + + // #i29062# pass info that we are currently + // painting. + const_cast<SwTextFrame*>(this)->GetFormatted( true ); + if( IsEmpty() ) + { + PaintEmpty( rRect, false ); + return; + } + if( !HasPara() ) + { + OSL_ENSURE( false, "+SwTextFrame::PaintSwFrame: missing format information" ); + return; + } + } + + // We don't want to be interrupted while painting. + // Do that after thr Format()! + TextFrameLockGuard aLock(const_cast<SwTextFrame*>(this)); + + // We only paint the part of the TextFrame which changed, is within the + // range and was requested to paint. + // One could think that the area rRect _needs_ to be painted, although + // rRepaint is set. Indeed, we cannot avoid this problem from a formal + // perspective. Luckily we can assume rRepaint to be empty when we need + // paint the while Frame. + SwTextLineAccess aAccess( this ); + SwParaPortion *pPara = aAccess.GetPara(); + + SwRepaint &rRepaint = pPara->GetRepaint(); + + // Switch off recycling when in the FlyContentFrame. + // A DrawRect is called for repainting the line anyways. + if( rRepaint.GetOffset() ) + { + const SwFlyFrame *pFly = FindFlyFrame(); + if( pFly && pFly->IsFlyInContentFrame() ) + rRepaint.SetOffset( 0 ); + } + + // Ge the String for painting. The length is of special interest. + + // Rectangle + OSL_ENSURE( ! IsSwapped(), "A frame is swapped before Paint" ); + SwRect aOldRect( rRect ); + + { + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + + if ( IsVertical() ) + SwitchVerticalToHorizontal( const_cast<SwRect&>(rRect) ); + + if ( IsRightToLeft() ) + SwitchRTLtoLTR( const_cast<SwRect&>(rRect) ); + + SwTextPaintInfo aInf( const_cast<SwTextFrame*>(this), rRect ); + sw::WrongListIterator iterWrong(*this, &SwTextNode::GetWrong); + sw::WrongListIterator iterGrammar(*this, &SwTextNode::GetGrammarCheck); + sw::WrongListIterator iterSmartTags(*this, &SwTextNode::GetSmartTags); + if (iterWrong.LooksUseful()) + { + aInf.SetWrongList( &iterWrong ); + } + if (iterGrammar.LooksUseful()) + { + aInf.SetGrammarCheckList( &iterGrammar ); + } + if (iterSmartTags.LooksUseful()) + { + aInf.SetSmartTags( &iterSmartTags ); + } + aInf.GetTextFly().SetTopRule(); + + SwTextPainter aLine( const_cast<SwTextFrame*>(this), &aInf ); + // Optimization: if no free flying Frame overlaps into our line, the + // SwTextFly just switches off + aInf.GetTextFly().Relax(); + + OutputDevice* pOut = aInf.GetOut(); + const bool bOnWin = pSh->GetWin() != nullptr; + + SwSaveClip aClip( bOnWin || IsUndersized() ? pOut : nullptr ); + + // Output loop: For each Line ... (which is still visible) ... + // adapt rRect (Top + 1, Bottom - 1) + // Because the Iterator attaches the Lines without a gap to each other + aLine.TwipsToLine( rRect.Top() + 1 ); + long nBottom = rRect.Bottom(); + + bool bNoPrtLine = 0 == GetMinPrtLine(); + if( !bNoPrtLine ) + { + while ( aLine.Y() < GetMinPrtLine() && aLine.Next() ) + ; + bNoPrtLine = aLine.Y() >= GetMinPrtLine(); + } + if( bNoPrtLine ) + { + do + { + aLine.DrawTextLine( rRect, aClip, IsUndersized() ); + + } while( aLine.Next() && aLine.Y() <= nBottom ); + } + + // Once is enough: + if( aLine.IsPaintDrop() ) + aLine.PaintDropPortion(); + + if( rRepaint.HasArea() ) + rRepaint.Clear(); + } + + const_cast<SwRect&>(rRect) = aOldRect; + + OSL_ENSURE( ! IsSwapped(), "A frame is swapped after Paint" ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx new file mode 100644 index 000000000..0118f7479 --- /dev/null +++ b/sw/source/core/text/guess.cxx @@ -0,0 +1,591 @@ +/* -*- 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 <editeng/unolingu.hxx> +#include <breakit.hxx> +#include <IDocumentSettingAccess.hxx> +#include "guess.hxx" +#include "inftxt.hxx" +#include <pagefrm.hxx> +#include <tgrditem.hxx> +#include <com/sun/star/i18n/BreakType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <unotools/charclass.hxx> +#include "porfld.hxx" +#include <paratr.hxx> +#include <doc.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + +namespace{ + +bool IsBlank(sal_Unicode ch) { return ch == CH_BLANK || ch == CH_FULL_BLANK || ch == CH_NB_SPACE || ch == CH_SIX_PER_EM; } + +} + +// provides information for line break calculation +// returns true if no line break has to be performed +// otherwise possible break or hyphenation position is determined +bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, + const sal_uInt16 nPorHeight ) +{ + nCutPos = rInf.GetIdx(); + + // Empty strings are always 0 + if( !rInf.GetLen() || rInf.GetText().isEmpty() ) + return false; + + OSL_ENSURE( rInf.GetIdx() < TextFrameIndex(rInf.GetText().getLength()), + "+SwTextGuess::Guess: invalid SwTextFormatInfo" ); + + OSL_ENSURE( nPorHeight, "+SwTextGuess::Guess: no height" ); + + sal_uInt16 nMaxSizeDiff; + + const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); + + sal_uInt16 nMaxComp = ( SwFontScript::CJK == rInf.GetFont()->GetActual() ) && + rSI.CountCompChg() && + ! rInf.IsMulti() && + ! rPor.InFieldGrp() && + ! rPor.IsDropPortion() ? + 10000 : + 0 ; + + SwTwips nLineWidth = rInf.GetLineWidth(); + TextFrameIndex nMaxLen = TextFrameIndex(rInf.GetText().getLength()) - rInf.GetIdx(); + + const SvxAdjust& rAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust(); + + // tdf#104668 space chars at the end should be cut if the compatibility option is enabled + // for LTR mode only + if ( !rInf.GetTextFrame()->IsRightToLeft() ) + { + if (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS)) + { + if ( rAdjust == SvxAdjust::Right || rAdjust == SvxAdjust::Center ) + { + TextFrameIndex nSpaceCnt(0); + for (sal_Int32 i = rInf.GetText().getLength() - 1; + sal_Int32(rInf.GetIdx()) <= i; --i) + { + sal_Unicode cChar = rInf.GetText()[i]; + if ( cChar != CH_BLANK && cChar != CH_FULL_BLANK && cChar != CH_SIX_PER_EM ) + break; + ++nSpaceCnt; + } + TextFrameIndex nCharsCnt = nMaxLen - nSpaceCnt; + if ( nSpaceCnt && nCharsCnt < rPor.GetLen() ) + { + nMaxLen = nCharsCnt; + if ( !nMaxLen ) + return true; + } + } + } + } + + if ( rInf.GetLen() < nMaxLen ) + nMaxLen = rInf.GetLen(); + + if( !nMaxLen ) + return false; + + sal_uInt16 nItalic = 0; + if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() ) + { + bool bAddItalic = true; + + // do not add extra italic value if we have an active character grid + if ( rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid( + GetGridItem(rInf.GetTextFrame()->FindPageFrame())); + bAddItalic = !pGrid || GRID_LINES_CHARS != pGrid->GetGridType(); + } + + // do not add extra italic value for an isolated blank: + if (TextFrameIndex(1) == rInf.GetLen() && + CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx())]) + { + bAddItalic = false; + } + + if (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::TAB_OVER_MARGIN)) + { + // Content is allowed over the margin: in this case over-margin content caused by italic + // formatting is OK. + bAddItalic = false; + } + + nItalic = bAddItalic ? nPorHeight / 12 : 0; + + nLineWidth -= nItalic; + + // #i46524# LineBreak bug with italics + if ( nLineWidth < 0 ) nLineWidth = 0; + } + + const sal_Int32 nLeftRightBorderSpace = + (!rPor.GetJoinBorderWithNext() ? rInf.GetFont()->GetRightBorderSpace() : 0) + + (!rPor.GetJoinBorderWithPrev() ? rInf.GetFont()->GetLeftBorderSpace() : 0); + + nLineWidth -= nLeftRightBorderSpace; + + const bool bUnbreakableNumberings = rInf.GetTextFrame()->GetDoc() + .getIDocumentSettingAccess().get(DocumentSettingId::UNBREAKABLE_NUMBERINGS); + + // first check if everything fits to line + if ( ( nLineWidth * 2 > SwTwips(sal_Int32(nMaxLen)) * nPorHeight ) || + ( bUnbreakableNumberings && rPor.IsNumberPortion() ) ) + { + // call GetTextSize with maximum compression (for kanas) + rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen, + nMaxComp, nBreakWidth, nMaxSizeDiff ); + + if ( ( nBreakWidth <= nLineWidth ) || ( bUnbreakableNumberings && rPor.IsNumberPortion() ) ) + { + // portion fits to line + nCutPos = rInf.GetIdx() + nMaxLen; + if( nItalic && + (nCutPos >= TextFrameIndex(rInf.GetText().getLength()) || + // #i48035# Needed for CalcFitToContent + // if first line ends with a manual line break + rInf.GetText()[sal_Int32(nCutPos)] == CH_BREAK)) + nBreakWidth = nBreakWidth + nItalic; + + // save maximum width for later use + if ( nMaxSizeDiff ) + rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff ); + + nBreakWidth += nLeftRightBorderSpace; + + return true; + } + } + + bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud(); + TextFrameIndex nHyphPos(0); + + // nCutPos is the first character not fitting to the current line + // nHyphPos is the first character not fitting to the current line, + // considering an additional "-" for hyphenation + if( bHyph ) + { + nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, nHyphPos, rInf.GetCachedVclData().get() ); + + if ( !nHyphPos && rInf.GetIdx() ) + nHyphPos = rInf.GetIdx() - TextFrameIndex(1); + } + else + { + nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, rInf.GetCachedVclData().get() ); + +#if OSL_DEBUG_LEVEL > 1 + if ( TextFrameIndex(COMPLETE_STRING) != nCutPos ) + { + sal_uInt16 nMinSize; + rInf.GetTextSize( &rSI, rInf.GetIdx(), nCutPos - rInf.GetIdx(), + nMaxComp, nMinSize, nMaxSizeDiff ); + OSL_ENSURE( nMinSize <= nLineWidth, "What a Guess!!!" ); + } +#endif + } + + if( nCutPos > rInf.GetIdx() + nMaxLen ) + { + // second check if everything fits to line + nCutPos = nBreakPos = rInf.GetIdx() + nMaxLen - TextFrameIndex(1); + rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen, nMaxComp, + nBreakWidth, nMaxSizeDiff ); + + // The following comparison should always give true, otherwise + // there likely has been a pixel rounding error in GetTextBreak + if ( nBreakWidth <= nLineWidth ) + { + if (nItalic && (nBreakPos + TextFrameIndex(1)) >= TextFrameIndex(rInf.GetText().getLength())) + nBreakWidth = nBreakWidth + nItalic; + + // save maximum width for later use + if ( nMaxSizeDiff ) + rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff ); + + nBreakWidth += nLeftRightBorderSpace; + + return true; + } + } + + // we have to trigger an underflow for a footnote portion + // which does not fit to the current line + if ( rPor.IsFootnotePortion() ) + { + nBreakPos = rInf.GetIdx(); + nCutPos = TextFrameIndex(-1); + return false; + } + + TextFrameIndex nPorLen(0); + // do not call the break iterator nCutPos is a blank + sal_Unicode cCutChar = nCutPos < TextFrameIndex(rInf.GetText().getLength()) + ? rInf.GetText()[sal_Int32(nCutPos)] + : 0; + if (IsBlank(cCutChar)) + { + nBreakPos = nCutPos; + TextFrameIndex nX = nBreakPos; + + if ( rAdjust == SvxAdjust::Left ) + { + // we step back until a non blank character has been found + // or there is only one more character left + while (nX && TextFrameIndex(rInf.GetText().getLength()) < nBreakPos && + IsBlank(rInf.GetChar(--nX))) + --nBreakPos; + } + else // #i20878# + { + while (nX && nBreakPos > rInf.GetLineStart() + TextFrameIndex(1) && + IsBlank(rInf.GetChar(--nX))) + --nBreakPos; + } + + if( nBreakPos > rInf.GetIdx() ) + nPorLen = nBreakPos - rInf.GetIdx(); + while (++nCutPos < TextFrameIndex(rInf.GetText().getLength()) && + IsBlank(rInf.GetChar(nCutPos))) + ; // nothing + + nBreakStart = nCutPos; + } + else + { + // New: We should have a look into the last portion, if it was a + // field portion. For this, we expand the text of the field portion + // into our string. If the line break position is inside of before + // the field portion, we trigger an underflow. + + TextFrameIndex nOldIdx = rInf.GetIdx(); + sal_Unicode cFieldChr = 0; + +#if OSL_DEBUG_LEVEL > 0 + OUString aDebugString; +#endif + + // be careful: a field portion can be both: 0x01 (common field) + // or 0x02 (the follow of a footnode) + if ( rInf.GetLast() && rInf.GetLast()->InFieldGrp() && + ! rInf.GetLast()->IsFootnotePortion() && + rInf.GetIdx() > rInf.GetLineStart() && + CH_TXTATR_BREAKWORD == + (cFieldChr = rInf.GetText()[sal_Int32(rInf.GetIdx()) - 1])) + { + SwFieldPortion* pField = static_cast<SwFieldPortion*>(rInf.GetLast()); + OUString aText; + pField->GetExpText( rInf, aText ); + + if ( !aText.isEmpty() ) + { + nFieldDiff = TextFrameIndex(aText.getLength() - 1); + nCutPos = nCutPos + nFieldDiff; + nHyphPos = nHyphPos + nFieldDiff; + +#if OSL_DEBUG_LEVEL > 0 + aDebugString = rInf.GetText(); +#endif + + // this is pretty nutso... reverted at the end... + OUString& rOldText = const_cast<OUString&> (rInf.GetText()); + rOldText = rOldText.replaceAt(sal_Int32(rInf.GetIdx()) - 1, 1, aText); + rInf.SetIdx( rInf.GetIdx() + nFieldDiff ); + } + else + cFieldChr = 0; + } + + LineBreakHyphenationOptions aHyphOpt; + Reference< XHyphenator > xHyph; + if( bHyph ) + { + xHyph = ::GetHyphenator(); + aHyphOpt = LineBreakHyphenationOptions( xHyph, + rInf.GetHyphValues(), sal_Int32(nHyphPos)); + } + + // Get Language for break iterator. + // We have to switch the current language if we have a script + // change at nCutPos. Otherwise LATIN punctuation would never + // be allowed to be hanging punctuation. + // NEVER call GetLang if the string has been modified!!! + LanguageType aLang = rInf.GetFont()->GetLanguage(); + + // If we are inside a field portion, we use a temporary string which + // differs from the string at the textnode. Therefore we are not allowed + // to call the GetLang function. + if ( nCutPos && ! rPor.InFieldGrp() ) + { + const CharClass& rCC = GetAppCharClass(); + + // step back until a non-punctuation character is reached + TextFrameIndex nLangIndex = nCutPos; + + // If a field has been expanded right in front of us we do not + // step further than the beginning of the expanded field + // (which is the position of the field placeholder in our + // original string). + const TextFrameIndex nDoNotStepOver = CH_TXTATR_BREAKWORD == cFieldChr + ? rInf.GetIdx() - nFieldDiff - TextFrameIndex(1) + : TextFrameIndex(0); + + if ( nLangIndex > nDoNotStepOver && + TextFrameIndex(rInf.GetText().getLength()) == nLangIndex) + --nLangIndex; + + while ( nLangIndex > nDoNotStepOver && + !rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nLangIndex))) + --nLangIndex; + + // last "real" character is not inside our current portion + // we have to check the script type of the last "real" character + if ( nLangIndex < rInf.GetIdx() ) + { + sal_uInt16 nScript = g_pBreakIt->GetRealScriptOfText( rInf.GetText(), + sal_Int32(nLangIndex)); + OSL_ENSURE( nScript, "Script is not between 1 and 4" ); + + // compare current script with script from last "real" character + if ( SwFontScript(nScript - 1) != rInf.GetFont()->GetActual() ) + { + aLang = rInf.GetTextFrame()->GetLangOfChar( + CH_TXTATR_BREAKWORD == cFieldChr + ? nDoNotStepOver + : nLangIndex, + nScript, true); + } + } + } + + const ForbiddenCharacters aForbidden( + *rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().getForbiddenCharacters(aLang, true)); + + const bool bAllowHanging = rInf.IsHanging() && ! rInf.IsMulti() && + ! rInf.GetTextFrame()->IsInTab() && + ! rPor.InFieldGrp(); + + LineBreakUserOptions aUserOpt( + aForbidden.beginLine, aForbidden.endLine, + rInf.HasForbiddenChars(), bAllowHanging, false ); + + // !!! We must have a local copy of the locale, because inside + // getLineBreak the LinguEventListener can trigger a new formatting, + // which can corrupt the locale pointer inside pBreakIt. + const lang::Locale aLocale = g_pBreakIt->GetLocale( aLang ); + + // determines first possible line break from nCutPos to + // start index of current line + LineBreakResults aResult = g_pBreakIt->GetBreakIter()->getLineBreak( + rInf.GetText(), sal_Int32(nCutPos), aLocale, + sal_Int32(rInf.GetLineStart()), aHyphOpt, aUserOpt ); + + nBreakPos = TextFrameIndex(aResult.breakIndex); + + // if we are formatting multi portions we want to allow line breaks + // at the border between single line and multi line portion + // we have to be careful with footnote portions, they always come in + // with an index 0 + if ( nBreakPos < rInf.GetLineStart() && rInf.IsFirstMulti() && + ! rInf.IsFootnoteInside() ) + nBreakPos = rInf.GetLineStart(); + + nBreakStart = nBreakPos; + + bHyph = BreakType::HYPHENATION == aResult.breakType; + + if (bHyph && nBreakPos != TextFrameIndex(COMPLETE_STRING)) + { + // found hyphenation position within line + // nBreakPos is set to the hyphenation position + xHyphWord = aResult.rHyphenatedWord; + nBreakPos += TextFrameIndex(xHyphWord->getHyphenationPos() + 1); + + // if not in interactive mode, we have to break behind a soft hyphen + if ( ! rInf.IsInterHyph() && rInf.GetIdx() ) + { + sal_Int32 const nSoftHyphPos = + xHyphWord->getWord().indexOf( CHAR_SOFTHYPHEN ); + + if ( nSoftHyphPos >= 0 && + nBreakStart + TextFrameIndex(nSoftHyphPos) <= nBreakPos && + nBreakPos > rInf.GetLineStart() ) + nBreakPos = rInf.GetIdx() - TextFrameIndex(1); + } + + if( nBreakPos >= rInf.GetIdx() ) + { + nPorLen = nBreakPos - rInf.GetIdx(); + if ('-' == rInf.GetText()[ sal_Int32(nBreakPos) - 1 ]) + xHyphWord = nullptr; + } + } + else if ( !bHyph && nBreakPos >= rInf.GetLineStart() ) + { + OSL_ENSURE(sal_Int32(nBreakPos) != COMPLETE_STRING, "we should have found a break pos"); + + // found break position within line + xHyphWord = nullptr; + + // check, if break position is soft hyphen and an underflow + // has to be triggered + if( nBreakPos > rInf.GetLineStart() && rInf.GetIdx() && + CHAR_SOFTHYPHEN == rInf.GetText()[ sal_Int32(nBreakPos) - 1 ]) + { + nBreakPos = rInf.GetIdx() - TextFrameIndex(1); + } + + if( rAdjust != SvxAdjust::Left ) + { + // Delete any blanks at the end of a line, but be careful: + // If a field has been expanded, we do not want to delete any + // blanks inside the field portion. This would cause an unwanted + // underflow + TextFrameIndex nX = nBreakPos; + while( nX > rInf.GetLineStart() && + ( CH_TXTATR_BREAKWORD != cFieldChr || nX > rInf.GetIdx() ) && + ( CH_BLANK == rInf.GetChar( --nX ) || + CH_SIX_PER_EM == rInf.GetChar( nX ) || + CH_FULL_BLANK == rInf.GetChar( nX ) ) ) + nBreakPos = nX; + } + if( nBreakPos > rInf.GetIdx() ) + nPorLen = nBreakPos - rInf.GetIdx(); + } + else + { + // no line break found, setting nBreakPos to COMPLETE_STRING + // causes a break cut + nBreakPos = TextFrameIndex(COMPLETE_STRING); + OSL_ENSURE( nCutPos >= rInf.GetIdx(), "Deep cut" ); + nPorLen = nCutPos - rInf.GetIdx(); + } + + if (nBreakPos > nCutPos && nBreakPos != TextFrameIndex(COMPLETE_STRING)) + { + const TextFrameIndex nHangingLen = nBreakPos - nCutPos; + SwPosSize aTmpSize = rInf.GetTextSize( &rSI, nCutPos, nHangingLen ); + aTmpSize.Width(aTmpSize.Width() + nLeftRightBorderSpace); + OSL_ENSURE( !pHanging, "A hanging portion is hanging around" ); + pHanging.reset( new SwHangingPortion( aTmpSize ) ); + pHanging->SetLen( nHangingLen ); + nPorLen = nCutPos - rInf.GetIdx(); + } + + // If we expanded a field, we must repair the original string. + // In case we do not trigger an underflow, we correct the nBreakPos + // value, but we cannot correct the nBreakStart value: + // If we have found a hyphenation position, nBreakStart can lie before + // the field. + if ( CH_TXTATR_BREAKWORD == cFieldChr ) + { + if ( nBreakPos < rInf.GetIdx() ) + nBreakPos = nOldIdx - TextFrameIndex(1); + else if (TextFrameIndex(COMPLETE_STRING) != nBreakPos) + { + OSL_ENSURE( nBreakPos >= nFieldDiff, "I've got field trouble!" ); + nBreakPos = nBreakPos - nFieldDiff; + } + + OSL_ENSURE( nCutPos >= rInf.GetIdx() && nCutPos >= nFieldDiff, + "I've got field trouble, part2!" ); + nCutPos = nCutPos - nFieldDiff; + + OUString& rOldText = const_cast<OUString&> (rInf.GetText()); + OUString aReplacement( cFieldChr ); + rOldText = rOldText.replaceAt(sal_Int32(nOldIdx) - 1, sal_Int32(nFieldDiff) + 1, aReplacement); + rInf.SetIdx( nOldIdx ); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( aDebugString == rInf.GetText(), + "Somebody, somebody, somebody put something in my string" ); +#endif + } + } + + if( nPorLen ) + { + rInf.GetTextSize( &rSI, rInf.GetIdx(), nPorLen, + nMaxComp, nBreakWidth, nMaxSizeDiff, + rInf.GetCachedVclData().get() ); + + // save maximum width for later use + if ( nMaxSizeDiff ) + rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff ); + + nBreakWidth += nItalic + nLeftRightBorderSpace; + } + else + nBreakWidth = 0; + + if( pHanging ) + { + nBreakPos = nCutPos; + // Keep following SwBreakPortion in the same line. + if ( CH_BREAK == rInf.GetChar( nBreakPos + pHanging->GetLen() ) ) + return true; + } + + return false; +} + +// returns true if word at position nPos has a different spelling +// if hyphenated at this position (old german spelling) +bool SwTextGuess::AlternativeSpelling( const SwTextFormatInfo &rInf, + const TextFrameIndex nPos) +{ + // get word boundaries + Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( + rInf.GetText(), sal_Int32(nPos), + g_pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ), + WordType::DICTIONARY_WORD, true ); + nBreakStart = TextFrameIndex(aBound.startPos); + sal_Int32 nWordLen = aBound.endPos - sal_Int32(nBreakStart); + + // if everything else fails, we want to cut at nPos + nCutPos = nPos; + + OUString const aText( rInf.GetText().copy(sal_Int32(nBreakStart), nWordLen) ); + + // check, if word has alternative spelling + Reference< XHyphenator > xHyph( ::GetHyphenator() ); + OSL_ENSURE( xHyph.is(), "Hyphenator is missing"); + //! subtract 1 since the UNO-interface is 0 based + xHyphWord = xHyph->queryAlternativeSpelling( aText, + g_pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ), + sal::static_int_cast<sal_Int16>(sal_Int32(nPos - nBreakStart)), + rInf.GetHyphValues() ); + return xHyphWord.is() && xHyphWord->isAlternativeSpelling(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/guess.hxx b/sw/source/core/text/guess.hxx new file mode 100644 index 000000000..5ff7e7474 --- /dev/null +++ b/sw/source/core/text/guess.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_GUESS_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_GUESS_HXX +#include <memory> + +#include "porrst.hxx" + +#include <com/sun/star/uno/Reference.hxx> + +namespace com::sun::star::linguistic2 { class XHyphenatedWord; } + +class SwTextFormatInfo; + +class SwTextGuess +{ + css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord; + std::unique_ptr<SwHangingPortion> pHanging; // for hanging punctuation + TextFrameIndex nCutPos; // this character doesn't fit + TextFrameIndex nBreakStart; // start index of word containing line break + TextFrameIndex nBreakPos; // start index of break position + TextFrameIndex nFieldDiff; // absolute positions can be wrong if we + // a field in the text has been expanded + sal_uInt16 nBreakWidth; // width of the broken portion +public: + SwTextGuess(): nCutPos(0), nBreakStart(0), + nBreakPos(0), nFieldDiff(0), nBreakWidth(0) + { } + + // true, if current portion still fits to current line + bool Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, + const sal_uInt16 nHeight ); + bool AlternativeSpelling( const SwTextFormatInfo &rInf, const TextFrameIndex nPos ); + + SwHangingPortion* GetHangingPortion() const { return pHanging.get(); } + SwHangingPortion* ReleaseHangingPortion() { return pHanging.release(); } + sal_uInt16 BreakWidth() const { return nBreakWidth; } + TextFrameIndex CutPos() const { return nCutPos; } + TextFrameIndex BreakStart() const { return nBreakStart; } + TextFrameIndex BreakPos() const {return nBreakPos; } + TextFrameIndex FieldDiff() const {return nFieldDiff; } + const css::uno::Reference< css::linguistic2::XHyphenatedWord >& HyphWord() const + { return xHyphWord; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx new file mode 100644 index 000000000..b7a58b198 --- /dev/null +++ b/sw/source/core/text/inftxt.cxx @@ -0,0 +1,1991 @@ +/* -*- 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 <com/sun/star/linguistic2/XHyphenator.hpp> + +#include <unotools/linguprops.hxx> +#include <unotools/lingucfg.hxx> +#include <hintids.hxx> +#include <svl/ctloptions.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/printer.hxx> +#include <sal/log.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/splwrap.hxx> +#include <editeng/pgrditem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/shaditem.hxx> + +#include <SwSmartTagMgr.hxx> +#include <breakit.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <paintfrm.hxx> +#include <swmodule.hxx> +#include <vcl/svapp.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <frmtool.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <paratr.hxx> +#include <rootfrm.hxx> +#include "inftxt.hxx" +#include <noteurl.hxx> +#include "porftn.hxx" +#include "porrst.hxx" +#include "itratr.hxx" +#include "portab.hxx" +#include <wrong.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <numrule.hxx> +#include <EnhancedPDFExportHelper.hxx> +#include <docsh.hxx> +#include <strings.hrc> +#include <o3tl/deleter.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gradient.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::linguistic2; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +#define CHAR_UNDERSCORE u'_' +#define CHAR_LEFT_ARROW u'\x25C0' +#define CHAR_RIGHT_ARROW u'\x25B6' +#define CHAR_TAB u'\x2192' +#define CHAR_TAB_RTL u'\x2190' +#define CHAR_LINEBREAK u'\x21B5' +#define CHAR_LINEBREAK_RTL u'\x21B3' + +#define DRAW_SPECIAL_OPTIONS_CENTER 1 +#define DRAW_SPECIAL_OPTIONS_ROTATE 2 + +SwLineInfo::SwLineInfo() + : pSpace( nullptr ), + nVertAlign( SvxParaVertAlignItem::Align::Automatic ), + nDefTabStop( 0 ), + bListTabStopIncluded( false ), + nListTabStopPosition( 0 ) +{ +} + +SwLineInfo::~SwLineInfo() +{ +} + +void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, + const SwTextNode& rTextNode ) +{ + pRuler.reset( new SvxTabStopItem( rAttrSet.GetTabStops() ) ); + if ( rTextNode.GetListTabStopPosition( nListTabStopPosition ) ) + { + bListTabStopIncluded = true; + + // insert the list tab stop into SvxTabItem instance <pRuler> + const SvxTabStop aListTabStop( nListTabStopPosition, + SvxTabAdjust::Left ); + pRuler->Insert( aListTabStop ); + + // remove default tab stops, which are before the inserted list tab stop + for ( sal_uInt16 i = 0; i < pRuler->Count(); i++ ) + { + if ( (*pRuler)[i].GetTabPos() < nListTabStopPosition && + (*pRuler)[i].GetAdjustment() == SvxTabAdjust::Default ) + { + pRuler->Remove(i); + continue; + } + } + } + + if ( !rTextNode.getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ) + { + // remove default tab stop at position 0 + for ( sal_uInt16 i = 0; i < pRuler->Count(); i++ ) + { + if ( (*pRuler)[i].GetTabPos() == 0 && + (*pRuler)[i].GetAdjustment() == SvxTabAdjust::Default ) + { + pRuler->Remove(i); + break; + } + } + } + + pSpace = &rAttrSet.GetLineSpacing(); + nVertAlign = rAttrSet.GetParaVertAlign().GetValue(); + nDefTabStop = USHRT_MAX; +} + +void SwTextInfo::CtorInitTextInfo( SwTextFrame *pFrame ) +{ + m_pPara = pFrame->GetPara(); + m_nTextStart = pFrame->GetOffset(); + if (!m_pPara) + { + SAL_WARN("sw.core", "+SwTextInfo::CTOR: missing paragraph information"); + pFrame->Format(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + m_pPara = pFrame->GetPara(); + } +} + +SwTextInfo::SwTextInfo( const SwTextInfo &rInf ) + : m_pPara( const_cast<SwTextInfo&>(rInf).GetParaPortion() ) + , m_nTextStart( rInf.GetTextStart() ) +{ } + +#if OSL_DEBUG_LEVEL > 0 + +static void ChkOutDev( const SwTextSizeInfo &rInf ) +{ + if ( !rInf.GetVsh() ) + return; + + const OutputDevice* pOut = rInf.GetOut(); + const OutputDevice* pRef = rInf.GetRefDev(); + OSL_ENSURE( pOut && pRef, "ChkOutDev: invalid output devices" ); +} +#endif + +static TextFrameIndex GetMinLen( const SwTextSizeInfo &rInf ) +{ + const TextFrameIndex nTextLen(rInf.GetText().getLength()); + if (rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)) + return nTextLen; + const TextFrameIndex nInfLen = rInf.GetIdx() + rInf.GetLen(); + return std::min(nTextLen, nInfLen); +} + +SwTextSizeInfo::SwTextSizeInfo() +: m_pKanaComp(nullptr) +, m_pVsh(nullptr) +, m_pOut(nullptr) +, m_pRef(nullptr) +, m_pFnt(nullptr) +, m_pUnderFnt(nullptr) +, m_pFrame(nullptr) +, m_pOpt(nullptr) +, m_pText(nullptr) +, m_nIdx(0) +, m_nLen(0) +, m_nKanaIdx(0) +, m_bOnWin (false) +, m_bNotEOL (false) +, m_bURLNotify(false) +, m_bStopUnderflow(false) +, m_bFootnoteInside(false) +, m_bOtherThanFootnoteInside(false) +, m_bMulti(false) +, m_bFirstMulti(false) +, m_bRuby(false) +, m_bHanging(false) +, m_bScriptSpace(false) +, m_bForbiddenChars(false) +, m_bSnapToGrid(false) +, m_nDirection(0) +{} + +SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew ) + : SwTextInfo( rNew ), + m_pKanaComp(rNew.GetpKanaComp()), + m_pVsh(const_cast<SwTextSizeInfo&>(rNew).GetVsh()), + m_pOut(const_cast<SwTextSizeInfo&>(rNew).GetOut()), + m_pRef(const_cast<SwTextSizeInfo&>(rNew).GetRefDev()), + m_pFnt(const_cast<SwTextSizeInfo&>(rNew).GetFont()), + m_pUnderFnt(rNew.GetUnderFnt()), + m_pFrame(rNew.m_pFrame), + m_pOpt(&rNew.GetOpt()), + m_pText(&rNew.GetText()), + m_nIdx(rNew.GetIdx()), + m_nLen(rNew.GetLen()), + m_nKanaIdx( rNew.GetKanaIdx() ), + m_bOnWin( rNew.OnWin() ), + m_bNotEOL( rNew.NotEOL() ), + m_bURLNotify( rNew.URLNotify() ), + m_bStopUnderflow( rNew.StopUnderflow() ), + m_bFootnoteInside( rNew.IsFootnoteInside() ), + m_bOtherThanFootnoteInside( rNew.IsOtherThanFootnoteInside() ), + m_bMulti( rNew.IsMulti() ), + m_bFirstMulti( rNew.IsFirstMulti() ), + m_bRuby( rNew.IsRuby() ), + m_bHanging( rNew.IsHanging() ), + m_bScriptSpace( rNew.HasScriptSpace() ), + m_bForbiddenChars( rNew.HasForbiddenChars() ), + m_bSnapToGrid( rNew.SnapToGrid() ), + m_nDirection( rNew.GetDirection() ) +{ +#if OSL_DEBUG_LEVEL > 0 + ChkOutDev( *this ); +#endif +} + +void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, + TextFrameIndex const nNewIdx) +{ + m_pKanaComp = nullptr; + m_nKanaIdx = 0; + m_pFrame = pFrame; + CtorInitTextInfo( m_pFrame ); + SwDoc const& rDoc(m_pFrame->GetDoc()); + m_pVsh = m_pFrame->getRootFrame()->GetCurrShell(); + + // Get the output and reference device + if ( m_pVsh ) + { + m_pOut = pRenderContext; + m_pRef = &m_pVsh->GetRefDev(); + m_bOnWin = m_pVsh->GetWin() || OUTDEV_WINDOW == m_pOut->GetOutDevType() || m_pVsh->isOutputToWindow(); + } + else + { + // Access via StarONE. We do not need a Shell or an active one. + if (rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)) + { + // We can only pick the AppWin here? (there's nothing better to pick?) + m_pOut = Application::GetDefaultDevice(); + } + else + m_pOut = rDoc.getIDocumentDeviceAccess().getPrinter(false); + + m_pRef = m_pOut; + } + +#if OSL_DEBUG_LEVEL > 0 + ChkOutDev( *this ); +#endif + + // Set default layout mode ( LTR or RTL ). + if ( m_pFrame->IsRightToLeft() ) + { + m_pOut->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl ); + m_pRef->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl ); + m_nDirection = DIR_RIGHT2LEFT; + } + else + { + m_pOut->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong ); + m_pRef->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong ); + m_nDirection = DIR_LEFT2RIGHT; + } + + // The Options + + m_pOpt = m_pVsh ? + m_pVsh->GetViewOptions() : + SW_MOD()->GetViewOption(rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)); // Options from Module, due to StarONE + + // bURLNotify is set if MakeGraphic prepares it + // TODO: Unwind + m_bURLNotify = pNoteURL && !m_bOnWin; + + SetSnapToGrid( m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue() && + m_pFrame->IsInDocBody() ); + + m_pFnt = nullptr; + m_pUnderFnt = nullptr; + m_pText = &m_pFrame->GetText(); + + m_nIdx = nNewIdx; + m_nLen = TextFrameIndex(COMPLETE_STRING); + m_bNotEOL = false; + m_bStopUnderflow = m_bFootnoteInside = m_bOtherThanFootnoteInside = false; + m_bMulti = m_bFirstMulti = m_bRuby = m_bHanging = m_bScriptSpace = + m_bForbiddenChars = false; + + SetLen( GetMinLen( *this ) ); +} + +SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pText, + TextFrameIndex const nIndex) + : SwTextInfo( rNew ), + m_pKanaComp(rNew.GetpKanaComp()), + m_pVsh(const_cast<SwTextSizeInfo&>(rNew).GetVsh()), + m_pOut(const_cast<SwTextSizeInfo&>(rNew).GetOut()), + m_pRef(const_cast<SwTextSizeInfo&>(rNew).GetRefDev()), + m_pFnt(const_cast<SwTextSizeInfo&>(rNew).GetFont()), + m_pUnderFnt(rNew.GetUnderFnt()), + m_pFrame( rNew.m_pFrame ), + m_pOpt(&rNew.GetOpt()), + m_pText(pText), + m_nIdx(nIndex), + m_nLen(COMPLETE_STRING), + m_nKanaIdx( rNew.GetKanaIdx() ), + m_bOnWin( rNew.OnWin() ), + m_bNotEOL( rNew.NotEOL() ), + m_bURLNotify( rNew.URLNotify() ), + m_bStopUnderflow( rNew.StopUnderflow() ), + m_bFootnoteInside( rNew.IsFootnoteInside() ), + m_bOtherThanFootnoteInside( rNew.IsOtherThanFootnoteInside() ), + m_bMulti( rNew.IsMulti() ), + m_bFirstMulti( rNew.IsFirstMulti() ), + m_bRuby( rNew.IsRuby() ), + m_bHanging( rNew.IsHanging() ), + m_bScriptSpace( rNew.HasScriptSpace() ), + m_bForbiddenChars( rNew.HasForbiddenChars() ), + m_bSnapToGrid( rNew.SnapToGrid() ), + m_nDirection( rNew.GetDirection() ) +{ +#if OSL_DEBUG_LEVEL > 0 + ChkOutDev( *this ); +#endif + SetLen( GetMinLen( *this ) ); +} + +SwTextSizeInfo::SwTextSizeInfo(SwTextFrame *const pTextFrame, + TextFrameIndex const nIndex) + : m_bOnWin(false) +{ + CtorInitTextSizeInfo( pTextFrame->getRootFrame()->GetCurrShell()->GetOut(), pTextFrame, nIndex ); +} + +void SwTextSizeInfo::SelectFont() +{ + // The path needs to go via ChgPhysFnt or the FontMetricCache gets confused. + // In this case pLastMet has it's old value. + // Wrong: GetOut()->SetFont( GetFont()->GetFnt() ); + GetFont()->Invalidate(); + GetFont()->ChgPhysFnt( m_pVsh, *GetOut() ); +} + +void SwTextSizeInfo::NoteAnimation() const +{ + if( OnWin() ) + SwRootFrame::FlushVout(); + + OSL_ENSURE( m_pOut == m_pVsh->GetOut(), + "SwTextSizeInfo::NoteAnimation() changed m_pOut" ); +} + +SwPosSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev, + const SwScriptInfo* pSI, + const OUString& rText, + const TextFrameIndex nIndex, + const TextFrameIndex nLength) const +{ + SwDrawTextInfo aDrawInf( m_pVsh, *pOutDev, pSI, rText, nIndex, nLength ); + aDrawInf.SetFrame( m_pFrame ); + aDrawInf.SetFont( m_pFnt ); + aDrawInf.SetSnapToGrid( SnapToGrid() ); + aDrawInf.SetKanaComp( 0 ); + return SwPosSize(m_pFnt->GetTextSize_( aDrawInf )); +} + +SwPosSize SwTextSizeInfo::GetTextSize() const +{ + const SwScriptInfo& rSI = + const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); + + // in some cases, compression is not allowed or suppressed for + // performance reasons + sal_uInt16 nComp =( SwFontScript::CJK == GetFont()->GetActual() && + rSI.CountCompChg() && + ! IsMulti() ) ? + GetKanaComp() : + 0 ; + + SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen ); + aDrawInf.SetFrame( m_pFrame ); + aDrawInf.SetFont( m_pFnt ); + aDrawInf.SetSnapToGrid( SnapToGrid() ); + aDrawInf.SetKanaComp( nComp ); + return SwPosSize(m_pFnt->GetTextSize_( aDrawInf )); +} + +void SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, const TextFrameIndex nIndex, + const TextFrameIndex nLength, const sal_uInt16 nComp, + sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff, + vcl::TextLayoutCache const*const pCache) const +{ + SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength, + 0, false, pCache); + aDrawInf.SetFrame( m_pFrame ); + aDrawInf.SetFont( m_pFnt ); + aDrawInf.SetSnapToGrid( SnapToGrid() ); + aDrawInf.SetKanaComp( nComp ); + SwPosSize aSize( m_pFnt->GetTextSize_( aDrawInf ) ); + nMaxSizeDiff = static_cast<sal_uInt16>(aDrawInf.GetKanaDiff()); + nMinSize = aSize.Width(); +} + +TextFrameIndex SwTextSizeInfo::GetTextBreak( const long nLineWidth, + const TextFrameIndex nMaxLen, + const sal_uInt16 nComp, + vcl::TextLayoutCache const*const pCache) const +{ + const SwScriptInfo& rScriptInfo = + const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); + + OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" ); + SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rScriptInfo, + *m_pText, GetIdx(), nMaxLen, 0, false, pCache ); + aDrawInf.SetFrame( m_pFrame ); + aDrawInf.SetFont( m_pFnt ); + aDrawInf.SetSnapToGrid( SnapToGrid() ); + aDrawInf.SetKanaComp( nComp ); + aDrawInf.SetHyphPos( nullptr ); + + return m_pFnt->GetTextBreak( aDrawInf, nLineWidth ); +} + +TextFrameIndex SwTextSizeInfo::GetTextBreak( const long nLineWidth, + const TextFrameIndex nMaxLen, + const sal_uInt16 nComp, + TextFrameIndex& rExtraCharPos, + vcl::TextLayoutCache const*const pCache) const +{ + const SwScriptInfo& rScriptInfo = + const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); + + OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" ); + SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rScriptInfo, + *m_pText, GetIdx(), nMaxLen, 0, false, pCache ); + aDrawInf.SetFrame( m_pFrame ); + aDrawInf.SetFont( m_pFnt ); + aDrawInf.SetSnapToGrid( SnapToGrid() ); + aDrawInf.SetKanaComp( nComp ); + aDrawInf.SetHyphPos( &rExtraCharPos ); + + return m_pFnt->GetTextBreak( aDrawInf, nLineWidth ); +} + +bool SwTextSizeInfo::HasHint(TextFrameIndex const nPos) const +{ + std::pair<SwTextNode const*, sal_Int32> const pos(m_pFrame->MapViewToModel(nPos)); + return pos.first->GetTextAttrForCharAt(pos.second); +} + +void SwTextPaintInfo::CtorInitTextPaintInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const SwRect &rPaint ) +{ + CtorInitTextSizeInfo( pRenderContext, pFrame, TextFrameIndex(0) ); + aTextFly.CtorInitTextFly( pFrame ); + aPaintRect = rPaint; + nSpaceIdx = 0; + pSpaceAdd = nullptr; + m_pWrongList = nullptr; + m_pGrammarCheckList = nullptr; + m_pSmartTags = nullptr; + pBrushItem = nullptr; +} + +SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf, const OUString* pText ) + : SwTextSizeInfo( rInf, pText ) + , m_pWrongList( rInf.GetpWrongList() ) + , m_pGrammarCheckList( rInf.GetGrammarCheckList() ) + , m_pSmartTags( rInf.GetSmartTags() ) + , pSpaceAdd( rInf.GetpSpaceAdd() ), + pBrushItem( rInf.GetBrushItem() ), + aTextFly( rInf.GetTextFly() ), + aPos( rInf.GetPos() ), + aPaintRect( rInf.GetPaintRect() ), + nSpaceIdx( rInf.GetSpaceIdx() ) +{ } + +SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf ) + : SwTextSizeInfo( rInf ) + , m_pWrongList( rInf.GetpWrongList() ) + , m_pGrammarCheckList( rInf.GetGrammarCheckList() ) + , m_pSmartTags( rInf.GetSmartTags() ) + , pSpaceAdd( rInf.GetpSpaceAdd() ), + pBrushItem( rInf.GetBrushItem() ), + aTextFly( rInf.GetTextFly() ), + aPos( rInf.GetPos() ), + aPaintRect( rInf.GetPaintRect() ), + nSpaceIdx( rInf.GetSpaceIdx() ) +{ } + +SwTextPaintInfo::SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint ) +{ + CtorInitTextPaintInfo( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, rPaint ); +} + +/// Returns if the current background color is dark. +static bool lcl_IsDarkBackground( const SwTextPaintInfo& rInf ) +{ + const Color* pCol = rInf.GetFont()->GetBackColor(); + if( ! pCol || COL_TRANSPARENT == *pCol ) + { + const SvxBrushItem* pItem; + SwRect aOrigBackRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + // Consider, that [GetBackgroundBrush(...)] can set <pCol> + // See implementation in /core/layout/paintfrm.cxx + // There is a background color, if there is a background brush and + // its color is *not* "no fill"/"auto fill". + if( rInf.GetTextFrame()->GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/false ) ) + { + if ( !pCol ) + pCol = &pItem->GetColor(); + + // Determined color <pCol> can be <COL_TRANSPARENT>. Thus, check it. + if ( *pCol == COL_TRANSPARENT) + pCol = nullptr; + } + else + pCol = nullptr; + } + + if( !pCol ) + pCol = &aGlobalRetoucheColor; + + return pCol->IsDark(); +} + +namespace +{ +/** + * Context class that captures the draw operations on rDrawInf's output device for transparency + * purposes. + */ +class SwTransparentTextGuard +{ + ScopedVclPtrInstance<VirtualDevice> m_aContentVDev; + GDIMetaFile m_aContentMetafile; + MapMode m_aNewMapMode; + SwRect m_aPorRect; + SwTextPaintInfo& m_rPaintInf; + SwDrawTextInfo& m_rDrawInf; + +public: + SwTransparentTextGuard(const SwLinePortion& rPor, SwTextPaintInfo& rPaintInf, + SwDrawTextInfo& rDrawInf); + ~SwTransparentTextGuard(); +}; + +SwTransparentTextGuard::SwTransparentTextGuard(const SwLinePortion& rPor, + SwTextPaintInfo& rPaintInf, SwDrawTextInfo& rDrawInf) + : m_aNewMapMode(rPaintInf.GetOut()->GetMapMode()) + , m_rPaintInf(rPaintInf) + , m_rDrawInf(rDrawInf) +{ + rPaintInf.CalcRect(rPor, &m_aPorRect); + rDrawInf.SetOut(*m_aContentVDev); + m_aContentVDev->SetMapMode(rPaintInf.GetOut()->GetMapMode()); + m_aContentMetafile.Record(m_aContentVDev.get()); + m_aContentVDev->SetLineColor(rPaintInf.GetOut()->GetLineColor()); + m_aContentVDev->SetFillColor(rPaintInf.GetOut()->GetFillColor()); + m_aContentVDev->SetFont(rPaintInf.GetOut()->GetFont()); + m_aContentVDev->SetDrawMode(rPaintInf.GetOut()->GetDrawMode()); + m_aContentVDev->SetSettings(rPaintInf.GetOut()->GetSettings()); + m_aContentVDev->SetRefPoint(rPaintInf.GetOut()->GetRefPoint()); +} + +SwTransparentTextGuard::~SwTransparentTextGuard() +{ + m_aContentMetafile.Stop(); + m_aContentMetafile.WindStart(); + m_aNewMapMode.SetOrigin(m_aPorRect.TopLeft()); + m_aContentMetafile.SetPrefMapMode(m_aNewMapMode); + m_aContentMetafile.SetPrefSize(m_aPorRect.SSize()); + m_rDrawInf.SetOut(*m_rPaintInf.GetOut()); + Gradient aVCLGradient; + sal_uInt8 nTransPercentVcl = m_rPaintInf.GetFont()->GetColor().GetTransparency(); + const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); + aVCLGradient.SetStyle(GradientStyle::Linear); + aVCLGradient.SetStartColor(aTransColor); + aVCLGradient.SetEndColor(aTransColor); + aVCLGradient.SetAngle(0); + aVCLGradient.SetBorder(0); + aVCLGradient.SetOfsX(0); + aVCLGradient.SetOfsY(0); + aVCLGradient.SetStartIntensity(100); + aVCLGradient.SetEndIntensity(100); + aVCLGradient.SetSteps(2); + m_rPaintInf.GetOut()->DrawTransparent(m_aContentMetafile, m_aPorRect.TopLeft(), + m_aPorRect.SSize(), aVCLGradient); +} +} + +void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPor, + TextFrameIndex const nStart, TextFrameIndex const nLength, + const bool bKern, const bool bWrong, + const bool bSmartTag, + const bool bGrammarCheck ) +{ + if( !nLength ) + return; + + // The SwScriptInfo is useless if we are inside a field portion + SwScriptInfo* pSI = nullptr; + if ( ! rPor.InFieldGrp() ) + pSI = &GetParaPortion()->GetScriptInfo(); + + // in some cases, kana compression is not allowed or suppressed for + // performance reasons + sal_uInt16 nComp = 0; + if ( ! IsMulti() ) + nComp = GetKanaComp(); + + bool bCfgIsAutoGrammar = false; + SvtLinguConfig().GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bCfgIsAutoGrammar; + const bool bBullet = OnWin() && GetOpt().IsBlank() && IsNoSymbol(); + const bool bTmpWrong = bWrong && OnWin() && GetOpt().IsOnlineSpell(); + const bool bTmpGrammarCheck = bGrammarCheck && OnWin() && bCfgIsAutoGrammar && GetOpt().IsOnlineSpell(); + const bool bTmpSmart = bSmartTag && OnWin() && !GetOpt().IsPagePreview() && SwSmartTagMgr::Get().IsSmartTagsEnabled(); + + OSL_ENSURE( GetParaPortion(), "No paragraph!"); + SwDrawTextInfo aDrawInf( m_pFrame->getRootFrame()->GetCurrShell(), *m_pOut, pSI, rText, nStart, nLength, + rPor.Width(), bBullet ); + + aDrawInf.SetUnderFnt( m_pUnderFnt ); + + const long nSpaceAdd = ( rPor.IsBlankPortion() || rPor.IsDropPortion() || + rPor.InNumberGrp() ) ? 0 : GetSpaceAdd(); + if ( nSpaceAdd ) + { + TextFrameIndex nCharCnt(0); + // #i41860# Thai justified alignment needs some + // additional information: + aDrawInf.SetNumberOfBlanks( rPor.InTextGrp() ? + static_cast<const SwTextPortion&>(rPor).GetSpaceCnt( *this, nCharCnt ) : + TextFrameIndex(0) ); + } + + aDrawInf.SetSpace( nSpaceAdd ); + aDrawInf.SetKanaComp( nComp ); + + // the font is used to identify the current script via nActual + aDrawInf.SetFont( m_pFnt ); + // the frame is used to identify the orientation + aDrawInf.SetFrame( GetTextFrame() ); + // we have to know if the paragraph should snap to grid + aDrawInf.SetSnapToGrid( SnapToGrid() ); + // for underlining we must know when not to add extra space behind + // a character in justified mode + aDrawInf.SetSpaceStop( ! rPor.GetNextPortion() || + rPor.GetNextPortion()->InFixMargGrp() || + rPor.GetNextPortion()->IsHolePortion() ); + + // Draw text next to the left border + Point aFontPos(aPos); + if( m_pFnt->GetLeftBorder() && !static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev() ) + { + const sal_uInt16 nLeftBorderSpace = m_pFnt->GetLeftBorderSpace(); + if ( GetTextFrame()->IsRightToLeft() ) + { + aFontPos.AdjustX( -nLeftBorderSpace ); + } + else + { + switch( m_pFnt->GetOrientation(GetTextFrame()->IsVertical()) ) + { + case 0 : + aFontPos.AdjustX(nLeftBorderSpace ); + break; + case 900 : + aFontPos.AdjustY( -nLeftBorderSpace ); + break; + case 1800 : + aFontPos.AdjustX( -nLeftBorderSpace ); + break; + case 2700 : + aFontPos.AdjustY(nLeftBorderSpace ); + break; + } + } + if( aFontPos.X() < 0 ) + aFontPos.setX( 0 ); + if( aFontPos.Y() < 0 ) + aFontPos.setY( 0 ); + } + + // Handle semi-transparent text if necessary. + std::unique_ptr<SwTransparentTextGuard, o3tl::default_delete<SwTransparentTextGuard>> pTransparentText; + if (m_pFnt->GetColor() != COL_AUTO && m_pFnt->GetColor().GetTransparency() != 0) + { + pTransparentText.reset(new SwTransparentTextGuard(rPor, *this, aDrawInf)); + } + + if( GetTextFly().IsOn() ) + { + // aPos needs to be the TopLeft, because we cannot calculate the + // ClipRects otherwise + const Point aPoint( aFontPos.X(), aFontPos.Y() - rPor.GetAscent() ); + const Size aSize( rPor.Width(), rPor.Height() ); + aDrawInf.SetPos( aPoint ); + aDrawInf.SetSize( aSize ); + aDrawInf.SetAscent( rPor.GetAscent() ); + aDrawInf.SetKern( bKern ? rPor.Width() : 0 ); + aDrawInf.SetWrong( bTmpWrong ? m_pWrongList : nullptr ); + aDrawInf.SetGrammarCheck( bTmpGrammarCheck ? m_pGrammarCheckList : nullptr ); + aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr ); + GetTextFly().DrawTextOpaque( aDrawInf ); + } + else + { + aDrawInf.SetPos( aFontPos ); + if( bKern ) + m_pFnt->DrawStretchText_( aDrawInf ); + else + { + aDrawInf.SetWrong( bTmpWrong ? m_pWrongList : nullptr ); + aDrawInf.SetGrammarCheck( bTmpGrammarCheck ? m_pGrammarCheckList : nullptr ); + aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr ); + m_pFnt->DrawText_( aDrawInf ); + } + } +} + +void SwTextPaintInfo::CalcRect( const SwLinePortion& rPor, + SwRect* pRect, SwRect* pIntersect, + const bool bInsideBox ) const +{ + Size aSize( rPor.Width(), rPor.Height() ); + if( rPor.IsHangingPortion() ) + aSize.setWidth( static_cast<const SwHangingPortion&>(rPor).GetInnerWidth() ); + if( rPor.InSpaceGrp() && GetSpaceAdd() ) + { + SwTwips nAdd = rPor.CalcSpacing( GetSpaceAdd(), *this ); + if( rPor.InFieldGrp() && GetSpaceAdd() < 0 && nAdd ) + nAdd += GetSpaceAdd() / SPACING_PRECISION_FACTOR; + aSize.AdjustWidth(nAdd ); + } + + Point aPoint; + + if( IsRotated() ) + { + long nTmp = aSize.Width(); + aSize.setWidth( aSize.Height() ); + aSize.setHeight( nTmp ); + if ( 1 == GetDirection() ) + { + aPoint.setX( X() - rPor.GetAscent() ); + aPoint.setY( Y() - aSize.Height() ); + } + else + { + aPoint.setX( X() - rPor.Height() + rPor.GetAscent() ); + aPoint.setY( Y() ); + } + } + else + { + aPoint.setX( X() ); + if (GetTextFrame()->IsVertLR() && !GetTextFrame()->IsVertLRBT()) + aPoint.setY( Y() - rPor.Height() + rPor.GetAscent() ); + else + aPoint.setY( Y() - rPor.GetAscent() ); + } + + // Adjust x coordinate if we are inside a bidi portion + const bool bFrameDir = GetTextFrame()->IsRightToLeft(); + const bool bCounterDir = ( !bFrameDir && DIR_RIGHT2LEFT == GetDirection() ) || + ( bFrameDir && DIR_LEFT2RIGHT == GetDirection() ); + + if ( bCounterDir ) + aPoint.AdjustX( -(aSize.Width()) ); + + SwRect aRect( aPoint, aSize ); + + if ( GetTextFrame()->IsRightToLeft() ) + GetTextFrame()->SwitchLTRtoRTL( aRect ); + + if ( GetTextFrame()->IsVertical() ) + GetTextFrame()->SwitchHorizontalToVertical( aRect ); + + if( bInsideBox && rPor.InTextGrp() ) + { + const bool bJoinWithPrev = + static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev(); + const bool bJoinWithNext = + static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithNext(); + const bool bIsVert = GetTextFrame()->IsVertical(); + const bool bIsVertLRBT = GetTextFrame()->IsVertLRBT(); + aRect.AddTop( GetFont()->CalcShadowSpace(SvxShadowItemSide::TOP, bIsVert, bIsVertLRBT, + bJoinWithPrev, bJoinWithNext)); + aRect.AddBottom( - GetFont()->CalcShadowSpace(SvxShadowItemSide::BOTTOM, bIsVert, bIsVertLRBT, + bJoinWithPrev, bJoinWithNext)); + aRect.AddLeft( GetFont()->CalcShadowSpace(SvxShadowItemSide::LEFT, bIsVert, bIsVertLRBT, + bJoinWithPrev, bJoinWithNext)); + aRect.AddRight( - GetFont()->CalcShadowSpace(SvxShadowItemSide::RIGHT, bIsVert, bIsVertLRBT, + bJoinWithPrev, bJoinWithNext)); + } + + if ( pRect ) + *pRect = aRect; + + if( aRect.HasArea() && pIntersect ) + { + ::SwAlignRect( aRect, GetVsh(), GetOut() ); + + if ( GetOut()->IsClipRegion() ) + { + SwRect aClip( GetOut()->GetClipRegion().GetBoundRect() ); + aRect.Intersection( aClip ); + } + + *pIntersect = aRect; + } +} + +/** + * Draws a special portion + * E.g.: line break portion, tab portion + * + * @param rPor The portion + * @param rRect The rectangle surrounding the character + * @param rCol Specify a color for the character + * @param bCenter Draw the character centered, otherwise left aligned + * @param bRotate Rotate the character if character rotation is set + */ +static void lcl_DrawSpecial( const SwTextPaintInfo& rTextPaintInfo, const SwLinePortion& rPor, + SwRect& rRect, const Color& rCol, sal_Unicode cChar, + sal_uInt8 nOptions ) +{ + bool bCenter = 0 != ( nOptions & DRAW_SPECIAL_OPTIONS_CENTER ); + bool bRotate = 0 != ( nOptions & DRAW_SPECIAL_OPTIONS_ROTATE ); + + // rRect is given in absolute coordinates + if ( rTextPaintInfo.GetTextFrame()->IsRightToLeft() ) + rTextPaintInfo.GetTextFrame()->SwitchRTLtoLTR( rRect ); + if ( rTextPaintInfo.GetTextFrame()->IsVertical() ) + rTextPaintInfo.GetTextFrame()->SwitchVerticalToHorizontal( rRect ); + + const SwFont* pOldFnt = rTextPaintInfo.GetFont(); + + // Font is generated only once: + static SwFont s_aFnt = [&]() + { + SwFont tmp( *pOldFnt ); + tmp.SetFamily( FAMILY_DONTKNOW, tmp.GetActual() ); + tmp.SetName( numfunc::GetDefBulletFontname(), tmp.GetActual() ); + tmp.SetStyleName(OUString(), tmp.GetActual()); + tmp.SetCharSet( RTL_TEXTENCODING_SYMBOL, tmp.GetActual() ); + return tmp; + }(); + + // Some of the current values are set at the font: + if ( ! bRotate ) + s_aFnt.SetVertical( 0, rTextPaintInfo.GetTextFrame()->IsVertical() ); + else + s_aFnt.SetVertical( pOldFnt->GetOrientation() ); + + s_aFnt.SetColor(rCol); + + Size aFontSize( 0, SPECIAL_FONT_HEIGHT ); + s_aFnt.SetSize( aFontSize, s_aFnt.GetActual() ); + + SwTextPaintInfo& rNonConstTextPaintInfo = const_cast<SwTextPaintInfo&>(rTextPaintInfo); + + rNonConstTextPaintInfo.SetFont( &s_aFnt ); + + // The maximum width depends on the current orientation + const sal_uInt16 nDir = s_aFnt.GetOrientation( rTextPaintInfo.GetTextFrame()->IsVertical() ); + SwTwips nMaxWidth; + if (nDir == 900 || nDir == 2700) + nMaxWidth = rRect.Height(); + else + { + assert(nDir == 0); //Unknown direction set at font + nMaxWidth = rRect.Width(); + } + + // check if char fits into rectangle + const OUString aTmp( cChar ); + aFontSize = rTextPaintInfo.GetTextSize( aTmp ).SvLSize(); + while ( aFontSize.Width() > nMaxWidth ) + { + SwTwips nFactor = ( 100 * aFontSize.Width() ) / nMaxWidth; + const SwTwips nOldWidth = aFontSize.Width(); + + // new height for font + const SwFontScript nAct = s_aFnt.GetActual(); + aFontSize.setHeight( ( 100 * s_aFnt.GetSize( nAct ).Height() ) / nFactor ); + aFontSize.setWidth( ( 100 * s_aFnt.GetSize( nAct).Width() ) / nFactor ); + + if ( !aFontSize.Width() && !aFontSize.Height() ) + break; + + s_aFnt.SetSize( aFontSize, nAct ); + + aFontSize = rTextPaintInfo.GetTextSize( aTmp ).SvLSize(); + + if ( aFontSize.Width() >= nOldWidth ) + break; + } + + const Point aOldPos( rTextPaintInfo.GetPos() ); + + // adjust values so that tab is vertically and horizontally centered + SwTwips nX = rRect.Left(); + SwTwips nY = rRect.Top(); + switch ( nDir ) + { + case 0 : + if ( bCenter ) + nX += ( rRect.Width() - aFontSize.Width() ) / 2; + nY += ( rRect.Height() - aFontSize.Height() ) / 2 + rTextPaintInfo.GetAscent(); + break; + case 900 : + if ( bCenter ) + nX += ( rRect.Width() - aFontSize.Height() ) / 2 + rTextPaintInfo.GetAscent(); + nY += ( rRect.Height() + aFontSize.Width() ) / 2; + break; + case 2700 : + if ( bCenter ) + nX += ( rRect.Width() + aFontSize.Height() ) / 2 - rTextPaintInfo.GetAscent(); + nY += ( rRect.Height() - aFontSize.Width() ) / 2; + break; + } + + Point aTmpPos( nX, nY ); + rNonConstTextPaintInfo.SetPos( aTmpPos ); + sal_uInt16 nOldWidth = rPor.Width(); + const_cast<SwLinePortion&>(rPor).Width( static_cast<sal_uInt16>(aFontSize.Width()) ); + rTextPaintInfo.DrawText( aTmp, rPor ); + const_cast<SwLinePortion&>(rPor).Width( nOldWidth ); + rNonConstTextPaintInfo.SetFont( const_cast<SwFont*>(pOldFnt) ); + rNonConstTextPaintInfo.SetPos( aOldPos ); +} + +void SwTextPaintInfo::DrawRect( const SwRect &rRect, bool bRetouche ) const +{ + if ( OnWin() || !bRetouche ) + { + if( aTextFly.IsOn() ) + const_cast<SwTextPaintInfo*>(this)->GetTextFly(). + DrawFlyRect( m_pOut, rRect ); + else + m_pOut->DrawRect( rRect.SVRect() ); + } +} + +void SwTextPaintInfo::DrawTab( const SwLinePortion &rPor ) const +{ + if( OnWin() ) + { + SwRect aRect; + CalcRect( rPor, &aRect ); + + if ( ! aRect.HasArea() ) + return; + + const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ? CHAR_TAB_RTL : CHAR_TAB; + const sal_uInt8 nOptions = DRAW_SPECIAL_OPTIONS_CENTER | DRAW_SPECIAL_OPTIONS_ROTATE; + + lcl_DrawSpecial( *this, rPor, aRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions ); + } +} + +void SwTextPaintInfo::DrawLineBreak( const SwLinePortion &rPor ) const +{ + if( OnWin() ) + { + sal_uInt16 nOldWidth = rPor.Width(); + const_cast<SwLinePortion&>(rPor).Width( LINE_BREAK_WIDTH ); + + SwRect aRect; + CalcRect( rPor, &aRect ); + + if( aRect.HasArea() ) + { + const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ? + CHAR_LINEBREAK_RTL : CHAR_LINEBREAK; + const sal_uInt8 nOptions = 0; + + lcl_DrawSpecial( *this, rPor, aRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions ); + } + + const_cast<SwLinePortion&>(rPor).Width( nOldWidth ); + } +} + +void SwTextPaintInfo::DrawRedArrow( const SwLinePortion &rPor ) const +{ + Size aSize( SPECIAL_FONT_HEIGHT, SPECIAL_FONT_HEIGHT ); + SwRect aRect( static_cast<const SwArrowPortion&>(rPor).GetPos(), aSize ); + sal_Unicode cChar; + if( static_cast<const SwArrowPortion&>(rPor).IsLeft() ) + { + aRect.Pos().AdjustY(20 - GetAscent() ); + aRect.Pos().AdjustX(20 ); + if( aSize.Height() > rPor.Height() ) + aRect.Height( rPor.Height() ); + cChar = CHAR_LEFT_ARROW; + } + else + { + if( aSize.Height() > rPor.Height() ) + aRect.Height( rPor.Height() ); + aRect.Pos().AdjustY( -(aRect.Height() + 20) ); + aRect.Pos().AdjustX( -(aRect.Width() + 20) ); + cChar = CHAR_RIGHT_ARROW; + } + + if ( GetTextFrame()->IsVertical() ) + GetTextFrame()->SwitchHorizontalToVertical( aRect ); + + if( aRect.HasArea() ) + { + const sal_uInt8 nOptions = 0; + lcl_DrawSpecial( *this, rPor, aRect, COL_LIGHTRED, cChar, nOptions ); + } +} + +void SwTextPaintInfo::DrawPostIts( bool bScript ) const +{ + if( !OnWin() || !m_pOpt->IsPostIts() ) + return; + + Size aSize; + Point aTmp; + + const sal_uInt16 nPostItsWidth = SwViewOption::GetPostItsWidth( GetOut() ); + const sal_uInt16 nFontHeight = m_pFnt->GetHeight( m_pVsh, *GetOut() ); + const sal_uInt16 nFontAscent = m_pFnt->GetAscent( m_pVsh, *GetOut() ); + + switch ( m_pFnt->GetOrientation( GetTextFrame()->IsVertical() ) ) + { + case 0 : + aSize.setWidth( nPostItsWidth ); + aSize.setHeight( nFontHeight ); + aTmp.setX( aPos.X() ); + aTmp.setY( aPos.Y() - nFontAscent ); + break; + case 900 : + aSize.setHeight( nPostItsWidth ); + aSize.setWidth( nFontHeight ); + aTmp.setX( aPos.X() - nFontAscent ); + aTmp.setY( aPos.Y() ); + break; + case 2700 : + aSize.setHeight( nPostItsWidth ); + aSize.setWidth( nFontHeight ); + aTmp.setX( aPos.X() - nFontHeight + + nFontAscent ); + aTmp.setY( aPos.Y() ); + break; + } + + SwRect aTmpRect( aTmp, aSize ); + + if ( GetTextFrame()->IsRightToLeft() ) + GetTextFrame()->SwitchLTRtoRTL( aTmpRect ); + + if ( GetTextFrame()->IsVertical() ) + GetTextFrame()->SwitchHorizontalToVertical( aTmpRect ); + + const tools::Rectangle aRect( aTmpRect.SVRect() ); + SwViewOption::PaintPostIts( const_cast<OutputDevice*>(GetOut()), aRect, bScript ); + +} + +void SwTextPaintInfo::DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool bChecked) const +{ + SwRect aIntersect; + CalcRect( rPor, &aIntersect ); + if ( aIntersect.HasArea() ) + { + if (OnWin() && SwViewOption::IsFieldShadings() && + !GetOpt().IsPagePreview()) + { + OutputDevice* pOut = const_cast<OutputDevice*>(GetOut()); + pOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + pOut->SetFillColor( SwViewOption::GetFieldShadingsColor() ); + pOut->SetLineColor(); + pOut->DrawRect( aIntersect.SVRect() ); + pOut->Pop(); + } + const int delta=10; + tools::Rectangle r(aIntersect.Left()+delta, aIntersect.Top()+delta, aIntersect.Right()-delta, aIntersect.Bottom()-delta); + m_pOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + m_pOut->SetLineColor( Color(0, 0, 0)); + m_pOut->SetFillColor(); + m_pOut->DrawRect( r ); + if (bChecked) + { + m_pOut->DrawLine(r.TopLeft(), r.BottomRight()); + m_pOut->DrawLine(r.TopRight(), r.BottomLeft()); + } + m_pOut->Pop(); + } +} + +void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor ) const +{ + OSL_ENSURE( OnWin(), "SwTextPaintInfo::DrawBackground: printer pollution ?" ); + + SwRect aIntersect; + CalcRect( rPor, nullptr, &aIntersect, true ); + + if ( aIntersect.HasArea() ) + { + OutputDevice* pOut = const_cast<OutputDevice*>(GetOut()); + pOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + + // For dark background we do not want to have a filled rectangle + if ( GetVsh() && GetVsh()->GetWin() && lcl_IsDarkBackground( *this ) ) + { + pOut->SetLineColor( SwViewOption::GetFontColor() ); + } + else + { + pOut->SetFillColor( SwViewOption::GetFieldShadingsColor() ); + pOut->SetLineColor(); + } + + DrawRect( aIntersect, true ); + pOut->Pop(); + } +} + +void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const +{ + { + SwRect aIntersect; + CalcRect( rPor, &aIntersect, nullptr, true ); + if(aIntersect.HasArea()) + { + SwPosition const aPosition(m_pFrame->MapViewToModelPos(GetIdx())); + const ::sw::mark::IMark* pFieldmark = + m_pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition); + bool bIsStartMark = (TextFrameIndex(1) == GetLen() + && CH_TXT_ATR_FIELDSTART == GetText()[sal_Int32(GetIdx())]); + if(pFieldmark) { + SAL_INFO("sw.core", "Found Fieldmark " << pFieldmark->ToString()); + } + if(bIsStartMark) + SAL_INFO("sw.core", "Found StartMark"); + if (OnWin() && (pFieldmark!=nullptr || bIsStartMark) && + SwViewOption::IsFieldShadings() && + !GetOpt().IsPagePreview()) + { + OutputDevice* pOutDev = const_cast<OutputDevice*>(GetOut()); + pOutDev->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + pOutDev->SetFillColor( SwViewOption::GetFieldShadingsColor() ); + pOutDev->SetLineColor( ); + pOutDev->DrawRect( aIntersect.SVRect() ); + pOutDev->Pop(); + } + } + } + + SwRect aIntersect; + CalcRect( rPor, nullptr, &aIntersect, true ); + + if ( aIntersect.HasArea() ) + { + OutputDevice* pTmpOut = const_cast<OutputDevice*>(GetOut()); + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pTmpOut ); + + Color aFillColor; + + if( m_pFnt->GetHighlightColor() != COL_TRANSPARENT ) + { + aFillColor = m_pFnt->GetHighlightColor(); + } + else + { + if( !m_pFnt->GetBackColor() ) + return; + aFillColor = *m_pFnt->GetBackColor(); + } + + // tdf#104349 do not highlight portions of space chars before end of line if the compatibility option is enabled + // for LTR mode only + if ( !GetTextFrame()->IsRightToLeft() ) + { + if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS)) + { + bool draw = false; + bool full = false; + SwLinePortion *pPos = const_cast<SwLinePortion *>(&rPor); + TextFrameIndex nIdx = GetIdx(); + TextFrameIndex nLen; + + do + { + nLen = pPos->GetLen(); + for (TextFrameIndex i = nIdx; i < (nIdx + nLen); ++i) + { + if (i < TextFrameIndex(GetText().getLength()) + && GetText()[sal_Int32(i)] == CH_TXTATR_NEWLINE) + { + if ( i >= (GetIdx() + rPor.GetLen()) ) + { + goto drawcontinue; + } + } + if (i >= TextFrameIndex(GetText().getLength()) + || GetText()[sal_Int32(i)] != CH_BLANK) + { + draw = true; + if ( i >= (GetIdx() + rPor.GetLen()) ) + { + full = true; + goto drawcontinue; + } + } + } + nIdx += nLen; + pPos = pPos->GetNextPortion(); + } while ( pPos ); + + drawcontinue: + + if ( !draw ) + return; + + if ( !full ) + { + pPos = const_cast<SwLinePortion *>(&rPor); + nIdx = GetIdx(); + + nLen = pPos->GetLen(); + for (TextFrameIndex i = nIdx + nLen - TextFrameIndex(1); + i >= nIdx; --i) + { + if (i < TextFrameIndex(GetText().getLength()) + && GetText()[sal_Int32(i)] == CH_TXTATR_NEWLINE) + { + continue; + } + if (i >= TextFrameIndex(GetText().getLength()) + || GetText()[sal_Int32(i)] != CH_BLANK) + { + sal_uInt16 nOldWidth = rPor.Width(); + sal_uInt16 nNewWidth = GetTextSize(m_pOut, nullptr, + GetText(), nIdx, (i + TextFrameIndex(1) - nIdx)).Width(); + + const_cast<SwLinePortion&>(rPor).Width( nNewWidth ); + CalcRect( rPor, nullptr, &aIntersect, true ); + const_cast<SwLinePortion&>(rPor).Width( nOldWidth ); + + if ( !aIntersect.HasArea() ) + { + return; + } + + break; + } + } + } + } + } + + pTmpOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + + pTmpOut->SetFillColor(aFillColor); + pTmpOut->SetLineColor(); + + DrawRect( aIntersect, false ); + + pTmpOut->Pop(); + } +} + +void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const +{ + SwRect aDrawArea; + CalcRect( rPor, &aDrawArea ); + if ( aDrawArea.HasArea() ) + { + PaintCharacterBorder(*m_pFnt, aDrawArea, GetTextFrame()->IsVertical(), + GetTextFrame()->IsVertLRBT(), rPor.GetJoinBorderWithPrev(), + rPor.GetJoinBorderWithNext()); + } +} + +void SwTextPaintInfo::DrawViewOpt( const SwLinePortion &rPor, + PortionType nWhich ) const +{ + if( OnWin() && !IsMulti() ) + { + bool bDraw = false; + switch( nWhich ) + { + case PortionType::Footnote: + case PortionType::QuoVadis: + case PortionType::Number: + case PortionType::Field: + case PortionType::Hidden: + case PortionType::Tox: + case PortionType::Ref: + case PortionType::Meta: + case PortionType::ControlChar: + if ( !GetOpt().IsPagePreview() + && !GetOpt().IsReadonly() + && SwViewOption::IsFieldShadings() + && ( PortionType::Number != nWhich + || m_pFrame->GetTextNodeForParaProps()->HasMarkedLabel())) // #i27615# + { + bDraw = true; + } + break; + case PortionType::Bookmark: + // no shading + break; + case PortionType::InputField: + // input field shading also in read-only mode + if ( !GetOpt().IsPagePreview() + && SwViewOption::IsFieldShadings() ) + { + bDraw = true; + } + break; + case PortionType::Table: + if ( GetOpt().IsTab() ) bDraw = true; + break; + case PortionType::SoftHyphen: + if ( GetOpt().IsSoftHyph() )bDraw = true; + break; + case PortionType::Blank: + if ( GetOpt().IsHardBlank())bDraw = true; + break; + default: + { + OSL_ENSURE( false, "SwTextPaintInfo::DrawViewOpt: don't know how to draw this" ); + break; + } + } + if ( bDraw ) + DrawBackground( rPor ); + } +} + +static void lcl_InitHyphValues( PropertyValues &rVals, + sal_Int16 nMinLeading, sal_Int16 nMinTrailing, bool bNoCapsHyphenation ) +{ + sal_Int32 nLen = rVals.getLength(); + + if (0 == nLen) // yet to be initialized? + { + rVals.realloc( 3 ); + PropertyValue *pVal = rVals.getArray(); + + pVal[0].Name = UPN_HYPH_MIN_LEADING; + pVal[0].Handle = UPH_HYPH_MIN_LEADING; + pVal[0].Value <<= nMinLeading; + + pVal[1].Name = UPN_HYPH_MIN_TRAILING; + pVal[1].Handle = UPH_HYPH_MIN_TRAILING; + pVal[1].Value <<= nMinTrailing; + + pVal[2].Name = UPN_HYPH_NO_CAPS; + pVal[2].Handle = UPH_HYPH_NO_CAPS; + pVal[2].Value <<= bNoCapsHyphenation; + } + else if (3 == nLen) // already initialized once? + { + PropertyValue *pVal = rVals.getArray(); + pVal[0].Value <<= nMinLeading; + pVal[1].Value <<= nMinTrailing; + pVal[2].Value <<= bNoCapsHyphenation; + } + else { + OSL_FAIL( "unexpected size of sequence" ); + } +} + +const PropertyValues & SwTextFormatInfo::GetHyphValues() const +{ + OSL_ENSURE( 3 == m_aHyphVals.getLength(), + "hyphenation values not yet initialized" ); + return m_aHyphVals; +} + +bool SwTextFormatInfo::InitHyph( const bool bAutoHyphen ) +{ + const SwAttrSet& rAttrSet = GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet(); + SetHanging( rAttrSet.GetHangingPunctuation().GetValue() ); + SetScriptSpace( rAttrSet.GetScriptSpace().GetValue() ); + SetForbiddenChars( rAttrSet.GetForbiddenRule().GetValue() ); + const SvxHyphenZoneItem &rAttr = rAttrSet.GetHyphenZone(); + MaxHyph() = rAttr.GetMaxHyphens(); + const bool bAuto = bAutoHyphen || rAttr.IsHyphen(); + if( bAuto || m_bInterHyph ) + { + const sal_Int16 nMinimalLeading = std::max(rAttr.GetMinLead(), sal_uInt8(2)); + const sal_Int16 nMinimalTrailing = rAttr.GetMinTrail(); + const bool bNoCapsHyphenation = rAttr.IsNoCapsHyphenation(); + lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing, bNoCapsHyphenation); + } + return bAuto; +} + +void SwTextFormatInfo::CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwTextFrame *pNewFrame, const bool bNewInterHyph, + const bool bNewQuick, const bool bTst ) +{ + CtorInitTextPaintInfo( pRenderContext, pNewFrame, SwRect() ); + + m_bQuick = bNewQuick; + m_bInterHyph = bNewInterHyph; + + //! needs to be done in this order + m_bAutoHyph = InitHyph(); + + m_bIgnoreFly = false; + m_bFakeLineStart = false; + m_bShift = false; + m_bDropInit = false; + m_bTestFormat = bTst; + m_nLeft = 0; + m_nRight = 0; + m_nFirst = 0; + m_nRealWidth = 0; + m_nForcedLeftMargin = 0; + m_pRest = nullptr; + m_nLineHeight = 0; + m_nLineNetHeight = 0; + SetLineStart(TextFrameIndex(0)); + + SvtCTLOptions::TextNumerals const nTextNumerals( + SW_MOD()->GetCTLOptions().GetCTLTextNumerals()); + // cannot cache for NUMERALS_CONTEXT because we need to know the string + // for the whole paragraph now + if (nTextNumerals != SvtCTLOptions::NUMERALS_CONTEXT) + { + // set digit mode to what will be used later to get same results + SwDigitModeModifier const m(*m_pRef, LANGUAGE_NONE /*dummy*/); + assert(m_pRef->GetDigitLanguage() != LANGUAGE_NONE); + SetCachedVclData(OutputDevice::CreateTextLayoutCache(*m_pText)); + } + + Init(); +} + +/** + * If the Hyphenator returns ERROR or the language is set to NOLANGUAGE + * we do not hyphenate. + * Else, we always hyphenate if we do interactive hyphenation. + * If we do not do interactive hyphenation, we only hyphenate if ParaFormat is + * set to automatic hyphenation. + */ +bool SwTextFormatInfo::IsHyphenate() const +{ + if( !m_bInterHyph && !m_bAutoHyph ) + return false; + + LanguageType eTmp = GetFont()->GetLanguage(); + if( LANGUAGE_DONTKNOW == eTmp || LANGUAGE_NONE == eTmp ) + return false; + + uno::Reference< XHyphenator > xHyph = ::GetHyphenator(); + if (!xHyph.is()) + return false; + + if (m_bInterHyph) + SvxSpellWrapper::CheckHyphLang( xHyph, eTmp ); + + if (!xHyph->hasLocale(g_pBreakIt->GetLocale(eTmp))) + { + SfxObjectShell* pShell = m_pFrame->GetDoc().GetDocShell(); + if (pShell) + { + pShell->AppendInfoBarWhenReady( + "hyphenationmissing", SwResId(STR_HYPH_MISSING), + SwResId(STR_HYPH_MISSING_DETAIL) + .replaceFirst("%1", g_pBreakIt->GetLocale(eTmp).Language), + InfobarType::WARNING); + } + } + + return xHyph->hasLocale( g_pBreakIt->GetLocale(eTmp) ); +} + +const SwFormatDrop *SwTextFormatInfo::GetDropFormat() const +{ + const SwFormatDrop *pDrop = &GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetDrop(); + if( 1 >= pDrop->GetLines() || + ( !pDrop->GetChars() && !pDrop->GetWholeWord() ) ) + pDrop = nullptr; + return pDrop; +} + +void SwTextFormatInfo::Init() +{ + // Not initialized: pRest, nLeft, nRight, nFirst, nRealWidth + X(0); + m_bArrowDone = m_bFull = m_bFootnoteDone = m_bErgoDone = m_bNumDone = m_bNoEndHyph = + m_bNoMidHyph = m_bStop = m_bNewLine = m_bUnderflow = m_bTabOverflow = false; + + // generally we do not allow number portions in follows, except... + if ( GetTextFrame()->IsFollow() ) + { + const SwTextFrame* pMaster = GetTextFrame()->FindMaster(); + OSL_ENSURE(pMaster, "pTextFrame without Master"); + const SwLinePortion* pTmpPara = pMaster ? pMaster->GetPara() : nullptr; + + // there is a master for this follow and the master does not have + // any contents (especially it does not have a number portion) + m_bNumDone = ! pTmpPara || + ! static_cast<const SwParaPortion*>(pTmpPara)->GetFirstPortion()->IsFlyPortion(); + } + + m_pRoot = nullptr; + m_pLast = nullptr; + m_pFly = nullptr; + m_pLastTab = nullptr; + m_pUnderflow = nullptr; + m_cTabDecimal = 0; + m_nWidth = m_nRealWidth; + m_nForcedLeftMargin = 0; + m_nSoftHyphPos = TextFrameIndex(0); + m_nUnderScorePos = TextFrameIndex(COMPLETE_STRING); + m_nLastBookmarkPos = TextFrameIndex(-1); + m_cHookChar = 0; + SetIdx(TextFrameIndex(0)); + SetLen(TextFrameIndex(GetText().getLength())); + SetPaintOfst(0); +} + +SwTextFormatInfo::SwTextFormatInfo(OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyphL, + const bool bQuickL, const bool bTst) +{ + CtorInitTextFormatInfo(pRenderContext, pFrame, bInterHyphL, bQuickL, bTst); +} + +/** + * There are a few differences between a copy constructor + * and the following constructor for multi-line formatting. + * The root is the first line inside the multi-portion, + * the line start is the actual position in the text, + * the line width is the rest width from the surrounding line + * and the bMulti and bFirstMulti-flag has to be set correctly. + */ +SwTextFormatInfo::SwTextFormatInfo( const SwTextFormatInfo& rInf, + SwLineLayout& rLay, SwTwips nActWidth ) : + SwTextPaintInfo( rInf ), + m_pRoot(&rLay), + m_pLast(&rLay), + m_pFly(nullptr), + m_pUnderflow(nullptr), + m_pRest(nullptr), + m_pLastTab(nullptr), + m_nSoftHyphPos(TextFrameIndex(0)), + m_nLineStart(rInf.GetIdx()), + m_nUnderScorePos(TextFrameIndex(COMPLETE_STRING)), + m_nLeft(rInf.m_nLeft), + m_nRight(rInf.m_nRight), + m_nFirst(rInf.m_nLeft), + m_nRealWidth(sal_uInt16(nActWidth)), + m_nWidth(m_nRealWidth), + m_nLineHeight(0), + m_nLineNetHeight(0), + m_nForcedLeftMargin(0), + m_bFull(false), + m_bFootnoteDone(true), + m_bErgoDone(true), + m_bNumDone(true), + m_bArrowDone(true), + m_bStop(false), + m_bNewLine(true), + m_bShift(false), + m_bUnderflow(false), + m_bInterHyph(false), + m_bAutoHyph(false), + m_bDropInit(false), + m_bQuick(rInf.m_bQuick), + m_bNoEndHyph(false), + m_bNoMidHyph(false), + m_bIgnoreFly(false), + m_bFakeLineStart(false), + m_bTabOverflow( false ), + m_bTestFormat(rInf.m_bTestFormat), + m_cTabDecimal(0), + m_cHookChar(0), + m_nMaxHyph(0) +{ + SetMulti( true ); + SetFirstMulti( rInf.IsFirstMulti() ); +} + +bool SwTextFormatInfo::CheckFootnotePortion_( SwLineLayout const * pCurr ) +{ + const sal_uInt16 nHeight = pCurr->GetRealHeight(); + for( SwLinePortion *pPor = pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion() ) + { + if( pPor->IsFootnotePortion() && nHeight > static_cast<SwFootnotePortion*>(pPor)->Orig() ) + { + SetLineHeight( nHeight ); + SetLineNetHeight( pCurr->Height() ); + return true; + } + } + return false; +} + +TextFrameIndex SwTextFormatInfo::ScanPortionEnd(TextFrameIndex const nStart, + TextFrameIndex const nEnd) +{ + m_cHookChar = 0; + TextFrameIndex i = nStart; + + // Used for decimal tab handling: + const sal_Unicode cTabDec = GetLastTab() ? GetTabDecimal() : 0; + const sal_Unicode cThousandSep = ',' == cTabDec ? '.' : ','; + + // #i45951# German (Switzerland) uses ' as thousand separator + const sal_Unicode cThousandSep2 = ',' == cTabDec ? '.' : '\''; + + bool bNumFound = false; + const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT); + + for( ; i < nEnd; ++i ) + { + const sal_Unicode cPos = GetChar( i ); + switch( cPos ) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + if( !HasHint( i )) + break; + [[fallthrough]]; + + case CHAR_SOFTHYPHEN: + case CHAR_HARDHYPHEN: + case CHAR_HARDBLANK: + case CH_TAB: + case CH_BREAK: + case CHAR_ZWSP : + case CHAR_ZWNBSP : + m_cHookChar = cPos; + return i; + + case CHAR_UNDERSCORE: + if (TextFrameIndex(COMPLETE_STRING) == m_nUnderScorePos) + m_nUnderScorePos = i; + break; + + default: + if ( cTabDec ) + { + if( cTabDec == cPos ) + { + OSL_ENSURE( cPos, "Unexpected end of string" ); + if( cPos ) // robust + { + m_cHookChar = cPos; + return i; + } + } + + // Compatibility: First non-digit character behind a + // a digit character becomes the hook character + if ( bTabCompat ) + { + if ( ( 0x2F < cPos && cPos < 0x3A ) || + ( bNumFound && ( cPos == cThousandSep || cPos == cThousandSep2 ) ) ) + { + bNumFound = true; + } + else + { + if ( bNumFound ) + { + m_cHookChar = cPos; + SetTabDecimal( cPos ); + return i; + } + } + } + } + } + } + + // Check if character *behind* the portion has + // to become the hook: + if (i == nEnd && i < TextFrameIndex(GetText().getLength()) && bNumFound) + { + const sal_Unicode cPos = GetChar( i ); + if ( cPos != cTabDec && cPos != cThousandSep && cPos !=cThousandSep2 && ( 0x2F >= cPos || cPos >= 0x3A ) ) + { + m_cHookChar = GetChar( i ); + SetTabDecimal( m_cHookChar ); + } + } + + return i; +} + +bool SwTextFormatInfo::LastKernPortion() +{ + if( GetLast() ) + { + if( GetLast()->IsKernPortion() ) + return true; + if( GetLast()->Width() || ( GetLast()->GetLen() && + !GetLast()->IsHolePortion() ) ) + return false; + } + SwLinePortion* pPor = GetRoot(); + SwLinePortion *pKern = nullptr; + while( pPor ) + { + if( pPor->IsKernPortion() ) + pKern = pPor; + else if( pPor->Width() || ( pPor->GetLen() && !pPor->IsHolePortion() ) ) + pKern = nullptr; + pPor = pPor->GetNextPortion(); + } + if( pKern ) + { + SetLast( pKern ); + return true; + } + return false; +} + +SwTwips SwTextFormatInfo::GetLineWidth() +{ + SwTwips nLineWidth = Width() - X(); + + const bool bTabOverMargin = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::TAB_OVER_MARGIN); + if (!bTabOverMargin) + return nLineWidth; + + SwTabPortion* pLastTab = GetLastTab(); + if (!pLastTab) + return nLineWidth; + + // Consider tab portions over the printing bounds of the text frame. + if (pLastTab->GetTabPos() <= Width()) + return nLineWidth; + + // Calculate the width that starts at the left (or in case of first line: + // first) margin, but ends after the right paragraph margin: + // + // +--------------------+ + // |LL| |RR| + // +--------------------+ + // ^ m_nLeftMargin (absolute) + // ^ nLeftMarginWidth (relative to m_nLeftMargin), X() is relative to this + // ^ right margin + // ^ paragraph right + // <--------------------> is GetTextFrame()->getFrameArea().Width() + // <--------------> is Width() + // <-----------------> is what we need to be able to compare to X() (nTextFrameWidth) + SwTwips nLeftMarginWidth = m_nLeftMargin - GetTextFrame()->getFrameArea().Left(); + SwTwips nTextFrameWidth = GetTextFrame()->getFrameArea().Width() - nLeftMarginWidth; + + // If there is one such tab portion, then text is allowed to use the full + // text frame area to the right (RR above, but not LL). + nLineWidth = nTextFrameWidth - X(); + + return nLineWidth; +} + +SwTextSlot::SwTextSlot( + const SwTextSizeInfo *pNew, + const SwLinePortion *pPor, + bool bTextLen, + bool bExgLists, + OUString const & rCh ) + : pOldText(nullptr) + , m_pOldSmartTagList(nullptr) + , m_pOldGrammarCheckList(nullptr) + , nIdx(0) + , nLen(0) + , pInf(nullptr) +{ + if( rCh.isEmpty() ) + { + bOn = pPor->GetExpText( *pNew, aText ); + } + else + { + aText = rCh; + bOn = true; + } + + // The text is replaced ... + if( bOn ) + { + pInf = const_cast<SwTextSizeInfo*>(pNew); + nIdx = pInf->GetIdx(); + nLen = pInf->GetLen(); + pOldText = &(pInf->GetText()); + m_pOldCachedVclData = pInf->GetCachedVclData(); + pInf->SetText( aText ); + pInf->SetIdx(TextFrameIndex(0)); + pInf->SetLen(bTextLen ? TextFrameIndex(pInf->GetText().getLength()) : pPor->GetLen()); + pInf->SetCachedVclData(nullptr); + + // ST2 + if ( bExgLists ) + { + m_pOldSmartTagList = static_cast<SwTextPaintInfo*>(pInf)->GetSmartTags(); + if (m_pOldSmartTagList) + { + std::pair<SwTextNode const*, sal_Int32> pos(pNew->GetTextFrame()->MapViewToModel(nIdx)); + SwWrongList const*const pSmartTags(pos.first->GetSmartTags()); + if (pSmartTags) + { + const sal_uInt16 nPos = pSmartTags->GetWrongPos(pos.second); + const sal_Int32 nListPos = pSmartTags->Pos(nPos); + if (nListPos == pos.second && pSmartTags->SubList(nPos) != nullptr) + { + m_pTempIter.reset(new sw::WrongListIterator(*pSmartTags->SubList(nPos))); + static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pTempIter.get()); + } + else if (!m_pTempList && nPos < pSmartTags->Count() + && nListPos < pos.second && !aText.isEmpty()) + { + m_pTempList.reset(new SwWrongList( WRONGLIST_SMARTTAG )); + m_pTempList->Insert( OUString(), nullptr, 0, aText.getLength(), 0 ); + m_pTempIter.reset(new sw::WrongListIterator(*m_pTempList)); + static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pTempIter.get()); + } + else + static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(nullptr); + } + else + static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(nullptr); + } + m_pOldGrammarCheckList = static_cast<SwTextPaintInfo*>(pInf)->GetGrammarCheckList(); + if (m_pOldGrammarCheckList) + { + std::pair<SwTextNode const*, sal_Int32> pos(pNew->GetTextFrame()->MapViewToModel(nIdx)); + SwWrongList const*const pGrammar(pos.first->GetGrammarCheck()); + if (pGrammar) + { + const sal_uInt16 nPos = pGrammar->GetWrongPos(pos.second); + const sal_Int32 nListPos = pGrammar->Pos(nPos); + if (nListPos == pos.second && pGrammar->SubList(nPos) != nullptr) + { + m_pTempIter.reset(new sw::WrongListIterator(*pGrammar->SubList(nPos))); + static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pTempIter.get()); + } + else if (!m_pTempList && nPos < pGrammar->Count() + && nListPos < pos.second && !aText.isEmpty()) + { + m_pTempList.reset(new SwWrongList( WRONGLIST_GRAMMAR )); + m_pTempList->Insert( OUString(), nullptr, 0, aText.getLength(), 0 ); + m_pTempIter.reset(new sw::WrongListIterator(*m_pTempList)); + static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pTempIter.get()); + } + else + static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(nullptr); + } + else + static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(nullptr); + } + } + } +} + +SwTextSlot::~SwTextSlot() +{ + if( bOn ) + { + pInf->SetCachedVclData(m_pOldCachedVclData); + pInf->SetText( *pOldText ); + pInf->SetIdx( nIdx ); + pInf->SetLen( nLen ); + + // ST2 + // Restore old smart tag list + if (m_pOldSmartTagList) + static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pOldSmartTagList); + if (m_pOldGrammarCheckList) + static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pOldGrammarCheckList); + } +} + +SwFontSave::SwFontSave(const SwTextSizeInfo &rInf, SwFont *pNew, + SwAttrIter* pItr) + : pInf(nullptr) + , pFnt(pNew ? const_cast<SwTextSizeInfo&>(rInf).GetFont() : nullptr) + , pIter(nullptr) +{ + if( pFnt ) + { + pInf = &const_cast<SwTextSizeInfo&>(rInf); + // In these cases we temporarily switch to the new font: + // 1. the fonts have a different magic number + // 2. they have different script types + // 3. their background colors differ (this is not covered by 1.) + if( pFnt->DifferentFontCacheId( pNew, pFnt->GetActual() ) || + pNew->GetActual() != pFnt->GetActual() || + ( ! pNew->GetBackColor() && pFnt->GetBackColor() ) || + ( pNew->GetBackColor() && ! pFnt->GetBackColor() ) || + ( pNew->GetBackColor() && pFnt->GetBackColor() && + ( *pNew->GetBackColor() != *pFnt->GetBackColor() ) ) ) + { + pNew->SetTransparent( true ); + pNew->SetAlign( ALIGN_BASELINE ); + pInf->SetFont( pNew ); + } + else + pFnt = nullptr; + pNew->Invalidate(); + pNew->ChgPhysFnt( pInf->GetVsh(), *pInf->GetOut() ); + if( pItr && pItr->GetFnt() == pFnt ) + { + pIter = pItr; + pIter->SetFnt( pNew ); + } + } +} + +SwFontSave::~SwFontSave() +{ + if( pFnt ) + { + // Reset SwFont + pFnt->Invalidate(); + pInf->SetFont( pFnt ); + if( pIter ) + { + pIter->SetFnt( pFnt ); + pIter->m_nPosition = COMPLETE_STRING; + } + } +} + +bool SwTextFormatInfo::ChgHyph( const bool bNew ) +{ + const bool bOld = m_bAutoHyph; + if( m_bAutoHyph != bNew ) + { + m_bAutoHyph = bNew; + InitHyph( bNew ); + // Set language in the Hyphenator + if( m_pFnt ) + m_pFnt->ChgPhysFnt( m_pVsh, *m_pOut ); + } + return bOld; +} + + +bool SwTextFormatInfo::CheckCurrentPosBookmark() +{ + if (m_nLastBookmarkPos != GetIdx()) + { + m_nLastBookmarkPos = GetIdx(); + return true; + } + else + { + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx new file mode 100644 index 000000000..83c173e2d --- /dev/null +++ b/sw/source/core/text/inftxt.hxx @@ -0,0 +1,788 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_INFTXT_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_INFTXT_HXX +#include <memory> +#include <com/sun/star/beans/PropertyValues.hpp> + +#include <map> + +#include <swtypes.hxx> +#include <swrect.hxx> +#include <txtfly.hxx> +#include <swfont.hxx> +#include "porlay.hxx" +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <editeng/paravertalignitem.hxx> +#include <sal/log.hxx> + +namespace com::sun::star::linguistic2 { class XHyphenatedWord; } + +class SvxBrushItem; +class SvxLineSpacingItem; +class SvxTabStop; +class SvxTabStopItem; +class SwFlyPortion; +class SwFormatDrop; +class SwLinePortion; +class SwTabPortion; +class SwViewOption; +class SwViewShell; +class SwAttrIter; +struct SwMultiCreator; +class SwMultiPortion; +namespace sw { class WrongListIterator; } + +#define ARROW_WIDTH 200 +#define DIR_LEFT2RIGHT 0 +#define DIR_BOTTOM2TOP 1 +#define DIR_RIGHT2LEFT 2 +#define DIR_TOP2BOTTOM 3 + +#ifdef DBG_UTIL +#define OPTDBG( rInf ) (rInf).IsOptDbg() +#else +#define OPTDBG( rInf ) false +#endif + +// Respects the attribute LineSpace when calculating the Height/Ascent +class SwLineInfo +{ + friend class SwTextIter; + + std::unique_ptr<SvxTabStopItem> pRuler; + const SvxLineSpacingItem *pSpace; + SvxParaVertAlignItem::Align nVertAlign; + sal_uInt16 nDefTabStop; + bool bListTabStopIncluded; + long nListTabStopPosition; + + void CtorInitLineInfo( const SwAttrSet& rAttrSet, + const SwTextNode& rTextNode ); + + SwLineInfo(); + ~SwLineInfo(); +public: + // #i24363# tab stops relative to indent - returns the tab stop following nSearchPos or NULL + const SvxTabStop *GetTabStop( const SwTwips nSearchPos, + const SwTwips nRight ) const; + const SvxLineSpacingItem *GetLineSpacing() const { return pSpace; } + sal_uInt16 GetDefTabStop() const { return nDefTabStop; } + void SetDefTabStop( sal_uInt16 nNew ) const + { const_cast<SwLineInfo*>(this)->nDefTabStop = nNew; } + + // vertical alignment + SvxParaVertAlignItem::Align GetVertAlign() const { return nVertAlign; } + bool HasSpecialAlign( bool bVert ) const + { return bVert ? + ( SvxParaVertAlignItem::Align::Baseline != nVertAlign ) : + ( SvxParaVertAlignItem::Align::Baseline != nVertAlign && + SvxParaVertAlignItem::Align::Automatic != nVertAlign ); } + + sal_uInt16 NumberOfTabStops() const; + + bool IsListTabStopIncluded() const + { + return bListTabStopIncluded; + } + long GetListTabStopPosition() const + { + return nListTabStopPosition; + } +}; + +class SwTextInfo +{ + // Implementation in txthyph.cxx + friend void SetParaPortion( SwTextInfo *pInf, SwParaPortion *pRoot ); + SwParaPortion *m_pPara; + TextFrameIndex m_nTextStart; // TextOfst for Follows + +protected: + SwTextInfo() + : m_pPara(nullptr) + , m_nTextStart(0) + {} + +public: + void CtorInitTextInfo( SwTextFrame *pFrame ); + SwTextInfo( const SwTextInfo &rInf ); + explicit SwTextInfo( SwTextFrame *pFrame ) { CtorInitTextInfo( pFrame ); } + SwParaPortion *GetParaPortion() { return m_pPara; } + const SwParaPortion *GetParaPortion() const { return m_pPara; } + TextFrameIndex GetTextStart() const { return m_nTextStart; } +}; + +class SwTextSizeInfo : public SwTextInfo +{ +private: + typedef std::map< SwLinePortion const *, sal_uInt16 > SwTextPortionMap; + +protected: + // during formatting, a small database is built, mapping portion pointers + // to their maximum size (used for kana compression) + SwTextPortionMap m_aMaxWidth; + // for each line, an array of compression values is calculated + // this array is passed over to the info structure + std::deque<sal_uInt16>* m_pKanaComp; + + SwViewShell *m_pVsh; + + // m_pOut is the output device, m_pRef is the device used for formatting + VclPtr<OutputDevice> m_pOut; + VclPtr<OutputDevice> m_pRef; + + // performance hack - this is only used by SwTextFormatInfo but + // because it's not even possible to dynamic_cast these things + // currently it has to be stored here + std::shared_ptr<vcl::TextLayoutCache> m_pCachedVclData; + + SwFont *m_pFnt; + SwUnderlineFont *m_pUnderFnt; // Font for underlining + SwTextFrame *m_pFrame; + const SwViewOption *m_pOpt; + const OUString *m_pText; + TextFrameIndex m_nIdx; + TextFrameIndex m_nLen; + sal_uInt16 m_nKanaIdx; + bool m_bOnWin : 1; + bool m_bNotEOL : 1; + bool m_bURLNotify : 1; + bool m_bStopUnderflow : 1; // Underflow was stopped e.g. by a FlyPortion + bool m_bFootnoteInside : 1; // the current line contains a footnote + bool m_bOtherThanFootnoteInside : 1; // the current line contains another portion than a footnote portion. + // needed for checking keep together of footnote portion with previous portion + bool m_bMulti : 1; // inside a multiportion + bool m_bFirstMulti : 1; // this flag is used for two purposes: + // - the multiportion is the first lineportion + // - indicates, if we are currently in second + // line of multi portion + bool m_bRuby : 1; // during the formatting of a phonetic line + bool m_bHanging : 1; // formatting of hanging punctuation allowed + bool m_bScriptSpace : 1; // space between different scripts (Asian/Latin) + bool m_bForbiddenChars : 1; // Forbidden start/endline characters + bool m_bSnapToGrid : 1; // paragraph snaps to grid + sal_uInt8 m_nDirection : 2; // writing direction: 0/90/180/270 degree + +protected: + void CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, + TextFrameIndex nIdx); + SwTextSizeInfo(); +public: + SwTextSizeInfo( const SwTextSizeInfo &rInf ); + SwTextSizeInfo( const SwTextSizeInfo &rInf, const OUString* pText, + TextFrameIndex nIdx = TextFrameIndex(0) ); + SwTextSizeInfo(SwTextFrame *pTextFrame, TextFrameIndex nIndex = TextFrameIndex(0)); + + // GetMultiAttr returns the text attribute of the multiportion, + // if rPos is inside any multi-line part. + // rPos will set to the end of the multi-line part. + std::unique_ptr<SwMultiCreator> GetMultiCreator(TextFrameIndex &rPos, SwMultiPortion const* pM) const; + + bool OnWin() const { return m_bOnWin; } + void SetOnWin( const bool bNew ) { m_bOnWin = bNew; } + bool NotEOL() const { return m_bNotEOL; } + void SetNotEOL( const bool bNew ) { m_bNotEOL = bNew; } + bool URLNotify() const { return m_bURLNotify; } + bool StopUnderflow() const { return m_bStopUnderflow; } + void SetStopUnderflow( const bool bNew ) { m_bStopUnderflow = bNew; } + bool IsFootnoteInside() const { return m_bFootnoteInside; } + void SetFootnoteInside( const bool bNew ) { m_bFootnoteInside = bNew; } + bool IsOtherThanFootnoteInside() const { return m_bOtherThanFootnoteInside; } + void SetOtherThanFootnoteInside( const bool bNew ) { m_bOtherThanFootnoteInside = bNew; } + bool IsMulti() const { return m_bMulti; } + void SetMulti( const bool bNew ) { m_bMulti = bNew; } + bool IsFirstMulti() const { return m_bFirstMulti; } + void SetFirstMulti( const bool bNew ) { m_bFirstMulti = bNew; } + bool IsRuby() const { return m_bRuby; } + void SetRuby( const bool bNew ) { m_bRuby = bNew; } + bool IsHanging() const { return m_bHanging; } + void SetHanging( const bool bNew ) { m_bHanging = bNew; } + bool HasScriptSpace() const { return m_bScriptSpace; } + void SetScriptSpace( const bool bNew ) { m_bScriptSpace = bNew; } + bool HasForbiddenChars() const { return m_bForbiddenChars; } + void SetForbiddenChars( const bool bN ) { m_bForbiddenChars = bN; } + bool SnapToGrid() const { return m_bSnapToGrid; } + void SetSnapToGrid( const bool bN ) { m_bSnapToGrid = bN; } + sal_uInt8 GetDirection() const { return m_nDirection; } + void SetDirection( const sal_uInt8 nNew ) { m_nDirection = nNew; } + bool IsRotated() const { return ( 1 & m_nDirection ); } + + SwViewShell *GetVsh() { return m_pVsh; } + const SwViewShell *GetVsh() const { return m_pVsh; } + + vcl::RenderContext *GetOut() { return m_pOut; } + const vcl::RenderContext *GetOut() const { return m_pOut; } + void SetOut( OutputDevice* pNewOut ) { m_pOut = pNewOut; } + + vcl::RenderContext *GetRefDev() { return m_pRef; } + const vcl::RenderContext *GetRefDev() const { return m_pRef; } + + SwFont *GetFont() { return m_pFnt; } + const SwFont *GetFont() const { return m_pFnt; } + void SetFont( SwFont *pNew ) { m_pFnt = pNew; } + void SelectFont(); + void SetUnderFnt( SwUnderlineFont* pNew ) { m_pUnderFnt = pNew; } + SwUnderlineFont* GetUnderFnt() const { return m_pUnderFnt; } + + const SwViewOption &GetOpt() const { return *m_pOpt; } + const OUString &GetText() const { return *m_pText; } + sal_Unicode GetChar(TextFrameIndex const nPos) const { + if (m_pText && nPos < TextFrameIndex(m_pText->getLength())) return (*m_pText)[sal_Int32(nPos)]; + return 0; + } + + sal_uInt16 GetTextHeight() const; + + SwPosSize GetTextSize( OutputDevice* pOut, const SwScriptInfo* pSI, + const OUString& rText, TextFrameIndex nIdx, + TextFrameIndex nLen ) const; + SwPosSize GetTextSize() const; + void GetTextSize( const SwScriptInfo* pSI, TextFrameIndex nIdx, + TextFrameIndex nLen, const sal_uInt16 nComp, + sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff, + vcl::TextLayoutCache const* = nullptr) const; + inline SwPosSize GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx, + TextFrameIndex nLen) const; + inline SwPosSize GetTextSize( const OUString &rText ) const; + + TextFrameIndex GetTextBreak( const long nLineWidth, + const TextFrameIndex nMaxLen, + const sal_uInt16 nComp, + vcl::TextLayoutCache const*) const; + TextFrameIndex GetTextBreak( const long nLineWidth, + const TextFrameIndex nMaxLen, + const sal_uInt16 nComp, + TextFrameIndex& rExtraCharPos, + vcl::TextLayoutCache const*) const; + + sal_uInt16 GetAscent() const; + + TextFrameIndex GetIdx() const { return m_nIdx; } + void SetIdx(const TextFrameIndex nNew) { m_nIdx = nNew; } + TextFrameIndex GetLen() const { return m_nLen; } + void SetLen(const TextFrameIndex nNew) { m_nLen = nNew; } + void SetText( const OUString &rNew ){ m_pText = &rNew; } + + // No Bullets for the symbol font! + bool IsNoSymbol() const + { return RTL_TEXTENCODING_SYMBOL != m_pFnt->GetCharSet( m_pFnt->GetActual() ); } + + void NoteAnimation() const; + + // Home is where Your heart is... + SwTextFrame *GetTextFrame() { return m_pFrame; } + const SwTextFrame *GetTextFrame() const { return m_pFrame; } + + bool HasHint(TextFrameIndex nPos) const; + + // If Kana Compression is enabled, a minimum and maximum portion width + // is calculated. We format lines with minimal size and share remaining + // space among compressed kanas. + // During formatting, the maximum values of compressible portions are + // stored in m_aMaxWidth and discarded after a line has been formatted. + void SetMaxWidthDiff( const SwLinePortion *nKey, sal_uInt16 nVal ) + { + m_aMaxWidth.insert( std::make_pair( nKey, nVal ) ); + }; + sal_uInt16 GetMaxWidthDiff( const SwLinePortion *nKey ) + { + SwTextPortionMap::iterator it = m_aMaxWidth.find( nKey ); + + if( it != m_aMaxWidth.end() ) + return it->second; + else + return 0; + }; + void ResetMaxWidthDiff() + { + m_aMaxWidth.clear(); + }; + bool CompressLine() + { + return !m_aMaxWidth.empty(); + }; + + // Feature: Kana Compression + + sal_uInt16 GetKanaIdx() const { return m_nKanaIdx; } + void ResetKanaIdx(){ m_nKanaIdx = 0; } + void SetKanaIdx( sal_uInt16 nNew ) { m_nKanaIdx = nNew; } + void IncKanaIdx() { ++m_nKanaIdx; } + void SetKanaComp( std::deque<sal_uInt16> *pNew ){ m_pKanaComp = pNew; } + std::deque<sal_uInt16>* GetpKanaComp() const { return m_pKanaComp; } + sal_uInt16 GetKanaComp() const + { return ( m_pKanaComp && m_nKanaIdx < m_pKanaComp->size() ) + ? (*m_pKanaComp)[m_nKanaIdx] : 0; } + + const std::shared_ptr<vcl::TextLayoutCache>& GetCachedVclData() const + { + return m_pCachedVclData; + } + void SetCachedVclData(std::shared_ptr<vcl::TextLayoutCache> const& pCachedVclData) + { + m_pCachedVclData = pCachedVclData; + } +}; + +class SwTextPaintInfo : public SwTextSizeInfo +{ + sw::WrongListIterator *m_pWrongList; + sw::WrongListIterator *m_pGrammarCheckList; + sw::WrongListIterator *m_pSmartTags; + std::vector<long>* pSpaceAdd; + const SvxBrushItem *pBrushItem; // For the background + SwTextFly aTextFly; // Calculate the FlyFrame + Point aPos; // Paint position + SwRect aPaintRect; // Original paint rect (from Layout paint) + + sal_uInt16 nSpaceIdx; + void DrawText_(const OUString &rText, const SwLinePortion &rPor, + const TextFrameIndex nIdx, const TextFrameIndex nLen, + const bool bKern, const bool bWrong = false, + const bool bSmartTag = false, + const bool bGrammarCheck = false ); + + SwTextPaintInfo &operator=(const SwTextPaintInfo&) = delete; + +protected: + SwTextPaintInfo() + : m_pWrongList(nullptr) + , m_pGrammarCheckList(nullptr) + , m_pSmartTags(nullptr) + , pSpaceAdd(nullptr) + , pBrushItem(nullptr) + , nSpaceIdx(0) + {} + +public: + SwTextPaintInfo( const SwTextPaintInfo &rInf ); + SwTextPaintInfo( const SwTextPaintInfo &rInf, const OUString* pText ); + + void CtorInitTextPaintInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const SwRect &rPaint ); + + const SvxBrushItem *GetBrushItem() const { return pBrushItem; } + + SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint ); + + SwTwips X() const { return aPos.X(); } + void X( const long nNew ) { aPos.setX(nNew); } + SwTwips Y() const { return aPos.Y(); } + void Y( const SwTwips nNew ) { aPos.setY(nNew); } + + SwTextFly& GetTextFly() { return aTextFly; } + const SwTextFly& GetTextFly() const { return aTextFly; } + inline void DrawText( const OUString &rText, const SwLinePortion &rPor, + TextFrameIndex nIdx = TextFrameIndex(0), + TextFrameIndex nLen = TextFrameIndex(COMPLETE_STRING), + const bool bKern = false) const; + inline void DrawText( const SwLinePortion &rPor, TextFrameIndex nLen, + const bool bKern = false ) const; + inline void DrawMarkedText( const SwLinePortion &rPor, TextFrameIndex nLen, + const bool bWrong, + const bool bSmartTags, + const bool bGrammarCheck ) const; + + void DrawRect( const SwRect &rRect, bool bRetouche ) const; + + void DrawTab( const SwLinePortion &rPor ) const; + void DrawLineBreak( const SwLinePortion &rPor ) const; + void DrawRedArrow( const SwLinePortion &rPor ) const; + void DrawPostIts( bool bScript ) const; + void DrawBackground( const SwLinePortion &rPor ) const; + void DrawViewOpt( const SwLinePortion &rPor, PortionType nWhich ) const; + void DrawBackBrush( const SwLinePortion &rPor ) const; + + /** + * Draw character border around a line portion. + * + * @param[in] rPor line portion around which border have to be drawn. + **/ + void DrawBorder( const SwLinePortion &rPor ) const; + + void DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool bChecked) const; + + /** + * Calculate the rectangular area where the portion takes place. + * @param[in] rPor portion for which the method specify the painting area + * @param[out] pRect whole area of the portion + * @param[out] pIntersect part of the portion area clipped by OutputDevice's clip region + * @param[in] bInsideBox area of portion's content, padding and border, but shadow + * is excluded (e.g. for background) + **/ + void CalcRect( const SwLinePortion& rPor, SwRect* pRect, + SwRect* pIntersect = nullptr, const bool bInsideBox = false ) const; + + inline SwTwips GetPaintOfst() const; + inline void SetPaintOfst( const SwTwips nNew ); + const Point &GetPos() const { return aPos; } + void SetPos( const Point &rNew ) { aPos = rNew; } + + const SwRect &GetPaintRect() const { return aPaintRect; } + + // STUFF FOR JUSTIFIED ALIGNMENT + + sal_uInt16 GetSpaceIdx() const { return nSpaceIdx; } + void ResetSpaceIdx(){nSpaceIdx = 0; } + void SetSpaceIdx( sal_uInt16 nNew ) { nSpaceIdx = nNew; } + void IncSpaceIdx() { ++nSpaceIdx; } + void RemoveFirstSpaceAdd() { pSpaceAdd->erase( pSpaceAdd->begin() ); } + long GetSpaceAdd() const + { return ( pSpaceAdd && nSpaceIdx < pSpaceAdd->size() ) + ? (*pSpaceAdd)[nSpaceIdx] : 0; } + + void SetpSpaceAdd( std::vector<long>* pNew ){ pSpaceAdd = pNew; } + std::vector<long>* GetpSpaceAdd() const { return pSpaceAdd; } + + void SetWrongList(sw::WrongListIterator *const pNew) { m_pWrongList = pNew; } + sw::WrongListIterator* GetpWrongList() const { return m_pWrongList; } + + void SetGrammarCheckList(sw::WrongListIterator *const pNew) { m_pGrammarCheckList = pNew; } + sw::WrongListIterator* GetGrammarCheckList() const { return m_pGrammarCheckList; } + + void SetSmartTags(sw::WrongListIterator *const pNew) { m_pSmartTags = pNew; } + sw::WrongListIterator* GetSmartTags() const { return m_pSmartTags; } +}; + +class SwTextFormatInfo : public SwTextPaintInfo +{ + // temporary arguments for hyphenation + css::beans::PropertyValues m_aHyphVals; + + SwLineLayout *m_pRoot; // The Root of the current line (pCurr) + SwLinePortion *m_pLast; // The last Portion + SwFlyPortion *m_pFly; // The following FlyPortion + SwLinePortion *m_pUnderflow; // Underflow: Last Portion + SwLinePortion *m_pRest; // The Rest is the start of the next Line + + SwTabPortion *m_pLastTab; // The _last_ TabPortion + + TextFrameIndex m_nSoftHyphPos; ///< SoftHyphPos for Hyphenation + TextFrameIndex m_nLineStart; ///< Current line start in rText + TextFrameIndex m_nUnderScorePos; ///< enlarge repaint if underscore has been found + TextFrameIndex m_nLastBookmarkPos; ///< need to check for bookmarks at every portion + // #i34348# Changed type from sal_uInt16 to SwTwips + SwTwips m_nLeft; // Left margin + SwTwips m_nRight; // Right margin + SwTwips m_nFirst; // EZE + /// First or left margin, depending on context. + SwTwips m_nLeftMargin = 0; + sal_uInt16 m_nRealWidth; // "real" line width + sal_uInt16 m_nWidth; // "virtual" line width + sal_uInt16 m_nLineHeight; // Final height after CalcLine + sal_uInt16 m_nLineNetHeight; // line height without spacing + sal_uInt16 m_nForcedLeftMargin; // Shift of left margin due to frame + + bool m_bFull : 1; // Line is full + bool m_bFootnoteDone : 1; // Footnote already formatted + bool m_bErgoDone : 1; // ErgoDone already formatted + bool m_bNumDone : 1; // bNumDone already formatted + bool m_bArrowDone : 1; // Arrow to the left for scrolling paragraphs + bool m_bStop : 1; // Cancel immediately, discarding the line + bool m_bNewLine : 1; // Format another line + bool m_bShift : 1; // Position change: Repaint until further notice + bool m_bUnderflow : 1; // Context: Underflow() ? + bool m_bInterHyph : 1; // Interactive hyphenation? + bool m_bAutoHyph : 1; // Automatic hyphenation? + bool m_bDropInit : 1; // Set DropWidth + bool m_bQuick : 1; // FormatQuick() + bool m_bNoEndHyph : 1; // Switch off hyphenation at the line end (due to MaxHyphens) + bool m_bNoMidHyph : 1; // Switch off hyphenation before flys (due to MaxHyphens) + bool m_bIgnoreFly : 1; // FitToContent ignores flys + bool m_bFakeLineStart : 1; // String has been replaced by field portion + // info structure only pretends that we are at + // the beginning of a line + bool m_bTabOverflow : 1; // Tabs are expanding after the end margin + bool m_bTestFormat : 1; // Test formatting from WouldFit, no notification etc. + + sal_Unicode m_cTabDecimal; // the current decimal delimiter + sal_Unicode m_cHookChar; // For tabs in fields etc. + sal_uInt8 m_nMaxHyph; // Max. line count of followup hyphenations + + // Hyphenating ... + bool InitHyph( const bool bAuto = false ); + bool CheckFootnotePortion_( SwLineLayout const * pCurr ); + +public: + void CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyph = false, + const bool bQuick = false, const bool bTst = false ); + SwTextFormatInfo(OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyphL = false, + const bool bQuickL = false, const bool bTst = false); + + // For the formatting inside a double line in a line (multi-line portion) + // we need a modified text-format-info: + SwTextFormatInfo( const SwTextFormatInfo& rInf, SwLineLayout& rLay, + SwTwips nActWidth ); + + sal_uInt16 Width() const { return m_nWidth; } + void Width( const sal_uInt16 nNew ) { m_nWidth = nNew; } + void Init(); + + /** + * Returns the distance between the current horizontal position and the end + * of the line. + */ + SwTwips GetLineWidth(); + + // Returns the first changed position of the paragraph + inline TextFrameIndex GetReformatStart() const; + + // Margins + SwTwips Left() const { return m_nLeft; } + void Left( const SwTwips nNew ) { m_nLeft = nNew; } + SwTwips Right() const { return m_nRight; } + void Right( const SwTwips nNew ) { m_nRight = nNew; } + SwTwips First() const { return m_nFirst; } + void First( const SwTwips nNew ) { m_nFirst = nNew; } + void LeftMargin( const SwTwips nNew) { m_nLeftMargin = nNew; } + sal_uInt16 RealWidth() const { return m_nRealWidth; } + void RealWidth( const sal_uInt16 nNew ) { m_nRealWidth = nNew; } + sal_uInt16 ForcedLeftMargin() const { return m_nForcedLeftMargin; } + void ForcedLeftMargin( const sal_uInt16 nN ) { m_nForcedLeftMargin = nN; } + + sal_uInt8 &MaxHyph() { return m_nMaxHyph; } + const sal_uInt8 &MaxHyph() const { return m_nMaxHyph; } + + SwLineLayout *GetRoot() { return m_pRoot; } + const SwLineLayout *GetRoot() const { return m_pRoot; } + + void SetRoot( SwLineLayout *pNew ) { m_pRoot = pNew; } + SwLinePortion *GetLast() { return m_pLast; } + void SetLast( SwLinePortion *pNewLast ) { m_pLast = pNewLast; } + bool IsFull() const { return m_bFull; } + void SetFull( const bool bNew ) { m_bFull = bNew; } + bool IsHyphForbud() const + { return m_pFly ? m_bNoMidHyph : m_bNoEndHyph; } + void ChkNoHyph( const sal_uInt8 bEnd, const sal_uInt8 bMid ) + { m_bNoEndHyph = (m_nMaxHyph && bEnd >= m_nMaxHyph); + m_bNoMidHyph = (m_nMaxHyph && bMid >= m_nMaxHyph); } + bool IsIgnoreFly() const { return m_bIgnoreFly; } + void SetIgnoreFly( const bool bNew ) { m_bIgnoreFly = bNew; } + bool IsFakeLineStart() const { return m_bFakeLineStart; } + void SetFakeLineStart( const bool bNew ) { m_bFakeLineStart = bNew; } + bool IsStop() const { return m_bStop; } + void SetStop( const bool bNew ) { m_bStop = bNew; } + SwLinePortion *GetRest() { return m_pRest; } + void SetRest( SwLinePortion *pNewRest ) { m_pRest = pNewRest; } + bool IsNewLine() const { return m_bNewLine; } + void SetNewLine( const bool bNew ) { m_bNewLine = bNew; } + bool IsShift() const { return m_bShift; } + void SetShift( const bool bNew ) { m_bShift = bNew; } + bool IsInterHyph() const { return m_bInterHyph; } + bool IsUnderflow() const { return m_bUnderflow; } + void ClrUnderflow() { m_bUnderflow = false; } + bool IsDropInit() const { return m_bDropInit; } + void SetDropInit( const bool bNew ) { m_bDropInit = bNew; } + bool IsQuick() const { return m_bQuick; } + bool IsTest() const { return m_bTestFormat; } + + TextFrameIndex GetLineStart() const { return m_nLineStart; } + void SetLineStart(TextFrameIndex const nNew) { m_nLineStart = nNew; } + + // these are used during fly calculation + sal_uInt16 GetLineHeight() const { return m_nLineHeight; } + void SetLineHeight( const sal_uInt16 nNew ) { m_nLineHeight = nNew; } + sal_uInt16 GetLineNetHeight() const { return m_nLineNetHeight; } + void SetLineNetHeight( const sal_uInt16 nNew ) { m_nLineNetHeight = nNew; } + + const SwLinePortion *GetUnderflow() const { return m_pUnderflow; } + SwLinePortion *GetUnderflow() { return m_pUnderflow; } + void SetUnderflow( SwLinePortion *pNew ) + { m_pUnderflow = pNew; m_bUnderflow = true; } + TextFrameIndex GetSoftHyphPos() const { return m_nSoftHyphPos; } + void SetSoftHyphPos(TextFrameIndex const nNew) { m_nSoftHyphPos = nNew; } + + inline void SetParaFootnote(); + + // FlyFrames + SwFlyPortion *GetFly() { return m_pFly; } + void SetFly( SwFlyPortion *pNew ) { m_pFly = pNew; } + + inline const SwAttrSet& GetCharAttr() const; + + // Tabs + SwTabPortion *GetLastTab() { return m_pLastTab; } + void SetLastTab( SwTabPortion *pNew ) { m_pLastTab = pNew; } + sal_Unicode GetTabDecimal() const { return m_cTabDecimal; } + void SetTabDecimal( const sal_Unicode cNew ) { m_cTabDecimal = cNew;} + + void ClearHookChar() { m_cHookChar = 0; } + void SetHookChar( const sal_Unicode cNew ) { m_cHookChar = cNew; } + sal_Unicode GetHookChar() const { return m_cHookChar; } + + // Done-Flags + bool IsFootnoteDone() const { return m_bFootnoteDone; } + void SetFootnoteDone( const bool bNew ) { m_bFootnoteDone = bNew; } + bool IsErgoDone() const { return m_bErgoDone; } + void SetErgoDone( const bool bNew ) { m_bErgoDone = bNew; } + bool IsNumDone() const { return m_bNumDone; } + void SetNumDone( const bool bNew ) { m_bNumDone = bNew; } + bool IsArrowDone() const { return m_bArrowDone; } + void SetArrowDone( const bool bNew ) { m_bArrowDone = bNew; } + + bool CheckCurrentPosBookmark(); + + // For SwTextPortion::Hyphenate + bool ChgHyph( const bool bNew ); + + // Should the hyphenate helper be discarded? + bool IsHyphenate() const; + TextFrameIndex GetUnderScorePos() const { return m_nUnderScorePos; } + void SetUnderScorePos(TextFrameIndex const nNew) { m_nUnderScorePos = nNew; } + + // Calls HyphenateWord() of Hyphenator + css::uno::Reference< css::linguistic2::XHyphenatedWord > + HyphWord( const OUString &rText, const sal_Int32 nMinTrail ); + const css::beans::PropertyValues & GetHyphValues() const; + + bool CheckFootnotePortion( SwLineLayout const * pCurr ) + { return IsFootnoteInside() && CheckFootnotePortion_( pCurr ); } + + // Dropcaps called by SwTextFormatter::CTOR + const SwFormatDrop *GetDropFormat() const; + + // Sets the last SwKernPortion as pLast, if it is followed by empty portions + bool LastKernPortion(); + + // Looks for tabs, TabDec, TXTATR and BRK from nIdx until nEnd. + // Return: Position; sets cHookChar if necessary + TextFrameIndex ScanPortionEnd(TextFrameIndex nStart, TextFrameIndex nEnd); + + void SetTabOverflow( bool bOverflow ) { m_bTabOverflow = bOverflow; } + bool IsTabOverflow() const { return m_bTabOverflow; } + +}; + +/** + * For the text replacement and restoration of SwTextSizeInfo. + * The way this is done is a bit of a hack: Although rInf is const we change it + * anyway. + * Because rInf is restored again in the DTOR, we can do this. + * You could call it a "logical const", if you wish. + */ +class SwTextSlot final +{ + OUString aText; + std::shared_ptr<vcl::TextLayoutCache> m_pOldCachedVclData; + const OUString *pOldText; + sw::WrongListIterator * m_pOldSmartTagList; + sw::WrongListIterator * m_pOldGrammarCheckList; + std::unique_ptr<SwWrongList> m_pTempList; + std::unique_ptr<sw::WrongListIterator> m_pTempIter; + TextFrameIndex nIdx; + TextFrameIndex nLen; + bool bOn; + SwTextSizeInfo *pInf; + +public: + // The replacement string originates either from the portion via GetExpText() + // or from the rCh, if it is not empty. + SwTextSlot( const SwTextSizeInfo *pNew, const SwLinePortion *pPor, bool bTextLen, + bool bExgLists, OUString const & rCh = OUString() ); + ~SwTextSlot(); +}; + +class SwFontSave +{ + SwTextSizeInfo *pInf; + SwFont *pFnt; + SwAttrIter *pIter; +public: + SwFontSave( const SwTextSizeInfo &rInf, SwFont *pFnt, + SwAttrIter* pItr = nullptr ); + ~SwFontSave(); +}; + +inline sal_uInt16 SwTextSizeInfo::GetAscent() const +{ + assert(GetOut()); + return const_cast<SwFont*>(GetFont())->GetAscent( m_pVsh, *GetOut() ); +} + +inline sal_uInt16 SwTextSizeInfo::GetTextHeight() const +{ + assert(GetOut()); + return const_cast<SwFont*>(GetFont())->GetHeight( m_pVsh, *GetOut() ); +} + +inline SwPosSize SwTextSizeInfo::GetTextSize( const OUString &rText ) const +{ + return GetTextSize(m_pOut, nullptr, rText, TextFrameIndex(0), TextFrameIndex(rText.getLength())); +} + +inline SwPosSize SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, + TextFrameIndex const nNewIdx, + TextFrameIndex const nNewLen) const +{ + return GetTextSize( m_pOut, pSI, *m_pText, nNewIdx, nNewLen ); +} + +inline SwTwips SwTextPaintInfo::GetPaintOfst() const +{ + return GetParaPortion()->GetRepaint().GetOffset(); +} + +inline void SwTextPaintInfo::SetPaintOfst( const SwTwips nNew ) +{ + GetParaPortion()->GetRepaint().SetOffset( nNew ); +} + +inline void SwTextPaintInfo::DrawText( const OUString &rText, + const SwLinePortion &rPor, + const TextFrameIndex nStart, const TextFrameIndex nLength, + const bool bKern ) const +{ + const_cast<SwTextPaintInfo*>(this)->DrawText_( rText, rPor, nStart, nLength, bKern ); +} + +inline void SwTextPaintInfo::DrawText( const SwLinePortion &rPor, + const TextFrameIndex nLength, const bool bKern ) const +{ + const_cast<SwTextPaintInfo*>(this)->DrawText_( *m_pText, rPor, m_nIdx, nLength, bKern ); +} + +inline void SwTextPaintInfo::DrawMarkedText( const SwLinePortion &rPor, + const TextFrameIndex nLength, + const bool bWrong, + const bool bSmartTags, + const bool bGrammarCheck ) const +{ + const_cast<SwTextPaintInfo*>(this)->DrawText_( *m_pText, rPor, m_nIdx, nLength, false/*bKern*/, bWrong, bSmartTags, bGrammarCheck ); +} + +inline TextFrameIndex SwTextFormatInfo::GetReformatStart() const +{ + return GetParaPortion()->GetReformat().Start(); +} + +inline const SwAttrSet& SwTextFormatInfo::GetCharAttr() const +{ + // sw_redlinehide: this is used for numbering/footnote number portions, so: + return GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet(); +} + +inline void SwTextFormatInfo::SetParaFootnote() +{ + GetTextFrame()->SetFootnote( true ); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx new file mode 100644 index 000000000..64a53c3c4 --- /dev/null +++ b/sw/source/core/text/itradj.cxx @@ -0,0 +1,841 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> + +#include <IDocumentSettingAccess.hxx> +#include <doc.hxx> + +#include "itrtxt.hxx" +#include "porglue.hxx" +#include "porlay.hxx" +#include "porfly.hxx" +#include "pormulti.hxx" +#include "portab.hxx" +#include <memory> + +#define MIN_TAB_WIDTH 60 + +using namespace ::com::sun::star; + +void SwTextAdjuster::FormatBlock( ) +{ + // Block format does not apply to the last line. + // And for tabs it doesn't exist out of tradition + // If we have Flys we continue. + + const SwLinePortion *pFly = nullptr; + + bool bSkip = !IsLastBlock() && + m_nStart + m_pCurr->GetLen() >= TextFrameIndex(GetInfo().GetText().getLength()); + + // Multi-line fields are tricky, because we need to check whether there are + // any other text portions in the paragraph. + if( bSkip ) + { + const SwLineLayout *pLay = m_pCurr->GetNext(); + while( pLay && !pLay->GetLen() ) + { + const SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + while( pPor && bSkip ) + { + if( pPor->InTextGrp() ) + bSkip = false; + pPor = pPor->GetNextPortion(); + } + pLay = bSkip ? pLay->GetNext() : nullptr; + } + } + + if( bSkip ) + { + if( !GetInfo().GetParaPortion()->HasFly() ) + { + if( IsLastCenter() ) + CalcFlyAdjust( m_pCurr ); + m_pCurr->FinishSpaceAdd(); + return; + } + else + { + const SwLinePortion *pTmpFly = nullptr; + + // End at the last Fly + const SwLinePortion *pPos = m_pCurr->GetFirstPortion(); + while( pPos ) + { + // Look for the last Fly which has text coming after it: + if( pPos->IsFlyPortion() ) + pTmpFly = pPos; // Found a Fly + else if ( pTmpFly && pPos->InTextGrp() ) + { + pFly = pTmpFly; // A Fly with follow-up text! + pTmpFly = nullptr; + } + pPos = pPos->GetNextPortion(); + } + // End if we didn't find one + if( !pFly ) + { + if( IsLastCenter() ) + CalcFlyAdjust( m_pCurr ); + m_pCurr->FinishSpaceAdd(); + return; + } + } + } + + const TextFrameIndex nOldIdx = GetInfo().GetIdx(); + GetInfo().SetIdx( m_nStart ); + CalcNewBlock( m_pCurr, pFly ); + GetInfo().SetIdx( nOldIdx ); + GetInfo().GetParaPortion()->GetRepaint().SetOffset(0); +} + +static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, + sal_Int32& rKashidas, TextFrameIndex& nGluePortion) +{ + // i60594 validate Kashida justification + TextFrameIndex nIdx = rItr.GetStart(); + TextFrameIndex nEnd = rItr.GetEnd(); + + // Note on calling KashidaJustify(): + // Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean + // total number of kashida positions, or the number of kashida positions after some positions + // have been dropped. + // Here we want the clean total, which is OK: We have called ClearKashidaInvalid() before. + rKashidas = rSI.KashidaJustify ( nullptr, nullptr, rItr.GetStart(), rItr.GetLength() ); + + if (rKashidas <= 0) // nothing to do + return true; + + // kashida positions found in SwScriptInfo are not necessarily valid in every font + // if two characters are replaced by a ligature glyph, there will be no place for a kashida + std::vector<TextFrameIndex> aKashidaPos; + rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aKashidaPos); + assert(aKashidaPos.size() >= o3tl::make_unsigned(rKashidas)); + std::vector<TextFrameIndex> aKashidaPosDropped(aKashidaPos.size()); + sal_Int32 nKashidaIdx = 0; + while ( rKashidas && nIdx < nEnd ) + { + rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() ); + TextFrameIndex nNext = rItr.GetNextAttr(); + + // is there also a script change before? + // if there is, nNext should point to the script change + TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx ); + if( nNextScript < nNext ) + nNext = nNextScript; + + if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd) + nNext = nEnd; + sal_Int32 nKashidasInAttr = rSI.KashidaJustify ( nullptr, nullptr, nIdx, nNext - nIdx ); + if (nKashidasInAttr > 0) + { + // Kashida glyph looks suspicious, skip Kashida justification + if ( rInf.GetOut()->GetMinKashida() <= 0 ) + { + return false; + } + + sal_Int32 nKashidasDropped = 0; + if ( !SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) ) + { + nKashidasDropped = nKashidasInAttr; + rKashidas -= nKashidasDropped; + } + else + { + ComplexTextLayoutFlags nOldLayout = rInf.GetOut()->GetLayoutMode(); + rInf.GetOut()->SetLayoutMode ( nOldLayout | ComplexTextLayoutFlags::BiDiRtl ); + nKashidasDropped = rInf.GetOut()->ValidateKashidas( + rInf.GetText(), sal_Int32(nIdx), sal_Int32(nNext - nIdx), + nKashidasInAttr, + reinterpret_cast<sal_Int32*>(aKashidaPos.data() + nKashidaIdx), + reinterpret_cast<sal_Int32*>(aKashidaPosDropped.data())); + rInf.GetOut()->SetLayoutMode ( nOldLayout ); + if ( nKashidasDropped ) + { + rSI.MarkKashidasInvalid(nKashidasDropped, aKashidaPosDropped.data()); + rKashidas -= nKashidasDropped; + nGluePortion -= TextFrameIndex(nKashidasDropped); + } + } + nKashidaIdx += nKashidasInAttr; + } + nIdx = nNext; + } + + // return false if all kashidas have been eliminated + return (rKashidas > 0); +} + +static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, sal_Int32& rKashidas, + TextFrameIndex& nGluePortion, const long nGluePortionWidth, long& nSpaceAdd ) +{ + // check kashida width + // if width is smaller than minimal kashida width allowed by fonts in the current line + // drop one kashida after the other until kashida width is OK + while (rKashidas) + { + bool bAddSpaceChanged = false; + TextFrameIndex nIdx = rItr.GetStart(); + TextFrameIndex nEnd = rItr.GetEnd(); + while ( nIdx < nEnd ) + { + rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() ); + TextFrameIndex nNext = rItr.GetNextAttr(); + + // is there also a script change before? + // if there is, nNext should point to the script change + TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx ); + if( nNextScript < nNext ) + nNext = nNextScript; + + if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd) + nNext = nEnd; + sal_Int32 nKashidasInAttr = rSI.KashidaJustify ( nullptr, nullptr, nIdx, nNext - nIdx ); + + long nFontMinKashida = rInf.GetOut()->GetMinKashida(); + if ( nFontMinKashida && nKashidasInAttr > 0 && SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) ) + { + sal_Int32 nKashidasDropped = 0; + while ( rKashidas && nGluePortion && nKashidasInAttr > 0 && + nSpaceAdd / SPACING_PRECISION_FACTOR < nFontMinKashida ) + { + --nGluePortion; + --rKashidas; + --nKashidasInAttr; + ++nKashidasDropped; + if( !rKashidas || !nGluePortion ) // nothing left, return false to + return false; // do regular blank justification + + nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); + bAddSpaceChanged = true; + } + if( nKashidasDropped ) + rSI.MarkKashidasInvalid( nKashidasDropped, nIdx, nNext - nIdx ); + } + if ( bAddSpaceChanged ) + break; // start all over again + nIdx = nNext; + } + if ( !bAddSpaceChanged ) + break; // everything was OK + } + return true; +} + +// CalcNewBlock() must only be called _after_ CalcLine()! +// We always span between two RandPortions or FixPortions (Tabs and Flys). +// We count the Glues and call ExpandBlock. +void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, + const SwLinePortion *pStopAt, SwTwips nReal, bool bSkipKashida ) +{ + OSL_ENSURE( GetInfo().IsMulti() || SvxAdjust::Block == GetAdjust(), + "CalcNewBlock: Why?" ); + OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" ); + + pCurrent->InitSpaceAdd(); + TextFrameIndex nGluePortion(0); + TextFrameIndex nCharCnt(0); + sal_uInt16 nSpaceIdx = 0; + + // i60591: hennerdrews + SwScriptInfo& rSI = GetInfo().GetParaPortion()->GetScriptInfo(); + SwTextSizeInfo aInf ( GetTextFrame() ); + SwTextIter aItr ( GetTextFrame(), &aInf ); + + if ( rSI.CountKashida() ) + { + while (aItr.GetCurr() != pCurrent && aItr.GetNext()) + aItr.Next(); + + if( bSkipKashida ) + { + rSI.SetNoKashidaLine ( aItr.GetStart(), aItr.GetLength()); + } + else + { + rSI.ClearKashidaInvalid ( aItr.GetStart(), aItr.GetLength() ); + rSI.ClearNoKashidaLine( aItr.GetStart(), aItr.GetLength() ); + } + } + + // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width! + if (!bSkipKashida) + CalcRightMargin( pCurrent, nReal ); + + // #i49277# + const bool bDoNotJustifyLinesWithManualBreak = + GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK); + bool bDoNotJustifyTab = false; + + SwLinePortion *pPos = pCurrent->GetNextPortion(); + + while( pPos ) + { + if ( ( bDoNotJustifyLinesWithManualBreak || bDoNotJustifyTab ) && + pPos->IsBreakPortion() && !IsLastBlock() ) + { + pCurrent->FinishSpaceAdd(); + break; + } + + switch ( pPos->GetWhichPor() ) + { + case PortionType::TabCenter : + case PortionType::TabRight : + case PortionType::TabDecimal : + bDoNotJustifyTab = true; + break; + case PortionType::TabLeft : + case PortionType::Break: + bDoNotJustifyTab = false; + break; + default: break; + } + + if ( pPos->InTextGrp() ) + nGluePortion = nGluePortion + static_cast<SwTextPortion*>(pPos)->GetSpaceCnt( GetInfo(), nCharCnt ); + else if( pPos->IsMultiPortion() ) + { + SwMultiPortion* pMulti = static_cast<SwMultiPortion*>(pPos); + // a multiportion with a tabulator inside breaks the text adjustment + // a ruby portion will not be stretched by text adjustment + // a double line portion takes additional space for each blank + // in the wider line + if( pMulti->HasTabulator() ) + { + if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() ) + pCurrent->SetLLSpaceAdd( 0, nSpaceIdx ); + + nSpaceIdx++; + nGluePortion = TextFrameIndex(0); + nCharCnt = TextFrameIndex(0); + } + else if( pMulti->IsDouble() ) + nGluePortion = nGluePortion + static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt(); + else if ( pMulti->IsBidi() ) + nGluePortion = nGluePortion + static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt( GetInfo() ); // i60594 + } + + if( pPos->InGlueGrp() ) + { + if( pPos->InFixMargGrp() ) + { + if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() ) + pCurrent->SetLLSpaceAdd( 0, nSpaceIdx ); + + const long nGluePortionWidth = static_cast<SwGluePortion*>(pPos)->GetPrtGlue() * + SPACING_PRECISION_FACTOR; + + sal_Int32 nKashidas = 0; + if( nGluePortion && rSI.CountKashida() && !bSkipKashida ) + { + // kashida positions found in SwScriptInfo are not necessarily valid in every font + // if two characters are replaced by a ligature glyph, there will be no place for a kashida + if ( !lcl_CheckKashidaPositions ( rSI, aInf, aItr, nKashidas, nGluePortion )) + { + // all kashida positions are invalid + // do regular blank justification + pCurrent->FinishSpaceAdd(); + GetInfo().SetIdx( m_nStart ); + CalcNewBlock( pCurrent, pStopAt, nReal, true ); + return; + } + } + + if( nGluePortion ) + { + long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); + + // i60594 + if( rSI.CountKashida() && !bSkipKashida ) + { + if( !lcl_CheckKashidaWidth( rSI, aInf, aItr, nKashidas, nGluePortion, nGluePortionWidth, nSpaceAdd )) + { + // no kashidas left + // do regular blank justification + pCurrent->FinishSpaceAdd(); + GetInfo().SetIdx( m_nStart ); + CalcNewBlock( pCurrent, pStopAt, nReal, true ); + return; + } + } + + pCurrent->SetLLSpaceAdd( nSpaceAdd , nSpaceIdx ); + pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() ); + } + else if (IsOneBlock() && nCharCnt > TextFrameIndex(1)) + { + const long nSpaceAdd = - nGluePortionWidth / (sal_Int32(nCharCnt) - 1); + pCurrent->SetLLSpaceAdd( nSpaceAdd, nSpaceIdx ); + pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() ); + } + + nSpaceIdx++; + nGluePortion = TextFrameIndex(0); + nCharCnt = TextFrameIndex(0); + } + else + ++nGluePortion; + } + GetInfo().SetIdx( GetInfo().GetIdx() + pPos->GetLen() ); + if ( pPos == pStopAt ) + { + pCurrent->SetLLSpaceAdd( 0, nSpaceIdx ); + break; + } + pPos = pPos->GetNextPortion(); + } +} + +SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent ) +{ + OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" ); + OSL_ENSURE( !pCurrent->GetpKanaComp(), "pKanaComp already exists!!" ); + + pCurrent->SetKanaComp( std::make_unique<std::deque<sal_uInt16>>() ); + + const sal_uInt16 nNull = 0; + size_t nKanaIdx = 0; + long nKanaDiffSum = 0; + SwTwips nRepaintOfst = 0; + SwTwips nX = 0; + bool bNoCompression = false; + + // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width! + CalcRightMargin( pCurrent ); + + SwLinePortion* pPos = pCurrent->GetNextPortion(); + + while( pPos ) + { + if ( pPos->InTextGrp() ) + { + // get maximum portion width from info structure, calculated + // during text formatting + sal_uInt16 nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos ); + + // check, if information is stored under other key + if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() ) + nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent ); + + // calculate difference between portion width and max. width + nKanaDiffSum += nMaxWidthDiff; + + // we store the beginning of the first compressible portion + // for repaint + if ( nMaxWidthDiff && !nRepaintOfst ) + nRepaintOfst = nX + GetLeftMargin(); + } + else if( pPos->InGlueGrp() && pPos->InFixMargGrp() ) + { + if ( nKanaIdx == pCurrent->GetKanaComp().size() ) + pCurrent->GetKanaComp().push_back( nNull ); + + long nRest; + + if ( pPos->InTabGrp() ) + { + nRest = ! bNoCompression && + ( pPos->Width() > MIN_TAB_WIDTH ) ? + pPos->Width() - MIN_TAB_WIDTH : + 0; + + // for simplifying the handling of left, right ... tabs, + // we do expand portions, which are lying behind + // those special tabs + bNoCompression = !pPos->IsTabLeftPortion(); + } + else + { + nRest = ! bNoCompression ? + static_cast<SwGluePortion*>(pPos)->GetPrtGlue() : + 0; + + bNoCompression = false; + } + + if( nKanaDiffSum ) + { + sal_uLong nCompress = ( 10000 * nRest ) / nKanaDiffSum; + + if ( nCompress >= 10000 ) + // kanas can be expanded to 100%, and there is still + // some space remaining + nCompress = 0; + + else + nCompress = 10000 - nCompress; + + ( pCurrent->GetKanaComp() )[ nKanaIdx ] = static_cast<sal_uInt16>(nCompress); + nKanaDiffSum = 0; + } + + nKanaIdx++; + } + + nX += pPos->Width(); + pPos = pPos->GetNextPortion(); + } + + // set portion width + nKanaIdx = 0; + sal_uInt16 nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ]; + pPos = pCurrent->GetNextPortion(); + long nDecompress = 0; + + while( pPos ) + { + if ( pPos->InTextGrp() ) + { + const sal_uInt16 nMinWidth = pPos->Width(); + + // get maximum portion width from info structure, calculated + // during text formatting + sal_uInt16 nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos ); + + // check, if information is stored under other key + if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() ) + nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent ); + pPos->Width( nMinWidth + + ( ( 10000 - nCompress ) * nMaxWidthDiff ) / 10000 ); + nDecompress += pPos->Width() - nMinWidth; + } + else if( pPos->InGlueGrp() && pPos->InFixMargGrp() ) + { + pPos->Width( static_cast<sal_uInt16>(pPos->Width() - nDecompress) ); + + if ( pPos->InTabGrp() ) + // set fix width to width + static_cast<SwTabPortion*>(pPos)->SetFixWidth( pPos->Width() ); + + if ( ++nKanaIdx < pCurrent->GetKanaComp().size() ) + nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ]; + + nDecompress = 0; + } + pPos = pPos->GetNextPortion(); + } + + return nRepaintOfst; +} + +SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent, + SwTwips nReal ) +{ + long nRealWidth; + const sal_uInt16 nRealHeight = GetLineHeight(); + const sal_uInt16 nLineHeight = pCurrent->Height(); + + sal_uInt16 nPrtWidth = pCurrent->PrtWidth(); + SwLinePortion *pLast = pCurrent->FindLastPortion(); + + if( GetInfo().IsMulti() ) + nRealWidth = nReal; + else + { + nRealWidth = GetLineWidth(); + // For each FlyFrame extending into the right margin, we create a FlyPortion. + const long nLeftMar = GetLeftMargin(); + SwRect aCurrRect( nLeftMar + nPrtWidth, Y() + nRealHeight - nLineHeight, + nRealWidth - nPrtWidth, nLineHeight ); + + SwFlyPortion *pFly = CalcFlyPortion( nRealWidth, aCurrRect ); + while( pFly && long( nPrtWidth )< nRealWidth ) + { + pLast->Append( pFly ); + pLast = pFly; + if( pFly->GetFix() > nPrtWidth ) + pFly->Width( ( pFly->GetFix() - nPrtWidth) + pFly->Width() + 1); + nPrtWidth += pFly->Width() + 1; + aCurrRect.Left( nLeftMar + nPrtWidth ); + pFly = CalcFlyPortion( nRealWidth, aCurrRect ); + } + delete pFly; + } + + SwMarginPortion *pRight = new SwMarginPortion; + pLast->Append( pRight ); + + if( long( nPrtWidth )< nRealWidth ) + pRight->PrtWidth( sal_uInt16( nRealWidth - nPrtWidth ) ); + + // pCurrent->Width() is set to the real size, because we attach the + // MarginPortions. + // This trick gives miraculous results: + // If pCurrent->Width() == nRealWidth, then the adjustment gets overruled + // implicitly. GetLeftMarginAdjust() and IsJustified() think they have a + // line filled with chars. + + pCurrent->PrtWidth( sal_uInt16( nRealWidth ) ); + return pRight; +} + +void SwTextAdjuster::CalcFlyAdjust( SwLineLayout *pCurrent ) +{ + // 1) We insert a left margin: + SwMarginPortion *pLeft = pCurrent->CalcLeftMargin(); + SwGluePortion *pGlue = pLeft; // the last GluePortion + + // 2) We attach a right margin: + // CalcRightMargin also calculates a possible overlap with FlyFrames. + CalcRightMargin( pCurrent ); + + SwLinePortion *pPos = pLeft->GetNextPortion(); + TextFrameIndex nLen(0); + + // If we only have one line, the text portion is consecutive and we center, then ... + bool bComplete = TextFrameIndex(0) == m_nStart; + const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT); + bool bMultiTab = false; + + while( pPos ) + { + if ( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasTabulator() ) + bMultiTab = true; + else if( pPos->InFixMargGrp() && + ( bTabCompat ? ! pPos->InTabGrp() : ! bMultiTab ) ) + { + // in tab compat mode we do not want to change tab portions + // in non tab compat mode we do not want to change margins if we + // found a multi portion with tabs + if( SvxAdjust::Right == GetAdjust() ) + static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue ); + else + { + // We set the first text portion to right-aligned and the last one + // to left-aligned. + // The first text portion gets the whole Glue, but only if we have + // more than one line. + if (bComplete && TextFrameIndex(GetInfo().GetText().getLength()) == nLen) + static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue ); + else + { + if ( ! bTabCompat ) + { + if( pLeft == pGlue ) + { + // If we only have a left and right margin, the + // margins share the Glue. + if( nLen + pPos->GetLen() >= pCurrent->GetLen() ) + static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue ); + else + static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue ); + } + else + { + // The last text portion retains its Glue. + if( !pPos->IsMarginPortion() ) + static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue ); + } + } + else + static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue ); + } + } + + pGlue = static_cast<SwGluePortion*>(pPos); + bComplete = false; + } + nLen = nLen + pPos->GetLen(); + pPos = pPos->GetNextPortion(); + } + + if( ! bTabCompat && ! bMultiTab && SvxAdjust::Right == GetAdjust() ) + // portions are moved to the right if possible + pLeft->AdjustRight( pCurrent ); +} + +void SwTextAdjuster::CalcAdjLine( SwLineLayout *pCurrent ) +{ + OSL_ENSURE( pCurrent->IsFormatAdj(), "CalcAdjLine: Why?" ); + + pCurrent->SetFormatAdj(false); + + SwParaPortion* pPara = GetInfo().GetParaPortion(); + + switch( GetAdjust() ) + { + case SvxAdjust::Right: + case SvxAdjust::Center: + { + CalcFlyAdjust( pCurrent ); + pPara->GetRepaint().SetOffset( 0 ); + break; + } + case SvxAdjust::Block: + { + FormatBlock(); + break; + } + default : return; + } +} + +// This is a quite complicated calculation: nCurrWidth is the width _before_ +// adding the word, that still fits onto the line! For this reason the FlyPortion's +// width is still correct if we get a deadlock-situation of: +// bFirstWord && !WORDFITS +SwFlyPortion *SwTextAdjuster::CalcFlyPortion( const long nRealWidth, + const SwRect &rCurrRect ) +{ + SwTextFly aTextFly( GetTextFrame() ); + + const sal_uInt16 nCurrWidth = m_pCurr->PrtWidth(); + SwFlyPortion *pFlyPortion = nullptr; + + SwRect aLineVert( rCurrRect ); + if ( GetTextFrame()->IsRightToLeft() ) + GetTextFrame()->SwitchLTRtoRTL( aLineVert ); + if ( GetTextFrame()->IsVertical() ) + GetTextFrame()->SwitchHorizontalToVertical( aLineVert ); + + // aFlyRect is document-global! + SwRect aFlyRect( aTextFly.GetFrame( aLineVert ) ); + + if ( GetTextFrame()->IsRightToLeft() ) + GetTextFrame()->SwitchRTLtoLTR( aFlyRect ); + if ( GetTextFrame()->IsVertical() ) + GetTextFrame()->SwitchVerticalToHorizontal( aFlyRect ); + + // If a Frame overlapps we open a Portion + if( aFlyRect.HasArea() ) + { + // aLocal is frame-local + SwRect aLocal( aFlyRect ); + aLocal.Pos( aLocal.Left() - GetLeftMargin(), aLocal.Top() ); + if( nCurrWidth > aLocal.Left() ) + aLocal.Left( nCurrWidth ); + + // If the rect is wider than the line, we adjust it to the right size + const long nLocalWidth = aLocal.Left() + aLocal.Width(); + if( nRealWidth < nLocalWidth ) + aLocal.Width( nRealWidth - aLocal.Left() ); + GetInfo().GetParaPortion()->SetFly(); + pFlyPortion = new SwFlyPortion( aLocal ); + pFlyPortion->Height( sal_uInt16( rCurrRect.Height() ) ); + // The Width could be smaller than the FixWidth, thus: + pFlyPortion->AdjFixWidth(); + } + return pFlyPortion; +} + +// CalcDropAdjust is called at the end by Format() if needed +void SwTextAdjuster::CalcDropAdjust() +{ + OSL_ENSURE( 1<GetDropLines() && SvxAdjust::Left!=GetAdjust() && SvxAdjust::Block!=GetAdjust(), + "CalcDropAdjust: No reason for DropAdjustment." ); + + const sal_uInt16 nLineNumber = GetLineNr(); + + // 1) Skip dummies + Top(); + + if( !m_pCurr->IsDummy() || NextLine() ) + { + // Adjust first + GetAdjusted(); + + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + + // 2) Make sure we include the ropPortion + // 3) pLeft is the GluePor preceding the DropPor + if( pPor->InGlueGrp() && pPor->GetNextPortion() + && pPor->GetNextPortion()->IsDropPortion() ) + { + const SwLinePortion *pDropPor = pPor->GetNextPortion(); + SwGluePortion *pLeft = static_cast<SwGluePortion*>( pPor ); + + // 4) pRight: Find the GluePor coming after the DropPor + pPor = pPor->GetNextPortion(); + while( pPor && !pPor->InFixMargGrp() ) + pPor = pPor->GetNextPortion(); + + SwGluePortion *pRight = ( pPor && pPor->InGlueGrp() ) ? + static_cast<SwGluePortion*>(pPor) : nullptr; + if( pRight && pRight != pLeft ) + { + // 5) Calculate nMinLeft. Who is the most to left? + const auto nDropLineStart = + GetLineStart() + pLeft->Width() + pDropPor->Width(); + auto nMinLeft = nDropLineStart; + for( sal_uInt16 i = 1; i < GetDropLines(); ++i ) + { + if( NextLine() ) + { + // Adjust first + GetAdjusted(); + + pPor = m_pCurr->GetFirstPortion(); + const SwMarginPortion *pMar = pPor->IsMarginPortion() ? + static_cast<SwMarginPortion*>(pPor) : nullptr; + if( !pMar ) + nMinLeft = 0; + else + { + const auto nLineStart = + GetLineStart() + pMar->Width(); + if( nMinLeft > nLineStart ) + nMinLeft = nLineStart; + } + } + } + + // 6) Distribute the Glue anew between pLeft and pRight + if( nMinLeft < nDropLineStart ) + { + // The Glue is always passed from pLeft to pRight, so that + // the text moves to the left. + const auto nGlue = nDropLineStart - nMinLeft; + if( !nMinLeft ) + pLeft->MoveAllGlue( pRight ); + else + pLeft->MoveGlue( pRight, nGlue ); + } + } + } + } + + if( nLineNumber != GetLineNr() ) + { + Top(); + while( nLineNumber != GetLineNr() && Next() ) + ; + } +} + +void SwTextAdjuster::CalcDropRepaint() +{ + Top(); + SwRepaint &rRepaint = GetInfo().GetParaPortion()->GetRepaint(); + if( rRepaint.Top() > Y() ) + rRepaint.Top( Y() ); + for( sal_uInt16 i = 1; i < GetDropLines(); ++i ) + NextLine(); + const SwTwips nBottom = Y() + GetLineHeight() - 1; + if( rRepaint.Bottom() < nBottom ) + rRepaint.Bottom( nBottom ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx new file mode 100644 index 000000000..1fadbf708 --- /dev/null +++ b/sw/source/core/text/itratr.cxx @@ -0,0 +1,1481 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include <hintids.hxx> +#include <editeng/charscaleitem.hxx> +#include <svl/itemiter.hxx> +#include <svx/svdobj.hxx> +#include <vcl/svapp.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtflcnt.hxx> +#include <fmtcntnt.hxx> +#include <fmtftn.hxx> +#include <frmatr.hxx> +#include <frmfmt.hxx> +#include <fmtfld.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <txatbase.hxx> +#include <viewsh.hxx> +#include <rootfrm.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <fldbas.hxx> +#include <pam.hxx> +#include "itratr.hxx" +#include <htmltbl.hxx> +#include <swtable.hxx> +#include "redlnitr.hxx" +#include <redline.hxx> +#include <fmtsrnd.hxx> +#include "itrtxt.hxx" +#include <breakit.hxx> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <editeng/lrspitem.hxx> +#include <calbck.hxx> +#include <frameformats.hxx> + +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star; + +static sal_Int32 GetNextAttrImpl(SwTextNode const* pTextNode, + size_t nStartIndex, size_t nEndIndex, sal_Int32 nPosition); + +SwAttrIter::SwAttrIter(SwTextNode const * pTextNode) + : m_pViewShell(nullptr) + , m_pFont(nullptr) + , m_pScriptInfo(nullptr) + , m_pLastOut(nullptr) + , m_nChgCnt(0) + , m_nStartIndex(0) + , m_nEndIndex(0) + , m_nPosition(0) + , m_nPropFont(0) + , m_pTextNode(pTextNode) + , m_pMergedPara(nullptr) +{ + m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr; +} + +SwAttrIter::SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame) + : m_pViewShell(nullptr) + , m_pFont(nullptr) + , m_pScriptInfo(nullptr) + , m_pLastOut(nullptr) + , m_nChgCnt(0) + , m_nPropFont(0) + , m_pTextNode(&rTextNode) + , m_pMergedPara(nullptr) +{ + CtorInitAttrIter(rTextNode, rScrInf, pFrame); +} + +void SwAttrIter::Chg( SwTextAttr const *pHt ) +{ + assert(pHt && m_pFont && "No attribute of font available for change"); + if( m_pRedline && m_pRedline->IsOn() ) + m_pRedline->ChangeTextAttr( m_pFont, *pHt, true ); + else + m_aAttrHandler.PushAndChg( *pHt, *m_pFont ); + m_nChgCnt++; +} + +void SwAttrIter::Rst( SwTextAttr const *pHt ) +{ + assert(pHt && m_pFont && "No attribute of font available for reset"); + // get top from stack after removing pHt + if( m_pRedline && m_pRedline->IsOn() ) + m_pRedline->ChangeTextAttr( m_pFont, *pHt, false ); + else + m_aAttrHandler.PopAndChg( *pHt, *m_pFont ); + m_nChgCnt--; +} + +SwAttrIter::~SwAttrIter() +{ + m_pRedline.reset(); + delete m_pFont; +} + +bool SwAttrIter::MaybeHasHints() const +{ + return nullptr != m_pTextNode->GetpSwpHints() || nullptr != m_pMergedPara; +} + +/** + * Returns the attribute for a position + * + * Only if the attribute is exactly at the position @param nPos and + * does not have an EndIndex + * + * We need this function for attributes which should alter formatting without + * changing the content of the string. + * Such "degenerated" attributes are e.g.: fields which retain expanded text and + * line-bound Frames. + * In order to avoid ambiguities between different such attributes, we insert a + * special character at the start of the string, when creating such an attribute. + * The Formatter later on encounters such a special character and retrieves the + * degenerate attribute via GetAttr(). + */ +SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const nPosition) const +{ + std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara + ? sw::MapViewToModel(*m_pMergedPara, nPosition) + : std::make_pair(m_pTextNode, sal_Int32(nPosition))); + return pos.first->GetTextAttrForCharAt(pos.second); +} + +bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, OutputDevice* pOut) +{ + std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara + ? sw::MapViewToModel(*m_pMergedPara, nNewPos) + : std::make_pair(m_pTextNode, sal_Int32(nNewPos))); + bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == m_nPosition + ? m_pFont->IsFntChg() + : Seek( nNewPos ); + if ( m_pLastOut.get() != pOut ) + { + m_pLastOut = pOut; + m_pFont->SetFntChg( true ); + bChg = true; + } + if( bChg ) + { + // if the change counter is zero, we know the cache id of the wanted font + if ( !m_nChgCnt && !m_nPropFont ) + m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ], + m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() ); + m_pFont->ChgPhysFnt( m_pViewShell, *pOut ); + } + + return bChg; +} + +bool SwAttrIter::IsSymbol(TextFrameIndex const nNewPos) +{ + Seek( nNewPos ); + if ( !m_nChgCnt && !m_nPropFont ) + m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ], + m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() ); + return m_pFont->IsSymbol( m_pViewShell ); +} + +bool SwTextFrame::IsSymbolAt(TextFrameIndex const nPos) const +{ + SwTextInfo info(const_cast<SwTextFrame*>(this)); + SwTextIter iter(const_cast<SwTextFrame*>(this), &info); + return iter.IsSymbol(nPos); +} + +bool SwAttrIter::SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont ) +{ + SwTextNode const*const pFirstTextNode(m_pMergedPara ? m_pMergedPara->pFirstNode : m_pTextNode); + if ( m_pRedline && m_pRedline->ExtOn() ) + m_pRedline->LeaveExtend(*m_pFont, pFirstTextNode->GetIndex(), 0); + + if (m_pTextNode != pFirstTextNode) + { + assert(m_pMergedPara); + m_pTextNode = m_pMergedPara->pFirstNode; + InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode, + m_pMergedPara->mergedText, nullptr, nullptr); + } + + // reset font to its original state + m_aAttrHandler.Reset(); + m_aAttrHandler.ResetFont( *m_pFont ); + + m_nStartIndex = 0; + m_nEndIndex = 0; + m_nPosition = 0; + m_nChgCnt = 0; + if( m_nPropFont ) + m_pFont->SetProportion( m_nPropFont ); + if( m_pRedline ) + { + m_pRedline->Clear( m_pFont ); + if( !bParaFont ) + m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, pFirstTextNode->GetIndex(), 0, COMPLETE_STRING); + else + m_pRedline->Reset(); + } + + SwpHints const*const pHints(m_pTextNode->GetpSwpHints()); + if (pHints && !bParaFont) + { + SwTextAttr *pTextAttr; + // While we've not reached the end of the StartArray && the TextAttribute starts at position 0... + while ((m_nStartIndex < pHints->Count()) && + !((pTextAttr = pHints->Get(m_nStartIndex))->GetStart())) + { + // open the TextAttributes + Chg( pTextAttr ); + m_nStartIndex++; + } + } + + bool bChg = m_pFont->IsFntChg(); + if ( m_pLastOut.get() != pOut ) + { + m_pLastOut = pOut; + m_pFont->SetFntChg( true ); + bChg = true; + } + if( bChg ) + { + // if the application counter is zero, we know the cache id of the wanted font + if ( !m_nChgCnt && !m_nPropFont ) + m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ], + m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() ); + m_pFont->ChgPhysFnt( m_pViewShell, *pOut ); + } + return bChg; +} + +// AMA: New AttrIter Nov 94 +void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos) +{ + SwpHints const*const pHints(m_pTextNode->GetpSwpHints()); + SwTextAttr *pTextAttr; + + if ( m_nStartIndex ) // If attributes have been opened at all ... + { + // Close attributes that are currently open, but stop at nNewPos+1 + + // As long as we've not yet reached the end of EndArray and the + // TextAttribute ends before or at the new position ... + while ((m_nEndIndex < pHints->Count()) && + ((pTextAttr = pHints->GetSortedByEnd(m_nEndIndex))->GetAnyEnd() <= nNewPos)) + { + // Close the TextAttributes, whose StartPos were before or at + // the old nPos and are currently open + if (pTextAttr->GetStart() <= nOldPos) Rst( pTextAttr ); + m_nEndIndex++; + } + } + else // skip the not opened ends + { + while ((m_nEndIndex < pHints->Count()) && + (pHints->GetSortedByEnd(m_nEndIndex)->GetAnyEnd() <= nNewPos)) + { + m_nEndIndex++; + } + } + + // As long as we've not yet reached the end of EndArray and the + // TextAttribute ends before or at the new position... + while ((m_nStartIndex < pHints->Count()) && + ((pTextAttr = pHints->Get(m_nStartIndex))->GetStart() <= nNewPos)) + { + + // open the TextAttributes, whose ends lie behind the new position + if ( pTextAttr->GetAnyEnd() > nNewPos ) Chg( pTextAttr ); + m_nStartIndex++; + } + +} + +bool SwAttrIter::Seek(TextFrameIndex const nNewPos) +{ + // note: nNewPos isn't necessarily an index returned from GetNextAttr + std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara + ? sw::MapViewToModel(*m_pMergedPara, nNewPos) + : std::make_pair(m_pTextNode, sal_Int32(nNewPos))); + + if ( m_pRedline && m_pRedline->ExtOn() ) + m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), newPos.second); + if (m_pTextNode->GetIndex() < newPos.first->GetIndex()) + { + // Skipping to a different node - first seek until the end of this node + // to get rid of all hint items + if (m_pTextNode->GetpSwpHints()) + { + sal_Int32 nPos(m_nPosition); + do + { + sal_Int32 const nOldPos(nPos); + nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos); + if (nPos <= m_pTextNode->Len()) + { + SeekFwd(nOldPos, nPos); + } + else + { + SeekFwd(nOldPos, m_pTextNode->Len()); + } + } + while (nPos < m_pTextNode->Len()); + } + assert(m_nChgCnt == 0); // should have reset it all? there cannot be ExtOn() inside of a Delete redline, surely? + // Unapply current para items: + // the SwAttrHandler doesn't appear to be capable of *unapplying* + // items at all; it can only apply a previously effective item. + // So do this by recreating the font from scratch. + // Apply new para items: + assert(m_pMergedPara); + InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *newPos.first, + m_pMergedPara->mergedText, nullptr, nullptr); + // reset to next + m_pTextNode = newPos.first; + m_nStartIndex = 0; + m_nEndIndex = 0; + m_nPosition = 0; + assert(m_pRedline); + } + + // sw_redlinehide: Seek(0) must move before the first character, which + // has a special case where the first node starts with delete redline. + if ((!nNewPos && !m_pMergedPara) + || newPos.first != m_pTextNode + || newPos.second < m_nPosition) + { + if (m_pMergedPara) + { + if (m_pTextNode != newPos.first) + { + m_pTextNode = newPos.first; + // sw_redlinehide: hope it's okay to use the current text node + // here; the AttrHandler shouldn't care about non-char items + InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode, + m_pMergedPara->mergedText, nullptr, nullptr); + } + } + if (m_pMergedPara || m_pTextNode->GetpSwpHints()) + { + if( m_pRedline ) + m_pRedline->Clear( nullptr ); + + // reset font to its original state + m_aAttrHandler.Reset(); + m_aAttrHandler.ResetFont( *m_pFont ); + + if( m_nPropFont ) + m_pFont->SetProportion( m_nPropFont ); + m_nStartIndex = 0; + m_nEndIndex = 0; + m_nPosition = 0; + m_nChgCnt = 0; + + // Attention! + // resetting the font here makes it necessary to apply any + // changes for extended input directly to the font + if ( m_pRedline && m_pRedline->ExtOn() ) + { + m_pRedline->UpdateExtFont( *m_pFont ); + ++m_nChgCnt; + } + } + } + + if (m_pTextNode->GetpSwpHints()) + { + if (m_pMergedPara) + { + // iterate hint by hint: SeekFwd does not mix ends and starts, + // it always applies all the starts last, so it must be called once + // per position where hints start/end! + sal_Int32 nPos(m_nPosition); + do + { + sal_Int32 const nOldPos(nPos); + nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos); + if (nPos <= newPos.second) + { + SeekFwd(nOldPos, nPos); + } + else + { + SeekFwd(nOldPos, newPos.second); + } + } + while (nPos < newPos.second); + } + else + { + SeekFwd(m_nPosition, newPos.second); + } + } + + m_pFont->SetActual( m_pScriptInfo->WhichFont(nNewPos) ); + + if( m_pRedline ) + m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, m_pTextNode->GetIndex(), newPos.second, m_nPosition); + m_nPosition = newPos.second; + + if( m_nPropFont ) + m_pFont->SetProportion( m_nPropFont ); + + return m_pFont->IsFntChg(); +} + +static void InsertCharAttrs(SfxPoolItem const** pAttrs, SfxItemSet const& rItems) +{ + SfxItemIter iter(rItems); + for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem()) + { + auto const nWhich(pItem->Which()); + if (isCHRATR(nWhich) && RES_CHRATR_RSID != nWhich) + { + pAttrs[nWhich - RES_CHRATR_BEGIN] = pItem; + } + else if (nWhich == RES_TXTATR_UNKNOWN_CONTAINER) + { + pAttrs[RES_CHRATR_END] = pItem; + } + } +} + +// if return false: portion ends at start of redline, indexes unchanged +// if return true: portion end not known (past end of redline), indexes point to first hint past end of redline +static bool CanSkipOverRedline( + SwTextNode const& rStartNode, sal_Int32 const nStartRedline, + SwRangeRedline const& rRedline, + size_t & rStartIndex, size_t & rEndIndex, + bool const isTheAnswerYes) +{ + size_t nStartIndex(rStartIndex); + size_t nEndIndex(rEndIndex); + SwPosition const*const pRLEnd(rRedline.End()); + if (!pRLEnd->nNode.GetNode().IsTextNode() // if fully deleted... + || pRLEnd->nContent == pRLEnd->nNode.GetNode().GetTextNode()->Len()) + { + // shortcut: nothing follows redline + // current state is end state + return false; + } + std::vector<SwTextAttr*> activeCharFmts; + // can't compare the SwFont that's stored somewhere, it doesn't have compare + // operator, so try to recreate the situation with some temp arrays here + SfxPoolItem const* activeCharAttrsStart[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, }; + if (&rStartNode != &pRLEnd->nNode.GetNode()) + { // nodes' attributes are only needed if there are different nodes + InsertCharAttrs(activeCharAttrsStart, rStartNode.GetSwAttrSet()); + } + if (SwpHints const*const pStartHints = rStartNode.GetpSwpHints()) + { + // check hint ends of hints that start before and end within + sal_Int32 const nRedlineEnd(&rStartNode == &pRLEnd->nNode.GetNode() + ? pRLEnd->nContent.GetIndex() + : rStartNode.Len()); + for ( ; nEndIndex < pStartHints->Count(); ++nEndIndex) + { + SwTextAttr *const pAttr(pStartHints->GetSortedByEnd(nEndIndex)); + if (!pAttr->End()) + { + continue; + } + if (nRedlineEnd < *pAttr->End()) + { + break; + } + if (nStartRedline <= pAttr->GetStart()) + { + continue; + } + if (pAttr->IsFormatIgnoreEnd()) + { + continue; + } + switch (pAttr->Which()) + { + // if any of these ends inside RL then we need a new portion + case RES_TXTATR_REFMARK: + case RES_TXTATR_TOXMARK: + case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ??? + case RES_TXTATR_METAFIELD: + case RES_TXTATR_INETFMT: + case RES_TXTATR_CJK_RUBY: + case RES_TXTATR_INPUTFIELD: + { + if (!isTheAnswerYes) return false; // always break + } + break; + // these are guaranteed not to overlap + // and come in order of application + case RES_TXTATR_AUTOFMT: + case RES_TXTATR_CHARFMT: + { + if (pAttr->Which() == RES_TXTATR_CHARFMT) + { + activeCharFmts.push_back(pAttr); + } + // pure formatting hints may end inside the redline & + // start again inside the redline, which must not cause + // a new text portion if they have the same items - so + // store the effective items & compare all at the end + SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT) + ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet()) + : *pAttr->GetAutoFormat().GetStyleHandle()); + InsertCharAttrs(activeCharAttrsStart, rSet); + } + break; + // SwTextNode::SetAttr puts it into AUTOFMT which is quite + // sensible so it doesn't actually exist as a hint + case RES_TXTATR_UNKNOWN_CONTAINER: + default: assert(false); + } + } + assert(nEndIndex == pStartHints->Count() || + pRLEnd->nContent.GetIndex() < pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd()); + } + + if (&rStartNode != &pRLEnd->nNode.GetNode()) + { + nStartIndex = 0; + nEndIndex = 0; + } + + // treat para properties as text properties + // ... with the FormatToTextAttr we get autofmts that correspond to the *effective* attr set difference + // effective attr set: para props + charfmts + autofmt *in that order* + // ... and the charfmt must be *nominally* the same + + SfxPoolItem const* activeCharAttrsEnd[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, }; + if (&rStartNode != &pRLEnd->nNode.GetNode()) + { // nodes' attributes are only needed if there are different nodes + InsertCharAttrs(activeCharAttrsEnd, + pRLEnd->nNode.GetNode().GetTextNode()->GetSwAttrSet()); + } + + if (SwpHints *const pEndHints = pRLEnd->nNode.GetNode().GetTextNode()->GetpSwpHints()) + { + // check hint starts of hints that start within and end after +#ifndef NDEBUG + sal_Int32 const nRedlineStart(&rStartNode == &pRLEnd->nNode.GetNode() + ? nStartRedline + : 0); +#endif + for ( ; nStartIndex < pEndHints->Count(); ++nStartIndex) + { + SwTextAttr *const pAttr(pEndHints->Get(nStartIndex)); + // compare with < here, not <=, to get the effective formatting + // of the 1st char after the redline; should not cause problems + // with consecutive delete redlines because those are handed by + // GetNextRedln() and here we have the last end pos. + if (pRLEnd->nContent.GetIndex() < pAttr->GetStart()) + { + break; + } + if (!pAttr->End()) + continue; + if (pAttr->IsFormatIgnoreStart()) + { + continue; + } + assert(nRedlineStart <= pAttr->GetStart()); // we wouldn't be here otherwise? + if (*pAttr->End() <= pRLEnd->nContent.GetIndex()) + { + continue; + } + switch (pAttr->Which()) + { + case RES_TXTATR_REFMARK: + case RES_TXTATR_TOXMARK: + case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ??? + case RES_TXTATR_METAFIELD: + case RES_TXTATR_INETFMT: + case RES_TXTATR_CJK_RUBY: + case RES_TXTATR_INPUTFIELD: + { + if (!isTheAnswerYes) return false; + } + break; + case RES_TXTATR_AUTOFMT: + case RES_TXTATR_CHARFMT: + { + // char formats must be *nominally* the same + if (pAttr->Which() == RES_TXTATR_CHARFMT) + { + auto iter = std::find_if(activeCharFmts.begin(), activeCharFmts.end(), + [&pAttr](const SwTextAttr* pCharFmt) { return *pCharFmt == *pAttr; }); + if (iter != activeCharFmts.end()) + activeCharFmts.erase(iter); + else if (!isTheAnswerYes) + return false; + } + SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT) + ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet()) + : *pAttr->GetAutoFormat().GetStyleHandle()); + InsertCharAttrs(activeCharAttrsEnd, rSet); + + } + break; + // SwTextNode::SetAttr puts it into AUTOFMT which is quite + // sensible so it doesn't actually exist as a hint + case RES_TXTATR_UNKNOWN_CONTAINER: + default: assert(false); + } + } + if (&rStartNode != &pRLEnd->nNode.GetNode()) + { + // need to iterate the nEndIndex forward too so the loop in the + // caller can look for the right ends in the next iteration + for (nEndIndex = 0; nEndIndex < pEndHints->Count(); ++nEndIndex) + { + SwTextAttr *const pAttr(pEndHints->GetSortedByEnd(nEndIndex)); + if (!pAttr->End()) + continue; + if (pRLEnd->nContent.GetIndex() < *pAttr->End()) + { + break; + } + } + } + } + + // if we didn't find a matching start for any end, then it really ends inside + if (!activeCharFmts.empty()) + { + if (!isTheAnswerYes) return false; + } + for (size_t i = 0; i < SAL_N_ELEMENTS(activeCharAttrsStart); ++i) + { + // all of these are poolable +// assert(!activeCharAttrsStart[i] || activeCharAttrsStart[i]->GetItemPool()->IsItemPoolable(*activeCharAttrsStart[i])); + if (activeCharAttrsStart[i] != activeCharAttrsEnd[i]) + { + if (!isTheAnswerYes) return false; + } + } + rStartIndex = nStartIndex; + rEndIndex = nEndIndex; + return true; +} + +static sal_Int32 GetNextAttrImpl(SwTextNode const*const pTextNode, + size_t const nStartIndex, size_t const nEndIndex, + sal_Int32 const nPosition) +{ + // note: this used to be COMPLETE_STRING, but was set to Len() + 1 below, + // which is rather silly, so set it to Len() instead + sal_Int32 nNext = pTextNode->Len(); + if (SwpHints const*const pHints = pTextNode->GetpSwpHints()) + { + // are there attribute starts left? + for (size_t i = nStartIndex; i < pHints->Count(); ++i) + { + SwTextAttr *const pAttr(pHints->Get(i)); + if (!pAttr->IsFormatIgnoreStart()) + { + nNext = pAttr->GetStart(); + break; + } + } + // are there attribute ends left? + for (size_t i = nEndIndex; i < pHints->Count(); ++i) + { + SwTextAttr *const pAttr(pHints->GetSortedByEnd(i)); + if (!pAttr->IsFormatIgnoreEnd()) + { + sal_Int32 const nNextEnd = pAttr->GetAnyEnd(); + nNext = std::min(nNext, nNextEnd); // pick nearest one + break; + } + } + } + // TODO: maybe use hints like FieldHints for this instead of looking at the text... + const sal_Int32 l = std::min(nNext, pTextNode->Len()); + sal_Int32 p = nPosition; + const sal_Unicode* pStr = pTextNode->GetText().getStr(); + while (p < l) + { + sal_Unicode aChar = pStr[p]; + switch (aChar) + { + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + goto break_; // sigh... + default: + ++p; + } + } +break_: + assert(p <= nNext); + if (p < l) + { + // found a CH_TXT_ATR_FIELD*: if it's same as current position, + // skip behind it so that both before- and after-positions are returned + nNext = (nPosition < p) ? p : p + 1; + } + return nNext; +} + +TextFrameIndex SwAttrIter::GetNextAttr() const +{ + size_t nStartIndex(m_nStartIndex); + size_t nEndIndex(m_nEndIndex); + size_t nPosition(m_nPosition); + SwTextNode const* pTextNode(m_pTextNode); + SwRedlineTable::size_type nActRedline(m_pRedline ? m_pRedline->GetAct() : SwRedlineTable::npos); + + while (true) + { + sal_Int32 nNext = GetNextAttrImpl(pTextNode, nStartIndex, nEndIndex, nPosition); + if( m_pRedline ) + { + std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> const redline( + m_pRedline->GetNextRedln(nNext, pTextNode, nActRedline)); + if (redline.second.first) + { + assert(m_pMergedPara); + assert(redline.second.first->End()->nNode.GetIndex() <= m_pMergedPara->pLastNode->GetIndex() + || !redline.second.first->End()->nNode.GetNode().IsTextNode()); + if (CanSkipOverRedline(*pTextNode, redline.first, *redline.second.first, + nStartIndex, nEndIndex, m_nPosition == redline.first)) + { // if current position is start of the redline, must skip! + nActRedline += redline.second.second; + if (&redline.second.first->End()->nNode.GetNode() != pTextNode) + { + pTextNode = redline.second.first->End()->nNode.GetNode().GetTextNode(); + nPosition = redline.second.first->End()->nContent.GetIndex(); + } + else + { + nPosition = redline.second.first->End()->nContent.GetIndex(); + } + } + else + { + return sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first); + } + } + else + { + return m_pMergedPara + ? sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first) + : TextFrameIndex(redline.first); + } + } + else + { + return TextFrameIndex(nNext); + } + } +} + +namespace { + +class SwMinMaxArgs +{ +public: + VclPtr<OutputDevice> pOut; + SwViewShell const * pSh; + sal_uLong &rMin; + sal_uLong &rAbsMin; + long nRowWidth; + long nWordWidth; + long nWordAdd; + sal_Int32 nNoLineBreak; + SwMinMaxArgs( OutputDevice* pOutI, SwViewShell const * pShI, sal_uLong& rMinI, sal_uLong &rAbsI ) + : pOut( pOutI ), pSh( pShI ), rMin( rMinI ), rAbsMin( rAbsI ), nRowWidth(0), + nWordWidth(0), nWordAdd(0), nNoLineBreak(COMPLETE_STRING) + { } + void Minimum( long nNew ) const { if( static_cast<long>(rMin) < nNew ) rMin = nNew; } + void NewWord() { nWordAdd = nWordWidth = 0; } +}; + +} + +static bool lcl_MinMaxString( SwMinMaxArgs& rArg, SwFont* pFnt, const OUString &rText, + sal_Int32 nIdx, sal_Int32 nEnd ) +{ + bool bRet = false; + while( nIdx < nEnd ) + { + sal_Int32 nStop = nIdx; + LanguageType eLang = pFnt->GetLanguage(); + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + bool bClear = CH_BLANK == rText[ nStop ]; + Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary( rText, nIdx, + g_pBreakIt->GetLocale( eLang ), + WordType::DICTIONARY_WORD, true ) ); + nStop = aBndry.endPos; + if( nIdx <= aBndry.startPos && nIdx && nIdx-1 != rArg.nNoLineBreak ) + rArg.NewWord(); + if( nStop == nIdx ) + ++nStop; + if( nStop > nEnd ) + nStop = nEnd; + + SwDrawTextInfo aDrawInf(rArg.pSh, *rArg.pOut, rText, nIdx, nStop - nIdx); + long nCurrentWidth = pFnt->GetTextSize_( aDrawInf ).Width(); + rArg.nRowWidth += nCurrentWidth; + if( bClear ) + rArg.NewWord(); + else + { + rArg.nWordWidth += nCurrentWidth; + if( static_cast<long>(rArg.rAbsMin) < rArg.nWordWidth ) + rArg.rAbsMin = rArg.nWordWidth; + rArg.Minimum( rArg.nWordWidth + rArg.nWordAdd ); + bRet = true; + } + nIdx = nStop; + } + return bRet; +} + +bool SwTextNode::IsSymbolAt(const sal_Int32 nBegin) const +{ + SwScriptInfo aScriptInfo; + SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo ); + aIter.Seek( TextFrameIndex(nBegin) ); + return aIter.GetFnt()->IsSymbol( getIDocumentLayoutAccess().GetCurrentViewShell() ); +} + +namespace { + +class SwMinMaxNodeArgs +{ +public: + sal_uLong nMaxWidth; // sum of all frame widths + long nMinWidth; // biggest frame + long nLeftRest; // space not already covered by frames in the left margin + long nRightRest; // space not already covered by frames in the right margin + long nLeftDiff; // Min/Max-difference of the frame in the left margin + long nRightDiff; // Min/Max-difference of the frame in the right margin + sal_uLong nIndx; // index of the node + void Minimum( long nNew ) { if( nNew > nMinWidth ) nMinWidth = nNew; } +}; + +} + +static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) +{ + const SwFormatAnchor& rFormatA = pNd->GetAnchor(); + + if ((RndStdIds::FLY_AT_PARA != rFormatA.GetAnchorId()) && + (RndStdIds::FLY_AT_CHAR != rFormatA.GetAnchorId())) + { + return; + } + + const SwPosition *pPos = rFormatA.GetContentAnchor(); + OSL_ENSURE(pPos && pIn, "Unexpected NULL arguments"); + if (!pPos || !pIn || pIn->nIndx != pPos->nNode.GetIndex()) + return; + + long nMin, nMax; + SwHTMLTableLayout *pLayout = nullptr; + const bool bIsDrawFrameFormat = pNd->Which()==RES_DRAWFRMFMT; + if( !bIsDrawFrameFormat ) + { + // Does the frame contain a table at the start or the end? + const SwNodes& rNodes = pNd->GetDoc()->GetNodes(); + const SwFormatContent& rFlyContent = pNd->GetContent(); + sal_uLong nStt = rFlyContent.GetContentIdx()->GetIndex(); + SwTableNode* pTableNd = rNodes[nStt+1]->GetTableNode(); + if( !pTableNd ) + { + SwNode *pNd2 = rNodes[nStt]; + pNd2 = rNodes[pNd2->EndOfSectionIndex()-1]; + if( pNd2->IsEndNode() ) + pTableNd = pNd2->StartOfSectionNode()->GetTableNode(); + } + + if( pTableNd ) + pLayout = pTableNd->GetTable().GetHTMLTableLayout(); + } + + const SwFormatHoriOrient& rOrient = pNd->GetHoriOrient(); + sal_Int16 eHoriOri = rOrient.GetHoriOrient(); + + long nDiff; + if( pLayout ) + { + nMin = pLayout->GetMin(); + nMax = pLayout->GetMax(); + nDiff = nMax - nMin; + } + else + { + if( bIsDrawFrameFormat ) + { + const SdrObject* pSObj = pNd->FindSdrObject(); + if( pSObj ) + nMin = pSObj->GetCurrentBoundRect().GetWidth(); + else + nMin = 0; + + } + else + { + const SwFormatFrameSize &rSz = pNd->GetFrameSize(); + nMin = rSz.GetWidth(); + } + nMax = nMin; + nDiff = 0; + } + + const SvxLRSpaceItem &rLR = pNd->GetLRSpace(); + nMin += rLR.GetLeft(); + nMin += rLR.GetRight(); + nMax += rLR.GetLeft(); + nMax += rLR.GetRight(); + + if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() ) + { + pIn->Minimum( nMin ); + return; + } + + // Frames, which are left- or right-aligned are only party considered + // when calculating the maximum, since the border is already being considered. + // Only if the frame extends into the text body, this part is being added + switch( eHoriOri ) + { + case text::HoriOrientation::RIGHT: + { + if( nDiff ) + { + pIn->nRightRest -= pIn->nRightDiff; + pIn->nRightDiff = nDiff; + } + if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() ) + { + if( pIn->nRightRest > 0 ) + pIn->nRightRest = 0; + } + pIn->nRightRest -= nMin; + break; + } + case text::HoriOrientation::LEFT: + { + if( nDiff ) + { + pIn->nLeftRest -= pIn->nLeftDiff; + pIn->nLeftDiff = nDiff; + } + if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() && + pIn->nLeftRest < 0 ) + pIn->nLeftRest = 0; + pIn->nLeftRest -= nMin; + break; + } + default: + { + pIn->nMaxWidth += nMax; + pIn->Minimum( nMin ); + } + } +} + +#define FLYINCNT_MIN_WIDTH 284 + +/** + * Changing this method very likely requires changing of GetScalingOfSelectedText + * This one is called exclusively from import filters, so there is no layout. + */ +void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rMax, + sal_uLong& rAbsMin ) const +{ + SwViewShell const * pSh = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + OutputDevice* pOut = nullptr; + if( pSh ) + pOut = pSh->GetWin(); + if( !pOut ) + pOut = Application::GetDefaultDevice(); + + MapMode aOldMap( pOut->GetMapMode() ); + pOut->SetMapMode( MapMode( MapUnit::MapTwip ) ); + + rMin = 0; + rMax = 0; + rAbsMin = 0; + + const SvxLRSpaceItem &rSpace = GetSwAttrSet().GetLRSpace(); + long nLROffset = rSpace.GetTextLeft() + GetLeftMarginWithNum( true ); + short nFLOffs; + // For enumerations a negative first line indentation is probably filled already + if( !GetFirstLineOfsWithNum( nFLOffs ) || nFLOffs > nLROffset ) + nLROffset = nFLOffs; + + SwMinMaxNodeArgs aNodeArgs; + aNodeArgs.nMinWidth = 0; + aNodeArgs.nMaxWidth = 0; + aNodeArgs.nLeftRest = nLROffset; + aNodeArgs.nRightRest = rSpace.GetRight(); + aNodeArgs.nLeftDiff = 0; + aNodeArgs.nRightDiff = 0; + if( nIndex ) + { + SwFrameFormats* pTmp = const_cast<SwFrameFormats*>(GetDoc()->GetSpzFrameFormats()); + if( pTmp ) + { + aNodeArgs.nIndx = nIndex; + for( SwFrameFormat *pFormat : *pTmp ) + lcl_MinMaxNode( pFormat, &aNodeArgs ); + } + } + if( aNodeArgs.nLeftRest < 0 ) + aNodeArgs.Minimum( nLROffset - aNodeArgs.nLeftRest ); + aNodeArgs.nLeftRest -= aNodeArgs.nLeftDiff; + if( aNodeArgs.nLeftRest < 0 ) + aNodeArgs.nMaxWidth -= aNodeArgs.nLeftRest; + + if( aNodeArgs.nRightRest < 0 ) + aNodeArgs.Minimum( rSpace.GetRight() - aNodeArgs.nRightRest ); + aNodeArgs.nRightRest -= aNodeArgs.nRightDiff; + if( aNodeArgs.nRightRest < 0 ) + aNodeArgs.nMaxWidth -= aNodeArgs.nRightRest; + + SwScriptInfo aScriptInfo; + SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo ); + TextFrameIndex nIdx(0); + aIter.SeekAndChgAttrIter( nIdx, pOut ); + TextFrameIndex nLen(m_Text.getLength()); + long nCurrentWidth = 0; + long nAdd = 0; + SwMinMaxArgs aArg( pOut, pSh, rMin, rAbsMin ); + while( nIdx < nLen ) + { + TextFrameIndex nNextChg = aIter.GetNextAttr(); + TextFrameIndex nStop = aScriptInfo.NextScriptChg( nIdx ); + if( nNextChg > nStop ) + nNextChg = nStop; + SwTextAttr *pHint = nullptr; + sal_Unicode cChar = CH_BLANK; + nStop = nIdx; + while( nStop < nLen && nStop < nNextChg && + CH_TAB != (cChar = m_Text[sal_Int32(nStop)]) && + CH_BREAK != cChar && CHAR_HARDBLANK != cChar && + CHAR_HARDHYPHEN != cChar && CHAR_SOFTHYPHEN != cChar && + CH_TXT_ATR_INPUTFIELDSTART != cChar && + CH_TXT_ATR_INPUTFIELDEND != cChar && + CH_TXT_ATR_FORMELEMENT != cChar && + CH_TXT_ATR_FIELDSTART != cChar && + CH_TXT_ATR_FIELDSEP != cChar && + CH_TXT_ATR_FIELDEND != cChar && + !pHint ) + { + // this looks like some defensive programming to handle dummy char + // with missing hint? but it's rather silly because it may pass the + // dummy char to lcl_MinMaxString in that case... + if( ( CH_TXTATR_BREAKWORD != cChar && CH_TXTATR_INWORD != cChar ) + || ( nullptr == ( pHint = aIter.GetAttr( nStop ) ) ) ) + ++nStop; + } + if (lcl_MinMaxString(aArg, aIter.GetFnt(), m_Text, sal_Int32(nIdx), sal_Int32(nStop))) + { + nAdd = 20; + } + nIdx = nStop; + aIter.SeekAndChgAttrIter( nIdx, pOut ); + switch( cChar ) + { + case CH_BREAK : + { + if( static_cast<long>(rMax) < aArg.nRowWidth ) + rMax = aArg.nRowWidth; + aArg.nRowWidth = 0; + aArg.NewWord(); + aIter.SeekAndChgAttrIter( ++nIdx, pOut ); + } + break; + case CH_TAB : + { + aArg.NewWord(); + aIter.SeekAndChgAttrIter( ++nIdx, pOut ); + } + break; + case CHAR_SOFTHYPHEN: + ++nIdx; + break; + case CHAR_HARDBLANK: + case CHAR_HARDHYPHEN: + { + OUString sTmp( cChar ); + SwDrawTextInfo aDrawInf( pSh, + *pOut, sTmp, 0, 1, 0, false ); + nCurrentWidth = aIter.GetFnt()->GetTextSize_( aDrawInf ).Width(); + aArg.nWordWidth += nCurrentWidth; + aArg.nRowWidth += nCurrentWidth; + if( static_cast<long>(rAbsMin) < aArg.nWordWidth ) + rAbsMin = aArg.nWordWidth; + aArg.Minimum( aArg.nWordWidth + aArg.nWordAdd ); + aArg.nNoLineBreak = sal_Int32(nIdx++); + } + break; + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + { + if( !pHint ) + break; + long nOldWidth = aArg.nWordWidth; + long nOldAdd = aArg.nWordAdd; + aArg.NewWord(); + + switch( pHint->Which() ) + { + case RES_TXTATR_FLYCNT : + { + SwFrameFormat *pFrameFormat = pHint->GetFlyCnt().GetFrameFormat(); + const SvxLRSpaceItem &rLR = pFrameFormat->GetLRSpace(); + if( RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + const SdrObject* pSObj = pFrameFormat->FindSdrObject(); + if( pSObj ) + nCurrentWidth = pSObj->GetCurrentBoundRect().GetWidth(); + else + nCurrentWidth = 0; + } + else + { + const SwFormatFrameSize& rTmpSize = pFrameFormat->GetFrameSize(); + if( RES_FLYFRMFMT == pFrameFormat->Which() + && rTmpSize.GetWidthPercent() ) + { + // This is a hack for the following situation: In the paragraph there's a + // text frame with relative size. Then let's take 0.5 cm as minimum width + // and USHRT_MAX as maximum width + // It were cleaner and maybe necessary later on to iterate over the content + // of the text frame and call GetMinMaxSize recursively + nCurrentWidth = FLYINCNT_MIN_WIDTH; // 0.5 cm + rMax = std::max(rMax, sal_uLong(USHRT_MAX)); + } + else + nCurrentWidth = pFrameFormat->GetFrameSize().GetWidth(); + } + nCurrentWidth += rLR.GetLeft(); + nCurrentWidth += rLR.GetRight(); + aArg.nWordAdd = nOldWidth + nOldAdd; + aArg.nWordWidth = nCurrentWidth; + aArg.nRowWidth += nCurrentWidth; + if( static_cast<long>(rAbsMin) < aArg.nWordWidth ) + rAbsMin = aArg.nWordWidth; + aArg.Minimum( aArg.nWordWidth + aArg.nWordAdd ); + break; + } + case RES_TXTATR_FTN : + { + const OUString aText = pHint->GetFootnote().GetNumStr(); + if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0, + aText.getLength() ) ) + nAdd = 20; + break; + } + + case RES_TXTATR_FIELD : + case RES_TXTATR_ANNOTATION : + { + SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField()); + const OUString aText = pField->ExpandField(true, nullptr); + if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0, + aText.getLength() ) ) + nAdd = 20; + break; + } + default: aArg.nWordWidth = nOldWidth; + aArg.nWordAdd = nOldAdd; + + } + aIter.SeekAndChgAttrIter( ++nIdx, pOut ); + } + break; + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + { // just skip it and continue with the content... + aIter.SeekAndChgAttrIter( ++nIdx, pOut ); + } + break; + } + } + if( static_cast<long>(rMax) < aArg.nRowWidth ) + rMax = aArg.nRowWidth; + + nLROffset += rSpace.GetRight(); + + rAbsMin += nLROffset; + rAbsMin += nAdd; + rMin += nLROffset; + rMin += nAdd; + if( static_cast<long>(rMin) < aNodeArgs.nMinWidth ) + rMin = aNodeArgs.nMinWidth; + if( static_cast<long>(rAbsMin) < aNodeArgs.nMinWidth ) + rAbsMin = aNodeArgs.nMinWidth; + rMax += aNodeArgs.nMaxWidth; + rMax += nLROffset; + rMax += nAdd; + if( rMax < rMin ) // e.g. Frames with flow through only contribute to the minimum + rMax = rMin; + pOut->SetMapMode( aOldMap ); +} + +/** + * Calculates the width of the text part specified by nStart and nEnd, + * the height of the line containing nStart is divided by this width, + * indicating the scaling factor, if the text part is rotated. + * Having CH_BREAKs in the text part, this method returns the scaling + * factor for the longest of the text parts separated by the CH_BREAK + * + * Changing this method very likely requires changing of "GetMinMaxSize" + */ +sal_uInt16 SwTextFrame::GetScalingOfSelectedText( + TextFrameIndex nStart, TextFrameIndex nEnd) +{ + assert(GetOffset() <= nStart && (!GetFollow() || nStart < GetFollow()->GetOffset())); + SwViewShell const*const pSh = getRootFrame()->GetCurrShell(); + assert(pSh); + OutputDevice *const pOut = &pSh->GetRefDev(); + assert(pOut); + + MapMode aOldMap( pOut->GetMapMode() ); + pOut->SetMapMode( MapMode( MapUnit::MapTwip ) ); + + if (nStart == nEnd) + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + SwScriptInfo aScriptInfo; + SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this); + aIter.SeekAndChgAttrIter( nStart, pOut ); + + Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( + GetText(), sal_Int32(nStart), + g_pBreakIt->GetLocale( aIter.GetFnt()->GetLanguage() ), + WordType::DICTIONARY_WORD, true ); + + if (sal_Int32(nStart) == aBound.startPos) + { + // cursor is at left or right border of word + pOut->SetMapMode( aOldMap ); + return 100; + } + + nStart = TextFrameIndex(aBound.startPos); + nEnd = TextFrameIndex(aBound.endPos); + + if (nStart == nEnd) + { + pOut->SetMapMode( aOldMap ); + return 100; + } + } + + SwScriptInfo aScriptInfo; + SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this); + + // We do not want scaling attributes to be considered during this + // calculation. For this, we push a temporary scaling attribute with + // scaling value 100 and priority flag on top of the scaling stack + SwAttrHandler& rAH = aIter.GetAttrHandler(); + SvxCharScaleWidthItem aItem(100, RES_CHRATR_SCALEW); + SwTextAttrEnd aAttr( aItem, 0, COMPLETE_STRING ); + aAttr.SetPriorityAttr( true ); + rAH.PushAndChg( aAttr, *(aIter.GetFnt()) ); + + TextFrameIndex nIdx = nStart; + + sal_uLong nWidth = 0; + sal_uLong nProWidth = 0; + + while( nIdx < nEnd ) + { + aIter.SeekAndChgAttrIter( nIdx, pOut ); + + // scan for end of portion + TextFrameIndex const nNextChg = std::min(aIter.GetNextAttr(), aScriptInfo.NextScriptChg(nIdx)); + + TextFrameIndex nStop = nIdx; + sal_Unicode cChar = CH_BLANK; + SwTextAttr* pHint = nullptr; + + // stop at special characters in [ nIdx, nNextChg ] + while( nStop < nEnd && nStop < nNextChg ) + { + cChar = GetText()[sal_Int32(nStop)]; + if ( + CH_TAB == cChar || + CH_BREAK == cChar || + CHAR_HARDBLANK == cChar || + CHAR_HARDHYPHEN == cChar || + CHAR_SOFTHYPHEN == cChar || + CH_TXT_ATR_INPUTFIELDSTART == cChar || + CH_TXT_ATR_INPUTFIELDEND == cChar || + CH_TXT_ATR_FORMELEMENT == cChar || + CH_TXT_ATR_FIELDSTART == cChar || + CH_TXT_ATR_FIELDSEP == cChar || + CH_TXT_ATR_FIELDEND == cChar || + ( + (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) && + (nullptr == (pHint = aIter.GetAttr(nStop))) + ) + ) + { + break; + } + else + ++nStop; + } + + // calculate text widths up to cChar + if ( nStop > nIdx ) + { + SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nIdx), sal_Int32(nStop - nIdx)); + nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width(); + } + + nIdx = nStop; + aIter.SeekAndChgAttrIter( nIdx, pOut ); + + if ( cChar == CH_BREAK ) + { + nWidth = std::max( nWidth, nProWidth ); + nProWidth = 0; + nIdx++; + } + else if ( cChar == CH_TAB ) + { + // tab receives width of one space + OUString sTmp( CH_BLANK ); + SwDrawTextInfo aDrawInf(pSh, *pOut, sTmp, 0, 1); + nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width(); + nIdx++; + } + else if ( cChar == CHAR_SOFTHYPHEN ) + ++nIdx; + else if ( cChar == CHAR_HARDBLANK || cChar == CHAR_HARDHYPHEN ) + { + OUString sTmp( cChar ); + SwDrawTextInfo aDrawInf(pSh, *pOut, sTmp, 0, 1); + nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width(); + nIdx++; + } + else if ( pHint && ( cChar == CH_TXTATR_BREAKWORD || cChar == CH_TXTATR_INWORD ) ) + { + switch( pHint->Which() ) + { + case RES_TXTATR_FTN : + { + const OUString aText = pHint->GetFootnote().GetNumStr(); + SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength()); + + nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width(); + break; + } + + case RES_TXTATR_FIELD : + case RES_TXTATR_ANNOTATION : + { + SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField()); + OUString const aText = pField->ExpandField(true, getRootFrame()); + SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength()); + + nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width(); + break; + } + + default: + { + // any suggestions for a default action? + } + } // end of switch + nIdx++; + } + else if (CH_TXT_ATR_INPUTFIELDSTART == cChar || + CH_TXT_ATR_INPUTFIELDEND == cChar || + CH_TXT_ATR_FORMELEMENT == cChar || + CH_TXT_ATR_FIELDSTART == cChar || + CH_TXT_ATR_FIELDSEP == cChar || + CH_TXT_ATR_FIELDEND == cChar) + { // just skip it and continue with the content... + ++nIdx; + } + } // end of while + + nWidth = std::max( nWidth, nProWidth ); + + // search for the line containing nStart + if (HasPara()) + { + SwTextInfo aInf(this); + SwTextIter aLine(this, &aInf); + aLine.CharToLine( nStart ); + pOut->SetMapMode( aOldMap ); + return static_cast<sal_uInt16>( nWidth ? + ( ( 100 * aLine.GetCurr()->Height() ) / nWidth ) : 0 ); + } + // no frame or no paragraph, we take the height of the character + // at nStart as line height + + aIter.SeekAndChgAttrIter( nStart, pOut ); + pOut->SetMapMode( aOldMap ); + + SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nStart), 1); + return static_cast<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 ); +} + +SwTwips SwTextNode::GetWidthOfLeadingTabs() const +{ + SwTwips nRet = 0; + + sal_Int32 nIdx = 0; + + while ( nIdx < GetText().getLength() ) + { + const sal_Unicode cCh = GetText()[nIdx]; + if ( cCh!='\t' && cCh!=' ' ) + { + break; + } + ++nIdx; + } + + if ( nIdx > 0 ) + { + SwPosition aPos( *this ); + aPos.nContent += nIdx; + + // Find the non-follow text frame: + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + // Only consider master frames: + if (!pFrame->IsFollow() && + pFrame->GetTextNodeForFirstText() == this) + { + SwRectFnSet aRectFnSet(pFrame); + SwRect aRect; + pFrame->GetCharRect( aRect, aPos ); + nRet = pFrame->IsRightToLeft() ? + aRectFnSet.GetPrtRight(*pFrame) - aRectFnSet.GetRight(aRect) : + aRectFnSet.GetLeft(aRect) - aRectFnSet.GetPrtLeft(*pFrame); + break; + } + } + } + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itratr.hxx b/sw/source/core/text/itratr.hxx new file mode 100644 index 000000000..78130e5de --- /dev/null +++ b/sw/source/core/text/itratr.hxx @@ -0,0 +1,114 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_ITRATR_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_ITRATR_HXX +#include <o3tl/deleter.hxx> +#include "atrhndl.hxx" +#include <swfont.hxx> + +namespace sw { struct MergedPara; } +class SwTextAttr; +class SwTextNode; +class SwRedlineItr; +class SwViewShell; +class SwTextFrame; + +class SwAttrIter +{ + friend class SwFontSave; +protected: + + SwAttrHandler m_aAttrHandler; + SwViewShell *m_pViewShell; + SwFont* m_pFont; + SwScriptInfo* m_pScriptInfo; + +private: + VclPtr<OutputDevice> m_pLastOut; + /// count currently open hints, redlines, ext-input + short m_nChgCnt; + std::unique_ptr<SwRedlineItr, o3tl::default_delete<SwRedlineItr>> m_pRedline; + /// current iteration index in HintStarts + size_t m_nStartIndex; + /// current iteration index in HintEnds + size_t m_nEndIndex; + /// current iteration index in text node + sal_Int32 m_nPosition; + sal_uInt8 m_nPropFont; + o3tl::enumarray<SwFontScript, const void*> m_aFontCacheIds; + o3tl::enumarray<SwFontScript, sal_uInt16> m_aFontIdx; + /// input: the current text node + const SwTextNode* m_pTextNode; + sw::MergedPara const* m_pMergedPara; + + void SeekFwd(sal_Int32 nOldPos, sal_Int32 nNewPos); + void SetFnt( SwFont* pNew ) { m_pFont = pNew; } + void InitFontAndAttrHandler( + SwTextNode const& rPropsNode, SwTextNode const& rTextNode, + OUString const& rText, bool const* pbVertLayout, + bool const* pbVertLayoutLRBT); + +protected: + void Chg( SwTextAttr const *pHt ); + void Rst( SwTextAttr const *pHt ); + void CtorInitAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const* pFrame = nullptr); + explicit SwAttrIter(SwTextNode const * pTextNode); + +public: + /// All subclasses of this always have a SwTextFrame passed to the + /// constructor, but SwAttrIter itself may be created without a + /// SwTextFrame in certain special cases via this ctor here + SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame = nullptr); + + virtual ~SwAttrIter(); + + SwRedlineItr *GetRedln() { return m_pRedline.get(); } + // The parameter returns the position of the next change before or at the + // char position. + TextFrameIndex GetNextAttr() const; + /// Enables the attributes used at char pos nPos in the logical font + bool Seek(TextFrameIndex nPos); + // Creates the font at the specified position via Seek() and checks + // if it's a symbol font. + bool IsSymbol(TextFrameIndex nPos); + + /** Executes ChgPhysFnt if Seek() returns true + * and change font to merge character border with neighbours. + **/ + bool SeekAndChgAttrIter(TextFrameIndex nPos, OutputDevice* pOut); + bool SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont ); + + // Do we possibly have nasty things like footnotes? + bool MaybeHasHints() const; + + // Returns the attribute for a position + SwTextAttr *GetAttr(TextFrameIndex nPos) const; + + SwFont *GetFnt() { return m_pFont; } + const SwFont *GetFnt() const { return m_pFont; } + + sal_uInt8 GetPropFont() const { return m_nPropFont; } + void SetPropFont( const sal_uInt8 nNew ) { m_nPropFont = nNew; } + + SwAttrHandler& GetAttrHandler() { return m_aAttrHandler; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx new file mode 100644 index 000000000..cb830f327 --- /dev/null +++ b/sw/source/core/text/itrcrsr.cxx @@ -0,0 +1,1913 @@ +/* -*- 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 <ndtxt.hxx> +#include <doc.hxx> +#include <paratr.hxx> +#include <flyfrm.hxx> +#include <pam.hxx> +#include <swselectionlist.hxx> +#include <sortedobjs.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/lrspitem.hxx> +#include <frmatr.hxx> +#include <tgrditem.hxx> +#include <IDocumentSettingAccess.hxx> +#include <pagefrm.hxx> + +#include "itrtxt.hxx" +#include <txtfrm.hxx> +#include <flyfrms.hxx> +#include "porfld.hxx" +#include "porfly.hxx" +#include "pordrop.hxx" +#include <crstate.hxx> +#include "pormulti.hxx" +#include <numrule.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> + +// Not reentrant !!! +// is set in GetCharRect and is interpreted in UnitUp/Down. +bool SwTextCursor::bRightMargin = false; + +// After calculating the position of a character during GetCharRect +// this function allows to find the coordinates of a position (defined +// in pCMS->pSpecialPos) inside a special portion (e.g., a field) +static void lcl_GetCharRectInsideField( SwTextSizeInfo& rInf, SwRect& rOrig, + const SwCursorMoveState& rCMS, + const SwLinePortion& rPor ) +{ + OSL_ENSURE( rCMS.m_pSpecialPos, "Information about special pos missing" ); + + if ( rPor.InFieldGrp() && !static_cast<const SwFieldPortion&>(rPor).GetExp().isEmpty() ) + { + const sal_Int32 nCharOfst = rCMS.m_pSpecialPos->nCharOfst; + sal_Int32 nFieldIdx = 0; + sal_Int32 nFieldLen = 0; + + OUString sString; + const OUString* pString = nullptr; + const SwLinePortion* pPor = &rPor; + do + { + if ( pPor->InFieldGrp() ) + { + sString = static_cast<const SwFieldPortion*>(pPor)->GetExp(); + pString = &sString; + nFieldLen = pString->getLength(); + } + else + { + pString = nullptr; + nFieldLen = 0; + } + + if ( ! pPor->GetNextPortion() || nFieldIdx + nFieldLen > nCharOfst ) + break; + + nFieldIdx = nFieldIdx + nFieldLen; + rOrig.Pos().AdjustX(pPor->Width() ); + pPor = pPor->GetNextPortion(); + + } while ( true ); + + OSL_ENSURE( nCharOfst >= nFieldIdx, "Request of position inside field failed" ); + sal_Int32 nLen = nCharOfst - nFieldIdx + 1; + + if ( pString ) + { + // get script for field portion + rInf.GetFont()->SetActual( SwScriptInfo::WhichFont(0, *pString) ); + + TextFrameIndex const nOldLen = pPor->GetLen(); + const_cast<SwLinePortion*>(pPor)->SetLen(TextFrameIndex(nLen - 1)); + const SwTwips nX1 = pPor->GetLen() ? + pPor->GetTextSize( rInf ).Width() : + 0; + + SwTwips nX2 = 0; + if ( rCMS.m_bRealWidth ) + { + const_cast<SwLinePortion*>(pPor)->SetLen(TextFrameIndex(nLen)); + nX2 = pPor->GetTextSize( rInf ).Width(); + } + + const_cast<SwLinePortion*>(pPor)->SetLen( nOldLen ); + + rOrig.Pos().AdjustX(nX1 ); + rOrig.Width( ( nX2 > nX1 ) ? + ( nX2 - nX1 ) : + 1 ); + } + } + else + { + // special cases: no common fields, e.g., graphic number portion, + // FlyInCntPortions, Notes + rOrig.Width( rCMS.m_bRealWidth && rPor.Width() ? rPor.Width() : 1 ); + } +} + +// #i111284# +namespace { + bool IsLabelAlignmentActive( const SwTextNode& rTextNode ) + { + bool bRet( false ); + + if ( rTextNode.GetNumRule() ) + { + int nListLevel = rTextNode.GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const SwNumFormat& rNumFormat = + rTextNode.GetNumRule()->Get( static_cast<sal_uInt16>(nListLevel) ); + if ( rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + bRet = true; + } + } + + return bRet; + } +} // end of anonymous namespace + +void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *pNewInf ) +{ + CtorInitTextIter( pNewFrame, pNewInf ); + + m_pInf = pNewInf; + GetInfo().SetFont( GetFnt() ); + const SwTextNode *const pNode = m_pFrame->GetTextNodeForParaProps(); + + const SvxLRSpaceItem &rSpace = pNode->GetSwAttrSet().GetLRSpace(); + // #i95907# + // #i111284# + const SwTextNode *pTextNode = m_pFrame->GetTextNodeForParaProps(); + const bool bLabelAlignmentActive = IsLabelAlignmentActive( *pTextNode ); + const bool bListLevelIndentsApplicable = pTextNode->AreListLevelIndentsApplicable(); + const bool bListLevelIndentsApplicableAndLabelAlignmentActive = bListLevelIndentsApplicable && bLabelAlignmentActive; + + // Carefully adjust the text formatting ranges. + + // This whole area desperately needs some rework. There are + // quite a couple of values that need to be considered: + // 1. paragraph indent + // 2. paragraph first line indent + // 3. numbering indent + // 4. numbering spacing to text + // 5. paragraph border + // Note: These values have already been used during calculation + // of the printing area of the paragraph. + const int nLMWithNum = pNode->GetLeftMarginWithNum( true ); + if ( m_pFrame->IsRightToLeft() ) + { + // this calculation is identical this the calculation for L2R layout - see below + nLeft = m_pFrame->getFrameArea().Left() + + m_pFrame->getFramePrintArea().Left() + + nLMWithNum - + pNode->GetLeftMarginWithNum() - + // #i95907# + // #i111284# + // rSpace.GetLeft() + rSpace.GetTextLeft(); + ( bListLevelIndentsApplicableAndLabelAlignmentActive + ? 0 + : ( rSpace.GetLeft() - rSpace.GetTextLeft() ) ); + } + else + { + // #i95907# + // #i111284# + if ( bListLevelIndentsApplicableAndLabelAlignmentActive || + !pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) ) + { + // this calculation is identical this the calculation for R2L layout - see above + nLeft = m_pFrame->getFrameArea().Left() + + m_pFrame->getFramePrintArea().Left() + + nLMWithNum - + pNode->GetLeftMarginWithNum() - + // #i95907# + // #i111284# + ( bListLevelIndentsApplicableAndLabelAlignmentActive + ? 0 + : ( rSpace.GetLeft() - rSpace.GetTextLeft() ) ); + } + else + { + nLeft = m_pFrame->getFrameArea().Left() + + std::max( long( rSpace.GetTextLeft() + nLMWithNum ), + m_pFrame->getFramePrintArea().Left() ); + } + } + + nRight = m_pFrame->getFrameArea().Left() + m_pFrame->getFramePrintArea().Left() + m_pFrame->getFramePrintArea().Width(); + + if( nLeft >= nRight && + // #i53066# Omit adjustment of nLeft for numbered + // paras inside cells inside new documents: + ( pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) || + !m_pFrame->IsInTab() || + ( !nLMWithNum && !(bLabelAlignmentActive && !bListLevelIndentsApplicable) ) ) ) + { + nLeft = m_pFrame->getFramePrintArea().Left() + m_pFrame->getFrameArea().Left(); + if( nLeft >= nRight ) // e.g. with large paragraph indentations in slim table columns + nRight = nLeft + 1; // einen goennen wir uns immer + } + + if( m_pFrame->IsFollow() && m_pFrame->GetOffset() ) + nFirst = nLeft; + else + { + short nFLOfst = 0; + long nFirstLineOfs = 0; + if( !pNode->GetFirstLineOfsWithNum( nFLOfst ) && + rSpace.IsAutoFirst() ) + { + nFirstLineOfs = GetFnt()->GetSize( GetFnt()->GetActual() ).Height(); + LanguageType const aLang = m_pFrame->GetLangOfChar( + TextFrameIndex(0), css::i18n::ScriptType::ASIAN); + if (aLang != LANGUAGE_KOREAN && aLang != LANGUAGE_JAPANESE) + nFirstLineOfs<<=1; + + const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing(); + if( pSpace ) + { + switch( pSpace->GetLineSpaceRule() ) + { + case SvxLineSpaceRule::Auto: + break; + case SvxLineSpaceRule::Min: + { + if( nFirstLineOfs < pSpace->GetLineHeight() ) + nFirstLineOfs = pSpace->GetLineHeight(); + break; + } + case SvxLineSpaceRule::Fix: + nFirstLineOfs = pSpace->GetLineHeight(); + break; + default: OSL_FAIL( ": unknown LineSpaceRule" ); + } + switch( pSpace->GetInterLineSpaceRule() ) + { + case SvxInterLineSpaceRule::Off: + break; + case SvxInterLineSpaceRule::Prop: + { + long nTmp = pSpace->GetPropLineSpace(); + // 50% is the minimum, at 0% we switch to + // the default value 100%... + if( nTmp < 50 ) + nTmp = nTmp ? 50 : 100; + + nTmp *= nFirstLineOfs; + nTmp /= 100; + if( !nTmp ) + ++nTmp; + nFirstLineOfs = nTmp; + break; + } + case SvxInterLineSpaceRule::Fix: + { + nFirstLineOfs += pSpace->GetInterLineSpace(); + break; + } + default: OSL_FAIL( ": unknown InterLineSpaceRule" ); + } + } + } + else + nFirstLineOfs = nFLOfst; + + // #i95907# + // #i111284# + if ( m_pFrame->IsRightToLeft() || + bListLevelIndentsApplicableAndLabelAlignmentActive || + !pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) ) + { + if ( nFirstLineOfs < 0 && m_pFrame->IsInTab() && + nLeft == m_pFrame->getFramePrintArea().Left() + m_pFrame->getFrameArea().Left() && + !m_pFrame->IsRightToLeft() && + !bListLevelIndentsApplicableAndLabelAlignmentActive ) + { + // tdf#130218 always show hanging indent in narrow table cells + // to avoid hiding the text content of the first line + nLeft -= nFirstLineOfs; + } + + nFirst = nLeft + nFirstLineOfs; + } + else + { + nFirst = m_pFrame->getFrameArea().Left() + + std::max( rSpace.GetTextLeft() + nLMWithNum+ nFirstLineOfs, + m_pFrame->getFramePrintArea().Left() ); + } + + // Note: <SwTextFrame::GetAdditionalFirstLineOffset()> returns a negative + // value for the new list label position and space mode LABEL_ALIGNMENT + // and label alignment CENTER and RIGHT in L2R layout respectively + // label alignment LEFT and CENTER in R2L layout + nFirst += m_pFrame->GetAdditionalFirstLineOffset(); + + if( nFirst >= nRight ) + nFirst = nRight - 1; + } + const SvxAdjustItem& rAdjust = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust(); + nAdjust = rAdjust.GetAdjust(); + + // left is left and right is right + if ( m_pFrame->IsRightToLeft() ) + { + if ( SvxAdjust::Left == nAdjust ) + nAdjust = SvxAdjust::Right; + else if ( SvxAdjust::Right == nAdjust ) + nAdjust = SvxAdjust::Left; + } + + m_bOneBlock = rAdjust.GetOneWord() == SvxAdjust::Block; + m_bLastBlock = rAdjust.GetLastBlock() == SvxAdjust::Block; + m_bLastCenter = rAdjust.GetLastBlock() == SvxAdjust::Center; + + // #i91133# + mnTabLeft = pNode->GetLeftMarginForTabCalculation(); + + DropInit(); +} + +void SwTextMargin::DropInit() +{ + nDropLeft = nDropLines = nDropHeight = nDropDescent = 0; + const SwParaPortion *pPara = GetInfo().GetParaPortion(); + if( pPara ) + { + const SwDropPortion *pPorDrop = pPara->FindDropPortion(); + if ( pPorDrop ) + { + nDropLeft = pPorDrop->GetDropLeft(); + nDropLines = pPorDrop->GetLines(); + nDropHeight = pPorDrop->GetDropHeight(); + nDropDescent = pPorDrop->GetDropDescent(); + } + } +} + +// The function is interpreting / observing / evaluating / keeping / respecting the first line indention and the specified width. +SwTwips SwTextMargin::GetLineStart() const +{ + SwTwips nRet = GetLeftMargin(); + if( GetAdjust() != SvxAdjust::Left && + !m_pCurr->GetFirstPortion()->IsMarginPortion() ) + { + // If the first portion is a Margin, then the + // adjustment is expressed by the portions. + if( GetAdjust() == SvxAdjust::Right ) + nRet = Right() - CurrWidth(); + else if( GetAdjust() == SvxAdjust::Center ) + nRet += (GetLineWidth() - CurrWidth()) / 2; + } + return nRet; +} + +void SwTextCursor::CtorInitTextCursor( SwTextFrame *pNewFrame, SwTextSizeInfo *pNewInf ) +{ + CtorInitTextMargin( pNewFrame, pNewInf ); + // 6096: Attention, the iterators are derived! + // GetInfo().SetOut( GetInfo().GetWin() ); +} + +// 1170: Ancient bug: Shift-End forgets the last character ... +void SwTextCursor::GetEndCharRect(SwRect* pOrig, const TextFrameIndex nOfst, + SwCursorMoveState* pCMS, const long nMax ) +{ + // 1170: Ambiguity of document positions + bRightMargin = true; + CharCursorToLine(nOfst); + + // Somehow twisted: nOfst names the position behind the last + // character of the last line == This is the position in front of the first character + // of the line, in which we are situated: + if( nOfst != GetStart() || !m_pCurr->GetLen() ) + { + // 8810: Master line RightMargin, after that LeftMargin + GetCharRect( pOrig, nOfst, pCMS, nMax ); + bRightMargin = nOfst >= GetEnd() && nOfst < TextFrameIndex(GetInfo().GetText().getLength()); + return; + } + + if( !GetPrev() || !GetPrev()->GetLen() || !PrevLine() ) + { + GetCharRect( pOrig, nOfst, pCMS, nMax ); + return; + } + + // If necessary, as catch up, do the adjustment + GetAdjusted(); + + long nX = 0; + long nLast = 0; + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + + sal_uInt16 nTmpHeight, nTmpAscent; + CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + sal_uInt16 nPorHeight = nTmpHeight; + sal_uInt16 nPorAscent = nTmpAscent; + + // Search for the last Text/EndPortion of the line + while( pPor ) + { + nX = nX + pPor->Width(); + if( pPor->InTextGrp() || ( pPor->GetLen() && !pPor->IsFlyPortion() + && !pPor->IsHolePortion() ) || pPor->IsBreakPortion() ) + { + nLast = nX; + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + pPor = pPor->GetNextPortion(); + } + + const Size aCharSize( 1, nTmpHeight ); + pOrig->Pos( GetTopLeft() ); + pOrig->SSize( aCharSize ); + pOrig->Pos().AdjustX(nLast ); + const SwTwips nTmpRight = Right() - 1; + if( pOrig->Left() > nTmpRight ) + pOrig->Pos().setX( nTmpRight ); + + if ( pCMS && pCMS->m_bRealHeight ) + { + if ( nTmpAscent > nPorAscent ) + pCMS->m_aRealHeight.setX( nTmpAscent - nPorAscent ); + else + pCMS->m_aRealHeight.setX( 0 ); + OSL_ENSURE( nPorHeight, "GetCharRect: Missing Portion-Height" ); + pCMS->m_aRealHeight.setY( nPorHeight ); + } +} + +// internal function, called by SwTextCursor::GetCharRect() to calculate +// the relative character position in the current line. +// pOrig refers to x and y coordinates, width and height of the cursor +// pCMS is used for restricting the cursor, if there are different font +// heights in one line ( first value = offset to y of pOrig, second +// value = real height of (shortened) cursor +void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, + SwCursorMoveState* pCMS ) +{ + const OUString aText = GetInfo().GetText(); + SwTextSizeInfo aInf( GetInfo(), &aText, m_nStart ); + if( GetPropFont() ) + aInf.GetFont()->SetProportion( GetPropFont() ); + sal_uInt16 nTmpAscent, nTmpHeight; // Line height + CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + const Size aCharSize( 1, nTmpHeight ); + const Point aCharPos; + pOrig->Pos( aCharPos ); + pOrig->SSize( aCharSize ); + + // If we are looking for a position inside a field which covers + // more than one line we may not skip any "empty portions" at the + // beginning of a line + const bool bInsideFirstField = pCMS && pCMS->m_pSpecialPos && + ( pCMS->m_pSpecialPos->nLineOfst || + SwSPExtendRange::BEFORE == + pCMS->m_pSpecialPos->nExtendRange ); + + bool bWidth = pCMS && pCMS->m_bRealWidth; + if( !m_pCurr->GetLen() && !m_pCurr->Width() ) + { + if ( pCMS && pCMS->m_bRealHeight ) + { + pCMS->m_aRealHeight.setX( 0 ); + pCMS->m_aRealHeight.setY( nTmpHeight ); + } + } + else + { + sal_uInt16 nPorHeight = nTmpHeight; + sal_uInt16 nPorAscent = nTmpAscent; + SwTwips nX = 0; + SwTwips nTmpFirst = 0; + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + SwBidiPortion* pLastBidiPor = nullptr; + TextFrameIndex nLastBidiIdx(-1); + SwTwips nLastBidiPorWidth = 0; + std::deque<sal_uInt16>* pKanaComp = m_pCurr->GetpKanaComp(); + sal_uInt16 nSpaceIdx = 0; + size_t nKanaIdx = 0; + long nSpaceAdd = m_pCurr->IsSpaceAdd() ? m_pCurr->GetLLSpaceAdd( 0 ) : 0; + + bool bNoText = true; + + // First all portions without Len at beginning of line are skipped. + // Exceptions are the mean special portions from WhichFirstPortion: + // Num, ErgoSum, FootnoteNum, FieldRests + // 8477: but also the only Textportion of an empty line with + // Right/Center-Adjustment! So not just pPor->GetExpandPortion() ... + while( pPor && !pPor->GetLen() && ! bInsideFirstField ) + { + nX += pPor->Width(); + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nX += pPor->CalcSpacing( nSpaceAdd, aInf ); + if( bNoText ) + nTmpFirst = nX; + // 8670: EndPortions count once as TextPortions. + // if( pPor->InTextGrp() || pPor->IsBreakPortion() ) + if( pPor->InTextGrp() || pPor->IsBreakPortion() || pPor->InTabGrp() ) + { + bNoText = false; + nTmpFirst = nX; + } + if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() ) + { + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() ) + ++nKanaIdx; + } + if( pPor->InFixMargGrp() ) + { + if( pPor->IsMarginPortion() ) + bNoText = false; + else + { + // fix margin portion => next SpaceAdd, KanaComp value + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() ) + ++nKanaIdx; + } + } + pPor = pPor->GetNextPortion(); + } + + if( !pPor ) + { + // There's just Spezialportions. + nX = nTmpFirst; + } + else + { + if( !pPor->IsMarginPortion() && !pPor->IsPostItsPortion() && + (!pPor->InFieldGrp() || pPor->GetAscent() ) ) + { + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + while( pPor && !pPor->IsBreakPortion() && ( aInf.GetIdx() < nOfst || + ( bWidth && ( pPor->IsKernPortion() || pPor->IsMultiPortion() ) ) ) ) + { + if( !pPor->IsMarginPortion() && !pPor->IsPostItsPortion() && + (!pPor->InFieldGrp() || pPor->GetAscent() ) ) + { + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + + // If we are behind the portion, we add the portion width to + // nX. Special case: nOfst = aInf.GetIdx() + pPor->GetLen(). + // For common portions (including BidiPortions) we want to add + // the portion width to nX. For MultiPortions, nExtra = 0, + // therefore we go to the 'else' branch and start a recursion. + const TextFrameIndex nExtra( (pPor->IsMultiPortion() + && !static_cast<SwMultiPortion*>(pPor)->IsBidi() + && !bWidth) + ? 0 : 1 ); + if ( aInf.GetIdx() + pPor->GetLen() < nOfst + nExtra ) + { + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nX += pPor->PrtWidth() + + pPor->CalcSpacing( nSpaceAdd, aInf ); + else + { + if( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) + { + // update to current SpaceAdd, KanaComp values + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if ( pKanaComp && + ( nKanaIdx + 1 ) < pKanaComp->size() + ) + ++nKanaIdx; + } + if ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() && + !pPor->GetNextPortion()->IsMarginPortion() ) ) + nX += pPor->PrtWidth(); + } + if( pPor->IsMultiPortion() ) + { + if ( static_cast<SwMultiPortion*>(pPor)->HasTabulator() ) + { + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() ) + ++nKanaIdx; + } + + // if we are right behind a BidiPortion, we have to + // hold a pointer to the BidiPortion in order to + // find the correct cursor position, depending on the + // cursor level + if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() && + aInf.GetIdx() + pPor->GetLen() == nOfst ) + { + pLastBidiPor = static_cast<SwBidiPortion*>(pPor); + nLastBidiIdx = aInf.GetIdx(); + nLastBidiPorWidth = pLastBidiPor->Width() + + pLastBidiPor->CalcSpacing( nSpaceAdd, aInf ); + } + } + + aInf.SetIdx( aInf.GetIdx() + pPor->GetLen() ); + pPor = pPor->GetNextPortion(); + } + else + { + if( pPor->IsMultiPortion() ) + { + nTmpAscent = AdjustBaseLine( *m_pCurr, pPor ); + GetInfo().SetMulti( true ); + pOrig->Pos().AdjustY(nTmpAscent - nPorAscent ); + + if( pCMS && pCMS->m_b2Lines ) + { + const bool bRecursion (pCMS->m_p2Lines); + if ( !bRecursion ) + { + pCMS->m_p2Lines.reset(new Sw2LinesPos); + pCMS->m_p2Lines->aLine = SwRect(aCharPos, aCharSize); + } + + if( static_cast<SwMultiPortion*>(pPor)->HasRotation() ) + { + if( static_cast<SwMultiPortion*>(pPor)->IsRevers() ) + pCMS->m_p2Lines->nMultiType = MultiPortionType::ROT_270; + else + pCMS->m_p2Lines->nMultiType = MultiPortionType::ROT_90; + } + else if( static_cast<SwMultiPortion*>(pPor)->IsDouble() ) + pCMS->m_p2Lines->nMultiType = MultiPortionType::TWOLINE; + else if( static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + pCMS->m_p2Lines->nMultiType = MultiPortionType::BIDI; + else + pCMS->m_p2Lines->nMultiType = MultiPortionType::RUBY; + + SwTwips nTmpWidth = pPor->Width(); + if( nSpaceAdd ) + nTmpWidth += pPor->CalcSpacing(nSpaceAdd, aInf); + + SwRect aRect( Point(aCharPos.X() + nX, pOrig->Top() ), + Size( nTmpWidth, pPor->Height() ) ); + + if ( ! bRecursion ) + pCMS->m_p2Lines->aPortion = aRect; + else + pCMS->m_p2Lines->aPortion2 = aRect; + } + + // In a multi-portion we use GetCharRect()-function + // recursively and must add the x-position + // of the multi-portion. + TextFrameIndex const nOldStart = m_nStart; + SwTwips nOldY = m_nY; + sal_uInt8 nOldProp = GetPropFont(); + m_nStart = aInf.GetIdx(); + SwLineLayout* pOldCurr = m_pCurr; + m_pCurr = &static_cast<SwMultiPortion*>(pPor)->GetRoot(); + if( static_cast<SwMultiPortion*>(pPor)->IsDouble() ) + SetPropFont( 50 ); + + SwTextGridItem const*const pGrid( + GetGridItem(GetTextFrame()->FindPageFrame())); + const bool bHasGrid = pGrid && GetInfo().SnapToGrid(); + const sal_uInt16 nRubyHeight = bHasGrid ? + pGrid->GetRubyHeight() : 0; + + if( m_nStart + m_pCurr->GetLen() <= nOfst && GetNext() && + ( ! static_cast<SwMultiPortion*>(pPor)->IsRuby() || + static_cast<SwMultiPortion*>(pPor)->OnTop() ) ) + { + sal_uInt16 nOffset; + // in grid mode we may only add the height of the + // ruby line if ruby line is on top + if ( bHasGrid && + static_cast<SwMultiPortion*>(pPor)->IsRuby() && + static_cast<SwMultiPortion*>(pPor)->OnTop() ) + nOffset = nRubyHeight; + else + nOffset = GetLineHeight(); + + pOrig->Pos().AdjustY(nOffset ); + Next(); + } + + const bool bSpaceChg = static_cast<SwMultiPortion*>(pPor)-> + ChgSpaceAdd( m_pCurr, nSpaceAdd ); + Point aOldPos = pOrig->Pos(); + + // Ok, for ruby portions in grid mode we have to + // temporarily set the inner line height to the + // outer line height because that value is needed + // for the adjustment inside the recursion + const sal_uInt16 nOldRubyHeight = m_pCurr->Height(); + const sal_uInt16 nOldRubyRealHeight = m_pCurr->GetRealHeight(); + const bool bChgHeight = + static_cast<SwMultiPortion*>(pPor)->IsRuby() && bHasGrid; + + if ( bChgHeight ) + { + m_pCurr->Height( pOldCurr->Height() - nRubyHeight ); + m_pCurr->SetRealHeight( pOldCurr->GetRealHeight() - + nRubyHeight ); + } + + SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() ); + if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + { + aLayoutModeModifier.Modify( + static_cast<SwBidiPortion*>(pPor)->GetLevel() % 2 ); + } + + GetCharRect_( pOrig, nOfst, pCMS ); + + if ( bChgHeight ) + { + m_pCurr->Height( nOldRubyHeight ); + m_pCurr->SetRealHeight( nOldRubyRealHeight ); + } + + // if we are still in the first row of + // our 2 line multiportion, we use the FirstMulti flag + // to indicate this + if ( static_cast<SwMultiPortion*>(pPor)->IsDouble() ) + { + // the recursion may have damaged our font size + SetPropFont( nOldProp ); + GetInfo().GetFont()->SetProportion( 100 ); + + if ( m_pCurr == &static_cast<SwMultiPortion*>(pPor)->GetRoot() ) + { + GetInfo().SetFirstMulti( true ); + + // we want to treat a double line portion like a + // single line portion, if there is no text in + // the second line + if ( !m_pCurr->GetNext() || + !m_pCurr->GetNext()->GetLen() ) + GetInfo().SetMulti( false ); + } + } + // ruby portions are treated like single line portions + else if( static_cast<SwMultiPortion*>(pPor)->IsRuby() || + static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + GetInfo().SetMulti( false ); + + // calculate cursor values + if( static_cast<SwMultiPortion*>(pPor)->HasRotation() ) + { + GetInfo().SetMulti( false ); + long nTmp = pOrig->Width(); + pOrig->Width( pOrig->Height() ); + pOrig->Height( nTmp ); + nTmp = pOrig->Left() - aOldPos.X(); + + // if we travel into our rotated portion from + // a line below, we have to take care, that the + // y coord in pOrig is less than line height: + if ( nTmp ) + nTmp--; + + pOrig->Pos().setX( nX + aOldPos.X() ); + if( static_cast<SwMultiPortion*>(pPor)->IsRevers() ) + pOrig->Pos().setY( aOldPos.Y() + nTmp ); + else + pOrig->Pos().setY( aOldPos.Y() + + pPor->Height() - nTmp - pOrig->Height() ); + if ( pCMS && pCMS->m_bRealHeight ) + { + pCMS->m_aRealHeight.setY( -pCMS->m_aRealHeight.Y() ); + // result for rotated multi portion is not + // correct for reverse (270 degree) portions + if( static_cast<SwMultiPortion*>(pPor)->IsRevers() ) + { + if ( SvxParaVertAlignItem::Align::Automatic == + GetLineInfo().GetVertAlign() ) + // if vertical alignment is set to auto, + // we switch from base line alignment + // to centered alignment + pCMS->m_aRealHeight.setX( + ( pOrig->Width() + + pCMS->m_aRealHeight.Y() ) / 2 ); + else + pCMS->m_aRealHeight.setX( + pOrig->Width() - + pCMS->m_aRealHeight.X() + + pCMS->m_aRealHeight.Y() ); + } + } + } + else + { + pOrig->Pos().AdjustY(aOldPos.Y() ); + if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + { + const SwTwips nPorWidth = pPor->Width() + + pPor->CalcSpacing( nSpaceAdd, aInf ); + const SwTwips nInsideOfst = pOrig->Pos().X(); + pOrig->Pos().setX( nX + nPorWidth - + nInsideOfst - pOrig->Width() ); + } + else + pOrig->Pos().AdjustX(nX ); + + if( static_cast<SwMultiPortion*>(pPor)->HasBrackets() ) + pOrig->Pos().AdjustX( + static_cast<SwDoubleLinePortion*>(pPor)->PreWidth() ); + } + + if( bSpaceChg ) + SwDoubleLinePortion::ResetSpaceAdd( m_pCurr ); + + m_pCurr = pOldCurr; + m_nStart = nOldStart; + m_nY = nOldY; + m_bPrev = false; + + return; + } + if ( pPor->PrtWidth() ) + { + TextFrameIndex const nOldLen = pPor->GetLen(); + pPor->SetLen( nOfst - aInf.GetIdx() ); + aInf.SetLen( pPor->GetLen() ); + if( nX || !pPor->InNumberGrp() ) + { + SeekAndChg( aInf ); + const bool bOldOnWin = aInf.OnWin(); + aInf.SetOnWin( false ); // no BULLETs! + SwTwips nTmp = nX; + aInf.SetKanaComp( pKanaComp ); + aInf.SetKanaIdx( nKanaIdx ); + nX += pPor->GetTextSize( aInf ).Width(); + aInf.SetOnWin( bOldOnWin ); + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nX += pPor->CalcSpacing( nSpaceAdd, aInf ); + if( bWidth ) + { + pPor->SetLen(pPor->GetLen() + TextFrameIndex(1)); + aInf.SetLen( pPor->GetLen() ); + aInf.SetOnWin( false ); // no BULLETs! + nTmp += pPor->GetTextSize( aInf ).Width(); + aInf.SetOnWin( bOldOnWin ); + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nTmp += pPor->CalcSpacing(nSpaceAdd, aInf); + pOrig->Width( nTmp - nX ); + } + } + pPor->SetLen( nOldLen ); + + // Shift the cursor with the right border width + // Note: nX remains positive because GetTextSize() also include the width of the right border + if( aInf.GetIdx() < nOfst && nOfst < aInf.GetIdx() + pPor->GetLen() ) + { + // Find the current drop portion part and use its right border + if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 ) + { + SwDropPortion* pDrop = static_cast<SwDropPortion*>(pPor); + const SwDropPortionPart* pCurrPart = pDrop->GetPart(); + TextFrameIndex nSumLength(0); + while( pCurrPart && (nSumLength += pCurrPart->GetLen()) < nOfst - aInf.GetIdx() ) + { + pCurrPart = pCurrPart->GetFollow(); + } + if( pCurrPart && nSumLength != nOfst - aInf.GetIdx() && + pCurrPart->GetFont().GetRightBorder() && !pCurrPart->GetJoinBorderWithNext() ) + { + nX -= pCurrPart->GetFont().GetRightBorderSpace(); + } + } + else if( GetInfo().GetFont()->GetRightBorder() && !pPor->GetJoinBorderWithNext()) + { + nX -= GetInfo().GetFont()->GetRightBorderSpace(); + } + } + } + bWidth = false; + break; + } + } + } + + if( pPor ) + { + OSL_ENSURE( !pPor->InNumberGrp() || bInsideFirstField, "Number surprise" ); + bool bEmptyField = false; + if( pPor->InFieldGrp() && pPor->GetLen() ) + { + SwFieldPortion *pTmp = static_cast<SwFieldPortion*>(pPor); + while( pTmp->HasFollow() && pTmp->GetExp().isEmpty() ) + { + sal_uInt16 nAddX = pTmp->Width(); + SwLinePortion *pNext = pTmp->GetNextPortion(); + while( pNext && !pNext->InFieldGrp() ) + { + OSL_ENSURE( !pNext->GetLen(), "Where's my field follow?" ); + nAddX = nAddX + pNext->Width(); + pNext = pNext->GetNextPortion(); + } + if( !pNext ) + break; + pTmp = static_cast<SwFieldPortion*>(pNext); + nPorHeight = pTmp->Height(); + nPorAscent = pTmp->GetAscent(); + nX += nAddX; + bEmptyField = true; + } + } + // 8513: Fields in justified text, skipped + while( pPor && !pPor->GetLen() && ! bInsideFirstField && + ( pPor->IsFlyPortion() || pPor->IsKernPortion() || + pPor->IsBlankPortion() || pPor->InTabGrp() || + ( !bEmptyField && pPor->InFieldGrp() ) ) ) + { + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nX += pPor->PrtWidth() + + pPor->CalcSpacing( nSpaceAdd, aInf ); + else + { + if( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) + { + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() ) + ++nKanaIdx; + } + if ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() && + !pPor->GetNextPortion()->IsMarginPortion() ) ) + nX += pPor->PrtWidth(); + } + if( pPor->IsMultiPortion() && + static_cast<SwMultiPortion*>(pPor)->HasTabulator() ) + { + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() ) + ++nKanaIdx; + } + if( !pPor->IsFlyPortion() ) + { + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + pPor = pPor->GetNextPortion(); + } + + if( aInf.GetIdx() == nOfst && pPor && pPor->InHyphGrp() && + pPor->GetNextPortion() && pPor->GetNextPortion()->InFixGrp() ) + { + // All special portions have to be skipped + // Taking the German word "zusammen" as example: zu-[FLY]sammen, 'u' == 19, 's' == 20; Right() + // Without the adjustment we end up in front of '-', with the + // adjustment in front of the 's'. + while( pPor && !pPor->GetLen() ) + { + nX += pPor->Width(); + if( !pPor->IsMarginPortion() ) + { + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + pPor = pPor->GetNextPortion(); + } + } + if( pPor && pCMS ) + { + if( pCMS->m_bFieldInfo && pPor->InFieldGrp() && pPor->Width() ) + pOrig->Width( pPor->Width() ); + if( pPor->IsDropPortion() ) + { + nPorAscent = static_cast<SwDropPortion*>(pPor)->GetDropHeight(); + // The drop height is only calculated, if we have more than + // one line. Otherwise it is 0. + if ( ! nPorAscent) + nPorAscent = pPor->Height(); + nPorHeight = nPorAscent; + pOrig->Height( nPorHeight + + static_cast<SwDropPortion*>(pPor)->GetDropDescent() ); + if( nTmpHeight < pOrig->Height() ) + { + nTmpAscent = nPorAscent; + nTmpHeight = sal_uInt16( pOrig->Height() ); + } + } + if( bWidth && pPor->PrtWidth() && pPor->GetLen() && + aInf.GetIdx() == nOfst ) + { + if( !pPor->IsFlyPortion() && pPor->Height() && + pPor->GetAscent() ) + { + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + SwTwips nTmp; + if (TextFrameIndex(2) > pPor->GetLen()) + { + nTmp = pPor->Width(); + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nTmp += pPor->CalcSpacing( nSpaceAdd, aInf ); + } + else + { + const bool bOldOnWin = aInf.OnWin(); + TextFrameIndex const nOldLen = pPor->GetLen(); + pPor->SetLen( TextFrameIndex(1) ); + aInf.SetLen( pPor->GetLen() ); + SeekAndChg( aInf ); + aInf.SetOnWin( false ); // no BULLETs! + aInf.SetKanaComp( pKanaComp ); + aInf.SetKanaIdx( nKanaIdx ); + nTmp = pPor->GetTextSize( aInf ).Width(); + aInf.SetOnWin( bOldOnWin ); + if ( pPor->InSpaceGrp() && nSpaceAdd ) + nTmp += pPor->CalcSpacing( nSpaceAdd, aInf ); + pPor->SetLen( nOldLen ); + } + pOrig->Width( nTmp ); + } + + // travel inside field portion? + if ( pCMS->m_pSpecialPos ) + { + // apply attributes to font + Seek( nOfst ); + lcl_GetCharRectInsideField( aInf, *pOrig, *pCMS, *pPor ); + } + } + } + + // special case: We are at the beginning of a BidiPortion or + // directly behind a BidiPortion + if ( pCMS && + ( pLastBidiPor || + ( pPor && + pPor->IsMultiPortion() && + static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ) ) + { + // we determine if the cursor has to blink before or behind + // the bidi portion + if ( pLastBidiPor ) + { + const sal_uInt8 nPortionLevel = pLastBidiPor->GetLevel(); + + if ( pCMS->m_nCursorBidiLevel >= nPortionLevel ) + { + // we came from inside the bidi portion, we want to blink + // behind the portion + pOrig->Pos().AdjustX( -nLastBidiPorWidth ); + + // Again, there is a special case: logically behind + // the portion can actually mean that the cursor is inside + // the portion. This can happen is the last portion + // inside the bidi portion is a nested bidi portion + SwLineLayout& rLineLayout = + static_cast<SwMultiPortion*>(pLastBidiPor)->GetRoot(); + + const SwLinePortion *pLast = rLineLayout.FindLastPortion(); + if ( pLast->IsMultiPortion() ) + { + OSL_ENSURE( static_cast<const SwMultiPortion*>(pLast)->IsBidi(), + "Non-BidiPortion inside BidiPortion" ); + TextFrameIndex const nIdx = aInf.GetIdx(); + // correct the index before using CalcSpacing. + aInf.SetIdx(nLastBidiIdx); + pOrig->Pos().AdjustX(pLast->Width() + + pLast->CalcSpacing( nSpaceAdd, aInf ) ); + aInf.SetIdx(nIdx); + } + } + } + else + { + const sal_uInt8 nPortionLevel = static_cast<SwBidiPortion*>(pPor)->GetLevel(); + + if ( pCMS->m_nCursorBidiLevel >= nPortionLevel ) + { + // we came from inside the bidi portion, we want to blink + // behind the portion + pOrig->Pos().AdjustX(pPor->Width() + + pPor->CalcSpacing( nSpaceAdd, aInf ) ); + } + } + } + + pOrig->Pos().AdjustX(nX ); + + if ( pCMS && pCMS->m_bRealHeight ) + { + nTmpAscent = AdjustBaseLine( *m_pCurr, nullptr, nPorHeight, nPorAscent ); + if ( nTmpAscent > nPorAscent ) + pCMS->m_aRealHeight.setX( nTmpAscent - nPorAscent ); + else + pCMS->m_aRealHeight.setX( 0 ); + OSL_ENSURE( nPorHeight, "GetCharRect: Missing Portion-Height" ); + if ( nTmpHeight > nPorHeight ) + pCMS->m_aRealHeight.setY( nPorHeight ); + else + pCMS->m_aRealHeight.setY( nTmpHeight ); + } + } +} + +void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst, + SwCursorMoveState* pCMS, const long nMax ) +{ + CharCursorToLine(nOfst); + + // Indicates that a position inside a special portion (field, number portion) + // is requested. + const bool bSpecialPos = pCMS && pCMS->m_pSpecialPos; + TextFrameIndex nFindOfst = nOfst; + + if ( bSpecialPos ) + { + const SwSPExtendRange nExtendRange = pCMS->m_pSpecialPos->nExtendRange; + + OSL_ENSURE( ! pCMS->m_pSpecialPos->nLineOfst || SwSPExtendRange::BEFORE != nExtendRange, + "LineOffset AND Number Portion?" ); + + // portions which are behind the string + if ( SwSPExtendRange::BEHIND == nExtendRange ) + ++nFindOfst; + + // skip lines for fields which cover more than one line + for ( sal_uInt16 i = 0; i < pCMS->m_pSpecialPos->nLineOfst; i++ ) + Next(); + } + + // If necessary, as catch up, do the adjustment + GetAdjusted(); + + const Point aCharPos( GetTopLeft() ); + + GetCharRect_( pOrig, nFindOfst, pCMS ); + + // This actually would have to be "-1 LogicToPixel", but that seems too + // expensive, so it's a value (-12), that should hopefully be OK. + const SwTwips nTmpRight = Right() - 12; + + pOrig->Pos().AdjustX(aCharPos.X() ); + pOrig->Pos().AdjustY(aCharPos.Y() ); + + if( pCMS && pCMS->m_b2Lines && pCMS->m_p2Lines ) + { + pCMS->m_p2Lines->aLine.Pos().AdjustX(aCharPos.X() ); + pCMS->m_p2Lines->aLine.Pos().AdjustY(aCharPos.Y() ); + pCMS->m_p2Lines->aPortion.Pos().AdjustX(aCharPos.X() ); + pCMS->m_p2Lines->aPortion.Pos().AdjustY(aCharPos.Y() ); + } + + const bool bTabOverMargin = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN); + // Make sure the cursor respects the right margin, unless in compat mode, where the tab size has priority over the margin size. + if( pOrig->Left() > nTmpRight && !bTabOverMargin) + pOrig->Pos().setX( nTmpRight ); + + if( nMax ) + { + if( pOrig->Top() + pOrig->Height() > nMax ) + { + if( pOrig->Top() > nMax ) + pOrig->Top( nMax ); + pOrig->Height( nMax - pOrig->Top() ); + } + if ( pCMS && pCMS->m_bRealHeight && pCMS->m_aRealHeight.Y() >= 0 ) + { + long nTmp = pCMS->m_aRealHeight.X() + pOrig->Top(); + if( nTmp >= nMax ) + { + pCMS->m_aRealHeight.setX( nMax - pOrig->Top() ); + pCMS->m_aRealHeight.setY( 0 ); + } + else if( nTmp + pCMS->m_aRealHeight.Y() > nMax ) + pCMS->m_aRealHeight.setY( nMax - nTmp ); + } + } + long nOut = pOrig->Right() - GetTextFrame()->getFrameArea().Right(); + if( nOut > 0 ) + { + if( GetTextFrame()->getFrameArea().Width() < GetTextFrame()->getFramePrintArea().Left() + + GetTextFrame()->getFramePrintArea().Width() ) + nOut += GetTextFrame()->getFrameArea().Width() - GetTextFrame()->getFramePrintArea().Left() + - GetTextFrame()->getFramePrintArea().Width(); + if( nOut > 0 ) + pOrig->Pos().AdjustX( -(nOut + 10) ); + } +} + +/** + * Determines if SwTextCursor::GetModelPositionForViewPoint() should consider the next portion when calculating the + * doc model position from a Point. + */ +static bool ConsiderNextPortionForCursorOffset(const SwLinePortion* pPor, sal_uInt16 nWidth30, sal_uInt16 nX) +{ + if (!pPor->GetNextPortion()) + { + return false; + } + + // If we're past the target position, stop the iteration in general. + // Exception: don't stop the iteration between as-char fly portions and their comments. + if (nWidth30 >= nX && (!pPor->IsFlyCntPortion() || !pPor->GetNextPortion()->IsPostItsPortion())) + { + return false; + } + + return !pPor->IsBreakPortion(); +} + +// Return: Offset in String +TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, const Point &rPoint, + bool bChgNode, SwCursorMoveState* pCMS ) const +{ + // If necessary, as catch up, do the adjustment + GetAdjusted(); + + const OUString &rText = GetInfo().GetText(); + TextFrameIndex nOffset(0); + + // x is the horizontal offset within the line. + SwTwips x = rPoint.X(); + const SwTwips nLeftMargin = GetLineStart(); + SwTwips nRightMargin = GetLineEnd() + + ( GetCurr()->IsHanging() ? GetCurr()->GetHangingMargin() : 0 ); + if( nRightMargin == nLeftMargin ) + nRightMargin += 30; + + const bool bLeftOver = x < nLeftMargin; + if( bLeftOver ) + x = nLeftMargin; + const bool bRightOver = x > nRightMargin; + if( bRightOver ) + x = nRightMargin; + + const bool bRightAllowed = pCMS && ( pCMS->m_eState == CursorMoveState::NONE ); + + // Until here everything in document coordinates. + x -= nLeftMargin; + + sal_uInt16 nX = sal_uInt16( x ); + + // If there are attribute changes in the line, search for the paragraph, + // in which nX is situated. + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + TextFrameIndex nCurrStart = m_nStart; + bool bHolePortion = false; + bool bLastHyph = false; + + std::deque<sal_uInt16> *pKanaComp = m_pCurr->GetpKanaComp(); + TextFrameIndex const nOldIdx = GetInfo().GetIdx(); + sal_uInt16 nSpaceIdx = 0; + size_t nKanaIdx = 0; + long nSpaceAdd = m_pCurr->IsSpaceAdd() ? m_pCurr->GetLLSpaceAdd( 0 ) : 0; + short nKanaComp = pKanaComp ? (*pKanaComp)[0] : 0; + + // nWidth is the width of the line, or the width of + // the paragraph with the font change, in which nX is situated. + + sal_uInt16 nWidth = pPor->Width(); + if ( m_pCurr->IsSpaceAdd() || pKanaComp ) + { + if ( pPor->InSpaceGrp() && nSpaceAdd ) + { + const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart ); + nWidth = nWidth + sal_uInt16( pPor->CalcSpacing( nSpaceAdd, GetInfo() ) ); + } + if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) || + ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() ) + ) + { + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if( pKanaComp ) + { + if ( nKanaIdx + 1 < pKanaComp->size() ) + nKanaComp = (*pKanaComp)[++nKanaIdx]; + else + nKanaComp = 0; + } + } + } + + sal_uInt16 nWidth30; + if ( pPor->IsPostItsPortion() ) + nWidth30 = 30 + pPor->GetViewWidth( GetInfo() ) / 2; + else + nWidth30 = ! nWidth && pPor->GetLen() && pPor->InToxRefOrFieldGrp() ? + 30 : + nWidth; + + while (ConsiderNextPortionForCursorOffset(pPor, nWidth30, nX)) + { + nX = nX - nWidth; + nCurrStart = nCurrStart + pPor->GetLen(); + bHolePortion = pPor->IsHolePortion(); + pPor = pPor->GetNextPortion(); + nWidth = pPor->Width(); + if ( m_pCurr->IsSpaceAdd() || pKanaComp ) + { + if ( pPor->InSpaceGrp() && nSpaceAdd ) + { + const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart ); + nWidth = nWidth + sal_uInt16( pPor->CalcSpacing( nSpaceAdd, GetInfo() ) ); + } + + if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) || + ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() ) + ) + { + if ( m_pCurr->IsSpaceAdd() ) + { + if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() ) + nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx ); + else + nSpaceAdd = 0; + } + + if ( pKanaComp ) + { + if( nKanaIdx + 1 < pKanaComp->size() ) + nKanaComp = (*pKanaComp)[++nKanaIdx]; + else + nKanaComp = 0; + } + } + } + + if ( pPor->IsPostItsPortion() ) + nWidth30 = 30 + pPor->GetViewWidth( GetInfo() ) / 2; + else + nWidth30 = ! nWidth && pPor->GetLen() && pPor->InToxRefOrFieldGrp() ? + 30 : + nWidth; + if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() ) + bLastHyph = pPor->InHyphGrp(); + } + + const bool bLastPortion = (nullptr == pPor->GetNextPortion()); + + if( nX==nWidth ) + { + SwLinePortion *pNextPor = pPor->GetNextPortion(); + while( pNextPor && pNextPor->InFieldGrp() && !pNextPor->Width() ) + { + nCurrStart = nCurrStart + pPor->GetLen(); + pPor = pNextPor; + if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() ) + bLastHyph = pPor->InHyphGrp(); + pNextPor = pPor->GetNextPortion(); + } + } + + const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nOldIdx ); + + TextFrameIndex nLength = pPor->GetLen(); + + const bool bFieldInfo = pCMS && pCMS->m_bFieldInfo; + + if( bFieldInfo && ( nWidth30 < nX || bRightOver || bLeftOver || + ( pPor->InNumberGrp() && !pPor->IsFootnoteNumPortion() ) || + ( pPor->IsMarginPortion() && nWidth > nX + 30 ) ) ) + pCMS->m_bPosCorr = true; + + // #i27615# + if (pCMS && pCMS->m_bInFrontOfLabel) + { + if (! (2 * nX < nWidth && pPor->InNumberGrp() && + !pPor->IsFootnoteNumPortion())) + pCMS->m_bInFrontOfLabel = false; + } + + // 7684: We are exactly ended up at their HyphPortion. It is our task to + // provide, that we end up in the String. + // 7993: If length = 0, then we must exit... + if( !nLength ) + { + if( pCMS ) + { + if( pPor->IsFlyPortion() && bFieldInfo ) + pCMS->m_bPosCorr = true; + + if (!bRightOver && nX) + { + if( pPor->IsFootnoteNumPortion()) + pCMS->m_bFootnoteNoInfo = true; + else if (pPor->InNumberGrp() ) // #i23726# + { + pCMS->m_nInNumPortionOffset = nX; + pCMS->m_bInNumPortion = true; + } + } + } + if( !nCurrStart ) + return TextFrameIndex(0); + + // 7849, 7816: pPor->GetHyphPortion is mandatory! + if( bHolePortion || ( !bRightAllowed && bLastHyph ) || + ( pPor->IsMarginPortion() && !pPor->GetNextPortion() && + // 46598: Consider the situation: We might end up behind the last character, + // in the last line of a centered paragraph + nCurrStart < TextFrameIndex(rText.getLength()))) + --nCurrStart; + else if( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->IsFollow() + && nWidth > nX ) + { + if( bFieldInfo ) + --nCurrStart; + else + { + sal_uInt16 nHeight = pPor->Height(); + if ( !nHeight || nHeight > nWidth ) + nHeight = nWidth; + if( bChgNode && nWidth - nHeight/2 > nX ) + --nCurrStart; + } + } + return nCurrStart; + } + if (TextFrameIndex(1) == nLength) + { + if ( nWidth ) + { + // no quick return for as-character frames, we want to peek inside + if (!(bChgNode && pPos && pPor->IsFlyCntPortion()) + // if we want to get the position inside the field, we should not return + && (!pCMS || !pCMS->m_pSpecialPos)) + { + if ( pPor->InFieldGrp() || + ( pPor->IsMultiPortion() && + static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ) + { + sal_uInt16 nHeight = 0; + if( !bFieldInfo ) + { + nHeight = pPor->Height(); + if ( !nHeight || nHeight > nWidth ) + nHeight = nWidth; + } + + if( nWidth - nHeight/2 <= nX && + ( ! pPor->InFieldGrp() || + !static_cast<SwFieldPortion*>(pPor)->HasFollow() ) ) + ++nCurrStart; + } + else if ( ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() && + !pPor->GetNextPortion()->IsMarginPortion() && + !pPor->GetNextPortion()->IsHolePortion() ) ) + && ( nWidth/2 < nX ) && + ( !bFieldInfo || + ( pPor->GetNextPortion() && + pPor->GetNextPortion()->IsPostItsPortion() ) ) + && ( bRightAllowed || !bLastHyph )) + ++nCurrStart; + + return nCurrStart; + } + } + else + { + if ( pPor->IsPostItsPortion() || pPor->IsBreakPortion() || + pPor->InToxRefGrp() ) + { + if (pPor->IsPostItsPortion()) + { + // Offset would be nCurrStart + nLength below, do the same for post-it portions. + nCurrStart += pPor->GetLen(); + } + return nCurrStart; + } + if ( pPor->InFieldGrp() ) + { + if( bRightOver && !static_cast<SwFieldPortion*>(pPor)->HasFollow() ) + ++nCurrStart; + return nCurrStart; + } + } + } + + // Skip space at the end of the line + if( bLastPortion && (m_pCurr->GetNext() || m_pFrame->GetFollow() ) + && rText[sal_Int32(nCurrStart + nLength) - 1] == ' ' ) + --nLength; + + if( nWidth > nX || + ( nWidth == nX && pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsDouble() ) ) + { + if( pPor->IsMultiPortion() ) + { + // In a multi-portion we use GetModelPositionForViewPoint()-function recursively + SwTwips nTmpY = rPoint.Y() - m_pCurr->GetAscent() + pPor->GetAscent(); + // if we are in the first line of a double line portion, we have + // to add a value to nTmpY for not staying in this line + // we also want to skip the first line, if we are inside ruby + if ( ( static_cast<SwTextSizeInfo*>(m_pInf)->IsMulti() && + static_cast<SwTextSizeInfo*>(m_pInf)->IsFirstMulti() ) || + ( static_cast<SwMultiPortion*>(pPor)->IsRuby() && + static_cast<SwMultiPortion*>(pPor)->OnTop() ) ) + nTmpY += static_cast<SwMultiPortion*>(pPor)->Height(); + + // Important for cursor traveling in ruby portions: + // We have to set nTmpY to 0 in order to stay in the first row + // if the phonetic line is the second row + if ( static_cast<SwMultiPortion*>(pPor)->IsRuby() && + ! static_cast<SwMultiPortion*>(pPor)->OnTop() ) + nTmpY = 0; + + SwTextCursorSave aSave( const_cast<SwTextCursor*>(this), static_cast<SwMultiPortion*>(pPor), + nTmpY, nX, nCurrStart, nSpaceAdd ); + + SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() ); + if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + { + const sal_uInt8 nBidiLevel = static_cast<SwBidiPortion*>(pPor)->GetLevel(); + aLayoutModeModifier.Modify( nBidiLevel % 2 ); + } + + if( static_cast<SwMultiPortion*>(pPor)->HasRotation() ) + { + nTmpY -= m_nY; + if( !static_cast<SwMultiPortion*>(pPor)->IsRevers() ) + nTmpY = pPor->Height() - nTmpY; + if( nTmpY < 0 ) + nTmpY = 0; + nX = static_cast<sal_uInt16>(nTmpY); + } + + if( static_cast<SwMultiPortion*>(pPor)->HasBrackets() ) + { + const sal_uInt16 nPreWidth = static_cast<SwDoubleLinePortion*>(pPor)->PreWidth(); + if ( nX > nPreWidth ) + nX = nX - nPreWidth; + else + nX = 0; + } + + return GetModelPositionForViewPoint( pPos, Point( GetLineStart() + nX, rPoint.Y() ), + bChgNode, pCMS ); + } + if( pPor->InTextGrp() ) + { + sal_uInt8 nOldProp; + if( GetPropFont() ) + { + const_cast<SwFont*>(GetFnt())->SetProportion( GetPropFont() ); + nOldProp = GetFnt()->GetPropr(); + } + else + nOldProp = 0; + { + SwTextSizeInfo aSizeInf( GetInfo(), &rText, nCurrStart ); + const_cast<SwTextCursor*>(this)->SeekAndChg( aSizeInf ); + SwTextSlot aDiffText( &aSizeInf, static_cast<SwTextPortion*>(pPor), false, false ); + SwFontSave aSave( aSizeInf, pPor->IsDropPortion() ? + static_cast<SwDropPortion*>(pPor)->GetFnt() : nullptr ); + + SwParaPortion* pPara = const_cast<SwParaPortion*>(GetInfo().GetParaPortion()); + OSL_ENSURE( pPara, "No paragraph!" ); + + SwDrawTextInfo aDrawInf( aSizeInf.GetVsh(), + *aSizeInf.GetOut(), + &pPara->GetScriptInfo(), + aSizeInf.GetText(), + aSizeInf.GetIdx(), + pPor->GetLen() ); + + // Drop portion works like a multi portion, just its parts are not portions + if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 ) + { + SwDropPortion* pDrop = static_cast<SwDropPortion*>(pPor); + const SwDropPortionPart* pCurrPart = pDrop->GetPart(); + sal_uInt16 nSumWidth = 0; + sal_uInt16 nSumBorderWidth = 0; + // Shift offset with the right and left border of previous parts and left border of actual one + while (pCurrPart && nSumWidth <= nX - sal_Int32(nCurrStart)) + { + nSumWidth += pCurrPart->GetWidth(); + if( pCurrPart->GetFont().GetLeftBorder() && !pCurrPart->GetJoinBorderWithPrev() ) + { + nSumBorderWidth += pCurrPart->GetFont().GetLeftBorderSpace(); + } + if (nSumWidth <= nX - sal_Int32(nCurrStart) && pCurrPart->GetFont().GetRightBorder() && + !pCurrPart->GetJoinBorderWithNext() ) + { + nSumBorderWidth += pCurrPart->GetFont().GetRightBorderSpace(); + } + pCurrPart = pCurrPart->GetFollow(); + } + nX = std::max(0, nX - nSumBorderWidth); + } + // Shift the offset with the left border width + else if( GetInfo().GetFont()->GetLeftBorder() && !pPor->GetJoinBorderWithPrev() ) + { + nX = std::max(0, nX - GetInfo().GetFont()->GetLeftBorderSpace()); + } + + aDrawInf.SetOffset( nX ); + + if ( nSpaceAdd ) + { + TextFrameIndex nCharCnt(0); + // #i41860# Thai justified alignment needs some + // additional information: + aDrawInf.SetNumberOfBlanks( pPor->InTextGrp() ? + static_cast<const SwTextPortion*>(pPor)->GetSpaceCnt( aSizeInf, nCharCnt ) : + TextFrameIndex(0) ); + } + + if ( pPor->InFieldGrp() && pCMS && pCMS->m_pSpecialPos ) + aDrawInf.SetLen( TextFrameIndex(COMPLETE_STRING) ); + + aDrawInf.SetSpace( nSpaceAdd ); + aDrawInf.SetFont( aSizeInf.GetFont() ); + aDrawInf.SetFrame( m_pFrame ); + aDrawInf.SetSnapToGrid( aSizeInf.SnapToGrid() ); + aDrawInf.SetPosMatchesBounds( pCMS && pCMS->m_bPosMatchesBounds ); + + if ( SwFontScript::CJK == aSizeInf.GetFont()->GetActual() && + pPara->GetScriptInfo().CountCompChg() && + ! pPor->InFieldGrp() ) + aDrawInf.SetKanaComp( nKanaComp ); + + nLength = aSizeInf.GetFont()->GetModelPositionForViewPoint_( aDrawInf ); + + // get position inside field portion? + if ( pPor->InFieldGrp() && pCMS && pCMS->m_pSpecialPos ) + { + pCMS->m_pSpecialPos->nCharOfst = sal_Int32(nLength); + nLength = TextFrameIndex(0); + } + + // set cursor bidi level + if ( pCMS ) + pCMS->m_nCursorBidiLevel = + aDrawInf.GetCursorBidiLevel(); + + if( bFieldInfo && nLength == pPor->GetLen() && + ( ! pPor->GetNextPortion() || + ! pPor->GetNextPortion()->IsPostItsPortion() ) ) + --nLength; + } + if( nOldProp ) + const_cast<SwFont*>(GetFnt())->SetProportion( nOldProp ); + } + else + { + sw::FlyContentPortion* pFlyPor(nullptr); + if(bChgNode && pPos && (pFlyPor = dynamic_cast<sw::FlyContentPortion*>(pPor))) + { + // JP 24.11.94: if the Position is not in Fly, then + // we many not return with COMPLETE_STRING as value! + // (BugId: 9692 + Change in feshview) + SwFlyInContentFrame *pTmp = pFlyPor->GetFlyFrame(); + SwFrame* pLower = pTmp->GetLower(); + bool bChgNodeInner = pLower + && (pLower->IsTextFrame() || pLower->IsLayoutFrame()); + Point aTmpPoint( rPoint ); + + if ( m_pFrame->IsRightToLeft() ) + m_pFrame->SwitchLTRtoRTL( aTmpPoint ); + + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchHorizontalToVertical( aTmpPoint ); + + if( bChgNodeInner && pTmp->getFrameArea().IsInside( aTmpPoint ) && + !( pTmp->IsProtected() ) ) + { + pFlyPor->GetFlyCursorOfst(aTmpPoint, *pPos, pCMS); + // After a change of the frame, our font must be still + // available for/in the OutputDevice. + // For comparison: Paint and new SwFlyCntPortion ! + static_cast<SwTextSizeInfo*>(m_pInf)->SelectFont(); + + // 6776: The pIter->GetModelPositionForViewPoint is returning here + // from a nesting with COMPLETE_STRING. + return TextFrameIndex(COMPLETE_STRING); + } + } + else + nLength = pPor->GetModelPositionForViewPoint( nX ); + } + } + nOffset = nCurrStart + nLength; + + // 7684: We end up in front of the HyphPortion. We must assure + // that we end up in the string. + // If we are at end of line in front of FlyFrames, we must proceed the same way. + if( nOffset && pPor->GetLen() == nLength && pPor->GetNextPortion() && + !pPor->GetNextPortion()->GetLen() && pPor->GetNextPortion()->InHyphGrp() ) + --nOffset; + + return nOffset; +} + +/** Looks for text portions which are inside the given rectangle + + For a rectangular text selection every text portions which is inside the given + rectangle has to be put into the SwSelectionList as SwPaM + From these SwPaM the SwCursors will be created. + + @param rSelList + The container for the overlapped text portions + + @param rRect + A rectangle in document coordinates, text inside this rectangle has to be + selected. + + @return [ true, false ] + true if any overlapping text portion has been found and put into list + false if no portion overlaps, the list has been unchanged +*/ +bool SwTextFrame::FillSelection( SwSelectionList& rSelList, const SwRect& rRect ) const +{ + bool bRet = false; + // GetPaintArea() instead getFrameArea() for negative indents + SwRect aTmpFrame( GetPaintArea() ); + if( !rRect.IsOver( aTmpFrame ) ) + return false; + if( rSelList.checkContext( this ) ) + { + SwRect aRect( aTmpFrame ); + aRect.Intersection( rRect ); + SwPosition aPosL( MapViewToModelPos(TextFrameIndex(0)) ); + if( IsEmpty() ) + { + SwPaM *pPam = new SwPaM( aPosL, aPosL ); + rSelList.insertPaM( pPam ); + } + else if( aRect.HasArea() ) + { + SwPosition aOld(aPosL.nNode.GetNodes().GetEndOfContent()); + SwPosition aPosR( aPosL ); + Point aPoint; + SwTextInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextIter aLine( const_cast<SwTextFrame*>(this), &aInf ); + // We have to care for top-to-bottom layout, where right becomes top etc. + SwRectFnSet aRectFnSet(this); + SwTwips nTop = aRectFnSet.GetTop(aRect); + SwTwips nBottom = aRectFnSet.GetBottom(aRect); + SwTwips nLeft = aRectFnSet.GetLeft(aRect); + SwTwips nRight = aRectFnSet.GetRight(aRect); + SwTwips nY = aLine.Y(); // Top position of the first line + SwTwips nLastY = nY; + while( nY < nTop && aLine.Next() ) // line above rectangle + { + nLastY = nY; + nY = aLine.Y(); + } + bool bLastLine = false; + if( nY < nTop && !aLine.GetNext() ) + { + bLastLine = true; + nY += aLine.GetLineHeight(); + } + do // check the lines for overlapping + { + if( nLastY < nTop ) // if the last line was above rectangle + nLastY = nTop; + if( nY > nBottom ) // if the current line leaves the rectangle + nY = nBottom; + if( nY >= nLastY ) // gotcha: overlapping + { + nLastY += nY; + nLastY /= 2; + if( aRectFnSet.IsVert() ) + { + aPoint.setX( nLastY ); + aPoint.setY( nLeft ); + } + else + { + aPoint.setX( nLeft ); + aPoint.setY( nLastY ); + } + // Looking for the position of the left border of the rectangle + // in this text line + SwCursorMoveState aState( CursorMoveState::UpDown ); + if( GetModelPositionForViewPoint( &aPosL, aPoint, &aState ) ) + { + if( aRectFnSet.IsVert() ) + { + aPoint.setX( nLastY ); + aPoint.setY( nRight ); + } + else + { + aPoint.setX( nRight ); + aPoint.setY( nLastY ); + } + // If we get a right position and if the left position + // is not the same like the left position of the line before + // which could happen e.g. for field portions or fly frames + // a SwPaM will be inserted with these positions + if( GetModelPositionForViewPoint( &aPosR, aPoint, &aState ) && + aOld != aPosL) + { + SwPaM *pPam = new SwPaM( aPosL, aPosR ); + rSelList.insertPaM( pPam ); + aOld = aPosL; + } + } + } + if( aLine.Next() ) + { + nLastY = nY; + nY = aLine.Y(); + } + else if( !bLastLine ) + { + bLastLine = true; + nLastY = nY; + nY += aLine.GetLineHeight(); + } + else + break; + }while( nLastY < nBottom ); + } + } + if( GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *GetDrawObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr ) + continue; + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pAnchoredObj); + if( pFly->IsFlyInContentFrame() && pFly->FillSelection( rSelList, rRect ) ) + bRet = true; + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx new file mode 100644 index 000000000..8d0cda953 --- /dev/null +++ b/sw/source/core/text/itrform2.cxx @@ -0,0 +1,2889 @@ +/* -*- 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 <hintids.hxx> + +#include <memory> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <editeng/lspcitem.hxx> +#include <txtflcnt.hxx> +#include <txtftn.hxx> +#include <flyfrms.hxx> +#include <fmtflcnt.hxx> +#include <fmtftn.hxx> +#include <ftninfo.hxx> +#include <charfmt.hxx> +#include <editeng/charrotateitem.hxx> +#include <layfrm.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <paratr.hxx> +#include "itrform2.hxx" +#include "porrst.hxx" +#include "portab.hxx" +#include "porfly.hxx" +#include "portox.hxx" +#include "porref.hxx" +#include "porfld.hxx" +#include "porftn.hxx" +#include "porhyph.hxx" +#include "pordrop.hxx" +#include "redlnitr.hxx" +#include <pagefrm.hxx> +#include <tgrditem.hxx> +#include <doc.hxx> +#include "pormulti.hxx" +#include <unotools/charclass.hxx> +#include <xmloff/odffields.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IMark.hxx> +#include <IDocumentMarkAccess.hxx> + +#include <vector> + +using namespace ::com::sun::star; + +namespace { + //! Calculates and sets optimal repaint offset for the current line + long lcl_CalcOptRepaint( SwTextFormatter &rThis, + SwLineLayout const &rCurr, + TextFrameIndex nOldLineEnd, + const std::vector<long> &rFlyStarts ); + //! Determine if we need to build hidden portions + bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos); + + // Check whether the two font has the same border + bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond); +} + +static void ClearFly( SwTextFormatInfo &rInf ) +{ + delete rInf.GetFly(); + rInf.SetFly(nullptr); +} + +void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf ) +{ + CtorInitTextPainter( pNewFrame, pNewInf ); + m_pInf = pNewInf; + m_pDropFormat = GetInfo().GetDropFormat(); + m_pMulti = nullptr; + + m_bOnceMore = false; + m_bFlyInContentBase = false; + m_bTruncLines = false; + m_nContentEndHyph = 0; + m_nContentMidHyph = 0; + m_nLeftScanIdx = TextFrameIndex(COMPLETE_STRING); + m_nRightScanIdx = TextFrameIndex(0); + m_pByEndIter.reset(); + m_pFirstOfBorderMerge = nullptr; + + if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength())) + { + OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" ); + m_nStart = TextFrameIndex(GetInfo().GetText().getLength()); + } + +} + +SwTextFormatter::~SwTextFormatter() +{ + // Extremely unlikely, but still possible + // e.g.: field splits up, widows start to matter + if( GetInfo().GetRest() ) + { + delete GetInfo().GetRest(); + GetInfo().SetRest(nullptr); + } +} + +void SwTextFormatter::Insert( SwLineLayout *pLay ) +{ + // Insert BEHIND the current element + if ( m_pCurr ) + { + pLay->SetNext( m_pCurr->GetNext() ); + m_pCurr->SetNext( pLay ); + } + else + m_pCurr = pLay; +} + +sal_uInt16 SwTextFormatter::GetFrameRstHeight() const +{ + // We want the rest height relative to the page. + // If we're in a table, then pFrame->GetUpper() is not the page. + + // GetFrameRstHeight() is being called with Footnote. + // Wrong: const SwFrame *pUpper = pFrame->GetUpper(); + const SwFrame *pPage = m_pFrame->FindPageFrame(); + const SwTwips nHeight = pPage->getFrameArea().Top() + + pPage->getFramePrintArea().Top() + + pPage->getFramePrintArea().Height() - Y(); + if( 0 > nHeight ) + return m_pCurr->Height(); + else + return sal_uInt16( nHeight ); +} + +SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) +{ + // Save values and initialize rInf + SwLinePortion *pUnderflow = rInf.GetUnderflow(); + if( !pUnderflow ) + return nullptr; + + // We format backwards, i.e. attribute changes can happen the next + // line again. + // Can be seen in 8081.sdw, if you enter text in the first line + + TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos(); + TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos(); + + // Save flys and set to 0, or else segmentation fault + // Not ClearFly(rInf) ! + SwFlyPortion *pFly = rInf.GetFly(); + rInf.SetFly( nullptr ); + + FeedInf( rInf ); + rInf.SetLast( m_pCurr ); + // pUnderflow does not need to be deleted, because it will drown in the following + // Truncate() + rInf.SetUnderflow(nullptr); + rInf.SetSoftHyphPos( nSoftHyphPos ); + rInf.SetUnderScorePos( nUnderScorePos ); + rInf.SetPaintOfst( GetLeftMargin() ); + + // We look for the portion with the under-flow position + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + if( pPor != pUnderflow ) + { + // pPrev will be the last portion before pUnderflow, + // which still has a real width. + // Exception: SoftHyphPortion must not be forgotten, of course! + // Although they don't have a width. + SwLinePortion *pTmpPrev = pPor; + while( pPor && pPor != pUnderflow ) + { + if( !pPor->IsKernPortion() && + ( pPor->Width() || pPor->IsSoftHyphPortion() ) ) + { + while( pTmpPrev != pPor ) + { + pTmpPrev->Move( rInf ); + rInf.SetLast( pTmpPrev ); + pTmpPrev = pTmpPrev->GetNextPortion(); + OSL_ENSURE( pTmpPrev, "Underflow: losing control!" ); + }; + } + pPor = pPor->GetNextPortion(); + } + pPor = pTmpPrev; + if( pPor && // Skip flys and initials when underflow. + ( pPor->IsFlyPortion() || pPor->IsDropPortion() || + pPor->IsFlyCntPortion() ) ) + { + pPor->Move( rInf ); + rInf.SetLast( pPor ); + rInf.SetStopUnderflow( true ); + pPor = pUnderflow; + } + } + + // What? The under-flow portion is not in the portion chain? + OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" ); + + // Snapshot + if ( pPor==rInf.GetLast() ) + { + // We end up here, if the portion triggering the under-flow + // spans over the whole line. E.g. if a word spans across + // multiple lines and flows into a fly in the second line. + rInf.SetFly( pFly ); + pPor->Truncate(); + return pPor; // Is that enough? + } + // End the snapshot + + // X + Width == 0 with SoftHyph > Line?! + if( !pPor || !(rInf.X() + pPor->Width()) ) + { + delete pFly; + return nullptr; + } + + // Preparing for Format() + // We need to chip off the chain behind pLast, because we Insert after the Format() + SeekAndChg( rInf ); + + // line width is adjusted, so that pPor does not fit to current + // line anymore + rInf.Width( static_cast<sal_uInt16>(rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0)) ); + rInf.SetLen( pPor->GetLen() ); + rInf.SetFull( false ); + if( pFly ) + { + // We need to recalculate the FlyPortion due to the following reason: + // If the base line is lowered by a big font in the middle of the line, + // causing overlapping with a fly, the FlyPortion has a wrong size/fixed + // size. + rInf.SetFly( pFly ); + CalcFlyWidth( rInf ); + } + rInf.GetLast()->SetNextPortion(nullptr); + + // The SwLineLayout is an exception to this, which splits at the first + // portion change. + // Here only the other way around: + if( rInf.GetLast() == m_pCurr ) + { + if( pPor->InTextGrp() && !pPor->InExpGrp() ) + { + const PortionType nOldWhich = m_pCurr->GetWhichPor(); + *static_cast<SwLinePortion*>(m_pCurr) = *pPor; + m_pCurr->SetNextPortion( pPor->GetNextPortion() ); + m_pCurr->SetWhichPor( nOldWhich ); + pPor->SetNextPortion( nullptr ); + delete pPor; + pPor = m_pCurr; + } + } + + // Make sure that m_pFirstOfBorderMerge does not point to a portion which + // will be deleted by Truncate() below. + SwLinePortion* pNext = pPor->GetNextPortion(); + while (pNext) + { + if (pNext == m_pFirstOfBorderMerge) + { + m_pFirstOfBorderMerge = nullptr; + break; + } + pNext = pNext->GetNextPortion(); + } + pPor->Truncate(); + SwLinePortion *const pRest( rInf.GetRest() ); + if (pRest && pRest->InFieldGrp() && + static_cast<SwFieldPortion*>(pRest)->IsNoLength()) + { + // HACK: decrement again, so we pick up the suffix in next line! + m_pByEndIter->PrevAttr(); + } + delete pRest; + rInf.SetRest(nullptr); + return pPor; +} + +void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf, + SwLinePortion *pPor ) +{ + SwLinePortion *pLast = nullptr; + // The new portion is inserted, but everything's different for + // LineLayout... + if( pPor == m_pCurr ) + { + if ( m_pCurr->GetNextPortion() ) + { + pLast = pPor; + pPor = m_pCurr->GetNextPortion(); + } + + // i#112181 - Prevent footnote anchor being wrapped to next line + // without preceding word + rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() ); + } + else + { + pLast = rInf.GetLast(); + if( pLast->GetNextPortion() ) + { + while( pLast->GetNextPortion() ) + pLast = pLast->GetNextPortion(); + rInf.SetLast( pLast ); + } + pLast->Insert( pPor ); + + rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() ); + + // Adjust maxima + if( m_pCurr->Height() < pPor->Height() ) + m_pCurr->Height( pPor->Height() ); + if( m_pCurr->GetAscent() < pPor->GetAscent() ) + m_pCurr->SetAscent( pPor->GetAscent() ); + + if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY)) + { + // For DOCX with compat=14 the only shape in line defines height of the line inspite of used font + if (pLast->IsFlyCntPortion() && pPor->IsTextPortion() && pPor->GetLen() == TextFrameIndex(0)) + { + m_pCurr->SetAscent(pLast->GetAscent()); + m_pCurr->Height(pLast->Height()); + } + } + } + + // Sometimes chains are constructed (e.g. by hyphenate) + rInf.SetLast( pPor ); + while( pPor ) + { + if (!pPor->IsDropPortion()) + MergeCharacterBorder(*pPor, pLast, rInf); + + pPor->Move( rInf ); + rInf.SetLast( pPor ); + pLast = pPor; + pPor = pPor->GetNextPortion(); + } +} + +void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) +{ + OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING, + "SwTextFormatter::BuildPortions: bad text length in info" ); + + rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() ); + + // First NewTextPortion() decides whether pCurr ends up in pPor. + // We need to make sure that the font is being set in any case. + // This is done automatically in CalcAscent. + rInf.SetLast( m_pCurr ); + rInf.ForcedLeftMargin( 0 ); + + OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" ); + + if( !m_pCurr->GetAscent() && !m_pCurr->Height() ) + CalcAscent( rInf, m_pCurr ); + + SeekAndChg( rInf ); + + // Width() is shortened in CalcFlyWidth if we have a FlyPortion + OSL_ENSURE( !rInf.X() || m_pMulti, "SwTextFormatter::BuildPortion X=0?" ); + CalcFlyWidth( rInf ); + SwFlyPortion *pFly = rInf.GetFly(); + if( pFly ) + { + if ( 0 < pFly->GetFix() ) + ClearFly( rInf ); + else + rInf.SetFull(true); + } + + SwLinePortion *pPor = NewPortion( rInf ); + + // Asian grid stuff + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + const bool bHasGrid = pGrid && rInf.SnapToGrid() && + GRID_LINES_CHARS == pGrid->GetGridType(); + + + const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); + const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0; + + // used for grid mode only: + // the pointer is stored, because after formatting of non-asian text, + // the width of the kerning portion has to be adjusted + // Inserting a SwKernPortion before a SwTabPortion isn't necessary + // and will break the SwTabPortion. + SwKernPortion* pGridKernPortion = nullptr; + + bool bFull = false; + SwTwips nUnderLineStart = 0; + rInf.Y( Y() ); + + while( pPor && !rInf.IsStop() ) + { + OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) && + rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()), + "SwTextFormatter::BuildPortions: bad length in info" ); + + // We have to check the script for fields in order to set the + // correct nActual value for the font. + if( pPor->InFieldGrp() ) + static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf ); + + if( ! bHasGrid && rInf.HasScriptSpace() && + rInf.GetLast() && rInf.GetLast()->InTextGrp() && + rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() ) + { + SwFontScript nNxtActual = rInf.GetFont()->GetActual(); + SwFontScript nLstActual = nNxtActual; + sal_uInt16 nLstHeight = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()); + bool bAllowBehind = false; + const CharClass& rCC = GetAppCharClass(); + + // are there any punctuation characters on both sides + // of the kerning portion? + if ( pPor->InFieldGrp() ) + { + OUString aAltText; + if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) && + !aAltText.isEmpty() ) + { + bAllowBehind = rCC.isLetterNumeric( aAltText, 0 ); + + const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont(); + if ( pTmpFnt ) + nNxtActual = pTmpFnt->GetActual(); + } + } + else + { + const OUString& rText = rInf.GetText(); + sal_Int32 nIdx = sal_Int32(rInf.GetIdx()); + bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx); + } + + const SwLinePortion* pLast = rInf.GetLast(); + if ( bAllowBehind && pLast ) + { + bool bAllowBefore = false; + + if ( pLast->InFieldGrp() ) + { + OUString aAltText; + if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) && + !aAltText.isEmpty() ) + { + bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 ); + + const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont(); + if ( pTmpFnt ) + { + nLstActual = pTmpFnt->GetActual(); + nLstHeight = static_cast<sal_uInt16>(pTmpFnt->GetHeight()); + } + } + } + else if ( rInf.GetIdx() ) + { + bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1); + // Note: ScriptType returns values in [1,4] + if ( bAllowBefore ) + nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1); + } + + nLstHeight /= 5; + // does the kerning portion still fit into the line? + if( bAllowBefore && ( nLstActual != nNxtActual ) && + nLstHeight && rInf.X() + nLstHeight <= rInf.Width() && + ! pPor->InTabGrp() ) + { + SwKernPortion* pKrn = + new SwKernPortion( *rInf.GetLast(), nLstHeight, + pLast->InFieldGrp() && pPor->InFieldGrp() ); + rInf.GetLast()->SetNextPortion( nullptr ); + InsertPortion( rInf, pKrn ); + } + } + } + else if ( bHasGrid && pGrid->IsSnapToChars() && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) + { + // insert a grid kerning portion + pGridKernPortion = pPor->IsKernPortion() ? + static_cast<SwKernPortion*>(pPor) : + new SwKernPortion( *m_pCurr ); + + // if we have a new GridKernPortion, we initially calculate + // its size so that its ends on the grid + const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); + const SwLayoutFrame* pBody = pPageFrame->FindBodyCont(); + SwRectFnSet aRectFnSet(pPageFrame); + + const long nGridOrigin = pBody ? + aRectFnSet.GetPrtLeft(*pBody) : + aRectFnSet.GetPrtLeft(*pPageFrame); + + SwTwips nStartX = rInf.X() + GetLeftMargin(); + if ( aRectFnSet.IsVert() ) + { + Point aPoint( nStartX, 0 ); + m_pFrame->SwitchHorizontalToVertical( aPoint ); + nStartX = aPoint.Y(); + } + + const SwTwips nOfst = nStartX - nGridOrigin; + if ( nOfst ) + { + const sal_uLong i = ( nOfst > 0 ) ? + ( ( nOfst - 1 ) / nGridWidth + 1 ) : + 0; + const SwTwips nKernWidth = i * nGridWidth - nOfst; + const SwTwips nRestWidth = rInf.Width() - rInf.X(); + + if ( nKernWidth <= nRestWidth ) + pGridKernPortion->Width( static_cast<sal_uInt16>(nKernWidth) ); + } + + if ( pGridKernPortion != pPor ) + InsertPortion( rInf, pGridKernPortion ); + } + + if( pPor->IsDropPortion() ) + MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor)); + + // the multi-portion has its own format function + if( pPor->IsMultiPortion() && ( !m_pMulti || m_pMulti->IsBidi() ) ) + bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) ); + else + bFull = pPor->Format( rInf ); + + if( rInf.IsRuby() && !rInf.GetRest() ) + bFull = true; + + // if we are underlined, we store the beginning of this underlined + // segment for repaint optimization + if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart ) + nUnderLineStart = GetLeftMargin() + rInf.X(); + + if ( pPor->IsFlyPortion() ) + m_pCurr->SetFly( true ); + // some special cases, where we have to take care for the repaint + // offset: + // 1. Underlined portions due to special underline feature + // 2. Right Tab + // 3. BidiPortions + // 4. other Multiportions + // 5. DropCaps + // 6. Grid Mode + else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) && + // 1. Underlined portions + nUnderLineStart && + // reformat is at end of an underlined portion and next portion + // is not underlined + ( ( rInf.GetReformatStart() == rInf.GetIdx() && + LINESTYLE_NONE == m_pFont->GetUnderline() + ) || + // reformat is inside portion and portion is underlined + ( rInf.GetReformatStart() >= rInf.GetIdx() && + rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() && + LINESTYLE_NONE != m_pFont->GetUnderline() ) ) ) + rInf.SetPaintOfst( nUnderLineStart ); + else if ( ! rInf.GetPaintOfst() && + // 2. Right Tab + ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) || + // 3. BidiPortions + ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) || + // 4. Multi Portion and 5. Drop Caps + ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) && + rInf.GetReformatStart() >= rInf.GetIdx() && + rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() ) + // 6. Grid Mode + || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() ) + ) + ) + // we store the beginning of the critical portion as our + // paint offset + rInf.SetPaintOfst( GetLeftMargin() + rInf.X() ); + + // under one of these conditions we are allowed to delete the + // start of the underline portion + if ( IsUnderlineBreak( *pPor, *m_pFont ) ) + nUnderLineStart = 0; + + if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() && + static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) ) + SetFlyInCntBase(); + // bUnderflow needs to be reset or we wrap again at the next softhyphen + if ( !bFull ) + { + rInf.ClrUnderflow(); + if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() && + pPor->GetLen() && !pPor->InFieldGrp() ) + { + // The distance between two different scripts is set + // to 20% of the fontheight. + TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); + if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1)) && + nTmp != TextFrameIndex(rInf.GetText().getLength()) && + (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN || + m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) ) + { + const sal_uInt16 nDist = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()/5); + + if( nDist ) + { + // we do not want a kerning portion if any end + // would be a punctuation character + const CharClass& rCC = GetAppCharClass(); + if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1) + && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp))) + { + // does the kerning portion still fit into the line? + if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() ) + new SwKernPortion( *pPor, nDist ); + else + bFull = true; + } + } + } + } + } + + if ( bHasGrid && pGrid->IsSnapToChars() && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) + { + TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); + const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width(); + + const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() ); + const SwFontScript nNextScript = + nTmp >= TextFrameIndex(rInf.GetText().getLength()) + ? SwFontScript::CJK + : m_pScriptInfo->WhichFont(nTmp); + + // snap non-asian text to grid if next portion is ASIAN or + // there are no more portions in this line + // be careful when handling an underflow event: the gridkernportion + // could have been deleted + if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript && + ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) ) + { + OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" ); + + // calculate size + SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion(); + sal_uInt16 nSumWidth = pPor->Width(); + while ( pTmpPor ) + { + nSumWidth = nSumWidth + pTmpPor->Width(); + pTmpPor = pTmpPor->GetNextPortion(); + } + + const SwTwips i = nSumWidth ? + ( nSumWidth - 1 ) / nGridWidth + 1 : + 0; + const SwTwips nTmpWidth = i * nGridWidth; + const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth); + const sal_uInt16 nKernWidth_1 = static_cast<sal_uInt16>(nKernWidth / 2); + + OSL_ENSURE( nKernWidth <= nRestWidth, + "Not enough space left for adjusting non-asian text in grid mode" ); + + pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 ); + rInf.X( rInf.X() + nKernWidth_1 ); + + if ( ! bFull ) + new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1), + false, true ); + + pGridKernPortion = nullptr; + } + else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() || + pPor->IsFlyCntPortion() || pPor->InNumberGrp() || + pPor->InFieldGrp() || nCurrScript != nNextScript ) + // next portion should snap to grid + pGridKernPortion = nullptr; + } + + rInf.SetFull( bFull ); + + // Restportions from fields with multiple lines don't yet have the right ascent + if ( !pPor->GetLen() && !pPor->IsFlyPortion() + && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp() + && !pPor->IsMultiPortion() ) + CalcAscent( rInf, pPor ); + + InsertPortion( rInf, pPor ); + if (pPor->IsMultiPortion() && (!m_pMulti || m_pMulti->IsBidi())) + { + (void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion! + } + pPor = NewPortion( rInf ); + } + + if( !rInf.IsStop() ) + { + // The last right centered, decimal tab + SwTabPortion *pLastTab = rInf.GetLastTab(); + if( pLastTab ) + pLastTab->FormatEOL( rInf ); + else if( rInf.GetLast() && rInf.LastKernPortion() ) + rInf.GetLast()->FormatEOL( rInf ); + } + if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp() + && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() ) + rInf.SetNumDone( false ); + + // Delete fly in any case + ClearFly( rInf ); + + // Reinit the tab overflow flag after the line + rInf.SetTabOverflow( false ); +} + +void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent ) +{ + if( SvxAdjust::Left != GetAdjust() && !m_pMulti) + { + pCurrent->SetFormatAdj(true); + if( IsFlyInCntBase() ) + { + CalcAdjLine( pCurrent ); + // For e.g. centered fly we need to switch the RefPoint + // That's why bAlways = true + UpdatePos( pCurrent, GetTopLeft(), GetStart(), true ); + } + } +} + +void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor ) +{ + bool bCalc = false; + if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() ) + { + // Numbering + InterNetFields can keep an own font, then their size is + // independent from hard attribute values + SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get(); + SwFontSave aSave( rInf, pFieldFnt ); + pPor->Height( rInf.GetTextHeight() ); + pPor->SetAscent( rInf.GetAscent() ); + bCalc = true; + } + // i#89179 + // tab portion representing the list tab of a list label gets the + // same height and ascent as the corresponding number portion + else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) && + rInf.GetLast() && rInf.GetLast()->InNumberGrp() && + static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() ) + { + const SwLinePortion* pLast = rInf.GetLast(); + pPor->Height( pLast->Height() ); + pPor->SetAscent( pLast->GetAscent() ); + } + else if (pPor->GetWhichPor() == PortionType::Bookmark + && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength())) + { + // bookmark at end of paragraph: *don't* advance iterator, use the + // current font instead; it's possible that there's a font size on the + // paragraph and it's overridden on the last line of the paragraph and + // we don't want to apply it via SwBookmarkPortion and grow the line + // height (example: n758883.docx) + SwLinePortion const*const pLast = rInf.GetLast(); + assert(pLast); + pPor->Height( pLast->Height() ); + pPor->SetAscent( pLast->GetAscent() ); + } + else + { + const SwLinePortion *pLast = rInf.GetLast(); + bool bChg = false; + + // In empty lines the attributes are switched on via SeekStart + const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx(); + if ( pPor->IsQuoVadisPortion() ) + bChg = SeekStartAndChg( rInf, true ); + else + { + if( bFirstPor ) + { + if( !rInf.GetText().isEmpty() ) + { + if ( pPor->GetLen() || !rInf.GetIdx() + || ( m_pCurr != pLast && !pLast->IsFlyPortion() ) + || !m_pCurr->IsRest() ) // instead of !rInf.GetRest() + bChg = SeekAndChg( rInf ); + else + bChg = SeekAndChgBefore( rInf ); + } + else if ( m_pMulti ) + // do not open attributes starting at 0 in empty multi + // portions (rotated numbering followed by a footnote + // can cause trouble, because the footnote attribute + // starts at 0, but if we open it, the attribute handler + // cannot handle it. + bChg = false; + else + bChg = SeekStartAndChg( rInf ); + } + else + bChg = SeekAndChg( rInf ); + } + if( bChg || bFirstPor || !pPor->GetAscent() + || !rInf.GetLast()->InTextGrp() ) + { + pPor->SetAscent( rInf.GetAscent() ); + pPor->Height( rInf.GetTextHeight() ); + bCalc = true; + } + else + { + pPor->Height( pLast->Height() ); + pPor->SetAscent( pLast->GetAscent() ); + } + } + + if( pPor->InTextGrp() && bCalc ) + { + pPor->SetAscent(pPor->GetAscent() + + rInf.GetFont()->GetTopBorderSpace()); + pPor->Height(pPor->Height() + + rInf.GetFont()->GetTopBorderSpace() + + rInf.GetFont()->GetBottomBorderSpace() ); + } +} + +namespace { + +class SwMetaPortion : public SwTextPortion +{ +public: + SwMetaPortion() { SetWhichPor( PortionType::Meta ); } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +} + +void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if ( Width() ) + { + rInf.DrawViewOpt( *this, PortionType::Meta ); + SwTextPortion::Paint( rInf ); + } +} + +namespace sw::mark { + OUString ExpandFieldmark(IFieldmark* pBM) + { + const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters(); + sal_Int32 nCurrentIdx = 0; + const IFieldmark::parameter_map_t::const_iterator pResult = pParameters->find(OUString(ODF_FORMDROPDOWN_RESULT)); + if(pResult != pParameters->end()) + pResult->second >>= nCurrentIdx; + + const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(OUString(ODF_FORMDROPDOWN_LISTENTRY)); + if (pListEntries != pParameters->end()) + { + uno::Sequence< OUString > vListEntries; + pListEntries->second >>= vListEntries; + if (nCurrentIdx < vListEntries.getLength()) + return vListEntries[nCurrentIdx]; + } + + static const sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194}; + return OUString(vEnSpaces, ODF_FORMFIELD_DEFAULT_LENGTH); + } +} + +SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const +{ + SwTextPortion *pPor = nullptr; + if( GetFnt()->IsTox() ) + { + pPor = new SwToxPortion; + } + else if ( GetFnt()->IsInputField() ) + { + pPor = new SwTextInputFieldPortion(); + } + else + { + if( GetFnt()->IsRef() ) + pPor = new SwRefPortion; + else if (GetFnt()->IsMeta()) + { + pPor = new SwMetaPortion; + } + else + { + // Only at the End! + // If pCurr does not have a width, it can however already have content. + // E.g. for non-displayable characters + + auto const ch(rInf.GetText()[sal_Int32(rInf.GetIdx())]); + SwTextFrame const*const pFrame(rInf.GetTextFrame()); + SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx())); + sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition); + if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE) + { + if (ch == CH_TXT_ATR_FIELDSTART) + pPor = new SwFieldFormDatePortion(pBM, true); + else if (ch == CH_TXT_ATR_FIELDSEP) + pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark? + else if (ch == CH_TXT_ATR_FIELDEND) + pPor = new SwFieldFormDatePortion(pBM, false); + } + else if (ch == CH_TXT_ATR_FIELDSTART) + pPor = new SwFieldMarkPortion(); + else if (ch == CH_TXT_ATR_FIELDSEP) + pPor = new SwFieldMarkPortion(); + else if (ch == CH_TXT_ATR_FIELDEND) + pPor = new SwFieldMarkPortion(); + else if (ch == CH_TXT_ATR_FORMELEMENT) + { + OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???"); + if (pBM != nullptr) + { + if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX) + { + pPor = new SwFieldFormCheckboxPortion(); + } + else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN) + { + pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM)); + } + /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT. + * Otherwise file will crash on open. + */ + else if (pBM->GetFieldname( ) == ODF_FORMTEXT) + { + pPor = new SwFieldMarkPortion(); + } + } + } + if( !pPor ) + { + if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() ) + pPor = m_pCurr; + else + pPor = new SwTextPortion; + } + } + } + return pPor; +} + +// We calculate the length, the following portion limits are defined: +// 1) Tabs +// 2) Linebreaks +// 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD +// 4) next attribute change + +SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf ) +{ + // If we're at the line's beginning, we take pCurr + // If pCurr is not derived from SwTextPortion, we need to duplicate + Seek( rInf.GetIdx() ); + SwTextPortion *pPor = WhichTextPor( rInf ); + + // until next attribute change: + const TextFrameIndex nNextAttr = GetNextAttr(); + TextFrameIndex nNextChg = std::min(nNextAttr, TextFrameIndex(rInf.GetText().getLength())); + + // end of script type: + const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx()); + nNextChg = std::min( nNextChg, nNextScript ); + + // end of direction: + const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx()); + nNextChg = std::min( nNextChg, nNextDir ); + + // hidden change (potentially via bookmark): + const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx()); + nNextChg = std::min( nNextChg, nNextHidden ); + + // bookmarks + const TextFrameIndex nNextBookmark = m_pScriptInfo->NextBookmark(rInf.GetIdx()); + nNextChg = std::min(nNextChg, nNextBookmark); + + // Turbo boost: + // We assume that font characters are not larger than twice + // as wide as height. + // Very crazy: we need to take the ascent into account. + + // Mind the trap! GetSize() contains the wished-for height, the real height + // is only known in CalcAscent! + + // The ratio is even crazier: a blank in Times New Roman has an ascent of + // 182, a height of 200 and a width of 53! + // It follows that a line with a lot of blanks is processed incorrectly. + // Therefore we increase from factor 2 to 8 (due to negative kerning). + + pPor->SetLen(TextFrameIndex(1)); + CalcAscent( rInf, pPor ); + + const SwFont* pTmpFnt = rInf.GetFont(); + sal_Int32 nExpect = std::min( sal_Int32( pTmpFnt->GetHeight() ), + sal_Int32( pPor->GetAscent() ) ) / 8; + if ( !nExpect ) + nExpect = 1; + nExpect = sal_Int32(rInf.GetIdx()) + (rInf.GetLineWidth() / nExpect); + if (TextFrameIndex(nExpect) > rInf.GetIdx() && nNextChg > TextFrameIndex(nExpect)) + nNextChg = TextFrameIndex(std::min(nExpect, rInf.GetText().getLength())); + + // we keep an invariant during method calls: + // there are no portion ending characters like hard spaces + // or tabs in [ nLeftScanIdx, nRightScanIdx ] + if ( m_nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= m_nRightScanIdx ) + { + if ( nNextChg > m_nRightScanIdx ) + nNextChg = m_nRightScanIdx = + rInf.ScanPortionEnd( m_nRightScanIdx, nNextChg ); + } + else + { + m_nLeftScanIdx = rInf.GetIdx(); + nNextChg = m_nRightScanIdx = + rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg ); + } + + pPor->SetLen( nNextChg - rInf.GetIdx() ); + rInf.SetLen( pPor->GetLen() ); + return pPor; +} + +// first portions have no length +SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf) +{ + SwLinePortion *pPor = nullptr; + + if( rInf.GetRest() ) + { + // Tabs and fields + if( '\0' != rInf.GetHookChar() ) + return nullptr; + + pPor = rInf.GetRest(); + if( pPor->IsErgoSumPortion() ) + rInf.SetErgoDone(true); + else + if( pPor->IsFootnoteNumPortion() ) + rInf.SetFootnoteDone(true); + else + if( pPor->InNumberGrp() ) + rInf.SetNumDone(true); + + rInf.SetRest(nullptr); + m_pCurr->SetRest( true ); + return pPor; + } + + // We can stand in the follow, it's crucial that + // pFrame->GetOffset() == 0! + if( rInf.GetIdx() ) + { + // We now too can elongate FootnotePortions and ErgoSumPortions + + // 1. The ErgoSumTexts + if( !rInf.IsErgoDone() ) + { + if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) + pPor = NewErgoSumPortion( rInf ); + rInf.SetErgoDone( true ); + } + + // 2. Arrow portions + if( !pPor && !rInf.IsArrowDone() ) + { + if( m_pFrame->GetOffset() && !m_pFrame->IsFollow() && + rInf.GetIdx() == m_pFrame->GetOffset() ) + pPor = new SwArrowPortion( *m_pCurr ); + rInf.SetArrowDone( true ); + } + + // 3. Kerning portions at beginning of line in grid mode + if ( ! pPor && ! m_pCurr->GetNextPortion() ) + { + SwTextGridItem const*const pGrid( + GetGridItem(GetTextFrame()->FindPageFrame())); + if ( pGrid ) + pPor = new SwKernPortion( *m_pCurr ); + } + + // 4. The line rests (multiline fields) + if( !pPor ) + { + pPor = rInf.GetRest(); + // Only for pPor of course + if( pPor ) + { + m_pCurr->SetRest( true ); + rInf.SetRest(nullptr); + } + } + } + else + { + // 5. The foot note count + if( !rInf.IsFootnoteDone() ) + { + OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(), + "Rotated number portion trouble" ); + + const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame(); + rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum ); + if( bFootnoteNum ) + pPor = NewFootnoteNumPortion( rInf ); + rInf.SetFootnoteDone( true ); + } + + // 6. The ErgoSumTexts of course also exist in the TextMaster, + // it's crucial whether the SwFootnoteFrame is aFollow + if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() ) + { + if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) + pPor = NewErgoSumPortion( rInf ); + rInf.SetErgoDone( true ); + } + + // 7. The numbering + if( !rInf.IsNumDone() && !pPor ) + { + OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(), + "Rotated number portion trouble" ); + + // If we're in the follow, then of course not + if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule()) + pPor = NewNumberPortion( rInf ); + rInf.SetNumDone( true ); + } + // 8. The DropCaps + if( !pPor && GetDropFormat() && ! rInf.IsMulti() ) + pPor = NewDropPortion( rInf ); + + // 9. Kerning portions at beginning of line in grid mode + if ( !pPor && !m_pCurr->GetNextPortion() ) + { + SwTextGridItem const*const pGrid( + GetGridItem(GetTextFrame()->FindPageFrame())); + if ( pGrid ) + pPor = new SwKernPortion( *m_pCurr ); + } + } + + // 10. Decimal tab portion at the beginning of each line in table cells + if ( !pPor && !m_pCurr->GetNextPortion() && + GetTextFrame()->IsInTab() && + GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT)) + { + pPor = NewTabPortion( rInf, true ); + } + + // 11. suffix of meta-field + if (!pPor) + { + pPor = TryNewNoLengthPortion(rInf); + } + + // 12. bookmarks + // check this *last* so that BuildMultiPortion() can find it! + if (!pPor && rInf.CheckCurrentPosBookmark()) + { + auto const bookmark(m_pScriptInfo->GetBookmark(rInf.GetIdx())); + if (static_cast<bool>(bookmark)) + { + sal_Unicode mark; + if ((bookmark & (SwScriptInfo::MarkKind::Start|SwScriptInfo::MarkKind::End)) + == (SwScriptInfo::MarkKind::Start|SwScriptInfo::MarkKind::End)) + { + //mark = u'\u2336'; // not in OpenSymbol :( + mark = '|'; + // hmm ... paint U+2345 over U+2346 should be same width? + // and U+237F // or U+2E20/U+2E21 + } + else if (bookmark & SwScriptInfo::MarkKind::Start) + { + mark = '['; + } + else if (bookmark & SwScriptInfo::MarkKind::End) + { + mark = ']'; + } + else + { + assert(bookmark & SwScriptInfo::MarkKind::Point); + mark = '|'; + } + pPor = new SwBookmarkPortion(mark); + } + } + + return pPor; +} + +static bool lcl_OldFieldRest( const SwLineLayout* pCurr ) +{ + if( !pCurr->GetNext() ) + return false; + const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion(); + bool bRet = false; + while( pPor && !bRet ) + { + bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) || + (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField()); + if( !pPor->GetLen() ) + break; + pPor = pPor->GetNextPortion(); + } + return bRet; +} + +/* NewPortion sets rInf.nLen + * A SwTextPortion is limited by a tab, break, txtatr or attr change + * We can have three cases: + * 1) The line is full and the wrap was not emulated + * -> return 0; + * 2) The line is full and a wrap was emulated + * -> Reset width and return new FlyPortion + * 3) We need to construct a new portion + * -> CalcFlyWidth emulates the width and return portion, if needed + */ + +SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) +{ + // Underflow takes precedence + rInf.SetStopUnderflow( false ); + if( rInf.GetUnderflow() ) + { + OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" ); + return Underflow( rInf ); + } + + // If the line is full, flys and Underflow portions could be waiting ... + if( rInf.IsFull() ) + { + // LineBreaks and Flys (bug05.sdw) + // IsDummy() + if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) ) + return nullptr; + + // When the text bumps into the Fly, or when the Fly comes first because + // it juts out over the left edge, GetFly() is returned. + // When IsFull() and no GetFly() is available, naturally zero is returned. + if( rInf.GetFly() ) + { + if( rInf.GetLast()->IsBreakPortion() ) + { + delete rInf.GetFly(); + rInf.SetFly( nullptr ); + } + + return rInf.GetFly(); + } + + // A nasty special case: A frame without wrap overlaps the Footnote area. + // We must declare the Footnote portion as rest of line, so that + // SwTextFrame::Format doesn't abort (the text mass already was formatted). + if( rInf.GetRest() ) + rInf.SetNewLine( true ); + else + { + // When the next line begins with a rest of a field, but now no + // rest remains, the line must definitely be formatted anew! + if( lcl_OldFieldRest( GetCurr() ) ) + rInf.SetNewLine( true ); + else + { + SwLinePortion *pFirst = WhichFirstPortion( rInf ); + if( pFirst ) + { + rInf.SetNewLine( true ); + if( pFirst->InNumberGrp() ) + rInf.SetNumDone( false) ; + delete pFirst; + } + } + } + + return nullptr; + } + + SwLinePortion *pPor = WhichFirstPortion( rInf ); + + // Check for Hidden Portion: + if ( !pPor ) + { + TextFrameIndex nEnd = rInf.GetIdx(); + if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) ) + pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() ); + } + + if( !pPor ) + { + if( ( !m_pMulti || m_pMulti->IsBidi() ) && + // i#42734 + // No multi portion if there is a hook character waiting: + ( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) ) + { + // We open a multiportion part, if we enter a multi-line part + // of the paragraph. + TextFrameIndex nEnd = rInf.GetIdx(); + std::unique_ptr<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti ); + if( pCreate ) + { + SwMultiPortion* pTmp = nullptr; + + if ( SwMultiCreatorId::Bidi == pCreate->nId ) + pTmp = new SwBidiPortion( nEnd, pCreate->nLevel ); + else if ( SwMultiCreatorId::Ruby == pCreate->nId ) + { + pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(), + GetTextFrame()->GetDoc().getIDocumentSettingAccess(), + nEnd, TextFrameIndex(0), rInf ); + } + else if( SwMultiCreatorId::Rotate == pCreate->nId ) + { + pTmp = new SwRotatedPortion( *pCreate, nEnd, + GetTextFrame()->IsRightToLeft() ); + GetTextFrame()->SetHasRotatedPortions(true); + } + else + pTmp = new SwDoubleLinePortion( *pCreate, nEnd ); + + pCreate.reset(); + CalcFlyWidth( rInf ); + + return pTmp; + } + } + // Tabs and Fields + sal_Unicode cChar = rInf.GetHookChar(); + + if( cChar ) + { + /* We fetch cChar again to be sure that the tab is pending now and + * didn't move to the next line (as happens behind frames). + * However, when a FieldPortion is in the rest, we must naturally fetch + * the cChar from the field content, e.g. DecimalTabs and fields (22615) + */ + if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() ) + cChar = rInf.GetChar( rInf.GetIdx() ); + rInf.ClearHookChar(); + } + else + { + if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength())) + { + rInf.SetFull(true); + CalcFlyWidth( rInf ); + return pPor; + } + cChar = rInf.GetChar( rInf.GetIdx() ); + } + + switch( cChar ) + { + case CH_TAB: + pPor = NewTabPortion( rInf, false ); break; + + case CH_BREAK: + pPor = new SwBreakPortion( *rInf.GetLast() ); break; + + case CHAR_SOFTHYPHEN: // soft hyphen + pPor = new SwSoftHyphPortion; break; + + case CHAR_HARDBLANK: // no-break space + // Please check tdf#115067 if you want to edit the char + pPor = new SwBlankPortion( cChar ); break; + + case CHAR_HARDHYPHEN: // non-breaking hyphen + pPor = new SwBlankPortion( '-' ); break; + + case CHAR_ZWSP: // zero width space + case CHAR_ZWNBSP : // word joiner + pPor = new SwControlCharPortion( cChar ); break; + + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + if( rInf.HasHint( rInf.GetIdx() ) ) + { + pPor = NewExtraPortion( rInf ); + break; + } + [[fallthrough]]; + default : + { + SwTabPortion* pLastTabPortion = rInf.GetLastTab(); + if ( pLastTabPortion && cChar == rInf.GetTabDecimal() ) + { + // Abandon dec. tab position if line is full + // We have a decimal tab portion in the line and the next character has to be + // aligned at the tab stop position. We store the width from the beginning of + // the tab stop portion up to the portion containing the decimal separator: + if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ && + PortionType::TabDecimal == pLastTabPortion->GetWhichPor() ) + { + OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" ); + const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = static_cast<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() ); + static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition ); + rInf.SetTabDecimal( 0 ); + } + else + rInf.SetFull( rInf.GetLastTab()->Format( rInf ) ); + } + + if( rInf.GetRest() ) + { + if( rInf.IsFull() ) + { + rInf.SetNewLine(true); + return nullptr; + } + pPor = rInf.GetRest(); + rInf.SetRest(nullptr); + } + else + { + if( rInf.IsFull() ) + return nullptr; + pPor = NewTextPortion( rInf ); + } + break; + } + } + + // if a portion is created despite there being a pending RestPortion, + // then it is a field which has been split (e.g. because it contains a Tab) + if( pPor && rInf.GetRest() ) + pPor->SetLen(TextFrameIndex(0)); + + // robust: + if( !pPor || rInf.IsStop() ) + { + delete pPor; + return nullptr; + } + } + + assert(pPor && "can only reach here with pPor existing"); + + // Special portions containing numbers (footnote anchor, footnote number, + // numbering) can be contained in a rotated portion, if the user + // choose a rotated character attribute. + if (!m_pMulti) + { + if ( pPor->IsFootnotePortion() ) + { + const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote(); + + if ( pTextFootnote ) + { + SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote()); + const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc(); + const SwEndNoteInfo* pInfo; + if( rFootnote.IsEndNote() ) + pInfo = &pDoc->GetEndNoteInfo(); + else + pInfo = &pDoc->GetFootnoteInfo(); + const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet(); + + const SfxPoolItem* pItem; + sal_uInt16 nDir = 0; + if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE, + true, &pItem )) + nDir = static_cast<const SvxCharRotateItem*>(pItem)->GetValue(); + + if ( 0 != nDir ) + { + delete pPor; + pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1), + 900 == nDir + ? DIR_BOTTOM2TOP + : DIR_TOP2BOTTOM ); + } + } + } + else if ( pPor->InNumberGrp() ) + { + const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont(); + + if ( pNumFnt ) + { + sal_uInt16 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() ); + if ( 0 != nDir ) + { + delete pPor; + pPor = new SwRotatedPortion(TextFrameIndex(0), 900 == nDir + ? DIR_BOTTOM2TOP + : DIR_TOP2BOTTOM ); + + rInf.SetNumDone( false ); + rInf.SetFootnoteDone( false ); + } + } + } + } + + // The font is set in output device, + // the ascent and the height will be calculated. + if( !pPor->GetAscent() && !pPor->Height() ) + CalcAscent( rInf, pPor ); + rInf.SetLen( pPor->GetLen() ); + + // In CalcFlyWidth Width() will be shortened if a FlyPortion is present. + CalcFlyWidth( rInf ); + + // One must not forget that pCurr as GetLast() must provide reasonable values: + if( !m_pCurr->Height() ) + { + OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" ); + m_pCurr->Height( pPor->Height() ); + m_pCurr->SetAscent( pPor->GetAscent() ); + } + + OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong"); + if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() ) + { + delete pPor; + pPor = rInf.GetFly(); + } + return pPor; +} + +TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" ); + + // For the formatting routines, we set pOut to the reference device. + SwHookOut aHook( GetInfo() ); + if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength())) + GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength())); + + bool bBuild = true; + SetFlyInCntBase( false ); + GetInfo().SetLineHeight( 0 ); + GetInfo().SetLineNetHeight( 0 ); + + // Recycling must be suppressed by changed line height and also + // by changed ascent (lowering of baseline). + const sal_uInt16 nOldHeight = m_pCurr->Height(); + const sal_uInt16 nOldAscent = m_pCurr->GetAscent(); + + m_pCurr->SetEndHyph( false ); + m_pCurr->SetMidHyph( false ); + + // fly positioning can make it necessary format a line several times + // for this, we have to keep a copy of our rest portion + SwLinePortion* pField = GetInfo().GetRest(); + std::unique_ptr<SwFieldPortion> xSaveField; + + if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() ) + xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) )); + + // for an optimal repaint rectangle, we want to compare fly portions + // before and after the BuildPortions call + const bool bOptimizeRepaint = AllowRepaintOpt(); + TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen(); + std::vector<long> flyStarts; + + // these are the conditions for a fly position comparison + if ( bOptimizeRepaint && m_pCurr->IsFly() ) + { + SwLinePortion* pPor = m_pCurr->GetFirstPortion(); + long nPOfst = 0; + while ( pPor ) + { + if ( pPor->IsFlyPortion() ) + // insert start value of fly portion + flyStarts.push_back( nPOfst ); + + nPOfst += pPor->Width(); + pPor = pPor->GetNextPortion(); + } + } + + // Here soon the underflow check follows. + while( bBuild ) + { + GetInfo().SetFootnoteInside( false ); + GetInfo().SetOtherThanFootnoteInside( false ); + + // These values must not be reset by FormatReset(); + const bool bOldNumDone = GetInfo().IsNumDone(); + const bool bOldArrowDone = GetInfo().IsArrowDone(); + const bool bOldErgoDone = GetInfo().IsErgoDone(); + + // besides other things, this sets the repaint offset to 0 + FormatReset( GetInfo() ); + + GetInfo().SetNumDone( bOldNumDone ); + GetInfo().SetArrowDone( bOldArrowDone ); + GetInfo().SetErgoDone( bOldErgoDone ); + + // build new portions for this line + BuildPortions( GetInfo() ); + + if( GetInfo().IsStop() ) + { + m_pCurr->SetLen(TextFrameIndex(0)); + m_pCurr->Height( GetFrameRstHeight() + 1 ); + m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 ); + m_pCurr->Width(0); + m_pCurr->Truncate(); + return nStartPos; + } + else if( GetInfo().IsDropInit() ) + { + DropInit(); + GetInfo().SetDropInit( false ); + } + + m_pCurr->CalcLine( *this, GetInfo() ); + CalcRealHeight( GetInfo().IsNewLine() ); + + //i#120864 For Special case that at the first calculation couldn't get + //correct height. And need to recalculate for the right height. + SwLinePortion* pPorTmp = m_pCurr->GetNextPortion(); + if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() && + m_pCurr->Height() > pPorTmp->Height()))) + { + sal_uInt16 nTmpAscent, nTmpHeight; + CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + AlignFlyInCntBase( Y() + long( nTmpAscent ) ); + m_pCurr->CalcLine( *this, GetInfo() ); + CalcRealHeight(); + } + + // bBuild decides if another lap of honor is done + if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() ) + { + m_pCurr->SetRealHeight( GetInfo().GetLineHeight() ); + bBuild = false; + } + else + { + bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) ) + || GetInfo().CheckFootnotePortion(m_pCurr); + if( bBuild ) + { + GetInfo().SetNumDone( bOldNumDone ); + GetInfo().ResetMaxWidthDiff(); + + // delete old rest + if ( GetInfo().GetRest() ) + { + delete GetInfo().GetRest(); + GetInfo().SetRest( nullptr ); + } + + // set original rest portion + if ( xSaveField ) + GetInfo().SetRest( new SwFieldPortion( *xSaveField ) ); + + m_pCurr->SetLen(TextFrameIndex(0)); + m_pCurr->Width(0); + m_pCurr->Truncate(); + } + } + } + + // In case of compat mode, it's possible that a tab portion is wider after + // formatting than before. If this is the case, we also have to make sure + // the SwLineLayout is wider as well. + if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN)) + { + sal_uInt16 nSum = 0; + SwLinePortion* pPor = m_pCurr->GetFirstPortion(); + + while (pPor) + { + nSum += pPor->Width(); + pPor = pPor->GetNextPortion(); + } + + if (nSum > m_pCurr->Width()) + m_pCurr->Width(nSum); + } + + // calculate optimal repaint rectangle + if ( bOptimizeRepaint ) + { + GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) ); + flyStarts.clear(); + } + else + // Special case: we do not allow an optimization of the repaint + // area, but during formatting the repaint offset is set to indicate + // a maximum value for the offset. This value has to be reset: + GetInfo().SetPaintOfst( 0 ); + + // This corrects the start of the reformat range if something has + // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt + // will give us a wrong result if we have to reformat another line + GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() ); + + // delete master copy of rest portion + xSaveField.reset(); + + TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen(); + + // adjust text if kana compression is enabled + if ( GetInfo().CompressLine() ) + { + SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr ); + + // adjust repaint offset + if ( nRepaintOfst < GetInfo().GetPaintOfst() ) + GetInfo().SetPaintOfst( nRepaintOfst ); + } + + CalcAdjustLine( m_pCurr ); + + if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() ) + { + SetFlyInCntBase(); + GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling + // all following line must be painted and when Flys are around, + // also formatted + GetInfo().SetShift( true ); + } + + if ( IsFlyInCntBase() && !IsQuick() ) + UpdatePos( m_pCurr, GetTopLeft(), GetStart() ); + + return nNewStart; +} + +void SwTextFormatter::RecalcRealHeight() +{ + do + { + CalcRealHeight(); + } while (Next()); +} + +void SwTextFormatter::CalcRealHeight( bool bNewLine ) +{ + sal_uInt16 nLineHeight = m_pCurr->Height(); + m_pCurr->SetClipping( false ); + + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + if ( pGrid && GetInfo().SnapToGrid() ) + { + const sal_uInt16 nGridWidth = pGrid->GetBaseHeight(); + const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight(); + const bool bRubyTop = ! pGrid->GetRubyTextBelow(); + + nLineHeight = nGridWidth + nRubyHeight; + const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight; + nLineHeight *= nAmpRatio; + + const sal_uInt16 nAsc = m_pCurr->GetAscent() + + ( bRubyTop ? + ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 : + ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 ); + + m_pCurr->Height( nLineHeight ); + m_pCurr->SetAscent( nAsc ); + m_pInf->GetParaPortion()->SetFixLineHeight(); + + // we ignore any line spacing options except from ... + const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing(); + if ( ! IsParaLine() && pSpace && + SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() ) + { + sal_uLong nTmp = pSpace->GetPropLineSpace(); + + if( nTmp < 100 ) + nTmp = 100; + + nTmp *= nLineHeight; + nLineHeight = static_cast<sal_uInt16>(nTmp / 100); + } + + m_pCurr->SetRealHeight( nLineHeight ); + return; + } + + // The dummy flag is set on lines that only contain flyportions, these shouldn't + // consider register-true and so on. Unfortunately an empty line can be at + // the end of a paragraph (empty paragraphs or behind a Shift-Return), + // which should consider the register. + if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext() + && GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength()) + && !bNewLine)) + { + const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing(); + if( pSpace ) + { + switch( pSpace->GetLineSpaceRule() ) + { + case SvxLineSpaceRule::Auto: + // shrink first line of paragraph too on spacing < 100% + if (IsParaLine() && + pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop + && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE)) + { + long nTmp = pSpace->GetPropLineSpace(); + // Word will render < 50% too but it's just not readable + if( nTmp < 50 ) + nTmp = nTmp ? 50 : 100; + if (nTmp<100) { // code adapted from fixed line height + nTmp *= nLineHeight; + nTmp /= 100; + if( !nTmp ) + ++nTmp; + nLineHeight = static_cast<sal_uInt16>(nTmp); + sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% +#if 0 + // could do clipping here (like Word does) + // but at 0.5 its unreadable either way... + if( nAsc < pCurr->GetAscent() || + nLineHeight - nAsc < pCurr->Height() - + pCurr->GetAscent() ) + pCurr->SetClipping( true ); +#endif + m_pCurr->SetAscent( nAsc ); + m_pCurr->Height( nLineHeight ); + m_pInf->GetParaPortion()->SetFixLineHeight(); + } + } + break; + case SvxLineSpaceRule::Min: + { + if( nLineHeight < pSpace->GetLineHeight() ) + nLineHeight = pSpace->GetLineHeight(); + break; + } + case SvxLineSpaceRule::Fix: + { + nLineHeight = pSpace->GetLineHeight(); + const sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% + if( nAsc < m_pCurr->GetAscent() || + nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() ) + m_pCurr->SetClipping( true ); + m_pCurr->Height( nLineHeight ); + m_pCurr->SetAscent( nAsc ); + m_pInf->GetParaPortion()->SetFixLineHeight(); + } + break; + default: OSL_FAIL( ": unknown LineSpaceRule" ); + } + // Note: for the _first_ line the line spacing of the previous + // paragraph is applied in SwFlowFrame::CalcUpperSpace() + if( !IsParaLine() ) + switch( pSpace->GetInterLineSpaceRule() ) + { + case SvxInterLineSpaceRule::Off: + break; + case SvxInterLineSpaceRule::Prop: + { + long nTmp = pSpace->GetPropLineSpace(); + // 50% is the minimum, if 0% we switch to the + // default value 100% ... + if( nTmp < 50 ) + nTmp = nTmp ? 50 : 100; + + nTmp *= nLineHeight; + nTmp /= 100; + if( !nTmp ) + ++nTmp; + nLineHeight = static_cast<sal_uInt16>(nTmp); + break; + } + case SvxInterLineSpaceRule::Fix: + { + nLineHeight = nLineHeight + pSpace->GetInterLineSpace(); + break; + } + default: OSL_FAIL( ": unknown InterLineSpaceRule" ); + } + } + + if( IsRegisterOn() ) + { + SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height(); + SwRectFnSet aRectFnSet(m_pFrame); + if ( aRectFnSet.IsVert() ) + nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY ); + nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() ); + const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() ); + if( nDiff ) + nLineHeight += RegDiff() - nDiff; + } + } + m_pCurr->SetRealHeight( nLineHeight ); +} + +void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const +{ + // delete Fly in any case! + ClearFly( rInf ); + rInf.Init(); + + rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() ); + rInf.SetRoot( m_pCurr ); + rInf.SetLineStart( m_nStart ); + rInf.SetIdx( m_nStart ); + rInf.Left( Left() ); + rInf.Right( Right() ); + rInf.First( FirstLeft() ); + rInf.LeftMargin(GetLeftMargin()); + + rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) ); + rInf.Width( rInf.RealWidth() ); + if( const_cast<SwTextFormatter*>(this)->GetRedln() ) + { + const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() ); + const_cast<SwTextFormatter*>(this)->GetRedln()->Reset(); + } +} + +void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf ) +{ + m_pFirstOfBorderMerge = nullptr; + m_pCurr->Truncate(); + m_pCurr->Init(); + + // delete pSpaceAdd and pKanaComp + m_pCurr->FinishSpaceAdd(); + m_pCurr->FinishKanaComp(); + m_pCurr->ResetFlags(); + FeedInf( rInf ); +} + +bool SwTextFormatter::CalcOnceMore() +{ + if( m_pDropFormat ) + { + const sal_uInt16 nOldDrop = GetDropHeight(); + CalcDropHeight( m_pDropFormat->GetLines() ); + m_bOnceMore = nOldDrop != GetDropHeight(); + } + else + m_bOnceMore = false; + return m_bOnceMore; +} + +SwTwips SwTextFormatter::CalcBottomLine() const +{ + SwTwips nRet = Y() + GetLineHeight(); + SwTwips nMin = GetInfo().GetTextFly().GetMinBottom(); + if( nMin && ++nMin > nRet ) + { + SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height() + - m_pFrame->getFramePrintArea().Top(); + if( nRet + nDist < nMin ) + { + const bool bRepaint = HasTruncLines() && + GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1; + nRet = nMin - nDist; + if( bRepaint ) + { + const_cast<SwRepaint&>(GetInfo().GetParaPortion() + ->GetRepaint()).Bottom( nRet-1 ); + const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 ); + } + } + } + return nRet; +} + +// FME/OD: This routine does a limited text formatting. +SwTwips SwTextFormatter::CalcFitToContent_() +{ + FormatReset( GetInfo() ); + BuildPortions( GetInfo() ); + m_pCurr->CalcLine( *this, GetInfo() ); + return m_pCurr->Width(); +} + +// determines if the calculation of a repaint offset is allowed +// otherwise each line is painted from 0 (this is a copy of the beginning +// of the former SwTextFormatter::Recycle() function +bool SwTextFormatter::AllowRepaintOpt() const +{ + // reformat position in front of current line? Only in this case + // we want to set the repaint offset + bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() && + m_pCurr->GetLen(); + + // a special case is the last line of a block adjusted paragraph: + if ( bOptimizeRepaint ) + { + switch( GetAdjust() ) + { + case SvxAdjust::Block: + { + if( IsLastBlock() || IsLastCenter() ) + bOptimizeRepaint = false; + else + { + // ????: blank in the last master line (blocksat.sdw) + bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow(); + if ( bOptimizeRepaint ) + { + SwLinePortion *pPos = m_pCurr->GetFirstPortion(); + while ( pPos && !pPos->IsFlyPortion() ) + pPos = pPos->GetNextPortion(); + bOptimizeRepaint = !pPos; + } + } + break; + } + case SvxAdjust::Center: + case SvxAdjust::Right: + bOptimizeRepaint = false; + break; + default: ; + } + } + + // Again another special case: invisible SoftHyphs + const TextFrameIndex nReformat = GetInfo().GetReformatStart(); + if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat) + { + const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength()) + ? 0 + : GetInfo().GetText()[ sal_Int32(nReformat) ]; + bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh ) + || ! GetInfo().HasHint( nReformat ); + } + + return bOptimizeRepaint; +} + +void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom ) +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::CalcUnclipped with unswapped frame" ); + + long nFlyAsc, nFlyDesc; + m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc ); + rTop = Y() + GetCurr()->GetAscent(); + rBottom = rTop + nFlyDesc; + rTop -= nFlyAsc; +} + +void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart, + TextFrameIndex const nStartIdx, bool bAlways) const +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::UpdatePos with unswapped frame" ); + + if( GetInfo().IsTest() ) + return; + SwLinePortion *pFirst = pCurrent->GetFirstPortion(); + SwLinePortion *pPos = pFirst; + SwTextPaintInfo aTmpInf( GetInfo() ); + aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() ); + aTmpInf.ResetSpaceIdx(); + aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() ); + aTmpInf.ResetKanaIdx(); + + // The frame's size + aTmpInf.SetIdx( nStartIdx ); + aTmpInf.SetPos( aStart ); + + long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; + pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); + + const sal_uInt16 nTmpHeight = pCurrent->GetRealHeight(); + sal_uInt16 nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height(); + AsCharFlags nFlags = AsCharFlags::UlSpace; + if( GetMulti() ) + { + aTmpInf.SetDirection( GetMulti()->GetDirection() ); + if( GetMulti()->HasRotation() ) + { + nFlags |= AsCharFlags::Rotate; + if( GetMulti()->IsRevers() ) + { + nFlags |= AsCharFlags::Reverse; + aTmpInf.X( aTmpInf.X() - nAscent ); + } + else + aTmpInf.X( aTmpInf.X() + nAscent ); + } + else + { + if ( GetMulti()->IsBidi() ) + nFlags |= AsCharFlags::Bidi; + aTmpInf.Y( aTmpInf.Y() + nAscent ); + } + } + else + aTmpInf.Y( aTmpInf.Y() + nAscent ); + + while( pPos ) + { + // We only know one case where changing the position (caused by the + // adjustment) could be relevant for a portion: We need to SetRefPoint + // for FlyCntPortions. + if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() ) + && ( bAlways || !IsQuick() ) ) + { + pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos ); + + if( pPos->IsGrfNumPortion() ) + { + if( !nFlyAsc && !nFlyDesc ) + { + nTmpAscent = nAscent; + nFlyAsc = nAscent; + nTmpDescent = nTmpHeight - nAscent; + nFlyDesc = nTmpDescent; + } + static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent, + nFlyAsc, nFlyDesc ); + } + else + { + Point aBase( aTmpInf.GetPos() ); + if ( GetInfo().GetTextFrame()->IsVertical() ) + GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase ); + + static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(), + aBase, nTmpAscent, nTmpDescent, nFlyAsc, + nFlyDesc, nFlags ); + } + } + if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() ) + { + OSL_ENSURE( !GetMulti(), "Too much multi" ); + const_cast<SwTextFormatter*>(this)->m_pMulti = static_cast<SwMultiPortion*>(pPos); + SwLineLayout *pLay = &GetMulti()->GetRoot(); + Point aSt( aTmpInf.X(), aStart.Y() ); + + if ( GetMulti()->HasBrackets() ) + { + OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles"); + aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() ); + } + else if( GetMulti()->HasRotation() ) + { + aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() ); + if( GetMulti()->IsRevers() ) + aSt.AdjustX(GetMulti()->Width() ); + else + aSt.AdjustY(GetMulti()->Height() ); + } + else if ( GetMulti()->IsBidi() ) + // jump to end of the bidi portion + aSt.AdjustX(pLay->Width() ); + + TextFrameIndex nStIdx = aTmpInf.GetIdx(); + do + { + UpdatePos( pLay, aSt, nStIdx, bAlways ); + nStIdx = nStIdx + pLay->GetLen(); + aSt.AdjustY(pLay->Height() ); + pLay = pLay->GetNext(); + } while ( pLay ); + const_cast<SwTextFormatter*>(this)->m_pMulti = nullptr; + } + pPos->Move( aTmpInf ); + pPos = pPos->GetNextPortion(); + } +} + +void SwTextFormatter::AlignFlyInCntBase( long nBaseLine ) const +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::AlignFlyInCntBase with unswapped frame" ); + + if( GetInfo().IsTest() ) + return; + SwLinePortion *pFirst = m_pCurr->GetFirstPortion(); + SwLinePortion *pPos = pFirst; + AsCharFlags nFlags = AsCharFlags::None; + if( GetMulti() && GetMulti()->HasRotation() ) + { + nFlags |= AsCharFlags::Rotate; + if( GetMulti()->IsRevers() ) + nFlags |= AsCharFlags::Reverse; + } + + long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; + + while( pPos ) + { + if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() ) + { + m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos ); + + if( pPos->IsGrfNumPortion() ) + static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent, + nFlyAsc, nFlyDesc ); + else + { + Point aBase; + if ( GetInfo().GetTextFrame()->IsVertical() ) + { + nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine ); + aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() ); + } + else + aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine ); + + static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent, + nFlyAsc, nFlyDesc, nFlags ); + } + } + pPos = pPos->GetNextPortion(); + } +} + +bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const +{ + OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" ); + if( GetCurr() ) + { + // First we check, whether a fly overlaps with the line. + // = GetLineHeight() + const sal_uInt16 nHeight = GetCurr()->GetRealHeight(); + SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight ); + + SwRect aLineVert( aLine ); + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchHorizontalToVertical( aLineVert ); + SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) ); + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchVerticalToHorizontal( aInter ); + + if( !aInter.HasArea() ) + return false; + + // We now check every portion that could have lowered for overlapping + // with the fly. + const SwLinePortion *pPos = GetCurr()->GetFirstPortion(); + aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() ); + aLine.Height( GetCurr()->Height() ); + + while( pPos ) + { + aLine.Width( pPos->Width() ); + + aLineVert = aLine; + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchHorizontalToVertical( aLineVert ); + aInter = rInf.GetTextFly().GetFrame( aLineVert ); + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchVerticalToHorizontal( aInter ); + + // New flys from below? + if( !pPos->IsFlyPortion() ) + { + if( aInter.IsOver( aLine ) ) + { + aInter.Intersection_( aLine ); + if( aInter.HasArea() ) + { + // To be evaluated during reformat of this line: + // RealHeight including spacing + rInf.SetLineHeight( nHeight ); + // Height without extra spacing + rInf.SetLineNetHeight( m_pCurr->Height() ); + return true; + } + } + } + else + { + // The fly portion is not intersected by a fly anymore + if ( ! aInter.IsOver( aLine ) ) + { + rInf.SetLineHeight( nHeight ); + rInf.SetLineNetHeight( m_pCurr->Height() ); + return true; + } + else + { + aInter.Intersection_( aLine ); + + // No area means a fly has become invalid because of + // lowering the line => reformat the line + // we also have to reformat the line, if the fly size + // differs from the intersection interval's size. + if( ! aInter.HasArea() || + static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() ) + { + rInf.SetLineHeight( nHeight ); + rInf.SetLineNetHeight( m_pCurr->Height() ); + return true; + } + } + } + + aLine.Left( aLine.Left() + pPos->Width() ); + pPos = pPos->GetNextPortion(); + } + } + return false; +} + +void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) +{ + if( GetMulti() || rInf.GetFly() ) + return; + + SwTextFly& rTextFly = rInf.GetTextFly(); + if( !rTextFly.IsOn() || rInf.IsIgnoreFly() ) + return; + + const SwLinePortion *pLast = rInf.GetLast(); + + long nAscent; + long nTop = Y(); + long nHeight; + + if( rInf.GetLineHeight() ) + { + // Real line height has already been calculated, we only have to + // search for intersections in the lower part of the strip + nAscent = m_pCurr->GetAscent(); + nHeight = rInf.GetLineNetHeight(); + nTop += rInf.GetLineHeight() - nHeight; + } + else + { + // We make a first guess for the lines real height + if ( ! m_pCurr->GetRealHeight() ) + CalcRealHeight(); + + nAscent = pLast->GetAscent(); + nHeight = pLast->Height(); + + if ( m_pCurr->GetRealHeight() > nHeight ) + nTop += m_pCurr->GetRealHeight() - nHeight; + else + // Important for fixed space between lines + nHeight = m_pCurr->GetRealHeight(); + } + + const long nLeftMar = GetLeftMargin(); + const long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin(); + + SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X() + + nLeftMar - nLeftMin , nHeight ); + + // tdf#116486: consider also the upper margin from getFramePrintArea because intersections + // with this additional space should lead to repositioning of paragraphs + // For compatibility we grab a related compat flag: + if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS) + && IsFirstTextLine()) + { + const long nUpper = m_pFrame->getFramePrintArea().Top(); + // Increase the rectangle + if( nUpper > 0 && nTop >= nUpper ) + aLine.SubTop( nUpper ); + } + SwRect aLineVert( aLine ); + if ( m_pFrame->IsRightToLeft() ) + m_pFrame->SwitchLTRtoRTL( aLineVert ); + + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchHorizontalToVertical( aLineVert ); + + // GetFrame(...) determines and returns the intersection rectangle + SwRect aInter( rTextFly.GetFrame( aLineVert ) ); + + if ( m_pFrame->IsRightToLeft() ) + m_pFrame->SwitchRTLtoLTR( aInter ); + + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchVerticalToHorizontal( aInter ); + + if (!aInter.IsEmpty() && aInter.Bottom() < nTop) + { + // Intersects with the frame area (with upper margin), but not with the print area (without + // upper margin). Don't reserve space for the fly portion in this case, text is allowed to + // flow there. + aInter.Height(0); + } + + if( !aInter.IsOver( aLine ) ) + return; + + aLine.Left( rInf.X() + nLeftMar ); + bool bForced = false; + if( aInter.Left() <= nLeftMin ) + { + SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left(); + if( GetTextFrame()->getFramePrintArea().Left() < 0 ) + nFrameLeft += GetTextFrame()->getFramePrintArea().Left(); + if( aInter.Left() < nFrameLeft ) + aInter.Left( nFrameLeft ); + + long nAddMar = 0; + if ( m_pFrame->IsRightToLeft() ) + { + nAddMar = m_pFrame->getFrameArea().Right() - Right(); + if ( nAddMar < 0 ) + nAddMar = 0; + } + else + nAddMar = nLeftMar - nFrameLeft; + + aInter.Width( aInter.Width() + nAddMar ); + // For a negative first line indent, we set this flag to show + // that the indentation/margin has been moved. + // This needs to be respected by the DefaultTab at the zero position. + if( IsFirstTextLine() && HasNegFirst() ) + bForced = true; + } + aInter.Intersection( aLine ); + if( !aInter.HasArea() ) + return; + + const bool bFullLine = aLine.Left() == aInter.Left() && + aLine.Right() == aInter.Right(); + + // Although no text is left, we need to format another line, + // because also empty lines need to avoid a Fly with no wrapping. + if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength())) + { + rInf.SetNewLine( true ); + // We know that for dummies, it holds ascent == height + m_pCurr->SetDummy(true); + } + + // aInter becomes frame-local + aInter.Pos().AdjustX( -nLeftMar ); + SwFlyPortion *pFly = new SwFlyPortion( aInter ); + if( bForced ) + { + m_pCurr->SetForcedLeftMargin(); + rInf.ForcedLeftMargin( static_cast<sal_uInt16>(aInter.Width()) ); + } + + if( bFullLine ) + { + // In order to properly flow around Flys with different + // wrapping attributes, we need to increase by units of line height. + // The last avoiding line should be adjusted in height, so that + // we don't get a frame spacing effect. + // It is important that ascent == height, because the FlyPortion + // values are transferred to pCurr in CalcLine and IsDummy() relies + // on this behaviour. + // To my knowledge we only have two places where DummyLines can be + // created: here and in MakeFlyDummies. + // IsDummy() is evaluated in IsFirstTextLine(), when moving lines + // and in relation with DropCaps. + pFly->Height( sal_uInt16(aInter.Height()) ); + + // nNextTop now contains the margin's bottom edge, which we avoid + // or the next margin's top edge, which we need to respect. + // That means we can comfortably grow up to this value; that's how + // we save a few empty lines. + long nNextTop = rTextFly.GetNextTop(); + if ( m_pFrame->IsVertical() ) + nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop ); + if( nNextTop > aInter.Bottom() ) + { + SwTwips nH = nNextTop - aInter.Top(); + if( nH < SAL_MAX_UINT16 ) + pFly->Height( sal_uInt16( nH ) ); + } + if( nAscent < pFly->Height() ) + pFly->SetAscent( sal_uInt16(nAscent) ); + else + pFly->SetAscent( pFly->Height() ); + } + else + { + if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength())) + { + // Don't use nHeight, or we have a huge descent + pFly->Height( pLast->Height() ); + pFly->SetAscent( pLast->GetAscent() ); + } + else + { + pFly->Height( sal_uInt16(aInter.Height()) ); + if( nAscent < pFly->Height() ) + pFly->SetAscent( sal_uInt16(nAscent) ); + else + pFly->SetAscent( pFly->Height() ); + } + } + + rInf.SetFly( pFly ); + + if( pFly->GetFix() < rInf.Width() ) + rInf.Width( pFly->GetFix() ); + + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + if ( !pGrid ) + return; + + const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); + const SwLayoutFrame* pBody = pPageFrame->FindBodyCont(); + + SwRectFnSet aRectFnSet(pPageFrame); + + const long nGridOrigin = pBody ? + aRectFnSet.GetPrtLeft(*pBody) : + aRectFnSet.GetPrtLeft(*pPageFrame); + + const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc); + + SwTwips nStartX = GetLeftMargin(); + if ( aRectFnSet.IsVert() ) + { + Point aPoint( nStartX, 0 ); + m_pFrame->SwitchHorizontalToVertical( aPoint ); + nStartX = aPoint.Y(); + } + + const SwTwips nOfst = nStartX - nGridOrigin; + const SwTwips nTmpWidth = rInf.Width() + nOfst; + + const sal_uLong i = nTmpWidth / nGridWidth + 1; + + const long nNewWidth = ( i - 1 ) * nGridWidth - nOfst; + if ( nNewWidth > 0 ) + rInf.Width( static_cast<sal_uInt16>(nNewWidth) ); + else + rInf.Width( 0 ); + + +} + +SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf, + SwTextAttr *pHint ) const +{ + const SwFrame *pFrame = m_pFrame; + + SwFlyInContentFrame *pFly; + SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat(); + if( RES_FLYFRMFMT == pFrameFormat->Which() ) + pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame); + else + pFly = nullptr; + // aBase is the document-global position, from which the new extra portion is placed + // aBase.X() = Offset in the line after the current position + // aBase.Y() = LineIter.Y() + Ascent of the current position + + long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; + // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)> + // to change line spacing behaviour at paragraph - Compatibility to MS Word + //SwLinePortion *pPos = pCurr->GetFirstPortion(); + //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); + m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); + + // If the ascent of the frame is larger than the ascent of the current position, + // we use this one when calculating the base, or the frame would be positioned + // too much to the top, sliding down after all causing a repaint in an area + // he actually never was in. + sal_uInt16 nAscent = 0; + + const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical(); + + const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() && + 0 != ( bTextFrameVertical ? + pFly->GetRefPoint().X() : + pFly->GetRefPoint().Y() ); + + if ( bUseFlyAscent ) + nAscent = static_cast<sal_uInt16>( std::abs( int( bTextFrameVertical ? + pFly->GetRelPos().X() : + pFly->GetRelPos().Y() ) ) ); + + // Check if be prefer to use the ascent of the last portion: + if ( IsQuick() || + !bUseFlyAscent || + nAscent < rInf.GetLast()->GetAscent() ) + { + nAscent = rInf.GetLast()->GetAscent(); + } + else if( nAscent > nFlyAsc ) + nFlyAsc = nAscent; + + Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent ); + AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None; + if( GetMulti() && GetMulti()->HasRotation() ) + { + nMode |= AsCharFlags::Rotate; + if( GetMulti()->IsRevers() ) + nMode |= AsCharFlags::Reverse; + } + + Point aTmpBase( aBase ); + if ( GetInfo().GetTextFrame()->IsVertical() ) + GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase ); + + SwFlyCntPortion* pRet(nullptr); + if( pFly ) + { + pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode); + // We need to make sure that our font is set again in the OutputDevice + // It could be that the FlyInCnt was added anew and GetFlyFrame() would + // in turn cause, that it'd be created anew again. + // This one's frames get formatted right away, which change the font. + rInf.SelectFont(); + if( pRet->GetAscent() > nAscent ) + { + aBase.setY( Y() + pRet->GetAscent() ); + nMode |= AsCharFlags::UlSpace; + if( !rInf.IsTest() ) + { + aTmpBase = aBase; + if ( GetInfo().GetTextFrame()->IsVertical() ) + GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase ); + + pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent, + nTmpDescent, nFlyAsc, nFlyDesc, nMode ); + } + } + } + else + { + pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode); + } + return pRet; +} + +/* Drop portion is a special case, because it has parts which aren't portions + but we have handle them just like portions */ +void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion ) +{ + if( rPortion.GetLines() > 1 ) + { + SwDropPortionPart* pCurrPart = rPortion.GetPart(); + while( pCurrPart ) + { + if( pCurrPart->GetFollow() && + ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) ) + { + pCurrPart->SetJoinBorderWithNext(true); + pCurrPart->GetFollow()->SetJoinBorderWithPrev(true); + } + pCurrPart = pCurrPart->GetFollow(); + } + } +} + +void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf ) +{ + const SwFont aCurFont = *rInf.GetFont(); + if( aCurFont.HasBorder() ) + { + if (pPrev && pPrev->GetJoinBorderWithNext() ) + { + // In some case border merge is called twice to the portion + if( !rPortion.GetJoinBorderWithPrev() ) + { + rPortion.SetJoinBorderWithPrev(true); + if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() ) + rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace()); + } + } + else + { + rPortion.SetJoinBorderWithPrev(false); + m_pFirstOfBorderMerge = &rPortion; + } + + // Get next portion's font + bool bSeek = false; + if (!rInf.IsFull() && // Not the last portion of the line (in case of line break) + rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph + { + bSeek = Seek(rInf.GetIdx() + rPortion.GetLen()); + } + // Don't join the next portion if SwKernPortion sits between two different boxes. + bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev(); + // If next portion has the same border then merge + if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect ) + { + // In some case border merge is called twice to the portion + if( !rPortion.GetJoinBorderWithNext() ) + { + rPortion.SetJoinBorderWithNext(true); + if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() ) + rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace()); + } + } + // If this is the last portion of the merge group then make the real height merge + else + { + rPortion.SetJoinBorderWithNext(false); + if( m_pFirstOfBorderMerge != &rPortion ) + { + // Calculate maximum height and ascent + SwLinePortion* pActPor = m_pFirstOfBorderMerge; + sal_uInt16 nMaxAscent = 0; + sal_uInt16 nMaxHeight = 0; + bool bReachCurrent = false; + while( pActPor ) + { + if( nMaxHeight < pActPor->Height() ) + nMaxHeight = pActPor->Height(); + if( nMaxAscent < pActPor->GetAscent() ) + nMaxAscent = pActPor->GetAscent(); + + pActPor = pActPor->GetNextPortion(); + if( !pActPor && !bReachCurrent ) + { + pActPor = &rPortion; + bReachCurrent = true; + } + } + + // Change all portion's height and ascent + pActPor = m_pFirstOfBorderMerge; + bReachCurrent = false; + while( pActPor ) + { + if( nMaxHeight > pActPor->Height() ) + pActPor->Height(nMaxHeight); + if( nMaxAscent > pActPor->GetAscent() ) + pActPor->SetAscent(nMaxAscent); + + pActPor = pActPor->GetNextPortion(); + if( !pActPor && !bReachCurrent ) + { + pActPor = &rPortion; + bReachCurrent = true; + } + } + m_pFirstOfBorderMerge = nullptr; + } + } + Seek(rInf.GetIdx()); + } +} + +namespace { + // calculates and sets optimal repaint offset for the current line + long lcl_CalcOptRepaint( SwTextFormatter &rThis, + SwLineLayout const &rCurr, + TextFrameIndex const nOldLineEnd, + const std::vector<long> &rFlyStarts ) + { + SwTextFormatInfo& txtFormatInfo = rThis.GetInfo(); + if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() ) + // the reformat position is behind our new line, that means + // something of our text has moved to the next line + return 0; + + TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd); + + // in case we do not have any fly in our line, our repaint position + // is the changed position - 1 + if ( rFlyStarts.empty() && ! rCurr.IsFly() ) + { + // this is the maximum repaint offset determined during formatting + // for example: the beginning of the first right tab stop + // if this value is 0, this means that we do not have an upper + // limit for the repaint offset + const long nFormatRepaint = txtFormatInfo.GetPaintOfst(); + + if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3)) + return 0; + + // step back two positions for smoother repaint + nReformat -= TextFrameIndex(2); + + // i#28795, i#34607, i#38388 + // step back more characters, this is required by complex scripts + // e.g., for Khmer (thank you, Javier!) + static const TextFrameIndex nMaxContext(10); + if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext) + nReformat = nReformat - nMaxContext; + else + { + nReformat = txtFormatInfo.GetLineStart(); + //reset the margin flag - prevent loops + SwTextCursor::SetRightMargin(false); + } + + // Weird situation: Our line used to end with a hole portion + // and we delete some characters at the end of our line. We have + // to take care for repainting the blanks which are not anymore + // covered by the hole portion + while ( nReformat > txtFormatInfo.GetLineStart() && + CH_BLANK == txtFormatInfo.GetChar( nReformat ) ) + --nReformat; + + OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" ); + SwRect aRect; + + // Note: GetChareRect is not const. It definitely changes the + // bMulti flag. We have to save and restore the old value. + bool bOldMulti = txtFormatInfo.IsMulti(); + rThis.GetCharRect( &aRect, nReformat ); + txtFormatInfo.SetMulti( bOldMulti ); + + return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) : + aRect.Left(); + } + else + { + // nReformat may be wrong, if something around flys has changed: + // we compare the former and the new fly positions in this line + // if anything has changed, we carefully have to adjust the right + // repaint position + long nPOfst = 0; + size_t nCnt = 0; + long nX = 0; + TextFrameIndex nIdx = rThis.GetInfo().GetLineStart(); + SwLinePortion* pPor = rCurr.GetFirstPortion(); + + while ( pPor ) + { + if ( pPor->IsFlyPortion() ) + { + // compare start of fly with former start of fly + if (nCnt < rFlyStarts.size() && + nX == rFlyStarts[ nCnt ] && + nIdx < nReformat + ) + // found fix position, nothing has changed left from nX + nPOfst = nX + pPor->Width(); + else + break; + + nCnt++; + } + nX = nX + pPor->Width(); + nIdx = nIdx + pPor->GetLen(); + pPor = pPor->GetNextPortion(); + } + + return nPOfst + rThis.GetLeftMargin(); + } + } + + // Determine if we need to build hidden portions + bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos) + { + // Only if hidden text should not be shown: + // if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() ) + const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar(); + const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting(); + if (bShowInDocView || bShowForPrinting) + return false; + + const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); + TextFrameIndex nHiddenStart; + TextFrameIndex nHiddenEnd; + rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd ); + if ( nHiddenEnd ) + { + rPos = nHiddenEnd; + return true; + } + + return false; + } + + bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond) + { + return + rFirst.GetTopBorder() == rSecond.GetTopBorder() && + rFirst.GetBottomBorder() == rSecond.GetBottomBorder() && + rFirst.GetLeftBorder() == rSecond.GetLeftBorder() && + rFirst.GetRightBorder() == rSecond.GetRightBorder() && + rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() && + rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() && + rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() && + rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() && + rFirst.GetOrientation() == rSecond.GetOrientation() && + rFirst.GetShadowColor() == rSecond.GetShadowColor() && + rFirst.GetShadowWidth() == rSecond.GetShadowWidth() && + rFirst.GetShadowLocation() == rSecond.GetShadowLocation(); + } + +} //end unnamed namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrform2.hxx b/sw/source/core/text/itrform2.hxx new file mode 100644 index 000000000..c9a14f566 --- /dev/null +++ b/sw/source/core/text/itrform2.hxx @@ -0,0 +1,245 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_ITRFORM2_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_ITRFORM2_HXX +#include "itrpaint.hxx" + +class SwFlyCntPortion; +class SwDropPortion; +class SwFormatDrop; +class SwTextAttr; +class SwNumberPortion; +class SwErgoSumPortion; +class SwExpandPortion; +class SwMultiPortion; +class SwFootnotePortion; + +class SwTextFormatter : public SwTextPainter +{ + const SwFormatDrop *m_pDropFormat; + SwMultiPortion* m_pMulti; // during formatting a multi-portion + sal_uInt8 m_nContentEndHyph; // Counts consecutive hyphens at the line end + sal_uInt8 m_nContentMidHyph; // Counts consecutive hyphens before flies + TextFrameIndex m_nLeftScanIdx; // for increasing performance during + TextFrameIndex m_nRightScanIdx; // scanning for portion ends + bool m_bOnceMore : 1; // Another round? + bool m_bFlyInContentBase : 1; // Base reference that sets a character-bound frame + bool m_bTruncLines : 1; // Flag for extending the repaint rect, if needed + bool m_bUnclipped : 1; // Flag whether repaint is larger than the fixed line height + std::unique_ptr<sw::MergedAttrIterByEnd> m_pByEndIter; // HACK for TryNewNoLengthPortion + SwLinePortion* m_pFirstOfBorderMerge; // The first text portion of a joined border (during portion building) + + SwLinePortion *NewPortion( SwTextFormatInfo &rInf ); + SwTextPortion *NewTextPortion( SwTextFormatInfo &rInf ); + SwLinePortion *NewExtraPortion( SwTextFormatInfo &rInf ); + SwTabPortion *NewTabPortion( SwTextFormatInfo &rInf, bool bAuto ) const; + SwNumberPortion *NewNumberPortion( SwTextFormatInfo &rInf ) const; + SwDropPortion *NewDropPortion( SwTextFormatInfo &rInf ); + SwNumberPortion *NewFootnoteNumPortion( SwTextFormatInfo const &rInf ) const; + SwErgoSumPortion *NewErgoSumPortion( SwTextFormatInfo const &rInf ) const; + SwExpandPortion *NewFieldPortion( SwTextFormatInfo &rInf, + const SwTextAttr *pHt ) const; + SwFootnotePortion *NewFootnotePortion( SwTextFormatInfo &rInf, SwTextAttr *pHt ); + + /** + Sets a new portion for an object anchored as character + */ + SwFlyCntPortion *NewFlyCntPortion( SwTextFormatInfo &rInf, + SwTextAttr *pHt ) const; + SwLinePortion *WhichFirstPortion( SwTextFormatInfo &rInf ); + SwTextPortion *WhichTextPor( SwTextFormatInfo &rInf ) const; + SwExpandPortion * TryNewNoLengthPortion( SwTextFormatInfo const & rInfo ); + + // The center piece of formatting + void BuildPortions( SwTextFormatInfo &rInf ); + + bool BuildMultiPortion( SwTextFormatInfo &rInf, SwMultiPortion& rMulti ); + + /** + Calculation of the emulated right side. + + Determines the next object, that reaches into the rest of the line and + constructs the appropriate FlyPortion. + SwTextFly::GetFrame(const SwRect&, bool) will be needed for this. + + The right edge can be shortened by flys + */ + void CalcFlyWidth( SwTextFormatInfo &rInf ); + + // Is overloaded by SwTextFormatter because of UpdatePos + void CalcAdjustLine( SwLineLayout *pCurr ); + + // considers line spacing attributes + void CalcRealHeight( bool bNewLine = false ); + + // Transfers the data to rInf + void FeedInf( SwTextFormatInfo &rInf ) const; + + // Treats underflow situations + SwLinePortion *Underflow( SwTextFormatInfo &rInf ); + + // Calculates the ascent and the height from the fontmetric + void CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor ); + + // determines, if an optimized repaint rectangle is allowed + bool AllowRepaintOpt() const; + + // Is called by FormatLine + void FormatReset( SwTextFormatInfo &rInf ); + + /** + The position of the portions changes with the adjustment. + + This method updates the reference point of the anchored as character objects, + for example after adjustment change (right alignment, justified, etc.) + Mainly to correct the X position. + */ + void UpdatePos(SwLineLayout *pCurr, Point aStart, TextFrameIndex nStartIdx, + bool bAlways = false ) const; + + /** + Set all anchored as character objects to the passed BaseLine + (in Y direction). + */ + void AlignFlyInCntBase( long nBaseLine ) const; + + /** + This is called after the real height of the line has been calculated + Therefore it is possible, that more flys from below intersect with the + line, or that flys from above do not intersect with the line anymore. + We check this and return true, meaning that the line has to be + formatted again. + */ + bool ChkFlyUnderflow( SwTextFormatInfo &rInf ) const; + + // Insert portion + void InsertPortion( SwTextFormatInfo &rInf, SwLinePortion *pPor ); + + // Guess height for the DropPortion + void GuessDropHeight( const sal_uInt16 nLines ); + +public: + // Calculate the height for the DropPortion + void CalcDropHeight( const sal_uInt16 nLines ); + + // Calculates the paragraphs bottom, takes anchored objects within it into + // account which have a wrap setting of "wrap at 1st paragraph" + SwTwips CalcBottomLine() const; + + // Takes character-bound objects into account when calculating the + // repaint rect in lines with fixed line height + void CalcUnclipped( SwTwips& rTop, SwTwips& rBottom ); + + // Amongst others for DropCaps + bool CalcOnceMore(); + + void CtorInitTextFormatter( SwTextFrame *pFrame, SwTextFormatInfo *pInf ); + SwTextFormatter(SwTextFrame *pTextFrame, SwTextFormatInfo *pTextFormatInf) + : SwTextPainter(pTextFrame->GetTextNodeFirst()) + , m_bUnclipped(false) + { + CtorInitTextFormatter( pTextFrame, pTextFormatInf ); + } + virtual ~SwTextFormatter() override; + + TextFrameIndex FormatLine(TextFrameIndex nStart); + + void RecalcRealHeight(); + + // We format a line for interactive hyphenation + bool Hyphenate(SwInterHyphInfoTextFrame & rInf); + + // A special method for QuoVadis texts: + // nErgo is the page number of the ErgoSum Footnote + // At 0 it's still unclear + TextFrameIndex FormatQuoVadis(TextFrameIndex nStart); + + // The emergency break: Cancel formatting, discard line + bool IsStop() const { return GetInfo().IsStop(); } + + // The counterpart: Continue formatting at all costs + bool IsNewLine() const { return GetInfo().IsNewLine(); } + + // FormatQuick(); Refresh formatting information + bool IsQuick() const { return GetInfo().IsQuick(); } + + // Create a SwLineLayout if needed, which avoids Footnote/Fly to oscillate + void MakeDummyLine(); + + // SwTextIter functionality + void Insert( SwLineLayout *pLine ); + + // The remaining height to the page border + sal_uInt16 GetFrameRstHeight() const; + + // How wide would you be without any bounds (Flys etc.)? + SwTwips CalcFitToContent_( ); + + SwLinePortion* MakeRestPortion(const SwLineLayout* pLine, TextFrameIndex nPos); + + const SwFormatDrop *GetDropFormat() const { return m_pDropFormat; } + void ClearDropFormat() { m_pDropFormat = nullptr; } + + SwMultiPortion *GetMulti() const { return m_pMulti; } + + bool IsOnceMore() const { return m_bOnceMore; } + void SetOnceMore( bool bNew ) { m_bOnceMore = bNew; } + + bool HasTruncLines() const { return m_bTruncLines; } + void SetTruncLines( bool bNew ) { m_bTruncLines = bNew; } + + bool IsUnclipped() const { return m_bUnclipped; } + void SetUnclipped( bool bNew ) { m_bUnclipped = bNew; } + + bool IsFlyInCntBase() const { return m_bFlyInContentBase; } + void SetFlyInCntBase( bool bNew = true ) { m_bFlyInContentBase = bNew; } + + SwTextFormatInfo &GetInfo() + { return static_cast<SwTextFormatInfo&>(SwTextIter::GetInfo()); } + const SwTextFormatInfo &GetInfo() const + { return static_cast<const SwTextFormatInfo&>(SwTextIter::GetInfo()); } + + void InitCntHyph() { CntHyphens( m_nContentEndHyph, m_nContentMidHyph ); } + const sal_uInt8 &CntEndHyph() const { return m_nContentEndHyph; } + const sal_uInt8 &CntMidHyph() const { return m_nContentMidHyph; } + sal_uInt8 &CntEndHyph() { return m_nContentEndHyph; } + sal_uInt8 &CntMidHyph() { return m_nContentMidHyph; } + + /** + * Merge border of the drop portion with modifying the font of + * the portions' part. Removing left or right border. + * @param rPortion drop portion for merge + **/ + static void MergeCharacterBorder( SwDropPortion const & rPortion ); + + /** + * Merge border of the line portion with setting the portion's + * m_bJoinBorderWidthNext and m_bJoinBorderWidthPrev members and + * changing the size (width, height and ascent) of the portion + * to get a merged border. + * @param rPortion portion for merge + * @param pPrev portion immediately before rPortion + * @param rInf contain information + **/ + void MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx new file mode 100644 index 000000000..28eb142d6 --- /dev/null +++ b/sw/source/core/text/itrpaint.cxx @@ -0,0 +1,693 @@ +/* -*- 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 <hintids.hxx> +#include <viewopt.hxx> +#include <tools/multisel.hxx> +#include <editeng/udlnitem.hxx> +#include <pagefrm.hxx> +#include <tgrditem.hxx> + +#include <EnhancedPDFExportHelper.hxx> +#include <IDocumentSettingAccess.hxx> + +#include <viewsh.hxx> +#include "itrpaint.hxx" +#include <txtfrm.hxx> +#include <swfont.hxx> +#include "txtpaint.hxx" +#include "portab.hxx" +#include <txatbase.hxx> +#include <charfmt.hxx> +#include "redlnitr.hxx" +#include "porrst.hxx" +#include "pormulti.hxx" +#include <doc.hxx> + +// Returns, if we have an underline breaking situation +// Adding some more conditions here means you also have to change them +// in SwTextPainter::CheckSpecialUnderline +bool IsUnderlineBreak( const SwLinePortion& rPor, const SwFont& rFnt ) +{ + return LINESTYLE_NONE == rFnt.GetUnderline() || + rPor.IsFlyPortion() || rPor.IsFlyCntPortion() || + rPor.IsBreakPortion() || rPor.IsMarginPortion() || + rPor.IsHolePortion() || + ( rPor.IsMultiPortion() && ! static_cast<const SwMultiPortion&>(rPor).IsBidi() ) || + rFnt.GetEscapement() < 0 || rFnt.IsWordLineMode() || + SvxCaseMap::SmallCaps == rFnt.GetCaseMap(); +} + +static Color GetUnderColor( const SwFont *pFont ) +{ + return pFont->GetUnderColor() == COL_AUTO ? + pFont->GetColor() : pFont->GetUnderColor(); +} + +void SwTextPainter::CtorInitTextPainter( SwTextFrame *pNewFrame, SwTextPaintInfo *pNewInf ) +{ + CtorInitTextCursor( pNewFrame, pNewInf ); + m_pInf = pNewInf; + SwFont *pMyFnt = GetFnt(); + GetInfo().SetFont( pMyFnt ); + bPaintDrop = false; +} + +SwLinePortion *SwTextPainter::CalcPaintOfst( const SwRect &rPaint ) +{ + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + GetInfo().SetPaintOfst( 0 ); + SwTwips nPaintOfst = rPaint.Left(); + + // nPaintOfst was exactly set to the end, therefore <= + // nPaintOfst is document global, therefore add up nLeftMar + // const sal_uInt16 nLeftMar = sal_uInt16(GetLeftMargin()); + // 8310: paint of LineBreaks in empty lines. + if( nPaintOfst && m_pCurr->Width() ) + { + SwLinePortion *pLast = nullptr; + // 7529 and 4757: not <= nPaintOfst + while( pPor && GetInfo().X() + pPor->Width() + (pPor->Height()/2) + < nPaintOfst ) + { + if( pPor->InSpaceGrp() && GetInfo().GetSpaceAdd() ) + { + long nTmp = GetInfo().X() +pPor->Width() + + pPor->CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() ); + if( nTmp + (pPor->Height()/2) >= nPaintOfst ) + break; + GetInfo().X( nTmp ); + GetInfo().SetIdx( GetInfo().GetIdx() + pPor->GetLen() ); + } + else + pPor->Move( GetInfo() ); + pLast = pPor; + pPor = pPor->GetNextPortion(); + } + + // 7529: if PostIts return also pLast. + if( pLast && !pLast->Width() && pLast->IsPostItsPortion() ) + { + pPor = pLast; + GetInfo().SetIdx( GetInfo().GetIdx() - pPor->GetLen() ); + } + } + return pPor; +} + +// There are two possibilities to output transparent font: +// 1) DrawRect on the whole line and DrawText afterwards +// (objectively fast, subjectively slow) +// 2) For every portion a DrawRect with subsequent DrawText is done +// (objectively slow, subjectively fast) +// Since the user usually judges subjectively the second method is set as default. +void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, + const bool bUnderSized ) +{ +#if OSL_DEBUG_LEVEL > 1 +// sal_uInt16 nFntHeight = GetInfo().GetFont()->GetHeight( GetInfo().GetVsh(), GetInfo().GetOut() ); +// sal_uInt16 nFntAscent = GetInfo().GetFont()->GetAscent( GetInfo().GetVsh(), GetInfo().GetOut() ); +#endif + + // maybe catch-up adjustment + GetAdjusted(); + GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() ); + GetInfo().ResetSpaceIdx(); + GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() ); + GetInfo().ResetKanaIdx(); + // The size of the frame + GetInfo().SetIdx( GetStart() ); + GetInfo().SetPos( GetTopLeft() ); + + const bool bDrawInWindow = GetInfo().OnWin(); + + // 6882: blank lines can't be optimized by removing them if Formatting Marks are shown + const bool bEndPor = GetInfo().GetOpt().IsParagraph() && GetInfo().GetText().isEmpty(); + + SwLinePortion *pPor = bEndPor ? m_pCurr->GetFirstPortion() : CalcPaintOfst( rPaint ); + + // Optimization! + SwTwips nMaxRight = std::min( rPaint.Right(), Right() ); + const SwTwips nTmpLeft = GetInfo().X(); + //compatibility setting: allow tabstop text to exceed right margin + if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN)) + { + SwLinePortion* pPorIter = pPor; + while( pPorIter ) + { + if( pPorIter->InTabGrp() ) + { + const SwTabPortion* pTabPor = static_cast<SwTabPortion*>(pPorIter); + const SwTwips nTabPos = nTmpLeft + pTabPor->GetTabPos(); + if( nMaxRight < nTabPos ) + { + nMaxRight = rPaint.Right(); + break; + } + } + pPorIter = pPorIter->GetNextPortion(); + } + } + if( !bEndPor && nTmpLeft >= nMaxRight ) + return; + + // DropCaps! + // 7538: of course for the printer, too + if( !bPaintDrop ) + { + // 8084: Optimization, less painting + // AMA: By 8084 7538 has been revived + // bDrawInWindow removed, so that DropCaps also can be printed + bPaintDrop = pPor == m_pCurr->GetFirstPortion() + && GetDropLines() >= GetLineNr(); + } + + sal_uInt16 nTmpHeight, nTmpAscent; + CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + + // bClip decides if there's a need to clip + // The whole thing must be done before retouching + + bool bClip = ( bDrawInWindow || bUnderSized ) && !rClip.IsChg(); + if( bClip && pPor ) + { + // If TopLeft or BottomLeft of the line are outside, the we must clip. + // The check for Right() is done in the output loop ... + + if( GetInfo().GetPos().X() < rPaint.Left() || + GetInfo().GetPos().Y() < rPaint.Top() || + GetInfo().GetPos().Y() + nTmpHeight > rPaint.Top() + rPaint.Height() ) + { + bClip = false; + rClip.ChgClip( rPaint, m_pFrame, m_pCurr->HasUnderscore() ); + } +#if OSL_DEBUG_LEVEL > 1 + static bool bClipAlways = false; + if( bClip && bClipAlways ) + { bClip = false; + rClip.ChgClip( rPaint ); + } +#endif + } + + // Alignment + OutputDevice* pOut = GetInfo().GetOut(); + Point aPnt1( nTmpLeft, GetInfo().GetPos().Y() ); + if ( aPnt1.X() < rPaint.Left() ) + aPnt1.setX( rPaint.Left() ); + if ( aPnt1.Y() < rPaint.Top() ) + aPnt1.setY( rPaint.Top() ); + Point aPnt2( GetInfo().GetPos().X() + nMaxRight - GetInfo().X(), + GetInfo().GetPos().Y() + nTmpHeight ); + if ( aPnt2.X() > rPaint.Right() ) + aPnt2.setX( rPaint.Right() ); + if ( aPnt2.Y() > rPaint.Bottom() ) + aPnt2.setY( rPaint.Bottom() ); + + const SwRect aLineRect( aPnt1, aPnt2 ); + + if( m_pCurr->IsClipping() ) + { + const SwTextFrame& rFrame = *GetInfo().GetTextFrame(); + // tdf#117448 at small fixed line height, enlarge clipping area in table cells + // to show previously clipped text content on the area of paragraph margins + if ( rFrame.IsInTab() ) + rClip.ChgClip( aLineRect, m_pFrame, false, rFrame.GetTopMargin(), rFrame.GetBottomMargin() ); + else + rClip.ChgClip( aLineRect, m_pFrame ); + bClip = false; + } + + if( !pPor && !bEndPor ) + return; + + // Baseline output also if non-TextPortion (compare TabPor with Fill) + // if no special vertical alignment is used, + // we calculate Y value for the whole line + SwTextGridItem const*const pGrid(GetGridItem(GetTextFrame()->FindPageFrame())); + const bool bAdjustBaseLine = + GetLineInfo().HasSpecialAlign( GetTextFrame()->IsVertical() ) || + ( nullptr != pGrid ); + const SwTwips nLineBaseLine = GetInfo().GetPos().Y() + nTmpAscent; + if ( ! bAdjustBaseLine ) + GetInfo().Y( nLineBaseLine ); + + // 7529: Pre-paint post-its + if( GetInfo().OnWin() && pPor && !pPor->Width() ) + { + SeekAndChg( GetInfo() ); + + if( bAdjustBaseLine ) + { + const SwTwips nOldY = GetInfo().Y(); + + GetInfo().Y( GetInfo().GetPos().Y() + AdjustBaseLine( *m_pCurr, nullptr, + GetInfo().GetFont()->GetHeight( GetInfo().GetVsh(), *pOut ), + GetInfo().GetFont()->GetAscent( GetInfo().GetVsh(), *pOut ) + ) ); + + pPor->PrePaint( GetInfo(), pPor ); + GetInfo().Y( nOldY ); + } + else + pPor->PrePaint( GetInfo(), pPor ); + } + + // 7923: EndPortions output chars, too, that's why we change the font + if( bEndPor ) + SeekStartAndChg( GetInfo() ); + + const bool bRest = m_pCurr->IsRest(); + bool bFirst = true; + + SwArrowPortion *pArrow = nullptr; + // Reference portion for the paragraph end portion + SwLinePortion* pEndTempl = m_pCurr->GetFirstPortion(); + + while( pPor ) + { + bool bSeeked = true; + GetInfo().SetLen( pPor->GetLen() ); + + const SwTwips nOldY = GetInfo().Y(); + + if ( bAdjustBaseLine ) + { + GetInfo().Y( GetInfo().GetPos().Y() + AdjustBaseLine( *m_pCurr, pPor ) ); + + // we store the last portion, because a possible paragraph + // end character has the same font as this portion + // (only in special vertical alignment case, otherwise the first + // portion of the line is used) + if ( pPor->Width() && pPor->InTextGrp() ) + pEndTempl = pPor; + } + + // A special case are GluePortions which output blanks. + + // 6168: Avoid that the rest of a FieldPortion gets the attributes of the + // next portion with SeekAndChgBefore(): + if( bRest && pPor->InFieldGrp() && !pPor->GetLen() ) + SeekAndChgBefore( GetInfo() ); + else if ( pPor->IsQuoVadisPortion() ) + { + // A remark on QuoVadis/ErgoSum: + // We use the Font set for the Paragraph for these portions. + // Thus, we initialize: + TextFrameIndex nOffset = GetInfo().GetIdx(); + SeekStartAndChg( GetInfo(), true ); + if( GetRedln() && m_pCurr->HasRedline() ) + { + std::pair<SwTextNode const*, sal_Int32> const pos( + GetTextFrame()->MapViewToModel(nOffset)); + GetRedln()->Seek(*m_pFont, pos.first->GetIndex(), pos.second, 0); + } + } + else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() ) + SeekAndChg( GetInfo() ); + else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() ) + { + // Paragraph symbols should have the same font as the paragraph in front of them, + // except for the case that there's redlining in the paragraph + if( GetRedln() ) + SeekAndChg( GetInfo() ); + else + SeekAndChgBefore( GetInfo() ); + } + else + bSeeked = false; + + // bRest = false; + + // If the end of the portion juts out, it is clipped. + // A safety distance of half the height is added, so that + // TTF-"f" isn't overlapping into the page margin. + if( bClip && + GetInfo().X() + pPor->Width() + ( pPor->Height() / 2 ) > nMaxRight ) + { + bClip = false; + rClip.ChgClip( rPaint, m_pFrame, m_pCurr->HasUnderscore() ); + } + + // Portions, which lay "below" the text like post-its + SwLinePortion *pNext = pPor->GetNextPortion(); + if( GetInfo().OnWin() && pNext && !pNext->Width() ) + { + // Fix 11289: Fields were omitted here because of Last!=Owner during + // loading Brief.sdw. Now the fields are allowed again, + // by bSeeked Last!=Owner is being avoided. + if ( !bSeeked ) + SeekAndChg( GetInfo() ); + pNext->PrePaint( GetInfo(), pPor ); + } + + // We calculate a separate font for underlining. + CheckSpecialUnderline( pPor, bAdjustBaseLine ? nOldY : 0 ); + SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt(); + if ( pUnderLineFnt ) + { + const Point aTmpPoint( GetInfo().X(), + bAdjustBaseLine ? + pUnderLineFnt->GetPos().Y() : + nLineBaseLine ); + pUnderLineFnt->SetPos( aTmpPoint ); + } + + // in extended input mode we do not want a common underline font. + SwUnderlineFont* pOldUnderLineFnt = nullptr; + if ( GetRedln() && GetRedln()->ExtOn() ) + { + pOldUnderLineFnt = GetInfo().GetUnderFnt(); + GetInfo().SetUnderFnt( nullptr ); + } + + { + // #i16816# tagged pdf support + Por_Info aPorInfo( *pPor, *this ); + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, &aPorInfo, *pOut ); + + if( pPor->IsMultiPortion() ) + PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor) ); + else + pPor->Paint( GetInfo() ); + } + + // reset underline font + if ( pOldUnderLineFnt ) + GetInfo().SetUnderFnt( pOldUnderLineFnt ); + + // reset (for special vertical alignment) + GetInfo().Y( nOldY ); + + bFirst &= !pPor->GetLen(); + if( pNext || !pPor->IsMarginPortion() ) + pPor->Move( GetInfo() ); + if( pPor->IsArrowPortion() && GetInfo().OnWin() && !pArrow ) + pArrow = static_cast<SwArrowPortion*>(pPor); + + pPor = bDrawInWindow || GetInfo().X() <= nMaxRight || + // #i16816# tagged pdf support + ( GetInfo().GetVsh() && + GetInfo().GetVsh()->GetViewOptions()->IsPDFExport() && + pNext && pNext->IsHolePortion() ) ? + pNext : + nullptr; + } + + // delete underline font + delete GetInfo().GetUnderFnt(); + GetInfo().SetUnderFnt( nullptr ); + + // paint remaining stuff + if( bDrawInWindow ) + { + // If special vertical alignment is enabled, GetInfo().Y() is the + // top of the current line. Therefore is has to be adjusted for + // the painting of the remaining stuff. We first store the old value. + const SwTwips nOldY = GetInfo().Y(); + + if( !GetNextLine() && + GetInfo().GetVsh() && !GetInfo().GetVsh()->IsPreview() && + GetInfo().GetOpt().IsParagraph() && !GetTextFrame()->GetFollow() && + GetInfo().GetIdx() >= TextFrameIndex(GetInfo().GetText().getLength())) + { + const SwTmpEndPortion aEnd( *pEndTempl ); + GetFnt()->ChgPhysFnt( GetInfo().GetVsh(), *pOut ); + + if ( bAdjustBaseLine ) + GetInfo().Y( GetInfo().GetPos().Y() + + AdjustBaseLine( *m_pCurr, &aEnd ) ); + GetInfo().X( GetInfo().X() + + ( GetCurr()->IsHanging() ? GetCurr()->GetHangingMargin() : 0 ) ); + aEnd.Paint( GetInfo() ); + GetInfo().Y( nOldY ); + } + if( GetInfo().GetVsh() && !GetInfo().GetVsh()->IsPreview() ) + { + const bool bNextUndersized = + ( GetTextFrame()->GetNext() && + 0 == GetTextFrame()->GetNext()->getFramePrintArea().Height() && + GetTextFrame()->GetNext()->IsTextFrame() && + static_cast<SwTextFrame*>(GetTextFrame()->GetNext())->IsUndersized() ) ; + + if( bUnderSized || bNextUndersized ) + { + if ( bAdjustBaseLine ) + GetInfo().Y( GetInfo().GetPos().Y() + m_pCurr->GetAscent() ); + + // Left arrow (text overflowing) + if( pArrow ) + GetInfo().DrawRedArrow( *pArrow ); + + // GetInfo().Y() must be current baseline + SwTwips nDiff = GetInfo().Y() + nTmpHeight - nTmpAscent - GetTextFrame()->getFrameArea().Bottom(); + if( ( nDiff > 0 && + (GetEnd() < TextFrameIndex(GetInfo().GetText().getLength()) || + ( nDiff > nTmpHeight/2 && GetPrevLine() ) ) ) || + (nDiff >= 0 && bNextUndersized) ) + + { + // Right arrow (text overflowing) + SwArrowPortion aArrow( GetInfo() ); + GetInfo().DrawRedArrow( aArrow ); + } + + GetInfo().Y( nOldY ); + } + } + } + + if( m_pCurr->IsClipping() ) + rClip.ChgClip( rPaint, m_pFrame ); +} + +void SwTextPainter::CheckSpecialUnderline( const SwLinePortion* pPor, + long nAdjustBaseLine ) +{ + // Check if common underline should not be continued + if ( IsUnderlineBreak( *pPor, *m_pFont ) ) + { + // delete underline font + delete GetInfo().GetUnderFnt(); + GetInfo().SetUnderFnt( nullptr ); + return; + } + // Reuse calculated underline font as much as possible. + if (GetInfo().GetUnderFnt() && + GetInfo().GetIdx() + pPor->GetLen() <= GetInfo().GetUnderFnt()->GetEnd() + TextFrameIndex(1)) + { + SwFont &rFont = GetInfo().GetUnderFnt()->GetFont(); + const Color aColor = GetUnderColor( GetInfo().GetFont() ); + if ( GetUnderColor( &rFont ) != aColor ) + rFont.SetColor( aColor ); + return; + } + + // If current underline matches the common underline font, we continue + // to use the common underline font. + // Bug 120769:Color of underline display wrongly + if ( GetInfo().GetUnderFnt() && + GetInfo().GetUnderFnt()->GetFont().GetUnderline() == GetFnt()->GetUnderline() && + GetInfo().GetFont() && GetInfo().GetFont()->GetUnderColor() != COL_AUTO ) + return; + //Bug 120769(End) + + OSL_ENSURE( GetFnt() && LINESTYLE_NONE != GetFnt()->GetUnderline(), + "CheckSpecialUnderline without underlined font" ); + MultiSelection aUnderMulti( Range( 0, GetInfo().GetText().getLength() ) ); + const SwFont* pParaFnt = GetAttrHandler().GetFont(); + if( pParaFnt && pParaFnt->GetUnderline() == GetFnt()->GetUnderline() ) + aUnderMulti.SelectAll(); + + if (sw::MergedPara const*const pMerged = GetTextFrame()->GetMergedPara()) + { + // first, add the paragraph properties to MultiSelection - if there are + // Hints too, they will override the positions if they're added later + sal_Int32 nTmp(0); + for (auto const& e : pMerged->extents) + { + const SfxPoolItem* pItem; + if (SfxItemState::SET == e.pNode->GetSwAttrSet().GetItemState( + RES_CHRATR_UNDERLINE, true, &pItem)) + { + const bool bUnderSelect(m_pFont->GetUnderline() == + static_cast<SvxUnderlineItem const*>(pItem)->GetLineStyle()); + aUnderMulti.Select(Range(nTmp, nTmp + e.nEnd - e.nStart - 1), + bUnderSelect); + } + nTmp += e.nEnd - e.nStart; + } + } + + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*GetTextFrame()); + for (SwTextAttr const* pTextAttr = iter.NextAttr(&pNode); pTextAttr; + pTextAttr = iter.NextAttr(&pNode)) + { + SvxUnderlineItem const*const pItem = + CharFormat::GetItem(*pTextAttr, RES_CHRATR_UNDERLINE); + + if (pItem) + { + TextFrameIndex const nStart( + GetTextFrame()->MapModelToView(pNode, pTextAttr->GetStart())); + TextFrameIndex const nEnd( + GetTextFrame()->MapModelToView(pNode, *pTextAttr->End())); + if (nEnd > nStart) + { + const bool bUnderSelect = m_pFont->GetUnderline() == pItem->GetLineStyle(); + aUnderMulti.Select(Range(sal_Int32(nStart), sal_Int32(nEnd) - 1), + bUnderSelect); + } + } + } + + const TextFrameIndex nIndx = GetInfo().GetIdx(); + TextFrameIndex nUnderEnd(0); + const size_t nCnt = aUnderMulti.GetRangeCount(); + + // find the underline range the current portion is contained in + for( size_t i = 0; i < nCnt; ++i ) + { + const Range& rRange = aUnderMulti.GetRange( i ); + if (nUnderEnd == TextFrameIndex(rRange.Min())) + nUnderEnd = TextFrameIndex(rRange.Max()); + else if (nIndx >= TextFrameIndex(rRange.Min())) + { + nUnderEnd = TextFrameIndex(rRange.Max()); + } + else + break; + } + + if ( GetEnd() && GetEnd() <= nUnderEnd ) + nUnderEnd = GetEnd() - TextFrameIndex(1); + + // calculate the new common underline font + SwFont* pUnderlineFnt = nullptr; + Point aCommonBaseLine; + + // check, if underlining is not isolated + if (nIndx + GetInfo().GetLen() < nUnderEnd + TextFrameIndex(1)) + { + // here starts the algorithm for calculating the underline font + SwScriptInfo& rScriptInfo = GetInfo().GetParaPortion()->GetScriptInfo(); + SwAttrIter aIter(*GetInfo().GetTextFrame()->GetTextNodeFirst(), + rScriptInfo, GetTextFrame()); + + TextFrameIndex nTmpIdx = nIndx; + sal_uLong nSumWidth = 0; + sal_uLong nSumHeight = 0; + sal_uLong nBold = 0; + sal_uInt16 nMaxBaseLineOfst = 0; + int nNumberOfPortions = 0; + + while (nTmpIdx <= nUnderEnd && pPor) + { + if ( pPor->IsFlyPortion() || pPor->IsFlyCntPortion() || + pPor->IsBreakPortion() || pPor->IsMarginPortion() || + pPor->IsHolePortion() || + ( pPor->IsMultiPortion() && ! static_cast<const SwMultiPortion*>(pPor)->IsBidi() ) ) + break; + + aIter.Seek( nTmpIdx ); + if ( aIter.GetFnt()->GetEscapement() < 0 || m_pFont->IsWordLineMode() || + SvxCaseMap::SmallCaps == m_pFont->GetCaseMap() ) + break; + + if ( !aIter.GetFnt()->GetEscapement() ) + { + nSumWidth += pPor->Width(); + const sal_uLong nFontHeight = aIter.GetFnt()->GetHeight(); + + // If we do not have a common baseline we take the baseline + // and the font of the lowest portion. + if ( nAdjustBaseLine ) + { + const sal_uInt16 nTmpBaseLineOfst = AdjustBaseLine( *m_pCurr, pPor ); + if ( nMaxBaseLineOfst < nTmpBaseLineOfst ) + { + nMaxBaseLineOfst = nTmpBaseLineOfst; + nSumHeight = nFontHeight; + } + } + // in horizontal layout we build a weighted sum of the heights + else + nSumHeight += pPor->Width() * nFontHeight; + + if ( WEIGHT_NORMAL != aIter.GetFnt()->GetWeight() ) + nBold += pPor->Width(); + } + + ++nNumberOfPortions; + + nTmpIdx += pPor->GetLen(); + pPor = pPor->GetNextPortion(); + } + + // resulting height + if ( nNumberOfPortions > 1 && nSumWidth ) + { + const sal_uLong nNewFontHeight = nAdjustBaseLine ? + nSumHeight : + nSumHeight / nSumWidth; + + pUnderlineFnt = new SwFont( *GetInfo().GetFont() ); + + // font height + const SwFontScript nActual = pUnderlineFnt->GetActual(); + pUnderlineFnt->SetSize( Size( pUnderlineFnt->GetSize( nActual ).Width(), + nNewFontHeight ), nActual ); + + // font weight + if ( 2 * nBold > nSumWidth ) + pUnderlineFnt->SetWeight( WEIGHT_BOLD, nActual ); + else + pUnderlineFnt->SetWeight( WEIGHT_NORMAL, nActual ); + + // common base line + aCommonBaseLine.setY( nAdjustBaseLine + nMaxBaseLineOfst ); + } + } + + // an escaped redlined portion should also have a special underlining + if( ! pUnderlineFnt && m_pFont->GetEscapement() > 0 && GetRedln() && + GetRedln()->ChkSpecialUnderline() ) + pUnderlineFnt = new SwFont( *m_pFont ); + + delete GetInfo().GetUnderFnt(); + + if ( pUnderlineFnt ) + { + pUnderlineFnt->SetProportion( 100 ); + pUnderlineFnt->SetEscapement( 0 ); + pUnderlineFnt->SetStrikeout( STRIKEOUT_NONE ); + pUnderlineFnt->SetOverline( LINESTYLE_NONE ); + const Color aFillColor( COL_TRANSPARENT ); + pUnderlineFnt->SetFillColor( aFillColor ); + + GetInfo().SetUnderFnt( new SwUnderlineFont( *pUnderlineFnt, nUnderEnd, + aCommonBaseLine ) ); + } + else + // I'm sorry, we do not have a special underlining font for you. + GetInfo().SetUnderFnt( nullptr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrpaint.hxx b/sw/source/core/text/itrpaint.hxx new file mode 100644 index 000000000..2256c3928 --- /dev/null +++ b/sw/source/core/text/itrpaint.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include "itrtxt.hxx" + +class SwSaveClip; // SwTextPainter +class SwMultiPortion; + +class SwTextPainter : public SwTextCursor +{ + bool bPaintDrop; + + SwLinePortion *CalcPaintOfst( const SwRect &rPaint ); + void CheckSpecialUnderline( const SwLinePortion* pPor, + long nAdjustBaseLine = 0 ); +protected: + void CtorInitTextPainter( SwTextFrame *pFrame, SwTextPaintInfo *pInf ); + explicit SwTextPainter(SwTextNode const * pTextNode) + : SwTextCursor(pTextNode) + , bPaintDrop(false) + { + } + +public: + SwTextPainter(SwTextFrame *pTextFrame, SwTextPaintInfo *pTextPaintInf) + : SwTextCursor(pTextFrame->GetTextNodeFirst()) + { + CtorInitTextPainter( pTextFrame, pTextPaintInf ); + } + void DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, + const bool bUnderSz ); + void PaintDropPortion(); + // if PaintMultiPortion is called recursively, we have to pass the + // surrounding SwBidiPortion + void PaintMultiPortion( const SwRect &rPaint, SwMultiPortion& rMulti, + const SwMultiPortion* pEnvPor = nullptr ); + void SetPaintDrop( const bool bNew ) { bPaintDrop = bNew; } + bool IsPaintDrop() const { return bPaintDrop; } + SwTextPaintInfo &GetInfo() + { return static_cast<SwTextPaintInfo&>(SwTextIter::GetInfo()); } + const SwTextPaintInfo &GetInfo() const + { return static_cast<const SwTextPaintInfo&>(SwTextIter::GetInfo()); } +}; + +bool IsUnderlineBreak( const SwLinePortion& rPor, const SwFont& rFnt ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrtxt.cxx b/sw/source/core/text/itrtxt.cxx new file mode 100644 index 000000000..b285307a8 --- /dev/null +++ b/sw/source/core/text/itrtxt.cxx @@ -0,0 +1,421 @@ +/* -*- 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 <ndtxt.hxx> +#include <txatbase.hxx> +#include <paratr.hxx> +#include <vcl/outdev.hxx> +#include <editeng/paravertalignitem.hxx> + +#include "pormulti.hxx" +#include <pagefrm.hxx> +#include <tgrditem.hxx> +#include "porfld.hxx" + +#include "itrtxt.hxx" +#include <txtfrm.hxx> + +void SwTextIter::CtorInitTextIter( SwTextFrame *pNewFrame, SwTextInfo *pNewInf ) +{ + assert(pNewFrame->GetPara()); + + CtorInitAttrIter( *pNewFrame->GetTextNodeFirst(), pNewFrame->GetPara()->GetScriptInfo(), pNewFrame ); + + SwTextNode const*const pNode = pNewFrame->GetTextNodeForParaProps(); + + m_pFrame = pNewFrame; + m_pInf = pNewInf; + m_aLineInf.CtorInitLineInfo( pNode->GetSwAttrSet(), *pNode ); + m_nFrameStart = m_pFrame->getFrameArea().Pos().Y() + m_pFrame->getFramePrintArea().Pos().Y(); + SwTextIter::Init(); + + // Order is important: only execute FillRegister if GetValue!=0 + m_bRegisterOn = pNode->GetSwAttrSet().GetRegister().GetValue() + && m_pFrame->FillRegister( m_nRegStart, m_nRegDiff ); +} + +void SwTextIter::Init() +{ + m_pCurr = m_pInf->GetParaPortion(); + m_nStart = m_pInf->GetTextStart(); + m_nY = m_nFrameStart; + m_bPrev = true; + m_pPrev = nullptr; + m_nLineNr = 1; +} + +void SwTextIter::CalcAscentAndHeight( sal_uInt16 &rAscent, sal_uInt16 &rHeight ) const +{ + rHeight = GetLineHeight(); + rAscent = m_pCurr->GetAscent() + rHeight - m_pCurr->Height(); +} + +SwLineLayout *SwTextIter::GetPrev_() +{ + m_pPrev = nullptr; + m_bPrev = true; + SwLineLayout *pLay = m_pInf->GetParaPortion(); + if( m_pCurr == pLay ) + return nullptr; + while( pLay->GetNext() != m_pCurr ) + pLay = pLay->GetNext(); + return m_pPrev = pLay; +} + +const SwLineLayout *SwTextIter::GetPrev() +{ + if(! m_bPrev) + GetPrev_(); + return m_pPrev; +} + +const SwLineLayout *SwTextIter::Prev() +{ + if( !m_bPrev ) + GetPrev_(); + if( m_pPrev ) + { + m_bPrev = false; + m_pCurr = m_pPrev; + m_nStart = m_nStart - m_pCurr->GetLen(); + m_nY = m_nY - GetLineHeight(); + if( !m_pCurr->IsDummy() && !(--m_nLineNr) ) + ++m_nLineNr; + return m_pCurr; + } + else + return nullptr; +} + +const SwLineLayout *SwTextIter::Next() +{ + if(m_pCurr->GetNext()) + { + m_pPrev = m_pCurr; + m_bPrev = true; + m_nStart = m_nStart + m_pCurr->GetLen(); + m_nY += GetLineHeight(); + if( m_pCurr->GetLen() || ( m_nLineNr>1 && !m_pCurr->IsDummy() ) ) + ++m_nLineNr; + return m_pCurr = m_pCurr->GetNext(); + } + else + return nullptr; +} + +const SwLineLayout *SwTextIter::NextLine() +{ + const SwLineLayout *pNext = Next(); + while( pNext && pNext->IsDummy() && pNext->GetNext() ) + { + pNext = Next(); + } + return pNext; +} + +const SwLineLayout *SwTextIter::GetNextLine() const +{ + const SwLineLayout *pNext = m_pCurr->GetNext(); + while( pNext && pNext->IsDummy() && pNext->GetNext() ) + { + pNext = pNext->GetNext(); + } + return pNext; +} + +const SwLineLayout *SwTextIter::GetPrevLine() +{ + const SwLineLayout *pRoot = m_pInf->GetParaPortion(); + if( pRoot == m_pCurr ) + return nullptr; + const SwLineLayout *pLay = pRoot; + + while( pLay->GetNext() != m_pCurr ) + pLay = pLay->GetNext(); + + if( pLay->IsDummy() ) + { + const SwLineLayout *pTmp = pRoot; + pLay = pRoot->IsDummy() ? nullptr : pRoot; + while( pTmp->GetNext() != m_pCurr ) + { + if( !pTmp->IsDummy() ) + pLay = pTmp; + pTmp = pTmp->GetNext(); + } + } + + // If nothing has changed, then there are only dummy's + return pLay; +} + +const SwLineLayout *SwTextIter::PrevLine() +{ + const SwLineLayout *pMyPrev = Prev(); + if( !pMyPrev ) + return nullptr; + + const SwLineLayout *pLast = pMyPrev; + while( pMyPrev && pMyPrev->IsDummy() ) + { + pLast = pMyPrev; + pMyPrev = Prev(); + } + return pMyPrev ? pMyPrev : pLast; +} + +void SwTextIter::Bottom() +{ + while( Next() ) + { + // nothing + } +} + +void SwTextIter::CharToLine(TextFrameIndex const nChar) +{ + while( m_nStart + m_pCurr->GetLen() <= nChar && Next() ) + ; + while( m_nStart > nChar && Prev() ) + ; +} + +// 1170: takes into account ambiguities: +const SwLineLayout *SwTextCursor::CharCursorToLine(TextFrameIndex const nPosition) +{ + CharToLine( nPosition ); + if( nPosition != m_nStart ) + bRightMargin = false; + bool bPrevious = bRightMargin && m_pCurr->GetLen() && GetPrev() && + GetPrev()->GetLen(); + if (bPrevious && nPosition && CH_BREAK == GetInfo().GetChar(nPosition - TextFrameIndex(1))) + bPrevious = false; + return bPrevious ? PrevLine() : m_pCurr; +} + +sal_uInt16 SwTextCursor::AdjustBaseLine( const SwLineLayout& rLine, + const SwLinePortion* pPor, + sal_uInt16 nPorHeight, sal_uInt16 nPorAscent, + const bool bAutoToCentered ) const +{ + if ( pPor ) + { + nPorHeight = pPor->Height(); + nPorAscent = pPor->GetAscent(); + } + + sal_uInt16 nOfst = rLine.GetRealHeight() - rLine.Height(); + + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + + if ( pGrid && GetInfo().SnapToGrid() && pGrid->IsSquaredMode() ) + { + const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight(); + const bool bRubyTop = ! pGrid->GetRubyTextBelow(); + + if ( GetInfo().IsMulti() ) + // we are inside the GetCharRect recursion for multi portions + // we center the portion in its surrounding line + nOfst = ( m_pCurr->Height() - nPorHeight ) / 2 + nPorAscent; + else + { + // We have to take care for ruby portions. + // The ruby portion is NOT centered + nOfst = nOfst + nPorAscent; + + if ( ! pPor || ! pPor->IsMultiPortion() || + ! static_cast<const SwMultiPortion*>(pPor)->IsRuby() ) + { + // Portions which are bigger than on grid distance are + // centered inside the whole line. + + //for text refactor + const sal_uInt16 nLineNet = rLine.Height() - nRubyHeight; + //const sal_uInt16 nLineNet = ( nPorHeight > nGridWidth ) ? + // rLine.Height() - nRubyHeight : + // nGridWidth; + nOfst += ( nLineNet - nPorHeight ) / 2; + if ( bRubyTop ) + nOfst += nRubyHeight; + } + } + } + else + { + switch ( GetLineInfo().GetVertAlign() ) { + case SvxParaVertAlignItem::Align::Top : + nOfst = nOfst + nPorAscent; + break; + case SvxParaVertAlignItem::Align::Center : + OSL_ENSURE( rLine.Height() >= nPorHeight, "Portion height > Line height"); + nOfst += ( rLine.Height() - nPorHeight ) / 2 + nPorAscent; + break; + case SvxParaVertAlignItem::Align::Bottom : + nOfst += rLine.Height() - nPorHeight + nPorAscent; + break; + case SvxParaVertAlignItem::Align::Automatic : + if ( bAutoToCentered || GetInfo().GetTextFrame()->IsVertical() ) + { + // Vertical text has these cases to calculate the baseline: + // - Implicitly TB and RL: the origo is the top right corner, offset is the + // ascent. + // - (Implicitly TB and) LR: the origo is the top left corner, offset is the + // descent. + // - BT and LR: the origo is the bottom left corner, offset is the ascent. + if (GetInfo().GetTextFrame()->IsVertLR() && !GetInfo().GetTextFrame()->IsVertLRBT()) + nOfst += rLine.Height() - ( rLine.Height() - nPorHeight ) / 2 - nPorAscent; + else + nOfst += ( rLine.Height() - nPorHeight ) / 2 + nPorAscent; + break; + } + [[fallthrough]]; + case SvxParaVertAlignItem::Align::Baseline : + // base line + nOfst = nOfst + rLine.GetAscent(); + break; + } + } + + return nOfst; +} + +void SwTextIter::TwipsToLine( const SwTwips y) +{ + while( m_nY + GetLineHeight() <= y && Next() ) + ; + while( m_nY > y && Prev() ) + ; +} + +// Local helper function to check, if pCurr needs a field rest portion: +static bool lcl_NeedsFieldRest( const SwLineLayout* pCurr ) +{ + const SwLinePortion *pPor = pCurr->GetNextPortion(); + bool bRet = false; + while( pPor && !bRet ) + { + bRet = pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->HasFollow(); + if( !pPor->GetNextPortion() || !pPor->GetNextPortion()->InFieldGrp() ) + break; + pPor = pPor->GetNextPortion(); + } + return bRet; +} + +void SwTextIter::TruncLines( bool bNoteFollow ) +{ + SwLineLayout *pDel = m_pCurr->GetNext(); + TextFrameIndex const nEnd = m_nStart + m_pCurr->GetLen(); + + if( pDel ) + { + m_pCurr->SetNext( nullptr ); + if (MaybeHasHints() && bNoteFollow) + { + GetInfo().GetParaPortion()->SetFollowField( pDel->IsRest() || + lcl_NeedsFieldRest( m_pCurr ) ); + + // bug 88534: wrong positioning of flys + SwTextFrame* pFollow = GetTextFrame()->GetFollow(); + if ( pFollow && ! pFollow->IsLocked() && + nEnd == pFollow->GetOffset() ) + { + TextFrameIndex nRangeEnd = nEnd; + SwLineLayout* pLine = pDel; + + // determine range to be searched for flys anchored as characters + while ( pLine ) + { + nRangeEnd = nRangeEnd + pLine->GetLen(); + pLine = pLine->GetNext(); + } + + // examine hints in range nEnd - (nEnd + nRangeChar) + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*GetTextFrame()); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if( RES_TXTATR_FLYCNT == pHt->Which() ) + { + // check if hint is in our range + TextFrameIndex const nTmpPos( + GetTextFrame()->MapModelToView(pNode, pHt->GetStart())); + if ( nEnd <= nTmpPos && nTmpPos < nRangeEnd ) + pFollow->InvalidateRange_( + SwCharRange( nTmpPos, nTmpPos ) ); + } + } + } + } + delete pDel; + } + if( m_pCurr->IsDummy() && + !m_pCurr->GetLen() && + m_nStart < TextFrameIndex(GetTextFrame()->GetText().getLength())) + { + m_pCurr->SetRealHeight( 1 ); + } + if (MaybeHasHints()) + m_pFrame->RemoveFootnote( nEnd ); +} + +void SwTextIter::CntHyphens( sal_uInt8 &nEndCnt, sal_uInt8 &nMidCnt) const +{ + nEndCnt = 0; + nMidCnt = 0; + if ( m_bPrev && m_pPrev && !m_pPrev->IsEndHyph() && !m_pPrev->IsMidHyph() ) + return; + SwLineLayout *pLay = m_pInf->GetParaPortion(); + if( m_pCurr == pLay ) + return; + while( pLay != m_pCurr ) + { + if ( pLay->IsEndHyph() ) + nEndCnt++; + else + nEndCnt = 0; + if ( pLay->IsMidHyph() ) + nMidCnt++; + else + nMidCnt = 0; + pLay = pLay->GetNext(); + } +} + +// Change current output device to formatting device, this has to be done before +// formatting. +SwHookOut::SwHookOut( SwTextSizeInfo& rInfo ) : + pInf( &rInfo ), + pOut( rInfo.GetOut() ), + bOnWin( rInfo.OnWin() ) +{ + OSL_ENSURE( rInfo.GetRefDev(), "No reference device for text formatting" ); + + // set new values + rInfo.SetOut( rInfo.GetRefDev() ); + rInfo.SetOnWin( false ); +} + +SwHookOut::~SwHookOut() +{ + pInf->SetOut( pOut ); + pInf->SetOnWin( bOnWin ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrtxt.hxx b/sw/source/core/text/itrtxt.hxx new file mode 100644 index 000000000..81e67503d --- /dev/null +++ b/sw/source/core/text/itrtxt.hxx @@ -0,0 +1,340 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_ITRTXT_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_ITRTXT_HXX +#include <swtypes.hxx> +#include "itratr.hxx" +#include "inftxt.hxx" + +struct SwPosition; +struct SwCursorMoveState; +class SwMarginPortion; +class SwFlyPortion; + +class SwTextIter : public SwAttrIter +{ +protected: + SwLineInfo m_aLineInf; + SwTextFrame *m_pFrame; + SwTextInfo *m_pInf; + SwLineLayout *m_pCurr; + SwLineLayout *m_pPrev; + SwTwips m_nFrameStart; + SwTwips m_nY; + SwTwips m_nRegStart; // The register's start position (Y) + TextFrameIndex m_nStart; // Start in the text string, end = pCurr->GetLen() + sal_uInt16 m_nRegDiff; // Register's line distance + sal_uInt16 m_nLineNr; // Line number + bool m_bPrev : 1; + bool m_bRegisterOn : 1; // Keep in register + bool m_bOneBlock : 1; // Justified text: Dispose single words + bool m_bLastBlock : 1; // Justified text: Also the last line + bool m_bLastCenter : 1; // Justified text: Center last line + + SwLineLayout *GetPrev_(); + + // Reset in the first line + void Init(); + void CtorInitTextIter( SwTextFrame *pFrame, SwTextInfo *pInf ); + explicit SwTextIter(SwTextNode const * pTextNode) + : SwAttrIter(pTextNode) + , m_pFrame(nullptr) + , m_pInf(nullptr) + , m_pCurr(nullptr) + , m_pPrev(nullptr) + , m_nFrameStart(0) + , m_nY(0) + , m_nRegStart(0) + , m_nStart(0) + , m_nRegDiff(0) + , m_nLineNr(0) + , m_bPrev(false) + , m_bRegisterOn(false) + , m_bOneBlock(false) + , m_bLastBlock(false) + , m_bLastCenter(false) + { + } +public: + SwTextIter(SwTextFrame *pTextFrame, SwTextInfo *pTextInf) + : SwAttrIter(pTextFrame->GetTextNodeFirst()) + , m_bOneBlock(false) + , m_bLastBlock(false) + , m_bLastCenter(false) + { + CtorInitTextIter(pTextFrame, pTextInf); + } + const SwLineLayout *GetCurr() const { return m_pCurr; } // NEVER 0! + const SwLineLayout *GetNext() const { return m_pCurr->GetNext(); } + const SwLineLayout *GetPrev(); + TextFrameIndex GetLength() const { return m_pCurr->GetLen(); } + sal_uInt16 GetLineNr() const { return m_nLineNr; } + TextFrameIndex GetStart() const { return m_nStart; } + TextFrameIndex GetEnd() const { return GetStart() + GetLength(); } + SwTwips Y() const { return m_nY; } + + SwTwips RegStart() const { return m_nRegStart; } + sal_uInt16 RegDiff() const { return m_nRegDiff; } + bool IsRegisterOn() const { return m_bRegisterOn; } + + SwTextInfo &GetInfo() { return *m_pInf; } + const SwTextInfo &GetInfo() const { return *m_pInf; } + + void Top() { Init(); } + void Bottom(); + const SwLineLayout *Next(); + const SwLineLayout *Prev(); + + // Skips the FlyFrames dummy line + const SwLineLayout *NextLine(); + const SwLineLayout *PrevLine(); + const SwLineLayout *GetNextLine() const; + const SwLineLayout *GetPrevLine(); + + void CharToLine(TextFrameIndex); + void TwipsToLine(const SwTwips); + + // Truncates all after pCurr + void TruncLines( bool bNoteFollow = false ); + + sal_uInt16 GetLineHeight() const { return m_pCurr->GetRealHeight(); } + void CalcAscentAndHeight( sal_uInt16 &rAscent, sal_uInt16 &rHeight ) const; + + // Lots of trouble for querying pCurr == pPara + bool IsFirstTextLine() const + { return m_nStart == GetInfo().GetTextStart() && + !( m_pCurr->IsDummy() && GetNextLine() ); } + + // Replacement for the old IsFirstLine() + bool IsParaLine() const + { return m_pCurr == m_pInf->GetParaPortion(); } + + const SwLineInfo &GetLineInfo() const { return m_aLineInf; } + SwTwips GetFirstPos() const { return m_nFrameStart; } + inline bool SeekAndChg( SwTextSizeInfo &rInf ); + inline bool SeekAndChgBefore( SwTextSizeInfo &rInf ); + inline bool SeekStartAndChg( SwTextSizeInfo &rInf, const bool bPara=false ); + + SwTextFrame *GetTextFrame() { return m_pFrame; } + const SwTextFrame *GetTextFrame() const { return m_pFrame; } + + // Counts consecutive hyphens in order to be within the boundary given by MaxHyphens + void CntHyphens( sal_uInt8 &nEndCnt, sal_uInt8 &nMidCnt) const; +}; + +class SwTextMargin : public SwTextIter +{ +private: + SwTwips nLeft; + SwTwips nRight; + SwTwips nFirst; + sal_uInt16 nDropLeft; + sal_uInt16 nDropHeight; + sal_uInt16 nDropDescent; + sal_uInt16 nDropLines; + SvxAdjust nAdjust; + // #i91133# + SwTwips mnTabLeft; + +protected: + // For FormatQuoVadis + void Right( const SwTwips nNew ) { nRight = nNew; } + + void CtorInitTextMargin( SwTextFrame *pFrame, SwTextSizeInfo *pInf ); + explicit SwTextMargin(SwTextNode const * pTextNode) + : SwTextIter(pTextNode) + , nLeft(0) + , nRight(0) + , nFirst(0) + , nDropLeft(0) + , nDropHeight(0) + , nDropDescent(0) + , nDropLines(0) + , nAdjust(SvxAdjust::Left) + , mnTabLeft(0) + { + } +public: + SwTextMargin(SwTextFrame *pTextFrame, SwTextSizeInfo *pTextSizeInf) + : SwTextIter(pTextFrame->GetTextNodeFirst()) + { + CtorInitTextMargin( pTextFrame, pTextSizeInf ); + } + inline SwTwips GetLeftMargin() const; + inline SwTwips Left() const; + SwTwips Right() const { return nRight; } + SwTwips FirstLeft() const { return nFirst; } + SwTwips CurrWidth() const { return m_pCurr->PrtWidth(); } + SwTwips GetLineStart() const; + SwTwips GetLineEnd() const { return GetLineStart() + CurrWidth(); } + Point GetTopLeft() const { return Point( GetLineStart(), Y() ); } + bool IsOneBlock() const { return m_bOneBlock; } + bool IsLastBlock() const { return m_bLastBlock; } + bool IsLastCenter() const { return m_bLastCenter; } + SvxAdjust GetAdjust() const { return nAdjust; } + sal_uInt16 GetLineWidth() const + { return sal_uInt16( Right() - GetLeftMargin() + 1 ); } + SwTwips GetLeftMin() const { return std::min(nFirst, nLeft); } + bool HasNegFirst() const { return nFirst < nLeft; } + + // #i91133# + SwTwips GetTabLeft() const + { + return mnTabLeft; + } + // DropCaps + sal_uInt16 GetDropLines() const { return nDropLines; } + void SetDropLines( const sal_uInt16 nNew ) { nDropLines = nNew; } + sal_uInt16 GetDropLeft() const { return nDropLeft; } + sal_uInt16 GetDropHeight() const { return nDropHeight; } + void SetDropHeight( const sal_uInt16 nNew ) { nDropHeight = nNew; } + sal_uInt16 GetDropDescent() const { return nDropDescent; } + void SetDropDescent( const sal_uInt16 nNew ) { nDropDescent = nNew; } + void DropInit(); + + // Returns the TextPos for start and end of the current line without whitespace + // Implemented in frminf.cxx + TextFrameIndex GetTextStart() const; + TextFrameIndex GetTextEnd() const; + + SwTextSizeInfo &GetInfo() + { return static_cast<SwTextSizeInfo&>(SwTextIter::GetInfo()); } + const SwTextSizeInfo &GetInfo() const + { return static_cast<const SwTextSizeInfo&>(SwTextIter::GetInfo()); } + +}; + +class SwTextAdjuster : public SwTextMargin +{ + // Adjusts the portion, if we have adjustment and FlyFrames + void CalcFlyAdjust( SwLineLayout *pCurr ); + + // Calls SplitGlues and CalcBlockAdjust + void FormatBlock( ); + + // Creates the glue chain for short lines + SwMarginPortion* CalcRightMargin( SwLineLayout *pCurr, SwTwips nReal = 0 ); + + // Calculate the adjustment (FlyPortions) + SwFlyPortion *CalcFlyPortion( const long nRealWidth, + const SwRect &rCurrRect ); + +protected: + explicit SwTextAdjuster(SwTextNode const * pTextNode) : SwTextMargin(pTextNode) { } + // Creates the Glues for adjusted paragraphs + void CalcNewBlock( SwLineLayout *pCurr, const SwLinePortion *pStopAt, + SwTwips nReal = 0, bool bSkipKashida = false ); + SwTwips CalcKanaAdj( SwLineLayout *pCurr ); + +public: + // Is overloaded by SwTextFormatter due to UpdatePos + void CalcAdjLine( SwLineLayout *pCurr ); + + // For adjusting afterwards + void GetAdjusted() const + { + if( m_pCurr->IsFormatAdj() ) + const_cast<SwTextAdjuster*>(this)->CalcAdjLine( m_pCurr ); + } + + // Special treatment for DropCaps + void CalcDropAdjust(); + void CalcDropRepaint(); +}; + +class SwTextCursor : public SwTextAdjuster +{ + // A small helper-class to save SwTextCursor member, manipulate them + // and to restore them + friend class SwTextCursorSave; + + // Ambiguities + static bool bRightMargin; + void GetCharRect_(SwRect *, TextFrameIndex, SwCursorMoveState *); +protected: + void CtorInitTextCursor( SwTextFrame *pFrame, SwTextSizeInfo *pInf ); + explicit SwTextCursor(SwTextNode const * pTextNode) : SwTextAdjuster(pTextNode) { } +public: + SwTextCursor( SwTextFrame *pTextFrame, SwTextSizeInfo *pTextSizeInf ) + : SwTextAdjuster(pTextFrame->GetTextNodeFirst()) + { + CtorInitTextCursor(pTextFrame, pTextSizeInf); + } + void GetCharRect(SwRect *, TextFrameIndex, SwCursorMoveState* = nullptr, + const long nMax = 0 ); + void GetEndCharRect(SwRect *, TextFrameIndex, SwCursorMoveState* = nullptr, + const long nMax = 0 ); + TextFrameIndex GetModelPositionForViewPoint( SwPosition *pPos, const Point &rPoint, + bool bChgNode, SwCursorMoveState* = nullptr ) const; + // Respects ambiguities: For the implementation see below + const SwLineLayout *CharCursorToLine(TextFrameIndex const nPos); + + // calculates baseline for portion rPor + // bAutoToCentered indicates, if AUTOMATIC mode means CENTERED or BASELINE + sal_uInt16 AdjustBaseLine( const SwLineLayout& rLine, const SwLinePortion* pPor, + sal_uInt16 nPorHeight = 0, sal_uInt16 nAscent = 0, + const bool bAutoToCentered = false ) const; + + static void SetRightMargin( const bool bNew ){ bRightMargin = bNew; } + static bool IsRightMargin() { return bRightMargin; } +}; + +// Change current output device to printer, this has to be done before +// formatting. +class SwHookOut +{ + SwTextSizeInfo* pInf; + VclPtr<OutputDevice> pOut; + bool bOnWin; +public: + explicit SwHookOut( SwTextSizeInfo& rInfo ); + ~SwHookOut(); +}; + +inline bool SwTextIter::SeekAndChg( SwTextSizeInfo &rInf ) +{ + return SeekAndChgAttrIter( rInf.GetIdx(), rInf.GetOut() ); +} + +inline bool SwTextIter::SeekAndChgBefore( SwTextSizeInfo &rInf ) +{ + if ( rInf.GetIdx() ) + return SeekAndChgAttrIter(rInf.GetIdx() - TextFrameIndex(1), rInf.GetOut()); + else + return SeekAndChgAttrIter( rInf.GetIdx(), rInf.GetOut() ); +} + +inline bool SwTextIter::SeekStartAndChg( SwTextSizeInfo &rInf, const bool bPara ) +{ + return SeekStartAndChgAttrIter( rInf.GetOut(), bPara ); +} + +inline SwTwips SwTextMargin::GetLeftMargin() const +{ + return IsFirstTextLine() ? nFirst : Left(); +} + +inline SwTwips SwTextMargin::Left() const +{ + return (nDropLines >= m_nLineNr && 1 != m_nLineNr) ? nFirst + nDropLeft : nLeft; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/noteurl.cxx b/sw/source/core/text/noteurl.cxx new file mode 100644 index 000000000..7e1f91493 --- /dev/null +++ b/sw/source/core/text/noteurl.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 <noteurl.hxx> + +// Global variable +SwNoteURL *pNoteURL = nullptr; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/pordrop.hxx b/sw/source/core/text/pordrop.hxx new file mode 100644 index 000000000..c8948197f --- /dev/null +++ b/sw/source/core/text/pordrop.hxx @@ -0,0 +1,106 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORDROP_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORDROP_HXX + +#include "portxt.hxx" + +class SwFont; + +// DropCap cache, global variable initialized/destroyed in txtinit.cxx +// and used in txtdrop.cxx for initial calculation + +class SwDropCapCache; +extern SwDropCapCache *pDropCapCache; + +// A drop portion can consist of one or more parts in order to allow +// attribute changes inside them. +class SwDropPortionPart +{ + std::unique_ptr<SwDropPortionPart> pFollow; + std::unique_ptr<SwFont> pFnt; + TextFrameIndex nLen; + sal_uInt16 nWidth; + bool m_bJoinBorderWithNext; + bool m_bJoinBorderWithPrev; + +public: + SwDropPortionPart( SwFont& rFont, const TextFrameIndex nL ) + : pFnt( &rFont ), nLen( nL ), nWidth( 0 ), m_bJoinBorderWithNext(false), m_bJoinBorderWithPrev(false) {}; + ~SwDropPortionPart(); + + SwDropPortionPart* GetFollow() const { return pFollow.get(); }; + void SetFollow( std::unique_ptr<SwDropPortionPart> pNew ) { pFollow = std::move(pNew); }; + SwFont& GetFont() const { return *pFnt; } + TextFrameIndex GetLen() const { return nLen; } + sal_uInt16 GetWidth() const { return nWidth; } + void SetWidth( sal_uInt16 nNew ) { nWidth = nNew; } + + bool GetJoinBorderWithPrev() const { return m_bJoinBorderWithPrev; } + bool GetJoinBorderWithNext() const { return m_bJoinBorderWithNext; } + void SetJoinBorderWithPrev( const bool bJoinPrev ) { m_bJoinBorderWithPrev = bJoinPrev; } + void SetJoinBorderWithNext( const bool bJoinNext ) { m_bJoinBorderWithNext = bJoinNext; } +}; + +class SwDropPortion : public SwTextPortion +{ + friend class SwDropCapCache; + std::unique_ptr<SwDropPortionPart> pPart; // due to script/attribute changes + sal_uInt16 nLines; // Line count + sal_uInt16 nDropHeight; // Height + sal_uInt16 nDropDescent; // Distance to the next line + sal_uInt16 nDistance; // Distance to the text + sal_uInt16 nFix; // Fixed position + short nY; // Y Offset + + bool FormatText( SwTextFormatInfo &rInf ); + void PaintText( const SwTextPaintInfo &rInf ) const; + +public: + SwDropPortion( const sal_uInt16 nLineCnt, + const sal_uInt16 nDropHeight, + const sal_uInt16 nDropDescent, + const sal_uInt16 nDistance ); + virtual ~SwDropPortion() override; + + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + void PaintDrop( const SwTextPaintInfo &rInf ) const; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + + sal_uInt16 GetLines() const { return nLines; } + sal_uInt16 GetDistance() const { return nDistance; } + sal_uInt16 GetDropHeight() const { return nDropHeight; } + sal_uInt16 GetDropDescent() const { return nDropDescent; } + sal_uInt16 GetDropLeft() const { return Width() + nFix; } + + SwDropPortionPart* GetPart() const { return pPart.get(); } + void SetPart( std::unique_ptr<SwDropPortionPart> pNew ) { pPart = std::move(pNew); } + + void SetY( short nNew ) { nY = nNew; } + + SwFont* GetFnt() const { return pPart ? &pPart->GetFont() : nullptr; } + + static void DeleteDropCapCache(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porexp.cxx b/sw/source/core/text/porexp.cxx new file mode 100644 index 000000000..659b5532d --- /dev/null +++ b/sw/source/core/text/porexp.cxx @@ -0,0 +1,250 @@ +/* -*- 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 <viewopt.hxx> +#include <SwPortionHandler.hxx> +#include "inftxt.hxx" +#include "porexp.hxx" + +TextFrameIndex SwExpandPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +{ return SwLinePortion::GetModelPositionForViewPoint( nOfst ); } + +bool SwExpandPortion::GetExpText( const SwTextSizeInfo&, OUString &rText ) const +{ + rText.clear(); + // Do not do: return 0 != rText.Len(); + // Reason being: empty fields replace CH_TXTATR with an empty string + return true; +} + +void SwExpandPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), OUString(), GetWhichPor() ); +} + +SwPosSize SwExpandPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +{ + SwTextSlot aDiffText( &rInf, this, false, false ); + return rInf.GetTextSize(); +} + +bool SwExpandPortion::Format( SwTextFormatInfo &rInf ) +{ + SwTextSlot aDiffText( &rInf, this, true, false ); + TextFrameIndex const nFullLen = rInf.GetLen(); + + // As odd as it may seem: the query for GetLen() must return + // false due to the ExpandPortions _after_ the aDiffText (see SoftHyphs) + // caused by the SetFull ... + if( !nFullLen ) + { + // Do not Init(), because we need height and ascent + Width(0); + return false; + } + return SwTextPortion::Format( rInf ); +} + +void SwExpandPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + SwTextSlot aDiffText( &rInf, this, true, true ); + const SwFont aOldFont = *rInf.GetFont(); + if( GetJoinBorderWithPrev() ) + const_cast<SwTextPaintInfo&>(rInf).GetFont()->SetLeftBorder(nullptr); + if( GetJoinBorderWithNext() ) + const_cast<SwTextPaintInfo&>(rInf).GetFont()->SetRightBorder(nullptr); + + rInf.DrawBackBrush( *this ); + rInf.DrawBorder( *this ); + + // Do we have to repaint a post it portion? + if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() ) + mpNextPortion->PrePaint( rInf, this ); + + // The contents of field portions is not considered during the + // calculation of the directions. Therefore we let vcl handle + // the calculation by removing the BIDI_STRONG_FLAG temporarily. + SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); + aLayoutModeModifier.SetAuto(); + + // ST2 + if ( rInf.GetSmartTags() || rInf.GetGrammarCheckList() ) + rInf.DrawMarkedText( *this, rInf.GetLen(), false, + nullptr != rInf.GetSmartTags(), nullptr != rInf.GetGrammarCheckList() ); + else + rInf.DrawText( *this, rInf.GetLen() ); + + if( GetJoinBorderWithPrev() || GetJoinBorderWithNext() ) + *const_cast<SwTextPaintInfo&>(rInf).GetFont() = aOldFont; +} + +SwLinePortion *SwBlankPortion::Compress() { return this; } + +/** + * If a Line is full of HardBlanks and overflows, we must not generate + * underflows! + * Causes problems with Fly + */ +sal_uInt16 SwBlankPortion::MayUnderflow( const SwTextFormatInfo &rInf, + TextFrameIndex const nIdx, bool bUnderflow) +{ + if( rInf.StopUnderflow() ) + return 0; + const SwLinePortion *pPos = rInf.GetRoot(); + if( pPos->GetNextPortion() ) + pPos = pPos->GetNextPortion(); + while( pPos && pPos->IsBlankPortion() ) + pPos = pPos->GetNextPortion(); + if( !pPos || !rInf.GetIdx() || ( !pPos->GetLen() && pPos == rInf.GetRoot() ) ) + return 0; // There are just BlankPortions left + + // If a Blank is preceding us, we do not need to trigger underflow + // If a Blank is succeeding us, we do not need to pass on the underflow + if (bUnderflow + && nIdx + TextFrameIndex(1) < TextFrameIndex(rInf.GetText().getLength()) + && CH_BLANK == rInf.GetText()[sal_Int32(nIdx) + 1]) + { + return 0; + } + if( nIdx && !const_cast<SwTextFormatInfo&>(rInf).GetFly() ) + { + while( pPos && !pPos->IsFlyPortion() ) + pPos = pPos->GetNextPortion(); + if( !pPos ) + { + // We check to see if there are useful line breaks, blanks or fields etc. left + // In case there still are some, no underflow + // If there are Flys, we still allow the underflow + TextFrameIndex nBlank = nIdx; + while( --nBlank > rInf.GetLineStart() ) + { + const sal_Unicode cCh = rInf.GetChar( nBlank ); + if( CH_BLANK == cCh || + (( CH_TXTATR_BREAKWORD == cCh || CH_TXTATR_INWORD == cCh ) + && rInf.HasHint( nBlank ) ) ) + break; + } + if( nBlank <= rInf.GetLineStart() ) + return 0; + } + } + if (nIdx < TextFrameIndex(2)) + return 1; + sal_Unicode const cCh(rInf.GetChar(nIdx - TextFrameIndex(1))); + if (CH_BLANK == cCh) + return 1; + if( CH_BREAK == cCh ) + return 0; + return 2; +} + +/** + * Format End of Line + */ +void SwBlankPortion::FormatEOL( SwTextFormatInfo &rInf ) +{ + sal_uInt16 nMay = MayUnderflow( rInf, rInf.GetIdx() - nLineLength, true ); + if( nMay ) + { + if( nMay > 1 ) + { + if( rInf.GetLast() == this ) + rInf.SetLast( FindPrevPortion( rInf.GetRoot() ) ); + rInf.X( rInf.X() - PrtWidth() ); + rInf.SetIdx( rInf.GetIdx() - GetLen() ); + } + Truncate(); + rInf.SetUnderflow( this ); + if( rInf.GetLast()->IsKernPortion() ) + rInf.SetUnderflow( rInf.GetLast() ); + } +} + +/** + * Pass on the underflows and trigger them ourselves! + */ +bool SwBlankPortion::Format( SwTextFormatInfo &rInf ) +{ + const bool bFull = rInf.IsUnderflow() || SwExpandPortion::Format( rInf ); + if( bFull && MayUnderflow( rInf, rInf.GetIdx(), rInf.IsUnderflow() ) ) + { + Truncate(); + rInf.SetUnderflow( this ); + if( rInf.GetLast()->IsKernPortion() ) + rInf.SetUnderflow( rInf.GetLast() ); + } + return bFull; +} + +void SwBlankPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( !m_bMulti ) // No gray background for multiportion brackets + rInf.DrawViewOpt( *this, PortionType::Blank ); + SwExpandPortion::Paint( rInf ); +} + +bool SwBlankPortion::GetExpText( const SwTextSizeInfo&, OUString &rText ) const +{ + rText = OUString(m_cChar); + return true; +} + +void SwBlankPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), OUString( m_cChar ), GetWhichPor() ); +} + +SwPostItsPortion::SwPostItsPortion( bool bScrpt ) + : bScript( bScrpt ) +{ + nLineLength = TextFrameIndex(1); + SetWhichPor( PortionType::PostIts ); +} + +void SwPostItsPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( rInf.OnWin() && Width() ) + rInf.DrawPostIts( IsScript() ); +} + +sal_uInt16 SwPostItsPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +{ + // Unbelievable: PostIts are always visible + return rInf.OnWin() ? SwViewOption::GetPostItsWidth( rInf.GetOut() ) : 0; +} + +bool SwPostItsPortion::Format( SwTextFormatInfo &rInf ) +{ + const bool bRet = SwLinePortion::Format( rInf ); + // PostIts should not have an effect on line height etc. + SetAscent( 1 ); + Height( 1 ); + return bRet; +} + +bool SwPostItsPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const +{ + if( rInf.OnWin() && rInf.GetOpt().IsPostIts() ) + rText = " "; + else + rText.clear(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porexp.hxx b/sw/source/core/text/porexp.hxx new file mode 100644 index 000000000..418e6ff3c --- /dev/null +++ b/sw/source/core/text/porexp.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_POREXP_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_POREXP_HXX + +#include "portxt.hxx" + +class SwExpandPortion : public SwTextPortion +{ +public: + SwExpandPortion() { SetWhichPor( PortionType::Expand ); } + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwBlankPortion : public SwExpandPortion +{ + sal_Unicode m_cChar; + bool m_bMulti; // For multiportion brackets +public: + SwBlankPortion( sal_Unicode cCh, bool bMult = false ) + : m_cChar( cCh ), m_bMulti( bMult ) + { SetLen(TextFrameIndex(1)); SetWhichPor( PortionType::Blank ); } + + virtual SwLinePortion *Compress() override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual void FormatEOL( SwTextFormatInfo &rInf ) override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + static sal_uInt16 MayUnderflow(const SwTextFormatInfo &rInf, TextFrameIndex nIdx, + bool bUnderflow ); + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwPostItsPortion : public SwExpandPortion +{ + bool bScript; +public: + explicit SwPostItsPortion( bool bScrpt ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + bool IsScript() const { return bScript; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx new file mode 100644 index 000000000..fb414dc87 --- /dev/null +++ b/sw/source/core/text/porfld.cxx @@ -0,0 +1,1350 @@ +/* -*- 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 <hintids.hxx> + +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <vcl/graph.hxx> +#include <editeng/brushitem.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <viewopt.hxx> +#include <SwPortionHandler.hxx> +#include "porlay.hxx" +#include "porfld.hxx" +#include "inftxt.hxx" +#include <fmtornt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewsh.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <rootfrm.hxx> +#include <breakit.hxx> +#include "porftn.hxx" +#include <accessibilityoptions.hxx> +#include <editeng/lrspitem.hxx> +#include <unicode/ubidi.h> +#include <bookmrk.hxx> + +using namespace ::com::sun::star; + +SwLinePortion *SwFieldPortion::Compress() +{ return (GetLen() || !m_aExpand.isEmpty() || SwLinePortion::Compress()) ? this : nullptr; } + +SwFieldPortion *SwFieldPortion::Clone( const OUString &rExpand ) const +{ + std::unique_ptr<SwFont> pNewFnt; + if( m_pFont ) + { + pNewFnt.reset(new SwFont( *m_pFont )); + } + // #i107143# + // pass placeholder property to created <SwFieldPortion> instance. + SwFieldPortion* pClone = new SwFieldPortion( rExpand, std::move(pNewFnt), m_bPlaceHolder ); + pClone->SetNextOffset( m_nNextOffset ); + pClone->m_bNoLength = m_bNoLength; + return pClone; +} + +void SwFieldPortion::TakeNextOffset( const SwFieldPortion* pField ) +{ + OSL_ENSURE( pField, "TakeNextOffset: Missing Source" ); + m_nNextOffset = pField->GetNextOffset(); + m_aExpand = m_aExpand.replaceAt(0, sal_Int32(m_nNextOffset), ""); + m_bFollow = true; +} + +SwFieldPortion::SwFieldPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFont, bool bPlaceHold ) + : m_aExpand(rExpand), m_pFont(std::move(pFont)), m_nNextOffset(0), m_nNextScriptChg(COMPLETE_STRING), m_nViewWidth(0) + , m_bFollow( false ), m_bLeft( false), m_bHide( false) + , m_bCenter (false), m_bHasFollow( false ) + , m_bAnimated( false), m_bNoPaint( false) + , m_bReplace( false), m_bPlaceHolder( bPlaceHold ) + , m_bNoLength( false ) + , m_nAttrFieldType(0) +{ + SetWhichPor( PortionType::Field ); +} + +SwFieldPortion::SwFieldPortion( const SwFieldPortion& rField ) + : SwExpandPortion( rField ) + , m_aExpand( rField.GetExp() ) + , m_nNextOffset( rField.GetNextOffset() ) + , m_nNextScriptChg( rField.m_nNextScriptChg ) + , m_nViewWidth( rField.m_nViewWidth ) + , m_bFollow( rField.IsFollow() ) + , m_bLeft( rField.IsLeft() ) + , m_bHide( rField.IsHide() ) + , m_bCenter( rField.IsCenter() ) + , m_bHasFollow( rField.HasFollow() ) + , m_bAnimated ( rField.m_bAnimated ) + , m_bNoPaint( rField.m_bNoPaint) + , m_bReplace( rField.m_bReplace ) + , m_bPlaceHolder( rField.m_bPlaceHolder ) + , m_bNoLength( rField.m_bNoLength ) + , m_nAttrFieldType( rField.m_nAttrFieldType) +{ + if ( rField.HasFont() ) + m_pFont.reset( new SwFont( *rField.GetFont() ) ); + + SetWhichPor( PortionType::Field ); +} + +SwFieldPortion::~SwFieldPortion() +{ + m_pFont.reset(); +} + +sal_uInt16 SwFieldPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +{ + // even though this is const, nViewWidth should be computed at the very end: + SwFieldPortion* pThis = const_cast<SwFieldPortion*>(this); + if( !Width() && rInf.OnWin() && !rInf.GetOpt().IsPagePreview() && + !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() ) + { + if( !m_nViewWidth ) + pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width(); + } + else + pThis->m_nViewWidth = 0; + return m_nViewWidth; +} + +namespace { + +/** + * Never just use SetLen(0) + */ +class SwFieldSlot +{ + std::shared_ptr<vcl::TextLayoutCache> m_pOldCachedVclData; + const OUString *pOldText; + OUString aText; + TextFrameIndex nIdx; + TextFrameIndex nLen; + SwTextFormatInfo *pInf; + bool bOn; +public: + SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pPor ); + ~SwFieldSlot(); +}; + +} + +SwFieldSlot::SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pPor ) + : pOldText(nullptr) + , nIdx(0) + , nLen(0) + , pInf(nullptr) +{ + bOn = pPor->GetExpText( *pNew, aText ); + + // The text will be replaced ... + if( bOn ) + { + pInf = const_cast<SwTextFormatInfo*>(pNew); + nIdx = pInf->GetIdx(); + nLen = pInf->GetLen(); + pOldText = &(pInf->GetText()); + m_pOldCachedVclData = pInf->GetCachedVclData(); + pInf->SetLen(TextFrameIndex(aText.getLength())); + pInf->SetCachedVclData(nullptr); + if( pPor->IsFollow() ) + { + pInf->SetFakeLineStart( nIdx > pInf->GetLineStart() ); + pInf->SetIdx(TextFrameIndex(0)); + } + else if (nIdx < TextFrameIndex(pOldText->getLength())) + { + aText = (*pOldText).replaceAt(sal_Int32(nIdx), 1, aText); + } + pInf->SetText( aText ); + } +} + +SwFieldSlot::~SwFieldSlot() +{ + if( bOn ) + { + pInf->SetCachedVclData(m_pOldCachedVclData); + pInf->SetText( *pOldText ); + pInf->SetIdx( nIdx ); + pInf->SetLen( nLen ); + pInf->SetFakeLineStart( false ); + } +} + +void SwFieldPortion::CheckScript( const SwTextSizeInfo &rInf ) +{ + OUString aText; + if (!GetExpText(rInf, aText) || aText.isEmpty()) + return; + + SwFontScript nActual = m_pFont ? m_pFont->GetActual() : rInf.GetFont()->GetActual(); + sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, 0 ); + sal_Int32 nChg = 0; + if( i18n::ScriptType::WEAK == nScript ) + { + nChg = g_pBreakIt->GetBreakIter()->endOfScript(aText,0,nScript); + if (nChg < aText.getLength() && nChg >= 0) + nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, nChg ); + } + + // nNextScriptChg will be evaluated during SwFieldPortion::Format() + + if (nChg < aText.getLength() && nChg >= 0) + m_nNextScriptChg = TextFrameIndex( + g_pBreakIt->GetBreakIter()->endOfScript(aText, nChg, nScript)); + else + m_nNextScriptChg = TextFrameIndex(aText.getLength()); + + SwFontScript nTmp; + switch ( nScript ) { + case i18n::ScriptType::LATIN : nTmp = SwFontScript::Latin; break; + case i18n::ScriptType::ASIAN : nTmp = SwFontScript::CJK; break; + case i18n::ScriptType::COMPLEX : nTmp = SwFontScript::CTL; break; + default: nTmp = nActual; + } + + // #i16354# Change script type for RTL text to CTL. + const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); + // #i98418# + const sal_uInt8 nFieldDir = (IsNumberPortion() || IsFootnoteNumPortion()) + ? rSI.GetDefaultDir() + : rSI.DirType(IsFollow() ? rInf.GetIdx() - TextFrameIndex(1) : rInf.GetIdx()); + + { + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError ); + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nFieldDir, nullptr, &nError ); + int32_t nEnd; + UBiDiLevel nCurrDir; + ubidi_getLogicalRun( pBidi, 0, &nEnd, &nCurrDir ); + ubidi_close( pBidi ); + const TextFrameIndex nNextDirChg(nEnd); + m_nNextScriptChg = std::min( m_nNextScriptChg, nNextDirChg ); + + // #i89825# change the script type also to CTL + // if there is no strong LTR char in the LTR run (numbers) + if (nCurrDir != UBIDI_RTL && + (UBIDI_LTR != nFieldDir || i18n::ScriptType::COMPLEX == nScript)) + { + nCurrDir = UBIDI_RTL; + for( sal_Int32 nCharIdx = 0; nCharIdx < nEnd; ++nCharIdx ) + { + UCharDirection nCharDir = u_charDirection ( aText[ nCharIdx ]); + if ( nCharDir == U_LEFT_TO_RIGHT || + nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || + nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) + { + nCurrDir = UBIDI_LTR; + break; + } + } + } + + if (nCurrDir == UBIDI_RTL) + { + nTmp = SwFontScript::CTL; + // If we decided that this range was RTL after all and the + // previous range was complex but clipped to the start of this + // range, then extend it to be complex over the additional RTL range + if (nScript == i18n::ScriptType::COMPLEX) + m_nNextScriptChg = nNextDirChg; + } + } + + // #i98418# + // keep determined script type for footnote portions as preferred script type. + // For footnote portions a font can not be created directly - see footnote + // portion format method. + if ( IsFootnotePortion() ) + { + static_cast<SwFootnotePortion*>(this)->SetPreferredScriptType( nTmp ); + } + else if ( nTmp != nActual ) + { + if( !m_pFont ) + m_pFont.reset( new SwFont( *rInf.GetFont() ) ); + m_pFont->SetActual( nTmp ); + } + +} + +bool SwFieldPortion::Format( SwTextFormatInfo &rInf ) +{ + // Scope wegen aDiffText::DTOR! + TextFrameIndex nRest; + bool bFull = false; + bool bEOL = false; + TextFrameIndex const nTextRest = TextFrameIndex(rInf.GetText().getLength()) - rInf.GetIdx(); + { + SwFieldSlot aDiffText( &rInf, this ); + SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); + aLayoutModeModifier.SetAuto(); + + // Field portion has to be split in several parts if + // 1. There are script/direction changes inside the field + // 2. There are portion breaks (tab, break) inside the field: + const TextFrameIndex nOldFullLen = rInf.GetLen(); + TextFrameIndex nFullLen = rInf.ScanPortionEnd(rInf.GetIdx(), rInf.GetIdx() + nOldFullLen) - rInf.GetIdx(); + if ( m_nNextScriptChg < nFullLen ) + { + nFullLen = m_nNextScriptChg; + rInf.SetHookChar( 0 ); + } + rInf.SetLen( nFullLen ); + + if (TextFrameIndex(COMPLETE_STRING) != rInf.GetUnderScorePos() && + rInf.GetUnderScorePos() > rInf.GetIdx() ) + rInf.SetUnderScorePos( rInf.GetIdx() ); + + if( m_pFont ) + m_pFont->AllocFontCacheId( rInf.GetVsh(), m_pFont->GetActual() ); + + SwFontSave aSave( rInf, m_pFont.get() ); + + // Length must be 0: the length is set for bFull after format + // and passed along in nRest. Or else the old length would be + // retained and be used for nRest! + SetLen(TextFrameIndex(0)); + TextFrameIndex const nFollow(IsFollow() ? 0 : 1); + + // As odd is may seem: the query for GetLen() must return false due + // to the ExpandPortions _after_ aDiffText (see SoftHyphs), caused + // by SetFull. + if( !nFullLen ) + { + // Don't Init(), as we need height and ascent + Width(0); + bFull = rInf.Width() <= rInf.GetPos().X(); + } + else + { + TextFrameIndex const nOldLineStart = rInf.GetLineStart(); + if( IsFollow() ) + rInf.SetLineStart(TextFrameIndex(0)); + rInf.SetNotEOL( nFullLen == nOldFullLen && nTextRest > nFollow ); + + // the height depending on the fields font is set, + // this is required for SwTextGuess::Guess + Height( rInf.GetTextHeight() + rInf.GetFont()->GetTopBorderSpace() + + rInf.GetFont()->GetBottomBorderSpace() ); + // If a kerning portion is inserted after our field portion, + // the ascent and height must be known + SetAscent( rInf.GetAscent() + rInf.GetFont()->GetTopBorderSpace() ); + bFull = SwTextPortion::Format( rInf ); + rInf.SetNotEOL( false ); + rInf.SetLineStart( nOldLineStart ); + } + TextFrameIndex const nTmpLen = GetLen(); + bEOL = !nTmpLen && nFollow && bFull; + nRest = nOldFullLen - nTmpLen; + + // The char is held in the first position + // Unconditionally after format! + SetLen( m_bNoLength ? TextFrameIndex(0) : nFollow ); + + if( nRest ) + { + // aExpand has not yet been shortened; the new Ofst is a + // result of nRest + TextFrameIndex nNextOfst = TextFrameIndex(m_aExpand.getLength()) - nRest; + + if ( IsQuoVadisPortion() ) + nNextOfst = nNextOfst + TextFrameIndex(static_cast<SwQuoVadisPortion*>(this)->GetContText().getLength()); + + OUString aNew( m_aExpand.copy(sal_Int32(nNextOfst)) ); + m_aExpand = m_aExpand.copy(0, sal_Int32(nNextOfst)); + + // These characters should not be contained in the follow + // field portion. They are handled via the HookChar mechanism. + const sal_Unicode nNew = !aNew.isEmpty() ? aNew[0] : 0; + switch (nNew) + { + case CH_BREAK : bFull = true; + [[fallthrough]]; + case ' ' : + case CH_TAB : + case CHAR_HARDHYPHEN: // non-breaking hyphen + case CHAR_SOFTHYPHEN: + case CHAR_HARDBLANK: + case CHAR_ZWSP : + case CHAR_ZWNBSP : + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + { + aNew = aNew.copy( 1 ); + ++nNextOfst; + break; + } + default: ; + } + + // Even if there is no more text left for a follow field, + // we have to build a follow field portion (without font), + // otherwise the HookChar mechanism would not work. + SwFieldPortion *pField = Clone( aNew ); + if( !aNew.isEmpty() && !pField->GetFont() ) + { + pField->SetFont( std::make_unique<SwFont>( *rInf.GetFont() ) ); + } + pField->SetFollow( true ); + SetHasFollow( true ); + + // For a newly created field, nNextOffset contains the Offset + // of its start of the original string + // If a FollowField is created when formatting, this FollowField's + // Offset is being held in nNextOffset + m_nNextOffset = m_nNextOffset + nNextOfst; + pField->SetNextOffset( m_nNextOffset ); + rInf.SetRest( pField ); + } + } + + if( bEOL && rInf.GetLast() && !rInf.GetUnderflow() ) + rInf.GetLast()->FormatEOL( rInf ); + return bFull; +} + +void SwFieldPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + SwFontSave aSave( rInf, m_pFont.get() ); + + OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?"); + if( Width() && ( !m_bPlaceHolder || rInf.GetOpt().IsShowPlaceHolderFields() ) ) + { + // A very liberal use of the background + rInf.DrawViewOpt( *this, PortionType::Field ); + SwExpandPortion::Paint( rInf ); + } +} + +bool SwFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const +{ + rText = m_aExpand; + if( rText.isEmpty() && rInf.OnWin() && + !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && + SwViewOption::IsFieldShadings() && + !HasFollow() ) + rText = " "; + return true; +} + +void SwFieldPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + sal_Int32 nH = 0; + sal_Int32 nW = 0; + if (m_pFont) + { + nH = m_pFont->GetSize(m_pFont->GetActual()).Height(); + nW = m_pFont->GetSize(m_pFont->GetActual()).Width(); + } + rPH.Special( GetLen(), m_aExpand, GetWhichPor(), nH, nW, m_pFont.get() ); +} + +SwPosSize SwFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +{ + SwFontSave aSave( rInf, m_pFont.get() ); + SwPosSize aSize( SwExpandPortion::GetTextSize( rInf ) ); + return aSize; +} + +SwFieldPortion *SwHiddenPortion::Clone(const OUString &rExpand ) const +{ + std::unique_ptr<SwFont> pNewFnt; + if( m_pFont ) + pNewFnt.reset(new SwFont( *m_pFont )); + return new SwHiddenPortion( rExpand, std::move(pNewFnt) ); +} + +void SwHiddenPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + { + SwFontSave aSave( rInf, m_pFont.get() ); + rInf.DrawViewOpt( *this, PortionType::Hidden ); + SwExpandPortion::Paint( rInf ); + } +} + +bool SwHiddenPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const +{ + // Do not query for IsHidden()! + return SwFieldPortion::GetExpText( rInf, rText ); +} + +SwNumberPortion::SwNumberPortion( const OUString &rExpand, + std::unique_ptr<SwFont> pFont, + const bool bLft, + const bool bCntr, + const sal_uInt16 nMinDst, + const bool bLabelAlignmentPosAndSpaceModeActive ) + : SwFieldPortion( rExpand, std::move(pFont) ), + nFixWidth(0), + nMinDist( nMinDst ), + mbLabelAlignmentPosAndSpaceModeActive( bLabelAlignmentPosAndSpaceModeActive ) +{ + SetWhichPor( PortionType::Number ); + SetLeft( bLft ); + SetHide( false ); + SetCenter( bCntr ); +} + +TextFrameIndex SwNumberPortion::GetModelPositionForViewPoint(const sal_uInt16) const +{ + return TextFrameIndex(0); +} + +SwFieldPortion *SwNumberPortion::Clone( const OUString &rExpand ) const +{ + std::unique_ptr<SwFont> pNewFnt; + if( m_pFont ) + pNewFnt.reset(new SwFont( *m_pFont )); + + return new SwNumberPortion( rExpand, std::move(pNewFnt), IsLeft(), IsCenter(), + nMinDist, mbLabelAlignmentPosAndSpaceModeActive ); +} + +/** + * We can create multiple NumFields + * Tricky, if one enters enough previous-text in the dialog box + * to cause the line to overflow + * We need to keep the Fly's evasion tactics in mind + */ +bool SwNumberPortion::Format( SwTextFormatInfo &rInf ) +{ + SetHide( false ); + const bool bFull = SwFieldPortion::Format( rInf ); + SetLen(TextFrameIndex(0)); + // a numbering portion can be contained in a rotated portion!!! + nFixWidth = rInf.IsMulti() ? Height() : Width(); + rInf.SetNumDone( !rInf.GetRest() ); + if( rInf.IsNumDone() ) + { +// SetAscent( rInf.GetAscent() ); + OSL_ENSURE( Height() && nAscent, "NumberPortions without Height | Ascent" ); + + long nDiff( 0 ); + + if ( !mbLabelAlignmentPosAndSpaceModeActive ) + { + if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) && + // #i32902# + !IsFootnoteNumPortion() ) + { + nDiff = rInf.Left() + + rInf.GetTextFrame()->GetTextNodeForParaProps()-> + GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset() + - rInf.First() + + rInf.ForcedLeftMargin(); + } + else + { + nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin(); + } + } + // The text part of the numbering should always at least + // start at the left margin + if( nDiff < 0 ) + nDiff = 0; + else if ( nDiff > rInf.X() ) + nDiff -= rInf.X(); + else + nDiff = 0; + + if( nDiff < nFixWidth + nMinDist ) + nDiff = nFixWidth + nMinDist; + + // Numbering evades the Fly, no nDiff in the second round + // Tricky special case: FlyFrame is in an Area we're just about to + // acquire + // The NumberPortion is marked as hidden + const bool bFly = rInf.GetFly() || + ( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() ); + if( nDiff > rInf.Width() ) + { + nDiff = rInf.Width(); + if ( bFly ) + SetHide( true ); + } + + // A numbering portion can be inside a SwRotatedPortion. Then the + // Height has to be changed + if ( rInf.IsMulti() ) + { + if ( Height() < nDiff ) + Height( sal_uInt16( nDiff ) ); + } + else if( Width() < nDiff ) + Width( sal_uInt16(nDiff) ); + } + return bFull; +} + + +/** + * A FormatEOL indicates that the subsequent text did not fit onto + * the line anymore. In order for the Numbering to follow through, + * we hide this NumberPortion + */ +void SwNumberPortion::FormatEOL( SwTextFormatInfo& ) +{ + + // This caused trouble with flys anchored as characters. + // If one of these is numbered but does not fit to the line, + // it calls this function, causing a loop because both the number + // portion and the fly portion go to the next line +// SetHide( true ); +} + + +/** + * A hidden NumberPortion is not displayed, unless there are TextPortions in + * this line or there's just one line at all + */ +void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() ) + { + SwLinePortion *pTmp = GetNextPortion(); + while ( pTmp && !pTmp->InTextGrp() ) + pTmp = pTmp->GetNextPortion(); + if ( !pTmp ) + return; + } + + // calculate the width of the number portion, including follows + const sal_uInt16 nOldWidth = Width(); + sal_uInt16 nSumWidth = 0; + sal_uInt16 nOffset = 0; + + const SwLinePortion* pTmp = this; + while ( pTmp && pTmp->InNumberGrp() ) + { + nSumWidth = nSumWidth + pTmp->Width(); + if ( static_cast<const SwNumberPortion*>(pTmp)->HasFollow() ) + pTmp = pTmp->GetNextPortion(); + else + { + nOffset = pTmp->Width() - static_cast<const SwNumberPortion*>(pTmp)->nFixWidth; + break; + } + } + + // The master portion takes care for painting the background of the + // follow field portions + if ( ! IsFollow() ) + { + SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this); + pThis->Width( nSumWidth ); + rInf.DrawViewOpt( *this, PortionType::Number ); + pThis->Width( nOldWidth ); + } + + if( !m_aExpand.isEmpty() ) + { + const SwFont *pTmpFnt = rInf.GetFont(); + bool bPaintSpace = ( LINESTYLE_NONE != pTmpFnt->GetUnderline() || + LINESTYLE_NONE != pTmpFnt->GetOverline() || + STRIKEOUT_NONE != pTmpFnt->GetStrikeout() ) && + !pTmpFnt->IsWordLineMode(); + if( bPaintSpace && m_pFont ) + bPaintSpace = ( LINESTYLE_NONE != m_pFont->GetUnderline() || + LINESTYLE_NONE != m_pFont->GetOverline() || + STRIKEOUT_NONE != m_pFont->GetStrikeout() ) && + !m_pFont->IsWordLineMode(); + + SwFontSave aSave( rInf, m_pFont.get() ); + + if( nFixWidth == Width() && ! HasFollow() ) + SwExpandPortion::Paint( rInf ); + else + { + // logical const: reset width + SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this); + bPaintSpace = bPaintSpace && nFixWidth < nOldWidth; + sal_uInt16 nSpaceOffs = nFixWidth; + pThis->Width( nFixWidth ); + + if( ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) || + ( ! IsLeft() && ! IsCenter() && rInf.GetTextFrame()->IsRightToLeft() ) ) + SwExpandPortion::Paint( rInf ); + else + { + SwTextPaintInfo aInf( rInf ); + if( nOffset < nMinDist ) + nOffset = 0; + else + { + if( IsCenter() ) + { + /* #110778# a / 2 * 2 == a is not a tautology */ + sal_uInt16 nTmpOffset = nOffset; + nOffset /= 2; + if( nOffset < nMinDist ) + nOffset = nTmpOffset - nMinDist; + } + else + nOffset = nOffset - nMinDist; + } + aInf.X( aInf.X() + nOffset ); + SwExpandPortion::Paint( aInf ); + if( bPaintSpace ) + nSpaceOffs = nSpaceOffs + nOffset; + } + if( bPaintSpace && nOldWidth > nSpaceOffs ) + { + SwTextPaintInfo aInf( rInf ); + aInf.X( aInf.X() + nSpaceOffs ); + + // #i53199# Adjust position of underline: + if ( rInf.GetUnderFnt() ) + { + const Point aNewPos( aInf.GetPos().X(), rInf.GetUnderFnt()->GetPos().Y() ); + rInf.GetUnderFnt()->SetPos( aNewPos ); + } + + pThis->Width( nOldWidth - nSpaceOffs + 12 ); + { + SwTextSlot aDiffText( &aInf, this, true, false, " " ); + aInf.DrawText( *this, aInf.GetLen(), true ); + } + } + pThis->Width( nOldWidth ); + } + } +} + +SwBulletPortion::SwBulletPortion( const sal_Unicode cBullet, + const OUString& rBulletFollowedBy, + std::unique_ptr<SwFont> pFont, + const bool bLft, + const bool bCntr, + const sal_uInt16 nMinDst, + const bool bLabelAlignmentPosAndSpaceModeActive ) + : SwNumberPortion( OUStringChar(cBullet) + rBulletFollowedBy, + std::move(pFont), bLft, bCntr, nMinDst, + bLabelAlignmentPosAndSpaceModeActive ) +{ + SetWhichPor( PortionType::Bullet ); +} + +#define GRFNUM_SECURE 10 + +SwGrfNumPortion::SwGrfNumPortion( + const OUString& rGraphicFollowedBy, + const SvxBrushItem* pGrfBrush, OUString const & referer, + const SwFormatVertOrient* pGrfOrient, const Size& rGrfSize, + const bool bLft, const bool bCntr, const sal_uInt16 nMinDst, + const bool bLabelAlignmentPosAndSpaceModeActive ) : + SwNumberPortion( rGraphicFollowedBy, nullptr, bLft, bCntr, nMinDst, + bLabelAlignmentPosAndSpaceModeActive ), + pBrush( new SvxBrushItem(RES_BACKGROUND) ), nId( 0 ) +{ + SetWhichPor( PortionType::GrfNum ); + SetAnimated( false ); + m_bReplace = false; + if( pGrfBrush ) + { + pBrush.reset(pGrfBrush->Clone()); + const Graphic* pGraph = pGrfBrush->GetGraphic(referer); + if( pGraph ) + SetAnimated( pGraph->IsAnimated() ); + else + m_bReplace = true; + } + if( pGrfOrient ) + { + nYPos = pGrfOrient->GetPos(); + eOrient = pGrfOrient->GetVertOrient(); + } + else + { + nYPos = 0; + eOrient = text::VertOrientation::TOP; + } + Width( static_cast<sal_uInt16>(rGrfSize.Width() + 2 * GRFNUM_SECURE) ); + nFixWidth = Width(); + nGrfHeight = rGrfSize.Height() + 2 * GRFNUM_SECURE; + Height( sal_uInt16(nGrfHeight) ); + m_bNoPaint = false; +} + +SwGrfNumPortion::~SwGrfNumPortion() +{ + if ( IsAnimated() ) + { + Graphic* pGraph = const_cast<Graphic*>(pBrush->GetGraphic()); + if (pGraph) + pGraph->StopAnimation( nullptr, nId ); + } + pBrush.reset(); +} + +void SwGrfNumPortion::StopAnimation( OutputDevice* pOut ) +{ + if ( IsAnimated() ) + { + Graphic* pGraph = const_cast<Graphic*>(pBrush->GetGraphic()); + if (pGraph) + pGraph->StopAnimation( pOut, nId ); + } +} + +bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf ) +{ + SetHide( false ); +// Width( nFixWidth ); + sal_uInt16 nFollowedByWidth( 0 ); + if ( mbLabelAlignmentPosAndSpaceModeActive ) + { + SwFieldPortion::Format( rInf ); + nFollowedByWidth = Width(); + SetLen(TextFrameIndex(0)); + } + Width( nFixWidth + nFollowedByWidth ); + const bool bFull = rInf.Width() < rInf.X() + Width(); + const bool bFly = rInf.GetFly() || + ( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() ); + SetAscent( GetRelPos() > 0 ? GetRelPos() : 0 ); + if( GetAscent() > Height() ) + Height( GetAscent() ); + + if( bFull ) + { + Width( rInf.Width() - static_cast<sal_uInt16>(rInf.X()) ); + if( bFly ) + { + SetLen(TextFrameIndex(0)); + m_bNoPaint = true; + rInf.SetNumDone( false ); + return true; + } + } + rInf.SetNumDone( true ); +// long nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin(); + long nDiff = mbLabelAlignmentPosAndSpaceModeActive + ? 0 + : rInf.Left() - rInf.First() + rInf.ForcedLeftMargin(); + // The TextPortion should at least always start on the + // left margin + if( nDiff < 0 ) + nDiff = 0; + else if ( nDiff > rInf.X() ) + nDiff -= rInf.X(); + if( nDiff < nFixWidth + nMinDist ) + nDiff = nFixWidth + nMinDist; + + // Numbering evades Fly, no nDiff in the second round + // Tricky special case: FlyFrame is in the Area we were just + // about to get a hold of. + // The NumberPortion is marked as hidden + if( nDiff > rInf.Width() ) + { + nDiff = rInf.Width(); + if( bFly ) + SetHide( true ); + } + + if( Width() < nDiff ) + Width( sal_uInt16(nDiff) ); + return bFull; +} + + +/** + * A hidden NumberPortion is not displayed, unless there are TextPortions in + * this line or there's only one line at all + */ +void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( m_bNoPaint ) + return; + if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() ) + { + SwLinePortion *pTmp = GetNextPortion(); + while ( pTmp && !pTmp->InTextGrp() ) + pTmp = pTmp->GetNextPortion(); + if ( !pTmp ) + return; + } + Point aPos( rInf.X() + GRFNUM_SECURE, rInf.Y() - GetRelPos() + GRFNUM_SECURE ); + long nTmpWidth = std::max( long(0), static_cast<long>(nFixWidth - 2 * GRFNUM_SECURE) ); + Size aSize( nTmpWidth, GetGrfHeight() - 2 * GRFNUM_SECURE ); + + const bool bTmpLeft = mbLabelAlignmentPosAndSpaceModeActive || + ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) || + ( ! IsLeft() && ! IsCenter() && rInf.GetTextFrame()->IsRightToLeft() ); + + if( nFixWidth < Width() && !bTmpLeft ) + { + sal_uInt16 nOffset = Width() - nFixWidth; + if( nOffset < nMinDist ) + nOffset = 0; + else + { + if( IsCenter() ) + { + nOffset /= 2; + if( nOffset < nMinDist ) + nOffset = Width() - nFixWidth - nMinDist; + } + else + nOffset = nOffset - nMinDist; + } + aPos.AdjustX(nOffset ); + } + + if( m_bReplace ) + { + const long nTmpH = GetNextPortion() ? GetNextPortion()->GetAscent() : 120; + aSize = Size( nTmpH, nTmpH ); + aPos.setY( rInf.Y() - nTmpH ); + } + SwRect aTmp( aPos, aSize ); + + bool bDraw = true; + + if ( IsAnimated() ) + { + bDraw = !rInf.GetOpt().IsGraphic(); + if( !nId ) + { + SetId( reinterpret_cast<sal_IntPtr>( rInf.GetTextFrame() ) ); + rInf.GetTextFrame()->SetAnimation(); + } + if( aTmp.IsOver( rInf.GetPaintRect() ) && !bDraw ) + { + rInf.NoteAnimation(); + const SwViewShell* pViewShell = rInf.GetVsh(); + + // virtual device, not pdf export + if( OUTDEV_VIRDEV == rInf.GetOut()->GetOutDevType() && + pViewShell && pViewShell->GetWin() ) + { + Graphic* pGraph = const_cast<Graphic*>(pBrush->GetGraphic()); + if (pGraph) + pGraph->StopAnimation(nullptr,nId); + rInf.GetTextFrame()->getRootFrame()->GetCurrShell()->InvalidateWindows( aTmp ); + } + + else if ( pViewShell && + !pViewShell->GetAccessibilityOptions()->IsStopAnimatedGraphics() && + !pViewShell->IsPreview() && + // #i9684# Stop animation during printing/pdf export. + pViewShell->GetWin() ) + { + Graphic* pGraph = const_cast<Graphic*>(pBrush->GetGraphic()); + if (pGraph) + { + pGraph->StartAnimation( + const_cast<OutputDevice*>(rInf.GetOut()), aPos, aSize, nId ); + } + } + + // pdf export, printing, preview, stop animations... + else + bDraw = true; + } + if( bDraw ) + { + + Graphic* pGraph = const_cast<Graphic*>(pBrush->GetGraphic()); + if (pGraph) + pGraph->StopAnimation( nullptr, nId ); + } + } + + SwRect aRepaint( rInf.GetPaintRect() ); + const SwTextFrame& rFrame = *rInf.GetTextFrame(); + if( rFrame.IsVertical() ) + { + rFrame.SwitchHorizontalToVertical( aTmp ); + rFrame.SwitchHorizontalToVertical( aRepaint ); + } + + if( rFrame.IsRightToLeft() ) + { + rFrame.SwitchLTRtoRTL( aTmp ); + rFrame.SwitchLTRtoRTL( aRepaint ); + } + + if( bDraw && aTmp.HasArea() ) + { + DrawGraphic( pBrush.get(), const_cast<OutputDevice*>(rInf.GetOut()), + aTmp, aRepaint, m_bReplace ? GRFNUM_REPLACE : GRFNUM_YES ); + } +} + +void SwGrfNumPortion::SetBase( long nLnAscent, long nLnDescent, + long nFlyAsc, long nFlyDesc ) +{ + if ( GetOrient() != text::VertOrientation::NONE ) + { + SetRelPos( 0 ); + if ( GetOrient() == text::VertOrientation::CENTER ) + SetRelPos( GetGrfHeight() / 2 ); + else if ( GetOrient() == text::VertOrientation::TOP ) + SetRelPos( GetGrfHeight() - GRFNUM_SECURE ); + else if ( GetOrient() == text::VertOrientation::BOTTOM ) + ; + else if ( GetOrient() == text::VertOrientation::CHAR_CENTER ) + SetRelPos( ( GetGrfHeight() + nLnAscent - nLnDescent ) / 2 ); + else if ( GetOrient() == text::VertOrientation::CHAR_TOP ) + SetRelPos( nLnAscent ); + else if ( GetOrient() == text::VertOrientation::CHAR_BOTTOM ) + SetRelPos( GetGrfHeight() - nLnDescent ); + else + { + if( GetGrfHeight() >= nFlyAsc + nFlyDesc ) + { + // If I'm as large as the line, I do not need to adjust + // at the line; I'll leave the max. ascent unchanged + SetRelPos( nFlyAsc ); + } + else if ( GetOrient() == text::VertOrientation::LINE_CENTER ) + SetRelPos( ( GetGrfHeight() + nFlyAsc - nFlyDesc ) / 2 ); + else if ( GetOrient() == text::VertOrientation::LINE_TOP ) + SetRelPos( nFlyAsc ); + else if ( GetOrient() == text::VertOrientation::LINE_BOTTOM ) + SetRelPos( GetGrfHeight() - nFlyDesc ); + } + } +} + +void SwTextFrame::StopAnimation( OutputDevice* pOut ) +{ + OSL_ENSURE( HasAnimation(), "SwTextFrame::StopAnimation: Which Animation?" ); + if( HasPara() ) + { + SwLineLayout *pLine = GetPara(); + while( pLine ) + { + SwLinePortion *pPor = pLine->GetNextPortion(); + while( pPor ) + { + if( pPor->IsGrfNumPortion() ) + static_cast<SwGrfNumPortion*>(pPor)->StopAnimation( pOut ); + // The NumberPortion is always at the first char, + // which means we can cancel as soon as we've reached a portion + // with a length > 0 + pPor = pPor->GetLen() ? nullptr : pPor->GetNextPortion(); + } + pLine = pLine->GetLen() ? nullptr : pLine->GetNext(); + } + } +} + +/** + * Initializes the script array and clears the width array + */ +SwCombinedPortion::SwCombinedPortion( const OUString &rText ) + : SwFieldPortion( rText ) + , nUpPos(0) + , nLowPos(0) + , nProportion(55) +{ + SetLen(TextFrameIndex(1)); + SetWhichPor( PortionType::Combined ); + if( m_aExpand.getLength() > 6 ) + m_aExpand = m_aExpand.copy( 0, 6 ); + + // Initialization of the scripttype array, + // the arrays of width and position are filled by the format function + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + SwFontScript nScr = SW_SCRIPTS; + for( sal_Int32 i = 0; i < rText.getLength(); ++i ) + { + switch ( g_pBreakIt->GetBreakIter()->getScriptType( rText, i ) ) { + case i18n::ScriptType::LATIN : nScr = SwFontScript::Latin; break; + case i18n::ScriptType::ASIAN : nScr = SwFontScript::CJK; break; + case i18n::ScriptType::COMPLEX : nScr = SwFontScript::CTL; break; + } + aScrType[i] = nScr; + } +} + +void SwCombinedPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?"); + if( !Width() ) + return; + + rInf.DrawBackBrush( *this ); + rInf.DrawViewOpt( *this, PortionType::Field ); + + // do we have to repaint a post it portion? + if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() ) + mpNextPortion->PrePaint( rInf, this ); + + const sal_Int32 nCount = m_aExpand.getLength(); + if( !nCount ) + return; + OSL_ENSURE( nCount < 7, "Too much combined characters" ); + + // the first character of the second row + const sal_Int32 nTop = ( nCount + 1 ) / 2; + + SwFont aTmpFont( *rInf.GetFont() ); + aTmpFont.SetProportion( nProportion ); // a smaller font + SwFontSave aFontSave( rInf, &aTmpFont ); + + Point aOldPos = rInf.GetPos(); + Point aOutPos( aOldPos.X(), aOldPos.Y() - nUpPos );// Y of the first row + for( sal_Int32 i = 0 ; i < nCount; ++i ) + { + if( i == nTop ) // change the row + aOutPos.setY( aOldPos.Y() + nLowPos ); // Y of the second row + aOutPos.setX( aOldPos.X() + aPos[i] ); // X position + const SwFontScript nAct = aScrType[i]; // script type + aTmpFont.SetActual( nAct ); + + // if there're more than 4 characters to display, we choose fonts + // with 2/3 of the original font width. + if( aWidth[ nAct ] ) + { + Size aTmpSz = aTmpFont.GetSize( nAct ); + if( aTmpSz.Width() != aWidth[ nAct ] ) + { + aTmpSz.setWidth( aWidth[ nAct ] ); + aTmpFont.SetSize( aTmpSz, nAct ); + } + } + const_cast<SwTextPaintInfo&>(rInf).SetPos( aOutPos ); + rInf.DrawText(m_aExpand, *this, TextFrameIndex(i), TextFrameIndex(1)); + } + // rInf is const, so we have to take back our manipulations + const_cast<SwTextPaintInfo&>(rInf).SetPos( aOldPos ); + +} + +bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) +{ + const sal_Int32 nCount = m_aExpand.getLength(); + if( !nCount ) + { + Width( 0 ); + return false; + } + + OSL_ENSURE( nCount < 7, "Too much combined characters" ); + + // If there are leading "weak"-scripttyped characters in this portion, + // they get the actual scripttype. + for( sal_Int32 i = 0; i < nCount && SW_SCRIPTS == aScrType[i]; ++i ) + aScrType[i] = rInf.GetFont()->GetActual(); + if( nCount > 4 ) + { + // more than four? Ok, then we need the 2/3 font width + for( sal_Int32 i = 0; i < m_aExpand.getLength(); ++i ) + { + OSL_ENSURE( aScrType[i] < SW_SCRIPTS, "Combined: Script fault" ); + if( !aWidth[ aScrType[i] ] ) + { + rInf.GetOut()->SetFont( rInf.GetFont()->GetFnt( aScrType[i] ) ); + aWidth[ aScrType[i] ] = + static_cast<sal_uInt16>(2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3); + } + } + } + + const sal_Int32 nTop = ( nCount + 1 ) / 2; // the first character of the second line + SwViewShell *pSh = rInf.GetTextFrame()->getRootFrame()->GetCurrShell(); + SwFont aTmpFont( *rInf.GetFont() ); + SwFontSave aFontSave( rInf, &aTmpFont ); + nProportion = 55; + // In nMainAscent/Descent we store the ascent and descent + // of the original surrounding font + sal_uInt16 nMaxDescent, nMaxAscent, nMaxWidth; + sal_uInt16 nMainDescent = rInf.GetFont()->GetHeight( pSh, *rInf.GetOut() ); + const sal_uInt16 nMainAscent = rInf.GetFont()->GetAscent( pSh, *rInf.GetOut() ); + nMainDescent = nMainDescent - nMainAscent; + // we start with a 50% font, but if we notice that the combined portion + // becomes bigger than the surrounding font, we check 45% and maybe 40%. + do + { + nProportion -= 5; + aTmpFont.SetProportion( nProportion ); + memset( &aPos, 0, sizeof(aPos) ); + nMaxDescent = 0; + nMaxAscent = 0; + nMaxWidth = 0; + nUpPos = nLowPos = 0; + + // Now we get the width of all characters. + // The ascent and the width of the first line are stored in the + // ascent member of the portion, the descent in nLowPos. + // The ascent, descent and width of the second line are stored in the + // local nMaxAscent, nMaxDescent and nMaxWidth variables. + for( sal_Int32 i = 0; i < nCount; ++i ) + { + SwFontScript nScrp = aScrType[i]; + aTmpFont.SetActual( nScrp ); + if( aWidth[ nScrp ] ) + { + Size aFontSize( aTmpFont.GetSize( nScrp ) ); + aFontSize.setWidth( aWidth[ nScrp ] ); + aTmpFont.SetSize( aFontSize, nScrp ); + } + + SwDrawTextInfo aDrawInf(pSh, *rInf.GetOut(), m_aExpand, i, 1); + Size aSize = aTmpFont.GetTextSize_( aDrawInf ); + const sal_uInt16 nAsc = aTmpFont.GetAscent( pSh, *rInf.GetOut() ); + aPos[ i ] = static_cast<sal_uInt16>(aSize.Width()); + if( i == nTop ) // enter the second line + { + nLowPos = nMaxDescent; + Height( nMaxDescent + nMaxAscent ); + Width( nMaxWidth ); + SetAscent( nMaxAscent ); + nMaxAscent = 0; + nMaxDescent = 0; + nMaxWidth = 0; + } + nMaxWidth = nMaxWidth + aPos[ i ]; + if( nAsc > nMaxAscent ) + nMaxAscent = nAsc; + if( aSize.Height() - nAsc > nMaxDescent ) + nMaxDescent = static_cast<sal_uInt16>(aSize.Height() - nAsc); + } + // for one or two characters we double the width of the portion + if( nCount < 3 ) + { + nMaxWidth *= 2; + Width( 2*Width() ); + if( nCount < 2 ) + { + Height( nMaxAscent + nMaxDescent ); + nLowPos = nMaxDescent; + } + } + Height( Height() + nMaxDescent + nMaxAscent ); + nUpPos = nMaxAscent; + SetAscent( Height() - nMaxDescent - nLowPos ); + } while( nProportion > 40 && ( GetAscent() > nMainAscent || + Height() - GetAscent() > nMainDescent ) ); + // if the combined portion is smaller than the surrounding text, + // the portion grows. This looks better, if there's a character background. + if( GetAscent() < nMainAscent ) + { + Height( Height() + nMainAscent - GetAscent() ); + SetAscent( nMainAscent ); + } + if( Height() < nMainAscent + nMainDescent ) + Height( nMainAscent + nMainDescent ); + + // We calculate the x positions of the characters in both lines... + sal_uInt16 nTopDiff = 0; + sal_uInt16 nBotDiff = 0; + if( nMaxWidth > Width() ) + { + nTopDiff = ( nMaxWidth - Width() ) / 2; + Width( nMaxWidth ); + } + else + nBotDiff = ( Width() - nMaxWidth ) / 2; + switch( nTop) + { + case 3: aPos[1] = aPos[0] + nTopDiff; + [[fallthrough]]; + case 2: aPos[nTop-1] = Width() - aPos[nTop-1]; + } + aPos[0] = 0; + switch( nCount ) + { + case 5: aPos[4] = aPos[3] + nBotDiff; + [[fallthrough]]; + case 3: aPos[nTop] = nBotDiff; break; + case 6: aPos[4] = aPos[3] + nBotDiff; + [[fallthrough]]; + case 4: aPos[nTop] = 0; + [[fallthrough]]; + case 2: aPos[nCount-1] = Width() - aPos[nCount-1]; + } + + // Does the combined portion fit the line? + const bool bFull = rInf.Width() < rInf.X() + Width(); + if( bFull ) + { + if( rInf.GetLineStart() == rInf.GetIdx() && (!rInf.GetLast()->InFieldGrp() + || !static_cast<SwFieldPortion*>(rInf.GetLast())->IsFollow() ) ) + Width( static_cast<sal_uInt16>( rInf.Width() - rInf.X() ) ); + else + { + Truncate(); + Width( 0 ); + SetLen(TextFrameIndex(0)); + if( rInf.GetLast() ) + rInf.GetLast()->FormatEOL( rInf ); + } + } + return bFull; +} + +sal_uInt16 SwCombinedPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +{ + if( !GetLen() ) // for the dummy part at the end of the line, where + return 0; // the combined portion doesn't fit. + return SwFieldPortion::GetViewWidth( rInf ); +} + +SwFieldPortion *SwFieldFormDropDownPortion::Clone(const OUString &rExpand) const +{ + return new SwFieldFormDropDownPortion(m_pFieldMark, rExpand); +} + +void SwFieldFormDropDownPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + SwFieldPortion::Paint( rInf ); + + ::sw::mark::DropDownFieldmark* pDropDownField = dynamic_cast< ::sw::mark::DropDownFieldmark* >(m_pFieldMark); + if(pDropDownField) + { + SwRect aPaintArea; + rInf.CalcRect( *this, &aPaintArea ); + pDropDownField->SetPortionPaintArea(aPaintArea); + } +} + +SwFieldPortion *SwFieldFormDatePortion::Clone(const OUString &/*rExpand*/) const +{ + return new SwFieldFormDatePortion(m_pFieldMark, m_bStart); +} + +void SwFieldFormDatePortion::Paint( const SwTextPaintInfo &rInf ) const +{ + SwFieldPortion::Paint( rInf ); + + ::sw::mark::DateFieldmark* pDateField = dynamic_cast< ::sw::mark::DateFieldmark* >(m_pFieldMark); + if(pDateField) + { + SwRect aPaintArea; + rInf.CalcRect( *this, &aPaintArea ); + if(m_bStart) + pDateField->SetPortionPaintAreaStart(aPaintArea); + else + pDateField->SetPortionPaintAreaEnd(aPaintArea); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porfld.hxx b/sw/source/core/text/porfld.hxx new file mode 100644 index 000000000..ec70c66d2 --- /dev/null +++ b/sw/source/core/text/porfld.hxx @@ -0,0 +1,254 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORFLD_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORFLD_HXX + +#include <swtypes.hxx> +#include <swfont.hxx> +#include "porexp.hxx" +#include <o3tl/enumarray.hxx> + +class SvxBrushItem; +class SwFormatVertOrient; + +class SwFieldPortion : public SwExpandPortion +{ + friend class SwTextFormatter; +protected: + OUString m_aExpand; // The expanded field + std::unique_ptr<SwFont> m_pFont; // For multi-line fields + TextFrameIndex m_nNextOffset; // Offset of the follow in the original string + TextFrameIndex m_nNextScriptChg; + sal_uInt16 m_nViewWidth; // Screen width for empty fields + bool m_bFollow : 1; // 2nd or later part of a field + bool m_bLeft : 1; // Used by SwNumberPortion + bool m_bHide : 1; // Used by SwNumberPortion + bool m_bCenter : 1; // Used by SwNumberPortion + bool m_bHasFollow : 1; // Continues on the next line + bool m_bAnimated : 1; // Used by SwGrfNumPortion + bool m_bNoPaint : 1; // Used by SwGrfNumPortion + bool m_bReplace : 1; // Used by SwGrfNumPortion + const bool m_bPlaceHolder : 1; + bool m_bNoLength : 1; // HACK for meta suffix (no CH_TXTATR) + + void SetFont( std::unique_ptr<SwFont> pNew ) { m_pFont = std::move(pNew); } + bool IsNoLength() const { return m_bNoLength; } + void SetNoLength() { m_bNoLength = true; } + +public: + SwFieldPortion( const SwFieldPortion& rField ); + SwFieldPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFnt = nullptr, bool bPlaceHolder = false ); + virtual ~SwFieldPortion() override; + + sal_uInt16 m_nAttrFieldType; + void TakeNextOffset( const SwFieldPortion* pField ); + void CheckScript( const SwTextSizeInfo &rInf ); + bool HasFont() const { return nullptr != m_pFont; } + // #i89179# - made public + const SwFont *GetFont() const { return m_pFont.get(); } + + const OUString& GetExp() const { return m_aExpand; } + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + + // Empty fields are also allowed + virtual SwLinePortion *Compress() override; + + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + + bool IsFollow() const { return m_bFollow; } + void SetFollow( bool bNew ) { m_bFollow = bNew; } + + bool IsLeft() const { return m_bLeft; } + void SetLeft( bool bNew ) { m_bLeft = bNew; } + + bool IsHide() const { return m_bHide; } + void SetHide( bool bNew ) { m_bHide = bNew; } + + bool IsCenter() const { return m_bCenter; } + void SetCenter( bool bNew ) { m_bCenter = bNew; } + + bool HasFollow() const { return m_bHasFollow; } + void SetHasFollow( bool bNew ) { m_bHasFollow = bNew; } + + TextFrameIndex GetNextOffset() const { return m_nNextOffset; } + void SetNextOffset(TextFrameIndex nNew) { m_nNextOffset = nNew; } + + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand ) const; + + // Extra GetTextSize because of pFnt + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +/** + * Distinguish only for painting/hide + */ +class SwHiddenPortion : public SwFieldPortion +{ +public: + SwHiddenPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFntL = nullptr ) + : SwFieldPortion( rExpand, std::move(pFntL) ) + { SetLen(TextFrameIndex(1)); SetWhichPor( PortionType::Hidden ); } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand ) const override; +}; + +class SwNumberPortion : public SwFieldPortion +{ +protected: + sal_uInt16 nFixWidth; // See Glues + sal_uInt16 nMinDist; // Minimal distance to the text + bool mbLabelAlignmentPosAndSpaceModeActive; + +public: + SwNumberPortion( const OUString &rExpand, + std::unique_ptr<SwFont> pFnt, + const bool bLeft, + const bool bCenter, + const sal_uInt16 nMinDst, + const bool bLabelAlignmentPosAndSpaceModeActive ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand ) const override; + virtual void FormatEOL( SwTextFormatInfo &rInf ) override; +}; + +class SwBulletPortion : public SwNumberPortion +{ +public: + SwBulletPortion( const sal_Unicode cCh, + const OUString& rBulletFollowedBy, + std::unique_ptr<SwFont> pFnt, + const bool bLeft, + const bool bCenter, + const sal_uInt16 nMinDst, + const bool bLabelAlignmentPosAndSpaceModeActive ); +}; + +class SwGrfNumPortion : public SwNumberPortion +{ + std::unique_ptr<SvxBrushItem> pBrush; + long nId; // For StopAnimation + SwTwips nYPos; // _Always_ contains the current RelPos + SwTwips nGrfHeight; + sal_Int16 eOrient; +public: + SwGrfNumPortion( const OUString& rGraphicFollowedBy, + const SvxBrushItem* pGrfBrush, + OUString const & referer, + const SwFormatVertOrient* pGrfOrient, + const Size& rGrfSize, + const bool bLeft, + const bool bCenter, + const sal_uInt16 nMinDst, + const bool bLabelAlignmentPosAndSpaceModeActive ); + virtual ~SwGrfNumPortion() override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + + void SetBase( long nLnAscent, long nLnDescent, + long nFlyAscent, long nFlyDescent ); + + void StopAnimation( OutputDevice* pOut ); + + bool IsAnimated() const { return m_bAnimated; } + void SetAnimated( bool bNew ) { m_bAnimated = bNew; } + void SetRelPos( SwTwips nNew ) { nYPos = nNew; } + void SetId( long nNew ) const + { const_cast<SwGrfNumPortion*>(this)->nId = nNew; } + SwTwips GetRelPos() const { return nYPos; } + SwTwips GetGrfHeight() const { return nGrfHeight; } + sal_Int16 GetOrient() const { return eOrient; } +}; + +/** + * Used in for asian layout specialities to display up to six characters + * in 2 rows and 2-3 columns. + * E.g.: <pre> + * A.. A.. A.B A.B A.B.C A.B.C + * ... ..B .C. C.D .D.E. D.E.F + * </pre> + */ +class SwCombinedPortion : public SwFieldPortion +{ + sal_uInt16 aPos[6]; // up to six X positions + o3tl::enumarray<SwFontScript,sal_uInt16> aWidth = {}; // one width for every scripttype + SwFontScript aScrType[6]; // scripttype of every character + sal_uInt16 nUpPos; // the Y position of the upper baseline + sal_uInt16 nLowPos; // the Y position of the lower baseline + sal_uInt8 nProportion; // relative font height +public: + explicit SwCombinedPortion( const OUString &rExpand ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; +}; + +namespace sw::mark { class IFieldmark; } + +class SwFieldFormDropDownPortion : public SwFieldPortion +{ +public: + explicit SwFieldFormDropDownPortion(sw::mark::IFieldmark *pFieldMark, const OUString &rExpand) + : SwFieldPortion(rExpand) + , m_pFieldMark(pFieldMark) + { + } + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand ) const override; + + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + +private: + sw::mark::IFieldmark* m_pFieldMark; +}; + +class SwFieldFormDatePortion : public SwFieldPortion +{ +public: + explicit SwFieldFormDatePortion(sw::mark::IFieldmark *pFieldMark, bool bStart) + : SwFieldPortion("") + , m_pFieldMark(pFieldMark) + , m_bStart(bStart) + { + } + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand) const override; + + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + +private: + sw::mark::IFieldmark* m_pFieldMark; + bool m_bStart; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porfly.cxx b/sw/source/core/text/porfly.cxx new file mode 100644 index 000000000..d785a9f3c --- /dev/null +++ b/sw/source/core/text/porfly.cxx @@ -0,0 +1,405 @@ +/* -*- 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 <dcontact.hxx> +#include <dflyobj.hxx> +#include <pam.hxx> +#include "portab.hxx" +#include <flyfrm.hxx> +#include <rootfrm.hxx> +#include <frmfmt.hxx> +#include <viewsh.hxx> +#include <textboxhelper.hxx> + +#include <sal/log.hxx> +#include <fmtanchr.hxx> +#include <fmtflcnt.hxx> +#include <fmtornt.hxx> +#include <flyfrms.hxx> +#include <txatbase.hxx> +#include "porfly.hxx" +#include "porlay.hxx" +#include "inftxt.hxx" + +#include <sortedobjs.hxx> + +/** + * class SwFlyPortion => we expect a frame-locale SwRect! + */ + +void SwFlyPortion::Paint( const SwTextPaintInfo& ) const +{ +} + +bool SwFlyPortion::Format( SwTextFormatInfo &rInf ) +{ + OSL_ENSURE( GetFix() >= rInf.X(), "SwFlyPortion::Format" ); + + // tabs must be expanded + if( rInf.GetLastTab() ) + rInf.GetLastTab()->FormatEOL( rInf ); + + rInf.GetLast()->FormatEOL( rInf ); + PrtWidth( static_cast<sal_uInt16>(GetFix() - rInf.X() + PrtWidth()) ); + if( !Width() ) + { + OSL_ENSURE( Width(), "+SwFlyPortion::Format: a fly is a fly is a fly" ); + Width(1); + } + + // resetting + rInf.SetFly( nullptr ); + rInf.Width( rInf.RealWidth() ); + rInf.GetParaPortion()->SetFly(); + + // trailing blank: + if( rInf.GetIdx() < TextFrameIndex(rInf.GetText().getLength()) + && TextFrameIndex(1) < rInf.GetIdx() + && !rInf.GetRest() + && ' ' == rInf.GetChar( rInf.GetIdx() ) + && ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) + && ( !rInf.GetLast() || !rInf.GetLast()->IsBreakPortion() ) ) + { + SetBlankWidth( rInf.GetTextSize(OUString(' ')).Width() ); + SetLen(TextFrameIndex(1)); + } + + const sal_uInt16 nNewWidth = static_cast<sal_uInt16>(rInf.X() + PrtWidth()); + if( rInf.Width() <= nNewWidth ) + { + Truncate(); + if( nNewWidth > rInf.Width() ) + { + PrtWidth( nNewWidth - rInf.Width() ); + SetFixWidth( PrtWidth() ); + } + return true; + } + return false; +} + +bool SwFlyCntPortion::Format( SwTextFormatInfo &rInf ) +{ + bool bFull = rInf.Width() < rInf.X() + PrtWidth(); + + if( bFull ) + { + // If the line is full, and the character-bound frame is at + // the beginning of a line + // If it is not possible to side step into a Fly + // "Begin of line" criteria ( ! rInf.X() ) has to be extended. + // KerningPortions at beginning of line, e.g., for grid layout + // must be considered. + const SwLinePortion* pLastPor = rInf.GetLast(); + const sal_uInt16 nLeft = ( pLastPor && + ( pLastPor->IsKernPortion() || + pLastPor->IsErgoSumPortion() ) ) ? + pLastPor->Width() : + 0; + + if( nLeft == rInf.X() && ! rInf.GetFly() ) + { + Width( rInf.Width() ); + bFull = false; // so that notes can still be placed in this line + } + else + { + if( !rInf.GetFly() ) + rInf.SetNewLine( true ); + Width(0); + SetAscent(0); + SetLen(TextFrameIndex(0)); + if( rInf.GetLast() ) + rInf.GetLast()->FormatEOL( rInf ); + + return bFull; + } + } + + rInf.GetParaPortion()->SetFly(); + return bFull; +} + +//TODO: improve documentation +/** move character-bound objects inside the given area + * + * This allows moving those objects from Master to Follow, or vice versa. + * + * @param pNew + * @param nStart + * @param nEnd + */ +void SwTextFrame::MoveFlyInCnt(SwTextFrame *pNew, + TextFrameIndex const nStart, TextFrameIndex const nEnd) +{ + SwSortedObjs *pObjs = nullptr; + if ( nullptr != (pObjs = GetDrawObjs()) ) + { + for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i ) + { + // Consider changed type of <SwSortedList> entries + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + const SwFormatAnchor& rAnch = pAnchoredObj->GetFrameFormat().GetAnchor(); + if (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + const SwPosition* pPos = rAnch.GetContentAnchor(); + TextFrameIndex const nIndex(MapModelToViewPos(*pPos)); + if (nStart <= nIndex && nIndex < nEnd) + { + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + RemoveFly( static_cast<SwFlyFrame*>(pAnchoredObj) ); + pNew->AppendFly( static_cast<SwFlyFrame*>(pAnchoredObj) ); + } + else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr ) + { + RemoveDrawObj( *pAnchoredObj ); + pNew->AppendDrawObj( *pAnchoredObj ); + } + --i; + } + } + } + } +} + +TextFrameIndex SwTextFrame::CalcFlyPos( SwFrameFormat const * pSearch ) +{ + sw::MergedAttrIter iter(*this); + for (SwTextAttr const* pHt = iter.NextAttr(); pHt; pHt = iter.NextAttr()) + { + if( RES_TXTATR_FLYCNT == pHt->Which() ) + { + SwFrameFormat* pFrameFormat = pHt->GetFlyCnt().GetFrameFormat(); + if( pFrameFormat == pSearch ) + { + return TextFrameIndex(pHt->GetStart()); + } + } + } + OSL_ENSURE(false, "CalcFlyPos: Not Found!"); + return TextFrameIndex(COMPLETE_STRING); +} + +void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const +{ + // Baseline output + // Re-paint everything at a CompletePaint call + SwRect aRepaintRect(rInf.GetPaintRect()); + + if(rInf.GetTextFrame()->IsRightToLeft()) + rInf.GetTextFrame()->SwitchLTRtoRTL(aRepaintRect); + + if(rInf.GetTextFrame()->IsVertical()) + rInf.GetTextFrame()->SwitchHorizontalToVertical(aRepaintRect); + + if((m_pFly->IsCompletePaint() || + m_pFly->getFrameArea().IsOver(aRepaintRect)) && + SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), m_pFly->getRootFrame()->GetCurrShell())) + { + SwRect aRect(m_pFly->getFrameArea()); + if(!m_pFly->IsCompletePaint()) + aRect.Intersection_(aRepaintRect); + + // GetFlyFrame() may change the layout mode at the output device. + { + SwLayoutModeModifier aLayoutModeModifier(*rInf.GetOut()); + m_pFly->PaintSwFrame(const_cast<vcl::RenderContext&>(*rInf.GetOut()), aRect); + } + const_cast<SwTextPaintInfo&>(rInf).GetRefDev()->SetLayoutMode(rInf.GetOut()->GetLayoutMode()); + + // As the OutputDevice might be anything, the font must be re-selected. + // Being in const method should not be a problem. + const_cast<SwTextPaintInfo&>(rInf).SelectFont(); + + assert(rInf.GetVsh()); + SAL_WARN_IF(rInf.GetVsh()->GetOut() != rInf.GetOut(), "sw.core", "SwFlyCntPortion::Paint: Outdev has changed"); + if(rInf.GetVsh()) + const_cast<SwTextPaintInfo&>(rInf).SetOut(rInf.GetVsh()->GetOut()); + } +} + +void sw::DrawFlyCntPortion::Paint(const SwTextPaintInfo&) const +{ + if(!m_pContact->GetAnchorFrame()) + { + // No direct positioning of the drawing object is needed + m_pContact->ConnectToLayout(); + } +} + +/** + * Use the dimensions of pFly->OutRect() + */ +SwFlyCntPortion::SwFlyCntPortion() + : m_bMax(false) + , m_eAlign(sw::LineAlign::NONE) +{ + nLineLength = TextFrameIndex(1); + SetWhichPor(PortionType::FlyCnt); +} + +sw::FlyContentPortion::FlyContentPortion(SwFlyInContentFrame* pFly) + : m_pFly(pFly) +{ + SAL_WARN_IF(!pFly, "sw.core", "SwFlyCntPortion::SwFlyCntPortion: no SwFlyInContentFrame!"); +} + +sw::DrawFlyCntPortion::DrawFlyCntPortion(SwFrameFormat const & rFormat) + : m_pContact(nullptr) +{ + rFormat.CallSwClientNotify(sw::CreatePortionHint(&m_pContact)); + assert(m_pContact); +} + +sw::FlyContentPortion* sw::FlyContentPortion::Create(const SwTextFrame& rFrame, SwFlyInContentFrame* pFly, const Point& rBase, long nLnAscent, long nLnDescent, long nFlyAsc, long nFlyDesc, AsCharFlags nFlags) +{ + auto pNew(new sw::FlyContentPortion(pFly)); + pNew->SetBase(rFrame, rBase, nLnAscent, nLnDescent, nFlyAsc, nFlyDesc, nFlags | AsCharFlags::UlSpace | AsCharFlags::Init); + return pNew; +} + +sw::DrawFlyCntPortion* sw::DrawFlyCntPortion::Create(const SwTextFrame& rFrame, SwFrameFormat const & rFormat, const Point& rBase, long nLnAscent, long nLnDescent, long nFlyAsc, long nFlyDesc, AsCharFlags nFlags) +{ + auto pNew(new DrawFlyCntPortion(rFormat)); + pNew->SetBase(rFrame, rBase, nLnAscent, nLnDescent, nFlyAsc, nFlyDesc, nFlags | AsCharFlags::UlSpace | AsCharFlags::Init); + return pNew; +} + +sw::DrawFlyCntPortion::~DrawFlyCntPortion() {}; +sw::FlyContentPortion::~FlyContentPortion() {}; + +SdrObject* sw::FlyContentPortion::GetSdrObj(const SwTextFrame&) +{ + return m_pFly->GetVirtDrawObj(); +} + +SdrObject* sw::DrawFlyCntPortion::GetSdrObj(const SwTextFrame& rFrame) +{ + SdrObject* pSdrObj; + // Determine drawing object ('master' or 'virtual') by frame + pSdrObj = m_pContact->GetDrawObjectByAnchorFrame(rFrame); + if(!pSdrObj) + { + SAL_WARN("sw.core", "SwFlyCntPortion::SetBase(..) - No drawing object found by <GetDrawContact()->GetDrawObjectByAnchorFrame( rFrame )>"); + pSdrObj = m_pContact->GetMaster(); + } + + // Call <SwAnchoredDrawObject::MakeObjPos()> to assure that flag at + // the <DrawFrameFormat> and at the <SwAnchoredDrawObject> instance are + // correctly set + if(pSdrObj) + m_pContact->GetAnchoredObj(pSdrObj)->MakeObjPos(); + return pSdrObj; +} + +/** + * After setting the RefPoints, the ascent needs to be recalculated + * because it is dependent on RelPos + * + * @param rBase CAUTION: needs to be an absolute value! + */ +void SwFlyCntPortion::SetBase( const SwTextFrame& rFrame, const Point &rBase, + long nLnAscent, long nLnDescent, + long nFlyAsc, long nFlyDesc, + AsCharFlags nFlags ) +{ + // Use new class to position object + // Determine drawing object + SdrObject* pSdrObj = GetSdrObj(rFrame); + if (!pSdrObj) + return; + + // position object + objectpositioning::SwAsCharAnchoredObjectPosition aObjPositioning( + *pSdrObj, + rBase, nFlags, + nLnAscent, nLnDescent, nFlyAsc, nFlyDesc ); + + // Scope of local variable <aObjPosInProgress> + { + SwObjPositioningInProgress aObjPosInProgress( *pSdrObj ); + aObjPositioning.CalcPosition(); + } + + SwFrameFormat* pShape = FindFrameFormat(pSdrObj); + const SwFormatAnchor& rAnchor(pShape->GetAnchor()); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + // This is an inline draw shape, see if it has a textbox. + SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT); + if (pTextBox) + { + // It has, so look up its text rectangle, and adjust the position + // of the textbox accordingly. + // Both rectangles are absolute, SwFormatHori/VertOrient's position + // is relative to the print area of the anchor text frame. + tools::Rectangle aTextRectangle = SwTextBoxHelper::getTextRectangle(pShape); + + SwFormatHoriOrient aHori(pTextBox->GetHoriOrient()); + aHori.SetHoriOrient(css::text::HoriOrientation::NONE); + sal_Int32 nLeft = aTextRectangle.getX() - rFrame.getFrameArea().Left() + - rFrame.getFramePrintArea().Left(); + aHori.SetPos(nLeft); + + SwFormatVertOrient aVert(pTextBox->GetVertOrient()); + aVert.SetVertOrient(css::text::VertOrientation::NONE); + sal_Int32 const nTop = aTextRectangle.getY() - rFrame.getFrameArea().Top() + - rFrame.getFramePrintArea().Top(); + aVert.SetPos(nTop); + + pTextBox->LockModify(); + pTextBox->SetFormatAttr(aHori); + pTextBox->SetFormatAttr(aVert); + pTextBox->UnlockModify(); + } + } + + SetAlign( aObjPositioning.GetLineAlignment() ); + + m_aRef = aObjPositioning.GetAnchorPos(); + if( nFlags & AsCharFlags::Rotate ) + SvXSize( aObjPositioning.GetObjBoundRectInclSpacing().SSize() ); + else + SvLSize( aObjPositioning.GetObjBoundRectInclSpacing().SSize() ); + if( Height() ) + { + // GetRelPosY returns the relative position to baseline (if 0, the + // upper border of the FlyCnt if on the baseline of a line) + SwTwips nRelPos = aObjPositioning.GetRelPosY(); + if ( nRelPos < 0 ) + { + nAscent = static_cast<sal_uInt16>(-nRelPos); + if( nAscent > Height() ) + Height( nAscent ); + } + else + { + nAscent = 0; + Height( Height() + static_cast<sal_uInt16>(nRelPos) ); + } + } + else + { + Height( 1 ); + nAscent = 0; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porfly.hxx b/sw/source/core/text/porfly.hxx new file mode 100644 index 000000000..c01abd499 --- /dev/null +++ b/sw/source/core/text/porfly.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORFLY_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORFLY_HXX + +#include <ascharanchoredobjectposition.hxx> + +#include "porglue.hxx" +#include <flyfrms.hxx> + +class SwDrawContact; +class SwTextFrame; +struct SwCursorMoveState; + +class SwFlyPortion : public SwFixPortion +{ + sal_uInt16 nBlankWidth; +public: + explicit SwFlyPortion( const SwRect &rFlyRect ) + : SwFixPortion(rFlyRect), nBlankWidth( 0 ) { SetWhichPor( PortionType::Fly ); } + sal_uInt16 GetBlankWidth( ) const { return nBlankWidth; } + void SetBlankWidth( const sal_uInt16 nNew ) { nBlankWidth = nNew; } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; +}; + +/// This portion represents an as-character anchored fly (shape, frame, etc.) +class SwFlyCntPortion : public SwLinePortion +{ + Point m_aRef; // Relatively to this point we calculate the AbsPos + bool m_bMax; // Line adjustment and height == line height + sw::LineAlign m_eAlign; + + virtual SdrObject* GetSdrObj(const SwTextFrame&) =0; + +public: + SwFlyCntPortion(); + const Point& GetRefPoint() const { return m_aRef; } + bool IsMax() const { return m_bMax; } + sw::LineAlign GetAlign() const { return m_eAlign; } + void SetAlign(sw::LineAlign eAlign) { m_eAlign = eAlign; } + void SetMax(bool bMax) { m_bMax = bMax; } + void SetBase(const SwTextFrame& rFrame, const Point& rBase, long nLnAscent, long nLnDescent, long nFlyAscent, long nFlyDescent, AsCharFlags nFlags); + virtual bool Format(SwTextFormatInfo& rInf) override; +}; + +namespace sw +{ + class FlyContentPortion final : public SwFlyCntPortion + { + SwFlyInContentFrame* m_pFly; + virtual SdrObject* GetSdrObj(const SwTextFrame&) override; + public: + FlyContentPortion(SwFlyInContentFrame* pFly); + static FlyContentPortion* Create(const SwTextFrame& rFrame, SwFlyInContentFrame* pFly, const Point& rBase, long nAscent, long nDescent, long nFlyAsc, long nFlyDesc, AsCharFlags nFlags); + SwFlyInContentFrame* GetFlyFrame() { return m_pFly; } + void GetFlyCursorOfst(Point& rPoint, SwPosition& rPos, SwCursorMoveState* pCMS) const { m_pFly->GetModelPositionForViewPoint(&rPos, rPoint, pCMS); }; + virtual void Paint(const SwTextPaintInfo& rInf) const override; + virtual ~FlyContentPortion() override; + }; + class DrawFlyCntPortion final : public SwFlyCntPortion + { + SwDrawContact* m_pContact; + virtual SdrObject* GetSdrObj(const SwTextFrame&) override; + public: + DrawFlyCntPortion(SwFrameFormat const & rFormat); + static DrawFlyCntPortion* Create(const SwTextFrame& rFrame, SwFrameFormat const & rFormat, const Point& rBase, long nAsc, long nDescent, long nFlyAsc, long nFlyDesc, AsCharFlags nFlags); + virtual void Paint(const SwTextPaintInfo& rInf) const override; + virtual ~DrawFlyCntPortion() override; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porftn.hxx b/sw/source/core/text/porftn.hxx new file mode 100644 index 000000000..181d77a64 --- /dev/null +++ b/sw/source/core/text/porftn.hxx @@ -0,0 +1,102 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORFTN_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORFTN_HXX + +#include "porfld.hxx" + +class SwTextFootnote; + +class SwFootnotePortion : public SwFieldPortion +{ + SwTextFootnote *pFootnote; + sal_uInt16 nOrigHeight; + // #i98418# + bool mbPreferredScriptTypeSet; + SwFontScript mnPreferredScriptType; +public: + SwFootnotePortion( const OUString &rExpand, SwTextFootnote *pFootnote, + sal_uInt16 nOrig = USHRT_MAX ); + sal_uInt16& Orig() { return nOrigHeight; } + + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + + // #i98418# + void SetPreferredScriptType( SwFontScript nPreferredScriptType ); + + const SwTextFootnote* GetTextFootnote() const { return pFootnote; }; +}; + +class SwFootnoteNumPortion : public SwNumberPortion +{ +public: + SwFootnoteNumPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFntL ) + : SwNumberPortion( rExpand, std::move(pFntL), true, false, 0, false ) + { SetWhichPor( PortionType::FootnoteNum ); } +}; + +/** + * Used in footnotes if they break across pages, master has this portion at the end. + * + * Created only in case Tools -> Footnotes and Endnotes sets the End of footnote to a non-empty + * value. + */ +class SwQuoVadisPortion : public SwFieldPortion +{ + OUString aErgo; +public: + SwQuoVadisPortion( const OUString &rExp, const OUString& rStr ); + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + + void SetNumber( const OUString& rStr ) { aErgo = rStr; } + const OUString& GetQuoText() const { return m_aExpand; } + const OUString &GetContText() const { return aErgo; } + + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +/** + * Used in footnotes if they break across pages, follow starts with this portion. + * + * Created only in case Tools -> Footnotes and Endnotes sets the Start of next page to a non-empty + * value. + */ +class SwErgoSumPortion : public SwFieldPortion +{ +public: + SwErgoSumPortion( const OUString &rExp, const OUString& rStr ); + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + + // Field cloner for SplitGlue + virtual SwFieldPortion *Clone( const OUString &rExpand ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porglue.cxx b/sw/source/core/text/porglue.cxx new file mode 100644 index 000000000..0d34ea304 --- /dev/null +++ b/sw/source/core/text/porglue.cxx @@ -0,0 +1,260 @@ +/* -*- 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 <swrect.hxx> +#include <viewopt.hxx> +#include "porglue.hxx" +#include "inftxt.hxx" +#include "porlay.hxx" +#include "porfly.hxx" +#include <comphelper/string.hxx> + +SwGluePortion::SwGluePortion( const sal_uInt16 nInitFixWidth ) + : nFixWidth( nInitFixWidth ) +{ + PrtWidth( nFixWidth ); + SetWhichPor( PortionType::Glue ); +} + +TextFrameIndex SwGluePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +{ + // FIXME why nOfst > GetLen() ? is that supposed to be > Width() ? + if( !GetLen() || nOfst > sal_Int32(GetLen()) || !Width() ) + return SwLinePortion::GetModelPositionForViewPoint( nOfst ); + else + return TextFrameIndex(nOfst / (Width() / sal_Int32(GetLen()))); +} + +SwPosSize SwGluePortion::GetTextSize( const SwTextSizeInfo &rInf ) const +{ + if (TextFrameIndex(1) >= GetLen() || rInf.GetLen() > GetLen() || !Width() || !GetLen()) + return SwPosSize(*this); + else + return SwPosSize((Width() / sal_Int32(GetLen())) * sal_Int32(rInf.GetLen()), Height()); +} + +bool SwGluePortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const +{ + if( GetLen() && rInf.OnWin() && + rInf.GetOpt().IsBlank() && rInf.IsNoSymbol() ) + { + OUStringBuffer aBuf; + comphelper::string::padToLength(aBuf, sal_Int32(GetLen()), CH_BULLET); + rText = aBuf.makeStringAndClear(); + return true; + } + return false; +} + +void SwGluePortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( !GetLen() ) + return; + + if( rInf.GetFont()->IsPaintBlank() ) + { + OUStringBuffer aBuf; + comphelper::string::padToLength(aBuf, GetFixWidth() / sal_Int32(GetLen()), ' '); + OUString aText(aBuf.makeStringAndClear()); + SwTextPaintInfo aInf( rInf, &aText ); + aInf.DrawText(*this, TextFrameIndex(aText.getLength()), true); + } + + if( rInf.OnWin() && rInf.GetOpt().IsBlank() && rInf.IsNoSymbol() ) + { +#if OSL_DEBUG_LEVEL > 0 + const sal_Unicode cChar = rInf.GetChar( rInf.GetIdx() ); + OSL_ENSURE( CH_BLANK == cChar || CH_BULLET == cChar, + "SwGluePortion::Paint: blank expected" ); +#endif + if (TextFrameIndex(1) == GetLen()) + { + OUString aBullet( CH_BULLET ); + SwPosSize aBulletSize( rInf.GetTextSize( aBullet ) ); + Point aPos( rInf.GetPos() ); + aPos.AdjustX((Width()/2) - (aBulletSize.Width()/2) ); + SwTextPaintInfo aInf( rInf, &aBullet ); + aInf.SetPos( aPos ); + SwTextPortion aBulletPor; + aBulletPor.Width( aBulletSize.Width() ); + aBulletPor.Height( aBulletSize.Height() ); + aBulletPor.SetAscent( GetAscent() ); + aInf.DrawText(aBulletPor, TextFrameIndex(aBullet.getLength()), true); + } + else + { + SwTextSlot aSlot( &rInf, this, true, false ); + rInf.DrawText( *this, rInf.GetLen(), true ); + } + } +} + +void SwGluePortion::MoveGlue( SwGluePortion *pTarget, const long nPrtGlue ) +{ + auto nPrt = std::min( nPrtGlue, GetPrtGlue() ); + if( 0 < nPrt ) + { + pTarget->AddPrtWidth( nPrt ); //TODO: overflow + SubPrtWidth( nPrt ); //TODO: overflow + } +} + +void SwGluePortion::Join( SwGluePortion *pVictim ) +{ + // The GluePortion is extracted and flushed away ... + AddPrtWidth( pVictim->PrtWidth() ); + SetLen( pVictim->GetLen() + GetLen() ); + if( Height() < pVictim->Height() ) + Height( pVictim->Height() ); + + AdjFixWidth(); + Cut( pVictim ); + delete pVictim; +} + +/** + * We're expecting a frame-local SwRect! + */ +SwFixPortion::SwFixPortion( const SwRect &rRect ) + :SwGluePortion( sal_uInt16(rRect.Width()) ), nFix( sal_uInt16(rRect.Left()) ) +{ + Height( sal_uInt16(rRect.Height()) ); + SetWhichPor( PortionType::Fix ); +} + +SwFixPortion::SwFixPortion() + : SwGluePortion(0), nFix(0) +{ + SetWhichPor( PortionType::Fix ); +} + +SwMarginPortion::SwMarginPortion() + :SwGluePortion( 0 ) +{ + SetWhichPor( PortionType::Margin ); +} + +/** + * In the outer loop all portions are inspected - the GluePortions + * at the end are processed first. + * The end is shifted forwardly till no more GluePortions remain. + * Always GluePortion-pairs (pLeft and pRight) are treated, where + * textportions between pLeft and pRight are moved at the back of + * pRight if pRight has enough Glue. With every move part of the + * Glue is transferred from pRight to pLeft. + * The next loop starts with the processed pLeft as pRight. + */ +void SwMarginPortion::AdjustRight( const SwLineLayout *pCurr ) +{ + SwGluePortion *pRight = nullptr; + bool bNoMove = nullptr != pCurr->GetpKanaComp(); + while( pRight != this ) + { + + // 1) We search for the left Glue + SwLinePortion *pPos = this; + SwGluePortion *pLeft = nullptr; + while( pPos ) + { + if( pPos->InFixMargGrp() ) + pLeft = static_cast<SwGluePortion*>(pPos); + pPos = pPos->GetNextPortion(); + if( pPos == pRight) + pPos = nullptr; + } + + // Two adjoining FlyPortions are merged + if( pRight && pLeft && pLeft->GetNextPortion() == pRight ) + { + pRight->MoveAllGlue( pLeft ); + pRight = nullptr; + } + auto nRightGlue = pRight && 0 < pRight->GetPrtGlue() + ? pRight->GetPrtGlue() : 0; + // 2) balance left and right Glue + // But not for tabs ... + if( pLeft && nRightGlue && !pRight->InTabGrp() ) + { + // pPrev is the portion immediately before pRight + SwLinePortion *pPrev = pRight->FindPrevPortion( pLeft ); + + if ( pRight->IsFlyPortion() && pRight->GetLen() ) + { + SwFlyPortion *pFly = static_cast<SwFlyPortion *>(pRight); + if ( pFly->GetBlankWidth() < nRightGlue ) + { + // Creating new TextPortion, that takes over the + // Blank previously swallowed by the Fly. + nRightGlue = nRightGlue - pFly->GetBlankWidth(); + pFly->SubPrtWidth( pFly->GetBlankWidth() ); + pFly->SetLen(TextFrameIndex(0)); + SwTextPortion *pNewPor = new SwTextPortion; + pNewPor->SetLen(TextFrameIndex(1)); + pNewPor->Height( pFly->Height() ); + pNewPor->Width( pFly->GetBlankWidth() ); + pFly->Insert( pNewPor ); + } + else + pPrev = pLeft; + } + while( pPrev != pLeft ) + { + if( bNoMove || pPrev->PrtWidth() >= nRightGlue || + pPrev->InHyphGrp() || pPrev->IsKernPortion() ) + { + // The portion before the pRight cannot be moved + // because no Glue is remaining. + // We set the break condition: + pPrev = pLeft; + } + else + { + nRightGlue = nRightGlue - pPrev->PrtWidth(); + // pPrev is moved behind pRight. For this the + // Glue value between pRight and pLeft gets balanced. + pRight->MoveGlue( pLeft, pPrev->PrtWidth() ); + // Now fix the linking of our portions. + SwLinePortion *pPrevPrev = pPrev->FindPrevPortion( pLeft ); + pPrevPrev->SetNextPortion( pRight ); + pPrev->SetNextPortion( pRight->GetNextPortion() ); + pRight->SetNextPortion( pPrev ); + if ( pPrev->GetNextPortion() && pPrev->InTextGrp() + && pPrev->GetNextPortion()->IsHolePortion() ) + { + SwHolePortion *pHolePor = + static_cast<SwHolePortion*>(pPrev->GetNextPortion()); + if ( !pHolePor->GetNextPortion() || + !pHolePor->GetNextPortion()->InFixMargGrp() ) + { + pPrev->AddPrtWidth( pHolePor->GetBlankWidth() ); + pPrev->SetLen(pPrev->GetLen() + TextFrameIndex(1)); + pPrev->SetNextPortion( pHolePor->GetNextPortion() ); + delete pHolePor; + } + } + pPrev = pPrevPrev; + } + } + } + // If no left Glue remains, we set the break condition. + pRight = pLeft ? pLeft : this; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porglue.hxx b/sw/source/core/text/porglue.hxx new file mode 100644 index 000000000..27e9b0dab --- /dev/null +++ b/sw/source/core/text/porglue.hxx @@ -0,0 +1,88 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORGLUE_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORGLUE_HXX + +#include "porlin.hxx" + +class SwRect; +class SwLineLayout; + +class SwGluePortion : public SwLinePortion +{ +private: + sal_uInt16 nFixWidth; +public: + explicit SwGluePortion( const sal_uInt16 nInitFixWidth ); + + void Join( SwGluePortion *pVictim ); + + inline long GetPrtGlue() const; + sal_uInt16 GetFixWidth() const { return nFixWidth; } + void SetFixWidth( const sal_uInt16 nNew ) { nFixWidth = nNew; } + void MoveGlue( SwGluePortion *pTarget, const long nPrtGlue ); + inline void MoveAllGlue( SwGluePortion *pTarget ); + inline void MoveHalfGlue( SwGluePortion *pTarget ); + inline void AdjFixWidth(); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; +}; + +class SwFixPortion : public SwGluePortion +{ + sal_uInt16 nFix; // The width offset in the line +public: + explicit SwFixPortion( const SwRect &rFlyRect ); + SwFixPortion(); + void SetFix( const sal_uInt16 nNewFix ) { nFix = nNewFix; } + sal_uInt16 GetFix() const { return nFix; } +}; + +class SwMarginPortion : public SwGluePortion +{ +public: + explicit SwMarginPortion(); + void AdjustRight( const SwLineLayout* pCurr ); +}; + +inline long SwGluePortion::GetPrtGlue() const +{ return Width() - nFixWidth; } + +// The FixWidth MUST NEVER be larger than the accumulated width! +inline void SwGluePortion::AdjFixWidth() +{ + if( nFixWidth > PrtWidth() ) + nFixWidth = PrtWidth(); +} + +inline void SwGluePortion::MoveAllGlue( SwGluePortion *pTarget ) +{ + MoveGlue( pTarget, GetPrtGlue() ); +} + +inline void SwGluePortion::MoveHalfGlue( SwGluePortion *pTarget ) +{ + MoveGlue( pTarget, GetPrtGlue() / 2 ); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porhyph.hxx b/sw/source/core/text/porhyph.hxx new file mode 100644 index 000000000..57f685abe --- /dev/null +++ b/sw/source/core/text/porhyph.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORHYPH_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORHYPH_HXX + +#include "porexp.hxx" + +class SwHyphPortion : public SwExpandPortion +{ +public: + SwHyphPortion() + { + SetWhichPor( PortionType::Hyphen ); + } + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwHyphStrPortion : public SwHyphPortion +{ + OUString aExpand; +public: + explicit SwHyphStrPortion(const OUString &rStr) + : aExpand(rStr + "-") + { + SetWhichPor( PortionType::HyphenStr ); + } + + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwSoftHyphPortion : public SwHyphPortion +{ + bool bExpand; + sal_uInt16 nViewWidth; + +public: + SwSoftHyphPortion(); + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual SwLinePortion *Compress() override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void FormatEOL( SwTextFormatInfo &rInf ) override; + void SetExpand( const bool bNew ) { bExpand = bNew; } + bool IsExpand() const { return bExpand; } + + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwSoftHyphStrPortion : public SwHyphStrPortion +{ +public: + explicit SwSoftHyphStrPortion( const OUString &rStr ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx new file mode 100644 index 000000000..499dac050 --- /dev/null +++ b/sw/source/core/text/porlay.cxx @@ -0,0 +1,2645 @@ +/* -*- 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 "porlay.hxx" +#include "itrform2.hxx" +#include "porglue.hxx" +#include "redlnitr.hxx" +#include "porfly.hxx" +#include "porrst.hxx" +#include "pormulti.hxx" +#include "pordrop.hxx" +#include <breakit.hxx> +#include <unicode/uchar.h> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/CTLScriptType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <paratr.hxx> +#include <sal/log.hxx> +#include <optional> +#include <editeng/adjustitem.hxx> +#include <editeng/charhiddenitem.hxx> +#include <svl/asiancfg.hxx> +#include <svl/languageoptions.hxx> +#include <tools/multisel.hxx> +#include <unotools/charclass.hxx> +#include <charfmt.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <calbck.hxx> +#include <doc.hxx> +#include <swscanner.hxx> +#include <txatbase.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentContentOperations.hxx> +#include <IMark.hxx> + +using namespace ::com::sun::star; +using namespace i18n::ScriptType; + +#include <unicode/ubidi.h> +#include <i18nutil/scripttypedetector.hxx> +#include <i18nutil/unicode.hxx> + +#define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g ) +#define isAinChar(c) IS_JOINING_GROUP((c), AIN) +#define isAlefChar(c) IS_JOINING_GROUP((c), ALEF) +#define isDalChar(c) IS_JOINING_GROUP((c), DAL) +#if U_ICU_VERSION_MAJOR_NUM >= 58 +#define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH)) +#else +#define isFehChar(c) IS_JOINING_GROUP((c), FEH) +#endif +#define isGafChar(c) IS_JOINING_GROUP((c), GAF) +#define isHehChar(c) IS_JOINING_GROUP((c), HEH) +#define isKafChar(c) IS_JOINING_GROUP((c), KAF) +#define isLamChar(c) IS_JOINING_GROUP((c), LAM) +#if U_ICU_VERSION_MAJOR_NUM >= 58 +#define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF)) +#else +#define isQafChar(c) IS_JOINING_GROUP((c), QAF) +#endif +#define isRehChar(c) IS_JOINING_GROUP((c), REH) +#define isTahChar(c) IS_JOINING_GROUP((c), TAH) +#define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA) +#define isWawChar(c) IS_JOINING_GROUP((c), WAW) +#define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN)) + +// Beh and charters that behave like Beh in medial form. +static bool isBehChar(sal_Unicode cCh) +{ + bool bRet = false; + switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) + { + case U_JG_BEH: + case U_JG_NOON: +#if U_ICU_VERSION_MAJOR_NUM >= 58 + case U_JG_AFRICAN_NOON: +#endif + case U_JG_NYA: + case U_JG_YEH: + case U_JG_FARSI_YEH: + case U_JG_BURUSHASKI_YEH_BARREE: + bRet = true; + break; + default: + bRet = false; + break; + } + + return bRet; +} + +// Yeh and charters that behave like Yeh in final form. +static bool isYehChar(sal_Unicode cCh) +{ + bool bRet = false; + switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) + { + case U_JG_YEH: + case U_JG_FARSI_YEH: + case U_JG_YEH_BARREE: + case U_JG_BURUSHASKI_YEH_BARREE: + case U_JG_YEH_WITH_TAIL: + bRet = true; + break; + default: + bRet = false; + break; + } + + return bRet; +} + +static bool isTransparentChar ( sal_Unicode cCh ) +{ + return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT; +} + +// Checks if cCh + cNectCh builds a ligature (used for Kashidas) +static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) +{ + // Lam + Alef + return ( isLamChar ( cCh ) && isAlefChar ( cNextCh )); +} + +// Checks if cCh is connectable to cPrevCh (used for Kashidas) +static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) +{ + const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE ); + bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING; + + // check for ligatures cPrevChar + cChar + if( bRet ) + bRet = !lcl_IsLigature( cPrevCh, cCh ); + + return bRet; +} + +static bool lcl_HasStrongLTR ( const OUString& rText, sal_Int32 nStart, sal_Int32 nEnd ) + { + for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx ) + { + const UCharDirection nCharDir = u_charDirection ( rText[ nCharIdx ] ); + if ( nCharDir == U_LEFT_TO_RIGHT || + nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || + nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) + return true; + } + return false; + } + +// This is (meant to be) functionally equivalent to 'delete m_pNext' where +// deleting a SwLineLayout recursively deletes the owned m_pNext SwLineLayout. +// +// Here, instead of using a potentially deep stack, iterate over all the +// SwLineLayouts that would be deleted recursively and delete them linearly +void SwLineLayout::DeleteNext() +{ + if (!m_pNext) + return; + std::vector<SwLineLayout*> aNexts; + SwLineLayout* pNext = m_pNext; + do + { + aNexts.push_back(pNext); + SwLineLayout* pLastNext = pNext; + pNext = pNext->GetNext(); + pLastNext->SetNext(nullptr); + } + while (pNext); + for (auto a : aNexts) + delete a; +} + +// class SwLineLayout: This is the layout of a single line, which is made +// up of its dimension, the character count and the word spacing in the line. +// Line objects are managed in an own pool, in order to store them continuously +// in memory so that they are paged out together and don't fragment memory. +SwLineLayout::~SwLineLayout() +{ + Truncate(); + DeleteNext(); + m_pLLSpaceAdd.reset(); + m_pKanaComp.reset(); +} + +SwLinePortion *SwLineLayout::Insert( SwLinePortion *pIns ) +{ + // First attribute change: copy mass and length from *pIns into the first + // text portion + if( !mpNextPortion ) + { + if( GetLen() ) + { + mpNextPortion = SwTextPortion::CopyLinePortion(*this); + if( IsBlinking() ) + { + SetBlinking( false ); + } + } + else + { + SetNextPortion( pIns ); + return pIns; + } + } + // Call with scope or we'll end up with recursion! + return mpNextPortion->SwLinePortion::Insert( pIns ); +} + +SwLinePortion *SwLineLayout::Append( SwLinePortion *pIns ) +{ + // First attribute change: copy mass and length from *pIns into the first + // text portion + if( !mpNextPortion ) + mpNextPortion = SwTextPortion::CopyLinePortion(*this); + // Call with scope or we'll end up with recursion! + return mpNextPortion->SwLinePortion::Append( pIns ); +} + +// For special treatment of empty lines + +bool SwLineLayout::Format( SwTextFormatInfo &rInf ) +{ + if( GetLen() ) + return SwTextPortion::Format( rInf ); + + Height( rInf.GetTextHeight() ); + return true; +} + +// We collect all FlyPortions at the beginning of the line and make that a +// MarginPortion. +SwMarginPortion *SwLineLayout::CalcLeftMargin() +{ + SwMarginPortion *pLeft = (GetNextPortion() && GetNextPortion()->IsMarginPortion()) ? + static_cast<SwMarginPortion *>(GetNextPortion()) : nullptr; + if( !GetNextPortion() ) + SetNextPortion(SwTextPortion::CopyLinePortion(*this)); + if( !pLeft ) + { + pLeft = new SwMarginPortion; + pLeft->SetNextPortion( GetNextPortion() ); + SetNextPortion( pLeft ); + } + else + { + pLeft->Height( 0 ); + pLeft->Width( 0 ); + pLeft->SetLen(TextFrameIndex(0)); + pLeft->SetAscent( 0 ); + pLeft->SetNextPortion( nullptr ); + pLeft->SetFixWidth(0); + } + + SwLinePortion *pPos = pLeft->GetNextPortion(); + while( pPos ) + { + if( pPos->IsFlyPortion() ) + { + // The FlyPortion gets sucked out... + pLeft->Join( static_cast<SwGluePortion*>(pPos) ); + pPos = pLeft->GetNextPortion(); + if( GetpKanaComp() && !GetKanaComp().empty() ) + GetKanaComp().pop_front(); + } + else + pPos = nullptr; + } + return pLeft; +} + +void SwLineLayout::InitSpaceAdd() +{ + if ( !m_pLLSpaceAdd ) + CreateSpaceAdd(); + else + SetLLSpaceAdd( 0, 0 ); +} + +void SwLineLayout::CreateSpaceAdd( const long nInit ) +{ + m_pLLSpaceAdd.reset( new std::vector<long> ); + SetLLSpaceAdd( nInit, 0 ); +} + +// Returns true if there are only blanks in [nStt, nEnd[ +static bool lcl_HasOnlyBlanks(const OUString& rText, TextFrameIndex nStt, TextFrameIndex nEnd) +{ + bool bBlankOnly = true; + while ( nStt < nEnd ) + { + const sal_Unicode cChar = rText[ sal_Int32(nStt++) ]; + if ( ' ' != cChar && CH_FULL_BLANK != cChar && CH_SIX_PER_EM != cChar ) + { + bBlankOnly = false; + break; + } + } + return bBlankOnly; +} + +// Swapped out from FormatLine() +void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) +{ + const sal_uInt16 nLineWidth = rInf.RealWidth(); + + sal_uInt16 nFlyAscent = 0; + sal_uInt16 nFlyHeight = 0; + sal_uInt16 nFlyDescent = 0; + bool bOnlyPostIts = true; + SetHanging( false ); + + bool bTmpDummy = !GetLen(); + SwFlyCntPortion* pFlyCnt = nullptr; + if( bTmpDummy ) + { + nFlyAscent = 0; + nFlyHeight = 0; + nFlyDescent = 0; + } + + // #i3952# + const bool bIgnoreBlanksAndTabsForLineHeightCalculation = + rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION); + + bool bHasBlankPortion = false; + bool bHasOnlyBlankPortions = true; + + if( mpNextPortion ) + { + SetContent( false ); + if( mpNextPortion->IsBreakPortion() ) + { + SetLen( mpNextPortion->GetLen() ); + if( GetLen() ) + bTmpDummy = false; + } + else + { + const sal_uInt16 nLineHeight = Height(); + Init( GetNextPortion() ); + SwLinePortion *pPos = mpNextPortion; + SwLinePortion *pLast = this; + sal_uInt16 nMaxDescent = 0; + + // A group is a segment in the portion chain of pCurr or a fixed + // portion spanning to the end or the next fixed portion + while( pPos ) + { + SAL_WARN_IF( PortionType::NONE == pPos->GetWhichPor(), + "sw.core", "SwLineLayout::CalcLine: don't use SwLinePortions !" ); + + // Null portions are eliminated. They can form if two FlyFrames + // overlap. + if( !pPos->Compress() ) + { + // Only take over Height and Ascent if the rest of the line + // is empty. + if( !pPos->GetNextPortion() ) + { + if( !Height() ) + Height( pPos->Height() ); + if( !GetAscent() ) + SetAscent( pPos->GetAscent() ); + } + delete pLast->Cut( pPos ); + pPos = pLast->GetNextPortion(); + continue; + } + + TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + nLineLength; + nLineLength += pPos->GetLen(); + AddPrtWidth( pPos->Width() ); + + // #i3952# + if ( bIgnoreBlanksAndTabsForLineHeightCalculation ) + { + if ( pPos->InTabGrp() || pPos->IsHolePortion() || + ( pPos->IsTextPortion() && + lcl_HasOnlyBlanks( rInf.GetText(), nPorSttIdx, nPorSttIdx + pPos->GetLen() ) ) ) + { + pLast = pPos; + pPos = pPos->GetNextPortion(); + bHasBlankPortion = true; + continue; + } + } + + // Ignore drop portion height + // tdf#130804 ... and bookmark portions + if ((pPos->IsDropPortion() && static_cast<SwDropPortion*>(pPos)->GetLines() > 1) + || pPos->GetWhichPor() == PortionType::Bookmark) + { + pLast = pPos; + pPos = pPos->GetNextPortion(); + continue; + } + + bHasOnlyBlankPortions = false; + + // We had an attribute change: Sum up/build maxima of length and mass + + sal_uInt16 nPosHeight = pPos->Height(); + sal_uInt16 nPosAscent = pPos->GetAscent(); + + SAL_WARN_IF( nPosHeight < nPosAscent, + "sw.core", "SwLineLayout::CalcLine: bad ascent or height" ); + + if( pPos->IsHangingPortion() ) + { + SetHanging(true); + rInf.GetParaPortion()->SetMargin(); + } + + // To prevent that a paragraph-end-character does not change + // the line height through a Descent and thus causing the line + // to reformat. + if ( !pPos->IsBreakPortion() || !Height() ) + { + if (!pPos->IsPostItsPortion()) bOnlyPostIts = false; + + if( bTmpDummy && !nLineLength ) + { + if( pPos->IsFlyPortion() ) + { + if( nFlyHeight < nPosHeight ) + nFlyHeight = nPosHeight; + if( nFlyAscent < nPosAscent ) + nFlyAscent = nPosAscent; + if( nFlyDescent < nPosHeight - nPosAscent ) + nFlyDescent = nPosHeight - nPosAscent; + } + else + { + if( pPos->InNumberGrp() ) + { + sal_uInt16 nTmp = rInf.GetFont()->GetAscent( + rInf.GetVsh(), *rInf.GetOut() ); + if( nTmp > nPosAscent ) + { + nPosHeight += nTmp - nPosAscent; + nPosAscent = nTmp; + } + nTmp = rInf.GetFont()->GetHeight( rInf.GetVsh(), + *rInf.GetOut() ); + if( nTmp > nPosHeight ) + nPosHeight = nTmp; + } + Height( nPosHeight ); + nAscent = nPosAscent; + nMaxDescent = nPosHeight - nPosAscent; + } + } + else if( !pPos->IsFlyPortion() ) + { + if( Height() < nPosHeight ) + { + // Height is set to 0 when Init() is called. + if (bIgnoreBlanksAndTabsForLineHeightCalculation && pPos->GetWhichPor() == PortionType::FlyCnt) + // Compat flag set: take the line height, if it's larger. + Height(std::max(nPosHeight, nLineHeight)); + else + // Just care about the portion height. + Height(nPosHeight); + } + SwFlyCntPortion* pAsFly(nullptr); + if(pPos->IsFlyCntPortion()) + pAsFly = static_cast<SwFlyCntPortion*>(pPos); + if( pAsFly || ( pPos->IsMultiPortion() + && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() ) ) + rLine.SetFlyInCntBase(); + if(pAsFly && pAsFly->GetAlign() != sw::LineAlign::NONE) + { + pAsFly->SetMax(false); + if( !pFlyCnt || pPos->Height() > pFlyCnt->Height() ) + pFlyCnt = pAsFly; + } + else + { + if( nAscent < nPosAscent ) + nAscent = nPosAscent; + if( nMaxDescent < nPosHeight - nPosAscent ) + nMaxDescent = nPosHeight - nPosAscent; + } + } + } + else if( pPos->GetLen() ) + bTmpDummy = false; + + if( !HasContent() && !pPos->InNumberGrp() ) + { + if ( pPos->InExpGrp() ) + { + OUString aText; + if( pPos->GetExpText( rInf, aText ) && !aText.isEmpty() ) + SetContent(true); + } + else if( ( pPos->InTextGrp() || pPos->IsMultiPortion() ) && + pPos->GetLen() ) + SetContent(true); + } + + bTmpDummy &= !HasContent() && ( !pPos->Width() || pPos->IsFlyPortion() ); + + pLast = pPos; + pPos = pPos->GetNextPortion(); + } + + if( pFlyCnt ) + { + if( pFlyCnt->Height() == Height() ) + { + pFlyCnt->SetMax( true ); + if( Height() > nMaxDescent + nAscent ) + { + if( sw::LineAlign::BOTTOM == pFlyCnt->GetAlign() ) + nAscent = Height() - nMaxDescent; + else if( sw::LineAlign::CENTER == pFlyCnt->GetAlign() ) + nAscent = ( Height() + nAscent - nMaxDescent ) / 2; + } + pFlyCnt->SetAscent( nAscent ); + } + } + + if( bTmpDummy && nFlyHeight ) + { + nAscent = nFlyAscent; + if( nFlyDescent > nFlyHeight - nFlyAscent ) + Height( nFlyHeight + nFlyDescent ); + else + Height( nFlyHeight ); + } + else if( nMaxDescent > Height() - nAscent ) + Height( nMaxDescent + nAscent ); + + if( bOnlyPostIts && !( bHasBlankPortion && bHasOnlyBlankPortions ) ) + { + Height( rInf.GetFont()->GetHeight( rInf.GetVsh(), *rInf.GetOut() ) ); + nAscent = rInf.GetFont()->GetAscent( rInf.GetVsh(), *rInf.GetOut() ); + } + } + } + else + { + SetContent( !bTmpDummy ); + + // #i3952# + if ( bIgnoreBlanksAndTabsForLineHeightCalculation && + lcl_HasOnlyBlanks( rInf.GetText(), rInf.GetLineStart(), rInf.GetLineStart() + GetLen() ) ) + { + bHasBlankPortion = true; + } + } + + // #i3952# + if ( bHasBlankPortion && bHasOnlyBlankPortions ) + { + sal_uInt16 nTmpAscent = GetAscent(); + sal_uInt16 nTmpHeight = Height(); + rLine.GetAttrHandler().GetDefaultAscentAndHeight( rInf.GetVsh(), *rInf.GetOut(), nTmpAscent, nTmpHeight ); + SetAscent( nTmpAscent ); + Height( nTmpHeight ); + } + + // Robust: + if( nLineWidth < Width() ) + Width( nLineWidth ); + SAL_WARN_IF( nLineWidth < Width(), "sw.core", "SwLineLayout::CalcLine: line is bursting" ); + SetDummy( bTmpDummy ); + std::pair<SwTextNode const*, sal_Int32> const start( + rInf.GetTextFrame()->MapViewToModel(rLine.GetStart())); + std::pair<SwTextNode const*, sal_Int32> const end( + rInf.GetTextFrame()->MapViewToModel(rLine.GetEnd())); + SetRedline( rLine.GetRedln() && + rLine.GetRedln()->CheckLine(start.first->GetIndex(), start.second, + end.first->GetIndex(), end.second) ); +} + +// #i47162# - add optional parameter <_bNoFlyCntPorAndLinePor> +// to control, if the fly content portions and line portion are considered. +void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent, + SwTwips& _orDescent, + SwTwips& _orObjAscent, + SwTwips& _orObjDescent, + const SwLinePortion* _pDontConsiderPortion, + const bool _bNoFlyCntPorAndLinePor ) const +{ + _orAscent = 0; + _orDescent = 0; + _orObjAscent = 0; + _orObjDescent = 0; + + const SwLinePortion* pTmpPortion = this; + if ( !pTmpPortion->GetLen() && pTmpPortion->GetNextPortion() ) + { + pTmpPortion = pTmpPortion->GetNextPortion(); + } + + while ( pTmpPortion ) + { + if ( !pTmpPortion->IsBreakPortion() && !pTmpPortion->IsFlyPortion() && + // tdf#130804 ignore bookmark portions + pTmpPortion->GetWhichPor() != PortionType::Bookmark && + ( !_bNoFlyCntPorAndLinePor || + ( !pTmpPortion->IsFlyCntPortion() && + !(pTmpPortion == this && pTmpPortion->GetNextPortion() ) ) ) ) + { + SwTwips nPortionAsc = static_cast<SwTwips>(pTmpPortion->GetAscent()); + SwTwips nPortionDesc = static_cast<SwTwips>(pTmpPortion->Height()) - + nPortionAsc; + + const bool bFlyCmp = pTmpPortion->IsFlyCntPortion() ? + static_cast<const SwFlyCntPortion*>(pTmpPortion)->IsMax() : + ( pTmpPortion != _pDontConsiderPortion ); + + if ( bFlyCmp ) + { + _orObjAscent = std::max( _orObjAscent, nPortionAsc ); + _orObjDescent = std::max( _orObjDescent, nPortionDesc ); + } + + if ( !pTmpPortion->IsFlyCntPortion() && !pTmpPortion->IsGrfNumPortion() ) + { + _orAscent = std::max( _orAscent, nPortionAsc ); + _orDescent = std::max( _orDescent, nPortionDesc ); + } + } + pTmpPortion = pTmpPortion->GetNextPortion(); + } +} + +void SwLineLayout::ResetFlags() +{ + m_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bFly + = m_bRest = m_bBlinking = m_bClipping = m_bContent = m_bRedline + = m_bForcedLeftMargin = m_bHanging = false; +} + +SwLineLayout::SwLineLayout() + : m_pNext( nullptr ), m_nRealHeight( 0 ), + m_bUnderscore( false ) +{ + ResetFlags(); + SetWhichPor( PortionType::Lay ); +} + +SwLinePortion *SwLineLayout::GetFirstPortion() const +{ + const SwLinePortion *pRet = mpNextPortion ? mpNextPortion : this; + return const_cast<SwLinePortion*>(pRet); +} + +SwCharRange &SwCharRange::operator+=(const SwCharRange &rRange) +{ + if (TextFrameIndex(0) != rRange.nLen) + { + if (TextFrameIndex(0) == nLen) { + nStart = rRange.nStart; + nLen = rRange.nLen ; + } + else { + if(rRange.nStart + rRange.nLen > nStart + nLen) { + nLen = rRange.nStart + rRange.nLen - nStart; + } + if(rRange.nStart < nStart) { + nLen += nStart - rRange.nStart; + nStart = rRange.nStart; + } + } + } + return *this; +} + +SwScriptInfo::SwScriptInfo() + : m_nInvalidityPos(0) + , m_nDefaultDir(0) +{ +}; + +SwScriptInfo::~SwScriptInfo() +{ +} + +// Converts i18n Script Type (LATIN, ASIAN, COMPLEX, WEAK) to +// Sw Script Types (SwFontScript::Latin, SwFontScript::CJK, SwFontScript::CTL), used to identify the font +static SwFontScript lcl_ScriptToFont(sal_uInt16 const nScript) +{ + switch ( nScript ) { + case i18n::ScriptType::LATIN : return SwFontScript::Latin; + case i18n::ScriptType::ASIAN : return SwFontScript::CJK; + case i18n::ScriptType::COMPLEX : return SwFontScript::CTL; + } + + OSL_FAIL( "Somebody tells lies about the script type!" ); + return SwFontScript::Latin; +} + +SwFontScript SwScriptInfo::WhichFont(TextFrameIndex const nIdx) const +{ + const sal_uInt16 nScript(ScriptType(nIdx)); + return lcl_ScriptToFont(nScript); +} + +SwFontScript SwScriptInfo::WhichFont(sal_Int32 nIdx, OUString const& rText) +{ + const sal_uInt16 nScript(g_pBreakIt->GetRealScriptOfText(rText, nIdx)); + return lcl_ScriptToFont(nScript); +} + +static void InitBookmarks( + std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter, + std::vector<sw::Extent>::const_iterator iter, + std::vector<sw::Extent>::const_iterator const end, + TextFrameIndex nOffset, + std::vector<std::pair<sw::mark::IBookmark const*, SwScriptInfo::MarkKind>> & rBookmarks, + std::vector<std::pair<TextFrameIndex, SwScriptInfo::MarkKind>> & o_rBookmarks) +{ + SwTextNode const*const pNode(iter->pNode); + for (auto const& it : rBookmarks) + { + assert(iter->pNode == pNode || pNode->GetIndex() < iter->pNode->GetIndex()); + assert(!oPrevIter || (*oPrevIter)->pNode->GetIndex() <= pNode->GetIndex()); + switch (it.second) + { + case SwScriptInfo::MarkKind::Start: + { + // SwUndoSaveContent::DelContentIndex() is rather messy but + // apparently bookmarks "on the edge" are deleted if + // * point: equals start-of-selection (not end-of-selection) + // * expanded: one position equals edge of selection + // and other does not (is inside) + // interesting case: if end[/start] of the mark is on the + // start of first[/end of last] extent, and the other one + // is outside this merged paragraph, is it deleted or not? + // assume "no" because the line break it contains isn't deleted. + SwPosition const& rStart(it.first->GetMarkStart()); + SwPosition const& rEnd(it.first->GetMarkEnd()); + assert(&rStart.nNode.GetNode() == pNode); + while (iter != end) + { + if (&rStart.nNode.GetNode() != iter->pNode // iter moved to next node + || rStart.nContent.GetIndex() < iter->nStart) + { + if (rEnd.nNode.GetIndex() < iter->pNode->GetIndex() + || (&rEnd.nNode.GetNode() == iter->pNode && rEnd.nContent.GetIndex() <= iter->nStart)) + { + break; // deleted - skip it + } + else + { + o_rBookmarks.emplace_back(nOffset, it.second); + break; + } + } + else if (rStart.nContent.GetIndex() <= iter->nEnd) + { + auto const iterNext(iter + 1); + if (rStart.nContent.GetIndex() == iter->nEnd + && (iterNext == end + ? &rEnd.nNode.GetNode() == iter->pNode + : (rEnd.nNode.GetIndex() < iterNext->pNode->GetIndex() + || (&rEnd.nNode.GetNode() == iterNext->pNode && rEnd.nContent.GetIndex() < iterNext->nStart)))) + { + break; // deleted - skip it + } + else + { + o_rBookmarks.emplace_back( + nOffset + TextFrameIndex(rStart.nContent.GetIndex() - iter->nStart), + it.second); + break; + } + } + else + { + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + oPrevIter = iter; + ++iter; // bookmarks are sorted... + } + } + if (iter == end) + { + if (pNode->GetIndex() < rEnd.nNode.GetIndex()) // pNode is last node of merged + { + break; // deleted - skip it + } + else + { + o_rBookmarks.emplace_back(nOffset, it.second); + } + } + break; + } + case SwScriptInfo::MarkKind::End: + { + SwPosition const& rEnd(it.first->GetMarkEnd()); + assert(&rEnd.nNode.GetNode() == pNode); + while (true) + { + if (iter == end + || &rEnd.nNode.GetNode() != iter->pNode // iter moved to next node + || rEnd.nContent.GetIndex() <= iter->nStart) + { + SwPosition const& rStart(it.first->GetMarkStart()); + // oPrevIter may point to pNode or a preceding node + if (oPrevIter + ? ((*oPrevIter)->pNode->GetIndex() < rStart.nNode.GetIndex() + || ((*oPrevIter)->pNode == &rStart.nNode.GetNode() + && ((iter != end && &rEnd.nNode.GetNode() == iter->pNode && rEnd.nContent.GetIndex() == iter->nStart) + ? (*oPrevIter)->nEnd < rStart.nContent.GetIndex() + : (*oPrevIter)->nEnd <= rStart.nContent.GetIndex()))) + : rStart.nNode == rEnd.nNode) + { + break; // deleted - skip it + } + else + { + o_rBookmarks.emplace_back(nOffset, it.second); + break; + } + } + else if (rEnd.nContent.GetIndex() <= iter->nEnd) + { + o_rBookmarks.emplace_back( + nOffset + TextFrameIndex(rEnd.nContent.GetIndex() - iter->nStart), + it.second); + break; + } + else + { + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + oPrevIter = iter; + ++iter; + } + } + break; + } + case SwScriptInfo::MarkKind::Point: + { + SwPosition const& rPos(it.first->GetMarkPos()); + assert(&rPos.nNode.GetNode() == pNode); + while (iter != end) + { + if (&rPos.nNode.GetNode() != iter->pNode // iter moved to next node + || rPos.nContent.GetIndex() < iter->nStart) + { + break; // deleted - skip it + } + else if (rPos.nContent.GetIndex() <= iter->nEnd) + { + if (rPos.nContent.GetIndex() == iter->nEnd + && rPos.nContent.GetIndex() != iter->pNode->Len()) + { + break; // deleted - skip it + } + else + { + o_rBookmarks.emplace_back( + nOffset + TextFrameIndex(rPos.nContent.GetIndex() - iter->nStart), + it.second); + } + break; + } + else + { + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + oPrevIter = iter; + ++iter; + } + } + break; + } + } + } +} + +// searches for script changes in rText and stores them +void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, + sw::MergedPara const*const pMerged) +{ + InitScriptInfo( rNode, pMerged, m_nDefaultDir == UBIDI_RTL ); +} + +void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, + sw::MergedPara const*const pMerged, bool bRTL) +{ + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + const OUString& rText(pMerged ? pMerged->mergedText : rNode.GetText()); + + // HIDDEN TEXT INFORMATION + + m_Bookmarks.clear(); + m_HiddenChg.clear(); + if (pMerged) + { + SwTextNode const* pNode(nullptr); + TextFrameIndex nOffset(0); + std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter; + for (auto iter = pMerged->extents.begin(); iter != pMerged->extents.end(); + oPrevIter = iter, ++iter) + { + if (iter->pNode == pNode) + { + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + continue; // skip extents at end of previous node + } + pNode = iter->pNode; + Range aRange( 0, pNode->Len() > 0 ? pNode->Len() - 1 : 0 ); + MultiSelection aHiddenMulti( aRange ); + std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks; + CalcHiddenRanges(*pNode, aHiddenMulti, &bookmarks); + + InitBookmarks(oPrevIter, iter, pMerged->extents.end(), nOffset, bookmarks, m_Bookmarks); + + for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i) + { + const Range& rRange = aHiddenMulti.GetRange( i ); + const sal_Int32 nStart = rRange.Min(); + const sal_Int32 nEnd = rRange.Max() + 1; + + while (true) + { + // because of the selectRedLineDeleted call, never overlaps + // extents, must be contained inside one extent + assert(!(iter->nStart <= nStart && nStart < iter->nEnd && iter->nEnd < nEnd)); + assert(!(nStart < iter->nStart && iter->nStart < nEnd && nEnd <= iter->nEnd)); + if (iter->nStart <= nStart && nEnd <= iter->nEnd) + { + if (iter->nStart == nStart && !m_HiddenChg.empty() + && m_HiddenChg.back() == nOffset) + { + // previous one went until end of extent, extend it + m_HiddenChg.back() += TextFrameIndex(nEnd - iter->nStart); + } + else // new one + { + m_HiddenChg.push_back(nOffset + TextFrameIndex(nStart - iter->nStart)); + m_HiddenChg.push_back(nOffset + TextFrameIndex(nEnd - iter->nStart)); + } + break; + } + else + { + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + ++iter; + // because selectRedLineDeleted, must find it in pNode + assert(iter != pMerged->extents.end()); + assert(iter->pNode == pNode); + } + } + } + nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + } + } + else + { + Range aRange( 0, !rText.isEmpty() ? rText.getLength() - 1 : 0 ); + MultiSelection aHiddenMulti( aRange ); + std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks; + CalcHiddenRanges(rNode, aHiddenMulti, &bookmarks); + + for (auto const& it : bookmarks) + { + switch (it.second) + { + case MarkKind::Start: + m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkStart().nContent.GetIndex()), it.second); + break; + case MarkKind::End: + m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkEnd().nContent.GetIndex()), it.second); + break; + case MarkKind::Point: + m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkPos().nContent.GetIndex()), it.second); + break; + } + } + + for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i) + { + const Range& rRange = aHiddenMulti.GetRange( i ); + const sal_Int32 nStart = rRange.Min(); + const sal_Int32 nEnd = rRange.Max() + 1; + + m_HiddenChg.push_back( TextFrameIndex(nStart) ); + m_HiddenChg.push_back( TextFrameIndex(nEnd) ); + } + } + + // SCRIPT AND SCRIPT RELATED INFORMATION + + TextFrameIndex nChg = m_nInvalidityPos; + + // COMPLETE_STRING means the data structure is up to date + m_nInvalidityPos = TextFrameIndex(COMPLETE_STRING); + + // this is the default direction + m_nDefaultDir = static_cast<sal_uInt8>(bRTL ? UBIDI_RTL : UBIDI_LTR); + + // counter for script info arrays + size_t nCnt = 0; + // counter for compression information arrays + size_t nCntComp = 0; + // counter for kashida array + size_t nCntKash = 0; + + sal_Int16 nScript = i18n::ScriptType::LATIN; + + // compression type + const CharCompressType aCompEnum = rNode.getIDocumentSettingAccess()->getCharacterCompressionType(); + + auto const& rParaItems((pMerged ? *pMerged->pParaPropsNode : rNode).GetSwAttrSet()); + // justification type + const bool bAdjustBlock = SvxAdjust::Block == rParaItems.GetAdjust().GetAdjust(); + + // FIND INVALID RANGES IN SCRIPT INFO ARRAYS: + + if( nChg ) + { + // if change position = 0 we do not use any data from the arrays + // because by deleting all characters of the first group at the beginning + // of a paragraph nScript is set to a wrong value + SAL_WARN_IF( !CountScriptChg(), "sw.core", "Where're my changes of script?" ); + while( nCnt < CountScriptChg() ) + { + if ( nChg > GetScriptChg( nCnt ) ) + nCnt++; + else + { + nScript = GetScriptType( nCnt ); + break; + } + } + if( CharCompressType::NONE != aCompEnum ) + { + while( nCntComp < CountCompChg() ) + { + if ( nChg <= GetCompStart( nCntComp ) ) + break; + nCntComp++; + } + } + if ( bAdjustBlock ) + { + while( nCntKash < CountKashida() ) + { + if ( nChg <= GetKashida( nCntKash ) ) + break; + nCntKash++; + } + } + } + + // ADJUST nChg VALUE: + + // by stepping back one position we know that we are inside a group + // declared as an nScript group + if ( nChg ) + --nChg; + + const TextFrameIndex nGrpStart = nCnt ? GetScriptChg(nCnt - 1) : TextFrameIndex(0); + + // we go back in our group until we reach the first character of + // type nScript + while ( nChg > nGrpStart && + nScript != g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg))) + --nChg; + + // If we are at the start of a group, we do not trust nScript, + // we better get nScript from the breakiterator: + if ( nChg == nGrpStart ) + nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg))); + + // INVALID DATA FROM THE SCRIPT INFO ARRAYS HAS TO BE DELETED: + + // remove invalid entries from script information arrays + m_ScriptChanges.erase(m_ScriptChanges.begin() + nCnt, m_ScriptChanges.end()); + + // get the start of the last compression group + TextFrameIndex nLastCompression = nChg; + if( nCntComp ) + { + --nCntComp; + nLastCompression = GetCompStart( nCntComp ); + if( nChg >= nLastCompression + GetCompLen( nCntComp ) ) + { + nLastCompression = nChg; + ++nCntComp; + } + } + + // remove invalid entries from compression information arrays + m_CompressionChanges.erase(m_CompressionChanges.begin() + nCntComp, + m_CompressionChanges.end()); + + // get the start of the last kashida group + TextFrameIndex nLastKashida = nChg; + if( nCntKash && i18n::ScriptType::COMPLEX == nScript ) + { + --nCntKash; + nLastKashida = GetKashida( nCntKash ); + } + + // remove invalid entries from kashida array + m_Kashida.erase(m_Kashida.begin() + nCntKash, m_Kashida.end()); + + // TAKE CARE OF WEAK CHARACTERS: WE MUST FIND AN APPROPRIATE + // SCRIPT FOR WEAK CHARACTERS AT THE BEGINNING OF A PARAGRAPH + + if (WEAK == g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg))) + { + // If the beginning of the current group is weak, this means that + // all of the characters in this group are weak. We have to assign + // the scripts to these characters depending on the fonts which are + // set for these characters to display them. + TextFrameIndex nEnd( + g_pBreakIt->GetBreakIter()->endOfScript(rText, sal_Int32(nChg), WEAK)); + + if (nEnd > TextFrameIndex(rText.getLength()) || nEnd < TextFrameIndex(0)) + nEnd = TextFrameIndex(rText.getLength()); + + nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + + SAL_WARN_IF( i18n::ScriptType::LATIN != nScript && + i18n::ScriptType::ASIAN != nScript && + i18n::ScriptType::COMPLEX != nScript, "sw.core", "Wrong default language" ); + + nChg = nEnd; + + // Get next script type or set to weak in order to exit + sal_uInt8 nNextScript = (nEnd < TextFrameIndex(rText.getLength())) + ? static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nEnd))) + : sal_uInt8(WEAK); + + if ( nScript != nNextScript ) + { + m_ScriptChanges.emplace_back(nEnd, nScript); + nCnt++; + nScript = nNextScript; + } + } + + // UPDATE THE SCRIPT INFO ARRAYS: + + while (nChg < TextFrameIndex(rText.getLength()) + || (m_ScriptChanges.empty() && rText.isEmpty())) + { + SAL_WARN_IF( i18n::ScriptType::WEAK == nScript, + "sw.core", "Inserting WEAK into SwScriptInfo structure" ); + + TextFrameIndex nSearchStt = nChg; + nChg = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfScript( + rText, sal_Int32(nSearchStt), nScript)); + + if (nChg > TextFrameIndex(rText.getLength()) || nChg < TextFrameIndex(0)) + nChg = TextFrameIndex(rText.getLength()); + + // #i28203# + // for 'complex' portions, we make sure that a portion does not contain more + // than one script: + if( i18n::ScriptType::COMPLEX == nScript ) + { + const short nScriptType = ScriptTypeDetector::getCTLScriptType( + rText, sal_Int32(nSearchStt) ); + TextFrameIndex nNextCTLScriptStart = nSearchStt; + short nCurrentScriptType = nScriptType; + while( css::i18n::CTLScriptType::CTL_UNKNOWN == nCurrentScriptType || nScriptType == nCurrentScriptType ) + { + nNextCTLScriptStart = TextFrameIndex( + ScriptTypeDetector::endOfCTLScriptType( + rText, sal_Int32(nNextCTLScriptStart))); + if (nNextCTLScriptStart >= TextFrameIndex(rText.getLength()) + || nNextCTLScriptStart >= nChg) + break; + nCurrentScriptType = ScriptTypeDetector::getCTLScriptType( + rText, sal_Int32(nNextCTLScriptStart)); + } + nChg = std::min( nChg, nNextCTLScriptStart ); + } + + // special case for dotted circle since it can be used with complex + // before a mark, so we want it associated with the mark's script + if (nChg < TextFrameIndex(rText.getLength()) && nChg > TextFrameIndex(0) + && (i18n::ScriptType::WEAK == + g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg) - 1))) + { + int8_t nType = u_charType(rText[sal_Int32(nChg)]); + if (nType == U_NON_SPACING_MARK || nType == U_ENCLOSING_MARK || + nType == U_COMBINING_SPACING_MARK ) + { + m_ScriptChanges.emplace_back(nChg-TextFrameIndex(1), nScript); + } + else + { + m_ScriptChanges.emplace_back(nChg, nScript); + } + } + else + { + m_ScriptChanges.emplace_back(nChg, nScript); + } + ++nCnt; + + // if current script is asian, we search for compressible characters + // in this range + if ( CharCompressType::NONE != aCompEnum && + i18n::ScriptType::ASIAN == nScript ) + { + CompType ePrevState = NONE; + CompType eState = NONE; + TextFrameIndex nPrevChg = nLastCompression; + + while ( nLastCompression < nChg ) + { + sal_Unicode cChar = rText[ sal_Int32(nLastCompression) ]; + + // examine current character + switch ( cChar ) + { + // Left punctuation found + case 0x3008: case 0x300A: case 0x300C: case 0x300E: + case 0x3010: case 0x3014: case 0x3016: case 0x3018: + case 0x301A: case 0x301D: + eState = SPECIAL_LEFT; + break; + // Right punctuation found + case 0x3009: case 0x300B: + case 0x300D: case 0x300F: case 0x3011: case 0x3015: + case 0x3017: case 0x3019: case 0x301B: case 0x301E: + case 0x301F: + eState = SPECIAL_RIGHT; + break; + case 0x3001: case 0x3002: // Fullstop or comma + eState = SPECIAL_MIDDLE ; + break; + default: + eState = ( 0x3040 <= cChar && 0x3100 > cChar ) ? KANA : NONE; + } + + // insert range of compressible characters + if( ePrevState != eState ) + { + if ( ePrevState != NONE ) + { + // insert start and type + if ( CharCompressType::PunctuationAndKana == aCompEnum || + ePrevState != KANA ) + { + m_CompressionChanges.emplace_back(nPrevChg, + nLastCompression - nPrevChg, ePrevState); + } + } + + ePrevState = eState; + nPrevChg = nLastCompression; + } + + nLastCompression++; + } + + // we still have to examine last entry + if ( ePrevState != NONE ) + { + // insert start and type + if ( CharCompressType::PunctuationAndKana == aCompEnum || + ePrevState != KANA ) + { + m_CompressionChanges.emplace_back(nPrevChg, + nLastCompression - nPrevChg, ePrevState); + } + } + } + + // we search for connecting opportunities (kashida) + else if ( bAdjustBlock && i18n::ScriptType::COMPLEX == nScript ) + { + // sw_redlinehide: this is the only place that uses SwScanner with + // frame text, so we convert to sal_Int32 here + std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfCharM( + [&pMerged](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar) + { + std::pair<SwTextNode const*, sal_Int32> const pos( + sw::MapViewToModel(*pMerged, TextFrameIndex(nBegin))); + return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, script); + }); + std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfChar1( + [&rNode](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar) + { return rNode.GetLang(nBegin, bNoChar ? 0 : 1, script); }); + auto pGetLangOfChar(pMerged ? pGetLangOfCharM : pGetLangOfChar1); + SwScanner aScanner( pGetLangOfChar, rText, nullptr, ModelToViewHelper(), + i18n::WordType::DICTIONARY_WORD, + sal_Int32(nLastKashida), sal_Int32(nChg)); + + // the search has to be performed on a per word base + while ( aScanner.NextWord() ) + { + const OUString& rWord = aScanner.GetWord(); + + sal_Int32 nIdx = 0; + sal_Int32 nKashidaPos = -1; + sal_Unicode cCh; + sal_Unicode cPrevCh = 0; + + int nPriorityLevel = 7; // 0..6 = level found + // 7 not found + + sal_Int32 nWordLen = rWord.getLength(); + + // ignore trailing vowel chars + while( nWordLen && isTransparentChar( rWord[ nWordLen - 1 ] )) + --nWordLen; + + while (nIdx < nWordLen) + { + cCh = rWord[ nIdx ]; + + // 1. Priority: + // after user inserted kashida + if ( 0x640 == cCh ) + { + nKashidaPos = aScanner.GetBegin() + nIdx; + nPriorityLevel = 0; + } + + // 2. Priority: + // after a Seen or Sad + if (nPriorityLevel >= 1 && nIdx < nWordLen - 1) + { + if( isSeenOrSadChar( cCh ) + && (rWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion + { + nKashidaPos = aScanner.GetBegin() + nIdx; + nPriorityLevel = 1; + } + } + + // 3. Priority: + // before final form of Teh Marbuta, Heh, Dal + if ( nPriorityLevel >= 2 && nIdx > 0 ) + { + if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining) + isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word + ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word + { + + SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nPriorityLevel = 2; + } + } + } + + // 4. Priority: + // before final form of Alef, Tah, Lam, Kaf or Gaf + if ( nPriorityLevel >= 3 && nIdx > 0 ) + { + if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word + (( isLamChar ( cCh ) || // Lam, + isTahChar ( cCh ) || // Tah, + isKafChar ( cCh ) || // Kaf (all dual joining) + isGafChar ( cCh ) ) + && nIdx == nWordLen - 1)) // only at end of word + { + SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nPriorityLevel = 3; + } + } + } + + // 5. Priority: + // before medial Beh-like + if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 ) + { + if ( isBehChar ( cCh ) ) + { + // check if next character is Reh or Yeh-like + sal_Unicode cNextCh = rWord[ nIdx + 1 ]; + if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh )) + { + SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nPriorityLevel = 4; + } + } + } + } + + // 6. Priority: + // before the final form of Waw, Ain, Qaf and Feh + if ( nPriorityLevel >= 5 && nIdx > 0 ) + { + if ( isWawChar ( cCh ) || // Wav (right joining) + // final form may appear in the middle of word + (( isAinChar ( cCh ) || // Ain (dual joining) + isQafChar ( cCh ) || // Qaf (dual joining) + isFehChar ( cCh ) ) // Feh (dual joining) + && nIdx == nWordLen - 1)) // only at end of word + { + SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nPriorityLevel = 5; + } + } + } + + // other connecting possibilities + if ( nPriorityLevel >= 6 && nIdx > 0 ) + { + // Reh, Zain + if ( isRehChar ( cCh ) ) + { + SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nPriorityLevel = 6; + } + } + } + + // Do not consider vowel marks when checking if a character + // can be connected to previous character. + if ( !isTransparentChar ( cCh) ) + cPrevCh = cCh; + + ++nIdx; + } // end of current word + + if ( -1 != nKashidaPos ) + { + m_Kashida.insert(m_Kashida.begin() + nCntKash, TextFrameIndex(nKashidaPos)); + nCntKash++; + } + } // end of kashida search + } + + if (nChg < TextFrameIndex(rText.getLength())) + nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg))); + + nLastCompression = nChg; + nLastKashida = nChg; + } + +#if OSL_DEBUG_LEVEL > 0 + // check kashida data + TextFrameIndex nTmpKashidaPos(-1); + bool bWrongKash = false; + for (size_t i = 0; i < m_Kashida.size(); ++i) + { + TextFrameIndex nCurrKashidaPos = GetKashida( i ); + if ( nCurrKashidaPos <= nTmpKashidaPos ) + { + bWrongKash = true; + break; + } + nTmpKashidaPos = nCurrKashidaPos; + } + SAL_WARN_IF( bWrongKash, "sw.core", "Kashida array contains wrong data" ); +#endif + + // remove invalid entries from direction information arrays + m_DirectionChanges.clear(); + + // Perform Unicode Bidi Algorithm for text direction information + { + UpdateBidiInfo( rText ); + + // #i16354# Change script type for RTL text to CTL: + // 1. All text in RTL runs will use the CTL font + // #i89825# change the script type also to CTL (hennerdrewes) + // 2. Text in embedded LTR runs that does not have any strong LTR characters (numbers!) + for (size_t nDirIdx = 0; nDirIdx < m_DirectionChanges.size(); ++nDirIdx) + { + const sal_uInt8 nCurrDirType = GetDirType( nDirIdx ); + // nStart is start of RTL run: + const TextFrameIndex nStart = nDirIdx > 0 ? GetDirChg(nDirIdx - 1) : TextFrameIndex(0); + // nEnd is end of RTL run: + const TextFrameIndex nEnd = GetDirChg( nDirIdx ); + + if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run + (nCurrDirType > UBIDI_LTR && // non-strong text in embedded LTR run + !lcl_HasStrongLTR(rText, sal_Int32(nStart), sal_Int32(nEnd)))) + { + // nScriptIdx points into the ScriptArrays: + size_t nScriptIdx = 0; + + // Skip entries in ScriptArray which are not inside the RTL run: + // Make nScriptIdx become the index of the script group with + // 1. nStartPosOfGroup <= nStart and + // 2. nEndPosOfGroup > nStart + while ( GetScriptChg( nScriptIdx ) <= nStart ) + ++nScriptIdx; + + const TextFrameIndex nStartPosOfGroup = nScriptIdx + ? GetScriptChg(nScriptIdx - 1) + : TextFrameIndex(0); + const sal_uInt8 nScriptTypeOfGroup = GetScriptType( nScriptIdx ); + + SAL_WARN_IF( nStartPosOfGroup > nStart || GetScriptChg( nScriptIdx ) <= nStart, + "sw.core", "Script override with CTL font trouble" ); + + // Check if we have to insert a new script change at + // position nStart. If nStartPosOfGroup < nStart, + // we have to insert a new script change: + if (nStart > TextFrameIndex(0) && nStartPosOfGroup < nStart) + { + m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx, + ScriptChangeInfo(nStart, nScriptTypeOfGroup) ); + ++nScriptIdx; + } + + // Remove entries in ScriptArray which end inside the RTL run: + while (nScriptIdx < m_ScriptChanges.size() + && GetScriptChg(nScriptIdx) <= nEnd) + { + m_ScriptChanges.erase(m_ScriptChanges.begin() + nScriptIdx); + } + + // Insert a new entry in ScriptArray for the end of the RTL run: + m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx, + ScriptChangeInfo(nEnd, i18n::ScriptType::COMPLEX) ); + +#if OSL_DEBUG_LEVEL > 1 + // Check that ScriptChangeInfos are in increasing order of + // position and that we don't have "empty" changes. + sal_uInt8 nLastTyp = i18n::ScriptType::WEAK; + TextFrameIndex nLastPos = TextFrameIndex(0); + for (const auto& rScriptChange : m_ScriptChanges) + { + SAL_WARN_IF( nLastTyp == rScriptChange.type || + nLastPos >= rScriptChange.position, + "sw.core", "Heavy InitScriptType() confusion" ); + nLastPos = rScriptChange.position; + nLastTyp = rScriptChange.type; + } +#endif + } + } + } +} + +void SwScriptInfo::UpdateBidiInfo( const OUString& rText ) +{ + // remove invalid entries from direction information arrays + m_DirectionChanges.clear(); + + // Bidi functions from icu 2.0 + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( rText.getLength(), 0, &nError ); + nError = U_ZERO_ERROR; + + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(rText.getStr()), rText.getLength(), + m_nDefaultDir, nullptr, &nError ); + nError = U_ZERO_ERROR; + int nCount = ubidi_countRuns( pBidi, &nError ); + int32_t nStart = 0; + int32_t nEnd; + UBiDiLevel nCurrDir; + for ( int nIdx = 0; nIdx < nCount; ++nIdx ) + { + ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir ); + m_DirectionChanges.emplace_back(TextFrameIndex(nEnd), nCurrDir); + nStart = nEnd; + } + + ubidi_close( pBidi ); +} + +// returns the position of the next character which belongs to another script +// than the character of the actual (input) position. +// If there's no script change until the end of the paragraph, it will return +// COMPLETE_STRING. +// Scripts are Asian (Chinese, Japanese, Korean), +// Latin ( English etc.) +// and Complex ( Hebrew, Arabian ) +TextFrameIndex SwScriptInfo::NextScriptChg(const TextFrameIndex nPos) const +{ + const size_t nEnd = CountScriptChg(); + for( size_t nX = 0; nX < nEnd; ++nX ) + { + if( nPos < GetScriptChg( nX ) ) + return GetScriptChg( nX ); + } + + return TextFrameIndex(COMPLETE_STRING); +} + +// returns the script of the character at the input position +sal_Int16 SwScriptInfo::ScriptType(const TextFrameIndex nPos) const +{ + const size_t nEnd = CountScriptChg(); + for( size_t nX = 0; nX < nEnd; ++nX ) + { + if( nPos < GetScriptChg( nX ) ) + return GetScriptType( nX ); + } + + // the default is the application language script + return SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); +} + +TextFrameIndex SwScriptInfo::NextDirChg(const TextFrameIndex nPos, + const sal_uInt8* pLevel ) const +{ + const sal_uInt8 nCurrDir = pLevel ? *pLevel : 62; + const size_t nEnd = CountDirChg(); + for( size_t nX = 0; nX < nEnd; ++nX ) + { + if( nPos < GetDirChg( nX ) && + ( nX + 1 == nEnd || GetDirType( nX + 1 ) <= nCurrDir ) ) + return GetDirChg( nX ); + } + + return TextFrameIndex(COMPLETE_STRING); +} + +sal_uInt8 SwScriptInfo::DirType(const TextFrameIndex nPos) const +{ + const size_t nEnd = CountDirChg(); + for( size_t nX = 0; nX < nEnd; ++nX ) + { + if( nPos < GetDirChg( nX ) ) + return GetDirType( nX ); + } + + return 0; +} + +TextFrameIndex SwScriptInfo::NextHiddenChg(TextFrameIndex const nPos) const +{ + for (auto const& it : m_HiddenChg) + { + if (nPos < it) + { + return it; + } + } + return TextFrameIndex(COMPLETE_STRING); +} + +TextFrameIndex SwScriptInfo::NextBookmark(TextFrameIndex const nPos) const +{ + for (auto const& it : m_Bookmarks) + { + if (nPos < it.first) + { + return it.first; + } + } + return TextFrameIndex(COMPLETE_STRING); +} + +auto SwScriptInfo::GetBookmark(TextFrameIndex const nPos) const -> MarkKind +{ + MarkKind ret{0}; + for (auto const& it : m_Bookmarks) + { + if (nPos == it.first) + { + ret |= it.second; + } + else if (nPos < it.first) + { + break; + } + } + return ret; +} + +// Takes a string and replaced the hidden ranges with cChar. +sal_Int32 SwScriptInfo::MaskHiddenRanges( const SwTextNode& rNode, OUStringBuffer & rText, + const sal_Int32 nStt, const sal_Int32 nEnd, + const sal_Unicode cChar ) +{ + assert(rNode.GetText().getLength() == rText.getLength()); + + std::vector<sal_Int32> aList; + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + sal_Int32 nNumOfHiddenChars = 0; + GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList ); + auto rFirst( aList.crbegin() ); + auto rLast( aList.crend() ); + while ( rFirst != rLast ) + { + nHiddenEnd = *(rFirst++); + nHiddenStart = *(rFirst++); + + if ( nHiddenEnd < nStt || nHiddenStart > nEnd ) + continue; + + while ( nHiddenStart < nHiddenEnd && nHiddenStart < nEnd ) + { + if (nHiddenStart >= nStt) + { + rText[nHiddenStart] = cChar; + ++nNumOfHiddenChars; + } + ++nHiddenStart; + } + } + + return nNumOfHiddenChars; +} + +// Takes a SwTextNode and deletes the hidden ranges from the node. +void SwScriptInfo::DeleteHiddenRanges( SwTextNode& rNode ) +{ + std::vector<sal_Int32> aList; + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList ); + auto rFirst( aList.crbegin() ); + auto rLast( aList.crend() ); + while ( rFirst != rLast ) + { + nHiddenEnd = *(rFirst++); + nHiddenStart = *(rFirst++); + + SwPaM aPam( rNode, nHiddenStart, rNode, nHiddenEnd ); + rNode.getIDocumentContentOperations().DeleteRange( aPam ); + } +} + +bool SwScriptInfo::GetBoundsOfHiddenRange( const SwTextNode& rNode, sal_Int32 nPos, + sal_Int32& rnStartPos, sal_Int32& rnEndPos, + std::vector<sal_Int32>* pList ) +{ + rnStartPos = COMPLETE_STRING; + rnEndPos = 0; + + bool bNewContainsHiddenChars = false; + + // Optimization: First examine the flags at the text node: + + if ( !rNode.IsCalcHiddenCharFlags() ) + { + bool bWholePara = rNode.HasHiddenCharAttribute( true ); + bool bContainsHiddenChars = rNode.HasHiddenCharAttribute( false ); + if ( !bContainsHiddenChars ) + return false; + + if ( bWholePara ) + { + if ( pList ) + { + pList->push_back( 0 ); + pList->push_back(rNode.GetText().getLength()); + } + + rnStartPos = 0; + rnEndPos = rNode.GetText().getLength(); + return true; + } + } + + // sw_redlinehide: this won't work if it's merged +#if 0 + const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo( rNode ); + if ( pSI ) + { + + // Check first, if we have a valid SwScriptInfo object for this text node: + + bNewContainsHiddenChars = pSI->GetBoundsOfHiddenRange( nPos, rnStartPos, rnEndPos, pList ); + const bool bNewHiddenCharsHidePara = + rnStartPos == 0 && rnEndPos >= rNode.GetText().getLength(); + rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars ); + } + else +#endif + { + + // No valid SwScriptInfo Object, we have to do it the hard way: + + Range aRange(0, (!rNode.GetText().isEmpty()) + ? rNode.GetText().getLength() - 1 + : 0); + MultiSelection aHiddenMulti( aRange ); + SwScriptInfo::CalcHiddenRanges(rNode, aHiddenMulti, nullptr); + for( sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i ) + { + const Range& rRange = aHiddenMulti.GetRange( i ); + const sal_Int32 nHiddenStart = rRange.Min(); + const sal_Int32 nHiddenEnd = rRange.Max() + 1; + + if ( nHiddenStart > nPos ) + break; + if (nPos < nHiddenEnd) + { + rnStartPos = nHiddenStart; + rnEndPos = std::min<sal_Int32>(nHiddenEnd, + rNode.GetText().getLength()); + break; + } + } + + if ( pList ) + { + for( sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i ) + { + const Range& rRange = aHiddenMulti.GetRange( i ); + pList->push_back( rRange.Min() ); + pList->push_back( rRange.Max() + 1 ); + } + } + + bNewContainsHiddenChars = aHiddenMulti.GetRangeCount() > 0; + } + + return bNewContainsHiddenChars; +} + +bool SwScriptInfo::GetBoundsOfHiddenRange(TextFrameIndex nPos, + TextFrameIndex & rnStartPos, TextFrameIndex & rnEndPos) const +{ + rnStartPos = TextFrameIndex(COMPLETE_STRING); + rnEndPos = TextFrameIndex(0); + + const size_t nEnd = CountHiddenChg(); + for( size_t nX = 0; nX < nEnd; ++nX ) + { + const TextFrameIndex nHiddenStart = GetHiddenChg( nX++ ); + const TextFrameIndex nHiddenEnd = GetHiddenChg( nX ); + + if ( nHiddenStart > nPos ) + break; + if (nPos < nHiddenEnd) + { + rnStartPos = nHiddenStart; + rnEndPos = nHiddenEnd; + break; + } + } + + return CountHiddenChg() > 0; +} + +bool SwScriptInfo::IsInHiddenRange( const SwTextNode& rNode, sal_Int32 nPos ) +{ + sal_Int32 nStartPos; + sal_Int32 nEndPos; + SwScriptInfo::GetBoundsOfHiddenRange( rNode, nPos, nStartPos, nEndPos ); + return nStartPos != COMPLETE_STRING; +} + +#ifdef DBG_UTIL +// returns the type of the compressed character +SwScriptInfo::CompType SwScriptInfo::DbgCompType(const TextFrameIndex nPos) const +{ + const size_t nEnd = CountCompChg(); + for( size_t nX = 0; nX < nEnd; ++nX ) + { + const TextFrameIndex nChg = GetCompStart(nX); + + if ( nPos < nChg ) + return NONE; + + if( nPos < nChg + GetCompLen( nX ) ) + return GetCompType( nX ); + } + return NONE; +} +#endif + +// returns, if there are compressible kanas or specials +// between nStart and nEnd +size_t SwScriptInfo::HasKana(TextFrameIndex const nStart, TextFrameIndex const nLen) const +{ + const size_t nCnt = CountCompChg(); + TextFrameIndex nEnd = nStart + nLen; + + for( size_t nX = 0; nX < nCnt; ++nX ) + { + TextFrameIndex nKanaStart = GetCompStart(nX); + TextFrameIndex nKanaEnd = nKanaStart + GetCompLen(nX); + + if ( nKanaStart >= nEnd ) + return SAL_MAX_SIZE; + + if ( nStart < nKanaEnd ) + return nX; + } + + return SAL_MAX_SIZE; +} + +long SwScriptInfo::Compress(long* pKernArray, TextFrameIndex nIdx, TextFrameIndex nLen, + const sal_uInt16 nCompress, const sal_uInt16 nFontHeight, + bool bCenter, + Point* pPoint ) const +{ + SAL_WARN_IF( !nCompress, "sw.core", "Compression without compression?!" ); + SAL_WARN_IF( !nLen, "sw.core", "Compression without text?!" ); + const size_t nCompCount = CountCompChg(); + + // In asian typography, there are full width and half width characters. + // Full width punctuation characters can be compressed by 50% + // to determine this, we compare the font width with 75% of its height + const long nMinWidth = ( 3 * nFontHeight ) / 4; + + size_t nCompIdx = HasKana( nIdx, nLen ); + + if ( SAL_MAX_SIZE == nCompIdx ) + return 0; + + TextFrameIndex nChg = GetCompStart( nCompIdx ); + TextFrameIndex nCompLen = GetCompLen( nCompIdx ); + sal_Int32 nI = 0; + nLen += nIdx; + + if( nChg > nIdx ) + { + nI = sal_Int32(nChg - nIdx); + nIdx = nChg; + } + else if( nIdx < nChg + nCompLen ) + nCompLen -= nIdx - nChg; + + if( nIdx > nLen || nCompIdx >= nCompCount ) + return 0; + + long nSub = 0; + long nLast = nI ? pKernArray[ nI - 1 ] : 0; + do + { + const CompType nType = GetCompType( nCompIdx ); +#ifdef DBG_UTIL + SAL_WARN_IF( nType != DbgCompType( nIdx ), "sw.core", "Gimme the right type!" ); +#endif + nCompLen += nIdx; + if( nCompLen > nLen ) + nCompLen = nLen; + + // are we allowed to compress the character? + if ( pKernArray[ nI ] - nLast < nMinWidth ) + { + nIdx++; nI++; + } + else + { + while( nIdx < nCompLen ) + { + SAL_WARN_IF( SwScriptInfo::NONE == nType, "sw.core", "None compression?!" ); + + // nLast is width of current character + nLast -= pKernArray[ nI ]; + + nLast *= nCompress; + long nMove = 0; + if( SwScriptInfo::KANA != nType ) + { + nLast /= 24000; + if( pPoint && SwScriptInfo::SPECIAL_LEFT == nType ) + { + if( nI ) + nMove = nLast; + else + { + pPoint->AdjustX(nLast ); + nLast = 0; + } + } + else if( bCenter && SwScriptInfo::SPECIAL_MIDDLE == nType ) + nMove = nLast / 2; + } + else + nLast /= 100000; + nSub -= nLast; + nLast = pKernArray[ nI ]; + if( nI && nMove ) + pKernArray[ nI - 1 ] += nMove; + pKernArray[ nI++ ] -= nSub; + ++nIdx; + } + } + + if( nIdx >= nLen ) + break; + + TextFrameIndex nTmpChg = nLen; + if( ++nCompIdx < nCompCount ) + { + nTmpChg = GetCompStart( nCompIdx ); + if( nTmpChg > nLen ) + nTmpChg = nLen; + nCompLen = GetCompLen( nCompIdx ); + } + + while( nIdx < nTmpChg ) + { + nLast = pKernArray[ nI ]; + pKernArray[ nI++ ] -= nSub; + ++nIdx; + } + } while( nIdx < nLen ); + return nSub; +} + +// Note on calling KashidaJustify(): +// Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean +// total number of kashida positions, or the number of kashida positions after some positions +// have been dropped, depending on the state of the m_KashidaInvalid set. + +sal_Int32 SwScriptInfo::KashidaJustify( long* pKernArray, + long* pScrArray, + TextFrameIndex const nStt, + TextFrameIndex const nLen, + long nSpaceAdd ) const +{ + SAL_WARN_IF( !nLen, "sw.core", "Kashida justification without text?!" ); + + if( !IsKashidaLine(nStt)) + return -1; + + // evaluate kashida information in collected in SwScriptInfo + + size_t nCntKash = 0; + while( nCntKash < CountKashida() ) + { + if ( nStt <= GetKashida( nCntKash ) ) + break; + ++nCntKash; + } + + const TextFrameIndex nEnd = nStt + nLen; + + size_t nCntKashEnd = nCntKash; + while ( nCntKashEnd < CountKashida() ) + { + if ( nEnd <= GetKashida( nCntKashEnd ) ) + break; + ++nCntKashEnd; + } + + size_t nActualKashCount = nCntKashEnd - nCntKash; + for (size_t i = nCntKash; i < nCntKashEnd; ++i) + { + if ( nActualKashCount && !IsKashidaValid ( i ) ) + --nActualKashCount; + } + + if ( !pKernArray ) + return nActualKashCount; + + // do nothing if there is no more kashida + if ( nCntKash < CountKashida() ) + { + // skip any invalid kashidas + while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash)) + ++nCntKash; + + TextFrameIndex nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) + ? GetKashida(nCntKash) + : nEnd; + long nKashAdd = nSpaceAdd; + + while ( nIdx < nEnd ) + { + TextFrameIndex nArrayPos = nIdx - nStt; + + // next kashida position + ++nCntKash; + while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash)) + ++nCntKash; + + nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) ? GetKashida(nCntKash) : nEnd; + if ( nIdx > nEnd ) + nIdx = nEnd; + + const TextFrameIndex nArrayEnd = nIdx - nStt; + + while ( nArrayPos < nArrayEnd ) + { + pKernArray[ sal_Int32(nArrayPos) ] += nKashAdd; + if ( pScrArray ) + pScrArray[ sal_Int32(nArrayPos) ] += nKashAdd; + ++nArrayPos; + } + nKashAdd += nSpaceAdd; + } + } + + return 0; +} + +// Checks if the current text is 'Arabic' text. Note that only the first +// character has to be checked because a ctl portion only contains one +// script, see NewTextPortion +bool SwScriptInfo::IsArabicText(const OUString& rText, + TextFrameIndex const nStt, TextFrameIndex const nLen) +{ + using namespace ::com::sun::star::i18n; + static const ScriptTypeList typeList[] = { + { UnicodeScript_kArabic, UnicodeScript_kArabic, sal_Int16(UnicodeScript_kArabic) }, // 11, + { UnicodeScript_kScriptCount, UnicodeScript_kScriptCount, sal_Int16(UnicodeScript_kScriptCount) } // 88 + }; + + // go forward if current position does not hold a regular character: + const CharClass& rCC = GetAppCharClass(); + sal_Int32 nIdx = sal_Int32(nStt); + const sal_Int32 nEnd = sal_Int32(nStt + nLen); + while ( nIdx < nEnd && !rCC.isLetterNumeric( rText, nIdx ) ) + { + ++nIdx; + } + + if( nIdx == nEnd ) + { + // no regular character found in this portion. Go backward: + --nIdx; + while ( nIdx >= 0 && !rCC.isLetterNumeric( rText, nIdx ) ) + { + --nIdx; + } + } + + if( nIdx >= 0 ) + { + const sal_Unicode cCh = rText[nIdx]; + const sal_Int16 type = unicode::getUnicodeScriptType( cCh, typeList, sal_Int16(UnicodeScript_kScriptCount) ); + return type == sal_Int16(UnicodeScript_kArabic); + } + return false; +} + +bool SwScriptInfo::IsKashidaValid(size_t const nKashPos) const +{ + return m_KashidaInvalid.find(nKashPos) == m_KashidaInvalid.end(); +} + +void SwScriptInfo::ClearKashidaInvalid(size_t const nKashPos) +{ + m_KashidaInvalid.erase(nKashPos); +} + +// bMark == true: +// marks the first valid kashida in the given text range as invalid +// bMark == false: +// clears all kashida invalid flags in the given text range +bool SwScriptInfo::MarkOrClearKashidaInvalid( + TextFrameIndex const nStt, TextFrameIndex const nLen, + bool bMark, sal_Int32 nMarkCount) +{ + size_t nCntKash = 0; + while( nCntKash < CountKashida() ) + { + if ( nStt <= GetKashida( nCntKash ) ) + break; + nCntKash++; + } + + const TextFrameIndex nEnd = nStt + nLen; + + while ( nCntKash < CountKashida() ) + { + if ( nEnd <= GetKashida( nCntKash ) ) + break; + if(bMark) + { + if ( MarkKashidaInvalid ( nCntKash ) ) + { + --nMarkCount; + if (!nMarkCount) + return true; + } + } + else + { + ClearKashidaInvalid ( nCntKash ); + } + nCntKash++; + } + return false; +} + +bool SwScriptInfo::MarkKashidaInvalid(size_t const nKashPos) +{ + return m_KashidaInvalid.insert(nKashPos).second; +} + +// retrieve the kashida positions in the given text range +void SwScriptInfo::GetKashidaPositions( + TextFrameIndex const nStt, TextFrameIndex const nLen, + std::vector<TextFrameIndex>& rKashidaPosition) +{ + size_t nCntKash = 0; + while( nCntKash < CountKashida() ) + { + if ( nStt <= GetKashida( nCntKash ) ) + break; + nCntKash++; + } + + const TextFrameIndex nEnd = nStt + nLen; + + size_t nCntKashEnd = nCntKash; + while ( nCntKashEnd < CountKashida() ) + { + if ( nEnd <= GetKashida( nCntKashEnd ) ) + break; + rKashidaPosition.push_back(GetKashida(nCntKashEnd)); + nCntKashEnd++; + } +} + +void SwScriptInfo::SetNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen) +{ + m_NoKashidaLine.push_back( nStt ); + m_NoKashidaLineEnd.push_back( nStt + nLen ); +} + +// determines if the line uses kashida justification +bool SwScriptInfo::IsKashidaLine(TextFrameIndex const nCharIdx) const +{ + for (size_t i = 0; i < m_NoKashidaLine.size(); ++i) + { + if (nCharIdx >= m_NoKashidaLine[i] && nCharIdx < m_NoKashidaLineEnd[i]) + return false; + } + return true; +} + +void SwScriptInfo::ClearNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen) +{ + size_t i = 0; + while (i < m_NoKashidaLine.size()) + { + if (nStt + nLen >= m_NoKashidaLine[i] && nStt < m_NoKashidaLineEnd[i]) + { + m_NoKashidaLine.erase(m_NoKashidaLine.begin() + i); + m_NoKashidaLineEnd.erase(m_NoKashidaLineEnd.begin() + i); + } + else + ++i; + } +} + +// mark the given character indices as invalid kashida positions +void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt, + const TextFrameIndex* pKashidaPositions) +{ + SAL_WARN_IF( !pKashidaPositions || nCnt == 0, "sw.core", "Where are kashidas?" ); + + size_t nCntKash = 0; + sal_Int32 nKashidaPosIdx = 0; + + while (nCntKash < CountKashida() && nKashidaPosIdx < nCnt) + { + if ( pKashidaPositions [nKashidaPosIdx] > GetKashida( nCntKash ) ) + { + ++nCntKash; + continue; + } + + if ( pKashidaPositions [nKashidaPosIdx] != GetKashida( nCntKash ) || !IsKashidaValid ( nCntKash ) ) + return; // something is wrong + + MarkKashidaInvalid ( nCntKash ); + nKashidaPosIdx++; + } +} + +TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, long* pKernArray, + long* pScrArray, TextFrameIndex const nStt, + TextFrameIndex const nLen, + TextFrameIndex nNumberOfBlanks, + long nSpaceAdd ) +{ + SAL_WARN_IF( nStt + nLen > TextFrameIndex(rText.getLength()), "sw.core", "String in ThaiJustify too small" ); + + SwTwips nNumOfTwipsToDistribute = nSpaceAdd * sal_Int32(nNumberOfBlanks) / + SPACING_PRECISION_FACTOR; + + long nSpaceSum = 0; + TextFrameIndex nCnt(0); + + for (sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI) + { + const sal_Unicode cCh = rText[sal_Int32(nStt) + nI]; + + // check if character is not above or below base + if ( ( 0xE34 > cCh || cCh > 0xE3A ) && + ( 0xE47 > cCh || cCh > 0xE4E ) && cCh != 0xE31 ) + { + if (nNumberOfBlanks > TextFrameIndex(0)) + { + nSpaceAdd = nNumOfTwipsToDistribute / sal_Int32(nNumberOfBlanks); + --nNumberOfBlanks; + nNumOfTwipsToDistribute -= nSpaceAdd; + } + nSpaceSum += nSpaceAdd; + ++nCnt; + } + + if ( pKernArray ) pKernArray[ nI ] += nSpaceSum; + if ( pScrArray ) pScrArray[ nI ] += nSpaceSum; + } + + return nCnt; +} + +SwScriptInfo* SwScriptInfo::GetScriptInfo( const SwTextNode& rTNd, + SwTextFrame const**const o_ppFrame, + bool const bAllowInvalid) +{ + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd); + SwScriptInfo* pScriptInfo = nullptr; + + for( SwTextFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + pScriptInfo = const_cast<SwScriptInfo*>(pLast->GetScriptInfo()); + if ( pScriptInfo ) + { + if (bAllowInvalid || + TextFrameIndex(COMPLETE_STRING) == pScriptInfo->GetInvalidityA()) + { + if (o_ppFrame) + { + *o_ppFrame = pLast; + } + break; + } + pScriptInfo = nullptr; + } + } + + return pScriptInfo; +} + +SwParaPortion::SwParaPortion() +{ + FormatReset(); + m_bFlys = m_bFootnoteNum = m_bMargin = false; + SetWhichPor( PortionType::Para ); +} + +SwParaPortion::~SwParaPortion() +{ +} + +TextFrameIndex SwParaPortion::GetParLen() const +{ + TextFrameIndex nLen(0); + const SwLineLayout *pLay = this; + while( pLay ) + { + nLen += pLay->GetLen(); + pLay = pLay->GetNext(); + } + return nLen; +} + +const SwDropPortion *SwParaPortion::FindDropPortion() const +{ + const SwLineLayout *pLay = this; + while( pLay && pLay->IsDummy() ) + pLay = pLay->GetNext(); + while( pLay ) + { + const SwLinePortion *pPos = pLay->GetNextPortion(); + while ( pPos && !pPos->GetLen() ) + pPos = pPos->GetNextPortion(); + if( pPos && pPos->IsDropPortion() ) + return static_cast<const SwDropPortion *>(pPos); + pLay = pLay->GetLen() ? nullptr : pLay->GetNext(); + } + return nullptr; +} + +void SwLineLayout::Init( SwLinePortion* pNextPortion ) +{ + Height( 0 ); + Width( 0 ); + SetLen(TextFrameIndex(0)); + SetAscent( 0 ); + SetRealHeight( 0 ); + SetNextPortion( pNextPortion ); +} + +// looks for hanging punctuation portions in the paragraph +// and return the maximum right offset of them. +// If no such portion is found, the Margin/Hanging-flags will be updated. +SwTwips SwLineLayout::GetHangingMargin_() const +{ + SwLinePortion* pPor = GetNextPortion(); + bool bFound = false; + SwTwips nDiff = 0; + while( pPor) + { + if( pPor->IsHangingPortion() ) + { + nDiff = static_cast<SwHangingPortion*>(pPor)->GetInnerWidth() - pPor->Width(); + if( nDiff ) + bFound = true; + } + // the last post its portion + else if ( pPor->IsPostItsPortion() && ! pPor->GetNextPortion() ) + nDiff = nAscent; + + pPor = pPor->GetNextPortion(); + } + if( !bFound ) // update the hanging-flag + const_cast<SwLineLayout*>(this)->SetHanging( false ); + return nDiff; +} + +SwTwips SwTextFrame::HangingMargin() const +{ + SAL_WARN_IF( !HasPara(), "sw.core", "Don't call me without a paraportion" ); + if( !GetPara()->IsMargin() ) + return 0; + const SwLineLayout* pLine = GetPara(); + SwTwips nRet = 0; + do + { + SwTwips nDiff = pLine->GetHangingMargin(); + if( nDiff > nRet ) + nRet = nDiff; + pLine = pLine->GetNext(); + } while ( pLine ); + if( !nRet ) // update the margin-flag + const_cast<SwParaPortion*>(GetPara())->SetMargin( false ); + return nRet; +} + +void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, + MultiSelection & rHiddenMulti, + std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks) +{ + assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1) + || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len())); + + const SfxPoolItem* pItem = nullptr; + if( SfxItemState::SET == rNode.GetSwAttrSet().GetItemState( RES_CHRATR_HIDDEN, true, &pItem ) && + static_cast<const SvxCharHiddenItem*>(pItem)->GetValue() ) + { + rHiddenMulti.SelectAll(); + } + + const SwpHints* pHints = rNode.GetpSwpHints(); + + if( pHints ) + { + for( size_t nTmp = 0; nTmp < pHints->Count(); ++nTmp ) + { + const SwTextAttr* pTextAttr = pHints->Get( nTmp ); + const SvxCharHiddenItem* pHiddenItem = CharFormat::GetItem( *pTextAttr, RES_CHRATR_HIDDEN ); + if( pHiddenItem ) + { + const sal_Int32 nSt = pTextAttr->GetStart(); + const sal_Int32 nEnd = *pTextAttr->End(); + if( nEnd > nSt ) + { + Range aTmp( nSt, nEnd - 1 ); + rHiddenMulti.Select( aTmp, pHiddenItem->GetValue() ); + } + } + } + } + + for (const SwIndex* pIndex = rNode.GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + { + const sw::mark::IMark* pMark = pIndex->GetMark(); + const sw::mark::IBookmark* pBookmark = dynamic_cast<const sw::mark::IBookmark*>(pMark); + if (pBookmarks && pBookmark) + { + if (!pBookmark->IsExpanded()) + { + pBookmarks->emplace_back(pBookmark, MarkKind::Point); + } + else if (pIndex == &pBookmark->GetMarkStart().nContent) + { + pBookmarks->emplace_back(pBookmark, MarkKind::Start); + } + else + { + assert(pIndex == &pBookmark->GetMarkEnd().nContent); + pBookmarks->emplace_back(pBookmark, MarkKind::End); + } + } + if (pBookmark && pBookmark->IsHidden()) + { + // intersect bookmark range with textnode range and add the intersection to rHiddenMulti + + const sal_Int32 nSt = pBookmark->GetMarkStart().nContent.GetIndex(); + const sal_Int32 nEnd = pBookmark->GetMarkEnd().nContent.GetIndex(); + + if( nEnd > nSt ) + { + Range aTmp( nSt, nEnd - 1 ); + rHiddenMulti.Select(aTmp, true); + } + } + } +} + +void SwScriptInfo::selectRedLineDeleted(const SwTextNode& rNode, MultiSelection &rHiddenMulti, bool bSelect) +{ + assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1) + || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len())); + + const IDocumentRedlineAccess& rIDRA = rNode.getIDocumentRedlineAccess(); + if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) ) + { + SwRedlineTable::size_type nAct = rIDRA.GetRedlinePos( rNode, RedlineType::Any ); + + for ( ; nAct < rIDRA.GetRedlineTable().size(); nAct++ ) + { + const SwRangeRedline* pRed = rIDRA.GetRedlineTable()[ nAct ]; + + if (pRed->Start()->nNode > rNode.GetIndex()) + break; + + if (pRed->GetType() != RedlineType::Delete) + continue; + + sal_Int32 nRedlStart; + sal_Int32 nRedlnEnd; + pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd ); + //clip it if the redline extends past the end of the nodes text + nRedlnEnd = std::min<sal_Int32>(nRedlnEnd, rNode.GetText().getLength()); + if ( nRedlnEnd > nRedlStart ) + { + Range aTmp( nRedlStart, nRedlnEnd - 1 ); + rHiddenMulti.Select( aTmp, bSelect ); + } + } + } +} + +// Returns a MultiSection indicating the hidden ranges. +void SwScriptInfo::CalcHiddenRanges( const SwTextNode& rNode, + MultiSelection & rHiddenMulti, + std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks) +{ + selectHiddenTextProperty(rNode, rHiddenMulti, pBookmarks); + + // If there are any hidden ranges in the current text node, we have + // to unhide the redlining ranges: + selectRedLineDeleted(rNode, rHiddenMulti, false); + + // We calculated a lot of stuff. Finally we can update the flags at the text node. + + const bool bNewContainsHiddenChars = rHiddenMulti.GetRangeCount() > 0; + bool bNewHiddenCharsHidePara = false; + if ( bNewContainsHiddenChars ) + { + const Range& rRange = rHiddenMulti.GetRange( 0 ); + const sal_Int32 nHiddenStart = rRange.Min(); + const sal_Int32 nHiddenEnd = rRange.Max() + 1; + bNewHiddenCharsHidePara = + (nHiddenStart == 0 && nHiddenEnd >= rNode.GetText().getLength()); + } + rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars ); +} + +TextFrameIndex SwScriptInfo::CountCJKCharacters(const OUString &rText, + TextFrameIndex nPos, TextFrameIndex const nEnd, LanguageType aLang) +{ + TextFrameIndex nCount(0); + if (nEnd > nPos) + { + sal_Int32 nDone = 0; + const lang::Locale &rLocale = g_pBreakIt->GetLocale( aLang ); + while ( nPos < nEnd ) + { + nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharacters( + rText, sal_Int32(nPos), + rLocale, + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone)); + nCount++; + } + } + else + nCount = nEnd - nPos ; + + return nCount; +} + +void SwScriptInfo::CJKJustify( const OUString& rText, long* pKernArray, + long* pScrArray, TextFrameIndex const nStt, + TextFrameIndex const nLen, LanguageType aLang, + long nSpaceAdd, bool bIsSpaceStop ) +{ + assert( pKernArray != nullptr && sal_Int32(nStt) >= 0 ); + if (sal_Int32(nLen) > 0) + { + long nSpaceSum = 0; + const lang::Locale &rLocale = g_pBreakIt->GetLocale( aLang ); + sal_Int32 nDone = 0; + sal_Int32 nNext(nStt); + for ( sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI ) + { + if (nI + sal_Int32(nStt) == nNext) + { + nNext = g_pBreakIt->GetBreakIter()->nextCharacters( rText, nNext, + rLocale, + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone ); + if (nNext < sal_Int32(nStt + nLen) || !bIsSpaceStop) + nSpaceSum += nSpaceAdd; + } + pKernArray[ nI ] += nSpaceSum; + if ( pScrArray ) + pScrArray[ nI ] += nSpaceSum; + } + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porlay.hxx b/sw/source/core/text/porlay.hxx new file mode 100644 index 000000000..ea8220247 --- /dev/null +++ b/sw/source/core/text/porlay.hxx @@ -0,0 +1,327 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORLAY_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORLAY_HXX + +#include <scriptinfo.hxx> + +#include <swrect.hxx> +#include <swtypes.hxx> +#include "portxt.hxx" + +#include <vector> +#include <deque> + +class SwMarginPortion; +class SwDropPortion; +class SwTextFormatter; + +class SwCharRange +{ +private: + TextFrameIndex nStart; + TextFrameIndex nLen; + +public: + SwCharRange(TextFrameIndex const nInitStart = TextFrameIndex(0), + TextFrameIndex const nInitLen = TextFrameIndex(0)) + : nStart( nInitStart ), nLen(nInitLen) {} + TextFrameIndex & Start() { return nStart; } + TextFrameIndex const& Start() const { return nStart; } + void LeftMove(TextFrameIndex const nNew) + { if ( nNew < nStart ) { nLen += nStart-nNew; nStart = nNew; } } + TextFrameIndex & Len() { return nLen; } + TextFrameIndex const& Len() const { return nLen; } + bool operator<(const SwCharRange &rRange) const + { return nStart < rRange.nStart; } + bool operator>(const SwCharRange &rRange) const + { return nStart + nLen > rRange.nStart + rRange.nLen; } + bool operator!=(const SwCharRange &rRange) const + { return *this < rRange || *this > rRange; } + SwCharRange &operator+=(const SwCharRange &rRange); +}; + +// SwRepaint is a document-global SwRect +// nOfst states from where in the first line should be painted +// nRightOfst gives the right margin +class SwRepaint : public SwRect +{ + SwTwips nOfst; + SwTwips nRightOfst; +public: + SwRepaint() : SwRect(), nOfst( 0 ), nRightOfst( 0 ) {} + + SwTwips GetOffset() const { return nOfst; } + void SetOffset( const SwTwips nNew ) { nOfst = nNew; } + SwTwips GetRightOfst() const { return nRightOfst; } + void SetRightOfst( const SwTwips nNew ) { nRightOfst = nNew; } +}; + +/// Collection of SwLinePortion instances, representing one line of text. +/// Typically owned by an SwParaPortion. +class SwLineLayout : public SwTextPortion +{ +private: + SwLineLayout *m_pNext; // The next Line + std::unique_ptr<std::vector<long>> m_pLLSpaceAdd; // Used for justified alignment + std::unique_ptr<std::deque<sal_uInt16>> m_pKanaComp; // Used for Kana compression + sal_uInt16 m_nRealHeight; // The height resulting from line spacing and register + bool m_bFormatAdj : 1; + bool m_bDummy : 1; + bool m_bEndHyph : 1; + bool m_bMidHyph : 1; + bool m_bFly : 1; + bool m_bRest : 1; + bool m_bBlinking : 1; + bool m_bClipping : 1; // Clipping needed for exact line height + bool m_bContent : 1; // Text for line numbering + bool m_bRedline : 1; // The Redlining + bool m_bForcedLeftMargin : 1; // Left adjustment moved by the Fly + bool m_bHanging : 1; // Contains a hanging portion in the margin + bool m_bUnderscore : 1; + + SwTwips GetHangingMargin_() const; + + void DeleteNext(); +public: + // From SwLinePortion + virtual SwLinePortion *Insert( SwLinePortion *pPortion ) override; + virtual SwLinePortion *Append( SwLinePortion *pPortion ) override; + SwLinePortion *GetFirstPortion() const; + + // Flags + void ResetFlags(); + void SetFormatAdj( const bool bNew ) { m_bFormatAdj = bNew; } + bool IsFormatAdj() const { return m_bFormatAdj; } + void SetEndHyph( const bool bNew ) { m_bEndHyph = bNew; } + bool IsEndHyph() const { return m_bEndHyph; } + void SetMidHyph( const bool bNew ) { m_bMidHyph = bNew; } + bool IsMidHyph() const { return m_bMidHyph; } + void SetFly( const bool bNew ) { m_bFly = bNew; } + bool IsFly() const { return m_bFly; } + void SetRest( const bool bNew ) { m_bRest = bNew; } + bool IsRest() const { return m_bRest; } + void SetBlinking( const bool bNew ) { m_bBlinking = bNew; } + bool IsBlinking() const { return m_bBlinking; } + void SetContent( const bool bNew ) { m_bContent = bNew; } + bool HasContent() const { return m_bContent; } + void SetRedline( const bool bNew ) { m_bRedline = bNew; } + bool HasRedline() const { return m_bRedline; } + void SetForcedLeftMargin() { m_bForcedLeftMargin = true; } + bool HasForcedLeftMargin() const { return m_bForcedLeftMargin; } + void SetHanging( const bool bNew ) { m_bHanging = bNew; } + bool IsHanging() const { return m_bHanging; } + void SetUnderscore( const bool bNew ) { m_bUnderscore = bNew; } + bool HasUnderscore() const { return m_bUnderscore; } + + // Respecting empty dummy lines + void SetDummy( const bool bNew ) { m_bDummy = bNew; } + bool IsDummy() const { return m_bDummy; } + + void SetClipping( const bool bNew ) { m_bClipping = bNew; } + bool IsClipping() const { return m_bClipping; } + + SwLineLayout(); + virtual ~SwLineLayout() override; + + SwLineLayout *GetNext() { return m_pNext; } + const SwLineLayout *GetNext() const { return m_pNext; } + void SetNext( SwLineLayout *pNew ) { m_pNext = pNew; } + + void Init( SwLinePortion *pNextPortion = nullptr); + + // Collects the data for the line + void CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ); + + void SetRealHeight( sal_uInt16 nNew ) { m_nRealHeight = nNew; } + sal_uInt16 GetRealHeight() const { return m_nRealHeight; } + + // Creates the glue chain for short lines + SwMarginPortion *CalcLeftMargin(); + + SwTwips GetHangingMargin() const + { return GetHangingMargin_(); } + + // For special treatment for empty lines + virtual bool Format( SwTextFormatInfo &rInf ) override; + + // Stuff for justified alignment + bool IsSpaceAdd() const { return m_pLLSpaceAdd != nullptr; } + void InitSpaceAdd(); // Creates pLLSpaceAdd if necessary + void CreateSpaceAdd( const long nInit = 0 ); + void FinishSpaceAdd() { m_pLLSpaceAdd.reset(); } + sal_uInt16 GetLLSpaceAddCount() const { return sal::static_int_cast< sal_uInt16 >(m_pLLSpaceAdd->size()); } + void SetLLSpaceAdd( long nNew, sal_uInt16 nIdx ) + { + if ( nIdx == GetLLSpaceAddCount() ) + m_pLLSpaceAdd->push_back( nNew ); + else + (*m_pLLSpaceAdd)[ nIdx ] = nNew; + } + long GetLLSpaceAdd( sal_uInt16 nIdx ) { return (*m_pLLSpaceAdd)[ nIdx ]; } + void RemoveFirstLLSpaceAdd() { m_pLLSpaceAdd->erase( m_pLLSpaceAdd->begin() ); } + std::vector<long>* GetpLLSpaceAdd() const { return m_pLLSpaceAdd.get(); } + + // Stuff for Kana compression + void SetKanaComp( std::unique_ptr<std::deque<sal_uInt16>> pNew ){ m_pKanaComp = std::move(pNew); } + void FinishKanaComp() { m_pKanaComp.reset(); } + std::deque<sal_uInt16>* GetpKanaComp() const { return m_pKanaComp.get(); } + std::deque<sal_uInt16>& GetKanaComp() { return *m_pKanaComp; } + + /** determine ascent and descent for positioning of as-character anchored + object + + OD 07.01.2004 #i11859# - previously local method <lcl_MaxAscDescent> + Method calculates maximum ascents and descents of the line layout. + One value considering as-character anchored objects, one without these + objects. + Portions for other anchored objects aren't considered. + OD 2005-05-20 #i47162# - add optional parameter <_bNoFlyCntPorAndLinePor> + to control, if the fly content portions and line portion are considered. + + @param _orAscent + output parameter - maximum ascent without as-character anchored objects + + @param _orDescent + output parameter - maximum descent without as-character anchored objects + + @param _orObjAscent + output parameter - maximum ascent with as-character anchored objects + + @param _orObjDescent + output parameter - maximum descent with as-character anchored objects + + @param _pDontConsiderPortion + input parameter - portion, which isn't considered for calculating + <_orObjAscent> and <_orObjDescent>, if it isn't a portion for a + as-character anchored object or it isn't as high as the line. + + @param _bNoFlyCntPorAndLinePor + optional input parameter - boolean, indicating that fly content portions + and the line portion are considered or not. + */ + void MaxAscentDescent( SwTwips& _orAscent, + SwTwips& _orDescent, + SwTwips& _orObjAscent, + SwTwips& _orObjDescent, + const SwLinePortion* _pDontConsiderPortion = nullptr, + const bool _bNoFlyCntPorAndLinePor = false ) const; +}; + +/// Collection of SwLineLayout instances, represents the paragraph text in Writer layout. +/// Typically owned by an SwTextFrame. +class SwParaPortion : public SwLineLayout +{ + // Area that needs repainting + SwRepaint m_aRepaint; + // Area that needs reformatting + SwCharRange m_aReformat; + SwScriptInfo m_aScriptInfo; + + // Fraction aZoom; + long m_nDelta; + + // If a SwTextFrame is locked, no changes occur to the formatting data (under + // pLine) (compare with Orphans) + bool m_bFlys : 1; // Overlapping Flys? + bool m_bPrep : 1; // PREP_* + bool m_bPrepWidows : 1; // PrepareHint::Widows + bool m_bPrepAdjust : 1; // PrepareHint::AdjustSizeWithoutFormatting + bool m_bPrepMustFit : 1; // PrepareHint::MustFit + bool m_bFollowField : 1; // We have a bit of field left for the Follow + + bool m_bFixLineHeight : 1; // Fixed line height + bool m_bFootnoteNum : 1; // contains a footnotenumberportion + bool m_bMargin : 1; // contains a hanging punctuation in the margin + +public: + SwParaPortion(); + virtual ~SwParaPortion() override; + + // Resets all formatting information (except for bFlys) + inline void FormatReset(); + + // Resets the Flags + inline void ResetPreps(); + + // Get/Set methods + SwRepaint& GetRepaint() { return m_aRepaint; } + const SwRepaint& GetRepaint() const { return m_aRepaint; } + SwCharRange& GetReformat() { return m_aReformat; } + const SwCharRange& GetReformat() const { return m_aReformat; } + long& GetDelta() { return m_nDelta; } + const long& GetDelta() const { return m_nDelta; } + SwScriptInfo& GetScriptInfo() { return m_aScriptInfo; } + const SwScriptInfo& GetScriptInfo() const { return m_aScriptInfo; } + + // For SwTextFrame::Format: returns the paragraph's current length + TextFrameIndex GetParLen() const; + + // For Prepare() + bool UpdateQuoVadis( const OUString &rQuo ); + + // Flags + void SetFly() { m_bFlys = true; } + bool HasFly() const { return m_bFlys; } + + // Preps + void SetPrep() { m_bPrep = true; } + bool IsPrep() const { return m_bPrep; } + void SetPrepWidows() { m_bPrepWidows = true; } + bool IsPrepWidows() const { return m_bPrepWidows; } + void SetPrepMustFit( const bool bNew ) { m_bPrepMustFit = bNew; } + bool IsPrepMustFit() const { return m_bPrepMustFit; } + void SetPrepAdjust() { m_bPrepAdjust = true; } + bool IsPrepAdjust() const { return m_bPrepAdjust; } + void SetFollowField( const bool bNew ) { m_bFollowField = bNew; } + bool IsFollowField() const { return m_bFollowField; } + void SetFixLineHeight() { m_bFixLineHeight = true; } + bool IsFixLineHeight() const { return m_bFixLineHeight; } + + void SetFootnoteNum( const bool bNew ) { m_bFootnoteNum = bNew; } + bool IsFootnoteNum() const { return m_bFootnoteNum; } + void SetMargin( const bool bNew = true ) { m_bMargin = bNew; } + bool IsMargin() const { return m_bMargin; } + + // Set nErgo in the QuoVadisPortion + void SetErgoSumNum( const OUString &rErgo ); + + const SwDropPortion *FindDropPortion() const; +}; + +inline void SwParaPortion::ResetPreps() +{ + m_bPrep = m_bPrepWidows = m_bPrepAdjust = m_bPrepMustFit = false; +} + +inline void SwParaPortion::FormatReset() +{ + m_nDelta = 0; + m_aReformat = SwCharRange(TextFrameIndex(0), TextFrameIndex(COMPLETE_STRING)); + // bFlys needs to be retained in SwTextFrame::Format_() so that empty + // paragraphs that needed to avoid Frames with no flow, reformat + // when the Frame disappears from the Area + // bFlys = false; + ResetPreps(); + m_bFollowField = m_bFixLineHeight = m_bMargin = false; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porlin.cxx b/sw/source/core/text/porlin.cxx new file mode 100644 index 000000000..a9b057faa --- /dev/null +++ b/sw/source/core/text/porlin.cxx @@ -0,0 +1,320 @@ +/* -*- 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 <vcl/outdev.hxx> +#include <SwPortionHandler.hxx> + +#include "porlin.hxx" +#include "inftxt.hxx" +#include "pormulti.hxx" +#if OSL_DEBUG_LEVEL > 0 + +static bool ChkChain( SwLinePortion *pStart ) +{ + SwLinePortion *pPor = pStart->GetNextPortion(); + sal_uInt16 nCount = 0; + while( pPor ) + { + ++nCount; + OSL_ENSURE( nCount < 200 && pPor != pStart, + "ChkChain(): lost in chains" ); + if( nCount >= 200 || pPor == pStart ) + { + // the lifesaver + pPor = pStart->GetNextPortion(); + pStart->SetNextPortion(nullptr); + pPor->Truncate(); + pStart->SetNextPortion( pPor ); + return false; + } + pPor = pPor->GetNextPortion(); + } + return true; +} +#endif + +SwLinePortion::~SwLinePortion() +{ +} + +SwLinePortion *SwLinePortion::Compress() +{ + return GetLen() || Width() ? this : nullptr; +} + +sal_uInt16 SwLinePortion::GetViewWidth( const SwTextSizeInfo & ) const +{ + return 0; +} + +SwLinePortion::SwLinePortion( ) : + mpNextPortion( nullptr ), + nLineLength( 0 ), + nAscent( 0 ), + nWhichPor( PortionType::NONE ), + m_bJoinBorderWithPrev(false), + m_bJoinBorderWithNext(false) +{ +} + +void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf, + const SwLinePortion* pLast ) const +{ + OSL_ENSURE( rInf.OnWin(), "SwLinePortion::PrePaint: don't prepaint on a printer"); + OSL_ENSURE( !Width(), "SwLinePortion::PrePaint: For Width()==0 only!"); + + const sal_uInt16 nViewWidth = GetViewWidth( rInf ); + + if( ! nViewWidth ) + return; + + const sal_uInt16 nHalfView = nViewWidth / 2; + sal_uInt16 nLastWidth = pLast->Width(); + + if ( pLast->InSpaceGrp() && rInf.GetSpaceAdd() ) + nLastWidth = nLastWidth + static_cast<sal_uInt16>(pLast->CalcSpacing( rInf.GetSpaceAdd(), rInf )); + + sal_uInt16 nPos; + SwTextPaintInfo aInf( rInf ); + + const bool bBidiPor = rInf.GetTextFrame()->IsRightToLeft() != + bool( ComplexTextLayoutFlags::BiDiRtl & rInf.GetOut()->GetLayoutMode() ); + + sal_uInt16 nDir = bBidiPor ? + 1800 : + rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ); + + // pLast == this *only* for the 1st portion in the line so nLastWidth is 0; + // allow this too, will paint outside the frame but might look better... + if (nLastWidth > nHalfView || pLast == this) + { + switch (nDir) + { + case 0: + nPos = sal_uInt16( rInf.X() ); + nPos += nLastWidth - nHalfView; + aInf.X( nPos ); + break; + case 900: + nPos = sal_uInt16( rInf.Y() ); + nPos -= nLastWidth - nHalfView; + aInf.Y( nPos ); + break; + case 1800: + nPos = sal_uInt16( rInf.X() ); + nPos -= nLastWidth - nHalfView; + aInf.X( nPos ); + break; + case 2700: + nPos = sal_uInt16( rInf.Y() ); + nPos += nLastWidth - nHalfView; + aInf.Y( nPos ); + break; + } + } + + SwLinePortion *pThis = const_cast<SwLinePortion*>(this); + pThis->Width( nViewWidth ); + Paint( aInf ); + pThis->Width(0); +} + +void SwLinePortion::CalcTextSize( const SwTextSizeInfo &rInf ) +{ + if( GetLen() == rInf.GetLen() ) + *static_cast<SwPosSize*>(this) = GetTextSize( rInf ); + else + { + SwTextSizeInfo aInf( rInf ); + aInf.SetLen( GetLen() ); + *static_cast<SwPosSize*>(this) = GetTextSize( aInf ); + } +} + +// all following portions will be deleted +void SwLinePortion::Truncate_() +{ + SwLinePortion *pPos = mpNextPortion; + do + { + OSL_ENSURE( pPos != this, "SwLinePortion::Truncate: loop" ); + SwLinePortion *pLast = pPos; + pPos = pPos->GetNextPortion(); + pLast->SetNextPortion( nullptr ); + delete pLast; + + } while( pPos ); + + mpNextPortion = nullptr; +} + +// It always will be inserted after us. +SwLinePortion *SwLinePortion::Insert( SwLinePortion *pIns ) +{ + pIns->FindLastPortion()->SetNextPortion( mpNextPortion ); + SetNextPortion( pIns ); +#if OSL_DEBUG_LEVEL > 0 + ChkChain( this ); +#endif + return pIns; +} + +SwLinePortion *SwLinePortion::FindLastPortion() +{ + SwLinePortion *pPos = this; + // Find the end and link pLinPortion to the last one... + while( pPos->GetNextPortion() ) + { + pPos = pPos->GetNextPortion(); + } + return pPos; +} + +SwLinePortion *SwLinePortion::Append( SwLinePortion *pIns ) +{ + SwLinePortion *pPos = FindLastPortion(); + pPos->SetNextPortion( pIns ); + pIns->SetNextPortion( nullptr ); +#if OSL_DEBUG_LEVEL > 0 + ChkChain( this ); +#endif + return pIns; +} + +SwLinePortion *SwLinePortion::Cut( SwLinePortion *pVictim ) +{ + SwLinePortion *pPrev = pVictim->FindPrevPortion( this ); + OSL_ENSURE( pPrev, "SwLinePortion::Cut(): can't cut" ); + pPrev->SetNextPortion( pVictim->GetNextPortion() ); + pVictim->SetNextPortion(nullptr); + return pVictim; +} + +SwLinePortion *SwLinePortion::FindPrevPortion( const SwLinePortion *pRoot ) +{ + OSL_ENSURE( pRoot != this, "SwLinePortion::FindPrevPortion(): invalid root" ); + SwLinePortion *pPos = const_cast<SwLinePortion*>(pRoot); + while( pPos->GetNextPortion() && pPos->GetNextPortion() != this ) + { + pPos = pPos->GetNextPortion(); + } + OSL_ENSURE( pPos->GetNextPortion(), + "SwLinePortion::FindPrevPortion: blowing in the wind"); + return pPos; +} + +TextFrameIndex SwLinePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +{ + if( nOfst > ( PrtWidth() / 2 ) ) + return GetLen(); + else + return TextFrameIndex(0); +} + +SwPosSize SwLinePortion::GetTextSize( const SwTextSizeInfo & ) const +{ + OSL_ENSURE( false, "SwLinePortion::GetTextSize: don't ask me about sizes, " + "I'm only a stupid SwLinePortion" ); + return SwPosSize(); +} + +bool SwLinePortion::Format( SwTextFormatInfo &rInf ) +{ + if( rInf.X() > rInf.Width() ) + { + Truncate(); + rInf.SetUnderflow( this ); + return true; + } + + const SwLinePortion *pLast = rInf.GetLast(); + Height( pLast->Height() ); + SetAscent( pLast->GetAscent() ); + const sal_uInt16 nNewWidth = static_cast<sal_uInt16>(rInf.X() + PrtWidth()); + // Only portions with true width can return true + // Notes for example never set bFull==true + if( rInf.Width() <= nNewWidth && PrtWidth() && ! IsKernPortion() ) + { + Truncate(); + if( nNewWidth > rInf.Width() ) + PrtWidth( nNewWidth - rInf.Width() ); + rInf.GetLast()->FormatEOL( rInf ); + return true; + } + return false; +} + +// Format end of line + +void SwLinePortion::FormatEOL( SwTextFormatInfo & ) +{ } + +void SwLinePortion::Move( SwTextPaintInfo &rInf ) +{ + bool bB2T = rInf.GetDirection() == DIR_BOTTOM2TOP; + const bool bFrameDir = rInf.GetTextFrame()->IsRightToLeft(); + bool bCounterDir = ( ! bFrameDir && DIR_RIGHT2LEFT == rInf.GetDirection() ) || + ( bFrameDir && DIR_LEFT2RIGHT == rInf.GetDirection() ); + + if ( InSpaceGrp() && rInf.GetSpaceAdd() ) + { + SwTwips nTmp = PrtWidth() + CalcSpacing( rInf.GetSpaceAdd(), rInf ); + if( rInf.IsRotated() ) + rInf.Y( rInf.Y() + ( bB2T ? -nTmp : nTmp ) ); + else if ( bCounterDir ) + rInf.X( rInf.X() - nTmp ); + else + rInf.X( rInf.X() + nTmp ); + } + else + { + if( InFixMargGrp() && !IsMarginPortion() ) + { + rInf.IncSpaceIdx(); + rInf.IncKanaIdx(); + } + if( rInf.IsRotated() ) + rInf.Y( rInf.Y() + ( bB2T ? -PrtWidth() : PrtWidth() ) ); + else if ( bCounterDir ) + rInf.X( rInf.X() - PrtWidth() ); + else + rInf.X( rInf.X() + PrtWidth() ); + } + if( IsMultiPortion() && static_cast<SwMultiPortion*>(this)->HasTabulator() ) + rInf.IncSpaceIdx(); + + rInf.SetIdx( rInf.GetIdx() + GetLen() ); +} + +long SwLinePortion::CalcSpacing( long , const SwTextSizeInfo & ) const +{ + return 0; +} + +bool SwLinePortion::GetExpText( const SwTextSizeInfo &, OUString & ) const +{ + return false; +} + +void SwLinePortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), OUString(), GetWhichPor(), Height(), Width() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porlin.hxx b/sw/source/core/text/porlin.hxx new file mode 100644 index 000000000..615c90022 --- /dev/null +++ b/sw/source/core/text/porlin.hxx @@ -0,0 +1,204 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORLIN_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORLIN_HXX + +#include "possiz.hxx" +#include <txttypes.hxx> +#include <TextFrameIndex.hxx> +#include <rtl/ustring.hxx> + +class SwTextSizeInfo; +class SwTextPaintInfo; +class SwTextFormatInfo; +class SwPortionHandler; + +/// Portion groups +/// @see enum PortionType in txttypes.hxx +#define PORGRP_TXT 0x8000 +#define PORGRP_EXP 0x4000 +#define PORGRP_FLD 0x2000 +#define PORGRP_HYPH 0x1000 +#define PORGRP_NUMBER 0x0800 +#define PORGRP_GLUE 0x0400 +#define PORGRP_FIX 0x0200 +#define PORGRP_TAB 0x0100 +// Small special groups +#define PORGRP_FIXMARG 0x0040 +//#define PORGRP_? 0x0020 +#define PORGRP_TABNOTLFT 0x0010 +#define PORGRP_TOXREF 0x0008 + +/// Base class for anything that can be part of a line in the Writer layout. +/// Typically owned by SwLineLayout. +class SwLinePortion: public SwPosSize +{ +protected: + // Here we have areas with different attributes + SwLinePortion *mpNextPortion; + // Count of chars and spaces on the line + TextFrameIndex nLineLength; + sal_uInt16 nAscent; // Maximum ascender + + SwLinePortion(); +private: + PortionType nWhichPor; // Who's who? + bool m_bJoinBorderWithPrev; + bool m_bJoinBorderWithNext; + + void Truncate_(); + +public: + explicit inline SwLinePortion(const SwLinePortion &rPortion); + virtual ~SwLinePortion(); + + // Access methods + SwLinePortion *GetNextPortion() const { return mpNextPortion; } + inline SwLinePortion &operator=(const SwLinePortion &rPortion); + TextFrameIndex GetLen() const { return nLineLength; } + void SetLen(TextFrameIndex const nLen) { nLineLength = nLen; } + void SetNextPortion( SwLinePortion *pNew ){ mpNextPortion = pNew; } + sal_uInt16 &GetAscent() { return nAscent; } + sal_uInt16 GetAscent() const { return nAscent; } + void SetAscent( const sal_uInt16 nNewAsc ) { nAscent = nNewAsc; } + void PrtWidth( sal_uInt16 nNewWidth ) { Width( nNewWidth ); } + sal_uInt16 PrtWidth() const { return Width(); } + void AddPrtWidth( const sal_uInt16 nNew ) { Width( Width() + nNew ); } + void SubPrtWidth( const sal_uInt16 nNew ) { Width( Width() - nNew ); } + + // Insert methods + virtual SwLinePortion *Insert( SwLinePortion *pPortion ); + virtual SwLinePortion *Append( SwLinePortion *pPortion ); + SwLinePortion *Cut( SwLinePortion *pVictim ); + inline void Truncate(); + + // Returns 0, if there's no payload + virtual SwLinePortion *Compress(); + + void SetWhichPor( const PortionType nNew ) { nWhichPor = nNew; } + PortionType GetWhichPor( ) const { return nWhichPor; } + +// Group queries + bool InTextGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_TXT) != 0; } + bool InGlueGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_GLUE) != 0; } + bool InTabGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_TAB) != 0; } + bool InHyphGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_HYPH) != 0; } + bool InNumberGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_NUMBER) != 0; } + bool InFixGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_FIX) != 0; } + bool InFieldGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_FLD) != 0; } + bool InToxRefGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_TOXREF) != 0; } + bool InToxRefOrFieldGrp() const { return (sal_uInt16(nWhichPor) & ( PORGRP_FLD | PORGRP_TOXREF )) != 0; } + bool InExpGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_EXP) != 0; } + bool InFixMargGrp() const { return (sal_uInt16(nWhichPor) & PORGRP_FIXMARG) != 0; } + bool InSpaceGrp() const { return InTextGrp() || IsMultiPortion(); } +// Individual queries + bool IsGrfNumPortion() const { return nWhichPor == PortionType::GrfNum; } + bool IsFlyCntPortion() const { return nWhichPor == PortionType::FlyCnt; } + bool IsBlankPortion() const { return nWhichPor == PortionType::Blank; } + bool IsBreakPortion() const { return nWhichPor == PortionType::Break; } + bool IsErgoSumPortion() const { return nWhichPor == PortionType::ErgoSum; } + bool IsQuoVadisPortion() const { return nWhichPor == PortionType::QuoVadis; } + bool IsTabLeftPortion() const { return nWhichPor == PortionType::TabLeft; } + bool IsTabRightPortion() const { return nWhichPor == PortionType::TabRight; } + bool IsFootnoteNumPortion() const { return nWhichPor == PortionType::FootnoteNum; } + bool IsFootnotePortion() const { return nWhichPor == PortionType::Footnote; } + bool IsDropPortion() const { return nWhichPor == PortionType::Drop; } + bool IsLayPortion() const { return nWhichPor == PortionType::Lay; } + bool IsParaPortion() const { return nWhichPor == PortionType::Para; } + bool IsMarginPortion() const { return nWhichPor == PortionType::Margin; } + bool IsFlyPortion() const { return nWhichPor == PortionType::Fly; } + bool IsHolePortion() const { return nWhichPor == PortionType::Hole; } + bool IsSoftHyphPortion() const { return nWhichPor == PortionType::SoftHyphen; } + bool IsPostItsPortion() const { return nWhichPor == PortionType::PostIts; } + bool IsCombinedPortion() const { return nWhichPor == PortionType::Combined; } + bool IsTextPortion() const { return nWhichPor == PortionType::Text; } + bool IsHangingPortion() const { return nWhichPor == PortionType::Hanging; } + bool IsKernPortion() const { return nWhichPor == PortionType::Kern; } + bool IsArrowPortion() const { return nWhichPor == PortionType::Arrow; } + bool IsMultiPortion() const { return nWhichPor == PortionType::Multi; } + bool IsNumberPortion() const { return nWhichPor == PortionType::Number; } // #i23726# + bool IsControlCharPortion() const { return nWhichPor == PortionType::ControlChar || nWhichPor == PortionType::Bookmark; } + + // Positioning + SwLinePortion *FindPrevPortion( const SwLinePortion *pRoot ); + SwLinePortion *FindLastPortion(); + + /// the parameter is actually SwTwips apparently? + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const; + void CalcTextSize( const SwTextSizeInfo &rInfo ); + + // Output + virtual void Paint( const SwTextPaintInfo &rInf ) const = 0; + void PrePaint( const SwTextPaintInfo &rInf, const SwLinePortion *pLast ) const; + + virtual bool Format( SwTextFormatInfo &rInf ); + // Is called for the line's last portion + virtual void FormatEOL( SwTextFormatInfo &rInf ); + void Move( SwTextPaintInfo &rInf ); + + // For SwTextSlot + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const; + + // For SwFieldPortion, SwSoftHyphPortion + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const; + + // for text- and multi-portions + virtual long CalcSpacing( long nSpaceAdd, const SwTextSizeInfo &rInf ) const; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const; + + bool GetJoinBorderWithPrev() const { return m_bJoinBorderWithPrev; } + bool GetJoinBorderWithNext() const { return m_bJoinBorderWithNext; } + void SetJoinBorderWithPrev( const bool bJoinPrev ) { m_bJoinBorderWithPrev = bJoinPrev; } + void SetJoinBorderWithNext( const bool bJoinNext ) { m_bJoinBorderWithNext = bJoinNext; } +}; + +inline SwLinePortion &SwLinePortion::operator=(const SwLinePortion &rPortion) +{ + *static_cast<SwPosSize*>(this) = rPortion; + nLineLength = rPortion.nLineLength; + nAscent = rPortion.nAscent; + nWhichPor = rPortion.nWhichPor; + m_bJoinBorderWithPrev = rPortion.m_bJoinBorderWithPrev; + m_bJoinBorderWithNext = rPortion.m_bJoinBorderWithNext; + return *this; +} + +inline SwLinePortion::SwLinePortion(const SwLinePortion &rPortion) : + SwPosSize( rPortion ), + mpNextPortion( nullptr ), + nLineLength( rPortion.nLineLength ), + nAscent( rPortion.nAscent ), + nWhichPor( rPortion.nWhichPor ), + m_bJoinBorderWithPrev( rPortion.m_bJoinBorderWithPrev ), + m_bJoinBorderWithNext( rPortion.m_bJoinBorderWithNext ) +{ +} + +inline void SwLinePortion::Truncate() +{ + if ( mpNextPortion ) + Truncate_(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx new file mode 100644 index 000000000..2154eba1d --- /dev/null +++ b/sw/source/core/text/pormulti.cxx @@ -0,0 +1,2569 @@ +/* -*- 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 <deque> +#include <memory> + +#include <hintids.hxx> + +#include <editeng/twolinesitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <vcl/outdev.hxx> +#include <txatbase.hxx> +#include <fmtruby.hxx> +#include <txtatr.hxx> +#include <charfmt.hxx> +#include <layfrm.hxx> +#include <SwPortionHandler.hxx> +#include "pormulti.hxx" +#include "inftxt.hxx" +#include "itrpaint.hxx" +#include <viewopt.hxx> +#include "itrform2.hxx" +#include "porfld.hxx" +#include "porglue.hxx" +#include "porrst.hxx" +#include <pagefrm.hxx> +#include <rowfrm.hxx> +#include <tgrditem.hxx> +#include <swtable.hxx> +#include <fmtfsize.hxx> +#include <doc.hxx> + +using namespace ::com::sun::star; + +// A SwMultiPortion is not a simple portion, +// it's a container, which contains almost a SwLineLayoutPortion. +// This SwLineLayout could be followed by other textportions via pPortion +// and by another SwLineLayout via pNext to realize a doubleline portion. +SwMultiPortion::~SwMultiPortion() +{ +} + +void SwMultiPortion::Paint( const SwTextPaintInfo & ) const +{ + OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" ); +} + +// Summarize the internal lines to calculate the (external) size. +// The internal line has to calculate first. +void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf ) +{ + Width( 0 ); + Height( 0 ); + SetAscent( 0 ); + SetFlyInContent( false ); + SwLineLayout *pLay = &GetRoot(); + do + { + pLay->CalcLine( rLine, rInf ); + if( rLine.IsFlyInCntBase() ) + SetFlyInContent( true ); + if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) ) + { + // An empty phonetic line don't need an ascent or a height. + if( !pLay->Width() ) + { + pLay->SetAscent( 0 ); + pLay->Height( 0 ); + } + if( OnTop() ) + SetAscent( GetAscent() + pLay->Height() ); + } + else + SetAscent( GetAscent() + pLay->GetAscent() ); + + // Increase the line height, except for ruby text on the right. + if ( !IsRuby() || !OnRight() || pLay == &GetRoot() ) + Height( Height() + pLay->Height() ); + else + { + // We already added the width after building the portion, + // so no need to add it twice. + break; + } + + if( Width() < pLay->Width() ) + Width( pLay->Width() ); + pLay = pLay->GetNext(); + } while ( pLay ); + if( HasBrackets() ) + { + sal_uInt16 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nHeight; + if( nTmp > Height() ) + { + const sal_uInt16 nAdd = ( nTmp - Height() ) / 2; + GetRoot().SetAscent( GetRoot().GetAscent() + nAdd ); + GetRoot().Height( GetRoot().Height() + nAdd ); + Height( nTmp ); + } + nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nAscent; + if( nTmp > GetAscent() ) + SetAscent( nTmp ); + } +} + +long SwMultiPortion::CalcSpacing( long , const SwTextSizeInfo & ) const +{ + return 0; +} + +bool SwMultiPortion::ChgSpaceAdd( SwLineLayout*, long ) const +{ + return false; +} + +void SwMultiPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Text( GetLen(), GetWhichPor() ); +} + +// sets the tabulator-flag, if there's any tabulator-portion inside. +void SwMultiPortion::ActualizeTabulator() +{ + SwLinePortion* pPor = GetRoot().GetFirstPortion(); + // First line + for( m_bTab1 = m_bTab2 = false; pPor; pPor = pPor->GetNextPortion() ) + if( pPor->InTabGrp() ) + SetTab1( true ); + if( GetRoot().GetNext() ) + { + // Second line + pPor = GetRoot().GetNext()->GetFirstPortion(); + do + { + if( pPor->InTabGrp() ) + SetTab2( true ); + pPor = pPor->GetNextPortion(); + } while ( pPor ); + } +} + +SwRotatedPortion::SwRotatedPortion( const SwMultiCreator& rCreate, + TextFrameIndex const nEnd, bool bRTL ) + : SwMultiPortion( nEnd ) +{ + const SvxCharRotateItem* pRot = static_cast<const SvxCharRotateItem*>(rCreate.pItem); + if( !pRot ) + { + const SwTextAttr& rAttr = *rCreate.pAttr; + const SfxPoolItem *const pItem = + CharFormat::GetItem(rAttr, RES_CHRATR_ROTATE); + if ( pItem ) + { + pRot = static_cast<const SvxCharRotateItem*>(pItem); + } + } + if( pRot ) + { + sal_uInt8 nDir; + if ( bRTL ) + nDir = pRot->IsBottomToTop() ? 3 : 1; + else + nDir = pRot->IsBottomToTop() ? 1 : 3; + + SetDirection( nDir ); + } +} + +SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv) + : SwMultiPortion( nEnd ), nLevel( nLv ) +{ + SetBidi(); + + if ( nLevel % 2 ) + SetDirection( DIR_RIGHT2LEFT ); + else + SetDirection( DIR_LEFT2RIGHT ); +} + +long SwBidiPortion::CalcSpacing( long nSpaceAdd, const SwTextSizeInfo& rInf ) const +{ + return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR; +} + +bool SwBidiPortion::ChgSpaceAdd( SwLineLayout* pCurr, long nSpaceAdd ) const +{ + if( !HasTabulator() && nSpaceAdd > 0 && !pCurr->IsSpaceAdd() ) + { + pCurr->CreateSpaceAdd(); + pCurr->SetLLSpaceAdd( nSpaceAdd, 0 ); + return true; + } + + return false; +} + +TextFrameIndex SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo &rInf) const +{ + // Calculate number of blanks for justified alignment + TextFrameIndex nTmpStart = rInf.GetIdx(); + TextFrameIndex nNull(0); + TextFrameIndex nBlanks(0); + + for (SwLinePortion* pPor = GetRoot().GetFirstPortion(); pPor; pPor = pPor->GetNextPortion()) + { + if( pPor->InTextGrp() ) + nBlanks = nBlanks + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull ); + else if ( pPor->IsMultiPortion() && + static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + nBlanks = nBlanks + static_cast<SwBidiPortion*>(pPor)->GetSpaceCnt( rInf ); + + const_cast<SwTextSizeInfo &>(rInf).SetIdx( rInf.GetIdx() + pPor->GetLen() ); + } + const_cast<SwTextSizeInfo &>(rInf).SetIdx( nTmpStart ); + return nBlanks; +} + +// This constructor is for the continuation of a doubleline portion +// in the next line. +// It takes the same brackets and if the original has no content except +// brackets, these will be deleted. +SwDoubleLinePortion::SwDoubleLinePortion( + SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd) + : SwMultiPortion(nEnd) + , nLineDiff(0) + , nBlank1(0) + , nBlank2(0) +{ + SetDirection( rDouble.GetDirection() ); + SetDouble(); + if( rDouble.GetBrackets() ) + { + SetBrackets( rDouble ); + // An empty multiportion needs no brackets. + // Notice: GetLen() might be zero, if the multiportion contains + // the second part of a field and the width might be zero, if + // it contains a note only. In this cases the brackets are okay. + // But if the length and the width are both zero, the portion + // is really empty. + if( rDouble.Width() == rDouble.BracketWidth() ) + rDouble.ClearBrackets(); + } +} + +// This constructor uses the textattribute to get the right brackets. +// The textattribute could be a 2-line-attribute or a character- or +// internet style, which contains the 2-line-attribute. +SwDoubleLinePortion::SwDoubleLinePortion( + const SwMultiCreator& rCreate, TextFrameIndex const nEnd) + : SwMultiPortion(nEnd) + , pBracket(new SwBracket) + , nLineDiff(0) + , nBlank1(0) + , nBlank2(0) +{ + pBracket->nAscent = 0; + pBracket->nHeight = 0; + pBracket->nPreWidth = 0; + pBracket->nPostWidth = 0; + + SetDouble(); + const SvxTwoLinesItem* pTwo = static_cast<const SvxTwoLinesItem*>(rCreate.pItem); + if( pTwo ) + pBracket->nStart = TextFrameIndex(0); + else + { + const SwTextAttr& rAttr = *rCreate.pAttr; + pBracket->nStart = rCreate.nStartOfAttr; + + const SfxPoolItem * const pItem = + CharFormat::GetItem( rAttr, RES_CHRATR_TWO_LINES ); + if ( pItem ) + { + pTwo = static_cast<const SvxTwoLinesItem*>(pItem); + } + } + if( pTwo ) + { + pBracket->cPre = pTwo->GetStartBracket(); + pBracket->cPost = pTwo->GetEndBracket(); + } + else + { + pBracket->cPre = 0; + pBracket->cPost = 0; + } + SwFontScript nTmp = SW_SCRIPTS; + if( pBracket->cPre > 255 ) + { + OUString aText(pBracket->cPre); + nTmp = SwScriptInfo::WhichFont(0, aText); + } + pBracket->nPreScript = nTmp; + nTmp = SW_SCRIPTS; + if( pBracket->cPost > 255 ) + { + OUString aText(pBracket->cPost); + nTmp = SwScriptInfo::WhichFont(0, aText); + } + pBracket->nPostScript = nTmp; + + if( !pBracket->cPre && !pBracket->cPost ) + { + pBracket.reset(); + } + + // double line portions have the same direction as the frame directions + if ( rCreate.nLevel % 2 ) + SetDirection( DIR_RIGHT2LEFT ); + else + SetDirection( DIR_LEFT2RIGHT ); +} + +// paints the wished bracket, +// if the multiportion has surrounding brackets. +// The X-position of the SwTextPaintInfo will be modified: +// the open bracket sets position behind itself, +// the close bracket in front of itself. +void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf, + long nSpaceAdd, + bool bOpen ) const +{ + sal_Unicode cCh = bOpen ? pBracket->cPre : pBracket->cPost; + if( !cCh ) + return; + const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth(); + if( !nChWidth ) + return; + if( !bOpen ) + rInf.X( rInf.X() + Width() - PostWidth() + + ( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) ); + + SwBlankPortion aBlank( cCh, true ); + aBlank.SetAscent( pBracket->nAscent ); + aBlank.Width( nChWidth ); + aBlank.Height( pBracket->nHeight ); + { + std::unique_ptr<SwFont> pTmpFnt( new SwFont( *rInf.GetFont() ) ); + SwFontScript nAct = bOpen ? pBracket->nPreScript : pBracket->nPostScript; + if( SW_SCRIPTS > nAct ) + pTmpFnt->SetActual( nAct ); + pTmpFnt->SetProportion( 100 ); + SwFontSave aSave( rInf, pTmpFnt.get() ); + aBlank.Paint( rInf ); + } + if( bOpen ) + rInf.X( rInf.X() + PreWidth() ); +} + +// creates the bracket-structure +// and fills it, if not both characters are 0x00. +void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble ) +{ + if( rDouble.pBracket ) + { + pBracket.reset( new SwBracket ); + pBracket->cPre = rDouble.pBracket->cPre; + pBracket->cPost = rDouble.pBracket->cPost; + pBracket->nPreScript = rDouble.pBracket->nPreScript; + pBracket->nPostScript = rDouble.pBracket->nPostScript; + pBracket->nStart = rDouble.pBracket->nStart; + } +} + +// calculates the size of the brackets => pBracket, +// reduces the nMaxWidth-parameter ( minus bracket-width ) +// and moves the rInf-x-position behind the opening bracket. +void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth ) +{ + nMaxWidth -= rInf.X(); + std::unique_ptr<SwFont> pTmpFnt( new SwFont( *rInf.GetFont() ) ); + pTmpFnt->SetProportion( 100 ); + pBracket->nAscent = 0; + pBracket->nHeight = 0; + if( pBracket->cPre ) + { + OUString aStr( pBracket->cPre ); + SwFontScript nActualScr = pTmpFnt->GetActual(); + if( SW_SCRIPTS > pBracket->nPreScript ) + pTmpFnt->SetActual( pBracket->nPreScript ); + SwFontSave aSave( rInf, pTmpFnt.get() ); + SwPosSize aSize = rInf.GetTextSize( aStr ); + pBracket->nAscent = rInf.GetAscent(); + pBracket->nHeight = aSize.Height(); + pTmpFnt->SetActual( nActualScr ); + if( nMaxWidth > aSize.Width() ) + { + pBracket->nPreWidth = aSize.Width(); + nMaxWidth -= aSize.Width(); + rInf.X( rInf.X() + aSize.Width() ); + } + else + { + pBracket->nPreWidth = 0; + nMaxWidth = 0; + } + } + else + pBracket->nPreWidth = 0; + if( pBracket->cPost ) + { + OUString aStr( pBracket->cPost ); + if( SW_SCRIPTS > pBracket->nPostScript ) + pTmpFnt->SetActual( pBracket->nPostScript ); + SwFontSave aSave( rInf, pTmpFnt.get() ); + SwPosSize aSize = rInf.GetTextSize( aStr ); + const sal_uInt16 nTmpAsc = rInf.GetAscent(); + if( nTmpAsc > pBracket->nAscent ) + { + pBracket->nHeight += nTmpAsc - pBracket->nAscent; + pBracket->nAscent = nTmpAsc; + } + if( aSize.Height() > pBracket->nHeight ) + pBracket->nHeight = aSize.Height(); + if( nMaxWidth > aSize.Width() ) + { + pBracket->nPostWidth = aSize.Width(); + nMaxWidth -= aSize.Width(); + } + else + { + pBracket->nPostWidth = 0; + nMaxWidth = 0; + } + } + else + pBracket->nPostWidth = 0; + nMaxWidth += rInf.X(); +} + +// calculates the number of blanks in each line and +// the difference of the width of the two lines. +// These results are used from the text adjustment. +void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf ) +{ + SwLinePortion* pPor = GetRoot().GetFirstPortion(); + TextFrameIndex nNull(0); + TextFrameIndex nStart = rInf.GetIdx(); + SetTab1( false ); + SetTab2( false ); + for (nBlank1 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion()) + { + if( pPor->InTextGrp() ) + nBlank1 = nBlank1 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull ); + rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() ); + if( pPor->InTabGrp() ) + SetTab1( true ); + } + nLineDiff = GetRoot().Width(); + if( GetRoot().GetNext() ) + { + pPor = GetRoot().GetNext()->GetFirstPortion(); + nLineDiff -= GetRoot().GetNext()->Width(); + } + for (nBlank2 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion()) + { + if( pPor->InTextGrp() ) + nBlank2 = nBlank2 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull ); + rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() ); + if( pPor->InTabGrp() ) + SetTab2( true ); + } + rInf.SetIdx( nStart ); +} + +long SwDoubleLinePortion::CalcSpacing( long nSpaceAdd, const SwTextSizeInfo & ) const +{ + return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR; +} + +// Merges the spaces for text adjustment from the inner and outer part. +// Inside the doubleline portion the wider line has no spaceadd-array, the +// smaller line has such an array to reach width of the wider line. +// If the surrounding line has text adjustment and the doubleline portion +// contains no tabulator, it is necessary to create/manipulate the inner +// space arrays. +bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr, + long nSpaceAdd ) const +{ + bool bRet = false; + if( !HasTabulator() && nSpaceAdd > 0 ) + { + if( !pCurr->IsSpaceAdd() ) + { + // The wider line gets the spaceadd from the surrounding line direct + pCurr->CreateSpaceAdd(); + pCurr->SetLLSpaceAdd( nSpaceAdd, 0 ); + bRet = true; + } + else + { + sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt()); + sal_Int32 const nOther = sal_Int32(GetSpaceCnt()); + SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd; + + if( nMyBlank ) + nMultiSpace /= sal_Int32(nMyBlank); + +// pCurr->SetLLSpaceAdd( nMultiSpace, 0 ); + // #i65711# SetLLSpaceAdd replaces the first value, + // instead we want to insert a new first value: + std::vector<long>* pVec = pCurr->GetpLLSpaceAdd(); + pVec->insert( pVec->begin(), nMultiSpace ); + bRet = true; + } + } + return bRet; +} +// cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..) +void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout* pCurr ) +{ + pCurr->RemoveFirstLLSpaceAdd(); + if( !pCurr->GetLLSpaceAddCount() ) + pCurr->FinishSpaceAdd(); +} + +SwDoubleLinePortion::~SwDoubleLinePortion() +{ +} + +// constructs a ruby portion, i.e. an additional text is displayed +// beside the main text, e.g. phonetic characters. +SwRubyPortion::SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex const nEnd) + : SwMultiPortion( nEnd ) + , nRubyOffset( rRuby.GetRubyOffset() ) + , nAdjustment( rRuby.GetAdjustment() ) +{ + SetDirection( rRuby.GetDirection() ); + SetRubyPosition( rRuby.GetRubyPosition() ); + SetRuby(); +} + +// constructs a ruby portion, i.e. an additional text is displayed +// beside the main text, e.g. phonetic characters. +SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt, + const IDocumentSettingAccess& rIDocumentSettingAccess, + TextFrameIndex const nEnd, TextFrameIndex const nOffs, + const SwTextSizeInfo &rInf ) + : SwMultiPortion( nEnd ) +{ + SetRuby(); + OSL_ENSURE( SwMultiCreatorId::Ruby == rCreate.nId, "Ruby expected" ); + OSL_ENSURE( RES_TXTATR_CJK_RUBY == rCreate.pAttr->Which(), "Wrong attribute" ); + const SwFormatRuby& rRuby = rCreate.pAttr->GetRuby(); + nAdjustment = rRuby.GetAdjustment(); + nRubyOffset = nOffs; + + const SwTextFrame *pFrame = rInf.GetTextFrame(); + RubyPosition ePos = static_cast<RubyPosition>( rRuby.GetPosition() ); + + // RIGHT is designed for horizontal writing mode only. + if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() ) + ePos = RubyPosition::ABOVE; + + // In grid mode we force the ruby text to the upper or lower line + if ( rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) ); + if ( pGrid ) + ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE; + } + + SetRubyPosition( ePos ); + + const SwCharFormat *const pFormat = + static_txtattr_cast<SwTextRuby const*>(rCreate.pAttr)->GetCharFormat(); + std::unique_ptr<SwFont> pRubyFont; + if( pFormat ) + { + const SwAttrSet& rSet = pFormat->GetAttrSet(); + pRubyFont.reset(new SwFont( rFnt )); + pRubyFont->SetDiffFnt( &rSet, &rIDocumentSettingAccess ); + + // we do not allow a vertical font for the ruby text + pRubyFont->SetVertical( rFnt.GetOrientation() , OnRight() ); + } + + OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) ); + SwFieldPortion *pField = new SwFieldPortion( aStr, std::move(pRubyFont) ); + pField->SetNextOffset( nOffs ); + pField->SetFollow( true ); + + if( OnTop() ) + GetRoot().SetNextPortion( pField ); + else + { + GetRoot().SetNext( new SwLineLayout() ); + GetRoot().GetNext()->SetNextPortion( pField ); + } + + // ruby portions have the same direction as the frame directions + if ( rCreate.nLevel % 2 ) + { + // switch right and left ruby adjustment in rtl environment + if ( css::text::RubyAdjust_LEFT == nAdjustment ) + nAdjustment = css::text::RubyAdjust_RIGHT; + else if ( css::text::RubyAdjust_RIGHT == nAdjustment ) + nAdjustment = css::text::RubyAdjust_LEFT; + + SetDirection( DIR_RIGHT2LEFT ); + } + else + SetDirection( DIR_LEFT2RIGHT ); +} + +// In ruby portion there are different alignments for +// the ruby text and the main text. +// Left, right, centered and two possibilities of block adjustment +// The block adjustment is realized by spacing between the characters, +// either with a half space or no space in front of the first letter and +// a half space at the end of the last letter. +// Notice: the smaller line will be manipulated, normally it's the ruby line, +// but it could be the main text, too. +// If there is a tabulator in smaller line, no adjustment is possible. +void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf ) +{ + SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width(); + TextFrameIndex const nOldIdx = rInf.GetIdx(); + if( !nLineDiff ) + return; + SwLineLayout *pCurr; + if( nLineDiff < 0 ) + { // The first line has to be adjusted. + if( GetTab1() ) + return; + pCurr = &GetRoot(); + nLineDiff = -nLineDiff; + } + else + { // The second line has to be adjusted. + if( GetTab2() ) + return; + pCurr = GetRoot().GetNext(); + rInf.SetIdx( nOldIdx + GetRoot().GetLen() ); + } + sal_uInt16 nLeft = 0; // the space in front of the first letter + sal_uInt16 nRight = 0; // the space at the end of the last letter + TextFrameIndex nSub(0); + switch ( nAdjustment ) + { + case css::text::RubyAdjust_CENTER: nRight = static_cast<sal_uInt16>(nLineDiff / 2); + [[fallthrough]]; + case css::text::RubyAdjust_RIGHT: nLeft = static_cast<sal_uInt16>(nLineDiff - nRight); break; + case css::text::RubyAdjust_BLOCK: nSub = TextFrameIndex(1); + [[fallthrough]]; + case css::text::RubyAdjust_INDENT_BLOCK: + { + TextFrameIndex nCharCnt(0); + SwLinePortion *pPor; + for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() ) + { + if( pPor->InTextGrp() ) + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt ); + rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() ); + } + if( nCharCnt > nSub ) + { + SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub); + short nTmp; + if( nCalc < SHRT_MAX ) + nTmp = -short(nCalc); + else + nTmp = SHRT_MIN; + + pCurr->CreateSpaceAdd( SPACING_PRECISION_FACTOR * nTmp ); + nLineDiff -= nCalc * (sal_Int32(nCharCnt) - 1); + } + if( nLineDiff > 1 ) + { + nRight = static_cast<sal_uInt16>(nLineDiff / 2); + nLeft = static_cast<sal_uInt16>(nLineDiff - nRight); + } + break; + } + default: OSL_FAIL( "New ruby adjustment" ); + } + if( nLeft || nRight ) + { + if( !pCurr->GetNextPortion() ) + pCurr->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr)); + if( nLeft ) + { + SwMarginPortion *pMarg = new SwMarginPortion; + pMarg->AddPrtWidth( nLeft ); + pMarg->SetNextPortion( pCurr->GetNextPortion() ); + pCurr->SetNextPortion( pMarg ); + } + if( nRight ) + { + SwMarginPortion *pMarg = new SwMarginPortion; + pMarg->AddPrtWidth( nRight ); + pCurr->FindLastPortion()->Append( pMarg ); + } + } + + pCurr->Width( Width() ); + rInf.SetIdx( nOldIdx ); +} + +// has to change the nRubyOffset, if there's a fieldportion +// in the phonetic line. +// The nRubyOffset is the position in the rubystring, where the +// next SwRubyPortion has start the displaying of the phonetics. +void SwRubyPortion::CalcRubyOffset() +{ + const SwLineLayout *pCurr = &GetRoot(); + if( !OnTop() ) + { + pCurr = pCurr->GetNext(); + if( !pCurr ) + return; + } + const SwLinePortion *pPor = pCurr->GetFirstPortion(); + const SwFieldPortion *pField = nullptr; + while( pPor ) + { + if( pPor->InFieldGrp() ) + pField = static_cast<const SwFieldPortion*>(pPor); + pPor = pPor->GetNextPortion(); + } + if( pField ) + { + if( pField->HasFollow() ) + nRubyOffset = pField->GetNextOffset(); + else + nRubyOffset = TextFrameIndex(COMPLETE_STRING); + } +} + +// A little helper function for GetMultiCreator(..) +// It extracts the 2-line-format from a 2-line-attribute or a character style. +// The rValue is set to true, if the 2-line-attribute's value is set and +// no 2-line-format reference is passed. If there is a 2-line-format reference, +// then the rValue is set only, if the 2-line-attribute's value is set _and_ +// the 2-line-formats has the same brackets. +static bool lcl_Check2Lines(const SfxPoolItem *const pItem, + const SvxTwoLinesItem* &rpRef, bool &rValue) +{ + if( pItem ) + { + rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue(); + if( !rpRef ) + rpRef = static_cast<const SvxTwoLinesItem*>(pItem); + else if( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() != + rpRef->GetEndBracket() || + static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() != + rpRef->GetStartBracket() ) + rValue = false; + return true; + } + return false; +} + +static bool lcl_Has2Lines(const SwTextAttr& rAttr, + const SvxTwoLinesItem* &rpRef, bool &rValue) +{ + const SfxPoolItem* pItem = CharFormat::GetItem(rAttr, RES_CHRATR_TWO_LINES); + return lcl_Check2Lines(pItem, rpRef, rValue); +} + +// is a little help function for GetMultiCreator(..) +// It extracts the charrotation from a charrotate-attribute or a character style. +// The rValue is set to true, if the charrotate-attribute's value is set and +// no charrotate-format reference is passed. +// If there is a charrotate-format reference, then the rValue is set only, +// if the charrotate-attribute's value is set _and_ identical +// to the charrotate-format's value. +static bool lcl_CheckRotation(const SfxPoolItem *const pItem, + const SvxCharRotateItem* &rpRef, bool &rValue) +{ + if ( pItem ) + { + rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue(); + if( !rpRef ) + rpRef = static_cast<const SvxCharRotateItem*>(pItem); + else if( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() != + rpRef->GetValue() ) + rValue = false; + return true; + } + + return false; +} + +static bool lcl_HasRotation(const SwTextAttr& rAttr, + const SvxCharRotateItem* &rpRef, bool &rValue) +{ + const SfxPoolItem* pItem = CharFormat::GetItem( rAttr, RES_CHRATR_ROTATE ); + return lcl_CheckRotation(pItem, rpRef, rValue); +} + +namespace sw { + namespace { + + // need to use a very special attribute iterator here that returns + // both the hints and the nodes, so that GetMultiCreator() can handle + // items in the nodes' set properly + class MergedAttrIterMulti + : public MergedAttrIterBase + { + private: + bool m_First = true; + public: + MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {} + SwTextAttr const* NextAttr(SwTextNode const*& rpNode); + // can't have operator= because m_pMerged/m_pNode const + void Assign(MergedAttrIterMulti const& rOther) + { + assert(m_pMerged == rOther.m_pMerged); + assert(m_pNode == rOther.m_pNode); + m_CurrentExtent = rOther.m_CurrentExtent; + m_CurrentHint = rOther.m_CurrentHint; + m_First = rOther.m_First; + } + }; + + } + + SwTextAttr const* MergedAttrIterMulti::NextAttr(SwTextNode const*& rpNode) + { + if (m_First) + { + m_First = false; + rpNode = m_pMerged + ? !m_pMerged->extents.empty() + ? m_pMerged->extents[0].pNode + : m_pMerged->pFirstNode + : m_pNode; + return nullptr; + } + if (m_pMerged) + { + while (m_CurrentExtent < m_pMerged->extents.size()) + { + sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]); + if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints()) + { + while (m_CurrentHint < pHints->Count()) + { + SwTextAttr const*const pHint(pHints->Get(m_CurrentHint)); + if (rExtent.nEnd < pHint->GetStart()) + { + break; + } + ++m_CurrentHint; + if (rExtent.nStart <= pHint->GetStart()) + { + rpNode = rExtent.pNode; + return pHint; + } + } + } + ++m_CurrentExtent; + if (m_CurrentExtent < m_pMerged->extents.size() && + rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode) + { + m_CurrentHint = 0; // reset + rpNode = m_pMerged->extents[m_CurrentExtent].pNode; + return nullptr; + } + } + return nullptr; + } + else + { + SwpHints const*const pHints(m_pNode->GetpSwpHints()); + if (pHints) + { + if (m_CurrentHint < pHints->Count()) + { + SwTextAttr const*const pHint(pHints->Get(m_CurrentHint)); + ++m_CurrentHint; + rpNode = m_pNode; + return pHint; + } + } + return nullptr; + } + } +} + +// If we (e.g. the position rPos) are inside a two-line-attribute or +// a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct, +// otherwise the function returns zero. +// The rPos parameter is set to the end of the multiportion, +// normally this is the end of the attribute, +// but sometimes it is the start of another attribute, which finished or +// interrupts the first attribute. +// E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute +// with different brackets interrupts another 2-line-attribute. +std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos, + SwMultiPortion const * pMulti ) const +{ + SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); + + // get the last embedding level + sal_uInt8 nCurrLevel; + if ( pMulti ) + { + OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" ); + // level associated with bidi-portion; + nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel(); + } + else + // no nested bidi portion required + nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; + + // check if there is a field at rPos: + sal_uInt8 nNextLevel = nCurrLevel; + bool bFieldBidi = false; + + if (rPos < TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD == GetChar(rPos)) + { + bFieldBidi = true; + } + else + nNextLevel = rSI.DirType( rPos ); + + if (TextFrameIndex(GetText().getLength()) != rPos && nNextLevel > nCurrLevel) + { + rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel); + if (TextFrameIndex(COMPLETE_STRING) == rPos) + return nullptr; + std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); + pRet->pItem = nullptr; + pRet->pAttr = nullptr; + pRet->nStartOfAttr = TextFrameIndex(-1); + pRet->nId = SwMultiCreatorId::Bidi; + pRet->nLevel = nCurrLevel + 1; + return pRet; + } + + // a bidi portion can only contain other bidi portions + if ( pMulti ) + return nullptr; + + // need the node that contains input rPos + std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos)); + const SvxCharRotateItem* pActiveRotateItem(nullptr); + const SfxPoolItem* pNodeRotateItem(nullptr); + const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr); + const SfxPoolItem* pNodeTwoLinesItem(nullptr); + SwTextAttr const* pActiveTwoLinesHint(nullptr); + SwTextAttr const* pActiveRotateHint(nullptr); + const SwTextAttr *pRuby = nullptr; + sw::MergedAttrIterMulti iterAtStartOfNode(*m_pFrame); + bool bTwo = false; + bool bRot = false; + + for (sw::MergedAttrIterMulti iter = *m_pFrame; ; ) + { + SwTextNode const* pNode(nullptr); + SwTextAttr const*const pAttr = iter.NextAttr(pNode); + if (!pNode) + { + break; + } + if (pAttr) + { + assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier + if (startPos.first->GetIndex() <= pNode->GetIndex()) + { + if (startPos.first->GetIndex() != pNode->GetIndex() + || startPos.second < pAttr->GetStart()) + { + break; + } + if (startPos.second < pAttr->GetAnyEnd()) + { + // sw_redlinehide: ruby *always* splits + if (RES_TXTATR_CJK_RUBY == pAttr->Which()) + pRuby = pAttr; + else + { + const SvxCharRotateItem* pRoTmp = nullptr; + if (lcl_HasRotation( *pAttr, pRoTmp, bRot )) + { + pActiveRotateHint = bRot ? pAttr : nullptr; + pActiveRotateItem = pRoTmp; + } + const SvxTwoLinesItem* p2Tmp = nullptr; + if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo )) + { + pActiveTwoLinesHint = bTwo ? pAttr : nullptr; + pActiveTwoLinesItem = p2Tmp; + } + } + } + } + } + else if (pNode) // !pAttr && pNode means the node changed + { + if (startPos.first->GetIndex() < pNode->GetIndex()) + { + break; // only one node initially + } + if (startPos.first->GetIndex() == pNode->GetIndex()) + { + iterAtStartOfNode.Assign(iter); + if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( + RES_CHRATR_ROTATE, true, &pNodeRotateItem) && + static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue()) + { + pActiveRotateItem = static_cast<const SvxCharRotateItem*>(pNodeRotateItem); + } + else + { + pNodeRotateItem = nullptr; + } + if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState( + RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) && + static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetValue()) + { + pActiveTwoLinesItem = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem); + } + else + { + pNodeTwoLinesItem = nullptr; + } + } + } + } + if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem) + return nullptr; + + if( pRuby ) + { // The winner is ... a ruby attribute and so + // the end of the multiportion is the end of the ruby attribute. + rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End()); + std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); + pRet->pItem = nullptr; + pRet->pAttr = pRuby; + pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart()); + pRet->nId = SwMultiCreatorId::Ruby; + pRet->nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; + return pRet; + } + if (pActiveTwoLinesHint || + (pNodeTwoLinesItem && pNodeTwoLinesItem == pActiveTwoLinesItem && + rPos < TextFrameIndex(GetText().getLength()))) + { // The winner is a 2-line-attribute, + // the end of the multiportion depends on the following attributes... + std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); + + // We note the endpositions of the 2-line attributes in aEnd as stack + std::deque<TextFrameIndex> aEnd; + + // The bOn flag signs the state of the last 2-line attribute in the + // aEnd-stack, it is compatible with the winner-attribute or + // it interrupts the other attribute. + bool bOn = true; + + if (pActiveTwoLinesHint) + { + pRet->pItem = nullptr; + pRet->pAttr = pActiveTwoLinesHint; + pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart()); + if (pNodeTwoLinesItem) + { + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len())); + bOn = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetEndBracket() == + pActiveTwoLinesItem->GetEndBracket() && + static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetStartBracket() == + pActiveTwoLinesItem->GetStartBracket(); + } + else + { + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *pRet->pAttr->End())); + } + } + else + { + pRet->pItem = pNodeTwoLinesItem; + pRet->pAttr = nullptr; + pRet->nStartOfAttr = TextFrameIndex(-1); + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len())); + } + pRet->nId = SwMultiCreatorId::Double; + pRet->nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; + + // pActiveTwoLinesHint is the last 2-line-attribute, which contains + // the actual position. + + // At this moment we know that at position rPos the "winner"-attribute + // causes a 2-line-portion. The end of the attribute is the end of the + // portion, if there's no interrupting attribute. + // There are two kinds of interrupters: + // - ruby attributes stops the 2-line-attribute, the end of the + // multiline is the start of the ruby attribute + // - 2-line-attributes with value "Off" or with different brackets, + // these attributes may interrupt the winner, but they could be + // neutralized by another 2-line-attribute starting at the same + // position with the same brackets as the winner-attribute. + + // In the following loop rPos is the critical position and it will be + // evaluated, if at rPos starts an interrupting or a maintaining + // continuity attribute. + + // iterAtStartOfNode is positioned to the first hint of the node + // (if any); the node item itself has already been handled above + for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; ) + { + SwTextNode const* pNode(nullptr); + SwTextAttr const*const pTmp = iter.NextAttr(pNode); + if (!pNode) + { + break; + } + assert(startPos.first->GetIndex() <= pNode->GetIndex()); + TextFrameIndex nTmpStart; + TextFrameIndex nTmpEnd; + if (pTmp) + { + nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd()); + if (nTmpEnd <= rPos) + continue; + nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart()); + } + else + { + pNodeTwoLinesItem = nullptr; + pNode->GetSwAttrSet().GetItemState( + RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem); + nTmpStart = m_pFrame->MapModelToView(pNode, 0); + nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len()); + assert(rPos <= nTmpEnd); // next node must not have smaller index + } + + if (rPos < nTmpStart) + { + // If bOn is false and the next attribute starts later than rPos + // the winner attribute is interrupted at rPos. + // If the start of the next attribute is behind the end of + // the last attribute on the aEnd-stack, this is the endposition + // on the stack is the end of the 2-line portion. + if (!bOn || aEnd.back() < nTmpStart) + break; + // At this moment, bOn is true and the next attribute starts + // behind rPos, so we could move rPos to the next startpoint + rPos = nTmpStart; + // We clean up the aEnd-stack, endpositions equal to rPos are + // superfluous. + while( !aEnd.empty() && aEnd.back() <= rPos ) + { + bOn = !bOn; + aEnd.pop_back(); + } + // If the endstack is empty, we simulate an attribute with + // state true and endposition rPos + if( aEnd.empty() ) + { + aEnd.push_front( rPos ); + bOn = true; + } + } + // A ruby attribute stops the 2-line immediately + if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which()) + return pRet; + if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo) + : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo)) + { // We have an interesting attribute... + if( bTwo == bOn ) + { // .. with the same state, so the last attribute could + // be continued. + if (aEnd.back() < nTmpEnd) + aEnd.back() = nTmpEnd; + } + else + { // .. with a different state. + bOn = bTwo; + // If this is smaller than the last on the stack, we put + // it on the stack. If it has the same endposition, the last + // could be removed. + if (nTmpEnd < aEnd.back()) + aEnd.push_back( nTmpEnd ); + else if( aEnd.size() > 1 ) + aEnd.pop_back(); + else + aEnd.back() = nTmpEnd; + } + } + } + if( bOn && !aEnd.empty() ) + rPos = aEnd.back(); + return pRet; + } + if (pActiveRotateHint || + (pNodeRotateItem && pNodeRotateItem == pActiveRotateItem && + rPos < TextFrameIndex(GetText().getLength()))) + { // The winner is a rotate-attribute, + // the end of the multiportion depends on the following attributes... + std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); + pRet->nId = SwMultiCreatorId::Rotate; + + // We note the endpositions of the 2-line attributes in aEnd as stack + std::deque<TextFrameIndex> aEnd; + + // The bOn flag signs the state of the last 2-line attribute in the + // aEnd-stack, which could interrupts the winning rotation attribute. + bool bOn = pNodeTwoLinesItem != nullptr; + aEnd.push_front(TextFrameIndex(GetText().getLength())); + + // first, search for the start position of the next TWOLINE portion + // because the ROTATE portion must end there at the latest + TextFrameIndex n2Start = rPos; + for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; ) + { + SwTextNode const* pNode(nullptr); + SwTextAttr const*const pTmp = iter.NextAttr(pNode); + if (!pNode) + { + break; + } + assert(startPos.first->GetIndex() <= pNode->GetIndex()); + TextFrameIndex nTmpStart; + TextFrameIndex nTmpEnd; + if (pTmp) + { + nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd()); + if (nTmpEnd <= n2Start) + continue; + nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart()); + } + else + { + pNodeTwoLinesItem = nullptr; + pNode->GetSwAttrSet().GetItemState( + RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem); + nTmpStart = m_pFrame->MapModelToView(pNode, 0); + nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len()); + assert(n2Start <= nTmpEnd); // next node must not have smaller index + } + + if (n2Start < nTmpStart) + { + if (bOn || aEnd.back() < nTmpStart) + break; + n2Start = nTmpStart; + while( !aEnd.empty() && aEnd.back() <= n2Start ) + { + bOn = !bOn; + aEnd.pop_back(); + } + if( aEnd.empty() ) + { + aEnd.push_front( n2Start ); + bOn = false; + } + } + // A ruby attribute stops immediately + if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which()) + { + bOn = true; + break; + } + const SvxTwoLinesItem* p2Lines = nullptr; + if (pTmp ? lcl_Has2Lines(*pTmp, p2Lines, bTwo) + : lcl_Check2Lines(pNodeTwoLinesItem, p2Lines, bTwo)) + { + if( bTwo == bOn ) + { + if (aEnd.back() < nTmpEnd) + aEnd.back() = nTmpEnd; + } + else + { + bOn = bTwo; + if (nTmpEnd < aEnd.back()) + aEnd.push_back( nTmpEnd ); + else if( aEnd.size() > 1 ) + aEnd.pop_back(); + else + aEnd.back() = nTmpEnd; + } + } + } + if( !bOn && !aEnd.empty() ) + n2Start = aEnd.back(); + + aEnd.clear(); + + // now, search for the end of the ROTATE portion, similar to above + bOn = true; + if (pActiveRotateHint) + { + pRet->pItem = nullptr; + pRet->pAttr = pActiveRotateHint; + pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart()); + if (pNodeRotateItem) + { + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len())); + bOn = static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue() == + pActiveRotateItem->GetValue(); + } + else + { + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *pRet->pAttr->End())); + } + } + else + { + pRet->pItem = pNodeRotateItem; + pRet->pAttr = nullptr; + pRet->nStartOfAttr = TextFrameIndex(-1); + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len())); + } + for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; ) + { + SwTextNode const* pNode(nullptr); + SwTextAttr const*const pTmp = iter.NextAttr(pNode); + if (!pNode) + { + break; + } + assert(startPos.first->GetIndex() <= pNode->GetIndex()); + TextFrameIndex nTmpStart; + TextFrameIndex nTmpEnd; + if (pTmp) + { + nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd()); + if (nTmpEnd <= rPos) + continue; + nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart()); + } + else + { + pNodeRotateItem = nullptr; + pNode->GetSwAttrSet().GetItemState( + RES_CHRATR_ROTATE, true, &pNodeRotateItem); + nTmpStart = m_pFrame->MapModelToView(pNode, 0); + nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len()); + assert(rPos <= nTmpEnd); // next node must not have smaller index + } + + if (rPos < nTmpStart) + { + if (!bOn || aEnd.back() < nTmpStart) + break; + rPos = nTmpStart; + while( !aEnd.empty() && aEnd.back() <= rPos ) + { + bOn = !bOn; + aEnd.pop_back(); + } + if( aEnd.empty() ) + { + aEnd.push_front( rPos ); + bOn = true; + } + } + if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which()) + { + bOn = false; + break; + } + // TODO why does this use bTwo, not bRot ??? + if (pTmp ? lcl_HasRotation(*pTmp, pActiveRotateItem, bTwo) + : lcl_CheckRotation(pNodeRotateItem, pActiveRotateItem, bTwo)) + { + if( bTwo == bOn ) + { + if (aEnd.back() < nTmpEnd) + aEnd.back() = nTmpEnd; + } + else + { + bOn = bTwo; + if (nTmpEnd < aEnd.back()) + aEnd.push_back( nTmpEnd ); + else if( aEnd.size() > 1 ) + aEnd.pop_back(); + else + aEnd.back() = nTmpEnd; + } + } + } + if( bOn && !aEnd.empty() ) + rPos = aEnd.back(); + if( rPos > n2Start ) + rPos = n2Start; + return pRet; + } + return nullptr; +} + +namespace { + +// A little helper class to manage the spaceadd-arrays of the text adjustment +// during a PaintMultiPortion. +// The constructor prepares the array for the first line of multiportion, +// the SecondLine-function restores the values for the first line and prepares +// the second line. +// The destructor restores the values of the last manipulation. +class SwSpaceManipulator +{ + SwTextPaintInfo& rInfo; + SwMultiPortion& rMulti; + std::vector<long>* pOldSpaceAdd; + sal_uInt16 nOldSpIdx; + long nSpaceAdd; + bool bSpaceChg; + sal_uInt8 nOldDir; +public: + SwSpaceManipulator( SwTextPaintInfo& rInf, SwMultiPortion& rMult ); + ~SwSpaceManipulator(); + void SecondLine(); + long GetSpaceAdd() const { return nSpaceAdd; } +}; + +} + +SwSpaceManipulator::SwSpaceManipulator( SwTextPaintInfo& rInf, + SwMultiPortion& rMult ) + : rInfo(rInf) + , rMulti(rMult) + , nSpaceAdd(0) +{ + pOldSpaceAdd = rInfo.GetpSpaceAdd(); + nOldSpIdx = rInfo.GetSpaceIdx(); + nOldDir = rInfo.GetDirection(); + rInfo.SetDirection( rMulti.GetDirection() ); + bSpaceChg = false; + + if( rMulti.IsDouble() ) + { + nSpaceAdd = ( pOldSpaceAdd && !rMulti.HasTabulator() ) ? + rInfo.GetSpaceAdd() : 0; + if( rMulti.GetRoot().IsSpaceAdd() ) + { + rInfo.SetpSpaceAdd( rMulti.GetRoot().GetpLLSpaceAdd() ); + rInfo.ResetSpaceIdx(); + bSpaceChg = rMulti.ChgSpaceAdd( &rMulti.GetRoot(), nSpaceAdd ); + } + else if( rMulti.HasTabulator() ) + rInfo.SetpSpaceAdd( nullptr ); + } + else if ( ! rMulti.IsBidi() ) + { + rInfo.SetpSpaceAdd( rMulti.GetRoot().GetpLLSpaceAdd() ); + rInfo.ResetSpaceIdx(); + } +} + +void SwSpaceManipulator::SecondLine() +{ + if( bSpaceChg ) + { + rInfo.RemoveFirstSpaceAdd(); + bSpaceChg = false; + } + SwLineLayout *pLay = rMulti.GetRoot().GetNext(); + if( pLay->IsSpaceAdd() ) + { + rInfo.SetpSpaceAdd( pLay->GetpLLSpaceAdd() ); + rInfo.ResetSpaceIdx(); + bSpaceChg = rMulti.ChgSpaceAdd( pLay, nSpaceAdd ); + } + else + { + rInfo.SetpSpaceAdd( (!rMulti.IsDouble() || rMulti.HasTabulator() ) ? + nullptr : pOldSpaceAdd ); + rInfo.SetSpaceIdx( nOldSpIdx); + } +} + +SwSpaceManipulator::~SwSpaceManipulator() +{ + if( bSpaceChg ) + { + rInfo.RemoveFirstSpaceAdd(); + bSpaceChg = false; + } + rInfo.SetpSpaceAdd( pOldSpaceAdd ); + rInfo.SetSpaceIdx( nOldSpIdx); + rInfo.SetDirection( nOldDir ); +} + +// Manages the paint for a SwMultiPortion. +// External, for the calling function, it seems to be a normal Paint-function, +// internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines +void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, + SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor ) +{ + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + const bool bHasGrid = pGrid && GetInfo().SnapToGrid(); + sal_uInt16 nRubyHeight = 0; + bool bRubyTop = true; + + if ( bHasGrid && pGrid->IsSquaredMode() ) + { + nRubyHeight = pGrid->GetRubyHeight(); + bRubyTop = ! pGrid->GetRubyTextBelow(); + } + + // do not allow grid mode for first line in ruby portion + const bool bRubyInGrid = bHasGrid && rMulti.IsRuby(); + + const sal_uInt16 nOldHeight = rMulti.Height(); + const bool bOldGridModeAllowed = GetInfo().SnapToGrid(); + + if ( bRubyInGrid ) + { + GetInfo().SetSnapToGrid( ! bRubyTop ); + if (pGrid->IsSquaredMode()) + rMulti.Height( m_pCurr->Height() ); + } + + SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() ); + bool bEnvDir = false; + bool bThisDir = false; + bool bFrameDir = false; + if ( rMulti.IsBidi() ) + { + // these values are needed for the calculation of the x coordinate + // and the layout mode + OSL_ENSURE( ! pEnvPor || pEnvPor->IsBidi(), + "Oh no, I expected a BidiPortion" ); + bFrameDir = GetInfo().GetTextFrame()->IsRightToLeft(); + bEnvDir = pEnvPor ? ((static_cast<const SwBidiPortion*>(pEnvPor)->GetLevel() % 2) != 0) : bFrameDir; + bThisDir = (static_cast<SwBidiPortion&>(rMulti).GetLevel() % 2) != 0; + } + +#if OSL_DEBUG_LEVEL > 1 + // only paint first level bidi portions + if( rMulti.Width() > 1 && ! pEnvPor ) + GetInfo().DrawViewOpt( rMulti, PortionType::Field ); +#endif + + if ( bRubyInGrid && pGrid->IsSquaredMode() ) + rMulti.Height( nOldHeight ); + + // do we have to repaint a post it portion? + if( GetInfo().OnWin() && rMulti.GetNextPortion() && + ! rMulti.GetNextPortion()->Width() ) + rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti ); + + // old values must be saved and restored at the end + TextFrameIndex const nOldLen = GetInfo().GetLen(); + const SwTwips nOldX = GetInfo().X(); + const SwTwips nOldY = GetInfo().Y(); + TextFrameIndex const nOldIdx = GetInfo().GetIdx(); + + SwSpaceManipulator aManip( GetInfo(), rMulti ); + + std::unique_ptr<SwFontSave> pFontSave; + std::unique_ptr<SwFont> pTmpFnt; + + if( rMulti.IsDouble() ) + { + pTmpFnt.reset(new SwFont( *GetInfo().GetFont() )); + if( rMulti.IsDouble() ) + { + SetPropFont( 50 ); + pTmpFnt->SetProportion( GetPropFont() ); + } + pFontSave.reset(new SwFontSave( GetInfo(), pTmpFnt.get(), this )); + } + else + { + pFontSave = nullptr; + pTmpFnt = nullptr; + } + + if( rMulti.HasBrackets() ) + { + TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx(); + GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart); + SeekAndChg( GetInfo() ); + static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), 0, true ); + GetInfo().SetIdx( nTmpOldIdx ); + } + + const SwTwips nTmpX = GetInfo().X(); + + SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion + SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line + SwTwips nOfst = 0; + + // GetInfo().Y() is the baseline from the surrounding line. We must switch + // this temporary to the baseline of the inner lines of the multiportion. + if( rMulti.HasRotation() ) + { + if( rMulti.IsRevers() ) + { + GetInfo().Y( nOldY - rMulti.GetAscent() ); + nOfst = nTmpX + rMulti.Width(); + } + else + { + GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() ); + nOfst = nTmpX; + } + } + else if ( rMulti.IsBidi() ) + { + // does the current bidi portion has the same direction + // as its environment? + if ( bEnvDir != bThisDir ) + { + // different directions, we have to adjust the x coordinate + SwTwips nMultiWidth = rMulti.Width() + + rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() ); + + if ( bFrameDir == bThisDir ) + GetInfo().X( GetInfo().X() - nMultiWidth ); + else + GetInfo().X( GetInfo().X() + nMultiWidth ); + } + + nOfst = nOldY - rMulti.GetAscent(); + + // set layout mode + aLayoutModeModifier.Modify( bThisDir ); + } + else + nOfst = nOldY - rMulti.GetAscent(); + + bool bRest = pLay->IsRest(); + bool bFirst = true; + + OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(), + " Only BiDi portions are allowed to use the common underlining font" ); + + if ( rMulti.IsRuby() ) + GetInfo().SetRuby( rMulti.OnTop() ); + + do + { + if ( bHasGrid && pGrid->IsSquaredMode() ) + { + if( rMulti.HasRotation() ) + { + const sal_uInt16 nAdjustment = ( pLay->Height() - pPor->Height() ) / 2 + + pPor->GetAscent(); + if( rMulti.IsRevers() ) + GetInfo().X( nOfst - nAdjustment ); + else + GetInfo().X( nOfst + nAdjustment ); + } + else + { + // special treatment for ruby portions in grid mode + SwTwips nAdjustment = 0; + if ( rMulti.IsRuby() ) + { + if ( bRubyTop != ( pLay == &rMulti.GetRoot() ) ) + // adjust base text + nAdjustment = ( m_pCurr->Height() - nRubyHeight - pPor->Height() ) / 2; + else if ( bRubyTop ) + // adjust upper ruby text + nAdjustment = nRubyHeight - pPor->Height(); + // else adjust lower ruby text + } + + GetInfo().Y( nOfst + nAdjustment + pPor->GetAscent() ); + } + } + else if( rMulti.HasRotation() ) + { + if( rMulti.IsRevers() ) + GetInfo().X( nOfst - AdjustBaseLine( *pLay, pPor, 0, 0, true ) ); + else + GetInfo().X( nOfst + AdjustBaseLine( *pLay, pPor ) ); + } + else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() ) + { + SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, 0 ); + GetInfo().Y( nOfst + nLineDiff ); + // Draw the ruby text on top of the preserved space. + GetInfo().X( GetInfo().X() - pPor->Height() ); + } + else + GetInfo().Y( nOfst + AdjustBaseLine( *pLay, pPor ) ); + + bool bSeeked = true; + GetInfo().SetLen( pPor->GetLen() ); + + if( bRest && pPor->InFieldGrp() && !pPor->GetLen() ) + { + if( static_cast<SwFieldPortion*>(pPor)->HasFont() ) + bSeeked = false; + else + SeekAndChgBefore( GetInfo() ); + } + else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() ) + SeekAndChg( GetInfo() ); + else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() ) + { + if( GetRedln() ) + SeekAndChg( GetInfo() ); + else + SeekAndChgBefore( GetInfo() ); + } + else + bSeeked = false; + + SwLinePortion *pNext = pPor->GetNextPortion(); + if(GetInfo().OnWin() && pNext && !pNext->Width() ) + { + if ( !bSeeked ) + SeekAndChg( GetInfo() ); + pNext->PrePaint( GetInfo(), pPor ); + } + + CheckSpecialUnderline( pPor ); + SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt(); + if ( pUnderLineFnt ) + { + if ( rMulti.IsDouble() ) + pUnderLineFnt->GetFont().SetProportion( 50 ); + pUnderLineFnt->SetPos( GetInfo().GetPos() ); + } + + if ( rMulti.IsBidi() ) + { + // we do not allow any rotation inside a bidi portion + SwFont* pTmpFont = GetInfo().GetFont(); + pTmpFont->SetVertical( 0, GetInfo().GetTextFrame()->IsVertical() ); + } + + if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) + { + // but we do allow nested bidi portions + OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" ); + PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti ); + } + else + pPor->Paint( GetInfo() ); + + bFirst &= !pPor->GetLen(); + if( pNext || !pPor->IsMarginPortion() ) + pPor->Move( GetInfo() ); + + pPor = pNext; + + // If there's no portion left, we go to the next line + if( !pPor && pLay->GetNext() ) + { + pLay = pLay->GetNext(); + pPor = pLay->GetFirstPortion(); + bRest = pLay->IsRest(); + aManip.SecondLine(); + + // delete underline font + delete GetInfo().GetUnderFnt(); + GetInfo().SetUnderFnt( nullptr ); + + if( rMulti.HasRotation() ) + { + if( rMulti.IsRevers() ) + { + nOfst += pLay->Height(); + GetInfo().Y( nOldY - rMulti.GetAscent() ); + } + else + { + nOfst -= pLay->Height(); + GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() ); + } + } + else if ( bHasGrid && rMulti.IsRuby() ) + { + GetInfo().SetSnapToGrid( bRubyTop ); + GetInfo().X( nTmpX ); + if (pGrid->IsSquaredMode() ) + { + if ( bRubyTop ) + nOfst += nRubyHeight; + else + nOfst += m_pCurr->Height() - nRubyHeight; + } + else + { + nOfst += rMulti.GetRoot().Height(); + } + } + else if ( rMulti.IsRuby() && rMulti.OnRight() ) + { + GetInfo().SetDirection( DIR_TOP2BOTTOM ); + GetInfo().SetRuby( true ); + } else + { + GetInfo().X( nTmpX ); + // We switch to the baseline of the next inner line + nOfst += rMulti.GetRoot().Height(); + } + } + } while( pPor ); + + if ( bRubyInGrid ) + GetInfo().SetSnapToGrid( bOldGridModeAllowed ); + + // delete underline font + if ( ! rMulti.IsBidi() ) + { + delete GetInfo().GetUnderFnt(); + GetInfo().SetUnderFnt( nullptr ); + } + + GetInfo().SetIdx( nOldIdx ); + GetInfo().Y( nOldY ); + + if( rMulti.HasBrackets() ) + { + TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx(); + GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart); + SeekAndChg( GetInfo() ); + GetInfo().X( nOldX ); + static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), + aManip.GetSpaceAdd(), false ); + GetInfo().SetIdx( nTmpOldIdx ); + } + // Restore the saved values + GetInfo().X( nOldX ); + GetInfo().SetLen( nOldLen ); + pFontSave.reset(); + pTmpFnt.reset(); + SetPropFont( 0 ); +} + +static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField ) +{ + SwLinePortion* pLast = pLine; + rpField = pLine->GetNextPortion(); + while( rpField && !rpField->InFieldGrp() ) + { + pLast = rpField; + rpField = rpField->GetNextPortion(); + } + bool bRet = rpField != nullptr; + if( bRet ) + { + if( static_cast<SwFieldPortion*>(rpField)->IsFollow() ) + { + rpField->Truncate(); + pLast->SetNextPortion( nullptr ); + } + else + rpField = nullptr; + } + pLine->Truncate(); + return bRet; +} + +// If a multi portion completely has to go to the +// next line, this function is called to truncate +// the rest of the remaining multi portion +static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf, + TextFrameIndex const nStartIdx) +{ + rMulti.GetRoot().Truncate(); + rMulti.GetRoot().SetLen(TextFrameIndex(0)); + rMulti.GetRoot().Width(0); +// rMulti.CalcSize( *this, aInf ); + if ( rMulti.GetRoot().GetNext() ) + { + rMulti.GetRoot().GetNext()->Truncate(); + rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0)); + rMulti.GetRoot().GetNext()->Width( 0 ); + } + rMulti.Width( 0 ); + rMulti.SetLen(TextFrameIndex(0)); + rInf.SetIdx( nStartIdx ); +} + +// Manages the formatting of a SwMultiPortion. External, for the calling +// function, it seems to be a normal Format-function, internal it is like a +// SwTextFrame::Format_ with multiple BuildPortions +bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, + SwMultiPortion& rMulti ) +{ + SwTwips nMaxWidth = rInf.Width(); + SwTwips nOldX = 0; + + if( rMulti.HasBrackets() ) + { + TextFrameIndex const nOldIdx = rInf.GetIdx(); + rInf.SetIdx( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart ); + SeekAndChg( rInf ); + nOldX = GetInfo().X(); + static_cast<SwDoubleLinePortion&>(rMulti).FormatBrackets( rInf, nMaxWidth ); + rInf.SetIdx( nOldIdx ); + } + + SeekAndChg( rInf ); + std::unique_ptr<SwFontSave> xFontSave; + if( rMulti.IsDouble() ) + { + SwFont* pTmpFnt = new SwFont( *rInf.GetFont() ); + if( rMulti.IsDouble() ) + { + SetPropFont( 50 ); + pTmpFnt->SetProportion( GetPropFont() ); + } + xFontSave.reset(new SwFontSave(rInf, pTmpFnt, this)); + } + + SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() ); + if ( rMulti.IsBidi() ) + { + // set layout mode + aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() ); + } + + SwTwips nTmpX = 0; + + if( rMulti.HasRotation() ) + { + // For nMaxWidth we take the height of the body frame. + // #i25067#: If the current frame is inside a table, we restrict + // nMaxWidth to the current frame height, unless the frame size + // attribute is set to variable size: + + // We set nTmpX (which is used for portion calculating) to the + // current Y value + const SwPageFrame* pPage = m_pFrame->FindPageFrame(); + OSL_ENSURE( pPage, "No page in frame!"); + const SwLayoutFrame* pUpperFrame = pPage; + + if ( m_pFrame->IsInTab() ) + { + pUpperFrame = m_pFrame->GetUpper(); + while ( pUpperFrame && !pUpperFrame->IsCellFrame() ) + pUpperFrame = pUpperFrame->GetUpper(); + assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame + if (!pUpperFrame) + return false; + const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine(); + const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize(); + if ( SwFrameSize::Variable == rFrameFormatSize.GetHeightSizeType() ) + pUpperFrame = pPage; + } + if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() ) + pUpperFrame = pPage->FindBodyCont(); + + nMaxWidth = pUpperFrame ? + ( rInf.GetTextFrame()->IsVertical() ? + pUpperFrame->getFramePrintArea().Width() : + pUpperFrame->getFramePrintArea().Height() ) : + USHRT_MAX; + } + else + nTmpX = rInf.X(); + + SwMultiPortion* pOldMulti = m_pMulti; + + m_pMulti = &rMulti; + SwLineLayout *pOldCurr = m_pCurr; + TextFrameIndex const nOldStart = GetStart(); + SwTwips nMinWidth = nTmpX + 1; + SwTwips nActWidth = nMaxWidth; + const TextFrameIndex nStartIdx = rInf.GetIdx(); + TextFrameIndex nMultiLen = rMulti.GetLen(); + + SwLinePortion *pFirstRest; + SwLinePortion *pSecondRest; + if( rMulti.IsFormatted() ) + { + if( !lcl_ExtractFieldFollow( &rMulti.GetRoot(), pFirstRest ) + && rMulti.IsDouble() && rMulti.GetRoot().GetNext() ) + lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pFirstRest ); + if( !rMulti.IsDouble() && rMulti.GetRoot().GetNext() ) + lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pSecondRest ); + else + pSecondRest = nullptr; + } + else + { + pFirstRest = rMulti.GetRoot().GetNextPortion(); + pSecondRest = rMulti.GetRoot().GetNext() ? + rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr; + if( pFirstRest ) + rMulti.GetRoot().SetNextPortion( nullptr ); + if( pSecondRest ) + rMulti.GetRoot().GetNext()->SetNextPortion( nullptr ); + rMulti.SetFormatted(); + nMultiLen = nMultiLen - rInf.GetIdx(); + } + + // save some values + const OUString* pOldText = &(rInf.GetText()); + const SwTwips nOldPaintOfst = rInf.GetPaintOfst(); + std::shared_ptr<vcl::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData()); + rInf.SetCachedVclData(nullptr); + + OUString const aMultiStr( rInf.GetText().copy(0, sal_Int32(nMultiLen + rInf.GetIdx())) ); + rInf.SetText( aMultiStr ); + SwTextFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth ); + // Do we allow break cuts? The FirstMulti-Flag is evaluated during + // line break determination. + bool bFirstMulti = rInf.GetIdx() != rInf.GetLineStart(); + + SwLinePortion *pNextFirst = nullptr; + SwLinePortion *pNextSecond = nullptr; + bool bRet = false; + + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + const bool bHasGrid = pGrid && GRID_LINES_CHARS == pGrid->GetGridType(); + + bool bRubyTop = false; + + if ( bHasGrid ) + bRubyTop = ! pGrid->GetRubyTextBelow(); + + do + { + m_pCurr = &rMulti.GetRoot(); + m_nStart = nStartIdx; + bRet = false; + FormatReset( aInf ); + aInf.X( nTmpX ); + aInf.Width( sal_uInt16(nActWidth) ); + aInf.RealWidth( sal_uInt16(nActWidth) ); + aInf.SetFirstMulti( bFirstMulti ); + aInf.SetNumDone( rInf.IsNumDone() ); + aInf.SetFootnoteDone( rInf.IsFootnoteDone() ); + + // if there's a bookmark at the start of the MultiPortion, it will be + // painted with the rotation etc. of the MultiPortion; move it *inside* + // so it gets positioned correctly; currently there's no other portion + // inserted between the end of WhichFirstPortion() and + // BuildMultiPortion() + if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark) + { + auto const pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast())); + auto *const pPrevious = pBookmark->FindPrevPortion(rInf.GetRoot()); + assert(!pPrevious || pPrevious->GetNextPortion() == pBookmark); + if (pPrevious) + { + pPrevious->SetNextPortion(nullptr); + } + rInf.SetLast(pPrevious); + assert(m_pCurr->GetNextPortion() == nullptr); + m_pCurr->SetNextPortion(pBookmark); + } + + if( pFirstRest ) + { + OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected"); + SwFieldPortion *pField = + static_cast<SwFieldPortion*>(pFirstRest)->Clone( + static_cast<SwFieldPortion*>(pFirstRest)->GetExp() ); + pField->SetFollow( true ); + aInf.SetRest( pField ); + } + aInf.SetRuby( rMulti.IsRuby() && rMulti.OnTop() ); + + // in grid mode we temporarily have to disable the grid for the ruby line + const bool bOldGridModeAllowed = GetInfo().SnapToGrid(); + if ( bHasGrid && aInf.IsRuby() && bRubyTop ) + aInf.SetSnapToGrid( false ); + + // If there's no more rubytext, then buildportion is forbidden + if( pFirstRest || !aInf.IsRuby() ) + BuildPortions( aInf ); + + aInf.SetSnapToGrid( bOldGridModeAllowed ); + + rMulti.CalcSize( *this, aInf ); + m_pCurr->SetRealHeight( m_pCurr->Height() ); + + if( rMulti.IsBidi() ) + { + pNextFirst = aInf.GetRest(); + break; + } + + if( rMulti.HasRotation() && !rMulti.IsDouble() ) + break; + // second line has to be formatted + else if( m_pCurr->GetLen()<nMultiLen || rMulti.IsRuby() || aInf.GetRest()) + { + TextFrameIndex const nFirstLen = m_pCurr->GetLen(); + delete m_pCurr->GetNext(); + m_pCurr->SetNext( new SwLineLayout() ); + m_pCurr = m_pCurr->GetNext(); + m_nStart = aInf.GetIdx(); + aInf.X( nTmpX ); + SwTextFormatInfo aTmp( aInf, *m_pCurr, nActWidth ); + if( rMulti.IsRuby() ) + { + aTmp.SetRuby( !rMulti.OnTop() ); + pNextFirst = aInf.GetRest(); + if( pSecondRest ) + { + OSL_ENSURE( pSecondRest->InFieldGrp(), "Fieldrest expected"); + SwFieldPortion *pField = static_cast<SwFieldPortion*>(pSecondRest)->Clone( + static_cast<SwFieldPortion*>(pSecondRest)->GetExp() ); + pField->SetFollow( true ); + aTmp.SetRest( pField ); + } + if( !rMulti.OnTop() && nFirstLen < nMultiLen ) + bRet = true; + } + else + aTmp.SetRest( aInf.GetRest() ); + aInf.SetRest( nullptr ); + + // in grid mode we temporarily have to disable the grid for the ruby line + if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop ) + aTmp.SetSnapToGrid( false ); + + BuildPortions( aTmp ); + + const SwLinePortion *pRightPortion = rMulti.OnRight() ? + rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr; + if (pRightPortion) + { + // The ruby text on the right is vertical. + // The width and the height are swapped. + SwTwips nHeight = pRightPortion->Height(); + // Keep room for the ruby text. + rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight ); + } + + aTmp.SetSnapToGrid( bOldGridModeAllowed ); + + rMulti.CalcSize( *this, aInf ); + rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() ); + m_pCurr->SetRealHeight( m_pCurr->Height() ); + if( rMulti.IsRuby() ) + { + pNextSecond = aTmp.GetRest(); + if( pNextFirst ) + bRet = true; + } + else + pNextFirst = aTmp.GetRest(); + if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen ) + || aTmp.GetRest() ) + // our guess for width of multiportion was too small, + // text did not fit into multiportion + bRet = true; + } + if( rMulti.IsRuby() ) + break; + if( bRet ) + { + // our guess for multiportion width was too small, + // we set min to act + nMinWidth = nActWidth; + nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4; + if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() ) + // we have too less space, we must allow break cuts + // ( the first multi flag is considered during TextPortion::Format_() ) + bFirstMulti = false; + if( nActWidth <= nMinWidth ) + break; + } + else + { + // For Solaris, this optimization can causes trouble: + // Setting this to the portion width ( = rMulti.Width() ) + // can make GetTextBreak inside SwTextGuess::Guess return too small + // values. Therefore we add some extra twips. + if( nActWidth > nTmpX + rMulti.Width() + 6 ) + nActWidth = nTmpX + rMulti.Width() + 6; + nMaxWidth = nActWidth; + nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4; + if( nActWidth >= nMaxWidth ) + break; + // we do not allow break cuts during formatting + bFirstMulti = true; + } + delete pNextFirst; + pNextFirst = nullptr; + } while ( true ); + + m_pMulti = pOldMulti; + + m_pCurr = pOldCurr; + m_nStart = nOldStart; + SetPropFont( 0 ); + + rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ? + rMulti.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) ); + + if( rMulti.IsDouble() ) + { + static_cast<SwDoubleLinePortion&>(rMulti).CalcBlanks( rInf ); + if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() ) + { + SwLineLayout* pLine = &rMulti.GetRoot(); + if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() > 0 ) + { + rInf.SetIdx( nStartIdx + pLine->GetLen() ); + pLine = pLine->GetNext(); + } + if( pLine ) + { + GetInfo().SetMulti( true ); + + // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification. + // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic. + // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters, + // kashida justification is accomplished by elongating characters at certain chosen points. + // Kashida justification can be combined with white-space justification to various extents. + // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false. + // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code. + CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block ); + + GetInfo().SetMulti( false ); + } + rInf.SetIdx( nStartIdx ); + } + if( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets() ) + { + rMulti.Width( rMulti.Width() + + static_cast<SwDoubleLinePortion&>(rMulti).BracketWidth() ); + GetInfo().X( nOldX ); + } + } + else + { + rMulti.ActualizeTabulator(); + if( rMulti.IsRuby() ) + { + static_cast<SwRubyPortion&>(rMulti).Adjust( rInf ); + static_cast<SwRubyPortion&>(rMulti).CalcRubyOffset(); + } + } + if( rMulti.HasRotation() ) + { + SwTwips nH = rMulti.Width(); + SwTwips nAsc = rMulti.GetAscent() + ( nH - rMulti.Height() )/2; + if( nAsc > nH ) + nAsc = nH; + else if( nAsc < 0 ) + nAsc = 0; + rMulti.Width( rMulti.Height() ); + rMulti.Height( sal_uInt16(nH) ); + rMulti.SetAscent( sal_uInt16(nAsc) ); + bRet = ( rInf.GetPos().X() + rMulti.Width() > rInf.Width() ) && + nStartIdx != rInf.GetLineStart(); + } + else if ( rMulti.IsBidi() ) + { + bRet = rMulti.GetLen() < nMultiLen || pNextFirst; + } + + // line break has to be performed! + if( bRet ) + { + OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(), + "BuildMultiPortion: Surprising restportion, field expected" ); + SwMultiPortion *pTmp; + if( rMulti.IsDouble() ) + pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti), + nMultiLen + rInf.GetIdx() ); + else if( rMulti.IsRuby() ) + { + OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(), + "BuildMultiPortion: Surprising restportion, field expected" ); + + if ( rInf.GetIdx() == rInf.GetLineStart() ) + { + // the ruby portion has to be split in two portions + pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti), + nMultiLen + rInf.GetIdx() ); + + if( pNextSecond ) + { + pTmp->GetRoot().SetNext( new SwLineLayout() ); + pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond ); + } + pTmp->SetFollowField(); + } + else + { + // we try to keep our ruby portion together + lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx ); + pTmp = nullptr; + } + } + else if( rMulti.HasRotation() ) + { + // we try to keep our rotated portion together + lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx ); + pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(), + rMulti.GetDirection() ); + } + // during a recursion of BuildMultiPortions we may not build + // a new SwBidiPortion, this would cause a memory leak + else if( rMulti.IsBidi() && ! m_pMulti ) + { + if ( ! rMulti.GetLen() ) + lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx ); + + // If there is a HolePortion at the end of the bidi portion, + // it has to be moved behind the bidi portion. Otherwise + // the visual cursor travelling gets into trouble. + SwLineLayout& aRoot = rMulti.GetRoot(); + SwLinePortion* pPor = aRoot.GetFirstPortion(); + while ( pPor ) + { + if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() ) + { + SwLinePortion* pHolePor = pPor->GetNextPortion(); + pPor->SetNextPortion( nullptr ); + aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() ); + rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() ); + rMulti.SetNextPortion( pHolePor ); + break; + } + pPor = pPor->GetNextPortion(); + } + + pTmp = new SwBidiPortion( nMultiLen + rInf.GetIdx(), + static_cast<SwBidiPortion&>(rMulti).GetLevel() ); + } + else + pTmp = nullptr; + + if ( ! rMulti.GetLen() && rInf.GetLast() ) + { + SeekAndChgBefore( rInf ); + rInf.GetLast()->FormatEOL( rInf ); + } + + if( pNextFirst && pTmp ) + { + pTmp->SetFollowField(); + pTmp->GetRoot().SetNextPortion( pNextFirst ); + } + else + // A follow field portion is still waiting. If nobody wants it, + // we delete it. + delete pNextFirst; + + rInf.SetRest( pTmp ); + } + + rInf.SetCachedVclData(pOldCachedVclData); + rInf.SetText( *pOldText ); + rInf.SetPaintOfst( nOldPaintOfst ); + rInf.SetStop( aInf.IsStop() ); + rInf.SetNumDone( true ); + rInf.SetFootnoteDone( true ); + SeekAndChg( rInf ); + delete pFirstRest; + delete pSecondRest; + xFontSave.reset(); + return bRet; +} + +// When a fieldportion at the end of line breaks and needs a following +// fieldportion in the next line, then the "restportion" of the formatinfo +// has to be set. Normally this happens during the formatting of the first +// part of the fieldportion. +// But sometimes the formatting starts at the line with the following part, +// especially when the following part is on the next page. +// In this case the MakeRestPortion-function has to create the following part. +// The first parameter is the line that contains possibly a first part +// of a field. When the function finds such field part, it creates the right +// restportion. This may be a multiportion, e.g. if the field is surrounded by +// a doubleline- or ruby-portion. +// The second parameter is the start index of the line. +SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine, + TextFrameIndex nPosition) +{ + if( !nPosition ) + return nullptr; + TextFrameIndex nMultiPos = nPosition - pLine->GetLen(); + const SwMultiPortion *pTmpMulti = nullptr; + const SwMultiPortion *pHelpMulti = nullptr; + const SwLinePortion* pPor = pLine->GetFirstPortion(); + SwFieldPortion *pField = nullptr; + while( pPor ) + { + if( pPor->GetLen() && !pHelpMulti ) + { + nMultiPos = nMultiPos + pPor->GetLen(); + pTmpMulti = nullptr; + } + if( pPor->InFieldGrp() ) + { + if( !pHelpMulti ) + pTmpMulti = nullptr; + pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor)); + } + else if( pPor->IsMultiPortion() ) + { + OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(), + "Nested multiportions are forbidden." ); + + pField = nullptr; + pTmpMulti = static_cast<const SwMultiPortion*>(pPor); + } + pPor = pPor->GetNextPortion(); + // If the last portion is a multi-portion, we enter it + // and look for a field portion inside. + // If we are already in a multiportion, we could change to the + // next line + if( !pPor && pTmpMulti ) + { + if( pHelpMulti ) + { // We're already inside the multiportion, let's take the second + // line, if we are in a double line portion + if( !pHelpMulti->IsRuby() ) + pPor = pHelpMulti->GetRoot().GetNext(); + pTmpMulti = nullptr; + } + else + { // Now we enter a multiportion, in a ruby portion we take the + // main line, not the phonetic line, in a doublelineportion we + // starts with the first line. + pHelpMulti = pTmpMulti; + nMultiPos = nMultiPos - pHelpMulti->GetLen(); + if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() ) + pPor = pHelpMulti->GetRoot().GetNext(); + else + pPor = pHelpMulti->GetRoot().GetFirstPortion(); + } + } + } + if( pField && !pField->HasFollow() ) + pField = nullptr; + + SwLinePortion *pRest = nullptr; + if( pField ) + { + const SwTextAttr *pHint = GetAttr(nPosition - TextFrameIndex(1)); + if ( pHint + && ( pHint->Which() == RES_TXTATR_FIELD + || pHint->Which() == RES_TXTATR_ANNOTATION ) ) + { + pRest = NewFieldPortion( GetInfo(), pHint ); + if( pRest->InFieldGrp() ) + static_cast<SwFieldPortion*>(pRest)->TakeNextOffset( pField ); + else + { + delete pRest; + pRest = nullptr; + } + } + } + if( !pHelpMulti ) + return pRest; + + nPosition = nMultiPos + pHelpMulti->GetLen(); + std::unique_ptr<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr ); + + if ( !pCreate ) + { + OSL_ENSURE( !pHelpMulti->GetLen(), "Multiportion without attribute?" ); + if ( nMultiPos ) + --nMultiPos; + pCreate = GetInfo().GetMultiCreator( --nMultiPos, nullptr ); + } + + if (!pCreate) + return pRest; + + if( pRest || nMultiPos > nPosition || ( pHelpMulti->IsRuby() && + static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset() < TextFrameIndex(COMPLETE_STRING))) + { + SwMultiPortion* pTmp; + if( pHelpMulti->IsDouble() ) + pTmp = new SwDoubleLinePortion( *pCreate, nMultiPos ); + else if( pHelpMulti->IsBidi() ) + pTmp = new SwBidiPortion( nMultiPos, pCreate->nLevel ); + else if( pHelpMulti->IsRuby() ) + { + pTmp = new SwRubyPortion( *pCreate, *GetInfo().GetFont(), + m_pFrame->GetDoc().getIDocumentSettingAccess(), + nMultiPos, static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset(), + GetInfo() ); + } + else if( pHelpMulti->HasRotation() ) + pTmp = new SwRotatedPortion( nMultiPos, pHelpMulti->GetDirection() ); + else + { + return pRest; + } + pCreate.reset(); + pTmp->SetFollowField(); + if( pRest ) + { + SwLineLayout *pLay = &pTmp->GetRoot(); + if( pTmp->IsRuby() && pTmp->OnTop() ) + { + pLay->SetNext( new SwLineLayout() ); + pLay = pLay->GetNext(); + } + pLay->SetNextPortion( pRest ); + } + return pTmp; + } + return pRest; +} + +// SwTextCursorSave notes the start and current line of a SwTextCursor, +// sets them to the values for GetModelPositionForViewPoint inside a multiportion +// and restores them in the destructor. +SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor, + SwMultiPortion* pMulti, + SwTwips nY, + sal_uInt16& nX, + TextFrameIndex const nCurrStart, + long nSpaceAdd ) + : pTextCursor(pCursor), + pCurr(pCursor->m_pCurr), + nStart(pCursor->m_nStart) +{ + pCursor->m_nStart = nCurrStart; + pCursor->m_pCurr = &pMulti->GetRoot(); + while( pCursor->Y() + pCursor->GetLineHeight() < nY && + pCursor->Next() ) + ; // nothing + nWidth = pCursor->m_pCurr->Width(); + nOldProp = pCursor->GetPropFont(); + + if ( pMulti->IsDouble() || pMulti->IsBidi() ) + { + bSpaceChg = pMulti->ChgSpaceAdd( pCursor->m_pCurr, nSpaceAdd ); + + TextFrameIndex nSpaceCnt; + if ( pMulti->IsDouble() ) + { + pCursor->SetPropFont( 50 ); + nSpaceCnt = static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt(); + } + else + { + TextFrameIndex const nOldIdx = pCursor->GetInfo().GetIdx(); + pCursor->GetInfo().SetIdx ( nCurrStart ); + nSpaceCnt = static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt(pCursor->GetInfo()); + pCursor->GetInfo().SetIdx ( nOldIdx ); + } + + if( nSpaceAdd > 0 && !pMulti->HasTabulator() ) + pCursor->m_pCurr->Width( static_cast<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) ); + + // For a BidiPortion we have to calculate the offset from the + // end of the portion + if ( nX && pMulti->IsBidi() ) + nX = pCursor->m_pCurr->Width() - nX; + } + else + bSpaceChg = false; +} + +SwTextCursorSave::~SwTextCursorSave() +{ + if( bSpaceChg ) + SwDoubleLinePortion::ResetSpaceAdd( pTextCursor->m_pCurr ); + pTextCursor->m_pCurr->Width( nWidth ); + pTextCursor->m_pCurr = pCurr; + pTextCursor->m_nStart = nStart; + pTextCursor->SetPropFont( nOldProp ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/pormulti.hxx b/sw/source/core/text/pormulti.hxx new file mode 100644 index 000000000..8a0d352df --- /dev/null +++ b/sw/source/core/text/pormulti.hxx @@ -0,0 +1,256 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORMULTI_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORMULTI_HXX + +#include <memory> +#include "porlay.hxx" +#include <com/sun/star/text/RubyAdjust.hpp> + +class IDocumentSettingAccess; +class SwTextFormatInfo; +class SwTextCursor; +class SwTextPaintInfo; +class SwTextAttr; +class SfxPoolItem; +class SwFont; + +// SwMultiCreator is a small structure to create a multiportion. +// It contains the kind of multiportion and a textattribute +// or a poolitem. +// The GetMultiCreator-function fills this structure and +// the Ctor of the SwMultiPortion uses it. +enum class SwMultiCreatorId +{ + Double, Ruby, Rotate, Bidi +}; + +enum class RubyPosition : sal_uInt16 +{ + ABOVE = 0, + BELOW = 1, + RIGHT = 2 +}; + +struct SwMultiCreator +{ + TextFrameIndex nStartOfAttr; + const SwTextAttr* pAttr; + const SfxPoolItem* pItem; + SwMultiCreatorId nId; + sal_uInt8 nLevel; +}; + +// A two-line-portion (SwMultiPortion) could have surrounding brackets, +// in this case the structure SwBracket will be used. +struct SwBracket +{ + TextFrameIndex nStart; // Start of text attribute determines the font + sal_uInt16 nAscent; // Ascent of the brackets + sal_uInt16 nHeight; // Height of them + sal_uInt16 nPreWidth; // Width of the opening bracket + sal_uInt16 nPostWidth; // Width of the closing bracket + sal_Unicode cPre; // Initial character, e.g. '(' + sal_Unicode cPost; // Final character, e.g. ')' + SwFontScript nPreScript; // Script of the initial character + SwFontScript nPostScript; // Script of the final character +}; + +// The SwMultiPortion is line portion inside a line portion, +// it's a group of portions, +// e.g. a double line portion in a line +// or phonetics (ruby) +// or combined characters +// or a rotated portion. +class SwMultiPortion : public SwLinePortion +{ + SwLineLayout m_aRoot; // One or more lines + bool m_bTab1 :1; // First line tabulator + bool m_bTab2 :1; // Second line includes tabulator + bool m_bDouble :1; // Double line + bool m_bRuby :1; // Phonetics + bool m_bBidi :1; + bool m_bFormatted :1; // Already formatted + bool m_bFollowField :1; // Field follow inside + bool m_bFlyInContent:1; // Fly as character inside + RubyPosition m_eRubyPosition; // Phonetic position + sal_uInt8 m_nDirection:2; // Direction (0/90/180/270 degrees) +protected: + explicit SwMultiPortion(TextFrameIndex const nEnd) + : m_bTab1(false) + , m_bTab2(false) + , m_bDouble(false) + , m_bRuby(false) + , m_bBidi(false) + , m_bFormatted(false) + , m_bFollowField(false) + , m_bFlyInContent(false) + , m_eRubyPosition( RubyPosition::ABOVE ) + , m_nDirection(0) + { + SetWhichPor(PortionType::Multi); + SetLen(nEnd); + } + void SetDouble() { m_bDouble = true; } + void SetRuby() { m_bRuby = true; } + void SetBidi() { m_bBidi = true; } + void SetRubyPosition( RubyPosition eNew ) { m_eRubyPosition = eNew; } + void SetTab1( bool bNew ) { m_bTab1 = bNew; } + void SetTab2( bool bNew ) { m_bTab2 = bNew; } + void SetDirection( sal_uInt8 nNew ) { m_nDirection = nNew; } + bool GetTab1() const { return m_bTab1; } + bool GetTab2() const { return m_bTab2; } +public: + virtual ~SwMultiPortion() override; + const SwLineLayout& GetRoot() const { return m_aRoot; } + SwLineLayout& GetRoot() { return m_aRoot; } + + bool HasTabulator() const { return m_bTab1 || m_bTab2; } + bool IsFormatted() const { return m_bFormatted; } + void SetFormatted() { m_bFormatted = true; } + bool IsFollowField() const { return m_bFollowField; } + void SetFollowField() { m_bFollowField = true; } + bool HasFlyInContent() const { return m_bFlyInContent; } + void SetFlyInContent( bool bNew ) { m_bFlyInContent = bNew; } + bool IsDouble() const { return m_bDouble; } + bool IsRuby() const { return m_bRuby; } + bool IsBidi() const { return m_bBidi; } + bool OnTop() const { return m_eRubyPosition == RubyPosition::ABOVE; } + bool OnRight() const { return m_eRubyPosition == RubyPosition::RIGHT; } + RubyPosition GetRubyPosition() const { return m_eRubyPosition; } + void ActualizeTabulator(); + + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual long CalcSpacing( long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; + virtual bool ChgSpaceAdd( SwLineLayout* pCurr, long nSpaceAdd ) const; + + // Summarize the internal lines to calculate the (external) size + void CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf ); + + inline bool HasBrackets() const; + bool HasRotation() const { return 0 != (1 & m_nDirection); } + bool IsRevers() const { return 0 != (2 & m_nDirection); } + sal_uInt8 GetDirection() const { return m_nDirection; } + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwDoubleLinePortion : public SwMultiPortion +{ + std::unique_ptr<SwBracket> pBracket; // Surrounding brackets + SwTwips nLineDiff; // Difference of the width of the both lines + TextFrameIndex nBlank1; ///< Number of blanks in the first line + TextFrameIndex nBlank2; ///< Number of blanks in the second line +public: + SwDoubleLinePortion(SwDoubleLinePortion& rDouble, TextFrameIndex nEnd); + SwDoubleLinePortion(const SwMultiCreator& rCreate, TextFrameIndex nEnd); + virtual ~SwDoubleLinePortion() override; + + SwBracket* GetBrackets() const { return pBracket.get(); } + void SetBrackets( const SwDoubleLinePortion& rDouble ); + void PaintBracket( SwTextPaintInfo& rInf, long nSpaceAdd, bool bOpen ) const; + void FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth ); + sal_uInt16 PreWidth() const { return pBracket->nPreWidth; }; + sal_uInt16 PostWidth() const { return pBracket->nPostWidth; } + void ClearBrackets() + { pBracket->nPreWidth = pBracket->nPostWidth=0; Width( 0 ); } + sal_uInt16 BracketWidth(){ return PreWidth() + PostWidth(); } + + void CalcBlanks( SwTextFormatInfo &rInf ); + static void ResetSpaceAdd( SwLineLayout* pCurr ); + SwTwips GetLineDiff() const { return nLineDiff; } + TextFrameIndex GetSpaceCnt() const + { return ( nLineDiff < 0 ) ? nBlank2 : nBlank1; } + TextFrameIndex GetSmallerSpaceCnt() const + { return ( nLineDiff < 0 ) ? nBlank1 : nBlank2; } + + virtual long CalcSpacing( long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; + virtual bool ChgSpaceAdd( SwLineLayout* pCurr, long nSpaceAdd ) const override; +}; + +class SwRubyPortion : public SwMultiPortion +{ + TextFrameIndex nRubyOffset; + css::text::RubyAdjust nAdjustment; + void Adjust_( SwTextFormatInfo &rInf); +public: + SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex nEnd); + + SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt, + const IDocumentSettingAccess& rIDocumentSettingAccess, + TextFrameIndex nEnd, TextFrameIndex nOffs, + const SwTextSizeInfo &rInf ); + + void CalcRubyOffset(); + void Adjust( SwTextFormatInfo &rInf ) + { if(nAdjustment != css::text::RubyAdjust_LEFT && GetRoot().GetNext()) Adjust_(rInf); } + css::text::RubyAdjust GetAdjustment() const { return nAdjustment; } + TextFrameIndex GetRubyOffset() const { return nRubyOffset; } +}; + +class SwRotatedPortion : public SwMultiPortion +{ +public: + SwRotatedPortion(TextFrameIndex const nEnd, sal_uInt8 nDir) + : SwMultiPortion( nEnd ) { SetDirection( nDir ); } + SwRotatedPortion( const SwMultiCreator& rCreate, TextFrameIndex nEnd, + bool bRTL ); +}; + +class SwBidiPortion : public SwMultiPortion +{ + sal_uInt8 nLevel; + +public: + SwBidiPortion(TextFrameIndex nEnd, sal_uInt8 nLv); + + sal_uInt8 GetLevel() const { return nLevel; } + // Get number of blanks for justified alignment + TextFrameIndex GetSpaceCnt(const SwTextSizeInfo &rInf) const; + // Calculates extra spacing based on number of blanks + virtual long CalcSpacing( long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; + // Manipulate the spacing array at pCurr + virtual bool ChgSpaceAdd( SwLineLayout* pCurr, long nSpaceAdd ) const override; +}; + +// For cursor travelling in multiportions + +class SwTextCursorSave +{ + SwTextCursor* pTextCursor; + SwLineLayout* pCurr; + TextFrameIndex nStart; + sal_uInt16 nWidth; + sal_uInt8 nOldProp; + bool bSpaceChg; +public: + SwTextCursorSave( SwTextCursor* pTextCursor, SwMultiPortion* pMulti, + SwTwips nY, sal_uInt16& nX, TextFrameIndex nCurrStart, long nSpaceAdd); + ~SwTextCursorSave(); +}; + +inline bool SwMultiPortion::HasBrackets() const +{ + return IsDouble() && nullptr != static_cast<const SwDoubleLinePortion*>(this)->GetBrackets(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porref.cxx b/sw/source/core/text/porref.cxx new file mode 100644 index 000000000..9ddb232d1 --- /dev/null +++ b/sw/source/core/text/porref.cxx @@ -0,0 +1,75 @@ +/* -*- 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 <SwPortionHandler.hxx> +#include <viewopt.hxx> + +#include "porref.hxx" +#include "inftxt.hxx" + +void SwRefPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + { + rInf.DrawViewOpt( *this, PortionType::Ref ); + SwTextPortion::Paint( rInf ); + } +} + +SwLinePortion *SwIsoRefPortion::Compress() { return this; } + +SwIsoRefPortion::SwIsoRefPortion() : nViewWidth(0) +{ + SetLen(TextFrameIndex(1)); + SetWhichPor( PortionType::IsoRef ); +} + +sal_uInt16 SwIsoRefPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +{ + // Although we are const, nViewWidth should be calculated in the last + // moment possible + SwIsoRefPortion* pThis = const_cast<SwIsoRefPortion*>(this); + if( !Width() && rInf.OnWin() && SwViewOption::IsFieldShadings() && + !rInf.GetOpt().IsReadonly() && !rInf.GetOpt().IsPagePreview() ) + { + if( !nViewWidth ) + pThis->nViewWidth = rInf.GetTextSize(OUString(' ')).Width(); + } + else + pThis->nViewWidth = 0; + return nViewWidth; +} + +bool SwIsoRefPortion::Format( SwTextFormatInfo &rInf ) +{ + return SwLinePortion::Format( rInf ); +} + +void SwIsoRefPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + rInf.DrawViewOpt( *this, PortionType::Ref ); +} + +void SwIsoRefPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), OUString(), GetWhichPor() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porref.hxx b/sw/source/core/text/porref.hxx new file mode 100644 index 000000000..0c7dc8503 --- /dev/null +++ b/sw/source/core/text/porref.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORREF_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORREF_HXX + +#include "portxt.hxx" + +class SwRefPortion : public SwTextPortion +{ +public: + SwRefPortion(){ SetWhichPor( PortionType::Ref ); } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +class SwIsoRefPortion : public SwRefPortion +{ + sal_uInt16 nViewWidth; + +public: + SwIsoRefPortion(); + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual SwLinePortion *Compress() override; + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx new file mode 100644 index 000000000..69183bc58 --- /dev/null +++ b/sw/source/core/text/porrst.cxx @@ -0,0 +1,633 @@ +/* -*- 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 <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/pgrditem.hxx> +#include <vcl/svapp.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <ndtxt.hxx> +#include <pagefrm.hxx> +#include <paratr.hxx> +#include <SwPortionHandler.hxx> +#include "porrst.hxx" +#include "inftxt.hxx" +#include "txtpaint.hxx" +#include <swfntcch.hxx> +#include <tgrditem.hxx> +#include <pagedesc.hxx> +#include <frmatr.hxx> +#include "redlnitr.hxx" +#include "atrhndl.hxx" +#include <rootfrm.hxx> + +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDeviceAccess.hxx> + +#include <crsrsh.hxx> + +SwTmpEndPortion::SwTmpEndPortion( const SwLinePortion &rPortion ) +{ + Height( rPortion.Height() ); + SetAscent( rPortion.GetAscent() ); + SetWhichPor( PortionType::TempEnd ); +} + +void SwTmpEndPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if (rInf.OnWin() && rInf.GetOpt().IsParagraph()) + { + const SwFont* pOldFnt = rInf.GetFont(); + + SwFont aFont(*pOldFnt); + aFont.SetColor(NON_PRINTING_CHARACTER_COLOR); + const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont); + + // draw the pilcrow + rInf.DrawText(OUString(CH_PAR), *this); + + const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt)); + } +} + +SwBreakPortion::SwBreakPortion( const SwLinePortion &rPortion ) + : SwLinePortion( rPortion ) +{ + nLineLength = TextFrameIndex(1); + SetWhichPor( PortionType::Break ); +} + +TextFrameIndex SwBreakPortion::GetModelPositionForViewPoint(const sal_uInt16) const +{ + return TextFrameIndex(0); +} + +sal_uInt16 SwBreakPortion::GetViewWidth( const SwTextSizeInfo & ) const +{ return 0; } + +SwLinePortion *SwBreakPortion::Compress() +{ return (GetNextPortion() && GetNextPortion()->InTextGrp() ? nullptr : this); } + +void SwBreakPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( rInf.OnWin() && rInf.GetOpt().IsLineBreak() ) + rInf.DrawLineBreak( *this ); +} + +bool SwBreakPortion::Format( SwTextFormatInfo &rInf ) +{ + const SwLinePortion *pRoot = rInf.GetRoot(); + Width( 0 ); + Height( pRoot->Height() ); + SetAscent( pRoot->GetAscent() ); + if (rInf.GetIdx() + TextFrameIndex(1) == TextFrameIndex(rInf.GetText().getLength())) + rInf.SetNewLine( true ); + return true; +} + +void SwBreakPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Text( GetLen(), GetWhichPor() ); +} + +SwKernPortion::SwKernPortion( SwLinePortion &rPortion, short nKrn, + bool bBG, bool bGK ) : + nKern( nKrn ), bBackground( bBG ), bGridKern( bGK ) +{ + Height( rPortion.Height() ); + SetAscent( rPortion.GetAscent() ); + nLineLength = TextFrameIndex(0); + SetWhichPor( PortionType::Kern ); + if( nKern > 0 ) + Width( nKern ); + rPortion.Insert( this ); +} + +SwKernPortion::SwKernPortion( const SwLinePortion& rPortion ) : + nKern( 0 ), bBackground( false ), bGridKern( true ) +{ + Height( rPortion.Height() ); + SetAscent( rPortion.GetAscent() ); + + nLineLength = TextFrameIndex(0); + SetWhichPor( PortionType::Kern ); +} + +void SwKernPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + { + // bBackground is set for Kerning Portions between two fields + if ( bBackground ) + rInf.DrawViewOpt( *this, PortionType::Field ); + + rInf.DrawBackBrush( *this ); + if (GetJoinBorderWithNext() ||GetJoinBorderWithPrev()) + rInf.DrawBorder( *this ); + + // do we have to repaint a post it portion? + if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() ) + mpNextPortion->PrePaint( rInf, this ); + + if( rInf.GetFont()->IsPaintBlank() ) + { + SwRect aClipRect; + rInf.CalcRect( *this, &aClipRect ); + SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) ); + aClip.ChgClip( aClipRect ); + rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(2), true ); + } + } +} + +void SwKernPortion::FormatEOL( SwTextFormatInfo &rInf ) +{ + if ( bGridKern ) + return; + + if( rInf.GetLast() == this ) + rInf.SetLast( FindPrevPortion( rInf.GetRoot() ) ); + if( nKern < 0 ) + Width( -nKern ); + else + Width( 0 ); + rInf.GetLast()->FormatEOL( rInf ); +} + +SwArrowPortion::SwArrowPortion( const SwLinePortion &rPortion ) : + bLeft( true ) +{ + Height( rPortion.Height() ); + SetAscent( rPortion.GetAscent() ); + nLineLength = TextFrameIndex(0); + SetWhichPor( PortionType::Arrow ); +} + +SwArrowPortion::SwArrowPortion( const SwTextPaintInfo &rInf ) + : bLeft( false ) +{ + Height( static_cast<sal_uInt16>(rInf.GetTextFrame()->getFramePrintArea().Height()) ); + aPos.setX( rInf.GetTextFrame()->getFrameArea().Left() + + rInf.GetTextFrame()->getFramePrintArea().Right() ); + aPos.setY( rInf.GetTextFrame()->getFrameArea().Top() + + rInf.GetTextFrame()->getFramePrintArea().Bottom() ); + SetWhichPor( PortionType::Arrow ); +} + +void SwArrowPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + const_cast<SwArrowPortion*>(this)->aPos = rInf.GetPos(); +} + +SwLinePortion *SwArrowPortion::Compress() { return this; } + +SwTwips SwTextFrame::EmptyHeight() const +{ + if (IsCollapse()) { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( dynamic_cast<const SwCursorShell*>( pSh ) != nullptr ) { + SwCursorShell *pCrSh = static_cast<SwCursorShell*>(pSh); + // this is called during formatting so avoid recursive layout + SwContentFrame const*const pCurrFrame = pCrSh->GetCurrFrame(false); + if (pCurrFrame==static_cast<SwContentFrame const *>(this)) { + // do nothing + } else { + return 1; + } + } else { + return 1; + } + } + OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::EmptyHeight with swapped frame" ); + + std::unique_ptr<SwFont> pFnt; + const SwTextNode& rTextNode = *GetTextNodeForParaProps(); + const IDocumentSettingAccess* pIDSA = rTextNode.getIDocumentSettingAccess(); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( rTextNode.HasSwAttrSet() ) + { + const SwAttrSet *pAttrSet = &( rTextNode.GetSwAttrSet() ); + pFnt.reset(new SwFont( pAttrSet, pIDSA )); + } + else + { + SwFontAccess aFontAccess( &rTextNode.GetAnyFormatColl(), pSh); + pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() )); + pFnt->CheckFontCacheId( pSh, pFnt->GetActual() ); + } + + if ( IsVertical() ) + pFnt->SetVertical( 2700 ); + + OutputDevice* pOut = pSh ? pSh->GetOut() : nullptr; + if ( !pOut || !pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsPrtFormat() ) + { + pOut = rTextNode.getIDocumentDeviceAccess().getReferenceDevice(true); + } + + const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess(); + if (IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags()) + && !getRootFrame()->IsHideRedlines()) + { + const SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwAttrHandler aAttrHandler; + aAttrHandler.Init(rTextNode.GetSwAttrSet(), + *rTextNode.getIDocumentSettingAccess()); + SwRedlineItr aRedln( rTextNode, *pFnt, aAttrHandler, + nRedlPos, SwRedlineItr::Mode::Show); + } + } + + SwTwips nRet; + if( !pOut ) + nRet = IsVertical() ? + getFramePrintArea().SSize().Width() + 1 : + getFramePrintArea().SSize().Height() + 1; + else + { + pFnt->SetFntChg( true ); + pFnt->ChgPhysFnt( pSh, *pOut ); + nRet = pFnt->GetHeight( pSh, *pOut ); + } + return nRet; +} + +bool SwTextFrame::FormatEmpty() +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::FormatEmpty with swapped frame" ); + + bool bCollapse = EmptyHeight( ) == 1 && IsCollapse( ); + + // sw_redlinehide: just disable FormatEmpty optimisation for now + if (HasFollow() || GetMergedPara() || GetTextNodeFirst()->GetpSwpHints() || + nullptr != GetTextNodeForParaProps()->GetNumRule() || + GetTextNodeFirst()->HasHiddenCharAttribute(true) || + IsInFootnote() || ( HasPara() && GetPara()->IsPrepMustFit() ) ) + return false; + const SwAttrSet& aSet = GetTextNodeForParaProps()->GetSwAttrSet(); + const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust(); + if( !bCollapse && ( ( ( ! IsRightToLeft() && ( SvxAdjust::Left != nAdjust ) ) || + ( IsRightToLeft() && ( SvxAdjust::Right != nAdjust ) ) ) || + aSet.GetRegister().GetValue() ) ) + return false; + const SvxLineSpacingItem &rSpacing = aSet.GetLineSpacing(); + if( !bCollapse && ( SvxLineSpaceRule::Min == rSpacing.GetLineSpaceRule() || + SvxLineSpaceRule::Fix == rSpacing.GetLineSpaceRule() || + aSet.GetLRSpace().IsAutoFirst() ) ) + return false; + + SwTextFly aTextFly( this ); + SwRect aRect; + bool bFirstFlyCheck = 0 != getFramePrintArea().Height(); + if ( !bCollapse && bFirstFlyCheck && + aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) ) + return false; + + // only need to check one node because of early return on GetMerged() + for (SwIndex const* pIndex = GetTextNodeFirst()->GetFirstIndex(); + pIndex; pIndex = pIndex->GetNext()) + { + sw::mark::IMark const*const pMark = pIndex->GetMark(); + if (dynamic_cast<const sw::mark::IBookmark*>(pMark) != nullptr) + { // need bookmark portions! + return false; + } + } + + SwTwips nHeight = EmptyHeight(); + + if (aSet.GetParaGrid().GetValue() && + IsInDocBody() ) + { + SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame())); + if ( pGrid ) + nHeight = pGrid->GetBaseHeight() + pGrid->GetRubyHeight(); + } + + SwRectFnSet aRectFnSet(this); + const SwTwips nChg = nHeight - aRectFnSet.GetHeight(getFramePrintArea()); + + if( !nChg ) + SetUndersized( false ); + AdjustFrame( nChg ); + + if (GetHasRotatedPortions()) + { + ClearPara(); + SetHasRotatedPortions(false); + } + + RemoveFromCache(); + if( !IsEmpty() ) + { + SetEmpty( true ); + SetCompletePaint(); + } + if( !bCollapse && !bFirstFlyCheck && + aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) ) + return false; + + // #i35635# - call method <HideAndShowObjects()> + // to assure that objects anchored at the empty paragraph are + // correctly visible resp. invisible. + HideAndShowObjects(); + return true; +} + +bool SwTextFrame::FillRegister( SwTwips& rRegStart, sal_uInt16& rRegDiff ) +{ + const SwFrame *pFrame = this; + rRegDiff = 0; + while( !( ( SwFrameType::Body | SwFrameType::Fly ) + & pFrame->GetType() ) && pFrame->GetUpper() ) + pFrame = pFrame->GetUpper(); + if( ( SwFrameType::Body| SwFrameType::Fly ) & pFrame->GetType() ) + { + SwRectFnSet aRectFnSet(pFrame); + rRegStart = aRectFnSet.GetPrtTop(*pFrame); + pFrame = pFrame->FindPageFrame(); + if( pFrame->IsPageFrame() ) + { + SwPageDesc* pDesc = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pFrame))->FindPageDesc(); + if( pDesc ) + { + rRegDiff = pDesc->GetRegHeight(); + if( !rRegDiff ) + { + const SwTextFormatColl *pFormat = pDesc->GetRegisterFormatColl(); + if( pFormat ) + { + const SvxLineSpacingItem &rSpace = pFormat->GetLineSpacing(); + if( SvxLineSpaceRule::Fix == rSpace.GetLineSpaceRule() ) + { + rRegDiff = rSpace.GetLineHeight(); + pDesc->SetRegHeight( rRegDiff ); + pDesc->SetRegAscent( ( 4 * rRegDiff ) / 5 ); + } + else + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwFontAccess aFontAccess( pFormat, pSh ); + SwFont aFnt( aFontAccess.Get()->GetFont() ); + + OutputDevice *pOut = nullptr; + if( !pSh || !pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsPrtFormat() ) + pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true ); + + if( pSh && !pOut ) + pOut = pSh->GetWin(); + + if( !pOut ) + pOut = Application::GetDefaultDevice(); + + MapMode aOldMap( pOut->GetMapMode() ); + pOut->SetMapMode( MapMode( MapUnit::MapTwip ) ); + + aFnt.ChgFnt( pSh, *pOut ); + rRegDiff = aFnt.GetHeight( pSh, *pOut ); + sal_uInt16 nNetHeight = rRegDiff; + + switch( rSpace.GetLineSpaceRule() ) + { + case SvxLineSpaceRule::Auto: + break; + case SvxLineSpaceRule::Min: + { + if( rRegDiff < rSpace.GetLineHeight() ) + rRegDiff = rSpace.GetLineHeight(); + break; + } + default: + OSL_FAIL( ": unknown LineSpaceRule" ); + } + switch( rSpace.GetInterLineSpaceRule() ) + { + case SvxInterLineSpaceRule::Off: + break; + case SvxInterLineSpaceRule::Prop: + { + long nTmp = rSpace.GetPropLineSpace(); + if( nTmp < 50 ) + nTmp = nTmp ? 50 : 100; + nTmp *= rRegDiff; + nTmp /= 100; + if( !nTmp ) + ++nTmp; + rRegDiff = static_cast<sal_uInt16>(nTmp); + nNetHeight = rRegDiff; + break; + } + case SvxInterLineSpaceRule::Fix: + { + rRegDiff = rRegDiff + rSpace.GetInterLineSpace(); + nNetHeight = rRegDiff; + break; + } + default: OSL_FAIL( ": unknown InterLineSpaceRule" ); + } + pDesc->SetRegHeight( rRegDiff ); + pDesc->SetRegAscent( rRegDiff - nNetHeight + + aFnt.GetAscent( pSh, *pOut ) ); + pOut->SetMapMode( aOldMap ); + } + } + } + const long nTmpDiff = pDesc->GetRegAscent() - rRegDiff; + if ( aRectFnSet.IsVert() ) + rRegStart -= nTmpDiff; + else + rRegStart += nTmpDiff; + } + } + } + return ( 0 != rRegDiff ); +} + +void SwHiddenTextPortion::Paint( const SwTextPaintInfo & rInf) const +{ +#ifdef DBG_UTIL + OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut()); + Color aCol( SwViewOption::GetFieldShadingsColor() ); + Color aOldColor( pOut->GetFillColor() ); + pOut->SetFillColor( aCol ); + Point aPos( rInf.GetPos() ); + aPos.AdjustY( -150 ); + aPos.AdjustX( -25 ); + SwRect aRect( aPos, Size( 100, 200 ) ); + pOut->DrawRect( aRect.SVRect() ); + pOut->SetFillColor( aOldColor ); +#else + (void)rInf; +#endif +} + +bool SwHiddenTextPortion::Format( SwTextFormatInfo &rInf ) +{ + Width( 0 ); + rInf.GetTextFrame()->HideFootnotes( rInf.GetIdx(), rInf.GetIdx() + GetLen() ); + + return false; +}; + +bool SwControlCharPortion::DoPaint(SwTextPaintInfo const&, + OUString & rOutString, SwFont & rTmpFont, int &) const +{ + if (mcChar == CHAR_ZWNBSP || !SwViewOption::IsFieldShadings()) + { + return false; + } + + switch (mcChar) + { + case CHAR_ZWSP: + rOutString = "/"; break; +// case CHAR_LRM : +// rText = sal_Unicode(0x2514); break; +// case CHAR_RLM : +// rText = sal_Unicode(0x2518); break; + default: + assert(false); + break; + } + + rTmpFont.SetEscapement( CHAR_ZWSP == mcChar ? DFLT_ESC_AUTO_SUB : -25 ); + const sal_uInt16 nProp = 40; + rTmpFont.SetProportion( nProp ); // a smaller font + + return true; +} + +bool SwBookmarkPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo, + OUString & rOutString, SwFont & rFont, int & rDeltaY) const +{ + if (!rTextPaintInfo.GetOpt().IsShowBookmarks()) + { + return false; + } + + rOutString = OUStringChar(mcChar); + + // init font: we want OpenSymbol to ensure it doesn't look too crazy; + // thin and a bit higher than the surrounding text + auto const nOrigAscent(rFont.GetAscent(rTextPaintInfo.GetVsh(), *rTextPaintInfo.GetOut())); + rFont.SetName("OpenSymbol", rFont.GetActual()); + Size aSize(rFont.GetSize(rFont.GetActual())); + // use also the external leading (line gap) of the portion, but don't use + // 100% of it because i can't figure out how to baseline align that + auto const nFactor = (Height() * 95) / aSize.Height(); + rFont.SetProportion(nFactor); + rFont.SetWeight(WEIGHT_THIN, rFont.GetActual()); + rFont.SetColor(NON_PRINTING_CHARACTER_COLOR); + // reset these to default... + rFont.SetAlign(ALIGN_BASELINE); + rFont.SetUnderline(LINESTYLE_NONE); + rFont.SetOverline(LINESTYLE_NONE); + rFont.SetStrikeout(STRIKEOUT_NONE); + rFont.SetOutline(false); + rFont.SetShadow(false); + rFont.SetTransparent(false); + rFont.SetEmphasisMark(FontEmphasisMark::NONE); + rFont.SetEscapement(0); + rFont.SetPitch(PITCH_DONTKNOW, rFont.GetActual()); + rFont.SetRelief(FontRelief::NONE); + + // adjust Y position to account for different baselines of the fonts + auto const nOSAscent(rFont.GetAscent(rTextPaintInfo.GetVsh(), *rTextPaintInfo.GetOut())); + rDeltaY = nOSAscent - nOrigAscent; + + return true; +} + +void SwControlCharPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if ( Width() ) // is only set during prepaint mode + { + rInf.DrawViewOpt(*this, GetWhichPor()); + + int deltaY(0); + SwFont aTmpFont( *rInf.GetFont() ); + OUString aOutString; + + if (rInf.OnWin() + && !rInf.GetOpt().IsPagePreview() + && !rInf.GetOpt().IsReadonly() + && DoPaint(rInf, aOutString, aTmpFont, deltaY)) + { + SwFontSave aFontSave( rInf, &aTmpFont ); + + if ( !mnHalfCharWidth ) + mnHalfCharWidth = rInf.GetTextSize( aOutString ).Width() / 2; + + Point aOldPos = rInf.GetPos(); + Point aNewPos( aOldPos ); + auto const deltaX((Width() / 2) - mnHalfCharWidth); + switch (rInf.GetFont()->GetOrientation(rInf.GetTextFrame()->IsVertical())) + { + case 0: + aNewPos.AdjustX(deltaX); + aNewPos.AdjustY(deltaY); + break; + case 900: + aNewPos.AdjustY(-deltaX); + aNewPos.AdjustX(deltaY); + break; + case 2700: + aNewPos.AdjustY(deltaX); + aNewPos.AdjustX(-deltaY); + break; + default: + assert(false); + break; + } + const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos ); + + rInf.DrawText( aOutString, *this ); + + const_cast< SwTextPaintInfo& >( rInf ).SetPos( aOldPos ); + } + } +} + +bool SwControlCharPortion::Format( SwTextFormatInfo &rInf ) +{ + const SwLinePortion* pRoot = rInf.GetRoot(); + Width( 0 ); + Height( pRoot->Height() ); + SetAscent( pRoot->GetAscent() ); + + return false; +} + +sal_uInt16 SwControlCharPortion::GetViewWidth( const SwTextSizeInfo& rInf ) const +{ + if( !mnViewWidth ) + mnViewWidth = rInf.GetTextSize(OUString(' ')).Width(); + + return mnViewWidth; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porrst.hxx b/sw/source/core/text/porrst.hxx new file mode 100644 index 000000000..7c716be8d --- /dev/null +++ b/sw/source/core/text/porrst.hxx @@ -0,0 +1,174 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORRST_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORRST_HXX + +#include <tools/gen.hxx> + +#include <TextFrameIndex.hxx> +#include <txttypes.hxx> + +#include "porlin.hxx" +#include "portxt.hxx" +#include "possiz.hxx" + +class SwPortionHandler; +class SwTextPaintInfo; +class SwTextSizeInfo; +class SwFont; + +#define LINE_BREAK_WIDTH 150 +#define SPECIAL_FONT_HEIGHT 200 + +class SwTextFormatInfo; + +class SwTmpEndPortion : public SwLinePortion +{ +public: + explicit SwTmpEndPortion( const SwLinePortion &rPortion ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +class SwBreakPortion : public SwLinePortion +{ +public: + explicit SwBreakPortion( const SwLinePortion &rPortion ); + // Returns 0 if we have no usable data + virtual SwLinePortion *Compress() override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwKernPortion : public SwLinePortion +{ + short nKern; + bool bBackground; + bool bGridKern; + +public: + + // This constructor automatically appends the portion to rPortion + // bBG indicates, that the background of the kerning portion has to + // be painted, e.g., if the portion if positioned between to fields. + // bGridKern indicates, that the kerning portion is used to provide + // additional space in grid mode. + SwKernPortion( SwLinePortion &rPortion, short nKrn, + bool bBG = false, bool bGridKern = false ); + + // This constructor only sets the height and ascent to the values + // of rPortion. It is only used for kerning portions for grid mode + explicit SwKernPortion( const SwLinePortion &rPortion ); + + virtual void FormatEOL( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +class SwArrowPortion : public SwLinePortion +{ + Point aPos; + bool bLeft; +public: + explicit SwArrowPortion( const SwLinePortion &rPortion ); + explicit SwArrowPortion( const SwTextPaintInfo &rInf ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual SwLinePortion *Compress() override; + bool IsLeft() const { return bLeft; } + const Point& GetPos() const { return aPos; } +}; + +// The characters which are forbidden at the start of a line like the dot and +// other punctuation marks are allowed to display in the margin of the page +// by a user option. +// The SwHangingPortion is the corresponding textportion to do that. +class SwHangingPortion : public SwTextPortion +{ + sal_uInt16 nInnerWidth; +public: + explicit SwHangingPortion( SwPosSize aSize ) : nInnerWidth( aSize.Width() ) + { + SetWhichPor( PortionType::Hanging ); + SetLen(TextFrameIndex(1)); + Height( aSize.Height() ); + } + + sal_uInt16 GetInnerWidth() const { return nInnerWidth; } +}; + +// Used to hide text +class SwHiddenTextPortion : public SwLinePortion +{ +public: + explicit SwHiddenTextPortion(TextFrameIndex const nLen) + { + SetWhichPor( PortionType::HiddenText ); SetLen( nLen ); + } + + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; +}; + +class SwControlCharPortion : public SwLinePortion +{ + +private: + mutable sal_uInt16 mnViewWidth; // used to cache a calculated value + mutable sal_uInt16 mnHalfCharWidth; // used to cache a calculated value +protected: + sal_Unicode mcChar; + +public: + + explicit SwControlCharPortion( sal_Unicode cChar ) + : mnViewWidth( 0 ), mnHalfCharWidth( 0 ), mcChar( cChar ) + { + SetWhichPor( PortionType::ControlChar ); SetLen( TextFrameIndex(1) ); + } + + virtual bool DoPaint(SwTextPaintInfo const& rInf, + OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo& rInf ) const override; +}; + +/// for showing bookmark starts and ends; note that in contrast to +/// SwControlCharPortion these do not have a character in the text. +class SwBookmarkPortion : public SwControlCharPortion +{ +public: + explicit SwBookmarkPortion(sal_Unicode const cChar) + : SwControlCharPortion(cChar) + { + SetWhichPor(PortionType::Bookmark); + SetLen(TextFrameIndex(0)); + } + + virtual bool DoPaint(SwTextPaintInfo const& rInf, + OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const override; + virtual SwLinePortion * Compress() override { return this; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/portab.hxx b/sw/source/core/text/portab.hxx new file mode 100644 index 000000000..3c6e7a5df --- /dev/null +++ b/sw/source/core/text/portab.hxx @@ -0,0 +1,114 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORTAB_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORTAB_HXX + +#include "porglue.hxx" + +class SwTabPortion : public SwFixPortion +{ + const sal_uInt16 nTabPos; + const sal_Unicode cFill; + const bool bAutoTabStop; + + // Format() branches either into PreFormat() or PostFormat() + bool PreFormat( SwTextFormatInfo &rInf ); +public: + SwTabPortion( const sal_uInt16 nTabPos, const sal_Unicode cFill, const bool bAutoTab = true ); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void FormatEOL( SwTextFormatInfo &rInf ) override; + bool PostFormat( SwTextFormatInfo &rInf ); + bool IsFilled() const { return 0 != cFill; } + sal_uInt16 GetTabPos() const { return nTabPos; } + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwTabLeftPortion : public SwTabPortion +{ +public: + SwTabLeftPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar, bool bAutoTab ) + : SwTabPortion( nTabPosVal, cFillChar, bAutoTab ) + { SetWhichPor( PortionType::TabLeft ); } +}; + +class SwTabRightPortion : public SwTabPortion +{ +public: + SwTabRightPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar ) + : SwTabPortion( nTabPosVal, cFillChar ) + { SetWhichPor( PortionType::TabRight ); } +}; + +class SwTabCenterPortion : public SwTabPortion +{ +public: + SwTabCenterPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar ) + : SwTabPortion( nTabPosVal, cFillChar ) + { SetWhichPor( PortionType::TabCenter ); } +}; + +class SwTabDecimalPortion : public SwTabPortion +{ + const sal_Unicode mcTab; + + /* + * During text formatting, we already store the width of the portions + * following the tab stop up to the decimal position. This value is + * evaluated during pLastTab->FormatEOL. FME 2006-01-06 #127428#. + */ + sal_uInt16 mnWidthOfPortionsUpTpDecimalPosition; + +public: + SwTabDecimalPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cTab, + const sal_Unicode cFillChar ) + : SwTabPortion( nTabPosVal, cFillChar ), + mcTab(cTab), + mnWidthOfPortionsUpTpDecimalPosition( USHRT_MAX ) + { SetWhichPor( PortionType::TabDecimal ); } + + sal_Unicode GetTabDecimal() const { return mcTab; } + + void SetWidthOfPortionsUpToDecimalPosition( sal_uInt16 nNew ) + { + mnWidthOfPortionsUpTpDecimalPosition = nNew; + } + sal_uInt16 GetWidthOfPortionsUpToDecimalPosition() const + { + return mnWidthOfPortionsUpTpDecimalPosition; + } +}; + +class SwAutoTabDecimalPortion : public SwTabDecimalPortion +{ +public: + SwAutoTabDecimalPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cTab, + const sal_Unicode cFillChar ) + : SwTabDecimalPortion( nTabPosVal, cTab, cFillChar ) + { + SetLen(TextFrameIndex(0)); + } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/portox.cxx b/sw/source/core/text/portox.cxx new file mode 100644 index 000000000..a746058e9 --- /dev/null +++ b/sw/source/core/text/portox.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <SwPortionHandler.hxx> +#include <viewopt.hxx> + +#include "portox.hxx" +#include "inftxt.hxx" + +void SwToxPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + { + rInf.DrawViewOpt( *this, PortionType::Tox ); + SwTextPortion::Paint( rInf ); + } +} + +SwLinePortion *SwIsoToxPortion::Compress() { return this; } + +SwIsoToxPortion::SwIsoToxPortion() : nViewWidth(0) +{ + SetLen(TextFrameIndex(1)); + SetWhichPor( PortionType::IsoTox ); +} + +sal_uInt16 SwIsoToxPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +{ + // Although we are const, nViewWidth should be calculated in the last + // moment possible + SwIsoToxPortion* pThis = const_cast<SwIsoToxPortion*>(this); + // nViewWidth need to be calculated + if( !Width() && rInf.OnWin() && + !rInf.GetOpt().IsPagePreview() && + !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() ) + { + if( !nViewWidth ) + pThis->nViewWidth = rInf.GetTextSize(OUString(' ')).Width(); + } + else + pThis->nViewWidth = 0; + return nViewWidth; +} + +bool SwIsoToxPortion::Format( SwTextFormatInfo &rInf ) +{ + return SwLinePortion::Format( rInf ); +} + +void SwIsoToxPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + rInf.DrawViewOpt( *this, PortionType::Tox ); +} + +void SwIsoToxPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), OUString(), GetWhichPor() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/portox.hxx b/sw/source/core/text/portox.hxx new file mode 100644 index 000000000..b2c8f23a7 --- /dev/null +++ b/sw/source/core/text/portox.hxx @@ -0,0 +1,49 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORTOX_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORTOX_HXX + +#include "portxt.hxx" + +class SwToxPortion : public SwTextPortion +{ +public: + SwToxPortion(){ SetWhichPor( PortionType::Tox ); } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; +}; + +class SwIsoToxPortion : public SwToxPortion +{ + sal_uInt16 nViewWidth; + +public: + SwIsoToxPortion(); + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual SwLinePortion *Compress() override; + virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx new file mode 100644 index 000000000..65d6a58f3 --- /dev/null +++ b/sw/source/core/text/portxt.cxx @@ -0,0 +1,845 @@ +/* -*- 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 <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <i18nlangtag/mslangid.hxx> +#include <breakit.hxx> +#include <hintids.hxx> +#include <EnhancedPDFExportHelper.hxx> +#include <SwPortionHandler.hxx> +#include "porlay.hxx" +#include "inftxt.hxx" +#include "guess.hxx" +#include "porfld.hxx" +#include <pagefrm.hxx> +#include <tgrditem.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentMarkAccess.hxx> + +#include <IMark.hxx> +#include <pam.hxx> +#include <doc.hxx> +#include <xmloff/odffields.hxx> +#include <viewopt.hxx> + +using namespace ::sw::mark; +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n::ScriptType; + +// Returns for how many characters an extra space has to be added +// (for justified alignment). +static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, + const OUString* pStr, const SwLinePortion& rPor) +{ + TextFrameIndex nPos, nEnd; + const SwScriptInfo* pSI = nullptr; + + if ( pStr ) + { + // passing a string means we are inside a field + nPos = TextFrameIndex(0); + nEnd = TextFrameIndex(pStr->getLength()); + } + else + { + nPos = rInf.GetIdx(); + nEnd = rInf.GetIdx() + rPor.GetLen(); + pStr = &rInf.GetText(); + pSI = &const_cast<SwParaPortion*>(rInf.GetParaPortion())->GetScriptInfo(); + } + + TextFrameIndex nCnt(0); + sal_uInt8 nScript = 0; + + // If portion consists of Asian characters and language is not + // Korean, we add extra space to each character. + // first we get the script type + if ( pSI ) + nScript = pSI->ScriptType( nPos ); + else + nScript = static_cast<sal_uInt8>( + g_pBreakIt->GetBreakIter()->getScriptType(*pStr, sal_Int32(nPos))); + + // Note: rInf.GetIdx() can differ from nPos, + // e.g., when rPor is a field portion. nPos refers to the string passed + // to the function, rInf.GetIdx() refers to the original string. + + // We try to find out which justification mode is required. This is done by + // evaluating the script type and the language attribute set for this portion + + // Asian Justification: Each character get some extra space + if ( nEnd > nPos && ASIAN == nScript ) + { + LanguageType aLang = + rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript); + + if (!MsLangId::isKorean(aLang)) + { + const SwLinePortion* pPor = rPor.GetNextPortion(); + if ( pPor && ( pPor->IsKernPortion() || + pPor->IsControlCharPortion() || + pPor->IsPostItsPortion() ) ) + pPor = pPor->GetNextPortion(); + + nCnt += SwScriptInfo::CountCJKCharacters( *pStr, nPos, nEnd, aLang ); + + if ( !pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() || + pPor->IsBreakPortion() ) + --nCnt; + + return nCnt; + } + } + + // Kashida Justification: Insert Kashidas + if ( nEnd > nPos && pSI && COMPLEX == nScript ) + { + if ( SwScriptInfo::IsArabicText( *pStr, nPos, nEnd - nPos ) && pSI->CountKashida() ) + { + const sal_Int32 nKashRes = pSI->KashidaJustify( nullptr, nullptr, nPos, nEnd - nPos ); + // i60591: need to check result of KashidaJustify + // determine if kashida justification is applicable + if (nKashRes != -1) + return TextFrameIndex(nKashRes); + } + } + + // Thai Justification: Each character cell gets some extra space + if ( nEnd > nPos && COMPLEX == nScript ) + { + LanguageType aLang = + rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript); + + if ( LANGUAGE_THAI == aLang ) + { + nCnt = SwScriptInfo::ThaiJustify( *pStr, nullptr, nullptr, nPos, nEnd - nPos ); + + const SwLinePortion* pPor = rPor.GetNextPortion(); + if ( pPor && ( pPor->IsKernPortion() || + pPor->IsControlCharPortion() || + pPor->IsPostItsPortion() ) ) + pPor = pPor->GetNextPortion(); + + if ( nCnt && ( ! pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ) ) + --nCnt; + + return nCnt; + } + } + + // Here starts the good old "Look for blanks and add space to them" part. + // Note: We do not want to add space to an isolated latin blank in front + // of some complex characters in RTL environment + const bool bDoNotAddSpace = + LATIN == nScript && (nEnd == nPos + TextFrameIndex(1)) && pSI && + ( i18n::ScriptType::COMPLEX == + pSI->ScriptType(nPos + TextFrameIndex(1))) && + rInf.GetTextFrame() && rInf.GetTextFrame()->IsRightToLeft(); + + if ( bDoNotAddSpace ) + return nCnt; + + TextFrameIndex nTextEnd = std::min(nEnd, TextFrameIndex(pStr->getLength())); + for ( ; nPos < nTextEnd; ++nPos ) + { + if (CH_BLANK == (*pStr)[ sal_Int32(nPos) ]) + ++nCnt; + } + + // We still have to examine the next character: + // If the next character is ASIAN and not KOREAN we have + // to add an extra space + // nPos refers to the original string, even if a field string has + // been passed to this function + nPos = rInf.GetIdx() + rPor.GetLen(); + if (nPos < TextFrameIndex(rInf.GetText().getLength())) + { + sal_uInt8 nNextScript = 0; + const SwLinePortion* pPor = rPor.GetNextPortion(); + if ( pPor && pPor->IsKernPortion() ) + pPor = pPor->GetNextPortion(); + + if (!pPor || pPor->InFixMargGrp()) + return nCnt; + + // next character is inside a field? + if ( CH_TXTATR_BREAKWORD == rInf.GetChar( nPos ) && pPor->InExpGrp() ) + { + bool bOldOnWin = rInf.OnWin(); + const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false ); + + OUString aStr; + pPor->GetExpText( rInf, aStr ); + const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin ); + + nNextScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType( aStr, 0 )); + } + else + nNextScript = static_cast<sal_uInt8>( + g_pBreakIt->GetBreakIter()->getScriptType(rInf.GetText(), sal_Int32(nPos))); + + if( ASIAN == nNextScript ) + { + LanguageType aLang = + rInf.GetTextFrame()->GetLangOfChar(nPos, nNextScript); + + if (!MsLangId::isKorean(aLang)) + ++nCnt; + } + } + + return nCnt; +} + +SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion) +{ + SwTextPortion *const pNew(new SwTextPortion); + static_cast<SwLinePortion&>(*pNew) = rPortion; + pNew->SetWhichPor( PortionType::Text ); // overwrite that! + return pNew; +} + +void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess ) +{ + // The word/char is larger than the line + // Special case 1: The word is larger than the line + // We truncate ... + const sal_uInt16 nLineWidth = static_cast<sal_uInt16>(rInf.Width() - rInf.X()); + TextFrameIndex nLen = rGuess.CutPos() - rInf.GetIdx(); + if (nLen > TextFrameIndex(0)) + { + // special case: guess does not always provide the correct + // width, only in common cases. + if ( !rGuess.BreakWidth() ) + { + rInf.SetLen( nLen ); + SetLen( nLen ); + CalcTextSize( rInf ); + + // changing these values requires also changing them in + // guess.cxx + sal_uInt16 nItalic = 0; + if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() ) + { + nItalic = Height() / 12; + } + Width( Width() + nItalic ); + } + else + { + Width( rGuess.BreakWidth() ); + SetLen( nLen ); + } + } + // special case: first character does not fit to line + else if ( rGuess.CutPos() == rInf.GetLineStart() ) + { + SetLen( TextFrameIndex(1) ); + Width( nLineWidth ); + } + else + { + SetLen( TextFrameIndex(0) ); + Width( 0 ); + } +} + +void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf ) +{ + Truncate(); + Height( 0 ); + Width( 0 ); + SetLen( TextFrameIndex(0) ); + SetAscent( 0 ); + rInf.SetUnderflow( this ); +} + +static bool lcl_HasContent( const SwFieldPortion& rField, SwTextFormatInfo const &rInf ) +{ + OUString aText; + return rField.GetExpText( rInf, aText ) && !aText.isEmpty(); +} + +bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) +{ + // 5744: If only the hyphen does not fit anymore, we still need to wrap + // the word, or else return true! + if( rInf.IsUnderflow() && rInf.GetSoftHyphPos() ) + { + // soft hyphen portion has triggered an underflow event because + // of an alternative spelling position + bool bFull = false; + const bool bHyph = rInf.ChgHyph( true ); + if( rInf.IsHyphenate() ) + { + SwTextGuess aGuess; + // check for alternative spelling left from the soft hyphen + // this should usually be true but + aGuess.AlternativeSpelling(rInf, rInf.GetSoftHyphPos() - TextFrameIndex(1)); + bFull = CreateHyphen( rInf, aGuess ); + OSL_ENSURE( bFull, "Problem with hyphenation!!!" ); + } + rInf.ChgHyph( bHyph ); + rInf.SetSoftHyphPos( TextFrameIndex(0) ); + return bFull; + } + + SwTextGuess aGuess; + const bool bFull = !aGuess.Guess( *this, rInf, Height() ); + + // these are the possible cases: + // A Portion fits to current line + // B Portion does not fit to current line but a possible line break + // within the portion has been found by the break iterator, 2 subcases + // B1 break is hyphen + // B2 break is word end + // C Portion does not fit to current line and no possible line break + // has been found by break iterator, 2 subcases: + // C1 break iterator found a possible line break in portion before us + // ==> this break is used (underflow) + // C2 break iterator does not found a possible line break at all: + // ==> line break + + // case A: line not yet full + if ( !bFull ) + { + Width( aGuess.BreakWidth() ); + // Caution! + if( !InExpGrp() || InFieldGrp() ) + SetLen( rInf.GetLen() ); + + short nKern = rInf.GetFont()->CheckKerning(); + if( nKern > 0 && rInf.Width() < rInf.X() + Width() + nKern ) + { + nKern = static_cast<short>(rInf.Width() - rInf.X() - Width() - 1); + if( nKern < 0 ) + nKern = 0; + } + if( nKern ) + new SwKernPortion( *this, nKern ); + } + // special case: hanging portion + else if( bFull && aGuess.GetHangingPortion() ) + { + Width( aGuess.BreakWidth() ); + SetLen( aGuess.BreakPos() - rInf.GetIdx() ); + aGuess.GetHangingPortion()->SetAscent( GetAscent() ); + Insert( aGuess.ReleaseHangingPortion() ); + } + // breakPos >= index + else if (aGuess.BreakPos() >= rInf.GetIdx() && aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING)) + { + // case B1 + if( aGuess.HyphWord().is() && aGuess.BreakPos() > rInf.GetLineStart() + && ( aGuess.BreakPos() > rInf.GetIdx() || + ( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) ) + { + CreateHyphen( rInf, aGuess ); + if ( rInf.GetFly() ) + rInf.GetRoot()->SetMidHyph( true ); + else + rInf.GetRoot()->SetEndHyph( true ); + } + // case C1 + // - Footnote portions with fake line start (i.e., not at beginning of line) + // should keep together with the text portion. (Note: no keep together + // with only footnote portions. + // - TabPortions not at beginning of line should keep together with the + // text portion, if they are not followed by a blank + // (work around different definition of tab stop character - breaking or + // non breaking character - in compatibility mode) + else if ( ( IsFootnotePortion() && rInf.IsFakeLineStart() && + + rInf.IsOtherThanFootnoteInside() ) || + ( rInf.GetLast() && + rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) && + rInf.GetLast()->InTabGrp() && + rInf.GetLineStart() + rInf.GetLast()->GetLen() < rInf.GetIdx() && + aGuess.BreakPos() == rInf.GetIdx() && + CH_BLANK != rInf.GetChar( rInf.GetIdx() ) && + CH_FULL_BLANK != rInf.GetChar( rInf.GetIdx() ) && + CH_SIX_PER_EM != rInf.GetChar( rInf.GetIdx() ) ) ) + BreakUnderflow( rInf ); + // case B2 + else if( rInf.GetIdx() > rInf.GetLineStart() || + aGuess.BreakPos() > rInf.GetIdx() || + // this is weird: during formatting the follow of a field + // the values rInf.GetIdx and rInf.GetLineStart are replaced + // IsFakeLineStart indicates GetIdx > GetLineStart + rInf.IsFakeLineStart() || + rInf.GetFly() || + rInf.IsFirstMulti() || + ( rInf.GetLast() && + ( rInf.GetLast()->IsFlyPortion() || + ( rInf.GetLast()->InFieldGrp() && + ! rInf.GetLast()->InNumberGrp() && + ! rInf.GetLast()->IsErgoSumPortion() && + lcl_HasContent(*static_cast<SwFieldPortion*>(rInf.GetLast()),rInf ) ) ) ) ) + { + // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN. + if (aGuess.BreakWidth() <= rInf.GetLineWidth()) + Width( aGuess.BreakWidth() ); + else + // this actually should not happen + Width( sal_uInt16(rInf.Width() - rInf.X()) ); + + SetLen( aGuess.BreakPos() - rInf.GetIdx() ); + + OSL_ENSURE( aGuess.BreakStart() >= aGuess.FieldDiff(), + "Trouble with expanded field portions during line break" ); + TextFrameIndex const nRealStart = aGuess.BreakStart() - aGuess.FieldDiff(); + if( aGuess.BreakPos() < nRealStart && !InExpGrp() ) + { + SwHolePortion *pNew = new SwHolePortion( *this ); + pNew->SetLen( nRealStart - aGuess.BreakPos() ); + Insert( pNew ); + } + } + else // case C2, last exit + BreakCut( rInf, aGuess ); + } + // breakPos < index or no breakpos at all + else + { + bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx(); + if (aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING) && + aGuess.BreakPos() != rInf.GetLineStart() && + ( !bFirstPor || rInf.GetFly() || rInf.GetLast()->IsFlyPortion() || + rInf.IsFirstMulti() ) && + ( !rInf.GetLast()->IsBlankPortion() || + SwBlankPortion::MayUnderflow(rInf, rInf.GetIdx() - TextFrameIndex(1), true))) + { // case C1 (former BreakUnderflow()) + BreakUnderflow( rInf ); + } + else + // case C2, last exit + BreakCut( rInf, aGuess ); + } + + return bFull; +} + +bool SwTextPortion::Format( SwTextFormatInfo &rInf ) +{ + // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN. + if( rInf.GetLineWidth() < 0 || (!GetLen() && !InExpGrp()) ) + { + Height( 0 ); + Width( 0 ); + SetLen( TextFrameIndex(0) ); + SetAscent( 0 ); + SetNextPortion( nullptr ); // ???? + return true; + } + + OSL_ENSURE( rInf.RealWidth() || (rInf.X() == rInf.Width()), + "SwTextPortion::Format: missing real width" ); + OSL_ENSURE( Height(), "SwTextPortion::Format: missing height" ); + + return Format_( rInf ); +} + +// Format end of line +// 5083: We can have awkward cases e.g.: +// "from {Santa}" +// Santa wraps, "from " turns into "from" and " " in a justified +// paragraph, in which the glue gets expanded instead of merged +// with the MarginPortion. + +// rInf.nIdx points to the next word, nIdx-1 is the portion's last char +void SwTextPortion::FormatEOL( SwTextFormatInfo &rInf ) +{ + if( !( + ( !GetNextPortion() || ( GetNextPortion()->IsKernPortion() && + !GetNextPortion()->GetNextPortion() ) ) && + GetLen() && + rInf.GetIdx() < TextFrameIndex(rInf.GetText().getLength()) && + TextFrameIndex(1) < rInf.GetIdx() && + ' ' == rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) && + !rInf.GetLast()->IsHolePortion()) ) + return; + + // calculate number of blanks + TextFrameIndex nX(rInf.GetIdx() - TextFrameIndex(1)); + TextFrameIndex nHoleLen(1); + while( nX && nHoleLen < GetLen() && CH_BLANK == rInf.GetChar( --nX ) ) + nHoleLen++; + + // First set ourselves and the insert, because there could be + // a SwLineLayout + sal_uInt16 nBlankSize; + if( nHoleLen == GetLen() ) + nBlankSize = Width(); + else + nBlankSize = sal_Int32(nHoleLen) * rInf.GetTextSize(OUString(' ')).Width(); + Width( Width() - nBlankSize ); + rInf.X( rInf.X() - nBlankSize ); + SetLen( GetLen() - nHoleLen ); + SwLinePortion *pHole = new SwHolePortion( *this ); + static_cast<SwHolePortion *>( pHole )->SetBlankWidth( nBlankSize ); + static_cast<SwHolePortion *>( pHole )->SetLen( nHoleLen ); + Insert( pHole ); + +} + +TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const +{ + OSL_ENSURE( false, "SwTextPortion::GetModelPositionForViewPoint: don't use this method!" ); + return SwLinePortion::GetModelPositionForViewPoint( nOfst ); +} + +// The GetTextSize() assumes that the own length is correct +SwPosSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +{ + SwPosSize aSize = rInf.GetTextSize(); + if( !GetJoinBorderWithPrev() ) + aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace() ); + if( !GetJoinBorderWithNext() ) + aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace() ); + + aSize.Height(aSize.Height() + + rInf.GetFont()->GetTopBorderSpace() + + rInf.GetFont()->GetBottomBorderSpace() ); + + return aSize; +} + +void SwTextPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen() + && CH_TXT_ATR_FIELDEND == rInf.GetText()[sal_Int32(rInf.GetIdx())]) + { + assert(false); // this is some debugging only code + rInf.DrawBackBrush( *this ); + const OUString aText(CH_TXT_ATR_SUBST_FIELDEND); + rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength())); + } + else if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen() + && CH_TXT_ATR_FIELDSTART == rInf.GetText()[sal_Int32(rInf.GetIdx())]) + { + assert(false); // this is some debugging only code + rInf.DrawBackBrush( *this ); + const OUString aText(CH_TXT_ATR_SUBST_FIELDSTART); + rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength())); + } + else if( GetLen() ) + { + rInf.DrawBackBrush( *this ); + rInf.DrawBorder( *this ); + + // do we have to repaint a post it portion? + if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() ) + mpNextPortion->PrePaint( rInf, this ); + + auto const* pWrongList = rInf.GetpWrongList(); + auto const* pGrammarCheckList = rInf.GetGrammarCheckList(); + auto const* pSmarttags = rInf.GetSmartTags(); + + const bool bWrong = nullptr != pWrongList; + const bool bGrammarCheck = nullptr != pGrammarCheckList; + const bool bSmartTags = nullptr != pSmarttags; + + if ( bWrong || bSmartTags || bGrammarCheck ) + rInf.DrawMarkedText( *this, rInf.GetLen(), bWrong, bSmartTags, bGrammarCheck ); + else + rInf.DrawText( *this, rInf.GetLen() ); + } +} + +bool SwTextPortion::GetExpText( const SwTextSizeInfo &, OUString & ) const +{ + return false; +} + +// Responsible for the justified paragraph. They calculate the blank +// count and the resulting added space. +TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf, + TextFrameIndex& rCharCnt) const +{ + TextFrameIndex nCnt(0); + TextFrameIndex nPos(0); + + if ( rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame())); + if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars()) + return TextFrameIndex(0); + } + + if ( InExpGrp() || PortionType::InputField == GetWhichPor() ) + { + if( !IsBlankPortion() && !InNumberGrp() && !IsCombinedPortion() ) + { + // OnWin() likes to return a blank instead of an empty string from + // time to time. We cannot use that here at all, however. + bool bOldOnWin = rInf.OnWin(); + const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false ); + + OUString aStr; + GetExpText( rInf, aStr ); + const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin ); + + nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this ); + nPos = TextFrameIndex(aStr.getLength()); + } + } + else if( !IsDropPortion() ) + { + nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this ); + nPos = GetLen(); + } + rCharCnt = rCharCnt + nPos; + return nCnt; +} + +long SwTextPortion::CalcSpacing( long nSpaceAdd, const SwTextSizeInfo &rInf ) const +{ + TextFrameIndex nCnt(0); + + if ( rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame())); + if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars()) + return 0; + } + + if ( InExpGrp() || PortionType::InputField == GetWhichPor() ) + { + if( !IsBlankPortion() && !InNumberGrp() && !IsCombinedPortion() ) + { + // OnWin() likes to return a blank instead of an empty string from + // time to time. We cannot use that here at all, however. + bool bOldOnWin = rInf.OnWin(); + const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false ); + + OUString aStr; + GetExpText( rInf, aStr ); + const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin ); + if( nSpaceAdd > 0 ) + nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this ); + else + { + nSpaceAdd = -nSpaceAdd; + nCnt = TextFrameIndex(aStr.getLength()); + } + } + } + else if( !IsDropPortion() ) + { + if( nSpaceAdd > 0 ) + nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this ); + else + { + nSpaceAdd = -nSpaceAdd; + nCnt = GetLen(); + SwLinePortion* pPor = GetNextPortion(); + + // we do not want an extra space in front of margin portions + if ( nCnt ) + { + while ( pPor && !pPor->Width() && ! pPor->IsHolePortion() ) + pPor = pPor->GetNextPortion(); + + if ( !pPor || pPor->InFixMargGrp() || pPor->IsHolePortion() ) + --nCnt; + } + } + } + + return sal_Int32(nCnt) * nSpaceAdd / SPACING_PRECISION_FACTOR; +} + +void SwTextPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Text( GetLen(), GetWhichPor(), Height(), Width() ); +} + +SwTextInputFieldPortion::SwTextInputFieldPortion() + : SwTextPortion() +{ + SetWhichPor( PortionType::InputField ); +} + +bool SwTextInputFieldPortion::Format(SwTextFormatInfo &rTextFormatInfo) +{ + return SwTextPortion::Format(rTextFormatInfo); +} + +void SwTextInputFieldPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if ( Width() ) + { + rInf.DrawViewOpt( *this, PortionType::InputField ); + SwTextSlot aPaintText( &rInf, this, true, true, OUString() ); + SwTextPortion::Paint( rInf ); + } + else + { + // highlight empty input field, elsewhere they are completely invisible for the user + SwRect aIntersect; + rInf.CalcRect(*this, &aIntersect); + const sal_uInt16 aAreaWidth = rInf.GetTextSize(OUString(' ')).Width(); + aIntersect.Left(aIntersect.Left() - aAreaWidth/2); + aIntersect.Width(aAreaWidth); + + if (aIntersect.HasArea() + && rInf.OnWin() + && SwViewOption::IsFieldShadings() + && !rInf.GetOpt().IsPagePreview()) + { + OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut()); + pOut->Push(PushFlags::LINECOLOR | PushFlags::FILLCOLOR); + pOut->SetFillColor(SwViewOption::GetFieldShadingsColor()); + pOut->SetLineColor(); + pOut->DrawRect(aIntersect.SVRect()); + pOut->Pop(); + } + } +} + +bool SwTextInputFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const +{ + sal_Int32 nIdx(rInf.GetIdx()); + sal_Int32 nLen(GetLen()); + if ( rInf.GetChar( rInf.GetIdx() ) == CH_TXT_ATR_INPUTFIELDSTART ) + { + ++nIdx; + --nLen; + } + if (rInf.GetChar(rInf.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND) + { + --nLen; + } + rText = rInf.GetText().copy( nIdx, std::min( nLen, rInf.GetText().getLength() - nIdx ) ); + + return true; +} + +SwPosSize SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +{ + SwTextSlot aFormatText( &rInf, this, true, false ); + if (rInf.GetLen() == TextFrameIndex(0)) + { + return SwPosSize( 0, 0 ); + } + + return rInf.GetTextSize(); +} + +SwHolePortion::SwHolePortion( const SwTextPortion &rPor ) + : nBlankWidth( 0 ) +{ + SetLen( TextFrameIndex(1) ); + Height( rPor.Height() ); + SetAscent( rPor.GetAscent() ); + SetWhichPor( PortionType::Hole ); +} + +SwLinePortion *SwHolePortion::Compress() { return this; } + +void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( !rInf.GetOut() ) + return; + + // #i16816# export stuff only needed for tagged pdf support + if (!SwTaggedPDFHelper::IsExportTaggedPDF( *rInf.GetOut()) ) + return; + + // #i68503# the hole must have no decoration for a consistent visual appearance + const SwFont* pOrigFont = rInf.GetFont(); + std::unique_ptr<SwFont> pHoleFont; + std::unique_ptr<SwFontSave> pFontSave; + if( pOrigFont->GetUnderline() != LINESTYLE_NONE + || pOrigFont->GetOverline() != LINESTYLE_NONE + || pOrigFont->GetStrikeout() != STRIKEOUT_NONE ) + { + pHoleFont.reset(new SwFont( *pOrigFont )); + pHoleFont->SetUnderline( LINESTYLE_NONE ); + pHoleFont->SetOverline( LINESTYLE_NONE ); + pHoleFont->SetStrikeout( STRIKEOUT_NONE ); + pFontSave.reset(new SwFontSave( rInf, pHoleFont.get() )); + } + + const OUString aText( ' ' ); + rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(1)); + + pFontSave.reset(); + pHoleFont.reset(); +} + +bool SwHolePortion::Format( SwTextFormatInfo &rInf ) +{ + return rInf.IsFull() || rInf.X() >= rInf.Width(); +} + +void SwHolePortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Text( GetLen(), GetWhichPor() ); +} + +void SwFieldMarkPortion::Paint( const SwTextPaintInfo & /*rInf*/) const +{ + // These shouldn't be painted! + //SwTextPortion::Paint(rInf); +} + +bool SwFieldMarkPortion::Format( SwTextFormatInfo & ) +{ + Width(0); + return false; +} + +void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo& rInf ) const +{ + SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx())); + + IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition); + + OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, + "Where is my form field bookmark???"); + + if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX) + { + const ICheckboxFieldmark* pCheckboxFm = dynamic_cast<ICheckboxFieldmark const*>(pBM); + bool bChecked = pCheckboxFm && pCheckboxFm->IsChecked(); + rInf.DrawCheckBox(*this, bChecked); + } +} + +bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo & rInf ) +{ + SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx())); + IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition); + OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, "Where is my form field bookmark???"); + if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX) + { + // the width of the checkbox portion is the same as its height since it's a square + // and that size depends on the font size. + // See: + // http://document-foundation-mail-archive.969070.n3.nabble.com/Wrong-copy-paste-in-SwFieldFormCheckboxPortion-Format-td4269112.html + Width( rInf.GetTextHeight( ) ); + Height( rInf.GetTextHeight( ) ); + SetAscent( rInf.GetAscent( ) ); + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx new file mode 100644 index 000000000..88e81a358 --- /dev/null +++ b/sw/source/core/text/portxt.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_PORTXT_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_PORTXT_HXX + +#include "porlin.hxx" + +class SwTextGuess; + +/// This portion represents a part of the paragraph string. +class SwTextPortion : public SwLinePortion +{ + void BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess ); + void BreakUnderflow( SwTextFormatInfo &rInf ); + bool Format_( SwTextFormatInfo &rInf ); + +public: + SwTextPortion(){ SetWhichPor( PortionType::Text ); } + static SwTextPortion * CopyLinePortion(const SwLinePortion &rPortion); + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void FormatEOL( SwTextFormatInfo &rInf ) override; + virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual long CalcSpacing( long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; + + // Counts the spaces for justified paragraph + TextFrameIndex GetSpaceCnt(const SwTextSizeInfo &rInf, TextFrameIndex& rCnt) const; + + bool CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGuess ); + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwTextInputFieldPortion : public SwTextPortion +{ +public: + SwTextInputFieldPortion(); + + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; + virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override; +}; + +class SwHolePortion : public SwLinePortion +{ + sal_uInt16 nBlankWidth; +public: + explicit SwHolePortion( const SwTextPortion &rPor ); + sal_uInt16 GetBlankWidth( ) const { return nBlankWidth; } + void SetBlankWidth( const sal_uInt16 nNew ) { nBlankWidth = nNew; } + virtual SwLinePortion *Compress() override; + virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + + // Accessibility: pass information about this portion to the PortionHandler + virtual void HandlePortion( SwPortionHandler& rPH ) const override; +}; + +class SwFieldMarkPortion : public SwTextPortion +{ + public: + SwFieldMarkPortion() : SwTextPortion() + { + SetWhichPor(PortionType::FieldMark); + } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; +}; + +class SwFieldFormCheckboxPortion : public SwTextPortion +{ +public: + SwFieldFormCheckboxPortion() : SwTextPortion() + { + SetWhichPor(PortionType::FieldFormCheckbox); + } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + virtual bool Format( SwTextFormatInfo &rInf ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/possiz.hxx b/sw/source/core/text/possiz.hxx new file mode 100644 index 000000000..eaad3597f --- /dev/null +++ b/sw/source/core/text/possiz.hxx @@ -0,0 +1,69 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_POSSIZ_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_POSSIZ_HXX + +#include <tools/gen.hxx> +#include <sal/types.h> + +// Compared to the SV sizes SwPosSize is always positive +class SwPosSize +{ + sal_uInt16 nWidth; + sal_uInt16 nHeight; +public: + SwPosSize( const sal_uInt16 nW = 0, const sal_uInt16 nH = 0 ) + : nWidth(nW) + , nHeight(nH) + { + } + explicit SwPosSize( const Size &rSize ) + : nWidth(sal_uInt16(rSize.Width())) + ,nHeight(sal_uInt16(rSize.Height())) + { + } +#if defined(__COVERITY__) + ~SwPosSize() COVERITY_NOEXCEPT_FALSE {} +#endif + sal_uInt16 Height() const { return nHeight; } + void Height( const sal_uInt16 nNew ) { nHeight = nNew; } + sal_uInt16 Width() const { return nWidth; } + void Width( const sal_uInt16 nNew ) { nWidth = nNew; } + Size SvLSize() const { return Size( nWidth, nHeight ); } + void SvLSize( const Size &rSize ) + { + nWidth = sal_uInt16(rSize.Width()); + nHeight = sal_uInt16(rSize.Height()); + } + void SvXSize( const Size &rSize ) + { + nHeight = sal_uInt16(rSize.Width()); + nWidth = sal_uInt16(rSize.Height()); + } + SwPosSize& operator=( const Size &rSize ) + { + nWidth = sal_uInt16(rSize.Width()); + nHeight = sal_uInt16(rSize.Height()); + return *this; + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx new file mode 100644 index 000000000..d4be4a1d3 --- /dev/null +++ b/sw/source/core/text/redlnitr.cxx @@ -0,0 +1,926 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <hintids.hxx> +#include <o3tl/safeint.hxx> +#include <svl/whiter.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <scriptinfo.hxx> +#include <swmodule.hxx> +#include <redline.hxx> +#include <txatbase.hxx> +#include <docary.hxx> +#include "itratr.hxx" +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <rootfrm.hxx> +#include <breakit.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/settings.hxx> +#include <txtfrm.hxx> +#include <ftnfrm.hxx> +#include <vcl/svapp.hxx> +#include "redlnitr.hxx" +#include <extinput.hxx> + +using namespace ::com::sun::star; + +namespace sw { + +std::unique_ptr<sw::MergedPara> +CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, + FrameMode const eMode) +{ + IDocumentRedlineAccess const& rIDRA = rTextNode.getIDocumentRedlineAccess(); + if (!rFrame.getRootFrame()->IsHideRedlines()) + { + return nullptr; + } + bool bHaveRedlines(false); + std::vector<SwTextNode *> nodes{ &rTextNode }; + std::vector<SwTableNode *> tables; + std::vector<SwSectionNode *> sections; + std::vector<sw::Extent> extents; + OUStringBuffer mergedText; + SwTextNode * pParaPropsNode(nullptr); + SwTextNode * pNode(&rTextNode); + sal_Int32 nLastEnd(0); + for (auto i = rIDRA.GetRedlinePos(rTextNode, RedlineType::Any); + i < rIDRA.GetRedlineTable().size(); ++i) + { + SwRangeRedline const*const pRed = rIDRA.GetRedlineTable()[i]; + + if (pNode->GetIndex() < pRed->Start()->nNode.GetIndex()) + break; + + if (pRed->GetType() != RedlineType::Delete) + continue; + + SwPosition const*const pStart(pRed->Start()); + SwPosition const*const pEnd(pRed->End()); + if (*pStart == *pEnd) + { // only allowed while moving (either way?) +// assert(IDocumentRedlineAccess::IsHideChanges(rIDRA.GetRedlineFlags())); + continue; + } + if (pStart->nNode.GetNode().IsTableNode()) + { + assert(&pEnd->nNode.GetNode() == &rTextNode && pEnd->nContent.GetIndex() == 0); + continue; // known pathology, ignore it + } + bHaveRedlines = true; + assert(pNode != &rTextNode || &pStart->nNode.GetNode() == &rTextNode); // detect calls with wrong start node + if (pStart->nContent != nLastEnd) // not 0 so we eliminate adjacent deletes + { + extents.emplace_back(pNode, nLastEnd, pStart->nContent.GetIndex()); + mergedText.append(std::u16string_view(pNode->GetText()).substr(nLastEnd, pStart->nContent.GetIndex() - nLastEnd)); + } + if (&pEnd->nNode.GetNode() != pNode) + { + if (pNode == &rTextNode) + { + pNode->SetRedlineMergeFlag(SwNode::Merge::First); + } // else: was already set before + int nLevel(0); + for (sal_uLong j = pNode->GetIndex() + 1; j < pEnd->nNode.GetIndex(); ++j) + { + SwNode *const pTmp(pNode->GetNodes()[j]); + if (nLevel == 0) + { + if (pTmp->IsTextNode()) + { + nodes.push_back(pTmp->GetTextNode()); + } + else if (pTmp->IsTableNode()) + { + tables.push_back(pTmp->GetTableNode()); + } + else if (pTmp->IsSectionNode()) + { + sections.push_back(pTmp->GetSectionNode()); + } + } + if (pTmp->IsStartNode()) + { + ++nLevel; + } + else if (pTmp->IsEndNode()) + { + --nLevel; + } + pTmp->SetRedlineMergeFlag(SwNode::Merge::Hidden); + } + // note: in DelLastPara() case, the end node is not actually merged + // and is likely a SwTableNode! + if (!pEnd->nNode.GetNode().IsTextNode()) + { + assert(pEnd->nNode != pStart->nNode); + // must set pNode too because it will mark the last node + pNode = nodes.back(); + assert(pNode == pNode->GetNodes()[pEnd->nNode.GetIndex() - 1]); + if (pNode != &rTextNode) + { // something might depend on last merged one being NonFirst? + pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst); + } + nLastEnd = pNode->Len(); + } + else + { + pNode = pEnd->nNode.GetNode().GetTextNode(); + nodes.push_back(pNode); + pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst); + nLastEnd = pEnd->nContent.GetIndex(); + } + } + else + { + nLastEnd = pEnd->nContent.GetIndex(); + } + } + if (pNode == &rTextNode) + { + if (rTextNode.GetRedlineMergeFlag() != SwNode::Merge::None) + { + rTextNode.SetRedlineMergeFlag(SwNode::Merge::None); + } + } + // Reset flag of the following text node since we know it's not merged; + // also any table/sections in between. + // * the following SwTextNode is in same nodes section as pNode (nLevel=0) + // * the start nodes that don't have a SwTextNode before them + // on their level, and their corresponding end nodes + // * the first SwTextNode inside each start node of the previous point + // Other (non-first) SwTextNodes in nested sections shouldn't be reset! + int nLevel(0); + for (sal_uLong j = pNode->GetIndex() + 1; j < pNode->GetNodes().Count(); ++j) + { + SwNode *const pTmp(pNode->GetNodes()[j]); + if (!pTmp->IsCreateFrameWhenHidingRedlines()) + { // clear stale flag caused by editing with redlines shown + pTmp->SetRedlineMergeFlag(SwNode::Merge::None); + } + if (pTmp->IsStartNode()) + { + ++nLevel; + } + else if (pTmp->IsEndNode()) + { + if (nLevel == 0) + { + break; // there is no following text node; avoid leaving section + } + --nLevel; + } + else if (pTmp->IsTextNode()) + { + if (nLevel == 0) + { + break; // done + } + else + { // skip everything other than 1st text node in section! + j = pTmp->EndOfSectionIndex() - 1; // will be incremented again + } + } + } + if (!bHaveRedlines) + { + if (rTextNode.IsInList() && !rTextNode.GetNum(rFrame.getRootFrame())) + { + rTextNode.AddToListRLHidden(); // try to add it... + } + return nullptr; + } + if (nLastEnd != pNode->Len()) + { + extents.emplace_back(pNode, nLastEnd, pNode->Len()); + mergedText.append(std::u16string_view(pNode->GetText()).substr(nLastEnd, pNode->Len() - nLastEnd)); + } + if (extents.empty()) // there was no text anywhere + { + assert(mergedText.isEmpty()); + pParaPropsNode = pNode; // if every node is empty, the last one wins + } + else + { + assert(!mergedText.isEmpty()); + pParaPropsNode = extents.begin()->pNode; // para props from first node that isn't empty + } +// pParaPropsNode = &rTextNode; // well, actually... + // keep lists up to date with visible nodes + if (pParaPropsNode->IsInList() && !pParaPropsNode->GetNum(rFrame.getRootFrame())) + { + pParaPropsNode->AddToListRLHidden(); // try to add it... + } + for (auto const pTextNode : nodes) + { + if (pTextNode != pParaPropsNode) + { + pTextNode->RemoveFromListRLHidden(); + } + } + if (eMode == FrameMode::Existing) + { + // remove existing footnote frames for first node; + // for non-first nodes with own frames, DelFrames will remove all + // (could possibly call lcl_ChangeFootnoteRef, not sure if worth it) + // note: must be done *before* changing listeners! + // for non-first nodes that are already merged with this frame, + // need to remove here too, otherwise footnotes can be removed only + // by lucky accident, e.g. TruncLines(). + auto itExtent(extents.begin()); + for (auto const pTextNode : nodes) + { + sal_Int32 nLast(0); + std::vector<std::pair<sal_Int32, sal_Int32>> hidden; + for ( ; itExtent != extents.end(); ++itExtent) + { + if (itExtent->pNode != pTextNode) + { + break; + } + if (itExtent->nStart != 0) + { + assert(itExtent->nStart != nLast); + hidden.emplace_back(nLast, itExtent->nStart); + } + nLast = itExtent->nEnd; + } + if (nLast != pTextNode->Len()) + { + hidden.emplace_back(nLast, pTextNode->Len()); + } + sw::RemoveFootnotesForNode(*rFrame.getRootFrame(), *pTextNode, &hidden); + } + // unfortunately DelFrames() must be done before StartListening too, + // otherwise footnotes cannot be deleted by SwTextFootnote::DelFrames! + auto const end(--nodes.rend()); + for (auto iter = nodes.rbegin(); iter != end; ++iter) + { + (**iter).DelFrames(rFrame.getRootFrame()); + } + // also delete tables & sections here; not necessary, but convenient + for (auto const pTableNode : tables) + { + pTableNode->DelFrames(rFrame.getRootFrame()); + } + for (auto const pSectionNode : sections) + { + pSectionNode->DelFrames(rFrame.getRootFrame()); + } + } + auto pRet(std::make_unique<sw::MergedPara>(rFrame, std::move(extents), + mergedText.makeStringAndClear(), pParaPropsNode, &rTextNode, + nodes.back())); + for (SwTextNode * pTmp : nodes) + { + pRet->listener.StartListening(pTmp); + } + rFrame.EndListeningAll(); + return pRet; +} + +} // namespace sw + +void SwAttrIter::InitFontAndAttrHandler( + SwTextNode const& rPropsNode, + SwTextNode const& rTextNode, + OUString const& rText, + bool const*const pbVertLayout, + bool const*const pbVertLayoutLRBT) +{ + // Build a font matching the default paragraph style: + SwFontAccess aFontAccess( &rPropsNode.GetAnyFormatColl(), m_pViewShell ); + // It is possible that Init is called more than once, e.g., in a + // SwTextFrame::FormatOnceMore situation or (since sw_redlinehide) + // from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt + // is an alias of m_pFont so it must not be deleted! + if (m_pFont) + { + *m_pFont = aFontAccess.Get()->GetFont(); + } + else + { + m_pFont = new SwFont( aFontAccess.Get()->GetFont() ); + } + + // set font to vertical if frame layout is vertical + // if it's a re-init, the vert flag never changes + bool bVertLayoutLRBT = false; + if (pbVertLayoutLRBT) + bVertLayoutLRBT = *pbVertLayoutLRBT; + if (pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout()) + { + m_pFont->SetVertical(m_pFont->GetOrientation(), true, bVertLayoutLRBT); + } + + // Initialize the default attribute of the attribute handler + // based on the attribute array cached together with the font. + // If any further attributes for the paragraph are given in pAttrSet + // consider them during construction of the default array, and apply + // them to the font + m_aAttrHandler.Init(aFontAccess.Get()->GetDefault(), rTextNode.GetpSwAttrSet(), + *rTextNode.getIDocumentSettingAccess(), m_pViewShell, *m_pFont, + pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout(), + bVertLayoutLRBT ); + + m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr; + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + m_pFont->SetActual( m_pScriptInfo->WhichFont(TextFrameIndex(0)) ); + + TextFrameIndex nChg(0); + size_t nCnt = 0; + + do + { + if ( nCnt >= m_pScriptInfo->CountScriptChg() ) + break; + nChg = m_pScriptInfo->GetScriptChg( nCnt ); + SwFontScript nTmp = SW_SCRIPTS; + switch ( m_pScriptInfo->GetScriptType( nCnt++ ) ) { + case i18n::ScriptType::ASIAN : + if( !m_aFontCacheIds[SwFontScript::CJK] ) nTmp = SwFontScript::CJK; + break; + case i18n::ScriptType::COMPLEX : + if( !m_aFontCacheIds[SwFontScript::CTL] ) nTmp = SwFontScript::CTL; + break; + default: + if( !m_aFontCacheIds[SwFontScript::Latin ] ) nTmp = SwFontScript::Latin; + } + if( nTmp < SW_SCRIPTS ) + { + m_pFont->CheckFontCacheId( m_pViewShell, nTmp ); + m_pFont->GetFontCacheId( m_aFontCacheIds[ nTmp ], m_aFontIdx[ nTmp ], nTmp ); + } + } + while (nChg < TextFrameIndex(rText.getLength())); +} + +void SwAttrIter::CtorInitAttrIter(SwTextNode & rTextNode, + SwScriptInfo & rScriptInfo, SwTextFrame const*const pFrame) +{ + // during HTML-Import it can happen, that no layout exists + SwRootFrame* pRootFrame = rTextNode.getIDocumentLayoutAccess().GetCurrentLayout(); + m_pViewShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr; + + m_pScriptInfo = &rScriptInfo; + + // set font to vertical if frame layout is vertical + bool bVertLayout = false; + bool bVertLayoutLRBT = false; + bool bRTL = false; + if ( pFrame ) + { + if ( pFrame->IsVertical() ) + { + bVertLayout = true; + } + if (pFrame->IsVertLRBT()) + { + bVertLayoutLRBT = true; + } + bRTL = pFrame->IsRightToLeft(); + m_pMergedPara = pFrame->GetMergedPara(); + } + + // determine script changes if not already done for current paragraph + assert(m_pScriptInfo); + if (m_pScriptInfo->GetInvalidityA() != TextFrameIndex(COMPLETE_STRING)) + m_pScriptInfo->InitScriptInfo(rTextNode, m_pMergedPara, bRTL); + + InitFontAndAttrHandler( + m_pMergedPara ? *m_pMergedPara->pParaPropsNode : rTextNode, + rTextNode, + m_pMergedPara ? m_pMergedPara->mergedText : rTextNode.GetText(), + & bVertLayout, + & bVertLayoutLRBT); + + m_nStartIndex = m_nEndIndex = m_nPosition = m_nChgCnt = 0; + m_nPropFont = 0; + SwDoc* pDoc = rTextNode.GetDoc(); + const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess(); + + // sw_redlinehide: this is a Ring - pExtInp is the first PaM that's inside + // the node. It's not clear whether there can be more than 1 PaM in the + // Ring, and this code doesn't handle that case; neither did the old code. + const SwExtTextInput* pExtInp = pDoc->GetExtTextInput( rTextNode ); + if (!pExtInp && m_pMergedPara) + { + SwTextNode const* pNode(&rTextNode); + for (auto const& rExtent : m_pMergedPara->extents) + { + if (rExtent.pNode != pNode) + { + pNode = rExtent.pNode; + pExtInp = pDoc->GetExtTextInput(*pNode); + if (pExtInp) + break; + } + } + } + const bool bShow = IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags()) + && pRootFrame && !pRootFrame->IsHideRedlines(); + if (pExtInp || m_pMergedPara || bShow) + { + SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any ); + if (SwRedlineTable::npos == nRedlPos && m_pMergedPara) + { + SwTextNode const* pNode(&rTextNode); + for (auto const& rExtent : m_pMergedPara->extents) + { // note: have to search because extents based only on Delete + if (rExtent.pNode != pNode) + { + pNode = rExtent.pNode; + nRedlPos = rIDRA.GetRedlinePos(*pNode, RedlineType::Any); + if (SwRedlineTable::npos != nRedlPos) + break; + } + } + // TODO this is true initially but after delete ops it may be false... need to delete m_pMerged somewhere? + // assert(SwRedlineTable::npos != nRedlPos); + assert(SwRedlineTable::npos != nRedlPos || m_pMergedPara->extents.size() <= 1); + } + if (pExtInp || m_pMergedPara || SwRedlineTable::npos != nRedlPos) + { + const std::vector<ExtTextInputAttr> *pArr = nullptr; + if( pExtInp ) + { + pArr = &pExtInp->GetAttrs(); + Seek( TextFrameIndex(0) ); + } + + m_pRedline.reset(new SwRedlineItr( rTextNode, *m_pFont, m_aAttrHandler, nRedlPos, + m_pMergedPara + ? SwRedlineItr::Mode::Hide + : bShow + ? SwRedlineItr::Mode::Show + : SwRedlineItr::Mode::Ignore, + pArr, pExtInp ? pExtInp->Start() : nullptr)); + + if( m_pRedline->IsOn() ) + ++m_nChgCnt; + } + } +} + +// The Redline-Iterator +// The following information/states exist in RedlineIterator: +// +// m_nFirst is the first index of RedlineTable, which overlaps with the paragraph. +// +// m_nAct is the currently active (if m_bOn is set) or the next possible index. +// m_nStart and m_nEnd give you the borders of the object within the paragraph. +// +// If m_bOn is set, the font has been manipulated according to it. +// +// If m_nAct is set to SwRedlineTable::npos (via Reset()), then currently no +// Redline is active, m_nStart and m_nEnd are invalid. +SwRedlineItr::SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt, + SwAttrHandler& rAH, sal_Int32 nRed, + Mode const mode, + const std::vector<ExtTextInputAttr> *pArr, + SwPosition const*const pExtInputStart) + : m_rDoc( *rTextNd.GetDoc() ) + , m_rAttrHandler( rAH ) + , m_nNdIdx( rTextNd.GetIndex() ) + , m_nFirst( nRed ) + , m_nAct( SwRedlineTable::npos ) + , m_bOn( false ) + , m_eMode( mode ) +{ + if( pArr ) + { + assert(pExtInputStart); + m_pExt.reset( new SwExtend(*pArr, pExtInputStart->nNode.GetIndex(), + pExtInputStart->nContent.GetIndex()) ); + } + else + m_pExt = nullptr; + assert(m_pExt || m_eMode != Mode::Ignore); // only create if necessary + Seek(rFnt, m_nNdIdx, 0, COMPLETE_STRING); +} + +SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE +{ + Clear( nullptr ); + m_pExt.reset(); +} + +// The return value of SwRedlineItr::Seek tells you if the current font +// has been manipulated by leaving (-1) or accessing (+1) of a section +short SwRedlineItr::Seek(SwFont& rFnt, + sal_uLong const nNode, sal_Int32 const nNew, sal_Int32 const nOld) +{ + short nRet = 0; + if( ExtOn() ) + return 0; // Abbreviation: if we're within an ExtendTextInputs + // there can't be other changes of attributes (not even by redlining) + assert(m_eMode == Mode::Hide || m_nNdIdx == nNode); + if (m_eMode == Mode::Show) + { + if (m_bOn) + { + if (nNew >= m_nEnd) + { + --nRet; + Clear_( &rFnt ); // We go behind the current section + ++m_nAct; // and check the next one + } + else if (nNew < m_nStart) + { + --nRet; + Clear_( &rFnt ); // We go in front of the current section + if (m_nAct > m_nFirst) + m_nAct = m_nFirst; // the test has to run from the beginning + else + return nRet + EnterExtend(rFnt, nNode, nNew); // There's none prior to us + } + else + return nRet + EnterExtend(rFnt, nNode, nNew); // We stayed in the same section + } + if (SwRedlineTable::npos == m_nAct || nOld > nNew) + m_nAct = m_nFirst; + + m_nStart = COMPLETE_STRING; + m_nEnd = COMPLETE_STRING; + + for ( ; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ; ++m_nAct) + { + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ]->CalcStartEnd(m_nNdIdx, m_nStart, m_nEnd); + + if (nNew < m_nEnd) + { + if (nNew >= m_nStart) // only possible candidate + { + m_bOn = true; + const SwRangeRedline *pRed = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ]; + + if (m_pSet) + m_pSet->ClearItem(); + else + { + SwAttrPool& rPool = + const_cast<SwDoc&>(m_rDoc).GetAttrPool(); + m_pSet = std::make_unique<SfxItemSet>(rPool, svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END-1>{}); + } + + if( 1 < pRed->GetStackCount() ) + FillHints( pRed->GetAuthor( 1 ), pRed->GetType( 1 ) ); + FillHints( pRed->GetAuthor(), pRed->GetType() ); + + SfxWhichIter aIter( *m_pSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + const SfxPoolItem* pItem; + if( ( nWhich < RES_CHRATR_END ) && + ( SfxItemState::SET == m_pSet->GetItemState( nWhich, true, &pItem ) ) ) + { + SwTextAttr* pAttr = MakeRedlineTextAttr( + const_cast<SwDoc&>(m_rDoc), + *const_cast<SfxPoolItem*>(pItem) ); + pAttr->SetPriorityAttr( true ); + m_Hints.push_back(pAttr); + m_rAttrHandler.PushAndChg( *pAttr, rFnt ); + } + nWhich = aIter.NextWhich(); + } + + ++nRet; + } + break; + } + m_nStart = COMPLETE_STRING; + m_nEnd = COMPLETE_STRING; + } + } + else if (m_eMode == Mode::Hide) + { // ... just iterate to update m_nAct for GetNextRedln(); + // there is no need to care about formatting in this mode + if (m_nAct == SwRedlineTable::npos || nOld == COMPLETE_STRING) + { // reset, or move backward + m_nAct = m_nFirst; + } + for ( ; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct) + { // only Start matters in this mode + // Seeks until it finds a RL that starts at or behind the seek pos. + // - then update m_nStart/m_nEnd to the intersection of it with the + // current node (if any). + // The only way to skip to a different node is if there is a Delete + // RL, so if there is no intersection we'll never skip again. + // Note: here, assume that delete can't nest inside delete! + SwRangeRedline const*const pRedline( + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[m_nAct]); + SwPosition const*const pStart(pRedline->Start()); + if (pRedline->GetType() == RedlineType::Delete + && (nNode < pStart->nNode.GetIndex() + || (nNode == pStart->nNode.GetIndex() + && nNew <= pStart->nContent.GetIndex()))) + { + pRedline->CalcStartEnd(nNode, m_nStart, m_nEnd); + break; + } + m_nStart = COMPLETE_STRING; + m_nEnd = COMPLETE_STRING; + } + } + return nRet + EnterExtend(rFnt, nNode, nNew); +} + +void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType ) +{ + switch ( eType ) + { + case RedlineType::Insert: + SW_MOD()->GetInsertAuthorAttr(nAuthor, *m_pSet); + break; + case RedlineType::Delete: + SW_MOD()->GetDeletedAuthorAttr(nAuthor, *m_pSet); + break; + case RedlineType::Format: + case RedlineType::FmtColl: + SW_MOD()->GetFormatAuthorAttr(nAuthor, *m_pSet); + break; + default: + break; + } +} + +void SwRedlineItr::ChangeTextAttr( SwFont* pFnt, SwTextAttr const &rHt, bool bChg ) +{ + OSL_ENSURE( IsOn(), "SwRedlineItr::ChangeTextAttr: Off?" ); + + if (m_eMode != Mode::Show && !m_pExt) + return; + + if( bChg ) + { + if (m_pExt && m_pExt->IsOn()) + m_rAttrHandler.PushAndChg( rHt, *m_pExt->GetFont() ); + else + m_rAttrHandler.PushAndChg( rHt, *pFnt ); + } + else + { + OSL_ENSURE( ! m_pExt || ! m_pExt->IsOn(), "Pop of attribute during opened extension" ); + m_rAttrHandler.PopAndChg( rHt, *pFnt ); + } +} + +void SwRedlineItr::Clear_( SwFont* pFnt ) +{ + OSL_ENSURE( m_bOn, "SwRedlineItr::Clear: Off?" ); + m_bOn = false; + for (auto const& hint : m_Hints) + { + if( pFnt ) + m_rAttrHandler.PopAndChg( *hint, *pFnt ); + else + m_rAttrHandler.Pop( *hint ); + SwTextAttr::Destroy(hint, const_cast<SwDoc&>(m_rDoc).GetAttrPool() ); + } + m_Hints.clear(); +} + +/// Ignore mode: does nothing. +/// Show mode: returns end of redline if currently in one, or start of next +/// Hide mode: returns start of next redline in current node, plus (if it's a +/// Delete) its end position and number of consecutive RLs +std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> +SwRedlineItr::GetNextRedln(sal_Int32 nNext, SwTextNode const*const pNode, + SwRedlineTable::size_type & rAct) +{ + sal_Int32 nStart(m_nStart); + sal_Int32 nEnd(m_nEnd); + nNext = NextExtend(pNode->GetIndex(), nNext); + if (m_eMode == Mode::Ignore || SwRedlineTable::npos == m_nFirst) + return std::make_pair(nNext, std::make_pair(nullptr, 0)); + if (SwRedlineTable::npos == rAct) + { + rAct = m_nFirst; + } + if (rAct != m_nAct) + { + while (rAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()) + { + SwRangeRedline const*const pRedline( + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]); + pRedline->CalcStartEnd(pNode->GetIndex(), nStart, nEnd); + if (m_eMode != Mode::Hide + || pRedline->GetType() == RedlineType::Delete) + { + break; + } + ++rAct; // Hide mode: search a Delete RL + } + } + if (rAct == m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()) + { + return std::make_pair(nNext, std::make_pair(nullptr, 0)); // no Delete here + } + if (m_bOn || (m_eMode == Mode::Show && nStart == 0)) + { // in Ignore mode, the end of redlines isn't relevant, except as returned in the second in the pair! + if (nEnd < nNext) + nNext = nEnd; + } + else if (nStart <= nNext) + { + if (m_eMode == Mode::Show) + { + nNext = nStart; + } + else + { + assert(m_eMode == Mode::Hide); + SwRangeRedline const* pRedline( + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]); + assert(pRedline->GetType() == RedlineType::Delete); //? + if (pRedline->GetType() == RedlineType::Delete) + { + nNext = nStart; + size_t nSkipped(1); // (consecutive) candidates to be skipped + while (rAct + nSkipped < + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()) + { + SwRangeRedline const*const pNext = + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct + nSkipped]; + if (*pRedline->End() < *pNext->Start()) + { + break; // done for now + } + else if (*pNext->Start() == *pRedline->End() && + pNext->GetType() == RedlineType::Delete) + { + // consecutive delete - continue + pRedline = pNext; + } + ++nSkipped; + } + return std::make_pair(nNext, std::make_pair(pRedline, nSkipped)); + } + } + } + return std::make_pair(nNext, std::make_pair(nullptr, 0)); +} + +bool SwRedlineItr::ChkSpecialUnderline_() const +{ + // If the underlining or the escapement is caused by redlining, + // we always apply the SpecialUnderlining, i.e. the underlining + // below the base line + for (SwTextAttr* pHint : m_Hints) + { + const sal_uInt16 nWhich = pHint->Which(); + if( RES_CHRATR_UNDERLINE == nWhich || + RES_CHRATR_ESCAPEMENT == nWhich ) + return true; + } + return false; +} + +bool SwRedlineItr::CheckLine( + sal_uLong const nStartNode, sal_Int32 const nChkStart, + sal_uLong const nEndNode, sal_Int32 nChkEnd) +{ + // note: previously this would return true in the (!m_bShow && m_pExt) + // case, but surely that was a bug? + if (m_nFirst == SwRedlineTable::npos || m_eMode != Mode::Show) + return false; + assert(nStartNode == nEndNode); (void) nStartNode; (void) nEndNode; + if( nChkEnd == nChkStart ) // empty lines look one char further + ++nChkEnd; + sal_Int32 nOldStart = m_nStart; + sal_Int32 nOldEnd = m_nEnd; + SwRedlineTable::size_type const nOldAct = m_nAct; + bool bRet = false; + + for (m_nAct = m_nFirst; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct) + { + m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ]->CalcStartEnd( m_nNdIdx, m_nStart, m_nEnd ); + if (nChkEnd < m_nStart) + break; + if (nChkStart <= m_nEnd && (nChkEnd > m_nStart || COMPLETE_STRING == m_nEnd)) + { + bRet = true; + break; + } + } + + m_nStart = nOldStart; + m_nEnd = nOldEnd; + m_nAct = nOldAct; + return bRet; +} + +void SwExtend::ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr ) +{ + if ( nAttr & ExtTextInputAttr::Underline ) + rFnt.SetUnderline( LINESTYLE_SINGLE ); + else if ( nAttr & ExtTextInputAttr::BoldUnderline ) + rFnt.SetUnderline( LINESTYLE_BOLD ); + else if ( nAttr & ExtTextInputAttr::DottedUnderline ) + rFnt.SetUnderline( LINESTYLE_DOTTED ); + else if ( nAttr & ExtTextInputAttr::DashDotUnderline ) + rFnt.SetUnderline( LINESTYLE_DOTTED ); + + if ( nAttr & ExtTextInputAttr::RedText ) + rFnt.SetColor( COL_RED ); + + if ( nAttr & ExtTextInputAttr::Highlight ) + { + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + rFnt.SetColor( rStyleSettings.GetHighlightTextColor() ); + rFnt.SetBackColor( new Color( rStyleSettings.GetHighlightColor() ) ); + } + if ( nAttr & ExtTextInputAttr::GrayWaveline ) + rFnt.SetGreyWave( true ); +} + +short SwExtend::Enter(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) +{ + OSL_ENSURE( !m_pFont, "SwExtend: Enter with Font" ); + if (nNode != m_nNode) + return 0; + OSL_ENSURE( !Inside(), "SwExtend: Enter without Leave" ); + m_nPos = nNew; + if( Inside() ) + { + m_pFont.reset( new SwFont(rFnt) ); + ActualizeFont( rFnt, m_rArr[m_nPos - m_nStart] ); + return 1; + } + return 0; +} + +bool SwExtend::Leave_(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) +{ + OSL_ENSURE(nNode == m_nNode && Inside(), "SwExtend: Leave without Enter"); + if (nNode != m_nNode) + return true; + const ExtTextInputAttr nOldAttr = m_rArr[m_nPos - m_nStart]; + m_nPos = nNew; + if( Inside() ) + { // We stayed within the ExtendText-section + const ExtTextInputAttr nAttr = m_rArr[m_nPos - m_nStart]; + if( nOldAttr != nAttr ) // Is there an (inner) change of attributes? + { + rFnt = *m_pFont; + ActualizeFont( rFnt, nAttr ); + } + } + else + { + rFnt = *m_pFont; + m_pFont.reset(); + return true; + } + return false; +} + +sal_Int32 SwExtend::Next(sal_uLong const nNode, sal_Int32 nNext) +{ + if (nNode != m_nNode) + return nNext; + if (m_nPos < m_nStart) + { + if (nNext > m_nStart) + nNext = m_nStart; + } + else if (m_nPos < m_nEnd) + { + sal_Int32 nIdx = m_nPos - m_nStart; + const ExtTextInputAttr nAttr = m_rArr[ nIdx ]; + while (o3tl::make_unsigned(++nIdx) < m_rArr.size() && nAttr == m_rArr[nIdx]) + ; //nothing + nIdx = nIdx + m_nStart; + if( nNext > nIdx ) + nNext = nIdx; + } + return nNext; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/redlnitr.hxx b/sw/source/core/text/redlnitr.hxx new file mode 100644 index 000000000..6fc39e14f --- /dev/null +++ b/sw/source/core/text/redlnitr.hxx @@ -0,0 +1,136 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_REDLNITR_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_REDLNITR_HXX + +#include <IDocumentRedlineAccess.hxx> + +#include <swfont.hxx> + +#include <vcl/commandevent.hxx> + +#include <cstddef> +#include <deque> +#include <memory> +#include <vector> + +class SwTextNode; +class SwDoc; +class SfxItemSet; +class SwAttrHandler; + +class SwExtend +{ + std::unique_ptr<SwFont> m_pFont; + const std::vector<ExtTextInputAttr> &m_rArr; + /// position of start of SwExtTextInput + sal_uLong const m_nNode; + sal_Int32 const m_nStart; + /// current position (inside) + sal_Int32 m_nPos; + /// position of end of SwExtTextInput (in same node as start) + sal_Int32 const m_nEnd; + bool Leave_(SwFont& rFnt, sal_uLong nNode, sal_Int32 nNew); + bool Inside() const { return (m_nPos >= m_nStart && m_nPos < m_nEnd); } + static void ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr ); +public: + SwExtend(const std::vector<ExtTextInputAttr> &rArr, + sal_uLong const nNode, sal_Int32 const nStart) + : m_rArr(rArr) + , m_nNode(nNode) + , m_nStart(nStart) + , m_nPos(COMPLETE_STRING) + , m_nEnd(m_nStart + rArr.size()) + {} + bool IsOn() const { return m_pFont != nullptr; } + void Reset() { m_pFont.reset(); m_nPos = COMPLETE_STRING; } + bool Leave(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) + { return m_pFont && Leave_(rFnt, nNode, nNew); } + short Enter(SwFont& rFnt, sal_uLong nNode, sal_Int32 nNew); + sal_Int32 Next(sal_uLong nNode, sal_Int32 nNext); + SwFont* GetFont() { return m_pFont.get(); } + void UpdateFont(SwFont &rFont) { ActualizeFont(rFont, m_rArr[m_nPos - m_nStart]); } +}; + +class SwRedlineItr +{ + std::deque<SwTextAttr *> m_Hints; + const SwDoc& m_rDoc; + SwAttrHandler& m_rAttrHandler; + std::unique_ptr<SfxItemSet> m_pSet; + std::unique_ptr<SwExtend> m_pExt; + // note: this isn't actually used in the merged-para (Hide) case + sal_uLong const m_nNdIdx; + SwRedlineTable::size_type const m_nFirst; + SwRedlineTable::size_type m_nAct; + sal_Int32 m_nStart; + sal_Int32 m_nEnd; + bool m_bOn; +public: + enum class Mode { Show, Ignore, Hide }; +private: + Mode const m_eMode; + + void Clear_( SwFont* pFnt ); + bool ChkSpecialUnderline_() const; + void FillHints( std::size_t nAuthor, RedlineType eType ); + short EnterExtend(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) + { + if (m_pExt) return m_pExt->Enter(rFnt, nNode, nNew); + return 0; + } + sal_Int32 NextExtend(sal_uLong const nNode, sal_Int32 const nNext) { + if (m_pExt) return m_pExt->Next(nNode, nNext); + return nNext; + } +public: + SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt, SwAttrHandler& rAH, + sal_Int32 nRedlPos, Mode mode, + const std::vector<ExtTextInputAttr> *pArr = nullptr, + SwPosition const* pExtInputStart = nullptr); + ~SwRedlineItr() COVERITY_NOEXCEPT_FALSE; + SwRedlineTable::size_type GetAct() const { return m_nAct; } + bool IsOn() const { return m_bOn || (m_pExt && m_pExt->IsOn()); } + void Clear( SwFont* pFnt ) { if (m_bOn) Clear_( pFnt ); } + void ChangeTextAttr( SwFont* pFnt, SwTextAttr const &rHt, bool bChg ); + short Seek(SwFont& rFnt, sal_uLong nNode, sal_Int32 nNew, sal_Int32 nOld); + void Reset() { + if (m_nAct != m_nFirst) m_nAct = SwRedlineTable::npos; + if (m_pExt) m_pExt->Reset(); + } + std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> GetNextRedln( + sal_Int32 nNext, SwTextNode const* pNode, SwRedlineTable::size_type & rAct); + bool ChkSpecialUnderline() const + { return IsOn() && ChkSpecialUnderline_(); } + bool CheckLine(sal_uLong nStartNode, sal_Int32 nChkStart, sal_uLong nEndNode, sal_Int32 nChkEnd); + bool LeaveExtend(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) + { return m_pExt->Leave(rFnt, nNode, nNew); } + bool ExtOn() { + if (m_pExt) return m_pExt->IsOn(); + return false; + } + void UpdateExtFont( SwFont &rFnt ) { + OSL_ENSURE( ExtOn(), "UpdateExtFont without ExtOn" ); + m_pExt->UpdateFont( rFnt ); + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtcache.cxx b/sw/source/core/text/txtcache.cxx new file mode 100644 index 000000000..17eedcc17 --- /dev/null +++ b/sw/source/core/text/txtcache.cxx @@ -0,0 +1,190 @@ +/* -*- 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 "txtcache.hxx" +#include <txtfrm.hxx> +#include "porlay.hxx" + +#include <sfx2/viewsh.hxx> +#include <view.hxx> + +SwTextLine::SwTextLine( SwTextFrame const *pFrame, std::unique_ptr<SwParaPortion> pNew ) : + SwCacheObj( static_cast<void const *>(pFrame) ), + pLine( std::move(pNew) ) +{ +} + +SwTextLine::~SwTextLine() +{ +} + +void SwTextLine::UpdateCachePos() +{ + // note: SwTextFrame lives longer than its SwTextLine, see ~SwTextFrame + assert(m_pOwner); + const_cast<SwTextFrame *>(static_cast<SwTextFrame const *>(m_pOwner))->SetCacheIdx(GetCachePos()); +} + +SwCacheObj *SwTextLineAccess::NewObj() +{ + return new SwTextLine( static_cast<SwTextFrame const *>(m_pOwner) ); +} + +SwParaPortion *SwTextLineAccess::GetPara() +{ + SwTextLine *pRet; + if ( m_pObj ) + pRet = static_cast<SwTextLine*>(m_pObj); + else + { + pRet = static_cast<SwTextLine*>(Get(false)); + const_cast<SwTextFrame *>(static_cast<SwTextFrame const *>(m_pOwner))->SetCacheIdx( pRet->GetCachePos() ); + } + if ( !pRet->GetPara() ) + pRet->SetPara( new SwParaPortion, true/*bDelete*/ ); + return pRet->GetPara(); +} + +SwTextLineAccess::SwTextLineAccess( const SwTextFrame *pOwn ) : + SwCacheAccess( *SwTextFrame::GetTextCache(), pOwn, pOwn->GetCacheIdx() ) +{ +} + +bool SwTextLineAccess::IsAvailable() const +{ + return m_pObj && static_cast<SwTextLine*>(m_pObj)->GetPara(); +} + +bool SwTextFrame::HasPara_() const +{ + SwTextLine *pTextLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()-> + Get( this, GetCacheIdx(), false )); + if ( pTextLine ) + { + if ( pTextLine->GetPara() ) + return true; + } + else + const_cast<SwTextFrame*>(this)->mnCacheIndex = USHRT_MAX; + + return false; +} + +SwParaPortion *SwTextFrame::GetPara() +{ + if ( GetCacheIdx() != USHRT_MAX ) + { + SwTextLine *pLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()-> + Get( this, GetCacheIdx(), false )); + if ( pLine ) + return pLine->GetPara(); + else + mnCacheIndex = USHRT_MAX; + } + return nullptr; +} + +void SwTextFrame::ClearPara() +{ + OSL_ENSURE( !IsLocked(), "+SwTextFrame::ClearPara: this is locked." ); + if ( !IsLocked() && GetCacheIdx() != USHRT_MAX ) + { + SwTextLine *pTextLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()-> + Get( this, GetCacheIdx(), false )); + if ( pTextLine ) + { + pTextLine->SetPara( nullptr, true/*bDelete*/ ); + } + else + mnCacheIndex = USHRT_MAX; + } +} + +void SwTextFrame::RemoveFromCache() +{ + if (GetCacheIdx() != USHRT_MAX) + { + s_pTextCache->Delete(this, GetCacheIdx()); + SetCacheIdx(USHRT_MAX); + } +} + +void SwTextFrame::SetPara( SwParaPortion *pNew, bool bDelete ) +{ + if ( GetCacheIdx() != USHRT_MAX ) + { + // Only change the information, the CacheObj stays there + SwTextLine *pTextLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()-> + Get( this, GetCacheIdx(), false )); + if ( pTextLine ) + { + pTextLine->SetPara( pNew, bDelete ); + } + else + { + OSL_ENSURE( !pNew, "+SetPara: Losing SwParaPortion" ); + mnCacheIndex = USHRT_MAX; + } + } + else if ( pNew ) + { // Insert a new one + SwTextLine *pTextLine = new SwTextLine( this, std::unique_ptr<SwParaPortion>(pNew) ); + if (SwTextFrame::GetTextCache()->Insert(pTextLine, false)) + mnCacheIndex = pTextLine->GetCachePos(); + else + { + OSL_FAIL( "+SetPara: InsertCache failed." ); + } + } +} + +/** Prevent the SwParaPortions of the *visible* paragraphs from being deleted; + they would just be recreated on the next paint. + + Heuristic: 100 per view are visible + + If the cache is too small, enlarge it to ensure there are sufficient free + entries for the layout so it doesn't have to throw away a node's + SwParaPortion when it starts formatting the next node. +*/ +SwSaveSetLRUOfst::SwSaveSetLRUOfst() +{ + sal_uInt16 nVisibleShells(0); + for (auto pView = SfxViewShell::GetFirst(true, checkSfxViewShell<SwView>); + pView != nullptr; + pView = SfxViewShell::GetNext(*pView, true, checkSfxViewShell<SwView>)) + { + ++nVisibleShells; + } + + sal_uInt16 const nPreserved(100 * nVisibleShells); + SwCache & rCache(*SwTextFrame::GetTextCache()); + if (rCache.GetCurMax() < nPreserved + 250) + { + rCache.IncreaseMax(nPreserved + 250 - rCache.GetCurMax()); + } + rCache.SetLRUOfst(nPreserved); +} + +SwSaveSetLRUOfst::~SwSaveSetLRUOfst() +{ + SwTextFrame::GetTextCache()->ResetLRUOfst(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtcache.hxx b/sw/source/core/text/txtcache.hxx new file mode 100644 index 000000000..d04a0eb63 --- /dev/null +++ b/sw/source/core/text/txtcache.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_TXTCACHE_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_TXTCACHE_HXX + +#include <swcache.hxx> +#include "porlay.hxx" +#include <memory> + +class SwTextFrame; + +class SwTextLine : public SwCacheObj +{ + std::unique_ptr<SwParaPortion> pLine; + + virtual void UpdateCachePos() override; + +public: + SwTextLine( SwTextFrame const *pFrame, std::unique_ptr<SwParaPortion> pNew = nullptr ); + virtual ~SwTextLine() override; + + SwParaPortion *GetPara() { return pLine.get(); } + const SwParaPortion *GetPara() const { return pLine.get(); } + + void SetPara(SwParaPortion* pNew, bool bDelete) + { + if (!bDelete) + (void)pLine.release(); + pLine.reset(pNew); + } +}; + +class SwTextLineAccess : public SwCacheAccess +{ + +protected: + virtual SwCacheObj *NewObj() override; + +public: + explicit SwTextLineAccess( const SwTextFrame *pOwner ); + + SwParaPortion *GetPara(); + + bool IsAvailable() const; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtdrop.cxx b/sw/source/core/text/txtdrop.cxx new file mode 100644 index 000000000..b1a3bb121 --- /dev/null +++ b/sw/source/core/text/txtdrop.cxx @@ -0,0 +1,1079 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/svapp.hxx> +#include <paratr.hxx> +#include <txtfrm.hxx> +#include <charfmt.hxx> +#include <viewopt.hxx> +#include <viewsh.hxx> +#include "pordrop.hxx" +#include "itrform2.hxx" +#include "txtpaint.hxx" +#include <breakit.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <editeng/langitem.hxx> +#include <charatr.hxx> +#include <editeng/fhgtitem.hxx> +#include <calbck.hxx> +#include <doc.hxx> + +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star; + +/** + * Calculates if a drop caps portion intersects with a fly + * The width and height of the drop caps portion are passed as arguments, + * the position is calculated from the values in rInf + */ +static bool lcl_IsDropFlyInter( const SwTextFormatInfo &rInf, + sal_uInt16 nWidth, sal_uInt16 nHeight ) +{ + const SwTextFly& rTextFly = rInf.GetTextFly(); + if( rTextFly.IsOn() ) + { + SwRect aRect( rInf.GetTextFrame()->getFrameArea().Pos(), Size( nWidth, nHeight) ); + aRect.Pos() += rInf.GetTextFrame()->getFramePrintArea().Pos(); + aRect.Pos().AdjustX(rInf.X() ); + aRect.Pos().setY( rInf.Y() ); + aRect = rTextFly.GetFrame( aRect ); + return aRect.HasArea(); + } + + return false; +} + +namespace { + +class SwDropSave +{ + SwTextPaintInfo* pInf; + sal_Int32 nIdx; + sal_Int32 nLen; + long nX; + long nY; + +public: + explicit SwDropSave( const SwTextPaintInfo &rInf ); + ~SwDropSave(); +}; + +} + +SwDropSave::SwDropSave( const SwTextPaintInfo &rInf ) : + pInf( const_cast<SwTextPaintInfo*>(&rInf) ), nIdx( rInf.GetIdx() ), + nLen( rInf.GetLen() ), nX( rInf.X() ), nY( rInf.Y() ) +{ +} + +SwDropSave::~SwDropSave() +{ + pInf->SetIdx(TextFrameIndex(nIdx)); + pInf->SetLen(TextFrameIndex(nLen)); + pInf->X( nX ); + pInf->Y( nY ); +} + +/// SwDropPortionPart DTor +SwDropPortionPart::~SwDropPortionPart() +{ + pFollow.reset(); + pFnt.reset(); +} + +/// SwDropPortion CTor, DTor +SwDropPortion::SwDropPortion( const sal_uInt16 nLineCnt, + const sal_uInt16 nDrpHeight, + const sal_uInt16 nDrpDescent, + const sal_uInt16 nDist ) + : nLines( nLineCnt ), + nDropHeight(nDrpHeight), + nDropDescent(nDrpDescent), + nDistance(nDist), + nFix(0), + nY(0) +{ + SetWhichPor( PortionType::Drop ); +} + +SwDropPortion::~SwDropPortion() +{ + pPart.reset(); +} + +/// nWishLen = 0 indicates that we want a whole word +sal_Int32 SwTextNode::GetDropLen( sal_Int32 nWishLen ) const +{ + sal_Int32 nEnd = GetText().getLength(); + if( nWishLen && nWishLen < nEnd ) + nEnd = nWishLen; + + if (! nWishLen) + { + // find first word + const SwAttrSet& rAttrSet = GetSwAttrSet(); + const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText( GetText(), 0 ); + + LanguageType eLanguage; + + switch ( nTextScript ) + { + case i18n::ScriptType::ASIAN : + eLanguage = rAttrSet.GetCJKLanguage().GetLanguage(); + break; + case i18n::ScriptType::COMPLEX : + eLanguage = rAttrSet.GetCTLLanguage().GetLanguage(); + break; + default : + eLanguage = rAttrSet.GetLanguage().GetLanguage(); + break; + } + + Boundary aBound = + g_pBreakIt->GetBreakIter()->getWordBoundary( GetText(), 0, + g_pBreakIt->GetLocale( eLanguage ), WordType::DICTIONARY_WORD, true ); + + nEnd = aBound.endPos; + } + + sal_Int32 i = 0; + for( ; i < nEnd; ++i ) + { + sal_Unicode const cChar = GetText()[i]; + if( CH_TAB == cChar || CH_BREAK == cChar || + (( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar ) + && GetTextAttrForCharAt(i)) ) + break; + } + return i; +} + +/// nWishLen = 0 indicates that we want a whole word +TextFrameIndex SwTextFrame::GetDropLen(TextFrameIndex const nWishLen) const +{ + TextFrameIndex nEnd(GetText().getLength()); + if (nWishLen && nWishLen < nEnd) + nEnd = nWishLen; + + if (! nWishLen) + { + // find first word + const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet(); + const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText(GetText(), 0); + + LanguageType eLanguage; + + switch ( nTextScript ) + { + case i18n::ScriptType::ASIAN : + eLanguage = rAttrSet.GetCJKLanguage().GetLanguage(); + break; + case i18n::ScriptType::COMPLEX : + eLanguage = rAttrSet.GetCTLLanguage().GetLanguage(); + break; + default : + eLanguage = rAttrSet.GetLanguage().GetLanguage(); + break; + } + + Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( + GetText(), 0, g_pBreakIt->GetLocale(eLanguage), + WordType::DICTIONARY_WORD, true ); + + nEnd = TextFrameIndex(aBound.endPos); + } + + TextFrameIndex i(0); + for ( ; i < nEnd; ++i) + { + sal_Unicode const cChar = GetText()[sal_Int32(i)]; + if (CH_TAB == cChar || CH_BREAK == cChar || + CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) + { +#ifndef NDEBUG + if (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) + { + std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(i)); + assert(pos.first->GetTextAttrForCharAt(pos.second) != nullptr); + } +#endif + break; + } + } + return i; +} + +/** + * If a dropcap is found the return value is true otherwise false. The + * drop cap sizes passed back by reference are font height, drop height + * and drop descent. + */ +bool SwTextNode::GetDropSize(int& rFontHeight, int& rDropHeight, int& rDropDescent) const +{ + rFontHeight = 0; + rDropHeight = 0; + rDropDescent =0; + + const SwAttrSet& rSet = GetSwAttrSet(); + const SwFormatDrop& rDrop = rSet.GetDrop(); + + // Return (0,0) if there is no drop cap at this paragraph + if( 1 >= rDrop.GetLines() || + ( !rDrop.GetChars() && !rDrop.GetWholeWord() ) ) + { + return false; + } + + // get text frame + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for( SwTextFrame* pLastFrame = aIter.First(); pLastFrame; pLastFrame = aIter.Next() ) + { + // Only (master-) text frames can have a drop cap. + if (!pLastFrame->IsFollow() && + pLastFrame->GetTextNodeForFirstText() == this) + { + + if( !pLastFrame->HasPara() ) + pLastFrame->GetFormatted(); + + if ( !pLastFrame->IsEmpty() ) + { + const SwParaPortion* pPara = pLastFrame->GetPara(); + OSL_ENSURE( pPara, "GetDropSize could not find the ParaPortion, I'll guess the drop cap size" ); + + if ( pPara ) + { + const SwLinePortion* pFirstPor = pPara->GetFirstPortion(); + if (pFirstPor && pFirstPor->IsDropPortion()) + { + const SwDropPortion* pDrop = static_cast<const SwDropPortion*>(pFirstPor); + rDropHeight = pDrop->GetDropHeight(); + rDropDescent = pDrop->GetDropDescent(); + if (const SwFont *pFont = pDrop->GetFnt()) + rFontHeight = pFont->GetSize(pFont->GetActual()).Height(); + else + { + const SvxFontHeightItem& rItem = rSet.Get(RES_CHRATR_FONTSIZE); + rFontHeight = rItem.GetHeight(); + } + } + } + } + break; + } + } + + if (rFontHeight==0 && rDropHeight==0 && rDropDescent==0) + { + const sal_uInt16 nLines = rDrop.GetLines(); + + const SvxFontHeightItem& rItem = rSet.Get( RES_CHRATR_FONTSIZE ); + rFontHeight = rItem.GetHeight(); + rDropHeight = nLines * rFontHeight; + rDropDescent = rFontHeight / 5; + return false; + } + + return true; +} + +/// Manipulate the width, otherwise the chars are being stretched +void SwDropPortion::PaintText( const SwTextPaintInfo &rInf ) const +{ + OSL_ENSURE( nDropHeight && pPart && nLines != 1, "Drop Portion painted twice" ); + + const SwDropPortionPart* pCurrPart = GetPart(); + const TextFrameIndex nOldLen = GetLen(); + const sal_uInt16 nOldWidth = Width(); + const sal_uInt16 nOldAscent = GetAscent(); + + const SwTwips nBasePosY = rInf.Y(); + const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY + nY ); + const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent + nY ); + SwDropSave aSave( rInf ); + // for text inside drop portions we let vcl handle the text directions + SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); + aLayoutModeModifier.SetAuto(); + + while ( pCurrPart ) + { + const_cast<SwDropPortion*>(this)->SetLen( pCurrPart->GetLen() ); + const_cast<SwDropPortion*>(this)->Width( pCurrPart->GetWidth() ); + const_cast<SwTextPaintInfo&>(rInf).SetLen( pCurrPart->GetLen() ); + SwFontSave aFontSave( rInf, &pCurrPart->GetFont() ); + const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext()); + const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev()); + + if ( rInf.OnWin() && + !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() && + (!pCurrPart->GetFont().GetBackColor() || *pCurrPart->GetFont().GetBackColor() == COL_TRANSPARENT) ) + { + rInf.DrawBackground( *this ); + } + + SwTextPortion::Paint( rInf ); + + const_cast<SwTextPaintInfo&>(rInf).SetIdx( rInf.GetIdx() + pCurrPart->GetLen() ); + const_cast<SwTextPaintInfo&>(rInf).X( rInf.X() + pCurrPart->GetWidth() ); + pCurrPart = pCurrPart->GetFollow(); + } + + const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY ); + const_cast<SwDropPortion*>(this)->Width( nOldWidth ); + const_cast<SwDropPortion*>(this)->SetLen( nOldLen ); + const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent ); + const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false); + const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false); +} + +void SwDropPortion::PaintDrop( const SwTextPaintInfo &rInf ) const +{ + // normal output is being done during the normal painting + if( ! nDropHeight || ! pPart || nLines == 1 ) + return; + + // set the lying values + const sal_uInt16 nOldHeight = Height(); + const sal_uInt16 nOldWidth = Width(); + const sal_uInt16 nOldAscent = GetAscent(); + const SwTwips nOldPosY = rInf.Y(); + const SwTwips nOldPosX = rInf.X(); + const SwParaPortion *pPara = rInf.GetParaPortion(); + const Point aOutPos( nOldPosX, nOldPosY - pPara->GetAscent() + - pPara->GetRealHeight() + pPara->Height() ); + // make good for retouching + + // Set baseline + const_cast<SwTextPaintInfo&>(rInf).Y( aOutPos.Y() + nDropHeight ); + + // for background + const_cast<SwDropPortion*>(this)->Height( nDropHeight + nDropDescent ); + const_cast<SwDropPortion*>(this)->SetAscent( nDropHeight ); + + // Always adapt Clipregion to us, never set it off using the existing ClipRect + // as that could be set for the line + SwRect aClipRect; + if ( rInf.OnWin() ) + { + aClipRect = SwRect( aOutPos, SvLSize() ); + aClipRect.Intersection( rInf.GetPaintRect() ); + } + SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) ); + aClip.ChgClip( aClipRect, rInf.GetTextFrame() ); + + // Just do, what we always do ... + PaintText( rInf ); + + // save old values + const_cast<SwDropPortion*>(this)->Height( nOldHeight ); + const_cast<SwDropPortion*>(this)->Width( nOldWidth ); + const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent ); + const_cast<SwTextPaintInfo&>(rInf).Y( nOldPosY ); +} + +void SwDropPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + // normal output is being done here + if( ! nDropHeight || ! pPart || 1 == nLines ) + { + if ( rInf.OnWin() && + !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() ) + rInf.DrawBackground( *this ); + + // make sure that font is not rotated + std::unique_ptr<SwFont> pTmpFont; + if ( rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ) ) + { + pTmpFont.reset(new SwFont( *rInf.GetFont() )); + pTmpFont->SetVertical( 0, rInf.GetTextFrame()->IsVertical() ); + } + + SwFontSave aFontSave( rInf, pTmpFont.get() ); + // for text inside drop portions we let vcl handle the text directions + SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); + aLayoutModeModifier.SetAuto(); + + SwTextPortion::Paint( rInf ); + } +} + +bool SwDropPortion::FormatText( SwTextFormatInfo &rInf ) +{ + const TextFrameIndex nOldLen = GetLen(); + const TextFrameIndex nOldInfLen = rInf.GetLen(); + if (!SwTextPortion::Format( rInf )) + return false; + + // looks like shit, but what can we do? + rInf.SetUnderflow( nullptr ); + Truncate(); + SetLen( nOldLen ); + rInf.SetLen( nOldInfLen ); + + return true; +} + +SwPosSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const +{ + sal_uInt16 nMyX = 0; + TextFrameIndex nIdx(0); + + const SwDropPortionPart* pCurrPart = GetPart(); + + // skip parts + while ( pCurrPart && nIdx + pCurrPart->GetLen() < rInf.GetLen() ) + { + nMyX = nMyX + pCurrPart->GetWidth(); + nIdx = nIdx + pCurrPart->GetLen(); + pCurrPart = pCurrPart->GetFollow(); + } + + TextFrameIndex const nOldIdx = rInf.GetIdx(); + TextFrameIndex const nOldLen = rInf.GetLen(); + + const_cast<SwTextSizeInfo&>(rInf).SetIdx( nIdx ); + const_cast<SwTextSizeInfo&>(rInf).SetLen( rInf.GetLen() - nIdx ); + + if( pCurrPart ) + { + const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext()); + const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev()); + } + + // robust + SwFontSave aFontSave( rInf, pCurrPart ? &pCurrPart->GetFont() : nullptr ); + SwPosSize aPosSize( SwTextPortion::GetTextSize( rInf ) ); + aPosSize.Width( aPosSize.Width() + nMyX ); + + const_cast<SwTextSizeInfo&>(rInf).SetIdx( nOldIdx ); + const_cast<SwTextSizeInfo&>(rInf).SetLen( nOldLen ); + if( pCurrPart ) + { + const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false); + const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false); + } + + return aPosSize; +} + +TextFrameIndex SwDropPortion::GetModelPositionForViewPoint(const sal_uInt16) const +{ + return TextFrameIndex(0); +} + +void SwTextFormatter::CalcDropHeight( const sal_uInt16 nLines ) +{ + const SwLinePortion *const pOldCurr = GetCurr(); + sal_uInt16 nDropHght = 0; + sal_uInt16 nAscent = 0; + sal_uInt16 nHeight = 0; + sal_uInt16 nDropLns = 0; + const bool bRegisterOld = IsRegisterOn(); + m_bRegisterOn = false; + + Top(); + + while( GetCurr()->IsDummy() ) + { + if ( !Next() ) + break; + } + + // If we have only one line we return 0 + if( GetNext() || GetDropLines() == 1 ) + { + for( ; nDropLns < nLines; nDropLns++ ) + { + if ( GetCurr()->IsDummy() ) + break; + else + { + CalcAscentAndHeight( nAscent, nHeight ); + nDropHght = nDropHght + nHeight; + m_bRegisterOn = bRegisterOld; + } + if ( !Next() ) + { + nDropLns++; + break; + } + } + + // We hit the line ascent when reaching the last line! + nDropHght = nDropHght - nHeight; + nDropHght = nDropHght + nAscent; + Top(); + } + m_bRegisterOn = bRegisterOld; + SetDropDescent( nHeight - nAscent ); + SetDropHeight( nDropHght ); + SetDropLines( nDropLns ); + // Find old position! + while( pOldCurr != GetCurr() ) + { + if( !Next() ) + { + OSL_ENSURE( false, "SwTextFormatter::_CalcDropHeight: left Toulouse" ); + break; + } + } +} + +/** + * We assume that the font height doesn't change and that at first there + * are at least as many lines, as the DropCap-setting claims + */ +void SwTextFormatter::GuessDropHeight( const sal_uInt16 nLines ) +{ + OSL_ENSURE( nLines, "GuessDropHeight: Give me more Lines!" ); + sal_uInt16 nAscent = 0; + sal_uInt16 nHeight = 0; + SetDropLines( nLines ); + if ( GetDropLines() > 1 ) + { + CalcRealHeight(); + CalcAscentAndHeight( nAscent, nHeight ); + } + SetDropDescent( nHeight - nAscent ); + SetDropHeight( nHeight * nLines - GetDropDescent() ); +} + +SwDropPortion *SwTextFormatter::NewDropPortion( SwTextFormatInfo &rInf ) +{ + if( !m_pDropFormat ) + return nullptr; + + TextFrameIndex nPorLen(m_pDropFormat->GetWholeWord() ? 0 : m_pDropFormat->GetChars()); + nPorLen = m_pFrame->GetDropLen( nPorLen ); + if( !nPorLen ) + { + ClearDropFormat(); + return nullptr; + } + + SwDropPortion *pDropPor = nullptr; + + // first or second round? + if ( !( GetDropHeight() || IsOnceMore() ) ) + { + if ( GetNext() ) + CalcDropHeight( m_pDropFormat->GetLines() ); + else + GuessDropHeight( m_pDropFormat->GetLines() ); + } + + // the DropPortion + if( GetDropHeight() ) + pDropPor = new SwDropPortion( GetDropLines(), GetDropHeight(), + GetDropDescent(), m_pDropFormat->GetDistance() ); + else + pDropPor = new SwDropPortion( 0,0,0,m_pDropFormat->GetDistance() ); + + pDropPor->SetLen( nPorLen ); + + // If it was not possible to create a proper drop cap portion + // due to avoiding endless loops. We return a drop cap portion + // with an empty SwDropCapPart. For these portions the current + // font is used. + if ( GetDropLines() < 2 ) + { + SetPaintDrop( true ); + return pDropPor; + } + + // build DropPortionParts: + OSL_ENSURE( ! rInf.GetIdx(), "Drop Portion not at 0 position!" ); + TextFrameIndex nNextChg(0); + const SwCharFormat* pFormat = m_pDropFormat->GetCharFormat(); + SwDropPortionPart* pCurrPart = nullptr; + + while ( nNextChg < nPorLen ) + { + // check for attribute changes and if the portion has to split: + Seek( nNextChg ); + + // the font is deleted in the destructor of the drop portion part + SwFont* pTmpFnt = new SwFont( *rInf.GetFont() ); + if ( pFormat ) + { + const SwAttrSet& rSet = pFormat->GetAttrSet(); + pTmpFnt->SetDiffFnt(&rSet, &m_pFrame->GetDoc().getIDocumentSettingAccess()); + } + + // we do not allow a vertical font for the drop portion + pTmpFnt->SetVertical( 0, rInf.GetTextFrame()->IsVertical() ); + + // find next attribute change / script change + const TextFrameIndex nTmpIdx = nNextChg; + TextFrameIndex nNextAttr = GetNextAttr(); + nNextChg = m_pScriptInfo->NextScriptChg( nTmpIdx ); + if( nNextChg > nNextAttr ) + nNextChg = nNextAttr; + if ( nNextChg > nPorLen ) + nNextChg = nPorLen; + + std::unique_ptr<SwDropPortionPart> pPart( + new SwDropPortionPart( *pTmpFnt, nNextChg - nTmpIdx ) ); + auto pPartTemp = pPart.get(); + + if ( ! pCurrPart ) + pDropPor->SetPart( std::move(pPart) ); + else + pCurrPart->SetFollow( std::move(pPart) ); + + pCurrPart = pPartTemp; + } + + SetPaintDrop( true ); + return pDropPor; +} + +void SwTextPainter::PaintDropPortion() +{ + const SwDropPortion *pDrop = GetInfo().GetParaPortion()->FindDropPortion(); + OSL_ENSURE( pDrop, "DrapCop-Portion not available." ); + if( !pDrop ) + return; + + const SwTwips nOldY = GetInfo().Y(); + + Top(); + + GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() ); + GetInfo().ResetSpaceIdx(); + GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() ); + GetInfo().ResetKanaIdx(); + + // 8047: Drops and Dummies + while( !m_pCurr->GetLen() && Next() ) + ; + + // MarginPortion and Adjustment! + const SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + long nX = 0; + while( pPor && !pPor->IsDropPortion() ) + { + nX = nX + pPor->Width(); + pPor = pPor->GetNextPortion(); + } + Point aLineOrigin( GetTopLeft() ); + + aLineOrigin.AdjustX(nX ); + sal_uInt16 nTmpAscent, nTmpHeight; + CalcAscentAndHeight( nTmpAscent, nTmpHeight ); + aLineOrigin.AdjustY(nTmpAscent ); + GetInfo().SetIdx( GetStart() ); + GetInfo().SetPos( aLineOrigin ); + GetInfo().SetLen( pDrop->GetLen() ); + + pDrop->PaintDrop( GetInfo() ); + + GetInfo().Y( nOldY ); +} + +// Since the calculation of the font size is expensive, this is being +// channeled through a DropCapCache +#define DROP_CACHE_SIZE 10 + +class SwDropCapCache +{ + const void* aFontCacheId[ DROP_CACHE_SIZE ] = {}; + OUString aText[ DROP_CACHE_SIZE ]; + sal_uInt16 aFactor[ DROP_CACHE_SIZE ]; + sal_uInt16 aWishedHeight[ DROP_CACHE_SIZE ] = {}; + short aDescent[ DROP_CACHE_SIZE ]; + sal_uInt16 nIndex = 0; +public: + SwDropCapCache() = default; + void CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf ); +}; + +void SwDropPortion::DeleteDropCapCache() +{ + delete pDropCapCache; +} + +void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf ) +{ + const void* nFntCacheId = nullptr; + sal_uInt16 nTmpIdx = 0; + + OSL_ENSURE( pDrop->GetPart(),"DropPortion without part during font calculation"); + + SwDropPortionPart* pCurrPart = pDrop->GetPart(); + const bool bUseCache = ! pCurrPart->GetFollow() && !pCurrPart->GetFont().HasBorder(); + TextFrameIndex nIdx = rInf.GetIdx(); + OUString aStr(rInf.GetText().copy(sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))); + + long nDescent = 0; + long nFactor = -1; + + if ( bUseCache ) + { + SwFont& rFnt = pCurrPart->GetFont(); + rFnt.CheckFontCacheId( rInf.GetVsh(), rFnt.GetActual() ); + rFnt.GetFontCacheId( nFntCacheId, nTmpIdx, rFnt.GetActual() ); + + nTmpIdx = 0; + + while( nTmpIdx < DROP_CACHE_SIZE && + ( aText[ nTmpIdx ] != aStr || aFontCacheId[ nTmpIdx ] != nFntCacheId || + aWishedHeight[ nTmpIdx ] != pDrop->GetDropHeight() ) ) + ++nTmpIdx; + } + + // we have to calculate a new font scaling factor if + // 1. we did not find a scaling factor in the cache or + // 2. we are not allowed to use the cache because the drop portion + // consists of more than one part + if( nTmpIdx >= DROP_CACHE_SIZE || ! bUseCache ) + { + ++nIndex; + nIndex %= DROP_CACHE_SIZE; + nTmpIdx = nIndex; + + long nWishedHeight = pDrop->GetDropHeight(); + long nAscent = 0; + + // find out biggest font size for initial scaling factor + long nMaxFontHeight = 1; + while ( pCurrPart ) + { + const SwFont& rFnt = pCurrPart->GetFont(); + const long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() ); + if ( nCurrHeight > nMaxFontHeight ) + nMaxFontHeight = nCurrHeight; + + pCurrPart = pCurrPart->GetFollow(); + } + + nFactor = ( 1000 * nWishedHeight ) / nMaxFontHeight; + + if ( bUseCache ) + { + // save keys for cache + aFontCacheId[ nTmpIdx ] = nFntCacheId; + aText[ nTmpIdx ] = aStr; + aWishedHeight[ nTmpIdx ] = sal_uInt16(nWishedHeight); + // save initial scaling factor + aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor); + } + + bool bGrow = (pDrop->GetLen() != TextFrameIndex(0)); + + // for growing control + long nMax = USHRT_MAX; + long nMin = 0; +#if OSL_DEBUG_LEVEL > 1 + long nGrow = 0; +#endif + + bool bWinUsed = false; + vcl::Font aOldFnt; + MapMode aOldMap( MapUnit::MapTwip ); + OutputDevice* pOut = rInf.GetOut(); + OutputDevice* pWin; + if( rInf.GetVsh() && rInf.GetVsh()->GetWin() ) + pWin = rInf.GetVsh()->GetWin(); + else + pWin = Application::GetDefaultDevice(); + + while( bGrow ) + { + // reset pCurrPart to first part + pCurrPart = pDrop->GetPart(); + bool bFirstGlyphRect = true; + tools::Rectangle aCommonRect, aRect; + + while ( pCurrPart ) + { + // current font + SwFont& rFnt = pCurrPart->GetFont(); + + // Get height including proportion + const long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() ); + + // Get without proportion + const sal_uInt8 nOldProp = rFnt.GetPropr(); + rFnt.SetProportion( 100 ); + Size aOldSize( 0, rFnt.GetHeight( rFnt.GetActual() ) ); + + Size aNewSize( 0, ( nFactor * nCurrHeight ) / 1000 ); + rFnt.SetSize( aNewSize, rFnt.GetActual() ); + rFnt.ChgPhysFnt( rInf.GetVsh(), *pOut ); + + nAscent = rFnt.GetAscent( rInf.GetVsh(), *pOut ); + + // we get the rectangle that covers all chars + bool bHaveGlyphRect = pOut->GetTextBoundRect( aRect, rInf.GetText(), 0, + sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen())) + && ! aRect.IsEmpty(); + + if ( ! bHaveGlyphRect ) + { + // getting glyph boundaries failed for some reason, + // we take the window for calculating sizes + if ( pWin ) + { + if ( ! bWinUsed ) + { + bWinUsed = true; + aOldMap = pWin->GetMapMode( ); + pWin->SetMapMode( MapMode( MapUnit::MapTwip ) ); + aOldFnt = pWin->GetFont(); + } + pWin->SetFont( rFnt.GetActualFont() ); + + bHaveGlyphRect = pWin->GetTextBoundRect( aRect, rInf.GetText(), 0, + sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen())) + && ! aRect.IsEmpty(); + } + if (!bHaveGlyphRect) + { + // We do not have a window or our window could not + // give us glyph boundaries. + aRect = tools::Rectangle( Point( 0, 0 ), Size( 0, nAscent ) ); + } + } + + // Now we (hopefully) have a bounding rectangle for the + // glyphs of the current portion and the ascent of the current + // font + + // reset font size and proportion + rFnt.SetSize( aOldSize, rFnt.GetActual() ); + rFnt.SetProportion( nOldProp ); + + // Modify the bounding rectangle with the borders + // Robust: If the padding is so big as drop cap letter has no enough space than + // remove all padding. + if( rFnt.GetTopBorderSpace() + rFnt.GetBottomBorderSpace() >= nWishedHeight ) + { + rFnt.SetTopBorderDist(0); + rFnt.SetBottomBorderDist(0); + rFnt.SetRightBorderDist(0); + rFnt.SetLeftBorderDist(0); + } + + if( rFnt.GetTopBorder() ) + { + aRect.setHeight(aRect.GetHeight() + rFnt.GetTopBorderSpace()); + aRect.setY(aRect.getY() - rFnt.GetTopBorderSpace()); + } + + if( rFnt.GetBottomBorder() ) + { + aRect.setHeight(aRect.GetHeight() + rFnt.GetBottomBorderSpace()); + } + + if ( bFirstGlyphRect ) + { + aCommonRect = aRect; + bFirstGlyphRect = false; + } + else + aCommonRect.Union( aRect ); + + nIdx = nIdx + pCurrPart->GetLen(); + pCurrPart = pCurrPart->GetFollow(); + } + + // now we have a union ( aCommonRect ) of all glyphs with + // respect to a common baseline : 0 + + // get descent and ascent from union + if ( rInf.GetTextFrame()->IsVertical() ) + { + nDescent = aCommonRect.Left(); + nAscent = aCommonRect.Right(); + + if ( nDescent < 0 ) + nDescent = -nDescent; + } + else + { + nDescent = aCommonRect.Bottom(); + nAscent = aCommonRect.Top(); + } + if ( nAscent < 0 ) + nAscent = -nAscent; + + const long nHght = nAscent + nDescent; + if ( nHght ) + { + if ( nHght > nWishedHeight ) + nMax = nFactor; + else + { + if ( bUseCache ) + aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor); + nMin = nFactor; + } + + nFactor = ( nFactor * nWishedHeight ) / nHght; + bGrow = ( nFactor > nMin ) && ( nFactor < nMax ); +#if OSL_DEBUG_LEVEL > 1 + if ( bGrow ) + nGrow++; +#endif + nIdx = rInf.GetIdx(); + } + else + bGrow = false; + } + + if ( bWinUsed ) + { + // reset window if it has been used + pWin->SetMapMode( aOldMap ); + pWin->SetFont( aOldFnt ); + } + + if ( bUseCache ) + aDescent[ nTmpIdx ] = -short( nDescent ); + } + + pCurrPart = pDrop->GetPart(); + + // did made any new calculations or did we use the cache? + if ( -1 == nFactor ) + { + nFactor = aFactor[ nTmpIdx ]; + nDescent = aDescent[ nTmpIdx ]; + } + else + nDescent = -nDescent; + + while ( pCurrPart ) + { + // scale current font + SwFont& rFnt = pCurrPart->GetFont(); + Size aNewSize( 0, ( nFactor * rFnt.GetHeight( rFnt.GetActual() ) ) / 1000 ); + + const sal_uInt8 nOldProp = rFnt.GetPropr(); + rFnt.SetProportion( 100 ); + rFnt.SetSize( aNewSize, rFnt.GetActual() ); + rFnt.SetProportion( nOldProp ); + + pCurrPart = pCurrPart->GetFollow(); + } + pDrop->SetY( static_cast<short>(nDescent) ); +} + +bool SwDropPortion::Format( SwTextFormatInfo &rInf ) +{ + bool bFull = false; + nFix = static_cast<sal_uInt16>(rInf.X()); + + SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); + aLayoutModeModifier.SetAuto(); + + if( nDropHeight && pPart && nLines!=1 ) + { + if( !pDropCapCache ) + pDropCapCache = new SwDropCapCache; + + // adjust font sizes to fit into the rectangle + pDropCapCache->CalcFontSize( this, rInf ); + + const long nOldX = rInf.X(); + { + SwDropSave aSave( rInf ); + SwDropPortionPart* pCurrPart = pPart.get(); + + while ( pCurrPart ) + { + rInf.SetLen( pCurrPart->GetLen() ); + SwFont& rFnt = pCurrPart->GetFont(); + { + SwFontSave aFontSave( rInf, &rFnt ); + SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext()); + SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev()); + bFull = FormatText( rInf ); + + if ( bFull ) + break; + } + + const SwTwips nTmpWidth = + ( InSpaceGrp() && rInf.GetSpaceAdd() ) ? + Width() + CalcSpacing( rInf.GetSpaceAdd(), rInf ) : + Width(); + + // set values + pCurrPart->SetWidth( static_cast<sal_uInt16>(nTmpWidth) ); + + // Move + rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() ); + rInf.X( rInf.X() + nTmpWidth ); + pCurrPart = pCurrPart->GetFollow(); + } + SetJoinBorderWithNext(false); + SetJoinBorderWithPrev(false); + Width( static_cast<sal_uInt16>(rInf.X() - nOldX) ); + } + + // reset my length + SetLen( rInf.GetLen() ); + + // Quit when Flys are overlapping + if( ! bFull ) + bFull = lcl_IsDropFlyInter( rInf, Width(), nDropHeight ); + + if( bFull ) + { + // FormatText could have caused nHeight to be 0 + if ( !Height() ) + Height( rInf.GetTextHeight() ); + + // And now for another round + nDropHeight = nLines = 0; + pPart.reset(); + + // Meanwhile use normal formatting + bFull = SwTextPortion::Format( rInf ); + } + else + rInf.SetDropInit( true ); + + Height( rInf.GetTextHeight() ); + SetAscent( rInf.GetAscent() ); + } + else + bFull = SwTextPortion::Format( rInf ); + + if( bFull ) + nDistance = 0; + else + { + const sal_uInt16 nWant = Width() + GetDistance(); + const sal_uInt16 nRest = static_cast<sal_uInt16>(rInf.Width() - rInf.X()); + if( ( nWant > nRest ) || + lcl_IsDropFlyInter( rInf, Width() + GetDistance(), nDropHeight ) ) + nDistance = 0; + + Width( Width() + nDistance ); + } + return bFull; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx new file mode 100644 index 000000000..4eac15a48 --- /dev/null +++ b/sw/source/core/text/txtfld.cxx @@ -0,0 +1,737 @@ +/* -*- 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 <hintids.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <charfmt.hxx> +#include <fmtautofmt.hxx> + +#include <viewsh.hxx> +#include <doc.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <ndtxt.hxx> +#include <fldbas.hxx> +#include <viewopt.hxx> +#include <flyfrm.hxx> +#include <viewimp.hxx> +#include <swfont.hxx> +#include <swmodule.hxx> +#include "porfld.hxx" +#include "porftn.hxx" +#include "porref.hxx" +#include "portox.hxx" +#include "porfly.hxx" +#include "itrform2.hxx" +#include <chpfld.hxx> +#include <dbfld.hxx> +#include <expfld.hxx> +#include <docufld.hxx> +#include <pagedesc.hxx> +#include <fmtmeta.hxx> +#include <reffld.hxx> +#include <flddat.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <redline.hxx> +#include <sfx2/docfile.hxx> +#include <svl/itemiter.hxx> +#include <svl/whiter.hxx> +#include <editeng/colritem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> + +static bool lcl_IsInBody( SwFrame const *pFrame ) +{ + if ( pFrame->IsInDocBody() ) + return true; + else + { + const SwFrame *pTmp = pFrame; + const SwFlyFrame *pFly; + while ( nullptr != (pFly = pTmp->FindFlyFrame()) ) + pTmp = pFly->GetAnchorFrame(); + return pTmp->IsInDocBody(); + } +} + +SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, + const SwTextAttr *pHint ) const +{ + SwExpandPortion *pRet = nullptr; + SwFrame *pFrame = m_pFrame; + SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField()); + const bool bName = rInf.GetOpt().IsFieldName(); + + SwCharFormat* pChFormat = nullptr; + bool bNewFlyPor = false; + sal_uInt16 subType = 0; + + // set language + const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf ); + if (pField->GetLanguage() != GetFnt()->GetLanguage()) + { + pField->SetLanguage( GetFnt()->GetLanguage() ); + // let the visual note know about its new language + if (pField->GetTyp()->Which()==SwFieldIds::Postit) + const_cast<SwFormatField*> (&pHint->GetFormatField())->Broadcast( SwFormatFieldHint( &pHint->GetFormatField(), SwFormatFieldHintWhich::LANGUAGE ) ); + } + + SwViewShell *pSh = rInf.GetVsh(); + SwDoc *const pDoc( pSh ? pSh->GetDoc() : nullptr ); + bool const bInClipboard( pDoc == nullptr || pDoc->IsClipBoard() ); + bool bPlaceHolder = false; + + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::Script: + case SwFieldIds::Postit: + pRet = new SwPostItsPortion( SwFieldIds::Script == pField->GetTyp()->Which() ); + break; + + case SwFieldIds::CombinedChars: + { + if( bName ) + pRet = new SwFieldPortion( pField->GetFieldName() ); + else + pRet = new SwCombinedPortion( pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + } + break; + + case SwFieldIds::HiddenText: + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwHiddenPortion(aStr); + } + break; + + case SwFieldIds::Chapter: + if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + { + static_cast<SwChapterField*>(pField)->ChangeExpansion(*pFrame, + &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode()); + } + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion( aStr ); + } + break; + + case SwFieldIds::DocStat: + if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + { + static_cast<SwDocStatField*>(pField)->ChangeExpansion( pFrame ); + } + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion( aStr ); + } + static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGECOOUNTFLD; + break; + + case SwFieldIds::PageNumber: + { + if( !bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields() ) + { + SwPageNumberFieldType *pPageNr = static_cast<SwPageNumberFieldType *>(pField->GetTyp()); + + const SwRootFrame* pTmpRootFrame = pSh->GetLayout(); + const bool bVirt = pTmpRootFrame->IsVirtPageNum(); + + sal_uInt16 nVirtNum = pFrame->GetVirtPageNum(); + sal_uInt16 nNumPages = pTmpRootFrame->GetPageNum(); + SvxNumType nNumFormat = SvxNumType(-1); + if(SVX_NUM_PAGEDESC == pField->GetFormat()) + nNumFormat = pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType(); + static_cast<SwPageNumberField*>(pField) + ->ChangeExpansion(nVirtNum, nNumPages); + pPageNr->ChangeExpansion(pDoc, + bVirt, nNumFormat != SvxNumType(-1) ? &nNumFormat : nullptr); + } + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion( aStr ); + } + static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGENUMBERFLD; + break; + } + case SwFieldIds::GetExp: + { + if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + { + SwGetExpField* pExpField = static_cast<SwGetExpField*>(pField); + if( !::lcl_IsInBody( pFrame ) ) + { + pExpField->ChgBodyTextFlag( false ); + pExpField->ChangeExpansion(*pFrame, + *static_txtattr_cast<SwTextField const*>(pHint)); + } + else if( !pExpField->IsInBodyText() ) + { + // Was something else previously, thus: expand first, then convert it! + pExpField->ChangeExpansion(*pFrame, + *static_txtattr_cast<SwTextField const*>(pHint)); + pExpField->ChgBodyTextFlag( true ); + } + } + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion( aStr ); + } + break; + } + case SwFieldIds::Database: + { + if( !bName ) + { + SwDBField* pDBField = static_cast<SwDBField*>(pField); + pDBField->ChgBodyTextFlag( ::lcl_IsInBody( pFrame ) ); + } + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion(aStr); + } + break; + } + case SwFieldIds::RefPageGet: + if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + { + static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(*pFrame, + static_txtattr_cast<SwTextField const*>(pHint)); + } + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion(aStr); + } + break; + + case SwFieldIds::JumpEdit: + if( !bName ) + pChFormat = static_cast<SwJumpEditField*>(pField)->GetCharFormat(); + bNewFlyPor = true; + bPlaceHolder = true; + break; + case SwFieldIds::GetRef: + subType = static_cast<SwGetRefField*>(pField)->GetSubType(); + { + OUString const str( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion(str); + } + if( subType == REF_BOOKMARK ) + static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_BOOKMARKFLD; + else if( subType == REF_SETREFATTR ) + static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_SETREFATTRFLD; + break; + case SwFieldIds::DateTime: + subType = static_cast<SwDateTimeField*>(pField)->GetSubType(); + { + OUString const str( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion(str); + } + if( subType & DATEFLD ) + static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_DATEFLD; + else if( subType & TIMEFLD ) + static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_TIMEFLD; + break; + default: + { + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion(aStr); + } + } + + if( bNewFlyPor ) + { + std::unique_ptr<SwFont> pTmpFnt; + if( !bName ) + { + pTmpFnt.reset(new SwFont( *m_pFont )); + pTmpFnt->SetDiffFnt(&pChFormat->GetAttrSet(), &m_pFrame->GetDoc().getIDocumentSettingAccess()); + } + OUString const aStr( bName + ? pField->GetFieldName() + : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); + pRet = new SwFieldPortion(aStr, std::move(pTmpFnt), bPlaceHolder); + } + + return pRet; +} + +static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefix) +{ + ::sw::Meta *const pMeta( + static_cast<SwFormatMeta &>(rHint.GetAttr()).GetMeta() ); + OUString fix; + ::sw::MetaField *const pField( dynamic_cast< ::sw::MetaField * >(pMeta) ); + OSL_ENSURE(pField, "lcl_NewMetaPortion: no meta field?"); + if (pField) + { + pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix); + } + return new SwFieldPortion( fix ); +} + +/** + * Try to create a new portion with zero length, for an end of a hint + * (where there is no CH_TXTATR). Because there may be multiple hint ends at a + * given index, m_pByEndIter is used to keep track of the already created + * portions. But the portions created here may actually be deleted again, + * due to Underflow. In that case, m_pByEndIter must be decremented, + * so the portion will be created again on the next line. + */ +SwExpandPortion * SwTextFormatter::TryNewNoLengthPortion(SwTextFormatInfo const & rInfo) +{ + const TextFrameIndex nIdx(rInfo.GetIdx()); + + // sw_redlinehide: because there is a dummy character at the start of these + // hints, it's impossible to have ends of hints from different nodes at the + // same view position, so it's sufficient to check the hints of the current + // node. However, m_pByEndIter exists for the whole text frame, so + // it's necessary to iterate all hints for that purpose... + if (!m_pByEndIter) + { + m_pByEndIter.reset(new sw::MergedAttrIterByEnd(*rInfo.GetTextFrame())); + } + SwTextNode const* pNode(nullptr); + for (SwTextAttr const* pHint = m_pByEndIter->NextAttr(pNode); pHint; + pHint = m_pByEndIter->NextAttr(pNode)) + { + SwTextAttr & rHint(const_cast<SwTextAttr&>(*pHint)); + TextFrameIndex const nEnd( + rInfo.GetTextFrame()->MapModelToView(pNode, rHint.GetAnyEnd())); + if (nEnd > nIdx) + { + m_pByEndIter->PrevAttr(); + break; + } + if (nEnd == nIdx) + { + if (RES_TXTATR_METAFIELD == rHint.Which()) + { + SwFieldPortion *const pPortion( + lcl_NewMetaPortion(rHint, false)); + pPortion->SetNoLength(); // no CH_TXTATR at hint end! + return pPortion; + } + } + } + return nullptr; +} + +SwLinePortion *SwTextFormatter::NewExtraPortion( SwTextFormatInfo &rInf ) +{ + SwTextAttr *pHint = GetAttr( rInf.GetIdx() ); + SwLinePortion *pRet = nullptr; + if( !pHint ) + { + pRet = new SwTextPortion; + pRet->SetLen(TextFrameIndex(1)); + rInf.SetLen(TextFrameIndex(1)); + return pRet; + } + + switch( pHint->Which() ) + { + case RES_TXTATR_FLYCNT : + { + pRet = NewFlyCntPortion( rInf, pHint ); + break; + } + case RES_TXTATR_FTN : + { + pRet = NewFootnotePortion( rInf, pHint ); + break; + } + case RES_TXTATR_FIELD : + case RES_TXTATR_ANNOTATION : + { + pRet = NewFieldPortion( rInf, pHint ); + break; + } + case RES_TXTATR_REFMARK : + { + pRet = new SwIsoRefPortion; + break; + } + case RES_TXTATR_TOXMARK : + { + pRet = new SwIsoToxPortion; + break; + } + case RES_TXTATR_METAFIELD: + { + pRet = lcl_NewMetaPortion( *pHint, true ); + break; + } + default: ; + } + if( !pRet ) + { + const OUString aNothing; + pRet = new SwFieldPortion( aNothing ); + rInf.SetLen(TextFrameIndex(1)); + } + return pRet; +} + +/** + * OOXML spec says that w:rPr inside w:pPr specifies formatting for the paragraph mark symbol (i.e. the control + * character than can be configured to be shown). However, in practice MSO also uses it as direct formatting + * for numbering in that paragraph. I don't know if the problem is in the spec or in MSWord. + */ +static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextFormatInfo& rInf, + const IDocumentSettingAccess* pIDSA, + const SwAttrSet* pFormat) +{ + if( !pIDSA->get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING )) + return; + + SwFormatAutoFormat const& rListAutoFormat(static_cast<SwFormatAutoFormat const&>(rInf.GetTextFrame()->GetTextNodeForParaProps()->GetAttr(RES_PARATR_LIST_AUTOFMT))); + std::shared_ptr<SfxItemSet> pSet(rListAutoFormat.GetStyleHandle()); + + // TODO remove this fallback (for WW8/RTF) + bool isDOCX = pIDSA->get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); + if (!isDOCX && !pSet) + { + TextFrameIndex const nTextLen(rInf.GetTextFrame()->GetText().getLength()); + SwTextNode const* pNode(nullptr); + sw::MergedAttrIterReverse iter(*rInf.GetTextFrame()); + for (SwTextAttr const* pHint = iter.PrevAttr(&pNode); pHint; + pHint = iter.PrevAttr(&pNode)) + { + TextFrameIndex const nHintEnd( + rInf.GetTextFrame()->MapModelToView(pNode, pHint->GetAnyEnd())); + if (nHintEnd < nTextLen) + { + break; // only those at para end are interesting + } + // Formatting for the paragraph mark is usually set to apply only to the + // (non-existent) extra character at end of the text node, but there can be + // other hints too (ending at nTextLen), so look for all matching hints. + // Still the (non-existent) extra character at the end is preferred if it exists. + if (pHint->Which() == RES_TXTATR_AUTOFMT + && pHint->GetStart() == *pHint->End()) + { + pSet = pHint->GetAutoFormat().GetStyleHandle(); + // When we find an empty hint (start == end) we got what we are looking for. + break; + } + } + } + + // TODO: apparently Word can apply Character Style too, see testParagraphMark + + // Check each item and in case it should be ignored, then clear it. + if (pSet) + { + std::unique_ptr<SfxItemSet> const pCleanedSet = pSet->Clone(); + + if (pCleanedSet->HasItem(RES_TXTATR_CHARFMT)) + { + // Insert attributes of referenced char format into current set + const SwFormatCharFormat& rCharFormat = pCleanedSet->Get(RES_TXTATR_CHARFMT); + const SwAttrSet& rStyleAttrs = static_cast<const SwCharFormat *>(rCharFormat.GetRegisteredIn())->GetAttrSet(); + SfxWhichIter aIter(rStyleAttrs); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + if (!SwTextNode::IsIgnoredCharFormatForNumbering(nWhich) + && !pCleanedSet->HasItem(nWhich) + && !(pFormat && pFormat->HasItem(nWhich)) ) + { + // Copy from parent sets only allowed items which will not overwrite + // values explicitly defined in current set (pCleanedSet) or in pFormat + const SfxPoolItem* pItem = rStyleAttrs.GetItem(nWhich, true); + pCleanedSet->Put(*pItem); + } + nWhich = aIter.NextWhich(); + } + + // It is not required here anymore, all referenced items are inserted + pCleanedSet->ClearItem(RES_TXTATR_CHARFMT); + }; + + SfxItemIter aIter(*pSet); + const SfxPoolItem* pItem = aIter.GetCurItem(); + while (pItem) + { + if (pItem->Which() != RES_CHRATR_BACKGROUND) + { + if (SwTextNode::IsIgnoredCharFormatForNumbering(pItem->Which())) + pCleanedSet->ClearItem(pItem->Which()); + else if (pFormat && pFormat->HasItem(pItem->Which())) + pCleanedSet->ClearItem(pItem->Which()); + } + pItem = aIter.NextItem(); + }; + pNumFnt->SetDiffFnt(pCleanedSet.get(), pIDSA); + } +} + +static const SwRangeRedline* lcl_GetRedlineAtNodeInsertionOrDeletion( const SwTextNode& rTextNode ) +{ + const SwDoc* pDoc = rTextNode.GetDoc(); + SwRedlineTable::size_type nRedlPos = pDoc->getIDocumentRedlineAccess().GetRedlinePos( rTextNode, RedlineType::Any ); + + if( SwRedlineTable::npos != nRedlPos ) + { + const sal_uLong nNdIdx = rTextNode.GetIndex(); + for( ; nRedlPos < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos ) + { + const SwRangeRedline* pTmp = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + if( RedlineType::Delete == pTmp->GetType() || + RedlineType::Insert == pTmp->GetType() ) + { + const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); + if( pRStt->nNode < nNdIdx && pREnd->nNode >= nNdIdx ) + return pTmp; + } + } + } + return nullptr; +} + +static void lcl_setRedlineAttr( SwTextFormatInfo &rInf, const SwTextNode& rTextNode, std::unique_ptr<SwFont>& pNumFnt ) +{ + if ( !rInf.GetVsh()->GetLayout()->IsHideRedlines() ) + { + const SwRangeRedline* pRedlineNum = lcl_GetRedlineAtNodeInsertionOrDeletion( rTextNode ); + if (pRedlineNum) + { + std::unique_ptr<SfxItemSet> pSet; + + SwAttrPool& rPool = rInf.GetVsh()->GetDoc()->GetAttrPool(); + pSet = std::make_unique<SfxItemSet>(rPool, svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END-1>{}); + + std::size_t aAuthor = (1 < pRedlineNum->GetStackCount()) + ? pRedlineNum->GetAuthor( 1 ) + : pRedlineNum->GetAuthor(); + + if ( RedlineType::Delete == pRedlineNum->GetType() ) + SW_MOD()->GetDeletedAuthorAttr(aAuthor, *pSet); + else + SW_MOD()->GetInsertAuthorAttr(aAuthor, *pSet); + + const SfxPoolItem* pItem = nullptr; + if (SfxItemState::SET == pSet->GetItemState(RES_CHRATR_COLOR, true, &pItem)) + pNumFnt->SetColor(static_cast<const SvxColorItem*>(pItem)->GetValue()); + if (SfxItemState::SET == pSet->GetItemState(RES_CHRATR_UNDERLINE, true, &pItem)) + pNumFnt->SetUnderline(static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle()); + if (SfxItemState::SET == pSet->GetItemState(RES_CHRATR_CROSSEDOUT, true, &pItem)) + pNumFnt->SetStrikeout( static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout() ); + } + } +} + +SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) const +{ + if( rInf.IsNumDone() || rInf.GetTextStart() != m_nStart + || rInf.GetTextStart() != rInf.GetIdx() ) + return nullptr; + + SwNumberPortion *pRet = nullptr; + // sw_redlinehide: at this point it's certain that pTextNd is the node with + // the numbering of the frame; only the actual number-vector (GetNumString) + // depends on the hide-mode in the layout so other calls don't need to care + const SwTextNode *const pTextNd = GetTextFrame()->GetTextNodeForParaProps(); + const SwNumRule* pNumRule = pTextNd->GetNumRule(); + + // Has a "valid" number? + // sw_redlinehide: check that pParaPropsNode is the correct one + assert(pTextNd->IsNumbered(m_pFrame->getRootFrame()) == pTextNd->IsNumbered(nullptr)); + if (pTextNd->IsNumbered(m_pFrame->getRootFrame()) && pTextNd->IsCountedInList()) + { + int nLevel = pTextNd->GetActualListLevel(); + + if (nLevel < 0) + nLevel = 0; + + if (nLevel >= MAXLEVEL) + nLevel = MAXLEVEL - 1; + + const SwNumFormat &rNumFormat = pNumRule->Get( nLevel ); + const bool bLeft = SvxAdjust::Left == rNumFormat.GetNumAdjust(); + const bool bCenter = SvxAdjust::Center == rNumFormat.GetNumAdjust(); + const bool bLabelAlignmentPosAndSpaceModeActive( + rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ); + const sal_uInt16 nMinDist = bLabelAlignmentPosAndSpaceModeActive + ? 0 : rNumFormat.GetCharTextDistance(); + + if( SVX_NUM_BITMAP == rNumFormat.GetNumberingType() ) + { + OUString referer; + if (auto const sh1 = rInf.GetVsh()) { + if (auto const doc = sh1->GetDoc()) { + auto const sh2 = doc->GetPersist(); + if (sh2 != nullptr && sh2->HasName()) { + referer = sh2->GetMedium()->GetName(); + } + } + } + pRet = new SwGrfNumPortion( pTextNd->GetLabelFollowedBy(), + rNumFormat.GetBrush(), referer, + rNumFormat.GetGraphicOrientation(), + rNumFormat.GetGraphicSize(), + bLeft, bCenter, nMinDist, + bLabelAlignmentPosAndSpaceModeActive ); + long nTmpA = rInf.GetLast()->GetAscent(); + long nTmpD = rInf.GetLast()->Height() - nTmpA; + if( !rInf.IsTest() ) + static_cast<SwGrfNumPortion*>(pRet)->SetBase( nTmpA, nTmpD, nTmpA, nTmpD ); + } + else + { + // The SwFont is created dynamically and passed in the ctor, + // as the CharFormat only returns an SV-Font. + // In the dtor of SwNumberPortion, the SwFont is deleted. + const SwAttrSet* pFormat = rNumFormat.GetCharFormat() ? + &rNumFormat.GetCharFormat()->GetAttrSet() : + nullptr; + const IDocumentSettingAccess* pIDSA = pTextNd->getIDocumentSettingAccess(); + + if( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() ) + { + const vcl::Font *pFormatFnt = rNumFormat.GetBulletFont(); + + // Build a new bullet font basing on the current paragraph font: + std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA )); + + // #i53199# + if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) ) + { + // i18463: + // Underline style of paragraph font should not be considered + // Overline style of paragraph font should not be considered + // Weight style of paragraph font should not be considered + // Posture style of paragraph font should not be considered + pNumFnt->SetUnderline( LINESTYLE_NONE ); + pNumFnt->SetOverline( LINESTYLE_NONE ); + pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin ); + pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK ); + pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL ); + pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin ); + pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK ); + pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL ); + } + + // Apply the explicit attributes from the character style + // associated with the numbering to the new bullet font. + if( pFormat ) + pNumFnt->SetDiffFnt( pFormat, pIDSA ); + + checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat); + + if ( pFormatFnt ) + { + const SwFontScript nAct = pNumFnt->GetActual(); + pNumFnt->SetFamily( pFormatFnt->GetFamilyType(), nAct ); + pNumFnt->SetName( pFormatFnt->GetFamilyName(), nAct ); + pNumFnt->SetStyleName( pFormatFnt->GetStyleName(), nAct ); + pNumFnt->SetCharSet( pFormatFnt->GetCharSet(), nAct ); + pNumFnt->SetPitch( pFormatFnt->GetPitch(), nAct ); + } + + // we do not allow a vertical font + pNumFnt->SetVertical( pNumFnt->GetOrientation(), + m_pFrame->IsVertical() ); + + lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ); + + // --> OD 2008-01-23 #newlistelevelattrs# + if (rNumFormat.GetBulletChar()) + { + pRet = new SwBulletPortion(rNumFormat.GetBulletChar(), + pTextNd->GetLabelFollowedBy(), + std::move(pNumFnt), + bLeft, bCenter, nMinDist, + bLabelAlignmentPosAndSpaceModeActive); + } + } + else + { + OUString aText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame()) ); + if ( !aText.isEmpty() ) + { + aText += pTextNd->GetLabelFollowedBy(); + } + + // Not just an optimization ... + // A number portion without text will be assigned a width of 0. + // The succeeding text portion will flow into the BreakCut in the BreakLine, + // although we have rInf.GetLast()->GetFlyPortion()! + if( !aText.isEmpty() ) + { + + // Build a new numbering font basing on the current paragraph font: + std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA )); + + // #i53199# + if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) ) + { + // i18463: + // Underline style of paragraph font should not be considered + pNumFnt->SetUnderline( LINESTYLE_NONE ); + // Overline style of paragraph font should not be considered + pNumFnt->SetOverline( LINESTYLE_NONE ); + } + + // Apply the explicit attributes from the character style + // associated with the numbering to the new bullet font. + if( pFormat ) + pNumFnt->SetDiffFnt( pFormat, pIDSA ); + + checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat); + + lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ); + + // we do not allow a vertical font + pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() ); + + pRet = new SwNumberPortion( aText, std::move(pNumFnt), + bLeft, bCenter, nMinDist, + bLabelAlignmentPosAndSpaceModeActive ); + } + } + } + } + return pRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx new file mode 100644 index 000000000..52979fde8 --- /dev/null +++ b/sw/source/core/text/txtfly.cxx @@ -0,0 +1,1407 @@ +/* -*- 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 <vcl/outdev.hxx> + +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <pam.hxx> +#include <swfont.hxx> +#include <swregion.hxx> +#include <dflyobj.hxx> +#include <drawfont.hxx> +#include <flyfrm.hxx> +#include <flyfrms.hxx> +#include <fmtornt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <txtfly.hxx> +#include "txtpaint.hxx" +#include <notxtfrm.hxx> +#include <fmtcnct.hxx> +#include <svx/obj3d.hxx> +#include <editeng/txtrange.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <fmtsrnd.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <pagedesc.hxx> +#include <sortedobjs.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <svx/svdoedge.hxx> + +#ifdef DBG_UTIL +#include <viewsh.hxx> +#include <doc.hxx> +#endif + +using namespace ::com::sun::star; + +namespace +{ + // #i68520# + struct AnchoredObjOrder + { + bool mbR2L; + SwRectFn mfnRect; + + AnchoredObjOrder( const bool bR2L, + SwRectFn fnRect ) + : mbR2L( bR2L ), + mfnRect( fnRect ) + {} + + bool operator()( const SwAnchoredObject* pListedAnchoredObj, + const SwAnchoredObject* pNewAnchoredObj ) + { + const SwRect& aBoundRectOfListedObj( pListedAnchoredObj->GetObjRectWithSpaces() ); + const SwRect& aBoundRectOfNewObj( pNewAnchoredObj->GetObjRectWithSpaces() ); + if ( ( mbR2L && + ( (aBoundRectOfListedObj.*mfnRect->fnGetRight)() == + (aBoundRectOfNewObj.*mfnRect->fnGetRight)() ) ) || + ( !mbR2L && + ( (aBoundRectOfListedObj.*mfnRect->fnGetLeft)() == + (aBoundRectOfNewObj.*mfnRect->fnGetLeft)() ) ) ) + { + SwTwips nTopDiff = + (*mfnRect->fnYDiff)( (aBoundRectOfNewObj.*mfnRect->fnGetTop)(), + (aBoundRectOfListedObj.*mfnRect->fnGetTop)() ); + if ( nTopDiff == 0 && + ( ( mbR2L && + ( (aBoundRectOfNewObj.*mfnRect->fnGetLeft)() > + (aBoundRectOfListedObj.*mfnRect->fnGetLeft)() ) ) || + ( !mbR2L && + ( (aBoundRectOfNewObj.*mfnRect->fnGetRight)() < + (aBoundRectOfListedObj.*mfnRect->fnGetRight)() ) ) ) ) + { + return true; + } + else if ( nTopDiff > 0 ) + { + return true; + } + } + else if ( ( mbR2L && + ( (aBoundRectOfListedObj.*mfnRect->fnGetRight)() > + (aBoundRectOfNewObj.*mfnRect->fnGetRight)() ) ) || + ( !mbR2L && + ( (aBoundRectOfListedObj.*mfnRect->fnGetLeft)() < + (aBoundRectOfNewObj.*mfnRect->fnGetLeft)() ) ) ) + { + return true; + } + + return false; + } + }; +} + +SwContourCache::SwContourCache() : + nPntCnt( 0 ) +{ +} + +SwContourCache::~SwContourCache() +{ +} + +void SwContourCache::ClrObject( sal_uInt16 nPos ) +{ + nPntCnt -= mvItems[ nPos ].mxTextRanger->GetPointCount(); + mvItems.erase(mvItems.begin() + nPos); +} + +void ClrContourCache( const SdrObject *pObj ) +{ + if( pContourCache && pObj ) + for( sal_uInt16 i = 0; i < pContourCache->GetCount(); ++i ) + if( pObj == pContourCache->GetObject( i ) ) + { + pContourCache->ClrObject( i ); + break; + } +} + +void ClrContourCache() +{ + if( pContourCache ) + { + pContourCache->mvItems.clear(); + pContourCache->nPntCnt = 0; + } +} + +// #i68520# +SwRect SwContourCache::CalcBoundRect( const SwAnchoredObject* pAnchoredObj, + const SwRect &rLine, + const SwTextFrame* pFrame, + const long nXPos, + const bool bRight ) +{ + SwRect aRet; + const SwFrameFormat* pFormat = &(pAnchoredObj->GetFrameFormat()); + bool bHandleContour(pFormat->GetSurround().IsContour()); + + if(!bHandleContour) + { + // RotateFlyFrame3: Object has no set contour, but for rotated + // FlyFrames we can create a 'default' contour to make text + // flow around the free, non-covered + const SwFlyFreeFrame* pSwFlyFreeFrame(dynamic_cast< const SwFlyFreeFrame* >(pAnchoredObj)); + + if(nullptr != pSwFlyFreeFrame && pSwFlyFreeFrame->supportsAutoContour()) + { + bHandleContour = true; + } + } + + if( bHandleContour && + ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr || + ( static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower() && + static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower()->IsNoTextFrame() ) ) ) + { + aRet = pAnchoredObj->GetObjRectWithSpaces(); + if( aRet.IsOver( rLine ) ) + { + if( !pContourCache ) + pContourCache = new SwContourCache; + + aRet = pContourCache->ContourRect( + pFormat, pAnchoredObj->GetDrawObj(), pFrame, rLine, nXPos, bRight ); + } + else + aRet.Width( 0 ); + } + else + { + aRet = pAnchoredObj->GetObjRectWithSpaces(); + } + + return aRet; +} + +SwRect SwContourCache::ContourRect( const SwFormat* pFormat, + const SdrObject* pObj, const SwTextFrame* pFrame, const SwRect &rLine, + const long nXPos, const bool bRight ) +{ + SwRect aRet; + sal_uInt16 nPos = 0; // Search in the Cache + while( nPos < GetCount() && pObj != mvItems[ nPos ].mpSdrObj ) + ++nPos; + if( GetCount() == nPos ) // Not found + { + if( GetCount() == POLY_CNT ) + { + nPntCnt -= mvItems.back().mxTextRanger->GetPointCount(); + mvItems.pop_back(); + } + ::basegfx::B2DPolyPolygon aPolyPolygon; + std::unique_ptr<::basegfx::B2DPolyPolygon> pPolyPolygon; + + if ( auto pVirtFlyDrawObj = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) ) + { + // GetContour() causes the graphic to be loaded, which may cause + // the graphic to change its size, call ClrObject() + tools::PolyPolygon aPoly; + if( !pVirtFlyDrawObj->GetFlyFrame()->GetContour( aPoly ) ) + aPoly = tools::PolyPolygon( static_cast<const SwVirtFlyDrawObj*>(pObj)-> + GetFlyFrame()->getFrameArea().SVRect() ); + aPolyPolygon.clear(); + aPolyPolygon.append(aPoly.getB2DPolyPolygon()); + } + else + { + if( dynamic_cast< const E3dObject *>( pObj ) == nullptr ) + { + aPolyPolygon = pObj->TakeXorPoly(); + } + + ::basegfx::B2DPolyPolygon aContourPoly(pObj->TakeContour()); + pPolyPolygon.reset(new ::basegfx::B2DPolyPolygon(aContourPoly)); + } + const SvxLRSpaceItem &rLRSpace = pFormat->GetLRSpace(); + const SvxULSpaceItem &rULSpace = pFormat->GetULSpace(); + CacheItem item { + pObj, // due to #37347 the Object must be entered only after GetContour() + std::make_unique<TextRanger>( aPolyPolygon, pPolyPolygon.get(), 20, + static_cast<sal_uInt16>(rLRSpace.GetLeft()), static_cast<sal_uInt16>(rLRSpace.GetRight()), + pFormat->GetSurround().IsOutside(), false, pFrame->IsVertical() ) + }; + mvItems.insert(mvItems.begin(), std::move(item)); + mvItems[0].mxTextRanger->SetUpper( rULSpace.GetUpper() ); + mvItems[0].mxTextRanger->SetLower( rULSpace.GetLower() ); + + pPolyPolygon.reset(); + + nPntCnt += mvItems[0].mxTextRanger->GetPointCount(); + while( nPntCnt > POLY_MAX && mvItems.size() > POLY_MIN ) + { + nPntCnt -= mvItems.back().mxTextRanger->GetPointCount(); + mvItems.pop_back(); + } + } + else if( nPos ) + { + CacheItem item = std::move(mvItems[nPos]); + mvItems.erase(mvItems.begin() + nPos); + mvItems.insert(mvItems.begin(), std::move(item)); + } + SwRectFnSet aRectFnSet(pFrame); + long nTmpTop = aRectFnSet.GetTop(rLine); + // fnGetBottom is top + height + long nTmpBottom = aRectFnSet.GetBottom(rLine); + + Range aRange( std::min( nTmpTop, nTmpBottom ), std::max( nTmpTop, nTmpBottom ) ); + + std::deque<long>* pTmp = mvItems[0].mxTextRanger->GetTextRanges( aRange ); + + const size_t nCount = pTmp->size(); + if( 0 != nCount ) + { + size_t nIdx = 0; + while( nIdx < nCount && (*pTmp)[ nIdx ] < nXPos ) + ++nIdx; + bool bOdd = nIdx % 2; + bool bSet = true; + if( bOdd ) + --nIdx; // within interval + else if( ! bRight && ( nIdx >= nCount || (*pTmp)[ nIdx ] != nXPos ) ) + { + if( nIdx ) + nIdx -= 2; // an interval to the left + else + bSet = false; // before the first interval + } + + if( bSet && nIdx < nCount ) + { + aRectFnSet.SetTopAndHeight( aRet, aRectFnSet.GetTop(rLine), + aRectFnSet.GetHeight(rLine) ); + aRectFnSet.SetLeft( aRet, (*pTmp)[ nIdx ] ); + aRectFnSet.SetRight( aRet, (*pTmp)[ nIdx + 1 ] + 1 ); + } + } + return aRet; +} + +SwTextFly::SwTextFly() + : pPage(nullptr) + , mpCurrAnchoredObj(nullptr) + , m_pCurrFrame(nullptr) + , m_pMaster(nullptr) + , nMinBottom(0) + , nNextTop(0) + , m_nCurrFrameNodeIndex(0) + , bOn(false) + , bTopRule(false) + , mbIgnoreCurrentFrame(false) + , mbIgnoreContour(false) + , mbIgnoreObjsInHeaderFooter(false) + +{ +} + +SwTextFly::SwTextFly( const SwTextFrame *pFrame ) +{ + CtorInitTextFly( pFrame ); +} + +SwTextFly::SwTextFly( const SwTextFly& rTextFly ) +{ + pPage = rTextFly.pPage; + mpCurrAnchoredObj = rTextFly.mpCurrAnchoredObj; + m_pCurrFrame = rTextFly.m_pCurrFrame; + m_pMaster = rTextFly.m_pMaster; + if( rTextFly.mpAnchoredObjList ) + { + mpAnchoredObjList.reset( new SwAnchoredObjList( *(rTextFly.mpAnchoredObjList) ) ); + } + + bOn = rTextFly.bOn; + bTopRule = rTextFly.bTopRule; + nMinBottom = rTextFly.nMinBottom; + nNextTop = rTextFly.nNextTop; + m_nCurrFrameNodeIndex = rTextFly.m_nCurrFrameNodeIndex; + mbIgnoreCurrentFrame = rTextFly.mbIgnoreCurrentFrame; + mbIgnoreContour = rTextFly.mbIgnoreContour; + mbIgnoreObjsInHeaderFooter = rTextFly.mbIgnoreObjsInHeaderFooter; +} + +SwTextFly::~SwTextFly() +{ +} + +void SwTextFly::CtorInitTextFly( const SwTextFrame *pFrame ) +{ + mbIgnoreCurrentFrame = false; + mbIgnoreContour = false; + mbIgnoreObjsInHeaderFooter = false; + pPage = pFrame->FindPageFrame(); + const SwFlyFrame* pTmp = pFrame->FindFlyFrame(); + // #i68520# + mpCurrAnchoredObj = pTmp; + m_pCurrFrame = pFrame; + m_pMaster = m_pCurrFrame->IsFollow() ? nullptr : m_pCurrFrame; + // If we're not overlapped by a frame or if a FlyCollection does not exist + // at all, we switch off forever. + // It could be, however, that a line is added while formatting, that + // extends into a frame. + // That's why we do not optimize for: bOn = pSortedFlys && IsAnyFrame(); + bOn = pPage->GetSortedObjs() != nullptr; + bTopRule = true; + nMinBottom = 0; + nNextTop = 0; + m_nCurrFrameNodeIndex = ULONG_MAX; +} + +SwRect SwTextFly::GetFrame_( const SwRect &rRect ) const +{ + SwRect aRet; + if( ForEach( rRect, &aRet, true ) ) + { + SwRectFnSet aRectFnSet(m_pCurrFrame); + aRectFnSet.SetTop( aRet, aRectFnSet.GetTop(rRect) ); + + // Do not always adapt the bottom + const SwTwips nRetBottom = aRectFnSet.GetBottom(aRet); + const SwTwips nRectBottom = aRectFnSet.GetBottom(rRect); + if ( aRectFnSet.YDiff( nRetBottom, nRectBottom ) > 0 || + aRectFnSet.GetHeight(aRet) < 0 ) + aRectFnSet.SetBottom( aRet, nRectBottom ); + } + return aRet; +} + +bool SwTextFly::IsAnyFrame() const +{ + SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame)); + + OSL_ENSURE( bOn, "IsAnyFrame: Why?" ); + SwRect aRect(m_pCurrFrame->getFrameArea().Pos() + m_pCurrFrame->getFramePrintArea().Pos(), + m_pCurrFrame->getFramePrintArea().SSize()); + + return ForEach( aRect, nullptr, false ); +} + +bool SwTextFly::IsAnyObj( const SwRect &rRect ) const +{ + OSL_ENSURE( bOn, "SwTextFly::IsAnyObj: Who's knocking?" ); + + SwRect aRect( rRect ); + if ( aRect.IsEmpty() ) + { + aRect = SwRect(m_pCurrFrame->getFrameArea().Pos() + m_pCurrFrame->getFramePrintArea().Pos(), + m_pCurrFrame->getFramePrintArea().SSize()); + } + + const SwSortedObjs *pSorted = pPage->GetSortedObjs(); + if( pSorted ) // bOn actually makes sure that we have objects on the side, + // but who knows who deleted something in the meantime? + { + for ( size_t i = 0; i < pSorted->size(); ++i ) + { + const SwAnchoredObject* pObj = (*pSorted)[i]; + + const SwRect aBound( pObj->GetObjRectWithSpaces() ); + + // Optimization + if( pObj->GetObjRect().Left() > aRect.Right() ) + continue; + + // #i68520# + if( mpCurrAnchoredObj != pObj && aBound.IsOver( aRect ) ) + return true; + } + } + return false; +} + +const SwTextFrame* SwTextFly::GetMaster_() +{ + m_pMaster = m_pCurrFrame; + while (m_pMaster && m_pMaster->IsFollow()) + m_pMaster = m_pMaster->FindMaster(); + return m_pMaster; +} + +void SwTextFly::DrawTextOpaque( SwDrawTextInfo &rInf ) +{ + SwSaveClip aClipSave( rInf.GetpOut() ); + SwRect aRect( rInf.GetPos(), rInf.GetSize() ); + if( rInf.GetSpace() ) + { + TextFrameIndex const nTmpLen = TextFrameIndex(COMPLETE_STRING) == rInf.GetLen() + ? TextFrameIndex(rInf.GetText().getLength()) + : rInf.GetLen(); + if( rInf.GetSpace() > 0 ) + { + sal_Int32 nSpaceCnt = 0; + const TextFrameIndex nEndPos = rInf.GetIdx() + nTmpLen; + for (TextFrameIndex nPos = rInf.GetIdx(); nPos < nEndPos; ++nPos) + { + if (CH_BLANK == rInf.GetText()[sal_Int32(nPos)]) + ++nSpaceCnt; + } + if( nSpaceCnt ) + aRect.Width( aRect.Width() + nSpaceCnt * rInf.GetSpace() ); + } + else + aRect.Width( aRect.Width() - sal_Int32(nTmpLen) * rInf.GetSpace() ); + } + + if( aClipSave.IsOn() && rInf.GetOut().IsClipRegion() ) + { + SwRect aClipRect( rInf.GetOut().GetClipRegion().GetBoundRect() ); + aRect.Intersection( aClipRect ); + } + + SwRegionRects aRegion( aRect ); + + bool bOpaque = false; + // #i68520# + const sal_uInt32 nCurrOrd = mpCurrAnchoredObj + ? mpCurrAnchoredObj->GetDrawObj()->GetOrdNum() + : SAL_MAX_UINT32; + OSL_ENSURE( !bTopRule, "DrawTextOpaque: Wrong TopRule" ); + + // #i68520# + const SwAnchoredObjList::size_type nCount( bOn ? GetAnchoredObjList()->size() : 0 ); + if (nCount > 0) + { + const SdrLayerID nHellId = pPage->getRootFrame()->GetCurrShell()->getIDocumentDrawModelAccess().GetHellId(); + for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i ) + { + // #i68520# + const SwAnchoredObject* pTmpAnchoredObj = (*mpAnchoredObjList)[i]; + if( dynamic_cast<const SwFlyFrame*>(pTmpAnchoredObj) && + mpCurrAnchoredObj != pTmpAnchoredObj ) + { + // #i68520# + const SwFlyFrame& rFly = dynamic_cast<const SwFlyFrame&>(*pTmpAnchoredObj); + if( aRegion.GetOrigin().IsOver( rFly.getFrameArea() ) ) + { + const SwFrameFormat *pFormat = rFly.GetFormat(); + const SwFormatSurround &rSur = pFormat->GetSurround(); + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + // Only the ones who are opaque and more to the top + if( ! rFly.IsBackgroundTransparent() && + css::text::WrapTextMode_THROUGH == rSur.GetSurround() && + ( !rSur.IsAnchorOnly() || + // #i68520# + GetMaster() == rFly.GetAnchorFrame() || + ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) && + (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId()) + ) + ) && + // #i68520# + pTmpAnchoredObj->GetDrawObj()->GetLayer() != nHellId && + nCurrOrd < pTmpAnchoredObj->GetDrawObj()->GetOrdNum() + ) + { + // Except for the content is transparent + const SwNoTextFrame *pNoText = + rFly.Lower() && rFly.Lower()->IsNoTextFrame() + ? static_cast<const SwNoTextFrame*>(rFly.Lower()) + : nullptr; + if ( !pNoText || + (!pNoText->IsTransparent() && !rSur.IsContour()) ) + { + bOpaque = true; + aRegion -= rFly.getFrameArea(); + } + } + } + } + } + } + + Point aPos( rInf.GetPos().X(), rInf.GetPos().Y() + rInf.GetAscent() ); + const Point aOldPos(rInf.GetPos()); + rInf.SetPos( aPos ); + + if( !bOpaque ) + { + if( rInf.GetKern() ) + rInf.GetFont()->DrawStretchText_( rInf ); + else + rInf.GetFont()->DrawText_( rInf ); + rInf.SetPos(aOldPos); + return; + } + else if( !aRegion.empty() ) + { + // What a huge effort ... + SwSaveClip aClipVout( rInf.GetpOut() ); + for( size_t i = 0; i < aRegion.size(); ++i ) + { + SwRect &rRect = aRegion[i]; + if( rRect != aRegion.GetOrigin() ) + aClipVout.ChgClip( rRect ); + if( rInf.GetKern() ) + rInf.GetFont()->DrawStretchText_( rInf ); + else + rInf.GetFont()->DrawText_( rInf ); + } + } + rInf.SetPos(aOldPos); +} + +void SwTextFly::DrawFlyRect( OutputDevice* pOut, const SwRect &rRect ) +{ + SwRegionRects aRegion( rRect ); + OSL_ENSURE( !bTopRule, "DrawFlyRect: Wrong TopRule" ); + // #i68520# + const SwAnchoredObjList::size_type nCount( bOn ? GetAnchoredObjList()->size() : 0 ); + if (nCount > 0) + { + const SdrLayerID nHellId = pPage->getRootFrame()->GetCurrShell()->getIDocumentDrawModelAccess().GetHellId(); + for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i ) + { + // #i68520# + const SwAnchoredObject* pAnchoredObjTmp = (*mpAnchoredObjList)[i]; + if (mpCurrAnchoredObj == pAnchoredObjTmp) + continue; + + // #i68520# + const SwFlyFrame* pFly = dynamic_cast<const SwFlyFrame*>(pAnchoredObjTmp); + if (pFly) + { + // #i68520# + const SwFormatSurround& rSur = pAnchoredObjTmp->GetFrameFormat().GetSurround(); + + // OD 24.01.2003 #106593# - correct clipping of fly frame area. + // Consider that fly frame background/shadow can be transparent + // and <SwAlignRect(..)> fly frame area + // #i47804# - consider transparent graphics + // and OLE objects. + bool bClipFlyArea = + ( ( css::text::WrapTextMode_THROUGH == rSur.GetSurround() ) + // #i68520# + ? (pAnchoredObjTmp->GetDrawObj()->GetLayer() != nHellId) + : !rSur.IsContour() ) && + !pFly->IsBackgroundTransparent() && + ( !pFly->Lower() || + !pFly->Lower()->IsNoTextFrame() || + !static_cast<const SwNoTextFrame*>(pFly->Lower())->IsTransparent() ); + if ( bClipFlyArea ) + { + // #i68520# + SwRect aFly( pAnchoredObjTmp->GetObjRect() ); + // OD 24.01.2003 #106593# + ::SwAlignRect( aFly, pPage->getRootFrame()->GetCurrShell(), pOut ); + if( !aFly.IsEmpty() ) + aRegion -= aFly; + } + } + } + } + + for( size_t i = 0; i < aRegion.size(); ++i ) + { + pOut->DrawRect( aRegion[i].SVRect() ); + } +} + +/** + * #i26945# - change first parameter + * Now it's the <SwAnchoredObject> instance of the floating screen object + */ +bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, + const bool bInFootnote, + const bool bInFooterOrHeader ) +{ + // #i68520# + // <mpCurrAnchoredObj> is set, if <m_pCurrFrame> is inside a fly frame + if( _pAnchoredObj != mpCurrAnchoredObj ) + { + // #i26945# + const SdrObject* pNew = _pAnchoredObj->GetDrawObj(); + // #102344# Ignore connectors which have one or more connections + if (const SdrEdgeObj* pEdgeObj = dynamic_cast<const SdrEdgeObj*>(pNew)) + { + if (pEdgeObj->GetConnectedNode(true) || pEdgeObj->GetConnectedNode(false)) + { + return false; + } + } + + if( ( bInFootnote || bInFooterOrHeader ) && bTopRule ) + { + // #i26945# + const SwFrameFormat& rFrameFormat = _pAnchoredObj->GetFrameFormat(); + const SwFormatAnchor& rNewA = rFrameFormat.GetAnchor(); + if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId()) + { + if ( bInFootnote ) + return false; + + if ( bInFooterOrHeader ) + { + const SwFormatVertOrient& aVert( rFrameFormat.GetVertOrient() ); + bool bVertPrt = aVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA || + aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA; + if( bVertPrt ) + return false; + } + } + } + + // #i68520# + // bEvade: consider pNew, if we are not inside a fly + // consider pNew, if pNew is lower of <mpCurrAnchoredObj> + bool bEvade = !mpCurrAnchoredObj || + Is_Lower_Of( dynamic_cast<const SwFlyFrame*>(mpCurrAnchoredObj), pNew); + + if ( !bEvade ) + { + // We are currently inside a fly frame and pNew is not + // inside this fly frame. We can do some more checks if + // we have to consider pNew. + + // If bTopRule is not set, we ignore the frame types. + // We directly check the z-order + if ( !bTopRule ) + bEvade = true; + else + { + // Within chained Flys we only avoid Lower + // #i68520# + const SwFormatChain &rChain = mpCurrAnchoredObj->GetFrameFormat().GetChain(); + if ( !rChain.GetPrev() && !rChain.GetNext() ) + { + // #i26945# + const SwFormatAnchor& rNewA = _pAnchoredObj->GetFrameFormat().GetAnchor(); + // #i68520# + const SwFormatAnchor& rCurrA = mpCurrAnchoredObj->GetFrameFormat().GetAnchor(); + + // If <mpCurrAnchoredObj> is anchored as character, its content + // does not wrap around pNew + if (RndStdIds::FLY_AS_CHAR == rCurrA.GetAnchorId()) + return false; + + // If pNew is anchored to page and <mpCurrAnchoredObj is not anchored + // to page, the content of <mpCurrAnchoredObj> does not wrap around pNew + // If both pNew and <mpCurrAnchoredObj> are anchored to page, we can do + // some more checks + if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId()) + { + if (RndStdIds::FLY_AT_PAGE == rCurrA.GetAnchorId()) + { + bEvade = true; + } + else + return false; + } + else if (RndStdIds::FLY_AT_PAGE == rCurrA.GetAnchorId()) + return false; // Page anchored ones only avoid page anchored ones + else if (RndStdIds::FLY_AT_FLY == rNewA.GetAnchorId()) + bEvade = true; // Non-page anchored ones avoid frame anchored ones + else if( RndStdIds::FLY_AT_FLY == rCurrA.GetAnchorId() ) + return false; // Frame anchored ones do not avoid paragraph anchored ones + // #i57062# + // In order to avoid loop situation, it's decided to adjust + // the wrapping behaviour of content of at-paragraph/at-character + // anchored objects to one in the page header/footer and + // the document body --> content of at-paragraph/at-character + // anchored objects doesn't wrap around each other. + else + return false; + } + } + + // But: we never avoid a subordinate one and additionally we only avoid when overlapping. + // #i68520# + bEvade &= ( mpCurrAnchoredObj->GetDrawObj()->GetOrdNum() < pNew->GetOrdNum() ); + if( bEvade ) + { + // #i68520# + const SwRect& aTmp( _pAnchoredObj->GetObjRectWithSpaces() ); + if ( !aTmp.IsOver( mpCurrAnchoredObj->GetObjRectWithSpaces() ) ) + bEvade = false; + } + } + + if ( bEvade ) + { + // #i26945# + const SwFormatAnchor& rNewA = _pAnchoredObj->GetFrameFormat().GetAnchor(); + OSL_ENSURE( RndStdIds::FLY_AS_CHAR != rNewA.GetAnchorId(), + "Don't call GetTop with a FlyInContentFrame" ); + if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId()) + return true; // We always avoid page anchored ones + + // If Flys anchored at paragraph are caught in a FlyCnt, then + // their influence ends at the borders of the FlyCnt! + // If we are currently formatting the text of the FlyCnt, then + // it has to get out of the way of the Frame anchored at paragraph! + // m_pCurrFrame is the anchor of pNew? + // #i26945# + const SwFrame* pTmp = _pAnchoredObj->GetAnchorFrame(); + if (pTmp == m_pCurrFrame) + return true; + if( pTmp->IsTextFrame() && ( pTmp->IsInFly() || pTmp->IsInFootnote() ) ) + { + // #i26945# + Point aPos = _pAnchoredObj->GetObjRect().Pos(); + pTmp = GetVirtualUpper( pTmp, aPos ); + } + // #i26945# + // If <pTmp> is a text frame inside a table, take the upper + // of the anchor frame, which contains the anchor position. + else if ( pTmp->IsTextFrame() && pTmp->IsInTab() ) + { + pTmp = const_cast<SwAnchoredObject*>(_pAnchoredObj) + ->GetAnchorFrameContainingAnchPos()->GetUpper(); + } + // #i28701# - consider all objects in same context, + // if wrapping style is considered on object positioning. + // Thus, text will wrap around negative positioned objects. + // #i3317# - remove condition on checking, + // if wrappings style is considered on object positioning. + // Thus, text is wrapping around negative positioned objects. + // #i35640# - no consideration of negative + // positioned objects, if wrapping style isn't considered on + // object position and former text wrapping is applied. + // This condition is typically for documents imported from the + // OpenOffice.org file format. + const IDocumentSettingAccess* pIDSA = &m_pCurrFrame->GetDoc().getIDocumentSettingAccess(); + if ( ( pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) || + !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) ) && + ::FindContext( pTmp, SwFrameType::None ) == ::FindContext(m_pCurrFrame, SwFrameType::None)) + { + return true; + } + + const SwFrame* pHeader = nullptr; + if (m_pCurrFrame->GetNext() != pTmp && + (IsFrameInSameContext( pTmp, m_pCurrFrame ) || + // #i13832#, #i24135# wrap around objects in page header + ( !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) && + nullptr != ( pHeader = pTmp->FindFooterOrHeader() ) && + m_pCurrFrame->IsInDocBody()))) + { + if( pHeader || RndStdIds::FLY_AT_FLY == rNewA.GetAnchorId() ) + return true; + + // Compare indices: + // The Index of the other is retrieved from the anchor attr. + sal_uLong nTmpIndex = rNewA.GetContentAnchor()->nNode.GetIndex(); + // Now check whether the current paragraph is before the anchor + // of the displaced object in the text, then we don't have to + // get out of its way. + // If possible determine Index via SwFormatAnchor because + // otherwise it's quite expensive. + if (ULONG_MAX == m_nCurrFrameNodeIndex) + m_nCurrFrameNodeIndex = m_pCurrFrame->GetTextNodeFirst()->GetIndex(); + + if (FrameContainsNode(*m_pCurrFrame, nTmpIndex) || nTmpIndex < m_nCurrFrameNodeIndex) + return true; + } + } + } + return false; +} + +// #i68520# +SwAnchoredObjList* SwTextFly::InitAnchoredObjList() +{ + OSL_ENSURE( m_pCurrFrame, "InitFlyList: No Frame, no FlyList" ); + // #i68520# + OSL_ENSURE( !mpAnchoredObjList, "InitFlyList: FlyList already initialized" ); + + SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame)); + + const SwSortedObjs *pSorted = pPage->GetSortedObjs(); + const size_t nCount = pSorted ? pSorted->size() : 0; + // --> #108724# Page header/footer content doesn't have to wrap around + // floating screen objects + const bool bFooterHeader = nullptr != m_pCurrFrame->FindFooterOrHeader(); + const IDocumentSettingAccess* pIDSA = &m_pCurrFrame->GetDoc().getIDocumentSettingAccess(); + // #i40155# - check, if frame is marked not to wrap + const bool bWrapAllowed = ( pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) || + (!m_pCurrFrame->IsInFootnote() && !bFooterHeader)); + + bOn = false; + + if( nCount && bWrapAllowed ) + { + // #i68520# + mpAnchoredObjList.reset(new SwAnchoredObjList ); + + // #i28701# - consider complete frame area for new + // text wrapping + SwRect aRect; + if ( pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) ) + { + aRect = m_pCurrFrame->getFramePrintArea(); + aRect += m_pCurrFrame->getFrameArea().Pos(); + } + else + { + aRect = m_pCurrFrame->getFrameArea(); + } + // Make ourselves a little smaller than we are, + // so that 1-Twip-overlappings are ignored (#49532) + SwRectFnSet aRectFnSet(m_pCurrFrame); + const long nRight = aRectFnSet.GetRight(aRect) - 1; + const long nLeft = aRectFnSet.GetLeft(aRect) + 1; + const bool bR2L = m_pCurrFrame->IsRightToLeft(); + + const IDocumentDrawModelAccess& rIDDMA = m_pCurrFrame->GetDoc().getIDocumentDrawModelAccess(); + + for( size_t i = 0; i < nCount; ++i ) + { + // #i68520# + // do not consider hidden objects + // check, if object has to be considered for text wrap + // #118809# - If requested, do not consider + // objects in page header|footer for text frames not in page + // header|footer. This is requested for the calculation of + // the base offset for objects <SwTextFrame::CalcBaseOfstForFly()> + // #i20505# Do not consider oversized objects + SwAnchoredObject* pAnchoredObj = (*pSorted)[ i ]; + assert(pAnchoredObj); + if ( !pAnchoredObj || + !rIDDMA.IsVisibleLayerId( pAnchoredObj->GetDrawObj()->GetLayer() ) || + !pAnchoredObj->ConsiderForTextWrap() || + ( mbIgnoreObjsInHeaderFooter && !bFooterHeader && + pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) ) + { + continue; + } + + const SwRect aBound( pAnchoredObj->GetObjRectWithSpaces() ); + if ( nRight < aRectFnSet.GetLeft(aBound) || + aRectFnSet.YDiff( aRectFnSet.GetTop(aRect), + aRectFnSet.GetBottom(aBound) ) > 0 || + nLeft > aRectFnSet.GetRight(aBound) || + aRectFnSet.GetHeight(aBound) > + 2 * aRectFnSet.GetHeight(pPage->getFrameArea()) ) + { + continue; + } + + // #i26945# - pass <pAnchoredObj> to method + // <GetTop(..)> instead of only the <SdrObject> instance of the + // anchored object + if (GetTop(pAnchoredObj, m_pCurrFrame->IsInFootnote(), bFooterHeader)) + { + // OD 11.03.2003 #107862# - adjust insert position: + // overlapping objects should be sorted from left to right and + // inside left to right sorting from top to bottom. + // If objects on the same position are found, they are sorted + // on its width. + // #i68520# + { + SwAnchoredObjList::iterator aInsPosIter = + std::lower_bound( mpAnchoredObjList->begin(), + mpAnchoredObjList->end(), + pAnchoredObj, + AnchoredObjOrder( bR2L, aRectFnSet.FnRect() ) ); + + mpAnchoredObjList->insert( aInsPosIter, pAnchoredObj ); + } + + const SwFormatSurround &rFlyFormat = pAnchoredObj->GetFrameFormat().GetSurround(); + // #i68520# + if ( rFlyFormat.IsAnchorOnly() && + pAnchoredObj->GetAnchorFrame() == GetMaster() ) + { + const SwFormatVertOrient &rTmpFormat = + pAnchoredObj->GetFrameFormat().GetVertOrient(); + if( text::VertOrientation::BOTTOM != rTmpFormat.GetVertOrient() ) + nMinBottom = ( aRectFnSet.IsVert() && nMinBottom ) ? + std::min( nMinBottom, aBound.Left() ) : + std::max( nMinBottom, aRectFnSet.GetBottom(aBound) ); + } + + bOn = true; + } + } + if( nMinBottom ) + { + SwTwips nMax = aRectFnSet.GetPrtBottom(*m_pCurrFrame->GetUpper()); + if( aRectFnSet.YDiff( nMinBottom, nMax ) > 0 ) + nMinBottom = nMax; + } + } + else + { + // #i68520# + mpAnchoredObjList.reset( new SwAnchoredObjList ); + } + + // #i68520# + return mpAnchoredObjList.get(); +} + +SwTwips SwTextFly::CalcMinBottom() const +{ + SwTwips nRet = 0; + const SwContentFrame *pLclMaster = GetMaster(); + OSL_ENSURE(pLclMaster, "SwTextFly without master"); + const SwSortedObjs *pDrawObj = pLclMaster ? pLclMaster->GetDrawObjs() : nullptr; + const size_t nCount = pDrawObj ? pDrawObj->size() : 0; + if( nCount ) + { + SwTwips nEndOfFrame = m_pCurrFrame->getFrameArea().Bottom(); + for( size_t i = 0; i < nCount; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pDrawObj)[ i ]; + const SwFormatSurround &rFlyFormat = pAnchoredObj->GetFrameFormat().GetSurround(); + if( rFlyFormat.IsAnchorOnly() ) + { + const SwFormatVertOrient &rTmpFormat = + pAnchoredObj->GetFrameFormat().GetVertOrient(); + if( text::VertOrientation::BOTTOM != rTmpFormat.GetVertOrient() ) + { + const SwRect& aBound( pAnchoredObj->GetObjRectWithSpaces() ); + if( aBound.Top() < nEndOfFrame ) + nRet = std::max( nRet, aBound.Bottom() ); + } + } + } + SwTwips nMax = m_pCurrFrame->GetUpper()->getFrameArea().Top() + + m_pCurrFrame->GetUpper()->getFramePrintArea().Bottom(); + if( nRet > nMax ) + nRet = nMax; + } + return nRet; +} + +bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const +{ + SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame)); + + // Optimization + SwRectFnSet aRectFnSet(m_pCurrFrame); + + // tdf#127235 stop if the area is larger than the page + if( aRectFnSet.GetHeight(pPage->getFrameArea()) < aRectFnSet.GetHeight(rRect)) + { + // get the doc model description + const SwPageDesc* pPageDesc = pPage->GetPageDesc(); + + // if there is no next page style or it is the same as the current + // => stop trying to place the frame (it would end in an infinite loop) + if( pPageDesc && + ( !pPageDesc->GetFollow() || pPageDesc->GetFollow() == pPageDesc) ) + { + return false; + } + } + + bool bRet = false; + // #i68520# + const SwAnchoredObjList::size_type nCount( bOn ? GetAnchoredObjList()->size() : 0 ); + if (nCount > 0) + { + for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i ) + { + // #i68520# + const SwAnchoredObject* pAnchoredObj = (*mpAnchoredObjList)[i]; + + SwRect aRect( pAnchoredObj->GetObjRectWithSpaces() ); + + if( aRectFnSet.GetLeft(aRect) > aRectFnSet.GetRight(rRect) ) + break; + + // #i68520# + if ( mpCurrAnchoredObj != pAnchoredObj && aRect.IsOver( rRect ) ) + { + // #i68520# + const SwFormat* pFormat( &(pAnchoredObj->GetFrameFormat()) ); + const SwFormatSurround &rSur = pFormat->GetSurround(); + if( bAvoid ) + { + // If the text flows below, it has no influence on + // formatting. In LineIter::DrawText() it is "just" + // necessary to cleverly set the ClippingRegions + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if( ( css::text::WrapTextMode_THROUGH == rSur.GetSurround() && + ( !rSur.IsAnchorOnly() || + // #i68520# + GetMaster() == pAnchoredObj->GetAnchorFrame() || + ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) && + (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId())) ) ) + || aRect.Top() == FAR_AWAY ) + continue; + } + + // #i58642# + // Compare <GetMaster()> instead of <m_pCurrFrame> with the + // anchor frame of the anchored object, because a follow frame + // has to ignore the anchored objects of its master frame. + // Note: Anchored objects are always registered at the master + // frame, exception are as-character anchored objects, + // but these aren't handled here. + // #i68520# + if ( mbIgnoreCurrentFrame && + GetMaster() == pAnchoredObj->GetAnchorFrame() ) + continue; + + if( pRect ) + { + // #i68520# + SwRect aFly = AnchoredObjToRect( pAnchoredObj, rRect ); + if( aFly.IsEmpty() || !aFly.IsOver( rRect ) ) + continue; + if( !bRet || ( + (!m_pCurrFrame->IsRightToLeft() && + ( aRectFnSet.GetLeft(aFly) < + aRectFnSet.GetLeft(*pRect) ) ) || + (m_pCurrFrame->IsRightToLeft() && + ( aRectFnSet.GetRight(aFly) > + aRectFnSet.GetRight(*pRect) ) ) ) ) + *pRect = aFly; + if( rSur.IsContour() ) + { + bRet = true; + continue; + } + } + bRet = true; + break; + } + } + } + + return bRet; +} + +// #i68520# +SwAnchoredObjList::size_type SwTextFly::GetPos( const SwAnchoredObject* pAnchoredObj ) const +{ + SwAnchoredObjList::size_type nCount = GetAnchoredObjList()->size(); + SwAnchoredObjList::size_type nRet = 0; + while ( nRet < nCount && pAnchoredObj != (*mpAnchoredObjList)[ nRet ] ) + ++nRet; + return nRet; +} + +// #i68520# +void SwTextFly::CalcRightMargin( SwRect &rFly, + SwAnchoredObjList::size_type nFlyPos, + const SwRect &rLine ) const +{ + // Usually the right margin is the right margin of the Printarea + OSL_ENSURE( !m_pCurrFrame->IsVertical() || !m_pCurrFrame->IsSwapped(), + "SwTextFly::CalcRightMargin with swapped frame" ); + SwRectFnSet aRectFnSet(m_pCurrFrame); + // #118796# - correct determination of right of printing area + SwTwips nRight = aRectFnSet.GetPrtRight(*m_pCurrFrame); + SwTwips nFlyRight = aRectFnSet.GetRight(rFly); + SwRect aLine( rLine ); + aRectFnSet.SetRight( aLine, nRight ); + aRectFnSet.SetLeft( aLine, aRectFnSet.GetLeft(rFly) ); + + // It is possible that there is another object that is _above_ us + // and protrudes into the same line. + // Flys with run-through are invisible for those below, i.e., they + // are ignored for computing the margins of other Flys. + // 3301: pNext->getFrameArea().IsOver( rLine ) is necessary + // #i68520# + css::text::WrapTextMode eSurroundForTextWrap; + + bool bStop = false; + // #i68520# + SwAnchoredObjList::size_type nPos = 0; + + // #i68520# + while( nPos < mpAnchoredObjList->size() && !bStop ) + { + if( nPos == nFlyPos ) + { + ++nPos; + continue; + } + // #i68520# + const SwAnchoredObject* pNext = (*mpAnchoredObjList)[ nPos++ ]; + if ( pNext == mpCurrAnchoredObj ) + continue; + eSurroundForTextWrap = GetSurroundForTextWrap( pNext ); + if( css::text::WrapTextMode_THROUGH == eSurroundForTextWrap ) + continue; + + const SwRect aTmp( SwContourCache::CalcBoundRect + ( pNext, aLine, m_pCurrFrame, nFlyRight, true ) ); + SwTwips nTmpRight = aRectFnSet.GetRight(aTmp); + + // optimization: + // Record in nNextTop at which Y-position frame related changes are + // likely. This is so that, despite only looking at frames in the + // current line height, for frames without wrap the line height is + // incremented so that with a single line the lower border of the frame + // (or possibly the upper border of another frame) is reached. + // Especially in HTML documents there are often (dummy) paragraphs in + // 2 pt font, and they used to only evade big frames after huge numbers + // of empty lines. + const long nTmpTop = aRectFnSet.GetTop(aTmp); + if( aRectFnSet.YDiff( nTmpTop, aRectFnSet.GetTop(aLine) ) > 0 ) + { + if( aRectFnSet.YDiff( nNextTop, nTmpTop ) > 0 ) + SetNextTop( nTmpTop ); // upper border of next frame + } + else if (!aRectFnSet.GetWidth(aTmp)) // typical for Objects with contour wrap + { // For Objects with contour wrap that start before the current + // line, and end below it, but do not actually overlap it, the + // optimization has to be disabled, because the circumstances + // can change in the next line. + if( ! aRectFnSet.GetHeight(aTmp) || + aRectFnSet.YDiff( aRectFnSet.GetBottom(aTmp), + aRectFnSet.GetTop(aLine) ) > 0 ) + SetNextTop( 0 ); + } + if( aTmp.IsOver( aLine ) && nTmpRight > nFlyRight ) + { + nFlyRight = nTmpRight; + if( css::text::WrapTextMode_RIGHT == eSurroundForTextWrap || + css::text::WrapTextMode_PARALLEL == eSurroundForTextWrap ) + { + // overrule the FlyFrame + if( nRight > nFlyRight ) + nRight = nFlyRight; + bStop = true; + } + } + } + aRectFnSet.SetRight( rFly, nRight ); +} + +// #i68520# +void SwTextFly::CalcLeftMargin( SwRect &rFly, + SwAnchoredObjList::size_type nFlyPos, + const SwRect &rLine ) const +{ + OSL_ENSURE( !m_pCurrFrame->IsVertical() || !m_pCurrFrame->IsSwapped(), + "SwTextFly::CalcLeftMargin with swapped frame" ); + SwRectFnSet aRectFnSet(m_pCurrFrame); + // #118796# - correct determination of left of printing area + SwTwips nLeft = aRectFnSet.GetPrtLeft(*m_pCurrFrame); + const SwTwips nFlyLeft = aRectFnSet.GetLeft(rFly); + + if( nLeft > nFlyLeft ) + nLeft = rFly.Left(); + + SwRect aLine( rLine ); + aRectFnSet.SetLeft( aLine, nLeft ); + + // It is possible that there is another object that is _above_ us + // and protrudes into the same line. + // Flys with run-through are invisible for those below, i.e., they + // are ignored for computing the margins of other Flys. + // 3301: pNext->getFrameArea().IsOver( rLine ) is necessary + + // #i68520# + SwAnchoredObjList::size_type nMyPos = nFlyPos; + while( ++nFlyPos < mpAnchoredObjList->size() ) + { + // #i68520# + const SwAnchoredObject* pNext = (*mpAnchoredObjList)[ nFlyPos ]; + const SwRect& aTmp( pNext->GetObjRectWithSpaces() ); + if( aRectFnSet.GetLeft(aTmp) >= nFlyLeft ) + break; + } + + while( nFlyPos ) + { + if( --nFlyPos == nMyPos ) + continue; + // #i68520# + const SwAnchoredObject* pNext = (*mpAnchoredObjList)[ nFlyPos ]; + if( pNext == mpCurrAnchoredObj ) + continue; + css::text::WrapTextMode eSurroundForTextWrap = GetSurroundForTextWrap( pNext ); + if( css::text::WrapTextMode_THROUGH == eSurroundForTextWrap ) + continue; + + const SwRect aTmp( SwContourCache::CalcBoundRect + (pNext, aLine, m_pCurrFrame, nFlyLeft, false) ); + + if( aRectFnSet.GetLeft(aTmp) < nFlyLeft && aTmp.IsOver( aLine ) ) + { + // #118796# - no '+1', because <..fnGetRight> + // returns the correct value. + SwTwips nTmpRight = aRectFnSet.GetRight(aTmp); + if ( nLeft <= nTmpRight ) + nLeft = nTmpRight; + + break; + } + } + aRectFnSet.SetLeft( rFly, nLeft ); +} + +// #i68520# +SwRect SwTextFly::AnchoredObjToRect( const SwAnchoredObject* pAnchoredObj, + const SwRect &rLine ) const +{ + SwRectFnSet aRectFnSet(m_pCurrFrame); + + const long nXPos = m_pCurrFrame->IsRightToLeft() ? + rLine.Right() : + aRectFnSet.GetLeft(rLine); + + SwRect aFly = mbIgnoreContour ? + pAnchoredObj->GetObjRectWithSpaces() : + SwContourCache::CalcBoundRect(pAnchoredObj, rLine, m_pCurrFrame, + nXPos, !m_pCurrFrame->IsRightToLeft()); + + if( !aFly.Width() ) + return aFly; + + // so the line may grow up to the lower edge of the frame + SetNextTop( aRectFnSet.GetBottom(aFly) ); + SwAnchoredObjList::size_type nFlyPos = GetPos( pAnchoredObj ); + + // LEFT and RIGHT, we grow the rectangle. + // We have some problems, when several frames are to be seen. + // At the moment, only the easier case is assumed: + // + LEFT means that the text must flow on the left of the frame, + // that is the frame expands to the right edge of the print area + // or to the next frame. + // + RIGHT is the opposite. + // Otherwise the set distance between text and frame is always + // added up. + switch( GetSurroundForTextWrap( pAnchoredObj ) ) + { + case css::text::WrapTextMode_LEFT : + { + CalcRightMargin( aFly, nFlyPos, rLine ); + break; + } + case css::text::WrapTextMode_RIGHT : + { + CalcLeftMargin( aFly, nFlyPos, rLine ); + break; + } + case css::text::WrapTextMode_NONE : + { + CalcRightMargin( aFly, nFlyPos, rLine ); + CalcLeftMargin( aFly, nFlyPos, rLine ); + break; + } + default: + break; + } + return aFly; +} + +// #i68520# + +// Wrap only on sides with at least 2cm space for the text +#define TEXT_MIN 1134 + +// MS Word wraps on sides with even less space (value guessed). +#define TEXT_MIN_SMALL 300 + +// Wrap on both sides up to a frame width of 1.5cm +#define FRAME_MAX 850 + +css::text::WrapTextMode SwTextFly::GetSurroundForTextWrap( const SwAnchoredObject* pAnchoredObj ) const +{ + const SwFrameFormat* pFormat = &(pAnchoredObj->GetFrameFormat()); + const SwFormatSurround &rFlyFormat = pFormat->GetSurround(); + css::text::WrapTextMode eSurroundForTextWrap = rFlyFormat.GetSurround(); + + if( rFlyFormat.IsAnchorOnly() && pAnchoredObj->GetAnchorFrame() != GetMaster() ) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) + { + return css::text::WrapTextMode_NONE; + } + } + + // in cause of run-through and nowrap ignore smartly + if( css::text::WrapTextMode_THROUGH == eSurroundForTextWrap || + css::text::WrapTextMode_NONE == eSurroundForTextWrap ) + return eSurroundForTextWrap; + + // left is left and right is right + if (m_pCurrFrame->IsRightToLeft()) + { + if ( css::text::WrapTextMode_LEFT == eSurroundForTextWrap ) + eSurroundForTextWrap = css::text::WrapTextMode_RIGHT; + else if ( css::text::WrapTextMode_RIGHT == eSurroundForTextWrap ) + eSurroundForTextWrap = css::text::WrapTextMode_LEFT; + } + + // "ideal page wrap": + if ( css::text::WrapTextMode_DYNAMIC == eSurroundForTextWrap ) + { + SwRectFnSet aRectFnSet(m_pCurrFrame); + const long nCurrLeft = aRectFnSet.GetPrtLeft(*m_pCurrFrame); + const long nCurrRight = aRectFnSet.GetPrtRight(*m_pCurrFrame); + const SwRect& aRect( pAnchoredObj->GetObjRectWithSpaces() ); + long nFlyLeft = aRectFnSet.GetLeft(aRect); + long nFlyRight = aRectFnSet.GetRight(aRect); + + if ( nFlyRight < nCurrLeft || nFlyLeft > nCurrRight ) + eSurroundForTextWrap = css::text::WrapTextMode_PARALLEL; + else + { + long nLeft = nFlyLeft - nCurrLeft; + long nRight = nCurrRight - nFlyRight; + if( nFlyRight - nFlyLeft > FRAME_MAX ) + { + if( nLeft < nRight ) + nLeft = 0; + else + nRight = 0; + } + const int textMin = GetMaster()->GetDoc() + .getIDocumentSettingAccess().get(DocumentSettingId::SURROUND_TEXT_WRAP_SMALL ) + ? TEXT_MIN_SMALL : TEXT_MIN; + + // In case there is no space on either side, then css::text::WrapTextMode_PARALLEL + // gives the same result when doing the initial layout or a layout + // update after editing, so prefer that over css::text::WrapTextMode_NONE. + if (nLeft == 0 && nRight == 0) + return css::text::WrapTextMode_PARALLEL; + + if( nLeft < textMin ) + nLeft = 0; + if( nRight < textMin ) + nRight = 0; + if( nLeft ) + eSurroundForTextWrap = nRight ? css::text::WrapTextMode_PARALLEL : css::text::WrapTextMode_LEFT; + else + eSurroundForTextWrap = nRight ? css::text::WrapTextMode_RIGHT: css::text::WrapTextMode_NONE; + } + } + + return eSurroundForTextWrap; +} + +bool SwTextFly::IsAnyFrame( const SwRect &rLine ) const +{ + + SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame)); + + OSL_ENSURE( bOn, "IsAnyFrame: Why?" ); + + return ForEach( rLine, nullptr, false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx new file mode 100644 index 000000000..2d585994e --- /dev/null +++ b/sw/source/core/text/txtfrm.cxx @@ -0,0 +1,4024 @@ +/* -*- 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 <hintids.hxx> +#include <hints.hxx> +#include <svl/ctloptions.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/pgrditem.hxx> +#include <unotools/configmgr.hxx> +#include <swmodule.hxx> +#include <SwSmartTagMgr.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewsh.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> +#include <viewopt.hxx> +#include <flyfrm.hxx> +#include <tabfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <tgrditem.hxx> +#include <dbg_lay.hxx> +#include <fmtfld.hxx> +#include <fmtftn.hxx> +#include <txtfld.hxx> +#include <txtftn.hxx> +#include <ftninfo.hxx> +#include <fmtline.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <sectfrm.hxx> +#include "itrform2.hxx" +#include "widorp.hxx" +#include "txtcache.hxx" +#include <fntcache.hxx> +#include <SwGrammarMarkUp.hxx> +#include <lineinfo.hxx> +#include <SwPortionHandler.hxx> +#include <dcontact.hxx> +#include <sortedobjs.hxx> +#include <txtflcnt.hxx> +#include <fmtflcnt.hxx> +#include <fmtcntnt.hxx> +#include <numrule.hxx> +#include <IGrammarContact.hxx> +#include <calbck.hxx> +#include <ftnidx.hxx> +#include <ftnfrm.hxx> + + +namespace sw { + + MergedAttrIterBase::MergedAttrIterBase(SwTextFrame const& rFrame) + : m_pMerged(rFrame.GetMergedPara()) + , m_pNode(m_pMerged ? nullptr : rFrame.GetTextNodeFirst()) + , m_CurrentExtent(0) + , m_CurrentHint(0) + { + } + + SwTextAttr const* MergedAttrIter::NextAttr(SwTextNode const** ppNode) + { + if (m_pMerged) + { + while (m_CurrentExtent < m_pMerged->extents.size()) + { + sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]); + if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints()) + { + while (m_CurrentHint < pHints->Count()) + { + SwTextAttr *const pHint(pHints->Get(m_CurrentHint)); + if (rExtent.nEnd < pHint->GetStart() + // <= if it has no end or isn't empty + || (rExtent.nEnd == pHint->GetStart() + && (!pHint->GetEnd() + || *pHint->GetEnd() != pHint->GetStart()))) + { + break; + } + ++m_CurrentHint; + if (rExtent.nStart <= pHint->GetStart()) + { + if (ppNode) + { + *ppNode = rExtent.pNode; + } + return pHint; + } + } + } + ++m_CurrentExtent; + if (m_CurrentExtent < m_pMerged->extents.size() && + rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode) + { + m_CurrentHint = 0; // reset + } + } + return nullptr; + } + else + { + SwpHints const*const pHints(m_pNode->GetpSwpHints()); + if (pHints) + { + if (m_CurrentHint < pHints->Count()) + { + SwTextAttr const*const pHint(pHints->Get(m_CurrentHint)); + ++m_CurrentHint; + if (ppNode) + { + *ppNode = m_pNode; + } + return pHint; + } + } + return nullptr; + } + } + + MergedAttrIterByEnd::MergedAttrIterByEnd(SwTextFrame const& rFrame) + : m_pNode(rFrame.GetMergedPara() ? nullptr : rFrame.GetTextNodeFirst()) + , m_CurrentHint(0) + { + if (!m_pNode) + { + MergedAttrIterReverse iter(rFrame); + SwTextNode const* pNode(nullptr); + while (SwTextAttr const* pHint = iter.PrevAttr(&pNode)) + { + m_Hints.emplace_back(pNode, pHint); + } + } + } + + SwTextAttr const* MergedAttrIterByEnd::NextAttr(SwTextNode const*& rpNode) + { + if (m_pNode) + { + SwpHints const*const pHints(m_pNode->GetpSwpHints()); + if (pHints) + { + if (m_CurrentHint < pHints->Count()) + { + SwTextAttr const*const pHint( + pHints->GetSortedByEnd(m_CurrentHint)); + ++m_CurrentHint; + rpNode = m_pNode; + return pHint; + } + } + return nullptr; + } + else + { + if (m_CurrentHint < m_Hints.size()) + { + auto const ret = m_Hints[m_Hints.size() - m_CurrentHint - 1]; + ++m_CurrentHint; + rpNode = ret.first; + return ret.second; + } + return nullptr; + } + } + + void MergedAttrIterByEnd::PrevAttr() + { + assert(0 < m_CurrentHint); // should only rewind as far as 0 + --m_CurrentHint; + } + + MergedAttrIterReverse::MergedAttrIterReverse(SwTextFrame const& rFrame) + : MergedAttrIterBase(rFrame) + { + if (m_pMerged) + { + m_CurrentExtent = m_pMerged->extents.size(); + SwpHints const*const pHints(0 < m_CurrentExtent + ? m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints() + : nullptr); + if (pHints) + { + pHints->SortIfNeedBe(); + m_CurrentHint = pHints->Count(); + } + } + else + { + if (SwpHints const*const pHints = m_pNode->GetpSwpHints()) + { + pHints->SortIfNeedBe(); + m_CurrentHint = pHints->Count(); + } + } + } + + SwTextAttr const* MergedAttrIterReverse::PrevAttr(SwTextNode const** ppNode) + { + if (m_pMerged) + { + while (0 < m_CurrentExtent) + { + sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent-1]); + if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints()) + { + while (0 < m_CurrentHint) + { + SwTextAttr *const pHint( + pHints->GetSortedByEnd(m_CurrentHint - 1)); + if (pHint->GetAnyEnd() < rExtent.nStart + // <= if it has end and isn't empty + || (pHint->GetEnd() + && *pHint->GetEnd() != pHint->GetStart() + && *pHint->GetEnd() == rExtent.nStart)) + { + break; + } + --m_CurrentHint; + if (pHint->GetAnyEnd() <= rExtent.nEnd) + { + if (ppNode) + { + *ppNode = rExtent.pNode; + } + return pHint; + } + } + } + --m_CurrentExtent; + if (0 < m_CurrentExtent && + rExtent.pNode != m_pMerged->extents[m_CurrentExtent-1].pNode) + { + SwpHints const*const pHints( + m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints()); + m_CurrentHint = pHints ? pHints->Count() : 0; // reset + if (pHints) + pHints->SortIfNeedBe(); + } + } + return nullptr; + } + else + { + SwpHints const*const pHints(m_pNode->GetpSwpHints()); + if (pHints && 0 < m_CurrentHint) + { + SwTextAttr const*const pHint(pHints->GetSortedByEnd(m_CurrentHint - 1)); + --m_CurrentHint; + if (ppNode) + { + *ppNode = m_pNode; + } + return pHint; + } + return nullptr; + } + } + + bool FrameContainsNode(SwContentFrame const& rFrame, sal_uLong const nNodeIndex) + { + if (rFrame.IsTextFrame()) + { + SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(rFrame)); + if (sw::MergedPara const*const pMerged = rTextFrame.GetMergedPara()) + { + sal_uLong const nFirst(pMerged->pFirstNode->GetIndex()); + sal_uLong const nLast(pMerged->pLastNode->GetIndex()); + return (nFirst <= nNodeIndex && nNodeIndex <= nLast); + } + else + { + return rTextFrame.GetTextNodeFirst()->GetIndex() == nNodeIndex; + } + } + else + { + assert(rFrame.IsNoTextFrame()); + return static_cast<SwNoTextFrame const&>(rFrame).GetNode()->GetIndex() == nNodeIndex; + } + } + + bool IsParaPropsNode(SwRootFrame const& rLayout, SwTextNode const& rNode) + { + if (rLayout.IsHideRedlines()) + { + if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(&rLayout))) + { + sw::MergedPara const*const pMerged(pFrame->GetMergedPara()); + if (pMerged && pMerged->pParaPropsNode != &rNode) + { + return false; + } + } + } + return true; + } + + SwTextNode * + GetParaPropsNode(SwRootFrame const& rLayout, SwNodeIndex const& rPos) + { + SwTextNode *const pTextNode(rPos.GetNode().GetTextNode()); + if (pTextNode && !sw::IsParaPropsNode(rLayout, *pTextNode)) + { + return static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))->GetMergedPara()->pParaPropsNode; + } + else + { + return pTextNode; + } + } + + SwPosition + GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos) + { + SwPosition pos(rPos); + SwTextNode const*const pNode(pos.nNode.GetNode().GetTextNode()); + if (pNode) + { + pos.nNode = *sw::GetParaPropsNode(rLayout, *pNode); + pos.nContent.Assign(pos.nNode.GetNode().GetContentNode(), 0); + } + return pos; + } + + std::pair<SwTextNode *, SwTextNode *> + GetFirstAndLastNode(SwRootFrame const& rLayout, SwNodeIndex const& rPos) + { + SwTextNode *const pTextNode(rPos.GetNode().GetTextNode()); + if (pTextNode && rLayout.IsHideRedlines()) + { + if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))) + { + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + return std::make_pair(pMerged->pFirstNode, const_cast<SwTextNode*>(pMerged->pLastNode)); + } + } + } + return std::make_pair(pTextNode, pTextNode); + } + + SwTextNode const& GetAttrMerged(SfxItemSet & rFormatSet, + SwTextNode const& rNode, SwRootFrame const*const pLayout) + { + rNode.SwContentNode::GetAttr(rFormatSet); + if (pLayout && pLayout->IsHideRedlines()) + { + auto pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(pLayout)); + if (sw::MergedPara const*const pMerged = pFrame ? pFrame->GetMergedPara() : nullptr) + { + if (pMerged->pFirstNode != &rNode) + { + rFormatSet.ClearItem(RES_PAGEDESC); + rFormatSet.ClearItem(RES_BREAK); + static_assert(RES_PAGEDESC + 1 == sal_uInt16(RES_BREAK), + "first-node items must be adjacent"); + SfxItemSet firstSet(*rFormatSet.GetPool(), + svl::Items<RES_PAGEDESC, RES_BREAK>{}); + pMerged->pFirstNode->SwContentNode::GetAttr(firstSet); + rFormatSet.Put(firstSet); + + } + if (pMerged->pParaPropsNode != &rNode) + { + for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i) + { + if (i != RES_PAGEDESC && i != RES_BREAK) + { + rFormatSet.ClearItem(i); + } + } + for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i) + { + rFormatSet.ClearItem(i); + } + SfxItemSet propsSet(*rFormatSet.GetPool(), + svl::Items<RES_PARATR_BEGIN, RES_PAGEDESC, + RES_BREAK+1, RES_FRMATR_END, + XATTR_FILL_FIRST, XATTR_FILL_LAST+1>{}); + pMerged->pParaPropsNode->SwContentNode::GetAttr(propsSet); + rFormatSet.Put(propsSet); + return *pMerged->pParaPropsNode; + } + // keep all the CHRATR/UNKNOWNATR anyway... + } + } + return rNode; + } + +} // namespace sw + +/// Switches width and height of the text frame +void SwTextFrame::SwapWidthAndHeight() +{ + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + if ( ! mbIsSwapped ) + { + const long nPrtOfstX = aPrt.Pos().X(); + aPrt.Pos().setX( aPrt.Pos().Y() ); + + if( IsVertLR() ) + { + aPrt.Pos().setY( nPrtOfstX ); + } + else + { + aPrt.Pos().setY( getFrameArea().Width() - ( nPrtOfstX + aPrt.Width() ) ); + } + } + else + { + const long nPrtOfstY = aPrt.Pos().Y(); + aPrt.Pos().setY( aPrt.Pos().X() ); + + if( IsVertLR() ) + { + aPrt.Pos().setX( nPrtOfstY ); + } + else + { + aPrt.Pos().setX( getFrameArea().Height() - ( nPrtOfstY + aPrt.Height() ) ); + } + } + + const long nPrtWidth = aPrt.Width(); + aPrt.Width( aPrt.Height() ); + aPrt.Height( nPrtWidth ); + } + + { + const long nFrameWidth = getFrameArea().Width(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( aFrm.Height() ); + aFrm.Height( nFrameWidth ); + } + + mbIsSwapped = ! mbIsSwapped; +} + +/** + * Calculates the coordinates of a rectangle when switching from + * horizontal to vertical layout. + */ +void SwTextFrame::SwitchHorizontalToVertical( SwRect& rRect ) const +{ + // calc offset inside frame + long nOfstX, nOfstY; + if ( IsVertLR() ) + { + if (IsVertLRBT()) + { + // X and Y offsets here mean the position of the point that will be the top left corner + // after the switch. + nOfstX = rRect.Left() + rRect.Width() - getFrameArea().Left(); + nOfstY = rRect.Top() - getFrameArea().Top(); + } + else + { + nOfstX = rRect.Left() - getFrameArea().Left(); + nOfstY = rRect.Top() - getFrameArea().Top(); + } + } + else + { + nOfstX = rRect.Left() - getFrameArea().Left(); + nOfstY = rRect.Top() + rRect.Height() - getFrameArea().Top(); + } + + const long nWidth = rRect.Width(); + const long nHeight = rRect.Height(); + + if ( IsVertLR() ) + { + rRect.Left(getFrameArea().Left() + nOfstY); + } + else + { + if ( mbIsSwapped ) + rRect.Left( getFrameArea().Left() + getFrameArea().Height() - nOfstY ); + else + // frame is rotated + rRect.Left( getFrameArea().Left() + getFrameArea().Width() - nOfstY ); + } + + if (IsVertLRBT()) + { + if (mbIsSwapped) + rRect.Top(getFrameArea().Top() + getFrameArea().Width() - nOfstX); + else + rRect.Top(getFrameArea().Top() + getFrameArea().Height() - nOfstX); + } + else + rRect.Top(getFrameArea().Top() + nOfstX); + rRect.Width( nHeight ); + rRect.Height( nWidth ); +} + +/** + * Calculates the coordinates of a point when switching from + * horizontal to vertical layout. + */ +void SwTextFrame::SwitchHorizontalToVertical( Point& rPoint ) const +{ + if (IsVertLRBT()) + { + // The horizontal origo is the top left corner, the LRBT origo is the + // bottom left corner. Finally x and y has to be swapped. + SAL_WARN_IF(!mbIsSwapped, "sw.core", + "SwTextFrame::SwitchHorizontalToVertical, IsVertLRBT, not swapped"); + Point aPoint(rPoint); + rPoint.setX(getFrameArea().Left() + (aPoint.Y() - getFrameArea().Top())); + // This would be bottom - x delta, but bottom is top + height, finally + // width (and not height), as it's swapped. + rPoint.setY(getFrameArea().Top() + getFrameArea().Width() + - (aPoint.X() - getFrameArea().Left())); + return; + } + + // calc offset inside frame + const long nOfstX = rPoint.X() - getFrameArea().Left(); + const long nOfstY = rPoint.Y() - getFrameArea().Top(); + if ( IsVertLR() ) + rPoint.setX( getFrameArea().Left() + nOfstY ); + else + { + if ( mbIsSwapped ) + rPoint.setX( getFrameArea().Left() + getFrameArea().Height() - nOfstY ); + else + // calc rotated coords + rPoint.setX( getFrameArea().Left() + getFrameArea().Width() - nOfstY ); + } + + rPoint.setY( getFrameArea().Top() + nOfstX ); +} + +/** + * Calculates the a limit value when switching from + * horizontal to vertical layout. + */ +long SwTextFrame::SwitchHorizontalToVertical( long nLimit ) const +{ + Point aTmp( 0, nLimit ); + SwitchHorizontalToVertical( aTmp ); + return aTmp.X(); +} + +/** + * Calculates the coordinates of a rectangle when switching from + * vertical to horizontal layout. + */ +void SwTextFrame::SwitchVerticalToHorizontal( SwRect& rRect ) const +{ + long nOfstX; + + // calc offset inside frame + if ( IsVertLR() ) + nOfstX = rRect.Left() - getFrameArea().Left(); + else + { + if ( mbIsSwapped ) + nOfstX = getFrameArea().Left() + getFrameArea().Height() - ( rRect.Left() + rRect.Width() ); + else + nOfstX = getFrameArea().Left() + getFrameArea().Width() - ( rRect.Left() + rRect.Width() ); + } + + long nOfstY; + if (IsVertLRBT()) + { + // Note that mbIsSwapped only affects the frame area, not rRect, so rRect.Height() is used + // here unconditionally. + if (mbIsSwapped) + nOfstY = getFrameArea().Top() + getFrameArea().Width() - (rRect.Top() + rRect.Height()); + else + nOfstY = getFrameArea().Top() + getFrameArea().Height() - (rRect.Top() + rRect.Height()); + } + else + nOfstY = rRect.Top() - getFrameArea().Top(); + const long nWidth = rRect.Height(); + const long nHeight = rRect.Width(); + + // calc rotated coords + rRect.Left( getFrameArea().Left() + nOfstY ); + rRect.Top( getFrameArea().Top() + nOfstX ); + rRect.Width( nWidth ); + rRect.Height( nHeight ); +} + +/** + * Calculates the coordinates of a point when switching from + * vertical to horizontal layout. + */ +void SwTextFrame::SwitchVerticalToHorizontal( Point& rPoint ) const +{ + long nOfstX; + + // calc offset inside frame + if ( IsVertLR() ) + // X offset is Y - left. + nOfstX = rPoint.X() - getFrameArea().Left(); + else + { + // X offset is right - X. + if ( mbIsSwapped ) + nOfstX = getFrameArea().Left() + getFrameArea().Height() - rPoint.X(); + else + nOfstX = getFrameArea().Left() + getFrameArea().Width() - rPoint.X(); + } + + long nOfstY; + if (IsVertLRBT()) + { + // Y offset is bottom - Y. + if (mbIsSwapped) + nOfstY = getFrameArea().Top() + getFrameArea().Width() - rPoint.Y(); + else + nOfstY = getFrameArea().Top() + getFrameArea().Height() - rPoint.Y(); + } + else + // Y offset is Y - top. + nOfstY = rPoint.Y() - getFrameArea().Top(); + + // calc rotated coords + rPoint.setX( getFrameArea().Left() + nOfstY ); + rPoint.setY( getFrameArea().Top() + nOfstX ); +} + +/** + * Calculates the a limit value when switching from + * vertical to horizontal layout. + */ +long SwTextFrame::SwitchVerticalToHorizontal( long nLimit ) const +{ + Point aTmp( nLimit, 0 ); + SwitchVerticalToHorizontal( aTmp ); + return aTmp.Y(); +} + +SwFrameSwapper::SwFrameSwapper( const SwTextFrame* pTextFrame, bool bSwapIfNotSwapped ) + : pFrame( pTextFrame ), bUndo( false ) +{ + if (pFrame->IsVertical() && bSwapIfNotSwapped != pFrame->IsSwapped()) + { + bUndo = true; + const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight(); + } +} + +SwFrameSwapper::~SwFrameSwapper() +{ + if ( bUndo ) + const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight(); +} + +void SwTextFrame::SwitchLTRtoRTL( SwRect& rRect ) const +{ + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + + long nWidth = rRect.Width(); + rRect.Left( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) + + getFramePrintArea().Width() - rRect.Right() - 1 ); + + rRect.Width( nWidth ); +} + +void SwTextFrame::SwitchLTRtoRTL( Point& rPoint ) const +{ + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + + rPoint.setX( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) + getFramePrintArea().Width() - rPoint.X() - 1 ); +} + +SwLayoutModeModifier::SwLayoutModeModifier( const OutputDevice& rOutp ) : + m_rOut( rOutp ), m_nOldLayoutMode( rOutp.GetLayoutMode() ) +{ +} + +SwLayoutModeModifier::~SwLayoutModeModifier() +{ + const_cast<OutputDevice&>(m_rOut).SetLayoutMode( m_nOldLayoutMode ); +} + +void SwLayoutModeModifier::Modify( bool bChgToRTL ) +{ + const_cast<OutputDevice&>(m_rOut).SetLayoutMode( bChgToRTL ? + ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl : + ComplexTextLayoutFlags::BiDiStrong ); +} + +void SwLayoutModeModifier::SetAuto() +{ + const ComplexTextLayoutFlags nNewLayoutMode = m_nOldLayoutMode & ~ComplexTextLayoutFlags::BiDiStrong; + const_cast<OutputDevice&>(m_rOut).SetLayoutMode( nNewLayoutMode ); +} + +SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang ) : + rOut( rOutp ), nOldLanguageType( rOutp.GetDigitLanguage() ) +{ + LanguageType eLang = eCurLang; + if (utl::ConfigManager::IsFuzzing()) + eLang = LANGUAGE_ENGLISH_US; + else + { + const SvtCTLOptions::TextNumerals nTextNumerals = SW_MOD()->GetCTLOptions().GetCTLTextNumerals(); + + if ( SvtCTLOptions::NUMERALS_HINDI == nTextNumerals ) + eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; + else if ( SvtCTLOptions::NUMERALS_ARABIC == nTextNumerals ) + eLang = LANGUAGE_ENGLISH; + else if ( SvtCTLOptions::NUMERALS_SYSTEM == nTextNumerals ) + eLang = ::GetAppLanguage(); + } + + const_cast<OutputDevice&>(rOut).SetDigitLanguage( eLang ); +} + +SwDigitModeModifier::~SwDigitModeModifier() +{ + const_cast<OutputDevice&>(rOut).SetDigitLanguage( nOldLanguageType ); +} + +void SwTextFrame::Init() +{ + OSL_ENSURE( !IsLocked(), "+SwTextFrame::Init: this is locked." ); + if( !IsLocked() ) + { + ClearPara(); + SetHasRotatedPortions(false); + // set flags directly to save a ResetPreps call, + // and thereby an unnecessary GetPara call + // don't set bOrphan, bLocked or bWait to false! + // bOrphan = bFlag7 = bFlag8 = false; + } +} + +SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib, + sw::FrameMode const eMode) + : SwContentFrame( pNode, pSib ) + , mnAllLines( 0 ) + , mnThisLines( 0 ) + , mnFlyAnchorOfst( 0 ) + , mnFlyAnchorOfstNoWrap( 0 ) + , mnFlyAnchorVertOfstNoWrap( 0 ) + , mnFootnoteLine( 0 ) + , mnHeightOfLastLine( 0 ) + , mnAdditionalFirstLineOffset( 0 ) + , mnOffset( 0 ) + , mnCacheIndex( USHRT_MAX ) + , mbLocked( false ) + , mbWidow( false ) + , mbJustWidow( false ) + , mbEmpty( false ) + , mbInFootnoteConnect( false ) + , mbFootnote( false ) + , mbRepaint( false ) + , mbHasRotatedPortions( false ) + , mbFieldFollow( false ) + , mbHasAnimation( false ) + , mbIsSwapped( false ) + , mbFollowFormatAllowed( true ) +{ + mnFrameType = SwFrameType::Txt; + // note: this may call SwClientNotify if it's in a list so do it last + // note: this may change this->pRegisteredIn to m_pMergedPara->listeners + m_pMergedPara = CheckParaRedlineMerge(*this, *pNode, eMode); +} + +namespace sw { + +SwTextFrame * MakeTextFrame(SwTextNode & rNode, SwFrame *const pSibling, + sw::FrameMode const eMode) +{ + return new SwTextFrame(&rNode, pSibling, eMode); +} + +void RemoveFootnotesForNode( + SwRootFrame const& rLayout, SwTextNode const& rTextNode, + std::vector<std::pair<sal_Int32, sal_Int32>> const*const pExtents) +{ + if (pExtents && pExtents->empty()) + { + return; // nothing to do + } + const SwFootnoteIdxs &rFootnoteIdxs = rTextNode.GetDoc()->GetFootnoteIdxs(); + size_t nPos = 0; + sal_uLong const nIndex = rTextNode.GetIndex(); + rFootnoteIdxs.SeekEntry( rTextNode, &nPos ); + if (nPos < rFootnoteIdxs.size()) + { + while (nPos && &rTextNode == &(rFootnoteIdxs[ nPos ]->GetTextNode())) + --nPos; + if (nPos || &rTextNode != &(rFootnoteIdxs[ nPos ]->GetTextNode())) + ++nPos; + } + size_t iter(0); + for ( ; nPos < rFootnoteIdxs.size(); ++nPos) + { + SwTextFootnote* pTextFootnote = rFootnoteIdxs[ nPos ]; + if (pTextFootnote->GetTextNode().GetIndex() > nIndex) + break; + if (pExtents) + { + while ((*pExtents)[iter].second <= pTextFootnote->GetStart()) + { + ++iter; + if (iter == pExtents->size()) + { + return; + } + } + if (pTextFootnote->GetStart() < (*pExtents)[iter].first) + { + continue; + } + } + pTextFootnote->DelFrames(&rLayout); + } +} + +} // namespace sw + +void SwTextFrame::DestroyImpl() +{ + // Remove associated SwParaPortion from s_pTextCache + ClearPara(); + + assert(!GetDoc().IsInDtor()); // this shouldn't be happening with ViewShell owning layout + if (!GetDoc().IsInDtor() && HasFootnote()) + { + if (m_pMergedPara) + { + SwTextNode const* pNode(nullptr); + for (auto const& e : m_pMergedPara->extents) + { + if (e.pNode != pNode) + { + pNode = e.pNode; + // sw_redlinehide: not sure if it's necessary to check + // if the nodes are still alive here, which would require + // accessing WriterMultiListener::m_vDepends + sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr); + } + } + } + else + { + SwTextNode *const pNode(static_cast<SwTextNode*>(GetDep())); + if (pNode) + { + sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr); + } + } + } + + SwContentFrame::DestroyImpl(); +} + +SwTextFrame::~SwTextFrame() +{ + RemoveFromCache(); +} + +namespace sw { + +// 1. if real insert => correct nStart/nEnd for full nLen +// 2. if rl un-delete => do not correct nStart/nEnd but just include un-deleted +static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged, + bool const isRealInsert, + SwTextNode const& rNode, sal_Int32 const nIndex, sal_Int32 const nLen) +{ + assert(!isRealInsert || nLen); // can 0 happen? yes, for redline in empty node + assert(nIndex <= rNode.Len()); + assert(nIndex + nLen <= rNode.Len()); + assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex()); + if (!nLen) + { + return TextFrameIndex(0); + } + OUStringBuffer text(rMerged.mergedText); + sal_Int32 nTFIndex(0); // index used for insertion at the end + sal_Int32 nInserted(0); + bool bInserted(false); + bool bFoundNode(false); + auto itInsert(rMerged.extents.end()); + for (auto it = rMerged.extents.begin(); it != rMerged.extents.end(); ++it) + { + if (it->pNode == &rNode) + { + if (isRealInsert) + { + bFoundNode = true; + if (it->nStart <= nIndex && nIndex <= it->nEnd) + { // note: this can happen only once + text.insert(nTFIndex + (nIndex - it->nStart), + rNode.GetText().copy(nIndex, nLen)); + it->nEnd += nLen; + nInserted = nLen; + assert(!bInserted); + bInserted = true; + } + else if (nIndex < it->nStart) + { + if (itInsert == rMerged.extents.end()) + { + itInsert = it; + } + it->nStart += nLen; + it->nEnd += nLen; + } + } + else + { + assert(it == rMerged.extents.begin() || (it-1)->pNode != &rNode || (it-1)->nEnd < nIndex); + if (nIndex + nLen < it->nStart) + { + itInsert = it; + break; + } + if (nIndex < it->nStart) + { + text.insert(nTFIndex, + rNode.GetText().copy(nIndex, it->nStart - nIndex)); + nInserted += it->nStart - nIndex; + it->nStart = nIndex; + bInserted = true; + } + assert(it->nStart <= nIndex); + if (nIndex <= it->nEnd) + { + nTFIndex += it->nEnd - it->nStart; + while (it->nEnd < nIndex + nLen) + { + auto *const pNext( + (it+1) != rMerged.extents.end() && (it+1)->pNode == it->pNode + ? &*(it+1) + : nullptr); + if (pNext && pNext->nStart <= nIndex + nLen) + { + text.insert(nTFIndex, + rNode.GetText().copy(it->nEnd, pNext->nStart - it->nEnd)); + nTFIndex += pNext->nStart - it->nEnd; + nInserted += pNext->nStart - it->nEnd; + pNext->nStart = it->nStart; + it = rMerged.extents.erase(it); + } + else + { + text.insert(nTFIndex, + rNode.GetText().copy(it->nEnd, nIndex + nLen - it->nEnd)); + nTFIndex += nIndex + nLen - it->nEnd; + nInserted += nIndex + nLen - it->nEnd; + it->nEnd = nIndex + nLen; + } + } + bInserted = true; + break; + } + } + } + else if (rNode.GetIndex() < it->pNode->GetIndex() || bFoundNode) + { + if (itInsert == rMerged.extents.end()) + { + itInsert = it; + } + break; + } + if (itInsert == rMerged.extents.end()) + { + nTFIndex += it->nEnd - it->nStart; + } + } +// assert((bFoundNode || rMerged.extents.empty()) && "text node not found - why is it sending hints to us"); + if (!bInserted) + { // must be in a gap + rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen); + text.insert(nTFIndex, rNode.GetText().copy(nIndex, nLen)); + nInserted = nLen; + if (rMerged.extents.size() == 1 // also if it was empty! + || rMerged.pParaPropsNode->GetIndex() < rNode.GetIndex()) + { // text inserted after current para-props node + rMerged.pParaPropsNode->RemoveFromListRLHidden(); + rMerged.pParaPropsNode = &const_cast<SwTextNode&>(rNode); + rMerged.pParaPropsNode->AddToListRLHidden(); + } + // called from SwRangeRedline::InvalidateRange() + if (rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + const_cast<SwTextNode&>(rNode).SetRedlineMergeFlag(SwNode::Merge::NonFirst); + } + } + rMerged.mergedText = text.makeStringAndClear(); + return TextFrameIndex(nInserted); +} + +// 1. if real delete => correct nStart/nEnd for full nLen +// 2. if rl delete => do not correct nStart/nEnd but just exclude deleted +TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, + bool const isRealDelete, + SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 const nLen) +{ + assert(nIndex <= rNode.Len()); + assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex()); + OUStringBuffer text(rMerged.mergedText); + sal_Int32 nTFIndex(0); + sal_Int32 nToDelete(nLen); + sal_Int32 nDeleted(0); + size_t nFoundNode(0); + size_t nErased(0); + auto it = rMerged.extents.begin(); + for (; it != rMerged.extents.end(); ) + { + bool bErase(false); + if (it->pNode == &rNode) + { + ++nFoundNode; + if (nIndex + nToDelete < it->nStart) + { + nToDelete = 0; + if (!isRealDelete) + { + break; + } + it->nStart -= nLen; + it->nEnd -= nLen; + } + else + { + if (nIndex < it->nStart) + { + // do not adjust nIndex into the text frame index space! + nToDelete -= it->nStart - nIndex; + nIndex = it->nStart; + // note: continue with the if check below, no else! + } + if (it->nStart <= nIndex && nIndex < it->nEnd) + { + sal_Int32 const nDeleteHere(nIndex + nToDelete <= it->nEnd + ? nToDelete + : it->nEnd - nIndex); + text.remove(nTFIndex + (nIndex - it->nStart), nDeleteHere); + bErase = nDeleteHere == it->nEnd - it->nStart; + if (bErase) + { + ++nErased; + assert(it->nStart == nIndex); + it = rMerged.extents.erase(it); + } + else if (isRealDelete) + { // adjust for deleted text + it->nStart -= (nLen - nToDelete); + it->nEnd -= (nLen - nToDelete + nDeleteHere); + if (it != rMerged.extents.begin() + && (it-1)->pNode == &rNode + && (it-1)->nEnd == it->nStart) + { // merge adjacent extents + nTFIndex += it->nEnd - it->nStart; + (it-1)->nEnd = it->nEnd; + it = rMerged.extents.erase(it); + bErase = true; // skip increment + } + } + else + { // exclude text marked as deleted + if (nIndex + nDeleteHere == it->nEnd) + { + it->nEnd -= nDeleteHere; + } + else + { + if (nIndex == it->nStart) + { + it->nStart += nDeleteHere; + } + else + { + sal_Int32 const nOldEnd(it->nEnd); + it->nEnd = nIndex; + it = rMerged.extents.emplace(it+1, + it->pNode, nIndex + nDeleteHere, nOldEnd); + } + assert(nDeleteHere == nToDelete); + } + } + nDeleted += nDeleteHere; + nToDelete -= nDeleteHere; + nIndex += nDeleteHere; + if (!isRealDelete && nToDelete == 0) + { + break; + } + } + } + } + else if (nFoundNode != 0) + { + break; + } + if (!bErase) + { + nTFIndex += it->nEnd - it->nStart; + ++it; + } + } +// assert(nFoundNode != 0 && "text node not found - why is it sending hints to us"); + assert(nIndex <= rNode.Len() + nLen); + // if there's a remaining deletion, it must be in gap at the end of the node +// can't do: might be last one in node was erased assert(nLen == 0 || rMerged.empty() || (it-1)->nEnd <= nIndex); + // note: if first node gets deleted then that must call DelFrames as + // pFirstNode is never updated + if (nErased && nErased == nFoundNode) + { // all visible text from node was erased +#if 1 + if (rMerged.pParaPropsNode == &rNode) + { + rMerged.pParaPropsNode->RemoveFromListRLHidden(); + rMerged.pParaPropsNode = rMerged.extents.empty() + ? const_cast<SwTextNode*>(rMerged.pLastNode) + : rMerged.extents.front().pNode; + rMerged.pParaPropsNode->AddToListRLHidden(); + } +#endif +// NOPE must listen on all non-hidden nodes; particularly on pLastNode rMerged.listener.EndListening(&const_cast<SwTextNode&>(rNode)); + } + rMerged.mergedText = text.makeStringAndClear(); + return TextFrameIndex(nDeleted); +} + +std::pair<SwTextNode*, sal_Int32> +MapViewToModel(MergedPara const& rMerged, TextFrameIndex const i_nIndex) +{ + sal_Int32 nIndex(i_nIndex); + sw::Extent const* pExtent(nullptr); + for (const auto& rExt : rMerged.extents) + { + pExtent = &rExt; + if (nIndex < (pExtent->nEnd - pExtent->nStart)) + { + return std::make_pair(pExtent->pNode, pExtent->nStart + nIndex); + } + nIndex = nIndex - (pExtent->nEnd - pExtent->nStart); + } + assert(nIndex == 0 && "view index out of bounds"); + return pExtent + ? std::make_pair(pExtent->pNode, pExtent->nEnd) //1-past-the-end index + : std::make_pair(const_cast<SwTextNode*>(rMerged.pLastNode), rMerged.pLastNode->Len()); +} + +TextFrameIndex MapModelToView(MergedPara const& rMerged, SwTextNode const*const pNode, sal_Int32 const nIndex) +{ + assert(rMerged.pFirstNode->GetIndex() <= pNode->GetIndex() + && pNode->GetIndex() <= rMerged.pLastNode->GetIndex()); + sal_Int32 nRet(0); + bool bFoundNode(false); + for (auto const& e : rMerged.extents) + { + if (pNode->GetIndex() < e.pNode->GetIndex()) + { + return TextFrameIndex(nRet); + } + if (e.pNode == pNode) + { + if (e.nStart <= nIndex && nIndex <= e.nEnd) + { + return TextFrameIndex(nRet + (nIndex - e.nStart)); + } + else if (nIndex < e.nStart) + { + // in gap before this extent => map to 0 here TODO??? + return TextFrameIndex(nRet); + } + bFoundNode = true; + } + else if (bFoundNode) + { + break; + } + nRet += e.nEnd - e.nStart; + } + if (bFoundNode) + { + // must be in a gap at the end of the node + assert(nIndex <= pNode->Len()); + return TextFrameIndex(nRet); + } + else if (rMerged.extents.empty()) + { + assert(nIndex <= pNode->Len()); + return TextFrameIndex(0); + } + return TextFrameIndex(rMerged.mergedText.getLength()); +} + +} // namespace sw + +std::pair<SwTextNode*, sal_Int32> +SwTextFrame::MapViewToModel(TextFrameIndex const nIndex) const +{ +//nope assert(GetPara()); + sw::MergedPara const*const pMerged(GetMergedPara()); + if (pMerged) + { + return sw::MapViewToModel(*pMerged, nIndex); + } + else + { + return std::make_pair(static_cast<SwTextNode*>(const_cast<SwModify*>( + SwFrame::GetDep())), sal_Int32(nIndex)); + } +} + +SwPosition SwTextFrame::MapViewToModelPos(TextFrameIndex const nIndex) const +{ + std::pair<SwTextNode*, sal_Int32> const ret(MapViewToModel(nIndex)); + return SwPosition(*ret.first, ret.second); +} + +TextFrameIndex SwTextFrame::MapModelToView(SwTextNode const*const pNode, sal_Int32 const nIndex) const +{ +//nope assert(GetPara()); + sw::MergedPara const*const pMerged(GetMergedPara()); + if (pMerged) + { + return sw::MapModelToView(*pMerged, pNode, nIndex); + } + else + { + assert(static_cast<SwTextNode*>(const_cast<SwModify*>(SwFrame::GetDep())) == pNode); + return TextFrameIndex(nIndex); + } +} + +TextFrameIndex SwTextFrame::MapModelToViewPos(SwPosition const& rPos) const +{ + SwTextNode const*const pNode(rPos.nNode.GetNode().GetTextNode()); + sal_Int32 const nIndex(rPos.nContent.GetIndex()); + return MapModelToView(pNode, nIndex); +} + +void SwTextFrame::SetMergedPara(std::unique_ptr<sw::MergedPara> p) +{ + SwTextNode *const pFirst(m_pMergedPara ? m_pMergedPara->pFirstNode : nullptr); + m_pMergedPara = std::move(p); + if (pFirst) + { + if (m_pMergedPara) + { + assert(pFirst == m_pMergedPara->pFirstNode); + } + else + { + pFirst->Add(this); // must register at node again + } + } +} + +const OUString& SwTextFrame::GetText() const +{ +//nope assert(GetPara()); + sw::MergedPara const*const pMerged(GetMergedPara()); + if (pMerged) + return pMerged->mergedText; + else + return static_cast<SwTextNode const*>(SwFrame::GetDep())->GetText(); +} + +SwTextNode const* SwTextFrame::GetTextNodeForParaProps() const +{ + // FIXME can GetPara be 0 ? yes... this is needed in SwContentNotify::SwContentNotify() which is called before any formatting is started +//nope assert(GetPara()); + sw::MergedPara const*const pMerged(GetMergedPara()); + if (pMerged) + { +// assert(pMerged->pFirstNode == pMerged->pParaPropsNode); // surprising news! + return pMerged->pParaPropsNode; + } + else + return static_cast<SwTextNode const*>(SwFrame::GetDep()); +} + +SwTextNode const* SwTextFrame::GetTextNodeForFirstText() const +{ + sw::MergedPara const*const pMerged(GetMergedPara()); + if (pMerged) + return pMerged->extents.empty() + ? pMerged->pFirstNode + : pMerged->extents.front().pNode; + else + return static_cast<SwTextNode const*>(SwFrame::GetDep()); +} + +SwTextNode const* SwTextFrame::GetTextNodeFirst() const +{ +//nope assert(GetPara()); + sw::MergedPara const*const pMerged(GetMergedPara()); + if (pMerged) + return pMerged->pFirstNode; + else + return static_cast<SwTextNode const*>(SwFrame::GetDep()); +} + +SwDoc const& SwTextFrame::GetDoc() const +{ + return *GetTextNodeFirst()->GetDoc(); +} + +LanguageType SwTextFrame::GetLangOfChar(TextFrameIndex const nIndex, + sal_uInt16 const nScript, bool const bNoChar) const +{ + // a single character can be mapped uniquely! + std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(nIndex)); + return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, nScript); +} + +void SwTextFrame::ResetPreps() +{ + if ( GetCacheIdx() != USHRT_MAX ) + { + if (SwParaPortion *pPara = GetPara()) + pPara->ResetPreps(); + } +} + +bool SwTextFrame::IsHiddenNow() const +{ + SwFrameSwapper aSwapper( this, true ); + + if( !getFrameArea().Width() && isFrameAreaDefinitionValid() && GetUpper()->isFrameAreaDefinitionValid() ) // invalid when stack overflows (StackHack)! + { +// OSL_FAIL( "SwTextFrame::IsHiddenNow: thin frame" ); + return true; + } + + bool bHiddenCharsHidePara(false); + bool bHiddenParaField(false); + if (m_pMergedPara) + { + TextFrameIndex nHiddenStart(COMPLETE_STRING); + TextFrameIndex nHiddenEnd(0); + if (auto const pScriptInfo = GetScriptInfo()) + { + pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0), + nHiddenStart, nHiddenEnd); + } + else // ParaPortion is created in Format, but this is called earlier + { + SwScriptInfo aInfo; + aInfo.InitScriptInfo(*m_pMergedPara->pFirstNode, m_pMergedPara.get(), IsRightToLeft()); + aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0), + nHiddenStart, nHiddenEnd); + } + if (TextFrameIndex(0) == nHiddenStart && + TextFrameIndex(GetText().getLength()) <= nHiddenEnd) + { + bHiddenCharsHidePara = true; + } + sw::MergedAttrIter iter(*this); + SwTextNode const* pNode(nullptr); + int nNewResultWeight = 0; + for (SwTextAttr const* pHint = iter.NextAttr(&pNode); pHint; pHint = iter.NextAttr(&pNode)) + { + if (pHint->Which() == RES_TXTATR_FIELD) + { + // see also SwpHints::CalcHiddenParaField() + const SwFormatField& rField = pHint->GetFormatField(); + int nCurWeight = pNode->GetDoc()->FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which()); + if (nCurWeight > nNewResultWeight) + { + nNewResultWeight = nCurWeight; + bHiddenParaField = pNode->GetDoc()->FieldHidesPara(*rField.GetField()); + } + else if (nCurWeight == nNewResultWeight && bHiddenParaField) + { + // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide" + // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only + // care about fields of higher weight. + bHiddenParaField = pNode->GetDoc()->FieldHidesPara(*rField.GetField()); + } + } + } + } + else + { + bHiddenCharsHidePara = static_cast<SwTextNode const*>(SwFrame::GetDep())->HasHiddenCharAttribute( true ); + bHiddenParaField = static_cast<SwTextNode const*>(SwFrame::GetDep())->IsHiddenByParaField(); + } + const SwViewShell* pVsh = getRootFrame()->GetCurrShell(); + + if ( pVsh && ( bHiddenCharsHidePara || bHiddenParaField ) ) + { + + if ( + ( bHiddenParaField && + ( !pVsh->GetViewOptions()->IsShowHiddenPara() && + !pVsh->GetViewOptions()->IsFieldName() ) ) || + ( bHiddenCharsHidePara && + !pVsh->GetViewOptions()->IsShowHiddenChar() ) ) + { + return true; + } + } + + return false; +} + +/// Removes Textfrm's attachments, when it's hidden +void SwTextFrame::HideHidden() +{ + OSL_ENSURE( !GetFollow() && IsHiddenNow(), + "HideHidden on visible frame of hidden frame has follow" ); + + HideFootnotes(GetOffset(), TextFrameIndex(COMPLETE_STRING)); + HideAndShowObjects(); + + // format information is obsolete + ClearPara(); +} + +void SwTextFrame::HideFootnotes(TextFrameIndex const nStart, TextFrameIndex const nEnd) +{ + SwPageFrame *pPage = nullptr; + sw::MergedAttrIter iter(*this); + SwTextNode const* pNode(nullptr); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if (pHt->Which() == RES_TXTATR_FTN) + { + TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart())); + if (nEnd < nIdx) + break; + if (nStart <= nIdx) + { + if (!pPage) + pPage = FindPageFrame(); + pPage->RemoveFootnote( this, static_cast<const SwTextFootnote*>(pHt) ); + } + } + } +} + +/** + * as-character anchored graphics, which are used for a graphic bullet list. + * As long as these graphic bullet list aren't imported, do not hide a + * at-character anchored object, if + * (a) the document is an imported WW8 document - + * checked by checking certain compatibility options - + * (b) the paragraph is the last content in the document and + * (c) the anchor character is an as-character anchored graphic. + */ +bool sw_HideObj( const SwTextFrame& _rFrame, + const RndStdIds _eAnchorType, + SwPosition const& rAnchorPos, + SwAnchoredObject* _pAnchoredObj ) +{ + bool bRet( true ); + + if (_eAnchorType == RndStdIds::FLY_AT_CHAR) + { + const IDocumentSettingAccess *const pIDSA = &_rFrame.GetDoc().getIDocumentSettingAccess(); + if ( !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) && + !pIDSA->get(DocumentSettingId::OLD_LINE_SPACING) && + !pIDSA->get(DocumentSettingId::USE_FORMER_OBJECT_POS) && + pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) && + _rFrame.IsInDocBody() && !_rFrame.FindNextCnt() ) + { + SwTextNode const& rNode(*rAnchorPos.nNode.GetNode().GetTextNode()); + assert(FrameContainsNode(_rFrame, rNode.GetIndex())); + sal_Int32 const nObjAnchorPos(rAnchorPos.nContent.GetIndex()); + const sal_Unicode cAnchorChar = nObjAnchorPos < rNode.Len() + ? rNode.GetText()[nObjAnchorPos] + : 0; + if (cAnchorChar == CH_TXTATR_BREAKWORD) + { + const SwTextAttr* const pHint( + rNode.GetTextAttrForCharAt(nObjAnchorPos, RES_TXTATR_FLYCNT)); + if ( pHint ) + { + const SwFrameFormat* pFrameFormat = + static_cast<const SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat(); + if ( pFrameFormat->Which() == RES_FLYFRMFMT ) + { + SwNodeIndex nContentIndex = *(pFrameFormat->GetContent().GetContentIdx()); + ++nContentIndex; + if ( nContentIndex.GetNode().IsNoTextNode() ) + { + bRet = false; + // set needed data structure values for object positioning + SwRectFnSet aRectFnSet(&_rFrame); + SwRect aLastCharRect( _rFrame.getFrameArea() ); + aRectFnSet.SetWidth( aLastCharRect, 1 ); + _pAnchoredObj->maLastCharRect = aLastCharRect; + _pAnchoredObj->mnLastTopOfLine = aRectFnSet.GetTop(aLastCharRect); + } + } + } + } + } + } + + return bRet; +} + +/** + * Hide/show objects + * + * Method hides respectively shows objects, which are anchored at paragraph, + * at/as a character of the paragraph, corresponding to the paragraph and + * paragraph portion visibility. + * + * - is called from HideHidden() - should hide objects in hidden paragraphs and + * - from Format_() - should hide/show objects in partly visible paragraphs + */ +void SwTextFrame::HideAndShowObjects() +{ + if ( GetDrawObjs() ) + { + if ( IsHiddenNow() ) + { + // complete paragraph is hidden. Thus, hide all objects + for (SwAnchoredObject* i : *GetDrawObjs()) + { + SdrObject* pObj = i->DrawObj(); + SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall()); + // under certain conditions + const RndStdIds eAnchorType( pContact->GetAnchorId() ); + if ((eAnchorType != RndStdIds::FLY_AT_CHAR) || + sw_HideObj(*this, eAnchorType, pContact->GetContentAnchor(), + i )) + { + pContact->MoveObjToInvisibleLayer( pObj ); + } + } + } + else + { + // paragraph is visible, but can contain hidden text portion. + // first we check if objects are allowed to be hidden: + const SwViewShell* pVsh = getRootFrame()->GetCurrShell(); + const bool bShouldBeHidden = !pVsh || !pVsh->GetWin() || + !pVsh->GetViewOptions()->IsShowHiddenChar(); + + // Thus, show all objects, which are anchored at paragraph and + // hide/show objects, which are anchored at/as character, according + // to the visibility of the anchor character. + for (SwAnchoredObject* i : *GetDrawObjs()) + { + SdrObject* pObj = i->DrawObj(); + SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall()); + // Determine anchor type only once + const RndStdIds eAnchorType( pContact->GetAnchorId() ); + + if (eAnchorType == RndStdIds::FLY_AT_PARA) + { + pContact->MoveObjToVisibleLayer( pObj ); + } + else if ((eAnchorType == RndStdIds::FLY_AT_CHAR) || + (eAnchorType == RndStdIds::FLY_AS_CHAR)) + { + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + const SwPosition& rAnchor = pContact->GetContentAnchor(); + SwScriptInfo::GetBoundsOfHiddenRange( + *rAnchor.nNode.GetNode().GetTextNode(), + rAnchor.nContent.GetIndex(), nHiddenStart, nHiddenEnd); + // Under certain conditions + if ( nHiddenStart != COMPLETE_STRING && bShouldBeHidden && + sw_HideObj(*this, eAnchorType, rAnchor, i)) + { + pContact->MoveObjToInvisibleLayer( pObj ); + } + else + pContact->MoveObjToVisibleLayer( pObj ); + } + else + { + OSL_FAIL( "<SwTextFrame::HideAndShowObjects()> - object not anchored at/inside paragraph!?" ); + } + } + } + } + + if (IsFollow()) + { + SwTextFrame *pMaster = FindMaster(); + OSL_ENSURE(pMaster, "SwTextFrame without master"); + if (pMaster) + pMaster->HideAndShowObjects(); + } +} + +/** + * Returns the first possible break point in the current line. + * This method is used in SwTextFrame::Format() to decide whether the previous + * line has to be formatted as well. + * nFound is <= nEndLine. + */ +TextFrameIndex SwTextFrame::FindBrk(const OUString &rText, + const TextFrameIndex nStart, + const TextFrameIndex nEnd) +{ + sal_Int32 nFound = sal_Int32(nStart); + const sal_Int32 nEndLine = std::min(sal_Int32(nEnd), rText.getLength() - 1); + + // Skip all leading blanks. + while( nFound <= nEndLine && ' ' == rText[nFound] ) + { + nFound++; + } + + // A tricky situation with the TextAttr-Dummy-character (in this case "$"): + // "Dr.$Meyer" at the beginning of the second line. Typing a blank after that + // doesn't result in the word moving into first line, even though that would work. + // For this reason we don't skip the dummy char. + while( nFound <= nEndLine && ' ' != rText[nFound] ) + { + nFound++; + } + + return TextFrameIndex(nFound); +} + +bool SwTextFrame::IsIdxInside(TextFrameIndex const nPos, TextFrameIndex const nLen) const +{ +// Silence over-eager warning emitted at least by GCC trunk towards 6: +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-overflow" +#endif + if (nLen != TextFrameIndex(COMPLETE_STRING) && GetOffset() > nPos + nLen) // the range preceded us +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic pop +#endif + return false; + + if( !GetFollow() ) // the range doesn't precede us, + return true; // nobody follows us. + + TextFrameIndex const nMax = GetFollow()->GetOffset(); + + // either the range overlap or our text has been deleted + // sw_redlinehide: GetText() should be okay here because it has already + // been updated in the INS/DEL hint case + if (nMax > nPos || nMax > TextFrameIndex(GetText().getLength())) + return true; + + // changes made in the first line of a follow can modify the master + const SwParaPortion* pPara = GetFollow()->GetPara(); + return pPara && ( nPos <= nMax + pPara->GetLen() ); +} + +inline void SwTextFrame::InvalidateRange(const SwCharRange &aRange, const long nD) +{ + if ( IsIdxInside( aRange.Start(), aRange.Len() ) ) + InvalidateRange_( aRange, nD ); +} + +void SwTextFrame::InvalidateRange_( const SwCharRange &aRange, const long nD) +{ + if ( !HasPara() ) + { InvalidateSize(); + return; + } + + SetWidow( false ); + SwParaPortion *pPara = GetPara(); + + bool bInv = false; + if( 0 != nD ) + { + // In nDelta the differences between old and new + // linelengths are being added, that's why it's negative + // if chars have been added and positive, if chars have + // deleted + pPara->GetDelta() += nD; + bInv = true; + } + SwCharRange &rReformat = pPara->GetReformat(); + if(aRange != rReformat) { + if (TextFrameIndex(COMPLETE_STRING) == rReformat.Len()) + rReformat = aRange; + else + rReformat += aRange; + bInv = true; + } + if(bInv) + { + InvalidateSize(); + } +} + +void SwTextFrame::CalcLineSpace() +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), + "SwTextFrame::CalcLineSpace with swapped frame!" ); + + if( IsLocked() || !HasPara() ) + return; + + if( GetDrawObjs() || + GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace().IsAutoFirst()) + { + Init(); + return; + } + + SwParaPortion *const pPara(GetPara()); + assert(pPara); + if (pPara->IsFixLineHeight()) + { + Init(); + return; + } + + Size aNewSize( getFramePrintArea().SSize() ); + + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); + SwTextFormatter aLine( this, &aInf ); + if( aLine.GetDropLines() ) + { + Init(); + return; + } + + aLine.Top(); + aLine.RecalcRealHeight(); + + aNewSize.setHeight( (aLine.Y() - getFrameArea().Top()) + aLine.GetLineHeight() ); + + SwTwips nDelta = aNewSize.Height() - getFramePrintArea().Height(); + // Underflow with free-flying frames + if( aInf.GetTextFly().IsOn() ) + { + SwRect aTmpFrame( getFrameArea() ); + if( nDelta < 0 ) + aTmpFrame.Height( getFramePrintArea().Height() ); + else + aTmpFrame.Height( aNewSize.Height() ); + if( aInf.GetTextFly().Relax( aTmpFrame ) ) + { + Init(); + return; + } + } + + if( nDelta ) + { + SwTextFrameBreak aBreak( this ); + if( GetFollow() || aBreak.IsBreakNow( aLine ) ) + { + // if there is a Follow() or if we need to break here, reformat + Init(); + } + else + { + // everything is business as usual... + pPara->SetPrepAdjust(); + pPara->SetPrep(); + } + } +} + +static void lcl_SetWrong( SwTextFrame& rFrame, SwTextNode const& rNode, + sal_Int32 const nPos, sal_Int32 const nCnt, bool const bMove) +{ + if ( !rFrame.IsFollow() ) + { + SwTextNode* pTextNode = const_cast<SwTextNode*>(&rNode); + IGrammarContact* pGrammarContact = getGrammarContact( *pTextNode ); + SwGrammarMarkUp* pWrongGrammar = pGrammarContact ? + pGrammarContact->getGrammarCheck( *pTextNode, false ) : + pTextNode->GetGrammarCheck(); + bool bGrammarProxy = pWrongGrammar != pTextNode->GetGrammarCheck(); + if( bMove ) + { + if( pTextNode->GetWrong() ) + pTextNode->GetWrong()->Move( nPos, nCnt ); + if( pWrongGrammar ) + pWrongGrammar->MoveGrammar( nPos, nCnt ); + if( bGrammarProxy && pTextNode->GetGrammarCheck() ) + pTextNode->GetGrammarCheck()->MoveGrammar( nPos, nCnt ); + if( pTextNode->GetSmartTags() ) + pTextNode->GetSmartTags()->Move( nPos, nCnt ); + } + else + { + if( pTextNode->GetWrong() ) + pTextNode->GetWrong()->Invalidate( nPos, nCnt ); + if( pWrongGrammar ) + pWrongGrammar->Invalidate( nPos, nCnt ); + if( pTextNode->GetSmartTags() ) + pTextNode->GetSmartTags()->Invalidate( nPos, nCnt ); + } + const sal_Int32 nEnd = nPos + (nCnt > 0 ? nCnt : 1 ); + if ( !pTextNode->GetWrong() && !pTextNode->IsWrongDirty() ) + { + pTextNode->SetWrong( new SwWrongList( WRONGLIST_SPELL ) ); + pTextNode->GetWrong()->SetInvalid( nPos, nEnd ); + } + if ( !pTextNode->GetSmartTags() && !pTextNode->IsSmartTagDirty() ) + { + pTextNode->SetSmartTags( new SwWrongList( WRONGLIST_SMARTTAG ) ); + pTextNode->GetSmartTags()->SetInvalid( nPos, nEnd ); + } + pTextNode->SetWrongDirty(SwTextNode::WrongState::TODO); + pTextNode->SetGrammarCheckDirty( true ); + pTextNode->SetWordCountDirty( true ); + pTextNode->SetAutoCompleteWordDirty( true ); + pTextNode->SetSmartTagDirty( true ); + } + + SwRootFrame *pRootFrame = rFrame.getRootFrame(); + if (pRootFrame) + { + pRootFrame->SetNeedGrammarCheck( true ); + } + + SwPageFrame *pPage = rFrame.FindPageFrame(); + if( pPage ) + { + pPage->InvalidateSpelling(); + pPage->InvalidateAutoCompleteWords(); + pPage->InvalidateWordCount(); + pPage->InvalidateSmartTags(); + } +} + +static void lcl_SetScriptInval(SwTextFrame& rFrame, TextFrameIndex const nPos) +{ + if( rFrame.GetPara() ) + rFrame.GetPara()->GetScriptInfo().SetInvalidityA( nPos ); +} + +// note: SwClientNotify will be called once for every frame => just fix own Ofst +static void lcl_ModifyOfst(SwTextFrame & rFrame, + TextFrameIndex const nPos, TextFrameIndex const nLen, + TextFrameIndex (* op)(TextFrameIndex const&, TextFrameIndex const&)) +{ + assert(nLen != TextFrameIndex(COMPLETE_STRING)); + if (rFrame.IsFollow() && nPos < rFrame.GetOffset()) + { + rFrame.ManipOfst( std::max(nPos, op(rFrame.GetOffset(), nLen)) ); + assert(sal_Int32(rFrame.GetOffset()) <= rFrame.GetText().getLength()); + } +} + +namespace { + +void UpdateMergedParaForMove(sw::MergedPara & rMerged, + SwTextFrame & rTextFrame, + bool & o_rbRecalcFootnoteFlag, + SwTextNode const& rDestNode, + SwTextNode const& rNode, + sal_Int32 const nDestStart, + sal_Int32 const nSourceStart, + sal_Int32 const nLen) +{ + std::vector<std::pair<sal_Int32, sal_Int32>> deleted; + sal_Int32 const nSourceEnd(nSourceStart + nLen); + sal_Int32 nLastEnd(0); + for (const auto& rExt : rMerged.extents) + { + if (rExt.pNode == &rNode) + { + sal_Int32 const nStart(std::max(nLastEnd, nSourceStart)); + sal_Int32 const nEnd(std::min(rExt.nStart, nSourceEnd)); + if (nStart < nEnd) + { + deleted.emplace_back(nStart, nEnd); + } + nLastEnd = rExt.nEnd; + if (nSourceEnd <= rExt.nEnd) + { + break; + } + } + else if (rNode.GetIndex() < rExt.pNode->GetIndex()) + { + break; + } + } + if (nLastEnd != rNode.Len()) // without nLen, string yet to be removed + { + if (nLastEnd < nSourceEnd) + { + deleted.emplace_back(std::max(nLastEnd, nSourceStart), nSourceEnd); + } + } + if (!deleted.empty()) + { + o_rbRecalcFootnoteFlag = true; + for (auto const& it : deleted) + { + sal_Int32 const nStart(it.first - nSourceStart + nDestStart); + TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, false, + rDestNode, nStart, it.second - it.first); +//FIXME asserts valid for join - but if called from split, the new node isn't there yet and it will be added later... assert(nDeleted); +// assert(nDeleted == it.second - it.first); + if(nDeleted) + { + // InvalidateRange/lcl_SetScriptInval was called sufficiently for SwInsText + lcl_SetWrong(rTextFrame, rDestNode, nStart, it.first - it.second, false); + TextFrameIndex const nIndex(sw::MapModelToView(rMerged, &rDestNode, nStart)); + lcl_ModifyOfst(rTextFrame, nIndex, nDeleted, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); + } + } + } +} + +} // namespace + +/** + * Related: fdo#56031 filter out attribute changes that don't matter for + * humans/a11y to stop flooding the destination mortal with useless noise + */ +static bool isA11yRelevantAttribute(sal_uInt16 nWhich) +{ + return nWhich != RES_CHRATR_RSID; +} + +static bool hasA11yRelevantAttribute( const std::vector<sal_uInt16>& rWhichFmtAttr ) +{ + for( sal_uInt16 nWhich : rWhichFmtAttr ) + if ( isA11yRelevantAttribute( nWhich ) ) + return true; + + return false; +} + +// Note: for now this overrides SwClient::SwClientNotify; the intermediary +// classes still override SwClient::Modify, which should continue to work +// as their implementation of SwClientNotify is SwClient's which calls Modify. +// Therefore we also don't need to call SwClient::SwClientNotify(rModify, rHint) +// because that's all it does, and this implementation calls +// SwContentFrame::Modify() when appropriate. +void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) +{ + SfxPoolItem const* pOld(nullptr); + SfxPoolItem const* pNew(nullptr); + sw::MoveText const* pMoveText(nullptr); + sw::RedlineDelText const* pRedlineDelText(nullptr); + sw::RedlineUnDelText const* pRedlineUnDelText(nullptr); + + if (auto const pHint = dynamic_cast<sw::LegacyModifyHint const*>(&rHint)) + { + pOld = pHint->m_pOld; + pNew = pHint->m_pNew; + } + else if (auto const pHt = dynamic_cast<sw::MoveText const*>(&rHint)) + { + pMoveText = pHt; + } + else if (auto const pHynt = dynamic_cast<sw::RedlineDelText const*>(&rHint)) + { + pRedlineDelText = pHynt; + } + else if (auto const pHnt = dynamic_cast<sw::RedlineUnDelText const*>(&rHint)) + { + pRedlineUnDelText = pHnt; + } + else + { + assert(!"unexpected hint"); + } + + if (m_pMergedPara) + { + assert(m_pMergedPara->listener.IsListeningTo(&rModify)); + } + + SwTextNode const& rNode(static_cast<SwTextNode const&>(rModify)); + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + + // modifications concerning frame attributes are processed by the base class + if( IsInRange( aFrameFormatSetRange, nWhich ) || RES_FMT_CHG == nWhich ) + { + if (m_pMergedPara) + { // ignore item set changes that don't apply + SwTextNode const*const pAttrNode( + (nWhich == RES_PAGEDESC || nWhich == RES_BREAK) + ? m_pMergedPara->pFirstNode + : m_pMergedPara->pParaPropsNode); + if (pAttrNode != &rModify) + { + return; + } + } + SwContentFrame::Modify( pOld, pNew ); + if( nWhich == RES_FMT_CHG && getRootFrame()->GetCurrShell() ) + { + // collection has changed + Prepare(); + InvalidatePrt_(); + lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); + SetDerivedR2L( false ); + CheckDirChange(); + // Force complete paint due to existing indents. + SetCompletePaint(); + InvalidateLineNum(); + } + return; + } + + if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify) + { + if (isPARATR(nWhich) || isPARATR_LIST(nWhich)) // FRMATR handled above + { + return; // ignore it + } + } + + Broadcast(SfxHint()); // notify SwAccessibleParagraph + + // while locked ignore all modifications + if( IsLocked() ) + return; + + // save stack + // warning: one has to ensure that all variables are set + TextFrameIndex nPos; + TextFrameIndex nLen; + bool bSetFieldsDirty = false; + bool bRecalcFootnoteFlag = false; + + if (pRedlineDelText) + { + if (m_pMergedPara) + { + sal_Int32 const nNPos = pRedlineDelText->nStart; + sal_Int32 const nNLen = pRedlineDelText->nLen; + nPos = MapModelToView(&rNode, nNPos); + // update merged before doing anything else + nLen = UpdateMergedParaForDelete(*m_pMergedPara, false, rNode, nNPos, nNLen); + const sal_Int32 m = -nNLen; + if (nLen && IsIdxInside(nPos, nLen)) + { + InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m ); + } + lcl_SetWrong( *this, rNode, nNPos, m, false ); + if (nLen) + { + lcl_SetScriptInval( *this, nPos ); + bSetFieldsDirty = bRecalcFootnoteFlag = true; + lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); + } + } + } + else if (pRedlineUnDelText) + { + if (m_pMergedPara) + { + sal_Int32 const nNPos = pRedlineUnDelText->nStart; + sal_Int32 const nNLen = pRedlineUnDelText->nLen; + nPos = MapModelToView(&rNode, nNPos); + nLen = UpdateMergedParaForInsert(*m_pMergedPara, false, rNode, nNPos, nNLen); + if (IsIdxInside(nPos, nLen)) + { + if (!nLen) + { + // Refresh NumPortions even when line is empty! + if (nPos) + InvalidateSize(); + else + Prepare(); + } + else + InvalidateRange_( SwCharRange( nPos, nLen ), nNLen ); + } + lcl_SetWrong( *this, rNode, nNPos, nNLen, false ); + lcl_SetScriptInval( *this, nPos ); + bSetFieldsDirty = true; + lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>); + } + } + else if (pMoveText) + { + if (m_pMergedPara + && m_pMergedPara->pFirstNode->GetIndex() <= pMoveText->pDestNode->GetIndex() + && pMoveText->pDestNode->GetIndex() <= m_pMergedPara->pLastNode->GetIndex()) + { // if it's not 2 nodes in merged frame, assume the target node doesn't have frames at all + assert(std::abs(static_cast<long>(rNode.GetIndex()) - static_cast<long>(pMoveText->pDestNode->GetIndex())) == 1); + UpdateMergedParaForMove(*m_pMergedPara, + *this, + bRecalcFootnoteFlag, + *pMoveText->pDestNode, rNode, + pMoveText->nDestStart, + pMoveText->nSourceStart, + pMoveText->nLen); + } + else + { + // there is a situation where this is okay: from JoinNext, which will then call CheckResetRedlineMergeFlag, which will then create merged from scratch for this frame + // assert(!m_pMergedPara || !getRootFrame()->IsHideRedlines() || !pMoveText->pDestNode->getLayoutFrame(getRootFrame())); + } + } + else switch (nWhich) + { + case RES_LINENUMBER: + { + assert(false); // should have been forwarded to SwContentFrame + InvalidateLineNum(); + } + break; + case RES_INS_TXT: + { + sal_Int32 const nNPos = static_cast<const SwInsText*>(pNew)->nPos; + sal_Int32 const nNLen = static_cast<const SwInsText*>(pNew)->nLen; + nPos = MapModelToView(&rNode, nNPos); + nLen = TextFrameIndex(nNLen); + if (m_pMergedPara) + { + UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, nNPos, nNLen); + } + if( IsIdxInside( nPos, nLen ) ) + { + if( !nLen ) + { + // Refresh NumPortions even when line is empty! + if( nPos ) + InvalidateSize(); + else + Prepare(); + } + else + InvalidateRange_( SwCharRange( nPos, nLen ), nNLen ); + } + lcl_SetWrong( *this, rNode, nNPos, nNLen, true ); + lcl_SetScriptInval( *this, nPos ); + bSetFieldsDirty = true; + lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>); + } + break; + case RES_DEL_CHR: + { + sal_Int32 const nNPos = static_cast<const SwDelChr*>(pNew)->nPos; + nPos = MapModelToView(&rNode, nNPos); + if (m_pMergedPara) + { + nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, nNPos, 1); + } + else + { + nLen = TextFrameIndex(1); + } + lcl_SetWrong( *this, rNode, nNPos, -1, true ); + if (nLen) + { + InvalidateRange( SwCharRange(nPos, nLen), -1 ); + lcl_SetScriptInval( *this, nPos ); + bSetFieldsDirty = bRecalcFootnoteFlag = true; + lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); + } + } + break; + case RES_DEL_TXT: + { + sal_Int32 const nNPos = static_cast<const SwDelText*>(pNew)->nStart; + sal_Int32 const nNLen = static_cast<const SwDelText*>(pNew)->nLen; + nPos = MapModelToView(&rNode, nNPos); + if (m_pMergedPara) + { // update merged before doing anything else + nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, nNPos, nNLen); + } + else + { + nLen = TextFrameIndex(nNLen); + } + const sal_Int32 m = -nNLen; + if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen)) + { + if( !nLen ) + InvalidateSize(); + else + InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m ); + } + lcl_SetWrong( *this, rNode, nNPos, m, true ); + if (nLen) + { + lcl_SetScriptInval( *this, nPos ); + bSetFieldsDirty = bRecalcFootnoteFlag = true; + lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); + } + } + break; + case RES_UPDATE_ATTR: + { + const SwUpdateAttr* pNewUpdate = static_cast<const SwUpdateAttr*>(pNew); + + sal_Int32 const nNPos = pNewUpdate->getStart(); + sal_Int32 const nNLen = pNewUpdate->getEnd() - nNPos; + nPos = MapModelToView(&rNode, nNPos); + nLen = MapModelToView(&rNode, nNPos + nNLen) - nPos; + if( IsIdxInside( nPos, nLen ) ) + { + // We need to reformat anyways, even if the invalidated + // range is empty. + // E.g.: empty line, set 14 pt! + + // FootnoteNumbers need to be formatted + if( !nLen ) + nLen = TextFrameIndex(1); + + InvalidateRange_( SwCharRange( nPos, nLen) ); + const sal_uInt16 nTmp = pNewUpdate->getWhichAttr(); + + if( ! nTmp || RES_TXTATR_CHARFMT == nTmp || RES_TXTATR_INETFMT == nTmp || RES_TXTATR_AUTOFMT == nTmp || + RES_FMT_CHG == nTmp || RES_ATTRSET_CHG == nTmp ) + { + lcl_SetWrong( *this, rNode, nNPos, nNPos + nNLen, false ); + lcl_SetScriptInval( *this, nPos ); + } + } + + if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) && + hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) ) + { + SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if ( pViewSh ) + { + pViewSh->InvalidateAccessibleParaAttrs( *this ); + } + } + } + break; + case RES_OBJECTDYING: + break; + + case RES_PARATR_LINESPACING: + { + CalcLineSpace(); + InvalidateSize(); + InvalidatePrt_(); + if( IsInSct() && !GetPrev() ) + { + SwSectionFrame *pSect = FindSctFrame(); + if( pSect->ContainsAny() == this ) + pSect->InvalidatePrt(); + } + + // i#11859 + // (1) Also invalidate next frame on next page/column. + // (2) Skip empty sections and hidden paragraphs + // Thus, use method <InvalidateNextPrtArea()> + InvalidateNextPrtArea(); + + SetCompletePaint(); + } + break; + + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + { + sal_Int32 const nNPos = static_cast<const SwFormatField*>(pNew)->GetTextField()->GetStart(); + nPos = MapModelToView(&rNode, nNPos); + if (IsIdxInside(nPos, TextFrameIndex(1))) + { + if( pNew == pOld ) + { + // only repaint + // opt: invalidate window? + InvalidatePage(); + SetCompletePaint(); + } + else + InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1))); + } + bSetFieldsDirty = true; + // ST2 + if ( SwSmartTagMgr::Get().IsSmartTagsEnabled() ) + lcl_SetWrong( *this, rNode, nNPos, nNPos + 1, false ); + } + break; + + case RES_TXTATR_FTN : + { + if (!IsInFootnote()) + { // the hint may be sent from the anchor node, or from a + // node in the footnote; the anchor index is only valid in the + // anchor node! + assert(&rNode == &static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetTextNode()); + nPos = MapModelToView(&rNode, + static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetStart()); + } +#ifdef _MSC_VER + else nPos = TextFrameIndex(42); // shut up MSVC 2017 spurious warning C4701 +#endif + if (IsInFootnote() || IsIdxInside(nPos, TextFrameIndex(1))) + Prepare( PrepareHint::FootnoteInvalidation, static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote() ); + break; + } + + case RES_ATTRSET_CHG: + { + InvalidateLineNum(); + + const SwAttrSet& rNewSet = *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet(); + const SfxPoolItem* pItem = nullptr; + int nClear = 0; + sal_uInt16 nCount = rNewSet.Count(); + + if( SfxItemState::SET == rNewSet.GetItemState( RES_TXTATR_FTN, false, &pItem )) + { + nPos = MapModelToView(&rNode, + static_cast<const SwFormatFootnote*>(pItem)->GetTextFootnote()->GetStart()); + if (IsIdxInside(nPos, TextFrameIndex(1))) + Prepare( PrepareHint::FootnoteInvalidation, pNew ); + nClear = 0x01; + --nCount; + } + + if( SfxItemState::SET == rNewSet.GetItemState( RES_TXTATR_FIELD, false, &pItem )) + { + nPos = MapModelToView(&rNode, + static_cast<const SwFormatField*>(pItem)->GetTextField()->GetStart()); + if (IsIdxInside(nPos, TextFrameIndex(1))) + { + const SfxPoolItem* pOldItem = pOld ? + &(static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->Get(RES_TXTATR_FIELD)) : nullptr; + if( pItem == pOldItem ) + { + InvalidatePage(); + SetCompletePaint(); + } + else + InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1))); + } + nClear |= 0x02; + --nCount; + } + bool bLineSpace = SfxItemState::SET == rNewSet.GetItemState( + RES_PARATR_LINESPACING, false ), + bRegister = SfxItemState::SET == rNewSet.GetItemState( + RES_PARATR_REGISTER, false ); + if ( bLineSpace || bRegister ) + { + if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + { + Prepare( bRegister ? PrepareHint::Register : PrepareHint::AdjustSizeWithoutFormatting ); + CalcLineSpace(); + InvalidateSize(); + InvalidatePrt_(); + + // i#11859 + // (1) Also invalidate next frame on next page/column. + // (2) Skip empty sections and hidden paragraphs + // Thus, use method <InvalidateNextPrtArea()> + InvalidateNextPrtArea(); + + SetCompletePaint(); + } + nClear |= 0x04; + if ( bLineSpace ) + { + --nCount; + if ((!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + && IsInSct() && !GetPrev()) + { + SwSectionFrame *pSect = FindSctFrame(); + if( pSect->ContainsAny() == this ) + pSect->InvalidatePrt(); + } + } + if ( bRegister ) + --nCount; + } + if ( SfxItemState::SET == rNewSet.GetItemState( RES_PARATR_SPLIT, + false )) + { + if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + { + if (GetPrev()) + CheckKeep(); + Prepare(); + InvalidateSize(); + } + nClear |= 0x08; + --nCount; + } + + if( SfxItemState::SET == rNewSet.GetItemState( RES_BACKGROUND, false) + && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify) + && !IsFollow() && GetDrawObjs() ) + { + SwSortedObjs *pObjs = GetDrawObjs(); + for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) != nullptr ) + { + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + if( !pFly->IsFlyInContentFrame() ) + { + const SvxBrushItem &rBack = + pFly->GetAttrSet()->GetBackground(); + // #GetTransChg# + // following condition determines, if the fly frame + // "inherites" the background color of text frame. + // This is the case, if fly frame background + // color is "no fill"/"auto fill" and if the fly frame + // has no background graphic. + // Thus, check complete fly frame background + // color and *not* only its transparency value + if ( (rBack.GetColor() == COL_TRANSPARENT) && + rBack.GetGraphicPos() == GPOS_NONE ) + { + pFly->SetCompletePaint(); + pFly->InvalidatePage(); + } + } + } + } + } + + if ( SfxItemState::SET == + rNewSet.GetItemState( RES_TXTATR_CHARFMT, false ) ) + { + lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); + lcl_SetScriptInval( *this, TextFrameIndex(0) ); + } + else if ( SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_LANGUAGE, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CJK_LANGUAGE, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CTL_LANGUAGE, false ) ) + lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false ); + else if ( SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_FONT, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CJK_FONT, false ) || + SfxItemState::SET == + rNewSet.GetItemState( RES_CHRATR_CTL_FONT, false ) ) + lcl_SetScriptInval( *this, TextFrameIndex(0) ); + else if ( SfxItemState::SET == + rNewSet.GetItemState( RES_FRAMEDIR, false ) + && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)) + { + SetDerivedR2L( false ); + CheckDirChange(); + // Force complete paint due to existing indents. + SetCompletePaint(); + } + + if( nCount ) + { + if( getRootFrame()->GetCurrShell() ) + { + Prepare(); + InvalidatePrt_(); + } + + if (nClear || (m_pMergedPara && + (m_pMergedPara->pParaPropsNode != &rModify || + m_pMergedPara->pFirstNode != &rModify))) + { + assert(pOld); + SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) ); + SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) ); + + if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify) + { + for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i) + { + if (i != RES_BREAK && i != RES_PAGEDESC) + { + aOldSet.ClearItem(i); + aNewSet.ClearItem(i); + } + } + for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i) + { + aOldSet.ClearItem(i); + aNewSet.ClearItem(i); + } + } + if (m_pMergedPara && m_pMergedPara->pFirstNode != &rModify) + { + aOldSet.ClearItem(RES_BREAK); + aNewSet.ClearItem(RES_BREAK); + aOldSet.ClearItem(RES_PAGEDESC); + aNewSet.ClearItem(RES_PAGEDESC); + } + + if( 0x01 & nClear ) + { + aOldSet.ClearItem( RES_TXTATR_FTN ); + aNewSet.ClearItem( RES_TXTATR_FTN ); + } + if( 0x02 & nClear ) + { + aOldSet.ClearItem( RES_TXTATR_FIELD ); + aNewSet.ClearItem( RES_TXTATR_FIELD ); + } + if ( 0x04 & nClear ) + { + if ( bLineSpace ) + { + aOldSet.ClearItem( RES_PARATR_LINESPACING ); + aNewSet.ClearItem( RES_PARATR_LINESPACING ); + } + if ( bRegister ) + { + aOldSet.ClearItem( RES_PARATR_REGISTER ); + aNewSet.ClearItem( RES_PARATR_REGISTER ); + } + } + if ( 0x08 & nClear ) + { + aOldSet.ClearItem( RES_PARATR_SPLIT ); + aNewSet.ClearItem( RES_PARATR_SPLIT ); + } + if (aOldSet.Count() || aNewSet.Count()) + { + SwContentFrame::Modify( &aOldSet, &aNewSet ); + } + } + else + SwContentFrame::Modify( pOld, pNew ); + } + + if (isA11yRelevantAttribute(nWhich)) + { + SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if ( pViewSh ) + { + pViewSh->InvalidateAccessibleParaAttrs( *this ); + } + } + } + break; + + // Process SwDocPosUpdate + case RES_DOCPOS_UPDATE: + { + if( pOld && pNew ) + { + const SwDocPosUpdate *pDocPos = static_cast<const SwDocPosUpdate*>(pOld); + if( pDocPos->nDocPos <= getFrameArea().Top() ) + { + const SwFormatField *pField = static_cast<const SwFormatField *>(pNew); + TextFrameIndex const nIndex(MapModelToView(&rNode, + pField->GetTextField()->GetStart())); + InvalidateRange(SwCharRange(nIndex, TextFrameIndex(1))); + } + } + break; + } + case RES_PARATR_SPLIT: + if ( GetPrev() ) + CheckKeep(); + Prepare(); + bSetFieldsDirty = true; + break; + case RES_FRAMEDIR : + assert(false); // should have been forwarded to SwContentFrame + SetDerivedR2L( false ); + CheckDirChange(); + break; + default: + { + Prepare(); + InvalidatePrt_(); + if ( !nWhich ) + { + // is called by e. g. HiddenPara with 0 + SwFrame *pNxt; + if ( nullptr != (pNxt = FindNext()) ) + pNxt->InvalidatePrt(); + } + } + } // switch + + if( bSetFieldsDirty ) + GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rNode, 1 ); + + if ( bRecalcFootnoteFlag ) + CalcFootnoteFlag(); +} + +bool SwTextFrame::GetInfo( SfxPoolItem &rHint ) const +{ + if ( RES_VIRTPAGENUM_INFO == rHint.Which() && IsInDocBody() && ! IsFollow() ) + { + SwVirtPageNumInfo &rInfo = static_cast<SwVirtPageNumInfo&>(rHint); + const SwPageFrame *pPage = FindPageFrame(); + if ( pPage ) + { + if ( pPage == rInfo.GetOrigPage() && !GetPrev() ) + { + // this should be the one + // (could only differ temporarily; is that disturbing?) + rInfo.SetInfo( pPage, this ); + return false; + } + if ( pPage->GetPhyPageNum() < rInfo.GetOrigPage()->GetPhyPageNum() && + (!rInfo.GetPage() || pPage->GetPhyPageNum() > rInfo.GetPage()->GetPhyPageNum())) + { + // this could be the one + rInfo.SetInfo( pPage, this ); + } + } + } + return true; +} + +void SwTextFrame::PrepWidows( const sal_uInt16 nNeed, bool bNotify ) +{ + OSL_ENSURE(GetFollow() && nNeed, "+SwTextFrame::Prepare: lost all friends"); + + SwParaPortion *pPara = GetPara(); + if ( !pPara ) + return; + pPara->SetPrepWidows(); + + sal_uInt16 nHave = nNeed; + + // We yield a few lines and shrink in CalcPreps() + SwSwapIfNotSwapped swap( this ); + + SwTextSizeInfo aInf( this ); + SwTextMargin aLine( this, &aInf ); + aLine.Bottom(); + TextFrameIndex nTmpLen = aLine.GetCurr()->GetLen(); + while( nHave && aLine.PrevLine() ) + { + if( nTmpLen ) + --nHave; + nTmpLen = aLine.GetCurr()->GetLen(); + } + + // If it's certain that we can yield lines, the Master needs + // to check the widow rule + if( !nHave ) + { + bool bSplit = true; + if( !IsFollow() ) // only a master decides about orphans + { + const WidowsAndOrphans aWidOrp( this ); + bSplit = ( aLine.GetLineNr() >= aWidOrp.GetOrphansLines() && + aLine.GetLineNr() >= aLine.GetDropLines() ); + } + + if( bSplit ) + { + GetFollow()->SetOffset( aLine.GetEnd() ); + aLine.TruncLines( true ); + if( pPara->IsFollowField() ) + GetFollow()->SetFieldFollow( true ); + } + } + if ( bNotify ) + { + InvalidateSize_(); + InvalidatePage(); + } +} + +static bool lcl_ErgoVadis(SwTextFrame* pFrame, TextFrameIndex & rPos, const PrepareHint ePrep) +{ + const SwFootnoteInfo &rFootnoteInfo = pFrame->GetDoc().GetFootnoteInfo(); + if( ePrep == PrepareHint::ErgoSum ) + { + if( rFootnoteInfo.m_aErgoSum.isEmpty() ) + return false; + rPos = pFrame->GetOffset(); + } + else + { + if( rFootnoteInfo.m_aQuoVadis.isEmpty() ) + return false; + if( pFrame->HasFollow() ) + rPos = pFrame->GetFollow()->GetOffset(); + else + rPos = TextFrameIndex(pFrame->GetText().getLength()); + if( rPos ) + --rPos; // our last character + } + return true; +} + +// Silence over-eager warning emitted at least by GCC 5.3.1 +#if defined __GNUC__ && !defined __clang__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstrict-overflow" +#endif +bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid, + bool bNotify ) +{ + bool bParaPossiblyInvalid = false; + + SwFrameSwapper aSwapper( this, false ); + + if ( IsEmpty() ) + { + switch ( ePrep ) + { + case PrepareHint::BossChanged: + SetInvalidVert( true ); // Test + [[fallthrough]]; + case PrepareHint::WidowsOrphans: + case PrepareHint::Widows: + case PrepareHint::FootnoteInvalidationGone : return bParaPossiblyInvalid; + + case PrepareHint::FramePositionChanged : + { + // We also need an InvalidateSize for Areas (with and without columns), + // so that we format and bUndersized is set (if needed) + if( IsInFly() || IsInSct() ) + { + SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() + + GetUpper()->getFramePrintArea().Bottom(); + if( nTmpBottom < getFrameArea().Bottom() ) + break; + } + // Are there any free-flying frames on this page? + SwTextFly aTextFly( this ); + if( aTextFly.IsOn() ) + { + // Does any free-flying frame overlap? + if ( aTextFly.Relax() || IsUndersized() ) + break; + } + if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue()) + break; + + SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame())); + if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue()) + break; + + // i#28701 - consider anchored objects + if ( GetDrawObjs() ) + break; + + return bParaPossiblyInvalid; + } + default: + break; + } + } + + if( !HasPara() && PrepareHint::MustFit != ePrep ) + { + SetInvalidVert( true ); // Test + OSL_ENSURE( !IsLocked(), "SwTextFrame::Prepare: three of a perfect pair" ); + if ( bNotify ) + InvalidateSize(); + else + InvalidateSize_(); + return bParaPossiblyInvalid; + } + + // Get object from cache while locking + SwTextLineAccess aAccess( this ); + SwParaPortion *pPara = aAccess.GetPara(); + + switch( ePrep ) + { + case PrepareHint::FootnoteMove : + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height(0); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height(0); + } + + InvalidatePrt_(); + InvalidateSize_(); + [[fallthrough]]; + case PrepareHint::AdjustSizeWithoutFormatting : + pPara->SetPrepAdjust(); + if( IsFootnoteNumFrame() != pPara->IsFootnoteNum() || + IsUndersized() ) + { + InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1); + if( GetOffset() && !IsFollow() ) + SetOffset_(TextFrameIndex(0)); + } + break; + case PrepareHint::MustFit : + pPara->SetPrepMustFit(true); + [[fallthrough]]; + case PrepareHint::WidowsOrphans : + pPara->SetPrepAdjust(); + break; + case PrepareHint::Widows : + // MustFit is stronger than anything else + if( pPara->IsPrepMustFit() ) + return bParaPossiblyInvalid; + // see comment in WidowsAndOrphans::FindOrphans and CalcPreps() + PrepWidows( *static_cast<const sal_uInt16 *>(pVoid), bNotify ); + break; + + case PrepareHint::FootnoteInvalidation : + { + SwTextFootnote const *pFootnote = static_cast<SwTextFootnote const *>(pVoid); + if( IsInFootnote() ) + { + // Am I the first TextFrame of a footnote? + if( !GetPrev() ) + // So we're a TextFrame of the footnote, which has + // to display the footnote number or the ErgoSum text + InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1); + + if( !GetNext() ) + { + // We're the last Footnote; we need to update the + // QuoVadis texts now + const SwFootnoteInfo &rFootnoteInfo = GetDoc().GetFootnoteInfo(); + if( !pPara->UpdateQuoVadis( rFootnoteInfo.m_aQuoVadis ) ) + { + TextFrameIndex nPos = pPara->GetParLen(); + if( nPos ) + --nPos; + InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), 1); + } + } + } + else + { + // We are the TextFrame _with_ the footnote + TextFrameIndex const nPos = MapModelToView( + &pFootnote->GetTextNode(), pFootnote->GetStart()); + InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)), 1); + } + break; + } + case PrepareHint::BossChanged : + { + // Test + { + SetInvalidVert( false ); + bool bOld = IsVertical(); + SetInvalidVert( true ); + if( bOld != IsVertical() ) + InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(COMPLETE_STRING))); + } + + if( HasFollow() ) + { + TextFrameIndex nNxtOfst = GetFollow()->GetOffset(); + if( nNxtOfst ) + --nNxtOfst; + InvalidateRange(SwCharRange( nNxtOfst, TextFrameIndex(1)), 1); + } + if( IsInFootnote() ) + { + TextFrameIndex nPos; + if( lcl_ErgoVadis( this, nPos, PrepareHint::QuoVadis ) ) + InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) ); + if( lcl_ErgoVadis( this, nPos, PrepareHint::ErgoSum ) ) + InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) ); + } + // If we have a page number field, we must invalidate those spots + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*this); + TextFrameIndex const nEnd = GetFollow() + ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + TextFrameIndex const nStart(MapModelToView(pNode, pHt->GetStart())); + if (nStart >= GetOffset()) + { + if (nStart >= nEnd) + break; + + // If we're flowing back and own a Footnote, the Footnote also flows + // with us. So that it doesn't obstruct us, we send ourselves + // an ADJUST_FRM. + // pVoid != 0 means MoveBwd() + const sal_uInt16 nWhich = pHt->Which(); + if (RES_TXTATR_FIELD == nWhich || + (HasFootnote() && pVoid && RES_TXTATR_FTN == nWhich)) + InvalidateRange(SwCharRange(nStart, TextFrameIndex(1)), 1); + } + } + // A new boss, a new chance for growing + if( IsUndersized() ) + { + InvalidateSize_(); + InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(1)), 1); + } + break; + } + + case PrepareHint::FramePositionChanged : + { + if ( isFramePrintAreaValid() ) + { + SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame())); + if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue()) + InvalidatePrt(); + } + + // If we don't overlap with anybody: + // did any free-flying frame overlapped _before_ the position change? + bool bFormat = pPara->HasFly(); + if( !bFormat ) + { + if( IsInFly() ) + { + SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() + + GetUpper()->getFramePrintArea().Bottom(); + if( nTmpBottom < getFrameArea().Bottom() ) + bFormat = true; + } + if( !bFormat ) + { + if ( GetDrawObjs() ) + { + const size_t nCnt = GetDrawObjs()->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i]; + // i#28701 - consider all + // to-character anchored objects + if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AT_CHAR ) + { + bFormat = true; + break; + } + } + } + if( !bFormat ) + { + // Are there any free-flying frames on this page? + SwTextFly aTextFly( this ); + if( aTextFly.IsOn() ) + { + // Does any free-flying frame overlap? + bFormat = aTextFly.Relax() || IsUndersized(); + } + } + } + } + + if( bFormat ) + { + if( !IsLocked() ) + { + if( pPara->GetRepaint().HasArea() ) + SetCompletePaint(); + Init(); + pPara = nullptr; + InvalidateSize_(); + } + } + else + { + if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue()) + bParaPossiblyInvalid = Prepare( PrepareHint::Register, nullptr, bNotify ); + // The Frames need to be readjusted, which caused by changes + // in position + else if( HasFootnote() ) + { + bParaPossiblyInvalid = Prepare( PrepareHint::AdjustSizeWithoutFormatting, nullptr, bNotify ); + InvalidateSize_(); + } + else + return bParaPossiblyInvalid; // So that there's no SetPrep() + + if (bParaPossiblyInvalid) + { + // It's possible that pPara was deleted above; retrieve it again + pPara = aAccess.GetPara(); + } + + } + break; + } + case PrepareHint::Register: + if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue()) + { + pPara->SetPrepAdjust(); + CalcLineSpace(); + + // It's possible that pPara was deleted above; retrieve it again + bParaPossiblyInvalid = true; + pPara = aAccess.GetPara(); + + InvalidateSize(); + InvalidatePrt_(); + SwFrame* pNxt; + if ( nullptr != ( pNxt = GetIndNext() ) ) + { + pNxt->InvalidatePrt_(); + if ( pNxt->IsLayoutFrame() ) + pNxt->InvalidatePage(); + } + SetCompletePaint(); + } + break; + case PrepareHint::FootnoteInvalidationGone : + { + // If a Follow is calling us, because a footnote is being deleted, our last + // line has to be formatted, so that the first line of the Follow can flow up. + // Which had flowed to the next page to be together with the footnote (this is + // especially true for areas with columns) + OSL_ENSURE( GetFollow(), "PrepareHint::FootnoteInvalidationGone may only be called by Follow" ); + TextFrameIndex nPos = GetFollow()->GetOffset(); + if( IsFollow() && GetOffset() == nPos ) // If we don't have a mass of text, we call our + FindMaster()->Prepare( PrepareHint::FootnoteInvalidationGone ); // Master's Prepare + if( nPos ) + --nPos; // The char preceding our Follow + InvalidateRange(SwCharRange(nPos, TextFrameIndex(1))); + return bParaPossiblyInvalid; + } + case PrepareHint::ErgoSum: + case PrepareHint::QuoVadis: + { + TextFrameIndex nPos; + if( lcl_ErgoVadis( this, nPos, ePrep ) ) + InvalidateRange(SwCharRange(nPos, TextFrameIndex(1))); + } + break; + case PrepareHint::FlyFrameAttributesChanged: + { + if( pVoid ) + { + TextFrameIndex const nWhere = CalcFlyPos( static_cast<SwFrameFormat const *>(pVoid) ); + OSL_ENSURE( TextFrameIndex(COMPLETE_STRING) != nWhere, "Prepare: Why me?" ); + InvalidateRange(SwCharRange(nWhere, TextFrameIndex(1))); + return bParaPossiblyInvalid; + } + [[fallthrough]]; // else: continue with default case block + } + case PrepareHint::Clear: + default: + { + if( IsLocked() ) + { + if( PrepareHint::FlyFrameArrive == ePrep || PrepareHint::FlyFrameLeave == ePrep ) + { + TextFrameIndex const nLen = (GetFollow() + ? GetFollow()->GetOffset() + : TextFrameIndex(COMPLETE_STRING)) + - GetOffset(); + InvalidateRange( SwCharRange( GetOffset(), nLen ) ); + } + } + else + { + if( pPara->GetRepaint().HasArea() ) + SetCompletePaint(); + Init(); + pPara = nullptr; + if( GetOffset() && !IsFollow() ) + SetOffset_( TextFrameIndex(0) ); + if ( bNotify ) + InvalidateSize(); + else + InvalidateSize_(); + } + return bParaPossiblyInvalid; // no SetPrep() happened + } + } + if( pPara ) + { + pPara->SetPrep(); + } + + return bParaPossiblyInvalid; +} +#if defined __GNUC__ && !defined __clang__ +# pragma GCC diagnostic pop +#endif + +/** + * Small Helper class: + * Prepares a test format. + * The frame is changed in size and position, its SwParaPortion is moved aside + * and a new one is created. + * To achieve this, run formatting with bTestFormat flag set. + * In the destructor the TextFrame is reset to its original state. + */ +class SwTestFormat +{ + SwTextFrame *pFrame; + SwParaPortion *pOldPara; + SwRect aOldFrame, aOldPrt; +public: + SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPrv, SwTwips nMaxHeight ); + ~SwTestFormat(); +}; + +SwTestFormat::SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPre, SwTwips nMaxHeight ) + : pFrame( pTextFrame ) +{ + aOldFrame = pFrame->getFrameArea(); + aOldPrt = pFrame->getFramePrintArea(); + + SwRectFnSet aRectFnSet(pFrame); + SwTwips nLower = aRectFnSet.GetBottomMargin(*pFrame); + + { + // indeed, here the GetUpper()->getFramePrintArea() gets copied and manipulated + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); + aFrm.setSwRect(pFrame->GetUpper()->getFramePrintArea()); + aFrm += pFrame->GetUpper()->getFrameArea().Pos(); + aRectFnSet.SetHeight( aFrm, nMaxHeight ); + + if( pFrame->GetPrev() ) + { + aRectFnSet.SetPosY( + aFrm, + aRectFnSet.GetBottom(pFrame->GetPrev()->getFrameArea()) - ( aRectFnSet.IsVert() ? nMaxHeight + 1 : 0 ) ); + } + } + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame); + aRectFnSet.SetPosX(aPrt, rAttrs.CalcLeft( pFrame ) ); + } + + if( pPre ) + { + SwTwips nUpper = pFrame->CalcUpperSpace( &rAttrs, pPre ); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame); + aRectFnSet.SetPosY(aPrt, nUpper ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame); + aRectFnSet.SetHeight( aPrt, std::max( 0L , aRectFnSet.GetHeight(pFrame->getFrameArea()) - aRectFnSet.GetTop(aPrt) - nLower ) ); + aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(pFrame->getFrameArea()) - ( rAttrs.CalcLeft( pFrame ) + rAttrs.CalcRight( pFrame ) ) ); + } + + pOldPara = pFrame->HasPara() ? pFrame->GetPara() : nullptr; + pFrame->SetPara( new SwParaPortion(), false ); + OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped before Format_" ); + + if ( pFrame->IsVertical() ) + pFrame->SwapWidthAndHeight(); + + SwTextFormatInfo aInf( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, false, true, true ); + SwTextFormatter aLine( pFrame, &aInf ); + + pFrame->Format_( aLine, aInf ); + + if ( pFrame->IsVertical() ) + pFrame->SwapWidthAndHeight(); + + OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped after Format_" ); +} + +SwTestFormat::~SwTestFormat() +{ + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); + aFrm.setSwRect(aOldFrame); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame); + aPrt.setSwRect(aOldPrt); + } + + pFrame->SetPara( pOldPara ); +} + +bool SwTextFrame::TestFormat( const SwFrame* pPrv, SwTwips &rMaxHeight, bool &bSplit ) +{ + PROTOCOL_ENTER( this, PROT::TestFormat, DbgAction::NONE, nullptr ) + + if( IsLocked() && GetUpper()->getFramePrintArea().Width() <= 0 ) + return false; + + SwTestFormat aSave( this, pPrv, rMaxHeight ); + + return SwTextFrame::WouldFit( rMaxHeight, bSplit, true ); +} + +/** + * We should not and don't need to reformat. + * We assume that we already formatted and that the formatting + * data is still current. + * + * We also assume that the frame width of the Master and Follow + * are the same. That's why we're not calling FindBreak() for + * FindOrphans(). + * The required height is coming from nMaxHeight. + * + * @returns true if I can split + */ +bool SwTextFrame::WouldFit( SwTwips &rMaxHeight, bool &bSplit, bool bTst ) +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), + "SwTextFrame::WouldFit with swapped frame" ); + SwRectFnSet aRectFnSet(this); + + if( IsLocked() ) + return false; + + // it can happen that the IdleCollector removed the cached information + if( !IsEmpty() ) + GetFormatted(); + + // i#27801 - correction: 'short cut' for empty paragraph + // can *not* be applied, if test format is in progress. The test format doesn't + // adjust the frame and the printing area - see method <SwTextFrame::Format_(..)>, + // which is called in <SwTextFrame::TestFormat(..)> + if ( IsEmpty() && !bTst ) + { + bSplit = false; + SwTwips nHeight = aRectFnSet.IsVert() ? getFramePrintArea().SSize().Width() : getFramePrintArea().SSize().Height(); + if( rMaxHeight < nHeight ) + return false; + else + { + rMaxHeight -= nHeight; + return true; + } + } + + // GetPara can still be 0 in edge cases + // We return true in order to be reformatted on the new Page + OSL_ENSURE( HasPara() || IsHiddenNow(), "WouldFit: GetFormatted() and then !HasPara()" ); + if( !HasPara() || ( !aRectFnSet.GetHeight(getFrameArea()) && IsHiddenNow() ) ) + return true; + + // Because the Orphan flag only exists for a short moment, we also check + // whether the Framesize is set to very huge by CalcPreps, in order to + // force a MoveFwd + if (IsWidow() || (aRectFnSet.IsVert() + ? (0 == getFrameArea().Left()) + : (sw::WIDOW_MAGIC - 20000 < getFrameArea().Bottom()))) + { + SetWidow(false); + if ( GetFollow() ) + { + // If we've ended up here due to a Widow request by our Follow, we check + // whether there's a Follow with a real height at all. + // Else (e.g. for newly created SctFrames) we ignore the IsWidow() and + // still check if we can find enough room + if (((!aRectFnSet.IsVert() && getFrameArea().Bottom() <= sw::WIDOW_MAGIC - 20000) || + ( aRectFnSet.IsVert() && 0 < getFrameArea().Left() ) ) && + ( GetFollow()->IsVertical() ? + !GetFollow()->getFrameArea().Width() : + !GetFollow()->getFrameArea().Height() ) ) + { + SwTextFrame* pFoll = GetFollow()->GetFollow(); + while( pFoll && + ( pFoll->IsVertical() ? + !pFoll->getFrameArea().Width() : + !pFoll->getFrameArea().Height() ) ) + pFoll = pFoll->GetFollow(); + if( pFoll ) + return false; + } + else + return false; + } + } + + SwSwapIfNotSwapped swap( this ); + + SwTextSizeInfo aInf( this ); + SwTextMargin aLine( this, &aInf ); + + WidowsAndOrphans aFrameBreak( this, rMaxHeight, bSplit ); + + bool bRet = true; + + aLine.Bottom(); + // is breaking necessary? + bSplit = !aFrameBreak.IsInside( aLine ); + if ( bSplit ) + bRet = !aFrameBreak.IsKeepAlways() && aFrameBreak.WouldFit( aLine, rMaxHeight, bTst ); + else + { + // we need the total height including the current line + aLine.Top(); + do + { + rMaxHeight -= aLine.GetLineHeight(); + } while ( aLine.Next() ); + } + + return bRet; +} + +sal_uInt16 SwTextFrame::GetParHeight() const +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), + "SwTextFrame::GetParHeight with swapped frame" ); + + if( !HasPara() ) + { // For non-empty paragraphs this is a special case + // For UnderSized we can simply just ask 1 Twip more + sal_uInt16 nRet = static_cast<sal_uInt16>(getFramePrintArea().SSize().Height()); + if( IsUndersized() ) + { + if( IsEmpty() || GetText().isEmpty() ) + nRet = static_cast<sal_uInt16>(EmptyHeight()); + else + ++nRet; + } + return nRet; + } + + // TODO: Refactor and improve code + const SwLineLayout* pLineLayout = GetPara(); + sal_uInt16 nHeight = pLineLayout ? pLineLayout->GetRealHeight() : 0; + + // Is this paragraph scrolled? Our height until now is at least + // one line height too low then + if( GetOffset() && !IsFollow() ) + nHeight *= 2; + + while ( pLineLayout && pLineLayout->GetNext() ) + { + pLineLayout = pLineLayout->GetNext(); + nHeight = nHeight + pLineLayout->GetRealHeight(); + } + + return nHeight; +} + +/** + * @returns this _always_ in the formatted state! + */ +SwTextFrame* SwTextFrame::GetFormatted( bool bForceQuickFormat ) +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + SwSwapIfSwapped swap( this ); + + // In case the SwLineLayout was cleared out of the s_pTextCache, recreate it + // Not for empty paragraphs + if( !HasPara() && !(isFrameAreaDefinitionValid() && IsEmpty()) ) + { + // Calc() must be called, because frame position can be wrong + const bool bFormat = isFrameAreaSizeValid(); + Calc(pRenderContext); // calls Format() if invalid + + // If the flags were valid (hence bFormat=true), Calc did nothing, + // so Format() must be called manually in order to recreate + // the SwLineLayout that has been deleted from the + // SwTextFrame::s_pTextCache (hence !HasPara() above). + // Optimization with FormatQuick() + if( bFormat && !FormatQuick( bForceQuickFormat ) ) + Format(getRootFrame()->GetCurrShell()->GetOut()); + } + + return this; +} + +SwTwips SwTextFrame::CalcFitToContent() +{ + // i#31490 + // If we are currently locked, we better return with a + // fairly reasonable value: + if ( IsLocked() ) + return getFramePrintArea().Width(); + + SwParaPortion* pOldPara = GetPara(); + SwParaPortion *pDummy = new SwParaPortion(); + SetPara( pDummy, false ); + const SwPageFrame* pPage = FindPageFrame(); + + const Point aOldFramePos = getFrameArea().Pos(); + const SwTwips nOldFrameWidth = getFrameArea().Width(); + const SwTwips nOldPrtWidth = getFramePrintArea().Width(); + const SwTwips nPageWidth = GetUpper()->IsVertical() ? + pPage->getFramePrintArea().Height() : + pPage->getFramePrintArea().Width(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( nPageWidth ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( nPageWidth ); + } + + // i#25422 objects anchored as character in RTL + if ( IsRightToLeft() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().AdjustX(nOldFrameWidth - nPageWidth ); + } + + TextFrameLockGuard aLock( this ); + + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true ); + aInf.SetIgnoreFly( true ); + SwTextFormatter aLine( this, &aInf ); + SwHookOut aHook( aInf ); + + // i#54031 - assure minimum of MINLAY twips. + const SwTwips nMax = std::max( SwTwips(MINLAY), aLine.CalcFitToContent_() + 1 ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( nOldFrameWidth ); + + // i#25422 objects anchored as character in RTL + if ( IsRightToLeft() ) + { + aFrm.Pos() = aOldFramePos; + } + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( nOldPrtWidth ); + } + + SetPara( pOldPara ); + + return nMax; +} + +/** + * Simulate format for a list item paragraph, whose list level attributes + * are in LABEL_ALIGNMENT mode, in order to determine additional first + * line offset for the real text formatting due to the value of label + * adjustment attribute of the list level. + */ +void SwTextFrame::CalcAdditionalFirstLineOffset() +{ + if ( IsLocked() ) + return; + + // reset additional first line offset + mnAdditionalFirstLineOffset = 0; + + const SwTextNode* pTextNode( GetTextNodeForParaProps() ); + // sw_redlinehide: check that pParaPropsNode is the correct one + assert(pTextNode->IsNumbered(getRootFrame()) == pTextNode->IsNumbered(nullptr)); + if (pTextNode->IsNumbered(getRootFrame()) && + pTextNode->IsCountedInList() && pTextNode->GetNumRule()) + { + int nListLevel = pTextNode->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const SwNumFormat& rNumFormat = + pTextNode->GetNumRule()->Get( static_cast<sal_uInt16>(nListLevel) ); + if ( rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + // keep current paragraph portion and apply dummy paragraph portion + SwParaPortion* pOldPara = GetPara(); + SwParaPortion *pDummy = new SwParaPortion(); + SetPara( pDummy, false ); + + // lock paragraph + TextFrameLockGuard aLock( this ); + + // simulate text formatting + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true ); + aInf.SetIgnoreFly( true ); + SwTextFormatter aLine( this, &aInf ); + SwHookOut aHook( aInf ); + aLine.CalcFitToContent_(); + + // determine additional first line offset + const SwLinePortion* pFirstPortion = aLine.GetCurr()->GetFirstPortion(); + if ( pFirstPortion->InNumberGrp() && !pFirstPortion->IsFootnoteNumPortion() ) + { + SwTwips nNumberPortionWidth( pFirstPortion->Width() ); + + const SwLinePortion* pPortion = pFirstPortion->GetNextPortion(); + while ( pPortion && + pPortion->InNumberGrp() && !pPortion->IsFootnoteNumPortion()) + { + nNumberPortionWidth += pPortion->Width(); + pPortion = pPortion->GetNextPortion(); + } + + if ( ( IsRightToLeft() && + rNumFormat.GetNumAdjust() == SvxAdjust::Left ) || + ( !IsRightToLeft() && + rNumFormat.GetNumAdjust() == SvxAdjust::Right ) ) + { + mnAdditionalFirstLineOffset = -nNumberPortionWidth; + } + else if ( rNumFormat.GetNumAdjust() == SvxAdjust::Center ) + { + mnAdditionalFirstLineOffset = -(nNumberPortionWidth/2); + } + } + + // restore paragraph portion + SetPara( pOldPara ); + } + } +} + +/** + * Determine the height of the last line for the calculation of + * the proportional line spacing + * + * Height of last line will be stored in new member + * mnHeightOfLastLine and can be accessed via method + * GetHeightOfLastLine() + * + * @param _bUseFont force the usage of the former algorithm to + * determine the height of the last line, which + * uses the font + */ +void SwTextFrame::CalcHeightOfLastLine( const bool _bUseFont ) +{ + // i#71281 + // Invalidate printing area, if height of last line changes + const SwTwips nOldHeightOfLastLine( mnHeightOfLastLine ); + + // determine output device + SwViewShell* pVsh = getRootFrame()->GetCurrShell(); + OSL_ENSURE( pVsh, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no SwViewShell" ); + + // i#78921 + // There could be no <SwViewShell> instance in the case of loading a binary + // StarOffice file format containing an embedded Writer document. + if ( !pVsh ) + { + return; + } + OutputDevice* pOut = pVsh->GetOut(); + const IDocumentSettingAccess *const pIDSA = &GetDoc().getIDocumentSettingAccess(); + if ( !pVsh->GetViewOptions()->getBrowseMode() || + pVsh->GetViewOptions()->IsPrtFormat() ) + { + pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true ); + } + OSL_ENSURE( pOut, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no OutputDevice" ); + + if ( !pOut ) + { + return; + } + + // determine height of last line + if ( _bUseFont || pIDSA->get(DocumentSettingId::OLD_LINE_SPACING ) ) + { + // former determination of last line height for proportional line + // spacing - take height of font set at the paragraph + // FIXME actually... must the font match across all nodes? + SwFont aFont( &GetTextNodeForParaProps()->GetSwAttrSet(), pIDSA ); + + // we must ensure that the font is restored correctly on the OutputDevice + // otherwise Last!=Owner could occur + if ( pLastFont ) + { + SwFntObj *pOldFont = pLastFont; + pLastFont = nullptr; + aFont.SetFntChg( true ); + aFont.ChgPhysFnt( pVsh, *pOut ); + mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut ); + assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt"); + pLastFont->Unlock(); + pLastFont = pOldFont; + pLastFont->SetDevFont( pVsh, *pOut ); + } + else + { + vcl::Font aOldFont = pOut->GetFont(); + aFont.SetFntChg( true ); + aFont.ChgPhysFnt( pVsh, *pOut ); + mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut ); + assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt"); + pLastFont->Unlock(); + pLastFont = nullptr; + pOut->SetFont( aOldFont ); + } + } + else + { + // new determination of last line height - take actually height of last line + // i#89000 + // assure same results, if paragraph is undersized + if ( IsUndersized() ) + { + mnHeightOfLastLine = 0; + } + else + { + bool bCalcHeightOfLastLine = true; + if ( ( !HasPara() && IsEmpty( ) ) || GetText().isEmpty() ) + { + mnHeightOfLastLine = EmptyHeight(); + bCalcHeightOfLastLine = false; + } + + if ( bCalcHeightOfLastLine ) + { + OSL_ENSURE( HasPara(), + "<SwTextFrame::CalcHeightOfLastLine()> - missing paragraph portions." ); + const SwLineLayout* pLineLayout = GetPara(); + while ( pLineLayout && pLineLayout->GetNext() ) + { + // iteration to last line + pLineLayout = pLineLayout->GetNext(); + } + if ( pLineLayout ) + { + SwTwips nAscent, nDescent, nDummy1, nDummy2; + // i#47162 - suppress consideration of + // fly content portions and the line portion. + pLineLayout->MaxAscentDescent( nAscent, nDescent, + nDummy1, nDummy2, + nullptr, true ); + // i#71281 + // Suppress wrong invalidation of printing area, if method is + // called recursive. + // Thus, member <mnHeightOfLastLine> is only set directly, if + // no recursive call is needed. + const SwTwips nNewHeightOfLastLine = nAscent + nDescent; + // i#47162 - if last line only contains + // fly content portions, <mnHeightOfLastLine> is zero. + // In this case determine height of last line by the font + if ( nNewHeightOfLastLine == 0 ) + { + CalcHeightOfLastLine( true ); + } + else + { + mnHeightOfLastLine = nNewHeightOfLastLine; + } + } + } + } + } + // i#71281 + // invalidate printing area, if height of last line changes + if ( mnHeightOfLastLine != nOldHeightOfLastLine ) + { + InvalidatePrt(); + } +} + +/** + * Method returns the value of the inter line spacing for a text frame. + * Such a value exists for proportional line spacings ("1,5 Lines", + * "Double", "Proportional" and for leading line spacing ("Leading"). + * + * @param _bNoPropLineSpacing (default = false) control whether the + * value of a proportional line spacing is + * returned or not + */ +long SwTextFrame::GetLineSpace( const bool _bNoPropLineSpace ) const +{ + long nRet = 0; + + const SvxLineSpacingItem &rSpace = GetTextNodeForParaProps()->GetSwAttrSet().GetLineSpacing(); + + switch( rSpace.GetInterLineSpaceRule() ) + { + case SvxInterLineSpaceRule::Prop: + { + if ( _bNoPropLineSpace ) + { + break; + } + + // i#11860 - adjust spacing implementation for object positioning + // - compatibility to MS Word + nRet = GetHeightOfLastLine(); + + long nTmp = nRet; + nTmp *= rSpace.GetPropLineSpace(); + nTmp /= 100; + nTmp -= nRet; + if ( nTmp > 0 ) + nRet = nTmp; + else + nRet = 0; + } + break; + case SvxInterLineSpaceRule::Fix: + { + if ( rSpace.GetInterLineSpace() > 0 ) + nRet = rSpace.GetInterLineSpace(); + } + break; + default: + break; + } + return nRet; +} + +sal_uInt16 SwTextFrame::FirstLineHeight() const +{ + if ( !HasPara() ) + { + if( IsEmpty() && isFrameAreaDefinitionValid() ) + return IsVertical() ? static_cast<sal_uInt16>(getFramePrintArea().Width()) : static_cast<sal_uInt16>(getFramePrintArea().Height()); + return USHRT_MAX; + } + const SwParaPortion *pPara = GetPara(); + if ( !pPara ) + return USHRT_MAX; + + return pPara->Height(); +} + +sal_uInt16 SwTextFrame::GetLineCount(TextFrameIndex const nPos) +{ + sal_uInt16 nRet = 0; + SwTextFrame *pFrame = this; + do + { + pFrame->GetFormatted(); + if( !pFrame->HasPara() ) + break; + SwTextSizeInfo aInf( pFrame ); + SwTextMargin aLine( pFrame, &aInf ); + if (TextFrameIndex(COMPLETE_STRING) == nPos) + aLine.Bottom(); + else + aLine.CharToLine( nPos ); + nRet = nRet + aLine.GetLineNr(); + pFrame = pFrame->GetFollow(); + } while ( pFrame && pFrame->GetOffset() <= nPos ); + return nRet; +} + +void SwTextFrame::ChgThisLines() +{ + // not necessary to format here (GetFormatted etc.), because we have to come from there! + sal_uLong nNew = 0; + const SwLineNumberInfo &rInf = GetDoc().GetLineNumberInfo(); + if ( !GetText().isEmpty() && HasPara() ) + { + SwTextSizeInfo aInf( this ); + SwTextMargin aLine( this, &aInf ); + if ( rInf.IsCountBlankLines() ) + { + aLine.Bottom(); + nNew = static_cast<sal_uLong>(aLine.GetLineNr()); + } + else + { + do + { + if( aLine.GetCurr()->HasContent() ) + ++nNew; + } while ( aLine.NextLine() ); + } + } + else if ( rInf.IsCountBlankLines() ) + nNew = 1; + + if ( nNew != mnThisLines ) + { + if (!IsInTab() && GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber().IsCount()) + { + mnAllLines -= mnThisLines; + mnThisLines = nNew; + mnAllLines += mnThisLines; + SwFrame *pNxt = GetNextContentFrame(); + while( pNxt && pNxt->IsInTab() ) + { + if( nullptr != (pNxt = pNxt->FindTabFrame()) ) + pNxt = pNxt->FindNextCnt(); + } + if( pNxt ) + pNxt->InvalidateLineNum(); + + // Extend repaint to the bottom. + if ( HasPara() ) + { + SwRepaint& rRepaint = GetPara()->GetRepaint(); + rRepaint.Bottom( std::max( rRepaint.Bottom(), + getFrameArea().Top()+getFramePrintArea().Bottom())); + } + } + else // Paragraphs which are not counted should not manipulate the AllLines. + mnThisLines = nNew; + } +} + +void SwTextFrame::RecalcAllLines() +{ + ValidateLineNum(); + + if ( !IsInTab() ) + { + const sal_uLong nOld = GetAllLines(); + const SwFormatLineNumber &rLineNum = GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber(); + sal_uLong nNewNum; + const bool bRestart = GetDoc().GetLineNumberInfo().IsRestartEachPage(); + + if ( !IsFollow() && rLineNum.GetStartValue() && rLineNum.IsCount() ) + nNewNum = rLineNum.GetStartValue() - 1; + // If it is a follow or not has not be considered if it is a restart at each page; the + // restart should also take effect at follows. + else if ( bRestart && FindPageFrame()->FindFirstBodyContent() == this ) + { + nNewNum = 0; + } + else + { + SwContentFrame *pPrv = GetPrevContentFrame(); + while ( pPrv && + (pPrv->IsInTab() || pPrv->IsInDocBody() != IsInDocBody()) ) + pPrv = pPrv->GetPrevContentFrame(); + + // i#78254 Restart line numbering at page change + // First body content may be in table! + if ( bRestart && pPrv && pPrv->FindPageFrame() != FindPageFrame() ) + pPrv = nullptr; + + nNewNum = pPrv ? static_cast<SwTextFrame*>(pPrv)->GetAllLines() : 0; + } + if ( rLineNum.IsCount() ) + nNewNum += GetThisLines(); + + if ( nOld != nNewNum ) + { + mnAllLines = nNewNum; + SwContentFrame *pNxt = GetNextContentFrame(); + while ( pNxt && + (pNxt->IsInTab() || pNxt->IsInDocBody() != IsInDocBody()) ) + pNxt = pNxt->GetNextContentFrame(); + if ( pNxt ) + { + if ( pNxt->GetUpper() != GetUpper() ) + pNxt->InvalidateLineNum(); + else + pNxt->InvalidateLineNum_(); + } + } + } +} + +void SwTextFrame::VisitPortions( SwPortionHandler& rPH ) const +{ + const SwParaPortion* pPara = isFrameAreaDefinitionValid() ? GetPara() : nullptr; + + if (pPara) + { + if ( IsFollow() ) + rPH.Skip( GetOffset() ); + + const SwLineLayout* pLine = pPara; + while ( pLine ) + { + const SwLinePortion* pPor = pLine->GetFirstPortion(); + while ( pPor ) + { + pPor->HandlePortion( rPH ); + pPor = pPor->GetNextPortion(); + } + + rPH.LineBreak(pLine->Width()); + pLine = pLine->GetNext(); + } + } + + rPH.Finish(); +} + +const SwScriptInfo* SwTextFrame::GetScriptInfo() const +{ + const SwParaPortion* pPara = GetPara(); + return pPara ? &pPara->GetScriptInfo() : nullptr; +} + +/** + * Helper function for SwTextFrame::CalcBasePosForFly() + */ +static SwTwips lcl_CalcFlyBasePos( const SwTextFrame& rFrame, SwRect aFlyRect, + SwTextFly const & rTextFly ) +{ + SwRectFnSet aRectFnSet(&rFrame); + SwTwips nRet = rFrame.IsRightToLeft() ? + aRectFnSet.GetRight(rFrame.getFrameArea()) : + aRectFnSet.GetLeft(rFrame.getFrameArea()); + + do + { + SwRect aRect = rTextFly.GetFrame( aFlyRect ); + if ( 0 != aRectFnSet.GetWidth(aRect) ) + { + if ( rFrame.IsRightToLeft() ) + { + if ( aRectFnSet.GetRight(aRect) - + aRectFnSet.GetRight(aFlyRect) >= 0 ) + { + aRectFnSet.SetRight( +aFlyRect, aRectFnSet.GetLeft(aRect) ); + nRet = aRectFnSet.GetLeft(aRect); + } + else + break; + } + else + { + if ( aRectFnSet.GetLeft(aFlyRect) - + aRectFnSet.GetLeft(aRect) >= 0 ) + { + aRectFnSet.SetLeft( +aFlyRect, aRectFnSet.GetRight(aRect) + 1 ); + nRet = aRectFnSet.GetRight(aRect); + } + else + break; + } + } + else + break; + } + while ( aRectFnSet.GetWidth(aFlyRect) > 0 ); + + return nRet; +} + +void SwTextFrame::CalcBaseOfstForFly() +{ + OSL_ENSURE( !IsVertical() || !IsSwapped(), + "SwTextFrame::CalcBasePosForFly with swapped frame!" ); + + if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_FLY_OFFSETS)) + return; + + SwRectFnSet aRectFnSet(this); + + SwRect aFlyRect( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + + // Get first 'real' line and adjust position and height of line rectangle. + // Correct behaviour if no 'real' line exists + // (empty paragraph with and without a dummy portion) + SwTwips nFlyAnchorVertOfstNoWrap = 0; + { + SwTwips nTop = aRectFnSet.GetTop(aFlyRect); + const SwLineLayout* pLay = GetPara(); + SwTwips nLineHeight = 200; + while( pLay && pLay->IsDummy() && pLay->GetNext() ) + { + nTop += pLay->Height(); + nFlyAnchorVertOfstNoWrap += pLay->Height(); + pLay = pLay->GetNext(); + } + if ( pLay ) + { + nLineHeight = pLay->Height(); + } + aRectFnSet.SetTopAndHeight( aFlyRect, nTop, nLineHeight ); + } + + SwTextFly aTextFly( this ); + aTextFly.SetIgnoreCurrentFrame( true ); + aTextFly.SetIgnoreContour( true ); + // ignore objects in page header|footer for + // text frames not in page header|footer + aTextFly.SetIgnoreObjsInHeaderFooter( true ); + SwTwips nRet1 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly ); + aTextFly.SetIgnoreCurrentFrame( false ); + SwTwips nRet2 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly ); + + // make values relative to frame start position + SwTwips nLeft = IsRightToLeft() ? + aRectFnSet.GetRight(getFrameArea()) : + aRectFnSet.GetLeft(getFrameArea()); + + mnFlyAnchorOfst = nRet1 - nLeft; + mnFlyAnchorOfstNoWrap = nRet2 - nLeft; + + if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS)) + return; + + if (mnFlyAnchorOfstNoWrap > 0) + mnFlyAnchorVertOfstNoWrap = nFlyAnchorVertOfstNoWrap; +} + +SwTwips SwTextFrame::GetBaseVertOffsetForFly(bool bIgnoreFlysAnchoredAtThisFrame) const +{ + return bIgnoreFlysAnchoredAtThisFrame ? 0 : mnFlyAnchorVertOfstNoWrap; +} + +/** + * Repaint all text frames of the given text node + */ +void SwTextFrame::repaintTextFrames( const SwTextNode& rNode ) +{ + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode); + for( const SwTextFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + SwRect aRec( pFrame->GetPaintArea() ); + const SwRootFrame *pRootFrame = pFrame->getRootFrame(); + SwViewShell *pCurShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr; + if( pCurShell ) + pCurShell->InvalidateWindows( aRec ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtftn.cxx b/sw/source/core/text/txtftn.cxx new file mode 100644 index 000000000..73aff1cf5 --- /dev/null +++ b/sw/source/core/text/txtftn.cxx @@ -0,0 +1,1553 @@ +/* -*- 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 <viewsh.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <ndtxt.hxx> +#include <SwPortionHandler.hxx> +#include <txtftn.hxx> +#include <flyfrm.hxx> +#include <fmtftn.hxx> +#include <ftninfo.hxx> +#include <charfmt.hxx> +#include <rowfrm.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <tabfrm.hxx> +#include <sortedobjs.hxx> + +#include <swfont.hxx> +#include "porftn.hxx" +#include "porfly.hxx" +#include "porlay.hxx" +#include <txtfrm.hxx> +#include "itrform2.hxx" +#include <ftnfrm.hxx> +#include <pagedesc.hxx> +#include "redlnitr.hxx" +#include <sectfrm.hxx> +#include <layouter.hxx> +#include <frmtool.hxx> +#include <ndindex.hxx> +#include <IDocumentSettingAccess.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/CharSet.hpp> +#include <com/sun/star/text/XTextRange.hpp> + +using namespace ::com::sun::star; + +bool SwTextFrame::IsFootnoteNumFrame_() const +{ + if (IsInTab()) + return false; // tdf#102073 first frame in cell doesn't have mpPrev set + const SwFootnoteFrame* pFootnote = FindFootnoteFrame()->GetMaster(); + while( pFootnote && !pFootnote->ContainsContent() ) + pFootnote = pFootnote->GetMaster(); + return !pFootnote; +} + +/** + * Looks for the TextFrame matching the SwTextFootnote within a master-follow chain + */ +SwTextFrame *SwTextFrame::FindFootnoteRef( const SwTextFootnote *pFootnote ) +{ + SwTextFrame *pFrame = this; + const bool bFwd = MapModelToView(&pFootnote->GetTextNode(), pFootnote->GetStart()) >= GetOffset(); + while( pFrame ) + { + if( SwFootnoteBossFrame::FindFootnote( pFrame, pFootnote ) ) + return pFrame; + pFrame = bFwd ? pFrame->GetFollow() : + pFrame->IsFollow() ? pFrame->FindMaster() : nullptr; + } + return pFrame; +} + +void SwTextFrame::SetHasRotatedPortions(bool bHasRotatedPortions) +{ + mbHasRotatedPortions = bHasRotatedPortions; +} + +#ifdef DBG_UTIL +void SwTextFrame::CalcFootnoteFlag(TextFrameIndex nStop) // For testing the SplitFrame +#else +void SwTextFrame::CalcFootnoteFlag() +#endif +{ + mbFootnote = false; + +#ifdef DBG_UTIL + const TextFrameIndex nEnd = nStop != TextFrameIndex(COMPLETE_STRING) + ? nStop + : GetFollow() ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING); +#else + const TextFrameIndex nEnd = GetFollow() + ? GetFollow()->GetOffset() + : TextFrameIndex(COMPLETE_STRING); +#endif + + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*this); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if ( pHt->Which() == RES_TXTATR_FTN ) + { + TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart())); + if ( nEnd < nIdx ) + break; + if( GetOffset() <= nIdx ) + { + mbFootnote = true; + break; + } + } + } +} + +bool SwTextFrame::CalcPrepFootnoteAdjust() +{ + OSL_ENSURE( HasFootnote(), "Who´s calling me?" ); + SwFootnoteBossFrame *pBoss = FindFootnoteBossFrame( true ); + const SwFootnoteFrame *pFootnote = pBoss->FindFirstFootnote( this ); + if (pFootnote && FTNPOS_CHAPTER != GetDoc().GetFootnoteInfo().m_ePos && + ( !pBoss->GetUpper()->IsSctFrame() || + !static_cast<SwSectionFrame*>(pBoss->GetUpper())->IsFootnoteAtEnd() ) ) + { + const SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont(); + bool bReArrange = true; + + SwRectFnSet aRectFnSet(this); + if ( pCont && aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), + aRectFnSet.GetBottom(getFrameArea()) ) > 0 ) + { + pBoss->RearrangeFootnotes( aRectFnSet.GetBottom(getFrameArea()), false, + pFootnote->GetAttr() ); + ValidateBodyFrame(); + ValidateFrame(); + pFootnote = pBoss->FindFirstFootnote( this ); + } + else + bReArrange = false; + if( !pCont || !pFootnote || bReArrange != (pFootnote->FindFootnoteBossFrame() == pBoss) ) + { + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); + SwTextFormatter aLine( this, &aInf ); + aLine.TruncLines(); + SetPara( nullptr ); // May be deleted! + ResetPreps(); + return false; + } + } + return true; +} + +/** + * Local helper function. Checks if nLower should be taken as the boundary + * for the footnote. + */ +static SwTwips lcl_GetFootnoteLower( const SwTextFrame* pFrame, SwTwips nLower ) +{ + // nLower is an absolute value. It denotes the bottom of the line + // containing the footnote. + SwRectFnSet aRectFnSet(pFrame); + + OSL_ENSURE( !pFrame->IsVertical() || !pFrame->IsSwapped(), + "lcl_GetFootnoteLower with swapped frame" ); + + SwTwips nAdd; + SwTwips nRet = nLower; + + // Check if text is inside a table. + if ( pFrame->IsInTab() ) + { + // If pFrame is inside a table, we have to check if + // a) The table is not allowed to split or + // b) The table row is not allowed to split + + // Inside a table, there are no footnotes, + // see SwFrame::FindFootnoteBossFrame. So we don't have to check + // the case that pFrame is inside a (footnote collecting) section + // within the table. + const SwFrame* pRow = pFrame; + while( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() ) + pRow = pRow->GetUpper(); + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + OSL_ENSURE( pTabFrame && pRow && + pRow->GetUpper()->IsTabFrame(), "Upper of row should be tab" ); + + const bool bDontSplit = !pTabFrame->IsFollow() && + !pTabFrame->IsLayoutSplitAllowed(); + + SwTwips nMin = 0; + if ( bDontSplit ) + nMin = aRectFnSet.GetBottom(pTabFrame->getFrameArea()); + else if ( !static_cast<const SwRowFrame*>(pRow)->IsRowSplitAllowed() ) + nMin = aRectFnSet.GetBottom(pRow->getFrameArea()); + + if ( nMin && aRectFnSet.YDiff( nMin, nLower ) > 0 ) + nRet = nMin; + + nAdd = aRectFnSet.GetBottomMargin(*pRow->GetUpper()); + } + else + nAdd = aRectFnSet.GetBottomMargin(*pFrame); + + if( nAdd > 0 ) + { + if ( aRectFnSet.IsVert() ) + nRet -= nAdd; + else + nRet += nAdd; + } + + // #i10770#: If there are fly frames anchored at previous paragraphs, + // the deadline should consider their lower borders. + const SwFrame* pStartFrame = pFrame->GetUpper()->GetLower(); + OSL_ENSURE( pStartFrame, "Upper has no lower" ); + SwTwips nFlyLower = aRectFnSet.IsVert() ? LONG_MAX : 0; + while ( pStartFrame != pFrame ) + { + OSL_ENSURE( pStartFrame, "Frame chain is broken" ); + if ( pStartFrame->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pStartFrame->GetDrawObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + SwRect aRect( pAnchoredObj->GetObjRect() ); + + if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr || + static_cast<SwFlyFrame*>(pAnchoredObj)->isFrameAreaDefinitionValid() ) + { + const SwTwips nBottom = aRectFnSet.GetBottom(aRect); + if ( aRectFnSet.YDiff( nBottom, nFlyLower ) > 0 ) + nFlyLower = nBottom; + } + } + } + + pStartFrame = pStartFrame->GetNext(); + } + + if ( aRectFnSet.IsVert() ) + nRet = std::min( nRet, nFlyLower ); + else + nRet = std::max( nRet, nFlyLower ); + + return nRet; +} + +SwTwips SwTextFrame::GetFootnoteLine( const SwTextFootnote *pFootnote ) const +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), + "SwTextFrame::GetFootnoteLine with swapped frame" ); + + SwTextFrame *pThis = const_cast<SwTextFrame*>(this); + + if( !HasPara() ) + { + // #109071# GetFormatted() does not work here, because most probably + // the frame is currently locked. We return the previous value. + return pThis->mnFootnoteLine > 0 ? + pThis->mnFootnoteLine : + IsVertical() ? getFrameArea().Left() : getFrameArea().Bottom(); + } + + SwTwips nRet; + { + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + + SwTextInfo aInf( pThis ); + SwTextIter aLine( pThis, &aInf ); + TextFrameIndex const nPos(MapModelToView( + &pFootnote->GetTextNode(), pFootnote->GetStart())); + aLine.CharToLine( nPos ); + + nRet = aLine.Y() + SwTwips(aLine.GetLineHeight()); + if( IsVertical() ) + nRet = SwitchHorizontalToVertical( nRet ); + } + + nRet = lcl_GetFootnoteLower( pThis, nRet ); + + pThis->mnFootnoteLine = nRet; + return nRet; +} + +/** + * Calculates the maximum reachable height for the TextFrame in the Footnote Area. + * The cell's bottom margin with the Footnote Reference limit's this height. + */ +SwTwips SwTextFrame::GetFootnoteFrameHeight_() const +{ + OSL_ENSURE( !IsFollow() && IsInFootnote(), "SwTextFrame::SetFootnoteLine: moon walk" ); + + const SwFootnoteFrame *pFootnoteFrame = FindFootnoteFrame(); + const SwTextFrame *pRef = static_cast<const SwTextFrame *>(pFootnoteFrame->GetRef()); + const SwFootnoteBossFrame *pBoss = FindFootnoteBossFrame(); + if( pBoss != pRef->FindFootnoteBossFrame( !pFootnoteFrame->GetAttr()-> + GetFootnote().IsEndNote() ) ) + return 0; + + SwSwapIfSwapped swap(const_cast<SwTextFrame *>(this)); + + SwTwips nHeight = pRef->IsInFootnoteConnect() ? + 1 : pRef->GetFootnoteLine( pFootnoteFrame->GetAttr() ); + if( nHeight ) + { + // As odd as it may seem: the first Footnote on the page may not touch the + // Footnote Reference, when entering text in the Footnote Area. + const SwFrame *pCont = pFootnoteFrame->GetUpper(); + + // Height within the Container which we're allowed to consume anyways + SwRectFnSet aRectFnSet(pCont); + SwTwips nTmp = aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pCont), + aRectFnSet.GetTop(getFrameArea()) ); + +#if OSL_DEBUG_LEVEL > 0 + if( nTmp < 0 ) + { + bool bInvalidPos = false; + const SwLayoutFrame* pTmp = GetUpper(); + while( !bInvalidPos && pTmp ) + { + bInvalidPos = !pTmp->isFrameAreaPositionValid() || + !pTmp->Lower()->isFrameAreaPositionValid(); + if( pTmp == pCont ) + break; + pTmp = pTmp->GetUpper(); + } + OSL_ENSURE( bInvalidPos, "Hanging below FootnoteCont" ); + } +#endif + + if ( aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), nHeight) > 0 ) + { + // Growth potential of the container + if ( !pRef->IsInFootnoteConnect() ) + { + SwSaveFootnoteHeight aSave( const_cast<SwFootnoteBossFrame*>(pBoss), nHeight ); + nHeight = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pCont))->Grow( LONG_MAX, true ); + } + else + nHeight = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pCont))->Grow( LONG_MAX, true ); + + nHeight += nTmp; + if( nHeight < 0 ) + nHeight = 0; + } + else + { // The container has to shrink + nTmp += aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), nHeight); + if( nTmp > 0 ) + nHeight = nTmp; + else + nHeight = 0; + } + } + + return nHeight; +} + +SwTextFrame *SwTextFrame::FindQuoVadisFrame() +{ + // Check whether we're in a FootnoteFrame + if( GetIndPrev() || !IsInFootnote() ) + return nullptr; + + // To the preceding FootnoteFrame + SwFootnoteFrame *pFootnoteFrame = FindFootnoteFrame()->GetMaster(); + if( !pFootnoteFrame ) + return nullptr; + + // Now the last Content + SwContentFrame *pCnt = pFootnoteFrame->ContainsContent(); + if( !pCnt ) + return nullptr; + SwContentFrame *pLast; + do + { pLast = pCnt; + pCnt = pCnt->GetNextContentFrame(); + } while( pCnt && pFootnoteFrame->IsAnLower( pCnt ) ); + return static_cast<SwTextFrame*>(pLast); +} + +void SwTextFrame::RemoveFootnote(TextFrameIndex const nStart, TextFrameIndex const nLen) +{ + if ( !IsFootnoteAllowed() ) + return; + + bool bRollBack = nLen != TextFrameIndex(COMPLETE_STRING); + TextFrameIndex nEnd; + SwTextFrame* pSource; + if( bRollBack ) + { + nEnd = nStart + nLen; + pSource = GetFollow(); + if( !pSource ) + return; + } + else + { + nEnd = TextFrameIndex(COMPLETE_STRING); + pSource = this; + } + + SwPageFrame* pUpdate = nullptr; + bool bRemove = false; + SwFootnoteBossFrame *pFootnoteBoss = nullptr; + SwFootnoteBossFrame *pEndBoss = nullptr; + bool bFootnoteEndDoc = FTNPOS_CHAPTER == GetDoc().GetFootnoteInfo().m_ePos; + SwTextNode const* pNode(nullptr); + sw::MergedAttrIterReverse iter(*this); + for (SwTextAttr const* pHt = iter.PrevAttr(&pNode); pHt; pHt = iter.PrevAttr(&pNode)) + { + if (RES_TXTATR_FTN != pHt->Which()) + continue; + + TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart())); + if (nStart > nIdx) + break; + + if (nEnd >= nIdx) + { + SwTextFootnote const*const pFootnote(static_cast<SwTextFootnote const*>(pHt)); + const bool bEndn = pFootnote->GetFootnote().IsEndNote(); + + if (bEndn) + { + if (!pEndBoss) + pEndBoss = pSource->FindFootnoteBossFrame(); + } + else + { + if (!pFootnoteBoss) + { + pFootnoteBoss = pSource->FindFootnoteBossFrame( true ); + if( pFootnoteBoss->GetUpper()->IsSctFrame() ) + { + SwSectionFrame* pSect = static_cast<SwSectionFrame*>( + pFootnoteBoss->GetUpper()); + if (pSect->IsFootnoteAtEnd()) + bFootnoteEndDoc = false; + } + } + } + + // We don't delete, but move instead. + // Three cases are to be considered: + // 1) There's neither Follow nor PrevFollow: + // -> RemoveFootnote() (maybe even a OSL_ENSURE(value)) + // + // 2) nStart > GetOffset, I have a Follow + // -> Footnote moves into Follow + // + // 3) nStart < GetOffset, I am a Follow + // -> Footnote moves into the PrevFollow + // + // Both need to be on one Page/in one Column + SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote(pSource, pFootnote); + + if (pFootnoteFrame) + { + const bool bEndDoc = bEndn || bFootnoteEndDoc; + if( bRollBack ) + { + while (pFootnoteFrame) + { + pFootnoteFrame->SetRef( this ); + pFootnoteFrame = pFootnoteFrame->GetFollow(); + SetFootnote( true ); + } + } + else if (GetFollow()) + { + SwContentFrame *pDest = GetFollow(); + while (pDest->GetFollow() && static_cast<SwTextFrame*>(pDest-> + GetFollow())->GetOffset() <= nIdx) + pDest = pDest->GetFollow(); + OSL_ENSURE( !SwFootnoteBossFrame::FindFootnote( + pDest,pFootnote),"SwTextFrame::RemoveFootnote: footnote exists"); + + // Never deregister; always move + if (bEndDoc || + !pFootnoteFrame->FindFootnoteBossFrame()->IsBefore(pDest->FindFootnoteBossFrame(!bEndn)) + ) + { + SwPageFrame* pTmp = pFootnoteFrame->FindPageFrame(); + if( pUpdate && pUpdate != pTmp ) + pUpdate->UpdateFootnoteNum(); + pUpdate = pTmp; + while ( pFootnoteFrame ) + { + pFootnoteFrame->SetRef( pDest ); + pFootnoteFrame = pFootnoteFrame->GetFollow(); + } + } + else + { + pFootnoteBoss->MoveFootnotes( this, pDest, pFootnote ); + bRemove = true; + } + static_cast<SwTextFrame*>(pDest)->SetFootnote( true ); + + OSL_ENSURE( SwFootnoteBossFrame::FindFootnote( pDest, + pFootnote),"SwTextFrame::RemoveFootnote: footnote ChgRef failed"); + } + else + { + if (!bEndDoc || ( bEndn && pEndBoss->IsInSct() && + !SwLayouter::Collecting( &GetDoc(), + pEndBoss->FindSctFrame(), nullptr ) )) + { + if( bEndn ) + pEndBoss->RemoveFootnote( this, pFootnote ); + else + pFootnoteBoss->RemoveFootnote( this, pFootnote ); + bRemove = bRemove || !bEndDoc; + OSL_ENSURE( !SwFootnoteBossFrame::FindFootnote( this, pFootnote ), + "SwTextFrame::RemoveFootnote: can't get off that footnote" ); + } + } + } + } + } + if (pUpdate) + pUpdate->UpdateFootnoteNum(); + + // We break the oscillation + if (bRemove && !bFootnoteEndDoc && HasPara()) + { + ValidateBodyFrame(); + ValidateFrame(); + } + + // We call the RemoveFootnote from within the FindBreak, because the last line is + // to be passed to the Follow. The Offset of the Follow is, however, outdated; + // it'll be set soon. CalcFntFlag depends on a correctly set Follow Offset. + // Therefore we temporarily calculate the Follow Offset here + TextFrameIndex nOldOfst(COMPLETE_STRING); + if( HasFollow() && nStart > GetOffset() ) + { + nOldOfst = GetFollow()->GetOffset(); + GetFollow()->ManipOfst(nStart + (bRollBack ? nLen : TextFrameIndex(0))); + } + pSource->CalcFootnoteFlag(); + if (nOldOfst < TextFrameIndex(COMPLETE_STRING)) + GetFollow()->ManipOfst( nOldOfst ); +} + + +/** + * We basically only have two possibilities: + * + * a) The Footnote is already present + * => we move it, if another pSrcFrame has been found + * + * b) The Footnote is not present + * => we have it created for us + * + * Whether the Footnote ends up on our Page/Column, doesn't matter in this + * context. + * + * Optimization for Endnotes. + * + * Another problem: if the Deadline falls within the Footnote Area, we need + * to move the Footnote. + * + * @returns false on any type of error + */ +void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDeadLine ) +{ + OSL_ENSURE( !IsVertical() || !IsSwapped(), + "SwTextFrame::ConnectFootnote with swapped frame" ); + + mbFootnote = true; + mbInFootnoteConnect = true; // Just reset! + // See if pFootnote is an endnote on a separate endnote page. + const IDocumentSettingAccess& rSettings = GetDoc().getIDocumentSettingAccess(); + const bool bContinuousEndnotes = rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES); + const bool bEnd = pFootnote->GetFootnote().IsEndNote(); + + // We want to store this value, because it is needed as a fallback + // in GetFootnoteLine(), if there is no paragraph information available + mnFootnoteLine = nDeadLine; + + // We always need a parent (Page/Column) + SwSectionFrame *pSect; + SwContentFrame *pContent = this; + if( bEnd && IsInSct() ) + { + pSect = FindSctFrame(); + if( pSect->IsEndnAtEnd() ) + pContent = pSect->FindLastContent( SwFindMode::EndNote ); + if( !pContent ) + pContent = this; + } + + SwFootnoteBossFrame *pBoss = pContent->FindFootnoteBossFrame( !bEnd ); + + pSect = pBoss->FindSctFrame(); + bool bDocEnd = bEnd ? !( pSect && pSect->IsEndnAtEnd() ) : + ( !( pSect && pSect->IsFootnoteAtEnd() ) && + FTNPOS_CHAPTER == GetDoc().GetFootnoteInfo().m_ePos); + + // Footnote can be registered with the Follow + SwContentFrame *pSrcFrame = FindFootnoteRef( pFootnote ); + + if( bDocEnd ) + { + if ((pSect || bContinuousEndnotes) && pSrcFrame) + { + SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ); + if (pFootnoteFrame && (pFootnoteFrame->IsInSct() || bContinuousEndnotes)) + { + // We either have a foot/endnote that goes to the end of the section or are in Word + // compatibility mode where endnotes go to the end of the document. Handle both + // cases by removing the footnote here, then later appending them to the correct + // last page of the document or section. + pBoss->RemoveFootnote( pSrcFrame, pFootnote ); + pSrcFrame = nullptr; + } + } + } + else if( bEnd && pSect ) + { + SwFootnoteFrame *pFootnoteFrame = pSrcFrame ? SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ) : nullptr; + if( pFootnoteFrame && !pFootnoteFrame->GetUpper() ) + pFootnoteFrame = nullptr; + SwDoc *const pDoc = &GetDoc(); + if( SwLayouter::Collecting( pDoc, pSect, pFootnoteFrame ) ) + { + if( !pSrcFrame ) + { + SwFootnoteFrame *pNew = new SwFootnoteFrame(pDoc->GetDfltFrameFormat(),this,this,pFootnote); + SwNodeIndex aIdx( *pFootnote->GetStartNode(), 1 ); + ::InsertCnt_( pNew, pDoc, aIdx.GetIndex() ); + pDoc->getIDocumentLayoutAccess().GetLayouter()->CollectEndnote( pNew ); + } + else if( pSrcFrame != this ) + SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this ); + mbInFootnoteConnect = false; + return; + } + else if( pSrcFrame ) + { + SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame(); + if( !pFootnoteBoss->IsInSct() || + pFootnoteBoss->ImplFindSctFrame()->GetSection()!=pSect->GetSection() ) + { + pBoss->RemoveFootnote( pSrcFrame, pFootnote ); + pSrcFrame = nullptr; + } + } + } + + if( bDocEnd || bEnd ) + { + if( !pSrcFrame ) + pBoss->AppendFootnote( this, pFootnote ); + else if( pSrcFrame != this ) + SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this ); + mbInFootnoteConnect = false; + return; + } + + SwSaveFootnoteHeight aHeight( pBoss, nDeadLine ); + + if( !pSrcFrame ) // No Footnote was found at all + pBoss->AppendFootnote( this, pFootnote ); + else + { + SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ); + SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame(); + + bool bBrutal = false; + + if( pFootnoteBoss == pBoss ) // Ref and Footnote are on the same Page/Column + { + SwFrame *pCont = pFootnoteFrame->GetUpper(); + + SwRectFnSet aRectFnSet(pCont); + long nDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), + nDeadLine ); + + if( nDiff >= 0 ) + { + // If the Footnote has been registered to a Follow, we need to + // rewire it now too + if ( pSrcFrame != this ) + SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this ); + + // We have some room left, so the Footnote can grow + if ( pFootnoteFrame->GetFollow() && nDiff > 0 ) + { + SwFrameDeleteGuard aDeleteGuard(pCont); + SwTwips nHeight = aRectFnSet.GetHeight(pCont->getFrameArea()); + pBoss->RearrangeFootnotes( nDeadLine, false, pFootnote ); + ValidateBodyFrame(); + ValidateFrame(); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( pSh && nHeight == aRectFnSet.GetHeight(pCont->getFrameArea()) ) + // So that we don't miss anything + pSh->InvalidateWindows( pCont->getFrameArea() ); + } + mbInFootnoteConnect = false; + return; + } + else + bBrutal = true; + } + else + { + // Ref and Footnote are not on one Page; attempt to move is necessary + SwFrame* pTmp = this; + while( pTmp->GetNext() && pSrcFrame != pTmp ) + pTmp = pTmp->GetNext(); + if( pSrcFrame == pTmp ) + bBrutal = true; + else + { // If our Parent is in a column Area, but the Page already has a + // FootnoteContainer, we can only brute force it + if( pSect && pSect->FindFootnoteBossFrame( !bEnd )->FindFootnoteCont() ) + bBrutal = true; + + else if ( !pFootnoteFrame->GetPrev() || + pFootnoteBoss->IsBefore( pBoss ) + ) + { + SwFootnoteBossFrame *pSrcBoss = pSrcFrame->FindFootnoteBossFrame( !bEnd ); + pSrcBoss->MoveFootnotes( pSrcFrame, this, pFootnote ); + } + else + SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this ); + } + } + + // The brute force method: Remove Footnote and append. + // We need to call SetFootnoteDeadLine(), as we can more easily adapt the + // nMaxFootnoteHeight after RemoveFootnote + if( bBrutal ) + { + pBoss->RemoveFootnote( pSrcFrame, pFootnote, false ); + std::unique_ptr<SwSaveFootnoteHeight> pHeight(bEnd ? nullptr : new SwSaveFootnoteHeight( pBoss, nDeadLine )); + pBoss->AppendFootnote( this, pFootnote ); + } + } + + // In column Areas, that not yet reach the Page's border a RearrangeFootnotes is not + // useful yet, as the Footnote container has not yet been calculated + if( !pSect || !pSect->Growable() ) + { + // Validate environment, to avoid oscillation + SwSaveFootnoteHeight aNochmal( pBoss, nDeadLine ); + ValidateBodyFrame(); + pBoss->RearrangeFootnotes( nDeadLine, true ); + ValidateFrame(); + } + else if( pSect->IsFootnoteAtEnd() ) + { + ValidateBodyFrame(); + ValidateFrame(); + } + + mbInFootnoteConnect = false; +} + +/** + * The portion for the Footnote Reference in the Text + */ +SwFootnotePortion *SwTextFormatter::NewFootnotePortion( SwTextFormatInfo &rInf, + SwTextAttr *pHint ) +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "NewFootnotePortion with unswapped frame" ); + + if( !m_pFrame->IsFootnoteAllowed() ) + return nullptr; + + SwTextFootnote *pFootnote = static_cast<SwTextFootnote*>(pHint); + const SwFormatFootnote& rFootnote = pFootnote->GetFootnote(); + SwDoc *const pDoc = &m_pFrame->GetDoc(); + + if( rInf.IsTest() ) + return new SwFootnotePortion(rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame()), pFootnote); + + SwSwapIfSwapped swap(m_pFrame); + + sal_uInt16 nReal; + { + sal_uInt16 nOldReal = m_pCurr->GetRealHeight(); + sal_uInt16 nOldAscent = m_pCurr->GetAscent(); + sal_uInt16 nOldHeight = m_pCurr->Height(); + CalcRealHeight(); + nReal = m_pCurr->GetRealHeight(); + if( nReal < nOldReal ) + nReal = nOldReal; + m_pCurr->SetRealHeight( nOldReal ); + m_pCurr->Height( nOldHeight ); + m_pCurr->SetAscent( nOldAscent ); + } + + SwTwips nLower = Y() + nReal; + + const bool bVertical = m_pFrame->IsVertical(); + if( bVertical ) + nLower = m_pFrame->SwitchHorizontalToVertical( nLower ); + + nLower = lcl_GetFootnoteLower( m_pFrame, nLower ); + + // We just refresh. + // The Connect does not do anything useful in this case, but will + // mostly throw away the Footnote and create it anew. + if( !rInf.IsQuick() ) + m_pFrame->ConnectFootnote( pFootnote, nLower ); + + SwTextFrame *pScrFrame = m_pFrame->FindFootnoteRef( pFootnote ); + SwFootnoteBossFrame *pBoss = m_pFrame->FindFootnoteBossFrame( !rFootnote.IsEndNote() ); + SwFootnoteFrame *pFootnoteFrame = nullptr; + if( pScrFrame ) + pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pScrFrame, pFootnote ); + + // We see whether our Append has caused some Footnote to + // still be on the Page/Column. If not, our line disappears too, + // which will lead to the following undesired behaviour: + // Footnote1 still fits onto the Page/Column, but Footnote2 doesn't. + // The Footnote2 Reference remains on the Page/Column. The Footnote itself + // is on the next Page/Column. + // + // Exception: If the Page/Column cannot accommodate another line, + // the Footnote Reference should be moved to the next one. + if( !rFootnote.IsEndNote() ) + { + SwSectionFrame *pSct = pBoss->FindSctFrame(); + bool bAtSctEnd = pSct && pSct->IsFootnoteAtEnd(); + if( FTNPOS_CHAPTER != pDoc->GetFootnoteInfo().m_ePos || bAtSctEnd ) + { + SwFrame* pFootnoteCont = pBoss->FindFootnoteCont(); + // If the Parent is within an Area, it can only be a Column of this + // Area. If this one is not the first Column, we can avoid it. + if( !m_pFrame->IsInTab() && ( GetLineNr() > 1 || m_pFrame->GetPrev() || + ( !bAtSctEnd && m_pFrame->GetIndPrev() ) || + ( pSct && pBoss->GetPrev() ) ) ) + { + if( !pFootnoteCont ) + { + rInf.SetStop( true ); + return nullptr; + } + else + { + // There must not be any Footnote Containers in column Areas and at the same time on the + // Page/Page column + if( pSct && !bAtSctEnd ) // Is the Container in a (column) Area? + { + SwFootnoteBossFrame* pTmp = pBoss->FindSctFrame()->FindFootnoteBossFrame( true ); + SwFootnoteContFrame* pFootnoteC = pTmp->FindFootnoteCont(); + if( pFootnoteC ) + { + SwFootnoteFrame* pTmpFrame = static_cast<SwFootnoteFrame*>(pFootnoteC->Lower()); + if( pTmpFrame && *pTmpFrame < pFootnote ) + { + rInf.SetStop( true ); + return nullptr; + } + } + } + // Is this the last Line that fits? + SwTwips nTmpBot = Y() + nReal * 2; + + if( bVertical ) + nTmpBot = m_pFrame->SwitchHorizontalToVertical( nTmpBot ); + + SwRectFnSet aRectFnSet(pFootnoteCont); + + const long nDiff = aRectFnSet.YDiff( + aRectFnSet.GetTop(pFootnoteCont->getFrameArea()), + nTmpBot ); + + if( pScrFrame && nDiff < 0 ) + { + if( pFootnoteFrame ) + { + SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame(); + if( pFootnoteBoss != pBoss ) + { + // We're in the last Line and the Footnote has moved + // to another Page. We also want to be on that Page! + rInf.SetStop( true ); + return nullptr; + } + } + } + } + } + } + } + // Finally: Create FootnotePortion and exit ... + SwFootnotePortion *pRet = new SwFootnotePortion( + rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame()), + pFootnote, nReal ); + rInf.SetFootnoteInside( true ); + + return pRet; +} + +/** + * The portion for the Footnote Numbering in the Footnote Area + */ +SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( SwTextFormatInfo const &rInf ) const +{ + OSL_ENSURE( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() && !rInf.IsFootnoteDone(), + "This is the wrong place for a ftnnumber" ); + if( rInf.GetTextStart() != m_nStart || + rInf.GetTextStart() != rInf.GetIdx() ) + return nullptr; + + const SwFootnoteFrame* pFootnoteFrame = m_pFrame->FindFootnoteFrame(); + const SwTextFootnote* pFootnote = pFootnoteFrame->GetAttr(); + + // Aha! So we're in the Footnote Area! + SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pFootnote->GetFootnote()); + + SwDoc *const pDoc = &m_pFrame->GetDoc(); + OUString aFootnoteText(rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame(), true)); + + const SwEndNoteInfo* pInfo; + if( rFootnote.IsEndNote() ) + pInfo = &pDoc->GetEndNoteInfo(); + else + pInfo = &pDoc->GetFootnoteInfo(); + + const SwAttrSet* pParSet = &rInf.GetCharAttr(); + const IDocumentSettingAccess* pIDSA = &pDoc->getIDocumentSettingAccess(); + std::unique_ptr<SwFont> pNumFnt(new SwFont( pParSet, pIDSA )); + + // #i37142# + // Underline style of paragraph font should not be considered + // Overline style of paragraph font should not be considered + // Weight style of paragraph font should not be considered + // Posture style of paragraph font should not be considered + // See also #i18463# and SwTextFormatter::NewNumberPortion() + pNumFnt->SetUnderline( LINESTYLE_NONE ); + pNumFnt->SetOverline( LINESTYLE_NONE ); + pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin ); + pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK ); + pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL ); + pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin ); + pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK ); + pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL ); + + const auto xAnchor = rFootnote.getAnchor(*pDoc); + uno::Reference<beans::XPropertySet> xAnchorProps(xAnchor, uno::UNO_QUERY); + if (xAnchorProps.is()) + { + auto aAny = xAnchorProps->getPropertyValue("CharFontCharSet"); + sal_Int16 eCharSet; + if ((aAny >>= eCharSet) && eCharSet == awt::CharSet::SYMBOL) + { + OUString aFontName; + aAny = xAnchorProps->getPropertyValue("CharFontName"); + if (aAny >>= aFontName) + { + pNumFnt->SetName(aFontName, SwFontScript::Latin); + pNumFnt->SetName(aFontName, SwFontScript::CJK); + pNumFnt->SetName(aFontName, SwFontScript::CTL); + pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::Latin); + pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::CJK); + pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::CTL); + } + } + } + + const SwAttrSet& rSet = pInfo->GetCharFormat(*pDoc)->GetAttrSet(); + pNumFnt->SetDiffFnt(&rSet, pIDSA ); + pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() ); + + SwFootnoteNumPortion* pNewPor = new SwFootnoteNumPortion( aFootnoteText, std::move(pNumFnt) ); + pNewPor->SetLeft( !m_pFrame->IsRightToLeft() ); + return pNewPor; +} + +static OUString lcl_GetPageNumber( const SwPageFrame* pPage ) +{ + OSL_ENSURE( pPage, "GetPageNumber: Homeless TextFrame" ); + const sal_uInt16 nVirtNum = pPage->GetVirtPageNum(); + const SvxNumberType& rNum = pPage->GetPageDesc()->GetNumType(); + return rNum.GetNumStr( nVirtNum ); +} + +SwErgoSumPortion *SwTextFormatter::NewErgoSumPortion( SwTextFormatInfo const &rInf ) const +{ + // We cannot assume we're a Follow + if( !m_pFrame->IsInFootnote() || m_pFrame->GetPrev() || + rInf.IsErgoDone() || rInf.GetIdx() != m_pFrame->GetOffset() || + m_pFrame->ImplFindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() ) + return nullptr; + + // we are in the footnote container + const SwFootnoteInfo &rFootnoteInfo = m_pFrame->GetDoc().GetFootnoteInfo(); + SwTextFrame *pQuoFrame = m_pFrame->FindQuoVadisFrame(); + if( !pQuoFrame ) + return nullptr; + const SwPageFrame* pPage = m_pFrame->FindPageFrame(); + const SwPageFrame* pQuoPage = pQuoFrame->FindPageFrame(); + if( pPage == pQuoFrame->FindPageFrame() ) + return nullptr; // If the QuoVadis is on the same Column/Page + const OUString aPage = lcl_GetPageNumber( pPage ); + SwParaPortion *pPara = pQuoFrame->GetPara(); + if( pPara ) + pPara->SetErgoSumNum( aPage ); + if( rFootnoteInfo.m_aErgoSum.isEmpty() ) + return nullptr; + SwErgoSumPortion *pErgo = new SwErgoSumPortion( rFootnoteInfo.m_aErgoSum, + lcl_GetPageNumber( pQuoPage ) ); + return pErgo; +} + +TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || ! m_pFrame->IsSwapped(), + "SwTextFormatter::FormatQuoVadis with swapped frame" ); + + if( !m_pFrame->IsInFootnote() || m_pFrame->ImplFindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() ) + return nOffset; + + const SwFrame* pErgoFrame = m_pFrame->FindFootnoteFrame()->GetFollow(); + if( !pErgoFrame && m_pFrame->HasFollow() ) + pErgoFrame = m_pFrame->GetFollow(); + if( !pErgoFrame ) + return nOffset; + + if( pErgoFrame == m_pFrame->GetNext() ) + { + SwFrame *pCol = m_pFrame->FindColFrame(); + while( pCol && !pCol->GetNext() ) + pCol = pCol->GetUpper()->FindColFrame(); + if( pCol ) + return nOffset; + } + else + { + const SwPageFrame* pPage = m_pFrame->FindPageFrame(); + const SwPageFrame* pErgoPage = pErgoFrame->FindPageFrame(); + if( pPage == pErgoPage ) + return nOffset; // If the ErgoSum is on the same Page + } + + SwTextFormatInfo &rInf = GetInfo(); + const SwFootnoteInfo &rFootnoteInfo = m_pFrame->GetDoc().GetFootnoteInfo(); + if( rFootnoteInfo.m_aQuoVadis.isEmpty() ) + return nOffset; + + // A remark on QuoVadis/ErgoSum: + // We use the Font set for the Paragraph for these texts. + // Thus, we initialize: + // TODO: ResetFont(); + FeedInf( rInf ); + SeekStartAndChg( rInf, true ); + if( GetRedln() && m_pCurr->HasRedline() ) + { + std::pair<SwTextNode const*, sal_Int32> const pos( + GetTextFrame()->MapViewToModel(nOffset)); + GetRedln()->Seek(*m_pFont, pos.first->GetIndex(), pos.second, 0); + } + + // A tricky special case: Flyfrms extend into the Line and are at the + // position we want to insert the Quovadis text + // Let's see if it is that bad indeed: + SwLinePortion *pPor = m_pCurr->GetFirstPortion(); + sal_uInt16 nLastLeft = 0; + while( pPor ) + { + if ( pPor->IsFlyPortion() ) + nLastLeft = static_cast<SwFlyPortion*>(pPor)->GetFix() + + static_cast<SwFlyPortion*>(pPor)->Width(); + pPor = pPor->GetNextPortion(); + } + + // The old game all over again: we want the Line to wrap around + // at a certain point, so we adjust the width. + // nLastLeft is now basically the right margin + const sal_uInt16 nOldRealWidth = rInf.RealWidth(); + rInf.RealWidth( nOldRealWidth - nLastLeft ); + + OUString aErgo = lcl_GetPageNumber( pErgoFrame->FindPageFrame() ); + SwQuoVadisPortion *pQuo = new SwQuoVadisPortion(rFootnoteInfo.m_aQuoVadis, aErgo ); + pQuo->SetAscent( rInf.GetAscent() ); + pQuo->Height( rInf.GetTextHeight() ); + pQuo->Format( rInf ); + sal_uInt16 nQuoWidth = pQuo->Width(); + SwLinePortion* pCurrPor = pQuo; + + while ( rInf.GetRest() ) + { + SwLinePortion* pFollow = rInf.GetRest(); + rInf.SetRest( nullptr ); + pCurrPor->Move( rInf ); + + OSL_ENSURE( pFollow->IsQuoVadisPortion(), + "Quo Vadis, rest of QuoVadisPortion" ); + + // format the rest and append it to the other QuoVadis parts + pFollow->Format( rInf ); + nQuoWidth = nQuoWidth + pFollow->Width(); + + pCurrPor->Append( pFollow ); + pCurrPor = pFollow; + } + + Right( Right() - nQuoWidth ); + + TextFrameIndex nRet; + { + SwSwapIfNotSwapped swap(m_pFrame); + + nRet = FormatLine( m_nStart ); + } + + Right( rInf.Left() + nOldRealWidth - 1 ); + + nLastLeft = nOldRealWidth - m_pCurr->Width(); + FeedInf( rInf ); + + // It's possible that there's a Margin Portion at the end, which would + // just cause a lot of trouble, when respanning + pPor = m_pCurr->FindLastPortion(); + SwGluePortion *pGlue = pPor->IsMarginPortion() ? static_cast<SwMarginPortion*>(pPor) : nullptr; + if( pGlue ) + { + pGlue->Height( 0 ); + pGlue->Width( 0 ); + pGlue->SetLen(TextFrameIndex(0)); + pGlue->SetAscent( 0 ); + pGlue->SetNextPortion( nullptr ); + pGlue->SetFixWidth(0); + } + + // Luxury: We make sure the QuoVadis text appears on the right, by + // using Glues. + nLastLeft = nLastLeft - nQuoWidth; + if( nLastLeft ) + { + if( nLastLeft > pQuo->GetAscent() ) // Minimum distance + { + switch( GetAdjust() ) + { + case SvxAdjust::Block: + { + if( !m_pCurr->GetLen() || + CH_BREAK != GetInfo().GetChar(m_nStart + m_pCurr->GetLen() - TextFrameIndex(1))) + nLastLeft = pQuo->GetAscent(); + nQuoWidth = nQuoWidth + nLastLeft; + break; + } + case SvxAdjust::Right: + { + nLastLeft = pQuo->GetAscent(); + nQuoWidth = nQuoWidth + nLastLeft; + break; + } + case SvxAdjust::Center: + { + nQuoWidth = nQuoWidth + pQuo->GetAscent(); + long nDiff = nLastLeft - nQuoWidth; + if( nDiff < 0 ) + { + nLastLeft = pQuo->GetAscent(); + nQuoWidth = static_cast<sal_uInt16>(-nDiff + nLastLeft); + } + else + { + nQuoWidth = 0; + nLastLeft = sal_uInt16(( pQuo->GetAscent() + nDiff ) / 2); + } + break; + } + default: + nQuoWidth = nQuoWidth + nLastLeft; + } + } + else + nQuoWidth = nQuoWidth + nLastLeft; + if( nLastLeft ) + { + pGlue = new SwGluePortion(0); + pGlue->Width( nLastLeft ); + pPor->Append( pGlue ); + pPor = pPor->GetNextPortion(); + } + } + + // Finally: we insert the QuoVadis Portion + pCurrPor = pQuo; + while ( pCurrPor ) + { + // pPor->Append deletes the pPortion pointer of pPor. + // Therefore we have to keep a pointer to the next portion + pQuo = static_cast<SwQuoVadisPortion*>(pCurrPor->GetNextPortion()); + pPor->Append( pCurrPor ); + pPor = pPor->GetNextPortion(); + pCurrPor = pQuo; + } + + m_pCurr->Width( m_pCurr->Width() + nQuoWidth ); + + // And adjust again, due to the adjustment and due to the following special + // case: + // The DummyUser has set a smaller Font in the Line than the one used + // by the QuoVadis text ... + CalcAdjustLine( m_pCurr ); + + return nRet; +} + +/** + * This function creates a Line that reaches to the other Page Margin. + * DummyLines or DummyPortions make sure, that oscillations stop, because + * there's no way to flow back. + * They are used for Footnotes in paragraph-bound Frames and for Footnote + * oscillations + */ +void SwTextFormatter::MakeDummyLine() +{ + sal_uInt16 nRstHeight = GetFrameRstHeight(); + if( m_pCurr && nRstHeight > m_pCurr->Height() ) + { + SwLineLayout *pLay = new SwLineLayout; + nRstHeight = nRstHeight - m_pCurr->Height(); + pLay->Height( nRstHeight ); + pLay->SetAscent( nRstHeight ); + Insert( pLay ); + Next(); + } +} + +namespace { + +class SwFootnoteSave +{ + SwTextSizeInfo *pInf; + SwFont *pFnt; + std::unique_ptr<SwFont> pOld; + + SwFootnoteSave(const SwFootnoteSave&) = delete; + SwFootnoteSave& operator=(const SwFootnoteSave&) = delete; + +public: + SwFootnoteSave( const SwTextSizeInfo &rInf, + const SwTextFootnote *pTextFootnote, + const bool bApplyGivenScriptType, + const SwFontScript nGivenScriptType ); + ~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE; +}; + +} + +SwFootnoteSave::SwFootnoteSave( const SwTextSizeInfo &rInf, + const SwTextFootnote* pTextFootnote, + const bool bApplyGivenScriptType, + const SwFontScript nGivenScriptType ) + : pInf( &const_cast<SwTextSizeInfo&>(rInf) ) + , pFnt( nullptr ) +{ + if( pTextFootnote && rInf.GetTextFrame() ) + { + pFnt = const_cast<SwTextSizeInfo&>(rInf).GetFont(); + pOld.reset( new SwFont( *pFnt ) ); + pOld->GetTox() = pFnt->GetTox(); + pFnt->GetTox() = 0; + SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote()); + const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc(); + + // #i98418# + if ( bApplyGivenScriptType ) + { + pFnt->SetActual( nGivenScriptType ); + } + else + { + // examine text and set script + OUString aTmpStr(rFootnote.GetViewNumStr(*pDoc, rInf.GetTextFrame()->getRootFrame())); + pFnt->SetActual( SwScriptInfo::WhichFont(0, aTmpStr) ); + } + + const SwEndNoteInfo* pInfo; + if( rFootnote.IsEndNote() ) + pInfo = &pDoc->GetEndNoteInfo(); + else + pInfo = &pDoc->GetFootnoteInfo(); + const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet(); + pFnt->SetDiffFnt( &rSet, &pDoc->getIDocumentSettingAccess() ); + + // we reduce footnote size, if we are inside a double line portion + if ( ! pOld->GetEscapement() && 50 == pOld->GetPropr() ) + { + Size aSize = pFnt->GetSize( pFnt->GetActual() ); + pFnt->SetSize( Size( aSize.Width() / 2, + aSize.Height() / 2 ), + pFnt->GetActual() ); + } + + // set the correct rotation at the footnote font + const SfxPoolItem* pItem; + if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE, + true, &pItem )) + pFnt->SetVertical( static_cast<const SvxCharRotateItem*>(pItem)->GetValue(), + rInf.GetTextFrame()->IsVertical() ); + + pFnt->ChgPhysFnt( pInf->GetVsh(), *pInf->GetOut() ); + + if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_BACKGROUND, + true, &pItem )) + pFnt->SetBackColor( new Color( static_cast<const SvxBrushItem*>(pItem)->GetColor() ) ); + } + else + pFnt = nullptr; +} + +SwFootnoteSave::~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE +{ + if( pFnt ) + { + // Put back SwFont + *pFnt = *pOld; + pFnt->GetTox() = pOld->GetTox(); + pFnt->ChgPhysFnt( pInf->GetVsh(), *pInf->GetOut() ); + pOld.reset(); + } +} + +SwFootnotePortion::SwFootnotePortion( const OUString &rExpand, + SwTextFootnote *pFootn, sal_uInt16 nReal ) + : SwFieldPortion( rExpand, nullptr ) + , pFootnote(pFootn) + , nOrigHeight( nReal ) + // #i98418# + , mbPreferredScriptTypeSet( false ) + , mnPreferredScriptType( SwFontScript::Latin ) +{ + SetLen(TextFrameIndex(1)); + SetWhichPor( PortionType::Footnote ); +} + +bool SwFootnotePortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const +{ + rText = m_aExpand; + return true; +} + +bool SwFootnotePortion::Format( SwTextFormatInfo &rInf ) +{ + // #i98418# +// SwFootnoteSave aFootnoteSave( rInf, pFootnote ); + SwFootnoteSave aFootnoteSave( rInf, pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType ); + // the idx is manipulated in SwExpandPortion::Format + // this flag indicates, that a footnote is allowed to trigger + // an underflow during SwTextGuess::Guess + rInf.SetFakeLineStart( rInf.GetIdx() > rInf.GetLineStart() ); + const bool bFull = SwFieldPortion::Format( rInf ); + rInf.SetFakeLineStart( false ); + SetAscent( rInf.GetAscent() ); + Height( rInf.GetTextHeight() ); + rInf.SetFootnoteDone( !bFull ); + if( !bFull ) + rInf.SetParaFootnote(); + return bFull; +} + +void SwFootnotePortion::Paint( const SwTextPaintInfo &rInf ) const +{ + // #i98418# +// SwFootnoteSave aFootnoteSave( rInf, pFootnote ); + SwFootnoteSave aFootnoteSave( rInf, pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType ); + rInf.DrawViewOpt( *this, PortionType::Footnote ); + SwExpandPortion::Paint( rInf ); +} + +SwPosSize SwFootnotePortion::GetTextSize( const SwTextSizeInfo &rInfo ) const +{ + // #i98418# +// SwFootnoteSave aFootnoteSave( rInfo, pFootnote ); + SwFootnoteSave aFootnoteSave( rInfo, pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType ); + return SwExpandPortion::GetTextSize( rInfo ); +} + +// #i98418# +void SwFootnotePortion::SetPreferredScriptType( SwFontScript nPreferredScriptType ) +{ + mbPreferredScriptTypeSet = true; + mnPreferredScriptType = nPreferredScriptType; +} + +SwFieldPortion *SwQuoVadisPortion::Clone( const OUString &rExpand ) const +{ + return new SwQuoVadisPortion( rExpand, aErgo ); +} + +SwQuoVadisPortion::SwQuoVadisPortion( const OUString &rExp, const OUString& rStr ) + : SwFieldPortion( rExp ), aErgo(rStr) +{ + SetLen(TextFrameIndex(0)); + SetWhichPor( PortionType::QuoVadis ); +} + +bool SwQuoVadisPortion::Format( SwTextFormatInfo &rInf ) +{ + // First try; maybe the Text fits + CheckScript( rInf ); + bool bFull = SwFieldPortion::Format( rInf ); + SetLen(TextFrameIndex(0)); + + if( bFull ) + { + // Second try; we make the String shorter + m_aExpand = "..."; + bFull = SwFieldPortion::Format( rInf ); + SetLen(TextFrameIndex(0)); + if( bFull ) + // Third try; we're done: we crush + Width( sal_uInt16(rInf.Width() - rInf.X()) ); + + // No multiline Fields for QuoVadis and ErgoSum + if( rInf.GetRest() ) + { + delete rInf.GetRest(); + rInf.SetRest( nullptr ); + } + } + return bFull; +} + +bool SwQuoVadisPortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const +{ + rText = m_aExpand; + // if this QuoVadisPortion has a follow, the follow is responsible for + // the ergo text. + if ( ! HasFollow() ) + rText += aErgo; + return true; +} + +void SwQuoVadisPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), m_aExpand + aErgo, GetWhichPor() ); +} + +void SwQuoVadisPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + // We _always_ want to output per DrawStretchText, because nErgo + // can quickly switch + if( PrtWidth() ) + { + rInf.DrawViewOpt( *this, PortionType::QuoVadis ); + SwTextSlot aDiffText( &rInf, this, true, false ); + SwFontSave aSave( rInf, m_pFont.get() ); + rInf.DrawText( *this, rInf.GetLen(), true ); + } +} + +SwFieldPortion *SwErgoSumPortion::Clone( const OUString &rExpand ) const +{ + return new SwErgoSumPortion( rExpand, OUString() ); +} + +SwErgoSumPortion::SwErgoSumPortion(const OUString &rExp, const OUString& rStr) + : SwFieldPortion( rExp ) +{ + SetLen(TextFrameIndex(0)); + m_aExpand += rStr; + + // One blank distance to the text + m_aExpand += " "; + SetWhichPor( PortionType::ErgoSum ); +} + +TextFrameIndex SwErgoSumPortion::GetModelPositionForViewPoint(const sal_uInt16) const +{ + return TextFrameIndex(0); +} + +bool SwErgoSumPortion::Format( SwTextFormatInfo &rInf ) +{ + const bool bFull = SwFieldPortion::Format( rInf ); + SetLen(TextFrameIndex(0)); + rInf.SetErgoDone( true ); + + // No multiline Fields for QuoVadis and ErgoSum + if( bFull && rInf.GetRest() ) + { + delete rInf.GetRest(); + rInf.SetRest( nullptr ); + } + + // We return false in order to get some text into the current line, + // even if it's full (better than looping) + return false; +} + +void SwParaPortion::SetErgoSumNum( const OUString& rErgo ) +{ + SwLineLayout *pLay = this; + while( pLay->GetNext() ) + { + pLay = pLay->GetNext(); + } + SwLinePortion *pPor = pLay; + SwQuoVadisPortion *pQuo = nullptr; + while( pPor && !pQuo ) + { + if ( pPor->IsQuoVadisPortion() ) + pQuo = static_cast<SwQuoVadisPortion*>(pPor); + pPor = pPor->GetNextPortion(); + } + if( pQuo ) + pQuo->SetNumber( rErgo ); +} + +/** + * Is called in SwTextFrame::Prepare() + */ +bool SwParaPortion::UpdateQuoVadis( const OUString &rQuo ) +{ + SwLineLayout *pLay = this; + while( pLay->GetNext() ) + { + pLay = pLay->GetNext(); + } + SwLinePortion *pPor = pLay; + SwQuoVadisPortion *pQuo = nullptr; + while( pPor && !pQuo ) + { + if ( pPor->IsQuoVadisPortion() ) + pQuo = static_cast<SwQuoVadisPortion*>(pPor); + pPor = pPor->GetNextPortion(); + } + + if( !pQuo ) + return false; + + return pQuo->GetQuoText() == rQuo; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txthyph.cxx b/sw/source/core/text/txthyph.cxx new file mode 100644 index 000000000..215cc019b --- /dev/null +++ b/sw/source/core/text/txthyph.cxx @@ -0,0 +1,572 @@ +/* -*- 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 <breakit.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <viewopt.hxx> +#include <viewsh.hxx> +#include <SwPortionHandler.hxx> +#include "porhyph.hxx" +#include "inftxt.hxx" +#include "itrform2.hxx" +#include "guess.hxx" +#include <rootfrm.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; +using namespace ::com::sun::star::i18n; + +Reference< XHyphenatedWord > SwTextFormatInfo::HyphWord( + const OUString &rText, const sal_Int32 nMinTrail ) +{ + if( rText.getLength() < 4 || m_pFnt->IsSymbol(m_pVsh) ) + return nullptr; + Reference< XHyphenator > xHyph = ::GetHyphenator(); + Reference< XHyphenatedWord > xHyphWord; + + if( xHyph.is() ) + xHyphWord = xHyph->hyphenate( rText, + g_pBreakIt->GetLocale( m_pFnt->GetLanguage() ), + rText.getLength() - nMinTrail, GetHyphValues() ); + return xHyphWord; + +} + +/** + * We format a row for interactive hyphenation + */ +bool SwTextFrame::Hyphenate(SwInterHyphInfoTextFrame & rHyphInf) +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"swapped frame at SwTextFrame::Hyphenate" ); + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + // We lock it, to start with + OSL_ENSURE( !IsLocked(), "SwTextFrame::Hyphenate: this is locked" ); + + // The frame::Frame must have a valid SSize! + Calc(pRenderContext); + GetFormatted(); + + bool bRet = false; + if( !IsEmpty() ) + { + // We always need to enable hyphenation + // Don't be afraid: the SwTextIter saves the old row in the hyphenate + TextFrameLockGuard aLock( this ); + + if ( IsVertical() ) + SwapWidthAndHeight(); + + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, true ); // true for interactive hyph! + SwTextFormatter aLine( this, &aInf ); + aLine.CharToLine( rHyphInf.m_nStart ); + + // If we're within the first word of a row, it could've been hyphenated + // in the row earlier. + // That's why we go one row back. + if( aLine.Prev() ) + { + SwLinePortion *pPor = aLine.GetCurr()->GetFirstPortion(); + while( pPor->GetNextPortion() ) + pPor = pPor->GetNextPortion(); + if( pPor->GetWhichPor() == PortionType::SoftHyphen || + pPor->GetWhichPor() == PortionType::SoftHyphenStr ) + aLine.Next(); + } + + const TextFrameIndex nEnd = rHyphInf.m_nEnd; + while( !bRet && aLine.GetStart() < nEnd ) + { + bRet = aLine.Hyphenate( rHyphInf ); + if( !aLine.Next() ) + break; + } + + if ( IsVertical() ) + SwapWidthAndHeight(); + } + return bRet; +} + +/** + * We format a row for interactive hyphenation + * We can assume that we've already formatted. + * We just reformat the row, the hyphenator will be prepared like + * the UI expects it to be. + * TODO: We can of course optimize this a lot. + */ +void SetParaPortion( SwTextInfo *pInf, SwParaPortion *pRoot ) +{ + OSL_ENSURE( pRoot, "SetParaPortion: no root anymore" ); + pInf->m_pPara = pRoot; +} + +bool SwTextFormatter::Hyphenate(SwInterHyphInfoTextFrame & rHyphInf) +{ + SwTextFormatInfo &rInf = GetInfo(); + + // We never need to hyphenate anything in the last row + // Except for, if it contains a FlyPortion or if it's the + // last row of the Master + if( !GetNext() && !rInf.GetTextFly().IsOn() && !m_pFrame->GetFollow() ) + return false; + + TextFrameIndex nWrdStart = m_nStart; + + // We need to retain the old row + // E.g.: The attribute for hyphenation was not set, but + // it's always set in SwTextFrame::Hyphenate, because we want + // to set breakpoints. + SwLineLayout *pOldCurr = m_pCurr; + + InitCntHyph(); + + // 5298: IsParaLine() (ex.IsFirstLine) calls GetParaPortion(). + // We have to create the same conditions: in the first line + // we format SwParaPortions... + if( pOldCurr->IsParaPortion() ) + { + SwParaPortion *pPara = new SwParaPortion(); + SetParaPortion( &rInf, pPara ); + m_pCurr = pPara; + OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: not the first" ); + } + else + m_pCurr = new SwLineLayout(); + + nWrdStart = FormatLine( nWrdStart ); + + // You always should keep in mind that for example there are fields + // which can be hyphenated + if( m_pCurr->PrtWidth() && m_pCurr->GetLen() ) + { + // We must be prepared that there are FlyFrames in the line, + // at which line breaking is possible. So we search for the first + // HyphPortion in the specified range. + + SwLinePortion *pPos = m_pCurr->GetNextPortion(); + const TextFrameIndex nPamStart = rHyphInf.m_nStart; + nWrdStart = m_nStart; + const TextFrameIndex nEnd = rHyphInf.m_nEnd; + while( pPos ) + { + // Either we are above or we are running into a HyphPortion + // at the end of line or before a Fly. + if( nWrdStart >= nEnd ) + { + nWrdStart = TextFrameIndex(0); + break; + } + + if( nWrdStart >= nPamStart && pPos->InHyphGrp() + && ( !pPos->IsSoftHyphPortion() + || static_cast<SwSoftHyphPortion*>(pPos)->IsExpand() ) ) + { + nWrdStart = nWrdStart + pPos->GetLen(); + break; + } + + nWrdStart = nWrdStart + pPos->GetLen(); + pPos = pPos->GetNextPortion(); + } + // When pPos is null, no hyphen position was found. + if( !pPos ) + nWrdStart = TextFrameIndex(0); + } + else + // In case the whole line is zero-length, that's the same situation as + // above when the portion iteration ends without explicitly breaking + // from the loop. + nWrdStart = TextFrameIndex(0); + + // the old LineLayout is set again ... + delete m_pCurr; + m_pCurr = pOldCurr; + + if( pOldCurr->IsParaPortion() ) + { + SetParaPortion( &rInf, static_cast<SwParaPortion*>(pOldCurr) ); + OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: even not the first" ); + } + + if (nWrdStart == TextFrameIndex(0)) + return false; + + // nWrdStart contains the position in string that should be hyphenated + rHyphInf.m_nWordStart = nWrdStart; + + TextFrameIndex nLen(0); + const TextFrameIndex nEnd = nWrdStart; + + // we search forwards + Reference< XHyphenatedWord > xHyphWord; + + Boundary const aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( + rInf.GetText(), sal_Int32(nWrdStart), + g_pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ), WordType::DICTIONARY_WORD, true ); + nWrdStart = TextFrameIndex(aBound.startPos); + nLen = TextFrameIndex(aBound.endPos) - nWrdStart; + if (nLen == TextFrameIndex(0)) + return false; + + OUString const aSelText(rInf.GetText().copy(sal_Int32(nWrdStart), sal_Int32(nLen))); + const sal_Int32 nMinTrail = (nWrdStart + nLen > nEnd) + ? sal_Int32(nWrdStart + nLen - nEnd) - 1 + : 0; + + //!! rHyphInf.SetHyphWord( ... ) must done here + xHyphWord = rInf.HyphWord( aSelText, nMinTrail ); + if ( xHyphWord.is() ) + { + rHyphInf.SetHyphWord( xHyphWord ); + rHyphInf.m_nWordStart = nWrdStart; + rHyphInf.m_nWordLen = nLen; + return true; + } + + return false; +} + +bool SwTextPortion::CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGuess ) +{ + const Reference< XHyphenatedWord >& xHyphWord = rGuess.HyphWord(); + + OSL_ENSURE( !mpNextPortion, "SwTextPortion::CreateHyphen(): another portion, another planet..." ); + OSL_ENSURE( xHyphWord.is(), "SwTextPortion::CreateHyphen(): You are lucky! The code is robust here." ); + + if( rInf.IsHyphForbud() || + mpNextPortion || // robust + !xHyphWord.is() || // more robust + // multi-line fields can't be hyphenated interactively + ( rInf.IsInterHyph() && InFieldGrp() ) ) + return false; + + std::unique_ptr<SwHyphPortion> pHyphPor; + TextFrameIndex nPorEnd; + SwTextSizeInfo aInf( rInf ); + + // first case: hyphenated word has alternative spelling + if ( xHyphWord->isAlternativeSpelling() ) + { + SvxAlternativeSpelling aAltSpell = SvxGetAltSpelling( xHyphWord ); + OSL_ENSURE( aAltSpell.bIsAltSpelling, "no alternative spelling" ); + + OUString aAltText = aAltSpell.aReplacement; + nPorEnd = TextFrameIndex(aAltSpell.nChangedPos) + rGuess.BreakStart() - rGuess.FieldDiff(); + sal_Int32 nTmpLen = 0; + + // soft hyphen at alternative spelling position? + if( rInf.GetText()[sal_Int32(rInf.GetSoftHyphPos())] == CHAR_SOFTHYPHEN ) + { + pHyphPor.reset(new SwSoftHyphStrPortion( aAltText )); + nTmpLen = 1; + } + else { + pHyphPor.reset(new SwHyphStrPortion( aAltText )); + } + + // length of pHyphPor is adjusted + pHyphPor->SetLen( TextFrameIndex(aAltText.getLength() + 1) ); + static_cast<SwPosSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf ); + pHyphPor->SetLen( TextFrameIndex(aAltSpell.nChangedLength + nTmpLen) ); + } + else + { + // second case: no alternative spelling + pHyphPor.reset(new SwHyphPortion); + pHyphPor->SetLen(TextFrameIndex(1)); + + static const void* nLastFontCacheId = nullptr; + static sal_uInt16 aMiniCacheH = 0, aMiniCacheW = 0; + const void* nTmpFontCacheId; + sal_uInt16 nFntIdx; + rInf.GetFont()->GetFontCacheId( nTmpFontCacheId, nFntIdx, rInf.GetFont()->GetActual() ); + if( !nLastFontCacheId || nLastFontCacheId != nTmpFontCacheId ) { + nLastFontCacheId = nTmpFontCacheId; + static_cast<SwPosSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf ); + aMiniCacheH = pHyphPor->Height(); + aMiniCacheW = pHyphPor->Width(); + } else { + pHyphPor->Height( aMiniCacheH ); + pHyphPor->Width( aMiniCacheW ); + } + pHyphPor->SetLen(TextFrameIndex(0)); + + // values required for this + nPorEnd = TextFrameIndex(xHyphWord->getHyphenPos() + 1) + + rGuess.BreakStart() - rGuess.FieldDiff(); + } + + // portion end must be in front of us + // we do not put hyphens at start of line + if ( nPorEnd > rInf.GetIdx() || + ( nPorEnd == rInf.GetIdx() && rInf.GetLineStart() != rInf.GetIdx() ) ) + { + aInf.SetLen( nPorEnd - rInf.GetIdx() ); + pHyphPor->SetAscent( GetAscent() ); + SetLen( aInf.GetLen() ); + CalcTextSize( aInf ); + + Insert( pHyphPor.release() ); + + short nKern = rInf.GetFont()->CheckKerning(); + if( nKern ) + new SwKernPortion( *this, nKern ); + + return true; + } + + // last exit for the lost + pHyphPor.reset(); + BreakCut( rInf, rGuess ); + return false; +} + +bool SwHyphPortion::GetExpText( const SwTextSizeInfo &/*rInf*/, OUString &rText ) const +{ + rText = "-"; + return true; +} + +void SwHyphPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), OUString('-'), GetWhichPor() ); +} + +bool SwHyphPortion::Format( SwTextFormatInfo &rInf ) +{ + const SwLinePortion *pLast = rInf.GetLast(); + Height( pLast->Height() ); + SetAscent( pLast->GetAscent() ); + OUString aText; + + if( !GetExpText( rInf, aText ) ) + return false; + + PrtWidth( rInf.GetTextSize( aText ).Width() ); + const bool bFull = rInf.Width() <= rInf.X() + PrtWidth(); + if( bFull && !rInf.IsUnderflow() ) { + Truncate(); + rInf.SetUnderflow( this ); + } + + return bFull; +} + +bool SwHyphStrPortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const +{ + rText = aExpand; + return true; +} + +void SwHyphStrPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Special( GetLen(), aExpand, GetWhichPor() ); +} + +SwLinePortion *SwSoftHyphPortion::Compress() { return this; } + +SwSoftHyphPortion::SwSoftHyphPortion() : + bExpand(false), nViewWidth(0) +{ + SetLen(TextFrameIndex(1)); + SetWhichPor( PortionType::SoftHyphen ); +} + +sal_uInt16 SwSoftHyphPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const +{ + // Although we're in the const, nViewWidth should be calculated at + // the last possible moment + if( !Width() && rInf.OnWin() && rInf.GetOpt().IsSoftHyph() && !IsExpand() ) + { + if( !nViewWidth ) + const_cast<SwSoftHyphPortion*>(this)->nViewWidth + = rInf.GetTextSize(OUString('-')).Width(); + } + else + const_cast<SwSoftHyphPortion*>(this)->nViewWidth = 0; + return nViewWidth; +} + +/** + * Cases: + * + * 1) SoftHyph is in the line, ViewOpt off + * -> invisible, neighbors unchanged + * 2) SoftHyph is in the line, ViewOpt on + * -> visible, neighbors unchanged + * 3) SoftHyph is at the end of the line, ViewOpt on or off + * -> always visible, neighbors unchanged + */ +void SwSoftHyphPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if( Width() ) + { + rInf.DrawViewOpt( *this, PortionType::SoftHyphen ); + SwExpandPortion::Paint( rInf ); + } +} + +/** + * We get the final width from the FormatEOL() + * + * During the underflow-phase we determine, whether or not + * there's an alternative spelling at all ... + * + * Case 1: "Au-to" + * 1) {Au}{-}{to}, {to} does not fit anymore => underflow + * 2) {-} calls hyphenate => no alternative + * 3) FormatEOL() and bFull = true + * + * Case 2: "Zuc-ker" + * 1) {Zuc}{-}{ker}, {ker} does not fit anymore => underflow + * 2) {-} calls hyphenate => alternative! + * 3) Underflow() and bFull = true + * 4) {Zuc} calls hyphenate => {Zuk}{-}{ker} + */ +bool SwSoftHyphPortion::Format( SwTextFormatInfo &rInf ) +{ + bool bFull = true; + + // special case for old German spelling + if( rInf.IsUnderflow() ) + { + if( rInf.GetSoftHyphPos() ) + return true; + + const bool bHyph = rInf.ChgHyph( true ); + if( rInf.IsHyphenate() ) + { + rInf.SetSoftHyphPos( rInf.GetIdx() ); + Width(0); + // if the soft hyphened word has an alternative spelling + // when hyphenated (old German spelling), the soft hyphen + // portion has to trigger an underflow + SwTextGuess aGuess; + bFull = rInf.IsInterHyph() || + !aGuess.AlternativeSpelling(rInf, rInf.GetIdx() - TextFrameIndex(1)); + } + rInf.ChgHyph( bHyph ); + + if( bFull && !rInf.IsHyphForbud() ) + { + rInf.SetSoftHyphPos(TextFrameIndex(0)); + FormatEOL( rInf ); + if ( rInf.GetFly() ) + rInf.GetRoot()->SetMidHyph( true ); + else + rInf.GetRoot()->SetEndHyph( true ); + } + else + { + rInf.SetSoftHyphPos( rInf.GetIdx() ); + Truncate(); + rInf.SetUnderflow( this ); + } + return true; + } + + rInf.SetSoftHyphPos(TextFrameIndex(0)); + SetExpand( true ); + bFull = SwHyphPortion::Format( rInf ); + SetExpand( false ); + if( !bFull ) + { + // By default, we do not have a width, but we do have a height + Width(0); + } + return bFull; +} + +/** + * Format End of Line + */ +void SwSoftHyphPortion::FormatEOL( SwTextFormatInfo &rInf ) +{ + if( !IsExpand() ) + { + SetExpand( true ); + if( rInf.GetLast() == this ) + rInf.SetLast( FindPrevPortion( rInf.GetRoot() ) ); + + // We need to reset the old values + const SwTwips nOldX = rInf.X(); + TextFrameIndex const nOldIdx = rInf.GetIdx(); + rInf.X( rInf.X() - PrtWidth() ); + rInf.SetIdx( rInf.GetIdx() - GetLen() ); + const bool bFull = SwHyphPortion::Format( rInf ); + + // Shady business: We're allowed to get wider, but a Fly is also + // being processed, which needs a correct X position + if( bFull || !rInf.GetFly() ) + rInf.X( nOldX ); + else + rInf.X( nOldX + Width() ); + rInf.SetIdx( nOldIdx ); + } +} + +/** + * We're expanding: + * - if the special characters should be visible + * - if we're at the end of the line + * - if we're before a (real/emulated) line break + */ +bool SwSoftHyphPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const +{ + if( IsExpand() || ( rInf.OnWin() && rInf.GetOpt().IsSoftHyph() ) || + ( GetNextPortion() && ( GetNextPortion()->InFixGrp() || + GetNextPortion()->IsDropPortion() || GetNextPortion()->IsLayPortion() || + GetNextPortion()->IsParaPortion() || GetNextPortion()->IsBreakPortion() ) ) ) + { + return SwHyphPortion::GetExpText( rInf, rText ); + } + return false; +} + +void SwSoftHyphPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + const PortionType nWhich = ! Width() ? + PortionType::SoftHyphenComp : + GetWhichPor(); + rPH.Special( GetLen(), OUString('-'), nWhich ); +} + +void SwSoftHyphStrPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + // Bug or feature?: + // {Zu}{k-}{ker}, {k-} will be gray instead of {-} + rInf.DrawViewOpt( *this, PortionType::SoftHyphen ); + SwHyphStrPortion::Paint( rInf ); +} + +SwSoftHyphStrPortion::SwSoftHyphStrPortion( const OUString &rStr ) + : SwHyphStrPortion( rStr ) +{ + SetLen(TextFrameIndex(1)); + SetWhichPor( PortionType::SoftHyphenStr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtinit.cxx b/sw/source/core/text/txtinit.cxx new file mode 100644 index 000000000..b8a6540f6 --- /dev/null +++ b/sw/source/core/text/txtinit.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <swcache.hxx> +#include <fntcache.hxx> +#include <swfntcch.hxx> +#include <txtfrm.hxx> +#include "pordrop.hxx" +#include <init.hxx> +#include <txtfly.hxx> +#include <dbg_lay.hxx> + +SwCache *SwTextFrame::s_pTextCache = nullptr; +SwContourCache *pContourCache = nullptr; +SwDropCapCache *pDropCapCache = nullptr; + +// Are ONLY used in init.cxx. +// There we have extern void TextFinit() +// and extern void TextInit_(...) + +void TextInit_() +{ + pFntCache = new SwFntCache; // Cache for SwSubFont -> SwFntObj = { Font aFont, Font* pScrFont, Font* pPrtFont, OutputDevice* pPrinter, ... } + pSwFontCache = new SwFontCache; // Cache for SwTextFormatColl -> SwFontObj = { SwFont aSwFont, SfxPoolItem* pDefaultArray } + SwCache *pTextCache = new SwCache( 250 // Cache for SwTextFrame -> SwTextLine = { SwParaPortion* pLine } +#ifdef DBG_UTIL + , "static SwTextFrame::s_pTextCache" +#endif + ); + SwTextFrame::SetTextCache( pTextCache ); + PROTOCOL_INIT +} + +void TextFinit() +{ + PROTOCOL_STOP + delete SwTextFrame::GetTextCache(); + delete pSwFontCache; + delete pFntCache; + delete pContourCache; + SwDropPortion::DeleteDropCapCache(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtpaint.cxx b/sw/source/core/text/txtpaint.cxx new file mode 100644 index 000000000..e2292d70b --- /dev/null +++ b/sw/source/core/text/txtpaint.cxx @@ -0,0 +1,113 @@ +/* -*- 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 "txtpaint.hxx" +#include <txtfrm.hxx> +#include <swrect.hxx> +#include <rootfrm.hxx> + +SwSaveClip::~SwSaveClip() +{ + // We recover the old state + if( pOut && bChg ) + { + if ( pOut->GetConnectMetaFile() ) + pOut->Pop(); + else + { + if( bOn ) + pOut->SetClipRegion( aClip ); + else + pOut->SetClipRegion(); + } + bChg = false; + } +} + +void SwSaveClip::ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame, + bool bEnlargeRect, + sal_Int32 nEnlargeTop, + sal_Int32 nEnlargeBottom ) +{ + SwRect aOldRect( rRect ); + const bool bVertical = pFrame && pFrame->IsVertical(); + + if ( pFrame && pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( const_cast<SwRect&>(rRect) ); + + if ( bVertical ) + pFrame->SwitchHorizontalToVertical( const_cast<SwRect&>(rRect) ); + + if ( !pOut || (!rRect.HasArea() && !pOut->IsClipRegion()) ) + { + const_cast<SwRect&>(rRect) = aOldRect; + return; + } + + if ( !bChg ) + { + if ( pOut->GetConnectMetaFile() ) + pOut->Push(); + else if ( bOn ) + aClip = pOut->GetClipRegion(); + } + + if ( !rRect.HasArea() ) + pOut->SetClipRegion(); + else + { + tools::Rectangle aRect( rRect.SVRect() ); + + // Having underscores in our line, we enlarged the repaint area + // (see frmform.cxx) because for some fonts it could be too small. + // Consequently, we have to enlarge the clipping rectangle as well. + if ( bEnlargeRect && ! bVertical ) + aRect.AdjustBottom(40 ); + + // enlarge clip for paragraph margins at small fixed line height + if ( nEnlargeTop > 0 ) + aRect.AdjustTop( -nEnlargeTop ); + + if ( nEnlargeBottom > 0 ) + aRect.AdjustBottom( nEnlargeBottom ); + + // If the ClipRect is identical, nothing will happen + if( pOut->IsClipRegion() ) // no && because of Mac + { + if ( aRect == pOut->GetClipRegion().GetBoundRect() ) + { + const_cast<SwRect&>(rRect) = aOldRect; + return; + } + } + + if( SwRootFrame::HasSameRect( rRect ) ) + pOut->SetClipRegion(); + else + { + const vcl::Region aClipRegion( aRect ); + pOut->SetClipRegion( aClipRegion ); + } + } + bChg = true; + + const_cast<SwRect&>(rRect) = aOldRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txtpaint.hxx b/sw/source/core/text/txtpaint.hxx new file mode 100644 index 000000000..0c8e0466b --- /dev/null +++ b/sw/source/core/text/txtpaint.hxx @@ -0,0 +1,127 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_TXTPAINT_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_TXTPAINT_HXX +#include <vcl/outdev.hxx> + +class SwRect; // SwSaveClip +class SwTextFrame; + +class SwSaveClip final +{ + vcl::Region aClip; + const bool bOn; + bool bChg; + + VclPtr<OutputDevice> pOut; + void ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame, + bool bEnlargeRect, + sal_Int32 nEnlargeTop, + sal_Int32 nEnlargeBottom ); +public: + explicit SwSaveClip(OutputDevice* pOutDev) + : bOn(pOutDev && pOutDev->IsClipRegion()) + , bChg(false) + , pOut(pOutDev) + { + } + + ~SwSaveClip(); + void ChgClip( const SwRect &rRect, const SwTextFrame* pFrame = nullptr, + bool bEnlargeRect = false, + sal_Int32 nEnlargeTop = 0, + sal_Int32 nEnlargeBottom = 0) + { if( pOut ) ChgClip_( rRect, pFrame, + bEnlargeRect, nEnlargeTop, nEnlargeBottom ); } + bool IsOn() const { return bOn; } + bool IsChg() const { return bChg; } +}; + + +#ifdef DBG_UTIL + +class SwDbgOut +{ +protected: + VclPtr<OutputDevice> pOut; +public: + inline SwDbgOut( OutputDevice* pOutDev, const bool bOn ); +}; + +class DbgBackColor : public SwDbgOut +{ + Color aOldFillColor; +public: + DbgBackColor( OutputDevice* pOut, const bool bOn ); + ~DbgBackColor(); +}; + +class DbgRect : public SwDbgOut +{ +public: + DbgRect( OutputDevice* pOut, const tools::Rectangle &rRect, + const bool bOn, + Color eColor ); +}; + +inline SwDbgOut::SwDbgOut( OutputDevice* pOutDev, const bool bOn ) + :pOut( bOn ? pOutDev : nullptr ) +{ } + +inline DbgBackColor::DbgBackColor( OutputDevice* pOutDev, const bool bOn ) + :SwDbgOut( pOutDev, bOn ) +{ + if( pOut ) + { + aOldFillColor = pOut->GetFillColor(); + pOut->SetFillColor( COL_RED ); + } +} + +inline DbgBackColor::~DbgBackColor() +{ + if( pOut ) + { + pOut->SetFillColor( aOldFillColor ); + } +} + +inline DbgRect::DbgRect( OutputDevice* pOutDev, const tools::Rectangle &rRect, + const bool bOn, + Color eColor ) + : SwDbgOut( pOutDev, bOn ) +{ + if( pOut ) + { + const Color aColor( eColor ); + Color aLineColor = pOut->GetLineColor(); + pOut->SetLineColor( aColor ); + Color aFillColor = pOut->GetFillColor(); + pOut->SetFillColor( COL_TRANSPARENT ); + pOut->DrawRect( rRect ); + pOut->SetLineColor( aLineColor ); + pOut->SetFillColor( aFillColor ); + } +} + +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/txttab.cxx b/sw/source/core/text/txttab.cxx new file mode 100644 index 000000000..a38cbe048 --- /dev/null +++ b/sw/source/core/text/txttab.cxx @@ -0,0 +1,600 @@ +/* -*- 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 <hintids.hxx> +#include <comphelper/string.hxx> +#include <editeng/tstpitem.hxx> +#include <rtl/ustrbuf.hxx> +#include <IDocumentSettingAccess.hxx> +#include <doc.hxx> +#include <SwPortionHandler.hxx> + +#include <viewopt.hxx> +#include "portab.hxx" +#include "inftxt.hxx" +#include "itrform2.hxx" +#include <txtfrm.hxx> +#include "porfld.hxx" +#include <memory> + +/** + * #i24363# tab stops relative to indent + * + * Return the first tab stop that is > nSearchPos. + * If the tab stop is outside the print area, we + * return 0 if it is not the first tab stop. + */ +const SvxTabStop *SwLineInfo::GetTabStop( const SwTwips nSearchPos, const SwTwips nRight ) const +{ + for( sal_uInt16 i = 0; i < pRuler->Count(); ++i ) + { + const SvxTabStop &rTabStop = pRuler->operator[](i); + if( rTabStop.GetTabPos() > SwTwips(nRight) ) + return i ? nullptr : &rTabStop; + + if( rTabStop.GetTabPos() > nSearchPos ) + return &rTabStop; + } + return nullptr; +} + +sal_uInt16 SwLineInfo::NumberOfTabStops() const +{ + return pRuler->Count(); +} + +SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto ) const +{ + sal_Unicode cFill = 0; + sal_Unicode cDec = 0; + SvxTabAdjust eAdj; + + sal_uInt16 nNewTabPos; + bool bAutoTabStop = true; + { + const bool bRTL = m_pFrame->IsRightToLeft(); + // #i24363# tab stops relative to indent + // nTabLeft: The absolute value, the tab stops are relative to: Tabs origin. + + // #i91133# + const bool bTabsRelativeToIndent = + m_pFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT); + const SwTwips nTabLeft = bRTL + ? m_pFrame->getFrameArea().Right() - + ( bTabsRelativeToIndent ? GetTabLeft() : 0 ) + : m_pFrame->getFrameArea().Left() + + ( bTabsRelativeToIndent ? GetTabLeft() : 0 ); + + // The absolute position, where we started the line formatting + SwTwips nLinePos = GetLeftMargin(); + if ( bRTL ) + { + Point aPoint( nLinePos, 0 ); + m_pFrame->SwitchLTRtoRTL( aPoint ); + nLinePos = aPoint.X(); + } + + // The current position, relative to the line start + SwTwips nTabPos = rInf.GetLastTab() ? rInf.GetLastTab()->GetTabPos() : 0; + if( nTabPos < rInf.X() ) + { + nTabPos = rInf.X(); + } + + // The current position in absolute coordinates + const SwTwips nCurrentAbsPos = bRTL ? + nLinePos - nTabPos : + nLinePos + nTabPos; + + SwTwips nMyRight; + if ( m_pFrame->IsVertLR() ) + nMyRight = Left(); + else + nMyRight = Right(); + + if ( m_pFrame->IsVertical() ) + { + Point aRightTop( nMyRight, m_pFrame->getFrameArea().Top() ); + m_pFrame->SwitchHorizontalToVertical( aRightTop ); + nMyRight = aRightTop.Y(); + } + + SwTwips nNextPos = 0; + + // #i24363# tab stops relative to indent + // nSearchPos: The current position relative to the tabs origin + const SwTwips nSearchPos = bRTL ? + nTabLeft - nCurrentAbsPos : + nCurrentAbsPos - nTabLeft; + + // First, we examine the tab stops set at the paragraph style or + // any hard set tab stops: + // Note: If there are no user defined tab stops, there is always a + // default tab stop. + const SvxTabStop* pTabStop = m_aLineInf.GetTabStop( nSearchPos, nMyRight ); + if ( pTabStop ) + { + cFill = ' ' != pTabStop->GetFill() ? pTabStop->GetFill() : 0; + cDec = pTabStop->GetDecimal(); + eAdj = pTabStop->GetAdjustment(); + nNextPos = pTabStop->GetTabPos(); + if(!bTabsRelativeToIndent && eAdj == SvxTabAdjust::Default && nSearchPos < 0) + { + //calculate default tab position of default tabs in negative indent + nNextPos = ( nSearchPos / nNextPos ) * nNextPos; + } + bAutoTabStop = false; + } + else + { + sal_uInt16 nDefTabDist = m_aLineInf.GetDefTabStop(); + if( USHRT_MAX == nDefTabDist ) + { + const SvxTabStopItem& rTab = + m_pFrame->GetAttrSet()->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP ); + if( rTab.Count() ) + nDefTabDist = static_cast<sal_uInt16>(rTab[0].GetTabPos()); + else + nDefTabDist = SVX_TAB_DEFDIST; + m_aLineInf.SetDefTabStop( nDefTabDist ); + } + SwTwips nCount = nSearchPos; + + // Minimum tab stop width is 1 + if (nDefTabDist <= 0) + nDefTabDist = 1; + + nCount /= nDefTabDist; + nNextPos = ( nCount < 0 || ( !nCount && nSearchPos <= 0 ) ) + ? ( nCount * nDefTabDist ) + : ( ( nCount + 1 ) * nDefTabDist ); + + // --> FME 2004-09-21 #117919 Minimum tab stop width is 1 or 51 twips: + const SwTwips nMinimumTabWidth = m_pFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) ? 0 : 50; + if( ( bRTL && nTabLeft - nNextPos >= nCurrentAbsPos - nMinimumTabWidth ) || + ( !bRTL && nNextPos + nTabLeft <= nCurrentAbsPos + nMinimumTabWidth ) ) + { + nNextPos += nDefTabDist; + } + cFill = 0; + eAdj = SvxTabAdjust::Left; + } + + // #i115705# - correction and refactoring: + // overrule determined next tab stop position in order to apply + // a tab stop at the left margin under the following conditions: + // - the new tab portion is inside the hanging indent + // - a tab stop at the left margin is allowed + // - the determined next tab stop is a default tab stop position OR + // the determined next tab stop is beyond the left margin + { + long nLeftMarginTabPos = 0; + { + if ( !bTabsRelativeToIndent ) + { + if ( bRTL ) + { + Point aPoint( Left(), 0 ); + m_pFrame->SwitchLTRtoRTL( aPoint ); + nLeftMarginTabPos = m_pFrame->getFrameArea().Right() - aPoint.X(); + } + else + { + nLeftMarginTabPos = Left() - m_pFrame->getFrameArea().Left(); + } + } + if( m_pCurr->HasForcedLeftMargin() ) + { + SwLinePortion* pPor = m_pCurr->GetNextPortion(); + while( pPor && !pPor->IsFlyPortion() ) + { + pPor = pPor->GetNextPortion(); + } + if ( pPor ) + { + nLeftMarginTabPos += pPor->Width(); + } + } + } + const bool bNewTabPortionInsideHangingIndent = + bRTL ? nCurrentAbsPos > nTabLeft - nLeftMarginTabPos + : nCurrentAbsPos < nTabLeft + nLeftMarginTabPos; + if ( bNewTabPortionInsideHangingIndent ) + { + // If the paragraph is not inside a list having a list tab stop following + // the list label or no further tab stop found in such a paragraph or + // the next tab stop position does not equal the list tab stop, + // a tab stop at the left margin can be applied. If this condition is + // not hold, it is overruled by compatibility option TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST. + const bool bTabAtLeftMarginAllowed = + ( !m_aLineInf.IsListTabStopIncluded() || + !pTabStop || + nNextPos != m_aLineInf.GetListTabStopPosition() ) || + // compatibility option TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: + m_pFrame->GetDoc().getIDocumentSettingAccess(). + get(DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST); + if ( bTabAtLeftMarginAllowed ) + { + if ( !pTabStop || eAdj == SvxTabAdjust::Default || + ( nNextPos > nLeftMarginTabPos ) ) + { + eAdj = SvxTabAdjust::Default; + cFill = 0; + nNextPos = nLeftMarginTabPos; + } + } + } + } + + nNextPos += bRTL ? nLinePos - nTabLeft : nTabLeft - nLinePos; + OSL_ENSURE( nNextPos >= 0, "GetTabStop: Don't go back!" ); + nNewTabPos = sal_uInt16(nNextPos); + } + + SwTabPortion *pTabPor = nullptr; + if ( bAuto ) + { + if ( SvxTabAdjust::Decimal == eAdj && + 1 == m_aLineInf.NumberOfTabStops() ) + pTabPor = new SwAutoTabDecimalPortion( nNewTabPos, cDec, cFill ); + } + else + { + switch( eAdj ) + { + case SvxTabAdjust::Right : + { + pTabPor = new SwTabRightPortion( nNewTabPos, cFill ); + break; + } + case SvxTabAdjust::Center : + { + pTabPor = new SwTabCenterPortion( nNewTabPos, cFill ); + break; + } + case SvxTabAdjust::Decimal : + { + pTabPor = new SwTabDecimalPortion( nNewTabPos, cDec, cFill ); + break; + } + default: + { + OSL_ENSURE( SvxTabAdjust::Left == eAdj || SvxTabAdjust::Default == eAdj, + "+SwTextFormatter::NewTabPortion: unknown adjustment" ); + pTabPor = new SwTabLeftPortion( nNewTabPos, cFill, bAutoTabStop ); + break; + } + } + } + + return pTabPor; +} + +/** + * The base class is initialized without setting anything + */ +SwTabPortion::SwTabPortion( const sal_uInt16 nTabPosition, const sal_Unicode cFillChar, const bool bAutoTab ) + : SwFixPortion(), nTabPos(nTabPosition), cFill(cFillChar), bAutoTabStop( bAutoTab ) +{ + nLineLength = TextFrameIndex(1); + OSL_ENSURE(!IsFilled() || ' ' != cFill, "SwTabPortion::CTOR: blanks ?!"); + SetWhichPor( PortionType::Table ); +} + +bool SwTabPortion::Format( SwTextFormatInfo &rInf ) +{ + SwTabPortion *pLastTab = rInf.GetLastTab(); + if( pLastTab == this ) + return PostFormat( rInf ); + if( pLastTab ) + pLastTab->PostFormat( rInf ); + return PreFormat( rInf ); +} + +void SwTabPortion::FormatEOL( SwTextFormatInfo &rInf ) +{ + if( rInf.GetLastTab() == this ) + PostFormat( rInf ); +} + +bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) +{ + OSL_ENSURE( rInf.X() <= GetTabPos(), "SwTabPortion::PreFormat: rush hour" ); + + // Here we settle down ... + SetFix( static_cast<sal_uInt16>(rInf.X()) ); + + IDocumentSettingAccess const& rIDSA(rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess()); + const bool bTabCompat = rIDSA.get(DocumentSettingId::TAB_COMPAT); + const bool bTabOverflow = rIDSA.get(DocumentSettingId::TAB_OVERFLOW); + const bool bTabOverMargin = rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN); + + // The minimal width of a tab is one blank at least. + // #i37686# In compatibility mode, the minimum width + // should be 1, even for non-left tab stops. + sal_uInt16 nMinimumTabWidth = 1; + if ( !bTabCompat ) + { + // #i89179# + // tab portion representing the list tab of a list label gets the + // same font as the corresponding number portion + std::unique_ptr< SwFontSave > pSave; + if ( GetLen() == TextFrameIndex(0) && + rInf.GetLast() && rInf.GetLast()->InNumberGrp() && + static_cast<SwNumberPortion*>(rInf.GetLast())->HasFont() ) + { + const SwFont* pNumberPortionFont = + static_cast<SwNumberPortion*>(rInf.GetLast())->GetFont(); + pSave.reset( new SwFontSave( rInf, const_cast<SwFont*>(pNumberPortionFont) ) ); + } + OUString aTmp( ' ' ); + SwTextSizeInfo aInf( rInf, &aTmp ); + nMinimumTabWidth = aInf.GetTextSize().Width(); + } + PrtWidth( nMinimumTabWidth ); + + // Break tab stop to next line if: + // 1. Minimal width does not fit to line anymore. + // 2. An underflow event was called for the tab portion. + bool bFull = ( bTabCompat && rInf.IsUnderflow() ) || + ( rInf.Width() <= rInf.X() + PrtWidth() && rInf.X() <= rInf.Width() ) ; + + // #95477# Rotated tab stops get the width of one blank + const sal_uInt16 nDir = rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ); + + if( ! bFull && 0 == nDir ) + { + const PortionType nWhich = GetWhichPor(); + switch( nWhich ) + { + case PortionType::TabRight: + case PortionType::TabDecimal: + case PortionType::TabCenter: + { + if( PortionType::TabDecimal == nWhich ) + rInf.SetTabDecimal( + static_cast<SwTabDecimalPortion*>(this)->GetTabDecimal()); + rInf.SetLastTab( this ); + break; + } + case PortionType::TabLeft: + { + // handle this case in PostFormat + if( bTabOverMargin && !bAutoTabStop && GetTabPos() > rInf.Width() ) + { + rInf.SetLastTab( this ); + break; + } + + PrtWidth( static_cast<sal_uInt16>(GetTabPos() - rInf.X()) ); + bFull = rInf.Width() <= rInf.X() + PrtWidth(); + + // In tabulator compatibility mode, we reset the bFull flag + // if the tabulator is at the end of the paragraph and the + // tab stop position is outside the frame: + bool bAtParaEnd = rInf.GetIdx() + GetLen() == TextFrameIndex(rInf.GetText().getLength()); + if ( bFull && bTabCompat && + ( ( bTabOverflow && ( rInf.IsTabOverflow() || !bAutoTabStop ) ) || bAtParaEnd ) && + GetTabPos() >= rInf.GetTextFrame()->getFrameArea().Width() ) + { + bFull = false; + if ( bTabOverflow && !bAutoTabStop ) + rInf.SetTabOverflow( true ); + } + + break; + } + default: OSL_ENSURE( false, "SwTabPortion::PreFormat: unknown adjustment" ); + } + } + + if( bFull ) + { + // We have to look for endless loops, if the width is smaller than one blank + if( rInf.GetIdx() == rInf.GetLineStart() && + // #119175# TabStop should be forced to current + // line if there is a fly reducing the line width: + !rInf.GetFly() ) + { + PrtWidth( static_cast<sal_uInt16>(rInf.Width() - rInf.X()) ); + SetFixWidth( PrtWidth() ); + } + else + { + Height( 0 ); + Width( 0 ); + SetLen( TextFrameIndex(0) ); + SetAscent( 0 ); + SetNextPortion( nullptr ); //????? + } + return true; + } + else + { + // A trick with impact: The new Tabportions now behave like + // FlyFrames, located in the line - including adjustment ! + SetFixWidth( PrtWidth() ); + return false; + } +} + +bool SwTabPortion::PostFormat( SwTextFormatInfo &rInf ) +{ + const bool bTabOverMargin = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN); + // If the tab position is larger than the right margin, it gets scaled down by default. + // However, if compat mode enabled, we allow tabs to go over the margin: the rest of the paragraph is not broken into lines. + const sal_uInt16 nRight = bTabOverMargin ? GetTabPos() : std::min(GetTabPos(), rInf.Width()); + const SwLinePortion *pPor = GetNextPortion(); + + sal_uInt16 nPorWidth = 0; + while( pPor ) + { + nPorWidth = nPorWidth + pPor->Width(); + pPor = pPor->GetNextPortion(); + } + + const PortionType nWhich = GetWhichPor(); + const bool bTabCompat = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT); + + if ( bTabOverMargin && PortionType::TabLeft == nWhich ) + { + nPorWidth = 0; + } + + // #127428# Abandon dec. tab position if line is full + if ( bTabCompat && PortionType::TabDecimal == nWhich ) + { + sal_uInt16 nPrePorWidth = static_cast<const SwTabDecimalPortion*>(this)->GetWidthOfPortionsUpToDecimalPosition(); + + // no value was set => no decimal character was found + if ( USHRT_MAX != nPrePorWidth ) + { + if ( !bTabOverMargin && nPrePorWidth && nPorWidth - nPrePorWidth > rInf.Width() - nRight ) + { + nPrePorWidth += nPorWidth - nPrePorWidth - ( rInf.Width() - nRight ); + } + + nPorWidth = nPrePorWidth - 1; + } + } + + if( PortionType::TabCenter == nWhich ) + { + // centered tabs are problematic: + // We have to detect how much fits into the line. + sal_uInt16 nNewWidth = nPorWidth /2; + if( !bTabOverMargin && nNewWidth > rInf.Width() - nRight ) + nNewWidth = nPorWidth - (rInf.Width() - nRight); + nPorWidth = nNewWidth; + } + + const sal_uInt16 nDiffWidth = nRight - GetFix(); + + if( nDiffWidth > nPorWidth ) + { + const sal_uInt16 nOldWidth = GetFixWidth(); + const sal_uInt16 nAdjDiff = nDiffWidth - nPorWidth; + if( nAdjDiff > GetFixWidth() ) + PrtWidth( nAdjDiff ); + // Don't be afraid: we have to move rInf further. + // The right-tab till now only had the width of one blank. + // Now that we stretched, the difference had to be added to rInf.X() ! + rInf.X( rInf.X() + PrtWidth() - nOldWidth ); + } + SetFixWidth( PrtWidth() ); + // reset last values + rInf.SetLastTab(nullptr); + if( PortionType::TabDecimal == nWhich ) + rInf.SetTabDecimal(0); + + return rInf.Width() <= rInf.X(); +} + +/** + * Ex: LineIter::DrawTab() + */ +void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + // #i89179# + // tab portion representing the list tab of a list label gets the + // same font as the corresponding number portion + std::unique_ptr< SwFontSave > pSave; + bool bAfterNumbering = false; + if (GetLen() == TextFrameIndex(0)) + { + const SwLinePortion* pPrevPortion = + const_cast<SwTabPortion*>(this)->FindPrevPortion( rInf.GetParaPortion() ); + if ( pPrevPortion && + pPrevPortion->InNumberGrp() && + static_cast<const SwNumberPortion*>(pPrevPortion)->HasFont() ) + { + const SwFont* pNumberPortionFont = + static_cast<const SwNumberPortion*>(pPrevPortion)->GetFont(); + pSave.reset( new SwFontSave( rInf, const_cast<SwFont*>(pNumberPortionFont) ) ); + bAfterNumbering = true; + } + } + rInf.DrawBackBrush( *this ); + if( !bAfterNumbering ) + rInf.DrawBorder( *this ); + + // do we have to repaint a post it portion? + if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() ) + mpNextPortion->PrePaint( rInf, this ); + + // display special characters + if( rInf.OnWin() && rInf.GetOpt().IsTab() ) + { + // filled tabs are shaded in gray + if( IsFilled() ) + rInf.DrawViewOpt( *this, PortionType::Table ); + else + rInf.DrawTab( *this ); + } + + // Tabs should be underlined at once + if( rInf.GetFont()->IsPaintBlank() ) + { + // Tabs with filling/filled tabs + const sal_uInt16 nCharWidth = rInf.GetTextSize(OUString(' ')).Width(); + + // Robust: + if( nCharWidth ) + { + // Always with kerning, also on printer! + sal_uInt16 nChar = Width() / nCharWidth; + OUStringBuffer aBuf; + comphelper::string::padToLength(aBuf, nChar, ' '); + rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0), + TextFrameIndex(nChar), true); + } + } + + // Display fill characters + if( IsFilled() ) + { + // Tabs with filling/filled tabs + const sal_uInt16 nCharWidth = rInf.GetTextSize(OUString(cFill)).Width(); + OSL_ENSURE( nCharWidth, "!SwTabPortion::Paint: sophisticated tabchar" ); + + // Robust: + if( nCharWidth ) + { + // Always with kerning, also on printer! + sal_uInt16 nChar = Width() / nCharWidth; + if ( cFill == '_' ) + ++nChar; // to avoid gaps + OUStringBuffer aBuf; + comphelper::string::padToLength(aBuf, nChar, cFill); + rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0), + TextFrameIndex(nChar), true); + } + } +} + +void SwAutoTabDecimalPortion::Paint( const SwTextPaintInfo & ) const +{ +} + +void SwTabPortion::HandlePortion( SwPortionHandler& rPH ) const +{ + rPH.Text( GetLen(), GetWhichPor(), Height(), Width() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/widorp.cxx b/sw/source/core/text/widorp.cxx new file mode 100644 index 000000000..c38aab287 --- /dev/null +++ b/sw/source/core/text/widorp.cxx @@ -0,0 +1,544 @@ +/* -*- 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 <layfrm.hxx> +#include <ftnboss.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> +#include <editeng/orphitem.hxx> +#include <editeng/widwitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/spltitem.hxx> +#include <frmatr.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <rowfrm.hxx> + +#include "widorp.hxx" +#include <txtfrm.hxx> +#include "itrtxt.hxx" +#include <sectfrm.hxx> +#include <ftnfrm.hxx> +#include <pagefrm.hxx> + +#undef WIDOWTWIPS + +namespace +{ + +// A Follow on the same page as its master is nasty. +bool IsNastyFollow( const SwTextFrame *pFrame ) +{ + OSL_ENSURE( !pFrame->IsFollow() || !pFrame->GetPrev() || + static_cast<const SwTextFrame*>(pFrame->GetPrev())->GetFollow() == pFrame, + "IsNastyFollow: What is going on here?" ); + return pFrame->IsFollow() && pFrame->GetPrev(); +} + +} + +SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, const SwTwips nRst ) + : m_nRstHeight(nRst), m_pFrame(pNewFrame) +{ + SwSwapIfSwapped swap(m_pFrame); + SwRectFnSet aRectFnSet(m_pFrame); + m_nOrigin = aRectFnSet.GetPrtTop(*m_pFrame); + m_bKeep = !m_pFrame->IsMoveable() || IsNastyFollow( m_pFrame ); + if( !m_bKeep && m_pFrame->IsInSct() ) + { + const SwSectionFrame* const pSct = m_pFrame->FindSctFrame(); + m_bKeep = pSct->Lower()->IsColumnFrame() && !pSct->MoveAllowed( m_pFrame ); + } + m_bKeep = m_bKeep || !m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetSplit().GetValue() || + m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep().GetValue(); + m_bBreak = false; + + if( !m_nRstHeight && !m_pFrame->IsFollow() && m_pFrame->IsInFootnote() && m_pFrame->HasPara() ) + { + m_nRstHeight = m_pFrame->GetFootnoteFrameHeight(); + m_nRstHeight += aRectFnSet.GetHeight(m_pFrame->getFramePrintArea()) - + aRectFnSet.GetHeight(m_pFrame->getFrameArea()); + if( m_nRstHeight < 0 ) + m_nRstHeight = 0; + } +} + +/** + * BP 18.6.93: Widows. + * In contrast to the first implementation the Widows are not calculated + * in advance but detected when formatting the split Follow. + * In Master the Widows-calculation is dropped completely + * (nWidows is manipulated). If the Follow detects that the + * Widows rule applies it sends a Prepare to its predecessor. + * A special problem is when the Widow rule applies but in Master + * there are some lines available. + * + * BP(22.07.92): Calculation of Widows and Orphans. + * The method returns true if one of the rules matches. + * + * One difficulty with Widows and different formats between + * Master- and Follow-Frame: + * Example: If the first column is 3cm and the second is 4cm and + * Widows is set to 3, the decision if the Widows rule matches can not + * be done until the Follow is formatted. Unfortunately this is crucial + * to decide if the whole paragraph goes to the next page or not. + */ +bool SwTextFrameBreak::IsInside( SwTextMargin const &rLine ) const +{ + bool bFit = false; + + SwSwapIfSwapped swap(m_pFrame); + SwRectFnSet aRectFnSet(m_pFrame); + // nOrigin is an absolute value, rLine refers to the swapped situation. + + SwTwips nTmpY; + if ( m_pFrame->IsVertical() ) + nTmpY = m_pFrame->SwitchHorizontalToVertical( rLine.Y() + rLine.GetLineHeight() ); + else + nTmpY = rLine.Y() + rLine.GetLineHeight(); + + SwTwips nLineHeight = aRectFnSet.YDiff( nTmpY , m_nOrigin ); + + // Calculate extra space for bottom border. + nLineHeight += aRectFnSet.GetBottomMargin(*m_pFrame); + + if( m_nRstHeight ) + bFit = m_nRstHeight >= nLineHeight; + else + { + // The Frame has a height to fit on the page. + SwTwips nHeight = + aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*m_pFrame->GetUpper()), m_nOrigin ); + SwTwips nDiff = nHeight - nLineHeight; + + // Hide whitespace may require not to insert a new page. + SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); + if (!pPageFrame->CheckPageHeightValidForHideWhitespace(nDiff)) + nDiff = 0; + + // If everything is inside the existing frame the result is true; + bFit = nDiff >= 0; + + if (!bFit && rLine.MaybeHasHints() && m_pFrame->GetFollow() + // if using same footnote container as the follow, pointless to try? + && m_pFrame->FindFootnoteBossFrame() != m_pFrame->GetFollow()->FindFootnoteBossFrame()) + { + // possibly a footnote that is anchored beyond the end of this + // (the last) line is in the way, try to remove it and check again + m_pFrame->RemoveFootnote(rLine.GetEnd()); + nHeight = aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*m_pFrame->GetUpper()), m_nOrigin ); + bFit = nHeight >= nLineHeight; + } + if ( !bFit ) + { + if ( rLine.GetNext() && + m_pFrame->IsInTab() && !m_pFrame->GetFollow() && !m_pFrame->GetIndNext() ) + { + // add additional space taken as lower space as last content in a table + // for all text lines except the last one. + nHeight += m_pFrame->CalcAddLowerSpaceAsLastInTableCell(); + bFit = nHeight >= nLineHeight; + } + } + if( !bFit ) + { + // The LineHeight exceeds the current Frame height. + // Call a test Grow to detect if the Frame could + // grow the requested area. + nHeight += m_pFrame->GrowTst( LONG_MAX ); + + // The Grow() returns the height by which the Upper of the TextFrame + // would let the TextFrame grow. + // The TextFrame itself can grow as much as it wants. + bFit = nHeight >= nLineHeight; + } + } + + return bFit; +} + +bool SwTextFrameBreak::IsBreakNow( SwTextMargin &rLine ) +{ + SwSwapIfSwapped swap(m_pFrame); + + // bKeep is stronger than IsBreakNow() + // Is there enough space ? + if( m_bKeep || IsInside( rLine ) ) + m_bBreak = false; + else + { + /* This class assumes that the SwTextMargin is processed from Top to + * Bottom. Because of performance reasons we stop splitting in the + * following cases: + * If only one line does not fit. + * Special case: with DummyPortions there is LineNr == 1, though we + * want to split. + */ + // Include DropLines + + bool bFirstLine = 1 == rLine.GetLineNr() && !rLine.GetPrev(); + m_bBreak = true; + if( ( bFirstLine && m_pFrame->GetIndPrev() ) + || ( rLine.GetLineNr() <= rLine.GetDropLines() ) ) + { + m_bKeep = true; + m_bBreak = false; + } + else if(bFirstLine && m_pFrame->IsInFootnote() && !m_pFrame->FindFootnoteFrame()->GetPrev()) + { + SwLayoutFrame* pTmp = m_pFrame->FindFootnoteBossFrame()->FindBodyCont(); + if( !pTmp || !pTmp->Lower() ) + m_bBreak = false; + } + } + + return m_bBreak; +} + +void SwTextFrameBreak::SetRstHeight( const SwTextMargin &rLine ) +{ + // Consider bottom margin + SwRectFnSet aRectFnSet(m_pFrame); + + m_nRstHeight = aRectFnSet.GetBottomMargin(*m_pFrame); + + if ( aRectFnSet.IsVert() ) + { + if ( m_pFrame->IsVertLR() ) + m_nRstHeight = aRectFnSet.YDiff( m_pFrame->SwitchHorizontalToVertical( rLine.Y() ) , m_nOrigin ); + else + m_nRstHeight += m_nOrigin - m_pFrame->SwitchHorizontalToVertical( rLine.Y() ); + } + else + m_nRstHeight += rLine.Y() - m_nOrigin; +} + +WidowsAndOrphans::WidowsAndOrphans( SwTextFrame *pNewFrame, const SwTwips nRst, + bool bChkKeep ) + : SwTextFrameBreak( pNewFrame, nRst ), nWidLines( 0 ), nOrphLines( 0 ) +{ + SwSwapIfSwapped swap(m_pFrame); + + if( m_bKeep ) + { + // If paragraph should not be split but is larger than + // the page, then bKeep is overruled. + if( bChkKeep && !m_pFrame->GetPrev() && !m_pFrame->IsInFootnote() && + m_pFrame->IsMoveable() && + ( !m_pFrame->IsInSct() || m_pFrame->FindSctFrame()->MoveAllowed(m_pFrame) ) ) + m_bKeep = false; + // Even if Keep is set, Orphans has to be respected. + // e.g. if there are chained frames where a Follow in the last frame + // receives a Keep, because it is not (forward) movable - + // nevertheless the paragraph can request lines from the Master + // because of the Orphan rule. + if( m_pFrame->IsFollow() ) + nWidLines = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetWidows().GetValue(); + } + else + { + const SwAttrSet& rSet = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet(); + const SvxOrphansItem &rOrph = rSet.GetOrphans(); + if ( rOrph.GetValue() > 1 ) + nOrphLines = rOrph.GetValue(); + if ( m_pFrame->IsFollow() ) + nWidLines = rSet.GetWidows().GetValue(); + + } + + if ( m_bKeep || nWidLines || nOrphLines ) + { + bool bResetFlags = false; + + if ( m_pFrame->IsInTab() ) + { + // For compatibility reasons, we disable Keep/Widows/Orphans + // inside splittable row frames: + if ( m_pFrame->GetNextCellLeaf() || m_pFrame->IsInFollowFlowRow() ) + { + const SwFrame* pTmpFrame = m_pFrame->GetUpper(); + while ( !pTmpFrame->IsRowFrame() ) + pTmpFrame = pTmpFrame->GetUpper(); + if ( static_cast<const SwRowFrame*>(pTmpFrame)->IsRowSplitAllowed() ) + bResetFlags = true; + } + } + + if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) + { + // Inside of footnotes there are good reasons to turn off the Keep attribute + // as well as Widows/Orphans. + SwFootnoteFrame *pFootnote = m_pFrame->FindFootnoteFrame(); + const bool bFt = !pFootnote->GetAttr()->GetFootnote().IsEndNote(); + if( !pFootnote->GetPrev() && + pFootnote->FindFootnoteBossFrame( bFt ) != pFootnote->GetRef()->FindFootnoteBossFrame( bFt ) + && ( !m_pFrame->IsInSct() || m_pFrame->FindSctFrame()->MoveAllowed(m_pFrame) ) ) + { + bResetFlags = true; + } + } + + if ( bResetFlags ) + { + m_bKeep = false; + nOrphLines = 0; + nWidLines = 0; + } + } +} + +/** + * The Find*-Methods do not only search, but adjust the SwTextMargin to the + * line where the paragraph should have a break and truncate the paragraph there. + * FindBreak() + */ +bool WidowsAndOrphans::FindBreak( SwTextFrame *pFrame, SwTextMargin &rLine, + bool bHasToFit ) +{ + // i#16128 - Why member <pFrame> _*and*_ parameter <pFrame>?? + // Thus, assertion on situation, that these are different to figure out why. + OSL_ENSURE( m_pFrame == pFrame, "<WidowsAndOrphans::FindBreak> - pFrame != pFrame" ); + + SwSwapIfSwapped swap(m_pFrame); + + bool bRet = true; + sal_uInt16 nOldOrphans = nOrphLines; + if( bHasToFit ) + nOrphLines = 0; + rLine.Bottom(); + + if( !IsBreakNowWidAndOrp( rLine ) ) + bRet = false; + if( !FindWidows( pFrame, rLine ) ) + { + bool bBack = false; + + while( IsBreakNowWidAndOrp( rLine ) ) + { + if( rLine.PrevLine() ) + bBack = true; + else + break; + } + // Usually Orphans are not taken into account for HasToFit. + // But if Dummy-Lines are concerned and the Orphans rule is violated + // we make an exception: We leave behind one Dummyline and take + // the whole text to the next page/column. + if( rLine.GetLineNr() <= nOldOrphans && + rLine.GetInfo().GetParaPortion()->IsDummy() && + ( ( bHasToFit && bRet ) || IsBreakNow( rLine ) ) ) + rLine.Top(); + + rLine.TruncLines( true ); + bRet = bBack; + } + nOrphLines = nOldOrphans; + + return bRet; +} + +/** + * FindWidows positions the SwTextMargin of the Master to the line where to + * break by examining and formatting the Follow. + * Returns true if the Widows-rule matches, that means that the + * paragraph should not be split (keep) ! + */ +bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) +{ + OSL_ENSURE( ! pFrame->IsVertical() || ! pFrame->IsSwapped(), + "WidowsAndOrphans::FindWidows with swapped frame" ); + + if( !nWidLines || !pFrame->IsFollow() ) + return false; + + rLine.Bottom(); + + // We can still cut something off + SwTextFrame *pMaster = pFrame->FindMaster(); + OSL_ENSURE(pMaster, "+WidowsAndOrphans::FindWidows: Widows in a master?"); + if( !pMaster ) + return false; + + // If the first line of the Follow does not fit, the master + // probably is full of Dummies. In this case a PrepareHint::Widows would be fatal. + if( pMaster->GetOffset() == pFrame->GetOffset() ) + return false; + + // Remaining height of the master + SwRectFnSet aRectFnSet(pFrame); + + const SwTwips nDocPrtTop = aRectFnSet.GetPrtTop(*pFrame); + SwTwips nOldHeight; + SwTwips nTmpY = rLine.Y() + rLine.GetLineHeight(); + + if ( aRectFnSet.IsVert() ) + { + nTmpY = pFrame->SwitchHorizontalToVertical( nTmpY ); + nOldHeight = -aRectFnSet.GetHeight(pFrame->getFramePrintArea()); + } + else + nOldHeight = aRectFnSet.GetHeight(pFrame->getFramePrintArea()); + + const SwTwips nChg = aRectFnSet.YDiff( nTmpY, nDocPrtTop + nOldHeight ); + + // below the Widows-threshold... + if( rLine.GetLineNr() >= nWidLines ) + { + // Follow to Master I + // If the Follow *grows*, there is the chance for the Master to + // receive lines, that it was forced to hand over to the Follow lately: + // Prepare(Need); check that below nChg! + // (0W, 2O, 2M, 2F) + 1F = 3M, 2F + if( rLine.GetLineNr() > nWidLines && pFrame->IsJustWidow() ) + { + // If the Master is locked, it has probably just donated a line + // to us, we don't return that just because we turned it into + // multiple lines (e.g. via frames). + if( !pMaster->IsLocked() && pMaster->GetUpper() ) + { + const SwTwips nTmpRstHeight = aRectFnSet.BottomDist( pMaster->getFrameArea(), + aRectFnSet.GetPrtBottom(*pMaster->GetUpper()) ); + if ( nTmpRstHeight >= + SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) ) + { + pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pMaster->InvalidateSize_(); + pMaster->InvalidatePage(); + } + } + + pFrame->SetJustWidow( false ); + } + return false; + } + + // Follow to Master II + // If the Follow *shrinks*, maybe the Master can absorb the whole Orphan. + // (0W, 2O, 2M, 1F) - 1F = 3M, 0F -> PrepareHint::AdjustSizeWithoutFormatting + // (0W, 2O, 3M, 2F) - 1F = 2M, 2F -> PrepareHint::Widows + + if( 0 > nChg && !pMaster->IsLocked() && pMaster->GetUpper() ) + { + SwTwips nTmpRstHeight = aRectFnSet.BottomDist( pMaster->getFrameArea(), + aRectFnSet.GetPrtBottom(*pMaster->GetUpper()) ); + if( nTmpRstHeight >= SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) ) + { + pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pMaster->InvalidateSize_(); + pMaster->InvalidatePage(); + pFrame->SetJustWidow( false ); + return false; + } + } + + // Master to Follow + // If the Follow contains fewer lines than Widows after formatting, + // we still can move over some lines from the Master. If this triggers + // the Orphans rule of the Master, the Master frame must be Grow()n + // in its CalcPreps(), such that it won't fit onto its page anymore. + // But if the Master Frame can still lose a few lines, we need to + // do a Shrink() in the CalcPreps(); the Follow with the Widows then + // moves onto the page of the Master, but remains unsplit, so that + // it (finally) moves onto the next page. So much for the theory! + // + // We only request one line at a time for now, because a Master's line + // could result in multiple lines for us. + // Therefore, the CalcFollow() remains in control until the Follow got all + // necessary lines. + sal_uInt16 nNeed = 1; // was: nWidLines - rLine.GetLineNr(); + + // Special case: Master cannot give lines to follow + // i#91421 + if ( !pMaster->GetIndPrev() ) + { + pMaster->ChgThisLines(); + sal_uLong nLines = pMaster->GetThisLines(); + if(nLines == 0 && pMaster->HasPara()) + { + const SwParaPortion *pMasterPara = pMaster->GetPara(); + if(pMasterPara && pMasterPara->GetNext()) + nLines = 2; + } + if( nLines <= nNeed ) + return false; + } + + pMaster->Prepare( PrepareHint::Widows, static_cast<void*>(&nNeed) ); + return true; +} + +bool WidowsAndOrphans::WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTst ) +{ + // Here it does not matter, if pFrame is swapped or not. + // IsInside() takes care of itself + + // We expect that rLine is set to the last line + OSL_ENSURE( !rLine.GetNext(), "WouldFit: aLine::Bottom missed!" ); + sal_uInt16 nLineCnt = rLine.GetLineNr(); + + // First satisfy the Orphans-rule and the wish for initials ... + const sal_uInt16 nMinLines = std::max( GetOrphansLines(), rLine.GetDropLines() ); + if ( nLineCnt < nMinLines ) + return false; + + rLine.Top(); + SwTwips nLineSum = rLine.GetLineHeight(); + + while( nMinLines > rLine.GetLineNr() ) + { + if( !rLine.NextLine() ) + return false; + nLineSum += rLine.GetLineHeight(); + } + + // We do not fit + if( !IsInside( rLine ) ) + return false; + + // Check the Widows-rule + if( !nWidLines && !m_pFrame->IsFollow() ) + { + // Usually we only have to check for Widows if we are a Follow. + // On WouldFit the rule has to be checked for the Master too, + // because we are just in the middle of calculating the break. + // In Ctor of WidowsAndOrphans the nWidLines are only calced for + // Follows from the AttrSet - so we catch up now: + const SwAttrSet& rSet = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet(); + nWidLines = rSet.GetWidows().GetValue(); + } + + // After Orphans/Initials, do enough lines remain for Widows? + // If we are currently doing a test formatting, we may not + // consider the widows rule for two reasons: + // 1. The columns may have different widths. + // Widow lines would have wrong width. + // 2. Test formatting is only done up to the given space. + // we do not have any lines for widows at all. + if( bTst || nLineCnt - nMinLines >= nWidLines ) + { + if( rMaxHeight >= nLineSum ) + { + rMaxHeight -= nLineSum; + return true; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/widorp.hxx b/sw/source/core/text/widorp.hxx new file mode 100644 index 000000000..9c0e37fc2 --- /dev/null +++ b/sw/source/core/text/widorp.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_WIDORP_HXX +#define INCLUDED_SW_SOURCE_CORE_TEXT_WIDORP_HXX +class SwTextFrame; + +#include <swtypes.hxx> +#include "itrtxt.hxx" + +class SwTextFrameBreak +{ +private: + SwTwips m_nRstHeight; + SwTwips m_nOrigin; +protected: + SwTextFrame *m_pFrame; + bool m_bBreak; + bool m_bKeep; +public: + SwTextFrameBreak( SwTextFrame *pFrame, const SwTwips nRst = 0 ); + bool IsBreakNow( SwTextMargin &rLine ); + bool IsKeepAlways() const { return m_bKeep; } + + void SetKeep( const bool bNew ) { m_bKeep = bNew; } + + bool IsInside( SwTextMargin const &rLine ) const; + + // In order to be able to handle special cases with Footnote. + // SetRstHeight sets the rest height for SwTextFrameBreak. This is needed + // to call TruncLines() without IsBreakNow() returning another value. + // We assume that rLine is pointing to the last non-fitting line. + + void SetRstHeight( const SwTextMargin &rLine ); +}; + +class WidowsAndOrphans : public SwTextFrameBreak +{ +private: + sal_uInt16 nWidLines, nOrphLines; + +public: + WidowsAndOrphans( SwTextFrame *pFrame, const SwTwips nRst = 0, + bool bCheckKeep = true ); + bool FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ); + sal_uInt16 GetOrphansLines() const + { return nOrphLines; } + void ClrOrphLines(){ nOrphLines = 0; } + + bool FindBreak( SwTextFrame *pFrame, SwTextMargin &rLine, bool bHasToFit ); + bool WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTest ); + // i#16128 - This method is named this way to avoid confusion with + // base class method <SwTextFrameBreak::IsBreakNow>, which isn't virtual. + bool IsBreakNowWidAndOrp( SwTextMargin &rLine ) + { + bool isOnFirstLine = (rLine.GetLineNr() == 1 && !rLine.GetPrev()); + if ( isOnFirstLine && rLine.GetCurr()->IsDummy()) { + return IsBreakNow( rLine ); + } + if ( rLine.GetLineNr() > nOrphLines ) { + return IsBreakNow( rLine ); + } + return false; + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/wrong.cxx b/sw/source/core/text/wrong.cxx new file mode 100644 index 000000000..5a70c7be9 --- /dev/null +++ b/sw/source/core/text/wrong.cxx @@ -0,0 +1,937 @@ +/* -*- 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 <swtypes.hxx> + +#include <SwGrammarMarkUp.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> + +#include <osl/diagnose.h> + +SwWrongArea::SwWrongArea( const OUString& rType, WrongListType listType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nPos, + sal_Int32 nLen) +: maType(rType), mnPos(nPos), mnLen(nLen), mpSubList(nullptr) +{ + mColor = getWrongAreaColor(listType, xPropertyBag); + mLineType = getWrongAreaLineType(listType, xPropertyBag); +} + +SwWrongArea::SwWrongArea( const OUString& rType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nPos, + sal_Int32 nLen, + SwWrongList* pSubList) +: maType(rType), mnPos(nPos), mnLen(nLen), mpSubList(pSubList), mLineType(WRONGAREA_NONE) +{ + if (pSubList != nullptr) + { + mColor = getWrongAreaColor(pSubList->GetWrongListType(), xPropertyBag); + mLineType = getWrongAreaLineType(pSubList->GetWrongListType(), xPropertyBag); + } +} + +SwWrongList::SwWrongList( WrongListType eType ) : + meType (eType), + mnBeginInvalid(COMPLETE_STRING), // everything correct... (the invalid area starts beyond the string) + mnEndInvalid (COMPLETE_STRING) +{ + maList.reserve( 5 ); +} + +SwWrongList::~SwWrongList() +{ + ClearList(); +} + +SwWrongList* SwWrongList::Clone() +{ + SwWrongList* pClone = new SwWrongList( meType ); + pClone->CopyFrom( *this ); + return pClone; +} + +void SwWrongList::CopyFrom( const SwWrongList& rCopy ) +{ + maList = rCopy.maList; + meType = rCopy.meType; + mnBeginInvalid = rCopy.mnBeginInvalid; + mnEndInvalid = rCopy.mnEndInvalid; + for(SwWrongArea & i : maList) + { + if( i.mpSubList ) + i.mpSubList = i.mpSubList->Clone(); + } +} + +void SwWrongList::ClearList() +{ + for (SwWrongArea & i : maList) + { + delete i.mpSubList; + i.mpSubList = nullptr; + } + maList.clear(); +} + +/** If a word is incorrectly selected, this method returns begin and length of it. + + @param[in,out] rChk starting position of the word to check + @param[out] rLn length of the word + + @return <true> if incorrectly selected, <false> otherwise + */ +bool SwWrongList::InWrongWord( sal_Int32 &rChk, sal_Int32 &rLn ) const +{ + const sal_uInt16 nPos = GetWrongPos( rChk ); + if ( nPos >= Count() ) + return false; + const sal_Int32 nWrPos = Pos( nPos ); + if ( nWrPos <= rChk ) + { + rLn = Len( nPos ); + if( nWrPos + rLn <= rChk ) + return false; + rChk = nWrPos; + return true; + } + return false; +} + +/** Calculate first incorrectly selected area. + + @param[in,out] rChk starting position of the word to check + @param[in,out] rLn length of the word + + @return <true> if incorrectly selected area was found, <false> otherwise + */ +bool SwWrongList::Check( sal_Int32 &rChk, sal_Int32 &rLn ) const +{ + sal_uInt16 nPos = GetWrongPos( rChk ); + rLn += rChk; + + if( nPos == Count() ) + return false; + + sal_Int32 nWrPos = Pos( nPos ); + sal_Int32 nEnd = nWrPos + Len( nPos ); + if( nEnd == rChk ) + { + ++nPos; + if( nPos == Count() ) + return false; + + nWrPos = Pos( nPos ); + nEnd = nWrPos + Len( nPos ); + } + if( nEnd > rChk && nWrPos < rLn ) + { + if( nWrPos > rChk ) + rChk = nWrPos; + if( nEnd < rLn ) + rLn = nEnd; + rLn -= rChk; + return 0 != rLn; + } + return false; +} + +/** Find next incorrectly selected position. + + @param[in] rChk starting position of the word to check + + @return starting position of incorrectly selected area, <COMPLETE_STRING> otherwise + */ +sal_Int32 SwWrongList::NextWrong( sal_Int32 nChk ) const +{ + sal_Int32 nRet = COMPLETE_STRING; + sal_uInt16 nPos = GetWrongPos( nChk ); + if( nPos < Count() ) + { + nRet = Pos( nPos ); + if( nRet < nChk && nRet + Len( nPos ) <= nChk ) + { + if( ++nPos < Count() ) + nRet = Pos( nPos ); + else + nRet = COMPLETE_STRING; + } + } + if( nRet > GetBeginInv() && nChk < GetEndInv() ) + nRet = std::max(nChk, GetBeginInv()); + return nRet; +} + +/** Find the first position that is greater or equal to the given value. + + @note Resulting position might be behind the last element of the array. + @param[in] nValue value for comparison + + @return first position that is greater or equal to the given value + */ +sal_uInt16 SwWrongList::GetWrongPos( sal_Int32 nValue ) const +{ + sal_uInt16 nMax = Count(); + sal_uInt16 nMin = 0; + + if( nMax > 0 ) + { + // For smart tag lists, we may not use a binary search. We return the + // position of the first smart tag which covers nValue + if ( !maList[0].maType.isEmpty() || maList[0].mpSubList ) + { + auto aIter = std::find_if(maList.begin(), maList.end(), + [nValue](const SwWrongArea& rST) { + return (rST.mnPos <= nValue && nValue < rST.mnPos + rST.mnLen) + || (rST.mnPos > nValue); + }); + return static_cast<sal_uInt16>(std::distance(maList.begin(), aIter)); + } + + --nMax; + sal_uInt16 nMid = 0; + while( nMin <= nMax ) + { + nMid = nMin + ( nMax - nMin ) / 2; + const sal_Int32 nTmp = Pos( nMid ); + if( nTmp == nValue ) + { + nMin = nMid; + break; + } + else if( nTmp < nValue ) + { + if( nTmp + Len( nMid ) >= nValue ) + { + nMin = nMid; + break; + } + nMin = nMid + 1; + } + else if( nMid == 0 ) + { + break; + } + else + nMax = nMid - 1; + } + } + + // nMin now points to an index i into the wrong list which + // 1. nValue is inside [ Area[i].pos, Area[i].pos + Area[i].len ] (inclusive!!!) + // 2. nValue < Area[i].pos + + return nMin; +} + +void SwWrongList::Invalidate_( sal_Int32 nBegin, sal_Int32 nEnd ) +{ + if ( nBegin < GetBeginInv() ) + mnBeginInvalid = nBegin; + if ( nEnd > GetEndInv() || GetEndInv() == COMPLETE_STRING ) + mnEndInvalid = nEnd; +} + +void SwWrongList::SetInvalid( sal_Int32 nBegin, sal_Int32 nEnd ) +{ + mnBeginInvalid = nBegin; + mnEndInvalid = nEnd; +} + +/** Change all values after the given position. + + Needed after insert/deletion of characters. + + @param nPos position after that everything should be changed + @param nDiff amount how much the positions should be moved + */ +void SwWrongList::Move( sal_Int32 nPos, sal_Int32 nDiff ) +{ + sal_uInt16 i = GetWrongPos( nPos ); + if( nDiff < 0 ) + { + const sal_Int32 nEnd = nPos - nDiff; + sal_uInt16 nLst = i; + bool bJump = false; + while( nLst < Count() && Pos( nLst ) < nEnd ) + ++nLst; + if( nLst > i ) + { + const sal_Int32 nWrPos = Pos( nLst - 1 ); + if ( nWrPos <= nPos ) + { + sal_Int32 nWrLen = Len( nLst - 1 ); + // calculate new length of word + nWrLen = ( nEnd > nWrPos + nWrLen ) ? + nPos - nWrPos : + nWrLen + nDiff; + if( nWrLen ) + { + maList[--nLst].mnLen = nWrLen; + bJump = true; + } + } + } + Remove( i, nLst - i ); + + if ( bJump ) + ++i; + if( COMPLETE_STRING == GetBeginInv() ) + SetInvalid( nPos ? nPos - 1 : nPos, nPos + 1 ); + else + { + ShiftLeft( mnBeginInvalid, nPos, nEnd ); + if( mnEndInvalid != COMPLETE_STRING ) + ShiftLeft( mnEndInvalid, nPos, nEnd ); + Invalidate_( nPos ? nPos - 1 : nPos, nPos + 1 ); + } + } + else + { + const sal_Int32 nEnd = nPos + nDiff; + if( COMPLETE_STRING != GetBeginInv() ) + { + if( mnBeginInvalid > nPos ) + mnBeginInvalid += nDiff; + if( mnEndInvalid >= nPos && mnEndInvalid != COMPLETE_STRING ) + mnEndInvalid += nDiff; + } + // If the pointer is in the middle of a wrong word, + // invalidation must happen from the beginning of that word. + if( i < Count() ) + { + const sal_Int32 nWrPos = Pos( i ); + if (nPos >= nWrPos) + { + Invalidate( nWrPos, nEnd ); + const sal_Int32 nWrLen = Len( i ) + nDiff; + maList[i++].mnLen = nWrLen; + Invalidate( nWrPos, nWrPos + nWrLen ); + } + } + else + Invalidate( nPos, nEnd ); + } + while( i < Count() ) + { + maList[i++].mnPos += nDiff; + } +} + +// TODO: Complete documentation +/** Remove given range of entries + + For a given range [nPos, nPos + nLen[ and an index nIndex, this function + basically counts the number of SwWrongArea entries starting with nIndex + up to nPos + nLen. All these entries are removed. + + @param rStart ??? + @param rEnd ??? + @param nPos starting position of the range + @param nLen length of the range + @param nIndex index to start lookup at + @param nCursorPos ??? + + @return <true> if ??? + */ +auto SwWrongList::Fresh( sal_Int32 &rStart, sal_Int32 &rEnd, sal_Int32 nPos, + sal_Int32 nLen, sal_uInt16 nIndex, sal_Int32 nCursorPos ) -> FreshState +{ + // length of word must be greater than 0 + // only report a spelling error if the cursor position is outside the word, + // so that the user is not annoyed while typing + FreshState eRet = nLen + ? (nCursorPos > nPos + nLen || nCursorPos < nPos) + ? FreshState::FRESH + : FreshState::CURSOR + : FreshState::NOTHING; + + sal_Int32 nWrPos = 0; + sal_Int32 nWrEnd = rEnd; + sal_uInt16 nCnt = nIndex; + if( nCnt < Count() ) + { + nWrPos = Pos( nCnt ); + if( nWrPos < nPos && rStart > nWrPos ) + rStart = nWrPos; + } + + while( nCnt < Count() ) + { + nWrPos = Pos( nCnt ); + if ( nWrPos >= nPos ) + break; + nWrEnd = nWrPos + Len( nCnt++ ); + } + + if( nCnt < Count() && nWrPos == nPos && Len( nCnt ) == nLen ) + { + ++nCnt; + eRet = FreshState::FRESH; + } + else + { + if (FreshState::FRESH == eRet) + { + if( rStart > nPos ) + rStart = nPos; + nWrEnd = nPos + nLen; + } + } + + nPos += nLen; + + if( nCnt < Count() ) + { + nWrPos = Pos( nCnt ); + if( nWrPos < nPos && rStart > nWrPos ) + rStart = nWrPos; + } + + while( nCnt < Count() ) + { + nWrPos = Pos( nCnt ); + if ( nWrPos >= nPos ) + break; + nWrEnd = nWrPos + Len( nCnt++ ); + } + + if( rEnd < nWrEnd ) + rEnd = nWrEnd; + + Remove( nIndex, nCnt - nIndex ); + + return eRet; +} + +void SwWrongList::Invalidate( sal_Int32 nBegin, sal_Int32 nEnd ) +{ + if (COMPLETE_STRING == GetBeginInv()) + SetInvalid( nBegin, nEnd ); + else + Invalidate_( nBegin, nEnd ); +} + +bool SwWrongList::InvalidateWrong( ) +{ + if( Count() ) + { + const sal_Int32 nFirst = Pos( 0 ); + const sal_Int32 nLast = Pos( Count() - 1 ) + Len( Count() - 1 ); + Invalidate( nFirst, nLast ); + return true; + } + return false; +} + +SwWrongList* SwWrongList::SplitList( sal_Int32 nSplitPos ) +{ + SwWrongList *pRet = nullptr; + sal_uInt16 nLst = 0; + while( nLst < Count() && Pos( nLst ) < nSplitPos ) + ++nLst; + if( nLst ) + { + sal_Int32 nWrPos = Pos( nLst - 1 ); + sal_Int32 nWrLen = Len( nLst - 1 ); + if ( nWrPos+nWrLen > nSplitPos ) + { + nWrLen += nWrPos - nSplitPos; + maList[--nLst].mnPos = nSplitPos; + maList[nLst].mnLen = nWrLen; + } + } + if( nLst ) + { + if( WRONGLIST_GRAMMAR == GetWrongListType() ) + pRet = new SwGrammarMarkUp(); + else + pRet = new SwWrongList( GetWrongListType() ); + pRet->Insert(0, maList.begin(), ( nLst >= maList.size() ? maList.end() : maList.begin() + nLst ) ); + pRet->SetInvalid( GetBeginInv(), GetEndInv() ); + pRet->Invalidate_( nSplitPos ? nSplitPos - 1 : nSplitPos, nSplitPos ); + Remove( 0, nLst ); + } + if( COMPLETE_STRING == GetBeginInv() ) + SetInvalid( 0, 1 ); + else + { + ShiftLeft( mnBeginInvalid, 0, nSplitPos ); + if( mnEndInvalid != COMPLETE_STRING ) + ShiftLeft( mnEndInvalid, 0, nSplitPos ); + Invalidate_( 0, 1 ); + } + for (nLst = 0; nLst < Count(); ++nLst ) + { + maList[nLst].mnPos -= nSplitPos; + } + return pRet; +} + +void SwWrongList::JoinList( SwWrongList* pNext, sal_Int32 nInsertPos ) +{ + if (pNext) + { + OSL_ENSURE( GetWrongListType() == pNext->GetWrongListType(), "type mismatch with next list" ); + + sal_uInt16 nCnt = Count(); + pNext->Move( 0, nInsertPos ); + Insert(nCnt, pNext->maList.begin(), pNext->maList.end()); + + Invalidate( pNext->GetBeginInv(), pNext->GetEndInv() ); + if( nCnt && Count() > nCnt ) + { + sal_Int32 nWrPos = Pos( nCnt ); + sal_Int32 nWrLen = Len( nCnt ); + if( !nWrPos ) + { + nWrPos += nInsertPos; + nWrLen -= nInsertPos; + maList[nCnt].mnPos = nWrPos; + maList[nCnt].mnLen = nWrLen; + } + if( nWrPos == Pos( nCnt - 1 ) + Len( nCnt - 1 ) ) + { + nWrLen += Len( nCnt - 1 ); + maList[nCnt - 1].mnLen = nWrLen; + Remove( nCnt, 1 ); + } + } + } + Invalidate( nInsertPos ? nInsertPos - 1 : nInsertPos, nInsertPos + 1 ); +} + +void SwWrongList::InsertSubList( sal_Int32 nNewPos, sal_Int32 nNewLen, sal_uInt16 nWhere, SwWrongList* pSubList ) +{ + if (pSubList) + { + OSL_ENSURE( GetWrongListType() == pSubList->GetWrongListType(), "type mismatch with sub list" ); + } + std::vector<SwWrongArea>::iterator i = maList.begin(); + if ( nWhere >= maList.size() ) + i = maList.end(); // robust + else + i += nWhere; + maList.insert(i, SwWrongArea( OUString(), nullptr, nNewPos, nNewLen, pSubList ) ); +} + +// New functions: Necessary because SwWrongList has been changed to use std::vector +void SwWrongList::Insert(sal_uInt16 nWhere, std::vector<SwWrongArea>::iterator startPos, std::vector<SwWrongArea>::iterator const & endPos) +{ + std::vector<SwWrongArea>::iterator i = maList.begin(); + if ( nWhere >= maList.size() ) + i = maList.end(); // robust + else + i += nWhere; + maList.insert(i, startPos, endPos); // insert [startPos, endPos[ before i + + // ownership of the sublist is passed to maList, therefore we have to set the + // pSubList-Pointers to 0 + while ( startPos != endPos ) + { + (*startPos).mpSubList = nullptr; + ++startPos; + } +} + +void SwWrongList::Remove(sal_uInt16 nIdx, sal_uInt16 nLen ) +{ + if ( nIdx >= maList.size() ) return; + std::vector<SwWrongArea>::iterator i1 = maList.begin(); + i1 += nIdx; + + std::vector<SwWrongArea>::iterator i2 = i1; + if ( nIdx + nLen >= static_cast<sal_uInt16>(maList.size()) ) + i2 = maList.end(); // robust + else + i2 += nLen; + + std::vector<SwWrongArea>::iterator iLoop = i1; + while ( iLoop != i2 ) + { + delete (*iLoop).mpSubList; + ++iLoop; + } + +#if OSL_DEBUG_LEVEL > 0 + const int nOldSize = Count(); +#endif + + maList.erase(i1, i2); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( Count() + nLen == nOldSize, "SwWrongList::Remove() trouble" ); +#endif +} + +void SwWrongList::RemoveEntry( sal_Int32 nBegin, sal_Int32 nEnd ) { + std::vector<SwWrongArea>::const_iterator aEnd(maList.end()); + auto aDelIter = std::find_if(maList.cbegin(), aEnd, + [nBegin](const SwWrongArea& rST) { return rST.mnPos >= nBegin; }); + auto aIter = aDelIter; + if( WRONGLIST_GRAMMAR == GetWrongListType() ) + { + if( nBegin < nEnd ) + { + aIter = std::find_if(aDelIter, aEnd, + [nEnd](const SwWrongArea& rST) { return rST.mnPos >= nEnd; }); + } + } + else + { + aIter = std::find_if(aDelIter, aEnd, + [nBegin, nEnd](const SwWrongArea& rST) { + return (rST.mnPos != nBegin) || ((rST.mnPos + rST.mnLen) != nEnd); + }); + } + auto nDel = static_cast<sal_uInt16>(std::distance(aDelIter, aIter)); + if( nDel ) + { + auto nDelPos = static_cast<sal_uInt16>(std::distance(maList.cbegin(), aDelIter)); + Remove( nDelPos, nDel ); + } +} + +bool SwWrongList::LookForEntry( sal_Int32 nBegin, sal_Int32 nEnd ) { + auto aIter = std::find_if(maList.begin(), maList.end(), + [nBegin](const SwWrongArea& rST) { return rST.mnPos >= nBegin; }); + return aIter != maList.end() + && nBegin == (*aIter).mnPos + && nEnd == (*aIter).mnPos + (*aIter).mnLen; +} + +void SwWrongList::Insert( const OUString& rType, + css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, + sal_Int32 nNewPos, sal_Int32 nNewLen ) +{ + auto aIter = std::find_if(maList.begin(), maList.end(), + [nNewPos](const SwWrongArea& rST) { return nNewPos <= rST.mnPos; }); + if ( aIter != maList.end() && nNewPos == (*aIter).mnPos ) + { + const sal_Int32 nSTPos = (*aIter).mnPos; + + aIter = std::find_if(aIter, maList.end(), + [nSTPos, nNewLen](const SwWrongArea& rST) { return rST.mnPos != nSTPos || nNewLen < rST.mnLen; }); + } + + maList.insert(aIter, SwWrongArea( rType, meType, xPropertyBag, nNewPos, nNewLen) ); +} + +namespace sw { + +WrongListIteratorBase::WrongListIteratorBase(SwTextFrame const& rFrame, + SwWrongList const* (SwTextNode::*pGetWrongList)() const) + : m_pGetWrongList(pGetWrongList) + , m_pMergedPara(rFrame.GetMergedPara()) + , m_CurrentExtent(0) + , m_CurrentIndex(0) + , m_pWrongList(m_pMergedPara + ? nullptr + : (rFrame.GetTextNodeFirst()->*pGetWrongList)()) +{ +} + +WrongListIteratorBase::WrongListIteratorBase(SwWrongList const& rWrongList) + : m_pGetWrongList(nullptr) + , m_pMergedPara(nullptr) + , m_CurrentExtent(0) + , m_CurrentIndex(0) + , m_pWrongList(&rWrongList) +{ +} + +WrongListIterator::WrongListIterator(SwTextFrame const& rFrame, + SwWrongList const* (SwTextNode::*pGetWrongList)() const) + : WrongListIteratorBase(rFrame, pGetWrongList) +{ +} + +WrongListIterator::WrongListIterator(SwWrongList const& rWrongList) + : WrongListIteratorBase(rWrongList) +{ +} + +bool WrongListIterator::Check(TextFrameIndex & rStart, TextFrameIndex & rLen) +{ + if (m_pMergedPara) + { + if (rStart < m_CurrentIndex) + { // rewind + m_CurrentExtent = 0; + m_CurrentIndex = TextFrameIndex(0); + } + while (m_CurrentExtent < m_pMergedPara->extents.size()) + { + sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]); + if (rStart + rLen <= m_CurrentIndex) + { + return false; + } + else if (rStart < m_CurrentIndex) + { + rLen -= m_CurrentIndex - rStart; + assert(0 < sal_Int32(rLen)); + rStart = m_CurrentIndex; + } + if (m_CurrentIndex <= rStart && + rStart < m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart)) + { + SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)()); + // found the extent containing start - first, call Check + sal_Int32 nStart(rExtent.nStart + sal_Int32(rStart - m_CurrentIndex)); // (m_CurrentIndex - m_CurrentNodeIndex)); + sal_Int32 nLen; + if (sal_Int32(rLen) < rExtent.nEnd - nStart) + { + nLen = sal_Int32(rLen); + } + else + { + sal_Int32 nInLen(rLen); + nLen = rExtent.nEnd - nStart; + nInLen -= nLen; + for (size_t i = m_CurrentExtent + 1; + i < m_pMergedPara->extents.size(); ++i) + { + sw::Extent const& rExtentEnd(m_pMergedPara->extents[i]); + if (rExtentEnd.pNode != rExtent.pNode) + { + break; + } + // add gap too + nLen += rExtentEnd.nStart - m_pMergedPara->extents[i-1].nEnd; + if (nInLen <= rExtentEnd.nEnd - rExtentEnd.nStart) + { + nLen += nInLen; + break; + } + nLen += rExtentEnd.nEnd - rExtentEnd.nStart; + nInLen -= rExtentEnd.nEnd - rExtentEnd.nStart; + } + } + if (pWrongList && pWrongList->Check(nStart, nLen)) + { + // check if there's overlap with this extent + if (rExtent.nStart <= nStart && nStart < rExtent.nEnd) + { + // yes - now compute end position / length + sal_Int32 const nEnd(nStart + nLen); + rStart = m_CurrentIndex + TextFrameIndex(nStart - rExtent.nStart); + TextFrameIndex const nOrigLen(rLen); + if (nEnd <= rExtent.nEnd) + { + rLen = TextFrameIndex(nEnd - nStart); + } + else // have to search other extents for the end... + { + rLen = TextFrameIndex(rExtent.nEnd - nStart); + for (size_t i = m_CurrentExtent + 1; + i < m_pMergedPara->extents.size(); ++i) + { + sw::Extent const& rExtentEnd(m_pMergedPara->extents[i]); + if (rExtentEnd.pNode != rExtent.pNode + || nEnd <= rExtentEnd.nStart) + { + break; + } + if (nEnd <= rExtentEnd.nEnd) + { + rLen += TextFrameIndex(nEnd - rExtentEnd.nStart); + break; + } + rLen += TextFrameIndex(rExtentEnd.nEnd - rExtentEnd.nStart); + } + } + assert(rLen <= nOrigLen); (void) nOrigLen; + return true; + } + } + } + m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart); + ++m_CurrentExtent; + } + return false; + } + else if (m_pWrongList) + { + sal_Int32 nStart(rStart); + sal_Int32 nLen(rLen); + bool const bRet(m_pWrongList->Check(nStart, nLen)); + rStart = TextFrameIndex(nStart); + rLen = TextFrameIndex(nLen); + return bRet; + } + return false; +} + +const SwWrongArea* +WrongListIterator::GetWrongElement(TextFrameIndex const nStart) +{ + if (m_pMergedPara) + { + if (nStart < m_CurrentIndex) + { // rewind + m_CurrentExtent = 0; + m_CurrentIndex = TextFrameIndex(0); + } + while (m_CurrentExtent < m_pMergedPara->extents.size()) + { + sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]); + if (m_CurrentIndex <= nStart && + nStart <= m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart)) + { + // note: the returned object isn't wrapped because fntcache.cxx + // does not look at its positions, only its formatting props + SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)()); + if (pWrongList) + { + sal_Int32 const nNStart(rExtent.nStart + sal_Int32(nStart - m_CurrentIndex)); // (m_CurrentIndex - m_CurrentNodeIndex)); + sal_Int16 const nPos(pWrongList->GetWrongPos(nNStart)); + return pWrongList->GetElement(nPos); + } + } + m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart); + ++m_CurrentExtent; + } + return nullptr; + } + else if (m_pWrongList) + { + sal_Int16 const nPos(m_pWrongList->GetWrongPos(sal_Int32(nStart))); + return m_pWrongList->GetElement(nPos); + } + return nullptr; +} + +WrongListIteratorCounter::WrongListIteratorCounter(SwTextFrame const& rFrame, + SwWrongList const* (SwTextNode::*pGetWrongList)() const) + : WrongListIteratorBase(rFrame, pGetWrongList) +{ +} + +WrongListIteratorCounter::WrongListIteratorCounter(SwWrongList const& rWrongList) + : WrongListIteratorBase(rWrongList) +{ +} + +sal_uInt16 WrongListIteratorCounter::GetElementCount() +{ + if (m_pMergedPara) + { + sal_uInt16 nRet(0); + m_CurrentExtent = 0; + m_CurrentIndex = TextFrameIndex(0); + SwNode const* pNode(nullptr); + sal_uInt16 InCurrentNode(0); + while (m_CurrentExtent < m_pMergedPara->extents.size()) + { + sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]); + if (rExtent.pNode != pNode) + { + InCurrentNode = 0; + pNode = rExtent.pNode; + } + SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)()); + for (; pWrongList && InCurrentNode < pWrongList->Count(); ++InCurrentNode) + { + SwWrongArea const*const pWrong(pWrongList->GetElement(InCurrentNode)); + TextFrameIndex const nExtentEnd( + m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart)); + if (nExtentEnd <= TextFrameIndex(pWrong->mnPos)) + { + break; // continue outer loop + } + if (m_CurrentIndex < TextFrameIndex(pWrong->mnPos + pWrong->mnLen)) + { + ++nRet; + } + } + m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart); + ++m_CurrentExtent; + } + return nRet; + } + else if (m_pWrongList) + { + return m_pWrongList->Count(); + } + return 0; +} + +std::optional<std::pair<TextFrameIndex, TextFrameIndex>> +WrongListIteratorCounter::GetElementAt(sal_uInt16 nIndex) +{ + if (m_pMergedPara) + { + m_CurrentExtent = 0; + m_CurrentIndex = TextFrameIndex(0); + SwNode const* pNode(nullptr); + sal_uInt16 InCurrentNode(0); + while (m_CurrentExtent < m_pMergedPara->extents.size()) + { + sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]); + if (rExtent.pNode != pNode) + { + InCurrentNode = 0; + pNode = rExtent.pNode; + } + SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)()); + for (; pWrongList && InCurrentNode < pWrongList->Count(); ++InCurrentNode) + { + SwWrongArea const*const pWrong(pWrongList->GetElement(InCurrentNode)); + TextFrameIndex const nExtentEnd( + m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart)); + if (nExtentEnd <= TextFrameIndex(pWrong->mnPos)) + { + break; // continue outer loop + } + if (m_CurrentIndex < TextFrameIndex(pWrong->mnPos + pWrong->mnLen)) + { + if (nIndex == 0) + { + return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>( + std::pair<TextFrameIndex, TextFrameIndex>( + m_CurrentIndex - TextFrameIndex(rExtent.nStart - + std::max(rExtent.nStart, pWrong->mnPos)), + m_CurrentIndex - TextFrameIndex(rExtent.nStart - + std::min(pWrong->mnPos + pWrong->mnLen, rExtent.nEnd)))); + } + --nIndex; + } + } + m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart); + ++m_CurrentExtent; + } + return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>(); + } + else if (m_pWrongList) + { + SwWrongArea const*const pWrong(m_pWrongList->GetElement(nIndex)); + return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>( + std::pair<TextFrameIndex, TextFrameIndex>( + TextFrameIndex(pWrong->mnPos), + TextFrameIndex(pWrong->mnPos + pWrong->mnLen))); + } + return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>(); +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx new file mode 100644 index 000000000..b551c2842 --- /dev/null +++ b/sw/source/core/text/xmldump.cxx @@ -0,0 +1,567 @@ +/* -*- 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 <frame.hxx> +#include <frmfmt.hxx> +#include <ftnfrm.hxx> +#include <sectfrm.hxx> +#include <tabfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <cellfrm.hxx> +#include <hffrm.hxx> +#include <rootfrm.hxx> +#include <ndtxt.hxx> +#include <sortedobjs.hxx> +#include <swfont.hxx> +#include <txttypes.hxx> +#include <anchoredobject.hxx> +#include <libxml/xmlwriter.h> +#include <SwPortionHandler.hxx> +#include <view.hxx> +#include <svx/svdobj.hxx> + +namespace { + +class XmlPortionDumper:public SwPortionHandler +{ + private: + xmlTextWriterPtr writer; + TextFrameIndex ofs; + const OUString& m_rText; + OUString m_aLine; + + static const char* getTypeName( PortionType nType ) + { + switch ( nType ) + { + case PortionType::NONE: return "PortionType::NONE"; + case PortionType::FlyCnt: return "PortionType::FlyCnt"; + + case PortionType::Hole: return "PortionType::Hole"; + case PortionType::TempEnd: return "PortionType::TempEnd"; + case PortionType::Break: return "PortionType::Break"; + case PortionType::Kern: return "PortionType::Kern"; + case PortionType::Arrow: return "PortionType::Arrow"; + case PortionType::Multi: return "PortionType::Multi"; + case PortionType::HiddenText: return "PortionType::HiddenText"; + case PortionType::ControlChar: return "PortionType::ControlChar"; + case PortionType::Bookmark: return "PortionType::Bookmark"; + + case PortionType::Text: return "PortionType::Text"; + case PortionType::Lay: return "PortionType::Lay"; + case PortionType::Para: return "PortionType::Para"; + case PortionType::Hanging: return "PortionType::Hanging"; + + case PortionType::Drop: return "PortionType::Drop"; + case PortionType::Tox: return "PortionType::Tox"; + case PortionType::IsoTox: return "PortionType::IsoTox"; + case PortionType::Ref: return "PortionType::Ref"; + case PortionType::IsoRef: return "PortionType::IsoRef"; + case PortionType::Meta: return "PortionType::Meta"; + case PortionType::FieldMark: return "PortionType::FieldMark"; + case PortionType::FieldFormCheckbox: return "PortionType::FieldFormCheckbox"; + + case PortionType::Expand: return "PortionType::Expand"; + case PortionType::Blank: return "PortionType::Blank"; + case PortionType::PostIts: return "PortionType::PostIts"; + + case PortionType::Hyphen: return "PortionType::Hyphen"; + case PortionType::HyphenStr: return "PortionType::HyphenStr"; + case PortionType::SoftHyphen: return "PortionType::SoftHyphen"; + case PortionType::SoftHyphenStr: return "PortionType::SoftHyphenStr"; + case PortionType::SoftHyphenComp: return "PortionType::SoftHyphenComp"; + + case PortionType::Field: return "PortionType::Field"; + case PortionType::Hidden: return "PortionType::Hidden"; + case PortionType::QuoVadis: return "PortionType::QuoVadis"; + case PortionType::ErgoSum: return "PortionType::ErgoSum"; + case PortionType::Combined: return "PortionType::Combined"; + case PortionType::Footnote: return "PortionType::Footnote"; + + case PortionType::FootnoteNum: return "PortionType::FootnoteNum"; + case PortionType::Number: return "PortionType::Number"; + case PortionType::Bullet: return "PortionType::Bullet"; + case PortionType::GrfNum: return "PortionType::GrfNum"; + + case PortionType::Glue: return "PortionType::Glue"; + + case PortionType::Margin: return "PortionType::Margin"; + + case PortionType::Fix: return "PortionType::Fix"; + case PortionType::Fly: return "PortionType::Fly"; + + case PortionType::Table: return "PortionType::Table"; + + case PortionType::TabRight: return "PortionType::TabRight"; + case PortionType::TabCenter: return "PortionType::TabCenter"; + case PortionType::TabDecimal: return "PortionType::TabDecimal"; + + case PortionType::TabLeft: return "PortionType::TabLeft"; + default: + return "Unknown"; + } + } + + public: + + explicit XmlPortionDumper( xmlTextWriterPtr some_writer, const OUString& rText ):writer( some_writer ), ofs( 0 ), m_rText(rText) + { + } + + /** + @param nLength + length of this portion in the model string + @param rText + text which is painted on-screen + */ + virtual void Text( TextFrameIndex nLength, + PortionType nType, + sal_Int32 nHeight, + sal_Int32 nWidth) override + { + xmlTextWriterStartElement( writer, BAD_CAST( "Text" ) ); + xmlTextWriterWriteFormatAttribute( writer, + BAD_CAST( "nLength" ), + "%i", static_cast<int>(static_cast<sal_Int32>(nLength)) ); + xmlTextWriterWriteFormatAttribute( writer, + BAD_CAST( "nType" ), + "%s", getTypeName( nType ) ); + if (nHeight > 0) + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("nHeight"), "%i", static_cast<int>(nHeight)); + if (nWidth > 0) + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("nWidth"), "%i", static_cast<int>(nWidth)); + if (nLength > TextFrameIndex(0)) + xmlTextWriterWriteAttribute(writer, BAD_CAST("Portion"), + BAD_CAST(m_rText.copy(sal_Int32(ofs), sal_Int32(nLength)).toUtf8().getStr())); + + xmlTextWriterEndElement( writer ); + m_aLine += m_rText.copy(sal_Int32(ofs), sal_Int32(nLength)); + ofs += nLength; + } + + /** + @param nLength + length of this portion in the model string + @param rText + text which is painted on-screen + @param nType + type of this portion + @param nHeight + font size of the painted text + */ + virtual void Special( TextFrameIndex nLength, + const OUString & rText, + PortionType nType, + sal_Int32 nHeight, + sal_Int32 nWidth, + const SwFont* pFont ) override + { + xmlTextWriterStartElement( writer, BAD_CAST( "Special" ) ); + xmlTextWriterWriteFormatAttribute( writer, + BAD_CAST( "nLength" ), + "%i", static_cast<int>(static_cast<sal_Int32>(nLength)) ); + xmlTextWriterWriteFormatAttribute( writer, + BAD_CAST( "nType" ), + "%s", getTypeName( nType ) ); + OString sText8 = OUStringToOString( rText, RTL_TEXTENCODING_UTF8 ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "rText" ), + "%s", sText8.getStr( ) ); + + if (nHeight > 0) + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("nHeight"), "%i", static_cast<int>(nHeight)); + + if (nWidth > 0) + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("nWidth"), "%i", static_cast<int>(nWidth)); + + if (pFont) + pFont->dumpAsXml(writer); + + xmlTextWriterEndElement( writer ); + m_aLine += rText; + ofs += nLength; + } + + virtual void LineBreak( sal_Int32 nWidth ) override + { + xmlTextWriterStartElement( writer, BAD_CAST( "LineBreak" ) ); + if (nWidth > 0) + xmlTextWriterWriteFormatAttribute( writer, + BAD_CAST( "nWidth" ), + "%i", static_cast<int>(nWidth) ); + if (!m_aLine.isEmpty()) + { + xmlTextWriterWriteAttribute(writer, BAD_CAST("Line"), + BAD_CAST(m_aLine.toUtf8().getStr())); + m_aLine.clear(); + } + xmlTextWriterEndElement( writer ); + } + + /** + * @param nLength + * number of 'model string' characters to be skipped + */ + virtual void Skip( TextFrameIndex nLength ) override + { + xmlTextWriterStartElement( writer, BAD_CAST( "Skip" ) ); + xmlTextWriterWriteFormatAttribute( writer, + BAD_CAST( "nLength" ), + "%i", static_cast<int>(static_cast<sal_Int32>(nLength)) ); + xmlTextWriterEndElement( writer ); + ofs += nLength; + } + + virtual void Finish( ) override + { + xmlTextWriterStartElement( writer, BAD_CAST( "Finish" ) ); + xmlTextWriterEndElement( writer ); + } + +}; + + xmlTextWriterPtr lcl_createDefaultWriter() + { + xmlTextWriterPtr writer = xmlNewTextWriterFilename( "layout.xml", 0 ); + xmlTextWriterSetIndent(writer,1); + xmlTextWriterSetIndentString(writer, BAD_CAST(" ")); + xmlTextWriterStartDocument( writer, nullptr, nullptr, nullptr ); + return writer; + } + + void lcl_freeWriter( xmlTextWriterPtr writer ) + { + xmlTextWriterEndDocument( writer ); + xmlFreeTextWriter( writer ); + } +} + +void SwFrame::dumpTopMostAsXml(xmlTextWriterPtr writer) const +{ + const SwFrame* pFrame = this; + while (pFrame->GetUpper()) + { + pFrame = pFrame->GetUpper(); + } + + pFrame->dumpAsXml(writer); +} + +void SwFrame::dumpAsXml( xmlTextWriterPtr writer ) const +{ + bool bCreateWriter = ( nullptr == writer ); + if ( bCreateWriter ) + writer = lcl_createDefaultWriter(); + + const char *name = nullptr; + + switch ( GetType( ) ) + { + case SwFrameType::Root: + name = "root"; + break; + case SwFrameType::Page: + name = "page"; + break; + case SwFrameType::Column: + name = "column"; + break; + case SwFrameType::Header: + name = "header"; + break; + case SwFrameType::Footer: + name = "footer"; + break; + case SwFrameType::FtnCont: + name = "ftncont"; + break; + case SwFrameType::Ftn: + name = "ftn"; + break; + case SwFrameType::Body: + name = "body"; + break; + case SwFrameType::Fly: + name = "fly"; + break; + case SwFrameType::Section: + name = "section"; + break; + case SwFrameType::Tab: + name = "tab"; + break; + case SwFrameType::Row: + name = "row"; + break; + case SwFrameType::Cell: + name = "cell"; + break; + case SwFrameType::Txt: + name = "txt"; + break; + case SwFrameType::NoTxt: + name = "notxt"; + break; + default: break; + } + + if ( name != nullptr ) + { + xmlTextWriterStartElement( writer, reinterpret_cast<const xmlChar *>(name) ); + + dumpAsXmlAttributes( writer ); + + if (IsRootFrame()) + { + const SwRootFrame* pRootFrame = static_cast<const SwRootFrame*>(this); + xmlTextWriterStartElement(writer, BAD_CAST("sfxViewShells")); + SwView* pView = static_cast<SwView*>(SfxViewShell::GetFirst(true, checkSfxViewShell<SwView>)); + while (pView) + { + if (pRootFrame->GetCurrShell()->GetSfxViewShell() && pView->GetObjectShell() == pRootFrame->GetCurrShell()->GetSfxViewShell()->GetObjectShell()) + pView->dumpAsXml(writer); + pView = static_cast<SwView*>(SfxViewShell::GetNext(*pView, true, checkSfxViewShell<SwView>)); + } + xmlTextWriterEndElement(writer); + } + + if (IsPageFrame()) + { + const SwPageFrame* pPageFrame = static_cast<const SwPageFrame*>(this); + xmlTextWriterStartElement(writer, BAD_CAST("page_status")); + xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidFlyLayout"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidFlyLayout()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidFlyContent"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidFlyContent()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidFlyInCnt"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidFlyInCnt()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidLayout"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidLayout()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidContent"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidContent()).getStr())); + xmlTextWriterEndElement(writer); + xmlTextWriterStartElement(writer, BAD_CAST("page_info")); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("phyNum"), "%d", pPageFrame->GetPhyPageNum()); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("virtNum"), "%d", pPageFrame->GetVirtPageNum()); + OUString aFormatName = pPageFrame->GetPageDesc()->GetName(); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("pageDesc"), "%s", BAD_CAST(OUStringToOString(aFormatName, RTL_TEXTENCODING_UTF8).getStr())); + xmlTextWriterEndElement(writer); + } + + if (IsTextFrame()) + { + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame *>(this); + sw::MergedPara const*const pMerged(pTextFrame->GetMergedPara()); + if (pMerged) + { + xmlTextWriterStartElement( writer, BAD_CAST( "merged" ) ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "paraPropsNodeIndex" ), "%" SAL_PRIuUINTPTR, pMerged->pParaPropsNode->GetIndex() ); + for (auto const& e : pMerged->extents) + { + xmlTextWriterStartElement( writer, BAD_CAST( "extent" ) ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIuUINTPTR, e.pNode->GetIndex() ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "start" ), "%" SAL_PRIdINT32, e.nStart ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "end" ), "%" SAL_PRIdINT32, e.nEnd ); + xmlTextWriterEndElement( writer ); + } + xmlTextWriterEndElement( writer ); + } + } + + if (IsCellFrame()) + { + SwCellFrame const* pCellFrame(static_cast<SwCellFrame const*>(this)); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "rowspan" ), "%ld", pCellFrame->GetLayoutRowSpan() ); + } + + xmlTextWriterStartElement( writer, BAD_CAST( "infos" ) ); + dumpInfosAsXml( writer ); + xmlTextWriterEndElement( writer ); + + // Dump Anchored objects if any + const SwSortedObjs* pAnchored = GetDrawObjs(); + if ( pAnchored && pAnchored->size() > 0 ) + { + xmlTextWriterStartElement( writer, BAD_CAST( "anchored" ) ); + + for (SwAnchoredObject* pObject : *pAnchored) + { + pObject->dumpAsXml( writer ); + } + + xmlTextWriterEndElement( writer ); + } + + // Dump the children + if ( IsTextFrame( ) ) + { + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame *>(this); + OUString aText = pTextFrame->GetText( ); + for ( int i = 0; i < 32; i++ ) + { + aText = aText.replace( i, '*' ); + } + OString aText8 =OUStringToOString( aText, + RTL_TEXTENCODING_UTF8 ); + xmlTextWriterWriteString( writer, + reinterpret_cast<const xmlChar *>(aText8.getStr( )) ); + XmlPortionDumper pdumper( writer, aText ); + pTextFrame->VisitPortions( pdumper ); + + } + else + { + dumpChildrenAsXml( writer ); + } + xmlTextWriterEndElement( writer ); + } + + if ( bCreateWriter ) + lcl_freeWriter( writer ); +} + +void SwFrame::dumpInfosAsXml( xmlTextWriterPtr writer ) const +{ + // output the Frame + xmlTextWriterStartElement( writer, BAD_CAST( "bounds" ) ); + getFrameArea().dumpAsXmlAttributes(writer); + xmlTextWriterWriteAttribute(writer, BAD_CAST("mbFixSize"), BAD_CAST(OString::boolean(HasFixSize()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("mbValidPos"), BAD_CAST(OString::boolean(isFrameAreaPositionValid()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("mbValidSize"), BAD_CAST(OString::boolean(isFrameAreaSizeValid()).getStr())); + xmlTextWriterWriteAttribute(writer, BAD_CAST("mbValidPrtArea"), BAD_CAST(OString::boolean(isFramePrintAreaValid()).getStr())); + xmlTextWriterEndElement( writer ); + + // output the print area + xmlTextWriterStartElement( writer, BAD_CAST( "prtBounds" ) ); + getFramePrintArea().dumpAsXmlAttributes(writer); + xmlTextWriterEndElement( writer ); +} + +// Hack: somehow conversion from "..." to va_list does +// bomb on two string literals in the format. +static const char* const TMP_FORMAT = "%" SAL_PRIuUINTPTR; + +void SwFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const +{ + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", this ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "id" ), "%" SAL_PRIuUINT32, GetFrameId() ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "symbol" ), "%s", BAD_CAST( typeid( *this ).name( ) ) ); + if ( GetNext( ) ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "next" ), "%" SAL_PRIuUINT32, GetNext()->GetFrameId() ); + if ( GetPrev( ) ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "prev" ), "%" SAL_PRIuUINT32, GetPrev()->GetFrameId() ); + if ( GetUpper( ) ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "upper" ), "%" SAL_PRIuUINT32, GetUpper()->GetFrameId() ); + if ( GetLower( ) ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "lower" ), "%" SAL_PRIuUINT32, GetLower()->GetFrameId() ); + if (IsFootnoteFrame()) + { + SwFootnoteFrame const*const pFF(static_cast<SwFootnoteFrame const*>(this)); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("ref"), "%" SAL_PRIuUINT32, pFF->GetRef()->GetFrameId() ); + if (pFF->GetMaster()) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("master"), "%" SAL_PRIuUINT32, pFF->GetMaster()->GetFrameId() ); + if (pFF->GetFollow()) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("follow"), "%" SAL_PRIuUINT32, pFF->GetFollow()->GetFrameId() ); + } + if ( IsTextFrame( ) ) + { + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame *>(this); + const SwTextNode *pTextNode = pTextFrame->GetTextNodeFirst(); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), TMP_FORMAT, pTextNode->GetIndex() ); + + OString aMode = "Horizontal"; + if (IsVertLRBT()) + { + aMode = "VertBTLR"; + } + else if (IsVertLR()) + { + aMode = "VertLR"; + } + else if (IsVertical()) + { + aMode = "Vertical"; + } + xmlTextWriterWriteAttribute(writer, BAD_CAST("WritingMode"), BAD_CAST(aMode.getStr())); + } + if (IsHeaderFrame() || IsFooterFrame()) + { + const SwHeadFootFrame *pHeadFootFrame = static_cast<const SwHeadFootFrame*>(this); + OUString aFormatName = pHeadFootFrame->GetFormat()->GetName(); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "fmtName" ), "%s", BAD_CAST(OUStringToOString(aFormatName, RTL_TEXTENCODING_UTF8).getStr())); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "fmtPtr" ), "%p", pHeadFootFrame->GetFormat()); + } +} + +void SwFrame::dumpChildrenAsXml( xmlTextWriterPtr writer ) const +{ + const SwFrame *pFrame = GetLower( ); + for ( ; pFrame != nullptr; pFrame = pFrame->GetNext( ) ) + { + pFrame->dumpAsXml( writer ); + } +} + +void SwAnchoredObject::dumpAsXml( xmlTextWriterPtr writer ) const +{ + bool bCreateWriter = ( nullptr == writer ); + if ( bCreateWriter ) + writer = lcl_createDefaultWriter(); + + xmlTextWriterStartElement( writer, BAD_CAST( getElementName() ) ); + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", this ); + + xmlTextWriterStartElement( writer, BAD_CAST( "bounds" ) ); + GetObjBoundRect().dumpAsXmlAttributes(writer); + xmlTextWriterEndElement( writer ); + + if (const SdrObject* pObject = GetDrawObj()) + pObject->dumpAsXml(writer); + + xmlTextWriterEndElement( writer ); + + if ( bCreateWriter ) + lcl_freeWriter( writer ); +} + +void SwFont::dumpAsXml(xmlTextWriterPtr writer) const +{ + xmlTextWriterStartElement(writer, BAD_CAST("SwFont")); + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", this); + // do not use Color::AsRGBHexString() as that omits the transparency + xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("color"), "%08" SAL_PRIxUINT32, sal_uInt32(GetColor())); + xmlTextWriterEndElement(writer); +} + +void SwTextFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const +{ + SwFrame::dumpAsXmlAttributes( writer ); + if ( HasFollow() ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "follow" ), "%" SAL_PRIuUINT32, GetFollow()->GetFrameId() ); + + if (m_pPrecede != nullptr) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwTextFrame*>(m_pPrecede)->GetFrameId() ); +} + +void SwSectionFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const +{ + SwFrame::dumpAsXmlAttributes( writer ); + if ( HasFollow() ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "follow" ), "%" SAL_PRIuUINT32, GetFollow()->GetFrameId() ); + + if (m_pPrecede != nullptr) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwSectionFrame*>( m_pPrecede )->GetFrameId() ); +} + +void SwTabFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const +{ + SwFrame::dumpAsXmlAttributes( writer ); + if ( HasFollow() ) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "follow" ), "%" SAL_PRIuUINT32, GetFollow()->GetFrameId() ); + + if (m_pPrecede != nullptr) + xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwTabFrame*>( m_pPrecede )->GetFrameId() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/tox/ToxLinkProcessor.cxx b/sw/source/core/tox/ToxLinkProcessor.cxx new file mode 100644 index 000000000..c86cde923 --- /dev/null +++ b/sw/source/core/tox/ToxLinkProcessor.cxx @@ -0,0 +1,75 @@ +/* -*- 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 <memory> +#include <ToxLinkProcessor.hxx> + +#include <SwStyleNameMapper.hxx> +#include <ndtxt.hxx> +#include <sal/log.hxx> + +namespace sw { + +void +ToxLinkProcessor::StartNewLink(sal_Int32 startPosition, const OUString& characterStyle) +{ + SAL_INFO_IF(m_pStartedLink, "sw.core", "ToxLinkProcessor: LS without LE"); + m_pStartedLink = std::make_unique<StartedLink>( + startPosition, characterStyle); +} + +void +ToxLinkProcessor::CloseLink(sal_Int32 endPosition, const OUString& url) +{ + if (m_pStartedLink == nullptr) + { + SAL_INFO("sw.core", "ToxLinkProcessor: LE without LS"); + return; + } + + if (url.isEmpty()) { + return; + } + + std::unique_ptr<ClosedLink> pClosedLink( + new ClosedLink(url, m_pStartedLink->mStartPosition, endPosition)); + + const OUString& characterStyle = m_pStartedLink->mCharacterStyle; + sal_uInt16 poolId = ObtainPoolId(characterStyle); + pClosedLink->mINetFormat.SetVisitedFormatAndId(characterStyle, poolId); + pClosedLink->mINetFormat.SetINetFormatAndId(characterStyle, poolId); + + m_ClosedLinks.push_back(std::move(pClosedLink)); + m_pStartedLink.reset(); +} + +sal_uInt16 +ToxLinkProcessor::ObtainPoolId(const OUString& characterStyle) const +{ + if (characterStyle.isEmpty()) { + return USHRT_MAX; + } + else { + return SwStyleNameMapper::GetPoolIdFromUIName(characterStyle, SwGetPoolIdFromName::ChrFmt); + } +} + + +void +ToxLinkProcessor::InsertLinkAttributes(SwTextNode& node) +{ + for (auto const& clink : m_ClosedLinks) + { + node.InsertItem(clink->mINetFormat, clink->mStartTextPos, clink->mEndTextPos); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/tox/ToxTabStopTokenHandler.cxx b/sw/source/core/tox/ToxTabStopTokenHandler.cxx new file mode 100644 index 000000000..e54271c9d --- /dev/null +++ b/sw/source/core/tox/ToxTabStopTokenHandler.cxx @@ -0,0 +1,128 @@ +/* -*- 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 <ToxTabStopTokenHandler.hxx> + +#include <editeng/tstpitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/boxitem.hxx> + +#include <cntfrm.hxx> +#include <fmtfsize.hxx> +#include <fmtpdsc.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <ndtxt.hxx> +#include <pagedesc.hxx> +#include <pagefrm.hxx> +#include <swrect.hxx> +#include <tox.hxx> + +namespace sw { + +DefaultToxTabStopTokenHandler::DefaultToxTabStopTokenHandler(sal_uInt32 indexOfSectionNode, + const SwPageDesc& defaultPageDescription, + bool tabPositionIsRelativeToParagraphIndent, + TabStopReferencePolicy referencePolicy) +: mIndexOfSectionNode(indexOfSectionNode), + mDefaultPageDescription(defaultPageDescription), + mTabPositionIsRelativeToParagraphIndent(tabPositionIsRelativeToParagraphIndent), + mTabStopReferencePolicy(referencePolicy) +{ +} + + +ToxTabStopTokenHandler::HandledTabStopToken +DefaultToxTabStopTokenHandler::HandleTabStopToken( + const SwFormToken& aToken, const SwTextNode& targetNode, const SwRootFrame *currentLayout) const +{ + HandledTabStopToken result; + + if (aToken.bWithTab) { // #i21237# + result.text = "\t"; + } + + // check whether a tab adjustment has been specified. + if (SvxTabAdjust::End > aToken.eTabAlign) { + const SvxLRSpaceItem& rLR = static_cast<const SvxLRSpaceItem&>( targetNode.SwContentNode::GetAttr(RES_LR_SPACE) ); + + long nTabPosition = aToken.nTabStopPosition; + if (!mTabPositionIsRelativeToParagraphIndent && rLR.GetTextLeft()) { + nTabPosition -= rLR.GetTextLeft(); + } + result.tabStop = SvxTabStop(nTabPosition, aToken.eTabAlign, cDfltDecimalChar, aToken.cTabFillChar); + return result; + } + + SwRect aNdRect; + if (CanUseLayoutRectangle(targetNode, currentLayout)) { + aNdRect = targetNode.FindLayoutRect(true); + } + long nRightMargin; + if (aNdRect.IsEmpty()) { + nRightMargin = CalculatePageMarginFromPageDescription(targetNode); + } else { + nRightMargin = aNdRect.Width(); + } + //#i24363# tab stops relative to indent + if (mTabStopReferencePolicy == TABSTOPS_RELATIVE_TO_INDENT) { + // left margin of paragraph style + const SvxLRSpaceItem& rLRSpace = targetNode.GetTextColl()->GetLRSpace(); + nRightMargin -= rLRSpace.GetLeft(); + nRightMargin -= rLRSpace.GetTextFirstLineOffset(); + } + + result.tabStop = SvxTabStop(nRightMargin, SvxTabAdjust::Right, cDfltDecimalChar, aToken.cTabFillChar); + return result; +} + +long +DefaultToxTabStopTokenHandler::CalculatePageMarginFromPageDescription(const SwTextNode& targetNode) const +{ + size_t nPgDescNdIdx = targetNode.GetIndex() + 1; + const SwPageDesc *pPageDesc = targetNode.FindPageDesc(&nPgDescNdIdx); + if (!pPageDesc || nPgDescNdIdx < mIndexOfSectionNode) { + // Use default page description, if none is found or the found one is given by a Node before the + // table-of-content section. + pPageDesc = &mDefaultPageDescription; + } + const SwFrameFormat& rPgDscFormat = pPageDesc->GetMaster(); + long result = rPgDscFormat.GetFrameSize().GetWidth() - rPgDscFormat.GetLRSpace().GetLeft() + - rPgDscFormat.GetLRSpace().GetRight(); + // Also consider borders + const SvxBoxItem& rBox = rPgDscFormat.GetBox(); + result -= rBox.CalcLineSpace(SvxBoxItemLine::LEFT) + rBox.CalcLineSpace(SvxBoxItemLine::RIGHT); + return result; +} + + +/*static*/ bool +DefaultToxTabStopTokenHandler::CanUseLayoutRectangle(const SwTextNode& targetNode, const SwRootFrame *currentLayout) +{ + const SwPageDesc* pageDescription = + static_cast<const SwFormatPageDesc&>( targetNode.SwContentNode::GetAttr(RES_PAGEDESC)).GetPageDesc(); + + if (!pageDescription) { + return false; + } + const SwFrame* pFrame = targetNode.getLayoutFrame(currentLayout); + if (!pFrame) { + return false; + } + pFrame = pFrame->FindPageFrame(); + if (!pFrame) { + return false; + } + const SwPageFrame* pageFrame = static_cast<const SwPageFrame*>(pFrame); + return pageDescription == pageFrame->GetPageDesc(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/tox/ToxTextGenerator.cxx b/sw/source/core/tox/ToxTextGenerator.cxx new file mode 100644 index 000000000..cfef4d356 --- /dev/null +++ b/sw/source/core/tox/ToxTextGenerator.cxx @@ -0,0 +1,440 @@ +/* -*- 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 <ToxTextGenerator.hxx> + +#include <chpfld.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <ndindex.hxx> +#include <fchrfmt.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <ndtxt.hxx> +#include <tox.hxx> +#include <txmsrt.hxx> +#include <fmtautofmt.hxx> +#include <swatrset.hxx> +#include <ToxWhitespaceStripper.hxx> +#include <ToxLinkProcessor.hxx> +#include <ToxTabStopTokenHandler.hxx> +#include <txatbase.hxx> +#include <modeltoviewhelper.hxx> + +#include <rtl/ustrbuf.hxx> +#include <svl/itemiter.hxx> + +#include <cassert> +#include <memory> + +namespace { + +bool sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(const SwTOXSortTabBase& sortTab) +{ + if (sortTab.aTOXSources.empty()) { + return true; + } + if (sortTab.aTOXSources.at(0).pNd == nullptr) { + return true; + } + return false; +} + +} // end anonymous namespace + +namespace sw { + +OUString +ToxTextGenerator::GetNumStringOfFirstNode(const SwTOXSortTabBase& rBase, + bool bUsePrefix, sal_uInt8 nLevel, + SwRootFrame const*const pLayout) +{ + if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) { + return OUString(); + } + + OUString sRet; + if (rBase.pTextMark) { // only if it's not a Mark + return sRet; + } + + const SwTextNode* pNd = rBase.aTOXSources[0].pNd->GetTextNode(); + if (!pNd) { + return sRet; + } + if (pLayout && pLayout->IsHideRedlines()) + { // note: pNd could be any node, since it could be Sequence etc. + pNd = sw::GetParaPropsNode(*pLayout, SwNodeIndex(*pNd)); + } + + const SwNumRule* pRule = pNd->GetNumRule(); + if (!pRule) { + return sRet; + } + + if (pNd->GetActualListLevel() < MAXLEVEL) { + sRet = pNd->GetNumString(bUsePrefix, nLevel, pLayout); + } + + if (!sRet.isEmpty()) { + sRet += " ";// Makes sure spacing is done only when there is outline numbering + } + + return sRet; +} + + +ToxTextGenerator::ToxTextGenerator(const SwForm& toxForm, + std::shared_ptr<ToxTabStopTokenHandler> const & tabStopHandler) +: mToxForm(toxForm), + mLinkProcessor(std::make_shared<ToxLinkProcessor>()), + mTabStopTokenHandler(tabStopHandler) +{} + +ToxTextGenerator::~ToxTextGenerator() +{} + +OUString +ToxTextGenerator::HandleChapterToken(const SwTOXSortTabBase& rBase, + const SwFormToken& aToken, SwRootFrame const*const pLayout) const +{ + if (sortTabHasNoToxSourcesOrFirstToxSourceHasNoNode(rBase)) { + return OUString(); + } + + // A bit tricky: Find a random Frame + const SwContentNode* contentNode = rBase.aTOXSources.at(0).pNd->GetContentNode(); + if (!contentNode) { + return OUString(); + } + + // #i53420# + const SwContentFrame* contentFrame = contentNode->getLayoutFrame(pLayout); + if (!contentFrame) { + return OUString(); + } + + return GenerateTextForChapterToken(aToken, contentFrame, contentNode, pLayout); +} + +OUString +ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken& chapterToken, const SwContentFrame* contentFrame, + const SwContentNode *contentNode, + SwRootFrame const*const pLayout) const +{ + OUString retval; + + SwChapterFieldType chapterFieldType; + SwChapterField aField = ObtainChapterField(&chapterFieldType, &chapterToken, contentFrame, contentNode); + + //---> #i89791# + // continue to support CF_NUMBER and CF_NUM_TITLE in order to handle ODF 1.0/1.1 written by OOo 3.x + // in the same way as OOo 2.x would handle them. + if (CF_NUM_NOPREPST_TITLE == chapterToken.nChapterFormat || CF_NUMBER == chapterToken.nChapterFormat) { + retval += aField.GetNumber(pLayout); // get the string number without pre/postfix + } + else if (CF_NUMBER_NOPREPST == chapterToken.nChapterFormat || CF_NUM_TITLE == chapterToken.nChapterFormat) { + retval += aField.GetNumber(pLayout) + " "; + retval += aField.GetTitle(pLayout); + } else if (CF_TITLE == chapterToken.nChapterFormat) { + retval += aField.GetTitle(pLayout); + } + return retval; +} + +// Add parameter <_TOXSectNdIdx> and <_pDefaultPageDesc> in order to control, +// which page description is used, no appropriate one is found. +void +ToxTextGenerator::GenerateText(SwDoc* pDoc, const std::vector<std::unique_ptr<SwTOXSortTabBase>> &entries, + sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess, + SwRootFrame const*const pLayout) +{ + // pTOXNd is only set at the first mark + SwTextNode* pTOXNd = const_cast<SwTextNode*>(entries.at(indexOfEntryToProcess)->pTOXNd); + // FIXME this operates directly on the node text + OUString & rText = const_cast<OUString&>(pTOXNd->GetText()); + rText.clear(); + for(sal_uInt16 nIndex = indexOfEntryToProcess; nIndex < indexOfEntryToProcess + numberOfEntriesToProcess; nIndex++) + { + if(nIndex > indexOfEntryToProcess) + rText += ", "; // comma separation + // Initialize String with the Pattern from the form + const SwTOXSortTabBase& rBase = *entries.at(nIndex); + sal_uInt16 nLvl = rBase.GetLevel(); + OSL_ENSURE( nLvl < mToxForm.GetFormMax(), "invalid FORM_LEVEL"); + + SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + // create an enumerator + // #i21237# + SwFormTokens aPattern = mToxForm.GetPattern(nLvl); + // remove text from node + for(const auto& aToken : aPattern) // #i21237# + { + sal_Int32 nStartCharStyle = rText.getLength(); + switch( aToken.eTokenType ) + { + case TOKEN_ENTRY_NO: + // for TOC numbering + rText += GetNumStringOfFirstNode(rBase, + aToken.nChapterFormat == CF_NUMBER, + static_cast<sal_uInt8>(aToken.nOutlineLevel - 1), pLayout); + break; + + case TOKEN_ENTRY_TEXT: { + HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout); + ApplyHandledTextToken(htt, *pTOXNd); + } + break; + + case TOKEN_ENTRY: + { + // for TOC numbering + rText += GetNumStringOfFirstNode(rBase, true, MAXLEVEL, pLayout); + HandledTextToken htt = HandleTextToken(rBase, pDoc->GetAttrPool(), pLayout); + ApplyHandledTextToken(htt, *pTOXNd); + } + break; + + case TOKEN_TAB_STOP: { + ToxTabStopTokenHandler::HandledTabStopToken htst = + mTabStopTokenHandler->HandleTabStopToken(aToken, *pTOXNd, pDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + rText += htst.text; + aTStops.Insert(htst.tabStop); + break; + } + + case TOKEN_TEXT: + rText += aToken.sText; + break; + + case TOKEN_PAGE_NUMS: + rText += ConstructPageNumberPlaceholder(rBase.aTOXSources.size()); + break; + + case TOKEN_CHAPTER_INFO: + rText += HandleChapterToken(rBase, aToken, pLayout); + break; + + case TOKEN_LINK_START: + mLinkProcessor->StartNewLink(rText.getLength(), aToken.sCharStyleName); + break; + + case TOKEN_LINK_END: + mLinkProcessor->CloseLink(rText.getLength(), rBase.GetURL()); + break; + + case TOKEN_AUTHORITY: + { + ToxAuthorityField eField = static_cast<ToxAuthorityField>(aToken.nAuthorityField); + SwIndex aIdx( pTOXNd, rText.getLength() ); + rBase.FillText( *pTOXNd, aIdx, static_cast<sal_uInt16>(eField), pLayout ); + } + break; + case TOKEN_END: break; + } + + if ( !aToken.sCharStyleName.isEmpty() ) + { + SwCharFormat* pCharFormat; + if( USHRT_MAX != aToken.nPoolId ) + pCharFormat = pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( aToken.nPoolId ); + else + pCharFormat = pDoc->FindCharFormatByName( aToken.sCharStyleName); + + if (pCharFormat) + { + SwFormatCharFormat aFormat( pCharFormat ); + pTOXNd->InsertItem( aFormat, nStartCharStyle, + rText.getLength(), SetAttrMode::DONTEXPAND ); + } + } + } + + pTOXNd->SetAttr( aTStops ); + } + mLinkProcessor->InsertLinkAttributes(*pTOXNd); +} + +/*static*/ std::shared_ptr<SfxItemSet> +ToxTextGenerator::CollectAttributesForTox(const SwTextAttr& hint, SwAttrPool& pool) +{ + auto retval = std::make_shared<SfxItemSet>(pool); + if (hint.Which() != RES_TXTATR_AUTOFMT) { + return retval; + } + const SwFormatAutoFormat& afmt = hint.GetAutoFormat(); + SfxItemIter aIter( *afmt.GetStyleHandle()); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if (pItem->Which() == RES_CHRATR_ESCAPEMENT || + pItem->Which() == RES_CHRATR_POSTURE || + pItem->Which() == RES_CHRATR_CJK_POSTURE || + pItem->Which() == RES_CHRATR_CTL_POSTURE) + { + retval->Put(std::unique_ptr<SfxPoolItem>(pItem->Clone())); + } + pItem = aIter.NextItem(); + } while (pItem); + return retval; +} + +void ToxTextGenerator::GetAttributesForNode( + ToxTextGenerator::HandledTextToken & rResult, + sal_Int32 & rOffset, + SwTextNode const& rNode, + ToxWhitespaceStripper const& rStripper, + SwAttrPool & rPool, + SwRootFrame const*const pLayout) +{ + // note: this *must* use the same flags as SwTextNode::GetExpandText() + // or indexes will be off! + ExpandMode eMode = ExpandMode::ExpandFields; + if (pLayout && pLayout->IsHideRedlines()) + { + eMode |= ExpandMode::HideDeletions; + } + ModelToViewHelper aConversionMap(rNode, pLayout, eMode); + if (SwpHints const*const pHints = rNode.GetpSwpHints()) + { + for (size_t i = 0; i < pHints->Count(); ++i) + { + const SwTextAttr* pHint = pHints->Get(i); + std::shared_ptr<SfxItemSet> attributesToClone = + CollectAttributesForTox(*pHint, rPool); + if (attributesToClone->Count() <= 0) { + continue; + } + + // sw_redlinehide: due to the ... interesting ... multi-level index + // mapping going on here, can't use the usual merged attr iterators :( + + sal_Int32 const nStart(aConversionMap.ConvertToViewPosition(pHint->GetStart())); + sal_Int32 const nEnd(aConversionMap.ConvertToViewPosition(pHint->GetAnyEnd())); + if (nStart != nEnd) // might be in delete redline, and useless anyway + { + std::unique_ptr<SwFormatAutoFormat> pClone(pHint->GetAutoFormat().Clone()); + pClone->SetStyleHandle(attributesToClone); + rResult.autoFormats.push_back(std::move(pClone)); + // note the rStripper is on the whole merged text, so need rOffset + rResult.startPositions.push_back( + rStripper.GetPositionInStrippedString(rOffset + nStart)); + rResult.endPositions.push_back( + rStripper.GetPositionInStrippedString(rOffset + nEnd)); + } + } + } + rOffset += aConversionMap.getViewText().getLength(); +} + +ToxTextGenerator::HandledTextToken +ToxTextGenerator::HandleTextToken(const SwTOXSortTabBase& source, + SwAttrPool& pool, SwRootFrame const*const pLayout) +{ + HandledTextToken result; + ToxWhitespaceStripper stripper(source.GetText().sText); + result.text = stripper.GetStrippedString(); + + // FIXME: there is a pre-existing problem that the index mapping of the + // attributes only works if the paragraph is fully selected + if (!source.IsFullPara() || source.aTOXSources.empty()) + return result; + + const SwTextNode* pSrc = source.aTOXSources.front().pNd->GetTextNode(); + if (!pSrc) + { + return result; + } + + sal_Int32 nOffset(0); + GetAttributesForNode(result, nOffset, *pSrc, stripper, pool, pLayout); + if (pLayout && pLayout->IsHideRedlines()) + { + if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pSrc->getLayoutFrame(pLayout))) + { + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + // pSrc already copied above + assert(pSrc == pMerged->pParaPropsNode); + for (sal_uLong i = pSrc->GetIndex() + 1; + i <= pMerged->pLastNode->GetIndex(); ++i) + { + SwNode *const pTmp(pSrc->GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + GetAttributesForNode(result, nOffset, + *pTmp->GetTextNode(), stripper, pool, pLayout); + } + } + } + } + } + + return result; +} + +/*static*/ void +ToxTextGenerator::ApplyHandledTextToken(const HandledTextToken& htt, SwTextNode& targetNode) +{ + sal_Int32 offset = targetNode.GetText().getLength(); + SwIndex aIdx(&targetNode, offset); + targetNode.InsertText(htt.text, aIdx); + for (size_t i=0; i < htt.autoFormats.size(); ++i) { + targetNode.InsertItem(*htt.autoFormats.at(i), + htt.startPositions.at(i) + offset, + htt.endPositions.at(i) + offset); + } +} + +/*static*/ OUString +ToxTextGenerator::ConstructPageNumberPlaceholder(size_t numberOfToxSources) +{ + if (numberOfToxSources == 0) { + return OUString(); + } + OUStringBuffer retval; + // Place holder for the PageNumber; we only respect the first one + retval.append(C_NUM_REPL); + for (size_t i = 1; i < numberOfToxSources; ++i) { + retval.append(S_PAGE_DELI); + retval.append(C_NUM_REPL); + } + retval.append(C_END_PAGE_NUM); + return retval.makeStringAndClear(); +} + +/*virtual*/ SwChapterField +ToxTextGenerator::ObtainChapterField(SwChapterFieldType* chapterFieldType, + const SwFormToken* chapterToken, const SwContentFrame* contentFrame, + const SwContentNode* contentNode) const +{ + assert(chapterToken); + assert(chapterToken->nOutlineLevel >= 1); + + SwChapterField retval(chapterFieldType, chapterToken->nChapterFormat); + retval.SetLevel(static_cast<sal_uInt8>(chapterToken->nOutlineLevel - 1)); + // #i53420# + retval.ChangeExpansion(*contentFrame, contentNode, true); + return retval; +} +} // end namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/tox/ToxWhitespaceStripper.cxx b/sw/source/core/tox/ToxWhitespaceStripper.cxx new file mode 100644 index 000000000..b2fe68727 --- /dev/null +++ b/sw/source/core/tox/ToxWhitespaceStripper.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <ToxWhitespaceStripper.hxx> + +#include <o3tl/safeint.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + + +namespace sw { + +ToxWhitespaceStripper::ToxWhitespaceStripper(const OUString& inputString) +{ + OUStringBuffer buffer; + + bool lastCharacterWasWhitespace = false; + for (sal_Int32 pos = 0; pos < inputString.getLength(); ++pos) { + sal_Unicode cur = inputString[pos]; + + if (cur == ' ' || cur == '\n' || cur == '\t') { + // merge consecutive whitespaces (and translate them to spaces) + if (!lastCharacterWasWhitespace) { + buffer.append(' '); + } + lastCharacterWasWhitespace = true; + } + else { + buffer.append(cur); + lastCharacterWasWhitespace = false; + } + mNewPositions.push_back(buffer.getLength()-1); + } + // strip the last whitespace (if there was one) + if (lastCharacterWasWhitespace) { + buffer.truncate(buffer.getLength() - 1); + } + mNewPositions.push_back(buffer.getLength()); + mStripped = buffer.getStr(); +} + + +sal_Int32 +ToxWhitespaceStripper::GetPositionInStrippedString(sal_Int32 pos) const +{ + assert(0 <= pos); + if (o3tl::make_unsigned(pos) >= mNewPositions.size()) { + // TODO probably this should assert, not just warn? + SAL_WARN("sw.core", "Requested position of TOX entry text which does not exist. " + "Maybe the formatting hint is corrupt?"); + return mNewPositions.back(); + } + return mNewPositions.at(pos); +} + + +} diff --git a/sw/source/core/tox/tox.cxx b/sw/source/core/tox/tox.cxx new file mode 100644 index 000000000..92b3bddcc --- /dev/null +++ b/sw/source/core/tox/tox.cxx @@ -0,0 +1,940 @@ +/* -*- 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 <hintids.hxx> +#include <swtypes.hxx> +#include <ndtxt.hxx> +#include <txttxmrk.hxx> +#include <tox.hxx> +#include <strings.hrc> +#include <doc.hxx> +#include <docary.hxx> +#include <paratr.hxx> +#include <editeng/tstpitem.hxx> +#include <hints.hxx> +#include <calbck.hxx> + +#include <optional> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <algorithm> + + +using namespace std; + + +const sal_Unicode C_NUM_REPL = '@'; +const sal_Unicode C_END_PAGE_NUM = '~'; +const OUString S_PAGE_DELI(", "); + + +namespace +{ + +void lcl_FillAuthPattern(SwFormTokens &rAuthTokens, sal_uInt16 nTypeId) +{ + rAuthTokens.reserve(9); // Worst case: Start+Sep1+Auth+3*(Sep2+Auth) + + SwFormToken aStartToken( TOKEN_AUTHORITY ); + aStartToken.nAuthorityField = AUTH_FIELD_IDENTIFIER; + rAuthTokens.push_back( aStartToken ); + SwFormToken aSeparatorToken( TOKEN_TEXT ); + aSeparatorToken.sText = ": "; + rAuthTokens.push_back( aSeparatorToken ); + + --nTypeId; // compensate +1 offset introduced by caller + + SwFormToken aTextToken( TOKEN_TEXT ); + aTextToken.sText = ", "; + + const ToxAuthorityField nVals[4] = { + AUTH_FIELD_AUTHOR, + AUTH_FIELD_TITLE, + AUTH_FIELD_YEAR, + nTypeId == AUTH_TYPE_WWW ? AUTH_FIELD_URL : AUTH_FIELD_END + }; + + for(size_t i = 0; i < SAL_N_ELEMENTS(nVals); ++i) + { + if(nVals[i] == AUTH_FIELD_END) + break; + if( i > 0 ) + rAuthTokens.push_back( aTextToken ); + + // -> #i21237# + SwFormToken aToken(TOKEN_AUTHORITY); + + aToken.nAuthorityField = nVals[i]; + rAuthTokens.push_back(aToken); + // <- #i21237# + } +} + +} + + +/// pool default constructor +SwTOXMark::SwTOXMark() + : SfxPoolItem( RES_TXTATR_TOXMARK ) + , m_pTextAttr( nullptr ), m_nLevel( 0 ) + , m_bAutoGenerated(false) + , m_bMainEntry(false) +{ } + +SwTOXMark::SwTOXMark(const SwTOXType* pTyp) + : SfxPoolItem(RES_TXTATR_TOXMARK ) + , m_pTextAttr( nullptr ) + , m_nLevel( 0 ) + , m_bAutoGenerated(false) + , m_bMainEntry(false) +{ + const_cast<SwTOXType*>(pTyp)->Add(this); +} + +SwTOXMark::SwTOXMark(const SwTOXMark& rCopy) + : SfxPoolItem(RES_TXTATR_TOXMARK) + , m_aPrimaryKey(rCopy.m_aPrimaryKey) + , m_aSecondaryKey(rCopy.m_aSecondaryKey) + , m_aTextReading(rCopy.m_aTextReading) + , m_aPrimaryKeyReading(rCopy.m_aPrimaryKeyReading) + , m_aSecondaryKeyReading(rCopy.m_aSecondaryKeyReading) + , m_pTextAttr(nullptr) + , m_nLevel(rCopy.m_nLevel) + , m_bAutoGenerated(rCopy.m_bAutoGenerated) + , m_bMainEntry(rCopy.m_bMainEntry) +{ + if (auto pRegister = const_cast<SwTOXMark&>(rCopy).GetRegisteredIn()) + pRegister->Add(this); + // Copy AlternativString + m_aAltText = rCopy.m_aAltText; +} + +SwTOXMark::~SwTOXMark() +{ +} + +void SwTOXMark::RegisterToTOXType(SwTOXType& rType) +{ + rType.Add(this); +} + +bool SwTOXMark::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return GetRegisteredIn() == static_cast<const SwTOXMark&>(rAttr).GetRegisteredIn(); +} + +SwTOXMark* SwTOXMark::Clone( SfxItemPool* ) const +{ + return new SwTOXMark( *this ); +} + +void SwTOXMark::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew) +{ + NotifyClients(pOld, pNew); + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXTOXMark(css::uno::Reference<css::text::XDocumentIndexMark>(nullptr)); + } +} + +void SwTOXMark::InvalidateTOXMark() +{ + SwPtrMsgPoolItem aMsgHint( RES_REMOVE_UNO_OBJECT, + &static_cast<SwModify&>(*this) ); // cast to base class! + NotifyClients(&aMsgHint, &aMsgHint); +} + +OUString SwTOXMark::GetText(SwRootFrame const*const pLayout) const +{ + if( !m_aAltText.isEmpty() ) + return m_aAltText; + + if( m_pTextAttr && m_pTextAttr->GetpTextNd() ) + { + const sal_Int32* pEndIdx = m_pTextAttr->GetEnd(); + OSL_ENSURE( pEndIdx, "TOXMark without mark!"); + if( pEndIdx ) + { + const sal_Int32 nStt = m_pTextAttr->GetStart(); + return m_pTextAttr->GetpTextNd()->GetExpandText(pLayout, nStt, *pEndIdx-nStt); + } + } + + return OUString(); +} + +void SwTOXMark::InsertTOXMarks( SwTOXMarks& aMarks, const SwTOXType& rType ) +{ + SwIterator<SwTOXMark,SwTOXType> aIter(rType); + SwTOXMark* pMark = aIter.First(); + while( pMark ) + { + if(pMark->GetTextTOXMark()) + aMarks.push_back(pMark); + pMark = aIter.Next(); + } +} + +// Manage types of TOX +SwTOXType::SwTOXType(SwDoc& rDoc, TOXTypes eTyp, const OUString& rName) + : m_rDoc(rDoc) + , m_aName(rName) + , m_eType(eTyp) +{ } + +SwTOXType::SwTOXType(const SwTOXType& rCopy) + : m_rDoc(rCopy.m_rDoc) + , m_aName(rCopy.m_aName) + , m_eType(rCopy.m_eType) +{ + if (auto pRegister = const_cast<SwTOXType&>(rCopy).GetRegisteredIn()) + pRegister->Add(this); +} + +static const char* STR_POOLCOLL_TOX_ARY[] = +{ + // Subcategory Index-Directories + STR_POOLCOLL_TOX_IDXH, + STR_POOLCOLL_TOX_IDX1, + STR_POOLCOLL_TOX_IDX2, + STR_POOLCOLL_TOX_IDX3, + STR_POOLCOLL_TOX_IDXBREAK +}; + +static const char* STR_POOLCOLL_TOX_CNTNT_ARY[] = +{ + // Subcategory Tables of Contents + STR_POOLCOLL_TOX_CNTNTH, + STR_POOLCOLL_TOX_CNTNT1, + STR_POOLCOLL_TOX_CNTNT2, + STR_POOLCOLL_TOX_CNTNT3, + STR_POOLCOLL_TOX_CNTNT4, + STR_POOLCOLL_TOX_CNTNT5 +}; + +static const char* STR_POOLCOLL_TOX_CNTNT_EXTRA_ARY[] = +{ + // Subcategory Table of Contents more Levels 5 - 10 + STR_POOLCOLL_TOX_CNTNT6, + STR_POOLCOLL_TOX_CNTNT7, + STR_POOLCOLL_TOX_CNTNT8, + STR_POOLCOLL_TOX_CNTNT9, + STR_POOLCOLL_TOX_CNTNT10 +}; + +static const char* STR_POOLCOLL_TOX_USER_ARY[] = +{ + // Subcategory User-Directories: + STR_POOLCOLL_TOX_USERH, + STR_POOLCOLL_TOX_USER1, + STR_POOLCOLL_TOX_USER2, + STR_POOLCOLL_TOX_USER3, + STR_POOLCOLL_TOX_USER4, + STR_POOLCOLL_TOX_USER5 +}; + +static const char* STR_POOLCOLL_TOX_USER_EXTRA_ARY[] = +{ + // Subcategory User-Directories more Levels 5 - 10 + STR_POOLCOLL_TOX_USER6, + STR_POOLCOLL_TOX_USER7, + STR_POOLCOLL_TOX_USER8, + STR_POOLCOLL_TOX_USER9, + STR_POOLCOLL_TOX_USER10 +}; + +static const char* STR_POOLCOLL_TOX_ILLUS_ARY[] = +{ + // Illustrations Index + STR_POOLCOLL_TOX_ILLUSH, + STR_POOLCOLL_TOX_ILLUS1 +}; + +static const char* STR_POOLCOLL_TOX_OBJECT_ARY[] = +{ + // Object Index + STR_POOLCOLL_TOX_OBJECTH, + STR_POOLCOLL_TOX_OBJECT1 +}; + +static const char* STR_POOLCOLL_TOX_TABLES_ARY[] = +{ + // Tables Index + STR_POOLCOLL_TOX_TABLESH, + STR_POOLCOLL_TOX_TABLES1 +}; + +static const char* STR_POOLCOLL_TOX_AUTHORITIES_ARY[] = +{ + // Index of Authorities + STR_POOLCOLL_TOX_AUTHORITIESH, + STR_POOLCOLL_TOX_AUTHORITIES1 +}; + +static const char* STR_POOLCOLL_TOX_CITATION_ARY[] = +{ + STR_POOLCOLL_TOX_CITATION +}; + +// Edit forms +SwForm::SwForm( TOXTypes eTyp ) // #i21237# + : m_eType( eTyp ), m_nFormMaxLevel( SwForm::GetFormMaxLevel( eTyp )), +// nFirstTabPos( lNumberIndent ), + m_bCommaSeparated(false) +{ + //bHasFirstTabPos = + m_bIsRelTabPos = true; + + // The table of contents has a certain number of headlines + headings + // The user has 10 levels + headings + // Keyword has 3 levels + headings+ separator + // Indexes of tables, object illustrations and authorities consist of a heading and one level + + const char** pPoolId; + switch( m_eType ) + { + case TOX_INDEX: pPoolId = STR_POOLCOLL_TOX_ARY; break; + case TOX_USER: pPoolId = STR_POOLCOLL_TOX_USER_ARY; break; + case TOX_CONTENT: pPoolId = STR_POOLCOLL_TOX_CNTNT_ARY; break; + case TOX_ILLUSTRATIONS: pPoolId = STR_POOLCOLL_TOX_ILLUS_ARY; break; + case TOX_OBJECTS : pPoolId = STR_POOLCOLL_TOX_OBJECT_ARY; break; + case TOX_TABLES : pPoolId = STR_POOLCOLL_TOX_TABLES_ARY; break; + case TOX_AUTHORITIES : pPoolId = STR_POOLCOLL_TOX_AUTHORITIES_ARY; break; + case TOX_CITATION : pPoolId = STR_POOLCOLL_TOX_CITATION_ARY; break; + default: + OSL_ENSURE( false, "invalid TOXTyp"); + return ; + } + + SwFormTokens aTokens; + if (TOX_CONTENT == m_eType || TOX_ILLUSTRATIONS == m_eType ) + { + SwFormToken aLinkStt (TOKEN_LINK_START); + aLinkStt.sCharStyleName = SwResId(STR_POOLCHR_TOXJUMP); + aTokens.push_back(aLinkStt); + } + + if (TOX_CONTENT == m_eType) + { + aTokens.emplace_back(TOKEN_ENTRY_NO); + aTokens.emplace_back(TOKEN_ENTRY_TEXT); + } + else + aTokens.emplace_back(TOKEN_ENTRY); + + if (TOX_AUTHORITIES != m_eType) + { + SwFormToken aToken(TOKEN_TAB_STOP); + aToken.nTabStopPosition = 0; + + // #i36870# right aligned tab for all + aToken.cTabFillChar = '.'; + aToken.eTabAlign = SvxTabAdjust::End; + + aTokens.push_back(aToken); + aTokens.emplace_back(TOKEN_PAGE_NUMS); + } + + if (TOX_CONTENT == m_eType || TOX_ILLUSTRATIONS == m_eType) + aTokens.emplace_back(TOKEN_LINK_END); + + SetTemplate(0, SwResId(*pPoolId++)); + + if(TOX_INDEX == m_eType) + { + for( sal_uInt16 i = 1; i < 5; ++i ) + { + if(1 == i) + { + SwFormTokens aTmpTokens; + SwFormToken aTmpToken(TOKEN_ENTRY); + aTmpTokens.push_back(aTmpToken); + + SetPattern( i, aTmpTokens ); + SetTemplate(i, SwResId(STR_POOLCOLL_TOX_IDXBREAK)); + } + else + { + SetPattern( i, aTokens ); + SetTemplate(i, SwResId(STR_POOLCOLL_TOX_ARY[i - 1])); + } + } + } + else + { + for (sal_uInt16 i = 1; i < GetFormMax(); ++i, ++pPoolId) // Number 0 is the title + { + if (TOX_AUTHORITIES == m_eType) + { + SwFormTokens aAuthTokens; + lcl_FillAuthPattern(aAuthTokens, i); + SetPattern(i, aAuthTokens); + } + else + SetPattern( i, aTokens ); + + if( TOX_CONTENT == m_eType && 6 == i ) + pPoolId = STR_POOLCOLL_TOX_CNTNT_EXTRA_ARY; + else if( TOX_USER == m_eType && 6 == i ) + pPoolId = STR_POOLCOLL_TOX_USER_EXTRA_ARY; + else if( TOX_AUTHORITIES == m_eType ) //reuse the same STR_POOLCOLL_TOX_AUTHORITIES1 id each time + pPoolId = STR_POOLCOLL_TOX_AUTHORITIES_ARY + 1; + SetTemplate(i, SwResId(*pPoolId)); + } + } +} + +SwForm::SwForm(const SwForm& rForm) + : m_eType( rForm.m_eType ) +{ + *this = rForm; +} + +SwForm& SwForm::operator=(const SwForm& rForm) +{ + m_eType = rForm.m_eType; + m_nFormMaxLevel = rForm.m_nFormMaxLevel; +// nFirstTabPos = rForm.nFirstTabPos; +// bHasFirstTabPos = rForm.bHasFirstTabPos; + m_bIsRelTabPos = rForm.m_bIsRelTabPos; + m_bCommaSeparated = rForm.m_bCommaSeparated; + for(sal_uInt16 i=0; i < m_nFormMaxLevel; ++i) + { + m_aPattern[i] = rForm.m_aPattern[i]; + m_aTemplate[i] = rForm.m_aTemplate[i]; + } + return *this; +} + +sal_uInt16 SwForm::GetFormMaxLevel( TOXTypes eTOXType ) +{ + switch( eTOXType ) + { + case TOX_INDEX: + return 5; + case TOX_USER: + case TOX_CONTENT: + return MAXLEVEL + 1; + case TOX_ILLUSTRATIONS: + case TOX_OBJECTS: + case TOX_TABLES: + return 2; + case TOX_BIBLIOGRAPHY: + case TOX_CITATION: + case TOX_AUTHORITIES: + return AUTH_TYPE_END + 1; + } + return 0; +} + +void SwForm::AdjustTabStops( SwDoc const & rDoc ) // #i21237# +{ + const sal_uInt16 nFormMax = GetFormMax(); + for ( sal_uInt16 nLevel = 1; nLevel < nFormMax; ++nLevel ) + { + SwTextFormatColl* pColl = rDoc.FindTextFormatCollByName( GetTemplate(nLevel) ); + if( pColl == nullptr ) + { + // Paragraph Style for this level has not been created. + // --> No need to propagate default values + continue; + } + + const SvxTabStopItem& rTabStops = pColl->GetTabStops(false); + const sal_uInt16 nTabCount = rTabStops.Count(); + if (nTabCount != 0) + { + SwFormTokens aCurrentPattern = GetPattern(nLevel); + SwFormTokens::iterator aIt = aCurrentPattern.begin(); + + bool bChanged = false; + for(sal_uInt16 nTab = 0; nTab < nTabCount; ++nTab) + { + const SvxTabStop& rTab = rTabStops[nTab]; + + if ( rTab.GetAdjustment() == SvxTabAdjust::Default ) + continue; // ignore the default tab stop + + aIt = find_if( aIt, aCurrentPattern.end(), SwFormTokenEqualToFormTokenType(TOKEN_TAB_STOP) ); + if ( aIt != aCurrentPattern.end() ) + { + bChanged = true; + aIt->nTabStopPosition = rTab.GetTabPos(); + aIt->eTabAlign = + ( nTab == nTabCount - 1 + && rTab.GetAdjustment() == SvxTabAdjust::Right ) + ? SvxTabAdjust::End + : rTab.GetAdjustment(); + aIt->cTabFillChar = rTab.GetFill(); + ++aIt; + } + else + break; // no more tokens to replace + } + + if ( bChanged ) + SetPattern( nLevel, aCurrentPattern ); + } + } +} + +OUString SwForm::GetFormEntry() {return "<E>";} +OUString SwForm::GetFormTab() {return "<T>";} +OUString SwForm::GetFormPageNums() {return "<#>";} +OUString SwForm::GetFormLinkStt() {return "<LS>";} +OUString SwForm::GetFormLinkEnd() {return "<LE>";} +OUString SwForm::GetFormEntryNum() {return "<E#>";} +OUString SwForm::GetFormEntryText() {return "<ET>";} +OUString SwForm::GetFormChapterMark() {return "<C>";} +OUString SwForm::GetFormText() {return "<X>";} +OUString SwForm::GetFormAuth() {return "<A>";} + +SwTOXBase::SwTOXBase(const SwTOXType* pTyp, const SwForm& rForm, + SwTOXElement nCreaType, const OUString& rTitle ) + : SwClient(const_cast<SwModify*>(static_cast<SwModify const *>(pTyp))) + , m_aForm(rForm) + , m_aTitle(rTitle) + , m_eLanguage(::GetAppLanguage()) + , m_nCreateType(nCreaType) + , m_nOLEOptions(SwTOOElements::NONE) + , m_eCaptionDisplay(CAPTION_COMPLETE) + , m_bProtected( true ) + , m_bFromChapter(false) + , m_bFromObjectNames(false) + , m_bLevelFromChapter(false) + , maMSTOCExpression() + , mbKeepExpression(true) +{ + m_aData.nOptions = SwTOIOptions::NONE; +} + +SwTOXBase::SwTOXBase( const SwTOXBase& rSource, SwDoc* pDoc ) + : SwClient( rSource.GetRegisteredInNonConst() ) + , mbKeepExpression(true) +{ + CopyTOXBase( pDoc, rSource ); +} + +void SwTOXBase::RegisterToTOXType( SwTOXType& rType ) +{ + rType.Add( this ); +} + +void SwTOXBase::CopyTOXBase( SwDoc* pDoc, const SwTOXBase& rSource ) +{ + maMSTOCExpression = rSource.maMSTOCExpression; + SwTOXType* pType = const_cast<SwTOXType*>(rSource.GetTOXType()); + if( pDoc && + std::find_if(pDoc->GetTOXTypes().begin(), pDoc->GetTOXTypes().end(), + [=](const std::unique_ptr<SwTOXType> & p) { return p.get() == pType; }) + == pDoc->GetTOXTypes().end()) + { + // type not in pDoc, so create it now + const SwTOXTypes& rTypes = pDoc->GetTOXTypes(); + bool bFound = false; + for( size_t n = rTypes.size(); n; ) + { + const SwTOXType* pCmp = rTypes[ --n ].get(); + if( pCmp->GetType() == pType->GetType() && + pCmp->GetTypeName() == pType->GetTypeName() ) + { + pType = const_cast<SwTOXType*>(pCmp); + bFound = true; + break; + } + } + + if( !bFound ) + pType = const_cast<SwTOXType*>(pDoc->InsertTOXType( *pType )); + } + pType->Add( this ); + + m_nCreateType = rSource.m_nCreateType; + m_aTitle = rSource.m_aTitle; + m_aForm = rSource.m_aForm; + m_aBookmarkName = rSource.m_aBookmarkName; + m_aEntryTypeName = rSource.m_aEntryTypeName ; + m_bProtected = rSource.m_bProtected; + m_bFromChapter = rSource.m_bFromChapter; + m_bFromObjectNames = rSource.m_bFromObjectNames; + m_sMainEntryCharStyle = rSource.m_sMainEntryCharStyle; + m_sSequenceName = rSource.m_sSequenceName; + m_eCaptionDisplay = rSource.m_eCaptionDisplay; + m_nOLEOptions = rSource.m_nOLEOptions; + m_eLanguage = rSource.m_eLanguage; + m_sSortAlgorithm = rSource.m_sSortAlgorithm; + m_bLevelFromChapter = rSource.m_bLevelFromChapter; + + for( sal_uInt16 i = 0; i < MAXLEVEL; ++i ) + m_aStyleNames[i] = rSource.m_aStyleNames[i]; + + // it's the same data type! + m_aData.nOptions = rSource.m_aData.nOptions; + + if( !pDoc || pDoc->IsCopyIsMove() ) + m_aName = rSource.GetTOXName(); + else + m_aName = pDoc->GetUniqueTOXBaseName( *pType, rSource.GetTOXName() ); +} + +// TOX specific functions +SwTOXBase::~SwTOXBase() +{ +// if( GetTOXType()->GetType() == TOX_USER ) +// delete aData.pTemplateName; +} + +void SwTOXBase::SetTitle(const OUString& rTitle) + { m_aTitle = rTitle; } + +void SwTOXBase::SetBookmarkName(const OUString& bName) +{ + m_aBookmarkName = bName; +} + +void SwTOXBase::SetEntryTypeName(const OUString& sName) +{ + m_aEntryTypeName = sName ; +} + +SwTOXBase & SwTOXBase::operator = (const SwTOXBase & rSource) +{ + m_aForm = rSource.m_aForm; + m_aName = rSource.m_aName; + m_aTitle = rSource.m_aTitle; + m_aBookmarkName = rSource.m_aBookmarkName; + m_aEntryTypeName = rSource.m_aEntryTypeName ; + m_sMainEntryCharStyle = rSource.m_sMainEntryCharStyle; + for(sal_uInt16 nLevel = 0; nLevel < MAXLEVEL; nLevel++) + m_aStyleNames[nLevel] = rSource.m_aStyleNames[nLevel]; + m_sSequenceName = rSource.m_sSequenceName; + m_eLanguage = rSource.m_eLanguage; + m_sSortAlgorithm = rSource.m_sSortAlgorithm; + m_aData = rSource.m_aData; + m_nCreateType = rSource.m_nCreateType; + m_nOLEOptions = rSource.m_nOLEOptions; + m_eCaptionDisplay = rSource.m_eCaptionDisplay; + m_bProtected = rSource.m_bProtected; + m_bFromChapter = rSource.m_bFromChapter; + m_bFromObjectNames = rSource.m_bFromObjectNames; + m_bLevelFromChapter = rSource.m_bLevelFromChapter; + + if (rSource.GetAttrSet()) + SetAttrSet(*rSource.GetAttrSet()); + + return *this; +} + +OUString SwFormToken::GetString() const +{ + OUString sToken; + + switch( eTokenType ) + { + case TOKEN_ENTRY_NO: + sToken = SwForm::GetFormEntryNum(); + break; + case TOKEN_ENTRY_TEXT: + sToken = SwForm::GetFormEntryText(); + break; + case TOKEN_ENTRY: + sToken = SwForm::GetFormEntry(); + break; + case TOKEN_TAB_STOP: + sToken = SwForm::GetFormTab(); + break; + case TOKEN_TEXT: + // Return a Token only if Text is not empty! + if( sText.isEmpty() ) + { + return OUString(); + } + sToken = SwForm::GetFormText(); + break; + case TOKEN_PAGE_NUMS: + sToken = SwForm::GetFormPageNums(); + break; + case TOKEN_CHAPTER_INFO: + sToken = SwForm::GetFormChapterMark(); + break; + case TOKEN_LINK_START: + sToken = SwForm::GetFormLinkStt(); + break; + case TOKEN_LINK_END: + sToken = SwForm::GetFormLinkEnd(); + break; + case TOKEN_AUTHORITY: + { + sToken = SwForm::GetFormAuth(); + } + break; + case TOKEN_END: + break; + } + + OUString sData = " " + sCharStyleName + "," + OUString::number( nPoolId ) + ","; + + // TabStopPosition and TabAlign or ChapterInfoFormat + switch (eTokenType) + { + case TOKEN_TAB_STOP: + sData += OUString::number( nTabStopPosition ) + "," + + OUString::number( static_cast< sal_Int32 >(eTabAlign) ) + "," + + OUStringChar(cTabFillChar) + "," + + OUString::number( bWithTab ? 1 : 0 ); + break; + case TOKEN_CHAPTER_INFO: + case TOKEN_ENTRY_NO: + // add also maximum permitted level + sData += OUString::number( nChapterFormat ) + "," + + OUString::number( nOutlineLevel ); + break; + case TOKEN_TEXT: + sData += OUStringChar(TOX_STYLE_DELIMITER) + + sText.replaceAll(OUStringChar(TOX_STYLE_DELIMITER), "") + + OUStringChar(TOX_STYLE_DELIMITER); + break; + case TOKEN_AUTHORITY: + if (nAuthorityField<10) + { + sData = "0" + OUString::number( nAuthorityField ) + sData; + } + else + { + sData = OUString::number( nAuthorityField ) + sData; + } + break; + default: + break; + } + + return sToken.copy(0, sToken.getLength()-1) + sData + sToken.copy(sToken.getLength()-1); +} + +// -> #i21237# + +/** + Returns the type of a token. + + @param sToken the string representation of the token + @param rTokenLen return parameter the length of the head of the token + + @return the type of the token +*/ +static FormTokenType lcl_GetTokenType(const OUString & sToken, + sal_Int32 & rTokenLen) +{ + static struct + { + OUString sTokenStart; + sal_Int16 nTokenLength; + FormTokenType eTokenType; + } const aTokenArr[] = { + { SwForm::GetFormTab().copy(0, 2), 3, TOKEN_TAB_STOP }, + { SwForm::GetFormPageNums().copy(0, 2), 3, TOKEN_PAGE_NUMS }, + { SwForm::GetFormLinkStt().copy(0, 3), 4, TOKEN_LINK_START }, + { SwForm::GetFormLinkEnd().copy(0, 3), 4, TOKEN_LINK_END }, + { SwForm::GetFormEntryNum().copy(0, 3), 4, TOKEN_ENTRY_NO }, + { SwForm::GetFormEntryText().copy(0, 3), 4, TOKEN_ENTRY_TEXT }, + { SwForm::GetFormChapterMark().copy(0, 2), 3, TOKEN_CHAPTER_INFO }, + { SwForm::GetFormText().copy(0, 2), 3, TOKEN_TEXT }, + { SwForm::GetFormEntry().copy(0, 2), 3, TOKEN_ENTRY }, + { SwForm::GetFormAuth().copy(0, 2), 5, TOKEN_AUTHORITY } + }; + + for(const auto & i : aTokenArr) + { + if( sToken.startsWith( i.sTokenStart ) ) + { + rTokenLen = i.nTokenLength; + return i.eTokenType; + } + } + + SAL_WARN("sw.core", "SwFormTokensHelper: invalid token"); + return TOKEN_END; +} + +/** + Returns the string of a token. + + @param sPattern the whole pattern + @param nStt starting position of the token + + @return the string representation of the token +*/ +static OUString +lcl_SearchNextToken(const OUString & sPattern, sal_Int32 const nStt) +{ + sal_Int32 nEnd = sPattern.indexOf( '>', nStt ); + if (nEnd >= 0) + { + // apparently the TOX_STYLE_DELIMITER act as a bracketing for + // TOKEN_TEXT tokens so that the user can have '>' inside the text... + const sal_Int32 nTextSeparatorFirst = sPattern.indexOf( TOX_STYLE_DELIMITER, nStt ); + if ( nTextSeparatorFirst >= 0 + && nTextSeparatorFirst + 1 < sPattern.getLength() + && nTextSeparatorFirst < nEnd) + { + const sal_Int32 nTextSeparatorSecond = sPattern.indexOf( TOX_STYLE_DELIMITER, + nTextSeparatorFirst + 1 ); + // Since nEnd>=0 we don't need to check if nTextSeparatorSecond<0! + if( nEnd < nTextSeparatorSecond ) + nEnd = sPattern.indexOf( '>', nTextSeparatorSecond ); + // FIXME: No check to verify that nEnd is still >=0? + assert(nEnd >= 0); + } + + ++nEnd; + + return sPattern.copy( nStt, nEnd - nStt ); + } + + return OUString(); +} + +/** + Builds a token from its string representation. + + @sPattern the whole pattern + @nCurPatternPos starting position of the token + + @return the token + */ +static std::optional<SwFormToken> +lcl_BuildToken(const OUString & sPattern, sal_Int32 & nCurPatternPos) +{ + OUString sToken( lcl_SearchNextToken(sPattern, nCurPatternPos) ); + nCurPatternPos += sToken.getLength(); + sal_Int32 nTokenLen = 0; + FormTokenType const eTokenType = lcl_GetTokenType(sToken, nTokenLen); + if (TOKEN_END == eTokenType) // invalid input? skip it + { + nCurPatternPos = sPattern.getLength(); + return std::optional<SwFormToken>(); + } + + // at this point sPattern contains the + // character style name, the PoolId, tab stop position, tab stop alignment, chapter info format + // the form is: CharStyleName, PoolId[, TabStopPosition|ChapterInfoFormat[, TabStopAlignment[, TabFillChar]]] + // in text tokens the form differs from the others: CharStyleName, PoolId[,\0xffinserted text\0xff] + SwFormToken eRet( eTokenType ); + const OUString sAuthFieldEnum = sToken.copy( 2, 2 ); + sToken = sToken.copy( nTokenLen, sToken.getLength() - nTokenLen - 1); + + sal_Int32 nIdx{ 0 }; + eRet.sCharStyleName = sToken.getToken( 0, ',', nIdx ); + OUString sTmp( sToken.getToken( 0, ',', nIdx )); + if( !sTmp.isEmpty() ) + eRet.nPoolId = static_cast<sal_uInt16>(sTmp.toInt32()); + + switch( eTokenType ) + { +//i53420 + case TOKEN_CHAPTER_INFO: +//i53420 + case TOKEN_ENTRY_NO: + sTmp = sToken.getToken( 0, ',', nIdx ); // token 2 + if( !sTmp.isEmpty() ) + eRet.nChapterFormat = static_cast<sal_uInt16>(sTmp.toInt32()); + sTmp = sToken.getToken( 0, ',', nIdx ); // token 3 + if( !sTmp.isEmpty() ) + eRet.nOutlineLevel = static_cast<sal_uInt16>(sTmp.toInt32()); //the maximum outline level to examine + break; + + case TOKEN_TEXT: + { + const sal_Int32 nStartText = sToken.indexOf( TOX_STYLE_DELIMITER ); + if( nStartText>=0 && nStartText+1<sToken.getLength()) + { + const sal_Int32 nEndText = sToken.indexOf( TOX_STYLE_DELIMITER, + nStartText + 1); + if( nEndText>=0 ) + { + eRet.sText = sToken.copy( nStartText + 1, + nEndText - nStartText - 1); + } + } + } + break; + + case TOKEN_TAB_STOP: + sTmp = sToken.getToken( 0, ',', nIdx ); // token 2 + if( !sTmp.isEmpty() ) + eRet.nTabStopPosition = sTmp.toInt32(); + + sTmp = sToken.getToken( 0, ',', nIdx ); // token 3 + if( !sTmp.isEmpty() ) + eRet.eTabAlign = static_cast<SvxTabAdjust>(sTmp.toInt32()); + + sTmp = sToken.getToken( 0, ',', nIdx ); // token 4 + if( !sTmp.isEmpty() ) + eRet.cTabFillChar = sTmp[0]; + + sTmp = sToken.getToken( 0, ',', nIdx ); // token 5 + if( !sTmp.isEmpty() ) + eRet.bWithTab = 0 != sTmp.toInt32(); + break; + + case TOKEN_AUTHORITY: + eRet.nAuthorityField = static_cast<sal_uInt16>(sAuthFieldEnum.toInt32()); + break; + default: break; + } + return eRet; +} + +SwFormTokensHelper::SwFormTokensHelper(const OUString & rPattern) +{ + sal_Int32 nCurPatternPos = 0; + + while (nCurPatternPos < rPattern.getLength()) + { + std::optional<SwFormToken> const oToken( + lcl_BuildToken(rPattern, nCurPatternPos)); + if (oToken) + m_Tokens.push_back(*oToken); + } +} + +// <- #i21237# + +void SwForm::SetPattern(sal_uInt16 nLevel, const SwFormTokens& rTokens) +{ + OSL_ENSURE(nLevel < GetFormMax(), "Index >= FORM_MAX"); + m_aPattern[nLevel] = rTokens; +} + +void SwForm::SetPattern(sal_uInt16 nLevel, const OUString & rStr) +{ + OSL_ENSURE(nLevel < GetFormMax(), "Index >= FORM_MAX"); + + SwFormTokensHelper aHelper(rStr); + m_aPattern[nLevel] = aHelper.GetTokens(); +} + +const SwFormTokens& SwForm::GetPattern(sal_uInt16 nLevel) const +{ + OSL_ENSURE(nLevel < GetFormMax(), "Index >= FORM_MAX"); + return m_aPattern[nLevel]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/tox/toxhlp.cxx b/sw/source/core/tox/toxhlp.cxx new file mode 100644 index 000000000..058d83b8d --- /dev/null +++ b/sw/source/core/tox/toxhlp.cxx @@ -0,0 +1,119 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/IndexEntrySupplier.hpp> +#include <toxwrap.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; + +IndexEntrySupplierWrapper::IndexEntrySupplierWrapper() +{ + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + try { + xIES = i18n::IndexEntrySupplier::create(xContext); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw.core", "IndexEntrySupplierWrapper" ); + } +} + +IndexEntrySupplierWrapper::~IndexEntrySupplierWrapper() +{ +} + +OUString IndexEntrySupplierWrapper::GetIndexKey( const OUString& rText, + const OUString& rTextReading, + const css::lang::Locale& rLocale ) const +{ + OUString sRet; + try { + sRet = xIES->getIndexKey( rText, rTextReading, rLocale ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw.core", "getIndexKey" ); + } + return sRet; +} + +OUString IndexEntrySupplierWrapper::GetFollowingText( bool bMorePages ) const +{ + OUString sRet; + try { + sRet = xIES->getIndexFollowPageWord( bMorePages, aLcl ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw.core", "getIndexFollowPageWord" ); + } + return sRet; +} + +css::uno::Sequence< OUString > IndexEntrySupplierWrapper::GetAlgorithmList( const css::lang::Locale& rLcl ) const +{ + uno::Sequence< OUString > sRet; + + try { + sRet = xIES->getAlgorithmList( rLcl ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw.core", "getAlgorithmList" ); + } + return sRet; +} + +bool IndexEntrySupplierWrapper::LoadAlgorithm( + const css::lang::Locale& rLcl, + const OUString& sSortAlgorithm, long nOptions ) const +{ + bool bRet = false; + try { + bRet = xIES->loadAlgorithm( rLcl, sSortAlgorithm, nOptions ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw.core", "loadAlgorithm" ); + } + return bRet; +} + +sal_Int16 IndexEntrySupplierWrapper::CompareIndexEntry( + const OUString& rText1, const OUString& rTextReading1, + const css::lang::Locale& rLocale1, + const OUString& rText2, const OUString& rTextReading2, + const css::lang::Locale& rLocale2 ) const +{ + sal_Int16 nRet = 0; + try { + nRet = xIES->compareIndexEntry( rText1, rTextReading1, rLocale1, + rText2, rTextReading2, rLocale2 ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sw.core", "compareIndexEntry" ); + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/tox/txmsrt.cxx b/sw/source/core/tox/txmsrt.cxx new file mode 100644 index 000000000..cc77ef66e --- /dev/null +++ b/sw/source/core/tox/txmsrt.cxx @@ -0,0 +1,864 @@ +/* -*- 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 <unotools/charclass.hxx> +#include <txtfld.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <modeltoviewhelper.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <txttxmrk.hxx> +#include <frmfmt.hxx> +#include <fmtfld.hxx> +#include <txmsrt.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <expfld.hxx> +#include <authfld.hxx> +#include <toxwrap.hxx> + +#include <strings.hrc> +#include <reffld.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +// Initialize strings +SwTOIOptions SwTOXSortTabBase::nOpt = SwTOIOptions::NONE; + +SwTOXInternational::SwTOXInternational( LanguageType nLang, SwTOIOptions nOpt, + const OUString& rSortAlgorithm ) : + m_eLang( nLang ), + m_sSortAlgorithm(rSortAlgorithm), + m_nOptions( nOpt ) +{ + Init(); +} + +SwTOXInternational::SwTOXInternational( const SwTOXInternational& rIntl ) : + m_eLang( rIntl.m_eLang ), + m_sSortAlgorithm(rIntl.m_sSortAlgorithm), + m_nOptions( rIntl.m_nOptions ) +{ + Init(); +} + +void SwTOXInternational::Init() +{ + m_pIndexWrapper.reset( new IndexEntrySupplierWrapper() ); + + const lang::Locale aLcl( LanguageTag::convertToLocale( m_eLang ) ); + m_pIndexWrapper->SetLocale( aLcl ); + + if(m_sSortAlgorithm.isEmpty()) + { + Sequence < OUString > aSeq( m_pIndexWrapper->GetAlgorithmList( aLcl )); + if(aSeq.hasElements()) + m_sSortAlgorithm = aSeq.getConstArray()[0]; + } + + if ( m_nOptions & SwTOIOptions::CaseSensitive ) + m_pIndexWrapper->LoadAlgorithm( aLcl, m_sSortAlgorithm, 0 ); + else + m_pIndexWrapper->LoadAlgorithm( aLcl, m_sSortAlgorithm, SW_COLLATOR_IGNORES ); + + m_pCharClass.reset( new CharClass( LanguageTag( aLcl )) ); + +} + +SwTOXInternational::~SwTOXInternational() +{ + m_pCharClass.reset(); + m_pIndexWrapper.reset(); +} + +OUString SwTOXInternational::ToUpper( const OUString& rStr, sal_Int32 nPos ) const +{ + return m_pCharClass->uppercase( rStr, nPos, 1 ); +} + +inline bool SwTOXInternational::IsNumeric( const OUString& rStr ) const +{ + return m_pCharClass->isNumeric( rStr ); +} + +sal_Int32 SwTOXInternational::Compare( const TextAndReading& rTaR1, + const lang::Locale& rLocale1, + const TextAndReading& rTaR2, + const lang::Locale& rLocale2 ) const +{ + return m_pIndexWrapper->CompareIndexEntry( rTaR1.sText, rTaR1.sReading, rLocale1, + rTaR2.sText, rTaR2.sReading, rLocale2 ); +} + +OUString SwTOXInternational::GetIndexKey( const TextAndReading& rTaR, + const lang::Locale& rLocale ) const +{ + return m_pIndexWrapper->GetIndexKey( rTaR.sText, rTaR.sReading, rLocale ); +} + +OUString SwTOXInternational::GetFollowingText( bool bMorePages ) const +{ + return m_pIndexWrapper->GetFollowingText( bMorePages ); +} + +// SortElement for TOX entries +SwTOXSortTabBase::SwTOXSortTabBase( TOXSortType nTyp, const SwContentNode* pNd, + const SwTextTOXMark* pMark, + const SwTOXInternational* pInter, + const lang::Locale* pLocale ) + : pTOXNd( nullptr ), pTextMark( pMark ), pTOXIntl( pInter ), + nPos( 0 ), nCntPos( 0 ), nType( static_cast<sal_uInt16>(nTyp) ) + , m_bValidText( false ) +{ + if ( pLocale ) + aLocale = *pLocale; + + if( pNd ) + { + sal_Int32 n = 0; + if( pTextMark ) + n = pTextMark->GetStart(); + SwTOXSource aTmp( pNd, n, pTextMark && pTextMark->GetTOXMark().IsMainEntry() ); + aTOXSources.push_back(aTmp); + + nPos = pNd->GetIndex(); + + switch( nTyp ) + { + case TOX_SORT_CONTENT: + case TOX_SORT_PARA: + case TOX_SORT_TABLE: + // If they are in a special areas, we should get the position at the + // body + if( nPos < pNd->GetNodes().GetEndOfExtras().GetIndex() ) + { + // Then get the 'anchor' (body) position + Point aPt; + std::pair<Point, bool> tmp(aPt, false); + const SwContentFrame *const pFrame = pNd->getLayoutFrame( + pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + if( pFrame ) + { + SwPosition aPos( *pNd ); + const SwDoc& rDoc = *pNd->GetDoc(); + bool const bResult = GetBodyTextNode( rDoc, aPos, *pFrame ); + OSL_ENSURE(bResult, "where is the text node"); + nPos = aPos.nNode.GetIndex(); + nCntPos = aPos.nContent.GetIndex(); + } + } + else + nCntPos = n; + break; + default: break; + } + } +} + +OUString SwTOXSortTabBase::GetURL() const +{ + return OUString(); +} + +bool SwTOXSortTabBase::IsFullPara() const +{ + return false; +} + +void SwTOXSortTabBase::FillText( SwTextNode& rNd, const SwIndex& rInsPos, + sal_uInt16, SwRootFrame const*const) const +{ + rNd.InsertText( GetText().sText, rInsPos ); +} + +bool SwTOXSortTabBase::equivalent(const SwTOXSortTabBase& rCmp) +{ + bool bRet = nPos == rCmp.nPos && nCntPos == rCmp.nCntPos && + (!aTOXSources[0].pNd || !rCmp.aTOXSources[0].pNd || + aTOXSources[0].pNd == rCmp.aTOXSources[0].pNd ); + + if( TOX_SORT_CONTENT == nType ) + { + bRet = bRet && pTextMark && rCmp.pTextMark && + pTextMark->GetStart() == rCmp.pTextMark->GetStart(); + + if( bRet ) + { + // Both pointers exist -> compare text + // else -> compare AlternativeText + const sal_Int32 *pEnd = pTextMark->End(); + const sal_Int32 *pEndCmp = rCmp.pTextMark->End(); + + bRet = ( ( pEnd && pEndCmp ) || ( !pEnd && !pEndCmp ) ) && + pTOXIntl->IsEqual( GetText(), GetLocale(), + rCmp.GetText(), rCmp.GetLocale() ); + } + } + return bRet; +} + +bool SwTOXSortTabBase::sort_lt(const SwTOXSortTabBase& rCmp) +{ + if( nPos < rCmp.nPos ) + return true; + + if( nPos == rCmp.nPos ) + { + if( nCntPos < rCmp.nCntPos ) + return true; + + if( nCntPos == rCmp.nCntPos ) + { + const SwNode* pFirst = aTOXSources[0].pNd; + const SwNode* pNext = rCmp.aTOXSources[0].pNd; + + if( pFirst && pFirst == pNext ) + { + if( TOX_SORT_CONTENT == nType && pTextMark && rCmp.pTextMark ) + { + if( pTextMark->GetStart() < rCmp.pTextMark->GetStart() ) + return true; + + if( pTextMark->GetStart() == rCmp.pTextMark->GetStart() ) + { + const sal_Int32 *pEnd = pTextMark->End(); + const sal_Int32 *pEndCmp = rCmp.pTextMark->End(); + + // Both pointers exist -> compare text + // else -> compare AlternativeText + if( ( pEnd && pEndCmp ) || ( !pEnd && !pEndCmp ) ) + { + return pTOXIntl->IsLess( GetText(), GetLocale(), + rCmp.GetText(), rCmp.GetLocale() ); + } + if( pEnd && !pEndCmp ) + return true; + } + } + } + else if( pFirst && pFirst->IsTextNode() && + pNext && pNext->IsTextNode() ) + return ::IsFrameBehind( *static_cast<const SwTextNode*>(pNext), nCntPos, + *static_cast<const SwTextNode*>(pFirst), nCntPos ); + } + } + return false; +} + +// Sorted keyword entry +SwTOXIndex::SwTOXIndex( const SwTextNode& rNd, + const SwTextTOXMark* pMark, SwTOIOptions nOptions, + sal_uInt8 nKyLevel, + const SwTOXInternational& rIntl, + const lang::Locale& rLocale ) + : SwTOXSortTabBase( TOX_SORT_INDEX, &rNd, pMark, &rIntl, &rLocale ), + nKeyLevel(nKyLevel) +{ + nPos = rNd.GetIndex(); + nOpt = nOptions; +} + +// Compare keywords. Only relates to the text. + +bool SwTOXIndex::equivalent(const SwTOXSortTabBase& rCmpBase) +{ + const SwTOXIndex& rCmp = static_cast<const SwTOXIndex&>(rCmpBase); + + // Respect case taking dependencies into account + if(GetLevel() != rCmp.GetLevel() || nKeyLevel != rCmp.nKeyLevel) + return false; + + OSL_ENSURE(pTextMark, "pTextMark == 0, No keyword"); + + bool bRet = pTOXIntl->IsEqual( GetText(), GetLocale(), + rCmp.GetText(), rCmp.GetLocale() ); + + // If we don't summarize we need to evaluate the Pos + if(bRet && !(GetOptions() & SwTOIOptions::SameEntry)) + bRet = nPos == rCmp.nPos; + + return bRet; +} + +// operator, only depends on the text + +bool SwTOXIndex::sort_lt(const SwTOXSortTabBase& rCmpBase) +{ + const SwTOXIndex& rCmp = static_cast<const SwTOXIndex&>(rCmpBase); + + OSL_ENSURE(pTextMark, "pTextMark == 0, No keyword"); + + const TextAndReading aMyTaR(GetText()); + const TextAndReading aOtherTaR(rCmp.GetText()); + + bool bRet = GetLevel() == rCmp.GetLevel() && + pTOXIntl->IsLess( aMyTaR, GetLocale(), + aOtherTaR, rCmp.GetLocale() ); + + // If we don't summarize we need to evaluate the Pos + if( !bRet && !(GetOptions() & SwTOIOptions::SameEntry) ) + { + bRet = pTOXIntl->IsEqual( aMyTaR, GetLocale(), + aOtherTaR, rCmp.GetLocale() ) && + nPos < rCmp.nPos; + } + + return bRet; +} + +// The keyword itself + +TextAndReading SwTOXIndex::GetText_Impl(SwRootFrame const*const pLayout) const +{ + OSL_ENSURE(pTextMark, "pTextMark == 0, No keyword"); + const SwTOXMark& rTOXMark = pTextMark->GetTOXMark(); + + TextAndReading aRet; + switch(nKeyLevel) + { + case FORM_PRIMARY_KEY : + { + aRet.sText = rTOXMark.GetPrimaryKey(); + aRet.sReading = rTOXMark.GetPrimaryKeyReading(); + } + break; + case FORM_SECONDARY_KEY : + { + aRet.sText = rTOXMark.GetSecondaryKey(); + aRet.sReading = rTOXMark.GetSecondaryKeyReading(); + } + break; + case FORM_ENTRY : + { + aRet.sText = rTOXMark.GetText(pLayout); + aRet.sReading = rTOXMark.GetTextReading(); + } + break; + } + // if SwTOIOptions::InitialCaps is set, first character is to be capitalized + if( SwTOIOptions::InitialCaps & nOpt && pTOXIntl && !aRet.sText.isEmpty()) + { + aRet.sText = pTOXIntl->ToUpper( aRet.sText, 0 ) + aRet.sText.copy(1); + } + + return aRet; +} + +void SwTOXIndex::FillText( SwTextNode& rNd, const SwIndex& rInsPos, sal_uInt16, + SwRootFrame const*const pLayout) const +{ + assert(!"sw_redlinehide: this is dead code, Bibliography only has SwTOXAuthority"); + const sal_Int32* pEnd = pTextMark->End(); + + TextAndReading aRet; + if( pEnd && !pTextMark->GetTOXMark().IsAlternativeText() && + !(GetOptions() & SwTOIOptions::KeyAsEntry)) + { + aRet.sText = static_cast<const SwTextNode*>(aTOXSources[0].pNd)->GetExpandText( + pLayout, + pTextMark->GetStart(), + *pEnd - pTextMark->GetStart(), + false, false, false, + ExpandMode::ExpandFootnote + | (pLayout && pLayout->IsHideRedlines() + ? ExpandMode::HideDeletions + : ExpandMode(0))); + if(SwTOIOptions::InitialCaps & nOpt && pTOXIntl && !aRet.sText.isEmpty()) + { + aRet.sText = pTOXIntl->ToUpper( aRet.sText, 0 ) + aRet.sText.copy(1); + } + } + else + aRet = GetText(); + + rNd.InsertText( aRet.sText, rInsPos ); +} + +sal_uInt16 SwTOXIndex::GetLevel() const +{ + OSL_ENSURE(pTextMark, "pTextMark == 0, No keyword"); + + sal_uInt16 nForm = FORM_PRIMARY_KEY; + + if( !(GetOptions() & SwTOIOptions::KeyAsEntry)&& + !pTextMark->GetTOXMark().GetPrimaryKey().isEmpty() ) + { + nForm = FORM_SECONDARY_KEY; + if( !pTextMark->GetTOXMark().GetSecondaryKey().isEmpty() ) + nForm = FORM_ENTRY; + } + return nForm; +} + +// Key and separator +SwTOXCustom::SwTOXCustom(const TextAndReading& rKey, + sal_uInt16 nLevel, + const SwTOXInternational& rIntl, + const lang::Locale& rLocale ) + : SwTOXSortTabBase( TOX_SORT_CUSTOM, nullptr, nullptr, &rIntl, &rLocale ), + m_aKey(rKey), nLev(nLevel) +{ +} + +bool SwTOXCustom::equivalent(const SwTOXSortTabBase& rCmpBase) +{ + return GetLevel() == rCmpBase.GetLevel() && + pTOXIntl->IsEqual( GetText(), GetLocale(), + rCmpBase.GetText(), rCmpBase.GetLocale() ); +} + +bool SwTOXCustom::sort_lt(const SwTOXSortTabBase& rCmpBase) +{ + return GetLevel() <= rCmpBase.GetLevel() && + pTOXIntl->IsLess( GetText(), GetLocale(), + rCmpBase.GetText(), rCmpBase.GetLocale() ); +} + +sal_uInt16 SwTOXCustom::GetLevel() const +{ + return nLev; +} + +TextAndReading SwTOXCustom::GetText_Impl(SwRootFrame const*const) const +{ + return m_aKey; +} + +// Sorts the TOX entries +SwTOXContent::SwTOXContent( const SwTextNode& rNd, const SwTextTOXMark* pMark, + const SwTOXInternational& rIntl) + : SwTOXSortTabBase( TOX_SORT_CONTENT, &rNd, pMark, &rIntl ) +{ +} + +// The content's text + +TextAndReading SwTOXContent::GetText_Impl(SwRootFrame const*const pLayout) const +{ + const sal_Int32* pEnd = pTextMark->End(); + if( pEnd && !pTextMark->GetTOXMark().IsAlternativeText() ) + { + return TextAndReading( + static_cast<const SwTextNode*>(aTOXSources[0].pNd)->GetExpandText( + pLayout, + pTextMark->GetStart(), + *pEnd - pTextMark->GetStart(), + false, false, false, + ExpandMode::ExpandFootnote + | (pLayout && pLayout->IsHideRedlines() + ? ExpandMode::HideDeletions + : ExpandMode(0))), + pTextMark->GetTOXMark().GetTextReading()); + } + + return TextAndReading(pTextMark->GetTOXMark().GetAlternativeText(), OUString()); +} + +void SwTOXContent::FillText(SwTextNode& rNd, const SwIndex& rInsPos, sal_uInt16, + SwRootFrame const*const pLayout) const +{ + assert(!"sw_redlinehide: this is dead code, Bibliography only has SwTOXAuthority"); + const sal_Int32* pEnd = pTextMark->End(); + if( pEnd && !pTextMark->GetTOXMark().IsAlternativeText() ) + // sw_redlinehide: this probably won't HideDeletions + static_cast<const SwTextNode*>(aTOXSources[0].pNd)->CopyExpandText( + rNd, &rInsPos, pTextMark->GetStart(), + *pEnd - pTextMark->GetStart(), pLayout); + else + { + rNd.InsertText( GetText().sText, rInsPos ); + } +} + +// The level for displaying it + +sal_uInt16 SwTOXContent::GetLevel() const +{ + return pTextMark->GetTOXMark().GetLevel(); +} + +// TOX assembled from paragraphs +// Watch out for OLE/graphics when sorting! +// The position must not come from the document, but from the "anchor"! +SwTOXPara::SwTOXPara(SwContentNode& rNd, SwTOXElement eT, sal_uInt16 nLevel, const OUString& sSeqName) + : SwTOXSortTabBase( TOX_SORT_PARA, &rNd, nullptr, nullptr ), + eType( eT ), + m_nLevel(nLevel), + nStartIndex(0), + nEndIndex(-1), + m_sSequenceName( sSeqName ) +{ + // tdf#123313 create any missing bookmarks *before* generating ToX nodes! + switch (eType) + { + case SwTOXElement::Template: + case SwTOXElement::OutlineLevel: + assert(rNd.IsTextNode()); + rNd.GetDoc()->getIDocumentMarkAccess()->getMarkForTextNode( + *rNd.GetTextNode(), IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK); + break; + default: + break; + } +} + +TextAndReading SwTOXPara::GetText_Impl(SwRootFrame const*const pLayout) const +{ + const SwContentNode* pNd = aTOXSources[0].pNd; + switch( eType ) + { + case SwTOXElement::Sequence: + if (nStartIndex != 0 || nEndIndex != -1) + { + // sw_redlinehide: "captions" are a rather fuzzily defined concept anyway + return TextAndReading(static_cast<const SwTextNode*>(pNd)->GetExpandText( + pLayout, + nStartIndex, + nEndIndex == -1 ? -1 : nEndIndex - nStartIndex, + false, false, false, + pLayout && pLayout->IsHideRedlines() + ? ExpandMode::HideDeletions + : ExpandMode(0)), + OUString()); + } + BOOST_FALLTHROUGH; + case SwTOXElement::Template: + case SwTOXElement::OutlineLevel: + { + assert(nStartIndex == 0); + assert(nEndIndex == -1); + return TextAndReading(sw::GetExpandTextMerged( + pLayout, *static_cast<const SwTextNode*>(pNd), + false, false, ExpandMode(0)), + OUString()); + } + break; + + case SwTOXElement::Ole: + case SwTOXElement::Graphic: + case SwTOXElement::Frame: + { + // Find the FlyFormat; the object/graphic name is there + SwFrameFormat* pFly = pNd->GetFlyFormat(); + if( pFly ) + return TextAndReading(pFly->GetName(), OUString()); + + OSL_ENSURE( false, "Graphic/object without name" ); + const char* pId = SwTOXElement::Ole == eType + ? STR_OBJECT_DEFNAME + : SwTOXElement::Graphic == eType + ? STR_GRAPHIC_DEFNAME + : STR_FRAME_DEFNAME; + return TextAndReading(SwResId(pId), OUString()); + } + break; + default: break; + } + return TextAndReading(); +} + +void SwTOXPara::FillText( SwTextNode& rNd, const SwIndex& rInsPos, sal_uInt16, + SwRootFrame const*const pLayout) const +{ + assert(!"sw_redlinehide: this is dead code, Bibliography only has SwTOXAuthority"); + if( SwTOXElement::Template == eType || SwTOXElement::Sequence == eType || SwTOXElement::OutlineLevel == eType) + { + const SwTextNode* pSrc = static_cast<const SwTextNode*>(aTOXSources[0].pNd); + if (SwTOXElement::Sequence == eType + && (nStartIndex != 0 || nEndIndex != -1)) + { + pSrc->CopyExpandText( rNd, &rInsPos, nStartIndex, + nEndIndex == -1 ? -1 : nEndIndex - nStartIndex, + pLayout, false, false, true ); + } + else + { + assert(nStartIndex == 0); + assert(nEndIndex == -1); + // sw_redlinehide: this probably won't HideDeletions + pSrc->CopyExpandText( rNd, &rInsPos, 0, -1, + pLayout, false, false, true ); + if (pLayout && pLayout->IsHideRedlines()) + { + if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pSrc->getLayoutFrame(pLayout))) + { + if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara()) + { + // pSrc already copied above + assert(pSrc == pMerged->pParaPropsNode); + for (sal_uLong i = pSrc->GetIndex() + 1; + i <= pMerged->pLastNode->GetIndex(); ++i) + { + SwNode *const pTmp(pSrc->GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + + pTmp->GetTextNode()->CopyExpandText( + rNd, &rInsPos, 0, -1, + pLayout, false, false, false ); + } + } + } + } + } + } + } + else + { + rNd.InsertText( GetText().sText.replace('\t', ' '), rInsPos ); + } +} + +sal_uInt16 SwTOXPara::GetLevel() const +{ + sal_uInt16 nRet = m_nLevel; + const SwContentNode* pNd = aTOXSources[0].pNd; + + if( SwTOXElement::OutlineLevel == eType && pNd->GetTextNode() ) + { + const int nTmp = static_cast<const SwTextNode*>(pNd)->GetAttrOutlineLevel(); + if(nTmp != 0 ) + nRet = static_cast<sal_uInt16>(nTmp); + } + return nRet; +} + +OUString SwTOXPara::GetURL() const +{ + OUString aText; + const SwContentNode* pNd = aTOXSources[0].pNd; + switch( eType ) + { + case SwTOXElement::Template: + case SwTOXElement::OutlineLevel: + { + const SwTextNode * pTextNd = pNd->GetTextNode(); + + SwDoc* pDoc = const_cast<SwDoc*>( pTextNd->GetDoc() ); + // tdf#123313: this *must not* create a bookmark, its Undo would + // be screwed! create it as preparatory step, in ctor! + ::sw::mark::IMark const * const pMark = pDoc->getIDocumentMarkAccess()->getMarkForTextNode( + *pTextNd, + IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK); + aText = "#" + pMark->GetName(); + } + break; + + case SwTOXElement::Ole: + case SwTOXElement::Graphic: + case SwTOXElement::Frame: + { + // Find the FlyFormat; the object/graphic name is there + SwFrameFormat* pFly = pNd->GetFlyFormat(); + if( pFly ) + { + aText = "#" + pFly->GetName() + OUStringChar(cMarkSeparator); + const char* pStr; + switch( eType ) + { + case SwTOXElement::Ole: pStr = "ole"; break; + case SwTOXElement::Graphic: pStr = "graphic"; break; + case SwTOXElement::Frame: pStr = "frame"; break; + default: pStr = nullptr; + } + if( pStr ) + aText += OUString::createFromAscii( pStr ); + } + } + break; + case SwTOXElement::Sequence: + { + aText = "#" + m_sSequenceName + OUStringChar(cMarkSeparator) + + "sequence"; + } + break; + default: break; + } + return aText; +} + +bool SwTOXPara::IsFullPara() const +{ + switch (eType) + { + case SwTOXElement::Sequence: + case SwTOXElement::Template: + case SwTOXElement::OutlineLevel: + return nStartIndex == 0 && nEndIndex == -1; + default: + return false; + } +} + +// Table +SwTOXTable::SwTOXTable( const SwContentNode& rNd ) + : SwTOXSortTabBase( TOX_SORT_TABLE, &rNd, nullptr, nullptr ), + nLevel(FORM_ALPHA_DELIMITER) +{ +} + +TextAndReading SwTOXTable::GetText_Impl(SwRootFrame const*const) const +{ + const SwNode* pNd = aTOXSources[0].pNd; + if( pNd ) + { + const SwTableNode* pTableNd = + reinterpret_cast<const SwTableNode*>(pNd->FindTableNode()); + if (pTableNd) + { + return TextAndReading(pTableNd->GetTable().GetFrameFormat()->GetName(), OUString()); + } + } + + OSL_ENSURE( false, "Where's my table?" ); + return TextAndReading(SwResId( STR_TABLE_DEFNAME ), OUString()); +} + +sal_uInt16 SwTOXTable::GetLevel() const +{ + return nLevel; +} + +OUString SwTOXTable::GetURL() const +{ + const SwNode* pNd = aTOXSources[0].pNd; + if (!pNd) + return OUString(); + + pNd = pNd->FindTableNode(); + if (!pNd) + return OUString(); + + const OUString sName = static_cast<const SwTableNode*>(pNd)->GetTable().GetFrameFormat()->GetName(); + if ( sName.isEmpty() ) + return OUString(); + + return "#" + sName + OUStringChar(cMarkSeparator) + "table"; +} + +SwTOXAuthority::SwTOXAuthority( const SwContentNode& rNd, + SwFormatField& rField, const SwTOXInternational& rIntl ) : + SwTOXSortTabBase( TOX_SORT_AUTHORITY, &rNd, nullptr, &rIntl ), + m_rField(rField) +{ + if(rField.GetTextField()) + nCntPos = rField.GetTextField()->GetStart(); +} + +sal_uInt16 SwTOXAuthority::GetLevel() const +{ + OUString sText(static_cast<SwAuthorityField*>(m_rField.GetField())->GetFieldText(AUTH_FIELD_AUTHORITY_TYPE)); + //#i18655# the level '0' is the heading level therefore the values are incremented here + sal_uInt16 nRet = 1; + if( pTOXIntl->IsNumeric( sText ) ) + { + nRet = sText.toUInt32(); + nRet++; + } + //illegal values are also set to 'ARTICLE' as non-numeric values are + if(nRet > AUTH_TYPE_END) + nRet = 1; + return nRet; +} + +static OUString lcl_GetText(SwFormatField const& rField, SwRootFrame const*const pLayout) +{ + return rField.GetField()->ExpandField(true, pLayout); +} + +TextAndReading SwTOXAuthority::GetText_Impl(SwRootFrame const*const pLayout) const +{ + return TextAndReading(lcl_GetText(m_rField, pLayout), OUString()); +} + +void SwTOXAuthority::FillText( SwTextNode& rNd, + const SwIndex& rInsPos, sal_uInt16 nAuthField, + SwRootFrame const*const pLayout) const +{ + SwAuthorityField* pField = static_cast<SwAuthorityField*>(m_rField.GetField()); + OUString sText; + if(AUTH_FIELD_IDENTIFIER == nAuthField) + { + sText = lcl_GetText(m_rField, pLayout); + const SwAuthorityFieldType* pType = static_cast<const SwAuthorityFieldType*>(pField->GetTyp()); + sal_Unicode cChar = pType->GetPrefix(); + if(cChar && cChar != ' ') + sText = sText.copy(1); + cChar = pType->GetSuffix(); + if(cChar && cChar != ' ') + sText = sText.copy(0, sText.getLength() - 1); + } + else if(AUTH_FIELD_AUTHORITY_TYPE == nAuthField) + { + sal_uInt16 nLevel = GetLevel(); + if(nLevel) + sText = SwAuthorityFieldType::GetAuthTypeName(static_cast<ToxAuthorityType>(--nLevel)); + } + else + sText = pField->GetFieldText(static_cast<ToxAuthorityField>(nAuthField)); + rNd.InsertText( sText, rInsPos ); +} + +bool SwTOXAuthority::equivalent(const SwTOXSortTabBase& rCmp) +{ + return nType == rCmp.nType && + static_cast<SwAuthorityField*>(m_rField.GetField())->GetAuthEntry() == + static_cast<SwAuthorityField*>(static_cast<const SwTOXAuthority&>(rCmp).m_rField.GetField())->GetAuthEntry(); +} + +bool SwTOXAuthority::sort_lt(const SwTOXSortTabBase& rBase) +{ + bool bRet = false; + SwAuthorityField* pField = static_cast<SwAuthorityField*>(m_rField.GetField()); + SwAuthorityFieldType* pType = static_cast<SwAuthorityFieldType*>( + pField->GetTyp()); + if(pType->IsSortByDocument()) + bRet = SwTOXSortTabBase::sort_lt(rBase); + else + { + SwAuthorityField* pCmpField = + static_cast<SwAuthorityField*>(static_cast<const SwTOXAuthority&>(rBase).m_rField.GetField()); + + for(sal_uInt16 i = 0; i < pType->GetSortKeyCount(); i++) + { + const SwTOXSortKey* pKey = pType->GetSortKey(i); + const TextAndReading aMy(pField->GetFieldText(pKey->eField), OUString()); + const TextAndReading aOther(pCmpField->GetFieldText(pKey->eField), OUString()); + + sal_Int32 nComp = pTOXIntl->Compare( aMy, GetLocale(), + aOther, rBase.GetLocale() ); + + if( nComp ) + { + bRet = (-1 == nComp) == pKey->bSortAscending; + break; + } + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/SwGrammarContact.cxx b/sw/source/core/txtnode/SwGrammarContact.cxx new file mode 100644 index 000000000..d957bfbcc --- /dev/null +++ b/sw/source/core/txtnode/SwGrammarContact.cxx @@ -0,0 +1,190 @@ +/* -*- 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 <vcl/timer.hxx> +#include <hints.hxx> +#include <IGrammarContact.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <SwGrammarMarkUp.hxx> +#include <txtfrm.hxx> + +namespace { + +/* + * This class is responsible for the delayed display of grammar checks when a paragraph is edited + * It's a client of the paragraph the cursor points to. + * If the cursor position changes, updateCursorPosition has to be called + * If the grammar checker wants to set a grammar marker at a paragraph, he has to request + * the grammar list from this class. If the requested paragraph is not edited, it returns + * the normal grammar list. But if the paragraph is the active one, a proxy list will be returned and + * all changes are set in this proxy list. If the cursor leaves the paragraph the proxy list + * will replace the old list. If the grammar checker has completed the paragraph ('setChecked') + * then a timer is setup which replaces the old list as well. + */ +class SwGrammarContact : public IGrammarContact, public SwClient +{ + Timer aTimer; + std::unique_ptr<SwGrammarMarkUp> mpProxyList; + bool mbFinished; + SwTextNode* getMyTextNode() { return static_cast<SwTextNode*>(GetRegisteredIn()); } + DECL_LINK( TimerRepaint, Timer *, void ); + +public: + SwGrammarContact(); + virtual ~SwGrammarContact() override { aTimer.Stop(); } + + // (pure) virtual functions of IGrammarContact + virtual void updateCursorPosition( const SwPosition& rNewPos ) override; + virtual SwGrammarMarkUp* getGrammarCheck( SwTextNode& rTextNode, bool bCreate ) override; + virtual void finishGrammarCheck( SwTextNode& rTextNode ) override; +protected: + // virtual function of SwClient + virtual void Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew) override; +}; + +} + +SwGrammarContact::SwGrammarContact() : mbFinished( false ) +{ + aTimer.SetTimeout( 2000 ); // Repaint of grammar check after 'setChecked' + aTimer.SetInvokeHandler( LINK(this, SwGrammarContact, TimerRepaint) ); + aTimer.SetDebugName( "sw::SwGrammarContact TimerRepaint" ); +} + +IMPL_LINK( SwGrammarContact, TimerRepaint, Timer *, pTimer, void ) +{ + if( pTimer ) + { + pTimer->Stop(); + if( GetRegisteredIn() ) + { //Replace the old wrong list by the proxy list and repaint all frames + getMyTextNode()->SetGrammarCheck( mpProxyList.release() ); + SwTextFrame::repaintTextFrames( *getMyTextNode() ); + } + } +} + +/* I'm always a client of the current paragraph */ +void SwGrammarContact::updateCursorPosition( const SwPosition& rNewPos ) +{ + SwTextNode* pTextNode = rNewPos.nNode.GetNode().GetTextNode(); + if( pTextNode != GetRegisteredIn() ) // paragraph has been changed + { + aTimer.Stop(); + if( GetRegisteredIn() ) // My last paragraph has been left + { + if( mpProxyList ) + { // replace old list by the proxy list and repaint + getMyTextNode()->SetGrammarCheck( mpProxyList.release() ); + SwTextFrame::repaintTextFrames( *getMyTextNode() ); + } + EndListeningAll(); + } + if( pTextNode ) + pTextNode->Add( this ); // welcome new paragraph + } +} + +/* deliver a grammar check list for the given text node */ +SwGrammarMarkUp* SwGrammarContact::getGrammarCheck( SwTextNode& rTextNode, bool bCreate ) +{ + SwGrammarMarkUp *pRet = nullptr; + if( GetRegisteredIn() == &rTextNode ) // hey, that's my current paragraph! + { // so you will get a proxy list... + if( bCreate ) + { + if( mbFinished ) + { + mpProxyList.reset(); + } + if( !mpProxyList ) + { + if( rTextNode.GetGrammarCheck() ) + mpProxyList.reset( static_cast<SwGrammarMarkUp*>(rTextNode.GetGrammarCheck()->Clone()) ); + else + { + mpProxyList.reset( new SwGrammarMarkUp() ); + mpProxyList->SetInvalid( 0, COMPLETE_STRING ); + } + } + mbFinished = false; + } + pRet = mpProxyList.get(); + } + else + { + pRet = rTextNode.GetGrammarCheck(); // do you have already a list? + if( bCreate && !pRet ) // do you want to create a list? + { + pRet = new SwGrammarMarkUp(); + pRet->SetInvalid( 0, COMPLETE_STRING ); + rTextNode.SetGrammarCheck( pRet ); + rTextNode.SetGrammarCheckDirty( true ); + } + } + return pRet; +} + +void SwGrammarContact::Modify( const SfxPoolItem* pOld, const SfxPoolItem * ) +{ + if( !pOld || pOld->Which() != RES_OBJECTDYING ) + return; + + const SwPtrMsgPoolItem *pDead = static_cast<const SwPtrMsgPoolItem *>(pOld); + if( pDead->pObject == GetRegisteredIn() ) + { // if my current paragraph dies, I throw the proxy list away + aTimer.Stop(); + EndListeningAll(); + mpProxyList.reset(); + } +} + +void SwGrammarContact::finishGrammarCheck( SwTextNode& rTextNode ) +{ + if( &rTextNode != GetRegisteredIn() ) // not my paragraph + SwTextFrame::repaintTextFrames( rTextNode ); // can be repainted directly + else + { + if( mpProxyList ) + { + mbFinished = true; + aTimer.Start(); // will replace old list and repaint with delay + } + else if( getMyTextNode()->GetGrammarCheck() ) + { // all grammar problems seems to be gone, no delay needed + getMyTextNode()->SetGrammarCheck( nullptr ); + SwTextFrame::repaintTextFrames( *getMyTextNode() ); + } + } +} + +IGrammarContact* createGrammarContact() +{ + return new SwGrammarContact(); +} + +void finishGrammarCheck( SwTextNode& rTextNode ) +{ + IGrammarContact* pGrammarContact = getGrammarContact( rTextNode ); + if( pGrammarContact ) + pGrammarContact->finishGrammarCheck( rTextNode ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/atrfld.cxx b/sw/source/core/txtnode/atrfld.cxx new file mode 100644 index 000000000..b8027bcd5 --- /dev/null +++ b/sw/source/core/txtnode/atrfld.cxx @@ -0,0 +1,732 @@ +/* -*- 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 <fmtfld.hxx> + +#include <libxml/xmlwriter.h> + +#include <fldbas.hxx> +#include <txtfld.hxx> +#include <txtannotationfld.hxx> +#include <docfld.hxx> +#include <docufld.hxx> +#include <doc.hxx> + +#include <pam.hxx> +#include <reffld.hxx> +#include <ddefld.hxx> +#include <usrfld.hxx> +#include <expfld.hxx> +#include <ndtxt.hxx> +#include <calc.hxx> +#include <hints.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <fieldhint.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + + +// constructor for default item in attribute-pool +SwFormatField::SwFormatField( sal_uInt16 nWhich ) + : SfxPoolItem( nWhich ) + , SfxBroadcaster() + , mpTextField( nullptr ) +{ +} + +SwFormatField::SwFormatField( const SwField &rField ) + : SfxPoolItem( RES_TXTATR_FIELD ) + , SfxBroadcaster() + , mpField( rField.CopyField() ) + , mpTextField( nullptr ) +{ + rField.GetTyp()->Add(this); + if ( mpField->GetTyp()->Which() == SwFieldIds::Input ) + { + // input field in-place editing + SetWhich( RES_TXTATR_INPUTFIELD ); + static_cast<SwInputField*>(mpField.get())->SetFormatField( *this ); + } + else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp) + { + // see SwWrtShell::StartInputFieldDlg + SwSetExpField *const pSetField(static_cast<SwSetExpField *>(mpField.get())); + if (pSetField->GetInputFlag() + // only for string fields for now - inline editing of number fields + // tends to produce error messages... + && (static_cast<SwSetExpFieldType*>(pSetField->GetTyp())->GetType() + & nsSwGetSetExpType::GSE_STRING)) + { + SetWhich( RES_TXTATR_INPUTFIELD ); + } + pSetField->SetFormatField(*this); + } + else if ( mpField->GetTyp()->Which() == SwFieldIds::Postit ) + { + // text annotation field + SetWhich( RES_TXTATR_ANNOTATION ); + } +} + +// #i24434# +// Since Items are used in ItemPool and in default constructed ItemSets with +// full pool range, all items need to be clonable. Thus, this one needed to be +// corrected +SwFormatField::SwFormatField( const SwFormatField& rAttr ) + : SfxPoolItem( rAttr ) + , SfxBroadcaster() + , mpTextField( nullptr ) +{ + if ( rAttr.mpField ) + { + rAttr.mpField->GetTyp()->Add(this); + mpField = rAttr.mpField->CopyField(); + if ( mpField->GetTyp()->Which() == SwFieldIds::Input ) + { + // input field in-place editing + SetWhich( RES_TXTATR_INPUTFIELD ); + SwInputField *pField = dynamic_cast<SwInputField*>(mpField.get()); + assert(pField); + if (pField) + pField->SetFormatField( *this ); + } + else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp) + { + SwSetExpField *const pSetField(static_cast<SwSetExpField *>(mpField.get())); + if (pSetField->GetInputFlag() + && (static_cast<SwSetExpFieldType*>(pSetField->GetTyp())->GetType() + & nsSwGetSetExpType::GSE_STRING)) + { + SetWhich( RES_TXTATR_INPUTFIELD ); + } + // see SwWrtShell::StartInputFieldDlg + pSetField->SetFormatField(*this); + } + else if ( mpField->GetTyp()->Which() == SwFieldIds::Postit ) + { + // text annotation field + SetWhich( RES_TXTATR_ANNOTATION ); + } + } +} + +SwFormatField::~SwFormatField() +{ + SwFieldType* pType = mpField ? mpField->GetTyp() : nullptr; + + if (pType && pType->Which() == SwFieldIds::Database) + pType = nullptr; // DB field types destroy themselves + + Broadcast( SwFormatFieldHint( this, SwFormatFieldHintWhich::REMOVED ) ); + mpField.reset(); + + // some fields need to delete their field type + if( pType && pType->HasOnlyOneListener() ) + { + bool bDel = false; + switch( pType->Which() ) + { + case SwFieldIds::User: + bDel = static_cast<SwUserFieldType*>(pType)->IsDeleted(); + break; + + case SwFieldIds::SetExp: + bDel = static_cast<SwSetExpFieldType*>(pType)->IsDeleted(); + break; + + case SwFieldIds::Dde: + bDel = static_cast<SwDDEFieldType*>(pType)->IsDeleted(); + break; + default: break; + } + + if( bDel ) + { + // unregister before deleting + pType->Remove( this ); + delete pType; + } + } +} + +void SwFormatField::RegisterToFieldType( SwFieldType& rType ) +{ + rType.Add(this); +} + +void SwFormatField::SetField(std::unique_ptr<SwField> _pField) +{ + mpField = std::move(_pField); + if ( mpField->GetTyp()->Which() == SwFieldIds::Input ) + { + static_cast<SwInputField* >(mpField.get())->SetFormatField( *this ); + } + else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp) + { + // see SwWrtShell::StartInputFieldDlg + static_cast<SwSetExpField *>(mpField.get())->SetFormatField(*this); + } + Broadcast( SwFormatFieldHint( this, SwFormatFieldHintWhich::CHANGED ) ); +} + +void SwFormatField::SetTextField( SwTextField& rTextField ) +{ + mpTextField = &rTextField; +} + +void SwFormatField::ClearTextField() +{ + mpTextField = nullptr; +} + +bool SwFormatField::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( mpField + && static_cast<const SwFormatField&>(rAttr).mpField + && mpField->GetTyp() == static_cast<const SwFormatField&>(rAttr).mpField->GetTyp() + && mpField->GetFormat() == static_cast<const SwFormatField&>(rAttr).mpField->GetFormat() ) + || + ( !mpField && !static_cast<const SwFormatField&>(rAttr).mpField ); +} + +SwFormatField* SwFormatField::Clone( SfxItemPool* ) const +{ + return new SwFormatField( *this ); +} + +void SwFormatField::InvalidateField() +{ + SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT, + &static_cast<SwModify&>(*this)); // cast to base class (void*) + CallSwClientNotify(sw::LegacyModifyHint{ &item, &item }); +} + +void SwFormatField::SwClientNotify( const SwModify& rModify, const SfxHint& rHint ) +{ + SwClient::SwClientNotify(rModify, rHint); + if (const auto pFieldHint = dynamic_cast<const SwFieldHint*>( &rHint )) + { + if( !mpTextField ) + return; + + // replace field content by text + SwPaM* pPaM = pFieldHint->m_pPaM; + SwDoc* pDoc = pPaM->GetDoc(); + const SwTextNode& rTextNode = mpTextField->GetTextNode(); + pPaM->GetPoint()->nNode = rTextNode; + pPaM->GetPoint()->nContent.Assign( const_cast<SwTextNode*>(&rTextNode), mpTextField->GetStart() ); + + OUString const aEntry(mpField->ExpandField(pDoc->IsClipBoard(), pFieldHint->m_pLayout)); + pPaM->SetMark(); + pPaM->Move( fnMoveForward ); + pDoc->getIDocumentContentOperations().DeleteRange( *pPaM ); + pDoc->getIDocumentContentOperations().InsertString( *pPaM, aEntry ); + } else if (const auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>( &rHint )) + { + if( !mpTextField ) + return; + UpdateTextNode(pLegacyHint->m_pOld, pLegacyHint->m_pNew); + } else if (const auto pFindForFieldHint = dynamic_cast<const sw::FindFormatForFieldHint*>( &rHint )) + { + if(pFindForFieldHint->m_rpFormat == nullptr && pFindForFieldHint->m_pField == GetField()) + pFindForFieldHint->m_rpFormat = this; + } else if (const auto pFindForPostItIdHint = dynamic_cast<const sw::FindFormatForPostItIdHint*>( &rHint )) + { + auto pPostItField = dynamic_cast<SwPostItField*>(mpField.get()); + if(pPostItField && pFindForPostItIdHint->m_rpFormat == nullptr && pFindForPostItIdHint->m_nPostItId == pPostItField->GetPostItId()) + pFindForPostItIdHint->m_rpFormat = this; + } else if (const auto pCollectPostItsHint = dynamic_cast<const sw::CollectPostItsHint*>( &rHint )) + { + if(GetTextField() && IsFieldInDoc() && (!pCollectPostItsHint->m_bHideRedlines || !sw::IsFieldDeletedInModel(pCollectPostItsHint->m_rIDRA, *GetTextField()))) + pCollectPostItsHint->m_rvFormatFields.push_back(this); + } else if (const auto pHasHiddenInfoHint = dynamic_cast<const sw::HasHiddenInformationNotesHint*>( &rHint )) + { + if(!pHasHiddenInfoHint->m_rbHasHiddenInformationNotes && GetTextField() && IsFieldInDoc()) + pHasHiddenInfoHint->m_rbHasHiddenInformationNotes = true; + } else if (const auto pGatherNodeIndexHint = dynamic_cast<const sw::GatherNodeIndexHint*>( &rHint )) + { + if(auto pTextField = GetTextField()) + pGatherNodeIndexHint->m_rvNodeIndex.push_back(pTextField->GetTextNode().GetIndex()); + } else if (const auto pGatherRefFieldsHint = dynamic_cast<const sw::GatherRefFieldsHint*>( &rHint )) + { + if(!GetTextField() || pGatherRefFieldsHint->m_nType != GetField()->GetSubType()) + return; + SwTextNode* pNd = GetTextField()->GetpTextNode(); + if(pNd && pNd->GetNodes().IsDocNodes()) + pGatherRefFieldsHint->m_rvRFields.push_back(static_cast<SwGetRefField*>(GetField())); + } else if (const auto pGatherFieldsHint = dynamic_cast<const sw::GatherFieldsHint*>( &rHint )) + { + if(pGatherFieldsHint->m_bCollectOnlyInDocNodes) + { + if(!GetTextField()) + return; + SwTextNode* pNd = GetTextField()->GetpTextNode(); + if(!pNd || !pNd->GetNodes().IsDocNodes()) + return; + } + pGatherFieldsHint->m_rvFields.push_back(this); + } +} + +void SwFormatField::UpdateTextNode(const SfxPoolItem* pOld, const SfxPoolItem* pNew) +{ + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached UNO object + m_wXTextField = nullptr; + // ??? why does this Modify method not already do this? + NotifyClients(pOld, pNew); + return; + } + + if( !mpTextField ) + return; + + // don't do anything, especially not expand! + if( pNew && pNew->Which() == RES_OBJECTDYING ) + return; + + SwTextNode* pTextNd = &mpTextField->GetTextNode(); + OSL_ENSURE( pTextNd, "Where is my Node?" ); + + bool bTriggerNode = false; + bool bExpand = false; + const SfxPoolItem* pNodeOld = nullptr; + const SfxPoolItem* pNodeNew = nullptr; + if(pNew) + { + switch(pNew->Which()) + { + case RES_REFMARKFLD_UPDATE: + // update GetRef fields + if( SwFieldIds::GetRef == mpField->GetTyp()->Which() ) + { + // #i81002# + static_cast<SwGetRefField*>(mpField.get())->UpdateField( mpTextField ); + } + break; + case RES_DOCPOS_UPDATE: + // handled in SwTextFrame::Modify() + bTriggerNode = true; + pNodeOld = pNew; + pNodeNew = this; + break; + case RES_ATTRSET_CHG: + case RES_FMT_CHG: + bTriggerNode = true; + pNodeOld = pOld; + pNodeNew = pNew; + break; + default: + break; + } + } + if(!bTriggerNode) + { + switch (mpField->GetTyp()->Which()) + { + case SwFieldIds::HiddenPara: + if( !pOld || pOld->Which() != RES_HIDDENPARA_PRINT ) { + bExpand =true; + break; + } + [[fallthrough]]; + case SwFieldIds::DbSetNumber: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + case SwFieldIds::DatabaseName: + bTriggerNode = true; + pNodeNew = pNew; + break; + case SwFieldIds::User: + { + SwUserFieldType* pType = static_cast<SwUserFieldType*>(mpField->GetTyp()); + if(!pType->IsValid()) + { + SwCalc aCalc( *pTextNd->GetDoc() ); + pType->GetValue( aCalc ); + } + bExpand = true; + } + break; + default: + bExpand = true; + break; + } + } + if(bTriggerNode) + { + pTextNd->ModifyNotification(pNodeOld, pNodeNew); + } + if(bExpand) + { + mpTextField->ExpandTextField( pOld == nullptr && pNew == nullptr ); + } +} + +bool SwFormatField::GetInfo( SfxPoolItem& rInfo ) const +{ + const SwTextNode* pTextNd; + return RES_AUTOFMT_DOCNODE != rInfo.Which() || + !mpTextField || nullptr == ( pTextNd = mpTextField->GetpTextNode() ) || + &pTextNd->GetNodes() != static_cast<SwAutoFormatGetDocNode&>(rInfo).pNodes; +} + +bool SwFormatField::IsFieldInDoc() const +{ + return mpTextField != nullptr + && mpTextField->IsFieldInDoc(); +} + +bool SwFormatField::IsProtect() const +{ + return mpTextField != nullptr + && mpTextField->GetpTextNode() != nullptr + && mpTextField->GetpTextNode()->IsProtect(); +} + +void SwFormatField::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatField")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mpTextField"), "%p", mpTextField); + + SfxPoolItem::dumpAsXml(pWriter); + mpField->dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +// class SwTextField //////////////////////////////////////////////////// + +SwTextField::SwTextField( + SwFormatField & rAttr, + sal_Int32 const nStartPos, + bool const bInClipboard) + : SwTextAttr( rAttr, nStartPos ) +// fdo#39694 the ExpandField here may not give the correct result in all cases, +// but is better than nothing + , m_aExpand( rAttr.GetField()->ExpandField(bInClipboard, nullptr) ) + , m_pTextNode( nullptr ) +{ + rAttr.SetTextField( *this ); + SetHasDummyChar(true); +} + +SwTextField::~SwTextField( ) +{ + SwFormatField & rFormatField( static_cast<SwFormatField &>(GetAttr()) ); + if ( this == rFormatField.GetTextField() ) + { + rFormatField.ClearTextField(); + } +} + +bool SwTextField::IsFieldInDoc() const +{ + return GetpTextNode() != nullptr + && GetpTextNode()->GetNodes().IsDocNodes(); +} + +void SwTextField::ExpandTextField(const bool bForceNotify) const +{ + OSL_ENSURE( m_pTextNode, "SwTextField: where is my TextNode?" ); + + const SwField* pField = GetFormatField().GetField(); + const OUString aNewExpand( pField->ExpandField(m_pTextNode->GetDoc()->IsClipBoard(), + // can't do any better than this here... + m_pTextNode->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()) ); + + const SwFieldIds nWhich = pField->GetTyp()->Which(); + const bool bSameExpandSimpleNotification + = SwFieldIds::Chapter != nWhich && SwFieldIds::PageNumber != nWhich + && SwFieldIds::RefPageGet != nWhich + // Page count fields to not use aExpand during formatting, + // therefore an invalidation of the text frame has to be triggered even if aNewExpand == aExpand: + && (SwFieldIds::DocStat != nWhich + || DS_PAGE != static_cast<const SwDocStatField*>(pField)->GetSubType()) + && (SwFieldIds::GetExp != nWhich + || static_cast<const SwGetExpField*>(pField)->IsInBodyText()); + + bool bHiddenParaChanged = false; + if (aNewExpand != m_aExpand || bSameExpandSimpleNotification) + bHiddenParaChanged = m_pTextNode->CalcHiddenParaField(); + + if (aNewExpand == m_aExpand) + { + if ( bSameExpandSimpleNotification ) + { + if( bHiddenParaChanged ) + { + m_pTextNode->ModifyNotification( nullptr, nullptr ); + } + if ( !bForceNotify ) + { + // done, if no further notification forced. + return; + } + } + } + else + m_aExpand = aNewExpand; + + const_cast<SwTextField*>(this)->NotifyContentChange( const_cast<SwFormatField&>(GetFormatField()) ); +} + +void SwTextField::CopyTextField( SwTextField *pDest ) const +{ + OSL_ENSURE( m_pTextNode, "SwTextField: where is my TextNode?" ); + OSL_ENSURE( pDest->m_pTextNode, "SwTextField: where is pDest's TextNode?" ); + + IDocumentFieldsAccess* pIDFA = &m_pTextNode->getIDocumentFieldsAccess(); + IDocumentFieldsAccess* pDestIDFA = &pDest->m_pTextNode->getIDocumentFieldsAccess(); + + SwFormatField& rDestFormatField = const_cast<SwFormatField&>(pDest->GetFormatField()); + const SwFieldIds nFieldWhich = rDestFormatField.GetField()->GetTyp()->Which(); + + if( pIDFA != pDestIDFA ) + { + // different documents, e.g. clipboard: + // register field type in target document + SwFieldType* pFieldType; + if( nFieldWhich != SwFieldIds::Database + && nFieldWhich != SwFieldIds::User + && nFieldWhich != SwFieldIds::SetExp + && nFieldWhich != SwFieldIds::Dde + && SwFieldIds::TableOfAuthorities != nFieldWhich ) + { + pFieldType = pDestIDFA->GetSysFieldType( nFieldWhich ); + } + else + { + pFieldType = pDestIDFA->InsertFieldType( *rDestFormatField.GetField()->GetTyp() ); + } + + // DDE fields need special treatment + if( SwFieldIds::Dde == nFieldWhich ) + { + if( rDestFormatField.GetTextField() ) + { + static_cast<SwDDEFieldType*>(rDestFormatField.GetField()->GetTyp())->DecRefCnt(); + } + static_cast<SwDDEFieldType*>(pFieldType)->IncRefCnt(); + } + + OSL_ENSURE( pFieldType, "unknown FieldType" ); + pFieldType->Add( &rDestFormatField ); // register at the field type + rDestFormatField.GetField()->ChgTyp( pFieldType ); + } + + // update expression fields + if( nFieldWhich == SwFieldIds::SetExp + || nFieldWhich == SwFieldIds::GetExp + || nFieldWhich == SwFieldIds::HiddenText ) + { + SwTextField* pField = const_cast<SwTextField*>(this); + pDestIDFA->UpdateExpFields( pField, true ); + } + // table fields: external display + else if( SwFieldIds::Table == nFieldWhich + && static_cast<SwTableField*>(rDestFormatField.GetField())->IsIntrnlName() ) + { + // convert internal (core) to external (UI) formula + const SwTableNode* pTableNd = m_pTextNode->FindTableNode(); + if( pTableNd ) // in a table? + static_cast<SwTableField*>(rDestFormatField.GetField())->PtrToBoxNm( &pTableNd->GetTable() ); + } +} + +void SwTextField::NotifyContentChange(SwFormatField& rFormatField) +{ + //if not in undo section notify the change + if (m_pTextNode && m_pTextNode->GetNodes().IsDocNodes()) + { + m_pTextNode->ModifyNotification(nullptr, &rFormatField); + } +} + +/*static*/ +void SwTextField::GetPamForTextField( + const SwTextField& rTextField, + std::shared_ptr< SwPaM >& rPamForTextField ) +{ + if (rTextField.GetpTextNode() == nullptr) + { + SAL_WARN("sw.core", "<SwTextField::GetPamForField> - missing <SwTextNode>"); + return; + } + + const SwTextNode& rTextNode = rTextField.GetTextNode(); + + rPamForTextField = std::make_shared<SwPaM>( rTextNode, + (rTextField.End() != nullptr) ? *(rTextField.End()) : ( rTextField.GetStart() + 1 ), + rTextNode, + rTextField.GetStart() ); + +} + +/*static*/ +void SwTextField::DeleteTextField( const SwTextField& rTextField ) +{ + if (rTextField.GetpTextNode() != nullptr) + { + std::shared_ptr< SwPaM > pPamForTextField; + GetPamForTextField(rTextField, pPamForTextField); + if (pPamForTextField != nullptr) + { + rTextField.GetTextNode().GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPamForTextField); + } + } +} + +// class SwTextInputField /////////////////////////////////////////////// + +// input field in-place editing +SwTextInputField::SwTextInputField( + SwFormatField & rAttr, + sal_Int32 const nStart, + sal_Int32 const nEnd, + bool const bInClipboard ) + + : SwTextAttr( rAttr, nStart ) + , SwTextAttrNesting( rAttr, nStart, nEnd ) + , SwTextField( rAttr, nStart, bInClipboard ) + , m_bLockNotifyContentChange( false ) +{ + SetHasDummyChar( false ); + SetHasContent( true ); +} + +SwTextInputField::~SwTextInputField() +{ +} + +bool SwTextInputField::LockNotifyContentChange() +{ + if (m_bLockNotifyContentChange) + { + return false; + } + m_bLockNotifyContentChange = true; + return true; +} + +void SwTextInputField::UnlockNotifyContentChange() +{ + m_bLockNotifyContentChange = false; +} + +void SwTextInputField::NotifyContentChange( SwFormatField& rFormatField ) +{ + if ( !m_bLockNotifyContentChange ) + { + LockNotifyContentChange(); + + SwTextField::NotifyContentChange( rFormatField ); + UpdateTextNodeContent( GetFieldContent() ); + + UnlockNotifyContentChange(); + } +} + +OUString SwTextInputField::GetFieldContent() const +{ + return GetFormatField().GetField()->ExpandField(false, nullptr/*ignored anyway*/); +} + +void SwTextInputField::UpdateFieldContent() +{ + if ( IsFieldInDoc() + && GetStart() != (*End()) ) + { + assert( (*End()) - GetStart() >= 2 && + "<SwTextInputField::UpdateFieldContent()> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" ); + // skip CH_TXT_ATR_INPUTFIELDSTART character + const sal_Int32 nIdx = GetStart() + 1; + // skip CH_TXT_ATR_INPUTFIELDEND character + const sal_Int32 nLen = static_cast<sal_Int32>(std::max<sal_Int32>( 0, ( (*End()) - 1 - nIdx ) )); + const OUString aNewFieldContent = GetTextNode().GetExpandText(nullptr, nIdx, nLen); + + const SwField* pField = GetFormatField().GetField(); + const SwInputField* pInputField = dynamic_cast<const SwInputField*>(pField); + if (pInputField) + const_cast<SwInputField*>(pInputField)->applyFieldContent( aNewFieldContent ); + + const SwSetExpField* pExpField = dynamic_cast<const SwSetExpField*>(pField); + if (pExpField) + { + assert(pExpField->GetInputFlag()); + const_cast<SwSetExpField*>(pExpField)->SetPar2(aNewFieldContent); + } + assert(pInputField || pExpField); + + // trigger update of fields for scenarios in which the Input Field's content is part of e.g. a table formula + GetTextNode().GetDoc()->getIDocumentFieldsAccess().GetUpdateFields().SetFieldsDirty(true); + } +} + +void SwTextInputField::UpdateTextNodeContent( const OUString& rNewContent ) +{ + assert(IsFieldInDoc() && + "<SwTextInputField::UpdateTextNodeContent(..)> - misusage as Input Field is not in document content."); + + assert( (*End()) - GetStart() >= 2 && + "<SwTextInputField::UpdateTextNodeContent(..)> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" ); + // skip CH_TXT_ATR_INPUTFIELDSTART character + const sal_Int32 nIdx = GetStart() + 1; + // skip CH_TXT_ATR_INPUTFIELDEND character + const sal_Int32 nDelLen = std::max<sal_Int32>( 0, ( (*End()) - 1 - nIdx ) ); + SwIndex aIdx( &GetTextNode(), nIdx ); + GetTextNode().ReplaceText( aIdx, nDelLen, rNewContent ); +} + +// class SwTextAnnotationField ////////////////////////////////////////// + +// text annotation field +SwTextAnnotationField::SwTextAnnotationField( + SwFormatField & rAttr, + sal_Int32 const nStart, + bool const bInClipboard ) + : SwTextAttr( rAttr, nStart ) + , SwTextField( rAttr, nStart, bInClipboard ) +{ +} + +SwTextAnnotationField::~SwTextAnnotationField() +{ +} + +::sw::mark::IMark* SwTextAnnotationField::GetAnnotationMark() const +{ + auto pPostItField = dynamic_cast<const SwPostItField*>(GetFormatField().GetField()); + assert(pPostItField); + + SwDoc* pDoc = static_cast<const SwPostItFieldType*>(pPostItField->GetTyp())->GetDoc(); + assert(pDoc != nullptr); + + IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess(); + IDocumentMarkAccess::const_iterator_t pMark = pMarksAccess->findAnnotationMark( pPostItField->GetName() ); + return pMark != pMarksAccess->getAnnotationMarksEnd() + ? *pMark + : nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/atrflyin.cxx b/sw/source/core/txtnode/atrflyin.cxx new file mode 100644 index 000000000..4e61e568e --- /dev/null +++ b/sw/source/core/txtnode/atrflyin.cxx @@ -0,0 +1,297 @@ +/* -*- 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 <hintids.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <flyfrm.hxx> +#include <ndtxt.hxx> +#include <frmfmt.hxx> +#include <fmtflcnt.hxx> +#include <txtflcnt.hxx> +#include <fmtanchr.hxx> +#include <txtfrm.hxx> +#include <flyfrms.hxx> +#include <objectformatter.hxx> +#include <calbck.hxx> +#include <dcontact.hxx> +#include <textboxhelper.hxx> + +SwFormatFlyCnt::SwFormatFlyCnt( SwFrameFormat *pFrameFormat ) + : SfxPoolItem( RES_TXTATR_FLYCNT ), + m_pTextAttr( nullptr ), + m_pFormat( pFrameFormat ) +{ +} + +bool SwFormatFlyCnt::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return( m_pTextAttr && static_cast<const SwFormatFlyCnt&>(rAttr).m_pTextAttr && + m_pTextAttr->GetStart() == static_cast<const SwFormatFlyCnt&>(rAttr).m_pTextAttr->GetStart() && + m_pFormat == static_cast<const SwFormatFlyCnt&>(rAttr).GetFrameFormat() ); +} + +SwFormatFlyCnt* SwFormatFlyCnt::Clone( SfxItemPool* ) const +{ + return new SwFormatFlyCnt( m_pFormat ); +} + +SwTextFlyCnt::SwTextFlyCnt( SwFormatFlyCnt& rAttr, sal_Int32 nStartPos ) + : SwTextAttr( rAttr, nStartPos ) +{ + rAttr.m_pTextAttr = this; + SetHasDummyChar(true); +} + +/** An overview of how a new SwTextFlyCnt is created: + * MakeTextAttr() is called e.g. by SwTextNode::CopyText(). + * The following steps are required to clone: + * 1) copying the pFormat with content, attributes etc. + * 2) setting the anchor + * 3) notification + * Because not all required information is available at all times, + * the steps are distributed variously: + * ad 1) MakeTextAttr() calls DocumentLayoutManager::CopyLayoutFormat() + * which creates the new SwFlyFrameFormat and copies the content of the + * fly frame. + * ad 2) SetAnchor() is called by SwTextNode::InsertHint() and sets the anchor + * position in the SwFlyFrameFormat to the SwPosition of the dummy + * CH_TXTATR_BREAKWORD. This cannot be done in MakeTextAttr() because it + * doesn't know the target text node. + * ad 3) GetFlyFrame_() is called during text formatting by SwTextFormatter + * and searches for the SwFlyFrame for the dummy char of the current + * SwTextFrame. If none is found, a new SwFlyInContentFrame is created. + * Important: pTextFrame->AppendFly() immediately triggers a reformat + * of pTextFrame. However, the recursion is blocked by the lock mechanism + * in SwTextFrame::Format(). + * The advantage of all this is that it's not necessary to explicitly iterate + * over all SwTextFrames that depend on the SwTextNode to create the + * SwFlyInContentFrame - this is done automatically already. + */ + +void SwTextFlyCnt::CopyFlyFormat( SwDoc* pDoc ) +{ + SwFrameFormat* pFormat = GetFlyCnt().GetFrameFormat(); + assert(pFormat); + // The FlyFrameFormat must be copied - CopyLayoutFormat + // (DocumentLayoutManager.cxx) creates the FlyFrameFormat and copies the + // content. + + // disable undo while copying attribute + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + if ((RndStdIds::FLY_AT_PAGE != aAnchor.GetAnchorId()) && + (pDoc != pFormat->GetDoc())) // different documents? + { + // JP 03.06.96: ensure that the copied anchor points to valid content! + // setting it to the correct position is done later. + SwNodeIndex aIdx( pDoc->GetNodes().GetEndOfExtras(), +2 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &aIdx ); + + SwPosition pos = *aAnchor.GetContentAnchor(); + pos.nNode = aIdx; + if (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId()) + { + pos.nContent.Assign( pCNd, 0 ); + } + else + { + pos.nContent.Assign( nullptr, 0 ); + assert(false); + } + aAnchor.SetAnchor( &pos ); + } + + SwFrameFormat* pNew = pDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *pFormat, aAnchor, false, false ); + const_cast<SwFormatFlyCnt&>(GetFlyCnt()).SetFlyFormat( pNew ); +} + +/** SetAnchor() is called by SwTextNode::InsertHint() and sets the anchor + * position in the SwFlyFrameFormat to the SwPosition of the dummy + * CH_TXTATR_BREAKWORD. This cannot be done in MakeTextAttr() because it + * doesn't know the target text node. + */ +void SwTextFlyCnt::SetAnchor( const SwTextNode *pNode ) +{ + // for Undo, the new anchor must be known already! + + SwDoc* pDoc = const_cast<SwDoc*>(pNode->GetDoc()); + + SwIndex aIdx( const_cast<SwTextNode*>(pNode), GetStart() ); + SwPosition aPos( *pNode->StartOfSectionNode(), aIdx ); + SwFrameFormat* pFormat = GetFlyCnt().GetFrameFormat(); + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + SwNode *const pOldNode(aAnchor.GetContentAnchor() + ? &aAnchor.GetContentAnchor()->nNode.GetNode() + : nullptr); + + if (!pOldNode || !pOldNode->GetNodes().IsDocNodes() || + pOldNode != static_cast<SwNode const *>(pNode)) + { + aPos.nNode = *pNode; + } + else + { + aPos.nNode = *pOldNode; + } + + aAnchor.SetType( RndStdIds::FLY_AS_CHAR ); // default! + aAnchor.SetAnchor( &aPos ); + + // in case of anchor change, delete all FlyFrames + // JP 25.04.95: if the Frames can be moved within SplitNode, they don't + // need to be deleted + if( ( !pNode->GetpSwpHints() || !pNode->GetpSwpHints()->IsInSplitNode() ) + && RES_DRAWFRMFMT != pFormat->Which() ) + pFormat->DelFrames(); + + // copy into a different document? + if( pDoc != pFormat->GetDoc() ) + { + // disable undo while copying attribute + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + SwFrameFormat* pNew = pDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *pFormat, aAnchor, false, false ); + + ::sw::UndoGuard const undoGuardFormat( + pFormat->GetDoc()->GetIDocumentUndoRedo()); + pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + const_cast<SwFormatFlyCnt&>(GetFlyCnt()).SetFlyFormat( pNew ); + } + else if( pNode->GetpSwpHints() && + pNode->GetpSwpHints()->IsInSplitNode() && + RES_DRAWFRMFMT != pFormat->Which() ) + { + pFormat->LockModify(); + pFormat->SetFormatAttr( aAnchor ); // only set the anchor + // tdf#91228 must notify the anchor nodes despite LockModify + assert(pOldNode); + pOldNode->RemoveAnchoredFly(pFormat); + aPos.nNode.GetNode().AddAnchoredFly(pFormat); + pFormat->UnlockModify(); + } + else + { + assert(!pFormat->IsModifyLocked()); // need to notify anchor node + if (RES_DRAWFRMFMT == pFormat->Which()) + { + if (SdrObject const*const pObj = pFormat->FindSdrObject()) + { // tdf#123259 disconnect with *old* anchor position + static_cast<SwDrawContact*>(::GetUserCall(pObj))->DisconnectFromLayout(false); + } + } + pFormat->SetFormatAttr( aAnchor ); // only set the anchor + + // If the draw format has a TextBox, then set its anchor as well. + if (SwFrameFormat* pTextBox + = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT)) + { + SwFormatAnchor aTextBoxAnchor(pTextBox->GetAnchor()); + aTextBoxAnchor.SetAnchor(aAnchor.GetContentAnchor()); + + // SwFlyAtContentFrame::Modify() assumes the anchor has a matching layout frame, which + // may not be the case when we're in the process of a node split, so block + // notifications. + bool bIsInSplitNode = pNode->GetpSwpHints() && pNode->GetpSwpHints()->IsInSplitNode(); + if (bIsInSplitNode) + { + pTextBox->LockModify(); + } + + pTextBox->SetFormatAttr(aTextBoxAnchor); + + if (bIsInSplitNode) + { + pOldNode->RemoveAnchoredFly(pTextBox); + aPos.nNode.GetNode().AddAnchoredFly(pTextBox); + pTextBox->UnlockModify(); + } + } + } + + // The node may have several SwTextFrames - for every SwTextFrame a + // SwFlyInContentFrame is created. +} + + +/** GetFlyFrame_() is called during text formatting by SwTextFormatter + * and searches for the SwFlyFrame for the dummy char of the current + * SwTextFrame. If none is found, a new SwFlyInContentFrame is created. + */ +SwFlyInContentFrame *SwTextFlyCnt::GetFlyFrame_( const SwFrame *pCurrFrame ) +{ + SwFrameFormat* pFrameFormat = GetFlyCnt().GetFrameFormat(); + if( RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + OSL_ENSURE( false, "SwTextFlyCnt::GetFlyFrame_: DrawInCnt-under construction!" ); + return nullptr; + } + + SwIterator<SwFlyFrame,SwFormat> aIter( *GetFlyCnt().m_pFormat ); + assert(pCurrFrame->IsTextFrame()); + SwFrame* pFrame = aIter.First(); + if ( pFrame ) + { + SwTextFrame *pFirst = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCurrFrame)); + while ( pFirst->IsFollow() ) + pFirst = pFirst->FindMaster(); + do + { + SwTextFrame *pTmp = pFirst; + do + { if( static_cast<SwFlyFrame*>(pFrame)->GetAnchorFrame() == static_cast<SwFrame*>(pTmp) ) + { + if ( pTmp != pCurrFrame ) + { + pTmp->RemoveFly( static_cast<SwFlyFrame*>(pFrame) ); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCurrFrame))->AppendFly( static_cast<SwFlyFrame*>(pFrame) ); + } + return static_cast<SwFlyInContentFrame*>(pFrame); + } + pTmp = pTmp->GetFollow(); + } while ( pTmp ); + + pFrame = aIter.Next(); + + } while( pFrame ); + } + + // We did not find a matching FlyFrame, so create a new one. + // AppendFly() triggers a reformat of pCurrentFrame. However, the + // recursion is blocked by the lock mechanism in SwTextFrame::Format(). + SwFrame* pCurrentFrame = const_cast<SwFrame*>(pCurrFrame); + SwFlyInContentFrame *pFly = new SwFlyInContentFrame(static_cast<SwFlyFrameFormat*>(pFrameFormat), pCurrentFrame, pCurrentFrame); + pCurrentFrame->AppendFly(pFly); + pFly->RegistFlys(); + + // We must ensure that the content of the FlyInCnt is fully formatted + // right after construction. + // #i26945# - Use new object formatter to format Writer + // fly frame and its content. + SwObjectFormatter::FormatObj( *pFly, const_cast<SwFrame*>(pCurrFrame), + pCurrFrame->FindPageFrame() ); + + return pFly; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/atrftn.cxx b/sw/source/core/txtnode/atrftn.cxx new file mode 100644 index 000000000..fd8f65185 --- /dev/null +++ b/sw/source/core/txtnode/atrftn.cxx @@ -0,0 +1,576 @@ +/* -*- 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 <fmtftn.hxx> + +#include <doc.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <cntfrm.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtftn.hxx> +#include <ftnidx.hxx> +#include <ftninfo.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <ftnfrm.hxx> +#include <ndindex.hxx> +#include <fmtftntx.hxx> +#include <section.hxx> +#include <calbck.hxx> +#include <hints.hxx> +#include <pam.hxx> +#include <rtl/ustrbuf.hxx> +#include <vcl/svapp.hxx> +#include <unotextrange.hxx> + +namespace { + /// Get a sorted list of the used footnote reference numbers. + /// @param[in] rDoc The active document. + /// @param[in] pExclude A footnote whose reference number should be excluded from the set. + /// @param[out] rUsedRef The set of used reference numbers. + /// @param[out] rInvalid A returned list of all items that had an invalid reference number. + void lcl_FillUsedFootnoteRefNumbers(SwDoc &rDoc, + SwTextFootnote const *pExclude, + std::set<sal_uInt16> &rUsedRef, + std::vector<SwTextFootnote*> &rInvalid) + { + SwFootnoteIdxs& ftnIdxs = rDoc.GetFootnoteIdxs(); + + rInvalid.clear(); + + for( size_t n = 0; n < ftnIdxs.size(); ++n ) + { + SwTextFootnote* pTextFootnote = ftnIdxs[ n ]; + if ( pTextFootnote != pExclude ) + { + if ( USHRT_MAX == pTextFootnote->GetSeqRefNo() ) + { + rInvalid.push_back(pTextFootnote); + } + else + { + rUsedRef.insert( pTextFootnote->GetSeqRefNo() ); + } + } + } + } + + /// Check whether a requested reference number is available. + /// @param[in] rUsedNums Set of used reference numbers. + /// @param[in] requested The requested reference number. + /// @returns true if the number is available, false if not. + bool lcl_IsRefNumAvailable(std::set<sal_uInt16> const &rUsedNums, + sal_uInt16 requested) + { + if ( USHRT_MAX == requested ) + return false; // Invalid sequence number. + if ( rUsedNums.count(requested) ) + return false; // Number already used. + return true; + } + + /// Get the first few unused sequential reference numbers. + /// @param[out] rLowestUnusedNums The lowest unused sequential reference numbers. + /// @param[in] rUsedNums The set of used sequential reference numbers. + /// @param[in] numRequired The number of reference number required. + void lcl_FillUnusedSeqRefNums(std::vector<sal_uInt16> &rLowestUnusedNums, + const std::set<sal_uInt16> &rUsedNums, + size_t numRequired) + { + if (!numRequired) + return; + + rLowestUnusedNums.reserve(numRequired); + sal_uInt16 newNum = 0; + //Start by using numbers from gaps in rUsedNums + for( const auto& rNum : rUsedNums ) + { + while ( newNum < rNum ) + { + rLowestUnusedNums.push_back( newNum++ ); + if ( --numRequired == 0) + return; + } + newNum++; + } + //Filled in all gaps. Fill the rest of the list with new numbers. + do + { + rLowestUnusedNums.push_back( newNum++ ); + } + while ( --numRequired > 0 ); + } + +} + +SwFormatFootnote::SwFormatFootnote( bool bEndNote ) + : SfxPoolItem( RES_TXTATR_FTN ) + , SwModify() + , m_pTextAttr(nullptr) + , m_nNumber(0) + , m_nNumberRLHidden(0) + , m_bEndNote(bEndNote) +{ +} + +bool SwFormatFootnote::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return m_nNumber == static_cast<const SwFormatFootnote&>(rAttr).m_nNumber && + //FIXME? + m_aNumber == static_cast<const SwFormatFootnote&>(rAttr).m_aNumber && + m_bEndNote == static_cast<const SwFormatFootnote&>(rAttr).m_bEndNote; +} + +SwFormatFootnote* SwFormatFootnote::Clone( SfxItemPool* ) const +{ + SwFormatFootnote* pNew = new SwFormatFootnote; + pNew->m_aNumber = m_aNumber; + pNew->m_nNumber = m_nNumber; + pNew->m_nNumberRLHidden = m_nNumberRLHidden; + pNew->m_bEndNote = m_bEndNote; + return pNew; +} + +void SwFormatFootnote::Modify(SfxPoolItem const* pOld, SfxPoolItem const* pNew) +{ + NotifyClients(pOld, pNew); + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached UNO object + SetXFootnote(css::uno::Reference<css::text::XFootnote>(nullptr)); + } +} + +void SwFormatFootnote::InvalidateFootnote() +{ + SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT, + &static_cast<SwModify&>(*this)); // cast to base class (void*) + NotifyClients(&item, &item); +} + +void SwFormatFootnote::SetEndNote( bool b ) +{ + if ( b != m_bEndNote ) + { + if ( GetTextFootnote() ) + { + GetTextFootnote()->DelFrames(nullptr); + } + m_bEndNote = b; + } +} + +SwFormatFootnote::~SwFormatFootnote() +{ +} + +OUString SwFormatFootnote::GetFootnoteText(SwRootFrame const& rLayout) const +{ + OUStringBuffer buf; + if( m_pTextAttr->GetStartNode() ) + { + SwNodeIndex aIdx( *m_pTextAttr->GetStartNode(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetTextNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + + if( pCNd->IsTextNode() ) { + buf.append(static_cast<SwTextNode*>(pCNd)->GetExpandText(&rLayout)); + + ++aIdx; + while ( !aIdx.GetNode().IsEndNode() ) { + if ( aIdx.GetNode().IsTextNode() ) + { + buf.append(" "); + buf.append(aIdx.GetNode().GetTextNode()->GetExpandText(&rLayout)); + } + ++aIdx; + } + } + } + return buf.makeStringAndClear(); +} + +/// return the view string of the foot/endnote +OUString SwFormatFootnote::GetViewNumStr(const SwDoc& rDoc, + SwRootFrame const*const pLayout, bool bInclStrings) const +{ + OUString sRet( GetNumStr() ); + if( sRet.isEmpty() ) + { + // in this case the number is needed, get it via SwDoc's FootnoteInfo + bool bMakeNum = true; + const SwSectionNode* pSectNd = m_pTextAttr + ? SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *m_pTextAttr ) + : nullptr; + sal_uInt16 const nNumber(pLayout && pLayout->IsHideRedlines() + ? GetNumberRLHidden() + : GetNumber()); + + if( pSectNd ) + { + const SwFormatFootnoteEndAtTextEnd& rFootnoteEnd = static_cast<const SwFormatFootnoteEndAtTextEnd&>( + pSectNd->GetSection().GetFormat()->GetFormatAttr( + IsEndNote() ? + static_cast<sal_uInt16>(RES_END_AT_TXTEND) : + static_cast<sal_uInt16>(RES_FTN_AT_TXTEND) ) ); + + if( FTNEND_ATTXTEND_OWNNUMANDFMT == rFootnoteEnd.GetValue() ) + { + bMakeNum = false; + sRet = rFootnoteEnd.GetSwNumType().GetNumStr( nNumber ); + if( bInclStrings ) + { + sRet = rFootnoteEnd.GetPrefix() + sRet + rFootnoteEnd.GetSuffix(); + } + } + } + + if( bMakeNum ) + { + const SwEndNoteInfo* pInfo; + if( IsEndNote() ) + pInfo = &rDoc.GetEndNoteInfo(); + else + pInfo = &rDoc.GetFootnoteInfo(); + sRet = pInfo->m_aFormat.GetNumStr( nNumber ); + if( bInclStrings ) + { + sRet = pInfo->GetPrefix() + sRet + pInfo->GetSuffix(); + } + } + } + return sRet; +} + +uno::Reference<text::XTextRange> SwFormatFootnote::getAnchor(SwDoc& rDoc) const +{ + SolarMutexGuard aGuard; + if (!m_pTextAttr) + return uno::Reference<text::XTextRange>(); + SwPaM aPam(m_pTextAttr->GetTextNode(), m_pTextAttr->GetStart()); + aPam.SetMark(); + ++aPam.GetMark()->nContent; + const uno::Reference<text::XTextRange> xRet = + SwXTextRange::CreateXTextRange(rDoc, *aPam.Start(), aPam.End()); + return xRet; +} + +SwTextFootnote::SwTextFootnote( SwFormatFootnote& rAttr, sal_Int32 nStartPos ) + : SwTextAttr( rAttr, nStartPos ) + , m_pTextNode( nullptr ) + , m_nSeqNo( USHRT_MAX ) +{ + rAttr.m_pTextAttr = this; + SetHasDummyChar(true); +} + +SwTextFootnote::~SwTextFootnote() +{ + SetStartNode( nullptr ); +} + +void SwTextFootnote::SetStartNode( const SwNodeIndex *pNewNode, bool bDelNode ) +{ + if( pNewNode ) + { + if ( !m_pStartNode ) + { + m_pStartNode.reset(new SwNodeIndex(*pNewNode)); + } + else + { + *m_pStartNode = *pNewNode; + } + } + else if ( m_pStartNode ) + { + // need to do 2 things: + // 1) unregister footnotes at their pages + // 2) delete the footnote section in the Inserts of the nodes-array + SwDoc* pDoc; + if ( m_pTextNode ) + { + pDoc = m_pTextNode->GetDoc(); + } + else + { + //JP 27.01.97: the sw3-Reader creates a StartNode but the + // attribute isn't anchored in the TextNode yet. + // If it is deleted (e.g. Insert File with footnote + // inside fly frame), the content must also be deleted. + pDoc = m_pStartNode->GetNodes().GetDoc(); + } + + // If called from ~SwDoc(), must not delete the footnote nodes, + // and not necessary to delete the footnote frames. + if( !pDoc->IsInDtor() ) + { + if( bDelNode ) + { + // 2) delete the section for the footnote nodes + // it's possible that the Inserts have already been deleted (how???) + pDoc->getIDocumentContentOperations().DeleteSection( &m_pStartNode->GetNode() ); + } + else + // If the nodes are not deleted, their frames must be removed + // from the page (deleted), there is nothing else that deletes + // them (particularly not Undo) + DelFrames( nullptr ); + } + m_pStartNode.reset(); + + // remove the footnote from the SwDoc's array + for( size_t n = 0; n < pDoc->GetFootnoteIdxs().size(); ++n ) + if( this == pDoc->GetFootnoteIdxs()[n] ) + { + pDoc->GetFootnoteIdxs().erase( pDoc->GetFootnoteIdxs().begin() + n ); + // if necessary, update following footnotes + if( !pDoc->IsInDtor() && n < pDoc->GetFootnoteIdxs().size() ) + { + SwNodeIndex aTmp( pDoc->GetFootnoteIdxs()[n]->GetTextNode() ); + pDoc->GetFootnoteIdxs().UpdateFootnote( aTmp ); + } + break; + } + } +} + +void SwTextFootnote::SetNumber(const sal_uInt16 nNewNum, + sal_uInt16 const nNumberRLHidden, const OUString &sNumStr) +{ + SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(GetFootnote()); + + rFootnote.m_aNumber = sNumStr; + if ( sNumStr.isEmpty() ) + { + rFootnote.m_nNumber = nNewNum; + rFootnote.m_nNumberRLHidden = nNumberRLHidden; + } + InvalidateNumberInLayout(); +} + +void SwTextFootnote::InvalidateNumberInLayout() +{ + assert(m_pTextNode); + SwFormatFootnote const& rFootnote(GetFootnote()); + SwNodes &rNodes = m_pTextNode->GetDoc()->GetNodes(); + m_pTextNode->ModifyNotification( nullptr, &rFootnote ); + if ( m_pStartNode ) + { + // must iterate over all TextNodes because of footnotes on other pages + sal_uLong nSttIdx = m_pStartNode->GetIndex() + 1; + sal_uLong nEndIdx = m_pStartNode->GetNode().EndOfSectionIndex(); + for( ; nSttIdx < nEndIdx; ++nSttIdx ) + { + SwNode* pNd; + if( ( pNd = rNodes[ nSttIdx ] )->IsTextNode() ) + static_cast<SwTextNode*>(pNd)->ModifyNotification( nullptr, &rFootnote ); + } + } +} + +void SwTextFootnote::CopyFootnote( + SwTextFootnote & rDest, + SwTextNode & rDestNode ) const +{ + if (m_pStartNode && !rDest.GetStartNode()) + { + // dest missing node section? create it here! + // (happens in SwTextNode::CopyText if pDest == this) + rDest.MakeNewTextSection( rDestNode.GetNodes() ); + } + if (m_pStartNode && rDest.GetStartNode()) + { + // footnotes not necessarily in same document! + SwDoc *const pDstDoc = rDestNode.GetDoc(); + SwNodes &rDstNodes = pDstDoc->GetNodes(); + + // copy only the content of the section + SwNodeRange aRg( *m_pStartNode, 1, + *m_pStartNode->GetNode().EndOfSectionNode() ); + + // insert at the end of rDest, i.e., the nodes are appended. + // nDestLen contains number of ContentNodes in rDest _before_ copy. + SwNodeIndex aStart( *(rDest.GetStartNode()) ); + SwNodeIndex aEnd( *aStart.GetNode().EndOfSectionNode() ); + sal_uLong nDestLen = aEnd.GetIndex() - aStart.GetIndex() - 1; + + m_pTextNode->GetDoc()->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aEnd); + + // in case the destination section was not empty, delete the old nodes + // before: Src: SxxxE, Dst: SnE + // now: Src: SxxxE, Dst: SnxxxE + // after: Src: SxxxE, Dst: SxxxE + ++aStart; + rDstNodes.Delete( aStart, nDestLen ); + } + + // also copy user defined number string + if( !GetFootnote().m_aNumber.isEmpty() ) + { + const_cast<SwFormatFootnote &>(rDest.GetFootnote()).m_aNumber = GetFootnote().m_aNumber; + } +} + +/// create a new nodes-array section for the footnote +void SwTextFootnote::MakeNewTextSection( SwNodes& rNodes ) +{ + if ( m_pStartNode ) + return; + + // set the footnote style on the SwTextNode + SwTextFormatColl *pFormatColl; + const SwEndNoteInfo* pInfo; + sal_uInt16 nPoolId; + + if( GetFootnote().IsEndNote() ) + { + pInfo = &rNodes.GetDoc()->GetEndNoteInfo(); + nPoolId = RES_POOLCOLL_ENDNOTE; + } + else + { + pInfo = &rNodes.GetDoc()->GetFootnoteInfo(); + nPoolId = RES_POOLCOLL_FOOTNOTE; + } + + if( nullptr == (pFormatColl = pInfo->GetFootnoteTextColl() ) ) + pFormatColl = rNodes.GetDoc()->getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId ); + + SwStartNode* pSttNd = rNodes.MakeTextSection( SwNodeIndex( rNodes.GetEndOfInserts() ), + SwFootnoteStartNode, pFormatColl ); + m_pStartNode.reset(new SwNodeIndex(*pSttNd)); +} + +void SwTextFootnote::DelFrames(SwRootFrame const*const pRoot) +{ + // delete the FootnoteFrames from the pages + OSL_ENSURE( m_pTextNode, "SwTextFootnote: where is my TextNode?" ); + if ( !m_pTextNode ) + return; + + bool bFrameFnd = false; + { + SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*m_pTextNode); + for( SwContentFrame* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + if( pRoot != pFnd->getRootFrame() && pRoot ) + continue; + SwPageFrame* pPage = pFnd->FindPageFrame(); + if( pPage ) + { + // note: we have found the correct frame only if the footnote + // was actually removed; in case this is called from + // SwTextFrame::DestroyImpl(), then that frame isn't connected + // to SwPageFrame any more, and RemoveFootnote on any follow + // must not prevent the fall-back to the !bFrameFnd code. + bFrameFnd = pPage->RemoveFootnote(pFnd, this); + } + } + } + //JP 13.05.97: if the layout is deleted before the footnotes are deleted, + // try to delete the footnote's frames by another way + if ( !bFrameFnd && m_pStartNode ) + { + SwNodeIndex aIdx( *m_pStartNode ); + SwContentNode* pCNd = m_pTextNode->GetNodes().GoNext( &aIdx ); + if( pCNd ) + { + SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pCNd); + for( SwContentFrame* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + if( pRoot != pFnd->getRootFrame() && pRoot ) + continue; + SwPageFrame* pPage = pFnd->FindPageFrame(); + + SwFrame *pFrame = pFnd->GetUpper(); + while ( pFrame && !pFrame->IsFootnoteFrame() ) + pFrame = pFrame->GetUpper(); + + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pFrame); + while ( pFootnote && pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + OSL_ENSURE( pFootnote->GetAttr() == this, "Footnote mismatch error." ); + + while ( pFootnote ) + { + SwFootnoteFrame *pFoll = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + pFootnote = pFoll; + } + + // #i20556# During hiding of a section, the connection + // to the layout is already lost. pPage may be 0: + if ( pPage ) + pPage->UpdateFootnoteNum(); + } + } + } +} + +/// Set the sequence number for the current footnote. +/// @returns The new sequence number or USHRT_MAX if invalid. +void SwTextFootnote::SetSeqRefNo() +{ + if( !m_pTextNode ) + return; + + SwDoc* pDoc = m_pTextNode->GetDoc(); + if( pDoc->IsInReading() ) + return; + + std::set<sal_uInt16> aUsedNums; + std::vector<SwTextFootnote*> badRefNums; + ::lcl_FillUsedFootnoteRefNumbers(*pDoc, this, aUsedNums, badRefNums); + if ( ::lcl_IsRefNumAvailable(aUsedNums, m_nSeqNo) ) + return; + std::vector<sal_uInt16> unused; + ::lcl_FillUnusedSeqRefNums(unused, aUsedNums, 1); + m_nSeqNo = unused[0]; +} + +/// Set a unique sequential reference number for every footnote in the document. +/// @param[in] rDoc The document to be processed. +void SwTextFootnote::SetUniqueSeqRefNo( SwDoc& rDoc ) +{ + std::set<sal_uInt16> aUsedNums; + std::vector<SwTextFootnote*> badRefNums; + ::lcl_FillUsedFootnoteRefNumbers(rDoc, nullptr, aUsedNums, badRefNums); + std::vector<sal_uInt16> aUnused; + ::lcl_FillUnusedSeqRefNums(aUnused, aUsedNums, badRefNums.size()); + + for (size_t i = 0; i < badRefNums.size(); ++i) + { + badRefNums[i]->m_nSeqNo = aUnused[i]; + } +} + +void SwTextFootnote::CheckCondColl() +{ +//FEATURE::CONDCOLL + if( GetStartNode() ) + static_cast<SwStartNode&>(GetStartNode()->GetNode()).CheckSectionCondColl(); +//FEATURE::CONDCOLL +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/atrref.cxx b/sw/source/core/txtnode/atrref.cxx new file mode 100644 index 000000000..88292c8a2 --- /dev/null +++ b/sw/source/core/txtnode/atrref.cxx @@ -0,0 +1,109 @@ +/* -*- 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 <fmtrfmrk.hxx> + +#include <hintids.hxx> +#include <hints.hxx> +#include <txtrfmrk.hxx> + +SwFormatRefMark::~SwFormatRefMark( ) +{ +} + +SwFormatRefMark::SwFormatRefMark( const OUString& rName ) + : SfxPoolItem(RES_TXTATR_REFMARK) + , SwModify() + , m_pTextAttr(nullptr) + , m_aRefName(rName) +{ +} + +SwFormatRefMark::SwFormatRefMark( const SwFormatRefMark& rAttr ) + : SfxPoolItem(RES_TXTATR_REFMARK) + , SwModify() + , BroadcasterMixin() + , m_pTextAttr(nullptr) + , m_aRefName(rAttr.m_aRefName) +{ +} + +bool SwFormatRefMark::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return m_aRefName == static_cast<const SwFormatRefMark&>(rAttr).m_aRefName; +} + +SwFormatRefMark* SwFormatRefMark::Clone( SfxItemPool* ) const +{ + return new SwFormatRefMark( *this ); +} + +void SwFormatRefMark::Modify(SfxPoolItem const* pOld, SfxPoolItem const* pNew) +{ + NotifyClients(pOld, pNew); + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached UNO object + SetXRefMark(css::uno::Reference<css::text::XTextContent>(nullptr)); + } +} + +void SwFormatRefMark::InvalidateRefMark() +{ + SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT, + &static_cast<SwModify&>(*this)); // cast to base class (void*) + NotifyClients(&item, &item); +} + +// attribute for content references in the text + +SwTextRefMark::SwTextRefMark( SwFormatRefMark& rAttr, + sal_Int32 const nStartPos, sal_Int32 const*const pEnd) + : SwTextAttr(rAttr, nStartPos) + , SwTextAttrEnd( rAttr, nStartPos, nStartPos ) + , m_pTextNode( nullptr ) + , m_pEnd( nullptr ) +{ + rAttr.m_pTextAttr = this; + if ( pEnd ) + { + m_nEnd = *pEnd; + m_pEnd = & m_nEnd; + } + else + { + SetHasDummyChar(true); + } + SetDontMoveAttr( true ); + SetOverlapAllowedAttr( true ); +} + +const sal_Int32* SwTextRefMark::GetEnd() const +{ + return m_pEnd; +} + +void SwTextRefMark::SetEnd(sal_Int32 n) +{ + *m_pEnd = n; + if (m_pHints) + m_pHints->EndPosChanged(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/atrtox.cxx b/sw/source/core/txtnode/atrtox.cxx new file mode 100644 index 000000000..de3a782d2 --- /dev/null +++ b/sw/source/core/txtnode/atrtox.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 <doc.hxx> +#include <txttxmrk.hxx> +#include <tox.hxx> + +SwTextTOXMark::SwTextTOXMark( SwTOXMark& rAttr, + sal_Int32 const nStartPos, sal_Int32 const*const pEnd) + : SwTextAttr( rAttr, nStartPos ) + , SwTextAttrEnd( rAttr, nStartPos, nStartPos ) + , m_pTextNode( nullptr ) + , m_pEnd( nullptr ) +{ + rAttr.m_pTextAttr = this; + if ( rAttr.GetAlternativeText().isEmpty() ) + { + m_nEnd = *pEnd; + m_pEnd = & m_nEnd; + } + else + { + SetHasDummyChar(true); + } + SetDontMoveAttr( true ); + SetOverlapAllowedAttr( true ); +} + +SwTextTOXMark::~SwTextTOXMark() +{ +} + +const sal_Int32* SwTextTOXMark::GetEnd() const +{ + return m_pEnd; +} + +void SwTextTOXMark::SetEnd(sal_Int32 n) +{ + *m_pEnd = n; + if (m_pHints) + m_pHints->EndPosChanged(); +} + +void SwTextTOXMark::CopyTOXMark( SwDoc* pDoc ) +{ + SwTOXMark& rTOX = const_cast<SwTOXMark&>(GetTOXMark()); + TOXTypes eType = rTOX.GetTOXType()->GetType(); + const sal_uInt16 nCount = pDoc->GetTOXTypeCount( eType ); + const SwTOXType* pType = nullptr; + const OUString rNm = rTOX.GetTOXType()->GetTypeName(); + + for(sal_uInt16 i=0; i < nCount; ++i) + { + const SwTOXType* pSrcType = pDoc->GetTOXType(eType, i); + if(pSrcType->GetTypeName() == rNm ) + { + pType = pSrcType; + break; + } + } + + // if the requested tox type does not exist, create it + if(!pType) + { + pDoc->InsertTOXType( SwTOXType( *pDoc, eType, rNm ) ); + pType = pDoc->GetTOXType(eType, 0); + } + + // register at target tox type + const_cast<SwTOXType*>(pType)->Add( &rTOX ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/chrfmt.cxx b/sw/source/core/txtnode/chrfmt.cxx new file mode 100644 index 000000000..b5fac1e16 --- /dev/null +++ b/sw/source/core/txtnode/chrfmt.cxx @@ -0,0 +1,42 @@ +/* -*- 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 <libxml/xmlwriter.h> + +#include <charfmt.hxx> +#include <docary.hxx> + + +void SwCharFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwCharFormat")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + GetAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +void SwCharFormats::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwCharFormats")); + for (size_t i = 0; i < size(); ++i) + GetFormat(i)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/fmtatr2.cxx b/sw/source/core/txtnode/fmtatr2.cxx new file mode 100644 index 000000000..e825a99a8 --- /dev/null +++ b/sw/source/core/txtnode/fmtatr2.cxx @@ -0,0 +1,843 @@ +/* -*- 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 <libxml/xmlwriter.h> +#include <hintids.hxx> +#include <poolfmt.hxx> +#include <unomid.h> + +#include <o3tl/any.hxx> +#include <svl/macitem.hxx> +#include <svl/stylepool.hxx> +#include <fmtautofmt.hxx> +#include <fchrfmt.hxx> +#include <fmtinfmt.hxx> +#include <txtatr.hxx> +#include <fmtruby.hxx> +#include <charfmt.hxx> +#include <hints.hxx> +#include <unoevent.hxx> +#include <com/sun/star/text/RubyAdjust.hpp> +#include <com/sun/star/text/RubyPosition.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/util/XCloneable.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <com/sun/star/uno/Any.h> +#include <SwStyleNameMapper.hxx> + +#include <fmtmeta.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <unometa.hxx> +#include <docsh.hxx> +#include <osl/diagnose.h> + +#include <algorithm> + +using namespace ::com::sun::star; + + +SfxPoolItem* SwFormatINetFormat::CreateDefault() { return new SwFormatINetFormat; } + +SwFormatCharFormat::SwFormatCharFormat( SwCharFormat *pFormat ) + : SfxPoolItem( RES_TXTATR_CHARFMT ), + SwClient(pFormat), + m_pTextAttribute( nullptr ) +{ +} + +SwFormatCharFormat::SwFormatCharFormat( const SwFormatCharFormat& rAttr ) + : SfxPoolItem( RES_TXTATR_CHARFMT ), + SwClient( rAttr.GetCharFormat() ), + m_pTextAttribute( nullptr ) +{ +} + +SwFormatCharFormat::~SwFormatCharFormat() {} + +bool SwFormatCharFormat::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return GetCharFormat() == static_cast<const SwFormatCharFormat&>(rAttr).GetCharFormat(); +} + +SwFormatCharFormat* SwFormatCharFormat::Clone( SfxItemPool* ) const +{ + return new SwFormatCharFormat( *this ); +} + +// forward to the TextAttribute +void SwFormatCharFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( m_pTextAttribute ) + m_pTextAttribute->ModifyNotification( pOld, pNew ); +} + +// forward to the TextAttribute +bool SwFormatCharFormat::GetInfo( SfxPoolItem& rInfo ) const +{ + return m_pTextAttribute && m_pTextAttribute->GetInfo( rInfo ); +} +bool SwFormatCharFormat::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + OUString sCharFormatName; + if(GetCharFormat()) + SwStyleNameMapper::FillProgName(GetCharFormat()->GetName(), sCharFormatName, SwGetPoolIdFromName::ChrFmt ); + rVal <<= sCharFormatName; + return true; +} +bool SwFormatCharFormat::PutValue( const uno::Any& , sal_uInt8 ) +{ + OSL_FAIL("format cannot be set with PutValue!"); + return false; +} + +SwFormatAutoFormat::SwFormatAutoFormat( sal_uInt16 nInitWhich ) + : SfxPoolItem( nInitWhich ) +{ +} + +bool SwFormatAutoFormat::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return mpHandle == static_cast<const SwFormatAutoFormat&>(rAttr).mpHandle; +} + +SwFormatAutoFormat* SwFormatAutoFormat::Clone( SfxItemPool* ) const +{ + return new SwFormatAutoFormat( *this ); +} + +bool SwFormatAutoFormat::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + rVal <<= StylePool::nameOf( mpHandle ); + return true; +} + +bool SwFormatAutoFormat::PutValue( const uno::Any& , sal_uInt8 ) +{ + //the format is not renameable via API + return false; +} + +void SwFormatAutoFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatAutoFormat")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + mpHandle->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +SwFormatINetFormat::SwFormatINetFormat() + : SfxPoolItem( RES_TXTATR_INETFMT ) + , msURL() + , msTargetFrame() + , msINetFormatName() + , msVisitedFormatName() + , msHyperlinkName() + , mpTextAttr( nullptr ) + , mnINetFormatId( 0 ) + , mnVisitedFormatId( 0 ) +{} + +SwFormatINetFormat::SwFormatINetFormat( const OUString& rURL, const OUString& rTarget ) + : SfxPoolItem( RES_TXTATR_INETFMT ) + , msURL( rURL ) + , msTargetFrame( rTarget ) + , msINetFormatName() + , msVisitedFormatName() + , msHyperlinkName() + , mpTextAttr( nullptr ) + , mnINetFormatId( RES_POOLCHR_INET_NORMAL ) + , mnVisitedFormatId( RES_POOLCHR_INET_VISIT ) +{ + SwStyleNameMapper::FillUIName( mnINetFormatId, msINetFormatName ); + SwStyleNameMapper::FillUIName( mnVisitedFormatId, msVisitedFormatName ); +} + +SwFormatINetFormat::SwFormatINetFormat( const SwFormatINetFormat& rAttr ) + : SfxPoolItem( RES_TXTATR_INETFMT ) + , sw::BroadcasterMixin() + , msURL( rAttr.GetValue() ) + , msTargetFrame( rAttr.msTargetFrame ) + , msINetFormatName( rAttr.msINetFormatName ) + , msVisitedFormatName( rAttr.msVisitedFormatName ) + , msHyperlinkName( rAttr.msHyperlinkName ) + , mpTextAttr( nullptr ) + , mnINetFormatId( rAttr.mnINetFormatId ) + , mnVisitedFormatId( rAttr.mnVisitedFormatId ) +{ + if ( rAttr.GetMacroTable() ) + mpMacroTable.reset( new SvxMacroTableDtor( *rAttr.GetMacroTable() ) ); +} + +SwFormatINetFormat::~SwFormatINetFormat() +{ +} + +bool SwFormatINetFormat::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + bool bRet = SfxPoolItem::operator==( rAttr ) + && msURL == static_cast<const SwFormatINetFormat&>(rAttr).msURL + && msHyperlinkName == static_cast<const SwFormatINetFormat&>(rAttr).msHyperlinkName + && msTargetFrame == static_cast<const SwFormatINetFormat&>(rAttr).msTargetFrame + && msINetFormatName == static_cast<const SwFormatINetFormat&>(rAttr).msINetFormatName + && msVisitedFormatName == static_cast<const SwFormatINetFormat&>(rAttr).msVisitedFormatName + && mnINetFormatId == static_cast<const SwFormatINetFormat&>(rAttr).mnINetFormatId + && mnVisitedFormatId == static_cast<const SwFormatINetFormat&>(rAttr).mnVisitedFormatId; + + if( !bRet ) + return false; + + const SvxMacroTableDtor* pOther = static_cast<const SwFormatINetFormat&>(rAttr).mpMacroTable.get(); + if( !mpMacroTable ) + return ( !pOther || pOther->empty() ); + if( !pOther ) + return mpMacroTable->empty(); + + const SvxMacroTableDtor& rOwn = *mpMacroTable; + const SvxMacroTableDtor& rOther = *pOther; + + return rOwn == rOther; +} + +SwFormatINetFormat* SwFormatINetFormat::Clone( SfxItemPool* ) const +{ + return new SwFormatINetFormat( *this ); +} + +void SwFormatINetFormat::SetMacroTable( const SvxMacroTableDtor* pNewTable ) +{ + if( pNewTable ) + { + if( mpMacroTable ) + *mpMacroTable = *pNewTable; + else + mpMacroTable.reset( new SvxMacroTableDtor( *pNewTable ) ); + } + else + { + mpMacroTable.reset(); + } +} + +void SwFormatINetFormat::SetMacro( SvMacroItemId nEvent, const SvxMacro& rMacro ) +{ + if( !mpMacroTable ) + mpMacroTable.reset( new SvxMacroTableDtor ); + + mpMacroTable->Insert( nEvent, rMacro ); +} + +const SvxMacro* SwFormatINetFormat::GetMacro( SvMacroItemId nEvent ) const +{ + const SvxMacro* pRet = nullptr; + if( mpMacroTable && mpMacroTable->IsKeyValid( nEvent ) ) + pRet = mpMacroTable->Get( nEvent ); + return pRet; +} + +bool SwFormatINetFormat::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_URL_URL: + rVal <<= msURL; + break; + case MID_URL_TARGET: + rVal <<= msTargetFrame; + break; + case MID_URL_HYPERLINKNAME: + rVal <<= msHyperlinkName; + break; + case MID_URL_VISITED_FMT: + { + OUString sVal = msVisitedFormatName; + if (sVal.isEmpty() && mnVisitedFormatId != 0) + SwStyleNameMapper::FillUIName(mnVisitedFormatId, sVal); + if (!sVal.isEmpty()) + SwStyleNameMapper::FillProgName(sVal, sVal, + SwGetPoolIdFromName::ChrFmt); + rVal <<= sVal; + } + break; + case MID_URL_UNVISITED_FMT: + { + OUString sVal = msINetFormatName; + if (sVal.isEmpty() && mnINetFormatId != 0) + SwStyleNameMapper::FillUIName(mnINetFormatId, sVal); + if (!sVal.isEmpty()) + SwStyleNameMapper::FillProgName(sVal, sVal, + SwGetPoolIdFromName::ChrFmt); + rVal <<= sVal; + } + break; + case MID_URL_HYPERLINKEVENTS: + { + // create (and return) event descriptor + SwHyperlinkEventDescriptor* pEvents = + new SwHyperlinkEventDescriptor(); + pEvents->copyMacrosFromINetFormat(*this); + uno::Reference<container::XNameReplace> xNameReplace(pEvents); + + // all others return a string; so we just set rVal here and exit + rVal <<= xNameReplace; + } + break; + default: + rVal <<= OUString(); + break; + } + return true; +} +bool SwFormatINetFormat::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = true; + nMemberId &= ~CONVERT_TWIPS; + + // all properties except HyperlinkEvents are of type string, hence + // we treat HyperlinkEvents specially + if (MID_URL_HYPERLINKEVENTS == nMemberId) + { + uno::Reference<container::XNameReplace> xReplace; + rVal >>= xReplace; + if (xReplace.is()) + { + // Create hyperlink event descriptor. Then copy events + // from argument into descriptor. Then copy events from + // the descriptor into the format. + rtl::Reference<SwHyperlinkEventDescriptor> pEvents = new SwHyperlinkEventDescriptor(); + pEvents->copyMacrosFromNameReplace(xReplace); + pEvents->copyMacrosIntoINetFormat(*this); + } + else + { + // wrong type! + bRet = false; + } + } + else + { + // all string properties: + if(rVal.getValueType() != ::cppu::UnoType<OUString>::get()) + return false; + + switch(nMemberId) + { + case MID_URL_URL: + rVal >>= msURL; + break; + case MID_URL_TARGET: + rVal >>= msTargetFrame; + break; + case MID_URL_HYPERLINKNAME: + rVal >>= msHyperlinkName; + break; + case MID_URL_VISITED_FMT: + { + OUString sVal; + rVal >>= sVal; + OUString aString; + SwStyleNameMapper::FillUIName( sVal, aString, SwGetPoolIdFromName::ChrFmt ); + msVisitedFormatName = aString; + mnVisitedFormatId = SwStyleNameMapper::GetPoolIdFromUIName( msVisitedFormatName, + SwGetPoolIdFromName::ChrFmt ); + } + break; + case MID_URL_UNVISITED_FMT: + { + OUString sVal; + rVal >>= sVal; + OUString aString; + SwStyleNameMapper::FillUIName( sVal, aString, SwGetPoolIdFromName::ChrFmt ); + msINetFormatName = aString; + mnINetFormatId = SwStyleNameMapper::GetPoolIdFromUIName( msINetFormatName, SwGetPoolIdFromName::ChrFmt ); + } + break; + default: + bRet = false; + } + } + return bRet; +} + +SwFormatRuby::SwFormatRuby( const OUString& rRubyText ) + : SfxPoolItem( RES_TXTATR_CJK_RUBY ), + m_sRubyText( rRubyText ), + m_pTextAttr( nullptr ), + m_nCharFormatId( 0 ), + m_nPosition( 0 ), + m_eAdjustment( css::text::RubyAdjust_LEFT ) +{ +} + +SwFormatRuby::SwFormatRuby( const SwFormatRuby& rAttr ) + : SfxPoolItem( RES_TXTATR_CJK_RUBY ), + m_sRubyText( rAttr.m_sRubyText ), + m_sCharFormatName( rAttr.m_sCharFormatName ), + m_pTextAttr( nullptr ), + m_nCharFormatId( rAttr.m_nCharFormatId), + m_nPosition( rAttr.m_nPosition ), + m_eAdjustment( rAttr.m_eAdjustment ) +{ +} + +SwFormatRuby::~SwFormatRuby() +{ +} + +SwFormatRuby& SwFormatRuby::operator=( const SwFormatRuby& rAttr ) +{ + if(this == &rAttr) + return *this; + + m_sRubyText = rAttr.m_sRubyText; + m_sCharFormatName = rAttr.m_sCharFormatName; + m_nCharFormatId = rAttr.m_nCharFormatId; + m_nPosition = rAttr.m_nPosition; + m_eAdjustment = rAttr.m_eAdjustment; + m_pTextAttr = nullptr; + return *this; +} + +bool SwFormatRuby::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return m_sRubyText == static_cast<const SwFormatRuby&>(rAttr).m_sRubyText && + m_sCharFormatName == static_cast<const SwFormatRuby&>(rAttr).m_sCharFormatName && + m_nCharFormatId == static_cast<const SwFormatRuby&>(rAttr).m_nCharFormatId && + m_nPosition == static_cast<const SwFormatRuby&>(rAttr).m_nPosition && + m_eAdjustment == static_cast<const SwFormatRuby&>(rAttr).m_eAdjustment; +} + +SwFormatRuby* SwFormatRuby::Clone( SfxItemPool* ) const +{ + return new SwFormatRuby( *this ); +} + +bool SwFormatRuby::QueryValue( uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + bool bRet = true; + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_RUBY_TEXT: rVal <<= m_sRubyText; break; + case MID_RUBY_ADJUST: rVal <<= static_cast<sal_Int16>(m_eAdjustment); break; + case MID_RUBY_CHARSTYLE: + { + OUString aString; + SwStyleNameMapper::FillProgName(m_sCharFormatName, aString, SwGetPoolIdFromName::ChrFmt ); + rVal <<= aString; + } + break; + case MID_RUBY_ABOVE: + { + rVal <<= static_cast<bool>(!m_nPosition); + } + break; + case MID_RUBY_POSITION: + { + rVal <<= m_nPosition; + } + break; + default: + bRet = false; + } + return bRet; +} +bool SwFormatRuby::PutValue( const uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + bool bRet = true; + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_RUBY_TEXT: + bRet = rVal >>= m_sRubyText; + break; + case MID_RUBY_ADJUST: + { + sal_Int16 nSet = 0; + rVal >>= nSet; + if(nSet >= sal_Int16(text::RubyAdjust_LEFT) && nSet <= sal_Int16(text::RubyAdjust_INDENT_BLOCK)) + m_eAdjustment = static_cast<text::RubyAdjust>(nSet); + else + bRet = false; + } + break; + case MID_RUBY_ABOVE: + { + const uno::Type& rType = cppu::UnoType<bool>::get(); + if(rVal.hasValue() && rVal.getValueType() == rType) + { + bool bAbove = *o3tl::doAccess<bool>(rVal); + m_nPosition = bAbove ? 0 : 1; + } + } + break; + case MID_RUBY_POSITION: + { + sal_Int16 nSet = 0; + rVal >>= nSet; + if(nSet >= sal_Int16(text::RubyPosition::ABOVE) && nSet <= sal_Int16(text::RubyPosition::INTER_CHARACTER)) + m_nPosition = nSet; + else + bRet = false; + } + break; + case MID_RUBY_CHARSTYLE: + { + OUString sTmp; + bRet = rVal >>= sTmp; + if(bRet) + m_sCharFormatName = SwStyleNameMapper::GetUIName(sTmp, SwGetPoolIdFromName::ChrFmt ); + } + break; + default: + bRet = false; + } + return bRet; +} + +SwFormatMeta * SwFormatMeta::CreatePoolDefault(const sal_uInt16 i_nWhich) +{ + return new SwFormatMeta(i_nWhich); +} + +SwFormatMeta::SwFormatMeta(const sal_uInt16 i_nWhich) + : SfxPoolItem( i_nWhich ) + , m_pMeta() + , m_pTextAttr( nullptr ) +{ + OSL_ENSURE((RES_TXTATR_META == i_nWhich) || (RES_TXTATR_METAFIELD == i_nWhich), + "ERROR: SwFormatMeta: invalid which id!"); +} + +SwFormatMeta::SwFormatMeta( std::shared_ptr< ::sw::Meta > const & i_pMeta, + const sal_uInt16 i_nWhich ) + : SfxPoolItem( i_nWhich ) + , m_pMeta( i_pMeta ) + , m_pTextAttr( nullptr ) +{ + OSL_ENSURE((RES_TXTATR_META == i_nWhich) || (RES_TXTATR_METAFIELD == i_nWhich), + "ERROR: SwFormatMeta: invalid which id!"); + OSL_ENSURE(m_pMeta, "SwFormatMeta: no Meta ?"); + // DO NOT call m_pMeta->SetFormatMeta(this) here; only from SetTextAttr! +} + +SwFormatMeta::~SwFormatMeta() +{ + if (m_pMeta && (m_pMeta->GetFormatMeta() == this)) + { + NotifyChangeTextNode(nullptr); + m_pMeta->SetFormatMeta(nullptr); + } +} + +bool SwFormatMeta::operator==( const SfxPoolItem & i_rOther ) const +{ + return SfxPoolItem::operator==( i_rOther ) + && m_pMeta == static_cast<SwFormatMeta const &>( i_rOther ).m_pMeta; +} + +SwFormatMeta* SwFormatMeta::Clone( SfxItemPool * /*pPool*/ ) const +{ + // if this is indeed a copy, then DoCopy must be called later! + return m_pMeta // #i105148# pool default may be cloned also! + ? new SwFormatMeta( m_pMeta, Which() ) : new SwFormatMeta( Which() ); +} + +void SwFormatMeta::SetTextAttr(SwTextMeta * const i_pTextAttr) +{ + OSL_ENSURE(!(m_pTextAttr && i_pTextAttr), + "SwFormatMeta::SetTextAttr: already has text attribute?"); + OSL_ENSURE( m_pTextAttr || i_pTextAttr , + "SwFormatMeta::SetTextAttr: no attribute to remove?"); + m_pTextAttr = i_pTextAttr; + OSL_ENSURE(m_pMeta, "inserted SwFormatMeta has no sw::Meta?"); + // the sw::Meta must be able to find the current text attribute! + if (m_pMeta) + { + if (i_pTextAttr) + { + m_pMeta->SetFormatMeta(this); + } + else if (m_pMeta->GetFormatMeta() == this) + { // text attribute gone => de-register from text node! + NotifyChangeTextNode(nullptr); + m_pMeta->SetFormatMeta(nullptr); + } + } +} + +void SwFormatMeta::NotifyChangeTextNode(SwTextNode *const pTextNode) +{ + // N.B.: do not reset m_pTextAttr here: see call in nodes.cxx, + // where the hint is not deleted! + OSL_ENSURE(m_pMeta, "SwFormatMeta::NotifyChangeTextNode: no Meta?"); + if (m_pMeta && (m_pMeta->GetFormatMeta() == this)) + { // do not call Modify, that would call SwXMeta::Modify! + m_pMeta->NotifyChangeTextNode(pTextNode); + } +} + +// this SwFormatMeta has been cloned and points at the same sw::Meta as the source +// this method copies the sw::Meta +void SwFormatMeta::DoCopy(::sw::MetaFieldManager & i_rTargetDocManager, + SwTextNode & i_rTargetTextNode) +{ + OSL_ENSURE(m_pMeta, "DoCopy called for SwFormatMeta with no sw::Meta?"); + if (m_pMeta) + { + const std::shared_ptr< ::sw::Meta> pOriginal( m_pMeta ); + if (RES_TXTATR_META == Which()) + { + m_pMeta = std::make_shared<::sw::Meta>(this); + } + else + { + ::sw::MetaField *const pMetaField( + static_cast< ::sw::MetaField* >(pOriginal.get())); + m_pMeta = i_rTargetDocManager.makeMetaField( this, + pMetaField->m_nNumberFormat, pMetaField->IsFixedLanguage() ); + } + // Meta must have a text node before calling RegisterAsCopyOf + m_pMeta->NotifyChangeTextNode(& i_rTargetTextNode); + // this cannot be done in Clone: a Clone is not necessarily a copy! + m_pMeta->RegisterAsCopyOf(*pOriginal); + } +} + +namespace sw { + +Meta::Meta(SwFormatMeta * const i_pFormat) + : ::sfx2::Metadatable() + , SwModify() + , m_pFormat(i_pFormat) + , m_pTextNode(nullptr) +{ +} + +Meta::~Meta() +{ +} + +SwTextMeta * Meta::GetTextAttr() const +{ + return m_pFormat ? m_pFormat->GetTextAttr() : nullptr; +} + + +void Meta::NotifyChangeTextNode(SwTextNode *const pTextNode) +{ + m_pTextNode = pTextNode; + if (m_pTextNode && (GetRegisteredIn() != m_pTextNode)) + { + m_pTextNode->Add(this); + } + else if (!m_pTextNode) + { + EndListeningAll(); + } + if (!pTextNode) // text node gone? invalidate UNO object! + { + GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing)); + } +} + +// SwClient +void Meta::Modify( const SfxPoolItem *pOld, const SfxPoolItem *pNew ) +{ + NotifyClients(pOld, pNew); + GetNotifier().Broadcast(SfxHint(SfxHintId::DataChanged)); + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXMeta(uno::Reference<rdf::XMetadatable>(nullptr)); + GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing)); + } +} + +// sfx2::Metadatable +::sfx2::IXmlIdRegistry& Meta::GetRegistry() +{ + SwTextNode * const pTextNode( GetTextNode() ); + // GetRegistry may only be called on a meta that is actually in the + // document, which means it has a pointer to its text node + OSL_ENSURE(pTextNode, "ERROR: GetRegistry: no text node?"); + if (!pTextNode) + throw uno::RuntimeException(); + return pTextNode->GetRegistry(); +} + +bool Meta::IsInClipboard() const +{ + const SwTextNode * const pTextNode( GetTextNode() ); +// no text node: in UNDO OSL_ENSURE(pTextNode, "IsInClipboard: no text node?"); + return pTextNode && pTextNode->IsInClipboard(); +} + +bool Meta::IsInUndo() const +{ + const SwTextNode * const pTextNode( GetTextNode() ); +// no text node: in UNDO OSL_ENSURE(pTextNode, "IsInUndo: no text node?"); + return pTextNode == nullptr || pTextNode->IsInUndo(); +} + +bool Meta::IsInContent() const +{ + const SwTextNode * const pTextNode( GetTextNode() ); + OSL_ENSURE(pTextNode, "IsInContent: no text node?"); + return pTextNode == nullptr || pTextNode->IsInContent(); +} + +css::uno::Reference< css::rdf::XMetadatable > Meta::MakeUnoObject() +{ + return SwXMeta::CreateXMeta(*this); +} + +MetaField::MetaField(SwFormatMeta * const i_pFormat, + const sal_uInt32 nNumberFormat, const bool bIsFixedLanguage) + : Meta(i_pFormat) + , m_nNumberFormat( nNumberFormat ) + , m_bIsFixedLanguage( bIsFixedLanguage ) +{ +} + +void MetaField::GetPrefixAndSuffix( + OUString *const o_pPrefix, OUString *const o_pSuffix) +{ + try + { + const uno::Reference<rdf::XMetadatable> xMetaField( MakeUnoObject() ); + OSL_ENSURE(dynamic_cast<SwXMetaField*>(xMetaField.get()), + "GetPrefixAndSuffix: no SwXMetaField?"); + if (xMetaField.is()) + { + SwTextNode * const pTextNode( GetTextNode() ); + SwDocShell const * const pShell(pTextNode->GetDoc()->GetDocShell()); + const uno::Reference<frame::XModel> xModel( + pShell ? pShell->GetModel() : nullptr, uno::UNO_SET_THROW); + getPrefixAndSuffix(xModel, xMetaField, o_pPrefix, o_pSuffix); + } + } + catch (const uno::Exception&) + { + OSL_FAIL("exception?"); + } +} + +sal_uInt32 MetaField::GetNumberFormat(OUString const & rContent) const +{ + //TODO: this probably lacks treatment for some special cases + sal_uInt32 nNumberFormat( m_nNumberFormat ); + SwTextNode * const pTextNode( GetTextNode() ); + if (pTextNode) + { + double number; + (void) pTextNode->GetDoc()->IsNumberFormat( rContent, nNumberFormat, number ); + } + return nNumberFormat; +} + +void MetaField::SetNumberFormat(sal_uInt32 nNumberFormat) +{ + // effectively, the member is only a default: + // GetNumberFormat checks if the text actually conforms + m_nNumberFormat = nNumberFormat; +} + +MetaFieldManager::MetaFieldManager() +{ +} + +std::shared_ptr<MetaField> +MetaFieldManager::makeMetaField(SwFormatMeta * const i_pFormat, + const sal_uInt32 nNumberFormat, const bool bIsFixedLanguage) +{ + const std::shared_ptr<MetaField> pMetaField( + new MetaField(i_pFormat, nNumberFormat, bIsFixedLanguage) ); + m_MetaFields.push_back(pMetaField); + return pMetaField; +} + +namespace { + +struct IsInUndo +{ + bool operator()(std::weak_ptr<MetaField> const & pMetaField) { + return pMetaField.lock()->IsInUndo(); + } +}; + +struct MakeUnoObject +{ + uno::Reference<text::XTextField> + operator()(std::weak_ptr<MetaField> const & pMetaField) { + return uno::Reference<text::XTextField>( + pMetaField.lock()->MakeUnoObject(), uno::UNO_QUERY); + } +}; + +} + +std::vector< uno::Reference<text::XTextField> > +MetaFieldManager::getMetaFields() +{ + // erase deleted fields + const MetaFieldList_t::iterator iter( + std::remove_if(m_MetaFields.begin(), m_MetaFields.end(), + [] (std::weak_ptr<MetaField> const& rField) { return rField.expired(); })); + m_MetaFields.erase(iter, m_MetaFields.end()); + // filter out fields in UNDO + MetaFieldList_t filtered(m_MetaFields.size()); + const MetaFieldList_t::iterator iter2( + std::remove_copy_if(m_MetaFields.begin(), m_MetaFields.end(), + filtered.begin(), IsInUndo())); + filtered.erase(iter2, filtered.end()); + // create uno objects + std::vector< uno::Reference<text::XTextField> > ret(filtered.size()); + std::transform(filtered.begin(), filtered.end(), ret.begin(), + MakeUnoObject()); + return ret; +} + +void MetaFieldManager::copyDocumentProperties(const SwDoc& rSource) +{ + const SwDocShell* pDocShell = rSource.GetDocShell(); + if (!pDocShell) + return; + + uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(pDocShell->GetModel(), uno::UNO_QUERY); + uno::Reference<util::XCloneable> xCloneable(xDocumentPropertiesSupplier->getDocumentProperties(), uno::UNO_QUERY); + m_xDocumentProperties.set(xCloneable->createClone(), uno::UNO_QUERY); +} + +const uno::Reference<document::XDocumentProperties>& MetaFieldManager::getDocumentProperties() const +{ + return m_xDocumentProperties; +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/fntcache.cxx b/sw/source/core/txtnode/fntcache.cxx new file mode 100644 index 000000000..decea02c4 --- /dev/null +++ b/sw/source/core/txtnode/fntcache.cxx @@ -0,0 +1,2712 @@ +/* -*- 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 <memory> +#include <sal/config.h> + +#include <cstdint> + +#include <i18nlangtag/mslangid.hxx> +#include <vcl/outdev.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metric.hxx> +#include <vcl/svapp.hxx> +#include <vcl/lazydelete.hxx> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <breakit.hxx> +#include <paintfrm.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <fntcache.hxx> +#include <IDocumentSettingAccess.hxx> +#include <swfont.hxx> +#include <wrong.hxx> +#include <txtfrm.hxx> +#include <pagefrm.hxx> +#include <tgrditem.hxx> +#include <scriptinfo.hxx> +#include <editeng/brushitem.hxx> +#include <swmodule.hxx> +#include <accessibilityoptions.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <doc.hxx> +#include <editeng/fhgtitem.hxx> +#include <vcl/glyphitem.hxx> +#include <vcl/vcllayout.hxx> +#include <docsh.hxx> +#include <strings.hrc> +#include <fntcap.hxx> +#include <vcl/outdev/ScopedStates.hxx> + +using namespace ::com::sun::star; + +// global variables declared in fntcache.hxx +// FontCache is created in txtinit.cxx TextInit_ and deleted in TextFinit +SwFntCache *pFntCache = nullptr; +// last Font set by ChgFntCache +SwFntObj *pLastFont = nullptr; + +static constexpr Color gWaveCol(COL_GRAY); + +long SwFntObj::nPixWidth; +MapMode* SwFntObj::pPixMap = nullptr; +static vcl::DeleteOnDeinit< VclPtr<OutputDevice> > s_pFntObjPixOut( new VclPtr<OutputDevice> ); + +namespace +{ + +long EvalGridWidthAdd( const SwTextGridItem *const pGrid, const SwDrawTextInfo &rInf ) +{ + SwDocShell* pDocShell = rInf.GetShell()->GetDoc()->GetDocShell(); + SfxStyleSheetBasePool* pBasePool = pDocShell->GetStyleSheetPool(); + + SfxStyleSheetBase* pStyle = pBasePool->Find(SwResId(STR_POOLCOLL_STANDARD), SfxStyleFamily::Para); + SfxItemSet& aTmpSet = pStyle->GetItemSet(); + const SvxFontHeightItem &aDefaultFontItem = aTmpSet.Get(RES_CHRATR_CJK_FONTSIZE); + + const SwDoc* pDoc = rInf.GetShell()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc); + const sal_uInt32 nFontHeight = aDefaultFontItem.GetHeight(); + const long nGridWidthAdd = nGridWidth > nFontHeight ? nGridWidth - nFontHeight : 0; + if( SwFontScript::Latin == rInf.GetFont()->GetActual() ) + return nGridWidthAdd / 2; + + return nGridWidthAdd; +} + +/** + * Pre-calculates glyph items for the rendered subset of rKey's text, assuming + * outdev state does not change between the outdev calls. + */ +SalLayoutGlyphs* lcl_CreateLayout(const SwTextGlyphsKey& rKey, SalLayoutGlyphs& rTextGlyphs) +{ + // Use pre-calculated result. + if (rTextGlyphs.IsValid()) + return &rTextGlyphs; + + if (rKey.m_nIndex >= rKey.m_aText.getLength()) + // Same as in OutputDevice::GetTextArray(). + return nullptr; + + // Calculate glyph items. + std::unique_ptr<SalLayout> pLayout + = rKey.m_pOutputDevice->ImplLayout(rKey.m_aText, rKey.m_nIndex, rKey.m_nLength, Point(0, 0), 0, + nullptr, SalLayoutFlags::GlyphItemsOnly); + if (!pLayout) + return nullptr; + + const SalLayoutGlyphs* pGlyphs = pLayout->GetGlyphs(); + if (!pGlyphs) + return nullptr; + + // Remember the calculation result. + rTextGlyphs = *pGlyphs; + + return &rTextGlyphs; +} +} + +bool operator<(const SwTextGlyphsKey& l, const SwTextGlyphsKey& r) +{ + if (l.m_pOutputDevice.get() < r.m_pOutputDevice.get()) + return true; + if (l.m_pOutputDevice.get() > r.m_pOutputDevice.get()) + return false; + if (l.m_nIndex < r.m_nIndex) + return true; + if (l.m_nIndex > r.m_nIndex) + return false; + if (l.m_nLength < r.m_nLength) + return true; + if (l.m_nLength > r.m_nLength) + return false; + + // Comparing strings is expensive, so compare them: + // - only at the end of this function + // - only once + // - only the relevant substring (if the index/length is not out of bounds) + sal_Int32 nRet = 0; + if (l.m_nLength < 0 || l.m_nIndex < 0 || l.m_nIndex + l.m_nLength > l.m_aText.getLength()) + nRet = l.m_aText.compareTo(r.m_aText); + else + nRet = memcmp(l.m_aText.getStr() + l.m_nIndex, r.m_aText.getStr() + r.m_nIndex, + l.m_nLength * sizeof(sal_Unicode)); + if (nRet < 0) + return true; + if (nRet > 0) + return false; + + return false; +}; + +void SwFntCache::Flush( ) +{ + if ( pLastFont ) + { + pLastFont->Unlock(); + pLastFont = nullptr; + } + SwCache::Flush( ); +} + +SwFntObj::SwFntObj(const SwSubFont &rFont, std::uintptr_t nFontCacheId, SwViewShell const *pSh) + : SwCacheObj(reinterpret_cast<void *>(nFontCacheId)) + , m_aFont(rFont) + , m_pScrFont(nullptr) + , m_pPrtFont(&m_aFont) + , m_pPrinter(nullptr) + , m_nGuessedLeading(USHRT_MAX) + , m_nExtLeading(USHRT_MAX) + , m_nScrAscent(0) + , m_nPrtAscent(USHRT_MAX) + , m_nScrHeight(0) + , m_nPrtHeight(USHRT_MAX) + , m_nPropWidth(rFont.GetPropWidth()) +{ + m_nZoom = pSh ? pSh->GetViewOptions()->GetZoom() : USHRT_MAX; + m_bSymbol = RTL_TEXTENCODING_SYMBOL == m_aFont.GetCharSet(); + m_bPaintBlank = ( LINESTYLE_NONE != m_aFont.GetUnderline() + || LINESTYLE_NONE != m_aFont.GetOverline() + || STRIKEOUT_NONE != m_aFont.GetStrikeout() ) + && !m_aFont.IsWordLineMode(); + m_aFont.SetLanguage(rFont.GetLanguage()); +} + +SwFntObj::~SwFntObj() +{ + if ( m_pScrFont != m_pPrtFont ) + delete m_pScrFont; + if ( m_pPrtFont != &m_aFont ) + delete m_pPrtFont; +} + +void SwFntObj::CreatePrtFont( const OutputDevice& rPrt ) +{ + if ( m_nPropWidth == 100 || m_pPrinter == &rPrt ) + return; + + if( m_pScrFont != m_pPrtFont ) + delete m_pScrFont; + if( m_pPrtFont != &m_aFont ) + delete m_pPrtFont; + + const vcl::Font aOldFnt( rPrt.GetFont() ); + const_cast<OutputDevice&>(rPrt).SetFont( m_aFont ); + const FontMetric aWinMet( rPrt.GetFontMetric() ); + const_cast<OutputDevice&>(rPrt).SetFont( aOldFnt ); + auto nWidth = ( aWinMet.GetFontSize().Width() * m_nPropWidth ) / 100; + + if( !nWidth ) + ++nWidth; + m_pPrtFont = new vcl::Font( m_aFont ); + m_pPrtFont->SetFontSize( Size( nWidth, m_aFont.GetFontSize().Height() ) ); + m_pScrFont = nullptr; + +} + +/* + * returns whether we have to adjust the output font to resemble + * the formatting font + * + * _Not_ necessary if + * + * 1. RefDef == OutDev (text formatting, online layout...) + * 2. PDF export from online layout + * 3. Prospect/PagePreview printing + */ +static bool lcl_IsFontAdjustNecessary( const vcl::RenderContext& rOutDev, + const vcl::RenderContext& rRefDev ) +{ + return &rRefDev != &rOutDev && + OUTDEV_WINDOW != rRefDev.GetOutDevType() && + ( OUTDEV_PRINTER != rRefDev.GetOutDevType() || + OUTDEV_PRINTER != rOutDev.GetOutDevType() ); +} + +namespace { + +struct CalcLinePosData +{ + SwDrawTextInfo& rInf; + vcl::Font& rFont; + TextFrameIndex nCnt; + const bool bSwitchH2V; + const bool bSwitchH2VLRBT; + const bool bSwitchL2R; + long nHalfSpace; + long* pKernArray; + const bool bBidiPor; + + CalcLinePosData( SwDrawTextInfo& _rInf, vcl::Font& _rFont, + TextFrameIndex const _nCnt, const bool _bSwitchH2V, const bool _bSwitchH2VLRBT, const bool _bSwitchL2R, + long _nHalfSpace, long* _pKernArray, const bool _bBidiPor) : + rInf( _rInf ), + rFont( _rFont ), + nCnt( _nCnt ), + bSwitchH2V( _bSwitchH2V ), + bSwitchH2VLRBT( _bSwitchH2VLRBT ), + bSwitchL2R( _bSwitchL2R ), + nHalfSpace( _nHalfSpace ), + pKernArray( _pKernArray ), + bBidiPor( _bBidiPor ) + { + } +}; + +} + +// Computes the start and end position of an underline. This function is called +// from the DrawText-method (for underlining misspelled words or smarttag terms). +static void lcl_calcLinePos( const CalcLinePosData &rData, + Point &rStart, Point &rEnd, TextFrameIndex const nStart, TextFrameIndex const nWrLen) +{ + long nBlank = 0; + const TextFrameIndex nEnd = nStart + nWrLen; + const long nTmpSpaceAdd = rData.rInf.GetSpace() / SPACING_PRECISION_FACTOR; + + if ( nEnd < rData.nCnt + && CH_BLANK == rData.rInf.GetText()[sal_Int32(rData.rInf.GetIdx() + nEnd)] ) + { + if (nEnd + TextFrameIndex(1) == rData.nCnt) + nBlank -= nTmpSpaceAdd; + else + nBlank -= rData.nHalfSpace; + } + + // determine start, end and length of wave line + sal_Int32 nKernStart = nStart ? rData.pKernArray[sal_Int32(nStart) - 1] : 0; + sal_Int32 nKernEnd = rData.pKernArray[sal_Int32(nEnd) - 1]; + + const sal_uInt16 nDir = rData.bBidiPor ? 1800 + : UnMapDirection(rData.rFont.GetOrientation(), + rData.bSwitchH2V, rData.bSwitchH2VLRBT); + + switch ( nDir ) + { + case 0 : + rStart.AdjustX(nKernStart ); + rEnd.setX( nBlank + rData.rInf.GetPos().X() + nKernEnd ); + rEnd.setY( rData.rInf.GetPos().Y() ); + break; + case 900 : + rStart.AdjustY( -nKernStart ); + rEnd.setX( rData.rInf.GetPos().X() ); + rEnd.setY( nBlank + rData.rInf.GetPos().Y() - nKernEnd ); + break; + case 1800 : + rStart.AdjustX( -nKernStart ); + rEnd.setX( rData.rInf.GetPos().X() - nKernEnd - nBlank ); + rEnd.setY( rData.rInf.GetPos().Y() ); + break; + case 2700 : + rStart.AdjustY(nKernStart ); + rEnd.setX( rData.rInf.GetPos().X() ); + rEnd.setY( nBlank + rData.rInf.GetPos().Y() + nKernEnd ); + break; + } + + if ( rData.bSwitchL2R ) + { + rData.rInf.GetFrame()->SwitchLTRtoRTL( rStart ); + rData.rInf.GetFrame()->SwitchLTRtoRTL( rEnd ); + } + + if ( rData.bSwitchH2V ) + { + rData.rInf.GetFrame()->SwitchHorizontalToVertical( rStart ); + rData.rInf.GetFrame()->SwitchHorizontalToVertical( rEnd ); + } +} + +// Returns the Ascent of the Font on the given output device; +// it may be necessary to create the screen font first. +sal_uInt16 SwFntObj::GetFontAscent( const SwViewShell *pSh, const OutputDevice& rOut ) +{ + sal_uInt16 nRet = 0; + const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut; + + if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) ) + { + CreateScrFont( *pSh, rOut ); + OSL_ENSURE( USHRT_MAX != m_nScrAscent, "nScrAscent is going berzerk" ); + nRet = m_nScrAscent; + } + else + { + if (m_nPrtAscent == USHRT_MAX) // printer ascent unknown? + { + CreatePrtFont( rOut ); + const vcl::Font aOldFnt( rRefDev.GetFont() ); + const_cast<OutputDevice&>(rRefDev).SetFont( *m_pPrtFont ); + const FontMetric aOutMet( rRefDev.GetFontMetric() ); + m_nPrtAscent = static_cast<sal_uInt16>(aOutMet.GetAscent()); + const_cast<OutputDevice&>(rRefDev).SetFont( aOldFnt ); + } + + nRet = m_nPrtAscent; + } + +#if !defined(MACOSX) // #i89844# extleading is below the line for Mac + // TODO: move extleading below the line for all platforms too + nRet += GetFontLeading( pSh, rRefDev ); +#endif + + OSL_ENSURE( USHRT_MAX != nRet, "GetFontAscent returned USHRT_MAX" ); + return nRet; +} + +// Returns the height of the Font on the given output device; +// it may be necessary to create the screen font first. +sal_uInt16 SwFntObj::GetFontHeight( const SwViewShell* pSh, const OutputDevice& rOut ) +{ + sal_uInt16 nRet = 0; + const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut; + + if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) ) + { + CreateScrFont( *pSh, rOut ); + OSL_ENSURE( USHRT_MAX != m_nScrHeight, "nScrHeight is going berzerk" ); + nRet = m_nScrHeight + GetFontLeading( pSh, rRefDev ); + } + else + { + if (m_nPrtHeight == USHRT_MAX) // printer height unknown? + { + CreatePrtFont( rOut ); + const vcl::Font aOldFnt( rRefDev.GetFont() ); + const_cast<OutputDevice&>(rRefDev).SetFont( *m_pPrtFont ); + m_nPrtHeight = static_cast<sal_uInt16>(rRefDev.GetTextHeight()); + +#if OSL_DEBUG_LEVEL > 0 + // Check if vcl did not change the meaning of GetTextHeight + const FontMetric aOutMet( rRefDev.GetFontMetric() ); + long nTmpPrtHeight = static_cast<sal_uInt16>(aOutMet.GetAscent()) + aOutMet.GetDescent(); + // #i106098#: do not compare with == here due to rounding error + OSL_ENSURE( std::abs(nTmpPrtHeight - m_nPrtHeight) < 3, + "GetTextHeight != Ascent + Descent" ); +#endif + + const_cast<OutputDevice&>(rRefDev).SetFont( aOldFnt ); + } + + nRet = m_nPrtHeight + GetFontLeading( pSh, rRefDev ); + } + + OSL_ENSURE( USHRT_MAX != nRet, "GetFontHeight returned USHRT_MAX" ); + return nRet; +} + +sal_uInt16 SwFntObj::GetFontLeading( const SwViewShell *pSh, const OutputDevice& rOut ) +{ + sal_uInt16 nRet = 0; + + if ( pSh ) + { + if ( USHRT_MAX == m_nGuessedLeading || USHRT_MAX == m_nExtLeading ) + { + SolarMutexGuard aGuard; + + const vcl::Font aOldFnt( rOut.GetFont() ); + const_cast<OutputDevice&>(rOut).SetFont( *m_pPrtFont ); + const FontMetric aMet( rOut.GetFontMetric() ); + const_cast<OutputDevice&>(rOut).SetFont( aOldFnt ); + m_bSymbol = RTL_TEXTENCODING_SYMBOL == aMet.GetCharSet(); + GuessLeading( *pSh, aMet ); + m_nExtLeading = static_cast<sal_uInt16>(aMet.GetExternalLeading()); + /* HACK: FIXME There is something wrong with Writer's bullet rendering, causing lines + with bullets to be higher than they should be. I think this is because + Writer uses font's external leading incorrect, as the vertical distance + added to every line instead of only a distance between multiple lines, + which means a single bullet has external leading added even though it + shouldn't, but frankly this is just an educated guess rather than understanding + Writer's layout (heh). + Symbol font in some documents is 'StarSymbol; Arial Unicode MS', and Windows + machines often do not have StarSymbol, falling back to Arial Unicode MS, which + has unusually high external leading. So just reset external leading for fonts + which are used to bullets, as those should not be used on multiple lines anyway, + so in correct rendering external leading should be irrelevant anyway. + Interestingly enough, bSymbol is false for 'StarSymbol; Arial Unicode MS', so + also check explicitly. + */ + if( m_bSymbol || IsStarSymbol( m_pPrtFont->GetFamilyName())) + m_nExtLeading = 0; + } + + const IDocumentSettingAccess& rIDSA = pSh->getIDocumentSettingAccess(); + const bool bBrowse = ( pSh->GetWin() && + pSh->GetViewOptions()->getBrowseMode() && + !pSh->GetViewOptions()->IsPrtFormat() ); + + if ( !bBrowse && rIDSA.get(DocumentSettingId::ADD_EXT_LEADING) ) + nRet = m_nExtLeading; + else + nRet = m_nGuessedLeading; + } + + OSL_ENSURE( USHRT_MAX != nRet, "GetFontLeading returned USHRT_MAX" ); + return nRet; +} + +// pOut is the output device, not the reference device +void SwFntObj::CreateScrFont( const SwViewShell& rSh, const OutputDevice& rOut ) +{ + if ( m_pScrFont ) + return; + + // any changes to the output device are reset at the end of the function + OutputDevice* pOut = const_cast<OutputDevice*>(&rOut); + + // Save old font + vcl::Font aOldOutFont( pOut->GetFont() ); + + m_nScrHeight = USHRT_MAX; + + // Condition for output font / refdev font adjustment + OutputDevice* pPrt = &rSh.GetRefDev(); + + if( !rSh.GetWin() || + !rSh.GetViewOptions()->getBrowseMode() || + rSh.GetViewOptions()->IsPrtFormat() ) + { + // After CreatePrtFont m_pPrtFont is the font which is actually used + // by the reference device + CreatePrtFont( *pPrt ); + m_pPrinter = pPrt; + + // save old reference device font + vcl::Font aOldPrtFnt( pPrt->GetFont() ); + + // set the font used at the reference device at the reference device + // and the output device + pPrt->SetFont( *m_pPrtFont ); + pOut->SetFont( *m_pPrtFont ); + + // This should be the default for pScrFont. + m_pScrFont = m_pPrtFont; + + FontMetric aMet = pPrt->GetFontMetric( ); + // Don't lose "faked" properties of the logical font that don't truly + // exist in the physical font metrics which vcl which fake up for us + aMet.SetWeight(m_pScrFont->GetWeight()); + aMet.SetItalic(m_pScrFont->GetItalic()); + + m_bSymbol = RTL_TEXTENCODING_SYMBOL == aMet.GetCharSet(); + + if ( USHRT_MAX == m_nGuessedLeading ) + GuessLeading( rSh, aMet ); + + if ( USHRT_MAX == m_nExtLeading ) + m_nExtLeading = static_cast<sal_uInt16>(aMet.GetExternalLeading()); + + // reset the original reference device font + pPrt->SetFont( aOldPrtFnt ); + } + else + { + m_bSymbol = RTL_TEXTENCODING_SYMBOL == m_aFont.GetCharSet(); + if ( m_nGuessedLeading == USHRT_MAX ) + m_nGuessedLeading = 0; + + // no external leading in browse mode + if ( m_nExtLeading == USHRT_MAX ) + m_nExtLeading = 0; + + m_pScrFont = m_pPrtFont; + } + + // check zoom factor, e.g. because of PrtOle2 during export + { + // In case the zoom factor of the output device differs from the + // one in the ViewOptions, this Font must not be cached, + // hence set zoom factor to an invalid value + long nTmp; + if( pOut->GetMapMode().GetScaleX().IsValid() && + pOut->GetMapMode().GetScaleY().IsValid() && + pOut->GetMapMode().GetScaleX() == pOut->GetMapMode().GetScaleY() ) + { + nTmp = long(100 * pOut->GetMapMode().GetScaleX()); + } + else + nTmp = 0; + if( nTmp != m_nZoom ) + m_nZoom = USHRT_MAX - 1; + } + + m_nScrAscent = static_cast<sal_uInt16>(pOut->GetFontMetric().GetAscent()); + if ( USHRT_MAX == m_nScrHeight ) + m_nScrHeight = static_cast<sal_uInt16>(pOut->GetTextHeight()); + + // reset original output device font + pOut->SetFont( aOldOutFont ); +} + +void SwFntObj::GuessLeading( const SwViewShell& +#if defined(_WIN32) + rSh +#endif + , const FontMetric& rMet ) +{ + // If leading >= 5, this seems to be enough leading. + // Nothing has to be done. + if ( rMet.GetInternalLeading() >= 5 ) + { + m_nGuessedLeading = 0; + return; + } + +#if defined(_WIN32) + OutputDevice *pWin = rSh.GetWin() ? + rSh.GetWin() : + Application::GetDefaultDevice(); + if ( pWin ) + { + MapMode aTmpMap( MapUnit::MapTwip ); + MapMode aOldMap = pWin->GetMapMode( ); + pWin->SetMapMode( aTmpMap ); + const vcl::Font aOldFnt( pWin->GetFont() ); + pWin->SetFont( *m_pPrtFont ); + const FontMetric aWinMet( pWin->GetFontMetric() ); + const sal_uInt16 nWinHeight = sal_uInt16( aWinMet.GetFontSize().Height() ); + if( m_pPrtFont->GetFamilyName().indexOf( aWinMet.GetFamilyName() ) != -1 ) + { + // If the Leading on the Window is also 0, then it has to stay + // that way (see also StarMath). + long nTmpLeading = aWinMet.GetInternalLeading(); + if( nTmpLeading <= 0 ) + { + pWin->SetFont( rMet ); + nTmpLeading = pWin->GetFontMetric().GetInternalLeading(); + if( nTmpLeading < 0 ) + m_nGuessedLeading = 0; + else + m_nGuessedLeading = sal_uInt16(nTmpLeading); + } + else + { + m_nGuessedLeading = sal_uInt16(nTmpLeading); + // Manta-Hack #50153#: + // Wer beim Leading luegt, luegt moeglicherweise auch beim + // Ascent/Descent, deshalb wird hier ggf. der Font ein wenig + // tiefergelegt, ohne dabei seine Hoehe zu aendern. + // (above original comment preserved for cultural reasons) + // Those who lie about their Leading, may lie about their + // Ascent/Descent as well, hence the Font will be lowered a + // little without changing its height. + long nDiff = std::min( rMet.GetDescent() - aWinMet.GetDescent(), + aWinMet.GetAscent() - rMet.GetAscent() - nTmpLeading ); + if( nDiff > 0 ) + { + OSL_ENSURE( m_nPrtAscent < USHRT_MAX, "GuessLeading: PrtAscent-Fault" ); + if ( m_nPrtAscent < USHRT_MAX ) + m_nPrtAscent = m_nPrtAscent + static_cast<sal_uInt16>(( 2 * nDiff ) / 5); + } + } + } + else + { + // If all else fails, take 15% of the height, as empirically + // determined by CL + m_nGuessedLeading = (nWinHeight * 15) / 100; + } + pWin->SetFont( aOldFnt ); + pWin->SetMapMode( aOldMap ); + } + else + m_nGuessedLeading = 0; +#else + m_nGuessedLeading = 0; +#endif +} + +// Set the font at the given output device; for screens it may be +// necessary to do some adjustment first. +void SwFntObj::SetDevFont( const SwViewShell *pSh, OutputDevice& rOut ) +{ + const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut; + + if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) ) + { + CreateScrFont( *pSh, rOut ); + if( !GetScrFont()->IsSameInstance( rOut.GetFont() ) ) + rOut.SetFont( *m_pScrFont ); + if( m_pPrinter && ( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) ) + m_pPrinter->SetFont( *m_pPrtFont ); + } + else + { + CreatePrtFont( rOut ); + if( !m_pPrtFont->IsSameInstance( rOut.GetFont() ) ) + rOut.SetFont( *m_pPrtFont ); + } + + // Here, we actually do not need the leading values, but by calling + // GetFontLeading() we assure that the values are calculated for later use. + GetFontLeading( pSh, rRefDev ); +} + +#define WRONG_SHOW_MIN 5 + +/* + * Output text: + * on screen => DrawTextArray + * on printer, !Kerning => DrawText + * on printer + Kerning => DrawStretchText + */ +static sal_uInt8 lcl_WhichPunctuation( sal_Unicode cChar ) +{ + if ( ( cChar < 0x3001 || cChar > 0x3002 ) && + ( cChar < 0x3008 || cChar > 0x3011 ) && + ( cChar < 0x3014 || cChar > 0x301F ) && + 0xFF62 != cChar && 0xFF63 != cChar ) + // no punctuation + return SwScriptInfo::NONE; + else if ( 0x3001 == cChar || 0x3002 == cChar || + 0x3009 == cChar || 0x300B == cChar || + 0x300D == cChar || 0x300F == cChar || + 0x3011 == cChar || 0x3015 == cChar || + 0x3017 == cChar || 0x3019 == cChar || + 0x301B == cChar || 0x301E == cChar || + 0x301F == cChar || 0xFF63 == cChar ) + // right punctuation + return SwScriptInfo::SPECIAL_RIGHT; + + return SwScriptInfo::SPECIAL_LEFT; +} + +static bool lcl_IsMonoSpaceFont( const vcl::RenderContext& rOut ) +{ + const long nWidth1 = rOut.GetTextWidth( OUString( u'\x3008' ) ); + const long nWidth2 = rOut.GetTextWidth( OUString( u'\x307C' ) ); + return nWidth1 == nWidth2; +} + +static bool lcl_IsFullstopCentered( const vcl::RenderContext& rOut ) +{ + const FontMetric aMetric( rOut.GetFontMetric() ); + return aMetric.IsFullstopCentered() ; +} + +/* This helper structure (SwForbidden) contains the already marked parts of the string + to avoid double lines (e.g grammar + spell check error) */ + +typedef std::vector<std::pair<TextFrameIndex, TextFrameIndex>> SwForbidden; + +static void lcl_DrawLineForWrongListData( + SwForbidden &rForbidden, + const SwDrawTextInfo &rInf, + sw::WrongListIterator *pWList, + const CalcLinePosData &rCalcLinePosData, + const Size &rPrtFontSize ) +{ + if (!pWList) return; + + TextFrameIndex nStart = rInf.GetIdx(); + TextFrameIndex nWrLen = rInf.GetLen(); + + // check if respective data is available in the current text range + if (!pWList->Check( nStart, nWrLen )) + { + return; + } + + long nHght = rInf.GetOut().LogicToPixel( rPrtFontSize ).Height(); + + // Draw wavy lines for spell and grammar errors only if font is large enough. + // Lines for smart tags will always be drawn. + if (pWList != rInf.GetSmartTags() && WRONG_SHOW_MIN >= nHght) + { + return; + } + + SwForbidden::iterator pIter = rForbidden.begin(); + if (rInf.GetOut().GetConnectMetaFile()) + rInf.GetOut().Push(); + + const Color aCol( rInf.GetOut().GetLineColor() ); + + // iterate over all ranges stored in the respective SwWrongList + do + { + nStart -= rInf.GetIdx(); + + const TextFrameIndex nEnd = nStart + nWrLen; + TextFrameIndex nNext = nStart; + while( nNext < nEnd ) + { + while( pIter != rForbidden.end() && pIter->second <= nNext ) + ++pIter; + + const TextFrameIndex nNextStart = nNext; + TextFrameIndex nNextEnd = nEnd; + + if( pIter == rForbidden.end() || nNextEnd <= pIter->first ) + { + // No overlapping mark up found + rForbidden.insert(pIter, std::make_pair(nNextStart, nNextEnd)); + pIter = rForbidden.begin(); + nNext = nEnd; + } + else + { + nNext = pIter->second; + if( nNextStart < pIter->first ) + { + nNextEnd = pIter->first; + pIter->first = nNextStart; + } + else + continue; + } + // determine line pos + Point aStart( rInf.GetPos() ); + Point aEnd; + lcl_calcLinePos( rCalcLinePosData, aStart, aEnd, nNextStart, nNextEnd - nNextStart ); + + SwWrongArea const*const wrongArea = pWList->GetWrongElement(nNextStart + rInf.GetIdx()); + if (wrongArea != nullptr) + { + if (WRONGAREA_WAVE == wrongArea->mLineType) + { + vcl::ScopedAntialiasing a(rInf.GetOut(), true); + rInf.GetOut().SetLineColor( wrongArea->mColor ); + rInf.GetOut().DrawWaveLine( aStart, aEnd, 1 ); + } + else if (WRONGAREA_BOLDWAVE == wrongArea->mLineType) + { + vcl::ScopedAntialiasing a(rInf.GetOut(), true); + rInf.GetOut().SetLineColor( wrongArea->mColor ); + rInf.GetOut().DrawWaveLine( aStart, aEnd, 2 ); + } + else if (WRONGAREA_BOLD == wrongArea->mLineType) + { + rInf.GetOut().SetLineColor( wrongArea->mColor ); + + aStart.AdjustY(30 ); + aEnd.AdjustY(30 ); + + LineInfo aLineInfo( LineStyle::Solid, 26 ); + + rInf.GetOut().DrawLine( aStart, aEnd, aLineInfo ); + } + else if (WRONGAREA_DASHED == wrongArea->mLineType) + { + rInf.GetOut().SetLineColor( wrongArea->mColor ); + + aStart.AdjustY(30 ); + aEnd.AdjustY(30 ); + + LineInfo aLineInfo( LineStyle::Dash ); + aLineInfo.SetDistance( 40 ); + aLineInfo.SetDashLen( 1 ); + aLineInfo.SetDashCount(1); + + rInf.GetOut().DrawLine( aStart, aEnd, aLineInfo ); + } + } + } + + nStart = nEnd + rInf.GetIdx(); + nWrLen = rInf.GetIdx() + rInf.GetLen() - nStart; + } + while (nWrLen && pWList->Check( nStart, nWrLen )); + + rInf.GetOut().SetLineColor( aCol ); + + if (rInf.GetOut().GetConnectMetaFile()) + rInf.GetOut().Pop(); +} + +void SwFntObj::DrawText( SwDrawTextInfo &rInf ) +{ + OSL_ENSURE( rInf.GetShell(), "SwFntObj::DrawText without shell" ); + + OutputDevice& rRefDev = rInf.GetShell()->GetRefDev(); + OutputDevice* pWin = rInf.GetShell()->GetWin(); + + // true if pOut is the printer and the printer has been used for formatting + const bool bPrt = OUTDEV_PRINTER == rInf.GetOut().GetOutDevType() && + OUTDEV_PRINTER == rRefDev.GetOutDevType(); + const bool bBrowse = ( pWin && + rInf.GetShell()->GetViewOptions()->getBrowseMode() && + !rInf.GetShell()->GetViewOptions()->IsPrtFormat() && + !rInf.GetBullet() && + ( rInf.GetSpace() || !rInf.GetKern() ) && + !rInf.GetWrong() && + !rInf.GetGrammarCheck() && + !rInf.GetSmartTags() && + !rInf.GetGreyWave() ); + + // bDirectPrint indicates that we can enter the branch which calls + // the DrawText functions instead of calling the DrawTextArray functions + const bool bDirectPrint = bPrt || bBrowse; + + // Condition for output font / refdev font adjustment + const bool bUseScrFont = + lcl_IsFontAdjustNecessary( rInf.GetOut(), rRefDev ); + + vcl::Font* pTmpFont = bUseScrFont ? m_pScrFont : m_pPrtFont; + + // bDirectPrint and bUseScrFont should have these values: + + // Outdev / RefDef | Printer | VirtPrinter | Window + + // Printer | 1 - 0 | 0 - 1 | - + + // VirtPrinter/PDF | 0 - 1 | 0 - 1 | - + + // Window/VirtWindow| 0 - 1 | 0 - 1 | 1 - 0 + + // Exception: During painting of a Writer OLE object, we do not have + // a window. Therefore bUseSrcFont is always 0 in this case. + +#if OSL_DEBUG_LEVEL > 0 + + const bool bNoAdjust = bPrt || + ( pWin && + rInf.GetShell()->GetViewOptions()->getBrowseMode() && + !rInf.GetShell()->GetViewOptions()->IsPrtFormat() ); + + if ( OUTDEV_PRINTER == rInf.GetOut().GetOutDevType() ) + { + // Printer output + if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() ) + { + OSL_ENSURE( bNoAdjust && !bUseScrFont, "Outdev Check failed" ); + } + else if ( rRefDev.IsVirtual() ) + { + OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" ); + } + else + { + OSL_FAIL( "Outdev Check failed" ); + } + } + else if ( rInf.GetOut().IsVirtual() && ! pWin ) + { + // PDF export + if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() ) + { + OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" ); + } + else if ( rRefDev.IsVirtual() ) + { + OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" ); + } + else + { + OSL_FAIL( "Outdev Check failed" ); + } + } + else if ( OUTDEV_WINDOW == rInf.GetOut().GetOutDevType() || + ( rInf.GetOut().IsVirtual() && pWin ) ) + { + // Window or virtual window + if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() ) + { + OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" ); + } + else if ( rRefDev.IsVirtual() ) + { + OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" ); + } + else if ( OUTDEV_WINDOW == rRefDev.GetOutDevType() ) + { + OSL_ENSURE( bNoAdjust && !bUseScrFont, "Outdev Check failed" ); + } + else + { + OSL_FAIL( "Outdev Check failed" ); + } + } + else + { + OSL_FAIL( "Outdev Check failed" ); + } + +#endif + + // robust: better use the printer font instead of using no font at all + OSL_ENSURE( pTmpFont, "No screen or printer font?" ); + if ( ! pTmpFont ) + pTmpFont = m_pPrtFont; + + // HACK: LINESTYLE_WAVE must not be abused any more, hence the grey wave + // line of the ExtendedAttributeSets will appear in the font color first + + const bool bSwitchH2V = rInf.GetFrame() && rInf.GetFrame()->IsVertical(); + const bool bSwitchH2VLRBT = rInf.GetFrame() && rInf.GetFrame()->IsVertLRBT(); + const bool bSwitchL2R = rInf.GetFrame() && rInf.GetFrame()->IsRightToLeft() && + ! rInf.IsIgnoreFrameRTL(); + const ComplexTextLayoutFlags nMode = rInf.GetOut().GetLayoutMode(); + const bool bBidiPor = ( bSwitchL2R != + ( ComplexTextLayoutFlags::Default != ( ComplexTextLayoutFlags::BiDiRtl & nMode ) ) ); + + // be sure to have the correct layout mode at the printer + if ( m_pPrinter ) + { + m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() ); + m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() ); + } + + Point aTextOriginPos( rInf.GetPos() ); + if( !bPrt ) + { + if( rInf.GetpOut() != *s_pFntObjPixOut.get() || rInf.GetOut().GetMapMode() != *pPixMap ) + { + *pPixMap = rInf.GetOut().GetMapMode(); + (*s_pFntObjPixOut.get()) = rInf.GetpOut(); + Size aTmp( 1, 1 ); + nPixWidth = rInf.GetOut().PixelToLogic( aTmp ).Width(); + } + + aTextOriginPos.AdjustX(rInf.GetFrame()->IsRightToLeft() ? 0 : nPixWidth ); + } + + Color aOldColor( pTmpFont->GetColor() ); + bool bChgColor = rInf.ApplyAutoColor( pTmpFont ); + if( !pTmpFont->IsSameInstance( rInf.GetOut().GetFont() ) ) + rInf.GetOut().SetFont( *pTmpFont ); + if ( bChgColor ) + pTmpFont->SetColor( aOldColor ); + + if (TextFrameIndex(COMPLETE_STRING) == rInf.GetLen()) + rInf.SetLen( TextFrameIndex(rInf.GetText().getLength()) ); + + // ASIAN LINE AND CHARACTER GRID MODE START + + if ( rInf.GetFrame() && rInf.SnapToGrid() && rInf.GetFont() && + SwFontScript::CJK == rInf.GetFont()->GetActual() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + + // ASIAN LINE AND CHARACTER GRID MODE: Do we want to snap asian characters to the grid? + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars()) + { + //for textgrid refactor + //const sal_uInt16 nGridWidth = pGrid->GetBaseHeight(); + const SwDoc* pDoc = rInf.GetShell()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc); + + // kerning array - gives the absolute position of end of each character + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())]); + + if ( m_pPrinter ) + m_pPrinter->GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + else + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + + // Change the average width per character to an appropriate grid width + // basically get the ratio of the avg width to the grid unit width, then + // multiple this ratio to give the new avg width - which in this case + // gives a new grid width unit size + + long nAvgWidthPerChar = pKernArray[sal_Int32(rInf.GetLen()) - 1] / sal_Int32(rInf.GetLen()); + + const sal_uLong nRatioAvgWidthCharToGridWidth = nAvgWidthPerChar ? + ( nAvgWidthPerChar - 1 ) / nGridWidth + 1: + 1; + + nAvgWidthPerChar = nRatioAvgWidthCharToGridWidth * nGridWidth; + + // the absolute end position of the first character is also its width + long nCharWidth = pKernArray[ 0 ]; + sal_uLong nHalfWidth = nAvgWidthPerChar / 2; + + long nNextFix=0; + + // we work out the start position (origin) of the first character, + // and we set the next "fix" offset to half the width of the char. + // The exceptions are for punctuation characters that are not centered + // so in these cases we just add half a regular "average" character width + // to the first characters actual width to allow the next character to + // be centered automatically + // If the character is "special right", then the offset is correct already + // so the fix offset is as normal - half the average character width + + sal_Unicode cChar = rInf.GetText()[ sal_Int32(rInf.GetIdx()) ]; + sal_uInt8 nType = lcl_WhichPunctuation( cChar ); + switch ( nType ) + { + // centre character + case SwScriptInfo::NONE : + aTextOriginPos.AdjustX(( nAvgWidthPerChar - nCharWidth ) / 2 ); + nNextFix = nCharWidth / 2; + break; + case SwScriptInfo::SPECIAL_RIGHT : + nNextFix = nHalfWidth; + break; + // punctuation + default: + aTextOriginPos.AdjustX(nAvgWidthPerChar - nCharWidth ); + nNextFix = nCharWidth - nHalfWidth; + } + + // calculate offsets + for (sal_Int32 j = 1; j < sal_Int32(rInf.GetLen()); ++j) + { + long nCurrentCharWidth = pKernArray[ j ] - pKernArray[ j - 1 ]; + nNextFix += nAvgWidthPerChar; + + // almost the same as getting the offset for the first character: + // punctuation characters are not centered, so just add half an + // average character width minus the characters actual char width + // to get the offset into the centre of the next character + + cChar = rInf.GetText()[ sal_Int32(rInf.GetIdx()) + j ]; + nType = lcl_WhichPunctuation( cChar ); + switch ( nType ) + { + case SwScriptInfo::NONE : + pKernArray[ j - 1 ] = nNextFix - ( nCurrentCharWidth / 2 ); + break; + case SwScriptInfo::SPECIAL_RIGHT : + pKernArray[ j - 1 ] = nNextFix - nHalfWidth; + break; + default: + pKernArray[ j - 1 ] = nNextFix + nHalfWidth - nCurrentCharWidth; + } + } + + // the layout engine requires the total width of the output + pKernArray[sal_Int32(rInf.GetLen()) - 1] = rInf.GetWidth() - + aTextOriginPos.X() + rInf.GetPos().X() ; + + if ( bSwitchH2V ) + rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos ); + + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + + return; + } + } + + // For text grid refactor + // ASIAN LINE AND CHARACTER GRID MODE START: not snap to characters + + if ( rInf.GetFrame() && rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + + // ASIAN LINE AND CHARACTER GRID MODE - do not snap to characters + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && !pGrid->IsSnapToChars() ) + { + const long nGridWidthAdd = EvalGridWidthAdd( pGrid, rInf ); + + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())]); + + if ( m_pPrinter ) + m_pPrinter->GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + else + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + if ( bSwitchH2V ) + rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos ); + if ( rInf.GetSpace() || rInf.GetKanaComp()) + { + long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR; + if ( rInf.GetFont() && rInf.GetLen() ) + { + bool bSpecialJust = false; + const SwScriptInfo* pSI = rInf.GetScriptInfo(); + const SwFontScript nActual = rInf.GetFont()->GetActual(); + ///Kana Compression + if( SwFontScript::CJK == nActual && rInf.GetKanaComp() && + pSI && pSI->CountCompChg() && + lcl_IsMonoSpaceFont( *(rInf.GetpOut()) ) ) + { + pSI->Compress( pKernArray.get(), rInf.GetIdx(), rInf.GetLen(), + rInf.GetKanaComp(), static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ) , &aTextOriginPos ); + bSpecialJust = true; + } + ///Asian Justification + if ( ( SwFontScript::CJK == nActual || SwFontScript::Latin == nActual ) && nSpaceAdd ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK ); + if (!MsLangId::isKorean(aLang)) + { + long nSpaceSum = nSpaceAdd; + for (sal_Int32 nI = 0; nI < sal_Int32(rInf.GetLen()); ++nI) + { + pKernArray[ nI ] += nSpaceSum; + nSpaceSum += nSpaceAdd; + } + bSpecialJust = true; + nSpaceAdd = 0; + } + } + long nGridAddSum = nGridWidthAdd; + for (sal_Int32 i = 0; i < sal_Int32(rInf.GetLen()); i++, nGridAddSum += nGridWidthAdd ) + { + pKernArray[i] += nGridAddSum; + } + long nKernSum = rInf.GetKern(); + if ( bSpecialJust || rInf.GetKern() ) + { + for (sal_Int32 i = 0; i < sal_Int32(rInf.GetLen()); i++, nKernSum += rInf.GetKern()) + { + if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx())+i]) + nKernSum += nSpaceAdd; + pKernArray[i] += nKernSum; + } + ///With through/uderstr. Grouped style requires a blank at the end + ///of a text edition special measures: + if( m_bPaintBlank && rInf.GetLen() && (CH_BLANK == + rInf.GetText()[sal_Int32(rInf.GetIdx() + rInf.GetLen()) - 1])) + { + ///If it concerns a singular, underlined space acts, + ///we must spend two: + if (TextFrameIndex(1) == rInf.GetLen()) + { + pKernArray[0] = rInf.GetWidth() + nSpaceAdd; + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), 1); + } + else + { + pKernArray[sal_Int32(rInf.GetLen()) - 2] += nSpaceAdd; + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), + sal_Int32(rInf.GetLen())); + } + } + else + { + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), + sal_Int32(rInf.GetLen())); + } + } + else + { + sal_Int32 i; + long nSpaceSum = 0; + for (i = 0; i < sal_Int32(rInf.GetLen()); i++) + { + if(CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i]) + nSpaceSum += nSpaceAdd + nKernSum; + + pKernArray[i] += nSpaceSum; + } + + rInf.GetOut().DrawTextArray(aTextOriginPos, + rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), + sal_Int32(rInf.GetLen())); + } + } + } + else + { + //long nKernAdd = rInf.GetKern(); + long nKernAdd = 0; + long nGridAddSum = nGridWidthAdd + nKernAdd; + for (sal_Int32 i = 0; i < sal_Int32(rInf.GetLen()); + i++, nGridAddSum += nGridWidthAdd + nKernAdd) + { + pKernArray[i] += nGridAddSum; + } + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + return; + } + } + + // DIRECT PAINTING WITHOUT SCREEN ADJUSTMENT + + if ( bDirectPrint ) + { + const Fraction aTmp( 1, 1 ); + bool bStretch = rInf.GetWidth() && (rInf.GetLen() > TextFrameIndex(1)) && bPrt + && ( aTmp != rInf.GetOut().GetMapMode().GetScaleX() ); + + if ( bSwitchL2R ) + rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos ); + + if ( bSwitchH2V ) + rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos ); + + // In the good old days we used to have a simple DrawText if the + // output device is the printer. Now we need a DrawTextArray if + // 1. KanaCompression is enabled + // 2. Justified alignment + // Simple kerning is handled by DrawStretchText + if( rInf.GetSpace() || rInf.GetKanaComp() ) + { + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())]); + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + + if( bStretch ) + { + sal_Int32 nZwi = sal_Int32(rInf.GetLen()) - 1; + long nDiff = rInf.GetWidth() - pKernArray[ nZwi ] + - sal_Int32(rInf.GetLen()) * rInf.GetKern(); + long nRest = nDiff % nZwi; + long nAdd; + if( nRest < 0 ) + { + nAdd = -1; + nRest += nZwi; + } + else + { + nAdd = +1; + nRest = nZwi - nRest; + } + nDiff /= nZwi; + long nSum = nDiff; + for( sal_Int32 i = 0; i < nZwi; ) + { + pKernArray[ i ] += nSum; + if( ++i == nRest ) + nDiff += nAdd; + nSum += nDiff; + } + } + + // Modify Array for special justifications + + long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR; + bool bSpecialJust = false; + + if ( rInf.GetFont() && rInf.GetLen() ) + { + const SwScriptInfo* pSI = rInf.GetScriptInfo(); + const SwFontScript nActual = rInf.GetFont()->GetActual(); + + // Kana Compression + if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() && + pSI && pSI->CountCompChg() && + lcl_IsMonoSpaceFont( rInf.GetOut() ) ) + { + pSI->Compress( pKernArray.get(), rInf.GetIdx(), rInf.GetLen(), + rInf.GetKanaComp(), + static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTextOriginPos ); + bSpecialJust = true; + } + + // Asian Justification + if ( SwFontScript::CJK == nActual && nSpaceAdd ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK ); + + if (!MsLangId::isKorean(aLang)) + { + SwScriptInfo::CJKJustify( rInf.GetText(), pKernArray.get(), nullptr, + rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() ); + + bSpecialJust = true; + nSpaceAdd = 0; + } + } + + // Kashida Justification + if ( SwFontScript::CTL == nActual && nSpaceAdd ) + { + if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) ) + { + if ( pSI && pSI->CountKashida() && + pSI->KashidaJustify( pKernArray.get(), nullptr, rInf.GetIdx(), + rInf.GetLen(), nSpaceAdd ) != -1 ) + { + bSpecialJust = true; + nSpaceAdd = 0; + } + } + } + + // Thai Justification + if ( SwFontScript::CTL == nActual && nSpaceAdd ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL ); + + if ( LANGUAGE_THAI == aLang ) + { + // Use rInf.GetSpace() because it has more precision than + // nSpaceAdd: + SwScriptInfo::ThaiJustify( rInf.GetText(), pKernArray.get(), nullptr, + rInf.GetIdx(), rInf.GetLen(), + rInf.GetNumberOfBlanks(), + rInf.GetSpace() ); + + // adding space to blanks is already done + bSpecialJust = true; + nSpaceAdd = 0; + } + } + } + + long nKernSum = rInf.GetKern(); + + if ( bStretch || m_bPaintBlank || rInf.GetKern() || bSpecialJust ) + { + for (sal_Int32 i = 0; i < sal_Int32(rInf.GetLen()); i++, + nKernSum += rInf.GetKern() ) + { + if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i]) + nKernSum += nSpaceAdd; + pKernArray[i] += nKernSum; + } + + // In case of underlined/strike-through justified text + // a blank at the end requires special handling: + if( m_bPaintBlank && rInf.GetLen() && ( CH_BLANK == + rInf.GetText()[sal_Int32(rInf.GetIdx() + rInf.GetLen())-1])) + { + // If it is a single underlined space, output 2 spaces: + if (TextFrameIndex(1) == rInf.GetLen()) + { + pKernArray[0] = rInf.GetWidth() + nSpaceAdd; + + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), 1 ); + } + else + { + pKernArray[ sal_Int32(rInf.GetLen()) - 2 ] += nSpaceAdd; + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + } + else + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + else + { + Point aTmpPos( aTextOriginPos ); + sal_Int32 j = 0; + sal_Int32 i; + for( i = 0; i < sal_Int32(rInf.GetLen()); i++ ) + { + if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i]) + { + nKernSum += nSpaceAdd; + if( j < i ) + rInf.GetOut().DrawText( aTmpPos, rInf.GetText(), + sal_Int32(rInf.GetIdx()) + j, i - j); + j = i + 1; + SwTwips nAdd = pKernArray[ i ] + nKernSum; + if ( ( ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl ) == nMode ) + nAdd *= -1; + aTmpPos.setX( aTextOriginPos.X() + nAdd ); + } + } + if( j < i ) + rInf.GetOut().DrawText( aTmpPos, rInf.GetText(), + sal_Int32(rInf.GetIdx()) + j, i - j); + } + } + else if( bStretch ) + { + long nTmpWidth = rInf.GetWidth(); + if( rInf.GetKern() && rInf.GetLen() && nTmpWidth > rInf.GetKern() ) + nTmpWidth -= rInf.GetKern(); + rInf.GetOut().DrawStretchText( aTextOriginPos, nTmpWidth, + rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + else if( rInf.GetKern() ) + { + const long nTmpWidth = GetTextSize( rInf ).Width(); + + const Color aSaveColor( pTmpFont->GetColor() ); + const bool bColorChanged = rInf.ApplyAutoColor( pTmpFont ); + + if( bColorChanged ) + { + if( !pTmpFont->IsSameInstance( rInf.GetOut().GetFont() ) ) + rInf.GetOut().SetFont( *pTmpFont ); + pTmpFont->SetColor( aSaveColor ); + } + + rInf.GetOut().DrawStretchText( aTextOriginPos, nTmpWidth, + rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + else + rInf.GetOut().DrawText( aTextOriginPos, rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + + // PAINTING WITH FORMATTING DEVICE/SCREEN ADJUSTMENT + + else + { + const OUString* pStr = &rInf.GetText(); + + OUString aStr; + OUString aBulletOverlay; + bool bBullet = rInf.GetBullet(); + if( m_bSymbol ) + bBullet = false; + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())]); + CreateScrFont( *rInf.GetShell(), rInf.GetOut() ); + long nScrPos; + + // get screen array + std::unique_ptr<long[]> pScrArray(new long[sal_Int32(rInf.GetLen())]); + SwTextGlyphsKey aGlyphsKey{ &rInf.GetOut(), rInf.GetText(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()) }; + SalLayoutGlyphs* pGlyphs = lcl_CreateLayout(aGlyphsKey, m_aTextGlyphs[aGlyphsKey]); + rInf.GetOut().GetTextArray( rInf.GetText(), pScrArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()), nullptr, pGlyphs); + + // OLE: no printer available + // OSL_ENSURE( pPrinter, "DrawText needs pPrinter" ) + if ( m_pPrinter ) + { + // pTmpFont has already been set as current font for rInf.GetOut() + if ( m_pPrinter.get() != rInf.GetpOut() || pTmpFont != m_pPrtFont ) + { + if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) + m_pPrinter->SetFont( *m_pPrtFont ); + } + aGlyphsKey = SwTextGlyphsKey{ m_pPrinter, rInf.GetText(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()) }; + pGlyphs = lcl_CreateLayout(aGlyphsKey, m_aTextGlyphs[aGlyphsKey]); + m_pPrinter->GetTextArray(rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()), nullptr, pGlyphs); + } + else + { + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + + // Modify Printer and ScreenArrays for special justifications + + long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR; + bool bNoHalfSpace = false; + + if ( rInf.GetFont() && rInf.GetLen() ) + { + const SwFontScript nActual = rInf.GetFont()->GetActual(); + const SwScriptInfo* pSI = rInf.GetScriptInfo(); + + // Kana Compression + if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() && + pSI && pSI->CountCompChg() && + lcl_IsMonoSpaceFont( rInf.GetOut() ) ) + { + Point aTmpPos( aTextOriginPos ); + pSI->Compress( pScrArray.get(), rInf.GetIdx(), rInf.GetLen(), + rInf.GetKanaComp(), + static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTmpPos ); + pSI->Compress( pKernArray.get(), rInf.GetIdx(), rInf.GetLen(), + rInf.GetKanaComp(), + static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTextOriginPos ); + } + + // Asian Justification + if ( SwFontScript::CJK == nActual && nSpaceAdd ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK ); + + if (!MsLangId::isKorean(aLang)) + { + SwScriptInfo::CJKJustify( rInf.GetText(), pKernArray.get(), pScrArray.get(), + rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() ); + + nSpaceAdd = 0; + } + } + + // Kashida Justification + if ( SwFontScript::CTL == nActual && nSpaceAdd ) + { + if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) ) + { + if ( pSI && pSI->CountKashida() && + pSI->KashidaJustify( pKernArray.get(), pScrArray.get(), rInf.GetIdx(), + rInf.GetLen(), nSpaceAdd ) != -1 ) + nSpaceAdd = 0; + else + bNoHalfSpace = true; + } + } + + // Thai Justification + if ( SwFontScript::CTL == nActual && nSpaceAdd ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL ); + + if ( LANGUAGE_THAI == aLang ) + { + SwScriptInfo::ThaiJustify( rInf.GetText(), pKernArray.get(), + pScrArray.get(), rInf.GetIdx(), + rInf.GetLen(), + rInf.GetNumberOfBlanks(), + rInf.GetSpace() ); + + // adding space to blanks is already done + nSpaceAdd = 0; + } + } + } + + nScrPos = pScrArray[ 0 ]; + + if( bBullet ) + { + // !!! HACK !!! + // The Arabic layout engine requires some context of the string + // which should be painted. + sal_Int32 nCopyStart = sal_Int32(rInf.GetIdx()); + if ( nCopyStart ) + --nCopyStart; + + sal_Int32 nCopyLen = sal_Int32(rInf.GetLen()); + if ( nCopyStart + nCopyLen < rInf.GetText().getLength() ) + ++nCopyLen; + + aStr = rInf.GetText().copy( nCopyStart, nCopyLen ); + pStr = &aStr; + + aBulletOverlay = rInf.GetText().copy( nCopyStart, nCopyLen ); + + for( sal_Int32 i = 0; i < aBulletOverlay.getLength(); ++i ) + if( CH_BLANK == aBulletOverlay[ i ] ) + { + /* fdo#72488 Hack: try to see if the space is zero width + * and don't bother with inserting a bullet in this case. + */ + if ((i + nCopyStart + 1 >= sal_Int32(rInf.GetLen())) || + pKernArray[i + nCopyStart] != pKernArray[ i + nCopyStart + 1]) + { + aBulletOverlay = aBulletOverlay.replaceAt(i, 1, OUString(CH_BULLET)); + } + else + { + aBulletOverlay = aBulletOverlay.replaceAt(i, 1, OUString(CH_BLANK)); + } + } + else + { + aBulletOverlay = aBulletOverlay.replaceAt(i, 1, OUString(CH_BLANK)); + } + } + + TextFrameIndex nCnt(rInf.GetText().getLength()); + if ( nCnt < rInf.GetIdx() ) + assert(false); // layout bug, not handled below + else + nCnt = nCnt - rInf.GetIdx(); + nCnt = std::min(nCnt, rInf.GetLen()); + long nKernSum = rInf.GetKern(); + sal_Unicode cChPrev = rInf.GetText()[sal_Int32(rInf.GetIdx())]; + + // In case of a single underlined space in justified text, + // have to output 2 spaces: + if ((nCnt == TextFrameIndex(1)) && rInf.GetSpace() && (cChPrev == CH_BLANK)) + { + pKernArray[0] = rInf.GetWidth() + + rInf.GetKern() + + ( rInf.GetSpace() / SPACING_PRECISION_FACTOR ); + + if ( bSwitchL2R ) + rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos ); + + if ( bSwitchH2V ) + rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos ); + + rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), + pKernArray.get(), sal_Int32(rInf.GetIdx()), 1 ); + if( bBullet ) + rInf.GetOut().DrawTextArray( aTextOriginPos, *pStr, pKernArray.get(), + rInf.GetIdx() ? 1 : 0, 1 ); + } + else + { + sal_Unicode nCh; + + // In case of Pair Kerning the printer influence on the positioning + // grows + const int nMul = m_pPrtFont->GetKerning() != FontKerning::NONE ? 1 : 3; + const int nDiv = nMul+1; + + // nSpaceSum contains the sum of the intermediate space distributed + // among Spaces by the Justification. + // The Spaces themselves will be positioned in the middle of the + // intermediate space, hence the nSpace/2. + // In case of word-by-word underlining they have to be positioned + // at the beginning of the intermediate space, so that the space + // is not underlined. + // A Space at the beginning or end of the text must be positioned + // before (resp. after) the whole intermediate space, otherwise + // the underline/strike-through would have gaps. + long nSpaceSum = 0; + // in word line mode and for Arabic, we disable the half space trick: + const long nHalfSpace = m_pPrtFont->IsWordLineMode() || bNoHalfSpace ? 0 : nSpaceAdd / 2; + const long nOtherHalf = nSpaceAdd - nHalfSpace; + if ( nSpaceAdd && ( cChPrev == CH_BLANK ) ) + nSpaceSum = nHalfSpace; + for (sal_Int32 i = 1; i < sal_Int32(nCnt); ++i, nKernSum += rInf.GetKern()) + { + nCh = rInf.GetText()[sal_Int32(rInf.GetIdx()) + i]; + + OSL_ENSURE( pScrArray, "Where is the screen array?" ); + long nScr; + nScr = pScrArray[ i ] - pScrArray[ i - 1 ]; + + // If there is an (ex-)Space before us, position optimally, + // i.e., our right margin to the 100% printer position; + // if we _are_ an ex-Space, position us left-aligned to the + // printer position. + if ( nCh == CH_BLANK ) + { + nScrPos = pKernArray[i-1] + nScr; + + if ( cChPrev == CH_BLANK ) + nSpaceSum += nOtherHalf; + if (i + 1 == sal_Int32(nCnt)) + nSpaceSum += nSpaceAdd; + else + nSpaceSum += nHalfSpace; + } + else + { + if ( cChPrev == CH_BLANK ) + { + nScrPos = pKernArray[i-1] + nScr; + // no Pixel is lost: + nSpaceSum += nOtherHalf; + } + else if ( cChPrev == '-' ) + nScrPos = pKernArray[i-1] + nScr; + else + { + nScrPos += nScr; + nScrPos = ( nMul * nScrPos + pKernArray[i] ) / nDiv; + } + } + cChPrev = nCh; + pKernArray[i-1] = nScrPos - nScr + nKernSum + nSpaceSum; + // In word line mode and for Arabic, we disabled the half space trick. If a portion + // ends with a blank, the full nSpaceAdd value has been added to the character in + // front of the blank. This leads to painting artifacts, therefore we remove the + // nSpaceAdd value again: + if ((bNoHalfSpace || m_pPrtFont->IsWordLineMode()) && i+1 == sal_Int32(nCnt) && nCh == CH_BLANK) + pKernArray[i-1] = pKernArray[i-1] - nSpaceAdd; + } + + // the layout engine requires the total width of the output + pKernArray[sal_Int32(rInf.GetLen()) - 1] += nKernSum + nSpaceSum; + + if( rInf.GetGreyWave() ) + { + if( rInf.GetLen() ) + { + long nHght = rInf.GetOut().LogicToPixel( + m_pPrtFont->GetFontSize() ).Height(); + if( WRONG_SHOW_MIN < nHght ) + { + if ( rInf.GetOut().GetConnectMetaFile() ) + rInf.GetOut().Push(); + + Color aCol( rInf.GetOut().GetLineColor() ); + bool bColSave = aCol != gWaveCol; + if ( bColSave ) + rInf.GetOut().SetLineColor( gWaveCol ); + + Point aEnd; + long nKernVal = pKernArray[sal_Int32(rInf.GetLen()) - 1]; + + const sal_uInt16 nDir = bBidiPor + ? 1800 + : UnMapDirection(GetFont().GetOrientation(), + bSwitchH2V, bSwitchH2VLRBT); + + switch ( nDir ) + { + case 0 : + aEnd.setX( rInf.GetPos().X() + nKernVal ); + aEnd.setY( rInf.GetPos().Y() ); + break; + case 900 : + aEnd.setX( rInf.GetPos().X() ); + aEnd.setY( rInf.GetPos().Y() - nKernVal ); + break; + case 1800 : + aEnd.setX( rInf.GetPos().X() - nKernVal ); + aEnd.setY( rInf.GetPos().Y() ); + break; + case 2700 : + aEnd.setX( rInf.GetPos().X() ); + aEnd.setY( rInf.GetPos().Y() + nKernVal ); + break; + } + + Point aCurrPos( rInf.GetPos() ); + + if ( bSwitchL2R ) + { + rInf.GetFrame()->SwitchLTRtoRTL( aCurrPos ); + rInf.GetFrame()->SwitchLTRtoRTL( aEnd ); + } + + if ( bSwitchH2V ) + { + rInf.GetFrame()->SwitchHorizontalToVertical( aCurrPos ); + rInf.GetFrame()->SwitchHorizontalToVertical( aEnd ); + } + { + vcl::ScopedAntialiasing a(rInf.GetOut(), true); + rInf.GetOut().DrawWaveLine( aCurrPos, aEnd ); + } + if ( bColSave ) + rInf.GetOut().SetLineColor( aCol ); + + if ( rInf.GetOut().GetConnectMetaFile() ) + rInf.GetOut().Pop(); + } + } + } + else if( !m_bSymbol && rInf.GetLen() ) + { + // anything to do? + if (rInf.GetWrong() || rInf.GetGrammarCheck() || rInf.GetSmartTags()) + { + CalcLinePosData aCalcLinePosData(rInf, GetFont(), nCnt, bSwitchH2V, + bSwitchH2VLRBT, bSwitchL2R, nHalfSpace, + pKernArray.get(), bBidiPor); + + SwForbidden aForbidden; + // draw line for smart tag data + lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetSmartTags(), aCalcLinePosData, Size() ); + // draw wave line for spell check errors + // draw them BEFORE the grammar check lines to 'override' the latter in case of conflict. + // reason: some grammar errors can only be found if spelling errors are fixed, + // therefore we don't want the user to miss a spelling error. + lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetWrong(), aCalcLinePosData, m_pPrtFont->GetFontSize() ); + // draw wave line for grammar check errors + lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetGrammarCheck(), aCalcLinePosData, m_pPrtFont->GetFontSize() ); + } + } + + sal_Int32 nLen = sal_Int32(rInf.GetLen()); + + if( nLen > 0 ) + { + + if ( bSwitchL2R ) + rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos ); + + if ( bSwitchH2V ) + rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos ); + + // If we paint bullets instead of spaces, we use a copy of + // the paragraph string. For the layout engine, the copy + // of the string has to be an environment of the range which + // is painted + sal_Int32 nTmpIdx = bBullet + ? (rInf.GetIdx() ? 1 : 0) + : sal_Int32(rInf.GetIdx()); + aGlyphsKey = SwTextGlyphsKey{ &rInf.GetOut(), *pStr, nTmpIdx, nLen }; + pGlyphs = lcl_CreateLayout(aGlyphsKey, m_aTextGlyphs[aGlyphsKey]); + rInf.GetOut().DrawTextArray( aTextOriginPos, *pStr, pKernArray.get(), + nTmpIdx , nLen, SalLayoutFlags::NONE, pGlyphs ); + if (bBullet) + { + rInf.GetOut().Push(); + Color aPreviousColor = pTmpFont->GetColor(); + + FontLineStyle aPreviousUnderline = pTmpFont->GetUnderline(); + FontLineStyle aPreviousOverline = pTmpFont->GetOverline(); + FontStrikeout aPreviousStrikeout = pTmpFont->GetStrikeout(); + + pTmpFont->SetColor( NON_PRINTING_CHARACTER_COLOR ); + pTmpFont->SetUnderline(LINESTYLE_NONE); + pTmpFont->SetOverline(LINESTYLE_NONE); + pTmpFont->SetStrikeout(STRIKEOUT_NONE); + rInf.GetOut().SetFont( *pTmpFont ); + long nShift = rInf.GetOut( ).GetFontMetric( ).GetBulletOffset( ); + if ( nShift ) + { + long nAdd = 0; + + if (aBulletOverlay.getLength() > nTmpIdx && + aBulletOverlay[ nTmpIdx ] == CH_BULLET ) + { + if (bSwitchH2V) + aTextOriginPos.AdjustY(nShift ) ; + else + aTextOriginPos.AdjustX(nShift ) ; + nAdd = nShift ; + } + for( sal_Int32 i = 1 ; i < nLen ; ++i ) + { + if ( aBulletOverlay[ i + nTmpIdx ] == CH_BULLET ) + pKernArray [ i - 1 ] += nShift ; + if ( nAdd ) + pKernArray [ i - 1 ] -= nAdd; + } + } + rInf.GetOut().DrawTextArray( aTextOriginPos, aBulletOverlay, pKernArray.get(), + nTmpIdx , nLen ); + pTmpFont->SetColor( aPreviousColor ); + + pTmpFont->SetUnderline(aPreviousUnderline); + pTmpFont->SetOverline(aPreviousOverline); + pTmpFont->SetStrikeout(aPreviousStrikeout); + rInf.GetOut().Pop(); + } + } + } + } +} + +Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) +{ + Size aTextSize; + const TextFrameIndex nLn = (TextFrameIndex(COMPLETE_STRING) != rInf.GetLen()) + ? rInf.GetLen() + : TextFrameIndex(rInf.GetText().getLength()); + + // be sure to have the correct layout mode at the printer + if ( m_pPrinter ) + { + m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() ); + m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() ); + } + + if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() && rInf.GetFont() && + SwFontScript::CJK == rInf.GetFont()->GetActual() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars() ) + { + const SwDoc* pDoc = rInf.GetShell()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc); + + OutputDevice* pOutDev; + + if ( m_pPrinter ) + { + if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) + m_pPrinter->SetFont(*m_pPrtFont); + pOutDev = m_pPrinter; + } + else + pOutDev = rInf.GetpOut(); + + aTextSize.setWidth( pOutDev->GetTextWidth(rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn)) ); + + OSL_ENSURE( !rInf.GetShell() || + ( USHRT_MAX != GetGuessedLeading() && USHRT_MAX != GetExternalLeading() ), + "Leading values should be already calculated" ); + aTextSize.setHeight( pOutDev->GetTextHeight() + + GetFontLeading( rInf.GetShell(), rInf.GetOut() ) ); + + long nAvgWidthPerChar = aTextSize.Width() / sal_Int32(nLn); + + const sal_uLong i = nAvgWidthPerChar ? + ( nAvgWidthPerChar - 1 ) / nGridWidth + 1: + 1; + + aTextSize.setWidth(i * nGridWidth * sal_Int32(nLn)); + rInf.SetKanaDiff( 0 ); + return aTextSize; + } + } + + //for textgrid refactor + if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() && rInf.GetFont() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && !pGrid->IsSnapToChars() ) + { + const long nGridWidthAdd = EvalGridWidthAdd( pGrid, rInf ); + OutputDevice* pOutDev; + if ( m_pPrinter ) + { + if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) + m_pPrinter->SetFont(*m_pPrtFont); + pOutDev = m_pPrinter; + } + else + pOutDev = rInf.GetpOut(); + aTextSize.setWidth(pOutDev->GetTextWidth(rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn))); + aTextSize.setHeight( pOutDev->GetTextHeight() + + GetFontLeading( rInf.GetShell(), rInf.GetOut() ) ); + aTextSize.AdjustWidth(sal_Int32(nLn) * nGridWidthAdd); + //if ( rInf.GetKern() && nLn ) + // aTextSize.Width() += ( nLn ) * long( rInf.GetKern() ); + + rInf.SetKanaDiff( 0 ); + return aTextSize; + } + } + + const bool bCompress = rInf.GetKanaComp() && nLn && + rInf.GetFont() && + SwFontScript::CJK == rInf.GetFont()->GetActual() && + rInf.GetScriptInfo() && + rInf.GetScriptInfo()->CountCompChg() && + lcl_IsMonoSpaceFont( rInf.GetOut() ); + + OSL_ENSURE( !bCompress || ( rInf.GetScriptInfo() && rInf.GetScriptInfo()-> + CountCompChg()), "Compression without info" ); + + // This is the part used e.g., for cursor travelling + // See condition for DrawText or DrawTextArray (bDirectPrint) + if ( m_pPrinter && m_pPrinter.get() != rInf.GetpOut() ) + { + if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) + m_pPrinter->SetFont(*m_pPrtFont); + aTextSize.setWidth( m_pPrinter->GetTextWidth( rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn))); + aTextSize.setHeight( m_pPrinter->GetTextHeight() ); + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(nLn)]); + CreateScrFont( *rInf.GetShell(), rInf.GetOut() ); + if( !GetScrFont()->IsSameInstance( rInf.GetOut().GetFont() ) ) + rInf.GetOut().SetFont( *m_pScrFont ); + long nScrPos; + + m_pPrinter->GetTextArray(rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn)); + if( bCompress ) + rInf.SetKanaDiff( rInf.GetScriptInfo()->Compress( pKernArray.get(), + rInf.GetIdx(), nLn, rInf.GetKanaComp(), + static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()) ,lcl_IsFullstopCentered( rInf.GetOut() ) ) ); + else + rInf.SetKanaDiff( 0 ); + + if ( rInf.GetKanaDiff() ) + nScrPos = pKernArray[ sal_Int32(nLn) - 1 ]; + else + { + std::unique_ptr<long[]> pScrArray(new long[sal_Int32(rInf.GetLen())]); + rInf.GetOut().GetTextArray( rInf.GetText(), pScrArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + nScrPos = pScrArray[ 0 ]; + TextFrameIndex nCnt(rInf.GetText().getLength()); + if ( nCnt < rInf.GetIdx() ) + nCnt = TextFrameIndex(0); // assert??? + else + nCnt = nCnt - rInf.GetIdx(); + nCnt = std::min(nCnt, nLn); + sal_Unicode nChPrev = rInf.GetText()[ sal_Int32(rInf.GetIdx()) ]; + + sal_Unicode nCh; + + // In case of Pair Kerning the printer influence on the positioning + // grows + const int nMul = m_pPrtFont->GetKerning() != FontKerning::NONE ? 1 : 3; + const int nDiv = nMul+1; + for (sal_Int32 i = 1; i < sal_Int32(nCnt); i++) + { + nCh = rInf.GetText()[ sal_Int32(rInf.GetIdx()) + i ]; + long nScr; + nScr = pScrArray[ i ] - pScrArray[ i - 1 ]; + if ( nCh == CH_BLANK ) + nScrPos = pKernArray[i-1]+nScr; + else + { + if ( nChPrev == CH_BLANK || nChPrev == '-' ) + nScrPos = pKernArray[i-1]+nScr; + else + { + nScrPos += nScr; + nScrPos = ( nMul * nScrPos + pKernArray[i] ) / nDiv; + } + } + nChPrev = nCh; + pKernArray[i-1] = nScrPos - nScr; + } + } + + pKernArray.reset(); + aTextSize.setWidth( nScrPos ); + } + else + { + if( !m_pPrtFont->IsSameInstance( rInf.GetOut().GetFont() ) ) + rInf.GetOut().SetFont( *m_pPrtFont ); + if( bCompress ) + { + std::unique_ptr<long[]> pKernArray( new long[sal_Int32(nLn)] ); + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn)); + rInf.SetKanaDiff( rInf.GetScriptInfo()->Compress( pKernArray.get(), + rInf.GetIdx(), nLn, rInf.GetKanaComp(), + static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()) ,lcl_IsFullstopCentered( rInf.GetOut() ) ) ); + aTextSize.setWidth( pKernArray[sal_Int32(nLn) - 1] ); + } + else + { + SwTextGlyphsKey aGlyphsKey{ &rInf.GetOut(), rInf.GetText(), sal_Int32(rInf.GetIdx()), sal_Int32(nLn) }; + SalLayoutGlyphs* pGlyphs = lcl_CreateLayout(aGlyphsKey, m_aTextGlyphs[aGlyphsKey]); + aTextSize.setWidth( rInf.GetOut().GetTextWidth( rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn), + rInf.GetVclCache(), pGlyphs) ); + rInf.SetKanaDiff( 0 ); + } + + aTextSize.setHeight( rInf.GetOut().GetTextHeight() ); + } + + if ( rInf.GetKern() && nLn ) + aTextSize.AdjustWidth((sal_Int32(nLn) - 1) * rInf.GetKern()); + + OSL_ENSURE( !rInf.GetShell() || + ( USHRT_MAX != GetGuessedLeading() && USHRT_MAX != GetExternalLeading() ), + "Leading values should be already calculated" ); + aTextSize.AdjustHeight(GetFontLeading( rInf.GetShell(), rInf.GetOut() ) ); + return aTextSize; +} + +TextFrameIndex SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf) +{ + long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR; + const long nSperren = -rInf.GetSperren() / SPACING_PRECISION_FACTOR; + long nKern = rInf.GetKern(); + + if( 0 != nSperren ) + nKern -= nSperren; + + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())]); + + // be sure to have the correct layout mode at the printer + if ( m_pPrinter ) + { + m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() ); + m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() ); + SwTextGlyphsKey aGlyphsKey{ m_pPrinter, rInf.GetText(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()) }; + SalLayoutGlyphs* pGlyphs = lcl_CreateLayout(aGlyphsKey, m_aTextGlyphs[aGlyphsKey]); + m_pPrinter->GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()), nullptr, pGlyphs); + } + else + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + + const SwScriptInfo* pSI = rInf.GetScriptInfo(); + if ( rInf.GetFont() && rInf.GetLen() ) + { + const SwFontScript nActual = rInf.GetFont()->GetActual(); + + // Kana Compression + if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() && + pSI && pSI->CountCompChg() && + lcl_IsMonoSpaceFont( rInf.GetOut() ) ) + { + pSI->Compress( pKernArray.get(), rInf.GetIdx(), rInf.GetLen(), + rInf.GetKanaComp(), + static_cast<sal_uInt16>(m_aFont.GetFontSize().Height()), + lcl_IsFullstopCentered( rInf.GetOut() ) ); + } + + // Asian Justification + if ( SwFontScript::CJK == rInf.GetFont()->GetActual() ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK ); + + if (!MsLangId::isKorean(aLang)) + { + SwScriptInfo::CJKJustify( rInf.GetText(), pKernArray.get(), nullptr, + rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() ); + + nSpaceAdd = 0; + } + + } + + // Kashida Justification + if ( SwFontScript::CTL == nActual && rInf.GetSpace() ) + { + if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) ) + { + if ( pSI && pSI->CountKashida() && + pSI->KashidaJustify( pKernArray.get(), nullptr, rInf.GetIdx(), rInf.GetLen(), + nSpaceAdd ) != -1 ) + nSpaceAdd = 0; + } + } + + // Thai Justification + if ( SwFontScript::CTL == nActual && nSpaceAdd ) + { + LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL ); + + if ( LANGUAGE_THAI == aLang ) + { + SwScriptInfo::ThaiJustify( rInf.GetText(), pKernArray.get(), nullptr, + rInf.GetIdx(), rInf.GetLen(), + rInf.GetNumberOfBlanks(), + rInf.GetSpace() ); + + // adding space to blanks is already done + nSpaceAdd = 0; + } + } + } + + long nLeft = 0; + long nRight = 0; + TextFrameIndex nCnt(0); + long nSpaceSum = 0; + long nKernSum = 0; + + if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() && + rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars() ) + { + const SwDoc* pDoc = rInf.GetShell()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc); + + long nAvgWidthPerChar = pKernArray[sal_Int32(rInf.GetLen()) - 1] / sal_Int32(rInf.GetLen()); + + sal_uLong i = nAvgWidthPerChar ? + ( nAvgWidthPerChar - 1 ) / nGridWidth + 1: + 1; + + nAvgWidthPerChar = i * nGridWidth; + +// stupid CLANG + nCnt = TextFrameIndex(rInf.GetOffset() / nAvgWidthPerChar); + if (2 * (rInf.GetOffset() - sal_Int32(nCnt) * nAvgWidthPerChar) > nAvgWidthPerChar) + ++nCnt; + + return nCnt; + } + } + + //for textgrid refactor + if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && !pGrid->IsSnapToChars() ) + { + + const long nGridWidthAdd = EvalGridWidthAdd( pGrid, rInf ); + + for (TextFrameIndex j(0); j < rInf.GetLen(); j++) + { + long nScr = pKernArray[sal_Int32(j)] + (nSpaceAdd + nGridWidthAdd) * (sal_Int32(j) + 1); + if( nScr >= rInf.GetOffset()) + { + nCnt = j; + break; + } + } + return nCnt; + } + } + + sal_Int32 nDone = 0; + TextFrameIndex nIdx = rInf.GetIdx(); + TextFrameIndex nLastIdx = nIdx; + const TextFrameIndex nEnd = rInf.GetIdx() + rInf.GetLen(); + + // #i105901# + // skip character cells for all script types + LanguageType aLang = rInf.GetFont()->GetLanguage(); + + while ( ( nRight < long( rInf.GetOffset() ) ) && ( nIdx < nEnd ) ) + { + if (nSpaceAdd && CH_BLANK == rInf.GetText()[ sal_Int32(nIdx)]) + nSpaceSum += nSpaceAdd; + + // go to next character (cell). + nLastIdx = nIdx; + + nIdx = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharacters( + rInf.GetText(), sal_Int32(nIdx), + g_pBreakIt->GetLocale( aLang ), + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone)); + if ( nIdx <= nLastIdx ) + break; + + nLeft = nRight; + nRight = pKernArray[sal_Int32(nIdx - rInf.GetIdx()) - 1] + nKernSum + nSpaceSum; + + nKernSum += nKern; + } + + // step back if position is before the middle of the character + // or if we do not want to go to the next character + if ( nIdx > rInf.GetIdx() && + ( rInf.IsPosMatchesBounds() || + ( ( nRight > long( rInf.GetOffset() ) ) && + ( nRight - rInf.GetOffset() > rInf.GetOffset() - nLeft ) ) ) ) + nCnt = nLastIdx - rInf.GetIdx(); // first half + else + nCnt = nIdx - rInf.GetIdx(); // second half + + if ( pSI ) + rInf.SetCursorBidiLevel( pSI->DirType( nLastIdx ) ); + + return nCnt; +} + +SwFntAccess::SwFntAccess( const void* & rnFontCacheId, + sal_uInt16 &rIndex, const void *pOwn, SwViewShell const *pSh, + bool bCheck ) : + SwCacheAccess( *pFntCache, rnFontCacheId, rIndex ), + m_pShell( pSh ) +{ + // the used ctor of SwCacheAccess searches for rnFontCacheId+rIndex in the cache + if ( m_pObj ) + { + // fast case: known Font (rnFontCacheId), no need to check printer and zoom + if ( !bCheck ) + return; + + // Font is known, but has to be checked + } + else + { // Font not known, must be searched + bCheck = false; + } + + { + OutputDevice* pOut = nullptr; + sal_uInt16 nZoom = USHRT_MAX; + + // Get the reference device + if ( pSh ) + { + pOut = &pSh->GetRefDev(); + nZoom = pSh->GetViewOptions()->GetZoom(); + } + + SwFntObj *pFntObj; + if ( bCheck ) + { + pFntObj = Get(); + if ( ( pFntObj->GetZoom( ) == nZoom ) && + ( pFntObj->m_pPrinter == pOut ) && + pFntObj->GetPropWidth() == + static_cast<SwSubFont const *>(pOwn)->GetPropWidth() ) + { + return; // result of Check: Drucker+Zoom okay. + } + pFntObj->Unlock(); // forget this object, printer/zoom differs + m_pObj = nullptr; + } + + // Search by font comparison, quite expensive! + // Look for same font and same printer + pFntObj = pFntCache->First(); + while ( pFntObj && !( pFntObj->m_aFont == *static_cast<vcl::Font const *>(pOwn) && + pFntObj->GetZoom() == nZoom && + pFntObj->GetPropWidth() == + static_cast<SwSubFont const *>(pOwn)->GetPropWidth() && + ( !pFntObj->m_pPrinter || pFntObj->m_pPrinter == pOut ) ) ) + pFntObj = SwFntCache::Next( pFntObj ); + + if( pFntObj && pFntObj->m_pPrinter.get() != pOut ) + { + // found one without printer, let's see if there is one with + // the same printer as well + SwFntObj *pTmpObj = pFntObj; + while( pTmpObj && !( pTmpObj->m_aFont == *static_cast<vcl::Font const *>(pOwn) && + pTmpObj->GetZoom()==nZoom && pTmpObj->m_pPrinter==pOut && + pTmpObj->GetPropWidth() == + static_cast<SwSubFont const *>(pOwn)->GetPropWidth() ) ) + pTmpObj = SwFntCache::Next( pTmpObj ); + if( pTmpObj ) + pFntObj = pTmpObj; + } + + if ( !pFntObj ) // Font has not been found, create one + { + // Have to create new Object, hence Owner must be a SwFont, later + // the Owner will be the "MagicNumber" + SwCacheAccess::m_pOwner = pOwn; + pFntObj = Get(); // will create via NewObj() and lock + OSL_ENSURE(pFntObj, "No Font, no Fun."); + } + else // Font has been found, so we lock it. + { + pFntObj->Lock(); + if (pFntObj->m_pPrinter.get() != pOut) // if no printer is known by now + { + OSL_ENSURE( !pFntObj->m_pPrinter, "SwFntAccess: Printer Changed" ); + pFntObj->CreatePrtFont( *pOut ); + pFntObj->m_pPrinter = pOut; + pFntObj->m_pScrFont = nullptr; + pFntObj->m_nGuessedLeading = USHRT_MAX; + pFntObj->m_nExtLeading = USHRT_MAX; + pFntObj->m_nPrtAscent = USHRT_MAX; + pFntObj->m_nPrtHeight = USHRT_MAX; + } + m_pObj = pFntObj; + } + + // no matter if new or found, now the Owner of the Object is a + // MagicNumber, and will be given to the SwFont, as well as the Index + // for later direct access + rnFontCacheId = reinterpret_cast<void*>(reinterpret_cast<sal_IntPtr>(pFntObj->GetOwner())); + SwCacheAccess::m_pOwner = pFntObj->GetOwner(); + rIndex = pFntObj->GetCachePos(); + } +} + +SwCacheObj *SwFntAccess::NewObj( ) +{ + // "MagicNumber" used to identify Fonts + static std::uintptr_t fontCacheIdCounter = 0; + // a new Font, a new "MagicNumber". + return new SwFntObj( *static_cast<SwSubFont const *>(m_pOwner), ++fontCacheIdCounter, m_pShell ); +} + +TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const & rInf, long nTextWidth) +{ + ChgFnt( rInf.GetShell(), rInf.GetOut() ); + + const bool bCompress = rInf.GetKanaComp() && rInf.GetLen() && + SwFontScript::CJK == GetActual() && + rInf.GetScriptInfo() && + rInf.GetScriptInfo()->CountCompChg() && + lcl_IsMonoSpaceFont( rInf.GetOut() ); + + OSL_ENSURE( !bCompress || ( rInf.GetScriptInfo() && rInf.GetScriptInfo()-> + CountCompChg()), "Compression without info" ); + + TextFrameIndex nTextBreak(0); + long nKern = 0; + + TextFrameIndex nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING) + ? TextFrameIndex(rInf.GetText().getLength()) : rInf.GetLen(); + + if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() && + rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars() ) + { + const SwDoc* pDoc = rInf.GetShell()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc); + + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())]); + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + + long nAvgWidthPerChar = pKernArray[sal_Int32(rInf.GetLen()) - 1] / sal_Int32(rInf.GetLen()); + + const sal_uLong i = nAvgWidthPerChar ? + ( nAvgWidthPerChar - 1 ) / nGridWidth + 1: + 1; + + nAvgWidthPerChar = i * nGridWidth; + long nCurrPos = nAvgWidthPerChar; + + while( nTextBreak < rInf.GetLen() && nTextWidth >= nCurrPos ) + { + nCurrPos += nAvgWidthPerChar; + ++nTextBreak; + } + + return nTextBreak + rInf.GetIdx(); + } + } + + //for text grid enhancement + if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() ) + { + SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame())); + if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && !pGrid->IsSnapToChars() ) + { + const long nGridWidthAdd = EvalGridWidthAdd( pGrid, rInf ); + + std::unique_ptr<long[]> pKernArray(new long[sal_Int32(rInf.GetLen())] ); + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + long nCurrPos = pKernArray[sal_Int32(nTextBreak)] + nGridWidthAdd; + while (++nTextBreak < rInf.GetLen() && nTextWidth >= nCurrPos) + { + nCurrPos = pKernArray[sal_Int32(nTextBreak)] + nGridWidthAdd * (sal_Int32(nTextBreak) + 1); + } + return nTextBreak + rInf.GetIdx(); + } + } + + if( m_aSub[m_nActual].IsCapital() && nLn ) + { + nTextBreak = GetCapitalBreak( rInf.GetShell(), rInf.GetpOut(), + rInf.GetScriptInfo(), rInf.GetText(), nTextWidth, rInf.GetIdx(), + nLn ); + } + else + { + nKern = CheckKerning(); + + const OUString* pTmpText; + OUString aTmpText; + TextFrameIndex nTmpIdx; + TextFrameIndex nTmpLen; + bool bTextReplaced = false; + + if ( !m_aSub[m_nActual].IsCaseMap() ) + { + pTmpText = &rInf.GetText(); + nTmpIdx = rInf.GetIdx(); + nTmpLen = nLn; + } + else + { + const OUString aSnippet(rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(nLn))); + aTmpText = m_aSub[m_nActual].CalcCaseMap( aSnippet ); + const bool bTitle = SvxCaseMap::Capitalize == m_aSub[m_nActual].GetCaseMap(); + + // Uaaaaahhhh!!! In title case mode, we would get wrong results + if ( bTitle && nLn ) + { + // check if rInf.GetIdx() is begin of word + if ( !g_pBreakIt->GetBreakIter()->isBeginWord( + rInf.GetText(), sal_Int32(rInf.GetIdx()), + g_pBreakIt->GetLocale( m_aSub[m_nActual].GetLanguage() ), + i18n::WordType::ANYWORD_IGNOREWHITESPACES ) ) + { + // In this case, the beginning of aTmpText is wrong. + OUString aSnippetTmp(aSnippet.copy(0, 1)); + aSnippetTmp = m_aSub[m_nActual].CalcCaseMap( aSnippetTmp ); + aTmpText = aTmpText.replaceAt( 0, aSnippetTmp.getLength(), OUString(aSnippet[0]) ); + } + } + + pTmpText = &aTmpText; + nTmpIdx = TextFrameIndex(0); + nTmpLen = TextFrameIndex(aTmpText.getLength()); + bTextReplaced = true; + } + + if( rInf.GetHyphPos() ) { + sal_Int32 nHyphPos = sal_Int32(*rInf.GetHyphPos()); + nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak( + *pTmpText, nTextWidth, + u'-', nHyphPos, + sal_Int32(nTmpIdx), sal_Int32(nTmpLen), + nKern, rInf.GetVclCache())); + *rInf.GetHyphPos() = TextFrameIndex((nHyphPos == -1) ? COMPLETE_STRING : nHyphPos); + } + else + { + SwFntAccess aFntAccess(m_aSub[m_nActual].m_nFontCacheId, m_aSub[m_nActual].m_nFontIndex, + &m_aSub[m_nActual], rInf.GetShell()); + SwTextGlyphsKey aGlyphsKey{ &rInf.GetOut(), *pTmpText, sal_Int32(nTmpIdx), sal_Int32(nTmpLen) }; + SalLayoutGlyphs* pGlyphs + = lcl_CreateLayout(aGlyphsKey, aFntAccess.Get()->GetTextGlyphs()[aGlyphsKey]); + nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak( + *pTmpText, nTextWidth, + sal_Int32(nTmpIdx), sal_Int32(nTmpLen), + nKern, rInf.GetVclCache(), pGlyphs)); + } + + if (bTextReplaced && sal_Int32(nTextBreak) != -1) + { + if ( nTmpLen != nLn ) + nTextBreak = sw_CalcCaseMap( *this, rInf.GetText(), + rInf.GetIdx(), nLn, nTextBreak ); + else + nTextBreak = nTextBreak + rInf.GetIdx(); + } + } + + TextFrameIndex nTextBreak2 = sal_Int32(nTextBreak) == -1 + ? TextFrameIndex(COMPLETE_STRING) + : nTextBreak; + + if ( ! bCompress ) + return nTextBreak2; + + nTextBreak2 = nTextBreak2 - rInf.GetIdx(); + + if( nTextBreak2 < nLn ) + { + if( !nTextBreak2 && nLn ) + nLn = TextFrameIndex(1); + else if (nLn > nTextBreak2 + nTextBreak2) + nLn = nTextBreak2 + nTextBreak2; + std::unique_ptr<long[]> pKernArray( new long[sal_Int32(nLn)] ); + rInf.GetOut().GetTextArray( rInf.GetText(), pKernArray.get(), + sal_Int32(rInf.GetIdx()), sal_Int32(nLn)); + if( rInf.GetScriptInfo()->Compress( pKernArray.get(), rInf.GetIdx(), nLn, + rInf.GetKanaComp(), static_cast<sal_uInt16>(GetHeight( m_nActual )), + lcl_IsFullstopCentered( rInf.GetOut() ) ) ) + { + long nKernAdd = nKern; + TextFrameIndex const nTmpBreak = nTextBreak2; + if( nKern && nTextBreak2 ) + nKern *= sal_Int32(nTextBreak2) - 1; + while (nTextBreak2 < nLn && nTextWidth >= pKernArray[sal_Int32(nTextBreak2)] + nKern) + { + nKern += nKernAdd; + ++nTextBreak2; + } + if( rInf.GetHyphPos() ) + *rInf.GetHyphPos() += nTextBreak2 - nTmpBreak; // It's not perfect + } + } + nTextBreak2 = nTextBreak2 + rInf.GetIdx(); + + return nTextBreak2; +} + +bool SwDrawTextInfo::ApplyAutoColor( vcl::Font* pFont ) +{ + const vcl::Font& rFnt = pFont ? *pFont : GetOut().GetFont(); + Color nNewColor = COL_BLACK; + bool bChgFntColor = false; + bool bChgLineColor = false; + + if (GetShell() && !GetShell()->GetWin() && GetShell()->GetViewOptions()->IsBlackFont()) + { + if ( COL_BLACK != rFnt.GetColor() ) + bChgFntColor = true; + + if ( (COL_BLACK != GetOut().GetLineColor()) || + (COL_BLACK != GetOut().GetOverlineColor()) ) + bChgLineColor = true; + } + else + { + // FontColor has to be changed if: + // 1. FontColor = AUTO or 2. IsAlwaysAutoColor is set + // LineColor has to be changed if: + // 1. IsAlwaysAutoColor is set + + bChgLineColor = GetShell() && GetShell()->GetWin() && + GetShell()->GetAccessibilityOptions()->IsAlwaysAutoColor(); + + bChgFntColor = COL_AUTO == rFnt.GetColor() || bChgLineColor; + + if ( bChgFntColor ) + { + // check if current background has a user defined setting + const Color* pCol = GetFont() ? GetFont()->GetBackColor() : nullptr; + Color aColor; + if( ! pCol || COL_TRANSPARENT == *pCol ) + { + const SvxBrushItem* pItem; + SwRect aOrigBackRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + /// OD 21.08.2002 + /// consider, that [GetBackgroundBrush(...)] can set <pCol> + /// - see implementation in /core/layout/paintfrm.cxx + /// OD 21.08.2002 #99657# + /// There is a user defined setting for the background, if there + /// is a background brush and its color is *not* "no fill"/"auto fill". + if( GetFrame()->GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/true ) ) + { + if (aFillAttributes && aFillAttributes->isUsed()) + { + // First see if fill attributes provide a color. + aColor = Color(aFillAttributes->getAverageColor(aGlobalRetoucheColor.getBColor())); + pCol = &aColor; + } + + // If not, then fall back to the old brush item. + if ( !pCol ) + { + pCol = &pItem->GetColor(); + } + + /// OD 30.08.2002 #99657# + /// determined color <pCol> can be <COL_TRANSPARENT>. Thus, check it. + if ( *pCol == COL_TRANSPARENT) + pCol = nullptr; + } + else + pCol = nullptr; + } + + // no user defined color at paragraph or font background + if ( ! pCol ) + pCol = &aGlobalRetoucheColor; + + if( GetShell() && GetShell()->GetWin() ) + { + // here we determine the preferred window text color for painting + const SwViewOption* pViewOption = GetShell()->GetViewOptions(); + if(pViewOption->IsPagePreview() && + !SW_MOD()->GetAccessibilityOptions().GetIsForPagePreviews()) + nNewColor = COL_BLACK; + else + // we take the font color from the appearance page + nNewColor = SwViewOption::GetFontColor(); + } + + // change painting color depending of dark/bright background + Color aTmpColor( nNewColor ); + if ( pCol->IsDark() && aTmpColor.IsDark() ) + nNewColor = COL_WHITE; + else if ( pCol->IsBright() && aTmpColor.IsBright() ) + nNewColor = COL_BLACK; + } + } + + if ( bChgFntColor || bChgLineColor ) + { + Color aNewColor( nNewColor ); + + if ( bChgFntColor ) + { + if ( pFont && aNewColor != pFont->GetColor() ) + { + // only set the new color at the font passed as argument + pFont->SetColor( aNewColor ); + } + else if ( aNewColor != GetOut().GetFont().GetColor() ) + { + // set new font with new color at output device + vcl::Font aFont( rFnt ); + aFont.SetColor( aNewColor ); + GetOut().SetFont( aFont ); + } + } + + // the underline and overline colors have to be set separately + if ( bChgLineColor ) + { + // get current font color or color set at output device + aNewColor = pFont ? pFont->GetColor() : GetOut().GetFont().GetColor(); + if ( aNewColor != GetOut().GetLineColor() ) + GetOut().SetLineColor( aNewColor ); + if ( aNewColor != GetOut().GetOverlineColor() ) + GetOut().SetOverlineColor( aNewColor ); + } + + return true; + } + + return false; +} + +void SwClearFntCacheTextGlyphs() +{ + for (SwFntObj* pFntObj = pFntCache->First(); pFntObj; pFntObj = SwFntCache::Next(pFntObj)) + pFntObj->GetTextGlyphs().clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/fntcap.cxx b/sw/source/core/txtnode/fntcap.cxx new file mode 100644 index 000000000..8ab9cf830 --- /dev/null +++ b/sw/source/core/txtnode/fntcap.cxx @@ -0,0 +1,778 @@ +/* -*- 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 <editeng/svxfont.hxx> + +#include <vcl/outdev.hxx> +#include <com/sun/star/i18n/CharType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> + +#include <fntcache.hxx> +#include <swfont.hxx> +#include <breakit.hxx> +#include <txtfrm.hxx> +#include <scriptinfo.hxx> +#include <fntcap.hxx> + +using namespace ::com::sun::star::i18n; + +namespace { + +// The information encapsulated in SwCapitalInfo is required +// by the ::Do functions. They contain the information about +// the original string, whereas rDo.GetInf() contains information +// about the display string. +class SwCapitalInfo +{ +public: + explicit SwCapitalInfo( const OUString& rOrigText ) : + rString( rOrigText ), nIdx( 0 ), nLen( 0 ) {}; + const OUString& rString; + TextFrameIndex nIdx; + TextFrameIndex nLen; +}; + +} + +// rFnt: required for CalcCaseMap +// rOrigString: The original string +// nOfst: Position of the substring in rOrigString +// nLen: Length if the substring in rOrigString +// nIdx: Refers to a position in the display string and should be mapped +// to a position in rOrigString +TextFrameIndex sw_CalcCaseMap(const SwFont& rFnt, + const OUString& rOrigString, + TextFrameIndex const nOfst, + TextFrameIndex const nLen, + TextFrameIndex const nIdx) +{ + int j = 0; + const TextFrameIndex nEnd = nOfst + nLen; + OSL_ENSURE( sal_Int32(nEnd) <= rOrigString.getLength(), "sw_CalcCaseMap: Wrong parameters" ); + + // special case for title case: + const bool bTitle = SvxCaseMap::Capitalize == rFnt.GetCaseMap(); + for (TextFrameIndex i = nOfst; i < nEnd; ++i) + { + OUString aTmp(rOrigString.copy(sal_Int32(i), 1)); + + if ( !bTitle || + g_pBreakIt->GetBreakIter()->isBeginWord( + rOrigString, sal_Int32(i), + g_pBreakIt->GetLocale( rFnt.GetLanguage() ), + WordType::ANYWORD_IGNOREWHITESPACES ) ) + aTmp = rFnt.GetActualFont().CalcCaseMap( aTmp ); + + j += aTmp.getLength(); + + if (TextFrameIndex(j) > nIdx) + return i; + } + + return nOfst + nLen; +} + +class SwDoCapitals +{ +protected: + SwDrawTextInfo &rInf; + SwCapitalInfo* pCapInf; // refers to additional information + // required by the ::Do function + explicit SwDoCapitals ( SwDrawTextInfo &rInfo ) : rInf( rInfo ), pCapInf( nullptr ) { } + ~SwDoCapitals() {} +public: + virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) = 0; + virtual void Do() = 0; + OutputDevice& GetOut() { return rInf.GetOut(); } + SwDrawTextInfo& GetInf() { return rInf; } + SwCapitalInfo* GetCapInf() const { return pCapInf; } + void SetCapInf( SwCapitalInfo& rNew ) { pCapInf = &rNew; } +}; + +namespace { + +class SwDoGetCapitalSize : public SwDoCapitals +{ +protected: + Size aTextSize; +public: + explicit SwDoGetCapitalSize( SwDrawTextInfo &rInfo ) : SwDoCapitals ( rInfo ) { } + virtual ~SwDoGetCapitalSize() {} + virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override; + virtual void Do() override; + const Size &GetSize() const { return aTextSize; } +}; + +} + +void SwDoGetCapitalSize::Init( SwFntObj *, SwFntObj * ) +{ + aTextSize.setHeight( 0 ); + aTextSize.setWidth( 0 ); +} + +void SwDoGetCapitalSize::Do() +{ + aTextSize.AdjustWidth(rInf.GetSize().Width() ); + if( rInf.GetUpper() ) + aTextSize.setHeight( rInf.GetSize().Height() ); +} + +Size SwSubFont::GetCapitalSize( SwDrawTextInfo& rInf ) +{ + // Start: + const long nOldKern = rInf.GetKern(); + rInf.SetKern( CheckKerning() ); + rInf.SetPos( Point() ); + rInf.SetSpace( 0 ); + rInf.SetDrawSpace( false ); + SwDoGetCapitalSize aDo( rInf ); + DoOnCapitals( aDo ); + Size aTextSize( aDo.GetSize() ); + + // End: + if( !aTextSize.Height() ) + { + SV_STAT( nGetTextSize ); + aTextSize.setHeight( short ( rInf.GetpOut()->GetTextHeight() ) ); + } + rInf.SetKern( nOldKern ); + return aTextSize; +} + +namespace { + +class SwDoGetCapitalBreak : public SwDoCapitals +{ +protected: + long nTextWidth; + TextFrameIndex m_nBreak; + +public: + SwDoGetCapitalBreak( SwDrawTextInfo &rInfo, long const nWidth) + : SwDoCapitals ( rInfo ) + , nTextWidth( nWidth ) + , m_nBreak( -1 ) + { } + virtual ~SwDoGetCapitalBreak() {} + virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override; + virtual void Do() override; + TextFrameIndex getBreak() const { return m_nBreak; } +}; + +} + +void SwDoGetCapitalBreak::Init( SwFntObj *, SwFntObj * ) +{ +} + +void SwDoGetCapitalBreak::Do() +{ + if ( nTextWidth ) + { + if ( rInf.GetSize().Width() < nTextWidth ) + nTextWidth -= rInf.GetSize().Width(); + else + { + TextFrameIndex nEnd = rInf.GetEnd(); + m_nBreak = TextFrameIndex(GetOut().GetTextBreak( + rInf.GetText(), nTextWidth, sal_Int32(rInf.GetIdx()), + sal_Int32(rInf.GetLen()), rInf.GetKern())); + + if (m_nBreak > nEnd || m_nBreak < TextFrameIndex(0)) + m_nBreak = nEnd; + + // m_nBreak may be relative to the display string. It has to be + // calculated relative to the original string: + if ( GetCapInf() ) + { + if ( GetCapInf()->nLen != rInf.GetLen() ) + m_nBreak = sw_CalcCaseMap( *rInf.GetFont(), + GetCapInf()->rString, + GetCapInf()->nIdx, + GetCapInf()->nLen, m_nBreak ); + else + m_nBreak = m_nBreak + GetCapInf()->nIdx; + } + + nTextWidth = 0; + } + } +} + +TextFrameIndex SwFont::GetCapitalBreak( SwViewShell const * pSh, const OutputDevice* pOut, + const SwScriptInfo* pScript, const OUString& rText, long const nTextWidth, + TextFrameIndex const nIdx, TextFrameIndex const nLen) +{ + // Start: + Point aPos( 0, 0 ); + SwDrawTextInfo aInfo(pSh, *const_cast<OutputDevice*>(pOut), pScript, rText, nIdx, nLen, + 0, false); + aInfo.SetPos( aPos ); + aInfo.SetSpace( 0 ); + aInfo.SetWrong( nullptr ); + aInfo.SetGrammarCheck( nullptr ); + aInfo.SetSmartTags( nullptr ); + aInfo.SetDrawSpace( false ); + aInfo.SetKern( CheckKerning() ); + aInfo.SetKanaComp( pScript ? 0 : 100 ); + aInfo.SetFont( this ); + + SwDoGetCapitalBreak aDo(aInfo, nTextWidth); + DoOnCapitals( aDo ); + return aDo.getBreak(); +} + +namespace { + +class SwDoDrawCapital : public SwDoCapitals +{ +protected: + SwFntObj *pUpperFnt; + SwFntObj *pLowerFnt; +public: + explicit SwDoDrawCapital( SwDrawTextInfo &rInfo ) : + SwDoCapitals( rInfo ), pUpperFnt(nullptr), pLowerFnt(nullptr) + { } + virtual ~SwDoDrawCapital() {} + virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override; + virtual void Do() override; + void DrawSpace( Point &rPos ); +}; + +} + +void SwDoDrawCapital::Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) +{ + pUpperFnt = pUpperFont; + pLowerFnt = pLowerFont; +} + +void SwDoDrawCapital::Do() +{ + SV_STAT( nDrawText ); + const sal_uInt16 nOrgWidth = rInf.GetWidth(); + rInf.SetWidth( sal_uInt16(rInf.GetSize().Width()) ); + if ( rInf.GetUpper() ) + pUpperFnt->DrawText( rInf ); + else + { + bool bOldBullet = rInf.GetBullet(); + rInf.SetBullet( false ); + pLowerFnt->DrawText( rInf ); + rInf.SetBullet( bOldBullet ); + } + + OSL_ENSURE( pUpperFnt, "No upper font, dying soon!"); + rInf.Shift( pUpperFnt->GetFont().GetOrientation() ); + rInf.SetWidth( nOrgWidth ); +} + +void SwDoDrawCapital::DrawSpace( Point &rPos ) +{ + long nDiff = rInf.GetPos().X() - rPos.X(); + + Point aPos( rPos ); + const bool bSwitchL2R = rInf.GetFrame()->IsRightToLeft() && + ! rInf.IsIgnoreFrameRTL(); + + if ( bSwitchL2R ) + rInf.GetFrame()->SwitchLTRtoRTL( aPos ); + + const ComplexTextLayoutFlags nMode = rInf.GetpOut()->GetLayoutMode(); + const bool bBidiPor = ( bSwitchL2R != + ( ComplexTextLayoutFlags::Default != ( ComplexTextLayoutFlags::BiDiRtl & nMode ) ) ); + + if ( bBidiPor ) + nDiff = -nDiff; + + if ( rInf.GetFrame()->IsVertical() ) + rInf.GetFrame()->SwitchHorizontalToVertical( aPos ); + + if ( nDiff ) + { + rInf.ApplyAutoColor(); + GetOut().DrawStretchText( aPos, nDiff, + " ", 0, 2 ); + } + rPos.setX( rInf.GetPos().X() + rInf.GetWidth() ); +} + +void SwSubFont::DrawCapital( SwDrawTextInfo &rInf ) +{ + // Precondition: rInf.GetPos() has already been calculated + + rInf.SetDrawSpace( GetUnderline() != LINESTYLE_NONE || + GetOverline() != LINESTYLE_NONE || + GetStrikeout() != STRIKEOUT_NONE ); + SwDoDrawCapital aDo( rInf ); + DoOnCapitals( aDo ); +} + +namespace { + +class SwDoCapitalCursorOfst : public SwDoCapitals +{ +protected: + SwFntObj *pUpperFnt; + SwFntObj *pLowerFnt; + TextFrameIndex nCursor; + sal_uInt16 nOfst; +public: + SwDoCapitalCursorOfst( SwDrawTextInfo &rInfo, const sal_uInt16 nOfs ) : + SwDoCapitals( rInfo ), pUpperFnt(nullptr), pLowerFnt(nullptr), nCursor( 0 ), nOfst( nOfs ) + { } + virtual ~SwDoCapitalCursorOfst() {} + virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override; + virtual void Do() override; + + TextFrameIndex GetCursor() const { return nCursor; } +}; + +} + +void SwDoCapitalCursorOfst::Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) +{ + pUpperFnt = pUpperFont; + pLowerFnt = pLowerFont; +} + +void SwDoCapitalCursorOfst::Do() +{ + if ( nOfst ) + { + if ( static_cast<long>(nOfst) > rInf.GetSize().Width() ) + { + nOfst -= rInf.GetSize().Width(); + nCursor = nCursor + rInf.GetLen(); + } + else + { + SwDrawTextInfo aDrawInf( rInf.GetShell(), *rInf.GetpOut(), + rInf.GetScriptInfo(), + rInf.GetText(), + rInf.GetIdx(), + rInf.GetLen(), 0, false ); + aDrawInf.SetOffset( nOfst ); + aDrawInf.SetKern( rInf.GetKern() ); + aDrawInf.SetKanaComp( rInf.GetKanaComp() ); + aDrawInf.SetFrame( rInf.GetFrame() ); + aDrawInf.SetFont( rInf.GetFont() ); + + if ( rInf.GetUpper() ) + { + aDrawInf.SetSpace( 0 ); + nCursor = nCursor + pUpperFnt->GetModelPositionForViewPoint( aDrawInf ); + } + else + { + aDrawInf.SetSpace( rInf.GetSpace() ); + nCursor = nCursor + pLowerFnt->GetModelPositionForViewPoint( aDrawInf ); + } + nOfst = 0; + } + } +} + +TextFrameIndex SwSubFont::GetCapitalCursorOfst( SwDrawTextInfo& rInf ) +{ + const long nOldKern = rInf.GetKern(); + rInf.SetKern( CheckKerning() ); + SwDoCapitalCursorOfst aDo( rInf, rInf.GetOffset() ); + rInf.SetPos( Point() ); + rInf.SetDrawSpace( false ); + DoOnCapitals( aDo ); + rInf.SetKern( nOldKern ); + return aDo.GetCursor(); +} + +namespace { + +class SwDoDrawStretchCapital : public SwDoDrawCapital +{ + const TextFrameIndex nStrLen; + const sal_uInt16 nCapWidth; + const sal_uInt16 nOrgWidth; +public: + virtual void Do() override; + + SwDoDrawStretchCapital( SwDrawTextInfo &rInfo, const sal_uInt16 nCapitalWidth ) + : SwDoDrawCapital( rInfo ), + nStrLen( rInfo.GetLen() ), + nCapWidth( nCapitalWidth ), + nOrgWidth( rInfo.GetWidth() ) + { } +}; + +} + +void SwDoDrawStretchCapital::Do() +{ + SV_STAT( nDrawStretchText ); + long nPartWidth = rInf.GetSize().Width(); + + if( rInf.GetLen() ) + { + // small caps and kerning + long nDiff = long(nOrgWidth) - long(nCapWidth); + if( nDiff ) + { + nDiff *= sal_Int32(rInf.GetLen()); + nDiff /= sal_Int32(nStrLen); + nDiff += nPartWidth; + if( 0 < nDiff ) + nPartWidth = nDiff; + } + + rInf.ApplyAutoColor(); + + Point aPos( rInf.GetPos() ); + const bool bSwitchL2R = rInf.GetFrame()->IsRightToLeft() && + ! rInf.IsIgnoreFrameRTL(); + + if ( bSwitchL2R ) + rInf.GetFrame()->SwitchLTRtoRTL( aPos ); + + if ( rInf.GetFrame()->IsVertical() ) + rInf.GetFrame()->SwitchHorizontalToVertical( aPos ); + + // Optimise: + if (TextFrameIndex(1) >= rInf.GetLen()) + GetOut().DrawText(aPos, rInf.GetText(), sal_Int32(rInf.GetIdx()), + sal_Int32(rInf.GetLen())); + else + GetOut().DrawStretchText(aPos, nPartWidth, rInf.GetText(), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + const_cast<Point&>(rInf.GetPos()).AdjustX(nPartWidth ); +} + +void SwSubFont::DrawStretchCapital( SwDrawTextInfo &rInf ) +{ + // Precondition: rInf.GetPos() has already been calculated + + if (rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)) + rInf.SetLen(TextFrameIndex(rInf.GetText().getLength())); + + const Point aOldPos = rInf.GetPos(); + const sal_uInt16 nCapWidth = static_cast<sal_uInt16>( GetCapitalSize( rInf ).Width() ); + rInf.SetPos(aOldPos); + + rInf.SetDrawSpace( GetUnderline() != LINESTYLE_NONE || + GetOverline() != LINESTYLE_NONE || + GetStrikeout() != STRIKEOUT_NONE ); + SwDoDrawStretchCapital aDo( rInf, nCapWidth ); + DoOnCapitals( aDo ); +} + +void SwSubFont::DoOnCapitals( SwDoCapitals &rDo ) +{ + OSL_ENSURE( pLastFont, "SwFont::DoOnCapitals: No LastFont?!" ); + + long nKana = 0; + const OUString aText( CalcCaseMap( rDo.GetInf().GetText() ) ); + TextFrameIndex nMaxPos = std::min( + TextFrameIndex(rDo.GetInf().GetText().getLength()) - rDo.GetInf().GetIdx(), + rDo.GetInf().GetLen() ); + rDo.GetInf().SetLen( nMaxPos ); + + const OUString oldText = rDo.GetInf().GetText(); + rDo.GetInf().SetText( aText ); + TextFrameIndex nPos = rDo.GetInf().GetIdx(); + TextFrameIndex nOldPos = nPos; + nMaxPos = nMaxPos + nPos; + + // Look if the length of the original text and the ToUpper-converted + // text is different. If yes, do special handling. + SwCapitalInfo aCapInf(oldText); + bool bCaseMapLengthDiffers(aText.getLength() != oldText.getLength()); + if ( bCaseMapLengthDiffers ) + rDo.SetCapInf( aCapInf ); + + SwFntObj *pOldLast = pLastFont; + std::unique_ptr<SwFntAccess> pBigFontAccess; + SwFntObj *pBigFont; + std::unique_ptr<SwFntAccess> pSpaceFontAccess; + SwFntObj *pSpaceFont = nullptr; + + const void* nFontCacheId2 = nullptr; + sal_uInt16 nIndex2 = 0; + SwSubFont aFont( *this ); + Point aStartPos( rDo.GetInf().GetPos() ); + + const bool bTextLines = aFont.GetUnderline() != LINESTYLE_NONE + || aFont.GetOverline() != LINESTYLE_NONE + || aFont.GetStrikeout() != STRIKEOUT_NONE; + const bool bWordWise = bTextLines && aFont.IsWordLineMode() && + rDo.GetInf().GetDrawSpace(); + const long nTmpKern = rDo.GetInf().GetKern(); + + if ( bTextLines ) + { + if ( bWordWise ) + { + aFont.SetWordLineMode( false ); + pSpaceFontAccess.reset(new SwFntAccess( nFontCacheId2, nIndex2, &aFont, + rDo.GetInf().GetShell() )); + pSpaceFont = pSpaceFontAccess->Get(); + } + else + pSpaceFont = pLastFont; + + // Construct a font for the capitals: + aFont.SetUnderline( LINESTYLE_NONE ); + aFont.SetOverline( LINESTYLE_NONE ); + aFont.SetStrikeout( STRIKEOUT_NONE ); + nFontCacheId2 = nullptr; + nIndex2 = 0; + pBigFontAccess.reset(new SwFntAccess( nFontCacheId2, nIndex2, &aFont, + rDo.GetInf().GetShell() )); + pBigFont = pBigFontAccess->Get(); + } + else + pBigFont = pLastFont; + + // Older LO versions had 66 as the small caps percentage size, later changed to 80, + // therefore a backwards compatibility option is kept (otherwise layout is changed). + // NOTE: There are more uses of SMALL_CAPS_PERCENTAGE in editeng, but it seems they + // do not matter for Writer (and if they did it'd be pretty ugly to propagate + // the option there). + int smallCapsPercentage = m_bSmallCapsPercentage66 ? 66 : SMALL_CAPS_PERCENTAGE; + aFont.SetProportion( (aFont.GetPropr() * smallCapsPercentage ) / 100 ); + nFontCacheId2 = nullptr; + nIndex2 = 0; + std::unique_ptr<SwFntAccess> pSmallFontAccess( new SwFntAccess( nFontCacheId2, nIndex2, &aFont, + rDo.GetInf().GetShell() )); + SwFntObj *pSmallFont = pSmallFontAccess->Get(); + + rDo.Init( pBigFont, pSmallFont ); + OutputDevice* pOutSize = pSmallFont->GetPrt(); + if( !pOutSize ) + pOutSize = &rDo.GetOut(); + OutputDevice* pOldOut = &rDo.GetOut(); + + const LanguageType eLng = LANGUAGE_DONTKNOW == GetLanguage() + ? LANGUAGE_SYSTEM : GetLanguage(); + + if( nPos < nMaxPos ) + { + nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfCharBlock( + oldText, sal_Int32(nPos), + g_pBreakIt->GetLocale( eLng ), CharType::LOWERCASE_LETTER)); + if (nPos < TextFrameIndex(0)) + nPos = nOldPos; + else if( nPos > nMaxPos ) + nPos = nMaxPos; + } + + while( nOldPos < nMaxPos ) + { + + // The lower ones... + if( nOldPos != nPos ) + { + SV_STAT( nGetTextSize ); + pLastFont = pSmallFont; + pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() ); + + // #107816#, #i14820# + if( bCaseMapLengthDiffers ) + { + // Build an own 'changed' string for the given part of the + // source string and use it. That new string may differ in length + // from the source string. + const OUString aNewText = CalcCaseMap( + oldText.copy(sal_Int32(nOldPos), sal_Int32(nPos-nOldPos))); + aCapInf.nIdx = nOldPos; + aCapInf.nLen = nPos - nOldPos; + rDo.GetInf().SetIdx(TextFrameIndex(0)); + rDo.GetInf().SetLen(TextFrameIndex(aNewText.getLength())); + rDo.GetInf().SetText( aNewText ); + } + else + { + rDo.GetInf().SetIdx( nOldPos ); + rDo.GetInf().SetLen( nPos - nOldPos ); + } + + rDo.GetInf().SetUpper( false ); + rDo.GetInf().SetOut( *pOutSize ); + Size aPartSize = pSmallFont->GetTextSize( rDo.GetInf() ); + nKana += rDo.GetInf().GetKanaDiff(); + rDo.GetInf().SetOut( *pOldOut ); + if( nTmpKern && nPos < nMaxPos ) + aPartSize.AdjustWidth(nTmpKern ); + rDo.GetInf().SetSize( aPartSize ); + rDo.Do(); + nOldPos = nPos; + } + nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharBlock( + oldText, sal_Int32(nPos), + g_pBreakIt->GetLocale( eLng ), CharType::LOWERCASE_LETTER)); + if (nPos < TextFrameIndex(0) || nPos > nMaxPos) + nPos = nMaxPos; + OSL_ENSURE( nPos, "nextCharBlock not implemented?" ); +#if OSL_DEBUG_LEVEL > 1 + if( !nPos ) + nPos = nMaxPos; +#endif + // The upper ones... + if( nOldPos != nPos ) + { + const long nSpaceAdd = rDo.GetInf().GetSpace() / SPACING_PRECISION_FACTOR; + + do + { + rDo.GetInf().SetUpper( true ); + pLastFont = pBigFont; + pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() ); + TextFrameIndex nTmp; + if( bWordWise ) + { + nTmp = nOldPos; + while (nTmp < nPos && CH_BLANK == oldText[sal_Int32(nTmp)]) + ++nTmp; + if( nOldPos < nTmp ) + { + pLastFont = pSpaceFont; + pLastFont->SetDevFont( rDo.GetInf().GetShell(), + rDo.GetOut() ); + static_cast<SwDoDrawCapital&>(rDo).DrawSpace( aStartPos ); + pLastFont = pBigFont; + pLastFont->SetDevFont( rDo.GetInf().GetShell(), + rDo.GetOut() ); + + // #107816#, #i14820# + if( bCaseMapLengthDiffers ) + { + // Build an own 'changed' string for the given part of the + // source string and use it. That new string may differ in length + // from the source string. + const OUString aNewText = CalcCaseMap( + oldText.copy(sal_Int32(nOldPos), sal_Int32(nTmp-nOldPos))); + aCapInf.nIdx = nOldPos; + aCapInf.nLen = nTmp - nOldPos; + rDo.GetInf().SetIdx(TextFrameIndex(0)); + rDo.GetInf().SetLen(TextFrameIndex(aNewText.getLength())); + rDo.GetInf().SetText( aNewText ); + } + else + { + rDo.GetInf().SetIdx( nOldPos ); + rDo.GetInf().SetLen( nTmp - nOldPos ); + } + + rDo.GetInf().SetOut( *pOutSize ); + Size aPartSize = pBigFont->GetTextSize( rDo.GetInf() ); + nKana += rDo.GetInf().GetKanaDiff(); + rDo.GetInf().SetOut( *pOldOut ); + if( nSpaceAdd ) + aPartSize.AdjustWidth(nSpaceAdd * sal_Int32(nTmp - nOldPos)); + if( nTmpKern && nPos < nMaxPos ) + aPartSize.AdjustWidth(nTmpKern ); + rDo.GetInf().SetSize( aPartSize ); + rDo.Do(); + aStartPos = rDo.GetInf().GetPos(); + nOldPos = nTmp; + } + + while (nTmp < nPos && CH_BLANK != oldText[sal_Int32(nTmp)]) + ++nTmp; + } + else + nTmp = nPos; + if( nTmp > nOldPos ) + { + // #107816#, #i14820# + if( bCaseMapLengthDiffers ) + { + // Build an own 'changed' string for the given part of the + // source string and use it. That new string may differ in length + // from the source string. + const OUString aNewText = CalcCaseMap( + oldText.copy(sal_Int32(nOldPos), sal_Int32(nTmp-nOldPos))); + aCapInf.nIdx = nOldPos; + aCapInf.nLen = nTmp - nOldPos; + rDo.GetInf().SetIdx(TextFrameIndex(0)); + rDo.GetInf().SetLen(TextFrameIndex(aNewText.getLength())); + rDo.GetInf().SetText( aNewText ); + } + else + { + rDo.GetInf().SetIdx( nOldPos ); + rDo.GetInf().SetLen( nTmp - nOldPos ); + } + + rDo.GetInf().SetOut( *pOutSize ); + Size aPartSize = pBigFont->GetTextSize( rDo.GetInf() ); + nKana += rDo.GetInf().GetKanaDiff(); + rDo.GetInf().SetOut( *pOldOut ); + if( !bWordWise && rDo.GetInf().GetSpace() ) + { + for (TextFrameIndex nI = nOldPos; nI < nPos; ++nI) + { + if (CH_BLANK == oldText[sal_Int32(nI)]) + aPartSize.AdjustWidth(nSpaceAdd ); + } + } + if( nTmpKern && nPos < nMaxPos ) + aPartSize.AdjustWidth(nTmpKern ); + rDo.GetInf().SetSize( aPartSize ); + rDo.Do(); + nOldPos = nTmp; + } + } while( nOldPos != nPos ); + } + nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfCharBlock( + oldText, sal_Int32(nPos), + g_pBreakIt->GetLocale( eLng ), CharType::LOWERCASE_LETTER)); + if (nPos < TextFrameIndex(0) || nPos > nMaxPos) + nPos = nMaxPos; + OSL_ENSURE( nPos, "endOfCharBlock not implemented?" ); +#if OSL_DEBUG_LEVEL > 1 + if( !nPos ) + nPos = nMaxPos; +#endif + } + + // clean up: + if( pBigFont != pOldLast ) + pBigFontAccess.reset(); + + if( bTextLines ) + { + if( rDo.GetInf().GetDrawSpace() ) + { + pLastFont = pSpaceFont; + pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() ); + static_cast<SwDoDrawCapital&>( rDo ).DrawSpace( aStartPos ); + } + if ( bWordWise ) + pSpaceFontAccess.reset(); + } + pLastFont = pOldLast; + pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() ); + + pSmallFontAccess.reset(); + rDo.GetInf().SetText(oldText); + rDo.GetInf().SetKanaDiff( nKana ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/modeltoviewhelper.cxx b/sw/source/core/txtnode/modeltoviewhelper.cxx new file mode 100644 index 000000000..4675a632a --- /dev/null +++ b/sw/source/core/txtnode/modeltoviewhelper.cxx @@ -0,0 +1,337 @@ +/* -*- 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 <tools/multisel.hxx> +#include <doc.hxx> +#include <IMark.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <fmtftn.hxx> +#include <modeltoviewhelper.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <txatbase.hxx> +#include <txtfld.hxx> +#include <txtftn.hxx> +#include <scriptinfo.hxx> +#include <IDocumentMarkAccess.hxx> +#include <set> +#include <vector> + +namespace { + +struct FieldResult +{ + sal_Int32 m_nFieldPos; + OUString m_sExpand; + enum { NONE, FIELD, FOOTNOTE } m_eType; + explicit FieldResult(sal_Int32 const nPos) + : m_nFieldPos(nPos), m_eType(NONE) + { } +}; + +class sortfieldresults +{ +public: + bool operator()(const FieldResult &rOne, const FieldResult &rTwo) const + { + return rOne.m_nFieldPos < rTwo.m_nFieldPos; + } +}; + +} + +typedef std::set<FieldResult, sortfieldresults> FieldResultSet; + +namespace { + +struct block +{ + sal_Int32 m_nStart; + sal_Int32 m_nLen; + bool m_bVisible; + FieldResultSet m_aAttrs; + block(sal_Int32 nStart, sal_Int32 nLen, bool bVisible) + : m_nStart(nStart), m_nLen(nLen), m_bVisible(bVisible) + { + } +}; + +struct containsPos +{ + const sal_Int32 m_nPos; + explicit containsPos(const sal_Int32 nPos) + : m_nPos(nPos) + { + } + bool operator() (const block& rIn) const + { + return m_nPos >= rIn.m_nStart && m_nPos < rIn.m_nStart + rIn.m_nLen; + } +}; + +} + +ModelToViewHelper::ModelToViewHelper(const SwTextNode &rNode, + SwRootFrame const*const pLayout, ExpandMode eMode) +{ + const OUString& rNodeText = rNode.GetText(); + m_aRetText = rNodeText; + + if (eMode == ExpandMode::PassThrough) + return; + + Range aRange( 0, rNodeText.isEmpty() ? 0 : rNodeText.getLength() - 1); + MultiSelection aHiddenMulti(aRange); + + if (eMode & ExpandMode::HideInvisible) + SwScriptInfo::selectHiddenTextProperty(rNode, aHiddenMulti, nullptr); + + if (eMode & ExpandMode::HideDeletions) + SwScriptInfo::selectRedLineDeleted(rNode, aHiddenMulti); + + std::vector<block> aBlocks; + + sal_Int32 nShownStart = 0; + for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i) + { + const Range& rRange = aHiddenMulti.GetRange(i); + const sal_Int32 nHiddenStart = rRange.Min(); + const sal_Int32 nHiddenEnd = rRange.Max() + 1; + const sal_Int32 nHiddenLen = nHiddenEnd - nHiddenStart; + + const sal_Int32 nShownEnd = nHiddenStart; + const sal_Int32 nShownLen = nShownEnd - nShownStart; + + if (nShownLen) + aBlocks.emplace_back(nShownStart, nShownLen, true); + + if (nHiddenLen) + aBlocks.emplace_back(nHiddenStart, nHiddenLen, false); + + nShownStart = nHiddenEnd; + } + + sal_Int32 nTrailingShownLen = rNodeText.getLength() - nShownStart; + if (nTrailingShownLen) + aBlocks.emplace_back(nShownStart, nTrailingShownLen, true); + + if (eMode & ExpandMode::ExpandFields || eMode & ExpandMode::ExpandFootnote) + { + //first the normal fields, get their position in the node and what the text they expand + //to is + const SwpHints* pSwpHints2 = rNode.GetpSwpHints(); + for ( size_t i = 0; pSwpHints2 && i < pSwpHints2->Count(); ++i ) + { + const SwTextAttr* pAttr = pSwpHints2->Get(i); + if (pAttr->HasDummyChar()) + { + const sal_Int32 nDummyCharPos = pAttr->GetStart(); + if (aHiddenMulti.IsSelected(nDummyCharPos)) + continue; + std::vector<block>::iterator aFind = std::find_if(aBlocks.begin(), + aBlocks.end(), containsPos(nDummyCharPos)); + if (aFind != aBlocks.end()) + { + FieldResult aFieldResult(nDummyCharPos); + switch (pAttr->Which()) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + if (eMode & ExpandMode::ExpandFields) + { + // add a ZWSP before the expanded field in replace mode + aFieldResult.m_sExpand = ((eMode & ExpandMode::ReplaceMode) + ? OUString(CHAR_ZWSP) : OUString("")) + + static_txtattr_cast<SwTextField const*>(pAttr)-> + GetFormatField().GetField()->ExpandField(true, pLayout); + aFieldResult.m_eType = FieldResult::FIELD; + } + break; + case RES_TXTATR_FTN: + if (eMode & ExpandMode::ExpandFootnote) + { + const SwFormatFootnote& rFootnote = static_cast<SwTextFootnote const*>(pAttr)->GetFootnote(); + const SwDoc *pDoc = rNode.GetDoc(); + aFieldResult.m_sExpand = (eMode & ExpandMode::ReplaceMode) + ? OUString(CHAR_ZWSP) + : rFootnote.GetViewNumStr(*pDoc, pLayout); + aFieldResult.m_eType = FieldResult::FOOTNOTE; + } + break; + default: + break; + } + aFind->m_aAttrs.insert(aFieldResult); + } + } + } + + if (eMode & ExpandMode::ExpandFields) + { + //now get the dropdown formfields, get their position in the node and what the text they expand + //to is + SwPaM aPaM(rNode, 0, rNode, rNode.Len()); + std::vector<sw::mark::IFieldmark*> aDropDowns = + rNode.GetDoc()->getIDocumentMarkAccess()->getDropDownsFor(aPaM); + + for (sw::mark::IFieldmark *pMark : aDropDowns) + { + const sal_Int32 nDummyCharPos = pMark->GetMarkPos().nContent.GetIndex()-1; + if (aHiddenMulti.IsSelected(nDummyCharPos)) + continue; + std::vector<block>::iterator aFind = std::find_if(aBlocks.begin(), aBlocks.end(), + containsPos(nDummyCharPos)); + if (aFind != aBlocks.end()) + { + FieldResult aFieldResult(nDummyCharPos); + aFieldResult.m_sExpand = (eMode & ExpandMode::ReplaceMode) + ? OUString(CHAR_ZWSP) + : sw::mark::ExpandFieldmark(pMark); + aFieldResult.m_eType = FieldResult::FIELD; + aFind->m_aAttrs.insert(aFieldResult); + } + } + } + } + + //store the end of each range in the model and where that end of range + //maps to in the view + sal_Int32 nOffset = 0; + for (const auto& rBlock : aBlocks) + { + const sal_Int32 nBlockLen = rBlock.m_nLen; + if (!nBlockLen) + continue; + const sal_Int32 nBlockStart = rBlock.m_nStart; + const sal_Int32 nBlockEnd = nBlockStart + nBlockLen; + + if (!rBlock.m_bVisible) + { + sal_Int32 const modelBlockPos(nBlockEnd); + sal_Int32 const viewBlockPos(nBlockStart + nOffset); + m_aMap.emplace_back(modelBlockPos, viewBlockPos, false); + + m_aRetText = m_aRetText.replaceAt(nOffset + nBlockStart, nBlockLen, OUString()); + nOffset -= nBlockLen; + } + else + { + for (const auto& rAttr : rBlock.m_aAttrs) + { + sal_Int32 const modelFieldPos(rAttr.m_nFieldPos); + sal_Int32 const viewFieldPos(rAttr.m_nFieldPos + nOffset); + m_aMap.emplace_back(modelFieldPos, viewFieldPos, true ); + + m_aRetText = m_aRetText.replaceAt(viewFieldPos, 1, rAttr.m_sExpand); + nOffset += rAttr.m_sExpand.getLength() - 1; + + switch (rAttr.m_eType) + { + case FieldResult::FIELD: + m_FieldPositions.push_back(viewFieldPos); + break; + case FieldResult::FOOTNOTE: + m_FootnotePositions.push_back(viewFieldPos); + break; + case FieldResult::NONE: /*ignore*/ + break; + } + } + + sal_Int32 const modelEndBlock(nBlockEnd); + sal_Int32 const viewFieldPos(nBlockEnd + nOffset); + m_aMap.emplace_back(modelEndBlock, viewFieldPos, true); + } + } +} + +/** Converts a model position into a view position +*/ +sal_Int32 ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos ) const +{ + // Search for entry after nPos: + auto aIter = std::find_if(m_aMap.begin(), m_aMap.end(), + [nModelPos](const ConversionMapEntry& rEntry) { return rEntry.m_nModelPos >= nModelPos; }); + if (aIter != m_aMap.end()) + { + //if it's an invisible portion, map all contained positions + //to the anchor viewpos + if (!aIter->m_bVisible) + return aIter->m_nViewPos; + + //if it's a visible portion, then the view position is the anchor + //viewpos - the offset of the input modelpos from the anchor + //modelpos + const sal_Int32 nOffsetFromEnd = aIter->m_nModelPos - nModelPos; + return aIter->m_nViewPos - nOffsetFromEnd; + } + + return nModelPos; +} + +/** Converts a view position into a model position +*/ +ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_Int32 nViewPos ) const +{ + ModelPosition aRet; + aRet.mnPos = nViewPos; + + // Search for entry after nPos: + auto aIter = std::find_if(m_aMap.begin(), m_aMap.end(), + [nViewPos](const ConversionMapEntry& rEntry) { return rEntry.m_nViewPos > nViewPos; }); + + // If nViewPos is in front of first field, we are finished. + if (aIter != m_aMap.end() && aIter != m_aMap.begin()) + { + const sal_Int32 nPosModel = aIter->m_nModelPos; + const sal_Int32 nPosExpand = aIter->m_nViewPos; + + --aIter; + + // nPrevPosModel is the field position + const sal_Int32 nPrevPosModel = aIter->m_nModelPos; + const sal_Int32 nPrevPosExpand = aIter->m_nViewPos; + + const sal_Int32 nLengthModel = nPosModel - nPrevPosModel; + const sal_Int32 nLengthExpand = nPosExpand - nPrevPosExpand; + + const sal_Int32 nFieldLengthExpand = nLengthExpand - nLengthModel + 1; + const sal_Int32 nFieldEndExpand = nPrevPosExpand + nFieldLengthExpand; + + // Check if nPos is outside of field: + if ( nFieldEndExpand <= nViewPos ) + { + // nPos is outside of field: + const sal_Int32 nDistToField = nViewPos - nFieldEndExpand + 1; + aRet.mnPos = nPrevPosModel + nDistToField; + } + else + { + // nViewPos is inside a field: + aRet.mnPos = nPrevPosModel; + aRet.mnSubPos = nViewPos - nPrevPosExpand; + aRet.mbIsField = true; + } + } + + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/ndhints.cxx b/sw/source/core/txtnode/ndhints.cxx new file mode 100644 index 000000000..e7f34081c --- /dev/null +++ b/sw/source/core/txtnode/ndhints.cxx @@ -0,0 +1,475 @@ +/* -*- 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 <editeng/rsiditem.hxx> +#include <sal/log.hxx> +#include <txatbase.hxx> +#include <ndhints.hxx> +#include <txtatr.hxx> + +#ifdef DBG_UTIL +#include <pam.hxx> +#include <fmtautofmt.hxx> +#include <set> +#endif + +/// sort order: Start, End (reverse), Which (reverse), +/// (char style: sort number), at last the pointer +static bool CompareSwpHtStart( const SwTextAttr* lhs, const SwTextAttr* rhs ) +{ + const SwTextAttr &rHt1 = *lhs; + const SwTextAttr &rHt2 = *rhs; + if ( rHt1.GetStart() == rHt2.GetStart() ) + { + const sal_Int32 nHt1 = rHt1.GetAnyEnd(); + const sal_Int32 nHt2 = rHt2.GetAnyEnd(); + if ( nHt1 == nHt2 ) + { + const sal_uInt16 nWhich1 = rHt1.Which(); + const sal_uInt16 nWhich2 = rHt2.Which(); + if ( nWhich1 == nWhich2 ) + { + if ( RES_TXTATR_CHARFMT == nWhich1 ) + { + const sal_uInt16 nS1 = + static_txtattr_cast<const SwTextCharFormat&>(rHt1).GetSortNumber(); + const sal_uInt16 nS2 = + static_txtattr_cast<const SwTextCharFormat&>(rHt2).GetSortNumber(); + if ( nS1 != nS2 ) // robust + return nS1 < nS2; + } + + return reinterpret_cast<sal_IntPtr>(&rHt1) < reinterpret_cast<sal_IntPtr>(&rHt2); + } + // order is important! for requirements see hintids.hxx + return ( nWhich1 > nWhich2 ); + } + return ( nHt1 > nHt2 ); + } + return ( rHt1.GetStart() < rHt2.GetStart() ); +} + +/// sort order: Which, Start, End(reverse) at last the pointer +bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const sal_uInt16 nWhich ) const +{ + return lhs->Which() < nWhich; +} +bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const SwTextAttr* rhs ) const +{ + const SwTextAttr &rHt1 = *lhs; + const SwTextAttr &rHt2 = *rhs; + const sal_uInt16 nWhich1 = rHt1.Which(); + const sal_uInt16 nWhich2 = rHt2.Which(); + if ( nWhich1 < nWhich2 ) + return true; + if ( nWhich1 > nWhich2 ) + return false; + if (rHt1.GetStart() < rHt2.GetStart()) + return true; + if (rHt1.GetStart() > rHt2.GetStart()) + return false; + if ( RES_TXTATR_CHARFMT == nWhich1 ) + { + const sal_uInt16 nS1 = + static_txtattr_cast<const SwTextCharFormat&>(rHt1).GetSortNumber(); + const sal_uInt16 nS2 = + static_txtattr_cast<const SwTextCharFormat&>(rHt2).GetSortNumber(); + if ( nS1 != nS2 ) // robust + return nS1 < nS2; + } + const sal_Int32 nEnd1 = rHt1.GetAnyEnd(); + const sal_Int32 nEnd2 = rHt2.GetAnyEnd(); + if ( nEnd1 > nEnd2 ) + return true; + if ( nEnd1 < nEnd2 ) + return false; + return reinterpret_cast<sal_IntPtr>(&rHt1) < reinterpret_cast<sal_IntPtr>(&rHt2); +} + +/// sort order: End, Start(reverse), Which +/// (char style: sort number), at last the pointer(reverse) +bool CompareSwpHtEnd::operator()( sal_Int32 nEndPos, const SwTextAttr* rhs ) const +{ + return nEndPos < rhs->GetAnyEnd(); +} +bool CompareSwpHtEnd::operator()( const SwTextAttr* lhs, const SwTextAttr* rhs ) const +{ + const SwTextAttr &rHt1 = *lhs; + const SwTextAttr &rHt2 = *rhs; + const sal_Int32 nHt1 = rHt1.GetAnyEnd(); + const sal_Int32 nHt2 = rHt2.GetAnyEnd(); + if ( nHt1 == nHt2 ) + { + if ( rHt1.GetStart() == rHt2.GetStart() ) + { + const sal_uInt16 nWhich1 = rHt1.Which(); + const sal_uInt16 nWhich2 = rHt2.Which(); + if ( nWhich1 == nWhich2 ) + { + if ( RES_TXTATR_CHARFMT == nWhich1 ) + { + const sal_uInt16 nS1 = + static_txtattr_cast<const SwTextCharFormat&>(rHt1).GetSortNumber(); + const sal_uInt16 nS2 = + static_txtattr_cast<const SwTextCharFormat&>(rHt2).GetSortNumber(); + if ( nS1 != nS2 ) // robust + return nS1 > nS2; + } + + return reinterpret_cast<sal_IntPtr>(&rHt1) > reinterpret_cast<sal_IntPtr>(&rHt2); + } + // order is important! for requirements see hintids.hxx + return ( nWhich1 < nWhich2 ); + } + else + return ( rHt1.GetStart() > rHt2.GetStart() ); + } + return ( nHt1 < nHt2 ); +} + +void SwpHints::Insert(SwTextAttr* pHt) +{ + assert(std::find(m_HintsByStart.begin(), m_HintsByStart.end(), pHt) + == m_HintsByStart.end()); // "Insert: hint already in HtStart" + assert( pHt->m_pHints == nullptr ); + pHt->m_pHints = this; + + if (m_bStartMapNeedsSorting) + ResortStartMap(); + if (m_bEndMapNeedsSorting) + ResortEndMap(); + if (m_bWhichMapNeedsSorting) + ResortWhichMap(); + + auto it1 = std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), pHt, CompareSwpHtStart); + m_HintsByStart.insert(it1, pHt); + + auto it2 = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd()); + m_HintsByEnd.insert(it2, pHt); + + auto it3 = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), pHt, CompareSwpHtWhichStart()); + m_HintsByWhichAndStart.insert(it3, pHt); +} + +bool SwpHints::Contains( const SwTextAttr *pHt ) const +{ + // DO NOT use find() or CHECK here! + // if called from SwTextNode::InsertItem, pHt has already been deleted, + // so it cannot be dereferenced + return std::find(m_HintsByStart.begin(), m_HintsByStart.end(), pHt) + != m_HintsByStart.end(); +} + +#ifdef DBG_UTIL + +#define CHECK_ERR(cond, text) \ + if(!(cond)) \ + { \ + SAL_WARN("sw.core", text); \ + Resort(); \ + return false; \ + } + +bool SwpHints::Check(bool bPortionsMerged) const +{ + // 1) both arrays have same size + CHECK_ERR( m_HintsByStart.size() == m_HintsByEnd.size(), + "HintsCheck: wrong sizes" ); + sal_Int32 nLastStart = 0; + sal_Int32 nLastEnd = 0; + + const SwTextAttr *pLastStart = nullptr; + const SwTextAttr *pLastEnd = nullptr; + o3tl::sorted_vector<SwTextAttr const*> RsidOnlyAutoFormats; + if (bPortionsMerged) + { + for (size_t i = 0; i < Count(); ++i) + { + SwTextAttr const*const pHint(m_HintsByStart[i]); + if (RES_TXTATR_AUTOFMT == pHint->Which()) + { + std::shared_ptr<SfxItemSet> const pSet( + pHint->GetAutoFormat().GetStyleHandle()); + if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false)) + { + RsidOnlyAutoFormats.insert(pHint); + } + } + } + } + + for( size_t i = 0; i < Count(); ++i ) + { + // --- check Starts --- + + // 2a) valid pointer? depends on overwriting freed mem with 0xFF + const SwTextAttr *pHt = m_HintsByStart[i]; + CHECK_ERR( 0xFF != *reinterpret_cast<unsigned char const *>(pHt), "HintsCheck: start ptr was deleted" ); + + // 3a) start sort order? + sal_Int32 nIdx = pHt->GetStart(); + CHECK_ERR( nIdx >= nLastStart, "HintsCheck: starts are unsorted" ); + + // 4a) IsLessStart consistency + if( pLastStart ) + CHECK_ERR( CompareSwpHtStart( pLastStart, pHt ), "HintsCheck: IsLastStart" ); + + nLastStart = nIdx; + pLastStart = pHt; + + // --- check Ends --- + + // 2b) valid pointer? see DELETEFF + const SwTextAttr *pHtEnd = m_HintsByEnd[i]; + CHECK_ERR( 0xFF != *reinterpret_cast<unsigned char const *>(pHtEnd), "HintsCheck: end ptr was deleted" ); + + // 3b) end sort order? + nIdx = pHtEnd->GetAnyEnd(); + CHECK_ERR( nIdx >= nLastEnd, "HintsCheck: ends are unsorted" ); + + // 4b) IsLessEnd consistency + if( pLastEnd ) + CHECK_ERR( CompareSwpHtEnd()( pLastEnd, pHtEnd ), "HintsCheck: IsLastEnd" ); + + nLastEnd = nIdx; + pLastEnd = pHtEnd; + + // --- cross checks --- + + // 5) same pointers in both arrays + if (std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), const_cast<SwTextAttr*>(pHt), CompareSwpHtStart) == m_HintsByStart.end()) + nIdx = COMPLETE_STRING; + + CHECK_ERR( COMPLETE_STRING != nIdx, "HintsCheck: no GetStartOf" ); + + // 6) same pointers in both arrays + if (std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), const_cast<SwTextAttr*>(pHt), CompareSwpHtEnd()) == m_HintsByEnd.end()) + nIdx = COMPLETE_STRING; + + CHECK_ERR( COMPLETE_STRING != nIdx, "HintsCheck: no GetEndOf" ); + + // 7a) character attributes in array? + sal_uInt16 nWhich = pHt->Which(); + CHECK_ERR( !isCHRATR(nWhich), + "HintsCheck: Character attribute in start array" ); + + // 7b) character attributes in array? + nWhich = pHtEnd->Which(); + CHECK_ERR( !isCHRATR(nWhich), + "HintsCheck: Character attribute in end array" ); + + // 8) style portion check + const SwTextAttr* pHtThis = m_HintsByStart[i]; + const SwTextAttr* pHtLast = i > 0 ? m_HintsByStart[i-1] : nullptr; + CHECK_ERR( (0 == i) + || ( (RES_TXTATR_CHARFMT != pHtLast->Which()) + && (RES_TXTATR_AUTOFMT != pHtLast->Which())) + || ( (RES_TXTATR_CHARFMT != pHtThis->Which()) + && (RES_TXTATR_AUTOFMT != pHtThis->Which())) + || (pHtThis->GetStart() >= *pHtLast->End()) // no overlap + || ( ( (pHtThis->GetStart() == pHtLast->GetStart()) + && (*pHtThis->End() == *pHtLast->End()) + ) // same range + && ( (pHtThis->Which() != RES_TXTATR_AUTOFMT) + || (pHtLast->Which() != RES_TXTATR_AUTOFMT) + ) // never two AUTOFMT on same range + && ( (pHtThis->Which() != RES_TXTATR_CHARFMT) + || (pHtLast->Which() != RES_TXTATR_CHARFMT) + || (static_txtattr_cast<const SwTextCharFormat *>(pHtThis) + ->GetSortNumber() != + static_txtattr_cast<const SwTextCharFormat *>(pHtLast) + ->GetSortNumber()) + ) // multiple CHARFMT on same range need distinct sorter + ) + || (pHtThis->GetStart() == *pHtThis->End()), // this empty + "HintsCheck: Portion inconsistency. " + "This can be temporarily ok during undo operations" ); + + // 8 1/2) format ignore start/end flag check + // (problems because MergePortions buggy or not called) + if (bPortionsMerged) + { + if (RES_TXTATR_AUTOFMT == pHt->Which() || + RES_TXTATR_CHARFMT == pHt->Which()) + { + // mostly ignore the annoying no-length hints + // BuildPortions inserts these in the middle of an existing one + bool const bNoLength(pHt->GetStart() == *pHt->End()); + bool bNeedContinuation(!bNoLength && pHt->IsFormatIgnoreEnd()); + bool bForbidContinuation(!bNoLength && !bNeedContinuation); + if (RES_TXTATR_AUTOFMT == pHt->Which()) + { + if (RsidOnlyAutoFormats.find(pHt) != RsidOnlyAutoFormats.end()) + { + assert(pHt->IsFormatIgnoreStart()); + bNeedContinuation = false; + // don't forbid continuation - may be other hint here! + } + } + if (bNeedContinuation || bForbidContinuation) + { + bool bFound(false); + for (size_t j = i + 1; j < Count(); ++j) + { + SwTextAttr *const pOther(m_HintsByStart[j]); + if (pOther->GetStart() > *pHt->End()) + { + break; // done + } + else if (pOther->GetStart() == pOther->GetAnyEnd()) + { + continue; // empty hint: ignore + } + else if (pOther->GetStart() == *pHt->End()) + { + if (RES_TXTATR_AUTOFMT == pOther->Which() || + RES_TXTATR_CHARFMT == pOther->Which()) + { // multiple charfmt on same range must all match + if (bNeedContinuation) + { + assert(pOther->IsFormatIgnoreStart()); + bFound = true; + } + else if (bForbidContinuation && + (RsidOnlyAutoFormats.find(pOther) == + RsidOnlyAutoFormats.end())) + { + assert(!pOther->IsFormatIgnoreStart()); + } + } + } + } + if (bNeedContinuation) + { + assert(bFound); // ? can this happen temp. during undo? + } + } + } + else + { + assert(!pHt->IsFormatIgnoreStart()); + assert(!pHt->IsFormatIgnoreEnd()); + } + } + + // 9) nesting portion check + if (pHtThis->IsNesting()) + { + for (size_t j = 0; j < i; ++j) + { + SwTextAttr const * const pOther( m_HintsByStart[j] ); + if (pOther->IsNesting()) + { + SwComparePosition cmp = ComparePosition( + pHtThis->GetStart(), *pHtThis->End(), + pOther->GetStart(), *pOther->End()); + CHECK_ERR( (SwComparePosition::OverlapBefore != cmp) && + (SwComparePosition::OverlapBehind != cmp), + "HintsCheck: overlapping nesting hints!!!" ); + } + } + } + + // 10) dummy char check (unfortunately cannot check SwTextNode::m_Text) + if (pHtThis->HasDummyChar()) + { + for ( size_t j = 0; j < i; ++j ) + { + SwTextAttr const * const pOther( m_HintsByStart[j] ); + if (pOther->HasDummyChar()) + { + CHECK_ERR( (pOther->GetStart() != pHtThis->GetStart()), + "HintsCheck: multiple hints claim same CH_TXTATR!"); + } + } + } + } + return true; +} + +#endif /* DBG_UTIL */ + +// Resort() is called before every Insert and Delete. +// Various SwTextNode methods modify hints in a way that violates the +// sort order of the m_HintsByStart, m_HintsByEnd arrays, so this method is needed +// to restore the order. + +void SwpHints::Resort() const +{ + auto & rStartMap = const_cast<SwpHints*>(this)->m_HintsByStart; + std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart); + auto & rEndMap = const_cast<SwpHints*>(this)->m_HintsByEnd; + std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd()); + auto & rWhichStartMap = const_cast<SwpHints*>(this)->m_HintsByWhichAndStart; + std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart()); + m_bStartMapNeedsSorting = false; + m_bEndMapNeedsSorting = false; + m_bWhichMapNeedsSorting = false; +} + +void SwpHints::ResortStartMap() const +{ + auto & rStartMap = const_cast<SwpHints*>(this)->m_HintsByStart; + std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart); + m_bStartMapNeedsSorting = false; +} + +void SwpHints::ResortEndMap() const +{ + auto & rEndMap = const_cast<SwpHints*>(this)->m_HintsByEnd; + std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd()); + m_bEndMapNeedsSorting = false; +} + +void SwpHints::ResortWhichMap() const +{ + m_bWhichMapNeedsSorting = false; + auto & rWhichStartMap = const_cast<SwpHints*>(this)->m_HintsByWhichAndStart; + std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart()); +} + +size_t SwpHints::GetFirstPosSortedByWhichAndStart( sal_uInt16 nWhich ) const +{ + if (m_bWhichMapNeedsSorting) + ResortWhichMap(); + auto it = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), nWhich, CompareSwpHtWhichStart()); + if ( it == m_HintsByWhichAndStart.end() ) + return SAL_MAX_SIZE; + return it - m_HintsByWhichAndStart.begin(); +} + +int SwpHints::GetLastPosSortedByEnd( sal_Int32 nEndPos ) const +{ + if (m_bEndMapNeedsSorting) + ResortEndMap(); + auto it = std::upper_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), nEndPos, CompareSwpHtEnd()); + return it - m_HintsByEnd.begin() - 1; +} + +size_t SwpHints::GetIndexOf( const SwTextAttr *pHt ) const +{ + if (m_bStartMapNeedsSorting) + ResortStartMap(); + auto it = std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), const_cast<SwTextAttr*>(pHt), CompareSwpHtStart); + if ( it == m_HintsByStart.end() || *it != pHt ) + return SAL_MAX_SIZE; + return it - m_HintsByStart.begin(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx new file mode 100644 index 000000000..62b817138 --- /dev/null +++ b/sw/source/core/txtnode/ndtxt.cxx @@ -0,0 +1,5326 @@ +/* -*- 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 <hintids.hxx> +#include <hints.hxx> + +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/rsiditem.hxx> +#include <sal/log.hxx> +#include <anchoredobject.hxx> +#include <txtfld.hxx> +#include <txtinet.hxx> +#include <fmtanchr.hxx> +#include <fmtinfmt.hxx> +#include <fmtrfmrk.hxx> +#include <txttxmrk.hxx> +#include <fchrfmt.hxx> +#include <txtftn.hxx> +#include <fmtflcnt.hxx> +#include <fmtfld.hxx> +#include <frmatr.hxx> +#include <ftnidx.hxx> +#include <ftninfo.hxx> +#include <fmtftn.hxx> +#include <fmtmeta.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentListsAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <fldbas.hxx> +#include <paratr.hxx> +#include <txtfrm.hxx> +#include <ftnfrm.hxx> +#include <rootfrm.hxx> +#include <expfld.hxx> +#include <section.hxx> +#include <mvsave.hxx> +#include <swcache.hxx> +#include <SwGrammarMarkUp.hxx> +#include <redline.hxx> +#include <IMark.hxx> +#include <scriptinfo.hxx> +#include <istyleaccess.hxx> +#include <SwStyleNameMapper.hxx> +#include <numrule.hxx> +#include <docsh.hxx> +#include <SwNodeNum.hxx> +#include <svl/intitem.hxx> +#include <list.hxx> +#include <sortedobjs.hxx> +#include <calbck.hxx> +#include <attrhint.hxx> +#include <memory> +#include <unoparagraph.hxx> +#include <wrtsh.hxx> +#include <frameformats.hxx> +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <svl/itemiter.hxx> + +using namespace ::com::sun::star; + +typedef std::vector<SwTextAttr*> SwpHts; + + +// unfortunately everyone can change Hints without ensuring order or the linking between them +#ifdef DBG_UTIL +#define CHECK_SWPHINTS(pNd) { if( pNd->GetpSwpHints() && \ + !pNd->GetDoc()->IsInReading() ) \ + pNd->GetpSwpHints()->Check(true); } +#define CHECK_SWPHINTS_IF_FRM(pNd) { if( pNd->GetpSwpHints() && \ + !pNd->GetDoc()->IsInReading() ) \ + pNd->GetpSwpHints()->Check(getLayoutFrame(nullptr, nullptr, nullptr) != nullptr); } +#else +#define CHECK_SWPHINTS(pNd) +#define CHECK_SWPHINTS_IF_FRM(pNd) +#endif + +SwTextNode *SwNodes::MakeTextNode( const SwNodeIndex & rWhere, + SwTextFormatColl *pColl, bool const bNewFrames) +{ + OSL_ENSURE( pColl, "Collection pointer is 0." ); + + SwTextNode *pNode = new SwTextNode( rWhere, pColl, nullptr ); + + SwNodeIndex aIdx( *pNode ); + + // call method <UpdateOutlineNode(..)> only for the document nodes array + if ( IsDocNodes() ) + UpdateOutlineNode(*pNode); + + // if there is no layout or it is in a hidden section, MakeFrames is not needed + const SwSectionNode* pSectNd; + if (!bNewFrames || + !GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() || + ( nullptr != (pSectNd = pNode->FindSectionNode()) && + pSectNd->GetSection().IsHiddenFlag() )) + return pNode; + + SwNodeIndex aTmp( rWhere ); + do { + // max. 2 loops: + // 1. take the successor + // 2. take the predecessor + + SwNode * pNd = & aTmp.GetNode(); + switch (pNd->GetNodeType()) + { + case SwNodeType::Table: + static_cast<SwTableNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx); + return pNode; + + case SwNodeType::Section: + if( static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() || + static_cast<SwSectionNode*>(pNd)->IsContentHidden() ) + { + SwNodeIndex aTmpIdx( *pNode ); + pNd = FindPrvNxtFrameNode( aTmpIdx, pNode ); + if( !pNd ) + return pNode; + aTmp = *pNd; + break; + } + static_cast<SwSectionNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx); + return pNode; + + case SwNodeType::Text: + case SwNodeType::Grf: + case SwNodeType::Ole: + static_cast<SwContentNode*>(pNd)->MakeFramesForAdjacentContentNode(*pNode); + return pNode; + + case SwNodeType::End: + if( pNd->StartOfSectionNode()->IsSectionNode() && + aTmp.GetIndex() < rWhere.GetIndex() ) + { + if( pNd->StartOfSectionNode()->GetSectionNode()->GetSection().IsHiddenFlag()) + { + if( !GoPrevSection( &aTmp, true, false ) || + aTmp.GetNode().FindTableNode() != + pNode->FindTableNode() ) + return pNode; + } + else + aTmp = *pNd->StartOfSectionNode(); + break; + } + else if( pNd->StartOfSectionNode()->IsTableNode() && + aTmp.GetIndex() < rWhere.GetIndex() ) + { + // after a table node + aTmp = *pNd->StartOfSectionNode(); + break; + } + [[fallthrough]]; + default: + if( rWhere == aTmp ) + aTmp -= 2; + else + return pNode; + break; + } + } while( true ); +} + +SwTextNode::SwTextNode( const SwNodeIndex &rWhere, SwTextFormatColl *pTextColl, const SfxItemSet* pAutoAttr ) +: SwContentNode( rWhere, SwNodeType::Text, pTextColl ), + m_Text(), + m_pParaIdleData_Impl(nullptr), + m_bContainsHiddenChars(false), + m_bHiddenCharsHidePara(false), + m_bRecalcHiddenCharFlags(false), + m_bLastOutlineState( false ), + m_bNotifiable( false ), + mbEmptyListStyleSetDueToSetOutlineLevelAttr( false ), + mbInSetOrResetAttr( false ), + m_pNumStringCache(), + m_wXParagraph(), + maFillAttributes() +{ + InitSwParaStatistics( true ); + + if( pAutoAttr ) + SetAttr( *pAutoAttr ); + + if (!IsInList() && GetNumRule() && !GetListId().isEmpty()) + { + // #i101516# + // apply paragraph style's assigned outline style list level as + // list level of the paragraph, if it has none set already. + if ( !HasAttrListLevel() && + pTextColl && pTextColl->IsAssignedToListLevelOfOutlineStyle() ) + { + SetAttrListLevel( pTextColl->GetAssignedOutlineStyleLevel() ); + } + AddToList(); + } + GetNodes().UpdateOutlineNode(*this); + + m_bNotifiable = true; + + m_bContainsHiddenChars = m_bHiddenCharsHidePara = false; + m_bRecalcHiddenCharFlags = true; +} + +SwTextNode::~SwTextNode() +{ + // delete only removes the pointer not the array elements! + if ( m_pSwpHints ) + { + // do not delete attributes twice when those delete their content + std::unique_ptr<SwpHints> pTmpHints(std::move(m_pSwpHints)); + + for( size_t j = pTmpHints->Count(); j; ) + { + // first remove the attribute from the array otherwise + // if would delete itself + DestroyAttr( pTmpHints->Get( --j ) ); + } + } + + // must be removed from outline nodes by now +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + SwOutlineNodes::size_type foo; + assert(!GetNodes().GetOutLineNds().Seek_Entry(this, &foo)); +#endif + + RemoveFromList(); + + InitSwParaStatistics( false ); + DelFrames(nullptr); // must be called here while it's still a SwTextNode + DelFrames_TextNodePart(); +} + +void SwTextNode::FileLoadedInitHints() +{ + if (m_pSwpHints) + { + m_pSwpHints->MergePortions(*this); + } +} + +SwContentFrame *SwTextNode::MakeFrame( SwFrame* pSib ) +{ + SwContentFrame *pFrame = sw::MakeTextFrame(*this, pSib, sw::FrameMode::New); + return pFrame; +} + +sal_Int32 SwTextNode::Len() const +{ + return m_Text.getLength(); +} + +// After a split node, it's necessary to actualize the ref-pointer of the ftnfrms. +static void lcl_ChangeFootnoteRef( SwTextNode &rNode ) +{ + SwpHints *pSwpHints = rNode.GetpSwpHints(); + if( pSwpHints && rNode.GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + SwContentFrame* pFrame = nullptr; + // OD 07.11.2002 #104840# - local variable to remember first footnote + // of node <rNode> in order to invalidate position of its first content. + // Thus, in its <MakeAll()> it will checked its position relative to its reference. + SwFootnoteFrame* pFirstFootnoteOfNode = nullptr; + for( size_t j = pSwpHints->Count(); j; ) + { + SwTextAttr* pHt = pSwpHints->Get(--j); + if (RES_TXTATR_FTN == pHt->Which()) + { + if( !pFrame ) + { + pFrame = SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti>(rNode).First(); + if (!pFrame) + return; + } + SwTextFootnote *pAttr = static_cast<SwTextFootnote*>(pHt); + OSL_ENSURE( pAttr->GetStartNode(), "FootnoteAtr without StartNode." ); + SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 ); + SwContentNode *pNd = aIdx.GetNode().GetContentNode(); + if ( !pNd ) + pNd = pFrame->GetAttrSet()->GetDoc()-> + GetNodes().GoNextSection( &aIdx, true, false ); + if ( !pNd ) + continue; + + SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd); + SwContentFrame* pContent = aIter.First(); + if( pContent ) + { + OSL_ENSURE( pContent->getRootFrame() == pFrame->getRootFrame(), + "lcl_ChangeFootnoteRef: Layout double?" ); + SwFootnoteFrame *pFootnote = pContent->FindFootnoteFrame(); + if( pFootnote && pFootnote->GetAttr() == pAttr ) + { + while( pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + // #104840# - remember footnote frame + pFirstFootnoteOfNode = pFootnote; + while ( pFootnote ) + { + pFootnote->SetRef( pFrame ); + pFootnote = pFootnote->GetFollow(); + static_cast<SwTextFrame*>(pFrame)->SetFootnote( true ); + } + } +#if OSL_DEBUG_LEVEL > 0 + while( nullptr != (pContent = aIter.Next()) ) + { + SwFootnoteFrame *pDbgFootnote = pContent->FindFootnoteFrame(); + OSL_ENSURE( !pDbgFootnote || pDbgFootnote->GetRef() == pFrame, + "lcl_ChangeFootnoteRef: Who's that guy?" ); + } +#endif + } + } + } // end of for-loop on <SwpHints> + // #104840# - invalidate + if ( pFirstFootnoteOfNode ) + { + SwContentFrame* pContent = pFirstFootnoteOfNode->ContainsContent(); + if ( pContent ) + { + pContent->InvalidatePos_(); + } + } + } +} + +namespace sw { + +// check if there are flys on the existing frames (now on "pNode") +// that need to be moved to the new frames of "this" +void MoveMergedFlysAndFootnotes(std::vector<SwTextFrame*> const& rFrames, + SwTextNode const& rFirstNode, SwTextNode & rSecondNode, + bool isSplitNode) +{ + if (!isSplitNode) + { + lcl_ChangeFootnoteRef(rSecondNode); + } + for (sal_uLong nIndex = rSecondNode.GetIndex() + 1; ; ++nIndex) + { + SwNode *const pTmp(rSecondNode.GetNodes()[nIndex]); + if (pTmp->IsCreateFrameWhenHidingRedlines() || pTmp->IsEndNode()) + { + break; + } + else if (pTmp->IsStartNode()) + { + nIndex = pTmp->EndOfSectionIndex(); + } + else if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst + && pTmp->IsTextNode()) + { + lcl_ChangeFootnoteRef(*pTmp->GetTextNode()); + } + } + for (SwTextFrame *const pFrame : rFrames) + { + if (SwSortedObjs *const pObjs = pFrame->GetDrawObjs()) + { + std::vector<SwAnchoredObject*> objs; + objs.reserve(pObjs->size()); + for (SwAnchoredObject *const pObj : *pObjs) + { + objs.push_back(pObj); + } + for (SwAnchoredObject *const pObj : objs) + { + SwFrameFormat & rFormat(pObj->GetFrameFormat()); + SwFormatAnchor const& rAnchor(rFormat.GetAnchor()); + if (rFirstNode.GetIndex() < rAnchor.GetContentAnchor()->nNode.GetIndex()) + { + // move it to the new frame of "this" + rFormat.NotifyClients(&rAnchor, &rAnchor); + // note pObjs will be deleted if it becomes empty + assert(!pFrame->GetDrawObjs() || !pObjs->Contains(*pObj)); + } + } + } + } +} + +} // namespace + +SwTextNode *SwTextNode::SplitContentNode(const SwPosition & rPos, + std::function<void (SwTextNode *, sw::mark::RestoreMode)> const*const pContentIndexRestore) +{ + bool isHide(false); + SwNode::Merge const eOldMergeFlag(GetRedlineMergeFlag()); + bool parentIsOutline = IsOutline(); + + // create a node "in front" of me + const sal_Int32 nSplitPos = rPos.nContent.GetIndex(); + const sal_Int32 nTextLen = m_Text.getLength(); + SwTextNode* const pNode = + MakeNewTextNode( rPos.nNode, false, nSplitPos==nTextLen ); + + // the first paragraph gets the XmlId, + // _except_ if it is empty and the second is not empty + if (nSplitPos != 0) { + pNode->RegisterAsCopyOf(*this, true); + if (nSplitPos == nTextLen) + { + RemoveMetadataReference(); + // NB: SwUndoSplitNode will call pNode->JoinNext, + // which is sufficient even in this case! + } + } + + ResetAttr( RES_PARATR_LIST_ISRESTART ); + ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + if ( GetNumRule() == nullptr || (parentIsOutline && !IsOutline()) ) + { + ResetAttr( RES_PARATR_LIST_ID ); + ResetAttr( RES_PARATR_LIST_LEVEL ); + } + + if ( HasWriterListeners() && !m_Text.isEmpty() && (nTextLen / 2) < nSplitPos ) + { + // optimization for SplitNode: If a split is at the end of a node then + // move the frames from the current to the new one and create new ones + // for the current one. + + // If fly frames are moved, they don't need to destroy their layout + // frames. Set a flag that is checked in SwTextFlyCnt::SetAnchor. + if ( HasHints() ) + { + pNode->GetOrCreateSwpHints().SetInSplitNode(true); + } + + // Move the first part of the content to the new node and delete + // it in the old node. + SwIndex aIdx( this ); + CutText( pNode, aIdx, nSplitPos ); + + if( GetWrong() ) + { + pNode->SetWrong( GetWrong()->SplitList( nSplitPos ) ); + } + SetWrongDirty(WrongState::TODO); + + if( GetGrammarCheck() ) + { + pNode->SetGrammarCheck( GetGrammarCheck()->SplitGrammarList( nSplitPos ) ); + } + SetGrammarCheckDirty( true ); + + SetWordCountDirty( true ); + + if( GetSmartTags() ) + { + pNode->SetSmartTags( GetSmartTags()->SplitList( nSplitPos ) ); + } + SetSmartTagDirty( true ); + + if ( pNode->HasHints() ) + { + if ( pNode->m_pSwpHints->CanBeDeleted() ) + { + pNode->m_pSwpHints.reset(); + } + else + { + pNode->m_pSwpHints->SetInSplitNode(false); + } + + // All fly frames anchored as char that are moved to the new + // node must have their layout frames deleted. + // This comment is sort of silly because we actually delete the + // layout frames of those which were not moved? + // JP 01.10.96: delete all empty and not-to-be-expanded attributes + if ( HasHints() ) + { + for ( size_t j = m_pSwpHints->Count(); j; ) + { + SwTextAttr* const pHt = m_pSwpHints->Get( --j ); + if ( RES_TXTATR_FLYCNT == pHt ->Which() ) + { + pHt->GetFlyCnt().GetFrameFormat()->DelFrames(); + } + else if ( pHt->DontExpand() ) + { + const sal_Int32* const pEnd = pHt->GetEnd(); + if (pEnd && pHt->GetStart() == *pEnd ) + { + // delete it! + m_pSwpHints->DeleteAtPos( j ); + DestroyAttr( pHt ); + } + } + } + } + + } + + if (pContentIndexRestore) + { // call before making frames and before RegisterToNode + (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys); + } + if (eOldMergeFlag != SwNode::Merge::None) + { // clear before making frames and before RegisterToNode + SetRedlineMergeFlag(SwNode::Merge::None); + } // now RegisterToNode will set merge flags in both nodes properly! + + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + isHide = true; + } + frames.push_back(pFrame); + } + for (SwTextFrame * pFrame : frames) + { + pFrame->RegisterToNode( *pNode ); + if (!pFrame->IsFollow() && pFrame->GetOffset()) + { + pFrame->SetOffset( TextFrameIndex(0) ); + } + } + + if ( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + + if ( HasHints() ) + { + MoveTextAttr_To_AttrSet(); + } + // in case there are frames, the RegisterToNode has set the merge flag + pNode->MakeFramesForAdjacentContentNode(*this); + lcl_ChangeFootnoteRef( *this ); + if (pContentIndexRestore) + { // call after making frames; listeners will take care of adding to the right frame + (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys); + } + if (eOldMergeFlag != SwNode::Merge::None) + { + MoveMergedFlysAndFootnotes(frames, *pNode, *this, true); + } + } + else + { + SwWrongList *pList = GetWrong(); + SetWrong( nullptr, false ); + SetWrongDirty(WrongState::TODO); + + SwGrammarMarkUp *pList3 = GetGrammarCheck(); + SetGrammarCheck( nullptr, false ); + SetGrammarCheckDirty( true ); + + SetWordCountDirty( true ); + + SwWrongList *pList2 = GetSmartTags(); + SetSmartTags( nullptr, false ); + SetSmartTagDirty( true ); + + SwIndex aIdx( this ); + CutText( pNode, aIdx, nSplitPos ); + + // JP 01.10.96: delete all empty and not-to-be-expanded attributes + if ( HasHints() ) + { + for ( size_t j = m_pSwpHints->Count(); j; ) + { + SwTextAttr* const pHt = m_pSwpHints->Get( --j ); + const sal_Int32* const pEnd = pHt->GetEnd(); + if ( pHt->DontExpand() && pEnd && (pHt->GetStart() == *pEnd) ) + { + // delete it! + m_pSwpHints->DeleteAtPos( j ); + DestroyAttr( pHt ); + } + } + MoveTextAttr_To_AttrSet(); + } + + if( pList ) + { + pNode->SetWrong( pList->SplitList( nSplitPos ) ); + SetWrong( pList, false ); + } + + if( pList3 ) + { + pNode->SetGrammarCheck( pList3->SplitGrammarList( nSplitPos ) ); + SetGrammarCheck( pList3, false ); + } + + if( pList2 ) + { + pNode->SetSmartTags( pList2->SplitList( nSplitPos ) ); + SetSmartTags( pList2, false ); + } + + if (pContentIndexRestore) + { // call before making frames and before RegisterToNode + (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys); + } + + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + frames.push_back(pFrame); + if (pFrame->getRootFrame()->IsHideRedlines()) + { + isHide = true; + } + } + bool bNonMerged(false); + bool bRecreateThis(false); + for (SwTextFrame * pFrame : frames) + { + // sw_redlinehide: for this to work properly with hidden nodes, + // the frame needs to listen on them too. + // also: have to check the frame; this->GetRedlineMergeFlag() + // is None in case there's a delete redline inside the paragraph, + // but that could still result in a merged frame after split... + if (pFrame->GetMergedPara()) + { + // Can't special case this == First here - that could (if + // both nodes are still merged by redline) lead to + // duplicate frames on "this". + // Update the extents with new node; also inits merge flag, + // so the MakeFramesForAdjacentContentNode below respects it + pFrame->RegisterToNode(*pNode); + if (pFrame->GetText().isEmpty()) + { + // turns out it's empty - in this case, it was not + // invalidated because Cut didn't sent it any hints, + // so we have to invalidate it here! + pFrame->Prepare(PrepareHint::Clear, nullptr, false); + } + if (!pFrame->GetMergedPara() || + !pFrame->GetMergedPara()->listener.IsListeningTo(this)) + { + // it's no longer listening - need to recreate frame + // (note this is idempotent, can be done once per frame) + SetRedlineMergeFlag(SwNode::Merge::None); + bRecreateThis = true; + } + } + else + { + bNonMerged = true; + } + } + assert(!(bNonMerged && bRecreateThis)); // 2 layouts not handled yet - maybe best to simply use the other branch then? + if (!frames.empty() && bNonMerged) + { + // the existing frame on "this" should have been updated by Cut + MakeFramesForAdjacentContentNode(*pNode); + lcl_ChangeFootnoteRef(*pNode); + } + else if (bRecreateThis) + { + assert(pNode->HasWriterListeners()); // was just moved there + pNode->MakeFramesForAdjacentContentNode(*this); + lcl_ChangeFootnoteRef(*this); + } + + if (pContentIndexRestore) + { // call after making frames; listeners will take care of adding to the right frame + (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys); + } + + if (bRecreateThis) + { + MoveMergedFlysAndFootnotes(frames, *pNode, *this, true); + } + } + +#ifndef NDEBUG + if (isHide) // otherwise flags won't be set anyway + { + // First + // -> First,NonFirst + // -> First,Hidden + // -> None,First + // Hidden + // -> Hidden,Hidden (if still inside merge rl) + // -> NonFirst,First (if redline was split) + // NonFirst + // -> NonFirst,First (if split after end of "incoming" redline & + // before start of "outgoing" redline) + // -> NonFirst,None (if split after end of "incoming" redline) + // -> NonFirst,Hidden (if split after start of "outgoing" redline) + // -> Hidden, NonFirst (if split before end of "incoming" redline) + // None + // -> None,None + // -> First,NonFirst (if splitting inside a delete redline) + SwNode::Merge const eFirst(pNode->GetRedlineMergeFlag()); + SwNode::Merge const eSecond(GetRedlineMergeFlag()); + switch (eOldMergeFlag) + { + case Merge::First: + assert((eFirst == Merge::First && eSecond == Merge::NonFirst) + || (eFirst == Merge::First && eSecond == Merge::Hidden) + || (eFirst == Merge::None && eSecond == Merge::First)); + break; + case Merge::Hidden: + assert((eFirst == Merge::Hidden && eSecond == Merge::Hidden) + || (eFirst == Merge::NonFirst && eSecond == Merge::First) + // next ones can happen temp. in UndoDelete :( + || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst) + || (eFirst == Merge::NonFirst && eSecond == Merge::None)); + break; + case Merge::NonFirst: + assert((eFirst == Merge::NonFirst && eSecond == Merge::First) + || (eFirst == Merge::NonFirst && eSecond == Merge::None) + || (eFirst == Merge::NonFirst && eSecond == Merge::Hidden) + || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst)); + break; + case Merge::None: + assert((eFirst == Merge::None && eSecond == Merge::None) + || (eFirst == Merge::First && eSecond == Merge::NonFirst)); + break; + } + } +#else + (void) isHide; +#endif + + { + // Send Hint for PageDesc. This should be done in the Layout when + // pasting the frames, but that causes other problems that look + // expensive to solve. + const SfxPoolItem *pItem; + if( HasWriterListeners() && SfxItemState::SET == pNode->GetSwAttrSet(). + GetItemState( RES_PAGEDESC, true, &pItem ) ) + { + pNode->ModifyNotification( pItem, pItem ); + } + } + return pNode; +} + +void SwTextNode::MoveTextAttr_To_AttrSet() +{ + OSL_ENSURE( m_pSwpHints, "MoveTextAttr_To_AttrSet without SwpHints?" ); + for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i ) + { + SwTextAttr *pHt = m_pSwpHints->Get(i); + + if( pHt->GetStart() ) + break; + + const sal_Int32* pHtEndIdx = pHt->GetEnd(); + + if( !pHtEndIdx ) + continue; + + if (*pHtEndIdx < m_Text.getLength() || pHt->IsCharFormatAttr()) + break; + + if( !pHt->IsDontMoveAttr() && + SetAttr( pHt->GetAttr() ) ) + { + m_pSwpHints->DeleteAtPos(i); + DestroyAttr( pHt ); + --i; + } + } + +} + +namespace sw { + +/// if first node is deleted & second survives, then the first node's frame +/// will be deleted too; prevent this by moving the frame to the second node +/// if necessary. +void MoveDeletedPrevFrames(const SwTextNode & rDeletedPrev, SwTextNode & rNode) +{ + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rDeletedPrev); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + { + auto frames2(frames); + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIt(rNode); + for (SwTextFrame* pFrame = aIt.First(); pFrame; pFrame = aIt.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + auto const it(std::find(frames2.begin(), frames2.end(), pFrame)); + assert(it != frames2.end()); + frames2.erase(it); + } + } + assert(frames2.empty()); + } + for (SwTextFrame *const pFrame : frames) + { + pFrame->RegisterToNode(rNode, true); + } +} + +// typical Join: +// None,Node->None +// None,First->First +// First,NonFirst->First +// NonFirst,First->NonFirst +// NonFirst,None->NonFirst + +/// if first node is First, its frames may need to be moved, never deleted. +/// if first node is NonFirst, second node's own frames (First/None) must be deleted +void CheckResetRedlineMergeFlag(SwTextNode & rNode, Recreate const eRecreateMerged) +{ + if (eRecreateMerged != sw::Recreate::No) + { + SwTextNode * pMergeNode(&rNode); + if (eRecreateMerged == sw::Recreate::Predecessor + // tdf#135018 check that there is a predecessor node, i.e. rNode + // isn't the first node after the body start node + && rNode.GetNodes()[rNode.GetIndex() - 1]->StartOfSectionIndex() != 0) + { + for (sal_uLong i = rNode.GetIndex() - 1; ; --i) + { + SwNode *const pNode(rNode.GetNodes()[i]); + assert(!pNode->IsStartNode()); + if (pNode->IsEndNode()) + { + i = pNode->StartOfSectionIndex(); + } + else if (pNode->IsTextNode()) + { + pMergeNode = pNode->GetTextNode(); // use predecessor to merge + break; + } + } + } + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pMergeNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + auto eMode(sw::FrameMode::Existing); + for (SwTextFrame * pFrame : frames) + { + SwTextNode & rFirstNode(pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pFirstNode + : *pMergeNode); + assert(rFirstNode.GetIndex() <= rNode.GetIndex()); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + assert(pFrame->GetMergedPara()); + assert(pFrame->GetMergedPara()->listener.IsListeningTo(&rNode)); + assert(rNode.GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex()); + eMode = sw::FrameMode::New; // Existing is not idempotent! + } + } + else if (rNode.GetRedlineMergeFlag() != SwNode::Merge::None) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (auto const pMergedPara = pFrame->GetMergedPara()) + { + if (pMergedPara->pFirstNode == pMergedPara->pLastNode) + { + assert(pMergedPara->pFirstNode == &rNode); + rNode.SetRedlineMergeFlag(SwNode::Merge::None); + } + break; // checking once is enough + } + else if (pFrame->getRootFrame()->IsHideRedlines()) + { + rNode.SetRedlineMergeFlag(SwNode::Merge::None); + break; // checking once is enough + } + } + } +} + +} // namespace + +SwContentNode *SwTextNode::JoinNext() +{ + SwNodes& rNds = GetNodes(); + SwNodeIndex aIdx( *this ); + if( SwContentNode::CanJoinNext( &aIdx ) ) + { + SwDoc* pDoc = rNds.GetDoc(); + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save(pDoc, aIdx.GetIndex(), SAL_MAX_INT32); + SwTextNode *pTextNode = aIdx.GetNode().GetTextNode(); + sal_Int32 nOldLen = m_Text.getLength(); + + // METADATA: merge + JoinMetadatable(*pTextNode, !Len(), !pTextNode->Len()); + + SwWrongList *pList = GetWrong(); + if( pList ) + { + pList->JoinList( pTextNode->GetWrong(), nOldLen ); + SetWrongDirty(WrongState::TODO); + SetWrong( nullptr, false ); + } + else + { + pList = pTextNode->GetWrong(); + if( pList ) + { + pList->Move( 0, nOldLen ); + SetWrongDirty(WrongState::TODO); + pTextNode->SetWrong( nullptr, false ); + } + } + + SwGrammarMarkUp *pList3 = GetGrammarCheck(); + if( pList3 ) + { + pList3->JoinGrammarList( pTextNode->GetGrammarCheck(), nOldLen ); + SetGrammarCheckDirty( true ); + SetGrammarCheck( nullptr, false ); + } + else + { + pList3 = pTextNode->GetGrammarCheck(); + if( pList3 ) + { + pList3->MoveGrammar( 0, nOldLen ); + SetGrammarCheckDirty( true ); + pTextNode->SetGrammarCheck( nullptr, false ); + } + } + + SwWrongList *pList2 = GetSmartTags(); + if( pList2 ) + { + pList2->JoinList( pTextNode->GetSmartTags(), nOldLen ); + SetSmartTagDirty( true ); + SetSmartTags( nullptr, false ); + } + else + { + pList2 = pTextNode->GetSmartTags(); + if( pList2 ) + { + pList2->Move( 0, nOldLen ); + SetSmartTagDirty( true ); + pTextNode->SetSmartTags( nullptr, false ); + } + } + + { // scope for SwIndex + pTextNode->CutText( this, SwIndex(pTextNode), pTextNode->Len() ); + } + // move all Bookmarks/TOXMarks + if( !pContentStore->Empty()) + pContentStore->Restore( pDoc, GetIndex(), nOldLen ); + + if( pTextNode->HasAnyIndex() ) + { + // move all ShellCursor/StackCursor/UnoCursor out of delete range + pDoc->CorrAbs( aIdx, SwPosition( *this ), nOldLen, true ); + } + SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag()); + rNds.Delete(aIdx); + SetWrong( pList, false ); + SetGrammarCheck( pList3, false ); + SetSmartTags( pList2, false ); + InvalidateNumRule(); + CheckResetRedlineMergeFlag(*this, eOldMergeFlag == SwNode::Merge::First + ? sw::Recreate::ThisNode + : sw::Recreate::No); + } + else { + OSL_FAIL( "No TextNode." ); + } + + return this; +} + +void SwTextNode::JoinPrev() +{ + SwNodes& rNds = GetNodes(); + SwNodeIndex aIdx( *this ); + if( SwContentNode::CanJoinPrev( &aIdx ) ) + { + SwDoc* pDoc = rNds.GetDoc(); + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( pDoc, aIdx.GetIndex(), SAL_MAX_INT32); + SwTextNode *pTextNode = aIdx.GetNode().GetTextNode(); + const sal_Int32 nLen = pTextNode->Len(); + + SwWrongList *pList = pTextNode->GetWrong(); + if( pList ) + { + pList->JoinList( GetWrong(), Len() ); + SetWrongDirty(WrongState::TODO); + pTextNode->SetWrong( nullptr, false ); + SetWrong( nullptr ); + } + else + { + pList = GetWrong(); + if( pList ) + { + pList->Move( 0, nLen ); + SetWrongDirty(WrongState::TODO); + SetWrong( nullptr, false ); + } + } + + SwGrammarMarkUp *pList3 = pTextNode->GetGrammarCheck(); + if( pList3 ) + { + pList3->JoinGrammarList( GetGrammarCheck(), Len() ); + SetGrammarCheckDirty( true ); + pTextNode->SetGrammarCheck( nullptr, false ); + SetGrammarCheck( nullptr ); + } + else + { + pList3 = GetGrammarCheck(); + if( pList3 ) + { + pList3->MoveGrammar( 0, nLen ); + SetGrammarCheckDirty( true ); + SetGrammarCheck( nullptr, false ); + } + } + + SwWrongList *pList2 = pTextNode->GetSmartTags(); + if( pList2 ) + { + pList2->JoinList( GetSmartTags(), Len() ); + SetSmartTagDirty( true ); + pTextNode->SetSmartTags( nullptr, false ); + SetSmartTags( nullptr ); + } + else + { + pList2 = GetSmartTags(); + if( pList2 ) + { + pList2->Move( 0, nLen ); + SetSmartTagDirty( true ); + SetSmartTags( nullptr, false ); + } + } + + { // scope for SwIndex + pTextNode->CutText( this, SwIndex(this), SwIndex(pTextNode), nLen ); + } + // move all Bookmarks/TOXMarks + if( !pContentStore->Empty() ) + pContentStore->Restore( pDoc, GetIndex() ); + + if( pTextNode->HasAnyIndex() ) + { + // move all ShellCursor/StackCursor/UnoCursor out of delete range + pDoc->CorrAbs( aIdx, SwPosition( *this ), nLen, true ); + } + SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag()); + if (eOldMergeFlag == SwNode::Merge::First + && !IsCreateFrameWhenHidingRedlines()) + { + sw::MoveDeletedPrevFrames(*pTextNode, *this); + } + rNds.Delete(aIdx); + SetWrong( pList, false ); + SetGrammarCheck( pList3, false ); + SetSmartTags( pList2, false ); + InvalidateNumRule(); + sw::CheckResetRedlineMergeFlag(*this, + eOldMergeFlag == SwNode::Merge::NonFirst + ? sw::Recreate::Predecessor + : sw::Recreate::No); + } + else { + OSL_FAIL( "No TextNode." ); + } +} + +// create an AttrSet with ranges for Frame-/Para/Char-attributes +void SwTextNode::NewAttrSet( SwAttrPool& rPool ) +{ + OSL_ENSURE( !mpAttrSet, "AttrSet is set after all" ); + SwAttrSet aNewAttrSet( rPool, aTextNodeSetRange ); + + // put names of parent style and conditional style: + const SwFormatColl* pAnyFormatColl = &GetAnyFormatColl(); + const SwFormatColl* pFormatColl = GetFormatColl(); + OUString sVal; + SwStyleNameMapper::FillProgName( pAnyFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + SfxStringItem aAnyFormatColl( RES_FRMATR_STYLE_NAME, sVal ); + if ( pFormatColl != pAnyFormatColl ) + SwStyleNameMapper::FillProgName( pFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + SfxStringItem aFormatColl( RES_FRMATR_CONDITIONAL_STYLE_NAME, sVal ); + aNewAttrSet.Put( aAnyFormatColl ); + aNewAttrSet.Put( aFormatColl ); + + aNewAttrSet.SetParent( &pAnyFormatColl->GetAttrSet() ); + mpAttrSet = GetDoc()->GetIStyleAccess().getAutomaticStyle( aNewAttrSet, IStyleAccess::AUTO_STYLE_PARA, &sVal ); +} + +// override SwIndexReg::Update => text hints do not need SwIndex for start/end! +void SwTextNode::Update( + SwIndex const & rPos, + const sal_Int32 nChangeLen, + const bool bNegative, + const bool bDelete ) +{ + SetAutoCompleteWordDirty( true ); + + std::unique_ptr<SwpHts> pCollector; + const sal_Int32 nChangePos = rPos.GetIndex(); + + if ( HasHints() ) + { + if ( bNegative ) + { + std::vector<SwTextInputField*> aTextInputFields; + + const sal_Int32 nChangeEnd = nChangePos + nChangeLen; + for ( size_t n = 0; n < m_pSwpHints->Count(); ++n ) + { + bool bTextAttrChanged = false; + bool bStartOfTextAttrChanged = false; + SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n); + if ( pHint->GetStart() > nChangePos ) + { + if ( pHint->GetStart() > nChangeEnd ) + { + pHint->SetStart( pHint->GetStart() - nChangeLen ); + } + else + { + pHint->SetStart( nChangePos ); + } + bStartOfTextAttrChanged = true; + } + + const sal_Int32 * const pEnd = pHint->GetEnd(); + if (pEnd && *pEnd > nChangePos ) + { + if( *pEnd > nChangeEnd ) + { + pHint->SetEnd(*pEnd - nChangeLen); + } + else + { + pHint->SetEnd(nChangePos); + } + bTextAttrChanged = !bStartOfTextAttrChanged; + } + + if ( bTextAttrChanged + && pHint->Which() == RES_TXTATR_INPUTFIELD ) + { + SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint); + if ( pTextInputField ) + aTextInputFields.push_back(pTextInputField); + } + } + + //wait until all the attribute positions are correct + //before updating the field contents + for (SwTextInputField* pTextInputField : aTextInputFields) + { + pTextInputField->UpdateFieldContent(); + } + + m_pSwpHints->MergePortions( *this ); + } + else + { + bool bNoExp = false; + bool bResort = false; + bool bMergePortionsNeeded = false; + const int coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN; + std::vector<SwTextInputField*> aTextInputFields; + + bool aDontExp[ coArrSz ] = {}; + + for ( size_t n = 0; n < m_pSwpHints->Count(); ++n ) + { + bool bTextAttrChanged = false; + SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n); + const sal_Int32 * const pEnd = pHint->GetEnd(); + if ( pHint->GetStart() >= nChangePos ) + { + pHint->SetStart( pHint->GetStart() + nChangeLen ); + if ( pEnd ) + { + pHint->SetEnd(*pEnd + nChangeLen); + } + } + else if ( pEnd && (*pEnd >= nChangePos) ) + { + if ( (*pEnd > nChangePos) || IsIgnoreDontExpand() ) + { + pHint->SetEnd(*pEnd + nChangeLen); + bTextAttrChanged = true; + } + else // *pEnd == nChangePos + { + const sal_uInt16 nWhich = pHint->Which(); + + OSL_ENSURE(!isCHRATR(nWhich), "Update: char attr hint?"); + if (!(isCHRATR(nWhich) || isTXTATR_WITHEND(nWhich))) + continue; + + const sal_uInt16 nWhPos = nWhich - RES_CHRATR_BEGIN; + + if( aDontExp[ nWhPos ] ) + continue; + + if ( pHint->DontExpand() ) + { + pHint->SetDontExpand( false ); + bResort = true; + // could have a continuation with IgnoreStart()... + if (pHint->IsFormatIgnoreEnd()) + { + bMergePortionsNeeded = true; + } + if ( pHint->IsCharFormatAttr() ) + { + bNoExp = true; + aDontExp[ RES_TXTATR_CHARFMT - RES_CHRATR_BEGIN ] = true; + aDontExp[ RES_TXTATR_INETFMT - RES_CHRATR_BEGIN ] = true; + } + else + aDontExp[ nWhPos ] = true; + } + else if( bNoExp ) + { + if (!pCollector) + { + pCollector.reset( new SwpHts ); + } + auto it = std::find_if(pCollector->begin(), pCollector->end(), + [nWhich](const SwTextAttr *pTmp) { return nWhich == pTmp->Which(); }); + if (it != pCollector->end()) + { + SwTextAttr *pTmp = *it; + pCollector->erase( it ); + SwTextAttr::Destroy( pTmp, GetDoc()->GetAttrPool() ); + } + SwTextAttr * const pTmp = + MakeTextAttr( *GetDoc(), + pHint->GetAttr(), nChangePos, nChangePos + nChangeLen); + pCollector->push_back( pTmp ); + } + else + { + pHint->SetEnd(*pEnd + nChangeLen); + bTextAttrChanged = true; + } + } + } + + if ( bTextAttrChanged + && pHint->Which() == RES_TXTATR_INPUTFIELD ) + { + SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint); + if ( pTextInputField ) + aTextInputFields.push_back(pTextInputField); + } + } + + //wait until all the attribute positions are correct + //before updating the field contents + for (SwTextInputField* pTextInputField : aTextInputFields) + { + pTextInputField->UpdateFieldContent(); + } + + if (bMergePortionsNeeded) + { + m_pSwpHints->MergePortions(*this); // does Resort too + } + else if (bResort) + { + m_pSwpHints->Resort(); + } + } + } + + bool bSortMarks = false; + SwIndexReg aTmpIdxReg; + if ( !bNegative && !bDelete ) + { + const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRangeRedline* pRedl : rTable) + { + if ( pRedl->HasMark() ) + { + SwPosition* const pEnd = pRedl->End(); + if ( this == &pEnd->nNode.GetNode() && + *pRedl->GetPoint() != *pRedl->GetMark() ) + { + SwIndex & rIdx = pEnd->nContent; + if (nChangePos == rIdx.GetIndex()) + { + rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() ); + } + } + } + else if ( this == &pRedl->GetPoint()->nNode.GetNode() ) + { + SwIndex & rIdx = pRedl->GetPoint()->nContent; + if (nChangePos == rIdx.GetIndex()) + { + rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() ); + } + // the unused position must not be on a SwTextNode + bool const isOneUsed(&pRedl->GetBound() == pRedl->GetPoint()); + assert(!pRedl->GetBound(!isOneUsed).nNode.GetNode().IsTextNode()); + assert(!pRedl->GetBound(!isOneUsed).nContent.GetIdxReg()); (void)isOneUsed; + } + } + + // Bookmarks must never grow to either side, when editing (directly) + // to the left or right (i#29942)! Exception: if the bookmark has + // 2 positions and start == end, then expand it (tdf#96479) + { + bool bAtLeastOneBookmarkMoved = false; + bool bAtLeastOneExpandedBookmarkAtInsertionPosition = false; + // A text node already knows its marks via its SwIndexes. + o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks; + const SwIndex* next; + for (const SwIndex* pIndex = GetFirstIndex(); pIndex; pIndex = next ) + { + next = pIndex->GetNext(); + const sw::mark::IMark* pMark = pIndex->GetMark(); + if (!pMark) + continue; + // Only handle bookmarks once, if they start and end at this node as well. + if (!aSeenMarks.insert(pMark).second) + continue; + const SwPosition* pEnd = &pMark->GetMarkEnd(); + SwIndex & rEndIdx = const_cast<SwIndex&>(pEnd->nContent); + if( this == &pEnd->nNode.GetNode() && + rPos.GetIndex() == rEndIdx.GetIndex() ) + { + if (&rEndIdx == next) // nasty corner case: + { // don't switch to iterating aTmpIdxReg! + next = rEndIdx.GetNext(); + } + // tdf#96479: if start == end, ignore the other position + // so it is moved! + rEndIdx.Assign( &aTmpIdxReg, rEndIdx.GetIndex() ); + bAtLeastOneBookmarkMoved = true; + } + else if ( !bAtLeastOneExpandedBookmarkAtInsertionPosition ) + { + if ( pMark->IsExpanded() ) + { + const SwPosition* pStart = &pMark->GetMarkStart(); + if ( this == &pStart->nNode.GetNode() + && rPos.GetIndex() == pStart->nContent.GetIndex() ) + { + bAtLeastOneExpandedBookmarkAtInsertionPosition = true; + } + } + } + } + + bSortMarks = bAtLeastOneBookmarkMoved && bAtLeastOneExpandedBookmarkAtInsertionPosition; + } + + // at-char anchored flys shouldn't be moved, either. +#if OSL_DEBUG_LEVEL > 0 + std::vector<SwFrameFormat*> checkFormats; + const SwFrameFormats& rFormats = *GetDoc()->GetSpzFrameFormats(); + for (auto& rpFormat : rFormats) + { + const SwFormatAnchor& rAnchor = rpFormat->GetAnchor(); + const SwPosition* pContentAnchor = rAnchor.GetContentAnchor(); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pContentAnchor) + { + // The fly is at-char anchored and has an anchor position. + SwIndex& rEndIdx = const_cast<SwIndex&>(pContentAnchor->nContent); + if (&pContentAnchor->nNode.GetNode() == this && rEndIdx.GetIndex() == rPos.GetIndex()) + { + // The anchor position is exactly our insert position. + #if 0 + rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex()); + #endif + checkFormats.push_back( rpFormat ); + } + } + } +#endif + std::vector<SwFrameFormat*> const*const pFlys(GetAnchoredFlys()); + for (size_t i = 0; pFlys && i != pFlys->size(); ++i) + { + SwFrameFormat const*const pFormat = (*pFlys)[i]; + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + const SwPosition* pContentAnchor = rAnchor.GetContentAnchor(); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pContentAnchor) + { + // The fly is at-char anchored and has an anchor position. + SwIndex& rEndIdx = const_cast<SwIndex&>(pContentAnchor->nContent); + if (&pContentAnchor->nNode.GetNode() == this && rEndIdx.GetIndex() == rPos.GetIndex()) + { + // The anchor position is exactly our insert position. + rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex()); +#if OSL_DEBUG_LEVEL > 0 + auto checkPos = std::find( checkFormats.begin(), checkFormats.end(), pFormat ); + assert( checkPos != checkFormats.end()); + checkFormats.erase( checkPos ); +#endif + } + } + } +#if OSL_DEBUG_LEVEL > 0 + assert( checkFormats.empty()); +#endif + + // The cursors of other shells shouldn't be moved, either. + if (SwDocShell* pDocShell = GetDoc()->GetDocShell()) + { + if (pDocShell->GetWrtShell()) + { + for (SwViewShell& rShell : pDocShell->GetWrtShell()->GetRingContainer()) + { + auto pWrtShell = dynamic_cast<SwWrtShell*>(&rShell); + if (!pWrtShell || pWrtShell == pDocShell->GetWrtShell()) + continue; + + SwShellCursor* pCursor = pWrtShell->GetCursor_(); + if (!pCursor) + continue; + + SwIndex& rIndex = pCursor->Start()->nContent; + if (&pCursor->Start()->nNode.GetNode() == this && rIndex.GetIndex() == rPos.GetIndex()) + { + // The cursor position of this other shell is exactly our insert position. + rIndex.Assign(&aTmpIdxReg, rIndex.GetIndex()); + } + } + } + } + } + + // base class + SwIndexReg::Update( rPos, nChangeLen, bNegative, bDelete ); + + if (pCollector) + { + const size_t nCount = pCollector->size(); + for ( size_t i = 0; i < nCount; ++i ) + { + m_pSwpHints->TryInsertHint( (*pCollector)[ i ], *this ); + } + } + + aTmpIdxReg.MoveTo( *this ); + if ( bSortMarks ) + { + getIDocumentMarkAccess()->assureSortedMarkContainers(); + } + + //Any drawing objects anchored into this text node may be sorted by their + //anchor position which may have changed here, so resort them + SwContentFrame* pContentFrame = getLayoutFrame(GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()); + SwSortedObjs* pSortedObjs = pContentFrame ? pContentFrame->GetDrawObjs() : nullptr; + if (pSortedObjs) + pSortedObjs->UpdateAll(); + + // Update the paragraph signatures. + if (SwEditShell* pEditShell = GetDoc()->GetEditShell()) + { + pEditShell->ValidateParagraphSignatures(this, true); + } + + // Inform LOK clients about change in position of redlines (if any) + // Don't emit notifications during save: redline flags are temporarily changed during save, but + // it's not useful to let clients know about such changes. + if (comphelper::LibreOfficeKit::isActive() && !GetDoc()->IsInWriting()) + { + const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos) + { + SwRangeRedline* pRedln = rTable[nRedlnPos]; + if (pRedln->HasMark()) + { + if (this == &pRedln->End()->nNode.GetNode() && *pRedln->GetPoint() != *pRedln->GetMark()) + { + // Redline is changed only when some change occurs before it + if (nChangePos <= pRedln->Start()->nContent.GetIndex()) + { + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln); + } + } + } + else if (this == &pRedln->GetPoint()->nNode.GetNode()) + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln); + } + } +} + +void SwTextNode::ChgTextCollUpdateNum( const SwTextFormatColl *pOldColl, + const SwTextFormatColl *pNewColl) +{ + SwDoc* pDoc = GetDoc(); + OSL_ENSURE( pDoc, "No Doc?" ); + // query the OutlineLevel and if it changed, notify the Nodes-Array! + const int nOldLevel = pOldColl && pOldColl->IsAssignedToListLevelOfOutlineStyle() ? + pOldColl->GetAssignedOutlineStyleLevel() : MAXLEVEL; + const int nNewLevel = pNewColl && pNewColl->IsAssignedToListLevelOfOutlineStyle() ? + pNewColl->GetAssignedOutlineStyleLevel() : MAXLEVEL; + + if ( MAXLEVEL != nNewLevel && -1 != nNewLevel ) + { + SetAttrListLevel(nNewLevel); + } + if (pDoc) + { + pDoc->GetNodes().UpdateOutlineNode(*this); + } + + SwNodes& rNds = GetNodes(); + // If Level 0 (Chapter), update the footnotes! + if( ( !nNewLevel || !nOldLevel) && pDoc && !pDoc->GetFootnoteIdxs().empty() && + FTNNUM_CHAPTER == pDoc->GetFootnoteInfo().m_eNum && + rNds.IsDocNodes() ) + { + SwNodeIndex aTmpIndex( rNds, GetIndex()); + + pDoc->GetFootnoteIdxs().UpdateFootnote( aTmpIndex); + } + + if( pNewColl && RES_CONDTXTFMTCOLL == pNewColl->Which() ) + { + // check the condition of the text node again + ChkCondColl(); + } +} + +// If positioned exactly at the end of a CharStyle or Hyperlink, +// set its DontExpand flag. +bool SwTextNode::DontExpandFormat( const SwIndex& rIdx, bool bFlag, + bool bFormatToTextAttributes ) +{ + const sal_Int32 nIdx = rIdx.GetIndex(); + if (bFormatToTextAttributes && nIdx == m_Text.getLength()) + { + FormatToTextAttr( this ); + } + + bool bRet = false; + if ( HasHints() ) + { + m_pSwpHints->SortIfNeedBe(); + int nPos = m_pSwpHints->GetLastPosSortedByEnd(nIdx); + for ( ; nPos >= 0; --nPos) + { + SwTextAttr *pTmp = m_pSwpHints->GetSortedByEnd( nPos ); + const sal_Int32 *pEnd = pTmp->GetEnd(); + if( !pEnd ) + continue; + assert( *pEnd <= nIdx ); + if( nIdx != *pEnd ) + break; + if( bFlag != pTmp->DontExpand() && !pTmp->IsLockExpandFlag() + && *pEnd > pTmp->GetStart()) + { + bRet = true; + m_pSwpHints->NoteInHistory( pTmp ); + pTmp->SetDontExpand( bFlag ); + } + } + } + return bRet; +} + +static bool lcl_GetTextAttrDefault(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd) +{ + return ((nHintStart <= nIndex) && (nIndex < nHintEnd)); +} +static bool lcl_GetTextAttrExpand(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd) +{ + return ((nHintStart < nIndex) && (nIndex <= nHintEnd)); +} +static bool lcl_GetTextAttrParent(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd) +{ + return ((nHintStart < nIndex) && (nIndex < nHintEnd)); +} + +static void +lcl_GetTextAttrs( + std::vector<SwTextAttr *> *const pVector, + SwTextAttr **const ppTextAttr, + SwpHints const *const pSwpHints, + sal_Int32 const nIndex, sal_uInt16 const nWhich, + enum SwTextNode::GetTextAttrMode const eMode) +{ + assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END); + if (!pSwpHints) + return; + size_t const nSize = pSwpHints->Count(); + sal_Int32 nPreviousIndex(0); // index of last hint with nWhich + bool (*pMatchFunc)(sal_Int32, sal_Int32, sal_Int32)=nullptr; + switch (eMode) + { + case SwTextNode::DEFAULT: pMatchFunc = &lcl_GetTextAttrDefault; break; + case SwTextNode::EXPAND: pMatchFunc = &lcl_GetTextAttrExpand; break; + case SwTextNode::PARENT: pMatchFunc = &lcl_GetTextAttrParent; break; + default: assert(false); + } + + for( size_t i = pSwpHints->GetFirstPosSortedByWhichAndStart(nWhich); i < nSize; ++i ) + { + SwTextAttr *const pHint = pSwpHints->GetSortedByWhichAndStart(i); + if (pHint->Which() != nWhich) + break; // hints are sorted by which&start, so we are done... + + sal_Int32 const nHintStart = pHint->GetStart(); + if (nIndex < nHintStart) + break; // hints are sorted by which&start, so we are done... + + sal_Int32 const*const pEndIdx = pHint->GetEnd(); + // cannot have hint with no end and no dummy char + assert(pEndIdx || pHint->HasDummyChar()); + // If EXPAND is set, simulate the text input behavior, i.e. + // move the start, and expand the end. + bool const bContained( pEndIdx + ? (*pMatchFunc)(nIndex, nHintStart, *pEndIdx) + : (nHintStart == nIndex) ); + if (bContained) + { + if (pVector) + { + if (nPreviousIndex < nHintStart) + { + pVector->clear(); // clear hints that are outside pHint + nPreviousIndex = nHintStart; + } + pVector->push_back(pHint); + } + else + { + *ppTextAttr = pHint; // and possibly overwrite outer hint + } + if (!pEndIdx) + { + break; + } + } + } +} + +std::vector<SwTextAttr *> +SwTextNode::GetTextAttrsAt(sal_Int32 const nIndex, sal_uInt16 const nWhich) const +{ + assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END); + std::vector<SwTextAttr *> ret; + lcl_GetTextAttrs(&ret, nullptr, m_pSwpHints.get(), nIndex, nWhich, DEFAULT); + return ret; +} + +SwTextAttr * +SwTextNode::GetTextAttrAt(sal_Int32 const nIndex, sal_uInt16 const nWhich, + enum GetTextAttrMode const eMode) const +{ + assert( (nWhich == RES_TXTATR_META) + || (nWhich == RES_TXTATR_METAFIELD) + || (nWhich == RES_TXTATR_AUTOFMT) + || (nWhich == RES_TXTATR_INETFMT) + || (nWhich == RES_TXTATR_CJK_RUBY) + || (nWhich == RES_TXTATR_UNKNOWN_CONTAINER) + || (nWhich == RES_TXTATR_INPUTFIELD ) ); + // "GetTextAttrAt() will give wrong result for this hint!") + + SwTextAttr * pRet(nullptr); + lcl_GetTextAttrs(nullptr, & pRet, m_pSwpHints.get(), nIndex, nWhich, eMode); + return pRet; +} + +const SwTextInputField* SwTextNode::GetOverlappingInputField( const SwTextAttr& rTextAttr ) const +{ + const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt( rTextAttr.GetStart(), RES_TXTATR_INPUTFIELD, PARENT )); + + if ( pTextInputField == nullptr && rTextAttr.End() != nullptr ) + { + pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt( *(rTextAttr.End()), RES_TXTATR_INPUTFIELD, PARENT )); + } + + return pTextInputField; +} + +void SwTextNode::DelFrames_TextNodePart() +{ + SetWrong( nullptr ); + SetWrongDirty(WrongState::TODO); + + SetGrammarCheck( nullptr ); + SetGrammarCheckDirty( true ); + + SetSmartTags( nullptr ); + SetSmartTagDirty( true ); + + SetWordCountDirty( true ); + SetAutoCompleteWordDirty( true ); +} + +SwTextField* SwTextNode::GetFieldTextAttrAt( + const sal_Int32 nIndex, + const bool bIncludeInputFieldAtStart ) const +{ + SwTextField* pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_FIELD )); + if ( pTextField == nullptr ) + { + pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_ANNOTATION )); + } + if ( pTextField == nullptr ) + { + pTextField = + dynamic_cast<SwTextField*>( GetTextAttrAt( + nIndex, + RES_TXTATR_INPUTFIELD, + bIncludeInputFieldAtStart ? DEFAULT : EXPAND )); + } + + return pTextField; +} + +static SwCharFormat* lcl_FindCharFormat( const SwCharFormats* pCharFormats, const OUString& rName ) +{ + if( !rName.isEmpty() ) + { + const size_t nArrLen = pCharFormats->size(); + for( size_t i = 1; i < nArrLen; i++ ) + { + SwCharFormat* pFormat = (*pCharFormats)[ i ]; + if( pFormat->GetName()==rName ) + return pFormat; + } + } + return nullptr; +} + +static void lcl_CopyHint( + const sal_uInt16 nWhich, + const SwTextAttr * const pHt, + SwTextAttr *const pNewHt, + SwDoc *const pOtherDoc, + SwTextNode *const pDest ) +{ + assert(nWhich == pHt->Which()); // wrong hint-id + switch( nWhich ) + { + // copy nodesarray section with footnote content + case RES_TXTATR_FTN : + assert(pDest); // "lcl_CopyHint: no destination text node?" + static_cast<const SwTextFootnote*>(pHt)->CopyFootnote( *static_cast<SwTextFootnote*>(pNewHt), *pDest); + break; + + // Fields that are copied into different SwDocs must be registered + // at their new FieldTypes. + + case RES_TXTATR_FIELD : + { + if( pOtherDoc != nullptr ) + { + static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField( + static_txtattr_cast<SwTextField*>(pNewHt)); + } + + // Table Formula must be copied relative. + const SwFormatField& rField = pHt->GetFormatField(); + if( SwFieldIds::Table == rField.GetField()->GetTyp()->Which() + && static_cast<const SwTableField*>(rField.GetField())->IsIntrnlName()) + { + // convert internal formula to external + const SwTableNode* const pDstTableNd = + static_txtattr_cast<const SwTextField*>(pHt)->GetTextNode().FindTableNode(); + if( pDstTableNd ) + { + SwTableField* const pTableField = + const_cast<SwTableField*>(static_cast<const SwTableField*>( + pNewHt->GetFormatField().GetField())); + pTableField->PtrToBoxNm( &pDstTableNd->GetTable() ); + } + } + } + break; + + case RES_TXTATR_INPUTFIELD : + case RES_TXTATR_ANNOTATION : + if( pOtherDoc != nullptr ) + { + static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField( + static_txtattr_cast<SwTextField*>(pNewHt)); + } + break; + + case RES_TXTATR_TOXMARK : + if( pOtherDoc && pDest && pDest->GetpSwpHints() + && pDest->GetpSwpHints()->Contains( pNewHt ) ) + { + // ToXMarks that are copied to different SwDocs must register + // at their new ToX (SwModify). + static_txtattr_cast<SwTextTOXMark*>(pNewHt)->CopyTOXMark(pOtherDoc); + } + break; + + case RES_TXTATR_CHARFMT : + // For CharacterStyles, the format must be copied too. + if( pDest && pDest->GetpSwpHints() + && pDest->GetpSwpHints()->Contains( pNewHt ) ) + { + SwCharFormat* pFormat = pHt->GetCharFormat().GetCharFormat(); + + if (pOtherDoc) + { + pFormat = pOtherDoc->CopyCharFormat( *pFormat ); + } + const_cast<SwFormatCharFormat&>( + pNewHt->GetCharFormat() ).SetCharFormat( pFormat ); + } + break; + case RES_TXTATR_INETFMT : + { + // For Hyperlinks, the format must be copied too. + if( pOtherDoc && pDest && pDest->GetpSwpHints() + && pDest->GetpSwpHints()->Contains( pNewHt ) ) + { + const SwDoc* const pDoc = static_txtattr_cast< + const SwTextINetFormat*>(pHt)->GetTextNode().GetDoc(); + if ( pDoc ) + { + const SwCharFormats* pCharFormats = pDoc->GetCharFormats(); + const SwFormatINetFormat& rFormat = pHt->GetINetFormat(); + SwCharFormat* pFormat; + pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetINetFormat() ); + if( pFormat ) + pOtherDoc->CopyCharFormat( *pFormat ); + pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetVisitedFormat() ); + if( pFormat ) + pOtherDoc->CopyCharFormat( *pFormat ); + } + } + //JP 24.04.98: The attribute must point to a text node, so that + // the styles can be created. + SwTextINetFormat *const pINetHt = static_txtattr_cast<SwTextINetFormat*>(pNewHt); + if ( !pINetHt->GetpTextNode() ) + { + pINetHt->ChgTextNode( pDest ); + } + + //JP 22.10.97: set up link to char style + pINetHt->GetCharFormat(); + break; + } + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + OSL_ENSURE( pNewHt, "copying Meta should not fail!" ); + OSL_ENSURE( pDest + && (CH_TXTATR_INWORD == pDest->GetText()[pNewHt->GetStart()]), + "missing CH_TXTATR?"); + break; + } +} + +/// copy attributes at position nTextStartIdx to node pDest +// BP 7.6.93: Intentionally copy only attributes _with_ EndIdx! +// CopyAttr is usually called when attributes are set on a +// node with no text. +void SwTextNode::CopyAttr( SwTextNode *pDest, const sal_Int32 nTextStartIdx, + const sal_Int32 nOldPos ) +{ + if ( HasHints() ) + { + SwDoc* const pOtherDoc = (pDest->GetDoc() != GetDoc()) ? + pDest->GetDoc() : nullptr; + + for ( size_t i = 0; i < m_pSwpHints->Count(); ++i ) + { + SwTextAttr *const pHt = m_pSwpHints->Get(i); + sal_Int32 const nAttrStartIdx = pHt->GetStart(); + if ( nTextStartIdx < nAttrStartIdx ) + break; // beyond end of text, because nLen == 0 + + const sal_Int32 *const pEndIdx = pHt->GetEnd(); + if ( pEndIdx && !pHt->HasDummyChar() ) + { + sal_uInt16 const nWhich = pHt->Which(); + if (RES_TXTATR_INPUTFIELD != nWhich // fdo#74981 skip fields + && ( *pEndIdx > nTextStartIdx + || (*pEndIdx == nTextStartIdx + && nAttrStartIdx == nTextStartIdx))) + { + if ( RES_TXTATR_REFMARK != nWhich ) + { + // attribute in the area => copy + SwTextAttr *const pNewHt = + pDest->InsertItem( pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY); + if ( pNewHt ) + { + lcl_CopyHint( nWhich, pHt, pNewHt, + pOtherDoc, pDest ); + } + } + else if( !pOtherDoc + ? GetDoc()->IsCopyIsMove() + : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) ) + { + pDest->InsertItem( + pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY); + } + } + } + } + } + + if( this != pDest ) + { + // notify layout frames, to prevent disappearance of footnote numbers + SwUpdateAttr aHint( + nOldPos, + nOldPos, + 0); + + pDest->ModifyNotification( nullptr, &aHint ); + } +} + +/// copy text and attributes to node pDest +void SwTextNode::CopyText( SwTextNode *const pDest, + const SwIndex &rStart, + const sal_Int32 nLen, + const bool bForceCopyOfAllAttrs ) +{ + SwIndex const aIdx( pDest, pDest->m_Text.getLength() ); + CopyText( pDest, aIdx, rStart, nLen, bForceCopyOfAllAttrs ); +} + +void SwTextNode::CopyText( SwTextNode *const pDest, + const SwIndex &rDestStart, + const SwIndex &rStart, + sal_Int32 nLen, + const bool bForceCopyOfAllAttrs ) +{ + CHECK_SWPHINTS_IF_FRM(this); + CHECK_SWPHINTS(pDest); + sal_Int32 nTextStartIdx = rStart.GetIndex(); + sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos + + if (pDest->GetDoc()->IsClipBoard() && GetNum()) + { + // #i111677# cache expansion of source (for clipboard) + pDest->m_pNumStringCache.reset( (nTextStartIdx != 0) + ? new OUString // fdo#49076: numbering only if copy from para start + : new OUString(GetNumString())); + } + + if( !nLen ) + { + // if no length is given, copy attributes at position rStart + CopyAttr( pDest, nTextStartIdx, nDestStart ); + + // copy hard attributes on whole paragraph + if( HasSwAttrSet() ) + { + // i#96213 all or just the Char attributes? + if ( !bForceCopyOfAllAttrs && + ( nDestStart || + pDest->HasSwAttrSet() || + nLen != pDest->GetText().getLength())) + { + SfxItemSet aCharSet( + pDest->GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{} ); + aCharSet.Put( *GetpSwAttrSet() ); + if( aCharSet.Count() ) + { + pDest->SetAttr( aCharSet, nDestStart, nDestStart ); + } + } + else + { + GetpSwAttrSet()->CopyToModify( *pDest ); + } + } + return; + } + + // 1. copy text + const sal_Int32 oldLen = pDest->m_Text.getLength(); + // JP 15.02.96: missing attribute handling at the end! + // hence call InsertText and don't modify m_Text directly + pDest->InsertText( m_Text.copy(nTextStartIdx, nLen), rDestStart, + SwInsertFlags::EMPTYEXPAND ); + + // update with actual new size + nLen = pDest->m_Text.getLength() - oldLen; + if ( !nLen ) // string not longer? + return; + + SwDoc* const pOtherDoc = (pDest->GetDoc() != GetDoc()) ? pDest->GetDoc() : nullptr; + + // copy hard attributes on whole paragraph + if( HasSwAttrSet() ) + { + // i#96213 all or just the Char attributes? + if ( !bForceCopyOfAllAttrs && + ( nDestStart || + pDest->HasSwAttrSet() || + nLen != pDest->GetText().getLength())) + { + SfxItemSet aCharSet( + pDest->GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{}); + aCharSet.Put( *GetpSwAttrSet() ); + if( aCharSet.Count() ) + { + pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen ); + } + } + else + { + GetpSwAttrSet()->CopyToModify( *pDest ); + } + } + + bool const bUndoNodes = !pOtherDoc + && GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(GetNodes()); + + // Fetch end only now, because copying into self updates the start index + // and all attributes + nTextStartIdx = rStart.GetIndex(); + const sal_Int32 nEnd = nTextStartIdx + nLen; + + // 2. copy attributes + // Iterate over attribute array until the start of the attribute + // is behind the copied range + const size_t nSize = m_pSwpHints ? m_pSwpHints->Count() : 0; + + // If copying into self, inserting can delete attributes! + // Hence first copy into temp-array, and then move that into hints array. + SwpHts aArr; + + // Del-Array for all RefMarks without extent + SwpHts aRefMrkArr; + + std::vector<std::pair<sal_Int32, sal_Int32>> metaFieldRanges; + sal_Int32 nDeletedDummyChars(0); + for (size_t n = 0; n < nSize; ++n) + { + SwTextAttr * const pHt = m_pSwpHints->Get(n); + + const sal_Int32 nAttrStartIdx = pHt->GetStart(); + if ( nAttrStartIdx >= nEnd ) + break; + + const sal_Int32 * const pEndIdx = pHt->GetEnd(); + const sal_uInt16 nWhich = pHt->Which(); + + // JP 26.04.94: RefMarks are never copied. If the refmark doesn't have + // an extent, there is a dummy char in the text, which + // must be removed. So we first copy the attribute, + // but remember it, and when we're done delete it, + // which also deletes the dummy character! + // JP 14.08.95: May RefMarks be moved? + const bool bCopyRefMark = RES_TXTATR_REFMARK == nWhich + && ( bUndoNodes + || ( !pOtherDoc + ? GetDoc()->IsCopyIsMove() + : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) ) ); + + if ( pEndIdx + && RES_TXTATR_REFMARK == nWhich + && !bCopyRefMark ) + { + continue; + } + + // Input Fields are only copied, if completely covered by copied text + if ( nWhich == RES_TXTATR_INPUTFIELD ) + { + assert(pEndIdx != nullptr && + "<SwTextNode::CopyText(..)> - RES_TXTATR_INPUTFIELD without EndIndex!" ); + if ( nAttrStartIdx < nTextStartIdx + || ( pEndIdx != nullptr + && *pEndIdx > nEnd ) ) + { + continue; + } + } + + if (nWhich == RES_TXTATR_METAFIELD) + { + // Skip metadata fields. Also remember the range to strip the text later. + metaFieldRanges.emplace_back(nAttrStartIdx, pEndIdx ? *pEndIdx : nEnd); + continue; + } + + sal_Int32 nAttrStt = 0; + sal_Int32 nAttrEnd = 0; + + if( nAttrStartIdx < nTextStartIdx ) + { + // start is before selection + // copy hints with end and CH_TXTATR only if dummy char is copied + if ( pEndIdx && (*pEndIdx > nTextStartIdx) && !pHt->HasDummyChar() ) + { + // attribute with extent and the end is in the selection + nAttrStt = nDestStart; + nAttrEnd = (*pEndIdx > nEnd) + ? rDestStart.GetIndex() + : nDestStart + (*pEndIdx) - nTextStartIdx; + } + else + { + continue; + } + } + else + { + // start is in the selection + nAttrStt = nDestStart + ( nAttrStartIdx - nTextStartIdx ); + if( pEndIdx ) + { + nAttrEnd = *pEndIdx > nEnd + ? rDestStart.GetIndex() + : nDestStart + ( *pEndIdx - nTextStartIdx ); + } + else + { + nAttrEnd = nAttrStt; + } + } + + SwTextAttr * pNewHt = nullptr; + + if( pDest == this ) + { + // copy the hint here, but insert it later + pNewHt = MakeTextAttr( *GetDoc(), pHt->GetAttr(), + nAttrStt, nAttrEnd, CopyOrNewType::Copy, pDest ); + + lcl_CopyHint(nWhich, pHt, pNewHt, nullptr, pDest); + aArr.push_back( pNewHt ); + } + else + { + pNewHt = pDest->InsertItem( + pHt->GetAttr(), + nAttrStt - nDeletedDummyChars, + nAttrEnd - nDeletedDummyChars, + SetAttrMode::NOTXTATRCHR | SetAttrMode::IS_COPY); + if (pNewHt) + { + lcl_CopyHint( nWhich, pHt, pNewHt, pOtherDoc, pDest ); + } + else if (pHt->HasDummyChar()) + { + // The attribute that has failed to be copied would insert + // dummy char, so positions of the following attributes have + // to be shifted by one to compensate for that missing char. + ++nDeletedDummyChars; + } + } + + if( RES_TXTATR_REFMARK == nWhich && !pEndIdx && !bCopyRefMark ) + { + aRefMrkArr.push_back( pNewHt ); + } + } + + // Strip the metadata fields, since we don't copy the RDF entries + // yet and so they are inconsistent upon copy/pasting. + if (!metaFieldRanges.empty()) + { + // Reverse to remove without messing the offsets. + std::reverse(metaFieldRanges.begin(), metaFieldRanges.end()); + for (const auto& pair : metaFieldRanges) + { + const SwIndex aIdx(pDest, pair.first); + pDest->EraseText(aIdx, pair.second - pair.first); + } + } + + // this can only happen when copying into self + for (SwTextAttr* i : aArr) + { + InsertHint( i, SetAttrMode::NOTXTATRCHR ); + } + + if( pDest->GetpSwpHints() ) + { + for (SwTextAttr* pNewHt : aRefMrkArr) + { + if( pNewHt->GetEnd() ) + { + pDest->GetpSwpHints()->Delete( pNewHt ); + pDest->DestroyAttr( pNewHt ); + } + else + { + const SwIndex aIdx( pDest, pNewHt->GetStart() ); + pDest->EraseText( aIdx, 1 ); + } + } + } + + CHECK_SWPHINTS_IF_FRM(this); + CHECK_SWPHINTS(pDest); +} + +OUString SwTextNode::InsertText( const OUString & rStr, const SwIndex & rIdx, + const SwInsertFlags nMode ) +{ + assert(rIdx <= m_Text.getLength()); // invalid index + + const sal_Int32 aPos = rIdx.GetIndex(); + sal_Int32 nLen = m_Text.getLength() - aPos; + sal_Int32 const nOverflow(rStr.getLength() - GetSpaceLeft()); + SAL_WARN_IF(nOverflow > 0, "sw.core", + "SwTextNode::InsertText: node text with insertion > capacity."); + OUString const sInserted( + (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr); + if (sInserted.isEmpty()) + { + return sInserted; + } + m_Text = m_Text.replaceAt(aPos, 0, sInserted); + assert(GetSpaceLeft()>=0); + nLen = m_Text.getLength() - aPos - nLen; + assert(nLen != 0); + + bool bOldExpFlg = IsIgnoreDontExpand(); + if (nMode & SwInsertFlags::FORCEHINTEXPAND) + { + SetIgnoreDontExpand( true ); + } + + Update( rIdx, nLen ); // text content changed! + + if (nMode & SwInsertFlags::FORCEHINTEXPAND) + { + SetIgnoreDontExpand( bOldExpFlg ); + } + + if ( HasWriterListeners() ) + { // send this before messing with hints, which will send RES_UPDATE_ATTR + SwInsText aHint( aPos, nLen ); + NotifyClients( nullptr, &aHint ); + } + + if ( HasHints() ) + { + m_pSwpHints->SortIfNeedBe(); + bool const bHadHints(!m_pSwpHints->CanBeDeleted()); + bool bMergePortionsNeeded(false); + for ( size_t i = 0; i < m_pSwpHints->Count() && + rIdx >= m_pSwpHints->GetWithoutResorting(i)->GetStart(); ++i ) + { + SwTextAttr * const pHt = m_pSwpHints->GetWithoutResorting( i ); + const sal_Int32 * const pEndIdx = pHt->GetEnd(); + if( !pEndIdx ) + continue; + + if( rIdx == *pEndIdx ) + { + if ( (nMode & SwInsertFlags::NOHINTEXPAND) || + (!(nMode & SwInsertFlags::FORCEHINTEXPAND) + && pHt->DontExpand()) ) + { + m_pSwpHints->DeleteAtPos(i); + // on empty attributes also adjust Start + if( rIdx == pHt->GetStart() ) + pHt->SetStart( pHt->GetStart() - nLen ); + pHt->SetEnd(*pEndIdx - nLen); + // could be that pHt has IsFormatIgnoreEnd set, and it's + // not a RSID-only hint - now we have the inserted text + // between pHt and its continuation... which we don't know. + // punt the job to MergePortions below. + if (pHt->IsFormatIgnoreEnd()) + { + bMergePortionsNeeded = true; + } + InsertHint( pHt, SetAttrMode::NOHINTADJUST ); + } + // empty hints at insert position? + else if ( (nMode & SwInsertFlags::EMPTYEXPAND) + && (*pEndIdx == pHt->GetStart()) ) + { + m_pSwpHints->DeleteAtPos(i); + pHt->SetStart( pHt->GetStart() - nLen ); + const size_t nCurrentLen = m_pSwpHints->Count(); + InsertHint( pHt/* AUTOSTYLES:, SetAttrMode::NOHINTADJUST*/ ); + if ( nCurrentLen > m_pSwpHints->Count() && i ) + { + --i; + } + continue; + } + else + { + continue; + } + } + if ( !(nMode & SwInsertFlags::NOHINTEXPAND) && + rIdx == nLen && pHt->GetStart() == rIdx.GetIndex() && + !pHt->IsDontExpandStartAttr() ) + { + // no field, at paragraph start, HintExpand + m_pSwpHints->DeleteAtPos(i); + pHt->SetStart( pHt->GetStart() - nLen ); + // no effect on format ignore flags here (para start) + InsertHint( pHt, SetAttrMode::NOHINTADJUST ); + } + } + if (bMergePortionsNeeded) + { + m_pSwpHints->MergePortions(*this); + } + SAL_WARN_IF(bHadHints && m_pSwpHints->CanBeDeleted(), "sw.core", + "SwTextNode::InsertText: unexpected loss of hints"); + } + + // By inserting a character, the hidden flags + // at the TextNode can become invalid: + SetCalcHiddenCharFlags(); + + CHECK_SWPHINTS(this); + return sInserted; +} + +void SwTextNode::CutText( SwTextNode * const pDest, + const SwIndex & rStart, const sal_Int32 nLen ) +{ + assert(pDest); // Cut requires a destination + SwIndex aDestStt(pDest, pDest->GetText().getLength()); + CutImpl( pDest, aDestStt, rStart, nLen, false ); +} + +void SwTextNode::CutImpl( SwTextNode * const pDest, const SwIndex & rDestStart, + const SwIndex & rStart, sal_Int32 nLen, const bool bUpdate ) +{ + assert(pDest); // Cut requires a destination + + assert(GetDoc() == pDest->GetDoc()); // must be same document + + assert(pDest != this); // destination must be different node + + if( !nLen ) + { + // if no length is given, copy attributes at position rStart + CopyAttr( pDest, rStart.GetIndex(), rDestStart.GetIndex() ); + return; + } + + sal_Int32 nTextStartIdx = rStart.GetIndex(); + sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos + const sal_Int32 nInitSize = pDest->m_Text.getLength(); + + if (pDest->GetSpaceLeft() < nLen) + { // FIXME: could only happen when called from SwRangeRedline::Show. + // unfortunately can't really do anything here to handle that... + abort(); + } + pDest->m_Text = pDest->m_Text.replaceAt(nDestStart, 0, + m_Text.copy(nTextStartIdx, nLen)); + OUString const newText = m_Text.replaceAt(nTextStartIdx, nLen, ""); + nLen = pDest->m_Text.getLength() - nInitSize; // update w/ current size! + if (!nLen) // String didn't grow? + return; + + if (bUpdate) + { + // Update all SwIndex + pDest->Update( rDestStart, nLen, false, false/*??? why was it true*/); + } + + CHECK_SWPHINTS(pDest); + + const sal_Int32 nEnd = rStart.GetIndex() + nLen; + bool const bUndoNodes = + GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(GetNodes()); + + // copy hard attributes on whole paragraph + if (HasSwAttrSet()) + { + bool hasSwAttrSet = pDest->HasSwAttrSet(); + if (hasSwAttrSet) + { + // if we have our own property set it doesn't mean + // that this set defines any style different to Standard one. + hasSwAttrSet = false; + + // so, let's check deeper if property set has defined any property + if (pDest->GetpSwAttrSet()) + { + // check all items in the property set + SfxItemIter aIter( *pDest->GetpSwAttrSet() ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + // check current item + sal_uInt16 nWhich = IsInvalidItem( pItem ) + ? pDest->GetpSwAttrSet()->GetWhichByPos( aIter.GetCurPos() ) + : pItem->Which(); + if( RES_FRMATR_STYLE_NAME != nWhich && + RES_FRMATR_CONDITIONAL_STYLE_NAME != nWhich && + SfxItemState::SET == pDest->GetpSwAttrSet()->GetItemState( nWhich, false ) ) + { + // check if parent value (original value in style) has the same value as in [pItem] + const SfxPoolItem& rParentItem = pDest->GetpSwAttrSet()->GetParent()->Get( nWhich, true ); + + hasSwAttrSet = (rParentItem != *pItem); + + // property set is not empty => no need to make anymore checks + if (hasSwAttrSet) + break; + } + + // let's check next item + pItem = aIter.NextItem(); + } while (pItem); + } + } + + // all or just the Char attributes? + if( nInitSize || hasSwAttrSet || + nLen != pDest->GetText().getLength()) + { + SfxItemSet aCharSet( + pDest->GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{}); + aCharSet.Put( *GetpSwAttrSet() ); + if( aCharSet.Count() ) + pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen ); + } + else + { + GetpSwAttrSet()->CopyToModify( *pDest ); + } + } + + // notify frames - before moving hints, because footnotes + // want to find their anchor text frame in the follow chain + SwInsText aInsHint( nDestStart, nLen ); + pDest->ModifyNotification( nullptr, &aInsHint ); + sw::MoveText const moveHint(pDest, nDestStart, nTextStartIdx, nLen); + CallSwClientNotify(moveHint); + SwDelText aDelHint( nTextStartIdx, nLen ); + ModifyNotification( nullptr, &aDelHint ); + + // 2. move attributes + // Iterate over attribute array until the start of the attribute + // is behind the moved range + bool bMergePortionsNeeded(false); + size_t nAttrCnt = 0; + while (m_pSwpHints && (nAttrCnt < m_pSwpHints->Count())) + { + SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt); + const sal_Int32 nAttrStartIdx = pHt->GetStart(); + if ( nAttrStartIdx >= nEnd ) + break; + const sal_Int32 * const pEndIdx = pHt->GetEnd(); + const sal_uInt16 nWhich = pHt->Which(); + SwTextAttr *pNewHt = nullptr; + + // if the hint has a dummy character, then it must not be split! + if(nAttrStartIdx < nTextStartIdx) + { + // start is before the range + if (!pHt->HasDummyChar() && ( RES_TXTATR_REFMARK != nWhich + || bUndoNodes ) && pEndIdx && *pEndIdx > nTextStartIdx) + { + // attribute with extent and end of attribute is in the range + pNewHt = MakeTextAttr( *pDest->GetDoc(), pHt->GetAttr(), + nDestStart, + nDestStart + ( + *pEndIdx > nEnd + ? nLen + : *pEndIdx - nTextStartIdx ) ); + } + } + else + { + // start is inside the range + if (!pEndIdx || *pEndIdx < nEnd || + (!bUndoNodes && RES_TXTATR_REFMARK == nWhich) + || pHt->HasDummyChar() ) + { + // do not delete note and later add it -> sidebar flickering + if (GetDoc()->GetDocShell()) + { + GetDoc()->GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation)); + } + // move attribute + m_pSwpHints->Delete( pHt ); + // reset start/end indexes + if (pHt->IsFormatIgnoreStart() || pHt->IsFormatIgnoreEnd()) + { + bMergePortionsNeeded = true; + } + pHt->SetStart(nDestStart + (nAttrStartIdx - nTextStartIdx)); + if (pEndIdx) + { + pHt->SetEnd( nDestStart + ( + *pEndIdx > nEnd + ? nLen + : *pEndIdx - nTextStartIdx ) ); + } + pDest->InsertHint( pHt, + SetAttrMode::NOTXTATRCHR + | SetAttrMode::DONTREPLACE ); + if (GetDoc()->GetDocShell()) + { + GetDoc()->GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation)); + } + continue; // iterate while loop, no ++ ! + } + // the end is behind the range + else if (RES_TXTATR_REFMARK != nWhich || bUndoNodes) + { + pNewHt = MakeTextAttr( *GetDoc(), pHt->GetAttr(), + nDestStart + (nAttrStartIdx - nTextStartIdx), + nDestStart + (*pEndIdx > nEnd + ? nLen + : *pEndIdx - nTextStartIdx)); + } + } + if (pNewHt) + { + const bool bSuccess( pDest->InsertHint( pNewHt, + SetAttrMode::NOTXTATRCHR + | SetAttrMode::DONTREPLACE + | SetAttrMode::IS_COPY) ); + if (bSuccess) + { + lcl_CopyHint( nWhich, pHt, pNewHt, nullptr, pDest ); + } + } + ++nAttrCnt; + } + // If there are still empty attributes around, they have a higher priority + // than any attributes that become empty due to the move. + // So temporarily remove them and Update the array, then re-insert the + // removed ones so they overwrite the others. + if (m_pSwpHints && nAttrCnt < m_pSwpHints->Count()) + { + SwpHts aArr; + while (nAttrCnt < m_pSwpHints->Count()) + { + SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt); + if (nEnd != pHt->GetStart()) + break; + const sal_Int32 * const pEndIdx = pHt->GetEnd(); + if (pEndIdx && *pEndIdx == nEnd) + { + aArr.push_back( pHt ); + m_pSwpHints->Delete( pHt ); + } + else + { + ++nAttrCnt; + } + } + Update( rStart, nLen, true, true ); + + for (SwTextAttr* pHt : aArr) + { + pHt->SetStart( rStart.GetIndex() ); + pHt->SetEnd( rStart.GetIndex() ); + InsertHint( pHt ); + } + } + else + { + Update( rStart, nLen, true, true ); + } + + // set after moving hints + m_Text = newText; + + if (bMergePortionsNeeded) + { + m_pSwpHints->MergePortions(*this); + } + + CHECK_SWPHINTS(this); + + TryDeleteSwpHints(); +} + +void SwTextNode::EraseText(const SwIndex &rIdx, const sal_Int32 nCount, + const SwInsertFlags nMode ) +{ + assert(rIdx <= m_Text.getLength()); // invalid index + + const sal_Int32 nStartIdx = rIdx.GetIndex(); + const sal_Int32 nCnt = (nCount==SAL_MAX_INT32) + ? m_Text.getLength() - nStartIdx : nCount; + const sal_Int32 nEndIdx = nStartIdx + nCnt; + if (nEndIdx <= m_Text.getLength()) + m_Text = m_Text.replaceAt(nStartIdx, nCnt, ""); + + // GCAttr(); don't remove all empty ones, just the ones that are in the + // range but not at the end of the range. + + for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i ) + { + SwTextAttr *pHt = m_pSwpHints->Get(i); + + const sal_Int32 nHintStart = pHt->GetStart(); + + if ( nHintStart < nStartIdx ) + continue; + + if ( nHintStart > nEndIdx ) + break; // hints are sorted by end, so break here + + const sal_Int32* pHtEndIdx = pHt->GetEnd(); + const sal_uInt16 nWhich = pHt->Which(); + + if( !pHtEndIdx ) + { + // attribute with neither end nor CH_TXTATR? + assert(pHt->HasDummyChar()); + if (isTXTATR(nWhich) && (nHintStart < nEndIdx)) + { + m_pSwpHints->DeleteAtPos(i); + DestroyAttr( pHt ); + --i; + } + continue; + } + + assert(!( (nHintStart < nEndIdx) && (*pHtEndIdx > nEndIdx) + && pHt->HasDummyChar() ) + // next line: deleting exactly dummy char: DeleteAttributes + || ((nHintStart == nStartIdx) && (nHintStart + 1 == nEndIdx))); + // "ERROR: deleting left-overlapped attribute with CH_TXTATR"); + + // Delete the hint if: + // 1. The hint ends before the deletion end position or + // 2. The hint ends at the deletion end position and + // we are not in empty expand mode and + // the hint is a [toxmark|refmark|ruby|inputfield] text attribute + // 3. deleting exactly the dummy char of an hint with end and dummy + // char deletes the hint + if ( (*pHtEndIdx < nEndIdx) + || ( (*pHtEndIdx == nEndIdx) && + !(SwInsertFlags::EMPTYEXPAND & nMode) && + ( (RES_TXTATR_TOXMARK == nWhich) || + (RES_TXTATR_REFMARK == nWhich) || + (RES_TXTATR_CJK_RUBY == nWhich) || + (RES_TXTATR_INPUTFIELD == nWhich) ) ) + || ( (nHintStart < nEndIdx) && + pHt->HasDummyChar() ) + ) + { + m_pSwpHints->DeleteAtPos(i); + DestroyAttr( pHt ); + --i; + } + } + + OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?"); + + TryDeleteSwpHints(); + + Update( rIdx, nCnt, true ); + + if( 1 == nCnt ) + { + SwDelChr aHint( nStartIdx ); + NotifyClients( nullptr, &aHint ); + } + else + { + SwDelText aHint( nStartIdx, nCnt ); + NotifyClients( nullptr, &aHint ); + } + + OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?"); + + // By deleting a character, the hidden flags + // at the TextNode can become invalid: + SetCalcHiddenCharFlags(); + + CHECK_SWPHINTS(this); +} + +void SwTextNode::GCAttr() +{ + if ( !HasHints() ) + return; + + bool bChanged = false; + sal_Int32 nMin = m_Text.getLength(); + sal_Int32 nMax = 0; + const bool bAll = nMin != 0; // on empty paragraphs only remove INetFormats + + for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i ) + { + SwTextAttr * const pHt = m_pSwpHints->Get(i); + + // if end and start are equal, delete it + const sal_Int32 * const pEndIdx = pHt->GetEnd(); + if (pEndIdx && !pHt->HasDummyChar() && (*pEndIdx == pHt->GetStart()) + && ( bAll || pHt->Which() == RES_TXTATR_INETFMT ) ) + { + bChanged = true; + nMin = std::min( nMin, pHt->GetStart() ); + nMax = std::max( nMax, *pHt->GetEnd() ); + DestroyAttr( m_pSwpHints->Cut(i) ); + --i; + } + else + { + pHt->SetDontExpand( false ); + } + } + TryDeleteSwpHints(); + + if(bChanged) + { + // textframes react to aHint, others to aNew + SwUpdateAttr aHint( + nMin, + nMax, + 0); + + NotifyClients( nullptr, &aHint ); + SwFormatChg aNew( GetTextColl() ); + NotifyClients( nullptr, &aNew ); + } +} + +SwNumRule* SwTextNode::GetNumRule(bool bInParent) const +{ + SwNumRule* pRet = nullptr; + + const SfxPoolItem* pItem = GetNoCondAttr( RES_PARATR_NUMRULE, bInParent ); + bool bNoNumRule = false; + if ( pItem ) + { + OUString sNumRuleName = + static_cast<const SwNumRuleItem *>(pItem)->GetValue(); + if (!sNumRuleName.isEmpty()) + { + pRet = GetDoc()->FindNumRulePtr( sNumRuleName ); + } + else // numbering is turned off + bNoNumRule = true; + } + + if ( !bNoNumRule ) + { + if ( pRet && pRet == GetDoc()->GetOutlineNumRule() && + ( !HasSwAttrSet() || + SfxItemState::SET != + GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) ) ) + { + SwTextFormatColl* pColl = GetTextColl(); + if ( pColl ) + { + const SwNumRuleItem& rDirectItem = pColl->GetNumRule( false ); + if ( rDirectItem.GetValue().isEmpty() ) + { + pRet = nullptr; + } + } + } + } + + return pRet; +} + +void SwTextNode::NumRuleChgd() +{ + if ( IsInList() ) + { + SwNumRule* pNumRule = GetNumRule(); + if ( pNumRule && pNumRule != GetNum()->GetNumRule() ) + { + mpNodeNum->ChangeNumRule( *pNumRule ); + if (mpNodeNumRLHidden) + { + mpNodeNumRLHidden->ChangeNumRule(*pNumRule); + } + } + } + + if( IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + SetInSwFntCache( false ); + + // Sending "noop" modify in order to cause invalidations of registered + // <SwTextFrame> instances to get the list style change respectively the change + // in the list tree reflected in the layout. + // Important note: + { + SvxLRSpaceItem& rLR = const_cast<SvxLRSpaceItem&>(GetSwAttrSet().GetLRSpace()); + NotifyClients( &rLR, &rLR ); + } + + SetWordCountDirty( true ); +} + +// -> #i27615# +bool SwTextNode::IsNumbered(SwRootFrame const*const pLayout) const +{ + SwNumRule* pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr; + return pRule && IsCountedInList(); +} + +bool SwTextNode::HasMarkedLabel() const +{ + bool bResult = false; + + if ( IsInList() ) + { + bResult = + GetDoc()->getIDocumentListsAccess().getListByName( GetListId() )->IsListLevelMarked( GetActualListLevel() ); + } + + return bResult; +} +// <- #i27615# + +SwTextNode* SwTextNode::MakeNewTextNode( const SwNodeIndex& rPos, bool bNext, + bool bChgFollow ) +{ + // ignore hard PageBreak/PageDesc/ColumnBreak from Auto-Set + std::unique_ptr<SwAttrSet> pNewAttrSet; + // #i75353# + bool bClearHardSetNumRuleWhenFormatCollChanges( false ); + if( HasSwAttrSet() ) + { + pNewAttrSet.reset(new SwAttrSet( *GetpSwAttrSet() )); + const SfxItemSet* pTmpSet = GetpSwAttrSet(); + + if (bNext) // successor doesn't inherit breaks! + pTmpSet = pNewAttrSet.get(); + + // !bNext: remove PageBreaks/PageDesc/ColBreak from this + bool bRemoveFromCache = false; + std::vector<sal_uInt16> aClearWhichIds; + if ( bNext ) + bRemoveFromCache = ( 0 != pNewAttrSet->ClearItem( RES_PAGEDESC ) ); + else + aClearWhichIds.push_back( RES_PAGEDESC ); + + if( SfxItemState::SET == pTmpSet->GetItemState( RES_BREAK, false ) ) + { + if ( bNext ) + pNewAttrSet->ClearItem( RES_BREAK ); + else + aClearWhichIds.push_back( RES_BREAK ); + bRemoveFromCache = true; + } + if( SfxItemState::SET == pTmpSet->GetItemState( RES_KEEP, false ) ) + { + if ( bNext ) + pNewAttrSet->ClearItem( RES_KEEP ); + else + aClearWhichIds.push_back( RES_KEEP ); + bRemoveFromCache = true; + } + if( SfxItemState::SET == pTmpSet->GetItemState( RES_PARATR_SPLIT, false ) ) + { + if ( bNext ) + pNewAttrSet->ClearItem( RES_PARATR_SPLIT ); + else + aClearWhichIds.push_back( RES_PARATR_SPLIT ); + bRemoveFromCache = true; + } + if(SfxItemState::SET == pTmpSet->GetItemState(RES_PARATR_NUMRULE, false)) + { + SwNumRule * pRule = GetNumRule(); + + if (pRule && IsOutline()) + { + if ( bNext ) + pNewAttrSet->ClearItem(RES_PARATR_NUMRULE); + else + { + // #i75353# + // No clear of hard set numbering rule at an outline paragraph at this point. + // Only if the paragraph style changes - see below. + bClearHardSetNumRuleWhenFormatCollChanges = true; + } + bRemoveFromCache = true; + } + } + + if ( !aClearWhichIds.empty() ) + bRemoveFromCache = 0 != ClearItemsFromAttrSet( aClearWhichIds ); + + if( !bNext && bRemoveFromCache && IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + } + SwNodes& rNds = GetNodes(); + + SwTextFormatColl* pColl = GetTextColl(); + + SwTextNode *pNode = new SwTextNode( rPos, pColl, pNewAttrSet.get() ); + + pNewAttrSet.reset(); + + const SwNumRule* pRule = GetNumRule(); + if( pRule && pRule == pNode->GetNumRule() && rNds.IsDocNodes() ) + { + // #i55459# + // - correction: parameter <bNext> has to be checked, as it was in the + // previous implementation. + if ( !bNext && !IsCountedInList() ) + SetCountedInList(true); + } + + // In case the numbering caused a style from the pool to be assigned to + // the new node, don't overwrite that here! + if( pColl != pNode->GetTextColl() || + ( bChgFollow && pColl != GetTextColl() )) + return pNode; // that ought to be enough? + + pNode->ChgTextCollUpdateNum( nullptr, pColl ); // for numbering/outline + if( bNext || !bChgFollow ) + return pNode; + + SwTextFormatColl *pNextColl = &pColl->GetNextTextFormatColl(); + // i#101870 perform action on different paragraph styles before applying + // the new paragraph style + if (pNextColl != pColl) + { + // #i75353# + if ( bClearHardSetNumRuleWhenFormatCollChanges ) + { + std::vector<sal_uInt16> aClearWhichIds; + aClearWhichIds.push_back( RES_PARATR_NUMRULE ); + if ( ClearItemsFromAttrSet( aClearWhichIds ) != 0 && IsInCache() ) + { + SwFrame::GetCache().Delete( this ); + SetInCache( false ); + } + } + } + ChgFormatColl( pNextColl ); + + return pNode; +} + +SwContentNode* SwTextNode::AppendNode( const SwPosition & rPos ) +{ + // position behind which it will be inserted + SwNodeIndex aIdx( rPos.nNode, 1 ); + SwTextNode* pNew = MakeNewTextNode( aIdx ); + + // reset list attributes at appended text node + pNew->ResetAttr( RES_PARATR_LIST_ISRESTART ); + pNew->ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + pNew->ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + if ( pNew->GetNumRule() == nullptr ) + { + pNew->ResetAttr( RES_PARATR_LIST_ID ); + pNew->ResetAttr( RES_PARATR_LIST_LEVEL ); + } + + if (!IsInList() && GetNumRule() && !GetListId().isEmpty()) + { + AddToList(); + } + + if( HasWriterListeners() ) + MakeFramesForAdjacentContentNode(*pNew); + return pNew; +} + +SwTextAttr * SwTextNode::GetTextAttrForCharAt( + const sal_Int32 nIndex, + const sal_uInt16 nWhich ) const +{ + assert(nWhich >= RES_TXTATR_BEGIN && nWhich <= RES_TXTATR_END); + if ( HasHints() ) + { + for ( size_t i = 0; i < m_pSwpHints->Count(); ++i ) + { + SwTextAttr * const pHint = m_pSwpHints->Get(i); + const sal_Int32 nStartPos = pHint->GetStart(); + if ( nIndex < nStartPos ) + { + return nullptr; + } + if ( (nIndex == nStartPos) && pHint->HasDummyChar() ) + { + return ( RES_TXTATR_END == nWhich || nWhich == pHint->Which() ) + ? pHint : nullptr; + } + } + } + return nullptr; +} + +namespace +{ + +sal_uInt16 lcl_BoundListLevel(const int nActualLevel) +{ + return static_cast<sal_uInt16>( std::min( std::max(nActualLevel, 0), MAXLEVEL-1 ) ); +} + +} + +// -> #i29560# +bool SwTextNode::HasNumber() const +{ + bool bResult = false; + + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pRule ) + { + const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel()))); + + // #i40041# + bResult = aFormat.IsEnumeration() && + SVX_NUM_NUMBER_NONE != aFormat.GetNumberingType(); + } + + return bResult; +} + +bool SwTextNode::HasBullet() const +{ + bool bResult = false; + + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pRule ) + { + const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel()))); + + bResult = aFormat.IsItemize(); + } + + return bResult; +} +// <- #i29560# + +// #128041# - introduce parameter <_bInclPrefixAndSuffixStrings> +//i53420 added max outline parameter +OUString SwTextNode::GetNumString( const bool _bInclPrefixAndSuffixStrings, + const unsigned int _nRestrictToThisLevel, + SwRootFrame const*const pLayout) const +{ + if (GetDoc()->IsClipBoard() && m_pNumStringCache) + { + // #i111677# do not expand number strings in clipboard documents + return *m_pNumStringCache; + } + const SwNumRule* pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr; + if ( pRule && + IsCountedInList() ) + { + SvxNumberType const& rNumberType( + pRule->Get( lcl_BoundListLevel(GetActualListLevel()) ) ); + if (rNumberType.IsTextFormat() || + + (style::NumberingType::NUMBER_NONE == rNumberType.GetNumberingType())) + { + return pRule->MakeNumString( GetNum(pLayout)->GetNumberVector(), + _bInclPrefixAndSuffixStrings, + false, + _nRestrictToThisLevel, + nullptr, + GetLang(0)); + } + } + + return OUString(); +} + +long SwTextNode::GetLeftMarginWithNum( bool bTextLeft ) const +{ + long nRet = 0; + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if( pRule ) + { + const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel())); + + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + nRet = rFormat.GetAbsLSpace(); + + if( !bTextLeft ) + { + if( 0 > rFormat.GetFirstLineOffset() && + nRet > -rFormat.GetFirstLineOffset() ) + nRet = nRet + rFormat.GetFirstLineOffset(); + else + nRet = 0; + } + + if( pRule->IsAbsSpaces() ) + nRet = nRet - GetSwAttrSet().GetLRSpace().GetLeft(); + } + else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + if ( AreListLevelIndentsApplicable() ) + { + nRet = rFormat.GetIndentAt(); + // #i90401# + // Only negative first line indents have consider for the left margin + if ( !bTextLeft && + rFormat.GetFirstLineIndent() < 0 ) + { + nRet = nRet + rFormat.GetFirstLineIndent(); + } + } + } + } + + return nRet; +} + +bool SwTextNode::GetFirstLineOfsWithNum( short& rFLOffset ) const +{ + // #i95907# + rFLOffset = 0; + + // #i51089# + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pRule ) + { + if ( IsCountedInList() ) + { + const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel())); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + rFLOffset = rFormat.GetFirstLineOffset(); //TODO: overflow + + if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING)) + { + SvxLRSpaceItem aItem = GetSwAttrSet().GetLRSpace(); + rFLOffset = rFLOffset + aItem.GetTextFirstLineOffset(); + } + } + else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + if ( AreListLevelIndentsApplicable() ) + { + rFLOffset = rFormat.GetFirstLineIndent(); + } + else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING)) + { + SvxLRSpaceItem aItem = GetSwAttrSet().GetLRSpace(); + rFLOffset = aItem.GetTextFirstLineOffset(); + } + } + } + + return true; + } + + rFLOffset = GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset(); + return false; +} + +SwTwips SwTextNode::GetAdditionalIndentForStartingNewList() const +{ + SwTwips nAdditionalIndent = 0; + + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pRule ) + { + const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel())); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + nAdditionalIndent = GetSwAttrSet().GetLRSpace().GetLeft(); + + if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING)) + { + nAdditionalIndent = nAdditionalIndent - + GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset(); + } + } + else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + if ( AreListLevelIndentsApplicable() ) + { + nAdditionalIndent = rFormat.GetIndentAt() + rFormat.GetFirstLineIndent(); + } + else + { + nAdditionalIndent = GetSwAttrSet().GetLRSpace().GetLeft(); + if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING)) + { + nAdditionalIndent = nAdditionalIndent - + GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset(); + } + } + } + } + else + { + nAdditionalIndent = GetSwAttrSet().GetLRSpace().GetLeft(); + } + + return nAdditionalIndent; +} + +// #i96772# +void SwTextNode::ClearLRSpaceItemDueToListLevelIndents( std::shared_ptr<SvxLRSpaceItem>& o_rLRSpaceItem ) const +{ + if ( AreListLevelIndentsApplicable() ) + { + const SwNumRule* pRule = GetNumRule(); + if ( pRule && GetActualListLevel() >= 0 ) + { + const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel())); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + o_rLRSpaceItem = std::make_shared<SvxLRSpaceItem>(RES_LR_SPACE); + } + } + } +} + +// #i91133# +long SwTextNode::GetLeftMarginForTabCalculation() const +{ + long nLeftMarginForTabCalc = 0; + + bool bLeftMarginForTabCalcSetToListLevelIndent( false ); + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if( pRule ) + { + const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel())); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + if ( AreListLevelIndentsApplicable() ) + { + nLeftMarginForTabCalc = rFormat.GetIndentAt(); + bLeftMarginForTabCalcSetToListLevelIndent = true; + } + } + } + if ( !bLeftMarginForTabCalcSetToListLevelIndent ) + { + nLeftMarginForTabCalc = GetSwAttrSet().GetLRSpace().GetTextLeft(); + } + + return nLeftMarginForTabCalc; +} + +static void Replace0xFF( + SwTextNode const& rNode, + OUStringBuffer & rText, + sal_Int32 & rTextStt, + sal_Int32 nEndPos ) +{ + if (rNode.GetpSwpHints()) + { + sal_Unicode cSrchChr = CH_TXTATR_BREAKWORD; + for( int nSrchIter = 0; 2 > nSrchIter; ++nSrchIter, cSrchChr = CH_TXTATR_INWORD ) + { + sal_Int32 nPos = rText.indexOf(cSrchChr); + while (-1 != nPos && nPos < nEndPos) + { + const SwTextAttr* const pAttr = + rNode.GetTextAttrForCharAt(rTextStt + nPos); + if( pAttr ) + { + switch( pAttr->Which() ) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + rText.remove(nPos, 1); + ++rTextStt; + break; + + case RES_TXTATR_FTN: + rText.remove(nPos, 1); + ++rTextStt; + break; + + default: + rText.remove(nPos, 1); + ++rTextStt; + } + } + else + { + ++nPos; + ++nEndPos; + } + nPos = rText.indexOf(cSrchChr, nPos); + } + } + } +} + +// Expand fields +// #i83479# - handling of new parameters +OUString SwTextNode::GetExpandText(SwRootFrame const*const pLayout, + const sal_Int32 nIdx, + const sal_Int32 nLen, + const bool bWithNum, + const bool bAddSpaceAfterListLabelStr, + const bool bWithSpacesForLevel, + const ExpandMode eAdditionalMode) const + +{ + ExpandMode eMode = ExpandMode::ExpandFields | eAdditionalMode; + if (pLayout && pLayout->IsHideRedlines()) + { + eMode |= ExpandMode::HideDeletions; + } + + ModelToViewHelper aConversionMap(*this, pLayout, eMode); + const OUString aExpandText = aConversionMap.getViewText(); + const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nIdx ); + sal_Int32 nEnd = nLen == -1 ? GetText().getLength() : nIdx + nLen; + const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nEnd ); + OUStringBuffer aText(aExpandText.copy(nExpandBegin, nExpandEnd-nExpandBegin)); + + // remove dummy characters of Input Fields + comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDSTART); + comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDEND); + + if( bWithNum ) + { + if (!GetNumString(true, MAXLEVEL, pLayout).isEmpty()) + { + if ( bAddSpaceAfterListLabelStr ) + { + const sal_Unicode aSpace = ' '; + aText.insert(0, aSpace); + } + aText.insert(0, GetNumString(true, MAXLEVEL, pLayout)); + } + } + + if (bWithSpacesForLevel) + { + const sal_Unicode aSpace = ' '; + for (int nLevel = GetActualListLevel(); nLevel > 0; --nLevel) + { + aText.insert(0, aSpace); + aText.insert(0, aSpace); + } + } + + return aText.makeStringAndClear(); +} + +bool SwTextNode::CopyExpandText(SwTextNode& rDestNd, const SwIndex* pDestIdx, + sal_Int32 nIdx, sal_Int32 nLen, + SwRootFrame const*const pLayout, bool bWithNum, + bool bWithFootnote, bool bReplaceTabsWithSpaces ) const +{ + if( &rDestNd == this ) + return false; + + SwIndex aDestIdx(&rDestNd, rDestNd.GetText().getLength()); + if( pDestIdx ) + aDestIdx = *pDestIdx; + const sal_Int32 nDestStt = aDestIdx.GetIndex(); + + // first, start with the text + OUStringBuffer buf(GetText()); + if( bReplaceTabsWithSpaces ) + buf.replace('\t', ' '); + + // mask hidden characters + const sal_Unicode cChar = CH_TXTATR_BREAKWORD; + SwScriptInfo::MaskHiddenRanges(*this, buf, 0, buf.getLength(), cChar); + + buf.remove(0, nIdx); + if (nLen != -1) + { + buf.truncate(nLen); + } + // remove dummy characters of Input Fields + { + comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDSTART); + comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDEND); + } + rDestNd.InsertText(buf.makeStringAndClear(), aDestIdx); + nLen = aDestIdx.GetIndex() - nDestStt; + + // set all char attributes with Symbol font + if ( HasHints() ) + { + sal_Int32 nInsPos = nDestStt - nIdx; + for ( size_t i = 0; i < m_pSwpHints->Count(); ++i ) + { + const SwTextAttr* pHt = m_pSwpHints->Get(i); + const sal_Int32 nAttrStartIdx = pHt->GetStart(); + const sal_uInt16 nWhich = pHt->Which(); + if (nIdx + nLen <= nAttrStartIdx) + break; // behind end of text + + const sal_Int32 *pEndIdx = pHt->End(); + if( pEndIdx && *pEndIdx > nIdx && + ( RES_CHRATR_FONT == nWhich || + RES_TXTATR_CHARFMT == nWhich || + RES_TXTATR_AUTOFMT == nWhich )) + { + const SvxFontItem* const pFont = + CharFormat::GetItem( *pHt, RES_CHRATR_FONT ); + if ( pFont && RTL_TEXTENCODING_SYMBOL == pFont->GetCharSet() ) + { + // attribute in area => copy + rDestNd.InsertItem( *const_cast<SvxFontItem*>(pFont), + nInsPos + nAttrStartIdx, nInsPos + *pEndIdx ); + } + } + else if ( pHt->HasDummyChar() && (nAttrStartIdx >= nIdx) ) + { + aDestIdx = nInsPos + nAttrStartIdx; + switch( nWhich ) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + { + OUString const aExpand( + static_txtattr_cast<SwTextField const*>(pHt)->GetFormatField().GetField()->ExpandField(true, pLayout)); + if (!aExpand.isEmpty()) + { + ++aDestIdx; // insert behind + OUString const ins( + rDestNd.InsertText( aExpand, aDestIdx)); + SAL_INFO_IF(ins.getLength() != aExpand.getLength(), + "sw.core", "GetExpandText lossage"); + aDestIdx = nInsPos + nAttrStartIdx; + nInsPos += ins.getLength(); + } + rDestNd.EraseText( aDestIdx, 1 ); + --nInsPos; + } + break; + + case RES_TXTATR_FTN: + { + if ( bWithFootnote ) + { + const SwFormatFootnote& rFootnote = pHt->GetFootnote(); + OUString sExpand; + auto const number(pLayout && pLayout->IsHideRedlines() + ? rFootnote.GetNumberRLHidden() + : rFootnote.GetNumber()); + if( !rFootnote.GetNumStr().isEmpty() ) + sExpand = rFootnote.GetNumStr(); + else if( rFootnote.IsEndNote() ) + sExpand = GetDoc()->GetEndNoteInfo().m_aFormat. + GetNumStr(number); + else + sExpand = GetDoc()->GetFootnoteInfo().m_aFormat. + GetNumStr(number); + if( !sExpand.isEmpty() ) + { + ++aDestIdx; // insert behind + SvxEscapementItem aItem( SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT ); + rDestNd.InsertItem( + aItem, + aDestIdx.GetIndex(), + aDestIdx.GetIndex() ); + OUString const ins( rDestNd.InsertText(sExpand, aDestIdx, SwInsertFlags::EMPTYEXPAND)); + SAL_INFO_IF(ins.getLength() != sExpand.getLength(), + "sw.core", "GetExpandText lossage"); + aDestIdx = nInsPos + nAttrStartIdx; + nInsPos += ins.getLength(); + } + } + rDestNd.EraseText( aDestIdx, 1 ); + --nInsPos; + } + break; + + default: + rDestNd.EraseText( aDestIdx, 1 ); + --nInsPos; + } + } + } + } + + if( bWithNum ) + { + aDestIdx = nDestStt; + rDestNd.InsertText( GetNumString(true, MAXLEVEL, pLayout), aDestIdx ); + } + + aDestIdx = 0; + sal_Int32 nStartDelete(-1); + while (aDestIdx < rDestNd.GetText().getLength()) + { + sal_Unicode const cur(rDestNd.GetText()[aDestIdx.GetIndex()]); + if ( (cChar == cur) // filter substituted hidden text + || (CH_TXT_ATR_FIELDSTART == cur) // filter all fieldmarks + || (CH_TXT_ATR_FIELDSEP == cur) + || (CH_TXT_ATR_FIELDEND == cur) + || (CH_TXT_ATR_FORMELEMENT == cur)) + { + if (-1 == nStartDelete) + { + nStartDelete = aDestIdx.GetIndex(); // start deletion range + } + ++aDestIdx; + if (aDestIdx < rDestNd.GetText().getLength()) + { + continue; + } // else: end of paragraph => delete, see below + } + else + { + if (-1 == nStartDelete) + { + ++aDestIdx; + continue; + } // else: delete, see below + } + assert(-1 != nStartDelete); // without delete range, would have continued + rDestNd.EraseText( + SwIndex(&rDestNd, nStartDelete), + aDestIdx.GetIndex() - nStartDelete); + assert(aDestIdx.GetIndex() == nStartDelete); + nStartDelete = -1; // reset + } + + return true; +} + +OUString SwTextNode::GetRedlineText() const +{ + std::vector<sal_Int32> aRedlArr; + const SwDoc* pDoc = GetDoc(); + SwRedlineTable::size_type nRedlPos = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *this, RedlineType::Delete ); + if( SwRedlineTable::npos != nRedlPos ) + { + // some redline-delete object exists for the node + const sal_uLong nNdIdx = GetIndex(); + for( ; nRedlPos < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos ) + { + const SwRangeRedline* pTmp = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + if( RedlineType::Delete == pTmp->GetType() ) + { + const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); + if( pRStt->nNode < nNdIdx ) + { + if( pREnd->nNode > nNdIdx ) + // paragraph is fully deleted + return OUString(); + else if( pREnd->nNode == nNdIdx ) + { + // deleted from 0 to nContent + aRedlArr.push_back( 0 ); + aRedlArr.push_back( pREnd->nContent.GetIndex() ); + } + } + else if( pRStt->nNode == nNdIdx ) + { + //aRedlArr.Insert( pRStt->nContent.GetIndex(), aRedlArr.Count() ); + aRedlArr.push_back( pRStt->nContent.GetIndex() ); + if( pREnd->nNode == nNdIdx ) + aRedlArr.push_back( pREnd->nContent.GetIndex() ); + else + { + aRedlArr.push_back(GetText().getLength()); + break; // that was all + } + } + else + break; // that was all + } + } + } + + OUStringBuffer aText(GetText()); + + sal_Int32 nTextStt = 0; + sal_Int32 nIdxEnd = aText.getLength(); + for( size_t n = 0; n < aRedlArr.size(); n += 2 ) + { + sal_Int32 nStt = aRedlArr[ n ]; + sal_Int32 nEnd = aRedlArr[ n+1 ]; + if( ( 0 <= nStt && nStt <= nIdxEnd ) || + ( 0 <= nEnd && nEnd <= nIdxEnd )) + { + if( nStt < 0 ) nStt = 0; + if( nIdxEnd < nEnd ) nEnd = nIdxEnd; + const sal_Int32 nDelCnt = nEnd - nStt; + aText.remove(nStt - nTextStt, nDelCnt); + Replace0xFF(*this, aText, nTextStt, nStt - nTextStt); + nTextStt += nDelCnt; + } + else if( nStt >= nIdxEnd ) + break; + } + Replace0xFF(*this, aText, nTextStt, aText.getLength()); + + return aText.makeStringAndClear(); +} + +void SwTextNode::ReplaceText( const SwIndex& rStart, const sal_Int32 nDelLen, + const OUString & rStr) +{ + assert( rStart.GetIndex() < m_Text.getLength() // index out of bounds + && rStart.GetIndex() + nDelLen <= m_Text.getLength()); + + sal_Int32 const nOverflow(rStr.getLength() - nDelLen - GetSpaceLeft()); + SAL_WARN_IF(nOverflow > 0, "sw.core", + "SwTextNode::ReplaceText: node text with insertion > node capacity."); + OUString const sInserted( + (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr); + if (sInserted.isEmpty() && 0 == nDelLen) + { + return; // nothing to do + } + + const sal_Int32 nStartPos = rStart.GetIndex(); + sal_Int32 nEndPos = nStartPos + nDelLen; + sal_Int32 nLen = nDelLen; + for( sal_Int32 nPos = nStartPos; nPos < nEndPos; ++nPos ) + { + if ((CH_TXTATR_BREAKWORD == m_Text[nPos]) || + (CH_TXTATR_INWORD == m_Text[nPos])) + { + SwTextAttr *const pHint = GetTextAttrForCharAt( nPos ); + if (pHint) + { + assert(!( pHint->GetEnd() && pHint->HasDummyChar() + && (pHint->GetStart() < nEndPos) + && (*pHint->GetEnd() > nEndPos) )); + // "deleting left-overlapped attribute with CH_TXTATR" + DeleteAttribute( pHint ); + --nEndPos; + --nLen; + } + } + } + + bool bOldExpFlg = IsIgnoreDontExpand(); + SetIgnoreDontExpand( true ); + + if (nLen && sInserted.getLength()) + { + // Replace the 1st char, then delete the rest and insert. + // This way the attributes of the 1st char are expanded! + m_Text = m_Text.replaceAt(nStartPos, 1, sInserted.copy(0, 1)); + + ++const_cast<SwIndex&>(rStart); + m_Text = m_Text.replaceAt(rStart.GetIndex(), nLen - 1, ""); + Update( rStart, nLen - 1, true ); + + OUString aTmpText( sInserted.copy(1) ); + m_Text = m_Text.replaceAt(rStart.GetIndex(), 0, aTmpText); + Update( rStart, aTmpText.getLength() ); + } + else + { + m_Text = m_Text.replaceAt(nStartPos, nLen, ""); + Update( rStart, nLen, true ); + + m_Text = m_Text.replaceAt(nStartPos, 0, sInserted); + Update( rStart, sInserted.getLength() ); + } + + SetIgnoreDontExpand( bOldExpFlg ); + SwDelText aDelHint( nStartPos, nDelLen ); + NotifyClients( nullptr, &aDelHint ); + + if (sInserted.getLength()) + { + SwInsText aHint( nStartPos, sInserted.getLength() ); + NotifyClients( nullptr, &aHint ); + } +} + +namespace { + void lcl_ResetParAttrs( SwTextNode &rTextNode ) + { + std::set<sal_uInt16> aAttrs; + aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_ID ); + aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_LEVEL ); + aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_ISRESTART ); + aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_RESTARTVALUE ); + aAttrs.insert( aAttrs.end(), RES_PARATR_LIST_ISCOUNTED ); + SwPaM aPam( rTextNode ); + // #i96644# + // suppress side effect "send data changed events" + rTextNode.GetDoc()->ResetAttrs( aPam, false, aAttrs, false ); + } + + // Helper method for special handling of modified attributes at text node. + // The following is handled: + // (1) on changing the paragraph style - RES_FMT_CHG: + // Check, if list style of the text node is changed. If yes, add respectively + // remove the text node to the corresponding list. + // (2) on changing the attributes - RES_ATTRSET_CHG: + // Same as (1). + // (3) on changing the list style - RES_PARATR_NUMRULE: + // Same as (1). + void HandleModifyAtTextNode( SwTextNode& rTextNode, + const SfxPoolItem* pOldValue, + const SfxPoolItem* pNewValue ) + { + const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() : + pNewValue ? pNewValue->Which() : 0 ; + bool bNumRuleSet = false; + bool bParagraphStyleChanged = false; + OUString sNumRule; + OUString sOldNumRule; + switch ( nWhich ) + { + case RES_FMT_CHG: + { + bParagraphStyleChanged = true; + if( rTextNode.GetNodes().IsDocNodes() ) + { + const SwNumRule* pFormerNumRuleAtTextNode = + rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr; + if ( pFormerNumRuleAtTextNode ) + { + sOldNumRule = pFormerNumRuleAtTextNode->GetName(); + } + if ( rTextNode.IsEmptyListStyleDueToSetOutlineLevelAttr() ) + { + const SwNumRuleItem& rNumRuleItem = rTextNode.GetTextColl()->GetNumRule(); + if ( !rNumRuleItem.GetValue().isEmpty() ) + { + rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + } + } + const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule(); + if ( pNumRuleAtTextNode ) + { + bNumRuleSet = true; + sNumRule = pNumRuleAtTextNode->GetName(); + } + } + break; + } + case RES_ATTRSET_CHG: + { + const SfxPoolItem* pItem = nullptr; + const SwNumRule* pFormerNumRuleAtTextNode = + rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr; + if ( pFormerNumRuleAtTextNode ) + { + sOldNumRule = pFormerNumRuleAtTextNode->GetName(); + } + + const SwAttrSetChg* pSet = dynamic_cast<const SwAttrSetChg*>(pNewValue); + if ( pSet && pSet->GetChgSet()->GetItemState( RES_PARATR_NUMRULE, false, &pItem ) == + SfxItemState::SET ) + { + // #i70748# + rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + bNumRuleSet = true; + } + // #i70748# + // The new list style set at the paragraph. + const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule(); + if ( pNumRuleAtTextNode ) + { + sNumRule = pNumRuleAtTextNode->GetName(); + } + break; + } + case RES_PARATR_NUMRULE: + { + if ( rTextNode.GetNodes().IsDocNodes() ) + { + const SwNumRule* pFormerNumRuleAtTextNode = + rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr; + if ( pFormerNumRuleAtTextNode ) + { + sOldNumRule = pFormerNumRuleAtTextNode->GetName(); + } + + if ( pNewValue ) + { + // #i70748# + rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + bNumRuleSet = true; + } + // #i70748# + // The new list style set at the paragraph. + const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule(); + if ( pNumRuleAtTextNode ) + { + sNumRule = pNumRuleAtTextNode->GetName(); + } + } + break; + } + } + if ( sNumRule != sOldNumRule ) + { + if ( bNumRuleSet ) + { + if (sNumRule.isEmpty()) + { + rTextNode.RemoveFromList(); + if ( bParagraphStyleChanged ) + { + lcl_ResetParAttrs(rTextNode); + } + } + else + { + rTextNode.RemoveFromList(); + // If new list style is the outline style, apply outline + // level as the list level. + if (sNumRule==SwNumRule::GetOutlineRuleName()) + { + // #i70748# + OSL_ENSURE( rTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle(), + "<HandleModifyAtTextNode()> - text node with outline style, but its paragraph style is not assigned to outline style." ); + const int nNewListLevel = + rTextNode.GetTextColl()->GetAssignedOutlineStyleLevel(); + if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL ) + { + rTextNode.SetAttrListLevel( nNewListLevel ); + } + } + rTextNode.AddToList(); + } + } + else // <sNumRule.Len() == 0 && sOldNumRule.Len() != 0> + { + rTextNode.RemoveFromList(); + if ( bParagraphStyleChanged ) + { + lcl_ResetParAttrs(rTextNode); + // #i70748# + if ( dynamic_cast<const SfxUInt16Item &>(rTextNode.GetAttr( RES_PARATR_OUTLINELEVEL, false )).GetValue() > 0 ) + { + rTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr(); + } + } + } + } + else if (!sNumRule.isEmpty() && !rTextNode.IsInList()) + { + rTextNode.AddToList(); + } + } + // End of method <HandleModifyAtTextNode> +} + +SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl ) +{ + OSL_ENSURE( pNewColl,"ChgFormatColl: Collectionpointer has value 0." ); + OSL_ENSURE( dynamic_cast<const SwTextFormatColl *>(pNewColl) != nullptr, + "ChgFormatColl: is not a Text Collection pointer." ); + + SwTextFormatColl *pOldColl = GetTextColl(); + if( pNewColl != pOldColl ) + { + SetCalcHiddenCharFlags(); + SwContentNode::ChgFormatColl( pNewColl ); + OSL_ENSURE( !mbInSetOrResetAttr, + "DEBUG OSL_ENSURE(ON - <SwTextNode::ChgFormatColl(..)> called during <Set/ResetAttr(..)>" ); + if ( !mbInSetOrResetAttr ) + { + SwFormatChg aTmp1( pOldColl ); + SwFormatChg aTmp2( pNewColl ); + HandleModifyAtTextNode( *this, &aTmp1, &aTmp2 ); + } + + // reset fill information on parent style change + if(maFillAttributes) + { + maFillAttributes.reset(); + } + } + + // only for real nodes-array + if( GetNodes().IsDocNodes() ) + { + ChgTextCollUpdateNum( pOldColl, static_cast<SwTextFormatColl *>(pNewColl) ); + } + + GetNodes().UpdateOutlineNode(*this); + + return pOldColl; +} + +const SwNodeNum* SwTextNode::GetNum(SwRootFrame const*const pLayout) const +{ + // invariant: it's only in list in Hide mode if it's in list in normal mode + assert(mpNodeNum || !mpNodeNumRLHidden); + return pLayout && pLayout->IsHideRedlines() ? mpNodeNumRLHidden.get() : mpNodeNum.get(); +} + +void SwTextNode::DoNum(std::function<void (SwNodeNum &)> const& rFunc) +{ + // temp. clear because GetActualListLevel() may be called and the assert + // there triggered during update, which is unhelpful + std::unique_ptr<SwNodeNum> pBackup = std::move(mpNodeNumRLHidden); + assert(mpNodeNum); + rFunc(*mpNodeNum); + if (pBackup) + { + mpNodeNumRLHidden = std::move(pBackup); + rFunc(*mpNodeNumRLHidden); + } +} + +SwNumberTree::tNumberVector +SwTextNode::GetNumberVector(SwRootFrame const*const pLayout) const +{ + if (SwNodeNum const*const pNum = GetNum(pLayout)) + { + return pNum->GetNumberVector(); + } + else + { + SwNumberTree::tNumberVector aResult; + return aResult; + } +} + +bool SwTextNode::IsOutline() const +{ + bool bResult = false; + + if ( GetAttrOutlineLevel() > 0 ) + { + bResult = !IsInRedlines(); + } + else + { + const SwNumRule* pRule( GetNum() ? GetNum()->GetNumRule() : nullptr ); + if ( pRule && pRule->IsOutlineRule() ) + { + bResult = !IsInRedlines(); + } + } + + return bResult; +} + +bool SwTextNode::IsOutlineStateChanged() const +{ + return IsOutline() != m_bLastOutlineState; +} + +void SwTextNode::UpdateOutlineState() +{ + m_bLastOutlineState = IsOutline(); +} + +int SwTextNode::GetAttrOutlineLevel() const +{ + return static_cast<const SfxUInt16Item &>(GetAttr(RES_PARATR_OUTLINELEVEL)).GetValue(); +} + +void SwTextNode::SetAttrOutlineLevel(int nLevel) +{ + assert(0 <= nLevel && nLevel <= MAXLEVEL); // Level Out Of Range + if ( 0 <= nLevel && nLevel <= MAXLEVEL ) + { + SetAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL, + static_cast<sal_uInt16>(nLevel) ) ); + } +} + +// #i70748# + +void SwTextNode::SetEmptyListStyleDueToSetOutlineLevelAttr() +{ + if ( !mbEmptyListStyleSetDueToSetOutlineLevelAttr ) + { + SetAttr( SwNumRuleItem() ); + mbEmptyListStyleSetDueToSetOutlineLevelAttr = true; + } +} + +void SwTextNode::ResetEmptyListStyleDueToResetOutlineLevelAttr() +{ + if ( mbEmptyListStyleSetDueToSetOutlineLevelAttr ) + { + ResetAttr( RES_PARATR_NUMRULE ); + mbEmptyListStyleSetDueToSetOutlineLevelAttr = false; + } +} + +void SwTextNode::SetAttrListLevel( int nLevel ) +{ + if ( nLevel < 0 || nLevel >= MAXLEVEL ) + { + assert(false); // invalid level + return; + } + + SfxInt16Item aNewListLevelItem( RES_PARATR_LIST_LEVEL, + static_cast<sal_Int16>(nLevel) ); + SetAttr( aNewListLevelItem ); +} + +bool SwTextNode::HasAttrListLevel() const +{ + return GetpSwAttrSet() && + GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_LEVEL, false ) == SfxItemState::SET; +} + +int SwTextNode::GetAttrListLevel() const +{ + int nAttrListLevel = 0; + + const SfxInt16Item& aListLevelItem = + dynamic_cast<const SfxInt16Item&>(GetAttr( RES_PARATR_LIST_LEVEL )); + nAttrListLevel = static_cast<int>(aListLevelItem.GetValue()); + + return nAttrListLevel; +} + +int SwTextNode::GetActualListLevel() const +{ + assert(!GetNum() || !mpNodeNumRLHidden || // must be in sync + GetNum()->GetLevelInListTree() == mpNodeNumRLHidden->GetLevelInListTree()); + return GetNum() ? GetNum()->GetLevelInListTree() : -1; +} + +void SwTextNode::SetListRestart( bool bRestart ) +{ + if ( !bRestart ) + { + // attribute not contained in paragraph style's attribute set. Thus, + // it can be reset to the attribute pool default by resetting the attribute. + ResetAttr( RES_PARATR_LIST_ISRESTART ); + } + else + { + SfxBoolItem aNewIsRestartItem( RES_PARATR_LIST_ISRESTART, + true ); + SetAttr( aNewIsRestartItem ); + } +} + +bool SwTextNode::IsListRestart() const +{ + const SfxBoolItem& aIsRestartItem = + dynamic_cast<const SfxBoolItem&>(GetAttr( RES_PARATR_LIST_ISRESTART )); + + return aIsRestartItem.GetValue(); +} + +/** Returns if the paragraph has a visible numbering or bullet. + This includes all kinds of numbering/bullet/outlines. + The concrete list label string has to be checked, too. + */ +bool SwTextNode::HasVisibleNumberingOrBullet() const +{ + const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pRule && IsCountedInList()) + { + // #i87154# + // Correction of #newlistlevelattrs#: + // The numbering type has to be checked for bullet lists. + const SwNumFormat& rFormat = pRule->Get( lcl_BoundListLevel(GetActualListLevel()) ); + return SVX_NUM_NUMBER_NONE != rFormat.GetNumberingType() || + !pRule->MakeNumString( *(GetNum()) ).isEmpty(); + } + + return false; +} + +void SwTextNode::SetAttrListRestartValue( SwNumberTree::tSwNumTreeNumber nNumber ) +{ + const bool bChanged( HasAttrListRestartValue() + ? GetAttrListRestartValue() != nNumber + : nNumber != USHRT_MAX ); + + if ( bChanged || !HasAttrListRestartValue() ) + { + if ( nNumber == USHRT_MAX ) + { + ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + } + else + { + SfxInt16Item aNewListRestartValueItem( RES_PARATR_LIST_RESTARTVALUE, + static_cast<sal_Int16>(nNumber) ); + SetAttr( aNewListRestartValueItem ); + } + } +} + +bool SwTextNode::HasAttrListRestartValue() const +{ + return GetpSwAttrSet() && + GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_RESTARTVALUE, false ) == SfxItemState::SET; +} +SwNumberTree::tSwNumTreeNumber SwTextNode::GetAttrListRestartValue() const +{ + OSL_ENSURE( HasAttrListRestartValue(), + "<SwTextNode::GetAttrListRestartValue()> - only ask for list restart value, if attribute is set at text node." ); + + const SfxInt16Item& aListRestartValueItem = + dynamic_cast<const SfxInt16Item&>(GetAttr( RES_PARATR_LIST_RESTARTVALUE )); + return static_cast<SwNumberTree::tSwNumTreeNumber>(aListRestartValueItem.GetValue()); +} + +SwNumberTree::tSwNumTreeNumber SwTextNode::GetActualListStartValue() const +{ + SwNumberTree::tSwNumTreeNumber nListRestartValue = 1; + + if ( IsListRestart() && HasAttrListRestartValue() ) + { + nListRestartValue = GetAttrListRestartValue(); + } + else + { + SwNumRule* pRule = GetNumRule(); + if ( pRule ) + { + const SwNumFormat* pFormat = + pRule->GetNumFormat( static_cast<sal_uInt16>(GetAttrListLevel()) ); + if ( pFormat ) + { + nListRestartValue = pFormat->GetStart(); + } + } + } + + return nListRestartValue; +} + +bool SwTextNode::IsNotifiable() const +{ + return m_bNotifiable && IsNotificationEnabled(); +} + +bool SwTextNode::IsNotificationEnabled() const +{ + bool bResult = false; + const SwDoc * pDoc = GetDoc(); + if( pDoc ) + { + bResult = !(pDoc->IsInReading() || pDoc->IsInDtor()); + } + return bResult; +} + +void SwTextNode::SetCountedInList( bool bCounted ) +{ + if ( bCounted ) + { + // attribute not contained in paragraph style's attribute set. Thus, + // it can be reset to the attribute pool default by resetting the attribute. + ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + } + else + { + SfxBoolItem aIsCountedInListItem( RES_PARATR_LIST_ISCOUNTED, false ); + SetAttr( aIsCountedInListItem ); + } +} + +bool SwTextNode::IsCountedInList() const +{ + const SfxBoolItem& aIsCountedInListItem = + dynamic_cast<const SfxBoolItem&>(GetAttr( RES_PARATR_LIST_ISCOUNTED )); + + return aIsCountedInListItem.GetValue(); +} + +static SwList * FindList(SwTextNode *const pNode) +{ + const OUString sListId = pNode->GetListId(); + if (!sListId.isEmpty()) + { + auto & rIDLA(pNode->GetDoc()->getIDocumentListsAccess()); + SwList* pList = rIDLA.getListByName( sListId ); + if ( pList == nullptr ) + { + // Create corresponding list. + SwNumRule* pNumRule = pNode->GetNumRule(); + if ( pNumRule ) + { + pList = rIDLA.createList(sListId, pNode->GetNumRule()->GetName()); + } + } + OSL_ENSURE( pList != nullptr, + "<SwTextNode::AddToList()> - no list for given list id. Serious defect" ); + return pList; + } + return nullptr; +} + +void SwTextNode::AddToList() +{ + if ( IsInList() ) + { + OSL_FAIL( "<SwTextNode::AddToList()> - the text node is already added to a list. Serious defect" ); + return; + } + + SwList *const pList(FindList(this)); + if (pList && GetNodes().IsDocNodes()) // not for undo nodes + { + assert(!mpNodeNum); + mpNodeNum.reset(new SwNodeNum(this, false)); + pList->InsertListItem(*mpNodeNum, false, GetAttrListLevel()); + // iterate all frames & if there's one with hidden layout... + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this); + for (SwTextFrame* pFrame = iter.First(); pFrame; pFrame = iter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + if (pFrame->GetTextNodeForParaProps() == this) + { + AddToListRLHidden(); + } + break; // assume it's consistent, need to check only once + } + } + } +} + +void SwTextNode::AddToListRLHidden() +{ + if (mpNodeNumRLHidden) + { + assert(false); + OSL_FAIL( "<SwTextNode::AddToListRLHidden()> - the text node is already added to a list. Serious defect" ); + return; + } + + SwList *const pList(FindList(this)); + if (pList) + { + assert(!mpNodeNumRLHidden); + mpNodeNumRLHidden.reset(new SwNodeNum(this, true)); + pList->InsertListItem(*mpNodeNumRLHidden, true, GetAttrListLevel()); + } +} + +void SwTextNode::RemoveFromList() +{ + // sw_redlinehide: ensure it's removed from the other half too! + RemoveFromListRLHidden(); + if ( IsInList() ) + { + SwList::RemoveListItem( *mpNodeNum ); + mpNodeNum.reset(); + + SetWordCountDirty( true ); + } +} + +void SwTextNode::RemoveFromListRLHidden() +{ + if (mpNodeNumRLHidden) // direct access because RemoveFromList doesn't have layout + { + assert(mpNodeNumRLHidden->GetParent() || !GetNodes().IsDocNodes()); + SwList::RemoveListItem(*mpNodeNumRLHidden); + mpNodeNumRLHidden.reset(); + + SetWordCountDirty( true ); + } +} + +bool SwTextNode::IsInList() const +{ + return GetNum() != nullptr && GetNum()->GetParent() != nullptr; +} + +bool SwTextNode::IsFirstOfNumRule(SwRootFrame const& rLayout) const +{ + bool bResult = false; + + SwNodeNum const*const pNum(GetNum(&rLayout)); + if (pNum && pNum->GetNumRule()) + bResult = pNum->IsFirst(); + + return bResult; +} + +void SwTextNode::SetListId(OUString const& rListId) +{ + const SfxStringItem& rListIdItem = + dynamic_cast<const SfxStringItem&>(GetAttr( RES_PARATR_LIST_ID )); + if (rListIdItem.GetValue() != rListId) + { + if (rListId.isEmpty()) + { + ResetAttr( RES_PARATR_LIST_ID ); + } + else + { + SfxStringItem aNewListIdItem(RES_PARATR_LIST_ID, rListId); + SetAttr( aNewListIdItem ); + } + } +} + +OUString SwTextNode::GetListId() const +{ + const SfxStringItem& rListIdItem = + dynamic_cast<const SfxStringItem&>(GetAttr( RES_PARATR_LIST_ID )); + const OUString& sListId {rListIdItem.GetValue()}; + + // As long as no explicit list id attribute is set, use the list id of + // the list, which has been created for the applied list style. + if (sListId.isEmpty()) + { + SwNumRule* pRule = GetNumRule(); + if ( pRule ) + { + return pRule->GetDefaultListId(); + } + } + + return sListId; +} + +/** Determines, if the list level indent attributes can be applied to the + paragraph. + + The list level indents can be applied to the paragraph under the one + of following conditions: + - the list style is directly applied to the paragraph and the paragraph + has no own indent attributes. + - the list style is applied to the paragraph through one of its paragraph + styles, the paragraph has no own indent attributes and on the paragraph + style hierarchy from the paragraph to the paragraph style with the + list style no indent attributes are found. + + @return boolean +*/ +bool SwTextNode::AreListLevelIndentsApplicable() const +{ + bool bAreListLevelIndentsApplicable( true ); + + if ( !GetNum() || !GetNum()->GetNumRule() ) + { + // no list style applied to paragraph + bAreListLevelIndentsApplicable = false; + } + else if ( HasSwAttrSet() && + GetpSwAttrSet()->GetItemState( RES_LR_SPACE, false ) == SfxItemState::SET ) + { + // paragraph has hard-set indent attributes + bAreListLevelIndentsApplicable = false; + } + else if ( HasSwAttrSet() && + GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + { + // list style is directly applied to paragraph and paragraph has no + // hard-set indent attributes + bAreListLevelIndentsApplicable = true; + } + else + { + // list style is applied through one of the paragraph styles and + // paragraph has no hard-set indent attributes + + // check, paragraph's + const SwTextFormatColl* pColl = GetTextColl(); + while ( pColl ) + { + if ( pColl->GetAttrSet().GetItemState( RES_LR_SPACE, false ) == SfxItemState::SET ) + { + // indent attributes found in the paragraph style hierarchy. + bAreListLevelIndentsApplicable = false; + break; + } + + if ( pColl->GetAttrSet().GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + { + // paragraph style with the list style found and until now no + // indent attributes are found in the paragraph style hierarchy. + bAreListLevelIndentsApplicable = true; + break; + } + + pColl = dynamic_cast<const SwTextFormatColl*>(pColl->DerivedFrom()); + OSL_ENSURE( pColl, + "<SwTextNode::AreListLevelIndentsApplicable()> - something wrong in paragraph's style hierarchy. The applied list style is not found." ); + } + } + + return bAreListLevelIndentsApplicable; +} + +/** Retrieves the list tab stop position, if the paragraph's list level defines + one and this list tab stop has to merged into the tap stops of the paragraph + + @param nListTabStopPosition + output parameter - containing the list tab stop position + + @return boolean - indicating, if a list tab stop position is provided +*/ +bool SwTextNode::GetListTabStopPosition( long& nListTabStopPosition ) const +{ + bool bListTabStopPositionProvided(false); + + const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 ) + { + const SwNumFormat& rFormat = pNumRule->Get( static_cast<sal_uInt16>(GetActualListLevel()) ); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT && + rFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB ) + { + bListTabStopPositionProvided = true; + nListTabStopPosition = rFormat.GetListtabPos(); + + if ( getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ) + { + // tab stop position are treated to be relative to the "before text" + // indent value of the paragraph. Thus, adjust <nListTabStopPos>. + if ( AreListLevelIndentsApplicable() ) + { + nListTabStopPosition -= rFormat.GetIndentAt(); + } + else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING)) + { + SvxLRSpaceItem aItem = GetSwAttrSet().GetLRSpace(); + nListTabStopPosition -= aItem.GetTextLeft(); + } + } + } + } + + return bListTabStopPositionProvided; +} + +OUString SwTextNode::GetLabelFollowedBy() const +{ + const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr; + if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 ) + { + const SwNumFormat& rFormat = pNumRule->Get( static_cast<sal_uInt16>(GetActualListLevel()) ); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + switch ( rFormat.GetLabelFollowedBy() ) + { + case SvxNumberFormat::LISTTAB: + { + return "\t"; + } + break; + case SvxNumberFormat::SPACE: + { + return " "; + } + break; + case SvxNumberFormat::NEWLINE: + { + return "\n"; + } + break; + case SvxNumberFormat::NOTHING: + { + // intentionally left blank. + } + break; + default: + { + OSL_FAIL( "<SwTextNode::GetLabelFollowedBy()> - unknown SvxNumberFormat::GetLabelFollowedBy() return value" ); + } + } + } + } + + return OUString(); +} + +void SwTextNode::CalcHiddenCharFlags() const +{ + sal_Int32 nStartPos; + sal_Int32 nEndPos; + // Update of the flags is done inside GetBoundsOfHiddenRange() + SwScriptInfo::GetBoundsOfHiddenRange( *this, 0, nStartPos, nEndPos ); +} + +// #i12836# enhanced pdf export +bool SwTextNode::IsHidden() const +{ + if ( IsHiddenByParaField() || HasHiddenCharAttribute( true ) ) + return true; + + const SwSectionNode* pSectNd = FindSectionNode(); + return pSectNd && pSectNd->GetSection().IsHiddenFlag(); +} + +namespace { + // Helper class for special handling of setting attributes at text node: + // In constructor an instance of the helper class recognize whose attributes + // are set and perform corresponding actions before the intrinsic set of + // attributes has been taken place. + // In the destructor - after the attributes have been set at the text + // node - corresponding actions are performed. + // The following is handled: + // (1) When the list style attribute - RES_PARATR_NUMRULE - is set, + // (A) list style attribute is empty -> the text node is removed from + // its list. + // (B) list style attribute is not empty + // (a) text node has no list style -> add text node to its list after + // the attributes have been set. + // (b) text node has list style -> change of list style is notified + // after the attributes have been set. + // (2) When the list id attribute - RES_PARATR_LIST_ID - is set and changed, + // the text node is removed from its current list before the attributes + // are set and added to its new list after the attributes have been set. + // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is set + // and changed after the attributes have been set + // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is set + // and changed after the attributes have been set + // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE - + // is set and changed after the attributes have been set + // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is set + // and changed after the attributes have been set + // (7) Set or Reset empty list style due to changed outline level - RES_PARATR_OUTLINELEVEL. + class HandleSetAttrAtTextNode + { + public: + HandleSetAttrAtTextNode( SwTextNode& rTextNode, + const SfxPoolItem& pItem ); + HandleSetAttrAtTextNode( SwTextNode& rTextNode, + const SfxItemSet& rItemSet ); + ~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE; + + private: + SwTextNode& mrTextNode; + bool mbAddTextNodeToList; + bool mbUpdateListLevel; + bool mbUpdateListRestart; + bool mbUpdateListCount; + // #i70748# + bool mbOutlineLevelSet; + }; + + HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode, + const SfxPoolItem& pItem ) + : mrTextNode( rTextNode ), + mbAddTextNodeToList( false ), + mbUpdateListLevel( false ), + mbUpdateListRestart( false ), + mbUpdateListCount( false ), + // #i70748# + mbOutlineLevelSet( false ) + { + switch ( pItem.Which() ) + { + // handle RES_PARATR_NUMRULE + case RES_PARATR_NUMRULE: + { + mrTextNode.RemoveFromList(); + + const SwNumRuleItem& rNumRuleItem = + dynamic_cast<const SwNumRuleItem&>(pItem); + if ( !rNumRuleItem.GetValue().isEmpty() ) + { + mbAddTextNodeToList = true; + // #i105562# + + mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + } + } + break; + + // handle RES_PARATR_LIST_ID + case RES_PARATR_LIST_ID: + { + const SfxStringItem& rListIdItem = + dynamic_cast<const SfxStringItem&>(pItem); + OSL_ENSURE( rListIdItem.GetValue().getLength() > 0, + "<HandleSetAttrAtTextNode(..)> - empty list id attribute not expected. Serious defect." ); + const OUString sListIdOfTextNode = rTextNode.GetListId(); + if ( rListIdItem.GetValue() != sListIdOfTextNode ) + { + mbAddTextNodeToList = true; + if ( mrTextNode.IsInList() ) + { + mrTextNode.RemoveFromList(); + } + } + } + break; + + // handle RES_PARATR_LIST_LEVEL + case RES_PARATR_LIST_LEVEL: + { + const SfxInt16Item& aListLevelItem = + dynamic_cast<const SfxInt16Item&>(pItem); + if ( aListLevelItem.GetValue() != mrTextNode.GetAttrListLevel() ) + { + mbUpdateListLevel = true; + } + } + break; + + // handle RES_PARATR_LIST_ISRESTART + case RES_PARATR_LIST_ISRESTART: + { + const SfxBoolItem& aListIsRestartItem = + dynamic_cast<const SfxBoolItem&>(pItem); + if ( aListIsRestartItem.GetValue() != + mrTextNode.IsListRestart() ) + { + mbUpdateListRestart = true; + } + } + break; + + // handle RES_PARATR_LIST_RESTARTVALUE + case RES_PARATR_LIST_RESTARTVALUE: + { + const SfxInt16Item& aListRestartValueItem = + dynamic_cast<const SfxInt16Item&>(pItem); + if ( !mrTextNode.HasAttrListRestartValue() || + aListRestartValueItem.GetValue() != mrTextNode.GetAttrListRestartValue() ) + { + mbUpdateListRestart = true; + } + } + break; + + // handle RES_PARATR_LIST_ISCOUNTED + case RES_PARATR_LIST_ISCOUNTED: + { + const SfxBoolItem& aIsCountedInListItem = + dynamic_cast<const SfxBoolItem&>(pItem); + if ( aIsCountedInListItem.GetValue() != + mrTextNode.IsCountedInList() ) + { + mbUpdateListCount = true; + } + } + break; + + // #i70748# + // handle RES_PARATR_OUTLINELEVEL + case RES_PARATR_OUTLINELEVEL: + { + const SfxUInt16Item& aOutlineLevelItem = + dynamic_cast<const SfxUInt16Item&>(pItem); + if ( aOutlineLevelItem.GetValue() != mrTextNode.GetAttrOutlineLevel() ) + { + mbOutlineLevelSet = true; + } + } + break; + } + + } + + HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode, + const SfxItemSet& rItemSet ) + : mrTextNode( rTextNode ), + mbAddTextNodeToList( false ), + mbUpdateListLevel( false ), + mbUpdateListRestart( false ), + mbUpdateListCount( false ), + // #i70748# + mbOutlineLevelSet( false ) + { + const SfxPoolItem* pItem = nullptr; + // handle RES_PARATR_NUMRULE + if ( rItemSet.GetItemState( RES_PARATR_NUMRULE, false, &pItem ) == SfxItemState::SET ) + { + mrTextNode.RemoveFromList(); + + const SwNumRuleItem* pNumRuleItem = + dynamic_cast<const SwNumRuleItem*>(pItem); + assert(pNumRuleItem); + if ( !pNumRuleItem->GetValue().isEmpty() ) + { + mbAddTextNodeToList = true; + // #i70748# + mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + } + } + + // handle RES_PARATR_LIST_ID + if ( rItemSet.GetItemState( RES_PARATR_LIST_ID, false, &pItem ) == SfxItemState::SET ) + { + const SfxStringItem* pListIdItem = + dynamic_cast<const SfxStringItem*>(pItem); + const OUString sListIdOfTextNode = mrTextNode.GetListId(); + if ( pListIdItem && + pListIdItem->GetValue() != sListIdOfTextNode ) + { + mbAddTextNodeToList = true; + if ( mrTextNode.IsInList() ) + { + mrTextNode.RemoveFromList(); + } + } + } + + // handle RES_PARATR_LIST_LEVEL + if ( rItemSet.GetItemState( RES_PARATR_LIST_LEVEL, false, &pItem ) == SfxItemState::SET ) + { + const SfxInt16Item* pListLevelItem = + dynamic_cast<const SfxInt16Item*>(pItem); + if (pListLevelItem && pListLevelItem->GetValue() != mrTextNode.GetAttrListLevel()) + { + mbUpdateListLevel = true; + } + } + + // handle RES_PARATR_LIST_ISRESTART + if ( rItemSet.GetItemState( RES_PARATR_LIST_ISRESTART, false, &pItem ) == SfxItemState::SET ) + { + const SfxBoolItem* pListIsRestartItem = + dynamic_cast<const SfxBoolItem*>(pItem); + if (pListIsRestartItem && pListIsRestartItem->GetValue() != mrTextNode.IsListRestart()) + { + mbUpdateListRestart = true; + } + } + + // handle RES_PARATR_LIST_RESTARTVALUE + if ( rItemSet.GetItemState( RES_PARATR_LIST_RESTARTVALUE, false, &pItem ) == SfxItemState::SET ) + { + const SfxInt16Item* pListRestartValueItem = + dynamic_cast<const SfxInt16Item*>(pItem); + if ( !mrTextNode.HasAttrListRestartValue() || (pListRestartValueItem && + pListRestartValueItem->GetValue() != mrTextNode.GetAttrListRestartValue()) ) + { + mbUpdateListRestart = true; + } + } + + // handle RES_PARATR_LIST_ISCOUNTED + if ( rItemSet.GetItemState( RES_PARATR_LIST_ISCOUNTED, false, &pItem ) == SfxItemState::SET ) + { + const SfxBoolItem* pIsCountedInListItem = + dynamic_cast<const SfxBoolItem*>(pItem); + if (pIsCountedInListItem && pIsCountedInListItem->GetValue() != + mrTextNode.IsCountedInList()) + { + mbUpdateListCount = true; + } + } + + // #i70748# + // handle RES_PARATR_OUTLINELEVEL + if ( rItemSet.GetItemState( RES_PARATR_OUTLINELEVEL, false, &pItem ) == SfxItemState::SET ) + { + const SfxUInt16Item* pOutlineLevelItem = + dynamic_cast<const SfxUInt16Item*>(pItem); + if (pOutlineLevelItem && pOutlineLevelItem->GetValue() != + mrTextNode.GetAttrOutlineLevel()) + { + mbOutlineLevelSet = true; + } + } + } + + HandleSetAttrAtTextNode::~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE + { + if ( mbAddTextNodeToList ) + { + SwNumRule* pNumRuleAtTextNode = mrTextNode.GetNumRule(); + if ( pNumRuleAtTextNode ) + { + mrTextNode.AddToList(); + } + } + else + { + if ( mbUpdateListLevel && mrTextNode.IsInList() ) + { + auto const nLevel(mrTextNode.GetAttrListLevel()); + mrTextNode.DoNum( + [nLevel](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel); }); + } + + if ( mbUpdateListRestart && mrTextNode.IsInList() ) + { + mrTextNode.DoNum( + [](SwNodeNum & rNum) { + rNum.InvalidateMe(); + rNum.NotifyInvalidSiblings(); + }); + } + + if ( mbUpdateListCount && mrTextNode.IsInList() ) + { + mrTextNode.DoNum( + [](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(); }); + } + } + + // #i70748# + if (mbOutlineLevelSet) + { + mrTextNode.GetNodes().UpdateOutlineNode(mrTextNode); + if (mrTextNode.GetAttrOutlineLevel() == 0) + { + mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + } + else + { + const SfxPoolItem* pItem = nullptr; + if ( mrTextNode.GetSwAttrSet().GetItemState( RES_PARATR_NUMRULE, + true, &pItem ) + != SfxItemState::SET ) + { + mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr(); + } + } + } + } + // End of class <HandleSetAttrAtTextNode> +} + +bool SwTextNode::SetAttr( const SfxPoolItem& pItem ) +{ + const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr ); + mbInSetOrResetAttr = true; + + HandleSetAttrAtTextNode aHandleSetAttr( *this, pItem ); + + bool bRet = SwContentNode::SetAttr( pItem ); + + mbInSetOrResetAttr = bOldIsSetOrResetAttr; + + return bRet; +} + +bool SwTextNode::SetAttr( const SfxItemSet& rSet ) +{ + const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr ); + mbInSetOrResetAttr = true; + + HandleSetAttrAtTextNode aHandleSetAttr( *this, rSet ); + + bool bRet = SwContentNode::SetAttr( rSet ); + + mbInSetOrResetAttr = bOldIsSetOrResetAttr; + + return bRet; +} + +namespace { + // Helper class for special handling of resetting attributes at text node: + // In constructor an instance of the helper class recognize whose attributes + // are reset and perform corresponding actions before the intrinsic reset of + // attributes has been taken place. + // In the destructor - after the attributes have been reset at the text + // node - corresponding actions are performed. + // The following is handled: + // (1) When the list style attribute - RES_PARATR_NUMRULE - is reset, + // the text is removed from its list before the attributes have been reset. + // (2) When the list id attribute - RES_PARATR_LIST_ID - is reset, + // the text is removed from its list before the attributes have been reset. + // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is reset. + // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is reset. + // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE - is reset. + // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is reset. + // (7) Reset empty list style, if outline level attribute - RES_PARATR_OUTLINELEVEL - is reset. + class HandleResetAttrAtTextNode + { + public: + HandleResetAttrAtTextNode( SwTextNode& rTextNode, + const sal_uInt16 nWhich1, + sal_uInt16 nWhich2 ); + HandleResetAttrAtTextNode( SwTextNode& rTextNode, + const std::vector<sal_uInt16>& rWhichArr ); + explicit HandleResetAttrAtTextNode( SwTextNode& rTextNode ); + + ~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE; + + private: + SwTextNode& mrTextNode; + bool mbListStyleOrIdReset; + bool mbUpdateListLevel; + bool mbUpdateListRestart; + bool mbUpdateListCount; + + void init( const std::vector<sal_uInt16>& rWhichArr ); + }; + + HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode, + const sal_uInt16 nWhich1, + sal_uInt16 nWhich2 ) + : mrTextNode( rTextNode ), + mbListStyleOrIdReset( false ), + mbUpdateListLevel( false ), + mbUpdateListRestart( false ), + mbUpdateListCount( false ) + { + if ( nWhich2 < nWhich1 ) + nWhich2 = nWhich1; + std::vector<sal_uInt16> rWhichArr; + for ( sal_uInt16 nWhich = nWhich1; nWhich <= nWhich2; ++nWhich ) + rWhichArr.push_back( nWhich ); + + init( rWhichArr ); + } + + HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode, + const std::vector<sal_uInt16>& rWhichArr ) + : mrTextNode( rTextNode ), + mbListStyleOrIdReset( false ), + mbUpdateListLevel( false ), + mbUpdateListRestart( false ), + mbUpdateListCount( false ) + { + init( rWhichArr ); + } + + HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode ) + : mrTextNode( rTextNode ), + mbListStyleOrIdReset( true ), + mbUpdateListLevel( false ), + mbUpdateListRestart( false ), + mbUpdateListCount( false ) + { + if ( rTextNode.IsInList() ) + { + rTextNode.RemoveFromList(); + } + // #i70748# + mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + } + + void HandleResetAttrAtTextNode::init( const std::vector<sal_uInt16>& rWhichArr ) + { + bool bRemoveFromList( false ); + { + for (const auto& rWhich : rWhichArr) + { + if ( rWhich == RES_PARATR_NUMRULE ) + { + bRemoveFromList = bRemoveFromList || + mrTextNode.GetNumRule() != nullptr; + mbListStyleOrIdReset = true; + } + else if ( rWhich == RES_PARATR_LIST_ID ) + { + bRemoveFromList = bRemoveFromList || + ( mrTextNode.GetpSwAttrSet() && + mrTextNode.GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_ID, false ) == SfxItemState::SET ); + mbListStyleOrIdReset = true; + } + else if ( rWhich == RES_PARATR_OUTLINELEVEL ) + mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr(); + else if ( rWhich == RES_BACKGROUND ) + mrTextNode.ResetAttr( XATTR_FILL_FIRST, XATTR_FILL_LAST ); + + if ( !bRemoveFromList ) + { + // RES_PARATR_LIST_LEVEL + mbUpdateListLevel = mbUpdateListLevel || + ( rWhich == RES_PARATR_LIST_LEVEL && + mrTextNode.HasAttrListLevel() ); + + // RES_PARATR_LIST_ISRESTART and RES_PARATR_LIST_RESTARTVALUE + mbUpdateListRestart = mbUpdateListRestart || + ( rWhich == RES_PARATR_LIST_ISRESTART && + mrTextNode.IsListRestart() ) || + ( rWhich == RES_PARATR_LIST_RESTARTVALUE && + mrTextNode.HasAttrListRestartValue() ); + + // RES_PARATR_LIST_ISCOUNTED + mbUpdateListCount = mbUpdateListCount || + ( rWhich == RES_PARATR_LIST_ISCOUNTED && + !mrTextNode.IsCountedInList() ); + } + } + } + + if ( bRemoveFromList && mrTextNode.IsInList() ) + { + mrTextNode.RemoveFromList(); + } + } + + HandleResetAttrAtTextNode::~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE + { + if ( mbListStyleOrIdReset && !mrTextNode.IsInList() ) + { + // check, if in spite of the reset of the list style or the list id + // the paragraph still has to be added to a list. + if (mrTextNode.GetNumRule() && !mrTextNode.GetListId().isEmpty()) + { + // #i96062# + // If paragraph has no list level attribute set and list style + // is the outline style, apply outline level as the list level. + if ( !mrTextNode.HasAttrListLevel() && + mrTextNode.GetNumRule()->GetName()==SwNumRule::GetOutlineRuleName() && + mrTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle() ) + { + int nNewListLevel = mrTextNode.GetTextColl()->GetAssignedOutlineStyleLevel(); + if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL ) + { + mrTextNode.SetAttrListLevel( nNewListLevel ); + } + } + mrTextNode.AddToList(); + } + // #i70748# + // #i105562# + else + { + assert(!mrTextNode.GetpSwAttrSet() + || dynamic_cast<const SfxUInt16Item*>( + &mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false))); + if (mrTextNode.GetpSwAttrSet() + && static_cast<const SfxUInt16Item&>( + mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false)).GetValue() > 0) + { + mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr(); + } + } + } + + if ( mrTextNode.IsInList() ) + { + if ( mbUpdateListLevel ) + { + auto const nLevel(mrTextNode.GetAttrListLevel()); + mrTextNode.DoNum( + [nLevel](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel); }); + } + + if ( mbUpdateListRestart ) + { + mrTextNode.DoNum( + [](SwNodeNum & rNum) { + rNum.InvalidateMe(); + rNum.NotifyInvalidSiblings(); + }); + } + + if ( mbUpdateListCount ) + { + mrTextNode.DoNum( + [](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(); }); + } + } + } + // End of class <HandleResetAttrAtTextNode> +} + +bool SwTextNode::ResetAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 ) +{ + const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr ); + mbInSetOrResetAttr = true; + + HandleResetAttrAtTextNode aHandleResetAttr( *this, nWhich1, nWhich2 ); + + bool bRet = SwContentNode::ResetAttr( nWhich1, nWhich2 ); + + mbInSetOrResetAttr = bOldIsSetOrResetAttr; + + return bRet; +} + +bool SwTextNode::ResetAttr( const std::vector<sal_uInt16>& rWhichArr ) +{ + const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr ); + mbInSetOrResetAttr = true; + + HandleResetAttrAtTextNode aHandleResetAttr( *this, rWhichArr ); + + bool bRet = SwContentNode::ResetAttr( rWhichArr ); + + mbInSetOrResetAttr = bOldIsSetOrResetAttr; + + return bRet; +} + +sal_uInt16 SwTextNode::ResetAllAttr() +{ + const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr ); + mbInSetOrResetAttr = true; + + HandleResetAttrAtTextNode aHandleResetAttr( *this ); + + const sal_uInt16 nRet = SwContentNode::ResetAllAttr(); + + mbInSetOrResetAttr = bOldIsSetOrResetAttr; + + return nRet; +} + +void SwTextNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextNode")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(GetIndex()).getStr())); + + OUString sText = GetText(); + for (int i = 0; i < 32; ++i) + sText = sText.replace(i, '*'); + xmlTextWriterStartElement(pWriter, BAD_CAST("m_Text")); + xmlTextWriterWriteString(pWriter, BAD_CAST(sText.toUtf8().getStr())); + xmlTextWriterEndElement(pWriter); + + if (GetFormatColl()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetFormatColl()->GetName().toUtf8().getStr())); + xmlTextWriterEndElement(pWriter); + } + + if (HasSwAttrSet()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("SwAttrSet")); + GetSwAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + + if (HasHints()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("SwpHints")); + const SwpHints& rHints = GetSwpHints(); + for (size_t i = 0; i < rHints.Count(); ++i) + rHints.Get(i)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + + if (GetNumRule()) + GetNumRule()->dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +sal_uInt32 SwTextNode::GetRsid( sal_Int32 nStt, sal_Int32 nEnd ) const +{ + SfxItemSet aSet( const_cast<SfxItemPool&>(static_cast<SfxItemPool const &>(GetDoc()->GetAttrPool())), svl::Items<RES_CHRATR_RSID, RES_CHRATR_RSID>{} ); + if (GetParaAttr(aSet, nStt, nEnd)) + { + const SvxRsidItem* pRsid = aSet.GetItem<SvxRsidItem>(RES_CHRATR_RSID); + if( pRsid ) + return pRsid->GetValue(); + } + + return 0; +} + +sal_uInt32 SwTextNode::GetParRsid() const +{ + return reinterpret_cast<const SvxRsidItem&>(GetAttr( RES_PARATR_RSID )).GetValue(); +} + +bool SwTextNode::CompareParRsid( const SwTextNode &rTextNode ) const +{ + sal_uInt32 nThisRsid = GetParRsid(); + sal_uInt32 nRsid = rTextNode.GetParRsid(); + + return nThisRsid == nRsid; +} + +bool SwTextNode::CompareRsid( const SwTextNode &rTextNode, sal_Int32 nStt1, sal_Int32 nStt2 ) const +{ + sal_uInt32 nThisRsid = GetRsid( nStt1, nStt1 ); + sal_uInt32 nRsid = rTextNode.GetRsid( nStt2, nStt2 ); + + return nThisRsid == nRsid; +} + +// sw::Metadatable +::sfx2::IXmlIdRegistry& SwTextNode::GetRegistry() +{ + return GetDoc()->GetXmlIdRegistry(); +} + +bool SwTextNode::IsInClipboard() const +{ + return GetDoc()->IsClipBoard(); +} + +bool SwTextNode::IsInUndo() const +{ + return GetDoc()->GetIDocumentUndoRedo().IsUndoNodes(GetNodes()); +} + +bool SwTextNode::IsInContent() const +{ + return !GetDoc()->IsInHeaderFooter( SwNodeIndex(*this) ); +} + +void SwTextNode::SwClientNotify( const SwModify& rModify, const SfxHint& rHint ) +{ + if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + bool bWasNotifiable = m_bNotifiable; + m_bNotifiable = false; + + const auto pOldValue = pLegacyHint->m_pOld; + const auto pNewValue = pLegacyHint->m_pNew; + // Override Modify so that deleting styles works properly (outline + // numbering!). + // Never call ChgTextCollUpdateNum for Nodes in Undo. + if( pOldValue + && pNewValue + && RES_FMT_CHG == pOldValue->Which() + && GetRegisteredIn() == static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat + && GetNodes().IsDocNodes() ) + { + ChgTextCollUpdateNum( + static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat), + static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat) ); + } + + // reset fill information + if (maFillAttributes && pNewValue) + { + const sal_uInt16 nWhich = pNewValue->Which(); + bool bReset(RES_FMT_CHG == nWhich); // ..on format change (e.g. style changed) + + if(!bReset && RES_ATTRSET_CHG == nWhich) // ..on ItemChange from DrawingLayer FillAttributes + { + SfxItemIter aIter(*static_cast<const SwAttrSetChg*>(pNewValue)->GetChgSet()); + + for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem && !bReset; pItem = aIter.NextItem()) + { + bReset = !IsInvalidItem(pItem) && pItem->Which() >= XATTR_FILL_FIRST && pItem->Which() <= XATTR_FILL_LAST; + } + } + + if(bReset) + { + maFillAttributes.reset(); + } + } + + if ( !mbInSetOrResetAttr ) + { + HandleModifyAtTextNode( *this, pOldValue, pNewValue ); + } + + SwContentNode::SwClientNotify(rModify, rHint); + + SwDoc* pDoc = GetDoc(); + // #125329# - assure that text node is in document nodes array + if ( pDoc && !pDoc->IsInDtor() && &pDoc->GetNodes() == &GetNodes() ) + { + pDoc->GetNodes().UpdateOutlineNode(*this); + } + + m_bNotifiable = bWasNotifiable; + + if (pOldValue && (RES_REMOVE_UNO_OBJECT == pOldValue->Which())) + { // invalidate cached uno object + SetXParagraph(css::uno::Reference<css::text::XTextContent>(nullptr)); + } + } + else if (dynamic_cast<const SwAttrHint*>(&rHint)) + { + if (&rModify == GetRegisteredIn()) + ChkCondColl(); + } +} + +uno::Reference< rdf::XMetadatable > +SwTextNode::MakeUnoObject() +{ + const uno::Reference<rdf::XMetadatable> xMeta( + SwXParagraph::CreateXParagraph(*GetDoc(), this), uno::UNO_QUERY); + return xMeta; +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwTextNode::getSdrAllFillAttributesHelper() const +{ + // create SdrAllFillAttributesHelper on demand + if(!maFillAttributes) + { + const_cast< SwTextNode* >(this)->maFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(GetSwAttrSet()); + } + + return maFillAttributes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/swfntcch.cxx b/sw/source/core/txtnode/swfntcch.cxx new file mode 100644 index 000000000..49783fd25 --- /dev/null +++ b/sw/source/core/txtnode/swfntcch.cxx @@ -0,0 +1,75 @@ +/* -*- 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 <sal/config.h> + +#include <shellio.hxx> +#include <viewsh.hxx> +#include <swfntcch.hxx> +#include <fmtcol.hxx> +#include <fntcache.hxx> +#include <swfont.hxx> + +// from atrstck.cxx +extern const sal_uInt8 StackPos[]; + +// FontCache is created in txtinit.cxx TextInit_ and deleted in TextFinit +SwFontCache *pSwFontCache = nullptr; + +SwFontObj::SwFontObj( const void *pOwn, SwViewShell *pSh ) : + SwCacheObj( pOwn ), + m_aSwFont( &static_cast<SwTextFormatColl const *>(pOwn)->GetAttrSet(), pSh ? &pSh->getIDocumentSettingAccess() : nullptr ) +{ + m_aSwFont.AllocFontCacheId( pSh, m_aSwFont.GetActual() ); + const SwAttrSet& rAttrSet = static_cast<SwTextFormatColl const *>(pOwn)->GetAttrSet(); + for (sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++) + m_pDefaultArray[ StackPos[ i ] ] = &rAttrSet.Get( i ); +} + +SwFontObj::~SwFontObj() +{ +} + +SwFontAccess::SwFontAccess( const void *pOwn, SwViewShell *pSh ) : + SwCacheAccess( *pSwFontCache, pOwn, + static_cast<const SwTextFormatColl*>(pOwn)->IsInSwFntCache() ), + m_pShell( pSh ) +{ +} + +SwFontObj *SwFontAccess::Get( ) +{ + return static_cast<SwFontObj *>( SwCacheAccess::Get( ) ); +} + +SwCacheObj *SwFontAccess::NewObj( ) +{ + const_cast<SwTextFormatColl*>(static_cast<const SwTextFormatColl*>(m_pOwner))->SetInSwFntCache( true ); + return new SwFontObj( m_pOwner, m_pShell ); +} + +SAL_DLLPUBLIC_EXPORT void FlushFontCache() +{ + if (pSwFontCache) + pSwFontCache->Flush(); + if (pFntCache) + pFntCache->Flush(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/swfont.cxx b/sw/source/core/txtnode/swfont.cxx new file mode 100644 index 000000000..60d22422c --- /dev/null +++ b/sw/source/core/txtnode/swfont.cxx @@ -0,0 +1,1541 @@ +/* -*- 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 <hintids.hxx> + +#include <com/sun/star/i18n/ScriptType.hpp> +#include <vcl/outdev.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/charhiddenitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <IDocumentSettingAccess.hxx> +#include <charatr.hxx> +#include <viewsh.hxx> +#include <swfont.hxx> +#include <fntcache.hxx> +#include <txtfrm.hxx> +#include <scriptinfo.hxx> + +#ifdef DBG_UTIL +// global Variable +SvStatistics g_SvStat; +#endif + +using namespace ::com::sun::star; + +// set background brush, depending on character formatting +void SwFont::SetBackColor( Color* pNewColor ) +{ + m_pBackColor.reset( pNewColor ); + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +void SwFont::SetTopBorder( const editeng::SvxBorderLine* pTopBorder ) +{ + if( pTopBorder ) + m_aTopBorder = *pTopBorder; + else + { + m_aTopBorder.reset(); + m_nTopBorderDist = 0; + } + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +void SwFont::SetBottomBorder( const editeng::SvxBorderLine* pBottomBorder ) +{ + if( pBottomBorder ) + m_aBottomBorder = *pBottomBorder; + else + { + m_aBottomBorder.reset(); + m_nBottomBorderDist = 0; + } + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +void SwFont::SetRightBorder( const editeng::SvxBorderLine* pRightBorder ) +{ + if( pRightBorder ) + m_aRightBorder = *pRightBorder; + else + { + m_aRightBorder.reset(); + m_nRightBorderDist = 0; + } + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +void SwFont::SetLeftBorder( const editeng::SvxBorderLine* pLeftBorder ) +{ + if( pLeftBorder ) + m_aLeftBorder = *pLeftBorder; + else + { + m_aLeftBorder.reset(); + m_nLeftBorderDist = 0; + } + m_bFontChg = true; + m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; +} + +const std::optional<editeng::SvxBorderLine>& +SwFont::GetAbsTopBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const +{ + switch (GetOrientation(bVertLayout, bVertLayoutLRBT)) + { + case 0 : + return m_aTopBorder; + break; + case 900 : + return m_aRightBorder; + break; + case 1800 : + return m_aBottomBorder; + break; + case 2700 : + return m_aLeftBorder; + break; + default : + assert(false); + return m_aTopBorder; + break; + } +} + +const std::optional<editeng::SvxBorderLine>& +SwFont::GetAbsBottomBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const +{ + switch (GetOrientation(bVertLayout, bVertLayoutLRBT)) + { + case 0 : + return m_aBottomBorder; + break; + case 900 : + return m_aLeftBorder; + break; + case 1800 : + return m_aTopBorder; + break; + case 2700 : + return m_aRightBorder; + break; + default : + assert(false); + return m_aBottomBorder; + break; + } +} + +const std::optional<editeng::SvxBorderLine>& +SwFont::GetAbsLeftBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const +{ + switch (GetOrientation(bVertLayout, bVertLayoutLRBT)) + { + case 0 : + return m_aLeftBorder; + break; + case 900 : + return m_aTopBorder; + break; + case 1800 : + return m_aRightBorder; + break; + case 2700 : + return m_aBottomBorder; + break; + default : + assert(false); + return m_aLeftBorder; + break; + } +} + +const std::optional<editeng::SvxBorderLine>& +SwFont::GetAbsRightBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const +{ + switch (GetOrientation(bVertLayout, bVertLayoutLRBT)) + { + case 0 : + return m_aRightBorder; + break; + case 900 : + return m_aBottomBorder; + break; + case 1800 : + return m_aLeftBorder; + break; + case 2700 : + return m_aTopBorder; + break; + default : + assert(false); + return m_aRightBorder; + break; + } +} + +SvxShadowLocation SwFont::GetAbsShadowLocation(const bool bVertLayout, + const bool bVertLayoutLRBT) const +{ + SvxShadowLocation aLocation = SvxShadowLocation::NONE; + switch (GetOrientation(bVertLayout, bVertLayoutLRBT)) + { + case 0: + aLocation = m_aShadowLocation; + break; + + case 900: + switch ( m_aShadowLocation ) + { + case SvxShadowLocation::TopLeft: + aLocation = SvxShadowLocation::BottomLeft; + break; + case SvxShadowLocation::TopRight: + aLocation = SvxShadowLocation::TopLeft; + break; + case SvxShadowLocation::BottomLeft: + aLocation = SvxShadowLocation::BottomRight; + break; + case SvxShadowLocation::BottomRight: + aLocation = SvxShadowLocation::TopRight; + break; + case SvxShadowLocation::NONE: + case SvxShadowLocation::End: + aLocation = m_aShadowLocation; + break; + } + break; + + case 1800: + switch ( m_aShadowLocation ) + { + case SvxShadowLocation::TopLeft: + aLocation = SvxShadowLocation::BottomRight; + break; + case SvxShadowLocation::TopRight: + aLocation = SvxShadowLocation::BottomLeft; + break; + case SvxShadowLocation::BottomLeft: + aLocation = SvxShadowLocation::TopRight; + break; + case SvxShadowLocation::BottomRight: + aLocation = SvxShadowLocation::TopLeft; + break; + case SvxShadowLocation::NONE: + case SvxShadowLocation::End: + aLocation = m_aShadowLocation; + break; + } + break; + + case 2700: + switch ( m_aShadowLocation ) + { + case SvxShadowLocation::TopLeft: + aLocation = SvxShadowLocation::TopRight; + break; + case SvxShadowLocation::TopRight: + aLocation = SvxShadowLocation::BottomRight; + break; + case SvxShadowLocation::BottomLeft: + aLocation = SvxShadowLocation::TopLeft; + break; + case SvxShadowLocation::BottomRight: + aLocation = SvxShadowLocation::BottomLeft; + break; + case SvxShadowLocation::NONE: + case SvxShadowLocation::End: + aLocation = m_aShadowLocation; + break; + } + break; + + default: + assert(false); + break; + } + return aLocation; +} + +sal_uInt16 SwFont::CalcShadowSpace(const SvxShadowItemSide nShadow, const bool bVertLayout, + const bool bVertLayoutLRBT, const bool bSkipLeft, + const bool bSkipRight) const +{ + sal_uInt16 nSpace = 0; + const sal_uInt16 nOrient = GetOrientation(bVertLayout, bVertLayoutLRBT); + const SvxShadowLocation aLoc = GetAbsShadowLocation(bVertLayout, bVertLayoutLRBT); + switch( nShadow ) + { + case SvxShadowItemSide::TOP: + if(( aLoc == SvxShadowLocation::TopLeft || + aLoc == SvxShadowLocation::TopRight ) && + ( nOrient == 0 || nOrient == 1800 || + ( nOrient == 900 && !bSkipRight ) || + ( nOrient == 2700 && !bSkipLeft ))) + { + nSpace = m_nShadowWidth; + } + break; + + case SvxShadowItemSide::BOTTOM: + if(( aLoc == SvxShadowLocation::BottomLeft || + aLoc == SvxShadowLocation::BottomRight ) && + ( nOrient == 0 || nOrient == 1800 || + ( nOrient == 900 && !bSkipLeft ) || + ( nOrient == 2700 && !bSkipRight ))) + { + nSpace = m_nShadowWidth; + } + break; + + case SvxShadowItemSide::LEFT: + if(( aLoc == SvxShadowLocation::TopLeft || + aLoc == SvxShadowLocation::BottomLeft ) && + ( nOrient == 900 || nOrient == 2700 || + ( nOrient == 0 && !bSkipLeft ) || + ( nOrient == 1800 && !bSkipRight ))) + { + nSpace = m_nShadowWidth; + } + break; + + case SvxShadowItemSide::RIGHT: + if(( aLoc == SvxShadowLocation::TopRight || + aLoc == SvxShadowLocation::BottomRight ) && + ( nOrient == 900 || nOrient == 2700 || + ( nOrient == 0 && !bSkipRight ) || + ( nOrient == 1800 && !bSkipLeft ))) + { + nSpace = m_nShadowWidth; + } + break; + default: + assert(false); + break; + } + + return nSpace; +} + +// maps directions for vertical layout +static sal_uInt16 MapDirection(sal_uInt16 nDir, const bool bVertFormat, const bool bVertFormatLRBT) +{ + if ( bVertFormat ) + { + switch ( nDir ) + { + case 0 : + if (bVertFormatLRBT) + nDir = 900; + else + nDir = 2700; + break; + case 900 : + nDir = 0; + break; + case 2700 : + nDir = 1800; + break; +#if OSL_DEBUG_LEVEL > 0 + default : + OSL_FAIL( "Unsupported direction" ); + break; +#endif + } + } + return nDir; +} + +// maps the absolute direction set at the font to its logical counterpart +// in the rotated environment +sal_uInt16 UnMapDirection(sal_uInt16 nDir, const bool bVertFormat, const bool bVertFormatLRBT) +{ + if (bVertFormatLRBT) + { + switch (nDir) + { + case 900: + nDir = 0; + break; + default: + SAL_WARN("sw.core", "unsupported direction for VertLRBT"); + break; + } + return nDir; + } + + if ( bVertFormat ) + { + switch ( nDir ) + { + case 0 : + nDir = 900; + break; + case 1800 : + nDir = 2700; + break; + case 2700 : + nDir = 0; + break; +#if OSL_DEBUG_LEVEL > 0 + default : + OSL_FAIL( "Unsupported direction" ); + break; +#endif + } + } + return nDir; +} + +sal_uInt16 SwFont::GetOrientation(const bool bVertFormat, const bool bVertFormatLRBT) const +{ + return UnMapDirection(m_aSub[m_nActual].GetOrientation(), bVertFormat, bVertFormatLRBT); +} + +void SwFont::SetVertical(sal_uInt16 nDir, const bool bVertFormat, const bool bVertLayoutLRBT) +{ + // map direction if frame has vertical layout + nDir = MapDirection(nDir, bVertFormat, bVertLayoutLRBT); + + if( nDir != m_aSub[SwFontScript::Latin].GetOrientation() ) + { + m_bFontChg = true; + bool bVertical = bVertFormat && !bVertLayoutLRBT; + m_aSub[SwFontScript::Latin].SetVertical(nDir, bVertical); + m_aSub[SwFontScript::CJK].SetVertical(nDir, bVertical); + m_aSub[SwFontScript::CTL].SetVertical(nDir, bVertical); + } +} + +/* + Escapement: + frEsc: Fraction, ratio of Escapements + Esc = resulting Escapement + A1 = original Ascent (nOrgAscent) + A2 = shrunk Ascent (nEscAscent) + Ax = resulting Ascent (GetAscent()) + H1 = original Height (nOrgHeight) + H2 = shrunk Height (nEscHeight) + Hx = resulting Height (GetHeight()) + Bx = resulting Baseline for Text (CalcPos()) + (Attention: Y - A1!) + + Escapement: + Esc = H1 * frEsc; + + Superscript: + Ax = A2 + Esc; + Hx = H2 + Esc; + Bx = A1 - Esc; + + Subscript: + Ax = A1; + Hx = A1 + Esc + (H2 - A2); + Bx = A1 + Esc; +*/ + +// nEsc is the percentage +sal_uInt16 SwSubFont::CalcEscAscent( const sal_uInt16 nOldAscent ) const +{ + if( DFLT_ESC_AUTO_SUPER != GetEscapement() && + DFLT_ESC_AUTO_SUB != GetEscapement() ) + { + const long nAscent = nOldAscent + + ( static_cast<long>(m_nOrgHeight) * GetEscapement() ) / 100; + if ( nAscent>0 ) + return std::max<sal_uInt16>( nAscent, m_nOrgAscent ); + } + return m_nOrgAscent; +} + +void SwFont::SetDiffFnt( const SfxItemSet *pAttrSet, + const IDocumentSettingAccess *pIDocumentSettingAccess ) +{ + m_pBackColor.reset(); + + if( pAttrSet ) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_FONT, + true, &pItem )) + { + const SvxFontItem *pFont = static_cast<const SvxFontItem *>(pItem); + m_aSub[SwFontScript::Latin].SetFamily( pFont->GetFamily() ); + m_aSub[SwFontScript::Latin].Font::SetFamilyName( pFont->GetFamilyName() ); + m_aSub[SwFontScript::Latin].Font::SetStyleName( pFont->GetStyleName() ); + m_aSub[SwFontScript::Latin].Font::SetPitch( pFont->GetPitch() ); + m_aSub[SwFontScript::Latin].Font::SetCharSet( pFont->GetCharSet() ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_FONTSIZE, + true, &pItem )) + { + const SvxFontHeightItem *pHeight = static_cast<const SvxFontHeightItem *>(pItem); + m_aSub[SwFontScript::Latin].SvxFont::SetPropr( 100 ); + m_aSub[SwFontScript::Latin].m_aSize = m_aSub[SwFontScript::Latin].Font::GetFontSize(); + Size aTmpSize = m_aSub[SwFontScript::Latin].m_aSize; + aTmpSize.setHeight( pHeight->GetHeight() ); + m_aSub[SwFontScript::Latin].SetSize( aTmpSize ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_POSTURE, + true, &pItem )) + m_aSub[SwFontScript::Latin].Font::SetItalic( static_cast<const SvxPostureItem*>(pItem)->GetPosture() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_WEIGHT, + true, &pItem )) + m_aSub[SwFontScript::Latin].Font::SetWeight( static_cast<const SvxWeightItem*>(pItem)->GetWeight() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_LANGUAGE, + true, &pItem )) + m_aSub[SwFontScript::Latin].SetLanguage( static_cast<const SvxLanguageItem*>(pItem)->GetLanguage() ); + + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CJK_FONT, + true, &pItem )) + { + const SvxFontItem *pFont = static_cast<const SvxFontItem *>(pItem); + m_aSub[SwFontScript::CJK].SetFamily( pFont->GetFamily() ); + m_aSub[SwFontScript::CJK].Font::SetFamilyName( pFont->GetFamilyName() ); + m_aSub[SwFontScript::CJK].Font::SetStyleName( pFont->GetStyleName() ); + m_aSub[SwFontScript::CJK].Font::SetPitch( pFont->GetPitch() ); + m_aSub[SwFontScript::CJK].Font::SetCharSet( pFont->GetCharSet() ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CJK_FONTSIZE, + true, &pItem )) + { + const SvxFontHeightItem *pHeight = static_cast<const SvxFontHeightItem *>(pItem); + m_aSub[SwFontScript::CJK].SvxFont::SetPropr( 100 ); + m_aSub[SwFontScript::CJK].m_aSize = m_aSub[SwFontScript::CJK].Font::GetFontSize(); + Size aTmpSize = m_aSub[SwFontScript::CJK].m_aSize; + aTmpSize.setHeight( pHeight->GetHeight() ); + m_aSub[SwFontScript::CJK].SetSize( aTmpSize ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CJK_POSTURE, + true, &pItem )) + m_aSub[SwFontScript::CJK].Font::SetItalic( static_cast<const SvxPostureItem*>(pItem)->GetPosture() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CJK_WEIGHT, + true, &pItem )) + m_aSub[SwFontScript::CJK].Font::SetWeight( static_cast<const SvxWeightItem*>(pItem)->GetWeight() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CJK_LANGUAGE, + true, &pItem )) + { + LanguageType eNewLang = static_cast<const SvxLanguageItem*>(pItem)->GetLanguage(); + m_aSub[SwFontScript::CJK].SetLanguage( eNewLang ); + m_aSub[SwFontScript::Latin].SetCJKContextLanguage( eNewLang ); + m_aSub[SwFontScript::CJK].SetCJKContextLanguage( eNewLang ); + m_aSub[SwFontScript::CTL].SetCJKContextLanguage( eNewLang ); + } + + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CTL_FONT, + true, &pItem )) + { + const SvxFontItem *pFont = static_cast<const SvxFontItem *>(pItem); + m_aSub[SwFontScript::CTL].SetFamily( pFont->GetFamily() ); + m_aSub[SwFontScript::CTL].Font::SetFamilyName( pFont->GetFamilyName() ); + m_aSub[SwFontScript::CTL].Font::SetStyleName( pFont->GetStyleName() ); + m_aSub[SwFontScript::CTL].Font::SetPitch( pFont->GetPitch() ); + m_aSub[SwFontScript::CTL].Font::SetCharSet( pFont->GetCharSet() ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CTL_FONTSIZE, + true, &pItem )) + { + const SvxFontHeightItem *pHeight = static_cast<const SvxFontHeightItem *>(pItem); + m_aSub[SwFontScript::CTL].SvxFont::SetPropr( 100 ); + m_aSub[SwFontScript::CTL].m_aSize = m_aSub[SwFontScript::CTL].Font::GetFontSize(); + Size aTmpSize = m_aSub[SwFontScript::CTL].m_aSize; + aTmpSize.setHeight( pHeight->GetHeight() ); + m_aSub[SwFontScript::CTL].SetSize( aTmpSize ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CTL_POSTURE, + true, &pItem )) + m_aSub[SwFontScript::CTL].Font::SetItalic( static_cast<const SvxPostureItem*>(pItem)->GetPosture() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CTL_WEIGHT, + true, &pItem )) + m_aSub[SwFontScript::CTL].Font::SetWeight( static_cast<const SvxWeightItem*>(pItem)->GetWeight() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CTL_LANGUAGE, + true, &pItem )) + m_aSub[SwFontScript::CTL].SetLanguage( static_cast<const SvxLanguageItem*>(pItem)->GetLanguage() ); + + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_UNDERLINE, + true, &pItem )) + { + SetUnderline( static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle() ); + SetUnderColor( static_cast<const SvxUnderlineItem*>(pItem)->GetColor() ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_OVERLINE, + true, &pItem )) + { + SetOverline( static_cast<const SvxOverlineItem*>(pItem)->GetLineStyle() ); + SetOverColor( static_cast<const SvxOverlineItem*>(pItem)->GetColor() ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CROSSEDOUT, + true, &pItem )) + SetStrikeout( static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_COLOR, + true, &pItem )) + SetColor( static_cast<const SvxColorItem*>(pItem)->GetValue() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_EMPHASIS_MARK, + true, &pItem )) + SetEmphasisMark( static_cast<const SvxEmphasisMarkItem*>(pItem)->GetEmphasisMark() ); + + SetTransparent( true ); + SetAlign( ALIGN_BASELINE ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CONTOUR, + true, &pItem )) + SetOutline( static_cast<const SvxContourItem*>(pItem)->GetValue() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_SHADOWED, + true, &pItem )) + SetShadow( static_cast<const SvxShadowedItem*>(pItem)->GetValue() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_RELIEF, + true, &pItem )) + SetRelief( static_cast<const SvxCharReliefItem*>(pItem)->GetValue() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_SHADOWED, + true, &pItem )) + SetPropWidth(static_cast<const SvxShadowedItem*>(pItem)->GetValue() ? 50 : 100 ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_AUTOKERN, + true, &pItem )) + { + if( static_cast<const SvxAutoKernItem*>(pItem)->GetValue() ) + { + SetAutoKern( ( !pIDocumentSettingAccess || + !pIDocumentSettingAccess->get(DocumentSettingId::KERN_ASIAN_PUNCTUATION) ) ? + FontKerning::FontSpecific : + FontKerning::Asian ); + } + else + SetAutoKern( FontKerning::NONE ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_WORDLINEMODE, + true, &pItem )) + SetWordLineMode( static_cast<const SvxWordLineModeItem*>(pItem)->GetValue() ); + + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_ESCAPEMENT, + true, &pItem )) + { + const SvxEscapementItem *pEsc = static_cast<const SvxEscapementItem *>(pItem); + SetEscapement( pEsc->GetEsc() ); + if( m_aSub[SwFontScript::Latin].IsEsc() ) + SetProportion( pEsc->GetProportionalHeight() ); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_CASEMAP, + true, &pItem )) + SetCaseMap( static_cast<const SvxCaseMapItem*>(pItem)->GetCaseMap() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_KERNING, + true, &pItem )) + SetFixKerning( static_cast<const SvxKerningItem*>(pItem)->GetValue() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_ROTATE, + true, &pItem )) + SetVertical( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_BACKGROUND, + true, &pItem )) + m_pBackColor.reset( new Color( static_cast<const SvxBrushItem*>(pItem)->GetColor() ) ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_HIGHLIGHT, + true, &pItem )) + SetHighlightColor(static_cast<const SvxBrushItem*>(pItem)->GetColor()); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_BOX, + true, &pItem )) + { + const SvxBoxItem* pBoxItem = static_cast<const SvxBoxItem*>(pItem); + SetTopBorder(pBoxItem->GetTop()); + SetBottomBorder(pBoxItem->GetBottom()); + SetRightBorder(pBoxItem->GetRight()); + SetLeftBorder(pBoxItem->GetLeft()); + SetTopBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP)); + SetBottomBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM)); + SetRightBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::RIGHT)); + SetLeftBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::LEFT)); + } + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_SHADOW, + true, &pItem )) + { + const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem); + SetShadowColor(pShadowItem->GetColor()); + SetShadowWidth(pShadowItem->GetWidth()); + SetShadowLocation(pShadowItem->GetLocation()); + } + const SfxPoolItem* pTwoLinesItem = nullptr; + if( SfxItemState::SET == + pAttrSet->GetItemState( RES_CHRATR_TWO_LINES, true, &pTwoLinesItem )) + if ( static_cast<const SvxTwoLinesItem*>(pTwoLinesItem)->GetValue() ) + SetVertical( 0 ); + } + else + { + Invalidate(); + } + m_bPaintBlank = false; + OSL_ENSURE( m_aSub[SwFontScript::Latin].IsTransparent(), "SwFont: Transparent revolution" ); +} + +SwFont::SwFont( const SwFont &rFont ) + : m_aSub(rFont.m_aSub) +{ + m_nActual = rFont.m_nActual; + m_pBackColor.reset( rFont.m_pBackColor ? new Color( *rFont.m_pBackColor ) : nullptr ); + m_aHighlightColor = rFont.m_aHighlightColor; + m_aTopBorder = rFont.m_aTopBorder; + m_aBottomBorder = rFont.m_aBottomBorder; + m_aRightBorder = rFont.m_aRightBorder; + m_aLeftBorder = rFont.m_aLeftBorder; + m_nTopBorderDist = rFont.m_nTopBorderDist; + m_nBottomBorderDist = rFont.m_nBottomBorderDist; + m_nRightBorderDist = rFont.m_nRightBorderDist; + m_nLeftBorderDist = rFont.m_nLeftBorderDist; + m_aShadowColor = rFont.m_aShadowColor; + m_nShadowWidth = rFont.m_nShadowWidth; + m_aShadowLocation = rFont.m_aShadowLocation; + m_aUnderColor = rFont.GetUnderColor(); + m_aOverColor = rFont.GetOverColor(); + m_nToxCount = 0; + m_nRefCount = 0; + m_nMetaCount = 0; + m_nInputFieldCount = 0; + m_bFontChg = rFont.m_bFontChg; + m_bOrgChg = rFont.m_bOrgChg; + m_bPaintBlank = rFont.m_bPaintBlank; + m_bGreyWave = rFont.m_bGreyWave; +} + +SwFont::SwFont( const SwAttrSet* pAttrSet, + const IDocumentSettingAccess* pIDocumentSettingAccess ) + : m_aSub() +{ + m_nActual = SwFontScript::Latin; + m_nToxCount = 0; + m_nRefCount = 0; + m_nMetaCount = 0; + m_nInputFieldCount = 0; + m_bPaintBlank = false; + m_bGreyWave = false; + m_bOrgChg = true; + { + const SvxFontItem& rFont = pAttrSet->GetFont(); + m_aSub[SwFontScript::Latin].SetFamily( rFont.GetFamily() ); + m_aSub[SwFontScript::Latin].SetFamilyName( rFont.GetFamilyName() ); + m_aSub[SwFontScript::Latin].SetStyleName( rFont.GetStyleName() ); + m_aSub[SwFontScript::Latin].SetPitch( rFont.GetPitch() ); + m_aSub[SwFontScript::Latin].SetCharSet( rFont.GetCharSet() ); + m_aSub[SwFontScript::Latin].SvxFont::SetPropr( 100 ); // 100% of FontSize + Size aTmpSize = m_aSub[SwFontScript::Latin].m_aSize; + aTmpSize.setHeight( pAttrSet->GetSize().GetHeight() ); + m_aSub[SwFontScript::Latin].SetSize( aTmpSize ); + m_aSub[SwFontScript::Latin].SetItalic( pAttrSet->GetPosture().GetPosture() ); + m_aSub[SwFontScript::Latin].SetWeight( pAttrSet->GetWeight().GetWeight() ); + m_aSub[SwFontScript::Latin].SetLanguage( pAttrSet->GetLanguage().GetLanguage() ); + } + + { + const SvxFontItem& rFont = pAttrSet->GetCJKFont(); + m_aSub[SwFontScript::CJK].SetFamily( rFont.GetFamily() ); + m_aSub[SwFontScript::CJK].SetFamilyName( rFont.GetFamilyName() ); + m_aSub[SwFontScript::CJK].SetStyleName( rFont.GetStyleName() ); + m_aSub[SwFontScript::CJK].SetPitch( rFont.GetPitch() ); + m_aSub[SwFontScript::CJK].SetCharSet( rFont.GetCharSet() ); + m_aSub[SwFontScript::CJK].SvxFont::SetPropr( 100 ); // 100% of FontSize + Size aTmpSize = m_aSub[SwFontScript::CJK].m_aSize; + aTmpSize.setHeight( pAttrSet->GetCJKSize().GetHeight() ); + m_aSub[SwFontScript::CJK].SetSize( aTmpSize ); + m_aSub[SwFontScript::CJK].SetItalic( pAttrSet->GetCJKPosture().GetPosture() ); + m_aSub[SwFontScript::CJK].SetWeight( pAttrSet->GetCJKWeight().GetWeight() ); + LanguageType eNewLang = pAttrSet->GetCJKLanguage().GetLanguage(); + m_aSub[SwFontScript::CJK].SetLanguage( eNewLang ); + m_aSub[SwFontScript::Latin].SetCJKContextLanguage( eNewLang ); + m_aSub[SwFontScript::CJK].SetCJKContextLanguage( eNewLang ); + m_aSub[SwFontScript::CTL].SetCJKContextLanguage( eNewLang ); + } + + { + const SvxFontItem& rFont = pAttrSet->GetCTLFont(); + m_aSub[SwFontScript::CTL].SetFamily( rFont.GetFamily() ); + m_aSub[SwFontScript::CTL].SetFamilyName( rFont.GetFamilyName() ); + m_aSub[SwFontScript::CTL].SetStyleName( rFont.GetStyleName() ); + m_aSub[SwFontScript::CTL].SetPitch( rFont.GetPitch() ); + m_aSub[SwFontScript::CTL].SetCharSet( rFont.GetCharSet() ); + m_aSub[SwFontScript::CTL].SvxFont::SetPropr( 100 ); // 100% of FontSize + Size aTmpSize = m_aSub[SwFontScript::CTL].m_aSize; + aTmpSize.setHeight( pAttrSet->GetCTLSize().GetHeight() ); + m_aSub[SwFontScript::CTL].SetSize( aTmpSize ); + m_aSub[SwFontScript::CTL].SetItalic( pAttrSet->GetCTLPosture().GetPosture() ); + m_aSub[SwFontScript::CTL].SetWeight( pAttrSet->GetCTLWeight().GetWeight() ); + m_aSub[SwFontScript::CTL].SetLanguage( pAttrSet->GetCTLLanguage().GetLanguage() ); + } + if ( pAttrSet->GetCharHidden().GetValue() ) + SetUnderline( LINESTYLE_DOTTED ); + else + SetUnderline( pAttrSet->GetUnderline().GetLineStyle() ); + SetUnderColor( pAttrSet->GetUnderline().GetColor() ); + SetOverline( pAttrSet->GetOverline().GetLineStyle() ); + SetOverColor( pAttrSet->GetOverline().GetColor() ); + SetEmphasisMark( pAttrSet->GetEmphasisMark().GetEmphasisMark() ); + SetStrikeout( pAttrSet->GetCrossedOut().GetStrikeout() ); + SetColor( pAttrSet->GetColor().GetValue() ); + SetTransparent( true ); + SetAlign( ALIGN_BASELINE ); + SetOutline( pAttrSet->GetContour().GetValue() ); + SetShadow( pAttrSet->GetShadowed().GetValue() ); + SetPropWidth( pAttrSet->GetCharScaleW().GetValue() ); + SetRelief( pAttrSet->GetCharRelief().GetValue() ); + if( pAttrSet->GetAutoKern().GetValue() ) + { + SetAutoKern( ( !pIDocumentSettingAccess || + !pIDocumentSettingAccess->get(DocumentSettingId::KERN_ASIAN_PUNCTUATION) ) ? + FontKerning::FontSpecific : + FontKerning::Asian ); + } + else + SetAutoKern( FontKerning::NONE ); + SetWordLineMode( pAttrSet->GetWordLineMode().GetValue() ); + const SvxEscapementItem &rEsc = pAttrSet->GetEscapement(); + SetEscapement( rEsc.GetEsc() ); + if( m_aSub[SwFontScript::Latin].IsEsc() ) + SetProportion( rEsc.GetProportionalHeight() ); + SetCaseMap( pAttrSet->GetCaseMap().GetCaseMap() ); + SetFixKerning( pAttrSet->GetKerning().GetValue() ); + const SfxPoolItem* pItem; + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_BACKGROUND, + true, &pItem )) + m_pBackColor.reset( new Color( static_cast<const SvxBrushItem*>(pItem)->GetColor() ) ); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_HIGHLIGHT, + true, &pItem )) + SetHighlightColor(static_cast<const SvxBrushItem*>(pItem)->GetColor()); + else + SetHighlightColor(COL_TRANSPARENT); + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_BOX, + true, &pItem )) + { + const SvxBoxItem* pBoxItem = static_cast<const SvxBoxItem*>(pItem); + SetTopBorder(pBoxItem->GetTop()); + SetBottomBorder(pBoxItem->GetBottom()); + SetRightBorder(pBoxItem->GetRight()); + SetLeftBorder(pBoxItem->GetLeft()); + SetTopBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP)); + SetBottomBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM)); + SetRightBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::RIGHT)); + SetLeftBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::LEFT)); + } + else + { + SetTopBorder(nullptr); + SetBottomBorder(nullptr); + SetRightBorder(nullptr); + SetLeftBorder(nullptr); + SetTopBorderDist(0); + SetBottomBorderDist(0); + SetRightBorderDist(0); + SetLeftBorderDist(0); + } + + if( SfxItemState::SET == pAttrSet->GetItemState( RES_CHRATR_SHADOW, + true, &pItem )) + { + const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem); + SetShadowColor(pShadowItem->GetColor()); + SetShadowWidth(pShadowItem->GetWidth()); + SetShadowLocation(pShadowItem->GetLocation()); + } + else + { + SetShadowColor(COL_TRANSPARENT); + SetShadowWidth(0); + SetShadowLocation(SvxShadowLocation::NONE); + } + + const SvxTwoLinesItem& rTwoLinesItem = pAttrSet->Get2Lines(); + if ( ! rTwoLinesItem.GetValue() ) + SetVertical( pAttrSet->GetCharRotate().GetValue() ); + else + SetVertical( 0 ); + if( pIDocumentSettingAccess && pIDocumentSettingAccess->get( DocumentSettingId::SMALL_CAPS_PERCENTAGE_66 )) + { + m_aSub[ SwFontScript::Latin ].m_bSmallCapsPercentage66 = true; + m_aSub[ SwFontScript::CJK ].m_bSmallCapsPercentage66 = true; + m_aSub[ SwFontScript::CTL ].m_bSmallCapsPercentage66 = true; + } +} + +SwFont::~SwFont() +{ +} + +SwFont& SwFont::operator=( const SwFont &rFont ) +{ + if (this != &rFont) + { + m_aSub[SwFontScript::Latin] = rFont.m_aSub[SwFontScript::Latin]; + m_aSub[SwFontScript::CJK] = rFont.m_aSub[SwFontScript::CJK]; + m_aSub[SwFontScript::CTL] = rFont.m_aSub[SwFontScript::CTL]; + m_nActual = rFont.m_nActual; + m_pBackColor.reset( rFont.m_pBackColor ? new Color( *rFont.m_pBackColor ) : nullptr ); + m_aHighlightColor = rFont.m_aHighlightColor; + m_aTopBorder = rFont.m_aTopBorder; + m_aBottomBorder = rFont.m_aBottomBorder; + m_aRightBorder = rFont.m_aRightBorder; + m_aLeftBorder = rFont.m_aLeftBorder; + m_nTopBorderDist = rFont.m_nTopBorderDist; + m_nBottomBorderDist = rFont.m_nBottomBorderDist; + m_nRightBorderDist = rFont.m_nRightBorderDist; + m_nLeftBorderDist = rFont.m_nLeftBorderDist; + m_aShadowColor = rFont.m_aShadowColor; + m_nShadowWidth = rFont.m_nShadowWidth; + m_aShadowLocation = rFont.m_aShadowLocation; + m_aUnderColor = rFont.GetUnderColor(); + m_aOverColor = rFont.GetOverColor(); + m_nToxCount = 0; + m_nRefCount = 0; + m_nMetaCount = 0; + m_nInputFieldCount = 0; + m_bFontChg = rFont.m_bFontChg; + m_bOrgChg = rFont.m_bOrgChg; + m_bPaintBlank = rFont.m_bPaintBlank; + m_bGreyWave = rFont.m_bGreyWave; + } + return *this; +} + +void SwFont::AllocFontCacheId( SwViewShell const *pSh, SwFontScript nWhich ) +{ + SwFntAccess aFntAccess( m_aSub[nWhich].m_nFontCacheId, m_aSub[nWhich].m_nFontIndex, + &m_aSub[nWhich], pSh, true ); +} + +bool SwSubFont::IsSymbol( SwViewShell const *pSh ) +{ + SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh, false ); + return aFntAccess.Get()->IsSymbol(); +} + +bool SwSubFont::ChgFnt( SwViewShell const *pSh, OutputDevice& rOut ) +{ + if ( pLastFont ) + pLastFont->Unlock(); + SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh, true ); + SV_STAT( nChangeFont ); + + pLastFont = aFntAccess.Get(); + + pLastFont->SetDevFont( pSh, rOut ); + + pLastFont->Lock(); + return LINESTYLE_NONE != GetUnderline() || + LINESTYLE_NONE != GetOverline() || + STRIKEOUT_NONE != GetStrikeout(); +} + +void SwFont::ChgPhysFnt( SwViewShell const *pSh, OutputDevice& rOut ) +{ + if( m_bOrgChg && m_aSub[m_nActual].IsEsc() ) + { + const sal_uInt8 nOldProp = m_aSub[m_nActual].GetPropr(); + SetProportion( 100 ); + ChgFnt( pSh, rOut ); + SwFntAccess aFntAccess( m_aSub[m_nActual].m_nFontCacheId, m_aSub[m_nActual].m_nFontIndex, + &m_aSub[m_nActual], pSh ); + m_aSub[m_nActual].m_nOrgHeight = aFntAccess.Get()->GetFontHeight( pSh, rOut ); + m_aSub[m_nActual].m_nOrgAscent = aFntAccess.Get()->GetFontAscent( pSh, rOut ); + SetProportion( nOldProp ); + m_bOrgChg = false; + } + + if( m_bFontChg ) + { + ChgFnt( pSh, rOut ); + m_bFontChg = m_bOrgChg; + } + if( rOut.GetTextLineColor() != m_aUnderColor ) + rOut.SetTextLineColor( m_aUnderColor ); + if( rOut.GetOverlineColor() != m_aOverColor ) + rOut.SetOverlineColor( m_aOverColor ); +} + +// Height = MaxAscent + MaxDescent +// MaxAscent = Max (T1_ascent, T2_ascent + (Esc * T1_height) ); +// MaxDescent = Max (T1_height-T1_ascent, +// T2_height-T2_ascent - (Esc * T1_height) +sal_uInt16 SwSubFont::CalcEscHeight( const sal_uInt16 nOldHeight, + const sal_uInt16 nOldAscent ) const +{ + if( DFLT_ESC_AUTO_SUPER != GetEscapement() && + DFLT_ESC_AUTO_SUB != GetEscapement() ) + { + long nDescent = nOldHeight - nOldAscent - + ( static_cast<long>(m_nOrgHeight) * GetEscapement() ) / 100; + const sal_uInt16 nDesc = nDescent>0 + ? std::max<sal_uInt16>( nDescent, m_nOrgHeight - m_nOrgAscent) + : m_nOrgHeight - m_nOrgAscent; + return ( nDesc + CalcEscAscent( nOldAscent ) ); + } + return m_nOrgHeight; +} + +short SwSubFont::CheckKerning_( ) +{ + short nKernx = - short( Font::GetFontSize().Height() / 6 ); + + if ( nKernx < GetFixKerning() ) + return GetFixKerning(); + return nKernx; +} + +sal_uInt16 SwSubFont::GetAscent( SwViewShell const *pSh, const OutputDevice& rOut ) +{ + SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh ); + const sal_uInt16 nAscent = aFntAccess.Get()->GetFontAscent( pSh, rOut ); + return GetEscapement() ? CalcEscAscent( nAscent ) : nAscent; +} + +sal_uInt16 SwSubFont::GetHeight( SwViewShell const *pSh, const OutputDevice& rOut ) +{ + SV_STAT( nGetTextSize ); + SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh ); + const sal_uInt16 nHeight = aFntAccess.Get()->GetFontHeight( pSh, rOut ); + if ( GetEscapement() ) + { + const sal_uInt16 nAscent = aFntAccess.Get()->GetFontAscent( pSh, rOut ); + return CalcEscHeight( nHeight, nAscent ); // + nLeading; + } + return nHeight; // + nLeading; +} + +Size SwSubFont::GetTextSize_( SwDrawTextInfo& rInf ) +{ + // Robust: the font is supposed to be set already, but better safe than + // sorry... + if ( !pLastFont || pLastFont->GetOwner() != reinterpret_cast<const void*>(m_nFontCacheId) || + !IsSameInstance( rInf.GetpOut()->GetFont() ) ) + ChgFnt( rInf.GetShell(), rInf.GetOut() ); + + SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() ); + + Size aTextSize; + TextFrameIndex const nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING) + ? TextFrameIndex(rInf.GetText().getLength()) + : rInf.GetLen(); + rInf.SetLen( nLn ); + if( IsCapital() && nLn ) + aTextSize = GetCapitalSize( rInf ); + else + { + SV_STAT( nGetTextSize ); + long nOldKern = rInf.GetKern(); + const OUString oldText = rInf.GetText(); + rInf.SetKern( CheckKerning() ); + if ( !IsCaseMap() ) + aTextSize = pLastFont->GetTextSize( rInf ); + else + { + const OUString aTmp = CalcCaseMap( rInf.GetText() ); + const OUString oldStr = rInf.GetText(); + bool bCaseMapLengthDiffers(aTmp.getLength() != oldStr.getLength()); + + if(bCaseMapLengthDiffers && rInf.GetLen()) + { + // If the length of the original string and the CaseMapped one + // are different, it is necessary to handle the given text part as + // a single snippet since its size may differ, too. + TextFrameIndex const nOldIdx(rInf.GetIdx()); + TextFrameIndex const nOldLen(rInf.GetLen()); + const OUString aSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldLen))); + const OUString aNewText(CalcCaseMap(aSnippet)); + + rInf.SetText( aNewText ); + rInf.SetIdx( TextFrameIndex(0) ); + rInf.SetLen( TextFrameIndex(aNewText.getLength()) ); + + aTextSize = pLastFont->GetTextSize( rInf ); + + rInf.SetIdx( nOldIdx ); + rInf.SetLen( nOldLen ); + } + else + { + rInf.SetText( aTmp ); + aTextSize = pLastFont->GetTextSize( rInf ); + } + + rInf.SetText(oldStr); + } + rInf.SetKern( nOldKern ); + rInf.SetText(oldText); + // A word that's longer than one line, with escapement at the line + // break, must report its effective height. + if( GetEscapement() ) + { + const sal_uInt16 nAscent = pLastFont->GetFontAscent( rInf.GetShell(), + rInf.GetOut() ); + aTextSize.setHeight( + static_cast<long>(CalcEscHeight( static_cast<sal_uInt16>(aTextSize.Height()), nAscent)) ); + } + } + + if (TextFrameIndex(1) == rInf.GetLen() + && CH_TXT_ATR_FIELDSTART == rInf.GetText()[sal_Int32(rInf.GetIdx())]) + { + assert(!"this is presumably dead code"); + TextFrameIndex const nOldIdx(rInf.GetIdx()); + TextFrameIndex const nOldLen(rInf.GetLen()); + const OUString aNewText(CH_TXT_ATR_SUBST_FIELDSTART); + rInf.SetText( aNewText ); + rInf.SetIdx( TextFrameIndex(0) ); + rInf.SetLen( TextFrameIndex(aNewText.getLength()) ); + aTextSize = pLastFont->GetTextSize( rInf ); + rInf.SetIdx( nOldIdx ); + rInf.SetLen( nOldLen ); + } + else if (TextFrameIndex(1) == rInf.GetLen() + && CH_TXT_ATR_FIELDEND == rInf.GetText()[sal_Int32(rInf.GetIdx())]) + { + assert(!"this is presumably dead code"); + TextFrameIndex const nOldIdx(rInf.GetIdx()); + TextFrameIndex const nOldLen(rInf.GetLen()); + const OUString aNewText(CH_TXT_ATR_SUBST_FIELDEND); + rInf.SetText( aNewText ); + rInf.SetIdx( TextFrameIndex(0) ); + rInf.SetLen( TextFrameIndex(aNewText.getLength()) ); + aTextSize = pLastFont->GetTextSize( rInf ); + rInf.SetIdx( nOldIdx ); + rInf.SetLen( nOldLen ); + } + + return aTextSize; +} + +void SwSubFont::DrawText_( SwDrawTextInfo &rInf, const bool bGrey ) +{ + rInf.SetGreyWave( bGrey ); + TextFrameIndex const nLn(rInf.GetText().getLength()); + if( !rInf.GetLen() || !nLn ) + return; + if (TextFrameIndex(COMPLETE_STRING) == rInf.GetLen()) + rInf.SetLen( nLn ); + + FontLineStyle nOldUnder = LINESTYLE_NONE; + SwUnderlineFont* pUnderFnt = nullptr; + + if( rInf.GetUnderFnt() ) + { + nOldUnder = GetUnderline(); + SetUnderline( LINESTYLE_NONE ); + pUnderFnt = rInf.GetUnderFnt(); + } + + if( !pLastFont || pLastFont->GetOwner() != reinterpret_cast<const void*>(m_nFontCacheId) ) + ChgFnt( rInf.GetShell(), rInf.GetOut() ); + + SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() ); + + const Point aOldPos(rInf.GetPos()); + Point aPos( rInf.GetPos() ); + + if( GetEscapement() ) + CalcEsc( rInf, aPos ); + + rInf.SetPos( aPos ); + rInf.SetKern( CheckKerning() + rInf.GetSperren() / SPACING_PRECISION_FACTOR ); + + if( IsCapital() ) + DrawCapital( rInf ); + else + { + SV_STAT( nDrawText ); + if ( !IsCaseMap() ) + pLastFont->DrawText( rInf ); + else + { + const OUString oldStr = rInf.GetText(); + const OUString aString( CalcCaseMap(oldStr) ); + bool bCaseMapLengthDiffers(aString.getLength() != oldStr.getLength()); + + if(bCaseMapLengthDiffers && rInf.GetLen()) + { + // If the length of the original string and the CaseMapped one + // are different, it is necessary to handle the given text part as + // a single snippet since its size may differ, too. + TextFrameIndex const nOldIdx(rInf.GetIdx()); + TextFrameIndex const nOldLen(rInf.GetLen()); + const OUString aSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldLen))); + const OUString aNewText = CalcCaseMap(aSnippet); + + rInf.SetText( aNewText ); + rInf.SetIdx( TextFrameIndex(0) ); + rInf.SetLen( TextFrameIndex(aNewText.getLength()) ); + + pLastFont->DrawText( rInf ); + + rInf.SetIdx( nOldIdx ); + rInf.SetLen( nOldLen ); + } + else + { + rInf.SetText( aString ); + pLastFont->DrawText( rInf ); + } + + rInf.SetText(oldStr); + } + } + + if( pUnderFnt && nOldUnder != LINESTYLE_NONE ) + { + Size aFontSize = GetTextSize_( rInf ); + const OUString oldStr = rInf.GetText(); + + TextFrameIndex const nOldIdx = rInf.GetIdx(); + TextFrameIndex const nOldLen = rInf.GetLen(); + long nSpace = 0; + if( rInf.GetSpace() ) + { + TextFrameIndex nTmpEnd = nOldIdx + nOldLen; + if (nTmpEnd > TextFrameIndex(oldStr.getLength())) + nTmpEnd = TextFrameIndex(oldStr.getLength()); + + const SwScriptInfo* pSI = rInf.GetScriptInfo(); + + const bool bAsianFont = + ( rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() ); + for (TextFrameIndex nTmp = nOldIdx; nTmp < nTmpEnd; ++nTmp) + { + if (CH_BLANK == oldStr[sal_Int32(nTmp)] || bAsianFont || + (nTmp + TextFrameIndex(1) < TextFrameIndex(oldStr.getLength()) + && pSI + && i18n::ScriptType::ASIAN == pSI->ScriptType(nTmp + TextFrameIndex(1)))) + { + ++nSpace; + } + } + + // if next portion if a hole portion we do not consider any + // extra space added because the last character was ASIAN + if ( nSpace && rInf.IsSpaceStop() && bAsianFont ) + --nSpace; + + nSpace *= rInf.GetSpace() / SPACING_PRECISION_FACTOR; + } + + rInf.SetWidth( sal_uInt16(aFontSize.Width() + nSpace) ); + rInf.SetText( " " ); + rInf.SetIdx( TextFrameIndex(0) ); + rInf.SetLen( TextFrameIndex(2) ); + SetUnderline( nOldUnder ); + rInf.SetUnderFnt( nullptr ); + + // set position for underline font + rInf.SetPos( pUnderFnt->GetPos() ); + + pUnderFnt->GetFont().DrawStretchText_( rInf ); + + rInf.SetUnderFnt( pUnderFnt ); + rInf.SetText(oldStr); + rInf.SetIdx( nOldIdx ); + rInf.SetLen( nOldLen ); + } + + rInf.SetPos(aOldPos); +} + +void SwSubFont::DrawStretchText_( SwDrawTextInfo &rInf ) +{ + if( !rInf.GetLen() || !rInf.GetText().getLength() ) + return; + + FontLineStyle nOldUnder = LINESTYLE_NONE; + SwUnderlineFont* pUnderFnt = nullptr; + + if( rInf.GetUnderFnt() ) + { + nOldUnder = GetUnderline(); + SetUnderline( LINESTYLE_NONE ); + pUnderFnt = rInf.GetUnderFnt(); + } + + if ( !pLastFont || pLastFont->GetOwner() != reinterpret_cast<const void*>(m_nFontCacheId) ) + ChgFnt( rInf.GetShell(), rInf.GetOut() ); + + SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() ); + + rInf.ApplyAutoColor(); + + const Point aOldPos(rInf.GetPos()); + Point aPos( rInf.GetPos() ); + + if( GetEscapement() ) + CalcEsc( rInf, aPos ); + + rInf.SetKern( CheckKerning() + rInf.GetSperren() / SPACING_PRECISION_FACTOR ); + rInf.SetPos( aPos ); + + if( IsCapital() ) + DrawStretchCapital( rInf ); + else + { + SV_STAT( nDrawStretchText ); + + if ( rInf.GetFrame() ) + { + if ( rInf.GetFrame()->IsRightToLeft() ) + rInf.GetFrame()->SwitchLTRtoRTL( aPos ); + + if ( rInf.GetFrame()->IsVertical() ) + rInf.GetFrame()->SwitchHorizontalToVertical( aPos ); + + rInf.SetPos( aPos ); + } + + if ( !IsCaseMap() ) + rInf.GetOut().DrawStretchText( aPos, rInf.GetWidth(), + rInf.GetText(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + else + rInf.GetOut().DrawStretchText( aPos, rInf.GetWidth(), + CalcCaseMap(rInf.GetText()), + sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen())); + } + + if( pUnderFnt && nOldUnder != LINESTYLE_NONE ) + { + const OUString oldStr = rInf.GetText(); + TextFrameIndex const nOldIdx = rInf.GetIdx(); + TextFrameIndex const nOldLen = rInf.GetLen(); + rInf.SetText( " " ); + rInf.SetIdx( TextFrameIndex(0) ); + rInf.SetLen( TextFrameIndex(2) ); + SetUnderline( nOldUnder ); + rInf.SetUnderFnt( nullptr ); + + // set position for underline font + rInf.SetPos( pUnderFnt->GetPos() ); + + pUnderFnt->GetFont().DrawStretchText_( rInf ); + + rInf.SetUnderFnt( pUnderFnt ); + rInf.SetText(oldStr); + rInf.SetIdx( nOldIdx ); + rInf.SetLen( nOldLen ); + } + + rInf.SetPos(aOldPos); +} + +TextFrameIndex SwSubFont::GetModelPositionForViewPoint_( SwDrawTextInfo& rInf ) +{ + if ( !pLastFont || pLastFont->GetOwner() != reinterpret_cast<const void*>(m_nFontCacheId) ) + ChgFnt( rInf.GetShell(), rInf.GetOut() ); + + SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() ); + + TextFrameIndex const nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING) + ? TextFrameIndex(rInf.GetText().getLength()) + : rInf.GetLen(); + rInf.SetLen( nLn ); + TextFrameIndex nCursor(0); + if( IsCapital() && nLn ) + nCursor = GetCapitalCursorOfst( rInf ); + else + { + const OUString oldText = rInf.GetText(); + long nOldKern = rInf.GetKern(); + rInf.SetKern( CheckKerning() ); + SV_STAT( nGetTextSize ); + if ( !IsCaseMap() ) + nCursor = pLastFont->GetModelPositionForViewPoint( rInf ); + else + { + rInf.SetText( CalcCaseMap( rInf.GetText() ) ); + nCursor = pLastFont->GetModelPositionForViewPoint( rInf ); + } + rInf.SetKern( nOldKern ); + rInf.SetText(oldText); + } + return nCursor; +} + +void SwSubFont::CalcEsc( SwDrawTextInfo const & rInf, Point& rPos ) +{ + long nOfst; + + bool bVert = false; + bool bVertLRBT = false; + if (rInf.GetFrame()) + { + bVert = rInf.GetFrame()->IsVertical(); + bVertLRBT = rInf.GetFrame()->IsVertLRBT(); + } + const sal_uInt16 nDir = UnMapDirection(GetOrientation(), bVert, bVertLRBT); + + switch ( GetEscapement() ) + { + case DFLT_ESC_AUTO_SUB : + nOfst = m_nOrgHeight - m_nOrgAscent - + pLastFont->GetFontHeight( rInf.GetShell(), rInf.GetOut() ) + + pLastFont->GetFontAscent( rInf.GetShell(), rInf.GetOut() ); + + switch ( nDir ) + { + case 0 : + rPos.AdjustY(nOfst ); + break; + case 900 : + rPos.AdjustX(nOfst ); + break; + case 2700 : + rPos.AdjustX( -nOfst ); + break; + } + + break; + case DFLT_ESC_AUTO_SUPER : + nOfst = pLastFont->GetFontAscent( rInf.GetShell(), rInf.GetOut() ) - + m_nOrgAscent; + + switch ( nDir ) + { + case 0 : + rPos.AdjustY(nOfst ); + break; + case 900 : + rPos.AdjustX(nOfst ); + break; + case 2700 : + rPos.AdjustX( -nOfst ); + break; + } + + break; + default : + nOfst = (static_cast<long>(m_nOrgHeight) * GetEscapement()) / 100; + + switch ( nDir ) + { + case 0 : + rPos.AdjustY( -nOfst ); + break; + case 900 : + rPos.AdjustX( -nOfst ); + break; + case 2700 : + rPos.AdjustX(nOfst ); + break; + } + } +} + +// used during painting of small capitals +void SwDrawTextInfo::Shift( sal_uInt16 nDir ) +{ +#ifdef DBG_UTIL + OSL_ENSURE( m_bPos, "DrawTextInfo: Undefined Position" ); + OSL_ENSURE( m_bSize, "DrawTextInfo: Undefined Width" ); +#endif + + const bool bBidiPor = ( GetFrame() && GetFrame()->IsRightToLeft() ) != + ( ComplexTextLayoutFlags::Default != ( ComplexTextLayoutFlags::BiDiRtl & GetpOut()->GetLayoutMode() ) ); + + bool bVert = false; + bool bVertLRBT = false; + if (GetFrame()) + { + bVert = GetFrame()->IsVertical(); + bVertLRBT = GetFrame()->IsVertLRBT(); + } + nDir = bBidiPor ? 1800 : UnMapDirection(nDir, bVert, bVertLRBT); + + switch ( nDir ) + { + case 0 : + m_aPos.AdjustX(GetSize().Width() ); + break; + case 900 : + OSL_ENSURE( m_aPos.Y() >= GetSize().Width(), "Going underground" ); + m_aPos.AdjustY( -(GetSize().Width()) ); + break; + case 1800 : + m_aPos.AdjustX( -(GetSize().Width()) ); + break; + case 2700 : + m_aPos.AdjustY(GetSize().Width() ); + break; + } +} + +/** + * @note Used for the "continuous underline" feature. + **/ +SwUnderlineFont::SwUnderlineFont(SwFont& rFnt, TextFrameIndex const nEnd, const Point& rPoint) + : m_aPos( rPoint ), m_nEnd( nEnd ), m_pFont( &rFnt ) +{ +}; + +SwUnderlineFont::~SwUnderlineFont() +{ +} + +/// Helper for filters to find true lineheight of a font +long AttrSetToLineHeight( const IDocumentSettingAccess& rIDocumentSettingAccess, + const SwAttrSet &rSet, + const vcl::RenderContext &rOut, sal_Int16 nScript) +{ + SwFont aFont(&rSet, &rIDocumentSettingAccess); + SwFontScript nActual; + switch (nScript) + { + default: + case i18n::ScriptType::LATIN: + nActual = SwFontScript::Latin; + break; + case i18n::ScriptType::ASIAN: + nActual = SwFontScript::CJK; + break; + case i18n::ScriptType::COMPLEX: + nActual = SwFontScript::CTL; + break; + } + aFont.SetActual(nActual); + + vcl::RenderContext &rMutableOut = const_cast<vcl::RenderContext &>(rOut); + const vcl::Font aOldFont(rMutableOut.GetFont()); + + rMutableOut.SetFont(aFont.GetActualFont()); + long nHeight = rMutableOut.GetTextHeight(); + + rMutableOut.SetFont(aOldFont); + return nHeight; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/thints.cxx b/sw/source/core/txtnode/thints.cxx new file mode 100644 index 000000000..f31e39713 --- /dev/null +++ b/sw/source/core/txtnode/thints.cxx @@ -0,0 +1,3512 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <DocumentContentOperationsManager.hxx> +#include <hintids.hxx> +#include <editeng/xmlcnitm.hxx> +#include <editeng/rsiditem.hxx> +#include <svl/whiter.hxx> +#include <svl/itemiter.hxx> +#include <editeng/charhiddenitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/lrspitem.hxx> +#include <txtinet.hxx> +#include <txtflcnt.hxx> +#include <fmtfld.hxx> +#include <fmtrfmrk.hxx> +#include <fmtanchr.hxx> +#include <fmtinfmt.hxx> +#include <txtatr.hxx> +#include <fchrfmt.hxx> +#include <fmtautofmt.hxx> +#include <fmtflcnt.hxx> +#include <fmtftn.hxx> +#include <txttxmrk.hxx> +#include <txtrfmrk.hxx> +#include <txtftn.hxx> +#include <txtfld.hxx> +#include <txtannotationfld.hxx> +#include <charfmt.hxx> +#include <frmfmt.hxx> +#include <ftnidx.hxx> +#include <fmtruby.hxx> +#include <fmtmeta.hxx> +#include <breakit.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <fldbas.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <rolbck.hxx> +#include <ddefld.hxx> +#include <docufld.hxx> +#include <expfld.hxx> +#include <usrfld.hxx> +#include <poolfmt.hxx> +#include <istyleaccess.hxx> +#include <docsh.hxx> +#include <algorithm> +#include <map> +#include <memory> +#include <unordered_map> + +#include <rdfhelper.hxx> +#include <hints.hxx> + +#ifdef DBG_UTIL +#define CHECK Check(true); +#define CHECK_NOTMERGED Check(false); +#else +#define CHECK_NOTMERGED +#endif + +using namespace ::com::sun::star::i18n; + +SwpHints::SwpHints(const SwTextNode& rParent) + : m_rParent(rParent) + , m_pHistory(nullptr) + , m_bInSplitNode(false) + , m_bCalcHiddenParaField(false) + , m_bHiddenByParaField(false) + , m_bFootnote(false) + , m_bDDEFields(false) + , m_bStartMapNeedsSorting(false) + , m_bEndMapNeedsSorting(false) + , m_bWhichMapNeedsSorting(false) +{ +} + +static void TextAttrDelete( SwDoc & rDoc, SwTextAttr * const pAttr ) +{ + if (RES_TXTATR_META == pAttr->Which() || + RES_TXTATR_METAFIELD == pAttr->Which()) + { + static_txtattr_cast<SwTextMeta *>(pAttr)->ChgTextNode(nullptr); // prevents ASSERT + } + SwTextAttr::Destroy( pAttr, rDoc.GetAttrPool() ); +} + +static bool TextAttrContains(const sal_Int32 nPos, const SwTextAttrEnd * const pAttr) +{ + return (pAttr->GetStart() < nPos) && (nPos < *pAttr->End()); +} + +// a: |-----| +// b: +// |---| => valid: b before a +// |-----| => valid: start == end; b before a +// |---------| => invalid: overlap (1) +// |-----------| => valid: same end; b around a +// |-----------------| => valid: b around a +// |---| => valid; same start; b within a +// |-----| => valid; same start and end; b around or within a? +// |-----------| => valid: same start: b around a +// |-| => valid: b within a +// |---| => valid: same end; b within a +// |---------| => invalid: overlap (2) +// |-----| => valid: end == start; b after a +// |---| => valid: b after a +// ===> 2 invalid overlap cases +static +bool isOverlap(const sal_Int32 nStart1, const sal_Int32 nEnd1, + const sal_Int32 nStart2, const sal_Int32 nEnd2) +{ + return + ((nStart1 > nStart2) && (nStart1 < nEnd2) && (nEnd1 > nEnd2)) // (1) + || ((nStart1 < nStart2) && (nStart2 < nEnd1) && (nEnd1 < nEnd2)); // (2) +} + +/// #i106930#: now asymmetric: empty hint1 is _not_ nested, but empty hint2 is +static +bool isNestedAny(const sal_Int32 nStart1, const sal_Int32 nEnd1, + const sal_Int32 nStart2, const sal_Int32 nEnd2) +{ + return ((nStart1 == nStart2) || (nEnd1 == nEnd2)) + // same start/end: nested except if hint1 empty and hint2 not empty + ? (nStart1 != nEnd1) || (nStart2 == nEnd2) + : ((nStart1 < nStart2) ? (nEnd1 >= nEnd2) : (nEnd1 <= nEnd2)); +} + +static +bool isSelfNestable(const sal_uInt16 nWhich) +{ + if ((RES_TXTATR_INETFMT == nWhich) || + (RES_TXTATR_CJK_RUBY == nWhich) || + (RES_TXTATR_INPUTFIELD == nWhich)) + return false; + assert((RES_TXTATR_META == nWhich) || + (RES_TXTATR_METAFIELD == nWhich)); + return true; +} + +static +bool isSplittable(const sal_uInt16 nWhich) +{ + if ((RES_TXTATR_INETFMT == nWhich) || + (RES_TXTATR_CJK_RUBY == nWhich)) + return true; + assert((RES_TXTATR_META == nWhich) || + (RES_TXTATR_METAFIELD == nWhich) || + (RES_TXTATR_INPUTFIELD == nWhich)); + return false; +} + +namespace { + +enum Split_t { FAIL, SPLIT_NEW, SPLIT_OTHER }; + +} + +/** + Calculate splitting policy for overlapping hints, based on what kind of + hint is inserted, and what kind of existing hint overlaps. + */ +static Split_t +splitPolicy(const sal_uInt16 nWhichNew, const sal_uInt16 nWhichOther) +{ + if (!isSplittable(nWhichOther)) + { + if (!isSplittable(nWhichNew)) + return FAIL; + else + return SPLIT_NEW; + } + else + { + if ( RES_TXTATR_INPUTFIELD == nWhichNew ) + return FAIL; + else if ( (RES_TXTATR_INETFMT == nWhichNew) && + (RES_TXTATR_CJK_RUBY == nWhichOther) ) + return SPLIT_NEW; + else + return SPLIT_OTHER; + } +} + +void SwTextINetFormat::InitINetFormat(SwTextNode & rNode) +{ + ChgTextNode(&rNode); + SwCharFormat * const pFormat( + rNode.GetDoc()->getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_INET_NORMAL) ); + pFormat->Add( this ); +} + +void SwTextRuby::InitRuby(SwTextNode & rNode) +{ + ChgTextNode(&rNode); + SwCharFormat * const pFormat( + rNode.GetDoc()->getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_RUBYTEXT) ); + pFormat->Add( this ); +} + +/** + Create a new nesting text hint. + */ +static SwTextAttrNesting * +MakeTextAttrNesting(SwTextNode & rNode, SwTextAttrNesting & rNesting, + const sal_Int32 nStart, const sal_Int32 nEnd) +{ + SwTextAttr * const pNew( MakeTextAttr( + *rNode.GetDoc(), rNesting.GetAttr(), nStart, nEnd ) ); + switch (pNew->Which()) + { + case RES_TXTATR_INETFMT: + { + static_txtattr_cast<SwTextINetFormat*>(pNew)->InitINetFormat(rNode); + break; + } + case RES_TXTATR_CJK_RUBY: + { + static_txtattr_cast<SwTextRuby*>(pNew)->InitRuby(rNode); + break; + } + default: + assert(!"MakeTextAttrNesting: what the hell is that?"); + break; + } + return static_txtattr_cast<SwTextAttrNesting*>(pNew); +} + +typedef std::vector<SwTextAttrNesting *> NestList_t; + +static NestList_t::iterator +lcl_DoSplitImpl(NestList_t & rSplits, SwTextNode & rNode, + NestList_t::iterator const iter, sal_Int32 const nSplitPos, + bool const bSplitAtStart, bool const bOtherDummy) +{ + const sal_Int32 nStartPos( // skip other's dummy character! + (bSplitAtStart && bOtherDummy) ? nSplitPos + 1 : nSplitPos ); + SwTextAttrNesting * const pNew( MakeTextAttrNesting( + rNode, **iter, nStartPos, *(*iter)->GetEnd() ) ); + (*iter)->SetEnd(nSplitPos); + return rSplits.insert(iter + 1, pNew); +} + +static void +lcl_DoSplitNew(NestList_t & rSplits, SwTextNode & rNode, + const sal_Int32 nNewStart, + const sal_Int32 nOtherStart, const sal_Int32 nOtherEnd, bool bOtherDummy) +{ + const bool bSplitAtStart(nNewStart < nOtherStart); + const sal_Int32 nSplitPos( bSplitAtStart ? nOtherStart : nOtherEnd ); + // first find the portion that is split (not necessarily the last one!) + NestList_t::iterator const iter( + std::find_if( rSplits.begin(), rSplits.end(), + [nSplitPos](SwTextAttrEnd * const pAttr) { + return TextAttrContains(nSplitPos, pAttr); + } ) ); + if (iter != rSplits.end()) // already split here? + { + lcl_DoSplitImpl(rSplits, rNode, iter, nSplitPos, bSplitAtStart, bOtherDummy); + } +} + +/** + Insert nesting hint into the hints array. Also calls NoteInHistory. + @param rNewHint the hint to be inserted (must not overlap existing!) + */ +void SwpHints::InsertNesting(SwTextAttrNesting & rNewHint) +{ + Insert(& rNewHint); + NoteInHistory( & rNewHint, true ); +} + +/** + +The following hints correspond to well-formed XML elements in ODF: +RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD + +The writer core must ensure that these do not overlap; if they did, +the document would not be storable as ODF. + +Also, a Hyperlink must not be nested within another Hyperlink, +and a Ruby must not be nested within another Ruby. + +The ODF export in xmloff will only put a hyperlink into a ruby, never a ruby +into a hyperlink. + +Unfortunately the UNO API for Hyperlink and Ruby consists of the properties +Hyperlink* and Ruby* of the css.text.CharacterProperties service. In other +words, they are treated as formatting attributes, not as content entities. +Furthermore, for API users it is not possible to easily test whether a certain +range would be overlapping with other nested attributes, and most importantly, +<em>which ones</em>, so we can hardly refuse to insert these in cases of +overlap. + +It is possible to split Hyperlink and Ruby into multiple portions, such that +the result is properly nested. + +meta and meta-field must not be split, because they have xml:id. + +These constraints result in the following design: + +RES_TXTATR_INETFMT: + always succeeds + inserts n attributes split at RES_TXTATR_CJK_RUBY, RES_TXTATR_META, + RES_TXTATR_METAFIELD + may replace existing RES_TXTATR_INETFMT at overlap +RES_TXTATR_CJK_RUBY: + always succeeds + inserts n attributes split at RES_TXTATR_META, RES_TXTATR_METAFIELD + may replace existing RES_TXTATR_CJK_RUBY at overlap + may split existing overlapping RES_TXTATR_INETFMT +RES_TXTATR_META: + may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD + may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY + inserts 1 attribute +RES_TXTATR_METAFIELD: + may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD + may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY + inserts 1 attribute + +The nesting is expressed by the position of the hints. +RES_TXTATR_META and RES_TXTATR_METAFIELD have a CH_TXTATR, and there can +only be one such hint starting and ending at a given position. +Only RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY lack a CH_TXTATR. +The interpretation given is that RES_TXTATR_CJK_RUBY is always around +a RES_TXTATR_INETFMT at the same start and end position (which corresponds +with the UNO API). +Both of these are always around a nesting hint with CH_TXTATR at the same +start and end position (if they should be inside, then the start should be +after the CH_TXTATR). +It would probably be a bad idea to add another nesting hint without +CH_TXTATR; on the other hand, it would be difficult adding a CH_TXTATR to +RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY, due to the overwriting and +splitting of existing hints that is necessary for backward compatibility. + + @param rNode the text node + @param rHint the hint to be inserted + @returns true iff hint was successfully inserted +*/ +bool +SwpHints::TryInsertNesting( SwTextNode & rNode, SwTextAttrNesting & rNewHint ) +{ +// INVARIANT: the nestable hints in the array are properly nested + const sal_uInt16 nNewWhich( rNewHint.Which() ); + const sal_Int32 nNewStart( rNewHint.GetStart() ); + const sal_Int32 nNewEnd ( *rNewHint.GetEnd() ); + const bool bNewSelfNestable( isSelfNestable(nNewWhich) ); + + assert( (RES_TXTATR_INETFMT == nNewWhich) || + (RES_TXTATR_CJK_RUBY == nNewWhich) || + (RES_TXTATR_META == nNewWhich) || + (RES_TXTATR_METAFIELD == nNewWhich) || + (RES_TXTATR_INPUTFIELD == nNewWhich)); + + NestList_t OverlappingExisting; // existing hints to be split + NestList_t OverwrittenExisting; // existing hints to be replaced + NestList_t SplitNew; // new hints to be inserted + + SplitNew.push_back(& rNewHint); + + // pass 1: split the inserted hint into fragments if necessary + for ( size_t i = 0; i < Count(); ++i ) + { + SwTextAttr * const pOther = GetSortedByEnd(i); + + if (pOther->IsNesting()) + { + const sal_uInt16 nOtherWhich( pOther->Which() ); + const sal_Int32 nOtherStart( pOther->GetStart() ); + const sal_Int32 nOtherEnd ( *pOther->GetEnd() ); + if (isOverlap(nNewStart, nNewEnd, nOtherStart, nOtherEnd )) + { + switch (splitPolicy(nNewWhich, nOtherWhich)) + { + case FAIL: + SAL_INFO("sw.core", "cannot insert hint: overlap"); + for (const auto& aSplit : SplitNew) + TextAttrDelete(*rNode.GetDoc(), aSplit); + return false; + case SPLIT_NEW: + lcl_DoSplitNew(SplitNew, rNode, nNewStart, + nOtherStart, nOtherEnd, pOther->HasDummyChar()); + break; + case SPLIT_OTHER: + OverlappingExisting.push_back( + static_txtattr_cast<SwTextAttrNesting*>(pOther)); + break; + default: + assert(!"bad code monkey"); + break; + } + } + else if (isNestedAny(nNewStart, nNewEnd, nOtherStart, nOtherEnd)) + { + if (!bNewSelfNestable && (nNewWhich == nOtherWhich)) + { + // ruby and hyperlink: if there is nesting, _overwrite_ + OverwrittenExisting.push_back( + static_txtattr_cast<SwTextAttrNesting*>(pOther)); + } + else if ((nNewStart == nOtherStart) && pOther->HasDummyChar()) + { + if (rNewHint.HasDummyChar()) + { + assert(!"ERROR: inserting duplicate CH_TXTATR hint"); + return false; + } else if (nNewEnd < nOtherEnd) { + // other has dummy char, new is inside other, but + // new contains the other's dummy char? + // should be corrected because it may lead to problems + // in SwXMeta::createEnumeration + // SplitNew is sorted, so this is the first split + assert(SplitNew.front()->GetStart() == nNewStart); + SplitNew.front()->SetStart(nNewStart + 1); + } + } + } + } + } + + // pass 1b: tragically need to check for fieldmarks here too + for (auto iter = SplitNew.begin(); iter != SplitNew.end(); ++iter) + { + SwPaM const temp(rNode, (*iter)->GetStart(), rNode, *(*iter)->GetEnd()); + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + sw::CalcBreaks(Breaks, temp, true); + if (!Breaks.empty()) + { + if (!isSplittable(nNewWhich)) + { + SAL_INFO("sw.core", "cannot insert hint: fieldmark overlap"); + assert(SplitNew.size() == 1); + TextAttrDelete(*rNode.GetDoc(), &rNewHint); + return false; + } + else + { + for (auto const& rPos : Breaks) + { + assert(rPos.first == rNode.GetIndex()); + iter = lcl_DoSplitImpl(SplitNew, rNode, iter, + rPos.second, true, true); + } + } + } + } + + assert((isSplittable(nNewWhich) || SplitNew.size() == 1) && + "splitting the unsplittable ???"); + + // pass 2: split existing hints that overlap/nest with new hint + // do not iterate over hints array, but over remembered set of overlapping + // hints, to keep things simple w.r.t. insertion/removal + // N.B: if there is a hint that splits the inserted hint, then + // that hint would also have already split any hint in OverlappingExisting + // so any hint in OverlappingExisting can be split at most by one hint + // in SplitNew, or even not at all (this is not true for existing hints + // that go _around_ new hint, which is the reason d'^etre for pass 4) + for (auto& rpOther : OverlappingExisting) + { + const sal_Int32 nOtherStart( rpOther->GetStart() ); + const sal_Int32 nOtherEnd ( *rpOther->GetEnd() ); + + for (const auto& rpNew : SplitNew) + { + const sal_Int32 nSplitNewStart( rpNew->GetStart() ); + const sal_Int32 nSplitNewEnd ( *rpNew->GetEnd() ); + // 4 cases: within, around, overlap l, overlap r, (OTHER: no action) + const bool bRemoveOverlap( + !bNewSelfNestable && (nNewWhich == rpOther->Which()) ); + + switch (ComparePosition(nSplitNewStart, nSplitNewEnd, + nOtherStart, nOtherEnd)) + { + case SwComparePosition::Inside: + { + assert(!bRemoveOverlap && + "this one should be in OverwrittenExisting?"); + } + break; + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + assert(!"existing hint inside new hint: why?"); + } + break; + case SwComparePosition::OverlapBefore: + { + Delete( rpOther ); // this also does NoteInHistory! + rpOther->SetStart(nSplitNewEnd); + InsertNesting( *rpOther ); + if (!bRemoveOverlap) + { + if ( MAX_HINTS <= Count() ) + { + SAL_INFO("sw.core", "hints array full :-("); + return false; + } + SwTextAttrNesting * const pOtherLeft( + MakeTextAttrNesting( rNode, *rpOther, + nOtherStart, nSplitNewEnd ) ); + InsertNesting( *pOtherLeft ); + } + } + break; + case SwComparePosition::OverlapBehind: + { + Delete( rpOther ); // this also does NoteInHistory! + rpOther->SetEnd(nSplitNewStart); + InsertNesting( *rpOther ); + if (!bRemoveOverlap) + { + if ( MAX_HINTS <= Count() ) + { + SAL_INFO("sw.core", "hints array full :-("); + return false; + } + SwTextAttrNesting * const pOtherRight( + MakeTextAttrNesting( rNode, *rpOther, + nSplitNewStart, nOtherEnd ) ); + InsertNesting( *pOtherRight ); + } + } + break; + default: + break; // overlap resolved by splitting new: nothing to do + } + } + } + + if ( MAX_HINTS <= Count() || MAX_HINTS - Count() <= SplitNew.size() ) + { + SAL_INFO("sw.core", "hints array full :-("); + return false; + } + + // pass 3: insert new hints + for (const auto& rpHint : SplitNew) + { + InsertNesting(*rpHint); + } + + // pass 4: handle overwritten hints + // RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY should displace attributes + // of the same kind. + for (auto& rpOther : OverwrittenExisting) + { + const sal_Int32 nOtherStart( rpOther->GetStart() ); + const sal_Int32 nOtherEnd ( *rpOther->GetEnd() ); + + // overwritten portion is given by start/end of inserted hint + if ((nNewStart <= nOtherStart) && (nOtherEnd <= nNewEnd)) + { + Delete(rpOther); + rNode.DestroyAttr( rpOther ); + } + else + { + assert((nOtherStart < nNewStart) || (nNewEnd < nOtherEnd)); + // scenario: there is a RUBY, and contained within that a META; + // now a RUBY is inserted within the META => the existing RUBY is split: + // here it is not possible to simply insert the left/right fragment + // of the existing RUBY because they <em>overlap</em> with the META! + Delete( rpOther ); // this also does NoteInHistory! + if (nNewEnd < nOtherEnd) + { + SwTextAttrNesting * const pOtherRight( + MakeTextAttrNesting( + rNode, *rpOther, nNewEnd, nOtherEnd ) ); + bool const bSuccess( TryInsertNesting(rNode, *pOtherRight) ); + SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 1 failed?"); + } + if (nOtherStart < nNewStart) + { + rpOther->SetEnd(nNewStart); + bool const bSuccess( TryInsertNesting(rNode, *rpOther) ); + SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 2 failed?"); + } + else + { + rNode.DestroyAttr(rpOther); + } + } + } + + return true; +} + +// This function takes care for the following text attribute: +// RES_TXTATR_CHARFMT, RES_TXTATR_AUTOFMT +// These attributes have to be handled in a special way (Portion building). + +// The new attribute will be split by any existing RES_TXTATR_AUTOFMT or +// RES_TXTATR_CHARFMT. The new attribute itself will +// split any existing RES_TXTATR_AUTOFMT or RES_TXTATR_CHARFMT. + +void SwpHints::BuildPortions( SwTextNode& rNode, SwTextAttr& rNewHint, + const SetAttrMode nMode ) +{ + const sal_uInt16 nWhich = rNewHint.Which(); + + const sal_Int32 nThisStart = rNewHint.GetStart(); + const sal_Int32 nThisEnd = *rNewHint.GetEnd(); + const bool bNoLengthAttribute = nThisStart == nThisEnd; + + std::vector<SwTextAttr*> aInsDelHints; + + assert( RES_TXTATR_CHARFMT == rNewHint.Which() || + RES_TXTATR_AUTOFMT == rNewHint.Which() ); + + // 2. Find the hints which cover the start and end position + // of the new hint. These hints have to be split into two portions: + + if ( !bNoLengthAttribute ) // nothing to do for no length attributes + { + for ( size_t i = 0; i < Count(); ++i ) + { + // we're modifying stuff here which affects the sorting, and we + // don't want it changing underneath us + SwTextAttr* pOther = GetWithoutResorting(i); + + if ( RES_TXTATR_CHARFMT != pOther->Which() && + RES_TXTATR_AUTOFMT != pOther->Which() ) + continue; + + sal_Int32 nOtherStart = pOther->GetStart(); + const sal_Int32 nOtherEnd = *pOther->GetEnd(); + + // Check if start of new attribute overlaps with pOther: + // Split pOther if necessary: + if ( nOtherStart < nThisStart && nThisStart < nOtherEnd ) + { + SwTextAttr* pNewAttr = MakeTextAttr( *rNode.GetDoc(), + pOther->GetAttr(), nOtherStart, nThisStart ); + if ( RES_TXTATR_CHARFMT == pOther->Which() ) + { + static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber( + static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber() ); + } + aInsDelHints.push_back( pNewAttr ); + + NoteInHistory( pOther ); + pOther->SetStart(nThisStart); + NoteInHistory( pOther, true ); + + nOtherStart = nThisStart; + } + + // Check if end of new attribute overlaps with pOther: + // Split pOther if necessary: + if ( nOtherStart < nThisEnd && nThisEnd < nOtherEnd ) + { + SwTextAttr* pNewAttr = MakeTextAttr( *rNode.GetDoc(), + pOther->GetAttr(), nOtherStart, nThisEnd ); + if ( RES_TXTATR_CHARFMT == pOther->Which() ) + { + static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber( + static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber()); + } + aInsDelHints.push_back( pNewAttr ); + + NoteInHistory( pOther ); + pOther->SetStart(nThisEnd); + NoteInHistory( pOther, true ); + } + } + + // Insert the newly created attributes: + for ( const auto& rpHint : aInsDelHints ) + { + Insert( rpHint ); + NoteInHistory( rpHint, true ); + } + } + +#ifdef DBG_UTIL + if( !rNode.GetDoc()->IsInReading() ) + CHECK_NOTMERGED; // ignore flags not set properly yet, don't check them +#endif + + // 4. Split rNewHint into 1 ... n new hints: + + std::set<sal_Int32> aBounds; + aBounds.insert( nThisStart ); + aBounds.insert( nThisEnd ); + + if ( !bNoLengthAttribute ) // nothing to do for no length attributes + { + for ( size_t i = 0; i < Count(); ++i ) + { + const SwTextAttr* pOther = Get(i); + + if ( RES_TXTATR_CHARFMT != pOther->Which() && + RES_TXTATR_AUTOFMT != pOther->Which() ) + continue; + + const sal_Int32 nOtherStart = pOther->GetStart(); + const sal_Int32 nOtherEnd = *pOther->End(); + + aBounds.insert( nOtherStart ); + aBounds.insert( nOtherEnd ); + } + } + + std::set<sal_Int32>::iterator aStartIter = aBounds.lower_bound( nThisStart ); + std::set<sal_Int32>::iterator aEndIter = aBounds.upper_bound( nThisEnd ); + sal_Int32 nPorStart = *aStartIter; + ++aStartIter; + bool bDestroyHint = true; + + // Insert the 1...n new parts of the new attribute: + + while ( aStartIter != aEndIter || bNoLengthAttribute ) + { + OSL_ENSURE( bNoLengthAttribute || nPorStart < *aStartIter, "AUTOSTYLES: BuildPortion trouble" ); + + const sal_Int32 nPorEnd = bNoLengthAttribute ? nPorStart : *aStartIter; + aInsDelHints.clear(); + + // Get all hints that are in [nPorStart, nPorEnd[: + for ( size_t i = 0; i < Count(); ++i ) + { + // we get called from TryInsertHint, which changes ordering + SwTextAttr *pOther = GetWithoutResorting(i); + + if ( RES_TXTATR_CHARFMT != pOther->Which() && + RES_TXTATR_AUTOFMT != pOther->Which() ) + continue; + + const sal_Int32 nOtherStart = pOther->GetStart(); + + if ( nOtherStart > nPorStart ) + break; + + if ( pOther->GetEnd() && *pOther->GetEnd() == nPorEnd && nOtherStart == nPorStart ) + { + OSL_ENSURE( *pOther->GetEnd() == nPorEnd, "AUTOSTYLES: BuildPortion trouble" ); + aInsDelHints.push_back( pOther ); + } + } + + SwTextAttr* pNewAttr = nullptr; + if ( RES_TXTATR_CHARFMT == nWhich ) + { + // pNewHint can be inserted after calculating the sort value. + // This should ensure, that pNewHint comes behind the already present + // character style + sal_uInt16 nCharStyleCount = 0; + for ( const auto& rpHint : aInsDelHints ) + { + if ( RES_TXTATR_CHARFMT == rpHint->Which() ) + { + // #i74589# + const SwFormatCharFormat& rOtherCharFormat = rpHint->GetCharFormat(); + const SwFormatCharFormat& rThisCharFormat = rNewHint.GetCharFormat(); + const bool bSameCharFormat = rOtherCharFormat.GetCharFormat() == rThisCharFormat.GetCharFormat(); + + // #i90311# + // Do not remove existing character format hint during XML import + if ( !rNode.GetDoc()->IsInXMLImport() && + ( !( SetAttrMode::DONTREPLACE & nMode ) || + bNoLengthAttribute || + bSameCharFormat ) ) + { + // Remove old hint + Delete( rpHint ); + rNode.DestroyAttr( rpHint ); + } + else + ++nCharStyleCount; + } + else + { + // remove all attributes from auto styles, which are explicitly set in + // the new character format: + OSL_ENSURE( RES_TXTATR_AUTOFMT == rpHint->Which(), "AUTOSTYLES - Misc trouble" ); + SwTextAttr* pOther = rpHint; + std::shared_ptr<SfxItemSet> pOldStyle = static_cast<const SwFormatAutoFormat&>(pOther->GetAttr()).GetStyleHandle(); + + // For each attribute in the automatic style check if it + // is also set the new character style: + SfxItemSet aNewSet( *pOldStyle->GetPool(), + aCharAutoFormatSetRange); + SfxItemIter aItemIter( *pOldStyle ); + const SfxPoolItem* pItem = aItemIter.GetCurItem(); + do + { + if ( !CharFormat::IsItemIncluded( pItem->Which(), &rNewHint ) ) + { + aNewSet.Put( *pItem ); + } + + pItem = aItemIter.NextItem(); + } while (pItem); + + // Remove old hint + Delete( pOther ); + rNode.DestroyAttr( pOther ); + + // Create new AutoStyle + if ( aNewSet.Count() ) + { + pNewAttr = MakeTextAttr( *rNode.GetDoc(), + aNewSet, nPorStart, nPorEnd ); + Insert( pNewAttr ); + NoteInHistory( pNewAttr, true ); + } + } + } + + // If there is no current hint and start and end of rNewHint + // is ok, we do not need to create a new txtattr. + if ( nPorStart == nThisStart && + nPorEnd == nThisEnd && + !nCharStyleCount ) + { + pNewAttr = &rNewHint; + bDestroyHint = false; + } + else + { + pNewAttr = MakeTextAttr( *rNode.GetDoc(), rNewHint.GetAttr(), + nPorStart, nPorEnd ); + static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(nCharStyleCount); + } + } + else + { + // Find the current autostyle. Mix attributes if necessary. + SwTextAttr* pCurrentAutoStyle = nullptr; + SwTextAttr* pCurrentCharFormat = nullptr; + for ( const auto& rpHint : aInsDelHints ) + { + if ( RES_TXTATR_AUTOFMT == rpHint->Which() ) + pCurrentAutoStyle = rpHint; + else if ( RES_TXTATR_CHARFMT == rpHint->Which() ) + pCurrentCharFormat = rpHint; + } + + std::shared_ptr<SfxItemSet> pNewStyle = static_cast<const SwFormatAutoFormat&>(rNewHint.GetAttr()).GetStyleHandle(); + if ( pCurrentAutoStyle ) + { + std::shared_ptr<SfxItemSet> pCurrentStyle = static_cast<const SwFormatAutoFormat&>(pCurrentAutoStyle->GetAttr()).GetStyleHandle(); + + // Merge attributes + SfxItemSet aNewSet( *pCurrentStyle ); + aNewSet.Put( *pNewStyle ); + + // #i75750# Remove attributes already set at whole paragraph + // #i81764# This should not be applied for no length attributes!!! <-- + if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && aNewSet.Count() ) + { + SfxItemIter aIter2( aNewSet ); + const SfxPoolItem* pItem = aIter2.GetCurItem(); + const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet(); + + do + { + const SfxPoolItem* pTmpItem = nullptr; + if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) && + pTmpItem == pItem ) + { + // Do not clear item if the attribute is set in a character format: + if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) ) + aNewSet.ClearItem( pItem->Which() ); + } + } + while ((pItem = aIter2.NextItem())); + } + + // Remove old hint + Delete( pCurrentAutoStyle ); + rNode.DestroyAttr( pCurrentAutoStyle ); + + // Create new AutoStyle + if ( aNewSet.Count() ) + pNewAttr = MakeTextAttr( *rNode.GetDoc(), aNewSet, + nPorStart, nPorEnd ); + } + else + { + // Remove any attributes which are already set at the whole paragraph: + bool bOptimizeAllowed = true; + + // #i75750# Remove attributes already set at whole paragraph + // #i81764# This should not be applied for no length attributes!!! <-- + if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && pNewStyle->Count() ) + { + std::unique_ptr<SfxItemSet> pNewSet; + + SfxItemIter aIter2( *pNewStyle ); + const SfxPoolItem* pItem = aIter2.GetCurItem(); + const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet(); + + do + { + const SfxPoolItem* pTmpItem = nullptr; + if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) && + pTmpItem == pItem ) + { + // Do not clear item if the attribute is set in a character format: + if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) ) + { + if ( !pNewSet ) + pNewSet = pNewStyle->Clone(); + pNewSet->ClearItem( pItem->Which() ); + } + } + } + while ((pItem = aIter2.NextItem())); + + if ( pNewSet ) + { + bOptimizeAllowed = false; + if ( pNewSet->Count() ) + pNewStyle = rNode.getIDocumentStyleAccess().getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR ); + else + pNewStyle.reset(); + } + } + + // Create new AutoStyle + // If there is no current hint and start and end of rNewHint + // is ok, we do not need to create a new txtattr. + if ( bOptimizeAllowed && + nPorStart == nThisStart && + nPorEnd == nThisEnd ) + { + pNewAttr = &rNewHint; + bDestroyHint = false; + } + else if ( pNewStyle ) + { + pNewAttr = MakeTextAttr( *rNode.GetDoc(), *pNewStyle, + nPorStart, nPorEnd ); + } + } + } + + if ( pNewAttr ) + { + Insert( pNewAttr ); +// if ( bDestroyHint ) + NoteInHistory( pNewAttr, true ); + } + + if ( !bNoLengthAttribute ) + { + nPorStart = *aStartIter; + ++aStartIter; + } + else + break; + } + + if ( bDestroyHint ) + rNode.DestroyAttr( &rNewHint ); +} + +SwTextAttr* MakeRedlineTextAttr( SwDoc & rDoc, SfxPoolItem const & rAttr ) +{ + // this is intended _only_ for special-purpose redline attributes! + switch (rAttr.Which()) + { + case RES_CHRATR_COLOR: + case RES_CHRATR_WEIGHT: + case RES_CHRATR_CJK_WEIGHT: + case RES_CHRATR_CTL_WEIGHT: + case RES_CHRATR_POSTURE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CTL_POSTURE: + case RES_CHRATR_UNDERLINE: + case RES_CHRATR_CROSSEDOUT: + case RES_CHRATR_CASEMAP: + case RES_CHRATR_BACKGROUND: + break; + default: + assert(!"unsupported redline attribute"); + break; + } + + // Put new attribute into pool + // FIXME: this const_cast is evil! + SfxPoolItem& rNew = + const_cast<SfxPoolItem&>( rDoc.GetAttrPool().Put( rAttr ) ); + return new SwTextAttrEnd( rNew, 0, 0 ); +} + +// create new text attribute +SwTextAttr* MakeTextAttr( + SwDoc & rDoc, + SfxPoolItem& rAttr, + sal_Int32 const nStt, + sal_Int32 const nEnd, + CopyOrNewType const bIsCopy, + SwTextNode *const pTextNode ) +{ + if ( isCHRATR(rAttr.Which()) ) + { + // Somebody wants to build a SwTextAttr for a character attribute. + // Sorry, this is not allowed any longer. + // You'll get a brand new autostyle attribute: + SfxItemSet aItemSet( rDoc.GetAttrPool(), + svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END>{} ); + aItemSet.Put( rAttr ); + return MakeTextAttr( rDoc, aItemSet, nStt, nEnd ); + } + else if ( RES_TXTATR_AUTOFMT == rAttr.Which() && + static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle()-> + GetPool() != &rDoc.GetAttrPool() ) + { + // If the attribute is an auto-style which refers to a pool that is + // different from rDoc's pool, we have to correct this: + const std::shared_ptr<SfxItemSet> pAutoStyle = static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle(); + std::unique_ptr<const SfxItemSet> pNewSet( + pAutoStyle->SfxItemSet::Clone( true, &rDoc.GetAttrPool() )); + SwTextAttr* pNew = MakeTextAttr( rDoc, *pNewSet, nStt, nEnd ); + return pNew; + } + + // Put new attribute into pool + // FIXME: this const_cast is evil! + SfxPoolItem& rNew = + const_cast<SfxPoolItem&>( rDoc.GetAttrPool().Put( rAttr ) ); + + SwTextAttr* pNew = nullptr; + switch( rNew.Which() ) + { + case RES_TXTATR_CHARFMT: + { + SwFormatCharFormat &rFormatCharFormat = static_cast<SwFormatCharFormat&>(rNew); + if( !rFormatCharFormat.GetCharFormat() ) + { + rFormatCharFormat.SetCharFormat( rDoc.GetDfltCharFormat() ); + } + + pNew = new SwTextCharFormat( rFormatCharFormat, nStt, nEnd ); + } + break; + case RES_TXTATR_INETFMT: + pNew = new SwTextINetFormat( static_cast<SwFormatINetFormat&>(rNew), nStt, nEnd ); + break; + + case RES_TXTATR_FIELD: + pNew = new SwTextField( static_cast<SwFormatField &>(rNew), nStt, + rDoc.IsClipBoard() ); + break; + + case RES_TXTATR_ANNOTATION: + { + pNew = new SwTextAnnotationField( static_cast<SwFormatField &>(rNew), nStt, rDoc.IsClipBoard() ); + if (bIsCopy == CopyOrNewType::Copy) + { + // On copy of the annotation field do not keep the annotated text range by removing + // the relation to its annotation mark (relation established via annotation field's name). + // If the annotation mark is also copied, the relation and thus the annotated text range will be reestablished, + // when the annotation mark is created and inserted into the document. + const_cast<SwPostItField&>(dynamic_cast<const SwPostItField&>(*(pNew->GetFormatField().GetField()))).SetName(OUString()); + } + } + break; + + case RES_TXTATR_INPUTFIELD: + pNew = new SwTextInputField( static_cast<SwFormatField &>(rNew), nStt, nEnd, + rDoc.IsClipBoard() ); + break; + + case RES_TXTATR_FLYCNT: + { + // finally, copy the frame format (with content) + pNew = new SwTextFlyCnt( static_cast<SwFormatFlyCnt&>(rNew), nStt ); + if ( static_cast<const SwFormatFlyCnt &>(rAttr).GetTextFlyCnt() ) + { + // if it has an existing attr then the format must be copied + static_cast<SwTextFlyCnt *>(pNew)->CopyFlyFormat( &rDoc ); + } + } + break; + case RES_TXTATR_FTN: + pNew = new SwTextFootnote( static_cast<SwFormatFootnote&>(rNew), nStt ); + // copy note's SeqNo + if( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote() ) + static_cast<SwTextFootnote*>(pNew)->SetSeqNo( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote()->GetSeqRefNo() ); + break; + case RES_TXTATR_REFMARK: + pNew = nStt == nEnd + ? new SwTextRefMark( static_cast<SwFormatRefMark&>(rNew), nStt ) + : new SwTextRefMark( static_cast<SwFormatRefMark&>(rNew), nStt, &nEnd ); + break; + case RES_TXTATR_TOXMARK: + { + SwTOXMark& rMark = static_cast<SwTOXMark&>(rNew); + + // tdf#98868 if the SwTOXType is from a different document that the + // target, re-register the TOXMark against a matching SwTOXType from + // the target document instead + const SwTOXType* pTOXType = rMark.GetTOXType(); + if (pTOXType && &pTOXType->GetDoc() != &rDoc) + { + SwTOXType* pToxType = SwHistorySetTOXMark::GetSwTOXType(rDoc, pTOXType->GetType(), + pTOXType->GetTypeName()); + rMark.RegisterToTOXType(*pToxType); + } + + pNew = new SwTextTOXMark(rMark, nStt, &nEnd); + break; + } + case RES_TXTATR_CJK_RUBY: + pNew = new SwTextRuby( static_cast<SwFormatRuby&>(rNew), nStt, nEnd ); + break; + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + pNew = SwTextMeta::CreateTextMeta( rDoc.GetMetaFieldManager(), pTextNode, + static_cast<SwFormatMeta&>(rNew), nStt, nEnd, bIsCopy == CopyOrNewType::Copy ); + break; + default: + assert(RES_TXTATR_AUTOFMT == rNew.Which()); + pNew = new SwTextAttrEnd( rNew, nStt, nEnd ); + break; + } + + return pNew; +} + +SwTextAttr* MakeTextAttr( SwDoc & rDoc, const SfxItemSet& rSet, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess(); + const std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle( rSet, IStyleAccess::AUTO_STYLE_CHAR ); + SwFormatAutoFormat aNewAutoFormat; + aNewAutoFormat.SetStyleHandle( pAutoStyle ); + SwTextAttr* pNew = MakeTextAttr( rDoc, aNewAutoFormat, nStt, nEnd ); + return pNew; +} + +// delete the text attribute and unregister its item at the pool +void SwTextNode::DestroyAttr( SwTextAttr* pAttr ) +{ + if( pAttr ) + { + // some things need to be done before deleting the formatting attribute + SwDoc* pDoc = GetDoc(); + switch( pAttr->Which() ) + { + case RES_TXTATR_FLYCNT: + { + SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat(); + if( pFormat ) // set to 0 by Undo? + pDoc->getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + } + break; + + case RES_CHRATR_HIDDEN: + SetCalcHiddenCharFlags(); + break; + + case RES_TXTATR_FTN: + static_cast<SwTextFootnote*>(pAttr)->SetStartNode( nullptr ); + static_cast<SwFormatFootnote&>(pAttr->GetAttr()).InvalidateFootnote(); + break; + + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + if( !pDoc->IsInDtor() ) + { + SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pAttr)); + SwFieldType* pFieldType = pAttr->GetFormatField().GetField()->GetTyp(); + + //JP 06-08-95: DDE-fields are an exception + assert(SwFieldIds::Dde == pFieldType->Which() || + this == pTextField->GetpTextNode()); + + // certain fields must update the SwDoc's calculation flags + + // Certain fields (like HiddenParaField) must trigger recalculation of visible flag + if (GetDoc()->FieldCanHideParaWeight(pFieldType->Which())) + SetCalcHiddenParaField(); + + switch( pFieldType->Which() ) + { + case SwFieldIds::HiddenPara: + case SwFieldIds::DbSetNumber: + case SwFieldIds::GetExp: + case SwFieldIds::Database: + case SwFieldIds::SetExp: + case SwFieldIds::HiddenText: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + if( !pDoc->getIDocumentFieldsAccess().IsNewFieldLst() && GetNodes().IsDocNodes() ) + pDoc->getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField); + break; + case SwFieldIds::Dde: + if (GetNodes().IsDocNodes() && pTextField->GetpTextNode()) + static_cast<SwDDEFieldType*>(pFieldType)->DecRefCnt(); + break; + case SwFieldIds::Postit: + { + const_cast<SwFormatField&>(pAttr->GetFormatField()).Broadcast( + SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED)); + break; + } + default: break; + } + } + static_cast<SwFormatField&>(pAttr->GetAttr()).InvalidateField(); + break; + + case RES_TXTATR_TOXMARK: + static_cast<SwTOXMark&>(pAttr->GetAttr()).InvalidateTOXMark(); + break; + + case RES_TXTATR_REFMARK: + static_cast<SwFormatRefMark&>(pAttr->GetAttr()).InvalidateRefMark(); + break; + + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + auto pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr); + SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(pTextMeta->GetAttr()) ); + if (::sw::Meta* pMeta = rFormatMeta.GetMeta()) + { + if (SwDocShell* pDocSh = pDoc->GetDocShell()) + { + static const OUString metaNS("urn:bails"); + const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject(); + uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel(); + SwRDFHelper::clearStatements(xModel, metaNS, xSubject); + } + } + + static_txtattr_cast<SwTextMeta*>(pAttr)->ChgTextNode(nullptr); + } + break; + + default: + break; + } + + SwTextAttr::Destroy( pAttr, pDoc->GetAttrPool() ); + } +} + +SwTextAttr* SwTextNode::InsertItem( + SfxPoolItem& rAttr, + const sal_Int32 nStart, + const sal_Int32 nEnd, + const SetAttrMode nMode ) +{ + // character attributes will be inserted as automatic styles: + assert( !isCHRATR(rAttr.Which()) && "AUTOSTYLES - " + "SwTextNode::InsertItem should not be called with character attributes"); + + SwTextAttr *const pNew = + MakeTextAttr( + *GetDoc(), + rAttr, + nStart, + nEnd, + (nMode & SetAttrMode::IS_COPY) ? CopyOrNewType::Copy : CopyOrNewType::New, + this ); + + if ( pNew ) + { + const bool bSuccess( InsertHint( pNew, nMode ) ); + // N.B.: also check that the hint is actually in the hints array, + // because hints of certain types may be merged after successful + // insertion, and thus destroyed! + if (!bSuccess || !m_pSwpHints->Contains( pNew )) + { + return nullptr; + } + } + + return pNew; +} + +// take ownership of pAttr; if insertion fails, delete pAttr +bool SwTextNode::InsertHint( SwTextAttr * const pAttr, const SetAttrMode nMode ) +{ + bool bHiddenPara = false; + + assert(pAttr && pAttr->GetStart() <= Len()); + assert(!pAttr->GetEnd() || (*pAttr->GetEnd() <= Len())); + + // translate from SetAttrMode to InsertMode (for hints with CH_TXTATR) + const SwInsertFlags nInsertFlags = + (nMode & SetAttrMode::NOHINTEXPAND) + ? SwInsertFlags::NOHINTEXPAND + : (nMode & SetAttrMode::FORCEHINTEXPAND) + ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND) + : SwInsertFlags::EMPTYEXPAND; + + // need this after TryInsertHint, when pAttr may be deleted + const sal_Int32 nStart( pAttr->GetStart() ); + const bool bDummyChar( pAttr->HasDummyChar() ); + if (bDummyChar) + { + SetAttrMode nInsMode = nMode; + switch( pAttr->Which() ) + { + case RES_TXTATR_FLYCNT: + { + SwTextFlyCnt *pFly = static_cast<SwTextFlyCnt *>(pAttr); + SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat(); + if( !(SetAttrMode::NOTXTATRCHR & nInsMode) ) + { + // Need to insert char first, because SetAnchor() reads + // GetStart(). + //JP 11.05.98: if the anchor is already set correctly, + // fix it after inserting the char, so that clients don't + // have to worry about it. + const SwFormatAnchor* pAnchor = nullptr; + (void)pFormat->GetItemState( RES_ANCHOR, false, + reinterpret_cast<const SfxPoolItem**>(&pAnchor) ); + + SwIndex aIdx( this, pAttr->GetStart() ); + const OUString c(GetCharOfTextAttr(*pAttr)); + OUString const ins( InsertText(c, aIdx, nInsertFlags) ); + if (ins.isEmpty()) + { + // do not record deletion of Format! + ::sw::UndoGuard const ug( + pFormat->GetDoc()->GetIDocumentUndoRedo()); + DestroyAttr(pAttr); + return false; // text node full :( + } + nInsMode |= SetAttrMode::NOTXTATRCHR; + + if (pAnchor && + (RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) && + pAnchor->GetContentAnchor() && + pAnchor->GetContentAnchor()->nNode == *this && + pAnchor->GetContentAnchor()->nContent == aIdx ) + { + --const_cast<SwIndex&>( + pAnchor->GetContentAnchor()->nContent); + } + } + pFly->SetAnchor( this ); + + // format pointer could have changed in SetAnchor, + // when copying to other docs! + pFormat = pAttr->GetFlyCnt().GetFrameFormat(); + SwDoc *pDoc = pFormat->GetDoc(); + + // OD 26.06.2003 - allow drawing objects in header/footer. + // But don't allow control objects in header/footer + if( RES_DRAWFRMFMT == pFormat->Which() && + pDoc->IsInHeaderFooter( pFormat->GetAnchor().GetContentAnchor()->nNode ) ) + { + bool bCheckControlLayer = false; + pFormat->CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer)); + if( bCheckControlLayer ) + { + // This should not be allowed, prevent it here. + // The dtor of the SwTextAttr does not delete the + // char, so delete it explicitly here. + if( SetAttrMode::NOTXTATRCHR & nInsMode ) + { + // delete the char from the string + assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()] + || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]); + m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, ""); + // Update SwIndexes + SwIndex aTmpIdx( this, pAttr->GetStart() ); + Update( aTmpIdx, 1, true ); + } + // do not record deletion of Format! + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + DestroyAttr( pAttr ); + return false; + } + } + break; + } + + case RES_TXTATR_FTN : + { + // Footnotes: create text node and put it into Inserts-section + SwDoc *pDoc = GetDoc(); + SwNodes &rNodes = pDoc->GetNodes(); + + // check that footnote is inserted into body or redline section + if( StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex() ) + { + // This should not be allowed, prevent it here. + // The dtor of the SwTextAttr does not delete the + // char, so delete it explicitly here. + if( SetAttrMode::NOTXTATRCHR & nInsMode ) + { + // delete the char from the string + assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()] + || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]); + m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, ""); + // Update SwIndexes + SwIndex aTmpIdx( this, pAttr->GetStart() ); + Update( aTmpIdx, 1, true ); + } + DestroyAttr( pAttr ); + return false; + } + + // is a new footnote being inserted? + bool bNewFootnote = nullptr == static_cast<SwTextFootnote*>(pAttr)->GetStartNode(); + if( bNewFootnote ) + { + static_cast<SwTextFootnote*>(pAttr)->MakeNewTextSection( GetNodes() ); + SwRegHistory* pHist = GetpSwpHints() + ? GetpSwpHints()->GetHistory() : nullptr; + if( pHist ) + pHist->ChangeNodeIndex( GetIndex() ); + } + else if ( !GetpSwpHints() || !GetpSwpHints()->IsInSplitNode() ) + { + // existing footnote: delete all layout frames of its + // footnote section + sal_uLong nSttIdx = + static_cast<SwTextFootnote*>(pAttr)->GetStartNode()->GetIndex(); + sal_uLong nEndIdx = rNodes[ nSttIdx++ ]->EndOfSectionIndex(); + for( ; nSttIdx < nEndIdx; ++nSttIdx ) + { + SwContentNode* pCNd = rNodes[ nSttIdx ]->GetContentNode(); + if( nullptr != pCNd ) + pCNd->DelFrames(nullptr); + else if (SwTableNode *const pTable = rNodes[nSttIdx]->GetTableNode()) + { + pTable->DelFrames(); + } + } + } + + if( !(SetAttrMode::NOTXTATRCHR & nInsMode) ) + { + // must insert first, to prevent identical indexes + // that could later prevent insertion into SwDoc's + // footnote array + SwIndex aNdIdx( this, pAttr->GetStart() ); + const OUString c(GetCharOfTextAttr(*pAttr)); + OUString const ins( InsertText(c, aNdIdx, nInsertFlags) ); + if (ins.isEmpty()) + { + DestroyAttr(pAttr); + return false; // text node full :( + } + nInsMode |= SetAttrMode::NOTXTATRCHR; + } + + // insert into SwDoc's footnote index array + SwTextFootnote* pTextFootnote = nullptr; + if( !bNewFootnote ) + { + // moving an existing footnote (e.g. SplitNode) + for( size_t n = 0; n < pDoc->GetFootnoteIdxs().size(); ++n ) + if( pAttr == pDoc->GetFootnoteIdxs()[n] ) + { + // assign new index by removing and re-inserting + pTextFootnote = pDoc->GetFootnoteIdxs()[n]; + pDoc->GetFootnoteIdxs().erase( pDoc->GetFootnoteIdxs().begin() + n ); + break; + } + // if the Undo set the StartNode, the Index isn't + // in the doc's array yet! + } + if( !pTextFootnote ) + pTextFootnote = static_cast<SwTextFootnote*>(pAttr); + + // to update the numbers and for sorting, the Node must be set + static_cast<SwTextFootnote*>(pAttr)->ChgTextNode( this ); + + // do not insert footnote in redline section into footnote array + if( StartOfSectionIndex() > rNodes.GetEndOfRedlines().GetIndex() ) + { + const bool bSuccess = pDoc->GetFootnoteIdxs().insert(pTextFootnote).second; + OSL_ENSURE( bSuccess, "FootnoteIdx not inserted." ); + } + SwNodeIndex aTmpIndex( *this ); + pDoc->GetFootnoteIdxs().UpdateFootnote( aTmpIndex); + static_cast<SwTextFootnote*>(pAttr)->SetSeqRefNo(); + } + break; + + case RES_TXTATR_FIELD: + { + // trigger notification for relevant fields, like HiddenParaFields + if (GetDoc()->FieldCanHideParaWeight( + pAttr->GetFormatField().GetField()->GetTyp()->Which())) + { + bHiddenPara = true; + } + } + break; + + } + // CH_TXTATR_* are inserted for SwTextHints without EndIndex + // If the caller is SwTextNode::Copy, the char has already been copied, + // and SETATTR_NOTXTATRCHR prevents inserting it again here. + if( !(SetAttrMode::NOTXTATRCHR & nInsMode) ) + { + SwIndex aIdx( this, pAttr->GetStart() ); + OUString const ins( InsertText(OUString(GetCharOfTextAttr(*pAttr)), + aIdx, nInsertFlags) ); + if (ins.isEmpty()) + { + DestroyAttr(pAttr); + return false; // text node full :( + } + + // adjust end of hint to account for inserted CH_TXTATR + const sal_Int32 * const pEnd(pAttr->GetEnd()); + if (pEnd) + { + pAttr->SetEnd(*pEnd + 1); + } + } + } + + // handle attributes which provide content + sal_Int32 nEnd = nStart; + bool bInputFieldStartCharInserted = false; + bool bInputFieldEndCharInserted = false; + const bool bHasContent( pAttr->HasContent() ); + if ( bHasContent ) + { + switch( pAttr->Which() ) + { + case RES_TXTATR_INPUTFIELD: + { + SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pAttr); + if ( pTextInputField ) + { + if( !(SetAttrMode::NOTXTATRCHR & nMode) ) + { + SwIndex aIdx( this, pAttr->GetStart() ); + const OUString aContent = OUStringChar(CH_TXT_ATR_INPUTFIELDSTART) + + pTextInputField->GetFieldContent() + OUStringChar(CH_TXT_ATR_INPUTFIELDEND); + InsertText( aContent, aIdx, nInsertFlags ); + + const sal_Int32* const pEnd(pAttr->GetEnd()); + assert(pEnd != nullptr); + pAttr->SetEnd(*pEnd + aContent.getLength()); + nEnd = *pAttr->GetEnd(); + } + else + { + // assure that CH_TXT_ATR_INPUTFIELDSTART and CH_TXT_ATR_INPUTFIELDEND are inserted. + if ( m_Text[ pAttr->GetStart() ] != CH_TXT_ATR_INPUTFIELDSTART ) + { + SwIndex aIdx( this, pAttr->GetStart() ); + InsertText( OUString(CH_TXT_ATR_INPUTFIELDSTART), aIdx, nInsertFlags ); + bInputFieldStartCharInserted = true; + const sal_Int32* const pEnd(pAttr->GetEnd()); + assert(pEnd != nullptr); + pAttr->SetEnd(*pEnd + 1); + nEnd = *pAttr->GetEnd(); + } + + const sal_Int32* const pEnd(pAttr->GetEnd()); + assert(pEnd != nullptr); + if (m_Text[ *pEnd - 1 ] != CH_TXT_ATR_INPUTFIELDEND) + { + SwIndex aIdx( this, *pEnd ); + InsertText( OUString(CH_TXT_ATR_INPUTFIELDEND), aIdx, nInsertFlags ); + bInputFieldEndCharInserted = true; + pAttr->SetEnd(*pEnd + 1); + nEnd = *pAttr->GetEnd(); + } + } + } + } + break; + default: + break; + } + } + + GetOrCreateSwpHints(); + + // handle overlap with an existing InputField + bool bInsertHint = true; + { + const SwTextInputField* pTextInputField = GetOverlappingInputField( *pAttr ); + if ( pTextInputField != nullptr ) + { + if ( pAttr->End() == nullptr ) + { + bInsertHint = false; + } + else + { + if ( pAttr->GetStart() > pTextInputField->GetStart() ) + { + pAttr->SetStart( pTextInputField->GetStart() ); + } + if ( *(pAttr->End()) < *(pTextInputField->End()) ) + { + pAttr->SetEnd(*(pTextInputField->End())); + } + } + } + } + + const bool bRet = bInsertHint + && m_pSwpHints->TryInsertHint( pAttr, *this, nMode ); + + if ( !bRet ) + { + if ( bDummyChar + && !(SetAttrMode::NOTXTATRCHR & nMode) ) + { + // undo insertion of dummy character + // N.B. cannot insert the dummy character after inserting the hint, + // because if the hint has no extent it will be moved in InsertText, + // resulting in infinite recursion + assert((CH_TXTATR_BREAKWORD == m_Text[nStart] || + CH_TXTATR_INWORD == m_Text[nStart] )); + SwIndex aIdx( this, nStart ); + EraseText( aIdx, 1 ); + } + + if ( bHasContent ) + { + if ( !(SetAttrMode::NOTXTATRCHR & nMode) + && (nEnd - nStart) > 0 ) + { + SwIndex aIdx( this, nStart ); + EraseText( aIdx, (nEnd - nStart) ); + } + else + { + if ( bInputFieldEndCharInserted + && (nEnd - nStart) > 0 ) + { + SwIndex aIdx( this, nEnd - 1 ); + EraseText( aIdx, 1 ); + } + + if ( bInputFieldStartCharInserted ) + { + SwIndex aIdx( this, nStart ); + EraseText( aIdx, 1 ); + } + } + } + } + + if ( bHiddenPara ) + { + SetCalcHiddenParaField(); + } + + return bRet; +} + +void SwTextNode::DeleteAttribute( SwTextAttr * const pAttr ) +{ + if ( !HasHints() ) + { + OSL_FAIL("DeleteAttribute called, but text node without hints?"); + return; + } + + if ( pAttr->HasDummyChar() ) + { + // copy index! + const SwIndex aIdx( this, pAttr->GetStart() ); + // erase the CH_TXTATR, which will also delete pAttr + EraseText( aIdx, 1 ); + } + else if ( pAttr->HasContent() ) + { + const SwIndex aIdx( this, pAttr->GetStart() ); + assert(pAttr->End() != nullptr); + EraseText( aIdx, *pAttr->End() - pAttr->GetStart() ); + } + else + { + // create MsgHint before start/end become invalid + SwUpdateAttr aHint( + pAttr->GetStart(), + *pAttr->GetEnd(), + pAttr->Which()); + + m_pSwpHints->Delete( pAttr ); + SwTextAttr::Destroy( pAttr, GetDoc()->GetAttrPool() ); + NotifyClients( nullptr, &aHint ); + + TryDeleteSwpHints(); + } +} + +//FIXME: this does NOT respect SORT NUMBER (for CHARFMT)! +void SwTextNode::DeleteAttributes( + const sal_uInt16 nWhich, + const sal_Int32 nStart, + const sal_Int32 nEnd ) +{ + if ( !HasHints() ) + return; + + for ( size_t nPos = 0; m_pSwpHints && nPos < m_pSwpHints->Count(); ++nPos ) + { + SwTextAttr * const pTextHt = m_pSwpHints->Get( nPos ); + const sal_Int32 nHintStart = pTextHt->GetStart(); + if (nStart < nHintStart) + { + break; // sorted by start + } + else if ( (nStart == nHintStart) && (nWhich == pTextHt->Which()) ) + { + if ( nWhich == RES_CHRATR_HIDDEN ) + { + assert(!"hey, that's a CHRATR! how did that get in?"); + SetCalcHiddenCharFlags(); + } + else if ( nWhich == RES_TXTATR_CHARFMT ) + { + // Check if character format contains hidden attribute: + const SwCharFormat* pFormat = pTextHt->GetCharFormat().GetCharFormat(); + const SfxPoolItem* pItem; + if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN, true, &pItem ) ) + SetCalcHiddenCharFlags(); + } + // #i75430# Recalc hidden flags if necessary + else if ( nWhich == RES_TXTATR_AUTOFMT ) + { + // Check if auto style contains hidden attribute: + const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pTextHt, RES_CHRATR_HIDDEN ); + if ( pHiddenItem ) + SetCalcHiddenCharFlags(); + // for auto styles DeleteAttributes is only called from Undo + // so it shouldn't need to care about ignore start/end flags + } + + sal_Int32 const * const pEndIdx = pTextHt->GetEnd(); + + if ( pTextHt->HasDummyChar() ) + { + // copy index! + const SwIndex aIdx( this, nStart ); + // erase the CH_TXTATR, which will also delete pTextHt + EraseText( aIdx, 1 ); + } + else if ( pTextHt->HasContent() ) + { + const SwIndex aIdx( this, nStart ); + OSL_ENSURE( pTextHt->End() != nullptr, "<SwTextNode::DeleteAttributes(..)> - missing End() at <SwTextAttr> instance which has content" ); + EraseText( aIdx, *pTextHt->End() - nStart ); + } + else if( *pEndIdx == nEnd ) + { + // Create MsgHint before Start and End are gone. + // For HiddenParaFields it's not necessary to call + // SetCalcHiddenParaField because the dtor does that. + SwUpdateAttr aHint( + nStart, + *pEndIdx, + nWhich); + + m_pSwpHints->DeleteAtPos( nPos ); + SwTextAttr::Destroy( pTextHt, GetDoc()->GetAttrPool() ); + NotifyClients( nullptr, &aHint ); + } + } + } + TryDeleteSwpHints(); +} + +void SwTextNode::DelSoftHyph( const sal_Int32 nStt, const sal_Int32 nEnd ) +{ + sal_Int32 nFndPos = nStt; + sal_Int32 nEndPos = nEnd; + for (;;) + { + nFndPos = m_Text.indexOf(CHAR_SOFTHYPHEN, nFndPos); + if (nFndPos<0 || nFndPos>=nEndPos ) + { + break; + } + const SwIndex aIdx( this, nFndPos ); + EraseText( aIdx, 1 ); + --nEndPos; + } +} + +bool SwTextNode::IsIgnoredCharFormatForNumbering(const sal_uInt16 nWhich) +{ + return (nWhich == RES_CHRATR_UNDERLINE || nWhich == RES_CHRATR_BACKGROUND + || nWhich == RES_CHRATR_ESCAPEMENT); +} + +//In MS Word, following properties of the paragraph end position won't affect the formatting of bullets, so we ignore them: +//Font underline; +//Font Italic of Western, CJK and CTL; +//Font Bold of Wertern, CJK and CTL; +static bool lcl_IsIgnoredCharFormatForBullets(const sal_uInt16 nWhich) +{ + return (nWhich == RES_CHRATR_UNDERLINE || nWhich == RES_CHRATR_POSTURE || nWhich == RES_CHRATR_WEIGHT + || nWhich == RES_CHRATR_CJK_POSTURE || nWhich == RES_CHRATR_CJK_WEIGHT + || nWhich == RES_CHRATR_CTL_POSTURE || nWhich == RES_CHRATR_CTL_WEIGHT); +} + +//Condition for expanding char set to character style of specified number rule level: +//The item inside the set should not conflict to any exist and non-default item inside paragraph properties set (SwContentNode::SwPAttrSet); +//The node should have applied a number rule; +//The node should be counted in a list, if not, make it to be; +//The item should not conflict to any exist and non-default item inside the character of specified number rule level; +//The item should not be ignored depend on the exact number rule type; +void SwTextNode::TryCharSetExpandToNum(const SfxItemSet& aCharSet) +{ + SfxItemIter aIter( aCharSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + if (!pItem) + return; + const sal_uInt16 nWhich = pItem->Which(); + + const SfxPoolItem& rInnerItem = GetAttr(nWhich,false); + + if (!IsDefaultItem(&rInnerItem) && !IsInvalidItem(&rInnerItem)) + return; + + if (!IsInList() && GetNumRule() && !GetListId().isEmpty()) + { + return; + } + + SwNumRule* pCurrNum = GetNumRule(false); + + int nLevel = GetActualListLevel(); + + if (nLevel != -1 && pCurrNum) + { + const SwNumFormat* pCurrNumFormat = pCurrNum->GetNumFormat(static_cast<sal_uInt16>(nLevel)); + if (pCurrNumFormat) + { + if (pCurrNumFormat->IsItemize() && lcl_IsIgnoredCharFormatForBullets(nWhich)) + return; + if (pCurrNumFormat->IsEnumeration() && SwTextNode::IsIgnoredCharFormatForNumbering(nWhich)) + return; + SwCharFormat* pCurrCharFormat =pCurrNumFormat->GetCharFormat(); + + if (pCurrCharFormat && pCurrCharFormat->GetItemState(nWhich,false) != SfxItemState::SET) + { + pCurrCharFormat->SetFormatAttr(*pItem); + SwNumFormat aNewNumFormat(*pCurrNumFormat); + aNewNumFormat.SetCharFormat(pCurrCharFormat); + pCurrNum->Set(nLevel,aNewNumFormat); + } + } + } +} + +// Set these attributes on SwTextNode. If they apply to the entire paragraph +// text, set them in the SwTextNode's item set (SwContentNode::SetAttr). +bool SwTextNode::SetAttr( + const SfxItemSet& rSet, + const sal_Int32 nStt, + const sal_Int32 nEnd, + const SetAttrMode nMode, + SwTextAttr **ppNewTextAttr ) +{ + if( !rSet.Count() ) + return false; + + // split sets (for selection in nodes) + const SfxItemSet* pSet = &rSet; + SfxItemSet aTextSet( *rSet.GetPool(), svl::Items<RES_TXTATR_BEGIN, RES_TXTATR_END-1>{} ); + + // entire paragraph + if ( !nStt && (nEnd == m_Text.getLength()) && + !(nMode & SetAttrMode::NOFORMATATTR ) ) + { + // if the node already has CharFormat hints, the new attributes must + // be set as hints too to override those. + bool bHasCharFormats = false; + if ( HasHints() ) + { + for ( size_t n = 0; n < m_pSwpHints->Count(); ++n ) + { + if ( m_pSwpHints->Get( n )->IsCharFormatAttr() ) + { + bHasCharFormats = true; + break; + } + } + } + + if( !bHasCharFormats ) + { + aTextSet.Put( rSet ); + // If there are any character attributes in rSet, + // we want to set them at the paragraph: + if( aTextSet.Count() != rSet.Count() ) + { + const bool bRet = SetAttr( rSet ); + if( !aTextSet.Count() ) + return bRet; + } + + // check for auto style: + const SfxPoolItem* pItem; + const bool bAutoStyle = SfxItemState::SET == aTextSet.GetItemState( RES_TXTATR_AUTOFMT, false, &pItem ); + if ( bAutoStyle ) + { + std::shared_ptr<SfxItemSet> pAutoStyleSet = static_cast<const SwFormatAutoFormat*>(pItem)->GetStyleHandle(); + const bool bRet = SetAttr( *pAutoStyleSet ); + if( 1 == aTextSet.Count() ) + return bRet; + } + + // Continue with the text attributes: + pSet = &aTextSet; + } + } + + GetOrCreateSwpHints(); + + SfxItemSet aCharSet( *rSet.GetPool(), aCharAutoFormatSetRange ); + + size_t nCount = 0; + SfxItemIter aIter( *pSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + + do + { + if (!IsInvalidItem(pItem)) + { + const sal_uInt16 nWhich = pItem->Which(); + OSL_ENSURE( isCHRATR(nWhich) || isTXTATR(nWhich), + "SwTextNode::SetAttr(): unknown attribute" ); + if ( isCHRATR(nWhich) || isTXTATR(nWhich) ) + { + if ((RES_TXTATR_CHARFMT == nWhich) && + (GetDoc()->GetDfltCharFormat() == + static_cast<const SwFormatCharFormat*>(pItem)->GetCharFormat())) + { + SwIndex aIndex( this, nStt ); + RstTextAttr( aIndex, nEnd - nStt, RES_TXTATR_CHARFMT ); + DontExpandFormat( aIndex ); + } + else + { + if (isCHRATR(nWhich) || + (RES_TXTATR_UNKNOWN_CONTAINER == nWhich)) + { + aCharSet.Put( *pItem ); + } + else + { + + SwTextAttr *const pNew = MakeTextAttr( *GetDoc(), + const_cast<SfxPoolItem&>(*pItem), nStt, nEnd ); + if ( pNew ) + { + // store the first one we create into the pp + if (ppNewTextAttr && !*ppNewTextAttr) + *ppNewTextAttr = pNew; + if ( nEnd != nStt && !pNew->GetEnd() ) + { + OSL_FAIL("Attribute without end, but area marked"); + DestroyAttr( pNew ); // do not insert + } + else if ( InsertHint( pNew, nMode ) ) + { + ++nCount; + } + } + } + } + } + } + pItem = aIter.NextItem(); + } while(pItem); + + if ( aCharSet.Count() ) + { + SwTextAttr* pTmpNew = MakeTextAttr( *GetDoc(), aCharSet, nStt, nEnd ); + if ( InsertHint( pTmpNew, nMode ) ) + { + ++nCount; + } + } + + TryDeleteSwpHints(); + + return nCount != 0; +} + +static void lcl_MergeAttr( SfxItemSet& rSet, const SfxPoolItem& rAttr ) +{ + if ( RES_TXTATR_AUTOFMT == rAttr.Which() ) + { + const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr ); + if ( !pCFSet ) + return; + SfxWhichIter aIter( *pCFSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + if( ( nWhich < RES_CHRATR_END || + RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) && + ( SfxItemState::SET == pCFSet->GetItemState( nWhich ) ) ) + rSet.Put( pCFSet->Get( nWhich ) ); + nWhich = aIter.NextWhich(); + } + } + else + rSet.Put( rAttr ); +} + +static void lcl_MergeAttr_ExpandChrFormat( SfxItemSet& rSet, const SfxPoolItem& rAttr ) +{ + if( RES_TXTATR_CHARFMT == rAttr.Which() || + RES_TXTATR_INETFMT == rAttr.Which() || + RES_TXTATR_AUTOFMT == rAttr.Which() ) + { + const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr ); + + if ( pCFSet ) + { + SfxWhichIter aIter( *pCFSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + if( ( nWhich < RES_CHRATR_END || + ( RES_TXTATR_AUTOFMT == rAttr.Which() && RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) ) && + ( SfxItemState::SET == pCFSet->GetItemState( nWhich ) ) ) + rSet.Put( pCFSet->Get( nWhich ) ); + nWhich = aIter.NextWhich(); + } + } + } + +/* If multiple attributes overlap, the last one wins! + Probably this can only happen between a RES_TXTATR_INETFMT and one of the + other hints, because BuildPortions ensures that CHARFMT/AUTOFMT don't + overlap. But there may be multiple CHARFMT/AUTOFMT with exactly the same + start/end, sorted by BuildPortions, in which case the same logic applies. + + 1234567890123456789 + |------------| Font1 + |------| Font2 + ^ ^ + |--| query range: -> Font2 +*/ + // merge into set + rSet.Put( rAttr ); +} + +namespace { + +struct SwPoolItemEndPair +{ +public: + const SfxPoolItem* mpItem; + sal_Int32 mnEndPos; + + SwPoolItemEndPair() : mpItem( nullptr ), mnEndPos( 0 ) {}; +}; + +} + +static void lcl_MergeListLevelIndentAsLRSpaceItem( const SwTextNode& rTextNode, + SfxItemSet& rSet ) +{ + if ( rTextNode.AreListLevelIndentsApplicable() ) + { + const SwNumRule* pRule = rTextNode.GetNumRule(); + if ( pRule && rTextNode.GetActualListLevel() >= 0 ) + { + const SwNumFormat& rFormat = pRule->Get(static_cast<sal_uInt16>(rTextNode.GetActualListLevel())); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextLeft( rFormat.GetIndentAt() ); + aLR.SetTextFirstLineOffset( static_cast<short>(rFormat.GetFirstLineIndent()) ); + rSet.Put( aLR ); + } + } + } +} + +// request the attributes of the TextNode at the range +bool SwTextNode::GetParaAttr(SfxItemSet& rSet, sal_Int32 nStt, sal_Int32 nEnd, + const bool bOnlyTextAttr, const bool bGetFromChrFormat, + const bool bMergeIndentValuesOfNumRule, + SwRootFrame const*const pLayout) const +{ + assert(!rSet.Count()); // handled inconsistently, typically an error? + + if (pLayout && pLayout->IsHideRedlines()) + { + if (GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return false; // ignore deleted node + } + } + + // get the node's automatic attributes + SfxItemSet aFormatSet( *rSet.GetPool(), rSet.GetRanges() ); + if (!bOnlyTextAttr) + { + SwTextNode const& rParaPropsNode( + sw::GetAttrMerged(aFormatSet, *this, pLayout)); + if (bMergeIndentValuesOfNumRule) + { + lcl_MergeListLevelIndentAsLRSpaceItem(rParaPropsNode, aFormatSet); + } + } + + if( HasHints() ) + { + // First, check which text attributes are valid in the range. + // cases: + // Ambiguous, if + // * the attribute is wholly contained in the range + // * the attribute end is in the range + // * the attribute start is in the range + // Unambiguous (merge into set), if + // * the attribute wholly contains the range + // Ignored, if + // * the attribute is wholly outside the range + + void (*fnMergeAttr)( SfxItemSet&, const SfxPoolItem& ) + = bGetFromChrFormat ? &lcl_MergeAttr_ExpandChrFormat + : &lcl_MergeAttr; + + const size_t nSize = m_pSwpHints->Count(); + + if (nStt == nEnd) // no range: + { + for (size_t n = 0; n < nSize; ++n) + { + const SwTextAttr* pHt = m_pSwpHints->Get(n); + const sal_Int32 nAttrStart = pHt->GetStart(); + if (nAttrStart > nEnd) // behind the range + break; + + const sal_Int32* pAttrEnd = pHt->End(); + if ( ! pAttrEnd ) // no attributes without end + continue; + + if( ( nAttrStart < nStt && + ( pHt->DontExpand() ? nStt < *pAttrEnd + : nStt <= *pAttrEnd )) || + ( nStt == nAttrStart && + ( nAttrStart == *pAttrEnd || !nStt ))) + (*fnMergeAttr)( rSet, pHt->GetAttr() ); + } + } + else // a query range is defined + { + // #i75299# + std::unique_ptr< std::vector< SwPoolItemEndPair > > pAttrArr; + + const size_t coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN; + + for (size_t n = 0; n < nSize; ++n) + { + const SwTextAttr* pHt = m_pSwpHints->Get(n); + const sal_Int32 nAttrStart = pHt->GetStart(); + if (nAttrStart > nEnd) // outside, behind + break; + + const sal_Int32* pAttrEnd = pHt->End(); + if ( ! pAttrEnd ) // no attributes without end + continue; + + bool bChkInvalid = false; + if (nAttrStart <= nStt) // before or exactly Start + { + if (*pAttrEnd <= nStt) // outside, before + continue; + + if (nEnd <= *pAttrEnd) // behind or exactly End + (*fnMergeAttr)( aFormatSet, pHt->GetAttr() ); + else +// else if( pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) ) + // ambiguous + bChkInvalid = true; + } + else if (nAttrStart < nEnd // starts in the range +)// && pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) ) + bChkInvalid = true; + + if( bChkInvalid ) + { + // ambiguous? + std::unique_ptr< SfxItemIter > pItemIter; + const SfxPoolItem* pItem = nullptr; + + if ( RES_TXTATR_AUTOFMT == pHt->Which() ) + { + const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() ); + if ( pAutoSet ) + { + pItemIter.reset( new SfxItemIter( *pAutoSet ) ); + pItem = pItemIter->GetCurItem(); + } + } + else + pItem = &pHt->GetAttr(); + + const sal_Int32 nHintEnd = *pAttrEnd; + + for (; pItem; pItem = pItemIter ? pItemIter->NextItem() : nullptr) + { + const sal_uInt16 nHintWhich = pItem->Which(); + OSL_ENSURE(!isUNKNOWNATR(nHintWhich), + "SwTextNode::GetAttr(): unknown attribute?"); + + if (!pAttrArr) + { + pAttrArr.reset( + new std::vector< SwPoolItemEndPair >(coArrSz)); + } + + std::vector< SwPoolItemEndPair >::iterator pPrev = pAttrArr->begin(); + if (isCHRATR(nHintWhich) || + isTXTATR_WITHEND(nHintWhich)) + { + pPrev += nHintWhich - RES_CHRATR_BEGIN; + } + else + { + pPrev = pAttrArr->end(); + } + + if( pPrev != pAttrArr->end() ) + { + if( !pPrev->mpItem ) + { + if ( bOnlyTextAttr || *pItem != aFormatSet.Get( nHintWhich ) ) + { + if( nAttrStart > nStt ) + { + rSet.InvalidateItem( nHintWhich ); + pPrev->mpItem = INVALID_POOL_ITEM; + } + else + { + pPrev->mpItem = pItem; + pPrev->mnEndPos = nHintEnd; + } + } + } + else if( !IsInvalidItem(pPrev->mpItem) ) + { + if( pPrev->mnEndPos == nAttrStart && + *pPrev->mpItem == *pItem ) + { + pPrev->mpItem = pItem; + pPrev->mnEndPos = nHintEnd; + } + else + { + rSet.InvalidateItem( nHintWhich ); + pPrev->mpItem = INVALID_POOL_ITEM; + } + } + } + } // end while + } + } + + if (pAttrArr) + { + for (size_t n = 0; n < coArrSz; ++n) + { + const SwPoolItemEndPair& rItemPair = (*pAttrArr)[ n ]; + if( rItemPair.mpItem && !IsInvalidItem(rItemPair.mpItem) ) + { + const sal_uInt16 nWh = + static_cast<sal_uInt16>(n + RES_CHRATR_BEGIN); + + if (nEnd <= rItemPair.mnEndPos) // behind or exactly end + { + if( *rItemPair.mpItem != aFormatSet.Get( nWh ) ) + (*fnMergeAttr)( rSet, *rItemPair.mpItem ); + } + else + // ambiguous + rSet.InvalidateItem( nWh ); + } + } + } + } + if( aFormatSet.Count() ) + { + // remove all from the format-set that are also set in the text-set + aFormatSet.Differentiate( rSet ); + } + } + + if (aFormatSet.Count()) + { + // now "merge" everything + rSet.Put( aFormatSet ); + } + + return rSet.Count() != 0; +} + +namespace +{ + +typedef std::pair<sal_Int32, sal_Int32> AttrSpan_t; +typedef std::multimap<AttrSpan_t, const SwTextAttr*> AttrSpanMap_t; + +struct IsAutoStyle +{ + bool + operator()(const AttrSpanMap_t::value_type& i_rAttrSpan) + const + { + return i_rAttrSpan.second && i_rAttrSpan.second->Which() == RES_TXTATR_AUTOFMT; + } +}; + +/** Removes from io_rAttrSet all items that are set by style on the + given span. + */ +struct RemovePresentAttrs +{ + explicit RemovePresentAttrs(SfxItemSet& io_rAttrSet) + : m_rAttrSet(io_rAttrSet) + { + } + + void + operator()(const AttrSpanMap_t::value_type& i_rAttrSpan) + const + { + if (!i_rAttrSpan.second) + { + return; + } + + const SwTextAttr* const pAutoStyle(i_rAttrSpan.second); + SfxItemIter aIter(m_rAttrSet); + for (const SfxPoolItem* pItem(aIter.GetCurItem()); pItem; pItem = aIter.NextItem()) + { + const sal_uInt16 nWhich(pItem->Which()); + if (CharFormat::IsItemIncluded(nWhich, pAutoStyle)) + { + m_rAttrSet.ClearItem(nWhich); + } + } + } + +private: + SfxItemSet& m_rAttrSet; +}; + +/** Collects all style-covered spans from i_rHints to o_rSpanMap. In + addition inserts dummy spans with pointer to format equal to 0 for + all gaps (i.e. spans not covered by any style). This simplifies + creation of autostyles for all needed spans, but it means all code + that tries to access the pointer has to check if it's non-null! + */ +void +lcl_CollectHintSpans(const SwpHints& i_rHints, const sal_Int32 nLength, + AttrSpanMap_t& o_rSpanMap) +{ + sal_Int32 nLastEnd(0); + + for (size_t i = 0; i < i_rHints.Count(); ++i) + { + const SwTextAttr* pHint = i_rHints.Get(i); + const sal_uInt16 nWhich(pHint->Which()); + if (nWhich == RES_TXTATR_CHARFMT || nWhich == RES_TXTATR_AUTOFMT) + { + const AttrSpan_t aSpan(pHint->GetStart(), *pHint->End()); + o_rSpanMap.emplace(aSpan, pHint); + + // < not != because there may be multiple CHARFMT at same range + if (nLastEnd < aSpan.first) + { + // insert dummy span covering the gap + o_rSpanMap.emplace( AttrSpan_t(nLastEnd, aSpan.first), nullptr ); + } + + nLastEnd = aSpan.second; + } + } + + // no hints at the end (special case: no hints at all in i_rHints) + if (nLastEnd != nLength && nLength != 0) + { + o_rSpanMap.emplace(AttrSpan_t(nLastEnd, nLength), nullptr); + } +} + +void +lcl_FillWhichIds(const SfxItemSet& i_rAttrSet, std::vector<sal_uInt16>& o_rClearIds) +{ + o_rClearIds.reserve(i_rAttrSet.Count()); + SfxItemIter aIter(i_rAttrSet); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + o_rClearIds.push_back(pItem->Which()); + } +} + +struct SfxItemSetClearer +{ + SfxItemSet & m_rItemSet; + explicit SfxItemSetClearer(SfxItemSet & rItemSet) : m_rItemSet(rItemSet) { } + void operator()(sal_uInt16 const nWhich) { m_rItemSet.ClearItem(nWhich); } +}; + +} + +/** Does the hard work of SwTextNode::FormatToTextAttr: the real conversion + of items to automatic styles. + */ +void +SwTextNode::impl_FormatToTextAttr(const SfxItemSet& i_rAttrSet) +{ + typedef AttrSpanMap_t::iterator AttrSpanMap_iterator_t; + AttrSpanMap_t aAttrSpanMap; + + if (i_rAttrSet.Count() == 0) + { + return; + } + + // 1. Identify all spans in hints' array + + lcl_CollectHintSpans(*m_pSwpHints, m_Text.getLength(), aAttrSpanMap); + + // 2. Go through all spans and insert new attrs + + AttrSpanMap_iterator_t aCurRange(aAttrSpanMap.begin()); + const AttrSpanMap_iterator_t aEnd(aAttrSpanMap.end()); + while (aCurRange != aEnd) + { + typedef std::pair<AttrSpanMap_iterator_t, AttrSpanMap_iterator_t> + AttrSpanMapRange_t; + AttrSpanMapRange_t aRange(aAttrSpanMap.equal_range(aCurRange->first)); + + // 2a. Collect attributes to insert + + SfxItemSet aCurSet(i_rAttrSet); + std::for_each(aRange.first, aRange.second, RemovePresentAttrs(aCurSet)); + + // 2b. Insert automatic style containing the collected attributes + + if (aCurSet.Count() != 0) + { + AttrSpanMap_iterator_t aAutoStyleIt( + std::find_if(aRange.first, aRange.second, IsAutoStyle())); + if (aAutoStyleIt != aRange.second) + { + // there already is an automatic style on that span: + // create new one and remove the original one + SwTextAttr* const pAutoStyle(const_cast<SwTextAttr*>(aAutoStyleIt->second)); + const std::shared_ptr<SfxItemSet> pOldStyle( + static_cast<const SwFormatAutoFormat&>( + pAutoStyle->GetAttr()).GetStyleHandle()); + aCurSet.Put(*pOldStyle); + + // remove the old hint + m_pSwpHints->Delete(pAutoStyle); + DestroyAttr(pAutoStyle); + } + m_pSwpHints->Insert( + MakeTextAttr(*GetDoc(), aCurSet, + aCurRange->first.first, aCurRange->first.second)); + } + + aCurRange = aRange.second; + } + + // hints were directly inserted, so need to fix the Ignore flags now + m_pSwpHints->MergePortions(*this); + + // 3. Clear items from the node + std::vector<sal_uInt16> aClearedIds; + lcl_FillWhichIds(i_rAttrSet, aClearedIds); + ClearItemsFromAttrSet(aClearedIds); +} + +void SwTextNode::FormatToTextAttr( SwTextNode* pNd ) +{ + SfxItemSet aThisSet( GetDoc()->GetAttrPool(), aCharFormatSetRange ); + if( HasSwAttrSet() && GetpSwAttrSet()->Count() ) + aThisSet.Put( *GetpSwAttrSet() ); + + GetOrCreateSwpHints(); + + if( pNd == this ) + { + impl_FormatToTextAttr(aThisSet); + } + else + { + // There are five possible combinations of items from this and + // pNd (pNd is the 'main' node): + + // case pNd this action + + // 1 - - do nothing + // 2 - a convert item to attr of this + // 3 a - convert item to attr of pNd + // 4 a a clear item in this + // 5 a b convert item to attr of this + + SfxItemSet aNdSet( pNd->GetDoc()->GetAttrPool(), aCharFormatSetRange ); + if( pNd->HasSwAttrSet() && pNd->GetpSwAttrSet()->Count() ) + aNdSet.Put( *pNd->GetpSwAttrSet() ); + + pNd->GetOrCreateSwpHints(); + + std::vector<sal_uInt16> aProcessedIds; + + if( aThisSet.Count() ) + { + SfxItemIter aIter( aThisSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(), *pNdItem = nullptr; + SfxItemSet aConvertSet( GetDoc()->GetAttrPool(), aCharFormatSetRange ); + std::vector<sal_uInt16> aClearWhichIds; + + do + { + if( SfxItemState::SET == aNdSet.GetItemState( pItem->Which(), false, &pNdItem ) ) + { + if (*pItem == *pNdItem) // 4 + { + aClearWhichIds.push_back( pItem->Which() ); + } + else // 5 + { + aConvertSet.Put(*pItem); + } + aProcessedIds.push_back(pItem->Which()); + } + else // 2 + { + aConvertSet.Put(*pItem); + } + + pItem = aIter.NextItem(); + } while (pItem); + + // 4/ clear items of this that are set with the same value on pNd + ClearItemsFromAttrSet( aClearWhichIds ); + + // 2, 5/ convert all other items to attrs + impl_FormatToTextAttr(aConvertSet); + } + + { + std::for_each(aProcessedIds.begin(), aProcessedIds.end(), + SfxItemSetClearer(aNdSet)); + + // 3/ convert items to attrs + pNd->impl_FormatToTextAttr(aNdSet); + + if( aNdSet.Count() ) + { + SwFormatChg aTmp1( pNd->GetFormatColl() ); + pNd->NotifyClients( &aTmp1, &aTmp1 ); + } + } + } + + SetCalcHiddenCharFlags(); + + pNd->TryDeleteSwpHints(); +} + +void SwpHints::CalcFlags() +{ + m_bDDEFields = m_bFootnote = false; + const size_t nSize = Count(); + for( size_t nPos = 0; nPos < nSize; ++nPos ) + { + const SwTextAttr* pAttr = Get( nPos ); + switch( pAttr->Which() ) + { + case RES_TXTATR_FTN: + m_bFootnote = true; + if ( m_bDDEFields ) + return; + break; + case RES_TXTATR_FIELD: + { + const SwField* pField = pAttr->GetFormatField().GetField(); + if( SwFieldIds::Dde == pField->GetTyp()->Which() ) + { + m_bDDEFields = true; + if ( m_bFootnote ) + return; + } + } + break; + } + } +} + +bool SwpHints::CalcHiddenParaField() const +{ + m_bCalcHiddenParaField = false; + const bool bOldHiddenByParaField = m_bHiddenByParaField; + bool bNewHiddenByParaField = false; + int nNewResultWeight = 0; + const size_t nSize = Count(); + const SwTextAttr* pTextHt; + + for (size_t nPos = 0; nPos < nSize; ++nPos) + { + pTextHt = Get(nPos); + const sal_uInt16 nWhich = pTextHt->Which(); + + if (RES_TXTATR_FIELD == nWhich) + { + // see also SwTextFrame::IsHiddenNow() + const SwFormatField& rField = pTextHt->GetFormatField(); + int nCurWeight = m_rParent.GetDoc()->FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which()); + if (nCurWeight > nNewResultWeight) + { + nNewResultWeight = nCurWeight; + bNewHiddenByParaField = m_rParent.GetDoc()->FieldHidesPara(*rField.GetField()); + } + else if (nCurWeight == nNewResultWeight && bNewHiddenByParaField) + { + // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide" + // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only + // care about fields of higher weight. + bNewHiddenByParaField = m_rParent.GetDoc()->FieldHidesPara(*rField.GetField()); + } + } + } + SetHiddenByParaField(bNewHiddenByParaField); + return bOldHiddenByParaField != bNewHiddenByParaField; +} + +void SwpHints::NoteInHistory( SwTextAttr *pAttr, const bool bNew ) +{ + if ( m_pHistory ) { m_pHistory->AddHint( pAttr, bNew ); } +} + +bool SwpHints::MergePortions( SwTextNode& rNode ) +{ + if ( !Count() ) + return false; + + // sort before merging + Resort(); + + bool bRet = false; + typedef std::multimap< int, std::pair<SwTextAttr*, bool> > PortionMap; + PortionMap aPortionMap; + std::unordered_map<int, bool> RsidOnlyAutoFormatFlagMap; + sal_Int32 nLastPorStart = COMPLETE_STRING; + int nKey = 0; + + // get portions by start position: + for ( size_t i = 0; i < Count(); ++i ) + { + SwTextAttr *pHt = Get( i ); + if ( RES_TXTATR_CHARFMT != pHt->Which() && + RES_TXTATR_AUTOFMT != pHt->Which() ) + //&& + //RES_TXTATR_INETFMT != pHt->Which() ) + continue; + + bool isRsidOnlyAutoFormat(false); + // check for RSID-only AUTOFMT + if (RES_TXTATR_AUTOFMT == pHt->Which()) + { + std::shared_ptr<SfxItemSet> const pSet( + pHt->GetAutoFormat().GetStyleHandle()); + if ((pSet->Count() == 1) && pSet->GetItem(RES_CHRATR_RSID, false)) + { + // fdo#70201: eliminate no-extent RSID-only AUTOFMT + // could be produced by ReplaceText or (maybe?) RstAttr + if (pHt->GetStart() == *pHt->GetEnd()) + { + DeleteAtPos(i); // kill it without History! + SwTextAttr::Destroy(pHt, rNode.GetDoc()->GetAttrPool()); + --i; + continue; + } + // fdo#52028: this one has _only_ RSID => ignore it completely + if (!pHt->IsFormatIgnoreStart() || !pHt->IsFormatIgnoreEnd()) + { + NoteInHistory(pHt); + pHt->SetFormatIgnoreStart(true); + pHt->SetFormatIgnoreEnd (true); + NoteInHistory(pHt, true); + } + isRsidOnlyAutoFormat = true; + } + } + + if (pHt->GetStart() == *pHt->GetEnd()) + { + // no-length hints are a disease. ignore them here. + // the SwAttrIter::SeekFwd will not call Rst/Chg for them. + continue; + } + + const sal_Int32 nPorStart = pHt->GetStart(); + if (nPorStart != nLastPorStart) + ++nKey; + nLastPorStart = nPorStart; + aPortionMap.insert(std::make_pair(nKey, + std::make_pair(pHt, isRsidOnlyAutoFormat))); + RsidOnlyAutoFormatFlagMap[nKey] = isRsidOnlyAutoFormat; + } + + // check if portion i can be merged with portion i+1: + // note: need to include i=0 to set IgnoreStart and j=nKey+1 to reset + // IgnoreEnd at first / last portion + int i = 0; + int j = i + 1; + while ( i <= nKey ) + { + std::pair< PortionMap::iterator, PortionMap::iterator > aRange1 = aPortionMap.equal_range( i ); + std::pair< PortionMap::iterator, PortionMap::iterator > aRange2 = aPortionMap.equal_range( j ); + PortionMap::iterator aIter1 = aRange1.first; + PortionMap::iterator aIter2 = aRange2.first; + + enum { MATCH, DIFFER_ONLY_RSID, DIFFER } eMerge(MATCH); + size_t const nAttributesInPor1 = std::distance(aRange1.first, aRange1.second); + size_t const nAttributesInPor2 = std::distance(aRange2.first, aRange2.second); + bool const isRsidOnlyAutoFormat1(RsidOnlyAutoFormatFlagMap[i]); + bool const isRsidOnlyAutoFormat2(RsidOnlyAutoFormatFlagMap[j]); + + // if both have one they could be equal, but not if only one has it + bool const bSkipRsidOnlyAutoFormat(nAttributesInPor1 != nAttributesInPor2); + + // this loop needs to handle the case where one has a CHARFMT and the + // other CHARFMT + RSID-only AUTOFMT, so... + // want to skip over RSID-only AUTOFMT here, hence the -1 + if ((nAttributesInPor1 - (isRsidOnlyAutoFormat1 ? 1 : 0)) == + (nAttributesInPor2 - (isRsidOnlyAutoFormat2 ? 1 : 0)) + && (nAttributesInPor1 != 0 || nAttributesInPor2 != 0)) + { + // _if_ there is one element more either in aRange1 or aRange2 + // it _must_ be an RSID-only AUTOFMT, which can be ignored here... + // But if both have RSID-only AUTOFMT they could be equal, no skip! + while (aIter1 != aRange1.second || aIter2 != aRange2.second) + { + // first of all test if there's no gap (before skipping stuff!) + if (aIter1 != aRange1.second && aIter2 != aRange2.second && + *aIter1->second.first->End() < aIter2->second.first->GetStart()) + { + eMerge = DIFFER; + break; + } + // skip it - cannot be equal if bSkipRsidOnlyAutoFormat is set + if (bSkipRsidOnlyAutoFormat + && aIter1 != aRange1.second && aIter1->second.second) + { + assert(DIFFER != eMerge); + eMerge = DIFFER_ONLY_RSID; + ++aIter1; + continue; + } + if (bSkipRsidOnlyAutoFormat + && aIter2 != aRange2.second && aIter2->second.second) + { + assert(DIFFER != eMerge); + eMerge = DIFFER_ONLY_RSID; + ++aIter2; + continue; + } + assert(aIter1 != aRange1.second && aIter2 != aRange2.second); + SwTextAttr const*const p1 = aIter1->second.first; + SwTextAttr const*const p2 = aIter2->second.first; + if (p1->Which() != p2->Which()) + { + eMerge = DIFFER; + break; + } + if (!(*p1 == *p2)) + { + // fdo#52028: for auto styles, check if they differ only + // in the RSID, which should have no effect on text layout + if (RES_TXTATR_AUTOFMT == p1->Which()) + { + const SfxItemSet& rSet1 = *p1->GetAutoFormat().GetStyleHandle(); + const SfxItemSet& rSet2 = *p2->GetAutoFormat().GetStyleHandle(); + + // sadly SfxItemSet::operator== does not seem to work? + SfxItemIter iter1(rSet1); + SfxItemIter iter2(rSet2); + for (SfxPoolItem const* pItem1 = iter1.GetCurItem(), + * pItem2 = iter2.GetCurItem(); + pItem1 && pItem2; + pItem1 = iter1.NextItem(), + pItem2 = iter2.NextItem()) + { + if (pItem1->Which() == RES_CHRATR_RSID) + pItem1 = iter1.NextItem(); + if (pItem2->Which() == RES_CHRATR_RSID) + pItem2 = iter2.NextItem(); + if (!pItem1 && !pItem2) + break; + if (!pItem1 || !pItem2) + { + eMerge = DIFFER; + break; + } + if (pItem1 != pItem2) // all are poolable + { + assert(IsInvalidItem(pItem1) || IsInvalidItem(pItem2) || pItem1->Which() != pItem2->Which() || *pItem1 != *pItem2); + eMerge = DIFFER; + break; + } + if (iter1.IsAtEnd() && iter2.IsAtEnd()) + break; + if (iter1.IsAtEnd() || iter2.IsAtEnd()) + { + eMerge = DIFFER; + break; + } + } + if (DIFFER == eMerge) + break; // outer loop too + else + eMerge = DIFFER_ONLY_RSID; + } + else + { + eMerge = DIFFER; + break; + } + } + ++aIter1; + ++aIter2; + } + } + else + { + eMerge = DIFFER; + } + + if (MATCH == eMerge) + { + // important: delete second range so any IgnoreStart on the first + // range is still valid + // erase all elements with key i + 1 + sal_Int32 nNewPortionEnd = 0; + for ( aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2 ) + { + SwTextAttr *const p2 = aIter2->second.first; + nNewPortionEnd = *p2->GetEnd(); + + const size_t nCountBeforeDelete = Count(); + Delete( p2 ); + + // robust: check if deletion actually took place before destroying attribute: + if ( Count() < nCountBeforeDelete ) + rNode.DestroyAttr( p2 ); + } + aPortionMap.erase( aRange2.first, aRange2.second ); + ++j; + + // change all attributes with key i + aRange1 = aPortionMap.equal_range( i ); + for ( aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1 ) + { + SwTextAttr *const p1 = aIter1->second.first; + NoteInHistory( p1 ); + p1->SetEnd(nNewPortionEnd); + NoteInHistory( p1, true ); + bRet = true; + } + + if (bRet) + { + Resort(); + } + } + else + { + // when not merging the ignore flags need to be either set or reset + // (reset too in case one of the autofmts was recently changed) + bool const bSetIgnoreFlag(DIFFER_ONLY_RSID == eMerge); + for (aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1) + { + if (!aIter1->second.second) // already set above, don't change + { + SwTextAttr *const pCurrent(aIter1->second.first); + if (pCurrent->IsFormatIgnoreEnd() != bSetIgnoreFlag) + { + NoteInHistory(pCurrent); + pCurrent->SetFormatIgnoreEnd(bSetIgnoreFlag); + NoteInHistory(pCurrent, true); + } + } + } + for (aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2) + { + if (!aIter2->second.second) // already set above, don't change + { + SwTextAttr *const pCurrent(aIter2->second.first); + if (pCurrent->IsFormatIgnoreStart() != bSetIgnoreFlag) + { + NoteInHistory(pCurrent); + pCurrent->SetFormatIgnoreStart(bSetIgnoreFlag); + NoteInHistory(pCurrent, true); + } + } + } + i = j; // ++i not enough: i + 1 may have been deleted (MATCH)! + ++j; + } + } + + return bRet; +} + +// check if there is already a character format and adjust the sort numbers +static void lcl_CheckSortNumber( const SwpHints& rHints, SwTextCharFormat& rNewCharFormat ) +{ + const sal_Int32 nHtStart = rNewCharFormat.GetStart(); + const sal_Int32 nHtEnd = *rNewCharFormat.GetEnd(); + sal_uInt16 nSortNumber = 0; + + for ( size_t i = 0; i < rHints.Count(); ++i ) + { + const SwTextAttr* pOtherHt = rHints.Get(i); + + const sal_Int32 nOtherStart = pOtherHt->GetStart(); + + if ( nOtherStart > nHtStart ) + break; + + if ( RES_TXTATR_CHARFMT == pOtherHt->Which() ) + { + const sal_Int32 nOtherEnd = *pOtherHt->End(); + + if ( nOtherStart == nHtStart && nOtherEnd == nHtEnd ) + { + nSortNumber = static_txtattr_cast<const SwTextCharFormat*>(pOtherHt)->GetSortNumber() + 1; + } + } + } + + if ( nSortNumber > 0 ) + rNewCharFormat.SetSortNumber( nSortNumber ); +} + +/* + * Try to insert the new hint. + * Depending on the type of the hint, this either always succeeds, or may fail. + * Depending on the type of the hint, other hints may be deleted or + * overwritten. + * The return value indicates successful insertion. + */ +bool SwpHints::TryInsertHint( + SwTextAttr* const pHint, + SwTextNode &rNode, + const SetAttrMode nMode ) +{ + if ( MAX_HINTS <= Count() ) // we're sorry, this flight is overbooked... + { + OSL_FAIL("hints array full :-("); + return false; + } + + const sal_Int32 *pHtEnd = pHint->GetEnd(); + const sal_uInt16 nWhich = pHint->Which(); + std::vector<sal_uInt16> aWhichSublist; + + switch( nWhich ) + { + case RES_TXTATR_CHARFMT: + { + // Check if character format contains hidden attribute: + const SwCharFormat* pFormat = pHint->GetCharFormat().GetCharFormat(); + const SfxPoolItem* pItem; + if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN, true, &pItem ) ) + rNode.SetCalcHiddenCharFlags(); + + static_txtattr_cast<SwTextCharFormat*>(pHint)->ChgTextNode( &rNode ); + break; + } + // #i75430# Recalc hidden flags if necessary + case RES_TXTATR_AUTOFMT: + { + std::shared_ptr<SfxItemSet> const pSet( pHint->GetAutoFormat().GetStyleHandle() ); + if (pHint->GetStart() == *pHint->GetEnd()) + { + if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false)) + { // empty range RSID-only hints could cause trouble, there's no + rNode.DestroyAttr(pHint); // need for them so don't insert + return false; + } + } + // Check if auto style contains hidden attribute: + const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pHint, RES_CHRATR_HIDDEN ); + if ( pHiddenItem ) + rNode.SetCalcHiddenCharFlags(); + + // fdo#71556: populate aWhichFormatAttr member of SwMsgPoolItem + const sal_uInt16 *pRanges = pSet->GetRanges(); + while( (*pRanges) != 0 ) + { + const sal_uInt16 nBeg = *pRanges; + ++pRanges; + const sal_uInt16 nEnd = *pRanges; + ++pRanges; + for( sal_uInt16 nSubElem = nBeg; nSubElem <= nEnd; ++nSubElem ) + if( pSet->HasItem( nSubElem ) ) + aWhichSublist.push_back( nSubElem ); + } + break; + } + case RES_TXTATR_INETFMT: + static_txtattr_cast<SwTextINetFormat*>(pHint)->InitINetFormat(rNode); + break; + + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + { + SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint)); + bool bDelFirst = nullptr != pTextField->GetpTextNode(); + pTextField->ChgTextNode( &rNode ); + SwDoc* pDoc = rNode.GetDoc(); + const SwField* pField = pTextField->GetFormatField().GetField(); + + if( !pDoc->getIDocumentFieldsAccess().IsNewFieldLst() ) + { + // certain fields must update the SwDoc's calculation flags + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::Database: + case SwFieldIds::SetExp: + case SwFieldIds::HiddenPara: + case SwFieldIds::HiddenText: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + { + if( bDelFirst ) + pDoc->getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField); + if( rNode.GetNodes().IsDocNodes() ) + pDoc->getIDocumentFieldsAccess().InsDelFieldInFieldLst(true, *pTextField); + } + break; + case SwFieldIds::Dde: + if( rNode.GetNodes().IsDocNodes() ) + static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt(); + break; + default: break; + } + } + + // insert into real document's nodes-array? + if( rNode.GetNodes().IsDocNodes() ) + { + bool bInsFieldType = false; + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::SetExp: + bInsFieldType = static_cast<SwSetExpFieldType*>(pField->GetTyp())->IsDeleted(); + if( nsSwGetSetExpType::GSE_SEQ & static_cast<SwSetExpFieldType*>(pField->GetTyp())->GetType() ) + { + // register the field at its FieldType before setting + // the sequence reference number! + SwSetExpFieldType* pFieldType = static_cast<SwSetExpFieldType*>( + pDoc->getIDocumentFieldsAccess().InsertFieldType( *pField->GetTyp() ) ); + if( pFieldType != pField->GetTyp() ) + { + SwFormatField* pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField()); + pFormatField->RegisterToFieldType( *pFieldType ); + pFormatField->GetField()->ChgTyp( pFieldType ); + } + pFieldType->SetSeqRefNo( *const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)) ); + } + break; + case SwFieldIds::User: + bInsFieldType = static_cast<SwUserFieldType*>(pField->GetTyp())->IsDeleted(); + break; + + case SwFieldIds::Dde: + if( pDoc->getIDocumentFieldsAccess().IsNewFieldLst() ) + static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt(); + bInsFieldType = static_cast<SwDDEFieldType*>(pField->GetTyp())->IsDeleted(); + break; + + case SwFieldIds::Postit: + if ( pDoc->GetDocShell() ) + { + pDoc->GetDocShell()->Broadcast( SwFormatFieldHint( + &pTextField->GetFormatField(), SwFormatFieldHintWhich::INSERTED)); + } + break; + default: break; + } + if( bInsFieldType ) + pDoc->getIDocumentFieldsAccess().InsDeletedFieldType( *pField->GetTyp() ); + } + } + break; + case RES_TXTATR_FTN : + static_cast<SwTextFootnote*>(pHint)->ChgTextNode( &rNode ); + break; + case RES_TXTATR_REFMARK: + static_txtattr_cast<SwTextRefMark*>(pHint)->ChgTextNode( &rNode ); + if( rNode.GetNodes().IsDocNodes() ) + { + // search for a reference with the same name + SwTextAttr* pTmpHt; + for( size_t n = 0, nEnd = Count(); n < nEnd; ++n ) + { + const sal_Int32 *pTmpHtEnd; + const sal_Int32 *pTmpHintEnd; + if (RES_TXTATR_REFMARK == (pTmpHt = Get(n))->Which() && + pHint->GetAttr() == pTmpHt->GetAttr() && + nullptr != ( pTmpHtEnd = pTmpHt->GetEnd() ) && + nullptr != ( pTmpHintEnd = pHint->GetEnd() ) ) + { + SwComparePosition eCmp = ::ComparePosition( + pTmpHt->GetStart(), *pTmpHtEnd, + pHint->GetStart(), *pTmpHintEnd ); + bool bDelOld = true, bChgStart = false, bChgEnd = false; + switch( eCmp ) + { + case SwComparePosition::Before: + case SwComparePosition::Behind: bDelOld = false; break; + + case SwComparePosition::Outside: bChgStart = bChgEnd = true; break; + + case SwComparePosition::CollideEnd: + case SwComparePosition::OverlapBefore: bChgStart = true; break; + case SwComparePosition::CollideStart: + case SwComparePosition::OverlapBehind: bChgEnd = true; break; + default: break; + } + + if( bChgStart ) + { + pHint->SetStart( pTmpHt->GetStart() ); + } + if( bChgEnd ) + pHint->SetEnd(*pTmpHtEnd); + + if( bDelOld ) + { + NoteInHistory( pTmpHt ); + rNode.DestroyAttr( Cut( n-- ) ); + --nEnd; + } + } + } + } + break; + case RES_TXTATR_TOXMARK: + static_txtattr_cast<SwTextTOXMark*>(pHint)->ChgTextNode( &rNode ); + break; + + case RES_TXTATR_CJK_RUBY: + static_txtattr_cast<SwTextRuby*>(pHint)->InitRuby(rNode); + break; + + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + static_txtattr_cast<SwTextMeta *>(pHint)->ChgTextNode( &rNode ); + break; + + case RES_CHRATR_HIDDEN: + rNode.SetCalcHiddenCharFlags(); + break; + } + + if( SetAttrMode::DONTEXPAND & nMode ) + pHint->SetDontExpand( true ); + + // special handling for SwTextAttrs without end: + // 1) they cannot overlap + // 2) if two fields are adjacent, they must not be merged into one + // this is guaranteed by inserting a CH_TXTATR_* into the paragraph text! + sal_Int32 nHtStart = pHint->GetStart(); + if( !pHtEnd ) + { + Insert( pHint ); + NoteInHistory(pHint, true); + CalcFlags(); +#ifdef DBG_UTIL + if( !rNode.GetDoc()->IsInReading() ) + CHECK; +#endif + // ... and notify listeners + if(rNode.HasWriterListeners()) + { + SwUpdateAttr aHint( + nHtStart, + nHtStart, + nWhich); + + rNode.ModifyNotification(nullptr,&aHint); + } + + return true; + } + + // from here on, pHint is known to have an end index! + + if( *pHtEnd < nHtStart ) + { + assert(*pHtEnd >= nHtStart); + + // just swap the nonsense: + pHint->SetStart(*pHtEnd); + pHint->SetEnd(nHtStart); + nHtStart = pHint->GetStart(); + } + + // I need this value later on for notification but the pointer may become invalid + const sal_Int32 nHintEnd = *pHtEnd; + const bool bNoHintAdjustMode = bool(SetAttrMode::NOHINTADJUST & nMode); + + // handle nesting attributes: inserting may fail due to overlap! + if (pHint->IsNesting()) + { + const bool bRet( + TryInsertNesting(rNode, *static_txtattr_cast<SwTextAttrNesting*>(pHint))); + if (!bRet) return false; + } + // Currently REFMARK and TOXMARK have OverlapAllowed set to true. + // These attributes may be inserted directly. + // Also attributes without length may be inserted directly. + // SETATTR_NOHINTADJUST is set e.g., during undo. + // Portion building in not necessary during XML import. + else if ( !bNoHintAdjustMode && + !pHint->IsOverlapAllowedAttr() && + !rNode.GetDoc()->IsInXMLImport() && + ( RES_TXTATR_AUTOFMT == nWhich || + RES_TXTATR_CHARFMT == nWhich ) ) + { + assert( nWhich != RES_TXTATR_AUTOFMT || + static_cast<const SwFormatAutoFormat&>(pHint->GetAttr()).GetStyleHandle()->GetPool() == + &rNode.GetDoc()->GetAttrPool()); + + BuildPortions( rNode, *pHint, nMode ); + + if ( nHtStart < nHintEnd ) // skip merging for 0-length attributes + MergePortions( rNode ); + } + else + { + // There may be more than one character style at the current position. + // Take care of the sort number. + // Special case ruby portion: During import, the ruby attribute is set + // multiple times + // Special case hyperlink: During import, the ruby attribute is set + // multiple times + // FME 2007-11-08 #i82989# in NOHINTADJUST mode, we want to insert + // character attributes directly + if ( RES_TXTATR_CHARFMT == nWhich && !bNoHintAdjustMode ) + { + BuildPortions( rNode, *pHint, nMode ); + } + else + { + // #i82989# Check sort numbers in NoHintAdjustMode + if ( RES_TXTATR_CHARFMT == nWhich ) + lcl_CheckSortNumber(*this, *static_txtattr_cast<SwTextCharFormat*>(pHint)); + + Insert( pHint ); + NoteInHistory( pHint, true ); + } + } + + // ... and notify listeners + if ( rNode.HasWriterListeners() ) + { + SwUpdateAttr aHint(nHtStart, nHintEnd, nWhich, aWhichSublist); + + rNode.ModifyNotification( nullptr, &aHint ); + } + +#ifdef DBG_UTIL + if( !bNoHintAdjustMode && !rNode.GetDoc()->IsInReading() ) + CHECK; +#endif + + return true; +} + +void SwpHints::DeleteAtPos( const size_t nPos ) +{ + assert(!m_bStartMapNeedsSorting && "deleting at pos and the list needs sorting?"); + + SwTextAttr *pHint = Get(nPos); + assert( pHint->m_pHints == this ); + // ChainDelete( pHint ); + NoteInHistory( pHint ); + + // optimization: nPos is the position in the Starts array + SwTextAttr *pHt = m_HintsByStart[ nPos ]; + m_HintsByStart.erase( m_HintsByStart.begin() + nPos ); + + if (m_bStartMapNeedsSorting) + ResortStartMap(); + if (m_bEndMapNeedsSorting) + ResortEndMap(); + if (m_bWhichMapNeedsSorting) + ResortWhichMap(); + + auto findIt = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd()); + assert(*findIt == pHt); + m_HintsByEnd.erase(findIt); + + auto findIt2 = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), pHt, CompareSwpHtWhichStart()); + assert(*findIt2 == pHt); + m_HintsByWhichAndStart.erase(findIt2); + + pHt->m_pHints = nullptr; + + if( pHint->Which() == RES_TXTATR_FIELD ) + { + SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint)); + const SwFieldType* pFieldTyp = pTextField->GetFormatField().GetField()->GetTyp(); + if( SwFieldIds::Dde == pFieldTyp->Which() ) + { + const SwTextNode* pNd = pTextField->GetpTextNode(); + if( pNd && pNd->GetNodes().IsDocNodes() ) + const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pFieldTyp))->DecRefCnt(); + pTextField->ChgTextNode(nullptr); + } + else if (m_bHiddenByParaField + && m_rParent.GetDoc()->FieldCanHideParaWeight(pFieldTyp->Which())) + { + m_bCalcHiddenParaField = true; + } + } + else if ( pHint->Which() == RES_TXTATR_ANNOTATION ) + { + SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint)); + const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast( + SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED)); + } + + CalcFlags(); + CHECK_NOTMERGED; // called from BuildPortions +} + +/// delete the hint +/// precondition: pTextHt must be in this array +void SwpHints::Delete( SwTextAttr const * pTextHt ) +{ + const size_t nPos = GetIndexOf( pTextHt ); + assert(SAL_MAX_SIZE != nPos); + if( SAL_MAX_SIZE != nPos ) + DeleteAtPos( nPos ); +} + +void SwTextNode::ClearSwpHintsArr( bool bDelFields ) +{ + if ( HasHints() ) + { + size_t nPos = 0; + while ( nPos < m_pSwpHints->Count() ) + { + SwTextAttr* pDel = m_pSwpHints->Get( nPos ); + bool bDel = false; + + switch( pDel->Which() ) + { + case RES_TXTATR_FLYCNT: + case RES_TXTATR_FTN: + break; + + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + if( bDelFields ) + bDel = true; + break; + default: + bDel = true; break; + } + + if( bDel ) + { + m_pSwpHints->DeleteAtPos( nPos ); + DestroyAttr( pDel ); + } + else + ++nPos; + } + } +} + +LanguageType SwTextNode::GetLang( const sal_Int32 nBegin, const sal_Int32 nLen, + sal_uInt16 nScript ) const +{ + LanguageType nRet = LANGUAGE_DONTKNOW; + + if ( ! nScript ) + { + nScript = g_pBreakIt->GetRealScriptOfText( m_Text, nBegin ); + } + + // #i91465# Consider nScript if pSwpHints == 0 + const sal_uInt16 nWhichId = GetWhichOfScript( RES_CHRATR_LANGUAGE, nScript ); + + if ( HasHints() ) + { + const sal_Int32 nEnd = nBegin + nLen; + const size_t nSize = m_pSwpHints->Count(); + for ( size_t i = 0; i < nSize; ++i ) + { + const SwTextAttr *pHt = m_pSwpHints->Get(i); + const sal_Int32 nAttrStart = pHt->GetStart(); + if( nEnd < nAttrStart ) + break; + + const sal_uInt16 nWhich = pHt->Which(); + + if( nWhichId == nWhich || + ( ( pHt->IsCharFormatAttr() || RES_TXTATR_AUTOFMT == nWhich ) && CharFormat::IsItemIncluded( nWhichId, pHt ) ) ) + { + const sal_Int32 *pEndIdx = pHt->End(); + // do the attribute and the range overlap? + if( !pEndIdx ) + continue; + if( nLen ) + { + if( nAttrStart >= nEnd || nBegin >= *pEndIdx ) + continue; + } + else if( nBegin != nAttrStart || ( nAttrStart != *pEndIdx && nBegin )) + { + if( nAttrStart >= nBegin ) + continue; + if( pHt->DontExpand() ? nBegin >= *pEndIdx : nBegin > *pEndIdx) + continue; + } + const SfxPoolItem* pItem = CharFormat::GetItem( *pHt, nWhichId ); + const LanguageType nLng = static_cast<const SvxLanguageItem*>(pItem)->GetLanguage(); + + // does the attribute completely cover the range? + if( nAttrStart <= nBegin && nEnd <= *pEndIdx ) + nRet = nLng; + else if( LANGUAGE_DONTKNOW == nRet ) + nRet = nLng; // partial overlap, the first one wins + } + } + } + if( LANGUAGE_DONTKNOW == nRet ) + { + nRet = static_cast<const SvxLanguageItem&>(GetSwAttrSet().Get( nWhichId )).GetLanguage(); + if( LANGUAGE_DONTKNOW == nRet ) + nRet = GetAppLanguage(); + } + return nRet; +} + +sal_Unicode GetCharOfTextAttr( const SwTextAttr& rAttr ) +{ + sal_Unicode cRet = CH_TXTATR_BREAKWORD; + switch ( rAttr.Which() ) + { + case RES_TXTATR_REFMARK: + case RES_TXTATR_TOXMARK: + case RES_TXTATR_ANNOTATION: + cRet = CH_TXTATR_INWORD; + break; + + case RES_TXTATR_FIELD: + case RES_TXTATR_FLYCNT: + case RES_TXTATR_FTN: + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + cRet = CH_TXTATR_BREAKWORD; + } + break; + + default: + assert(!"GetCharOfTextAttr: unknown attr"); + break; + } + return cRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/txatbase.cxx b/sw/source/core/txtnode/txatbase.cxx new file mode 100644 index 000000000..4609bd841 --- /dev/null +++ b/sw/source/core/txtnode/txatbase.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <optional> +#include <libxml/xmlwriter.h> +#include <svl/itempool.hxx> +#include <txatbase.hxx> +#include <fmtfld.hxx> + +SwTextAttr::SwTextAttr( SfxPoolItem& rAttr, sal_Int32 nStart ) + : m_pAttr( &rAttr ) + , m_nStart( nStart ) + , m_bDontExpand( false ) + , m_bLockExpandFlag( false ) + , m_bDontMoveAttr( false ) + , m_bCharFormatAttr( false ) + , m_bOverlapAllowedAttr( false ) + , m_bPriorityAttr( false ) + , m_bDontExpandStart( false ) + , m_bNesting( false ) + , m_bHasDummyChar( false ) + , m_bFormatIgnoreStart(false) + , m_bFormatIgnoreEnd(false) + , m_bHasContent( false ) +{ +} + +SwTextAttr::~SwTextAttr() COVERITY_NOEXCEPT_FALSE +{ +} + +const sal_Int32* SwTextAttr::GetEnd() const +{ + return nullptr; +} + +void SwTextAttr::SetEnd(sal_Int32 ) +{ + assert(false); +} + +void SwTextAttr::Destroy( SwTextAttr * pToDestroy, SfxItemPool& rPool ) +{ + if (!pToDestroy) return; + SfxPoolItem * const pAttr = pToDestroy->m_pAttr; + delete pToDestroy; + rPool.Remove( *pAttr ); +} + +bool SwTextAttr::operator==( const SwTextAttr& rAttr ) const +{ + return GetAttr() == rAttr.GetAttr(); +} + +SwTextAttrEnd::SwTextAttrEnd( SfxPoolItem& rAttr, + sal_Int32 nStart, sal_Int32 nEnd ) : + SwTextAttr( rAttr, nStart ), m_nEnd( nEnd ) +{ +} + +const sal_Int32* SwTextAttrEnd::GetEnd() const +{ + return & m_nEnd; +} + +void SwTextAttrEnd::SetEnd(sal_Int32 n) +{ + m_nEnd = n; + if (m_pHints) + m_pHints->EndPosChanged(); +} + +void SwTextAttr::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextAttr")); + + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("start"), BAD_CAST(OString::number(m_nStart).getStr())); + if (End()) + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("end"), BAD_CAST(OString::number(*End()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + const char* pWhich = nullptr; + std::optional<OString> oValue; + switch (Which()) + { + case RES_TXTATR_AUTOFMT: + pWhich = "autofmt"; + break; + case RES_TXTATR_ANNOTATION: + pWhich = "annotation"; + break; + case RES_TXTATR_FLYCNT: + pWhich = "fly content"; + break; + case RES_TXTATR_CHARFMT: + { + pWhich = "character format"; + if (SwCharFormat* pCharFormat = GetCharFormat().GetCharFormat()) + oValue = OString("name: " + OUStringToOString(pCharFormat->GetName(), RTL_TEXTENCODING_UTF8)); + break; + } + case RES_TXTATR_INETFMT: + { + pWhich = "inet format"; + const SwFormatINetFormat& rFormat = GetINetFormat(); + oValue = OString("url: " + rFormat.GetValue().toUtf8()); + break; + } + case RES_TXTATR_CJK_RUBY: + { + pWhich = "ruby"; + const SwFormatRuby& rFormat = GetRuby(); + oValue = OString("rubytext: " + rFormat.GetText().toUtf8()); + break; + } + case RES_TXTATR_META: + { + pWhich = "meta"; + break; + } + case RES_TXTATR_FIELD: + { + pWhich = "field"; + break; + } + default: + break; + } + if (pWhich) + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("which"), BAD_CAST(pWhich)); + if (oValue) + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(oValue->getStr())); + switch (Which()) + { + case RES_TXTATR_AUTOFMT: + GetAutoFormat().dumpAsXml(pWriter); + break; + case RES_TXTATR_FIELD: + GetFormatField().dumpAsXml(pWriter); + break; + default: + break; + } + + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/txatritr.cxx b/sw/source/core/txtnode/txatritr.cxx new file mode 100644 index 000000000..b90f1060f --- /dev/null +++ b/sw/source/core/txtnode/txatritr.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <txatritr.hxx> + +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <fchrfmt.hxx> +#include <charfmt.hxx> +#include <breakit.hxx> +#include <ndtxt.hxx> +#include <txatbase.hxx> + +using namespace ::com::sun::star; + +SwScriptIterator::SwScriptIterator( + const OUString& rStr, sal_Int32 nStt, bool const bFrwrd) + : m_rText(rStr) + , m_nChgPos(rStr.getLength()) + , m_nCurScript(i18n::ScriptType::WEAK) + , m_bForward(bFrwrd) +{ + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + if ( ! bFrwrd && nStt ) + --nStt; + + sal_Int32 nPos = nStt; + m_nCurScript = g_pBreakIt->GetBreakIter()->getScriptType(m_rText, nPos); + if( i18n::ScriptType::WEAK == m_nCurScript ) + { + if( nPos ) + { + nPos = g_pBreakIt->GetBreakIter()->beginOfScript( + m_rText, nPos, m_nCurScript); + if (nPos > 0 && nPos < m_rText.getLength()) + { + nStt = --nPos; + m_nCurScript = + g_pBreakIt->GetBreakIter()->getScriptType(m_rText,nPos); + } + } + } + + m_nChgPos = m_bForward + ? g_pBreakIt->GetBreakIter()->endOfScript( + m_rText, nStt, m_nCurScript) + : g_pBreakIt->GetBreakIter()->beginOfScript( + m_rText, nStt, m_nCurScript); +} + +void SwScriptIterator::Next() +{ + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + if (m_bForward && m_nChgPos >= 0 && m_nChgPos < m_rText.getLength()) + { + m_nCurScript = + g_pBreakIt->GetBreakIter()->getScriptType(m_rText, m_nChgPos); + m_nChgPos = g_pBreakIt->GetBreakIter()->endOfScript( + m_rText, m_nChgPos, m_nCurScript); + } + else if (!m_bForward && m_nChgPos > 0) + { + --m_nChgPos; + m_nCurScript = + g_pBreakIt->GetBreakIter()->getScriptType(m_rText, m_nChgPos); + m_nChgPos = g_pBreakIt->GetBreakIter()->beginOfScript( + m_rText, m_nChgPos, m_nCurScript); + } +} + +SwLanguageIterator::SwLanguageIterator( const SwTextNode& rTNd, + sal_Int32 nStt ) + : m_aScriptIter( rTNd.GetText(), nStt ), + m_rTextNode( rTNd ), + m_pParaItem( nullptr ), + m_nAttrPos( 0 ), + m_nChgPos( nStt ) +{ + SearchNextChg(); +} + +bool SwLanguageIterator::Next() +{ + bool bRet = false; + if (m_nChgPos < m_aScriptIter.GetText().getLength()) + { + bRet = true; + if( !m_aStack.empty() ) + { + do { + const SwTextAttr* pHt = m_aStack.front(); + const sal_Int32 nEndPos = *pHt->End(); + if( m_nChgPos >= nEndPos ) + m_aStack.pop_front(); + else + break; + } while( !m_aStack.empty() ); + } + + if( !m_aStack.empty() ) + { + const size_t nSavePos = m_nAttrPos; + SearchNextChg(); + if( !m_aStack.empty() ) + { + const SwTextAttr* pHt = m_aStack.front(); + const sal_Int32 nEndPos = *pHt->End(); + if( m_nChgPos >= nEndPos ) + { + m_nChgPos = nEndPos; + m_nAttrPos = nSavePos; + + if( RES_TXTATR_CHARFMT == pHt->Which() ) + { + const sal_uInt16 nWId = GetWhichOfScript( RES_CHRATR_LANGUAGE, m_aScriptIter.GetCurrScript() ); + m_pCurrentItem = &pHt->GetCharFormat().GetCharFormat()->GetFormatAttr(nWId); + } + else + m_pCurrentItem = &pHt->GetAttr(); + + m_aStack.pop_front(); + } + } + } + else + SearchNextChg(); + } + return bRet; +} + +void SwLanguageIterator::AddToStack( const SwTextAttr& rAttr ) +{ + size_t nIns = 0; + const sal_Int32 nEndPos = *rAttr.End(); + for( ; nIns < m_aStack.size(); ++nIns ) + if( *m_aStack[ nIns ]->End() > nEndPos ) + break; + + m_aStack.insert( m_aStack.begin() + nIns, &rAttr ); +} + +void SwLanguageIterator::SearchNextChg() +{ + sal_uInt16 nWh = 0; + if( m_nChgPos == m_aScriptIter.GetScriptChgPos() ) + { + m_aScriptIter.Next(); + m_pParaItem = nullptr; + m_nAttrPos = 0; // must be restart at the beginning, because + // some attributes can start before or inside + // the current scripttype! + m_aStack.clear(); + } + if( !m_pParaItem ) + { + nWh = GetWhichOfScript( RES_CHRATR_LANGUAGE, m_aScriptIter.GetCurrScript() ); + m_pParaItem = &m_rTextNode.GetSwAttrSet().Get( nWh ); + } + + sal_Int32 nStt = m_nChgPos; + m_nChgPos = m_aScriptIter.GetScriptChgPos(); + m_pCurrentItem = m_pParaItem; + + const SwpHints* pHts = m_rTextNode.GetpSwpHints(); + if( pHts ) + { + if( !nWh ) + { + nWh = GetWhichOfScript( RES_CHRATR_LANGUAGE, m_aScriptIter.GetCurrScript() ); + } + + const SfxPoolItem* pItem = nullptr; + for( ; m_nAttrPos < pHts->Count(); ++m_nAttrPos ) + { + const SwTextAttr* pHt = pHts->Get( m_nAttrPos ); + const sal_Int32* pEnd = pHt->End(); + const sal_Int32 nHtStt = pHt->GetStart(); + if( nHtStt < nStt && ( !pEnd || *pEnd <= nStt )) + continue; + + if( nHtStt >= m_nChgPos ) + break; + + pItem = CharFormat::GetItem( *pHt, nWh ); + if ( pItem ) + { + if( nHtStt > nStt ) + { + if( m_nChgPos > nHtStt ) + m_nChgPos = nHtStt; + break; + } + AddToStack( *pHt ); + m_pCurrentItem = pItem; + if( *pEnd < m_nChgPos ) + m_nChgPos = *pEnd; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/txtatr2.cxx b/sw/source/core/txtnode/txtatr2.cxx new file mode 100644 index 000000000..dd036d07e --- /dev/null +++ b/sw/source/core/txtnode/txtatr2.cxx @@ -0,0 +1,314 @@ +/* -*- 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 <hintids.hxx> +#include <hints.hxx> +#include <osl/diagnose.h> +#include <txtinet.hxx> +#include <txtatr.hxx> +#include <fchrfmt.hxx> +#include <fmtinfmt.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <doc.hxx> +#include <fmtruby.hxx> +#include <fmtmeta.hxx> +#include <IDocumentState.hxx> +#include <IDocumentStylePoolAccess.hxx> + + +SwTextCharFormat::SwTextCharFormat( SwFormatCharFormat& rAttr, + sal_Int32 nStt, sal_Int32 nEnd ) + : SwTextAttr( rAttr, nStt ) + , SwTextAttrEnd( rAttr, nStt, nEnd ) + , m_pTextNode( nullptr ) + , m_nSortNumber( 0 ) +{ + rAttr.m_pTextAttribute = this; + SetCharFormatAttr( true ); +} + +SwTextCharFormat::~SwTextCharFormat( ) +{ +} + +void SwTextCharFormat::ModifyNotification( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + OSL_ENSURE( isCHRATR(nWhich) || (RES_OBJECTDYING == nWhich) + || (RES_ATTRSET_CHG == nWhich) || (RES_FMT_CHG == nWhich), + "SwTextCharFormat::Modify(): unknown Modify"); + + if ( m_pTextNode ) + { + SwUpdateAttr aUpdateAttr( + GetStart(), + *GetEnd(), + nWhich); + + m_pTextNode->ModifyNotification( &aUpdateAttr, &aUpdateAttr ); + } +} + +bool SwTextCharFormat::GetInfo( SfxPoolItem const & rInfo ) const +{ + return RES_AUTOFMT_DOCNODE != rInfo.Which() || !m_pTextNode || + &m_pTextNode->GetNodes() != static_cast<SwAutoFormatGetDocNode const &>(rInfo).pNodes; +} + +SwTextAttrNesting::SwTextAttrNesting( SfxPoolItem & i_rAttr, + const sal_Int32 i_nStart, const sal_Int32 i_nEnd ) + : SwTextAttr( i_rAttr, i_nStart ) + , SwTextAttrEnd( i_rAttr, i_nStart, i_nEnd ) +{ + SetDontExpand( true ); // never expand this attribute + // lock the expand flag: simple guarantee that nesting will not be + // invalidated by expand operations + SetLockExpandFlag( true ); + SetDontExpandStartAttr( true ); + SetNesting( true ); +} + +SwTextAttrNesting::~SwTextAttrNesting() +{ +} + +SwTextINetFormat::SwTextINetFormat( SwFormatINetFormat& rAttr, + sal_Int32 nStart, sal_Int32 nEnd ) + : SwTextAttr( rAttr, nStart ) + , SwTextAttrNesting( rAttr, nStart, nEnd ) + , SwClient( nullptr ) + , m_pTextNode( nullptr ) + , m_bVisited( false ) + , m_bVisitedValid( false ) +{ + rAttr.mpTextAttr = this; + SetCharFormatAttr( true ); +} + +SwTextINetFormat::~SwTextINetFormat( ) +{ +} + +SwCharFormat* SwTextINetFormat::GetCharFormat() +{ + const SwFormatINetFormat& rFormat = SwTextAttrEnd::GetINetFormat(); + SwCharFormat* pRet = nullptr; + + if (!rFormat.GetValue().isEmpty()) + { + SwDoc* pDoc = GetTextNode().GetDoc(); + if( !IsVisitedValid() ) + { + SetVisited( pDoc->IsVisitedURL( rFormat.GetValue() ) ); + SetVisitedValid( true ); + } + + const sal_uInt16 nId = IsVisited() ? rFormat.GetVisitedFormatId() : rFormat.GetINetFormatId(); + const OUString& rStr = IsVisited() ? rFormat.GetVisitedFormat() : rFormat.GetINetFormat(); + if (rStr.isEmpty()) + { + OSL_ENSURE( false, "<SwTextINetFormat::GetCharFormat()> - missing character format at hyperlink attribute"); + } + + // JP 10.02.2000, Bug 72806: don't modify the doc for getting the + // correct charstyle. + bool bResetMod = !pDoc->getIDocumentState().IsModified(); + Link<bool,void> aOle2Lnk; + if ( bResetMod ) + { + aOle2Lnk = pDoc->GetOle2Link(); + pDoc->SetOle2Link( Link<bool,void>() ); + } + + pRet = IsPoolUserFormat( nId ) + ? pDoc->FindCharFormatByName( rStr ) + : pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( nId ); + + if ( bResetMod ) + { + pDoc->getIDocumentState().ResetModified(); + pDoc->SetOle2Link( aOle2Lnk ); + } + } + + if ( pRet ) + pRet->Add( this ); + else + EndListeningAll(); + + return pRet; +} + +void SwTextINetFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + OSL_ENSURE( isCHRATR(nWhich) || (RES_OBJECTDYING == nWhich) + || (RES_ATTRSET_CHG == nWhich) || (RES_FMT_CHG == nWhich), + "SwTextINetFormat::Modify(): unknown Modify"); + + if ( m_pTextNode ) + { + SwUpdateAttr aUpdateAttr( + GetStart(), + *GetEnd(), + nWhich); + + m_pTextNode->ModifyNotification( &aUpdateAttr, &aUpdateAttr ); + } +} + +bool SwTextINetFormat::GetInfo( SfxPoolItem& rInfo ) const +{ + return RES_AUTOFMT_DOCNODE != rInfo.Which() || !m_pTextNode || + &m_pTextNode->GetNodes() != static_cast<SwAutoFormatGetDocNode&>(rInfo).pNodes; +} + +bool SwTextINetFormat::IsProtect( ) const +{ + return m_pTextNode && m_pTextNode->IsProtect(); +} + +SwTextRuby::SwTextRuby( SwFormatRuby& rAttr, + sal_Int32 nStart, sal_Int32 nEnd ) + : SwTextAttr( rAttr, nStart ) + , SwTextAttrNesting( rAttr, nStart, nEnd ) + , SwClient( nullptr ) + , m_pTextNode( nullptr ) +{ + rAttr.m_pTextAttr = this; +} + +SwTextRuby::~SwTextRuby() +{ +} + +void SwTextRuby::Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew ) +{ + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + OSL_ENSURE( isCHRATR(nWhich) || (RES_OBJECTDYING == nWhich) + || (RES_ATTRSET_CHG == nWhich) || (RES_FMT_CHG == nWhich), + "SwTextRuby::Modify(): unknown Modify"); + + if ( m_pTextNode ) + { + SwUpdateAttr aUpdateAttr( + GetStart(), + *GetEnd(), + nWhich); + + m_pTextNode->ModifyNotification( &aUpdateAttr, &aUpdateAttr ); + } +} + +bool SwTextRuby::GetInfo( SfxPoolItem& rInfo ) const +{ + return RES_AUTOFMT_DOCNODE != rInfo.Which() || !m_pTextNode || + &m_pTextNode->GetNodes() != static_cast<SwAutoFormatGetDocNode&>(rInfo).pNodes; +} + +SwCharFormat* SwTextRuby::GetCharFormat() +{ + const SwFormatRuby& rFormat = SwTextAttrEnd::GetRuby(); + SwCharFormat* pRet = nullptr; + + if( !rFormat.GetText().isEmpty() ) + { + const SwDoc* pDoc = GetTextNode().GetDoc(); + const OUString& rStr = rFormat.GetCharFormatName(); + const sal_uInt16 nId = rStr.isEmpty() + ? static_cast<sal_uInt16>(RES_POOLCHR_RUBYTEXT) + : rFormat.GetCharFormatId(); + + // JP 10.02.2000, Bug 72806: don't modify the doc for getting the + // correct charstyle. + const bool bResetMod = !pDoc->getIDocumentState().IsModified(); + Link<bool,void> aOle2Lnk; + if( bResetMod ) + { + aOle2Lnk = pDoc->GetOle2Link(); + const_cast<SwDoc*>(pDoc)->SetOle2Link( Link<bool,void>() ); + } + + pRet = IsPoolUserFormat( nId ) + ? pDoc->FindCharFormatByName( rStr ) + : const_cast<SwDoc*>(pDoc)->getIDocumentStylePoolAccess().GetCharFormatFromPool( nId ); + + if( bResetMod ) + { + const_cast<SwDoc*>(pDoc)->getIDocumentState().ResetModified(); + const_cast<SwDoc*>(pDoc)->SetOle2Link( aOle2Lnk ); + } + } + + if( pRet ) + pRet->Add( this ); + else + EndListeningAll(); + + return pRet; +} + +SwTextMeta * +SwTextMeta::CreateTextMeta( + ::sw::MetaFieldManager & i_rTargetDocManager, + SwTextNode *const i_pTargetTextNode, + SwFormatMeta & i_rAttr, + sal_Int32 const i_nStart, + sal_Int32 const i_nEnd, + bool const i_bIsCopy) +{ + if (i_bIsCopy) + { // i_rAttr is already cloned, now call DoCopy to copy the sw::Meta + OSL_ENSURE(i_pTargetTextNode, "cannot copy Meta without target node"); + i_rAttr.DoCopy(i_rTargetDocManager, *i_pTargetTextNode); + } + SwTextMeta *const pTextMeta(new SwTextMeta(i_rAttr, i_nStart, i_nEnd)); + return pTextMeta; +} + +SwTextMeta::SwTextMeta( SwFormatMeta & i_rAttr, + const sal_Int32 i_nStart, const sal_Int32 i_nEnd ) + : SwTextAttr( i_rAttr, i_nStart ) + , SwTextAttrNesting( i_rAttr, i_nStart, i_nEnd ) +{ + i_rAttr.SetTextAttr( this ); + SetHasDummyChar(true); +} + +SwTextMeta::~SwTextMeta() +{ + SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(GetAttr()) ); + if (rFormatMeta.GetTextAttr() == this) + { + rFormatMeta.SetTextAttr(nullptr); + } +} + +void SwTextMeta::ChgTextNode(SwTextNode * const pNode) +{ + SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(GetAttr()) ); + if (rFormatMeta.GetTextAttr() == this) + { + rFormatMeta.NotifyChangeTextNode(pNode); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx new file mode 100644 index 000000000..cbc5f4f39 --- /dev/null +++ b/sw/source/core/txtnode/txtedt.cxx @@ -0,0 +1,2288 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/svapp.hxx> +#include <svl/itemiter.hxx> +#include <svl/languageoptions.hxx> +#include <editeng/splwrap.hxx> +#include <editeng/langitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/hangulhanja.hxx> +#include <i18nutil/transliteration.hxx> +#include <SwSmartTagMgr.hxx> +#include <o3tl/safeint.hxx> +#include <officecfg/Office/Writer.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <unotools/charclass.hxx> +#include <sal/log.hxx> +#include <swmodule.hxx> +#include <splargs.hxx> +#include <viewopt.hxx> +#include <acmplwrd.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docsh.hxx> +#include <txtfld.hxx> +#include <txatbase.hxx> +#include <charatr.hxx> +#include <pam.hxx> +#include <hints.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <SwGrammarMarkUp.hxx> +#include <rootfrm.hxx> +#include <swscanner.hxx> + +#include <breakit.hxx> +#include <UndoOverwrite.hxx> +#include <txatritr.hxx> +#include <redline.hxx> +#include <docary.hxx> +#include <scriptinfo.hxx> +#include <docstat.hxx> +#include <editsh.hxx> +#include <unotextmarkup.hxx> +#include <txtatr.hxx> +#include <fmtautofmt.hxx> +#include <istyleaccess.hxx> +#include <unicode/uchar.h> +#include <DocumentSettingManager.hxx> + +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> + +#include <vector> +#include <utility> + +#include <unotextrange.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; +using namespace ::com::sun::star::smarttags; + +namespace +{ + void DetectAndMarkMissingDictionaries( SwDoc* pDoc, + const uno::Reference< XSpellChecker1 >& xSpell, + const LanguageType eActLang ) + { + if( !pDoc ) + return; + + if( xSpell.is() && !xSpell->hasLanguage( eActLang.get() ) ) + pDoc->SetMissingDictionaries( true ); + else + pDoc->SetMissingDictionaries( false ); + } +} + +struct SwParaIdleData_Impl +{ + SwWrongList* pWrong; // for spell checking + SwGrammarMarkUp* pGrammarCheck; // for grammar checking / proof reading + SwWrongList* pSmartTags; + sal_uLong nNumberOfWords; + sal_uLong nNumberOfAsianWords; + sal_uLong nNumberOfChars; + sal_uLong nNumberOfCharsExcludingSpaces; + bool bWordCountDirty; + SwTextNode::WrongState eWrongDirty; ///< online spell checking needed/done? + bool bGrammarCheckDirty; + bool bSmartTagDirty; + bool bAutoComplDirty; ///< auto complete list dirty + + SwParaIdleData_Impl() : + pWrong ( nullptr ), + pGrammarCheck ( nullptr ), + pSmartTags ( nullptr ), + nNumberOfWords ( 0 ), + nNumberOfAsianWords ( 0 ), + nNumberOfChars ( 0 ), + nNumberOfCharsExcludingSpaces ( 0 ), + bWordCountDirty ( true ), + eWrongDirty ( SwTextNode::WrongState::TODO ), + bGrammarCheckDirty ( true ), + bSmartTagDirty ( true ), + bAutoComplDirty ( true ) {}; +}; + +/* + * This has basically the same function as SwScriptInfo::MaskHiddenRanges, + * only for deleted redlines + */ + +static sal_Int32 +lcl_MaskRedlines( const SwTextNode& rNode, OUStringBuffer& rText, + sal_Int32 nStt, sal_Int32 nEnd, + const sal_Unicode cChar ) +{ + sal_Int32 nNumOfMaskedRedlines = 0; + + const SwDoc& rDoc = *rNode.GetDoc(); + + for ( SwRedlineTable::size_type nAct = rDoc.getIDocumentRedlineAccess().GetRedlinePos( rNode, RedlineType::Any ); nAct < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++nAct ) + { + const SwRangeRedline* pRed = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nAct ]; + + if ( pRed->Start()->nNode > rNode.GetIndex() ) + break; + + if( RedlineType::Delete == pRed->GetType() ) + { + sal_Int32 nRedlineEnd; + sal_Int32 nRedlineStart; + + pRed->CalcStartEnd( rNode.GetIndex(), nRedlineStart, nRedlineEnd ); + + if ( nRedlineEnd < nStt || nRedlineStart > nEnd ) + continue; + + while ( nRedlineStart < nRedlineEnd && nRedlineStart < nEnd ) + { + if (nRedlineStart >= nStt) + { + rText[nRedlineStart] = cChar; + ++nNumOfMaskedRedlines; + } + ++nRedlineStart; + } + } + } + + return nNumOfMaskedRedlines; +} + +/** + * Used for spell checking. Deleted redlines and hidden characters are masked + */ +static bool +lcl_MaskRedlinesAndHiddenText( const SwTextNode& rNode, OUStringBuffer& rText, + sal_Int32 nStt, sal_Int32 nEnd, + const sal_Unicode cChar = CH_TXTATR_INWORD ) +{ + sal_Int32 nRedlinesMasked = 0; + sal_Int32 nHiddenCharsMasked = 0; + + const SwDoc& rDoc = *rNode.GetDoc(); + const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + + // If called from word count or from spell checking, deleted redlines + // should be masked: + if ( bShowChg ) + { + nRedlinesMasked = lcl_MaskRedlines( rNode, rText, nStt, nEnd, cChar ); + } + + const bool bHideHidden = !SW_MOD()->GetViewOption(rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE))->IsShowHiddenChar(); + + // If called from word count, we want to mask the hidden ranges even + // if they are visible: + if ( bHideHidden ) + { + nHiddenCharsMasked = + SwScriptInfo::MaskHiddenRanges( rNode, rText, nStt, nEnd, cChar ); + } + + return (nRedlinesMasked > 0) || (nHiddenCharsMasked > 0); +} + +/** + * Used for spell checking. Calculates a rectangle for repaint. + */ +static SwRect lcl_CalculateRepaintRect( + SwTextFrame & rTextFrame, SwTextNode & rNode, + sal_Int32 const nChgStart, sal_Int32 const nChgEnd) +{ + TextFrameIndex const iChgStart(rTextFrame.MapModelToView(&rNode, nChgStart)); + TextFrameIndex const iChgEnd(rTextFrame.MapModelToView(&rNode, nChgEnd)); + + SwRect aRect = rTextFrame.GetPaintArea(); + SwRect aTmp = rTextFrame.GetPaintArea(); + + const SwTextFrame* pStartFrame = &rTextFrame; + while( pStartFrame->HasFollow() && + iChgStart >= pStartFrame->GetFollow()->GetOffset()) + pStartFrame = pStartFrame->GetFollow(); + const SwTextFrame* pEndFrame = pStartFrame; + while( pEndFrame->HasFollow() && + iChgEnd >= pEndFrame->GetFollow()->GetOffset()) + pEndFrame = pEndFrame->GetFollow(); + + bool bSameFrame = true; + + if( rTextFrame.HasFollow() ) + { + if( pEndFrame != pStartFrame ) + { + bSameFrame = false; + SwRect aStFrame( pStartFrame->GetPaintArea() ); + { + SwRectFnSet aRectFnSet(pStartFrame); + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(aStFrame) ); + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(aStFrame) ); + aRectFnSet.SetBottom( aTmp, aRectFnSet.GetBottom(aStFrame) ); + } + aStFrame = pEndFrame->GetPaintArea(); + { + SwRectFnSet aRectFnSet(pEndFrame); + aRectFnSet.SetTop( aRect, aRectFnSet.GetTop(aStFrame) ); + aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aStFrame) ); + aRectFnSet.SetRight( aRect, aRectFnSet.GetRight(aStFrame) ); + } + aRect.Union( aTmp ); + while( true ) + { + pStartFrame = pStartFrame->GetFollow(); + if( pStartFrame == pEndFrame ) + break; + aRect.Union( pStartFrame->GetPaintArea() ); + } + } + } + if( bSameFrame ) + { + SwRectFnSet aRectFnSet(pStartFrame); + if( aRectFnSet.GetTop(aTmp) == aRectFnSet.GetTop(aRect) ) + aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aTmp) ); + else + { + SwRect aStFrame( pStartFrame->GetPaintArea() ); + aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aStFrame) ); + aRectFnSet.SetRight( aRect, aRectFnSet.GetRight(aStFrame) ); + aRectFnSet.SetTop( aRect, aRectFnSet.GetTop(aTmp) ); + } + + if( aTmp.Height() > aRect.Height() ) + aRect.Height( aTmp.Height() ); + } + + return aRect; +} + +/** + * Used for automatic styles. Used during RstAttr. + */ +static bool lcl_HaveCommonAttributes( IStyleAccess& rStyleAccess, + const SfxItemSet* pSet1, + sal_uInt16 nWhichId, + const SfxItemSet& rSet2, + std::shared_ptr<SfxItemSet>& pStyleHandle ) +{ + bool bRet = false; + + std::unique_ptr<SfxItemSet> pNewSet; + + if ( !pSet1 ) + { + OSL_ENSURE( nWhichId, "lcl_HaveCommonAttributes not used correctly" ); + if ( SfxItemState::SET == rSet2.GetItemState( nWhichId, false ) ) + { + pNewSet = rSet2.Clone(); + pNewSet->ClearItem( nWhichId ); + } + } + else if ( pSet1->Count() ) + { + SfxItemIter aIter( *pSet1 ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if ( SfxItemState::SET == rSet2.GetItemState( pItem->Which(), false ) ) + { + if ( !pNewSet ) + pNewSet = rSet2.Clone(); + pNewSet->ClearItem( pItem->Which() ); + } + + pItem = aIter.NextItem(); + } while (pItem); + } + + if ( pNewSet ) + { + if ( pNewSet->Count() ) + pStyleHandle = rStyleAccess.getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR ); + bRet = true; + } + + return bRet; +} + +/** Delete all attributes + * + * 5 cases: + * 1) The attribute is completely in the deletion range: + * -> delete it + * 2) The end of the attribute is in the deletion range: + * -> delete it, then re-insert it with new end + * 3) The start of the attribute is in the deletion range: + * -> delete it, then re-insert it with new start + * 4) The attribute contains the deletion range: + * Split, i.e., + * -> Delete, re-insert from old start to start of deletion range + * -> insert new attribute from end of deletion range to old end + * 5) The attribute is outside the deletion range + * -> nothing to do + * + * @param rIdx starting position + * @param nLen length of the deletion + * @param nthat ??? + * @param pSet ??? + * @param bInclRefToxMark ??? + */ + +void SwTextNode::RstTextAttr( + const SwIndex &rIdx, + const sal_Int32 nLen, + const sal_uInt16 nWhich, + const SfxItemSet* pSet, + const bool bInclRefToxMark, + const bool bExactRange ) +{ + if ( !GetpSwpHints() ) + return; + + sal_Int32 nStt = rIdx.GetIndex(); + sal_Int32 nEnd = nStt + nLen; + { + // enlarge range for the reset of text attributes in case of an overlapping input field + const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt( nStt, RES_TXTATR_INPUTFIELD, PARENT )); + if ( pTextInputField == nullptr ) + { + pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nEnd, RES_TXTATR_INPUTFIELD, PARENT )); + } + if ( pTextInputField != nullptr ) + { + if ( nStt > pTextInputField->GetStart() ) + { + nStt = pTextInputField->GetStart(); + } + if ( nEnd < *(pTextInputField->End()) ) + { + nEnd = *(pTextInputField->End()); + } + } + } + + bool bChanged = false; + + // nMin and nMax initialized to maximum / minimum (inverse) + sal_Int32 nMin = m_Text.getLength(); + sal_Int32 nMax = nStt; + const bool bNoLen = nMin == 0; + + // We have to remember the "new" attributes that have + // been introduced by splitting surrounding attributes (case 2,3,4). + std::vector<SwTextAttr *> newAttributes; + std::vector<SwTextAttr *> delAttributes; + + // iterate over attribute array until start of attribute is behind deletion range + m_pSwpHints->SortIfNeedBe(); // trigger sorting now, we don't want it during iteration + size_t i = 0; + sal_Int32 nAttrStart = sal_Int32(); + SwTextAttr *pHt = nullptr; + while ( (i < m_pSwpHints->Count()) + && ( ( ( nAttrStart = m_pSwpHints->GetWithoutResorting(i)->GetStart()) < nEnd ) + || nLen==0 ) && !bExactRange) + { + pHt = m_pSwpHints->GetWithoutResorting(i); + + // attributes without end stay in! + // but consider <bInclRefToxMark> used by Undo + const sal_Int32* const pAttrEnd = pHt->GetEnd(); + const bool bKeepAttrWithoutEnd = + pAttrEnd == nullptr + && ( !bInclRefToxMark + || ( RES_TXTATR_REFMARK != pHt->Which() + && RES_TXTATR_TOXMARK != pHt->Which() + && RES_TXTATR_META != pHt->Which() + && RES_TXTATR_METAFIELD != pHt->Which() ) ); + if ( bKeepAttrWithoutEnd ) + { + + i++; + continue; + } + // attributes with content stay in + if ( pHt->HasContent() ) + { + ++i; + continue; + } + + // Default behavior is to process all attributes: + bool bSkipAttr = false; + std::shared_ptr<SfxItemSet> pStyleHandle; + + // 1. case: We want to reset only the attributes listed in pSet: + if ( pSet ) + { + bSkipAttr = SfxItemState::SET != pSet->GetItemState( pHt->Which(), false ); + if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() ) + { + // if the current attribute is an autostyle, we have to check if the autostyle + // and pSet have any attributes in common. If so, pStyleHandle will contain + // a handle to AutoStyle / pSet: + bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), pSet, 0, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle ); + } + } + else if ( nWhich ) + { + // 2. case: We want to reset only the attributes with WhichId nWhich: + bSkipAttr = nWhich != pHt->Which(); + if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() ) + { + bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), nullptr, nWhich, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle ); + } + } + else if ( !bInclRefToxMark ) + { + // 3. case: Reset all attributes except from ref/toxmarks: + // skip hints with CH_TXTATR here + // (deleting those is ONLY allowed for UNDO!) + bSkipAttr = RES_TXTATR_REFMARK == pHt->Which() + || RES_TXTATR_TOXMARK == pHt->Which() + || RES_TXTATR_META == pHt->Which() + || RES_TXTATR_METAFIELD == pHt->Which(); + } + + if ( bSkipAttr ) + { + i++; + continue; + } + + if (nStt <= nAttrStart) // Case: 1,3,5 + { + const sal_Int32 nAttrEnd = pAttrEnd != nullptr + ? *pAttrEnd + : nAttrStart; + if (nEnd > nAttrStart + || (nEnd == nAttrEnd && nEnd == nAttrStart)) // Case: 1,3 + { + if ( nMin > nAttrStart ) + nMin = nAttrStart; + if ( nMax < nAttrEnd ) + nMax = nAttrEnd; + // If only a no-extent hint is deleted, no resorting is needed + bChanged = bChanged || nEnd > nAttrStart || bNoLen; + if (nAttrEnd <= nEnd) // Case: 1 + { + delAttributes.push_back(pHt); + + if ( pStyleHandle ) + { + SwTextAttr* pNew = MakeTextAttr( *GetDoc(), + *pStyleHandle, nAttrStart, nAttrEnd ); + newAttributes.push_back(pNew); + } + } + else // Case: 3 + { + bChanged = true; + m_pSwpHints->NoteInHistory( pHt ); + // UGLY: this may temporarily destroy the sorting! + pHt->SetStart(nEnd); + m_pSwpHints->NoteInHistory( pHt, true ); + + if ( pStyleHandle && nAttrStart < nEnd ) + { + SwTextAttr* pNew = MakeTextAttr( *GetDoc(), + *pStyleHandle, nAttrStart, nEnd ); + newAttributes.push_back(pNew); + } + } + } + } + else if (pAttrEnd != nullptr) // Case: 2,4,5 + { + if (*pAttrEnd > nStt) // Case: 2,4 + { + if (*pAttrEnd < nEnd) // Case: 2 + { + if ( nMin > nAttrStart ) + nMin = nAttrStart; + if ( nMax < *pAttrEnd ) + nMax = *pAttrEnd; + bChanged = true; + + const sal_Int32 nAttrEnd = *pAttrEnd; + + m_pSwpHints->NoteInHistory( pHt ); + // UGLY: this may temporarily destroy the sorting! + pHt->SetEnd(nStt); + m_pSwpHints->NoteInHistory( pHt, true ); + + if ( pStyleHandle ) + { + SwTextAttr* pNew = MakeTextAttr( *GetDoc(), + *pStyleHandle, nStt, nAttrEnd ); + newAttributes.push_back(pNew); + } + } + else if (nLen) // Case: 4 + { + // for Length 0 both hints would be merged again by + // InsertHint, so leave them alone! + if ( nMin > nAttrStart ) + nMin = nAttrStart; + if ( nMax < *pAttrEnd ) + nMax = *pAttrEnd; + bChanged = true; + const sal_Int32 nTmpEnd = *pAttrEnd; + m_pSwpHints->NoteInHistory( pHt ); + // UGLY: this may temporarily destroy the sorting! + pHt->SetEnd(nStt); + m_pSwpHints->NoteInHistory( pHt, true ); + + if ( pStyleHandle && nStt < nEnd ) + { + SwTextAttr* pNew = MakeTextAttr( *GetDoc(), + *pStyleHandle, nStt, nEnd ); + newAttributes.push_back(pNew); + } + + if( nEnd < nTmpEnd ) + { + SwTextAttr* pNew = MakeTextAttr( *GetDoc(), + pHt->GetAttr(), nEnd, nTmpEnd ); + if ( pNew ) + { + SwTextCharFormat* pCharFormat = dynamic_cast<SwTextCharFormat*>(pHt); + if ( pCharFormat ) + static_txtattr_cast<SwTextCharFormat*>(pNew)->SetSortNumber(pCharFormat->GetSortNumber()); + + newAttributes.push_back(pNew); + } + } + } + } + } + ++i; + } + + if (bExactRange) + { + // Only delete the hints which start at nStt and end at nEnd. + for (i = 0; i < m_pSwpHints->Count(); ++i) + { + SwTextAttr* pHint = m_pSwpHints->Get(i); + if ( (isTXTATR_WITHEND(pHint->Which()) && RES_TXTATR_AUTOFMT != pHint->Which()) + || pHint->GetStart() != nStt) + continue; + + const sal_Int32* pHintEnd = pHint->GetEnd(); + if (!pHintEnd || *pHintEnd != nEnd) + continue; + + delAttributes.push_back(pHint); + } + } + + if (bChanged && !delAttributes.empty()) + { // Delete() calls GetStartOf() - requires sorted hints! + m_pSwpHints->Resort(); + } + + // delay deleting the hints because it re-sorts the hints array + for (SwTextAttr *const pDel : delAttributes) + { + m_pSwpHints->Delete(pDel); + DestroyAttr(pDel); + } + + // delay inserting the hints because it re-sorts the hints array + for (SwTextAttr *const pNew : newAttributes) + { + InsertHint(pNew, SetAttrMode::NOHINTADJUST); + } + + TryDeleteSwpHints(); + + if (bChanged) + { + if ( HasHints() ) + { // possibly sometimes Resort would be sufficient, but... + m_pSwpHints->MergePortions(*this); + } + + // TextFrame's respond to aHint, others to aNew + SwUpdateAttr aHint( + nMin, + nMax, + 0); + + NotifyClients( nullptr, &aHint ); + SwFormatChg aNew( GetFormatColl() ); + NotifyClients( nullptr, &aNew ); + } +} + +static sal_Int32 clipIndexBounds(const OUString &rStr, sal_Int32 nPos) +{ + if (nPos < 0) + return 0; + if (nPos > rStr.getLength()) + return rStr.getLength(); + return nPos; +} + +// Return current word: +// Search from left to right, so find the word before nPos. +// Except if at the start of the paragraph, then return the first word. +// If the first word consists only of whitespace, return an empty string. +OUString SwTextFrame::GetCurWord(SwPosition const& rPos) const +{ + TextFrameIndex const nPos(MapModelToViewPos(rPos)); + SwTextNode *const pTextNode(rPos.nNode.GetNode().GetTextNode()); + assert(pTextNode); + OUString const& rText(GetText()); + assert(sal_Int32(nPos) <= rText.getLength()); // invalid index + + if (rText.isEmpty() || IsHiddenNow()) + return OUString(); + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + const uno::Reference< XBreakIterator > &rxBreak = g_pBreakIt->GetBreakIter(); + sal_Int16 nWordType = WordType::DICTIONARY_WORD; + lang::Locale aLocale( g_pBreakIt->GetLocale(pTextNode->GetLang(rPos.nContent.GetIndex())) ); + Boundary aBndry = + rxBreak->getWordBoundary(rText, sal_Int32(nPos), aLocale, nWordType, true); + + // if no word was found use previous word (if any) + if (aBndry.startPos == aBndry.endPos) + { + aBndry = rxBreak->previousWord(rText, sal_Int32(nPos), aLocale, nWordType); + } + + // check if word was found and if it uses a symbol font, if so + // enforce returning an empty string + if (aBndry.endPos != aBndry.startPos + && IsSymbolAt(TextFrameIndex(aBndry.startPos))) + { + aBndry.endPos = aBndry.startPos; + } + + // can have -1 as start/end of bounds not found + aBndry.startPos = clipIndexBounds(rText, aBndry.startPos); + aBndry.endPos = clipIndexBounds(rText, aBndry.endPos); + + return rText.copy(aBndry.startPos, + aBndry.endPos - aBndry.startPos); +} + +SwScanner::SwScanner( const SwTextNode& rNd, const OUString& rText, + const LanguageType* pLang, const ModelToViewHelper& rConvMap, + sal_uInt16 nType, sal_Int32 nStart, sal_Int32 nEnd, bool bClp ) + : SwScanner( + [&rNd](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const bNoChar) + { return rNd.GetLang(nBegin, bNoChar ? 0 : 1, nScript); } + , rText, pLang, rConvMap, nType, nStart, nEnd, bClp) +{ +} + +SwScanner::SwScanner(std::function<LanguageType(sal_Int32, sal_Int32, bool)> const& pGetLangOfChar, + const OUString& rText, const LanguageType* pLang, + const ModelToViewHelper& rConvMap, sal_uInt16 nType, sal_Int32 nStart, + sal_Int32 nEnd, bool bClp) + : m_pGetLangOfChar(pGetLangOfChar) + , m_aPreDashReplacementText(rText) + , m_pLanguage(pLang) + , m_ModelToView(rConvMap) + , m_nLength(0) + , m_nOverriddenDashCount(0) + , m_nWordType(nType) + , m_bClip(bClp) +{ + m_nStartPos = m_nBegin = nStart; + m_nEndPos = nEnd; + + //MSWord f.e has special emdash and endash behaviour in that they break + //words for the purposes of word counting, while a hyphen etc. doesn't. + + //The default configuration treats emdash/endash as a word break, but + //additional ones can be added in under tools->options + if (m_nWordType == i18n::WordType::WORD_COUNT) + { + OUString sDashes = officecfg::Office::Writer::WordCount::AdditionalSeparators::get(); + OUStringBuffer aBuf(m_aPreDashReplacementText); + for (sal_Int32 i = m_nStartPos; i < m_nEndPos; ++i) + { + if (i < 0) + continue; + sal_Unicode cChar = aBuf[i]; + if (sDashes.indexOf(cChar) != -1) + { + aBuf[i] = ' '; + ++m_nOverriddenDashCount; + } + } + m_aText = aBuf.makeStringAndClear(); + } + else + m_aText = m_aPreDashReplacementText; + + assert(m_aPreDashReplacementText.getLength() == m_aText.getLength()); + + if ( m_pLanguage ) + { + m_aCurrentLang = *m_pLanguage; + } + else + { + ModelToViewHelper::ModelPosition aModelBeginPos = + m_ModelToView.ConvertToModelPosition( m_nBegin ); + m_aCurrentLang = m_pGetLangOfChar(aModelBeginPos.mnPos, 0, true); + } +} + +namespace +{ + //fdo#45271 for Asian words count characters instead of words + sal_Int32 forceEachAsianCodePointToWord(const OUString &rText, sal_Int32 nBegin, sal_Int32 nLen) + { + if (nLen > 1) + { + const uno::Reference< XBreakIterator > &rxBreak = g_pBreakIt->GetBreakIter(); + + sal_uInt16 nCurrScript = rxBreak->getScriptType( rText, nBegin ); + + sal_Int32 indexUtf16 = nBegin; + rText.iterateCodePoints(&indexUtf16); + + //First character is Asian, consider it a word :-( + if (nCurrScript == i18n::ScriptType::ASIAN) + { + nLen = indexUtf16 - nBegin; + return nLen; + } + + //First character was not Asian, consider appearance of any Asian character + //to be the end of the word + while (indexUtf16 < nBegin + nLen) + { + nCurrScript = rxBreak->getScriptType( rText, indexUtf16 ); + if (nCurrScript == i18n::ScriptType::ASIAN) + { + nLen = indexUtf16 - nBegin; + return nLen; + } + rText.iterateCodePoints(&indexUtf16); + } + } + return nLen; + } +} + +bool SwScanner::NextWord() +{ + m_nBegin = m_nBegin + m_nLength; + Boundary aBound; + + CharClass& rCC = GetAppCharClass(); + LanguageTag aOldLanguageTag = rCC.getLanguageTag(); + + while ( true ) + { + // skip non-letter characters: + while (m_nBegin < m_aText.getLength()) + { + if (m_nBegin >= 0 && !u_isspace(m_aText[m_nBegin])) + { + if ( !m_pLanguage ) + { + const sal_uInt16 nNextScriptType = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, m_nBegin ); + ModelToViewHelper::ModelPosition aModelBeginPos = + m_ModelToView.ConvertToModelPosition( m_nBegin ); + m_aCurrentLang = m_pGetLangOfChar(aModelBeginPos.mnPos, nNextScriptType, false); + } + + if ( m_nWordType != i18n::WordType::WORD_COUNT ) + { + rCC.setLanguageTag( LanguageTag( g_pBreakIt->GetLocale( m_aCurrentLang )) ); + if ( rCC.isLetterNumeric(OUString(m_aText[m_nBegin])) ) + break; + } + else + break; + } + ++m_nBegin; + } + + if ( m_nBegin >= m_aText.getLength() || m_nBegin >= m_nEndPos ) + return false; + + // get the word boundaries + aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( m_aText, m_nBegin, + g_pBreakIt->GetLocale( m_aCurrentLang ), m_nWordType, true ); + OSL_ENSURE( aBound.endPos >= aBound.startPos, "broken aBound result" ); + + // we don't want to include preceding text + // to count words in text with mixed script punctuation correctly, + // but we want to include preceding symbols (eg. percent sign, section sign, + // degree sign defined by dict_word_hu to spell check their affixed forms). + if (m_nWordType == i18n::WordType::WORD_COUNT && aBound.startPos < m_nBegin) + aBound.startPos = m_nBegin; + + //no word boundaries could be found + if(aBound.endPos == aBound.startPos) + return false; + + //if a word before is found it has to be searched for the next + if(aBound.endPos == m_nBegin) + ++m_nBegin; + else + break; + } // end while( true ) + + rCC.setLanguageTag( aOldLanguageTag ); + + // #i89042, as discussed with HDU: don't evaluate script changes for word count. Use whole word. + if ( m_nWordType == i18n::WordType::WORD_COUNT ) + { + m_nBegin = std::max(aBound.startPos, m_nBegin); + m_nLength = 0; + if (aBound.endPos > m_nBegin) + m_nLength = aBound.endPos - m_nBegin; + } + else + { + // we have to differentiate between these cases: + if ( aBound.startPos <= m_nBegin ) + { + OSL_ENSURE( aBound.endPos >= m_nBegin, "Unexpected aBound result" ); + + // restrict boundaries to script boundaries and nEndPos + const sal_uInt16 nCurrScript = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, m_nBegin ); + OUString aTmpWord = m_aText.copy( m_nBegin, aBound.endPos - m_nBegin ); + const sal_Int32 nScriptEnd = m_nBegin + + g_pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript ); + const sal_Int32 nEnd = std::min( aBound.endPos, nScriptEnd ); + + // restrict word start to last script change position + sal_Int32 nScriptBegin = 0; + if ( aBound.startPos < m_nBegin ) + { + // search from nBegin backwards until the next script change + aTmpWord = m_aText.copy( aBound.startPos, + m_nBegin - aBound.startPos + 1 ); + nScriptBegin = aBound.startPos + + g_pBreakIt->GetBreakIter()->beginOfScript( aTmpWord, m_nBegin - aBound.startPos, + nCurrScript ); + } + + m_nBegin = std::max( aBound.startPos, nScriptBegin ); + m_nLength = nEnd - m_nBegin; + } + else + { + const sal_uInt16 nCurrScript = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, aBound.startPos ); + OUString aTmpWord = m_aText.copy( aBound.startPos, + aBound.endPos - aBound.startPos ); + const sal_Int32 nScriptEnd = aBound.startPos + + g_pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript ); + const sal_Int32 nEnd = std::min( aBound.endPos, nScriptEnd ); + m_nBegin = aBound.startPos; + m_nLength = nEnd - m_nBegin; + } + } + + // optionally clip the result of getWordBoundaries: + if ( m_bClip ) + { + aBound.startPos = std::max( aBound.startPos, m_nStartPos ); + aBound.endPos = std::min( aBound.endPos, m_nEndPos ); + if (aBound.endPos < aBound.startPos) + { + m_nBegin = m_nEndPos; + m_nLength = 0; // found word is outside of search interval + } + else + { + m_nBegin = aBound.startPos; + m_nLength = aBound.endPos - m_nBegin; + } + } + + if( ! m_nLength ) + return false; + + if ( m_nWordType == i18n::WordType::WORD_COUNT ) + m_nLength = forceEachAsianCodePointToWord(m_aText, m_nBegin, m_nLength); + + m_aWord = m_aPreDashReplacementText.copy( m_nBegin, m_nLength ); + + return true; +} + +// Note: this is a clone of SwTextFrame::AutoSpell_, so keep them in sync when fixing things! +bool SwTextNode::Spell(SwSpellArgs* pArgs) +{ + // modify string according to redline information and hidden text + const OUString aOldText( m_Text ); + OUStringBuffer buf(m_Text); + const bool bRestoreString = + lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength()); + if (bRestoreString) + { // ??? UGLY: is it really necessary to modify m_Text here? + m_Text = buf.makeStringAndClear(); + } + + sal_Int32 nBegin = ( pArgs->pStartNode != this ) + ? 0 + : pArgs->pStartIdx->GetIndex(); + + sal_Int32 nEnd = ( pArgs->pEndNode != this ) + ? m_Text.getLength() + : pArgs->pEndIdx->GetIndex(); + + pArgs->xSpellAlt = nullptr; + + // 4 cases: + + // 1. IsWrongDirty = 0 and GetWrong = 0 + // Everything is checked and correct + // 2. IsWrongDirty = 0 and GetWrong = 1 + // Everything is checked and errors are identified in the wrong list + // 3. IsWrongDirty = 1 and GetWrong = 0 + // Nothing has been checked + // 4. IsWrongDirty = 1 and GetWrong = 1 + // Text has been checked but there is an invalid range in the wrong list + + // Nothing has to be done for case 1. + if ( ( IsWrongDirty() || GetWrong() ) && m_Text.getLength() ) + { + if (nBegin > m_Text.getLength()) + { + nBegin = m_Text.getLength(); + } + if (nEnd > m_Text.getLength()) + { + nEnd = m_Text.getLength(); + } + + if(!IsWrongDirty()) + { + const sal_Int32 nTemp = GetWrong()->NextWrong( nBegin ); + if(nTemp > nEnd) + { + // reset original text + if ( bRestoreString ) + { + m_Text = aOldText; + } + return false; + } + if(nTemp > nBegin) + nBegin = nTemp; + + } + + // In case 2. we pass the wrong list to the scanned, because only + // the words in the wrong list have to be checked + SwScanner aScanner( *this, m_Text, nullptr, ModelToViewHelper(), + WordType::DICTIONARY_WORD, + nBegin, nEnd ); + while( !pArgs->xSpellAlt.is() && aScanner.NextWord() ) + { + const OUString& rWord = aScanner.GetWord(); + + // get next language for next word, consider language attributes + // within the word + LanguageType eActLang = aScanner.GetCurrentLanguage(); + DetectAndMarkMissingDictionaries( GetTextNode()->GetDoc(), pArgs->xSpeller, eActLang ); + + if( rWord.getLength() > 0 && LANGUAGE_NONE != eActLang ) + { + if (pArgs->xSpeller.is()) + { + SvxSpellWrapper::CheckSpellLang( pArgs->xSpeller, eActLang ); + pArgs->xSpellAlt = pArgs->xSpeller->spell( rWord, static_cast<sal_uInt16>(eActLang), + Sequence< PropertyValue >() ); + } + if( pArgs->xSpellAlt.is() ) + { + if (IsSymbolAt(aScanner.GetBegin())) + { + pArgs->xSpellAlt = nullptr; + } + else + { + // make sure the selection build later from the data + // below does not include "in word" character to the + // left and right in order to preserve those. Therefore + // count those "in words" in order to modify the + // selection accordingly. + const sal_Unicode* pChar = rWord.getStr(); + sal_Int32 nLeft = 0; + while (*pChar++ == CH_TXTATR_INWORD) + ++nLeft; + pChar = rWord.getLength() ? rWord.getStr() + rWord.getLength() - 1 : nullptr; + sal_Int32 nRight = 0; + while (pChar && *pChar-- == CH_TXTATR_INWORD) + ++nRight; + + pArgs->pStartNode = this; + pArgs->pEndNode = this; + pArgs->pStartIdx->Assign(this, aScanner.GetEnd() - nRight ); + pArgs->pEndIdx->Assign(this, aScanner.GetBegin() + nLeft ); + } + } + } + } + } + + // reset original text + if ( bRestoreString ) + { + m_Text = aOldText; + } + + return pArgs->xSpellAlt.is(); +} + +void SwTextNode::SetLanguageAndFont( const SwPaM &rPaM, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ) +{ + sal_uInt16 aRanges[] = { + nLangWhichId, nLangWhichId, + nFontWhichId, nFontWhichId, + 0, 0, 0 }; + if (!pFont) + aRanges[2] = aRanges[3] = 0; // clear entries with font WhichId + + SwEditShell *pEditShell = GetDoc()->GetEditShell(); + SfxItemSet aSet( pEditShell->GetAttrPool(), aRanges ); + aSet.Put( SvxLanguageItem( nLang, nLangWhichId ) ); + + OSL_ENSURE( pFont, "target font missing?" ); + if (pFont) + { + SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aSet.Get( nFontWhichId ) ); + aFontItem.SetFamilyName( pFont->GetFamilyName()); + aFontItem.SetFamily( pFont->GetFamilyType()); + aFontItem.SetStyleName( pFont->GetStyleName()); + aFontItem.SetPitch( pFont->GetPitch()); + aFontItem.SetCharSet( pFont->GetCharSet() ); + aSet.Put( aFontItem ); + } + + GetDoc()->getIDocumentContentOperations().InsertItemSet( rPaM, aSet ); + // SetAttr( aSet ); <- Does not set language attribute of empty paragraphs correctly, + // <- because since there is no selection the flag to garbage + // <- collect all attributes is set, and therefore attributes spanned + // <- over empty selection are removed. + +} + +bool SwTextNode::Convert( SwConversionArgs &rArgs ) +{ + // get range of text within node to be converted + // (either all the text or the text within the selection + // when the conversion was started) + const sal_Int32 nTextBegin = ( rArgs.pStartNode == this ) + ? std::min(rArgs.pStartIdx->GetIndex(), m_Text.getLength()) + : 0; + + const sal_Int32 nTextEnd = ( rArgs.pEndNode == this ) + ? std::min(rArgs.pEndIdx->GetIndex(), m_Text.getLength()) + : m_Text.getLength(); + + rArgs.aConvText.clear(); + + // modify string according to redline information and hidden text + const OUString aOldText( m_Text ); + OUStringBuffer buf(m_Text); + const bool bRestoreString = + lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength()); + if (bRestoreString) + { // ??? UGLY: is it really necessary to modify m_Text here? + m_Text = buf.makeStringAndClear(); + } + + bool bFound = false; + sal_Int32 nBegin = nTextBegin; + sal_Int32 nLen = 0; + LanguageType nLangFound = LANGUAGE_NONE; + if (m_Text.isEmpty()) + { + if (rArgs.bAllowImplicitChangesForNotConvertibleText) + { + // create SwPaM with mark & point spanning empty paragraph + //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different + SwPaM aCurPaM( *this, 0 ); + + SetLanguageAndFont( aCurPaM, + rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE, + rArgs.pTargetFont, RES_CHRATR_CJK_FONT ); + } + } + else + { + SwLanguageIterator aIter( *this, nBegin ); + + // Implicit changes require setting new attributes, which in turn destroys + // the attribute sequence on that aIter iterates. We store the necessary + // coordinates and apply those changes after iterating through the text. + typedef std::pair<sal_Int32, sal_Int32> ImplicitChangesRange; + std::vector<ImplicitChangesRange> aImplicitChanges; + + // find non zero length text portion of appropriate language + do { + nLangFound = aIter.GetLanguage(); + bool bLangOk = (nLangFound == rArgs.nConvSrcLang) || + (editeng::HangulHanjaConversion::IsChinese( nLangFound ) && + editeng::HangulHanjaConversion::IsChinese( rArgs.nConvSrcLang )); + + sal_Int32 nChPos = aIter.GetChgPos(); + // the position at the end of the paragraph is COMPLETE_STRING and + // thus must be cut to the end of the actual string. + assert(nChPos != -1); + if (nChPos == -1 || nChPos == COMPLETE_STRING) + { + nChPos = m_Text.getLength(); + } + + nLen = nChPos - nBegin; + bFound = bLangOk && nLen > 0; + if (!bFound) + { + // create SwPaM with mark & point spanning the attributed text + //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different + SwPaM aCurPaM( *this, nBegin ); + aCurPaM.SetMark(); + aCurPaM.GetPoint()->nContent = nBegin + nLen; + + // check script type of selected text + SwEditShell *pEditShell = GetDoc()->GetEditShell(); + pEditShell->Push(); // save current cursor on stack + pEditShell->SetSelection( aCurPaM ); + bool bIsAsianScript = (SvtScriptType::ASIAN == pEditShell->GetScriptType()); + pEditShell->Pop(SwCursorShell::PopMode::DeleteCurrent); // restore cursor from stack + + if (!bIsAsianScript && rArgs.bAllowImplicitChangesForNotConvertibleText) + { + // Store for later use + aImplicitChanges.emplace_back(nBegin, nBegin+nLen); + } + nBegin = nChPos; // start of next language portion + } + } while (!bFound && aIter.Next()); /* loop while nothing was found and still sth is left to be searched */ + + // Apply implicit changes, if any, now that aIter is no longer used + for (const auto& rImplicitChange : aImplicitChanges) + { + SwPaM aPaM( *this, rImplicitChange.first ); + aPaM.SetMark(); + aPaM.GetPoint()->nContent = rImplicitChange.second; + SetLanguageAndFont( aPaM, rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE, rArgs.pTargetFont, RES_CHRATR_CJK_FONT ); + } + + } + + // keep resulting text within selection / range of text to be converted + if (nBegin < nTextBegin) + nBegin = nTextBegin; + if (nBegin + nLen > nTextEnd) + nLen = nTextEnd - nBegin; + bool bInSelection = nBegin < nTextEnd; + + if (bFound && bInSelection) // convertible text found within selection/range? + { + OSL_ENSURE( !m_Text.isEmpty(), "convertible text portion missing!" ); + rArgs.aConvText = m_Text.copy(nBegin, nLen); + rArgs.nConvTextLang = nLangFound; + + // position where to start looking in next iteration (after current ends) + rArgs.pStartNode = this; + rArgs.pStartIdx->Assign(this, nBegin + nLen ); + // end position (when we have travelled over the whole document) + rArgs.pEndNode = this; + rArgs.pEndIdx->Assign(this, nBegin ); + } + + // restore original text + if ( bRestoreString ) + { + m_Text = aOldText; + } + + return !rArgs.aConvText.isEmpty(); +} + +// Note: this is a clone of SwTextNode::Spell, so keep them in sync when fixing things! +SwRect SwTextFrame::AutoSpell_(SwTextNode & rNode, sal_Int32 nActPos) +{ + SwRect aRect; + assert(sw::FrameContainsNode(*this, rNode.GetIndex())); + SwTextNode *const pNode(&rNode); + if (!nActPos) + nActPos = COMPLETE_STRING; + + SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords(); + + // modify string according to redline information and hidden text + const OUString aOldText( pNode->GetText() ); + OUStringBuffer buf(pNode->m_Text); + const bool bRestoreString = + lcl_MaskRedlinesAndHiddenText(*pNode, buf, 0, pNode->GetText().getLength()); + if (bRestoreString) + { // ??? UGLY: is it really necessary to modify m_Text here? just for GetLang()? + pNode->m_Text = buf.makeStringAndClear(); + } + + // a change of data indicates that at least one word has been modified + + sal_Int32 nBegin = 0; + sal_Int32 nEnd = pNode->GetText().getLength(); + sal_Int32 nInsertPos = 0; + sal_Int32 nChgStart = COMPLETE_STRING; + sal_Int32 nChgEnd = 0; + sal_Int32 nInvStart = COMPLETE_STRING; + sal_Int32 nInvEnd = 0; + + const bool bAddAutoCmpl = pNode->IsAutoCompleteWordDirty() && + SwViewOption::IsAutoCompleteWords(); + + if( pNode->GetWrong() ) + { + nBegin = pNode->GetWrong()->GetBeginInv(); + if( COMPLETE_STRING != nBegin ) + { + nEnd = std::max(pNode->GetWrong()->GetEndInv(), pNode->GetText().getLength()); + } + + // get word around nBegin, we start at nBegin - 1 + if ( COMPLETE_STRING != nBegin ) + { + if ( nBegin ) + --nBegin; + + LanguageType eActLang = pNode->GetLang( nBegin ); + Boundary aBound = + g_pBreakIt->GetBreakIter()->getWordBoundary( pNode->GetText(), nBegin, + g_pBreakIt->GetLocale( eActLang ), + WordType::DICTIONARY_WORD, true ); + nBegin = aBound.startPos; + } + + // get the position in the wrong list + nInsertPos = pNode->GetWrong()->GetWrongPos( nBegin ); + + // sometimes we have to skip one entry + if( nInsertPos < pNode->GetWrong()->Count() && + nBegin == pNode->GetWrong()->Pos( nInsertPos ) + + pNode->GetWrong()->Len( nInsertPos ) ) + nInsertPos++; + } + + bool bFresh = nBegin < nEnd; + bool bPending(false); + + if( bFresh ) + { + uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() ); + SwDoc* pDoc = pNode->GetDoc(); + + SwScanner aScanner( *pNode, pNode->GetText(), nullptr, ModelToViewHelper(), + WordType::DICTIONARY_WORD, nBegin, nEnd); + + while( aScanner.NextWord() ) + { + const OUString& rWord = aScanner.GetWord(); + nBegin = aScanner.GetBegin(); + sal_Int32 nLen = aScanner.GetLen(); + + // get next language for next word, consider language attributes + // within the word + LanguageType eActLang = aScanner.GetCurrentLanguage(); + DetectAndMarkMissingDictionaries( pDoc, xSpell, eActLang ); + + bool bSpell = xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) ); + if( bSpell && !rWord.isEmpty() ) + { + // check for: bAlter => xHyphWord.is() + OSL_ENSURE(!bSpell || xSpell.is(), "NULL pointer"); + + if( !xSpell->isValid( rWord, static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) + { + sal_Int32 nSmartTagStt = nBegin; + sal_Int32 nDummy = 1; + if ( !pNode->GetSmartTags() || !pNode->GetSmartTags()->InWrongWord( nSmartTagStt, nDummy ) ) + { + if( !pNode->GetWrong() ) + { + pNode->SetWrong( new SwWrongList( WRONGLIST_SPELL ) ); + pNode->GetWrong()->SetInvalid( 0, nEnd ); + } + SwWrongList::FreshState const eState(pNode->GetWrong()->Fresh( + nChgStart, nChgEnd, nBegin, nLen, nInsertPos, nActPos)); + switch (eState) + { + case SwWrongList::FreshState::FRESH: + pNode->GetWrong()->Insert(OUString(), nullptr, nBegin, nLen, nInsertPos++); + break; + case SwWrongList::FreshState::CURSOR: + bPending = true; + [[fallthrough]]; // to mark as invalid + case SwWrongList::FreshState::NOTHING: + nInvStart = nBegin; + nInvEnd = nBegin + nLen; + break; + } + } + } + else if( bAddAutoCmpl && rACW.GetMinWordLen() <= rWord.getLength() ) + { + rACW.InsertWord( rWord, *pDoc ); + } + } + } + } + + // reset original text + // i63141 before calling GetCharRect(..) with formatting! + if ( bRestoreString ) + { + pNode->m_Text = aOldText; + } + if( pNode->GetWrong() ) + { + if( bFresh ) + pNode->GetWrong()->Fresh( nChgStart, nChgEnd, + nEnd, 0, nInsertPos, nActPos ); + + // Calculate repaint area: + + if( nChgStart < nChgEnd ) + { + aRect = lcl_CalculateRepaintRect(*this, rNode, nChgStart, nChgEnd); + + // fdo#71558 notify misspelled word to accessibility + SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pViewSh ) + pViewSh->InvalidateAccessibleParaAttrs( *this ); + } + + pNode->GetWrong()->SetInvalid( nInvStart, nInvEnd ); + pNode->SetWrongDirty( + (COMPLETE_STRING != pNode->GetWrong()->GetBeginInv()) + ? (bPending + ? SwTextNode::WrongState::PENDING + : SwTextNode::WrongState::TODO) + : SwTextNode::WrongState::DONE); + if( !pNode->GetWrong()->Count() && ! pNode->IsWrongDirty() ) + pNode->SetWrong( nullptr ); + } + else + pNode->SetWrongDirty(SwTextNode::WrongState::DONE); + + if( bAddAutoCmpl ) + pNode->SetAutoCompleteWordDirty( false ); + + return aRect; +} + +/** Function: SmartTagScan + + Function scans words in current text and checks them in the + smarttag libraries. If the check returns true to bounds of the + recognized words are stored into a list that is used later for drawing + the underline. + + @return SwRect Repaint area +*/ +SwRect SwTextFrame::SmartTagScan(SwTextNode & rNode) +{ + SwRect aRet; + + assert(sw::FrameContainsNode(*this, rNode.GetIndex())); + SwTextNode *const pNode = &rNode; + const OUString& rText = pNode->GetText(); + + // Iterate over language portions + SmartTagMgr& rSmartTagMgr = SwSmartTagMgr::Get(); + + SwWrongList* pSmartTagList = pNode->GetSmartTags(); + + sal_Int32 nBegin = 0; + sal_Int32 nEnd = rText.getLength(); + + if ( pSmartTagList ) + { + if ( pSmartTagList->GetBeginInv() != COMPLETE_STRING ) + { + nBegin = pSmartTagList->GetBeginInv(); + nEnd = std::min( pSmartTagList->GetEndInv(), rText.getLength() ); + + if ( nBegin < nEnd ) + { + const LanguageType aCurrLang = pNode->GetLang( nBegin ); + const css::lang::Locale aCurrLocale = g_pBreakIt->GetLocale( aCurrLang ); + nBegin = g_pBreakIt->GetBreakIter()->beginOfSentence( rText, nBegin, aCurrLocale ); + nEnd = g_pBreakIt->GetBreakIter()->endOfSentence(rText, nEnd, aCurrLocale); + if (nEnd > rText.getLength() || nEnd < 0) + nEnd = rText.getLength(); + } + } + } + + const sal_uInt16 nNumberOfEntries = pSmartTagList ? pSmartTagList->Count() : 0; + sal_uInt16 nNumberOfRemovedEntries = 0; + sal_uInt16 nNumberOfInsertedEntries = 0; + + // clear smart tag list between nBegin and nEnd: + if ( 0 != nNumberOfEntries ) + { + sal_Int32 nChgStart = COMPLETE_STRING; + sal_Int32 nChgEnd = 0; + const sal_uInt16 nCurrentIndex = pSmartTagList->GetWrongPos( nBegin ); + pSmartTagList->Fresh( nChgStart, nChgEnd, nBegin, nEnd - nBegin, nCurrentIndex, COMPLETE_STRING ); + nNumberOfRemovedEntries = nNumberOfEntries - pSmartTagList->Count(); + } + + if ( nBegin < nEnd ) + { + // Expand the string: + const ModelToViewHelper aConversionMap(*pNode, getRootFrame() /*TODO - replace or expand fields for smart tags?*/); + const OUString& aExpandText = aConversionMap.getViewText(); + + // Ownership ov ConversionMap is passed to SwXTextMarkup object! + uno::Reference<text::XTextMarkup> const xTextMarkup = + new SwXTextMarkup(pNode, aConversionMap); + + css::uno::Reference< css::frame::XController > xController = pNode->GetDoc()->GetDocShell()->GetController(); + + SwPosition start(*pNode, nBegin); + SwPosition end (*pNode, nEnd); + Reference< css::text::XTextRange > xRange = SwXTextRange::CreateXTextRange(*pNode->GetDoc(), start, &end); + + rSmartTagMgr.RecognizeTextRange(xRange, xTextMarkup, xController); + + sal_Int32 nLangBegin = nBegin; + sal_Int32 nLangEnd; + + // smart tag recognition has to be done for each language portion: + SwLanguageIterator aIter( *pNode, nLangBegin ); + + do + { + const LanguageType nLang = aIter.GetLanguage(); + const css::lang::Locale aLocale = g_pBreakIt->GetLocale( nLang ); + nLangEnd = std::min<sal_Int32>( nEnd, aIter.GetChgPos() ); + + const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nLangBegin ); + const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nLangEnd ); + + rSmartTagMgr.RecognizeString(aExpandText, xTextMarkup, xController, aLocale, nExpandBegin, nExpandEnd - nExpandBegin ); + + nLangBegin = nLangEnd; + } + while ( aIter.Next() && nLangEnd < nEnd ); + + pSmartTagList = pNode->GetSmartTags(); + + const sal_uInt16 nNumberOfEntriesAfterRecognize = pSmartTagList ? pSmartTagList->Count() : 0; + nNumberOfInsertedEntries = nNumberOfEntriesAfterRecognize - ( nNumberOfEntries - nNumberOfRemovedEntries ); + } + + if( pSmartTagList ) + { + // Update WrongList stuff + pSmartTagList->SetInvalid( COMPLETE_STRING, 0 ); + pNode->SetSmartTagDirty( COMPLETE_STRING != pSmartTagList->GetBeginInv() ); + + if( !pSmartTagList->Count() && !pNode->IsSmartTagDirty() ) + pNode->SetSmartTags( nullptr ); + + // Calculate repaint area: + if ( nBegin < nEnd && ( 0 != nNumberOfRemovedEntries || + 0 != nNumberOfInsertedEntries ) ) + { + aRet = lcl_CalculateRepaintRect(*this, rNode, nBegin, nEnd); + } + } + else + pNode->SetSmartTagDirty( false ); + + return aRet; +} + +void SwTextFrame::CollectAutoCmplWrds(SwTextNode & rNode, sal_Int32 nActPos) +{ + assert(sw::FrameContainsNode(*this, rNode.GetIndex())); (void) this; + SwTextNode *const pNode(&rNode); + if (!nActPos) + nActPos = COMPLETE_STRING; + + SwDoc* pDoc = pNode->GetDoc(); + SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords(); + + sal_Int32 nBegin = 0; + sal_Int32 nEnd = pNode->GetText().getLength(); + sal_Int32 nLen; + bool bACWDirty = false; + + if( nBegin < nEnd ) + { + int nCnt = 200; + SwScanner aScanner( *pNode, pNode->GetText(), nullptr, ModelToViewHelper(), + WordType::DICTIONARY_WORD, nBegin, nEnd ); + while( aScanner.NextWord() ) + { + nBegin = aScanner.GetBegin(); + nLen = aScanner.GetLen(); + if( rACW.GetMinWordLen() <= nLen ) + { + const OUString& rWord = aScanner.GetWord(); + + if( nActPos < nBegin || ( nBegin + nLen ) < nActPos ) + { + if( rACW.GetMinWordLen() <= rWord.getLength() ) + rACW.InsertWord( rWord, *pDoc ); + } + else + bACWDirty = true; + } + if( !--nCnt ) + { + // don't wait for TIMER here, so we can finish big paragraphs + if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) + return; + nCnt = 100; + } + } + } + + if (!bACWDirty) + pNode->SetAutoCompleteWordDirty( false ); +} + +SwInterHyphInfoTextFrame::SwInterHyphInfoTextFrame( + SwTextFrame const& rFrame, SwTextNode const& rNode, + SwInterHyphInfo const& rHyphInfo) + : m_nStart(rFrame.MapModelToView(&rNode, rHyphInfo.m_nStart)) + , m_nEnd(rFrame.MapModelToView(&rNode, rHyphInfo.m_nEnd)) + , m_nWordStart(0) + , m_nWordLen(0) +{ +} + +void SwInterHyphInfoTextFrame::UpdateTextNodeHyphInfo(SwTextFrame const& rFrame, + SwTextNode const& rNode, SwInterHyphInfo & o_rHyphInfo) +{ + std::pair<SwTextNode const*, sal_Int32> const wordStart(rFrame.MapViewToModel(m_nWordStart)); + std::pair<SwTextNode const*, sal_Int32> const wordEnd(rFrame.MapViewToModel(m_nWordStart+m_nWordLen)); + if (wordStart.first != &rNode || wordEnd.first != &rNode) + { // not sure if this can happen since nStart/nEnd are in rNode + SAL_WARN("sw.core", "UpdateTextNodeHyphInfo: outside of node"); + return; + } + o_rHyphInfo.m_nWordStart = wordStart.second; + o_rHyphInfo.m_nWordLen = wordEnd.second - wordStart.second; + o_rHyphInfo.SetHyphWord(m_xHyphWord); +} + +/// Find the SwTextFrame and call its Hyphenate +bool SwTextNode::Hyphenate( SwInterHyphInfo &rHyphInf ) +{ + // shortcut: paragraph doesn't have a language set: + if ( LANGUAGE_NONE == GetSwAttrSet().GetLanguage().GetLanguage() + && LanguageType(USHRT_MAX) == GetLang(0, m_Text.getLength())) + { + return false; + } + + SwTextFrame *pFrame = ::sw::SwHyphIterCacheLastTextFrame(this, + [&rHyphInf, this]() { + std::pair<Point, bool> tmp; + Point const*const pPoint = rHyphInf.GetCursorPos(); + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = true; + } + return static_cast<SwTextFrame*>(this->getLayoutFrame( + this->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, pPoint ? &tmp : nullptr)); + }); + if (!pFrame) + { + // There was a comment here that claimed that the following assertion + // shouldn't exist as it's triggered by "Trennung ueber Sonderbereiche", + // (hyphenation across special sections?), whatever that means. + OSL_ENSURE( pFrame, "!SwTextNode::Hyphenate: can't find any frame" ); + return false; + } + SwInterHyphInfoTextFrame aHyphInfo(*pFrame, *this, rHyphInf); + + pFrame = &(pFrame->GetFrameAtOfst( aHyphInfo.m_nStart )); + + while( pFrame ) + { + if (pFrame->Hyphenate(aHyphInfo)) + { + // The layout is not robust wrt. "direct formatting" + // cf. layact.cxx, SwLayAction::TurboAction_(), if( !pCnt->IsValid() ... + pFrame->SetCompletePaint(); + aHyphInfo.UpdateTextNodeHyphInfo(*pFrame, *this, rHyphInf); + return true; + } + pFrame = pFrame->GetFollow(); + if( pFrame ) + { + aHyphInfo.m_nEnd = aHyphInfo.m_nEnd - (pFrame->GetOffset() - aHyphInfo.m_nStart); + aHyphInfo.m_nStart = pFrame->GetOffset(); + } + } + return false; +} + +namespace +{ + struct swTransliterationChgData + { + sal_Int32 nStart; + sal_Int32 nLen; + OUString sChanged; + Sequence< sal_Int32 > aOffsets; + }; +} + +// change text to Upper/Lower/Hiragana/Katakana/... +void SwTextNode::TransliterateText( + utl::TransliterationWrapper& rTrans, + sal_Int32 nStt, sal_Int32 nEnd, + SwUndoTransliterate* pUndo ) +{ + if (nStt < nEnd) + { + // since we don't use Hiragana/Katakana or half-width/full-width transliterations here + // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will + // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES + // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the + // proper thing to do. + const sal_Int16 nWordType = WordType::ANYWORD_IGNOREWHITESPACES; + + // In order to have less trouble with changing text size, e.g. because + // of ligatures or German small sz being resolved, we need to process + // the text replacements from end to start. + // This way the offsets for the yet to be changed words will be + // left unchanged by the already replaced text. + // For this we temporarily save the changes to be done in this vector + std::vector< swTransliterationChgData > aChanges; + swTransliterationChgData aChgData; + + if (rTrans.getType() == TransliterationFlags::TITLE_CASE) + { + // for 'capitalize every word' we need to iterate over each word + + Boundary aSttBndry; + Boundary aEndBndry; + aSttBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + GetText(), nStt, + g_pBreakIt->GetLocale( GetLang( nStt ) ), + nWordType, + true /*prefer forward direction*/); + aEndBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + GetText(), nEnd, + g_pBreakIt->GetLocale( GetLang( nEnd ) ), + nWordType, + false /*prefer backward direction*/); + + // prevent backtracking to the previous word if selection is at word boundary + if (aSttBndry.endPos <= nStt) + { + aSttBndry = g_pBreakIt->GetBreakIter()->nextWord( + GetText(), aSttBndry.endPos, + g_pBreakIt->GetLocale( GetLang( aSttBndry.endPos ) ), + nWordType); + } + // prevent advancing to the next word if selection is at word boundary + if (aEndBndry.startPos >= nEnd) + { + aEndBndry = g_pBreakIt->GetBreakIter()->previousWord( + GetText(), aEndBndry.startPos, + g_pBreakIt->GetLocale( GetLang( aEndBndry.startPos ) ), + nWordType); + } + + Boundary aCurWordBndry( aSttBndry ); + while (aCurWordBndry.startPos <= aEndBndry.startPos) + { + nStt = aCurWordBndry.startPos; + nEnd = aCurWordBndry.endPos; + const sal_Int32 nLen = nEnd - nStt; + OSL_ENSURE( nLen > 0, "invalid word length of 0" ); + + Sequence <sal_Int32> aOffsets; + OUString const sChgd( rTrans.transliterate( + GetText(), GetLang(nStt), nStt, nLen, &aOffsets) ); + + assert(nStt < m_Text.getLength()); + if (0 != rtl_ustr_shortenedCompare_WithLength( + m_Text.getStr() + nStt, m_Text.getLength() - nStt, + sChgd.getStr(), sChgd.getLength(), nLen)) + { + aChgData.nStart = nStt; + aChgData.nLen = nLen; + aChgData.sChanged = sChgd; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + aCurWordBndry = g_pBreakIt->GetBreakIter()->nextWord( + GetText(), nStt, + g_pBreakIt->GetLocale(GetLang(nStt, 1)), + nWordType); + } + } + else if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) + { + // for 'sentence case' we need to iterate sentence by sentence + + sal_Int32 nLastStart = g_pBreakIt->GetBreakIter()->beginOfSentence( + GetText(), nEnd, + g_pBreakIt->GetLocale( GetLang( nEnd ) ) ); + sal_Int32 nLastEnd = g_pBreakIt->GetBreakIter()->endOfSentence( + GetText(), nLastStart, + g_pBreakIt->GetLocale( GetLang( nLastStart ) ) ); + + // extend nStt, nEnd to the current sentence boundaries + sal_Int32 nCurrentStart = g_pBreakIt->GetBreakIter()->beginOfSentence( + GetText(), nStt, + g_pBreakIt->GetLocale( GetLang( nStt ) ) ); + sal_Int32 nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence( + GetText(), nCurrentStart, + g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) ); + + // prevent backtracking to the previous sentence if selection starts at end of a sentence + if (nCurrentEnd <= nStt) + { + // now nCurrentStart is probably located on a non-letter word. (unless we + // are in Asian text with no spaces...) + // Thus to get the real sentence start we should locate the next real word, + // that is one found by DICTIONARY_WORD + i18n::Boundary aBndry = g_pBreakIt->GetBreakIter()->nextWord( + GetText(), nCurrentEnd, + g_pBreakIt->GetLocale( GetLang( nCurrentEnd ) ), + i18n::WordType::DICTIONARY_WORD); + + // now get new current sentence boundaries + nCurrentStart = g_pBreakIt->GetBreakIter()->beginOfSentence( + GetText(), aBndry.startPos, + g_pBreakIt->GetLocale( GetLang( aBndry.startPos) ) ); + nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence( + GetText(), nCurrentStart, + g_pBreakIt->GetLocale( GetLang( nCurrentStart) ) ); + } + // prevent advancing to the next sentence if selection ends at start of a sentence + if (nLastStart >= nEnd) + { + // now nCurrentStart is probably located on a non-letter word. (unless we + // are in Asian text with no spaces...) + // Thus to get the real sentence start we should locate the previous real word, + // that is one found by DICTIONARY_WORD + i18n::Boundary aBndry = g_pBreakIt->GetBreakIter()->previousWord( + GetText(), nLastStart, + g_pBreakIt->GetLocale( GetLang( nLastStart) ), + i18n::WordType::DICTIONARY_WORD); + nLastEnd = g_pBreakIt->GetBreakIter()->endOfSentence( + GetText(), aBndry.startPos, + g_pBreakIt->GetLocale( GetLang( aBndry.startPos) ) ); + if (nCurrentEnd > nLastEnd) + nCurrentEnd = nLastEnd; + } + + while (nCurrentStart < nLastEnd) + { + sal_Int32 nLen = nCurrentEnd - nCurrentStart; + OSL_ENSURE( nLen > 0, "invalid word length of 0" ); + + Sequence <sal_Int32> aOffsets; + OUString const sChgd( rTrans.transliterate(GetText(), + GetLang(nCurrentStart), nCurrentStart, nLen, &aOffsets) ); + + assert(nStt < m_Text.getLength()); + if (0 != rtl_ustr_shortenedCompare_WithLength( + m_Text.getStr() + nStt, m_Text.getLength() - nStt, + sChgd.getStr(), sChgd.getLength(), nLen)) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.sChanged = sChgd; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + Boundary aFirstWordBndry = g_pBreakIt->GetBreakIter()->nextWord( + GetText(), nCurrentEnd, + g_pBreakIt->GetLocale( GetLang( nCurrentEnd ) ), + nWordType); + nCurrentStart = aFirstWordBndry.startPos; + nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence( + GetText(), nCurrentStart, + g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) ); + } + } + else + { + // here we may transliterate over complete language portions... + + std::unique_ptr<SwLanguageIterator> pIter; + if( rTrans.needLanguageForTheMode() ) + pIter.reset(new SwLanguageIterator( *this, nStt )); + + sal_Int32 nEndPos = 0; + LanguageType nLang = LANGUAGE_NONE; + do { + if( pIter ) + { + nLang = pIter->GetLanguage(); + nEndPos = pIter->GetChgPos(); + if( nEndPos > nEnd ) + nEndPos = nEnd; + } + else + { + nLang = LANGUAGE_SYSTEM; + nEndPos = nEnd; + } + const sal_Int32 nLen = nEndPos - nStt; + + Sequence <sal_Int32> aOffsets; + OUString const sChgd( rTrans.transliterate( + m_Text, nLang, nStt, nLen, &aOffsets) ); + + assert(nStt < m_Text.getLength()); + if (0 != rtl_ustr_shortenedCompare_WithLength( + m_Text.getStr() + nStt, m_Text.getLength() - nStt, + sChgd.getStr(), sChgd.getLength(), nLen)) + { + aChgData.nStart = nStt; + aChgData.nLen = nLen; + aChgData.sChanged = sChgd; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + nStt = nEndPos; + } while( nEndPos < nEnd && pIter && pIter->Next() ); + } + + if (!aChanges.empty()) + { + // now apply the changes from end to start to leave the offsets of the + // yet unchanged text parts remain the same. + size_t nSum(0); + for (size_t i = 0; i < aChanges.size(); ++i) + { // check this here since AddChanges cannot be moved below + // call to ReplaceTextOnly + swTransliterationChgData & rData = + aChanges[ aChanges.size() - 1 - i ]; + nSum += rData.sChanged.getLength() - rData.nLen; + if (nSum > o3tl::make_unsigned(GetSpaceLeft())) + { + SAL_WARN("sw.core", "SwTextNode::ReplaceTextOnly: " + "node text with insertion > node capacity."); + return; + } + if (pUndo) + pUndo->AddChanges( *this, rData.nStart, rData.nLen, rData.aOffsets ); + ReplaceTextOnly( rData.nStart, rData.nLen, rData.sChanged, rData.aOffsets ); + } + } + } +} + +void SwTextNode::ReplaceTextOnly( sal_Int32 nPos, sal_Int32 nLen, + const OUString & rText, + const Sequence<sal_Int32>& rOffsets ) +{ + assert(rText.getLength() - nLen <= GetSpaceLeft()); + + m_Text = m_Text.replaceAt(nPos, nLen, rText); + + sal_Int32 nTLen = rText.getLength(); + const sal_Int32* pOffsets = rOffsets.getConstArray(); + // now look for no 1-1 mapping -> move the indices! + sal_Int32 nMyOff = nPos; + for( sal_Int32 nI = 0; nI < nTLen; ++nI ) + { + const sal_Int32 nOff = pOffsets[ nI ]; + if( nOff < nMyOff ) + { + // something is inserted + sal_Int32 nCnt = 1; + while( nI + nCnt < nTLen && nOff == pOffsets[ nI + nCnt ] ) + ++nCnt; + + Update( SwIndex( this, nMyOff ), nCnt ); + nMyOff = nOff; + //nMyOff -= nCnt; + nI += nCnt - 1; + } + else if( nOff > nMyOff ) + { + // something is deleted + Update( SwIndex( this, nMyOff+1 ), nOff - nMyOff, true ); + nMyOff = nOff; + } + ++nMyOff; + } + if( nMyOff < nLen ) + // something is deleted at the end + Update( SwIndex( this, nMyOff ), nLen - nMyOff, true ); + + // notify the layout! + SwDelText aDelHint( nPos, nTLen ); + NotifyClients( nullptr, &aDelHint ); + + SwInsText aHint( nPos, nTLen ); + NotifyClients( nullptr, &aHint ); +} + +// the return values allows us to see if we did the heavy- +// lifting required to actually break and count the words. +bool SwTextNode::CountWords( SwDocStat& rStat, + sal_Int32 nStt, sal_Int32 nEnd ) const +{ + if( nStt > nEnd ) + { // bad call + return false; + } + if (IsInRedlines()) + { //not counting txtnodes used to hold deleted redline content + return false; + } + bool bCountAll = ( (0 == nStt) && (GetText().getLength() == nEnd) ); + ++rStat.nAllPara; // #i93174#: count _all_ paragraphs + if ( IsHidden() ) + { // not counting hidden paras + return false; + } + // count words in numbering string if started at beginning of para: + bool bCountNumbering = nStt == 0; + bool bHasBullet = false, bHasNumbering = false; + OUString sNumString; + if (bCountNumbering) + { + sNumString = GetNumString(); + bHasNumbering = !sNumString.isEmpty(); + if (!bHasNumbering) + bHasBullet = HasBullet(); + bCountNumbering = bHasNumbering || bHasBullet; + } + + if( nStt == nEnd && !bCountNumbering) + { // unnumbered empty node or empty selection + return false; + } + + // count of non-empty paras + ++rStat.nPara; + + // Shortcut when counting whole paragraph and current count is clean + if ( bCountAll && !IsWordCountDirty() ) + { + // accumulate into DocStat record to return the values + if (m_pParaIdleData_Impl) + { + rStat.nWord += m_pParaIdleData_Impl->nNumberOfWords; + rStat.nAsianWord += m_pParaIdleData_Impl->nNumberOfAsianWords; + rStat.nChar += m_pParaIdleData_Impl->nNumberOfChars; + rStat.nCharExcludingSpaces += m_pParaIdleData_Impl->nNumberOfCharsExcludingSpaces; + } + return false; + } + + // ConversionMap to expand fields, remove invisible and redline deleted text for scanner + const ModelToViewHelper aConversionMap(*this, + getIDocumentLayoutAccess().GetCurrentLayout(), + ExpandMode::ExpandFields | ExpandMode::ExpandFootnote | ExpandMode::HideInvisible | ExpandMode::HideDeletions); + const OUString& aExpandText = aConversionMap.getViewText(); + + if (aExpandText.isEmpty() && !bCountNumbering) + { + return false; + } + + // map start and end points onto the ConversionMap + const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nStt ); + const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nEnd ); + + //do the count + // all counts exclude hidden paras and hidden+redlined within para + // definition of space/white chars in SwScanner (and BreakIter!) + // uses both u_isspace and BreakIter getWordBoundary in SwScanner + sal_uInt32 nTmpWords = 0; // count of all words + sal_uInt32 nTmpAsianWords = 0; //count of all Asian codepoints + sal_uInt32 nTmpChars = 0; // count of all chars + sal_uInt32 nTmpCharsExcludingSpaces = 0; // all non-white chars + + // count words in masked and expanded text: + if (!aExpandText.isEmpty()) + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + + // zero is NULL for pLanguage -----------v last param = true for clipping + SwScanner aScanner( *this, aExpandText, nullptr, aConversionMap, i18n::WordType::WORD_COUNT, + nExpandBegin, nExpandEnd, true ); + + // used to filter out scanner returning almost empty strings (len=1; unichar=0x0001) + const OUString aBreakWord( CH_TXTATR_BREAKWORD ); + + while ( aScanner.NextWord() ) + { + if( !aExpandText.match(aBreakWord, aScanner.GetBegin() )) + { + ++nTmpWords; + const OUString &rWord = aScanner.GetWord(); + if (g_pBreakIt->GetBreakIter()->getScriptType(rWord, 0) == i18n::ScriptType::ASIAN) + ++nTmpAsianWords; + nTmpCharsExcludingSpaces += g_pBreakIt->getGraphemeCount(rWord); + } + } + + nTmpCharsExcludingSpaces += aScanner.getOverriddenDashCount(); + + nTmpChars = g_pBreakIt->getGraphemeCount(aExpandText, nExpandBegin, nExpandEnd); + } + + // no nTmpCharsExcludingSpaces adjust needed neither for blanked out MaskedChars + // nor for mid-word selection - set scanner bClip = true at creation + + // count outline number label - ? no expansion into map + // always counts all of number-ish label + if (bHasNumbering) // count words in numbering string + { + LanguageType aLanguage = GetLang( 0 ); + + SwScanner aScanner( *this, sNumString, &aLanguage, ModelToViewHelper(), + i18n::WordType::WORD_COUNT, 0, sNumString.getLength(), true ); + + while ( aScanner.NextWord() ) + { + ++nTmpWords; + const OUString &rWord = aScanner.GetWord(); + if (g_pBreakIt->GetBreakIter()->getScriptType(rWord, 0) == i18n::ScriptType::ASIAN) + ++nTmpAsianWords; + nTmpCharsExcludingSpaces += g_pBreakIt->getGraphemeCount(rWord); + } + + nTmpCharsExcludingSpaces += aScanner.getOverriddenDashCount(); + nTmpChars += g_pBreakIt->getGraphemeCount(sNumString); + } + else if ( bHasBullet ) + { + ++nTmpWords; + ++nTmpChars; + ++nTmpCharsExcludingSpaces; + } + + // If counting the whole para then update cached values and mark clean + if ( bCountAll ) + { + if ( m_pParaIdleData_Impl ) + { + m_pParaIdleData_Impl->nNumberOfWords = nTmpWords; + m_pParaIdleData_Impl->nNumberOfAsianWords = nTmpAsianWords; + m_pParaIdleData_Impl->nNumberOfChars = nTmpChars; + m_pParaIdleData_Impl->nNumberOfCharsExcludingSpaces = nTmpCharsExcludingSpaces; + } + SetWordCountDirty( false ); + } + // accumulate into DocStat record to return the values + rStat.nWord += nTmpWords; + rStat.nAsianWord += nTmpAsianWords; + rStat.nChar += nTmpChars; + rStat.nCharExcludingSpaces += nTmpCharsExcludingSpaces; + + return true; +} + +// Paragraph statistics start --> + +void SwTextNode::InitSwParaStatistics( bool bNew ) +{ + if ( bNew ) + { + m_pParaIdleData_Impl = new SwParaIdleData_Impl; + } + else if ( m_pParaIdleData_Impl ) + { + delete m_pParaIdleData_Impl->pWrong; + delete m_pParaIdleData_Impl->pGrammarCheck; + delete m_pParaIdleData_Impl->pSmartTags; + delete m_pParaIdleData_Impl; + m_pParaIdleData_Impl = nullptr; + } +} + +void SwTextNode::SetWrong( SwWrongList* pNew, bool bDelete ) +{ + if ( m_pParaIdleData_Impl ) + { + if ( bDelete ) + { + delete m_pParaIdleData_Impl->pWrong; + } + m_pParaIdleData_Impl->pWrong = pNew; + } +} + +SwWrongList* SwTextNode::GetWrong() +{ + return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pWrong : nullptr; +} + +// #i71360# +const SwWrongList* SwTextNode::GetWrong() const +{ + return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pWrong : nullptr; +} + +void SwTextNode::SetGrammarCheck( SwGrammarMarkUp* pNew, bool bDelete ) +{ + if ( m_pParaIdleData_Impl ) + { + if ( bDelete ) + { + delete m_pParaIdleData_Impl->pGrammarCheck; + } + m_pParaIdleData_Impl->pGrammarCheck = pNew; + } +} + +SwGrammarMarkUp* SwTextNode::GetGrammarCheck() +{ + return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pGrammarCheck : nullptr; +} + +SwWrongList const* SwTextNode::GetGrammarCheck() const +{ + return static_cast<SwWrongList const*>(const_cast<SwTextNode*>(this)->GetGrammarCheck()); +} + +void SwTextNode::SetSmartTags( SwWrongList* pNew, bool bDelete ) +{ + OSL_ENSURE( !pNew || SwSmartTagMgr::Get().IsSmartTagsEnabled(), + "Weird - we have a smart tag list without any recognizers?" ); + + if ( m_pParaIdleData_Impl ) + { + if ( bDelete ) + { + delete m_pParaIdleData_Impl->pSmartTags; + } + m_pParaIdleData_Impl->pSmartTags = pNew; + } +} + +SwWrongList* SwTextNode::GetSmartTags() +{ + return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pSmartTags : nullptr; +} + +SwWrongList const* SwTextNode::GetSmartTags() const +{ + return const_cast<SwWrongList const*>(const_cast<SwTextNode*>(this)->GetSmartTags()); +} + +void SwTextNode::SetWordCountDirty( bool bNew ) const +{ + if ( m_pParaIdleData_Impl ) + { + m_pParaIdleData_Impl->bWordCountDirty = bNew; + } +} + +bool SwTextNode::IsWordCountDirty() const +{ + return m_pParaIdleData_Impl && m_pParaIdleData_Impl->bWordCountDirty; +} + +void SwTextNode::SetWrongDirty(WrongState eNew) const +{ + if ( m_pParaIdleData_Impl ) + { + m_pParaIdleData_Impl->eWrongDirty = eNew; + } +} + +auto SwTextNode::GetWrongDirty() const -> WrongState +{ + return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->eWrongDirty : WrongState::DONE; +} + +bool SwTextNode::IsWrongDirty() const +{ + return m_pParaIdleData_Impl && m_pParaIdleData_Impl->eWrongDirty != WrongState::DONE; +} + +void SwTextNode::SetGrammarCheckDirty( bool bNew ) const +{ + if ( m_pParaIdleData_Impl ) + { + m_pParaIdleData_Impl->bGrammarCheckDirty = bNew; + } +} + +bool SwTextNode::IsGrammarCheckDirty() const +{ + return m_pParaIdleData_Impl && m_pParaIdleData_Impl->bGrammarCheckDirty; +} + +void SwTextNode::SetSmartTagDirty( bool bNew ) const +{ + if ( m_pParaIdleData_Impl ) + { + m_pParaIdleData_Impl->bSmartTagDirty = bNew; + } +} + +bool SwTextNode::IsSmartTagDirty() const +{ + return m_pParaIdleData_Impl && m_pParaIdleData_Impl->bSmartTagDirty; +} + +void SwTextNode::SetAutoCompleteWordDirty( bool bNew ) const +{ + if ( m_pParaIdleData_Impl ) + { + m_pParaIdleData_Impl->bAutoComplDirty = bNew; + } +} + +bool SwTextNode::IsAutoCompleteWordDirty() const +{ + return m_pParaIdleData_Impl && m_pParaIdleData_Impl->bAutoComplDirty; +} + +// <-- Paragraph statistics end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwRewriter.cxx b/sw/source/core/undo/SwRewriter.cxx new file mode 100644 index 000000000..05566a1bd --- /dev/null +++ b/sw/source/core/undo/SwRewriter.cxx @@ -0,0 +1,71 @@ +/* -*- 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 <algorithm> +#include <SwRewriter.hxx> + +using namespace std; + +SwRewriter::SwRewriter() +{ +} + +void SwRewriter::AddRule(SwUndoArg eWhat, const OUString & rWith) +{ + SwRewriteRule aRule(eWhat, rWith); + + vector<SwRewriteRule>::iterator aIt = find_if( + mRules.begin(), mRules.end(), + [&aRule](SwRewriteRule const & a) { return a.first == aRule.first; }); + + if (aIt != mRules.end()) + *aIt = aRule; + else + mRules.push_back(aRule); +} + +OUString SwRewriter::Apply(const OUString & rStr) const +{ + OUString aResult = rStr; + + for (const auto& rRule : mRules) + { + aResult = aResult.replaceAll(GetPlaceHolder(rRule.first), rRule.second); + } + + return aResult; +} + +OUString SwRewriter::GetPlaceHolder(SwUndoArg eId) +{ + switch (eId) + { + case UndoArg1: + return "$1"; + case UndoArg2: + return "$2"; + case UndoArg3: + return "$3"; + default: + break; + } + return "$1"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoField.cxx b/sw/source/core/undo/SwUndoField.cxx new file mode 100644 index 000000000..1eac1fa41 --- /dev/null +++ b/sw/source/core/undo/SwUndoField.cxx @@ -0,0 +1,150 @@ +/* -*- 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 <SwUndoField.hxx> +#include <swundo.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentFieldsManager.hxx> +#include <txtfld.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <docsh.hxx> +#include <pam.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star::uno; + +SwUndoField::SwUndoField(const SwPosition & rPos ) + : SwUndo(SwUndoId::FIELD, rPos.GetDoc()) +{ + nNodeIndex = rPos.nNode.GetIndex(); + nOffset = rPos.nContent.GetIndex(); + pDoc = rPos.GetDoc(); +} + +SwUndoField::~SwUndoField() +{ +} + +SwPosition SwUndoField::GetPosition() +{ + SwNode * pNode = pDoc->GetNodes()[nNodeIndex]; + SwNodeIndex aNodeIndex(*pNode); + SwIndex aIndex(pNode->GetContentNode(), nOffset); + SwPosition aResult(aNodeIndex, aIndex); + + return aResult; +} + +SwUndoFieldFromDoc::SwUndoFieldFromDoc(const SwPosition & rPos, + const SwField & rOldField, + const SwField & rNewField, + SwMsgPoolItem * _pHint, bool _bUpdate) + : SwUndoField(rPos) + , pOldField(rOldField.CopyField()) + , pNewField(rNewField.CopyField()) + , pHint(_pHint) + , bUpdate(_bUpdate) +{ + OSL_ENSURE(pOldField, "No old field!"); + OSL_ENSURE(pNewField, "No new field!"); + OSL_ENSURE(pDoc, "No document!"); +} + +SwUndoFieldFromDoc::~SwUndoFieldFromDoc() +{ +} + +void SwUndoFieldFromDoc::UndoImpl(::sw::UndoRedoContext &) +{ + SwTextField * pTextField = sw::DocumentFieldsManager::GetTextFieldAtPos(GetPosition()); + const SwField * pField = pTextField ? pTextField->GetFormatField().GetField() : nullptr; + + if (pField) + { + pDoc->getIDocumentFieldsAccess().UpdateField(pTextField, *pOldField, pHint, bUpdate); + } +} + +void SwUndoFieldFromDoc::DoImpl() +{ + SwTextField * pTextField = sw::DocumentFieldsManager::GetTextFieldAtPos(GetPosition()); + const SwField * pField = pTextField ? pTextField->GetFormatField().GetField() : nullptr; + + if (pField) + { + pDoc->getIDocumentFieldsAccess().UpdateField(pTextField, *pNewField, pHint, bUpdate); + SwFormatField* pDstFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField()); + + if (pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Postit, OUString(), false) == pDstFormatField->GetField()->GetTyp()) + pDoc->GetDocShell()->Broadcast( SwFormatFieldHint( pDstFormatField, SwFormatFieldHintWhich::INSERTED ) ); + } +} + +void SwUndoFieldFromDoc::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoFieldFromDoc::RepeatImpl(::sw::RepeatContext &) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + DoImpl(); +} + +SwUndoFieldFromAPI::SwUndoFieldFromAPI(const SwPosition & rPos, + const Any & rOldVal, const Any & rNewVal, + sal_uInt16 _nWhich) + : SwUndoField(rPos), aOldVal(rOldVal), aNewVal(rNewVal), nWhich(_nWhich) +{ +} + +SwUndoFieldFromAPI::~SwUndoFieldFromAPI() +{ +} + +void SwUndoFieldFromAPI::UndoImpl(::sw::UndoRedoContext &) +{ + SwField * pField = sw::DocumentFieldsManager::GetFieldAtPos(GetPosition()); + + if (pField) + pField->PutValue(aOldVal, nWhich); +} + +void SwUndoFieldFromAPI::DoImpl() +{ + SwField * pField = sw::DocumentFieldsManager::GetFieldAtPos(GetPosition()); + + if (pField) + pField->PutValue(aNewVal, nWhich); +} + +void SwUndoFieldFromAPI::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoFieldFromAPI::RepeatImpl(::sw::RepeatContext &) +{ + DoImpl(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoFmt.cxx b/sw/source/core/undo/SwUndoFmt.cxx new file mode 100644 index 000000000..d0e3798aa --- /dev/null +++ b/sw/source/core/undo/SwUndoFmt.cxx @@ -0,0 +1,466 @@ +/* -*- 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 <poolfmt.hxx> +#include <charfmt.hxx> +#include <frmfmt.hxx> +#include <SwUndoFmt.hxx> +#include <SwRewriter.hxx> +#include <swundo.hxx> +#include <undobj.hxx> +#include <fmtcol.hxx> +#include <doc.hxx> +#include <strings.hrc> + +SwUndoFormatCreate::SwUndoFormatCreate +(SwUndoId nUndoId, SwFormat * _pNew, SwFormat const * _pDerivedFrom, SwDoc * _pDoc) + : SwUndo(nUndoId, _pDoc), m_pNew(_pNew), + m_pDoc(_pDoc), m_pNewSet(nullptr), m_nId(0), m_bAuto(false) +{ + if (_pDerivedFrom) + m_sDerivedFrom = _pDerivedFrom->GetName(); +} + +SwUndoFormatCreate::~SwUndoFormatCreate() +{ +} + +void SwUndoFormatCreate::UndoImpl(::sw::UndoRedoContext &) +{ + if (m_pNew) + { + if (m_sNewName.isEmpty()) + m_sNewName = m_pNew->GetName(); + + if (!m_sNewName.isEmpty()) + m_pNew = Find(m_sNewName); + + if (m_pNew) + { + m_pNewSet = new SfxItemSet(m_pNew->GetAttrSet()); + m_nId = m_pNew->GetPoolFormatId() & COLL_GET_RANGE_BITS; + m_bAuto = m_pNew->IsAuto(); + + Delete(); + } + } +} + +void SwUndoFormatCreate::RedoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pDerivedFrom = Find(m_sDerivedFrom); + SwFormat * pFormat = Create(pDerivedFrom); + + if (pFormat && m_pNewSet) + { + pFormat->SetAuto(m_bAuto); + m_pDoc->ChgFormat(*pFormat, *m_pNewSet); + pFormat->SetPoolFormatId((pFormat->GetPoolFormatId() + & ~COLL_GET_RANGE_BITS) + | m_nId); + + m_pNew = pFormat; + } + else + m_pNew = nullptr; +} + +SwRewriter SwUndoFormatCreate::GetRewriter() const +{ + if (m_sNewName.isEmpty() && m_pNew) + m_sNewName = m_pNew->GetName(); + + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sNewName); + + return aRewriter; +} + +SwUndoFormatDelete::SwUndoFormatDelete +(SwUndoId nUndoId, SwFormat const * _pOld, SwDoc * _pDoc) + : SwUndo(nUndoId, _pDoc), + m_pDoc(_pDoc), m_sOldName(_pOld->GetName()), + m_aOldSet(_pOld->GetAttrSet()) +{ + m_sDerivedFrom = _pOld->DerivedFrom()->GetName(); + m_nId = _pOld->GetPoolFormatId() & COLL_GET_RANGE_BITS; + m_bAuto = _pOld->IsAuto(); +} + +SwUndoFormatDelete::~SwUndoFormatDelete() +{ +} + +void SwUndoFormatDelete::UndoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pDerivedFrom = Find(m_sDerivedFrom); + + SwFormat * pFormat = Create(pDerivedFrom); + + if (pFormat) + { + m_pDoc->ChgFormat(*pFormat, m_aOldSet); + pFormat->SetAuto(m_bAuto); + pFormat->SetPoolFormatId((pFormat->GetPoolFormatId() & + ~COLL_GET_RANGE_BITS) + | m_nId); + } +} + +void SwUndoFormatDelete::RedoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pOld = Find(m_sOldName); + + if (pOld) + { + Delete(pOld); + } +} + +SwRewriter SwUndoFormatDelete::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sOldName); + + return aRewriter; +} + +SwUndoRenameFormat::SwUndoRenameFormat(SwUndoId nUndoId, + const OUString & _sOldName, + const OUString & _sNewName, + SwDoc * _pDoc) + : SwUndo(nUndoId, _pDoc), m_sOldName(_sOldName), + m_sNewName(_sNewName), m_pDoc(_pDoc) +{ +} + +SwUndoRenameFormat::~SwUndoRenameFormat() +{ +} + +void SwUndoRenameFormat::UndoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pFormat = Find(m_sNewName); + + if (pFormat) + { + m_pDoc->RenameFormat(*pFormat, m_sOldName, true); + } +} + +void SwUndoRenameFormat::RedoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pFormat = Find(m_sOldName); + + if (pFormat) + { + m_pDoc->RenameFormat(*pFormat, m_sNewName, true); + } +} + +SwRewriter SwUndoRenameFormat::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sOldName); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, m_sNewName); + + return aRewriter; +} + +SwUndoTextFormatCollCreate::SwUndoTextFormatCollCreate +(SwTextFormatColl * _pNew, SwTextFormatColl const * _pDerivedFrom, SwDoc * _pDoc) + : SwUndoFormatCreate(SwUndoId::TXTFMTCOL_CREATE, _pNew, _pDerivedFrom, _pDoc) +{ +} + +SwFormat * SwUndoTextFormatCollCreate::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeTextFormatColl(m_sNewName, static_cast<SwTextFormatColl *>(pDerivedFrom), true); +} + +void SwUndoTextFormatCollCreate::Delete() +{ + m_pDoc->DelTextFormatColl(static_cast<SwTextFormatColl *>(m_pNew), true); +} + +SwFormat * SwUndoTextFormatCollCreate::Find(const OUString & rName) const +{ + return m_pDoc->FindTextFormatCollByName(rName); +} + +SwUndoTextFormatCollDelete::SwUndoTextFormatCollDelete(SwTextFormatColl const * _pOld, + SwDoc * _pDoc) + : SwUndoFormatDelete(SwUndoId::TXTFMTCOL_DELETE, _pOld, _pDoc) +{ +} + +SwFormat * SwUndoTextFormatCollDelete::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeTextFormatColl(m_sOldName, static_cast<SwTextFormatColl *>(pDerivedFrom), true); +} + +void SwUndoTextFormatCollDelete::Delete(SwFormat * pOld) +{ + m_pDoc->DelTextFormatColl(static_cast<SwTextFormatColl *>(pOld), true); +} + +SwFormat * SwUndoTextFormatCollDelete::Find(const OUString & rName) const +{ + return m_pDoc->FindTextFormatCollByName(rName); +} + +SwUndoCondTextFormatCollCreate::SwUndoCondTextFormatCollCreate(SwConditionTextFormatColl *_pNew, + SwTextFormatColl const *_pDerivedFrom, SwDoc *_pDoc) + : SwUndoTextFormatCollCreate(_pNew, _pDerivedFrom, _pDoc) +{ +} + +SwFormat * SwUndoCondTextFormatCollCreate::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeCondTextFormatColl(m_sNewName, static_cast<SwTextFormatColl *>(pDerivedFrom), true); +} + +SwUndoCondTextFormatCollDelete::SwUndoCondTextFormatCollDelete(SwTextFormatColl const * _pOld, + SwDoc * _pDoc) + : SwUndoTextFormatCollDelete(_pOld, _pDoc) +{ +} + +SwFormat * SwUndoCondTextFormatCollDelete::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeCondTextFormatColl(m_sOldName, static_cast<SwTextFormatColl *>(pDerivedFrom), true); +} + +SwUndoRenameFormatColl::SwUndoRenameFormatColl(const OUString & sInitOldName, + const OUString & sInitNewName, + SwDoc * _pDoc) + : SwUndoRenameFormat(SwUndoId::TXTFMTCOL_RENAME, sInitOldName, sInitNewName, _pDoc) +{ +} + +SwFormat * SwUndoRenameFormatColl::Find(const OUString & rName) const +{ + return m_pDoc->FindTextFormatCollByName(rName); +} + +SwUndoCharFormatCreate::SwUndoCharFormatCreate(SwCharFormat * pNewFormat, + SwCharFormat const * pDerivedFrom, + SwDoc * pDocument) + : SwUndoFormatCreate(SwUndoId::CHARFMT_CREATE, pNewFormat, pDerivedFrom, pDocument) +{ +} + +SwFormat * SwUndoCharFormatCreate::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeCharFormat(m_sNewName, static_cast<SwCharFormat *>(pDerivedFrom), true); +} + +void SwUndoCharFormatCreate::Delete() +{ + m_pDoc->DelCharFormat(static_cast<SwCharFormat *>(m_pNew), true); +} + +SwFormat * SwUndoCharFormatCreate::Find(const OUString & rName) const +{ + return m_pDoc->FindCharFormatByName(rName); +} + +SwUndoCharFormatDelete::SwUndoCharFormatDelete(SwCharFormat const * pOld, SwDoc * pDocument) + : SwUndoFormatDelete(SwUndoId::CHARFMT_DELETE, pOld, pDocument) +{ +} + +SwFormat * SwUndoCharFormatDelete::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeCharFormat(m_sOldName, static_cast<SwCharFormat *>(pDerivedFrom), true); +} + +void SwUndoCharFormatDelete::Delete(SwFormat * pFormat) +{ + m_pDoc->DelCharFormat(static_cast<SwCharFormat *>(pFormat), true); +} + +SwFormat * SwUndoCharFormatDelete::Find(const OUString & rName) const +{ + return m_pDoc->FindCharFormatByName(rName); +} + +SwUndoRenameCharFormat::SwUndoRenameCharFormat(const OUString & sInitOldName, + const OUString & sInitNewName, + SwDoc * pDocument) + : SwUndoRenameFormat(SwUndoId::CHARFMT_RENAME, sInitOldName, sInitNewName, pDocument) +{ +} + +SwFormat * SwUndoRenameCharFormat::Find(const OUString & rName) const +{ + return m_pDoc->FindCharFormatByName(rName); +} + +SwUndoFrameFormatCreate::SwUndoFrameFormatCreate(SwFrameFormat * pNewFormat, + SwFrameFormat const * pDerivedFrom, + SwDoc * pDocument) + : SwUndoFormatCreate(SwUndoId::FRMFMT_CREATE, pNewFormat, pDerivedFrom, pDocument) +{ +} + +SwFormat * SwUndoFrameFormatCreate::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeFrameFormat(m_sNewName, static_cast<SwFrameFormat *>(pDerivedFrom), true, m_pNew->IsAuto()); +} + +void SwUndoFrameFormatCreate::Delete() +{ + m_pDoc->DelFrameFormat(static_cast<SwFrameFormat *>(m_pNew), true); +} + +SwFormat * SwUndoFrameFormatCreate::Find(const OUString & rName) const +{ + return m_pDoc->FindFrameFormatByName(rName); +} + +SwUndoFrameFormatDelete::SwUndoFrameFormatDelete(SwFrameFormat const * pOld, SwDoc * pDocument) + : SwUndoFormatDelete(SwUndoId::FRMFMT_DELETE, pOld, pDocument) +{ +} + +SwFormat * SwUndoFrameFormatDelete::Create(SwFormat * pDerivedFrom) +{ + return m_pDoc->MakeFrameFormat(m_sOldName, static_cast<SwFrameFormat *>(pDerivedFrom), true); +} + +void SwUndoFrameFormatDelete::Delete(SwFormat * pFormat) +{ + m_pDoc->DelFrameFormat(static_cast<SwFrameFormat *>(pFormat), true); +} + +SwFormat * SwUndoFrameFormatDelete::Find(const OUString & rName) const +{ + return m_pDoc->FindFrameFormatByName(rName); +} + +SwUndoRenameFrameFormat::SwUndoRenameFrameFormat(const OUString & sInitOldName, + const OUString & sInitNewName, + SwDoc * pDocument) + : SwUndoRenameFormat(SwUndoId::FRMFMT_RENAME, sInitOldName, sInitNewName, pDocument) +{ +} + +SwFormat * SwUndoRenameFrameFormat::Find(const OUString & rName) const +{ + return m_pDoc->FindFrameFormatByName(rName); +} + +SwUndoNumruleCreate::SwUndoNumruleCreate(const SwNumRule * _pNew, + SwDoc * _pDoc) + : SwUndo(SwUndoId::NUMRULE_CREATE, _pDoc), m_pNew(_pNew), m_aNew(*_pNew), m_pDoc(_pDoc), + m_bInitialized(false) +{ +} + +void SwUndoNumruleCreate::UndoImpl(::sw::UndoRedoContext &) +{ + if (! m_bInitialized) + { + m_aNew = *m_pNew; + m_bInitialized = true; + } + + m_pDoc->DelNumRule(m_aNew.GetName(), true); +} + +void SwUndoNumruleCreate::RedoImpl(::sw::UndoRedoContext &) +{ + m_pDoc->MakeNumRule(m_aNew.GetName(), &m_aNew, true); +} + +SwRewriter SwUndoNumruleCreate::GetRewriter() const +{ + SwRewriter aResult; + + if (! m_bInitialized) + { + m_aNew = *m_pNew; + m_bInitialized = true; + } + + aResult.AddRule(UndoArg1, m_aNew.GetName()); + + return aResult; +} + +SwUndoNumruleDelete::SwUndoNumruleDelete(const SwNumRule & rRule, + SwDoc * _pDoc) + : SwUndo(SwUndoId::NUMRULE_DELETE, _pDoc), m_aOld(rRule), m_pDoc(_pDoc) +{ +} + +void SwUndoNumruleDelete::UndoImpl(::sw::UndoRedoContext &) +{ + m_pDoc->MakeNumRule(m_aOld.GetName(), &m_aOld, true); +} + +void SwUndoNumruleDelete::RedoImpl(::sw::UndoRedoContext &) +{ + m_pDoc->DelNumRule(m_aOld.GetName(), true); +} + +SwRewriter SwUndoNumruleDelete::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_aOld.GetName()); + + return aResult; +} + +SwUndoNumruleRename::SwUndoNumruleRename(const OUString & _aOldName, + const OUString & _aNewName, + SwDoc * _pDoc) + : SwUndo(SwUndoId::NUMRULE_RENAME, _pDoc), m_aOldName(_aOldName), m_aNewName(_aNewName), + m_pDoc(_pDoc) +{ +} + +void SwUndoNumruleRename::UndoImpl(::sw::UndoRedoContext &) +{ + m_pDoc->RenameNumRule(m_aNewName, m_aOldName, true); +} + +void SwUndoNumruleRename::RedoImpl(::sw::UndoRedoContext &) +{ + m_pDoc->RenameNumRule(m_aOldName, m_aNewName, true); +} + +SwRewriter SwUndoNumruleRename::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_aOldName); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, m_aNewName); + + return aRewriter; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoPageDesc.cxx b/sw/source/core/undo/SwUndoPageDesc.cxx new file mode 100644 index 000000000..1ca66f9fb --- /dev/null +++ b/sw/source/core/undo/SwUndoPageDesc.cxx @@ -0,0 +1,341 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <swundo.hxx> +#include <pagedesc.hxx> +#include <SwUndoPageDesc.hxx> +#include <SwRewriter.hxx> +#include <undobj.hxx> +#include <strings.hrc> +#include <fmtcntnt.hxx> +#include <fmthdft.hxx> +#include <osl/diagnose.h> + +SwUndoPageDesc::SwUndoPageDesc(const SwPageDesc & _aOld, + const SwPageDesc & _aNew, + SwDoc * _pDoc) + : SwUndo( _aOld.GetName() != _aNew.GetName() ? + SwUndoId::RENAME_PAGEDESC : + SwUndoId::CHANGE_PAGEDESC, + _pDoc ), + m_aOld(_aOld, _pDoc), m_aNew(_aNew, _pDoc), m_pDoc(_pDoc), m_bExchange( false ) +{ + OSL_ENSURE(nullptr != m_pDoc, "no document?"); + + /* + The page description changes. + If there are no header/footer content changes like header on/off or change from shared content + to unshared etc., there is no reason to duplicate the content nodes (Crash i55547) + But this happens, this Undo Ctor will destroy the unnecessary duplicate and manipulate the + content pointer of the both page descriptions. + */ + SwPageDesc &rOldDesc = m_aOld.m_PageDesc; + SwPageDesc &rNewDesc = m_aNew.m_PageDesc; + const SwFormatHeader& rOldHead = rOldDesc.GetMaster().GetHeader(); + const SwFormatHeader& rNewHead = rNewDesc.GetMaster().GetHeader(); + const SwFormatFooter& rOldFoot = rOldDesc.GetMaster().GetFooter(); + const SwFormatFooter& rNewFoot = rNewDesc.GetMaster().GetFooter(); + /* bExchange must not be set, if the old page descriptor will stay active. + Two known situations: + #i67735#: renaming a page descriptor + #i67334#: changing the follow style + If header/footer will be activated or deactivated, this undo will not work. + */ + m_bExchange = ( m_aOld.GetName() == m_aNew.GetName() ) && + ( _aOld.GetFollow() == _aNew.GetFollow() ) && + ( rOldHead.IsActive() == rNewHead.IsActive() ) && + ( rOldFoot.IsActive() == rNewFoot.IsActive() ); + if( rOldHead.IsActive() && ( rOldDesc.IsHeaderShared() != rNewDesc.IsHeaderShared() ) ) + m_bExchange = false; + if( rOldFoot.IsActive() && ( rOldDesc.IsFooterShared() != rNewDesc.IsFooterShared() ) ) + m_bExchange = false; + if( ( rOldHead.IsActive() || rOldFoot.IsActive() ) && ( rOldDesc.IsFirstShared() != rNewDesc.IsFirstShared() ) ) + m_bExchange = false; + if( m_bExchange ) + { + if( rNewHead.IsActive() ) + { + SwFrameFormat* pFormat = new SwFrameFormat( *rNewHead.GetHeaderFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatHeader aFormatHeader(pFormat); + (void)aFormatHeader; + if (!rNewDesc.IsHeaderShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetLeft().GetHeader().GetHeaderFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatHeader aLeftHeader(pFormat); + (void)aLeftHeader; + } + if (!rNewDesc.IsFirstShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetFirstMaster().GetHeader().GetHeaderFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatHeader aFirstHeader(pFormat); + (void)aFirstHeader; + } + } + // Same procedure for footers... + if( rNewFoot.IsActive() ) + { + SwFrameFormat* pFormat = new SwFrameFormat( *rNewFoot.GetFooterFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatFooter aFormatFooter(pFormat); + (void)aFormatFooter; + if (!rNewDesc.IsFooterShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetLeft().GetFooter().GetFooterFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatFooter aLeftFooter(pFormat); + (void)aLeftFooter; + } + if (!rNewDesc.IsFirstShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetFirstMaster().GetFooter().GetFooterFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatFooter aFirstFooter(pFormat); + (void)aFirstFooter; + } + } + + // After this exchange method the old page description will point to zero, + // the new one will point to the node position of the original content nodes. + ExchangeContentNodes( m_aOld.m_PageDesc, m_aNew.m_PageDesc ); + } +} + +SwUndoPageDesc::~SwUndoPageDesc() +{ +} + +void SwUndoPageDesc::ExchangeContentNodes( SwPageDesc& rSource, SwPageDesc &rDest ) +{ + OSL_ENSURE( m_bExchange, "You shouldn't do that." ); + const SwFormatHeader& rDestHead = rDest.GetMaster().GetHeader(); + const SwFormatHeader& rSourceHead = rSource.GetMaster().GetHeader(); + if( rDestHead.IsActive() ) + { + // Let the destination page description point to the source node position, + // from now on this descriptor is responsible for the content nodes! + const SfxPoolItem* pItem; + rDest.GetMaster().GetAttrSet().GetItemState( RES_HEADER, false, &pItem ); + std::unique_ptr<SfxPoolItem> pNewItem(pItem->Clone()); + SwFrameFormat* pNewFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + pNewFormat->SetFormatAttr( rSourceHead.GetHeaderFormat()->GetContent() ); + + // Let the source page description point to zero node position, + // it loses the responsible and can be destroyed without removing the content nodes. + rSource.GetMaster().GetAttrSet().GetItemState( RES_HEADER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + + if( !rDest.IsHeaderShared() ) + { + // Same procedure for unshared header... + const SwFormatHeader& rSourceLeftHead = rSource.GetLeft().GetHeader(); + rDest.GetLeft().GetAttrSet().GetItemState( RES_HEADER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + pNewFormat->SetFormatAttr( rSourceLeftHead.GetHeaderFormat()->GetContent() ); + rSource.GetLeft().GetAttrSet().GetItemState( RES_HEADER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + } + if (!rDest.IsFirstShared()) + { + // Same procedure for unshared header... + const SwFormatHeader& rSourceFirstMasterHead = rSource.GetFirstMaster().GetHeader(); + rDest.GetFirstMaster().GetAttrSet().GetItemState( RES_HEADER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + pNewFormat->SetFormatAttr( rSourceFirstMasterHead.GetHeaderFormat()->GetContent() ); + rSource.GetFirstMaster().GetAttrSet().GetItemState( RES_HEADER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + } + } + // Same procedure for footers... + const SwFormatFooter& rDestFoot = rDest.GetMaster().GetFooter(); + const SwFormatFooter& rSourceFoot = rSource.GetMaster().GetFooter(); + if( !rDestFoot.IsActive() ) + return; + + const SfxPoolItem* pItem; + rDest.GetMaster().GetAttrSet().GetItemState( RES_FOOTER, false, &pItem ); + std::unique_ptr<SfxPoolItem> pNewItem(pItem->Clone()); + SwFrameFormat *pNewFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + pNewFormat->SetFormatAttr( rSourceFoot.GetFooterFormat()->GetContent() ); + + rSource.GetMaster().GetAttrSet().GetItemState( RES_FOOTER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + + if( !rDest.IsFooterShared() ) + { + const SwFormatFooter& rSourceLeftFoot = rSource.GetLeft().GetFooter(); + rDest.GetLeft().GetAttrSet().GetItemState( RES_FOOTER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + pNewFormat->SetFormatAttr( rSourceLeftFoot.GetFooterFormat()->GetContent() ); + rSource.GetLeft().GetAttrSet().GetItemState( RES_FOOTER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + } + if (rDest.IsFirstShared()) + return; + + const SwFormatFooter& rSourceFirstMasterFoot = rSource.GetFirstMaster().GetFooter(); + rDest.GetFirstMaster().GetAttrSet().GetItemState( RES_FOOTER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + pNewFormat->SetFormatAttr( rSourceFirstMasterFoot.GetFooterFormat()->GetContent() ); + rSource.GetFirstMaster().GetAttrSet().GetItemState( RES_FOOTER, false, &pItem ); + pNewItem.reset(pItem->Clone()); + pNewFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); +} + +void SwUndoPageDesc::UndoImpl(::sw::UndoRedoContext &) +{ + // Move (header/footer)content node responsibility from new page descriptor to old one again. + if( m_bExchange ) + ExchangeContentNodes( m_aNew.m_PageDesc, m_aOld.m_PageDesc ); + m_pDoc->ChgPageDesc(m_aOld.GetName(), m_aOld); +} + +void SwUndoPageDesc::RedoImpl(::sw::UndoRedoContext &) +{ + // Move (header/footer)content node responsibility from old page descriptor to new one again. + if( m_bExchange ) + ExchangeContentNodes( m_aOld.m_PageDesc, m_aNew.m_PageDesc ); + m_pDoc->ChgPageDesc(m_aNew.GetName(), m_aNew); +} + +SwRewriter SwUndoPageDesc::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_aOld.GetName()); + aResult.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aResult.AddRule(UndoArg3, m_aNew.GetName()); + + return aResult; +} + +SwUndoPageDescCreate::SwUndoPageDescCreate(const SwPageDesc * pNew, + SwDoc * _pDoc) + : SwUndo(SwUndoId::CREATE_PAGEDESC, _pDoc), m_pDesc(pNew), m_aNew(*pNew, _pDoc), + m_pDoc(_pDoc) +{ + OSL_ENSURE(nullptr != m_pDoc, "no document?"); +} + +SwUndoPageDescCreate::~SwUndoPageDescCreate() +{ +} + +void SwUndoPageDescCreate::UndoImpl(::sw::UndoRedoContext &) +{ + if (m_pDesc) + { + m_aNew = *m_pDesc; + m_pDesc = nullptr; + } + + m_pDoc->DelPageDesc(m_aNew.GetName(), true); +} + +void SwUndoPageDescCreate::DoImpl() +{ + SwPageDesc aPageDesc = m_aNew; + m_pDoc->MakePageDesc(m_aNew.GetName(), &aPageDesc, false, true); +} + +void SwUndoPageDescCreate::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoPageDescCreate::RepeatImpl(::sw::RepeatContext &) +{ + ::sw::UndoGuard const undoGuard(m_pDoc->GetIDocumentUndoRedo()); + DoImpl(); +} + +SwRewriter SwUndoPageDescCreate::GetRewriter() const +{ + SwRewriter aResult; + + if (m_pDesc) + aResult.AddRule(UndoArg1, m_pDesc->GetName()); + else + aResult.AddRule(UndoArg1, m_aNew.GetName()); + + return aResult; +} + +SwUndoPageDescDelete::SwUndoPageDescDelete(const SwPageDesc & _aOld, + SwDoc * _pDoc) + : SwUndo(SwUndoId::DELETE_PAGEDESC, _pDoc), m_aOld(_aOld, _pDoc), m_pDoc(_pDoc) +{ + OSL_ENSURE(nullptr != m_pDoc, "no document?"); +} + +SwUndoPageDescDelete::~SwUndoPageDescDelete() +{ +} + +void SwUndoPageDescDelete::UndoImpl(::sw::UndoRedoContext &) +{ + SwPageDesc aPageDesc = m_aOld; + m_pDoc->MakePageDesc(m_aOld.GetName(), &aPageDesc, false, true); +} + +void SwUndoPageDescDelete::DoImpl() +{ + m_pDoc->DelPageDesc(m_aOld.GetName(), true); +} + +void SwUndoPageDescDelete::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoPageDescDelete::RepeatImpl(::sw::RepeatContext &) +{ + ::sw::UndoGuard const undoGuard(m_pDoc->GetIDocumentUndoRedo()); + DoImpl(); +} + +SwRewriter SwUndoPageDescDelete::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_aOld.GetName()); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoTOXChange.cxx b/sw/source/core/undo/SwUndoTOXChange.cxx new file mode 100644 index 000000000..c88182b7a --- /dev/null +++ b/sw/source/core/undo/SwUndoTOXChange.cxx @@ -0,0 +1,85 @@ +/* -*- 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 <SwUndoTOXChange.hxx> +#include <swundo.hxx> +#include <UndoCore.hxx> +#include <doctxm.hxx> +#include <doc.hxx> +#include <node.hxx> + +namespace +{ + sal_uLong GetSectionNodeIndex(SwTOXBaseSection const& rTOX) + { + const SwSectionNode* pSectNd = rTOX.GetFormat()->GetSectionNode(); + assert(pSectNd); + return pSectNd->GetIndex(); + } +} + +SwUndoTOXChange::SwUndoTOXChange(const SwDoc *pDoc, + SwTOXBaseSection const& rTOX, SwTOXBase const& rNew) + : SwUndo(SwUndoId::TOXCHANGE, pDoc) + , m_Old(rTOX) + , m_New(rNew) + , m_nNodeIndex(GetSectionNodeIndex(rTOX)) +{ +} + +SwUndoTOXChange::~SwUndoTOXChange() +{ +} + +// get the current ToXBase, which is not necessarily the same instance that existed there before +static SwTOXBase & GetTOX(SwDoc & rDoc, sal_uLong const nNodeIndex) +{ + SwSectionNode *const pNode(rDoc.GetNodes()[nNodeIndex]->GetSectionNode()); + assert(pNode); + assert(dynamic_cast<SwTOXBaseSection*>(&pNode->GetSection())); + auto & rTOX(static_cast<SwTOXBaseSection&>(pNode->GetSection())); + return rTOX; +} + +void SwUndoTOXChange::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + SwTOXBase & rTOX(GetTOX(rDoc, m_nNodeIndex)); + rTOX = m_Old; +} + +void SwUndoTOXChange::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + SwTOXBase & rTOX(GetTOX(rDoc, m_nNodeIndex)); + rTOX = m_New; +} + +void SwUndoTOXChange::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + SwTOXBase *const pTOX(SwDoc::GetCurTOX(*rContext.GetRepeatPaM().GetPoint())); + if (pTOX) + { + rDoc.ChangeTOX(*pTOX, m_New); + // intentionally limited to not Update because we'd need layout + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/docundo.cxx b/sw/source/core/undo/docundo.cxx new file mode 100644 index 000000000..61629e087 --- /dev/null +++ b/sw/source/core/undo/docundo.cxx @@ -0,0 +1,735 @@ +/* -*- 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 <UndoManager.hxx> + +#include <doc.hxx> +#include <docsh.hxx> +#include <view.hxx> +#include <drawdoc.hxx> +#include <ndarr.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <UndoCore.hxx> +#include <editsh.hxx> +#include <unobaseclass.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <comphelper/lok.hxx> +#include <assert.h> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> + +using namespace ::com::sun::star; + +// the undo array should never grow beyond this limit: +#define UNDO_ACTION_LIMIT (USHRT_MAX - 1000) + +namespace sw { + +UndoManager::UndoManager(std::shared_ptr<SwNodes> const & xUndoNodes, + IDocumentDrawModelAccess & rDrawModelAccess, + IDocumentRedlineAccess & rRedlineAccess, + IDocumentState & rState) + : m_rDrawModelAccess(rDrawModelAccess) + , m_rRedlineAccess(rRedlineAccess) + , m_rState(rState) + , m_xUndoNodes(xUndoNodes) + , m_bGroupUndo(true) + , m_bDrawUndo(true) + , m_bRepair(false) + , m_bLockUndoNoModifiedPosition(false) + , m_isAddWithIgnoreRepeat(false) + , m_UndoSaveMark(MARK_INVALID) + , m_pDocShell(nullptr) + , m_pView(nullptr) +{ + assert(bool(m_xUndoNodes)); + // writer expects it to be disabled initially + // Undo is enabled by SwEditShell constructor + SdrUndoManager::EnableUndo(false); +} + +SwNodes const& UndoManager::GetUndoNodes() const +{ + return *m_xUndoNodes; +} + +SwNodes & UndoManager::GetUndoNodes() +{ + return *m_xUndoNodes; +} + +bool UndoManager::IsUndoNodes(SwNodes const& rNodes) const +{ + return & rNodes == m_xUndoNodes.get(); +} + +void UndoManager::SetDocShell(SwDocShell* pDocShell) +{ + m_pDocShell = pDocShell; +} + +void UndoManager::SetView(SwView* pView) +{ + m_pView = pView; +} + +size_t UndoManager::GetUndoActionCount(const bool bCurrentLevel) const +{ + size_t nRet = SdrUndoManager::GetUndoActionCount(bCurrentLevel); + if (!comphelper::LibreOfficeKit::isActive() || !m_pView) + return nRet; + + if (!nRet || !SdrUndoManager::GetUndoActionCount()) + return nRet; + + const SfxUndoAction* pAction = SdrUndoManager::GetUndoAction(); + if (!pAction) + return nRet; + + if (!m_bRepair) + { + // If another view created the last undo action, prevent undoing it from this view. + ViewShellId nViewShellId = m_pView->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + nRet = 0; + } + + return nRet; +} + +size_t UndoManager::GetRedoActionCount(const bool bCurrentLevel) const +{ + size_t nRet = SdrUndoManager::GetRedoActionCount(bCurrentLevel); + if (!comphelper::LibreOfficeKit::isActive() || !m_pView) + return nRet; + + if (!nRet || !SdrUndoManager::GetRedoActionCount()) + return nRet; + + const SfxUndoAction* pAction = SdrUndoManager::GetRedoAction(); + if (!pAction) + return nRet; + + if (!m_bRepair) + { + // If another view created the first redo action, prevent redoing it from this view. + ViewShellId nViewShellId = m_pView->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + nRet = 0; + } + + return nRet; +} + +void UndoManager::DoUndo(bool const bDoUndo) +{ + if(!isTextEditActive()) + { + EnableUndo(bDoUndo); + + SwDrawModel*const pSdrModel = m_rDrawModelAccess.GetDrawModel(); + if( pSdrModel ) + { + pSdrModel->EnableUndo(bDoUndo); + } + } +} + +bool UndoManager::DoesUndo() const +{ + if(isTextEditActive()) + { + return false; + } + else + { + return IsUndoEnabled(); + } +} + +void UndoManager::DoGroupUndo(bool const bDoUndo) +{ + m_bGroupUndo = bDoUndo; +} + +bool UndoManager::DoesGroupUndo() const +{ + return m_bGroupUndo; +} + +void UndoManager::DoDrawUndo(bool const bDoUndo) +{ + m_bDrawUndo = bDoUndo; +} + +bool UndoManager::DoesDrawUndo() const +{ + return m_bDrawUndo; +} + +void UndoManager::DoRepair(bool bRepair) +{ + m_bRepair = bRepair; +} + +bool UndoManager::DoesRepair() const +{ + return m_bRepair; +} + +bool UndoManager::IsUndoNoResetModified() const +{ + return MARK_INVALID == m_UndoSaveMark; +} + +void UndoManager::SetUndoNoResetModified() +{ + if (MARK_INVALID != m_UndoSaveMark) + { + RemoveMark(m_UndoSaveMark); + m_UndoSaveMark = MARK_INVALID; + } +} + +void UndoManager::SetUndoNoModifiedPosition() +{ + if (!m_bLockUndoNoModifiedPosition) + { + m_UndoSaveMark = MarkTopUndoAction(); + } +} + +void UndoManager::LockUndoNoModifiedPosition() +{ + m_bLockUndoNoModifiedPosition = true; +} + +void UndoManager::UnLockUndoNoModifiedPosition() +{ + m_bLockUndoNoModifiedPosition = false; +} + +SwUndo* UndoManager::GetLastUndo() +{ + if (!SdrUndoManager::GetUndoActionCount()) + { + return nullptr; + } + SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction() ); + return dynamic_cast<SwUndo*>(pAction); +} + +void UndoManager::AppendUndo(std::unique_ptr<SwUndo> pUndo) +{ + AddUndoAction(std::move(pUndo)); +} + +void UndoManager::ClearRedo() +{ + return SdrUndoManager::ImplClearRedo_NoLock(TopLevel); +} + +void UndoManager::DelAllUndoObj() +{ + ::sw::UndoGuard const undoGuard(*this); + + SdrUndoManager::ClearAllLevels(); + + m_UndoSaveMark = MARK_INVALID; +} + +SwUndoId +UndoManager::StartUndo(SwUndoId const i_eUndoId, + SwRewriter const*const pRewriter) +{ + if (!IsUndoEnabled()) + { + return SwUndoId::EMPTY; + } + + SwUndoId const eUndoId( (i_eUndoId == SwUndoId::EMPTY) ? SwUndoId::START : i_eUndoId ); + + assert(SwUndoId::END != eUndoId); + OUString comment( (SwUndoId::START == eUndoId) + ? OUString("??") + : GetUndoComment(eUndoId) ); + if (pRewriter) + { + assert(SwUndoId::START != eUndoId); + comment = pRewriter->Apply(comment); + } + + ViewShellId nViewShellId(-1); + if (m_pDocShell) + { + if (const SwView* pView = m_pDocShell->GetView()) + nViewShellId = pView->GetViewShellId(); + } + SdrUndoManager::EnterListAction(comment, comment, static_cast<sal_uInt16>(eUndoId), nViewShellId); + + return eUndoId; +} + +SwUndoId +UndoManager::EndUndo(SwUndoId eUndoId, SwRewriter const*const pRewriter) +{ + if (!IsUndoEnabled()) + { + return SwUndoId::EMPTY; + } + + if ((eUndoId == SwUndoId::EMPTY) || (SwUndoId::START == eUndoId)) + eUndoId = SwUndoId::END; + OSL_ENSURE(!((SwUndoId::END == eUndoId) && pRewriter), + "EndUndo(): no Undo ID, but rewriter given?"); + + SfxUndoAction *const pLastUndo( + (0 == SdrUndoManager::GetUndoActionCount()) + ? nullptr : SdrUndoManager::GetUndoAction() ); + + int const nCount = LeaveListAction(); + + if (nCount) // otherwise: empty list action not inserted! + { + assert(pLastUndo); + assert(SwUndoId::START != eUndoId); + auto pListAction = dynamic_cast<SfxListUndoAction*>(SdrUndoManager::GetUndoAction()); + assert(pListAction); + if (SwUndoId::END != eUndoId) + { + OSL_ENSURE(static_cast<SwUndoId>(pListAction->GetId()) == eUndoId, + "EndUndo(): given ID different from StartUndo()"); + // comment set by caller of EndUndo + OUString comment = GetUndoComment(eUndoId); + if (pRewriter) + { + comment = pRewriter->Apply(comment); + } + pListAction->SetComment(comment); + } + else if (SwUndoId::START != static_cast<SwUndoId>(pListAction->GetId())) + { + // comment set by caller of StartUndo: nothing to do here + } + else if (pLastUndo) + { + // comment was not set at StartUndo or EndUndo: + // take comment of last contained action + // (note that this works recursively, i.e. the last contained + // action may be a list action created by StartUndo/EndUndo) + OUString const comment(pLastUndo->GetComment()); + pListAction->SetComment(comment); + } + else + { + OSL_ENSURE(false, "EndUndo(): no comment?"); + } + } + + return eUndoId; +} + +bool +UndoManager::GetLastUndoInfo( + OUString *const o_pStr, SwUndoId *const o_pId, const SwView* pView) const +{ + // this is actually expected to work on the current level, + // but that was really not obvious from the previous implementation... + if (!SdrUndoManager::GetUndoActionCount()) + { + return false; + } + + SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction() ); + + if (comphelper::LibreOfficeKit::isActive() && !m_bRepair) + { + // If another view created the undo action, prevent undoing it from this view. + ViewShellId nViewShellId = pView ? pView->GetViewShellId() : m_pDocShell->GetView()->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + { + if (o_pId) + { + *o_pId = SwUndoId::CONFLICT; + } + return false; + } + } + + if (o_pStr) + { + *o_pStr = pAction->GetComment(); + } + if (o_pId) + { + if (auto pListAction = dynamic_cast<const SfxListUndoAction*>(pAction)) + *o_pId = static_cast<SwUndoId>(pListAction->GetId()); + else if (auto pSwAction = dynamic_cast<const SwUndo*>(pAction)) + *o_pId = pSwAction->GetId(); + else + *o_pId = SwUndoId::EMPTY; + } + + return true; +} + +SwUndoComments_t UndoManager::GetUndoComments() const +{ + OSL_ENSURE(!SdrUndoManager::IsInListAction(), + "GetUndoComments() called while in list action?"); + + SwUndoComments_t ret; + const size_t nUndoCount(SdrUndoManager::GetUndoActionCount(TopLevel)); + for (size_t n = 0; n < nUndoCount; ++n) + { + OUString const comment( + SdrUndoManager::GetUndoActionComment(n, TopLevel)); + ret.push_back(comment); + } + + return ret; +} + +bool UndoManager::GetFirstRedoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView) const +{ + if (!SdrUndoManager::GetRedoActionCount()) + { + return false; + } + + SfxUndoAction *const pAction( SdrUndoManager::GetRedoAction() ); + if ( pAction == nullptr ) + { + return false; + } + + if (comphelper::LibreOfficeKit::isActive() && !m_bRepair) + { + // If another view created the undo action, prevent redoing it from this view. + ViewShellId nViewShellId = pView ? pView->GetViewShellId() : m_pDocShell->GetView()->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + { + if (o_pId) + { + *o_pId = SwUndoId::CONFLICT; + } + return false; + } + } + + if (o_pStr) + { + *o_pStr = pAction->GetComment(); + } + if (o_pId) + { + if (auto pListAction = dynamic_cast<const SfxListUndoAction*>(pAction)) + *o_pId = static_cast<SwUndoId>(pListAction->GetId()); + else if (auto pSwAction = dynamic_cast<const SwUndo*>(pAction)) + *o_pId = pSwAction->GetId(); + else + *o_pId = SwUndoId::EMPTY; + } + + return true; +} + +SwUndoComments_t UndoManager::GetRedoComments() const +{ + OSL_ENSURE(!SdrUndoManager::IsInListAction(), + "GetRedoComments() called while in list action?"); + + SwUndoComments_t ret; + const size_t nRedoCount(SdrUndoManager::GetRedoActionCount(TopLevel)); + for (size_t n = 0; n < nRedoCount; ++n) + { + OUString const comment( + SdrUndoManager::GetRedoActionComment(n, TopLevel)); + ret.push_back(comment); + } + + return ret; +} + +SwUndoId UndoManager::GetRepeatInfo(OUString *const o_pStr) const +{ + SwUndoId nRepeatId(SwUndoId::EMPTY); + GetLastUndoInfo(o_pStr, & nRepeatId); + if( SwUndoId::REPEAT_START <= nRepeatId && SwUndoId::REPEAT_END > nRepeatId ) + { + return nRepeatId; + } + if (o_pStr) // not repeatable -> clear comment + { + o_pStr->clear(); + } + return SwUndoId::EMPTY; +} + +SwUndo * UndoManager::RemoveLastUndo() +{ + if (SdrUndoManager::GetRedoActionCount() || + SdrUndoManager::GetRedoActionCount(TopLevel)) + { + OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?"); + return nullptr; + } + if (!SdrUndoManager::GetUndoActionCount()) + { + OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions"); + return nullptr; + } + SfxUndoAction *const pLastUndo(GetUndoAction()); + SdrUndoManager::RemoveLastUndoAction(); + return dynamic_cast<SwUndo *>(pLastUndo); +} + +// SfxUndoManager + +void UndoManager::AddUndoAction(std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge) +{ + SwUndo *const pUndo( dynamic_cast<SwUndo *>(pAction.get()) ); + if (pUndo) + { + if (RedlineFlags::NONE == pUndo->GetRedlineFlags()) + { + pUndo->SetRedlineFlags( m_rRedlineAccess.GetRedlineFlags() ); + } + if (m_isAddWithIgnoreRepeat) + { + pUndo->IgnoreRepeat(); + } + } + SdrUndoManager::AddUndoAction(std::move(pAction), bTryMerge); + if (m_pDocShell) + { + SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst( m_pDocShell ); + while ( pViewFrame ) + { + pViewFrame->GetBindings().Invalidate( SID_UNDO ); + pViewFrame->GetBindings().Invalidate( SID_REDO ); + pViewFrame = SfxViewFrame::GetNext( *pViewFrame, m_pDocShell ); + } + } + + // if the undo nodes array is too large, delete some actions + while (UNDO_ACTION_LIMIT < GetUndoNodes().Count()) + { + RemoveOldestUndoAction(); + } +} + +namespace { + +class CursorGuard +{ +public: + CursorGuard(SwEditShell & rShell, bool const bSave) + : m_rShell(rShell) + , m_bSaveCursor(bSave) + { + if (m_bSaveCursor) + { + m_rShell.Push(); // prevent modification of current cursor + } + } + ~CursorGuard() COVERITY_NOEXCEPT_FALSE + { + if (m_bSaveCursor) + { + m_rShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + } +private: + SwEditShell & m_rShell; + bool const m_bSaveCursor; +}; + +} + +bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo) +{ + SwDoc & rDoc(*GetUndoNodes().GetDoc()); + + UnoActionContext c(& rDoc); // exception-safe StartAllAction/EndAllAction + + SwEditShell *const pEditShell( rDoc.GetEditShell() ); + + OSL_ENSURE(pEditShell, "sw::UndoManager needs a SwEditShell!"); + if (!pEditShell) + { + throw uno::RuntimeException(); + } + + // in case the model has controllers locked, the Undo should not + // change the view cursors! + bool const bSaveCursors(pEditShell->CursorsLocked()); + CursorGuard aCursorGuard(*pEditShell, bSaveCursors); + if (!bSaveCursors) + { + // (in case Undo was called via API) clear the cursors: + pEditShell->KillPams(); + pEditShell->SetMark(); + pEditShell->ClearMark(); + } + + bool bRet(false); + + ::sw::UndoRedoContext context(rDoc, *pEditShell); + + // N.B. these may throw! + if (UndoOrRedoType::Undo == undoOrRedo) + { + bRet = SdrUndoManager::UndoWithContext(context); + } + else + { + bRet = SdrUndoManager::RedoWithContext(context); + } + + if (bRet) + { + // if we are at the "last save" position, the document is not modified + if (SdrUndoManager::HasTopUndoActionMark(m_UndoSaveMark)) + { + m_rState.ResetModified(); + } + else + { + m_rState.SetModified(); + } + } + + pEditShell->HandleUndoRedoContext(context); + + return bRet; +} + +bool UndoManager::Undo() +{ + if(isTextEditActive()) + { + return SdrUndoManager::Undo(); + } + else + { + return impl_DoUndoRedo(UndoOrRedoType::Undo); + } +} + +bool UndoManager::Redo() +{ + if(isTextEditActive()) + { + return SdrUndoManager::Redo(); + } + else + { + return impl_DoUndoRedo(UndoOrRedoType::Redo); + } +} + +void UndoManager::EmptyActionsChanged() +{ + if (m_pDocShell) + { + m_pDocShell->Broadcast(SfxHint(SfxHintId::DocumentRepair)); + } +} + +/** N.B.: this does _not_ call SdrUndoManager::Repeat because it is not + possible to wrap a list action around it: + calling EnterListAction here will cause SdrUndoManager::Repeat + to repeat the list action! + */ +bool UndoManager::Repeat(::sw::RepeatContext & rContext, + sal_uInt16 const nRepeatCount) +{ + if (SdrUndoManager::IsInListAction()) + { + OSL_ENSURE(false, "repeat in open list action???"); + return false; + } + if (!SdrUndoManager::GetUndoActionCount(TopLevel)) + { + return false; + } + SfxUndoAction *const pRepeatAction(GetUndoAction()); + assert(pRepeatAction); + if (!pRepeatAction->CanRepeat(rContext)) + { + return false; + } + + OUString const comment(pRepeatAction->GetComment()); + OUString const rcomment(pRepeatAction->GetRepeatComment(rContext)); + SwUndoId nId; + if (auto const* const pSwAction = dynamic_cast<SwUndo*>(pRepeatAction)) + nId = pSwAction->GetId(); + else if (auto const* const pListAction = dynamic_cast<SfxListUndoAction*>(pRepeatAction)) + nId = static_cast<SwUndoId>(pListAction->GetId()); + else + return false; + if (DoesUndo()) + { + ViewShellId nViewShellId(-1); + if (m_pDocShell) + { + if (const SwView* pView = m_pDocShell->GetView()) + nViewShellId = pView->GetViewShellId(); + } + EnterListAction(comment, rcomment, static_cast<sal_uInt16>(nId), nViewShellId); + } + + SwPaM* pTmp = rContext.m_pCurrentPaM; + for(SwPaM& rPaM : rContext.GetRepeatPaM().GetRingContainer()) + { // iterate over ring + rContext.m_pCurrentPaM = &rPaM; + if (DoesUndo() && & rPaM != pTmp) + { + m_isAddWithIgnoreRepeat = true; + } + for (sal_uInt16 nRptCnt = nRepeatCount; nRptCnt > 0; --nRptCnt) + { + pRepeatAction->Repeat(rContext); + } + if (DoesUndo() && & rPaM != pTmp) + { + m_isAddWithIgnoreRepeat = false; + } + rContext.m_bDeleteRepeated = false; // reset for next PaM + } + rContext.m_pCurrentPaM = pTmp; + + if (DoesUndo()) + { + LeaveListAction(); + } + return true; +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/rolbck.cxx b/sw/source/core/undo/rolbck.cxx new file mode 100644 index 000000000..206ad9343 --- /dev/null +++ b/sw/source/core/undo/rolbck.cxx @@ -0,0 +1,1485 @@ +/* -*- 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 <rolbck.hxx> +#include <svl/itemiter.hxx> +#include <editeng/formatbreakitem.hxx> +#include <hints.hxx> +#include <hintids.hxx> +#include <fmtftn.hxx> +#include <fchrfmt.hxx> +#include <fmtflcnt.hxx> +#include <fmtrfmrk.hxx> +#include <fmtfld.hxx> +#include <fmtpdsc.hxx> +#include <txtfld.hxx> +#include <txtrfmrk.hxx> +#include <txttxmrk.hxx> +#include <txtftn.hxx> +#include <txtflcnt.hxx> +#include <fmtanchr.hxx> +#include <fmtcnct.hxx> +#include <frmfmt.hxx> +#include <ftnidx.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> +#include <cellatr.hxx> +#include <fldbas.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <UndoCore.hxx> +#include <IMark.hxx> +#include <charfmt.hxx> +#include <strings.hrc> +#include <bookmrk.hxx> +#include <frameformats.hxx> +#include <memory> + +OUString SwHistoryHint::GetDescription() const +{ + return OUString(); +} + +SwHistorySetFormat::SwHistorySetFormat( const SfxPoolItem* pFormatHt, sal_uLong nNd ) + : SwHistoryHint( HSTRY_SETFMTHNT ) + , m_pAttr( pFormatHt->Clone() ) + , m_nNodeIndex( nNd ) +{ + switch ( m_pAttr->Which() ) + { + case RES_PAGEDESC: + static_cast<SwFormatPageDesc&>(*m_pAttr).ChgDefinedIn( nullptr ); + break; + case RES_PARATR_DROP: + static_cast<SwFormatDrop&>(*m_pAttr).ChgDefinedIn( nullptr ); + break; + case RES_BOXATR_FORMULA: + { + // save formulas always in plain text + SwTableBoxFormula& rNew = static_cast<SwTableBoxFormula&>(*m_pAttr); + if ( rNew.IsIntrnlName() ) + { + const SwTableBoxFormula& rOld = + *static_cast<const SwTableBoxFormula*>(pFormatHt); + const SwNode* pNd = rOld.GetNodeOfFormula(); + if ( pNd ) + { + const SwTableNode* pTableNode = pNd->FindTableNode(); + if (pTableNode) + { + SwTableFormulaUpdate aMsgHint( &pTableNode->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXNAME; + rNew.ChgDefinedIn( rOld.GetDefinedIn() ); + rNew.ChangeState( &aMsgHint ); + } + } + } + rNew.ChgDefinedIn( nullptr ); + } + break; + } +} + +OUString SwHistorySetFormat::GetDescription() const +{ + OUString aResult; + + switch (m_pAttr->Which()) + { + case RES_BREAK: + switch (static_cast<SvxFormatBreakItem &>(*m_pAttr).GetBreak()) + { + case SvxBreak::PageBefore: + case SvxBreak::PageAfter: + case SvxBreak::PageBoth: + aResult = SwResId(STR_UNDO_PAGEBREAKS); + + break; + case SvxBreak::ColumnBefore: + case SvxBreak::ColumnAfter: + case SvxBreak::ColumnBoth: + aResult = SwResId(STR_UNDO_COLBRKS); + + break; + default: + break; + } + break; + default: + break; + } + + return aResult; +} + +void SwHistorySetFormat::SetInDoc( SwDoc* pDoc, bool bTmpSet ) +{ + SwNode * pNode = pDoc->GetNodes()[ m_nNodeIndex ]; + if ( pNode->IsContentNode() ) + { + static_cast<SwContentNode*>(pNode)->SetAttr( *m_pAttr ); + } + else if ( pNode->IsTableNode() ) + { + static_cast<SwTableNode*>(pNode)->GetTable().GetFrameFormat()->SetFormatAttr( + *m_pAttr ); + } + else if ( pNode->IsStartNode() && (SwTableBoxStartNode == + static_cast<SwStartNode*>(pNode)->GetStartNodeType()) ) + { + SwTableNode* pTNd = pNode->FindTableNode(); + if ( pTNd ) + { + SwTableBox* pBox = pTNd->GetTable().GetTableBox( m_nNodeIndex ); + if (pBox) + { + pBox->ClaimFrameFormat()->SetFormatAttr( *m_pAttr ); + } + } + } + + if ( !bTmpSet ) + { + m_pAttr.reset(); + } +} + +SwHistorySetFormat::~SwHistorySetFormat() +{ +} + +SwHistoryResetFormat::SwHistoryResetFormat(const SfxPoolItem* pFormatHt, sal_uLong nNodeIdx) + : SwHistoryHint( HSTRY_RESETFMTHNT ) + , m_nNodeIndex( nNodeIdx ) + , m_nWhich( pFormatHt->Which() ) +{ +} + +void SwHistoryResetFormat::SetInDoc( SwDoc* pDoc, bool ) +{ + SwNode * pNode = pDoc->GetNodes()[ m_nNodeIndex ]; + if ( pNode->IsContentNode() ) + { + static_cast<SwContentNode*>(pNode)->ResetAttr( m_nWhich ); + } + else if ( pNode->IsTableNode() ) + { + static_cast<SwTableNode*>(pNode)->GetTable().GetFrameFormat()-> + ResetFormatAttr( m_nWhich ); + } +} + +SwHistorySetText::SwHistorySetText( SwTextAttr* pTextHt, sal_uLong nNodePos ) + : SwHistoryHint( HSTRY_SETTXTHNT ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextHt->GetStart() ) + , m_nEnd( pTextHt->GetAnyEnd() ) + , m_bFormatIgnoreStart(pTextHt->IsFormatIgnoreStart()) + , m_bFormatIgnoreEnd (pTextHt->IsFormatIgnoreEnd ()) +{ + // Caution: the following attributes generate no format attributes: + // - NoLineBreak, NoHyphen, Inserted, Deleted + // These cases must be handled separately !!! + + // a little bit complicated but works: first assign a copy of the + // default value and afterwards the values from text attribute + if ( RES_TXTATR_CHARFMT == pTextHt->Which() ) + { + m_pAttr.reset( new SwFormatCharFormat( pTextHt->GetCharFormat().GetCharFormat() ) ); + } + else + { + m_pAttr.reset( pTextHt->GetAttr().Clone() ); + } +} + +SwHistorySetText::~SwHistorySetText() +{ +} + +void SwHistorySetText::SetInDoc( SwDoc* pDoc, bool ) +{ + if (!m_pAttr) + return; + + if ( RES_TXTATR_CHARFMT == m_pAttr->Which() ) + { + // ask the Doc if the CharFormat still exists + if (!pDoc->GetCharFormats()->IsAlive(static_cast<SwFormatCharFormat&>(*m_pAttr).GetCharFormat())) + return; // do not set, format does not exist + } + + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetText::SetInDoc: not a TextNode" ); + + if ( pTextNd ) + { + SwTextAttr *const pAttr = pTextNd->InsertItem(*m_pAttr, m_nStart, m_nEnd, + SetAttrMode::NOTXTATRCHR | + SetAttrMode::NOHINTADJUST ); + // shouldn't be possible to hit any error/merging path from here + assert(pAttr); + if (m_bFormatIgnoreStart) + { + pAttr->SetFormatIgnoreStart(true); + } + if (m_bFormatIgnoreEnd) + { + pAttr->SetFormatIgnoreEnd(true); + } + } +} + +SwHistorySetTextField::SwHistorySetTextField( const SwTextField* pTextField, sal_uLong nNodePos ) + : SwHistoryHint( HSTRY_SETTXTFLDHNT ) + , m_pField( new SwFormatField( *pTextField->GetFormatField().GetField() ) ) +{ + // only copy if not Sys-FieldType + SwDoc* pDoc = pTextField->GetTextNode().GetDoc(); + + m_nFieldWhich = m_pField->GetField()->GetTyp()->Which(); + if (m_nFieldWhich == SwFieldIds::Database || + m_nFieldWhich == SwFieldIds::User || + m_nFieldWhich == SwFieldIds::SetExp || + m_nFieldWhich == SwFieldIds::Dde || + !pDoc->getIDocumentFieldsAccess().GetSysFieldType( m_nFieldWhich )) + { + m_pFieldType = m_pField->GetField()->GetTyp()->Copy(); + m_pField->GetField()->ChgTyp( m_pFieldType.get() ); // change field type + } + m_nNodeIndex = nNodePos; + m_nPos = pTextField->GetStart(); +} + +OUString SwHistorySetTextField::GetDescription() const +{ + return m_pField->GetField()->GetDescription(); +} + +SwHistorySetTextField::~SwHistorySetTextField() +{ +} + +void SwHistorySetTextField::SetInDoc( SwDoc* pDoc, bool ) +{ + if (!m_pField) + return; + + SwFieldType* pNewFieldType = m_pFieldType.get(); + if ( !pNewFieldType ) + { + pNewFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType( m_nFieldWhich ); + } + else + { + // register type with the document + pNewFieldType = pDoc->getIDocumentFieldsAccess().InsertFieldType( *m_pFieldType ); + } + + m_pField->GetField()->ChgTyp( pNewFieldType ); // change field type + + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetTextField: no TextNode" ); + + if ( pTextNd ) + { + pTextNd->InsertItem( *m_pField, m_nPos, m_nPos, + SetAttrMode::NOTXTATRCHR ); + } +} + +SwHistorySetRefMark::SwHistorySetRefMark( const SwTextRefMark* pTextHt, sal_uLong nNodePos ) + : SwHistoryHint( HSTRY_SETREFMARKHNT ) + , m_RefName( pTextHt->GetRefMark().GetRefName() ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextHt->GetStart() ) + , m_nEnd( pTextHt->GetAnyEnd() ) +{ +} + +void SwHistorySetRefMark::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetRefMark: no TextNode" ); + if ( !pTextNd ) + return; + + SwFormatRefMark aRefMark( m_RefName ); + + // if a reference mark without an end already exists here: must not insert! + if ( m_nStart != m_nEnd || + !pTextNd->GetTextAttrForCharAt( m_nStart, RES_TXTATR_REFMARK ) ) + { + pTextNd->InsertItem( aRefMark, m_nStart, m_nEnd, + SetAttrMode::NOTXTATRCHR ); + } +} + +SwHistorySetTOXMark::SwHistorySetTOXMark( const SwTextTOXMark* pTextHt, sal_uLong nNodePos ) + : SwHistoryHint( HSTRY_SETTOXMARKHNT ) + , m_TOXMark( pTextHt->GetTOXMark() ) + , m_TOXName( m_TOXMark.GetTOXType()->GetTypeName() ) + , m_eTOXTypes( m_TOXMark.GetTOXType()->GetType() ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextHt->GetStart() ) + , m_nEnd( pTextHt->GetAnyEnd() ) +{ + m_TOXMark.EndListeningAll(); +} + +SwTOXType* SwHistorySetTOXMark::GetSwTOXType(SwDoc& rDoc, TOXTypes eTOXTypes, const OUString& rTOXName) +{ + // search for respective TOX type + const sal_uInt16 nCnt = rDoc.GetTOXTypeCount(eTOXTypes); + SwTOXType* pToxType = nullptr; + for ( sal_uInt16 n = 0; n < nCnt; ++n ) + { + pToxType = const_cast<SwTOXType*>(rDoc.GetTOXType(eTOXTypes, n)); + if (pToxType->GetTypeName() == rTOXName) + break; + pToxType = nullptr; + } + + if ( !pToxType ) // TOX type not found, create new + { + pToxType = const_cast<SwTOXType*>( + rDoc.InsertTOXType(SwTOXType(rDoc, eTOXTypes, rTOXName))); + } + + return pToxType; +} + +void SwHistorySetTOXMark::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetTOXMark: no TextNode" ); + if ( !pTextNd ) + return; + + SwTOXType* pToxType = GetSwTOXType(*pDoc, m_eTOXTypes, m_TOXName); + + SwTOXMark aNew( m_TOXMark ); + aNew.RegisterToTOXType( *pToxType ); + + pTextNd->InsertItem( aNew, m_nStart, m_nEnd, + SetAttrMode::NOTXTATRCHR ); +} + +bool SwHistorySetTOXMark::IsEqual( const SwTOXMark& rCmp ) const +{ + return m_TOXName == rCmp.GetTOXType()->GetTypeName() && + m_eTOXTypes == rCmp.GetTOXType()->GetType() && + m_TOXMark.GetAlternativeText() == rCmp.GetAlternativeText() && + ( (TOX_INDEX == m_eTOXTypes) + ? ( m_TOXMark.GetPrimaryKey() == rCmp.GetPrimaryKey() && + m_TOXMark.GetSecondaryKey() == rCmp.GetSecondaryKey() ) + : m_TOXMark.GetLevel() == rCmp.GetLevel() + ); +} + +SwHistoryResetText::SwHistoryResetText( sal_uInt16 nWhich, + sal_Int32 nAttrStart, sal_Int32 nAttrEnd, sal_uLong nNodePos ) + : SwHistoryHint( HSTRY_RESETTXTHNT ) + , m_nNodeIndex( nNodePos ), m_nStart( nAttrStart ), m_nEnd( nAttrEnd ) + , m_nAttr( nWhich ) +{ +} + +void SwHistoryResetText::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistoryResetText: no TextNode" ); + if ( pTextNd ) + { + pTextNd->DeleteAttributes( m_nAttr, m_nStart, m_nEnd ); + } +} + +SwHistorySetFootnote::SwHistorySetFootnote( SwTextFootnote* pTextFootnote, sal_uLong nNodePos ) + : SwHistoryHint( HSTRY_SETFTNHNT ) + , m_pUndo( new SwUndoSaveSection ) + , m_FootnoteNumber( pTextFootnote->GetFootnote().GetNumStr() ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextFootnote->GetStart() ) + , m_bEndNote( pTextFootnote->GetFootnote().IsEndNote() ) +{ + OSL_ENSURE( pTextFootnote->GetStartNode(), + "SwHistorySetFootnote: Footnote without Section" ); + + // keep the old NodePos (because who knows what later will be saved/deleted + // in SaveSection) + SwDoc* pDoc = const_cast<SwDoc*>(pTextFootnote->GetTextNode().GetDoc()); + SwNode* pSaveNd = pDoc->GetNodes()[ m_nNodeIndex ]; + + // keep pointer to StartNode of FootnoteSection and reset its attribute for now + // (as a result, its/all Frames will be deleted automatically) + SwNodeIndex aSttIdx( *pTextFootnote->GetStartNode() ); + pTextFootnote->SetStartNode( nullptr, false ); + + m_pUndo->SaveSection( aSttIdx ); + m_nNodeIndex = pSaveNd->GetIndex(); +} + +SwHistorySetFootnote::SwHistorySetFootnote( const SwTextFootnote &rTextFootnote ) + : SwHistoryHint( HSTRY_SETFTNHNT ) + , m_FootnoteNumber( rTextFootnote.GetFootnote().GetNumStr() ) + , m_nNodeIndex( SwTextFootnote_GetIndex( (&rTextFootnote) ) ) + , m_nStart( rTextFootnote.GetStart() ) + , m_bEndNote( rTextFootnote.GetFootnote().IsEndNote() ) +{ + OSL_ENSURE( rTextFootnote.GetStartNode(), + "SwHistorySetFootnote: Footnote without Section" ); +} + +OUString SwHistorySetFootnote::GetDescription() const +{ + return SwResId(STR_FOOTNOTE); +} + +SwHistorySetFootnote::~SwHistorySetFootnote() +{ +} + +void SwHistorySetFootnote::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetFootnote: no TextNode" ); + if ( !pTextNd ) + return; + + if (m_pUndo) + { + // set the footnote in the TextNode + SwFormatFootnote aTemp( m_bEndNote ); + SwFormatFootnote& rNew = const_cast<SwFormatFootnote&>( + pDoc->GetAttrPool().Put(aTemp) ); + if ( !m_FootnoteNumber.isEmpty() ) + { + rNew.SetNumStr( m_FootnoteNumber ); + } + SwTextFootnote* pTextFootnote = new SwTextFootnote( rNew, m_nStart ); + + // create the section of the Footnote + SwNodeIndex aIdx( *pTextNd ); + m_pUndo->RestoreSection( pDoc, &aIdx, SwFootnoteStartNode ); + pTextFootnote->SetStartNode( &aIdx ); + if ( m_pUndo->GetHistory() ) + { + // create frames only now + m_pUndo->GetHistory()->Rollback( pDoc ); + } + + pTextNd->InsertHint( pTextFootnote ); + } + else + { + SwTextFootnote * const pFootnote = + static_cast<SwTextFootnote*>( + pTextNd->GetTextAttrForCharAt( m_nStart )); + assert(pFootnote); + SwFormatFootnote &rFootnote = const_cast<SwFormatFootnote&>(pFootnote->GetFootnote()); + rFootnote.SetNumStr( m_FootnoteNumber ); + if ( rFootnote.IsEndNote() != m_bEndNote ) + { + rFootnote.SetEndNote( m_bEndNote ); + pFootnote->CheckCondColl(); + } + } +} + +SwHistoryChangeFormatColl::SwHistoryChangeFormatColl( SwFormatColl* pFormatColl, sal_uLong nNd, + SwNodeType nNodeWhich ) + : SwHistoryHint( HSTRY_CHGFMTCOLL ) + , m_pColl( pFormatColl ) + , m_nNodeIndex( nNd ) + , m_nNodeType( nNodeWhich ) +{ +} + +void SwHistoryChangeFormatColl::SetInDoc( SwDoc* pDoc, bool ) +{ + SwContentNode * pContentNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetContentNode(); + OSL_ENSURE( pContentNd, "SwHistoryChangeFormatColl: no ContentNode" ); + + // before setting the format, check if it is still available in the + // document. if it has been deleted, there is no undo! + if ( pContentNd && m_nNodeType == pContentNd->GetNodeType() ) + { + if ( SwNodeType::Text == m_nNodeType ) + { + if (pDoc->GetTextFormatColls()->IsAlive(static_cast<SwTextFormatColl *>(m_pColl))) + { + pContentNd->ChgFormatColl( m_pColl ); + } + } + else if (pDoc->GetGrfFormatColls()->IsAlive(static_cast<SwGrfFormatColl *>(m_pColl))) + { + pContentNd->ChgFormatColl( m_pColl ); + } + } +} + +SwHistoryTextFlyCnt::SwHistoryTextFlyCnt( SwFrameFormat* const pFlyFormat ) + : SwHistoryHint( HSTRY_FLYCNT ) + , m_pUndo( new SwUndoDelLayFormat( pFlyFormat ) ) +{ + OSL_ENSURE( pFlyFormat, "SwHistoryTextFlyCnt: no Format" ); + m_pUndo->ChgShowSel( false ); +} + +SwHistoryTextFlyCnt::~SwHistoryTextFlyCnt() +{ +} + +void SwHistoryTextFlyCnt::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::IShellCursorSupplier *const pISCS(pDoc->GetIShellCursorSupplier()); + assert(pISCS); + ::sw::UndoRedoContext context(*pDoc, *pISCS); + m_pUndo->UndoImpl(context); +} + +SwHistoryBookmark::SwHistoryBookmark( + const ::sw::mark::IMark& rBkmk, + bool bSavePos, + bool bSaveOtherPos) + : SwHistoryHint(HSTRY_BOOKMARK) + , m_aName(rBkmk.GetName()) + , m_aShortName() + , m_aKeycode() + , m_nNode(bSavePos ? + rBkmk.GetMarkPos().nNode.GetIndex() : 0) + , m_nOtherNode(bSaveOtherPos ? + rBkmk.GetOtherMarkPos().nNode.GetIndex() : 0) + , m_nContent(bSavePos ? + rBkmk.GetMarkPos().nContent.GetIndex() : 0) + , m_nOtherContent(bSaveOtherPos ? + rBkmk.GetOtherMarkPos().nContent.GetIndex() :0) + , m_bSavePos(bSavePos) + , m_bSaveOtherPos(bSaveOtherPos) + , m_bHadOtherPos(rBkmk.IsExpanded()) + , m_eBkmkType(IDocumentMarkAccess::GetType(rBkmk)) +{ + const ::sw::mark::IBookmark* const pBookmark = dynamic_cast< const ::sw::mark::IBookmark* >(&rBkmk); + if(pBookmark) + { + m_aKeycode = pBookmark->GetKeyCode(); + m_aShortName = pBookmark->GetShortName(); + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pBookmark)); + if (pMetadatable) + { + m_pMetadataUndo = pMetadatable->CreateUndo(); + } + } +} + +void SwHistoryBookmark::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); + std::unique_ptr<SwPaM> pPam; + ::sw::mark::IMark* pMark = nullptr; + + if(m_bSavePos) + { + SwContentNode* const pContentNd = rNds[m_nNode]->GetContentNode(); + OSL_ENSURE(pContentNd, + "<SwHistoryBookmark::SetInDoc(..)>" + " - wrong node for a mark"); + + // #111660# don't crash when nNode1 doesn't point to content node. + if(pContentNd) + pPam.reset(new SwPaM(*pContentNd, m_nContent)); + } + else + { + pMark = *pMarkAccess->findMark(m_aName); + pPam.reset(new SwPaM(pMark->GetMarkPos())); + } + + if(m_bSaveOtherPos) + { + SwContentNode* const pContentNd = rNds[m_nOtherNode]->GetContentNode(); + OSL_ENSURE(pContentNd, + "<SwHistoryBookmark::SetInDoc(..)>" + " - wrong node for a mark"); + + if (pPam != nullptr && pContentNd) + { + pPam->SetMark(); + pPam->GetMark()->nNode = m_nOtherNode; + pPam->GetMark()->nContent.Assign(pContentNd, m_nOtherContent); + } + } + else if(m_bHadOtherPos) + { + if(!pMark) + pMark = *pMarkAccess->findMark(m_aName); + OSL_ENSURE(pMark->IsExpanded(), + "<SwHistoryBookmark::SetInDoc(..)>" + " - missing pos on old mark"); + pPam->SetMark(); + *pPam->GetMark() = pMark->GetOtherMarkPos(); + } + + if (pPam) + { + if ( pMark != nullptr ) + { + pMarkAccess->deleteMark( pMark ); + } + ::sw::mark::IBookmark* const pBookmark = + dynamic_cast<::sw::mark::IBookmark*>( + pMarkAccess->makeMark(*pPam, m_aName, m_eBkmkType, sw::mark::InsertMode::New)); + if ( pBookmark != nullptr ) + { + pBookmark->SetKeyCode(m_aKeycode); + pBookmark->SetShortName(m_aShortName); + if (m_pMetadataUndo) + { + ::sfx2::Metadatable * const pMeta( + dynamic_cast< ::sfx2::Metadatable* >(pBookmark)); + OSL_ENSURE(pMeta, "metadata undo, but not metadatable?"); + if (pMeta) + { + pMeta->RestoreMetadata(m_pMetadataUndo); + } + } + } + } +} + +bool SwHistoryBookmark::IsEqualBookmark(const ::sw::mark::IMark& rBkmk) +{ + return m_nNode == rBkmk.GetMarkPos().nNode.GetIndex() + && m_nContent == rBkmk.GetMarkPos().nContent.GetIndex() + && m_aName == rBkmk.GetName(); +} + +SwHistoryNoTextFieldmark::SwHistoryNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldMark) + : SwHistoryHint(HSTRY_NOTEXTFIELDMARK) + , m_sType(rFieldMark.GetFieldname()) + , m_nNode(rFieldMark.GetMarkPos().nNode.GetIndex()) + , m_nContent(rFieldMark.GetMarkPos().nContent.GetIndex()) +{ +} + +void SwHistoryNoTextFieldmark::SetInDoc(SwDoc* pDoc, bool) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + std::unique_ptr<SwPaM> pPam; + + const SwContentNode* pContentNd = rNds[m_nNode]->GetContentNode(); + if(pContentNd) + pPam.reset(new SwPaM(*pContentNd, m_nContent)); + + if (pPam) + { + IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); + pMarkAccess->makeNoTextFieldBookmark(*pPam, OUString(), m_sType); + } +} + +void SwHistoryNoTextFieldmark::ResetInDoc(SwDoc* pDoc) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + std::unique_ptr<SwPaM> pPam; + + const SwContentNode* pContentNd = rNds[m_nNode]->GetContentNode(); + if(pContentNd) + pPam.reset(new SwPaM(*pContentNd, m_nContent-1)); + + if (pPam) + { + IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); + pMarkAccess->deleteFieldmarkAt(*pPam->GetPoint()); + } +} + +SwHistoryTextFieldmark::SwHistoryTextFieldmark(const ::sw::mark::IFieldmark& rFieldMark) + : SwHistoryHint(HSTRY_TEXTFIELDMARK) + , m_sName(rFieldMark.GetName()) + , m_sType(rFieldMark.GetFieldname()) + , m_nStartNode(rFieldMark.GetMarkStart().nNode.GetIndex()) + , m_nStartContent(rFieldMark.GetMarkStart().nContent.GetIndex()) + , m_nEndNode(rFieldMark.GetMarkEnd().nNode.GetIndex()) + , m_nEndContent(rFieldMark.GetMarkEnd().nContent.GetIndex()) +{ + SwPosition const sepPos(sw::mark::FindFieldSep(rFieldMark)); + m_nSepNode = sepPos.nNode.GetIndex(); + m_nSepContent = sepPos.nContent.GetIndex(); +} + +void SwHistoryTextFieldmark::SetInDoc(SwDoc* pDoc, bool) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + + assert(rNds[m_nStartNode]->IsContentNode()); + assert(rNds[m_nEndNode]->IsContentNode()); + assert(rNds[m_nSepNode]->IsContentNode()); + + SwPaM const pam(*rNds[m_nStartNode]->GetContentNode(), m_nStartContent, + *rNds[m_nEndNode]->GetContentNode(), + // subtract 1 for the CH_TXT_ATR_FIELDEND itself, + // plus more if same node as other CH_TXT_ATR + m_nStartNode == m_nEndNode + ? (m_nEndContent - 3) + : m_nSepNode == m_nEndNode + ? (m_nEndContent - 2) + : (m_nEndContent - 1)); + SwPosition const sepPos(*rNds[m_nSepNode]->GetContentNode(), + m_nStartNode == m_nSepNode ? (m_nSepContent - 1) : m_nSepContent); + + IDocumentMarkAccess & rMarksAccess(*pDoc->getIDocumentMarkAccess()); + rMarksAccess.makeFieldBookmark(pam, m_sName, m_sType, &sepPos); +} + +void SwHistoryTextFieldmark::ResetInDoc(SwDoc* pDoc) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + + assert(rNds[m_nStartNode]->IsContentNode()); + assert(rNds[m_nEndNode]->IsContentNode()); + assert(rNds[m_nSepNode]->IsContentNode()); + + SwPosition const pos(*rNds[m_nStartNode]->GetContentNode(), m_nStartContent); + + IDocumentMarkAccess & rMarksAccess(*pDoc->getIDocumentMarkAccess()); + rMarksAccess.deleteFieldmarkAt(pos); +} + +SwHistorySetAttrSet::SwHistorySetAttrSet( const SfxItemSet& rSet, + sal_uLong nNodePos, const std::set<sal_uInt16> &rSetArr ) + : SwHistoryHint( HSTRY_SETATTRSET ) + , m_OldSet( rSet ) + , m_ResetArray( 0, 4 ) + , m_nNodeIndex( nNodePos ) +{ + SfxItemIter aIter( m_OldSet ), aOrigIter( rSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(), + * pOrigItem = aOrigIter.GetCurItem(); + while (pItem && pOrigItem) + { + if( !rSetArr.count( pOrigItem->Which() )) + { + m_ResetArray.push_back( pOrigItem->Which() ); + m_OldSet.ClearItem( pOrigItem->Which() ); + } + else + { + switch ( pItem->Which() ) + { + case RES_PAGEDESC: + static_cast<SwFormatPageDesc*>( + const_cast<SfxPoolItem*>(pItem))->ChgDefinedIn( nullptr ); + break; + + case RES_PARATR_DROP: + static_cast<SwFormatDrop*>( + const_cast<SfxPoolItem*>(pItem))->ChgDefinedIn( nullptr ); + break; + + case RES_BOXATR_FORMULA: + { + // When a formula is set, never save the value. It + // possibly must be recalculated! + // Save formulas always in plain text + m_OldSet.ClearItem( RES_BOXATR_VALUE ); + + SwTableBoxFormula& rNew = + *static_cast<SwTableBoxFormula*>( + const_cast<SfxPoolItem*>(pItem)); + if ( rNew.IsIntrnlName() ) + { + const SwTableBoxFormula& rOld = + rSet.Get( RES_BOXATR_FORMULA ); + const SwNode* pNd = rOld.GetNodeOfFormula(); + if ( pNd ) + { + const SwTableNode* pTableNode + = pNd->FindTableNode(); + if (pTableNode) + { + SwTableFormulaUpdate aMsgHint( + &pTableNode->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXNAME; + rNew.ChgDefinedIn( rOld.GetDefinedIn() ); + rNew.ChangeState( &aMsgHint ); + } + } + } + rNew.ChgDefinedIn( nullptr ); + } + break; + } + } + + pItem = aIter.NextItem(); + pOrigItem = aOrigIter.NextItem(); + } +} + +void SwHistorySetAttrSet::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNode * pNode = pDoc->GetNodes()[ m_nNodeIndex ]; + if ( pNode->IsContentNode() ) + { + static_cast<SwContentNode*>(pNode)->SetAttr( m_OldSet ); + if ( !m_ResetArray.empty() ) + { + static_cast<SwContentNode*>(pNode)->ResetAttr( m_ResetArray ); + } + } + else if ( pNode->IsTableNode() ) + { + SwFormat& rFormat = + *static_cast<SwTableNode*>(pNode)->GetTable().GetFrameFormat(); + rFormat.SetFormatAttr( m_OldSet ); + if ( !m_ResetArray.empty() ) + { + rFormat.ResetFormatAttr( m_ResetArray.front() ); + } + } +} + +SwHistoryChangeFlyAnchor::SwHistoryChangeFlyAnchor( SwFrameFormat& rFormat ) + : SwHistoryHint( HSTRY_CHGFLYANCHOR ) + , m_rFormat( rFormat ) + , m_nOldNodeIndex( rFormat.GetAnchor().GetContentAnchor()->nNode.GetIndex() ) + , m_nOldContentIndex( (RndStdIds::FLY_AT_CHAR == rFormat.GetAnchor().GetAnchorId()) + ? rFormat.GetAnchor().GetContentAnchor()->nContent.GetIndex() + : COMPLETE_STRING ) +{ +} + +void SwHistoryChangeFlyAnchor::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + if (pDoc->GetSpzFrameFormats()->IsAlive(&m_rFormat)) // Format does still exist + { + SwFormatAnchor aTmp( m_rFormat.GetAnchor() ); + + SwNode* pNd = pDoc->GetNodes()[ m_nOldNodeIndex ]; + SwContentNode* pCNd = pNd->GetContentNode(); + SwPosition aPos( *pNd ); + if ( COMPLETE_STRING != m_nOldContentIndex ) + { + OSL_ENSURE(pCNd, "SwHistoryChangeFlyAnchor: no ContentNode"); + if (pCNd) + { + aPos.nContent.Assign( pCNd, m_nOldContentIndex ); + } + } + aTmp.SetAnchor( &aPos ); + + // so the Layout does not get confused + if (!pCNd || !pCNd->getLayoutFrame(pDoc->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) + { + m_rFormat.DelFrames(); + } + + m_rFormat.SetFormatAttr( aTmp ); + } +} + +SwHistoryChangeFlyChain::SwHistoryChangeFlyChain( SwFlyFrameFormat& rFormat, + const SwFormatChain& rAttr ) + : SwHistoryHint( HSTRY_CHGFLYCHAIN ) + , m_pPrevFormat( rAttr.GetPrev() ) + , m_pNextFormat( rAttr.GetNext() ) + , m_pFlyFormat( &rFormat ) +{ +} + +void SwHistoryChangeFlyChain::SetInDoc( SwDoc* pDoc, bool ) +{ + if (pDoc->GetSpzFrameFormats()->IsAlive(m_pFlyFormat)) + { + SwFormatChain aChain; + + if (m_pPrevFormat && + pDoc->GetSpzFrameFormats()->IsAlive(m_pPrevFormat)) + { + aChain.SetPrev( m_pPrevFormat ); + SwFormatChain aTmp( m_pPrevFormat->GetChain() ); + aTmp.SetNext( m_pFlyFormat ); + m_pPrevFormat->SetFormatAttr( aTmp ); + } + + if (m_pNextFormat && + pDoc->GetSpzFrameFormats()->IsAlive(m_pNextFormat)) + { + aChain.SetNext( m_pNextFormat ); + SwFormatChain aTmp( m_pNextFormat->GetChain() ); + aTmp.SetPrev( m_pFlyFormat ); + m_pNextFormat->SetFormatAttr( aTmp ); + } + + if ( aChain.GetNext() || aChain.GetPrev() ) + { + m_pFlyFormat->SetFormatAttr( aChain ); + } + } +} + +// -> #i27615# +SwHistoryChangeCharFormat::SwHistoryChangeCharFormat(const SfxItemSet & rSet, + const OUString & sFormat) + : SwHistoryHint(HSTRY_CHGCHARFMT) + , m_OldSet(rSet), m_Format(sFormat) +{ +} + +void SwHistoryChangeCharFormat::SetInDoc(SwDoc * pDoc, bool ) +{ + SwCharFormat * pCharFormat = pDoc->FindCharFormatByName(m_Format); + + if (pCharFormat) + { + pCharFormat->SetFormatAttr(m_OldSet); + } +} +// <- #i27615# + +SwHistory::SwHistory() + : m_SwpHstry() + , m_nEndDiff( 0 ) +{ +} + +SwHistory::~SwHistory() +{ +} + +void SwHistory::Add( + const SfxPoolItem* pOldValue, + const SfxPoolItem* pNewValue, + sal_uLong nNodeIdx) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + const sal_uInt16 nWhich(pNewValue->Which()); + + // excluded values + if(nWhich == RES_TXTATR_FIELD || nWhich == RES_TXTATR_ANNOTATION) + { + return; + } + + // no default Attribute? + std::unique_ptr<SwHistoryHint> pHt; + + // To be able to include the DrawingLayer FillItems something more + // general has to be done to check if an Item is default than to check + // if its pointer equals that in Writer's global PoolDefaults (held in + // aAttrTab and used to fill the pool defaults in Writer - looks as if + // Writer is *older* than the SfxItemPool ?). I checked the possibility to + // get the SfxItemPool here (works), but decided to use the SfxPoolItem's + // global tooling aka IsDefaultItem(const SfxPoolItem*) for now + if(pOldValue && !IsDefaultItem(pOldValue)) + { + pHt.reset( new SwHistorySetFormat( pOldValue, nNodeIdx ) ); + } + else + { + pHt.reset( new SwHistoryResetFormat( pNewValue, nNodeIdx ) ); + } + + m_SwpHstry.push_back( std::move(pHt) ); +} + +// FIXME: refactor the following "Add" methods (DRY)? +void SwHistory::Add( SwTextAttr* pHint, sal_uLong nNodeIdx, bool bNewAttr ) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + std::unique_ptr<SwHistoryHint> pHt; + if( !bNewAttr ) + { + switch ( pHint->Which() ) + { + case RES_TXTATR_FTN: + pHt.reset( new SwHistorySetFootnote( + static_cast<SwTextFootnote*>(pHint), nNodeIdx ) ); + break; + case RES_TXTATR_FLYCNT: + pHt.reset( new SwHistoryTextFlyCnt( static_cast<SwTextFlyCnt*>(pHint) + ->GetFlyCnt().GetFrameFormat() ) ); + break; + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + pHt.reset( new SwHistorySetTextField( + static_txtattr_cast<SwTextField*>(pHint), nNodeIdx) ); + break; + case RES_TXTATR_TOXMARK: + pHt.reset( new SwHistorySetTOXMark( + static_txtattr_cast<SwTextTOXMark*>(pHint), nNodeIdx) ); + break; + case RES_TXTATR_REFMARK: + pHt.reset( new SwHistorySetRefMark( + static_txtattr_cast<SwTextRefMark*>(pHint), nNodeIdx) ); + break; + default: + pHt.reset( new SwHistorySetText( pHint, nNodeIdx ) ); + } + } + else + { + pHt.reset( new SwHistoryResetText( pHint->Which(), pHint->GetStart(), + pHint->GetAnyEnd(), nNodeIdx ) ); + } + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::Add( SwFormatColl* pColl, sal_uLong nNodeIdx, SwNodeType nWhichNd ) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + std::unique_ptr<SwHistoryHint> pHt( + new SwHistoryChangeFormatColl( pColl, nNodeIdx, nWhichNd )); + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::Add(const ::sw::mark::IMark& rBkmk, bool bSavePos, bool bSaveOtherPos) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + std::unique_ptr<SwHistoryHint> pHt; + + switch (IDocumentMarkAccess::GetType(rBkmk)) + { + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + assert(bSavePos && bSaveOtherPos); // must be deleted completely! + pHt.reset(new SwHistoryTextFieldmark(dynamic_cast<sw::mark::IFieldmark const&>(rBkmk))); + break; + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + assert(bSavePos && bSaveOtherPos); // must be deleted completely! + pHt.reset(new SwHistoryNoTextFieldmark(dynamic_cast<sw::mark::IFieldmark const&>(rBkmk))); + break; + default: + pHt.reset(new SwHistoryBookmark(rBkmk, bSavePos, bSaveOtherPos)); + break; + } + + assert(pHt); + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::AddChangeFlyAnchor(SwFrameFormat& rFormat) +{ + std::unique_ptr<SwHistoryHint> pHt(new SwHistoryChangeFlyAnchor( rFormat )); + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::AddDeleteFly(SwFrameFormat& rFormat, sal_uInt16& rSetPos) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + const sal_uInt16 nWh = rFormat.Which(); + (void) nWh; + // only Flys! + assert((RES_FLYFRMFMT == nWh && dynamic_cast<SwFlyFrameFormat*>(&rFormat)) + || (RES_DRAWFRMFMT == nWh && dynamic_cast<SwDrawFrameFormat*>(&rFormat))); + { + std::unique_ptr<SwHistoryHint> pHint(new SwHistoryTextFlyCnt( &rFormat )); + m_SwpHstry.push_back( std::move(pHint) ); + + const SwFormatChain* pChainItem; + if( SfxItemState::SET == rFormat.GetItemState( RES_CHAIN, false, + reinterpret_cast<const SfxPoolItem**>(&pChainItem) )) + { + assert(RES_FLYFRMFMT == nWh); // not supported on SdrObjects + if( pChainItem->GetNext() || pChainItem->GetPrev() ) + { + std::unique_ptr<SwHistoryHint> pHt( + new SwHistoryChangeFlyChain(static_cast<SwFlyFrameFormat&>(rFormat), *pChainItem)); + m_SwpHstry.insert( m_SwpHstry.begin() + rSetPos++, std::move(pHt) ); + if ( pChainItem->GetNext() ) + { + SwFormatChain aTmp( pChainItem->GetNext()->GetChain() ); + aTmp.SetPrev( nullptr ); + pChainItem->GetNext()->SetFormatAttr( aTmp ); + } + if ( pChainItem->GetPrev() ) + { + SwFormatChain aTmp( pChainItem->GetPrev()->GetChain() ); + aTmp.SetNext( nullptr ); + pChainItem->GetPrev()->SetFormatAttr( aTmp ); + } + } + rFormat.ResetFormatAttr( RES_CHAIN ); + } + } +} + +void SwHistory::Add( const SwTextFootnote& rFootnote ) +{ + std::unique_ptr<SwHistoryHint> pHt(new SwHistorySetFootnote( rFootnote )); + m_SwpHstry.push_back( std::move(pHt) ); +} + +// #i27615# +void SwHistory::Add(const SfxItemSet & rSet, const SwCharFormat & rFormat) +{ + std::unique_ptr<SwHistoryHint> pHt(new SwHistoryChangeCharFormat(rSet, rFormat.GetName())); + m_SwpHstry.push_back( std::move(pHt) ); +} + +bool SwHistory::Rollback( SwDoc* pDoc, sal_uInt16 nStart ) +{ + if ( !Count() ) + return false; + + for ( sal_uInt16 i = Count(); i > nStart ; ) + { + SwHistoryHint * pHHt = m_SwpHstry[ --i ].get(); + pHHt->SetInDoc( pDoc, false ); + } + m_SwpHstry.erase( m_SwpHstry.begin() + nStart, m_SwpHstry.end() ); + m_nEndDiff = 0; + return true; +} + +bool SwHistory::TmpRollback( SwDoc* pDoc, sal_uInt16 nStart, bool bToFirst ) +{ + sal_uInt16 nEnd = Count() - m_nEndDiff; + if ( !Count() || !nEnd || nStart >= nEnd ) + return false; + + if ( bToFirst ) + { + for ( ; nEnd > nStart; ++m_nEndDiff ) + { + SwHistoryHint* pHHt = m_SwpHstry[ --nEnd ].get(); + pHHt->SetInDoc( pDoc, true ); + } + } + else + { + for ( ; nStart < nEnd; ++m_nEndDiff, ++nStart ) + { + SwHistoryHint* pHHt = m_SwpHstry[ nStart ].get(); + pHHt->SetInDoc( pDoc, true ); + } + } + return true; +} + +sal_uInt16 SwHistory::SetTmpEnd( sal_uInt16 nNewTmpEnd ) +{ + OSL_ENSURE( nNewTmpEnd <= Count(), "SwHistory::SetTmpEnd: out of bounds" ); + + const sal_uInt16 nOld = Count() - m_nEndDiff; + m_nEndDiff = Count() - nNewTmpEnd; + + // for every SwHistoryFlyCnt, call the Redo of its UndoObject. + // this saves the formats of the flys! + for ( sal_uInt16 n = nOld; n < nNewTmpEnd; n++ ) + { + if ( HSTRY_FLYCNT == (*this)[ n ]->Which() ) + { + static_cast<SwHistoryTextFlyCnt*>((*this)[ n ]) + ->GetUDelLFormat()->RedoForRollback(); + } + } + + return nOld; +} + +void SwHistory::CopyFormatAttr( + const SfxItemSet& rSet, + sal_uLong nNodeIdx) +{ + if(rSet.Count()) + { + SfxItemIter aIter(rSet); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if(!IsInvalidItem(pItem)) + { + Add(pItem, pItem, nNodeIdx); + } + + pItem = aIter.NextItem(); + + } while(pItem); + } +} + +void SwHistory::CopyAttr( + SwpHints const * pHts, + const sal_uLong nNodeIdx, + const sal_Int32 nStart, + const sal_Int32 nEnd, + const bool bCopyFields ) +{ + if( !pHts ) + return; + + // copy all attributes of the TextNode in the area from nStart to nEnd + SwTextAttr* pHt; + for( size_t n = 0; n < pHts->Count(); ++n ) + { + // nAttrStt must even be set when !pEndIdx + pHt = pHts->Get(n); + const sal_Int32 nAttrStt = pHt->GetStart(); + const sal_Int32 * pEndIdx = pHt->GetEnd(); + if( nullptr != pEndIdx && nAttrStt > nEnd ) + break; + + // never copy Flys and Footnote !! + bool bNextAttr = false; + switch( pHt->Which() ) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + if( !bCopyFields ) + bNextAttr = true; + break; + case RES_TXTATR_FLYCNT: + case RES_TXTATR_FTN: + bNextAttr = true; + break; + } + + if( bNextAttr ) + continue; + + // save all attributes that are somehow in this area + if ( nStart <= nAttrStt ) + { + if ( nEnd > nAttrStt ) + { + Add( pHt, nNodeIdx, false ); + } + } + else if ( pEndIdx && nStart < *pEndIdx ) + { + Add( pHt, nNodeIdx, false ); + } + } +} + +// Class to register the history at a Node, Format, HintsArray, ... +SwRegHistory::SwRegHistory( SwHistory* pHst ) + : SwClient( nullptr ) + , m_pHistory( pHst ) + , m_nNodeIndex( ULONG_MAX ) +{ + MakeSetWhichIds(); +} + +SwRegHistory::SwRegHistory( SwModify* pRegIn, const SwNode& rNd, + SwHistory* pHst ) + : SwClient( pRegIn ) + , m_pHistory( pHst ) + , m_nNodeIndex( rNd.GetIndex() ) +{ + MakeSetWhichIds(); +} + +SwRegHistory::SwRegHistory( const SwNode& rNd, SwHistory* pHst ) + : SwClient( nullptr ) + , m_pHistory( pHst ) + , m_nNodeIndex( rNd.GetIndex() ) +{ + MakeSetWhichIds(); +} + +void SwRegHistory::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if ( m_pHistory && pNew && pOld != pNew ) + { + if ( pNew->Which() < POOLATTR_END ) + { + if(RES_UPDATE_ATTR == pNew->Which()) + { + // const SfxItemPool& rPool = static_cast< const SwUpdateAttr* >(pNew)->GetSfxItemPool(); + + m_pHistory->Add( + // rPool, + pOld, + pNew, + m_nNodeIndex); + } + else + { + OSL_ENSURE(false, "Unexpected update attribute (!)"); + } + } + else if (pOld && RES_ATTRSET_CHG == pNew->Which()) + { + std::unique_ptr<SwHistoryHint> pNewHstr; + const SfxItemSet& rSet = *static_cast< const SwAttrSetChg* >(pOld)->GetChgSet(); + + if ( 1 < rSet.Count() ) + { + pNewHstr.reset( new SwHistorySetAttrSet( rSet, m_nNodeIndex, m_WhichIdSet ) ); + } + else if (const SfxPoolItem* pItem = SfxItemIter(rSet).GetCurItem()) + { + if ( m_WhichIdSet.count( pItem->Which() ) ) + { + pNewHstr.reset( new SwHistorySetFormat( pItem, m_nNodeIndex ) ); + } + else + { + pNewHstr.reset( new SwHistoryResetFormat( pItem, m_nNodeIndex ) ); + } + } + + if (pNewHstr) + m_pHistory->m_SwpHstry.push_back( std::move(pNewHstr) ); + } + } +} + +void SwRegHistory::AddHint( SwTextAttr* pHt, const bool bNew ) +{ + m_pHistory->Add( pHt, m_nNodeIndex, bNew ); +} + +bool SwRegHistory::InsertItems( const SfxItemSet& rSet, + sal_Int32 const nStart, sal_Int32 const nEnd, SetAttrMode const nFlags, + SwTextAttr **ppNewTextAttr ) +{ + if( !rSet.Count() ) + return false; + + SwTextNode * const pTextNode = + dynamic_cast<SwTextNode *>(GetRegisteredIn()); + + OSL_ENSURE(pTextNode, "SwRegHistory not registered at text node?"); + if (!pTextNode) + return false; + + if (m_pHistory) + { + pTextNode->GetOrCreateSwpHints().Register(this); + } + + const bool bInserted = pTextNode->SetAttr( rSet, nStart, nEnd, nFlags, ppNewTextAttr ); + + // Caution: The array can be deleted when inserting an attribute! + // This can happen when the value that should be added first deletes + // an existing attribute but does not need to be added itself because + // the paragraph attributes are identical + // ( -> bForgetAttr in SwpHints::Insert ) + if ( pTextNode->GetpSwpHints() && m_pHistory ) + { + pTextNode->GetpSwpHints()->DeRegister(); + } + +#ifndef NDEBUG + if ( m_pHistory && bInserted ) + { + SfxItemIter aIter(rSet); + for (SfxPoolItem const* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { // check that the history recorded a hint to reset every item + sal_uInt16 const nWhich(pItem->Which()); + sal_uInt16 const nExpected( + (isCHRATR(nWhich) || RES_TXTATR_UNKNOWN_CONTAINER == nWhich) + ? RES_TXTATR_AUTOFMT + : nWhich); + if (RES_TXTATR_AUTOFMT == nExpected) + continue; // special case, may get set on text node itself + // tdf#105077 even worse, node's set could cause + // nothing at all to be inserted + assert(std::any_of( + m_pHistory->m_SwpHstry.begin(), m_pHistory->m_SwpHstry.end(), + [nExpected](std::unique_ptr<SwHistoryHint> const& pHint) -> bool { + SwHistoryResetText const*const pReset( + dynamic_cast<SwHistoryResetText const*>(pHint.get())); + return pReset && (pReset->GetWhich() == nExpected); + })); + } + } +#endif + + return bInserted; +} + +void SwRegHistory::RegisterInModify( SwModify* pRegIn, const SwNode& rNd ) +{ + if ( m_pHistory && pRegIn ) + { + pRegIn->Add( this ); + m_nNodeIndex = rNd.GetIndex(); + MakeSetWhichIds(); + } + else + { + m_WhichIdSet.clear(); + } +} + +void SwRegHistory::MakeSetWhichIds() +{ + if (!m_pHistory) return; + + m_WhichIdSet.clear(); + + if( GetRegisteredIn() ) + { + const SfxItemSet* pSet = nullptr; + if( dynamic_cast< const SwContentNode *>( GetRegisteredIn() ) != nullptr ) + { + pSet = static_cast<SwContentNode*>( + GetRegisteredIn())->GetpSwAttrSet(); + } + else if ( dynamic_cast< const SwFormat *>( GetRegisteredIn() ) != nullptr ) + { + pSet = &static_cast<SwFormat*>( + GetRegisteredIn())->GetAttrSet(); + } + if( pSet && pSet->Count() ) + { + SfxItemIter aIter( *pSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + sal_uInt16 nW = pItem->Which(); + m_WhichIdSet.insert( nW ); + } + } + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unattr.cxx b/sw/source/core/undo/unattr.cxx new file mode 100644 index 000000000..bccbaba80 --- /dev/null +++ b/sw/source/core/undo/unattr.cxx @@ -0,0 +1,1055 @@ +/* -*- 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 <sal/config.h> + +#include <utility> + +#include <UndoAttribute.hxx> +#include <svl/itemiter.hxx> +#include <editeng/tstpitem.hxx> +#include <svx/svdobj.hxx> +#include <hintids.hxx> +#include <fmtflcnt.hxx> +#include <txtftn.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <frmfmt.hxx> +#include <fmtcntnt.hxx> +#include <ftnidx.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IShellCursorSupplier.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swtblfmt.hxx> +#include <UndoCore.hxx> +#include <hints.hxx> +#include <rolbck.hxx> +#include <ndnotxt.hxx> +#include <ftninfo.hxx> +#include <redline.hxx> +#include <section.hxx> +#include <charfmt.hxx> +#include <calbck.hxx> +#include <frameformats.hxx> + +SwUndoFormatAttrHelper::SwUndoFormatAttrHelper( SwFormat& rFormat, bool bSvDrwPt ) + : SwClient( &rFormat ) + , m_bSaveDrawPt( bSvDrwPt ) +{ +} + +void SwUndoFormatAttrHelper::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( pOld ) { + if ( pOld->Which() == RES_OBJECTDYING ) { + CheckRegistration( pOld ); + } else if ( pNew ) { + const SwDoc& rDoc = *static_cast<SwFormat*>(GetRegisteredInNonConst())->GetDoc(); + if( POOLATTR_END >= pOld->Which() ) { + if ( GetUndo() ) { + m_pUndo->PutAttr( *pOld, rDoc ); + } else { + m_pUndo.reset( new SwUndoFormatAttr( *pOld, + *static_cast<SwFormat*>(GetRegisteredInNonConst()), m_bSaveDrawPt ) ); + } + } else if ( RES_ATTRSET_CHG == pOld->Which() ) { + if ( GetUndo() ) { + SfxItemIter aIter( + *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + for (auto pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + m_pUndo->PutAttr( *pItem, rDoc ); + } + } else { + m_pUndo.reset( new SwUndoFormatAttr( + *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet(), + *static_cast<SwFormat*>(GetRegisteredInNonConst()), m_bSaveDrawPt ) ); + } + } + } + } +} + +SwUndoFormatAttr::SwUndoFormatAttr( const SfxItemSet& rOldSet, + SwFormat& rChgFormat, + bool bSaveDrawPt ) + : SwUndo( SwUndoId::INSFMTATTR, rChgFormat.GetDoc() ) + , m_sFormatName ( rChgFormat.GetName() ) + // #i56253# + , m_pOldSet( new SfxItemSet( rOldSet ) ) + , m_nNodeIndex( 0 ) + , m_nFormatWhich( rChgFormat.Which() ) + , m_bSaveDrawPt( bSaveDrawPt ) +{ + Init( rChgFormat ); +} + +SwUndoFormatAttr::SwUndoFormatAttr( const SfxPoolItem& rItem, SwFormat& rChgFormat, + bool bSaveDrawPt ) + : SwUndo( SwUndoId::INSFMTATTR, rChgFormat.GetDoc() ) + , m_sFormatName(rChgFormat.GetName()) + , m_pOldSet( rChgFormat.GetAttrSet().Clone( false ) ) + , m_nNodeIndex( 0 ) + , m_nFormatWhich( rChgFormat.Which() ) + , m_bSaveDrawPt( bSaveDrawPt ) +{ + m_pOldSet->Put( rItem ); + Init( rChgFormat ); +} + +void SwUndoFormatAttr::Init( const SwFormat & rFormat ) +{ + // tdf#126017 never save SwNodeIndex, it will go stale + m_pOldSet->ClearItem(RES_CNTNT); + // treat change of anchor specially + if ( SfxItemState::SET == m_pOldSet->GetItemState( RES_ANCHOR, false )) { + SaveFlyAnchor( &rFormat, m_bSaveDrawPt ); + } else if ( RES_FRMFMT == m_nFormatWhich ) { + const SwDoc* pDoc = rFormat.GetDoc(); + if (pDoc->GetTableFrameFormats()->ContainsFormat(dynamic_cast<const SwFrameFormat&>(rFormat))) + { + // Table Format: save table position, table formats are volatile! + SwTable * pTable = SwIterator<SwTable,SwFormat>( rFormat ).First(); + if ( pTable ) { + m_nNodeIndex = pTable->GetTabSortBoxes()[ 0 ]->GetSttNd() + ->FindTableNode()->GetIndex(); + } + } else if (pDoc->GetSections().ContainsFormat(&rFormat)) { + m_nNodeIndex = rFormat.GetContent().GetContentIdx()->GetIndex(); + } else if ( dynamic_cast< const SwTableBoxFormat* >( &rFormat ) != nullptr ) { + SwTableBox * pTableBox = SwIterator<SwTableBox,SwFormat>( rFormat ).First(); + if ( pTableBox ) { + m_nNodeIndex = pTableBox->GetSttIdx(); + } + } + } +} + +SwUndoFormatAttr::~SwUndoFormatAttr() +{ +} + +void SwUndoFormatAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + // OD 2004-10-26 #i35443# + // Important note: <Undo(..)> also called by <ReDo(..)> + + if (!m_pOldSet) + return; + + SwFormat * pFormat = GetFormat(rContext.GetDoc()); + if (!pFormat) + return; + + // #i35443# - If anchor attribute has been successful + // restored, all other attributes are also restored. + // Thus, keep track of its restoration + bool bAnchorAttrRestored( false ); + if ( SfxItemState::SET == m_pOldSet->GetItemState( RES_ANCHOR, false )) { + bAnchorAttrRestored = RestoreFlyAnchor(rContext); + if ( bAnchorAttrRestored ) { + // Anchor attribute successful restored. + // Thus, keep anchor position for redo + SaveFlyAnchor(pFormat); + } else { + // Anchor attribute not restored due to invalid anchor position. + // Thus, delete anchor attribute. + m_pOldSet->ClearItem( RES_ANCHOR ); + } + } + + if ( !bAnchorAttrRestored ) { + SwUndoFormatAttrHelper aTmp( *pFormat, m_bSaveDrawPt ); + pFormat->SetFormatAttr( *m_pOldSet ); + if ( aTmp.GetUndo() ) { + // transfer ownership of helper object's old set + m_pOldSet = std::move(aTmp.GetUndo()->m_pOldSet); + } else { + m_pOldSet->ClearItem(); + } + + if ( RES_FLYFRMFMT == m_nFormatWhich || RES_DRAWFRMFMT == m_nFormatWhich ) { + rContext.SetSelections(static_cast<SwFrameFormat*>(pFormat), nullptr); + } + } +} + +// Check if it is still in Doc +SwFormat* SwUndoFormatAttr::GetFormat( const SwDoc& rDoc ) +{ + switch (m_nFormatWhich) + { + case RES_TXTFMTCOLL: + case RES_CONDTXTFMTCOLL: + return rDoc.FindTextFormatCollByName(m_sFormatName); + + case RES_GRFFMTCOLL: + return SwDoc::FindFormatByName(*rDoc.GetGrfFormatColls(), m_sFormatName); + + case RES_CHRFMT: + return rDoc.FindCharFormatByName(m_sFormatName); + + case RES_FRMFMT: + if (m_nNodeIndex && (m_nNodeIndex < rDoc.GetNodes().Count())) + { + SwNode* pNd = rDoc.GetNodes()[m_nNodeIndex]; + if (pNd->IsTableNode()) + { + return static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat(); + } + else if (pNd->IsSectionNode()) + { + return static_cast<SwSectionNode*>(pNd)->GetSection().GetFormat(); + } + else if (pNd->IsStartNode() && (SwTableBoxStartNode == + static_cast<SwStartNode*>(pNd)->GetStartNodeType())) + { + SwTableNode* pTableNode = pNd->FindTableNode(); + if (pTableNode) + { + SwTableBox* pBox = pTableNode->GetTable().GetTableBox(m_nNodeIndex); + if (pBox) + { + return pBox->GetFrameFormat(); + } + } + } + } + [[fallthrough]]; + case RES_DRAWFRMFMT: + case RES_FLYFRMFMT: + { + SwFormat * pFormat = SwDoc::FindFormatByName(*rDoc.GetSpzFrameFormats(), m_sFormatName); + if (pFormat) + return pFormat; + pFormat = SwDoc::FindFormatByName(*rDoc.GetFrameFormats(), m_sFormatName); + if (pFormat) + return pFormat; + } + break; + } + + return nullptr; +} + +void SwUndoFormatAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // #i35443# - Because the undo stores the attributes for + // redo, the same code as for <Undo(..)> can be applied for <Redo(..)> + UndoImpl(rContext); +} + +void SwUndoFormatAttr::RepeatImpl(::sw::RepeatContext & rContext) +{ + if (!m_pOldSet) + return; + + SwDoc & rDoc(rContext.GetDoc()); + + SwFormat * pFormat = GetFormat(rDoc); + if (!pFormat) + return; + + switch ( m_nFormatWhich ) { + case RES_GRFFMTCOLL: { + SwNoTextNode *const pNd = + rContext.GetRepeatPaM().GetNode().GetNoTextNode(); + if( pNd ) { + rDoc.SetAttr( pFormat->GetAttrSet(), *pNd->GetFormatColl() ); + } + } + break; + + case RES_TXTFMTCOLL: + case RES_CONDTXTFMTCOLL: + { + SwTextNode *const pNd = + rContext.GetRepeatPaM().GetNode().GetTextNode(); + if( pNd ) { + rDoc.SetAttr( pFormat->GetAttrSet(), *pNd->GetFormatColl() ); + } + } + break; + + case RES_FLYFRMFMT: { + // Check if the cursor is in a flying frame + // Steps: search in all FlyFrameFormats for the FlyContent attribute + // and validate if the cursor is in the respective section + SwFrameFormat *const pFly = + rContext.GetRepeatPaM().GetNode().GetFlyFormat(); + if( pFly ) { + // Bug 43672: do not set all attributes! + if (SfxItemState::SET == + pFormat->GetAttrSet().GetItemState( RES_CNTNT )) { + SfxItemSet aTmpSet( pFormat->GetAttrSet() ); + aTmpSet.ClearItem( RES_CNTNT ); + if( aTmpSet.Count() ) { + rDoc.SetAttr( aTmpSet, *pFly ); + } + } else { + rDoc.SetAttr( pFormat->GetAttrSet(), *pFly ); + } + } + break; + } + } +} + +SwRewriter SwUndoFormatAttr::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sFormatName); + + return aRewriter; +} + +void SwUndoFormatAttr::PutAttr( const SfxPoolItem& rItem, const SwDoc& rDoc ) +{ + if (RES_CNTNT == rItem.Which()) + { + return; // tdf#126017 never save SwNodeIndex, it will go stale + } + m_pOldSet->Put( rItem ); + if ( RES_ANCHOR == rItem.Which() ) + { + SwFormat * pFormat = GetFormat( rDoc ); + SaveFlyAnchor( pFormat, m_bSaveDrawPt ); + } +} + +void SwUndoFormatAttr::SaveFlyAnchor( const SwFormat * pFormat, bool bSvDrwPt ) +{ + // Format is valid, otherwise you would not reach this point here + if( bSvDrwPt ) { + if ( RES_DRAWFRMFMT == pFormat->Which() ) { + Point aPt( static_cast<const SwFrameFormat*>(pFormat)->FindSdrObject() + ->GetRelativePos() ); + // store old value as attribute, to keep SwUndoFormatAttr small + m_pOldSet->Put( SwFormatFrameSize( SwFrameSize::Variable, aPt.X(), aPt.Y() ) ); + } + } + + const SwFormatAnchor& rAnchor = + m_pOldSet->Get( RES_ANCHOR, false ); + if( !rAnchor.GetContentAnchor() ) + return; + + sal_Int32 nContent = 0; + switch( rAnchor.GetAnchorId() ) { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + nContent = rAnchor.GetContentAnchor()->nContent.GetIndex(); + [[fallthrough]]; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nNodeIndex = rAnchor.GetContentAnchor()->nNode.GetIndex(); + break; + default: + return; + } + + SwFormatAnchor aAnchor( rAnchor.GetAnchorId(), nContent ); + m_pOldSet->Put( aAnchor ); +} + +// #i35443# - Add return value, type <bool>. +// Return value indicates, if anchor attribute is restored. +// Note: If anchor attribute is restored, all other existing attributes +// are also restored. +bool SwUndoFormatAttr::RestoreFlyAnchor(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwFrameFormat* pFrameFormat = static_cast<SwFrameFormat*>( GetFormat( *pDoc ) ); + const SwFormatAnchor& rAnchor = + m_pOldSet->Get( RES_ANCHOR, false ); + + SwFormatAnchor aNewAnchor( rAnchor.GetAnchorId() ); + if (RndStdIds::FLY_AT_PAGE != rAnchor.GetAnchorId()) { + SwNode* pNd = pDoc->GetNodes()[ m_nNodeIndex ]; + + if ( (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) + ? ( !pNd->IsStartNode() || (SwFlyStartNode != + static_cast<SwStartNode*>(pNd)->GetStartNodeType()) ) + : !pNd->IsTextNode() ) { + // #i35443# - invalid position. + // Thus, anchor attribute not restored + return false; + } + + SwPosition aPos( *pNd ); + if ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) { + aPos.nContent.Assign( static_cast<SwTextNode*>(pNd), rAnchor.GetPageNum() ); + if ( aPos.nContent.GetIndex() > pNd->GetTextNode()->GetText().getLength()) { + // #i35443# - invalid position. + // Thus, anchor attribute not restored + return false; + } + } + aNewAnchor.SetAnchor( &aPos ); + } else + aNewAnchor.SetPageNum( rAnchor.GetPageNum() ); + + Point aDrawSavePt, aDrawOldPt; + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) { + if( RES_DRAWFRMFMT == pFrameFormat->Which() ) { + // get the old cached value + const SwFormatFrameSize& rOldSize = m_pOldSet->Get( RES_FRM_SIZE ); + aDrawSavePt.setX( rOldSize.GetWidth() ); + aDrawSavePt.setY( rOldSize.GetHeight() ); + m_pOldSet->ClearItem( RES_FRM_SIZE ); + + // write the current value into cache + aDrawOldPt = pFrameFormat->FindSdrObject()->GetRelativePos(); + } else { + pFrameFormat->DelFrames(); // delete Frames + } + } + + const SwFormatAnchor &rOldAnch = pFrameFormat->GetAnchor(); + // #i54336# + // Consider case, that as-character anchored object has moved its anchor position. + if (RndStdIds::FLY_AS_CHAR == rOldAnch.GetAnchorId()) { + // With InContents it's tricky: the text attribute needs to be deleted. + // Unfortunately, this not only destroys the Frames but also the format. + // To prevent that, first detach the connection between attribute and + // format. + const SwPosition *pPos = rOldAnch.GetContentAnchor(); + SwTextNode *pTextNode = static_cast<SwTextNode*>(&pPos->nNode.GetNode()); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing Hint."); + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt().GetFrameFormat() == pFrameFormat, + "Wrong TextFlyCnt-Hint." ); + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat(); + + // Connection is now detached, therefore the attribute can be deleted + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + + { + m_pOldSet->Put( aNewAnchor ); + SwUndoFormatAttrHelper aTmp( *pFrameFormat, m_bSaveDrawPt ); + pFrameFormat->SetFormatAttr( *m_pOldSet ); + if ( aTmp.GetUndo() ) { + m_nNodeIndex = aTmp.GetUndo()->m_nNodeIndex; + // transfer ownership of helper object's old set + m_pOldSet = std::move(aTmp.GetUndo()->m_pOldSet); + } else { + m_pOldSet->ClearItem(); + } + } + + if ( RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + // The Draw model also prepared an Undo object for its right positioning + // which unfortunately is relative. Therefore block here a position + // change of the Contact object by setting the anchor. + pFrameFormat->CallSwClientNotify(sw::RestoreFlyAnchorHint(aDrawSavePt)); + // cache the old value again + m_pOldSet->Put(SwFormatFrameSize(SwFrameSize::Variable, aDrawOldPt.X(), aDrawOldPt.Y())); + } + + if (RndStdIds::FLY_AS_CHAR == aNewAnchor.GetAnchorId()) { + const SwPosition* pPos = aNewAnchor.GetContentAnchor(); + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "no Text Node at position." ); + SwFormatFlyCnt aFormat( pFrameFormat ); + pTextNd->InsertItem( aFormat, pPos->nContent.GetIndex(), 0 ); + } + + if (RES_DRAWFRMFMT != pFrameFormat->Which()) + pFrameFormat->MakeFrames(); + else + { + pFrameFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR)); + } + + rContext.SetSelections(pFrameFormat, nullptr); + + // #i35443# - anchor attribute restored. + return true; +} + +SwUndoFormatResetAttr::SwUndoFormatResetAttr( SwFormat& rChangedFormat, + const sal_uInt16 nWhichId ) + : SwUndo( SwUndoId::RESETATTR, rChangedFormat.GetDoc() ) + , m_pChangedFormat( &rChangedFormat ) + , m_nWhichId( nWhichId ) +{ + const SfxPoolItem* pItem = nullptr; + if (rChangedFormat.GetItemState(nWhichId, false, &pItem ) == SfxItemState::SET && pItem) { + m_pOldItem.reset( pItem->Clone() ); + } +} + +SwUndoFormatResetAttr::~SwUndoFormatResetAttr() +{ +} + +void SwUndoFormatResetAttr::UndoImpl(::sw::UndoRedoContext &) +{ + if (m_pOldItem) + { + m_pChangedFormat->SetFormatAttr( *m_pOldItem ); + } +} + +void SwUndoFormatResetAttr::RedoImpl(::sw::UndoRedoContext &) +{ + if (m_pOldItem) + { + m_pChangedFormat->ResetFormatAttr( m_nWhichId ); + } +} + +SwUndoResetAttr::SwUndoResetAttr( const SwPaM& rRange, sal_uInt16 nFormatId ) + : SwUndo( SwUndoId::RESETATTR, rRange.GetDoc() ), SwUndRng( rRange ) + , m_pHistory( new SwHistory ) + , m_nFormatId( nFormatId ) +{ +} + +SwUndoResetAttr::SwUndoResetAttr( const SwPosition& rPos, sal_uInt16 nFormatId ) + : SwUndo( SwUndoId::RESETATTR, rPos.GetDoc() ) + , m_pHistory( new SwHistory ) + , m_nFormatId( nFormatId ) +{ + m_nSttNode = m_nEndNode = rPos.nNode.GetIndex(); + m_nSttContent = m_nEndContent = rPos.nContent.GetIndex(); +} + +SwUndoResetAttr::~SwUndoResetAttr() +{ +} + +void SwUndoResetAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + // reset old values + SwDoc & rDoc = rContext.GetDoc(); + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + if ((RES_CONDTXTFMTCOLL == m_nFormatId) && + (m_nSttNode == m_nEndNode) && (m_nSttContent == m_nEndContent)) { + SwTextNode* pTNd = rDoc.GetNodes()[ m_nSttNode ]->GetTextNode(); + if( pTNd ) { + SwIndex aIdx( pTNd, m_nSttContent ); + pTNd->DontExpandFormat( aIdx, false ); + } + } + + AddUndoRedoPaM(rContext); +} + +void SwUndoResetAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam = AddUndoRedoPaM(rContext); + + switch ( m_nFormatId ) { + case RES_CHRFMT: + rDoc.RstTextAttrs(rPam); + break; + case RES_TXTFMTCOLL: + rDoc.ResetAttrs(rPam, false, m_Ids ); + break; + case RES_CONDTXTFMTCOLL: + rDoc.ResetAttrs(rPam, true, m_Ids ); + + break; + case RES_TXTATR_TOXMARK: + // special treatment for TOXMarks + { + SwTOXMarks aArr; + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode ); + SwPosition aPos( aIdx, SwIndex( aIdx.GetNode().GetContentNode(), + m_nSttContent )); + + sal_uInt16 nCnt = SwDoc::GetCurTOXMark( aPos, aArr ); + if( nCnt ) { + if( 1 < nCnt ) { + // search for the right one + SwHistoryHint* pHHint = (GetHistory())[ 0 ]; + if( pHHint && HSTRY_SETTOXMARKHNT == pHHint->Which() ) { + while( nCnt ) { + if ( static_cast<SwHistorySetTOXMark*>(pHHint) + ->IsEqual( *aArr[ --nCnt ] ) ) { + ++nCnt; + break; + } + } + } else + nCnt = 0; + } + // found one, thus delete it + if( nCnt-- ) { + rDoc.DeleteTOXMark( aArr[ nCnt ] ); + } + } + } + break; + } +} + +void SwUndoResetAttr::RepeatImpl(::sw::RepeatContext & rContext) +{ + if (m_nFormatId < RES_FMT_BEGIN) { + return; + } + + switch ( m_nFormatId ) { + case RES_CHRFMT: + rContext.GetDoc().RstTextAttrs(rContext.GetRepeatPaM()); + break; + case RES_TXTFMTCOLL: + rContext.GetDoc().ResetAttrs(rContext.GetRepeatPaM(), false, m_Ids); + break; + case RES_CONDTXTFMTCOLL: + rContext.GetDoc().ResetAttrs(rContext.GetRepeatPaM(), true, m_Ids); + break; + } +} + +void SwUndoResetAttr::SetAttrs( const std::set<sal_uInt16> &rAttrs ) +{ + m_Ids = rAttrs; +} + +SwUndoAttr::SwUndoAttr( const SwPaM& rRange, const SfxPoolItem& rAttr, + const SetAttrMode nFlags ) + : SwUndo( SwUndoId::INSATTR, rRange.GetDoc() ), SwUndRng( rRange ) + , m_AttrSet( rRange.GetDoc()->GetAttrPool(), {{rAttr.Which(), rAttr.Which()}} ) + , m_pHistory( new SwHistory ) + , m_nNodeIndex( ULONG_MAX ) + , m_nInsertFlags( nFlags ) +{ + m_AttrSet.Put( rAttr ); + + // Save character style as a style name, not as a reference + const SfxPoolItem* pItem = m_AttrSet.GetItem(RES_TXTATR_CHARFMT); + if (pItem) + { + uno::Any aValue; + pItem->QueryValue(aValue, RES_TXTATR_CHARFMT); + aValue >>= m_aChrFormatName; + } +} + +SwUndoAttr::SwUndoAttr( const SwPaM& rRange, const SfxItemSet& rSet, + const SetAttrMode nFlags ) + : SwUndo( SwUndoId::INSATTR, rRange.GetDoc() ), SwUndRng( rRange ) + , m_AttrSet( rSet ) + , m_pHistory( new SwHistory ) + , m_nNodeIndex( ULONG_MAX ) + , m_nInsertFlags( nFlags ) +{ + // Save character style as a style name, not as a reference + const SfxPoolItem* pItem = m_AttrSet.GetItem(RES_TXTATR_CHARFMT); + if (pItem) + { + uno::Any aValue; + pItem->QueryValue(aValue, RES_TXTATR_CHARFMT); + aValue >>= m_aChrFormatName; + } +} + +SwUndoAttr::~SwUndoAttr() +{ +} + +void SwUndoAttr::SaveRedlineData( const SwPaM& rPam, bool bIsContent ) +{ + SwDoc* pDoc = rPam.GetDoc(); + if ( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) { + m_pRedlineData.reset( new SwRedlineData( bIsContent + ? RedlineType::Insert + : RedlineType::Format, + pDoc->getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + } + + m_pRedlineSaveData.reset( new SwRedlineSaveDatas ); + if ( !FillSaveDataForFormat( rPam, *m_pRedlineSaveData )) + m_pRedlineSaveData.reset(); + + SetRedlineFlags( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + if ( bIsContent ) { + m_nNodeIndex = rPam.GetPoint()->nNode.GetIndex(); + } +} + +void SwUndoAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + + RemoveIdx( *pDoc ); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) { + SwPaM aPam(pDoc->GetNodes().GetEndOfContent()); + if ( ULONG_MAX != m_nNodeIndex ) { + aPam.DeleteMark(); + aPam.GetPoint()->nNode = m_nNodeIndex; + aPam.GetPoint()->nContent.Assign( aPam.GetContentNode(), m_nSttContent ); + aPam.SetMark(); + ++aPam.GetPoint()->nContent; + pDoc->getIDocumentRedlineAccess().DeleteRedline(aPam, false, RedlineType::Any); + } else { + // remove all format redlines, will be recreated if needed + SetPaM(aPam); + pDoc->getIDocumentRedlineAccess().DeleteRedline(aPam, false, RedlineType::Format); + if (m_pRedlineSaveData) + { + SetSaveData( *pDoc, *m_pRedlineSaveData ); + } + } + } + + const bool bToLast = (1 == m_AttrSet.Count()) + && (RES_TXTATR_FIELD <= *m_AttrSet.GetRanges()) + && (*m_AttrSet.GetRanges() <= RES_TXTATR_ANNOTATION); + + // restore old values + m_pHistory->TmpRollback( pDoc, 0, !bToLast ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + // set cursor onto Undo area + AddUndoRedoPaM(rContext); +} + +void SwUndoAttr::RepeatImpl(::sw::RepeatContext & rContext) +{ + // RefMarks are not repeat capable + if ( SfxItemState::SET != m_AttrSet.GetItemState( RES_TXTATR_REFMARK, false ) ) { + rContext.GetDoc().getIDocumentContentOperations().InsertItemSet( rContext.GetRepeatPaM(), + m_AttrSet, m_nInsertFlags ); + } else if ( 1 < m_AttrSet.Count() ) { + SfxItemSet aTmpSet( m_AttrSet ); + aTmpSet.ClearItem( RES_TXTATR_REFMARK ); + rContext.GetDoc().getIDocumentContentOperations().InsertItemSet( rContext.GetRepeatPaM(), + aTmpSet, m_nInsertFlags ); + } +} + +void SwUndoAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam = AddUndoRedoPaM(rContext); + + // Restore pointer to char format from name + if (!m_aChrFormatName.isEmpty()) + { + SwCharFormat* pCharFormat = rDoc.FindCharFormatByName(m_aChrFormatName); + if (pCharFormat) + { + SwFormatCharFormat aFormat(pCharFormat); + m_AttrSet.Put(aFormat); + } + } + + if ( m_pRedlineData && + IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore ); + rDoc.getIDocumentContentOperations().InsertItemSet( rPam, m_AttrSet, m_nInsertFlags ); + + if ( ULONG_MAX != m_nNodeIndex ) { + rPam.SetMark(); + if ( rPam.Move( fnMoveBackward ) ) { + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), + true); + } + rPam.DeleteMark(); + } else { + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true); + } + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } else { + rDoc.getIDocumentContentOperations().InsertItemSet( rPam, m_AttrSet, m_nInsertFlags ); + } +} + +void SwUndoAttr::RemoveIdx( SwDoc& rDoc ) +{ + if ( SfxItemState::SET != m_AttrSet.GetItemState( RES_TXTATR_FTN, false )) + return ; + + SwNodes& rNds = rDoc.GetNodes(); + for ( sal_uInt16 n = 0; n < m_pHistory->Count(); ++n ) { + sal_Int32 nContent = 0; + sal_uLong nNode = 0; + SwHistoryHint* pHstHint = (*m_pHistory)[ n ]; + switch ( pHstHint->Which() ) { + case HSTRY_RESETTXTHNT: { + SwHistoryResetText * pHistoryHint + = static_cast<SwHistoryResetText*>(pHstHint); + if ( RES_TXTATR_FTN == pHistoryHint->GetWhich() ) { + nNode = pHistoryHint->GetNode(); + nContent = pHistoryHint->GetContent(); + } + } + break; + + default: + break; + } + + if( nNode ) { + SwTextNode* pTextNd = rNds[ nNode ]->GetTextNode(); + if( pTextNd ) { + SwTextAttr *const pTextHt = + pTextNd->GetTextAttrForCharAt(nContent, RES_TXTATR_FTN); + if( pTextHt ) { + // ok, so get values + SwTextFootnote* pFootnote = static_cast<SwTextFootnote*>(pTextHt); + RemoveIdxFromSection( rDoc, pFootnote->GetStartNode()->GetIndex() ); + return ; + } + } + } + } +} + +SwUndoDefaultAttr::SwUndoDefaultAttr( const SfxItemSet& rSet, const SwDoc* pDoc ) + : SwUndo( SwUndoId::SETDEFTATTR, pDoc ) +{ + const SfxPoolItem* pItem; + if( SfxItemState::SET == rSet.GetItemState( RES_PARATR_TABSTOP, false, &pItem ) ) { + // store separately, because it may change! + m_pTabStop.reset( static_cast<SvxTabStopItem*>(pItem->Clone()) ); + if ( 1 != rSet.Count() ) { // are there more attributes? + m_pOldSet.reset( new SfxItemSet( rSet ) ); + } + } else { + m_pOldSet.reset( new SfxItemSet( rSet ) ); + } +} + +SwUndoDefaultAttr::~SwUndoDefaultAttr() +{ +} + +void SwUndoDefaultAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if (m_pOldSet) + { + SwUndoFormatAttrHelper aTmp( + *rDoc.GetDfltTextFormatColl() ); + rDoc.SetDefault( *m_pOldSet ); + m_pOldSet.reset(); + if ( aTmp.GetUndo() ) { + // transfer ownership of helper object's old set + m_pOldSet = std::move(aTmp.GetUndo()->m_pOldSet); + } + } + if (m_pTabStop) + { + std::unique_ptr<SvxTabStopItem> pOld(rDoc.GetDefault(RES_PARATR_TABSTOP).Clone()); + rDoc.SetDefault( *m_pTabStop ); + m_pTabStop = std::move( pOld ); + } +} + +void SwUndoDefaultAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoImpl(rContext); +} + +SwUndoMoveLeftMargin::SwUndoMoveLeftMargin( + const SwPaM& rPam, bool bFlag, bool bMod ) + : SwUndo( bFlag ? SwUndoId::INC_LEFTMARGIN : SwUndoId::DEC_LEFTMARGIN, rPam.GetDoc() ) + , SwUndRng( rPam ) + , m_pHistory( new SwHistory ) + , m_bModulus( bMod ) +{ +} + +SwUndoMoveLeftMargin::~SwUndoMoveLeftMargin() +{ +} + +void SwUndoMoveLeftMargin::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // restore old values + m_pHistory->TmpRollback( & rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + AddUndoRedoPaM(rContext); +} + +void SwUndoMoveLeftMargin::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam = AddUndoRedoPaM(rContext); + + rDoc.MoveLeftMargin( rPam, + GetId() == SwUndoId::INC_LEFTMARGIN, m_bModulus, + rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ); +} + +void SwUndoMoveLeftMargin::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + rDoc.MoveLeftMargin(rContext.GetRepeatPaM(), GetId() == SwUndoId::INC_LEFTMARGIN, + m_bModulus, rDoc.getIDocumentLayoutAccess().GetCurrentLayout()); +} + +SwUndoChangeFootNote::SwUndoChangeFootNote( + const SwPaM& rRange, const OUString& rText, + bool const bIsEndNote) + : SwUndo( SwUndoId::CHGFTN, rRange.GetDoc() ), SwUndRng( rRange ) + , m_pHistory( new SwHistory() ) + , m_Text( rText ) + , m_bEndNote( bIsEndNote ) +{ +} + +SwUndoChangeFootNote::~SwUndoChangeFootNote() +{ +} + +void SwUndoChangeFootNote::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + + AddUndoRedoPaM(rContext); +} + +void SwUndoChangeFootNote::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc( rContext.GetDoc() ); + SwPaM & rPaM = AddUndoRedoPaM(rContext); + rDoc.SetCurFootnote(rPaM, m_Text, m_bEndNote); + SetPaM(rPaM); +} + +void SwUndoChangeFootNote::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + rDoc.SetCurFootnote(rContext.GetRepeatPaM(), m_Text, m_bEndNote); +} + +SwUndoFootNoteInfo::SwUndoFootNoteInfo( const SwFootnoteInfo &rInfo, const SwDoc* pDoc ) + : SwUndo( SwUndoId::FTNINFO, pDoc ) + , m_pFootNoteInfo( new SwFootnoteInfo( rInfo ) ) +{ +} + +SwUndoFootNoteInfo::~SwUndoFootNoteInfo() +{ +} + +void SwUndoFootNoteInfo::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwFootnoteInfo *pInf = new SwFootnoteInfo( rDoc.GetFootnoteInfo() ); + rDoc.SetFootnoteInfo( *m_pFootNoteInfo ); + m_pFootNoteInfo.reset( pInf ); +} + +void SwUndoFootNoteInfo::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwFootnoteInfo *pInf = new SwFootnoteInfo( rDoc.GetFootnoteInfo() ); + rDoc.SetFootnoteInfo( *m_pFootNoteInfo ); + m_pFootNoteInfo.reset( pInf ); +} + +SwUndoEndNoteInfo::SwUndoEndNoteInfo( const SwEndNoteInfo &rInfo, const SwDoc* pDoc ) + : SwUndo( SwUndoId::FTNINFO, pDoc ) + , m_pEndNoteInfo( new SwEndNoteInfo( rInfo ) ) +{ +} + +SwUndoEndNoteInfo::~SwUndoEndNoteInfo() +{ +} + +void SwUndoEndNoteInfo::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwEndNoteInfo *pInf = new SwEndNoteInfo( rDoc.GetEndNoteInfo() ); + rDoc.SetEndNoteInfo( *m_pEndNoteInfo ); + m_pEndNoteInfo.reset( pInf ); +} + +void SwUndoEndNoteInfo::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwEndNoteInfo *pInf = new SwEndNoteInfo( rDoc.GetEndNoteInfo() ); + rDoc.SetEndNoteInfo( *m_pEndNoteInfo ); + m_pEndNoteInfo.reset( pInf ); +} + +SwUndoDontExpandFormat::SwUndoDontExpandFormat( const SwPosition& rPos ) + : SwUndo( SwUndoId::DONTEXPAND, rPos.GetDoc() ) + , m_nNodeIndex( rPos.nNode.GetIndex() ) + , m_nContentIndex( rPos.nContent.GetIndex() ) +{ +} + +void SwUndoDontExpandFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + SwDoc *const pDoc = & rContext.GetDoc(); + + SwPosition& rPos = *pPam->GetPoint(); + rPos.nNode = m_nNodeIndex; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), m_nContentIndex); + pDoc->DontExpandFormat( rPos, false ); +} + +void SwUndoDontExpandFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + SwDoc *const pDoc = & rContext.GetDoc(); + + SwPosition& rPos = *pPam->GetPoint(); + rPos.nNode = m_nNodeIndex; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), m_nContentIndex); + pDoc->DontExpandFormat( rPos ); +} + +void SwUndoDontExpandFormat::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM & rPam = rContext.GetRepeatPaM(); + SwDoc & rDoc = rContext.GetDoc(); + rDoc.DontExpandFormat( *rPam.GetPoint() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unbkmk.cxx b/sw/source/core/undo/unbkmk.cxx new file mode 100644 index 000000000..16d45a7b7 --- /dev/null +++ b/sw/source/core/undo/unbkmk.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <UndoBookmark.hxx> + +#include <strings.hrc> +#include <doc.hxx> +#include <swundo.hxx> +#include <pam.hxx> + +#include <UndoCore.hxx> +#include <IMark.hxx> +#include <rolbck.hxx> + +#include <SwRewriter.hxx> + +SwUndoBookmark::SwUndoBookmark( SwUndoId nUndoId, + const ::sw::mark::IMark& rBkmk ) + : SwUndo( nUndoId, rBkmk.GetMarkPos().GetDoc() ) + , m_pHistoryBookmark(new SwHistoryBookmark(rBkmk, true, rBkmk.IsExpanded())) +{ +} + +SwUndoBookmark::~SwUndoBookmark() +{ +} + +void SwUndoBookmark::SetInDoc( SwDoc* pDoc ) +{ + m_pHistoryBookmark->SetInDoc( pDoc, false ); +} + +void SwUndoBookmark::ResetInDoc( SwDoc* pDoc ) +{ + IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess(); + for ( IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->getAllMarksBegin(); + ppBkmk != pMarkAccess->getAllMarksEnd(); + ++ppBkmk ) + { + if ( m_pHistoryBookmark->IsEqualBookmark( **ppBkmk ) ) + { + pMarkAccess->deleteMark( ppBkmk ); + break; + } + } +} + +SwRewriter SwUndoBookmark::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_pHistoryBookmark->GetName()); + + return aResult; +} + +SwUndoInsBookmark::SwUndoInsBookmark( const ::sw::mark::IMark& rBkmk ) + : SwUndoBookmark( SwUndoId::INSBOOKMARK, rBkmk ) +{ +} + +void SwUndoInsBookmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + ResetInDoc( &rContext.GetDoc() ); +} + +void SwUndoInsBookmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SetInDoc( &rContext.GetDoc() ); +} + +SwUndoDeleteBookmark::SwUndoDeleteBookmark( const ::sw::mark::IMark& rBkmk ) + : SwUndoBookmark( SwUndoId::DELBOOKMARK, rBkmk ) +{ +} + +void SwUndoDeleteBookmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SetInDoc( &rContext.GetDoc() ); +} + +void SwUndoDeleteBookmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + ResetInDoc( &rContext.GetDoc() ); +} + +SwUndoRenameBookmark::SwUndoRenameBookmark( const OUString& rOldName, const OUString& rNewName, const SwDoc* pDoc ) + : SwUndo( SwUndoId::BOOKMARK_RENAME, pDoc ) + , m_sOldName( rOldName ) + , m_sNewName( rNewName ) +{ +} + +SwUndoRenameBookmark::~SwUndoRenameBookmark() +{ +} + +static OUString lcl_QuoteName(const OUString& rName) +{ + static const OUString sStart = SwResId(STR_START_QUOTE); + static const OUString sEnd = SwResId(STR_END_QUOTE); + return sStart + rName + sEnd; +} + +SwRewriter SwUndoRenameBookmark::GetRewriter() const +{ + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, lcl_QuoteName(m_sOldName)); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, lcl_QuoteName(m_sNewName)); + return aRewriter; +} + +void SwUndoRenameBookmark::Rename(::sw::UndoRedoContext const & rContext, const OUString& sFrom, const OUString& sTo) +{ + IDocumentMarkAccess* const pMarkAccess = rContext.GetDoc().getIDocumentMarkAccess(); + IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->findMark(sFrom); + if (ppBkmk != pMarkAccess->getAllMarksEnd()) + { + pMarkAccess->renameMark( *ppBkmk, sTo ); + } +} + +void SwUndoRenameBookmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + Rename(rContext, m_sNewName, m_sOldName); +} + +void SwUndoRenameBookmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + Rename(rContext, m_sOldName, m_sNewName); +} + +SwUndoInsNoTextFieldmark::SwUndoInsNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::INSERT, rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryNoTextFieldmark(new SwHistoryNoTextFieldmark(rFieldmark)) +{ +} + +void SwUndoInsNoTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->ResetInDoc(&rContext.GetDoc()); +} + +void SwUndoInsNoTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +SwUndoDelNoTextFieldmark::SwUndoDelNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::DELETE, rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryNoTextFieldmark(new SwHistoryNoTextFieldmark(rFieldmark)) +{ +} + +SwUndoDelNoTextFieldmark::~SwUndoDelNoTextFieldmark() = default; + +void SwUndoDelNoTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +void SwUndoDelNoTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->ResetInDoc(&rContext.GetDoc()); +} + +SwUndoInsTextFieldmark::SwUndoInsTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::INSERT, rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryTextFieldmark(new SwHistoryTextFieldmark(rFieldmark)) +{ +} + +void SwUndoInsTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->ResetInDoc(&rContext.GetDoc()); +} + +void SwUndoInsTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +SwUndoDelTextFieldmark::SwUndoDelTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::DELETE, rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryTextFieldmark(new SwHistoryTextFieldmark(rFieldmark)) +{ +} + +SwUndoDelTextFieldmark::~SwUndoDelTextFieldmark() = default; + +void SwUndoDelTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +void SwUndoDelTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->ResetInDoc(&rContext.GetDoc()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undel.cxx b/sw/source/core/undo/undel.cxx new file mode 100644 index 000000000..e1e519fa2 --- /dev/null +++ b/sw/source/core/undo/undel.cxx @@ -0,0 +1,1317 @@ +/* -*- 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 <UndoDelete.hxx> +#include <hintids.hxx> +#include <rtl/ustrbuf.hxx> +#include <unotools/charclass.hxx> +#include <frmfmt.hxx> +#include <fmtanchr.hxx> +#include <doc.hxx> +#include <UndoManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <poolfmt.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <frmtool.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <strings.hrc> +#include <frameformats.hxx> +#include <vector> + +// DELETE +/* lcl_MakeAutoFrames has to call MakeFrames for objects bounded "AtChar" + ( == AUTO ), if the anchor frame has be moved via MoveNodes(..) and + DelFrames(..) +*/ +static void lcl_MakeAutoFrames( const SwFrameFormats& rSpzArr, sal_uLong nMovedIndex ) +{ + for( size_t n = 0; n < rSpzArr.size(); ++n ) + { + SwFrameFormat * pFormat = rSpzArr[n]; + const SwFormatAnchor* pAnchor = &pFormat->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + const SwPosition* pAPos = pAnchor->GetContentAnchor(); + if( pAPos && nMovedIndex == pAPos->nNode.GetIndex() ) + pFormat->MakeFrames(); + } + } +} + +static SwTextNode * FindFirstAndNextNode(SwDoc & rDoc, SwUndRng const& rRange, + SwRedlineSaveDatas const& rRedlineSaveData, + SwTextNode *& o_rpFirstMergedDeletedTextNode) +{ + // redlines are corrected now to exclude the deleted node + assert(rRange.m_nEndContent == 0); + sal_uLong nEndOfRedline = 0; + for (size_t i = 0; i < rRedlineSaveData.size(); ++i) + { + auto const& rRedline(rRedlineSaveData[i]); + if (rRedline.m_nSttNode <= rRange.m_nSttNode + && rRedline.m_nSttNode < rRange.m_nEndNode + && rRange.m_nEndNode <= rRedline.m_nEndNode + && rRedline.GetType() == RedlineType::Delete) + { + nEndOfRedline = rRedline.m_nEndNode; + o_rpFirstMergedDeletedTextNode = rDoc.GetNodes()[rRedline.m_nSttNode]->GetTextNode(); + assert(rRange.m_nSttNode == rRange.m_nEndNode - 1); // otherwise this needs to iterate more RL to find the first node? + break; + } + } + if (nEndOfRedline) + { + assert(o_rpFirstMergedDeletedTextNode); + SwTextNode * pNextNode(nullptr); + for (sal_uLong i = rRange.m_nEndNode; /* i <= nEndOfRedline */; ++i) + { + SwNode *const pNode(rDoc.GetNodes()[i]); + assert(!pNode->IsEndNode()); // cannot be both leaving section here *and* overlapping redline + if (pNode->IsStartNode()) + { + i = pNode->EndOfSectionIndex(); // will be incremented again + } + else if (pNode->IsTextNode()) + { + pNextNode = pNode->GetTextNode(); + break; + } + } + assert(pNextNode); + return pNextNode; + } + else + { + return nullptr; + } +} + +static void DelFullParaMoveFrames(SwDoc & rDoc, SwUndRng const& rRange, + SwRedlineSaveDatas const& rRedlineSaveData) +{ + SwTextNode * pFirstMergedDeletedTextNode(nullptr); + SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, rRange, + rRedlineSaveData, pFirstMergedDeletedTextNode); + if (pNextNode) + { + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pFirstMergedDeletedTextNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + assert(pFrame->GetMergedPara()); + assert(pFrame->GetMergedPara()->pFirstNode == pFirstMergedDeletedTextNode); + assert(pNextNode->GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex()); + frames.push_back(pFrame); + } + } + for (SwTextFrame *const pFrame : frames) + { + // sw_redlinehide: don't need FrameMode::Existing here + // because everything from pNextNode onwards is already + // correctly hidden + pFrame->RegisterToNode(*pNextNode, true); + } + } +} + +// SwUndoDelete has to perform a deletion and to record anything that is needed +// to restore the situation before the deletion. Unfortunately a part of the +// deletion will be done after calling this Ctor, this has to be kept in mind! +// In this Ctor only the complete paragraphs will be deleted, the joining of +// the first and last paragraph of the selection will be handled outside this +// function. +// Here are the main steps of the function: +// 1. Deletion/recording of content indices of the selection: footnotes, fly +// frames and bookmarks +// Step 1 could shift all nodes by deletion of footnotes => nNdDiff will be set. +// 2. If the paragraph where the selection ends, is the last content of a +// section so that this section becomes empty when the paragraphs will be +// joined we have to do some smart actions ;-) The paragraph will be moved +// outside the section and replaced by a dummy text node, the complete +// section will be deleted in step 3. The difference between replacement +// dummy and original is nReplacementDummy. +// 3. Moving complete selected nodes into the UndoArray. Before this happens the +// selection has to be extended if there are sections which would become +// empty otherwise. BTW: sections will be moved into the UndoArray if they +// are complete part of the selection. Sections starting or ending outside +// of the selection will not be removed from the DocNodeArray even they got +// a "dummy"-copy in the UndoArray. +// 4. We have to anticipate the joining of the two paragraphs if the start +// paragraph is inside a section and the end paragraph not. Then we have to +// move the paragraph into this section and to record this in nSectDiff. +SwUndoDelete::SwUndoDelete( + SwPaM& rPam, + bool bFullPara, + bool bCalledByTableCpy ) + : SwUndo(SwUndoId::DELETE, rPam.GetDoc()), + SwUndRng( rPam ), + m_nNode(0), + m_nNdDiff(0), + m_nSectDiff(0), + m_nReplaceDummy(0), + m_nSetPos(0), + m_bGroup( false ), + m_bBackSp( false ), + m_bJoinNext( false ), + m_bTableDelLastNd( false ), + // bFullPara is set e.g. if an empty paragraph before a table is deleted + m_bDelFullPara( bFullPara ), + m_bResetPgDesc( false ), + m_bResetPgBrk( false ), + m_bFromTableCopy( bCalledByTableCpy ) +{ + + m_bCacheComment = false; + + SwDoc * pDoc = rPam.GetDoc(); + + if( !pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + m_pRedlSaveData.reset(new SwRedlineSaveDatas); + if( !FillSaveData( rPam, *m_pRedlSaveData )) + { + m_pRedlSaveData.reset(); + } + } + + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + + // delete all footnotes for now + const SwPosition *pStt = rPam.Start(), + *pEnd = rPam.GetPoint() == pStt + ? rPam.GetMark() + : rPam.GetPoint(); + + // Step 1. deletion/record of content indices + if( m_bDelFullPara ) + { + OSL_ENSURE( rPam.HasMark(), "PaM without Mark" ); + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(), + DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) ); + + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + DelBookmarks(pStt->nNode, pEnd->nNode); + } + else + { + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint() ); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + if (m_nEndNode - m_nSttNode > 1) // check for fully selected nodes + { + SwNodeIndex const start(pStt->nNode, +1); + DelBookmarks(start, pEnd->nNode); + } + } + + m_nSetPos = m_pHistory ? m_pHistory->Count() : 0; + + // Is already anything deleted? + m_nNdDiff = m_nSttNode - pStt->nNode.GetIndex(); + + m_bJoinNext = !bFullPara && pEnd == rPam.GetPoint(); + m_bBackSp = !bFullPara && !m_bJoinNext; + + SwTextNode *pSttTextNd = nullptr, *pEndTextNd = nullptr; + if( !bFullPara ) + { + pSttTextNd = pStt->nNode.GetNode().GetTextNode(); + pEndTextNd = m_nSttNode == m_nEndNode + ? pSttTextNd + : pEnd->nNode.GetNode().GetTextNode(); + } + else if (m_pRedlSaveData) + { + DelFullParaMoveFrames(*pDoc, *this, *m_pRedlSaveData); + } + + bool bMoveNds = *pStt != *pEnd // any area still existent? + && ( SaveContent( pStt, pEnd, pSttTextNd, pEndTextNd ) || m_bFromTableCopy ); + + if( pSttTextNd && pEndTextNd && pSttTextNd != pEndTextNd ) + { + // two different TextNodes, thus save also the TextFormatCollection + m_pHistory->Add( pSttTextNd->GetTextColl(),pStt->nNode.GetIndex(), SwNodeType::Text ); + m_pHistory->Add( pEndTextNd->GetTextColl(),pEnd->nNode.GetIndex(), SwNodeType::Text ); + + if( !m_bJoinNext ) // Selection from bottom to top + { + // When using JoinPrev() all AUTO-PageBreak's will be copied + // correctly. To restore them with UNDO, Auto-PageBreak of the + // EndNode needs to be reset. Same for PageDesc and ColBreak. + if( pEndTextNd->HasSwAttrSet() ) + { + SwRegHistory aRegHist( *pEndTextNd, m_pHistory.get() ); + if( SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState( + RES_BREAK, false ) ) + pEndTextNd->ResetAttr( RES_BREAK ); + if( pEndTextNd->HasSwAttrSet() && + SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState( + RES_PAGEDESC, false ) ) + pEndTextNd->ResetAttr( RES_PAGEDESC ); + } + } + } + + // Move now also the PaM. The SPoint is at the beginning of a SSelection. + if( pEnd == rPam.GetPoint() && ( !bFullPara || pSttTextNd || pEndTextNd ) ) + rPam.Exchange(); + + if( !pSttTextNd && !pEndTextNd ) + --rPam.GetPoint()->nNode; + rPam.DeleteMark(); // the SPoint is in the selection + + if( !pEndTextNd ) + m_nEndContent = 0; + if( !pSttTextNd ) + m_nSttContent = 0; + + if( bMoveNds ) // Do Nodes exist that need to be moved? + { + SwNodes& rNds = pDoc->GetUndoManager().GetUndoNodes(); + SwNodes& rDocNds = pDoc->GetNodes(); + SwNodeRange aRg( rDocNds, m_nSttNode - m_nNdDiff, + rDocNds, m_nEndNode - m_nNdDiff ); + if( !bFullPara && !pEndTextNd && + &aRg.aEnd.GetNode() != &pDoc->GetNodes().GetEndOfContent() ) + { + SwNode* pNode = aRg.aEnd.GetNode().StartOfSectionNode(); + if( pNode->GetIndex() >= m_nSttNode - m_nNdDiff ) + ++aRg.aEnd; // Deletion of a complete table + } + SwNode* pTmpNd; + // Step 2: Expand selection if necessary + if( m_bJoinNext || bFullPara ) + { + // If all content of a section will be moved into Undo, the section + // itself should be moved completely. + while( aRg.aEnd.GetIndex() + 2 < rDocNds.Count() && + ( (pTmpNd = rDocNds[ aRg.aEnd.GetIndex()+1 ])->IsEndNode() && + pTmpNd->StartOfSectionNode()->IsSectionNode() && + pTmpNd->StartOfSectionNode()->GetIndex() >= aRg.aStart.GetIndex() ) ) + ++aRg.aEnd; + m_nReplaceDummy = aRg.aEnd.GetIndex() + m_nNdDiff - m_nEndNode; + if( m_nReplaceDummy ) + { // The selection has been expanded, because + ++aRg.aEnd; + if( pEndTextNd ) + { + // The end text node has to leave the (expanded) selection + // The dummy is needed because MoveNodes deletes empty + // sections + ++m_nReplaceDummy; + SwNodeRange aMvRg( *pEndTextNd, 0, *pEndTextNd, 1 ); + SwPosition aSplitPos( *pEndTextNd ); + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( aSplitPos, false ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd ); + --aRg.aEnd; + } + else + m_nReplaceDummy = 0; + } + } + if( m_bBackSp || bFullPara ) + { + // See above, the selection has to be expanded if there are "nearly + // empty" sections and a replacement dummy has to be set if needed. + while( 1 < aRg.aStart.GetIndex() && + ( (pTmpNd = rDocNds[ aRg.aStart.GetIndex()-1 ])->IsSectionNode() && + pTmpNd->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) ) + --aRg.aStart; + if( pSttTextNd ) + { + m_nReplaceDummy = m_nSttNode - m_nNdDiff - aRg.aStart.GetIndex(); + if( m_nReplaceDummy ) + { + SwNodeRange aMvRg( *pSttTextNd, 0, *pSttTextNd, 1 ); + SwPosition aSplitPos( *pSttTextNd ); + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( aSplitPos, false ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart ); + --aRg.aStart; + } + } + } + + if( m_bFromTableCopy ) + { + if( !pEndTextNd ) + { + if( pSttTextNd ) + ++aRg.aStart; + else if( !bFullPara && !aRg.aEnd.GetNode().IsContentNode() ) + --aRg.aEnd; + } + } + else if (pSttTextNd && (pEndTextNd || pSttTextNd->GetText().getLength())) + ++aRg.aStart; + + // Step 3: Moving into UndoArray... + m_nNode = rNds.GetEndOfContent().GetIndex(); + rDocNds.MoveNodes( aRg, rNds, SwNodeIndex( rNds.GetEndOfContent() )); + m_pMvStt.reset( new SwNodeIndex( rNds, m_nNode ) ); + // remember difference! + m_nNode = rNds.GetEndOfContent().GetIndex() - m_nNode; + + if( pSttTextNd && pEndTextNd ) + { + //Step 4: Moving around sections + m_nSectDiff = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); + // nSect is the number of sections which starts(ends) between start + // and end node of the selection. The "loser" paragraph has to be + // moved into the section(s) of the "winner" paragraph + if( m_nSectDiff ) + { + if( m_bJoinNext ) + { + SwNodeRange aMvRg( *pEndTextNd, 0, *pEndTextNd, 1 ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart ); + } + else + { + SwNodeRange aMvRg( *pSttTextNd, 0, *pSttTextNd, 1 ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd ); + } + } + } + if( m_nSectDiff || m_nReplaceDummy ) + lcl_MakeAutoFrames( *pDoc->GetSpzFrameFormats(), + m_bJoinNext ? pEndTextNd->GetIndex() : pSttTextNd->GetIndex() ); + } + else + m_nNode = 0; // moved no node -> no difference at the end + + // Are there any Nodes that got deleted before that (FootNotes + // have ContentNodes)? + if( !pSttTextNd && !pEndTextNd ) + { + m_nNdDiff = m_nSttNode - rPam.GetPoint()->nNode.GetIndex() - (bFullPara ? 0 : 1); + rPam.Move( fnMoveForward, GoInNode ); + } + else + { + m_nNdDiff = m_nSttNode; + if( m_nSectDiff && m_bBackSp ) + m_nNdDiff += m_nSectDiff; + m_nNdDiff -= rPam.GetPoint()->nNode.GetIndex(); + } + + if( !rPam.GetNode().IsContentNode() ) + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); + + // is a history necessary here at all? + if( m_pHistory && !m_pHistory->Count() ) + m_pHistory.reset(); +} + +bool SwUndoDelete::SaveContent( const SwPosition* pStt, const SwPosition* pEnd, + SwTextNode* pSttTextNd, SwTextNode* pEndTextNd ) +{ + sal_uLong nNdIdx = pStt->nNode.GetIndex(); + // 1 - copy start in Start-String + if( pSttTextNd ) + { + bool bOneNode = m_nSttNode == m_nEndNode; + SwRegHistory aRHst( *pSttTextNd, m_pHistory.get() ); + // always save all text atttibutes because of possibly overlapping + // areas of on/off + m_pHistory->CopyAttr( pSttTextNd->GetpSwpHints(), nNdIdx, + 0, pSttTextNd->GetText().getLength(), true ); + if( !bOneNode && pSttTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pSttTextNd->GetpSwAttrSet(), nNdIdx ); + + // the length might have changed (!!Fields!!) + sal_Int32 nLen = (bOneNode + ? pEnd->nContent.GetIndex() + : pSttTextNd->GetText().getLength()) + - pStt->nContent.GetIndex(); + + // delete now also the text (all attribute changes are added to + // UNDO history) + m_aSttStr = pSttTextNd->GetText().copy(m_nSttContent, nLen); + pSttTextNd->EraseText( pStt->nContent, nLen ); + if( pSttTextNd->GetpSwpHints() ) + pSttTextNd->GetpSwpHints()->DeRegister(); + + // METADATA: store + bool emptied( !m_aSttStr->isEmpty() && !pSttTextNd->Len() ); + if (!bOneNode || emptied) // merging may overwrite xmlids... + { + m_pMetadataUndoStart = emptied + ? pSttTextNd->CreateUndoForDelete() + : pSttTextNd->CreateUndo(); + } + + if( bOneNode ) + return false; // stop moving more nodes + } + + // 2 - copy end into End-String + if( pEndTextNd ) + { + SwIndex aEndIdx( pEndTextNd ); + nNdIdx = pEnd->nNode.GetIndex(); + SwRegHistory aRHst( *pEndTextNd, m_pHistory.get() ); + + // always save all text atttibutes because of possibly overlapping + // areas of on/off + m_pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), nNdIdx, 0, + pEndTextNd->GetText().getLength(), true ); + + if( pEndTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pEndTextNd->GetpSwAttrSet(), nNdIdx ); + + // delete now also the text (all attribute changes are added to + // UNDO history) + m_aEndStr = pEndTextNd->GetText().copy( 0, pEnd->nContent.GetIndex() ); + pEndTextNd->EraseText( aEndIdx, pEnd->nContent.GetIndex() ); + if( pEndTextNd->GetpSwpHints() ) + pEndTextNd->GetpSwpHints()->DeRegister(); + + // METADATA: store + bool emptied = !m_aEndStr->isEmpty() && !pEndTextNd->Len(); + + m_pMetadataUndoEnd = emptied + ? pEndTextNd->CreateUndoForDelete() + : pEndTextNd->CreateUndo(); + } + + // if there are only two Nodes then we're done + if( ( pSttTextNd || pEndTextNd ) && m_nSttNode + 1 == m_nEndNode ) + return false; // do not move any Node + + return true; // move Nodes lying in between +} + +bool SwUndoDelete::CanGrouping( SwDoc* pDoc, const SwPaM& rDelPam ) +{ + // Is Undo greater than one Node (that is Start and EndString)? + if( !m_aSttStr || m_aSttStr->isEmpty() || m_aEndStr ) + return false; + + // only the deletion of single char's can be condensed + if( m_nSttNode != m_nEndNode || ( !m_bGroup && m_nSttContent+1 != m_nEndContent )) + return false; + + const SwPosition *pStt = rDelPam.Start(), + *pEnd = rDelPam.GetPoint() == pStt + ? rDelPam.GetMark() + : rDelPam.GetPoint(); + + if( pStt->nNode != pEnd->nNode || + pStt->nContent.GetIndex()+1 != pEnd->nContent.GetIndex() || + pEnd->nNode != m_nSttNode ) + return false; + + // Distinguish between BackSpace and Delete because the Undo array needs to + // be constructed differently! + if( pEnd->nContent == m_nSttContent ) + { + if( m_bGroup && !m_bBackSp ) return false; + m_bBackSp = true; + } + else if( pStt->nContent == m_nSttContent ) + { + if( m_bGroup && m_bBackSp ) return false; + m_bBackSp = false; + } + else + return false; + + // are both Nodes (Node/Undo array) TextNodes at all? + SwTextNode * pDelTextNd = pStt->nNode.GetNode().GetTextNode(); + if( !pDelTextNd ) return false; + + sal_Int32 nUChrPos = m_bBackSp ? 0 : m_aSttStr->getLength()-1; + sal_Unicode cDelChar = pDelTextNd->GetText()[ pStt->nContent.GetIndex() ]; + CharClass& rCC = GetAppCharClass(); + if( ( CH_TXTATR_BREAKWORD == cDelChar || CH_TXTATR_INWORD == cDelChar ) || + rCC.isLetterNumeric( OUString( cDelChar ), 0 ) != + rCC.isLetterNumeric( *m_aSttStr, nUChrPos ) ) + return false; + + // tdf#132725 - if at-char/at-para flys would be deleted, don't group! + // DelContentIndex() would be called at the wrong time here, the indexes + // in the stored SwHistoryTextFlyCnt would be wrong when Undo is invoked + if (IsFlySelectedByCursor(*pDoc, *pStt, *pEnd)) + { + return false; + } + + { + SwRedlineSaveDatas aTmpSav; + const bool bSaved = FillSaveData( rDelPam, aTmpSav, false ); + + bool bOk = ( !m_pRedlSaveData && !bSaved ) || + ( m_pRedlSaveData && bSaved && + SwUndo::CanRedlineGroup( *m_pRedlSaveData, aTmpSav, m_bBackSp )); + // aTmpSav.DeleteAndDestroyAll(); + if( !bOk ) + return false; + + pDoc->getIDocumentRedlineAccess().DeleteRedline( rDelPam, false, RedlineType::Any ); + } + + // Both 'deletes' can be consolidated, so 'move' the related character + if( m_bBackSp ) + m_nSttContent--; // BackSpace: add char to array! + else + { + m_nEndContent++; // Delete: attach char at the end + nUChrPos++; + } + m_aSttStr = m_aSttStr->replaceAt( nUChrPos, 0, OUString(cDelChar) ); + pDelTextNd->EraseText( pStt->nContent, 1 ); + + m_bGroup = true; + return true; +} + +SwUndoDelete::~SwUndoDelete() +{ + if( m_pMvStt ) // Delete also the selection from UndoNodes array + { + // Insert saves content in IconSection + m_pMvStt->GetNode().GetNodes().Delete( *m_pMvStt, m_nNode ); + m_pMvStt.reset(); + } + m_pRedlSaveData.reset(); +} + +static SwRewriter lcl_RewriterFromHistory(SwHistory & rHistory) +{ + SwRewriter aRewriter; + + bool bDone = false; + + for ( sal_uInt16 n = 0; n < rHistory.Count(); n++) + { + OUString aDescr = rHistory[n]->GetDescription(); + + if (!aDescr.isEmpty()) + { + aRewriter.AddRule(UndoArg2, aDescr); + + bDone = true; + break; + } + } + + if (! bDone) + { + aRewriter.AddRule(UndoArg2, SwResId(STR_FIELD)); + } + + return aRewriter; +} + +static bool lcl_IsSpecialCharacter(sal_Unicode nChar) +{ + switch (nChar) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + case CH_TXTATR_TAB: + case CH_TXTATR_NEWLINE: + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + return true; + + default: + break; + } + + return false; +} + +static OUString lcl_DenotedPortion(const OUString& rStr, sal_Int32 nStart, sal_Int32 nEnd) +{ + OUString aResult; + + auto nCount = nEnd - nStart; + if (nCount > 0) + { + sal_Unicode cLast = rStr[nEnd - 1]; + if (lcl_IsSpecialCharacter(cLast)) + { + switch(cLast) + { + case CH_TXTATR_TAB: + aResult = SwResId(STR_UNDO_TABS, nCount); + + break; + case CH_TXTATR_NEWLINE: + aResult = SwResId(STR_UNDO_NLS, nCount); + + break; + + case CH_TXTATR_INWORD: + case CH_TXTATR_BREAKWORD: + aResult = SwRewriter::GetPlaceHolder(UndoArg2); + break; + + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + break; // nothing? + + default: + assert(!"unexpected special character"); + break; + } + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(nCount)); + aResult = aRewriter.Apply(aResult); + } + else + { + aResult = SwResId(STR_START_QUOTE) + + rStr.copy(nStart, nCount) + + SwResId(STR_END_QUOTE); + } + } + + return aResult; +} + +OUString DenoteSpecialCharacters(const OUString & rStr) +{ + OUStringBuffer aResult; + + if (!rStr.isEmpty()) + { + bool bStart = false; + sal_Int32 nStart = 0; + sal_Unicode cLast = 0; + + for( sal_Int32 i = 0; i < rStr.getLength(); i++) + { + if (lcl_IsSpecialCharacter(rStr[i])) + { + if (cLast != rStr[i]) + bStart = true; + + } + else + { + if (lcl_IsSpecialCharacter(cLast)) + bStart = true; + } + + if (bStart) + { + aResult.append(lcl_DenotedPortion(rStr, nStart, i)); + + nStart = i; + bStart = false; + } + + cLast = rStr[i]; + } + + aResult.append(lcl_DenotedPortion(rStr, nStart, rStr.getLength())); + } + else + aResult = SwRewriter::GetPlaceHolder(UndoArg2); + + return aResult.makeStringAndClear(); +} + +SwRewriter SwUndoDelete::GetRewriter() const +{ + SwRewriter aResult; + + if (m_nNode != 0) + { + if (!m_sTableName.isEmpty()) + { + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE)); + aRewriter.AddRule(UndoArg2, m_sTableName); + aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE)); + + OUString sTmp = aRewriter.Apply(SwResId(STR_TABLE_NAME)); + aResult.AddRule(UndoArg1, sTmp); + } + else + aResult.AddRule(UndoArg1, SwResId(STR_PARAGRAPHS)); + } + else + { + OUString aStr; + + if (m_aSttStr && m_aEndStr && m_aSttStr->isEmpty() && + m_aEndStr->isEmpty()) + { + aStr = SwResId(STR_PARAGRAPH_UNDO); + } + else + { + std::optional<OUString> aTmpStr; + if (m_aSttStr) + aTmpStr = m_aSttStr; + else if (m_aEndStr) + aTmpStr = m_aEndStr; + + if (aTmpStr) + { + aStr = DenoteSpecialCharacters(*aTmpStr); + } + else + { + aStr = SwRewriter::GetPlaceHolder(UndoArg2); + } + } + + aStr = ShortenString(aStr, nUndoStringLength, SwResId(STR_LDOTS)); + if (m_pHistory) + { + SwRewriter aRewriter = lcl_RewriterFromHistory(*m_pHistory); + aStr = aRewriter.Apply(aStr); + } + + aResult.AddRule(UndoArg1, aStr); + } + + return aResult; +} + +// Every object, anchored "AtContent" will be reanchored at rPos +static void lcl_ReAnchorAtContentFlyFrames( const SwFrameFormats& rSpzArr, SwPosition &rPos, sal_uLong nOldIdx ) +{ + if( !rSpzArr.empty() ) + { + SwFlyFrameFormat* pFormat; + const SwFormatAnchor* pAnchor; + const SwPosition* pAPos; + for( size_t n = 0; n < rSpzArr.size(); ++n ) + { + pFormat = static_cast<SwFlyFrameFormat*>(rSpzArr[n]); + pAnchor = &pFormat->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + pAPos = pAnchor->GetContentAnchor(); + if( pAPos && nOldIdx == pAPos->nNode.GetIndex() ) + { + SwFormatAnchor aAnch( *pAnchor ); + aAnch.SetAnchor( &rPos ); + pFormat->SetFormatAttr( aAnch ); + } + } + } + } +} + +void SwUndoDelete::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + + sal_uLong nCalcStt = m_nSttNode - m_nNdDiff; + + if( m_nSectDiff && m_bBackSp ) + nCalcStt += m_nSectDiff; + + SwNodeIndex aIdx(rDoc.GetNodes(), nCalcStt); + SwNode* pInsNd = &aIdx.GetNode(); + SwNode* pMovedNode = nullptr; + + { // code block so that SwPosition is detached when deleting a Node + SwPosition aPos( aIdx ); + if( !m_bDelFullPara ) + { + assert(!m_bTableDelLastNd || pInsNd->IsTextNode()); + if( pInsNd->IsTableNode() ) + { + pInsNd = rDoc.GetNodes().MakeTextNode( aIdx, + rDoc.GetDfltTextFormatColl() ); + --aIdx; + aPos.nNode = aIdx; + aPos.nContent.Assign( pInsNd->GetContentNode(), m_nSttContent ); + } + else + { + if( pInsNd->IsContentNode() ) + aPos.nContent.Assign( static_cast<SwContentNode*>(pInsNd), m_nSttContent ); + if( !m_bTableDelLastNd ) + pInsNd = nullptr; // do not delete Node! + } + } + else + pInsNd = nullptr; // do not delete Node! + + bool bNodeMove = 0 != m_nNode; + + if( m_aEndStr ) + { + // discard attributes since they all saved! + SwTextNode * pTextNd; + if (!m_bDelFullPara && aPos.nNode.GetNode().IsSectionNode()) + { // tdf#134250 section node wasn't deleted; but aPos must point to it in bNodeMove case below + assert(m_nSttContent == 0); + assert(!m_aSttStr); + pTextNd = rDoc.GetNodes()[aPos.nNode.GetIndex() + 1]->GetTextNode(); + } + else + { + pTextNd = aPos.nNode.GetNode().GetTextNode(); + } + + if( pTextNd && pTextNd->HasSwAttrSet() ) + pTextNd->ResetAllAttr(); + + if( pTextNd && pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( true ); + + if( m_aSttStr && !m_bFromTableCopy ) + { + sal_uLong nOldIdx = aPos.nNode.GetIndex(); + rDoc.getIDocumentContentOperations().SplitNode( aPos, false ); + // After the split all objects are anchored at the first + // paragraph, but the pHistory of the fly frame formats relies + // on anchoring at the start of the selection + // => selection backwards needs a correction. + if( m_bBackSp ) + lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx); + pTextNd = aPos.nNode.GetNode().GetTextNode(); + } + assert(pTextNd); // else where does m_aEndStr come from? + if( pTextNd ) + { + OUString const ins( pTextNd->InsertText(*m_aEndStr, aPos.nContent, + SwInsertFlags::NOHINTEXPAND) ); + assert(ins.getLength() == m_aEndStr->getLength()); // must succeed + (void) ins; + // METADATA: restore + pTextNd->RestoreMetadata(m_pMetadataUndoEnd); + } + } + else if (m_aSttStr && bNodeMove && pInsNd == nullptr) + { + SwTextNode * pNd = aPos.nNode.GetNode().GetTextNode(); + if( pNd ) + { + if (m_nSttContent < pNd->GetText().getLength()) + { + sal_uLong nOldIdx = aPos.nNode.GetIndex(); + rDoc.getIDocumentContentOperations().SplitNode( aPos, false ); + if( m_bBackSp ) + lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx); + } + else + ++aPos.nNode; + } + } + if( m_nSectDiff ) + { + sal_uLong nMoveIndex = aPos.nNode.GetIndex(); + int nDiff = 0; + if( m_bJoinNext ) + { + nMoveIndex += m_nSectDiff + 1; + pMovedNode = &aPos.nNode.GetNode(); + } + else + { + nMoveIndex -= m_nSectDiff + 1; + ++nDiff; + } + SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex); + SwNodeRange aRg( aPos.nNode, 0 - nDiff, aPos.nNode, 1 - nDiff ); + --aPos.nNode; + if( !m_bJoinNext ) + pMovedNode = &aPos.nNode.GetNode(); + rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx); + ++aPos.nNode; + } + + if( bNodeMove ) + { + SwNodeRange aRange( *m_pMvStt, 0, *m_pMvStt, m_nNode ); + SwNodeIndex aCopyIndex( aPos.nNode, -1 ); + rDoc.GetUndoManager().GetUndoNodes().Copy_(aRange, aPos.nNode, + // sw_redlinehide: delay creating frames: the flags on the + // nodes aren't necessarily up-to-date, and the redlines + // from m_pRedlSaveData aren't applied yet... + false); + + if( m_nReplaceDummy ) + { + sal_uLong nMoveIndex; + if( m_bJoinNext ) + { + nMoveIndex = m_nEndNode - m_nNdDiff; + aPos.nNode = nMoveIndex + m_nReplaceDummy; + } + else + { + aPos = SwPosition( aCopyIndex ); + nMoveIndex = aPos.nNode.GetIndex() + m_nReplaceDummy + 1; + } + SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex); + SwNodeRange aRg( aPos.nNode, 0, aPos.nNode, 1 ); + pMovedNode = &aPos.nNode.GetNode(); + // tdf#131684 without deleting frames + rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx, false); + rDoc.GetNodes().Delete( aMvIdx); + } + } + + if( m_aSttStr ) + { + aPos.nNode = m_nSttNode - m_nNdDiff + ( m_bJoinNext ? 0 : m_nReplaceDummy ); + SwTextNode * pTextNd = aPos.nNode.GetNode().GetTextNode(); + // If more than a single Node got deleted, also all "Node" + // attributes were saved + if (pTextNd != nullptr) + { + if( pTextNd->HasSwAttrSet() && bNodeMove && !m_aEndStr ) + pTextNd->ResetAllAttr(); + + if( pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( true ); + + // SectionNode mode and selection from top to bottom: + // -> in StartNode is still the rest of the Join => delete + aPos.nContent.Assign( pTextNd, m_nSttContent ); + OUString const ins( pTextNd->InsertText(*m_aSttStr, aPos.nContent, + SwInsertFlags::NOHINTEXPAND) ); + assert(ins.getLength() == m_aSttStr->getLength()); // must succeed + (void) ins; + // METADATA: restore + pTextNd->RestoreMetadata(m_pMetadataUndoStart); + } + } + + if( m_pHistory ) + { + m_pHistory->TmpRollback(&rDoc, m_nSetPos, false); + if( m_nSetPos ) // there were Footnodes/FlyFrames + { + // are there others than these ones? + if( m_nSetPos < m_pHistory->Count() ) + { + // if so save the attributes of the others + SwHistory aHstr; + aHstr.Move( 0, m_pHistory.get(), m_nSetPos ); + m_pHistory->Rollback(&rDoc); + m_pHistory->Move( 0, &aHstr ); + } + else + { + m_pHistory->Rollback(&rDoc); + m_pHistory.reset(); + } + } + } + + if( m_bResetPgDesc || m_bResetPgBrk ) + { + sal_uInt16 nStt = m_bResetPgDesc ? sal_uInt16(RES_PAGEDESC) : sal_uInt16(RES_BREAK); + sal_uInt16 nEnd = m_bResetPgBrk ? sal_uInt16(RES_BREAK) : sal_uInt16(RES_PAGEDESC); + + SwNode* pNode = rDoc.GetNodes()[ m_nEndNode + 1 ]; + if( pNode->IsContentNode() ) + static_cast<SwContentNode*>(pNode)->ResetAttr( nStt, nEnd ); + else if( pNode->IsTableNode() ) + static_cast<SwTableNode*>(pNode)->GetTable().GetFrameFormat()->ResetFormatAttr( nStt, nEnd ); + } + } + // delete the temporarily added Node + if (pInsNd && !m_bTableDelLastNd) + { + assert(&aIdx.GetNode() == pInsNd); + rDoc.GetNodes().Delete( aIdx ); + } + if( m_pRedlSaveData ) + SetSaveData(rDoc, *m_pRedlSaveData); + + sal_uLong delFullParaEndNode(m_nEndNode); + if (m_bDelFullPara && m_pRedlSaveData) + { + SwTextNode * pFirstMergedDeletedTextNode(nullptr); + SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, *this, + *m_pRedlSaveData, pFirstMergedDeletedTextNode); + if (pNextNode) + { + bool bNonMerged(false); + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNextNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + else + { + bNonMerged = true; + } + } + for (SwTextFrame *const pFrame : frames) + { + // could either destroy the text frames, or move them... + // destroying them would have the advantage that we don't + // need special code to *exclude* pFirstMergedDeletedTextNode + // from MakeFrames for the layouts in Hide mode but not + // layouts in Show mode ... + // ... except that MakeFrames won't create them then :( + pFrame->RegisterToNode(*pFirstMergedDeletedTextNode); + assert(pFrame->GetMergedPara()); + assert(!bNonMerged); // delFullParaEndNode is such an awful hack + (void) bNonMerged; + delFullParaEndNode = pFirstMergedDeletedTextNode->GetIndex(); + } + } + } + else if (m_aSttStr && (!m_bFromTableCopy || 0 != m_nNode)) + { + // only now do we have redlines in the document again; fix up the split + // frames + SwTextNode *const pStartNode(aIdx.GetNodes()[m_nSttNode]->GetTextNode()); + assert(pStartNode); + sw::RecreateStartTextFrames(*pStartNode); + } + + // create frames after SetSaveData has recreated redlines + if (0 != m_nNode) + { + // tdf#136453 only if section nodes at the start + if (m_bBackSp && m_nReplaceDummy != 0) + { + // tdf#134252 *first* create outer section frames + // note: text node m_nSttNode currently has frame with an upper; + // there's a hack in InsertCnt_() to move it below new section frame + SwNodeIndex const start(rDoc.GetNodes(), m_nSttNode - m_nReplaceDummy); + SwNodeIndex const end(rDoc.GetNodes(), m_nSttNode); // exclude m_nSttNode + ::MakeFrames(&rDoc, start, end); + } + // tdf#121031 if the start node is a text node, it already has a frame; + // if it's a table, it does not + // tdf#109376 exception: end on non-text-node -> start node was inserted + assert(!m_bDelFullPara || (m_nSectDiff == 0)); + SwNodeIndex const start(rDoc.GetNodes(), m_nSttNode + + ((m_bDelFullPara || !rDoc.GetNodes()[m_nSttNode]->IsTextNode() || pInsNd) + ? 0 : 1)); + // don't include end node in the range: it may have been merged already + // by the start node, or it may be merged by one of the moved nodes, + // but if it isn't merged, its current frame(s) should be good... + SwNodeIndex const end(rDoc.GetNodes(), m_bDelFullPara ? delFullParaEndNode : m_nEndNode); + ::MakeFrames(&rDoc, start, end); + } + + if (pMovedNode) + { // probably better do this after creating all frames + lcl_MakeAutoFrames(*rDoc.GetSpzFrameFormats(), pMovedNode->GetIndex()); + } + + // tdf#134021 only after MakeFrames(), because it may be the only node + // that has layout frames + if (pInsNd && m_bTableDelLastNd) + { + assert(&aIdx.GetNode() == pInsNd); + SwPaM tmp(aIdx, aIdx); + rDoc.getIDocumentContentOperations().DelFullPara(tmp); + } + + AddUndoRedoPaM(rContext, true); +} + +void SwUndoDelete::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam = AddUndoRedoPaM(rContext); + SwDoc& rDoc = *rPam.GetDoc(); + + if( m_pRedlSaveData ) + { + const bool bSuccess = FillSaveData(rPam, *m_pRedlSaveData); + OSL_ENSURE(bSuccess, + "SwUndoDelete::Redo: used to have redline data, but now none?"); + if (!bSuccess) + { + m_pRedlSaveData.reset(); + } + } + + if( !m_bDelFullPara ) + { + // tdf#128739 correct cursors but do not delete bookmarks yet + ::PaMCorrAbs(rPam, *rPam.End()); + SetPaM(rPam); + + if( !m_bJoinNext ) // then restore selection from bottom to top + rPam.Exchange(); + } + + if( m_pHistory ) // are the attributes saved? + { + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + SwHistory aHstr; + aHstr.Move( 0, m_pHistory.get() ); + + if( m_bDelFullPara ) + { + OSL_ENSURE( rPam.HasMark(), "PaM without Mark" ); + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(), + DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) ); + + DelBookmarks(rPam.GetMark()->nNode, rPam.GetPoint()->nNode); + } + else + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint() ); + m_nSetPos = m_pHistory ? m_pHistory->Count() : 0; + + m_pHistory->Move( m_nSetPos, &aHstr ); + } + else + { + if( m_bDelFullPara ) + { + OSL_ENSURE( rPam.HasMark(), "PaM without Mark" ); + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(), + DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) ); + + DelBookmarks( rPam.GetMark()->nNode, rPam.GetPoint()->nNode ); + } + else + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint() ); + m_nSetPos = m_pHistory ? m_pHistory->Count() : 0; + } + + if( !m_aSttStr && !m_aEndStr ) + { + if (m_bDelFullPara && m_pRedlSaveData) + { + DelFullParaMoveFrames(rDoc, *this, *m_pRedlSaveData); + } + + SwNodeIndex aSttIdx = ( m_bDelFullPara || m_bJoinNext ) + ? rPam.GetMark()->nNode + : rPam.GetPoint()->nNode; + SwTableNode* pTableNd = aSttIdx.GetNode().GetTableNode(); + if( pTableNd ) + { + if( m_bTableDelLastNd ) + { + // than add again a Node at the end + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + rDoc.GetNodes().MakeTextNode( aTmpIdx, + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + SwContentNode* pNextNd = rDoc.GetNodes()[ + pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + } + pTableNd->DelFrames(); + } + else if (*rPam.GetMark() == *rPam.GetPoint()) + { // paragraph with only footnote or as-char fly, delete that + // => DelContentIndex has already deleted it! nothing to do here + assert(m_nEndNode == m_nSttNode); + return; + } + + // avoid asserts from ~SwIndexReg for deleted nodes + SwPaM aTmp(*rPam.End()); + if (!aTmp.Move(fnMoveForward, GoInNode)) + { + *aTmp.GetPoint() = *rPam.Start(); + aTmp.Move(fnMoveBackward, GoInNode); + } + assert(aTmp.GetPoint()->nNode != rPam.GetPoint()->nNode + && aTmp.GetPoint()->nNode != rPam.GetMark()->nNode); + ::PaMCorrAbs(rPam, *aTmp.GetPoint()); + + rPam.DeleteMark(); + + rDoc.GetNodes().Delete( aSttIdx, m_nEndNode - m_nSttNode ); + } + else if( m_bDelFullPara ) + { + assert(!"dead code"); + // The Pam was incremented by one at Point (== end) to provide space + // for UNDO. This now needs to be reverted! + --rPam.End()->nNode; + if( rPam.GetPoint()->nNode == rPam.GetMark()->nNode ) + *rPam.GetMark() = *rPam.GetPoint(); + rDoc.getIDocumentContentOperations().DelFullPara( rPam ); + } + else + rDoc.getIDocumentContentOperations().DeleteAndJoin( rPam ); +} + +void SwUndoDelete::RepeatImpl(::sw::RepeatContext & rContext) +{ + // this action does not seem idempotent, + // so make sure it is only executed once on repeat + if (rContext.m_bDeleteRepeated) + return; + + SwPaM & rPam = rContext.GetRepeatPaM(); + SwDoc& rDoc = *rPam.GetDoc(); + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + if( !rPam.HasMark() ) + { + rPam.SetMark(); + rPam.Move( fnMoveForward, GoInContent ); + } + if( m_bDelFullPara ) + rDoc.getIDocumentContentOperations().DelFullPara( rPam ); + else + rDoc.getIDocumentContentOperations().DeleteAndJoin( rPam ); + rContext.m_bDeleteRepeated = true; +} + +void SwUndoDelete::SetTableName(const OUString & rName) +{ + m_sTableName = rName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx new file mode 100644 index 000000000..db2fee97b --- /dev/null +++ b/sw/source/core/undo/undobj.cxx @@ -0,0 +1,1682 @@ +/* -*- 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 <IShellCursorSupplier.hxx> +#include <txtftn.hxx> +#include <fmtanchr.hxx> +#include <ftnidx.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <UndoManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <ndnotxt.hxx> +#include <IMark.hxx> +#include <mvsave.hxx> +#include <redline.hxx> +#include <crossrefbookmark.hxx> +#include <strings.hrc> +#include <docsh.hxx> +#include <view.hxx> +#include <frameformats.hxx> +#include <sal/log.hxx> + +// This class saves the Pam as integers and can recompose those into a PaM +SwUndRng::SwUndRng() + : m_nSttNode( 0 ), m_nEndNode( 0 ), m_nSttContent( 0 ), m_nEndContent( 0 ) +{ +} + +SwUndRng::SwUndRng( const SwPaM& rPam ) +{ + SetValues( rPam ); +} + +void SwUndRng::SetValues( const SwPaM& rPam ) +{ + const SwPosition *pStt = rPam.Start(); + if( rPam.HasMark() ) + { + const SwPosition *pEnd = rPam.GetPoint() == pStt + ? rPam.GetMark() + : rPam.GetPoint(); + m_nEndNode = pEnd->nNode.GetIndex(); + m_nEndContent = pEnd->nContent.GetIndex(); + } + else + { + // no selection !! + m_nEndNode = 0; + m_nEndContent = COMPLETE_STRING; + } + + m_nSttNode = pStt->nNode.GetIndex(); + m_nSttContent = pStt->nContent.GetIndex(); +} + +void SwUndRng::SetPaM( SwPaM & rPam, bool bCorrToContent ) const +{ + rPam.DeleteMark(); + rPam.GetPoint()->nNode = m_nSttNode; + SwNode& rNd = rPam.GetNode(); + if( rNd.IsContentNode() ) + rPam.GetPoint()->nContent.Assign( rNd.GetContentNode(), m_nSttContent ); + else if( bCorrToContent ) + rPam.Move( fnMoveForward, GoInContent ); + else + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); + + if( !m_nEndNode && COMPLETE_STRING == m_nEndContent ) // no selection + return ; + + rPam.SetMark(); + if( m_nSttNode == m_nEndNode && m_nSttContent == m_nEndContent ) + return; // nothing left to do + + rPam.GetPoint()->nNode = m_nEndNode; + if( rPam.GetNode().IsContentNode() ) + rPam.GetPoint()->nContent.Assign( rPam.GetNode().GetContentNode(), m_nEndContent ); + else if( bCorrToContent ) + rPam.Move( fnMoveBackward, GoInContent ); + else + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); +} + +SwPaM & SwUndRng::AddUndoRedoPaM( + ::sw::UndoRedoContext & rContext, bool const bCorrToContent) const +{ + SwPaM & rPaM( rContext.GetCursorSupplier().CreateNewShellCursor() ); + SetPaM( rPaM, bCorrToContent ); + return rPaM; +} + +void SwUndo::RemoveIdxFromSection( SwDoc& rDoc, sal_uLong nSttIdx, + const sal_uLong* pEndIdx ) +{ + SwNodeIndex aIdx( rDoc.GetNodes(), nSttIdx ); + SwNodeIndex aEndIdx( rDoc.GetNodes(), pEndIdx ? *pEndIdx + : aIdx.GetNode().EndOfSectionIndex() ); + SwPosition aPos( rDoc.GetNodes().GetEndOfPostIts() ); + SwDoc::CorrAbs( aIdx, aEndIdx, aPos, true ); +} + +void SwUndo::RemoveIdxFromRange( SwPaM& rPam, bool bMoveNext ) +{ + const SwPosition* pEnd = rPam.End(); + if( bMoveNext ) + { + if( pEnd != rPam.GetPoint() ) + rPam.Exchange(); + + SwNodeIndex aStt( rPam.GetMark()->nNode ); + SwNodeIndex aEnd( rPam.GetPoint()->nNode ); + + if( !rPam.Move( fnMoveForward ) ) + { + rPam.Exchange(); + if( !rPam.Move( fnMoveBackward ) ) + { + rPam.GetPoint()->nNode = rPam.GetDoc()->GetNodes().GetEndOfPostIts(); + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); + } + } + + SwDoc::CorrAbs( aStt, aEnd, *rPam.GetPoint(), true ); + } + else + SwDoc::CorrAbs( rPam, *pEnd, true ); +} + +void SwUndo::RemoveIdxRel( sal_uLong nIdx, const SwPosition& rPos ) +{ + // Move only the Cursor. Bookmarks/TOXMarks/etc. are done by the corresponding + // JoinNext/JoinPrev + SwNodeIndex aIdx( rPos.nNode.GetNode().GetNodes(), nIdx ); + ::PaMCorrRel( aIdx, rPos ); +} + +SwUndo::SwUndo(SwUndoId const nId, const SwDoc* pDoc) + : m_nId(nId), m_nOrigRedlineFlags(RedlineFlags::NONE) + , m_nViewShellId(CreateViewShellId(pDoc)) + , m_isRepeatIgnored(false) + , m_bCacheComment(true) +{ +} + +ViewShellId SwUndo::CreateViewShellId(const SwDoc* pDoc) +{ + ViewShellId nRet(-1); + + if (const SwDocShell* pDocShell = pDoc->GetDocShell()) + { + if (const SwView* pView = pDocShell->GetView()) + nRet = pView->GetViewShellId(); + } + + return nRet; +} + +bool SwUndo::IsDelBox() const +{ + return GetId() == SwUndoId::COL_DELETE || GetId() == SwUndoId::ROW_DELETE || + GetId() == SwUndoId::TABLE_DELBOX; +} + +SwUndo::~SwUndo() +{ +} + +namespace { + +class UndoRedoRedlineGuard +{ +public: + UndoRedoRedlineGuard(::sw::UndoRedoContext const & rContext, SwUndo const & rUndo) + : m_rRedlineAccess(rContext.GetDoc().getIDocumentRedlineAccess()) + , m_eMode(m_rRedlineAccess.GetRedlineFlags()) + { + RedlineFlags const eTmpMode = rUndo.GetRedlineFlags(); + if ((RedlineFlags::ShowMask & eTmpMode) != (RedlineFlags::ShowMask & m_eMode)) + { + m_rRedlineAccess.SetRedlineFlags( eTmpMode ); + } + m_rRedlineAccess.SetRedlineFlags_intern( eTmpMode | RedlineFlags::Ignore ); + } + ~UndoRedoRedlineGuard() + { + m_rRedlineAccess.SetRedlineFlags(m_eMode); + } +private: + IDocumentRedlineAccess & m_rRedlineAccess; + RedlineFlags const m_eMode; +}; + +} + +void SwUndo::Undo() +{ + assert(false); // SwUndo::Undo(): ERROR: must call UndoWithContext instead +} + +void SwUndo::Redo() +{ + assert(false); // SwUndo::Redo(): ERROR: must call RedoWithContext instead +} + +void SwUndo::UndoWithContext(SfxUndoContext & rContext) +{ + ::sw::UndoRedoContext *const pContext( + dynamic_cast< ::sw::UndoRedoContext * >(& rContext)); + assert(pContext); + const UndoRedoRedlineGuard aUndoRedoRedlineGuard(*pContext, *this); + UndoImpl(*pContext); +} + +void SwUndo::RedoWithContext(SfxUndoContext & rContext) +{ + ::sw::UndoRedoContext *const pContext( + dynamic_cast< ::sw::UndoRedoContext * >(& rContext)); + assert(pContext); + const UndoRedoRedlineGuard aUndoRedoRedlineGuard(*pContext, *this); + RedoImpl(*pContext); +} + +void SwUndo::Repeat(SfxRepeatTarget & rContext) +{ + if (m_isRepeatIgnored) + { + return; // ignore Repeat for multi-selections + } + ::sw::RepeatContext *const pRepeatContext( + dynamic_cast< ::sw::RepeatContext * >(& rContext)); + assert(pRepeatContext); + RepeatImpl(*pRepeatContext); +} + +bool SwUndo::CanRepeat(SfxRepeatTarget & rContext) const +{ + assert(dynamic_cast< ::sw::RepeatContext * >(& rContext)); + (void)rContext; + // a MultiSelection action that doesn't do anything must still return true + return (SwUndoId::REPEAT_START <= GetId()) && (GetId() < SwUndoId::REPEAT_END); +} + +void SwUndo::RepeatImpl( ::sw::RepeatContext & ) +{ +} + +OUString GetUndoComment(SwUndoId eId) +{ + const char *pId = nullptr; + switch (eId) + { + case SwUndoId::EMPTY: + pId = STR_CANT_UNDO; + break; + case SwUndoId::START: + case SwUndoId::END: + break; + case SwUndoId::DELETE: + pId = STR_DELETE_UNDO; + break; + case SwUndoId::INSERT: + pId = STR_INSERT_UNDO; + break; + case SwUndoId::OVERWRITE: + pId = STR_OVR_UNDO; + break; + case SwUndoId::SPLITNODE: + pId = STR_SPLITNODE_UNDO; + break; + case SwUndoId::INSATTR: + pId = STR_INSATTR_UNDO; + break; + case SwUndoId::SETFMTCOLL: + pId = STR_SETFMTCOLL_UNDO; + break; + case SwUndoId::RESETATTR: + pId = STR_RESET_ATTR_UNDO; + break; + case SwUndoId::INSFMTATTR: + pId = STR_INSFMT_ATTR_UNDO; + break; + case SwUndoId::INSDOKUMENT: + pId = STR_INSERT_DOC_UNDO; + break; + case SwUndoId::COPY: + pId = STR_COPY_UNDO; + break; + case SwUndoId::INSTABLE: + pId = STR_INSTABLE_UNDO; + break; + case SwUndoId::TABLETOTEXT: + pId = STR_TABLETOTEXT_UNDO; + break; + case SwUndoId::TEXTTOTABLE: + pId = STR_TEXTTOTABLE_UNDO; + break; + case SwUndoId::SORT_TXT: + pId = STR_SORT_TXT; + break; + case SwUndoId::INSLAYFMT: + pId = STR_INSERTFLY; + break; + case SwUndoId::TABLEHEADLINE: + pId = STR_TABLEHEADLINE; + break; + case SwUndoId::INSSECTION: + pId = STR_INSERTSECTION; + break; + case SwUndoId::OUTLINE_LR: + pId = STR_OUTLINE_LR; + break; + case SwUndoId::OUTLINE_UD: + pId = STR_OUTLINE_UD; + break; + case SwUndoId::INSNUM: + pId = STR_INSNUM; + break; + case SwUndoId::NUMUP: + pId = STR_NUMUP; + break; + case SwUndoId::MOVENUM: + pId = STR_MOVENUM; + break; + case SwUndoId::INSDRAWFMT: + pId = STR_INSERTDRAW; + break; + case SwUndoId::NUMORNONUM: + pId = STR_NUMORNONUM; + break; + case SwUndoId::INC_LEFTMARGIN: + pId = STR_INC_LEFTMARGIN; + break; + case SwUndoId::DEC_LEFTMARGIN: + pId = STR_DEC_LEFTMARGIN; + break; + case SwUndoId::INSERTLABEL: + pId = STR_INSERTLABEL; + break; + case SwUndoId::SETNUMRULESTART: + pId = STR_SETNUMRULESTART; + break; + case SwUndoId::CHGFTN: + pId = STR_CHANGEFTN; + break; + case SwUndoId::REDLINE: + SAL_INFO("sw.core", "Should NEVER be used/translated"); + return "$1"; + case SwUndoId::ACCEPT_REDLINE: + pId = STR_ACCEPT_REDLINE; + break; + case SwUndoId::REJECT_REDLINE: + pId = STR_REJECT_REDLINE; + break; + case SwUndoId::SPLIT_TABLE: + pId = STR_SPLIT_TABLE; + break; + case SwUndoId::DONTEXPAND: + pId = STR_DONTEXPAND; + break; + case SwUndoId::AUTOCORRECT: + pId = STR_AUTOCORRECT; + break; + case SwUndoId::MERGE_TABLE: + pId = STR_MERGE_TABLE; + break; + case SwUndoId::TRANSLITERATE: + pId = STR_TRANSLITERATE; + break; + case SwUndoId::PASTE_CLIPBOARD: + pId = STR_PASTE_CLIPBOARD_UNDO; + break; + case SwUndoId::TYPING: + pId = STR_TYPING_UNDO; + break; + case SwUndoId::MOVE: + pId = STR_MOVE_UNDO; + break; + case SwUndoId::INSGLOSSARY: + pId = STR_INSERT_GLOSSARY; + break; + case SwUndoId::DELBOOKMARK: + pId = STR_DELBOOKMARK; + break; + case SwUndoId::INSBOOKMARK: + pId = STR_INSBOOKMARK; + break; + case SwUndoId::SORT_TBL: + pId = STR_SORT_TBL; + break; + case SwUndoId::DELLAYFMT: + pId = STR_DELETEFLY; + break; + case SwUndoId::AUTOFORMAT: + pId = STR_AUTOFORMAT; + break; + case SwUndoId::REPLACE: + pId = STR_REPLACE; + break; + case SwUndoId::DELSECTION: + pId = STR_DELETESECTION; + break; + case SwUndoId::CHGSECTION: + pId = STR_CHANGESECTION; + break; + case SwUndoId::SETDEFTATTR: + pId = STR_CHANGEDEFATTR; + break; + case SwUndoId::DELNUM: + pId = STR_DELNUM; + break; + case SwUndoId::DRAWUNDO: + pId = STR_DRAWUNDO; + break; + case SwUndoId::DRAWGROUP: + pId = STR_DRAWGROUP; + break; + case SwUndoId::DRAWUNGROUP: + pId = STR_DRAWUNGROUP; + break; + case SwUndoId::DRAWDELETE: + pId = STR_DRAWDELETE; + break; + case SwUndoId::REREAD: + pId = STR_REREAD; + break; + case SwUndoId::DELGRF: + pId = STR_DELGRF; + break; + case SwUndoId::TABLE_ATTR: + pId = STR_TABLE_ATTR; + break; + case SwUndoId::TABLE_AUTOFMT: + pId = STR_UNDO_TABLE_AUTOFMT; + break; + case SwUndoId::TABLE_INSCOL: + pId = STR_UNDO_TABLE_INSCOL; + break; + case SwUndoId::TABLE_INSROW: + pId = STR_UNDO_TABLE_INSROW; + break; + case SwUndoId::TABLE_DELBOX: + pId = STR_UNDO_TABLE_DELBOX; + break; + case SwUndoId::TABLE_SPLIT: + pId = STR_UNDO_TABLE_SPLIT; + break; + case SwUndoId::TABLE_MERGE: + pId = STR_UNDO_TABLE_MERGE; + break; + case SwUndoId::TBLNUMFMT: + pId = STR_TABLE_NUMFORMAT; + break; + case SwUndoId::INSTOX: + pId = STR_INSERT_TOX; + break; + case SwUndoId::CLEARTOXRANGE: + pId = STR_CLEAR_TOX_RANGE; + break; + case SwUndoId::TBLCPYTBL: + pId = STR_TABLE_TBLCPYTBL; + break; + case SwUndoId::CPYTBL: + pId = STR_TABLE_CPYTBL; + break; + case SwUndoId::INS_FROM_SHADOWCRSR: + pId = STR_INS_FROM_SHADOWCRSR; + break; + case SwUndoId::CHAINE: + pId = STR_UNDO_CHAIN; + break; + case SwUndoId::UNCHAIN: + pId = STR_UNDO_UNCHAIN; + break; + case SwUndoId::FTNINFO: + pId = STR_UNDO_FTNINFO; + break; + case SwUndoId::COMPAREDOC: + pId = STR_UNDO_COMPAREDOC; + break; + case SwUndoId::SETFLYFRMFMT: + pId = STR_UNDO_SETFLYFRMFMT; + break; + case SwUndoId::SETRUBYATTR: + pId = STR_UNDO_SETRUBYATTR; + break; + case SwUndoId::TOXCHANGE: + pId = STR_TOXCHANGE; + break; + case SwUndoId::CREATE_PAGEDESC: + pId = STR_UNDO_PAGEDESC_CREATE; + break; + case SwUndoId::CHANGE_PAGEDESC: + pId = STR_UNDO_PAGEDESC; + break; + case SwUndoId::DELETE_PAGEDESC: + pId = STR_UNDO_PAGEDESC_DELETE; + break; + case SwUndoId::HEADER_FOOTER: + pId = STR_UNDO_HEADER_FOOTER; + break; + case SwUndoId::FIELD: + pId = STR_UNDO_FIELD; + break; + case SwUndoId::TXTFMTCOL_CREATE: + pId = STR_UNDO_TXTFMTCOL_CREATE; + break; + case SwUndoId::TXTFMTCOL_DELETE: + pId = STR_UNDO_TXTFMTCOL_DELETE; + break; + case SwUndoId::TXTFMTCOL_RENAME: + pId = STR_UNDO_TXTFMTCOL_RENAME; + break; + case SwUndoId::CHARFMT_CREATE: + pId = STR_UNDO_CHARFMT_CREATE; + break; + case SwUndoId::CHARFMT_DELETE: + pId = STR_UNDO_CHARFMT_DELETE; + break; + case SwUndoId::CHARFMT_RENAME: + pId = STR_UNDO_CHARFMT_RENAME; + break; + case SwUndoId::FRMFMT_CREATE: + pId = STR_UNDO_FRMFMT_CREATE; + break; + case SwUndoId::FRMFMT_DELETE: + pId = STR_UNDO_FRMFMT_DELETE; + break; + case SwUndoId::FRMFMT_RENAME: + pId = STR_UNDO_FRMFMT_RENAME; + break; + case SwUndoId::NUMRULE_CREATE: + pId = STR_UNDO_NUMRULE_CREATE; + break; + case SwUndoId::NUMRULE_DELETE: + pId = STR_UNDO_NUMRULE_DELETE; + break; + case SwUndoId::NUMRULE_RENAME: + pId = STR_UNDO_NUMRULE_RENAME; + break; + case SwUndoId::BOOKMARK_RENAME: + pId = STR_UNDO_BOOKMARK_RENAME; + break; + case SwUndoId::INDEX_ENTRY_INSERT: + pId = STR_UNDO_INDEX_ENTRY_INSERT; + break; + case SwUndoId::INDEX_ENTRY_DELETE: + pId = STR_UNDO_INDEX_ENTRY_DELETE; + break; + case SwUndoId::COL_DELETE: + pId = STR_UNDO_COL_DELETE; + break; + case SwUndoId::ROW_DELETE: + pId = STR_UNDO_ROW_DELETE; + break; + case SwUndoId::RENAME_PAGEDESC: + pId = STR_UNDO_PAGEDESC_RENAME; + break; + case SwUndoId::NUMDOWN: + pId = STR_NUMDOWN; + break; + case SwUndoId::FLYFRMFMT_TITLE: + pId = STR_UNDO_FLYFRMFMT_TITLE; + break; + case SwUndoId::FLYFRMFMT_DESCRIPTION: + pId = STR_UNDO_FLYFRMFMT_DESCRIPTION; + break; + case SwUndoId::TBLSTYLE_CREATE: + pId = STR_UNDO_TBLSTYLE_CREATE; + break; + case SwUndoId::TBLSTYLE_DELETE: + pId = STR_UNDO_TBLSTYLE_DELETE; + break; + case SwUndoId::TBLSTYLE_UPDATE: + pId = STR_UNDO_TBLSTYLE_UPDATE; + break; + case SwUndoId::UI_REPLACE: + pId = STR_REPLACE_UNDO; + break; + case SwUndoId::UI_INSERT_PAGE_BREAK: + pId = STR_INSERT_PAGE_BREAK_UNDO; + break; + case SwUndoId::UI_INSERT_COLUMN_BREAK: + pId = STR_INSERT_COLUMN_BREAK_UNDO; + break; + case SwUndoId::UI_INSERT_ENVELOPE: + pId = STR_INSERT_ENV_UNDO; + break; + case SwUndoId::UI_DRAG_AND_COPY: + pId = STR_DRAG_AND_COPY; + break; + case SwUndoId::UI_DRAG_AND_MOVE: + pId = STR_DRAG_AND_MOVE; + break; + case SwUndoId::UI_INSERT_CHART: + pId = STR_INSERT_CHART; + break; + case SwUndoId::UI_INSERT_FOOTNOTE: + pId = STR_INSERT_FOOTNOTE; + break; + case SwUndoId::UI_INSERT_URLBTN: + pId = STR_INSERT_URLBTN; + break; + case SwUndoId::UI_INSERT_URLTXT: + pId = STR_INSERT_URLTXT; + break; + case SwUndoId::UI_DELETE_INVISIBLECNTNT: + pId = STR_DELETE_INVISIBLECNTNT; + break; + case SwUndoId::UI_REPLACE_STYLE: + pId = STR_REPLACE_STYLE; + break; + case SwUndoId::UI_DELETE_PAGE_BREAK: + pId = STR_DELETE_PAGE_BREAK; + break; + case SwUndoId::UI_TEXT_CORRECTION: + pId = STR_TEXT_CORRECTION; + break; + case SwUndoId::UI_TABLE_DELETE: + pId = STR_UNDO_TABLE_DELETE; + break; + case SwUndoId::CONFLICT: + break; + case SwUndoId::PARA_SIGN_ADD: + pId = STR_PARAGRAPH_SIGN_UNDO; + break; + case SwUndoId::INSERT_FORM_FIELD: + pId = STR_UNDO_INSERT_FORM_FIELD; + break; + } + + assert(pId); + return SwResId(pId); +} + +OUString SwUndo::GetComment() const +{ + OUString aResult; + + if (m_bCacheComment) + { + if (! maComment) + { + maComment = GetUndoComment(GetId()); + + SwRewriter aRewriter = GetRewriter(); + + maComment = aRewriter.Apply(*maComment); + } + + aResult = *maComment; + } + else + { + aResult = GetUndoComment(GetId()); + + SwRewriter aRewriter = GetRewriter(); + + aResult = aRewriter.Apply(aResult); + } + + return aResult; +} + +ViewShellId SwUndo::GetViewShellId() const +{ + return m_nViewShellId; +} + +SwRewriter SwUndo::GetRewriter() const +{ + SwRewriter aResult; + + return aResult; +} + +SwUndoSaveContent::SwUndoSaveContent() +{} + +SwUndoSaveContent::~SwUndoSaveContent() COVERITY_NOEXCEPT_FALSE +{ +} + +// This is needed when deleting content. For REDO all contents will be moved +// into the UndoNodesArray. These methods always create a new node to insert +// content. As a result, the attributes will not be expanded. +// - MoveTo moves from NodesArray into UndoNodesArray +// - MoveFrom moves from UndoNodesArray into NodesArray + +// If pEndNdIdx is given, Undo/Redo calls -Ins/DelFly. In that case the whole +// section should be moved. +void SwUndoSaveContent::MoveToUndoNds( SwPaM& rPaM, SwNodeIndex* pNodeIdx, + sal_uLong* pEndNdIdx ) +{ + SwDoc& rDoc = *rPaM.GetDoc(); + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwNoTextNode* pCpyNd = rPaM.GetNode().GetNoTextNode(); + + // here comes the actual delete (move) + SwNodes & rNds = rDoc.GetUndoManager().GetUndoNodes(); + SwPosition aPos( pEndNdIdx ? rNds.GetEndOfPostIts() + : rNds.GetEndOfExtras() ); + + const SwPosition* pStt = rPaM.Start(), *pEnd = rPaM.End(); + + sal_uLong nTmpMvNode = aPos.nNode.GetIndex(); + + if( pCpyNd || pEndNdIdx ) + { + SwNodeRange aRg( pStt->nNode, 0, pEnd->nNode, 1 ); + rDoc.GetNodes().MoveNodes( aRg, rNds, aPos.nNode, true ); + aPos.nContent = 0; + --aPos.nNode; + } + else + { + rDoc.GetNodes().MoveRange( rPaM, aPos, rNds ); + } + if( pEndNdIdx ) + *pEndNdIdx = aPos.nNode.GetIndex(); + + // old position + aPos.nNode = nTmpMvNode; + if( pNodeIdx ) + *pNodeIdx = aPos.nNode; +} + +void SwUndoSaveContent::MoveFromUndoNds( SwDoc& rDoc, sal_uLong nNodeIdx, + SwPosition& rInsPos, + const sal_uLong* pEndNdIdx, bool const bForceCreateFrames) +{ + // here comes the recovery + SwNodes & rNds = rDoc.GetUndoManager().GetUndoNodes(); + if( nNodeIdx == rNds.GetEndOfPostIts().GetIndex() ) + return; // nothing saved + + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwPaM aPaM( rInsPos ); + if( pEndNdIdx ) // than get the section from it + aPaM.GetPoint()->nNode.Assign( rNds, *pEndNdIdx ); + else + { + aPaM.GetPoint()->nNode = rNds.GetEndOfExtras(); + GoInContent( aPaM, fnMoveBackward ); + } + + SwTextNode* pTextNd = aPaM.GetNode().GetTextNode(); + if (!pEndNdIdx && pTextNd) + { + aPaM.SetMark(); + aPaM.GetPoint()->nNode = nNodeIdx; + aPaM.GetPoint()->nContent.Assign(aPaM.GetContentNode(), 0); + + SaveRedlEndPosForRestore aRedlRest( rInsPos.nNode, rInsPos.nContent.GetIndex() ); + + rNds.MoveRange( aPaM, rInsPos, rDoc.GetNodes() ); + + // delete the last Node as well + if( !aPaM.GetPoint()->nContent.GetIndex() || + ( aPaM.GetPoint()->nNode++ && // still empty Nodes at the end? + &rNds.GetEndOfExtras() != &aPaM.GetPoint()->nNode.GetNode() )) + { + aPaM.GetPoint()->nContent.Assign( nullptr, 0 ); + aPaM.SetMark(); + rNds.Delete( aPaM.GetPoint()->nNode, + rNds.GetEndOfExtras().GetIndex() - + aPaM.GetPoint()->nNode.GetIndex() ); + } + + aRedlRest.Restore(); + } + else + { + SwNodeRange aRg( rNds, nNodeIdx, rNds, (pEndNdIdx + ? ((*pEndNdIdx) + 1) + : rNds.GetEndOfExtras().GetIndex() ) ); + rNds.MoveNodes(aRg, rDoc.GetNodes(), rInsPos.nNode, nullptr == pEndNdIdx || bForceCreateFrames); + + } +} + +// These two methods move the Point of Pam backwards/forwards. With that, one +// can span an area for a Undo/Redo. (The Point is then positioned in front of +// the area to manipulate!) +// The flag indicates if there is still content in front of Point. +bool SwUndoSaveContent::MovePtBackward( SwPaM& rPam ) +{ + rPam.SetMark(); + if( rPam.Move( fnMoveBackward )) + return true; + + // If there is no content onwards, set Point simply to the previous position + // (Node and Content, so that Content will be detached!) + --rPam.GetPoint()->nNode; + rPam.GetPoint()->nContent.Assign( nullptr, 0 ); + return false; +} + +void SwUndoSaveContent::MovePtForward( SwPaM& rPam, bool bMvBkwrd ) +{ + // Was there content before this position? + if( bMvBkwrd ) + rPam.Move( fnMoveForward ); + else + { + ++rPam.GetPoint()->nNode; + SwContentNode* pCNd = rPam.GetContentNode(); + if( pCNd ) + pCNd->MakeStartIndex( &rPam.GetPoint()->nContent ); + else + rPam.Move( fnMoveForward ); + } +} + +// Delete all objects that have ContentIndices to the given area. +// Currently (1994) these exist: +// - Footnotes +// - Flys +// - Bookmarks + +// #i81002# - extending method +// delete certain (not all) cross-reference bookmarks at text node of <rMark> +// and at text node of <rPoint>, if these text nodes aren't the same. +void SwUndoSaveContent::DelContentIndex( const SwPosition& rMark, + const SwPosition& rPoint, + DelContentType nDelContentType ) +{ + const SwPosition *pStt = rMark < rPoint ? &rMark : &rPoint, + *pEnd = &rMark == pStt ? &rPoint : &rMark; + + SwDoc* pDoc = rMark.nNode.GetNode().GetDoc(); + + // if it's not in the doc array, probably missing some invalidation somewhere + assert(&rPoint.nNode.GetNodes() == &pDoc->GetNodes()); + assert(&rMark.nNode.GetNodes() == &pDoc->GetNodes()); + + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + // 1. Footnotes + if( DelContentType::Ftn & nDelContentType ) + { + SwFootnoteIdxs& rFootnoteArr = pDoc->GetFootnoteIdxs(); + if( !rFootnoteArr.empty() ) + { + const SwNode* pFootnoteNd; + size_t nPos = 0; + rFootnoteArr.SeekEntry( pStt->nNode, &nPos ); + SwTextFootnote* pSrch; + + // for now delete all that come afterwards + while( nPos < rFootnoteArr.size() && ( pFootnoteNd = + &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex() + <= pEnd->nNode.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( (DelContentType::CheckNoCntnt & nDelContentType ) + ? (&pEnd->nNode.GetNode() == pFootnoteNd ) + : (( &pStt->nNode.GetNode() == pFootnoteNd && + pStt->nContent.GetIndex() > nFootnoteSttIdx) || + ( &pEnd->nNode.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEnd->nContent.GetIndex() )) ) + { + ++nPos; // continue searching + continue; + } + +// FIXME: duplicated code here and below -> refactor? + // Unfortunately an index needs to be created. Otherwise there + // will be problems with TextNode because the index will be + // deleted in the DTOR of SwFootnote! + SwTextNode* pTextNd = const_cast<SwTextNode*>(static_cast<const SwTextNode*>(pFootnoteNd)); + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwTextAttr* const pFootnoteHint = + pTextNd->GetTextAttrForCharAt( nFootnoteSttIdx ); + assert(pFootnoteHint); + SwIndex aIdx( pTextNd, nFootnoteSttIdx ); + m_pHistory->Add( pFootnoteHint, pTextNd->GetIndex(), false ); + pTextNd->EraseText( aIdx, 1 ); + } + + while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )-> + GetTextNode())->GetIndex() >= pStt->nNode.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( !(DelContentType::CheckNoCntnt & nDelContentType) && ( + ( &pStt->nNode.GetNode() == pFootnoteNd && + pStt->nContent.GetIndex() > nFootnoteSttIdx ) || + ( &pEnd->nNode.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEnd->nContent.GetIndex() ))) + continue; // continue searching + + // Unfortunately an index needs to be created. Otherwise there + // will be problems with TextNode because the index will be + // deleted in the DTOR of SwFootnote! + SwTextNode* pTextNd = const_cast<SwTextNode*>(static_cast<const SwTextNode*>(pFootnoteNd)); + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwTextAttr* const pFootnoteHint = + pTextNd->GetTextAttrForCharAt( nFootnoteSttIdx ); + assert(pFootnoteHint); + SwIndex aIdx( pTextNd, nFootnoteSttIdx ); + m_pHistory->Add( pFootnoteHint, pTextNd->GetIndex(), false ); + pTextNd->EraseText( aIdx, 1 ); + } + } + } + + // 2. Flys + if( DelContentType::Fly & nDelContentType ) + { + sal_uInt16 nChainInsPos = m_pHistory ? m_pHistory->Count() : 0; + const SwFrameFormats& rSpzArr = *pDoc->GetSpzFrameFormats(); + if( !rSpzArr.empty() ) + { + SwFrameFormat* pFormat; + const SwFormatAnchor* pAnchor; + size_t n = rSpzArr.size(); + const SwPosition* pAPos; + + while( n && !rSpzArr.empty() ) + { + pFormat = rSpzArr[--n]; + pAnchor = &pFormat->GetAnchor(); + switch( pAnchor->GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + if( nullptr != (pAPos = pAnchor->GetContentAnchor() ) && + (( DelContentType::CheckNoCntnt & nDelContentType ) + ? ( pStt->nNode <= pAPos->nNode && + pAPos->nNode < pEnd->nNode ) + : ( *pStt <= *pAPos && *pAPos < *pEnd )) ) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwTextNode *const pTextNd = + pAPos->nNode.GetNode().GetTextNode(); + SwTextAttr* const pFlyHint = pTextNd->GetTextAttrForCharAt( + pAPos->nContent.GetIndex()); + assert(pFlyHint); + m_pHistory->Add( pFlyHint, 0, false ); + // reset n so that no Format is skipped + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + break; + case RndStdIds::FLY_AT_PARA: + { + pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + pStt->nNode <= pAPos->nNode && pAPos->nNode <= pEnd->nNode) + { + if (!m_pHistory) + m_pHistory.reset( new SwHistory ); + + if (!(DelContentType::Replace & nDelContentType) + && IsSelectFrameAnchoredAtPara(*pAPos, *pStt, *pEnd, nDelContentType)) + { + m_pHistory->AddDeleteFly(*pFormat, nChainInsPos); + // reset n so that no Format is skipped + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + // Moving the anchor? + else if (!((DelContentType::CheckNoCntnt|DelContentType::ExcludeFlyAtStartEnd) + & nDelContentType) && + // at least for calls from SwUndoDelete, + // this should work - other Undos don't + // remember the order of the cursor + (rPoint.nNode.GetIndex() == pAPos->nNode.GetIndex()) + // Do not try to move the anchor to a table! + && rMark.nNode.GetNode().IsTextNode()) + { + m_pHistory->AddChangeFlyAnchor(*pFormat); + SwFormatAnchor aAnch( *pAnchor ); + SwPosition aPos( rMark.nNode ); + aAnch.SetAnchor( &aPos ); + pFormat->SetFormatAttr( aAnch ); + } + } + } + break; + case RndStdIds::FLY_AT_CHAR: + if( nullptr != (pAPos = pAnchor->GetContentAnchor() ) && + ( pStt->nNode <= pAPos->nNode && pAPos->nNode <= pEnd->nNode ) ) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + if (!(DelContentType::Replace & nDelContentType) + && IsDestroyFrameAnchoredAtChar( + *pAPos, *pStt, *pEnd, nDelContentType)) + { + m_pHistory->AddDeleteFly(*pFormat, nChainInsPos); + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + else if (!((DelContentType::CheckNoCntnt | + DelContentType::ExcludeFlyAtStartEnd) + & nDelContentType)) + { + if( *pStt <= *pAPos && *pAPos < *pEnd ) + { + // These are the objects anchored + // between section start and end position + // Do not try to move the anchor to a table! + if( rMark.nNode.GetNode().GetTextNode() ) + { + m_pHistory->AddChangeFlyAnchor(*pFormat); + SwFormatAnchor aAnch( *pAnchor ); + aAnch.SetAnchor( &rMark ); + pFormat->SetFormatAttr( aAnch ); + } + } + } + } + break; + case RndStdIds::FLY_AT_FLY: + + if( nullptr != (pAPos = pAnchor->GetContentAnchor() ) && + pStt->nNode == pAPos->nNode ) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + + m_pHistory->AddDeleteFly(*pFormat, nChainInsPos); + + // reset n so that no Format is skipped + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + break; + default: break; + } + } + } + } + + // 3. Bookmarks + if( DelContentType::Bkm & nDelContentType ) + { + IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess(); + if( pMarkAccess->getAllMarksCount() ) + { + for( sal_Int32 n = 0; n < pMarkAccess->getAllMarksCount(); ++n ) + { + // #i81002# + bool bSavePos = false; + bool bSaveOtherPos = false; + const ::sw::mark::IMark *const pBkmk = pMarkAccess->getAllMarksBegin()[n]; + auto const type(IDocumentMarkAccess::GetType(*pBkmk)); + + if( DelContentType::CheckNoCntnt & nDelContentType ) + { + if ( pStt->nNode <= pBkmk->GetMarkPos().nNode + && pBkmk->GetMarkPos().nNode < pEnd->nNode ) + { + bSavePos = true; + } + if ( pBkmk->IsExpanded() + && pStt->nNode <= pBkmk->GetOtherMarkPos().nNode + && pBkmk->GetOtherMarkPos().nNode < pEnd->nNode ) + { + bSaveOtherPos = true; + } + } + else + { + // #i92125# + // keep cross-reference bookmarks, if content inside one paragraph is deleted. + if ( rMark.nNode == rPoint.nNode + && ( type == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK + || type == IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK)) + { + continue; + } + + bool bMaybe = false; + if ( *pStt <= pBkmk->GetMarkPos() && pBkmk->GetMarkPos() <= *pEnd ) + { + if ( pBkmk->GetMarkPos() == *pEnd + || ( *pStt == pBkmk->GetMarkPos() && pBkmk->IsExpanded() ) ) + bMaybe = true; + else + bSavePos = true; + } + if( pBkmk->IsExpanded() && + *pStt <= pBkmk->GetOtherMarkPos() && pBkmk->GetOtherMarkPos() <= *pEnd ) + { + assert(!bSaveOtherPos); + if ( bSavePos + || (*pStt < pBkmk->GetOtherMarkPos() && pBkmk->GetOtherMarkPos() < *pEnd) + || (bMaybe + && ( type == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || type == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK + || type == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK + || type == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))) + { + if( bMaybe ) + bSavePos = true; + bSaveOtherPos = true; + } + } + + if ( !bSavePos && !bSaveOtherPos + && dynamic_cast< const ::sw::mark::CrossRefBookmark* >(pBkmk) ) + { + // certain special handling for cross-reference bookmarks + const bool bDifferentTextNodesAtMarkAndPoint = + rMark.nNode != rPoint.nNode + && rMark.nNode.GetNode().GetTextNode() + && rPoint.nNode.GetNode().GetTextNode(); + if ( bDifferentTextNodesAtMarkAndPoint ) + { + // delete cross-reference bookmark at <pStt>, if only part of + // <pEnd> text node content is deleted. + if( pStt->nNode == pBkmk->GetMarkPos().nNode + && pEnd->nContent.GetIndex() != pEnd->nNode.GetNode().GetTextNode()->Len() ) + { + bSavePos = true; + bSaveOtherPos = false; // cross-reference bookmarks are not expanded + } + // delete cross-reference bookmark at <pEnd>, if only part of + // <pStt> text node content is deleted. + else if( pEnd->nNode == pBkmk->GetMarkPos().nNode && + pStt->nContent.GetIndex() != 0 ) + { + bSavePos = true; + bSaveOtherPos = false; // cross-reference bookmarks are not expanded + } + } + } + else if (type == IDocumentMarkAccess::MarkType::ANNOTATIONMARK) + { + // delete annotation marks, if its end position is covered by the deletion + const SwPosition& rAnnotationEndPos = pBkmk->GetMarkEnd(); + if ( *pStt < rAnnotationEndPos && rAnnotationEndPos <= *pEnd ) + { + bSavePos = true; + bSaveOtherPos = pBkmk->IsExpanded(); //tdf#90138, only save the other pos if there is one + } + } + } + + if ( bSavePos || bSaveOtherPos ) + { + if (type != IDocumentMarkAccess::MarkType::UNO_BOOKMARK) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + m_pHistory->Add( *pBkmk, bSavePos, bSaveOtherPos ); + } + if ( bSavePos + && ( bSaveOtherPos + || !pBkmk->IsExpanded() ) ) + { + pMarkAccess->deleteMark(pMarkAccess->getAllMarksBegin()+n); + n--; + } + } + } + } + } +} + +// save a complete section into UndoNodes array +SwUndoSaveSection::SwUndoSaveSection() + : m_nMoveLen( 0 ), m_nStartPos( ULONG_MAX ) +{ +} + +SwUndoSaveSection::~SwUndoSaveSection() +{ + if (m_pMovedStart) // delete also the section from UndoNodes array + { + // SaveSection saves the content in the PostIt section. + SwNodes& rUNds = m_pMovedStart->GetNode().GetNodes(); + rUNds.Delete( *m_pMovedStart, m_nMoveLen ); + + m_pMovedStart.reset(); + } + m_pRedlineSaveData.reset(); +} + +void SwUndoSaveSection::SaveSection( const SwNodeIndex& rSttIdx ) +{ + SwNodeRange aRg( rSttIdx.GetNode(), *rSttIdx.GetNode().EndOfSectionNode() ); + SaveSection( aRg ); +} + +void SwUndoSaveSection::SaveSection( + const SwNodeRange& rRange, bool const bExpandNodes) +{ + SwPaM aPam( rRange.aStart, rRange.aEnd ); + + // delete all footnotes, fly frames, bookmarks + DelContentIndex( *aPam.GetMark(), *aPam.GetPoint() ); + + // redlines *before* CorrAbs, because DelBookmarks will make them 0-length + // but *after* DelContentIndex because that also may use FillSaveData (in + // flys) and that will be restored *after* this one... + m_pRedlineSaveData.reset( new SwRedlineSaveDatas ); + if (!SwUndo::FillSaveData( aPam, *m_pRedlineSaveData )) + { + m_pRedlineSaveData.reset(); + } + + { + // move certain indexes out of deleted range + SwNodeIndex aSttIdx( aPam.Start()->nNode.GetNode() ); + SwNodeIndex aEndIdx( aPam.End()->nNode.GetNode() ); + SwNodeIndex aMvStt( aEndIdx, 1 ); + SwDoc::CorrAbs( aSttIdx, aEndIdx, SwPosition( aMvStt ), true ); + } + + m_nStartPos = rRange.aStart.GetIndex(); + + if (bExpandNodes) + { + --aPam.GetPoint()->nNode; + ++aPam.GetMark()->nNode; + } + + SwContentNode* pCNd = aPam.GetContentNode( false ); + if( pCNd ) + aPam.GetMark()->nContent.Assign( pCNd, 0 ); + if( nullptr != ( pCNd = aPam.GetContentNode()) ) + aPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + + // Keep positions as SwIndex so that this section can be deleted in DTOR + sal_uLong nEnd; + m_pMovedStart.reset(new SwNodeIndex(rRange.aStart)); + MoveToUndoNds(aPam, m_pMovedStart.get(), &nEnd); + m_nMoveLen = nEnd - m_pMovedStart->GetIndex() + 1; +} + +void SwUndoSaveSection::RestoreSection( SwDoc* pDoc, SwNodeIndex* pIdx, + sal_uInt16 nSectType ) +{ + if( ULONG_MAX != m_nStartPos ) // was there any content? + { + // check if the content is at the old position + SwNodeIndex aSttIdx( pDoc->GetNodes(), m_nStartPos ); + + // move the content from UndoNodes array into Fly + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aSttIdx, + static_cast<SwStartNodeType>(nSectType) ); + + RestoreSection( pDoc, SwNodeIndex( *pSttNd->EndOfSectionNode() )); + + if( pIdx ) + *pIdx = *pSttNd; + } +} + +void SwUndoSaveSection::RestoreSection( + SwDoc *const pDoc, const SwNodeIndex& rInsPos, bool bForceCreateFrames) +{ + if( ULONG_MAX != m_nStartPos ) // was there any content? + { + SwPosition aInsPos( rInsPos ); + sal_uLong nEnd = m_pMovedStart->GetIndex() + m_nMoveLen - 1; + MoveFromUndoNds(*pDoc, m_pMovedStart->GetIndex(), aInsPos, &nEnd, bForceCreateFrames); + + // destroy indices again, content was deleted from UndoNodes array + m_pMovedStart.reset(); + m_nMoveLen = 0; + + if( m_pRedlineSaveData ) + { + SwUndo::SetSaveData( *pDoc, *m_pRedlineSaveData ); + m_pRedlineSaveData.reset(); + } + } +} + +// save and set the RedlineData +SwRedlineSaveData::SwRedlineSaveData( + SwComparePosition eCmpPos, + const SwPosition& rSttPos, + const SwPosition& rEndPos, + SwRangeRedline& rRedl, + bool bCopyNext ) + : SwUndRng( rRedl ) + , SwRedlineData( rRedl.GetRedlineData(), bCopyNext ) +{ + assert( SwComparePosition::Outside == eCmpPos || + !rRedl.GetContentIdx() ); // "Redline with Content" + + switch (eCmpPos) + { + case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 at the beginning + m_nEndNode = rEndPos.nNode.GetIndex(); + m_nEndContent = rEndPos.nContent.GetIndex(); + break; + + case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at the end + m_nSttNode = rSttPos.nNode.GetIndex(); + m_nSttContent = rSttPos.nContent.GetIndex(); + break; + + case SwComparePosition::Inside: // Pos1 lays completely in Pos2 + m_nSttNode = rSttPos.nNode.GetIndex(); + m_nSttContent = rSttPos.nContent.GetIndex(); + m_nEndNode = rEndPos.nNode.GetIndex(); + m_nEndContent = rEndPos.nContent.GetIndex(); + break; + + case SwComparePosition::Outside: // Pos2 lays completely in Pos1 + if ( rRedl.GetContentIdx() ) + { + // than move section into UndoArray and memorize it + SaveSection( *rRedl.GetContentIdx() ); + rRedl.SetContentIdx( nullptr ); + } + break; + + case SwComparePosition::Equal: // Pos1 is exactly as big as Pos2 + break; + + default: + assert(false); + } + +#if OSL_DEBUG_LEVEL > 0 + m_nRedlineCount = rSttPos.nNode.GetNode().GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size(); +#endif +} + +SwRedlineSaveData::~SwRedlineSaveData() +{ +} + +void SwRedlineSaveData::RedlineToDoc( SwPaM const & rPam ) +{ + SwDoc& rDoc = *rPam.GetDoc(); + SwRangeRedline* pRedl = new SwRangeRedline( *this, rPam ); + + if( GetMvSttIdx() ) + { + SwNodeIndex aIdx( rDoc.GetNodes() ); + RestoreSection( &rDoc, &aIdx, SwNormalStartNode ); + if( GetHistory() ) + GetHistory()->Rollback( &rDoc ); + pRedl->SetContentIdx( &aIdx ); + } + SetPaM( *pRedl ); + // First, delete the "old" so that in an Append no unexpected things will + // happen, e.g. a delete in an insert. In the latter case the just restored + // content will be deleted and not the one you originally wanted. + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pRedl, false, RedlineType::Any ); + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::DontCombineRedlines ); + //#i92154# let UI know about a new redline with comment + if (rDoc.GetDocShell() && (!pRedl->GetComment().isEmpty()) ) + rDoc.GetDocShell()->Broadcast(SwRedlineHint()); + + auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline(pRedl, true)); + assert(result != IDocumentRedlineAccess::AppendResult::IGNORED); // SwRedlineSaveData::RedlineToDoc: insert redline failed + (void) result; // unused in non-debug + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwUndo::FillSaveData( + const SwPaM& rRange, + SwRedlineSaveDatas& rSData, + bool bDelRange, + bool bCopyNext ) +{ + rSData.clear(); + + const SwPosition* pStt = rRange.Start(); + const SwPosition* pEnd = rRange.End(); + const SwRedlineTable& rTable = rRange.GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + SwRedlineTable::size_type n = 0; + rRange.GetDoc()->getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for ( ; n < rTable.size(); ++n ) + { + SwRangeRedline* pRedl = rTable[n]; + + const SwComparePosition eCmpPos = + ComparePosition( *pStt, *pEnd, *pRedl->Start(), *pRedl->End() ); + if ( eCmpPos != SwComparePosition::Before + && eCmpPos != SwComparePosition::Behind + && eCmpPos != SwComparePosition::CollideEnd + && eCmpPos != SwComparePosition::CollideStart ) + { + + rSData.push_back(std::unique_ptr<SwRedlineSaveData, o3tl::default_delete<SwRedlineSaveData>>(new SwRedlineSaveData(eCmpPos, *pStt, *pEnd, *pRedl, bCopyNext))); + } + } + if( !rSData.empty() && bDelRange ) + { + rRange.GetDoc()->getIDocumentRedlineAccess().DeleteRedline( rRange, false, RedlineType::Any ); + } + return !rSData.empty(); +} + +bool SwUndo::FillSaveDataForFormat( + const SwPaM& rRange, + SwRedlineSaveDatas& rSData ) +{ + rSData.clear(); + + const SwPosition *pStt = rRange.Start(), *pEnd = rRange.End(); + const SwRedlineTable& rTable = rRange.GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + SwRedlineTable::size_type n = 0; + rRange.GetDoc()->getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for ( ; n < rTable.size(); ++n ) + { + SwRangeRedline* pRedl = rTable[n]; + if ( RedlineType::Format == pRedl->GetType() ) + { + const SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRedl->Start(), *pRedl->End() ); + if ( eCmpPos != SwComparePosition::Before + && eCmpPos != SwComparePosition::Behind + && eCmpPos != SwComparePosition::CollideEnd + && eCmpPos != SwComparePosition::CollideStart ) + { + rSData.push_back(std::unique_ptr<SwRedlineSaveData, o3tl::default_delete<SwRedlineSaveData>>(new SwRedlineSaveData(eCmpPos, *pStt, *pEnd, *pRedl, true))); + } + + } + } + return !rSData.empty(); +} + + +void SwUndo::SetSaveData( SwDoc& rDoc, SwRedlineSaveDatas& rSData ) +{ + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + SwPaM aPam( rDoc.GetNodes().GetEndOfContent() ); + + for( size_t n = rSData.size(); n; ) + rSData[ --n ].RedlineToDoc( aPam ); + +#if OSL_DEBUG_LEVEL > 0 + // check redline count against count saved in RedlineSaveData object + assert(rSData.empty() || + (rSData[0].m_nRedlineCount == rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())); + // "redline count not restored properly" +#endif + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwUndo::HasHiddenRedlines( const SwRedlineSaveDatas& rSData ) +{ + for( size_t n = rSData.size(); n; ) + if( rSData[ --n ].GetMvSttIdx() ) + return true; + return false; +} + +bool SwUndo::CanRedlineGroup( SwRedlineSaveDatas& rCurr, + const SwRedlineSaveDatas& rCheck, bool bCurrIsEnd ) +{ + if( rCurr.size() != rCheck.size() ) + return false; + + for( size_t n = 0; n < rCurr.size(); ++n ) + { + const SwRedlineSaveData& rSet = rCurr[ n ]; + const SwRedlineSaveData& rGet = rCheck[ n ]; + if( rSet.m_nSttNode != rGet.m_nSttNode || + rSet.GetMvSttIdx() || rGet.GetMvSttIdx() || + ( bCurrIsEnd ? rSet.m_nSttContent != rGet.m_nEndContent + : rSet.m_nEndContent != rGet.m_nSttContent ) || + !rGet.CanCombine( rSet ) ) + { + return false; + } + } + + for( size_t n = 0; n < rCurr.size(); ++n ) + { + SwRedlineSaveData& rSet = rCurr[ n ]; + const SwRedlineSaveData& rGet = rCheck[ n ]; + if( bCurrIsEnd ) + rSet.m_nSttContent = rGet.m_nSttContent; + else + rSet.m_nEndContent = rGet.m_nEndContent; + } + return true; +} + +OUString ShortenString(const OUString & rStr, sal_Int32 nLength, const OUString & rFillStr) +{ + assert(nLength - rFillStr.getLength() >= 2); + + if (rStr.getLength() <= nLength) + return rStr; + + nLength -= rFillStr.getLength(); + if ( nLength < 2 ) + nLength = 2; + + const sal_Int32 nFrontLen = nLength - nLength / 2; + const sal_Int32 nBackLen = nLength - nFrontLen; + + return rStr.copy(0, nFrontLen) + + rFillStr + + rStr.copy(rStr.getLength() - nBackLen); +} + +static bool IsAtEndOfSection(SwPosition const& rAnchorPos) +{ + SwNodeIndex node(*rAnchorPos.nNode.GetNode().EndOfSectionNode()); + SwContentNode *const pNode(SwNodes::GoPrevious(&node)); + assert(pNode); + assert(rAnchorPos.nNode <= node); // last valid anchor pos is last content + return node == rAnchorPos.nNode + // at-para fly has no SwIndex! + && (rAnchorPos.nContent == pNode->Len() || rAnchorPos.nContent.GetIdxReg() == nullptr); +} + +static bool IsAtStartOfSection(SwPosition const& rAnchorPos) +{ + SwNodes const& rNodes(rAnchorPos.nNode.GetNodes()); + SwNodeIndex node(*rAnchorPos.nNode.GetNode().StartOfSectionNode()); + SwContentNode *const pNode(rNodes.GoNext(&node)); + assert(pNode); + (void) pNode; + assert(node <= rAnchorPos.nNode); + return node == rAnchorPos.nNode && rAnchorPos.nContent == 0; +} + +/// passed start / end position could be on section start / end node +static bool IsAtEndOfSection2(SwPosition const& rPos) +{ + return rPos.nNode.GetNode().IsEndNode() + || IsAtEndOfSection(rPos); +} + +static bool IsAtStartOfSection2(SwPosition const& rPos) +{ + return rPos.nNode.GetNode().IsStartNode() + || IsAtStartOfSection(rPos); +} + +static bool IsNotBackspaceHeuristic( + SwPosition const& rStart, SwPosition const& rEnd) +{ + // check if the selection is backspace/delete created by DelLeft/DelRight + return rStart.nNode.GetIndex() + 1 != rEnd.nNode.GetIndex() + || rEnd.nContent != 0 + || rStart.nContent != rStart.nNode.GetNode().GetTextNode()->Len(); +} + +bool IsDestroyFrameAnchoredAtChar(SwPosition const & rAnchorPos, + SwPosition const & rStart, SwPosition const & rEnd, + DelContentType const nDelContentType) +{ + assert(rStart <= rEnd); + + // CheckNoCntnt means DelFullPara which is obvious to handle + if (DelContentType::CheckNoCntnt & nDelContentType) + { // exclude selection end node because it won't be deleted + return (rAnchorPos.nNode < rEnd.nNode) + && (rStart.nNode <= rAnchorPos.nNode); + } + + if ((nDelContentType & DelContentType::WriterfilterHack) + && rAnchorPos.GetDoc()->IsInWriterfilterImport()) + { // FIXME hack for writerfilter RemoveLastParagraph() and MakeFlyAndMove(); can't test file format more specific? + return (rStart < rAnchorPos) && (rAnchorPos < rEnd); + } + + if (nDelContentType & DelContentType::ExcludeFlyAtStartEnd) + { // exclude selection start and end node + return (rAnchorPos.nNode < rEnd.nNode) + && (rStart.nNode < rAnchorPos.nNode); + } + + // in general, exclude the start and end position + return ((rStart < rAnchorPos) + || (rStart == rAnchorPos + // special case: fully deleted node + && ((rStart.nNode != rEnd.nNode && rStart.nContent == 0 + // but not if the selection is backspace/delete! + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtStartOfSection(rAnchorPos) && IsAtEndOfSection2(rEnd))))) + && ((rAnchorPos < rEnd) + || (rAnchorPos == rEnd + // special case: fully deleted node + && ((rEnd.nNode != rStart.nNode && rEnd.nContent == rEnd.nNode.GetNode().GetTextNode()->Len() + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtEndOfSection(rAnchorPos) && IsAtStartOfSection2(rStart))))); +} + +bool IsSelectFrameAnchoredAtPara(SwPosition const & rAnchorPos, + SwPosition const & rStart, SwPosition const & rEnd, + DelContentType const nDelContentType) +{ + assert(rStart <= rEnd); + + // CheckNoCntnt means DelFullPara which is obvious to handle + if (DelContentType::CheckNoCntnt & nDelContentType) + { // exclude selection end node because it won't be deleted + return (rAnchorPos.nNode < rEnd.nNode) + && (rStart.nNode <= rAnchorPos.nNode); + } + + if ((nDelContentType & DelContentType::WriterfilterHack) + && rAnchorPos.GetDoc()->IsInWriterfilterImport()) + { // FIXME hack for writerfilter RemoveLastParagraph() and MakeFlyAndMove(); can't test file format more specific? + // but it MUST NOT be done during the SetRedlineFlags at the end of ODF + // import, where the IsInXMLImport() cannot be checked because the + // stupid code temp. overrides it - instead rely on setting the ALLFLYS + // flag in MoveFromSection() and converting that to CheckNoCntnt with + // adjusted cursor! + return (rStart.nNode < rAnchorPos.nNode) && (rAnchorPos.nNode < rEnd.nNode); + } + + // in general, exclude the start and end position + return ((rStart.nNode < rAnchorPos.nNode) + || (rStart.nNode == rAnchorPos.nNode + && !(nDelContentType & DelContentType::ExcludeFlyAtStartEnd) + // special case: fully deleted node + && ((rStart.nNode != rEnd.nNode && rStart.nContent == 0 + // but not if the selection is backspace/delete! + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtStartOfSection2(rStart) && IsAtEndOfSection2(rEnd))))) + && ((rAnchorPos.nNode < rEnd.nNode) + || (rAnchorPos.nNode == rEnd.nNode + && !(nDelContentType & DelContentType::ExcludeFlyAtStartEnd) + // special case: fully deleted node + && ((rEnd.nNode != rStart.nNode && rEnd.nContent == rEnd.nNode.GetNode().GetTextNode()->Len() + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtEndOfSection2(rEnd) && IsAtStartOfSection2(rStart))))); +} + +bool IsFlySelectedByCursor(SwDoc const & rDoc, + SwPosition const & rStart, SwPosition const & rEnd) +{ + for (SwFrameFormat const*const pFly : *rDoc.GetSpzFrameFormats()) + { + SwFormatAnchor const& rAnchor(pFly->GetAnchor()); + switch (rAnchor.GetAnchorId()) + { + case RndStdIds::FLY_AT_CHAR: + case RndStdIds::FLY_AT_PARA: + { + SwPosition const*const pAnchorPos(rAnchor.GetContentAnchor()); + // can this really be null? + if (pAnchorPos != nullptr + && ((rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + ? IsDestroyFrameAnchoredAtChar(*pAnchorPos, rStart, rEnd) + : IsSelectFrameAnchoredAtPara(*pAnchorPos, rStart, rEnd))) + { + return true; + } + } + break; + default: // other types not relevant + break; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undobj1.cxx b/sw/source/core/undo/undobj1.cxx new file mode 100644 index 000000000..2398ea8da --- /dev/null +++ b/sw/source/core/undo/undobj1.cxx @@ -0,0 +1,714 @@ +/* -*- 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 <svl/itemiter.hxx> +#include <svx/svdundo.hxx> +#include <hintids.hxx> +#include <hints.hxx> +#include <fmtflcnt.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <txtflcnt.hxx> +#include <frmfmt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <rootfrm.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <ndole.hxx> +#include <frameformats.hxx> +#include <svx/svdobj.hxx> + +SwUndoFlyBase::SwUndoFlyBase( SwFrameFormat* pFormat, SwUndoId nUndoId ) + : SwUndo(nUndoId, pFormat->GetDoc()) + , m_pFrameFormat(pFormat) + , m_nNodePagePos(0) + , m_nContentPos(0) + , m_nRndId(RndStdIds::FLY_AT_PARA) + , m_bDelFormat(false) +{ +} + +SwUndoFlyBase::~SwUndoFlyBase() +{ + if( m_bDelFormat ) // delete during an Undo? + { + if (m_pFrameFormat->GetOtherTextBoxFormat()) + { // clear that before delete + m_pFrameFormat->SetOtherTextBoxFormat(nullptr); + } + delete m_pFrameFormat; + } +} + +void SwUndoFlyBase::InsFly(::sw::UndoRedoContext & rContext, bool bShowSelFrame) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + + // add again into array + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.push_back( m_pFrameFormat ); + + // OD 26.06.2003 #108784# - insert 'master' drawing object into drawing page + if ( RES_DRAWFRMFMT == m_pFrameFormat->Which() ) + m_pFrameFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PREP_INSERT_FLY)); + + SwFormatAnchor aAnchor( m_nRndId ); + + if (RndStdIds::FLY_AT_PAGE == m_nRndId) + { + aAnchor.SetPageNum( static_cast<sal_uInt16>(m_nNodePagePos) ); + } + else + { + SwPosition aNewPos(pDoc->GetNodes().GetEndOfContent()); + aNewPos.nNode = m_nNodePagePos; + if ((RndStdIds::FLY_AS_CHAR == m_nRndId) || (RndStdIds::FLY_AT_CHAR == m_nRndId)) + { + aNewPos.nContent.Assign( aNewPos.nNode.GetNode().GetContentNode(), + m_nContentPos ); + } + aAnchor.SetAnchor( &aNewPos ); + } + + m_pFrameFormat->SetFormatAttr( aAnchor ); // reset anchor + + if( RES_DRAWFRMFMT != m_pFrameFormat->Which() ) + { + // get Content and reset ContentAttribute + SwNodeIndex aIdx( pDoc->GetNodes() ); + RestoreSection( pDoc, &aIdx, SwFlyStartNode ); + m_pFrameFormat->SetFormatAttr( SwFormatContent( aIdx.GetNode().GetStartNode() )); + } + + // Set InContentAttribute not until there is content! + // Otherwise the layout would format the Fly beforehand but would not find + // content; this happened with graphics from the internet. + if (RndStdIds::FLY_AS_CHAR == m_nRndId) + { + // there must be at least the attribute in a TextNode + SwContentNode* pCNd = aAnchor.GetContentAnchor()->nNode.GetNode().GetContentNode(); + OSL_ENSURE( pCNd->IsTextNode(), "no Text Node at position." ); + SwFormatFlyCnt aFormat( m_pFrameFormat ); + pCNd->GetTextNode()->InsertItem(aFormat, m_nContentPos, m_nContentPos, SetAttrMode::NOHINTEXPAND); + } + + if (m_pFrameFormat->GetOtherTextBoxFormat()) + { + // recklessly assume that this thing will live longer than the + // SwUndoFlyBase - not sure what could be done if that isn't the case... + m_pFrameFormat->GetOtherTextBoxFormat()->SetOtherTextBoxFormat(m_pFrameFormat); + + if (m_pFrameFormat->GetOtherTextBoxFormat()->Which() == RES_DRAWFRMFMT) + { + SdrObject* pSdrObject = m_pFrameFormat->GetOtherTextBoxFormat()->FindSdrObject(); + if (pSdrObject) + { + // Make sure the old UNO wrapper is no longer cached after changing the shape + + // textframe pair. Otherwise we would have a wrapper which doesn't know about its + // textframe, even if it's there. + pSdrObject->setUnoShape(nullptr); + } + } + if (m_pFrameFormat->Which() == RES_DRAWFRMFMT) + { + // This is a draw format and we just set the fly format's textbox pointer to this draw + // format. Sync the draw format's content with the fly format's content. + SwFrameFormat* pFlyFormat = m_pFrameFormat->GetOtherTextBoxFormat(); + m_pFrameFormat->SetFormatAttr(pFlyFormat->GetContent()); + } + } + + m_pFrameFormat->MakeFrames(); + + if( bShowSelFrame ) + { + rContext.SetSelections(m_pFrameFormat, nullptr); + } + + if( GetHistory() ) + GetHistory()->Rollback( pDoc ); + + switch( m_nRndId ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + { + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + m_nNodePagePos = rAnchor.GetContentAnchor()->nNode.GetIndex(); + m_nContentPos = rAnchor.GetContentAnchor()->nContent.GetIndex(); + } + break; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + { + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + m_nNodePagePos = rAnchor.GetContentAnchor()->nNode.GetIndex(); + } + break; + case RndStdIds::FLY_AT_PAGE: + break; + default: break; + } + m_bDelFormat = false; +} + +void SwUndoFlyBase::DelFly( SwDoc* pDoc ) +{ + m_bDelFormat = true; // delete Format in DTOR + m_pFrameFormat->DelFrames(); // destroy Frames + + if (m_pFrameFormat->GetOtherTextBoxFormat()) + { // tdf#108867 clear that pointer + m_pFrameFormat->GetOtherTextBoxFormat()->SetOtherTextBoxFormat(nullptr); + } + + // all Uno objects should now log themselves off + { + SwPtrMsgPoolItem aMsgHint( RES_REMOVE_UNO_OBJECT, m_pFrameFormat ); + m_pFrameFormat->ModifyNotification( &aMsgHint, &aMsgHint ); + } + + if ( RES_DRAWFRMFMT != m_pFrameFormat->Which() ) + { + // if there is content than save it + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "Fly without content" ); + + SaveSection( *rContent.GetContentIdx() ); + const_cast<SwFormatContent&>(rContent).SetNewContentIdx( nullptr ); + } + // OD 02.07.2003 #108784# - remove 'master' drawing object from drawing page + else + m_pFrameFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PREP_DELETE_FLY)); + + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + const SwPosition* pPos = rAnchor.GetContentAnchor(); + // The positions in Nodes array got shifted. + m_nRndId = rAnchor.GetAnchorId(); + if (RndStdIds::FLY_AS_CHAR == m_nRndId) + { + m_nNodePagePos = pPos->nNode.GetIndex(); + m_nContentPos = pPos->nContent.GetIndex(); + SwTextNode *const pTextNd = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "No Textnode found" ); + SwTextFlyCnt* const pAttr = static_cast<SwTextFlyCnt*>( + pTextNd->GetTextAttrForCharAt( m_nContentPos, RES_TXTATR_FLYCNT ) ); + // attribute is still in TextNode, delete + if( pAttr && pAttr->GetFlyCnt().GetFrameFormat() == m_pFrameFormat ) + { + // Pointer to 0, do not delete + const_cast<SwFormatFlyCnt&>(pAttr->GetFlyCnt()).SetFlyFormat(); + SwIndex aIdx( pPos->nContent ); + pTextNd->EraseText( aIdx, 1 ); + } + } + else if (RndStdIds::FLY_AT_CHAR == m_nRndId) + { + m_nNodePagePos = pPos->nNode.GetIndex(); + m_nContentPos = pPos->nContent.GetIndex(); + } + else if ((RndStdIds::FLY_AT_PARA == m_nRndId) || (RndStdIds::FLY_AT_FLY == m_nRndId)) + { + m_nNodePagePos = pPos->nNode.GetIndex(); + } + else + { + m_nNodePagePos = rAnchor.GetPageNum(); + } + + m_pFrameFormat->ResetFormatAttr( RES_ANCHOR ); // delete anchor + + // delete from array + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( m_pFrameFormat ); +} + +SwUndoInsLayFormat::SwUndoInsLayFormat( SwFrameFormat* pFormat, sal_uLong nNodeIdx, sal_Int32 nCntIdx ) + : SwUndoFlyBase( pFormat, RES_DRAWFRMFMT == pFormat->Which() ? + SwUndoId::INSDRAWFMT : SwUndoId::INSLAYFMT ), + mnCursorSaveIndexPara( nNodeIdx ), mnCursorSaveIndexPos( nCntIdx ) +{ + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + m_nRndId = rAnchor.GetAnchorId(); + m_bDelFormat = false; + switch( m_nRndId ) + { + case RndStdIds::FLY_AT_PAGE: + m_nNodePagePos = rAnchor.GetPageNum(); + break; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nNodePagePos = rAnchor.GetContentAnchor()->nNode.GetIndex(); + break; + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + { + const SwPosition* pPos = rAnchor.GetContentAnchor(); + m_nContentPos = pPos->nContent.GetIndex(); + m_nNodePagePos = pPos->nNode.GetIndex(); + } + break; + default: + OSL_FAIL( "Which FlyFrame?" ); + } +} + +SwUndoInsLayFormat::~SwUndoInsLayFormat() +{ +} + +void SwUndoInsLayFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + if( rContent.GetContentIdx() ) // no content + { + assert(&rContent.GetContentIdx()->GetNodes() == &rDoc.GetNodes()); + bool bRemoveIdx = true; + if( mnCursorSaveIndexPara > 0 ) + { + SwTextNode *const pNode = + rDoc.GetNodes()[mnCursorSaveIndexPara]->GetTextNode(); + if( pNode ) + { + SwNodeIndex aIdx( rDoc.GetNodes(), + rContent.GetContentIdx()->GetIndex() ); + SwNodeIndex aEndIdx( rDoc.GetNodes(), + aIdx.GetNode().EndOfSectionIndex() ); + SwIndex aIndex( pNode, mnCursorSaveIndexPos ); + SwPosition aPos( *pNode, aIndex ); + SwDoc::CorrAbs( aIdx, aEndIdx, aPos, true ); + bRemoveIdx = false; + } + } + if( bRemoveIdx ) + { + RemoveIdxFromSection( rDoc, rContent.GetContentIdx()->GetIndex() ); + } + } + DelFly(& rDoc); +} + +void SwUndoInsLayFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + InsFly(rContext); +} + +void SwUndoInsLayFormat::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + // get anchor and reset it + SwFormatAnchor aAnchor( m_pFrameFormat->GetAnchor() ); + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + SwPosition aPos( *rContext.GetRepeatPaM().GetPoint() ); + if (RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) + { + aPos.nContent.Assign( nullptr, 0 ); + } + aAnchor.SetAnchor( &aPos ); + } + else if( RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId() ) + { + SwStartNode const*const pSttNd = + rContext.GetRepeatPaM().GetNode().FindFlyStartNode(); + if( pSttNd ) + { + SwPosition aPos( *pSttNd ); + aAnchor.SetAnchor( &aPos ); + } + else + { + return ; + } + } + else if (RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId()) + { + aAnchor.SetPageNum( pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->GetCurrPage( &rContext.GetRepeatPaM() )); + } + else { + OSL_FAIL( "What kind of anchor is this?" ); + } + + (void) pDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *m_pFrameFormat, aAnchor, true, true ); +} + +OUString SwUndoInsLayFormat::GetComment() const +{ + OUString aResult; + + // HACK: disable caching: + // the SfxUndoManager calls GetComment() too early: the pFrameFormat does not + // have a SwDrawContact yet, so it will fall back to SwUndo::GetComment(), + // which sets pComment to a wrong value. +// if (! pComment) + if ((true)) + { + /* + If frame format is present and has an SdrObject use the undo + comment of the SdrObject. Otherwise use the default comment. + */ + bool bDone = false; + if (m_pFrameFormat) + { + const SdrObject * pSdrObj = m_pFrameFormat->FindSdrObject(); + if ( pSdrObj ) + { + aResult = SdrUndoNewObj::GetComment( *pSdrObj ); + bDone = true; + } + } + + if (! bDone) + aResult = SwUndo::GetComment(); + } + else + aResult = *maComment; + + return aResult; +} + +static SwUndoId +lcl_GetSwUndoId(SwFrameFormat const *const pFrameFormat) +{ + if (RES_DRAWFRMFMT != pFrameFormat->Which()) + { + const SwFormatContent& rContent = pFrameFormat->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "Fly without content" ); + + SwNodeIndex firstNode(*rContent.GetContentIdx(), 1); + SwNoTextNode *const pNoTextNode(firstNode.GetNode().GetNoTextNode()); + if (pNoTextNode && pNoTextNode->IsGrfNode()) + { + return SwUndoId::DELGRF; + } + else if (pNoTextNode && pNoTextNode->IsOLENode()) + { + // surprisingly not SwUndoId::DELOLE, which does not seem to work + return SwUndoId::DELETE; + } + } + return SwUndoId::DELLAYFMT; +} + +SwUndoDelLayFormat::SwUndoDelLayFormat( SwFrameFormat* pFormat ) + : SwUndoFlyBase( pFormat, lcl_GetSwUndoId(pFormat) ) + , m_bShowSelFrame( true ) +{ + SwDoc* pDoc = pFormat->GetDoc(); + DelFly( pDoc ); +} + +SwRewriter SwUndoDelLayFormat::GetRewriter() const +{ + SwRewriter aRewriter; + + SwDoc * pDoc = m_pFrameFormat->GetDoc(); + + if (pDoc) + { + SwNodeIndex* pIdx = GetMvSttIdx(); + if( 1 == GetMvNodeCnt() && pIdx) + { + SwNode *const pNd = & pIdx->GetNode(); + + if ( pNd->IsNoTextNode() && pNd->IsOLENode()) + { + SwOLENode * pOLENd = pNd->GetOLENode(); + + aRewriter.AddRule(UndoArg1, pOLENd->GetDescription()); + } + } + } + + return aRewriter; +} + +void SwUndoDelLayFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + InsFly( rContext, m_bShowSelFrame ); +} + +void SwUndoDelLayFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + if( rContent.GetContentIdx() ) // no content + { + RemoveIdxFromSection(rDoc, rContent.GetContentIdx()->GetIndex()); + } + + DelFly(& rDoc); +} + +void SwUndoDelLayFormat::RedoForRollback() +{ + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + if( rContent.GetContentIdx() ) // no content + RemoveIdxFromSection( *m_pFrameFormat->GetDoc(), + rContent.GetContentIdx()->GetIndex() ); + + DelFly( m_pFrameFormat->GetDoc() ); +} + +SwUndoSetFlyFormat::SwUndoSetFlyFormat( SwFrameFormat& rFlyFormat, const SwFrameFormat& rNewFrameFormat ) + : SwUndo( SwUndoId::SETFLYFRMFMT, rFlyFormat.GetDoc() ), SwClient( &rFlyFormat ), m_pFrameFormat( &rFlyFormat ), + m_DerivedFromFormatName( rFlyFormat.IsDefault() ? "" : rFlyFormat.DerivedFrom()->GetName() ), + m_NewFormatName( rNewFrameFormat.GetName() ), + m_pItemSet( new SfxItemSet( *rFlyFormat.GetAttrSet().GetPool(), + rFlyFormat.GetAttrSet().GetRanges() )), + m_nOldNode( 0 ), m_nNewNode( 0 ), + m_nOldContent( 0 ), m_nNewContent( 0 ), + m_nOldAnchorType( RndStdIds::FLY_AT_PARA ), m_nNewAnchorType( RndStdIds::FLY_AT_PARA ), m_bAnchorChanged( false ) +{ +} + +SwRewriter SwUndoSetFlyFormat::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_NewFormatName); + + return aRewriter; +} + +SwUndoSetFlyFormat::~SwUndoSetFlyFormat() +{ +} + +void SwUndoSetFlyFormat::GetAnchor( SwFormatAnchor& rAnchor, + sal_uLong nNode, sal_Int32 nContent ) +{ + RndStdIds nAnchorTyp = rAnchor.GetAnchorId(); + if (RndStdIds::FLY_AT_PAGE != nAnchorTyp) + { + SwNode* pNd = m_pFrameFormat->GetDoc()->GetNodes()[ nNode ]; + + if( RndStdIds::FLY_AT_FLY == nAnchorTyp + ? ( !pNd->IsStartNode() || SwFlyStartNode != + static_cast<SwStartNode*>(pNd)->GetStartNodeType() ) + : !pNd->IsTextNode() ) + { + pNd = nullptr; // invalid position + } + else + { + SwPosition aPos( *pNd ); + if ((RndStdIds::FLY_AS_CHAR == nAnchorTyp) || + (RndStdIds::FLY_AT_CHAR == nAnchorTyp)) + { + if (nContent > pNd->GetTextNode()->GetText().getLength()) + { + pNd = nullptr; // invalid position + } + else + { + aPos.nContent.Assign(pNd->GetTextNode(), nContent); + } + } + if ( pNd ) + { + rAnchor.SetAnchor( &aPos ); + } + } + + if( !pNd ) + { + // invalid position - assign first page + rAnchor.SetType( RndStdIds::FLY_AT_PAGE ); + rAnchor.SetPageNum( 1 ); + } + } + else + rAnchor.SetPageNum( nContent ); +} + +void SwUndoSetFlyFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // Is the new Format still existent? + SwFrameFormat* pDerivedFromFrameFormat = rDoc.FindFrameFormatByName(m_DerivedFromFormatName); + if (pDerivedFromFrameFormat) + { + if( m_bAnchorChanged ) + m_pFrameFormat->DelFrames(); + + if( m_pFrameFormat->DerivedFrom() != pDerivedFromFrameFormat) + m_pFrameFormat->SetDerivedFrom(pDerivedFromFrameFormat); + + SfxItemIter aIter( *m_pItemSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if( IsInvalidItem( pItem )) + m_pFrameFormat->ResetFormatAttr( m_pItemSet->GetWhichByPos( + aIter.GetCurPos() )); + else + m_pFrameFormat->SetFormatAttr( *pItem ); + } + + if( m_bAnchorChanged ) + { + const SwFormatAnchor& rOldAnch = m_pFrameFormat->GetAnchor(); + if (RndStdIds::FLY_AS_CHAR == rOldAnch.GetAnchorId()) + { + // With InContents it's tricky: the text attribute needs to be + // deleted. Unfortunately, this not only destroys the Frames but + // also the format. To prevent that, first detach the + // connection between attribute and format. + const SwPosition *pPos = rOldAnch.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * pHint = pTextNode->GetTextAttrForCharAt( + nIdx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing Hint."); + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt().GetFrameFormat() == m_pFrameFormat, + "Wrong TextFlyCnt-Hint." ); + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat(); + + // Connection is now detached, therefore the attribute can be + // deleted + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + + // reposition anchor + SwFormatAnchor aNewAnchor( m_nOldAnchorType ); + GetAnchor( aNewAnchor, m_nOldNode, m_nOldContent ); + m_pFrameFormat->SetFormatAttr( aNewAnchor ); + + if (RndStdIds::FLY_AS_CHAR == aNewAnchor.GetAnchorId()) + { + const SwPosition* pPos = aNewAnchor.GetContentAnchor(); + SwFormatFlyCnt aFormat( m_pFrameFormat ); + pPos->nNode.GetNode().GetTextNode()->InsertItem( aFormat, + m_nOldContent, 0 ); + } + + m_pFrameFormat->MakeFrames(); + } + rContext.SetSelections(m_pFrameFormat, nullptr); + } +} + +void SwUndoSetFlyFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // Is the new Format still existent? + SwFrameFormat* pNewFrameFormat = rDoc.FindFrameFormatByName(m_NewFormatName); + if (pNewFrameFormat) + { + if( m_bAnchorChanged ) + { + SwFormatAnchor aNewAnchor( m_nNewAnchorType ); + GetAnchor( aNewAnchor, m_nNewNode, m_nNewContent ); + SfxItemSet aSet( rDoc.GetAttrPool(), aFrameFormatSetRange ); + aSet.Put( aNewAnchor ); + rDoc.SetFrameFormatToFly( *m_pFrameFormat, *pNewFrameFormat, &aSet ); + } + else + rDoc.SetFrameFormatToFly( *m_pFrameFormat, *pNewFrameFormat); + + rContext.SetSelections(m_pFrameFormat, nullptr); + } +} + +void SwUndoSetFlyFormat::PutAttr( sal_uInt16 nWhich, const SfxPoolItem* pItem ) +{ + if( pItem && pItem != GetDfltAttr( nWhich ) ) + { + // Special treatment for this anchor + if( RES_ANCHOR == nWhich ) + { + // only keep the first change + OSL_ENSURE( !m_bAnchorChanged, "multiple changes of an anchor are not allowed!" ); + + m_bAnchorChanged = true; + + const SwFormatAnchor* pAnchor = static_cast<const SwFormatAnchor*>(pItem); + m_nOldAnchorType = pAnchor->GetAnchorId(); + switch( m_nOldAnchorType ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + m_nOldContent = pAnchor->GetContentAnchor()->nContent.GetIndex(); + [[fallthrough]]; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nOldNode = pAnchor->GetContentAnchor()->nNode.GetIndex(); + break; + + default: + m_nOldContent = pAnchor->GetPageNum(); + } + + pAnchor = &m_pFrameFormat->GetAnchor(); + m_nNewAnchorType = pAnchor->GetAnchorId(); + switch( m_nNewAnchorType ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + m_nNewContent = pAnchor->GetContentAnchor()->nContent.GetIndex(); + [[fallthrough]]; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nNewNode = pAnchor->GetContentAnchor()->nNode.GetIndex(); + break; + + default: + m_nNewContent = pAnchor->GetPageNum(); + } + } + else + m_pItemSet->Put( *pItem ); + } + else + m_pItemSet->InvalidateItem( nWhich ); +} + +void SwUndoSetFlyFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* ) +{ + if( pOld ) + { + sal_uInt16 nWhich = pOld->Which(); + + if( nWhich < POOLATTR_END ) + PutAttr( nWhich, pOld ); + else if( RES_ATTRSET_CHG == nWhich ) + { + SfxItemIter aIter( *static_cast<const SwAttrSetChg*>(pOld)->GetChgSet() ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + PutAttr( pItem->Which(), pItem ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undoflystrattr.cxx b/sw/source/core/undo/undoflystrattr.cxx new file mode 100644 index 000000000..1a811ad21 --- /dev/null +++ b/sw/source/core/undo/undoflystrattr.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 <undoflystrattr.hxx> +#include <frmfmt.hxx> + + +SwUndoFlyStrAttr::SwUndoFlyStrAttr( SwFlyFrameFormat& rFlyFrameFormat, + const SwUndoId eUndoId, + const OUString& sOldStr, + const OUString& sNewStr ) + : SwUndo( eUndoId, rFlyFrameFormat.GetDoc() ), + mrFlyFrameFormat( rFlyFrameFormat ), + msOldStr( sOldStr ), + msNewStr( sNewStr ) +{ + assert(eUndoId == SwUndoId::FLYFRMFMT_TITLE + || eUndoId == SwUndoId::FLYFRMFMT_DESCRIPTION); +} + +SwUndoFlyStrAttr::~SwUndoFlyStrAttr() +{ +} + +void SwUndoFlyStrAttr::UndoImpl(::sw::UndoRedoContext &) +{ + switch ( GetId() ) + { + case SwUndoId::FLYFRMFMT_TITLE: + { + mrFlyFrameFormat.SetObjTitle( msOldStr, true ); + } + break; + case SwUndoId::FLYFRMFMT_DESCRIPTION: + { + mrFlyFrameFormat.SetObjDescription( msOldStr, true ); + } + break; + default: + { + } + } +} + +void SwUndoFlyStrAttr::RedoImpl(::sw::UndoRedoContext &) +{ + switch ( GetId() ) + { + case SwUndoId::FLYFRMFMT_TITLE: + { + mrFlyFrameFormat.SetObjTitle( msNewStr, true ); + } + break; + case SwUndoId::FLYFRMFMT_DESCRIPTION: + { + mrFlyFrameFormat.SetObjDescription( msNewStr, true ); + } + break; + default: + { + } + } +} + +SwRewriter SwUndoFlyStrAttr::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule( UndoArg1, mrFlyFrameFormat.GetName() ); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undraw.cxx b/sw/source/core/undo/undraw.cxx new file mode 100644 index 000000000..b6c8bd521 --- /dev/null +++ b/sw/source/core/undo/undraw.cxx @@ -0,0 +1,559 @@ +/* -*- 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 <UndoDraw.hxx> + +#include <svx/svdogrp.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdmark.hxx> +#include <svx/svdview.hxx> + +#include <hintids.hxx> +#include <hints.hxx> +#include <fmtanchr.hxx> +#include <fmtflcnt.hxx> +#include <txtflcnt.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <dcontact.hxx> +#include <viewsh.hxx> +#include <frameformats.hxx> + +struct SwUndoGroupObjImpl +{ + SwDrawFrameFormat* pFormat; + SdrObject* pObj; + sal_uLong nNodeIdx; +}; + +// Draw-Objecte + +void SwDoc::AddDrawUndo( std::unique_ptr<SdrUndoAction> pUndo ) +{ + if (GetIDocumentUndoRedo().DoesUndo() && + GetIDocumentUndoRedo().DoesDrawUndo()) + { + const SdrMarkList* pMarkList = nullptr; + SwViewShell* pSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh && pSh->HasDrawView() ) + pMarkList = &pSh->GetDrawView()->GetMarkedObjectList(); + + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwSdrUndo>(std::move(pUndo), pMarkList, this) ); + } +} + +SwSdrUndo::SwSdrUndo( std::unique_ptr<SdrUndoAction> pUndo, const SdrMarkList* pMrkLst, const SwDoc* pDoc ) + : SwUndo( SwUndoId::DRAWUNDO, pDoc ), m_pSdrUndo( std::move(pUndo) ) +{ + if( pMrkLst && pMrkLst->GetMarkCount() ) + m_pMarkList.reset( new SdrMarkList( *pMrkLst ) ); +} + +SwSdrUndo::~SwSdrUndo() +{ + m_pSdrUndo.reset(); + m_pMarkList.reset(); +} + +void SwSdrUndo::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pSdrUndo->Undo(); + rContext.SetSelections(nullptr, m_pMarkList.get()); +} + +void SwSdrUndo::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pSdrUndo->Redo(); + rContext.SetSelections(nullptr, m_pMarkList.get()); +} + +OUString SwSdrUndo::GetComment() const +{ + return m_pSdrUndo->GetComment(); +} + +static void lcl_SendRemoveToUno( SwFormat& rFormat ) +{ + SwPtrMsgPoolItem aMsgHint( RES_REMOVE_UNO_OBJECT, &rFormat ); + rFormat.ModifyNotification( &aMsgHint, &aMsgHint ); +} + +static void lcl_SaveAnchor( SwFrameFormat* pFormat, sal_uLong& rNodePos ) +{ + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId())) + { + rNodePos = rAnchor.GetContentAnchor()->nNode.GetIndex(); + sal_Int32 nContentPos = 0; + + if (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) + { + nContentPos = rAnchor.GetContentAnchor()->nContent.GetIndex(); + + // destroy TextAttribute + SwTextNode *pTextNd = pFormat->GetDoc()->GetNodes()[ rNodePos ]->GetTextNode(); + OSL_ENSURE( pTextNd, "No text node found!" ); + SwTextFlyCnt* pAttr = static_cast<SwTextFlyCnt*>( + pTextNd->GetTextAttrForCharAt( nContentPos, RES_TXTATR_FLYCNT )); + // attribute still in text node, delete + if( pAttr && pAttr->GetFlyCnt().GetFrameFormat() == pFormat ) + { + // just set pointer to 0, don't delete + const_cast<SwFormatFlyCnt&>(pAttr->GetFlyCnt()).SetFlyFormat(); + SwIndex aIdx( pTextNd, nContentPos ); + pTextNd->EraseText( aIdx, 1 ); + } + } + else if (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) + { + nContentPos = rAnchor.GetContentAnchor()->nContent.GetIndex(); + } + + pFormat->SetFormatAttr( SwFormatAnchor( rAnchor.GetAnchorId(), nContentPos ) ); + } +} + +static void lcl_RestoreAnchor( SwFrameFormat* pFormat, sal_uLong nNodePos ) +{ + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId())) + { + const sal_Int32 nContentPos = rAnchor.GetPageNum(); + SwNodes& rNds = pFormat->GetDoc()->GetNodes(); + + SwNodeIndex aIdx( rNds, nNodePos ); + SwPosition aPos( aIdx ); + + SwFormatAnchor aTmp( rAnchor.GetAnchorId() ); + if ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) + { + aPos.nContent.Assign( aIdx.GetNode().GetContentNode(), nContentPos ); + } + aTmp.SetAnchor( &aPos ); + RndStdIds nAnchorId = rAnchor.GetAnchorId(); + pFormat->SetFormatAttr( aTmp ); + + if (RndStdIds::FLY_AS_CHAR == nAnchorId) + { + SwTextNode *pTextNd = aIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "no Text Node" ); + SwFormatFlyCnt aFormat( pFormat ); + pTextNd->InsertItem( aFormat, nContentPos, nContentPos ); + } + } +} + +SwUndoDrawGroup::SwUndoDrawGroup( sal_uInt16 nCnt, const SwDoc* pDoc ) + : SwUndo( SwUndoId::DRAWGROUP, pDoc ), m_nSize( nCnt + 1 ), m_bDeleteFormat( true ) +{ + m_pObjArray.reset( new SwUndoGroupObjImpl[ m_nSize ] ); +} + +SwUndoDrawGroup::~SwUndoDrawGroup() +{ + if( m_bDeleteFormat ) + { + SwUndoGroupObjImpl* pTmp = m_pObjArray.get() + 1; + for( sal_uInt16 n = 1; n < m_nSize; ++n, ++pTmp ) + delete pTmp->pFormat; + } + else + delete m_pObjArray[0].pFormat; +} + +void SwUndoDrawGroup::UndoImpl(::sw::UndoRedoContext &) +{ + m_bDeleteFormat = false; + + // save group object + SwDrawFrameFormat* pFormat = m_pObjArray[0].pFormat; + + pFormat->CallSwClientNotify(sw::ContactChangedHint(&m_pObjArray[0].pObj)); + auto pObj = m_pObjArray[0].pObj; + pObj->SetUserCall(nullptr); + + ::lcl_SaveAnchor( pFormat, m_pObjArray[0].nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *pFormat ); + + // remove from array + SwDoc* pDoc = pFormat->GetDoc(); + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + ::lcl_RestoreAnchor( rSave.pFormat, rSave.nNodeIdx ); + rFlyFormats.push_back( rSave.pFormat ); + + pObj = rSave.pObj; + + SwDrawContact *pContact = new SwDrawContact( rSave.pFormat, pObj ); + pContact->ConnectToLayout(); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = rSave.pFormat; + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + "<SwUndoDrawGroup::Undo(..)> - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); + } +} + +void SwUndoDrawGroup::RedoImpl(::sw::UndoRedoContext &) +{ + m_bDeleteFormat = true; + + // remove from array + SwDoc* pDoc = m_pObjArray[0].pFormat->GetDoc(); + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + SdrObject* pObj = rSave.pObj; + + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + + // object will destroy itself + pContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + ::lcl_SaveAnchor( rSave.pFormat, rSave.nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *rSave.pFormat ); + + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), rSave.pFormat )); + } + + // re-insert group object + ::lcl_RestoreAnchor( m_pObjArray[0].pFormat, m_pObjArray[0].nNodeIdx ); + rFlyFormats.push_back( m_pObjArray[0].pFormat ); + + SwDrawContact *pContact = new SwDrawContact( m_pObjArray[0].pFormat, m_pObjArray[0].pObj ); + // #i26791# - correction: connect object to layout + pContact->ConnectToLayout(); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( m_pObjArray[0].pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = m_pObjArray[0].pFormat; + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + "<SwUndoDrawGroup::Undo(..)> - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); +} + +void SwUndoDrawGroup::AddObj( sal_uInt16 nPos, SwDrawFrameFormat* pFormat, SdrObject* pObj ) +{ + SwUndoGroupObjImpl& rSave = m_pObjArray[nPos + 1]; + rSave.pObj = pObj; + rSave.pFormat = pFormat; + ::lcl_SaveAnchor( pFormat, rSave.nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *pFormat ); + + // remove from array + SwFrameFormats& rFlyFormats = *pFormat->GetDoc()->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); +} + +void SwUndoDrawGroup::SetGroupFormat( SwDrawFrameFormat* pFormat ) +{ + m_pObjArray[0].pObj = nullptr; + m_pObjArray[0].pFormat = pFormat; +} + +SwUndoDrawUnGroup::SwUndoDrawUnGroup( SdrObjGroup* pObj, const SwDoc* pDoc ) + : SwUndo( SwUndoId::DRAWUNGROUP, pDoc ), m_bDeleteFormat( false ) +{ + m_nSize = static_cast<sal_uInt16>(pObj->GetSubList()->GetObjCount()) + 1; + m_pObjArray.reset( new SwUndoGroupObjImpl[ m_nSize ] ); + + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + SwDrawFrameFormat* pFormat = static_cast<SwDrawFrameFormat*>(pContact->GetFormat()); + + m_pObjArray[0].pObj = pObj; + m_pObjArray[0].pFormat = pFormat; + + // object will destroy itself + pContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + ::lcl_SaveAnchor( pFormat, m_pObjArray[0].nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *pFormat ); + + // remove from array + SwFrameFormats& rFlyFormats = *pFormat->GetDoc()->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); +} + +SwUndoDrawUnGroup::~SwUndoDrawUnGroup() +{ + if( m_bDeleteFormat ) + { + SwUndoGroupObjImpl* pTmp = m_pObjArray.get() + 1; + for( sal_uInt16 n = 1; n < m_nSize; ++n, ++pTmp ) + delete pTmp->pFormat; + } + else + delete m_pObjArray[0].pFormat; +} + +void SwUndoDrawUnGroup::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_bDeleteFormat = true; + + SwDoc *const pDoc = & rContext.GetDoc(); + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + + // remove from array + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + ::lcl_SaveAnchor( rSave.pFormat, rSave.nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *rSave.pFormat ); + + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), rSave.pFormat )); + } + + // re-insert group object + ::lcl_RestoreAnchor( m_pObjArray[0].pFormat, m_pObjArray[0].nNodeIdx ); + rFlyFormats.push_back( m_pObjArray[0].pFormat ); + + SwDrawContact *pContact = new SwDrawContact( m_pObjArray[0].pFormat, m_pObjArray[0].pObj ); + pContact->ConnectToLayout(); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( m_pObjArray[0].pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = m_pObjArray[0].pFormat; + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + "<SwUndoDrawGroup::Undo(..)> - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); +} + +void SwUndoDrawUnGroup::RedoImpl(::sw::UndoRedoContext &) +{ + m_bDeleteFormat = false; + + // save group object + SwDrawFrameFormat* pFormat = m_pObjArray[0].pFormat; + pFormat->CallSwClientNotify(sw::ContactChangedHint(&(m_pObjArray[0].pObj))); + m_pObjArray[0].pObj->SetUserCall( nullptr ); + + ::lcl_SaveAnchor( pFormat, m_pObjArray[0].nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *pFormat ); + + // remove from array + SwDoc* pDoc = pFormat->GetDoc(); + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + ::lcl_RestoreAnchor( rSave.pFormat, rSave.nNodeIdx ); + rFlyFormats.push_back( rSave.pFormat ); + + SwDrawFrameFormat* pDrawFrameFormat = rSave.pFormat; + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + "<SwUndoDrawGroup::Undo(..)> - wrong type of frame format for drawing object" ); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); + } +} + +void SwUndoDrawUnGroup::AddObj( sal_uInt16 nPos, SwDrawFrameFormat* pFormat ) +{ + SwUndoGroupObjImpl& rSave = m_pObjArray[ nPos + 1 ]; + rSave.pFormat = pFormat; + rSave.pObj = nullptr; +} + +SwUndoDrawUnGroupConnectToLayout::SwUndoDrawUnGroupConnectToLayout(const SwDoc* pDoc) + : SwUndo( SwUndoId::DRAWUNGROUP, pDoc ) +{ +} + +SwUndoDrawUnGroupConnectToLayout::~SwUndoDrawUnGroupConnectToLayout() +{ +} + +void +SwUndoDrawUnGroupConnectToLayout::UndoImpl(::sw::UndoRedoContext &) +{ + for (const std::pair< SwDrawFrameFormat*, SdrObject* > & rPair : m_aDrawFormatsAndObjs) + { + SdrObject* pObj( rPair.second ); + SwDrawContact* pDrawContact( dynamic_cast<SwDrawContact*>(pObj->GetUserCall()) ); + OSL_ENSURE( pDrawContact, + "<SwUndoDrawUnGroupConnectToLayout::Undo(..)> -- missing SwDrawContact instance" ); + if ( pDrawContact ) + { + // deletion of instance <pDrawContact> and thus disconnection from + // the Writer layout. + pDrawContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + } + } +} + +void +SwUndoDrawUnGroupConnectToLayout::RedoImpl(::sw::UndoRedoContext &) +{ + for (const std::pair< SwDrawFrameFormat*, SdrObject* > & rPair : m_aDrawFormatsAndObjs) + { + SwDrawFrameFormat* pFormat( rPair.first ); + SdrObject* pObj( rPair.second ); + SwDrawContact *pContact = new SwDrawContact( pFormat, pObj ); + pContact->ConnectToLayout(); + pContact->MoveObjToVisibleLayer( pObj ); + } +} + +void SwUndoDrawUnGroupConnectToLayout::AddFormatAndObj( SwDrawFrameFormat* pDrawFrameFormat, + SdrObject* pDrawObject ) +{ + m_aDrawFormatsAndObjs.emplace_back( pDrawFrameFormat, pDrawObject ); +} + +SwUndoDrawDelete::SwUndoDrawDelete( sal_uInt16 nCnt, const SwDoc* pDoc ) + : SwUndo( SwUndoId::DRAWDELETE, pDoc ), m_bDeleteFormat( true ) +{ + m_pObjArray.reset( new SwUndoGroupObjImpl[ nCnt ] ); + m_pMarkList.reset( new SdrMarkList() ); +} + +SwUndoDrawDelete::~SwUndoDrawDelete() +{ + if( m_bDeleteFormat ) + { + SwUndoGroupObjImpl* pTmp = m_pObjArray.get(); + for( size_t n = 0; n < m_pMarkList->GetMarkCount(); ++n, ++pTmp ) + delete pTmp->pFormat; + } +} + +void SwUndoDrawDelete::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_bDeleteFormat = false; + SwFrameFormats & rFlyFormats = *rContext.GetDoc().GetSpzFrameFormats(); + for( size_t n = 0; n < m_pMarkList->GetMarkCount(); ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + ::lcl_RestoreAnchor( rSave.pFormat, rSave.nNodeIdx ); + rFlyFormats.push_back( rSave.pFormat ); + SdrObject *pObj = rSave.pObj; + SwDrawContact *pContact = new SwDrawContact( rSave.pFormat, pObj ); + pContact->Changed_( *pObj, SdrUserCallType::Inserted, nullptr ); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = rSave.pFormat; + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + "<SwUndoDrawGroup::Undo(..)> - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); + } + rContext.SetSelections(nullptr, m_pMarkList.get()); +} + +void SwUndoDrawDelete::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_bDeleteFormat = true; + SwFrameFormats & rFlyFormats = *rContext.GetDoc().GetSpzFrameFormats(); + for( size_t n = 0; n < m_pMarkList->GetMarkCount(); ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + SdrObject *pObj = rSave.pObj; + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + SwDrawFrameFormat *pFormat = static_cast<SwDrawFrameFormat*>(pContact->GetFormat()); + + // object will destroy itself + pContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *pFormat ); + + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + ::lcl_SaveAnchor( pFormat, rSave.nNodeIdx ); + } +} + +void SwUndoDrawDelete::AddObj( SwDrawFrameFormat* pFormat, + const SdrMark& rMark ) +{ + SwUndoGroupObjImpl& rSave = m_pObjArray[ m_pMarkList->GetMarkCount() ]; + rSave.pObj = rMark.GetMarkedSdrObj(); + rSave.pFormat = pFormat; + ::lcl_SaveAnchor( pFormat, rSave.nNodeIdx ); + + // notify UNO objects to decouple + ::lcl_SendRemoveToUno( *pFormat ); + + // remove from array + SwDoc* pDoc = pFormat->GetDoc(); + SwFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + + m_pMarkList->InsertEntry( rMark ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unfmco.cxx b/sw/source/core/undo/unfmco.cxx new file mode 100644 index 000000000..118cf98f3 --- /dev/null +++ b/sw/source/core/undo/unfmco.cxx @@ -0,0 +1,87 @@ +/* -*- 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 <doc.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> + +SwUndoFormatColl::SwUndoFormatColl( const SwPaM& rRange, + const SwFormatColl* pColl, + const bool bReset, + const bool bResetListAttrs ) + : SwUndo( SwUndoId::SETFMTCOLL, rRange.GetDoc() ), + SwUndRng( rRange ), + mpHistory( new SwHistory ), + mbReset( bReset ), + mbResetListAttrs( bResetListAttrs ) +{ + // #i31191# + if ( pColl ) + maFormatName = pColl->GetName(); +} + +SwUndoFormatColl::~SwUndoFormatColl() +{ +} + +void SwUndoFormatColl::UndoImpl(::sw::UndoRedoContext & rContext) +{ + // restore old values + mpHistory->TmpRollback(& rContext.GetDoc(), 0); + mpHistory->SetTmpEnd( mpHistory->Count() ); + + // create cursor for undo range + AddUndoRedoPaM(rContext); +} + +void SwUndoFormatColl::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam = AddUndoRedoPaM(rContext); + + DoSetFormatColl(rContext.GetDoc(), rPam); +} + +void SwUndoFormatColl::RepeatImpl(::sw::RepeatContext & rContext) +{ + DoSetFormatColl(rContext.GetDoc(), rContext.GetRepeatPaM()); +} + +void SwUndoFormatColl::DoSetFormatColl(SwDoc & rDoc, SwPaM const & rPaM) +{ + // Only one TextFrameColl can be applied to a section, thus request only in + // this array. + SwTextFormatColl * pFormatColl = rDoc.FindTextFormatCollByName(maFormatName); + if (pFormatColl) + { + rDoc.SetTextFormatColl(rPaM, pFormatColl, mbReset, mbResetListAttrs); + } +} + +SwRewriter SwUndoFormatColl::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, maFormatName ); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unins.cxx b/sw/source/core/undo/unins.cxx new file mode 100644 index 000000000..6e08fb502 --- /dev/null +++ b/sw/source/core/undo/unins.cxx @@ -0,0 +1,1045 @@ +/* -*- 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 <UndoInsert.hxx> + +#include <hintids.hxx> +#include <unotools/charclass.hxx> +#include <sot/storage.hxx> +#include <editeng/keepitem.hxx> +#include <svx/svdobj.hxx> + +#include <fmtcntnt.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IShellCursorSupplier.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <UndoDelete.hxx> +#include <UndoAttribute.hxx> +#include <rolbck.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <grfatr.hxx> +#include <cntfrm.hxx> +#include <flyfrm.hxx> +#include <swtable.hxx> +#include <redline.hxx> +#include <docary.hxx> +#include <acorrect.hxx> + +#include <strings.hrc> + +using namespace ::com::sun::star; + +// INSERT + +std::optional<OUString> SwUndoInsert::GetTextFromDoc() const +{ + std::optional<OUString> aResult; + + SwNodeIndex aNd( m_pDoc->GetNodes(), m_nNode); + SwContentNode* pCNd = aNd.GetNode().GetContentNode(); + + if( pCNd->IsTextNode() ) + { + OUString sText = pCNd->GetTextNode()->GetText(); + + sal_Int32 nStart = m_nContent-m_nLen; + sal_Int32 nLength = m_nLen; + + if (nStart < 0) + { + nLength += nStart; + nStart = 0; + } + + aResult = sText.copy(nStart, nLength); + } + + return aResult; +} + +void SwUndoInsert::Init(const SwNodeIndex & rNd) +{ + // consider Redline + m_pDoc = rNd.GetNode().GetDoc(); + if( m_pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlData.reset( new SwRedlineData( RedlineType::Insert, + m_pDoc->getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + maUndoText = GetTextFromDoc(); + + m_bCacheComment = false; +} + +SwUndoInsert::SwUndoInsert( const SwNodeIndex& rNd, sal_Int32 nCnt, + sal_Int32 nL, + const SwInsertFlags nInsertFlags, + bool bWDelim ) + : SwUndo(SwUndoId::TYPING, rNd.GetNode().GetDoc()), + m_nNode( rNd.GetIndex() ), m_nContent(nCnt), m_nLen(nL), + m_bIsWordDelim( bWDelim ), m_bIsAppend( false ) + , m_bWithRsid(false) + , m_nInsertFlags(nInsertFlags) +{ + Init(rNd); +} + +SwUndoInsert::SwUndoInsert( const SwNodeIndex& rNd ) + : SwUndo(SwUndoId::SPLITNODE, rNd.GetNode().GetDoc()), + m_nNode( rNd.GetIndex() ), m_nContent(0), m_nLen(1), + m_bIsWordDelim( false ), m_bIsAppend( true ) + , m_bWithRsid(false) + , m_nInsertFlags(SwInsertFlags::EMPTYEXPAND) +{ + Init(rNd); +} + +// Check if the next Insert can be combined with the current one. If so +// change the length and InsPos. As a result, SwDoc::Inser will not add a +// new object into the Undo list. + +bool SwUndoInsert::CanGrouping( sal_Unicode cIns ) +{ + if( !m_bIsAppend && m_bIsWordDelim == + !GetAppCharClass().isLetterNumeric( OUString( cIns )) ) + { + m_nLen++; + m_nContent++; + + if (maUndoText) + (*maUndoText) += OUStringChar(cIns); + + return true; + } + return false; +} + +bool SwUndoInsert::CanGrouping( const SwPosition& rPos ) +{ + bool bRet = false; + if( m_nNode == rPos.nNode.GetIndex() && + m_nContent == rPos.nContent.GetIndex() ) + { + // consider Redline + SwDoc& rDoc = *rPos.nNode.GetNode().GetDoc(); + if( ( ~RedlineFlags::ShowMask & rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ) == + ( ~RedlineFlags::ShowMask & GetRedlineFlags() ) ) + { + bRet = true; + + // then there is or was still an active Redline: + // Check if there is another Redline at the InsPosition. If the + // same exists only once, it can be combined. + const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + if( !rTable.empty() ) + { + SwRedlineData aRData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ); + const SwIndexReg* pIReg = rPos.nContent.GetIdxReg(); + for(SwRangeRedline* pRedl : rTable) + { + SwIndex* pIdx = &pRedl->End()->nContent; + if( pIReg == pIdx->GetIdxReg() && + m_nContent == pIdx->GetIndex() ) + { + if( !pRedl->HasMark() || !m_pRedlData || + *pRedl != *m_pRedlData || *pRedl != aRData ) + { + bRet = false; + break; + } + } + } + } + } + } + return bRet; +} + +SwUndoInsert::~SwUndoInsert() +{ + if (m_pUndoNodeIndex) // delete the section from UndoNodes array + { + // Insert saves the content in IconSection + SwNodes& rUNds = m_pUndoNodeIndex->GetNodes(); + rUNds.Delete(*m_pUndoNodeIndex, + rUNds.GetEndOfExtras().GetIndex() - m_pUndoNodeIndex->GetIndex()); + m_pUndoNodeIndex.reset(); + } + else // the inserted text + { + maText.reset(); + } + m_pRedlData.reset(); +} + +void SwUndoInsert::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pTmpDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + if( m_bIsAppend ) + { + pPam->GetPoint()->nNode = m_nNode; + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + pPam->GetPoint()->nContent.Assign( pPam->GetContentNode(), 0 ); + pPam->SetMark(); + pPam->Move( fnMoveBackward ); + pPam->Exchange(); + pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( *pPam, true, RedlineType::Any ); + } + pPam->DeleteMark(); + pTmpDoc->getIDocumentContentOperations().DelFullPara( *pPam ); + pPam->GetPoint()->nContent.Assign( pPam->GetContentNode(), 0 ); + } + else + { + sal_uLong nNd = m_nNode; + sal_Int32 nCnt = m_nContent; + if( m_nLen ) + { + SwNodeIndex aNd( pTmpDoc->GetNodes(), m_nNode); + SwContentNode* pCNd = aNd.GetNode().GetContentNode(); + SwPaM aPaM( *pCNd, m_nContent ); + + aPaM.SetMark(); + + SwTextNode * const pTextNode( pCNd->GetTextNode() ); + if ( pTextNode ) + { + aPaM.GetPoint()->nContent -= m_nLen; + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( aPaM, true, RedlineType::Any ); + if (m_bWithRsid) + { + // RSID was added: remove any CHARFMT/AUTOFMT that may be + // set on the deleted text; EraseText will leave empty + // ones behind otherwise + pTextNode->DeleteAttributes(RES_TXTATR_AUTOFMT, + aPaM.GetPoint()->nContent.GetIndex(), + aPaM.GetMark()->nContent.GetIndex()); + pTextNode->DeleteAttributes(RES_TXTATR_CHARFMT, + aPaM.GetPoint()->nContent.GetIndex(), + aPaM.GetMark()->nContent.GetIndex()); + } + RemoveIdxFromRange( aPaM, false ); + maText = pTextNode->GetText().copy(m_nContent-m_nLen, m_nLen); + pTextNode->EraseText( aPaM.GetPoint()->nContent, m_nLen ); + } + else // otherwise Graphics/OLE/Text/... + { + aPaM.Move(fnMoveBackward); + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( aPaM, true, RedlineType::Any ); + RemoveIdxFromRange( aPaM, false ); + } + + nNd = aPaM.GetPoint()->nNode.GetIndex(); + nCnt = aPaM.GetPoint()->nContent.GetIndex(); + + if (!maText) + { + m_pUndoNodeIndex.reset( + new SwNodeIndex(m_pDoc->GetNodes().GetEndOfContent())); + MoveToUndoNds(aPaM, m_pUndoNodeIndex.get()); + } + m_nNode = aPaM.GetPoint()->nNode.GetIndex(); + m_nContent = aPaM.GetPoint()->nContent.GetIndex(); + } + + // set cursor to Undo range + pPam->DeleteMark(); + + pPam->GetPoint()->nNode = nNd; + pPam->GetPoint()->nContent.Assign( + pPam->GetPoint()->nNode.GetNode().GetContentNode(), nCnt ); + } + + maUndoText.reset(); +} + +void SwUndoInsert::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pTmpDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + pPam->DeleteMark(); + + if( m_bIsAppend ) + { + pPam->GetPoint()->nNode = m_nNode - 1; + pTmpDoc->getIDocumentContentOperations().AppendTextNode( *pPam->GetPoint() ); + + pPam->SetMark(); + pPam->Move( fnMoveBackward ); + pPam->Exchange(); + + if( m_pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = pTmpDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + pTmpDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, *pPam ), true); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !pTmpDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + pTmpDoc->getIDocumentRedlineAccess().SplitRedline( *pPam ); + + pPam->DeleteMark(); + } + else + { + pPam->GetPoint()->nNode = m_nNode; + SwContentNode *const pCNd = + pPam->GetPoint()->nNode.GetNode().GetContentNode(); + pPam->GetPoint()->nContent.Assign( pCNd, m_nContent ); + + if( m_nLen ) + { + const bool bMvBkwrd = MovePtBackward( *pPam ); + + if (maText) + { + SwTextNode *const pTextNode = pCNd->GetTextNode(); + OSL_ENSURE( pTextNode, "where is my textnode ?" ); + OUString const ins( + pTextNode->InsertText( *maText, pPam->GetMark()->nContent, + m_nInsertFlags) ); + assert(ins.getLength() == maText->getLength()); // must succeed + maText.reset(); + if (m_bWithRsid) // re-insert RSID + { + SwPaM pam(*pPam->GetMark(), nullptr); // mark -> point + pTmpDoc->UpdateRsid(pam, ins.getLength()); + } + } + else + { + // re-insert content again (first detach m_pUndoNodeIndex!) + sal_uLong const nMvNd = m_pUndoNodeIndex->GetIndex(); + m_pUndoNodeIndex.reset(); + MoveFromUndoNds(*pTmpDoc, nMvNd, *pPam->GetMark()); + } + m_nNode = pPam->GetMark()->nNode.GetIndex(); + m_nContent = pPam->GetMark()->nContent.GetIndex(); + + MovePtForward( *pPam, bMvBkwrd ); + pPam->Exchange(); + if( m_pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = pTmpDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + pTmpDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, + *pPam ), true); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !pTmpDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + pTmpDoc->getIDocumentRedlineAccess().SplitRedline(*pPam); + } + } + + maUndoText = GetTextFromDoc(); +} + +void SwUndoInsert::RepeatImpl(::sw::RepeatContext & rContext) +{ + if( !m_nLen ) + return; + + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aNd( rDoc.GetNodes(), m_nNode ); + SwContentNode* pCNd = aNd.GetNode().GetContentNode(); + + if( !m_bIsAppend && 1 == m_nLen ) // >1 than always Text, otherwise Graphics/OLE/Text/... + { + SwPaM aPaM( *pCNd, m_nContent ); + aPaM.SetMark(); + aPaM.Move(fnMoveBackward); + pCNd = aPaM.GetContentNode(); + } + +// What happens with the possible selected range ??? + + switch( pCNd->GetNodeType() ) + { + case SwNodeType::Text: + if( m_bIsAppend ) + { + rDoc.getIDocumentContentOperations().AppendTextNode( *rContext.GetRepeatPaM().GetPoint() ); + } + else + { + OUString const aText( pCNd->GetTextNode()->GetText() ); + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().InsertString( rContext.GetRepeatPaM(), + aText.copy(m_nContent - m_nLen, m_nLen) ); + } + break; + case SwNodeType::Grf: + { + SwGrfNode* pGrfNd = static_cast<SwGrfNode*>(pCNd); + OUString sFile; + OUString sFilter; + if( pGrfNd->IsGrfLink() ) + pGrfNd->GetFileFilterNms( &sFile, &sFilter ); + + rDoc.getIDocumentContentOperations().InsertGraphic( + rContext.GetRepeatPaM(), sFile, sFilter, + &pGrfNd->GetGrf(), + nullptr/* Graphics collection*/, nullptr, nullptr ); + } + break; + + case SwNodeType::Ole: + { + // StarView does not yet provide an option to copy a StarOBJ + SwOLEObj& rSwOLE = static_cast<SwOLENode*>(pCNd)->GetOLEObj(); + + // temporary storage until object is inserted + // TODO/MBA: seems that here a physical copy is done - not as in drawing layer! Testing! + // TODO/LATER: Copying through the container would copy the replacement image as well + comphelper::EmbeddedObjectContainer aCnt; + OUString aName = aCnt.CreateUniqueObjectName(); + if (aCnt.StoreEmbeddedObject(rSwOLE.GetOleRef(), aName, true, OUString(), OUString())) + { + uno::Reference < embed::XEmbeddedObject > aNew = aCnt.GetEmbeddedObject( aName ); + rDoc.getIDocumentContentOperations().InsertEmbObject( + rContext.GetRepeatPaM(), + svt::EmbeddedObjectRef( aNew, + static_cast<SwOLENode*>(pCNd)->GetAspect() ), + nullptr ); + } + + break; + } + + default: break; + } +} + +SwRewriter SwUndoInsert::GetRewriter() const +{ + SwRewriter aResult; + std::optional<OUString> aStr; + bool bDone = false; + + if (maText) + aStr = maText; + else if (maUndoText) + aStr = maUndoText; + + if (aStr) + { + OUString aString = ShortenString(DenoteSpecialCharacters(*aStr), + nUndoStringLength, + SwResId(STR_LDOTS)); + + aResult.AddRule(UndoArg1, aString); + + bDone = true; + } + + if ( ! bDone ) + { + aResult.AddRule(UndoArg1, "??"); + } + + return aResult; +} + +class SwUndoReplace::Impl + : private SwUndoSaveContent +{ + OUString m_sOld; + OUString m_sIns; + sal_uLong m_nSttNd, m_nEndNd, m_nOffset; + sal_Int32 m_nSttCnt, m_nEndCnt, m_nSetPos, m_nSelEnd; + bool m_bSplitNext : 1; + bool m_bRegExp : 1; + // metadata references for paragraph and following para (if m_bSplitNext) + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd; + +public: + Impl(SwPaM const& rPam, OUString const& rIns, bool const bRegExp); + virtual ~Impl() + { + } + + void UndoImpl( ::sw::UndoRedoContext & ); + void RedoImpl( ::sw::UndoRedoContext & ); + + void SetEnd(SwPaM const& rPam); + + OUString const& GetOld() const { return m_sOld; } + OUString const& GetIns() const { return m_sIns; } +}; + +SwUndoReplace::SwUndoReplace(SwPaM const& rPam, + OUString const& rIns, bool const bRegExp) + : SwUndo( SwUndoId::REPLACE, rPam.GetDoc() ) + , m_pImpl(std::make_unique<Impl>(rPam, rIns, bRegExp)) +{ +} + +SwUndoReplace::~SwUndoReplace() +{ +} + +void SwUndoReplace::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pImpl->UndoImpl(rContext); +} + +void SwUndoReplace::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pImpl->RedoImpl(rContext); +} + +SwRewriter +MakeUndoReplaceRewriter(sal_uLong const occurrences, + OUString const& sOld, OUString const& sNew) +{ + SwRewriter aResult; + + if (1 < occurrences) + { + aResult.AddRule(UndoArg1, OUString::number(occurrences)); + aResult.AddRule(UndoArg2, SwResId(STR_OCCURRENCES_OF)); + + OUString aTmpStr = SwResId(STR_START_QUOTE); + aTmpStr += ShortenString(sOld, nUndoStringLength, + SwResId(STR_LDOTS)); + aTmpStr += SwResId(STR_END_QUOTE); + aResult.AddRule(UndoArg3, aTmpStr); + } + else if (1 == occurrences) + { + { + OUString aTmpStr = SwResId(STR_START_QUOTE); + // #i33488 # + aTmpStr += ShortenString(sOld, nUndoStringLength, + SwResId(STR_LDOTS)); + aTmpStr += SwResId(STR_END_QUOTE); + aResult.AddRule(UndoArg1, aTmpStr); + } + + aResult.AddRule(UndoArg2, SwResId(STR_YIELDS)); + + { + OUString aTmpStr = SwResId(STR_START_QUOTE); + // #i33488 # + aTmpStr += ShortenString(sNew, nUndoStringLength, + SwResId(STR_LDOTS)); + aTmpStr += SwResId(STR_END_QUOTE); + aResult.AddRule(UndoArg3, aTmpStr); + } + } + + return aResult; +} + +SwRewriter SwUndoReplace::GetRewriter() const +{ + return MakeUndoReplaceRewriter(1, m_pImpl->GetOld(), m_pImpl->GetIns()); +} + +void SwUndoReplace::SetEnd(SwPaM const& rPam) +{ + m_pImpl->SetEnd(rPam); +} + +SwUndoReplace::Impl::Impl( + SwPaM const& rPam, OUString const& rIns, bool const bRegExp) + : m_sIns( rIns ) + , m_nOffset( 0 ) + , m_bRegExp(bRegExp) +{ + + const SwPosition * pStt( rPam.Start() ); + const SwPosition * pEnd( rPam.End() ); + + m_nSttNd = m_nEndNd = pStt->nNode.GetIndex(); + m_nSttCnt = pStt->nContent.GetIndex(); + m_nSelEnd = m_nEndCnt = pEnd->nContent.GetIndex(); + + m_bSplitNext = m_nSttNd != pEnd->nNode.GetIndex(); + + SwTextNode* pNd = pStt->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + + m_pHistory.reset( new SwHistory ); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace); + + m_nSetPos = m_pHistory->Count(); + + sal_uLong nNewPos = pStt->nNode.GetIndex(); + m_nOffset = m_nSttNd - nNewPos; + + if ( pNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pNd->GetpSwpHints(), nNewPos, 0, + pNd->GetText().getLength(), true ); + } + + if ( m_bSplitNext ) + { + if( pNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pNd->GetpSwAttrSet(), nNewPos ); + m_pHistory->Add( pNd->GetTextColl(), nNewPos, SwNodeType::Text ); + + SwTextNode* pNext = pEnd->nNode.GetNode().GetTextNode(); + sal_uLong nTmp = pNext->GetIndex(); + m_pHistory->CopyAttr( pNext->GetpSwpHints(), nTmp, 0, + pNext->GetText().getLength(), true ); + if( pNext->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pNext->GetpSwAttrSet(), nTmp ); + m_pHistory->Add( pNext->GetTextColl(),nTmp, SwNodeType::Text ); + // METADATA: store + m_pMetadataUndoStart = pNd ->CreateUndo(); + m_pMetadataUndoEnd = pNext->CreateUndo(); + } + + if( !m_pHistory->Count() ) + { + m_pHistory.reset(); + } + + const sal_Int32 nECnt = m_bSplitNext ? pNd->GetText().getLength() + : pEnd->nContent.GetIndex(); + m_sOld = pNd->GetText().copy( m_nSttCnt, nECnt - m_nSttCnt ); +} + +void SwUndoReplace::Impl::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM & rPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + rPam.DeleteMark(); + + SwTextNode* pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + + SwAutoCorrExceptWord* pACEWord = pDoc->GetAutoCorrExceptWord(); + if( pACEWord ) + { + if ((1 == m_sIns.getLength()) && (1 == m_sOld.getLength())) + { + SwPosition aPos( *pNd ); aPos.nContent.Assign( pNd, m_nSttCnt ); + pACEWord->CheckChar( aPos, m_sOld[ 0 ] ); + } + pDoc->SetAutoCorrExceptWord( nullptr ); + } + + // don't look at m_sIns for deletion, maybe it was not completely inserted + { + rPam.GetPoint()->nNode = *pNd; + rPam.GetPoint()->nContent.Assign( pNd, m_nSttCnt ); + rPam.SetMark(); + rPam.GetPoint()->nNode = m_nSttNd - m_nOffset; + rPam.GetPoint()->nContent.Assign(rPam.GetContentNode(), m_nSttNd == m_nEndNd ? m_nEndCnt : pNd->Len()); + + // replace only in start node, without regex + bool const ret = pDoc->getIDocumentContentOperations().ReplaceRange(rPam, m_sOld, false); + assert(ret); (void)ret; + if (m_nSttNd != m_nEndNd) + { // in case of regex inserting paragraph breaks, join nodes... + assert(rPam.GetMark()->nContent == rPam.GetMark()->nNode.GetNode().GetTextNode()->Len()); + rPam.GetPoint()->nNode = m_nEndNd - m_nOffset; + rPam.GetPoint()->nContent.Assign(rPam.GetContentNode(true), m_nEndCnt); + pDoc->getIDocumentContentOperations().DeleteAndJoin(rPam); + } + rPam.DeleteMark(); + pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + } + + if( m_bSplitNext ) + { + SwPosition aPos(*pNd, pNd->Len()); + pDoc->getIDocumentContentOperations().SplitNode( aPos, false ); + pNd->RestoreMetadata(m_pMetadataUndoEnd); + pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); + // METADATA: restore + pNd->RestoreMetadata(m_pMetadataUndoStart); + } + + if( m_pHistory ) + { + if( pNd->GetpSwpHints() ) + pNd->ClearSwpHintsArr( true ); + + m_pHistory->TmpRollback( pDoc, m_nSetPos, false ); + if ( m_nSetPos ) // there were footnotes/FlyFrames + { + // are there others than these? + if( m_nSetPos < m_pHistory->Count() ) + { + // than save those attributes as well + SwHistory aHstr; + aHstr.Move( 0, m_pHistory.get(), m_nSetPos ); + m_pHistory->Rollback( pDoc ); + m_pHistory->Move( 0, &aHstr ); + } + else + { + m_pHistory->Rollback( pDoc ); + m_pHistory.reset(); + } + } + } + + rPam.GetPoint()->nNode = m_nSttNd; + rPam.GetPoint()->nContent = m_nSttCnt; +} + +void SwUndoReplace::Impl::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + rPam.DeleteMark(); + rPam.GetPoint()->nNode = m_nSttNd; + + SwTextNode* pNd = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + rPam.GetPoint()->nContent.Assign( pNd, m_nSttCnt ); + rPam.SetMark(); + if( m_bSplitNext ) + { + rPam.GetPoint()->nNode = m_nSttNd + 1; + pNd = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + } + rPam.GetPoint()->nContent.Assign( pNd, m_nSelEnd ); + + if( m_pHistory ) + { + auto xSave = std::make_unique<SwHistory>(); + std::swap(m_pHistory, xSave); + + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace); + m_nSetPos = m_pHistory->Count(); + + std::swap(xSave, m_pHistory); + m_pHistory->Move(0, xSave.get()); + } + else + { + m_pHistory.reset( new SwHistory ); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace); + m_nSetPos = m_pHistory->Count(); + if( !m_nSetPos ) + { + m_pHistory.reset(); + } + } + + rDoc.getIDocumentContentOperations().ReplaceRange( rPam, m_sIns, m_bRegExp ); + rPam.DeleteMark(); +} + +void SwUndoReplace::Impl::SetEnd(SwPaM const& rPam) +{ + const SwPosition* pEnd = rPam.End(); + m_nEndNd = m_nOffset + pEnd->nNode.GetIndex(); + m_nEndCnt = pEnd->nContent.GetIndex(); +} + +SwUndoReRead::SwUndoReRead( const SwPaM& rPam, const SwGrfNode& rGrfNd ) + : SwUndo( SwUndoId::REREAD, rPam.GetDoc() ), mnPosition( rPam.GetPoint()->nNode.GetIndex() ) +{ + SaveGraphicData( rGrfNd ); +} + +SwUndoReRead::~SwUndoReRead() +{ +} + +void SwUndoReRead::SetAndSave(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwGrfNode* pGrfNd = rDoc.GetNodes()[ mnPosition ]->GetGrfNode(); + + if( !pGrfNd ) + return ; + + // cache the old values + std::unique_ptr<Graphic> pOldGrf( mpGraphic ? new Graphic(*mpGraphic) : nullptr); + std::optional<OUString> aOldNm = maNm; + MirrorGraph nOldMirr = mnMirror; + // since all of them are cleared/modified by SaveGraphicData: + SaveGraphicData( *pGrfNd ); + + if( aOldNm ) + { + pGrfNd->ReRead( *aOldNm, maFltr ? *maFltr : OUString() ); + } + else + { + pGrfNd->ReRead( OUString(), OUString(), pOldGrf.get() ); + } + + if( MirrorGraph::Dont != nOldMirr ) + pGrfNd->SetAttr( SwMirrorGrf() ); + + rContext.SetSelections(pGrfNd->GetFlyFormat(), nullptr); +} + +void SwUndoReRead::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SetAndSave(rContext); +} + +void SwUndoReRead::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SetAndSave(rContext); +} + +void SwUndoReRead::SaveGraphicData( const SwGrfNode& rGrfNd ) +{ + if( rGrfNd.IsGrfLink() ) + { + maNm = OUString(); + maFltr = OUString(); + rGrfNd.GetFileFilterNms(&*maNm, &*maFltr); + mpGraphic.reset(); + } + else + { + mpGraphic.reset( new Graphic( rGrfNd.GetGrf(true) ) ); + maNm.reset(); + maFltr.reset(); + } + mnMirror = rGrfNd.GetSwAttrSet().GetMirrorGrf().GetValue(); +} + +SwUndoInsertLabel::SwUndoInsertLabel( const SwLabelType eTyp, + const OUString &rText, + const OUString& rSeparator, + const OUString& rNumberSeparator, + const bool bBef, + const sal_uInt16 nInitId, + const OUString& rCharacterStyle, + const bool bCpyBorder, + const SwDoc* pDoc ) + : SwUndo( SwUndoId::INSERTLABEL, pDoc ), + m_sText( rText ), + m_sSeparator( rSeparator ), + m_sNumberSeparator( rNumberSeparator ),//#i61007# order of captions + m_sCharacterStyle( rCharacterStyle ), + m_nFieldId( nInitId ), + m_eType( eTyp ), + m_nLayerId( 0 ), + m_bBefore( bBef ), + m_bCopyBorder( bCpyBorder ) +{ + m_bUndoKeep = false; + OBJECT.pUndoFly = nullptr; + OBJECT.pUndoAttr = nullptr; +} + +SwUndoInsertLabel::~SwUndoInsertLabel() +{ + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + delete OBJECT.pUndoFly; + delete OBJECT.pUndoAttr; + } + else + delete NODE.pUndoInsNd; +} + +void SwUndoInsertLabel::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + OSL_ENSURE( OBJECT.pUndoAttr && OBJECT.pUndoFly, "Pointer not initialized" ); + SwFrameFormat* pFormat; + SdrObject *pSdrObj = nullptr; + if( OBJECT.pUndoAttr && + nullptr != (pFormat = static_cast<SwFrameFormat*>(OBJECT.pUndoAttr->GetFormat( rDoc ))) && + ( SwLabelType::Draw != m_eType || + nullptr != (pSdrObj = pFormat->FindSdrObject()) ) ) + { + OBJECT.pUndoAttr->UndoImpl(rContext); + OBJECT.pUndoFly->UndoImpl(rContext); + if( SwLabelType::Draw == m_eType ) + { + pSdrObj->SetLayer( m_nLayerId ); + } + } + } + else if( NODE.nNode ) + { + if ( m_eType == SwLabelType::Table && m_bUndoKeep ) + { + SwTableNode *pNd = rDoc.GetNodes()[ + rDoc.GetNodes()[NODE.nNode-1]->StartOfSectionIndex()]->GetTableNode(); + if ( pNd ) + pNd->GetTable().GetFrameFormat()->ResetFormatAttr( RES_KEEP ); + } + SwPaM aPam( rDoc.GetNodes().GetEndOfContent() ); + aPam.GetPoint()->nNode = NODE.nNode; + aPam.SetMark(); + aPam.GetPoint()->nNode = NODE.nNode + 1; + NODE.pUndoInsNd = new SwUndoDelete( aPam, true ); + } +} + +void SwUndoInsertLabel::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + OSL_ENSURE( OBJECT.pUndoAttr && OBJECT.pUndoFly, "Pointer not initialized" ); + SwFrameFormat* pFormat; + SdrObject *pSdrObj = nullptr; + if( OBJECT.pUndoAttr && + nullptr != (pFormat = static_cast<SwFrameFormat*>(OBJECT.pUndoAttr->GetFormat( rDoc ))) && + ( SwLabelType::Draw != m_eType || + nullptr != (pSdrObj = pFormat->FindSdrObject()) ) ) + { + OBJECT.pUndoFly->RedoImpl(rContext); + OBJECT.pUndoAttr->RedoImpl(rContext); + if( SwLabelType::Draw == m_eType ) + { + pSdrObj->SetLayer( m_nLayerId ); + if( pSdrObj->GetLayer() == rDoc.getIDocumentDrawModelAccess().GetHellId() ) + pSdrObj->SetLayer( rDoc.getIDocumentDrawModelAccess().GetHeavenId() ); + // OD 02.07.2003 #108784# + else if( pSdrObj->GetLayer() == rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId() ) + pSdrObj->SetLayer( rDoc.getIDocumentDrawModelAccess().GetInvisibleHeavenId() ); + } + } + } + else if( NODE.pUndoInsNd ) + { + if ( m_eType == SwLabelType::Table && m_bUndoKeep ) + { + SwTableNode *pNd = rDoc.GetNodes()[ + rDoc.GetNodes()[NODE.nNode-1]->StartOfSectionIndex()]->GetTableNode(); + if ( pNd ) + pNd->GetTable().GetFrameFormat()->SetFormatAttr( SvxFormatKeepItem(true, RES_KEEP) ); + } + NODE.pUndoInsNd->UndoImpl(rContext); + delete NODE.pUndoInsNd; + NODE.pUndoInsNd = nullptr; + } +} + +void SwUndoInsertLabel::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + const SwPosition& rPos = *rContext.GetRepeatPaM().GetPoint(); + + sal_uLong nIdx = 0; + + SwContentNode* pCNd = rPos.nNode.GetNode().GetContentNode(); + if( pCNd ) + switch( m_eType ) + { + case SwLabelType::Table: + { + const SwTableNode* pTNd = pCNd->FindTableNode(); + if( pTNd ) + nIdx = pTNd->GetIndex(); + } + break; + + case SwLabelType::Fly: + case SwLabelType::Object: + { + SwFlyFrame* pFly; + SwContentFrame *pCnt = pCNd->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ); + if( pCnt && nullptr != ( pFly = pCnt->FindFlyFrame() ) ) + nIdx = pFly->GetFormat()->GetContent().GetContentIdx()->GetIndex(); + } + break; + case SwLabelType::Draw: + break; + } + + if( nIdx ) + { + rDoc.InsertLabel( m_eType, m_sText, m_sSeparator, m_sNumberSeparator, m_bBefore, + m_nFieldId, nIdx, m_sCharacterStyle, m_bCopyBorder ); + } +} + +SwRewriter SwUndoInsertLabel::GetRewriter() const +{ + return CreateRewriter(m_sText); +} + +SwRewriter SwUndoInsertLabel::CreateRewriter(const OUString &rStr) +{ + SwRewriter aRewriter; + + OUString aTmpStr; + + if (!rStr.isEmpty()) + { + aTmpStr += SwResId(STR_START_QUOTE); + aTmpStr += ShortenString(rStr, nUndoStringLength, + SwResId(STR_LDOTS)); + aTmpStr += SwResId(STR_END_QUOTE); + } + + aRewriter.AddRule(UndoArg1, aTmpStr); + + return aRewriter; +} + +void SwUndoInsertLabel::SetFlys( SwFrameFormat& rOldFly, SfxItemSet const & rChgSet, + SwFrameFormat& rNewFly ) +{ + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + SwUndoFormatAttrHelper aTmp( rOldFly, false ); + rOldFly.SetFormatAttr( rChgSet ); + if ( aTmp.GetUndo() ) + { + OBJECT.pUndoAttr = aTmp.ReleaseUndo().release(); + } + OBJECT.pUndoFly = new SwUndoInsLayFormat( &rNewFly,0,0 ); + } +} + +void SwUndoInsertLabel::SetDrawObj( SdrLayerID nLId ) +{ + if( SwLabelType::Draw == m_eType ) + { + m_nLayerId = nLId; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unmove.cxx b/sw/source/core/undo/unmove.cxx new file mode 100644 index 000000000..6888d477f --- /dev/null +++ b/sw/source/core/undo/unmove.cxx @@ -0,0 +1,308 @@ +/* -*- 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 <UndoSplitMove.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> + +// MOVE +SwUndoMove::SwUndoMove( const SwPaM& rRange, const SwPosition& rMvPos ) + : SwUndo( SwUndoId::MOVE, rRange.GetDoc() ) + , SwUndRng( rRange ) + , m_nDestStartNode(0) + , m_nDestEndNode(0) + , m_nInsPosNode(0) + , m_nMoveDestNode(rMvPos.nNode.GetIndex()) + , m_nDestStartContent(0) + , m_nDestEndContent(0) + , m_nInsPosContent(0) + , m_nMoveDestContent(rMvPos.nContent.GetIndex()) + , m_bJoinNext(false) + , m_bMoveRange(false) + , m_bMoveRedlines(false) +{ + // get StartNode from footnotes before delete! + SwDoc* pDoc = rRange.GetDoc(); + SwTextNode* pTextNd = pDoc->GetNodes()[ m_nSttNode ]->GetTextNode(); + SwTextNode* pEndTextNd = pDoc->GetNodes()[ m_nEndNode ]->GetTextNode(); + + m_pHistory.reset( new SwHistory ); + + if( pTextNd ) + { + m_pHistory->Add( pTextNd->GetTextColl(), m_nSttNode, SwNodeType::Text ); + if ( pTextNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode, + 0, pTextNd->GetText().getLength(), false ); + } + if( pTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode ); + } + if( pEndTextNd && pEndTextNd != pTextNd ) + { + m_pHistory->Add( pEndTextNd->GetTextColl(), m_nEndNode, SwNodeType::Text ); + if ( pEndTextNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), m_nEndNode, + 0, pEndTextNd->GetText().getLength(), false ); + } + if( pEndTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pEndTextNd->GetpSwAttrSet(), m_nEndNode ); + } + + pTextNd = rMvPos.nNode.GetNode().GetTextNode(); + if (nullptr != pTextNd) + { + m_pHistory->Add( pTextNd->GetTextColl(), m_nMoveDestNode, SwNodeType::Text ); + if ( pTextNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nMoveDestNode, + 0, pTextNd->GetText().getLength(), false ); + } + if( pTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nMoveDestNode ); + } + + m_nFootnoteStart = m_pHistory->Count(); + DelFootnote( rRange ); + + if( m_pHistory && !m_pHistory->Count() ) + m_pHistory.reset(); +} + +SwUndoMove::SwUndoMove( SwDoc* pDoc, const SwNodeRange& rRg, + const SwNodeIndex& rMvPos ) + : SwUndo(SwUndoId::MOVE, pDoc) + , m_nDestStartNode(0) + , m_nDestEndNode(0) + , m_nInsPosNode(0) + , m_nMoveDestNode(rMvPos.GetIndex()) + , m_nDestStartContent(0) + , m_nDestEndContent(0) + , m_nInsPosContent(0) + , m_bMoveRedlines(false) +{ + m_bMoveRange = true; + m_bJoinNext = false; + + m_nSttContent = m_nEndContent = m_nMoveDestContent = COMPLETE_STRING; + + m_nSttNode = rRg.aStart.GetIndex(); + m_nEndNode = rRg.aEnd.GetIndex(); + +// DelFootnote( rRange ); +// FIXME: duplication of the method body of DelFootnote below + + // is the current move from ContentArea into the special section? + sal_uLong nContentStt = pDoc->GetNodes().GetEndOfAutotext().GetIndex(); + if( m_nMoveDestNode < nContentStt && rRg.aStart.GetIndex() > nContentStt ) + { + // delete all footnotes since they are undesired there + SwPosition aPtPos( rRg.aEnd ); + SwContentNode* pCNd = rRg.aEnd.GetNode().GetContentNode(); + if( pCNd ) + aPtPos.nContent.Assign( pCNd, pCNd->Len() ); + SwPosition aMkPos( rRg.aStart ); + if( nullptr != ( pCNd = aMkPos.nNode.GetNode().GetContentNode() )) + aMkPos.nContent.Assign( pCNd, 0 ); + + DelContentIndex( aMkPos, aPtPos, DelContentType::Ftn ); + + if( m_pHistory && !m_pHistory->Count() ) + m_pHistory.reset(); + } + + m_nFootnoteStart = 0; +} + +void SwUndoMove::SetDestRange( const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + const SwNodeIndex& rInsPos ) +{ + m_nDestStartNode = rStt.GetIndex(); + m_nDestEndNode = rEnd.GetIndex(); + if( m_nDestStartNode > m_nDestEndNode ) + { + m_nDestStartNode = m_nDestEndNode; + m_nDestEndNode = rStt.GetIndex(); + } + m_nInsPosNode = rInsPos.GetIndex(); + + m_nDestStartContent = m_nDestEndContent = m_nInsPosContent = COMPLETE_STRING; +} + +void SwUndoMove::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + + // Block so that we can jump out of it + do { + // create index position and section based on the existing values + SwNodeIndex aIdx( pDoc->GetNodes(), m_nDestStartNode ); + + if( m_bMoveRange ) + { + // only a move with SwRange + SwNodeRange aRg( aIdx, aIdx ); + aRg.aEnd = m_nDestEndNode; + aIdx = m_nInsPosNode; + bool bSuccess = pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIdx, + SwMoveFlags::DEFAULT ); + if (!bSuccess) + break; + } + else + { + SwPaM aPam( aIdx.GetNode(), m_nDestStartContent, + *pDoc->GetNodes()[ m_nDestEndNode ], m_nDestEndContent ); + + // #i17764# if redlines are to be moved, we may not remove them + // before pDoc->Move gets a chance to handle them + if( ! m_bMoveRedlines ) + RemoveIdxFromRange( aPam, false ); + + SwPosition aPos( *pDoc->GetNodes()[ m_nInsPosNode] ); + SwContentNode* pCNd = aPos.nNode.GetNode().GetContentNode(); + aPos.nContent.Assign( pCNd, m_nInsPosContent ); + + if( pCNd->HasSwAttrSet() ) + pCNd->ResetAllAttr(); + + if( pCNd->IsTextNode() && static_cast<SwTextNode*>(pCNd)->GetpSwpHints() ) + static_cast<SwTextNode*>(pCNd)->ClearSwpHintsArr( false ); + + // first delete all attributes at InsertPos + const bool bSuccess = pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, m_bMoveRedlines + ? SwMoveFlags::REDLINES + : SwMoveFlags::DEFAULT ); + if (!bSuccess) + break; + + aPam.Exchange(); + aPam.DeleteMark(); + if( aPam.GetNode().IsContentNode() ) + aPam.GetNode().GetContentNode()->ResetAllAttr(); + // the Pam will be dropped now + } + + SwTextNode* pTextNd = aIdx.GetNode().GetTextNode(); + if( m_bJoinNext ) + { + { + RemoveIdxRel( aIdx.GetIndex() + 1, SwPosition( aIdx, + SwIndex(pTextNd, pTextNd->GetText().getLength()))); + } + // Are there any Pams in the next TextNode? + pTextNd->JoinNext(); + } + + } while( false ); + + if( m_pHistory ) + { + if( m_nFootnoteStart != m_pHistory->Count() ) + m_pHistory->Rollback( pDoc, m_nFootnoteStart ); + m_pHistory->TmpRollback( pDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + // set the cursor onto Undo area + if( !m_bMoveRange ) + { + AddUndoRedoPaM(rContext); + } +} + +void SwUndoMove::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM& rPam = AddUndoRedoPaM(rContext); + SwDoc& rDoc = rContext.GetDoc(); + + SwNodes& rNds = rDoc.GetNodes(); + SwNodeIndex aIdx( rNds, m_nMoveDestNode ); + + if( m_bMoveRange ) + { + // only a move with SwRange + SwNodeRange aRg( rNds, m_nSttNode, rNds, m_nEndNode ); + rDoc.getIDocumentContentOperations().MoveNodeRange( aRg, aIdx, m_bMoveRedlines + ? SwMoveFlags::REDLINES + : SwMoveFlags::DEFAULT ); + } + else + { + SwPaM aPam(*rPam.GetPoint()); + SetPaM( aPam ); + SwPosition aMvPos( aIdx, SwIndex( aIdx.GetNode().GetContentNode(), + m_nMoveDestContent )); + + DelFootnote( aPam ); + RemoveIdxFromRange( aPam, false ); + + aIdx = aPam.Start()->nNode; + bool bJoinText = aIdx.GetNode().IsTextNode(); + + --aIdx; + rDoc.getIDocumentContentOperations().MoveRange( aPam, aMvPos, + SwMoveFlags::DEFAULT ); + + if( m_nSttNode != m_nEndNode && bJoinText ) + { + ++aIdx; + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + if( pTextNd && pTextNd->CanJoinNext() ) + { + { + RemoveIdxRel( aIdx.GetIndex() + 1, SwPosition( aIdx, + SwIndex(pTextNd, pTextNd->GetText().getLength()))); + } + pTextNd->JoinNext(); + } + } + *rPam.GetPoint() = *aPam.GetPoint(); + rPam.SetMark(); + *rPam.GetMark() = *aPam.GetMark(); + } +} + +void SwUndoMove::DelFootnote( const SwPaM& rRange ) +{ + // is the current move from ContentArea into the special section? + SwDoc* pDoc = rRange.GetDoc(); + sal_uLong nContentStt = pDoc->GetNodes().GetEndOfAutotext().GetIndex(); + if( m_nMoveDestNode < nContentStt && + rRange.GetPoint()->nNode.GetIndex() >= nContentStt ) + { + // delete all footnotes since they are undesired there + DelContentIndex( *rRange.GetMark(), *rRange.GetPoint(), + DelContentType::Ftn ); + + if( m_pHistory && !m_pHistory->Count() ) + { + m_pHistory.reset(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unnum.cxx b/sw/source/core/undo/unnum.cxx new file mode 100644 index 000000000..a4e4b39c0 --- /dev/null +++ b/sw/source/core/undo/unnum.cxx @@ -0,0 +1,395 @@ +/* -*- 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 <UndoNumbering.hxx> +#include <doc.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <osl/diagnose.h> + +SwUndoInsNum::SwUndoInsNum( const SwNumRule& rOldRule, + const SwNumRule& rNewRule, + const SwDoc* pDoc, + SwUndoId nUndoId) + : SwUndo( nUndoId, pDoc ), + m_aNumRule( rNewRule ), + m_pOldNumRule( new SwNumRule( rOldRule )), m_nLRSavePos( 0 ) +{ +} + +SwUndoInsNum::SwUndoInsNum( const SwPaM& rPam, const SwNumRule& rRule ) + : SwUndo( SwUndoId::INSNUM, rPam.GetDoc() ), SwUndRng( rPam ), + m_aNumRule( rRule ), + m_nLRSavePos( 0 ) +{ +} + +SwUndoInsNum::SwUndoInsNum( const SwPosition& rPos, const SwNumRule& rRule, + const OUString& rReplaceRule ) + : SwUndo( SwUndoId::INSNUM, rPos.nNode.GetNode().GetDoc() ), + m_aNumRule( rRule ), + m_sReplaceRule( rReplaceRule ), m_nLRSavePos( 0 ) +{ + // No selection! + m_nEndNode = 0; + m_nEndContent = COMPLETE_STRING; + m_nSttNode = rPos.nNode.GetIndex(); + m_nSttContent = rPos.nContent.GetIndex(); +} + +SwUndoInsNum::~SwUndoInsNum() +{ + m_pHistory.reset(); + m_pOldNumRule.reset(); +} + +SwRewriter SwUndoInsNum::GetRewriter() const +{ + SwRewriter aResult; + if( SwUndoId::INSFMTATTR == GetId() ) + aResult.AddRule(UndoArg1, m_aNumRule.GetName()); + return aResult; +} + +void SwUndoInsNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if( m_pOldNumRule ) + rDoc.ChgNumRuleFormats( *m_pOldNumRule ); + + if( m_pHistory ) + { + if( m_nLRSavePos ) + { + // Update immediately so that potential "old" LRSpaces will be valid again. + m_pHistory->TmpRollback( &rDoc, m_nLRSavePos ); + + } + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + if (m_nSttNode) + { + AddUndoRedoPaM(rContext); + } +} + +void SwUndoInsNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if ( m_pOldNumRule ) + rDoc.ChgNumRuleFormats( m_aNumRule ); + else if ( m_pHistory ) + { + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + if( !m_sReplaceRule.isEmpty() ) + { + rDoc.ReplaceNumRule( *rPam.GetPoint(), m_sReplaceRule, m_aNumRule.GetName() ); + } + else + { + // #i42921# - adapt to changed signature + rDoc.SetNumRule(rPam, m_aNumRule, false); + } + } +} + +void SwUndoInsNum::SetLRSpaceEndPos() +{ + if( m_pHistory ) + m_nLRSavePos = m_pHistory->Count(); +} + +void SwUndoInsNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc( rContext.GetDoc() ); + if ( m_nSttNode ) + { + if( m_sReplaceRule.isEmpty() ) + { + // #i42921# - adapt to changed signature + rDoc.SetNumRule(rContext.GetRepeatPaM(), m_aNumRule, false); + } + } + else + { + rDoc.ChgNumRuleFormats( m_aNumRule ); + } +} + +SwHistory* SwUndoInsNum::GetHistory() +{ + if( !m_pHistory ) + m_pHistory.reset(new SwHistory); + return m_pHistory.get(); +} + +void SwUndoInsNum::SaveOldNumRule( const SwNumRule& rOld ) +{ + if( !m_pOldNumRule ) + m_pOldNumRule.reset(new SwNumRule( rOld )); +} + +SwUndoDelNum::SwUndoDelNum( const SwPaM& rPam ) + : SwUndo( SwUndoId::DELNUM, rPam.GetDoc() ), SwUndRng( rPam ) +{ + m_aNodes.reserve( std::min<sal_uLong>(m_nEndNode - m_nSttNode, 255) ); + m_pHistory.reset( new SwHistory ); +} + +SwUndoDelNum::~SwUndoDelNum() +{ +} + +void SwUndoDelNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + for( const auto& rNode : m_aNodes ) + { + SwTextNode* pNd = rDoc.GetNodes()[ rNode.index ]->GetTextNode(); + OSL_ENSURE( pNd, "Where has the TextNode gone?" ); + pNd->SetAttrListLevel( rNode.level ); + + if( pNd->GetCondFormatColl() ) + pNd->ChkCondColl(); + } + + AddUndoRedoPaM(rContext); +} + +void SwUndoDelNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().DelNumRules(rPam); +} + +void SwUndoDelNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().DelNumRules(rContext.GetRepeatPaM()); +} + +void SwUndoDelNum::AddNode( const SwTextNode& rNd ) +{ + if( rNd.GetNumRule() ) + { + m_aNodes.emplace_back( rNd.GetIndex(), rNd.GetActualListLevel() ); + } +} + +SwUndoMoveNum::SwUndoMoveNum( const SwPaM& rPam, long nOff, bool bIsOutlMv ) + : SwUndo( bIsOutlMv ? SwUndoId::OUTLINE_UD : SwUndoId::MOVENUM, rPam.GetDoc() ), + SwUndRng( rPam ), + nNewStt( 0 ), nOffset( nOff ) +{ + // nOffset: Down => 1 + // Up => -1 +} + +void SwUndoMoveNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + sal_uLong nTmpStt = m_nSttNode, nTmpEnd = m_nEndNode; + + if (m_nEndNode || m_nEndContent != COMPLETE_STRING) // section? + { + if( nNewStt < m_nSttNode ) // moved forwards + m_nEndNode = m_nEndNode - ( m_nSttNode - nNewStt ); + else + m_nEndNode = m_nEndNode + ( nNewStt - m_nSttNode ); + } + m_nSttNode = nNewStt; + + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().MoveParagraph( rPam, -nOffset, + SwUndoId::OUTLINE_UD == GetId() ); + m_nSttNode = nTmpStt; + m_nEndNode = nTmpEnd; +} + +void SwUndoMoveNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().MoveParagraph(rPam, nOffset, SwUndoId::OUTLINE_UD == GetId()); +} + +void SwUndoMoveNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if( SwUndoId::OUTLINE_UD == GetId() ) + { + rDoc.MoveOutlinePara(rContext.GetRepeatPaM(), + 0 < nOffset ? 1 : -1 ); + } + else + { + rDoc.MoveParagraph(rContext.GetRepeatPaM(), nOffset); + } +} + +SwUndoNumUpDown::SwUndoNumUpDown( const SwPaM& rPam, short nOff ) + : SwUndo( nOff > 0 ? SwUndoId::NUMUP : SwUndoId::NUMDOWN, rPam.GetDoc() ), + SwUndRng( rPam ), + nOffset( nOff ) +{ + // nOffset: Down => 1 + // Up => -1 +} + +void SwUndoNumUpDown::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().NumUpDown(rPam, 1 != nOffset ); +} + +void SwUndoNumUpDown::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().NumUpDown(rPam, 1 == nOffset); +} + +void SwUndoNumUpDown::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().NumUpDown(rContext.GetRepeatPaM(), 1 == nOffset); +} + +SwUndoNumOrNoNum::SwUndoNumOrNoNum( const SwNodeIndex& rIdx, bool bOldNum, + bool bNewNum) + : SwUndo( SwUndoId::NUMORNONUM, rIdx.GetNode().GetDoc() ), + nIdx( rIdx.GetIndex() ), mbNewNum(bNewNum), + mbOldNum(bOldNum) +{ +} + +// #115901#, #i40034# +void SwUndoNumOrNoNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwNodeIndex aIdx( rContext.GetDoc().GetNodes(), nIdx ); + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + + if (nullptr != pTextNd) + { + pTextNd->SetCountedInList(mbOldNum); + } +} + +// #115901#, #i40034# +void SwUndoNumOrNoNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwNodeIndex aIdx( rContext.GetDoc().GetNodes(), nIdx ); + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + + if (nullptr != pTextNd) + { + pTextNd->SetCountedInList(mbNewNum); + } +} + +void SwUndoNumOrNoNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if (mbOldNum && ! mbNewNum) + { + rDoc.NumOrNoNum(rContext.GetRepeatPaM().GetPoint()->nNode); + } + else if ( ! mbOldNum && mbNewNum ) + { + rDoc.NumOrNoNum(rContext.GetRepeatPaM().GetPoint()->nNode, true); + } +} + +SwUndoNumRuleStart::SwUndoNumRuleStart( const SwPosition& rPos, bool bFlg ) + : SwUndo( SwUndoId::SETNUMRULESTART, rPos.GetDoc() ), + nIdx( rPos.nNode.GetIndex() ), nOldStt( USHRT_MAX ), + nNewStt( USHRT_MAX ), bSetSttValue( false ), bFlag( bFlg ) +{ +} + +SwUndoNumRuleStart::SwUndoNumRuleStart( const SwPosition& rPos, sal_uInt16 nStt ) + : SwUndo(SwUndoId::SETNUMRULESTART, rPos.GetDoc()) + , nIdx(rPos.nNode.GetIndex()) + , nOldStt(USHRT_MAX) + , nNewStt(nStt) + , bSetSttValue(true) + , bFlag(false) +{ + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + if ( pTextNd ) + { + if ( pTextNd->HasAttrListRestartValue() ) + { + nOldStt = static_cast<sal_uInt16>(pTextNd->GetAttrListRestartValue()); + } + else + { + nOldStt = USHRT_MAX; // indicating, that the list restart value is not set + } + } +} + +void SwUndoNumRuleStart::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPosition const aPos( *rDoc.GetNodes()[ nIdx ] ); + if( bSetSttValue ) + { + rDoc.SetNodeNumStart( aPos, nOldStt ); + } + else + { + rDoc.SetNumRuleStart( aPos, !bFlag ); + } +} + +void SwUndoNumRuleStart::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPosition const aPos( *rDoc.GetNodes()[ nIdx ] ); + if( bSetSttValue ) + { + rDoc.SetNodeNumStart( aPos, nNewStt ); + } + else + { + rDoc.SetNumRuleStart( aPos, bFlag ); + } +} + +void SwUndoNumRuleStart::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if( bSetSttValue ) + { + rDoc.SetNodeNumStart(*rContext.GetRepeatPaM().GetPoint(), nNewStt); + } + else + { + rDoc.SetNumRuleStart(*rContext.GetRepeatPaM().GetPoint(), bFlag); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unoutl.cxx b/sw/source/core/undo/unoutl.cxx new file mode 100644 index 000000000..04341d61a --- /dev/null +++ b/sw/source/core/undo/unoutl.cxx @@ -0,0 +1,49 @@ +/* -*- 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 <doc.hxx> +#include <swundo.hxx> +#include <pam.hxx> + +#include <UndoCore.hxx> + +SwUndoOutlineLeftRight::SwUndoOutlineLeftRight( const SwPaM& rPam, + short nOff ) + : SwUndo( SwUndoId::OUTLINE_LR, rPam.GetDoc() ), SwUndRng( rPam ), m_nOffset( nOff ) +{ +} + +void SwUndoOutlineLeftRight::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPaM( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().OutlineUpDown(rPaM, -m_nOffset); +} + +void SwUndoOutlineLeftRight::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPaM( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().OutlineUpDown(rPaM, m_nOffset); +} + +void SwUndoOutlineLeftRight::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().OutlineUpDown(rContext.GetRepeatPaM(), m_nOffset); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unovwr.cxx b/sw/source/core/undo/unovwr.cxx new file mode 100644 index 000000000..7361da5a4 --- /dev/null +++ b/sw/source/core/undo/unovwr.cxx @@ -0,0 +1,474 @@ +/* -*- 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 <UndoOverwrite.hxx> +#include <unotools/charclass.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IShellCursorSupplier.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <acorrect.hxx> +#include <docary.hxx> +#include <strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +SwUndoOverwrite::SwUndoOverwrite( SwDoc* pDoc, SwPosition& rPos, + sal_Unicode cIns ) + : SwUndo(SwUndoId::OVERWRITE, pDoc), + bGroup( false ) +{ + SwTextNode *const pTextNd = rPos.nNode.GetNode().GetTextNode(); + assert(pTextNd); + sal_Int32 const nTextNdLen = pTextNd->GetText().getLength(); + + nSttNode = rPos.nNode.GetIndex(); + nSttContent = rPos.nContent.GetIndex(); + + if( !pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aPam( rPos.nNode, rPos.nContent.GetIndex(), + rPos.nNode, rPos.nContent.GetIndex()+1 ); + pRedlSaveData.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( aPam, *pRedlSaveData, false )) + { + pRedlSaveData.reset(); + } + if (nSttContent < nTextNdLen) + { + pDoc->getIDocumentRedlineAccess().DeleteRedline(aPam, false, RedlineType::Any); + } + } + + bInsChar = true; + if( nSttContent < nTextNdLen ) // no pure insert? + { + aDelStr += OUStringChar( pTextNd->GetText()[nSttContent] ); + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwRegHistory aRHst( *pTextNd, m_pHistory.get() ); + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), nSttNode, 0, + nTextNdLen, false ); + ++rPos.nContent; + bInsChar = false; + } + + bool bOldExpFlg = pTextNd->IsIgnoreDontExpand(); + pTextNd->SetIgnoreDontExpand( true ); + + pTextNd->InsertText( OUString(cIns), rPos.nContent, + SwInsertFlags::EMPTYEXPAND ); + aInsStr += OUStringChar( cIns ); + + if( !bInsChar ) + { + const SwIndex aTmpIndex( rPos.nContent, -2 ); + pTextNd->EraseText( aTmpIndex, 1 ); + } + pTextNd->SetIgnoreDontExpand( bOldExpFlg ); + + m_bCacheComment = false; +} + +SwUndoOverwrite::~SwUndoOverwrite() +{ +} + +bool SwUndoOverwrite::CanGrouping( SwDoc* pDoc, SwPosition& rPos, + sal_Unicode cIns ) +{ +// What is with only inserted characters? + + // Only deletion of single chars can be combined. + if( rPos.nNode != nSttNode || aInsStr.isEmpty() || + ( !bGroup && aInsStr.getLength() != 1 )) + return false; + + // Is the node a TextNode at all? + SwTextNode * pDelTextNd = rPos.nNode.GetNode().GetTextNode(); + if( !pDelTextNd || + (pDelTextNd->GetText().getLength() != rPos.nContent.GetIndex() && + rPos.nContent.GetIndex() != ( nSttContent + aInsStr.getLength() ))) + return false; + + CharClass& rCC = GetAppCharClass(); + + // ask the char that should be inserted + if (( CH_TXTATR_BREAKWORD == cIns || CH_TXTATR_INWORD == cIns ) || + rCC.isLetterNumeric( OUString( cIns ), 0 ) != + rCC.isLetterNumeric( aInsStr, aInsStr.getLength()-1 ) ) + return false; + + if (!bInsChar && rPos.nContent.GetIndex() < pDelTextNd->GetText().getLength()) + { + SwRedlineSaveDatas aTmpSav; + SwPaM aPam( rPos.nNode, rPos.nContent.GetIndex(), + rPos.nNode, rPos.nContent.GetIndex()+1 ); + + const bool bSaved = FillSaveData( aPam, aTmpSav, false ); + + bool bOk = ( !pRedlSaveData && !bSaved ) || + ( pRedlSaveData && bSaved && + SwUndo::CanRedlineGroup( *pRedlSaveData, aTmpSav, + nSttContent > rPos.nContent.GetIndex() )); + // aTmpSav.DeleteAndDestroyAll(); + if( !bOk ) + return false; + + pDoc->getIDocumentRedlineAccess().DeleteRedline( aPam, false, RedlineType::Any ); + } + + // both 'overwrites' can be combined so 'move' the corresponding character + if( !bInsChar ) + { + if (rPos.nContent.GetIndex() < pDelTextNd->GetText().getLength()) + { + aDelStr += OUStringChar( pDelTextNd->GetText()[rPos.nContent.GetIndex()] ); + ++rPos.nContent; + } + else + bInsChar = true; + } + + bool bOldExpFlg = pDelTextNd->IsIgnoreDontExpand(); + pDelTextNd->SetIgnoreDontExpand( true ); + + OUString const ins( pDelTextNd->InsertText(OUString(cIns), rPos.nContent, + SwInsertFlags::EMPTYEXPAND) ); + assert(ins.getLength() == 1); // check in SwDoc::Overwrite => cannot fail + (void) ins; + aInsStr += OUStringChar( cIns ); + + if( !bInsChar ) + { + const SwIndex aTmpIndex( rPos.nContent, -2 ); + pDelTextNd->EraseText( aTmpIndex, 1 ); + } + pDelTextNd->SetIgnoreDontExpand( bOldExpFlg ); + + bGroup = true; + return true; +} + +void SwUndoOverwrite::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pCurrentPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pCurrentPam->DeleteMark(); + pCurrentPam->GetPoint()->nNode = nSttNode; + SwTextNode* pTextNd = pCurrentPam->GetNode().GetTextNode(); + assert(pTextNd); + SwIndex& rIdx = pCurrentPam->GetPoint()->nContent; + rIdx.Assign( pTextNd, nSttContent ); + + SwAutoCorrExceptWord* pACEWord = pDoc->GetAutoCorrExceptWord(); + if( pACEWord ) + { + if( 1 == aInsStr.getLength() && 1 == aDelStr.getLength() ) + pACEWord->CheckChar( *pCurrentPam->GetPoint(), aDelStr[0] ); + pDoc->SetAutoCorrExceptWord( nullptr ); + } + + // If there was not only an overwrite but also an insert, delete the surplus + if( aInsStr.getLength() > aDelStr.getLength() ) + { + rIdx += aDelStr.getLength(); + pTextNd->EraseText( rIdx, aInsStr.getLength() - aDelStr.getLength() ); + rIdx = nSttContent; + } + + if( !aDelStr.isEmpty() ) + { + bool bOldExpFlg = pTextNd->IsIgnoreDontExpand(); + pTextNd->SetIgnoreDontExpand( true ); + + ++rIdx; + for( sal_Int32 n = 0; n < aDelStr.getLength(); n++ ) + { + // do it individually, to keep the attributes! + OUString aTmpStr(aDelStr[n]); + OUString const ins( pTextNd->InsertText(aTmpStr, rIdx) ); + assert(ins.getLength() == 1); // cannot fail + (void) ins; + rIdx -= 2; + pTextNd->EraseText( rIdx, 1 ); + rIdx += 2; + } + pTextNd->SetIgnoreDontExpand( bOldExpFlg ); + --rIdx; + } + + if( m_pHistory ) + { + if( pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( false ); + m_pHistory->TmpRollback( pDoc, 0, false ); + } + + if( pCurrentPam->GetMark()->nContent.GetIndex() != nSttContent ) + { + pCurrentPam->SetMark(); + pCurrentPam->GetMark()->nContent = nSttContent; + } + + if( pRedlSaveData ) + SetSaveData( *pDoc, *pRedlSaveData ); +} + +void SwUndoOverwrite::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM *const pCurrentPam = & rContext.GetRepeatPaM(); + if( aInsStr.isEmpty() || pCurrentPam->HasMark() ) + return; + + SwDoc & rDoc = rContext.GetDoc(); + + { + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().Overwrite(*pCurrentPam, OUString(aInsStr[0])); + } + for( sal_Int32 n = 1; n < aInsStr.getLength(); ++n ) + rDoc.getIDocumentContentOperations().Overwrite( *pCurrentPam, OUString(aInsStr[n]) ); +} + +void SwUndoOverwrite::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pCurrentPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pCurrentPam->DeleteMark(); + pCurrentPam->GetPoint()->nNode = nSttNode; + SwTextNode* pTextNd = pCurrentPam->GetNode().GetTextNode(); + assert(pTextNd); + SwIndex& rIdx = pCurrentPam->GetPoint()->nContent; + + if( pRedlSaveData ) + { + rIdx.Assign( pTextNd, nSttContent ); + pCurrentPam->SetMark(); + pCurrentPam->GetMark()->nContent += aDelStr.getLength(); + pDoc->getIDocumentRedlineAccess().DeleteRedline( *pCurrentPam, false, RedlineType::Any ); + pCurrentPam->DeleteMark(); + } + rIdx.Assign( pTextNd, !aDelStr.isEmpty() ? nSttContent+1 : nSttContent ); + + bool bOldExpFlg = pTextNd->IsIgnoreDontExpand(); + pTextNd->SetIgnoreDontExpand( true ); + + for( sal_Int32 n = 0; n < aInsStr.getLength(); n++ ) + { + // do it individually, to keep the attributes! + OUString const ins( + pTextNd->InsertText( OUString(aInsStr[n]), rIdx, + SwInsertFlags::EMPTYEXPAND) ); + assert(ins.getLength() == 1); // cannot fail + (void) ins; + if( n < aDelStr.getLength() ) + { + rIdx -= 2; + pTextNd->EraseText( rIdx, 1 ); + rIdx += n+1 < aDelStr.getLength() ? 2 : 1; + } + } + pTextNd->SetIgnoreDontExpand( bOldExpFlg ); + + // get back old start position from UndoNodes array + if( m_pHistory ) + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + if( pCurrentPam->GetMark()->nContent.GetIndex() != nSttContent ) + { + pCurrentPam->SetMark(); + pCurrentPam->GetMark()->nContent = nSttContent; + } +} + +SwRewriter SwUndoOverwrite::GetRewriter() const +{ + SwRewriter aResult; + + OUString aString = SwResId(STR_START_QUOTE) + + ShortenString(aInsStr, nUndoStringLength, SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + + aResult.AddRule(UndoArg1, aString); + + return aResult; +} + +struct UndoTransliterate_Data +{ + OUString sText; + std::unique_ptr<SwHistory> pHistory; + std::unique_ptr<Sequence< sal_Int32 >> pOffsets; + sal_uLong nNdIdx; + sal_Int32 nStart, nLen; + + UndoTransliterate_Data( sal_uLong nNd, sal_Int32 nStt, sal_Int32 nStrLen, const OUString& rText ) + : sText( rText ), + nNdIdx( nNd ), nStart( nStt ), nLen( nStrLen ) + {} + + void SetChangeAtNode( SwDoc& rDoc ); +}; + +SwUndoTransliterate::SwUndoTransliterate( + const SwPaM& rPam, + const utl::TransliterationWrapper& rTrans ) + : SwUndo( SwUndoId::TRANSLITERATE, rPam.GetDoc() ), SwUndRng( rPam ), nType( rTrans.getType() ) +{ +} + +SwUndoTransliterate::~SwUndoTransliterate() +{ +} + +void SwUndoTransliterate::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // since the changes were added to the vector from the end of the string/node towards + // the start, we need to revert them from the start towards the end now to keep the + // offset information of the undo data in sync with the changing text. + // Thus we need to iterate from the end of the vector to the start + for (sal_Int32 i = aChanges.size() - 1; i >= 0; --i) + aChanges[i]->SetChangeAtNode( rDoc ); + + AddUndoRedoPaM(rContext, true); +} + +void SwUndoTransliterate::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + DoTransliterate(rContext.GetDoc(), rPam); +} + +void SwUndoTransliterate::RepeatImpl(::sw::RepeatContext & rContext) +{ + DoTransliterate(rContext.GetDoc(), rContext.GetRepeatPaM()); +} + +void SwUndoTransliterate::DoTransliterate(SwDoc & rDoc, SwPaM const & rPam) +{ + utl::TransliterationWrapper aTrans( ::comphelper::getProcessComponentContext(), nType ); + rDoc.getIDocumentContentOperations().TransliterateText( rPam, aTrans ); +} + +void SwUndoTransliterate::AddChanges( SwTextNode& rTNd, + sal_Int32 nStart, sal_Int32 nLen, + uno::Sequence <sal_Int32> const & rOffsets ) +{ + long nOffsLen = rOffsets.getLength(); + UndoTransliterate_Data* pNew = new UndoTransliterate_Data( + rTNd.GetIndex(), nStart, static_cast<sal_Int32>(nOffsLen), + rTNd.GetText().copy(nStart, nLen)); + + aChanges.push_back( std::unique_ptr<UndoTransliterate_Data>(pNew) ); + + const sal_Int32* pOffsets = rOffsets.getConstArray(); + // where did we need less memory ? + const sal_Int32* p = pOffsets; + for( long n = 0; n < nOffsLen; ++n, ++p ) + if( *p != ( nStart + n )) + { + // create the Offset array + pNew->pOffsets.reset( new Sequence <sal_Int32> ( nLen ) ); + sal_Int32* pIdx = pNew->pOffsets->getArray(); + p = pOffsets; + long nMyOff, nNewVal = nStart; + for( n = 0, nMyOff = nStart; n < nOffsLen; ++p, ++n, ++nMyOff ) + { + if( *p < nMyOff ) + { + // something is deleted + nMyOff = *p; + *(pIdx-1) = nNewVal++; + } + else if( *p > nMyOff ) + { + for( ; *p > nMyOff; ++nMyOff ) + *pIdx++ = nNewVal; + --nMyOff; + --n; + --p; + } + else + *pIdx++ = nNewVal++; + } + + // and then we need to save the attributes/bookmarks + // but this data must moved every time to the last in the chain! + for (size_t i = 0; i + 1 < aChanges.size(); ++i) // check all changes but not the current one + { + UndoTransliterate_Data* pD = aChanges[i].get(); + if( pD->nNdIdx == pNew->nNdIdx && pD->pHistory ) + { + // same node and have a history? + pNew->pHistory = std::move(pD->pHistory); + break; // more can't exist + } + } + + if( !pNew->pHistory ) + { + pNew->pHistory.reset( new SwHistory ); + SwRegHistory aRHst( rTNd, pNew->pHistory.get() ); + pNew->pHistory->CopyAttr( rTNd.GetpSwpHints(), + pNew->nNdIdx, 0, rTNd.GetText().getLength(), false ); + } + break; + } +} + +void UndoTransliterate_Data::SetChangeAtNode( SwDoc& rDoc ) +{ + SwTextNode* pTNd = rDoc.GetNodes()[ nNdIdx ]->GetTextNode(); + if( pTNd ) + { + Sequence <sal_Int32> aOffsets( pOffsets ? pOffsets->getLength() : nLen ); + if( pOffsets ) + aOffsets = *pOffsets; + else + { + sal_Int32* p = aOffsets.getArray(); + for( sal_Int32 n = 0; n < nLen; ++n, ++p ) + *p = n + nStart; + } + pTNd->ReplaceTextOnly( nStart, nLen, sText, aOffsets ); + + if( pHistory ) + { + if( pTNd->GetpSwpHints() ) + pTNd->ClearSwpHintsArr( false ); + pHistory->TmpRollback( &rDoc, 0, false ); + pHistory->SetTmpEnd( pHistory->Count() ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unredln.cxx b/sw/source/core/undo/unredln.cxx new file mode 100644 index 000000000..4b98043a6 --- /dev/null +++ b/sw/source/core/undo/unredln.cxx @@ -0,0 +1,527 @@ +/* -*- 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 <UndoRedline.hxx> +#include <hintids.hxx> +#include <unotools/charclass.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <UndoCore.hxx> +#include <UndoDelete.hxx> +#include <strings.hrc> +#include <redline.hxx> +#include <docary.hxx> +#include <sortopt.hxx> +#include <docedt.hxx> + +SwUndoRedline::SwUndoRedline( SwUndoId nUsrId, const SwPaM& rRange ) + : SwUndo( SwUndoId::REDLINE, rRange.GetDoc() ), SwUndRng( rRange ), + mnUserId( nUsrId ), + mbHiddenRedlines( false ) +{ + // consider Redline + SwDoc& rDoc = *rRange.GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + switch( mnUserId ) + { + case SwUndoId::DELETE: + case SwUndoId::REPLACE: + mpRedlData.reset( new SwRedlineData( RedlineType::Delete, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + break; + default: + ; + } + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + sal_uLong nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + + mpRedlSaveData.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( rRange, *mpRedlSaveData, false, SwUndoId::REJECT_REDLINE != mnUserId )) + { + mpRedlSaveData.reset(); + } + else + { + mbHiddenRedlines = HasHiddenRedlines( *mpRedlSaveData ); + if( mbHiddenRedlines ) // then the NodeIndices of SwUndRng need to be corrected + { + nEndExtra -= rDoc.GetNodes().GetEndOfExtras().GetIndex(); + m_nSttNode -= nEndExtra; + m_nEndNode -= nEndExtra; + } + } +} + +SwUndoRedline::~SwUndoRedline() +{ + mpRedlData.reset(); + mpRedlSaveData.reset(); +} + +sal_uInt16 SwUndoRedline::GetRedlSaveCount() const +{ + return mpRedlSaveData ? mpRedlSaveData->size() : 0; +} + +void SwUndoRedline::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwPaM& rPam(AddUndoRedoPaM(rContext)); + + UndoRedlineImpl(rDoc, rPam); + + if( mpRedlSaveData ) + { + sal_uLong nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + SetSaveData(rDoc, *mpRedlSaveData); + if( mbHiddenRedlines ) + { + mpRedlSaveData->clear(); + + nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex() - nEndExtra; + m_nSttNode += nEndExtra; + m_nEndNode += nEndExtra; + } + SetPaM(rPam, true); + } + + // update frames after calling SetSaveData + if (dynamic_cast<SwUndoRedlineDelete*>(this)) + { + sw::UpdateFramesForRemoveDeleteRedline(rDoc, rPam); + } + else if (dynamic_cast<SwUndoAcceptRedline*>(this) + || dynamic_cast<SwUndoRejectRedline*>(this)) + { // (can't check here if there's a delete redline being accepted) + sw::UpdateFramesForAddDeleteRedline(rDoc, rPam); + } +} + +void SwUndoRedline::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + if( mpRedlSaveData && mbHiddenRedlines ) + { + sal_uLong nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + FillSaveData(rPam, *mpRedlSaveData, false, SwUndoId::REJECT_REDLINE != mnUserId ); + + nEndExtra -= rDoc.GetNodes().GetEndOfExtras().GetIndex(); + m_nSttNode -= nEndExtra; + m_nEndNode -= nEndExtra; + } + + RedoRedlineImpl(rDoc, rPam); + + SetPaM(rPam, true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwUndoRedline::UndoRedlineImpl(SwDoc &, SwPaM &) +{ +} + +// default: remove redlines +void SwUndoRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); +} + +SwUndoRedlineDelete::SwUndoRedlineDelete( const SwPaM& rRange, SwUndoId nUsrId ) + : SwUndoRedline( nUsrId != SwUndoId::EMPTY ? nUsrId : SwUndoId::DELETE, rRange ), + m_bCanGroup( false ), m_bIsDelim( false ), m_bIsBackspace( false ) +{ + const SwTextNode* pTNd; + SetRedlineText(rRange.GetText()); + if( SwUndoId::DELETE == mnUserId && + m_nSttNode == m_nEndNode && m_nSttContent + 1 == m_nEndContent && + nullptr != (pTNd = rRange.GetNode().GetTextNode()) ) + { + sal_Unicode const cCh = pTNd->GetText()[m_nSttContent]; + if( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh ) + { + m_bCanGroup = true; + m_bIsDelim = !GetAppCharClass().isLetterNumeric( pTNd->GetText(), + m_nSttContent ); + m_bIsBackspace = m_nSttContent == rRange.GetPoint()->nContent.GetIndex(); + } + } + + m_bCacheComment = false; +} + +// bit of a hack, replace everything... +SwRewriter SwUndoRedlineDelete::GetRewriter() const +{ + SwRewriter aResult; + OUString aStr = DenoteSpecialCharacters(m_sRedlineText); + aStr = ShortenString(aStr, nUndoStringLength, SwResId(STR_LDOTS)); + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aStr); + OUString ret = aRewriter.Apply(SwResId(STR_UNDO_REDLINE_DELETE)); + aResult.AddRule(UndoArg1, ret); + return aResult; +} + +void SwUndoRedlineDelete::SetRedlineText(const OUString & rText) +{ + m_sRedlineText = rText; +} + +void SwUndoRedlineDelete::UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); +} + +void SwUndoRedlineDelete::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + if (rPam.GetPoint() != rPam.GetMark()) + { + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline(*mpRedlData, rPam), false ); + } + sw::UpdateFramesForAddDeleteRedline(rDoc, rPam); +} + +bool SwUndoRedlineDelete::CanGrouping( const SwUndoRedlineDelete& rNext ) +{ + bool bRet = false; + if( SwUndoId::DELETE == mnUserId && mnUserId == rNext.mnUserId && + m_bCanGroup == rNext.m_bCanGroup && + m_bIsDelim == rNext.m_bIsDelim && + m_bIsBackspace == rNext.m_bIsBackspace && + m_nSttNode == m_nEndNode && + rNext.m_nSttNode == m_nSttNode && + rNext.m_nEndNode == m_nEndNode ) + { + int bIsEnd = 0; + if( rNext.m_nSttContent == m_nEndContent ) + bIsEnd = 1; + else if( rNext.m_nEndContent == m_nSttContent ) + bIsEnd = -1; + + if( bIsEnd && + (( !mpRedlSaveData && !rNext.mpRedlSaveData ) || + ( mpRedlSaveData && rNext.mpRedlSaveData && + SwUndo::CanRedlineGroup( *mpRedlSaveData, + *rNext.mpRedlSaveData, 1 != bIsEnd ) + ))) + { + if( 1 == bIsEnd ) + m_nEndContent = rNext.m_nEndContent; + else + m_nSttContent = rNext.m_nSttContent; + bRet = true; + } + } + return bRet; +} + +SwUndoRedlineSort::SwUndoRedlineSort( const SwPaM& rRange, + const SwSortOptions& rOpt ) + : SwUndoRedline( SwUndoId::SORT_TXT, rRange ), + m_pOpt( new SwSortOptions( rOpt ) ), + m_nSaveEndNode( m_nEndNode ), m_nSaveEndContent( m_nEndContent ) +{ +} + +SwUndoRedlineSort::~SwUndoRedlineSort() +{ +} + +void SwUndoRedlineSort::UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + // rPam contains the sorted range + // aSaveRange contains copied (i.e. original) range + + SwPosition *const pStart = rPam.Start(); + SwPosition *const pEnd = rPam.End(); + + SwNodeIndex aPrevIdx( pStart->nNode, -1 ); + sal_uLong nOffsetTemp = pEnd->nNode.GetIndex() - pStart->nNode.GetIndex(); + + if( !( RedlineFlags::ShowDelete & rDoc.getIDocumentRedlineAccess().GetRedlineFlags()) ) + { + // Search both Redline objects and make them visible to make the nodes + // consistent again. The 'delete' one is hidden, thus search for the + // 'insert' Redline object. The former is located directly after the latter. + SwRedlineTable::size_type nFnd = rDoc.getIDocumentRedlineAccess().GetRedlinePos( + *rDoc.GetNodes()[ m_nSttNode + 1 ], + RedlineType::Insert ); + OSL_ENSURE( SwRedlineTable::npos != nFnd && nFnd+1 < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(), + "could not find an Insert object" ); + ++nFnd; + rDoc.getIDocumentRedlineAccess().GetRedlineTable()[nFnd]->Show(1, nFnd); + } + + { + SwPaM aTmp( *rPam.GetMark() ); + aTmp.GetMark()->nContent = 0; + aTmp.SetMark(); + aTmp.GetPoint()->nNode = m_nSaveEndNode; + aTmp.GetPoint()->nContent.Assign( aTmp.GetContentNode(), m_nSaveEndContent ); + rDoc.getIDocumentRedlineAccess().DeleteRedline( aTmp, true, RedlineType::Any ); + } + + rDoc.getIDocumentContentOperations().DelFullPara(rPam); + + SwPaM *const pPam = & rPam; + pPam->DeleteMark(); + pPam->GetPoint()->nNode.Assign( aPrevIdx.GetNode(), +1 ); + SwContentNode* pCNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign(pCNd, 0 ); + pPam->SetMark(); + + pPam->GetPoint()->nNode += nOffsetTemp; + pCNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + + SetValues( *pPam ); + + SetPaM(rPam); +} + +void SwUndoRedlineSort::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + SwPaM* pPam = &rPam; + SwPosition* pStart = pPam->Start(); + SwPosition* pEnd = pPam->End(); + + SwNodeIndex aPrevIdx( pStart->nNode, -1 ); + sal_uLong nOffsetTemp = pEnd->nNode.GetIndex() - pStart->nNode.GetIndex(); + const sal_Int32 nCntStt = pStart->nContent.GetIndex(); + + rDoc.SortText(rPam, *m_pOpt); + + pPam->DeleteMark(); + pPam->GetPoint()->nNode.Assign( aPrevIdx.GetNode(), +1 ); + SwContentNode* pCNd = pPam->GetContentNode(); + sal_Int32 nLen = pCNd->Len(); + if( nLen > nCntStt ) + nLen = nCntStt; + pPam->GetPoint()->nContent.Assign(pCNd, nLen ); + pPam->SetMark(); + + pPam->GetPoint()->nNode += nOffsetTemp; + pCNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + + SetValues( rPam ); + + SetPaM( rPam ); + rPam.GetPoint()->nNode = m_nSaveEndNode; + rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), m_nSaveEndContent ); +} + +void SwUndoRedlineSort::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().SortText( rContext.GetRepeatPaM(), *m_pOpt ); +} + +void SwUndoRedlineSort::SetSaveRange( const SwPaM& rRange ) +{ + const SwPosition& rPos = *rRange.End(); + m_nSaveEndNode = rPos.nNode.GetIndex(); + m_nSaveEndContent = rPos.nContent.GetIndex(); +} + +SwUndoAcceptRedline::SwUndoAcceptRedline( const SwPaM& rRange ) + : SwUndoRedline( SwUndoId::ACCEPT_REDLINE, rRange ) +{ +} + +void SwUndoAcceptRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().AcceptRedline(rPam, false); +} + +void SwUndoAcceptRedline::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().getIDocumentRedlineAccess().AcceptRedline(rContext.GetRepeatPaM(), true); +} + +SwUndoRejectRedline::SwUndoRejectRedline( const SwPaM& rRange ) + : SwUndoRedline( SwUndoId::REJECT_REDLINE, rRange ) +{ +} + +void SwUndoRejectRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().RejectRedline(rPam, false); +} + +void SwUndoRejectRedline::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().getIDocumentRedlineAccess().RejectRedline(rContext.GetRepeatPaM(), true); +} + +SwUndoCompDoc::SwUndoCompDoc( const SwPaM& rRg, bool bIns ) + : SwUndo( SwUndoId::COMPAREDOC, rRg.GetDoc() ), SwUndRng( rRg ), + m_bInsert( bIns ) +{ + SwDoc* pDoc = rRg.GetDoc(); + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineType eTyp = m_bInsert ? RedlineType::Insert : RedlineType::Delete; + m_pRedlineData.reset( new SwRedlineData( eTyp, pDoc->getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + } +} + +SwUndoCompDoc::SwUndoCompDoc( const SwRangeRedline& rRedl ) + : SwUndo( SwUndoId::COMPAREDOC, rRedl.GetDoc() ), SwUndRng( rRedl ), + // for MergeDoc the corresponding inverse is needed + m_bInsert( RedlineType::Delete == rRedl.GetType() ) +{ + SwDoc* pDoc = rRedl.GetDoc(); + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( rRedl.GetRedlineData() ) ); + SetRedlineFlags( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + m_pRedlineSaveDatas.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( rRedl, *m_pRedlineSaveDatas, false )) + { + m_pRedlineSaveDatas.reset(); + } +} + +SwUndoCompDoc::~SwUndoCompDoc() +{ + m_pRedlineData.reset(); + m_pUndoDelete.reset(); + m_pUndoDelete2.reset(); + m_pRedlineSaveDatas.reset(); +} + +void SwUndoCompDoc::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwPaM& rPam(AddUndoRedoPaM(rContext)); + + if( !m_bInsert ) + { + // delete Redlines + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On); + + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + // per definition Point is end (in SwUndRng!) + SwContentNode* pCSttNd = rPam.GetContentNode(false); + SwContentNode* pCEndNd = rPam.GetContentNode(); + + // if start- and end-content is zero, then the doc-compare moves + // complete nodes into the current doc. And then the selection + // must be from end to start, so the delete join into the right + // direction. + if( !m_nSttContent && !m_nEndContent ) + rPam.Exchange(); + + bool bJoinText, bJoinPrev; + sw_GetJoinFlags(rPam, bJoinText, bJoinPrev); + + m_pUndoDelete.reset( new SwUndoDelete(rPam, false) ); + + if( bJoinText ) + sw_JoinText(rPam, bJoinPrev); + + if( pCSttNd && !pCEndNd) + { + // #112139# Do not step behind the end of content. + SwNode & rTmp = rPam.GetNode(); + SwNode * pEnd = rDoc.GetNodes().DocumentSectionEndNode(&rTmp); + + if (&rTmp != pEnd) + { + rPam.SetMark(); + ++rPam.GetPoint()->nNode; + rPam.GetBound().nContent.Assign( nullptr, 0 ); + rPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + m_pUndoDelete2.reset( new SwUndoDelete(rPam, true) ); + } + } + rPam.DeleteMark(); + } + else + { + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + + if( m_pRedlineSaveDatas ) + SetSaveData(rDoc, *m_pRedlineSaveDatas); + } + SetPaM(rPam, true); + } +} + +void SwUndoCompDoc::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + + if( m_bInsert ) + { + SwPaM& rPam(AddUndoRedoPaM(rContext)); + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + SwRangeRedline* pTmp = new SwRangeRedline(*m_pRedlineData, rPam); + rDoc.getIDocumentRedlineAccess().GetRedlineTable().Insert( pTmp ); + pTmp->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + rDoc.getIDocumentRedlineAccess().SplitRedline(rPam); + } + SetPaM(rPam, true); + } + else + { + if( m_pUndoDelete2 ) + { + m_pUndoDelete2->UndoImpl(rContext); + m_pUndoDelete2.reset(); + } + m_pUndoDelete->UndoImpl(rContext); + m_pUndoDelete.reset(); + + // note: don't call SetPaM before executing Undo of members + SwPaM& rPam(AddUndoRedoPaM(rContext)); + + SwRangeRedline* pTmp = new SwRangeRedline(*m_pRedlineData, rPam); + rDoc.getIDocumentRedlineAccess().GetRedlineTable().Insert( pTmp ); + pTmp->InvalidateRange(SwRangeRedline::Invalidation::Add); + + SetPaM(rPam, true); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unsect.cxx b/sw/source/core/undo/unsect.cxx new file mode 100644 index 000000000..ae2ba60a3 --- /dev/null +++ b/sw/source/core/undo/unsect.cxx @@ -0,0 +1,601 @@ +/* -*- 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 <memory> +#include <UndoSection.hxx> + +#include <osl/diagnose.h> +#include <comphelper/scopeguard.hxx> +#include <sfx2/linkmgr.hxx> +#include <fmtcntnt.hxx> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <poolfmt.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <section.hxx> +#include <rolbck.hxx> +#include <redline.hxx> +#include <doctxm.hxx> +#include <ftnidx.hxx> +#include <rootfrm.hxx> +#include <editsh.hxx> +/// OD 04.10.2002 #102894# +/// class Calc needed for calculation of the hidden condition of a section. +#include <calc.hxx> + +static std::unique_ptr<SfxItemSet> lcl_GetAttrSet( const SwSection& rSect ) +{ + // save attributes of the format (columns, color, ...) + // Content and Protect items are not interesting since they are already + // stored in Section, thus delete them. + std::unique_ptr<SfxItemSet> pAttr; + if( rSect.GetFormat() ) + { + sal_uInt16 nCnt = 1; + if( rSect.IsProtect() ) + ++nCnt; + + if( nCnt < rSect.GetFormat()->GetAttrSet().Count() ) + { + pAttr.reset(new SfxItemSet( rSect.GetFormat()->GetAttrSet() )); + pAttr->ClearItem( RES_PROTECT ); + pAttr->ClearItem( RES_CNTNT ); + if( !pAttr->Count() ) + { + pAttr.reset(); + } + } + } + return pAttr; +} + +SwUndoInsSection::SwUndoInsSection( + SwPaM const& rPam, SwSectionData const& rNewData, + SfxItemSet const*const pSet, + std::pair<SwTOXBase const*, sw::RedlineMode> const*const pTOXBase) + : SwUndo( SwUndoId::INSSECTION, rPam.GetDoc() ), SwUndRng( rPam ) + , m_pSectionData(new SwSectionData(rNewData)) + , m_pTOXBase( pTOXBase + ? std::make_unique<std::pair<SwTOXBase *, sw::RedlineMode>>( + new SwTOXBase(*pTOXBase->first), pTOXBase->second) + : nullptr ) + , m_pAttrSet( (pSet && pSet->Count()) ? new SfxItemSet( *pSet ) : nullptr ) + , m_nSectionNodePos(0) + , m_bSplitAtStart(false) + , m_bSplitAtEnd(false) + , m_bUpdateFootnote(false) +{ + SwDoc& rDoc = *rPam.GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlData.reset(new SwRedlineData( RedlineType::Insert, + rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() )); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + m_pRedlineSaveData.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( rPam, *m_pRedlineSaveData, false )) + m_pRedlineSaveData.reset(); + + if( !rPam.HasMark() ) + { + const SwContentNode* pCNd = rPam.GetPoint()->nNode.GetNode().GetContentNode(); + if( pCNd && pCNd->HasSwAttrSet() && ( + !rPam.GetPoint()->nContent.GetIndex() || + rPam.GetPoint()->nContent.GetIndex() == pCNd->Len() )) + { + SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange ); + aBrkSet.Put( *pCNd->GetpSwAttrSet() ); + if( aBrkSet.Count() ) + { + m_pHistory.reset( new SwHistory ); + m_pHistory->CopyFormatAttr( aBrkSet, pCNd->GetIndex() ); + } + } + } +} + +SwUndoInsSection::~SwUndoInsSection() +{ +} + +void SwUndoInsSection::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + RemoveIdxFromSection( rDoc, m_nSectionNodePos ); + + SwSectionNode *const pNd = + rDoc.GetNodes()[ m_nSectionNodePos ]->GetSectionNode(); + OSL_ENSURE( pNd, "where is my SectionNode?" ); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pNd, true, RedlineType::Any ); + + // no selection? + SwNodeIndex aIdx( *pNd ); + if( ( !m_nEndNode && COMPLETE_STRING == m_nEndContent ) || + ( m_nSttNode == m_nEndNode && m_nSttContent == m_nEndContent )) + // delete simply all nodes + rDoc.GetNodes().Delete( aIdx, pNd->EndOfSectionIndex() - + aIdx.GetIndex() ); + else + // just delete format, rest happens automatically + rDoc.DelSectionFormat( pNd->GetSection().GetFormat() ); + + // do we need to consolidate? + if (m_bSplitAtStart) + { + Join( rDoc, m_nSttNode ); + } + + if (m_bSplitAtEnd) + { + Join( rDoc, m_nEndNode ); + } + + if (m_pHistory) + { + m_pHistory->TmpRollback( &rDoc, 0, false ); + } + + if (m_bUpdateFootnote) + { + rDoc.GetFootnoteIdxs().UpdateFootnote( aIdx ); + } + + AddUndoRedoPaM(rContext); + + if (m_pRedlineSaveData) + SetSaveData( rDoc, *m_pRedlineSaveData ); +} + +void SwUndoInsSection::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + + const SwTOXBaseSection* pUpdateTOX = nullptr; + if (m_pTOXBase) + { + SwRootFrame const* pLayout(nullptr); + SwRootFrame * pLayoutToReset(nullptr); + comphelper::ScopeGuard g([&]() { + if (pLayoutToReset) + { + pLayoutToReset->SetHideRedlines(m_pTOXBase->second == sw::RedlineMode::Shown); + } + }); + o3tl::sorted_vector<SwRootFrame *> layouts(rDoc.GetAllLayouts()); + for (SwRootFrame const*const p : layouts) + { + if ((m_pTOXBase->second == sw::RedlineMode::Hidden) == p->IsHideRedlines()) + { + pLayout = p; + break; + } + } + if (!pLayout) + { + assert(!layouts.empty()); // must have one layout + pLayoutToReset = *layouts.begin(); + pLayoutToReset->SetHideRedlines(m_pTOXBase->second == sw::RedlineMode::Hidden); + pLayout = pLayoutToReset; + } + pUpdateTOX = rDoc.InsertTableOf( *rPam.GetPoint(), + // don't expand: will be done by SwUndoUpdateIndex::RedoImpl() + *m_pTOXBase->first, m_pAttrSet.get(), false, pLayout); + } + else + { + rDoc.InsertSwSection(rPam, *m_pSectionData, nullptr, m_pAttrSet.get()); + } + + if (m_pHistory) + { + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + SwSectionNode *const pSectNd = + rDoc.GetNodes()[ m_nSectionNodePos ]->GetSectionNode(); + if (m_pRedlData && + IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags())) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + + SwPaM aPam( *pSectNd->EndOfSectionNode(), *pSectNd, 1 ); + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, aPam ), true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aPam( *pSectNd->EndOfSectionNode(), *pSectNd, 1 ); + rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + if( pUpdateTOX ) + { + // initiate formatting + SwEditShell* pESh = rDoc.GetEditShell(); + if( pESh ) + pESh->CalcLayout(); + + // insert page numbers + const_cast<SwTOXBaseSection*>(pUpdateTOX)->UpdatePageNum(); + } +} + +void SwUndoInsSection::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if (m_pTOXBase) + { + rDoc.InsertTableOf(*rContext.GetRepeatPaM().GetPoint(), + *m_pTOXBase->first, m_pAttrSet.get(), true, + rDoc.getIDocumentLayoutAccess().GetCurrentLayout()); // TODO add shell to RepeatContext? + } + else + { + rDoc.InsertSwSection(rContext.GetRepeatPaM(), + *m_pSectionData, nullptr, m_pAttrSet.get()); + } +} + +void SwUndoInsSection::Join( SwDoc& rDoc, sal_uLong nNode ) +{ + SwNodeIndex aIdx( rDoc.GetNodes(), nNode ); + SwTextNode* pTextNd = aIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "Where is my TextNode?" ); + + { + RemoveIdxRel( + nNode + 1, + SwPosition( aIdx, SwIndex( pTextNd, pTextNd->GetText().getLength() ) ) ); + } + pTextNd->JoinNext(); + + if (m_pHistory) + { + SwIndex aCntIdx( pTextNd, 0 ); + pTextNd->RstTextAttr( aCntIdx, pTextNd->Len(), 0, nullptr, true ); + } +} + +void +SwUndoInsSection::SaveSplitNode(SwTextNode *const pTextNd, bool const bAtStart) +{ + if( pTextNd->GetpSwpHints() ) + { + if (!m_pHistory) + { + m_pHistory.reset( new SwHistory ); + } + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), pTextNd->GetIndex(), 0, + pTextNd->GetText().getLength(), false ); + } + + if (bAtStart) + { + m_bSplitAtStart = true; + } + else + { + m_bSplitAtEnd = true; + } +} + +class SwUndoDelSection + : public SwUndo +{ +private: + std::unique_ptr<SwSectionData> const m_pSectionData; /// section not TOX + std::unique_ptr<SwTOXBase> const m_pTOXBase; /// set iff section is TOX + std::unique_ptr<SfxItemSet> const m_pAttrSet; + std::shared_ptr< ::sfx2::MetadatableUndo > const m_pMetadataUndo; + sal_uLong const m_nStartNode; + sal_uLong const m_nEndNode; + +public: + SwUndoDelSection( + SwSectionFormat const&, SwSection const&, SwNodeIndex const*const); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +std::unique_ptr<SwUndo> MakeUndoDelSection(SwSectionFormat const& rFormat) +{ + return std::make_unique<SwUndoDelSection>(rFormat, *rFormat.GetSection(), + rFormat.GetContent().GetContentIdx()); +} + +SwUndoDelSection::SwUndoDelSection( + SwSectionFormat const& rSectionFormat, SwSection const& rSection, + SwNodeIndex const*const pIndex) + : SwUndo( SwUndoId::DELSECTION, rSectionFormat.GetDoc() ) + , m_pSectionData( new SwSectionData(rSection) ) + , m_pTOXBase( dynamic_cast<const SwTOXBaseSection*>( &rSection) != nullptr + ? new SwTOXBase(static_cast<SwTOXBaseSection const&>(rSection)) + : nullptr ) + , m_pAttrSet( ::lcl_GetAttrSet(rSection) ) + , m_pMetadataUndo( rSectionFormat.CreateUndo() ) + , m_nStartNode( pIndex->GetIndex() ) + , m_nEndNode( pIndex->GetNode().EndOfSectionIndex() ) +{ +} + +void SwUndoDelSection::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if (m_pTOXBase) + { + // sw_redlinehide: this should work as-is; there will be another undo for the update + rDoc.InsertTableOf(m_nStartNode, m_nEndNode-2, *m_pTOXBase, + m_pAttrSet.get()); + } + else + { + SwNodeIndex aStt( rDoc.GetNodes(), m_nStartNode ); + SwNodeIndex aEnd( rDoc.GetNodes(), m_nEndNode-2 ); + SwSectionFormat* pFormat = rDoc.MakeSectionFormat(); + if (m_pAttrSet) + { + pFormat->SetFormatAttr( *m_pAttrSet ); + } + + /// OD 04.10.2002 #102894# + /// remember inserted section node for further calculations + SwSectionNode* pInsertedSectNd = rDoc.GetNodes().InsertTextSection( + aStt, *pFormat, *m_pSectionData, nullptr, & aEnd); + + if( SfxItemState::SET == pFormat->GetItemState( RES_FTN_AT_TXTEND ) || + SfxItemState::SET == pFormat->GetItemState( RES_END_AT_TXTEND )) + { + rDoc.GetFootnoteIdxs().UpdateFootnote( aStt ); + } + + /// OD 04.10.2002 #102894# + /// consider that section is hidden by condition. + /// If section is hidden by condition, + /// recalculate condition and update hidden condition flag. + /// Recalculation is necessary, because fields, on which the hide + /// condition depends, can be changed - fields changes aren't undoable. + /// NOTE: setting hidden condition flag also creates/deletes corresponding + /// frames, if the hidden condition flag changes. + SwSection& aInsertedSect = pInsertedSectNd->GetSection(); + if ( aInsertedSect.IsHidden() && + !aInsertedSect.GetCondition().isEmpty() ) + { + SwCalc aCalc( rDoc ); + rDoc.getIDocumentFieldsAccess().FieldsToCalc(aCalc, pInsertedSectNd->GetIndex(), USHRT_MAX); + bool bRecalcCondHidden = + aCalc.Calculate( aInsertedSect.GetCondition() ).GetBool(); + aInsertedSect.SetCondHidden( bRecalcCondHidden ); + } + + pFormat->RestoreMetadata(m_pMetadataUndo); + } +} + +void SwUndoDelSection::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwSectionNode *const pNd = + rDoc.GetNodes()[ m_nStartNode ]->GetSectionNode(); + OSL_ENSURE( pNd, "Where is my SectionNode?" ); + // just delete format, rest happens automatically + rDoc.DelSectionFormat( pNd->GetSection().GetFormat() ); +} + +namespace { + +class SwUndoUpdateSection + : public SwUndo +{ +private: + std::unique_ptr<SwSectionData> m_pSectionData; + std::unique_ptr<SfxItemSet> m_pAttrSet; + sal_uLong const m_nStartNode; + bool const m_bOnlyAttrChanged; + +public: + SwUndoUpdateSection( + SwSection const&, SwNodeIndex const*const, bool const bOnlyAttr); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +} + +std::unique_ptr<SwUndo> +MakeUndoUpdateSection(SwSectionFormat const& rFormat, bool const bOnlyAttr) +{ + return std::make_unique<SwUndoUpdateSection>(*rFormat.GetSection(), + rFormat.GetContent().GetContentIdx(), bOnlyAttr); +} + +SwUndoUpdateSection::SwUndoUpdateSection( + SwSection const& rSection, SwNodeIndex const*const pIndex, + bool const bOnlyAttr) + : SwUndo( SwUndoId::CHGSECTION, pIndex->GetNode().GetDoc() ) + , m_pSectionData( new SwSectionData(rSection) ) + , m_pAttrSet( ::lcl_GetAttrSet(rSection) ) + , m_nStartNode( pIndex->GetIndex() ) + , m_bOnlyAttrChanged( bOnlyAttr ) +{ +} + +void SwUndoUpdateSection::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwSectionNode *const pSectNd = + rDoc.GetNodes()[ m_nStartNode ]->GetSectionNode(); + OSL_ENSURE( pSectNd, "Where is my SectionNode?" ); + + SwSection& rNdSect = pSectNd->GetSection(); + SwFormat* pFormat = rNdSect.GetFormat(); + + std::unique_ptr<SfxItemSet> pCur = ::lcl_GetAttrSet( rNdSect ); + if (m_pAttrSet) + { + // The Content and Protect items must persist + const SfxPoolItem* pItem; + m_pAttrSet->Put( pFormat->GetFormatAttr( RES_CNTNT )); + if( SfxItemState::SET == pFormat->GetItemState( RES_PROTECT, true, &pItem )) + { + m_pAttrSet->Put( *pItem ); + } + pFormat->DelDiffs( *m_pAttrSet ); + m_pAttrSet->ClearItem( RES_CNTNT ); + pFormat->SetFormatAttr( *m_pAttrSet ); + } + else + { + // than the old ones need to be deleted + pFormat->ResetFormatAttr( RES_FRMATR_BEGIN, RES_BREAK ); + pFormat->ResetFormatAttr( RES_HEADER, RES_OPAQUE ); + pFormat->ResetFormatAttr( RES_SURROUND, RES_FRMATR_END-1 ); + } + m_pAttrSet = std::move(pCur); + + if (!m_bOnlyAttrChanged) + { + const bool bUpdate = + (!rNdSect.IsLinkType() && m_pSectionData->IsLinkType()) + || ( !m_pSectionData->GetLinkFileName().isEmpty() + && (m_pSectionData->GetLinkFileName() != + rNdSect.GetLinkFileName())); + + // swap stored section data with live section data + SwSectionData *const pOld( new SwSectionData(rNdSect) ); + rNdSect.SetSectionData(*m_pSectionData); + m_pSectionData.reset(pOld); + + if( bUpdate ) + rNdSect.CreateLink( LinkCreateType::Update ); + else if( SectionType::Content == rNdSect.GetType() && rNdSect.IsConnected() ) + { + rNdSect.Disconnect(); + rDoc.getIDocumentLinksAdministration().GetLinkManager().Remove( &rNdSect.GetBaseLink() ); + } + } +} + +void SwUndoUpdateSection::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoImpl(rContext); +} + + +SwUndoUpdateIndex::SwUndoUpdateIndex(SwTOXBaseSection & rTOX) + : SwUndo(SwUndoId::INSSECTION, rTOX.GetFormat()->GetDoc()) + , m_pSaveSectionOriginal(new SwUndoSaveSection) + , m_pSaveSectionUpdated(new SwUndoSaveSection) + , m_nStartIndex(rTOX.GetFormat()->GetSectionNode()->GetIndex() + 1) +{ + SwDoc & rDoc(*rTOX.GetFormat()->GetDoc()); + assert(rDoc.GetNodes()[m_nStartIndex-1]->IsSectionNode()); + assert(rDoc.GetNodes()[rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex()-1]->IsTextNode()); // -1 for extra empty node + // note: title is optional + assert(rDoc.GetNodes()[m_nStartIndex]->IsTextNode() + || rDoc.GetNodes()[m_nStartIndex]->IsSectionNode()); + SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex); + if (first.GetNode().IsSectionNode()) + { + SwSectionFormat & rSectionFormat(*first.GetNode().GetSectionNode()->GetSection().GetFormat()); + // note: DelSectionFormat will create & append SwUndoDelSection! + rDoc.DelSectionFormat(& rSectionFormat); // remove inner section nodes + } + assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty + SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 2); // skip empty node + assert(last.GetNode().IsTextNode()); + m_pSaveSectionOriginal->SaveSection(SwNodeRange(first, last), false); +} + +SwUndoUpdateIndex::~SwUndoUpdateIndex() = default; + +void SwUndoUpdateIndex::TitleSectionInserted(SwSectionFormat & rFormat) +{ + SwNodeIndex const tmp(rFormat.GetDoc()->GetNodes(), m_nStartIndex); // title inserted before empty node + assert(tmp.GetNode().IsSectionNode()); + assert(tmp.GetNode().GetSectionNode()->GetSection().GetFormat() == &rFormat); + m_pTitleSectionUpdated.reset(static_cast<SwUndoDelSection*>(MakeUndoDelSection(rFormat).release())); +} + +void SwUndoUpdateIndex::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + if (m_pTitleSectionUpdated) + { + m_pTitleSectionUpdated->RedoImpl(rContext); + } + SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex); + assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty + SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 1); + assert(last.GetNode().IsTextNode()); + // dummy node so that SaveSection doesn't remove ToX section... + SwTextNode *const pDeletionPrevention = rDoc.GetNodes().MakeTextNode( + SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode()), + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT)); + m_pSaveSectionUpdated->SaveSection(SwNodeRange(first, last), false); + m_pSaveSectionOriginal->RestoreSection(&rDoc, first, true); + // delete before restoring nested undo, so its node indexes match + SwNodeIndex const del(*pDeletionPrevention); + SwDoc::CorrAbs(del, del, SwPosition(SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode())), true); + rDoc.GetNodes().Delete(del); + // original title section will be restored by next Undo, see ctor! +} + +void SwUndoUpdateIndex::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + // original title section was deleted by previous Undo, see ctor! + SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex); + assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty + SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 1); + assert(last.GetNode().IsTextNode()); + // dummy node so that SaveSection doesn't remove ToX section... + SwTextNode *const pDeletionPrevention = rDoc.GetNodes().MakeTextNode( + SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode()), + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT)); + m_pSaveSectionOriginal->SaveSection(SwNodeRange(first, last), false); + m_pSaveSectionUpdated->RestoreSection(&rDoc, first, true); + // delete before restoring nested undo, so its node indexes match + SwNodeIndex const del(*pDeletionPrevention); + SwDoc::CorrAbs(del, del, SwPosition(SwNodeIndex(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode())), true); + rDoc.GetNodes().Delete(del); + if (m_pTitleSectionUpdated) + { + m_pTitleSectionUpdated->UndoImpl(rContext); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unsort.cxx b/sw/source/core/undo/unsort.cxx new file mode 100644 index 000000000..8f0820fb6 --- /dev/null +++ b/sw/source/core/undo/unsort.cxx @@ -0,0 +1,253 @@ +/* -*- 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 <memory> +#include <UndoSort.hxx> +#include <doc.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <UndoTable.hxx> +#include <sortopt.hxx> +#include <docsort.hxx> +#include <node2lay.hxx> + +// Undo for Sorting +SwSortUndoElement::~SwSortUndoElement() +{ + // are there string pointers saved? + if( 0xffffffff != SORT_TXT_TBL.TXT.nID ) + { + delete SORT_TXT_TBL.TBL.pSource; + delete SORT_TXT_TBL.TBL.pTarget; + } +} + +SwUndoSort::SwUndoSort(const SwPaM& rRg, const SwSortOptions& rOpt) + : SwUndo(SwUndoId::SORT_TXT, rRg.GetDoc()) + , SwUndRng(rRg) + , nTableNd(0) +{ + pSortOpt.reset( new SwSortOptions(rOpt) ); +} + +SwUndoSort::SwUndoSort( sal_uLong nStt, sal_uLong nEnd, const SwTableNode& rTableNd, + const SwSortOptions& rOpt, bool bSaveTable ) + : SwUndo(SwUndoId::SORT_TBL, rTableNd.GetDoc()) +{ + m_nSttNode = nStt; + m_nEndNode = nEnd; + nTableNd = rTableNd.GetIndex(); + + pSortOpt.reset( new SwSortOptions(rOpt) ); + if( bSaveTable ) + pUndoTableAttr.reset( new SwUndoAttrTable( rTableNd ) ); +} + +SwUndoSort::~SwUndoSort() +{ + pSortOpt.reset(); + pUndoTableAttr.reset(); +} + +void SwUndoSort::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if(pSortOpt->bTable) + { + // Undo for Table + RemoveIdxFromSection( rDoc, m_nSttNode, &m_nEndNode ); + + if( pUndoTableAttr ) + { + pUndoTableAttr->UndoImpl(rContext); + } + + SwTableNode* pTableNd = rDoc.GetNodes()[ nTableNd ]->GetTableNode(); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + pTableNd->DelFrames(); + const SwTable& rTable = pTableNd->GetTable(); + + SwMovedBoxes aMovedList; + for (const std::unique_ptr<SwSortUndoElement> & i : m_SortList) + { + const SwTableBox* pSource = rTable.GetTableBox( + *i->SORT_TXT_TBL.TBL.pSource ); + const SwTableBox* pTarget = rTable.GetTableBox( + *i->SORT_TXT_TBL.TBL.pTarget ); + + // move back + MoveCell(&rDoc, pTarget, pSource, + USHRT_MAX != aMovedList.GetPos(pSource) ); + + // store moved entry in list + aMovedList.push_back(pTarget); + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const sal_uLong nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(), nIdx, nIdx + 1 ); + } + else + { + // Undo for Text + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + RemoveIdxFromRange(rPam, true); + + // create index for (sorted) positions + // The IndexList must be created based on (asc.) sorted SourcePosition. + std::vector<SwNodeIndex> aIdxList; + aIdxList.reserve(m_SortList.size()); + + for (size_t i = 0; i < m_SortList.size(); ++i) + { + for (const std::unique_ptr<SwSortUndoElement> & j : m_SortList) + { + if (j->SORT_TXT_TBL.TXT.nSource == m_nSttNode + i) + { + aIdxList.push_back( SwNodeIndex( rDoc.GetNodes(), + j->SORT_TXT_TBL.TXT.nTarget ) ); + break; + } + } + } + + for (size_t i = 0; i < m_SortList.size(); ++i) + { + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode + i ); + SwNodeRange aRg( aIdxList[i], 0, aIdxList[i], 1 ); + rDoc.getIDocumentContentOperations().MoveNodeRange(aRg, aIdx, + SwMoveFlags::DEFAULT); + } + // delete indices + aIdxList.clear(); + SetPaM(rPam, true); + } +} + +void SwUndoSort::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if(pSortOpt->bTable) + { + // Redo for Table + RemoveIdxFromSection( rDoc, m_nSttNode, &m_nEndNode ); + + SwTableNode* pTableNd = rDoc.GetNodes()[ nTableNd ]->GetTableNode(); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + pTableNd->DelFrames(); + const SwTable& rTable = pTableNd->GetTable(); + + SwMovedBoxes aMovedList; + for (const std::unique_ptr<SwSortUndoElement> & i : m_SortList) + { + const SwTableBox* pSource = rTable.GetTableBox( + *i->SORT_TXT_TBL.TBL.pSource ); + const SwTableBox* pTarget = rTable.GetTableBox( + *i->SORT_TXT_TBL.TBL.pTarget ); + + // move back + MoveCell(&rDoc, pSource, pTarget, + USHRT_MAX != aMovedList.GetPos( pTarget ) ); + // store moved entry in list + aMovedList.push_back( pSource ); + } + + if( pUndoTableAttr ) + { + pUndoTableAttr->RedoImpl(rContext); + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const sal_uLong nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(), nIdx, nIdx + 1 ); + } + else + { + // Redo for Text + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + SetPaM(rPam); + RemoveIdxFromRange(rPam, true); + + std::vector<SwNodeIndex> aIdxList; + aIdxList.reserve(m_SortList.size()); + + for (size_t i = 0; i < m_SortList.size(); ++i) + { // current position is starting point + aIdxList.push_back( SwNodeIndex( rDoc.GetNodes(), + m_SortList[i]->SORT_TXT_TBL.TXT.nSource) ); + } + + for (size_t i = 0; i < m_SortList.size(); ++i) + { + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode + i); + SwNodeRange aRg( aIdxList[i], 0, aIdxList[i], 1 ); + rDoc.getIDocumentContentOperations().MoveNodeRange(aRg, aIdx, + SwMoveFlags::DEFAULT); + } + // delete indices + aIdxList.clear(); + SetPaM(rPam, true); + SwTextNode const*const pTNd = rPam.GetNode().GetTextNode(); + if( pTNd ) + { + rPam.GetPoint()->nContent = pTNd->GetText().getLength(); + } + } +} + +void SwUndoSort::RepeatImpl(::sw::RepeatContext & rContext) +{ + // table not repeat capable + if(!pSortOpt->bTable) + { + SwPaM *const pPam = & rContext.GetRepeatPaM(); + SwDoc& rDoc = *pPam->GetDoc(); + + if( !rDoc.IsIdxInTable( pPam->Start()->nNode ) ) + rDoc.SortText(*pPam, *pSortOpt); + } +} + +void SwUndoSort::Insert( const OUString& rOrgPos, const OUString& rNewPos) +{ + m_SortList.push_back(std::make_unique< SwSortUndoElement>(rOrgPos, rNewPos)); +} + +void SwUndoSort::Insert( sal_uLong nOrgPos, sal_uLong nNewPos) +{ + m_SortList.push_back(std::make_unique<SwSortUndoElement>(nOrgPos, nNewPos)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unspnd.cxx b/sw/source/core/undo/unspnd.cxx new file mode 100644 index 000000000..112db668d --- /dev/null +++ b/sw/source/core/undo/unspnd.cxx @@ -0,0 +1,197 @@ +/* -*- 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 <UndoSplitMove.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <swundo.hxx> +#include <frmfmt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <redline.hxx> +#include <docary.hxx> +#include <IShellCursorSupplier.hxx> +#include <osl/diagnose.h> + +// SPLITNODE + +SwUndoSplitNode::SwUndoSplitNode( SwDoc* pDoc, const SwPosition& rPos, + bool bChkTable ) + : SwUndo( SwUndoId::SPLITNODE, pDoc ), nNode( rPos.nNode.GetIndex() ), + nContent( rPos.nContent.GetIndex() ), + bTableFlag( false ), bChkTableStt( bChkTable ) +{ + SwTextNode *const pTextNd = rPos.nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "only for TextNode" ); + if( pTextNd->GetpSwpHints() ) + { + m_pHistory.reset(new SwHistory); + m_pHistory->CopyAttr(pTextNd->GetpSwpHints(), nNode, 0, + pTextNd->GetText().getLength(), false ); + if (!m_pHistory->Count()) + { + m_pHistory.reset(); + } + } + // consider Redline + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + pRedlData.reset( new SwRedlineData( RedlineType::Insert, pDoc->getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + nParRsid = pTextNd->GetParRsid(); +} + +SwUndoSplitNode::~SwUndoSplitNode() +{ + m_pHistory.reset(); + pRedlData.reset(); +} + +void SwUndoSplitNode::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() ); + rPam.DeleteMark(); + if( bTableFlag ) + { + // than a TextNode was added directly before the current table + SwNodeIndex& rIdx = rPam.GetPoint()->nNode; + rIdx = nNode; + SwTextNode* pTNd; + SwNode* pCurrNd = pDoc->GetNodes()[ nNode + 1 ]; + SwTableNode* pTableNd = pCurrNd->FindTableNode(); + if( pCurrNd->IsContentNode() && pTableNd && + nullptr != ( pTNd = pDoc->GetNodes()[ pTableNd->GetIndex()-1 ]->GetTextNode() )) + { + // move break attributes + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxItemSet* pNdSet = pTNd->GetpSwAttrSet(); + if( pNdSet ) + { + const SfxPoolItem *pItem; + if( SfxItemState::SET == pNdSet->GetItemState( RES_PAGEDESC, false, + &pItem ) ) + pTableFormat->SetFormatAttr( *pItem ); + + if( SfxItemState::SET == pNdSet->GetItemState( RES_BREAK, false, + &pItem ) ) + pTableFormat->SetFormatAttr( *pItem ); + } + + // than delete it again + SwNodeIndex aDelNd( *pTableNd, -1 ); + rPam.GetPoint()->nContent.Assign( static_cast<SwContentNode*>(pCurrNd), 0 ); + RemoveIdxRel( aDelNd.GetIndex(), *rPam.GetPoint() ); + pDoc->GetNodes().Delete( aDelNd ); + } + } + else + { + SwTextNode * pTNd = pDoc->GetNodes()[ nNode ]->GetTextNode(); + if( pTNd ) + { + rPam.GetPoint()->nNode = *pTNd; + rPam.GetPoint()->nContent.Assign(pTNd, pTNd->GetText().getLength()); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + rPam.SetMark(); + ++rPam.GetMark()->nNode; + rPam.GetMark()->nContent.Assign( rPam.GetMark()-> + nNode.GetNode().GetContentNode(), 0 ); + pDoc->getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); + rPam.DeleteMark(); + } + + RemoveIdxRel( nNode+1, *rPam.GetPoint() ); + + pTNd->JoinNext(); + if (m_pHistory) + { + rPam.GetPoint()->nContent = 0; + rPam.SetMark(); + rPam.GetPoint()->nContent = pTNd->GetText().getLength(); + + pDoc->RstTextAttrs( rPam, true ); + m_pHistory->TmpRollback( pDoc, 0, false ); + } + + pDoc->UpdateParRsid( pTNd, nParRsid ); + } + } + + // also set the cursor onto undo section + rPam.DeleteMark(); + rPam.GetPoint()->nNode = nNode; + rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), nContent ); +} + +void SwUndoSplitNode::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() ); + rPam.GetPoint()->nNode = nNode; + SwTextNode * pTNd = rPam.GetNode().GetTextNode(); + OSL_ENSURE(pTNd, "SwUndoSplitNode::RedoImpl(): SwTextNode expected"); + if (pTNd) + { + rPam.GetPoint()->nContent.Assign( pTNd, nContent ); + + SwDoc* pDoc = rPam.GetDoc(); + pDoc->getIDocumentContentOperations().SplitNode( *rPam.GetPoint(), bChkTableStt ); + + if (m_pHistory) + { + m_pHistory->SetTmpEnd(m_pHistory->Count()); + } + + if( ( pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) || + ( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + rPam.SetMark(); + if( rPam.Move( fnMoveBackward )) + { + if( pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *pRedlData, rPam ), true); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else + pDoc->getIDocumentRedlineAccess().SplitRedline( rPam ); + rPam.Exchange(); + } + rPam.DeleteMark(); + } + } +} + +void SwUndoSplitNode::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().getIDocumentContentOperations().SplitNode( + *rContext.GetRepeatPaM().GetPoint(), bChkTableStt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/untbl.cxx b/sw/source/core/undo/untbl.cxx new file mode 100644 index 000000000..1cbf5c1bd --- /dev/null +++ b/sw/source/core/undo/untbl.cxx @@ -0,0 +1,3155 @@ +/* -*- 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 <UndoTable.hxx> +#include <UndoRedline.hxx> +#include <UndoDelete.hxx> +#include <UndoSplitMove.hxx> +#include <UndoCore.hxx> +#include <fesh.hxx> +#include <hintids.hxx> +#include <hints.hxx> +#include <doc.hxx> +#include <docredln.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <editsh.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <pam.hxx> +#include <tblsel.hxx> +#include <swundo.hxx> +#include <rolbck.hxx> +#include <ddefld.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <swcache.hxx> +#include <tblafmt.hxx> +#include <poolfmt.hxx> +#include <mvsave.hxx> +#include <cellatr.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <redline.hxx> +#include <node2lay.hxx> +#include <tblrwcl.hxx> +#include <fmtanchr.hxx> +#include <strings.hrc> +#include <unochart.hxx> +#include <calbck.hxx> +#include <frameformats.hxx> + +#include <memory> +#include <utility> +#include <vector> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +#ifdef DBG_UTIL + #define DEBUG_REDLINE( pDoc ) sw_DebugRedline( pDoc ); +#else + #define DEBUG_REDLINE( pDoc ) +#endif + +typedef std::vector<std::shared_ptr<SfxItemSet> > SfxItemSets; + +struct UndoTableCpyTable_Entry +{ + sal_uLong nBoxIdx, nOffset; + std::unique_ptr<SfxItemSet> pBoxNumAttr; + std::unique_ptr<SwUndo> pUndo; + + // Was the last paragraph of the new and the first paragraph of the old content joined? + bool bJoin; // For redlining only + + explicit UndoTableCpyTable_Entry( const SwTableBox& rBox ); +}; + +namespace { + +class SaveBox; +class SaveLine; + +} + +class SaveTable +{ + friend SaveBox; + friend SaveLine; + SfxItemSet m_aTableSet; + std::unique_ptr<SaveLine> m_pLine; + const SwTable* m_pSwTable; + SfxItemSets m_aSets; + SwFrameFormatsV m_aFrameFormats; + sal_uInt16 m_nLineCount; + bool m_bModifyBox : 1; + bool m_bSaveFormula : 1; + bool m_bNewModel : 1; + + SaveTable(const SaveTable&) = delete; + SaveTable& operator=(const SaveTable&) = delete; + +public: + SaveTable( const SwTable& rTable, sal_uInt16 nLnCnt = USHRT_MAX, + bool bSaveFormula = true ); + + sal_uInt16 AddFormat( SwFrameFormat* pFormat, bool bIsLine ); + void NewFrameFormat( const SwTableLine* , const SwTableBox*, sal_uInt16 nFormatPos, + SwFrameFormat* pOldFormat ); + + void RestoreAttr( SwTable& rTable, bool bModifyBox = false ); + void SaveContentAttrs( SwDoc* pDoc ); + void CreateNew( SwTable& rTable, bool bCreateFrames = true, + bool bRestoreChart = true ); + bool IsNewModel() const { return m_bNewModel; } +}; + +namespace { + +class SaveLine +{ + friend SaveTable; + friend class SaveBox; + + SaveLine* pNext; + SaveBox* pBox; + sal_uInt16 nItemSet; + + SaveLine(const SaveLine&) = delete; + SaveLine& operator=(const SaveLine&) = delete; + +public: + SaveLine( SaveLine* pPrev, const SwTableLine& rLine, SaveTable& rSTable ); + ~SaveLine(); + + void RestoreAttr( SwTableLine& rLine, SaveTable& rSTable ); + void SaveContentAttrs( SwDoc* pDoc ); + + void CreateNew( SwTable& rTable, SwTableBox& rParent, SaveTable& rSTable ); +}; + +class SaveBox +{ + friend class SaveLine; + + SaveBox* pNext; + sal_uLong nSttNode; + long nRowSpan; + sal_uInt16 nItemSet; + union + { + SfxItemSets* pContentAttrs; + SaveLine* pLine; + } Ptrs; + +public: + SaveBox( SaveBox* pPrev, const SwTableBox& rBox, SaveTable& rSTable ); + ~SaveBox(); + + void RestoreAttr( SwTableBox& rBox, SaveTable& rSTable ); + void SaveContentAttrs( SwDoc* pDoc ); + + void CreateNew( SwTable& rTable, SwTableLine& rParent, SaveTable& rSTable ); +}; + +} + +#if OSL_DEBUG_LEVEL > 0 +static void CheckTable( const SwTable& ); +#define CHECKTABLE(t) CheckTable( t ); +#else +#define CHECKTABLE(t) +#endif + +/* #130880: Crash in undo of table to text when the table has (freshly) merged cells +The order of cell content nodes in the nodes array is not given by the recursive table structure. +The algorithm must not rely on this even it holds for a fresh loaded table in odt file format. +So we need to remember not only the start node position but the end node position as well. +*/ + +struct SwTableToTextSave +{ + sal_uLong m_nSttNd; + sal_uLong m_nEndNd; + sal_Int32 m_nContent; + std::unique_ptr<SwHistory> m_pHstry; + // metadata references for first and last paragraph in cell + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd; + + SwTableToTextSave( SwDoc& rDoc, sal_uLong nNd, sal_uLong nEndIdx, sal_Int32 nContent ); + +private: + SwTableToTextSave(const SwTableToTextSave&) = delete; + SwTableToTextSave& operator=(const SwTableToTextSave&) = delete; + +}; + +sal_uInt16 const aSave_BoxContentSet[] = { + RES_CHRATR_COLOR, RES_CHRATR_CROSSEDOUT, + RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_POSTURE, RES_CHRATR_POSTURE, + RES_CHRATR_SHADOWED, RES_CHRATR_WEIGHT, + RES_PARATR_ADJUST, RES_PARATR_ADJUST, + 0 }; + +SwUndoInsTable::SwUndoInsTable( const SwPosition& rPos, sal_uInt16 nCl, sal_uInt16 nRw, + sal_uInt16 nAdj, const SwInsertTableOptions& rInsTableOpts, + const SwTableAutoFormat* pTAFormat, + const std::vector<sal_uInt16> *pColArr, + const OUString & rName) + : SwUndo( SwUndoId::INSTABLE, rPos.GetDoc() ), + m_aInsTableOptions( rInsTableOpts ), + m_nStartNode( rPos.nNode.GetIndex() ), m_nRows( nRw ), m_nColumns( nCl ), m_nAdjust( nAdj ) +{ + if( pColArr ) + { + m_pColumnWidth.reset( new std::vector<sal_uInt16>(*pColArr) ); + } + if( pTAFormat ) + m_pAutoFormat.reset( new SwTableAutoFormat( *pTAFormat ) ); + + // consider redline + SwDoc& rDoc = *rPos.nNode.GetNode().GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + m_sTableName = rName; +} + +SwUndoInsTable::~SwUndoInsTable() +{ + m_pDDEFieldType.reset(); + m_pColumnWidth.reset(); + m_pRedlineData.reset(); + m_pAutoFormat.reset(); +} + +void SwUndoInsTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aIdx( rDoc.GetNodes(), m_nStartNode ); + + SwTableNode* pTableNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + pTableNd->DelFrames(); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any ); + RemoveIdxFromSection( rDoc, m_nStartNode ); + + // move hard page breaks into next node + SwContentNode* pNextNd = rDoc.GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + } + + m_sTableName = pTableNd->GetTable().GetFrameFormat()->GetName(); + if( auto pDDETable = dynamic_cast<const SwDDETable *>(&pTableNd->GetTable()) ) + m_pDDEFieldType.reset(static_cast<SwDDEFieldType*>(pDDETable->GetDDEFieldType()->Copy().release())); + + rDoc.GetNodes().Delete( aIdx, pTableNd->EndOfSectionIndex() - + aIdx.GetIndex() + 1 ); + + SwPaM & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() ); + rPam.DeleteMark(); + rPam.GetPoint()->nNode = aIdx; + rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), 0 ); +} + +void SwUndoInsTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwPosition const aPos(SwNodeIndex(rDoc.GetNodes(), m_nStartNode)); + const SwTable* pTable = rDoc.InsertTable( m_aInsTableOptions, aPos, m_nRows, m_nColumns, + m_nAdjust, + m_pAutoFormat.get(), m_pColumnWidth.get() ); + rDoc.GetEditShell()->MoveTable( GotoPrevTable, fnTableStart ); + static_cast<SwFrameFormat*>(pTable->GetFrameFormat())->SetName( m_sTableName ); + SwTableNode* pTableNode = rDoc.GetNodes()[m_nStartNode]->GetTableNode(); + + if( m_pDDEFieldType ) + { + SwDDEFieldType* pNewType = static_cast<SwDDEFieldType*>(rDoc.getIDocumentFieldsAccess().InsertFieldType( + *m_pDDEFieldType)); + std::unique_ptr<SwDDETable> pDDETable(new SwDDETable( pTableNode->GetTable(), pNewType )); + pTableNode->SetNewTable( std::move(pDDETable) ); + m_pDDEFieldType.reset(); + } + + if( (m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) || + ( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( *pTableNode->EndOfSectionNode(), *pTableNode, 1 ); + SwContentNode* pCNd = aPam.GetContentNode( false ); + if( pCNd ) + aPam.GetMark()->nContent.Assign( pCNd, 0 ); + + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, aPam ), true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else + rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } +} + +void SwUndoInsTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().InsertTable( + m_aInsTableOptions, *rContext.GetRepeatPaM().GetPoint(), + m_nRows, m_nColumns, m_nAdjust, m_pAutoFormat.get(), m_pColumnWidth.get() ); +} + +SwRewriter SwUndoInsTable::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE)); + aRewriter.AddRule(UndoArg2, m_sTableName); + aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE)); + + return aRewriter; +} + +SwTableToTextSave::SwTableToTextSave( SwDoc& rDoc, sal_uLong nNd, sal_uLong nEndIdx, sal_Int32 nCnt ) + : m_nSttNd( nNd ), m_nEndNd( nEndIdx), m_nContent( nCnt ) +{ + // keep attributes of the joined node + SwTextNode* pNd = rDoc.GetNodes()[ nNd ]->GetTextNode(); + if( pNd ) + { + m_pHstry.reset( new SwHistory ); + + m_pHstry->Add( pNd->GetTextColl(), nNd, SwNodeType::Text ); + if ( pNd->GetpSwpHints() ) + { + m_pHstry->CopyAttr( pNd->GetpSwpHints(), nNd, 0, + pNd->GetText().getLength(), false ); + } + if( pNd->HasSwAttrSet() ) + m_pHstry->CopyFormatAttr( *pNd->GetpSwAttrSet(), nNd ); + + if( !m_pHstry->Count() ) + { + m_pHstry.reset(); + } + + // METADATA: store + m_pMetadataUndoStart = pNd->CreateUndo(); + } + + // we also need to store the metadata reference of the _last_ paragraph + // we subtract 1 to account for the removed cell start/end node pair + // (after SectionUp, the end of the range points to the node after the cell) + if ( nEndIdx - 1 > nNd ) + { + SwTextNode* pLastNode( rDoc.GetNodes()[ nEndIdx - 1 ]->GetTextNode() ); + if( pLastNode ) + { + // METADATA: store + m_pMetadataUndoEnd = pLastNode->CreateUndo(); + } + } +} + +SwUndoTableToText::SwUndoTableToText( const SwTable& rTable, sal_Unicode cCh ) + : SwUndo( SwUndoId::TABLETOTEXT, rTable.GetFrameFormat()->GetDoc() ), + m_sTableName( rTable.GetFrameFormat()->GetName() ), + m_nStartNode( 0 ), m_nEndNode( 0 ), + m_cSeparator( cCh ), m_nHeadlineRepeat( rTable.GetRowsToRepeat() ) +{ + m_pTableSave.reset( new SaveTable( rTable ) ); + m_vBoxSaves.reserve(rTable.GetTabSortBoxes().size()); + + if( auto pDDETable = dynamic_cast<const SwDDETable *>(&rTable) ) + m_pDDEFieldType.reset(static_cast<SwDDEFieldType*>(pDDETable->GetDDEFieldType()->Copy().release())); + + m_bCheckNumFormat = rTable.GetFrameFormat()->GetDoc()->IsInsTableFormatNum(); + + m_pHistory.reset(new SwHistory); + const SwTableNode* pTableNd = rTable.GetTableNode(); + sal_uLong nTableStt = pTableNd->GetIndex(), nTableEnd = pTableNd->EndOfSectionIndex(); + + const SwFrameFormats& rFrameFormatTable = *pTableNd->GetDoc()->GetSpzFrameFormats(); + for( size_t n = 0; n < rFrameFormatTable.size(); ++n ) + { + SwFrameFormat* pFormat = rFrameFormatTable[ n ]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) && + nTableStt <= pAPos->nNode.GetIndex() && + pAPos->nNode.GetIndex() < nTableEnd ) + { + m_pHistory->AddChangeFlyAnchor(*pFormat); + } + } + + if( !m_pHistory->Count() ) + { + m_pHistory.reset(); + } +} + +SwUndoTableToText::~SwUndoTableToText() +{ + m_pDDEFieldType.reset(); + m_pTableSave.reset(); + m_vBoxSaves.clear(); + m_pHistory.reset(); +} + +void SwUndoTableToText::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + SwNodeIndex aFrameIdx( rDoc.GetNodes(), m_nStartNode ); + SwNodeIndex aEndIdx( rDoc.GetNodes(), m_nEndNode ); + + pPam->GetPoint()->nNode = aFrameIdx; + pPam->SetMark(); + pPam->GetPoint()->nNode = aEndIdx; + rDoc.DelNumRules( *pPam ); + pPam->DeleteMark(); + + // now collect all Uppers + SwNode2LayoutSaveUpperFrames aNode2Layout(aFrameIdx.GetNode()); + + // create TableNode structure + SwTableNode* pTableNd = rDoc.GetNodes().UndoTableToText( m_nStartNode, m_nEndNode, m_vBoxSaves ); + pTableNd->GetTable().SetTableModel( m_pTableSave->IsNewModel() ); + SwTableFormat* pTableFormat = rDoc.MakeTableFrameFormat( m_sTableName, rDoc.GetDfltFrameFormat() ); + pTableNd->GetTable().RegisterToFormat( *pTableFormat ); + pTableNd->GetTable().SetRowsToRepeat( m_nHeadlineRepeat ); + + // create old table structure + m_pTableSave->CreateNew( pTableNd->GetTable() ); + + if( m_pDDEFieldType ) + { + SwDDEFieldType* pNewType = static_cast<SwDDEFieldType*>(rDoc.getIDocumentFieldsAccess().InsertFieldType( + *m_pDDEFieldType)); + std::unique_ptr<SwDDETable> pDDETable( new SwDDETable( pTableNd->GetTable(), pNewType ) ); + pTableNd->SetNewTable( std::move(pDDETable), false ); + m_pDDEFieldType.reset(); + } + + if( m_bCheckNumFormat ) + { + SwTableSortBoxes& rBxs = pTableNd->GetTable().GetTabSortBoxes(); + for (size_t nBoxes = rBxs.size(); nBoxes; ) + { + rDoc.ChkBoxNumFormat( *rBxs[ --nBoxes ], false ); + } + } + + if( m_pHistory ) + { + sal_uInt16 nTmpEnd = m_pHistory->GetTmpEnd(); + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( nTmpEnd ); + } + + aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(), + pTableNd->GetIndex(), pTableNd->GetIndex()+1 ); + + // Is a table selection requested? + pPam->DeleteMark(); + pPam->GetPoint()->nNode = *pTableNd->EndOfSectionNode(); + pPam->SetMark(); + pPam->GetPoint()->nNode = *pPam->GetNode().StartOfSectionNode(); + pPam->Move( fnMoveForward, GoInContent ); + pPam->Exchange(); + pPam->Move( fnMoveBackward, GoInContent ); + + ClearFEShellTabCols(rDoc, nullptr); +} + +// located in untbl.cxx and only an Undo object is allowed to call it +SwTableNode* SwNodes::UndoTableToText( sal_uLong nSttNd, sal_uLong nEndNd, + const SwTableToTextSaves& rSavedData ) +{ + SwNodeIndex aSttIdx( *this, nSttNd ); + SwNodeIndex aEndIdx( *this, nEndNd+1 ); + + SwTableNode * pTableNd = new SwTableNode( aSttIdx ); + SwEndNode* pEndNd = new SwEndNode( aEndIdx, *pTableNd ); + + aEndIdx = *pEndNd; + + /* Set pTableNd as start of section for all nodes in [nSttNd, nEndNd]. + Delete all Frames attached to the nodes in that range. */ + SwNode* pNd; + { + sal_uLong n, nTmpEnd = aEndIdx.GetIndex(); + for( n = pTableNd->GetIndex() + 1; n < nTmpEnd; ++n ) + { + if( ( pNd = (*this)[ n ] )->IsContentNode() ) + static_cast<SwContentNode*>(pNd)->DelFrames(nullptr); + pNd->m_pStartOfSection = pTableNd; + } + } + + // than create table structure partially. First a single line that contains + // all boxes. The correct structure is then taken from SaveStruct. + SwTableBoxFormat* pBoxFormat = GetDoc()->MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = GetDoc()->MakeTableLineFormat(); + SwTableLine* pLine = new SwTableLine( pLineFormat, rSavedData.size(), nullptr ); + pTableNd->GetTable().GetTabLines().insert( pTableNd->GetTable().GetTabLines().begin(), pLine ); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + for( size_t n = rSavedData.size(); n; ) + { + const SwTableToTextSave *const pSave = rSavedData[ --n ].get(); + // if the start node was merged with last from prev. cell, + // subtract 1 from index to get the merged paragraph, and split that + aSttIdx = pSave->m_nSttNd - ( ( SAL_MAX_INT32 != pSave->m_nContent ) ? 1 : 0); + SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode(); + + if( SAL_MAX_INT32 != pSave->m_nContent ) + { + // split at ContentPosition, delete previous char (= separator) + OSL_ENSURE( pTextNd, "Where is my TextNode?" ); + SwIndex aCntPos( pTextNd, pSave->m_nContent - 1 ); + + pTextNd->EraseText( aCntPos, 1 ); + + std::function<void (SwTextNode *, sw::mark::RestoreMode)> restoreFunc( + [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(*pNewNode, pSave->m_nContent, pSave->m_nContent + 1, eMode); + } + }); + pTextNd->SplitContentNode( + SwPosition(aSttIdx, aCntPos), &restoreFunc); + } + else + { + pContentStore->Clear(); + if( pTextNd ) + pContentStore->Save( GetDoc(), aSttIdx.GetIndex(), pTextNd->GetText().getLength() ); + } + + if( pTextNd ) + { + // METADATA: restore + pTextNd->GetTextNode()->RestoreMetadata(pSave->m_pMetadataUndoStart); + if( pTextNd->HasSwAttrSet() ) + pTextNd->ResetAllAttr(); + + if( pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( false ); + } + + if( pSave->m_pHstry ) + { + sal_uInt16 nTmpEnd = pSave->m_pHstry->GetTmpEnd(); + pSave->m_pHstry->TmpRollback( GetDoc(), 0 ); + pSave->m_pHstry->SetTmpEnd( nTmpEnd ); + } + + // METADATA: restore + // end points to node after cell + if ( pSave->m_nEndNd - 1 > pSave->m_nSttNd ) + { + SwTextNode* pLastNode = (*this)[ pSave->m_nEndNd - 1 ]->GetTextNode(); + if (pLastNode) + { + pLastNode->RestoreMetadata(pSave->m_pMetadataUndoEnd); + } + } + + aEndIdx = pSave->m_nEndNd; + SwStartNode* pSttNd = new SwStartNode( aSttIdx, SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + new SwEndNode( aEndIdx, *pSttNd ); + + for( sal_uLong i = aSttIdx.GetIndex(); i < aEndIdx.GetIndex()-1; ++i ) + { + pNd = (*this)[ i ]; + pNd->m_pStartOfSection = pSttNd; + if( pNd->IsStartNode() ) + i = pNd->EndOfSectionIndex(); + } + + SwTableBox* pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin(), pBox ); + } + return pTableNd; +} + +void SwUndoTableToText::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->GetPoint()->nNode = m_nStartNode; + pPam->GetPoint()->nContent.Assign( nullptr, 0 ); + SwNodeIndex aSaveIdx( pPam->GetPoint()->nNode, -1 ); + + pPam->SetMark(); // log off all indices + pPam->DeleteMark(); + + SwTableNode* pTableNd = pPam->GetNode().GetTableNode(); + OSL_ENSURE( pTableNd, "Could not find any TableNode" ); + + if( auto pDDETable = dynamic_cast<const SwDDETable *>(&pTableNd->GetTable()) ) + m_pDDEFieldType.reset(static_cast<SwDDEFieldType*>(pDDETable->GetDDEFieldType()->Copy().release())); + + rDoc.TableToText( pTableNd, m_cSeparator ); + + ++aSaveIdx; + SwContentNode* pCNd = aSaveIdx.GetNode().GetContentNode(); + if( !pCNd && nullptr == ( pCNd = rDoc.GetNodes().GoNext( &aSaveIdx ) ) && + nullptr == ( pCNd = SwNodes::GoPrevious( &aSaveIdx )) ) + { + OSL_FAIL( "Where is the TextNode now?" ); + } + + pPam->GetPoint()->nNode = aSaveIdx; + pPam->GetPoint()->nContent.Assign( pCNd, 0 ); + + pPam->SetMark(); // log off all indices + pPam->DeleteMark(); +} + +void SwUndoTableToText::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM *const pPam = & rContext.GetRepeatPaM(); + SwTableNode *const pTableNd = pPam->GetNode().FindTableNode(); + if( pTableNd ) + { + // move cursor out of table + pPam->GetPoint()->nNode = *pTableNd->EndOfSectionNode(); + pPam->Move( fnMoveForward, GoInContent ); + pPam->SetMark(); + pPam->DeleteMark(); + + rContext.GetDoc().TableToText( pTableNd, m_cSeparator ); + } +} + +void SwUndoTableToText::SetRange( const SwNodeRange& rRg ) +{ + m_nStartNode = rRg.aStart.GetIndex(); + m_nEndNode = rRg.aEnd.GetIndex(); +} + +void SwUndoTableToText::AddBoxPos( SwDoc& rDoc, sal_uLong nNdIdx, sal_uLong nEndIdx, sal_Int32 nContentIdx ) +{ + m_vBoxSaves.push_back(std::make_unique<SwTableToTextSave>(rDoc, nNdIdx, nEndIdx, nContentIdx)); +} + +SwUndoTextToTable::SwUndoTextToTable( const SwPaM& rRg, + const SwInsertTableOptions& rInsTableOpts, + sal_Unicode cCh, sal_uInt16 nAdj, + const SwTableAutoFormat* pAFormat ) + : SwUndo( SwUndoId::TEXTTOTABLE, rRg.GetDoc() ), SwUndRng( rRg ), m_aInsertTableOpts( rInsTableOpts ), + m_pHistory( nullptr ), m_cSeparator( cCh ), m_nAdjust( nAdj ) +{ + if( pAFormat ) + m_pAutoFormat.reset( new SwTableAutoFormat( *pAFormat ) ); + + const SwPosition* pEnd = rRg.End(); + SwNodes& rNds = rRg.GetDoc()->GetNodes(); + m_bSplitEnd = pEnd->nContent.GetIndex() && ( pEnd->nContent.GetIndex() + != pEnd->nNode.GetNode().GetContentNode()->Len() || + pEnd->nNode.GetIndex() >= rNds.GetEndOfContent().GetIndex()-1 ); +} + +SwUndoTextToTable::~SwUndoTextToTable() +{ + m_pAutoFormat.reset(); +} + +void SwUndoTextToTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + sal_uLong nTableNd = m_nSttNode; + if( m_nSttContent ) + ++nTableNd; // Node was split previously + SwNodeIndex aIdx( rDoc.GetNodes(), nTableNd ); + SwTableNode *const pTNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTNd, "Could not find a TableNode" ); + + RemoveIdxFromSection( rDoc, nTableNd ); + + m_sTableName = pTNd->GetTable().GetFrameFormat()->GetName(); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + if( !mvDelBoxes.empty() ) + { + pTNd->DelFrames(); + SwTable& rTable = pTNd->GetTable(); + for( size_t n = mvDelBoxes.size(); n; ) + { + SwTableBox* pBox = rTable.GetTableBox( mvDelBoxes[ --n ] ); + if( pBox ) + ::DeleteBox_( rTable, pBox, nullptr, false, false ); + else { + OSL_ENSURE( false, "Where is my box?" ); + } + } + } + + rDoc.TableToText( pTNd, 0x0b == m_cSeparator ? 0x09 : m_cSeparator ); + + // join again at start? + SwPaM aPam(rDoc.GetNodes().GetEndOfContent()); + SwPosition *const pPos = aPam.GetPoint(); + if( m_nSttContent ) + { + pPos->nNode = nTableNd; + pPos->nContent.Assign(pPos->nNode.GetNode().GetContentNode(), 0); + if (aPam.Move(fnMoveBackward, GoInContent)) + { + SwNodeIndex & rIdx = aPam.GetPoint()->nNode; + + // than move, relatively, the Cursor/etc. again + RemoveIdxRel( rIdx.GetIndex()+1, *pPos ); + + rIdx.GetNode().GetContentNode()->JoinNext(); + } + } + + // join again at end? + if( m_bSplitEnd ) + { + SwNodeIndex& rIdx = pPos->nNode; + rIdx = m_nEndNode; + SwTextNode* pTextNd = rIdx.GetNode().GetTextNode(); + if( pTextNd && pTextNd->CanJoinNext() ) + { + aPam.GetMark()->nContent.Assign( nullptr, 0 ); + aPam.GetPoint()->nContent.Assign( nullptr, 0 ); + + // than move, relatively, the Cursor/etc. again + pPos->nContent.Assign(pTextNd, pTextNd->GetText().getLength()); + RemoveIdxRel( m_nEndNode + 1, *pPos ); + + pTextNd->JoinNext(); + } + } + + AddUndoRedoPaM(rContext); +} + +void SwUndoTextToTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + RemoveIdxFromRange(rPam, false); + SetPaM(rPam); + + SwTable const*const pTable = rContext.GetDoc().TextToTable( + m_aInsertTableOpts, rPam, m_cSeparator, m_nAdjust, m_pAutoFormat.get() ); + static_cast<SwFrameFormat*>(pTable->GetFrameFormat())->SetName( m_sTableName ); +} + +void SwUndoTextToTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + // no Table In Table + if (!rContext.GetRepeatPaM().GetNode().FindTableNode()) + { + rContext.GetDoc().TextToTable( m_aInsertTableOpts, rContext.GetRepeatPaM(), + m_cSeparator, m_nAdjust, + m_pAutoFormat.get() ); + } +} + +void SwUndoTextToTable::AddFillBox( const SwTableBox& rBox ) +{ + mvDelBoxes.push_back( rBox.GetSttIdx() ); +} + +SwHistory& SwUndoTextToTable::GetHistory() +{ + if( !m_pHistory ) + m_pHistory = new SwHistory; + return *m_pHistory; +} + +SwUndoTableHeadline::SwUndoTableHeadline( const SwTable& rTable, sal_uInt16 nOldHdl, + sal_uInt16 nNewHdl ) + : SwUndo( SwUndoId::TABLEHEADLINE, rTable.GetFrameFormat()->GetDoc() ), + m_nOldHeadline( nOldHdl ), + m_nNewHeadline( nNewHdl ) +{ + OSL_ENSURE( !rTable.GetTabSortBoxes().empty(), "Table without content" ); + const SwStartNode *pSttNd = rTable.GetTabSortBoxes()[ 0 ]->GetSttNd(); + OSL_ENSURE( pSttNd, "Box without content" ); + + m_nTableNode = pSttNd->StartOfSectionIndex(); +} + +void SwUndoTableHeadline::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + OSL_ENSURE( pTNd, "could not find any TableNode" ); + + rDoc.SetRowsToRepeat( pTNd->GetTable(), m_nOldHeadline ); +} + +void SwUndoTableHeadline::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + OSL_ENSURE( pTNd, "could not find any TableNode" ); + + rDoc.SetRowsToRepeat( pTNd->GetTable(), m_nNewHeadline ); +} + +void SwUndoTableHeadline::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwTableNode *const pTableNd = + rContext.GetRepeatPaM().GetNode().FindTableNode(); + if( pTableNd ) + { + rContext.GetDoc().SetRowsToRepeat( pTableNd->GetTable(), m_nNewHeadline ); + } +} + +SaveTable::SaveTable( const SwTable& rTable, sal_uInt16 nLnCnt, bool bSaveFormula ) + : m_aTableSet(*rTable.GetFrameFormat()->GetAttrSet().GetPool(), aTableSetRange), + m_pSwTable(&rTable), m_nLineCount(nLnCnt), m_bSaveFormula(bSaveFormula) +{ + m_bModifyBox = false; + m_bNewModel = rTable.IsNewModel(); + m_aTableSet.Put(rTable.GetFrameFormat()->GetAttrSet()); + m_pLine.reset( new SaveLine( nullptr, *rTable.GetTabLines()[ 0 ], *this ) ); + + SaveLine* pLn = m_pLine.get(); + if( USHRT_MAX == nLnCnt ) + nLnCnt = rTable.GetTabLines().size(); + for( sal_uInt16 n = 1; n < nLnCnt; ++n ) + pLn = new SaveLine( pLn, *rTable.GetTabLines()[ n ], *this ); + + m_aFrameFormats.clear(); + m_pSwTable = nullptr; +} + +sal_uInt16 SaveTable::AddFormat( SwFrameFormat* pFormat, bool bIsLine ) +{ + size_t nRet = m_aFrameFormats.GetPos(pFormat); + if( SIZE_MAX == nRet ) + { + // Create copy of ItemSet + auto pSet = std::make_shared<SfxItemSet>( *pFormat->GetAttrSet().GetPool(), + bIsLine ? aTableLineSetRange : aTableBoxSetRange ); + pSet->Put( pFormat->GetAttrSet() ); + // When a formula is set, never save the value. It possibly must be + // recalculated. + // Save formulas always in plain text. + const SfxPoolItem* pItem; + if( SfxItemState::SET == pSet->GetItemState( RES_BOXATR_FORMULA, true, &pItem )) + { + pSet->ClearItem( RES_BOXATR_VALUE ); + if (m_pSwTable && m_bSaveFormula) + { + SwTableFormulaUpdate aMsgHint(m_pSwTable); + aMsgHint.m_eFlags = TBL_BOXNAME; + SwTableBoxFormula* pFormulaItem = const_cast<SwTableBoxFormula*>(static_cast<const SwTableBoxFormula*>(pItem)); + pFormulaItem->ChgDefinedIn( pFormat ); + pFormulaItem->ChangeState( &aMsgHint ); + pFormulaItem->ChgDefinedIn( nullptr ); + } + } + nRet = m_aSets.size(); + m_aSets.push_back(pSet); + m_aFrameFormats.insert(m_aFrameFormats.begin() + nRet, pFormat); + } + return static_cast<sal_uInt16>(nRet); +} + +void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox ) +{ + m_bModifyBox = bMdfyBox; + + // first, get back attributes of TableFrameFormat + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SfxItemSet& rFormatSet = const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pFormat->GetAttrSet())); + rFormatSet.ClearItem(); + rFormatSet.Put(m_aTableSet); + + if( pFormat->IsInCache() ) + { + SwFrame::GetCache().Delete( pFormat ); + pFormat->SetInCache( false ); + } + + // for safety, invalidate all TableFrames + SwIterator<SwTabFrame,SwFormat> aIter( *pFormat ); + for( SwTabFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + if( pLast->GetTable() == &rTable ) + { + pLast->InvalidateAll(); + pLast->SetCompletePaint(); + } + + // fill FrameFormats with defaults (0) + pFormat = nullptr; + for (size_t n = m_aSets.size(); n; --n) + m_aFrameFormats.push_back(pFormat); + + const size_t nLnCnt = (USHRT_MAX == m_nLineCount) + ? rTable.GetTabLines().size() + : m_nLineCount; + + SaveLine* pLn = m_pLine.get(); + for( size_t n = 0; n < nLnCnt; ++n, pLn = pLn->pNext ) + { + if( !pLn ) + { + OSL_ENSURE( false, "Number of lines changed" ); + break; + } + + pLn->RestoreAttr( *rTable.GetTabLines()[ n ], *this ); + } + + m_aFrameFormats.clear(); + m_bModifyBox = false; +} + +void SaveTable::SaveContentAttrs( SwDoc* pDoc ) +{ + m_pLine->SaveContentAttrs(pDoc); +} + +void SaveTable::CreateNew( SwTable& rTable, bool bCreateFrames, + bool bRestoreChart ) +{ + FndBox_ aTmpBox( nullptr, nullptr ); + aTmpBox.DelFrames( rTable ); + + // first, get back attributes of TableFrameFormat + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SfxItemSet& rFormatSet = const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pFormat->GetAttrSet())); + rFormatSet.ClearItem(); + rFormatSet.Put(m_aTableSet); + + if( pFormat->IsInCache() ) + { + SwFrame::GetCache().Delete( pFormat ); + pFormat->SetInCache( false ); + } + + // SwTableBox must have a format - the SwTableBox takes ownership of it + SwTableBoxFormat *const pNewFormat(pFormat->GetDoc()->MakeTableBoxFormat()); + SwTableBox aParent(pNewFormat, rTable.GetTabLines().size(), nullptr); + + // fill FrameFormats with defaults (0) + pFormat = nullptr; + for( size_t n = m_aSets.size(); n; --n ) + m_aFrameFormats.push_back(pFormat); + + m_pLine->CreateNew(rTable, aParent, *this); + m_aFrameFormats.clear(); + + // add new lines, delete old ones + const size_t nOldLines = (USHRT_MAX == m_nLineCount) + ? rTable.GetTabLines().size() + : m_nLineCount; + + SwDoc *pDoc = rTable.GetFrameFormat()->GetDoc(); + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + size_t n = 0; + for( ; n < aParent.GetTabLines().size(); ++n ) + { + SwTableLine* pLn = aParent.GetTabLines()[ n ]; + pLn->SetUpper( nullptr ); + if( n < nOldLines ) + { + SwTableLine* pOld = rTable.GetTabLines()[ n ]; + + // TL_CHART2: notify chart about boxes to be removed + const SwTableBoxes &rBoxes = pOld->GetTabBoxes(); + const size_t nBoxes = rBoxes.size(); + for (size_t k = 0; k < nBoxes; ++k) + { + SwTableBox *pBox = rBoxes[k]; + if (pPCD) + pPCD->DeleteBox( &rTable, *pBox ); + } + + rTable.GetTabLines()[n] = pLn; + delete pOld; + } + else + rTable.GetTabLines().insert( rTable.GetTabLines().begin() + n, pLn ); + } + + if( n < nOldLines ) + { + // remove remaining lines... + for (size_t k1 = 0; k1 < nOldLines - n; ++k1) + { + const SwTableBoxes &rBoxes = rTable.GetTabLines()[n + k1]->GetTabBoxes(); + const size_t nBoxes = rBoxes.size(); + for (size_t k2 = 0; k2 < nBoxes; ++k2) + { + SwTableBox *pBox = rBoxes[k2]; + // TL_CHART2: notify chart about boxes to be removed + if (pPCD) + pPCD->DeleteBox( &rTable, *pBox ); + } + } + + for( SwTableLines::const_iterator it = rTable.GetTabLines().begin() + n; + it != rTable.GetTabLines().begin() + nOldLines; ++it ) + delete *it; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + n, rTable.GetTabLines().begin() + nOldLines ); + } + + aParent.GetTabLines().erase( aParent.GetTabLines().begin(), aParent.GetTabLines().begin() + n ); + assert(aParent.GetTabLines().empty()); + + if( bCreateFrames ) + aTmpBox.MakeFrames( rTable ); + if( bRestoreChart ) + { + // TL_CHART2: need to inform chart of probably changed cell names + pDoc->UpdateCharts( rTable.GetFrameFormat()->GetName() ); + } +} + +void SaveTable::NewFrameFormat( const SwTableLine* pTableLn, const SwTableBox* pTableBx, + sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat ) +{ + SwDoc* pDoc = pOldFormat->GetDoc(); + + SwFrameFormat* pFormat = m_aFrameFormats[ nFormatPos ]; + if( !pFormat ) + { + if( pTableLn ) + pFormat = pDoc->MakeTableLineFormat(); + else + pFormat = pDoc->MakeTableBoxFormat(); + pFormat->SetFormatAttr(*m_aSets[nFormatPos]); + m_aFrameFormats[nFormatPos] = pFormat; + } + + // first re-assign Frames + SwIterator<SwLayoutFrame,SwFormat> aIter( *pOldFormat ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pTableLn ? static_cast<SwRowFrame*>(pLast)->GetTabLine() == pTableLn + : static_cast<SwCellFrame*>(pLast)->GetTabBox() == pTableBx ) + { + pLast->RegisterToFormat(*pFormat); + pLast->InvalidateAll(); + pLast->ReinitializeFrameSizeAttrFlags(); + if ( !pTableLn ) + { + static_cast<SwCellFrame*>(pLast)->SetDerivedVert( false ); + static_cast<SwCellFrame*>(pLast)->CheckDirChange(); + } + } + } + + // than re-assign myself + if ( pTableLn ) + const_cast<SwTableLine*>(pTableLn)->RegisterToFormat( *pFormat ); + else if ( pTableBx ) + const_cast<SwTableBox*>(pTableBx)->RegisterToFormat( *pFormat ); + + if (m_bModifyBox && !pTableLn) + { + const SfxPoolItem& rOld = pOldFormat->GetFormatAttr( RES_BOXATR_FORMAT ), + & rNew = pFormat->GetFormatAttr( RES_BOXATR_FORMAT ); + if( rOld != rNew ) + pFormat->ModifyNotification( &rOld, &rNew ); + } + + if( !pOldFormat->HasWriterListeners() ) + delete pOldFormat; +} + +SaveLine::SaveLine( SaveLine* pPrev, const SwTableLine& rLine, SaveTable& rSTable ) + : pNext( nullptr ) +{ + if( pPrev ) + pPrev->pNext = this; + + nItemSet = rSTable.AddFormat( rLine.GetFrameFormat(), true ); + + pBox = new SaveBox( nullptr, *rLine.GetTabBoxes()[ 0 ], rSTable ); + SaveBox* pBx = pBox; + for( size_t n = 1; n < rLine.GetTabBoxes().size(); ++n ) + pBx = new SaveBox( pBx, *rLine.GetTabBoxes()[ n ], rSTable ); +} + +SaveLine::~SaveLine() +{ + delete pBox; + delete pNext; +} + +void SaveLine::RestoreAttr( SwTableLine& rLine, SaveTable& rSTable ) +{ + rSTable.NewFrameFormat( &rLine, nullptr, nItemSet, rLine.GetFrameFormat() ); + + SaveBox* pBx = pBox; + for( size_t n = 0; n < rLine.GetTabBoxes().size(); ++n, pBx = pBx->pNext ) + { + if( !pBx ) + { + OSL_ENSURE( false, "Number of boxes changed" ); + break; + } + pBx->RestoreAttr( *rLine.GetTabBoxes()[ n ], rSTable ); + } +} + +void SaveLine::SaveContentAttrs( SwDoc* pDoc ) +{ + pBox->SaveContentAttrs( pDoc ); + if( pNext ) + pNext->SaveContentAttrs( pDoc ); +} + +void SaveLine::CreateNew( SwTable& rTable, SwTableBox& rParent, SaveTable& rSTable ) +{ + SwTableLineFormat* pFormat = static_cast<SwTableLineFormat*>(rSTable.m_aFrameFormats[ nItemSet ]); + if( !pFormat ) + { + SwDoc* pDoc = rTable.GetFrameFormat()->GetDoc(); + pFormat = pDoc->MakeTableLineFormat(); + pFormat->SetFormatAttr(*rSTable.m_aSets[nItemSet]); + rSTable.m_aFrameFormats[nItemSet] = pFormat; + } + SwTableLine* pNew = new SwTableLine( pFormat, 1, &rParent ); + + rParent.GetTabLines().push_back( pNew ); + + pBox->CreateNew( rTable, *pNew, rSTable ); + + if( pNext ) + pNext->CreateNew( rTable, rParent, rSTable ); +} + +SaveBox::SaveBox( SaveBox* pPrev, const SwTableBox& rBox, SaveTable& rSTable ) + : pNext( nullptr ), nSttNode( ULONG_MAX ), nRowSpan(0) +{ + Ptrs.pLine = nullptr; + + if( pPrev ) + pPrev->pNext = this; + + nItemSet = rSTable.AddFormat( rBox.GetFrameFormat(), false ); + + if( rBox.GetSttNd() ) + { + nSttNode = rBox.GetSttIdx(); + nRowSpan = rBox.getRowSpan(); + } + else + { + Ptrs.pLine = new SaveLine( nullptr, *rBox.GetTabLines()[ 0 ], rSTable ); + + SaveLine* pLn = Ptrs.pLine; + for( size_t n = 1; n < rBox.GetTabLines().size(); ++n ) + pLn = new SaveLine( pLn, *rBox.GetTabLines()[ n ], rSTable ); + } +} + +SaveBox::~SaveBox() +{ + if( ULONG_MAX == nSttNode ) // no EndBox + delete Ptrs.pLine; + else + delete Ptrs.pContentAttrs; + delete pNext; +} + +void SaveBox::RestoreAttr( SwTableBox& rBox, SaveTable& rSTable ) +{ + rSTable.NewFrameFormat( nullptr, &rBox, nItemSet, rBox.GetFrameFormat() ); + + if( ULONG_MAX == nSttNode ) // no EndBox + { + if( rBox.GetTabLines().empty() ) + { + OSL_ENSURE( false, "Number of lines changed" ); + } + else + { + SaveLine* pLn = Ptrs.pLine; + for( size_t n = 0; n < rBox.GetTabLines().size(); ++n, pLn = pLn->pNext ) + { + if( !pLn ) + { + OSL_ENSURE( false, "Number of lines changed" ); + break; + } + + pLn->RestoreAttr( *rBox.GetTabLines()[ n ], rSTable ); + } + } + } + else if( rBox.GetSttNd() && rBox.GetSttIdx() == nSttNode ) + { + if( Ptrs.pContentAttrs ) + { + SwNodes& rNds = rBox.GetFrameFormat()->GetDoc()->GetNodes(); + sal_uInt16 nSet = 0; + sal_uLong nEnd = rBox.GetSttNd()->EndOfSectionIndex(); + for( sal_uLong n = nSttNode + 1; n < nEnd; ++n ) + { + SwContentNode* pCNd = rNds[ n ]->GetContentNode(); + if( pCNd ) + { + std::shared_ptr<SfxItemSet> pSet( (*Ptrs.pContentAttrs)[ nSet++ ] ); + if( pSet ) + { + sal_uInt16 const *pRstAttr = aSave_BoxContentSet; + while( *pRstAttr ) + { + pCNd->ResetAttr( *pRstAttr, *(pRstAttr+1) ); + pRstAttr += 2; + } + pCNd->SetAttr( *pSet ); + } + else + pCNd->ResetAllAttr(); + } + } + } + } + else + { + OSL_ENSURE( false, "Box not anymore at the same node" ); + } +} + +void SaveBox::SaveContentAttrs( SwDoc* pDoc ) +{ + if( ULONG_MAX == nSttNode ) // no EndBox + { + // continue in current line + Ptrs.pLine->SaveContentAttrs( pDoc ); + } + else + { + sal_uLong nEnd = pDoc->GetNodes()[ nSttNode ]->EndOfSectionIndex(); + Ptrs.pContentAttrs = new SfxItemSets; + for( sal_uLong n = nSttNode + 1; n < nEnd; ++n ) + { + SwContentNode* pCNd = pDoc->GetNodes()[ n ]->GetContentNode(); + if( pCNd ) + { + std::shared_ptr<SfxItemSet> pSet; + if( pCNd->HasSwAttrSet() ) + { + pSet = std::make_shared<SfxItemSet>( pDoc->GetAttrPool(), + aSave_BoxContentSet ); + pSet->Put( *pCNd->GetpSwAttrSet() ); + } + + Ptrs.pContentAttrs->push_back( pSet ); + } + } + } + if( pNext ) + pNext->SaveContentAttrs( pDoc ); +} + +void SaveBox::CreateNew( SwTable& rTable, SwTableLine& rParent, SaveTable& rSTable ) +{ + SwTableBoxFormat* pFormat = static_cast<SwTableBoxFormat*>(rSTable.m_aFrameFormats[ nItemSet ]); + if( !pFormat ) + { + SwDoc* pDoc = rTable.GetFrameFormat()->GetDoc(); + pFormat = pDoc->MakeTableBoxFormat(); + pFormat->SetFormatAttr(*rSTable.m_aSets[nItemSet]); + rSTable.m_aFrameFormats[nItemSet] = pFormat; + } + + if( ULONG_MAX == nSttNode ) // no EndBox + { + SwTableBox* pNew = new SwTableBox( pFormat, 1, &rParent ); + rParent.GetTabBoxes().push_back( pNew ); + + Ptrs.pLine->CreateNew( rTable, *pNew, rSTable ); + } + else + { + // search box for StartNode in old table + SwTableBox* pBox = rTable.GetTableBox( nSttNode ); + if (pBox) + { + SwFrameFormat* pOld = pBox->GetFrameFormat(); + pBox->RegisterToFormat( *pFormat ); + if( !pOld->HasWriterListeners() ) + delete pOld; + + pBox->setRowSpan( nRowSpan ); + + SwTableBoxes* pTBoxes = &pBox->GetUpper()->GetTabBoxes(); + pTBoxes->erase( std::find( pTBoxes->begin(), pTBoxes->end(), pBox ) ); + + pBox->SetUpper( &rParent ); + pTBoxes = &rParent.GetTabBoxes(); + pTBoxes->push_back( pBox ); + } + } + + if( pNext ) + pNext->CreateNew( rTable, rParent, rSTable ); +} + +// UndoObject for attribute changes on table +SwUndoAttrTable::SwUndoAttrTable( const SwTableNode& rTableNd, bool bClearTabCols ) + : SwUndo( SwUndoId::TABLE_ATTR, rTableNd.GetDoc() ), + m_nStartNode( rTableNd.GetIndex() ) +{ + m_bClearTableCol = bClearTabCols; + m_pSaveTable.reset( new SaveTable( rTableNd.GetTable() ) ); +} + +SwUndoAttrTable::~SwUndoAttrTable() +{ +} + +void SwUndoAttrTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nStartNode ]->GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + if (pTableNd) + { + SaveTable* pOrig = new SaveTable( pTableNd->GetTable() ); + m_pSaveTable->RestoreAttr( pTableNd->GetTable() ); + m_pSaveTable.reset( pOrig ); + } + + if( m_bClearTableCol ) + { + ClearFEShellTabCols(rDoc, nullptr); + } +} + +void SwUndoAttrTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoImpl(rContext); +} + +// UndoObject for AutoFormat on Table +SwUndoTableAutoFormat::SwUndoTableAutoFormat( const SwTableNode& rTableNd, + const SwTableAutoFormat& rAFormat ) + : SwUndo( SwUndoId::TABLE_AUTOFMT, rTableNd.GetDoc() ) + , m_TableStyleName(rTableNd.GetTable().GetTableStyleName()) + , m_nStartNode( rTableNd.GetIndex() ) + , m_bSaveContentAttr( false ) + , m_nRepeatHeading(rTableNd.GetTable().GetRowsToRepeat()) +{ + m_pSaveTable.reset( new SaveTable( rTableNd.GetTable() ) ); + + if( rAFormat.IsFont() || rAFormat.IsJustify() ) + { + // then also go over the ContentNodes of the EndBoxes and collect + // all paragraph attributes + m_pSaveTable->SaveContentAttrs( const_cast<SwDoc*>(rTableNd.GetDoc()) ); + m_bSaveContentAttr = true; + } +} + +SwUndoTableAutoFormat::~SwUndoTableAutoFormat() +{ +} + +void SwUndoTableAutoFormat::SaveBoxContent( const SwTableBox& rBox ) +{ + m_Undos.push_back(std::make_shared<SwUndoTableNumFormat>(rBox)); +} + +void +SwUndoTableAutoFormat::UndoRedo(bool const bUndo, ::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nStartNode ]->GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + SwTable& table = pTableNd->GetTable(); + if (table.GetTableStyleName() != m_TableStyleName) + { + OUString const temp(table.GetTableStyleName()); + table.SetTableStyleName(m_TableStyleName); + m_TableStyleName = temp; + } + SaveTable* pOrig = new SaveTable( table ); + // then go also over the ContentNodes of the EndBoxes and collect + // all paragraph attributes + if( m_bSaveContentAttr ) + pOrig->SaveContentAttrs( &rDoc ); + + if (bUndo) + { + for (size_t n = m_Undos.size(); 0 < n; --n) + { + m_Undos.at(n-1)->UndoImpl(rContext); + } + + table.SetRowsToRepeat(m_nRepeatHeading); + } + + m_pSaveTable->RestoreAttr( pTableNd->GetTable(), !bUndo ); + m_pSaveTable.reset( pOrig ); +} + +void SwUndoTableAutoFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + UndoRedo(true, rContext); +} + +void SwUndoTableAutoFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoRedo(false, rContext); +} + +SwUndoTableNdsChg::SwUndoTableNdsChg( SwUndoId nAction, + const SwSelBoxes& rBoxes, + const SwTableNode& rTableNd, + long nMn, long nMx, + sal_uInt16 nCnt, bool bFlg, bool bSmHght ) + : SwUndo( nAction, rTableNd.GetDoc() ), + m_nMin( nMn ), m_nMax( nMx ), + m_nSttNode( rTableNd.GetIndex() ), + m_nCount( nCnt ), + m_bFlag( bFlg ), + m_bSameHeight( bSmHght ) +{ + const SwTable& rTable = rTableNd.GetTable(); + m_pSaveTable.reset( new SaveTable( rTable ) ); + + // and remember selection + ReNewBoxes( rBoxes ); +} + +void SwUndoTableNdsChg::ReNewBoxes( const SwSelBoxes& rBoxes ) +{ + if (rBoxes.size() != m_Boxes.size()) + { + m_Boxes.clear(); + for (size_t n = 0; n < rBoxes.size(); ++n) + { + m_Boxes.insert( rBoxes[n]->GetSttIdx() ); + } + } +} + +SwUndoTableNdsChg::~SwUndoTableNdsChg() +{ +} + +void SwUndoTableNdsChg::SaveNewBoxes( const SwTableNode& rTableNd, + const SwTableSortBoxes& rOld ) +{ + const SwTable& rTable = rTableNd.GetTable(); + const SwTableSortBoxes& rTableBoxes = rTable.GetTabSortBoxes(); + + OSL_ENSURE( ! IsDelBox(), "wrong Action" ); + m_pNewSttNds.reset( new std::set<BoxMove> ); + + size_t i = 0; + for (size_t n = 0; n < rOld.size(); ++i) + { + if( rOld[ n ] == rTableBoxes[ i ] ) + ++n; + else + // new box: insert sorted + m_pNewSttNds->insert( BoxMove(rTableBoxes[ i ]->GetSttIdx()) ); + } + + for( ; i < rTableBoxes.size(); ++i ) + // new box: insert sorted + m_pNewSttNds->insert( BoxMove(rTableBoxes[ i ]->GetSttIdx()) ); +} + +static SwTableLine* lcl_FindTableLine( const SwTable& rTable, + const SwTableBox& rBox ) +{ + SwTableLine* pRet = nullptr; + // i63949: For nested cells we have to take nLineNo - 1, too, not 0! + const SwTableLines &rTableLines = ( rBox.GetUpper()->GetUpper() != nullptr ) ? + rBox.GetUpper()->GetUpper()->GetTabLines() + : rTable.GetTabLines(); + const SwTableLine* pLine = rBox.GetUpper(); + sal_uInt16 nLineNo = rTableLines.GetPos( pLine ); + pRet = rTableLines[nLineNo - 1]; + + return pRet; +} + +static const SwTableLines& lcl_FindParentLines( const SwTable& rTable, + const SwTableBox& rBox ) +{ + const SwTableLines& rRet = + ( rBox.GetUpper()->GetUpper() != nullptr ) ? + rBox.GetUpper()->GetUpper()->GetTabLines() : + rTable.GetTabLines(); + + return rRet; +} + +void SwUndoTableNdsChg::SaveNewBoxes( const SwTableNode& rTableNd, + const SwTableSortBoxes& rOld, + const SwSelBoxes& rBoxes, + const std::vector<sal_uLong> &rNodeCnts ) +{ + const SwTable& rTable = rTableNd.GetTable(); + const SwTableSortBoxes& rTableBoxes = rTable.GetTabSortBoxes(); + + OSL_ENSURE( ! IsDelBox(), "wrong Action" ); + m_pNewSttNds.reset( new std::set<BoxMove> ); + + OSL_ENSURE( rTable.IsNewModel() || rOld.size() + m_nCount * rBoxes.size() == rTableBoxes.size(), + "unexpected boxes" ); + OSL_ENSURE( rOld.size() <= rTableBoxes.size(), "more unexpected boxes" ); + for (size_t n = 0, i = 0; i < rTableBoxes.size(); ++i) + { + if( ( n < rOld.size() ) && + ( rOld[ n ] == rTableBoxes[ i ] ) ) + { + // box already known? Then nothing to be done. + ++n; + } + else + { + // new box found: insert (obey sort order) + const SwTableBox* pBox = rTableBoxes[ i ]; + + // find the source box. It must be one in rBoxes. + // We found the right one if it's in the same column as pBox. + // No, if more than one selected cell in the same column has been split, + // we have to look for the nearest one (i65201)! + const SwTableBox* pSourceBox = nullptr; + const SwTableBox* pCheckBox = nullptr; + const SwTableLine* pBoxLine = pBox->GetUpper(); + sal_uInt16 nLineDiff = lcl_FindParentLines(rTable,*pBox).GetPos(pBoxLine); + sal_uInt16 nLineNo = 0; + for (size_t j = 0; j < rBoxes.size(); ++j) + { + pCheckBox = rBoxes[j]; + if( pCheckBox->GetUpper()->GetUpper() == pBox->GetUpper()->GetUpper() ) + { + const SwTableLine* pCheckLine = pCheckBox->GetUpper(); + sal_uInt16 nCheckLine = lcl_FindParentLines( rTable, *pCheckBox ). + GetPos( pCheckLine ); + if( ( !pSourceBox || nCheckLine > nLineNo ) && nCheckLine < nLineDiff ) + { + nLineNo = nCheckLine; + pSourceBox = pCheckBox; + } + } + } + + // find the line number difference + // (to help determine bNodesMoved flag below) + nLineDiff = nLineDiff - nLineNo; + OSL_ENSURE( pSourceBox, "Split source box not found!" ); + // find out how many nodes the source box used to have + // (to help determine bNodesMoved flag below) + size_t nNdsPos = 0; + while( rBoxes[ nNdsPos ] != pSourceBox ) + ++nNdsPos; + sal_uLong nNodes = rNodeCnts[ nNdsPos ]; + + // When a new table cell is created, it either gets a new + // node, or it gets node(s) from elsewhere. The undo must + // know, of course, and thus we must determine here just + // where pBox's nodes are from: + // If 1) the source box has lost nodes, and + // 2) we're in the node range that got nodes + // then pBox received nodes from elsewhere. + // If bNodesMoved is set for pBox the undo must move the + // boxes back, otherwise it must delete them. + bool bNodesMoved = pSourceBox && + ( nNodes != ( pSourceBox->GetSttNd()->EndOfSectionIndex() - + pSourceBox->GetSttIdx() ) ) + && ( nNodes - 1 > nLineDiff ); + m_pNewSttNds->insert( BoxMove(pBox->GetSttIdx(), bNodesMoved) ); + } + } +} + +void SwUndoTableNdsChg::SaveSection( SwStartNode* pSttNd ) +{ + OSL_ENSURE( IsDelBox(), "wrong Action" ); + if (m_pDelSects == nullptr) + m_pDelSects.reset(new SwUndoSaveSections); + + SwTableNode* pTableNd = pSttNd->FindTableNode(); + std::unique_ptr<SwUndoSaveSection, o3tl::default_delete<SwUndoSaveSection>> pSave(new SwUndoSaveSection); + pSave->SaveSection( SwNodeIndex( *pSttNd )); + + m_pDelSects->push_back(std::move(pSave)); + m_nSttNode = pTableNd->GetIndex(); +} + +void SwUndoTableNdsChg::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode ); + + SwTableNode *const pTableNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + rDoc.getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + CHECK_TABLE( pTableNd->GetTable() ) + + FndBox_ aTmpBox( nullptr, nullptr ); + // ? TL_CHART2: notification or locking of controller required ? + + SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider(); + SwSelBoxes aDelBoxes; + std::vector< std::pair<SwTableBox *, sal_uLong> > aDelNodes; + if( IsDelBox() ) + { + // Trick: add missing boxes in any line, they will be connected + // correctly when calling CreateNew + SwTableBox* pCpyBox = pTableNd->GetTable().GetTabSortBoxes()[0]; + SwTableBoxes& rLnBoxes = pCpyBox->GetUpper()->GetTabBoxes(); + + // restore sections + for (size_t n = m_pDelSects->size(); n; ) + { + SwUndoSaveSection *const pSave = (*m_pDelSects)[ --n ].get(); + pSave->RestoreSection( &rDoc, &aIdx, SwTableBoxStartNode ); + if( pSave->GetHistory() ) + pSave->GetHistory()->Rollback( &rDoc ); + SwTableBox* pBox = new SwTableBox( static_cast<SwTableBoxFormat*>(pCpyBox->GetFrameFormat()), aIdx, + pCpyBox->GetUpper() ); + rLnBoxes.push_back( pBox ); + } + m_pDelSects->clear(); + } + else if( !m_pNewSttNds->empty() ) + { + // Then the nodes have be moved and not deleted! + // But for that we need a temp array. + std::vector<BoxMove> aTmp( m_pNewSttNds->begin(), m_pNewSttNds->end() ); + + // backwards + for (size_t n = aTmp.size(); n > 0 ; ) + { + --n; + // delete box from table structure + sal_uLong nIdx = aTmp[n].index; + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nIdx ); + OSL_ENSURE( pBox, "Where is my TableBox?" ); + + // TL_CHART2: notify chart about box to be removed + if (pPCD) + pPCD->DeleteBox( &pTableNd->GetTable(), *pBox ); + + // insert _before_ deleting the section - otherwise the box + // has no start node so all boxes sort equal in SwSelBoxes + aDelBoxes.insert(pBox); + + if( aTmp[n].hasMoved ) + { + SwNodeRange aRg( *pBox->GetSttNd(), 1, + *pBox->GetSttNd()->EndOfSectionNode() ); + + SwTableLine* pLine = lcl_FindTableLine( pTableNd->GetTable(), *pBox ); + SwNodeIndex aInsPos( *(pLine->GetTabBoxes()[0]->GetSttNd()), 2 ); + + // adjust all StartNode indices + size_t i = n; + sal_uLong nSttIdx = aInsPos.GetIndex() - 2, + nNdCnt = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); + while( i && aTmp[ --i ].index > nSttIdx ) + aTmp[ i ].index += nNdCnt; + + // first delete box + delete pBox; + // than move nodes + rDoc.GetNodes().MoveNodes( aRg, rDoc.GetNodes(), aInsPos, false ); + } + else + { + aDelNodes.emplace_back(pBox, nIdx); + } + } + } + else + { + // Remove nodes from nodes array (backwards!) + std::set<BoxMove>::reverse_iterator it; + for( it = m_pNewSttNds->rbegin(); it != m_pNewSttNds->rend(); ++it ) + { + sal_uLong nIdx = (*it).index; + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nIdx ); + OSL_ENSURE( pBox, "Where's my table box?" ); + // TL_CHART2: notify chart about box to be removed + if (pPCD) + pPCD->DeleteBox( &pTableNd->GetTable(), *pBox ); + aDelBoxes.insert(pBox); + aDelNodes.emplace_back(pBox, nIdx); + } + } + + // fdo#57197: before deleting the SwTableBoxes, delete the SwTabFrames + aTmpBox.SetTableLines(aDelBoxes, pTableNd->GetTable()); + aTmpBox.DelFrames(pTableNd->GetTable()); + + // do this _after_ deleting Frames because disposing SwAccessible requires + // connection to the nodes, see SwAccessibleChild::IsAccessible() + for (const std::pair<SwTableBox *, sal_uLong> & rDelNode : aDelNodes) + { + // first disconnect box from node, otherwise ~SwTableBox would + // access pBox->pSttNd, deleted by DeleteSection + rDelNode.first->RemoveFromTable(); + rDoc.getIDocumentContentOperations().DeleteSection(rDoc.GetNodes()[ rDelNode.second ]); + } + + // Remove boxes from table structure + for( size_t n = 0; n < aDelBoxes.size(); ++n ) + { + SwTableBox* pCurrBox = aDelBoxes[n]; + SwTableBoxes* pTBoxes = &pCurrBox->GetUpper()->GetTabBoxes(); + pTBoxes->erase( std::find( pTBoxes->begin(), pTBoxes->end(), pCurrBox ) ); + delete pCurrBox; + } + + m_pSaveTable->CreateNew( pTableNd->GetTable(), true, false ); + + // TL_CHART2: need to inform chart of probably changed cell names + rDoc.UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + + if( IsDelBox() ) + m_nSttNode = pTableNd->GetIndex(); + ClearFEShellTabCols(rDoc, nullptr); + CHECK_TABLE( pTableNd->GetTable() ) +} + +void SwUndoTableNdsChg::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nSttNode ]->GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + CHECK_TABLE( pTableNd->GetTable() ) + + SwSelBoxes aSelBoxes; + for (const auto& rBox : m_Boxes) + { + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( rBox ); + aSelBoxes.insert( pBox ); + } + + // create SelBoxes and call InsertCell/-Row/SplitTable + switch( GetId() ) + { + case SwUndoId::TABLE_INSCOL: + rDoc.InsertCol( aSelBoxes, m_nCount, m_bFlag ); + break; + + case SwUndoId::TABLE_INSROW: + rDoc.InsertRow( aSelBoxes, m_nCount, m_bFlag ); + break; + + case SwUndoId::TABLE_SPLIT: + rDoc.SplitTable( aSelBoxes, m_bFlag, m_nCount, m_bSameHeight ); + break; + case SwUndoId::TABLE_DELBOX: + case SwUndoId::ROW_DELETE: + case SwUndoId::COL_DELETE: + { + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + rDoc.getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + SwTable &rTable = pTableNd->GetTable(); + if( m_nMax > m_nMin && rTable.IsNewModel() ) + rTable.PrepareDeleteCol( m_nMin, m_nMax ); + rTable.DeleteSel( &rDoc, aSelBoxes, nullptr, this, true, true ); + m_nSttNode = pTableNd->GetIndex(); + } + break; + default: + ; + } + ClearFEShellTabCols(rDoc, nullptr); + CHECK_TABLE( pTableNd->GetTable() ) +} + +SwUndoTableMerge::SwUndoTableMerge( const SwPaM& rTableSel ) + : SwUndo( SwUndoId::TABLE_MERGE, rTableSel.GetDoc() ), SwUndRng( rTableSel ) +{ + const SwTableNode* pTableNd = rTableSel.GetNode().FindTableNode(); + OSL_ENSURE( pTableNd, "Where is the TableNode?" ); + m_pSaveTable.reset( new SaveTable( pTableNd->GetTable() ) ); + m_nTableNode = pTableNd->GetIndex(); +} + +SwUndoTableMerge::~SwUndoTableMerge() +{ + m_pSaveTable.reset(); + m_vMoves.clear(); + m_pHistory.reset(); +} + +void SwUndoTableMerge::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aIdx( rDoc.GetNodes(), m_nTableNode ); + + SwTableNode *const pTableNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + rDoc.getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + // ? TL_CHART2: notification or locking of controller required ? + + // 1. restore deleted boxes: + // Trick: add missing boxes in any line, they will be connected + // correctly when calling CreateNew + SwTableBox *pBox, *pCpyBox = pTableNd->GetTable().GetTabSortBoxes()[0]; + SwTableBoxes& rLnBoxes = pCpyBox->GetUpper()->GetTabBoxes(); + + CHECKTABLE(pTableNd->GetTable()) + + SwSelBoxes aSelBoxes; + SwTextFormatColl* pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ); + + for (const auto& rBox : m_Boxes) + { + aIdx = rBox; + SwStartNode* pSttNd = rDoc.GetNodes().MakeTextSection( aIdx, + SwTableBoxStartNode, pColl ); + pBox = new SwTableBox( static_cast<SwTableBoxFormat*>(pCpyBox->GetFrameFormat()), *pSttNd, + pCpyBox->GetUpper() ); + rLnBoxes.push_back( pBox ); + + aSelBoxes.insert( pBox ); + } + + CHECKTABLE(pTableNd->GetTable()) + + SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider(); + // 2. deleted the inserted boxes + // delete nodes (from last to first) + for( size_t n = m_aNewStartNodes.size(); n; ) + { + // remove box from table structure + sal_uLong nIdx = m_aNewStartNodes[ --n ]; + + if( !nIdx && n ) + { + nIdx = m_aNewStartNodes[ --n ]; + pBox = pTableNd->GetTable().GetTableBox( nIdx ); + OSL_ENSURE( pBox, "Where is my TableBox?" ); + + if( !m_pSaveTable->IsNewModel() ) + rDoc.GetNodes().MakeTextNode( SwNodeIndex( + *pBox->GetSttNd()->EndOfSectionNode() ), pColl ); + + // this was the separator -> restore moved ones + for (size_t i = m_vMoves.size(); i; ) + { + SwTextNode* pTextNd = nullptr; + sal_Int32 nDelPos = 0; + SwUndoMove *const pUndo = m_vMoves[ --i ].get(); + if( !pUndo->IsMoveRange() ) + { + pTextNd = rDoc.GetNodes()[ pUndo->GetDestSttNode() ]->GetTextNode(); + nDelPos = pUndo->GetDestSttContent() - 1; + } + pUndo->UndoImpl(rContext); + if( pUndo->IsMoveRange() ) + { + // delete the unnecessary node + aIdx = pUndo->GetEndNode(); + SwContentNode *pCNd = aIdx.GetNode().GetContentNode(); + if( pCNd ) + { + SwNodeIndex aTmp( aIdx, -1 ); + SwContentNode *pMove = aTmp.GetNode().GetContentNode(); + if( pMove ) + pCNd->MoveTo( *pMove ); + } + rDoc.GetNodes().Delete( aIdx ); + } + else if( pTextNd ) + { + // also delete not needed attributes + SwIndex aTmpIdx( pTextNd, nDelPos ); + if( pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count() ) + pTextNd->RstTextAttr( aTmpIdx, pTextNd->GetText().getLength() - nDelPos + 1 ); + // delete separator + pTextNd->EraseText( aTmpIdx, 1 ); + } + } + nIdx = pBox->GetSttIdx(); + } + else + pBox = pTableNd->GetTable().GetTableBox( nIdx ); + + if( !m_pSaveTable->IsNewModel() ) + { + // TL_CHART2: notify chart about box to be removed + if (pPCD) + pPCD->DeleteBox( &pTableNd->GetTable(), *pBox ); + + SwTableBoxes* pTBoxes = &pBox->GetUpper()->GetTabBoxes(); + pTBoxes->erase( std::find(pTBoxes->begin(), pTBoxes->end(), pBox ) ); + + // delete indices from section + { + SwNodeIndex aTmpIdx( *pBox->GetSttNd() ); + SwDoc::CorrAbs( SwNodeIndex( aTmpIdx, 1 ), + SwNodeIndex( *aTmpIdx.GetNode().EndOfSectionNode() ), + SwPosition( aTmpIdx, SwIndex( nullptr, 0 )), true ); + } + + delete pBox; + rDoc.getIDocumentContentOperations().DeleteSection( rDoc.GetNodes()[ nIdx ] ); + } + } + CHECKTABLE(pTableNd->GetTable()) + + m_pSaveTable->CreateNew( pTableNd->GetTable(), true, false ); + + // TL_CHART2: need to inform chart of probably changed cell names + rDoc.UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + pPam->DeleteMark(); + pPam->GetPoint()->nNode = m_nSttNode; + pPam->GetPoint()->nContent.Assign( pPam->GetContentNode(), m_nSttContent ); + pPam->SetMark(); + pPam->DeleteMark(); + + CHECKTABLE(pTableNd->GetTable()) + ClearFEShellTabCols(rDoc, nullptr); +} + +void SwUndoTableMerge::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rDoc.MergeTable(rPam); +} + +void SwUndoTableMerge::MoveBoxContent( SwDoc* pDoc, SwNodeRange& rRg, SwNodeIndex& rPos ) +{ + SwNodeIndex aTmp( rRg.aStart, -1 ), aTmp2( rPos, -1 ); + std::unique_ptr<SwUndoMove> pUndo(new SwUndoMove( pDoc, rRg, rPos )); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().MoveNodeRange( rRg, rPos, m_pSaveTable->IsNewModel() ? + SwMoveFlags::NO_DELFRMS : + SwMoveFlags::DEFAULT ); + ++aTmp; + ++aTmp2; + pUndo->SetDestRange( aTmp2, rPos, aTmp ); + + m_vMoves.push_back(std::move(pUndo)); +} + +void SwUndoTableMerge::SetSelBoxes( const SwSelBoxes& rBoxes ) +{ + // memorize selection + for (size_t n = 0; n < rBoxes.size(); ++n) + { + m_Boxes.insert(rBoxes[n]->GetSttIdx()); + } + + // as separator for inserts of new boxes after shifting + m_aNewStartNodes.push_back( sal_uLong(0) ); + + // The new table model does not delete overlapped cells (by row span), + // so the rBoxes array might be empty even some cells have been merged. + if( !rBoxes.empty() ) + m_nTableNode = rBoxes[ 0 ]->GetSttNd()->FindTableNode()->GetIndex(); +} + +void SwUndoTableMerge::SaveCollection( const SwTableBox& rBox ) +{ + if( !m_pHistory ) + m_pHistory.reset(new SwHistory); + + SwNodeIndex aIdx( *rBox.GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + + m_pHistory->Add( pCNd->GetFormatColl(), aIdx.GetIndex(), pCNd->GetNodeType()); + if( pCNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pCNd->GetpSwAttrSet(), aIdx.GetIndex() ); +} + +SwUndoTableNumFormat::SwUndoTableNumFormat( const SwTableBox& rBox, + const SfxItemSet* pNewSet ) + : SwUndo(SwUndoId::TBLNUMFMT, rBox.GetFrameFormat()->GetDoc()) + , m_nFormatIdx(getSwDefaultTextFormat()) + , m_nNewFormatIdx(0) + , m_fNum(0.0) + , m_fNewNum(0.0) + , m_bNewFormat(false) + , m_bNewFormula(false) + , m_bNewValue(false) +{ + m_nNode = rBox.GetSttIdx(); + + m_nNodePos = rBox.IsValidNumTextNd( nullptr == pNewSet ); + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + + if( ULONG_MAX != m_nNodePos ) + { + SwTextNode* pTNd = pDoc->GetNodes()[ m_nNodePos ]->GetTextNode(); + + m_pHistory.reset(new SwHistory); + SwRegHistory aRHst( *rBox.GetSttNd(), m_pHistory.get() ); + // always save all text atttibutes because of possibly overlapping + // areas of on/off + m_pHistory->CopyAttr( pTNd->GetpSwpHints(), m_nNodePos, 0, + pTNd->GetText().getLength(), true ); + + if( pTNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTNd->GetpSwAttrSet(), m_nNodePos ); + + m_aStr = pTNd->GetText(); + if( pTNd->GetpSwpHints() ) + pTNd->GetpSwpHints()->DeRegister(); + } + + m_pBoxSet.reset( new SfxItemSet( pDoc->GetAttrPool(), aTableBoxSetRange ) ); + m_pBoxSet->Put( rBox.GetFrameFormat()->GetAttrSet() ); + + if( pNewSet ) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pNewSet->GetItemState( RES_BOXATR_FORMAT, + false, &pItem )) + { + m_bNewFormat = true; + m_nNewFormatIdx = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + } + if( SfxItemState::SET == pNewSet->GetItemState( RES_BOXATR_FORMULA, + false, &pItem )) + { + m_bNewFormula = true; + m_aNewFormula = static_cast<const SwTableBoxFormula*>(pItem)->GetFormula(); + } + if( SfxItemState::SET == pNewSet->GetItemState( RES_BOXATR_VALUE, + false, &pItem )) + { + m_bNewValue = true; + m_fNewNum = static_cast<const SwTableBoxValue*>(pItem)->GetValue(); + } + } + + // is a history needed at all? + if (m_pHistory && !m_pHistory->Count()) + { + m_pHistory.reset(); + } +} + +SwUndoTableNumFormat::~SwUndoTableNumFormat() +{ + m_pHistory.reset(); + m_pBoxSet.reset(); +} + +void SwUndoTableNumFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + OSL_ENSURE( m_pBoxSet, "Where's the stored item set?" ); + + SwDoc & rDoc = rContext.GetDoc(); + SwStartNode* pSttNd = rDoc.GetNodes()[ m_nNode ]-> + FindSttNodeByType( SwTableBoxStartNode ); + OSL_ENSURE( pSttNd, "without StartNode no TableBox" ); + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "found no TableBox" ); + + SwTableBoxFormat* pFormat = rDoc.MakeTableBoxFormat(); + pFormat->SetFormatAttr( *m_pBoxSet ); + pBox->ChgFrameFormat( pFormat ); + + if( ULONG_MAX == m_nNodePos ) + return; + + SwTextNode* pTextNd = rDoc.GetNodes()[ m_nNodePos ]->GetTextNode(); + // If more than one node was deleted then all "node" attributes were also + // saved + if( pTextNd->HasSwAttrSet() ) + pTextNd->ResetAllAttr(); + + if( pTextNd->GetpSwpHints() && !m_aStr.isEmpty() ) + pTextNd->ClearSwpHintsArr( true ); + + // ChgTextToNum(..) only acts when the strings are different. We need to do + // the same here. + if( pTextNd->GetText() != m_aStr ) + { + rDoc.getIDocumentRedlineAccess().DeleteRedline( *( pBox->GetSttNd() ), false, RedlineType::Any ); + + SwIndex aIdx( pTextNd, 0 ); + if( !m_aStr.isEmpty() ) + { + pTextNd->EraseText( aIdx ); + pTextNd->InsertText( m_aStr, aIdx, + SwInsertFlags::NOHINTEXPAND ); + } + } + + if( m_pHistory ) + { + sal_uInt16 nTmpEnd = m_pHistory->GetTmpEnd(); + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( nTmpEnd ); + } + + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + pPam->DeleteMark(); + pPam->GetPoint()->nNode = m_nNode + 1; + pPam->GetPoint()->nContent.Assign( pTextNd, 0 ); +} + +namespace { + +/** switch the RedlineFlags on the given document, using + * SetRedlineFlags_intern. This class set the mode in the constructor, + * and changes it back in the destructor, i.e. it uses the + * initialization-is-resource-acquisition idiom. + */ +class RedlineFlagsInternGuard +{ + SwDoc& mrDoc; + RedlineFlags meOldRedlineFlags; + +public: + RedlineFlagsInternGuard( + SwDoc& rDoc, // change mode of this document + RedlineFlags eNewRedlineFlags, // new redline mode + RedlineFlags eRedlineFlagsMask /*change only bits set in this mask*/); + + ~RedlineFlagsInternGuard(); +}; + +} + +RedlineFlagsInternGuard::RedlineFlagsInternGuard( + SwDoc& rDoc, + RedlineFlags eNewRedlineFlags, + RedlineFlags eRedlineFlagsMask ) + : mrDoc( rDoc ), + meOldRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ) +{ + mrDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( meOldRedlineFlags & ~eRedlineFlagsMask ) | + ( eNewRedlineFlags & eRedlineFlagsMask ) ); +} + +RedlineFlagsInternGuard::~RedlineFlagsInternGuard() +{ + mrDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( meOldRedlineFlags ); +} + +void SwUndoTableNumFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // Could the box be changed? + if( !m_pBoxSet ) + return ; + + SwDoc & rDoc = rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + pPam->GetPoint()->nNode = m_nNode; + + SwNode * pNd = & pPam->GetPoint()->nNode.GetNode(); + SwStartNode* pSttNd = pNd->FindSttNodeByType( SwTableBoxStartNode ); + assert(pSttNd && "without StartNode no TableBox"); + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "found no TableBox" ); + + SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat(); + if( m_bNewFormat || m_bNewFormula || m_bNewValue ) + { + SfxItemSet aBoxSet( rDoc.GetAttrPool(), + svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{} ); + + // Resetting attributes is not enough. In addition, take care that the + // text will be also formatted correctly. + pBoxFormat->LockModify(); + + if( m_bNewFormula ) + aBoxSet.Put( SwTableBoxFormula( m_aNewFormula )); + else + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + if( m_bNewFormat ) + aBoxSet.Put( SwTableBoxNumFormat( m_nNewFormatIdx )); + else + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT ); + if( m_bNewValue ) + aBoxSet.Put( SwTableBoxValue( m_fNewNum )); + else + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + pBoxFormat->UnlockModify(); + + // dvo: When redlining is (was) enabled, setting the attribute + // will also change the cell content. To allow this, the + // RedlineFlags::Ignore flag must be removed during Redo. #108450# + RedlineFlagsInternGuard aGuard( rDoc, RedlineFlags::NONE, RedlineFlags::Ignore ); + pBoxFormat->SetFormatAttr( aBoxSet ); + } + else if( getSwDefaultTextFormat() != m_nFormatIdx ) + { + SfxItemSet aBoxSet( rDoc.GetAttrPool(), + svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{} ); + + aBoxSet.Put( SwTableBoxNumFormat( m_nFormatIdx )); + aBoxSet.Put( SwTableBoxValue( m_fNum )); + + // Resetting attributes is not enough. In addition, take care that the + // text will be also formatted correctly. + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + pBoxFormat->UnlockModify(); + + // dvo: When redlining is (was) enabled, setting the attribute + // will also change the cell content. To allow this, the + // RedlineFlags::Ignore flag must be removed during Redo. #108450# + RedlineFlagsInternGuard aGuard( rDoc, RedlineFlags::NONE, RedlineFlags::Ignore ); + pBoxFormat->SetFormatAttr( aBoxSet ); + } + else + { + // it's no number + + // Resetting attributes is not enough. In addition, take care that the + // text will be also formatted correctly. + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + } + + if( m_bNewFormula ) + { + // No matter what was set, an update of the table is always a good idea + SwTableFormulaUpdate aTableUpdate( &pSttNd->FindTableNode()->GetTable() ); + rDoc.getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); + } + + if( !pNd->IsContentNode() ) + pNd = rDoc.GetNodes().GoNext( &pPam->GetPoint()->nNode ); + pPam->GetPoint()->nContent.Assign( static_cast<SwContentNode*>(pNd), 0 ); +} + +void SwUndoTableNumFormat::SetBox( const SwTableBox& rBox ) +{ + m_nNode = rBox.GetSttIdx(); +} + +UndoTableCpyTable_Entry::UndoTableCpyTable_Entry( const SwTableBox& rBox ) + : nBoxIdx( rBox.GetSttIdx() ), nOffset( 0 ), + bJoin( false ) +{ +} + +SwUndoTableCpyTable::SwUndoTableCpyTable(const SwDoc* pDoc) + : SwUndo( SwUndoId::TBLCPYTBL, pDoc ) +{ +} + +SwUndoTableCpyTable::~SwUndoTableCpyTable() +{ + m_vArr.clear(); + m_pInsRowUndo.reset(); +} + +void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + DEBUG_REDLINE( &rDoc ) + + SwTableNode* pTableNd = nullptr; + for (size_t n = m_vArr.size(); n; ) + { + UndoTableCpyTable_Entry *const pEntry = m_vArr[ --n ].get(); + sal_uLong nSttPos = pEntry->nBoxIdx + pEntry->nOffset; + SwStartNode* pSNd = rDoc.GetNodes()[ nSttPos ]->StartOfSectionNode(); + if( !pTableNd ) + pTableNd = pSNd->FindTableNode(); + + SwTableBox& rBox = *pTableNd->GetTable().GetTableBox( nSttPos ); + + SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); + rDoc.GetNodes().MakeTextNode( aInsIdx, rDoc.GetDfltTextFormatColl() ); + + // b62341295: Redline for copying tables + const SwNode *pEndNode = rBox.GetSttNd()->EndOfSectionNode(); + SwPaM aPam( aInsIdx.GetNode(), *pEndNode ); + std::unique_ptr<SwUndoDelete> pUndo; + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { + bool bDeleteCompleteParagraph = false; + bool bShiftPam = false; + // There are a couple of different situations to consider during redlining + if( pEntry->pUndo ) + { + SwUndoDelete *const pUndoDelete = + dynamic_cast<SwUndoDelete*>(pEntry->pUndo.get()); + SwUndoRedlineDelete *const pUndoRedlineDelete = + dynamic_cast<SwUndoRedlineDelete*>(pEntry->pUndo.get()); + assert(pUndoDelete || pUndoRedlineDelete); + if (pUndoRedlineDelete) + { + // The old content was not empty or he has been merged with the new content + bDeleteCompleteParagraph = !pEntry->bJoin; // bJoin is set when merged + // Set aTmpIdx to the beginning of the old content + SwNodeIndex aTmpIdx( *pEndNode, + pUndoRedlineDelete->NodeDiff()-1 ); + SwTextNode *pText = aTmpIdx.GetNode().GetTextNode(); + if( pText ) + { + aPam.GetPoint()->nNode = *pText; + aPam.GetPoint()->nContent.Assign( pText, + pUndoRedlineDelete->ContentStart() ); + } + else + *aPam.GetPoint() = SwPosition( aTmpIdx ); + } + else if (pUndoDelete && pUndoDelete->IsDelFullPara()) + { + // When the old content was an empty paragraph, but could not be joined + // with the new content (e.g. because of a section or table) + // We "save" the aPam.Point, we go one step backwards (because later on the + // empty paragraph will be inserted by the undo) and set the "ShiftPam-flag + // for step forward later on. + bDeleteCompleteParagraph = true; + bShiftPam = true; + SwNodeIndex aTmpIdx( *pEndNode, -1 ); + SwTextNode *pText = aTmpIdx.GetNode().GetTextNode(); + if( pText ) + { + aPam.GetPoint()->nNode = *pText; + aPam.GetPoint()->nContent.Assign( pText, 0 ); + } + else + *aPam.GetPoint() = SwPosition( aTmpIdx ); + } + } + rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any ); + + if( pEntry->pUndo ) + { + pEntry->pUndo->UndoImpl(rContext); + pEntry->pUndo.reset(); + } + if( bShiftPam ) + { + // The aPam.Point is at the moment at the last position of the new content and has to be + // moved to the first position of the old content for the SwUndoDelete operation + SwNodeIndex aTmpIdx( aPam.GetPoint()->nNode, 1 ); + SwTextNode *pText = aTmpIdx.GetNode().GetTextNode(); + if( pText ) + { + aPam.GetPoint()->nNode = *pText; + aPam.GetPoint()->nContent.Assign( pText, 0 ); + } + else + *aPam.GetPoint() = SwPosition( aTmpIdx ); + } + pUndo = std::make_unique<SwUndoDelete>( aPam, bDeleteCompleteParagraph, true ); + } + else + { + pUndo = std::make_unique<SwUndoDelete>( aPam, true ); + if( pEntry->pUndo ) + { + pEntry->pUndo->UndoImpl(rContext); + pEntry->pUndo.reset(); + } + } + pEntry->pUndo = std::move(pUndo); + + aInsIdx = rBox.GetSttIdx() + 1; + rDoc.GetNodes().Delete( aInsIdx ); + + SfxItemSet aTmpSet( + rDoc.GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + aTmpSet.Put( rBox.GetFrameFormat()->GetAttrSet() ); + if( aTmpSet.Count() ) + { + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + pBoxFormat->ResetFormatAttr( RES_VERT_ORIENT ); + } + + if( pEntry->pBoxNumAttr ) + { + rBox.ClaimFrameFormat()->SetFormatAttr( *pEntry->pBoxNumAttr ); + pEntry->pBoxNumAttr.reset(); + } + + if( aTmpSet.Count() ) + { + pEntry->pBoxNumAttr = std::make_unique<SfxItemSet>( + rDoc.GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + pEntry->pBoxNumAttr->Put( aTmpSet ); + } + + pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; + } + + if( m_pInsRowUndo ) + { + m_pInsRowUndo->UndoImpl(rContext); + } + DEBUG_REDLINE( &rDoc ) +} + +void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + DEBUG_REDLINE( &rDoc ) + + if( m_pInsRowUndo ) + { + m_pInsRowUndo->RedoImpl(rContext); + } + + SwTableNode* pTableNd = nullptr; + for (size_t n = 0; n < m_vArr.size(); ++n) + { + UndoTableCpyTable_Entry *const pEntry = m_vArr[ n ].get(); + sal_uLong nSttPos = pEntry->nBoxIdx + pEntry->nOffset; + SwStartNode* pSNd = rDoc.GetNodes()[ nSttPos ]->StartOfSectionNode(); + if( !pTableNd ) + pTableNd = pSNd->FindTableNode(); + + SwTableBox& rBox = *pTableNd->GetTable().GetTableBox( nSttPos ); + + SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); + + // b62341295: Redline for copying tables - Start. + rDoc.GetNodes().MakeTextNode( aInsIdx, rDoc.GetDfltTextFormatColl() ); + SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode()); + std::unique_ptr<SwUndo> pUndo = IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ? nullptr : std::make_unique<SwUndoDelete>( aPam, true ); + if( pEntry->pUndo ) + { + pEntry->pUndo->UndoImpl(rContext); + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { + // PrepareRedline has to be called with the beginning of the old content + // When new and old content has been joined, the rIter.pAktPam has been set + // by the Undo operation to this point. + // Otherwise aInsIdx has been moved during the Undo operation + if( pEntry->bJoin ) + { + SwPaM const& rLastPam = + rContext.GetCursorSupplier().GetCurrentShellCursor(); + pUndo = PrepareRedline( &rDoc, rBox, *rLastPam.GetPoint(), + pEntry->bJoin, true ); + } + else + { + SwPosition aTmpPos( aInsIdx ); + pUndo = PrepareRedline( &rDoc, rBox, aTmpPos, pEntry->bJoin, true ); + } + } + pEntry->pUndo.reset(); + } + pEntry->pUndo = std::move(pUndo); + // b62341295: Redline for copying tables - End. + + aInsIdx = rBox.GetSttIdx() + 1; + rDoc.GetNodes().Delete( aInsIdx ); + + SfxItemSet aTmpSet( + rDoc.GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + aTmpSet.Put( rBox.GetFrameFormat()->GetAttrSet() ); + if( aTmpSet.Count() ) + { + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + pBoxFormat->ResetFormatAttr( RES_VERT_ORIENT ); + } + if( pEntry->pBoxNumAttr ) + { + rBox.ClaimFrameFormat()->SetFormatAttr( *pEntry->pBoxNumAttr ); + pEntry->pBoxNumAttr.reset(); + } + + if( aTmpSet.Count() ) + { + pEntry->pBoxNumAttr = std::make_unique<SfxItemSet>( + rDoc.GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + pEntry->pBoxNumAttr->Put( aTmpSet ); + } + + pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; + } + DEBUG_REDLINE( &rDoc ) +} + +void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent ) +{ + if (!m_vArr.empty() && !bDelContent) + return; + + UndoTableCpyTable_Entry* pEntry = new UndoTableCpyTable_Entry( rBox ); + m_vArr.push_back(std::unique_ptr<UndoTableCpyTable_Entry>(pEntry)); + + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + DEBUG_REDLINE( pDoc ) + if( bDelContent ) + { + SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); + pDoc->GetNodes().MakeTextNode( aInsIdx, pDoc->GetDfltTextFormatColl() ); + SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode() ); + + if( !pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pEntry->pUndo = std::make_unique<SwUndoDelete>( aPam, true ); + } + + pEntry->pBoxNumAttr = std::make_unique<SfxItemSet>( + pDoc->GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + pEntry->pBoxNumAttr->Put( rBox.GetFrameFormat()->GetAttrSet() ); + if( !pEntry->pBoxNumAttr->Count() ) + { + pEntry->pBoxNumAttr.reset(); + } + DEBUG_REDLINE( pDoc ) +} + +void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, bool bDelContent ) +{ + UndoTableCpyTable_Entry *const pEntry = m_vArr.back().get(); + + // If the content was deleted then remove also the temporarily created node + if( bDelContent ) + { + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + DEBUG_REDLINE( pDoc ) + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPosition aTmpPos( rIdx ); + pEntry->pUndo = PrepareRedline( pDoc, rBox, aTmpPos, pEntry->bJoin, false ); + } + SwNodeIndex aDelIdx( *rBox.GetSttNd(), 1 ); + rBox.GetFrameFormat()->GetDoc()->GetNodes().Delete( aDelIdx ); + DEBUG_REDLINE( pDoc ) + } + + pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; +} + +// PrepareRedline is called from AddBoxAfter() and from Redo() in slightly different situations. +// bRedo is set by calling from Redo() +// rJoin is false by calling from AddBoxAfter() and will be set if the old and new content has +// been merged. +// rJoin is true if Redo() is calling and the content has already been merged + +std::unique_ptr<SwUndo> SwUndoTableCpyTable::PrepareRedline( SwDoc* pDoc, const SwTableBox& rBox, + const SwPosition& rPos, bool& rJoin, bool bRedo ) +{ + std::unique_ptr<SwUndo> pUndo; + // b62341295: Redline for copying tables + // What's to do? + // Mark the cell content before rIdx as insertion, + // mark the cell content behind rIdx as deletion + // merge text nodes at rIdx if possible + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld | RedlineFlags::DontCombineRedlines ) & ~RedlineFlags::Ignore ); + SwPosition aInsertEnd( rPos ); + SwTextNode* pText; + if( !rJoin ) + { + // If the content is not merged, the end of the insertion is at the end of the node + // _before_ the given position rPos + --aInsertEnd.nNode; + pText = aInsertEnd.nNode.GetNode().GetTextNode(); + if( pText ) + { + aInsertEnd.nContent.Assign(pText, pText->GetText().getLength()); + if( !bRedo && rPos.nNode.GetNode().GetTextNode() ) + { // Try to merge, if not called by Redo() + rJoin = true; + pText->JoinNext(); + } + } + else + aInsertEnd.nContent.Assign(nullptr, 0); + } + // For joined (merged) contents the start of deletion and end of insertion are identical + // otherwise adjacent nodes. + SwPosition aDeleteStart( rJoin ? aInsertEnd : rPos ); + if( !rJoin ) + { + pText = aDeleteStart.nNode.GetNode().GetTextNode(); + if( pText ) + aDeleteStart.nContent.Assign( pText, 0 ); + } + SwPosition aCellEnd( SwNodeIndex( *rBox.GetSttNd()->EndOfSectionNode(), -1 ) ); + pText = aCellEnd.nNode.GetNode().GetTextNode(); + if( pText ) + aCellEnd.nContent.Assign(pText, pText->GetText().getLength()); + if( aDeleteStart != aCellEnd ) + { // If the old (deleted) part is not empty, here we are... + SwPaM aDeletePam( aDeleteStart, aCellEnd ); + pUndo = std::make_unique<SwUndoRedlineDelete>( aDeletePam, SwUndoId::DELETE ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDeletePam ), true ); + } + else if( !rJoin ) // If the old part is empty and joined, we are finished + { // if it is not joined, we have to delete this empty paragraph + aCellEnd = SwPosition( + SwNodeIndex( *rBox.GetSttNd()->EndOfSectionNode() )); + SwPaM aTmpPam( aDeleteStart, aCellEnd ); + pUndo = std::make_unique<SwUndoDelete>( aTmpPam, true ); + } + SwPosition aCellStart( SwNodeIndex( *rBox.GetSttNd(), 2 ) ); + pText = aCellStart.nNode.GetNode().GetTextNode(); + if( pText ) + aCellStart.nContent.Assign( pText, 0 ); + if( aCellStart != aInsertEnd ) // An empty insertion will not been marked + { + SwPaM aTmpPam( aCellStart, aInsertEnd ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aTmpPam ), true ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + return pUndo; +} + +bool SwUndoTableCpyTable::InsertRow( SwTable& rTable, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt ) +{ + SwTableNode* pTableNd = const_cast<SwTableNode*>(rTable.GetTabSortBoxes()[0]-> + GetSttNd()->FindTableNode()); + + m_pInsRowUndo.reset( new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW, rBoxes, *pTableNd, + 0, 0, nCnt, true, false ) ); + SwTableSortBoxes aTmpLst( rTable.GetTabSortBoxes() ); + + bool bRet = rTable.InsertRow( rTable.GetFrameFormat()->GetDoc(), rBoxes, nCnt, /*bBehind*/true ); + if( bRet ) + m_pInsRowUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + else + { + m_pInsRowUndo.reset(); + } + return bRet; +} + +bool SwUndoTableCpyTable::IsEmpty() const +{ + return !m_pInsRowUndo && m_vArr.empty(); +} + +SwUndoCpyTable::SwUndoCpyTable(const SwDoc* pDoc) + : SwUndo( SwUndoId::CPYTBL, pDoc ), m_nTableNode( 0 ) +{ +} + +SwUndoCpyTable::~SwUndoCpyTable() +{ +} + +void SwUndoCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + + // move hard page breaks into next node + SwContentNode* pNextNd = rDoc.GetNodes()[ pTNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + } + + SwPaM aPam( *pTNd, *pTNd->EndOfSectionNode(), 0 , 1 ); + m_pDelete.reset( new SwUndoDelete( aPam, true ) ); +} + +void SwUndoCpyTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pDelete->UndoImpl(rContext); + m_pDelete.reset(); +} + +SwUndoSplitTable::SwUndoSplitTable( const SwTableNode& rTableNd, + std::unique_ptr<SwSaveRowSpan> pRowSp, SplitTable_HeadlineOption eMode, bool bNewSize ) + : SwUndo( SwUndoId::SPLIT_TABLE, rTableNd.GetDoc() ), + m_nTableNode( rTableNd.GetIndex() ), m_nOffset( 0 ), mpSaveRowSpan( std::move(pRowSp) ), + m_nMode( eMode ), m_nFormulaEnd( 0 ), m_bCalcNewSize( bNewSize ) +{ + switch( m_nMode ) + { + case SplitTable_HeadlineOption::BoxAttrAllCopy: + m_pHistory.reset(new SwHistory); + [[fallthrough]]; + case SplitTable_HeadlineOption::BorderCopy: + case SplitTable_HeadlineOption::BoxAttrCopy: + m_pSavedTable.reset(new SaveTable( rTableNd.GetTable(), 1, false )); + break; + default: break; + } +} + +SwUndoSplitTable::~SwUndoSplitTable() +{ + m_pSavedTable.reset(); + m_pHistory.reset(); + mpSaveRowSpan.reset(); +} + +void SwUndoSplitTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + SwNodeIndex& rIdx = pPam->GetPoint()->nNode; + rIdx = m_nTableNode + m_nOffset; + pPam->GetPoint()->nContent.Assign(rIdx.GetNode().GetContentNode(), 0); + assert(rIdx.GetNode().GetContentNode()->Len() == 0); // empty para inserted + + { + // avoid asserts from ~SwIndexReg + SwNodeIndex const idx(pDoc->GetNodes(), m_nTableNode + m_nOffset); + { + SwPaM pam(idx); + pam.Move(fnMoveBackward, GoInContent); + ::PaMCorrAbs(*pPam, *pam.GetPoint()); + } + + // remove implicitly created paragraph again + pDoc->GetNodes().Delete( idx ); + } + + rIdx = m_nTableNode + m_nOffset; + SwTableNode* pTableNd = rIdx.GetNode().GetTableNode(); + SwTable& rTable = pTableNd->GetTable(); + + SwTableFormulaUpdate aMsgHint( &rTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + pDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + switch( m_nMode ) + { + case SplitTable_HeadlineOption::BoxAttrAllCopy: + if( m_pHistory ) + m_pHistory->TmpRollback( pDoc, m_nFormulaEnd ); + [[fallthrough]]; + case SplitTable_HeadlineOption::BoxAttrCopy: + case SplitTable_HeadlineOption::BorderCopy: + { + m_pSavedTable->CreateNew( rTable, false ); + m_pSavedTable->RestoreAttr( rTable ); + } + break; + + case SplitTable_HeadlineOption::ContentCopy: + // the created first line has to be removed again + { + SwSelBoxes aSelBoxes; + SwTableBox* pBox = rTable.GetTableBox( m_nTableNode + m_nOffset + 1 ); + SwTable::SelLineFromBox( pBox, aSelBoxes ); + FndBox_ aTmpBox( nullptr, nullptr ); + aTmpBox.SetTableLines( aSelBoxes, rTable ); + aTmpBox.DelFrames( rTable ); + rTable.DeleteSel( pDoc, aSelBoxes, nullptr, nullptr, false, false ); + } + break; + default: break; + } + + pDoc->GetNodes().MergeTable( rIdx ); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( pDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + if( mpSaveRowSpan ) + { + pTableNd = rIdx.GetNode().FindTableNode(); + if( pTableNd ) + pTableNd->GetTable().RestoreRowSpan( *mpSaveRowSpan ); + } + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoSplitTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + pPam->GetPoint()->nNode = m_nTableNode; + pDoc->SplitTable( *pPam->GetPoint(), m_nMode, m_bCalcNewSize ); + + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoSplitTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM *const pPam = & rContext.GetRepeatPaM(); + SwDoc *const pDoc = & rContext.GetDoc(); + + pDoc->SplitTable( *pPam->GetPoint(), m_nMode, m_bCalcNewSize ); + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoSplitTable::SaveFormula( SwHistory& rHistory ) +{ + if( !m_pHistory ) + m_pHistory.reset(new SwHistory); + + m_nFormulaEnd = rHistory.Count(); + m_pHistory->Move( 0, &rHistory ); +} + +SwUndoMergeTable::SwUndoMergeTable( const SwTableNode& rTableNd, + const SwTableNode& rDelTableNd, + bool bWithPrv, sal_uInt16 nMd ) + : SwUndo( SwUndoId::MERGE_TABLE, rTableNd.GetDoc() ), + m_nMode( nMd ), m_bWithPrev( bWithPrv ) +{ + // memorize end node of the last table cell that'll stay in position + if( m_bWithPrev ) + m_nTableNode = rDelTableNd.EndOfSectionIndex() - 1; + else + m_nTableNode = rTableNd.EndOfSectionIndex() - 1; + + m_aName = rDelTableNd.GetTable().GetFrameFormat()->GetName(); + m_pSaveTable.reset(new SaveTable( rDelTableNd.GetTable() )); + + if (m_bWithPrev) + m_pSaveHdl.reset( new SaveTable( rTableNd.GetTable(), 1 ) ); +} + +SwUndoMergeTable::~SwUndoMergeTable() +{ + m_pSaveTable.reset(); + m_pSaveHdl.reset(); + m_pHistory.reset(); +} + +void SwUndoMergeTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + SwNodeIndex& rIdx = pPam->GetPoint()->nNode; + rIdx = m_nTableNode; + + SwTableNode* pTableNd = rIdx.GetNode().FindTableNode(); + SwTable* pTable = &pTableNd->GetTable(); + + SwTableFormulaUpdate aMsgHint( pTable ); + aMsgHint.m_eFlags = TBL_BOXPTR; + pDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + // get lines for layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( *pTable ); + aFndBox.DelFrames( *pTable ); + // ? TL_CHART2: notification or locking of controller required ? + + SwTableNode* pNew = pDoc->GetNodes().SplitTable( rIdx ); + + // update layout + aFndBox.MakeFrames( *pTable ); + // ? TL_CHART2: notification or locking of controller required ? + + if( m_bWithPrev ) + { + // move name + pNew->GetTable().GetFrameFormat()->SetName( pTable->GetFrameFormat()->GetName() ); + m_pSaveHdl->RestoreAttr( pNew->GetTable() ); + } + else + pTable = &pNew->GetTable(); + + pTable->GetFrameFormat()->SetName( m_aName ); + m_pSaveTable->RestoreAttr( *pTable ); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( pDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + // create frames for the new table + SwNodeIndex aTmpIdx( *pNew ); + pNew->MakeOwnFrames(&aTmpIdx); + + // position cursor somewhere in content + SwContentNode* pCNd = pDoc->GetNodes().GoNext( &rIdx ); + pPam->GetPoint()->nContent.Assign( pCNd, 0 ); + + ClearFEShellTabCols(*pDoc, nullptr); + + // TL_CHART2: need to inform chart of probably changed cell names + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + { + pDoc->UpdateCharts( pTable->GetFrameFormat()->GetName() ); + pDoc->UpdateCharts( pNew->GetTable().GetFrameFormat()->GetName() ); + } +} + +void SwUndoMergeTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + pPam->GetPoint()->nNode = m_nTableNode; + if( m_bWithPrev ) + pPam->GetPoint()->nNode = m_nTableNode + 3; + else + pPam->GetPoint()->nNode = m_nTableNode; + + pDoc->MergeTable( *pPam->GetPoint(), m_bWithPrev, m_nMode ); + + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoMergeTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam = & rContext.GetRepeatPaM(); + + pDoc->MergeTable( *pPam->GetPoint(), m_bWithPrev, m_nMode ); + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoMergeTable::SaveFormula( SwHistory& rHistory ) +{ + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + m_pHistory->Move( 0, &rHistory ); +} + +void InsertSort( std::vector<sal_uInt16>& rArr, sal_uInt16 nIdx ) +{ + size_t nO = rArr.size(); + size_t nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + const size_t nM = nU + ( nO - nU ) / 2; + if ( rArr[nM] == nIdx ) + { + OSL_FAIL( "Index already exists. This should never happen." ); + return; + } + if( rArr[nM] < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + break; + else + nO = nM - 1; + } + } + rArr.insert( rArr.begin() + nU, nIdx ); +} + +#if OSL_DEBUG_LEVEL > 0 +void CheckTable( const SwTable& rTable ) +{ + const SwNodes& rNds = rTable.GetFrameFormat()->GetDoc()->GetNodes(); + const SwTableSortBoxes& rSrtArr = rTable.GetTabSortBoxes(); + for (size_t n = 0; n < rSrtArr.size(); ++n) + { + const SwTableBox* pBox = rSrtArr[ n ]; + const SwNode* pNd = pBox->GetSttNd(); + OSL_ENSURE( rNds[ pBox->GetSttIdx() ] == pNd, "Box with wrong StartNode" ); + } +} +#endif + +SwUndoTableStyleMake::SwUndoTableStyleMake(const OUString& rName, const SwDoc* pDoc) + : SwUndo(SwUndoId::TBLSTYLE_CREATE, pDoc), + m_sName(rName) +{ } + +SwUndoTableStyleMake::~SwUndoTableStyleMake() +{ } + +void SwUndoTableStyleMake::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pAutoFormat = rContext.GetDoc().DelTableStyle(m_sName, true); +} + +void SwUndoTableStyleMake::RedoImpl(::sw::UndoRedoContext & rContext) +{ + if (m_pAutoFormat) + { + SwTableAutoFormat* pFormat = rContext.GetDoc().MakeTableStyle(m_sName, true); + if (pFormat) + { + *pFormat = *m_pAutoFormat; + m_pAutoFormat.reset(); + } + } +} + +SwRewriter SwUndoTableStyleMake::GetRewriter() const +{ + SwRewriter aResult; + aResult.AddRule(UndoArg1, m_sName); + return aResult; +} + +SwUndoTableStyleDelete::SwUndoTableStyleDelete(std::unique_ptr<SwTableAutoFormat> pAutoFormat, const std::vector<SwTable*>& rAffectedTables, const SwDoc* pDoc) + : SwUndo(SwUndoId::TBLSTYLE_DELETE, pDoc), + m_pAutoFormat(std::move(pAutoFormat)), + m_rAffectedTables(rAffectedTables) +{ } + +SwUndoTableStyleDelete::~SwUndoTableStyleDelete() +{ } + +void SwUndoTableStyleDelete::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwTableAutoFormat* pNewFormat = rContext.GetDoc().MakeTableStyle(m_pAutoFormat->GetName(), true); + *pNewFormat = *m_pAutoFormat; + for (size_t i=0; i < m_rAffectedTables.size(); i++) + m_rAffectedTables[i]->SetTableStyleName(m_pAutoFormat->GetName()); +} + +void SwUndoTableStyleDelete::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // Don't need to remember deleted table style nor affected tables, because they must be the same as these already known. + rContext.GetDoc().DelTableStyle(m_pAutoFormat->GetName()); +} + +SwRewriter SwUndoTableStyleDelete::GetRewriter() const +{ + SwRewriter aResult; + aResult.AddRule(UndoArg1, m_pAutoFormat->GetName()); + return aResult; +} + +SwUndoTableStyleUpdate::SwUndoTableStyleUpdate(const SwTableAutoFormat& rNewFormat, const SwTableAutoFormat& rOldFormat, const SwDoc* pDoc) + : SwUndo(SwUndoId::TBLSTYLE_UPDATE, pDoc) + , m_pOldFormat(new SwTableAutoFormat(rOldFormat)) + , m_pNewFormat(new SwTableAutoFormat(rNewFormat)) +{ } + +SwUndoTableStyleUpdate::~SwUndoTableStyleUpdate() +{ } + +void SwUndoTableStyleUpdate::UndoImpl(::sw::UndoRedoContext & rContext) +{ + rContext.GetDoc().ChgTableStyle(m_pNewFormat->GetName(), *m_pOldFormat); +} + +void SwUndoTableStyleUpdate::RedoImpl(::sw::UndoRedoContext & rContext) +{ + rContext.GetDoc().ChgTableStyle(m_pNewFormat->GetName(), *m_pNewFormat); +} + +SwRewriter SwUndoTableStyleUpdate::GetRewriter() const +{ + SwRewriter aResult; + aResult.AddRule(UndoArg1, m_pNewFormat->GetName()); + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/untblk.cxx b/sw/source/core/undo/untblk.cxx new file mode 100644 index 000000000..374241404 --- /dev/null +++ b/sw/source/core/undo/untblk.cxx @@ -0,0 +1,451 @@ +/* -*- 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 <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IShellCursorSupplier.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <pam.hxx> +#include <mvsave.hxx> +#include <ndtxt.hxx> +#include <UndoCore.hxx> +#include <rolbck.hxx> +#include <redline.hxx> +#include <frameformats.hxx> + +namespace sw { + +std::unique_ptr<std::vector<SwFrameFormat*>> +GetFlysAnchoredAt(SwDoc & rDoc, sal_uLong const nSttNode) +{ + std::unique_ptr<std::vector<SwFrameFormat*>> pFrameFormats; + const size_t nArrLen = rDoc.GetSpzFrameFormats()->size(); + for (size_t n = 0; n < nArrLen; ++n) + { + SwFrameFormat *const pFormat = (*rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos + && nSttNode == pAPos->nNode.GetIndex() + && ((pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + || (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR))) + { + if (!pFrameFormats) + pFrameFormats.reset( new std::vector<SwFrameFormat*> ); + pFrameFormats->push_back( pFormat ); + } + } + return pFrameFormats; +} + +} // namespace sw + +//note: parameter is SwPam just so we can init SwUndRng, the End is ignored! +SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam ) + : SwUndo( nUndoId, rPam.GetDoc() ) + , SwUndRng( rPam ) + , m_pTextFormatColl(nullptr) + , m_pLastNodeColl(nullptr) + , m_nDeleteTextNodes(1) + , m_nNodeDiff(0) + , m_nSetPos(0) +{ + m_pHistory.reset( new SwHistory ); + SwDoc* pDoc = rPam.GetDoc(); + + SwTextNode* pTextNd = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + if( pTextNd ) + { + m_pTextFormatColl = pTextNd->GetTextColl(); + assert(m_pTextFormatColl); + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode, + 0, pTextNd->GetText().getLength(), false ); + if( pTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode ); + + // We may have some flys anchored to paragraph where we inserting. + // These flys will be saved in pFrameFormats array (only flys which exist BEFORE insertion!) + // Then in SwUndoInserts::SetInsertRange the flys saved in pFrameFormats will NOT create Undos. + // m_FlyUndos will only be filled with newly inserted flys. + m_pFrameFormats = sw::GetFlysAnchoredAt(*pDoc, m_nSttNode); + } + // consider Redline + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, pDoc->getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + } +} + +// This method does two things: +// 1. Adjusts SwUndoRng members, required for Undo. +// Members are: +// SwUndoRng::nSttNode - all nodes starting from this node will be deleted during Undo (in SwUndoInserts::UndoImpl) +// SwUndoRng::nSttContent - corresponding content index in SwUndoRng::nSttNode +// SwUndoRng::nEndNode - end node for deletion +// SwUndoRng::nEndContent - end content index +// All these members are filled in during construction of SwUndoInserts instance, and can be adjusted using this method +// +// 2. Fills in m_FlyUndos array with flys anchored ONLY to first and last paragraphs (first == rPam.Start(), last == rPam.End()) +// Flys, anchored to any paragraph, but not first and last, are handled by DelContentIndex (see SwUndoInserts::UndoImpl) and are not stored in m_FlyUndos. + +void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys, + int const nDeleteTextNodes) +{ + const SwPosition* pTmpPos = rPam.End(); + m_nEndNode = pTmpPos->nNode.GetIndex(); + m_nEndContent = pTmpPos->nContent.GetIndex(); + if( rPam.HasMark() ) + { + if( pTmpPos == rPam.GetPoint() ) + pTmpPos = rPam.GetMark(); + else + pTmpPos = rPam.GetPoint(); + + m_nSttNode = pTmpPos->nNode.GetIndex(); + m_nSttContent = pTmpPos->nContent.GetIndex(); + + m_nDeleteTextNodes = nDeleteTextNodes; + if (m_nDeleteTextNodes == 0) // if a table selection is added... + { + ++m_nSttNode; // ... then the CopyPam is not fully correct + } + } + + // Fill m_FlyUndos with flys anchored to first and last paragraphs + + if( bScanFlys) + { + // than collect all new Flys + SwDoc* pDoc = rPam.GetDoc(); + const size_t nArrLen = pDoc->GetSpzFrameFormats()->size(); + for( size_t n = 0; n < nArrLen; ++n ) + { + SwFrameFormat* pFormat = (*pDoc->GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + if (IsCreateUndoForNewFly(*pAnchor, m_nSttNode, m_nEndNode)) + { + std::vector<SwFrameFormat*>::iterator it; + if( !m_pFrameFormats || + m_pFrameFormats->end() == ( it = std::find( m_pFrameFormats->begin(), m_pFrameFormats->end(), pFormat ) ) ) + { + std::shared_ptr<SwUndoInsLayFormat> const pFlyUndo = + std::make_shared<SwUndoInsLayFormat>(pFormat, 0, 0); + m_FlyUndos.push_back(pFlyUndo); + } + else + m_pFrameFormats->erase( it ); + } + } + m_pFrameFormats.reset(); + } +} + +/** This is not the same as IsDestroyFrameAnchoredAtChar() + and intentionally so: because the SwUndoInserts::UndoImpl() must remove + the flys at the start/end position that were inserted but not the ones + at the start/insert position that were already there; + handle all at-char flys at start/end node like this, even if they're + not *on* the start/end position, because it makes it easier to ensure + that the Undo/Redo run in inverse order. + */ +bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor, + sal_uLong const nStartNode, sal_uLong const nEndNode) +{ + assert(nStartNode <= nEndNode); + + // check all at-char flys at the start/end nodes: + // ExcludeFlyAtStartEnd will exclude them! + SwPosition const*const pAnchorPos = rAnchor.GetContentAnchor(); + return pAnchorPos != nullptr + && ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && ( nStartNode == pAnchorPos->nNode.GetIndex() + || nEndNode == pAnchorPos->nNode.GetIndex()); +} + +SwUndoInserts::~SwUndoInserts() +{ + if (m_pUndoNodeIndex) // delete also the section from UndoNodes array + { + // Insert saves content in IconSection + SwNodes& rUNds = m_pUndoNodeIndex->GetNodes(); + rUNds.Delete(*m_pUndoNodeIndex, + rUNds.GetEndOfExtras().GetIndex() - m_pUndoNodeIndex->GetIndex()); + m_pUndoNodeIndex.reset(); + } + m_pFrameFormats.reset(); + m_pRedlineData.reset(); +} + +// Undo Insert operation +// It's important to note that Undo stores absolute node indexes. I.e. if during insertion, you insert nodes 31 to 33, +// during Undo nodes with indices from 31 to 33 will be deleted. Undo doesn't check that nodes 31 to 33 are the same nodes which were inserted. +// It just deletes them. +// This may seem as bad programming practice, but Undo actions are strongly ordered. If you change your document in some way, a new Undo action is added. +// During Undo most recent actions will be executed first. So during execution of particular Undo action indices will be correct. +// But storing absolute indices leads to crashes if some action in Undo fails to roll back some modifications. + +// Has following main steps: +// 1. m_FlyUndos removes flys anchored to first and last paragraph in Undo range. +// This array may be empty. +// 2. DelContentIndex to delete footnotes, flys, bookmarks (see comment for this function) +// Deleted flys are stored in pHistory array. +// First and last paragraphs flys are not deleted by DelContentIndex! +// For flys anchored to last paragraph, DelContentIndex re-anchors them to +// the last paragraph that will remain after Undo. +// 3. MoveToUndoNds moves nodes to Undo nodes array and removes them from document. +// 4. Lastly (starting from if(pTextNode)), text from last paragraph is joined to last remaining paragraph and FormatColl for last paragraph is restored. +// Format coll for last paragraph is removed during execution of UndoImpl + +void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwPaM& rPam = AddUndoRedoPaM(rContext); + + m_nNodeDiff = 0; + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + + // if Point and Mark are different text nodes so a JoinNext has to be done + bool bJoinNext = m_nSttNode != m_nEndNode && + rPam.GetMark()->nNode.GetNode().GetTextNode() && + rPam.GetPoint()->nNode.GetNode().GetTextNode(); + + // Is there any content? (loading from template does not have content) + if( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) + { + if( m_nSttNode != m_nEndNode ) + { + SwTextNode* pTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode(); + if (pTextNd && pTextNd->GetText().getLength() == m_nEndContent) + m_pLastNodeColl = pTextNd->GetTextColl(); + } + + // tdf#128739 correct cursors but do not delete bookmarks yet + ::PaMCorrAbs(rPam, *rPam.End()); + + SetPaM(rPam); + } + + // ... for consistency with the Insert File code in shellio.cxx, which + // creates separate SwUndoInsLayFormat for mysterious reasons, do this + // *before* anything else: + // after SetPaM but before MoveToUndoNds and DelContentIndex. + // note: there isn't an order dep wrt. initial Copy action because Undo + // overwrites the indexes but there is wrt. Redo because that uses the + // indexes + if (!m_FlyUndos.empty()) + { + sal_uLong nTmp = rPam.GetPoint()->nNode.GetIndex(); + for (size_t n = m_FlyUndos.size(); 0 < n; --n) + { + m_FlyUndos[ n-1 ]->UndoImpl(rContext); + } + m_nNodeDiff += nTmp - rPam.GetPoint()->nNode.GetIndex(); + } + + if (m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent) + { + // are there Footnotes or ContentFlyFrames in text? + m_nSetPos = m_pHistory->Count(); + sal_uLong nTmp = rPam.GetMark()->nNode.GetIndex(); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), + DelContentType::AllMask|DelContentType::ExcludeFlyAtStartEnd); + m_nNodeDiff += nTmp - rPam.GetMark()->nNode.GetIndex(); + if( *rPam.GetPoint() != *rPam.GetMark() ) + { + m_pUndoNodeIndex.reset( + new SwNodeIndex(rDoc.GetNodes().GetEndOfContent())); + MoveToUndoNds(rPam, m_pUndoNodeIndex.get()); + + if (m_nDeleteTextNodes == 0) + { + rPam.Move( fnMoveBackward, GoInContent ); + } + } + } + + SwNodeIndex& rIdx = rPam.GetPoint()->nNode; + SwTextNode* pTextNode = rIdx.GetNode().GetTextNode(); + if( pTextNode ) + { + if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete + { + SwNodeIndex aDelIdx( rIdx ); + assert(0 < m_nDeleteTextNodes && m_nDeleteTextNodes < 3); + for (int i = 0; i < m_nDeleteTextNodes; ++i) + { + rPam.Move(fnMoveForward, GoInNode); + } + rPam.DeleteMark(); + + for (int i = 0; i < m_nDeleteTextNodes; ++i) + { + RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint()); + } + + rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes ); + } + else + { + if( bJoinNext && pTextNode->CanJoinNext()) + { + { + RemoveIdxRel( rIdx.GetIndex()+1, SwPosition( rIdx, + SwIndex( pTextNode, pTextNode->GetText().getLength() ))); + } + pTextNode->JoinNext(); + } + // reset all text attributes in the paragraph! + pTextNode->RstTextAttr( SwIndex(pTextNode, 0), pTextNode->Len(), 0, nullptr, true ); + + pTextNode->ResetAllAttr(); + + if (rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl)) + m_pTextFormatColl = static_cast<SwTextFormatColl*>(pTextNode->ChgFormatColl( m_pTextFormatColl )) ; + + m_pHistory->SetTmpEnd( m_nSetPos ); + m_pHistory->TmpRollback(&rDoc, 0, false); + } + } +} + +// See SwUndoInserts::UndoImpl comments +// All actions here should be done in reverse order to what is done in SwUndoInserts::UndoImpl! + +void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // position cursor onto REDO section + SwPaM& rPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + SwDoc* pDoc = rPam.GetDoc(); + rPam.DeleteMark(); + rPam.GetPoint()->nNode = m_nSttNode - m_nNodeDiff; + SwContentNode* pCNd = rPam.GetContentNode(); + rPam.GetPoint()->nContent.Assign( pCNd, m_nSttContent ); + + SwTextFormatColl* pSavTextFormatColl = m_pTextFormatColl; + if( m_pTextFormatColl && pCNd && pCNd->IsTextNode() ) + pSavTextFormatColl = static_cast<SwTextNode*>(pCNd)->GetTextColl(); + + m_pHistory->SetTmpEnd( m_nSetPos ); + + // retrieve start position for rollback + if( ( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) && m_pUndoNodeIndex) + { + auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(*pDoc, + rPam.GetPoint()->nNode.GetIndex())); + + const bool bMvBkwrd = MovePtBackward(rPam); + + // re-insert content again (first detach m_pUndoNodeIndex!) + sal_uLong const nMvNd = m_pUndoNodeIndex->GetIndex(); + m_pUndoNodeIndex.reset(); + MoveFromUndoNds(*pDoc, nMvNd, *rPam.GetMark()); + if (m_nDeleteTextNodes != 0) + { + MovePtForward(rPam, bMvBkwrd); + } + rPam.Exchange(); + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + if (pFlysAtInsPos) + { + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( rPam.GetMark() ); + pFly->SetFormatAttr(anchor); + } + } + } + } + + if (pDoc->GetTextFormatColls()->IsAlive(m_pTextFormatColl)) + { + SwTextNode* pTextNd = rPam.GetMark()->nNode.GetNode().GetTextNode(); + if( pTextNd ) + pTextNd->ChgFormatColl( m_pTextFormatColl ); + } + m_pTextFormatColl = pSavTextFormatColl; + + if (m_pLastNodeColl && pDoc->GetTextFormatColls()->IsAlive(m_pLastNodeColl) + && rPam.GetPoint()->nNode != rPam.GetMark()->nNode) + { + SwTextNode* pTextNd = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + if( pTextNd ) + pTextNd->ChgFormatColl( m_pLastNodeColl ); + } + + // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before + // m_FlyUndos as they were created by DelContentIndex() + m_pHistory->Rollback( pDoc, m_nSetPos ); + + // tdf#108124 (10/25/2017) + // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order, + // firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc. + // As absolute node index of fly stored in SwUndoFlyBase::nNdPgPos we + // should recover from Undo in direct order (last should be recovered first) + // During REDO we should recover Flys (Images) in direct order, + // firstly m_FlyUndos[0], then with m_FlyUndos[1] index, etc. + + for (size_t n = 0; m_FlyUndos.size() > n; ++n) + { + m_FlyUndos[n]->RedoImpl(rContext); + } + + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + pDoc->getIDocumentRedlineAccess().SplitRedline(rPam); +} + +void SwUndoInserts::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM aPam( rContext.GetDoc().GetNodes().GetEndOfContent() ); + SetPaM( aPam ); + SwPaM & rRepeatPaM( rContext.GetRepeatPaM() ); + aPam.GetDoc()->getIDocumentContentOperations().CopyRange( aPam, *rRepeatPaM.GetPoint(), SwCopyFlags::CheckPosInFly); +} + +SwUndoInsDoc::SwUndoInsDoc( const SwPaM& rPam ) + : SwUndoInserts( SwUndoId::INSDOKUMENT, rPam ) +{ +} + +SwUndoCpyDoc::SwUndoCpyDoc( const SwPaM& rPam ) + : SwUndoInserts( SwUndoId::COPY, rPam ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/SwXTextDefaults.cxx b/sw/source/core/unocore/SwXTextDefaults.cxx new file mode 100644 index 000000000..19ceff59d --- /dev/null +++ b/sw/source/core/unocore/SwXTextDefaults.cxx @@ -0,0 +1,235 @@ +/* -*- 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 <com/sun/star/beans/PropertyAttribute.hpp> + +#include <vcl/svapp.hxx> +#include <osl/diagnose.h> +#include <svl/itemprop.hxx> + +#include <SwXTextDefaults.hxx> +#include <SwStyleNameMapper.hxx> +#include <fchrfmt.hxx> +#include <charfmt.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <docstyle.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <unomap.hxx> +#include <unomid.h> +#include <paratr.hxx> +#include <unocrsrhelper.hxx> +#include <hintids.hxx> +#include <fmtpdsc.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; + +SwXTextDefaults::SwXTextDefaults ( SwDoc * pNewDoc ) : + m_pPropSet( aSwMapProvider.GetPropertySet( PROPERTY_MAP_TEXT_DEFAULT ) ), + m_pDoc ( pNewDoc ) +{ +} + +SwXTextDefaults::~SwXTextDefaults () +{ +} + +uno::Reference< XPropertySetInfo > SAL_CALL SwXTextDefaults::getPropertySetInfo( ) +{ + static uno::Reference < XPropertySetInfo > xRef = m_pPropSet->getPropertySetInfo(); + return xRef; +} + +void SAL_CALL SwXTextDefaults::setPropertyValue( const OUString& rPropertyName, const Any& aValue ) +{ + SolarMutexGuard aGuard; + if (!m_pDoc) + throw RuntimeException(); + const SfxItemPropertySimpleEntry *pMap = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pMap) + throw UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if ( pMap->nFlags & PropertyAttribute::READONLY) + throw PropertyVetoException ( "Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const SfxPoolItem& rItem = m_pDoc->GetDefault(pMap->nWID); + if (RES_PAGEDESC == pMap->nWID && MID_PAGEDESC_PAGEDESCNAME == pMap->nMemberId) + { + SfxItemSet aSet( m_pDoc->GetAttrPool(), svl::Items<RES_PAGEDESC, RES_PAGEDESC>{} ); + aSet.Put(rItem); + SwUnoCursorHelper::SetPageDesc( aValue, *m_pDoc, aSet ); + m_pDoc->SetDefault(aSet.Get(RES_PAGEDESC)); + } + else if ((RES_PARATR_DROP == pMap->nWID && MID_DROPCAP_CHAR_STYLE_NAME == pMap->nMemberId) || + (RES_TXTATR_CHARFMT == pMap->nWID)) + { + OUString uStyle; + if(!(aValue >>= uStyle)) + throw lang::IllegalArgumentException(); + + OUString sStyle; + SwStyleNameMapper::FillUIName(uStyle, sStyle, SwGetPoolIdFromName::ChrFmt ); + SwDocStyleSheet* pStyle = + static_cast<SwDocStyleSheet*>(m_pDoc->GetDocShell()->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Char)); + std::unique_ptr<SwFormatDrop> pDrop; + std::unique_ptr<SwFormatCharFormat> pCharFormat; + if(!pStyle) + throw lang::IllegalArgumentException(); + + rtl::Reference< SwDocStyleSheet > xStyle( new SwDocStyleSheet( *pStyle ) ); + if (xStyle->GetCharFormat() == m_pDoc->GetDfltCharFormat()) + return; // don't SetCharFormat with formats from mpDfltCharFormat + + if (RES_PARATR_DROP == pMap->nWID) + { + pDrop.reset(static_cast<SwFormatDrop*>(rItem.Clone())); // because rItem is const... + pDrop->SetCharFormat(xStyle->GetCharFormat()); + m_pDoc->SetDefault(*pDrop); + } + else // RES_TXTATR_CHARFMT == pMap->nWID + { + pCharFormat.reset(static_cast<SwFormatCharFormat*>(rItem.Clone())); // because rItem is const... + pCharFormat->SetCharFormat(xStyle->GetCharFormat()); + m_pDoc->SetDefault(*pCharFormat); + } + } + else + { + std::unique_ptr<SfxPoolItem> pNewItem(rItem.Clone()); + pNewItem->PutValue( aValue, pMap->nMemberId); + m_pDoc->SetDefault(*pNewItem); + } +} + +Any SAL_CALL SwXTextDefaults::getPropertyValue( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + if (!m_pDoc) + throw RuntimeException(); + const SfxItemPropertySimpleEntry *pMap = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pMap) + throw UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + Any aRet; + const SfxPoolItem& rItem = m_pDoc->GetDefault(pMap->nWID); + rItem.QueryValue( aRet, pMap->nMemberId ); + return aRet; +} + +void SAL_CALL SwXTextDefaults::addPropertyChangeListener( const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ + OSL_FAIL ( "not implemented" ); +} + +void SAL_CALL SwXTextDefaults::removePropertyChangeListener( const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ + OSL_FAIL ( "not implemented" ); +} + +void SAL_CALL SwXTextDefaults::addVetoableChangeListener( const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +{ + OSL_FAIL ( "not implemented" ); +} + +void SAL_CALL SwXTextDefaults::removeVetoableChangeListener( const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +{ + OSL_FAIL ( "not implemented" ); +} + +// XPropertyState +PropertyState SAL_CALL SwXTextDefaults::getPropertyState( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + PropertyState eRet = PropertyState_DIRECT_VALUE; + if (!m_pDoc) + throw RuntimeException(); + const SfxItemPropertySimpleEntry *pMap = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pMap) + throw UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const SfxPoolItem& rItem = m_pDoc->GetDefault(pMap->nWID); + if (IsStaticDefaultItem ( &rItem ) ) + eRet = PropertyState_DEFAULT_VALUE; + return eRet; +} + +Sequence< PropertyState > SAL_CALL SwXTextDefaults::getPropertyStates( const Sequence< OUString >& rPropertyNames ) +{ + const sal_Int32 nCount = rPropertyNames.getLength(); + Sequence < PropertyState > aRet ( nCount ); + + std::transform(rPropertyNames.begin(), rPropertyNames.end(), aRet.begin(), + [this](const OUString& rName) -> PropertyState { return getPropertyState(rName); }); + + return aRet; +} + +void SAL_CALL SwXTextDefaults::setPropertyToDefault( const OUString& rPropertyName ) +{ + if (!m_pDoc) + throw RuntimeException(); + const SfxItemPropertySimpleEntry *pMap = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pMap) + throw UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if ( pMap->nFlags & PropertyAttribute::READONLY) + throw RuntimeException( "setPropertyToDefault: property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + SfxItemPool& rSet (m_pDoc->GetAttrPool()); + rSet.ResetPoolDefaultItem ( pMap->nWID ); +} + +Any SAL_CALL SwXTextDefaults::getPropertyDefault( const OUString& rPropertyName ) +{ + if (!m_pDoc) + throw RuntimeException(); + const SfxItemPropertySimpleEntry *pMap = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pMap) + throw UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + Any aRet; + SfxItemPool& rSet (m_pDoc->GetAttrPool()); + SfxPoolItem const*const pItem = rSet.GetPoolDefaultItem(pMap->nWID); + if (pItem) + { + pItem->QueryValue( aRet, pMap->nMemberId ); + } + return aRet; +} + +OUString SAL_CALL SwXTextDefaults::getImplementationName( ) +{ + return "SwXTextDefaults"; +} + +sal_Bool SAL_CALL SwXTextDefaults::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwXTextDefaults::getSupportedServiceNames( ) +{ + return { "com.sun.star.text.Defaults", + "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesAsian", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.ParagraphProperties", + "com.sun.star.style.ParagraphPropertiesAsian", + "com.sun.star.style.ParagraphPropertiesComplex" }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/TextCursorHelper.cxx b/sw/source/core/unocore/TextCursorHelper.cxx new file mode 100644 index 000000000..d2c45acf4 --- /dev/null +++ b/sw/source/core/unocore/TextCursorHelper.cxx @@ -0,0 +1,46 @@ +/* -*- 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 <TextCursorHelper.hxx> +#include <comphelper/servicehelper.hxx> + +using namespace ::com::sun::star; + +namespace +{ + class theOTextCursorHelperUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theOTextCursorHelperUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & OTextCursorHelper::getUnoTunnelId() +{ + return theOTextCursorHelperUnoTunnelId::get().getSeq(); +} + +//XUnoTunnel +sal_Int64 SAL_CALL OTextCursorHelper::getSomething( + const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<OTextCursorHelper>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/XMLRangeHelper.cxx b/sw/source/core/unocore/XMLRangeHelper.cxx new file mode 100644 index 000000000..4542469f5 --- /dev/null +++ b/sw/source/core/unocore/XMLRangeHelper.cxx @@ -0,0 +1,387 @@ +/* -*- 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 "XMLRangeHelper.hxx" +#include <rtl/character.hxx> +#include <rtl/ustrbuf.hxx> + +#include <algorithm> + +namespace +{ +/** unary function that escapes backslashes and single quotes in a sal_Unicode + array (which you can get from an OUString with getStr()) and puts the result + into the OUStringBuffer given in the CTOR + */ +class lcl_Escape +{ +public: + explicit lcl_Escape( OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} + void operator() ( sal_Unicode aChar ) + { + static const sal_Unicode s_aQuote( '\'' ); + static const sal_Unicode s_aBackslash( '\\' ); + + if( aChar == s_aQuote || + aChar == s_aBackslash ) + m_aResultBuffer.append( s_aBackslash ); + m_aResultBuffer.append( aChar ); + } + +private: + OUStringBuffer & m_aResultBuffer; +}; + +/** unary function that removes backslash escapes in a sal_Unicode array (which + you can get from an OUString with getStr()) and puts the result into the + OUStringBuffer given in the CTOR + */ +class lcl_UnEscape +{ +public: + explicit lcl_UnEscape( OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} + void operator() ( sal_Unicode aChar ) + { + static const sal_Unicode s_aBackslash( '\\' ); + + if( aChar != s_aBackslash ) + m_aResultBuffer.append( aChar ); + } + +private: + OUStringBuffer & m_aResultBuffer; +}; + +void lcl_getXMLStringForCell( const /*::chart::*/XMLRangeHelper::Cell & rCell, OUStringBuffer * output ) +{ + assert(output != nullptr); + + if( rCell.empty()) + return; + + sal_Int32 nCol = rCell.nColumn; + output->append( '.' ); + if( ! rCell.bRelativeColumn ) + output->append( '$' ); + + // get A, B, C, ..., AA, AB, ... representation of column number + if( nCol < 26 ) + output->append( static_cast<sal_Unicode>('A' + nCol) ); + else if( nCol < 702 ) + { + output->append( static_cast<sal_Unicode>('A' + nCol / 26 - 1 )); + output->append( static_cast<sal_Unicode>('A' + nCol % 26) ); + } + else // works for nCol <= 18,278 + { + output->append( static_cast<sal_Unicode>('A' + nCol / 702 - 1 )); + output->append( static_cast<sal_Unicode>('A' + (nCol % 702) / 26 )); + output->append( static_cast<sal_Unicode>('A' + nCol % 26) ); + } + + // write row number as number + if( ! rCell.bRelativeRow ) + output->append( '$' ); + output->append( rCell.nRow + sal_Int32(1) ); +} + +void lcl_getSingleCellAddressFromXMLString( + const OUString& rXMLString, + sal_Int32 nStartPos, sal_Int32 nEndPos, + /*::chart::*/XMLRangeHelper::Cell & rOutCell ) +{ + // expect "\$?[a-zA-Z]+\$?[1-9][0-9]*" + static const sal_Unicode aDollar( '$' ); + static const sal_Unicode aLetterA( 'A' ); + + OUString aCellStr = rXMLString.copy( nStartPos, nEndPos - nStartPos + 1 ).toAsciiUpperCase(); + const sal_Unicode* pStrArray = aCellStr.getStr(); + sal_Int32 nLength = aCellStr.getLength(); + sal_Int32 i = nLength - 1, nColumn = 0; + + // parse number for row + while( rtl::isAsciiDigit( pStrArray[ i ] ) && i >= 0 ) + i--; + rOutCell.nRow = (aCellStr.copy( i + 1 )).toInt32() - 1; + // a dollar in XML means absolute (whereas in UI it means relative) + if( pStrArray[ i ] == aDollar ) + { + i--; + rOutCell.bRelativeRow = false; + } + else + rOutCell.bRelativeRow = true; + + // parse rest for column + sal_Int32 nPower = 1; + while( rtl::isAsciiAlpha( pStrArray[ i ] )) + { + nColumn += (pStrArray[ i ] - aLetterA + 1) * nPower; + i--; + nPower *= 26; + } + rOutCell.nColumn = nColumn - 1; + + rOutCell.bRelativeColumn = true; + if( i >= 0 && + pStrArray[ i ] == aDollar ) + rOutCell.bRelativeColumn = false; + rOutCell.bIsEmpty = false; +} + +bool lcl_getCellAddressFromXMLString( + const OUString& rXMLString, + sal_Int32 nStartPos, sal_Int32 nEndPos, + /*::chart::*/XMLRangeHelper::Cell & rOutCell, + OUString& rOutTableName ) +{ + static const sal_Unicode aDot( '.' ); + static const sal_Unicode aQuote( '\'' ); + static const sal_Unicode aBackslash( '\\' ); + + sal_Int32 nNextDelimiterPos = nStartPos; + + sal_Int32 nDelimiterPos = nStartPos; + bool bInQuotation = false; + // parse table name + while( nDelimiterPos < nEndPos && + ( bInQuotation || rXMLString[ nDelimiterPos ] != aDot )) + { + // skip escaped characters (with backslash) + if( rXMLString[ nDelimiterPos ] == aBackslash ) + ++nDelimiterPos; + // toggle quotation mode when finding single quotes + else if( rXMLString[ nDelimiterPos ] == aQuote ) + bInQuotation = ! bInQuotation; + + ++nDelimiterPos; + } + + if( nDelimiterPos == -1 || + nDelimiterPos >= nEndPos ) + { + return false; + } + if( nDelimiterPos > nStartPos ) + { + // there is a table name before the address + + OUStringBuffer aTableNameBuffer; + const sal_Unicode * pTableName = rXMLString.getStr(); + + // remove escapes from table name + std::for_each( pTableName + nStartPos, + pTableName + nDelimiterPos, + lcl_UnEscape( aTableNameBuffer )); + + // unquote quoted table name + const sal_Unicode * pBuf = aTableNameBuffer.getStr(); + if( pBuf[ 0 ] == aQuote && + pBuf[ aTableNameBuffer.getLength() - 1 ] == aQuote ) + { + OUString aName = aTableNameBuffer.makeStringAndClear(); + rOutTableName = aName.copy( 1, aName.getLength() - 2 ); + } + else + rOutTableName = aTableNameBuffer.makeStringAndClear(); + } + + for( sal_Int32 i = 0; + nNextDelimiterPos < nEndPos; + nDelimiterPos = nNextDelimiterPos, i++ ) + { + nNextDelimiterPos = rXMLString.indexOf( aDot, nDelimiterPos + 1 ); + if( nNextDelimiterPos == -1 || + nNextDelimiterPos > nEndPos ) + nNextDelimiterPos = nEndPos + 1; + + if( i==0 ) + // only take first cell + lcl_getSingleCellAddressFromXMLString( + rXMLString, nDelimiterPos + 1, nNextDelimiterPos - 1, rOutCell ); + } + + return true; +} + +bool lcl_getCellRangeAddressFromXMLString( + const OUString& rXMLString, + sal_Int32 nStartPos, sal_Int32 nEndPos, + /*::chart::*/XMLRangeHelper::CellRange & rOutRange ) +{ + bool bResult = true; + static const sal_Unicode aColon( ':' ); + static const sal_Unicode aQuote( '\'' ); + static const sal_Unicode aBackslash( '\\' ); + + sal_Int32 nDelimiterPos = nStartPos; + bool bInQuotation = false; + // parse table name + while( nDelimiterPos < nEndPos && + ( bInQuotation || rXMLString[ nDelimiterPos ] != aColon )) + { + // skip escaped characters (with backslash) + if( rXMLString[ nDelimiterPos ] == aBackslash ) + ++nDelimiterPos; + // toggle quotation mode when finding single quotes + else if( rXMLString[ nDelimiterPos ] == aQuote ) + bInQuotation = ! bInQuotation; + + ++nDelimiterPos; + } + + if( nDelimiterPos == nEndPos ) + { + // only one cell + bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nEndPos, + rOutRange.aUpperLeft, + rOutRange.aTableName ); + } + else + { + // range (separated by a colon) + bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nDelimiterPos - 1, + rOutRange.aUpperLeft, + rOutRange.aTableName ); + OUString sTableSecondName; + if( bResult ) + { + bResult = lcl_getCellAddressFromXMLString( rXMLString, nDelimiterPos + 1, nEndPos, + rOutRange.aLowerRight, + sTableSecondName ); + } + if( bResult && + !sTableSecondName.isEmpty() && + sTableSecondName != rOutRange.aTableName ) + bResult = false; + } + + return bResult; +} + +} // anonymous namespace + +namespace XMLRangeHelper +{ + +CellRange getCellRangeFromXMLString( const OUString & rXMLString ) +{ + static const sal_Unicode aSpace( ' ' ); + static const sal_Unicode aQuote( '\'' ); + static const sal_Unicode aDollar( '$' ); + static const sal_Unicode aBackslash( '\\' ); + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = nStartPos; + const sal_Int32 nLength = rXMLString.getLength(); + + // reset + CellRange aResult; + + // iterate over different ranges + for( sal_Int32 i = 0; + nEndPos < nLength; + nStartPos = ++nEndPos, i++ ) + { + // find start point of next range + + // ignore leading '$' + if( rXMLString[ nEndPos ] == aDollar) + nEndPos++; + + bool bInQuotation = false; + // parse range + while( nEndPos < nLength && + ( bInQuotation || rXMLString[ nEndPos ] != aSpace )) + { + // skip escaped characters (with backslash) + if( rXMLString[ nEndPos ] == aBackslash ) + ++nEndPos; + // toggle quotation mode when finding single quotes + else if( rXMLString[ nEndPos ] == aQuote ) + bInQuotation = ! bInQuotation; + + ++nEndPos; + } + + if( ! lcl_getCellRangeAddressFromXMLString( + rXMLString, + nStartPos, nEndPos - 1, + aResult )) + { + // if an error occurred, bail out + return CellRange(); + } + } + + return aResult; +} + +OUString getXMLStringFromCellRange( const CellRange & rRange ) +{ + static const sal_Unicode aSpace( ' ' ); + static const sal_Unicode aQuote( '\'' ); + + OUStringBuffer aBuffer; + + if( !rRange.aTableName.isEmpty()) + { + bool bNeedsEscaping = ( rRange.aTableName.indexOf( aQuote ) > -1 ); + bool bNeedsQuoting = bNeedsEscaping || ( rRange.aTableName.indexOf( aSpace ) > -1 ); + + // quote table name if it contains spaces or quotes + if( bNeedsQuoting ) + { + // leading quote + aBuffer.append( aQuote ); + + // escape existing quotes + if( bNeedsEscaping ) + { + const sal_Unicode * pTableNameBeg = rRange.aTableName.getStr(); + + // append the quoted string at the buffer + std::for_each( pTableNameBeg, + pTableNameBeg + rRange.aTableName.getLength(), + lcl_Escape( aBuffer ) ); + } + else + aBuffer.append( rRange.aTableName ); + + // final quote + aBuffer.append( aQuote ); + } + else + aBuffer.append( rRange.aTableName ); + } + lcl_getXMLStringForCell( rRange.aUpperLeft, &aBuffer ); + + if( ! rRange.aLowerRight.empty()) + { + // we have a range (not a single cell) + aBuffer.append( ':' ); + lcl_getXMLStringForCell( rRange.aLowerRight, &aBuffer ); + } + + return aBuffer.makeStringAndClear(); +} + +} // namespace XMLRangeHelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/XMLRangeHelper.hxx b/sw/source/core/unocore/XMLRangeHelper.hxx new file mode 100644 index 000000000..51966860f --- /dev/null +++ b/sw/source/core/unocore/XMLRangeHelper.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +//!! +//!! This file is an exact copy of the same file in chart2 project +//!! + +#ifndef INCLUDED_SW_SOURCE_CORE_UNOCORE_XMLRANGEHELPER_HXX +#define INCLUDED_SW_SOURCE_CORE_UNOCORE_XMLRANGEHELPER_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> + +namespace XMLRangeHelper +{ + +struct Cell +{ + sal_Int32 nColumn; + sal_Int32 nRow; + bool bRelativeColumn; + bool bRelativeRow; + bool bIsEmpty; + + Cell() : + nColumn(0), + nRow(0), + bRelativeColumn(false), + bRelativeRow(false), + bIsEmpty(true) + {} + + bool empty() const { return bIsEmpty; } +}; + +struct CellRange +{ + Cell aUpperLeft; + Cell aLowerRight; + OUString aTableName; +}; + +CellRange getCellRangeFromXMLString( const OUString & rXMLString ); + +OUString getXMLStringFromCellRange( const CellRange & rRange ); + +} // namespace XMLRangeHelper + +// INCLUDED_SW_SOURCE_CORE_UNOCORE_XMLRANGEHELPER_HXX +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/swunohelper.cxx b/sw/source/core/unocore/swunohelper.cxx new file mode 100644 index 000000000..9bfa185af --- /dev/null +++ b/sw/source/core/unocore/swunohelper.cxx @@ -0,0 +1,336 @@ +/* -*- 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 <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XContentIdentifier.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/extract.hxx> +#include <o3tl/any.hxx> +#include <tools/urlobj.hxx> +#include <tools/datetime.hxx> +#include <rtl/ustring.hxx> +#include <osl/diagnose.h> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/content.hxx> +#include <swunohelper.hxx> +#include <svx/xdef.hxx> +#include <svx/xfillit0.hxx> +#include <editeng/memberids.h> +#include <svl/itemset.hxx> + +using namespace com::sun::star; + +namespace SWUnoHelper +{ + +sal_Int32 GetEnumAsInt32( const css::uno::Any& rVal ) +{ + sal_Int32 nReturn = 0; + if (! ::cppu::enum2int(nReturn,rVal) ) + OSL_FAIL( "can't get EnumAsInt32" ); + return nReturn; +} + +// methods for UCB actions +bool UCB_DeleteFile( const OUString& rURL ) +{ + bool bRemoved; + try + { + ucbhelper::Content aTempContent( rURL, + css::uno::Reference< css::ucb::XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + aTempContent.executeCommand("delete", css::uno::makeAny( true ) ); + bRemoved = true; + } + catch( css::uno::Exception& ) + { + bRemoved = false; + OSL_FAIL( "Exception from executeCommand( delete )" ); + } + return bRemoved; +} + +bool UCB_MoveFile( const OUString& rURL, const OUString& rNewURL ) +{ + bool bCopyCompleted = true; + try + { + INetURLObject aURL( rNewURL ); + const OUString sName(aURL.GetLastName()); + aURL.removeSegment(); + const OUString sMainURL( aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE) ); + + ucbhelper::Content aTempContent( sMainURL, + css::uno::Reference< css::ucb::XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + + css::ucb::TransferInfo aInfo; + aInfo.NameClash = css::ucb::NameClash::ERROR; + aInfo.NewTitle = sName; + aInfo.SourceURL = rURL; + aInfo.MoveData = true; + aTempContent.executeCommand( "transfer", uno::Any(aInfo) ); + } + catch( css::uno::Exception& ) + { + OSL_FAIL( "Exception from executeCommand( transfer )" ); + bCopyCompleted = false; + } + return bCopyCompleted; +} + +bool UCB_IsCaseSensitiveFileName( const OUString& rURL ) +{ + bool bCaseSensitive; + try + { + INetURLObject aTempObj( rURL ); + aTempObj.SetBase( aTempObj.GetBase().toAsciiLowerCase() ); + css::uno::Reference< css::ucb::XContentIdentifier > xRef1 = new + ucbhelper::ContentIdentifier( aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )); + + aTempObj.SetBase(aTempObj.GetBase().toAsciiUpperCase()); + css::uno::Reference< css::ucb::XContentIdentifier > xRef2 = new + ucbhelper::ContentIdentifier( aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )); + + css::uno::Reference< css::ucb::XUniversalContentBroker > xUcb = + css::ucb::UniversalContentBroker::create(comphelper::getProcessComponentContext()); + + sal_Int32 nCompare = xUcb->compareContentIds( xRef1, xRef2 ); + bCaseSensitive = 0 != nCompare; + } + catch( css::uno::Exception& ) + { + bCaseSensitive = false; + OSL_FAIL( "Exception from compareContentIds()" ); + } + return bCaseSensitive; +} + +bool UCB_IsReadOnlyFileName( const OUString& rURL ) +{ + bool bIsReadOnly = false; + try + { + ucbhelper::Content aCnt( rURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + css::uno::Any aAny = aCnt.getPropertyValue("IsReadOnly"); + if(aAny.hasValue()) + bIsReadOnly = *o3tl::doAccess<bool>(aAny); + } + catch( css::uno::Exception& ) + { + bIsReadOnly = false; + } + return bIsReadOnly; +} + +bool UCB_IsFile( const OUString& rURL ) +{ + bool bExists = false; + try + { + ::ucbhelper::Content aContent( rURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + bExists = aContent.isDocument(); + } + catch (css::uno::Exception &) + { + } + return bExists; +} + +bool UCB_IsDirectory( const OUString& rURL ) +{ + bool bExists = false; + try + { + ::ucbhelper::Content aContent( rURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + bExists = aContent.isFolder(); + } + catch (css::uno::Exception &) + { + } + return bExists; +} + + // get a list of files from the folder of the URL + // options: pExtension = 0 -> all, else this specific extension + // pDateTime != 0 -> returns also the modified date/time of + // the files in a std::vector<OUString> --> + // !! objects must be deleted from the caller!! +bool UCB_GetFileListOfFolder( const OUString& rURL, + std::vector<OUString>& rList, + const OUString* pExtension, + std::vector< ::DateTime >* pDateTimeList ) +{ + bool bOk = false; + try + { + ucbhelper::Content aCnt( rURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + css::uno::Reference< css::sdbc::XResultSet > xResultSet; + + const sal_Int32 nSeqSize = pDateTimeList ? 2 : 1; + css::uno::Sequence < OUString > aProps( nSeqSize ); + OUString* pProps = aProps.getArray(); + pProps[ 0 ] = "Title"; + if( pDateTimeList ) + pProps[ 1 ] = "DateModified"; + + try + { + xResultSet = aCnt.createCursor( aProps, ::ucbhelper::INCLUDE_DOCUMENTS_ONLY ); + } + catch( css::uno::Exception& ) + { + OSL_FAIL( "create cursor failed!" ); + } + + if( xResultSet.is() ) + { + css::uno::Reference< css::sdbc::XRow > xRow( xResultSet, css::uno::UNO_QUERY ); + const sal_Int32 nExtLen = pExtension ? pExtension->getLength() : 0; + try + { + if( xResultSet->first() ) + { + do { + const OUString sTitle( xRow->getString( 1 ) ); + if( !nExtLen || + ( sTitle.getLength() > nExtLen && + sTitle.endsWith( *pExtension )) ) + { + rList.push_back( sTitle ); + + if( pDateTimeList ) + { + css::util::DateTime aStamp = xRow->getTimestamp(2); + ::DateTime aDateTime( + ::Date( aStamp.Day, + aStamp.Month, + aStamp.Year ), + ::tools::Time( aStamp.Hours, + aStamp.Minutes, + aStamp.Seconds, + aStamp.NanoSeconds )); + pDateTimeList->push_back( aDateTime ); + } + } + + } while( xResultSet->next() ); + } + bOk = true; + } + catch( css::uno::Exception& ) + { + OSL_FAIL( "Exception caught!" ); + } + } + } + catch( css::uno::Exception& ) + { + OSL_FAIL( "Exception caught!" ); + bOk = false; + } + return bOk; +} + +bool needToMapFillItemsToSvxBrushItemTypes(const SfxItemSet& rSet, + sal_uInt16 const nMID) +{ + const XFillStyleItem* pXFillStyleItem(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE, false)); + + if(!pXFillStyleItem) + { + return false; + } + + // here different FillStyles can be excluded for export; it will depend on the + // quality these fallbacks can reach. That again is done in getSvxBrushItemFromSourceSet, + // take a look there how the superset of DrawObject FillStyles is mapped to SvxBrushItem. + const drawing::FillStyle eFill = pXFillStyleItem->GetValue(); + switch (eFill) + { + case drawing::FillStyle_NONE: + // claim that BackColor and BackTransparent are available so that + // fo:background="transparent" attribute is exported to override + // the parent style in case it is != NONE + switch (nMID) + { + case MID_BACK_COLOR: + case MID_BACK_COLOR_R_G_B: + case MID_GRAPHIC_TRANSPARENT: // this is *BackTransparent + return true; + default: + return false; + } + break; + case drawing::FillStyle_SOLID: + case drawing::FillStyle_GRADIENT: // gradient and hatch don't exist in + case drawing::FillStyle_HATCH: // SvxBrushItem so average color is emulated + switch (nMID) + { + case MID_BACK_COLOR: + case MID_GRAPHIC_TRANSPARENT: // this is *BackTransparent + // Gradient/Hatch always have emulated color + return (drawing::FillStyle_SOLID != eFill) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLCOLOR) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLTRANSPARENCE) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLFLOATTRANSPARENCE); + case MID_BACK_COLOR_R_G_B: + // Gradient/Hatch always have emulated color + return (drawing::FillStyle_SOLID != eFill) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLCOLOR); + case MID_BACK_COLOR_TRANSPARENCY: + return SfxItemState::SET == rSet.GetItemState(XATTR_FILLTRANSPARENCE) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLFLOATTRANSPARENCE); + } + break; + case drawing::FillStyle_BITMAP: + switch (nMID) + { + case MID_GRAPHIC: + return SfxItemState::SET == rSet.GetItemState(XATTR_FILLBITMAP); + case MID_GRAPHIC_POSITION: + return SfxItemState::SET == rSet.GetItemState(XATTR_FILLBMP_STRETCH) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLBMP_TILE) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLBMP_POS); + case MID_GRAPHIC_TRANSPARENT: + case MID_GRAPHIC_TRANSPARENCY: + return SfxItemState::SET == rSet.GetItemState(XATTR_FILLTRANSPARENCE) + || SfxItemState::SET == rSet.GetItemState(XATTR_FILLFLOATTRANSPARENCE); + } + break; + default: + assert(false); + } + + + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unobkm.cxx b/sw/source/core/unocore/unobkm.cxx new file mode 100644 index 000000000..c8fa27faa --- /dev/null +++ b/sw/source/core/unocore/unobkm.cxx @@ -0,0 +1,734 @@ +/* -*- 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 <unobookmark.hxx> + +#include <comphelper/interfacecontainer2.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <svl/itemprop.hxx> +#include <svl/listener.hxx> +#include <vcl/svapp.hxx> +#include <xmloff/odffields.hxx> + +#include <TextCursorHelper.hxx> +#include <unotextrange.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <IMark.hxx> +#include <crossrefbookmark.hxx> +#include <doc.hxx> +#include <docsh.hxx> + +using namespace ::sw::mark; +using namespace ::com::sun::star; + +class SwXBookmark::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + SwDoc* m_pDoc; + ::sw::mark::IMark* m_pRegisteredBookmark; + OUString m_sMarkName; + bool m_bHidden; + OUString m_HideCondition; + + Impl( SwDoc *const pDoc ) + : m_EventListeners(m_Mutex) + , m_pDoc(pDoc) + , m_pRegisteredBookmark(nullptr) + , m_bHidden(false) + { + // DO NOT registerInMark here! (because SetXBookmark would delete rThis) + } + + void registerInMark(SwXBookmark & rThis, ::sw::mark::IMark *const pBkmk); +protected: + virtual void Notify(const SfxHint&) override; + +}; + +void SwXBookmark::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pRegisteredBookmark = nullptr; + m_pDoc = nullptr; + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); + } +} + +void SwXBookmark::Impl::registerInMark(SwXBookmark& rThis, + ::sw::mark::IMark* const pBkmk) +{ + const uno::Reference<text::XTextContent> xBookmark(&rThis); + if (pBkmk) + { + EndListeningAll(); + StartListening(pBkmk->GetNotifier()); + ::sw::mark::MarkBase *const pMarkBase(dynamic_cast< ::sw::mark::MarkBase * >(pBkmk)); + OSL_ENSURE(pMarkBase, "registerInMark: no MarkBase?"); + if (pMarkBase) + { + pMarkBase->SetXBookmark(xBookmark); + } + assert(m_pDoc == nullptr || m_pDoc == pBkmk->GetMarkPos().GetDoc()); + m_pDoc = pBkmk->GetMarkPos().GetDoc(); + } + else if (m_pRegisteredBookmark) + { + m_sMarkName = m_pRegisteredBookmark->GetName(); + + // the following applies only to bookmarks (not to fieldmarks) + IBookmark* pBookmark = dynamic_cast<IBookmark*>(m_pRegisteredBookmark); + if (pBookmark) + { + m_bHidden = pBookmark->IsHidden(); + m_HideCondition = pBookmark->GetHideCondition(); + } + EndListeningAll(); + } + m_pRegisteredBookmark = pBkmk; + // need a permanent Reference to initialize m_wThis + m_wThis = xBookmark; +} + +void SwXBookmark::registerInMark(SwXBookmark & rThis, + ::sw::mark::IMark *const pBkmk) +{ + m_pImpl->registerInMark( rThis, pBkmk ); +} + +::sw::mark::IMark* SwXBookmark::GetBookmark() const +{ + return m_pImpl->m_pRegisteredBookmark; +} + +IDocumentMarkAccess* SwXBookmark::GetIDocumentMarkAccess() +{ + return m_pImpl->m_pDoc->getIDocumentMarkAccess(); +} + +SwXBookmark::SwXBookmark(SwDoc *const pDoc) + : m_pImpl( new SwXBookmark::Impl(pDoc) ) +{ +} + +SwXBookmark::SwXBookmark() + : m_pImpl( new SwXBookmark::Impl(nullptr) ) +{ +} + +SwXBookmark::~SwXBookmark() +{ +} + +uno::Reference<text::XTextContent> SwXBookmark::CreateXBookmark( + SwDoc & rDoc, + ::sw::mark::IMark *const pBookmark) +{ + // #i105557#: do not iterate over the registered clients: race condition + ::sw::mark::MarkBase *const pMarkBase(dynamic_cast< ::sw::mark::MarkBase * >(pBookmark)); + OSL_ENSURE(!pBookmark || pMarkBase, "CreateXBookmark: no MarkBase?"); + uno::Reference<text::XTextContent> xBookmark; + if (pMarkBase) + { + xBookmark = pMarkBase->GetXBookmark(); + } + if (!xBookmark.is()) + { + OSL_ENSURE(!pBookmark || + dynamic_cast< ::sw::mark::IBookmark* >(pBookmark) || + IDocumentMarkAccess::GetType(*pBookmark) == IDocumentMarkAccess::MarkType::ANNOTATIONMARK, + "<SwXBookmark::GetObject(..)>" + "SwXBookmark requested for non-bookmark mark and non-annotation mark."); + SwXBookmark *const pXBookmark = + pBookmark ? new SwXBookmark(&rDoc) : new SwXBookmark; + xBookmark.set(pXBookmark); + pXBookmark->m_pImpl->registerInMark(*pXBookmark, pMarkBase); + } + return xBookmark; +} + +::sw::mark::IMark const* SwXBookmark::GetBookmarkInDoc(SwDoc const*const pDoc, + const uno::Reference< lang::XUnoTunnel> & xUT) +{ + SwXBookmark *const pXBkm( + ::sw::UnoTunnelGetImplementation<SwXBookmark>(xUT)); + if (pXBkm && (pDoc == pXBkm->m_pImpl->m_pDoc)) + { + return pXBkm->m_pImpl->m_pRegisteredBookmark; + } + return nullptr; +} + +namespace +{ + class theSwXBookmarkUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXBookmarkUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXBookmark::getUnoTunnelId() +{ + return theSwXBookmarkUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXBookmark::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return ::sw::UnoTunnelImpl<SwXBookmark>(rId, this); +} + +void SwXBookmark::attachToRangeEx( + const uno::Reference< text::XTextRange > & xTextRange, + IDocumentMarkAccess::MarkType eType) +{ + if (m_pImpl->m_pRegisteredBookmark) + { + throw uno::RuntimeException(); + } + + const uno::Reference<lang::XUnoTunnel> xRangeTunnel( + xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + if(xRangeTunnel.is()) + { + pRange = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + } + + SwDoc *const pDoc = + pRange ? &pRange->GetDoc() : (pCursor ? pCursor->GetDoc() : nullptr); + if (!pDoc) + { + throw lang::IllegalArgumentException(); + } + + m_pImpl->m_pDoc = pDoc; + SwUnoInternalPaM aPam(*m_pImpl->m_pDoc); + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + UnoActionContext aCont(m_pImpl->m_pDoc); + if (m_pImpl->m_sMarkName.isEmpty()) + { + m_pImpl->m_sMarkName = "Bookmark"; + } + if ((eType == IDocumentMarkAccess::MarkType::BOOKMARK) && + ::sw::mark::CrossRefNumItemBookmark::IsLegalName(m_pImpl->m_sMarkName)) + { + eType = IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK; + } + else if ((eType == IDocumentMarkAccess::MarkType::BOOKMARK) && + ::sw::mark::CrossRefHeadingBookmark::IsLegalName(m_pImpl->m_sMarkName) && + IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark( aPam ) ) + { + eType = IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK; + } + m_pImpl->registerInMark(*this, + m_pImpl->m_pDoc->getIDocumentMarkAccess()->makeMark( + aPam, m_pImpl->m_sMarkName, eType, ::sw::mark::InsertMode::New)); + // #i81002# + // Check, if bookmark has been created. + // E.g., the creation of a cross-reference bookmark is suppress, + // if the PaM isn't a valid one for cross-reference bookmarks. + if (!m_pImpl->m_pRegisteredBookmark) + { + OSL_FAIL("<SwXBookmark::attachToRange(..)>" + " - could not create Mark."); + throw lang::IllegalArgumentException(); + } +} + +void SwXBookmark::attachToRange( const uno::Reference< text::XTextRange > & xTextRange ) +{ + attachToRangeEx(xTextRange, IDocumentMarkAccess::MarkType::BOOKMARK); +} + +void SAL_CALL SwXBookmark::attach( const uno::Reference< text::XTextRange > & xTextRange ) +{ + SolarMutexGuard aGuard; + attachToRange( xTextRange ); +} + +uno::Reference< text::XTextRange > SAL_CALL SwXBookmark::getAnchor() +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_pRegisteredBookmark) + { + throw uno::RuntimeException(); + } + return SwXTextRange::CreateXTextRange( + *m_pImpl->m_pDoc, + m_pImpl->m_pRegisteredBookmark->GetMarkPos(), + (m_pImpl->m_pRegisteredBookmark->IsExpanded()) + ? &m_pImpl->m_pRegisteredBookmark->GetOtherMarkPos() : nullptr); +} + +void SAL_CALL SwXBookmark::dispose() +{ + SolarMutexGuard aGuard; + if (m_pImpl->m_pRegisteredBookmark) + { + m_pImpl->m_pDoc->getIDocumentMarkAccess()->deleteMark( m_pImpl->m_pRegisteredBookmark ); + } +} + +void SAL_CALL SwXBookmark::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXBookmark::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +OUString SAL_CALL SwXBookmark::getName() +{ + SolarMutexGuard aGuard; + + return (m_pImpl->m_pRegisteredBookmark) + ? m_pImpl->m_pRegisteredBookmark->GetName() + : m_pImpl->m_sMarkName; +} + +void SAL_CALL SwXBookmark::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_pRegisteredBookmark) + { + m_pImpl->m_sMarkName = rName; + } + if (!m_pImpl->m_pRegisteredBookmark || (getName() == rName)) + { + return; + } + IDocumentMarkAccess *const pMarkAccess = + m_pImpl->m_pDoc->getIDocumentMarkAccess(); + if(pMarkAccess->findMark(rName) != pMarkAccess->getAllMarksEnd()) + { + throw uno::RuntimeException("setName(): name already in use", + static_cast<::cppu::OWeakObject*>(this)); + } + + SwPaM aPam(m_pImpl->m_pRegisteredBookmark->GetMarkPos()); + if (m_pImpl->m_pRegisteredBookmark->IsExpanded()) + { + aPam.SetMark(); + *aPam.GetMark() = m_pImpl->m_pRegisteredBookmark->GetOtherMarkPos(); + } + + pMarkAccess->renameMark(m_pImpl->m_pRegisteredBookmark, rName); +} + +OUString SAL_CALL +SwXBookmark::getImplementationName() +{ + return "SwXBookmark"; +} + +sal_Bool SAL_CALL SwXBookmark::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXBookmark::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextContent", + "com.sun.star.text.Bookmark", + "com.sun.star.document.LinkTarget" + }; +} + +// MetadatableMixin +::sfx2::Metadatable* SwXBookmark::GetCoreObject() +{ + return dynamic_cast< ::sfx2::Metadatable* >(m_pImpl->m_pRegisteredBookmark); +} + +uno::Reference<frame::XModel> SwXBookmark::GetModel() +{ + if (m_pImpl->m_pDoc) + { + SwDocShell const * const pShell( m_pImpl->m_pDoc->GetDocShell() ); + return pShell ? pShell->GetModel() : nullptr; + } + return nullptr; +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXBookmark::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xRef( + aSwMapProvider.GetPropertySet(PROPERTY_MAP_BOOKMARK) + ->getPropertySetInfo() ); + return xRef; +} + +void SAL_CALL +SwXBookmark::setPropertyValue(const OUString& PropertyName, + const uno::Any& rValue) +{ + if (PropertyName == UNO_NAME_BOOKMARK_HIDDEN) + { + bool bNewValue = false; + if (!(rValue >>= bNewValue)) + throw lang::IllegalArgumentException("Property BookmarkHidden requires value of type boolean", nullptr, 0); + + IBookmark* pBookmark = dynamic_cast<IBookmark*>(m_pImpl->m_pRegisteredBookmark); + if (pBookmark) + { + pBookmark->Hide(bNewValue); + } + else + { + m_pImpl->m_bHidden = bNewValue; + } + return; + } + else if (PropertyName == UNO_NAME_BOOKMARK_CONDITION) + { + OUString newValue; + if (!(rValue >>= newValue)) + throw lang::IllegalArgumentException("Property BookmarkCondition requires value of type string", nullptr, 0); + + IBookmark* pBookmark = dynamic_cast<IBookmark*>(m_pImpl->m_pRegisteredBookmark); + if (pBookmark) + { + pBookmark->SetHideCondition(newValue); + } + else + { + m_pImpl->m_HideCondition = newValue; + } + return; + } + + // nothing to set here + throw lang::IllegalArgumentException("Property is read-only: " + + PropertyName, static_cast< cppu::OWeakObject * >(this), 0 ); +} + +uno::Any SAL_CALL SwXBookmark::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard g; + + uno::Any aRet; + if (! ::sw::GetDefaultTextContentValue(aRet, rPropertyName)) + { + if(rPropertyName == UNO_LINK_DISPLAY_NAME) + { + aRet <<= getName(); + } + else if (rPropertyName == UNO_NAME_BOOKMARK_HIDDEN) + { + IBookmark* pBookmark = dynamic_cast<IBookmark*>(m_pImpl->m_pRegisteredBookmark); + if (pBookmark) + { + aRet <<= pBookmark->IsHidden(); + } + else + { + aRet <<= m_pImpl->m_bHidden; + } + } + else if (rPropertyName == UNO_NAME_BOOKMARK_CONDITION) + { + IBookmark* pBookmark = dynamic_cast<IBookmark*>(m_pImpl->m_pRegisteredBookmark); + if (pBookmark) + { + aRet <<= pBookmark->GetHideCondition(); + } + else + { + aRet <<= m_pImpl->m_HideCondition; + } + } + } + return aRet; +} + +void SAL_CALL +SwXBookmark::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXBookmark::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXBookmark::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXBookmark::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXBookmark::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXBookmark::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXBookmark::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXBookmark::removeVetoableChangeListener(): not implemented"); +} + +SwXFieldmark::SwXFieldmark(bool _isReplacementObject, SwDoc* pDc) + : SwXFieldmark_Base(pDc) + , m_bReplacementObject(_isReplacementObject) +{ } + +void SwXFieldmarkParameters::insertByName(const OUString& aName, const uno::Any& aElement) +{ + SolarMutexGuard aGuard; + IFieldmark::parameter_map_t* pParameters = getCoreParameters(); + if(pParameters->find(aName) != pParameters->end()) + throw container::ElementExistException(); + (*pParameters)[aName] = aElement; +} + +void SwXFieldmarkParameters::removeByName(const OUString& aName) +{ + SolarMutexGuard aGuard; + if(!getCoreParameters()->erase(aName)) + throw container::NoSuchElementException(); +} + +void SwXFieldmarkParameters::replaceByName(const OUString& aName, const uno::Any& aElement) +{ + SolarMutexGuard aGuard; + IFieldmark::parameter_map_t* pParameters = getCoreParameters(); + IFieldmark::parameter_map_t::iterator pEntry = pParameters->find(aName); + if(pEntry == pParameters->end()) + throw container::NoSuchElementException(); + pEntry->second = aElement; +} + +uno::Any SwXFieldmarkParameters::getByName(const OUString& aName) +{ + SolarMutexGuard aGuard; + IFieldmark::parameter_map_t* pParameters = getCoreParameters(); + IFieldmark::parameter_map_t::iterator pEntry = pParameters->find(aName); + if(pEntry == pParameters->end()) + throw container::NoSuchElementException(); + return pEntry->second; +} + +uno::Sequence<OUString> SwXFieldmarkParameters::getElementNames() +{ + SolarMutexGuard aGuard; + IFieldmark::parameter_map_t* pParameters = getCoreParameters(); + return comphelper::mapKeysToSequence(*pParameters); +} + +sal_Bool SwXFieldmarkParameters::hasByName(const OUString& aName) +{ + SolarMutexGuard aGuard; + IFieldmark::parameter_map_t* pParameters = getCoreParameters(); + return (pParameters->find(aName) != pParameters->end()); +} + +uno::Type SwXFieldmarkParameters::getElementType() +{ + return ::cppu::UnoType<void>::get(); +} + +sal_Bool SwXFieldmarkParameters::hasElements() +{ + SolarMutexGuard aGuard; + return !getCoreParameters()->empty(); +} + +void SwXFieldmarkParameters::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFieldmark = nullptr; +} + +IFieldmark::parameter_map_t* SwXFieldmarkParameters::getCoreParameters() +{ + if(!m_pFieldmark) + throw uno::RuntimeException(); + return m_pFieldmark->GetParameters(); +} + +void SwXFieldmark::attachToRange( const uno::Reference < text::XTextRange >& xTextRange ) +{ + + attachToRangeEx( xTextRange, + ( m_bReplacementObject ? IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK : IDocumentMarkAccess::MarkType::TEXT_FIELDMARK ) ); +} + +OUString SwXFieldmark::getFieldType() +{ + SolarMutexGuard aGuard; + const IFieldmark *pBkm = dynamic_cast<const IFieldmark*>(GetBookmark()); + if(!pBkm) + throw uno::RuntimeException(); + return pBkm->GetFieldname(); +} + +void SwXFieldmark::setFieldType(const OUString & fieldType) +{ + SolarMutexGuard aGuard; + IFieldmark *pBkm = dynamic_cast<IFieldmark*>(GetBookmark()); + if(!pBkm) + throw uno::RuntimeException(); + if(fieldType != getFieldType()) + { + if(fieldType == ODF_FORMDROPDOWN || fieldType == ODF_FORMCHECKBOX || fieldType == ODF_FORMDATE) + { + ::sw::mark::IFieldmark* pNewFieldmark = GetIDocumentMarkAccess()->changeFormFieldmarkType(pBkm, fieldType); + if (pNewFieldmark) + { + registerInMark(*this, pNewFieldmark); + return; + } + } + + // We did not generate a new fieldmark, so set the type ID + pBkm->SetFieldname(fieldType); + } +} + +uno::Reference<container::XNameContainer> SwXFieldmark::getParameters() +{ + SolarMutexGuard aGuard; + IFieldmark *pBkm = dynamic_cast<IFieldmark*>(GetBookmark()); + if(!pBkm) + throw uno::RuntimeException(); + return uno::Reference<container::XNameContainer>(new SwXFieldmarkParameters(pBkm)); +} + +uno::Reference<text::XTextContent> +SwXFieldmark::CreateXFieldmark(SwDoc & rDoc, ::sw::mark::IMark *const pMark, + bool const isReplacementObject) +{ + // #i105557#: do not iterate over the registered clients: race condition + ::sw::mark::MarkBase *const pMarkBase( + dynamic_cast< ::sw::mark::MarkBase * >(pMark)); + assert(!pMark || pMarkBase); + uno::Reference<text::XTextContent> xMark; + if (pMarkBase) + { + xMark = pMarkBase->GetXBookmark(); + } + if (!xMark.is()) + { + // FIXME: These belong in XTextFieldsSupplier + SwXFieldmark* pXBkmk = nullptr; + if (dynamic_cast< ::sw::mark::TextFieldmark* >(pMark)) + pXBkmk = new SwXFieldmark(false, &rDoc); + else if (dynamic_cast< ::sw::mark::CheckboxFieldmark* >(pMark)) + pXBkmk = new SwXFieldmark(true, &rDoc); + else if (dynamic_cast< ::sw::mark::DropDownFieldmark* >(pMark)) + pXBkmk = new SwXFieldmark(true, &rDoc); + else if (dynamic_cast< ::sw::mark::DateFieldmark* >(pMark)) + pXBkmk = new SwXFieldmark(false, &rDoc); + else + pXBkmk = new SwXFieldmark(isReplacementObject, &rDoc); + + xMark.set(pXBkmk); + pXBkmk->registerInMark(*pXBkmk, pMarkBase); + } + return xMark; +} + +::sw::mark::ICheckboxFieldmark* +SwXFieldmark::getCheckboxFieldmark() +{ + ::sw::mark::ICheckboxFieldmark* pCheckboxFm = nullptr; + if ( getFieldType() == ODF_FORMCHECKBOX ) + { + pCheckboxFm = dynamic_cast< ::sw::mark::ICheckboxFieldmark* >( GetBookmark()); + assert( GetBookmark() == nullptr || pCheckboxFm != nullptr ); + // unclear to me whether GetBookmark() can be null here + } + return pCheckboxFm; + +} + +// support 'hidden' "Checked" property ( note: this property is just for convenience to support +// docx import filter thus not published via PropertySet info ) + +void SAL_CALL +SwXFieldmark::setPropertyValue(const OUString& PropertyName, + const uno::Any& rValue) +{ + SolarMutexGuard g; + if ( PropertyName == "Checked" ) + { + ::sw::mark::ICheckboxFieldmark* pCheckboxFm = getCheckboxFieldmark(); + bool bChecked( false ); + if ( !(pCheckboxFm && ( rValue >>= bChecked )) ) + throw uno::RuntimeException(); + + pCheckboxFm->SetChecked( bChecked ); + } + else + SwXFieldmark_Base::setPropertyValue( PropertyName, rValue ); +} + +// support 'hidden' "Checked" property ( note: this property is just for convenience to support +// docx import filter thus not published via PropertySet info ) + +uno::Any SAL_CALL SwXFieldmark::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard g; + if ( rPropertyName == "Checked" ) + { + ::sw::mark::ICheckboxFieldmark* pCheckboxFm = getCheckboxFieldmark(); + if ( !pCheckboxFm ) + throw uno::RuntimeException(); + + return uno::makeAny( pCheckboxFm->IsChecked() ); + } + return SwXFieldmark_Base::getPropertyValue( rPropertyName ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unochart.cxx b/sw/source/core/unocore/unochart.cxx new file mode 100644 index 000000000..4b0cf5738 --- /dev/null +++ b/sw/source/core/unocore/unochart.cxx @@ -0,0 +1,2717 @@ +/* -*- 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 <algorithm> + +#include <com/sun/star/chart/ChartDataRowSource.hpp> +#include <com/sun/star/chart2/data/LabelOrigin.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> + +#include "XMLRangeHelper.hxx" +#include <unochart.hxx> +#include <swtable.hxx> +#include <unoprnms.hxx> +#include <unomap.hxx> +#include <unocrsr.hxx> +#include <unotbl.hxx> +#include <doc.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <frmfmt.hxx> +#include <ndole.hxx> +#include <swtypes.hxx> +#include <strings.hrc> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> +#include <svl/itemprop.hxx> + +using namespace ::com::sun::star; + +void SwChartHelper::DoUpdateAllCharts( SwDoc* pDoc ) +{ + if (!pDoc) + return; + + SwOLENode *pONd; + SwStartNode *pStNd; + SwNodeIndex aIdx( *pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + if (nullptr != ( pONd = aIdx.GetNode().GetOLENode() ) && + pONd->GetOLEObj().GetObject().IsChart() ) + { + // Load the object and set modified + + uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + try + { + uno::Reference< util::XModifiable > xModif( xIP->getComponent(), uno::UNO_QUERY_THROW ); + xModif->setModified( true ); + } + catch ( uno::Exception& ) + { + } + + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } +} + +SwChartLockController_Helper::SwChartLockController_Helper( SwDoc *pDocument ) : + m_pDoc( pDocument ) + , m_bIsLocked( false ) +{ + m_aUnlockTimer.SetTimeout( 1500 ); + m_aUnlockTimer.SetInvokeHandler( LINK( this, SwChartLockController_Helper, DoUnlockAllCharts )); + m_aUnlockTimer.SetDebugName( "sw::SwChartLockController_Helper aUnlockTimer" ); +} + +SwChartLockController_Helper::~SwChartLockController_Helper() COVERITY_NOEXCEPT_FALSE +{ + if (m_pDoc) // still connected? + Disconnect(); +} + +void SwChartLockController_Helper::StartOrContinueLocking() +{ + if (!m_bIsLocked) + LockAllCharts(); + m_aUnlockTimer.Start(); // start or continue time of locking +} + +void SwChartLockController_Helper::Disconnect() +{ + m_aUnlockTimer.Stop(); + UnlockAllCharts(); + m_pDoc = nullptr; +} + +void SwChartLockController_Helper::LockUnlockAllCharts( bool bLock ) +{ + if (!m_pDoc) + return; + + uno::Reference< frame::XModel > xRes; + SwOLENode *pONd; + SwStartNode *pStNd; + SwNodeIndex aIdx( *m_pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + if (nullptr != ( pONd = aIdx.GetNode().GetOLENode() ) && + !pONd->GetChartTableName().isEmpty() /* is chart object? */) + { + uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + xRes.set( xIP->getComponent(), uno::UNO_QUERY ); + if (xRes.is()) + { + if (bLock) + xRes->lockControllers(); + else + xRes->unlockControllers(); + } + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + + m_bIsLocked = bLock; +} + +IMPL_LINK_NOARG( SwChartLockController_Helper, DoUnlockAllCharts, Timer *, void ) +{ + UnlockAllCharts(); +} + +static osl::Mutex & GetChartMutex() +{ + static osl::Mutex aMutex; + return aMutex; +} + +static void LaunchModifiedEvent( + ::comphelper::OInterfaceContainerHelper2 &rICH, + const uno::Reference< uno::XInterface > &rxI ) +{ + lang::EventObject aEvtObj( rxI ); + comphelper::OInterfaceIteratorHelper2 aIt( rICH ); + while (aIt.hasMoreElements()) + { + uno::Reference< util::XModifyListener > xRef( aIt.next(), uno::UNO_QUERY ); + if (xRef.is()) + xRef->modified( aEvtObj ); + } +} + +/** + * rCellRangeName needs to be of one of the following formats: + * - e.g. "A2:E5" or + * - e.g. "Table1.A2:E5" + */ +bool FillRangeDescriptor( + SwRangeDescriptor &rDesc, + const OUString &rCellRangeName ) +{ + sal_Int32 nToken = -1 == rCellRangeName.indexOf('.') ? 0 : 1; + OUString aCellRangeNoTableName( rCellRangeName.getToken( nToken, '.' ) ); + OUString aTLName( aCellRangeNoTableName.getToken(0, ':') ); // name of top left cell + OUString aBRName( aCellRangeNoTableName.getToken(1, ':') ); // name of bottom right cell + if(aTLName.isEmpty() || aBRName.isEmpty()) + return false; + + rDesc.nTop = rDesc.nLeft = rDesc.nBottom = rDesc.nRight = -1; + SwXTextTable::GetCellPosition( aTLName, rDesc.nLeft, rDesc.nTop ); + SwXTextTable::GetCellPosition( aBRName, rDesc.nRight, rDesc.nBottom ); + rDesc.Normalize(); + OSL_ENSURE( rDesc.nTop != -1 && + rDesc.nLeft != -1 && + rDesc.nBottom != -1 && + rDesc.nRight != -1, + "failed to get range descriptor" ); + OSL_ENSURE( rDesc.nTop <= rDesc.nBottom && rDesc.nLeft <= rDesc.nRight, + "invalid range descriptor"); + return true; +} + +static OUString GetCellRangeName( SwFrameFormat &rTableFormat, SwUnoCursor &rTableCursor ) +{ + OUString aRes; + + //!! see also SwXTextTableCursor::getRangeName + + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&rTableCursor); + if (!pUnoTableCursor) + return OUString(); + pUnoTableCursor->MakeBoxSels(); + + const SwStartNode* pStart; + const SwTableBox* pStartBox = nullptr; + const SwTableBox* pEndBox = nullptr; + + pStart = pUnoTableCursor->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + if (pStart) + { + const SwTable* pTable = SwTable::FindTable( &rTableFormat ); + pEndBox = pTable->GetTableBox( pStart->GetIndex()); + aRes = pEndBox->GetName(); + + if(pUnoTableCursor->HasMark()) + { + pStart = pUnoTableCursor->GetMark()->nNode.GetNode().FindTableBoxStartNode(); + pStartBox = pTable->GetTableBox( pStart->GetIndex()); + } + OSL_ENSURE( pStartBox, "start box not found" ); + OSL_ENSURE( pEndBox, "end box not found" ); + + // need to switch start and end? + if (*pUnoTableCursor->GetPoint() < *pUnoTableCursor->GetMark()) + { + const SwTableBox* pTmpBox = pStartBox; + pStartBox = pEndBox; + pEndBox = pTmpBox; + } + + if (!pStartBox) + return aRes; + + aRes = pStartBox->GetName() + ":"; + if (pEndBox) + aRes += pEndBox->GetName(); + else + aRes += pStartBox->GetName(); + } + + return aRes; +} + +static OUString GetRangeRepFromTableAndCells( const OUString &rTableName, + const OUString &rStartCell, const OUString &rEndCell, + bool bForceEndCellName ) +{ + OSL_ENSURE( !rTableName.isEmpty(), "table name missing" ); + OSL_ENSURE( !rStartCell.isEmpty(), "cell name missing" ); + OUString aRes = rTableName + "." + rStartCell; + + if (!rEndCell.isEmpty()) + { + aRes += ":" + rEndCell; + } + else if (bForceEndCellName) + { + aRes += ":" + rStartCell; + } + + return aRes; +} + +static bool GetTableAndCellsFromRangeRep( + const OUString &rRangeRepresentation, + OUString &rTableName, + OUString &rStartCell, + OUString &rEndCell, + bool bSortStartEndCells = true ) +{ + // parse range representation for table name and cell/range names + // accepted format sth like: "Table1.A2:C5" , "Table2.A2.1:B3.2" + OUString aTableName; // table name + OUString aRange; // cell range + OUString aStartCell; // name of top left cell + OUString aEndCell; // name of bottom right cell + sal_Int32 nIdx = rRangeRepresentation.indexOf( '.' ); + if (nIdx >= 0) + { + aTableName = rRangeRepresentation.copy( 0, nIdx ); + aRange = rRangeRepresentation.copy( nIdx + 1 ); + sal_Int32 nPos = aRange.indexOf( ':' ); + if (nPos >= 0) // a cell-range like "Table1.A2:D4" + { + aStartCell = aRange.copy( 0, nPos ); + aEndCell = aRange.copy( nPos + 1 ); + + // need to switch start and end cell ? + // (does not check for normalization here) + if (bSortStartEndCells && 1 == sw_CompareCellsByColFirst( aStartCell, aEndCell )) + { + OUString aTmp( aStartCell ); + aStartCell = aEndCell; + aEndCell = aTmp; + } + } + else // a single cell like in "Table1.B3" + { + aStartCell = aEndCell = aRange; + } + } + + bool bSuccess = !aTableName.isEmpty() && + !aStartCell.isEmpty() && !aEndCell.isEmpty(); + if (bSuccess) + { + rTableName = aTableName; + rStartCell = aStartCell; + rEndCell = aEndCell; + } + return bSuccess; +} + +static void GetTableByName( const SwDoc &rDoc, const OUString &rTableName, + SwFrameFormat **ppTableFormat, SwTable **ppTable) +{ + SwFrameFormat *pTableFormat = nullptr; + + // find frame format of table + //! see SwXTextTables::getByName + const size_t nCount = rDoc.GetTableFrameFormatCount(true); + for (size_t i = 0; i < nCount && !pTableFormat; ++i) + { + SwFrameFormat& rTableFormat = rDoc.GetTableFrameFormat(i, true); + if(rTableName == rTableFormat.GetName()) + pTableFormat = &rTableFormat; + } + + if (ppTableFormat) + *ppTableFormat = pTableFormat; + + if (ppTable) + *ppTable = pTableFormat ? SwTable::FindTable( pTableFormat ) : nullptr; +} + +static void GetFormatAndCreateCursorFromRangeRep( + const SwDoc *pDoc, + const OUString &rRangeRepresentation, // must be a single range (i.e. so called sub-range) + SwFrameFormat **ppTableFormat, // will be set to the table format of the table used in the range representation + std::shared_ptr<SwUnoCursor>& rpUnoCursor ) // will be set to cursor spanning the cell range (cursor will be created!) +{ + OUString aTableName; // table name + OUString aStartCell; // name of top left cell + OUString aEndCell; // name of bottom right cell + bool bNamesFound = GetTableAndCellsFromRangeRep( rRangeRepresentation, + aTableName, aStartCell, aEndCell ); + + if (!bNamesFound) + { + if (ppTableFormat) + *ppTableFormat = nullptr; + rpUnoCursor.reset(); + } + else + { + SwFrameFormat *pTableFormat = nullptr; + + // is the correct table format already provided? + if (*ppTableFormat != nullptr && (*ppTableFormat)->GetName() == aTableName) + pTableFormat = *ppTableFormat; + else + GetTableByName( *pDoc, aTableName, &pTableFormat, nullptr ); + + *ppTableFormat = pTableFormat; + + rpUnoCursor.reset(); // default result in case of failure + + SwTable *pTable = pTableFormat ? SwTable::FindTable( pTableFormat ) : nullptr; + // create new SwUnoCursor spanning the specified range + //! see also SwXTextTable::GetRangeByName + // #i80314# + // perform validation check. Thus, pass <true> as 2nd parameter to <SwTable::GetTableBox(..)> + const SwTableBox* pTLBox = + pTable ? pTable->GetTableBox( aStartCell, true ) : nullptr; + if(pTLBox) + { + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + + // set cursor to top left box of range + auto pUnoCursor = pTableFormat->GetDoc()->CreateUnoCursor(aPos, true); + pUnoCursor->Move( fnMoveForward, GoInNode ); + pUnoCursor->SetRemainInSection( false ); + + // #i80314# + // perform validation check. Thus, pass <true> as 2nd parameter to <SwTable::GetTableBox(..)> + const SwTableBox* pBRBox = pTable->GetTableBox( aEndCell, true ); + if(pBRBox) + { + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->nNode = *pBRBox->GetSttNd(); + pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rCursor = + dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + // HACK: remove pending actions for old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + rCursor.MakeBoxSels(); + rpUnoCursor = pUnoCursor; + } + } + } +} + +static bool GetSubranges( const OUString &rRangeRepresentation, + uno::Sequence< OUString > &rSubRanges, bool bNormalize ) +{ + bool bRes = true; + const sal_Int32 nLen = comphelper::string::getTokenCount(rRangeRepresentation, ';'); + uno::Sequence< OUString > aRanges( nLen ); + + sal_Int32 nCnt = 0; + if (nLen != 0) + { + OUString *pRanges = aRanges.getArray(); + OUString aFirstTable; + sal_Int32 nPos = 0; + for( sal_Int32 i = 0; i < nLen && bRes; ++i ) + { + const OUString aRange( rRangeRepresentation.getToken( 0, ';', nPos ) ); + if (!aRange.isEmpty()) + { + pRanges[nCnt] = aRange; + + OUString aTableName, aStartCell, aEndCell; + if (!GetTableAndCellsFromRangeRep( aRange, + aTableName, aStartCell, aEndCell )) + bRes = false; + + if (bNormalize) + { + sw_NormalizeRange( aStartCell, aEndCell ); + pRanges[nCnt] = GetRangeRepFromTableAndCells( aTableName, + aStartCell, aEndCell, true ); + } + + // make sure to use only a single table + if (nCnt == 0) + aFirstTable = aTableName; + else + if (aFirstTable != aTableName) bRes = false; + + ++nCnt; + } + } + } + aRanges.realloc( nCnt ); + + rSubRanges = aRanges; + return bRes; +} + +static void SortSubranges( uno::Sequence< OUString > &rSubRanges, bool bCmpByColumn ) +{ + sal_Int32 nLen = rSubRanges.getLength(); + OUString *pSubRanges = rSubRanges.getArray(); + + OUString aSmallestTableName; + OUString aSmallestStartCell; + OUString aSmallestEndCell; + + for (sal_Int32 i = 0; i < nLen; ++i) + { + sal_Int32 nIdxOfSmallest = i; + GetTableAndCellsFromRangeRep( pSubRanges[nIdxOfSmallest], + aSmallestTableName, aSmallestStartCell, aSmallestEndCell ); + if (aSmallestEndCell.isEmpty()) + aSmallestEndCell = aSmallestStartCell; + + for (sal_Int32 k = i+1; k < nLen; ++k) + { + // get cell names for sub range + OUString aTableName; + OUString aStartCell; + OUString aEndCell; + GetTableAndCellsFromRangeRep( pSubRanges[k], + aTableName, aStartCell, aEndCell ); + if (aEndCell.isEmpty()) + aEndCell = aStartCell; + + // compare cell ranges ( is the new one smaller? ) + if (-1 == sw_CompareCellRanges( aStartCell, aEndCell, + aSmallestStartCell, aSmallestEndCell, bCmpByColumn )) + { + nIdxOfSmallest = k; + aSmallestTableName = aTableName; + aSmallestStartCell = aStartCell; + aSmallestEndCell = aEndCell; + } + } + + // move smallest element to the start of the not sorted area + const OUString aTmp( pSubRanges[ nIdxOfSmallest ] ); + pSubRanges[ nIdxOfSmallest ] = pSubRanges[ i ]; + pSubRanges[ i ] = aTmp; + } +} + +SwChartDataProvider::SwChartDataProvider( const SwDoc* pSwDoc ) : + m_aEventListeners( GetChartMutex() ), + m_pDoc( pSwDoc ) +{ + m_bDisposed = false; +} + +SwChartDataProvider::~SwChartDataProvider() +{ +} + +uno::Reference< chart2::data::XDataSource > SwChartDataProvider::Impl_createDataSource( + const uno::Sequence< beans::PropertyValue >& rArguments, bool bTestOnly ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Reference< chart2::data::XDataSource > xRes; + + if (!m_pDoc) + throw uno::RuntimeException("Not connected to a document."); + + // get arguments + OUString aRangeRepresentation; + uno::Sequence< sal_Int32 > aSequenceMapping; + bool bFirstIsLabel = false; + bool bDtaSrcIsColumns = true; // true : DataSource will be sequence of columns + // false: DataSource will be sequence of rows + + OUString aChartOleObjectName; //work around wrong writer ranges ( see Issue 58464 ) + sal_Int32 nArgs = rArguments.getLength(); + OSL_ENSURE( nArgs != 0, "no properties provided" ); + if (nArgs == 0) + return xRes; + for (const beans::PropertyValue& rArg : rArguments) + { + if ( rArg.Name == "DataRowSource" ) + { + chart::ChartDataRowSource eSource; + if (!(rArg.Value >>= eSource)) + { + sal_Int32 nTmp = 0; + if (!(rArg.Value >>= nTmp)) + throw lang::IllegalArgumentException(); + eSource = static_cast< chart::ChartDataRowSource >( nTmp ); + } + bDtaSrcIsColumns = eSource == chart::ChartDataRowSource_COLUMNS; + } + else if ( rArg.Name == "FirstCellAsLabel" ) + { + if (!(rArg.Value >>= bFirstIsLabel)) + throw lang::IllegalArgumentException(); + } + else if ( rArg.Name == "CellRangeRepresentation" ) + { + if (!(rArg.Value >>= aRangeRepresentation)) + throw lang::IllegalArgumentException(); + } + else if ( rArg.Name == "SequenceMapping" ) + { + if (!(rArg.Value >>= aSequenceMapping)) + throw lang::IllegalArgumentException(); + } + else if ( rArg.Name == "ChartOleObjectName" ) + { + if (!(rArg.Value >>= aChartOleObjectName)) + throw lang::IllegalArgumentException(); + } + } + + uno::Sequence< OUString > aSubRanges; + // get sub-ranges and check that they all are from the very same table + bool bOk = GetSubranges( aRangeRepresentation, aSubRanges, true ); + + if (!bOk && m_pDoc && !aChartOleObjectName.isEmpty() ) + { + //try to correct the range here + //work around wrong writer ranges ( see Issue 58464 ) + OUString aChartTableName; + + const SwNodes& rNodes = m_pDoc->GetNodes(); + for( sal_uLong nN = rNodes.Count(); nN--; ) + { + SwNodePtr pNode = rNodes[nN]; + if( !pNode ) + continue; + const SwOLENode* pOleNode = pNode->GetOLENode(); + if( !pOleNode ) + continue; + const SwOLEObj& rOObj = pOleNode->GetOLEObj(); + if( aChartOleObjectName == rOObj.GetCurrentPersistName() ) + { + aChartTableName = pOleNode->GetChartTableName(); + break; + } + } + + if( !aChartTableName.isEmpty() ) + { + //the wrong range is still shifted one row down + //thus the first row is missing and an invalid row at the end is added. + //Therefore we need to shift the range one row up + SwRangeDescriptor aDesc; + if (aRangeRepresentation.isEmpty()) + return xRes; // we can't handle this thus returning an empty references + + aRangeRepresentation = aRangeRepresentation.copy( 1 ); // get rid of '.' to have only the cell range left + FillRangeDescriptor( aDesc, aRangeRepresentation ); + aDesc.Normalize(); + + if (aDesc.nTop <= 0) // no chance to shift the range one row up? + return xRes; // we can't handle this thus returning an empty references + + aDesc.nTop -= 1; + aDesc.nBottom -= 1; + + OUString aNewStartCell( sw_GetCellName( aDesc.nLeft, aDesc.nTop ) ); + OUString aNewEndCell( sw_GetCellName( aDesc.nRight, aDesc.nBottom ) ); + aRangeRepresentation = GetRangeRepFromTableAndCells( + aChartTableName, aNewStartCell, aNewEndCell, true ); + bOk = GetSubranges( aRangeRepresentation, aSubRanges, true ); + } + } + if (!bOk) // different tables used, or incorrect range specifiers + throw lang::IllegalArgumentException(); + + SortSubranges( aSubRanges, bDtaSrcIsColumns ); + + // get table format for that single table from above + SwFrameFormat *pTableFormat = nullptr; // pointer to table format + std::shared_ptr<SwUnoCursor> pUnoCursor; // here required to check if the cells in the range do actually exist + if (aSubRanges.hasElements()) + GetFormatAndCreateCursorFromRangeRep( m_pDoc, aSubRanges[0], &pTableFormat, pUnoCursor ); + + if (!pTableFormat || !pUnoCursor) + throw lang::IllegalArgumentException(); + + SwTable* pTable = SwTable::FindTable(pTableFormat); + if (pTable->IsTableComplex()) + return xRes; // we can't handle this thus returning an empty references + + // get a character map in the size of the table to mark + // all the ranges to use in + sal_Int32 nRows = pTable->GetTabLines().size(); + sal_Int32 nCols = pTable->GetTabLines().front()->GetTabBoxes().size(); + std::vector<std::vector<char>> aMap(nRows); + for (sal_Int32 i = 0; i < nRows; ++i) + aMap[i].resize(nCols); + + // iterate over subranges and mark used cells in above map + //!! by proceeding this way we automatically get rid of + //!! multiple listed or overlapping cell ranges which should + //!! just be ignored silently + for (const OUString& rSubRange : std::as_const(aSubRanges)) + { + OUString aTableName, aStartCell, aEndCell; + bool bOk2 = GetTableAndCellsFromRangeRep( + rSubRange, aTableName, aStartCell, aEndCell ); + OSL_ENSURE(bOk2, "failed to get table and start/end cells"); + + sal_Int32 nStartRow, nStartCol, nEndRow, nEndCol; + SwXTextTable::GetCellPosition(aStartCell, nStartCol, nStartRow); + SwXTextTable::GetCellPosition(aEndCell, nEndCol, nEndRow); + OSL_ENSURE( nStartRow <= nEndRow && nStartCol <= nEndCol, + "cell range not normalized"); + + // test if the ranges span more than the available cells + if( nStartRow < 0 || nEndRow >= nRows || + nStartCol < 0 || nEndCol >= nCols ) + { + throw lang::IllegalArgumentException(); + } + for (sal_Int32 k1 = nStartRow; k1 <= nEndRow; ++k1) + { + for (sal_Int32 k2 = nStartCol; k2 <= nEndCol; ++k2) + aMap[k1][k2] = 'x'; + } + } + + // find label and data sequences to use + + sal_Int32 oi; // outer index (slower changing index) + sal_Int32 ii; // inner index (faster changing index) + sal_Int32 oiEnd = bDtaSrcIsColumns ? nCols : nRows; + sal_Int32 iiEnd = bDtaSrcIsColumns ? nRows : nCols; + std::vector<sal_Int32> aLabelIdx(oiEnd); + std::vector<sal_Int32> aDataStartIdx(oiEnd); + std::vector<sal_Int32> aDataLen(oiEnd); + for (oi = 0; oi < oiEnd; ++oi) + { + aLabelIdx[oi] = -1; + aDataStartIdx[oi] = -1; + aDataLen[oi] = 0; + } + + for (oi = 0; oi < oiEnd; ++oi) + { + ii = 0; + while (ii < iiEnd) + { + char &rChar = bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii]; + + // label should be used but is not yet found? + if (rChar == 'x' && bFirstIsLabel && aLabelIdx[oi] == -1) + { + aLabelIdx[oi] = ii; + rChar = 'L'; // setting a different char for labels here + // makes the test for the data sequence below + // easier + } + + // find data sequence + if (rChar == 'x' && aDataStartIdx[oi] == -1) + { + aDataStartIdx[oi] = ii; + + // get length of data sequence + sal_Int32 nL = 0; + while (ii< iiEnd && 'x' == (bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii])) + { + ++nL; ++ii; + } + aDataLen[oi] = nL; + + // check that there is no other separate sequence of data + // to be found because that is not supported + while (ii < iiEnd) + { + if ('x' == (bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii])) + throw lang::IllegalArgumentException(); + ++ii; + } + } + else + ++ii; + } + } + + // make some other consistency checks while calculating + // the number of XLabeledDataSequence to build: + // - labels should always be used or not at all + // - the data sequences should have equal non-zero length + sal_Int32 nNumLDS = 0; + if (oiEnd > 0) + { + sal_Int32 nFirstSeqLen = 0; + sal_Int32 nFirstSeqLabelIdx = -1; + bool bFirstFound = false; + for (oi = 0; oi < oiEnd; ++oi) + { + // row/col used at all? + if (aDataStartIdx[oi] != -1 && + (!bFirstIsLabel || aLabelIdx[oi] != -1)) + { + ++nNumLDS; + if (!bFirstFound) + { + nFirstSeqLen = aDataLen[oi]; + nFirstSeqLabelIdx = aLabelIdx[oi]; + bFirstFound = true; + } + else + { + if (nFirstSeqLen != aDataLen[oi] || + nFirstSeqLabelIdx != aLabelIdx[oi]) + throw lang::IllegalArgumentException(); + } + } + } + } + if (nNumLDS == 0) + throw lang::IllegalArgumentException(); + + // now we should have all necessary data to build a proper DataSource + // thus if we came this far there should be no further problem + if (bTestOnly) + return xRes; // have createDataSourcePossible return true + + // create data source from found label and data sequences + uno::Sequence<uno::Reference<chart2::data::XDataSequence>> aLabelSeqs(nNumLDS); + uno::Reference<chart2::data::XDataSequence>* pLabelSeqs = aLabelSeqs.getArray(); + uno::Sequence<uno::Reference<chart2::data::XDataSequence>> aDataSeqs(nNumLDS); + uno::Reference<chart2::data::XDataSequence>* pDataSeqs = aDataSeqs.getArray(); + sal_Int32 nSeqsIdx = 0; + for (oi = 0; oi < oiEnd; ++oi) + { + // row/col not used? (see if-statement above where nNumLDS was counted) + if (!(aDataStartIdx[oi] != -1 && + (!bFirstIsLabel || aLabelIdx[oi] != -1))) + continue; + + // get cell ranges for label and data + + SwRangeDescriptor aLabelDesc; + SwRangeDescriptor aDataDesc; + if (bDtaSrcIsColumns) // use columns + { + aLabelDesc.nTop = aLabelIdx[oi]; + aLabelDesc.nLeft = oi; + aLabelDesc.nBottom = aLabelDesc.nTop; + aLabelDesc.nRight = oi; + + aDataDesc.nTop = aDataStartIdx[oi]; + aDataDesc.nLeft = oi; + aDataDesc.nBottom = aDataDesc.nTop + aDataLen[oi] - 1; + aDataDesc.nRight = oi; + } + else // use rows + { + aLabelDesc.nTop = oi; + aLabelDesc.nLeft = aLabelIdx[oi]; + aLabelDesc.nBottom = oi; + aLabelDesc.nRight = aLabelDesc.nLeft; + + aDataDesc.nTop = oi; + aDataDesc.nLeft = aDataStartIdx[oi]; + aDataDesc.nBottom = oi; + aDataDesc.nRight = aDataDesc.nLeft + aDataLen[oi] - 1; + } + const OUString aBaseName = pTableFormat->GetName() + "."; + + OUString aLabelRange; + if (aLabelIdx[oi] != -1) + { + aLabelRange = aBaseName + + sw_GetCellName( aLabelDesc.nLeft, aLabelDesc.nTop ) + + ":" + sw_GetCellName( aLabelDesc.nRight, aLabelDesc.nBottom ); + } + + OUString aDataRange = aBaseName + + sw_GetCellName( aDataDesc.nLeft, aDataDesc.nTop ) + + ":" + sw_GetCellName( aDataDesc.nRight, aDataDesc.nBottom ); + + // get cursors spanning the cell ranges for label and data + std::shared_ptr<SwUnoCursor> pLabelUnoCursor; + std::shared_ptr<SwUnoCursor> pDataUnoCursor; + GetFormatAndCreateCursorFromRangeRep(m_pDoc, aLabelRange, &pTableFormat, pLabelUnoCursor); + GetFormatAndCreateCursorFromRangeRep(m_pDoc, aDataRange, &pTableFormat, pDataUnoCursor); + + // create XDataSequence's from cursors + if (pLabelUnoCursor) + pLabelSeqs[nSeqsIdx] = new SwChartDataSequence(*this, *pTableFormat, pLabelUnoCursor); + OSL_ENSURE(pDataUnoCursor, "pointer to data sequence missing"); + if (pDataUnoCursor) + pDataSeqs[nSeqsIdx] = new SwChartDataSequence(*this, *pTableFormat, pDataUnoCursor); + if (pLabelUnoCursor || pDataUnoCursor) + ++nSeqsIdx; + } + OSL_ENSURE(nSeqsIdx == nNumLDS, "mismatch between sequence size and num,ber of entries"); + + // build data source from data and label sequences + uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> aLDS(nNumLDS); + uno::Reference<chart2::data::XLabeledDataSequence>* pLDS = aLDS.getArray(); + for (sal_Int32 i = 0; i < nNumLDS; ++i) + { + SwChartLabeledDataSequence* pLabeledDtaSeq = new SwChartLabeledDataSequence; + pLabeledDtaSeq->setLabel(pLabelSeqs[i]); + pLabeledDtaSeq->setValues(pDataSeqs[i]); + pLDS[i] = pLabeledDtaSeq; + } + + // apply 'SequenceMapping' if it was provided + if (aSequenceMapping.hasElements()) + { + uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> aOld_LDS(aLDS); + uno::Reference<chart2::data::XLabeledDataSequence>* pOld_LDS = aOld_LDS.getArray(); + + sal_Int32 nNewCnt = 0; + for (sal_Int32 nIdx : aSequenceMapping) + { + // check that index to be used is valid + // and has not yet been used + if (0 <= nIdx && nIdx < nNumLDS && pOld_LDS[nIdx].is()) + { + pLDS[nNewCnt++] = pOld_LDS[nIdx]; + + // mark index as being used already (avoids duplicate entries) + pOld_LDS[nIdx].clear(); + } + } + // add not yet used 'old' sequences to new one + for (sal_Int32 i = 0; i < nNumLDS; ++i) + { + if (pOld_LDS[i].is()) + pLDS[nNewCnt++] = pOld_LDS[i]; + } + OSL_ENSURE(nNewCnt == nNumLDS, "unexpected size of resulting sequence"); + } + + xRes = new SwChartDataSource(aLDS); + return xRes; +} + +sal_Bool SAL_CALL SwChartDataProvider::createDataSourcePossible( + const uno::Sequence< beans::PropertyValue >& rArguments ) +{ + SolarMutexGuard aGuard; + + bool bPossible = true; + try + { + Impl_createDataSource( rArguments, true ); + } + catch (lang::IllegalArgumentException &) + { + bPossible = false; + } + + return bPossible; +} + +uno::Reference< chart2::data::XDataSource > SAL_CALL SwChartDataProvider::createDataSource( + const uno::Sequence< beans::PropertyValue >& rArguments ) +{ + SolarMutexGuard aGuard; + return Impl_createDataSource( rArguments ); +} + +/** + * Fix for #i79009 + * we need to return a property that has the same value as the property + * 'CellRangeRepresentation' but for all rows which are increased by one. + * E.g. Table1.A1:D5 -> Table1,A2:D6 + * Since the problem is only for old charts which did not support multiple + * we do not need to provide that property/string if the 'CellRangeRepresentation' + * contains multiple ranges. + */ +OUString SwChartDataProvider::GetBrokenCellRangeForExport( + const OUString &rCellRangeRepresentation ) +{ + // check that we do not have multiple ranges + if (-1 == rCellRangeRepresentation.indexOf( ';' )) + { + // get current cell and table names + OUString aTableName, aStartCell, aEndCell; + GetTableAndCellsFromRangeRep( rCellRangeRepresentation, + aTableName, aStartCell, aEndCell, false ); + sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1; + SwXTextTable::GetCellPosition( aStartCell, nStartCol, nStartRow ); + SwXTextTable::GetCellPosition( aEndCell, nEndCol, nEndRow ); + + // get new cell names + ++nStartRow; + ++nEndRow; + aStartCell = sw_GetCellName( nStartCol, nStartRow ); + aEndCell = sw_GetCellName( nEndCol, nEndRow ); + + return GetRangeRepFromTableAndCells( aTableName, + aStartCell, aEndCell, false ); + } + + return OUString(); +} + +uno::Sequence< beans::PropertyValue > SAL_CALL SwChartDataProvider::detectArguments( + const uno::Reference< chart2::data::XDataSource >& xDataSource ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Sequence< beans::PropertyValue > aResult; + if (!xDataSource.is()) + return aResult; + + const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aDS_LDS( xDataSource->getDataSequences() ); + const uno::Reference< chart2::data::XLabeledDataSequence > *pDS_LDS = aDS_LDS.getConstArray(); + sal_Int32 nNumDS_LDS = aDS_LDS.getLength(); + + if (nNumDS_LDS == 0) + { + OSL_FAIL( "XLabeledDataSequence in data source contains 0 entries" ); + return aResult; + } + + SwFrameFormat *pTableFormat = nullptr; + SwTable *pTable = nullptr; + OUString aTableName; + sal_Int32 nTableRows = 0; + sal_Int32 nTableCols = 0; + + // data used to build 'CellRangeRepresentation' from later on + std::vector< std::vector< char > > aMap; + + uno::Sequence< sal_Int32 > aSequenceMapping( nNumDS_LDS ); + sal_Int32 *pSequenceMapping = aSequenceMapping.getArray(); + + OUString aCellRanges; + sal_Int16 nDtaSrcIsColumns = -1;// -1: don't know yet, 0: false, 1: true -2: neither + sal_Int32 nLabelSeqLen = -1; // used to see if labels are always used or not and have + // the expected size of 1 (i.e. if FirstCellAsLabel can + // be determined) + // -1: don't know yet, 0: not used, 1: always a single labe cell, ... + // -2: neither/failed + for (sal_Int32 nDS1 = 0; nDS1 < nNumDS_LDS; ++nDS1) + { + uno::Reference< chart2::data::XLabeledDataSequence > xLabeledDataSequence( pDS_LDS[nDS1] ); + if( !xLabeledDataSequence.is() ) + { + OSL_FAIL("got NULL for XLabeledDataSequence from Data source"); + continue; + } + const uno::Reference< chart2::data::XDataSequence > xCurLabel = xLabeledDataSequence->getLabel(); + const uno::Reference< chart2::data::XDataSequence > xCurValues = xLabeledDataSequence->getValues(); + + // get sequence lengths for label and values. + // (0 length is Ok) + sal_Int32 nCurLabelSeqLen = -1; + sal_Int32 nCurValuesSeqLen = -1; + if (xCurLabel.is()) + nCurLabelSeqLen = xCurLabel->getData().getLength(); + if (xCurValues.is()) + nCurValuesSeqLen = xCurValues->getData().getLength(); + + // check for consistent use of 'first cell as label' + if (nLabelSeqLen == -1) // set initial value to compare with below further on + nLabelSeqLen = nCurLabelSeqLen; + if (nLabelSeqLen != nCurLabelSeqLen) + nLabelSeqLen = -2; // failed / no consistent use of label cells + + // get table and cell names for label and values data sequences + // (start and end cell will be sorted, i.e. start cell <= end cell) + OUString aLabelTableName, aLabelStartCell, aLabelEndCell; + OUString aValuesTableName, aValuesStartCell, aValuesEndCell; + OUString aLabelRange, aValuesRange; + if (xCurLabel.is()) + aLabelRange = xCurLabel->getSourceRangeRepresentation(); + if (xCurValues.is()) + aValuesRange = xCurValues->getSourceRangeRepresentation(); + if ((!aLabelRange.isEmpty() && !GetTableAndCellsFromRangeRep( aLabelRange, + aLabelTableName, aLabelStartCell, aLabelEndCell )) || + !GetTableAndCellsFromRangeRep( aValuesRange, + aValuesTableName, aValuesStartCell, aValuesEndCell )) + { + return aResult; // failed -> return empty property sequence + } + + // make sure all sequences use the same table + if (aTableName.isEmpty()) + aTableName = aValuesTableName; // get initial value to compare with + if (aTableName.isEmpty() || + aTableName != aValuesTableName || + (!aLabelTableName.isEmpty() && aTableName != aLabelTableName)) + { + return aResult; // failed -> return empty property sequence + } + + // try to get 'DataRowSource' value (ROWS or COLUMNS) from inspecting + // first and last cell used in both sequences + + sal_Int32 nFirstCol = -1, nFirstRow = -1, nLastCol = -1, nLastRow = -1; + const OUString aCell( !aLabelStartCell.isEmpty() ? aLabelStartCell : aValuesStartCell ); + OSL_ENSURE( !aCell.isEmpty() , "start cell missing?" ); + SwXTextTable::GetCellPosition( aCell, nFirstCol, nFirstRow); + SwXTextTable::GetCellPosition( aValuesEndCell, nLastCol, nLastRow); + + sal_Int16 nDirection = -1; // -1: not yet set, 0: columns, 1: rows, -2: failed + if (nFirstCol == nLastCol && nFirstRow == nLastRow) // a single cell... + { + OSL_ENSURE( nCurLabelSeqLen == 0 && nCurValuesSeqLen == 1, + "trying to determine 'DataRowSource': something's fishy... should have been a single cell"); + nDirection = 0; // default direction for a single cell should be 'columns' + } + else // more than one cell is available (in values and label together!) + { + if (nFirstCol == nLastCol && nFirstRow != nLastRow) + nDirection = 1; + else if (nFirstCol != nLastCol && nFirstRow == nLastRow) + nDirection = 0; + else + { + OSL_FAIL( "trying to determine 'DataRowSource': unexpected case found" ); + nDirection = -2; + } + } + // check for consistent direction of data source + if (nDtaSrcIsColumns == -1) // set initial value to compare with below + nDtaSrcIsColumns = nDirection; + if (nDtaSrcIsColumns != nDirection) + { + nDtaSrcIsColumns = -2; // failed + } + + if (nDtaSrcIsColumns == 0 || nDtaSrcIsColumns == 1) + { + // build data to obtain 'SequenceMapping' later on + + OSL_ENSURE( nDtaSrcIsColumns == 0 || /* rows */ + nDtaSrcIsColumns == 1, /* columns */ + "unexpected value for 'nDtaSrcIsColumns'" ); + pSequenceMapping[nDS1] = nDtaSrcIsColumns ? nFirstCol : nFirstRow; + + // build data used to determine 'CellRangeRepresentation' later on + + GetTableByName( *m_pDoc, aTableName, &pTableFormat, &pTable ); + if (!pTable || pTable->IsTableComplex()) + return aResult; // failed -> return empty property sequence + nTableRows = pTable->GetTabLines().size(); + nTableCols = pTable->GetTabLines().front()->GetTabBoxes().size(); + aMap.resize( nTableRows ); + for (sal_Int32 i = 0; i < nTableRows; ++i) + aMap[i].resize( nTableCols ); + + if (!aLabelStartCell.isEmpty() && !aLabelEndCell.isEmpty()) + { + sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1; + SwXTextTable::GetCellPosition( aLabelStartCell, nStartCol, nStartRow ); + SwXTextTable::GetCellPosition( aLabelEndCell, nEndCol, nEndRow ); + if (nStartRow < 0 || nEndRow >= nTableRows || + nStartCol < 0 || nEndCol >= nTableCols) + { + return aResult; // failed -> return empty property sequence + } + for (sal_Int32 i = nStartRow; i <= nEndRow; ++i) + { + for (sal_Int32 k = nStartCol; k <= nEndCol; ++k) + { + char &rChar = aMap[i][k]; + if (rChar == '\0') // check for overlapping values and/or labels + rChar = 'L'; + else + return aResult; // failed -> return empty property sequence + } + } + } + if (!aValuesStartCell.isEmpty() && !aValuesEndCell.isEmpty()) + { + sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1; + SwXTextTable::GetCellPosition( aValuesStartCell, nStartCol, nStartRow ); + SwXTextTable::GetCellPosition( aValuesEndCell, nEndCol, nEndRow ); + if (nStartRow < 0 || nEndRow >= nTableRows || + nStartCol < 0 || nEndCol >= nTableCols) + { + return aResult; // failed -> return empty property sequence + } + for (sal_Int32 i = nStartRow; i <= nEndRow; ++i) + { + for (sal_Int32 k = nStartCol; k <= nEndCol; ++k) + { + char &rChar = aMap[i][k]; + if (rChar == '\0') // check for overlapping values and/or labels + rChar = 'x'; + else + return aResult; // failed -> return empty property sequence + } + } + } + } + +#if OSL_DEBUG_LEVEL > 0 + // do some extra sanity checking that the length of the sequences + // matches their range representation + { + sal_Int32 nStartRow = -1, nStartCol = -1, nEndRow = -1, nEndCol = -1; + if (xCurLabel.is()) + { + SwXTextTable::GetCellPosition( aLabelStartCell, nStartCol, nStartRow); + SwXTextTable::GetCellPosition( aLabelEndCell, nEndCol, nEndRow); + OSL_ENSURE( (nStartCol == nEndCol && (nEndRow - nStartRow + 1) == xCurLabel->getData().getLength()) || + (nStartRow == nEndRow && (nEndCol - nStartCol + 1) == xCurLabel->getData().getLength()), + "label sequence length does not match range representation!" ); + } + if (xCurValues.is()) + { + SwXTextTable::GetCellPosition( aValuesStartCell, nStartCol, nStartRow); + SwXTextTable::GetCellPosition( aValuesEndCell, nEndCol, nEndRow); + OSL_ENSURE( (nStartCol == nEndCol && (nEndRow - nStartRow + 1) == xCurValues->getData().getLength()) || + (nStartRow == nEndRow && (nEndCol - nStartCol + 1) == xCurValues->getData().getLength()), + "value sequence length does not match range representation!" ); + } + } +#endif + } // for + + // build value for 'CellRangeRepresentation' + + const OUString aCellRangeBase = aTableName + "."; + OUString aCurRange; + for (sal_Int32 i = 0; i < nTableRows; ++i) + { + for (sal_Int32 k = 0; k < nTableCols; ++k) + { + if (aMap[i][k] != '\0') // top-left cell of a sub-range found + { + // find rectangular sub-range to use + sal_Int32 nRowIndex1 = i; // row index + sal_Int32 nColIndex1 = k; // column index + sal_Int32 nRowSubLen = 0; + sal_Int32 nColSubLen = 0; + while (nRowIndex1 < nTableRows && aMap[nRowIndex1++][k] != '\0') + ++nRowSubLen; + // be aware of shifted sequences! + // (according to the checks done prior the length should be ok) + while (nColIndex1 < nTableCols && aMap[i][nColIndex1] != '\0' + && aMap[i + nRowSubLen-1][nColIndex1] != '\0') + { + ++nColIndex1; + ++nColSubLen; + } + OUString aStartCell( sw_GetCellName( k, i ) ); + OUString aEndCell( sw_GetCellName( k + nColSubLen - 1, i + nRowSubLen - 1) ); + aCurRange = aCellRangeBase + aStartCell + ":" + aEndCell; + if (!aCellRanges.isEmpty()) + aCellRanges += ";"; + aCellRanges += aCurRange; + + // clear already found sub-range from map + for (sal_Int32 nRowIndex2 = 0; nRowIndex2 < nRowSubLen; ++nRowIndex2) + for (sal_Int32 nColumnIndex2 = 0; nColumnIndex2 < nColSubLen; ++nColumnIndex2) + aMap[i + nRowIndex2][k + nColumnIndex2] = '\0'; + } + } + } + // to be nice to the user we now sort the cell ranges according to + // rows or columns depending on the direction used in the data source + uno::Sequence< OUString > aSortedRanges; + GetSubranges( aCellRanges, aSortedRanges, false /*sub ranges should already be normalized*/ ); + SortSubranges( aSortedRanges, (nDtaSrcIsColumns == 1) ); + OUString aSortedCellRanges; + for (const OUString& rSortedRange : std::as_const(aSortedRanges)) + { + if (!aSortedCellRanges.isEmpty()) + aSortedCellRanges += ";"; + aSortedCellRanges += rSortedRange; + } + + // build value for 'SequenceMapping' + + uno::Sequence< sal_Int32 > aSortedMapping( aSequenceMapping ); + std::sort( aSortedMapping.begin(), aSortedMapping.end() ); + bool bNeedSequenceMapping = false; + for (sal_Int32 i = 0; i < aSequenceMapping.getLength(); ++i) + { + auto it = std::find( aSortedMapping.begin(), aSortedMapping.end(), + aSequenceMapping[i] ); + aSequenceMapping[i] = std::distance(aSortedMapping.begin(), it); + + if (i != aSequenceMapping[i]) + bNeedSequenceMapping = true; + } + + // check if 'SequenceMapping' is actually not required... + // (don't write unnecessary properties to the XML file) + if (!bNeedSequenceMapping) + aSequenceMapping.realloc(0); + + // build resulting properties + + OSL_ENSURE(nLabelSeqLen >= 0 || nLabelSeqLen == -2 /*not used*/, + "unexpected value for 'nLabelSeqLen'" ); + bool bFirstCellIsLabel = false; // default value if 'nLabelSeqLen' could not properly determined + if (nLabelSeqLen > 0) // == 0 means no label sequence in use + bFirstCellIsLabel = true; + + OSL_ENSURE( !aSortedCellRanges.isEmpty(), "CellRangeRepresentation missing" ); + const OUString aBrokenCellRangeForExport( GetBrokenCellRangeForExport( aSortedCellRanges ) ); + + aResult.realloc(5); + sal_Int32 nProps = 0; + aResult[nProps ].Name = "FirstCellAsLabel"; + aResult[nProps++].Value <<= bFirstCellIsLabel; + aResult[nProps ].Name = "CellRangeRepresentation"; + aResult[nProps++].Value <<= aSortedCellRanges; + if (!aBrokenCellRangeForExport.isEmpty()) + { + aResult[nProps ].Name = "BrokenCellRangeForExport"; + aResult[nProps++].Value <<= aBrokenCellRangeForExport; + } + if (nDtaSrcIsColumns == 0 || nDtaSrcIsColumns == 1) + { + chart::ChartDataRowSource eDataRowSource = (nDtaSrcIsColumns == 1) ? + chart::ChartDataRowSource_COLUMNS : chart::ChartDataRowSource_ROWS; + aResult[nProps ].Name = "DataRowSource"; + aResult[nProps++].Value <<= eDataRowSource; + + if (aSequenceMapping.hasElements()) + { + aResult[nProps ].Name = "SequenceMapping"; + aResult[nProps++].Value <<= aSequenceMapping; + } + } + aResult.realloc( nProps ); + + return aResult; +} + +uno::Reference< chart2::data::XDataSequence > SwChartDataProvider::Impl_createDataSequenceByRangeRepresentation( + const OUString& rRangeRepresentation, bool bTestOnly ) +{ + if (m_bDisposed) + throw lang::DisposedException(); + + SwFrameFormat *pTableFormat = nullptr; // pointer to table format + std::shared_ptr<SwUnoCursor> pUnoCursor; // pointer to new created cursor spanning the cell range + GetFormatAndCreateCursorFromRangeRep( m_pDoc, rRangeRepresentation, + &pTableFormat, pUnoCursor ); + if (!pTableFormat || !pUnoCursor) + throw lang::IllegalArgumentException(); + + // check that cursors point and mark are in a single row or column. + OUString aCellRange( GetCellRangeName( *pTableFormat, *pUnoCursor ) ); + SwRangeDescriptor aDesc; + FillRangeDescriptor( aDesc, aCellRange ); + if (aDesc.nTop != aDesc.nBottom && aDesc.nLeft != aDesc.nRight) + throw lang::IllegalArgumentException(); + + OSL_ENSURE( pTableFormat && pUnoCursor, "table format or cursor missing" ); + uno::Reference< chart2::data::XDataSequence > xDataSeq; + if (!bTestOnly) + xDataSeq = new SwChartDataSequence( *this, *pTableFormat, pUnoCursor ); + + return xDataSeq; +} + +sal_Bool SAL_CALL SwChartDataProvider::createDataSequenceByRangeRepresentationPossible( + const OUString& rRangeRepresentation ) +{ + SolarMutexGuard aGuard; + + bool bPossible = true; + try + { + Impl_createDataSequenceByRangeRepresentation( rRangeRepresentation, true ); + } + catch (lang::IllegalArgumentException &) + { + bPossible = false; + } + + return bPossible; +} + +uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartDataProvider::createDataSequenceByRangeRepresentation( + const OUString& rRangeRepresentation ) +{ + SolarMutexGuard aGuard; + return Impl_createDataSequenceByRangeRepresentation( rRangeRepresentation ); +} + +uno::Reference< sheet::XRangeSelection > SAL_CALL SwChartDataProvider::getRangeSelection( ) +{ + // note: it is no error to return nothing here + return uno::Reference< sheet::XRangeSelection >(); +} + +uno::Reference<css::chart2::data::XDataSequence> SAL_CALL + SwChartDataProvider::createDataSequenceByValueArray( + const OUString& /*aRole*/, const OUString& /*aRangeRepresentation*/ ) +{ + return uno::Reference<css::chart2::data::XDataSequence>(); +} + +void SAL_CALL SwChartDataProvider::dispose( ) +{ + bool bMustDispose( false ); + { + osl::MutexGuard aGuard( GetChartMutex() ); + bMustDispose = !m_bDisposed; + if (!m_bDisposed) + m_bDisposed = true; + } + if (bMustDispose) + { + // dispose all data-sequences + for (const auto& rEntry : m_aDataSequences) + { + DisposeAllDataSequences( rEntry.first ); + } + // release all references to data-sequences + m_aDataSequences.clear(); + + // require listeners to release references to this object + lang::EventObject aEvtObj( dynamic_cast< chart2::data::XDataProvider * >(this) ); + m_aEventListeners.disposeAndClear( aEvtObj ); + } +} + +void SAL_CALL SwChartDataProvider::addEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.addInterface( rxListener ); +} + +void SAL_CALL SwChartDataProvider::removeEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.removeInterface( rxListener ); +} + +OUString SAL_CALL SwChartDataProvider::getImplementationName( ) +{ + return "SwChartDataProvider"; +} + +sal_Bool SAL_CALL SwChartDataProvider::supportsService(const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataProvider::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.DataProvider"}; +} + +void SwChartDataProvider::AddDataSequence( const SwTable &rTable, uno::Reference< chart2::data::XDataSequence > const &rxDataSequence ) +{ + m_aDataSequences[ &rTable ].insert( rxDataSequence ); +} + +void SwChartDataProvider::RemoveDataSequence( const SwTable &rTable, uno::Reference< chart2::data::XDataSequence > const &rxDataSequence ) +{ + m_aDataSequences[ &rTable ].erase( rxDataSequence ); +} + +void SwChartDataProvider::InvalidateTable( const SwTable *pTable ) +{ + OSL_ENSURE( pTable, "table pointer is NULL" ); + if (pTable) + { + if (!m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking(); + + const Set_DataSequenceRef_t &rSet = m_aDataSequences[ pTable ]; + for (const auto& rItem : rSet) + { + uno::Reference< chart2::data::XDataSequence > xTemp(rItem); // temporary needed for g++ 3.3.5 + uno::Reference< util::XModifiable > xRef( xTemp, uno::UNO_QUERY ); + if (xRef.is()) + { + // mark the sequence as 'dirty' and notify listeners + xRef->setModified( true ); + } + } + } +} + +void SwChartDataProvider::DeleteBox( const SwTable *pTable, const SwTableBox &rBox ) +{ + OSL_ENSURE( pTable, "table pointer is NULL" ); + if (pTable) + { + if (!m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking(); + + Set_DataSequenceRef_t &rSet = m_aDataSequences[ pTable ]; + + // iterate over all data-sequences for that table... + Set_DataSequenceRef_t::iterator aIt( rSet.begin() ); + Set_DataSequenceRef_t::iterator aEndIt( rSet.end() ); + Set_DataSequenceRef_t::iterator aDelIt; // iterator used for deletion when appropriate + while (aIt != aEndIt) + { + SwChartDataSequence *pDataSeq = nullptr; + bool bNowEmpty = false; + bool bSeqDisposed = false; + + // check if weak reference is still valid... + uno::Reference< chart2::data::XDataSequence > xTemp(*aIt); + if (xTemp.is()) + { + // then delete that table box (check if implementation cursor needs to be adjusted) + pDataSeq = static_cast< SwChartDataSequence * >( xTemp.get() ); + if (pDataSeq) + { + try + { + bNowEmpty = pDataSeq->DeleteBox( rBox ); + } + catch (const lang::DisposedException&) + { + bNowEmpty = true; + bSeqDisposed = true; + } + + if (bNowEmpty) + aDelIt = aIt; + } + } + ++aIt; + + if (bNowEmpty) + { + rSet.erase( aDelIt ); + if (pDataSeq && !bSeqDisposed) + pDataSeq->dispose(); // the current way to tell chart that sth. got removed + } + } + } +} + +void SwChartDataProvider::DisposeAllDataSequences( const SwTable *pTable ) +{ + OSL_ENSURE( pTable, "table pointer is NULL" ); + if (pTable) + { + if (!m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking(); + + //! make a copy of the STL container! + //! This is necessary since calling 'dispose' will implicitly remove an element + //! of the original container, and thus any iterator in the original container + //! would become invalid. + const Set_DataSequenceRef_t aSet( m_aDataSequences[ pTable ] ); + + for (const auto& rItem : aSet) + { + uno::Reference< chart2::data::XDataSequence > xTemp(rItem); // temporary needed for g++ 3.3.5 + uno::Reference< lang::XComponent > xRef( xTemp, uno::UNO_QUERY ); + if (xRef.is()) + { + xRef->dispose(); + } + } + } +} + +/** + * SwChartDataProvider::AddRowCols tries to notify charts of added columns + * or rows and extends the value sequence respectively (if possible). + * If those can be added to the end of existing value data-sequences those + * sequences get modified accordingly and will send a modification + * notification (calling 'setModified + * + * Since this function is a work-around for non existent Writer core functionality + * (no arbitrary multi-selection in tables that can be used to define a + * data-sequence) this function will be somewhat unreliable. + * For example we will only try to adapt value sequences. For this we assume + * that a sequence of length 1 is a label sequence and those with length >= 2 + * we presume to be value sequences. Also new cells can only be added in the + * direction the value sequence is already pointing (rows / cols) and at the + * start or end of the values data-sequence. + * Nothing needs to be done if the new cells are in between the table cursors + * point and mark since data-sequence are considered to consist of all cells + * between those. + * New rows/cols need to be added already to the table before calling + * this function. + */ +void SwChartDataProvider::AddRowCols( + const SwTable &rTable, + const SwSelBoxes& rBoxes, + sal_uInt16 nLines, bool bBehind ) +{ + if (rTable.IsTableComplex()) + return; + + const size_t nBoxes = rBoxes.size(); + if (nBoxes < 1 || nLines < 1) + return; + + SwTableBox* pFirstBox = rBoxes[0]; + SwTableBox* pLastBox = rBoxes.back(); + + if (pFirstBox && pLastBox) + { + sal_Int32 nFirstCol = -1, nFirstRow = -1, nLastCol = -1, nLastRow = -1; + SwXTextTable::GetCellPosition( pFirstBox->GetName(), nFirstCol, nFirstRow ); + SwXTextTable::GetCellPosition( pLastBox->GetName(), nLastCol, nLastRow ); + + bool bAddCols = false; // default; also to be used if nBoxes == 1 :-/ + if (nFirstCol == nLastCol && nFirstRow != nLastRow) + bAddCols = true; + if (nFirstCol == nLastCol || nFirstRow == nLastRow) + { + //get range of indices in col/rows for new cells + sal_Int32 nFirstNewCol = nFirstCol; + sal_Int32 nFirstNewRow = bBehind ? nFirstRow + 1 : nFirstRow - nLines; + if (bAddCols) + { + OSL_ENSURE( nFirstCol == nLastCol, "column indices seem broken" ); + nFirstNewCol = bBehind ? nFirstCol + 1 : nFirstCol - nLines; + nFirstNewRow = nFirstRow; + } + + // iterate over all data-sequences for the table + const Set_DataSequenceRef_t &rSet = m_aDataSequences[ &rTable ]; + for (const auto& rItem : rSet) + { + uno::Reference< chart2::data::XDataSequence > xTemp(rItem); // temporary needed for g++ 3.3.5 + uno::Reference< chart2::data::XTextualDataSequence > xRef( xTemp, uno::UNO_QUERY ); + if (xRef.is()) + { + const sal_Int32 nLen = xRef->getTextualData().getLength(); + if (nLen > 1) // value data-sequence ? + { + auto pDataSeq = comphelper::getUnoTunnelImplementation<SwChartDataSequence>(xRef); + if (pDataSeq) + { + SwRangeDescriptor aDesc; + pDataSeq->FillRangeDesc( aDesc ); + + chart::ChartDataRowSource eDRSource = chart::ChartDataRowSource_COLUMNS; + if (aDesc.nTop == aDesc.nBottom && aDesc.nLeft != aDesc.nRight) + eDRSource = chart::ChartDataRowSource_ROWS; + + if (!bAddCols && eDRSource == chart::ChartDataRowSource_COLUMNS) + { + // add rows: extend affected columns by newly added row cells + pDataSeq->ExtendTo( true, nFirstNewRow, nLines ); + } + else if (bAddCols && eDRSource == chart::ChartDataRowSource_ROWS) + { + // add cols: extend affected rows by newly added column cells + pDataSeq->ExtendTo( false, nFirstNewCol, nLines ); + } + } + } + } + } + } + } +} + +// XRangeXMLConversion +OUString SAL_CALL SwChartDataProvider::convertRangeToXML( const OUString& rRangeRepresentation ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (rRangeRepresentation.isEmpty()) + return OUString(); + + OUStringBuffer aRes; + + // multiple ranges are delimited by a ';' like in + // "Table1.A1:A4;Table1.C2:C5" the same table must be used in all ranges! + SwTable* pFirstFoundTable = nullptr; // to check that only one table will be used + sal_Int32 nPos = 0; + do { + const OUString aRange( rRangeRepresentation.getToken(0, ';', nPos) ); + SwFrameFormat *pTableFormat = nullptr; // pointer to table format + std::shared_ptr<SwUnoCursor> pCursor; + GetFormatAndCreateCursorFromRangeRep( m_pDoc, aRange, &pTableFormat, pCursor ); + if (!pTableFormat) + throw lang::IllegalArgumentException(); + SwTable* pTable = SwTable::FindTable( pTableFormat ); + if (pTable->IsTableComplex()) + throw uno::RuntimeException("Table too complex."); + + // check that there is only one table used in all ranges + if (!pFirstFoundTable) + pFirstFoundTable = pTable; + if (pTable != pFirstFoundTable) + throw lang::IllegalArgumentException(); + + OUString aTableName; + OUString aStartCell; + OUString aEndCell; + if (!GetTableAndCellsFromRangeRep( aRange, aTableName, aStartCell, aEndCell )) + throw lang::IllegalArgumentException(); + + sal_Int32 nCol, nRow; + SwXTextTable::GetCellPosition( aStartCell, nCol, nRow ); + if (nCol < 0 || nRow < 0) + throw uno::RuntimeException("Cell not found."); + + //!! following objects/functions are implemented in XMLRangeHelper.?xx + //!! which is a copy of the respective file from chart2 !! + XMLRangeHelper::CellRange aCellRange; + aCellRange.aTableName = aTableName; + aCellRange.aUpperLeft.nColumn = nCol; + aCellRange.aUpperLeft.nRow = nRow; + aCellRange.aUpperLeft.bIsEmpty = false; + if (aStartCell != aEndCell && !aEndCell.isEmpty()) + { + SwXTextTable::GetCellPosition( aEndCell, nCol, nRow ); + if (nCol < 0 || nRow < 0) + throw uno::RuntimeException("Cell not found."); + + aCellRange.aLowerRight.nColumn = nCol; + aCellRange.aLowerRight.nRow = nRow; + aCellRange.aLowerRight.bIsEmpty = false; + } + OUString aTmp( XMLRangeHelper::getXMLStringFromCellRange( aCellRange ) ); + if (!aRes.isEmpty()) // in case of multiple ranges add delimiter + aRes.append(" "); + aRes.append(aTmp); + } + while (nPos>0); + + return aRes.makeStringAndClear(); +} + +OUString SAL_CALL SwChartDataProvider::convertRangeFromXML( const OUString& rXMLRange ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (rXMLRange.isEmpty()) + return OUString(); + + OUStringBuffer aRes; + + // multiple ranges are delimited by a ' ' like in + // "Table1.$A$1:.$A$4 Table1.$C$2:.$C$5" the same table must be used in all ranges! + OUString aFirstFoundTable; // to check that only one table will be used + sal_Int32 nPos = 0; + do + { + OUString aRange( rXMLRange.getToken(0, ' ', nPos) ); + + //!! following objects and function are implemented in XMLRangeHelper.?xx + //!! which is a copy of the respective file from chart2 !! + XMLRangeHelper::CellRange aCellRange( XMLRangeHelper::getCellRangeFromXMLString( aRange )); + + // check that there is only one table used in all ranges + if (aFirstFoundTable.isEmpty()) + aFirstFoundTable = aCellRange.aTableName; + if (aCellRange.aTableName != aFirstFoundTable) + throw lang::IllegalArgumentException(); + + OUString aTmp = aCellRange.aTableName + "." + + sw_GetCellName( aCellRange.aUpperLeft.nColumn, + aCellRange.aUpperLeft.nRow ); + // does cell range consist of more than a single cell? + if (!aCellRange.aLowerRight.bIsEmpty) + { + aTmp += ":" + sw_GetCellName( aCellRange.aLowerRight.nColumn, + aCellRange.aLowerRight.nRow ); + } + + if (!aRes.isEmpty()) // in case of multiple ranges add delimiter + aRes.append(";"); + aRes.append(aTmp); + } + while (nPos>0); + + return aRes.makeStringAndClear(); +} + +SwChartDataSource::SwChartDataSource( + const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > &rLDS ) : + m_aLDS( rLDS ) +{ +} + +SwChartDataSource::~SwChartDataSource() +{ +} + +uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > SAL_CALL SwChartDataSource::getDataSequences( ) +{ + SolarMutexGuard aGuard; + return m_aLDS; +} + +OUString SAL_CALL SwChartDataSource::getImplementationName( ) +{ + return "SwChartDataSource"; +} + +sal_Bool SAL_CALL SwChartDataSource::supportsService(const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSource::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.DataSource" }; +} + +SwChartDataSequence::SwChartDataSequence( + SwChartDataProvider& rProvider, + SwFrameFormat& rTableFormat, + const std::shared_ptr<SwUnoCursor>& pTableCursor ) : + m_pFormat(&rTableFormat), + m_aEvtListeners( GetChartMutex() ), + m_aModifyListeners( GetChartMutex() ), + m_aRowLabelText( SwResId( STR_CHART2_ROW_LABEL_TEXT ) ), + m_aColLabelText( SwResId( STR_CHART2_COL_LABEL_TEXT ) ), + m_xDataProvider( &rProvider ), + m_pTableCursor( pTableCursor ), + m_pPropSet( aSwMapProvider.GetPropertySet( PROPERTY_MAP_CHART2_DATA_SEQUENCE ) ) +{ + StartListening(rTableFormat.GetNotifier()); + m_bDisposed = false; + + acquire(); + try + { + const SwTable* pTable = SwTable::FindTable( &rTableFormat ); + if (pTable) + { + uno::Reference< chart2::data::XDataSequence > xRef( dynamic_cast< chart2::data::XDataSequence * >(this), uno::UNO_QUERY ); + m_xDataProvider->AddDataSequence( *pTable, xRef ); + m_xDataProvider->addEventListener( dynamic_cast< lang::XEventListener * >(this) ); + } + else { + OSL_FAIL( "table missing" ); + } + } + catch (uno::RuntimeException &) + { + // TODO: shouldn't there be a call to release() here? + throw; + } + catch (uno::Exception &) + { + } + release(); + +#if OSL_DEBUG_LEVEL > 0 + // check if it can properly convert into a SwUnoTableCursor + // which is required for some functions + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&(*m_pTableCursor)); + OSL_ENSURE(pUnoTableCursor, "SwChartDataSequence: cursor not SwUnoTableCursor"); +#endif +} + +SwChartDataSequence::SwChartDataSequence( const SwChartDataSequence &rObj ) : + SwChartDataSequenceBaseClass(rObj), + SvtListener(), + m_pFormat( rObj.m_pFormat ), + m_aEvtListeners( GetChartMutex() ), + m_aModifyListeners( GetChartMutex() ), + m_aRole( rObj.m_aRole ), + m_aRowLabelText( SwResId(STR_CHART2_ROW_LABEL_TEXT) ), + m_aColLabelText( SwResId(STR_CHART2_COL_LABEL_TEXT) ), + m_xDataProvider( rObj.m_xDataProvider ), + m_pTableCursor( rObj.m_pTableCursor ), + m_pPropSet( rObj.m_pPropSet ) +{ + if(m_pFormat) + StartListening(m_pFormat->GetNotifier()); + m_bDisposed = false; + + acquire(); + try + { + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + if (pTable) + { + uno::Reference< chart2::data::XDataSequence > xRef( dynamic_cast< chart2::data::XDataSequence * >(this), uno::UNO_QUERY ); + m_xDataProvider->AddDataSequence( *pTable, xRef ); + m_xDataProvider->addEventListener( dynamic_cast< lang::XEventListener * >(this) ); + } + else { + OSL_FAIL( "table missing" ); + } + } + catch (uno::RuntimeException &) + { + // TODO: shouldn't there be a call to release() here? + throw; + } + catch (uno::Exception &) + { + } + release(); + +#if OSL_DEBUG_LEVEL > 0 + // check if it can properly convert into a SwUnoTableCursor + // which is required for some functions + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&(*m_pTableCursor)); + OSL_ENSURE(pUnoTableCursor, "SwChartDataSequence: cursor not SwUnoTableCursor"); +#endif +} + +SwChartDataSequence::~SwChartDataSequence() +{ +} + +namespace +{ + class theSwChartDataSequenceUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwChartDataSequenceUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwChartDataSequence::getUnoTunnelId() +{ + return theSwChartDataSequenceUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwChartDataSequence::getSomething( const uno::Sequence< sal_Int8 > &rId ) +{ + if( isUnoTunnelId<SwChartDataSequence>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + + +OUString SAL_CALL SwChartDataSequence::getSourceRangeRepresentation( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + OUString aRes; + SwFrameFormat* pTableFormat = GetFrameFormat(); + if (pTableFormat) + { + const OUString aCellRange( GetCellRangeName( *pTableFormat, *m_pTableCursor ) ); + OSL_ENSURE( !aCellRange.isEmpty(), "failed to get cell range" ); + aRes = pTableFormat->GetName() + "." + aCellRange; + } + return aRes; +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSequence::generateLabel( + chart2::data::LabelOrigin eLabelOrigin ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Sequence< OUString > aLabels; + + { + SwRangeDescriptor aDesc; + bool bOk = false; + SwFrameFormat* pTableFormat = GetFrameFormat(); + if (!pTableFormat) + throw uno::RuntimeException("No table format found."); + SwTable* pTable = SwTable::FindTable( pTableFormat ); + if (!pTable) + throw uno::RuntimeException("No table found."); + if (pTable->IsTableComplex()) + throw uno::RuntimeException("Table too complex."); + + const OUString aCellRange( GetCellRangeName( *pTableFormat, *m_pTableCursor ) ); + OSL_ENSURE( !aCellRange.isEmpty(), "failed to get cell range" ); + bOk = FillRangeDescriptor( aDesc, aCellRange ); + OSL_ENSURE( bOk, "failed to get SwRangeDescriptor" ); + + if (bOk) + { + aDesc.Normalize(); + sal_Int32 nColSpan = aDesc.nRight - aDesc.nLeft + 1; + sal_Int32 nRowSpan = aDesc.nBottom - aDesc.nTop + 1; + OSL_ENSURE( nColSpan == 1 || nRowSpan == 1, + "unexpected range of selected cells" ); + + OUString aText; // label text to be returned + bool bReturnEmptyText = false; + bool bUseCol = true; + if (eLabelOrigin == chart2::data::LabelOrigin_COLUMN) + bUseCol = true; + else if (eLabelOrigin == chart2::data::LabelOrigin_ROW) + bUseCol = false; + else if (eLabelOrigin == chart2::data::LabelOrigin_SHORT_SIDE) + { + bUseCol = nColSpan < nRowSpan; + bReturnEmptyText = nColSpan == nRowSpan; + } + else if (eLabelOrigin == chart2::data::LabelOrigin_LONG_SIDE) + { + bUseCol = nColSpan > nRowSpan; + bReturnEmptyText = nColSpan == nRowSpan; + } + else { + OSL_FAIL( "unexpected case" ); + } + + // build label sequence + + sal_Int32 nSeqLen = bUseCol ? nColSpan : nRowSpan; + aLabels.realloc( nSeqLen ); + OUString *pLabels = aLabels.getArray(); + for (sal_Int32 i = 0; i < nSeqLen; ++i) + { + if (!bReturnEmptyText) + { + aText = bUseCol ? m_aColLabelText : m_aRowLabelText; + sal_Int32 nCol = aDesc.nLeft; + sal_Int32 nRow = aDesc.nTop; + if (bUseCol) + nCol = nCol + i; + else + nRow = nRow + i; + OUString aCellName( sw_GetCellName( nCol, nRow ) ); + + sal_Int32 nLen = aCellName.getLength(); + if (nLen) + { + const sal_Unicode *pBuf = aCellName.getStr(); + const sal_Unicode *pEnd = pBuf + nLen; + while (pBuf < pEnd && !('0' <= *pBuf && *pBuf <= '9')) + ++pBuf; + // start of number found? + if (pBuf < pEnd && ('0' <= *pBuf && *pBuf <= '9')) + { + OUString aRplc; + OUString aNew; + if (bUseCol) + { + aRplc = "%COLUMNLETTER"; + aNew = aCellName.copy(0, pBuf - aCellName.getStr()); + } + else + { + aRplc = "%ROWNUMBER"; + aNew = OUString(pBuf, (aCellName.getStr() + nLen) - pBuf); + } + aText = aText.replaceFirst( aRplc, aNew ); + } + } + } + pLabels[i] = aText; + } + } + } + + return aLabels; +} + +::sal_Int32 SAL_CALL SwChartDataSequence::getNumberFormatKeyByIndex( + ::sal_Int32 /*nIndex*/ ) +{ + return 0; +} + +std::vector< css::uno::Reference< css::table::XCell > > SwChartDataSequence::GetCells() +{ + if (m_bDisposed) + throw lang::DisposedException(); + auto pTableFormat(GetFrameFormat()); + if(!pTableFormat) + return std::vector< css::uno::Reference< css::table::XCell > >(); + auto pTable(SwTable::FindTable(pTableFormat)); + if(pTable->IsTableComplex()) + return std::vector< css::uno::Reference< css::table::XCell > >(); + SwRangeDescriptor aDesc; + if(!FillRangeDescriptor(aDesc, GetCellRangeName(*pTableFormat, *m_pTableCursor))) + return std::vector< css::uno::Reference< css::table::XCell > >(); + return SwXCellRange::CreateXCellRange(m_pTableCursor, *pTableFormat, aDesc)->GetCells(); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSequence::getTextualData() +{ + SolarMutexGuard aGuard; + auto vCells(GetCells()); + uno::Sequence< OUString > vTextData(vCells.size()); + std::transform(vCells.begin(), + vCells.end(), + vTextData.begin(), + [] (decltype(vCells)::value_type& xCell) + { return static_cast<SwXCell*>(xCell.get())->getString(); }); + return vTextData; +} + +uno::Sequence< uno::Any > SAL_CALL SwChartDataSequence::getData() +{ + SolarMutexGuard aGuard; + auto vCells(GetCells()); + uno::Sequence< uno::Any > vAnyData(vCells.size()); + std::transform(vCells.begin(), + vCells.end(), + vAnyData.begin(), + [] (decltype(vCells)::value_type& xCell) + { return static_cast<SwXCell*>(xCell.get())->GetAny(); }); + return vAnyData; +} + +uno::Sequence< double > SAL_CALL SwChartDataSequence::getNumericalData() +{ + SolarMutexGuard aGuard; + auto vCells(GetCells()); + uno::Sequence< double > vNumData(vCells.size()); + std::transform(vCells.begin(), + vCells.end(), + vNumData.begin(), + [] (decltype(vCells)::value_type& xCell) + { return static_cast<SwXCell*>(xCell.get())->GetForcedNumericalValue(); }); + return vNumData; +} + +uno::Reference< util::XCloneable > SAL_CALL SwChartDataSequence::createClone( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + return new SwChartDataSequence( *this ); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL SwChartDataSequence::getPropertySetInfo( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + static uno::Reference< beans::XPropertySetInfo > xRes = m_pPropSet->getPropertySetInfo(); + return xRes; +} + +void SAL_CALL SwChartDataSequence::setPropertyValue( + const OUString& rPropertyName, + const uno::Any& rValue ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (rPropertyName != UNO_NAME_ROLE) + throw beans::UnknownPropertyException(rPropertyName); + + if ( !(rValue >>= m_aRole) ) + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL SwChartDataSequence::getPropertyValue( + const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (!(rPropertyName == UNO_NAME_ROLE)) + throw beans::UnknownPropertyException(rPropertyName); + + return uno::Any(m_aRole); +} + +void SAL_CALL SwChartDataSequence::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +void SAL_CALL SwChartDataSequence::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +void SAL_CALL SwChartDataSequence::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +void SAL_CALL SwChartDataSequence::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +OUString SAL_CALL SwChartDataSequence::getImplementationName( ) +{ + return "SwChartDataSequence"; +} + +sal_Bool SAL_CALL SwChartDataSequence::supportsService(const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSequence::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.DataSequence" }; +} + +void SwChartDataSequence::Notify( const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFormat = nullptr; + if(!m_pFormat || !m_pTableCursor) + { + m_pFormat = nullptr; + m_pTableCursor.reset(nullptr); + dispose(); + } + else if (dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + setModified( true ); + } +} + +sal_Bool SAL_CALL SwChartDataSequence::isModified( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + return true; +} + +void SAL_CALL SwChartDataSequence::setModified( + sal_Bool bModified ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (bModified) + LaunchModifiedEvent( m_aModifyListeners, dynamic_cast< XModifyBroadcaster * >(this) ); +} + +void SAL_CALL SwChartDataSequence::addModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.addInterface( rxListener ); +} + +void SAL_CALL SwChartDataSequence::removeModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.removeInterface( rxListener ); +} + +void SAL_CALL SwChartDataSequence::disposing( const lang::EventObject& rSource ) +{ + if (m_bDisposed) + throw lang::DisposedException(); + if (rSource.Source == static_cast<cppu::OWeakObject*>(m_xDataProvider.get())) + { + m_xDataProvider.clear(); + } +} + +void SAL_CALL SwChartDataSequence::dispose( ) +{ + bool bMustDispose( false ); + { + osl::MutexGuard aGuard( GetChartMutex() ); + bMustDispose = !m_bDisposed; + if (!m_bDisposed) + m_bDisposed = true; + } + if (bMustDispose) + { + m_bDisposed = true; + if (m_xDataProvider.is()) + { + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + if (pTable) + { + uno::Reference< chart2::data::XDataSequence > xRef( dynamic_cast< chart2::data::XDataSequence * >(this), uno::UNO_QUERY ); + m_xDataProvider->RemoveDataSequence( *pTable, xRef ); + } + else { + OSL_FAIL( "table missing" ); + } + + //#i119653# The bug is crashed for an exception thrown by + //SwCharDataSequence::setModified() because + //the SwCharDataSequence object has been disposed. + + //Actually, the former design of SwClient will disconnect itself + //from the notification list in its destructor. + + //But the SwCharDataSequence won't be destructed but disposed in code + //(the data member SwChartDataSequence::bDisposed will be set to + //TRUE), the relationship between client and modification is not + //released. + + //So any notification from modify object will lead to said + //exception threw out. Recorrect the logic of code in + //SwChartDataSequence::Dispose(), release the relationship + //here... + if (m_pFormat && m_pFormat->HasWriterListeners()) + { + EndListeningAll(); + m_pFormat = nullptr; + m_pTableCursor.reset(nullptr); + } + } + + // require listeners to release references to this object + lang::EventObject aEvtObj( dynamic_cast< chart2::data::XDataSequence * >(this) ); + m_aModifyListeners.disposeAndClear( aEvtObj ); + m_aEvtListeners.disposeAndClear( aEvtObj ); + } +} + +void SAL_CALL SwChartDataSequence::addEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEvtListeners.addInterface( rxListener ); +} + +void SAL_CALL SwChartDataSequence::removeEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEvtListeners.removeInterface( rxListener ); +} + +bool SwChartDataSequence::DeleteBox( const SwTableBox &rBox ) +{ + if (m_bDisposed) + throw lang::DisposedException(); + + // to be set if the last box of the data-sequence was removed here + bool bNowEmpty = false; + + // if the implementation cursor gets affected (i.e. the box where it is located + // in gets removed) we need to move it before that... (otherwise it does not need to change) + + const SwStartNode* pPointStartNode = m_pTableCursor->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + const SwStartNode* pMarkStartNode = m_pTableCursor->GetMark()->nNode.GetNode().FindTableBoxStartNode(); + + if (!m_pTableCursor->HasMark() || (pPointStartNode == rBox.GetSttNd() && pMarkStartNode == rBox.GetSttNd())) + { + bNowEmpty = true; + } + else if (pPointStartNode == rBox.GetSttNd() || pMarkStartNode == rBox.GetSttNd()) + { + sal_Int32 nPointRow = -1, nPointCol = -1; + sal_Int32 nMarkRow = -1, nMarkCol = -1; + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + OUString aPointCellName( pTable->GetTableBox( pPointStartNode->GetIndex() )->GetName() ); + OUString aMarkCellName( pTable->GetTableBox( pMarkStartNode->GetIndex() )->GetName() ); + + SwXTextTable::GetCellPosition( aPointCellName, nPointCol, nPointRow ); + SwXTextTable::GetCellPosition( aMarkCellName, nMarkCol, nMarkRow ); + OSL_ENSURE( nPointRow >= 0 && nPointCol >= 0, "invalid row and col" ); + OSL_ENSURE( nMarkRow >= 0 && nMarkCol >= 0, "invalid row and col" ); + + // move vertical or horizontal? + OSL_ENSURE( nPointRow == nMarkRow || nPointCol == nMarkCol, + "row/col indices not matching" ); + OSL_ENSURE( nPointRow != nMarkRow || nPointCol != nMarkCol, + "point and mark are identical" ); + bool bMoveVertical = (nPointCol == nMarkCol); + bool bMoveHorizontal = (nPointRow == nMarkRow); + + // get movement direction + bool bMoveLeft = false; // move left or right? + bool bMoveUp = false; // move up or down? + if (bMoveVertical) + { + if (pPointStartNode == rBox.GetSttNd()) // move point? + bMoveUp = nPointRow > nMarkRow; + else // move mark + bMoveUp = nMarkRow > nPointRow; + } + else if (bMoveHorizontal) + { + if (pPointStartNode == rBox.GetSttNd()) // move point? + bMoveLeft = nPointCol > nMarkCol; + else // move mark + bMoveLeft = nMarkCol > nPointCol; + } + else { + OSL_FAIL( "neither vertical nor horizontal movement" ); + } + + // get new box (position) to use... + sal_Int32 nRow = (pPointStartNode == rBox.GetSttNd()) ? nPointRow : nMarkRow; + sal_Int32 nCol = (pPointStartNode == rBox.GetSttNd()) ? nPointCol : nMarkCol; + if (bMoveVertical) + nRow += bMoveUp ? -1 : +1; + if (bMoveHorizontal) + nCol += bMoveLeft ? -1 : +1; + const OUString aNewCellName = sw_GetCellName( nCol, nRow ); + SwTableBox* pNewBox = const_cast<SwTableBox*>(pTable->GetTableBox( aNewCellName )); + + if (pNewBox) // set new position (cell range) to use + { + // This is how you get the first content node of a row: + // First get a SwNodeIndex pointing to the node after SwStartNode of the box... + SwNodeIndex aIdx( *pNewBox->GetSttNd(), +1 ); + // This can be a SwContentNode, but might also be a table or section node, + // therefore call GoNext + SwContentNode *pCNd = aIdx.GetNode().GetContentNode(); + if (!pCNd) + pCNd = GetFrameFormat()->GetDoc()->GetNodes().GoNext( &aIdx ); + // and then one can e.g. create a SwPosition: + SwPosition aNewPos( *pCNd ); // new position to be used with cursor + + // if the mark is to be changed, make sure there is one + if (pMarkStartNode == rBox.GetSttNd() && !m_pTableCursor->HasMark()) + m_pTableCursor->SetMark(); + + // set cursor to new position + SwPosition *pPos = (pPointStartNode == rBox.GetSttNd()) ? + m_pTableCursor->GetPoint() : m_pTableCursor->GetMark(); + if (pPos) + { + pPos->nNode = aNewPos.nNode; + pPos->nContent = aNewPos.nContent; + } + else { + OSL_FAIL( "neither point nor mark available for change" ); + } + } + else { + OSL_FAIL( "failed to get position" ); + } + } + + return bNowEmpty; +} + +void SwChartDataSequence::FillRangeDesc( SwRangeDescriptor &rRangeDesc ) const +{ + SwFrameFormat* pTableFormat = GetFrameFormat(); + if(pTableFormat) + { + SwTable* pTable = SwTable::FindTable( pTableFormat ); + if(!pTable->IsTableComplex()) + { + FillRangeDescriptor( rRangeDesc, GetCellRangeName( *pTableFormat, *m_pTableCursor ) ); + } + } +} + +/** + * Extends the data-sequence by new cells added at the end of the direction + * the data-sequence points to. + * If the cells are already within the range of the sequence nothing needs + * to be done. + * If the cells are beyond the end of the sequence (are not adjacent to the + * current last cell) nothing can be done. Only if the cells are adjacent to + * the last cell they can be added. + * + * @returns true if the data-sequence was changed. + * @param bExtendCols - specifies if columns or rows are to be extended + * @param nFirstNew - index of first new row/col to be included in data-sequence + * @param nLastNew - index of last new row/col to be included in data-sequence + */ +void SwChartDataSequence::ExtendTo( bool bExtendCol, + sal_Int32 nFirstNew, sal_Int32 nCount ) +{ + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&(*m_pTableCursor)); + if (!pUnoTableCursor) + return; + + const SwStartNode *pStartNd = nullptr; + const SwTableBox *pStartBox = nullptr; + const SwTableBox *pEndBox = nullptr; + + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + OSL_ENSURE( !pTable->IsTableComplex(), "table too complex" ); + if (nCount < 1 || nFirstNew < 0 || pTable->IsTableComplex()) + return; + + // get range descriptor (cell range) for current data-sequence + + pStartNd = pUnoTableCursor->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + pEndBox = pTable->GetTableBox( pStartNd->GetIndex() ); + const OUString aEndBox( pEndBox->GetName() ); + + pStartNd = pUnoTableCursor->GetMark()->nNode.GetNode().FindTableBoxStartNode(); + pStartBox = pTable->GetTableBox( pStartNd->GetIndex() ); + const OUString aStartBox( pStartBox->GetName() ); + + SwRangeDescriptor aDesc; + // note that cell range here takes the newly added rows/cols already into account + FillRangeDescriptor( aDesc, aStartBox + ":" + aEndBox ); + + bool bChanged = false; + OUString aNewStartCell; + OUString aNewEndCell; + if (bExtendCol && aDesc.nBottom + 1 == nFirstNew) + { + // new column cells adjacent to the bottom of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nLeft == aDesc.nRight, "data-sequence is not a column" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft, aDesc.nTop); + aNewEndCell = sw_GetCellName(aDesc.nRight, aDesc.nBottom + nCount); + bChanged = true; + } + else if (bExtendCol && aDesc.nTop - nCount == nFirstNew) + { + // new column cells adjacent to the top of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nLeft == aDesc.nRight, "data-sequence is not a column" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft, aDesc.nTop - nCount); + aNewEndCell = sw_GetCellName(aDesc.nRight, aDesc.nBottom); + bChanged = true; + } + else if (!bExtendCol && aDesc.nRight + 1 == nFirstNew) + { + // new row cells adjacent to the right of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nTop == aDesc.nBottom, "data-sequence is not a row" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft, aDesc.nTop); + aNewEndCell = sw_GetCellName(aDesc.nRight + nCount, aDesc.nBottom); + bChanged = true; + } + else if (!bExtendCol && aDesc.nLeft - nCount == nFirstNew) + { + // new row cells adjacent to the left of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nTop == aDesc.nBottom, "data-sequence is not a row" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft - nCount, aDesc.nTop); + aNewEndCell = sw_GetCellName(aDesc.nRight, aDesc.nBottom); + bChanged = true; + } + + if (bChanged) + { + // move table cursor to new start and end of data-sequence + const SwTableBox *pNewStartBox = pTable->GetTableBox( aNewStartCell ); + const SwTableBox *pNewEndBox = pTable->GetTableBox( aNewEndCell ); + pUnoTableCursor->SetMark(); + pUnoTableCursor->GetPoint()->nNode = *pNewEndBox->GetSttNd(); + pUnoTableCursor->GetMark()->nNode = *pNewStartBox->GetSttNd(); + pUnoTableCursor->Move( fnMoveForward, GoInNode ); + pUnoTableCursor->MakeBoxSels(); + } +} + +SwChartLabeledDataSequence::SwChartLabeledDataSequence() : + m_aEventListeners( GetChartMutex() ), + m_aModifyListeners( GetChartMutex() ) +{ + m_bDisposed = false; +} + +SwChartLabeledDataSequence::~SwChartLabeledDataSequence() +{ +} + +uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartLabeledDataSequence::getValues( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + return m_xData; +} + +void SwChartLabeledDataSequence::SetDataSequence( + uno::Reference< chart2::data::XDataSequence >& rxDest, + const uno::Reference< chart2::data::XDataSequence >& rxSource) +{ + uno::Reference< util::XModifyListener > xML( dynamic_cast< util::XModifyListener* >(this), uno::UNO_QUERY ); + uno::Reference< lang::XEventListener > xEL( dynamic_cast< lang::XEventListener* >(this), uno::UNO_QUERY ); + + // stop listening to old data-sequence + uno::Reference< util::XModifyBroadcaster > xMB( rxDest, uno::UNO_QUERY ); + if (xMB.is()) + xMB->removeModifyListener( xML ); + uno::Reference< lang::XComponent > xC( rxDest, uno::UNO_QUERY ); + if (xC.is()) + xC->removeEventListener( xEL ); + + rxDest = rxSource; + + // start listening to new data-sequence + xC.set( rxDest, uno::UNO_QUERY ); + if (xC.is()) + xC->addEventListener( xEL ); + xMB.set( rxDest, uno::UNO_QUERY ); + if (xMB.is()) + xMB->addModifyListener( xML ); +} + +void SAL_CALL SwChartLabeledDataSequence::setValues( + const uno::Reference< chart2::data::XDataSequence >& rxSequence ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (m_xData != rxSequence) + { + SetDataSequence( m_xData, rxSequence ); + // inform listeners of changes + LaunchModifiedEvent( m_aModifyListeners, dynamic_cast< XModifyBroadcaster * >(this) ); + } +} + +uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartLabeledDataSequence::getLabel( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + return m_xLabels; +} + +void SAL_CALL SwChartLabeledDataSequence::setLabel( + const uno::Reference< chart2::data::XDataSequence >& rxSequence ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (m_xLabels != rxSequence) + { + SetDataSequence( m_xLabels, rxSequence ); + // inform listeners of changes + LaunchModifiedEvent( m_aModifyListeners, dynamic_cast< XModifyBroadcaster * >(this) ); + } +} + +uno::Reference< util::XCloneable > SAL_CALL SwChartLabeledDataSequence::createClone( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Reference< util::XCloneable > xRes; + + uno::Reference< util::XCloneable > xDataCloneable( m_xData, uno::UNO_QUERY ); + uno::Reference< util::XCloneable > xLabelsCloneable( m_xLabels, uno::UNO_QUERY ); + SwChartLabeledDataSequence *pRes = new SwChartLabeledDataSequence(); + if (xDataCloneable.is()) + { + uno::Reference< chart2::data::XDataSequence > xDataClone( xDataCloneable->createClone(), uno::UNO_QUERY ); + pRes->setValues( xDataClone ); + } + + if (xLabelsCloneable.is()) + { + uno::Reference< chart2::data::XDataSequence > xLabelsClone( xLabelsCloneable->createClone(), uno::UNO_QUERY ); + pRes->setLabel( xLabelsClone ); + } + xRes = pRes; + return xRes; +} + +OUString SAL_CALL SwChartLabeledDataSequence::getImplementationName( ) +{ + return "SwChartLabeledDataSequence"; +} + +sal_Bool SAL_CALL SwChartLabeledDataSequence::supportsService( + const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartLabeledDataSequence::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.LabeledDataSequence" }; +} + +void SAL_CALL SwChartLabeledDataSequence::disposing( + const lang::EventObject& rSource ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + uno::Reference< uno::XInterface > xRef( rSource.Source ); + if (xRef == m_xData) + m_xData.clear(); + if (xRef == m_xLabels) + m_xLabels.clear(); + if (!m_xData.is() && !m_xLabels.is()) + dispose(); +} + +void SAL_CALL SwChartLabeledDataSequence::modified( + const lang::EventObject& rEvent ) +{ + if (rEvent.Source == m_xData || rEvent.Source == m_xLabels) + { + LaunchModifiedEvent( m_aModifyListeners, dynamic_cast< XModifyBroadcaster * >(this) ); + } +} + +void SAL_CALL SwChartLabeledDataSequence::addModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.addInterface( rxListener ); +} + +void SAL_CALL SwChartLabeledDataSequence::removeModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.removeInterface( rxListener ); +} + +void SAL_CALL SwChartLabeledDataSequence::dispose( ) +{ + bool bMustDispose( false ); + { + osl::MutexGuard aGuard( GetChartMutex() ); + bMustDispose = !m_bDisposed; + if (!m_bDisposed) + m_bDisposed = true; + } + if (bMustDispose) + { + m_bDisposed = true; + + // require listeners to release references to this object + lang::EventObject aEvtObj( dynamic_cast< chart2::data::XLabeledDataSequence * >(this) ); + m_aModifyListeners.disposeAndClear( aEvtObj ); + m_aEventListeners.disposeAndClear( aEvtObj ); + } +} + +void SAL_CALL SwChartLabeledDataSequence::addEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.addInterface( rxListener ); +} + +void SAL_CALL SwChartLabeledDataSequence::removeEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + osl::MutexGuard aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.removeInterface( rxListener ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unocoll.cxx b/sw/source/core/unocore/unocoll.cxx new file mode 100644 index 000000000..9965a5b9c --- /dev/null +++ b/sw/source/core/unocore/unocoll.cxx @@ -0,0 +1,1941 @@ +/* -*- 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 <config_features.h> + +#include <hintids.hxx> +#include <doc.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <unocoll.hxx> +#include <unosett.hxx> +#include <section.hxx> +#include <IMark.hxx> +#include <ftnidx.hxx> +#include <fmtftn.hxx> +#include <txtftn.hxx> +#include <com/sun/star/text/XTextTable.hpp> +#include <o3tl/safeint.hxx> +#include <svtools/unoimap.hxx> +#include <svtools/unoevent.hxx> +#include <unotbl.hxx> +#include <unostyle.hxx> +#include <unofield.hxx> +#include <unoidx.hxx> +#include <unoframe.hxx> +#include <textboxhelper.hxx> +#include <unofootnote.hxx> +#include <vcl/svapp.hxx> +#include <fmtcntnt.hxx> +#include <authfld.hxx> +#include <SwXTextDefaults.hxx> +#include <unochart.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unosection.hxx> +#include <unoparagraph.hxx> +#include <unobookmark.hxx> +#include <unorefmark.hxx> +#include <unometa.hxx> +#include <docsh.hxx> +#include <hints.hxx> +#include <frameformats.hxx> +#include <com/sun/star/document/XCodeNameQuery.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/form/XFormsSupplier.hpp> +#include <com/sun/star/script/ModuleInfo.hpp> +#include <com/sun/star/script/ModuleType.hpp> +#include <com/sun/star/script/vba/XVBAModuleInfo.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <vbahelper/vbaaccesshelper.hxx> +#include <basic/basmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <cppuhelper/implbase.hxx> +#include <sfx2/event.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; + +#if HAVE_FEATURE_SCRIPTING + +namespace { + +class SwVbaCodeNameProvider : public ::cppu::WeakImplHelper< document::XCodeNameQuery > +{ + SwDocShell* mpDocShell; + OUString msThisDocumentCodeName; +public: + explicit SwVbaCodeNameProvider( SwDocShell* pDocShell ) : mpDocShell( pDocShell ) {} + // XCodeNameQuery + + OUString SAL_CALL getCodeNameForContainer( const uno::Reference< uno::XInterface >& /*xIf*/ ) override + { + // #FIXME not implemented... + return OUString(); + } + + OUString SAL_CALL getCodeNameForObject( const uno::Reference< uno::XInterface >& xIf ) override + { + // Initialise the code name + if ( msThisDocumentCodeName.isEmpty() ) + { + try + { + uno::Reference< beans::XPropertySet > xProps( mpDocShell->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xLibContainer( xProps->getPropertyValue("BasicLibraries"), uno::UNO_QUERY_THROW ); + OUString sProjectName( "Standard"); + if ( !mpDocShell->GetBasicManager()->GetName().isEmpty() ) + { + sProjectName = mpDocShell->GetBasicManager()->GetName(); + } + uno::Reference< container::XNameAccess > xLib( xLibContainer->getByName( sProjectName ), uno::UNO_QUERY_THROW ); + uno::Sequence< OUString > sModuleNames = xLib->getElementNames(); + uno::Reference< script::vba::XVBAModuleInfo > xVBAModuleInfo( xLib, uno::UNO_QUERY ); + + auto pModuleName = std::find_if(sModuleNames.begin(), sModuleNames.end(), [&xVBAModuleInfo](const OUString& rName) { + return xVBAModuleInfo->hasModuleInfo(rName) + && xVBAModuleInfo->getModuleInfo(rName).ModuleType == script::ModuleType::DOCUMENT; }); + if (pModuleName != sModuleNames.end()) + msThisDocumentCodeName = *pModuleName; + } + catch( uno::Exception& ) + { + } + } + OUString sCodeName; + if ( mpDocShell ) + { + // need to find the page ( and index ) for this control + uno::Reference< drawing::XDrawPageSupplier > xSupplier( mpDocShell->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XIndexAccess > xIndex( xSupplier->getDrawPage(), uno::UNO_QUERY_THROW ); + + try + { + uno::Reference< form::XFormsSupplier > xFormSupplier( xIndex, uno::UNO_QUERY_THROW ); + uno::Reference< container::XIndexAccess > xFormIndex( xFormSupplier->getForms(), uno::UNO_QUERY_THROW ); + // get the www-standard container + uno::Reference< container::XIndexAccess > xFormControls( xFormIndex->getByIndex(0), uno::UNO_QUERY_THROW ); + sal_Int32 nCntrls = xFormControls->getCount(); + for( sal_Int32 cIndex = 0; cIndex < nCntrls; ++cIndex ) + { + uno::Reference< uno::XInterface > xControl( xFormControls->getByIndex( cIndex ), uno::UNO_QUERY_THROW ); + bool bMatched = ( xControl == xIf ); + if ( bMatched ) + { + sCodeName = msThisDocumentCodeName; + break; + } + } + } + catch( uno::Exception& ) + { + } + } + // #TODO Probably should throw here ( if !bMatched ) + return sCodeName; + } +}; + +} + +typedef std::unordered_map< OUString, OUString > StringHashMap; + +namespace { + +class SwVbaProjectNameProvider : public ::cppu::WeakImplHelper< container::XNameContainer > +{ + StringHashMap mTemplateToProject; +public: + SwVbaProjectNameProvider() + { + } + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override + { + return ( mTemplateToProject.find( aName ) != mTemplateToProject.end() ); + } + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override + { + if ( !hasByName( aName ) ) + throw container::NoSuchElementException(); + return uno::makeAny( mTemplateToProject.find( aName )->second ); + } + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override + { + return comphelper::mapKeysToSequence( mTemplateToProject ); + } + + virtual void SAL_CALL insertByName( const OUString& aName, const uno::Any& aElement ) override + { + + OUString sProjectName; + aElement >>= sProjectName; + SAL_INFO("sw.uno", "Template cache inserting template name " << aName + << " with project " << sProjectName); + mTemplateToProject[ aName ] = sProjectName; + } + + virtual void SAL_CALL removeByName( const OUString& Name ) override + { + if ( !hasByName( Name ) ) + throw container::NoSuchElementException(); + mTemplateToProject.erase( Name ); + } + virtual void SAL_CALL replaceByName( const OUString& aName, const uno::Any& aElement ) override + { + if ( !hasByName( aName ) ) + throw container::NoSuchElementException(); + insertByName( aName, aElement ); // insert will overwrite + } + // XElemenAccess + virtual css::uno::Type SAL_CALL getElementType( ) override + { + return ::cppu::UnoType<OUString>::get(); + } + virtual sal_Bool SAL_CALL hasElements( ) override + { + + return ( !mTemplateToProject.empty() ); + } + +}; + +class SwVbaObjectForCodeNameProvider : public ::cppu::WeakImplHelper< container::XNameAccess > +{ + SwDocShell* mpDocShell; +public: + explicit SwVbaObjectForCodeNameProvider( SwDocShell* pDocShell ) : mpDocShell( pDocShell ) + { + // #FIXME #TODO is the code name for ThisDocument read anywhere? + } + + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override + { + // #FIXME #TODO we really need to be checking against the codename for + // ThisDocument + if ( aName == "ThisDocument" ) + return true; + return false; + } + + css::uno::Any SAL_CALL getByName( const OUString& aName ) override + { + if ( !hasByName( aName ) ) + throw container::NoSuchElementException(); + uno::Sequence< uno::Any > aArgs( 2 ); + aArgs[0] <<= uno::Reference< uno::XInterface >(); + aArgs[1] <<= mpDocShell->GetModel(); + uno::Reference< uno::XInterface > xDocObj = ooo::vba::createVBAUnoAPIServiceWithArgs( mpDocShell, "ooo.vba.word.Document" , aArgs ); + SAL_INFO("sw.uno", + "Creating Object ( ooo.vba.word.Document ) 0x" << xDocObj.get()); + return uno::makeAny( xDocObj ); + } + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override + { + uno::Sequence< OUString > aNames; + return aNames; + } + // XElemenAccess + virtual css::uno::Type SAL_CALL getElementType( ) override { return uno::Type(); } + virtual sal_Bool SAL_CALL hasElements( ) override { return true; } + +}; + +} + +#endif + +namespace { + +struct ProvNamesId_Type +{ + const char * pName; + SwServiceType nType; +}; + +} + +// note: this thing is indexed as an array, so do not insert/remove entries! +const ProvNamesId_Type aProvNamesId[] = +{ + { "com.sun.star.text.TextTable", SwServiceType::TypeTextTable }, + { "com.sun.star.text.TextFrame", SwServiceType::TypeTextFrame }, + { "com.sun.star.text.GraphicObject", SwServiceType::TypeGraphic }, + { "com.sun.star.text.TextEmbeddedObject", SwServiceType::TypeOLE }, + { "com.sun.star.text.Bookmark", SwServiceType::TypeBookmark }, + { "com.sun.star.text.Footnote", SwServiceType::TypeFootnote }, + { "com.sun.star.text.Endnote", SwServiceType::TypeEndnote }, + { "com.sun.star.text.DocumentIndexMark", SwServiceType::TypeIndexMark }, + { "com.sun.star.text.DocumentIndex", SwServiceType::TypeIndex }, + { "com.sun.star.text.ReferenceMark", SwServiceType::ReferenceMark }, + { "com.sun.star.style.CharacterStyle", SwServiceType::StyleCharacter }, + { "com.sun.star.style.ParagraphStyle", SwServiceType::StyleParagraph }, + { "com.sun.star.style.FrameStyle", SwServiceType::StyleFrame }, + { "com.sun.star.style.PageStyle", SwServiceType::StylePage }, + { "com.sun.star.style.NumberingStyle", SwServiceType::StyleNumbering }, + { "com.sun.star.text.ContentIndexMark", SwServiceType::ContentIndexMark }, + { "com.sun.star.text.ContentIndex", SwServiceType::ContentIndex }, + { "com.sun.star.text.UserIndexMark", SwServiceType::UserIndexMark }, + { "com.sun.star.text.UserIndex", SwServiceType::UserIndex }, + { "com.sun.star.text.TextSection", SwServiceType::TextSection }, + { "com.sun.star.text.TextField.DateTime", SwServiceType::FieldTypeDateTime }, + { "com.sun.star.text.TextField.User", SwServiceType::FieldTypeUser }, + { "com.sun.star.text.TextField.SetExpression", SwServiceType::FieldTypeSetExp }, + { "com.sun.star.text.TextField.GetExpression", SwServiceType::FieldTypeGetExp }, + { "com.sun.star.text.TextField.FileName", SwServiceType::FieldTypeFileName }, + { "com.sun.star.text.TextField.PageNumber", SwServiceType::FieldTypePageNum }, + { "com.sun.star.text.TextField.Author", SwServiceType::FieldTypeAuthor }, + { "com.sun.star.text.TextField.Chapter", SwServiceType::FieldTypeChapter }, + { "", SwServiceType::FieldTypeDummy0 }, + { "com.sun.star.text.TextField.GetReference", SwServiceType::FieldTypeGetReference }, + { "com.sun.star.text.TextField.ConditionalText", SwServiceType::FieldTypeConditionedText }, + { "com.sun.star.text.TextField.Annotation", SwServiceType::FieldTypeAnnotation }, + { "com.sun.star.text.TextField.Input", SwServiceType::FieldTypeInput }, + { "com.sun.star.text.TextField.Macro", SwServiceType::FieldTypeMacro }, + { "com.sun.star.text.TextField.DDE", SwServiceType::FieldTypeDDE }, + { "com.sun.star.text.TextField.HiddenParagraph", SwServiceType::FieldTypeHiddenPara }, + { "" /*com.sun.star.text.TextField.DocumentInfo"*/, SwServiceType::FieldTypeDocInfo }, + { "com.sun.star.text.TextField.TemplateName", SwServiceType::FieldTypeTemplateName }, + { "com.sun.star.text.TextField.ExtendedUser", SwServiceType::FieldTypeUserExt }, + { "com.sun.star.text.TextField.ReferencePageSet", SwServiceType::FieldTypeRefPageSet }, + { "com.sun.star.text.TextField.ReferencePageGet", SwServiceType::FieldTypeRefPageGet }, + { "com.sun.star.text.TextField.JumpEdit", SwServiceType::FieldTypeJumpEdit }, + { "com.sun.star.text.TextField.Script", SwServiceType::FieldTypeScript }, + { "com.sun.star.text.TextField.DatabaseNextSet", SwServiceType::FieldTypeDatabaseNextSet }, + { "com.sun.star.text.TextField.DatabaseNumberOfSet", SwServiceType::FieldTypeDatabaseNumSet }, + { "com.sun.star.text.TextField.DatabaseSetNumber", SwServiceType::FieldTypeDatabaseSetNum }, + { "com.sun.star.text.TextField.Database", SwServiceType::FieldTypeDatabase }, + { "com.sun.star.text.TextField.DatabaseName", SwServiceType::FieldTypeDatabaseName }, + { "com.sun.star.text.TextField.TableFormula", SwServiceType::FieldTypeTableFormula }, + { "com.sun.star.text.TextField.PageCount", SwServiceType::FieldTypePageCount }, + { "com.sun.star.text.TextField.ParagraphCount", SwServiceType::FieldTypeParagraphCount }, + { "com.sun.star.text.TextField.WordCount", SwServiceType::FieldTypeWordCount }, + { "com.sun.star.text.TextField.CharacterCount", SwServiceType::FieldTypeCharacterCount }, + { "com.sun.star.text.TextField.TableCount", SwServiceType::FieldTypeTableCount }, + { "com.sun.star.text.TextField.GraphicObjectCount", SwServiceType::FieldTypeGraphicObjectCount }, + { "com.sun.star.text.TextField.EmbeddedObjectCount", SwServiceType::FieldTypeEmbeddedObjectCount }, + { "com.sun.star.text.TextField.DocInfo.ChangeAuthor", SwServiceType::FieldTypeDocInfoChangeAuthor }, + { "com.sun.star.text.TextField.DocInfo.ChangeDateTime", SwServiceType::FieldTypeDocInfoChangeDateTime }, + { "com.sun.star.text.TextField.DocInfo.EditTime", SwServiceType::FieldTypeDocInfoEditTime }, + { "com.sun.star.text.TextField.DocInfo.Description", SwServiceType::FieldTypeDocInfoDescription }, + { "com.sun.star.text.TextField.DocInfo.CreateAuthor", SwServiceType::FieldTypeDocInfoCreateAuthor }, + { "com.sun.star.text.TextField.DocInfo.CreateDateTime", SwServiceType::FieldTypeDocInfoCreateDateTime }, + { "", SwServiceType::FieldTypeDummy0 }, + { "", SwServiceType::FieldTypeDummy1 }, + { "", SwServiceType::FieldTypeDummy2 }, + { "", SwServiceType::FieldTypeDummy3 }, + { "com.sun.star.text.TextField.DocInfo.Custom", SwServiceType::FieldTypeDocInfoCustom }, + { "com.sun.star.text.TextField.DocInfo.PrintAuthor", SwServiceType::FieldTypeDocInfoPrintAuthor }, + { "com.sun.star.text.TextField.DocInfo.PrintDateTime", SwServiceType::FieldTypeDocInfoPrintDateTime }, + { "com.sun.star.text.TextField.DocInfo.KeyWords", SwServiceType::FieldTypeDocInfoKeywords }, + { "com.sun.star.text.TextField.DocInfo.Subject", SwServiceType::FieldTypeDocInfoSubject }, + { "com.sun.star.text.TextField.DocInfo.Title", SwServiceType::FieldTypeDocInfoTitle }, + { "com.sun.star.text.TextField.DocInfo.Revision", SwServiceType::FieldTypeDocInfoRevision }, + { "com.sun.star.text.TextField.Bibliography", SwServiceType::FieldTypeBibliography }, + { "com.sun.star.text.TextField.CombinedCharacters", SwServiceType::FieldTypeCombinedCharacters }, + { "com.sun.star.text.TextField.DropDown", SwServiceType::FieldTypeDropdown }, + { "com.sun.star.text.textfield.MetadataField", SwServiceType::FieldTypeMetafield }, + { "", SwServiceType::FieldTypeDummy4 }, + { "", SwServiceType::FieldTypeDummy5 }, + { "", SwServiceType::FieldTypeDummy6 }, + { "", SwServiceType::FieldTypeDummy7 }, + { "com.sun.star.text.FieldMaster.User", SwServiceType::FieldMasterUser }, + { "com.sun.star.text.FieldMaster.DDE", SwServiceType::FieldMasterDDE }, + { "com.sun.star.text.FieldMaster.SetExpression", SwServiceType::FieldMasterSetExp }, + { "com.sun.star.text.FieldMaster.Database", SwServiceType::FieldMasterDatabase }, + { "com.sun.star.text.FieldMaster.Bibliography", SwServiceType::FieldMasterBibliography }, + { "", SwServiceType::FieldMasterDummy2 }, + { "", SwServiceType::FieldMasterDummy3 }, + { "", SwServiceType::FieldMasterDummy4 }, + { "", SwServiceType::FieldMasterDummy5 }, + { "com.sun.star.text.IllustrationsIndex", SwServiceType::IndexIllustrations }, + { "com.sun.star.text.ObjectIndex", SwServiceType::IndexObjects }, + { "com.sun.star.text.TableIndex", SwServiceType::IndexTables }, + { "com.sun.star.text.Bibliography", SwServiceType::IndexBibliography }, + { "com.sun.star.text.Paragraph", SwServiceType::Paragraph }, + { "com.sun.star.text.TextField.InputUser", SwServiceType::FieldTypeInputUser }, + { "com.sun.star.text.TextField.HiddenText", SwServiceType::FieldTypeHiddenText }, + { "com.sun.star.style.ConditionalParagraphStyle", SwServiceType::StyleConditionalParagraph }, + { "com.sun.star.text.NumberingRules", SwServiceType::NumberingRules }, + { "com.sun.star.text.TextColumns", SwServiceType::TextColumns }, + { "com.sun.star.text.IndexHeaderSection", SwServiceType::IndexHeaderSection }, + { "com.sun.star.text.Defaults", SwServiceType::Defaults }, + { "com.sun.star.image.ImageMapRectangleObject", SwServiceType::IMapRectangle }, + { "com.sun.star.image.ImageMapCircleObject", SwServiceType::IMapCircle }, + { "com.sun.star.image.ImageMapPolygonObject", SwServiceType::IMapPolygon }, + { "com.sun.star.text.TextGraphicObject", SwServiceType::TypeTextGraphic }, + { "com.sun.star.chart2.data.DataProvider", SwServiceType::Chart2DataProvider }, + { "com.sun.star.text.Fieldmark", SwServiceType::TypeFieldMark }, + { "com.sun.star.text.FormFieldmark", SwServiceType::TypeFormFieldMark }, + { "com.sun.star.text.InContentMetadata", SwServiceType::TypeMeta }, + { "ooo.vba.VBAObjectModuleObjectProvider", SwServiceType::VbaObjectProvider }, + { "ooo.vba.VBACodeNameProvider", SwServiceType::VbaCodeNameProvider }, + { "ooo.vba.VBAProjectNameProvider", SwServiceType::VbaProjectNameProvider }, + { "ooo.vba.VBAGlobals", SwServiceType::VbaGlobals }, + + // case-correct versions of the service names (see #i67811) + { CSS_TEXT_TEXTFIELD_DATE_TIME, SwServiceType::FieldTypeDateTime }, + { CSS_TEXT_TEXTFIELD_USER, SwServiceType::FieldTypeUser }, + { CSS_TEXT_TEXTFIELD_SET_EXPRESSION, SwServiceType::FieldTypeSetExp }, + { CSS_TEXT_TEXTFIELD_GET_EXPRESSION, SwServiceType::FieldTypeGetExp }, + { CSS_TEXT_TEXTFIELD_FILE_NAME, SwServiceType::FieldTypeFileName }, + { CSS_TEXT_TEXTFIELD_PAGE_NUMBER, SwServiceType::FieldTypePageNum }, + { CSS_TEXT_TEXTFIELD_AUTHOR, SwServiceType::FieldTypeAuthor }, + { CSS_TEXT_TEXTFIELD_CHAPTER, SwServiceType::FieldTypeChapter }, + { CSS_TEXT_TEXTFIELD_GET_REFERENCE, SwServiceType::FieldTypeGetReference }, + { CSS_TEXT_TEXTFIELD_CONDITIONAL_TEXT, SwServiceType::FieldTypeConditionedText }, + { CSS_TEXT_TEXTFIELD_ANNOTATION, SwServiceType::FieldTypeAnnotation }, + { CSS_TEXT_TEXTFIELD_INPUT, SwServiceType::FieldTypeInput }, + { CSS_TEXT_TEXTFIELD_MACRO, SwServiceType::FieldTypeMacro }, + { CSS_TEXT_TEXTFIELD_DDE, SwServiceType::FieldTypeDDE }, + { CSS_TEXT_TEXTFIELD_HIDDEN_PARAGRAPH, SwServiceType::FieldTypeHiddenPara }, + { CSS_TEXT_TEXTFIELD_TEMPLATE_NAME, SwServiceType::FieldTypeTemplateName }, + { CSS_TEXT_TEXTFIELD_EXTENDED_USER, SwServiceType::FieldTypeUserExt }, + { CSS_TEXT_TEXTFIELD_REFERENCE_PAGE_SET, SwServiceType::FieldTypeRefPageSet }, + { CSS_TEXT_TEXTFIELD_REFERENCE_PAGE_GET, SwServiceType::FieldTypeRefPageGet }, + { CSS_TEXT_TEXTFIELD_JUMP_EDIT, SwServiceType::FieldTypeJumpEdit }, + { CSS_TEXT_TEXTFIELD_SCRIPT, SwServiceType::FieldTypeScript }, + { CSS_TEXT_TEXTFIELD_DATABASE_NEXT_SET, SwServiceType::FieldTypeDatabaseNextSet }, + { CSS_TEXT_TEXTFIELD_DATABASE_NUMBER_OF_SET, SwServiceType::FieldTypeDatabaseNumSet }, + { CSS_TEXT_TEXTFIELD_DATABASE_SET_NUMBER, SwServiceType::FieldTypeDatabaseSetNum }, + { CSS_TEXT_TEXTFIELD_DATABASE, SwServiceType::FieldTypeDatabase }, + { CSS_TEXT_TEXTFIELD_DATABASE_NAME, SwServiceType::FieldTypeDatabaseName }, + { CSS_TEXT_TEXTFIELD_TABLE_FORMULA, SwServiceType::FieldTypeTableFormula }, + { CSS_TEXT_TEXTFIELD_PAGE_COUNT, SwServiceType::FieldTypePageCount }, + { CSS_TEXT_TEXTFIELD_PARAGRAPH_COUNT, SwServiceType::FieldTypeParagraphCount }, + { CSS_TEXT_TEXTFIELD_WORD_COUNT, SwServiceType::FieldTypeWordCount }, + { CSS_TEXT_TEXTFIELD_CHARACTER_COUNT, SwServiceType::FieldTypeCharacterCount }, + { CSS_TEXT_TEXTFIELD_TABLE_COUNT, SwServiceType::FieldTypeTableCount }, + { CSS_TEXT_TEXTFIELD_GRAPHIC_OBJECT_COUNT, SwServiceType::FieldTypeGraphicObjectCount }, + { CSS_TEXT_TEXTFIELD_EMBEDDED_OBJECT_COUNT, SwServiceType::FieldTypeEmbeddedObjectCount }, + { CSS_TEXT_TEXTFIELD_DOCINFO_CHANGE_AUTHOR, SwServiceType::FieldTypeDocInfoChangeAuthor }, + { CSS_TEXT_TEXTFIELD_DOCINFO_CHANGE_DATE_TIME, SwServiceType::FieldTypeDocInfoChangeDateTime }, + { CSS_TEXT_TEXTFIELD_DOCINFO_EDIT_TIME, SwServiceType::FieldTypeDocInfoEditTime }, + { CSS_TEXT_TEXTFIELD_DOCINFO_DESCRIPTION, SwServiceType::FieldTypeDocInfoDescription }, + { CSS_TEXT_TEXTFIELD_DOCINFO_CREATE_AUTHOR, SwServiceType::FieldTypeDocInfoCreateAuthor }, + { CSS_TEXT_TEXTFIELD_DOCINFO_CREATE_DATE_TIME, SwServiceType::FieldTypeDocInfoCreateDateTime }, + { CSS_TEXT_TEXTFIELD_DOCINFO_PRINT_AUTHOR, SwServiceType::FieldTypeDocInfoPrintAuthor }, + { CSS_TEXT_TEXTFIELD_DOCINFO_PRINT_DATE_TIME, SwServiceType::FieldTypeDocInfoPrintDateTime }, + { CSS_TEXT_TEXTFIELD_DOCINFO_KEY_WORDS, SwServiceType::FieldTypeDocInfoKeywords }, + { CSS_TEXT_TEXTFIELD_DOCINFO_SUBJECT, SwServiceType::FieldTypeDocInfoSubject }, + { CSS_TEXT_TEXTFIELD_DOCINFO_TITLE, SwServiceType::FieldTypeDocInfoTitle }, + { CSS_TEXT_TEXTFIELD_DOCINFO_REVISION, SwServiceType::FieldTypeDocInfoRevision }, + { CSS_TEXT_TEXTFIELD_DOCINFO_CUSTOM, SwServiceType::FieldTypeDocInfoCustom }, + { CSS_TEXT_TEXTFIELD_BIBLIOGRAPHY, SwServiceType::FieldTypeBibliography }, + { CSS_TEXT_TEXTFIELD_COMBINED_CHARACTERS, SwServiceType::FieldTypeCombinedCharacters }, + { CSS_TEXT_TEXTFIELD_DROP_DOWN, SwServiceType::FieldTypeDropdown }, + { CSS_TEXT_TEXTFIELD_INPUT_USER, SwServiceType::FieldTypeInputUser }, + { CSS_TEXT_TEXTFIELD_HIDDEN_TEXT, SwServiceType::FieldTypeHiddenText }, + { CSS_TEXT_FIELDMASTER_USER, SwServiceType::FieldMasterUser }, + { CSS_TEXT_FIELDMASTER_DDE, SwServiceType::FieldMasterDDE }, + { CSS_TEXT_FIELDMASTER_SET_EXPRESSION, SwServiceType::FieldMasterSetExp }, + { CSS_TEXT_FIELDMASTER_DATABASE, SwServiceType::FieldMasterDatabase }, + { CSS_TEXT_FIELDMASTER_BIBLIOGRAPHY, SwServiceType::FieldMasterBibliography }, + { "com.sun.star.style.TableStyle", SwServiceType::StyleTable }, + { "com.sun.star.style.CellStyle", SwServiceType::StyleCell } +}; + +const SvEventDescription* sw_GetSupportedMacroItems() +{ + static const SvEventDescription aMacroDescriptionsImpl[] = + { + { SvMacroItemId::OnMouseOver, "OnMouseOver" }, + { SvMacroItemId::OnMouseOut, "OnMouseOut" }, + { SvMacroItemId::NONE, nullptr } + }; + + return aMacroDescriptionsImpl; +} + +OUString SwXServiceProvider::GetProviderName(SwServiceType nObjectType) +{ + SolarMutexGuard aGuard; + OUString sRet; + const sal_uInt16 nEntries = SAL_N_ELEMENTS(aProvNamesId); + if(static_cast<sal_uInt16>(nObjectType) < nEntries) + sRet = OUString::createFromAscii(aProvNamesId[static_cast<sal_uInt16>(nObjectType)].pName); + return sRet; +} + +uno::Sequence<OUString> SwXServiceProvider::GetAllServiceNames() +{ + const sal_uInt16 nEntries = SAL_N_ELEMENTS(aProvNamesId); + uno::Sequence<OUString> aRet(nEntries); + OUString* pArray = aRet.getArray(); + sal_uInt16 n = 0; + for(const ProvNamesId_Type & i : aProvNamesId) + { + OUString sProv(OUString::createFromAscii(i.pName)); + if(!sProv.isEmpty()) + { + pArray[n] = sProv; + n++; + } + } + aRet.realloc(n); + return aRet; + +} + +SwServiceType SwXServiceProvider::GetProviderType(const OUString& rServiceName) +{ + for(const ProvNamesId_Type & i : aProvNamesId) + { + if (rServiceName.equalsAscii(i.pName)) + return i.nType; + } + return SwServiceType::Invalid; +} + +uno::Reference<uno::XInterface> +SwXServiceProvider::MakeInstance(SwServiceType nObjectType, SwDoc & rDoc) +{ + SolarMutexGuard aGuard; + uno::Reference< uno::XInterface > xRet; + switch(nObjectType) + { + case SwServiceType::TypeTextTable: + { + xRet = SwXTextTable::CreateXTextTable(nullptr); + } + break; + case SwServiceType::TypeTextFrame: + { + xRet = SwXTextFrame::CreateXTextFrame(rDoc, nullptr); + } + break; + case SwServiceType::TypeGraphic : + case SwServiceType::TypeTextGraphic /* #i47503# */ : + { + xRet = SwXTextGraphicObject::CreateXTextGraphicObject(rDoc, nullptr); + + } + break; + case SwServiceType::TypeOLE : + { + xRet = SwXTextEmbeddedObject::CreateXTextEmbeddedObject(rDoc, nullptr); + } + break; + case SwServiceType::TypeBookmark : + { + xRet = SwXBookmark::CreateXBookmark(rDoc, nullptr); + } + break; + case SwServiceType::TypeFieldMark : + { + xRet = SwXFieldmark::CreateXFieldmark(rDoc, nullptr); + } + break; + case SwServiceType::TypeFormFieldMark : + { + xRet = SwXFieldmark::CreateXFieldmark(rDoc, nullptr, true); + } + break; + case SwServiceType::VbaObjectProvider : +#if HAVE_FEATURE_SCRIPTING + { + SwVbaObjectForCodeNameProvider* pObjProv = + new SwVbaObjectForCodeNameProvider(rDoc.GetDocShell()); + xRet = static_cast<cppu::OWeakObject*>(pObjProv); + } +#endif + break; + case SwServiceType::VbaCodeNameProvider : +#if HAVE_FEATURE_SCRIPTING + { + if (rDoc.GetDocShell() && ooo::vba::isAlienWordDoc(*rDoc.GetDocShell())) + { + SwVbaCodeNameProvider* pObjProv = new SwVbaCodeNameProvider(rDoc.GetDocShell()); + xRet = static_cast<cppu::OWeakObject*>(pObjProv); + } + } +#endif + break; + case SwServiceType::VbaProjectNameProvider : +#if HAVE_FEATURE_SCRIPTING + { + uno::Reference< container::XNameContainer > xProjProv = rDoc.GetVBATemplateToProjectCache(); + if (!xProjProv.is() && rDoc.GetDocShell() + && ooo::vba::isAlienWordDoc(*rDoc.GetDocShell())) + { + xProjProv = new SwVbaProjectNameProvider; + rDoc.SetVBATemplateToProjectCache(xProjProv); + } + xRet = xProjProv; + } +#endif + break; + case SwServiceType::VbaGlobals : +#if HAVE_FEATURE_SCRIPTING + { + uno::Any aGlobs; + BasicManager *pBasicMan = rDoc.GetDocShell()->GetBasicManager(); + if (pBasicMan && !pBasicMan->GetGlobalUNOConstant("VBAGlobals", aGlobs)) + { + uno::Sequence< uno::Any > aArgs(1); + aArgs[ 0 ] <<= rDoc.GetDocShell()->GetModel(); + aGlobs <<= ::comphelper::getProcessServiceFactory()->createInstanceWithArguments( "ooo.vba.word.Globals", aArgs ); + pBasicMan->SetGlobalUNOConstant( "VBAGlobals", aGlobs ); + } + aGlobs >>= xRet; + } +#endif + break; + + case SwServiceType::TypeFootnote : + xRet = SwXFootnote::CreateXFootnote(rDoc, nullptr); + break; + case SwServiceType::TypeEndnote : + xRet = SwXFootnote::CreateXFootnote(rDoc, nullptr, true); + break; + case SwServiceType::ContentIndexMark : + case SwServiceType::UserIndexMark : + case SwServiceType::TypeIndexMark: + { + TOXTypes eType = TOX_INDEX; + if(SwServiceType::ContentIndexMark== nObjectType) + eType = TOX_CONTENT; + else if(SwServiceType::UserIndexMark == nObjectType) + eType = TOX_USER; + xRet = SwXDocumentIndexMark::CreateXDocumentIndexMark(rDoc, nullptr, eType); + } + break; + case SwServiceType::ContentIndex : + case SwServiceType::UserIndex : + case SwServiceType::TypeIndex : + case SwServiceType::IndexIllustrations: + case SwServiceType::IndexObjects : + case SwServiceType::IndexTables: + case SwServiceType::IndexBibliography : + { + TOXTypes eType = TOX_INDEX; + if(SwServiceType::ContentIndex == nObjectType) + eType = TOX_CONTENT; + else if(SwServiceType::UserIndex == nObjectType) + eType = TOX_USER; + else if(SwServiceType::IndexIllustrations == nObjectType) + { + eType = TOX_ILLUSTRATIONS; + } + else if(SwServiceType::IndexObjects == nObjectType) + { + eType = TOX_OBJECTS; + } + else if(SwServiceType::IndexBibliography == nObjectType) + { + eType = TOX_AUTHORITIES; + } + else if(SwServiceType::IndexTables == nObjectType) + { + eType = TOX_TABLES; + } + xRet = SwXDocumentIndex::CreateXDocumentIndex(rDoc, nullptr, eType); + } + break; + case SwServiceType::IndexHeaderSection : + case SwServiceType::TextSection : + xRet = SwXTextSection::CreateXTextSection(nullptr, + (SwServiceType::IndexHeaderSection == nObjectType)); + + break; + case SwServiceType::ReferenceMark : + xRet = SwXReferenceMark::CreateXReferenceMark(rDoc, nullptr); + break; + case SwServiceType::StyleCharacter: + case SwServiceType::StyleParagraph: + case SwServiceType::StyleConditionalParagraph: + case SwServiceType::StyleFrame: + case SwServiceType::StylePage: + case SwServiceType::StyleNumbering: + case SwServiceType::StyleTable: + case SwServiceType::StyleCell: + { + SfxStyleFamily eFamily = SfxStyleFamily::Char; + switch(nObjectType) + { + case SwServiceType::StyleParagraph: + eFamily = SfxStyleFamily::Para; + break; + case SwServiceType::StyleConditionalParagraph: + eFamily = SfxStyleFamily::Para; + xRet = SwXStyleFamilies::CreateStyleCondParagraph(rDoc); + break; + case SwServiceType::StyleFrame: + eFamily = SfxStyleFamily::Frame; + break; + case SwServiceType::StylePage: + eFamily = SfxStyleFamily::Page; + break; + case SwServiceType::StyleNumbering: + eFamily = SfxStyleFamily::Pseudo; + break; + case SwServiceType::StyleTable: + eFamily = SfxStyleFamily::Table; + break; + case SwServiceType::StyleCell: + eFamily = SfxStyleFamily::Cell; + break; + default: break; + } + if(!xRet.is()) + xRet = SwXStyleFamilies::CreateStyle(eFamily, rDoc); + } + break; + case SwServiceType::FieldTypeDateTime: + case SwServiceType::FieldTypeUser: + case SwServiceType::FieldTypeSetExp: + case SwServiceType::FieldTypeGetExp: + case SwServiceType::FieldTypeFileName: + case SwServiceType::FieldTypePageNum: + case SwServiceType::FieldTypeAuthor: + case SwServiceType::FieldTypeChapter: + case SwServiceType::FieldTypeGetReference: + case SwServiceType::FieldTypeConditionedText: + case SwServiceType::FieldTypeInput: + case SwServiceType::FieldTypeMacro: + case SwServiceType::FieldTypeDDE: + case SwServiceType::FieldTypeHiddenPara: + case SwServiceType::FieldTypeDocInfo: + case SwServiceType::FieldTypeTemplateName: + case SwServiceType::FieldTypeUserExt: + case SwServiceType::FieldTypeRefPageSet: + case SwServiceType::FieldTypeRefPageGet: + case SwServiceType::FieldTypeJumpEdit: + case SwServiceType::FieldTypeScript: + case SwServiceType::FieldTypeDatabaseNextSet: + case SwServiceType::FieldTypeDatabaseNumSet: + case SwServiceType::FieldTypeDatabaseSetNum: + case SwServiceType::FieldTypeDatabase: + case SwServiceType::FieldTypeDatabaseName: + case SwServiceType::FieldTypePageCount: + case SwServiceType::FieldTypeParagraphCount: + case SwServiceType::FieldTypeWordCount: + case SwServiceType::FieldTypeCharacterCount: + case SwServiceType::FieldTypeTableCount: + case SwServiceType::FieldTypeGraphicObjectCount: + case SwServiceType::FieldTypeEmbeddedObjectCount: + case SwServiceType::FieldTypeDocInfoChangeAuthor: + case SwServiceType::FieldTypeDocInfoChangeDateTime: + case SwServiceType::FieldTypeDocInfoEditTime: + case SwServiceType::FieldTypeDocInfoDescription: + case SwServiceType::FieldTypeDocInfoCreateAuthor: + case SwServiceType::FieldTypeDocInfoCreateDateTime: + case SwServiceType::FieldTypeDocInfoCustom: + case SwServiceType::FieldTypeDocInfoPrintAuthor: + case SwServiceType::FieldTypeDocInfoPrintDateTime: + case SwServiceType::FieldTypeDocInfoKeywords: + case SwServiceType::FieldTypeDocInfoSubject: + case SwServiceType::FieldTypeDocInfoTitle: + case SwServiceType::FieldTypeDocInfoRevision: + case SwServiceType::FieldTypeBibliography: + case SwServiceType::FieldTypeInputUser: + case SwServiceType::FieldTypeHiddenText: + case SwServiceType::FieldTypeCombinedCharacters: + case SwServiceType::FieldTypeDropdown: + case SwServiceType::FieldTypeTableFormula: + // NOTE: the sw.SwXAutoTextEntry unoapi test depends on pDoc = 0 + xRet = SwXTextField::CreateXTextField(nullptr, nullptr, nObjectType); + break; + case SwServiceType::FieldTypeAnnotation: + xRet = SwXTextField::CreateXTextField(&rDoc, nullptr, nObjectType); + break; + case SwServiceType::FieldMasterUser: + case SwServiceType::FieldMasterDDE: + case SwServiceType::FieldMasterSetExp : + case SwServiceType::FieldMasterDatabase: + { + SwFieldIds nResId = SwFieldIds::Unknown; + switch(nObjectType) + { + case SwServiceType::FieldMasterUser: nResId = SwFieldIds::User; break; + case SwServiceType::FieldMasterDDE: nResId = SwFieldIds::Dde; break; + case SwServiceType::FieldMasterSetExp : nResId = SwFieldIds::SetExp; break; + case SwServiceType::FieldMasterDatabase: nResId = SwFieldIds::Database; break; + default: break; + } + xRet = SwXFieldMaster::CreateXFieldMaster(&rDoc, nullptr, nResId); + } + break; + case SwServiceType::FieldMasterBibliography: + { + SwFieldType* pType = rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::TableOfAuthorities, OUString(), true); + if(!pType) + { + SwAuthorityFieldType aType(&rDoc); + pType = rDoc.getIDocumentFieldsAccess().InsertFieldType(aType); + } + xRet = SwXFieldMaster::CreateXFieldMaster(&rDoc, pType); + } + break; + case SwServiceType::Paragraph: + xRet = SwXParagraph::CreateXParagraph(rDoc, nullptr); + break; + case SwServiceType::NumberingRules: + xRet = static_cast<cppu::OWeakObject*>(new SwXNumberingRules(rDoc)); + break; + case SwServiceType::TextColumns: + xRet = static_cast<cppu::OWeakObject*>(new SwXTextColumns); + break; + case SwServiceType::Defaults: + xRet = static_cast<cppu::OWeakObject*>(new SwXTextDefaults(&rDoc)); + break; + case SwServiceType::IMapRectangle: + xRet = SvUnoImageMapRectangleObject_createInstance( sw_GetSupportedMacroItems() ); + break; + case SwServiceType::IMapCircle: + xRet = SvUnoImageMapCircleObject_createInstance( sw_GetSupportedMacroItems() ); + break; + case SwServiceType::IMapPolygon: + xRet = SvUnoImageMapPolygonObject_createInstance( sw_GetSupportedMacroItems() ); + break; + case SwServiceType::Chart2DataProvider: + // #i64497# If a chart is in a temporary document during clipboard + // paste, there should be no data provider, so that own data is used + // This should not happen during copy/paste, as this will unlink + // charts using table data. + if (rDoc.GetDocShell()->GetCreateMode() != SfxObjectCreateMode::EMBEDDED) + xRet = static_cast<cppu::OWeakObject*>(rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider( true /* create - if not yet available */ )); + else + SAL_WARN("sw.uno", + "not creating chart data provider for embedded object"); + + break; + case SwServiceType::TypeMeta: + xRet = SwXMeta::CreateXMeta(rDoc, false); + break; + case SwServiceType::FieldTypeMetafield: + xRet = SwXMeta::CreateXMeta(rDoc, true); + break; + default: + throw uno::RuntimeException(); + } + return xRet; +} + +//SMART_UNO_IMPLEMENTATION( SwXTextTables, UsrObject ); +SwXTextTables::SwXTextTables(SwDoc* pDc) : + SwUnoCollection(pDc) +{ + +} + +SwXTextTables::~SwXTextTables() +{ + +} + +sal_Int32 SwXTextTables::getCount() +{ + SolarMutexGuard aGuard; + sal_Int32 nRet = 0; + if(IsValid()) + nRet = static_cast<sal_Int32>(GetDoc()->GetTableFrameFormatCount(true)); + return nRet; +} + +uno::Any SAL_CALL SwXTextTables::getByIndex(sal_Int32 nInputIndex) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if (!IsValid()) + throw uno::RuntimeException(); + + if (nInputIndex < 0) + throw IndexOutOfBoundsException(); + + SwAutoFormatGetDocNode aGetHt( &GetDoc()->GetNodes() ); + size_t nIndex = static_cast<size_t>(nInputIndex); + size_t nCurrentIndex = 0; + + for (SwFrameFormat* const & pFormat : *GetDoc()->GetTableFrameFormats()) + { + if (!pFormat->GetInfo(aGetHt)) + { + if (nCurrentIndex == nIndex) + { + uno::Reference<XTextTable> xTable = SwXTextTables::GetObject(*pFormat); + aRet <<= xTable; + return aRet; + } + else + nCurrentIndex++; + } + } + throw IndexOutOfBoundsException(); +} + +uno::Any SwXTextTables::getByName(const OUString& rItemName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(!IsValid()) + throw uno::RuntimeException(); + + const size_t nCount = GetDoc()->GetTableFrameFormatCount(true); + uno::Reference< XTextTable > xTable; + for( size_t i = 0; i < nCount; ++i) + { + SwFrameFormat& rFormat = GetDoc()->GetTableFrameFormat(i, true); + if (rItemName == rFormat.GetName()) + { + xTable = SwXTextTables::GetObject(rFormat); + aRet <<= xTable; + break; + } + } + if(!xTable.is()) + throw NoSuchElementException(); + + return aRet; +} + +uno::Sequence< OUString > SwXTextTables::getElementNames() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const size_t nCount = GetDoc()->GetTableFrameFormatCount(true); + uno::Sequence<OUString> aSeq(static_cast<sal_Int32>(nCount)); + if(nCount) + { + OUString* pArray = aSeq.getArray(); + for( size_t i = 0; i < nCount; ++i) + { + SwFrameFormat& rFormat = GetDoc()->GetTableFrameFormat(i, true); + + pArray[i] = rFormat.GetName(); + } + } + return aSeq; +} + +sal_Bool SwXTextTables::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + bool bRet= false; + if(!IsValid()) + throw uno::RuntimeException(); + + const size_t nCount = GetDoc()->GetTableFrameFormatCount(true); + for( size_t i = 0; i < nCount; ++i) + { + SwFrameFormat& rFormat = GetDoc()->GetTableFrameFormat(i, true); + if (rName == rFormat.GetName()) + { + bRet = true; + break; + } + } + return bRet; +} + +uno::Type SAL_CALL + SwXTextTables::getElementType( ) +{ + return cppu::UnoType<XTextTable>::get(); +} + +sal_Bool SwXTextTables::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return 0 != GetDoc()->GetTableFrameFormatCount(true); +} + +OUString SwXTextTables::getImplementationName() +{ + return "SwXTextTables"; +} + +sal_Bool SwXTextTables::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextTables::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextTables" }; +} + +uno::Reference<text::XTextTable> SwXTextTables::GetObject(SwFrameFormat& rFormat) +{ + return SwXTextTable::CreateXTextTable(& rFormat); +} + +namespace +{ + template<FlyCntType T> struct UnoFrameWrap_traits {}; + + template<> + struct UnoFrameWrap_traits<FLYCNTTYPE_FRM> + { + static uno::Any wrapFrame(SwFrameFormat & rFrameFormat) + { + uno::Reference<text::XTextFrame> const xRet( + SwXTextFrame::CreateXTextFrame(*rFrameFormat.GetDoc(), &rFrameFormat)); + return uno::makeAny(xRet); + } + static bool filter(const SwNode* const pNode) { return !pNode->IsNoTextNode(); }; + }; + + template<> + struct UnoFrameWrap_traits<FLYCNTTYPE_GRF> + { + static uno::Any wrapFrame(SwFrameFormat & rFrameFormat) + { + uno::Reference<text::XTextContent> const xRet( + SwXTextGraphicObject::CreateXTextGraphicObject(*rFrameFormat.GetDoc(), &rFrameFormat)); + return uno::makeAny(xRet); + } + static bool filter(const SwNode* const pNode) { return pNode->IsGrfNode(); }; + }; + + template<> + struct UnoFrameWrap_traits<FLYCNTTYPE_OLE> + { + static uno::Any wrapFrame(SwFrameFormat & rFrameFormat) + { + uno::Reference<text::XTextContent> const xRet( + SwXTextEmbeddedObject::CreateXTextEmbeddedObject(*rFrameFormat.GetDoc(), &rFrameFormat)); + return uno::makeAny(xRet); + } + static bool filter(const SwNode* const pNode) { return pNode->IsOLENode(); }; + }; + + template<FlyCntType T> + uno::Any lcl_UnoWrapFrame(SwFrameFormat* pFormat) + { + return UnoFrameWrap_traits<T>::wrapFrame(*pFormat); + } + + // runtime adapter for lcl_UnoWrapFrame + /// @throws uno::RuntimeException + uno::Any lcl_UnoWrapFrame(SwFrameFormat* pFormat, FlyCntType eType) + { + switch(eType) + { + case FLYCNTTYPE_FRM: + return lcl_UnoWrapFrame<FLYCNTTYPE_FRM>(pFormat); + case FLYCNTTYPE_GRF: + return lcl_UnoWrapFrame<FLYCNTTYPE_GRF>(pFormat); + case FLYCNTTYPE_OLE: + return lcl_UnoWrapFrame<FLYCNTTYPE_OLE>(pFormat); + default: + throw uno::RuntimeException(); + } + } + + template<FlyCntType T> + class SwXFrameEnumeration + : public SwSimpleEnumeration_Base + { + private: + std::vector< Any > m_aFrames; + protected: + virtual ~SwXFrameEnumeration() override {}; + public: + SwXFrameEnumeration(const SwDoc* const pDoc); + + //XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual Any SAL_CALL nextElement() override; + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + }; +} + +template<FlyCntType T> +SwXFrameEnumeration<T>::SwXFrameEnumeration(const SwDoc* const pDoc) + : m_aFrames() +{ + SolarMutexGuard aGuard; + const SwFrameFormats* const pFormats = pDoc->GetSpzFrameFormats(); + if(pFormats->empty()) + return; + // #i104937# + const size_t nSize = pFormats->size(); + // #i104937# + SwFrameFormat* pFormat( nullptr ); + for( size_t i = 0; i < nSize; ++i ) + { + // #i104937# + pFormat = (*pFormats)[i]; + if(pFormat->Which() != RES_FLYFRMFMT || SwTextBoxHelper::isTextBox(pFormat, RES_FLYFRMFMT)) + continue; + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(!pIdx || !pIdx->GetNodes().IsDocNodes()) + continue; + const SwNode* pNd = pDoc->GetNodes()[ pIdx->GetIndex() + 1 ]; + if(UnoFrameWrap_traits<T>::filter(pNd)) + m_aFrames.push_back(lcl_UnoWrapFrame<T>(pFormat)); + } +} + +template<FlyCntType T> +sal_Bool SwXFrameEnumeration<T>::hasMoreElements() +{ + SolarMutexGuard aGuard; + return !m_aFrames.empty(); +} + +template<FlyCntType T> +Any SwXFrameEnumeration<T>::nextElement() +{ + SolarMutexGuard aGuard; + if(m_aFrames.empty()) + throw NoSuchElementException(); + + Any aResult = m_aFrames.back(); + m_aFrames.pop_back(); + return aResult; +} + +template<FlyCntType T> +OUString SwXFrameEnumeration<T>::getImplementationName() +{ + return "SwXFrameEnumeration"; +} + +template<FlyCntType T> +sal_Bool SwXFrameEnumeration<T>::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +template<FlyCntType T> +Sequence< OUString > SwXFrameEnumeration<T>::getSupportedServiceNames() +{ + return { OUString("com.sun.star.container.XEnumeration") }; +} + +OUString SwXFrames::getImplementationName() +{ + return "SwXFrames"; +} + +sal_Bool SwXFrames::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence<OUString> SwXFrames::getSupportedServiceNames() +{ + return { OUString("com.sun.star.text.TextFrames") }; +} + +SwXFrames::SwXFrames(SwDoc* _pDoc, FlyCntType eSet) : + SwUnoCollection(_pDoc), + m_eType(eSet) +{} + +SwXFrames::~SwXFrames() +{} + +uno::Reference<container::XEnumeration> SwXFrames::createEnumeration() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + switch(m_eType) + { + case FLYCNTTYPE_FRM: + return uno::Reference< container::XEnumeration >( + new SwXFrameEnumeration<FLYCNTTYPE_FRM>(GetDoc())); + case FLYCNTTYPE_GRF: + return uno::Reference< container::XEnumeration >( + new SwXFrameEnumeration<FLYCNTTYPE_GRF>(GetDoc())); + case FLYCNTTYPE_OLE: + return uno::Reference< container::XEnumeration >( + new SwXFrameEnumeration<FLYCNTTYPE_OLE>(GetDoc())); + default: + throw uno::RuntimeException(); + } +} + +sal_Int32 SwXFrames::getCount() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + // Ignore TextBoxes for TextFrames. + return static_cast<sal_Int32>(GetDoc()->GetFlyCount(m_eType, /*bIgnoreTextBoxes=*/m_eType == FLYCNTTYPE_FRM)); +} + +uno::Any SwXFrames::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + if(nIndex < 0) + throw IndexOutOfBoundsException(); + // Ignore TextBoxes for TextFrames. + SwFrameFormat* pFormat = GetDoc()->GetFlyNum(static_cast<size_t>(nIndex), m_eType, /*bIgnoreTextBoxes=*/m_eType == FLYCNTTYPE_FRM); + if(!pFormat) + throw IndexOutOfBoundsException(); + return lcl_UnoWrapFrame(pFormat, m_eType); +} + +uno::Any SwXFrames::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const SwFrameFormat* pFormat; + switch(m_eType) + { + case FLYCNTTYPE_GRF: + pFormat = GetDoc()->FindFlyByName(rName, SwNodeType::Grf); + break; + case FLYCNTTYPE_OLE: + pFormat = GetDoc()->FindFlyByName(rName, SwNodeType::Ole); + break; + default: + pFormat = GetDoc()->FindFlyByName(rName, SwNodeType::Text); + break; + } + if(!pFormat) + throw NoSuchElementException(); + return lcl_UnoWrapFrame(const_cast<SwFrameFormat*>(pFormat), m_eType); +} + +uno::Sequence<OUString> SwXFrames::getElementNames() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const Reference<XEnumeration> xEnum = createEnumeration(); + std::vector<OUString> vNames; + while(xEnum->hasMoreElements()) + { + Reference<container::XNamed> xNamed; + xEnum->nextElement() >>= xNamed; + if(xNamed.is()) + vNames.push_back(xNamed->getName()); + } + return ::comphelper::containerToSequence(vNames); +} + +sal_Bool SwXFrames::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + switch(m_eType) + { + case FLYCNTTYPE_GRF: + return GetDoc()->FindFlyByName(rName, SwNodeType::Grf) != nullptr; + case FLYCNTTYPE_OLE: + return GetDoc()->FindFlyByName(rName, SwNodeType::Ole) != nullptr; + default: + return GetDoc()->FindFlyByName(rName, SwNodeType::Text) != nullptr; + } +} + +uno::Type SAL_CALL SwXFrames::getElementType() +{ + SolarMutexGuard aGuard; + switch(m_eType) + { + case FLYCNTTYPE_FRM: + return cppu::UnoType<XTextFrame>::get(); + case FLYCNTTYPE_GRF: + return cppu::UnoType<XTextContent>::get(); + case FLYCNTTYPE_OLE: + return cppu::UnoType<XEmbeddedObjectSupplier>::get(); + default: + return uno::Type(); + } +} + +sal_Bool SwXFrames::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return GetDoc()->GetFlyCount(m_eType) > 0; +} + + +OUString SwXTextFrames::getImplementationName() +{ + return "SwXTextFrames"; +} + +sal_Bool SwXTextFrames::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextFrames::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextFrames" }; +} + +SwXTextFrames::SwXTextFrames(SwDoc* _pDoc) : + SwXFrames(_pDoc, FLYCNTTYPE_FRM) +{ +} + +SwXTextFrames::~SwXTextFrames() +{ +} + +OUString SwXTextGraphicObjects::getImplementationName() +{ + return "SwXTextGraphicObjects"; +} + +sal_Bool SwXTextGraphicObjects::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextGraphicObjects::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextGraphicObjects" }; +} + +SwXTextGraphicObjects::SwXTextGraphicObjects(SwDoc* _pDoc) : + SwXFrames(_pDoc, FLYCNTTYPE_GRF) +{ +} + +SwXTextGraphicObjects::~SwXTextGraphicObjects() +{ +} + +OUString SwXTextEmbeddedObjects::getImplementationName() +{ + return "SwXTextEmbeddedObjects"; +} + +sal_Bool SwXTextEmbeddedObjects::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextEmbeddedObjects::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextEmbeddedObjects" }; +} + +SwXTextEmbeddedObjects::SwXTextEmbeddedObjects(SwDoc* _pDoc) : + SwXFrames(_pDoc, FLYCNTTYPE_OLE) +{ +} + +SwXTextEmbeddedObjects::~SwXTextEmbeddedObjects() +{ +} + +OUString SwXTextSections::getImplementationName() +{ + return "SwXTextSections"; +} + +sal_Bool SwXTextSections::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextSections::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextSections" }; +} + +SwXTextSections::SwXTextSections(SwDoc* _pDoc) : + SwUnoCollection(_pDoc) +{ +} + +SwXTextSections::~SwXTextSections() +{ +} + +sal_Int32 SwXTextSections::getCount() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const SwSectionFormats& rSectFormats = GetDoc()->GetSections(); + size_t nCount = rSectFormats.size(); + for(size_t i = nCount; i; --i) + { + if( !rSectFormats[i - 1]->IsInNodesArr()) + nCount--; + } + return nCount; +} + +uno::Any SwXTextSections::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + uno::Reference< XTextSection > xRet; + if(!IsValid()) + throw uno::RuntimeException(); + + SwSectionFormats& rFormats = GetDoc()->GetSections(); + + const SwSectionFormats& rSectFormats = GetDoc()->GetSections(); + const size_t nCount = rSectFormats.size(); + for(size_t i = 0; i < nCount; ++i) + { + if( !rSectFormats[i]->IsInNodesArr()) + nIndex ++; + else if(static_cast<size_t>(nIndex) == i) + break; + if(static_cast<size_t>(nIndex) == i) + break; + } + if(!(nIndex >= 0 && o3tl::make_unsigned(nIndex) < rFormats.size())) + throw IndexOutOfBoundsException(); + + SwSectionFormat* pFormat = rFormats[nIndex]; + xRet = GetObject(*pFormat); + + return makeAny(xRet); +} + +uno::Any SwXTextSections::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(!IsValid()) + throw uno::RuntimeException(); + + SwSectionFormats& rFormats = GetDoc()->GetSections(); + uno::Reference< XTextSection > xSect; + for(size_t i = 0; i < rFormats.size(); ++i) + { + SwSectionFormat* pFormat = rFormats[i]; + if (pFormat->IsInNodesArr() + && (rName == pFormat->GetSection()->GetSectionName())) + { + xSect = GetObject(*pFormat); + aRet <<= xSect; + break; + } + } + if(!xSect.is()) + throw NoSuchElementException(); + + return aRet; +} + +uno::Sequence< OUString > SwXTextSections::getElementNames() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + size_t nCount = GetDoc()->GetSections().size(); + SwSectionFormats& rSectFormats = GetDoc()->GetSections(); + for(size_t i = nCount; i; --i) + { + if( !rSectFormats[i - 1]->IsInNodesArr()) + nCount--; + } + + uno::Sequence<OUString> aSeq(nCount); + if(nCount) + { + SwSectionFormats& rFormats = GetDoc()->GetSections(); + OUString* pArray = aSeq.getArray(); + size_t nIndex = 0; + for( size_t i = 0; i < nCount; ++i, ++nIndex) + { + const SwSectionFormat* pFormat = rFormats[nIndex]; + while(!pFormat->IsInNodesArr()) + { + pFormat = rFormats[++nIndex]; + } + pArray[i] = pFormat->GetSection()->GetSectionName(); + } + } + return aSeq; +} + +sal_Bool SwXTextSections::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + bool bRet = false; + if(IsValid()) + { + SwSectionFormats& rFormats = GetDoc()->GetSections(); + for(size_t i = 0; i < rFormats.size(); ++i) + { + const SwSectionFormat* pFormat = rFormats[i]; + if (rName == pFormat->GetSection()->GetSectionName()) + { + bRet = true; + break; + } + } + } + else + { + // special handling for dbg_ methods + if( !rName.startsWith("dbg_")) + throw uno::RuntimeException(); + } + return bRet; +} + +uno::Type SAL_CALL SwXTextSections::getElementType() +{ + return cppu::UnoType<XTextSection>::get(); +} + +sal_Bool SwXTextSections::hasElements() +{ + SolarMutexGuard aGuard; + size_t nCount = 0; + if(!IsValid()) + throw uno::RuntimeException(); + + SwSectionFormats& rFormats = GetDoc()->GetSections(); + nCount = rFormats.size(); + + return nCount > 0; +} + +uno::Reference< XTextSection > SwXTextSections::GetObject( SwSectionFormat& rFormat ) +{ + return SwXTextSection::CreateXTextSection(&rFormat); +} + +OUString SwXBookmarks::getImplementationName() +{ + return "SwXBookmarks"; +} + +sal_Bool SwXBookmarks::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXBookmarks::getSupportedServiceNames() +{ + return { "com.sun.star.text.Bookmarks" }; +} + +SwXBookmarks::SwXBookmarks(SwDoc* _pDoc) : + SwUnoCollection(_pDoc) +{ } + +SwXBookmarks::~SwXBookmarks() +{ } + +sal_Int32 SwXBookmarks::getCount() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + + sal_Int32 count(0); + IDocumentMarkAccess* const pMarkAccess = GetDoc()->getIDocumentMarkAccess(); + for (IDocumentMarkAccess::const_iterator_t ppMark = + pMarkAccess->getBookmarksBegin(); + ppMark != pMarkAccess->getBookmarksEnd(); ++ppMark) + { + if (IDocumentMarkAccess::MarkType::BOOKMARK == + IDocumentMarkAccess::GetType(**ppMark)) + { + ++count; // only count real bookmarks + } + } + return count; +} + +uno::Any SwXBookmarks::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + IDocumentMarkAccess* const pMarkAccess = GetDoc()->getIDocumentMarkAccess(); + if(nIndex < 0 || nIndex >= pMarkAccess->getBookmarksCount()) + throw IndexOutOfBoundsException(); + + sal_Int32 count(0); + for (IDocumentMarkAccess::const_iterator_t ppMark = + pMarkAccess->getBookmarksBegin(); + ppMark != pMarkAccess->getBookmarksEnd(); ++ppMark) + { + if (IDocumentMarkAccess::MarkType::BOOKMARK == + IDocumentMarkAccess::GetType(**ppMark)) + { + if (count == nIndex) + { + uno::Any aRet; + const uno::Reference< text::XTextContent > xRef = + SwXBookmark::CreateXBookmark(*GetDoc(), *ppMark); + aRet <<= xRef; + return aRet; + } + ++count; // only count real bookmarks + } + } + throw IndexOutOfBoundsException(); +} + +uno::Any SwXBookmarks::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + + IDocumentMarkAccess* const pMarkAccess = GetDoc()->getIDocumentMarkAccess(); + IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->findBookmark(rName); + if(ppBkmk == pMarkAccess->getBookmarksEnd()) + throw NoSuchElementException(); + + uno::Any aRet; + const uno::Reference< text::XTextContent > xRef = + SwXBookmark::CreateXBookmark(*GetDoc(), *ppBkmk); + aRet <<= xRef; + return aRet; +} + +uno::Sequence< OUString > SwXBookmarks::getElementNames() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + + std::vector< OUString > ret; + IDocumentMarkAccess* const pMarkAccess = GetDoc()->getIDocumentMarkAccess(); + for (IDocumentMarkAccess::const_iterator_t ppMark = + pMarkAccess->getBookmarksBegin(); + ppMark != pMarkAccess->getBookmarksEnd(); ++ppMark) + { + if (IDocumentMarkAccess::MarkType::BOOKMARK == + IDocumentMarkAccess::GetType(**ppMark)) + { + ret.push_back((*ppMark)->GetName()); // only add real bookmarks + } + } + return comphelper::containerToSequence(ret); +} + +sal_Bool SwXBookmarks::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + + IDocumentMarkAccess* const pMarkAccess = GetDoc()->getIDocumentMarkAccess(); + return pMarkAccess->findBookmark(rName) != pMarkAccess->getBookmarksEnd(); +} + +uno::Type SAL_CALL SwXBookmarks::getElementType() +{ + return cppu::UnoType<XTextContent>::get(); +} + +sal_Bool SwXBookmarks::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + + IDocumentMarkAccess* const pMarkAccess = GetDoc()->getIDocumentMarkAccess(); + for (IDocumentMarkAccess::const_iterator_t ppMark = + pMarkAccess->getBookmarksBegin(); + ppMark != pMarkAccess->getBookmarksEnd(); ++ppMark) + { + if (IDocumentMarkAccess::MarkType::BOOKMARK == + IDocumentMarkAccess::GetType(**ppMark)) + { + return true; + } + } + return false; +} + +SwXNumberingRulesCollection::SwXNumberingRulesCollection( SwDoc* _pDoc ) : + SwUnoCollection(_pDoc) +{ +} + +SwXNumberingRulesCollection::~SwXNumberingRulesCollection() +{ +} + +sal_Int32 SwXNumberingRulesCollection::getCount() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return GetDoc()->GetNumRuleTable().size(); +} + +uno::Any SwXNumberingRulesCollection::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(!IsValid()) + throw uno::RuntimeException(); + + uno::Reference< XIndexReplace > xRef; + if ( o3tl::make_unsigned(nIndex) < GetDoc()->GetNumRuleTable().size() ) + { + xRef = new SwXNumberingRules( *GetDoc()->GetNumRuleTable()[ nIndex ], GetDoc()); + aRet <<= xRef; + } + + if(!xRef.is()) + throw IndexOutOfBoundsException(); + + return aRet; +} + +uno::Type SAL_CALL SwXNumberingRulesCollection::getElementType() +{ + return cppu::UnoType<XIndexReplace>::get(); +} + +sal_Bool SwXNumberingRulesCollection::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return !GetDoc()->GetNumRuleTable().empty(); +} + +OUString SwXFootnotes::getImplementationName() +{ + return "SwXFootnotes"; +} + +sal_Bool SwXFootnotes::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXFootnotes::getSupportedServiceNames() +{ + return { "com.sun.star.text.Footnotes" }; +} + +SwXFootnotes::SwXFootnotes(bool bEnd, SwDoc* _pDoc) + : SwUnoCollection(_pDoc) + , m_bEndnote(bEnd) +{ +} + +SwXFootnotes::~SwXFootnotes() +{ +} + +sal_Int32 SwXFootnotes::getCount() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + sal_Int32 nCount = 0; + const size_t nFootnoteCnt = GetDoc()->GetFootnoteIdxs().size(); + SwTextFootnote* pTextFootnote; + for( size_t n = 0; n < nFootnoteCnt; ++n ) + { + pTextFootnote = GetDoc()->GetFootnoteIdxs()[ n ]; + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() != m_bEndnote ) + continue; + nCount++; + } + return nCount; +} + +uno::Any SwXFootnotes::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + sal_Int32 nCount = 0; + if(!IsValid()) + throw uno::RuntimeException(); + + const size_t nFootnoteCnt = GetDoc()->GetFootnoteIdxs().size(); + SwTextFootnote* pTextFootnote; + uno::Reference< XFootnote > xRef; + for( size_t n = 0; n < nFootnoteCnt; ++n ) + { + pTextFootnote = GetDoc()->GetFootnoteIdxs()[ n ]; + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() != m_bEndnote ) + continue; + + if(nCount == nIndex) + { + xRef = SwXFootnote::CreateXFootnote(*GetDoc(), + &const_cast<SwFormatFootnote&>(rFootnote)); + aRet <<= xRef; + break; + } + nCount++; + } + if(!xRef.is()) + throw IndexOutOfBoundsException(); + + return aRet; +} + +uno::Type SAL_CALL SwXFootnotes::getElementType() +{ + return cppu::UnoType<XFootnote>::get(); +} + +sal_Bool SwXFootnotes::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return !GetDoc()->GetFootnoteIdxs().empty(); +} + +Reference<XFootnote> SwXFootnotes::GetObject( SwDoc& rDoc, const SwFormatFootnote& rFormat ) +{ + return SwXFootnote::CreateXFootnote(rDoc, &const_cast<SwFormatFootnote&>(rFormat)); +} + +OUString SwXReferenceMarks::getImplementationName() +{ + return "SwXReferenceMarks"; +} + +sal_Bool SwXReferenceMarks::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXReferenceMarks::getSupportedServiceNames() +{ + return { "com.sun.star.text.ReferenceMarks" }; +} + +SwXReferenceMarks::SwXReferenceMarks(SwDoc* _pDoc) : + SwUnoCollection(_pDoc) +{ +} + +SwXReferenceMarks::~SwXReferenceMarks() +{ +} + +sal_Int32 SwXReferenceMarks::getCount() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return GetDoc()->GetRefMarks(); +} + +uno::Any SwXReferenceMarks::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(!IsValid()) + throw uno::RuntimeException(); + uno::Reference< XTextContent > xRef; + if(0 <= nIndex && nIndex < SAL_MAX_UINT16) + { + SwFormatRefMark *const pMark = const_cast<SwFormatRefMark*>( + GetDoc()->GetRefMark(static_cast<sal_uInt16>(nIndex))); + if(pMark) + { + xRef = SwXReferenceMark::CreateXReferenceMark(*GetDoc(), pMark); + aRet <<= xRef; + } + } + if(!xRef.is()) + throw IndexOutOfBoundsException(); + return aRet; +} + +uno::Any SwXReferenceMarks::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(!IsValid()) + throw uno::RuntimeException(); + + SwFormatRefMark *const pMark = + const_cast<SwFormatRefMark*>(GetDoc()->GetRefMark(rName)); + if(!pMark) + throw NoSuchElementException(); + + uno::Reference<XTextContent> const xRef = + SwXReferenceMark::CreateXReferenceMark(*GetDoc(), pMark); + aRet <<= xRef; + + return aRet; +} + +uno::Sequence< OUString > SwXReferenceMarks::getElementNames() +{ + SolarMutexGuard aGuard; + uno::Sequence<OUString> aRet; + if(!IsValid()) + throw uno::RuntimeException(); + + std::vector<OUString> aStrings; + const sal_uInt16 nCount = GetDoc()->GetRefMarks( &aStrings ); + aRet.realloc(nCount); + OUString* pNames = aRet.getArray(); + for(sal_uInt16 i = 0; i < nCount; i++) + pNames[i] = aStrings[i]; + + return aRet; +} + +sal_Bool SwXReferenceMarks::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return nullptr != GetDoc()->GetRefMark( rName); +} + +uno::Type SAL_CALL SwXReferenceMarks::getElementType() +{ + return cppu::UnoType<XTextContent>::get(); +} + +sal_Bool SwXReferenceMarks::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return 0 != GetDoc()->GetRefMarks(); +} + +void SwUnoCollection::Invalidate() +{ + m_bObjectValid = false; + m_pDoc = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unocrsr.cxx b/sw/source/core/unocore/unocrsr.cxx new file mode 100644 index 000000000..76bdb939b --- /dev/null +++ b/sw/source/core/unocore/unocrsr.cxx @@ -0,0 +1,214 @@ +/* -*- 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 <unocrsr.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> + +sw::UnoCursorHint::~UnoCursorHint() {} + +SwUnoCursor::SwUnoCursor( const SwPosition &rPos ) + : SwCursor( rPos, nullptr ) + , m_bRemainInSection(true) + , m_bSkipOverHiddenSections(false) + , m_bSkipOverProtectSections(false) +{} + +SwUnoCursor::~SwUnoCursor() +{ + SwDoc* pDoc = GetDoc(); + if( !pDoc->IsInDtor() ) + { + assert(!m_aNotifier.HasListeners()); + } + + // delete the whole ring + while( GetNext() != this ) + { + Ring* pNxt = GetNextInRing(); + pNxt->MoveTo(nullptr); // remove from chain + delete pNxt; // and delete + } +} + +bool SwUnoCursor::IsReadOnlyAvailable() const +{ + return true; +} + +const SwContentFrame* +SwUnoCursor::DoSetBidiLevelLeftRight( bool &, bool, bool ) +{ + return nullptr; // not for uno cursor +} + +void SwUnoCursor::DoSetBidiLevelUpDown() +{ + // not for uno cursor +} + +bool SwUnoCursor::IsSelOvr( SwCursorSelOverFlags eFlags ) +{ + if (m_bRemainInSection) + { + SwDoc* pDoc = GetDoc(); + SwNodeIndex aOldIdx( *pDoc->GetNodes()[ GetSavePos()->nNode ] ); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + SwStartNode *pOldSttNd = aOldIdx.GetNode().StartOfSectionNode(), + *pNewSttNd = rPtIdx.GetNode().StartOfSectionNode(); + if( pOldSttNd != pNewSttNd ) + { + bool bMoveDown = GetSavePos()->nNode < rPtIdx.GetIndex(); + bool bValidPos = false; + + // search the correct surrounded start node - which the index + // can't leave. + while( pOldSttNd->IsSectionNode() ) + pOldSttNd = pOldSttNd->StartOfSectionNode(); + + // is the new index inside this surrounded section? + if( rPtIdx > *pOldSttNd && + rPtIdx < pOldSttNd->EndOfSectionIndex() ) + { + // check if it a valid move inside this section + // (only over SwSection's !) + const SwStartNode* pInvalidNode; + do { + pInvalidNode = nullptr; + pNewSttNd = rPtIdx.GetNode().StartOfSectionNode(); + + const SwStartNode *pSttNd = pNewSttNd, *pEndNd = pOldSttNd; + if( pSttNd->EndOfSectionIndex() > + pEndNd->EndOfSectionIndex() ) + { + pEndNd = pNewSttNd; + pSttNd = pOldSttNd; + } + + while( pSttNd->GetIndex() > pEndNd->GetIndex() ) + { + if( !pSttNd->IsSectionNode() ) + pInvalidNode = pSttNd; + pSttNd = pSttNd->StartOfSectionNode(); + } + if( pInvalidNode ) + { + if( bMoveDown ) + { + rPtIdx.Assign( *pInvalidNode->EndOfSectionNode(), 1 ); + + if( !rPtIdx.GetNode().IsContentNode() && + ( !pDoc->GetNodes().GoNextSection( &rPtIdx ) || + rPtIdx > pOldSttNd->EndOfSectionIndex() ) ) + break; + } + else + { + rPtIdx.Assign( *pInvalidNode, -1 ); + + if( !rPtIdx.GetNode().IsContentNode() && + ( !SwNodes::GoPrevSection( &rPtIdx ) || + rPtIdx < *pOldSttNd ) ) + break; + } + } + else + bValidPos = true; + } while ( pInvalidNode ); + } + + if( bValidPos ) + { + SwContentNode* pCNd = GetContentNode(); + GetPoint()->nContent.Assign( pCNd, (pCNd && !bMoveDown) ? pCNd->Len() : 0); + } + else + { + rPtIdx = GetSavePos()->nNode; + GetPoint()->nContent.Assign( GetContentNode(), GetSavePos()->nContent ); + return true; + } + } + } + return SwCursor::IsSelOvr( eFlags ); +} + +SwUnoTableCursor::SwUnoTableCursor(const SwPosition& rPos) + : SwCursor(rPos, nullptr) + , SwUnoCursor(rPos) + , SwTableCursor(rPos) + , m_aTableSel(rPos, nullptr) +{ + SetRemainInSection(false); +} + +SwUnoTableCursor::~SwUnoTableCursor() +{ + while (m_aTableSel.GetNext() != &m_aTableSel) + delete m_aTableSel.GetNext(); +} + +bool SwUnoTableCursor::IsSelOvr( SwCursorSelOverFlags eFlags ) +{ + bool bRet = SwUnoCursor::IsSelOvr( eFlags ); + if( !bRet ) + { + const SwTableNode* pTNd = GetPoint()->nNode.GetNode().FindTableNode(); + bRet = !(pTNd == GetDoc()->GetNodes()[ GetSavePos()->nNode ]-> + FindTableNode() && (!HasMark() || + pTNd == GetMark()->nNode.GetNode().FindTableNode() )); + } + return bRet; +} + +void SwUnoTableCursor::MakeBoxSels() +{ + const SwContentNode* pCNd; + bool bMakeTableCursors = true; + if( GetPoint()->nNode.GetIndex() && GetMark()->nNode.GetIndex() && + nullptr != ( pCNd = GetContentNode() ) && pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) && + nullptr != ( pCNd = GetContentNode(false) ) && pCNd->getLayoutFrame( pCNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) ) + bMakeTableCursors = GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()->MakeTableCursors( *this ); + + if ( !bMakeTableCursors ) + { + SwSelBoxes const& rTmpBoxes = GetSelectedBoxes(); + while (!rTmpBoxes.empty()) + { + DeleteBox(0); + } + } + + if( IsChgd() ) + { + SwTableCursor::MakeBoxSels( &m_aTableSel ); + if (!GetSelectedBoxesCount()) + { + const SwTableBox* pBox; + const SwNode* pBoxNd = GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + const SwTableNode* pTableNd = pBoxNd ? pBoxNd->FindTableNode() : nullptr; + if( pTableNd && nullptr != ( pBox = pTableNd->GetTable().GetTableBox( pBoxNd->GetIndex() )) ) + InsertBox( *pBox ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unocrsrhelper.cxx b/sw/source/core/unocore/unocrsrhelper.cxx new file mode 100644 index 000000000..a82e7a1c2 --- /dev/null +++ b/sw/source/core/unocore/unocrsrhelper.cxx @@ -0,0 +1,1507 @@ +/* -*- 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 <unocrsrhelper.hxx> + +#include <map> +#include <algorithm> +#include <memory> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyState.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/text/XTextSection.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include <svx/unoshape.hxx> + +#include <cmdid.h> +#include <unotextrange.hxx> +#include <unodraw.hxx> +#include <unofootnote.hxx> +#include <unobookmark.hxx> +#include <unomap.hxx> +#include <unorefmark.hxx> +#include <unoidx.hxx> +#include <unofield.hxx> +#include <unotbl.hxx> +#include <unosett.hxx> +#include <unoframe.hxx> +#include <unocrsr.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <fmtftn.hxx> +#include <charfmt.hxx> +#include <pagedesc.hxx> +#include <docstyle.hxx> +#include <ndtxt.hxx> +#include <docsh.hxx> +#include <section.hxx> +#include <shellio.hxx> +#include <edimp.hxx> +#include <swundo.hxx> +#include <cntfrm.hxx> +#include <pagefrm.hxx> +#include <svl/eitem.hxx> +#include <svl/lngmisc.hxx> +#include <docary.hxx> +#include <swtable.hxx> +#include <tox.hxx> +#include <doctxm.hxx> +#include <fchrfmt.hxx> +#include <editeng/editids.hrc> +#include <editeng/flstitem.hxx> +#include <vcl/metric.hxx> +#include <svtools/ctrltool.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/fcontnr.hxx> +#include <svl/stritem.hxx> +#include <SwStyleNameMapper.hxx> +#include <redline.hxx> +#include <numrule.hxx> +#include <comphelper/storagehelper.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <SwNodeNum.hxx> +#include <fmtmeta.hxx> +#include <txtfld.hxx> +#include <unoparagraph.hxx> +#include <poolfmt.hxx> +#include <paratr.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; + +namespace SwUnoCursorHelper +{ + +static SwPaM* lcl_createPamCopy(const SwPaM& rPam) +{ + SwPaM *const pRet = new SwPaM(*rPam.GetPoint()); + ::sw::DeepCopyPaM(rPam, *pRet); + return pRet; +} + +void GetSelectableFromAny(uno::Reference<uno::XInterface> const& xIfc, + SwDoc & rTargetDoc, + SwPaM *& o_rpPaM, std::pair<OUString, FlyCntType> & o_rFrame, + OUString & o_rTableName, SwUnoTableCursor const*& o_rpTableCursor, + ::sw::mark::IMark const*& o_rpMark, + std::vector<SdrObject *> & o_rSdrObjects) +{ + uno::Reference<drawing::XShapes> const xShapes(xIfc, UNO_QUERY); + if (xShapes.is()) + { + sal_Int32 nShapes(xShapes->getCount()); + for (sal_Int32 i = 0; i < nShapes; ++i) + { + uno::Reference<lang::XUnoTunnel> xShape; + xShapes->getByIndex(i) >>= xShape; + if (xShape.is()) + { + SvxShape *const pSvxShape( + ::sw::UnoTunnelGetImplementation<SvxShape>(xShape)); + if (pSvxShape) + { + SdrObject *const pSdrObject = pSvxShape->GetSdrObject(); + if (pSdrObject) + { // hmm... needs view to verify it's in right doc... + o_rSdrObjects.push_back(pSdrObject); + } + } + } + } + return; + } + + uno::Reference<lang::XUnoTunnel> const xTunnel(xIfc, UNO_QUERY); + if (!xTunnel.is()) // everything below needs tunnel + { + return; + } + + SwXShape *const pShape(::sw::UnoTunnelGetImplementation<SwXShape>(xTunnel)); + if (pShape) + { + uno::Reference<uno::XAggregation> const xAgg( + pShape->GetAggregationInterface()); + if (xAgg.is()) + { + SvxShape *const pSvxShape( + ::sw::UnoTunnelGetImplementation<SvxShape>(xTunnel)); + if (pSvxShape) + { + SdrObject *const pSdrObject = pSvxShape->GetSdrObject(); + if (pSdrObject) + { // hmm... needs view to verify it's in right doc... + o_rSdrObjects.push_back(pSdrObject); + } + } + } + return; + } + + OTextCursorHelper *const pCursor( + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xTunnel)); + if (pCursor) + { + if (pCursor->GetDoc() == &rTargetDoc) + { + o_rpPaM = lcl_createPamCopy(*pCursor->GetPaM()); + } + return; + } + + SwXTextRanges* const pRanges( + ::sw::UnoTunnelGetImplementation<SwXTextRanges>(xTunnel)); + if (pRanges) + { + SwUnoCursor const* pUnoCursor = pRanges->GetCursor(); + if (pUnoCursor && pUnoCursor->GetDoc() == &rTargetDoc) + { + o_rpPaM = lcl_createPamCopy(*pUnoCursor); + } + return; + } + + // check these before Range to prevent misinterpretation of text frames + // and cells also implement XTextRange + SwXFrame *const pFrame( + ::sw::UnoTunnelGetImplementation<SwXFrame>(xTunnel)); + if (pFrame) + { + const SwFrameFormat *const pFrameFormat(pFrame->GetFrameFormat()); + if (pFrameFormat && pFrameFormat->GetDoc() == &rTargetDoc) + { + o_rFrame = std::make_pair(pFrameFormat->GetName(), pFrame->GetFlyCntType()); + } + return; + } + + SwXTextTable *const pTextTable( + ::sw::UnoTunnelGetImplementation<SwXTextTable>(xTunnel)); + if (pTextTable) + { + SwFrameFormat *const pFrameFormat(pTextTable->GetFrameFormat()); + if (pFrameFormat && pFrameFormat->GetDoc() == &rTargetDoc) + { + o_rTableName = pFrameFormat->GetName(); + } + return; + } + + SwXCell *const pCell( + ::sw::UnoTunnelGetImplementation<SwXCell>(xTunnel)); + if (pCell) + { + SwFrameFormat *const pFrameFormat(pCell->GetFrameFormat()); + if (pFrameFormat && pFrameFormat->GetDoc() == &rTargetDoc) + { + SwTableBox * pBox = pCell->GetTableBox(); + SwTable *const pTable = SwTable::FindTable(pFrameFormat); + // ??? what's the benefit of setting pBox in this convoluted way? + pBox = pCell->FindBox(pTable, pBox); + if (pBox) + { + SwPosition const aPos(*pBox->GetSttNd()); + SwPaM aPam(aPos); + aPam.Move(fnMoveForward, GoInNode); + o_rpPaM = lcl_createPamCopy(aPam); + } + } + return; + } + + uno::Reference<text::XTextRange> const xTextRange(xTunnel, UNO_QUERY); + if (xTextRange.is()) + { + SwUnoInternalPaM aPam(rTargetDoc); + if (::sw::XTextRangeToSwPaM(aPam, xTextRange)) + { + o_rpPaM = lcl_createPamCopy(aPam); + } + return; + } + + SwXCellRange *const pCellRange( + ::sw::UnoTunnelGetImplementation<SwXCellRange>(xTunnel)); + if (pCellRange) + { + SwUnoCursor const*const pUnoCursor(pCellRange->GetTableCursor()); + if (pUnoCursor && pUnoCursor->GetDoc() == &rTargetDoc) + { + // probably can't copy it to o_rpPaM for this since it's + // a SwTableCursor + o_rpTableCursor = dynamic_cast<SwUnoTableCursor const*>(pUnoCursor); + } + return; + } + + ::sw::mark::IMark const*const pMark( + SwXBookmark::GetBookmarkInDoc(& rTargetDoc, xTunnel)); + if (pMark) + { + o_rpMark = pMark; + return; + } +} + +uno::Reference<text::XTextContent> +GetNestedTextContent(SwTextNode const & rTextNode, sal_Int32 const nIndex, + bool const bParent) +{ + // these should be unambiguous because of the dummy character + SwTextNode::GetTextAttrMode const eMode( bParent + ? SwTextNode::PARENT : SwTextNode::EXPAND ); + SwTextAttr *const pMetaTextAttr = + rTextNode.GetTextAttrAt(nIndex, RES_TXTATR_META, eMode); + SwTextAttr *const pMetaFieldTextAttr = + rTextNode.GetTextAttrAt(nIndex, RES_TXTATR_METAFIELD, eMode); + // which is innermost? + SwTextAttr *const pTextAttr = pMetaTextAttr + ? (pMetaFieldTextAttr + ? ((pMetaFieldTextAttr->GetStart() > + pMetaTextAttr->GetStart()) + ? pMetaFieldTextAttr : pMetaTextAttr) + : pMetaTextAttr) + : pMetaFieldTextAttr; + uno::Reference<XTextContent> xRet; + if (pTextAttr) + { + ::sw::Meta *const pMeta( + static_cast<SwFormatMeta &>(pTextAttr->GetAttr()).GetMeta()); + assert(pMeta); + xRet.set(pMeta->MakeUnoObject(), uno::UNO_QUERY); + } + return xRet; +} + +static uno::Any GetParaListAutoFormat(SwTextNode const& rNode) +{ + SwFormatAutoFormat const*const pFormat( + rNode.GetSwAttrSet().GetItem<SwFormatAutoFormat>(RES_PARATR_LIST_AUTOFMT, false)); + if (!pFormat) + { + return uno::Any(); + } + SfxItemSet const& rSet(*pFormat->GetStyleHandle()); + SfxItemPropertySet const& rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE)); + SfxItemPropertyMap const& rMap(rPropSet.getPropertyMap()); + std::vector<beans::NamedValue> props; + // have to iterate the map, not the item set? + for (auto const& rEntry : rMap.getPropertyEntries()) + { + if (rPropSet.getPropertyState(rEntry, rSet) == PropertyState_DIRECT_VALUE) + { + Any value; + rPropSet.getPropertyValue(rEntry, rSet, value); + props.emplace_back(rEntry.sName, value); + } + } + return uno::makeAny(comphelper::containerToSequence(props)); +} + +// Read the special properties of the cursor +bool getCursorPropertyValue(const SfxItemPropertySimpleEntry& rEntry + , SwPaM& rPam + , Any *pAny + , PropertyState& eState + , const SwTextNode* pNode ) +{ + PropertyState eNewState = PropertyState_DIRECT_VALUE; + bool bDone = true; + switch(rEntry.nWID) + { + case FN_UNO_PARA_CONT_PREV_SUBTREE: + if (pAny) + { + const SwTextNode * pTmpNode = pNode; + + if (!pTmpNode) + pTmpNode = rPam.GetNode().GetTextNode(); + + bool bRet = false; + + if ( pTmpNode && + pTmpNode->GetNum() && + pTmpNode->GetNum()->IsContinueingPreviousSubTree() ) + { + bRet = true; + } + + *pAny <<= bRet; + } + break; + case FN_UNO_PARA_NUM_STRING: + if (pAny) + { + const SwTextNode * pTmpNode = pNode; + + if (!pTmpNode) + pTmpNode = rPam.GetNode().GetTextNode(); + + OUString sRet; + if ( pTmpNode && pTmpNode->GetNum() ) + { + sRet = pTmpNode->GetNumString(); + } + + *pAny <<= sRet; + } + break; + case RES_PARATR_OUTLINELEVEL: + if (pAny) + { + const SwTextNode * pTmpNode = pNode; + + if (!pTmpNode) + pTmpNode = rPam.GetNode().GetTextNode(); + + sal_Int16 nRet = -1; + if ( pTmpNode ) + nRet = sal::static_int_cast< sal_Int16 >( pTmpNode->GetAttrOutlineLevel() ); + + *pAny <<= nRet; + } + break; + case FN_UNO_PARA_CONDITIONAL_STYLE_NAME: + case FN_UNO_PARA_STYLE : + { + SwFormatColl* pFormat = nullptr; + if(pNode) + pFormat = FN_UNO_PARA_CONDITIONAL_STYLE_NAME == rEntry.nWID + ? pNode->GetFormatColl() : &pNode->GetAnyFormatColl(); + else + { + pFormat = SwUnoCursorHelper::GetCurTextFormatColl(rPam, + FN_UNO_PARA_CONDITIONAL_STYLE_NAME == rEntry.nWID); + } + if(pFormat) + { + if( pAny ) + { + OUString sVal; + SwStyleNameMapper::FillProgName(pFormat->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + *pAny <<= sVal; + } + } + else + eNewState = PropertyState_AMBIGUOUS_VALUE; + } + break; + case FN_UNO_PAGE_STYLE : + { + OUString sVal; + GetCurPageStyle(rPam, sVal); + if( pAny ) + *pAny <<= sVal; + if(sVal.isEmpty()) + eNewState = PropertyState_AMBIGUOUS_VALUE; + } + break; + case FN_UNO_NUM_START_VALUE : + if( pAny ) + { + sal_Int16 nValue = IsNodeNumStart(rPam, eNewState); + *pAny <<= nValue; + } + break; + case FN_UNO_NUM_LEVEL : + case FN_UNO_IS_NUMBER : + // #i91601# + case FN_UNO_LIST_ID: + case FN_NUMBER_NEWSTART: + case FN_UNO_PARA_NUM_AUTO_FORMAT: + { + if (!pAny) + { + break; + } + // a multi selection is not considered + const SwTextNode* pTextNd = rPam.GetNode().GetTextNode(); + if ( pTextNd && pTextNd->IsInList() ) + { + switch (rEntry.nWID) + { + case FN_UNO_NUM_LEVEL: + { + *pAny <<= static_cast<sal_Int16>(pTextNd->GetActualListLevel()); + break; + } + case FN_UNO_IS_NUMBER: + { + *pAny <<= pTextNd->IsCountedInList(); + break; + } + // #i91601# + case FN_UNO_LIST_ID: + { + *pAny <<= pTextNd->GetListId(); + break; + } + case FN_NUMBER_NEWSTART: + { + *pAny <<= pTextNd->IsListRestart(); + break; + } + case FN_UNO_PARA_NUM_AUTO_FORMAT: + { + *pAny = GetParaListAutoFormat(*pTextNd); + break; + } + default: + assert(false); + } + } + else + { + eNewState = PropertyState_DEFAULT_VALUE; + + // #i30838# set default values for default properties + switch (rEntry.nWID) + { + case FN_UNO_NUM_LEVEL: + { + *pAny <<= static_cast<sal_Int16>( 0 ); + break; + } + case FN_UNO_IS_NUMBER: + { + *pAny <<= false; + break; + } + // #i91601# + case FN_UNO_LIST_ID: + { + *pAny <<= OUString(); + break; + } + case FN_NUMBER_NEWSTART: + { + *pAny <<= false; + break; + } + case FN_UNO_PARA_NUM_AUTO_FORMAT: + { + break; // void + } + default: + assert(false); + } + } + //PROPERTY_MAYBEVOID! + } + break; + case FN_UNO_NUM_RULES : + if( pAny ) + getNumberingProperty(rPam, eNewState, pAny); + else + { + if( !SwDoc::GetNumRuleAtPos( *rPam.GetPoint() ) ) + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_DOCUMENT_INDEX_MARK: + { + std::vector<SwTextAttr *> marks; + if (rPam.GetNode().IsTextNode()) + { + marks = rPam.GetNode().GetTextNode()->GetTextAttrsAt( + rPam.GetPoint()->nContent.GetIndex(), RES_TXTATR_TOXMARK); + } + if (!marks.empty()) + { + if( pAny ) + { // hmm... can only return 1 here + SwTOXMark & rMark = + static_cast<SwTOXMark &>((*marks.begin())->GetAttr()); + const uno::Reference< text::XDocumentIndexMark > xRef = + SwXDocumentIndexMark::CreateXDocumentIndexMark( + *rPam.GetDoc(), &rMark); + (*pAny) <<= xRef; + } + } + else + //also here - indistinguishable + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_DOCUMENT_INDEX: + { + SwTOXBase* pBase = SwDoc::GetCurTOX( + *rPam.Start() ); + if( pBase ) + { + if( pAny ) + { + const uno::Reference< text::XDocumentIndex > xRef = + SwXDocumentIndex::CreateXDocumentIndex(*rPam.GetDoc(), + static_cast<SwTOXBaseSection *>(pBase)); + (*pAny) <<= xRef; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_TEXT_FIELD: + { + const SwPosition *pPos = rPam.Start(); + const SwTextNode *pTextNd = + rPam.GetDoc()->GetNodes()[pPos->nNode.GetIndex()]->GetTextNode(); + const SwTextAttr* pTextAttr = pTextNd + ? pTextNd->GetFieldTextAttrAt( pPos->nContent.GetIndex(), true ) + : nullptr; + if ( pTextAttr != nullptr ) + { + if( pAny ) + { + uno::Reference<text::XTextField> const xField( + SwXTextField::CreateXTextField(rPam.GetDoc(), + &pTextAttr->GetFormatField())); + *pAny <<= xField; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_TEXT_TABLE: + case FN_UNO_CELL: + { + SwStartNode* pSttNode = rPam.GetNode().StartOfSectionNode(); + SwStartNodeType eType = pSttNode->GetStartNodeType(); + if(SwTableBoxStartNode == eType) + { + if( pAny ) + { + const SwTableNode* pTableNode = pSttNode->FindTableNode(); + SwFrameFormat* pTableFormat = pTableNode->GetTable().GetFrameFormat(); + //SwTable& rTable = static_cast<SwTableNode*>(pSttNode)->GetTable(); + if(FN_UNO_TEXT_TABLE == rEntry.nWID) + { + uno::Reference< XTextTable > xTable = SwXTextTables::GetObject(*pTableFormat); + *pAny <<= xTable; + } + else + { + SwTableBox* pBox = pSttNode->GetTableBox(); + uno::Reference< XCell > xCell = SwXCell::CreateXCell(pTableFormat, pBox); + *pAny <<= xCell; + } + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_TEXT_FRAME: + { + SwStartNode* pSttNode = rPam.GetNode().StartOfSectionNode(); + SwStartNodeType eType = pSttNode->GetStartNodeType(); + + SwFrameFormat* pFormat; + if(eType == SwFlyStartNode && nullptr != (pFormat = pSttNode->GetFlyFormat())) + { + if( pAny ) + { + uno::Reference<XTextFrame> const xFrame( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat)); + (*pAny) <<= xFrame; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_TEXT_SECTION: + { + SwSection* pSect = SwDoc::GetCurrSection(*rPam.GetPoint()); + if(pSect) + { + if( pAny ) + { + uno::Reference< XTextSection > xSect = SwXTextSections::GetObject( *pSect->GetFormat() ); + *pAny <<= xSect; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_TEXT_PARAGRAPH: + { + SwTextNode* pTextNode = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + if (pTextNode) + { + if (pAny) + { + uno::Reference<text::XTextContent> xParagraph = SwXParagraph::CreateXParagraph(*pTextNode->GetDoc(), pTextNode); + *pAny <<= xParagraph; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_ENDNOTE: + case FN_UNO_FOOTNOTE: + { + SwTextAttr *const pTextAttr = rPam.GetNode().IsTextNode() ? + rPam.GetNode().GetTextNode()->GetTextAttrForCharAt( + rPam.GetPoint()->nContent.GetIndex(), RES_TXTATR_FTN) : nullptr; + if(pTextAttr) + { + const SwFormatFootnote& rFootnote = pTextAttr->GetFootnote(); + if(rFootnote.IsEndNote() == (FN_UNO_ENDNOTE == rEntry.nWID)) + { + if( pAny ) + { + const uno::Reference< text::XFootnote > xFootnote = + SwXFootnote::CreateXFootnote(*rPam.GetDoc(), + &const_cast<SwFormatFootnote&>(rFootnote)); + *pAny <<= xFootnote; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_REFERENCE_MARK: + { + std::vector<SwTextAttr *> marks; + if (rPam.GetNode().IsTextNode()) + { + marks = rPam.GetNode().GetTextNode()->GetTextAttrsAt( + rPam.GetPoint()->nContent.GetIndex(), RES_TXTATR_REFMARK); + } + if (!marks.empty()) + { + if( pAny ) + { // hmm... can only return 1 here + const SwFormatRefMark& rRef = (*marks.begin())->GetRefMark(); + uno::Reference<XTextContent> const xRef = + SwXReferenceMark::CreateXReferenceMark(*rPam.GetDoc(), + const_cast<SwFormatRefMark*>(&rRef)); + *pAny <<= xRef; + } + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case FN_UNO_NESTED_TEXT_CONTENT: + { + uno::Reference<XTextContent> const xRet(rPam.GetNode().IsTextNode() + ? GetNestedTextContent(*rPam.GetNode().GetTextNode(), + rPam.GetPoint()->nContent.GetIndex(), false) + : nullptr); + if (xRet.is()) + { + if (pAny) + { + (*pAny) <<= xRet; + } + } + else + { + eNewState = PropertyState_DEFAULT_VALUE; + } + } + break; + case FN_UNO_CHARFMT_SEQUENCE: + { + + SwTextNode *const pTextNode = rPam.GetNode().GetTextNode(); + if (&rPam.GetNode() == &rPam.GetNode(false) + && pTextNode && pTextNode->GetpSwpHints()) + { + sal_Int32 nPaMStart = rPam.GetPoint()->nContent.GetIndex(); + sal_Int32 nPaMEnd = rPam.GetMark() ? rPam.GetMark()->nContent.GetIndex() : nPaMStart; + if(nPaMStart > nPaMEnd) + { + std::swap(nPaMStart, nPaMEnd); + } + Sequence< OUString> aCharStyles; + SwpHints* pHints = pTextNode->GetpSwpHints(); + for( size_t nAttr = 0; nAttr < pHints->Count(); ++nAttr ) + { + SwTextAttr* pAttr = pHints->Get( nAttr ); + if(pAttr->Which() != RES_TXTATR_CHARFMT) + continue; + const sal_Int32 nAttrStart = pAttr->GetStart(); + const sal_Int32 nAttrEnd = *pAttr->GetEnd(); + //check if the attribute touches the selection + if( ( nAttrEnd > nPaMStart && nAttrStart < nPaMEnd ) || + ( !nAttrStart && !nAttrEnd && !nPaMStart && !nPaMEnd ) ) + { + //check for overlapping + if(nAttrStart > nPaMStart || + nAttrEnd < nPaMEnd) + { + aCharStyles.realloc(0); + break; + } + else + { + //now the attribute should start before or at the selection + //and it should end at the end of the selection or behind + OSL_ENSURE(nAttrStart <= nPaMStart && nAttrEnd >=nPaMEnd, + "attribute overlaps or is outside"); + //now the name of the style has to be added to the sequence + aCharStyles.realloc(aCharStyles.getLength() + 1); + OSL_ENSURE(pAttr->GetCharFormat().GetCharFormat(), "no character format set"); + aCharStyles.getArray()[aCharStyles.getLength() - 1] = + SwStyleNameMapper::GetProgName( + pAttr->GetCharFormat().GetCharFormat()->GetName(), SwGetPoolIdFromName::ChrFmt); + } + } + + } + eNewState = + aCharStyles.hasElements() ? + PropertyState_DIRECT_VALUE : PropertyState_DEFAULT_VALUE; + if(pAny) + (*pAny) <<= aCharStyles; + } + else + eNewState = PropertyState_DEFAULT_VALUE; + } + break; + case RES_TXTATR_CHARFMT: + // no break here! + default: bDone = false; + } + if( bDone ) + eState = eNewState; + return bDone; +}; + +sal_Int16 IsNodeNumStart(SwPaM const & rPam, PropertyState& eState) +{ + const SwTextNode* pTextNd = rPam.GetNode().GetTextNode(); + // correction: check, if restart value is set at the text node and use + // new method <SwTextNode::GetAttrListRestartValue()> to retrieve the value + if ( pTextNd && pTextNd->GetNumRule() && pTextNd->IsListRestart() && + pTextNd->HasAttrListRestartValue() ) + { + eState = PropertyState_DIRECT_VALUE; + sal_Int16 nTmp = sal::static_int_cast< sal_Int16 >(pTextNd->GetAttrListRestartValue()); + return nTmp; + } + eState = PropertyState_DEFAULT_VALUE; + return -1; +} + +void setNumberingProperty(const Any& rValue, SwPaM& rPam) +{ + uno::Reference<XIndexReplace> xIndexReplace; + if(rValue >>= xIndexReplace) + { + auto pSwNum = comphelper::getUnoTunnelImplementation<SwXNumberingRules>(xIndexReplace); + if(pSwNum) + { + SwDoc* pDoc = rPam.GetDoc(); + if(pSwNum->GetNumRule()) + { + SwNumRule aRule(*pSwNum->GetNumRule()); + const OUString* pNewCharStyles = pSwNum->GetNewCharStyleNames(); + const OUString* pBulletFontNames = pSwNum->GetBulletFontNames(); + for(sal_uInt16 i = 0; i < MAXLEVEL; i++) + { + SwNumFormat aFormat(aRule.Get( i )); + if (!pNewCharStyles[i].isEmpty() && + !SwXNumberingRules::isInvalidStyle(pNewCharStyles[i]) && + (!aFormat.GetCharFormat() || pNewCharStyles[i] != aFormat.GetCharFormat()->GetName())) + { + if (pNewCharStyles[i].isEmpty()) + { + // FIXME + // Is something missing/wrong here? + // if condition is always false due to outer check! + aFormat.SetCharFormat(nullptr); + } + else + { + + // get CharStyle and set the rule + const size_t nChCount = pDoc->GetCharFormats()->size(); + SwCharFormat* pCharFormat = nullptr; + for(size_t nCharFormat = 0; nCharFormat < nChCount; ++nCharFormat) + { + SwCharFormat& rChFormat = *((*(pDoc->GetCharFormats()))[nCharFormat]); + if(rChFormat.GetName() == pNewCharStyles[i]) + { + pCharFormat = &rChFormat; + break; + } + } + + if(!pCharFormat) + { + SfxStyleSheetBasePool* pPool = pDoc->GetDocShell()->GetStyleSheetPool(); + SfxStyleSheetBase* pBase; + pBase = pPool->Find(pNewCharStyles[i], SfxStyleFamily::Char); + // shall it really be created? + if(!pBase) + pBase = &pPool->Make(pNewCharStyles[i], SfxStyleFamily::Page); + pCharFormat = static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat(); + } + if(pCharFormat) + aFormat.SetCharFormat(pCharFormat); + } + } + //Now again for fonts + if( + !pBulletFontNames[i].isEmpty() && + !SwXNumberingRules::isInvalidStyle(pBulletFontNames[i]) && + (!aFormat.GetBulletFont() || aFormat.GetBulletFont()->GetFamilyName() != pBulletFontNames[i]) + ) + { + const SvxFontListItem* pFontListItem = + static_cast<const SvxFontListItem* >(pDoc->GetDocShell() + ->GetItem( SID_ATTR_CHAR_FONTLIST )); + const FontList* pList = pFontListItem->GetFontList(); + + FontMetric aFontMetric = pList->Get( + pBulletFontNames[i],WEIGHT_NORMAL, ITALIC_NONE); + vcl::Font aFont(aFontMetric); + aFormat.SetBulletFont(&aFont); + } + aRule.Set( i, aFormat ); + } + UnoActionContext aAction(pDoc); + + if( rPam.GetNext() != &rPam ) // Multiple selection? + { + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( rPam ); + SwPaM aPam( *rPam.GetPoint() ); + for ( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + // no start of a new list + pDoc->SetNumRule( aRangeArr.SetPam( n, aPam ), aRule, false ); + } + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + { + // no start of a new list + pDoc->SetNumRule( rPam, aRule, false ); + } + + } + else if(!pSwNum->GetCreatedNumRuleName().isEmpty()) + { + UnoActionContext aAction( pDoc ); + SwNumRule* pRule = pDoc->FindNumRulePtr( pSwNum->GetCreatedNumRuleName() ); + if ( !pRule ) + throw RuntimeException(); + // no start of a new list + pDoc->SetNumRule( rPam, *pRule, false ); + } + else + { + // #i103817# + // outline numbering + UnoActionContext aAction(pDoc); + SwNumRule* pRule = pDoc->GetOutlineNumRule(); + if(!pRule) + throw RuntimeException(); + pDoc->SetNumRule( rPam, *pRule, false ); + } + } + } + else if ( rValue.getValueType() == cppu::UnoType<void>::get() ) + { + rPam.GetDoc()->DelNumRules(rPam); + } +} + +void getNumberingProperty(SwPaM& rPam, PropertyState& eState, Any * pAny ) +{ + const SwNumRule* pNumRule = SwDoc::GetNumRuleAtPos( *rPam.GetPoint() ); + if(pNumRule) + { + uno::Reference< XIndexReplace > xNum = new SwXNumberingRules(*pNumRule); + if ( pAny ) + *pAny <<= xNum; + eState = PropertyState_DIRECT_VALUE; + } + else + eState = PropertyState_DEFAULT_VALUE; +} + +void GetCurPageStyle(SwPaM const & rPaM, OUString &rString) +{ + if (!rPaM.GetContentNode()) + return; // TODO: is there an easy way to get it for tables/sections? + SwRootFrame* pLayout = rPaM.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(); + // Consider the position inside the content node, since the node may span over multiple pages + // with different page styles. + SwContentFrame* pFrame = rPaM.GetContentNode()->getLayoutFrame(pLayout, rPaM.GetPoint()); + if(pFrame) + { + const SwPageFrame* pPage = pFrame->FindPageFrame(); + if(pPage) + { + SwStyleNameMapper::FillProgName(pPage->GetPageDesc()->GetName(), + rString, SwGetPoolIdFromName::PageDesc); + } + } +} + +// reset special properties of the cursor +void resetCursorPropertyValue(const SfxItemPropertySimpleEntry& rEntry, SwPaM& rPam) +{ + SwDoc* pDoc = rPam.GetDoc(); + switch(rEntry.nWID) + { + case FN_UNO_PARA_STYLE : + break; + case FN_UNO_PAGE_STYLE : + break; + case FN_UNO_NUM_START_VALUE : + { + UnoActionContext aAction(pDoc); + + if( rPam.GetNext() != &rPam ) // Multiple selection? + { + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( rPam ); + SwPaM aPam( *rPam.GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + pDoc->SetNodeNumStart( *aRangeArr.SetPam( n, aPam ).GetPoint(), 1 ); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + pDoc->SetNodeNumStart( *rPam.GetPoint(), 0 ); + } + + break; + case FN_UNO_NUM_LEVEL : + break; + case FN_UNO_NUM_RULES: + break; + case FN_UNO_CHARFMT_SEQUENCE: + { + std::set<sal_uInt16> aWhichIds; + aWhichIds.insert( RES_TXTATR_CHARFMT); + pDoc->ResetAttrs(rPam, true, aWhichIds); + } + break; + } +} + +void InsertFile(SwUnoCursor* pUnoCursor, const OUString& rURL, + const uno::Sequence< beans::PropertyValue >& rOptions) +{ + std::unique_ptr<SfxMedium> pMed; + SwDoc* pDoc = pUnoCursor->GetDoc(); + SwDocShell* pDocSh = pDoc->GetDocShell(); + utl::MediaDescriptor aMediaDescriptor( rOptions ); + OUString sFileName = rURL; + OUString sFilterName, sFilterOptions, sPassword, sBaseURL; + uno::Reference < io::XStream > xStream; + uno::Reference < io::XInputStream > xInputStream; + + if( sFileName.isEmpty() ) + aMediaDescriptor[utl::MediaDescriptor::PROP_URL()] >>= sFileName; + if( sFileName.isEmpty() ) + aMediaDescriptor[utl::MediaDescriptor::PROP_FILENAME()] >>= sFileName; + aMediaDescriptor[utl::MediaDescriptor::PROP_INPUTSTREAM()] >>= xInputStream; + aMediaDescriptor[utl::MediaDescriptor::PROP_STREAM()] >>= xStream; + aMediaDescriptor[utl::MediaDescriptor::PROP_INPUTSTREAM()] >>= xInputStream; + aMediaDescriptor[utl::MediaDescriptor::PROP_FILTERNAME()] >>= sFilterName; + aMediaDescriptor[utl::MediaDescriptor::PROP_FILTEROPTIONS()] >>= sFilterOptions; + aMediaDescriptor[utl::MediaDescriptor::PROP_PASSWORD()] >>= sPassword; + aMediaDescriptor[utl::MediaDescriptor::PROP_DOCUMENTBASEURL() ] >>= sBaseURL; + if ( !xInputStream.is() && xStream.is() ) + xInputStream = xStream->getInputStream(); + + if(!pDocSh || (sFileName.isEmpty() && !xInputStream.is())) + return; + + SfxObjectFactory& rFact = pDocSh->GetFactory(); + std::shared_ptr<const SfxFilter> pFilter = rFact.GetFilterContainer()->GetFilter4FilterName( sFilterName ); + uno::Reference < embed::XStorage > xReadStorage; + if( xInputStream.is() ) + { + uno::Sequence< uno::Any > aArgs( 2 ); + aArgs[0] <<= xInputStream; + aArgs[1] <<= embed::ElementModes::READ; + try + { + xReadStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ), + uno::UNO_QUERY ); + } + catch( const io::IOException&) {} + } + if ( !pFilter ) + { + if( xInputStream.is() && !xReadStorage.is()) + { + pMed.reset(new SfxMedium); + pMed->setStreamToLoadFrom(xInputStream, true ); + } + else + pMed.reset(xReadStorage.is() ? + new SfxMedium(xReadStorage, sBaseURL ) : + new SfxMedium(sFileName, StreamMode::READ )); + if( !sBaseURL.isEmpty() ) + pMed->GetItemSet()->Put( SfxStringItem( SID_DOC_BASEURL, sBaseURL ) ); + + SfxFilterMatcher aMatcher( rFact.GetFilterContainer()->GetName() ); + ErrCode nErr = aMatcher.GuessFilter(*pMed, pFilter, SfxFilterFlags::NONE); + if ( nErr || !pFilter) + return; + pMed->SetFilter( pFilter ); + } + else + { + if( xInputStream.is() && !xReadStorage.is()) + { + pMed.reset(new SfxMedium); + pMed->setStreamToLoadFrom(xInputStream, true ); + pMed->SetFilter( pFilter ); + } + else + { + if( xReadStorage.is() ) + { + pMed.reset(new SfxMedium(xReadStorage, sBaseURL )); + pMed->SetFilter( pFilter ); + } + else + pMed.reset(new SfxMedium(sFileName, StreamMode::READ, pFilter, nullptr)); + } + if(!sFilterOptions.isEmpty()) + pMed->GetItemSet()->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, sFilterOptions ) ); + if(!sBaseURL.isEmpty()) + pMed->GetItemSet()->Put( SfxStringItem( SID_DOC_BASEURL, sBaseURL ) ); + } + + // this sourcecode is not responsible for the lifetime of the shell, SfxObjectShellLock should not be used + SfxObjectShellRef aRef( pDocSh ); + + pMed->Download(); // if necessary: start the download + if( aRef.is() && 1 < aRef->GetRefCount() ) // Ref still valid? + { + SwReaderPtr pRdr; + SfxItemSet* pSet = pMed->GetItemSet(); + pSet->Put(SfxBoolItem(FN_API_CALL, true)); + if(!sPassword.isEmpty()) + pSet->Put(SfxStringItem(SID_PASSWORD, sPassword)); + Reader *pRead = pDocSh->StartConvertFrom( *pMed, pRdr, nullptr, pUnoCursor); + if( pRead ) + { + + UnoActionContext aContext(pDoc); + + if(pUnoCursor->HasMark()) + pDoc->getIDocumentContentOperations().DeleteAndJoin(*pUnoCursor); + + SwNodeIndex aSave( pUnoCursor->GetPoint()->nNode, -1 ); + sal_Int32 nContent = pUnoCursor->GetPoint()->nContent.GetIndex(); + + ErrCode nErrno = pRdr->Read( *pRead ); // and paste the document + + if(!nErrno) + { + ++aSave; + pUnoCursor->SetMark(); + pUnoCursor->GetMark()->nNode = aSave; + + SwContentNode* pCntNode = aSave.GetNode().GetContentNode(); + if( !pCntNode ) + nContent = 0; + pUnoCursor->GetMark()->nContent.Assign( pCntNode, nContent ); + } + } + } +} + +// insert text and scan for CR characters in order to insert +// paragraph breaks at those positions by calling SplitNode +bool DocInsertStringSplitCR( + SwDoc &rDoc, + const SwPaM &rNewCursor, + const OUString &rText, + const bool bForceExpandHints ) +{ + bool bOK = true; + + for (sal_Int32 i = 0; i < rText.getLength(); ++i) + { + sal_Unicode const ch(rText[i]); + if (linguistic::IsControlChar(ch) + && ch != '\r' && ch != '\n' && ch != '\t') + { + SAL_WARN("sw.uno", "DocInsertStringSplitCR: refusing to insert control character " << int(ch)); + return false; + } + } + + const SwInsertFlags nInsertFlags = + bForceExpandHints + ? ( SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND) + : SwInsertFlags::EMPTYEXPAND; + + // grouping done in InsertString is intended for typing, not API calls + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + SwTextNode* const pTextNd = + rNewCursor.GetPoint()->nNode.GetNode().GetTextNode(); + if (!pTextNd) + { + SAL_INFO("sw.uno", "DocInsertStringSplitCR: need a text node"); + return false; + } + OUString aText; + sal_Int32 nStartIdx = 0; + const sal_Int32 nMaxLength = COMPLETE_STRING - pTextNd->GetText().getLength(); + + sal_Int32 nIdx = rText.indexOf( '\r', nStartIdx ); + if( ( nIdx == -1 && nMaxLength < rText.getLength() ) || + ( nIdx != -1 && nMaxLength < nIdx ) ) + { + nIdx = nMaxLength; + } + while (nIdx != -1 ) + { + OSL_ENSURE( nIdx - nStartIdx >= 0, "index negative!" ); + aText = rText.copy( nStartIdx, nIdx - nStartIdx ); + if (!aText.isEmpty() && + !rDoc.getIDocumentContentOperations().InsertString( rNewCursor, aText, nInsertFlags )) + { + OSL_FAIL( "Doc->Insert(Str) failed." ); + bOK = false; + } + if (!rDoc.getIDocumentContentOperations().SplitNode( *rNewCursor.GetPoint(), false ) ) + { + OSL_FAIL( "SplitNode failed" ); + bOK = false; + } + nStartIdx = nIdx + 1; + nIdx = rText.indexOf( '\r', nStartIdx ); + } + aText = rText.copy( nStartIdx ); + if (!aText.isEmpty() && + !rDoc.getIDocumentContentOperations().InsertString( rNewCursor, aText, nInsertFlags )) + { + OSL_FAIL( "Doc->Insert(Str) failed." ); + bOK = false; + } + + return bOK; +} + +void makeRedline( SwPaM const & rPaM, + const OUString& rRedlineType, + const uno::Sequence< beans::PropertyValue >& rRedlineProperties ) +{ + IDocumentRedlineAccess* pRedlineAccess = &rPaM.GetDoc()->getIDocumentRedlineAccess(); + + RedlineType eType; + if ( rRedlineType == "Insert" ) + eType = RedlineType::Insert; + else if ( rRedlineType == "Delete" ) + eType = RedlineType::Delete; + else if ( rRedlineType == "Format" ) + eType = RedlineType::Format; + else if ( rRedlineType == "TextTable" ) + eType = RedlineType::Table; + else if ( rRedlineType == "ParagraphFormat" ) + eType = RedlineType::ParagraphFormat; + else + throw lang::IllegalArgumentException(); + + //todo: what about REDLINE_FMTCOLL? + comphelper::SequenceAsHashMap aPropMap( rRedlineProperties ); + std::size_t nAuthor = 0; + OUString sAuthor; + if( aPropMap.getValue("RedlineAuthor") >>= sAuthor ) + nAuthor = pRedlineAccess->InsertRedlineAuthor(sAuthor); + + OUString sComment; + SwRedlineData aRedlineData( eType, nAuthor ); + if( aPropMap.getValue("RedlineComment") >>= sComment ) + aRedlineData.SetComment( sComment ); + + ::util::DateTime aStamp; + if( aPropMap.getValue("RedlineDateTime") >>= aStamp ) + { + aRedlineData.SetTimeStamp( DateTime( aStamp)); + } + + std::unique_ptr<SwRedlineExtraData_FormatColl> xRedlineExtraData; + + // Read the 'Redline Revert Properties' from the parameters + uno::Sequence< beans::PropertyValue > aRevertProperties; + // Check if the value exists + if ( aPropMap.getValue("RedlineRevertProperties") >>= aRevertProperties ) + { + int nMap = 0; + // Make sure that paragraph format gets its own map, otherwise e.g. fill attributes are not preserved. + if (eType == RedlineType::ParagraphFormat) + { + nMap = PROPERTY_MAP_PARAGRAPH; + if (!aRevertProperties.hasElements()) + { + // to reject the paragraph style change, use standard style + xRedlineExtraData.reset(new SwRedlineExtraData_FormatColl( "", RES_POOLCOLL_STANDARD, nullptr )); + } + } + else + nMap = PROPERTY_MAP_TEXTPORTION_EXTENSIONS; + SfxItemPropertySet const& rPropSet = *aSwMapProvider.GetPropertySet(nMap); + + // Check if there are any properties + if (aRevertProperties.hasElements()) + { + SwDoc *const pDoc = rPaM.GetDoc(); + + // Build set of attributes we want to fetch + std::vector<sal_uInt16> aWhichPairs; + std::vector<SfxItemPropertySimpleEntry const*> aEntries; + std::vector<uno::Any> aValues; + aEntries.reserve(aRevertProperties.getLength()); + sal_uInt16 nStyleId = USHRT_MAX; + sal_uInt16 nNumId = USHRT_MAX; + for (const auto& rRevertProperty : std::as_const(aRevertProperties)) + { + const OUString &rPropertyName = rRevertProperty.Name; + SfxItemPropertySimpleEntry const* pEntry = rPropSet.getPropertyMap().getByName(rPropertyName); + + if (!pEntry) + { + // unknown property + break; + } + else if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + break; + } + else if (rPropertyName == "NumberingRules") + { + aWhichPairs.push_back(RES_PARATR_NUMRULE); + aWhichPairs.push_back(RES_PARATR_NUMRULE); + nNumId = aEntries.size(); + } + else + { + // FIXME: we should have some nice way of merging ranges surely ? + aWhichPairs.push_back(pEntry->nWID); + aWhichPairs.push_back(pEntry->nWID); + if (rPropertyName == "ParaStyleName") + nStyleId = aEntries.size(); + } + aEntries.push_back(pEntry); + aValues.push_back(rRevertProperty.Value); + } + + if (!aWhichPairs.empty()) + { + sal_uInt16 nStylePoolId = USHRT_MAX; + OUString sParaStyleName; + aWhichPairs.push_back(0); // terminate + SfxItemSet aItemSet(pDoc->GetAttrPool(), aWhichPairs.data()); + + for (size_t i = 0; i < aEntries.size(); ++i) + { + SfxItemPropertySimpleEntry const*const pEntry = aEntries[i]; + const uno::Any &rValue = aValues[i]; + if (i == nNumId) + { + uno::Reference<container::XNamed> xNumberingRules; + rValue >>= xNumberingRules; + if (xNumberingRules.is()) + { + aItemSet.Put( SwNumRuleItem( xNumberingRules->getName() )); + // keep it during export + SwNumRule* pRule = pDoc->FindNumRulePtr( + xNumberingRules->getName()); + if (pRule) + pRule->SetUsedByRedline(true); + } + } + else + { + rPropSet.setPropertyValue(*pEntry, rValue, aItemSet); + if (i == nStyleId) + rValue >>= sParaStyleName; + } + } + + if (eType == RedlineType::ParagraphFormat && sParaStyleName.isEmpty()) + nStylePoolId = RES_POOLCOLL_STANDARD; + + xRedlineExtraData.reset(new SwRedlineExtraData_FormatColl( sParaStyleName, nStylePoolId, &aItemSet )); + } + else if (eType == RedlineType::ParagraphFormat) + xRedlineExtraData.reset(new SwRedlineExtraData_FormatColl( "", RES_POOLCOLL_STANDARD, nullptr )); + } + } + + SwRangeRedline* pRedline = new SwRangeRedline( aRedlineData, rPaM ); + RedlineFlags nPrevMode = pRedlineAccess->GetRedlineFlags( ); + // xRedlineExtraData is copied here + pRedline->SetExtraData( xRedlineExtraData.get() ); + + pRedlineAccess->SetRedlineFlags_intern(RedlineFlags::On); + auto const result(pRedlineAccess->AppendRedline(pRedline, false)); + pRedlineAccess->SetRedlineFlags_intern( nPrevMode ); + if (IDocumentRedlineAccess::AppendResult::IGNORED == result) + throw lang::IllegalArgumentException(); +} + +void makeTableRowRedline( SwTableLine& rTableLine, + const OUString& rRedlineType, + const uno::Sequence< beans::PropertyValue >& rRedlineProperties ) +{ + IDocumentRedlineAccess* pRedlineAccess = &rTableLine.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess(); + + RedlineType eType; + if ( rRedlineType == "TableRowInsert" ) + { + eType = RedlineType::TableRowInsert; + } + else if ( rRedlineType == "TableRowDelete" ) + { + eType = RedlineType::TableRowDelete; + } + else + { + throw lang::IllegalArgumentException(); + } + + comphelper::SequenceAsHashMap aPropMap( rRedlineProperties ); + std::size_t nAuthor = 0; + OUString sAuthor; + if( aPropMap.getValue("RedlineAuthor") >>= sAuthor ) + nAuthor = pRedlineAccess->InsertRedlineAuthor(sAuthor); + + OUString sComment; + SwRedlineData aRedlineData( eType, nAuthor ); + if( aPropMap.getValue("RedlineComment") >>= sComment ) + aRedlineData.SetComment( sComment ); + + ::util::DateTime aStamp; + if( aPropMap.getValue("RedlineDateTime") >>= aStamp ) + { + aRedlineData.SetTimeStamp( + DateTime( Date( aStamp.Day, aStamp.Month, aStamp.Year ), tools::Time( aStamp.Hours, aStamp.Minutes, aStamp.Seconds ) ) ); + } + + SwTableRowRedline* pRedline = new SwTableRowRedline( aRedlineData, rTableLine ); + RedlineFlags nPrevMode = pRedlineAccess->GetRedlineFlags( ); + pRedline->SetExtraData( nullptr ); + + pRedlineAccess->SetRedlineFlags_intern(RedlineFlags::On); + bool bRet = pRedlineAccess->AppendTableRowRedline( pRedline ); + pRedlineAccess->SetRedlineFlags_intern( nPrevMode ); + if( !bRet ) + throw lang::IllegalArgumentException(); +} + +void makeTableCellRedline( SwTableBox& rTableBox, + const OUString& rRedlineType, + const uno::Sequence< beans::PropertyValue >& rRedlineProperties ) +{ + IDocumentRedlineAccess* pRedlineAccess = &rTableBox.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess(); + + RedlineType eType; + if ( rRedlineType == "TableCellInsert" ) + { + eType = RedlineType::TableCellInsert; + } + else if ( rRedlineType == "TableCellDelete" ) + { + eType = RedlineType::TableCellDelete; + } + else + { + throw lang::IllegalArgumentException(); + } + + comphelper::SequenceAsHashMap aPropMap( rRedlineProperties ); + std::size_t nAuthor = 0; + OUString sAuthor; + if( aPropMap.getValue("RedlineAuthor") >>= sAuthor ) + nAuthor = pRedlineAccess->InsertRedlineAuthor(sAuthor); + + OUString sComment; + SwRedlineData aRedlineData( eType, nAuthor ); + if( aPropMap.getValue("RedlineComment") >>= sComment ) + aRedlineData.SetComment( sComment ); + + ::util::DateTime aStamp; + if( aPropMap.getValue("RedlineDateTime") >>= aStamp ) + { + aRedlineData.SetTimeStamp( + DateTime( Date( aStamp.Day, aStamp.Month, aStamp.Year ), tools::Time( aStamp.Hours, aStamp.Minutes, aStamp.Seconds ) ) ); + } + + SwTableCellRedline* pRedline = new SwTableCellRedline( aRedlineData, rTableBox ); + RedlineFlags nPrevMode = pRedlineAccess->GetRedlineFlags( ); + pRedline->SetExtraData( nullptr ); + + pRedlineAccess->SetRedlineFlags_intern(RedlineFlags::On); + bool bRet = pRedlineAccess->AppendTableCellRedline( pRedline ); + pRedlineAccess->SetRedlineFlags_intern( nPrevMode ); + if( !bRet ) + throw lang::IllegalArgumentException(); +} + +void SwAnyMapHelper::SetValue( sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any& rAny ) +{ + sal_uInt32 nKey = (nWhichId << 16) + nMemberId; + m_Map[nKey] = rAny; +} + +bool SwAnyMapHelper::FillValue( sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any*& pAny ) +{ + bool bRet = false; + sal_uInt32 nKey = (nWhichId << 16) + nMemberId; + auto aIt = m_Map.find( nKey ); + if (aIt != m_Map.end()) + { + pAny = & aIt->second; + bRet = true; + } + return bRet; +} + +}//namespace SwUnoCursorHelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unodraw.cxx b/sw/source/core/unocore/unodraw.cxx new file mode 100644 index 000000000..3c1949063 --- /dev/null +++ b/sw/source/core/unocore/unodraw.cxx @@ -0,0 +1,2854 @@ +/* -*- 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 <sal/config.h> + +#include <initializer_list> +#include <memory> +#include <sal/log.hxx> + +#include <cmdid.h> +#include <unomid.h> + +#include <drawdoc.hxx> +#include <unodraw.hxx> +#include <unoframe.hxx> +#include <unoparagraph.hxx> +#include <unotextrange.hxx> +#include <svx/svditer.hxx> +#include <swunohelper.hxx> +#include <textboxhelper.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <fmtcntnt.hxx> +#include <fmtflcnt.hxx> +#include <txatbase.hxx> +#include <docsh.hxx> +#include <unomap.hxx> +#include <unoport.hxx> +#include <TextCursorHelper.hxx> +#include <dflyobj.hxx> +#include <ndtxt.hxx> +#include <svx/svdview.hxx> +#include <svx/unoshape.hxx> +#include <dcontact.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <fmtfollowtextflow.hxx> +#include <rootfrm.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <svx/shapepropertynotifier.hxx> +#include <crstate.hxx> +#include <comphelper/extract.hxx> +#include <comphelper/profilezone.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svx/scene3d.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <com/sun/star/drawing/PointSequence.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +using namespace ::com::sun::star; + +class SwShapeDescriptor_Impl +{ + bool m_isInReading; + std::unique_ptr<SwFormatHoriOrient> m_pHOrient; + std::unique_ptr<SwFormatVertOrient> m_pVOrient; + std::unique_ptr<SwFormatAnchor> m_pAnchor; + std::unique_ptr<SwFormatSurround> m_pSurround; + std::unique_ptr<SvxULSpaceItem> m_pULSpace; + std::unique_ptr<SvxLRSpaceItem> m_pLRSpace; + bool bOpaque; + uno::Reference< text::XTextRange > xTextRange; + // #i26791# + std::unique_ptr<SwFormatFollowTextFlow> m_pFollowTextFlow; + // #i28701# + std::unique_ptr<SwFormatWrapInfluenceOnObjPos> m_pWrapInfluenceOnObjPos; + // #i28749# + sal_Int16 mnPositionLayoutDir; + + SwShapeDescriptor_Impl(const SwShapeDescriptor_Impl&) = delete; + SwShapeDescriptor_Impl& operator=(const SwShapeDescriptor_Impl&) = delete; + +public: + bool bInitializedPropertyNotifier; + +public: + SwShapeDescriptor_Impl(SwDoc const*const pDoc) + : m_isInReading(pDoc && pDoc->IsInReading()) + // #i32349# - no defaults, in order to determine on + // adding a shape, if positioning attributes are set or not. + , bOpaque(false) + // #i26791# + , m_pFollowTextFlow( new SwFormatFollowTextFlow(false) ) + // #i28701# #i35017# + , m_pWrapInfluenceOnObjPos( new SwFormatWrapInfluenceOnObjPos( + text::WrapInfluenceOnPosition::ONCE_CONCURRENT) ) + // #i28749# + , mnPositionLayoutDir(text::PositionLayoutDir::PositionInLayoutDirOfAnchor) + , bInitializedPropertyNotifier(false) + {} + + SwFormatAnchor* GetAnchor(bool bCreate = false) + { + if (bCreate && !m_pAnchor) + { + m_pAnchor.reset(new SwFormatAnchor(RndStdIds::FLY_AS_CHAR)); + } + return m_pAnchor.get(); + } + SwFormatHoriOrient* GetHOrient(bool bCreate = false) + { + if (bCreate && !m_pHOrient) + { + // #i26791# + m_pHOrient.reset(new SwFormatHoriOrient(0, text::HoriOrientation::NONE, text::RelOrientation::FRAME)); + } + return m_pHOrient.get(); + } + SwFormatVertOrient* GetVOrient(bool bCreate = false) + { + if (bCreate && !m_pVOrient) + { + if (m_isInReading && // tdf#113938 extensions might rely on old default + (!GetAnchor(true) || m_pAnchor->GetAnchorId() == RndStdIds::FLY_AS_CHAR)) + { // for as-char, NONE ("from-top") is not a good default + m_pVOrient.reset(new SwFormatVertOrient(0, text::VertOrientation::TOP, text::RelOrientation::FRAME)); + } + else + { // #i26791# + m_pVOrient.reset(new SwFormatVertOrient(0, text::VertOrientation::NONE, text::RelOrientation::FRAME)); + } + } + return m_pVOrient.get(); + } + + SwFormatSurround* GetSurround(bool bCreate = false) + { + if (bCreate && !m_pSurround) + { + m_pSurround.reset(new SwFormatSurround()); + } + return m_pSurround.get(); + } + SvxLRSpaceItem* GetLRSpace(bool bCreate = false) + { + if (bCreate && !m_pLRSpace) + { + m_pLRSpace.reset(new SvxLRSpaceItem(RES_LR_SPACE)); + } + return m_pLRSpace.get(); + } + SvxULSpaceItem* GetULSpace(bool bCreate = false) + { + if (bCreate && !m_pULSpace) + { + m_pULSpace.reset(new SvxULSpaceItem(RES_UL_SPACE)); + } + return m_pULSpace.get(); + } + uno::Reference< text::XTextRange > & GetTextRange() + { + return xTextRange; + } + bool IsOpaque() const + { + return bOpaque; + } + const bool& GetOpaque() const + { + return bOpaque; + } + void RemoveHOrient() { m_pHOrient.reset(); } + void RemoveVOrient() { m_pVOrient.reset(); } + void RemoveAnchor() { m_pAnchor.reset(); } + void RemoveSurround() { m_pSurround.reset(); } + void RemoveULSpace() { m_pULSpace.reset(); } + void RemoveLRSpace() { m_pLRSpace.reset(); } + void SetOpaque(bool bSet){bOpaque = bSet;} + + // #i26791# + SwFormatFollowTextFlow* GetFollowTextFlow( bool _bCreate = false ) + { + if (_bCreate && !m_pFollowTextFlow) + { + m_pFollowTextFlow.reset(new SwFormatFollowTextFlow(false)); + } + return m_pFollowTextFlow.get(); + } + void RemoveFollowTextFlow() + { + m_pFollowTextFlow.reset(); + } + + // #i28749# + sal_Int16 GetPositionLayoutDir() const + { + return mnPositionLayoutDir; + } + void SetPositionLayoutDir( sal_Int16 _nPositionLayoutDir ) + { + switch ( _nPositionLayoutDir ) + { + case text::PositionLayoutDir::PositionInHoriL2R: + case text::PositionLayoutDir::PositionInLayoutDirOfAnchor: + { + mnPositionLayoutDir = _nPositionLayoutDir; + } + break; + default: + { + OSL_FAIL( "<SwShapeDescriptor_Impl::SetPositionLayoutDir(..)> - invalid attribute value." ); + } + } + } + + // #i28701# + SwFormatWrapInfluenceOnObjPos* GetWrapInfluenceOnObjPos( + const bool _bCreate = false ) + { + if (_bCreate && !m_pWrapInfluenceOnObjPos) + { + m_pWrapInfluenceOnObjPos.reset(new SwFormatWrapInfluenceOnObjPos( + // #i35017# + text::WrapInfluenceOnPosition::ONCE_CONCURRENT)); + } + return m_pWrapInfluenceOnObjPos.get(); + } + void RemoveWrapInfluenceOnObjPos() + { + m_pWrapInfluenceOnObjPos.reset(); + } +}; + +SwFmDrawPage::SwFmDrawPage( SdrPage* pPage ) : + SvxFmDrawPage( pPage ), pPageView(nullptr) +{ +} + +SwFmDrawPage::~SwFmDrawPage() throw () +{ + while (!m_vShapes.empty()) + m_vShapes.back()->dispose(); + RemovePageView(); +} + +const SdrMarkList& SwFmDrawPage::PreGroup(const uno::Reference< drawing::XShapes > & xShapes) +{ + SelectObjectsInView( xShapes, GetPageView() ); + const SdrMarkList& rMarkList = mpView->GetMarkedObjectList(); + return rMarkList; +} + +void SwFmDrawPage::PreUnGroup(const uno::Reference< drawing::XShapeGroup >& rShapeGroup) +{ + SelectObjectInView( rShapeGroup, GetPageView() ); +} + +SdrPageView* SwFmDrawPage::GetPageView() +{ + if(!pPageView) + pPageView = mpView->ShowSdrPage( mpPage ); + return pPageView; +} + +void SwFmDrawPage::RemovePageView() +{ + if(pPageView && mpView) + mpView->HideSdrPage(); + pPageView = nullptr; +} + +uno::Reference<drawing::XShape> SwFmDrawPage::GetShape(SdrObject* pObj) +{ + if(!pObj) + return nullptr; + SwFrameFormat* pFormat = ::FindFrameFormat( pObj ); + SwFmDrawPage* pPage = dynamic_cast<SwFmDrawPage*>(pFormat); + if(!pPage || pPage->m_vShapes.empty()) + return uno::Reference<drawing::XShape>(pObj->getUnoShape(), uno::UNO_QUERY); + for(auto pShape : pPage->m_vShapes) + { + SvxShape* pSvxShape = pShape->GetSvxShape(); + if (pSvxShape && pSvxShape->GetSdrObject() == pObj) + return uno::Reference<drawing::XShape>(static_cast<::cppu::OWeakObject*>(pShape), uno::UNO_QUERY); + } + return nullptr; +} + +uno::Reference<drawing::XShapeGroup> SwFmDrawPage::GetShapeGroup(SdrObject* pObj) +{ + return uno::Reference<drawing::XShapeGroup>(GetShape(pObj), uno::UNO_QUERY); +} + +uno::Reference< drawing::XShape > SwFmDrawPage::CreateShape( SdrObject *pObj ) const +{ + uno::Reference< drawing::XShape > xRet; + if(dynamic_cast<const SwVirtFlyDrawObj*>( pObj) != nullptr || pObj->GetObjInventor() == SdrInventor::Swg) + { + SwFlyDrawContact* pFlyContact = static_cast<SwFlyDrawContact*>(pObj->GetUserCall()); + if(pFlyContact) + { + SwFrameFormat* pFlyFormat = pFlyContact->GetFormat(); + SwDoc* pDoc = pFlyFormat->GetDoc(); + const SwNodeIndex* pIdx; + if( RES_FLYFRMFMT == pFlyFormat->Which() + && nullptr != ( pIdx = pFlyFormat->GetContent().GetContentIdx() ) + && pIdx->GetNodes().IsDocNodes() + ) + { + const SwNode* pNd = pDoc->GetNodes()[ pIdx->GetIndex() + 1 ]; + if(!pNd->IsNoTextNode()) + { + xRet.set(SwXTextFrame::CreateXTextFrame(*pDoc, pFlyFormat), + uno::UNO_QUERY); + } + else if( pNd->IsGrfNode() ) + { + xRet.set(SwXTextGraphicObject::CreateXTextGraphicObject( + *pDoc, pFlyFormat), uno::UNO_QUERY); + } + else if( pNd->IsOLENode() ) + { + xRet.set(SwXTextEmbeddedObject::CreateXTextEmbeddedObject( + *pDoc, pFlyFormat), uno::UNO_QUERY); + } + } + else + { + OSL_FAIL( "<SwFmDrawPage::CreateShape(..)> - could not retrieve type. Thus, no shape created." ); + return xRet; + } + } + } + else + { + // own block - temporary object has to be destroyed before + // the delegator is set #81670# + { + xRet = SvxFmDrawPage::CreateShape( pObj ); + } + uno::Reference< XUnoTunnel > xShapeTunnel(xRet, uno::UNO_QUERY); + //don't create an SwXShape if it already exists + SwXShape* pShape = nullptr; + if(xShapeTunnel.is()) + pShape = reinterpret_cast< SwXShape * >( + sal::static_int_cast< sal_IntPtr >( xShapeTunnel->getSomething(SwXShape::getUnoTunnelId()) )); + if(!pShape) + { + xShapeTunnel = nullptr; + uno::Reference< uno::XInterface > xCreate(xRet, uno::UNO_QUERY); + xRet = nullptr; + if ( pObj->IsGroupObject() && (!pObj->Is3DObj() || (dynamic_cast<const E3dScene*>( pObj) != nullptr)) ) + pShape = new SwXGroupShape(xCreate, nullptr); + else + pShape = new SwXShape(xCreate, nullptr); + uno::Reference<beans::XPropertySet> xPrSet = pShape; + xRet.set(xPrSet, uno::UNO_QUERY); + } + const_cast<std::vector<SwXShape*>*>(&m_vShapes)->push_back(pShape); + pShape->m_pPage = this; + } + return xRet; +} + +namespace +{ + class SwXShapesEnumeration + : public SwSimpleEnumeration_Base + { + private: + std::vector< css::uno::Any > m_aShapes; + protected: + virtual ~SwXShapesEnumeration() override {}; + public: + explicit SwXShapesEnumeration(SwXDrawPage* const pDrawPage); + + //XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual uno::Any SAL_CALL nextElement() override; + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + }; +} + +SwXShapesEnumeration::SwXShapesEnumeration(SwXDrawPage* const pDrawPage) + : m_aShapes() +{ + SolarMutexGuard aGuard; + sal_Int32 nCount = pDrawPage->getCount(); + m_aShapes.reserve(nCount); + for(sal_Int32 nIdx = 0; nIdx < nCount; nIdx++) + { + uno::Reference<drawing::XShape> xShape(pDrawPage->getByIndex(nIdx), uno::UNO_QUERY); + m_aShapes.push_back(uno::makeAny(xShape)); + } +} + +sal_Bool SwXShapesEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + return !m_aShapes.empty(); +} + +uno::Any SwXShapesEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + if(m_aShapes.empty()) + throw container::NoSuchElementException(); + uno::Any aResult = m_aShapes.back(); + m_aShapes.pop_back(); + return aResult; +} + +OUString SwXShapesEnumeration::getImplementationName() +{ + return "SwXShapeEnumeration"; +} + +sal_Bool SwXShapesEnumeration::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SwXShapesEnumeration::getSupportedServiceNames() +{ + return { OUString("com.sun.star.container.XEnumeration") }; +} + +uno::Reference< container::XEnumeration > SwXDrawPage::createEnumeration() +{ + SolarMutexGuard aGuard; + return uno::Reference< container::XEnumeration >( + new SwXShapesEnumeration(this)); +} + +OUString SwXDrawPage::getImplementationName() +{ + return "SwXDrawPage"; +} + +sal_Bool SwXDrawPage::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXDrawPage::getSupportedServiceNames() +{ + return { "com.sun.star.drawing.GenericDrawPage" }; +} + +SwXDrawPage::SwXDrawPage(SwDoc* pDc) : + pDoc(pDc), + pDrawPage(nullptr) +{ +} + +SwXDrawPage::~SwXDrawPage() +{ + if(xPageAgg.is()) + { + uno::Reference< uno::XInterface > xInt; + xPageAgg->setDelegator(xInt); + } +} + +uno::Any SwXDrawPage::queryInterface( const uno::Type& aType ) +{ + uno::Any aRet = SwXDrawPageBaseClass::queryInterface(aType); + if(!aRet.hasValue()) + { + // secure with checking if page exists. This may not be the case + // either for new SW docs with no yet graphics usage or when + // the doc is closed and someone else still holds a UNO reference + // to the XDrawPage (in that case, pDoc is set to 0) + SwFmDrawPage* pPage = GetSvxPage(); + + if(pPage) + { + aRet = pPage->queryAggregation(aType); + } + } + return aRet; +} + +uno::Sequence< uno::Type > SwXDrawPage::getTypes() +{ + return comphelper::concatSequences( + SwXDrawPageBaseClass::getTypes(), + GetSvxPage()->getTypes(), + uno::Sequence { cppu::UnoType<form::XFormsSupplier2>::get() }); +} + +sal_Int32 SwXDrawPage::getCount() +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + if(!pDoc->getIDocumentDrawModelAccess().GetDrawModel()) + return 0; + else + { + GetSvxPage(); + return SwTextBoxHelper::getCount(pDrawPage->GetSdrPage()); + } +} + +uno::Any SwXDrawPage::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + if(!pDoc->getIDocumentDrawModelAccess().GetDrawModel()) + throw lang::IndexOutOfBoundsException(); + + GetSvxPage(); + return SwTextBoxHelper::getByIndex(pDrawPage->GetSdrPage(), nIndex); +} + +uno::Type SwXDrawPage::getElementType() +{ + return cppu::UnoType<drawing::XShape>::get(); +} + +sal_Bool SwXDrawPage::hasElements() +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + if(!pDoc->getIDocumentDrawModelAccess().GetDrawModel()) + return false; + else + return GetSvxPage()->hasElements(); +} + +void SwXDrawPage::add(const uno::Reference< drawing::XShape > & xShape) +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + uno::Reference< lang::XUnoTunnel > xShapeTunnel(xShape, uno::UNO_QUERY); + SwXShape* pShape = nullptr; + SvxShape* pSvxShape = nullptr; + if(xShapeTunnel.is()) + { + pShape = reinterpret_cast< SwXShape * >( + sal::static_int_cast< sal_IntPtr >( xShapeTunnel->getSomething(SwXShape::getUnoTunnelId()) )); + pSvxShape = reinterpret_cast< SvxShape * >( + sal::static_int_cast< sal_IntPtr >( xShapeTunnel->getSomething(SvxShape::getUnoTunnelId()) )); + } + + // this is not a writer shape + if(!pShape) + throw uno::RuntimeException("illegal object", + static_cast< cppu::OWeakObject * > ( this ) ); + + // we're already registered in the model / SwXDrawPage::add() already called + if(pShape->m_pPage || pShape->m_pFormat || !pShape->m_bDescriptor ) + return; + + // we're inserted elsewhere already + if ( pSvxShape->GetSdrObject() ) + { + if ( pSvxShape->GetSdrObject()->IsInserted() ) + { + return; + } + } + GetSvxPage()->add(xShape); + + OSL_ENSURE(pSvxShape, "Why is here no SvxShape?"); + // this position is definitely in 1/100 mm + awt::Point aMM100Pos(pSvxShape->getPosition()); + + // now evaluate the properties of SwShapeDescriptor_Impl + SwShapeDescriptor_Impl* pDesc = pShape->GetDescImpl(); + + SfxItemSet aSet( pDoc->GetAttrPool(), svl::Items<RES_FRMATR_BEGIN, + RES_FRMATR_END-1>{} ); + SwFormatAnchor aAnchor( RndStdIds::FLY_AS_CHAR ); + bool bOpaque = false; + if( pDesc ) + { + if(pDesc->GetSurround()) + aSet.Put( *pDesc->GetSurround()); + // all items are already in Twip + if(pDesc->GetLRSpace()) + { + aSet.Put(*pDesc->GetLRSpace()); + } + if(pDesc->GetULSpace()) + { + aSet.Put(*pDesc->GetULSpace()); + } + if(pDesc->GetAnchor()) + aAnchor = *pDesc->GetAnchor(); + + // #i32349# - if no horizontal position exists, create one + if ( !pDesc->GetHOrient() ) + { + SwFormatHoriOrient* pHori = pDesc->GetHOrient( true ); + SwTwips nHoriPos = convertMm100ToTwip(aMM100Pos.X); + pHori->SetPos( nHoriPos ); + } + { + if(pDesc->GetHOrient()->GetHoriOrient() == text::HoriOrientation::NONE) + aMM100Pos.X = convertTwipToMm100(pDesc->GetHOrient()->GetPos()); + aSet.Put( *pDesc->GetHOrient() ); + } + // #i32349# - if no vertical position exists, create one + if ( !pDesc->GetVOrient() ) + { + SwFormatVertOrient* pVert = pDesc->GetVOrient( true ); + SwTwips nVertPos = convertMm100ToTwip(aMM100Pos.Y); + pVert->SetPos( nVertPos ); + } + { + if(pDesc->GetVOrient()->GetVertOrient() == text::VertOrientation::NONE) + aMM100Pos.Y = convertTwipToMm100(pDesc->GetVOrient()->GetPos()); + aSet.Put( *pDesc->GetVOrient() ); + } + + if(pDesc->GetSurround()) + aSet.Put( *pDesc->GetSurround()); + bOpaque = pDesc->IsOpaque(); + + // #i26791# + if ( pDesc->GetFollowTextFlow() ) + { + aSet.Put( *pDesc->GetFollowTextFlow() ); + } + + // #i28701# + if ( pDesc->GetWrapInfluenceOnObjPos() ) + { + aSet.Put( *pDesc->GetWrapInfluenceOnObjPos() ); + } + } + + pSvxShape->setPosition(aMM100Pos); + SdrObject* pObj = pSvxShape->GetSdrObject(); + // #108784# - set layer of new drawing object to corresponding + // invisible layer. + if(SdrInventor::FmForm != pObj->GetObjInventor()) + pObj->SetLayer( bOpaque ? pDoc->getIDocumentDrawModelAccess().GetInvisibleHeavenId() : pDoc->getIDocumentDrawModelAccess().GetInvisibleHellId() ); + else + pObj->SetLayer(pDoc->getIDocumentDrawModelAccess().GetInvisibleControlsId()); + + std::unique_ptr<SwPaM> pPam(new SwPaM(pDoc->GetNodes().GetEndOfContent())); + std::unique_ptr<SwUnoInternalPaM> pInternalPam; + uno::Reference< text::XTextRange > xRg; + if( pDesc && (xRg = pDesc->GetTextRange()).is() ) + { + pInternalPam.reset(new SwUnoInternalPaM(*pDoc)); + if (!::sw::XTextRangeToSwPaM(*pInternalPam, xRg)) + throw uno::RuntimeException(); + + if(RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId() && + !pInternalPam->GetNode().FindFlyStartNode()) + { + aAnchor.SetType(RndStdIds::FLY_AS_CHAR); + } + else if (RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId()) + { + aAnchor.SetAnchor(pInternalPam->Start()); + } + + } + else if ((aAnchor.GetAnchorId() != RndStdIds::FLY_AT_PAGE) && pDoc->getIDocumentLayoutAccess().GetCurrentLayout()) + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + Point aTmp(convertMm100ToTwip(aMM100Pos.X), convertMm100ToTwip(aMM100Pos.Y)); + pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( pPam->GetPoint(), aTmp, &aState ); + aAnchor.SetAnchor( pPam->GetPoint() ); + + // #i32349# - adjustment of vertical positioning + // attributes no longer needed, because it's already got a default. + } + else + { + aAnchor.SetType(RndStdIds::FLY_AT_PAGE); + + // #i32349# - adjustment of vertical positioning + // attributes no longer needed, because it's already got a default. + } + aSet.Put(aAnchor); + SwPaM* pTemp = pInternalPam.get(); + if ( !pTemp ) + pTemp = pPam.get(); + UnoActionContext aAction(pDoc); + pDoc->getIDocumentContentOperations().InsertDrawObj( *pTemp, *pObj, aSet ); + + if (pSvxShape->GetSdrObject()->GetName().isEmpty()) + { + pSvxShape->GetSdrObject()->SetName(pDoc->GetUniqueShapeName()); + } + + SwFrameFormat* pFormat = ::FindFrameFormat( pObj ); + if (pFormat) + { + if (pFormat->GetName().isEmpty()) + { + pFormat->SetName(pSvxShape->GetSdrObject()->GetName(), false); + } + pShape->SetFrameFormat(pFormat); + } + pShape->m_bDescriptor = false; + + pPam.reset(); + pInternalPam.reset(); +} + +void SwXDrawPage::remove(const uno::Reference< drawing::XShape > & xShape) +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + uno::Reference<lang::XComponent> xComp(xShape, uno::UNO_QUERY); + xComp->dispose(); +} + +uno::Reference< drawing::XShapeGroup > SwXDrawPage::group(const uno::Reference< drawing::XShapes > & xShapes) +{ + SolarMutexGuard aGuard; + if(!pDoc || !xShapes.is()) + throw uno::RuntimeException(); + uno::Reference< drawing::XShapeGroup > xRet; + if(xPageAgg.is()) + { + + SwFmDrawPage* pPage = GetSvxPage(); + if(pPage) //TODO: can this be Null? + { + // mark and return MarkList + const SdrMarkList& rMarkList = pPage->PreGroup(xShapes); + if ( rMarkList.GetMarkCount() > 0 ) + { + for (size_t i = 0; i < rMarkList.GetMarkCount(); ++i) + { + const SdrObject *pObj = rMarkList.GetMark( i )->GetMarkedSdrObj(); + if (RndStdIds::FLY_AS_CHAR == ::FindFrameFormat(const_cast<SdrObject*>( + pObj))->GetAnchor().GetAnchorId()) + { + throw uno::RuntimeException(); // FlyInCnt! + } + } + + UnoActionContext aContext(pDoc); + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + SwDrawContact* pContact = pDoc->GroupSelection( *pPage->GetDrawView() ); + pDoc->ChgAnchor( + pPage->GetDrawView()->GetMarkedObjectList(), + RndStdIds::FLY_AT_PARA, + true, false ); + + pPage->GetDrawView()->UnmarkAll(); + if(pContact) + xRet = SwFmDrawPage::GetShapeGroup( pContact->GetMaster() ); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + pPage->RemovePageView(); + } + } + return xRet; +} + +void SwXDrawPage::ungroup(const uno::Reference< drawing::XShapeGroup > & rShapeGroup) +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + if(xPageAgg.is()) + { + SwFmDrawPage* pPage = GetSvxPage(); + if(pPage) //TODO: can this be Null? + { + pPage->PreUnGroup(rShapeGroup); + UnoActionContext aContext(pDoc); + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + pDoc->UnGroupSelection( *pPage->GetDrawView() ); + pDoc->ChgAnchor( pPage->GetDrawView()->GetMarkedObjectList(), + RndStdIds::FLY_AT_PARA, + true, false ); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + pPage->RemovePageView(); + } + } +} + +SwFmDrawPage* SwXDrawPage::GetSvxPage() +{ + if(!xPageAgg.is() && pDoc) + { + SolarMutexGuard aGuard; + // #i52858# + SwDrawModel* pModel = pDoc->getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + SdrPage* pPage = pModel->GetPage( 0 ); + + { + // We need a Ref to the object during queryInterface or else + // it will be deleted + pDrawPage = new SwFmDrawPage(pPage); + uno::Reference< drawing::XDrawPage > xPage = pDrawPage; + uno::Any aAgg = xPage->queryInterface(cppu::UnoType<uno::XAggregation>::get()); + aAgg >>= xPageAgg; + } + if( xPageAgg.is() ) + xPageAgg->setDelegator( static_cast<cppu::OWeakObject*>(this) ); + } + return pDrawPage; +} + +/** + * Renamed and outlined to detect where it's called + */ +void SwXDrawPage::InvalidateSwDoc() +{ + pDoc = nullptr; +} + +namespace +{ + class theSwXShapeUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXShapeUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXShape::getUnoTunnelId() +{ + return theSwXShapeUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXShape::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXShape>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + + if( xShapeAgg.is() ) + { + const uno::Type& rTunnelType = cppu::UnoType<lang::XUnoTunnel>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rTunnelType ); + if(auto xAggTunnel = o3tl::tryAccess<uno::Reference<lang::XUnoTunnel>>( + aAgg)) + { + if(xAggTunnel->is()) + return (*xAggTunnel)->getSomething(rId); + } + } + return 0; +} +namespace +{ + void lcl_addShapePropertyEventFactories( SdrObject& _rObj, SwXShape& _rShape ) + { + auto pProvider = std::make_shared<svx::PropertyValueProvider>( _rShape, "AnchorType" ); + _rObj.getShapePropertyChangeNotifier().registerProvider( svx::ShapeProperty::TextDocAnchor, pProvider ); + } +} + +SwXShape::SwXShape( + uno::Reference<uno::XInterface> & xShape, + SwDoc const*const pDoc) + : m_pPage(nullptr) + , m_pFormat(nullptr) + , m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_SHAPE)) + , m_pPropertyMapEntries(aSwMapProvider.GetPropertyMapEntries(PROPERTY_MAP_TEXT_SHAPE)) + , pImpl(new SwShapeDescriptor_Impl(pDoc)) + , m_bDescriptor(true) +{ + if(!xShape.is()) // default Ctor + return; + + const uno::Type& rAggType = cppu::UnoType<uno::XAggregation>::get(); + //aAgg contains a reference of the SvxShape! + { + uno::Any aAgg = xShape->queryInterface(rAggType); + aAgg >>= xShapeAgg; + // #i31698# + if ( xShapeAgg.is() ) + { + xShapeAgg->queryAggregation( cppu::UnoType<drawing::XShape>::get()) >>= mxShape; + OSL_ENSURE( mxShape.is(), + "<SwXShape::SwXShape(..)> - no XShape found at <xShapeAgg>" ); + } + } + xShape = nullptr; + osl_atomic_increment(&m_refCount); + if( xShapeAgg.is() ) + xShapeAgg->setDelegator( static_cast<cppu::OWeakObject*>(this) ); + osl_atomic_decrement(&m_refCount); + + SvxShape* pShape = comphelper::getUnoTunnelImplementation<SvxShape>(xShapeAgg); + + SdrObject* pObj = pShape ? pShape->GetSdrObject() : nullptr; + if(pObj) + { + auto pFormat = ::FindFrameFormat( pObj ); + if(pFormat) + SetFrameFormat(pFormat); + + lcl_addShapePropertyEventFactories( *pObj, *this ); + pImpl->bInitializedPropertyNotifier = true; + } + +} + +void SwXShape::AddExistingShapeToFormat( SdrObject const & _rObj ) +{ + SdrObjListIter aIter( _rObj, SdrIterMode::DeepNoGroups ); + while ( aIter.IsMore() ) + { + SdrObject* pCurrent = aIter.Next(); + OSL_ENSURE( pCurrent, "SwXShape::AddExistingShapeToFormat: invalid object list element!" ); + if ( !pCurrent ) + continue; + + auto pSwShape = comphelper::getUnoTunnelImplementation<SwXShape>(pCurrent->getWeakUnoShape()); + if ( pSwShape ) + { + if ( pSwShape->m_bDescriptor ) + { + auto pFormat = ::FindFrameFormat( pCurrent ); + if ( pFormat ) + pSwShape->SetFrameFormat(pFormat); + pSwShape->m_bDescriptor = false; + } + + if ( !pSwShape->pImpl->bInitializedPropertyNotifier ) + { + lcl_addShapePropertyEventFactories( *pCurrent, *pSwShape ); + pSwShape->pImpl->bInitializedPropertyNotifier = true; + } + } + } +} + +SwXShape::~SwXShape() +{ + SolarMutexGuard aGuard; + if (xShapeAgg.is()) + { + uno::Reference< uno::XInterface > xRef; + xShapeAgg->setDelegator(xRef); + } + pImpl.reset(); + EndListeningAll(); + if(m_pPage) + const_cast<SwFmDrawPage*>(m_pPage)->RemoveShape(this); + m_pPage = nullptr; +} + +uno::Any SwXShape::queryInterface( const uno::Type& aType ) +{ + uno::Any aRet = SwTextBoxHelper::queryInterface(GetFrameFormat(), aType); + if (aRet.hasValue()) + return aRet; + + aRet = SwXShapeBaseClass::queryInterface(aType); + // #i53320# - follow-up of #i31698# + // interface drawing::XShape is overloaded. Thus, provide + // correct object instance. + if(!aRet.hasValue() && xShapeAgg.is()) + { + if(aType == cppu::UnoType<XShape>::get()) + aRet <<= uno::Reference<XShape>(this); + else + aRet = xShapeAgg->queryAggregation(aType); + } + return aRet; +} + +uno::Sequence< uno::Type > SwXShape::getTypes( ) +{ + uno::Sequence< uno::Type > aRet = SwXShapeBaseClass::getTypes(); + if(xShapeAgg.is()) + { + uno::Any aProv = xShapeAgg->queryAggregation(cppu::UnoType<XTypeProvider>::get()); + if(aProv.hasValue()) + { + uno::Reference< XTypeProvider > xAggProv; + aProv >>= xAggProv; + return comphelper::concatSequences(aRet, xAggProv->getTypes()); + } + } + return aRet; +} + +uno::Sequence< sal_Int8 > SwXShape::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Reference< beans::XPropertySetInfo > SwXShape::getPropertySetInfo() +{ + SolarMutexGuard aGuard; + uno::Reference< beans::XPropertySetInfo > aRet; + if(xShapeAgg.is()) + { + const uno::Type& rPropSetType = cppu::UnoType<beans::XPropertySet>::get(); + uno::Any aPSet = xShapeAgg->queryAggregation( rPropSetType ); + if(auto xPrSet = o3tl::tryAccess<uno::Reference<beans::XPropertySet>>( + aPSet)) + { + uno::Reference< beans::XPropertySetInfo > xInfo = (*xPrSet)->getPropertySetInfo(); + // Expand PropertySetInfo! + const uno::Sequence<beans::Property> aPropSeq = xInfo->getProperties(); + aRet = new SfxExtItemPropertySetInfo( m_pPropertyMapEntries, aPropSeq ); + } + } + if(!aRet.is()) + aRet = m_pPropSet->getPropertySetInfo(); + return aRet; +} + +void SwXShape::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if(xShapeAgg.is()) + { + if(pEntry) + { + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException ("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + // with the layout it is possible to move the anchor without changing the position + if(pFormat) + { + SwAttrSet aSet(pFormat->GetAttrSet()); + SwDoc* pDoc = pFormat->GetDoc(); + if(RES_ANCHOR == pEntry->nWID && MID_ANCHOR_ANCHORFRAME == pEntry->nMemberId) + { + bool bDone = false; + uno::Reference<text::XTextFrame> xFrame; + if(aValue >>= xFrame) + { + SwXFrame* pFrame = comphelper::getUnoTunnelImplementation<SwXFrame>(xFrame); + if(pFrame && pFrame->GetFrameFormat() && + pFrame->GetFrameFormat()->GetDoc() == pDoc) + { + UnoActionContext aCtx(pDoc); + SfxItemSet aItemSet( pDoc->GetAttrPool(), + svl::Items<RES_FRMATR_BEGIN, RES_FRMATR_END - 1>{} ); + aItemSet.SetParent(&pFormat->GetAttrSet()); + SwFormatAnchor aAnchor = static_cast<const SwFormatAnchor&>(aItemSet.Get(pEntry->nWID)); + SwPosition aPos(*pFrame->GetFrameFormat()->GetContent().GetContentIdx()); + aAnchor.SetAnchor(&aPos); + aAnchor.SetType(RndStdIds::FLY_AT_FLY); + aItemSet.Put(aAnchor); + pFormat->SetFormatAttr(aItemSet); + bDone = true; + } + } + if(!bDone) + throw lang::IllegalArgumentException(); + } + else if(RES_OPAQUE == pEntry->nWID) + { + SvxShape* pSvxShape = GetSvxShape(); + SAL_WARN_IF(!pSvxShape, "sw.uno", "No SvxShape found!"); + if(pSvxShape) + { + SdrObject* pObj = pSvxShape->GetSdrObject(); + // set layer of new drawing + // object to corresponding invisible layer. + bool bIsVisible = pDoc->getIDocumentDrawModelAccess().IsVisibleLayerId( pObj->GetLayer() ); + if(SdrInventor::FmForm != pObj->GetObjInventor()) + { + pObj->SetLayer( *o3tl::doAccess<bool>(aValue) + ? ( bIsVisible ? pDoc->getIDocumentDrawModelAccess().GetHeavenId() : pDoc->getIDocumentDrawModelAccess().GetInvisibleHeavenId() ) + : ( bIsVisible ? pDoc->getIDocumentDrawModelAccess().GetHellId() : pDoc->getIDocumentDrawModelAccess().GetInvisibleHellId() )); + } + else + { + pObj->SetLayer( bIsVisible ? pDoc->getIDocumentDrawModelAccess().GetControlsId() : pDoc->getIDocumentDrawModelAccess().GetInvisibleControlsId()); + } + + } + + } + // #i26791# - special handling for property FN_TEXT_RANGE + else if ( FN_TEXT_RANGE == pEntry->nWID ) + { + SwFormatAnchor aAnchor( aSet.Get( RES_ANCHOR ) ); + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + // set property <TextRange> not valid for to-page anchored shapes + throw lang::IllegalArgumentException(); + } + + std::unique_ptr<SwUnoInternalPaM> pInternalPam( + new SwUnoInternalPaM( *(pFormat->GetDoc()) )); + uno::Reference< text::XTextRange > xRg; + aValue >>= xRg; + if (!::sw::XTextRangeToSwPaM(*pInternalPam, xRg) ) + { + throw uno::RuntimeException(); + } + + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + //delete old SwFormatFlyCnt + //With AnchorAsCharacter the current TextAttribute has to be deleted. + //Tbis removes the frame format too. + //To prevent this the connection between format and attribute has to be broken before. + const SwPosition *pPos = aAnchor.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + SAL_WARN_IF( !pTextNode->HasHints(), "sw.uno", "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( + nIdx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing Hint."); + SAL_WARN_IF( pHint->Which() != RES_TXTATR_FLYCNT, + "sw.uno", "Missing FlyInCnt-Hint." ); + SAL_WARN_IF( pHint->GetFlyCnt().GetFrameFormat() != pFormat, + "sw.uno", "Wrong TextFlyCnt-Hint." ); + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()) + .SetFlyFormat(); + + //The connection is removed now the attribute can be deleted. + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx ); + //create a new one + SwTextNode *pNd = pInternalPam->GetNode().GetTextNode(); + SAL_WARN_IF( !pNd, "sw.uno", "Cursor not at TextNode." ); + SwFormatFlyCnt aFormat( pFormat ); + pNd->InsertItem(aFormat, pInternalPam->GetPoint() + ->nContent.GetIndex(), 0 ); + } + else + { + aAnchor.SetAnchor( pInternalPam->GetPoint() ); + aSet.Put(aAnchor); + pFormat->SetFormatAttr(aSet); + } + } + else if (pEntry->nWID == FN_TEXT_BOX) + { + bool bValue(false); + aValue >>= bValue; + if (bValue) + SwTextBoxHelper::create(pFormat); + else + SwTextBoxHelper::destroy(pFormat); + + } + else if (pEntry->nWID == RES_CHAIN) + { + if (pEntry->nMemberId == MID_CHAIN_NEXTNAME || pEntry->nMemberId == MID_CHAIN_PREVNAME) + SwTextBoxHelper::syncProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aValue); + } + // #i28749# + else if ( FN_SHAPE_POSITION_LAYOUT_DIR == pEntry->nWID ) + { + sal_Int16 nPositionLayoutDir = 0; + aValue >>= nPositionLayoutDir; + pFormat->SetPositionLayoutDir( nPositionLayoutDir ); + } + else if( pDoc->getIDocumentLayoutAccess().GetCurrentLayout()) + { + UnoActionContext aCtx(pDoc); + if(RES_ANCHOR == pEntry->nWID && MID_ANCHOR_ANCHORTYPE == pEntry->nMemberId) + { + SdrObject* pObj = pFormat->FindSdrObject(); + SdrMarkList aList; + SdrMark aMark(pObj); + aList.InsertEntry(aMark); + sal_Int32 nAnchor = 0; + cppu::enum2int( nAnchor, aValue ); + pDoc->ChgAnchor( aList, static_cast<RndStdIds>(nAnchor), + false, true ); + } + else + { + m_pPropSet->setPropertyValue(*pEntry, aValue, aSet); + pFormat->SetFormatAttr(aSet); + } + } + else if( RES_FRM_SIZE == pEntry->nWID && + ( pEntry->nMemberId == MID_FRMSIZE_REL_HEIGHT || pEntry->nMemberId == MID_FRMSIZE_REL_WIDTH + || pEntry->nMemberId == MID_FRMSIZE_REL_HEIGHT_RELATION + || pEntry->nMemberId == MID_FRMSIZE_REL_WIDTH_RELATION ) ) + { + SvxShape* pSvxShape = GetSvxShape(); + SAL_WARN_IF(!pSvxShape, "sw.uno", "No SvxShape found!"); + if(pSvxShape) + { + SdrObject* pObj = pSvxShape->GetSdrObject(); + sal_Int16 nPercent(100); + aValue >>= nPercent; + switch (pEntry->nMemberId) + { + case MID_FRMSIZE_REL_WIDTH: + pObj->SetRelativeWidth( nPercent / 100.0 ); + break; + case MID_FRMSIZE_REL_HEIGHT: + pObj->SetRelativeHeight( nPercent / 100.0 ); + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + pObj->SetRelativeWidthRelation(nPercent); + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + pObj->SetRelativeHeightRelation(nPercent); + break; + } + } + } + else if (pEntry->nWID == RES_HORI_ORIENT + && pEntry->nMemberId == MID_HORIORIENT_RELATION + && aSet.Get(RES_ANCHOR).GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + uno::Any value(aValue); + sal_Int16 nRelOrient(text::RelOrientation::PAGE_FRAME); + aValue >>= nRelOrient; + if (sw::GetAtPageRelOrientation(nRelOrient, true)) + { + SAL_WARN("sw.core", "SwXShape: fixing invalid horizontal RelOrientation for at-page anchor"); + value <<= nRelOrient; + } + m_pPropSet->setPropertyValue( *pEntry, value, aSet ); + pFormat->SetFormatAttr(aSet); + } + else + { + m_pPropSet->setPropertyValue( *pEntry, aValue, aSet ); + + if(RES_ANCHOR == pEntry->nWID && MID_ANCHOR_ANCHORTYPE == pEntry->nMemberId) + { + bool bSetAttr = true; + text::TextContentAnchorType eNewAnchor = static_cast<text::TextContentAnchorType>(SWUnoHelper::GetEnumAsInt32( aValue )); + + //if old anchor was in_cntnt the related text attribute has to be removed + const SwFormatAnchor& rOldAnchor = pFormat->GetAnchor(); + RndStdIds eOldAnchorId = rOldAnchor.GetAnchorId(); + SdrObject* pObj = pFormat->FindSdrObject(); + SwFrameFormat *pFlyFormat = FindFrameFormat( pObj ); + pFlyFormat->DelFrames(); + if( text::TextContentAnchorType_AS_CHARACTER != eNewAnchor && + (RndStdIds::FLY_AS_CHAR == eOldAnchorId)) + { + //With AnchorAsCharacter the current TextAttribute has to be deleted. + //Tbis removes the frame format too. + //To prevent this the connection between format and attribute has to be broken before. + const SwPosition *pPos = rOldAnchor.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + SAL_WARN_IF( !pTextNode->HasHints(), "sw.uno", "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( + nIdx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing Hint."); + SAL_WARN_IF( pHint->Which() != RES_TXTATR_FLYCNT, + "sw.uno", "Missing FlyInCnt-Hint." ); + SAL_WARN_IF( pHint->GetFlyCnt().GetFrameFormat() != pFlyFormat, + "sw.uno", "Wrong TextFlyCnt-Hint." ); + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()) + .SetFlyFormat(); + + //The connection is removed now the attribute can be deleted. + pTextNode->DeleteAttributes(RES_TXTATR_FLYCNT, nIdx); + } + else if( text::TextContentAnchorType_AT_PAGE != eNewAnchor && + (RndStdIds::FLY_AT_PAGE == eOldAnchorId)) + { + SwFormatAnchor aNewAnchor( dynamic_cast< const SwFormatAnchor& >( aSet.Get( RES_ANCHOR ) ) ); + //if the fly has been anchored at page then it needs to be connected + //to the content position + SwPaM aPam(pDoc->GetNodes().GetEndOfContent()); + if( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + Point aTmp( pObj->GetSnapRect().TopLeft() ); + pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aTmp, &aState ); + } + else + { + //without access to the layout the last node of the body will be used as anchor position + aPam.Move( fnMoveBackward, GoInDoc ); + } + //anchor position has to be inserted after the text attribute has been inserted + aNewAnchor.SetAnchor( aPam.GetPoint() ); + aSet.Put( aNewAnchor ); + pFormat->SetFormatAttr(aSet); + bSetAttr = false; + } + if( text::TextContentAnchorType_AS_CHARACTER == eNewAnchor && + (RndStdIds::FLY_AS_CHAR != eOldAnchorId)) + { + SwPaM aPam(pDoc->GetNodes().GetEndOfContent()); + if( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + Point aTmp( pObj->GetSnapRect().TopLeft() ); + pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aTmp, &aState ); + } + else + { + //without access to the layout the last node of the body will be used as anchor position + aPam.Move( fnMoveBackward, GoInDoc ); + } + //the RES_TXTATR_FLYCNT needs to be added now + SwTextNode *pNd = aPam.GetNode().GetTextNode(); + SAL_WARN_IF( !pNd, "sw.uno", "Cursor is not in a TextNode." ); + SwFormatFlyCnt aFormat( pFlyFormat ); + pNd->InsertItem(aFormat, + aPam.GetPoint()->nContent.GetIndex(), 0 ); + --aPam.GetPoint()->nContent; // InsertItem moved it + SwFormatAnchor aNewAnchor( + dynamic_cast<const SwFormatAnchor&>( + aSet.Get(RES_ANCHOR))); + aNewAnchor.SetAnchor( aPam.GetPoint() ); + aSet.Put( aNewAnchor ); + } + if( bSetAttr ) + pFormat->SetFormatAttr(aSet); + } + else + pFormat->SetFormatAttr(aSet); + } + // We have a pFormat and a pEntry as well: try to sync TextBox property. + SwTextBoxHelper::syncProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aValue); + } + else + { + SfxPoolItem* pItem = nullptr; + switch(pEntry->nWID) + { + case RES_ANCHOR: + pItem = pImpl->GetAnchor(true); + break; + case RES_HORI_ORIENT: + pItem = pImpl->GetHOrient(true); + break; + case RES_VERT_ORIENT: + pItem = pImpl->GetVOrient(true); + break; + case RES_LR_SPACE: + pItem = pImpl->GetLRSpace(true); + break; + case RES_UL_SPACE: + pItem = pImpl->GetULSpace(true); + break; + case RES_SURROUND: + pItem = pImpl->GetSurround(true); + break; + case FN_TEXT_RANGE: + if(auto tr = o3tl::tryAccess< + uno::Reference<text::XTextRange>>(aValue)) + { + uno::Reference< text::XTextRange > & rRange = pImpl->GetTextRange(); + rRange = *tr; + } + break; + case RES_OPAQUE : + pImpl->SetOpaque(*o3tl::doAccess<bool>(aValue)); + break; + // #i26791# + case RES_FOLLOW_TEXT_FLOW: + { + pItem = pImpl->GetFollowTextFlow( true ); + } + break; + // #i28701# + case RES_WRAP_INFLUENCE_ON_OBJPOS: + { + pItem = pImpl->GetWrapInfluenceOnObjPos( true ); + } + break; + // #i28749# + case FN_SHAPE_POSITION_LAYOUT_DIR : + { + sal_Int16 nPositionLayoutDir = 0; + aValue >>= nPositionLayoutDir; + pImpl->SetPositionLayoutDir( nPositionLayoutDir ); + } + break; + } + if(pItem) + pItem->PutValue(aValue, pEntry->nMemberId); + } + } + else + { + const uno::Type& rPSetType = + cppu::UnoType<beans::XPropertySet>::get(); + uno::Any aPSet = xShapeAgg->queryAggregation(rPSetType); + auto xPrSet = o3tl::tryAccess<uno::Reference<beans::XPropertySet>>( + aPSet); + if(!xPrSet) + throw uno::RuntimeException(); + // #i31698# - setting the caption point of a + // caption object doesn't have to change the object position. + // Thus, keep the position, before the caption point is set and + // restore it afterwards. + awt::Point aKeepedPosition( 0, 0 ); + if ( rPropertyName == "CaptionPoint" && getShapeType() == "com.sun.star.drawing.CaptionShape" ) + { + aKeepedPosition = getPosition(); + } + if( pFormat && pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + UnoActionContext aCtx(pFormat->GetDoc()); + (*xPrSet)->setPropertyValue(rPropertyName, aValue); + } + else + (*xPrSet)->setPropertyValue(rPropertyName, aValue); + + if (pFormat) + { + // We have a pFormat (but no pEntry): try to sync TextBox property. + SwTextBoxHelper::syncProperty(pFormat, rPropertyName, aValue); + } + + // #i31698# - restore object position, if caption point is set. + if ( rPropertyName == "CaptionPoint" && getShapeType() == "com.sun.star.drawing.CaptionShape" ) + { + setPosition( aKeepedPosition ); + } + } + } +} + +uno::Any SwXShape::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwFrameFormat* pFormat = GetFrameFormat(); + if(xShapeAgg.is()) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if(pEntry) + { + if(pFormat) + { + if(RES_OPAQUE == pEntry->nWID) + { + SvxShape* pSvxShape = GetSvxShape(); + OSL_ENSURE(pSvxShape, "No SvxShape found!"); + if(pSvxShape) + { + SdrObject* pObj = pSvxShape->GetSdrObject(); + // consider invisible layers + aRet <<= + ( pObj->GetLayer() != pFormat->GetDoc()->getIDocumentDrawModelAccess().GetHellId() && + pObj->GetLayer() != pFormat->GetDoc()->getIDocumentDrawModelAccess().GetInvisibleHellId() ); + } + } + else if(FN_ANCHOR_POSITION == pEntry->nWID) + { + SvxShape* pSvxShape = GetSvxShape(); + OSL_ENSURE(pSvxShape, "No SvxShape found!"); + if(pSvxShape) + { + SdrObject* pObj = pSvxShape->GetSdrObject(); + Point aPt = pObj->GetAnchorPos(); + awt::Point aPoint( convertTwipToMm100( aPt.X() ), + convertTwipToMm100( aPt.Y() ) ); + aRet <<= aPoint; + } + } + // #i26791# - special handling for FN_TEXT_RANGE + else if ( FN_TEXT_RANGE == pEntry->nWID ) + { + const SwFormatAnchor aAnchor = pFormat->GetAnchor(); + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + // return nothing, because property <TextRange> isn't + // valid for to-page anchored shapes + uno::Any aAny; + aRet = aAny; + } + else + { + if ( aAnchor.GetContentAnchor() ) + { + const uno::Reference< text::XTextRange > xTextRange + = SwXTextRange::CreateXTextRange( + *pFormat->GetDoc(), + *aAnchor.GetContentAnchor(), + nullptr ); + aRet <<= xTextRange; + } + else + { + // return nothing + uno::Any aAny; + aRet = aAny; + } + } + } + else if (pEntry->nWID == FN_TEXT_BOX) + { + bool bValue = SwTextBoxHelper::isTextBox(pFormat, RES_DRAWFRMFMT); + aRet <<= bValue; + } + else if (pEntry->nWID == RES_CHAIN) + { + switch (pEntry->nMemberId) + { + case MID_CHAIN_PREVNAME: + case MID_CHAIN_NEXTNAME: + case MID_CHAIN_NAME: + SwTextBoxHelper::getProperty(pFormat, pEntry->nWID, pEntry->nMemberId, aRet); + break; + } + } + // #i28749# + else if ( FN_SHAPE_TRANSFORMATION_IN_HORI_L2R == pEntry->nWID ) + { + // get property <::drawing::Shape::Transformation> + // without conversion to layout direction as below + aRet = _getPropAtAggrObj( "Transformation" ); + } + else if ( FN_SHAPE_POSITION_LAYOUT_DIR == pEntry->nWID ) + { + aRet <<= pFormat->GetPositionLayoutDir(); + } + // #i36248# + else if ( FN_SHAPE_STARTPOSITION_IN_HORI_L2R == pEntry->nWID ) + { + // get property <::drawing::Shape::StartPosition> + // without conversion to layout direction as below + aRet = _getPropAtAggrObj( "StartPosition" ); + } + else if ( FN_SHAPE_ENDPOSITION_IN_HORI_L2R == pEntry->nWID ) + { + // get property <::drawing::Shape::EndPosition> + // without conversion to layout direction as below + aRet = _getPropAtAggrObj( "EndPosition" ); + } + else if (pEntry->nWID == RES_FRM_SIZE && + (pEntry->nMemberId == MID_FRMSIZE_REL_HEIGHT || + pEntry->nMemberId == MID_FRMSIZE_REL_WIDTH || + pEntry->nMemberId == MID_FRMSIZE_REL_HEIGHT_RELATION || + pEntry->nMemberId == MID_FRMSIZE_REL_WIDTH_RELATION)) + { + SvxShape* pSvxShape = GetSvxShape(); + SAL_WARN_IF(!pSvxShape, "sw.uno", "No SvxShape found!"); + sal_Int16 nRet = 0; + if (pSvxShape) + { + SdrObject* pObj = pSvxShape->GetSdrObject(); + switch (pEntry->nMemberId) + { + case MID_FRMSIZE_REL_WIDTH: + if (pObj->GetRelativeWidth()) + nRet = *pObj->GetRelativeWidth() * 100; + break; + case MID_FRMSIZE_REL_HEIGHT: + if (pObj->GetRelativeHeight()) + nRet = *pObj->GetRelativeHeight() * 100; + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + nRet = pObj->GetRelativeWidthRelation(); + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + nRet = pObj->GetRelativeHeightRelation(); + break; + } + } + aRet <<= nRet; + } + else + { + const SwAttrSet& rSet = pFormat->GetAttrSet(); + m_pPropSet->getPropertyValue(*pEntry, rSet, aRet); + } + } + else + { + SfxPoolItem* pItem = nullptr; + switch(pEntry->nWID) + { + case RES_ANCHOR: + pItem = pImpl->GetAnchor(); + break; + case RES_HORI_ORIENT: + pItem = pImpl->GetHOrient(); + break; + case RES_VERT_ORIENT: + pItem = pImpl->GetVOrient(); + break; + case RES_LR_SPACE: + pItem = pImpl->GetLRSpace(); + break; + case RES_UL_SPACE: + pItem = pImpl->GetULSpace(); + break; + case RES_SURROUND: + pItem = pImpl->GetSurround(); + break; + case FN_TEXT_RANGE : + aRet <<= pImpl->GetTextRange(); + break; + case RES_OPAQUE : + aRet <<= pImpl->GetOpaque(); + break; + case FN_ANCHOR_POSITION : + { + aRet <<= awt::Point(); + } + break; + // #i26791# + case RES_FOLLOW_TEXT_FLOW : + { + pItem = pImpl->GetFollowTextFlow(); + } + break; + // #i28701# + case RES_WRAP_INFLUENCE_ON_OBJPOS: + { + pItem = pImpl->GetWrapInfluenceOnObjPos(); + } + break; + // #i28749# + case FN_SHAPE_TRANSFORMATION_IN_HORI_L2R: + { + // get property <::drawing::Shape::Transformation> + // without conversion to layout direction as below + aRet = _getPropAtAggrObj( "Transformation" ); + } + break; + case FN_SHAPE_POSITION_LAYOUT_DIR: + { + aRet <<= pImpl->GetPositionLayoutDir(); + } + break; + // #i36248# + case FN_SHAPE_STARTPOSITION_IN_HORI_L2R: + { + // get property <::drawing::Shape::StartPosition> + // without conversion to layout direction as below + aRet = _getPropAtAggrObj( "StartPosition" ); + } + break; + case FN_SHAPE_ENDPOSITION_IN_HORI_L2R: + { + // get property <::drawing::Shape::StartPosition> + // without conversion to layout direction as below + aRet = _getPropAtAggrObj( "EndPosition" ); + } + break; + } + if(pItem) + pItem->QueryValue(aRet, pEntry->nMemberId); + } + } + else + { + aRet = _getPropAtAggrObj( rPropertyName ); + + // #i31698# - convert the position (translation) + // of the drawing object in the transformation + if ( rPropertyName == "Transformation" ) + { + drawing::HomogenMatrix3 aMatrix; + aRet >>= aMatrix; + aRet <<= ConvertTransformationToLayoutDir( aMatrix ); + } + // #i36248# + else if ( rPropertyName == "StartPosition" ) + { + awt::Point aStartPos; + aRet >>= aStartPos; + // #i59051# + aRet <<= ConvertStartOrEndPosToLayoutDir( aStartPos ); + } + else if ( rPropertyName == "EndPosition" ) + { + awt::Point aEndPos; + aRet >>= aEndPos; + // #i59051# + aRet <<= ConvertStartOrEndPosToLayoutDir( aEndPos ); + } + // #i59051# + else if ( rPropertyName == "PolyPolygonBezier" ) + { + drawing::PolyPolygonBezierCoords aPath; + aRet >>= aPath; + aRet <<= ConvertPolyPolygonBezierToLayoutDir( aPath ); + } + else if (rPropertyName == "ZOrder") + { + // Convert the real draw page position to the logical one that ignores textboxes. + if (pFormat) + { + const SdrObject* pObj = pFormat->FindRealSdrObject(); + if (pObj) + { + bool bConvert = true; + if (SvxShape* pSvxShape = GetSvxShape()) + // In case of group shapes, pSvxShape points to the child shape, while pObj points to the outermost group shape. + if (pSvxShape->GetSdrObject() != pObj) + // Textboxes are not expected inside group shapes, so no conversion is necessary there. + bConvert = false; + if (bConvert) + { + aRet <<= SwTextBoxHelper::getOrdNum(pObj); + } + } + } + } + } + } + return aRet; +} + +uno::Any SwXShape::_getPropAtAggrObj( const OUString& _rPropertyName ) +{ + uno::Any aRet; + + const uno::Type& rPSetType = + cppu::UnoType<beans::XPropertySet>::get(); + uno::Any aPSet = xShapeAgg->queryAggregation(rPSetType); + auto xPrSet = o3tl::tryAccess<uno::Reference<beans::XPropertySet>>(aPSet); + if ( !xPrSet ) + { + throw uno::RuntimeException(); + } + aRet = (*xPrSet)->getPropertyValue( _rPropertyName ); + + return aRet; +} + +beans::PropertyState SwXShape::getPropertyState( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + uno::Sequence< OUString > aNames { rPropertyName }; + uno::Sequence< beans::PropertyState > aStates = getPropertyStates(aNames); + return aStates.getConstArray()[0]; +} + +uno::Sequence< beans::PropertyState > SwXShape::getPropertyStates( + const uno::Sequence< OUString >& aPropertyNames ) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + uno::Sequence< beans::PropertyState > aRet(aPropertyNames.getLength()); + if(!xShapeAgg.is()) + throw uno::RuntimeException(); + + SvxShape* pSvxShape = GetSvxShape(); + bool bGroupMember = false; + bool bFormControl = false; + SdrObject* pObject = pSvxShape ? pSvxShape->GetSdrObject() : nullptr; + if(pObject) + { + bGroupMember = pObject->getParentSdrObjectFromSdrObject() != nullptr; + bFormControl = pObject->GetObjInventor() == SdrInventor::FmForm; + } + const OUString* pNames = aPropertyNames.getConstArray(); + beans::PropertyState* pRet = aRet.getArray(); + uno::Reference< XPropertyState > xShapePrState; + for(sal_Int32 nProperty = 0; nProperty < aPropertyNames.getLength(); nProperty++) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( pNames[nProperty] ); + if(pEntry) + { + if(RES_OPAQUE == pEntry->nWID) + pRet[nProperty] = bFormControl ? + beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + else if(FN_ANCHOR_POSITION == pEntry->nWID) + pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; + else if(FN_TEXT_RANGE == pEntry->nWID) + pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; + else if(bGroupMember) + pRet[nProperty] = beans::PropertyState_DEFAULT_VALUE; + else if (pEntry->nWID == RES_FRM_SIZE && + (pEntry->nMemberId == MID_FRMSIZE_REL_HEIGHT_RELATION || + pEntry->nMemberId == MID_FRMSIZE_REL_WIDTH_RELATION)) + pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; + else if (pEntry->nWID == FN_TEXT_BOX) + { + // The TextBox property is set, if we can find a textbox for this shape. + if (pFormat && SwTextBoxHelper::isTextBox(pFormat, RES_DRAWFRMFMT)) + pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; + else + pRet[nProperty] = beans::PropertyState_DEFAULT_VALUE; + } + else if(pFormat) + { + const SwAttrSet& rSet = pFormat->GetAttrSet(); + SfxItemState eItemState = rSet.GetItemState(pEntry->nWID, false); + + if(SfxItemState::SET == eItemState) + pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; + else if(SfxItemState::DEFAULT == eItemState) + pRet[nProperty] = beans::PropertyState_DEFAULT_VALUE; + else + pRet[nProperty] = beans::PropertyState_AMBIGUOUS_VALUE; + } + else + { + SfxPoolItem* pItem = nullptr; + switch(pEntry->nWID) + { + case RES_ANCHOR: + pItem = pImpl->GetAnchor(); + break; + case RES_HORI_ORIENT: + pItem = pImpl->GetHOrient(); + break; + case RES_VERT_ORIENT: + pItem = pImpl->GetVOrient(); + break; + case RES_LR_SPACE: + pItem = pImpl->GetLRSpace(); + break; + case RES_UL_SPACE: + pItem = pImpl->GetULSpace(); + break; + case RES_SURROUND: + pItem = pImpl->GetSurround(); + break; + // #i28701# + case RES_WRAP_INFLUENCE_ON_OBJPOS: + { + pItem = pImpl->GetWrapInfluenceOnObjPos(); + } + break; + } + if(pItem) + pRet[nProperty] = beans::PropertyState_DIRECT_VALUE; + else + pRet[nProperty] = beans::PropertyState_DEFAULT_VALUE; + } + } + else + { + if(!xShapePrState.is()) + { + const uno::Type& rPStateType = cppu::UnoType<XPropertyState>::get(); + uno::Any aPState = xShapeAgg->queryAggregation(rPStateType); + auto ps = o3tl::tryAccess<uno::Reference<XPropertyState>>( + aPState); + if(!ps) + throw uno::RuntimeException(); + xShapePrState = *ps; + } + pRet[nProperty] = xShapePrState->getPropertyState(pNames[nProperty]); + } + } + + return aRet; +} + +void SwXShape::setPropertyToDefault( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(!xShapeAgg.is()) + throw uno::RuntimeException(); + + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if(pEntry) + { + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw uno::RuntimeException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if(pFormat) + { + const SfxItemSet& rSet = pFormat->GetAttrSet(); + SfxItemSet aSet(pFormat->GetDoc()->GetAttrPool(), {{pEntry->nWID, pEntry->nWID}}); + aSet.SetParent(&rSet); + aSet.ClearItem(pEntry->nWID); + pFormat->GetDoc()->SetAttr(aSet, *pFormat); + } + else + { + switch(pEntry->nWID) + { + case RES_ANCHOR: pImpl->RemoveAnchor(); break; + case RES_HORI_ORIENT: pImpl->RemoveHOrient(); break; + case RES_VERT_ORIENT: pImpl->RemoveVOrient(); break; + case RES_LR_SPACE: pImpl->RemoveLRSpace(); break; + case RES_UL_SPACE: pImpl->RemoveULSpace(); break; + case RES_SURROUND: pImpl->RemoveSurround();break; + case RES_OPAQUE : pImpl->SetOpaque(false); break; + case FN_TEXT_RANGE : + break; + // #i26791# + case RES_FOLLOW_TEXT_FLOW: + { + pImpl->RemoveFollowTextFlow(); + } + break; + // #i28701# + case RES_WRAP_INFLUENCE_ON_OBJPOS: + { + pImpl->RemoveWrapInfluenceOnObjPos(); + } + break; + } + } + } + else + { + const uno::Type& rPStateType = cppu::UnoType<XPropertyState>::get(); + uno::Any aPState = xShapeAgg->queryAggregation(rPStateType); + auto xShapePrState = o3tl::tryAccess<uno::Reference<XPropertyState>>( + aPState); + if(!xShapePrState) + throw uno::RuntimeException(); + (*xShapePrState)->setPropertyToDefault( rPropertyName ); + } + +} + +uno::Any SwXShape::getPropertyDefault( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + uno::Any aRet; + if(!xShapeAgg.is()) + throw uno::RuntimeException(); + + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if(pEntry) + { + if(!(pEntry->nWID < RES_FRMATR_END && pFormat)) + throw uno::RuntimeException(); + + const SfxPoolItem& rDefItem = + pFormat->GetDoc()->GetAttrPool().GetDefaultItem(pEntry->nWID); + rDefItem.QueryValue(aRet, pEntry->nMemberId); + + } + else + { + const uno::Type& rPStateType = cppu::UnoType<XPropertyState>::get(); + uno::Any aPState = xShapeAgg->queryAggregation(rPStateType); + auto xShapePrState = o3tl::tryAccess<uno::Reference<XPropertyState>>( + aPState); + if(!xShapePrState) + throw uno::RuntimeException(); + (*xShapePrState)->getPropertyDefault( rPropertyName ); + } + + return aRet; +} + +void SwXShape::addPropertyChangeListener( + const OUString& _propertyName, + const uno::Reference< beans::XPropertyChangeListener > & _listener ) +{ + if ( !xShapeAgg.is() ) + throw uno::RuntimeException("no shape aggregate", *this ); + + // must be handled by the aggregate + uno::Reference< beans::XPropertySet > xShapeProps; + if ( xShapeAgg->queryAggregation( cppu::UnoType<beans::XPropertySet>::get() ) >>= xShapeProps ) + xShapeProps->addPropertyChangeListener( _propertyName, _listener ); +} + +void SwXShape::removePropertyChangeListener( + const OUString& _propertyName, + const uno::Reference< beans::XPropertyChangeListener > & _listener) +{ + if ( !xShapeAgg.is() ) + throw uno::RuntimeException("no shape aggregate", *this ); + + // must be handled by the aggregate + uno::Reference< beans::XPropertySet > xShapeProps; + if ( xShapeAgg->queryAggregation( cppu::UnoType<beans::XPropertySet>::get() ) >>= xShapeProps ) + xShapeProps->removePropertyChangeListener( _propertyName, _listener ); +} + +void SwXShape::addVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/ ) +{ + OSL_FAIL("not implemented"); +} + +void SwXShape::removeVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXShape::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pFormat = nullptr; + EndListeningAll(); + } +} + +void SwXShape::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard aGuard; + + // get access to SwDoc + // (see also SwXTextRange::XTextRangeToSwPaM) + const SwDoc* pDoc = nullptr; + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + if(xRangeTunnel.is()) + { + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + SwXTextPortion* pPortion = nullptr; + SwXText* pText = nullptr; + SwXParagraph* pParagraph = nullptr; + + pRange = reinterpret_cast< SwXTextRange * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( SwXTextRange::getUnoTunnelId()) )); + pText = reinterpret_cast< SwXText * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( SwXText::getUnoTunnelId()) )); + pCursor = reinterpret_cast< OTextCursorHelper * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( OTextCursorHelper::getUnoTunnelId()) )); + pPortion = reinterpret_cast< SwXTextPortion * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( SwXTextPortion::getUnoTunnelId()) )); + pParagraph = reinterpret_cast< SwXParagraph * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( SwXParagraph::getUnoTunnelId( ) ) ) ); + + if (pRange) + pDoc = &pRange->GetDoc(); + else if (pText) + pDoc = pText->GetDoc(); + else if (pCursor) + pDoc = pCursor->GetDoc(); + else if (pPortion) + pDoc = pPortion->GetCursor().GetDoc(); + else if (pParagraph && pParagraph->GetTextNode()) + pDoc = pParagraph->GetTextNode()->GetDoc(); + + } + + if(!pDoc) + throw uno::RuntimeException(); + if (const SwDocShell* pDocSh = pDoc->GetDocShell()) + { + uno::Reference<frame::XModel> xModel = pDocSh->GetModel(); + uno::Reference< drawing::XDrawPageSupplier > xDPS(xModel, uno::UNO_QUERY); + if (xDPS.is()) + { + uno::Reference< drawing::XDrawPage > xDP( xDPS->getDrawPage() ); + if (xDP.is()) + { + uno::Any aPos; + aPos <<= xTextRange; + setPropertyValue("TextRange", aPos); + uno::Reference< drawing::XShape > xTemp( static_cast<cppu::OWeakObject*>(this), uno::UNO_QUERY ); + xDP->add( xTemp ); + } + } + } +} + +uno::Reference< text::XTextRange > SwXShape::getAnchor() +{ + SolarMutexGuard aGuard; + uno::Reference< text::XTextRange > aRef; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + // return an anchor for non-page bound frames + // and for page bound frames that have a page no == NULL and a content position + if ((rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PAGE) || + (rAnchor.GetContentAnchor() && !rAnchor.GetPageNum())) + { + const SwPosition &rPos = *(pFormat->GetAnchor().GetContentAnchor()); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { // ensure that SwXTextRange has SwIndex + aRef = SwXTextRange::CreateXTextRange(*pFormat->GetDoc(), SwPosition(rPos.nNode), nullptr); + } + else + { + aRef = SwXTextRange::CreateXTextRange(*pFormat->GetDoc(), rPos, nullptr); + } + } + } + else + aRef = pImpl->GetTextRange(); + return aRef; +} + +void SwXShape::dispose() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + // determine correct <SdrObject> + SvxShape* pSvxShape = GetSvxShape(); + SdrObject* pObj = pSvxShape ? pSvxShape->GetSdrObject() : nullptr; + // safety assertion: + // <pObj> must be the same as <pFormat->FindSdrObject()>, if <pObj> isn't + // a 'virtual' drawing object. + // correct assertion and refine it for safety reason. + OSL_ENSURE( !pObj || + dynamic_cast<const SwDrawVirtObj*>( pObj) != nullptr || + pObj->getParentSdrObjectFromSdrObject() || + pObj == pFormat->FindSdrObject(), + "<SwXShape::dispose(..) - different 'master' drawing objects!!" ); + // perform delete of draw frame format *not* + // for 'virtual' drawing objects. + // no delete of draw format for members + // of a group + if ( pObj && + dynamic_cast<const SwDrawVirtObj*>( pObj) == nullptr && + !pObj->getParentSdrObjectFromSdrObject() && + pObj->IsInserted() ) + { + if (pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + const SwPosition &rPos = *(pFormat->GetAnchor().GetContentAnchor()); + SwTextNode *pTextNode = rPos.nNode.GetNode().GetTextNode(); + const sal_Int32 nIdx = rPos.nContent.GetIndex(); + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx ); + } + else + pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + } + } + if(xShapeAgg.is()) + { + uno::Any aAgg(xShapeAgg->queryAggregation( cppu::UnoType<XComponent>::get())); + uno::Reference<XComponent> xComp; + aAgg >>= xComp; + if(xComp.is()) + xComp->dispose(); + } + if(m_pPage) + const_cast<SwFmDrawPage*>(m_pPage)->RemoveShape(this); + m_pPage = nullptr; +} + +void SwXShape::addEventListener( + const uno::Reference< lang::XEventListener > & aListener) +{ + SvxShape* pSvxShape = GetSvxShape(); + if(pSvxShape) + pSvxShape->addEventListener(aListener); +} + +void SwXShape::removeEventListener( + const uno::Reference< lang::XEventListener > & aListener) +{ + SvxShape* pSvxShape = GetSvxShape(); + if(pSvxShape) + pSvxShape->removeEventListener(aListener); +} + +OUString SwXShape::getImplementationName() +{ + return "SwXShape"; +} + +sal_Bool SwXShape::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXShape::getSupportedServiceNames() +{ + uno::Sequence< OUString > aSeq; + if (SvxShape* pSvxShape = GetSvxShape()) + aSeq = pSvxShape->getSupportedServiceNames(); + return comphelper::concatSequences( + aSeq, std::initializer_list<OUStringLiteral>{ "com.sun.star.drawing.Shape" }); +} + +SvxShape* SwXShape::GetSvxShape() +{ + if(xShapeAgg.is()) + return comphelper::getUnoTunnelImplementation<SvxShape>(xShapeAgg); + return nullptr; +} + +// #i31698# +// implementation of virtual methods from drawing::XShape +awt::Point SAL_CALL SwXShape::getPosition() +{ + awt::Point aPos( GetAttrPosition() ); + + // handle group members + SvxShape* pSvxShape = GetSvxShape(); + if ( pSvxShape ) + { + SdrObject* pTopGroupObj = GetTopGroupObj( pSvxShape ); + if ( pTopGroupObj ) + { + // #i34750# - get attribute position of top group + // shape and add offset between top group object and group member + uno::Reference< drawing::XShape > xGroupShape( pTopGroupObj->getUnoShape(), uno::UNO_QUERY ); + aPos = xGroupShape->getPosition(); + // add offset between top group object and group member + // to the determined attribute position + // #i34750#: + // consider the layout direction + const tools::Rectangle aMemberObjRect = GetSvxShape()->GetSdrObject()->GetSnapRect(); + const tools::Rectangle aGroupObjRect = pTopGroupObj->GetSnapRect(); + // #i53320# - relative position of group member and + // top group object is always given in horizontal left-to-right layout. + awt::Point aOffset( 0, 0 ); + { + aOffset.X = ( aMemberObjRect.Left() - aGroupObjRect.Left() ); + aOffset.Y = ( aMemberObjRect.Top() - aGroupObjRect.Top() ); + } + aOffset.X = convertTwipToMm100(aOffset.X); + aOffset.Y = convertTwipToMm100(aOffset.Y); + aPos.X += aOffset.X; + aPos.Y += aOffset.Y; + } + } + + return aPos; +} + +void SAL_CALL SwXShape::setPosition( const awt::Point& aPosition ) +{ + SdrObject* pTopGroupObj = GetTopGroupObj(); + if ( !pTopGroupObj ) + { + // #i37877# - no adjustment of position attributes, + // if the position also has to be applied at the drawing object and + // a contact object is already registered at the drawing object. + bool bApplyPosAtDrawObj(false); + bool bNoAdjustOfPosProp(false); + // #i35798# - apply position also to drawing object, + // if drawing object has no anchor position set. + if ( mxShape.is() ) + { + SvxShape* pSvxShape = GetSvxShape(); + if ( pSvxShape ) + { + const SdrObject* pObj = pSvxShape->GetSdrObject(); + if ( pObj && + pObj->GetAnchorPos().X() == 0 && + pObj->GetAnchorPos().Y() == 0 ) + { + bApplyPosAtDrawObj = true; + if ( pObj->GetUserCall() && + dynamic_cast<const SwDrawContact*>( pObj->GetUserCall()) != nullptr ) + { + bNoAdjustOfPosProp = true; + } + } + } + } + // shape isn't a group member. Thus, set positioning attributes + if ( !bNoAdjustOfPosProp ) + { + AdjustPositionProperties( aPosition ); + } + if ( bApplyPosAtDrawObj ) + { + mxShape->setPosition( aPosition ); + } + } + else if ( mxShape.is() ) + { + // shape is a member of a group. Thus, set its position. + awt::Point aNewPos( aPosition ); + // The given position is given in the according layout direction. Thus, + // it has to be converted to a position in horizontal left-to-right + // layout. + // convert given absolute attribute position in layout direction into + // position in horizontal left-to-right layout. + { + aNewPos = ConvertPositionToHoriL2R( aNewPos, getSize() ); + } + // Convert given absolute position in horizontal left-to-right + // layout into relative position in horizontal left-to-right layout. + uno::Reference< drawing::XShape > xGroupShape( pTopGroupObj->getUnoShape(), uno::UNO_QUERY ); + { + // #i34750# + // use method <xGroupShape->getPosition()> to get the correct + // position of the top group object. + awt::Point aAttrPosInHoriL2R( + ConvertPositionToHoriL2R( xGroupShape->getPosition(), + xGroupShape->getSize() ) ); + aNewPos.X = o3tl::saturating_sub(aNewPos.X, aAttrPosInHoriL2R.X); + aNewPos.Y = o3tl::saturating_sub(aNewPos.Y, aAttrPosInHoriL2R.Y); + } + // convert relative position in horizontal left-to-right layout into + // absolute position in horizontal left-to-right layout + { + // #i34750# + // use method <SvxShape->getPosition()> to get the correct + // 'Drawing layer' position of the top group shape. + auto pSvxGroupShape = comphelper::getUnoTunnelImplementation<SvxShape>(pTopGroupObj->getUnoShape()); + const awt::Point aGroupPos = pSvxGroupShape->getPosition(); + aNewPos.X = o3tl::saturating_add(aNewPos.X, aGroupPos.X); + aNewPos.Y = o3tl::saturating_add(aNewPos.Y, aGroupPos.Y); + } + // set position + mxShape->setPosition( aNewPos ); + } +} + +awt::Size SAL_CALL SwXShape::getSize() +{ + awt::Size aSize; + if ( mxShape.is() ) + { + aSize = mxShape->getSize(); + } + return aSize; +} + +void SAL_CALL SwXShape::setSize( const awt::Size& aSize ) +{ + comphelper::ProfileZone aZone("SwXShape::setSize"); + + if ( mxShape.is() ) + { + mxShape->setSize( aSize ); + } + SwTextBoxHelper::syncProperty(GetFrameFormat(), RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::makeAny(aSize)); +} +// #i31698# +// implementation of virtual methods from drawing::XShapeDescriptor +OUString SAL_CALL SwXShape::getShapeType() +{ + if ( mxShape.is() ) + { + return mxShape->getShapeType(); + } + return OUString(); +} +/** method to determine top group object + #i31698# +*/ +SdrObject* SwXShape::GetTopGroupObj( SvxShape* _pSvxShape ) +{ + SdrObject* pTopGroupObj( nullptr ); + + SvxShape* pSvxShape = _pSvxShape ? _pSvxShape : GetSvxShape(); + if ( pSvxShape ) + { + SdrObject* pSdrObj = pSvxShape->GetSdrObject(); + if ( pSdrObj && pSdrObj->getParentSdrObjectFromSdrObject() ) + { + pTopGroupObj = pSdrObj->getParentSdrObjectFromSdrObject(); + while ( pTopGroupObj->getParentSdrObjectFromSdrObject() ) + { + pTopGroupObj = pTopGroupObj->getParentSdrObjectFromSdrObject(); + } + } + } + + return pTopGroupObj; +} + +/** method to determine position according to the positioning attributes + #i31698# +*/ +awt::Point SwXShape::GetAttrPosition() +{ + awt::Point aAttrPos; + + uno::Any aHoriPos( getPropertyValue("HoriOrientPosition") ); + aHoriPos >>= aAttrPos.X; + uno::Any aVertPos( getPropertyValue("VertOrientPosition") ); + aVertPos >>= aAttrPos.Y; + // #i35798# - fallback, if attribute position is (0,0) + // and no anchor position is applied to the drawing object + SvxShape* pSvxShape = GetSvxShape(); + if ( pSvxShape ) + { + const SdrObject* pObj = pSvxShape->GetSdrObject(); + if ( pObj && + pObj->GetAnchorPos().X() == 0 && + pObj->GetAnchorPos().Y() == 0 && + aAttrPos.X == 0 && aAttrPos.Y == 0 ) + { + const tools::Rectangle aObjRect = pObj->GetSnapRect(); + aAttrPos.X = convertTwipToMm100(aObjRect.Left()); + aAttrPos.Y = convertTwipToMm100(aObjRect.Top()); + } + } + // #i35007# - If drawing object is anchored as-character, + // it's x-position isn't sensible. Thus, return the x-position as zero in this case. + text::TextContentAnchorType eTextAnchorType = + text::TextContentAnchorType_AT_PARAGRAPH; + { + uno::Any aAny = getPropertyValue( "AnchorType" ); + aAny >>= eTextAnchorType; + } + if ( eTextAnchorType == text::TextContentAnchorType_AS_CHARACTER ) + { + aAttrPos.X = 0; + } + + return aAttrPos; +} + +/** method to convert the position (translation) of the drawing object to + the layout direction horizontal left-to-right. + #i31698# +*/ +awt::Point SwXShape::ConvertPositionToHoriL2R( const awt::Point& rObjPos, + const awt::Size& rObjSize ) +{ + awt::Point aObjPosInHoriL2R( rObjPos ); + + SwFrameFormat* pFrameFormat = GetFrameFormat(); + if ( pFrameFormat ) + { + SwFrameFormat::tLayoutDir eLayoutDir = pFrameFormat->GetLayoutDir(); + switch ( eLayoutDir ) + { + case SwFrameFormat::HORI_L2R: + { + // nothing to do + } + break; + case SwFrameFormat::HORI_R2L: + { + aObjPosInHoriL2R.X = -rObjPos.X - rObjSize.Width; + } + break; + case SwFrameFormat::VERT_R2L: + { + aObjPosInHoriL2R.X = -rObjPos.Y - rObjSize.Width; + aObjPosInHoriL2R.Y = rObjPos.X; + } + break; + default: + { + OSL_FAIL( "<SwXShape::ConvertPositionToHoriL2R(..)> - unsupported layout direction" ); + } + } + } + + return aObjPosInHoriL2R; +} + +/** method to convert the transformation of the drawing object to the layout + direction, the drawing object is in + #i31698# +*/ +drawing::HomogenMatrix3 SwXShape::ConvertTransformationToLayoutDir( + const drawing::HomogenMatrix3& rMatrixInHoriL2R ) +{ + drawing::HomogenMatrix3 aMatrix(rMatrixInHoriL2R); + + // #i44334#, #i44681# - direct manipulation of the + // transformation structure isn't valid, if it contains rotation. + SvxShape* pSvxShape = GetSvxShape(); + OSL_ENSURE( pSvxShape, + "<SwXShape::ConvertTransformationToLayoutDir(..)> - no SvxShape found!"); + if ( pSvxShape ) + { + const SdrObject* pObj = pSvxShape->GetSdrObject(); + OSL_ENSURE( pObj, + "<SwXShape::ConvertTransformationToLayoutDir(..)> - no SdrObject found!"); + if ( pObj ) + { + // get position of object in Writer coordinate system. + awt::Point aPos( getPosition() ); + // get position of object in Drawing layer coordinate system + const Point aTmpObjPos( pObj->GetSnapRect().TopLeft() ); + const awt::Point aObjPos( + convertTwipToMm100( aTmpObjPos.X() - pObj->GetAnchorPos().X() ), + convertTwipToMm100( aTmpObjPos.Y() - pObj->GetAnchorPos().Y() ) ); + // determine difference between these positions according to the + // Writer coordinate system + const awt::Point aTranslateDiff( aPos.X - aObjPos.X, + aPos.Y - aObjPos.Y ); + // apply translation difference to transformation matrix. + if ( aTranslateDiff.X != 0 || aTranslateDiff.Y != 0 ) + { + // #i73079# - use correct matrix type + ::basegfx::B2DHomMatrix aTempMatrix; + + aTempMatrix.set(0, 0, aMatrix.Line1.Column1 ); + aTempMatrix.set(0, 1, aMatrix.Line1.Column2 ); + aTempMatrix.set(0, 2, aMatrix.Line1.Column3 ); + aTempMatrix.set(1, 0, aMatrix.Line2.Column1 ); + aTempMatrix.set(1, 1, aMatrix.Line2.Column2 ); + aTempMatrix.set(1, 2, aMatrix.Line2.Column3 ); + aTempMatrix.set(2, 0, aMatrix.Line3.Column1 ); + aTempMatrix.set(2, 1, aMatrix.Line3.Column2 ); + aTempMatrix.set(2, 2, aMatrix.Line3.Column3 ); + // #i73079# + aTempMatrix.translate( aTranslateDiff.X, aTranslateDiff.Y ); + aMatrix.Line1.Column1 = aTempMatrix.get(0, 0); + aMatrix.Line1.Column2 = aTempMatrix.get(0, 1); + aMatrix.Line1.Column3 = aTempMatrix.get(0, 2); + aMatrix.Line2.Column1 = aTempMatrix.get(1, 0); + aMatrix.Line2.Column2 = aTempMatrix.get(1, 1); + aMatrix.Line2.Column3 = aTempMatrix.get(1, 2); + aMatrix.Line3.Column1 = aTempMatrix.get(2, 0); + aMatrix.Line3.Column2 = aTempMatrix.get(2, 1); + aMatrix.Line3.Column3 = aTempMatrix.get(2, 2); + } + } + } + + return aMatrix; +} + +/** method to adjust the positioning properties + #i31698# +*/ +void SwXShape::AdjustPositionProperties( const awt::Point& rPosition ) +{ + // handle x-position + // #i35007# - no handling of x-position, if drawing + // object is anchored as-character, because it doesn't make sense. + text::TextContentAnchorType eTextAnchorType = + text::TextContentAnchorType_AT_PARAGRAPH; + { + uno::Any aAny = getPropertyValue( "AnchorType" ); + aAny >>= eTextAnchorType; + } + if ( eTextAnchorType != text::TextContentAnchorType_AS_CHARACTER ) + { + // determine current x-position + const OUString aHoriPosPropStr("HoriOrientPosition"); + uno::Any aHoriPos( getPropertyValue( aHoriPosPropStr ) ); + sal_Int32 dCurrX = 0; + aHoriPos >>= dCurrX; + // change x-position attribute, if needed + if ( dCurrX != rPosition.X ) + { + // adjust x-position orientation to text::HoriOrientation::NONE, if needed + // Note: has to be done before setting x-position attribute + const OUString aHoriOrientPropStr("HoriOrient"); + uno::Any aHoriOrient( getPropertyValue( aHoriOrientPropStr ) ); + sal_Int16 eHoriOrient; + if (aHoriOrient >>= eHoriOrient) // may be void + { + if ( eHoriOrient != text::HoriOrientation::NONE ) + { + eHoriOrient = text::HoriOrientation::NONE; + aHoriOrient <<= eHoriOrient; + setPropertyValue( aHoriOrientPropStr, aHoriOrient ); + } + } + // set x-position attribute + aHoriPos <<= rPosition.X; + setPropertyValue( aHoriPosPropStr, aHoriPos ); + } + } + + // handle y-position + { + // determine current y-position + const OUString aVertPosPropStr("VertOrientPosition"); + uno::Any aVertPos( getPropertyValue( aVertPosPropStr ) ); + sal_Int32 dCurrY = 0; + aVertPos >>= dCurrY; + // change y-position attribute, if needed + if ( dCurrY != rPosition.Y ) + { + // adjust y-position orientation to text::VertOrientation::NONE, if needed + // Note: has to be done before setting y-position attribute + const OUString aVertOrientPropStr("VertOrient"); + uno::Any aVertOrient( getPropertyValue( aVertOrientPropStr ) ); + sal_Int16 eVertOrient; + if (aVertOrient >>= eVertOrient) // may be void + { + if ( eVertOrient != text::VertOrientation::NONE ) + { + eVertOrient = text::VertOrientation::NONE; + aVertOrient <<= eVertOrient; + setPropertyValue( aVertOrientPropStr, aVertOrient ); + } + } + // set y-position attribute + aVertPos <<= rPosition.Y; + setPropertyValue( aVertPosPropStr, aVertPos ); + } + } +} + +/** method to convert start or end position of the drawing object to the + Writer specific position, which is the attribute position in layout direction + #i59051# +*/ +css::awt::Point SwXShape::ConvertStartOrEndPosToLayoutDir( + const css::awt::Point& aStartOrEndPos ) +{ + awt::Point aConvertedPos( aStartOrEndPos ); + + SvxShape* pSvxShape = GetSvxShape(); + OSL_ENSURE( pSvxShape, + "<SwXShape::ConvertStartOrEndPosToLayoutDir(..)> - no SvxShape found!"); + if ( pSvxShape ) + { + const SdrObject* pObj = pSvxShape->GetSdrObject(); + OSL_ENSURE( pObj, + "<SwXShape::ConvertStartOrEndPosToLayoutDir(..)> - no SdrObject found!"); + if ( pObj ) + { + // get position of object in Writer coordinate system. + awt::Point aPos( getPosition() ); + // get position of object in Drawing layer coordinate system + const Point aTmpObjPos( pObj->GetSnapRect().TopLeft() ); + const awt::Point aObjPos( + convertTwipToMm100( aTmpObjPos.X() - pObj->GetAnchorPos().X() ), + convertTwipToMm100( aTmpObjPos.Y() - pObj->GetAnchorPos().Y() ) ); + // determine difference between these positions according to the + // Writer coordinate system + const awt::Point aTranslateDiff( aPos.X - aObjPos.X, + aPos.Y - aObjPos.Y ); + // apply translation difference to transformation matrix. + if ( aTranslateDiff.X != 0 || aTranslateDiff.Y != 0 ) + { + aConvertedPos.X = aConvertedPos.X + aTranslateDiff.X; + aConvertedPos.Y = aConvertedPos.Y + aTranslateDiff.Y; + } + } + } + + return aConvertedPos; +} + +css::drawing::PolyPolygonBezierCoords SwXShape::ConvertPolyPolygonBezierToLayoutDir( + const css::drawing::PolyPolygonBezierCoords& aPath ) +{ + drawing::PolyPolygonBezierCoords aConvertedPath( aPath ); + + SvxShape* pSvxShape = GetSvxShape(); + OSL_ENSURE( pSvxShape, + "<SwXShape::ConvertStartOrEndPosToLayoutDir(..)> - no SvxShape found!"); + if ( pSvxShape ) + { + const SdrObject* pObj = pSvxShape->GetSdrObject(); + OSL_ENSURE( pObj, + "<SwXShape::ConvertStartOrEndPosToLayoutDir(..)> - no SdrObject found!"); + if ( pObj ) + { + // get position of object in Writer coordinate system. + awt::Point aPos( getPosition() ); + // get position of object in Drawing layer coordinate system + const Point aTmpObjPos( pObj->GetSnapRect().TopLeft() ); + const awt::Point aObjPos( + convertTwipToMm100( aTmpObjPos.X() - pObj->GetAnchorPos().X() ), + convertTwipToMm100( aTmpObjPos.Y() - pObj->GetAnchorPos().Y() ) ); + // determine difference between these positions according to the + // Writer coordinate system + const awt::Point aTranslateDiff( aPos.X - aObjPos.X, + aPos.Y - aObjPos.Y ); + // apply translation difference to PolyPolygonBezier. + if ( aTranslateDiff.X != 0 || aTranslateDiff.Y != 0 ) + { + const basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix( + aTranslateDiff.X, aTranslateDiff.Y)); + + for(drawing::PointSequence& rInnerSequence : aConvertedPath.Coordinates) + { + for(awt::Point& rPoint : rInnerSequence) + { + basegfx::B2DPoint aNewCoordinatePair(rPoint.X, rPoint.Y); + aNewCoordinatePair *= aMatrix; + rPoint.X = basegfx::fround(aNewCoordinatePair.getX()); + rPoint.Y = basegfx::fround(aNewCoordinatePair.getY()); + } + } + } + } + } + + return aConvertedPath; +} + +SwXGroupShape::SwXGroupShape(uno::Reference<XInterface> & xShape, + SwDoc const*const pDoc) + : SwXShape(xShape, pDoc) +{ +#if OSL_DEBUG_LEVEL > 0 + uno::Reference<XShapes> xShapes(xShapeAgg, uno::UNO_QUERY); + OSL_ENSURE(xShapes.is(), "no SvxShape found or shape is not a group shape"); +#endif +} + +SwXGroupShape::~SwXGroupShape() +{ +} + +uno::Any SwXGroupShape::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet; + if(rType == cppu::UnoType<XShapes>::get()) + aRet <<= uno::Reference<XShapes>(this); + else + aRet = SwXShape::queryInterface(rType); + return aRet; +} + +void SwXGroupShape::acquire( ) throw() +{ + SwXShape::acquire(); +} + +void SwXGroupShape::release( ) throw() +{ + SwXShape::release(); +} + +void SwXGroupShape::add( const uno::Reference< XShape >& xShape ) +{ + SolarMutexGuard aGuard; + SvxShape* pSvxShape = GetSvxShape(); + SwFrameFormat* pFormat = GetFrameFormat(); + if(!(pSvxShape && pFormat)) + throw uno::RuntimeException(); + + uno::Reference<XShapes> xShapes; + if( xShapeAgg.is() ) + { + const uno::Type& rType = cppu::UnoType<XShapes>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rType ); + aAgg >>= xShapes; + } + if(!xShapes.is()) + throw uno::RuntimeException(); + + xShapes->add(xShape); + + + uno::Reference<lang::XUnoTunnel> xTunnel(xShape, uno::UNO_QUERY); + SwXShape* pSwShape = nullptr; + if(xShape.is()) + pSwShape = reinterpret_cast< SwXShape * >( + sal::static_int_cast< sal_IntPtr >( xTunnel->getSomething(SwXShape::getUnoTunnelId()) )); + if(pSwShape && pSwShape->m_bDescriptor) + { + SvxShape* pAddShape = reinterpret_cast< SvxShape * >( + sal::static_int_cast< sal_IntPtr >( xTunnel->getSomething(SvxShape::getUnoTunnelId()) )); + if(pAddShape) + { + SdrObject* pObj = pAddShape->GetSdrObject(); + if(pObj) + { + SwDoc* pDoc = pFormat->GetDoc(); + // set layer of new drawing + // object to corresponding invisible layer. + if( SdrInventor::FmForm != pObj->GetObjInventor()) + { + pObj->SetLayer( pSwShape->pImpl->GetOpaque() + ? pDoc->getIDocumentDrawModelAccess().GetInvisibleHeavenId() + : pDoc->getIDocumentDrawModelAccess().GetInvisibleHellId() ); + } + else + { + pObj->SetLayer(pDoc->getIDocumentDrawModelAccess().GetInvisibleControlsId()); + } + } + } + pSwShape->m_bDescriptor = false; + //add the group member to the format of the group + SwFrameFormat* pShapeFormat = ::FindFrameFormat( pSvxShape->GetSdrObject() ); + + if(pShapeFormat) + pSwShape->SetFrameFormat(pShapeFormat); + } + +} + +void SwXGroupShape::remove( const uno::Reference< XShape >& xShape ) +{ + SolarMutexGuard aGuard; + uno::Reference<XShapes> xShapes; + if( xShapeAgg.is() ) + { + const uno::Type& rType = cppu::UnoType<XShapes>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rType ); + aAgg >>= xShapes; + } + if(!xShapes.is()) + throw uno::RuntimeException(); + xShapes->remove(xShape); +} + +sal_Int32 SwXGroupShape::getCount() +{ + SolarMutexGuard aGuard; + uno::Reference<XIndexAccess> xAcc; + if( xShapeAgg.is() ) + { + const uno::Type& rType = cppu::UnoType<XIndexAccess>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rType ); + aAgg >>= xAcc; + } + if(!xAcc.is()) + throw uno::RuntimeException(); + return xAcc->getCount(); +} + +uno::Any SwXGroupShape::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + uno::Reference<XIndexAccess> xAcc; + if( xShapeAgg.is() ) + { + const uno::Type& rType = cppu::UnoType<XIndexAccess>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rType ); + aAgg >>= xAcc; + } + if(!xAcc.is()) + throw uno::RuntimeException(); + return xAcc->getByIndex(nIndex); +} + +uno::Type SwXGroupShape::getElementType( ) +{ + SolarMutexGuard aGuard; + uno::Reference<XIndexAccess> xAcc; + if( xShapeAgg.is() ) + { + const uno::Type& rType = cppu::UnoType<XIndexAccess>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rType ); + aAgg >>= xAcc; + } + if(!xAcc.is()) + throw uno::RuntimeException(); + return xAcc->getElementType(); +} + +sal_Bool SwXGroupShape::hasElements( ) +{ + SolarMutexGuard aGuard; + uno::Reference<XIndexAccess> xAcc; + if( xShapeAgg.is() ) + { + const uno::Type& rType = cppu::UnoType<XIndexAccess>::get(); + uno::Any aAgg = xShapeAgg->queryAggregation( rType ); + aAgg >>= xAcc; + } + if(!xAcc.is()) + throw uno::RuntimeException(); + return xAcc->hasElements(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoevent.cxx b/sw/source/core/unocore/unoevent.cxx new file mode 100644 index 000000000..7ca58df40 --- /dev/null +++ b/sw/source/core/unocore/unoevent.cxx @@ -0,0 +1,233 @@ +/* -*- 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 . + */ + +// HINTIDs must be on top; it is required for the macitem.hxx header +#include <hintids.hxx> +#include <unoevent.hxx> +#include <unoframe.hxx> +#include <unostyle.hxx> +#include <fmtinfmt.hxx> +#include <svl/macitem.hxx> +#include <sfx2/event.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +using ::com::sun::star::container::XNameReplace; + +// tables of allowed events for specific objects + +const struct SvEventDescription aGraphicEvents[] = +{ + { SvMacroItemId::SwObjectSelect, "OnSelect" }, + { SvMacroItemId::OnMouseOver, "OnMouseOver" }, + { SvMacroItemId::OnClick, "OnClick" }, + { SvMacroItemId::OnMouseOut, "OnMouseOut" }, + { SvMacroItemId::OnImageLoadDone, "OnLoadDone" }, + { SvMacroItemId::OnImageLoadCancel, "OnLoadCancel" }, + { SvMacroItemId::OnImageLoadError, "OnLoadError" }, + { SvMacroItemId::NONE, nullptr } +}; + +const struct SvEventDescription aFrameEvents[] = +{ + { SvMacroItemId::SwObjectSelect, "OnSelect" }, + { SvMacroItemId::SwFrmKeyInputAlpha, "OnAlphaCharInput" }, + { SvMacroItemId::SwFrmKeyInputNoAlpha, "OnNonAlphaCharInput" }, + { SvMacroItemId::SwFrmResize, "OnResize" }, + { SvMacroItemId::SwFrmMove, "OnMove" }, + { SvMacroItemId::OnMouseOver, "OnMouseOver" }, + { SvMacroItemId::OnClick, "OnClick" }, + { SvMacroItemId::OnMouseOut, "OnMouseOut" }, + { SvMacroItemId::NONE, nullptr } +}; + +const struct SvEventDescription aOLEEvents[] = +{ + { SvMacroItemId::SwObjectSelect, "OnSelect" }, + { SvMacroItemId::OnMouseOver, "OnMouseOver" }, + { SvMacroItemId::OnClick, "OnClick" }, + { SvMacroItemId::OnMouseOut, "OnMouseOut" }, + { SvMacroItemId::NONE, nullptr } +}; + +const struct SvEventDescription aHyperlinkEvents[] = +{ + { SvMacroItemId::OnMouseOver, "OnMouseOver" }, + { SvMacroItemId::OnClick, "OnClick" }, + { SvMacroItemId::OnMouseOut, "OnMouseOut" }, + { SvMacroItemId::NONE, nullptr } +}; + +const struct SvEventDescription aFrameStyleEvents[] = +{ + { SvMacroItemId::SwObjectSelect, "OnSelect" }, + { SvMacroItemId::SwFrmKeyInputAlpha, "OnAlphaCharInput" }, + { SvMacroItemId::SwFrmKeyInputNoAlpha, "OnNonAlphaCharInput" }, + { SvMacroItemId::SwFrmResize, "OnResize" }, + { SvMacroItemId::SwFrmMove, "OnMove" }, + { SvMacroItemId::OnMouseOver, "OnMouseOver" }, + { SvMacroItemId::OnClick, "OnClick" }, + { SvMacroItemId::OnMouseOut, "OnMouseOut" }, + { SvMacroItemId::OnImageLoadDone, "OnLoadDone" }, + { SvMacroItemId::OnImageLoadCancel, "OnLoadCancel" }, + { SvMacroItemId::OnImageLoadError, "OnLoadError" }, + { SvMacroItemId::NONE, nullptr } +}; + +SwHyperlinkEventDescriptor::SwHyperlinkEventDescriptor() : + SvDetachedEventDescriptor(aHyperlinkEvents) +{ +} + +SwHyperlinkEventDescriptor::~SwHyperlinkEventDescriptor() +{ +} + +OUString SwHyperlinkEventDescriptor::getImplementationName() +{ + return "SwHyperlinkEventDescriptor"; +} + +void SwHyperlinkEventDescriptor::copyMacrosFromINetFormat( + const SwFormatINetFormat& aFormat) +{ + for(sal_uInt16 i = 0; mpSupportedMacroItems[i].mnEvent != SvMacroItemId::NONE; ++i) + { + const SvMacroItemId nEvent = mpSupportedMacroItems[i].mnEvent; + const SvxMacro* aMacro = aFormat.GetMacro(nEvent); + if (nullptr != aMacro) + replaceByName(nEvent, *aMacro); + } +} + +void SwHyperlinkEventDescriptor::copyMacrosIntoINetFormat( + SwFormatINetFormat& aFormat) +{ + for(sal_uInt16 i = 0; mpSupportedMacroItems[i].mnEvent != SvMacroItemId::NONE; ++i) + { + const SvMacroItemId nEvent = mpSupportedMacroItems[i].mnEvent; + if (hasById(nEvent)) + { + SvxMacro aMacro("", ""); + getByName(aMacro, nEvent); + aFormat.SetMacro(nEvent, aMacro); + } + } +} + +void SwHyperlinkEventDescriptor::copyMacrosFromNameReplace( + uno::Reference< + container::XNameReplace> const & xReplace) +{ + // iterate over all names (all names that *we* support) + const Sequence<OUString> aNames = getElementNames(); + for(const OUString& rName : aNames) + { + // copy element for that name + if (xReplace->hasByName(rName)) + { + SvBaseEventDescriptor::replaceByName(rName, + xReplace->getByName(rName)); + } + } +} + +// use double cast in superclass constructor to avoid ambiguous cast +SwFrameEventDescriptor::SwFrameEventDescriptor( + SwXTextFrame& rFrameRef ) : + SvEventDescriptor(static_cast<text::XTextFrame&>(rFrameRef), aFrameEvents), + rFrame(rFrameRef) +{ +} + +SwFrameEventDescriptor::SwFrameEventDescriptor( + SwXTextGraphicObject& rGraphicRef ) : + SvEventDescriptor(static_cast<text::XTextContent&>(rGraphicRef), aGraphicEvents), + rFrame(static_cast<SwXFrame&>(rGraphicRef)) +{ +} + +SwFrameEventDescriptor::SwFrameEventDescriptor( + SwXTextEmbeddedObject& rObjectRef ) : + SvEventDescriptor(static_cast<text::XTextContent&>(rObjectRef), aOLEEvents), + rFrame(static_cast<SwXFrame&>(rObjectRef)) +{ +} + +SwFrameEventDescriptor::~SwFrameEventDescriptor() +{ +} + +void SwFrameEventDescriptor::setMacroItem(const SvxMacroItem& rItem) +{ + rFrame.GetFrameFormat()->SetFormatAttr(rItem); +} + +const SvxMacroItem& SwFrameEventDescriptor::getMacroItem() +{ + return rFrame.GetFrameFormat()->GetFormatAttr(RES_FRMMACRO); +} + +sal_uInt16 SwFrameEventDescriptor::getMacroItemWhich() const +{ + return RES_FRMMACRO; +} + +OUString SwFrameEventDescriptor::getImplementationName() +{ + return "SwFrameEventDescriptor"; +} + +SwFrameStyleEventDescriptor::SwFrameStyleEventDescriptor( + sw::ICoreFrameStyle& rStyle ) : + SvEventDescriptor(rStyle.GetEventsSupplier(), + aFrameStyleEvents), + m_rStyle(rStyle) +{ +} + +SwFrameStyleEventDescriptor::~SwFrameStyleEventDescriptor() +{ +} + +void SwFrameStyleEventDescriptor::setMacroItem(const SvxMacroItem& rItem) +{ + m_rStyle.SetItem(RES_FRMMACRO, rItem); +} + +static const SvxMacroItem aEmptyMacroItem(RES_FRMMACRO); + +const SvxMacroItem& SwFrameStyleEventDescriptor::getMacroItem() +{ + const SfxPoolItem* pItem(m_rStyle.GetItem(RES_FRMMACRO)); + return pItem ? static_cast<const SvxMacroItem&>(*pItem) : aEmptyMacroItem; +} + +OUString SwFrameStyleEventDescriptor::getImplementationName() +{ + return "SwFrameStyleEventDescriptor"; +} + +sal_uInt16 SwFrameStyleEventDescriptor::getMacroItemWhich() const +{ + return RES_FRMMACRO; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unofield.cxx b/sw/source/core/unocore/unofield.cxx new file mode 100644 index 000000000..d6c9e5a92 --- /dev/null +++ b/sw/source/core/unocore/unofield.cxx @@ -0,0 +1,3034 @@ +/* -*- 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 <sal/config.h> +#include <algorithm> +#include <memory> + +#include <unofield.hxx> +#include <unofieldcoll.hxx> +#include <swtypes.hxx> +#include <cmdid.h> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentState.hxx> +#include <hints.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <ndtxt.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unocoll.hxx> +#include <sfx2/linkmgr.hxx> +#include <editsh.hxx> +#include <viewsh.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +//undef to prevent error (from sfx2/docfile.cxx) +#undef SEQUENCE +#include <com/sun/star/text/SetVariableType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <authfld.hxx> +#include <flddat.hxx> +#include <dbfld.hxx> +#include <usrfld.hxx> +#include <docufld.hxx> +#include <expfld.hxx> +#include <chpfld.hxx> +#include <flddropdown.hxx> +#include <poolfmt.hxx> +#include <strings.hrc> +#include <pagedesc.hxx> +#include <docary.hxx> +#include <reffld.hxx> +#include <ddefld.hxx> +#include <SwStyleNameMapper.hxx> +#include <swunohelper.hxx> +#include <unofldmid.h> +#include <scriptinfo.hxx> +#include <tools/datetime.hxx> +#include <tools/urlobj.hxx> +#include <svl/itemprop.hxx> +#include <svl/listener.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include <textapi.hxx> +#include <fmtmeta.hxx> +#include <vector> + +using namespace ::com::sun::star; +using namespace nsSwDocInfoSubType; + +// case-corrected version of the first part for the service names (see #i67811) +#define COM_TEXT_FLDMASTER_CC "com.sun.star.text.fieldmaster." + +// note: this thing is indexed as an array, so do not insert/remove entries! +static const sal_uInt16 aDocInfoSubTypeFromService[] = +{ + DI_CHANGE | DI_SUB_AUTHOR, //PROPERTY_MAP_FLDTYP_DOCINFO_CHANGE_AUTHOR + DI_CHANGE | DI_SUB_DATE, //PROPERTY_MAP_FLDTYP_DOCINFO_CHANGE_DATE_TIME + DI_EDIT | DI_SUB_TIME, //PROPERTY_MAP_FLDTYP_DOCINFO_EDIT_TIME + DI_COMMENT, //PROPERTY_MAP_FLDTYP_DOCINFO_DESCRIPTION + DI_CREATE | DI_SUB_AUTHOR, //PROPERTY_MAP_FLDTYP_DOCINFO_CREATE_AUTHOR + DI_CREATE | DI_SUB_DATE, //PROPERTY_MAP_FLDTYP_DOCINFO_CREATE_DATE_TIME + 0, //DUMMY + 0, //DUMMY + 0, //DUMMY + 0, //DUMMY + DI_CUSTOM, //PROPERTY_MAP_FLDTYP_DOCINFO_CUSTOM + DI_PRINT | DI_SUB_AUTHOR, //PROPERTY_MAP_FLDTYP_DOCINFO_PRINT_AUTHOR + DI_PRINT | DI_SUB_DATE, //PROPERTY_MAP_FLDTYP_DOCINFO_PRINT_DATE_TIME + DI_KEYS, //PROPERTY_MAP_FLDTYP_DOCINFO_KEY_WORDS + DI_SUBJECT, //PROPERTY_MAP_FLDTYP_DOCINFO_SUBJECT + DI_TITLE, //PROPERTY_MAP_FLDTYP_DOCINFO_TITLE + DI_DOCNO //PROPERTY_MAP_FLDTYP_DOCINFO_REVISION +}; + +namespace { + +struct ServiceIdResId +{ + SwFieldIds nResId; + SwServiceType nServiceId; +}; + +} + +static const ServiceIdResId aServiceToRes[] = +{ + {SwFieldIds::DateTime, SwServiceType::FieldTypeDateTime }, + {SwFieldIds::User, SwServiceType::FieldTypeUser }, + {SwFieldIds::SetExp, SwServiceType::FieldTypeSetExp }, + {SwFieldIds::GetExp, SwServiceType::FieldTypeGetExp }, + {SwFieldIds::Filename, SwServiceType::FieldTypeFileName }, + {SwFieldIds::PageNumber, SwServiceType::FieldTypePageNum }, + {SwFieldIds::Author, SwServiceType::FieldTypeAuthor }, + {SwFieldIds::Chapter, SwServiceType::FieldTypeChapter }, + {SwFieldIds::GetRef, SwServiceType::FieldTypeGetReference }, + {SwFieldIds::HiddenText, SwServiceType::FieldTypeConditionedText }, + {SwFieldIds::Postit, SwServiceType::FieldTypeAnnotation }, + {SwFieldIds::Input, SwServiceType::FieldTypeInput }, + {SwFieldIds::Macro, SwServiceType::FieldTypeMacro }, + {SwFieldIds::Dde, SwServiceType::FieldTypeDDE }, + {SwFieldIds::HiddenPara, SwServiceType::FieldTypeHiddenPara }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfo }, + {SwFieldIds::TemplateName, SwServiceType::FieldTypeTemplateName }, + {SwFieldIds::ExtUser, SwServiceType::FieldTypeUserExt }, + {SwFieldIds::RefPageSet, SwServiceType::FieldTypeRefPageSet }, + {SwFieldIds::RefPageGet, SwServiceType::FieldTypeRefPageGet }, + {SwFieldIds::JumpEdit, SwServiceType::FieldTypeJumpEdit }, + {SwFieldIds::Script, SwServiceType::FieldTypeScript }, + {SwFieldIds::DbNextSet, SwServiceType::FieldTypeDatabaseNextSet }, + {SwFieldIds::DbNumSet, SwServiceType::FieldTypeDatabaseNumSet }, + {SwFieldIds::DbSetNumber, SwServiceType::FieldTypeDatabaseSetNum }, + {SwFieldIds::Database, SwServiceType::FieldTypeDatabase }, + {SwFieldIds::DatabaseName, SwServiceType::FieldTypeDatabaseName }, + {SwFieldIds::DocStat, SwServiceType::FieldTypePageCount }, + {SwFieldIds::DocStat, SwServiceType::FieldTypeParagraphCount }, + {SwFieldIds::DocStat, SwServiceType::FieldTypeWordCount }, + {SwFieldIds::DocStat, SwServiceType::FieldTypeCharacterCount }, + {SwFieldIds::DocStat, SwServiceType::FieldTypeTableCount }, + {SwFieldIds::DocStat, SwServiceType::FieldTypeGraphicObjectCount }, + {SwFieldIds::DocStat, SwServiceType::FieldTypeEmbeddedObjectCount }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoChangeAuthor }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoChangeDateTime }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoEditTime }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoDescription }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoCreateAuthor }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoCreateDateTime }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoCustom }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoPrintAuthor }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoPrintDateTime }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoKeywords }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoSubject }, + {SwFieldIds::DocInfo, SwServiceType::FieldTypeDocInfoTitle }, + {SwFieldIds::Input, SwServiceType::FieldTypeInputUser }, + {SwFieldIds::HiddenText, SwServiceType::FieldTypeHiddenText }, + {SwFieldIds::TableOfAuthorities, SwServiceType::FieldTypeBibliography }, + {SwFieldIds::CombinedChars, SwServiceType::FieldTypeCombinedCharacters }, + {SwFieldIds::Dropdown, SwServiceType::FieldTypeDropdown }, + {SwFieldIds::Table, SwServiceType::FieldTypeTableFormula } +}; + +static SwFieldIds lcl_ServiceIdToResId(SwServiceType nServiceId) +{ + for (size_t i=0; i<SAL_N_ELEMENTS(aServiceToRes); ++i) + if (aServiceToRes[i].nServiceId == nServiceId) + return aServiceToRes[i].nResId; +#if OSL_DEBUG_LEVEL > 0 + OSL_FAIL("service id not found"); +#endif + return SwFieldIds::Unknown; +} + +static SwServiceType lcl_GetServiceForField( const SwField& rField ) +{ + const SwFieldIds nWhich = rField.Which(); + SwServiceType nSrvId = SwServiceType::Invalid; + //special handling for some fields + switch( nWhich ) + { + case SwFieldIds::Input: + if( INP_USR == (rField.GetSubType() & 0x00ff) ) + nSrvId = SwServiceType::FieldTypeInputUser; + break; + + case SwFieldIds::DocInfo: + { + const sal_uInt16 nSubType = rField.GetSubType(); + switch( nSubType & 0xff ) + { + case DI_CHANGE: + nSrvId = ((nSubType&0x300) == DI_SUB_AUTHOR) + ? SwServiceType::FieldTypeDocInfoChangeAuthor + : SwServiceType::FieldTypeDocInfoChangeDateTime; + break; + case DI_CREATE: + nSrvId = ((nSubType&0x300) == DI_SUB_AUTHOR) + ? SwServiceType::FieldTypeDocInfoCreateAuthor + : SwServiceType::FieldTypeDocInfoCreateDateTime; + break; + case DI_PRINT: + nSrvId = ((nSubType&0x300) == DI_SUB_AUTHOR) + ? SwServiceType::FieldTypeDocInfoPrintAuthor + : SwServiceType::FieldTypeDocInfoPrintDateTime; + break; + case DI_EDIT: nSrvId = SwServiceType::FieldTypeDocInfoEditTime;break; + case DI_COMMENT:nSrvId = SwServiceType::FieldTypeDocInfoDescription;break; + case DI_KEYS: nSrvId = SwServiceType::FieldTypeDocInfoKeywords;break; + case DI_SUBJECT:nSrvId = SwServiceType::FieldTypeDocInfoSubject; break; + case DI_TITLE: nSrvId = SwServiceType::FieldTypeDocInfoTitle; break; + case DI_DOCNO: nSrvId = SwServiceType::FieldTypeDocInfoRevision; break; + case DI_CUSTOM: nSrvId = SwServiceType::FieldTypeDocInfoCustom; break; + } + } + break; + + case SwFieldIds::HiddenText: + nSrvId = SwFieldTypesEnum::ConditionalText == static_cast<SwFieldTypesEnum>(rField.GetSubType()) + ? SwServiceType::FieldTypeConditionedText + : SwServiceType::FieldTypeHiddenText; + break; + + case SwFieldIds::DocStat: + { + switch( rField.GetSubType() ) + { + case DS_PAGE: nSrvId = SwServiceType::FieldTypePageCount; break; + case DS_PARA: nSrvId = SwServiceType::FieldTypeParagraphCount; break; + case DS_WORD: nSrvId = SwServiceType::FieldTypeWordCount ; break; + case DS_CHAR: nSrvId = SwServiceType::FieldTypeCharacterCount; break; + case DS_TBL: nSrvId = SwServiceType::FieldTypeTableCount ; break; + case DS_GRF: nSrvId = SwServiceType::FieldTypeGraphicObjectCount; break; + case DS_OLE: nSrvId = SwServiceType::FieldTypeEmbeddedObjectCount; break; + } + } + break; + default: break; + } + if( SwServiceType::Invalid == nSrvId ) + { + for( const ServiceIdResId* pMap = aServiceToRes; + SwFieldIds::Unknown != pMap->nResId; ++pMap ) + if( nWhich == pMap->nResId ) + { + nSrvId = pMap->nServiceId; + break; + } + } +#if OSL_DEBUG_LEVEL > 0 + if( SwServiceType::Invalid == nSrvId ) + OSL_FAIL("resid not found"); +#endif + return nSrvId; +} + +static sal_uInt16 lcl_GetPropMapIdForFieldType( SwFieldIds nWhich ) +{ + sal_uInt16 nId; + switch( nWhich ) + { + case SwFieldIds::User: nId = PROPERTY_MAP_FLDMSTR_USER; break; + case SwFieldIds::Database: nId = PROPERTY_MAP_FLDMSTR_DATABASE; break; + case SwFieldIds::SetExp: nId = PROPERTY_MAP_FLDMSTR_SET_EXP; break; + case SwFieldIds::Dde: nId = PROPERTY_MAP_FLDMSTR_DDE; break; + case SwFieldIds::TableOfAuthorities: + nId = PROPERTY_MAP_FLDMSTR_BIBLIOGRAPHY; break; + default: nId = PROPERTY_MAP_FLDMSTR_DUMMY0; + } + return nId; +} + +static sal_Int32 lcl_PropName2TokenPos(const OUString& rPropertyName) +{ + if (rPropertyName == UNO_NAME_DDE_COMMAND_TYPE) + return 0; + + if (rPropertyName == UNO_NAME_DDE_COMMAND_FILE) + return 1; + + if (rPropertyName == UNO_NAME_DDE_COMMAND_ELEMENT) + return 2; + + if (rPropertyName == UNO_NAME_IS_AUTOMATIC_UPDATE) + return 3; + + return SAL_MAX_INT32; +} + +static sal_uInt16 GetFieldTypeMId( const OUString& rProperty, const SwFieldType& rTyp ) +{ + sal_uInt16 nId = lcl_GetPropMapIdForFieldType( rTyp.Which() ); + const SfxItemPropertySet* pSet = aSwMapProvider.GetPropertySet( nId ); + if( !pSet ) + nId = USHRT_MAX; + else + { + const SfxItemPropertySimpleEntry* pEntry = pSet->getPropertyMap().getByName(rProperty); + nId = pEntry ? pEntry->nWID : USHRT_MAX; + } + return nId; +} + +static sal_uInt16 lcl_GetPropertyMapOfService( SwServiceType nServiceId ) +{ + sal_uInt16 nRet; + switch ( nServiceId) + { + case SwServiceType::FieldTypeDateTime: nRet = PROPERTY_MAP_FLDTYP_DATETIME; break; + case SwServiceType::FieldTypeUser: nRet = PROPERTY_MAP_FLDTYP_USER; break; + case SwServiceType::FieldTypeSetExp: nRet = PROPERTY_MAP_FLDTYP_SET_EXP; break; + case SwServiceType::FieldTypeGetExp: nRet = PROPERTY_MAP_FLDTYP_GET_EXP; break; + case SwServiceType::FieldTypeFileName: nRet = PROPERTY_MAP_FLDTYP_FILE_NAME; break; + case SwServiceType::FieldTypePageNum: nRet = PROPERTY_MAP_FLDTYP_PAGE_NUM; break; + case SwServiceType::FieldTypeAuthor: nRet = PROPERTY_MAP_FLDTYP_AUTHOR; break; + case SwServiceType::FieldTypeChapter: nRet = PROPERTY_MAP_FLDTYP_CHAPTER; break; + case SwServiceType::FieldTypeGetReference: nRet = PROPERTY_MAP_FLDTYP_GET_REFERENCE; break; + case SwServiceType::FieldTypeConditionedText: nRet = PROPERTY_MAP_FLDTYP_CONDITIONED_TEXT; break; + case SwServiceType::FieldTypeAnnotation: nRet = PROPERTY_MAP_FLDTYP_ANNOTATION; break; + case SwServiceType::FieldTypeInputUser: + case SwServiceType::FieldTypeInput: nRet = PROPERTY_MAP_FLDTYP_INPUT; break; + case SwServiceType::FieldTypeMacro: nRet = PROPERTY_MAP_FLDTYP_MACRO; break; + case SwServiceType::FieldTypeDDE: nRet = PROPERTY_MAP_FLDTYP_DDE; break; + case SwServiceType::FieldTypeHiddenPara: nRet = PROPERTY_MAP_FLDTYP_HIDDEN_PARA; break; + case SwServiceType::FieldTypeDocInfo: nRet = PROPERTY_MAP_FLDTYP_DOC_INFO; break; + case SwServiceType::FieldTypeTemplateName: nRet = PROPERTY_MAP_FLDTYP_TEMPLATE_NAME; break; + case SwServiceType::FieldTypeUserExt: nRet = PROPERTY_MAP_FLDTYP_USER_EXT; break; + case SwServiceType::FieldTypeRefPageSet: nRet = PROPERTY_MAP_FLDTYP_REF_PAGE_SET; break; + case SwServiceType::FieldTypeRefPageGet: nRet = PROPERTY_MAP_FLDTYP_REF_PAGE_GET; break; + case SwServiceType::FieldTypeJumpEdit: nRet = PROPERTY_MAP_FLDTYP_JUMP_EDIT; break; + case SwServiceType::FieldTypeScript: nRet = PROPERTY_MAP_FLDTYP_SCRIPT; break; + case SwServiceType::FieldTypeDatabaseNextSet: nRet = PROPERTY_MAP_FLDTYP_DATABASE_NEXT_SET; break; + case SwServiceType::FieldTypeDatabaseNumSet: nRet = PROPERTY_MAP_FLDTYP_DATABASE_NUM_SET; break; + case SwServiceType::FieldTypeDatabaseSetNum: nRet = PROPERTY_MAP_FLDTYP_DATABASE_SET_NUM; break; + case SwServiceType::FieldTypeDatabase: nRet = PROPERTY_MAP_FLDTYP_DATABASE; break; + case SwServiceType::FieldTypeDatabaseName: nRet = PROPERTY_MAP_FLDTYP_DATABASE_NAME; break; + case SwServiceType::FieldTypeTableFormula: nRet = PROPERTY_MAP_FLDTYP_TABLE_FORMULA; break; + case SwServiceType::FieldTypePageCount: + case SwServiceType::FieldTypeParagraphCount: + case SwServiceType::FieldTypeWordCount: + case SwServiceType::FieldTypeCharacterCount: + case SwServiceType::FieldTypeTableCount: + case SwServiceType::FieldTypeGraphicObjectCount: + case SwServiceType::FieldTypeEmbeddedObjectCount: nRet = PROPERTY_MAP_FLDTYP_DOCSTAT; break; + case SwServiceType::FieldTypeDocInfoChangeAuthor: + case SwServiceType::FieldTypeDocInfoCreateAuthor: + case SwServiceType::FieldTypeDocInfoPrintAuthor: nRet = PROPERTY_MAP_FLDTYP_DOCINFO_AUTHOR; break; + case SwServiceType::FieldTypeDocInfoChangeDateTime: + case SwServiceType::FieldTypeDocInfoCreateDateTime: + case SwServiceType::FieldTypeDocInfoPrintDateTime: nRet = PROPERTY_MAP_FLDTYP_DOCINFO_DATE_TIME; break; + case SwServiceType::FieldTypeDocInfoEditTime: nRet = PROPERTY_MAP_FLDTYP_DOCINFO_EDIT_TIME; break; + case SwServiceType::FieldTypeDocInfoCustom: nRet = PROPERTY_MAP_FLDTYP_DOCINFO_CUSTOM; break; + case SwServiceType::FieldTypeDocInfoDescription: + case SwServiceType::FieldTypeDocInfoKeywords: + case SwServiceType::FieldTypeDocInfoSubject: + case SwServiceType::FieldTypeDocInfoTitle: nRet = PROPERTY_MAP_FLDTYP_DOCINFO_MISC; break; + case SwServiceType::FieldTypeDocInfoRevision: nRet = PROPERTY_MAP_FLDTYP_DOCINFO_REVISION; break; + case SwServiceType::FieldTypeBibliography: nRet = PROPERTY_MAP_FLDTYP_BIBLIOGRAPHY; break; + case SwServiceType::FieldTypeDummy0: + case SwServiceType::FieldTypeCombinedCharacters: nRet = PROPERTY_MAP_FLDTYP_COMBINED_CHARACTERS; break; + case SwServiceType::FieldTypeDropdown: nRet = PROPERTY_MAP_FLDTYP_DROPDOWN; break; + case SwServiceType::FieldTypeDummy4: + case SwServiceType::FieldTypeDummy5: + case SwServiceType::FieldTypeDummy6: + case SwServiceType::FieldTypeDummy7: + nRet = PROPERTY_MAP_FLDTYP_DUMMY_0; break; + case SwServiceType::FieldMasterUser: nRet = PROPERTY_MAP_FLDMSTR_USER; break; + case SwServiceType::FieldMasterDDE: nRet = PROPERTY_MAP_FLDMSTR_DDE; break; + case SwServiceType::FieldMasterSetExp: nRet = PROPERTY_MAP_FLDMSTR_SET_EXP; break; + case SwServiceType::FieldMasterDatabase: nRet = PROPERTY_MAP_FLDMSTR_DATABASE; break; + case SwServiceType::FieldMasterBibliography: nRet = PROPERTY_MAP_FLDMSTR_BIBLIOGRAPHY; break; + case SwServiceType::FieldMasterDummy2: + case SwServiceType::FieldMasterDummy3: + case SwServiceType::FieldMasterDummy4: + case SwServiceType::FieldMasterDummy5: nRet = PROPERTY_MAP_FLDMSTR_DUMMY0; break; + case SwServiceType::FieldTypeHiddenText: nRet = PROPERTY_MAP_FLDTYP_HIDDEN_TEXT; break; + default: + nRet = USHRT_MAX; + } + assert(nRet != USHRT_MAX && "wrong service id"); + return nRet; +} + +class SwXFieldMaster::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + + SwDoc* m_pDoc; + SwFieldType* m_pType; + + SwFieldIds m_nResTypeId; + + OUString m_sParam1; // Content / Database / NumberingSeparator + OUString m_sParam2; // - /DataTablename + OUString m_sParam3; // - /DataFieldName + OUString m_sParam5; // - /DataBaseURL + double m_fParam1; // Value / - + sal_Int8 m_nParam1; // ChapterNumberingLevel + bool m_bParam1; // IsExpression + sal_Int32 m_nParam2; + + Impl(SwPageDesc* const pPageDesc, SwDoc* pDoc, SwFieldIds nResId) + : m_EventListeners(m_Mutex) + , m_pDoc(pDoc) + , m_pType(nullptr) + , m_nResTypeId(nResId) + , m_fParam1(0.0) + , m_nParam1(-1) + , m_bParam1(false) + , m_nParam2(0) + { + StartListening(pPageDesc->GetNotifier()); + } + + Impl(SwFieldType* const pType, SwDoc* pDoc, SwFieldIds nResId) + : m_EventListeners(m_Mutex) + , m_pDoc(pDoc) + , m_pType(pType) + , m_nResTypeId(nResId) + , m_fParam1(0.0) + , m_nParam1(-1) + , m_bParam1(false) + , m_nParam2(0) + { + StartListening(m_pType->GetNotifier()); + } + void SetFieldType(SwFieldType* pType) + { + EndListeningAll(); + m_pType = pType; + StartListening(m_pType->GetNotifier()); + } +protected: + virtual void Notify(const SfxHint& rHint) override; +}; + +namespace +{ + class theSwXFieldMasterUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXFieldMasterUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXFieldMaster::getUnoTunnelId() +{ + return theSwXFieldMasterUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXFieldMaster::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXFieldMaster>(rId, this); +} + +OUString SAL_CALL +SwXFieldMaster::getImplementationName() +{ + return "SwXFieldMaster"; +} + +namespace +{ + +OUString getServiceName(const SwFieldIds aId) +{ + const char* pEntry; + switch (aId) + { + case SwFieldIds::User: + pEntry = "User"; + break; + case SwFieldIds::Database: + pEntry = "Database"; + break; + case SwFieldIds::SetExp: + pEntry = "SetExpression"; + break; + case SwFieldIds::Dde: + pEntry = "DDE"; + break; + case SwFieldIds::TableOfAuthorities: + pEntry = "Bibliography"; + break; + default: + return OUString(); + } + + return "com.sun.star.text.fieldmaster." + OUString::createFromAscii(pEntry); +} + +} + +sal_Bool SAL_CALL SwXFieldMaster::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXFieldMaster::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextFieldMaster", getServiceName(m_pImpl->m_nResTypeId) }; +} + +SwXFieldMaster::SwXFieldMaster(SwDoc *const pDoc, SwFieldIds const nResId) + : m_pImpl(new Impl(pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD), pDoc, nResId)) +{ +} + +SwXFieldMaster::SwXFieldMaster(SwFieldType& rType, SwDoc * pDoc) + : m_pImpl(new Impl(&rType, pDoc, rType.Which())) +{ +} + +SwXFieldMaster::~SwXFieldMaster() +{ +} + +uno::Reference<beans::XPropertySet> +SwXFieldMaster::CreateXFieldMaster(SwDoc * pDoc, SwFieldType *const pType, + SwFieldIds nResId) +{ + // re-use existing SwXFieldMaster + uno::Reference<beans::XPropertySet> xFM; + if (pType) + { + xFM = pType->GetXObject(); + } + if (!xFM.is()) + { + SwXFieldMaster *const pFM( pType + ? new SwXFieldMaster(*pType, pDoc) + : new SwXFieldMaster(pDoc, nResId)); + xFM.set(pFM); + if (pType) + { + pType->SetXObject(xFM); + } + // need a permanent Reference to initialize m_wThis + pFM->m_pImpl->m_wThis = xFM; + } + return xFM; +} + +uno::Reference<beans::XPropertySetInfo> SAL_CALL +SwXFieldMaster::getPropertySetInfo() +{ + SolarMutexGuard aGuard; + uno::Reference< beans::XPropertySetInfo > aRef = + aSwMapProvider.GetPropertySet( + lcl_GetPropMapIdForFieldType(m_pImpl->m_nResTypeId))->getPropertySetInfo(); + return aRef; +} + +void SAL_CALL SwXFieldMaster::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + SwFieldType* pType = GetFieldType(true); + if(pType) + { + bool bSetValue = true; + if( rPropertyName == UNO_NAME_SUB_TYPE ) + { + const std::vector<OUString>& rExtraArr( + SwStyleNameMapper::GetExtraUINameArray()); + const OUString sTypeName = pType->GetName(); + static sal_uInt16 nIds[] = + { + RES_POOLCOLL_LABEL_DRAWING - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_ABB - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_TABLE - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_FRAME- RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_FIGURE - RES_POOLCOLL_EXTRA_BEGIN, + 0 + }; + for(const sal_uInt16 * pIds = nIds; *pIds; ++pIds) + { + if(sTypeName == rExtraArr[ *pIds ] ) + { + bSetValue = false; + break; + } + } + } + if ( bSetValue ) + { + // nothing special to be done here for the properties + // UNO_NAME_DATA_BASE_NAME and UNO_NAME_DATA_BASE_URL. + // We just call PutValue (empty string is allowed). + // Thus the last property set will be used as Data Source. + + const sal_uInt16 nMemberValueId = GetFieldTypeMId( rPropertyName, *pType ); + if ( USHRT_MAX == nMemberValueId ) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast< cppu::OWeakObject * >( this ) ); + } + + pType->PutValue( rValue, nMemberValueId ); + if ( pType->Which() == SwFieldIds::User ) + { + // trigger update of User field in order to get depending Input Fields updated. + pType->UpdateFields(); + } + + } + } + else if (m_pImpl->m_pDoc && rPropertyName == UNO_NAME_NAME) + { + OUString sTypeName; + rValue >>= sTypeName; + SwFieldType * pType2 = m_pImpl->m_pDoc->getIDocumentFieldsAccess().GetFieldType( + m_pImpl->m_nResTypeId, sTypeName, false); + + if(pType2 || + (SwFieldIds::SetExp == m_pImpl->m_nResTypeId && + ( sTypeName == SwResId(STR_POOLCOLL_LABEL_TABLE) || + sTypeName == SwResId(STR_POOLCOLL_LABEL_DRAWING) || + sTypeName == SwResId(STR_POOLCOLL_LABEL_FRAME) || + sTypeName == SwResId(STR_POOLCOLL_LABEL_ABB) || + sTypeName == SwResId(STR_POOLCOLL_LABEL_FIGURE) ))) + { + throw lang::IllegalArgumentException(); + } + + switch (m_pImpl->m_nResTypeId) + { + case SwFieldIds::User : + { + SwUserFieldType aType(m_pImpl->m_pDoc, sTypeName); + pType2 = m_pImpl->m_pDoc->getIDocumentFieldsAccess().InsertFieldType(aType); + static_cast<SwUserFieldType*>(pType2)->SetContent(m_pImpl->m_sParam1); + static_cast<SwUserFieldType*>(pType2)->SetValue(m_pImpl->m_fParam1); + static_cast<SwUserFieldType*>(pType2)->SetType(m_pImpl->m_bParam1 + ? nsSwGetSetExpType::GSE_EXPR : nsSwGetSetExpType::GSE_STRING); + } + break; + case SwFieldIds::Dde : + { + SwDDEFieldType aType(sTypeName, m_pImpl->m_sParam1, + m_pImpl->m_bParam1 ? SfxLinkUpdateMode::ALWAYS : SfxLinkUpdateMode::ONCALL); + pType2 = m_pImpl->m_pDoc->getIDocumentFieldsAccess().InsertFieldType(aType); + } + break; + case SwFieldIds::SetExp : + { + SwSetExpFieldType aType(m_pImpl->m_pDoc, sTypeName); + if (!m_pImpl->m_sParam1.isEmpty()) + aType.SetDelimiter(OUString(m_pImpl->m_sParam1[0])); + if (m_pImpl->m_nParam1 > -1 && m_pImpl->m_nParam1 < MAXLEVEL) + aType.SetOutlineLvl(m_pImpl->m_nParam1); + pType2 = m_pImpl->m_pDoc->getIDocumentFieldsAccess().InsertFieldType(aType); + } + break; + case SwFieldIds::Database : + { + rValue >>= m_pImpl->m_sParam3; + pType2 = GetFieldType(); + } + break; + default: break; + } + if (!pType2) + { + throw uno::RuntimeException("no field type found!", *this); + } + m_pImpl->SetFieldType(pType2); + } + else + { + switch (m_pImpl->m_nResTypeId) + { + case SwFieldIds::User: + if(rPropertyName == UNO_NAME_CONTENT) + rValue >>= m_pImpl->m_sParam1; + else if(rPropertyName == UNO_NAME_VALUE) + { + if(rValue.getValueType() != ::cppu::UnoType<double>::get()) + throw lang::IllegalArgumentException(); + rValue >>= m_pImpl->m_fParam1; + } + else if(rPropertyName == UNO_NAME_IS_EXPRESSION) + { + if(rValue.getValueType() != cppu::UnoType<bool>::get()) + throw lang::IllegalArgumentException(); + rValue >>= m_pImpl->m_bParam1; + } + + break; + case SwFieldIds::Database: + if(rPropertyName == UNO_NAME_DATA_BASE_NAME) + rValue >>= m_pImpl->m_sParam1; + else if(rPropertyName == UNO_NAME_DATA_TABLE_NAME) + rValue >>= m_pImpl->m_sParam2; + else if(rPropertyName == UNO_NAME_DATA_COLUMN_NAME) + rValue >>= m_pImpl->m_sParam3; + else if(rPropertyName == UNO_NAME_DATA_COMMAND_TYPE) + rValue >>= m_pImpl->m_nParam2; + if(rPropertyName == UNO_NAME_DATA_BASE_URL) + rValue >>= m_pImpl->m_sParam5; + + if ( ( !m_pImpl->m_sParam1.isEmpty() + || !m_pImpl->m_sParam5.isEmpty()) + && !m_pImpl->m_sParam2.isEmpty() + && !m_pImpl->m_sParam3.isEmpty()) + { + GetFieldType(); + } + break; + case SwFieldIds::SetExp: + if(rPropertyName == UNO_NAME_NUMBERING_SEPARATOR) + rValue >>= m_pImpl->m_sParam1; + else if(rPropertyName == UNO_NAME_CHAPTER_NUMBERING_LEVEL) + rValue >>= m_pImpl->m_nParam1; + break; + case SwFieldIds::Dde: + { + sal_Int32 nPart = lcl_PropName2TokenPos(rPropertyName); + if(nPart < 3 ) + { + if (m_pImpl->m_sParam1.isEmpty()) + { + m_pImpl->m_sParam1 + = OUStringChar(sfx2::cTokenSeparator) + + OUStringChar(sfx2::cTokenSeparator); + } + OUString sTmp; + rValue >>= sTmp; + sal_Int32 nIndex(0); + sal_Int32 nStart(0); + while (nIndex < m_pImpl->m_sParam1.getLength()) + { + if (m_pImpl->m_sParam1[nIndex] == sfx2::cTokenSeparator) + { + if (0 == nPart) + break; + nStart = nIndex + 1; + --nPart; + } + ++nIndex; + } + assert(0 == nPart); + m_pImpl->m_sParam1 = m_pImpl->m_sParam1.replaceAt( + nStart, nIndex - nStart, sTmp); + } + else if(3 == nPart) + { + rValue >>= m_pImpl->m_bParam1; + } + } + break; + default: + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + } + } +} + +SwFieldType* SwXFieldMaster::GetFieldType(bool const bDontCreate) const +{ + if (!bDontCreate && SwFieldIds::Database == m_pImpl->m_nResTypeId + && !m_pImpl->m_pType && m_pImpl->m_pDoc) + { + SwDBData aData; + + // set DataSource + svx::ODataAccessDescriptor aAcc; + if (!m_pImpl->m_sParam1.isEmpty()) + aAcc[svx::DataAccessDescriptorProperty::DataSource] <<= m_pImpl->m_sParam1; // DataBaseName + else if (!m_pImpl->m_sParam5.isEmpty()) + aAcc[svx::DataAccessDescriptorProperty::DatabaseLocation] <<= m_pImpl->m_sParam5; // DataBaseURL + aData.sDataSource = aAcc.getDataSource(); + + aData.sCommand = m_pImpl->m_sParam2; + aData.nCommandType = m_pImpl->m_nParam2; + + SwDBFieldType aType(m_pImpl->m_pDoc, m_pImpl->m_sParam3, aData); + SwFieldType *const pType = m_pImpl->m_pDoc->getIDocumentFieldsAccess().InsertFieldType(aType); + m_pImpl->SetFieldType(pType); + } + return m_pImpl->m_pType; +} + +uno::Any SAL_CALL +SwXFieldMaster::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwFieldType* pType = GetFieldType(true); + if( rPropertyName == UNO_NAME_INSTANCE_NAME ) + { + OUString sName; + if(pType) + SwXTextFieldMasters::getInstanceName(*pType, sName); + aRet <<= sName; + } + else if(pType) + { + if(rPropertyName == UNO_NAME_NAME) + { + aRet <<= SwXFieldMaster::GetProgrammaticName(*pType, *m_pImpl->m_pDoc); + } + else if(rPropertyName == UNO_NAME_DEPENDENT_TEXT_FIELDS) + { + //fill all text fields into a sequence + std::vector<SwFormatField*> vpFields; + pType->GatherFields(vpFields); + uno::Sequence<uno::Reference <text::XDependentTextField> > aSeq(vpFields.size()); + std::transform(vpFields.begin(), vpFields.end(), aSeq.begin(), + [this](SwFormatField* pF) { return uno::Reference<text::XDependentTextField>(SwXTextField::CreateXTextField(m_pImpl->m_pDoc, pF), uno::UNO_QUERY); }); + aRet <<= aSeq; + } + else + { + //TODO: add properties for the other field types + const sal_uInt16 nMId = GetFieldTypeMId( rPropertyName, *pType ); + if (USHRT_MAX == nMId) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + pType->QueryValue( aRet, nMId ); + + if (rPropertyName == UNO_NAME_DATA_BASE_NAME || + rPropertyName == UNO_NAME_DATA_BASE_URL) + { + OUString aDataSource; + aRet >>= aDataSource; + aRet <<= OUString(); + + OUString *pStr = nullptr; // only one of this properties will return + // a non-empty string. + INetURLObject aObj; + aObj.SetURL( aDataSource ); + bool bIsURL = aObj.GetProtocol() != INetProtocol::NotValid; + if (bIsURL && rPropertyName == UNO_NAME_DATA_BASE_URL) + pStr = &aDataSource; // DataBaseURL + else if (!bIsURL && rPropertyName == UNO_NAME_DATA_BASE_NAME) + pStr = &aDataSource; // DataBaseName + + if (pStr) + aRet <<= *pStr; + } + } + } + else + { + if(rPropertyName == UNO_NAME_DATA_COMMAND_TYPE) + aRet <<= m_pImpl->m_nParam2; + else if(rPropertyName == UNO_NAME_DEPENDENT_TEXT_FIELDS ) + { + uno::Sequence<uno::Reference <text::XDependentTextField> > aRetSeq(0); + aRet <<= aRetSeq; + } + else + { + switch (m_pImpl->m_nResTypeId) + { + case SwFieldIds::User: + if( rPropertyName == UNO_NAME_CONTENT ) + aRet <<= m_pImpl->m_sParam1; + else if(rPropertyName == UNO_NAME_VALUE) + aRet <<= m_pImpl->m_fParam1; + else if(rPropertyName == UNO_NAME_IS_EXPRESSION) + aRet <<= m_pImpl->m_bParam1; + break; + case SwFieldIds::Database: + if(rPropertyName == UNO_NAME_DATA_BASE_NAME || + rPropertyName == UNO_NAME_DATA_BASE_URL) + { + // only one of these properties returns a non-empty string. + INetURLObject aObj; + aObj.SetURL(m_pImpl->m_sParam5); // SetSmartURL + bool bIsURL = aObj.GetProtocol() != INetProtocol::NotValid; + if (bIsURL && rPropertyName == UNO_NAME_DATA_BASE_URL) + aRet <<= m_pImpl->m_sParam5; // DataBaseURL + else if ( rPropertyName == UNO_NAME_DATA_BASE_NAME) + aRet <<= m_pImpl->m_sParam1; // DataBaseName + } + else if(rPropertyName == UNO_NAME_DATA_TABLE_NAME) + aRet <<= m_pImpl->m_sParam2; + else if(rPropertyName == UNO_NAME_DATA_COLUMN_NAME) + aRet <<= m_pImpl->m_sParam3; + break; + case SwFieldIds::SetExp: + if(rPropertyName == UNO_NAME_NUMBERING_SEPARATOR) + aRet <<= m_pImpl->m_sParam1; + else if(rPropertyName == UNO_NAME_CHAPTER_NUMBERING_LEVEL) + aRet <<= m_pImpl->m_nParam1; + break; + case SwFieldIds::Dde: + { + const sal_Int32 nPart = lcl_PropName2TokenPos(rPropertyName); + if(nPart < 3 ) + aRet <<= m_pImpl->m_sParam1.getToken(nPart, sfx2::cTokenSeparator); + else if(3 == nPart) + aRet <<= m_pImpl->m_bParam1; + } + break; + default: + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + } + } + } + return aRet; +} + +void SwXFieldMaster::addPropertyChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFieldMaster::removePropertyChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFieldMaster::addVetoableChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFieldMaster::removeVetoableChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SwXFieldMaster::dispose() +{ + SolarMutexGuard aGuard; + SwFieldType *const pFieldType = GetFieldType(true); + if (!pFieldType) + throw uno::RuntimeException(); + size_t nTypeIdx = SIZE_MAX; + const SwFieldTypes* pTypes = m_pImpl->m_pDoc->getIDocumentFieldsAccess().GetFieldTypes(); + for( size_t i = 0; i < pTypes->size(); i++ ) + { + if((*pTypes)[i].get()== pFieldType) + nTypeIdx = i; + } + + // first delete all fields + std::vector<SwFormatField*> vpFields; + pFieldType->GatherFields(vpFields); + for(auto pField : vpFields) + SwTextField::DeleteTextField(*pField->GetTextField()); + // then delete FieldType + m_pImpl->m_pDoc->getIDocumentFieldsAccess().RemoveFieldType(nTypeIdx); +} + +void SAL_CALL SwXFieldMaster::addEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXFieldMaster::removeEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +void SwXFieldMaster::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pDoc = nullptr; + m_pType = nullptr; + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); + } +} + +OUString SwXFieldMaster::GetProgrammaticName(const SwFieldType& rType, SwDoc& rDoc) +{ + const OUString sName(rType.GetName()); + if(SwFieldIds::SetExp == rType.Which()) + { + const SwFieldTypes* pTypes = rDoc.getIDocumentFieldsAccess().GetFieldTypes(); + for( size_t i = 0; i <= o3tl::make_unsigned(INIT_FLDTYPES); i++ ) + { + if((*pTypes)[i].get() == &rType) + { + return SwStyleNameMapper::GetProgName( sName, SwGetPoolIdFromName::TxtColl ); + } + } + } + return sName; +} + +OUString SwXFieldMaster::LocalizeFormula( + const SwSetExpField& rField, + const OUString& rFormula, + bool bQuery) +{ + const OUString sTypeName(rField.GetTyp()->GetName()); + const OUString sProgName( + SwStyleNameMapper::GetProgName(sTypeName, SwGetPoolIdFromName::TxtColl )); + if(sProgName != sTypeName) + { + const OUString sSource = bQuery ? sTypeName : sProgName; + const OUString sDest = bQuery ? sProgName : sTypeName; + if(rFormula.startsWith(sSource)) + { + return sDest + rFormula.copy(sSource.getLength()); + } + } + return rFormula; +} + +namespace { + +struct SwFieldProperties_Impl +{ + OUString sPar1; + OUString sPar2; + OUString sPar3; + OUString sPar4; + Date aDate; + double fDouble; + uno::Sequence<beans::PropertyValue> aPropSeq; + uno::Sequence<OUString> aStrings; + std::unique_ptr<util::DateTime> pDateTime; + + sal_Int32 nSubType; + sal_Int32 nFormat; + sal_uInt16 nUSHORT1; + sal_uInt16 nUSHORT2; + sal_Int16 nSHORT1; + sal_Int8 nByte1; + bool bFormatIsDefault; + bool bBool1; + bool bBool2; + bool bBool3; + bool bBool4; + + SwFieldProperties_Impl(): + aDate( Date::EMPTY ), + fDouble(0.), + nSubType(0), + nFormat(0), + nUSHORT1(0), + nUSHORT2(0), + nSHORT1(0), + nByte1(0), + bFormatIsDefault(true), + bBool1(false), + bBool2(false), + bBool3(false), + bBool4(true) //Automatic language + {} +}; + +} + +class SwXTextField::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + SwFieldType* m_pFieldType; + SwFormatField* m_pFormatField; + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + + SwDoc* m_pDoc; + rtl::Reference<SwTextAPIObject> m_xTextObject; + bool m_bIsDescriptor; + bool m_bCallUpdate; + SwServiceType m_nServiceId; + OUString m_sTypeName; + std::unique_ptr<SwFieldProperties_Impl> m_pProps; + + Impl(SwDoc *const pDoc, SwFormatField *const pFormat, SwServiceType nServiceId) + : m_pFieldType(nullptr) + , m_pFormatField(pFormat) + , m_EventListeners(m_Mutex) + , m_pDoc(pDoc) + , m_bIsDescriptor(pFormat == nullptr) + , m_bCallUpdate(false) + , m_nServiceId(pFormat + ? lcl_GetServiceForField(*pFormat->GetField()) + : nServiceId) + , m_pProps(pFormat ? nullptr : new SwFieldProperties_Impl) + { + if(m_pFormatField) + StartListening(m_pFormatField->GetNotifier()); + } + + virtual ~Impl() override + { + if (m_xTextObject.is()) + { + m_xTextObject->DisposeEditSource(); + } + } + + void SetFormatField(SwFormatField* pFormatField, SwDoc* pDoc) + { + m_pFormatField = pFormatField; + m_pDoc = pDoc; + if(m_pFormatField) + { + EndListeningAll(); + StartListening(m_pFormatField->GetNotifier()); + } + } + SwFormatField* GetFormatField() + { + return m_pFormatField; + } + bool IsDescriptor() const + { + // ideally should be: !m_pFormatField && m_pDoc + // but: SwXServiceProvider::MakeInstance() passes nullptr SwDoc, see comment there + return m_bIsDescriptor; + } + void Invalidate(); + + const SwField* GetField() const; + + SwFieldType* GetFieldType() const + { + if(!m_pDoc && !IsDescriptor()) + throw uno::RuntimeException(); + else if (IsDescriptor()) + return m_pFieldType; + + return m_pFormatField->GetField()->GetTyp(); + } + void SetFieldType(SwFieldType& rType) + { + EndListeningAll(); + m_pFieldType = &rType; + StartListening(m_pFieldType->GetNotifier()); + } + void ClearFieldType() + { + SvtListener::EndListeningAll(); + m_pFieldType = nullptr; + } + virtual void Notify(const SfxHint&) override; +}; + +namespace +{ + class theSwXTextFieldUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextFieldUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextField::getUnoTunnelId() +{ + return theSwXTextFieldUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXTextField::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXTextField>(rId, this); +} + +SwXTextField::SwXTextField( + SwServiceType nServiceId, + SwDoc* pDoc) + : m_pImpl(new Impl(pDoc, nullptr, nServiceId)) +{ + //Set visible as default! + if ( SwServiceType::FieldTypeSetExp == nServiceId + || SwServiceType::FieldTypeDatabaseSetNum == nServiceId + || SwServiceType::FieldTypeDatabase == nServiceId + || SwServiceType::FieldTypeDatabaseName == nServiceId ) + { + m_pImpl->m_pProps->bBool2 = true; + } + else if(SwServiceType::FieldTypeTableFormula == nServiceId) + { + m_pImpl->m_pProps->bBool1 = true; + } + if(SwServiceType::FieldTypeSetExp == nServiceId) + { + m_pImpl->m_pProps->nUSHORT2 = USHRT_MAX; + } +} + +SwXTextField::SwXTextField(SwFormatField& rFormat, SwDoc & rDoc) + : m_pImpl(new Impl(&rDoc, &rFormat, SwServiceType::Invalid)) +{ +} + +SwXTextField::~SwXTextField() +{ +} + +uno::Reference<text::XTextField> +SwXTextField::CreateXTextField(SwDoc *const pDoc, SwFormatField const* pFormat, + SwServiceType nServiceId) +{ + assert(!pFormat || pDoc); + assert(pFormat || nServiceId != SwServiceType::Invalid); + // re-use existing SwXTextField + uno::Reference<text::XTextField> xField; + if (pFormat) + { + xField = pFormat->GetXTextField(); + } + if (!xField.is()) + { + SwXTextField *const pField( pFormat + ? new SwXTextField(const_cast<SwFormatField&>(*pFormat), *pDoc) + : new SwXTextField(nServiceId, pDoc)); + xField.set(pField); + if (pFormat) + { + const_cast<SwFormatField *>(pFormat)->SetXTextField(xField); + } + // need a permanent Reference to initialize m_wThis + pField->m_pImpl->m_wThis = xField; + } + return xField; +} + +SwServiceType SwXTextField::GetServiceId() const +{ + return m_pImpl->m_nServiceId; +} + +/** Convert between SwSetExpField with InputFlag false and InputFlag true. + Unfortunately the InputFlag is exposed in the API as "Input" property + and is mutable; in the UI and in ODF these are 2 different types of + fields, so the API design is very questionable. + In order to keep the mutable property, the whole thing has to be + reconstructed from scratch, to replace the SwTextField hint with + SwTextInputField or vice versa. + The SwFormatField will be replaced - it must be, because the Which + changes - but the SwXTextField *must not* be disposed in the operation, + it has to be disconnected first and at the end connected to the + new instance! + */ +void SwXTextField::TransmuteLeadToInputField(SwSetExpField & rField) +{ + assert(rField.GetFormatField()->Which() == (rField.GetInputFlag() ? RES_TXTATR_INPUTFIELD : RES_TXTATR_FIELD)); + uno::Reference<text::XTextField> const xField( + rField.GetFormatField()->GetXTextField()); + SwXTextField *const pXField = xField.is() + ? reinterpret_cast<SwXTextField*>( + sal::static_int_cast<sal_IntPtr>( + uno::Reference<lang::XUnoTunnel>(xField, uno::UNO_QUERY_THROW) + ->getSomething(getUnoTunnelId()))) + : nullptr; + if (pXField) + pXField->m_pImpl->SetFormatField(nullptr, nullptr); + SwTextField *const pOldAttr(rField.GetFormatField()->GetTextField()); + SwSetExpField tempField(rField); + tempField.SetInputFlag(!rField.GetInputFlag()); + SwFormatField tempFormat(tempField); + assert(tempFormat.GetField() != &rField); + assert(tempFormat.GetField() != &tempField); // this copies it again? + assert(tempFormat.Which() == (static_cast<SwSetExpField const*>(tempFormat.GetField())->GetInputFlag() ? RES_TXTATR_INPUTFIELD : RES_TXTATR_FIELD)); + SwTextNode & rNode(pOldAttr->GetTextNode()); + std::shared_ptr<SwPaM> pPamForTextField; + IDocumentContentOperations & rIDCO(rNode.GetDoc()->getIDocumentContentOperations()); + SwTextField::GetPamForTextField(*pOldAttr, pPamForTextField); + assert(pPamForTextField); + sal_Int32 const nStart(pPamForTextField->Start()->nContent.GetIndex()); + rIDCO.DeleteAndJoin(*pPamForTextField); + // ATTENTION: rField is dead now! hope nobody accesses it... + bool bSuccess = rIDCO.InsertPoolItem(*pPamForTextField, tempFormat); + assert(bSuccess); + (void) bSuccess; + SwTextField const* pNewAttr(rNode.GetFieldTextAttrAt(nStart, true)); + assert(pNewAttr); + SwFormatField const& rNewFormat(pNewAttr->GetFormatField()); + assert(rNewFormat.Which() == (static_cast<SwSetExpField const*>(rNewFormat.GetField())->GetInputFlag() ? RES_TXTATR_INPUTFIELD : RES_TXTATR_FIELD)); + assert(static_cast<SwSetExpField const*>(rNewFormat.GetField())->GetInputFlag() == (dynamic_cast<SwTextInputField const*>(pNewAttr) != nullptr)); + if (xField.is()) + { + pXField->m_pImpl->SetFormatField(const_cast<SwFormatField*>(&rNewFormat), rNode.GetDoc()); + const_cast<SwFormatField&>(rNewFormat).SetXTextField(xField); + } +} + +void SAL_CALL SwXTextField::attachTextFieldMaster( + const uno::Reference< beans::XPropertySet > & xFieldMaster) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->IsDescriptor()) + throw uno::RuntimeException(); + uno::Reference< lang::XUnoTunnel > xMasterTunnel(xFieldMaster, uno::UNO_QUERY); + if (!xMasterTunnel.is()) + throw lang::IllegalArgumentException(); + SwXFieldMaster* pMaster = reinterpret_cast< SwXFieldMaster * >( + sal::static_int_cast< sal_IntPtr >( xMasterTunnel->getSomething( SwXFieldMaster::getUnoTunnelId()) )); + + SwFieldType* pFieldType = pMaster ? pMaster->GetFieldType() : nullptr; + if (!pFieldType || + pFieldType->Which() != lcl_ServiceIdToResId(m_pImpl->m_nServiceId)) + { + throw lang::IllegalArgumentException(); + } + m_pImpl->m_sTypeName = pFieldType->GetName(); + m_pImpl->SetFieldType(*pFieldType); +} + +uno::Reference< beans::XPropertySet > SAL_CALL +SwXTextField::getTextFieldMaster() +{ + SolarMutexGuard aGuard; + + SwFieldType* pType = m_pImpl->GetFieldType(); + uno::Reference<beans::XPropertySet> const xRet( + SwXFieldMaster::CreateXFieldMaster(m_pImpl->m_pDoc, pType)); + return xRet; +} + +OUString SAL_CALL SwXTextField::getPresentation(sal_Bool bShowCommand) +{ + SolarMutexGuard aGuard; + + SwField const*const pField = m_pImpl->GetField(); + if (!pField) + { + throw uno::RuntimeException(); + } + return bShowCommand ? pField->GetFieldName() : pField->ExpandField(true, nullptr); +} + +void SAL_CALL SwXTextField::attach( + const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard aGuard; + if (m_pImpl->IsDescriptor()) + { + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + if(xRangeTunnel.is()) + { + pRange = reinterpret_cast< SwXTextRange * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( SwXTextRange::getUnoTunnelId()) )); + pCursor = reinterpret_cast< OTextCursorHelper * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( OTextCursorHelper::getUnoTunnelId()) )); + } + + SwDoc* pDoc = pRange ? &pRange->GetDoc() : pCursor ? pCursor->GetDoc() : nullptr; + // if a FieldMaster was attached, then the document is already fixed! + // NOTE: sw.SwXAutoTextEntry unoapi test depends on m_pDoc = 0 being valid + if (!pDoc || (m_pImpl->m_pDoc && m_pImpl->m_pDoc != pDoc)) + throw lang::IllegalArgumentException(); + + SwUnoInternalPaM aPam(*pDoc); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + std::unique_ptr<SwField> xField; + switch (m_pImpl->m_nServiceId) + { + case SwServiceType::FieldTypeAnnotation: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Postit); + + DateTime aDateTime( DateTime::EMPTY ); + if (m_pImpl->m_pProps->pDateTime) + { + aDateTime = *(m_pImpl->m_pProps->pDateTime); + } + SwPostItField* pPostItField = new SwPostItField( + static_cast<SwPostItFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar1, // author + m_pImpl->m_pProps->sPar2, // content + m_pImpl->m_pProps->sPar3, // author's initials + m_pImpl->m_pProps->sPar4, // name + aDateTime, + m_pImpl->m_pProps->bBool1 // resolvedflag + ); + if ( m_pImpl->m_xTextObject.is() ) + { + pPostItField->SetTextObject( m_pImpl->m_xTextObject->CreateText() ); + pPostItField->SetPar2(m_pImpl->m_xTextObject->GetText()); + } + xField.reset(pPostItField); + } + break; + case SwServiceType::FieldTypeScript: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Script); + xField.reset(new SwScriptField(static_cast<SwScriptFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar1, m_pImpl->m_pProps->sPar2, + m_pImpl->m_pProps->bBool1)); + } + break; + case SwServiceType::FieldTypeDateTime: + { + sal_uInt16 nSub = 0; + if (m_pImpl->m_pProps->bBool1) + nSub |= FIXEDFLD; + if (m_pImpl->m_pProps->bBool2) + nSub |= DATEFLD; + else + nSub |= TIMEFLD; + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DateTime); + SwDateTimeField *const pDTField = new SwDateTimeField( + static_cast<SwDateTimeFieldType*>(pFieldType), + nSub, m_pImpl->m_pProps->nFormat); + xField.reset(pDTField); + if (m_pImpl->m_pProps->fDouble > 0.) + { + pDTField->SetValue(m_pImpl->m_pProps->fDouble); + } + if (m_pImpl->m_pProps->pDateTime) + { + uno::Any aVal; aVal <<= *m_pImpl->m_pProps->pDateTime; + xField->PutValue( aVal, FIELD_PROP_DATE_TIME ); + } + pDTField->SetOffset(m_pImpl->m_pProps->nSubType); + } + break; + case SwServiceType::FieldTypeFileName: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Filename); + sal_Int32 nFormat = m_pImpl->m_pProps->nFormat; + if (m_pImpl->m_pProps->bBool2) + nFormat |= FF_FIXED; + SwFileNameField *const pFNField = new SwFileNameField( + static_cast<SwFileNameFieldType*>(pFieldType), nFormat); + xField.reset(pFNField); + if (!m_pImpl->m_pProps->sPar3.isEmpty()) + pFNField->SetExpansion(m_pImpl->m_pProps->sPar3); + uno::Any aFormat; + aFormat <<= m_pImpl->m_pProps->nFormat; + xField->PutValue( aFormat, FIELD_PROP_FORMAT ); + } + break; + case SwServiceType::FieldTypeTemplateName: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::TemplateName); + xField.reset(new SwTemplNameField(static_cast<SwTemplNameFieldType*>(pFieldType), + m_pImpl->m_pProps->nFormat)); + uno::Any aFormat; + aFormat <<= m_pImpl->m_pProps->nFormat; + xField->PutValue(aFormat, FIELD_PROP_FORMAT); + } + break; + case SwServiceType::FieldTypeChapter: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Chapter); + SwChapterField *const pChapterField = new SwChapterField( + static_cast<SwChapterFieldType*>(pFieldType), + m_pImpl->m_pProps->nUSHORT1); + xField.reset(pChapterField); + pChapterField->SetLevel(m_pImpl->m_pProps->nByte1); + uno::Any aVal; + aVal <<= static_cast<sal_Int16>(m_pImpl->m_pProps->nUSHORT1); + xField->PutValue(aVal, FIELD_PROP_USHORT1 ); + } + break; + case SwServiceType::FieldTypeAuthor: + { + long nFormat = m_pImpl->m_pProps->bBool1 ? AF_NAME : AF_SHORTCUT; + if (m_pImpl->m_pProps->bBool2) + nFormat |= AF_FIXED; + + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Author); + SwAuthorField *const pAuthorField = new SwAuthorField( + static_cast<SwAuthorFieldType*>(pFieldType), nFormat); + xField.reset(pAuthorField); + pAuthorField->SetExpansion(m_pImpl->m_pProps->sPar1); + } + break; + case SwServiceType::FieldTypeConditionedText: + case SwServiceType::FieldTypeHiddenText: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::HiddenText); + SwHiddenTextField *const pHTField = new SwHiddenTextField( + static_cast<SwHiddenTextFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar1, + m_pImpl->m_pProps->sPar2, m_pImpl->m_pProps->sPar3, + SwServiceType::FieldTypeHiddenText == m_pImpl->m_nServiceId ? + SwFieldTypesEnum::HiddenText : SwFieldTypesEnum::ConditionalText); + xField.reset(pHTField); + pHTField->SetValue(m_pImpl->m_pProps->bBool1); + uno::Any aVal; + aVal <<= m_pImpl->m_pProps->sPar4; + xField->PutValue(aVal, FIELD_PROP_PAR4 ); + } + break; + case SwServiceType::FieldTypeHiddenPara: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::HiddenPara); + SwHiddenParaField *const pHPField = new SwHiddenParaField( + static_cast<SwHiddenParaFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar1); + xField.reset(pHPField); + pHPField->SetHidden(m_pImpl->m_pProps->bBool1); + } + break; + case SwServiceType::FieldTypeGetReference: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::GetRef); + xField.reset(new SwGetRefField(static_cast<SwGetRefFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar1, + m_pImpl->m_pProps->sPar4, + 0, + 0, + 0)); + if (!m_pImpl->m_pProps->sPar3.isEmpty()) + static_cast<SwGetRefField*>(xField.get())->SetExpand(m_pImpl->m_pProps->sPar3); + uno::Any aVal; + aVal <<= static_cast<sal_Int16>(m_pImpl->m_pProps->nUSHORT1); + xField->PutValue(aVal, FIELD_PROP_USHORT1 ); + aVal <<= static_cast<sal_Int16>(m_pImpl->m_pProps->nUSHORT2); + xField->PutValue(aVal, FIELD_PROP_USHORT2 ); + aVal <<= m_pImpl->m_pProps->nSHORT1; + xField->PutValue(aVal, FIELD_PROP_SHORT1 ); + } + break; + case SwServiceType::FieldTypeJumpEdit: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::JumpEdit); + xField.reset(new SwJumpEditField(static_cast<SwJumpEditFieldType*>(pFieldType), + m_pImpl->m_pProps->nUSHORT1, m_pImpl->m_pProps->sPar2, + m_pImpl->m_pProps->sPar1)); + } + break; + case SwServiceType::FieldTypeDocInfoChangeAuthor: + case SwServiceType::FieldTypeDocInfoChangeDateTime: + case SwServiceType::FieldTypeDocInfoEditTime: + case SwServiceType::FieldTypeDocInfoDescription: + case SwServiceType::FieldTypeDocInfoCreateAuthor: + case SwServiceType::FieldTypeDocInfoCreateDateTime: + case SwServiceType::FieldTypeDocInfoCustom: + case SwServiceType::FieldTypeDocInfoPrintAuthor: + case SwServiceType::FieldTypeDocInfoPrintDateTime: + case SwServiceType::FieldTypeDocInfoKeywords: + case SwServiceType::FieldTypeDocInfoSubject: + case SwServiceType::FieldTypeDocInfoTitle: + case SwServiceType::FieldTypeDocInfoRevision: + case SwServiceType::FieldTypeDocInfo: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DocInfo); + sal_uInt16 nSubType = aDocInfoSubTypeFromService[ + static_cast<sal_uInt16>(m_pImpl->m_nServiceId) - sal_uInt16(SwServiceType::FieldTypeDocInfoChangeAuthor)]; + if (SwServiceType::FieldTypeDocInfoChangeDateTime == m_pImpl->m_nServiceId || + SwServiceType::FieldTypeDocInfoCreateDateTime == m_pImpl->m_nServiceId || + SwServiceType::FieldTypeDocInfoPrintDateTime == m_pImpl->m_nServiceId || + SwServiceType::FieldTypeDocInfoEditTime == m_pImpl->m_nServiceId) + { + if (m_pImpl->m_pProps->bBool2) //IsDate + { + nSubType &= 0xf0ff; + nSubType |= DI_SUB_DATE; + } + else + { + nSubType &= 0xf0ff; + nSubType |= DI_SUB_TIME; + } + } + if (m_pImpl->m_pProps->bBool1) + nSubType |= DI_SUB_FIXED; + xField.reset(new SwDocInfoField( + static_cast<SwDocInfoFieldType*>(pFieldType), nSubType, + m_pImpl->m_pProps->sPar4, m_pImpl->m_pProps->nFormat)); + if (!m_pImpl->m_pProps->sPar3.isEmpty()) + static_cast<SwDocInfoField*>(xField.get())->SetExpansion(m_pImpl->m_pProps->sPar3); + } + break; + case SwServiceType::FieldTypeUserExt: + { + sal_Int32 nFormat = 0; + if (m_pImpl->m_pProps->bBool1) + nFormat = AF_FIXED; + + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::ExtUser); + SwExtUserField *const pEUField = new SwExtUserField( + static_cast<SwExtUserFieldType*>(pFieldType), + m_pImpl->m_pProps->nUSHORT1, nFormat); + xField.reset(pEUField); + pEUField->SetExpansion(m_pImpl->m_pProps->sPar1); + } + break; + case SwServiceType::FieldTypeUser: + { + SwFieldType* pFieldType = + pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::User, m_pImpl->m_sTypeName, true); + if (!pFieldType) + throw uno::RuntimeException(); + sal_uInt16 nUserSubType = (m_pImpl->m_pProps->bBool1) + ? nsSwExtendedSubType::SUB_INVISIBLE : 0; + if (m_pImpl->m_pProps->bBool2) + nUserSubType |= nsSwExtendedSubType::SUB_CMD; + if (m_pImpl->m_pProps->bFormatIsDefault && + nsSwGetSetExpType::GSE_STRING == static_cast<SwUserFieldType*>(pFieldType)->GetType()) + { + m_pImpl->m_pProps->nFormat = -1; + } + xField.reset(new SwUserField(static_cast<SwUserFieldType*>(pFieldType), + nUserSubType, + m_pImpl->m_pProps->nFormat)); + } + break; + case SwServiceType::FieldTypeRefPageSet: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::RefPageSet); + xField.reset(new SwRefPageSetField( static_cast<SwRefPageSetFieldType*>(pFieldType), + m_pImpl->m_pProps->nUSHORT1, + m_pImpl->m_pProps->bBool1 )); + } + break; + case SwServiceType::FieldTypeRefPageGet: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::RefPageGet); + SwRefPageGetField *const pRGField = new SwRefPageGetField( + static_cast<SwRefPageGetFieldType*>(pFieldType), + m_pImpl->m_pProps->nUSHORT1 ); + xField.reset(pRGField); + pRGField->SetText(m_pImpl->m_pProps->sPar1, nullptr); + } + break; + case SwServiceType::FieldTypePageNum: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::PageNumber); + SwPageNumberField *const pPNField = new SwPageNumberField( + static_cast<SwPageNumberFieldType*>(pFieldType), PG_RANDOM, + m_pImpl->m_pProps->nFormat, + m_pImpl->m_pProps->nUSHORT1); + xField.reset(pPNField); + pPNField->SetUserString(m_pImpl->m_pProps->sPar1); + uno::Any aVal; + aVal <<= m_pImpl->m_pProps->nSubType; + xField->PutValue( aVal, FIELD_PROP_SUBTYPE ); + } + break; + case SwServiceType::FieldTypeDDE: + { + SwFieldType* pFieldType = + pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Dde, m_pImpl->m_sTypeName, true); + if (!pFieldType) + throw uno::RuntimeException(); + xField.reset(new SwDDEField( static_cast<SwDDEFieldType*>(pFieldType) )); + } + break; + case SwServiceType::FieldTypeDatabaseName: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DatabaseName); + SwDBData aData; + aData.sDataSource = m_pImpl->m_pProps->sPar1; + aData.sCommand = m_pImpl->m_pProps->sPar2; + aData.nCommandType = m_pImpl->m_pProps->nSHORT1; + xField.reset(new SwDBNameField(static_cast<SwDBNameFieldType*>(pFieldType), aData)); + sal_uInt16 nSubType = xField->GetSubType(); + if (m_pImpl->m_pProps->bBool2) + nSubType &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + nSubType |= nsSwExtendedSubType::SUB_INVISIBLE; + xField->SetSubType(nSubType); + } + break; + case SwServiceType::FieldTypeDatabaseNextSet: + { + SwDBData aData; + aData.sDataSource = m_pImpl->m_pProps->sPar1; + aData.sCommand = m_pImpl->m_pProps->sPar2; + aData.nCommandType = m_pImpl->m_pProps->nSHORT1; + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DbNextSet); + xField.reset(new SwDBNextSetField(static_cast<SwDBNextSetFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar3, aData)); + } + break; + case SwServiceType::FieldTypeDatabaseNumSet: + { + SwDBData aData; + aData.sDataSource = m_pImpl->m_pProps->sPar1; + aData.sCommand = m_pImpl->m_pProps->sPar2; + aData.nCommandType = m_pImpl->m_pProps->nSHORT1; + xField.reset(new SwDBNumSetField( static_cast<SwDBNumSetFieldType*>( + pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DbNumSet)), + m_pImpl->m_pProps->sPar3, + OUString::number(m_pImpl->m_pProps->nFormat), + aData )); + } + break; + case SwServiceType::FieldTypeDatabaseSetNum: + { + SwDBData aData; + aData.sDataSource = m_pImpl->m_pProps->sPar1; + aData.sCommand = m_pImpl->m_pProps->sPar2; + aData.nCommandType = m_pImpl->m_pProps->nSHORT1; + SwDBSetNumberField *const pDBSNField = + new SwDBSetNumberField(static_cast<SwDBSetNumberFieldType*>( + pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DbSetNumber)), aData, + m_pImpl->m_pProps->nUSHORT1); + xField.reset(pDBSNField); + pDBSNField->SetSetNumber(m_pImpl->m_pProps->nFormat); + sal_uInt16 nSubType = xField->GetSubType(); + if (m_pImpl->m_pProps->bBool2) + nSubType &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + nSubType |= nsSwExtendedSubType::SUB_INVISIBLE; + xField->SetSubType(nSubType); + } + break; + case SwServiceType::FieldTypeDatabase: + { + SwFieldType* pFieldType = + pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Database, m_pImpl->m_sTypeName, false); + if (!pFieldType) + throw uno::RuntimeException(); + xField.reset(new SwDBField(static_cast<SwDBFieldType*>(pFieldType), + m_pImpl->m_pProps->nFormat)); + static_cast<SwDBField*>(xField.get())->InitContent(m_pImpl->m_pProps->sPar1); + sal_uInt16 nSubType = xField->GetSubType(); + if (m_pImpl->m_pProps->bBool2) + nSubType &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + nSubType |= nsSwExtendedSubType::SUB_INVISIBLE; + xField->SetSubType(nSubType); + } + break; + case SwServiceType::FieldTypeSetExp: + { + SwFieldType* pFieldType = + pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, m_pImpl->m_sTypeName, true); + if (!pFieldType) + throw uno::RuntimeException(); + // detect the field type's sub type and set an appropriate number format + if (m_pImpl->m_pProps->bFormatIsDefault && + nsSwGetSetExpType::GSE_STRING == static_cast<SwSetExpFieldType*>(pFieldType)->GetType()) + { + m_pImpl->m_pProps->nFormat = -1; + } + SwSetExpField *const pSEField = new SwSetExpField( + static_cast<SwSetExpFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar2, + m_pImpl->m_pProps->nUSHORT2 != USHRT_MAX ? //#i79471# the field can have a number format or a number_ing_ format + m_pImpl->m_pProps->nUSHORT2 : m_pImpl->m_pProps->nFormat); + xField.reset(pSEField); + + sal_uInt16 nSubType = xField->GetSubType(); + if (m_pImpl->m_pProps->bBool2) + nSubType &= ~nsSwExtendedSubType::SUB_INVISIBLE; + else + nSubType |= nsSwExtendedSubType::SUB_INVISIBLE; + if (m_pImpl->m_pProps->bBool3) + nSubType |= nsSwExtendedSubType::SUB_CMD; + else + nSubType &= ~nsSwExtendedSubType::SUB_CMD; + xField->SetSubType(nSubType); + pSEField->SetSeqNumber(m_pImpl->m_pProps->nUSHORT1); + pSEField->SetInputFlag(m_pImpl->m_pProps->bBool1); + pSEField->SetPromptText(m_pImpl->m_pProps->sPar3); + if (!m_pImpl->m_pProps->sPar4.isEmpty()) + pSEField->ChgExpStr(m_pImpl->m_pProps->sPar4, nullptr); + + } + break; + case SwServiceType::FieldTypeGetExp: + { + sal_uInt16 nSubType; + switch (m_pImpl->m_pProps->nSubType) + { + case text::SetVariableType::STRING: nSubType = nsSwGetSetExpType::GSE_STRING; break; + case text::SetVariableType::VAR: nSubType = nsSwGetSetExpType::GSE_EXPR; break; + //case text::SetVariableType::SEQUENCE: nSubType = nsSwGetSetExpType::GSE_SEQ; break; + case text::SetVariableType::FORMULA: nSubType = nsSwGetSetExpType::GSE_FORMULA; break; + default: + OSL_FAIL("wrong value"); + nSubType = nsSwGetSetExpType::GSE_EXPR; + } + //make sure the SubType matches the field type + SwFieldType* pSetExpField = pDoc->getIDocumentFieldsAccess().GetFieldType( + SwFieldIds::SetExp, m_pImpl->m_pProps->sPar1, false); + bool bSetGetExpFieldUninitialized = false; + if (pSetExpField) + { + if (nSubType != nsSwGetSetExpType::GSE_STRING && + static_cast< SwSetExpFieldType* >(pSetExpField)->GetType() == nsSwGetSetExpType::GSE_STRING) + nSubType = nsSwGetSetExpType::GSE_STRING; + } + else + bSetGetExpFieldUninitialized = true; // #i82544# + + if (m_pImpl->m_pProps->bBool2) + nSubType |= nsSwExtendedSubType::SUB_CMD; + else + nSubType &= ~nsSwExtendedSubType::SUB_CMD; + SwGetExpField *const pGEField = new SwGetExpField( + static_cast<SwGetExpFieldType*>( + pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::GetExp)), + m_pImpl->m_pProps->sPar1, nSubType, + m_pImpl->m_pProps->nFormat); + xField.reset(pGEField); + //TODO: evaluate SubType! + if (!m_pImpl->m_pProps->sPar4.isEmpty()) + pGEField->ChgExpStr(m_pImpl->m_pProps->sPar4, nullptr); + // #i82544# + if (bSetGetExpFieldUninitialized) + pGEField->SetLateInitialization(); + } + break; + case SwServiceType::FieldTypeInputUser: + case SwServiceType::FieldTypeInput: + { + SwFieldType* pFieldType = + pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Input, m_pImpl->m_sTypeName, true); + if (!pFieldType) + throw uno::RuntimeException(); + sal_uInt16 nInpSubType = + sal::static_int_cast<sal_uInt16>( + SwServiceType::FieldTypeInputUser == m_pImpl->m_nServiceId + ? INP_USR : INP_TXT); + SwInputField * pTextField = + new SwInputField(static_cast<SwInputFieldType*>(pFieldType), + m_pImpl->m_pProps->sPar1, + m_pImpl->m_pProps->sPar2, + nInpSubType); + pTextField->SetHelp(m_pImpl->m_pProps->sPar3); + pTextField->SetToolTip(m_pImpl->m_pProps->sPar4); + + xField.reset(pTextField); + } + break; + case SwServiceType::FieldTypeMacro: + { + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Macro); + OUString aName; + + // support for Scripting Framework macros + if (!m_pImpl->m_pProps->sPar4.isEmpty()) + { + aName = m_pImpl->m_pProps->sPar4; + } + else + { + SwMacroField::CreateMacroString(aName, + m_pImpl->m_pProps->sPar1, m_pImpl->m_pProps->sPar3); + } + xField.reset(new SwMacroField(static_cast<SwMacroFieldType*>(pFieldType), aName, + m_pImpl->m_pProps->sPar2)); + } + break; + case SwServiceType::FieldTypePageCount: + case SwServiceType::FieldTypeParagraphCount: + case SwServiceType::FieldTypeWordCount: + case SwServiceType::FieldTypeCharacterCount: + case SwServiceType::FieldTypeTableCount: + case SwServiceType::FieldTypeGraphicObjectCount: + case SwServiceType::FieldTypeEmbeddedObjectCount: + { + sal_uInt16 nSubType = DS_PAGE; + switch (m_pImpl->m_nServiceId) + { + case SwServiceType::FieldTypeParagraphCount : nSubType = DS_PARA; break; + case SwServiceType::FieldTypeWordCount : nSubType = DS_WORD; break; + case SwServiceType::FieldTypeCharacterCount : nSubType = DS_CHAR; break; + case SwServiceType::FieldTypeTableCount : nSubType = DS_TBL; break; + case SwServiceType::FieldTypeGraphicObjectCount : nSubType = DS_GRF; break; + case SwServiceType::FieldTypeEmbeddedObjectCount : nSubType = DS_OLE; break; + default: break; + } + SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DocStat); + xField.reset(new SwDocStatField( + static_cast<SwDocStatFieldType*>(pFieldType), + nSubType, m_pImpl->m_pProps->nUSHORT2)); + } + break; + case SwServiceType::FieldTypeBibliography: + { + SwAuthorityFieldType const type(pDoc); + xField.reset(new SwAuthorityField(static_cast<SwAuthorityFieldType*>( + pDoc->getIDocumentFieldsAccess().InsertFieldType(type)), + OUString())); + if (m_pImpl->m_pProps->aPropSeq.hasElements()) + { + uno::Any aVal; + aVal <<= m_pImpl->m_pProps->aPropSeq; + xField->PutValue( aVal, FIELD_PROP_PROP_SEQ ); + } + } + break; + case SwServiceType::FieldTypeCombinedCharacters: + // create field + xField.reset(new SwCombinedCharField( static_cast<SwCombinedCharFieldType*>( + pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::CombinedChars)), + m_pImpl->m_pProps->sPar1)); + break; + case SwServiceType::FieldTypeDropdown: + { + SwDropDownField *const pDDField = new SwDropDownField( + static_cast<SwDropDownFieldType *>( + pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Dropdown))); + xField.reset(pDDField); + + pDDField->SetItems(m_pImpl->m_pProps->aStrings); + pDDField->SetSelectedItem(m_pImpl->m_pProps->sPar1); + pDDField->SetName(m_pImpl->m_pProps->sPar2); + pDDField->SetHelp(m_pImpl->m_pProps->sPar3); + pDDField->SetToolTip(m_pImpl->m_pProps->sPar4); + } + break; + + case SwServiceType::FieldTypeTableFormula: + { + // create field + sal_uInt16 nType = nsSwGetSetExpType::GSE_FORMULA; + if (m_pImpl->m_pProps->bBool1) + { + nType |= nsSwExtendedSubType::SUB_CMD; + if (m_pImpl->m_pProps->bFormatIsDefault) + m_pImpl->m_pProps->nFormat = -1; + } + xField.reset(new SwTableField( static_cast<SwTableFieldType*>( + pDoc->getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Table)), + m_pImpl->m_pProps->sPar2, + nType, + m_pImpl->m_pProps->nFormat)); + static_cast<SwTableField*>(xField.get())->ChgExpStr(m_pImpl->m_pProps->sPar1); + } + break; + default: OSL_FAIL("What kind of type is that?"); + } + + if (!xField) + throw uno::RuntimeException("no SwField created?"); + + xField->SetAutomaticLanguage(!m_pImpl->m_pProps->bBool4); + SwFormatField aFormat(*xField); + + UnoActionContext aCont(pDoc); + if (aPam.HasMark() && + m_pImpl->m_nServiceId != SwServiceType::FieldTypeAnnotation) + { + pDoc->getIDocumentContentOperations().DeleteAndJoin(aPam); + } + + SwXTextCursor const*const pTextCursor(dynamic_cast<SwXTextCursor*>(pCursor)); + const bool bForceExpandHints( + pTextCursor + && pTextCursor->IsAtEndOfMeta() ); + const SetAttrMode nInsertFlags = + bForceExpandHints + ? SetAttrMode::FORCEHINTEXPAND + : SetAttrMode::DEFAULT; + + if (*aPam.GetPoint() != *aPam.GetMark() && + m_pImpl->m_nServiceId == SwServiceType::FieldTypeAnnotation) + { + // Make sure we always insert the field at the end + SwPaM aEnd(*aPam.End(), *aPam.End()); + pDoc->getIDocumentContentOperations().InsertPoolItem(aEnd, aFormat, nInsertFlags); + } + else + pDoc->getIDocumentContentOperations().InsertPoolItem(aPam, aFormat, nInsertFlags); + + SwTextAttr* pTextAttr = aPam.GetNode().GetTextNode()->GetFieldTextAttrAt( aPam.GetPoint()->nContent.GetIndex()-1, true ); + + // What about updating the fields? (see fldmgr.cxx) + if (!pTextAttr) + throw uno::RuntimeException("no SwTextAttr inserted?"); // could theoretically happen, if paragraph is full + + const SwFormatField& rField = pTextAttr->GetFormatField(); + m_pImpl->SetFormatField(const_cast<SwFormatField*>(&rField), pDoc); + + if ( pTextAttr->Which() == RES_TXTATR_ANNOTATION + && *aPam.GetPoint() != *aPam.GetMark() ) + { + // create annotation mark + const SwPostItField* pPostItField = dynamic_cast< const SwPostItField* >(pTextAttr->GetFormatField().GetField()); + OSL_ENSURE( pPostItField != nullptr, "<SwXTextField::attachToRange(..)> - annotation field missing!" ); + if ( pPostItField != nullptr ) + { + IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess(); + pMarksAccess->makeAnnotationMark( aPam, pPostItField->GetName() ); + } + } + + xField.reset(); + + assert(m_pImpl->GetFormatField()); + m_pImpl->m_pDoc = pDoc; + m_pImpl->GetFormatField()->SetXTextField(this); + m_pImpl->m_wThis = *this; + m_pImpl->m_bIsDescriptor = false; + m_pImpl->ClearFieldType(); + m_pImpl->m_pProps.reset(); + if (m_pImpl->m_bCallUpdate) + update(); + } + else if ( !m_pImpl->IsDescriptor() + && m_pImpl->m_pDoc != nullptr + && m_pImpl->m_nServiceId == SwServiceType::FieldTypeAnnotation ) + { + SwUnoInternalPaM aIntPam( *m_pImpl->m_pDoc ); + if ( !::sw::XTextRangeToSwPaM( aIntPam, xTextRange ) ) + throw lang::IllegalArgumentException(); + + // Nothing to do, if the text range has a separate start and end, but they have the same + // value. + if (!aIntPam.HasMark() || *aIntPam.Start() != *aIntPam.End()) + { + UnoActionContext aCont( m_pImpl->m_pDoc ); + // insert copy of annotation at new text range + std::unique_ptr<SwPostItField> pPostItField(static_cast< SwPostItField* >(m_pImpl->GetFormatField()->GetField()->CopyField().release())); + SwFormatField aFormatField( *pPostItField ); + pPostItField.reset(); + SwPaM aEnd( *aIntPam.End(), *aIntPam.End() ); + m_pImpl->m_pDoc->getIDocumentContentOperations().InsertPoolItem( aEnd, aFormatField ); + // delete former annotation + { + const SwTextField* pTextField = m_pImpl->GetFormatField()->GetTextField(); + SwTextNode& rTextNode = *pTextField->GetpTextNode(); + SwPaM aPam( rTextNode, pTextField->GetStart() ); + aPam.SetMark(); + aPam.Move(); + m_pImpl->m_pDoc->getIDocumentContentOperations().DeleteAndJoin(aPam); + } + // keep inserted annotation + { + SwTextField* pTextAttr = aEnd.GetNode().GetTextNode()->GetFieldTextAttrAt( aEnd.End()->nContent.GetIndex()-1, true ); + if ( pTextAttr != nullptr ) + { + m_pImpl->SetFormatField(const_cast<SwFormatField*>(&pTextAttr->GetFormatField()), m_pImpl->m_pDoc); + + if ( *aIntPam.GetPoint() != *aIntPam.GetMark() ) + { + // create annotation mark + const SwPostItField* pField = dynamic_cast< const SwPostItField* >(pTextAttr->GetFormatField().GetField()); + OSL_ENSURE( pField != nullptr, "<SwXTextField::attach(..)> - annotation field missing!" ); + if ( pField != nullptr ) + { + IDocumentMarkAccess* pMarksAccess = aIntPam.GetDoc()->getIDocumentMarkAccess(); + pMarksAccess->makeAnnotationMark( aIntPam, pField->GetName() ); + } + } + } + } + } + + } + else + throw lang::IllegalArgumentException(); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXTextField::getAnchor() +{ + SolarMutexGuard aGuard; + + SwField const*const pField = m_pImpl->GetField(); + if (!pField) + return nullptr; + + const SwTextField* pTextField = m_pImpl->GetFormatField()->GetTextField(); + if (!pTextField) + throw uno::RuntimeException(); + + std::shared_ptr< SwPaM > pPamForTextField; + SwTextField::GetPamForTextField(*pTextField, pPamForTextField); + if (pPamForTextField == nullptr) + return nullptr; + + // If this is a postit field, then return the range of its annotation mark if it has one. + if (pField->Which() == SwFieldIds::Postit) + { + const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField); + IDocumentMarkAccess* pMarkAccess = m_pImpl->m_pDoc->getIDocumentMarkAccess(); + for (IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getAnnotationMarksBegin(); ppMark != pMarkAccess->getAnnotationMarksEnd(); ++ppMark) + { + if ((*ppMark)->GetName() == pPostItField->GetName()) + { + pPamForTextField = std::make_shared<SwPaM>((*ppMark)->GetMarkStart(), (*ppMark)->GetMarkEnd()); + break; + } + } + } + + uno::Reference<text::XTextRange> xRange = SwXTextRange::CreateXTextRange( + *m_pImpl->m_pDoc, *(pPamForTextField->GetPoint()), pPamForTextField->GetMark()); + return xRange; +} + +void SAL_CALL SwXTextField::dispose() +{ + SolarMutexGuard aGuard; + SwField const*const pField = m_pImpl->GetField(); + if(pField && m_pImpl->m_pDoc) + { + UnoActionContext aContext(m_pImpl->m_pDoc); + assert(m_pImpl->GetFormatField()->GetTextField() && "<SwXTextField::dispose()> - missing <SwTextField> --> crash"); + SwTextField::DeleteTextField(*(m_pImpl->GetFormatField()->GetTextField())); + } + + if (m_pImpl->m_xTextObject.is()) + { + m_pImpl->m_xTextObject->DisposeEditSource(); + m_pImpl->m_xTextObject.clear(); + } + m_pImpl->Invalidate(); +} + +void SAL_CALL SwXTextField::addEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXTextField::removeEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXTextField::getPropertySetInfo() +{ + SolarMutexGuard aGuard; + // no static + uno::Reference< beans::XPropertySetInfo > aRef; + if (m_pImpl->m_nServiceId == SwServiceType::Invalid) + { + throw uno::RuntimeException(); + } + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet( + lcl_GetPropertyMapOfService(m_pImpl->m_nServiceId)); + const uno::Reference<beans::XPropertySetInfo>& xInfo = pPropSet->getPropertySetInfo(); + // extend PropertySetInfo! + const uno::Sequence<beans::Property> aPropSeq = xInfo->getProperties(); + aRef = new SfxExtItemPropertySetInfo( + aSwMapProvider.GetPropertyMapEntries(PROPERTY_MAP_PARAGRAPH_EXTENSIONS), + aPropSeq ); + return aRef; +} + +void SAL_CALL +SwXTextField::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + SwField const*const pField = m_pImpl->GetField(); + const SfxItemPropertySet* _pPropSet = aSwMapProvider.GetPropertySet( + lcl_GetPropertyMapOfService(m_pImpl->m_nServiceId)); + const SfxItemPropertySimpleEntry* pEntry = _pPropSet->getPropertyMap().getByName(rPropertyName); + + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException( "Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if(pField) + { + // special treatment for mail merge fields + const SwFieldIds nWhich = pField->Which(); + if( SwFieldIds::Database == nWhich && + (rPropertyName == UNO_NAME_DATA_BASE_NAME || + rPropertyName == UNO_NAME_DATA_BASE_URL|| + rPropertyName == UNO_NAME_DATA_TABLE_NAME|| + rPropertyName == UNO_NAME_DATA_COLUMN_NAME)) + { + // here a new field type must be created and the field must + // be registered at the new type + OSL_FAIL("not implemented"); + } + else + { + SwDoc * pDoc = m_pImpl->m_pDoc; + assert(pDoc); + const SwTextField* pTextField = m_pImpl->GetFormatField()->GetTextField(); + if(!pTextField) + throw uno::RuntimeException(); + SwPosition aPosition( pTextField->GetTextNode() ); + aPosition.nContent = pTextField->GetStart(); + pDoc->getIDocumentFieldsAccess().PutValueToField( aPosition, rValue, pEntry->nWID); + } + + //#i100374# notify SwPostIt about new field content + assert(m_pImpl->GetFormatField()); + if (SwFieldIds::Postit == nWhich) + { + m_pImpl->GetFormatField()->Broadcast( + SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::CHANGED )); + } + + // fdo#42073 notify SwTextField about changes of the expanded string + if (m_pImpl->GetFormatField()->GetTextField()) + { + m_pImpl->GetFormatField()->GetTextField()->ExpandTextField(); + } + + //#i100374# changing a document field should set the modify flag + SwDoc* pDoc = m_pImpl->m_pDoc; + if (pDoc) + pDoc->getIDocumentState().SetModified(); + + } + else if (m_pImpl->m_pProps) + { + bool* pBool = nullptr; + switch(pEntry->nWID) + { + case FIELD_PROP_PAR1: + rValue >>= m_pImpl->m_pProps->sPar1; + break; + case FIELD_PROP_PAR2: + rValue >>= m_pImpl->m_pProps->sPar2; + break; + case FIELD_PROP_PAR3: + rValue >>= m_pImpl->m_pProps->sPar3; + break; + case FIELD_PROP_PAR4: + rValue >>= m_pImpl->m_pProps->sPar4; + break; + case FIELD_PROP_FORMAT: + rValue >>= m_pImpl->m_pProps->nFormat; + m_pImpl->m_pProps->bFormatIsDefault = false; + break; + case FIELD_PROP_SUBTYPE: + m_pImpl->m_pProps->nSubType = SWUnoHelper::GetEnumAsInt32(rValue); + break; + case FIELD_PROP_BYTE1 : + rValue >>= m_pImpl->m_pProps->nByte1; + break; + case FIELD_PROP_BOOL1 : + pBool = &m_pImpl->m_pProps->bBool1; + break; + case FIELD_PROP_BOOL2 : + pBool = &m_pImpl->m_pProps->bBool2; + break; + case FIELD_PROP_BOOL3 : + pBool = &m_pImpl->m_pProps->bBool3; + break; + case FIELD_PROP_BOOL4: + pBool = &m_pImpl->m_pProps->bBool4; + break; + case FIELD_PROP_DATE : + { + auto aTemp = o3tl::tryAccess<util::Date>(rValue); + if(!aTemp) + throw lang::IllegalArgumentException(); + + m_pImpl->m_pProps->aDate = Date(aTemp->Day, aTemp->Month, aTemp->Year); + } + break; + case FIELD_PROP_USHORT1: + case FIELD_PROP_USHORT2: + { + sal_Int16 nVal = 0; + rValue >>= nVal; + if( FIELD_PROP_USHORT1 == pEntry->nWID) + m_pImpl->m_pProps->nUSHORT1 = nVal; + else + m_pImpl->m_pProps->nUSHORT2 = nVal; + } + break; + case FIELD_PROP_SHORT1: + rValue >>= m_pImpl->m_pProps->nSHORT1; + break; + case FIELD_PROP_DOUBLE: + if(rValue.getValueType() != ::cppu::UnoType<double>::get()) + throw lang::IllegalArgumentException(); + rValue >>= m_pImpl->m_pProps->fDouble; + break; + + case FIELD_PROP_DATE_TIME : + if (!m_pImpl->m_pProps->pDateTime) + m_pImpl->m_pProps->pDateTime.reset( new util::DateTime ); + rValue >>= *m_pImpl->m_pProps->pDateTime; + break; + case FIELD_PROP_PROP_SEQ: + rValue >>= m_pImpl->m_pProps->aPropSeq; + break; + case FIELD_PROP_STRINGS: + rValue >>= m_pImpl->m_pProps->aStrings; + break; + } + if (pBool) + { + auto b = o3tl::tryAccess<bool>(rValue); + if( !b ) + throw lang::IllegalArgumentException(); + *pBool = *b; + + } + } + else + throw uno::RuntimeException(); +} + +uno::Any SAL_CALL SwXTextField::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwField const*const pField = m_pImpl->GetField(); + const SfxItemPropertySet* _pPropSet = aSwMapProvider.GetPropertySet( + lcl_GetPropertyMapOfService(m_pImpl->m_nServiceId)); + const SfxItemPropertySimpleEntry* pEntry = _pPropSet->getPropertyMap().getByName(rPropertyName); + if(!pEntry ) + { + const SfxItemPropertySet* _pParaPropSet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_PARAGRAPH_EXTENSIONS); + pEntry = _pParaPropSet->getPropertyMap().getByName(rPropertyName); + } + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + switch( pEntry->nWID ) + { + case FN_UNO_TEXT_WRAP: + aRet <<= text::WrapTextMode_NONE; + break; + case FN_UNO_ANCHOR_TYPE: + aRet <<= text::TextContentAnchorType_AS_CHARACTER; + break; + case FN_UNO_ANCHOR_TYPES: + { + uno::Sequence<text::TextContentAnchorType> aTypes(1); + text::TextContentAnchorType* pArray = aTypes.getArray(); + pArray[0] = text::TextContentAnchorType_AS_CHARACTER; + aRet <<= aTypes; + } + break; + + default: + if( pField ) + { + if (FIELD_PROP_IS_FIELD_USED == pEntry->nWID || + FIELD_PROP_IS_FIELD_DISPLAYED == pEntry->nWID) + { + bool bIsFieldUsed = false; + bool bIsFieldDisplayed = false; + + // in order to have the information about fields + // correctly evaluated the document needs a layout + // (has to be already formatted) + SwDoc *pDoc = m_pImpl->m_pDoc; + SwViewShell *pViewShell = nullptr; + SwEditShell *pEditShell = nullptr; + if( pDoc ) + { + pViewShell = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + pEditShell = pDoc->GetEditShell(); + } + + if (pEditShell) + pEditShell->CalcLayout(); + else if (pViewShell) // a page preview has no SwEditShell it should only have a view shell + pViewShell->CalcLayout(); + else + throw uno::RuntimeException(); + + // get text node for the text field + const SwFormatField *pFieldFormat = + (m_pImpl->GetField()) ? m_pImpl->GetFormatField() : nullptr; + const SwTextField* pTextField = pFieldFormat + ? m_pImpl->GetFormatField()->GetTextField() : nullptr; + if(!pTextField) + throw uno::RuntimeException(); + const SwTextNode& rTextNode = pTextField->GetTextNode(); + + // skip fields that are currently not in the document + // e.g. fields in undo or redo array + if (rTextNode.GetNodes().IsDocNodes()) + { + bool bFrame = 0 != rTextNode.FindLayoutRect().Width(); // or so + bool bHidden = rTextNode.IsHidden(); + if ( !bHidden ) + { + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + bHidden = SwScriptInfo::GetBoundsOfHiddenRange( pTextField->GetTextNode(), + pTextField->GetStart(), + nHiddenStart, nHiddenEnd ); + } + + // !bFrame && !bHidden: most probably a field in an unused page style + + // FME: Problem: hidden field in unused page template => + // bIsFieldUsed = true + // bIsFieldDisplayed = false + bIsFieldUsed = bFrame || bHidden; + bIsFieldDisplayed = bIsFieldUsed && !bHidden; + } + aRet <<= (FIELD_PROP_IS_FIELD_USED == pEntry->nWID) ? bIsFieldUsed : bIsFieldDisplayed; + } + else + pField->QueryValue( aRet, pEntry->nWID ); + } + else if (m_pImpl->m_pProps) // currently just a descriptor... + { + switch(pEntry->nWID) + { + case FIELD_PROP_TEXT: + { + if (!m_pImpl->m_xTextObject.is()) + { + m_pImpl->m_xTextObject + = new SwTextAPIObject( std::make_unique<SwTextAPIEditSource>(m_pImpl->m_pDoc) ); + } + + uno::Reference<text::XText> xText(m_pImpl->m_xTextObject.get()); + aRet <<= xText; + break; + } + case FIELD_PROP_PAR1: + aRet <<= m_pImpl->m_pProps->sPar1; + break; + case FIELD_PROP_PAR2: + aRet <<= m_pImpl->m_pProps->sPar2; + break; + case FIELD_PROP_PAR3: + aRet <<= m_pImpl->m_pProps->sPar3; + break; + case FIELD_PROP_PAR4: + aRet <<= m_pImpl->m_pProps->sPar4; + break; + case FIELD_PROP_FORMAT: + aRet <<= m_pImpl->m_pProps->nFormat; + break; + case FIELD_PROP_SUBTYPE: + aRet <<= m_pImpl->m_pProps->nSubType; + break; + case FIELD_PROP_BYTE1 : + aRet <<= m_pImpl->m_pProps->nByte1; + break; + case FIELD_PROP_BOOL1 : + aRet <<= m_pImpl->m_pProps->bBool1; + break; + case FIELD_PROP_BOOL2 : + aRet <<= m_pImpl->m_pProps->bBool2; + break; + case FIELD_PROP_BOOL3 : + aRet <<= m_pImpl->m_pProps->bBool3; + break; + case FIELD_PROP_BOOL4 : + aRet <<= m_pImpl->m_pProps->bBool4; + break; + case FIELD_PROP_DATE : + aRet <<= m_pImpl->m_pProps->aDate.GetUNODate(); + break; + case FIELD_PROP_USHORT1: + aRet <<= static_cast<sal_Int16>(m_pImpl->m_pProps->nUSHORT1); + break; + case FIELD_PROP_USHORT2: + aRet <<= static_cast<sal_Int16>(m_pImpl->m_pProps->nUSHORT2); + break; + case FIELD_PROP_SHORT1: + aRet <<= m_pImpl->m_pProps->nSHORT1; + break; + case FIELD_PROP_DOUBLE: + aRet <<= m_pImpl->m_pProps->fDouble; + break; + case FIELD_PROP_DATE_TIME : + if (m_pImpl->m_pProps->pDateTime) + aRet <<= *m_pImpl->m_pProps->pDateTime; + break; + case FIELD_PROP_PROP_SEQ: + aRet <<= m_pImpl->m_pProps->aPropSeq; + break; + case FIELD_PROP_STRINGS: + aRet <<= m_pImpl->m_pProps->aStrings; + break; + case FIELD_PROP_IS_FIELD_USED: + case FIELD_PROP_IS_FIELD_DISPLAYED: + aRet <<= false; + break; + } + } + else + throw uno::RuntimeException(); + } + return aRet; +} + +void SwXTextField::addPropertyChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextField::removePropertyChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextField::addVetoableChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextField::removeVetoableChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SwXTextField::update() +{ + SolarMutexGuard aGuard; + SwField * pField = const_cast<SwField*>(m_pImpl->GetField()); + if (pField) + { + switch(pField->Which()) + { + case SwFieldIds::DateTime: + static_cast<SwDateTimeField*>(pField)->SetDateTime( ::DateTime( ::DateTime::SYSTEM ) ); + break; + + case SwFieldIds::ExtUser: + { + SwExtUserField* pExtUserField = static_cast<SwExtUserField*>(pField); + pExtUserField->SetExpansion( SwExtUserFieldType::Expand( + pExtUserField->GetSubType() ) ); + } + break; + + case SwFieldIds::Author: + { + SwAuthorField* pAuthorField = static_cast<SwAuthorField*>(pField); + pAuthorField->SetExpansion( SwAuthorFieldType::Expand( + pAuthorField->GetFormat() ) ); + } + break; + + case SwFieldIds::Filename: + { + SwFileNameField* pFileNameField = static_cast<SwFileNameField*>(pField); + pFileNameField->SetExpansion( static_cast<SwFileNameFieldType*>(pField->GetTyp())->Expand( + pFileNameField->GetFormat() ) ); + } + break; + + case SwFieldIds::DocInfo: + { + SwDocInfoField* pDocInfField = static_cast<SwDocInfoField*>(pField); + pDocInfField->SetExpansion( static_cast<SwDocInfoFieldType*>(pField->GetTyp())->Expand( + pDocInfField->GetSubType(), + pDocInfField->GetFormat(), + pDocInfField->GetLanguage(), + pDocInfField->GetName() ) ); + } + break; + default: break; + } + // Text formatting has to be triggered. + m_pImpl->GetFormatField()->ModifyNotification(nullptr, nullptr); + } + else + m_pImpl->m_bCallUpdate = true; +} + +OUString SAL_CALL SwXTextField::getImplementationName() +{ + return "SwXTextField"; +} + +static OUString OldNameToNewName_Impl( const OUString &rOld ) +{ + static const char aOldNamePart1[] = ".TextField.DocInfo."; + static const char aOldNamePart2[] = ".TextField."; + OUString sServiceNameCC( rOld ); + sal_Int32 nIdx = sServiceNameCC.indexOf( aOldNamePart1 ); + if (nIdx >= 0) + sServiceNameCC = sServiceNameCC.replaceAt( nIdx, strlen(aOldNamePart1), ".textfield.docinfo." ); + nIdx = sServiceNameCC.indexOf( aOldNamePart2 ); + if (nIdx >= 0) + sServiceNameCC = sServiceNameCC.replaceAt( nIdx, strlen(aOldNamePart2), ".textfield." ); + return sServiceNameCC; +} + +sal_Bool SAL_CALL SwXTextField::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwXTextField::getSupportedServiceNames() +{ + const OUString sServiceName = + SwXServiceProvider::GetProviderName(m_pImpl->m_nServiceId); + + // case-corrected version of service-name (see #i67811) + // (need to supply both because of compatibility to older versions) + const OUString sServiceNameCC( OldNameToNewName_Impl( sServiceName ) ); + sal_Int32 nLen = sServiceName == sServiceNameCC ? 2 : 3; + + uno::Sequence< OUString > aRet( nLen ); + OUString* pArray = aRet.getArray(); + *pArray++ = sServiceName; + if (nLen == 3) + *pArray++ = sServiceNameCC; + *pArray++ = "com.sun.star.text.TextContent"; + return aRet; +} + +void SwXTextField::Impl::Invalidate() +{ + EndListeningAll(); + m_pFormatField = nullptr; + m_pDoc = nullptr; + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); +} + +void SwXTextField::Impl::Notify(const SfxHint& rHint) +{ + + if(rHint.GetId() == SfxHintId::Dying) + Invalidate(); + else if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + switch(pLegacyHint->m_pOld ? pLegacyHint->m_pOld->Which() : 0) + { + case RES_REMOVE_UNO_OBJECT: + case RES_OBJECTDYING: + Invalidate(); + break; + } + } +} + +const SwField* SwXTextField::Impl::GetField() const +{ + return m_pFormatField ? m_pFormatField->GetField() : nullptr; +} + +OUString SwXTextFieldMasters::getImplementationName() +{ + return "SwXTextFieldMasters"; +} + +sal_Bool SwXTextFieldMasters::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextFieldMasters::getSupportedServiceNames() +{ + uno::Sequence<OUString> aRet { "com.sun.star.text.TextFieldMasters" }; + return aRet; +} + +SwXTextFieldMasters::SwXTextFieldMasters(SwDoc* _pDoc) : + SwUnoCollection(_pDoc) +{ +} + +SwXTextFieldMasters::~SwXTextFieldMasters() +{ + +} + +/* + Iteration over non-standard field types + USER/SETEXP/DDE/DATABASE + Thus the names are: + "com.sun.star.text.fieldmaster.User" + <field type name> + "com.sun.star.text.fieldmaster.DDE" + <field type name> + "com.sun.star.text.fieldmaster.SetExpression" + <field type name> + "com.sun.star.text.fieldmaster.DataBase" + <field type name> + + If too much, maybe one could leave out the "com.sun.star.text". + */ +static SwFieldIds lcl_GetIdByName( OUString& rName, OUString& rTypeName ) +{ + if (rName.startsWithIgnoreAsciiCase(COM_TEXT_FLDMASTER_CC)) + rName = rName.copy(RTL_CONSTASCII_LENGTH(COM_TEXT_FLDMASTER_CC)); + + SwFieldIds nResId = SwFieldIds::Unknown; + sal_Int32 nIdx = 0; + rTypeName = rName.getToken( 0, '.', nIdx ); + if (rTypeName == "User") + nResId = SwFieldIds::User; + else if (rTypeName == "DDE") + nResId = SwFieldIds::Dde; + else if (rTypeName == "SetExpression") + { + nResId = SwFieldIds::SetExp; + + const OUString sFieldTypName( rName.getToken( 0, '.', nIdx )); + const OUString sUIName( SwStyleNameMapper::GetSpecialExtraUIName( sFieldTypName ) ); + + if( sUIName != sFieldTypName ) + rName = comphelper::string::setToken(rName, 1, '.', sUIName); + } + else if (rTypeName.equalsIgnoreAsciiCase("DataBase")) + { + rName = rName.copy(RTL_CONSTASCII_LENGTH("DataBase.")); + if (!rName.isEmpty()) + { + // #i51815# + rName = "DataBase." + rName; + nResId = SwFieldIds::Database; + } + } + else if (rTypeName == "Bibliography") + nResId = SwFieldIds::TableOfAuthorities; + return nResId; +} + +uno::Any SwXTextFieldMasters::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!GetDoc()) + throw uno::RuntimeException(); + + OUString sName(rName), sTypeName; + const SwFieldIds nResId = lcl_GetIdByName( sName, sTypeName ); + if( SwFieldIds::Unknown == nResId ) + throw container::NoSuchElementException( + "SwXTextFieldMasters::getByName(" + rName + ")", + css::uno::Reference<css::uno::XInterface>()); + + sName = sName.copy(std::min(sTypeName.getLength()+1, sName.getLength())); + SwFieldType* pType = GetDoc()->getIDocumentFieldsAccess().GetFieldType(nResId, sName, true); + if(!pType) + throw container::NoSuchElementException( + "SwXTextFieldMasters::getByName(" + rName + ")", + css::uno::Reference<css::uno::XInterface>()); + + uno::Reference<beans::XPropertySet> const xRet( + SwXFieldMaster::CreateXFieldMaster(GetDoc(), pType)); + return uno::makeAny(xRet); +} + +bool SwXTextFieldMasters::getInstanceName( + const SwFieldType& rFieldType, OUString& rName) +{ + OUString sField; + + switch( rFieldType.Which() ) + { + case SwFieldIds::User: + sField = "User." + rFieldType.GetName(); + break; + case SwFieldIds::Dde: + sField = "DDE." + rFieldType.GetName(); + break; + + case SwFieldIds::SetExp: + sField = "SetExpression." + SwStyleNameMapper::GetSpecialExtraProgName( rFieldType.GetName() ); + break; + + case SwFieldIds::Database: + sField = "DataBase." + rFieldType.GetName().replaceAll(OUStringChar(DB_DELIM), "."); + break; + + case SwFieldIds::TableOfAuthorities: + sField = "Bibliography"; + break; + + default: + return false; + } + + rName += COM_TEXT_FLDMASTER_CC + sField; + return true; +} + +uno::Sequence< OUString > SwXTextFieldMasters::getElementNames() +{ + SolarMutexGuard aGuard; + if(!GetDoc()) + throw uno::RuntimeException(); + + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + const size_t nCount = pFieldTypes->size(); + + std::vector<OUString> aFieldNames; + for( size_t i = 0; i < nCount; ++i ) + { + SwFieldType& rFieldType = *((*pFieldTypes)[i]); + + OUString sFieldName; + if (SwXTextFieldMasters::getInstanceName(rFieldType, sFieldName)) + { + aFieldNames.push_back(sFieldName); + } + } + + return comphelper::containerToSequence(aFieldNames); +} + +sal_Bool SwXTextFieldMasters::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!GetDoc()) + throw uno::RuntimeException(); + + OUString sName(rName), sTypeName; + const SwFieldIds nResId = lcl_GetIdByName( sName, sTypeName ); + bool bRet = false; + if( SwFieldIds::Unknown != nResId ) + { + sName = sName.copy(std::min(sTypeName.getLength()+1, sName.getLength())); + bRet = nullptr != GetDoc()->getIDocumentFieldsAccess().GetFieldType(nResId, sName, true); + } + return bRet; +} + +uno::Type SwXTextFieldMasters::getElementType() +{ + return cppu::UnoType<beans::XPropertySet>::get(); + +} + +sal_Bool SwXTextFieldMasters::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return true; +} + +class SwXTextFieldTypes::Impl +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + ::comphelper::OInterfaceContainerHelper2 m_RefreshListeners; + + Impl() : m_RefreshListeners(m_Mutex) { } +}; + +OUString SwXTextFieldTypes::getImplementationName() +{ + return "SwXTextFieldTypes"; +} + +sal_Bool SwXTextFieldTypes::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextFieldTypes::getSupportedServiceNames() +{ + uno::Sequence<OUString> aRet { "com.sun.star.text.TextFields" }; + return aRet; +} + +SwXTextFieldTypes::SwXTextFieldTypes(SwDoc* _pDoc) + : SwUnoCollection (_pDoc) + , m_pImpl(new Impl) +{ +} + +SwXTextFieldTypes::~SwXTextFieldTypes() +{ +} + +void SwXTextFieldTypes::Invalidate() +{ + SwUnoCollection::Invalidate(); + lang::EventObject const ev(static_cast< ::cppu::OWeakObject&>(*this)); + m_pImpl->m_RefreshListeners.disposeAndClear(ev); +} + +uno::Reference< container::XEnumeration > SwXTextFieldTypes::createEnumeration() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return new SwXFieldEnumeration(*GetDoc()); +} + +uno::Type SwXTextFieldTypes::getElementType() +{ + return cppu::UnoType<text::XDependentTextField>::get(); +} + +sal_Bool SwXTextFieldTypes::hasElements() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return true; // they always exist +} + +void SAL_CALL SwXTextFieldTypes::refresh() +{ + { + SolarMutexGuard aGuard; + if (!IsValid()) + throw uno::RuntimeException(); + UnoActionContext aContext(GetDoc()); + GetDoc()->getIDocumentStatistics().UpdateDocStat( false, true ); + GetDoc()->getIDocumentFieldsAccess().UpdateFields(false); + } + // call refresh listeners (without SolarMutex locked) + lang::EventObject const event(static_cast< ::cppu::OWeakObject*>(this)); + m_pImpl->m_RefreshListeners.notifyEach( + & util::XRefreshListener::refreshed, event); +} + +void SAL_CALL SwXTextFieldTypes::addRefreshListener( + const uno::Reference<util::XRefreshListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_RefreshListeners.addInterface(xListener); +} + +void SAL_CALL SwXTextFieldTypes::removeRefreshListener( + const uno::Reference<util::XRefreshListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_RefreshListeners.removeInterface(xListener); +} + +class SwXFieldEnumeration::Impl + : public SvtListener +{ +public: + SwDoc* m_pDoc; + std::vector<uno::Reference<text::XTextField>> m_Items; + sal_Int32 m_nNextIndex; ///< index of next element to be returned + + explicit Impl(SwDoc& rDoc) + : m_pDoc(&rDoc) + , m_nNextIndex(0) + { + StartListening(rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); + } + + virtual void Notify(const SfxHint& rHint) override + { + if(rHint.GetId() == SfxHintId::Dying) + m_pDoc = nullptr; + } +}; + +OUString SAL_CALL +SwXFieldEnumeration::getImplementationName() +{ + return "SwXFieldEnumeration"; +} + +sal_Bool SAL_CALL SwXFieldEnumeration::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> SAL_CALL +SwXFieldEnumeration::getSupportedServiceNames() +{ + return { "com.sun.star.text.FieldEnumeration" }; +} + +SwXFieldEnumeration::SwXFieldEnumeration(SwDoc & rDoc) + : m_pImpl(new Impl(rDoc)) +{ + // build sequence + m_pImpl->m_Items.clear(); + + const SwFieldTypes* pFieldTypes = m_pImpl->m_pDoc->getIDocumentFieldsAccess().GetFieldTypes(); + const size_t nCount = pFieldTypes->size(); + for(size_t nType = 0; nType < nCount; ++nType) + { + const SwFieldType* pCurType = (*pFieldTypes)[nType].get(); + std::vector<SwFormatField*> vFormatFields; + pCurType->GatherFields(vFormatFields); + std::for_each(vFormatFields.begin(), vFormatFields.end(), + [this](SwFormatField* pF) { m_pImpl->m_Items.push_back(SwXTextField::CreateXTextField(m_pImpl->m_pDoc, pF)); }); + } + // now handle meta-fields, which are not SwFields + const std::vector< uno::Reference<text::XTextField> > MetaFields( + m_pImpl->m_pDoc->GetMetaFieldManager().getMetaFields() ); + for (const auto & rMetaField : MetaFields) + { + m_pImpl->m_Items.push_back( rMetaField ); + } +} + +SwXFieldEnumeration::~SwXFieldEnumeration() +{ +} + +sal_Bool SAL_CALL SwXFieldEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + + return m_pImpl->m_nNextIndex < static_cast<sal_Int32>(m_pImpl->m_Items.size()); +} + +uno::Any SAL_CALL SwXFieldEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if (m_pImpl->m_nNextIndex >= static_cast<sal_Int32>(m_pImpl->m_Items.size())) + throw container::NoSuchElementException( + "SwXFieldEnumeration::nextElement", + css::uno::Reference<css::uno::XInterface>()); + + uno::Reference< text::XTextField > &rxField = + m_pImpl->m_Items[ m_pImpl->m_nNextIndex++ ]; + uno::Any aRet; + aRet <<= rxField; + rxField = nullptr; // free memory for item that is no longer used + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoflatpara.cxx b/sw/source/core/unocore/unoflatpara.cxx new file mode 100644 index 000000000..15ffb4441 --- /dev/null +++ b/sw/source/core/unocore/unoflatpara.cxx @@ -0,0 +1,593 @@ +/* -*- 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 <unobaseclass.hxx> +#include <unocrsrhelper.hxx> +#include <unoflatpara.hxx> + +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/text/TextMarkupType.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <unotextmarkup.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <viewsh.hxx> +#include <viewimp.hxx> +#include <breakit.hxx> +#include <pam.hxx> +#include <unotextrange.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <IGrammarContact.hxx> +#include <viewopt.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/text/XTextRange.hpp> + +using namespace ::com::sun::star; + +namespace SwUnoCursorHelper { + +uno::Reference<text::XFlatParagraphIterator> +CreateFlatParagraphIterator(SwDoc & rDoc, sal_Int32 const nTextMarkupType, + bool const bAutomatic) +{ + return new SwXFlatParagraphIterator(rDoc, nTextMarkupType, bAutomatic); +} + +} + +SwXFlatParagraph::SwXFlatParagraph( SwTextNode& rTextNode, const OUString& aExpandText, const ModelToViewHelper& rMap ) + : SwXFlatParagraph_Base(& rTextNode, rMap) + , maExpandText(aExpandText) +{ +} + +SwXFlatParagraph::~SwXFlatParagraph() +{ +} + + +// XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXFlatParagraph::getPropertySetInfo() +{ + static comphelper::PropertyMapEntry s_Entries[] = { + { OUString("FieldPositions"), -1, ::cppu::UnoType<uno::Sequence<sal_Int32>>::get(), beans::PropertyAttribute::READONLY, 0 }, + { OUString("FootnotePositions"), -1, ::cppu::UnoType<uno::Sequence<sal_Int32>>::get(), beans::PropertyAttribute::READONLY, 0 }, + { OUString(), -1, css::uno::Type(), 0, 0 } + }; + return new comphelper::PropertySetInfo(s_Entries); +} + +void SAL_CALL +SwXFlatParagraph::setPropertyValue(const OUString&, const uno::Any&) +{ + throw lang::IllegalArgumentException("no values can be set", + static_cast< ::cppu::OWeakObject*>(this), 0); +} + +uno::Any SAL_CALL +SwXFlatParagraph::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard g; + + if (rPropertyName == "FieldPositions") + { + return uno::makeAny( comphelper::containerToSequence( GetConversionMap().getFieldPositions() ) ); + } + else if (rPropertyName == "FootnotePositions") + { + return uno::makeAny( comphelper::containerToSequence( GetConversionMap().getFootnotePositions() ) ); + } + return uno::Any(); +} + +void SAL_CALL +SwXFlatParagraph::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + SAL_WARN("sw.uno", + "SwXFlatParagraph::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXFlatParagraph::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + SAL_WARN("sw.uno", + "SwXFlatParagraph::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXFlatParagraph::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + SAL_WARN("sw.uno", + "SwXFlatParagraph::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXFlatParagraph::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + SAL_WARN("sw.uno", + "SwXFlatParagraph::removeVetoableChangeListener(): not implemented"); +} + + +css::uno::Reference< css::container::XStringKeyMap > SAL_CALL SwXFlatParagraph::getMarkupInfoContainer() +{ + return SwXTextMarkup::getMarkupInfoContainer(); +} + +void SAL_CALL SwXFlatParagraph::commitTextRangeMarkup(::sal_Int32 nType, const OUString & aIdentifier, const uno::Reference< text::XTextRange> & xRange, + const css::uno::Reference< css::container::XStringKeyMap > & xMarkupInfoContainer) +{ + SolarMutexGuard aGuard; + SwXTextMarkup::commitTextRangeMarkup( nType, aIdentifier, xRange, xMarkupInfoContainer ); +} + +void SAL_CALL SwXFlatParagraph::commitStringMarkup(::sal_Int32 nType, const OUString & rIdentifier, ::sal_Int32 nStart, ::sal_Int32 nLength, const css::uno::Reference< css::container::XStringKeyMap > & rxMarkupInfoContainer) +{ + SolarMutexGuard aGuard; + SwXTextMarkup::commitStringMarkup( nType, rIdentifier, nStart, nLength, rxMarkupInfoContainer ); +} + +// text::XFlatParagraph: +OUString SAL_CALL SwXFlatParagraph::getText() +{ + return maExpandText; +} + +// text::XFlatParagraph: +void SAL_CALL SwXFlatParagraph::setChecked( ::sal_Int32 nType, sal_Bool bVal ) +{ + SolarMutexGuard aGuard; + + if (GetTextNode()) + { + if ( text::TextMarkupType::SPELLCHECK == nType ) + { + GetTextNode()->SetWrongDirty( + bVal ? SwTextNode::WrongState::DONE : SwTextNode::WrongState::TODO); + } + else if ( text::TextMarkupType::SMARTTAG == nType ) + GetTextNode()->SetSmartTagDirty( !bVal ); + else if( text::TextMarkupType::PROOFREADING == nType ) + { + GetTextNode()->SetGrammarCheckDirty( !bVal ); + if( bVal ) + ::finishGrammarCheck( *GetTextNode() ); + } + } +} + +// text::XFlatParagraph: +sal_Bool SAL_CALL SwXFlatParagraph::isChecked( ::sal_Int32 nType ) +{ + SolarMutexGuard aGuard; + if (GetTextNode()) + { + if ( text::TextMarkupType::SPELLCHECK == nType ) + return !GetTextNode()->IsWrongDirty(); + else if ( text::TextMarkupType::PROOFREADING == nType ) + return !GetTextNode()->IsGrammarCheckDirty(); + else if ( text::TextMarkupType::SMARTTAG == nType ) + return !GetTextNode()->IsSmartTagDirty(); + } + + return true; +} + +// text::XFlatParagraph: +sal_Bool SAL_CALL SwXFlatParagraph::isModified() +{ + SolarMutexGuard aGuard; + return nullptr == GetTextNode(); +} + +// text::XFlatParagraph: +lang::Locale SAL_CALL SwXFlatParagraph::getLanguageOfText(::sal_Int32 nPos, ::sal_Int32 nLen) +{ + SolarMutexGuard aGuard; + if (!GetTextNode()) + return LanguageTag::convertToLocale( LANGUAGE_NONE ); + + const lang::Locale aLocale( SW_BREAKITER()->GetLocale( GetTextNode()->GetLang(nPos, nLen) ) ); + return aLocale; +} + +// text::XFlatParagraph: +lang::Locale SAL_CALL SwXFlatParagraph::getPrimaryLanguageOfText(::sal_Int32 nPos, ::sal_Int32 nLen) +{ + SolarMutexGuard aGuard; + + if (!GetTextNode()) + return LanguageTag::convertToLocale( LANGUAGE_NONE ); + + const lang::Locale aLocale( SW_BREAKITER()->GetLocale( GetTextNode()->GetLang(nPos, nLen) ) ); + return aLocale; +} + +// text::XFlatParagraph: +void SAL_CALL SwXFlatParagraph::changeText(::sal_Int32 nPos, ::sal_Int32 nLen, const OUString & aNewText, const css::uno::Sequence< css::beans::PropertyValue > & aAttributes) +{ + SolarMutexGuard aGuard; + + if (!GetTextNode()) + return; + + SwTextNode *const pOldTextNode = GetTextNode(); + + if (nPos < 0 || pOldTextNode->Len() < nPos || nLen < 0 || o3tl::make_unsigned(pOldTextNode->Len()) < static_cast<sal_uInt32>(nPos) + nLen) + { + throw lang::IllegalArgumentException(); + } + + SwPaM aPaM( *GetTextNode(), nPos, *GetTextNode(), nPos+nLen ); + + UnoActionContext aAction( GetTextNode()->GetDoc() ); + + const uno::Reference< text::XTextRange > xRange = + SwXTextRange::CreateXTextRange( + *GetTextNode()->GetDoc(), *aPaM.GetPoint(), aPaM.GetMark() ); + uno::Reference< beans::XPropertySet > xPropSet( xRange, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + for ( const auto& rAttribute : aAttributes ) + xPropSet->setPropertyValue( rAttribute.Name, rAttribute.Value ); + } + + IDocumentContentOperations& rIDCO = pOldTextNode->getIDocumentContentOperations(); + rIDCO.ReplaceRange( aPaM, aNewText, false ); + + ClearTextNode(); // TODO: is this really needed? +} + +// text::XFlatParagraph: +void SAL_CALL SwXFlatParagraph::changeAttributes(::sal_Int32 nPos, ::sal_Int32 nLen, const css::uno::Sequence< css::beans::PropertyValue > & aAttributes) +{ + SolarMutexGuard aGuard; + + if (!GetTextNode()) + return; + + if (nPos < 0 || GetTextNode()->Len() < nPos || nLen < 0 || o3tl::make_unsigned(GetTextNode()->Len()) < static_cast<sal_uInt32>(nPos) + nLen) + { + throw lang::IllegalArgumentException(); + } + + SwPaM aPaM( *GetTextNode(), nPos, *GetTextNode(), nPos+nLen ); + + UnoActionContext aAction( GetTextNode()->GetDoc() ); + + const uno::Reference< text::XTextRange > xRange = + SwXTextRange::CreateXTextRange( + *GetTextNode()->GetDoc(), *aPaM.GetPoint(), aPaM.GetMark() ); + uno::Reference< beans::XPropertySet > xPropSet( xRange, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + for ( const auto& rAttribute : aAttributes ) + xPropSet->setPropertyValue( rAttribute.Name, rAttribute.Value ); + } + + ClearTextNode(); // TODO: is this really needed? +} + +// text::XFlatParagraph: +css::uno::Sequence< ::sal_Int32 > SAL_CALL SwXFlatParagraph::getLanguagePortions() +{ + return css::uno::Sequence< ::sal_Int32>(); +} + +namespace +{ + class theSwXFlatParagraphUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXFlatParagraphUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 >& +SwXFlatParagraph::getUnoTunnelId() +{ + return theSwXFlatParagraphUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXFlatParagraph::getSomething( + const uno::Sequence< sal_Int8 >& rId) +{ + return sw::UnoTunnelImpl(rId, this); +} + +SwXFlatParagraphIterator::SwXFlatParagraphIterator( SwDoc& rDoc, sal_Int32 nType, bool bAutomatic ) + : mpDoc( &rDoc ), + mnType( nType ), + mbAutomatic( bAutomatic ), + mnCurrentNode( 0 ), + mnEndNode( rDoc.GetNodes().Count() ) +{ + //mnStartNode = mnCurrentNode = get node from current cursor TODO! + + // register as listener and get notified when document is closed + StartListening(mpDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD )->GetNotifier()); +} + +SwXFlatParagraphIterator::~SwXFlatParagraphIterator() +{ + SolarMutexGuard aGuard; + EndListeningAll(); +} + +void SwXFlatParagraphIterator::Notify( const SfxHint& rHint ) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + SolarMutexGuard aGuard; + mpDoc = nullptr; + } +} + +uno::Reference< text::XFlatParagraph > SwXFlatParagraphIterator::getFirstPara() +{ + return getNextPara(); // TODO +} + +uno::Reference< text::XFlatParagraph > SwXFlatParagraphIterator::getNextPara() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XFlatParagraph > xRet; + if (!mpDoc) + return xRet; + + SwTextNode* pRet = nullptr; + if ( mbAutomatic ) + { + SwViewShell* pViewShell = mpDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + + SwPageFrame* pCurrentPage = pViewShell ? pViewShell->Imp()->GetFirstVisPage(pViewShell->GetOut()) : nullptr; + SwPageFrame* pStartPage = pCurrentPage; + SwPageFrame* pStopPage = nullptr; + + while ( pCurrentPage && pCurrentPage != pStopPage ) + { + if (mnType != text::TextMarkupType::SPELLCHECK || pCurrentPage->IsInvalidSpelling() ) + { + // this method is supposed to return an empty paragraph in case Online Checking is disabled + if ( ( mnType == text::TextMarkupType::PROOFREADING || mnType == text::TextMarkupType::SPELLCHECK ) + && !pViewShell->GetViewOptions()->IsOnlineSpell() ) + return xRet; + + // search for invalid content: + SwContentFrame* pCnt = pCurrentPage->ContainsContent(); + + while( pCnt && pCurrentPage->IsAnLower( pCnt ) ) + { + if (pCnt->IsTextFrame()) + { + SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pCnt)); + if (sw::MergedPara const*const pMergedPara = pText->GetMergedPara() + ) + { + SwTextNode * pTextNode(nullptr); + for (auto const& e : pMergedPara->extents) + { + if (e.pNode != pTextNode) + { + pTextNode = e.pNode; + if ((mnType == text::TextMarkupType::SPELLCHECK + && pTextNode->IsWrongDirty()) || + (mnType == text::TextMarkupType::PROOFREADING + && pTextNode->IsGrammarCheckDirty())) + { + pRet = pTextNode; + break; + } + } + } + } + else + { + SwTextNode const*const pTextNode(pText->GetTextNodeFirst()); + if ((mnType == text::TextMarkupType::SPELLCHECK + && pTextNode->IsWrongDirty()) || + (mnType == text::TextMarkupType::PROOFREADING + && pTextNode->IsGrammarCheckDirty())) + + { + pRet = const_cast<SwTextNode*>(pTextNode); + } + } + + if (pRet) + { + break; + } + } + + pCnt = pCnt->GetNextContentFrame(); + } + } + + if ( pRet ) + break; + + // if there is no invalid text node on the current page, + // we validate the page + pCurrentPage->ValidateSpelling(); + + // proceed with next page, wrap at end of document if required: + pCurrentPage = static_cast<SwPageFrame*>(pCurrentPage->GetNext()); + + if ( !pCurrentPage && !pStopPage ) + { + pStopPage = pStartPage; + pCurrentPage = static_cast<SwPageFrame*>(pViewShell->GetLayout()->Lower()); + } + } + } + else // non-automatic checking + { + const SwNodes& rNodes = mpDoc->GetNodes(); + const sal_uLong nMaxNodes = rNodes.Count(); + + while ( mnCurrentNode < mnEndNode && mnCurrentNode < nMaxNodes ) + { + SwNode* pNd = rNodes[ mnCurrentNode ]; + + ++mnCurrentNode; + + pRet = dynamic_cast<SwTextNode*>(pNd); + if ( pRet ) + break; + + if ( mnCurrentNode == mnEndNode ) + { + mnCurrentNode = 0; + mnEndNode = 0; + } + } + } + + if ( pRet ) + { + // Expand the string: + const ModelToViewHelper aConversionMap(*pRet, mpDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + + xRet = new SwXFlatParagraph( *pRet, aExpandText, aConversionMap ); + // keep hard references... + m_aFlatParaList.insert( xRet ); + } + + return xRet; +} + +uno::Reference< text::XFlatParagraph > SwXFlatParagraphIterator::getLastPara() +{ + return getNextPara(); +} + +uno::Reference< text::XFlatParagraph > SwXFlatParagraphIterator::getParaAfter(const uno::Reference< text::XFlatParagraph > & xPara) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XFlatParagraph > xRet; + if (!mpDoc) + return xRet; + + const uno::Reference<lang::XUnoTunnel> xFPTunnel(xPara, uno::UNO_QUERY); + SAL_WARN_IF(!xFPTunnel.is(), "sw.core", "invalid argument"); + SwXFlatParagraph* const pFlatParagraph(sw::UnoTunnelGetImplementation<SwXFlatParagraph>(xFPTunnel)); + + if ( !pFlatParagraph ) + return xRet; + + SwTextNode const*const pCurrentNode = pFlatParagraph->GetTextNode(); + + if ( !pCurrentNode ) + return xRet; + + SwTextNode* pNextTextNode = nullptr; + const SwNodes& rNodes = pCurrentNode->GetDoc()->GetNodes(); + + for( sal_uLong nCurrentNode = pCurrentNode->GetIndex() + 1; nCurrentNode < rNodes.Count(); ++nCurrentNode ) + { + SwNode* pNd = rNodes[ nCurrentNode ]; + pNextTextNode = dynamic_cast<SwTextNode*>(pNd); + if ( pNextTextNode ) + break; + } + + if ( pNextTextNode ) + { + // Expand the string: + const ModelToViewHelper aConversionMap(*pNextTextNode, mpDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + + xRet = new SwXFlatParagraph( *pNextTextNode, aExpandText, aConversionMap ); + // keep hard references... + m_aFlatParaList.insert( xRet ); + } + + return xRet; +} + +uno::Reference< text::XFlatParagraph > SwXFlatParagraphIterator::getParaBefore(const uno::Reference< text::XFlatParagraph > & xPara ) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XFlatParagraph > xRet; + if (!mpDoc) + return xRet; + + const uno::Reference<lang::XUnoTunnel> xFPTunnel(xPara, uno::UNO_QUERY); + + SAL_WARN_IF(!xFPTunnel.is(), "sw.core", "invalid argument"); + SwXFlatParagraph* const pFlatParagraph(sw::UnoTunnelGetImplementation<SwXFlatParagraph>(xFPTunnel)); + + if ( !pFlatParagraph ) + return xRet; + + SwTextNode const*const pCurrentNode = pFlatParagraph->GetTextNode(); + + if ( !pCurrentNode ) + return xRet; + + SwTextNode* pPrevTextNode = nullptr; + const SwNodes& rNodes = pCurrentNode->GetDoc()->GetNodes(); + + for( sal_uLong nCurrentNode = pCurrentNode->GetIndex() - 1; nCurrentNode > 0; --nCurrentNode ) + { + SwNode* pNd = rNodes[ nCurrentNode ]; + pPrevTextNode = dynamic_cast<SwTextNode*>(pNd); + if ( pPrevTextNode ) + break; + } + + if ( pPrevTextNode ) + { + // Expand the string: + const ModelToViewHelper aConversionMap(*pPrevTextNode, mpDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + + xRet = new SwXFlatParagraph( *pPrevTextNode, aExpandText, aConversionMap ); + // keep hard references... + m_aFlatParaList.insert( xRet ); + } + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoframe.cxx b/sw/source/core/unocore/unoframe.cxx new file mode 100644 index 000000000..c96dc6676 --- /dev/null +++ b/sw/source/core/unocore/unoframe.cxx @@ -0,0 +1,3656 @@ +/* -*- 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 <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/embed/NoVisualAreaSizeException.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/frame/XTitle.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <o3tl/any.hxx> +#include <svx/svxids.hrc> +#include <svx/xfillit0.hxx> +#include <svx/xflgrit.hxx> +#include <svx/sdtaitm.hxx> +#include <svx/xflclit.hxx> +#include <tools/globname.hxx> +#include <editeng/memberids.h> +#include <swtypes.hxx> +#include <cmdid.h> +#include <unomid.h> +#include <memory> +#include <utility> +#include <cntfrm.hxx> +#include <doc.hxx> +#include <drawdoc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docsh.hxx> +#include <editsh.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <ndnotxt.hxx> +#include <svx/unomid.hxx> +#include <unocrsr.hxx> +#include <unocrsrhelper.hxx> +#include <docstyle.hxx> +#include <dcontact.hxx> +#include <fmtcnct.hxx> +#include <ndole.hxx> +#include <frmfmt.hxx> +#include <frame.hxx> +#include <textboxhelper.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unoparagraph.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unoevent.hxx> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <com/sun/star/drawing/PointSequence.hpp> +#include <tools/poly.hxx> +#include <swundo.hxx> +#include <svx/svdpage.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/protitem.hxx> +#include <fmtornt.hxx> +#include <fmteiro.hxx> +#include <fmturl.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/prntitem.hxx> +#include <editeng/shaditem.hxx> +#include <fmtsrnd.hxx> +#include <fmtfsize.hxx> +#include <grfatr.hxx> +#include <unoframe.hxx> +#include <fmtanchr.hxx> +#include <fmtclds.hxx> +#include <fmtcntnt.hxx> +#include <frmatr.hxx> +#include <ndtxt.hxx> +#include <ndgrf.hxx> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/GraphicLoader.hxx> +#include <SwStyleNameMapper.hxx> +#include <editeng/xmlcnitm.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <editeng/frmdiritem.hxx> +#include <fmtfollowtextflow.hxx> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> + +#include <svx/unobrushitemhelper.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xgrscit.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/xflbmpit.hxx> +#include <svx/xflbmsxy.hxx> +#include <svx/xflftrit.hxx> +#include <svx/xsflclit.hxx> +#include <svx/xflbmsli.hxx> +#include <svx/xflbtoxy.hxx> +#include <svx/xflbstit.hxx> +#include <svx/xflboxy.hxx> +#include <svx/xflbckit.hxx> +#include <svx/unoshape.hxx> +#include <svx/xflhtit.hxx> +#include <svx/xfltrit.hxx> +#include <swunohelper.hxx> +#include <fefly.hxx> + +using namespace ::com::sun::star; + +using ::com::sun::star::frame::XModel; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::style::XStyleFamiliesSupplier; + +class BaseFrameProperties_Impl +{ + SwUnoCursorHelper::SwAnyMapHelper aAnyMap; + +public: + virtual ~BaseFrameProperties_Impl(); + + void SetProperty(sal_uInt16 nWID, sal_uInt8 nMemberId, const uno::Any& rVal); + bool GetProperty(sal_uInt16 nWID, sal_uInt8 nMemberId, const uno::Any*& pAny ); + bool FillBaseProperties(SfxItemSet& rToSet, const SfxItemSet &rFromSet, bool& rSizeFound); + + virtual bool AnyToItemSet( SwDoc* pDoc, SfxItemSet& rFrameSet, SfxItemSet& rSet, bool& rSizeFound) = 0; +}; + +BaseFrameProperties_Impl::~BaseFrameProperties_Impl() +{ +} + +void BaseFrameProperties_Impl::SetProperty(sal_uInt16 nWID, sal_uInt8 nMemberId, const uno::Any& rVal) +{ + aAnyMap.SetValue( nWID, nMemberId, rVal ); +} + +bool BaseFrameProperties_Impl::GetProperty(sal_uInt16 nWID, sal_uInt8 nMemberId, const uno::Any*& rpAny) +{ + return aAnyMap.FillValue( nWID, nMemberId, rpAny ); +} + +bool BaseFrameProperties_Impl::FillBaseProperties(SfxItemSet& rToSet, const SfxItemSet& rFromSet, bool& rSizeFound) +{ + // assert when the target SfxItemSet has no parent. It *should* have the pDfltFrameFormat + // from SwDoc set as parent (or similar) to have the necessary XFILL_NONE in the ItemSet + if(!rToSet.GetParent()) + { + OSL_ENSURE(false, "OOps, target SfxItemSet *should* have a parent which contains XFILL_NONE as XFillStyleItem (!)"); + } + + bool bRet = true; + // always add an anchor to the set + SwFormatAnchor aAnchor ( rFromSet.Get ( RES_ANCHOR ) ); + { + const ::uno::Any* pAnchorPgNo; + if(GetProperty(RES_ANCHOR, MID_ANCHOR_PAGENUM, pAnchorPgNo)) + bRet &= static_cast<SfxPoolItem&>(aAnchor).PutValue(*pAnchorPgNo, MID_ANCHOR_PAGENUM); + const ::uno::Any* pAnchorType; + if(GetProperty(RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, pAnchorType)) + bRet &= static_cast<SfxPoolItem&>(aAnchor).PutValue(*pAnchorType, MID_ANCHOR_ANCHORTYPE); + } + + rToSet.Put(aAnchor); + + // check for SvxBrushItem (RES_BACKGROUND) properties + const ::uno::Any* pCol = nullptr; GetProperty(RES_BACKGROUND, MID_BACK_COLOR, pCol ); + const ::uno::Any* pRGBCol = nullptr; GetProperty(RES_BACKGROUND, MID_BACK_COLOR_R_G_B, pRGBCol ); + const ::uno::Any* pColTrans = nullptr; GetProperty(RES_BACKGROUND, MID_BACK_COLOR_TRANSPARENCY, pColTrans); + const ::uno::Any* pTrans = nullptr; GetProperty(RES_BACKGROUND, MID_GRAPHIC_TRANSPARENT, pTrans ); + const ::uno::Any* pGrLoc = nullptr; GetProperty(RES_BACKGROUND, MID_GRAPHIC_POSITION, pGrLoc ); + const ::uno::Any* pGraphic = nullptr; GetProperty(RES_BACKGROUND, MID_GRAPHIC, pGraphic ); + const ::uno::Any* pGrFilter = nullptr; GetProperty(RES_BACKGROUND, MID_GRAPHIC_FILTER, pGrFilter ); + const ::uno::Any* pGraphicURL = nullptr; GetProperty(RES_BACKGROUND, MID_GRAPHIC_URL, pGraphicURL ); + const ::uno::Any* pGrTransparency = nullptr; GetProperty(RES_BACKGROUND, MID_GRAPHIC_TRANSPARENCY, pGrTransparency ); + const bool bSvxBrushItemPropertiesUsed( + pCol || + pTrans || + pGraphic || + pGraphicURL || + pGrFilter || + pGrLoc || + pGrTransparency || + pColTrans || + pRGBCol); + + // check for FillStyle properties in the range XATTR_FILL_FIRST, XATTR_FILL_LAST + const uno::Any* pXFillStyleItem = nullptr; GetProperty(XATTR_FILLSTYLE, 0, pXFillStyleItem); + const uno::Any* pXFillColorItem = nullptr; GetProperty(XATTR_FILLCOLOR, 0, pXFillColorItem); + + // XFillGradientItem: two possible slots supported in UNO API + const uno::Any* pXFillGradientItem = nullptr; GetProperty(XATTR_FILLGRADIENT, MID_FILLGRADIENT, pXFillGradientItem); + const uno::Any* pXFillGradientNameItem = nullptr; GetProperty(XATTR_FILLGRADIENT, MID_NAME, pXFillGradientNameItem); + + // XFillHatchItem: two possible slots supported in UNO API + const uno::Any* pXFillHatchItem = nullptr; GetProperty(XATTR_FILLHATCH, MID_FILLHATCH, pXFillHatchItem); + const uno::Any* pXFillHatchNameItem = nullptr; GetProperty(XATTR_FILLHATCH, MID_NAME, pXFillHatchNameItem); + + // XFillBitmapItem: three possible slots supported in UNO API + const uno::Any* pXFillBitmapItem = nullptr; GetProperty(XATTR_FILLBITMAP, MID_BITMAP, pXFillBitmapItem); + const uno::Any* pXFillBitmapNameItem = nullptr; GetProperty(XATTR_FILLBITMAP, MID_NAME, pXFillBitmapNameItem); + + const uno::Any* pXFillTransparenceItem = nullptr; GetProperty(XATTR_FILLTRANSPARENCE, 0, pXFillTransparenceItem); + const uno::Any* pXGradientStepCountItem = nullptr; GetProperty(XATTR_GRADIENTSTEPCOUNT, 0, pXGradientStepCountItem); + const uno::Any* pXFillBmpPosItem = nullptr; GetProperty(XATTR_FILLBMP_POS, 0, pXFillBmpPosItem); + const uno::Any* pXFillBmpSizeXItem = nullptr; GetProperty(XATTR_FILLBMP_SIZEX, 0, pXFillBmpSizeXItem); + const uno::Any* pXFillBmpSizeYItem = nullptr; GetProperty(XATTR_FILLBMP_SIZEY, 0, pXFillBmpSizeYItem); + + // XFillFloatTransparenceItem: two possible slots supported in UNO API + const uno::Any* pXFillFloatTransparenceItem = nullptr; GetProperty(XATTR_FILLFLOATTRANSPARENCE, MID_FILLGRADIENT, pXFillFloatTransparenceItem); + const uno::Any* pXFillFloatTransparenceNameItem = nullptr; GetProperty(XATTR_FILLFLOATTRANSPARENCE, MID_NAME, pXFillFloatTransparenceNameItem); + + const uno::Any* pXSecondaryFillColorItem = nullptr; GetProperty(XATTR_SECONDARYFILLCOLOR, 0, pXSecondaryFillColorItem); + const uno::Any* pXFillBmpSizeLogItem = nullptr; GetProperty(XATTR_FILLBMP_SIZELOG, 0, pXFillBmpSizeLogItem); + const uno::Any* pXFillBmpTileOffsetXItem = nullptr; GetProperty(XATTR_FILLBMP_TILEOFFSETX, 0, pXFillBmpTileOffsetXItem); + const uno::Any* pXFillBmpTileOffsetYItem = nullptr; GetProperty(XATTR_FILLBMP_TILEOFFSETY, 0, pXFillBmpTileOffsetYItem); + const uno::Any* pXFillBmpPosOffsetXItem = nullptr; GetProperty(XATTR_FILLBMP_POSOFFSETX, 0, pXFillBmpPosOffsetXItem); + const uno::Any* pXFillBmpPosOffsetYItem = nullptr; GetProperty(XATTR_FILLBMP_POSOFFSETY, 0, pXFillBmpPosOffsetYItem); + const uno::Any* pXFillBackgroundItem = nullptr; GetProperty(XATTR_FILLBACKGROUND, 0, pXFillBackgroundItem); + const uno::Any* pOwnAttrFillBmpItem = nullptr; GetProperty(OWN_ATTR_FILLBMP_MODE, 0, pOwnAttrFillBmpItem); + + // tdf#91140: ignore SOLID fill style for determining if fill style is used + // but there is a Graphic + const bool bFillStyleUsed(pXFillStyleItem && pXFillStyleItem->hasValue() && + (pXFillStyleItem->get<drawing::FillStyle>() != drawing::FillStyle_SOLID || (!pGraphic || !pGraphicURL) )); + SAL_INFO_IF(pXFillStyleItem && pXFillStyleItem->hasValue() && !bFillStyleUsed, + "sw.uno", "FillBaseProperties: ignoring invalid FillStyle"); + const bool bXFillStyleItemUsed( + bFillStyleUsed || + pXFillColorItem || + pXFillGradientItem || pXFillGradientNameItem || + pXFillHatchItem || pXFillHatchNameItem || + pXFillBitmapItem || pXFillBitmapNameItem || + pXFillTransparenceItem || + pXGradientStepCountItem || + pXFillBmpPosItem || + pXFillBmpSizeXItem || + pXFillBmpSizeYItem || + pXFillFloatTransparenceItem || pXFillFloatTransparenceNameItem || + pXSecondaryFillColorItem || + pXFillBmpSizeLogItem || + pXFillBmpTileOffsetXItem || + pXFillBmpTileOffsetYItem || + pXFillBmpPosOffsetXItem || + pXFillBmpPosOffsetYItem || + pXFillBackgroundItem || + pOwnAttrFillBmpItem); + + // use brush items, but *only* if no FillStyle properties are used; if both are used and when applying both + // in the obvious order some attributes may be wrong since they are set by the 1st set, but not + // redefined as needed by the 2nd set when they are default (and thus no tset) in the 2nd set. If + // it is necessary for any reason to set both (it should not) an in-between step will be needed + // that resets the items for FillAttributes in rToSet to default. + // Note: There are other mechanisms in XMLOFF to pre-sort this relationship already, but this version + // was used initially, is tested and works. Keep it to be able to react when another feed adds attributes + // from both sets. + if(bSvxBrushItemPropertiesUsed && !bXFillStyleItemUsed) + { + // create a temporary SvxBrushItem, fill the attributes to it and use it to set + // the corresponding FillAttributes + SvxBrushItem aBrush(RES_BACKGROUND); + + if(pCol) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pCol,MID_BACK_COLOR ); + } + + if(pColTrans) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pColTrans, MID_BACK_COLOR_TRANSPARENCY); + } + + if(pRGBCol) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pRGBCol, MID_BACK_COLOR_R_G_B); + } + + if(pTrans) + { + // don't overwrite transparency with a non-transparence flag + if(!pColTrans || Any2Bool( *pTrans )) + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pTrans, MID_GRAPHIC_TRANSPARENT); + } + + if (pGraphic) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pGraphic, MID_GRAPHIC); + } + + if (pGraphicURL) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pGraphicURL, MID_GRAPHIC_URL); + } + + if(pGrFilter) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pGrFilter, MID_GRAPHIC_FILTER); + } + + if(pGrLoc) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pGrLoc, MID_GRAPHIC_POSITION); + } + + if(pGrTransparency) + { + bRet &= static_cast<SfxPoolItem&>(aBrush).PutValue(*pGrTransparency, MID_GRAPHIC_TRANSPARENCY); + } + + setSvxBrushItemAsFillAttributesToTargetSet(aBrush, rToSet); + } + + if(bXFillStyleItemUsed) + { + XFillStyleItem aXFillStyleItem; + std::unique_ptr<SvxBrushItem> aBrush(std::make_unique<SvxBrushItem>(RES_BACKGROUND)); + + if(pXFillStyleItem) + { + aXFillStyleItem.PutValue(*pXFillStyleItem, 0); + rToSet.Put(aXFillStyleItem); + } + + if(pXFillColorItem) + { + const Color aNullCol(COL_DEFAULT_SHAPE_FILLING); + XFillColorItem aXFillColorItem(OUString(), aNullCol); + + aXFillColorItem.PutValue(*pXFillColorItem, 0); + rToSet.Put(aXFillColorItem); + //set old-school brush color if we later encounter the + //MID_BACK_COLOR_TRANSPARENCY case below + aBrush = getSvxBrushItemFromSourceSet(rToSet, RES_BACKGROUND, false); + } + else if (aXFillStyleItem.GetValue() == drawing::FillStyle_SOLID && (pCol || pRGBCol)) + { + // Fill style is set to solid, but no fill color is given. + // On the other hand, we have a BackColor, so use that. + if (pCol) + aBrush->PutValue(*pCol, MID_BACK_COLOR); + else + aBrush->PutValue(*pRGBCol, MID_BACK_COLOR_R_G_B); + setSvxBrushItemAsFillAttributesToTargetSet(*aBrush, rToSet); + } + + if(pXFillGradientItem || pXFillGradientNameItem) + { + if(pXFillGradientItem) + { + const XGradient aNullGrad(COL_BLACK, COL_WHITE); + XFillGradientItem aXFillGradientItem(aNullGrad); + + aXFillGradientItem.PutValue(*pXFillGradientItem, MID_FILLGRADIENT); + rToSet.Put(aXFillGradientItem); + } + + if(pXFillGradientNameItem) + { + OUString aTempName; + + if(!(*pXFillGradientNameItem >>= aTempName )) + { + throw lang::IllegalArgumentException(); + } + + bool const bSuccess = SvxShape::SetFillAttribute( + XATTR_FILLGRADIENT, aTempName, rToSet); + if (aXFillStyleItem.GetValue() == drawing::FillStyle_GRADIENT) + { // tdf#90946 ignore invalid gradient-name if SOLID + bRet &= bSuccess; + } + else + { + SAL_INFO_IF(!bSuccess, "sw.uno", + "FillBaseProperties: ignoring invalid FillGradientName"); + } + } + } + + if(pXFillHatchItem || pXFillHatchNameItem) + { + if(pXFillHatchItem) + { + const Color aNullCol(COL_DEFAULT_SHAPE_STROKE); + const XHatch aNullHatch(aNullCol); + XFillHatchItem aXFillHatchItem(aNullHatch); + + aXFillHatchItem.PutValue(*pXFillHatchItem, MID_FILLHATCH); + rToSet.Put(aXFillHatchItem); + } + + if(pXFillHatchNameItem) + { + OUString aTempName; + + if(!(*pXFillHatchNameItem >>= aTempName )) + { + throw lang::IllegalArgumentException(); + } + + bRet &= SvxShape::SetFillAttribute(XATTR_FILLHATCH, aTempName, rToSet); + } + } + + if (pXFillBitmapItem || pXFillBitmapNameItem) + { + if(pXFillBitmapItem) + { + const Graphic aNullGraphic; + XFillBitmapItem aXFillBitmapItem(aNullGraphic); + + aXFillBitmapItem.PutValue(*pXFillBitmapItem, MID_BITMAP); + rToSet.Put(aXFillBitmapItem); + } + + if(pXFillBitmapNameItem) + { + OUString aTempName; + + if(!(*pXFillBitmapNameItem >>= aTempName )) + { + throw lang::IllegalArgumentException(); + } + + bRet &= SvxShape::SetFillAttribute(XATTR_FILLBITMAP, aTempName, rToSet); + } + } + + if (pXFillTransparenceItem) + { + XFillTransparenceItem aXFillTransparenceItem; + aXFillTransparenceItem.PutValue(*pXFillTransparenceItem, 0); + rToSet.Put(aXFillTransparenceItem); + } + else if (pColTrans && + !pXFillFloatTransparenceItem && !pXFillFloatTransparenceNameItem) + { + // No fill transparency is given. On the other hand, we have a + // BackColorTransparency, so use that. + // tdf#90640 tdf#90130: this is necessary for LO 4.4.0 - 4.4.2 + // that forgot to write draw:opacity into documents + // but: the value was *always* wrong for bitmaps! => ignore it + sal_Int8 nGraphicTransparency(0); + *pColTrans >>= nGraphicTransparency; + if (aXFillStyleItem.GetValue() != drawing::FillStyle_BITMAP) + { + rToSet.Put(XFillTransparenceItem(nGraphicTransparency)); + } + if (aXFillStyleItem.GetValue() == drawing::FillStyle_SOLID) + { + aBrush->PutValue(*pColTrans, MID_BACK_COLOR_TRANSPARENCY); + setSvxBrushItemAsFillAttributesToTargetSet(*aBrush, rToSet); + } + } + + if(pXGradientStepCountItem) + { + XGradientStepCountItem aXGradientStepCountItem; + + aXGradientStepCountItem.PutValue(*pXGradientStepCountItem, 0); + rToSet.Put(aXGradientStepCountItem); + } + + if(pXFillBmpPosItem) + { + XFillBmpPosItem aXFillBmpPosItem; + + aXFillBmpPosItem.PutValue(*pXFillBmpPosItem, 0); + rToSet.Put(aXFillBmpPosItem); + } + + if(pXFillBmpSizeXItem) + { + XFillBmpSizeXItem aXFillBmpSizeXItem; + + aXFillBmpSizeXItem.PutValue(*pXFillBmpSizeXItem, 0); + rToSet.Put(aXFillBmpSizeXItem); + } + + if(pXFillBmpSizeYItem) + { + XFillBmpSizeYItem aXFillBmpSizeYItem; + + aXFillBmpSizeYItem.PutValue(*pXFillBmpSizeYItem, 0); + rToSet.Put(aXFillBmpSizeYItem); + } + + if(pXFillFloatTransparenceItem || pXFillFloatTransparenceNameItem) + { + if(pXFillFloatTransparenceItem) + { + const XGradient aNullGrad(COL_BLACK, COL_WHITE); + XFillFloatTransparenceItem aXFillFloatTransparenceItem(aNullGrad, false); + + aXFillFloatTransparenceItem.PutValue(*pXFillFloatTransparenceItem, MID_FILLGRADIENT); + rToSet.Put(aXFillFloatTransparenceItem); + } + + if(pXFillFloatTransparenceNameItem) + { + OUString aTempName; + + if(!(*pXFillFloatTransparenceNameItem >>= aTempName )) + { + throw lang::IllegalArgumentException(); + } + + bRet &= SvxShape::SetFillAttribute(XATTR_FILLFLOATTRANSPARENCE, aTempName, rToSet); + } + } + + if(pXSecondaryFillColorItem) + { + const Color aNullCol(COL_DEFAULT_SHAPE_FILLING); + XSecondaryFillColorItem aXSecondaryFillColorItem(OUString(), aNullCol); + + aXSecondaryFillColorItem.PutValue(*pXSecondaryFillColorItem, 0); + rToSet.Put(aXSecondaryFillColorItem); + } + + if(pXFillBmpSizeLogItem) + { + XFillBmpSizeLogItem aXFillBmpSizeLogItem; + + aXFillBmpSizeLogItem.PutValue(*pXFillBmpSizeLogItem, 0); + rToSet.Put(aXFillBmpSizeLogItem); + } + + if(pXFillBmpTileOffsetXItem) + { + XFillBmpTileOffsetXItem aXFillBmpTileOffsetXItem; + + aXFillBmpTileOffsetXItem.PutValue(*pXFillBmpTileOffsetXItem, 0); + rToSet.Put(aXFillBmpTileOffsetXItem); + } + + if(pXFillBmpTileOffsetYItem) + { + XFillBmpTileOffsetYItem aXFillBmpTileOffsetYItem; + + aXFillBmpTileOffsetYItem.PutValue(*pXFillBmpTileOffsetYItem, 0); + rToSet.Put(aXFillBmpTileOffsetYItem); + } + + if(pXFillBmpPosOffsetXItem) + { + XFillBmpPosOffsetXItem aXFillBmpPosOffsetXItem; + + aXFillBmpPosOffsetXItem.PutValue(*pXFillBmpPosOffsetXItem, 0); + rToSet.Put(aXFillBmpPosOffsetXItem); + } + + if(pXFillBmpPosOffsetYItem) + { + XFillBmpPosOffsetYItem aXFillBmpPosOffsetYItem; + + aXFillBmpPosOffsetYItem.PutValue(*pXFillBmpPosOffsetYItem, 0); + rToSet.Put(aXFillBmpPosOffsetYItem); + } + + if(pXFillBackgroundItem) + { + XFillBackgroundItem aXFillBackgroundItem; + + aXFillBackgroundItem.PutValue(*pXFillBackgroundItem, 0); + rToSet.Put(aXFillBackgroundItem); + } + + if(pOwnAttrFillBmpItem) + { + drawing::BitmapMode eMode; + + if(!(*pOwnAttrFillBmpItem >>= eMode)) + { + sal_Int32 nMode = 0; + + if(!(*pOwnAttrFillBmpItem >>= nMode)) + { + throw lang::IllegalArgumentException(); + } + + eMode = static_cast<drawing::BitmapMode>(nMode); + } + + rToSet.Put(XFillBmpStretchItem(drawing::BitmapMode_STRETCH == eMode)); + rToSet.Put(XFillBmpTileItem(drawing::BitmapMode_REPEAT == eMode)); + } + } + { + const ::uno::Any* pCont = nullptr; + GetProperty(RES_PROTECT, MID_PROTECT_CONTENT, pCont ); + const ::uno::Any* pPos = nullptr; + GetProperty(RES_PROTECT,MID_PROTECT_POSITION, pPos ); + const ::uno::Any* pName = nullptr; + GetProperty(RES_PROTECT, MID_PROTECT_SIZE, pName ); + if(pCont||pPos||pName) + { + SvxProtectItem aProt ( rFromSet.Get ( RES_PROTECT ) ); + if(pCont) + bRet &= static_cast<SfxPoolItem&>(aProt).PutValue(*pCont, MID_PROTECT_CONTENT); + if(pPos ) + bRet &= static_cast<SfxPoolItem&>(aProt).PutValue(*pPos, MID_PROTECT_POSITION); + if(pName) + bRet &= static_cast<SfxPoolItem&>(aProt).PutValue(*pName, MID_PROTECT_SIZE); + rToSet.Put(aProt); + } + } + { + const ::uno::Any* pHori = nullptr; + GetProperty(RES_HORI_ORIENT, MID_HORIORIENT_ORIENT, pHori ); + const ::uno::Any* pHoriP = nullptr; + GetProperty(RES_HORI_ORIENT, MID_HORIORIENT_POSITION|CONVERT_TWIPS, pHoriP ); + const ::uno::Any* pHoriR = nullptr; + GetProperty(RES_HORI_ORIENT, MID_HORIORIENT_RELATION, pHoriR ); + const ::uno::Any* pPageT = nullptr; + GetProperty(RES_HORI_ORIENT, MID_HORIORIENT_PAGETOGGLE, pPageT); + if(pHori||pHoriP||pHoriR||pPageT) + { + SwFormatHoriOrient aOrient ( rFromSet.Get ( RES_HORI_ORIENT ) ); + if(pHori ) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pHori, MID_HORIORIENT_ORIENT); + if(pHoriP) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pHoriP, MID_HORIORIENT_POSITION|CONVERT_TWIPS); + if(pHoriR) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pHoriR, MID_HORIORIENT_RELATION); + if(pPageT) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pPageT, MID_HORIORIENT_PAGETOGGLE); + rToSet.Put(aOrient); + } + } + + { + const ::uno::Any* pVert = nullptr; + GetProperty(RES_VERT_ORIENT, MID_VERTORIENT_ORIENT, pVert); + const ::uno::Any* pVertP = nullptr; + GetProperty(RES_VERT_ORIENT, MID_VERTORIENT_POSITION|CONVERT_TWIPS, pVertP ); + const ::uno::Any* pVertR = nullptr; + GetProperty(RES_VERT_ORIENT, MID_VERTORIENT_RELATION, pVertR ); + if(pVert||pVertP||pVertR) + { + SwFormatVertOrient aOrient ( rFromSet.Get ( RES_VERT_ORIENT ) ); + if(pVert ) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pVert, MID_VERTORIENT_ORIENT); + if(pVertP) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pVertP, MID_VERTORIENT_POSITION|CONVERT_TWIPS); + if(pVertR) + bRet &= static_cast<SfxPoolItem&>(aOrient).PutValue(*pVertR, MID_VERTORIENT_RELATION); + rToSet.Put(aOrient); + } + } + { + const ::uno::Any* pURL = nullptr; + GetProperty(RES_URL, MID_URL_URL, pURL ); + const ::uno::Any* pTarget = nullptr; + GetProperty(RES_URL, MID_URL_TARGET, pTarget ); + const ::uno::Any* pHyLNm = nullptr; + GetProperty(RES_URL, MID_URL_HYPERLINKNAME, pHyLNm ); + const ::uno::Any* pHySMp = nullptr; + GetProperty(RES_URL, MID_URL_SERVERMAP, pHySMp ); + if(pURL||pTarget||pHyLNm||pHySMp) + { + SwFormatURL aURL ( rFromSet.Get ( RES_URL ) ); + if(pURL) + bRet &= static_cast<SfxPoolItem&>(aURL).PutValue(*pURL, MID_URL_URL); + if(pTarget) + bRet &= static_cast<SfxPoolItem&>(aURL).PutValue(*pTarget, MID_URL_TARGET); + if(pHyLNm) + bRet &= static_cast<SfxPoolItem&>(aURL).PutValue(*pHyLNm, MID_URL_HYPERLINKNAME ); + if(pHySMp) + bRet &= static_cast<SfxPoolItem&>(aURL).PutValue(*pHySMp, MID_URL_SERVERMAP); + rToSet.Put(aURL); + } + } + const ::uno::Any* pL = nullptr; + GetProperty(RES_LR_SPACE, MID_L_MARGIN|CONVERT_TWIPS, pL ); + const ::uno::Any* pR = nullptr; + GetProperty(RES_LR_SPACE, MID_R_MARGIN|CONVERT_TWIPS, pR ); + if(pL||pR) + { + SvxLRSpaceItem aLR ( rFromSet.Get ( RES_LR_SPACE ) ); + if(pL) + bRet &= static_cast<SfxPoolItem&>(aLR).PutValue(*pL, MID_L_MARGIN|CONVERT_TWIPS); + if(pR) + bRet &= static_cast<SfxPoolItem&>(aLR).PutValue(*pR, MID_R_MARGIN|CONVERT_TWIPS); + rToSet.Put(aLR); + } + const ::uno::Any* pT = nullptr; + GetProperty(RES_UL_SPACE, MID_UP_MARGIN|CONVERT_TWIPS, pT ); + const ::uno::Any* pB = nullptr; + GetProperty(RES_UL_SPACE, MID_LO_MARGIN|CONVERT_TWIPS, pB ); + if(pT||pB) + { + SvxULSpaceItem aTB ( rFromSet.Get ( RES_UL_SPACE ) ); + if(pT) + bRet &= static_cast<SfxPoolItem&>(aTB).PutValue(*pT, MID_UP_MARGIN|CONVERT_TWIPS); + if(pB) + bRet &= static_cast<SfxPoolItem&>(aTB).PutValue(*pB, MID_LO_MARGIN|CONVERT_TWIPS); + rToSet.Put(aTB); + } + const ::uno::Any* pOp; + if(GetProperty(RES_OPAQUE, 0, pOp)) + { + SvxOpaqueItem aOp ( rFromSet.Get ( RES_OPAQUE ) ); + bRet &= static_cast<SfxPoolItem&>(aOp).PutValue(*pOp, 0); + rToSet.Put(aOp); + } + const ::uno::Any* pPrt; + if(GetProperty(RES_PRINT, 0, pPrt)) + { + SvxPrintItem aPrt ( rFromSet.Get ( RES_PRINT ) ); + bRet &= static_cast<SfxPoolItem&>(aPrt).PutValue(*pPrt, 0); + rToSet.Put(aPrt); + } + const ::uno::Any* pSh; + if(GetProperty(RES_SHADOW, CONVERT_TWIPS, pSh)) + { + SvxShadowItem aSh ( rFromSet.Get ( RES_SHADOW ) ); + bRet &= static_cast<SfxPoolItem&>(aSh).PutValue(*pSh, CONVERT_TWIPS); + rToSet.Put(aSh); + } + const ::uno::Any* pShTr; + if(GetProperty(RES_SHADOW, MID_SHADOW_TRANSPARENCE, pShTr) && rToSet.HasItem(RES_SHADOW)) + { + SvxShadowItem aSh(rToSet.Get(RES_SHADOW)); + bRet &= aSh.PutValue(*pShTr, MID_SHADOW_TRANSPARENCE); + rToSet.Put(aSh); + } + const ::uno::Any* pSur = nullptr; + GetProperty(RES_SURROUND, MID_SURROUND_SURROUNDTYPE, pSur); + const ::uno::Any* pSurCont = nullptr; + GetProperty(RES_SURROUND, MID_SURROUND_CONTOUR, pSurCont); + const ::uno::Any* pSurAnch = nullptr; + GetProperty(RES_SURROUND, MID_SURROUND_ANCHORONLY, pSurAnch); + if(pSur || pSurAnch) + { + SwFormatSurround aSrnd ( rFromSet.Get ( RES_SURROUND ) ); + if(pSur) + bRet &= static_cast<SfxPoolItem&>(aSrnd).PutValue(*pSur, MID_SURROUND_SURROUNDTYPE); + if(pSurCont) + bRet &= static_cast<SfxPoolItem&>(aSrnd).PutValue(*pSurCont, MID_SURROUND_CONTOUR); + if(pSurAnch) + bRet &= static_cast<SfxPoolItem&>(aSrnd).PutValue(*pSurAnch, MID_SURROUND_ANCHORONLY); + rToSet.Put(aSrnd); + } + const ::uno::Any* pLeft = nullptr; + GetProperty(RES_BOX, LEFT_BORDER |CONVERT_TWIPS, pLeft ); + const ::uno::Any* pRight = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|RIGHT_BORDER , pRight ); + const ::uno::Any* pTop = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|TOP_BORDER , pTop ); + const ::uno::Any* pBottom = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|BOTTOM_BORDER, pBottom); + const ::uno::Any* pDistance = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|BORDER_DISTANCE, pDistance); + const ::uno::Any* pLeftDistance = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|LEFT_BORDER_DISTANCE, pLeftDistance); + const ::uno::Any* pRightDistance = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|RIGHT_BORDER_DISTANCE, pRightDistance); + const ::uno::Any* pTopDistance = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|TOP_BORDER_DISTANCE, pTopDistance); + const ::uno::Any* pBottomDistance = nullptr; + GetProperty(RES_BOX, CONVERT_TWIPS|BOTTOM_BORDER_DISTANCE, pBottomDistance); + const ::uno::Any* pLineStyle = nullptr; + GetProperty(RES_BOX, LINE_STYLE, pLineStyle); + const ::uno::Any* pLineWidth = nullptr; + GetProperty(RES_BOX, LINE_WIDTH, pLineWidth); + if( pLeft || pRight || pTop || pBottom || pDistance || + pLeftDistance || pRightDistance || pTopDistance || pBottomDistance || + pLineStyle || pLineWidth ) + { + SvxBoxItem aBox ( rFromSet.Get ( RES_BOX ) ); + if( pLeft ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pLeft, CONVERT_TWIPS|LEFT_BORDER ); + if( pRight ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pRight, CONVERT_TWIPS|RIGHT_BORDER ); + if( pTop ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pTop, CONVERT_TWIPS|TOP_BORDER); + if( pBottom ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pBottom, CONVERT_TWIPS|BOTTOM_BORDER); + if( pDistance ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pDistance, CONVERT_TWIPS|BORDER_DISTANCE); + if( pLeftDistance ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pLeftDistance, CONVERT_TWIPS|LEFT_BORDER_DISTANCE); + if( pRightDistance ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pRightDistance, CONVERT_TWIPS|RIGHT_BORDER_DISTANCE); + if( pTopDistance ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pTopDistance, CONVERT_TWIPS|TOP_BORDER_DISTANCE); + if( pBottomDistance ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pBottomDistance, CONVERT_TWIPS|BOTTOM_BORDER_DISTANCE); + if( pLineStyle ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pLineStyle, LINE_STYLE); + if( pLineWidth ) + bRet &= static_cast<SfxPoolItem&>(aBox).PutValue(*pLineWidth, LINE_WIDTH|CONVERT_TWIPS); + rToSet.Put(aBox); + } + { + const ::uno::Any* pRelH = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_REL_HEIGHT, pRelH); + const ::uno::Any* pRelHRelation = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_REL_HEIGHT_RELATION, pRelHRelation); + const ::uno::Any* pRelW = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_REL_WIDTH, pRelW); + const ::uno::Any* pRelWRelation = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_REL_WIDTH_RELATION, pRelWRelation); + const ::uno::Any* pSyncWidth = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT, pSyncWidth); + const ::uno::Any* pSyncHeight = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH, pSyncHeight); + const ::uno::Any* pWidth = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_WIDTH|CONVERT_TWIPS, pWidth); + const ::uno::Any* pHeight = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_HEIGHT|CONVERT_TWIPS, pHeight); + const ::uno::Any* pSize = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_SIZE|CONVERT_TWIPS, pSize); + const ::uno::Any* pSizeType = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_SIZE_TYPE, pSizeType); + const ::uno::Any* pWidthType = nullptr; + GetProperty(RES_FRM_SIZE, MID_FRMSIZE_WIDTH_TYPE, pWidthType); + if( pWidth || pHeight ||pRelH || pRelHRelation || pRelW || pRelWRelation || pSize ||pSizeType || + pWidthType ||pSyncWidth || pSyncHeight ) + { + rSizeFound = true; + SwFormatFrameSize aFrameSz ( rFromSet.Get ( RES_FRM_SIZE ) ); + if(pWidth) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pWidth, MID_FRMSIZE_WIDTH|CONVERT_TWIPS); + if(pHeight) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pHeight, MID_FRMSIZE_HEIGHT|CONVERT_TWIPS); + if(pRelH ) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pRelH, MID_FRMSIZE_REL_HEIGHT); + if (pRelHRelation) + bRet &= aFrameSz.PutValue(*pRelHRelation, MID_FRMSIZE_REL_HEIGHT_RELATION); + if(pRelW ) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pRelW, MID_FRMSIZE_REL_WIDTH); + if (pRelWRelation) + bRet &= aFrameSz.PutValue(*pRelWRelation, MID_FRMSIZE_REL_WIDTH_RELATION); + if(pSyncWidth) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pSyncWidth, MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT); + if(pSyncHeight) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pSyncHeight, MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH); + if(pSize) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pSize, MID_FRMSIZE_SIZE|CONVERT_TWIPS); + if(pSizeType) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pSizeType, MID_FRMSIZE_SIZE_TYPE); + if(pWidthType) + bRet &= static_cast<SfxPoolItem&>(aFrameSz).PutValue(*pWidthType, MID_FRMSIZE_WIDTH_TYPE); + if(!aFrameSz.GetWidth()) + aFrameSz.SetWidth(MINFLY); + if(!aFrameSz.GetHeight()) + aFrameSz.SetHeight(MINFLY); + rToSet.Put(aFrameSz); + } + else + { + rSizeFound = false; + SwFormatFrameSize aFrameSz; + awt::Size aSize; + aSize.Width = 2 * MM50; + aSize.Height = 2 * MM50; + ::uno::Any aSizeVal; + aSizeVal <<= aSize; + static_cast<SfxPoolItem&>(aFrameSz).PutValue(aSizeVal, MID_FRMSIZE_SIZE|CONVERT_TWIPS); + rToSet.Put(aFrameSz); + } + } + const ::uno::Any* pFrameDirection = nullptr; + GetProperty(RES_FRAMEDIR, 0, pFrameDirection); + if(pFrameDirection) + { + SvxFrameDirectionItem aAttr(SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR); + aAttr.PutValue(*pFrameDirection, 0); + rToSet.Put(aAttr); + } + const ::uno::Any* pUnknown = nullptr; + GetProperty(RES_UNKNOWNATR_CONTAINER, 0, pUnknown); + if(pUnknown) + { + SvXMLAttrContainerItem aAttr(RES_UNKNOWNATR_CONTAINER); + aAttr.PutValue(*pUnknown, 0); + rToSet.Put(aAttr); + } + + // #i18732# + const ::uno::Any* pFollowTextFlow = nullptr; + GetProperty(RES_FOLLOW_TEXT_FLOW, MID_FOLLOW_TEXT_FLOW, pFollowTextFlow); + + if (pFollowTextFlow) + { + SwFormatFollowTextFlow aFormatFollowTextFlow; + if( pFollowTextFlow ) + { + aFormatFollowTextFlow.PutValue(*pFollowTextFlow, MID_FOLLOW_TEXT_FLOW); + } + + rToSet.Put(aFormatFollowTextFlow); + } + + // #i28701# - RES_WRAP_INFLUENCE_ON_OBJPOS + const ::uno::Any* pWrapInfluenceOnObjPos = nullptr; + GetProperty(RES_WRAP_INFLUENCE_ON_OBJPOS, MID_WRAP_INFLUENCE, pWrapInfluenceOnObjPos); + const ::uno::Any* pAllowOverlap = nullptr; + GetProperty(RES_WRAP_INFLUENCE_ON_OBJPOS, MID_ALLOW_OVERLAP, pAllowOverlap); + if ( pWrapInfluenceOnObjPos || pAllowOverlap ) + { + SwFormatWrapInfluenceOnObjPos aFormatWrapInfluenceOnObjPos; + if (pWrapInfluenceOnObjPos) + aFormatWrapInfluenceOnObjPos.PutValue( *pWrapInfluenceOnObjPos, MID_WRAP_INFLUENCE ); + if (pAllowOverlap) + aFormatWrapInfluenceOnObjPos.PutValue( *pAllowOverlap, MID_ALLOW_OVERLAP ); + rToSet.Put(aFormatWrapInfluenceOnObjPos); + } + + { + const ::uno::Any* pTextVertAdjust = nullptr; + GetProperty(RES_TEXT_VERT_ADJUST, 0, pTextVertAdjust); + if ( pTextVertAdjust ) + { + SdrTextVertAdjustItem aTextVertAdjust( rFromSet.Get ( RES_TEXT_VERT_ADJUST ) ); + bRet &= static_cast<SfxPoolItem&>(aTextVertAdjust).PutValue(*pTextVertAdjust, 0); + rToSet.Put(aTextVertAdjust); + } + } + + return bRet; +} + +namespace { + +class SwFrameProperties_Impl : public BaseFrameProperties_Impl +{ +public: + SwFrameProperties_Impl(); + + bool AnyToItemSet( SwDoc* pDoc, SfxItemSet& rFrameSet, SfxItemSet& rSet, bool& rSizeFound) override; +}; + +} + +SwFrameProperties_Impl::SwFrameProperties_Impl(): + BaseFrameProperties_Impl(/*aSwMapProvider.GetPropertyMap(PROPERTY_MAP_TEXT_FRAME)*/ ) +{ +} + +static void lcl_FillCol ( SfxItemSet &rToSet, const ::SfxItemSet &rFromSet, const ::uno::Any *pAny) +{ + if ( pAny ) + { + SwFormatCol aCol ( rFromSet.Get ( RES_COL ) ); + static_cast<SfxPoolItem&>(aCol).PutValue( *pAny, MID_COLUMNS); + rToSet.Put(aCol); + } +} + +bool SwFrameProperties_Impl::AnyToItemSet(SwDoc *pDoc, SfxItemSet& rSet, SfxItemSet&, bool& rSizeFound) +{ + // Properties for all frames + const ::uno::Any *pStyleName; + SwDocStyleSheet* pStyle = nullptr; + bool bRet; + + if ( GetProperty ( FN_UNO_FRAME_STYLE_NAME, 0, pStyleName ) ) + { + OUString sStyle; + *pStyleName >>= sStyle; + SwStyleNameMapper::FillUIName(sStyle, sStyle, SwGetPoolIdFromName::FrmFmt); + pStyle = static_cast<SwDocStyleSheet*>(pDoc->GetDocShell()->GetStyleSheetPool()->Find(sStyle, + SfxStyleFamily::Frame)); + } + + const ::uno::Any* pColumns = nullptr; + GetProperty (RES_COL, MID_COLUMNS, pColumns); + if ( pStyle ) + { + rtl::Reference< SwDocStyleSheet > xStyle( new SwDocStyleSheet( *pStyle ) ); + const ::SfxItemSet *pItemSet = &xStyle->GetItemSet(); + bRet = FillBaseProperties( rSet, *pItemSet, rSizeFound ); + lcl_FillCol ( rSet, *pItemSet, pColumns ); + } + else + { + const ::SfxItemSet *pItemSet = &pDoc->getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME )->GetAttrSet(); + bRet = FillBaseProperties( rSet, *pItemSet, rSizeFound ); + lcl_FillCol ( rSet, *pItemSet, pColumns ); + } + const ::uno::Any* pEdit; + if(GetProperty(RES_EDIT_IN_READONLY, 0, pEdit)) + { + SwFormatEditInReadonly item(RES_EDIT_IN_READONLY); + item.PutValue(*pEdit, 0); + rSet.Put(item); + } + return bRet; +} + +namespace { + +class SwGraphicProperties_Impl : public BaseFrameProperties_Impl +{ +public: + SwGraphicProperties_Impl(); + + virtual bool AnyToItemSet( SwDoc* pDoc, SfxItemSet& rFrameSet, SfxItemSet& rSet, bool& rSizeFound) override; +}; + +} + +SwGraphicProperties_Impl::SwGraphicProperties_Impl( ) : + BaseFrameProperties_Impl(/*aSwMapProvider.GetPropertyMap(PROPERTY_MAP_TEXT_GRAPHIC)*/ ) +{ +} + +static void lcl_FillMirror ( SfxItemSet &rToSet, const ::SfxItemSet &rFromSet, const ::uno::Any *pHEvenMirror, const ::uno::Any *pHOddMirror, const ::uno::Any *pVMirror, bool &rRet ) +{ + if(pHEvenMirror || pHOddMirror || pVMirror ) + { + SwMirrorGrf aMirror ( rFromSet.Get ( RES_GRFATR_MIRRORGRF ) ); + if(pHEvenMirror) + rRet &= static_cast<SfxPoolItem&>(aMirror).PutValue(*pHEvenMirror, MID_MIRROR_HORZ_EVEN_PAGES); + if(pHOddMirror) + rRet &= static_cast<SfxPoolItem&>(aMirror).PutValue(*pHOddMirror, MID_MIRROR_HORZ_ODD_PAGES); + if(pVMirror) + rRet &= static_cast<SfxPoolItem&>(aMirror).PutValue(*pVMirror, MID_MIRROR_VERT); + rToSet.Put(aMirror); + } +} + +bool SwGraphicProperties_Impl::AnyToItemSet( + SwDoc* pDoc, + SfxItemSet& rFrameSet, + SfxItemSet& rGrSet, + bool& rSizeFound) +{ + // Properties for all frames + bool bRet; + const ::uno::Any *pStyleName; + SwDocStyleSheet* pStyle = nullptr; + + if ( GetProperty ( FN_UNO_FRAME_STYLE_NAME, 0, pStyleName ) ) + { + OUString sStyle; + *pStyleName >>= sStyle; + SwStyleNameMapper::FillUIName(sStyle, sStyle, SwGetPoolIdFromName::FrmFmt); + pStyle = static_cast<SwDocStyleSheet*>(pDoc->GetDocShell()->GetStyleSheetPool()->Find(sStyle, + SfxStyleFamily::Frame)); + } + + const ::uno::Any* pHEvenMirror = nullptr; + const ::uno::Any* pHOddMirror = nullptr; + const ::uno::Any* pVMirror = nullptr; + GetProperty(RES_GRFATR_MIRRORGRF, MID_MIRROR_HORZ_EVEN_PAGES, pHEvenMirror); + GetProperty(RES_GRFATR_MIRRORGRF, MID_MIRROR_HORZ_ODD_PAGES, pHOddMirror); + GetProperty(RES_GRFATR_MIRRORGRF, MID_MIRROR_VERT, pVMirror); + + if ( pStyle ) + { + rtl::Reference< SwDocStyleSheet > xStyle( new SwDocStyleSheet(*pStyle) ); + const ::SfxItemSet *pItemSet = &xStyle->GetItemSet(); + bRet = FillBaseProperties(rFrameSet, *pItemSet, rSizeFound); + lcl_FillMirror ( rGrSet, *pItemSet, pHEvenMirror, pHOddMirror, pVMirror, bRet ); + } + else + { + const ::SfxItemSet *pItemSet = &pDoc->getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC )->GetAttrSet(); + bRet = FillBaseProperties(rFrameSet, *pItemSet, rSizeFound); + lcl_FillMirror ( rGrSet, *pItemSet, pHEvenMirror, pHOddMirror, pVMirror, bRet ); + } + + static const ::sal_uInt16 nIDs[] = + { + RES_GRFATR_CROPGRF, + RES_GRFATR_ROTATION, + RES_GRFATR_LUMINANCE, + RES_GRFATR_CONTRAST, + RES_GRFATR_CHANNELR, + RES_GRFATR_CHANNELG, + RES_GRFATR_CHANNELB, + RES_GRFATR_GAMMA, + RES_GRFATR_INVERT, + RES_GRFATR_TRANSPARENCY, + RES_GRFATR_DRAWMODE, + 0 + }; + const ::uno::Any* pAny; + for(sal_Int16 nIndex = 0; nIDs[nIndex]; nIndex++) + { + sal_uInt8 nMId = RES_GRFATR_CROPGRF == nIDs[nIndex] ? CONVERT_TWIPS : 0; + if(GetProperty(nIDs[nIndex], nMId, pAny )) + { + std::unique_ptr<SfxPoolItem> pItem(::GetDfltAttr( nIDs[nIndex] )->Clone()); + bRet &= pItem->PutValue(*pAny, nMId ); + rGrSet.Put(std::move(pItem)); + } + } + + return bRet; +} + +namespace { + +class SwOLEProperties_Impl : public SwFrameProperties_Impl +{ +public: + SwOLEProperties_Impl() : + SwFrameProperties_Impl(/*aSwMapProvider.GetPropertyMap(PROPERTY_MAP_EMBEDDED_OBJECT)*/ ){} + + virtual bool AnyToItemSet( SwDoc* pDoc, SfxItemSet& rFrameSet, SfxItemSet& rSet, bool& rSizeFound) override; +}; + +} + +bool SwOLEProperties_Impl::AnyToItemSet( + SwDoc* pDoc, SfxItemSet& rFrameSet, SfxItemSet& rSet, bool& rSizeFound) +{ + const ::uno::Any* pTemp; + if(!GetProperty(FN_UNO_CLSID, 0, pTemp) && !GetProperty(FN_UNO_STREAM_NAME, 0, pTemp) + && !GetProperty(FN_EMBEDDED_OBJECT, 0, pTemp) + && !GetProperty(FN_UNO_VISIBLE_AREA_WIDTH, 0, pTemp) + && !GetProperty(FN_UNO_VISIBLE_AREA_HEIGHT, 0, pTemp) ) + return false; + SwFrameProperties_Impl::AnyToItemSet( pDoc, rFrameSet, rSet, rSizeFound); + + return true; +} + +class SwXFrame::Impl +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + + Impl() : m_EventListeners(m_Mutex) { } +}; + +namespace +{ + class theSwXFrameUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXFrameUnoTunnelId > {}; +} + +const ::uno::Sequence< sal_Int8 > & SwXFrame::getUnoTunnelId() +{ + return theSwXFrameUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXFrame::getSomething( const ::uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXFrame>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + + +OUString SwXFrame::getImplementationName() +{ + return "SwXFrame"; +} + +sal_Bool SwXFrame::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXFrame::getSupportedServiceNames() +{ + return { "com.sun.star.text.BaseFrame", "com.sun.star.text.TextContent", "com.sun.star.document.LinkTarget" }; +} + +SwXFrame::SwXFrame(FlyCntType eSet, const ::SfxItemPropertySet* pSet, SwDoc *pDoc) + : m_pImpl(new Impl) + , m_pFrameFormat(nullptr) + , m_pPropSet(pSet) + , m_pDoc(pDoc) + , eType(eSet) + , bIsDescriptor(true) + , m_nDrawAspect(embed::Aspects::MSOLE_CONTENT) + , m_nVisibleAreaWidth(0) + , m_nVisibleAreaHeight(0) +{ + // Register ourselves as a listener to the document (via the page descriptor) + StartListening(pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); + // get the property set for the default style data + // First get the model + uno::Reference < XModel > xModel = pDoc->GetDocShell()->GetBaseModel(); + // Ask the model for its family supplier interface + uno::Reference < XStyleFamiliesSupplier > xFamilySupplier ( xModel, uno::UNO_QUERY ); + // Get the style families + uno::Reference < XNameAccess > xFamilies = xFamilySupplier->getStyleFamilies(); + // Get the Frame family (and keep it for later) + const ::uno::Any aAny = xFamilies->getByName ("FrameStyles"); + aAny >>= mxStyleFamily; + // In the derived class, we'll ask mxStyleFamily for the relevant default style + // mxStyleFamily is initialised in the SwXFrame constructor + switch(eType) + { + case FLYCNTTYPE_FRM: + { + uno::Any aAny2 = mxStyleFamily->getByName ("Frame"); + aAny2 >>= mxStyleData; + m_pProps.reset(new SwFrameProperties_Impl); + } + break; + case FLYCNTTYPE_GRF: + { + uno::Any aAny2 = mxStyleFamily->getByName ("Graphics"); + aAny2 >>= mxStyleData; + m_pProps.reset(new SwGraphicProperties_Impl); + } + break; + case FLYCNTTYPE_OLE: + { + uno::Any aAny2 = mxStyleFamily->getByName ("OLE"); + aAny2 >>= mxStyleData; + m_pProps.reset(new SwOLEProperties_Impl); + } + break; + + default: + m_pProps.reset(); + break; + } +} + +SwXFrame::SwXFrame(SwFrameFormat& rFrameFormat, FlyCntType eSet, const ::SfxItemPropertySet* pSet) + : m_pImpl(new Impl) + , m_pFrameFormat(&rFrameFormat) + , m_pPropSet(pSet) + , m_pDoc(nullptr) + , eType(eSet) + , bIsDescriptor(false) + , m_nDrawAspect(embed::Aspects::MSOLE_CONTENT) + , m_nVisibleAreaWidth(0) + , m_nVisibleAreaHeight(0) +{ + StartListening(rFrameFormat.GetNotifier()); +} + +SwXFrame::~SwXFrame() +{ + SolarMutexGuard aGuard; + m_pProps.reset(); + EndListeningAll(); +} + +template<class Interface, class NameLookupIsHard> +uno::Reference<Interface> +SwXFrame::CreateXFrame(SwDoc & rDoc, SwFrameFormat *const pFrameFormat) +{ + assert(!pFrameFormat || &rDoc == pFrameFormat->GetDoc()); + uno::Reference<Interface> xFrame; + if (pFrameFormat) + { + xFrame.set(pFrameFormat->GetXObject(), uno::UNO_QUERY); // cached? + } + if (!xFrame.is()) + { + NameLookupIsHard *const pNew(pFrameFormat + ? new NameLookupIsHard(*pFrameFormat) + : new NameLookupIsHard(&rDoc)); + xFrame.set(pNew); + if (pFrameFormat) + { + pFrameFormat->SetXObject(xFrame); + } + // need a permanent Reference to initialize m_wThis + pNew->SwXFrame::m_pImpl->m_wThis = xFrame; + } + return xFrame; +} + +OUString SwXFrame::getName() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + return pFormat->GetName(); + if(!bIsDescriptor) + throw uno::RuntimeException(); + return m_sName; +} + +void SwXFrame::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + pFormat->GetDoc()->SetFlyName(static_cast<SwFlyFrameFormat&>(*pFormat), rName); + if(pFormat->GetName() != rName) + { + throw uno::RuntimeException(); + } + } + else if(bIsDescriptor) + m_sName = rName; + else + throw uno::RuntimeException(); +} + +uno::Reference< beans::XPropertySetInfo > SwXFrame::getPropertySetInfo() +{ + uno::Reference< beans::XPropertySetInfo > xRef; + static uno::Reference< beans::XPropertySetInfo > xFrameRef; + static uno::Reference< beans::XPropertySetInfo > xGrfRef; + static uno::Reference< beans::XPropertySetInfo > xOLERef; + switch(eType) + { + case FLYCNTTYPE_FRM: + if( !xFrameRef.is() ) + xFrameRef = m_pPropSet->getPropertySetInfo(); + xRef = xFrameRef; + break; + case FLYCNTTYPE_GRF: + if( !xGrfRef.is() ) + xGrfRef = m_pPropSet->getPropertySetInfo(); + xRef = xGrfRef; + break; + case FLYCNTTYPE_OLE: + if( !xOLERef.is() ) + xOLERef = m_pPropSet->getPropertySetInfo(); + xRef = xOLERef; + break; + default: + ; + } + return xRef; +} + +SdrObject *SwXFrame::GetOrCreateSdrObject(SwFlyFrameFormat &rFormat) +{ + SdrObject* pObject = rFormat.FindSdrObject(); + if( !pObject ) + { + SwDoc *pDoc = rFormat.GetDoc(); + // #i52858# - method name changed + SwFlyDrawContact* pContactObject(rFormat.GetOrCreateContact()); + pObject = pContactObject->GetMaster(); + + const ::SwFormatSurround& rSurround = rFormat.GetSurround(); + pObject->SetLayer( + ( css::text::WrapTextMode_THROUGH == rSurround.GetSurround() && + !rFormat.GetOpaque().GetValue() ) ? pDoc->getIDocumentDrawModelAccess().GetHellId() + : pDoc->getIDocumentDrawModelAccess().GetHeavenId() ); + SwDrawModel* pDrawModel = pDoc->getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + pDrawModel->GetPage(0)->InsertObject( pObject ); + } + + return pObject; +} + +static SwFrameFormat *lcl_GetFrameFormat( const ::uno::Any& rValue, SwDoc *pDoc ) +{ + SwFrameFormat *pRet = nullptr; + SwDocShell* pDocSh = pDoc->GetDocShell(); + if(pDocSh) + { + OUString uTemp; + rValue >>= uTemp; + OUString sStyle; + SwStyleNameMapper::FillUIName(uTemp, sStyle, + SwGetPoolIdFromName::FrmFmt); + SwDocStyleSheet* pStyle = + static_cast<SwDocStyleSheet*>(pDocSh->GetStyleSheetPool()->Find(sStyle, + SfxStyleFamily::Frame)); + if(pStyle) + pRet = pStyle->GetFrameFormat(); + } + + return pRet; +} + +void SwXFrame::setPropertyValue(const OUString& rPropertyName, const ::uno::Any& _rValue) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + const ::SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName); + + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const sal_uInt8 nMemberId(pEntry->nMemberId); + uno::Any aValue(_rValue); + + // check for needed metric translation + if(pEntry->nMoreFlags & PropertyMoreFlags::METRIC_ITEM) + { + bool bDoIt(true); + + if(XATTR_FILLBMP_SIZEX == pEntry->nWID || XATTR_FILLBMP_SIZEY == pEntry->nWID) + { + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + sal_Int32 nValue = 0; + + if(aValue >>= nValue) + { + bDoIt = nValue > 0; + } + } + + if(bDoIt) + { + const SwDoc* pDoc = (IsDescriptor() ? m_pDoc : GetFrameFormat()->GetDoc()); + const SfxItemPool& rPool = pDoc->GetAttrPool(); + const MapUnit eMapUnit(rPool.GetMetric(pEntry->nWID)); + + if(eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertFromMM(eMapUnit, aValue); + } + } + } + + if(pFormat) + { + bool bNextFrame = false; + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + SwDoc* pDoc = pFormat->GetDoc(); + if ( ((eType == FLYCNTTYPE_GRF) && isGRFATR(pEntry->nWID)) || + (FN_PARAM_CONTOUR_PP == pEntry->nWID) || + (FN_UNO_IS_AUTOMATIC_CONTOUR == pEntry->nWID) || + (FN_UNO_IS_PIXEL_CONTOUR == pEntry->nWID) ) + { + const ::SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwNoTextNode* pNoText = aIdx.GetNode().GetNoTextNode(); + if(pEntry->nWID == FN_PARAM_CONTOUR_PP) + { + drawing::PointSequenceSequence aParam; + if(!aValue.hasValue()) + pNoText->SetContour(nullptr); + else if(aValue >>= aParam) + { + tools::PolyPolygon aPoly(static_cast<sal_uInt16>(aParam.getLength())); + for(const ::drawing::PointSequence& rPointSeq : std::as_const(aParam)) + { + sal_Int32 nPoints = rPointSeq.getLength(); + const ::awt::Point* pPoints = rPointSeq.getConstArray(); + tools::Polygon aSet( static_cast<sal_uInt16>(nPoints) ); + for(sal_Int32 j = 0; j < nPoints; j++) + { + Point aPoint(pPoints[j].X, pPoints[j].Y); + aSet.SetPoint(aPoint, static_cast<sal_uInt16>(j)); + } + // Close polygon if it isn't closed already. + aSet.Optimize( PolyOptimizeFlags::CLOSE ); + aPoly.Insert( aSet ); + } + pNoText->SetContourAPI( &aPoly ); + } + else + throw lang::IllegalArgumentException(); + } + else if(pEntry->nWID == FN_UNO_IS_AUTOMATIC_CONTOUR ) + { + pNoText->SetAutomaticContour( *o3tl::doAccess<bool>(aValue) ); + } + else if(pEntry->nWID == FN_UNO_IS_PIXEL_CONTOUR ) + { + // The IsPixelContour property can only be set if there + // is no contour, or if the contour has been set by the + // API itself (or in other words, if the contour isn't + // used already). + if( pNoText->HasContour_() && pNoText->IsContourMapModeValid() ) + throw lang::IllegalArgumentException(); + + pNoText->SetPixelContour( *o3tl::doAccess<bool>(aValue) ); + + } + else + { + SfxItemSet aSet(pNoText->GetSwAttrSet()); + m_pPropSet->setPropertyValue(*pEntry, aValue, aSet); + pNoText->SetAttr(aSet); + } + } + } + // New attribute Title + else if( FN_UNO_TITLE == pEntry->nWID ) + { + SwFlyFrameFormat& rFlyFormat = dynamic_cast<SwFlyFrameFormat&>(*pFormat); + OUString sTitle; + aValue >>= sTitle; + // assure that <SdrObject> instance exists. + GetOrCreateSdrObject(rFlyFormat); + rFlyFormat.GetDoc()->SetFlyFrameTitle(rFlyFormat, sTitle); + } + // New attribute Description + else if( FN_UNO_DESCRIPTION == pEntry->nWID ) + { + SwFlyFrameFormat& rFlyFormat = dynamic_cast<SwFlyFrameFormat&>(*pFormat); + OUString sDescription; + aValue >>= sDescription; + // assure that <SdrObject> instance exists. + GetOrCreateSdrObject(rFlyFormat); + rFlyFormat.GetDoc()->SetFlyFrameDescription(rFlyFormat, sDescription); + } + else if(FN_UNO_FRAME_STYLE_NAME == pEntry->nWID) + { + SwFrameFormat *pFrameFormat = lcl_GetFrameFormat( aValue, pFormat->GetDoc() ); + if( !pFrameFormat ) + throw lang::IllegalArgumentException(); + + UnoActionContext aAction(pFormat->GetDoc()); + + std::unique_ptr<SfxItemSet> pSet; + // #i31771#, #i25798# - No adjustment of + // anchor ( no call of method <sw_ChkAndSetNewAnchor(..)> ), + // if document is currently in reading mode. + if ( !pFormat->GetDoc()->IsInReading() ) + { + // see SwFEShell::SetFrameFormat( SwFrameFormat *pNewFormat, bool bKeepOrient, Point* pDocPos ) + SwFlyFrame *pFly = nullptr; + if (auto pFlyFrameFormat = dynamic_cast<const SwFlyFrameFormat*>(pFormat) ) + pFly = pFlyFrameFormat->GetFrame(); + if ( pFly ) + { + const ::SfxPoolItem* pItem; + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_ANCHOR, false, &pItem )) + { + pSet.reset(new SfxItemSet( pDoc->GetAttrPool(), aFrameFormatSetRange )); + pSet->Put( *pItem ); + if ( pFormat->GetDoc()->GetEditShell() != nullptr + && !sw_ChkAndSetNewAnchor( *pFly, *pSet ) ) + { + pSet.reset(); + } + } + } + } + + pFormat->GetDoc()->SetFrameFormatToFly( *pFormat, *pFrameFormat, pSet.get() ); + } + else if (FN_UNO_GRAPHIC_FILTER == pEntry->nWID) + { + OUString sGrfName; + OUString sFltName; + SwDoc::GetGrfNms( *static_cast<SwFlyFrameFormat*>(pFormat), &sGrfName, &sFltName ); + aValue >>= sFltName; + UnoActionContext aAction(pFormat->GetDoc()); + const ::SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if (pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwGrfNode* pGrfNode = aIdx.GetNode().GetGrfNode(); + if(!pGrfNode) + { + throw uno::RuntimeException(); + } + SwPaM aGrfPaM(*pGrfNode); + pFormat->GetDoc()->getIDocumentContentOperations().ReRead(aGrfPaM, sGrfName, sFltName, nullptr); + } + } + else if (FN_UNO_GRAPHIC == pEntry->nWID || FN_UNO_GRAPHIC_URL == pEntry->nWID) + { + Graphic aGraphic; + if (aValue.has<OUString>()) + { + OUString aURL = aValue.get<OUString>(); + if (!aURL.isEmpty()) + { + aGraphic = vcl::graphic::loadFromURL(aURL); + } + } + else if (aValue.has<uno::Reference<graphic::XGraphic>>()) + { + uno::Reference<graphic::XGraphic> xGraphic = aValue.get<uno::Reference<graphic::XGraphic>>(); + if (xGraphic.is()) + { + aGraphic = Graphic(xGraphic); + } + } + + if (!aGraphic.IsNone()) + { + const ::SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if (pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwGrfNode* pGrfNode = aIdx.GetNode().GetGrfNode(); + if (!pGrfNode) + { + throw uno::RuntimeException(); + } + SwPaM aGrfPaM(*pGrfNode); + pFormat->GetDoc()->getIDocumentContentOperations().ReRead(aGrfPaM, OUString(), OUString(), &aGraphic); + } + } + } + else if (FN_UNO_REPLACEMENT_GRAPHIC == pEntry->nWID || FN_UNO_REPLACEMENT_GRAPHIC_URL == pEntry->nWID) + { + Graphic aGraphic; + if (aValue.has<OUString>()) + { + OUString aURL = aValue.get<OUString>(); + if (!aURL.isEmpty()) + { + aGraphic = vcl::graphic::loadFromURL(aURL); + } + } + else if (aValue.has<uno::Reference<graphic::XGraphic>>()) + { + uno::Reference<graphic::XGraphic> xGraphic = aValue.get<uno::Reference<graphic::XGraphic>>(); + if (xGraphic.is()) + { + aGraphic = Graphic(xGraphic); + } + } + + if (!aGraphic.IsNone()) + { + const ::SwFormatContent* pCnt = &pFormat->GetContent(); + if ( pCnt->GetContentIdx() && pDoc->GetNodes()[ pCnt->GetContentIdx()->GetIndex() + 1 ] ) + { + SwOLENode* pOleNode = pDoc->GetNodes()[ pCnt->GetContentIdx()->GetIndex() + 1 ]->GetOLENode(); + + if ( pOleNode ) + { + svt::EmbeddedObjectRef &rEmbeddedObject = pOleNode->GetOLEObj().GetObject(); + rEmbeddedObject.SetGraphic(aGraphic, OUString() ); + } + } + } + } + else if((bNextFrame = (rPropertyName == UNO_NAME_CHAIN_NEXT_NAME)) + || rPropertyName == UNO_NAME_CHAIN_PREV_NAME) + { + OUString sChainName; + aValue >>= sChainName; + if (sChainName.isEmpty()) + { + if(bNextFrame) + pDoc->Unchain(*pFormat); + else + { + const SwFormatChain& aChain( pFormat->GetChain() ); + SwFrameFormat *pPrev = aChain.GetPrev(); + if(pPrev) + pDoc->Unchain(*pPrev); + } + } + else + { + const size_t nCount = pDoc->GetFlyCount(FLYCNTTYPE_FRM); + + SwFrameFormat* pChain = nullptr; + for( size_t i = 0; i < nCount; ++i ) + { + SwFrameFormat* pFormat2 = pDoc->GetFlyNum(i, FLYCNTTYPE_FRM); + if(sChainName == pFormat2->GetName() ) + { + pChain = pFormat2; + break; + } + } + if(pChain) + { + SwFrameFormat* pSource = bNextFrame ? pFormat : pChain; + SwFrameFormat* pDest = bNextFrame ? pChain: pFormat; + pDoc->Chain(*pSource, *pDest); + } + } + } + else if(FN_UNO_Z_ORDER == pEntry->nWID) + { + sal_Int32 nZOrder = - 1; + aValue >>= nZOrder; + + // Don't set an explicit ZOrder on TextBoxes. + if( nZOrder >= 0 && !SwTextBoxHelper::isTextBox(pFormat, RES_FLYFRMFMT) ) + { + SdrObject* pObject = + GetOrCreateSdrObject( static_cast<SwFlyFrameFormat&>(*pFormat) ); + SwDrawModel *pDrawModel = pDoc->getIDocumentDrawModelAccess().GetDrawModel(); + pDrawModel->GetPage(0)-> + SetObjectOrdNum(pObject->GetOrdNum(), nZOrder); + } + } + else if(RES_ANCHOR == pEntry->nWID && MID_ANCHOR_ANCHORFRAME == nMemberId) + { + bool bDone = false; + uno::Reference<text::XTextFrame> xFrame; + if(aValue >>= xFrame) + { + SwXFrame* pFrame = comphelper::getUnoTunnelImplementation<SwXFrame>(xFrame); + if(pFrame && this != pFrame && pFrame->GetFrameFormat() && pFrame->GetFrameFormat()->GetDoc() == pDoc) + { + SfxItemSet aSet( pDoc->GetAttrPool(), + svl::Items<RES_FRMATR_BEGIN, RES_FRMATR_END - 1>{} ); + aSet.SetParent(&pFormat->GetAttrSet()); + SwFormatAnchor aAnchor = static_cast<const SwFormatAnchor&>(aSet.Get(pEntry->nWID)); + + SwPosition aPos(*pFrame->GetFrameFormat()->GetContent().GetContentIdx()); + aAnchor.SetAnchor(&aPos); + aAnchor.SetType(RndStdIds::FLY_AT_FLY); + aSet.Put(aAnchor); + pDoc->SetFlyFrameAttr( *pFormat, aSet ); + bDone = true; + } + } + if(!bDone) + throw lang::IllegalArgumentException(); + } + else + { + // standard UNO API write attributes + // adapt former attr from SvxBrushItem::PutValue to new items XATTR_FILL_FIRST, XATTR_FILL_LAST + SfxItemSet aSet( pDoc->GetAttrPool(), + svl::Items<RES_FRMATR_BEGIN, RES_FRMATR_END - 1, + RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER, + + // FillAttribute support + XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + bool bDone(false); + + aSet.SetParent(&pFormat->GetAttrSet()); + + if(RES_BACKGROUND == pEntry->nWID) + { + const SwAttrSet& rSet = pFormat->GetAttrSet(); + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND, true, pDoc->IsInXMLImport())); + std::unique_ptr<SvxBrushItem> aChangedBrushItem(aOriginalBrushItem->Clone()); + + aChangedBrushItem->PutValue(aValue, nMemberId); + + if(*aChangedBrushItem != *aOriginalBrushItem) + { + setSvxBrushItemAsFillAttributesToTargetSet(*aChangedBrushItem, aSet); + pFormat->GetDoc()->SetFlyFrameAttr( *pFormat, aSet ); + } + + bDone = true; + } + else if(OWN_ATTR_FILLBMP_MODE == pEntry->nWID) + { + drawing::BitmapMode eMode; + + if(!(aValue >>= eMode)) + { + sal_Int32 nMode = 0; + + if(!(aValue >>= nMode)) + { + throw lang::IllegalArgumentException(); + } + + eMode = static_cast<drawing::BitmapMode>(nMode); + } + + aSet.Put(XFillBmpStretchItem(drawing::BitmapMode_STRETCH == eMode)); + aSet.Put(XFillBmpTileItem(drawing::BitmapMode_REPEAT == eMode)); + pFormat->GetDoc()->SetFlyFrameAttr( *pFormat, aSet ); + bDone = true; + } + + switch(nMemberId) + { + case MID_NAME: + { + // when named items get set, replace these with the NameOrIndex items + // which exist already in the pool + switch(pEntry->nWID) + { + case XATTR_FILLGRADIENT: + case XATTR_FILLHATCH: + case XATTR_FILLBITMAP: + case XATTR_FILLFLOATTRANSPARENCE: + { + OUString aTempName; + + if(!(aValue >>= aTempName )) + { + throw lang::IllegalArgumentException(); + } + + bDone = SvxShape::SetFillAttribute(pEntry->nWID, aTempName, aSet); + break; + } + default: + { + break; + } + } + break; + } + case MID_BITMAP: + { + switch(pEntry->nWID) + { + case XATTR_FILLBITMAP: + { + const Graphic aNullGraphic; + XFillBitmapItem aXFillBitmapItem(aNullGraphic); + + aXFillBitmapItem.PutValue(aValue, nMemberId); + aSet.Put(aXFillBitmapItem); + bDone = true; + break; + } + default: + { + break; + } + } + break; + } + default: + { + break; + } + } + + if(!bDone) + { + m_pPropSet->setPropertyValue(*pEntry, aValue, aSet); + } + + if(RES_ANCHOR == pEntry->nWID && MID_ANCHOR_ANCHORTYPE == nMemberId) + { + SwFormatAnchor aAnchor = static_cast<const SwFormatAnchor&>(aSet.Get(pEntry->nWID)); + if(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_FLY) + { + const ::SwPosition* pPosition = aAnchor.GetContentAnchor(); + SwFrameFormat* pFlyFormat = pPosition ? pPosition->nNode.GetNode().GetFlyFormat() : nullptr; + if(!pFlyFormat || pFlyFormat->Which() == RES_DRAWFRMFMT) + { + lang::IllegalArgumentException aExcept; + aExcept.Message = "Anchor to frame: no frame found"; + throw aExcept; + } + else + { + SwPosition aPos = *pPosition; + aPos.nNode = *pFlyFormat->GetContent().GetContentIdx(); + aAnchor.SetAnchor(&aPos); + aSet.Put(aAnchor); + } + } + else if ((aAnchor.GetAnchorId() != RndStdIds::FLY_AT_PAGE) && + !aAnchor.GetContentAnchor()) + { + SwNode& rNode = pDoc->GetNodes().GetEndOfContent(); + SwPaM aPam(rNode); + aPam.Move( fnMoveBackward, GoInDoc ); + aAnchor.SetAnchor( aPam.Start() ); + aSet.Put(aAnchor); + } + + // #i31771#, #i25798# - No adjustment of + // anchor ( no call of method <sw_ChkAndSetNewAnchor(..)> ), + // if document is currently in reading mode. + if ( !pFormat->GetDoc()->IsInReading() ) + { + // see SwFEShell::SetFlyFrameAttr( SfxItemSet& rSet ) + SwFlyFrame *pFly = nullptr; + if (auto pFrameFormat = dynamic_cast<SwFlyFrameFormat*>( pFormat) ) + pFly = pFrameFormat->GetFrame(); + if (pFly) + { + const ::SfxPoolItem* pItem; + if( SfxItemState::SET == aSet.GetItemState( RES_ANCHOR, false, &pItem )) + { + aSet.Put( *pItem ); + if ( pFormat->GetDoc()->GetEditShell() != nullptr ) + { + sw_ChkAndSetNewAnchor( *pFly, aSet ); + } + } + } + } + + pFormat->GetDoc()->SetFlyFrameAttr( *pFormat, aSet ); + } + else if(FN_UNO_CLSID == pEntry->nWID || FN_UNO_STREAM_NAME == pEntry->nWID || FN_EMBEDDED_OBJECT == pEntry->nWID) + { + throw lang::IllegalArgumentException(); + } + else + { + pFormat->SetFormatAttr(aSet); + } + } + } + else if(IsDescriptor()) + { + m_pProps->SetProperty(pEntry->nWID, nMemberId, aValue); + if( FN_UNO_FRAME_STYLE_NAME == pEntry->nWID ) + { + OUString sStyleName; + aValue >>= sStyleName; + try + { + uno::Any aAny = mxStyleFamily->getByName ( sStyleName ); + aAny >>= mxStyleData; + } + catch ( container::NoSuchElementException const & ) + { + } + catch ( lang::WrappedTargetException const & ) + { + } + catch ( uno::RuntimeException const & ) + { + } + } + else if (FN_UNO_DRAW_ASPECT == pEntry->nWID) + { + OUString sAspect = ""; + aValue >>= sAspect; + + if (sAspect == "Icon") + m_nDrawAspect = embed::Aspects::MSOLE_ICON; + else if (sAspect == "Content") + m_nDrawAspect = embed::Aspects::MSOLE_CONTENT; + } + else if (FN_UNO_VISIBLE_AREA_WIDTH == pEntry->nWID) + { + OUString sAspect = ""; + aValue >>= sAspect; + m_nVisibleAreaWidth = sAspect.toInt64(); + } + else if (FN_UNO_VISIBLE_AREA_HEIGHT == pEntry->nWID) + { + OUString sAspect = ""; + aValue >>= sAspect; + m_nVisibleAreaHeight = sAspect.toInt64(); + } + } + else + throw uno::RuntimeException(); +} + +uno::Any SwXFrame::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aAny; + SwFrameFormat* pFormat = GetFrameFormat(); + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName); + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const sal_uInt8 nMemberId(pEntry->nMemberId); + + if(FN_UNO_ANCHOR_TYPES == pEntry->nWID) + { + uno::Sequence<text::TextContentAnchorType> aTypes(5); + text::TextContentAnchorType* pArray = aTypes.getArray(); + pArray[0] = text::TextContentAnchorType_AT_PARAGRAPH; + pArray[1] = text::TextContentAnchorType_AS_CHARACTER; + pArray[2] = text::TextContentAnchorType_AT_PAGE; + pArray[3] = text::TextContentAnchorType_AT_FRAME; + pArray[4] = text::TextContentAnchorType_AT_CHARACTER; + aAny <<= aTypes; + } + else if(pFormat) + { + if( ((eType == FLYCNTTYPE_GRF) || (eType == FLYCNTTYPE_OLE)) && + (isGRFATR(pEntry->nWID) || + pEntry->nWID == FN_PARAM_CONTOUR_PP || + pEntry->nWID == FN_UNO_IS_AUTOMATIC_CONTOUR || + pEntry->nWID == FN_UNO_IS_PIXEL_CONTOUR )) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwNoTextNode* pNoText = aIdx.GetNode().GetNoTextNode(); + if(pEntry->nWID == FN_PARAM_CONTOUR_PP) + { + tools::PolyPolygon aContour; + if( pNoText->GetContourAPI( aContour ) ) + { + drawing::PointSequenceSequence aPtSeq(aContour.Count()); + drawing::PointSequence* pPSeq = aPtSeq.getArray(); + for(sal_uInt16 i = 0; i < aContour.Count(); i++) + { + const tools::Polygon& rPoly = aContour.GetObject(i); + pPSeq[i].realloc(rPoly.GetSize()); + awt::Point* pPoints = pPSeq[i].getArray(); + for(sal_uInt16 j = 0; j < rPoly.GetSize(); j++) + { + const Point& rPoint = rPoly.GetPoint(j); + pPoints[j].X = rPoint.X(); + pPoints[j].Y = rPoint.Y(); + } + } + aAny <<= aPtSeq; + } + } + else if(pEntry->nWID == FN_UNO_IS_AUTOMATIC_CONTOUR ) + { + aAny <<= pNoText->HasAutomaticContour(); + } + else if(pEntry->nWID == FN_UNO_IS_PIXEL_CONTOUR ) + { + aAny <<= pNoText->IsPixelContour(); + } + else + { + const SfxItemSet& aSet(pNoText->GetSwAttrSet()); + m_pPropSet->getPropertyValue(*pEntry, aSet, aAny); + } + } + } + else if (FN_UNO_REPLACEMENT_GRAPHIC == pEntry->nWID) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + uno::Reference<graphic::XGraphic> xGraphic; + + if (pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwGrfNode* pGrfNode = aIdx.GetNode().GetGrfNode(); + if (!pGrfNode) + throw uno::RuntimeException(); + + const GraphicObject* pGraphicObject = pGrfNode->GetReplacementGrfObj(); + + if (pGraphicObject) + { + xGraphic = pGraphicObject->GetGraphic().GetXGraphic(); + } + } + aAny <<= xGraphic; + } + else if( FN_UNO_GRAPHIC_FILTER == pEntry->nWID ) + { + OUString sFltName; + SwDoc::GetGrfNms( *static_cast<SwFlyFrameFormat*>(pFormat), nullptr, &sFltName ); + aAny <<= sFltName; + } + else if( FN_UNO_GRAPHIC_URL == pEntry->nWID ) + { + throw uno::RuntimeException("Getting from this property is not supported"); + } + else if( FN_UNO_GRAPHIC == pEntry->nWID ) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwGrfNode* pGrfNode = aIdx.GetNode().GetGrfNode(); + if(!pGrfNode) + throw uno::RuntimeException(); + aAny <<= pGrfNode->GetGrf().GetXGraphic(); + } + } + else if( FN_UNO_TRANSFORMED_GRAPHIC == pEntry->nWID ) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwGrfNode* pGrfNode = aIdx.GetNode().GetGrfNode(); + if(!pGrfNode) + throw uno::RuntimeException(); + + SwDoc* pDoc = pFormat->GetDoc(); + if (pDoc) + { + const SwEditShell* pEditShell = pDoc->GetEditShell(); + if (pEditShell) + { + SwFrame* pCurrFrame = pEditShell->GetCurrFrame(false); + GraphicAttr aGraphicAttr; + pGrfNode->GetGraphicAttr( aGraphicAttr, pCurrFrame ); + const GraphicObject aGraphicObj = pGrfNode->GetGrfObj(); + + awt::Size aFrameSize = getSize(); + Size aSize100thmm(aFrameSize.Width, aFrameSize.Height); + Size aSize = OutputDevice::LogicToLogic(aSize100thmm, MapMode(MapUnit::Map100thMM), aGraphicObj.GetPrefMapMode()); + Graphic aGraphic = aGraphicObj.GetTransformedGraphic(aSize, aGraphicObj.GetPrefMapMode(), aGraphicAttr); + aAny <<= aGraphic.GetXGraphic(); + } + } + } + } + else if(FN_UNO_FRAME_STYLE_NAME == pEntry->nWID) + { + aAny <<= SwStyleNameMapper::GetProgName(pFormat->DerivedFrom()->GetName(), SwGetPoolIdFromName::FrmFmt ); + } + // #i73249# + else if( FN_UNO_TITLE == pEntry->nWID ) + { + SwFlyFrameFormat& rFlyFormat = dynamic_cast<SwFlyFrameFormat&>(*pFormat); + // assure that <SdrObject> instance exists. + GetOrCreateSdrObject(rFlyFormat); + aAny <<= rFlyFormat.GetObjTitle(); + } + // New attribute Description + else if( FN_UNO_DESCRIPTION == pEntry->nWID ) + { + SwFlyFrameFormat& rFlyFormat = dynamic_cast<SwFlyFrameFormat&>(*pFormat); + // assure that <SdrObject> instance exists. + GetOrCreateSdrObject(rFlyFormat); + aAny <<= rFlyFormat.GetObjDescription(); + } + else if(eType == FLYCNTTYPE_GRF && + (rPropertyName == UNO_NAME_ACTUAL_SIZE)) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + Size aActSize = aIdx.GetNode().GetNoTextNode()->GetTwipSize(); + awt::Size aTmp; + aTmp.Width = convertTwipToMm100(aActSize.Width()); + aTmp.Height = convertTwipToMm100(aActSize.Height()); + aAny <<= aTmp; + } + } + else if(FN_PARAM_LINK_DISPLAY_NAME == pEntry->nWID) + { + aAny <<= pFormat->GetName(); + } + else if(FN_UNO_Z_ORDER == pEntry->nWID) + { + const SdrObject* pObj = pFormat->FindRealSdrObject(); + if( pObj == nullptr ) + pObj = pFormat->FindSdrObject(); + if( pObj ) + { + aAny <<= static_cast<sal_Int32>(pObj->GetOrdNum()); + } + } + else if(FN_UNO_CLSID == pEntry->nWID || FN_UNO_MODEL == pEntry->nWID|| + FN_UNO_COMPONENT == pEntry->nWID ||FN_UNO_STREAM_NAME == pEntry->nWID|| + FN_EMBEDDED_OBJECT == pEntry->nWID) + { + SwDoc* pDoc = pFormat->GetDoc(); + const SwFormatContent* pCnt = &pFormat->GetContent(); + OSL_ENSURE( pCnt->GetContentIdx() && + pDoc->GetNodes()[ pCnt->GetContentIdx()-> + GetIndex() + 1 ]->GetOLENode(), "no OLE-Node?"); + + SwOLENode* pOleNode = pDoc->GetNodes()[ pCnt->GetContentIdx() + ->GetIndex() + 1 ]->GetOLENode(); + uno::Reference < embed::XEmbeddedObject > xIP = pOleNode->GetOLEObj().GetOleRef(); + OUString aHexCLSID; + { + SvGlobalName aClassName( xIP->getClassID() ); + aHexCLSID = aClassName.GetHexName(); + if(FN_UNO_CLSID != pEntry->nWID) + { + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + uno::Reference < lang::XComponent > xComp( xIP->getComponent(), uno::UNO_QUERY ); + uno::Reference < frame::XModel > xModel( xComp, uno::UNO_QUERY ); + if ( FN_EMBEDDED_OBJECT == pEntry->nWID ) + { + // when exposing the EmbeddedObject, ensure it has a client site + OSL_ENSURE( pDoc->GetDocShell(), "no doc shell => no client site" ); + if ( pDoc->GetDocShell() ) + pDoc->GetDocShell()->GetIPClient( svt::EmbeddedObjectRef( xIP, embed::Aspects::MSOLE_CONTENT ) ); + aAny <<= xIP; + } + else if ( xModel.is() ) + aAny <<= xModel; + else if ( FN_UNO_COMPONENT == pEntry->nWID ) + aAny <<= xComp; + } + } + } + + if(FN_UNO_CLSID == pEntry->nWID) + aAny <<= aHexCLSID; + else if(FN_UNO_STREAM_NAME == pEntry->nWID) + { + aAny <<= pOleNode->GetOLEObj().GetCurrentPersistName(); + } + else if(FN_EMBEDDED_OBJECT == pEntry->nWID) + { + aAny <<= pOleNode->GetOLEObj().GetOleRef(); + } + } + else if(WID_LAYOUT_SIZE == pEntry->nWID) + { + // format document completely in order to get correct value + pFormat->GetDoc()->GetEditShell()->CalcLayout(); + + SwFrame* pTmpFrame = SwIterator<SwFrame,SwFormat>( *pFormat ).First(); + if ( pTmpFrame ) + { + OSL_ENSURE( pTmpFrame->isFrameAreaDefinitionValid(), "frame not valid" ); + const SwRect &rRect = pTmpFrame->getFrameArea(); + Size aMM100Size = OutputDevice::LogicToLogic( + Size( rRect.Width(), rRect.Height() ), + MapMode( MapUnit::MapTwip ), MapMode( MapUnit::Map100thMM )); + aAny <<= awt::Size( aMM100Size.Width(), aMM100Size.Height() ); + } + } + else if(pEntry->nWID == FN_UNO_PARENT_TEXT) + { + if (!m_xParentText.is()) + { + const SwPosition* pContentAnchor = pFormat->GetAnchor().GetContentAnchor(); + if (pContentAnchor) + { + m_xParentText = sw::CreateParentXText(*pFormat->GetDoc(), *pContentAnchor); + } + } + aAny <<= m_xParentText; + } + else + { + // standard UNO API read attributes + // adapt former attr from SvxBrushItem::PutValue to new items XATTR_FILL_FIRST, XATTR_FILL_LAST + const SwAttrSet& rSet = pFormat->GetAttrSet(); + bool bDone(false); + + if(RES_BACKGROUND == pEntry->nWID) + { + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND)); + + if(!aOriginalBrushItem->QueryValue(aAny, nMemberId)) + { + OSL_ENSURE(false, "Error getting attribute from RES_BACKGROUND (!)"); + } + + bDone = true; + } + else if(OWN_ATTR_FILLBMP_MODE == pEntry->nWID) + { + if (rSet.Get(XATTR_FILLBMP_TILE).GetValue()) + { + aAny <<= drawing::BitmapMode_REPEAT; + } + else if (rSet.Get(XATTR_FILLBMP_STRETCH).GetValue()) + { + aAny <<= drawing::BitmapMode_STRETCH; + } + else + { + aAny <<= drawing::BitmapMode_NO_REPEAT; + } + + bDone = true; + } + + if(!bDone) + { + m_pPropSet->getPropertyValue(*pEntry, rSet, aAny); + } + } + } + else if(IsDescriptor()) + { + if ( ! m_pDoc ) + throw uno::RuntimeException(); + if(WID_LAYOUT_SIZE != pEntry->nWID) // there is no LayoutSize in a descriptor + { + const uno::Any* pAny = nullptr; + if (!m_pProps->GetProperty(pEntry->nWID, nMemberId, pAny)) + aAny = mxStyleData->getPropertyValue( rPropertyName ); + else if ( pAny ) + aAny = *pAny; + } + } + else + throw uno::RuntimeException(); + + if (pEntry->aType == ::cppu::UnoType<sal_Int16>::get() && pEntry->aType != aAny.getValueType()) + { + // since the sfx uint16 item now exports a sal_Int32, we may have to fix this here + sal_Int32 nValue = 0; + aAny >>= nValue; + aAny <<= static_cast<sal_Int16>(nValue); + } + + // check for needed metric translation + if(pEntry->nMoreFlags & PropertyMoreFlags::METRIC_ITEM) + { + bool bDoIt(true); + + if(XATTR_FILLBMP_SIZEX == pEntry->nWID || XATTR_FILLBMP_SIZEY == pEntry->nWID) + { + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + sal_Int32 nValue = 0; + + if(aAny >>= nValue) + { + bDoIt = nValue > 0; + } + } + + if(bDoIt) + { + const SwDoc* pDoc = (IsDescriptor() ? m_pDoc : GetFrameFormat()->GetDoc()); + const SfxItemPool& rPool = pDoc->GetAttrPool(); + const MapUnit eMapUnit(rPool.GetMetric(pEntry->nWID)); + + if(eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertToMM(eMapUnit, aAny); + } + } + } + + return aAny; +} + +void SwXFrame::addPropertyChangeListener(const OUString& /*PropertyName*/, + const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFrame::removePropertyChangeListener(const OUString& /*PropertyName*/, + const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFrame::addVetoableChangeListener(const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFrame::removeVetoableChangeListener( + const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +beans::PropertyState SwXFrame::getPropertyState( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + uno::Sequence< OUString > aPropertyNames { rPropertyName }; + uno::Sequence< beans::PropertyState > aStates = getPropertyStates(aPropertyNames); + return aStates.getConstArray()[0]; +} + +uno::Sequence< beans::PropertyState > SwXFrame::getPropertyStates( + const uno::Sequence< OUString >& aPropertyNames ) +{ + SolarMutexGuard aGuard; + uno::Sequence< beans::PropertyState > aStates(aPropertyNames.getLength()); + beans::PropertyState* pStates = aStates.getArray(); + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + const OUString* pNames = aPropertyNames.getConstArray(); + const SwAttrSet& rFormatSet = pFormat->GetAttrSet(); + for(int i = 0; i < aPropertyNames.getLength(); i++) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(pNames[i]); + if (!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + pNames[i], static_cast < cppu::OWeakObject * > ( this ) ); + + if(pEntry->nWID == FN_UNO_ANCHOR_TYPES|| + pEntry->nWID == FN_PARAM_LINK_DISPLAY_NAME|| + FN_UNO_FRAME_STYLE_NAME == pEntry->nWID|| + FN_UNO_GRAPHIC == pEntry->nWID|| + FN_UNO_GRAPHIC_URL == pEntry->nWID|| + FN_UNO_GRAPHIC_FILTER == pEntry->nWID|| + FN_UNO_ACTUAL_SIZE == pEntry->nWID|| + FN_UNO_ALTERNATIVE_TEXT == pEntry->nWID) + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + else if(OWN_ATTR_FILLBMP_MODE == pEntry->nWID) + { + if(SfxItemState::SET == rFormatSet.GetItemState(XATTR_FILLBMP_STRETCH, false) + || SfxItemState::SET == rFormatSet.GetItemState(XATTR_FILLBMP_TILE, false)) + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_AMBIGUOUS_VALUE; + } + } + // for FlyFrames we need to mark the used properties from type RES_BACKGROUND + // as beans::PropertyState_DIRECT_VALUE to let users of this property call + // getPropertyValue where the member properties will be mapped from the + // fill attributes to the according SvxBrushItem entries + else if (RES_BACKGROUND == pEntry->nWID) + { + if (SWUnoHelper::needToMapFillItemsToSvxBrushItemTypes(rFormatSet, pEntry->nMemberId)) + pStates[i] = beans::PropertyState_DIRECT_VALUE; + else + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + else + { + if ((eType == FLYCNTTYPE_GRF) && isGRFATR(pEntry->nWID)) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwNoTextNode* pNoText = aIdx.GetNode().GetNoTextNode(); + const SfxItemSet& aSet(pNoText->GetSwAttrSet()); + aSet.GetItemState(pEntry->nWID); + if(SfxItemState::SET == aSet.GetItemState( pEntry->nWID, false )) + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + } + else + { + if(SfxItemState::SET == rFormatSet.GetItemState( pEntry->nWID, false )) + pStates[i] = beans::PropertyState_DIRECT_VALUE; + else + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + } + } + } + else if(IsDescriptor()) + { + std::fill(aStates.begin(), aStates.end(), beans::PropertyState_DIRECT_VALUE); + } + else + throw uno::RuntimeException(); + return aStates; +} + +void SwXFrame::setPropertyToDefault( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName); + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw uno::RuntimeException("setPropertyToDefault: property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + bool bNextFrame; + if(OWN_ATTR_FILLBMP_MODE == pEntry->nWID) + { + SwDoc* pDoc = pFormat->GetDoc(); + SfxItemSet aSet(pDoc->GetAttrPool(), svl::Items<XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + aSet.SetParent(&pFormat->GetAttrSet()); + + aSet.ClearItem(XATTR_FILLBMP_STRETCH); + aSet.ClearItem(XATTR_FILLBMP_TILE); + + pFormat->SetFormatAttr(aSet); + } + else if( pEntry->nWID && + pEntry->nWID != FN_UNO_ANCHOR_TYPES && + pEntry->nWID != FN_PARAM_LINK_DISPLAY_NAME) + { + if ( (eType == FLYCNTTYPE_GRF) && isGRFATR(pEntry->nWID) ) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if(pIdx) + { + SwNodeIndex aIdx(*pIdx, 1); + SwNoTextNode* pNoText = aIdx.GetNode().GetNoTextNode(); + { + SfxItemSet aSet(pNoText->GetSwAttrSet()); + aSet.ClearItem(pEntry->nWID); + pNoText->SetAttr(aSet); + } + } + } + // #i73249# + else if( FN_UNO_TITLE == pEntry->nWID ) + { + SwFlyFrameFormat& rFlyFormat = dynamic_cast<SwFlyFrameFormat&>(*pFormat); + // assure that <SdrObject> instance exists. + GetOrCreateSdrObject(rFlyFormat); + rFlyFormat.GetDoc()->SetFlyFrameTitle(rFlyFormat, OUString()); + } + // New attribute Description + else if( FN_UNO_DESCRIPTION == pEntry->nWID ) + { + SwFlyFrameFormat& rFlyFormat = dynamic_cast<SwFlyFrameFormat&>(*pFormat); + // assure that <SdrObject> instance exists. + GetOrCreateSdrObject(rFlyFormat); + rFlyFormat.GetDoc()->SetFlyFrameDescription(rFlyFormat, OUString()); + } + else + { + SwDoc* pDoc = pFormat->GetDoc(); + SfxItemSet aSet( pDoc->GetAttrPool(), + svl::Items<RES_FRMATR_BEGIN, RES_FRMATR_END - 1>{} ); + aSet.SetParent(&pFormat->GetAttrSet()); + aSet.ClearItem(pEntry->nWID); + if(rPropertyName != UNO_NAME_ANCHOR_TYPE) + pFormat->SetFormatAttr(aSet); + } + } + else + { + bNextFrame = rPropertyName == UNO_NAME_CHAIN_NEXT_NAME; + if( bNextFrame || rPropertyName == UNO_NAME_CHAIN_PREV_NAME ) + { + SwDoc* pDoc = pFormat->GetDoc(); + if(bNextFrame) + pDoc->Unchain(*pFormat); + else + { + const SwFormatChain& aChain( pFormat->GetChain() ); + SwFrameFormat *pPrev = aChain.GetPrev(); + if(pPrev) + pDoc->Unchain(*pPrev); + } + } + } + } + else if(!IsDescriptor()) + throw uno::RuntimeException(); + +} + +uno::Any SwXFrame::getPropertyDefault( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName); + if(!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if ( pEntry->nWID < RES_FRMATR_END ) + { + const SfxPoolItem& rDefItem = + pFormat->GetDoc()->GetAttrPool().GetDefaultItem(pEntry->nWID); + rDefItem.QueryValue(aRet, pEntry->nMemberId); + } + + } + else if(!IsDescriptor()) + throw uno::RuntimeException(); + return aRet; +} + +void SAL_CALL SwXFrame::addEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXFrame::removeEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +void SwXFrame::DisposeInternal() +{ + mxStyleData.clear(); + mxStyleFamily.clear(); + m_pDoc = nullptr; + uno::Reference<uno::XInterface> const xThis(m_pImpl->m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_pImpl->m_EventListeners.disposeAndClear(ev); + m_pFrameFormat = nullptr; + EndListeningAll(); +} +void SwXFrame::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + DisposeInternal(); +} + +void SwXFrame::dispose() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if (pFormat) + { + DisposeInternal(); + SdrObject* pObj = pFormat->FindSdrObject(); + // OD 11.09.2003 #112039# - add condition to perform delete of + // format/anchor sign, not only if the object is inserted, but also + // if a contact object is registered, which isn't in the destruction. + if ( pObj && + ( pObj->IsInserted() || + ( pObj->GetUserCall() && + !static_cast<SwContact*>(pObj->GetUserCall())->IsInDTOR() ) ) ) + { + if (pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + const SwPosition &rPos = *(pFormat->GetAnchor().GetContentAnchor()); + SwTextNode *pTextNode = rPos.nNode.GetNode().GetTextNode(); + const sal_Int32 nIdx = rPos.nContent.GetIndex(); + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + else + pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat(pFormat); + } + } + +} + +uno::Reference< text::XTextRange > SwXFrame::getAnchor() +{ + SolarMutexGuard aGuard; + uno::Reference< text::XTextRange > aRef; + SwFrameFormat* pFormat = GetFrameFormat(); + if(!pFormat) + throw uno::RuntimeException(); + + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + // return an anchor for non-page bound frames + // and for page bound frames that have a page no == NULL and a content position + if ((rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PAGE) || + (rAnchor.GetContentAnchor() && !rAnchor.GetPageNum())) + { + const SwPosition &rPos = *(rAnchor.GetContentAnchor()); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { // ensure that SwXTextRange has SwIndex + aRef = SwXTextRange::CreateXTextRange(*pFormat->GetDoc(), SwPosition(rPos.nNode), nullptr); + } + else + { + aRef = SwXTextRange::CreateXTextRange(*pFormat->GetDoc(), rPos, nullptr); + } + } + + return aRef; +} + +void SwXFrame::ResetDescriptor() +{ + bIsDescriptor = false; + mxStyleData.clear(); + mxStyleFamily.clear(); + m_pProps.reset(); +} + +void SwXFrame::attachToRange(uno::Reference<text::XTextRange> const& xTextRange, + SwPaM const*const pCopySource) +{ + SolarMutexGuard aGuard; + if(!IsDescriptor()) + throw uno::RuntimeException(); + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + if(xRangeTunnel.is()) + { + pRange = reinterpret_cast< SwXTextRange * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( SwXTextRange::getUnoTunnelId()) )); + pCursor = reinterpret_cast< OTextCursorHelper * >( + sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething( OTextCursorHelper::getUnoTunnelId()) )); + } + + SwDoc* pDoc = pRange ? &pRange->GetDoc() : pCursor ? pCursor->GetDoc() : nullptr; + if(!pDoc) + throw lang::IllegalArgumentException(); + + SwUnoInternalPaM aIntPam(*pDoc); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aIntPam, xTextRange); + + SwNode& rNode = pDoc->GetNodes().GetEndOfContent(); + SwPaM aPam(rNode); + aPam.Move( fnMoveBackward, GoInDoc ); + static sal_uInt16 const aFrameAttrRange[] = + { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER, + + // FillAttribute support + XATTR_FILL_FIRST, XATTR_FILL_LAST, + + SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER, + 0 + }; + static sal_uInt16 const aGrAttrRange[] = + { + RES_GRFATR_BEGIN, RES_GRFATR_END-1, + 0 + }; + SfxItemSet aGrSet(pDoc->GetAttrPool(), aGrAttrRange ); + + SfxItemSet aFrameSet(pDoc->GetAttrPool(), aFrameAttrRange ); + + // set correct parent to get the XFILL_NONE FillStyle as needed + aFrameSet.SetParent(&pDoc->GetDfltFrameFormat()->GetAttrSet()); + + // no the related items need to be added to the set + bool bSizeFound; + if (!m_pProps->AnyToItemSet(pDoc, aFrameSet, aGrSet, bSizeFound)) + throw lang::IllegalArgumentException(); + // a TextRange is handled separately + *aPam.GetPoint() = *aIntPam.GetPoint(); + if(aIntPam.HasMark()) + { + aPam.SetMark(); + *aPam.GetMark() = *aIntPam.GetMark(); + } + + const SfxPoolItem* pItem; + RndStdIds eAnchorId = RndStdIds::FLY_AT_PARA; + if(SfxItemState::SET == aFrameSet.GetItemState(RES_ANCHOR, false, &pItem) ) + { + eAnchorId = static_cast<const SwFormatAnchor*>(pItem)->GetAnchorId(); + if( RndStdIds::FLY_AT_FLY == eAnchorId && + !aPam.GetNode().FindFlyStartNode()) + { + // framebound only where a frame exists + SwFormatAnchor aAnchor(RndStdIds::FLY_AT_PARA); + aFrameSet.Put(aAnchor); + } + else if ((RndStdIds::FLY_AT_PAGE == eAnchorId) && + 0 == static_cast<const SwFormatAnchor*>(pItem)->GetPageNum() ) + { + SwFormatAnchor aAnchor( *static_cast<const SwFormatAnchor*>(pItem) ); + aAnchor.SetAnchor( aPam.GetPoint() ); + aFrameSet.Put(aAnchor); + } + + if (eAnchorId == RndStdIds::FLY_AT_PAGE) + { + sal_Int16 nRelOrient(aFrameSet.Get(RES_HORI_ORIENT).GetRelationOrient()); + if (sw::GetAtPageRelOrientation(nRelOrient, true)) + { + SAL_WARN("sw.core", "SwXFrame: fixing invalid horizontal RelOrientation for at-page anchor"); + + SwFormatHoriOrient item(aFrameSet.Get(RES_HORI_ORIENT)); + item.SetRelationOrient(nRelOrient); + aFrameSet.Put(item); + } + } + } + + const ::uno::Any* pStyle; + SwFrameFormat *pParentFrameFormat = nullptr; + if (m_pProps->GetProperty(FN_UNO_FRAME_STYLE_NAME, 0, pStyle)) + pParentFrameFormat = lcl_GetFrameFormat( *pStyle, pDoc ); + + SwFlyFrameFormat* pFormat = nullptr; + if( eType == FLYCNTTYPE_FRM) + { + UnoActionContext aCont(pDoc); + if (pCopySource) + { + std::unique_ptr<SwFormatAnchor> pAnchorItem; + // the frame is inserted bound to page + // to prevent conflicts if the to-be-anchored position is part of the to-be-copied text + if (eAnchorId != RndStdIds::FLY_AT_PAGE) + { + pAnchorItem.reset(aFrameSet.Get(RES_ANCHOR).Clone()); + aFrameSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PAGE, 1 )); + } + + aPam.DeleteMark(); // mark position node will be deleted! + aIntPam.DeleteMark(); // mark position node will be deleted! + pFormat = pDoc->MakeFlyAndMove( *pCopySource, aFrameSet, + nullptr, + pParentFrameFormat ); + if(pAnchorItem && pFormat) + { + pFormat->DelFrames(); + pAnchorItem->SetAnchor( pCopySource->Start() ); + SfxItemSet aAnchorSet( pDoc->GetAttrPool(), svl::Items<RES_ANCHOR, RES_ANCHOR>{} ); + aAnchorSet.Put( *pAnchorItem ); + pDoc->SetFlyFrameAttr( *pFormat, aAnchorSet ); + } + } + else + { + pFormat = pDoc->MakeFlySection( RndStdIds::FLY_AT_PARA, aPam.GetPoint(), + &aFrameSet, pParentFrameFormat ); + } + if(pFormat) + { + EndListeningAll(); + m_pFrameFormat = pFormat; + StartListening(pFormat->GetNotifier()); + if(!m_sName.isEmpty()) + pDoc->SetFlyName(*pFormat, m_sName); + } + // wake up the SwXTextFrame + static_cast<SwXTextFrame*>(this)->SetDoc( bIsDescriptor ? m_pDoc : GetFrameFormat()->GetDoc() ); + } + else if( eType == FLYCNTTYPE_GRF) + { + UnoActionContext aActionContext(pDoc); + Graphic aGraphic; + + // Read graphic URL from the descriptor, if it has any. + const ::uno::Any* pGraphicURL; + if (m_pProps->GetProperty(FN_UNO_GRAPHIC_URL, 0, pGraphicURL)) + { + OUString sGraphicURL; + uno::Reference<awt::XBitmap> xBitmap; + if (((*pGraphicURL) >>= sGraphicURL) && !sGraphicURL.isEmpty()) + aGraphic = vcl::graphic::loadFromURL(sGraphicURL); + else if ((*pGraphicURL) >>= xBitmap) + { + uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY); + if (xGraphic.is()) + aGraphic = xGraphic; + } + } + + const ::uno::Any* pGraphicAny; + const bool bHasGraphic = m_pProps->GetProperty(FN_UNO_GRAPHIC, 0, pGraphicAny); + if (bHasGraphic) + { + uno::Reference<graphic::XGraphic> xGraphic; + (*pGraphicAny) >>= xGraphic; + aGraphic = Graphic(xGraphic); + } + + OUString sFilterName; + const uno::Any* pFilterAny; + if (m_pProps->GetProperty(FN_UNO_GRAPHIC_FILTER, 0, pFilterAny)) + { + (*pFilterAny) >>= sFilterName; + } + + pFormat = pDoc->getIDocumentContentOperations().InsertGraphic( + aPam, OUString(), sFilterName, &aGraphic, &aFrameSet, &aGrSet, pParentFrameFormat); + if (pFormat) + { + SwGrfNode *pGrfNd = pDoc->GetNodes()[ pFormat->GetContent().GetContentIdx() + ->GetIndex()+1 ]->GetGrfNode(); + if (pGrfNd) + pGrfNd->SetChgTwipSize( !bSizeFound ); + m_pFrameFormat = pFormat; + EndListeningAll(); + StartListening(m_pFrameFormat->GetNotifier()); + if(!m_sName.isEmpty()) + pDoc->SetFlyName(*pFormat, m_sName); + + } + const ::uno::Any* pSurroundContour; + if (m_pProps->GetProperty(RES_SURROUND, MID_SURROUND_CONTOUR, pSurroundContour)) + setPropertyValue(UNO_NAME_SURROUND_CONTOUR, *pSurroundContour); + const ::uno::Any* pContourOutside; + if (m_pProps->GetProperty(RES_SURROUND, MID_SURROUND_CONTOUROUTSIDE, pContourOutside)) + setPropertyValue(UNO_NAME_CONTOUR_OUTSIDE, *pContourOutside); + const ::uno::Any* pContourPoly; + if (m_pProps->GetProperty(FN_PARAM_CONTOUR_PP, 0, pContourPoly)) + setPropertyValue(UNO_NAME_CONTOUR_POLY_POLYGON, *pContourPoly); + const ::uno::Any* pPixelContour; + if (m_pProps->GetProperty(FN_UNO_IS_PIXEL_CONTOUR, 0, pPixelContour)) + setPropertyValue(UNO_NAME_IS_PIXEL_CONTOUR, *pPixelContour); + const ::uno::Any* pAutoContour; + if (m_pProps->GetProperty(FN_UNO_IS_AUTOMATIC_CONTOUR, 0, pAutoContour)) + setPropertyValue(UNO_NAME_IS_AUTOMATIC_CONTOUR, *pAutoContour); + } + else + { + const ::uno::Any* pCLSID = nullptr; + const ::uno::Any* pStreamName = nullptr; + const ::uno::Any* pEmbeddedObject = nullptr; + if (!m_pProps->GetProperty(FN_UNO_CLSID, 0, pCLSID) + && !m_pProps->GetProperty(FN_UNO_STREAM_NAME, 0, pStreamName) + && !m_pProps->GetProperty(FN_EMBEDDED_OBJECT, 0, pEmbeddedObject)) + { + throw uno::RuntimeException(); + } + if(pCLSID) + { + OUString aCLSID; + SvGlobalName aClassName; + uno::Reference < embed::XEmbeddedObject > xIPObj; + std::unique_ptr < comphelper::EmbeddedObjectContainer > pCnt; + if( (*pCLSID) >>= aCLSID ) + { + if( !aClassName.MakeId( aCLSID ) ) + { + lang::IllegalArgumentException aExcept; + aExcept.Message = "CLSID invalid"; + throw aExcept; + } + + pCnt.reset( new comphelper::EmbeddedObjectContainer ); + OUString aName; + + OUString sDocumentBaseURL = pDoc->GetPersist()->getDocumentBaseURL(); + xIPObj = pCnt->CreateEmbeddedObject(aClassName.GetByteSequence(), aName, + &sDocumentBaseURL); + } + if ( xIPObj.is() ) + { + UnoActionContext aAction(pDoc); + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + + // tdf#99631 set imported VisibleArea settings of embedded XLSX OLE objects + if ( m_nDrawAspect == embed::Aspects::MSOLE_CONTENT + && m_nVisibleAreaWidth && m_nVisibleAreaHeight ) + { + sal_Int64 nAspect = m_nDrawAspect; + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xIPObj->getMapUnit( nAspect ) ); + Size aSize( OutputDevice::LogicToLogic(Size( m_nVisibleAreaWidth, m_nVisibleAreaHeight), + MapMode(MapUnit::MapTwip), MapMode(aUnit))); + awt::Size aSz; + aSz.Width = aSize.Width(); + aSz.Height = aSize.Height(); + xIPObj->setVisualAreaSize(m_nDrawAspect, aSz); + } + + if(!bSizeFound) + { + //TODO/LATER: how do I transport it to the OLENode? + sal_Int64 nAspect = m_nDrawAspect; + + // TODO/LEAN: VisualArea still needs running state + svt::EmbeddedObjectRef::TryRunningState( xIPObj ); + + // set parent to get correct VisArea(in case of object needing parent printer) + uno::Reference < container::XChild > xChild( xIPObj, uno::UNO_QUERY ); + if ( xChild.is() ) + xChild->setParent( pDoc->GetDocShell()->GetModel() ); + + //The Size should be suggested by the OLE server if not manually set + MapUnit aRefMap = VCLUnoHelper::UnoEmbed2VCLMapUnit( xIPObj->getMapUnit( nAspect ) ); + awt::Size aSize; + try + { + aSize = xIPObj->getVisualAreaSize( nAspect ); + } + catch ( embed::NoVisualAreaSizeException& ) + { + // the default size will be set later + } + + Size aSz( aSize.Width, aSize.Height ); + if ( !aSz.Width() || !aSz.Height() ) + { + aSz.setWidth(5000); + aSz.setHeight(5000); + aSz = OutputDevice::LogicToLogic(aSz, + MapMode(MapUnit::Map100thMM), MapMode(aRefMap)); + } + MapMode aMyMap( MapUnit::MapTwip ); + aSz = OutputDevice::LogicToLogic(aSz, MapMode(aRefMap), aMyMap); + SwFormatFrameSize aFrameSz; + aFrameSz.SetSize(aSz); + aFrameSet.Put(aFrameSz); + } + SwFlyFrameFormat* pFormat2 = nullptr; + + ::svt::EmbeddedObjectRef xObjRef( xIPObj, m_nDrawAspect); + pFormat2 = pDoc->getIDocumentContentOperations().InsertEmbObject( + aPam, xObjRef, &aFrameSet ); + + // store main document name to show in the title bar + uno::Reference< frame::XTitle > xModelTitle( pDoc->GetDocShell()->GetModel(), css::uno::UNO_QUERY ); + if( xModelTitle.is() ) + xIPObj->setContainerName( xModelTitle->getTitle() ); + + assert(pFormat2 && "Doc->Insert(notxt) failed."); + + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); + m_pFrameFormat = pFormat2; + EndListeningAll(); + StartListening(m_pFrameFormat->GetNotifier()); + if(!m_sName.isEmpty()) + pDoc->SetFlyName(*pFormat2, m_sName); + } + } + else if( pStreamName ) + { + OUString sStreamName; + (*pStreamName) >>= sStreamName; + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + + SwFlyFrameFormat* pFrameFormat = pDoc->getIDocumentContentOperations().InsertOLE( + aPam, sStreamName, m_nDrawAspect, &aFrameSet, nullptr); + + // store main document name to show in the title bar + SwOLENode* pNd = nullptr; + const SwNodeIndex* pIdx = pFrameFormat->GetContent().GetContentIdx(); + if( pIdx ) + { + SwNodeIndex aIdx( *pIdx, 1 ); + SwNoTextNode* pNoText = aIdx.GetNode().GetNoTextNode(); + pNd = pNoText->GetOLENode(); + } + if( pNd ) + { + uno::Reference < embed::XEmbeddedObject > xObj = pNd->GetOLEObj().GetOleRef(); + if( xObj.is() ) + { + uno::Reference< frame::XTitle > xModelTitle( pDoc->GetDocShell()->GetModel(), css::uno::UNO_QUERY ); + if( xModelTitle.is() ) + xObj->setContainerName( xModelTitle->getTitle() ); + } + } + + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); + m_pFrameFormat = pFrameFormat; + EndListeningAll(); + StartListening(m_pFrameFormat->GetNotifier()); + if(!m_sName.isEmpty()) + pDoc->SetFlyName(*pFrameFormat, m_sName); + } + else if (pEmbeddedObject) + { + uno::Reference< embed::XEmbeddedObject > obj; + (*pEmbeddedObject) >>= obj; + svt::EmbeddedObjectRef xObj; + xObj.Assign( obj, embed::Aspects::MSOLE_CONTENT ); + + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + + // Do not call here container::XChild(obj)->setParent() and + // pDoc->GetPersist()->GetEmbeddedObjectContainer().InsertEmbeddedObject: + // they are called indirectly by pDoc->getIDocumentContentOperations().InsertEmbObject + // below. Calling them twice will add the same object twice to EmbeddedObjectContainer's + // pImpl->maNameToObjectMap, and then it will misbehave in + // EmbeddedObjectContainer::StoreAsChildren and SfxObjectShell::SaveCompletedChildren. + + SwFlyFrameFormat* pFrameFormat + = pDoc->getIDocumentContentOperations().InsertEmbObject(aPam, xObj, &aFrameSet); + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); + m_pFrameFormat = pFrameFormat; + EndListeningAll(); + StartListening(m_pFrameFormat->GetNotifier()); + if(!m_sName.isEmpty()) + pDoc->SetFlyName(*pFrameFormat, m_sName); + } + } + if( pFormat && pDoc->getIDocumentDrawModelAccess().GetDrawModel() ) + GetOrCreateSdrObject(*pFormat); + const ::uno::Any* pOrder; + if (m_pProps->GetProperty(FN_UNO_Z_ORDER, 0, pOrder)) + setPropertyValue(UNO_NAME_Z_ORDER, *pOrder); + const ::uno::Any* pReplacement; + if (m_pProps->GetProperty(FN_UNO_REPLACEMENT_GRAPHIC, 0, pReplacement)) + setPropertyValue(UNO_NAME_GRAPHIC, *pReplacement); + // new attribute Title + const ::uno::Any* pTitle; + if (m_pProps->GetProperty(FN_UNO_TITLE, 0, pTitle)) + { + setPropertyValue(UNO_NAME_TITLE, *pTitle); + } + // new attribute Description + const ::uno::Any* pDescription; + if (m_pProps->GetProperty(FN_UNO_DESCRIPTION, 0, pDescription)) + { + setPropertyValue(UNO_NAME_DESCRIPTION, *pDescription); + } + + // For grabbag + const uno::Any* pFrameIntropgrabbagItem; + if (m_pProps->GetProperty(RES_FRMATR_GRABBAG, 0, pFrameIntropgrabbagItem)) + { + setPropertyValue(UNO_NAME_FRAME_INTEROP_GRAB_BAG, *pFrameIntropgrabbagItem); + } + + // reset the flag and delete Descriptor pointer + ResetDescriptor(); +} + +void SwXFrame::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard g; + + SwFrameFormat* pFormat; + if(IsDescriptor()) + attachToRange(xTextRange); + else if(nullptr != (pFormat = GetFrameFormat())) + { + SwDoc* pDoc = pFormat->GetDoc(); + SwUnoInternalPaM aIntPam(*pDoc); + if (!::sw::XTextRangeToSwPaM(aIntPam, xTextRange)) + throw lang::IllegalArgumentException(); + + SfxItemSet aSet( pDoc->GetAttrPool(), svl::Items<RES_ANCHOR, RES_ANCHOR>{} ); + aSet.SetParent(&pFormat->GetAttrSet()); + SwFormatAnchor aAnchor = aSet.Get(RES_ANCHOR); + + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + throw lang::IllegalArgumentException( + "SwXFrame::attach(): re-anchoring AS_CHAR not supported", + *this, 0); + } + + aAnchor.SetAnchor( aIntPam.Start() ); + aSet.Put(aAnchor); + pDoc->SetFlyFrameAttr( *pFormat, aSet ); + + } +} + +awt::Point SwXFrame::getPosition() +{ + SolarMutexGuard aGuard; + uno::RuntimeException aRuntime; + aRuntime.Message = "position cannot be determined with this method"; + throw aRuntime; +} + +void SwXFrame::setPosition(const awt::Point& /*aPosition*/) +{ + SolarMutexGuard aGuard; + uno::RuntimeException aRuntime; + aRuntime.Message = "position cannot be changed with this method"; + throw aRuntime; +} + +awt::Size SwXFrame::getSize() +{ + const ::uno::Any aVal = getPropertyValue("Size"); + awt::Size const * pRet = o3tl::doAccess<awt::Size>(aVal); + return *pRet; +} + +void SwXFrame::setSize(const awt::Size& aSize) +{ + const ::uno::Any aVal(&aSize, ::cppu::UnoType<awt::Size>::get()); + setPropertyValue("Size", aVal); +} + +OUString SwXFrame::getShapeType() +{ + return "FrameShape"; +} + +SwXTextFrame::SwXTextFrame( SwDoc *_pDoc ) : + SwXText(nullptr, CursorType::Frame), + SwXFrame(FLYCNTTYPE_FRM, aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_FRAME), _pDoc ) +{ +} + +SwXTextFrame::SwXTextFrame(SwFrameFormat& rFormat) : + SwXText(rFormat.GetDoc(), CursorType::Frame), + SwXFrame(rFormat, FLYCNTTYPE_FRM, aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_FRAME)) +{ + +} + +SwXTextFrame::~SwXTextFrame() +{ +} + +uno::Reference<text::XTextFrame> +SwXTextFrame::CreateXTextFrame(SwDoc & rDoc, SwFrameFormat *const pFrameFormat) +{ + return CreateXFrame<text::XTextFrame, SwXTextFrame>(rDoc, pFrameFormat); +} + +void SAL_CALL SwXTextFrame::acquire( )throw() +{ + SwXFrame::acquire(); +} + +void SAL_CALL SwXTextFrame::release( )throw() +{ + SwXFrame::release(); +} + +::uno::Any SAL_CALL SwXTextFrame::queryInterface( const uno::Type& aType ) +{ + ::uno::Any aRet = SwXFrame::queryInterface(aType); + if(aRet.getValueType() == cppu::UnoType<void>::get()) + aRet = SwXText::queryInterface(aType); + if(aRet.getValueType() == cppu::UnoType<void>::get()) + aRet = SwXTextFrameBaseClass::queryInterface(aType); + return aRet; +} + +uno::Sequence< uno::Type > SAL_CALL SwXTextFrame::getTypes( ) +{ + return comphelper::concatSequences( + SwXTextFrameBaseClass::getTypes(), + SwXFrame::getTypes(), + SwXText::getTypes() + ); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwXTextFrame::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Reference< text::XText > SwXTextFrame::getText() +{ + return this; +} + +const SwStartNode *SwXTextFrame::GetStartNode() const +{ + const SwStartNode *pSttNd = nullptr; + + const SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + const SwFormatContent& rFlyContent = pFormat->GetContent(); + if( rFlyContent.GetContentIdx() ) + pSttNd = rFlyContent.GetContentIdx()->GetNode().GetStartNode(); + } + + return pSttNd; +} + +uno::Reference< text::XTextCursor > +SwXTextFrame::CreateCursor() +{ + return createTextCursor(); +} + +uno::Reference< text::XTextCursor > SwXTextFrame::createTextCursor() +{ + SolarMutexGuard aGuard; + uno::Reference< text::XTextCursor > aRef; + SwFrameFormat* pFormat = GetFrameFormat(); + if(!pFormat) + throw uno::RuntimeException(); + + //save current start node to be able to check if there is content after the table - + //otherwise the cursor would be in the body text! + const SwNode& rNode = pFormat->GetContent().GetContentIdx()->GetNode(); + const SwStartNode* pOwnStartNode = rNode.FindSttNodeByType(SwFlyStartNode); + + SwPaM aPam(rNode); + aPam.Move(fnMoveForward, GoInNode); + SwTableNode* pTableNode = aPam.GetNode().FindTableNode(); + SwContentNode* pCont = nullptr; + while( pTableNode ) + { + aPam.GetPoint()->nNode = *pTableNode->EndOfSectionNode(); + pCont = GetDoc()->GetNodes().GoNext(&aPam.GetPoint()->nNode); + pTableNode = pCont->FindTableNode(); + } + if(pCont) + aPam.GetPoint()->nContent.Assign(pCont, 0); + + const SwStartNode* pNewStartNode = + aPam.GetNode().FindSttNodeByType(SwFlyStartNode); + if(!pNewStartNode || pNewStartNode != pOwnStartNode) + { + uno::RuntimeException aExcept; + aExcept.Message = "no text available"; + throw aExcept; + } + + SwXTextCursor *const pXCursor = new SwXTextCursor( + *pFormat->GetDoc(), this, CursorType::Frame, *aPam.GetPoint()); + aRef = static_cast<text::XWordCursor*>(pXCursor); + + return aRef; +} + +uno::Reference< text::XTextCursor > SwXTextFrame::createTextCursorByRange(const uno::Reference< text::XTextRange > & aTextPosition) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if (!pFormat) + throw uno::RuntimeException(); + SwUnoInternalPaM aPam(*GetDoc()); + if (!::sw::XTextRangeToSwPaM(aPam, aTextPosition)) + throw uno::RuntimeException(); + + uno::Reference<text::XTextCursor> aRef; + SwNode& rNode = pFormat->GetContent().GetContentIdx()->GetNode(); + if(aPam.GetNode().FindFlyStartNode() == rNode.FindFlyStartNode()) + { + aRef = static_cast<text::XWordCursor*>( + new SwXTextCursor(*pFormat->GetDoc(), this, CursorType::Frame, + *aPam.GetPoint(), aPam.GetMark())); + } + + return aRef; +} + +uno::Reference< container::XEnumeration > SwXTextFrame::createEnumeration() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(!pFormat) + return nullptr; + SwPosition aPos(pFormat->GetContent().GetContentIdx()->GetNode()); + auto pUnoCursor(GetDoc()->CreateUnoCursor(aPos)); + pUnoCursor->Move(fnMoveForward, GoInNode); + return SwXParagraphEnumeration::Create(this, pUnoCursor, CursorType::Frame); +} + +uno::Type SwXTextFrame::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SwXTextFrame::hasElements() +{ + return true; +} + +void SwXTextFrame::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SwXFrame::attach(xTextRange); +} + +uno::Reference< text::XTextRange > SwXTextFrame::getAnchor() +{ + SolarMutexGuard aGuard; + return SwXFrame::getAnchor(); +} + +void SwXTextFrame::dispose() +{ + SolarMutexGuard aGuard; + SwXFrame::dispose(); +} + +void SwXTextFrame::addEventListener(const uno::Reference< lang::XEventListener > & aListener) +{ + SwXFrame::addEventListener(aListener); +} + +void SwXTextFrame::removeEventListener(const uno::Reference< lang::XEventListener > & aListener) +{ + SwXFrame::removeEventListener(aListener); +} + +OUString SwXTextFrame::getImplementationName() +{ + return "SwXTextFrame"; +} + +sal_Bool SwXTextFrame::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextFrame::getSupportedServiceNames() +{ + uno::Sequence < OUString > aRet = SwXFrame::getSupportedServiceNames(); + aRet.realloc(aRet.getLength() + 2); + OUString* pArray = aRet.getArray(); + pArray[aRet.getLength() - 2] = "com.sun.star.text.TextFrame"; + pArray[aRet.getLength() - 1] = "com.sun.star.text.Text"; + return aRet; +} + +uno::Reference<container::XNameReplace > SAL_CALL SwXTextFrame::getEvents() +{ + return new SwFrameEventDescriptor( *this ); +} + +sal_Int64 SAL_CALL SwXTextFrame::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + sal_Int64 nRet = SwXFrame::getSomething( rId ); + if( !nRet ) + nRet = SwXText::getSomething( rId ); + + return nRet; +} + +::uno::Any SwXTextFrame::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + ::uno::Any aRet; + if(rPropertyName == UNO_NAME_START_REDLINE|| + rPropertyName == UNO_NAME_END_REDLINE) + { + //redline can only be returned if it's a living object + if(!IsDescriptor()) + aRet = SwXText::getPropertyValue(rPropertyName); + } + else + aRet = SwXFrame::getPropertyValue(rPropertyName); + return aRet; +} + +SwXTextGraphicObject::SwXTextGraphicObject( SwDoc *pDoc ) + : SwXTextGraphicObjectBaseClass(FLYCNTTYPE_GRF, + aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_GRAPHIC), pDoc) +{ +} + +SwXTextGraphicObject::SwXTextGraphicObject(SwFrameFormat& rFormat) + : SwXTextGraphicObjectBaseClass(rFormat, FLYCNTTYPE_GRF, + aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_GRAPHIC)) +{ +} + +SwXTextGraphicObject::~SwXTextGraphicObject() +{ +} + +uno::Reference<text::XTextContent> +SwXTextGraphicObject::CreateXTextGraphicObject(SwDoc & rDoc, SwFrameFormat *const pFrameFormat) +{ + return CreateXFrame<text::XTextContent, SwXTextGraphicObject>(rDoc, pFrameFormat); +} + +OUString SwXTextGraphicObject::getImplementationName() +{ + return "SwXTextGraphicObject"; +} + +sal_Bool SwXTextGraphicObject::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextGraphicObject::getSupportedServiceNames() +{ + uno::Sequence < OUString > aRet = SwXFrame::getSupportedServiceNames(); + aRet.realloc(aRet.getLength() + 1); + OUString* pArray = aRet.getArray(); + pArray[aRet.getLength() - 1] = "com.sun.star.text.TextGraphicObject"; + return aRet; +} + +uno::Reference<container::XNameReplace> SAL_CALL + SwXTextGraphicObject::getEvents() +{ + return new SwFrameEventDescriptor( *this ); +} + +SwXTextEmbeddedObject::SwXTextEmbeddedObject( SwDoc *pDoc ) + : SwXTextEmbeddedObjectBaseClass(FLYCNTTYPE_OLE, + aSwMapProvider.GetPropertySet(PROPERTY_MAP_EMBEDDED_OBJECT), pDoc) +{ +} + +SwXTextEmbeddedObject::SwXTextEmbeddedObject(SwFrameFormat& rFormat) + : SwXTextEmbeddedObjectBaseClass(rFormat, FLYCNTTYPE_OLE, + aSwMapProvider.GetPropertySet(PROPERTY_MAP_EMBEDDED_OBJECT)) +{ +} + +SwXTextEmbeddedObject::~SwXTextEmbeddedObject() +{ +} + +uno::Reference<text::XTextContent> +SwXTextEmbeddedObject::CreateXTextEmbeddedObject(SwDoc & rDoc, SwFrameFormat *const pFrameFormat) +{ + return CreateXFrame<text::XTextContent, SwXTextEmbeddedObject>(rDoc, pFrameFormat); +} + +uno::Reference< lang::XComponent > SwXTextEmbeddedObject::getEmbeddedObject() +{ + uno::Reference<embed::XEmbeddedObject> xObj(getExtendedControlOverEmbeddedObject()); + return xObj.is() ? uno::Reference<lang::XComponent>(xObj->getComponent(), uno::UNO_QUERY) : nullptr; +} + +uno::Reference< embed::XEmbeddedObject > SAL_CALL SwXTextEmbeddedObject::getExtendedControlOverEmbeddedObject() +{ + uno::Reference< embed::XEmbeddedObject > xResult; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + SwDoc* pDoc = pFormat->GetDoc(); + const SwFormatContent* pCnt = &pFormat->GetContent(); + OSL_ENSURE( pCnt->GetContentIdx() && + pDoc->GetNodes()[ pCnt->GetContentIdx()-> + GetIndex() + 1 ]->GetOLENode(), "no OLE-Node?"); + + SwOLENode* pOleNode = pDoc->GetNodes()[ pCnt->GetContentIdx() + ->GetIndex() + 1 ]->GetOLENode(); + xResult = pOleNode->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xResult ) ) + { + // TODO/LATER: the listener registered after client creation should be able to handle scaling, after that the client is not necessary here + if ( pDoc->GetDocShell() ) + pDoc->GetDocShell()->GetIPClient( svt::EmbeddedObjectRef( xResult, embed::Aspects::MSOLE_CONTENT ) ); + + uno::Reference < lang::XComponent > xComp( xResult->getComponent(), uno::UNO_QUERY ); + uno::Reference< util::XModifyBroadcaster > xBrdcst( xComp, uno::UNO_QUERY); + uno::Reference< frame::XModel > xModel( xComp, uno::UNO_QUERY); + if(xBrdcst.is() && xModel.is() && !m_xOLEListener.is()) + { + m_xOLEListener = new SwXOLEListener(*pFormat, xModel); + xBrdcst->addModifyListener( m_xOLEListener ); + } + } + } + return xResult; +} + +sal_Int64 SAL_CALL SwXTextEmbeddedObject::getAspect() +{ + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + SwDoc* pDoc = pFormat->GetDoc(); + const SwFormatContent* pCnt = &pFormat->GetContent(); + OSL_ENSURE( pCnt->GetContentIdx() && + pDoc->GetNodes()[ pCnt->GetContentIdx()-> + GetIndex() + 1 ]->GetOLENode(), "no OLE-Node?"); + + return pDoc->GetNodes()[ pCnt->GetContentIdx()->GetIndex() + 1 ]->GetOLENode()->GetAspect(); + } + + return embed::Aspects::MSOLE_CONTENT; // return the default value +} + +void SAL_CALL SwXTextEmbeddedObject::setAspect( sal_Int64 nAspect ) +{ + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + SwDoc* pDoc = pFormat->GetDoc(); + const SwFormatContent* pCnt = &pFormat->GetContent(); + OSL_ENSURE( pCnt->GetContentIdx() && + pDoc->GetNodes()[ pCnt->GetContentIdx()-> + GetIndex() + 1 ]->GetOLENode(), "no OLE-Node?"); + + pDoc->GetNodes()[ pCnt->GetContentIdx()->GetIndex() + 1 ]->GetOLENode()->SetAspect( nAspect ); + } +} + +uno::Reference< graphic::XGraphic > SAL_CALL SwXTextEmbeddedObject::getReplacementGraphic() +{ + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + SwDoc* pDoc = pFormat->GetDoc(); + const SwFormatContent* pCnt = &pFormat->GetContent(); + OSL_ENSURE( pCnt->GetContentIdx() && + pDoc->GetNodes()[ pCnt->GetContentIdx()-> + GetIndex() + 1 ]->GetOLENode(), "no OLE-Node?"); + + const Graphic* pGraphic = pDoc->GetNodes()[ pCnt->GetContentIdx()->GetIndex() + 1 ]->GetOLENode()->GetGraphic(); + if ( pGraphic ) + return pGraphic->GetXGraphic(); + } + + return uno::Reference< graphic::XGraphic >(); +} + +OUString SwXTextEmbeddedObject::getImplementationName() +{ + return "SwXTextEmbeddedObject"; +} + +sal_Bool SwXTextEmbeddedObject::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextEmbeddedObject::getSupportedServiceNames() +{ + uno::Sequence < OUString > aRet = SwXFrame::getSupportedServiceNames(); + aRet.realloc(aRet.getLength() + 1); + OUString* pArray = aRet.getArray(); + pArray[aRet.getLength() - 1] = "com.sun.star.text.TextEmbeddedObject"; + return aRet; +} + +uno::Reference<container::XNameReplace> SAL_CALL + SwXTextEmbeddedObject::getEvents() +{ + return new SwFrameEventDescriptor( *this ); +} + +namespace +{ + SwOLENode* lcl_GetOLENode(const SwFormat* pFormat) + { + if(!pFormat) + return nullptr; + const SwNodeIndex* pIdx(pFormat->GetContent().GetContentIdx()); + if(!pIdx) + return nullptr; + const SwNodeIndex aIdx(*pIdx, 1); + return aIdx.GetNode().GetNoTextNode()->GetOLENode(); + } +} + +SwXOLEListener::SwXOLEListener( SwFormat& rOLEFormat, uno::Reference< XModel > const & xOLE) + : m_pOLEFormat(&rOLEFormat) + , m_xOLEModel(xOLE) +{ + StartListening(m_pOLEFormat->GetNotifier()); +} + +SwXOLEListener::~SwXOLEListener() +{} + +void SwXOLEListener::modified( const lang::EventObject& /*rEvent*/ ) +{ + SolarMutexGuard aGuard; + const auto pNd = lcl_GetOLENode(m_pOLEFormat); + if(!pNd) + throw uno::RuntimeException(); + const auto xIP = pNd->GetOLEObj().GetOleRef(); + if(xIP.is()) + { + sal_Int32 nState = xIP->getCurrentState(); + if(nState == embed::EmbedStates::INPLACE_ACTIVE || nState == embed::EmbedStates::UI_ACTIVE) + // if the OLE-Node is UI-Active do nothing + return; + } + pNd->SetOLESizeInvalid(true); + pNd->GetDoc()->SetOLEObjModified(); +} + +void SwXOLEListener::disposing( const lang::EventObject& rEvent ) +{ + SolarMutexGuard aGuard; + uno::Reference<util::XModifyListener> xListener( this ); + uno::Reference<frame::XModel> xModel(rEvent.Source, uno::UNO_QUERY); + uno::Reference<util::XModifyBroadcaster> xBrdcst(xModel, uno::UNO_QUERY); + if(!xBrdcst.is()) + return; + try + { + xBrdcst->removeModifyListener(xListener); + } + catch(uno::Exception const &) + { + OSL_FAIL("OLE Listener couldn't be removed"); + } +} + +void SwXOLEListener::Notify( const SfxHint& rHint ) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_xOLEModel = nullptr; + m_pOLEFormat = nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoftn.cxx b/sw/source/core/unocore/unoftn.cxx new file mode 100644 index 000000000..f9efe7742 --- /dev/null +++ b/sw/source/core/unocore/unoftn.cxx @@ -0,0 +1,575 @@ +/* -*- 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 <sal/config.h> + +#include <comphelper/interfacecontainer2.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svl/listener.hxx> +#include <osl/mutex.hxx> + +#include <unofootnote.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unoparagraph.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <doc.hxx> +#include <ftnidx.hxx> +#include <fmtftn.hxx> +#include <txtftn.hxx> +#include <ndtxt.hxx> +#include <unocrsr.hxx> +#include <svl/itemprop.hxx> + +using namespace ::com::sun::star; + +namespace { + +uno::Sequence< OUString > +GetSupportedServiceNamesImpl( + size_t const nServices, char const*const pServices[]) +{ + uno::Sequence< OUString > ret(static_cast<sal_Int32>(nServices)); + + std::transform(pServices, pServices + nServices, ret.begin(), + [](const char* pService) -> OUString { return OUString::createFromAscii(pService); }); + + return ret; +} + +} + +class SwXFootnote::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + + SwXFootnote& m_rThis; + uno::WeakReference<uno::XInterface> m_wThis; + const bool m_bIsEndnote; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + bool m_bIsDescriptor; + SwFormatFootnote* m_pFormatFootnote; + OUString m_sLabel; + + Impl(SwXFootnote& rThis, + SwFormatFootnote* const pFootnote, + const bool bIsEndnote) + : m_rThis(rThis) + , m_bIsEndnote(bIsEndnote) + , m_EventListeners(m_Mutex) + , m_bIsDescriptor(nullptr == pFootnote) + , m_pFormatFootnote(pFootnote) + { + m_pFormatFootnote && StartListening(m_pFormatFootnote->GetNotifier()); + } + + const SwFormatFootnote* GetFootnoteFormat() const { + return m_rThis.GetDoc() ? m_pFormatFootnote : nullptr; + } + + SwFormatFootnote const& GetFootnoteFormatOrThrow() const { + SwFormatFootnote const*const pFootnote( GetFootnoteFormat() ); + if (!pFootnote) { + throw uno::RuntimeException("SwXFootnote: disposed or invalid", nullptr); + } + return *pFootnote; + } + + void Invalidate(); +protected: + void Notify(const SfxHint& rHint) override; + +}; + +void SwXFootnote::Impl::Invalidate() +{ + EndListeningAll(); + m_pFormatFootnote = nullptr; + m_rThis.SetDoc(nullptr); + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); +} + +void SwXFootnote::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + Invalidate(); +} + +SwXFootnote::SwXFootnote(const bool bEndnote) + : SwXText(nullptr, CursorType::Footnote) + , m_pImpl( new SwXFootnote::Impl(*this, nullptr, bEndnote) ) +{ +} + +SwXFootnote::SwXFootnote(SwDoc & rDoc, SwFormatFootnote & rFormat) + : SwXText(& rDoc, CursorType::Footnote) + , m_pImpl( new SwXFootnote::Impl(*this, &rFormat, rFormat.IsEndNote()) ) +{ +} + +SwXFootnote::~SwXFootnote() +{ +} + +uno::Reference<text::XFootnote> +SwXFootnote::CreateXFootnote(SwDoc & rDoc, SwFormatFootnote *const pFootnoteFormat, + bool const isEndnote) +{ + // i#105557: do not iterate over the registered clients: race condition + uno::Reference<text::XFootnote> xNote; + if (pFootnoteFormat) + { + xNote = pFootnoteFormat->GetXFootnote(); + } + if (!xNote.is()) + { + SwXFootnote *const pNote(pFootnoteFormat + ? new SwXFootnote(rDoc, *pFootnoteFormat) + : new SwXFootnote(isEndnote)); + xNote.set(pNote); + if (pFootnoteFormat) + { + pFootnoteFormat->SetXFootnote(xNote); + } + // need a permanent Reference to initialize m_wThis + pNote->m_pImpl->m_wThis = xNote; + } + return xNote; +} + +namespace +{ + class theSwXFootnoteUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXFootnoteUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXFootnote::getUnoTunnelId() +{ + return theSwXFootnoteUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXFootnote::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + const sal_Int64 nRet( ::sw::UnoTunnelImpl<SwXFootnote>(rId, this) ); + return nRet ? nRet : SwXText::getSomething(rId); +} + +OUString SAL_CALL +SwXFootnote::getImplementationName() +{ + return "SwXFootnote"; +} + +static char const*const g_ServicesFootnote[] = +{ + "com.sun.star.text.TextContent", + "com.sun.star.text.Footnote", + "com.sun.star.text.Text", + "com.sun.star.text.Endnote", // NB: only supported for endnotes! +}; + +static const size_t g_nServicesEndnote( SAL_N_ELEMENTS(g_ServicesFootnote) ); + +static const size_t g_nServicesFootnote( g_nServicesEndnote - 1 ); // NB: omit! + +sal_Bool SAL_CALL SwXFootnote::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXFootnote::getSupportedServiceNames() +{ + SolarMutexGuard g; + return GetSupportedServiceNamesImpl( + (m_pImpl->m_bIsEndnote) ? g_nServicesEndnote : g_nServicesFootnote, + g_ServicesFootnote); +} + +uno::Sequence< uno::Type > SAL_CALL +SwXFootnote::getTypes() +{ + const uno::Sequence< uno::Type > aTypes = SwXFootnote_Base::getTypes(); + const uno::Sequence< uno::Type > aTextTypes = SwXText::getTypes(); + return ::comphelper::concatSequences(aTypes, aTextTypes); +} + +uno::Sequence< sal_Int8 > SAL_CALL +SwXFootnote::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Any SAL_CALL +SwXFootnote::queryInterface(const uno::Type& rType) +{ + const uno::Any ret = SwXFootnote_Base::queryInterface(rType); + return (ret.getValueType() == cppu::UnoType<void>::get()) + ? SwXText::queryInterface(rType) + : ret; +} + +OUString SAL_CALL SwXFootnote::getLabel() +{ + SolarMutexGuard aGuard; + + OUString sRet; + SwFormatFootnote const*const pFormat = m_pImpl->GetFootnoteFormat(); + if(pFormat) + { + sRet = pFormat->GetNumStr(); + } + else if (m_pImpl->m_bIsDescriptor) + { + sRet = m_pImpl->m_sLabel; + } + else + { + throw uno::RuntimeException(); + } + return sRet; +} + +void SAL_CALL +SwXFootnote::setLabel(const OUString& aLabel) +{ + SolarMutexGuard aGuard; + OUString newLabel(aLabel); + //new line must not occur as footnote label + if(newLabel.indexOf('\n') >=0 ) + { + newLabel = newLabel.replace('\n', ' '); + } + SwFormatFootnote const*const pFormat = m_pImpl->GetFootnoteFormat(); + if(pFormat) + { + const SwTextFootnote* pTextFootnote = pFormat->GetTextFootnote(); + OSL_ENSURE(pTextFootnote, "No TextNode?"); + SwTextNode& rTextNode = const_cast<SwTextNode&>(pTextFootnote->GetTextNode()); + + SwPaM aPam(rTextNode, pTextFootnote->GetStart()); + GetDoc()->SetCurFootnote(aPam, newLabel, pFormat->IsEndNote()); + } + else if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_sLabel = newLabel; + } + else + { + throw uno::RuntimeException(); + } +} + +void SAL_CALL +SwXFootnote::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + const uno::Reference<lang::XUnoTunnel> xRangeTunnel( + xTextRange, uno::UNO_QUERY); + SwXTextRange *const pRange = + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + SwDoc *const pNewDoc = + pRange ? &pRange->GetDoc() : (pCursor ? pCursor->GetDoc() : nullptr); + if (!pNewDoc) + { + throw lang::IllegalArgumentException(); + } + + SwUnoInternalPaM aPam(*pNewDoc); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + + UnoActionContext aCont(pNewDoc); + pNewDoc->getIDocumentContentOperations().DeleteAndJoin(aPam); + aPam.DeleteMark(); + SwFormatFootnote aFootNote(m_pImpl->m_bIsEndnote); + if (!m_pImpl->m_sLabel.isEmpty()) + { + aFootNote.SetNumStr(m_pImpl->m_sLabel); + } + + SwXTextCursor const*const pTextCursor( + dynamic_cast<SwXTextCursor*>(pCursor)); + const bool bForceExpandHints( pTextCursor && pTextCursor->IsAtEndOfMeta() ); + const SetAttrMode nInsertFlags = bForceExpandHints + ? SetAttrMode::FORCEHINTEXPAND + : SetAttrMode::DEFAULT; + + pNewDoc->getIDocumentContentOperations().InsertPoolItem(aPam, aFootNote, nInsertFlags); + + SwTextFootnote *const pTextAttr = static_cast<SwTextFootnote*>( + aPam.GetNode().GetTextNode()->GetTextAttrForCharAt( + aPam.GetPoint()->nContent.GetIndex()-1, RES_TXTATR_FTN )); + + if (pTextAttr) + { + m_pImpl->EndListeningAll(); + SwFormatFootnote* pFootnote = const_cast<SwFormatFootnote*>(&pTextAttr->GetFootnote()); + m_pImpl->m_pFormatFootnote = pFootnote; + m_pImpl->StartListening(pFootnote->GetNotifier()); + // force creation of sequence id - is used for references + if (pNewDoc->IsInReading()) + { + pTextAttr->SetSeqNo(pNewDoc->GetFootnoteIdxs().size()); + } + else + { + pTextAttr->SetSeqRefNo(); + } + } + m_pImpl->m_bIsDescriptor = false; + SetDoc(pNewDoc); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXFootnote::getAnchor() +{ + SolarMutexGuard aGuard; + return m_pImpl->GetFootnoteFormatOrThrow().getAnchor(*GetDoc()); +} + +void SAL_CALL SwXFootnote::dispose() +{ + SolarMutexGuard aGuard; + + SwFormatFootnote const& rFormat( m_pImpl->GetFootnoteFormatOrThrow() ); + + SwTextFootnote const*const pTextFootnote = rFormat.GetTextFootnote(); + OSL_ENSURE(pTextFootnote, "no TextNode?"); + SwTextNode& rTextNode = const_cast<SwTextNode&>(pTextFootnote->GetTextNode()); + const sal_Int32 nPos = pTextFootnote->GetStart(); + SwPaM aPam(rTextNode, nPos, rTextNode, nPos+1); + GetDoc()->getIDocumentContentOperations().DeleteAndJoin( aPam ); +} + +void SAL_CALL +SwXFootnote::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL +SwXFootnote::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +const SwStartNode *SwXFootnote::GetStartNode() const +{ + SwFormatFootnote const*const pFormat = m_pImpl->GetFootnoteFormat(); + if(pFormat) + { + const SwTextFootnote* pTextFootnote = pFormat->GetTextFootnote(); + if( pTextFootnote ) + { + return pTextFootnote->GetStartNode()->GetNode().GetStartNode(); + } + } + return nullptr; +} + +uno::Reference< text::XTextCursor > +SwXFootnote::CreateCursor() +{ + return createTextCursor(); +} + +uno::Reference< text::XTextCursor > SAL_CALL +SwXFootnote::createTextCursor() +{ + SolarMutexGuard aGuard; + + SwFormatFootnote const& rFormat( m_pImpl->GetFootnoteFormatOrThrow() ); + + SwTextFootnote const*const pTextFootnote = rFormat.GetTextFootnote(); + SwPosition aPos( *pTextFootnote->GetStartNode() ); + SwXTextCursor *const pXCursor = + new SwXTextCursor(*GetDoc(), this, CursorType::Footnote, aPos); + auto& rUnoCursor(pXCursor->GetCursor()); + rUnoCursor.Move(fnMoveForward, GoInNode); + const uno::Reference< text::XTextCursor > xRet = + static_cast<text::XWordCursor*>(pXCursor); + return xRet; +} + +uno::Reference< text::XTextCursor > SAL_CALL +SwXFootnote::createTextCursorByRange( + const uno::Reference< text::XTextRange > & xTextPosition) +{ + SolarMutexGuard aGuard; + + SwFormatFootnote const& rFormat( m_pImpl->GetFootnoteFormatOrThrow() ); + + SwUnoInternalPaM aPam(*GetDoc()); + if (!::sw::XTextRangeToSwPaM(aPam, xTextPosition)) + { + throw uno::RuntimeException(); + } + + SwTextFootnote const*const pTextFootnote = rFormat.GetTextFootnote(); + SwNode const*const pFootnoteStartNode = &pTextFootnote->GetStartNode()->GetNode(); + + const SwNode* pStart = aPam.GetNode().FindFootnoteStartNode(); + if (pStart != pFootnoteStartNode) + { + throw uno::RuntimeException(); + } + + const uno::Reference< text::XTextCursor > xRet = + static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), this, CursorType::Footnote, + *aPam.GetPoint(), aPam.GetMark())); + return xRet; +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXFootnote::createEnumeration() +{ + SolarMutexGuard aGuard; + + SwFormatFootnote const& rFormat( m_pImpl->GetFootnoteFormatOrThrow() ); + + SwTextFootnote const*const pTextFootnote = rFormat.GetTextFootnote(); + SwPosition aPos( *pTextFootnote->GetStartNode() ); + auto pUnoCursor(GetDoc()->CreateUnoCursor(aPos)); + pUnoCursor->Move(fnMoveForward, GoInNode); + return SwXParagraphEnumeration::Create(this, pUnoCursor, CursorType::Footnote); +} + +uno::Type SAL_CALL SwXFootnote::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SwXFootnote::hasElements() +{ + return true; +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXFootnote::getPropertySetInfo() +{ + SolarMutexGuard g; + static uno::Reference< beans::XPropertySetInfo > xRet = + aSwMapProvider.GetPropertySet(PROPERTY_MAP_FOOTNOTE) + ->getPropertySetInfo(); + return xRet; +} + +void SAL_CALL +SwXFootnote::setPropertyValue(const OUString&, const uno::Any&) +{ + //no values to be set + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL +SwXFootnote::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Any aRet; + if (! ::sw::GetDefaultTextContentValue(aRet, rPropertyName)) + { + if (rPropertyName == UNO_NAME_START_REDLINE || + rPropertyName == UNO_NAME_END_REDLINE) + { + //redline can only be returned if it's a living object + if (!m_pImpl->m_bIsDescriptor) + { + aRet = SwXText::getPropertyValue(rPropertyName); + } + } + else if (rPropertyName == UNO_NAME_REFERENCE_ID) + { + SwFormatFootnote const*const pFormat = m_pImpl->GetFootnoteFormat(); + if (pFormat) + { + SwTextFootnote const*const pTextFootnote = pFormat->GetTextFootnote(); + OSL_ENSURE(pTextFootnote, "no TextNode?"); + aRet <<= static_cast<sal_Int16>(pTextFootnote->GetSeqRefNo()); + } + } + else + { + beans::UnknownPropertyException aExcept; + aExcept.Message = rPropertyName; + throw aExcept; + } + } + return aRet; +} + +void SAL_CALL +SwXFootnote::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXFootnote::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXFootnote::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXFootnote::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXFootnote::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXFootnote::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXFootnote::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXFootnote::removeVetoableChangeListener(): not implemented"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoidx.cxx b/sw/source/core/unocore/unoidx.cxx new file mode 100644 index 000000000..873058e98 --- /dev/null +++ b/sw/source/core/unocore/unoidx.cxx @@ -0,0 +1,3118 @@ +/* -*- 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 <memory> +#include <unoidx.hxx> +#include <unoidxcoll.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/ChapterFormat.hpp> +#include <com/sun/star/text/ReferenceFieldPart.hpp> +#include <com/sun/star/text/BibliographyDataField.hpp> +#include <com/sun/star/text/XTextDocument.hpp> + +#include <osl/mutex.hxx> +#include <cppuhelper/interfacecontainer.h> +#include <comphelper/interfacecontainer2.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <editeng/memberids.h> +#include <hints.hxx> +#include <swtypes.hxx> +#include <shellres.hxx> +#include <viewsh.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <fmtcntnt.hxx> +#include <unomap.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unosection.hxx> +#include <doctxm.hxx> +#include <txttxmrk.hxx> +#include <ndtxt.hxx> +#include <docsh.hxx> +#include <chpfld.hxx> +#include <editsh.hxx> +#include <SwStyleNameMapper.hxx> +#include <strings.hrc> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> +#include <cppuhelper/implbase.hxx> +#include <svl/itemprop.hxx> +#include <svl/listener.hxx> + +using namespace ::com::sun::star; + +/// @throws lang::IllegalArgumentException +template<typename T> +static T +lcl_AnyToType(uno::Any const& rVal) +{ + T aRet{}; + if(!(rVal >>= aRet)) + { + throw lang::IllegalArgumentException(); + } + return aRet; +} + +/// @throws lang::IllegalArgumentException +template<typename T> +static void lcl_AnyToBitMask(uno::Any const& rValue, + T & rBitMask, const T nBit) +{ + rBitMask = lcl_AnyToType<bool>(rValue) + ? (rBitMask | nBit) + : (rBitMask & ~nBit); +} + +template<typename T> +static void lcl_BitMaskToAny(uno::Any & o_rValue, + const T nBitMask, const T nBit) +{ + const bool bRet(nBitMask & nBit); + o_rValue <<= bRet; +} + +static void +lcl_ReAssignTOXType(SwDoc* pDoc, SwTOXBase& rTOXBase, const OUString& rNewName) +{ + const sal_uInt16 nUserCount = pDoc->GetTOXTypeCount( TOX_USER ); + const SwTOXType* pNewType = nullptr; + for(sal_uInt16 nUser = 0; nUser < nUserCount; nUser++) + { + const SwTOXType* pType = pDoc->GetTOXType( TOX_USER, nUser ); + if (pType->GetTypeName()==rNewName) + { + pNewType = pType; + break; + } + } + if(!pNewType) + { + SwTOXType aNewType(*pDoc, TOX_USER, rNewName); + pNewType = pDoc->InsertTOXType( aNewType ); + } + + rTOXBase.RegisterToTOXType( *const_cast<SwTOXType*>(pNewType) ); +} + +static const char cUserDefined[] = "User-Defined"; +static const char cUserSuffix[] = " (user)"; +#define USER_LEN 12 +#define USER_AND_SUFFIXLEN 19 + +static void lcl_ConvertTOUNameToProgrammaticName(OUString& rTmp) +{ + ShellResource* pShellRes = SwViewShell::GetShellRes(); + + if(rTmp==pShellRes->aTOXUserName) + { + rTmp = cUserDefined; + } + // if the version is not English but the alternative index's name is + // "User-Defined" a " (user)" is appended + else if(rTmp == cUserDefined) + { + rTmp += cUserSuffix; + } +} + +static void +lcl_ConvertTOUNameToUserName(OUString& rTmp) +{ + ShellResource* pShellRes = SwViewShell::GetShellRes(); + if (rTmp == cUserDefined) + { + rTmp = pShellRes->aTOXUserName; + } + else if (pShellRes->aTOXUserName != cUserDefined && + USER_AND_SUFFIXLEN == rTmp.getLength()) + { + //make sure that in non-English versions the " (user)" suffix is removed + if (rTmp.matchAsciiL(cUserDefined, sizeof(cUserDefined)) && + rTmp.matchAsciiL(cUserSuffix, sizeof(cUserSuffix), USER_LEN)) + { + rTmp = cUserDefined; + } + } +} + +typedef ::cppu::WeakImplHelper +< lang::XServiceInfo +, container::XIndexReplace +> SwXDocumentIndexStyleAccess_Base; + +class SwXDocumentIndex::StyleAccess_Impl + : public SwXDocumentIndexStyleAccess_Base +{ + +private: + /// can be destroyed threadsafely, so no UnoImplPtr here + ::rtl::Reference<SwXDocumentIndex> m_xParent; + + virtual ~StyleAccess_Impl() override; + +public: + explicit StyleAccess_Impl(SwXDocumentIndex& rParentIdx); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL + supportsService(const OUString& rServiceName) override; + virtual uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XElementAccess + virtual uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override; + virtual uno::Any SAL_CALL getByIndex(sal_Int32 nIndex) override; + + // XIndexReplace + virtual void SAL_CALL + replaceByIndex(sal_Int32 Index, const uno::Any& rElement) override; + +}; + +typedef ::cppu::WeakImplHelper +< lang::XServiceInfo +, container::XIndexReplace +> SwXDocumentIndexTokenAccess_Base; + +class SwXDocumentIndex::TokenAccess_Impl + : public SwXDocumentIndexTokenAccess_Base +{ + +private: + /// can be destroyed threadsafely, so no UnoImplPtr here + ::rtl::Reference<SwXDocumentIndex> m_xParent; + + virtual ~TokenAccess_Impl() override; + +public: + + explicit TokenAccess_Impl(SwXDocumentIndex& rParentIdx); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL + supportsService(const OUString& rServiceName) override; + virtual uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XElementAccess + virtual uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override; + virtual uno::Any SAL_CALL getByIndex(sal_Int32 nIndex) override; + + // XIndexReplace + virtual void SAL_CALL + replaceByIndex(sal_Int32 Index, const uno::Any& rElement) override; + +}; + +namespace { + +class SwDocIndexDescriptorProperties_Impl +{ +private: + std::unique_ptr<SwTOXBase> m_pTOXBase; + OUString m_sUserTOXTypeName; + +public: + explicit SwDocIndexDescriptorProperties_Impl(SwTOXType const*const pType); + + SwTOXBase & GetTOXBase() { return *m_pTOXBase; } + const OUString& GetTypeName() const { return m_sUserTOXTypeName; } + void SetTypeName(const OUString& rSet) { m_sUserTOXTypeName = rSet; } +}; + +} + +SwDocIndexDescriptorProperties_Impl::SwDocIndexDescriptorProperties_Impl( + SwTOXType const*const pType) +{ + SwForm aForm(pType->GetType()); + m_pTOXBase.reset(new SwTOXBase(pType, aForm, + SwTOXElement::Mark, pType->GetTypeName())); + if(pType->GetType() == TOX_CONTENT || pType->GetType() == TOX_USER) + { + m_pTOXBase->SetLevel(MAXLEVEL); + } + m_sUserTOXTypeName = pType->GetTypeName(); +} + +static sal_uInt16 +lcl_TypeToPropertyMap_Index(const TOXTypes eType) +{ + switch (eType) + { + case TOX_INDEX: return PROPERTY_MAP_INDEX_IDX; + case TOX_CONTENT: return PROPERTY_MAP_INDEX_CNTNT; + case TOX_TABLES: return PROPERTY_MAP_INDEX_TABLES; + case TOX_ILLUSTRATIONS: return PROPERTY_MAP_INDEX_ILLUSTRATIONS; + case TOX_OBJECTS: return PROPERTY_MAP_INDEX_OBJECTS; + case TOX_AUTHORITIES: return PROPERTY_MAP_BIBLIOGRAPHY; + //case TOX_USER: + default: + return PROPERTY_MAP_INDEX_USER; + } +} + +class SwXDocumentIndex::Impl final: public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + SwSectionFormat* m_pFormat; + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::cppu::OMultiTypeInterfaceContainerHelper m_Listeners; + SfxItemPropertySet const& m_rPropSet; + const TOXTypes m_eTOXType; + bool m_bIsDescriptor; + SwDoc* m_pDoc; + std::unique_ptr<SwDocIndexDescriptorProperties_Impl> m_pProps; + uno::WeakReference<container::XIndexReplace> m_wStyleAccess; + uno::WeakReference<container::XIndexReplace> m_wTokenAccess; + + Impl(SwDoc& rDoc, const TOXTypes eType, SwTOXBaseSection *const pBaseSection) + : m_pFormat(pBaseSection ? pBaseSection->GetFormat() : nullptr) + , m_Listeners(m_Mutex) + , m_rPropSet(*aSwMapProvider.GetPropertySet(lcl_TypeToPropertyMap_Index(eType))) + , m_eTOXType(eType) + , m_bIsDescriptor(nullptr == pBaseSection) + , m_pDoc(&rDoc) + , m_pProps(m_bIsDescriptor + ? new SwDocIndexDescriptorProperties_Impl(rDoc.GetTOXType(eType, 0)) + : nullptr) + { + if(m_pFormat) + StartListening(m_pFormat->GetNotifier()); + } + + void SetSectionFormat(SwSectionFormat& rFormat) + { + EndListeningAll(); + m_pFormat = &rFormat; + StartListening(rFormat.GetNotifier()); + } + + SwSectionFormat* GetSectionFormat() const { + return m_pFormat; + } + + SwTOXBase & GetTOXSectionOrThrow() const + { + SwSectionFormat *const pSectionFormat(GetSectionFormat()); + SwTOXBase *const pTOXSection( m_bIsDescriptor + ? &m_pProps->GetTOXBase() + : (pSectionFormat + ? static_cast<SwTOXBaseSection*>(pSectionFormat->GetSection()) + : nullptr)); + if (!pTOXSection) + { + throw uno::RuntimeException( + "SwXDocumentIndex: disposed or invalid", nullptr); + } + return *pTOXSection; + } + + sal_Int32 GetFormMax() const + { + SwTOXBase & rSection( GetTOXSectionOrThrow() ); + return m_bIsDescriptor + ? SwForm::GetFormMaxLevel(m_eTOXType) + : rSection.GetTOXForm().GetFormMax(); + } + virtual void Notify(const SfxHint&) override; + +}; + +void SwXDocumentIndex::Impl::Notify(const SfxHint& rHint) +{ + if(auto pLegacy = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + if(pLegacy->m_pOld && pLegacy->m_pOld->Which() == RES_REMOVE_UNO_OBJECT) + m_pFormat = nullptr; + } + else if(rHint.GetId() == SfxHintId::Dying) + m_pFormat = nullptr; + if(!m_pFormat) + { + EndListeningAll(); + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_Listeners.disposeAndClear(ev); + } +} + +SwXDocumentIndex::SwXDocumentIndex( + SwTOXBaseSection & rBaseSection, SwDoc & rDoc) + : m_pImpl( new SwXDocumentIndex::Impl( + rDoc, rBaseSection.SwTOXBase::GetType(), & rBaseSection) ) +{ +} + +SwXDocumentIndex::SwXDocumentIndex(const TOXTypes eType, SwDoc& rDoc) + : m_pImpl( new SwXDocumentIndex::Impl(rDoc, eType, nullptr) ) +{ +} + +SwXDocumentIndex::~SwXDocumentIndex() +{ +} + +uno::Reference<text::XDocumentIndex> +SwXDocumentIndex::CreateXDocumentIndex( + SwDoc & rDoc, SwTOXBaseSection * pSection, TOXTypes const eTypes) +{ + // re-use existing SwXDocumentIndex + // #i105557#: do not iterate over the registered clients: race condition + uno::Reference<text::XDocumentIndex> xIndex; + if (pSection) + { + SwSectionFormat const *const pFormat = pSection->GetFormat(); + xIndex.set(pFormat->GetXObject(), uno::UNO_QUERY); + } + if (!xIndex.is()) + { + SwXDocumentIndex *const pIndex(pSection + ? new SwXDocumentIndex(*pSection, rDoc) + : new SwXDocumentIndex(eTypes, rDoc)); + xIndex.set(pIndex); + if (pSection) + { + pSection->GetFormat()->SetXObject(xIndex); + } + // need a permanent Reference to initialize m_wThis + pIndex->m_pImpl->m_wThis = xIndex; + } + return xIndex; +} + +namespace +{ + class theSwXDocumentIndexUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXDocumentIndexUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXDocumentIndex::getUnoTunnelId() +{ + return theSwXDocumentIndexUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXDocumentIndex::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXDocumentIndex>(rId, this); +} + +OUString SAL_CALL +SwXDocumentIndex::getImplementationName() +{ + return "SwXDocumentIndex"; +} + +sal_Bool SAL_CALL +SwXDocumentIndex::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXDocumentIndex::getSupportedServiceNames() +{ + SolarMutexGuard g; + + uno::Sequence< OUString > aRet(2); + OUString* pArray = aRet.getArray(); + pArray[0] = "com.sun.star.text.BaseIndex"; + switch (m_pImpl->m_eTOXType) + { + case TOX_INDEX: + pArray[1] = "com.sun.star.text.DocumentIndex"; + break; + case TOX_CONTENT: + pArray[1] = "com.sun.star.text.ContentIndex"; + break; + case TOX_TABLES: + pArray[1] = "com.sun.star.text.TableIndex"; + break; + case TOX_ILLUSTRATIONS: + pArray[1] = "com.sun.star.text.IllustrationsIndex"; + break; + case TOX_OBJECTS: + pArray[1] = "com.sun.star.text.ObjectIndex"; + break; + case TOX_AUTHORITIES: + pArray[1] = "com.sun.star.text.Bibliography"; + break; + //case TOX_USER: + default: + pArray[1] = "com.sun.star.text.UserDefinedIndex"; + } + return aRet; +} + +OUString SAL_CALL SwXDocumentIndex::getServiceName() +{ + SolarMutexGuard g; + + SwServiceType nObjectType = SwServiceType::TypeIndex; + switch (m_pImpl->m_eTOXType) + { + case TOX_USER: nObjectType = SwServiceType::UserIndex; + break; + case TOX_CONTENT: nObjectType = SwServiceType::ContentIndex; + break; + case TOX_ILLUSTRATIONS: nObjectType = SwServiceType::IndexIllustrations; + break; + case TOX_OBJECTS: nObjectType = SwServiceType::IndexObjects; + break; + case TOX_TABLES: nObjectType = SwServiceType::IndexTables; + break; + case TOX_AUTHORITIES: nObjectType = SwServiceType::IndexBibliography; + break; + default: + break; + } + return SwXServiceProvider::GetProviderName(nObjectType); +} + +void SAL_CALL SwXDocumentIndex::update() +{ + return refresh(); // update is from deprecated XDocumentIndex +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXDocumentIndex::getPropertySetInfo() +{ + SolarMutexGuard g; + + const uno::Reference< beans::XPropertySetInfo > xRef = + m_pImpl->m_rPropSet.getPropertySetInfo(); + return xRef; +} + +void SAL_CALL +SwXDocumentIndex::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw beans::PropertyVetoException( + "Property is read-only: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + SwSectionFormat *const pSectionFormat(m_pImpl->GetSectionFormat()); + SwTOXBase & rTOXBase( m_pImpl->GetTOXSectionOrThrow() ); + + SwTOXElement nCreate = rTOXBase.GetCreateType(); + SwTOOElements nOLEOptions = rTOXBase.GetOLEOptions(); + const TOXTypes eTxBaseType = rTOXBase.GetTOXType()->GetType(); + SwTOIOptions nTOIOptions = (eTxBaseType == TOX_INDEX) + ? rTOXBase.GetOptions() : SwTOIOptions::NONE; + SwForm aForm(rTOXBase.GetTOXForm()); + bool bForm = false; + switch (pEntry->nWID) + { + case WID_IDX_TITLE: + { + OUString sNewName; + if (!(rValue >>= sNewName)) + { + throw lang::IllegalArgumentException(); + } + rTOXBase.SetTitle(sNewName); + } + break; + case WID_IDX_NAME: + { + OUString sNewName; + if (!(rValue >>= sNewName)) + { + throw lang::IllegalArgumentException(); + } + rTOXBase.SetTOXName(sNewName); + } + break; + case WID_USER_IDX_NAME: + { + OUString sNewName; + if (!(rValue >>= sNewName)) + { + throw lang::IllegalArgumentException(); + } + lcl_ConvertTOUNameToUserName(sNewName); + OSL_ENSURE(TOX_USER == eTxBaseType, + "tox type name can only be changed for user indexes"); + if (pSectionFormat) + { + if (rTOXBase.GetTOXType()->GetTypeName() != sNewName) + { + lcl_ReAssignTOXType(pSectionFormat->GetDoc(), + rTOXBase, sNewName); + } + } + else + { + m_pImpl->m_pProps->SetTypeName(sNewName); + } + } + break; + case WID_IDX_LOCALE: + { + lang::Locale aLocale; + if (!(rValue>>= aLocale)) + { + throw lang::IllegalArgumentException(); + } + rTOXBase.SetLanguage( LanguageTag::convertToLanguageType(aLocale)); + } + break; + case WID_IDX_SORT_ALGORITHM: + { + OUString sTmp; + if (!(rValue >>= sTmp)) + { + throw lang::IllegalArgumentException(); + } + rTOXBase.SetSortAlgorithm(sTmp); + } + break; + case WID_LEVEL: + { + rTOXBase.SetLevel(lcl_AnyToType<sal_Int16>(rValue)); + } + break; + case WID_TOC_BOOKMARK: + { + rTOXBase.SetBookmarkName(lcl_AnyToType<OUString>(rValue)); + nCreate = SwTOXElement::Bookmark; + rTOXBase.SetCreate(nCreate); + } + break; + case WID_INDEX_ENTRY_TYPE: + { + rTOXBase.SetEntryTypeName(lcl_AnyToType<OUString>(rValue)); + nCreate = SwTOXElement::IndexEntryType; + rTOXBase.SetCreate(nCreate); + } + break; + case WID_CREATE_FROM_MARKS: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Mark); + break; + case WID_CREATE_FROM_OUTLINE: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::OutlineLevel); + break; + case WID_TOC_PARAGRAPH_OUTLINE_LEVEL: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::ParagraphOutlineLevel); + break; + case WID_TAB_IN_TOC: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::TableInToc); + break; + case WID_TOC_NEWLINE: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Newline); + break; +// case WID_PARAGRAPH_STYLE_NAMES :OSL_FAIL("not implemented") +// break; + case WID_HIDE_TABLEADER_PAGENUMBERS: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::TableLeader); + break ; + case WID_CREATE_FROM_CHAPTER: + rTOXBase.SetFromChapter(lcl_AnyToType<bool>(rValue)); + break; + case WID_CREATE_FROM_LABELS: + rTOXBase.SetFromObjectNames(! lcl_AnyToType<bool>(rValue)); + break; + case WID_PROTECTED: + { + bool bSet = lcl_AnyToType<bool>(rValue); + rTOXBase.SetProtected(bSet); + if (pSectionFormat) + { + static_cast<SwTOXBaseSection &>(rTOXBase).SetProtect(bSet); + } + } + break; + case WID_USE_ALPHABETICAL_SEPARATORS: + lcl_AnyToBitMask(rValue, nTOIOptions, + SwTOIOptions::AlphaDelimiter); + break; + case WID_USE_KEY_AS_ENTRY: + lcl_AnyToBitMask(rValue, nTOIOptions, + SwTOIOptions::KeyAsEntry); + break; + case WID_USE_COMBINED_ENTRIES: + lcl_AnyToBitMask(rValue, nTOIOptions, + SwTOIOptions::SameEntry); + break; + case WID_IS_CASE_SENSITIVE: + lcl_AnyToBitMask(rValue, nTOIOptions, + SwTOIOptions::CaseSensitive); + break; + case WID_USE_P_P: + lcl_AnyToBitMask(rValue, nTOIOptions, SwTOIOptions::FF); + break; + case WID_USE_DASH: + lcl_AnyToBitMask(rValue, nTOIOptions, SwTOIOptions::Dash); + break; + case WID_USE_UPPER_CASE: + lcl_AnyToBitMask(rValue, nTOIOptions, + SwTOIOptions::InitialCaps); + break; + case WID_IS_COMMA_SEPARATED: + bForm = true; + aForm.SetCommaSeparated(lcl_AnyToType<bool>(rValue)); + break; + case WID_LABEL_CATEGORY: + { + // convert file-format/API/external programmatic english name + // to internal UI name before usage + rTOXBase.SetSequenceName( SwStyleNameMapper::GetSpecialExtraUIName( + lcl_AnyToType<OUString>(rValue) ) ); + } + break; + case WID_LABEL_DISPLAY_TYPE: + { + const sal_Int16 nVal = lcl_AnyToType<sal_Int16>(rValue); + sal_uInt16 nSet = CAPTION_COMPLETE; + switch (nVal) + { + case text::ReferenceFieldPart::TEXT: + nSet = CAPTION_COMPLETE; + break; + case text::ReferenceFieldPart::CATEGORY_AND_NUMBER: + nSet = CAPTION_NUMBER; + break; + case text::ReferenceFieldPart::ONLY_CAPTION: + nSet = CAPTION_TEXT; + break; + default: + throw lang::IllegalArgumentException(); + } + rTOXBase.SetCaptionDisplay(static_cast<SwCaptionDisplay>(nSet)); + } + break; + case WID_USE_LEVEL_FROM_SOURCE: + rTOXBase.SetLevelFromChapter(lcl_AnyToType<bool>(rValue)); + break; + case WID_MAIN_ENTRY_CHARACTER_STYLE_NAME: + { + OUString aString; + SwStyleNameMapper::FillUIName(lcl_AnyToType<OUString>(rValue), + aString, SwGetPoolIdFromName::ChrFmt); + rTOXBase.SetMainEntryCharStyle( aString ); + } + break; + case WID_CREATE_FROM_TABLES: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Table); + break; + case WID_CREATE_FROM_TEXT_FRAMES: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Frame); + break; + case WID_CREATE_FROM_GRAPHIC_OBJECTS: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Graphic); + break; + case WID_CREATE_FROM_EMBEDDED_OBJECTS: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Ole); + break; + case WID_CREATE_FROM_STAR_MATH: + lcl_AnyToBitMask(rValue, nOLEOptions, SwTOOElements::Math); + break; + case WID_CREATE_FROM_STAR_CHART: + lcl_AnyToBitMask(rValue, nOLEOptions, SwTOOElements::Chart); + break; + case WID_CREATE_FROM_STAR_CALC: + lcl_AnyToBitMask(rValue, nOLEOptions, SwTOOElements::Calc); + break; + case WID_CREATE_FROM_STAR_DRAW: + lcl_AnyToBitMask(rValue, nOLEOptions, + SwTOOElements::DrawImpress); + break; + case WID_CREATE_FROM_OTHER_EMBEDDED_OBJECTS: + lcl_AnyToBitMask(rValue, nOLEOptions, SwTOOElements::Other); + break; + case WID_PARA_HEAD: + { + OUString aString; + SwStyleNameMapper::FillUIName( lcl_AnyToType<OUString>(rValue), + aString, SwGetPoolIdFromName::TxtColl); + bForm = true; + // Header is on Pos 0 + aForm.SetTemplate( 0, aString ); + } + break; + case WID_IS_RELATIVE_TABSTOPS: + bForm = true; + aForm.SetRelTabPos(lcl_AnyToType<bool>(rValue)); + break; + case WID_PARA_SEP: + { + OUString aString; + bForm = true; + SwStyleNameMapper::FillUIName( lcl_AnyToType<OUString>(rValue), + aString, SwGetPoolIdFromName::TxtColl); + aForm.SetTemplate( 1, aString ); + } + break; + case WID_CREATE_FROM_PARAGRAPH_STYLES: + lcl_AnyToBitMask(rValue, nCreate, SwTOXElement::Template); + break; + + case WID_PARA_LEV1: + case WID_PARA_LEV2: + case WID_PARA_LEV3: + case WID_PARA_LEV4: + case WID_PARA_LEV5: + case WID_PARA_LEV6: + case WID_PARA_LEV7: + case WID_PARA_LEV8: + case WID_PARA_LEV9: + case WID_PARA_LEV10: + { + bForm = true; + // in sdbcx::Index Label 1 begins at Pos 2 otherwise at Pos 1 + const sal_uInt16 nLPos = rTOXBase.GetType() == TOX_INDEX ? 2 : 1; + OUString aString; + SwStyleNameMapper::FillUIName( lcl_AnyToType<OUString>(rValue), + aString, SwGetPoolIdFromName::TxtColl); + aForm.SetTemplate(nLPos + pEntry->nWID - WID_PARA_LEV1, aString ); + } + break; + default: + //this is for items only + if (WID_PRIMARY_KEY > pEntry->nWID) + { + const SwAttrSet& rSet = + SwDoc::GetTOXBaseAttrSet(rTOXBase); + SfxItemSet aAttrSet(rSet); + m_pImpl->m_rPropSet.setPropertyValue( + rPropertyName, rValue, aAttrSet); + + const SwSectionFormats& rSects = m_pImpl->m_pDoc->GetSections(); + for (size_t i = 0; i < rSects.size(); ++i) + { + const SwSectionFormat* pTmpFormat = rSects[ i ]; + if (pTmpFormat == pSectionFormat) + { + SwSectionData tmpData( + static_cast<SwTOXBaseSection&>(rTOXBase)); + m_pImpl->m_pDoc->UpdateSection(i, tmpData, & aAttrSet); + break; + } + } + } + } + rTOXBase.SetCreate(nCreate); + rTOXBase.SetOLEOptions(nOLEOptions); + if (rTOXBase.GetTOXType()->GetType() == TOX_INDEX) + { + rTOXBase.SetOptions(nTOIOptions); + } + if (bForm) + { + rTOXBase.SetTOXForm(aForm); + } +} + +uno::Any SAL_CALL +SwXDocumentIndex::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Any aRet; + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast< cppu::OWeakObject * >(this)); + } + // TODO: is this the best approach to tell API clients about the change? + if (pEntry->nWID == RES_BACKGROUND && pEntry->nMemberId == MID_GRAPHIC_URL) + { + throw uno::RuntimeException("Getting GraphicURL property is not supported"); + } + + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + SwTOXBase* pTOXBase = nullptr; + if (pSectionFormat) + { + pTOXBase = static_cast<SwTOXBaseSection*>(pSectionFormat->GetSection()); + } + else if (m_pImpl->m_bIsDescriptor) + { + pTOXBase = &m_pImpl->m_pProps->GetTOXBase(); + } + if(pTOXBase) + { + const SwTOXElement nCreate = pTOXBase->GetCreateType(); + const SwTOOElements nOLEOptions = pTOXBase->GetOLEOptions(); + const SwTOIOptions nTOIOptions = + (pTOXBase->GetTOXType()->GetType() == TOX_INDEX) + ? pTOXBase->GetOptions() + : SwTOIOptions::NONE; + const SwForm& rForm = pTOXBase->GetTOXForm(); + switch(pEntry->nWID) + { + case WID_IDX_CONTENT_SECTION: + case WID_IDX_HEADER_SECTION : + if(WID_IDX_CONTENT_SECTION == pEntry->nWID) + { + const uno::Reference <text::XTextSection> xContentSect = + SwXTextSection::CreateXTextSection( pSectionFormat ); + aRet <<= xContentSect; + } + else if (pSectionFormat) + { + SwSections aSectArr; + pSectionFormat->GetChildSections(aSectArr, + SectionSort::Not, false); + for(SwSection* pSect : aSectArr) + { + if(pSect->GetType() == SectionType::ToxHeader) + { + const uno::Reference <text::XTextSection> xHeader = + SwXTextSection::CreateXTextSection( + pSect->GetFormat() ); + aRet <<= xHeader; + break; + } + } + } + break; + case WID_IDX_TITLE : + { + aRet <<= pTOXBase->GetTitle(); + break; + } + case WID_IDX_NAME: + aRet <<= pTOXBase->GetTOXName(); + break; + case WID_USER_IDX_NAME: + { + OUString sTmp((!m_pImpl->m_bIsDescriptor) + ? pTOXBase->GetTOXType()->GetTypeName() + : m_pImpl->m_pProps->GetTypeName()); + //I18N + lcl_ConvertTOUNameToProgrammaticName(sTmp); + aRet <<= sTmp; + } + break; + case WID_IDX_LOCALE: + aRet <<= LanguageTag(pTOXBase->GetLanguage()).getLocale(); + break; + case WID_IDX_SORT_ALGORITHM: + aRet <<= pTOXBase->GetSortAlgorithm(); + break; + case WID_LEVEL : + aRet <<= static_cast<sal_Int16>(pTOXBase->GetLevel()); + break; + case WID_TOC_BOOKMARK : + aRet <<= pTOXBase->GetBookmarkName(); + break; + case WID_INDEX_ENTRY_TYPE : + aRet <<= pTOXBase->GetEntryTypeName(); + break; + case WID_CREATE_FROM_MARKS: + lcl_BitMaskToAny(aRet, nCreate, SwTOXElement::Mark); + break; + case WID_CREATE_FROM_OUTLINE: + lcl_BitMaskToAny(aRet, nCreate, + SwTOXElement::OutlineLevel); + break; + case WID_CREATE_FROM_CHAPTER: + { + const bool bRet = pTOXBase->IsFromChapter(); + aRet <<= bRet; + } + break; + case WID_CREATE_FROM_LABELS: + { + const bool bRet = ! pTOXBase->IsFromObjectNames(); + aRet <<= bRet; + } + break; + case WID_PROTECTED: + { + const bool bRet = pTOXBase->IsProtected(); + aRet <<= bRet; + } + break; + case WID_USE_ALPHABETICAL_SEPARATORS: + lcl_BitMaskToAny(aRet, nTOIOptions, + SwTOIOptions::AlphaDelimiter); + break; + case WID_USE_KEY_AS_ENTRY: + lcl_BitMaskToAny(aRet, nTOIOptions, + SwTOIOptions::KeyAsEntry); + break; + case WID_USE_COMBINED_ENTRIES: + lcl_BitMaskToAny(aRet, nTOIOptions, + SwTOIOptions::SameEntry); + break; + case WID_IS_CASE_SENSITIVE: + lcl_BitMaskToAny(aRet, nTOIOptions, + SwTOIOptions::CaseSensitive); + break; + case WID_USE_P_P: + lcl_BitMaskToAny(aRet, nTOIOptions, SwTOIOptions::FF); + break; + case WID_USE_DASH: + lcl_BitMaskToAny(aRet, nTOIOptions, SwTOIOptions::Dash); + break; + case WID_USE_UPPER_CASE: + lcl_BitMaskToAny(aRet, nTOIOptions, + SwTOIOptions::InitialCaps); + break; + case WID_IS_COMMA_SEPARATED: + { + const bool bRet = rForm.IsCommaSeparated(); + aRet <<= bRet; + } + break; + case WID_LABEL_CATEGORY: + { + // convert internal UI name to + // file-format/API/external programmatic english name + // before usage + aRet <<= SwStyleNameMapper::GetSpecialExtraProgName( + pTOXBase->GetSequenceName() ); + } + break; + case WID_LABEL_DISPLAY_TYPE: + { + sal_Int16 nSet = text::ReferenceFieldPart::TEXT; + switch (pTOXBase->GetCaptionDisplay()) + { + case CAPTION_COMPLETE: + nSet = text::ReferenceFieldPart::TEXT; + break; + case CAPTION_NUMBER: + nSet = text::ReferenceFieldPart::CATEGORY_AND_NUMBER; + break; + case CAPTION_TEXT: + nSet = text::ReferenceFieldPart::ONLY_CAPTION; + break; + } + aRet <<= nSet; + } + break; + case WID_USE_LEVEL_FROM_SOURCE: + { + const bool bRet = pTOXBase->IsLevelFromChapter(); + aRet <<= bRet; + } + break; + case WID_LEVEL_FORMAT: + { + uno::Reference< container::XIndexReplace > xTokenAccess( + m_pImpl->m_wTokenAccess); + if (!xTokenAccess.is()) + { + xTokenAccess = new TokenAccess_Impl(*this); + m_pImpl->m_wTokenAccess = xTokenAccess; + } + aRet <<= xTokenAccess; + } + break; + case WID_LEVEL_PARAGRAPH_STYLES: + { + uno::Reference< container::XIndexReplace > xStyleAccess( + m_pImpl->m_wStyleAccess); + if (!xStyleAccess.is()) + { + xStyleAccess = new StyleAccess_Impl(*this); + m_pImpl->m_wStyleAccess = xStyleAccess; + } + aRet <<= xStyleAccess; + } + break; + case WID_MAIN_ENTRY_CHARACTER_STYLE_NAME: + { + OUString aString; + SwStyleNameMapper::FillProgName( + pTOXBase->GetMainEntryCharStyle(), + aString, + SwGetPoolIdFromName::ChrFmt); + aRet <<= aString; + } + break; + case WID_CREATE_FROM_TABLES: + lcl_BitMaskToAny(aRet, nCreate, SwTOXElement::Table); + break; + case WID_CREATE_FROM_TEXT_FRAMES: + lcl_BitMaskToAny(aRet, nCreate, SwTOXElement::Frame); + break; + case WID_CREATE_FROM_GRAPHIC_OBJECTS: + lcl_BitMaskToAny(aRet, nCreate, SwTOXElement::Graphic); + break; + case WID_CREATE_FROM_EMBEDDED_OBJECTS: + lcl_BitMaskToAny(aRet, nCreate, SwTOXElement::Ole); + break; + case WID_CREATE_FROM_STAR_MATH: + lcl_BitMaskToAny(aRet, nOLEOptions, SwTOOElements::Math); + break; + case WID_CREATE_FROM_STAR_CHART: + lcl_BitMaskToAny(aRet, nOLEOptions, SwTOOElements::Chart); + break; + case WID_CREATE_FROM_STAR_CALC: + lcl_BitMaskToAny(aRet, nOLEOptions, SwTOOElements::Calc); + break; + case WID_CREATE_FROM_STAR_DRAW: + lcl_BitMaskToAny(aRet, nOLEOptions, + SwTOOElements::DrawImpress); + break; + case WID_CREATE_FROM_OTHER_EMBEDDED_OBJECTS: + lcl_BitMaskToAny(aRet, nOLEOptions, SwTOOElements::Other); + break; + case WID_CREATE_FROM_PARAGRAPH_STYLES: + lcl_BitMaskToAny(aRet, nCreate, SwTOXElement::Template); + break; + case WID_PARA_HEAD: + { + //Header is at position 0 + OUString aString; + SwStyleNameMapper::FillProgName(rForm.GetTemplate( 0 ), aString, + SwGetPoolIdFromName::TxtColl ); + aRet <<= aString; + } + break; + case WID_PARA_SEP: + { + OUString aString; + SwStyleNameMapper::FillProgName( + rForm.GetTemplate( 1 ), + aString, + SwGetPoolIdFromName::TxtColl); + aRet <<= aString; + } + break; + case WID_PARA_LEV1: + case WID_PARA_LEV2: + case WID_PARA_LEV3: + case WID_PARA_LEV4: + case WID_PARA_LEV5: + case WID_PARA_LEV6: + case WID_PARA_LEV7: + case WID_PARA_LEV8: + case WID_PARA_LEV9: + case WID_PARA_LEV10: + { + // in sdbcx::Index Label 1 begins at Pos 2 otherwise at Pos 1 + const sal_uInt16 nLPos = pTOXBase->GetType() == TOX_INDEX ? 2 : 1; + OUString aString; + SwStyleNameMapper::FillProgName( + rForm.GetTemplate(nLPos + pEntry->nWID - WID_PARA_LEV1), + aString, + SwGetPoolIdFromName::TxtColl); + aRet <<= aString; + } + break; + case WID_IS_RELATIVE_TABSTOPS: + { + const bool bRet = rForm.IsRelTabPos(); + aRet <<= bRet; + } + break; + case WID_INDEX_MARKS: + { + SwTOXMarks aMarks; + const SwTOXType* pType = pTOXBase->GetTOXType(); + SwTOXMark::InsertTOXMarks( aMarks, *pType ); + uno::Sequence< uno::Reference<text::XDocumentIndexMark> > aXMarks(aMarks.size()); + uno::Reference<text::XDocumentIndexMark>* pxMarks = aXMarks.getArray(); + for(size_t i = 0; i < aMarks.size(); ++i) + { + SwTOXMark* pMark = aMarks[i]; + pxMarks[i] = SwXDocumentIndexMark::CreateXDocumentIndexMark( + *m_pImpl->m_pDoc, pMark); + } + aRet <<= aXMarks; + } + break; + default: + //this is for items only + if(WID_PRIMARY_KEY > pEntry->nWID) + { + const SwAttrSet& rSet = + SwDoc::GetTOXBaseAttrSet(*pTOXBase); + aRet = m_pImpl->m_rPropSet.getPropertyValue( + rPropertyName, rSet); + } + } + } + return aRet; +} + +void SAL_CALL +SwXDocumentIndex::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndex::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXDocumentIndex::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndex::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXDocumentIndex::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndex::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXDocumentIndex::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndex::removeVetoableChangeListener(): not implemented"); +} + +static void lcl_CalcLayout(SwDoc *pDoc) +{ + SwViewShell *pViewShell = nullptr; + SwEditShell* pEditShell = nullptr; + if( pDoc ) + { + pViewShell = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + pEditShell = pDoc->GetEditShell(); + } + + if (pEditShell) + { + pEditShell->CalcLayout(); + } + else if (pViewShell) + { + pViewShell->CalcLayout(); + } +} + +// XRefreshable +void SAL_CALL SwXDocumentIndex::refresh() +{ + { + SolarMutexGuard g; + + SwSectionFormat *const pFormat = m_pImpl->GetSectionFormat(); + SwTOXBaseSection *const pTOXBase = pFormat ? + static_cast<SwTOXBaseSection*>(pFormat->GetSection()) : nullptr; + if (!pTOXBase) + { + throw uno::RuntimeException( + "SwXDocumentIndex::refresh: must be in attached state", + static_cast< ::cppu::OWeakObject*>(this)); + } + pTOXBase->Update(nullptr, m_pImpl->m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + + // the insertion of TOC will affect the document layout + lcl_CalcLayout(m_pImpl->m_pDoc); + + // page numbers + pTOXBase->UpdatePageNum(); + } + + ::cppu::OInterfaceContainerHelper *const pContainer( + m_pImpl->m_Listeners.getContainer( + cppu::UnoType<util::XRefreshListener>::get())); + if (pContainer) + { + lang::EventObject const event(static_cast< ::cppu::OWeakObject*>(this)); + pContainer->notifyEach(& util::XRefreshListener::refreshed, event); + } +} + +void SAL_CALL SwXDocumentIndex::addRefreshListener( + const uno::Reference<util::XRefreshListener>& xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.addInterface( + cppu::UnoType<util::XRefreshListener>::get(), xListener); +} + +void SAL_CALL SwXDocumentIndex::removeRefreshListener( + const uno::Reference<util::XRefreshListener>& xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.removeInterface( + cppu::UnoType<util::XRefreshListener>::get(), xListener); +} + +void SAL_CALL +SwXDocumentIndex::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + const uno::Reference<XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + SwXTextRange *const pRange = + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + + SwDoc *const pDoc = + pRange ? &pRange->GetDoc() : (pCursor ? pCursor->GetDoc() : nullptr); + if (!pDoc) + { + throw lang::IllegalArgumentException(); + } + + SwUnoInternalPaM aPam(*pDoc); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + + const SwTOXBase* pOld = SwDoc::GetCurTOX( *aPam.Start() ); + if (pOld) + { + throw lang::IllegalArgumentException(); + } + + UnoActionContext aAction(pDoc); + + SwTOXBase & rTOXBase = m_pImpl->m_pProps->GetTOXBase(); + SwTOXType const*const pTOXType = rTOXBase.GetTOXType(); + if ((TOX_USER == pTOXType->GetType()) && + m_pImpl->m_pProps->GetTypeName() != pTOXType->GetTypeName()) + { + lcl_ReAssignTOXType(pDoc, rTOXBase, m_pImpl->m_pProps->GetTypeName()); + } + //TODO: apply Section attributes (columns and background) + SwTOXBaseSection *const pTOX = + pDoc->InsertTableOf( aPam, rTOXBase, nullptr, false, + m_pImpl->m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + + pDoc->SetTOXBaseName(*pTOX, m_pImpl->m_pProps->GetTOXBase().GetTOXName()); + + // update page numbers + m_pImpl->SetSectionFormat(*pTOX->GetFormat()); + pTOX->GetFormat()->SetXObject(static_cast< ::cppu::OWeakObject*>(this)); + pTOX->UpdatePageNum(); + + m_pImpl->m_pProps.reset(); + m_pImpl->m_pDoc = pDoc; + m_pImpl->m_bIsDescriptor = false; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXDocumentIndex::getAnchor() +{ + SolarMutexGuard aGuard; + + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + if (!pSectionFormat) + { + throw uno::RuntimeException(); + } + + uno::Reference< text::XTextRange > xRet; + SwNodeIndex const*const pIdx( pSectionFormat->GetContent().GetContentIdx() ); + if (pIdx && pIdx->GetNode().GetNodes().IsDocNodes()) + { + SwPaM aPaM(*pIdx); + aPaM.Move( fnMoveForward, GoInContent ); + aPaM.SetMark(); + aPaM.GetPoint()->nNode = *pIdx->GetNode().EndOfSectionNode(); + aPaM.Move( fnMoveBackward, GoInContent ); + xRet = SwXTextRange::CreateXTextRange(*pSectionFormat->GetDoc(), + *aPaM.GetMark(), aPaM.GetPoint()); + } + return xRet; +} + +void SAL_CALL SwXDocumentIndex::dispose() +{ + SolarMutexGuard aGuard; + + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + if (pSectionFormat) + { + pSectionFormat->GetDoc()->DeleteTOX( + *static_cast<SwTOXBaseSection*>(pSectionFormat->GetSection()), + true); + } +} + +void SAL_CALL +SwXDocumentIndex::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.addInterface( + cppu::UnoType<lang::XEventListener>::get(), xListener); +} + +void SAL_CALL +SwXDocumentIndex::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.removeInterface( + cppu::UnoType<lang::XEventListener>::get(), xListener); +} + +OUString SAL_CALL SwXDocumentIndex::getName() +{ + SolarMutexGuard g; + + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + if (m_pImpl->m_bIsDescriptor) + { + return m_pImpl->m_pProps->GetTOXBase().GetTOXName(); + } + + if(!pSectionFormat) + { + throw uno::RuntimeException(); + } + + return pSectionFormat->GetSection()->GetSectionName(); +} + +void SAL_CALL +SwXDocumentIndex::setName(const OUString& rName) +{ + SolarMutexGuard g; + + if (rName.isEmpty()) + { + throw uno::RuntimeException(); + } + + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->GetTOXBase().SetTOXName(rName); + } + else if (pSectionFormat) + { + const bool bSuccess = pSectionFormat->GetDoc()->SetTOXBaseName( + *static_cast<SwTOXBaseSection*>(pSectionFormat->GetSection()), rName); + if (!bSuccess) + { + throw uno::RuntimeException(); + } + } + else + { + throw uno::RuntimeException(); + } +} + +// MetadatableMixin +::sfx2::Metadatable* SwXDocumentIndex::GetCoreObject() +{ + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + return pSectionFormat; +} + +uno::Reference<frame::XModel> SwXDocumentIndex::GetModel() +{ + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + if (pSectionFormat) + { + SwDocShell const*const pShell( pSectionFormat->GetDoc()->GetDocShell() ); + return pShell ? pShell->GetModel() : nullptr; + } + return nullptr; +} + +static sal_uInt16 +lcl_TypeToPropertyMap_Mark(const TOXTypes eType) +{ + switch (eType) + { + case TOX_INDEX: return PROPERTY_MAP_INDEX_MARK; + case TOX_CONTENT: return PROPERTY_MAP_CNTIDX_MARK; + case TOX_CITATION : return PROPERTY_MAP_FLDTYP_BIBLIOGRAPHY; + //case TOX_USER: + default: + return PROPERTY_MAP_USER_MARK; + } +} + +class SwXDocumentIndexMark::Impl final: public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + SwXDocumentIndexMark & m_rThis; + bool m_bInReplaceMark; + +public: + + uno::WeakReference<uno::XInterface> m_wThis; + SfxItemPropertySet const& m_rPropSet; + const TOXTypes m_eTOXType; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + bool m_bIsDescriptor; + const SwTOXType* m_pTOXType; + const SwTOXMark* m_pTOXMark; + SwDoc* m_pDoc; + + bool m_bMainEntry; + sal_uInt16 m_nLevel; + OUString m_aBookmarkName; + OUString m_aEntryTypeName; + OUString m_sAltText; + OUString m_sPrimaryKey; + OUString m_sSecondaryKey; + OUString m_sTextReading; + OUString m_sPrimaryKeyReading; + OUString m_sSecondaryKeyReading; + OUString m_sUserIndexName; + + Impl(SwXDocumentIndexMark& rThis, + SwDoc* const pDoc, + const enum TOXTypes eType, + const SwTOXType* pType, + SwTOXMark const* pMark) + : m_rThis(rThis) + , m_bInReplaceMark(false) + , m_rPropSet( + *aSwMapProvider.GetPropertySet(lcl_TypeToPropertyMap_Mark(eType))) + , m_eTOXType(eType) + , m_EventListeners(m_Mutex) + , m_bIsDescriptor(nullptr == pMark) + , m_pTOXType(pType) + , m_pTOXMark(pMark) + , m_pDoc(pDoc) + , m_bMainEntry(false) + , m_nLevel(0) + { + auto pMarkNonConst = const_cast<SwTOXMark*>(m_pTOXMark); + auto pTypeNonConst = const_cast<SwTOXType*>(m_pTOXType); + + if(pMarkNonConst) + StartListening(pMarkNonConst->GetNotifier()); + if(pTypeNonConst) + StartListening(pTypeNonConst->GetNotifier()); + } + + SwTOXType* GetTOXType() const { + return const_cast<SwTOXType*>(m_pTOXType); + } + + void DeleteTOXMark() + { + m_pDoc->DeleteTOXMark(m_pTOXMark); + Invalidate(); + } + + void InsertTOXMark(SwTOXType & rTOXType, SwTOXMark & rMark, SwPaM & rPam, + SwXTextCursor const*const pTextCursor); + + void ReplaceTOXMark(SwTOXType & rTOXType, SwTOXMark & rMark, SwPaM & rPam) + { + m_bInReplaceMark = true; + DeleteTOXMark(); + m_bInReplaceMark = false; + try { + InsertTOXMark(rTOXType, rMark, rPam, nullptr); + } catch (...) { + OSL_FAIL("ReplaceTOXMark() failed!"); + lang::EventObject const ev( + static_cast< ::cppu::OWeakObject&>(m_rThis)); + m_EventListeners.disposeAndClear(ev); + throw; + } + } + + void Invalidate(); + virtual void Notify(const SfxHint&) override; +}; + +void SwXDocumentIndexMark::Impl::Invalidate() +{ + if (!m_bInReplaceMark) // #i109983# only dispose on delete, not on replace! + { + uno::Reference<uno::XInterface> const xThis(m_wThis); + // fdo#72695: if UNO object is already dead, don't revive it with event + if (xThis.is()) + { + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); + } + } + EndListeningAll(); + m_pDoc = nullptr; + m_pTOXMark = nullptr; + m_pTOXType = nullptr; +} + +void SwXDocumentIndexMark::Impl::Notify(const SfxHint& rHint) +{ + if(auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + if(auto pNewType = dynamic_cast<const SwTOXType*>(pModifyChangedHint->m_pNew)) + m_pTOXType = pNewType; + + else + Invalidate(); + } +} + +SwXDocumentIndexMark::SwXDocumentIndexMark(const TOXTypes eToxType) + : m_pImpl( new SwXDocumentIndexMark::Impl(*this, nullptr, eToxType, nullptr, nullptr) ) +{ +} + +SwXDocumentIndexMark::SwXDocumentIndexMark(SwDoc & rDoc, + SwTOXType & rType, SwTOXMark & rMark) + : m_pImpl( new SwXDocumentIndexMark::Impl(*this, &rDoc, rType.GetType(), + &rType, &rMark) ) +{ +} + +SwXDocumentIndexMark::~SwXDocumentIndexMark() +{ +} + +uno::Reference<text::XDocumentIndexMark> +SwXDocumentIndexMark::CreateXDocumentIndexMark( + SwDoc & rDoc, SwTOXMark *const pMark, TOXTypes const eType) +{ + // re-use existing SwXDocumentIndexMark + // NB: xmloff depends on this caching to generate ID from the address! + // #i105557#: do not iterate over the registered clients: race condition + uno::Reference<text::XDocumentIndexMark> xTOXMark; + if (pMark) + { + xTOXMark = pMark->GetXTOXMark(); + } + if (!xTOXMark.is()) + { + SwXDocumentIndexMark *const pNew(pMark + ? new SwXDocumentIndexMark(rDoc, + *const_cast<SwTOXType*>(pMark->GetTOXType()), *pMark) + : new SwXDocumentIndexMark(eType)); + xTOXMark.set(pNew); + if (pMark) + { + pMark->SetXTOXMark(xTOXMark); + } + // need a permanent Reference to initialize m_wThis + pNew->m_pImpl->m_wThis = xTOXMark; + } + return xTOXMark; +} + +namespace +{ + class theSwXDocumentIndexMarkUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXDocumentIndexMarkUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXDocumentIndexMark::getUnoTunnelId() +{ + return theSwXDocumentIndexMarkUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXDocumentIndexMark::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXDocumentIndexMark>(rId, this); +} + +OUString SAL_CALL +SwXDocumentIndexMark::getImplementationName() +{ + return "SwXDocumentIndexMark"; +} + +sal_Bool SAL_CALL SwXDocumentIndexMark::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXDocumentIndexMark::getSupportedServiceNames() +{ + SolarMutexGuard g; + + const sal_Int32 nCnt = (m_pImpl->m_eTOXType == TOX_INDEX) ? 4 : 3; + uno::Sequence< OUString > aRet(nCnt); + OUString* pArray = aRet.getArray(); + pArray[0] = "com.sun.star.text.BaseIndexMark"; + pArray[1] = "com.sun.star.text.TextContent"; + switch (m_pImpl->m_eTOXType) + { + case TOX_USER: + pArray[2] = "com.sun.star.text.UserIndexMark"; + break; + case TOX_CONTENT: + pArray[2] = "com.sun.star.text.ContentIndexMark"; + break; + case TOX_INDEX: + pArray[2] = "com.sun.star.text.DocumentIndexMark"; + pArray[3] = "com.sun.star.text.DocumentIndexMarkAsian"; + break; + + default: + ; + } + return aRet; +} + +OUString SAL_CALL +SwXDocumentIndexMark::getMarkEntry() +{ + SolarMutexGuard aGuard; + + SwTOXType *const pType = m_pImpl->GetTOXType(); + if (pType && m_pImpl->m_pTOXMark) + { + return m_pImpl->m_pTOXMark->GetAlternativeText(); + } + + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + + return m_pImpl->m_sAltText; +} + +void SAL_CALL +SwXDocumentIndexMark::setMarkEntry(const OUString& rIndexEntry) +{ + SolarMutexGuard aGuard; + + SwTOXType *const pType = m_pImpl->GetTOXType(); + if (pType && m_pImpl->m_pTOXMark) + { + SwTOXMark aMark(*m_pImpl->m_pTOXMark); + aMark.SetAlternativeText(rIndexEntry); + SwTextTOXMark const*const pTextMark = + m_pImpl->m_pTOXMark->GetTextTOXMark(); + SwPaM aPam(pTextMark->GetTextNode(), pTextMark->GetStart()); + aPam.SetMark(); + if(pTextMark->End()) + { + aPam.GetPoint()->nContent = *pTextMark->End(); + } + else + ++aPam.GetPoint()->nContent; + + m_pImpl->ReplaceTOXMark(*pType, aMark, aPam); + } + else if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_sAltText = rIndexEntry; + } + else + { + throw uno::RuntimeException(); + } +} + +void SAL_CALL +SwXDocumentIndexMark::attach( + const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + + const uno::Reference<XUnoTunnel> xRangeTunnel(xTextRange, uno::UNO_QUERY); + SwXTextRange *const pRange = + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + SwDoc *const pDoc = + pRange ? &pRange->GetDoc() : (pCursor ? pCursor->GetDoc() : nullptr); + if (!pDoc) + { + throw lang::IllegalArgumentException(); + } + + const SwTOXType* pTOXType = nullptr; + switch (m_pImpl->m_eTOXType) + { + case TOX_INDEX: + case TOX_CONTENT: + case TOX_CITATION: + pTOXType = pDoc->GetTOXType( m_pImpl->m_eTOXType, 0 ); + break; + case TOX_USER: + { + if (m_pImpl->m_sUserIndexName.isEmpty()) + { + pTOXType = pDoc->GetTOXType( m_pImpl->m_eTOXType, 0 ); + } + else + { + const sal_uInt16 nCount = + pDoc->GetTOXTypeCount(m_pImpl->m_eTOXType); + for (sal_uInt16 i = 0; i < nCount; i++) + { + SwTOXType const*const pTemp = + pDoc->GetTOXType( m_pImpl->m_eTOXType, i ); + if (m_pImpl->m_sUserIndexName == pTemp->GetTypeName()) + { + pTOXType = pTemp; + break; + } + } + if (!pTOXType) + { + SwTOXType aUserType(*pDoc, TOX_USER, m_pImpl->m_sUserIndexName); + pTOXType = pDoc->InsertTOXType(aUserType); + } + } + } + break; + + default: + break; + } + if (!pTOXType) + { + throw lang::IllegalArgumentException(); + } + + SwUnoInternalPaM aPam(*pDoc); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + SwTOXMark aMark (pTOXType); + if (!m_pImpl->m_sAltText.isEmpty()) + { + aMark.SetAlternativeText(m_pImpl->m_sAltText); + } + switch (m_pImpl->m_eTOXType) + { + case TOX_INDEX: + if (!m_pImpl->m_sPrimaryKey.isEmpty()) + { + aMark.SetPrimaryKey(m_pImpl->m_sPrimaryKey); + } + if (!m_pImpl->m_sSecondaryKey.isEmpty()) + { + aMark.SetSecondaryKey(m_pImpl->m_sSecondaryKey); + } + if (!m_pImpl->m_sTextReading.isEmpty()) + { + aMark.SetTextReading(m_pImpl->m_sTextReading); + } + if (!m_pImpl->m_sPrimaryKeyReading.isEmpty()) + { + aMark.SetPrimaryKeyReading(m_pImpl->m_sPrimaryKeyReading); + } + if (!m_pImpl->m_sSecondaryKeyReading.isEmpty()) + { + aMark.SetSecondaryKeyReading(m_pImpl->m_sSecondaryKeyReading); + } + aMark.SetMainEntry(m_pImpl->m_bMainEntry); + break; + case TOX_CITATION: + aMark.SetMainEntry(m_pImpl->m_bMainEntry); + break; + case TOX_USER: + case TOX_CONTENT: + if (USHRT_MAX != m_pImpl->m_nLevel) + { + aMark.SetLevel(m_pImpl->m_nLevel+1); + } + break; + + default: + break; + } + + m_pImpl->InsertTOXMark(*const_cast<SwTOXType *>(pTOXType), aMark, aPam, + dynamic_cast<SwXTextCursor const*>(pCursor)); + + m_pImpl->m_bIsDescriptor = false; +} + +namespace { + +template<typename T> struct NotContainedIn +{ + std::vector<T> const& m_rVector; + explicit NotContainedIn(std::vector<T> const& rVector) + : m_rVector(rVector) { } + bool operator() (T const& rT) { + return std::find(m_rVector.begin(), m_rVector.end(), rT) + == m_rVector.end(); + } +}; + +} + +void SwXDocumentIndexMark::Impl::InsertTOXMark( + SwTOXType & rTOXType, SwTOXMark & rMark, SwPaM & rPam, + SwXTextCursor const*const pTextCursor) +{ + SwDoc *const pDoc( rPam.GetDoc() ); + UnoActionContext aAction(pDoc); + bool bMark = *rPam.GetPoint() != *rPam.GetMark(); + // n.b.: toxmarks must have either alternative text or an extent + if (bMark && !rMark.GetAlternativeText().isEmpty()) + { + rPam.Normalize(); + rPam.DeleteMark(); + bMark = false; + } + // Marks without alternative text and without selected text cannot be inserted, + // thus use a space - is this really the ideal solution? + if (!bMark && rMark.GetAlternativeText().isEmpty()) + { + rMark.SetAlternativeText( " " ); + } + + const bool bForceExpandHints( !bMark && pTextCursor && pTextCursor->IsAtEndOfMeta() ); + const SetAttrMode nInsertFlags = bForceExpandHints + ? ( SetAttrMode::FORCEHINTEXPAND + | SetAttrMode::DONTEXPAND) + : SetAttrMode::DONTEXPAND; + + // rMark gets copied into the document pool; + // pNewTextAttr comes back with the real format + SwTextAttr *pNewTextAttr = nullptr; + pDoc->getIDocumentContentOperations().InsertPoolItem(rPam, rMark, nInsertFlags, + /*pLayout*/nullptr, /*bExpandCharToPara*/false, &pNewTextAttr); + if (bMark && *rPam.GetPoint() > *rPam.GetMark()) + { + rPam.Exchange(); + } + + if (!pNewTextAttr) + { + throw uno::RuntimeException( + "SwXDocumentIndexMark::InsertTOXMark(): cannot insert attribute", + nullptr); + } + + m_pDoc = pDoc; + m_pTOXMark = &pNewTextAttr->GetTOXMark(); + m_pTOXType = &rTOXType; + EndListeningAll(); + StartListening(const_cast<SwTOXMark*>(m_pTOXMark)->GetNotifier()); + StartListening(const_cast<SwTOXType*>(m_pTOXType)->GetNotifier()); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXDocumentIndexMark::getAnchor() +{ + SolarMutexGuard aGuard; + + SwTOXType *const pType = m_pImpl->GetTOXType(); + if (!pType || !m_pImpl->m_pTOXMark) + { + throw uno::RuntimeException(); + } + if (!m_pImpl->m_pTOXMark->GetTextTOXMark()) + { + throw uno::RuntimeException(); + } + const SwTextTOXMark* pTextMark = m_pImpl->m_pTOXMark->GetTextTOXMark(); + SwPaM aPam(pTextMark->GetTextNode(), pTextMark->GetStart()); + aPam.SetMark(); + if(pTextMark->End()) + { + aPam.GetPoint()->nContent = *pTextMark->End(); + } + else + { + ++aPam.GetPoint()->nContent; + } + const uno::Reference< frame::XModel > xModel = + m_pImpl->m_pDoc->GetDocShell()->GetBaseModel(); + const uno::Reference< text::XTextDocument > xTDoc(xModel, uno::UNO_QUERY); + const uno::Reference< text::XTextRange > xRet = + new SwXTextRange(aPam, xTDoc->getText()); + + return xRet; +} + +void SAL_CALL +SwXDocumentIndexMark::dispose() +{ + SolarMutexGuard aGuard; + + SwTOXType *const pType = m_pImpl->GetTOXType(); + if (pType && m_pImpl->m_pTOXMark) + { + m_pImpl->DeleteTOXMark(); // call Invalidate() via modify! + } +} + +void SAL_CALL +SwXDocumentIndexMark::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL +SwXDocumentIndexMark::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXDocumentIndexMark::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xInfos[3]; + int nPos = 0; + switch (m_pImpl->m_eTOXType) + { + case TOX_INDEX: nPos = 0; break; + case TOX_CONTENT: nPos = 1; break; + case TOX_USER: nPos = 2; break; + default: + ; + } + if(!xInfos[nPos].is()) + { + const uno::Reference< beans::XPropertySetInfo > xInfo = + m_pImpl->m_rPropSet.getPropertySetInfo(); + // extend PropertySetInfo! + const uno::Sequence<beans::Property> aPropSeq = xInfo->getProperties(); + xInfos[nPos] = new SfxExtItemPropertySetInfo( + aSwMapProvider.GetPropertyMapEntries( + PROPERTY_MAP_PARAGRAPH_EXTENSIONS), + aPropSeq ); + } + return xInfos[nPos]; +} + +void SAL_CALL +SwXDocumentIndexMark::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw beans::PropertyVetoException( + "Property is read-only: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + SwTOXType *const pType = m_pImpl->GetTOXType(); + if (pType && m_pImpl->m_pTOXMark) + { + SwTOXMark aMark(*m_pImpl->m_pTOXMark); + switch(pEntry->nWID) + { + case WID_ALT_TEXT: + aMark.SetAlternativeText(lcl_AnyToType<OUString>(rValue)); + break; + case WID_LEVEL: + aMark.SetLevel(std::min( static_cast<sal_Int8>( MAXLEVEL ), + static_cast<sal_Int8>(lcl_AnyToType<sal_Int16>(rValue)+1))); + break; + case WID_TOC_BOOKMARK : + aMark.SetBookmarkName(lcl_AnyToType<OUString>(rValue)); + break; + case WID_INDEX_ENTRY_TYPE : + aMark.SetEntryTypeName(lcl_AnyToType<OUString>(rValue)); + break; + case WID_PRIMARY_KEY : + aMark.SetPrimaryKey(lcl_AnyToType<OUString>(rValue)); + break; + case WID_SECONDARY_KEY: + aMark.SetSecondaryKey(lcl_AnyToType<OUString>(rValue)); + break; + case WID_MAIN_ENTRY: + aMark.SetMainEntry(lcl_AnyToType<bool>(rValue)); + break; + case WID_TEXT_READING: + aMark.SetTextReading(lcl_AnyToType<OUString>(rValue)); + break; + case WID_PRIMARY_KEY_READING: + aMark.SetPrimaryKeyReading(lcl_AnyToType<OUString>(rValue)); + break; + case WID_SECONDARY_KEY_READING: + aMark.SetSecondaryKeyReading(lcl_AnyToType<OUString>(rValue)); + break; + } + SwTextTOXMark const*const pTextMark = + m_pImpl->m_pTOXMark->GetTextTOXMark(); + SwPaM aPam(pTextMark->GetTextNode(), pTextMark->GetStart()); + aPam.SetMark(); + if(pTextMark->End()) + { + aPam.GetPoint()->nContent = *pTextMark->End(); + } + else + { + ++aPam.GetPoint()->nContent; + } + + m_pImpl->ReplaceTOXMark(*pType, aMark, aPam); + } + else if (m_pImpl->m_bIsDescriptor) + { + switch(pEntry->nWID) + { + case WID_ALT_TEXT: + m_pImpl->m_sAltText = lcl_AnyToType<OUString>(rValue); + break; + case WID_LEVEL: + { + const sal_Int16 nVal = lcl_AnyToType<sal_Int16>(rValue); + if(nVal < 0 || nVal >= MAXLEVEL) + { + throw lang::IllegalArgumentException(); + } + m_pImpl->m_nLevel = nVal; + } + break; + case WID_TOC_BOOKMARK : + { + m_pImpl->m_aBookmarkName = lcl_AnyToType<OUString>(rValue); + } + break; + case WID_INDEX_ENTRY_TYPE : + { + m_pImpl->m_aEntryTypeName = lcl_AnyToType<OUString>(rValue); + } + break; + case WID_PRIMARY_KEY: + m_pImpl->m_sPrimaryKey = lcl_AnyToType<OUString>(rValue); + break; + case WID_SECONDARY_KEY: + m_pImpl->m_sSecondaryKey = lcl_AnyToType<OUString>(rValue); + break; + case WID_TEXT_READING: + m_pImpl->m_sTextReading = lcl_AnyToType<OUString>(rValue); + break; + case WID_PRIMARY_KEY_READING: + m_pImpl->m_sPrimaryKeyReading = lcl_AnyToType<OUString>(rValue); + break; + case WID_SECONDARY_KEY_READING: + m_pImpl->m_sSecondaryKeyReading = lcl_AnyToType<OUString>(rValue); + break; + case WID_USER_IDX_NAME: + { + OUString sTmp(lcl_AnyToType<OUString>(rValue)); + lcl_ConvertTOUNameToUserName(sTmp); + m_pImpl->m_sUserIndexName = sTmp; + } + break; + case WID_MAIN_ENTRY: + m_pImpl->m_bMainEntry = lcl_AnyToType<bool>(rValue); + break; + case PROPERTY_MAP_INDEX_OBJECTS: + // unsupported + break; + } + } + else + { + throw uno::RuntimeException(); + } +} + +uno::Any SAL_CALL +SwXDocumentIndexMark::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Any aRet; + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + if (::sw::GetDefaultTextContentValue(aRet, rPropertyName, pEntry->nWID)) + { + return aRet; + } + + SwTOXType *const pType = m_pImpl->GetTOXType(); + if (pType && m_pImpl->m_pTOXMark) + { + switch(pEntry->nWID) + { + case WID_ALT_TEXT: + aRet <<= m_pImpl->m_pTOXMark->GetAlternativeText(); + break; + case WID_LEVEL: + aRet <<= static_cast<sal_Int16>( + m_pImpl->m_pTOXMark->GetLevel() - 1); + break; + case WID_TOC_BOOKMARK : + aRet <<= m_pImpl->m_pTOXMark->GetBookmarkName(); + break; + case WID_INDEX_ENTRY_TYPE : + aRet <<= m_pImpl->m_pTOXMark->GetEntryTypeName(); + break; + case WID_PRIMARY_KEY : + aRet <<= m_pImpl->m_pTOXMark->GetPrimaryKey(); + break; + case WID_SECONDARY_KEY: + aRet <<= m_pImpl->m_pTOXMark->GetSecondaryKey(); + break; + case WID_TEXT_READING: + aRet <<= m_pImpl->m_pTOXMark->GetTextReading(); + break; + case WID_PRIMARY_KEY_READING: + aRet <<= m_pImpl->m_pTOXMark->GetPrimaryKeyReading(); + break; + case WID_SECONDARY_KEY_READING: + aRet <<= m_pImpl->m_pTOXMark->GetSecondaryKeyReading(); + break; + case WID_USER_IDX_NAME : + { + OUString sTmp(pType->GetTypeName()); + lcl_ConvertTOUNameToProgrammaticName(sTmp); + aRet <<= sTmp; + } + break; + case WID_MAIN_ENTRY: + { + const bool bTemp = m_pImpl->m_pTOXMark->IsMainEntry(); + aRet <<= bTemp; + } + break; + } + } + else if (m_pImpl->m_bIsDescriptor) + { + switch(pEntry->nWID) + { + case WID_ALT_TEXT: + aRet <<= m_pImpl->m_sAltText; + break; + case WID_LEVEL: + aRet <<= static_cast<sal_Int16>(m_pImpl->m_nLevel); + break; + case WID_TOC_BOOKMARK : + aRet <<= m_pImpl->m_aBookmarkName; + break; + case WID_INDEX_ENTRY_TYPE : + aRet <<= m_pImpl->m_aEntryTypeName; + break; + case WID_PRIMARY_KEY: + aRet <<= m_pImpl->m_sPrimaryKey; + break; + case WID_SECONDARY_KEY: + aRet <<= m_pImpl->m_sSecondaryKey; + break; + case WID_TEXT_READING: + aRet <<= m_pImpl->m_sTextReading; + break; + case WID_PRIMARY_KEY_READING: + aRet <<= m_pImpl->m_sPrimaryKeyReading; + break; + case WID_SECONDARY_KEY_READING: + aRet <<= m_pImpl->m_sSecondaryKeyReading; + break; + case WID_USER_IDX_NAME : + aRet <<= m_pImpl->m_sUserIndexName; + break; + case WID_MAIN_ENTRY: + aRet <<= m_pImpl->m_bMainEntry; + break; + } + } + else + { + throw uno::RuntimeException(); + } + return aRet; +} + +void SAL_CALL +SwXDocumentIndexMark::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndexMark::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXDocumentIndexMark::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndexMark::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXDocumentIndexMark::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndexMark::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXDocumentIndexMark::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXDocumentIndexMark::removeVetoableChangeListener(): not implemented"); +} + +SwXDocumentIndexes::SwXDocumentIndexes(SwDoc *const _pDoc) + : SwUnoCollection(_pDoc) +{ +} + +SwXDocumentIndexes::~SwXDocumentIndexes() +{ +} + +OUString SAL_CALL +SwXDocumentIndexes::getImplementationName() +{ + return "SwXDocumentIndexes"; +} + +sal_Bool SAL_CALL SwXDocumentIndexes::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXDocumentIndexes::getSupportedServiceNames() +{ + return { "com.sun.star.text.DocumentIndexes" }; +} + +sal_Int32 SAL_CALL +SwXDocumentIndexes::getCount() +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + throw uno::RuntimeException(); + + sal_uInt32 nRet = 0; + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + for( size_t n = 0; n < rFormats.size(); ++n ) + { + const SwSection* pSect = rFormats[ n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() ) + { + ++nRet; + } + } + return nRet; +} + +uno::Any SAL_CALL +SwXDocumentIndexes::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + throw uno::RuntimeException(); + + sal_Int32 nIdx = 0; + + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + for( size_t n = 0; n < rFormats.size(); ++n ) + { + SwSection* pSect = rFormats[ n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() && + nIdx++ == nIndex ) + { + const uno::Reference< text::XDocumentIndex > xTmp = + SwXDocumentIndex::CreateXDocumentIndex( + *GetDoc(), static_cast<SwTOXBaseSection *>(pSect)); + uno::Any aRet; + aRet <<= xTmp; + return aRet; + } + } + + throw lang::IndexOutOfBoundsException(); +} + +uno::Any SAL_CALL +SwXDocumentIndexes::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + throw uno::RuntimeException(); + + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + for( size_t n = 0; n < rFormats.size(); ++n ) + { + SwSection* pSect = rFormats[ n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() && + (static_cast<SwTOXBaseSection const*>(pSect)->GetTOXName() + == rName)) + { + const uno::Reference< text::XDocumentIndex > xTmp = + SwXDocumentIndex::CreateXDocumentIndex( + *GetDoc(), static_cast<SwTOXBaseSection *>(pSect)); + uno::Any aRet; + aRet <<= xTmp; + return aRet; + } + } + throw container::NoSuchElementException(); +} + +uno::Sequence< OUString > SAL_CALL +SwXDocumentIndexes::getElementNames() +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + throw uno::RuntimeException(); + + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + sal_Int32 nCount = 0; + for( size_t n = 0; n < rFormats.size(); ++n ) + { + SwSection const*const pSect = rFormats[ n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() ) + { + ++nCount; + } + } + + uno::Sequence< OUString > aRet(nCount); + OUString* pArray = aRet.getArray(); + sal_Int32 nCnt = 0; + for( size_t n = 0; n < rFormats.size(); ++n ) + { + SwSection const*const pSect = rFormats[ n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode()) + { + pArray[nCnt++] = static_cast<SwTOXBaseSection const*>(pSect)->GetTOXName(); + } + } + return aRet; +} + +sal_Bool SAL_CALL +SwXDocumentIndexes::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + throw uno::RuntimeException(); + + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + for( size_t n = 0; n < rFormats.size(); ++n ) + { + SwSection const*const pSect = rFormats[ n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode()) + { + if (static_cast<SwTOXBaseSection const*>(pSect)->GetTOXName() + == rName) + { + return true; + } + } + } + return false; +} + +uno::Type SAL_CALL +SwXDocumentIndexes::getElementType() +{ + return cppu::UnoType<text::XDocumentIndex>::get(); +} + +sal_Bool SAL_CALL +SwXDocumentIndexes::hasElements() +{ + return 0 != getCount(); +} + +SwXDocumentIndex::StyleAccess_Impl::StyleAccess_Impl( + SwXDocumentIndex& rParentIdx) + : m_xParent(&rParentIdx) +{ +} + +SwXDocumentIndex::StyleAccess_Impl::~StyleAccess_Impl() +{ +} + +OUString SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::getImplementationName() +{ + return "SwXDocumentIndex::StyleAccess_Impl"; +} + +sal_Bool SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::getSupportedServiceNames() +{ + return { "com.sun.star.text.DocumentIndexParagraphStyles" }; +} + +void SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::replaceByIndex( + sal_Int32 nIndex, const uno::Any& rElement) +{ + SolarMutexGuard aGuard; + + if(nIndex < 0 || nIndex >= MAXLEVEL) + { + throw lang::IndexOutOfBoundsException(); + } + + SwTOXBase & rTOXBase( m_xParent->m_pImpl->GetTOXSectionOrThrow() ); + + uno::Sequence<OUString> aSeq; + if(!(rElement >>= aSeq)) + { + throw lang::IllegalArgumentException(); + } + + const sal_Int32 nStyles = aSeq.getLength(); + const OUString* pStyles = aSeq.getConstArray(); + OUStringBuffer sSetStyles; + OUString aString; + for(sal_Int32 i = 0; i < nStyles; i++) + { + if(i) + { + sSetStyles.append(TOX_STYLE_DELIMITER); + } + SwStyleNameMapper::FillUIName(pStyles[i], aString, + SwGetPoolIdFromName::TxtColl); + sSetStyles.append(aString); + } + rTOXBase.SetStyleNames(sSetStyles.makeStringAndClear(), static_cast<sal_uInt16>(nIndex)); +} + +sal_Int32 SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::getCount() +{ + return MAXLEVEL; +} + +uno::Any SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + + if(nIndex < 0 || nIndex >= MAXLEVEL) + { + throw lang::IndexOutOfBoundsException(); + } + + SwTOXBase & rTOXBase( m_xParent->m_pImpl->GetTOXSectionOrThrow() ); + + const OUString& rStyles = + rTOXBase.GetStyleNames(static_cast<sal_uInt16>(nIndex)); + const sal_Int32 nStyles = comphelper::string::getTokenCount(rStyles, TOX_STYLE_DELIMITER); + uno::Sequence<OUString> aStyles(nStyles); + OUString* pStyles = aStyles.getArray(); + OUString aString; + sal_Int32 nPos = 0; + for(sal_Int32 i = 0; i < nStyles; ++i) + { + SwStyleNameMapper::FillProgName( + rStyles.getToken(0, TOX_STYLE_DELIMITER, nPos), + aString, + SwGetPoolIdFromName::TxtColl); + pStyles[i] = aString; + } + uno::Any aRet(&aStyles, cppu::UnoType<uno::Sequence<OUString>>::get()); + return aRet; +} + +uno::Type SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::getElementType() +{ + return cppu::UnoType<uno::Sequence<OUString>>::get(); +} + +sal_Bool SAL_CALL +SwXDocumentIndex::StyleAccess_Impl::hasElements() +{ + return true; +} + +SwXDocumentIndex::TokenAccess_Impl::TokenAccess_Impl( + SwXDocumentIndex& rParentIdx) + : m_xParent(&rParentIdx) +{ +} + +SwXDocumentIndex::TokenAccess_Impl::~TokenAccess_Impl() +{ +} + +OUString SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::getImplementationName() +{ + return "SwXDocumentIndex::TokenAccess_Impl"; +} + +sal_Bool SAL_CALL SwXDocumentIndex::TokenAccess_Impl::supportsService( + const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::getSupportedServiceNames() +{ + return { "com.sun.star.text.DocumentIndexLevelFormat" }; +} + +namespace { + +struct TokenType_ { + const char *pName; + enum FormTokenType eTokenType; +}; + +} + +static const struct TokenType_ g_TokenTypes[] = +{ + { "TokenEntryNumber", TOKEN_ENTRY_NO }, + { "TokenEntryText", TOKEN_ENTRY_TEXT }, + { "TokenTabStop", TOKEN_TAB_STOP }, + { "TokenText", TOKEN_TEXT }, + { "TokenPageNumber", TOKEN_PAGE_NUMS }, + { "TokenChapterInfo", TOKEN_CHAPTER_INFO }, + { "TokenHyperlinkStart", TOKEN_LINK_START }, + { "TokenHyperlinkEnd", TOKEN_LINK_END }, + { "TokenBibliographyDataField", TOKEN_AUTHORITY }, + { nullptr, static_cast<enum FormTokenType>(0) } +}; + +void SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::replaceByIndex( + sal_Int32 nIndex, const uno::Any& rElement) +{ + SolarMutexGuard aGuard; + + SwTOXBase & rTOXBase( m_xParent->m_pImpl->GetTOXSectionOrThrow() ); + + if ((nIndex < 0) || (nIndex > rTOXBase.GetTOXForm().GetFormMax())) + { + throw lang::IndexOutOfBoundsException(); + } + + uno::Sequence<beans::PropertyValues> aSeq; + if(!(rElement >>= aSeq)) + { + throw lang::IllegalArgumentException(); + } + + OUStringBuffer sPattern; + for(const beans::PropertyValues& rToken : std::as_const(aSeq)) + { + const beans::PropertyValue* pProperties = rToken.getConstArray(); + const sal_Int32 nProperties = rToken.getLength(); + //create an invalid token + SwFormToken aToken(TOKEN_END); + for(sal_Int32 j = 0; j < nProperties; j++) + { + if ( pProperties[j].Name == "TokenType" ) + { + const OUString sTokenType = + lcl_AnyToType<OUString>(pProperties[j].Value); + for (TokenType_ const* pTokenType = g_TokenTypes; + pTokenType->pName; ++pTokenType) + { + if (sTokenType.equalsAscii(pTokenType->pName)) + { + aToken.eTokenType = pTokenType->eTokenType; + break; + } + } + } + else if ( pProperties[j].Name == "CharacterStyleName" ) + { + OUString sCharStyleName; + SwStyleNameMapper::FillUIName( + lcl_AnyToType<OUString>(pProperties[j].Value), + sCharStyleName, + SwGetPoolIdFromName::ChrFmt); + aToken.sCharStyleName = sCharStyleName; + aToken.nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( + sCharStyleName, SwGetPoolIdFromName::ChrFmt ); + } + else if ( pProperties[j].Name == "TabStopRightAligned" ) + { + const bool bRight = lcl_AnyToType<bool>(pProperties[j].Value); + aToken.eTabAlign = bRight ? + SvxTabAdjust::End : SvxTabAdjust::Left; + } + else if ( pProperties[j].Name == "TabStopPosition" ) + { + sal_Int32 nPosition = 0; + if (!(pProperties[j].Value >>= nPosition)) + { + throw lang::IllegalArgumentException(); + } + nPosition = convertMm100ToTwip(nPosition); + if(nPosition < 0) + { + throw lang::IllegalArgumentException(); + } + aToken.nTabStopPosition = nPosition; + } + else if ( pProperties[j].Name == "TabStopFillCharacter" ) + { + const OUString sFillChar = + lcl_AnyToType<OUString>(pProperties[j].Value); + if (sFillChar.getLength() > 1) + { + throw lang::IllegalArgumentException(); + } + aToken.cTabFillChar = + sFillChar.isEmpty() ? ' ' : sFillChar[0]; + } + else if ( pProperties[j].Name == "Text" ) + { + aToken.sText = lcl_AnyToType<OUString>(pProperties[j].Value); + } + else if ( pProperties[j].Name == "ChapterFormat" ) + { + sal_Int16 nFormat = lcl_AnyToType<sal_Int16>(pProperties[j].Value); + switch(nFormat) + { + case text::ChapterFormat::NUMBER: + nFormat = CF_NUMBER; + break; + case text::ChapterFormat::NAME: + nFormat = CF_TITLE; + break; + case text::ChapterFormat::NAME_NUMBER: + nFormat = CF_NUM_TITLE; + break; + case text::ChapterFormat::NO_PREFIX_SUFFIX: + nFormat = CF_NUMBER_NOPREPST; + break; + case text::ChapterFormat::DIGIT: + nFormat = CF_NUM_NOPREPST_TITLE; + break; + default: + throw lang::IllegalArgumentException(); + } + aToken.nChapterFormat = nFormat; + } +// #i53420# + else if ( pProperties[j].Name == "ChapterLevel" ) + { + const sal_Int16 nLevel = lcl_AnyToType<sal_Int16>(pProperties[j].Value); + if( nLevel < 1 || nLevel > MAXLEVEL ) + { + throw lang::IllegalArgumentException(); + } + aToken.nOutlineLevel = nLevel; + } + else if ( pProperties[j].Name == "BibliographyDataField" ) + { + sal_Int16 nType = 0; + pProperties[j].Value >>= nType; + if(nType < 0 || nType > text::BibliographyDataField::ISBN) + { + lang::IllegalArgumentException aExcept; + aExcept.Message = "BibliographyDataField - wrong value"; + aExcept.ArgumentPosition = static_cast< sal_Int16 >(j); + throw aExcept; + } + aToken.nAuthorityField = nType; + } + // #i21237# + else if ( pProperties[j].Name == "WithTab" ) + { + aToken.bWithTab = lcl_AnyToType<bool>(pProperties[j].Value); + } + + } + //exception if wrong TokenType + if(TOKEN_END <= aToken.eTokenType ) + { + throw lang::IllegalArgumentException(); + } + // set TokenType from TOKEN_ENTRY_TEXT to TOKEN_ENTRY if it is + // not a content index + if(TOKEN_ENTRY_TEXT == aToken.eTokenType && + (TOX_CONTENT != rTOXBase.GetType())) + { + aToken.eTokenType = TOKEN_ENTRY; + } +// #i53420# +// check for chapter format allowed values if it was TOKEN_ENTRY_NO type +// only allowed value are CF_NUMBER and CF_NUM_NOPREPST_TITLE +// reading from file + if( TOKEN_ENTRY_NO == aToken.eTokenType ) + { + switch(aToken.nChapterFormat) + { + case CF_NUMBER: + case CF_NUM_NOPREPST_TITLE: + break; + default: + throw lang::IllegalArgumentException(); + } + } + + if (rTOXBase.GetType() == TOX_CONTENT) + { + if (aToken.eTokenType == TOKEN_LINK_START && aToken.sCharStyleName.isEmpty()) + { + aToken.sCharStyleName = SwResId(STR_POOLCHR_TOXJUMP); + } + } + + sPattern.append(aToken.GetString()); + } + SwForm aForm(rTOXBase.GetTOXForm()); + aForm.SetPattern(static_cast<sal_uInt16>(nIndex), sPattern.makeStringAndClear()); + rTOXBase.SetTOXForm(aForm); +} + +sal_Int32 SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::getCount() +{ + SolarMutexGuard aGuard; + + const sal_Int32 nRet = m_xParent->m_pImpl->GetFormMax(); + return nRet; +} + +uno::Any SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + + SwTOXBase & rTOXBase( m_xParent->m_pImpl->GetTOXSectionOrThrow() ); + + if ((nIndex < 0) || (nIndex > rTOXBase.GetTOXForm().GetFormMax())) + { + throw lang::IndexOutOfBoundsException(); + } + + // #i21237# + SwFormTokens aPattern = rTOXBase.GetTOXForm(). + GetPattern(static_cast<sal_uInt16>(nIndex)); + + sal_Int32 nTokenCount = 0; + uno::Sequence< beans::PropertyValues > aRetSeq; + OUString aProgCharStyle; + for(const SwFormToken& aToken : aPattern) // #i21237# + { + nTokenCount++; + aRetSeq.realloc(nTokenCount); + beans::PropertyValues* pTokenProps = aRetSeq.getArray(); + + uno::Sequence< beans::PropertyValue >& rCurTokenSeq = + pTokenProps[nTokenCount-1]; + SwStyleNameMapper::FillProgName( + aToken.sCharStyleName, + aProgCharStyle, + SwGetPoolIdFromName::ChrFmt); + switch(aToken.eTokenType) + { + case TOKEN_ENTRY_NO: + { +// #i53420# +// writing to file (from doc to properties) + sal_Int32 nElements = 2; + sal_Int32 nCurrentElement = 0; + + // check for default value + if (aToken.nChapterFormat != CF_NUMBER) + { + nElements++;//we need the element + } + if( aToken.nOutlineLevel != MAXLEVEL ) + { + nElements++; + } + + rCurTokenSeq.realloc( nElements ); + + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[nCurrentElement].Name = "TokenType"; + pArr[nCurrentElement++].Value <<= + OUString("TokenEntryNumber"); + + pArr[nCurrentElement].Name = "CharacterStyleName"; + pArr[nCurrentElement++].Value <<= aProgCharStyle; + if( aToken.nChapterFormat != CF_NUMBER ) + { + pArr[nCurrentElement].Name = "ChapterFormat"; + sal_Int16 nVal; +// the allowed values for chapter format, when used as entry number, +// are CF_NUMBER and CF_NUM_NOPREPST_TITLE only, all else forced to +//CF_NUMBER + switch(aToken.nChapterFormat) + { + default: + case CF_NUMBER: + nVal = text::ChapterFormat::NUMBER; + break; + case CF_NUM_NOPREPST_TITLE: + nVal = text::ChapterFormat::DIGIT; + break; + } + pArr[nCurrentElement++].Value <<= nVal; + } + + // only a ChapterLevel != MAXLEVEL is registered + if (aToken.nOutlineLevel != MAXLEVEL) + { + pArr[nCurrentElement].Name = "ChapterLevel"; + pArr[nCurrentElement].Value <<= aToken.nOutlineLevel; + } + } + break; + case TOKEN_ENTRY: // no difference between Entry and Entry Text + case TOKEN_ENTRY_TEXT: + { + rCurTokenSeq.realloc( 2 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= OUString("TokenEntryText"); + + pArr[1].Name = "CharacterStyleName"; + pArr[1].Value <<= aProgCharStyle; + } + break; + case TOKEN_TAB_STOP: + { + rCurTokenSeq.realloc(5); // #i21237# + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= OUString("TokenTabStop"); + + if(SvxTabAdjust::End == aToken.eTabAlign) + { + pArr[1].Name = "TabStopRightAligned"; + pArr[1].Value <<= true; + } + else + { + pArr[1].Name = "TabStopPosition"; + sal_Int32 nPos = convertTwipToMm100(aToken.nTabStopPosition); + if(nPos < 0) + nPos = 0; + pArr[1].Value <<= nPos; + } + pArr[2].Name = "TabStopFillCharacter"; + pArr[2].Value <<= OUString(aToken.cTabFillChar); + pArr[3].Name = "CharacterStyleName"; + pArr[3].Value <<= aProgCharStyle; + // #i21237# + pArr[4].Name = "WithTab"; + pArr[4].Value <<= aToken.bWithTab; + } + break; + case TOKEN_TEXT: + { + rCurTokenSeq.realloc( 3 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= OUString("TokenText"); + + pArr[1].Name = "CharacterStyleName"; + pArr[1].Value <<= aProgCharStyle; + + pArr[2].Name = "Text"; + pArr[2].Value <<= aToken.sText; + } + break; + case TOKEN_PAGE_NUMS: + { + rCurTokenSeq.realloc( 2 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= OUString("TokenPageNumber"); + + pArr[1].Name = "CharacterStyleName"; + pArr[1].Value <<= aProgCharStyle; + } + break; + case TOKEN_CHAPTER_INFO: + { + rCurTokenSeq.realloc( 4 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= OUString("TokenChapterInfo"); + + pArr[1].Name = "CharacterStyleName"; + pArr[1].Value <<= aProgCharStyle; + + pArr[2].Name = "ChapterFormat"; + sal_Int16 nVal = text::ChapterFormat::NUMBER; + switch(aToken.nChapterFormat) + { + case CF_NUMBER: + nVal = text::ChapterFormat::NUMBER; + break; + case CF_TITLE: + nVal = text::ChapterFormat::NAME; + break; + case CF_NUM_TITLE: + nVal = text::ChapterFormat::NAME_NUMBER; + break; + case CF_NUMBER_NOPREPST: + nVal = text::ChapterFormat::NO_PREFIX_SUFFIX; + break; + case CF_NUM_NOPREPST_TITLE: + nVal = text::ChapterFormat::DIGIT; + break; + } + pArr[2].Value <<= nVal; +// #i53420# + pArr[3].Name = "ChapterLevel"; + pArr[3].Value <<= aToken.nOutlineLevel; + } + break; + case TOKEN_LINK_START: + { + rCurTokenSeq.realloc( 2 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= + OUString("TokenHyperlinkStart"); + pArr[1].Name = "CharacterStyleName"; + pArr[1].Value <<= aProgCharStyle; + } + break; + case TOKEN_LINK_END: + { + rCurTokenSeq.realloc( 1 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= + OUString("TokenHyperlinkEnd"); + } + break; + case TOKEN_AUTHORITY: + { + rCurTokenSeq.realloc( 3 ); + beans::PropertyValue* pArr = rCurTokenSeq.getArray(); + + pArr[0].Name = "TokenType"; + pArr[0].Value <<= + OUString("TokenBibliographyDataField"); + + pArr[1].Name = "CharacterStyleName"; + pArr[1].Value <<= aProgCharStyle; + + pArr[2].Name = "BibliographyDataField"; + pArr[2].Value <<= sal_Int16(aToken.nAuthorityField); + } + break; + + default: + ; + } + } + + uno::Any aRet; + aRet <<= aRetSeq; + return aRet; +} + +uno::Type SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::getElementType() +{ + return cppu::UnoType<uno::Sequence< beans::PropertyValues >>::get(); +} + +sal_Bool SAL_CALL +SwXDocumentIndex::TokenAccess_Impl::hasElements() +{ + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx new file mode 100644 index 000000000..8da991f62 --- /dev/null +++ b/sw/source/core/unocore/unomap.cxx @@ -0,0 +1,1569 @@ +/* -*- 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 <hintids.hxx> + +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/PropertyValues.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/i18n/XForbiddenCharacters.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/style/GraphicLocation.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <com/sun/star/table/BorderLine.hpp> +#include <com/sun/star/text/PageNumberType.hpp> +#include <com/sun/star/text/TableColumnSeparator.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XDependentTextField.hpp> +#include <com/sun/star/text/XDocumentIndexMark.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/text/XTextSection.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/drawing/HomogenMatrix3.hpp> +#include <osl/diagnose.h> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unomid.h> +#include <cmdid.h> +#include <unofldmid.h> +#include <editeng/colritem.hxx> +#include <editeng/memberids.h> +#include <editeng/unoprnms.hxx> +#include <svl/itemprop.hxx> +#include "unomapproperties.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +#define COMMON_FLDTYP_PROPERTIES \ + { OUString(UNO_NAME_IS_FIELD_USED), FIELD_PROP_IS_FIELD_USED, cppu::UnoType<float>::get(), PropertyAttribute::READONLY, 0},\ + { OUString(UNO_NAME_IS_FIELD_DISPLAYED), FIELD_PROP_IS_FIELD_DISPLAYED, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::READONLY, 0},\ + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPropertyMapEntries(sal_uInt16 nPropertyId) +{ + OSL_ENSURE(nPropertyId < PROPERTY_MAP_END, "Id ?" ); + if( !m_aMapEntriesArr[ nPropertyId ] ) + { + switch(nPropertyId) + { + case PROPERTY_MAP_TEXT_CURSOR: + { + m_aMapEntriesArr[nPropertyId] = GetTextCursorPropertyMap(); + } + break; + case PROPERTY_MAP_ACCESSIBILITY_TEXT_ATTRIBUTE: + { + m_aMapEntriesArr[nPropertyId] = GetAccessibilityTextAttrPropertyMap(); + } + break; + case PROPERTY_MAP_PARAGRAPH: + { + m_aMapEntriesArr[nPropertyId] = GetParagraphPropertyMap(); + } + break; + case PROPERTY_MAP_PARA_AUTO_STYLE : + { + m_aMapEntriesArr[nPropertyId] = GetAutoParaStylePropertyMap(); + } + break; + case PROPERTY_MAP_CHAR_STYLE : + { + m_aMapEntriesArr[nPropertyId] = GetCharStylePropertyMap(); + } + break; + case PROPERTY_MAP_CHAR_AUTO_STYLE : + { + m_aMapEntriesArr[nPropertyId] = GetAutoCharStylePropertyMap(); + } + break; + case PROPERTY_MAP_RUBY_AUTO_STYLE : + { + static SfxItemPropertyMapEntry const aAutoRubyStyleMap [] = + { + { OUString(UNO_NAME_RUBY_ADJUST), RES_TXTATR_CJK_RUBY, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_ADJUST }, + { OUString(UNO_NAME_RUBY_IS_ABOVE), RES_TXTATR_CJK_RUBY, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_ABOVE }, + { OUString(UNO_NAME_RUBY_POSITION), RES_TXTATR_CJK_RUBY, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_POSITION }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aAutoRubyStyleMap; + } + break; + case PROPERTY_MAP_PARA_STYLE : + { + m_aMapEntriesArr[nPropertyId] = GetParaStylePropertyMap(); + } + break; + case PROPERTY_MAP_CONDITIONAL_PARA_STYLE : + { + m_aMapEntriesArr[nPropertyId] = GetConditionalParaStylePropertyMap(); + } + break; + case PROPERTY_MAP_FRAME_STYLE: + { + m_aMapEntriesArr[nPropertyId] = GetFrameStylePropertyMap(); + } + break; + case PROPERTY_MAP_PAGE_STYLE : + { + m_aMapEntriesArr[nPropertyId] = GetPageStylePropertyMap(); + } + break; + case PROPERTY_MAP_NUM_STYLE : + { + static SfxItemPropertyMapEntry const aNumStyleMap [] = + { + { OUString(UNO_NAME_NUMBERING_RULES), FN_UNO_NUM_RULES, cppu::UnoType<css::container::XIndexReplace>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_IS_PHYSICAL), FN_UNO_IS_PHYSICAL, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_DISPLAY_NAME), FN_UNO_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_HIDDEN), FN_UNO_HIDDEN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_STYLE_INTEROP_GRAB_BAG), FN_UNO_STYLE_INTEROP_GRAB_BAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aNumStyleMap; + } + break; + case PROPERTY_MAP_TEXT_TABLE : + { + m_aMapEntriesArr[nPropertyId] = GetTablePropertyMap(); + } + break; + case PROPERTY_MAP_TABLE_CELL : + { + static SfxItemPropertyMapEntry const aCellMap_Impl[] = + { + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE , MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_NUMBER_FORMAT), RES_BOXATR_FORMAT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID ,0 }, + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_TEXT_SECTION), FN_UNO_TEXT_SECTION, cppu::UnoType<css::text::XTextSection>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_IS_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), 0, MID_PROTECT_CONTENT}, + { OUString(UNO_NAME_CELL_NAME), FN_UNO_CELL_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY,0}, + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_ORIENT }, + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_ROW_SPAN), FN_UNO_CELL_ROW_SPAN, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { OUString(UNO_NAME_CELL_INTEROP_GRAB_BAG), RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_PARENT_TEXT), FN_UNO_PARENT_TEXT, cppu::UnoType<text::XText>::get(), PropertyAttribute::MAYBEVOID | PropertyAttribute::READONLY, 0 }, + REDLINE_NODE_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aCellMap_Impl; + } + break; + case PROPERTY_MAP_TABLE_RANGE: + { + m_aMapEntriesArr[nPropertyId] = GetRangePropertyMap(); + } + break; + case PROPERTY_MAP_SECTION: + { + m_aMapEntriesArr[nPropertyId] = GetSectionPropertyMap(); + } + break; + case PROPERTY_MAP_TEXT_SEARCH: + { + static SfxItemPropertyMapEntry const aSearchPropertyMap_Impl[] = + { + { OUString(UNO_NAME_SEARCH_ALL), WID_SEARCH_ALL, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_BACKWARDS), WID_BACKWARDS, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_CASE_SENSITIVE), WID_CASE_SENSITIVE, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_REGULAR_EXPRESSION), WID_REGULAR_EXPRESSION, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_SIMILARITY), WID_SIMILARITY, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_SIMILARITY_ADD), WID_SIMILARITY_ADD, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_SIMILARITY_EXCHANGE), WID_SIMILARITY_EXCHANGE,cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_SIMILARITY_RELAX), WID_SIMILARITY_RELAX, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_SIMILARITY_REMOVE), WID_SIMILARITY_REMOVE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_STYLES), WID_STYLES, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEARCH_WORDS), WID_WORDS, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aSearchPropertyMap_Impl; + } + break; + case PROPERTY_MAP_TEXT_FRAME: + { + m_aMapEntriesArr[nPropertyId] = GetFramePropertyMap(); + } + break; + case PROPERTY_MAP_TEXT_GRAPHIC: + { + m_aMapEntriesArr[nPropertyId] = GetGraphicPropertyMap(); + } + break; + case PROPERTY_MAP_EMBEDDED_OBJECT: + { + m_aMapEntriesArr[nPropertyId] = GetEmbeddedPropertyMap(); + } + break; + case PROPERTY_MAP_TEXT_SHAPE: + { + static SfxItemPropertyMapEntry const aShapeMap_Impl[] = + { + { OUString(UNO_NAME_ANCHOR_PAGE_NO), RES_ANCHOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID, MID_ANCHOR_PAGENUM }, + { OUString(UNO_NAME_ANCHOR_TYPE), RES_ANCHOR, cppu::UnoType<css::text::TextContentAnchorType>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID, MID_ANCHOR_ANCHORTYPE}, + { OUString(UNO_NAME_ANCHOR_FRAME), RES_ANCHOR, cppu::UnoType<css::text::XTextFrame>::get(), PropertyAttribute::MAYBEVOID, MID_ANCHOR_ANCHORFRAME}, + { OUString(UNO_NAME_HORI_ORIENT), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID ,MID_HORIORIENT_ORIENT }, + { OUString(UNO_NAME_HORI_ORIENT_POSITION), RES_HORI_ORIENT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID ,MID_HORIORIENT_POSITION|CONVERT_TWIPS }, + { OUString(UNO_NAME_HORI_ORIENT_RELATION), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID ,MID_HORIORIENT_RELATION }, + { OUString(UNO_NAME_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_SURROUND), RES_SURROUND, cppu::UnoType<css::text::WrapTextMode>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID, MID_SURROUND_SURROUNDTYPE }, + { OUString(UNO_NAME_TEXT_WRAP), RES_SURROUND, cppu::UnoType<css::text::WrapTextMode>::get(), PROPERTY_NONE, MID_SURROUND_SURROUNDTYPE }, + { OUString(UNO_NAME_SURROUND_ANCHORONLY), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID, MID_SURROUND_ANCHORONLY }, + { OUString(UNO_NAME_SURROUND_CONTOUR), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUR }, + { OUString(UNO_NAME_CONTOUR_OUTSIDE), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUROUTSIDE }, + { OUString(UNO_NAME_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_UP_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_LO_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID ,MID_VERTORIENT_ORIENT }, + { OUString(UNO_NAME_VERT_ORIENT_POSITION), RES_VERT_ORIENT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID ,MID_VERTORIENT_POSITION|CONVERT_TWIPS }, + { OUString(UNO_NAME_VERT_ORIENT_RELATION), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE|PropertyAttribute::MAYBEVOID ,MID_VERTORIENT_RELATION }, + { OUString(UNO_NAME_TEXT_RANGE), FN_TEXT_RANGE, cppu::UnoType<css::text::XTextRange>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_OPAQUE), RES_OPAQUE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_ANCHOR_POSITION), FN_ANCHOR_POSITION, cppu::UnoType<css::awt::Point>::get(), PropertyAttribute::READONLY, 0}, + // #i26791# + { OUString(UNO_NAME_IS_FOLLOWING_TEXT_FLOW), RES_FOLLOW_TEXT_FLOW, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FOLLOW_TEXT_FLOW}, + // #i28701# + { OUString(UNO_NAME_WRAP_INFLUENCE_ON_POSITION), RES_WRAP_INFLUENCE_ON_OBJPOS, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, MID_WRAP_INFLUENCE}, + { OUString(UNO_NAME_ALLOW_OVERLAP), RES_WRAP_INFLUENCE_ON_OBJPOS, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_ALLOW_OVERLAP}, + // #i28749# + { OUString(UNO_NAME_TRANSFORMATION_IN_HORI_L2R), + FN_SHAPE_TRANSFORMATION_IN_HORI_L2R, + cppu::UnoType<css::drawing::HomogenMatrix3>::get(), + PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_POSITION_LAYOUT_DIR), + FN_SHAPE_POSITION_LAYOUT_DIR, + cppu::UnoType<sal_Int16>::get(), + PROPERTY_NONE, 0}, + // #i36248# + { OUString(UNO_NAME_STARTPOSITION_IN_HORI_L2R), + FN_SHAPE_STARTPOSITION_IN_HORI_L2R, + cppu::UnoType<css::awt::Point>::get(), + PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_ENDPOSITION_IN_HORI_L2R), + FN_SHAPE_ENDPOSITION_IN_HORI_L2R, + cppu::UnoType<css::awt::Point>::get(), + PropertyAttribute::READONLY, 0}, + // #i71182# + // missing map entry for property <PageToggle> + { OUString(UNO_NAME_PAGE_TOGGLE), RES_HORI_ORIENT, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_HORIORIENT_PAGETOGGLE }, + { OUString(UNO_NAME_RELATIVE_HEIGHT), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT }, + { OUString(UNO_NAME_RELATIVE_HEIGHT_RELATION), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT_RELATION }, + { OUString(UNO_NAME_RELATIVE_WIDTH), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH }, + { OUString(UNO_NAME_RELATIVE_WIDTH_RELATION), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH_RELATION }, + { OUString(UNO_NAME_TEXT_BOX), FN_TEXT_BOX, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAIN_NEXT_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_NEXTNAME}, + { OUString(UNO_NAME_CHAIN_PREV_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_PREVNAME}, + { OUString(UNO_NAME_CHAIN_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_NAME }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aShapeMap_Impl; + } + break; + case PROPERTY_MAP_INDEX_MARK: + { + m_aMapEntriesArr[nPropertyId] = GetIndexMarkPropertyMap(); + } + break; + case PROPERTY_MAP_CNTIDX_MARK: + { + m_aMapEntriesArr[nPropertyId] = GetContentMarkPropertyMap(); + } + break; + case PROPERTY_MAP_USER_MARK: + { + m_aMapEntriesArr[nPropertyId] = GetUserMarkPropertyMap(); + } + break; + case PROPERTY_MAP_INDEX_IDX: + { + static SfxItemPropertyMapEntry const aTOXIndexMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_CREATE_FROM_CHAPTER), WID_CREATE_FROM_CHAPTER , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_ALPHABETICAL_SEPARATORS), WID_USE_ALPHABETICAL_SEPARATORS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_KEY_AS_ENTRY), WID_USE_KEY_AS_ENTRY , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_COMBINED_ENTRIES), WID_USE_COMBINED_ENTRIES , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_CASE_SENSITIVE), WID_IS_CASE_SENSITIVE , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_P_P), WID_USE_P_P , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_DASH), WID_USE_DASH , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_UPPER_CASE), WID_USE_UPPER_CASE , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_MAIN_ENTRY_CHARACTER_STYLE_NAME), WID_MAIN_ENTRY_CHARACTER_STYLE_NAME , cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLESEPARATOR), WID_PARA_SEP, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL2), WID_PARA_LEV2, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL3), WID_PARA_LEV3, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_IS_COMMA_SEPARATED), WID_IS_COMMA_SEPARATED, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_DOCUMENT_INDEX_MARKS), WID_INDEX_MARKS, cppu::UnoType< cppu::UnoSequenceType<css::text::XDocumentIndexMark> >::get(), PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_IS_RELATIVE_TABSTOPS), WID_IS_RELATIVE_TABSTOPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LOCALE), WID_IDX_LOCALE, cppu::UnoType<css::lang::Locale>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SORT_ALGORITHM), WID_IDX_SORT_ALGORITHM, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_INDEX_ENTRY_TYPE), WID_INDEX_ENTRY_TYPE, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTOXIndexMap_Impl; + } + break; + case PROPERTY_MAP_INDEX_CNTNT: + { + static SfxItemPropertyMapEntry const aTOXContentMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_LEVEL), WID_LEVEL , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_MARKS), WID_CREATE_FROM_MARKS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_HIDE_TAB_LEADER_AND_PAGE_NUMBERS), WID_HIDE_TABLEADER_PAGENUMBERS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TAB_IN_TOC), WID_TAB_IN_TOC, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TOC_BOOKMARK), WID_TOC_BOOKMARK, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TOC_NEWLINE), WID_TOC_NEWLINE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TOC_PARAGRAPH_OUTLINE_LEVEL), WID_TOC_PARAGRAPH_OUTLINE_LEVEL, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_OUTLINE), WID_CREATE_FROM_OUTLINE , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_CHAPTER), WID_CREATE_FROM_CHAPTER , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_PARAGRAPH_STYLES), WID_LEVEL_PARAGRAPH_STYLES , cppu::UnoType<css::container::XIndexReplace>::get() , PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), WID_CREATE_FROM_PARAGRAPH_STYLES, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL2), WID_PARA_LEV2, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL3), WID_PARA_LEV3, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL4), WID_PARA_LEV4, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL5), WID_PARA_LEV5, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL6), WID_PARA_LEV6, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL7), WID_PARA_LEV7, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL8), WID_PARA_LEV8, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL9), WID_PARA_LEV9, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL10), WID_PARA_LEV10, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_IS_RELATIVE_TABSTOPS), WID_IS_RELATIVE_TABSTOPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DOCUMENT_INDEX_MARKS), WID_INDEX_MARKS, cppu::UnoType< cppu::UnoSequenceType<css::text::XDocumentIndexMark> >::get(), PropertyAttribute::READONLY ,0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTOXContentMap_Impl; + } + break; + case PROPERTY_MAP_INDEX_USER: + { + static SfxItemPropertyMapEntry const aTOXUserMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_CREATE_FROM_MARKS), WID_CREATE_FROM_MARKS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_HIDE_TAB_LEADER_AND_PAGE_NUMBERS), WID_HIDE_TABLEADER_PAGENUMBERS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TAB_IN_TOC), WID_TAB_IN_TOC, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TOC_BOOKMARK), WID_TOC_BOOKMARK, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TOC_NEWLINE), WID_TOC_NEWLINE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TOC_PARAGRAPH_OUTLINE_LEVEL), WID_TOC_PARAGRAPH_OUTLINE_LEVEL, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_CHAPTER), WID_CREATE_FROM_CHAPTER , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_LEVEL_FROM_SOURCE), WID_USE_LEVEL_FROM_SOURCE , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE,0}, + { OUString(UNO_NAME_LEVEL_PARAGRAPH_STYLES), WID_LEVEL_PARAGRAPH_STYLES , cppu::UnoType<css::container::XIndexReplace>::get() , PropertyAttribute::READONLY,0}, + { OUString(UNO_NAME_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), WID_CREATE_FROM_PARAGRAPH_STYLES, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_TABLES), WID_CREATE_FROM_TABLES , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_TEXT_FRAMES), WID_CREATE_FROM_TEXT_FRAMES , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_GRAPHIC_OBJECTS), WID_CREATE_FROM_GRAPHIC_OBJECTS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_EMBEDDED_OBJECTS), WID_CREATE_FROM_EMBEDDED_OBJECTS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL2), WID_PARA_LEV2, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL3), WID_PARA_LEV3, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL4), WID_PARA_LEV4, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL5), WID_PARA_LEV5, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL6), WID_PARA_LEV6, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL7), WID_PARA_LEV7, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL8), WID_PARA_LEV8, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL9), WID_PARA_LEV9, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL10), WID_PARA_LEV10, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_DOCUMENT_INDEX_MARKS), WID_INDEX_MARKS, cppu::UnoType< cppu::UnoSequenceType<css::text::XDocumentIndexMark> >::get(), PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_IS_RELATIVE_TABSTOPS), WID_IS_RELATIVE_TABSTOPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USER_INDEX_NAME), WID_USER_IDX_NAME, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTOXUserMap_Impl; + } + break; + case PROPERTY_MAP_INDEX_TABLES: + { + static SfxItemPropertyMapEntry const aTOXTablesMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_CREATE_FROM_CHAPTER), WID_CREATE_FROM_CHAPTER , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_LABELS), WID_CREATE_FROM_LABELS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LABEL_CATEGORY), WID_LABEL_CATEGORY , cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LABEL_DISPLAY_TYPE), WID_LABEL_DISPLAY_TYPE , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE,0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_IS_RELATIVE_TABSTOPS), WID_IS_RELATIVE_TABSTOPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTOXTablesMap_Impl; + } + break; + case PROPERTY_MAP_INDEX_OBJECTS: + { + static SfxItemPropertyMapEntry const aTOXObjectsMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_CREATE_FROM_CHAPTER), WID_CREATE_FROM_CHAPTER , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_ALPHABETICAL_SEPARATORS), WID_USE_ALPHABETICAL_SEPARATORS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE,0}, + { OUString(UNO_NAME_CREATE_FROM_STAR_MATH), WID_CREATE_FROM_STAR_MATH , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_STAR_CHART), WID_CREATE_FROM_STAR_CHART , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_STAR_CALC), WID_CREATE_FROM_STAR_CALC , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_STAR_DRAW), WID_CREATE_FROM_STAR_DRAW , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_OTHER_EMBEDDED_OBJECTS), WID_CREATE_FROM_OTHER_EMBEDDED_OBJECTS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_IS_RELATIVE_TABSTOPS), WID_IS_RELATIVE_TABSTOPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTOXObjectsMap_Impl; + } + break; + case PROPERTY_MAP_INDEX_ILLUSTRATIONS: + { + static SfxItemPropertyMapEntry const aTOXIllustrationsMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_CREATE_FROM_CHAPTER), WID_CREATE_FROM_CHAPTER , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CREATE_FROM_LABELS), WID_CREATE_FROM_LABELS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USE_ALPHABETICAL_SEPARATORS), WID_USE_ALPHABETICAL_SEPARATORS , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LABEL_CATEGORY), WID_LABEL_CATEGORY , cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LABEL_DISPLAY_TYPE), WID_LABEL_DISPLAY_TYPE , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE,0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_IS_RELATIVE_TABSTOPS), WID_IS_RELATIVE_TABSTOPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTOXIllustrationsMap_Impl; + } + break; + case PROPERTY_MAP_TEXT_TABLE_ROW: + { + static SfxItemPropertyMapEntry const aTableRowPropertyMap_Impl[] = + { + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_TABLE_COLUMN_SEPARATORS), FN_UNO_TABLE_COLUMN_SEPARATORS, cppu::UnoType< cppu::UnoSequenceType<css::text::TableColumnSeparator> >::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_HEIGHT), FN_UNO_ROW_HEIGHT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,CONVERT_TWIPS }, + { OUString(UNO_NAME_IS_AUTO_HEIGHT), FN_UNO_ROW_AUTO_HEIGHT, cppu::UnoType<bool>::get(), PROPERTY_NONE , 0 }, + { OUString(UNO_NAME_SIZE_TYPE), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_SIZE_TYPE }, + { OUString(UNO_NAME_WIDTH_TYPE), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH_TYPE }, + { OUString(UNO_NAME_IS_SPLIT_ALLOWED), RES_ROW_SPLIT, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_ROW_INTEROP_GRAB_BAG), RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + m_aMapEntriesArr[nPropertyId] = aTableRowPropertyMap_Impl; + } + break; + case PROPERTY_MAP_TEXT_TABLE_CURSOR: + { + m_aMapEntriesArr[nPropertyId] = GetTextTableCursorPropertyMap(); + } + break; + case PROPERTY_MAP_BOOKMARK: + { + m_aMapEntriesArr[nPropertyId] = GetBookmarkPropertyMap(); + } + break; + case PROPERTY_MAP_PARAGRAPH_EXTENSIONS: + { + m_aMapEntriesArr[nPropertyId] = GetParagraphExtensionsPropertyMap(); + } + break; + case PROPERTY_MAP_BIBLIOGRAPHY : + { + static SfxItemPropertyMapEntry const aBibliographyMap_Impl[] = + { + BASE_INDEX_PROPERTIES_ + { OUString(UNO_NAME_IS_PROTECTED), WID_PROTECTED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_PARA_STYLEHEADING), WID_PARA_HEAD, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_PARA_STYLELEVEL1), WID_PARA_LEV1, cppu::UnoType<OUString>::get() , 0, 0}, + { OUString(UNO_NAME_LEVEL_FORMAT), WID_LEVEL_FORMAT , cppu::UnoType<css::container::XIndexReplace>::get() , PROPERTY_NONE,0}, + { OUString(UNO_NAME_LOCALE), WID_IDX_LOCALE, cppu::UnoType<css::lang::Locale>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SORT_ALGORITHM), WID_IDX_SORT_ALGORITHM, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aBibliographyMap_Impl; + } + break; + case PROPERTY_MAP_TEXT_DOCUMENT: + { + static SfxItemPropertyMapEntry const aDocMap_Impl[] = + { + { OUString(UNO_NAME_BASIC_LIBRARIES), WID_DOC_BASIC_LIBRARIES, cppu::UnoType<css::script::XLibraryContainer>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_CHAR_FONT_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, + { OUString(UNO_NAME_CHAR_FONT_FAMILY), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, + { OUString(UNO_NAME_CHAR_FONT_PITCH), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, + { OUString(UNO_NAME_CHAR_FONT_NAME_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, + { OUString(UNO_NAME_CHAR_FONT_FAMILY_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, + { OUString(UNO_NAME_CHAR_FONT_PITCH_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, + { OUString(UNO_NAME_CHAR_FONT_NAME_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, + { OUString(UNO_NAME_CHAR_FONT_FAMILY_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, + { OUString(UNO_NAME_CHAR_FONT_PITCH_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, + { OUString(UNO_NAME_CHAR_LOCALE), RES_CHRATR_LANGUAGE , cppu::UnoType<css::lang::Locale>::get(), PropertyAttribute::MAYBEVOID, MID_LANG_LOCALE }, + { OUString(UNO_NAME_CHARACTER_COUNT), WID_DOC_CHAR_COUNT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_DIALOG_LIBRARIES), WID_DOC_DIALOG_LIBRARIES, cppu::UnoType<css::script::XLibraryContainer>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_VBA_DOCOBJ), WID_DOC_VBA_DOCOBJ, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_INDEX_AUTO_MARK_FILE_U_R_L), WID_DOC_AUTO_MARK_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PARAGRAPH_COUNT), WID_DOC_PARA_COUNT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_RECORD_CHANGES), WID_DOC_CHANGES_RECORD, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SHOW_CHANGES), WID_DOC_CHANGES_SHOW, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_WORD_COUNT), WID_DOC_WORD_COUNT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_IS_TEMPLATE), WID_DOC_ISTEMPLATEID, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_WORD_SEPARATOR), WID_DOC_WORD_SEPARATOR, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_HIDE_FIELD_TIPS), WID_DOC_HIDE_TIPS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_REDLINE_DISPLAY_TYPE), WID_DOC_REDLINE_DISPLAY, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_REDLINE_PROTECTION_KEY), WID_DOC_CHANGES_PASSWORD, cppu::UnoType< cppu::UnoSequenceType<sal_Int8> >::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_FORBIDDEN_CHARACTERS), WID_DOC_FORBIDDEN_CHARS, cppu::UnoType<css::i18n::XForbiddenCharacters>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TWO_DIGIT_YEAR), WID_DOC_TWO_DIGIT_YEAR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_AUTOMATIC_CONTROL_FOCUS), WID_DOC_AUTOMATIC_CONTROL_FOCUS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_APPLY_FORM_DESIGN_MODE), WID_DOC_APPLY_FORM_DESIGN_MODE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_RUNTIME_UID), WID_DOC_RUNTIME_UID, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_LOCK_UPDATES), WID_DOC_LOCK_UPDATES, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString("UndocumentedWriterfilterHack"), WID_DOC_WRITERFILTER, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_HAS_VALID_SIGNATURES), WID_DOC_HAS_VALID_SIGNATURES, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_BUILDID), WID_DOC_BUILDID, cppu::UnoType<OUString>::get(), 0, 0}, + { OUString(UNO_NAME_DOC_INTEROP_GRAB_BAG), WID_DOC_INTEROP_GRAB_BAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_DEFAULT_PAGE_MODE), WID_DOC_DEFAULT_PAGE_MODE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocMap_Impl; + } + break; + case PROPERTY_MAP_LINK_TARGET: + { + static SfxItemPropertyMapEntry const aLinkTargetMap_Impl[] = + { + { OUString(UNO_LINK_DISPLAY_BITMAP), 0, cppu::UnoType<css::awt::XBitmap>::get(), PropertyAttribute::READONLY, 0xbf}, + { OUString(UNO_LINK_DISPLAY_NAME), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0xbf}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aLinkTargetMap_Impl; + } + break; + case PROPERTY_MAP_AUTO_TEXT_GROUP : + { + static SfxItemPropertyMapEntry const aAutoTextGroupMap_Impl[] = + { + { OUString(UNO_NAME_FILE_PATH), WID_GROUP_PATH, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_TITLE), WID_GROUP_TITLE, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aAutoTextGroupMap_Impl; + } + break; + case PROPERTY_MAP_TEXTPORTION_EXTENSIONS: + { + m_aMapEntriesArr[nPropertyId] = GetTextPortionExtensionPropertyMap(); + } + break; + case PROPERTY_MAP_FOOTNOTE: + { + m_aMapEntriesArr[nPropertyId] = GetFootnotePropertyMap(); + } + break; + case PROPERTY_MAP_TEXT_COLUMS : + { + static SfxItemPropertyMapEntry const aTextColumns_Impl[] = + { + {OUString(UNO_NAME_IS_AUTOMATIC), WID_TXTCOL_IS_AUTOMATIC, cppu::UnoType<bool>::get(),PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_AUTOMATIC_DISTANCE), WID_TXTCOL_AUTO_DISTANCE, cppu::UnoType<sal_Int32>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEPARATOR_LINE_WIDTH), WID_TXTCOL_LINE_WIDTH, cppu::UnoType<sal_Int32>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEPARATOR_LINE_COLOR), WID_TXTCOL_LINE_COLOR, cppu::UnoType<sal_Int32>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEPARATOR_LINE_RELATIVE_HEIGHT), WID_TXTCOL_LINE_REL_HGT, cppu::UnoType<sal_Int32>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEPARATOR_LINE_VERTIVAL_ALIGNMENT), WID_TXTCOL_LINE_ALIGN, cppu::UnoType<css::style::VerticalAlignment>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEPARATOR_LINE_IS_ON), WID_TXTCOL_LINE_IS_ON, cppu::UnoType<bool>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEPARATOR_LINE_STYLE), WID_TXTCOL_LINE_STYLE, cppu::UnoType<sal_Int8>::get(),PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTextColumns_Impl; + } + break; + case PROPERTY_MAP_REDLINE : + { + m_aMapEntriesArr[nPropertyId] = GetRedlinePropertyMap(); + } + break; + case PROPERTY_MAP_TEXT_DEFAULT : + { + SfxItemPropertyMapEntry* aTextDefaultMap_Impl = GetTextDefaultPropertyMap(); + m_aMapEntriesArr[nPropertyId] = aTextDefaultMap_Impl; + for( SfxItemPropertyMapEntry * pMap = aTextDefaultMap_Impl; + !pMap->aName.isEmpty(); ++pMap ) + { + // OUString(UNO_NAME_PAGE_DESC_NAME) should keep its MAYBEVOID flag + if (!(RES_PAGEDESC == pMap->nWID && MID_PAGEDESC_PAGEDESCNAME == pMap->nMemberId)) + pMap->nFlags &= ~PropertyAttribute::MAYBEVOID; + } + } + break; + case PROPERTY_MAP_REDLINE_PORTION : + { + m_aMapEntriesArr[nPropertyId] = GetRedlinePortionPropertyMap(); + } + break; + case PROPERTY_MAP_FLDTYP_DATETIME: + { + static SfxItemPropertyMapEntry const aDateTimeFieldPropMap[] = + { + {OUString(UNO_NAME_ADJUST), FIELD_PROP_SUBTYPE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATE_TIME_VALUE), FIELD_PROP_DATE_TIME, cppu::UnoType<css::util::DateTime>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + {OUString(UNO_NAME_IS_DATE), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDateTimeFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_USER : + { + static SfxItemPropertyMapEntry const aUserFieldPropMap[] = + { + {OUString(UNO_NAME_IS_SHOW_FORMULA), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_VISIBLE), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + m_aMapEntriesArr[nPropertyId] = aUserFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_SET_EXP : + { + static SfxItemPropertyMapEntry const aSetExpFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_HINT), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBERING_TYPE), FIELD_PROP_USHORT2, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_INPUT), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + // #i69733# wrong name - OUString(UNO_NAME_IS_INPUT) expanded to "Input" instead of "IsInput" + {OUString(UNO_NAME_INPUT), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_SHOW_FORMULA), FIELD_PROP_BOOL3, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_VISIBLE), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEQUENCE_VALUE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SUB_TYPE), FIELD_PROP_SUBTYPE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_VALUE), FIELD_PROP_DOUBLE, cppu::UnoType<double>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_VARIABLE_NAME), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aSetExpFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_GET_EXP : + { + static SfxItemPropertyMapEntry const aGetExpFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_SHOW_FORMULA), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SUB_TYPE), FIELD_PROP_SUBTYPE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_VALUE), FIELD_PROP_DOUBLE, cppu::UnoType<double>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_VARIABLE_SUBTYPE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aGetExpFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_FILE_NAME: + { + static SfxItemPropertyMapEntry const aFileNameFieldPropMap [] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_FILE_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aFileNameFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_PAGE_NUM : + { + static SfxItemPropertyMapEntry const aPageNumFieldPropMap [] = + { + {OUString(UNO_NAME_NUMBERING_TYPE), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_OFFSET), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SUB_TYPE), FIELD_PROP_SUBTYPE, cppu::UnoType<css::text::PageNumberType>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_USERTEXT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aPageNumFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_AUTHOR : + { + static SfxItemPropertyMapEntry const aAuthorFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_FULL_NAME),FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aAuthorFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_CHAPTER : + { + static SfxItemPropertyMapEntry const aChapterFieldPropMap [] = + { + {OUString(UNO_NAME_CHAPTER_FORMAT),FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_LEVEL),FIELD_PROP_BYTE1, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aChapterFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_GET_REFERENCE : + { + static SfxItemPropertyMapEntry const aGetRefFieldPropMap [] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_REFERENCE_FIELD_PART),FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_REFERENCE_FIELD_SOURCE),FIELD_PROP_USHORT2, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SEQUENCE_NUMBER), FIELD_PROP_SHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SOURCE_NAME), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_REFERENCE_FIELD_LANGUAGE), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aGetRefFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_CONDITIONED_TEXT : + { + static SfxItemPropertyMapEntry const aConditionedTextFieldPropMap [] = + { + {OUString(UNO_NAME_CONDITION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_FALSE_CONTENT), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_CONDITION_TRUE) , FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_TRUE_CONTENT) , FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aConditionedTextFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_HIDDEN_TEXT : + { + static SfxItemPropertyMapEntry const aHiddenTextFieldPropMap [] = + { + {OUString(UNO_NAME_CONDITION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CONTENT) , FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_HIDDEN) , FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aHiddenTextFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_ANNOTATION : + { + static SfxItemPropertyMapEntry const aAnnotationFieldPropMap [] = + { + {OUString(UNO_NAME_AUTHOR), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INITIALS), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_RESOLVED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATE_TIME_VALUE), FIELD_PROP_DATE_TIME, cppu::UnoType<css::util::DateTime>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATE), FIELD_PROP_DATE, cppu::UnoType<css::util::Date>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_TEXT_RANGE), FIELD_PROP_TEXT, cppu::UnoType<css::uno::XInterface>::get(), PropertyAttribute::READONLY, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aAnnotationFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_INPUT: + { + static SfxItemPropertyMapEntry const aInputFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_HINT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_HELP), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_TOOLTIP), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aInputFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_MACRO : + { + static SfxItemPropertyMapEntry const aMacroFieldPropMap [] = + { + {OUString(UNO_NAME_HINT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_MACRO_NAME),FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_MACRO_LIBRARY),FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(),PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SCRIPT_URL),FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(),PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aMacroFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DDE : + { + static SfxItemPropertyMapEntry const aDDEFieldPropMap [] = + { + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDDEFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DROPDOWN : + { + static SfxItemPropertyMapEntry const aDropDownMap [] = + { + {OUString(UNO_NAME_ITEMS), FIELD_PROP_STRINGS, cppu::UnoType< cppu::UnoSequenceType<OUString> >::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SELITEM), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_HELP), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_TOOLTIP), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDropDownMap; + } + break; + case PROPERTY_MAP_FLDTYP_HIDDEN_PARA : + { + static SfxItemPropertyMapEntry const aHiddenParaFieldPropMap [] = + { + {OUString(UNO_NAME_CONDITION),FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_HIDDEN) , FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aHiddenParaFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOC_INFO : + { + static SfxItemPropertyMapEntry const aDocInfoFieldPropMap [] = + { + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INFO_FORMAT), FIELD_PROP_USHORT2, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INFO_TYPE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_TEMPLATE_NAME : + { + static SfxItemPropertyMapEntry const aTmplNameFieldPropMap [] = + { + {OUString(UNO_NAME_FILE_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTmplNameFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_USER_EXT : + { + static SfxItemPropertyMapEntry const aUsrExtFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_USER_DATA_TYPE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId]= aUsrExtFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_REF_PAGE_SET : + { + static SfxItemPropertyMapEntry const aRefPgSetFieldPropMap [] = + { + {OUString(UNO_NAME_OFFSET), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_ON), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aRefPgSetFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_REF_PAGE_GET : + { + static SfxItemPropertyMapEntry const aRefPgGetFieldPropMap [] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBERING_TYPE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aRefPgGetFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_JUMP_EDIT : + { + static SfxItemPropertyMapEntry const aJumpEdtFieldPropMap [] = + { + {OUString(UNO_NAME_HINT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_PLACEHOLDER), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_PLACEHOLDER_TYPE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aJumpEdtFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_SCRIPT : + { + static SfxItemPropertyMapEntry const aScriptFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SCRIPT_TYPE), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_URL_CONTENT), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aScriptFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_NEXT_SET : + { + static SfxItemPropertyMapEntry const aDBNextSetFieldPropMap [] = + { + // Note: DATA_BASE_NAME and DATA_BASE_URL + // are mapped to the same nMId, because internally we only use + // them as DataSource and it does not matter which one it is. + + {OUString(UNO_NAME_DATA_BASE_NAME) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_TABLE_NAME) , FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CONDITION) , FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_BASE_URL) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_COMMAND_TYPE), FIELD_PROP_SHORT1, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDBNextSetFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_NUM_SET : + { + static SfxItemPropertyMapEntry const aDBNumSetFieldPropMap [] = + { + // Note: DATA_BASE_NAME and DATA_BASE_URL + // are mapped to the same nMId, because internally we only use + // them as DataSource and it does not matter which one it is. + + {OUString(UNO_NAME_DATA_BASE_NAME) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_TABLE_NAME), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CONDITION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_BASE_URL) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_COMMAND_TYPE), FIELD_PROP_SHORT1, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SET_NUMBER), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDBNumSetFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_SET_NUM : + { + static SfxItemPropertyMapEntry const aDBSetNumFieldPropMap [] = + { + // Note: DATA_BASE_NAME and DATA_BASE_URL + // are mapped to the same nMId, because internally we only use + // them as DataSource and it does not matter which one it is. + + {OUString(UNO_NAME_DATA_BASE_NAME) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_TABLE_NAME) , FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_BASE_URL) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_COMMAND_TYPE), FIELD_PROP_SHORT1, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBERING_TYPE), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SET_NUMBER), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_VISIBLE), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDBSetNumFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE : + { + static SfxItemPropertyMapEntry const aDBFieldPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_FIELD_CODE), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_DATA_BASE_FORMAT),FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_VISIBLE), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDBFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_NAME : + { + static SfxItemPropertyMapEntry const aDBNameFieldPropMap [] = + { + // Note: DATA_BASE_NAME and DATA_BASE_URL + // are mapped to the same nMId, because internally we only use + // them as DataSource and it does not matter which one it is. + + {OUString(UNO_NAME_DATA_BASE_NAME) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_TABLE_NAME) , FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_BASE_URL) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_COMMAND_TYPE), FIELD_PROP_SHORT1, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_VISIBLE), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDBNameFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCSTAT: + { + static SfxItemPropertyMapEntry const aDocstatFieldPropMap [] = + { + {OUString(UNO_NAME_NUMBERING_TYPE), FIELD_PROP_USHORT2, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + // {OUString(UNO_NAME_STATISTIC_TYPE_ID),FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocstatFieldPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_AUTHOR: + { + static SfxItemPropertyMapEntry const aDocInfoAuthorPropMap [] = + { + {OUString(UNO_NAME_AUTHOR), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoAuthorPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_DATE_TIME: + { + static SfxItemPropertyMapEntry const aDocInfoDateTimePropMap [] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATE_TIME_VALUE), FIELD_PROP_DOUBLE, cppu::UnoType<double>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_IS_DATE), FIELD_PROP_BOOL2, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + {OUString(UNO_NAME_NUMBER_FORMAT),FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoDateTimePropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_EDIT_TIME : + { + static SfxItemPropertyMapEntry const aDocInfoEditTimePropMap [] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATE_TIME_VALUE), FIELD_PROP_DOUBLE, cppu::UnoType<double>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_NUMBER_FORMAT),FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoEditTimePropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_MISC: + { + static SfxItemPropertyMapEntry const aDocInfoStringContentPropMap [] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoStringContentPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_CUSTOM: + { + static SfxItemPropertyMapEntry const aDocInfoCustomPropMap [] = + { + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED_LANGUAGE), FIELD_PROP_BOOL4, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoCustomPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_REVISION : + { + static SfxItemPropertyMapEntry const aDocInfoRevisionPropMap [] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_REVISION), FIELD_PROP_USHORT1, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_FIXED), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get() , PROPERTY_NONE,0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDocInfoRevisionPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_COMBINED_CHARACTERS: + { + static SfxItemPropertyMapEntry const aCombinedCharactersPropMap[] = + { + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aCombinedCharactersPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_TABLE_FORMULA: + { + static SfxItemPropertyMapEntry const aTableFormulaPropMap[] = + { + {OUString(UNO_NAME_CURRENT_PRESENTATION), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_SHOW_FORMULA), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBER_FORMAT), FIELD_PROP_FORMAT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTableFormulaPropMap; + } + break; + case PROPERTY_MAP_FLDTYP_DUMMY_0: + { + static SfxItemPropertyMapEntry const aEmptyPropMap [] = + { + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aEmptyPropMap; + } + break; + case PROPERTY_MAP_FLDMSTR_USER : + { + static SfxItemPropertyMapEntry const aUserFieldTypePropMap[] = + { + {OUString(UNO_NAME_DEPENDENT_TEXT_FIELDS), FIELD_PROP_PROP_SEQ, cppu::UnoType< cppu::UnoSequenceType<css::text::XDependentTextField> >::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_IS_EXPRESSION), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + {OUString(UNO_NAME_VALUE), FIELD_PROP_DOUBLE, cppu::UnoType<double>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INSTANCE_NAME), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aUserFieldTypePropMap; + } + break; + case PROPERTY_MAP_FLDMSTR_DDE : + { + static SfxItemPropertyMapEntry const aDDEFieldTypePropMap[] = + { + {OUString(UNO_NAME_DDE_COMMAND_ELEMENT), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DDE_COMMAND_FILE), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DDE_COMMAND_TYPE), FIELD_PROP_SUBTYPE, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DEPENDENT_TEXT_FIELDS), FIELD_PROP_PROP_SEQ, cppu::UnoType< cppu::UnoSequenceType<css::text::XDependentTextField> >::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_IS_AUTOMATIC_UPDATE), FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INSTANCE_NAME), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_CONTENT), FIELD_PROP_PAR5, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDDEFieldTypePropMap; + } + break; + case PROPERTY_MAP_FLDMSTR_SET_EXP : + { + static SfxItemPropertyMapEntry const aSetExpFieldTypePropMap[] = + { + {OUString(UNO_NAME_CHAPTER_NUMBERING_LEVEL),FIELD_PROP_SHORT1, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DEPENDENT_TEXT_FIELDS), FIELD_PROP_PROP_SEQ, cppu::UnoType< cppu::UnoSequenceType<css::text::XDependentTextField> >::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NUMBERING_SEPARATOR), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SUB_TYPE), FIELD_PROP_SUBTYPE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INSTANCE_NAME), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aSetExpFieldTypePropMap; + } + break; + case PROPERTY_MAP_FLDMSTR_DATABASE : + { + static SfxItemPropertyMapEntry const aDBFieldTypePropMap [] = + { + // Note: DATA_BASE_NAME and DATA_BASE_URL + // are mapped to the same nMId, because internally we only use + // them as DataSource and it does not matter which one it is. + + {OUString(UNO_NAME_DATA_BASE_NAME) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_NAME), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + {OUString(UNO_NAME_DATA_TABLE_NAME), FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_COLUMN_NAME), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INSTANCE_NAME), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_DATA_BASE_URL) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DATA_COMMAND_TYPE), FIELD_PROP_SHORT1, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_DEPENDENT_TEXT_FIELDS), FIELD_PROP_PROP_SEQ, cppu::UnoType< cppu::UnoSequenceType<css::text::XDependentTextField> >::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aDBFieldTypePropMap; + } + break; + case PROPERTY_MAP_FLDMSTR_DUMMY0 : + { + static SfxItemPropertyMapEntry const aStandardFieldMasterMap[] = + { + {OUString(UNO_NAME_DEPENDENT_TEXT_FIELDS), 0, cppu::UnoType< cppu::UnoSequenceType<css::text::XDependentTextField> >::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_NAME), 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INSTANCE_NAME), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aStandardFieldMasterMap; + } + break; + case PROPERTY_MAP_FLDTYP_BIBLIOGRAPHY: + { + static SfxItemPropertyMapEntry const aBibliographyFieldMap[] = + { + {OUString(UNO_NAME_FIELDS) , FIELD_PROP_PROP_SEQ, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(),PROPERTY_NONE, 0}, + COMMON_FLDTYP_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aBibliographyFieldMap; + } + break; + case PROPERTY_MAP_FLDMSTR_BIBLIOGRAPHY: + { + static SfxItemPropertyMapEntry const aBibliographyFieldMasterMap[] = + { + {OUString(UNO_NAME_BRACKET_BEFORE) , FIELD_PROP_PAR1, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_BRACKET_AFTER) , FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_NUMBER_ENTRIES) , FIELD_PROP_BOOL1, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_SORT_BY_POSITION) , FIELD_PROP_BOOL2, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_LOCALE), FIELD_PROP_LOCALE, cppu::UnoType<css::lang::Locale>::get() , PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SORT_ALGORITHM), FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_SORT_KEYS) , FIELD_PROP_PROP_SEQ, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValues> >::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_INSTANCE_NAME), FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aBibliographyFieldMasterMap; + } + break; + case PROPERTY_MAP_TEXT : + { + static SfxItemPropertyMapEntry const aTextMap[] = + { + REDLINE_NODE_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTextMap; + } + break; + case PROPERTY_MAP_MAILMERGE : + { + static SfxItemPropertyMapEntry const aMailMergeMap[] = + { + { OUString(UNO_NAME_SELECTION), WID_SELECTION, cppu::UnoType< cppu::UnoSequenceType<css::uno::Any> >::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_RESULT_SET), WID_RESULT_SET, cppu::UnoType<css::sdbc::XResultSet>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CONNECTION), WID_CONNECTION, cppu::UnoType<css::sdbc::XConnection>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_MODEL), WID_MODEL, cppu::UnoType<css::frame::XModel>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_DATA_SOURCE_NAME), WID_DATA_SOURCE_NAME, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DAD_COMMAND), WID_DATA_COMMAND, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_FILTER), WID_FILTER, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DOCUMENT_URL), WID_DOCUMENT_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_OUTPUT_URL), WID_OUTPUT_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DAD_COMMAND_TYPE), WID_DATA_COMMAND_TYPE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_OUTPUT_TYPE), WID_OUTPUT_TYPE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_ESCAPE_PROCESSING), WID_ESCAPE_PROCESSING, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SINGLE_PRINT_JOBS), WID_SINGLE_PRINT_JOBS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_FILE_NAME_FROM_COLUMN), WID_FILE_NAME_FROM_COLUMN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_FILE_NAME_PREFIX), WID_FILE_NAME_PREFIX, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SUBJECT), WID_MAIL_SUBJECT, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_ADDRESS_FROM_COLUMN), WID_ADDRESS_FROM_COLUMN, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEND_AS_HTML), WID_SEND_AS_HTML, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEND_AS_ATTACHMENT), WID_SEND_AS_ATTACHMENT, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_MAIL_BODY), WID_MAIL_BODY, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_ATTACHMENT_NAME), WID_ATTACHMENT_NAME, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_ATTACHMENT_FILTER), WID_ATTACHMENT_FILTER, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PRINT_OPTIONS), WID_PRINT_OPTIONS, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SAVE_AS_SINGLE_FILE), WID_SAVE_AS_SINGLE_FILE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SAVE_FILTER), WID_SAVE_FILTER, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SAVE_FILTER_OPTIONS), WID_SAVE_FILTER_OPTIONS, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SAVE_FILTER_DATA), WID_SAVE_FILTER_DATA, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_COPIES_TO), WID_COPIES_TO, cppu::UnoType< cppu::UnoSequenceType<OUString> >::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_BLIND_COPIES_TO), WID_BLIND_COPIES_TO, cppu::UnoType< cppu::UnoSequenceType<OUString> >::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IN_SERVER_PASSWORD), WID_IN_SERVER_PASSWORD, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_OUT_SERVER_PASSWORD), WID_OUT_SERVER_PASSWORD, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aMailMergeMap; + } + break; + case PROPERTY_MAP_TEXT_VIEW : + { + static SfxItemPropertyMapEntry pTextViewMap[] = + { + {OUString(UNO_NAME_PAGE_COUNT), WID_PAGE_COUNT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_LINE_COUNT), WID_LINE_COUNT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_IS_CONSTANT_SPELLCHECK), WID_IS_CONSTANT_SPELLCHECK, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + {OUString(UNO_NAME_IS_HIDE_SPELL_MARKS), WID_IS_HIDE_SPELL_MARKS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, // deprecated #i91949 + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = pTextViewMap; + } + break; + case PROPERTY_MAP_CHART2_DATA_SEQUENCE : + { + static SfxItemPropertyMapEntry const aChart2DataSequenceMap[] = + { + {OUString(UNO_NAME_ROLE), 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aChart2DataSequenceMap; + } + break; + case PROPERTY_MAP_METAFIELD: + { + static SfxItemPropertyMapEntry const aMetaFieldMap[] = + { + { OUString(UNO_NAME_NUMBER_FORMAT), 0, + cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_IS_FIXED_LANGUAGE), 0, + cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aMetaFieldMap; + } + break; + case PROPERTY_MAP_TABLE_STYLE: + { + static SfxItemPropertyMapEntry const aTableStyleMap[] = + { + { OUString(UNO_NAME_TABLE_FIRST_ROW_END_COLUMN), 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_TABLE_FIRST_ROW_START_COLUMN), 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_TABLE_LAST_ROW_END_COLUMN), 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_TABLE_LAST_ROW_START_COLUMN), 0, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aTableStyleMap; + } + break; + case PROPERTY_MAP_CELL_STYLE: + { + static SfxItemPropertyMapEntry const aCellStyleMap[] = + { + // SvxBrushItem + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0 }, + // SvxBoxItem + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, LEFT_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, RIGHT_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, TOP_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, BOTTOM_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + // SwFormatVertOrient + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_VERTORIENT_ORIENT }, + // SvxFrameDirectionItem + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + // SvNumberformat + { OUString(UNO_NAME_NUMBER_FORMAT), RES_BOXATR_FORMAT, cppu::UnoType<sal_Int32>::get(),PropertyAttribute::MAYBEVOID, 0 }, + // SvxAdjustItem + { OUString(UNO_NAME_PARA_ADJUST), RES_PARATR_ADJUST, cppu::UnoType<sal_Int16>::get(),PropertyAttribute::MAYBEVOID, MID_PARA_ADJUST }, + // SvxColorItem + { OUString(UNO_NAME_CHAR_COLOR), RES_CHRATR_COLOR, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0 }, + // SvxShadowedItem + { OUString(UNO_NAME_CHAR_SHADOWED), RES_CHRATR_SHADOWED, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + // SvxContouredItem + { OUString(UNO_NAME_CHAR_CONTOURED), RES_CHRATR_CONTOUR, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + // SvxCrossedOutItem + { OUString(UNO_NAME_CHAR_STRIKEOUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<sal_Int16>::get(),PropertyAttribute::MAYBEVOID, MID_CROSS_OUT }, + // SvxUnderlineItem + { OUString(UNO_NAME_CHAR_UNDERLINE), RES_CHRATR_UNDERLINE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE }, + { OUString(UNO_NAME_CHAR_UNDERLINE_COLOR), RES_CHRATR_UNDERLINE,cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR }, + { OUString(UNO_NAME_CHAR_UNDERLINE_HAS_COLOR), RES_CHRATR_UNDERLINE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR }, + // standard font + // SvxFontHeightItem + { OUString(UNO_NAME_CHAR_HEIGHT), RES_CHRATR_FONTSIZE, cppu::UnoType<float>::get(),PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS }, + // SvxWeightItem + { OUString(UNO_NAME_CHAR_WEIGHT), RES_CHRATR_WEIGHT, cppu::UnoType<float>::get(),PropertyAttribute::MAYBEVOID, MID_WEIGHT }, + // SvxPostureItem + { OUString(UNO_NAME_CHAR_POSTURE), RES_CHRATR_POSTURE, cppu::UnoType<css::awt::FontSlant>::get(),PropertyAttribute::MAYBEVOID, MID_POSTURE }, + // SvxFontItem + { OUString(UNO_NAME_CHAR_FONT_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, + { OUString(UNO_NAME_CHAR_FONT_FAMILY), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, + { OUString(UNO_NAME_CHAR_FONT_PITCH), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, + // cjk font + { OUString(UNO_NAME_CHAR_HEIGHT_ASIAN), RES_CHRATR_CJK_FONTSIZE, cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_WEIGHT_ASIAN), RES_CHRATR_CJK_WEIGHT, cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_WEIGHT }, + { OUString(UNO_NAME_CHAR_POSTURE_ASIAN), RES_CHRATR_CJK_POSTURE, cppu::UnoType<css::awt::FontSlant>::get(), PropertyAttribute::MAYBEVOID, MID_POSTURE }, + { OUString(UNO_NAME_CHAR_FONT_NAME_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, + { OUString(UNO_NAME_CHAR_FONT_FAMILY_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, + { OUString(UNO_NAME_CHAR_FONT_PITCH_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, + // ctl font + { OUString(UNO_NAME_CHAR_HEIGHT_COMPLEX), RES_CHRATR_CTL_FONTSIZE, cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_WEIGHT_COMPLEX), RES_CHRATR_CTL_WEIGHT, cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_WEIGHT }, + { OUString(UNO_NAME_CHAR_POSTURE_COMPLEX), RES_CHRATR_CTL_POSTURE, cppu::UnoType<css::awt::FontSlant>::get(), PropertyAttribute::MAYBEVOID, MID_POSTURE }, + { OUString(UNO_NAME_CHAR_FONT_NAME_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, + { OUString(UNO_NAME_CHAR_FONT_FAMILY_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, + { OUString(UNO_NAME_CHAR_FONT_PITCH_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + m_aMapEntriesArr[nPropertyId] = aCellStyleMap; + } + break; + + default: + OSL_FAIL( "unexpected property map ID" ); + } + } + return m_aMapEntriesArr[nPropertyId]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unomap1.cxx b/sw/source/core/unocore/unomap1.cxx new file mode 100644 index 000000000..909f183bb --- /dev/null +++ b/sw/source/core/unocore/unomap1.cxx @@ -0,0 +1,1665 @@ +/* -*- 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 <hintids.hxx> + +#include <svx/svxids.hrc> +#include <svx/unomid.hxx> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/Gradient.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <com/sun/star/drawing/ColorMode.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/Hatch.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <com/sun/star/drawing/RectanglePoint.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/style/BreakType.hpp> +#include <com/sun/star/style/DropCapFormat.hpp> +#include <com/sun/star/style/GraphicLocation.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/PageStyleLayout.hpp> +#include <com/sun/star/style/TabStop.hpp> +#include <com/sun/star/table/BorderLine.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <com/sun/star/table/TableBorder.hpp> +#include <com/sun/star/table/TableBorder2.hpp> +#include <com/sun/star/table/TableBorderDistances.hpp> +#include <com/sun/star/table/XCell.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> +#include <com/sun/star/text/SectionFileLink.hpp> +#include <com/sun/star/text/TableColumnSeparator.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XDocumentIndex.hpp> +#include <com/sun/star/text/XDocumentIndexMark.hpp> +#include <com/sun/star/text/XFootnote.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/text/XTextSection.hpp> +#include <com/sun/star/text/XTextTable.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unomid.h> +#include <cmdid.h> +#include <editeng/colritem.hxx> +#include <editeng/memberids.h> +#include <editeng/unoprnms.hxx> +#include <svl/itemprop.hxx> +#include <svx/xdef.hxx> +#include "unomapproperties.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +SwUnoPropertyMapProvider aSwMapProvider; + +SwUnoPropertyMapProvider::SwUnoPropertyMapProvider() +{ + for( sal_uInt16 i = 0; i < PROPERTY_MAP_END; i++ ) + { + m_aMapEntriesArr[i] = nullptr; + m_aPropertySetArr[i] = nullptr; + } +} + +SwUnoPropertyMapProvider::~SwUnoPropertyMapProvider() +{ +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetTextCursorPropertyMap() +{ + static SfxItemPropertyMapEntry const aCharAndParaMap_Impl[] = + { + COMPLETE_TEXT_CURSOR_MAP + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aCharAndParaMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetAccessibilityTextAttrPropertyMap() +{ + static SfxItemPropertyMapEntry const aAccessibilityTextAttrMap_Impl[] = + { + COMMON_ACCESSIBILITY_TEXT_ATTRIBUTE + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aAccessibilityTextAttrMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetParagraphPropertyMap() +{ + static SfxItemPropertyMapEntry const aParagraphMap_Impl[] = + { + COMMON_CRSR_PARA_PROPERTIES_2 + TABSTOPS_MAP_ENTRY + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(UNO_NAME_CHAR_STYLE_NAME), RES_TXTATR_CHARFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_CHAR_STYLE_NAMES), FN_UNO_CHARFMT_SEQUENCE, cppu::UnoType< cppu::UnoSequenceType<OUString> >::get(), PropertyAttribute::MAYBEVOID, 0}, + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to style import/export + // Added for paragraph backgrounds, this is for paragraph itself + FILL_PROPERTIES_SW + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aParagraphMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetAutoParaStylePropertyMap() +{ + static SfxItemPropertyMapEntry const aAutoParaStyleMap [] = + { + { OUString(UNO_NAME_PARA_STYLE_NAME), RES_FRMATR_STYLE_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_PAGE_STYLE_NAME), FN_UNO_PAGE_STYLE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_NUMBERING_IS_NUMBER), FN_UNO_IS_NUMBER, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_NUMBERING_LEVEL), FN_UNO_NUM_LEVEL, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_NUMBERING_START_VALUE), FN_UNO_NUM_START_VALUE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, + { OUString(UNO_NAME_DOCUMENT_INDEX), FN_UNO_DOCUMENT_INDEX, cppu::UnoType<css::text::XDocumentIndex>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_TEXT_TABLE), FN_UNO_TEXT_TABLE, cppu::UnoType<css::text::XTextTable>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_CELL), FN_UNO_CELL, cppu::UnoType<css::table::XCell>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_TEXT_FRAME), FN_UNO_TEXT_FRAME, cppu::UnoType<css::text::XTextFrame>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_TEXT_SECTION), FN_UNO_TEXT_SECTION, cppu::UnoType<css::text::XTextSection>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_PARA_CHAPTER_NUMBERING_LEVEL), FN_UNO_PARA_CHAPTER_NUMBERING_LEVEL,cppu::UnoType<sal_Int8>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_PARA_CONDITIONAL_STYLE_NAME), RES_FRMATR_CONDITIONAL_STYLE_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_PARA_IS_NUMBERING_RESTART), FN_NUMBER_NEWSTART, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, + // TODO add RES_PARATR_LIST_AUTOFMT? + { OUString(UNO_NAME_OUTLINE_LEVEL), RES_PARATR_OUTLINELEVEL, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0}, + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN + TABSTOPS_MAP_ENTRY + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(UNO_NAME_PARA_AUTO_STYLE_NAME), RES_AUTO_STYLE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to style import/export + // Added for paragraph backgrounds, this is for Paragraph AutoStyles + FILL_PROPERTIES_SW + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aAutoParaStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetCharStylePropertyMap() +{ + static SfxItemPropertyMapEntry const aCharStyleMap [] = + { + { OUString(UNO_NAME_CHAR_AUTO_KERNING), RES_CHRATR_AUTOKERN , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_BACK_TRANSPARENT), RES_CHRATR_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_CHAR_BACK_COLOR), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_CHAR_HIGHLIGHT), RES_CHRATR_HIGHLIGHT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_BACK_COLOR }, + { OUString(UNO_NAME_CHAR_CASE_MAP), RES_CHRATR_CASEMAP, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_COLOR), RES_CHRATR_COLOR, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_TRANSPARENCE), RES_CHRATR_COLOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_COLOR_ALPHA}, + { OUString(UNO_NAME_CHAR_STRIKEOUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_CROSS_OUT}, + { OUString(UNO_NAME_CHAR_CROSSED_OUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_ESCAPEMENT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ESC }, + { OUString(UNO_NAME_CHAR_ESCAPEMENT_HEIGHT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int8>::get() , PROPERTY_NONE, MID_ESC_HEIGHT}, + { OUString(UNO_NAME_CHAR_FLASH), RES_CHRATR_BLINK , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_HIDDEN), RES_CHRATR_HIDDEN, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + STANDARD_FONT_PROPERTIES + CJK_FONT_PROPERTIES + CTL_FONT_PROPERTIES + { OUString(UNO_NAME_CHAR_UNDERLINE), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE}, + { OUString(UNO_NAME_CHAR_UNDERLINE_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR}, + { OUString(UNO_NAME_CHAR_UNDERLINE_HAS_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR}, + { OUString(UNO_NAME_CHAR_OVERLINE), RES_CHRATR_OVERLINE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE}, + { OUString(UNO_NAME_CHAR_OVERLINE_COLOR), RES_CHRATR_OVERLINE , cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR}, + { OUString(UNO_NAME_CHAR_OVERLINE_HAS_COLOR), RES_CHRATR_OVERLINE , cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR}, + { OUString(UNO_NAME_CHAR_KERNING), RES_CHRATR_KERNING , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_CHAR_NO_HYPHENATION), RES_CHRATR_NOHYPHEN , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_SHADOWED), RES_CHRATR_SHADOWED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_CONTOURED), RES_CHRATR_CONTOUR, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_WORD_MODE), RES_CHRATR_WORDLINEMODE,cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_PHYSICAL), FN_UNO_IS_PHYSICAL, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_HIDDEN), FN_UNO_HIDDEN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_STYLE_INTEROP_GRAB_BAG), FN_UNO_STYLE_INTEROP_GRAB_BAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DISPLAY_NAME), FN_UNO_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_CHAR_COMBINE_IS_ON), RES_CHRATR_TWO_LINES, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TWOLINES}, + { OUString(UNO_NAME_CHAR_COMBINE_PREFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PROPERTY_NONE, MID_START_BRACKET}, + { OUString(UNO_NAME_CHAR_COMBINE_SUFFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PROPERTY_NONE, MID_END_BRACKET}, + { OUString(UNO_NAME_CHAR_EMPHASIS), RES_CHRATR_EMPHASIS_MARK, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_EMPHASIS}, + PROP_DIFF_FONTHEIGHT + { OUString(UNO_NAME_CHAR_ROTATION), RES_CHRATR_ROTATE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ROTATE }, + { OUString(UNO_NAME_CHAR_ROTATION_IS_FIT_TO_LINE), RES_CHRATR_ROTATE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FITTOLINE }, + { OUString(UNO_NAME_CHAR_SCALE_WIDTH), RES_CHRATR_SCALEW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_CHAR_RELIEF), RES_CHRATR_RELIEF, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_RELIEF }, + { OUString(UNO_NAME_CHAR_LEFT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_RIGHT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_TOP_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, BOTTOM_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_LEFT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_RIGHT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_TOP_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_SHADOW_FORMAT), RES_CHRATR_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aCharStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetAutoCharStylePropertyMap() +{ + // same as PROPERTY_MAP_TEXTPORTION_EXTENSIONS + static SfxItemPropertyMapEntry const aAutoCharStyleMap [] = + { + { OUString(UNO_NAME_CHAR_AUTO_KERNING), RES_CHRATR_AUTOKERN , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_BACK_TRANSPARENT), RES_CHRATR_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_CHAR_BACK_COLOR), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_CHAR_HIGHLIGHT), RES_CHRATR_HIGHLIGHT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_BACK_COLOR }, + { OUString(UNO_NAME_CHAR_CASE_MAP), RES_CHRATR_CASEMAP, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_COLOR), RES_CHRATR_COLOR, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_TRANSPARENCE), RES_CHRATR_COLOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_COLOR_ALPHA}, + { OUString(UNO_NAME_CHAR_STRIKEOUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_CROSS_OUT}, + { OUString(UNO_NAME_CHAR_CROSSED_OUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_ESCAPEMENT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ESC }, + { OUString(UNO_NAME_CHAR_ESCAPEMENT_HEIGHT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int8>::get() , PROPERTY_NONE, MID_ESC_HEIGHT}, + { OUString(UNO_NAME_CHAR_FLASH), RES_CHRATR_BLINK , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_HIDDEN), RES_CHRATR_HIDDEN, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + STANDARD_FONT_PROPERTIES + CJK_FONT_PROPERTIES + CTL_FONT_PROPERTIES + { OUString(UNO_NAME_CHAR_UNDERLINE), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE}, + { OUString(UNO_NAME_CHAR_UNDERLINE_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR}, + { OUString(UNO_NAME_CHAR_UNDERLINE_HAS_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR}, + { OUString(UNO_NAME_CHAR_OVERLINE), RES_CHRATR_OVERLINE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE}, + { OUString(UNO_NAME_CHAR_OVERLINE_COLOR), RES_CHRATR_OVERLINE , cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR}, + { OUString(UNO_NAME_CHAR_OVERLINE_HAS_COLOR), RES_CHRATR_OVERLINE , cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR}, + { OUString(UNO_NAME_CHAR_KERNING), RES_CHRATR_KERNING , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_CHAR_NO_HYPHENATION), RES_CHRATR_NOHYPHEN , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_SHADOWED), RES_CHRATR_SHADOWED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_CONTOURED), RES_CHRATR_CONTOUR, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_WORD_MODE), RES_CHRATR_WORDLINEMODE,cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_TEXT_USER_DEFINED_ATTRIBUTES), RES_TXTATR_UNKNOWN_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_PHYSICAL), FN_UNO_IS_PHYSICAL, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_DISPLAY_NAME), FN_UNO_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_CHAR_COMBINE_IS_ON), RES_CHRATR_TWO_LINES, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TWOLINES}, + { OUString(UNO_NAME_CHAR_COMBINE_PREFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PROPERTY_NONE, MID_START_BRACKET}, + { OUString(UNO_NAME_CHAR_COMBINE_SUFFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PROPERTY_NONE, MID_END_BRACKET}, + { OUString(UNO_NAME_CHAR_EMPHASIS), RES_CHRATR_EMPHASIS_MARK, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_EMPHASIS}, + { OUString(UNO_NAME_CHAR_ROTATION), RES_CHRATR_ROTATE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ROTATE }, + { OUString(UNO_NAME_CHAR_ROTATION_IS_FIT_TO_LINE), RES_CHRATR_ROTATE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FITTOLINE }, + { OUString(UNO_NAME_CHAR_SCALE_WIDTH), RES_CHRATR_SCALEW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_CHAR_RELIEF), RES_CHRATR_RELIEF, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_RELIEF }, + { OUString(UNO_NAME_CHAR_AUTO_STYLE_NAME), RES_TXTATR_AUTOFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_CHAR_SHADING_VALUE), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_SHADING_VALUE }, + { OUString(UNO_NAME_CHAR_LEFT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_RIGHT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_TOP_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, BOTTOM_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_LEFT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_RIGHT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_TOP_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_CHAR_SHADOW_FORMAT), RES_CHRATR_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aAutoCharStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetParaStylePropertyMap() +{ + static SfxItemPropertyMapEntry const aParaStyleMap [] = + { + COMMON_PARA_STYLE_PROPERTIES + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to style import/export + // Added for paragraph backgrounds, this is for Paragraph Styles + FILL_PROPERTIES_SW + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aParaStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetConditionalParaStylePropertyMap() +{ + static SfxItemPropertyMapEntry const aParaStyleMap [] = + { + COMMON_PARA_STYLE_PROPERTIES + { OUString(UNO_NAME_PARA_STYLE_CONDITIONS), FN_UNO_PARA_STYLE_CONDITIONS, cppu::UnoType< cppu::UnoSequenceType<css::beans::NamedValue> >::get(), PropertyAttribute::MAYBEVOID, 0}, + + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to style import/export + // Added for paragraph backgrounds, this is for Paragraph Styles + FILL_PROPERTIES_SW + + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aParaStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetFrameStylePropertyMap() +{ + static SfxItemPropertyMapEntry const aFrameStyleMap [] = + { + { OUString(UNO_NAME_ANCHOR_PAGE_NO), RES_ANCHOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ANCHOR_PAGENUM }, + { OUString(UNO_NAME_ANCHOR_TYPE), RES_ANCHOR, cppu::UnoType<css::text::TextContentAnchorType>::get(), PROPERTY_NONE, MID_ANCHOR_ANCHORTYPE}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_COLOR_R_G_B), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR_R_G_B}, + { OUString(UNO_NAME_BACK_COLOR_TRANSPARENCY), RES_BACKGROUND, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE ,MID_BACK_COLOR_TRANSPARENCY}, + { OUString(UNO_NAME_FRAME_INTEROP_GRAB_BAG), RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, + // { OUString(UNO_NAME_CHAIN_NEXT_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_CHAIN_NEXTNAME}, + // { OUString(UNO_NAME_CHAIN_PREV_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_CHAIN_PREVNAME}, + /*not impl*/ { OUString(UNO_NAME_CLIENT_MAP), RES_URL, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_URL_CLIENTMAP }, + { OUString(UNO_NAME_CONTENT_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_PROTECT_CONTENT }, + { OUString(UNO_NAME_EDIT_IN_READONLY), RES_EDIT_IN_READONLY, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + // #i50322# - add missing map entry for transparency of graphic background + { OUString(UNO_NAME_BACK_GRAPHIC_TRANSPARENCY), RES_BACKGROUND, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENCY}, + { OUString(UNO_NAME_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_HORI_ORIENT), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_HORIORIENT_ORIENT }, + { OUString(UNO_NAME_HORI_ORIENT_POSITION), RES_HORI_ORIENT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_HORIORIENT_POSITION|CONVERT_TWIPS }, + { OUString(UNO_NAME_HORI_ORIENT_RELATION), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_HORIORIENT_RELATION }, + { OUString(UNO_NAME_HYPER_LINK_U_R_L), RES_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_URL_URL}, + { OUString(UNO_NAME_HYPER_LINK_TARGET), RES_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_URL_TARGET}, + { OUString(UNO_NAME_HYPER_LINK_NAME), RES_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_URL_HYPERLINKNAME }, + { OUString(UNO_NAME_OPAQUE), RES_OPAQUE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PAGE_TOGGLE), RES_HORI_ORIENT, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_HORIORIENT_PAGETOGGLE }, + { OUString(UNO_NAME_POSITION_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_PROTECT_POSITION}, + { OUString(UNO_NAME_PRINT), RES_PRINT, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_WIDTH), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH|CONVERT_TWIPS }, + { OUString(UNO_NAME_HEIGHT), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_FRMSIZE_HEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_RELATIVE_HEIGHT), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT }, + { OUString(UNO_NAME_RELATIVE_HEIGHT_RELATION), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT_RELATION }, + { OUString(UNO_NAME_RELATIVE_WIDTH), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH }, + { OUString(UNO_NAME_RELATIVE_WIDTH_RELATION), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH_RELATION }, + { OUString(UNO_NAME_SIZE_TYPE), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_SIZE_TYPE }, + { OUString(UNO_NAME_WIDTH_TYPE), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH_TYPE }, + { OUString(UNO_NAME_SIZE), RES_FRM_SIZE, cppu::UnoType<css::awt::Size>::get(), PROPERTY_NONE, MID_FRMSIZE_SIZE|CONVERT_TWIPS}, + { OUString(UNO_NAME_IS_SYNC_WIDTH_TO_HEIGHT), RES_FRM_SIZE, cppu::UnoType<bool>::get() , PROPERTY_NONE, MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT }, + { OUString(UNO_NAME_IS_SYNC_HEIGHT_TO_WIDTH), RES_FRM_SIZE, cppu::UnoType<bool>::get() , PROPERTY_NONE, MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH }, + // { OUString(UNO_NAME_WIDTH), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH }, + { OUString(UNO_NAME_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_SHADOW_TRANSPARENCE), RES_SHADOW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_SHADOW_TRANSPARENCE}, + { OUString(UNO_NAME_SERVER_MAP), RES_URL, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_URL_SERVERMAP }, + { OUString(UNO_NAME_SIZE_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_PROTECT_SIZE }, + // We keep Surround, as we delivered it with 5.1, although it's identical to text::WrapTextMode + { OUString(UNO_NAME_SURROUND), RES_SURROUND, cppu::UnoType<css::text::WrapTextMode>::get(), PROPERTY_NONE, MID_SURROUND_SURROUNDTYPE }, + { OUString(UNO_NAME_TEXT_WRAP), RES_SURROUND, cppu::UnoType<css::text::WrapTextMode>::get(), PROPERTY_NONE, MID_SURROUND_SURROUNDTYPE }, + { OUString(UNO_NAME_SURROUND_ANCHORONLY), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_ANCHORONLY }, + { OUString(UNO_NAME_SURROUND_CONTOUR), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUR }, + { OUString(UNO_NAME_CONTOUR_OUTSIDE), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUROUTSIDE }, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_UP_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_LO_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_ORIENT }, + { OUString(UNO_NAME_VERT_ORIENT_POSITION), RES_VERT_ORIENT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_VERTORIENT_POSITION|CONVERT_TWIPS }, + { OUString(UNO_NAME_VERT_ORIENT_RELATION), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_RELATION }, + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_PHYSICAL), FN_UNO_IS_PHYSICAL, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_IS_AUTO_UPDATE), FN_UNO_IS_AUTO_UPDATE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DISPLAY_NAME), FN_UNO_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + // #i18732# + { OUString(UNO_NAME_IS_FOLLOWING_TEXT_FLOW), RES_FOLLOW_TEXT_FLOW, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FOLLOW_TEXT_FLOW}, + // #i28701# + { OUString(UNO_NAME_WRAP_INFLUENCE_ON_POSITION), RES_WRAP_INFLUENCE_ON_OBJPOS, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, MID_WRAP_INFLUENCE}, + { OUString(UNO_NAME_ALLOW_OVERLAP), RES_WRAP_INFLUENCE_ON_OBJPOS, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_ALLOW_OVERLAP}, + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_HIDDEN), FN_UNO_HIDDEN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_VERT_ADJUST), RES_TEXT_VERT_ADJUST, cppu::UnoType<css::drawing::TextVerticalAdjust>::get(), PROPERTY_NONE ,0}, + + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to style import/export + FILL_PROPERTIES_SW + + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aFrameStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPageStylePropertyMap() +{ + static SfxItemPropertyMapEntry const aPageStyleMap [] = + { + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_SHADOW_TRANSPARENCE), RES_SHADOW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_SHADOW_TRANSPARENCE}, + + //UUU use real WhichIDs for Header, no longer use extra-defined WhichIDs which make handling harder as needed. + // The implementation will decide if these are part of Header/Footer or PageStyle depending on the SlotName, + // more precisely on the first characters. Thus it is necessary that these are 'Header' for the Header slots + { OUString(UNO_NAME_HEADER_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_HEADER_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_HEADER_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_HEADER_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_HEADER_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_HEADER_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_HEADER_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_HEADER_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_HEADER_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_HEADER_BODY_DISTANCE), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_LO_MARGIN|CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT), SID_ATTR_PAGE_DYNAMIC, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_HEADER_IS_SHARED), SID_ATTR_PAGE_SHARED, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_HEADER_HEIGHT), SID_ATTR_PAGE_SIZE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_SIZE_HEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_HEADER_IS_ON), SID_ATTR_PAGE_ON, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_HEADER_DYNAMIC_SPACING), RES_HEADER_FOOTER_EAT_SPACING, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID ,0 }, + + + { OUString(UNO_NAME_FIRST_IS_SHARED), SID_ATTR_PAGE_SHARED_FIRST, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + + //UUU use real WhichIDs for Footer, see Header (above) for more infos + { OUString(UNO_NAME_FOOTER_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_FOOTER_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_FOOTER_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_FOOTER_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_FOOTER_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_FOOTER_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_FOOTER_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_FOOTER_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_FOOTER_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, + { OUString(UNO_NAME_FOOTER_BODY_DISTANCE), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_UP_MARGIN|CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_IS_DYNAMIC_HEIGHT), SID_ATTR_PAGE_DYNAMIC, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_FOOTER_IS_SHARED), SID_ATTR_PAGE_SHARED, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_FOOTER_HEIGHT), SID_ATTR_PAGE_SIZE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_SIZE_HEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTER_IS_ON), SID_ATTR_PAGE_ON, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 }, + { OUString(UNO_NAME_FOOTER_DYNAMIC_SPACING), RES_HEADER_FOOTER_EAT_SPACING, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID ,0 }, + + { OUString(UNO_NAME_IS_LANDSCAPE), SID_ATTR_PAGE, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_PAGE_ORIENTATION }, + { OUString(UNO_NAME_NUMBERING_TYPE), SID_ATTR_PAGE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_PAGE_NUMTYPE }, + { OUString(UNO_NAME_PAGE_STYLE_LAYOUT), SID_ATTR_PAGE, cppu::UnoType<css::style::PageStyleLayout>::get(), PROPERTY_NONE ,MID_PAGE_LAYOUT }, + { OUString(UNO_NAME_PRINTER_PAPER_TRAY), RES_PAPER_BIN, cppu::UnoType<OUString>::get(), PROPERTY_NONE , 0 }, +// { OUString(UNO_NAME_REGISTER_MODE_ACTIVE), SID_SWREGISTER_MODE, cppu::UnoType<bool>::get(), PROPERTY_NONE , 0 }, + { OUString(UNO_NAME_REGISTER_PARAGRAPH_STYLE), SID_SWREGISTER_COLLECTION, cppu::UnoType<OUString>::get(), PROPERTY_NONE , 0 }, + { OUString(UNO_NAME_SIZE), SID_ATTR_PAGE_SIZE, cppu::UnoType<css::awt::Size>::get(), PROPERTY_NONE, MID_SIZE_SIZE|CONVERT_TWIPS}, + { OUString(UNO_NAME_WIDTH), SID_ATTR_PAGE_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_SIZE_WIDTH|CONVERT_TWIPS}, + { OUString(UNO_NAME_HEIGHT), SID_ATTR_PAGE_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_SIZE_HEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_TEXT_VERT_ADJUST), RES_TEXT_VERT_ADJUST, cppu::UnoType<css::drawing::TextVerticalAdjust>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_UP_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_LO_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_HEADER_TEXT), FN_UNO_HEADER, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_HEADER_TEXT_LEFT), FN_UNO_HEADER_LEFT, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_HEADER_TEXT_RIGHT), FN_UNO_HEADER_RIGHT, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_HEADER_TEXT_FIRST), FN_UNO_HEADER_FIRST, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_FOOTER_TEXT), FN_UNO_FOOTER, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_FOOTER_TEXT_LEFT), FN_UNO_FOOTER_LEFT, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_FOOTER_TEXT_RIGHT), FN_UNO_FOOTER_RIGHT, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_FOOTER_TEXT_FIRST), FN_UNO_FOOTER_FIRST, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_FOLLOW_STYLE), FN_UNO_FOLLOW_STYLE, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_PHYSICAL), FN_UNO_IS_PHYSICAL, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_DISPLAY_NAME), FN_UNO_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_FOOTNOTE_HEIGHT), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE , MID_FTN_HEIGHT|CONVERT_TWIPS}, + { OUString(UNO_NAME_FOOTNOTE_LINE_WEIGHT), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_LINE_WEIGHT|CONVERT_TWIPS}, + { OUString(UNO_NAME_FOOTNOTE_LINE_COLOR), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE , MID_LINE_COLOR}, + { OUString(UNO_NAME_FOOTNOTE_LINE_STYLE), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE , MID_FTN_LINE_STYLE}, + { OUString(UNO_NAME_FOOTNOTE_LINE_RELATIVE_WIDTH), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE , MID_LINE_RELWIDTH }, + { OUString(UNO_NAME_FOOTNOTE_LINE_ADJUST), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_LINE_ADJUST }, + { OUString(UNO_NAME_FOOTNOTE_LINE_TEXT_DISTANCE), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE , MID_LINE_TEXT_DIST |CONVERT_TWIPS }, + { OUString(UNO_NAME_FOOTNOTE_LINE_DISTANCE), FN_PARAM_FTN_INFO, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE , MID_LINE_FOOTNOTE_DIST|CONVERT_TWIPS}, + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + // writing grid + { OUString(UNO_NAME_GRID_COLOR), RES_TEXTGRID, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_GRID_COLOR}, + { OUString(UNO_NAME_GRID_LINES), RES_TEXTGRID, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_GRID_LINES}, + { OUString(UNO_NAME_GRID_BASE_HEIGHT), RES_TEXTGRID, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_GRID_BASEHEIGHT|CONVERT_TWIPS}, + { OUString(UNO_NAME_GRID_RUBY_HEIGHT), RES_TEXTGRID, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_GRID_RUBYHEIGHT|CONVERT_TWIPS}, + { OUString(UNO_NAME_GRID_MODE), RES_TEXTGRID, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_GRID_TYPE}, + { OUString(UNO_NAME_GRID_RUBY_BELOW), RES_TEXTGRID, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_GRID_RUBY_BELOW}, + { OUString(UNO_NAME_GRID_PRINT), RES_TEXTGRID, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_GRID_PRINT}, + { OUString(UNO_NAME_GRID_DISPLAY), RES_TEXTGRID, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_GRID_DISPLAY}, + { OUString(UNO_NAME_GRID_BASE_WIDTH), RES_TEXTGRID, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_GRID_BASEWIDTH|CONVERT_TWIPS}, + { OUString(UNO_NAME_GRID_SNAP_TO_CHARS), RES_TEXTGRID, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_GRID_SNAPTOCHARS}, + { OUString(UNO_NAME_GRID_STANDARD_PAGE_MODE), RES_TEXTGRID, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_GRID_STANDARD_MODE}, + { OUString(UNO_NAME_HIDDEN), FN_UNO_HIDDEN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to style import/export + FILL_PROPERTIES_SW + + // Added DrawingLayer FillStyle Properties for Header. These need an own unique name, + // but reuse the same WhichIDs as the regular fill. The implementation will decide to which + // group of fill properties it belongs based on the start of the name (was already done in + // the implementation partially), thus all SlotNames *have* to start with 'Header' + { OUString(UNO_NAME_HEADER_FILLBMP_LOGICAL_SIZE), XATTR_FILLBMP_SIZELOG, cppu::UnoType<bool>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_OFFSET_X), XATTR_FILLBMP_TILEOFFSETX, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_OFFSET_Y), XATTR_FILLBMP_TILEOFFSETY, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_POSITION_OFFSET_X), XATTR_FILLBMP_POSOFFSETX, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_POSITION_OFFSET_Y), XATTR_FILLBMP_POSOFFSETY, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_RECTANGLE_POINT), XATTR_FILLBMP_POS, cppu::UnoType<css::drawing::RectanglePoint>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_SIZE_X), XATTR_FILLBMP_SIZEX, cppu::UnoType<sal_Int32>::get() , 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { OUString(UNO_NAME_HEADER_FILLBMP_SIZE_Y), XATTR_FILLBMP_SIZEY, cppu::UnoType<sal_Int32>::get() , 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { OUString(UNO_NAME_HEADER_FILLBMP_STRETCH), XATTR_FILLBMP_STRETCH, cppu::UnoType<bool>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_TILE), XATTR_FILLBMP_TILE, cppu::UnoType<bool>::get() , 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBMP_MODE), OWN_ATTR_FILLBMP_MODE, cppu::UnoType<css::drawing::BitmapMode>::get(), 0, 0}, + { OUString(UNO_NAME_HEADER_FILLCOLOR), XATTR_FILLCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBACKGROUND), XATTR_FILLBACKGROUND, cppu::UnoType<bool>::get(), 0, 0}, + { OUString(UNO_NAME_HEADER_FILLBITMAP), XATTR_FILLBITMAP, cppu::UnoType<css::awt::XBitmap>::get(), 0, MID_BITMAP}, + { OUString(UNO_NAME_HEADER_FILLBITMAPNAME), XATTR_FILLBITMAP, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_HEADER_FILLGRADIENTSTEPCOUNT), XATTR_GRADIENTSTEPCOUNT, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_HEADER_FILLGRADIENT), XATTR_FILLGRADIENT, cppu::UnoType<css::awt::Gradient>::get(), 0, MID_FILLGRADIENT}, + { OUString(UNO_NAME_HEADER_FILLGRADIENTNAME), XATTR_FILLGRADIENT, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_HEADER_FILLHATCH), XATTR_FILLHATCH, cppu::UnoType<css::drawing::Hatch>::get(), 0, MID_FILLHATCH}, + { OUString(UNO_NAME_HEADER_FILLHATCHNAME), XATTR_FILLHATCH, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_HEADER_FILLSTYLE), XATTR_FILLSTYLE, cppu::UnoType<css::drawing::FillStyle>::get(), 0, 0}, + { OUString(UNO_NAME_HEADER_FILL_TRANSPARENCE), XATTR_FILLTRANSPARENCE, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_HEADER_FILLTRANSPARENCEGRADIENT), XATTR_FILLFLOATTRANSPARENCE, cppu::UnoType<css::awt::Gradient>::get(), 0, MID_FILLGRADIENT}, + { OUString(UNO_NAME_HEADER_FILLTRANSPARENCEGRADIENTNAME), XATTR_FILLFLOATTRANSPARENCE, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_HEADER_FILLCOLOR_2), XATTR_SECONDARYFILLCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0}, + + // Added DrawingLayer FillStyle Properties for Footer, similar as for Header (see there) + { OUString(UNO_NAME_FOOTER_FILLBMP_LOGICAL_SIZE), XATTR_FILLBMP_SIZELOG, cppu::UnoType<bool>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_OFFSET_X), XATTR_FILLBMP_TILEOFFSETX, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_OFFSET_Y), XATTR_FILLBMP_TILEOFFSETY, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_POSITION_OFFSET_X), XATTR_FILLBMP_POSOFFSETX, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_POSITION_OFFSET_Y), XATTR_FILLBMP_POSOFFSETY, cppu::UnoType<sal_Int32>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_RECTANGLE_POINT), XATTR_FILLBMP_POS, cppu::UnoType<css::drawing::RectanglePoint>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_SIZE_X), XATTR_FILLBMP_SIZEX, cppu::UnoType<sal_Int32>::get() , 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { OUString(UNO_NAME_FOOTER_FILLBMP_SIZE_Y), XATTR_FILLBMP_SIZEY, cppu::UnoType<sal_Int32>::get() , 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { OUString(UNO_NAME_FOOTER_FILLBMP_STRETCH), XATTR_FILLBMP_STRETCH, cppu::UnoType<bool>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_TILE), XATTR_FILLBMP_TILE, cppu::UnoType<bool>::get() , 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBMP_MODE), OWN_ATTR_FILLBMP_MODE, cppu::UnoType<css::drawing::BitmapMode>::get(), 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLCOLOR), XATTR_FILLCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBACKGROUND), XATTR_FILLBACKGROUND, cppu::UnoType<bool>::get(), 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLBITMAP), XATTR_FILLBITMAP, cppu::UnoType<css::awt::XBitmap>::get(), 0, MID_BITMAP}, + { OUString(UNO_NAME_FOOTER_FILLBITMAPNAME), XATTR_FILLBITMAP, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_FOOTER_FILLGRADIENTSTEPCOUNT), XATTR_GRADIENTSTEPCOUNT, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLGRADIENT), XATTR_FILLGRADIENT, cppu::UnoType<css::awt::Gradient>::get(), 0, MID_FILLGRADIENT}, + { OUString(UNO_NAME_FOOTER_FILLGRADIENTNAME), XATTR_FILLGRADIENT, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_FOOTER_FILLHATCH), XATTR_FILLHATCH, cppu::UnoType<css::drawing::Hatch>::get(), 0, MID_FILLHATCH}, + { OUString(UNO_NAME_FOOTER_FILLHATCHNAME), XATTR_FILLHATCH, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_FOOTER_FILLSTYLE), XATTR_FILLSTYLE, cppu::UnoType<css::drawing::FillStyle>::get(), 0, 0}, + { OUString(UNO_NAME_FOOTER_FILL_TRANSPARENCE), XATTR_FILLTRANSPARENCE, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_FOOTER_FILLTRANSPARENCEGRADIENT), XATTR_FILLFLOATTRANSPARENCE, cppu::UnoType<css::awt::Gradient>::get(), 0, MID_FILLGRADIENT}, + { OUString(UNO_NAME_FOOTER_FILLTRANSPARENCEGRADIENTNAME), XATTR_FILLFLOATTRANSPARENCE, cppu::UnoType<OUString>::get(), 0, MID_NAME }, + { OUString(UNO_NAME_FOOTER_FILLCOLOR_2), XATTR_SECONDARYFILLCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0}, + + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aPageStyleMap; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetTablePropertyMap() +{ + static SfxItemPropertyMapEntry const aTablePropertyMap_Impl[] = + { + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE,MID_BACK_COLOR }, + { OUString(UNO_NAME_BREAK_TYPE), RES_BREAK, cppu::UnoType<css::style::BreakType>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_HORI_ORIENT), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_HORIORIENT_ORIENT }, + { OUString(UNO_NAME_KEEP_TOGETHER), RES_KEEP, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SPLIT), RES_LAYOUT_SPLIT, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PAGE_NUMBER_OFFSET), RES_PAGEDESC, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_PAGEDESC_PAGENUMOFFSET}, + { OUString(UNO_NAME_PAGE_DESC_NAME), RES_PAGEDESC, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0xbf}, + { OUString(UNO_NAME_RELATIVE_WIDTH), FN_TABLE_RELATIVE_WIDTH,cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0xbf }, + { OUString(UNO_NAME_REPEAT_HEADLINE), FN_TABLE_HEADLINE_REPEAT,cppu::UnoType<bool>::get(), PROPERTY_NONE, 0xbf}, + { OUString(UNO_NAME_HEADER_ROW_COUNT), FN_TABLE_HEADLINE_COUNT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0xbf}, + { OUString(UNO_NAME_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SHADOW_TRANSPARENCE), RES_SHADOW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_SHADOW_TRANSPARENCE}, + { OUString(UNO_NAME_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_UP_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_LO_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_WIDTH), FN_TABLE_WIDTH, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, 0xbf}, + { OUString(UNO_NAME_IS_WIDTH_RELATIVE), FN_TABLE_IS_RELATIVE_WIDTH, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0xbf}, + { OUString(UNO_NAME_CHART_ROW_AS_LABEL), FN_UNO_RANGE_ROW_LABEL, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHART_COLUMN_AS_LABEL), FN_UNO_RANGE_COL_LABEL, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TABLE_BORDER), FN_UNO_TABLE_BORDER, cppu::UnoType<css::table::TableBorder>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, + { OUString(UNO_NAME_TABLE_BORDER2), FN_UNO_TABLE_BORDER2, cppu::UnoType<css::table::TableBorder2>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, + { OUString(UNO_NAME_TABLE_BORDER_DISTANCES), FN_UNO_TABLE_BORDER_DISTANCES, cppu::UnoType<css::table::TableBorderDistances>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, + { OUString(UNO_NAME_TABLE_COLUMN_SEPARATORS), FN_UNO_TABLE_COLUMN_SEPARATORS, cppu::UnoType< cppu::UnoSequenceType<css::text::TableColumnSeparator> >::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_TABLE_COLUMN_RELATIVE_SUM), FN_UNO_TABLE_COLUMN_RELATIVE_SUM, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::READONLY, 0 }, + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(UNO_LINK_DISPLAY_NAME), FN_PARAM_LINK_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0xbf}, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_TEXT_SECTION), FN_UNO_TEXT_SECTION, cppu::UnoType<css::text::XTextSection>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_TABLE_NAME), FN_UNO_TABLE_NAME, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_PAGE_STYLE_NAME), RES_PAGEDESC, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TABLE_TEMPLATE_NAME), FN_UNO_TABLE_TEMPLATE_NAME, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + // #i29550# + { OUString(UNO_NAME_COLLAPSING_BORDERS), RES_COLLAPSING_BORDERS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + REDLINE_NODE_PROPERTIES + { OUString(UNO_NAME_TABLE_INTEROP_GRAB_BAG), RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aTablePropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetRangePropertyMap() +{ + static SfxItemPropertyMapEntry const aRangePropertyMap_Impl[] = + { + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN_01 + TABSTOPS_MAP_ENTRY + { OUString(UNO_NAME_BACK_COLOR), FN_UNO_TABLE_CELL_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), FN_UNO_TABLE_CELL_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_TRANSPARENT), FN_UNO_TABLE_CELL_BACKGROUND, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_NUMBER_FORMAT), RES_BOXATR_FORMAT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID ,0 }, + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_ORIENT }, + { OUString(UNO_NAME_CHART_ROW_AS_LABEL), FN_UNO_RANGE_ROW_LABEL, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_CHART_COLUMN_AS_LABEL), FN_UNO_RANGE_COL_LABEL, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aRangePropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetSectionPropertyMap() +{ + static SfxItemPropertyMapEntry const aSectionPropertyMap_Impl[] = + { + { OUString(UNO_NAME_CONDITION), WID_SECT_CONDITION, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DDE_COMMAND_FILE), WID_SECT_DDE_TYPE, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DDE_COMMAND_TYPE), WID_SECT_DDE_FILE, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DDE_COMMAND_ELEMENT), WID_SECT_DDE_ELEMENT, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_AUTOMATIC_UPDATE), WID_SECT_DDE_AUTOUPDATE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_FILE_LINK), WID_SECT_LINK , cppu::UnoType<css::text::SectionFileLink>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_VISIBLE), WID_SECT_VISIBLE , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_PROTECTED), WID_SECT_PROTECTED, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_EDIT_IN_READONLY), WID_SECT_EDIT_IN_READONLY, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LINK_REGION), WID_SECT_REGION , cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_LINK_DISPLAY_NAME), FN_PARAM_LINK_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0xbf}, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_FOOTNOTE_IS_COLLECT_AT_TEXT_END), RES_FTN_AT_TXTEND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_COLLECT }, + { OUString(UNO_NAME_FOOTNOTE_IS_RESTART_NUMBERING), RES_FTN_AT_TXTEND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_RESTART_NUM }, + { OUString(UNO_NAME_FOOTNOTE_RESTART_NUMBERING_AT), RES_FTN_AT_TXTEND, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_NUM_START_AT}, + { OUString(UNO_NAME_FOOTNOTE_IS_OWN_NUMBERING), RES_FTN_AT_TXTEND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_OWN_NUM }, + { OUString(UNO_NAME_FOOTNOTE_NUMBERING_TYPE), RES_FTN_AT_TXTEND, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_NUM_TYPE }, + { OUString(UNO_NAME_FOOTNOTE_NUMBERING_PREFIX), RES_FTN_AT_TXTEND, cppu::UnoType<OUString>::get() , PROPERTY_NONE, MID_PREFIX }, + { OUString(UNO_NAME_FOOTNOTE_NUMBERING_SUFFIX), RES_FTN_AT_TXTEND, cppu::UnoType<OUString>::get() , PROPERTY_NONE, MID_SUFFIX }, + { OUString(UNO_NAME_ENDNOTE_IS_COLLECT_AT_TEXT_END), RES_END_AT_TXTEND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_COLLECT }, + { OUString(UNO_NAME_ENDNOTE_IS_RESTART_NUMBERING), RES_END_AT_TXTEND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_RESTART_NUM }, + { OUString(UNO_NAME_ENDNOTE_RESTART_NUMBERING_AT), RES_END_AT_TXTEND, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_NUM_START_AT }, + { OUString(UNO_NAME_ENDNOTE_IS_OWN_NUMBERING), RES_END_AT_TXTEND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_OWN_NUM }, + { OUString(UNO_NAME_ENDNOTE_NUMBERING_TYPE), RES_END_AT_TXTEND, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_NUM_TYPE }, + { OUString(UNO_NAME_ENDNOTE_NUMBERING_PREFIX), RES_END_AT_TXTEND, cppu::UnoType<OUString>::get() , PROPERTY_NONE, MID_PREFIX }, + { OUString(UNO_NAME_ENDNOTE_NUMBERING_SUFFIX), RES_END_AT_TXTEND, cppu::UnoType<OUString>::get() , PROPERTY_NONE, MID_SUFFIX }, + { OUString(UNO_NAME_DOCUMENT_INDEX), WID_SECT_DOCUMENT_INDEX, cppu::UnoType<css::text::XDocumentIndex>::get(), PropertyAttribute::READONLY | PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_GLOBAL_DOCUMENT_SECTION), WID_SECT_IS_GLOBAL_DOC_SECTION, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0 }, + { OUString(UNO_NAME_PROTECTION_KEY), WID_SECT_PASSWORD, cppu::UnoType< cppu::UnoSequenceType<sal_Int8> >::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_DONT_BALANCE_TEXT_COLUMNS), RES_COLUMNBALANCE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + COMMON_TEXT_CONTENT_PROPERTIES + REDLINE_NODE_PROPERTIES + { OUString(UNO_NAME_IS_CURRENTLY_VISIBLE), WID_SECT_CURRENTLY_VISIBLE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_SECT_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_L_MARGIN|CONVERT_TWIPS}, + { OUString(UNO_NAME_SECT_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_R_MARGIN|CONVERT_TWIPS}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aSectionPropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetFramePropertyMap() +{ + static SfxItemPropertyMapEntry const aFramePropertyMap_Impl[] = + { // + // TODO: We should consider completely removing SvxBrushItem() stuff + // add support for XATTR_FILL_FIRST, XATTR_FILL_LAST + // COMMON_FRAME_PROPERTIES currently hosts the RES_BACKGROUND entries from SvxBrushItem + COMMON_FRAME_PROPERTIES + REDLINE_NODE_PROPERTIES + { OUString(UNO_NAME_CHAIN_NEXT_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_NEXTNAME}, + { OUString(UNO_NAME_CHAIN_PREV_NAME), RES_CHAIN, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_CHAIN_PREVNAME}, + /*not impl*/ { OUString(UNO_NAME_CLIENT_MAP), RES_URL, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_URL_CLIENTMAP }, + { OUString(UNO_NAME_EDIT_IN_READONLY), RES_EDIT_IN_READONLY, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_COLUMNS), RES_COL, cppu::UnoType<css::text::XTextColumns>::get(), PROPERTY_NONE, MID_COLUMNS}, + //next elements are part of the service description + { OUString(UNO_NAME_FRAME_HEIGHT_ABSOLUTE), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_FRMSIZE_HEIGHT|CONVERT_TWIPS }, + { OUString(UNO_NAME_FRAME_HEIGHT_PERCENT), RES_FRM_SIZE, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT }, + { OUString(UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT), RES_FRM_SIZE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FRMSIZE_IS_AUTO_HEIGHT }, + { OUString(UNO_NAME_FRAME_WIDTH_ABSOLUTE), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_FRMSIZE_WIDTH|CONVERT_TWIPS }, + { OUString(UNO_NAME_FRAME_WIDTH_PERCENT), RES_FRM_SIZE, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH }, + { OUString(UNO_NAME_SIZE_TYPE), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_SIZE_TYPE }, + { OUString(UNO_NAME_WIDTH_TYPE), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH_TYPE }, + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to FlyFrame import/export + FILL_PROPERTIES_SW + + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aFramePropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetGraphicPropertyMap() +{ + static SfxItemPropertyMapEntry const aGraphicPropertyMap_Impl[] = + { + // TODO: We should consider completely removing SvxBrushItem() stuff + // add support for XATTR_FILL_FIRST, XATTR_FILL_LAST + // COMMON_FRAME_PROPERTIES currently hosts the RES_BACKGROUND entries from SvxBrushItem + COMMON_FRAME_PROPERTIES + { OUString(UNO_NAME_SURROUND_CONTOUR), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUR }, + { OUString(UNO_NAME_CONTOUR_OUTSIDE), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUROUTSIDE }, + { OUString(UNO_NAME_GRAPHIC_CROP), RES_GRFATR_CROPGRF, cppu::UnoType<css::text::GraphicCrop>::get(), PROPERTY_NONE, CONVERT_TWIPS }, + { OUString(UNO_NAME_HORI_MIRRORED_ON_EVEN_PAGES), RES_GRFATR_MIRRORGRF, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_MIRROR_HORZ_EVEN_PAGES }, + { OUString(UNO_NAME_HORI_MIRRORED_ON_ODD_PAGES), RES_GRFATR_MIRRORGRF, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_MIRROR_HORZ_ODD_PAGES }, + { OUString(UNO_NAME_VERT_MIRRORED), RES_GRFATR_MIRRORGRF, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_MIRROR_VERT }, + { OUString(UNO_NAME_REPLACEMENT_GRAPHIC), FN_UNO_REPLACEMENT_GRAPHIC, cppu::UnoType<css::graphic::XGraphic>::get(), 0, 0 }, + { OUString(UNO_NAME_GRAPHIC_FILTER), FN_UNO_GRAPHIC_FILTER, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString(UNO_NAME_GRAPHIC), FN_UNO_GRAPHIC, cppu::UnoType<css::graphic::XGraphic>::get(), 0, 0 }, + { OUString(UNO_NAME_GRAPHIC_URL), FN_UNO_GRAPHIC_URL, cppu::UnoType<css::uno::Any>::get(), 0, 0 }, + { OUString(UNO_NAME_TRANSFORMED_GRAPHIC), FN_UNO_TRANSFORMED_GRAPHIC, cppu::UnoType<css::graphic::XGraphic>::get(), 0, 0 }, + { OUString(UNO_NAME_ACTUAL_SIZE), FN_UNO_ACTUAL_SIZE, cppu::UnoType<css::awt::Size>::get(), PropertyAttribute::READONLY, CONVERT_TWIPS}, + { OUString(UNO_NAME_CONTOUR_POLY_POLYGON), FN_PARAM_CONTOUR_PP, cppu::UnoType<css::drawing::PointSequenceSequence>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_PIXEL_CONTOUR), FN_UNO_IS_PIXEL_CONTOUR, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_IS_AUTOMATIC_CONTOUR), FN_UNO_IS_AUTOMATIC_CONTOUR , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_GRAPHIC_ROTATION), RES_GRFATR_ROTATION, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_ADJUST_LUMINANCE), RES_GRFATR_LUMINANCE, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_ADJUST_CONTRAST), RES_GRFATR_CONTRAST, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_ADJUST_RED), RES_GRFATR_CHANNELR, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_ADJUST_GREEN), RES_GRFATR_CHANNELG, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_ADJUST_BLUE), RES_GRFATR_CHANNELB, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_GAMMA), RES_GRFATR_GAMMA, cppu::UnoType<double>::get(), 0, 0}, + { OUString(UNO_NAME_GRAPHIC_IS_INVERTED), RES_GRFATR_INVERT, cppu::UnoType<bool>::get(), 0, 0}, + { OUString(UNO_NAME_TRANSPARENCY), RES_GRFATR_TRANSPARENCY, cppu::UnoType<sal_Int16>::get(), 0, 0}, + { OUString(UNO_NAME_GRAPHIC_COLOR_MODE), RES_GRFATR_DRAWMODE, cppu::UnoType<css::drawing::ColorMode>::get(), 0, 0}, + + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to Writer GraphicObject import/export + FILL_PROPERTIES_SW + + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aGraphicPropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetEmbeddedPropertyMap() +{ + static SfxItemPropertyMapEntry const aEmbeddedPropertyMap_Impl[] = + { // + // TODO: We should consider completely removing SvxBrushItem() stuff + // add support for XATTR_FILL_FIRST, XATTR_FILL_LAST + // COMMON_FRAME_PROPERTIES currently hosts the RES_BACKGROUND entries from SvxBrushItem + COMMON_FRAME_PROPERTIES + { OUString(UNO_NAME_SURROUND_CONTOUR), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUR }, + { OUString(UNO_NAME_CONTOUR_OUTSIDE), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_CONTOUROUTSIDE}, + { OUString(UNO_NAME_CONTOUR_POLY_POLYGON), FN_PARAM_CONTOUR_PP, cppu::UnoType<css::drawing::PointSequenceSequence>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_IS_PIXEL_CONTOUR), FN_UNO_IS_PIXEL_CONTOUR, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_IS_AUTOMATIC_CONTOUR), FN_UNO_IS_AUTOMATIC_CONTOUR , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_CLSID), FN_UNO_CLSID, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_STREAM_NAME), FN_UNO_STREAM_NAME, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_MODEL), FN_UNO_MODEL, cppu::UnoType<css::frame::XModel>::get(), PropertyAttribute::READONLY|PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_GRAPHIC_URL), FN_UNO_REPLACEMENT_GRAPHIC_URL, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_GRAPHIC), FN_UNO_REPLACEMENT_GRAPHIC, cppu::UnoType<css::graphic::XGraphic>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_COMPONENT),FN_UNO_COMPONENT, cppu::UnoType<css::lang::XComponent>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_EMBEDDED_OBJECT),FN_EMBEDDED_OBJECT, cppu::UnoType<css::embed::XEmbeddedObject>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DRAW_ASPECT),FN_UNO_DRAW_ASPECT, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_VISIBLE_AREA_WIDTH),FN_UNO_VISIBLE_AREA_WIDTH, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_VISIBLE_AREA_HEIGHT),FN_UNO_VISIBLE_AREA_HEIGHT, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + // added FillProperties for SW, same as FILL_PROPERTIES in svx + // but need own defines in Writer due to later association of strings + // and uno types (see loop at end of this method and definition of SW_PROP_NMID) + // This entry is for adding that properties to OLE/EmbeddedObject import/export + FILL_PROPERTIES_SW + + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aEmbeddedPropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetIndexMarkPropertyMap() +{ + static SfxItemPropertyMapEntry const aIdxMarkMap_Impl[] = + { + { OUString(UNO_NAME_ALTERNATIVE_TEXT), WID_ALT_TEXT, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PRIMARY_KEY), WID_PRIMARY_KEY, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SECONDARY_KEY), WID_SECONDARY_KEY, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_TEXT_READING), WID_TEXT_READING, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PRIMARY_KEY_READING), WID_PRIMARY_KEY_READING, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SECONDARY_KEY_READING), WID_SECONDARY_KEY_READING, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_MAIN_ENTRY), WID_MAIN_ENTRY, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aIdxMarkMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetContentMarkPropertyMap() +{ + static SfxItemPropertyMapEntry const aContentMarkMap_Impl[] = + { + { OUString(UNO_NAME_ALTERNATIVE_TEXT), WID_ALT_TEXT, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL), WID_LEVEL , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aContentMarkMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetUserMarkPropertyMap() +{ + static SfxItemPropertyMapEntry const aUserMarkMap_Impl[] = + { + { OUString(UNO_NAME_ALTERNATIVE_TEXT), WID_ALT_TEXT, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_LEVEL), WID_LEVEL , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_USER_INDEX_NAME), WID_USER_IDX_NAME, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0}, + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aUserMarkMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetTextTableCursorPropertyMap() +{ + // The PropertySet corresponds to the Range without Chart properties + static SfxItemPropertyMapEntry const aTableCursorPropertyMap_Impl [] = + { + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN_01 + TABSTOPS_MAP_ENTRY + + // attributes from PROPERTY_MAP_TABLE_CELL: + { OUString(UNO_NAME_BACK_COLOR), FN_UNO_TABLE_CELL_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE , MID_BACK_COLOR }, + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), FN_UNO_TABLE_CELL_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, + { OUString(UNO_NAME_NUMBER_FORMAT), RES_BOXATR_FORMAT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID ,0 }, + { OUString(UNO_NAME_BACK_TRANSPARENT), FN_UNO_TABLE_CELL_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE , MID_GRAPHIC_TRANSPARENT }, + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, + { OUString(UNO_NAME_TEXT_SECTION), FN_UNO_TEXT_SECTION, cppu::UnoType<css::text::XTextSection>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + { OUString(UNO_NAME_IS_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), 0, MID_PROTECT_CONTENT}, + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_ORIENT }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aTableCursorPropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetBookmarkPropertyMap() +{ + static SfxItemPropertyMapEntry const aBookmarkPropertyMap_Impl [] = + { + { OUString(UNO_LINK_DISPLAY_NAME), FN_PARAM_LINK_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0xbf}, + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(UNO_NAME_BOOKMARK_HIDDEN), FN_BOOKMARK_HIDDEN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString(UNO_NAME_BOOKMARK_CONDITION), FN_BOOKMARK_CONDITION, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aBookmarkPropertyMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetParagraphExtensionsPropertyMap() +{ + static SfxItemPropertyMapEntry const aParagraphExtensionsMap_Impl[] = + { + COMMON_TEXT_CONTENT_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aParagraphExtensionsMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetTextPortionExtensionPropertyMap() +{ + static SfxItemPropertyMapEntry const aTextPortionExtensionMap_Impl[] = + { + COMPLETE_TEXT_CURSOR_MAP + {OUString(UNO_NAME_BOOKMARK), FN_UNO_BOOKMARK, cppu::UnoType<css::text::XTextContent>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + {OUString(UNO_NAME_CONTROL_CHARACTER), FN_UNO_CONTROL_CHARACTER, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, MID_HYPHEN_MIN_LEAD }, + {OUString(UNO_NAME_IS_COLLAPSED), FN_UNO_IS_COLLAPSED, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 }, + {OUString(UNO_NAME_IS_START), FN_UNO_IS_START, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 }, + //REDLINE_PROPERTIES + {OUString(UNO_NAME_TEXT_PORTION_TYPE), FN_UNO_TEXT_PORTION_TYPE, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_META), FN_UNO_META, cppu::UnoType<css::text::XTextContent>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aTextPortionExtensionMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetFootnotePropertyMap() +{ + static SfxItemPropertyMapEntry const aFootnoteMap_Impl[] = + { + {OUString(UNO_NAME_REFERENCE_ID), 0, cppu::UnoType<sal_Int16>::get(),PropertyAttribute::READONLY|PropertyAttribute::MAYBEVOID, 0}, + COMMON_TEXT_CONTENT_PROPERTIES + REDLINE_NODE_PROPERTIES + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aFootnoteMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetRedlinePropertyMap() +{ + static SfxItemPropertyMapEntry const aRedlineMap_Impl[] = + { + REDLINE_PROPERTIES + REDLINE_NODE_PROPERTIES + {OUString(UNO_NAME_REDLINE_START), 0, cppu::UnoType<css::uno::XInterface>::get(), PropertyAttribute::READONLY, 0}, + {OUString(UNO_NAME_REDLINE_END), 0, cppu::UnoType<css::uno::XInterface>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aRedlineMap_Impl; +} + +SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetTextDefaultPropertyMap() +{ + static SfxItemPropertyMapEntry aTextDefaultMap_Impl[] = + { + { OUString(UNO_NAME_TAB_STOP_DISTANCE), RES_PARATR_TABSTOP, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_STD_TAB | CONVERT_TWIPS}, + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN + COMMON_HYPERLINK_PROPERTIES + { OUString(UNO_NAME_CHAR_STYLE_NAME), RES_TXTATR_CHARFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + { OUString(UNO_NAME_IS_SPLIT_ALLOWED), RES_ROW_SPLIT, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0}, + // #i29550# + { OUString(UNO_NAME_COLLAPSING_BORDERS), RES_COLLAPSING_BORDERS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + + //text grid enhancement for better CJK support. 2007-04-01 + //just export the default page mode property, other properties are not handled in this version + { OUString(UNO_NAME_GRID_STANDARD_PAGE_MODE), RES_TEXTGRID, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_GRID_STANDARD_MODE}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aTextDefaultMap_Impl; +} + +const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetRedlinePortionPropertyMap() +{ + static SfxItemPropertyMapEntry const aRedlinePortionMap_Impl[] = + { + COMPLETE_TEXT_CURSOR_MAP + {OUString(UNO_NAME_BOOKMARK), FN_UNO_BOOKMARK, cppu::UnoType<css::text::XTextContent>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, + {OUString(UNO_NAME_CONTROL_CHARACTER), FN_UNO_CONTROL_CHARACTER, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, MID_HYPHEN_MIN_LEAD }, + {OUString(UNO_NAME_IS_COLLAPSED), FN_UNO_IS_COLLAPSED, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 }, + {OUString(UNO_NAME_IS_START), FN_UNO_IS_START, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 }, + REDLINE_PROPERTIES + {OUString(UNO_NAME_TEXT_PORTION_TYPE), FN_UNO_TEXT_PORTION_TYPE, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + + return aRedlinePortionMap_Impl; +} + +const SfxItemPropertySet* SwUnoPropertyMapProvider::GetPropertySet( sal_uInt16 nPropertyId) +{ + if( !m_aPropertySetArr[nPropertyId] ) + { + const SfxItemPropertyMapEntry* pEntries = GetPropertyMapEntries(nPropertyId); + switch( nPropertyId ) + { + case PROPERTY_MAP_TEXT_CURSOR: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_CURSOR(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_CURSOR; + } + break; + case PROPERTY_MAP_CHAR_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_CHAR_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_CHAR_STYLE; + } + break; + case PROPERTY_MAP_PARA_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_PARA_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_PARA_STYLE; + } + break; + case PROPERTY_MAP_FRAME_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_FRAME_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FRAME_STYLE; + } + break; + case PROPERTY_MAP_PAGE_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_PAGE_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_PAGE_STYLE; + } + break; + case PROPERTY_MAP_NUM_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_NUM_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_NUM_STYLE; + } + break; + case PROPERTY_MAP_SECTION: + { + static SfxItemPropertySet aPROPERTY_MAP_SECTION(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_SECTION; + } + break; + case PROPERTY_MAP_TEXT_TABLE: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_TABLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_TABLE; + } + break; + case PROPERTY_MAP_TABLE_CELL: + { + static SfxItemPropertySet aPROPERTY_MAP_TABLE_CELL(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TABLE_CELL; + } + break; + case PROPERTY_MAP_TABLE_RANGE: + { + static SfxItemPropertySet aPROPERTY_MAP_TABLE_RANGE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TABLE_RANGE; + } + break; + case PROPERTY_MAP_TEXT_SEARCH: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_SEARCH(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_SEARCH; + } + break; + case PROPERTY_MAP_TEXT_FRAME: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_FRAME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_FRAME; + } + break; + case PROPERTY_MAP_TEXT_GRAPHIC: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_GRAPHIC(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_GRAPHIC; + } + break; + case PROPERTY_MAP_TEXT_SHAPE: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_SHAPE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_SHAPE; + } + break; + case PROPERTY_MAP_INDEX_USER: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_USER(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_USER; + } + break; + case PROPERTY_MAP_INDEX_CNTNT: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_CNTNT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_CNTNT; + } + break; + case PROPERTY_MAP_INDEX_IDX: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_IDX(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_IDX; + } + break; + case PROPERTY_MAP_USER_MARK: + { + static SfxItemPropertySet aPROPERTY_MAP_USER_MARK(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_USER_MARK; + } + break; + case PROPERTY_MAP_CNTIDX_MARK: + { + static SfxItemPropertySet aPROPERTY_MAP_CNTIDX_MARK(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_CNTIDX_MARK; + } + break; + case PROPERTY_MAP_INDEX_MARK: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_MARK(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_MARK; + } + break; + case PROPERTY_MAP_TEXT_TABLE_ROW: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_TABLE_ROW(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_TABLE_ROW; + } + break; + case PROPERTY_MAP_TEXT_SHAPE_DESCRIPTOR: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_SHAPE_DESCRIPTOR(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_SHAPE_DESCRIPTOR; + } + break; + case PROPERTY_MAP_TEXT_TABLE_CURSOR: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_TABLE_CURSOR(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_TABLE_CURSOR; + } + break; + case PROPERTY_MAP_BOOKMARK: + { + static SfxItemPropertySet aPROPERTY_MAP_BOOKMARK(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_BOOKMARK; + } + break; + case PROPERTY_MAP_PARAGRAPH_EXTENSIONS: + { + static SfxItemPropertySet aPROPERTY_MAP_PARAGRAPH_EXTENSIONS(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_PARAGRAPH_EXTENSIONS; + } + break; + case PROPERTY_MAP_INDEX_ILLUSTRATIONS: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_ILLUSTRATIONS(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_ILLUSTRATIONS; + } + break; + case PROPERTY_MAP_INDEX_OBJECTS: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_OBJECTS(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_OBJECTS; + } + break; + case PROPERTY_MAP_INDEX_TABLES: + { + static SfxItemPropertySet aPROPERTY_MAP_INDEX_TABLES(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_INDEX_TABLES; + } + break; + case PROPERTY_MAP_BIBLIOGRAPHY : + { + static SfxItemPropertySet aPROPERTY_MAP_BIBLIOGRAPHY(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_BIBLIOGRAPHY; + } + break; + case PROPERTY_MAP_TEXT_DOCUMENT: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_DOCUMENT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_DOCUMENT; + } + break; + case PROPERTY_MAP_LINK_TARGET : + { + static SfxItemPropertySet aPROPERTY_MAP_LINK_TARGET(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_LINK_TARGET; + } + break; + case PROPERTY_MAP_AUTO_TEXT_GROUP : + { + static SfxItemPropertySet aPROPERTY_MAP_AUTO_TEXT_GROUP(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_AUTO_TEXT_GROUP; + } + break; + case PROPERTY_MAP_TEXTPORTION_EXTENSIONS : + { + static SfxItemPropertySet aPROPERTY_MAP_TEXTPORTION_EXTENSIONS(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXTPORTION_EXTENSIONS; + } + break; + case PROPERTY_MAP_FOOTNOTE : + { + static SfxItemPropertySet aPROPERTY_MAP_FOOTNOTE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FOOTNOTE; + } + break; + case PROPERTY_MAP_TEXT_COLUMS : + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_COLUMS(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_COLUMS; + } + break; + case PROPERTY_MAP_PARAGRAPH : + { + static SfxItemPropertySet aPROPERTY_MAP_PARAGRAPH(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_PARAGRAPH; + } + break; + case PROPERTY_MAP_EMBEDDED_OBJECT : + { + static SfxItemPropertySet aPROPERTY_MAP_EMBEDDED_OBJECT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_EMBEDDED_OBJECT; + } + break; + case PROPERTY_MAP_REDLINE : + { + static SfxItemPropertySet aPROPERTY_MAP_REDLINE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_REDLINE; + } + break; + case PROPERTY_MAP_TEXT_DEFAULT : + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_DEFAULT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_DEFAULT; + } + break; + case PROPERTY_MAP_FLDTYP_DATETIME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DATETIME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DATETIME; + } + break; + case PROPERTY_MAP_FLDTYP_USER: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_USER(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_USER; + } + break; + case PROPERTY_MAP_FLDTYP_SET_EXP: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_SET_EXP(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_SET_EXP; + } + break; + case PROPERTY_MAP_FLDTYP_GET_EXP: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_GET_EXP(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_GET_EXP; + } + break; + case PROPERTY_MAP_FLDTYP_FILE_NAME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_FILE_NAME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_FILE_NAME; + } + break; + case PROPERTY_MAP_FLDTYP_PAGE_NUM: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_PAGE_NUM(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_PAGE_NUM; + } + break; + case PROPERTY_MAP_FLDTYP_AUTHOR: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_AUTHOR(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_AUTHOR; + } + break; + case PROPERTY_MAP_FLDTYP_CHAPTER: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_CHAPTER(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_CHAPTER; + } + break; + case PROPERTY_MAP_FLDTYP_GET_REFERENCE: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_GET_REFERENCE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_GET_REFERENCE; + } + break; + case PROPERTY_MAP_FLDTYP_CONDITIONED_TEXT: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_CONDITIONED_TEXT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_CONDITIONED_TEXT; + } + break; + case PROPERTY_MAP_FLDTYP_HIDDEN_TEXT: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_HIDDEN_TEXT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_HIDDEN_TEXT; + } + break; + case PROPERTY_MAP_FLDTYP_ANNOTATION : + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_ANNOTATION(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_ANNOTATION; + } + break; + case PROPERTY_MAP_FLDTYP_INPUT: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_INPUT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_INPUT; + } + break; + case PROPERTY_MAP_FLDTYP_MACRO: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_MACRO(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_MACRO; + } + break; + case PROPERTY_MAP_FLDTYP_DDE: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DDE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DDE; + } + break; + case PROPERTY_MAP_FLDTYP_HIDDEN_PARA: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_HIDDEN_PARA(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_HIDDEN_PARA; + } + break; + case PROPERTY_MAP_FLDTYP_DOC_INFO : + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOC_INFO(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOC_INFO; + } + break; + case PROPERTY_MAP_FLDTYP_TEMPLATE_NAME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_TEMPLATE_NAME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_TEMPLATE_NAME; + } + break; + case PROPERTY_MAP_FLDTYP_USER_EXT : + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_USER_EXT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_USER_EXT; + } + break; + case PROPERTY_MAP_FLDTYP_REF_PAGE_SET: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_REF_PAGE_SET(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_REF_PAGE_SET; + } + break; + case PROPERTY_MAP_FLDTYP_REF_PAGE_GET: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_REF_PAGE_GET(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_REF_PAGE_GET; + } + break; + case PROPERTY_MAP_FLDTYP_JUMP_EDIT: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_JUMP_EDIT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_JUMP_EDIT; + } + break; + case PROPERTY_MAP_FLDTYP_SCRIPT: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_SCRIPT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_SCRIPT; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_NEXT_SET: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DATABASE_NEXT_SET(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DATABASE_NEXT_SET; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_NUM_SET: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DATABASE_NUM_SET(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DATABASE_NUM_SET; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_SET_NUM: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DATABASE_SET_NUM(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DATABASE_SET_NUM; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DATABASE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DATABASE; + } + break; + case PROPERTY_MAP_FLDTYP_DATABASE_NAME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DATABASE_NAME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DATABASE_NAME; + } + break; + case PROPERTY_MAP_FLDTYP_DOCSTAT: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCSTAT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCSTAT; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_AUTHOR: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_AUTHOR(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_AUTHOR; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_DATE_TIME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_DATE_TIME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_DATE_TIME; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_CHANGE_DATE_TIME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_CHANGE_DATE_TIME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_CHANGE_DATE_TIME; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_CREATE_DATE_TIME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_CREATE_DATE_TIME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_CREATE_DATE_TIME; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_EDIT_TIME: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_EDIT_TIME(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_EDIT_TIME; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_MISC : + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_MISC(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_MISC; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_REVISION: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_REVISION(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_REVISION; + } + break; + case PROPERTY_MAP_FLDTYP_COMBINED_CHARACTERS: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_COMBINED_CHARACTERS(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_COMBINED_CHARACTERS; + } + break; + case PROPERTY_MAP_FLDTYP_DUMMY_0: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DUMMY_0(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DUMMY_0; + } + break; + case PROPERTY_MAP_FLDTYP_TABLE_FORMULA: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_TABLE_FORMULA(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_TABLE_FORMULA; + } + break; + case PROPERTY_MAP_FLDMSTR_USER: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDMSTR_USER(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDMSTR_USER; + } + break; + case PROPERTY_MAP_FLDMSTR_DDE: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDMSTR_DDE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDMSTR_DDE; + } + break; + case PROPERTY_MAP_FLDMSTR_SET_EXP: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDMSTR_SET_EXP(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDMSTR_SET_EXP; + } + break; + case PROPERTY_MAP_FLDMSTR_DATABASE: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDMSTR_DATABASE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDMSTR_DATABASE; + } + break; + case PROPERTY_MAP_FLDMSTR_DUMMY0: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDMSTR_DUMMY0(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDMSTR_DUMMY0; + } + break; + case PROPERTY_MAP_FLDTYP_BIBLIOGRAPHY: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_BIBLIOGRAPHY(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_BIBLIOGRAPHY; + } + break; + case PROPERTY_MAP_FLDMSTR_BIBLIOGRAPHY: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDMSTR_BIBLIOGRAPHY(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDMSTR_BIBLIOGRAPHY; + } + break; + case PROPERTY_MAP_TEXT: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT; + } + break; + case PROPERTY_MAP_REDLINE_PORTION: + { + static SfxItemPropertySet aPROPERTY_MAP_REDLINE_PORTION(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_REDLINE_PORTION; + } + break; + case PROPERTY_MAP_MAILMERGE: + { + static SfxItemPropertySet aPROPERTY_MAP_MAILMERGE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_MAILMERGE; + } + break; + case PROPERTY_MAP_FLDTYP_DROPDOWN: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DROPDOWN(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DROPDOWN; + } + break; + case PROPERTY_MAP_CHART2_DATA_SEQUENCE: + { + static SfxItemPropertySet aPROPERTY_MAP_CHART2_DATA_SEQUENCE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_CHART2_DATA_SEQUENCE; + } + break; + case PROPERTY_MAP_TEXT_VIEW: + { + static SfxItemPropertySet aPROPERTY_MAP_TEXT_VIEW(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_VIEW; + } + break; + case PROPERTY_MAP_CONDITIONAL_PARA_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_CONDITIONAL_PARA_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_CONDITIONAL_PARA_STYLE; + } + break; + case PROPERTY_MAP_CHAR_AUTO_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_CHAR_AUTO_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_CHAR_AUTO_STYLE; + } + break; + case PROPERTY_MAP_RUBY_AUTO_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_RUBY_AUTO_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_RUBY_AUTO_STYLE; + } + break; + case PROPERTY_MAP_PARA_AUTO_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_PARA_AUTO_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_PARA_AUTO_STYLE; + } + break; + case PROPERTY_MAP_FLDTYP_DOCINFO_CUSTOM: + { + static SfxItemPropertySet aPROPERTY_MAP_FLDTYP_DOCINFO_CUSTOM(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FLDTYP_DOCINFO_CUSTOM; + } + break; + case PROPERTY_MAP_METAFIELD: + { + static SfxItemPropertySet aPROPERTY_MAP_METAFIELD(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_METAFIELD; + } + break; + case PROPERTY_MAP_TABLE_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_TABLE_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TABLE_STYLE; + } + break; + case PROPERTY_MAP_CELL_STYLE: + { + static SfxItemPropertySet aPROPERTY_MAP_CELL_STYLE(pEntries); + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_CELL_STYLE; + } + break; + } + } + return m_aPropertySetArr[nPropertyId]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unomapproperties.hxx b/sw/source/core/unocore/unomapproperties.hxx new file mode 100644 index 000000000..0b211f392 --- /dev/null +++ b/sw/source/core/unocore/unomapproperties.hxx @@ -0,0 +1,529 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_INC_UNOMAPPROPERTIES_HXX +#define INCLUDED_SW_INC_UNOMAPPROPERTIES_HXX + +// These are the unomap common properties used by unomap?.cxx + +#ifndef MID_TXT_LMARGIN +#define MID_TXT_LMARGIN 11 +#endif + +#define STANDARD_FONT_PROPERTIES \ + { OUString(UNO_NAME_CHAR_HEIGHT), RES_CHRATR_FONTSIZE , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_CHAR_WEIGHT), RES_CHRATR_WEIGHT , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_WEIGHT}, \ + { OUString(UNO_NAME_CHAR_FONT_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, \ + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, \ + { OUString(UNO_NAME_CHAR_FONT_FAMILY), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, \ + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, \ + { OUString(UNO_NAME_CHAR_FONT_PITCH), RES_CHRATR_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, \ + { OUString(UNO_NAME_CHAR_POSTURE), RES_CHRATR_POSTURE , cppu::UnoType<css::awt::FontSlant>::get(), PropertyAttribute::MAYBEVOID, MID_POSTURE}, \ + { OUString(UNO_NAME_RSID), RES_CHRATR_RSID, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_LOCALE), RES_CHRATR_LANGUAGE, cppu::UnoType<css::lang::Locale>::get(), PropertyAttribute::MAYBEVOID, MID_LANG_LOCALE }, \ + { OUString(UNO_NAME_CHAR_INTEROP_GRAB_BAG), RES_CHRATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 }, \ + +#define CJK_FONT_PROPERTIES \ + { OUString(UNO_NAME_CHAR_HEIGHT_ASIAN), RES_CHRATR_CJK_FONTSIZE , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_CHAR_WEIGHT_ASIAN), RES_CHRATR_CJK_WEIGHT , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_WEIGHT}, \ + { OUString(UNO_NAME_CHAR_FONT_NAME_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, \ + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, \ + { OUString(UNO_NAME_CHAR_FONT_FAMILY_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, \ + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, \ + { OUString(UNO_NAME_CHAR_FONT_PITCH_ASIAN), RES_CHRATR_CJK_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, \ + { OUString(UNO_NAME_CHAR_POSTURE_ASIAN), RES_CHRATR_CJK_POSTURE , cppu::UnoType<css::awt::FontSlant>::get(), PropertyAttribute::MAYBEVOID, MID_POSTURE}, \ + { OUString(UNO_NAME_CHAR_LOCALE_ASIAN), RES_CHRATR_CJK_LANGUAGE , cppu::UnoType<css::lang::Locale>::get() , PropertyAttribute::MAYBEVOID, MID_LANG_LOCALE }, + +#define CTL_FONT_PROPERTIES \ + { OUString(UNO_NAME_CHAR_HEIGHT_COMPLEX), RES_CHRATR_CTL_FONTSIZE , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS},\ + { OUString(UNO_NAME_CHAR_WEIGHT_COMPLEX), RES_CHRATR_CTL_WEIGHT , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_WEIGHT}, \ + { OUString(UNO_NAME_CHAR_FONT_NAME_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, \ + { OUString(UNO_NAME_CHAR_FONT_STYLE_NAME_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_STYLE_NAME }, \ + { OUString(UNO_NAME_CHAR_FONT_FAMILY_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY }, \ + { OUString(UNO_NAME_CHAR_FONT_CHAR_SET_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_CHAR_SET }, \ + { OUString(UNO_NAME_CHAR_FONT_PITCH_COMPLEX), RES_CHRATR_CTL_FONT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_PITCH }, \ + { OUString(UNO_NAME_CHAR_POSTURE_COMPLEX), RES_CHRATR_CTL_POSTURE , cppu::UnoType<css::awt::FontSlant>::get(), PropertyAttribute::MAYBEVOID, MID_POSTURE}, \ + { OUString(UNO_NAME_CHAR_LOCALE_COMPLEX), RES_CHRATR_CTL_LANGUAGE , cppu::UnoType<css::lang::Locale>::get() , PropertyAttribute::MAYBEVOID, MID_LANG_LOCALE }, + +#define REDLINE_NODE_PROPERTIES \ + { OUString(UNO_NAME_START_REDLINE), FN_UNO_REDLINE_NODE_START , cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0xbf }, \ + { OUString(UNO_NAME_END_REDLINE), FN_UNO_REDLINE_NODE_END , cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0xbf }, + +#define REDLINE_PROPERTIES \ + {OUString(UNO_NAME_REDLINE_AUTHOR), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_REDLINE_DATE_TIME), 0, cppu::UnoType<css::util::DateTime>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_REDLINE_COMMENT), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_REDLINE_DESCRIPTION), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID | PropertyAttribute::READONLY, 0}, \ + {OUString(UNO_NAME_REDLINE_TYPE), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_REDLINE_SUCCESSOR_DATA), 0, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_REDLINE_IDENTIFIER), 0, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_IS_IN_HEADER_FOOTER), 0, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_REDLINE_TEXT), 0, cppu::UnoType<css::text::XText>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + {OUString(UNO_NAME_MERGE_LAST_PARA), 0, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, + +#define COMMON_CRSR_PARA_PROPERTIES_FN_ONLY \ + { OUString(UNO_NAME_PARA_STYLE_NAME), FN_UNO_PARA_STYLE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_PAGE_STYLE_NAME), FN_UNO_PAGE_STYLE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0}, \ + { OUString(UNO_NAME_NUMBERING_IS_NUMBER), FN_UNO_IS_NUMBER, cppu::UnoType<bool>::get() , PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_NUMBERING_LEVEL), FN_UNO_NUM_LEVEL, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_NUMBERING_RULES), FN_UNO_NUM_RULES, cppu::UnoType<css::container::XIndexReplace>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, \ + { OUString(UNO_NAME_NUMBERING_START_VALUE), FN_UNO_NUM_START_VALUE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, \ + { OUString(UNO_NAME_DOCUMENT_INDEX), FN_UNO_DOCUMENT_INDEX, cppu::UnoType<css::text::XDocumentIndex>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, \ + { OUString(UNO_NAME_TEXT_TABLE), FN_UNO_TEXT_TABLE, cppu::UnoType<css::text::XTextTable>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, \ + { OUString(UNO_NAME_CELL), FN_UNO_CELL, cppu::UnoType<css::table::XCell>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, \ + { OUString(UNO_NAME_TEXT_FRAME), FN_UNO_TEXT_FRAME, cppu::UnoType<css::text::XTextFrame>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, \ + { OUString(UNO_NAME_TEXT_SECTION), FN_UNO_TEXT_SECTION, cppu::UnoType<css::text::XTextSection>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, \ + { OUString(UNO_NAME_TEXT_PARAGRAPH), FN_UNO_TEXT_PARAGRAPH, cppu::UnoType<css::text::XTextContent>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 }, \ + { OUString(UNO_NAME_PARA_CHAPTER_NUMBERING_LEVEL), FN_UNO_PARA_CHAPTER_NUMBERING_LEVEL,cppu::UnoType<sal_Int8>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_PARA_CONDITIONAL_STYLE_NAME), FN_UNO_PARA_CONDITIONAL_STYLE_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, \ + { OUString(UNO_NAME_LIST_ID), FN_UNO_LIST_ID, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_PARA_IS_NUMBERING_RESTART), FN_NUMBER_NEWSTART, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_CONTINUEING_PREVIOUS_SUB_TREE), FN_UNO_PARA_CONT_PREV_SUBTREE, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0 }, \ + { OUString(UNO_NAME_PARA_LIST_LABEL_STRING), FN_UNO_PARA_NUM_STRING, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0 }, \ + { OUString(UNO_NAME_PARA_LIST_AUTO_FORMAT), FN_UNO_PARA_NUM_AUTO_FORMAT, cppu::UnoType<cppu::UnoSequenceType<css::beans::NamedValue>>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_OUTLINE_LEVEL), RES_PARATR_OUTLINELEVEL, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0}, + +#define COMMON_HYPERLINK_PROPERTIES \ + { OUString(UNO_NAME_HYPER_LINK_U_R_L), RES_TXTATR_INETFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_URL_URL}, \ + { OUString(UNO_NAME_HYPER_LINK_TARGET), RES_TXTATR_INETFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_URL_TARGET}, \ + { OUString(UNO_NAME_HYPER_LINK_NAME), RES_TXTATR_INETFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_URL_HYPERLINKNAME }, \ + { OUString(UNO_NAME_UNVISITED_CHAR_STYLE_NAME), RES_TXTATR_INETFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_URL_UNVISITED_FMT }, \ + { OUString(UNO_NAME_VISITED_CHAR_STYLE_NAME), RES_TXTATR_INETFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID ,MID_URL_VISITED_FMT }, + +// same as COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN but without +// OUString(UNO_NAME_BREAK_TYPE) and OUString(UNO_NAME_PAGE_DESC_NAME) which can not be used +// by the SwXTextTableCursor +#define COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN_01 \ + { OUString(UNO_NAME_PARRSID), RES_PARATR_RSID, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_IS_HYPHENATION), RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_IS_HYPHEN }, \ + { OUString(UNO_NAME_PARA_HYPHENATION_NO_CAPS), RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_NO_CAPS }, \ + { OUString(UNO_NAME_PARA_HYPHENATION_MAX_LEADING_CHARS), RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_MIN_LEAD }, \ + { OUString(UNO_NAME_PARA_HYPHENATION_MAX_TRAILING_CHARS), RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_MIN_TRAIL }, \ + { OUString(UNO_NAME_PARA_HYPHENATION_MAX_HYPHENS), RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_MAX_HYPHENS }, \ + { OUString(UNO_NAME_CHAR_AUTO_KERNING), RES_CHRATR_AUTOKERN, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_BACK_COLOR), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_BACK_COLOR }, \ + { OUString(UNO_NAME_CHAR_HIGHLIGHT), RES_CHRATR_HIGHLIGHT, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_BACK_COLOR }, \ + { OUString(UNO_NAME_PARA_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_BACK_COLOR }, \ + { OUString(UNO_NAME_CHAR_CASE_MAP), RES_CHRATR_CASEMAP, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_COLOR), RES_CHRATR_COLOR, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_TRANSPARENCE), RES_CHRATR_COLOR, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_COLOR_ALPHA }, \ + { OUString(UNO_NAME_CHAR_STRIKEOUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_CROSS_OUT }, \ + { OUString(UNO_NAME_CHAR_CROSSED_OUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_CROSSED_OUT }, \ + { OUString(UNO_NAME_CHAR_ESCAPEMENT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_ESC }, \ + { OUString(UNO_NAME_CHAR_ESCAPEMENT_HEIGHT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int8>::get(), PropertyAttribute::MAYBEVOID, MID_ESC_HEIGHT }, \ + { OUString(UNO_NAME_CHAR_AUTO_ESCAPEMENT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_AUTO_ESC }, \ + { OUString(UNO_NAME_CHAR_FLASH), RES_CHRATR_BLINK, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_HIDDEN), RES_CHRATR_HIDDEN, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_UNDERLINE), RES_CHRATR_UNDERLINE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_TL_STYLE }, \ + { OUString(UNO_NAME_CHAR_UNDERLINE_COLOR), RES_CHRATR_UNDERLINE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_TL_COLOR }, \ + { OUString(UNO_NAME_CHAR_UNDERLINE_HAS_COLOR), RES_CHRATR_UNDERLINE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_TL_HASCOLOR }, \ + { OUString(UNO_NAME_CHAR_OVERLINE), RES_CHRATR_OVERLINE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_TL_STYLE }, \ + { OUString(UNO_NAME_CHAR_OVERLINE_COLOR), RES_CHRATR_OVERLINE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_TL_COLOR }, \ + { OUString(UNO_NAME_CHAR_OVERLINE_HAS_COLOR), RES_CHRATR_OVERLINE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_TL_HASCOLOR }, \ + { OUString(UNO_NAME_PARA_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_URL }, \ + { OUString(UNO_NAME_PARA_GRAPHIC), RES_BACKGROUND, cppu::UnoType<css::graphic::XGraphic>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC }, \ + { OUString(UNO_NAME_PARA_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_FILTER }, \ + { OUString(UNO_NAME_PARA_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_POSITION }, \ + { OUString(UNO_NAME_PARA_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_TXT_LMARGIN | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_PARA_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_R_MARGIN | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_PARA_IS_AUTO_FIRST_LINE_INDENT), RES_LR_SPACE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_FIRST_AUTO }, \ + { OUString(UNO_NAME_PARA_FIRST_LINE_INDENT), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_FIRST_LINE_INDENT | CONVERT_TWIPS }, \ + STANDARD_FONT_PROPERTIES \ + CJK_FONT_PROPERTIES \ + CTL_FONT_PROPERTIES \ + { OUString(UNO_NAME_CHAR_KERNING), RES_CHRATR_KERNING, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_NO_HYPHENATION), RES_CHRATR_NOHYPHEN, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_SHADOWED), RES_CHRATR_SHADOWED, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_CONTOURED), RES_CHRATR_CONTOUR, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_DROP_CAP_FORMAT), RES_PARATR_DROP, cppu::UnoType<css::style::DropCapFormat>::get(), PropertyAttribute::MAYBEVOID, MID_DROPCAP_FORMAT | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_DROP_CAP_WHOLE_WORD), RES_PARATR_DROP, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_DROPCAP_WHOLE_WORD }, \ + { OUString(UNO_NAME_DROP_CAP_CHAR_STYLE_NAME), RES_PARATR_DROP, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_DROPCAP_CHAR_STYLE_NAME }, \ + { OUString(UNO_NAME_PARA_KEEP_TOGETHER), RES_KEEP, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_SPLIT), RES_PARATR_SPLIT, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_WIDOWS), RES_PARATR_WIDOWS, cppu::UnoType<sal_Int8>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_ORPHANS), RES_PARATR_ORPHANS, cppu::UnoType<sal_Int8>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PAGE_NUMBER_OFFSET), RES_PAGEDESC, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_PAGEDESC_PAGENUMOFFSET }, \ + { OUString(UNO_NAME_PARA_ADJUST), RES_PARATR_ADJUST, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_PARA_ADJUST }, \ + { OUString(UNO_NAME_PARA_EXPAND_SINGLE_WORD), RES_PARATR_ADJUST, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_EXPAND_SINGLE }, \ + { OUString(UNO_NAME_PARA_LAST_LINE_ADJUST), RES_PARATR_ADJUST, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_LAST_LINE_ADJUST }, \ + { OUString(UNO_NAME_PARA_LINE_NUMBER_COUNT), RES_LINENUMBER, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_LINENUMBER_COUNT }, \ + { OUString(UNO_NAME_PARA_LINE_NUMBER_START_VALUE), RES_LINENUMBER, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_LINENUMBER_STARTVALUE }, \ + { OUString(UNO_NAME_PARA_LINE_SPACING), RES_PARATR_LINESPACING, cppu::UnoType<css::style::LineSpacing>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, \ + { OUString(UNO_NAME_PARA_REGISTER_MODE_ACTIVE), RES_PARATR_REGISTER, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_UP_MARGIN | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_PARA_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_LO_MARGIN | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_PARA_CONTEXT_MARGIN), RES_UL_SPACE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_CTX_MARGIN }, \ + { OUString(UNO_NAME_CHAR_BACK_TRANSPARENT), RES_CHRATR_BACKGROUND, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_TRANSPARENT }, \ + { OUString(UNO_NAME_PARA_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_GRAPHIC_TRANSPARENT }, \ + { OUString(UNO_NAME_NUMBERING_STYLE_NAME), RES_PARATR_NUMRULE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_WORD_MODE), RES_CHRATR_WORDLINEMODE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_LEFT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, LEFT_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_RIGHT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, RIGHT_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_TOP_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, TOP_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, BOTTOM_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_LEFT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, LEFT_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_RIGHT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, RIGHT_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_TOP_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, TOP_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, BOTTOM_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_SHADOW_FORMAT), RES_CHRATR_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, \ + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, LEFT_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, RIGHT_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, TOP_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), PropertyAttribute::MAYBEVOID, BOTTOM_BORDER | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, LEFT_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, RIGHT_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, TOP_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, BOTTOM_BORDER_DISTANCE | CONVERT_TWIPS }, \ + { OUString(UNO_NAME_PARA_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_TEXT_USER_DEFINED_ATTRIBUTES), RES_TXTATR_UNKNOWN_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS }, \ + { OUString(UNO_NAME_CHAR_COMBINE_IS_ON), RES_CHRATR_TWO_LINES, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_TWOLINES }, \ + { OUString(UNO_NAME_CHAR_COMBINE_PREFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_START_BRACKET }, \ + { OUString(UNO_NAME_CHAR_COMBINE_SUFFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_END_BRACKET }, \ + { OUString(UNO_NAME_CHAR_EMPHASIS), RES_CHRATR_EMPHASIS_MARK, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_EMPHASIS }, \ + { OUString(UNO_NAME_PARA_IS_HANGING_PUNCTUATION), RES_PARATR_HANGINGPUNCTUATION, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_IS_CHARACTER_DISTANCE), RES_PARATR_SCRIPTSPACE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_IS_FORBIDDEN_RULES), RES_PARATR_FORBIDDEN_RULES, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_VERT_ALIGNMENT), RES_PARATR_VERTALIGN, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_CHAR_ROTATION), RES_CHRATR_ROTATE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_ROTATE }, \ + { OUString(UNO_NAME_CHAR_ROTATION_IS_FIT_TO_LINE), RES_CHRATR_ROTATE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_FITTOLINE }, \ + { OUString(UNO_NAME_CHAR_SCALE_WIDTH), RES_CHRATR_SCALEW, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_RUBY_TEXT), RES_TXTATR_CJK_RUBY, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_TEXT }, \ + { OUString(UNO_NAME_RUBY_ADJUST), RES_TXTATR_CJK_RUBY, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_ADJUST }, \ + { OUString(UNO_NAME_RUBY_CHAR_STYLE_NAME), RES_TXTATR_CJK_RUBY, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_CHARSTYLE }, \ + { OUString(UNO_NAME_RUBY_POSITION), RES_TXTATR_CJK_RUBY, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_POSITION}, \ + { OUString(UNO_NAME_RUBY_IS_ABOVE), RES_TXTATR_CJK_RUBY, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_RUBY_ABOVE }, \ + { OUString(UNO_NAME_CHAR_RELIEF), RES_CHRATR_RELIEF, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_RELIEF }, \ + { OUString(UNO_NAME_SNAP_TO_GRID), RES_PARATR_SNAPTOGRID, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_PARA_IS_CONNECT_BORDER), RES_PARATR_CONNECT_BORDER, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, \ + { OUString(UNO_NAME_CHAR_SHADING_VALUE), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_SHADING_VALUE }, \ + { OUString(UNO_NAME_PARA_INTEROP_GRAB_BAG), RES_PARATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0 }, \ + +#define COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN \ + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN_01 \ + { OUString(UNO_NAME_BREAK_TYPE), RES_BREAK, cppu::UnoType<css::style::BreakType>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_PAGE_DESC_NAME), RES_PAGEDESC, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_PAGEDESC_PAGEDESCNAME }, + +#define TABSTOPS_MAP_ENTRY { OUString(UNO_NAME_TABSTOPS), RES_PARATR_TABSTOP, cppu::UnoType< cppu::UnoSequenceType<css::style::TabStop> >::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, + +#define COMMON_CRSR_PARA_PROPERTIES \ + COMMON_CRSR_PARA_PROPERTIES_FN_ONLY \ + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN \ + COMMON_HYPERLINK_PROPERTIES \ + { OUString(UNO_NAME_CHAR_STYLE_NAME), RES_TXTATR_CHARFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0},\ + { OUString(UNO_NAME_CHAR_STYLE_NAMES), FN_UNO_CHARFMT_SEQUENCE, cppu::UnoType< cppu::UnoSequenceType<OUString> >::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_CHAR_AUTO_STYLE_NAME), RES_TXTATR_AUTOFMT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0},\ + { OUString(UNO_NAME_PARA_AUTO_STYLE_NAME), RES_AUTO_STYLE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0}, + +#define COMMON_CRSR_PARA_PROPERTIES_2 \ + COMMON_CRSR_PARA_PROPERTIES_FN_ONLY \ + COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN + +#define COMPLETE_TEXT_CURSOR_MAP\ + COMMON_CRSR_PARA_PROPERTIES\ + { OUString(UNO_NAME_DOCUMENT_INDEX_MARK), FN_UNO_DOCUMENT_INDEX_MARK, cppu::UnoType<css::text::XDocumentIndexMark>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 },\ + { OUString(UNO_NAME_TEXT_FIELD), FN_UNO_TEXT_FIELD, cppu::UnoType<css::text::XTextField>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 },\ + { OUString(UNO_NAME_REFERENCE_MARK), FN_UNO_REFERENCE_MARK, cppu::UnoType<css::text::XTextContent>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 },\ + { OUString(UNO_NAME_FOOTNOTE), FN_UNO_FOOTNOTE, cppu::UnoType<css::text::XFootnote>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 },\ + { OUString(UNO_NAME_ENDNOTE), FN_UNO_ENDNOTE, cppu::UnoType<css::text::XFootnote>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY ,0 },\ + { OUString(UNO_NAME_HYPER_LINK_EVENTS), RES_TXTATR_INETFMT, cppu::UnoType<css::container::XNameReplace>::get(), PropertyAttribute::MAYBEVOID, MID_URL_HYPERLINKEVENTS},\ + { OUString(UNO_NAME_NESTED_TEXT_CONTENT), FN_UNO_NESTED_TEXT_CONTENT, cppu::UnoType<css::text::XTextContent>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 },\ + TABSTOPS_MAP_ENTRY + +#define BASE_INDEX_PROPERTIES_\ + { OUString(UNO_NAME_TITLE), WID_IDX_TITLE, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_NAME), WID_IDX_NAME, cppu::UnoType<OUString>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CONTENT_SECTION), WID_IDX_CONTENT_SECTION, cppu::UnoType<css::text::XTextSection>::get() , PropertyAttribute::READONLY, 0},\ + { OUString(UNO_NAME_HEADER_SECTION), WID_IDX_HEADER_SECTION, cppu::UnoType<css::text::XTextSection>::get() , PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0},\ + +#define ANCHOR_TYPES_PROPERTY { OUString(UNO_NAME_ANCHOR_TYPES), FN_UNO_ANCHOR_TYPES, cppu::UnoType< cppu::UnoSequenceType<css::text::TextContentAnchorType> >::get(),PropertyAttribute::READONLY, 0xbf}, + +// #i18732# #i28701# #i73249# +// all users of COMMON_FRAME_PROPERTIES add the new XATTR_FILL_FIRST, XATTR_FILL_LAST FillStyle, +// thus it may be possible to remove the RES_BACKGROUND entries from SvxBrushItem completely (this includes +// all using UNO_NAME_BACK_* slots) in the future +#define COMMON_FRAME_PROPERTIES \ + { OUString(UNO_NAME_ANCHOR_PAGE_NO), RES_ANCHOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ANCHOR_PAGENUM }, \ + { OUString(UNO_NAME_ANCHOR_TYPE), RES_ANCHOR, cppu::UnoType<css::text::TextContentAnchorType>::get(), PROPERTY_NONE, MID_ANCHOR_ANCHORTYPE}, \ + { OUString(UNO_NAME_ANCHOR_FRAME), RES_ANCHOR, cppu::UnoType<css::text::XTextFrame>::get(), PropertyAttribute::MAYBEVOID, MID_ANCHOR_ANCHORFRAME}, \ + ANCHOR_TYPES_PROPERTY\ + { OUString(UNO_NAME_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, \ + { OUString(UNO_NAME_BACK_COLOR_R_G_B), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR_R_G_B}, \ + { OUString(UNO_NAME_BACK_COLOR_TRANSPARENCY), RES_BACKGROUND, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE ,MID_BACK_COLOR_TRANSPARENCY}, \ + { OUString(UNO_NAME_FRAME_INTEROP_GRAB_BAG), RES_FRMATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_CONTENT_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_PROTECT_CONTENT }, \ + { OUString(UNO_NAME_FRAME_STYLE_NAME), FN_UNO_FRAME_STYLE_NAME,cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_BACK_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL }, \ + { OUString(UNO_NAME_BACK_GRAPHIC), RES_BACKGROUND, cppu::UnoType<css::graphic::XGraphic>::get(), PROPERTY_NONE, MID_GRAPHIC }, \ + { OUString(UNO_NAME_BACK_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER }, \ + { OUString(UNO_NAME_BACK_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, \ + { OUString(UNO_NAME_BACK_GRAPHIC_TRANSPARENCY), RES_BACKGROUND, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENCY}, \ + { OUString(UNO_NAME_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_L_MARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_WIDTH), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_FRMSIZE_WIDTH|CONVERT_TWIPS},\ + { OUString(UNO_NAME_HEIGHT), RES_FRM_SIZE, cppu::UnoType<sal_Int32>::get() , PROPERTY_NONE, MID_FRMSIZE_HEIGHT|CONVERT_TWIPS},\ + { OUString(UNO_NAME_HORI_ORIENT), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_HORIORIENT_ORIENT }, \ + { OUString(UNO_NAME_HORI_ORIENT_POSITION), RES_HORI_ORIENT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_HORIORIENT_POSITION|CONVERT_TWIPS }, \ + { OUString(UNO_NAME_HORI_ORIENT_RELATION), RES_HORI_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_HORIORIENT_RELATION }, \ + { OUString(UNO_NAME_HYPER_LINK_U_R_L), RES_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_URL_URL}, \ + { OUString(UNO_NAME_HYPER_LINK_TARGET), RES_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_URL_TARGET}, \ + { OUString(UNO_NAME_HYPER_LINK_NAME), RES_URL, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_URL_HYPERLINKNAME }, \ + { OUString(UNO_NAME_OPAQUE), RES_OPAQUE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_PAGE_TOGGLE), RES_HORI_ORIENT, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_HORIORIENT_PAGETOGGLE }, \ + { OUString(UNO_NAME_POSITION_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_PROTECT_POSITION}, \ + { OUString(UNO_NAME_PRINT), RES_PRINT, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_RELATIVE_HEIGHT), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT }, \ + { OUString(UNO_NAME_RELATIVE_HEIGHT_RELATION), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_HEIGHT_RELATION }, \ + { OUString(UNO_NAME_RELATIVE_WIDTH), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH }, \ + { OUString(UNO_NAME_RELATIVE_WIDTH_RELATION), RES_FRM_SIZE, cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, MID_FRMSIZE_REL_WIDTH_RELATION }, \ + { OUString(UNO_NAME_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS}, \ + { OUString(UNO_NAME_SHADOW_TRANSPARENCE), RES_SHADOW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_SHADOW_TRANSPARENCE}, \ + { OUString(UNO_NAME_IMAGE_MAP), RES_URL, cppu::UnoType<css::container::XIndexContainer>::get(), PROPERTY_NONE, MID_URL_CLIENTMAP}, \ + { OUString(UNO_NAME_SERVER_MAP), RES_URL, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_URL_SERVERMAP }, \ + { OUString(UNO_NAME_SIZE), RES_FRM_SIZE, cppu::UnoType<css::awt::Size>::get(), PROPERTY_NONE, MID_FRMSIZE_SIZE|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_SIZE_PROTECTED), RES_PROTECT, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_PROTECT_SIZE }, \ + { OUString(UNO_NAME_IS_SYNC_WIDTH_TO_HEIGHT), RES_FRM_SIZE, cppu::UnoType<bool>::get() , PROPERTY_NONE, MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT }, \ + { OUString(UNO_NAME_IS_SYNC_HEIGHT_TO_WIDTH), RES_FRM_SIZE, cppu::UnoType<bool>::get() , PROPERTY_NONE, MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH }, \ + { OUString(UNO_NAME_TEXT_WRAP), RES_SURROUND, cppu::UnoType<css::text::WrapTextMode>::get(), PROPERTY_NONE, MID_SURROUND_SURROUNDTYPE }, \ + { OUString(UNO_NAME_SURROUND), RES_SURROUND, cppu::UnoType<css::text::WrapTextMode>::get(), PROPERTY_NONE, MID_SURROUND_SURROUNDTYPE }, \ + { OUString(UNO_NAME_SURROUND_ANCHORONLY), RES_SURROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_SURROUND_ANCHORONLY }, \ + { OUString(UNO_NAME_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_UP_MARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_LO_MARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT }, \ + { OUString(UNO_NAME_VERT_ORIENT), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_ORIENT }, \ + { OUString(UNO_NAME_VERT_ORIENT_POSITION), RES_VERT_ORIENT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_VERTORIENT_POSITION|CONVERT_TWIPS }, \ + { OUString(UNO_NAME_VERT_ORIENT_RELATION), RES_VERT_ORIENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE ,MID_VERTORIENT_RELATION }, \ + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS }, \ + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BORDER_DISTANCE|CONVERT_TWIPS }, \ + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS }, \ + { OUString(UNO_LINK_DISPLAY_NAME), FN_PARAM_LINK_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0xbf}, \ + { OUString(UNO_NAME_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 },\ + { OUString(UNO_NAME_Z_ORDER), FN_UNO_Z_ORDER, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_IS_FOLLOWING_TEXT_FLOW), RES_FOLLOW_TEXT_FLOW, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FOLLOW_TEXT_FLOW}, \ + { OUString(UNO_NAME_PARENT_TEXT), FN_UNO_PARENT_TEXT, cppu::UnoType<text::XText>::get(), PropertyAttribute::MAYBEVOID | PropertyAttribute::READONLY, 0 }, \ + { OUString(UNO_NAME_WRAP_INFLUENCE_ON_POSITION), RES_WRAP_INFLUENCE_ON_OBJPOS, cppu::UnoType<sal_Int8>::get(), PROPERTY_NONE, MID_WRAP_INFLUENCE}, \ + { OUString(UNO_NAME_ALLOW_OVERLAP), RES_WRAP_INFLUENCE_ON_OBJPOS, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_ALLOW_OVERLAP}, \ + { OUString(UNO_NAME_TITLE), FN_UNO_TITLE, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_DESCRIPTION), FN_UNO_DESCRIPTION, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_LAYOUT_SIZE), WID_LAYOUT_SIZE, cppu::UnoType<css::awt::Size>::get(), PropertyAttribute::MAYBEVOID | PropertyAttribute::READONLY, 0 }, \ + { OUString(UNO_NAME_LINE_STYLE), RES_BOX, cppu::UnoType<css::drawing::LineStyle>::get(), 0, LINE_STYLE }, \ + { OUString(UNO_NAME_LINE_WIDTH), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LINE_WIDTH |CONVERT_TWIPS }, \ + { OUString(UNO_NAME_TEXT_VERT_ADJUST), RES_TEXT_VERT_ADJUST, cppu::UnoType<css::drawing::TextVerticalAdjust>::get(), PROPERTY_NONE ,0}, + +#define COMMON_TEXT_CONTENT_PROPERTIES \ + { OUString(UNO_NAME_ANCHOR_TYPE), FN_UNO_ANCHOR_TYPE, cppu::UnoType<css::text::TextContentAnchorType>::get(), PropertyAttribute::READONLY, MID_ANCHOR_ANCHORTYPE},\ + ANCHOR_TYPES_PROPERTY\ + { OUString(UNO_NAME_TEXT_WRAP), FN_UNO_TEXT_WRAP, cppu::UnoType<css::text::WrapTextMode>::get(), PropertyAttribute::READONLY, MID_SURROUND_SURROUNDTYPE }, + +#define PROP_DIFF_FONTHEIGHT \ + { OUString(UNO_NAME_CHAR_PROP_HEIGHT), RES_CHRATR_FONTSIZE , cppu::UnoType<float>::get(), PROPERTY_NONE , MID_FONTHEIGHT_PROP},\ + { OUString(UNO_NAME_CHAR_DIFF_HEIGHT), RES_CHRATR_FONTSIZE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_FONTHEIGHT_DIFF|CONVERT_TWIPS},\ + { OUString(UNO_NAME_CHAR_PROP_HEIGHT_ASIAN), RES_CHRATR_CJK_FONTSIZE , cppu::UnoType<float>::get(), PROPERTY_NONE , MID_FONTHEIGHT_PROP},\ + { OUString(UNO_NAME_CHAR_DIFF_HEIGHT_ASIAN), RES_CHRATR_CJK_FONTSIZE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_FONTHEIGHT_DIFF|CONVERT_TWIPS},\ + { OUString(UNO_NAME_CHAR_PROP_HEIGHT_COMPLEX), RES_CHRATR_CTL_FONTSIZE , cppu::UnoType<float>::get(), PROPERTY_NONE , MID_FONTHEIGHT_PROP},\ + { OUString(UNO_NAME_CHAR_DIFF_HEIGHT_COMPLEX), RES_CHRATR_CTL_FONTSIZE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , MID_FONTHEIGHT_DIFF|CONVERT_TWIPS}, + +#define COMMON_PARA_STYLE_PROPERTIES \ + { OUString(UNO_NAME_BREAK_TYPE), RES_BREAK, cppu::UnoType<css::style::BreakType>::get(), PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_PAGE_DESC_NAME), RES_PAGEDESC, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_PAGEDESC_PAGEDESCNAME },\ + { OUString(UNO_NAME_PAGE_NUMBER_OFFSET), RES_PAGEDESC, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_PAGEDESC_PAGENUMOFFSET},\ + { OUString(UNO_NAME_CHAR_AUTO_KERNING), RES_CHRATR_AUTOKERN , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_BACK_TRANSPARENT), RES_CHRATR_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT },\ + { OUString(UNO_NAME_CHAR_BACK_COLOR), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR },\ + { OUString(UNO_NAME_CHAR_HIGHLIGHT), RES_CHRATR_HIGHLIGHT, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR },\ + { OUString(UNO_NAME_PARA_BACK_COLOR), RES_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR },\ + { OUString(UNO_NAME_PARA_BACK_TRANSPARENT), RES_BACKGROUND, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_GRAPHIC_TRANSPARENT },\ + { OUString(UNO_NAME_PARA_GRAPHIC_URL), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_URL },\ + { OUString(UNO_NAME_PARA_GRAPHIC), RES_BACKGROUND, cppu::UnoType<css::graphic::XGraphic>::get(), PROPERTY_NONE ,MID_GRAPHIC },\ + { OUString(UNO_NAME_PARA_GRAPHIC_FILTER), RES_BACKGROUND, cppu::UnoType<OUString>::get(), PROPERTY_NONE ,MID_GRAPHIC_FILTER },\ + { OUString(UNO_NAME_PARA_GRAPHIC_LOCATION), RES_BACKGROUND, cppu::UnoType<css::style::GraphicLocation>::get(), PROPERTY_NONE ,MID_GRAPHIC_POSITION}, \ + { OUString(UNO_NAME_CHAR_CASE_MAP), RES_CHRATR_CASEMAP, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_COLOR), RES_CHRATR_COLOR, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_TRANSPARENCE), RES_CHRATR_COLOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_COLOR_ALPHA},\ + { OUString(UNO_NAME_CHAR_STRIKEOUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_CROSS_OUT},\ + { OUString(UNO_NAME_CHAR_CROSSED_OUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_ESCAPEMENT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ESC },\ + { OUString(UNO_NAME_CHAR_ESCAPEMENT_HEIGHT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int8>::get() , PROPERTY_NONE, MID_ESC_HEIGHT},\ + { OUString(UNO_NAME_CHAR_FLASH), RES_CHRATR_BLINK , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_HIDDEN), RES_CHRATR_HIDDEN, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + STANDARD_FONT_PROPERTIES\ + CJK_FONT_PROPERTIES\ + CTL_FONT_PROPERTIES\ + { OUString(UNO_NAME_CHAR_UNDERLINE), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE},\ + { OUString(UNO_NAME_CHAR_UNDERLINE_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR},\ + { OUString(UNO_NAME_CHAR_UNDERLINE_HAS_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR},\ + { OUString(UNO_NAME_CHAR_OVERLINE), RES_CHRATR_OVERLINE , cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_TL_STYLE},\ + { OUString(UNO_NAME_CHAR_OVERLINE_COLOR), RES_CHRATR_OVERLINE , cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TL_COLOR},\ + { OUString(UNO_NAME_CHAR_OVERLINE_HAS_COLOR), RES_CHRATR_OVERLINE , cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TL_HASCOLOR},\ + { OUString(UNO_NAME_PARA_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_TXT_LMARGIN|CONVERT_TWIPS},\ + { OUString(UNO_NAME_PARA_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_R_MARGIN|CONVERT_TWIPS},\ + { OUString(UNO_NAME_PARA_LEFT_MARGIN_RELATIVE), RES_LR_SPACE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_L_REL_MARGIN},\ + { OUString(UNO_NAME_PARA_RIGHT_MARGIN_RELATIVE), RES_LR_SPACE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_R_REL_MARGIN},\ + { OUString(UNO_NAME_PARA_IS_AUTO_FIRST_LINE_INDENT), RES_LR_SPACE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FIRST_AUTO},\ + { OUString(UNO_NAME_PARA_FIRST_LINE_INDENT), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_FIRST_LINE_INDENT|CONVERT_TWIPS},\ + { OUString(UNO_NAME_PARA_FIRST_LINE_INDENT_RELATIVE), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_FIRST_LINE_REL_INDENT|CONVERT_TWIPS},\ + { OUString(UNO_NAME_CHAR_KERNING), RES_CHRATR_KERNING , cppu::UnoType<sal_Int16>::get() , PROPERTY_NONE, CONVERT_TWIPS},\ + { OUString(UNO_NAME_CHAR_NO_HYPHENATION), RES_CHRATR_NOHYPHEN , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_SHADOWED), RES_CHRATR_SHADOWED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_CONTOURED), RES_CHRATR_CONTOUR, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_DROP_CAP_FORMAT), RES_PARATR_DROP, cppu::UnoType<css::style::DropCapFormat>::get() , PROPERTY_NONE, MID_DROPCAP_FORMAT|CONVERT_TWIPS },\ + { OUString(UNO_NAME_DROP_CAP_WHOLE_WORD), RES_PARATR_DROP, cppu::UnoType<bool>::get() , PROPERTY_NONE, MID_DROPCAP_WHOLE_WORD },\ + { OUString(UNO_NAME_DROP_CAP_CHAR_STYLE_NAME), RES_PARATR_DROP, cppu::UnoType<OUString>::get() , PropertyAttribute::MAYBEVOID, MID_DROPCAP_CHAR_STYLE_NAME },\ + { OUString(UNO_NAME_PARA_KEEP_TOGETHER), RES_KEEP, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_PARA_SPLIT), RES_PARATR_SPLIT, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_PARA_WIDOWS), RES_PARATR_WIDOWS, cppu::UnoType<sal_Int8>::get(),PropertyAttribute::MAYBEVOID, 0},\ + { OUString(UNO_NAME_PARA_ORPHANS), RES_PARATR_ORPHANS, cppu::UnoType<sal_Int8>::get(),PropertyAttribute::MAYBEVOID, 0},\ + { OUString(UNO_NAME_PARA_EXPAND_SINGLE_WORD), RES_PARATR_ADJUST, cppu::UnoType<bool>::get() , PROPERTY_NONE, MID_EXPAND_SINGLE },\ + { OUString(UNO_NAME_PARA_LAST_LINE_ADJUST), RES_PARATR_ADJUST, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_LAST_LINE_ADJUST},\ + { OUString(UNO_NAME_PARA_LINE_NUMBER_COUNT), RES_LINENUMBER, cppu::UnoType<bool>::get(), PROPERTY_NONE ,MID_LINENUMBER_COUNT },\ + { OUString(UNO_NAME_PARA_LINE_NUMBER_START_VALUE), RES_LINENUMBER, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_LINENUMBER_STARTVALUE},\ + { OUString(UNO_NAME_PARA_LINE_SPACING), RES_PARATR_LINESPACING, cppu::UnoType<css::style::LineSpacing>::get(),PROPERTY_NONE, CONVERT_TWIPS},\ + { OUString(UNO_NAME_PARA_ADJUST), RES_PARATR_ADJUST, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_PARA_ADJUST},\ + { OUString(UNO_NAME_PARA_REGISTER_MODE_ACTIVE), RES_PARATR_REGISTER, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_PARA_TOP_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_UP_MARGIN|CONVERT_TWIPS},\ + { OUString(UNO_NAME_PARA_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_LO_MARGIN|CONVERT_TWIPS},\ + { OUString(UNO_NAME_PARA_CONTEXT_MARGIN), RES_UL_SPACE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_CTX_MARGIN},\ + { OUString(UNO_NAME_PARA_TOP_MARGIN_RELATIVE), RES_UL_SPACE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_UP_REL_MARGIN},\ + { OUString(UNO_NAME_PARA_BOTTOM_MARGIN_RELATIVE), RES_UL_SPACE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_LO_REL_MARGIN},\ + TABSTOPS_MAP_ENTRY\ + { OUString(UNO_NAME_CHAR_WORD_MODE), RES_CHRATR_WORDLINEMODE,cppu::UnoType<bool>::get() , PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_CHAR_SHADING_VALUE), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, MID_SHADING_VALUE }, \ + { OUString(UNO_NAME_CHAR_LEFT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, LEFT_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_RIGHT_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, RIGHT_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_TOP_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, TOP_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER), RES_CHRATR_BOX, cppu::UnoType<css::table::BorderLine>::get(), PROPERTY_NONE, BOTTOM_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_LEFT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, LEFT_BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_RIGHT_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(),PROPERTY_NONE, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_TOP_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, TOP_BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_BOTTOM_BORDER_DISTANCE), RES_CHRATR_BOX, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS },\ + { OUString(UNO_NAME_CHAR_SHADOW_FORMAT), RES_CHRATR_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS},\ + { OUString(UNO_NAME_LEFT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, LEFT_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_RIGHT_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, RIGHT_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_TOP_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, TOP_BORDER |CONVERT_TWIPS },\ + { OUString(UNO_NAME_BOTTOM_BORDER), RES_BOX, cppu::UnoType<css::table::BorderLine>::get(), 0, BOTTOM_BORDER|CONVERT_TWIPS },\ + { OUString(UNO_NAME_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BORDER_DISTANCE|CONVERT_TWIPS },\ + { OUString(UNO_NAME_LEFT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, LEFT_BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_RIGHT_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, RIGHT_BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_TOP_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, TOP_BORDER_DISTANCE |CONVERT_TWIPS },\ + { OUString(UNO_NAME_BOTTOM_BORDER_DISTANCE), RES_BOX, cppu::UnoType<sal_Int32>::get(), 0, BOTTOM_BORDER_DISTANCE|CONVERT_TWIPS },\ + { OUString(UNO_NAME_PARA_IS_HYPHENATION), RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_IS_HYPHEN },\ + { OUString(UNO_NAME_PARA_HYPHENATION_NO_CAPS), RES_PARATR_HYPHENZONE, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_NO_CAPS },\ + { OUString(UNO_NAME_PARA_HYPHENATION_MAX_LEADING_CHARS), RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_MIN_LEAD },\ + { OUString(UNO_NAME_PARA_HYPHENATION_MAX_TRAILING_CHARS), RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_MIN_TRAIL },\ + { OUString(UNO_NAME_PARA_HYPHENATION_MAX_HYPHENS), RES_PARATR_HYPHENZONE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_HYPHEN_MAX_HYPHENS},\ + { OUString(UNO_NAME_NUMBERING_STYLE_NAME), RES_PARATR_NUMRULE, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, 0},\ + { OUString(UNO_NAME_PARA_USER_DEFINED_ATTRIBUTES), RES_UNKNOWNATR_CONTAINER, cppu::UnoType<css::container::XNameContainer>::get(), PropertyAttribute::MAYBEVOID, 0 },\ + { OUString(UNO_NAME_PARA_SHADOW_FORMAT), RES_SHADOW, cppu::UnoType<css::table::ShadowFormat>::get(), PROPERTY_NONE, CONVERT_TWIPS},\ + { OUString(UNO_NAME_CHAR_COMBINE_IS_ON), RES_CHRATR_TWO_LINES, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_TWOLINES},\ + { OUString(UNO_NAME_CHAR_COMBINE_PREFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PROPERTY_NONE, MID_START_BRACKET},\ + { OUString(UNO_NAME_CHAR_COMBINE_SUFFIX), RES_CHRATR_TWO_LINES, cppu::UnoType<OUString>::get(), PROPERTY_NONE, MID_END_BRACKET},\ + { OUString(UNO_NAME_CHAR_EMPHASIS), RES_CHRATR_EMPHASIS_MARK, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_EMPHASIS},\ + { OUString(UNO_NAME_PARA_IS_HANGING_PUNCTUATION), RES_PARATR_HANGINGPUNCTUATION, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 },\ + { OUString(UNO_NAME_PARA_IS_CHARACTER_DISTANCE), RES_PARATR_SCRIPTSPACE, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 },\ + { OUString(UNO_NAME_PARA_IS_FORBIDDEN_RULES), RES_PARATR_FORBIDDEN_RULES, cppu::UnoType<bool>::get(), PROPERTY_NONE ,0 },\ + { OUString(UNO_NAME_PARA_VERT_ALIGNMENT), RES_PARATR_VERTALIGN, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , 0 },\ + { OUString(UNO_NAME_CHAR_ROTATION), RES_CHRATR_ROTATE, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ROTATE },\ + { OUString(UNO_NAME_CHAR_ROTATION_IS_FIT_TO_LINE), RES_CHRATR_ROTATE, cppu::UnoType<bool>::get(), PROPERTY_NONE, MID_FITTOLINE },\ + { OUString(UNO_NAME_CHAR_SCALE_WIDTH), RES_CHRATR_SCALEW, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 },\ + { OUString(UNO_NAME_CHAR_RELIEF), RES_CHRATR_RELIEF, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_RELIEF },\ + PROP_DIFF_FONTHEIGHT\ + { OUString(UNO_NAME_FOLLOW_STYLE), FN_UNO_FOLLOW_STYLE, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_IS_PHYSICAL), FN_UNO_IS_PHYSICAL, cppu::UnoType<bool>::get(), PropertyAttribute::READONLY, 0},\ + { OUString(UNO_NAME_IS_AUTO_UPDATE), FN_UNO_IS_AUTO_UPDATE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0},\ + { OUString(UNO_NAME_DISPLAY_NAME), FN_UNO_DISPLAY_NAME, cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0},\ + { OUString(UNO_NAME_CATEGORY), FN_UNO_CATEGORY, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE , 0 },\ + { OUString(UNO_NAME_WRITING_MODE), RES_FRAMEDIR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 },\ + { OUString(UNO_NAME_PARA_IS_CONNECT_BORDER), RES_PARATR_CONNECT_BORDER, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0},\ + { OUString(UNO_NAME_SNAP_TO_GRID), RES_PARATR_SNAPTOGRID, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID, 0 }, \ + { OUString(UNO_NAME_OUTLINE_LEVEL), RES_PARATR_OUTLINELEVEL,cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_HIDDEN), FN_UNO_HIDDEN, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_STYLE_INTEROP_GRAB_BAG), FN_UNO_STYLE_INTEROP_GRAB_BAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_PARA_INTEROP_GRAB_BAG), RES_PARATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, + +#define COMMON_ACCESSIBILITY_TEXT_ATTRIBUTE \ + { OUString(UNO_NAME_CHAR_BACK_COLOR), RES_CHRATR_BACKGROUND, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE ,MID_BACK_COLOR }, \ + { OUString(UNO_NAME_CHAR_COLOR), RES_CHRATR_COLOR, cppu::UnoType<sal_Int32>::get(), PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_CHAR_TRANSPARENCE), RES_CHRATR_COLOR, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_COLOR_ALPHA }, \ + { OUString(UNO_NAME_CHAR_CONTOURED), RES_CHRATR_CONTOUR, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_CHAR_EMPHASIS), RES_CHRATR_EMPHASIS_MARK, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_EMPHASIS}, \ + { OUString(UNO_NAME_CHAR_ESCAPEMENT), RES_CHRATR_ESCAPEMENT, cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, MID_ESC }, \ + { OUString(UNO_NAME_CHAR_FONT_NAME), RES_CHRATR_FONT, cppu::UnoType<OUString>::get(), PropertyAttribute::MAYBEVOID, MID_FONT_FAMILY_NAME }, \ + { OUString(UNO_NAME_CHAR_HEIGHT), RES_CHRATR_FONTSIZE , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_FONTHEIGHT|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_CHAR_POSTURE), RES_CHRATR_POSTURE , cppu::UnoType<css::awt::FontSlant>::get(), PropertyAttribute::MAYBEVOID, MID_POSTURE}, \ + { OUString(UNO_NAME_CHAR_SHADOWED), RES_CHRATR_SHADOWED , cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, \ + { OUString(UNO_NAME_CHAR_STRIKEOUT), RES_CHRATR_CROSSEDOUT, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_CROSS_OUT}, \ + { OUString(UNO_NAME_CHAR_UNDERLINE_COLOR), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_TL_COLOR}, \ + { OUString(UNO_NAME_CHAR_WEIGHT), RES_CHRATR_WEIGHT , cppu::UnoType<float>::get(), PropertyAttribute::MAYBEVOID, MID_WEIGHT}, \ + { OUString(UNO_NAME_NUMBERING_LEVEL), RES_PARATR_LIST_LEVEL,cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, 0}, \ + { OUString(UNO_NAME_CHAR_UNDERLINE), RES_CHRATR_UNDERLINE , cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_TL_STYLE}, \ + { OUString(UNO_NAME_NUMBERING_RULES), RES_PARATR_NUMRULE,cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, \ + { OUString(UNO_NAME_PARA_ADJUST), RES_PARATR_ADJUST, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::MAYBEVOID, MID_PARA_ADJUST}, \ + { OUString(UNO_NAME_PARA_BOTTOM_MARGIN), RES_UL_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_LO_MARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_PARA_FIRST_LINE_INDENT), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_FIRST_LINE_INDENT|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_PARA_LEFT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_TXT_LMARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_PARA_LINE_SPACING), RES_PARATR_LINESPACING, cppu::UnoType<css::style::LineSpacing>::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, \ + { OUString(UNO_NAME_PARA_RIGHT_MARGIN), RES_LR_SPACE, cppu::UnoType<sal_Int32>::get(), PropertyAttribute::MAYBEVOID, MID_R_MARGIN|CONVERT_TWIPS}, \ + { OUString(UNO_NAME_TABSTOPS), RES_PARATR_TABSTOP, cppu::UnoType< cppu::UnoSequenceType<css::style::TabStop> >::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS}, \ + +#define FILL_PROPERTIES_SW_BMP \ + { OUString(UNO_NAME_SW_FILLBMP_LOGICAL_SIZE), XATTR_FILLBMP_SIZELOG, cppu::UnoType<bool>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_OFFSET_X), XATTR_FILLBMP_TILEOFFSETX, cppu::UnoType<sal_Int32>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_OFFSET_Y), XATTR_FILLBMP_TILEOFFSETY, cppu::UnoType<sal_Int32>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_POSITION_OFFSET_X), XATTR_FILLBMP_POSOFFSETX, cppu::UnoType<sal_Int32>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_POSITION_OFFSET_Y), XATTR_FILLBMP_POSOFFSETY, cppu::UnoType<sal_Int32>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_RECTANGLE_POINT), XATTR_FILLBMP_POS, cppu::UnoType<css::drawing::RectanglePoint>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_SIZE_X), XATTR_FILLBMP_SIZEX, cppu::UnoType<sal_Int32>::get(), 0, 0, PropertyMoreFlags::METRIC_ITEM}, \ + { OUString(UNO_NAME_SW_FILLBMP_SIZE_Y), XATTR_FILLBMP_SIZEY, cppu::UnoType<sal_Int32>::get(), 0, 0, PropertyMoreFlags::METRIC_ITEM}, \ + { OUString(UNO_NAME_SW_FILLBMP_STRETCH), XATTR_FILLBMP_STRETCH, cppu::UnoType<bool>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBMP_TILE), XATTR_FILLBMP_TILE, cppu::UnoType<bool>::get(), 0, 0},\ + { OUString(UNO_NAME_SW_FILLBMP_MODE), OWN_ATTR_FILLBMP_MODE, cppu::UnoType<drawing::BitmapMode>::get(), 0, 0}, \ + +#define FILL_PROPERTIES_SW_DEFAULTS \ + { OUString(UNO_NAME_SW_FILLCOLOR), XATTR_FILLCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0}, \ + +#define FILL_PROPERTIES_SW \ + FILL_PROPERTIES_SW_BMP \ + FILL_PROPERTIES_SW_DEFAULTS \ + { OUString(UNO_NAME_SW_FILLBACKGROUND), XATTR_FILLBACKGROUND, cppu::UnoType<bool>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLBITMAP), XATTR_FILLBITMAP, cppu::UnoType<css::awt::XBitmap>::get(), 0, MID_BITMAP}, \ + { OUString(UNO_NAME_SW_FILLBITMAPURL), XATTR_FILLBITMAP, cppu::UnoType<OUString>::get(), 0, MID_BITMAP }, \ + { OUString(UNO_NAME_SW_FILLBITMAPNAME), XATTR_FILLBITMAP, cppu::UnoType<OUString>::get(), 0, MID_NAME }, \ + { OUString(UNO_NAME_SW_FILLGRADIENTSTEPCOUNT), XATTR_GRADIENTSTEPCOUNT, cppu::UnoType<sal_Int16>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLGRADIENT), XATTR_FILLGRADIENT, cppu::UnoType<css::awt::Gradient>::get(), 0, MID_FILLGRADIENT}, \ + { OUString(UNO_NAME_SW_FILLGRADIENTNAME), XATTR_FILLGRADIENT, cppu::UnoType<OUString>::get(), 0, MID_NAME }, \ + { OUString(UNO_NAME_SW_FILLHATCH), XATTR_FILLHATCH, cppu::UnoType<css::drawing::Hatch>::get(), 0, MID_FILLHATCH}, \ + { OUString(UNO_NAME_SW_FILLHATCHNAME), XATTR_FILLHATCH, cppu::UnoType<OUString>::get(), 0, MID_NAME }, \ + { OUString(UNO_NAME_SW_FILLSTYLE), XATTR_FILLSTYLE, cppu::UnoType<css::drawing::FillStyle>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILL_TRANSPARENCE), XATTR_FILLTRANSPARENCE, cppu::UnoType<sal_Int16>::get(), 0, 0}, \ + { OUString(UNO_NAME_SW_FILLTRANSPARENCEGRADIENT), XATTR_FILLFLOATTRANSPARENCE, cppu::UnoType<css::awt::Gradient>::get(), 0, MID_FILLGRADIENT}, \ + { OUString(UNO_NAME_SW_FILLTRANSPARENCEGRADIENTNAME), XATTR_FILLFLOATTRANSPARENCE, cppu::UnoType<OUString>::get(), 0, MID_NAME }, \ + { OUString(UNO_NAME_SW_FILLCOLOR_2), XATTR_SECONDARYFILLCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0}, \ + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoobj.cxx b/sw/source/core/unocore/unoobj.cxx new file mode 100644 index 000000000..a70466608 --- /dev/null +++ b/sw/source/core/unocore/unoobj.cxx @@ -0,0 +1,2948 @@ +/* -*- 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 <com/sun/star/table/TableSortField.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svl/itemprop.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <osl/endian.h> +#include <unotools/collatorwrapper.hxx> +#include <swtypes.hxx> +#include <hintids.hxx> +#include <cmdid.h> +#include <unomid.h> +#include <hints.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <istyleaccess.hxx> +#include <ndtxt.hxx> +#include <unocrsr.hxx> +#include <unocrsrhelper.hxx> +#include <swundo.hxx> +#include <rootfrm.hxx> +#include <paratr.hxx> +#include <pam.hxx> +#include <shellio.hxx> +#include <fmtruby.hxx> +#include <docsh.hxx> +#include <docstyle.hxx> +#include <fmtpdsc.hxx> +#include <pagedesc.hxx> +#include <edimp.hxx> +#include <fchrfmt.hxx> +#include <fmtautofmt.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unometa.hxx> +#include <unotext.hxx> +#include <com/sun/star/text/TextMarkupType.hpp> +#include <vcl/svapp.hxx> +#include <unotools/syslocale.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <SwStyleNameMapper.hxx> +#include <sortopt.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <memory> +#include <unoparaframeenum.hxx> +#include <unoparagraph.hxx> +#include <iodetect.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/profilezone.hxx> + +using namespace ::com::sun::star; + +// Helper classes +SwUnoInternalPaM::SwUnoInternalPaM(SwDoc& rDoc) : + SwPaM(rDoc.GetNodes()) +{ +} + +SwUnoInternalPaM::~SwUnoInternalPaM() +{ + while( GetNext() != this) + { + delete GetNext(); + } +} + +SwUnoInternalPaM& SwUnoInternalPaM::operator=(const SwPaM& rPaM) +{ + const SwPaM* pTmp = &rPaM; + *GetPoint() = *rPaM.GetPoint(); + if(rPaM.HasMark()) + { + SetMark(); + *GetMark() = *rPaM.GetMark(); + } + else + DeleteMark(); + while(&rPaM != (pTmp = pTmp->GetNext())) + { + if(pTmp->HasMark()) + new SwPaM(*pTmp->GetMark(), *pTmp->GetPoint(), this); + else + new SwPaM(*pTmp->GetPoint(), this); + } + return *this; +} + +void SwUnoCursorHelper::SelectPam(SwPaM & rPam, const bool bExpand) +{ + if (bExpand) + { + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + } + else if (rPam.HasMark()) + { + rPam.DeleteMark(); + } +} + +void SwUnoCursorHelper::GetTextFromPam(SwPaM & rPam, OUString & rBuffer, + SwRootFrame const*const pLayout) +{ + if (!rPam.HasMark()) + { + return; + } + SvMemoryStream aStream; +#ifdef OSL_BIGENDIAN + aStream.SetEndian( SvStreamEndian::BIG ); +#else + aStream.SetEndian( SvStreamEndian::LITTLE ); +#endif + WriterRef xWrt; + // TODO/MBA: looks like a BaseURL doesn't make sense here + SwReaderWriter::GetWriter( FILTER_TEXT_DLG, OUString(), xWrt ); + if( !xWrt.is() ) + return; + + SwWriter aWriter( aStream, rPam ); + xWrt->m_bASCII_NoLastLineEnd = true; + xWrt->m_bExportPargraphNumbering = false; + SwAsciiOptions aOpt = xWrt->GetAsciiOptions(); + aOpt.SetCharSet( RTL_TEXTENCODING_UNICODE ); + xWrt->SetAsciiOptions( aOpt ); + xWrt->m_bUCS2_WithStartChar = false; + // #i68522# + const bool bOldShowProgress = xWrt->m_bShowProgress; + xWrt->m_bShowProgress = false; + xWrt->m_bHideDeleteRedlines = pLayout && pLayout->IsHideRedlines(); + + if( ! aWriter.Write( xWrt ).IsError() ) + { + const sal_uInt64 lUniLen = aStream.GetSize()/sizeof( sal_Unicode ); + if (lUniLen < o3tl::make_unsigned(SAL_MAX_INT32-1)) + { + aStream.WriteUInt16( '\0' ); + + aStream.Seek( 0 ); + aStream.ResetError(); + + rtl_uString *pStr = rtl_uString_alloc(lUniLen); + aStream.ReadBytes(pStr->buffer, lUniLen * sizeof(sal_Unicode)); + rBuffer = OUString(pStr, SAL_NO_ACQUIRE); + } + } + xWrt->m_bShowProgress = bOldShowProgress; + +} + +/// @throws lang::IllegalArgumentException +/// @throws uno::RuntimeException +static void +lcl_setCharStyle(SwDoc *const pDoc, const uno::Any & rValue, SfxItemSet & rSet) +{ + SwDocShell *const pDocSh = pDoc->GetDocShell(); + if(pDocSh) + { + OUString uStyle; + if (!(rValue >>= uStyle)) + { + throw lang::IllegalArgumentException(); + } + OUString sStyle; + SwStyleNameMapper::FillUIName(uStyle, sStyle, + SwGetPoolIdFromName::ChrFmt); + SwDocStyleSheet *const pStyle = static_cast<SwDocStyleSheet*>( + pDocSh->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Char)); + if (!pStyle) + { + throw lang::IllegalArgumentException(); + } + const SwFormatCharFormat aFormat(pStyle->GetCharFormat()); + rSet.Put(aFormat); + } +}; + +/// @throws lang::IllegalArgumentException +static void +lcl_setAutoStyle(IStyleAccess & rStyleAccess, const uno::Any & rValue, + SfxItemSet & rSet, const bool bPara) +{ + OUString uStyle; + if (!(rValue >>= uStyle)) + { + throw lang::IllegalArgumentException(); + } + std::shared_ptr<SfxItemSet> pStyle = bPara ? + rStyleAccess.getByName(uStyle, IStyleAccess::AUTO_STYLE_PARA ): + rStyleAccess.getByName(uStyle, IStyleAccess::AUTO_STYLE_CHAR ); + if(!pStyle) + { + throw lang::IllegalArgumentException(); + } + + SwFormatAutoFormat aFormat( bPara + ? sal::static_int_cast< sal_uInt16 >(RES_AUTO_STYLE) + : sal::static_int_cast< sal_uInt16 >(RES_TXTATR_AUTOFMT) ); + aFormat.SetStyleHandle( pStyle ); + rSet.Put(aFormat); +}; + +void +SwUnoCursorHelper::SetTextFormatColl(const uno::Any & rAny, SwPaM & rPaM) +{ + SwDoc *const pDoc = rPaM.GetDoc(); + SwDocShell *const pDocSh = pDoc->GetDocShell(); + if(!pDocSh) + return; + OUString uStyle; + rAny >>= uStyle; + OUString sStyle; + SwStyleNameMapper::FillUIName(uStyle, sStyle, + SwGetPoolIdFromName::TxtColl ); + SwDocStyleSheet *const pStyle = static_cast<SwDocStyleSheet*>( + pDocSh->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Para)); + if (!pStyle) + { + throw lang::IllegalArgumentException(); + } + + SwTextFormatColl *const pLocal = pStyle->GetCollection(); + UnoActionContext aAction(pDoc); + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPaM *pTmpCursor = &rPaM; + do { + pDoc->SetTextFormatColl(*pTmpCursor, pLocal); + pTmpCursor = pTmpCursor->GetNext(); + } while ( pTmpCursor != &rPaM ); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); +} + +bool +SwUnoCursorHelper::SetPageDesc( + const uno::Any& rValue, SwDoc & rDoc, SfxItemSet & rSet) +{ + OUString uDescName; + if (!(rValue >>= uDescName)) + { + return false; + } + std::unique_ptr<SwFormatPageDesc> pNewDesc; + const SfxPoolItem* pItem; + if(SfxItemState::SET == rSet.GetItemState( RES_PAGEDESC, true, &pItem ) ) + { + pNewDesc.reset(new SwFormatPageDesc( + *static_cast<const SwFormatPageDesc*>(pItem))); + } + if (!pNewDesc) + { + pNewDesc.reset(new SwFormatPageDesc()); + } + OUString sDescName; + SwStyleNameMapper::FillUIName(uDescName, sDescName, + SwGetPoolIdFromName::PageDesc); + if (!pNewDesc->GetPageDesc() || + (pNewDesc->GetPageDesc()->GetName() != sDescName)) + { + bool bPut = false; + if (!sDescName.isEmpty()) + { + SwPageDesc *const pPageDesc = SwPageDesc::GetByName(rDoc, sDescName); + if (!pPageDesc) + { + throw lang::IllegalArgumentException(); + } + pNewDesc->RegisterToPageDesc(*pPageDesc); + bPut = true; + } + if(!bPut) + { + rSet.ClearItem(RES_BREAK); + rSet.Put(SwFormatPageDesc()); + } + else + { + rSet.Put(*pNewDesc); + } + } + return true; +} + +static void +lcl_SetNodeNumStart(SwPaM & rCursor, uno::Any const& rValue) +{ + sal_Int16 nTmp = 1; + rValue >>= nTmp; + sal_uInt16 nStt = (nTmp < 0 ? USHRT_MAX : static_cast<sal_uInt16>(nTmp)); + SwDoc* pDoc = rCursor.GetDoc(); + UnoActionContext aAction(pDoc); + + if( rCursor.GetNext() != &rCursor ) // MultiSelection? + { + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( rCursor ); + SwPaM aPam( *rCursor.GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + pDoc->SetNumRuleStart(*aRangeArr.SetPam( n, aPam ).GetPoint()); + pDoc->SetNodeNumStart(*aRangeArr.SetPam( n, aPam ).GetPoint(), + nStt ); + } + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + { + pDoc->SetNumRuleStart( *rCursor.GetPoint()); + pDoc->SetNodeNumStart( *rCursor.GetPoint(), nStt ); + } +} + +static bool +lcl_setCharFormatSequence(SwPaM & rPam, uno::Any const& rValue) +{ + uno::Sequence<OUString> aCharStyles; + if (!(rValue >>= aCharStyles)) + { + return false; + } + + for (sal_Int32 nStyle = 0; nStyle < aCharStyles.getLength(); nStyle++) + { + uno::Any aStyle; + rPam.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::START, nullptr); + aStyle <<= aCharStyles.getConstArray()[nStyle]; + // create a local set and apply each format directly + SfxItemSet aSet(rPam.GetDoc()->GetAttrPool(), + svl::Items<RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT>{}); + lcl_setCharStyle(rPam.GetDoc(), aStyle, aSet); + // the first style should replace the current attributes, + // all other have to be added + SwUnoCursorHelper::SetCursorAttr(rPam, aSet, nStyle + ? SetAttrMode::DONTREPLACE + : SetAttrMode::DEFAULT); + rPam.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::START, nullptr); + } + return true; +} + +static void +lcl_setDropcapCharStyle(SwPaM const & rPam, SfxItemSet & rItemSet, + uno::Any const& rValue) +{ + OUString uStyle; + if (!(rValue >>= uStyle)) + { + throw lang::IllegalArgumentException(); + } + OUString sStyle; + SwStyleNameMapper::FillUIName(uStyle, sStyle, + SwGetPoolIdFromName::ChrFmt); + SwDoc *const pDoc = rPam.GetDoc(); + //default character style must not be set as default format + SwDocStyleSheet *const pStyle = static_cast<SwDocStyleSheet*>( + pDoc->GetDocShell() + ->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Char)); + if (!pStyle || pStyle->GetCharFormat() == pDoc->GetDfltCharFormat()) + { + throw lang::IllegalArgumentException(); + } + std::unique_ptr<SwFormatDrop> pDrop; + SfxPoolItem const* pItem(nullptr); + if (SfxItemState::SET == + rItemSet.GetItemState(RES_PARATR_DROP, true, &pItem)) + { + pDrop.reset(new SwFormatDrop(*static_cast<const SwFormatDrop*>(pItem))); + } + if (!pDrop) + { + pDrop.reset(new SwFormatDrop); + } + const rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*pStyle)); + pDrop->SetCharFormat(xStyle->GetCharFormat()); + rItemSet.Put(*pDrop); +} + +static void +lcl_setRubyCharstyle(SfxItemSet & rItemSet, uno::Any const& rValue) +{ + OUString sTmp; + if (!(rValue >>= sTmp)) + { + throw lang::IllegalArgumentException(); + } + + std::unique_ptr<SwFormatRuby> pRuby; + const SfxPoolItem* pItem; + if (SfxItemState::SET == + rItemSet.GetItemState(RES_TXTATR_CJK_RUBY, true, &pItem)) + { + pRuby.reset(new SwFormatRuby(*static_cast<const SwFormatRuby*>(pItem))); + } + if (!pRuby) + { + pRuby.reset(new SwFormatRuby(OUString())); + } + OUString sStyle; + SwStyleNameMapper::FillUIName(sTmp, sStyle, + SwGetPoolIdFromName::ChrFmt); + pRuby->SetCharFormatName(sStyle); + pRuby->SetCharFormatId(0); + if (!sStyle.isEmpty()) + { + const sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName( + sStyle, SwGetPoolIdFromName::ChrFmt); + pRuby->SetCharFormatId(nId); + } + rItemSet.Put(*pRuby); +} + +bool +SwUnoCursorHelper::SetCursorPropertyValue( + SfxItemPropertySimpleEntry const& rEntry, const uno::Any& rValue, + SwPaM & rPam, SfxItemSet & rItemSet) +{ + if (!(rEntry.nFlags & beans::PropertyAttribute::MAYBEVOID) && + (rValue.getValueType() == cppu::UnoType<void>::get())) + { + return false; + } + bool bRet = true; + switch (rEntry.nWID) + { + case RES_TXTATR_CHARFMT: + lcl_setCharStyle(rPam.GetDoc(), rValue, rItemSet); + break; + case RES_TXTATR_AUTOFMT: + lcl_setAutoStyle(rPam.GetDoc()->GetIStyleAccess(), + rValue, rItemSet, false); + break; + case FN_UNO_CHARFMT_SEQUENCE: + lcl_setCharFormatSequence(rPam, rValue); + break; + case FN_UNO_PARA_STYLE : + SwUnoCursorHelper::SetTextFormatColl(rValue, rPam); + break; + case RES_AUTO_STYLE: + lcl_setAutoStyle(rPam.GetDoc()->GetIStyleAccess(), + rValue, rItemSet, true); + break; + case FN_UNO_PAGE_STYLE: + //FIXME nothing here? + break; + case FN_UNO_NUM_START_VALUE: + lcl_SetNodeNumStart( rPam, rValue ); + break; + case FN_UNO_NUM_LEVEL: + // #i91601# + case FN_UNO_LIST_ID: + case FN_UNO_IS_NUMBER: + case FN_UNO_PARA_NUM_AUTO_FORMAT: + { + // multi selection is not considered + SwTextNode *const pTextNd = rPam.GetNode().GetTextNode(); + if (!pTextNd) + { + throw lang::IllegalArgumentException(); + } + if (FN_UNO_NUM_LEVEL == rEntry.nWID) + { + sal_Int16 nLevel = 0; + if (rValue >>= nLevel) + { + if (nLevel < 0 || MAXLEVEL <= nLevel) + { + throw lang::IllegalArgumentException( + "invalid NumberingLevel", nullptr, 0); + } + pTextNd->SetAttrListLevel(nLevel); + } + } + // #i91601# + else if (FN_UNO_LIST_ID == rEntry.nWID) + { + OUString sListId; + if (rValue >>= sListId) + { + pTextNd->SetListId( sListId ); + } + } + else if (FN_UNO_IS_NUMBER == rEntry.nWID) + { + bool bIsNumber(false); + if ((rValue >>= bIsNumber) && !bIsNumber) + { + pTextNd->SetCountedInList( false ); + } + } + else if (FN_UNO_PARA_NUM_AUTO_FORMAT == rEntry.nWID) + { + uno::Sequence<beans::NamedValue> props; + if (rValue >>= props) + { + // TODO create own map for this, it contains UNO_NAME_DISPLAY_NAME? or make property readable so ODF export can map it to a automatic style? + SfxItemPropertySet const& rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE)); + SfxItemPropertyMap const& rMap(rPropSet.getPropertyMap()); + SfxItemSet items( rPam.GetDoc()->GetAttrPool(), + svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>{} ); + + for (beans::NamedValue const & prop : std::as_const(props)) + { + SfxItemPropertySimpleEntry const*const pEntry = + rMap.getByName(prop.Name); + if (!pEntry) + { + if (prop.Name == "CharStyleName") + { + lcl_setCharStyle(rPam.GetDoc(), prop.Value, items); + continue; + } + throw beans::UnknownPropertyException( + "Unknown property: " + prop.Name); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw beans::PropertyVetoException( + "Property is read-only: " + prop.Name); + } + rPropSet.setPropertyValue(*pEntry, prop.Value, items); + } + + SwFormatAutoFormat item(RES_PARATR_LIST_AUTOFMT); + // TODO: for ODF export we'd need to add it to the autostyle pool + // note: paragraph auto styles have ParaStyleName property for the parent style; character auto styles currently do not because there's a separate hint, but for this it would be a good way to add it in order to export it as style:parent-style-name, see XMLTextParagraphExport::Add() + item.SetStyleHandle(std::make_shared<SfxItemSet>(items)); + pTextNd->SetAttr(item); + } + } + //PROPERTY_MAYBEVOID! + } + break; + case FN_NUMBER_NEWSTART: + { + bool bVal = false; + if (!(rValue >>= bVal)) + { + throw lang::IllegalArgumentException(); + } + rPam.GetDoc()->SetNumRuleStart(*rPam.GetPoint(), bVal); + } + break; + case FN_UNO_NUM_RULES: + SwUnoCursorHelper::setNumberingProperty(rValue, rPam); + break; + case RES_PARATR_DROP: + { + if (MID_DROPCAP_CHAR_STYLE_NAME == rEntry.nMemberId) + { + lcl_setDropcapCharStyle(rPam, rItemSet, rValue); + } + else + { + bRet = false; + } + } + break; + case RES_TXTATR_CJK_RUBY: + { + if (MID_RUBY_CHARSTYLE == rEntry.nMemberId) + { + lcl_setRubyCharstyle(rItemSet, rValue); + } + else + { + bRet = false; + } + } + break; + case RES_PAGEDESC: + { + if (MID_PAGEDESC_PAGEDESCNAME == rEntry.nMemberId) + { + SwUnoCursorHelper::SetPageDesc( + rValue, *rPam.GetDoc(), rItemSet); + } + else + { + bRet = false; + } + } + break; + default: + bRet = false; + } + return bRet; +} + +SwFormatColl * +SwUnoCursorHelper::GetCurTextFormatColl(SwPaM & rPaM, const bool bConditional) +{ + static const sal_uLong nMaxLookup = 1000; + SwFormatColl *pFormat = nullptr; + bool bError = false; + SwPaM *pTmpCursor = &rPaM; + do + { + const sal_uLong nSttNd = pTmpCursor->Start()->nNode.GetIndex(); + const sal_uLong nEndNd = pTmpCursor->End()->nNode.GetIndex(); + + if( nEndNd - nSttNd >= nMaxLookup ) + { + pFormat = nullptr; + break; + } + + const SwNodes& rNds = rPaM.GetDoc()->GetNodes(); + for( sal_uLong n = nSttNd; n <= nEndNd; ++n ) + { + SwTextNode const*const pNd = rNds[ n ]->GetTextNode(); + if( pNd ) + { + SwFormatColl *const pNdFormat = bConditional + ? pNd->GetFormatColl() : &pNd->GetAnyFormatColl(); + if( !pFormat ) + { + pFormat = pNdFormat; + } + else if( pFormat != pNdFormat ) + { + bError = true; + break; + } + } + } + + pTmpCursor = pTmpCursor->GetNext(); + } while ( pTmpCursor != &rPaM ); + return bError ? nullptr : pFormat; +} + +class SwXTextCursor::Impl +{ + +public: + const SfxItemPropertySet & m_rPropSet; + const CursorType m_eType; + const uno::Reference< text::XText > m_xParentText; + sw::UnoCursorPointer m_pUnoCursor; + + Impl( SwDoc & rDoc, + const CursorType eType, + uno::Reference<text::XText> const & xParent, + SwPosition const& rPoint, SwPosition const*const pMark) + : m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR)) + , m_eType(eType) + , m_xParentText(xParent) + , m_pUnoCursor(rDoc.CreateUnoCursor(rPoint)) + { + if (pMark) + { + m_pUnoCursor->SetMark(); + *m_pUnoCursor->GetMark() = *pMark; + } + } + + SwUnoCursor& GetCursorOrThrow() { + if(!m_pUnoCursor) + throw uno::RuntimeException("SwXTextCursor: disposed or invalid", nullptr); + return *m_pUnoCursor; + } +}; + +SwUnoCursor& SwXTextCursor::GetCursor() + { return *m_pImpl->m_pUnoCursor; } + +SwPaM const* SwXTextCursor::GetPaM() const + { return m_pImpl->m_pUnoCursor.get(); } + +SwPaM* SwXTextCursor::GetPaM() + { return m_pImpl->m_pUnoCursor.get(); } + +SwDoc const* SwXTextCursor::GetDoc() const + { return m_pImpl->m_pUnoCursor ? m_pImpl->m_pUnoCursor->GetDoc() : nullptr; } + +SwDoc* SwXTextCursor::GetDoc() + { return m_pImpl->m_pUnoCursor ? m_pImpl->m_pUnoCursor->GetDoc() : nullptr; } + +SwXTextCursor::SwXTextCursor( + SwDoc & rDoc, + uno::Reference< text::XText > const& xParent, + const CursorType eType, + const SwPosition& rPos, + SwPosition const*const pMark) + : m_pImpl( new Impl(rDoc, eType, xParent, rPos, pMark) ) +{ +} + +SwXTextCursor::SwXTextCursor(uno::Reference< text::XText > const& xParent, + SwPaM const& rSourceCursor, const CursorType eType) + : m_pImpl( new Impl(*rSourceCursor.GetDoc(), eType, + xParent, *rSourceCursor.GetPoint(), + rSourceCursor.HasMark() ? rSourceCursor.GetMark() : nullptr) ) +{ +} + +SwXTextCursor::~SwXTextCursor() +{ +} + +void SwXTextCursor::DeleteAndInsert(const OUString& rText, + const bool bForceExpandHints) +{ + auto pUnoCursor = static_cast<SwCursor*>(m_pImpl->m_pUnoCursor.get()); + if (pUnoCursor) + { + // Start/EndAction + SwDoc* pDoc = pUnoCursor->GetDoc(); + UnoActionContext aAction(pDoc); + const sal_Int32 nTextLen = rText.getLength(); + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + auto pCurrent = pUnoCursor; + do + { + if (pCurrent->HasMark()) + { + pDoc->getIDocumentContentOperations().DeleteAndJoin(*pCurrent); + } + if(nTextLen) + { + const bool bSuccess( + SwUnoCursorHelper::DocInsertStringSplitCR( + *pDoc, *pCurrent, rText, bForceExpandHints ) ); + OSL_ENSURE( bSuccess, "Doc->Insert(Str) failed." ); + + SwUnoCursorHelper::SelectPam(*pUnoCursor, true); + pCurrent->Left(rText.getLength()); + } + pCurrent = pCurrent->GetNext(); + } while (pCurrent != pUnoCursor); + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); + } +} + +namespace { + +enum ForceIntoMetaMode { META_CHECK_BOTH, META_INIT_START, META_INIT_END }; + +} + +static bool +lcl_ForceIntoMeta(SwPaM & rCursor, + uno::Reference<text::XText> const & xParentText, + const enum ForceIntoMetaMode eMode) +{ + bool bRet( true ); // means not forced in META_CHECK_BOTH + SwXMeta const * const pXMeta( dynamic_cast<SwXMeta*>(xParentText.get()) ); + OSL_ENSURE(pXMeta, "no parent?"); + if (!pXMeta) + throw uno::RuntimeException(); + SwTextNode * pTextNode; + sal_Int32 nStart; + sal_Int32 nEnd; + const bool bSuccess( pXMeta->SetContentRange(pTextNode, nStart, nEnd) ); + OSL_ENSURE(bSuccess, "no pam?"); + if (!bSuccess) + throw uno::RuntimeException(); + // force the cursor back into the meta if it has moved outside + SwPosition start(*pTextNode, nStart); + SwPosition end(*pTextNode, nEnd); + switch (eMode) + { + case META_INIT_START: + *rCursor.GetPoint() = start; + break; + case META_INIT_END: + *rCursor.GetPoint() = end; + break; + case META_CHECK_BOTH: + if (*rCursor.Start() < start) + { + *rCursor.Start() = start; + bRet = false; + } + if (*rCursor.End() > end) + { + *rCursor.End() = end; + bRet = false; + } + break; + } + return bRet; +} + +bool SwXTextCursor::IsAtEndOfMeta() const +{ + if (CursorType::Meta == m_pImpl->m_eType) + { + auto pCursor( m_pImpl->m_pUnoCursor ); + SwXMeta const*const pXMeta( + dynamic_cast<SwXMeta*>(m_pImpl->m_xParentText.get()) ); + OSL_ENSURE(pXMeta, "no meta?"); + if (pCursor && pXMeta) + { + SwTextNode * pTextNode; + sal_Int32 nStart; + sal_Int32 nEnd; + const bool bSuccess( + pXMeta->SetContentRange(pTextNode, nStart, nEnd) ); + OSL_ENSURE(bSuccess, "no pam?"); + if (bSuccess) + { + const SwPosition end(*pTextNode, nEnd); + if ( (*pCursor->GetPoint() == end) + || (*pCursor->GetMark() == end)) + { + return true; + } + } + } + } + return false; +} + +OUString SwXTextCursor::getImplementationName() +{ + return "SwXTextCursor"; +} + +sal_Bool SAL_CALL SwXTextCursor::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXTextCursor::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextCursor", + "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesAsian", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.ParagraphProperties", + "com.sun.star.style.ParagraphPropertiesAsian", + "com.sun.star.style.ParagraphPropertiesComplex", + "com.sun.star.text.TextSortable" + }; +} + +namespace +{ + class theSwXTextCursorUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextCursorUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextCursor::getUnoTunnelId() +{ + return theSwXTextCursorUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXTextCursor::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + const sal_Int64 nRet( ::sw::UnoTunnelImpl<SwXTextCursor>(rId, this) ); + return nRet ? nRet : OTextCursorHelper::getSomething(rId); +} + +void SAL_CALL SwXTextCursor::collapseToStart() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (rUnoCursor.HasMark()) + { + if (*rUnoCursor.GetPoint() > *rUnoCursor.GetMark()) + { + rUnoCursor.Exchange(); + } + rUnoCursor.DeleteMark(); + } +} + +void SAL_CALL SwXTextCursor::collapseToEnd() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (rUnoCursor.HasMark()) + { + if (*rUnoCursor.GetPoint() < *rUnoCursor.GetMark()) + { + rUnoCursor.Exchange(); + } + rUnoCursor.DeleteMark(); + } +} + +sal_Bool SAL_CALL SwXTextCursor::isCollapsed() +{ + SolarMutexGuard aGuard; + + bool bRet = true; + auto pUnoCursor(m_pImpl->m_pUnoCursor); + if(pUnoCursor && pUnoCursor->GetMark()) + { + bRet = (*pUnoCursor->GetPoint() == *pUnoCursor->GetMark()); + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::goLeft(sal_Int16 nCount, sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + bool bRet = rUnoCursor.Left( nCount); + if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH) + && bRet; + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::goRight(sal_Int16 nCount, sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + bool bRet = rUnoCursor.Right(nCount); + if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH) + && bRet; + } + return bRet; +} + +void SAL_CALL +SwXTextCursor::gotoStart(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + comphelper::ProfileZone aZone("gotoStart"); + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + if (CursorType::Body == m_pImpl->m_eType) + { + rUnoCursor.Move( fnMoveBackward, GoInDoc ); + //check, that the cursor is not in a table + SwTableNode * pTableNode = rUnoCursor.GetNode().FindTableNode(); + SwContentNode * pCNode = nullptr; + while (pTableNode) + { + rUnoCursor.GetPoint()->nNode = *pTableNode->EndOfSectionNode(); + pCNode = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode); + pTableNode = pCNode ? pCNode->FindTableNode() : nullptr; + } + if (pCNode) + { + rUnoCursor.GetPoint()->nContent.Assign(pCNode, 0); + } + SwStartNode const*const pTmp = + rUnoCursor.GetNode().StartOfSectionNode(); + if (pTmp->IsSectionNode()) + { + SwSectionNode const*const pSectionStartNode = + static_cast<SwSectionNode const*>(pTmp); + if (pSectionStartNode->GetSection().IsHiddenFlag()) + { + pCNode = GetDoc()->GetNodes().GoNextSection( + &rUnoCursor.GetPoint()->nNode, true, false); + if (pCNode) + { + rUnoCursor.GetPoint()->nContent.Assign(pCNode, 0); + } + } + } + } + else if ( (CursorType::Frame == m_pImpl->m_eType) + || (CursorType::TableText == m_pImpl->m_eType) + || (CursorType::Header == m_pImpl->m_eType) + || (CursorType::Footer == m_pImpl->m_eType) + || (CursorType::Footnote== m_pImpl->m_eType) + || (CursorType::Redline == m_pImpl->m_eType)) + { + rUnoCursor.MoveSection(GoCurrSection, fnSectionStart); + } + else if (CursorType::Meta == m_pImpl->m_eType) + { + lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, META_INIT_START); + } +} + +void SAL_CALL +SwXTextCursor::gotoEnd(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + comphelper::ProfileZone aZone("gotoEnd"); + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + if (CursorType::Body == m_pImpl->m_eType) + { + rUnoCursor.Move( fnMoveForward, GoInDoc ); + } + else if ( (CursorType::Frame == m_pImpl->m_eType) + || (CursorType::TableText == m_pImpl->m_eType) + || (CursorType::Header == m_pImpl->m_eType) + || (CursorType::Footer == m_pImpl->m_eType) + || (CursorType::Footnote== m_pImpl->m_eType) + || (CursorType::Redline == m_pImpl->m_eType)) + { + rUnoCursor.MoveSection( GoCurrSection, fnSectionEnd); + } + else if (CursorType::Meta == m_pImpl->m_eType) + { + lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, META_INIT_END); + } +} + +void SAL_CALL +SwXTextCursor::gotoRange( + const uno::Reference< text::XTextRange > & xRange, sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + + if (!xRange.is()) + { + throw uno::RuntimeException(); + } + + SwUnoCursor & rOwnCursor( m_pImpl->GetCursorOrThrow() ); + + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + if(xRangeTunnel.is()) + { + pRange = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + } + + if (!pRange && !pCursor) + { + throw uno::RuntimeException(); + } + + SwPaM aPam(GetDoc()->GetNodes()); + const SwPaM * pPam(nullptr); + if (pCursor) + { + pPam = pCursor->GetPaM(); + } + else if (pRange) + { + if (pRange->GetPositions(aPam)) + { + pPam = & aPam; + } + } + + if (!pPam) + { + throw uno::RuntimeException(); + } + + { + SwStartNodeType eSearchNodeType = SwNormalStartNode; + switch (m_pImpl->m_eType) + { + case CursorType::Frame: eSearchNodeType = SwFlyStartNode; break; + case CursorType::TableText: eSearchNodeType = SwTableBoxStartNode; break; + case CursorType::Footnote: eSearchNodeType = SwFootnoteStartNode; break; + case CursorType::Header: eSearchNodeType = SwHeaderStartNode; break; + case CursorType::Footer: eSearchNodeType = SwFooterStartNode; break; + //case CURSOR_INVALID: + //case CursorType::Body: + default: + ; + } + + const SwStartNode* pOwnStartNode = rOwnCursor.GetNode().FindSttNodeByType(eSearchNodeType); + while ( pOwnStartNode != nullptr + && pOwnStartNode->IsSectionNode()) + { + pOwnStartNode = pOwnStartNode->StartOfSectionNode(); + } + + const SwStartNode* pTmp = + pPam->GetNode().FindSttNodeByType(eSearchNodeType); + while ( pTmp != nullptr + && pTmp->IsSectionNode() ) + { + pTmp = pTmp->StartOfSectionNode(); + } + + if ( eSearchNodeType == SwTableBoxStartNode ) + { + if (!pOwnStartNode || !pTmp) + { + throw uno::RuntimeException(); + } + + if ( pOwnStartNode->FindTableNode() != pTmp->FindTableNode() ) + { + throw uno::RuntimeException(); + } + } + else + { + if ( pOwnStartNode != pTmp ) + { + throw uno::RuntimeException(); + } + } + } + + if (CursorType::Meta == m_pImpl->m_eType) + { + SwPaM CopyPam(*pPam->GetMark(), *pPam->GetPoint()); + const bool bNotForced( lcl_ForceIntoMeta( + CopyPam, m_pImpl->m_xParentText, META_CHECK_BOTH) ); + if (!bNotForced) + { + throw uno::RuntimeException( + "gotoRange: parameter range not contained in nesting" + " text content for which this cursor was created", + static_cast<text::XWordCursor*>(this)); + } + } + + // selection has to be expanded here + if(bExpand) + { + // cursor should include its previous range plus the given range + const SwPosition aOwnLeft(*rOwnCursor.Start()); + const SwPosition aOwnRight(*rOwnCursor.End()); + SwPosition const& rParamLeft = *pPam->Start(); + SwPosition const& rParamRight = *pPam->End(); + + // now there are four SwPositions, + // two of them are going to be used, but which ones? + if (aOwnRight > rParamRight) + *rOwnCursor.GetPoint() = aOwnRight; + else + *rOwnCursor.GetPoint() = rParamRight; + rOwnCursor.SetMark(); + if (aOwnLeft < rParamLeft) + *rOwnCursor.GetMark() = aOwnLeft; + else + *rOwnCursor.GetMark() = rParamLeft; + } + else + { + // cursor should be the given range + *rOwnCursor.GetPoint() = *pPam->GetPoint(); + if (pPam->HasMark()) + { + rOwnCursor.SetMark(); + *rOwnCursor.GetMark() = *pPam->GetMark(); + } + else + { + rOwnCursor.DeleteMark(); + } + } +} + +sal_Bool SAL_CALL SwXTextCursor::isStartOfWord() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const bool bRet = + rUnoCursor.IsStartWordWT( i18n::WordType::DICTIONARY_WORD ); + return bRet; +} + +sal_Bool SAL_CALL SwXTextCursor::isEndOfWord() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const bool bRet = + rUnoCursor.IsEndWordWT( i18n::WordType::DICTIONARY_WORD ); + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoNextWord(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + // problems arise when a paragraph starts with something other than a word + bool bRet = false; + // remember old position to check if cursor has moved + // since the called functions are sometimes a bit unreliable + // in specific cases... + SwPosition *const pPoint = rUnoCursor.GetPoint(); + SwNode *const pOldNode = &pPoint->nNode.GetNode(); + sal_Int32 const nOldIndex = pPoint->nContent.GetIndex(); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + // end of paragraph + if (rUnoCursor.GetContentNode() && + (pPoint->nContent == rUnoCursor.GetContentNode()->Len())) + { + rUnoCursor.Right(1); + } + else + { + const bool bTmp = + rUnoCursor.GoNextWordWT( i18n::WordType::DICTIONARY_WORD ); + // if there is no next word within the current paragraph + // try to go to the start of the next paragraph + if (!bTmp) + { + rUnoCursor.MovePara(GoNextPara, fnParaStart); + } + } + + // return true if cursor has moved + bRet = (&pPoint->nNode.GetNode() != pOldNode) || + (pPoint->nContent.GetIndex() != nOldIndex); + if (bRet && (CursorType::Meta == m_pImpl->m_eType)) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH); + } + + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoPreviousWord(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + // white spaces create problems on the paragraph start + bool bRet = false; + SwPosition *const pPoint = rUnoCursor.GetPoint(); + SwNode *const pOldNode = &pPoint->nNode.GetNode(); + sal_Int32 const nOldIndex = pPoint->nContent.GetIndex(); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + // start of paragraph? + if (pPoint->nContent == 0) + { + rUnoCursor.Left(1); + } + else + { + rUnoCursor.GoPrevWordWT( i18n::WordType::DICTIONARY_WORD ); + if (pPoint->nContent == 0) + { + rUnoCursor.Left(1); + } + } + + // return true if cursor has moved + bRet = (&pPoint->nNode.GetNode() != pOldNode) || + (pPoint->nContent.GetIndex() != nOldIndex); + if (bRet && (CursorType::Meta == m_pImpl->m_eType)) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH); + } + + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoEndOfWord(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + bool bRet = false; + SwPosition *const pPoint = rUnoCursor.GetPoint(); + SwNode & rOldNode = pPoint->nNode.GetNode(); + sal_Int32 const nOldIndex = pPoint->nContent.GetIndex(); + + const sal_Int16 nWordType = i18n::WordType::DICTIONARY_WORD; + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + if (!rUnoCursor.IsEndWordWT( nWordType )) + { + rUnoCursor.GoEndWordWT( nWordType ); + } + + // restore old cursor if we are not at the end of a word by now + // otherwise use current one + bRet = rUnoCursor.IsEndWordWT( nWordType ); + if (!bRet) + { + pPoint->nNode = rOldNode; + pPoint->nContent = nOldIndex; + } + else if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH); + } + + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoStartOfWord(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + bool bRet = false; + SwPosition *const pPoint = rUnoCursor.GetPoint(); + SwNode & rOldNode = pPoint->nNode.GetNode(); + sal_Int32 const nOldIndex = pPoint->nContent.GetIndex(); + + const sal_Int16 nWordType = i18n::WordType::DICTIONARY_WORD; + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + if (!rUnoCursor.IsStartWordWT( nWordType )) + { + rUnoCursor.GoStartWordWT( nWordType ); + } + + // restore old cursor if we are not at the start of a word by now + // otherwise use current one + bRet = rUnoCursor.IsStartWordWT( nWordType ); + if (!bRet) + { + pPoint->nNode = rOldNode; + pPoint->nContent = nOldIndex; + } + else if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH); + } + + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::isStartOfSentence() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + // start of paragraph? + bool bRet = rUnoCursor.GetPoint()->nContent == 0; + // with mark ->no sentence start + // (check if cursor is no selection, i.e. it does not have + // a mark or else point and mark are identical) + if (!bRet && (!rUnoCursor.HasMark() || + *rUnoCursor.GetPoint() == *rUnoCursor.GetMark())) + { + SwCursor aCursor(*rUnoCursor.GetPoint(),nullptr); + SwPosition aOrigPos = *aCursor.GetPoint(); + aCursor.GoSentence(SwCursor::START_SENT ); + bRet = aOrigPos == *aCursor.GetPoint(); + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::isEndOfSentence() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + // end of paragraph? + bool bRet = rUnoCursor.GetContentNode() && + (rUnoCursor.GetPoint()->nContent == rUnoCursor.GetContentNode()->Len()); + // with mark->no sentence end + // (check if cursor is no selection, i.e. it does not have + // a mark or else point and mark are identical) + if (!bRet && (!rUnoCursor.HasMark() || + *rUnoCursor.GetPoint() == *rUnoCursor.GetMark())) + { + SwCursor aCursor(*rUnoCursor.GetPoint(), nullptr); + SwPosition aOrigPos = *aCursor.GetPoint(); + aCursor.GoSentence(SwCursor::END_SENT); + bRet = aOrigPos == *aCursor.GetPoint(); + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoNextSentence(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const bool bWasEOS = isEndOfSentence(); + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + bool bRet = rUnoCursor.GoSentence(SwCursor::NEXT_SENT); + if (!bRet) + { + bRet = rUnoCursor.MovePara(GoNextPara, fnParaStart); + } + + // if at the end of the sentence (i.e. at the space after the '.') + // advance to next word in order for GoSentence to work properly + // next time and have isStartOfSentence return true after this call + if (!rUnoCursor.IsStartWordWT(css::i18n::WordType::ANYWORD_IGNOREWHITESPACES)) + { + const bool bNextWord = rUnoCursor.GoNextWordWT(i18n::WordType::ANYWORD_IGNOREWHITESPACES); + if (bWasEOS && !bNextWord) + { + bRet = false; + } + } + if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH) + && bRet; + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoPreviousSentence(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + bool bRet = rUnoCursor.GoSentence(SwCursor::PREV_SENT); + if (!bRet) + { + bRet = rUnoCursor.MovePara(GoPrevPara, fnParaStart); + if (bRet) + { + rUnoCursor.MovePara(GoCurrPara, fnParaEnd); + // at the end of a paragraph move to the sentence end again + rUnoCursor.GoSentence(SwCursor::PREV_SENT); + } + } + if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH) + && bRet; + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoStartOfSentence(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + // if we're at the para start then we won't move + // but bRet is also true if GoSentence failed but + // the start of the sentence is reached + bool bRet = SwUnoCursorHelper::IsStartOfPara(rUnoCursor) + || rUnoCursor.GoSentence(SwCursor::START_SENT) + || SwUnoCursorHelper::IsStartOfPara(rUnoCursor); + if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH) + && bRet; + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoEndOfSentence(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + // bRet is true if GoSentence() succeeded or if the + // MovePara() succeeded while the end of the para is + // not reached already + bool bAlreadyParaEnd = SwUnoCursorHelper::IsEndOfPara(rUnoCursor); + bool bRet = !bAlreadyParaEnd + && (rUnoCursor.GoSentence(SwCursor::END_SENT) + || rUnoCursor.MovePara(GoCurrPara, fnParaEnd)); + if (CursorType::Meta == m_pImpl->m_eType) + { + bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, + META_CHECK_BOTH) + && bRet; + } + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::isStartOfParagraph() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const bool bRet = SwUnoCursorHelper::IsStartOfPara(rUnoCursor); + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::isEndOfParagraph() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const bool bRet = SwUnoCursorHelper::IsEndOfPara(rUnoCursor); + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoStartOfParagraph(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (CursorType::Meta == m_pImpl->m_eType) + { + return false; + } + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + bool bRet = SwUnoCursorHelper::IsStartOfPara(rUnoCursor); + if (!bRet) + { + bRet = rUnoCursor.MovePara(GoCurrPara, fnParaStart); + } + + // since MovePara(GoCurrPara, fnParaStart) only returns false + // if we were already at the start of the paragraph this function + // should always complete successfully. + OSL_ENSURE( bRet, "gotoStartOfParagraph failed" ); + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoEndOfParagraph(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (CursorType::Meta == m_pImpl->m_eType) + { + return false; + } + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + bool bRet = SwUnoCursorHelper::IsEndOfPara(rUnoCursor); + if (!bRet) + { + bRet = rUnoCursor.MovePara(GoCurrPara, fnParaEnd); + } + + // since MovePara(GoCurrPara, fnParaEnd) only returns false + // if we were already at the end of the paragraph this function + // should always complete successfully. + OSL_ENSURE( bRet, "gotoEndOfParagraph failed" ); + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoNextParagraph(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (CursorType::Meta == m_pImpl->m_eType) + { + return false; + } + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + const bool bRet = rUnoCursor.MovePara(GoNextPara, fnParaStart); + return bRet; +} + +sal_Bool SAL_CALL +SwXTextCursor::gotoPreviousParagraph(sal_Bool Expand) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (CursorType::Meta == m_pImpl->m_eType) + { + return false; + } + SwUnoCursorHelper::SelectPam(rUnoCursor, Expand); + const bool bRet = rUnoCursor.MovePara(GoPrevPara, fnParaStart); + return bRet; +} + +uno::Reference< text::XText > SAL_CALL +SwXTextCursor::getText() +{ + SolarMutexGuard g; + + return m_pImpl->m_xParentText; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXTextCursor::getStart() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + uno::Reference< text::XTextRange > xRet; + SwPaM aPam(*rUnoCursor.Start()); + const uno::Reference< text::XText > xParent = getText(); + if (CursorType::Meta == m_pImpl->m_eType) + { + // return cursor to prevent modifying SwXTextRange for META + SwXTextCursor * const pXCursor( + new SwXTextCursor(*rUnoCursor.GetDoc(), xParent, CursorType::Meta, + *rUnoCursor.GetPoint()) ); + pXCursor->gotoStart(false); + xRet = static_cast<text::XWordCursor*>(pXCursor); + } + else + { + xRet = new SwXTextRange(aPam, xParent); + } + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXTextCursor::getEnd() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + uno::Reference< text::XTextRange > xRet; + SwPaM aPam(*rUnoCursor.End()); + const uno::Reference< text::XText > xParent = getText(); + if (CursorType::Meta == m_pImpl->m_eType) + { + // return cursor to prevent modifying SwXTextRange for META + SwXTextCursor * const pXCursor( + new SwXTextCursor(*rUnoCursor.GetDoc(), xParent, CursorType::Meta, + *rUnoCursor.GetPoint()) ); + pXCursor->gotoEnd(false); + xRet = static_cast<text::XWordCursor*>(pXCursor); + } + else + { + xRet = new SwXTextRange(aPam, xParent); + } + return xRet; +} + +OUString SAL_CALL SwXTextCursor::getString() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + OUString aText; + SwUnoCursorHelper::GetTextFromPam(rUnoCursor, aText); + return aText; +} + +void SAL_CALL +SwXTextCursor::setString(const OUString& aString) +{ + SolarMutexGuard aGuard; + + m_pImpl->GetCursorOrThrow(); // just to check if valid + + const bool bForceExpandHints( (CursorType::Meta == m_pImpl->m_eType) + && dynamic_cast<SwXMeta*>(m_pImpl->m_xParentText.get()) + ->CheckForOwnMemberMeta(*GetPaM(), true) ); + DeleteAndInsert(aString, bForceExpandHints); +} + +uno::Any SwUnoCursorHelper::GetPropertyValue( + SwPaM& rPaM, const SfxItemPropertySet& rPropSet, + const OUString& rPropertyName) +{ + uno::Any aAny; + SfxItemPropertySimpleEntry const*const pEntry = + rPropSet.getPropertyMap().getByName(rPropertyName); + + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(nullptr)); + } + + beans::PropertyState eTemp; + const bool bDone = SwUnoCursorHelper::getCursorPropertyValue( + *pEntry, rPaM, &aAny, eTemp ); + + if (!bDone) + { + SfxItemSet aSet( + rPaM.GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_FRMATR_END - 1, + RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER>{}); + SwUnoCursorHelper::GetCursorAttr(rPaM, aSet); + + rPropSet.getPropertyValue(*pEntry, aSet, aAny); + } + + return aAny; +} + +void SwUnoCursorHelper::SetPropertyValue( + SwPaM& rPaM, const SfxItemPropertySet& rPropSet, + const OUString& rPropertyName, + const uno::Any& rValue, + const SetAttrMode nAttrMode) +{ + uno::Sequence< beans::PropertyValue > aValues(1); + aValues[0].Name = rPropertyName; + aValues[0].Value = rValue; + SetPropertyValues(rPaM, rPropSet, aValues, nAttrMode); +} + +// FN_UNO_PARA_STYLE is known to set attributes for nodes, inside +// SwUnoCursorHelper::SetTextFormatColl, instead of extending item set. +// We need to get them from nodes in next call to GetCursorAttr. +// The rest could cause similar problems in theory, so we just list them here. +static bool propertyCausesSideEffectsInNodes(sal_uInt16 nWID) +{ + return nWID == FN_UNO_PARA_STYLE || + nWID == FN_UNO_CHARFMT_SEQUENCE || + nWID == FN_UNO_NUM_START_VALUE || + nWID == FN_UNO_NUM_RULES; +} + +void SwUnoCursorHelper::SetPropertyValues( + SwPaM& rPaM, const SfxItemPropertySet& rPropSet, + const uno::Sequence< beans::PropertyValue > &rPropertyValues, + const SetAttrMode nAttrMode) +{ + if (!rPropertyValues.hasElements()) + return; + + SwDoc *const pDoc = rPaM.GetDoc(); + OUString aUnknownExMsg, aPropertyVetoExMsg; + + // Build set of attributes we want to fetch + const sal_uInt16 zero = 0; + SfxItemSet aItemSet(pDoc->GetAttrPool(), &zero); + std::vector<std::pair<const SfxItemPropertySimpleEntry*, const uno::Any&>> aEntries; + aEntries.reserve(rPropertyValues.getLength()); + for (const auto& rPropVal : rPropertyValues) + { + const OUString &rPropertyName = rPropVal.Name; + + SfxItemPropertySimpleEntry const* pEntry = + rPropSet.getPropertyMap().getByName(rPropertyName); + + // Queue up any exceptions until the end ... + if (!pEntry) + { + aUnknownExMsg += "Unknown property: '" + rPropertyName + "' "; + continue; + } + else if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + aPropertyVetoExMsg += "Property is read-only: '" + rPropertyName + "' "; + continue; + } + aItemSet.MergeRange(pEntry->nWID, pEntry->nWID); + aEntries.emplace_back(pEntry, rPropVal.Value); + } + + if (!aEntries.empty()) + { + // Fetch, overwrite, and re-set the attributes from the core + + bool bPreviousPropertyCausesSideEffectsInNodes = false; + for (size_t i = 0; i < aEntries.size(); ++i) + { + SfxItemPropertySimpleEntry const*const pEntry = aEntries[i].first; + bool bPropertyCausesSideEffectsInNodes = + propertyCausesSideEffectsInNodes(pEntry->nWID); + + // we need to get up-to-date item set from nodes + if (i == 0 || bPreviousPropertyCausesSideEffectsInNodes) + { + aItemSet.ClearItem(); + SwUnoCursorHelper::GetCursorAttr(rPaM, aItemSet); + } + + const uno::Any &rValue = aEntries[i].second; + // this can set some attributes in nodes' mpAttrSet + if (!SwUnoCursorHelper::SetCursorPropertyValue(*pEntry, rValue, rPaM, aItemSet)) + rPropSet.setPropertyValue(*pEntry, rValue, aItemSet); + + if (i + 1 == aEntries.size() || bPropertyCausesSideEffectsInNodes) + SwUnoCursorHelper::SetCursorAttr(rPaM, aItemSet, nAttrMode, false/*bTableMode*/); + + bPreviousPropertyCausesSideEffectsInNodes = bPropertyCausesSideEffectsInNodes; + } + } + + if (!aUnknownExMsg.isEmpty()) + throw beans::UnknownPropertyException(aUnknownExMsg, static_cast<cppu::OWeakObject *>(nullptr)); + if (!aPropertyVetoExMsg.isEmpty()) + throw beans::PropertyVetoException(aPropertyVetoExMsg, static_cast<cppu::OWeakObject *>(nullptr)); +} + +namespace +{ + bool NotInRange(sal_uInt16 nWID, sal_uInt16 nStart, sal_uInt16 nEnd) + { + return nWID < nStart || nWID > nEnd; + } +} + +uno::Sequence< beans::PropertyState > +SwUnoCursorHelper::GetPropertyStates( + SwPaM& rPaM, const SfxItemPropertySet& rPropSet, + const uno::Sequence< OUString >& rPropertyNames, + const SwGetPropertyStatesCaller eCaller) +{ + const OUString* pNames = rPropertyNames.getConstArray(); + uno::Sequence< beans::PropertyState > aRet(rPropertyNames.getLength()); + beans::PropertyState* pStates = aRet.getArray(); + const SfxItemPropertyMap &rMap = rPropSet.getPropertyMap(); + std::unique_ptr<SfxItemSet> pSet; + std::unique_ptr<SfxItemSet> pSetParent; + + for (sal_Int32 i = 0, nEnd = rPropertyNames.getLength(); i < nEnd; i++) + { + SfxItemPropertySimpleEntry const*const pEntry = + rMap.getByName( pNames[i] ); + if(!pEntry) + { + if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT || + pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT) + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + continue; + } + else if (SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT == + eCaller) + { + //this values marks the element as unknown property + pStates[i] = beans::PropertyState::PropertyState_MAKE_FIXED_SIZE; + continue; + } + else + { + throw beans::UnknownPropertyException( + "Unknown property: " + pNames[i], + static_cast<cppu::OWeakObject *>(nullptr)); + } + } + if (((SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION == eCaller) || + (SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT == eCaller)) && + NotInRange(pEntry->nWID, FN_UNO_RANGE_BEGIN, FN_UNO_RANGE_END) && + NotInRange(pEntry->nWID, RES_CHRATR_BEGIN, RES_TXTATR_END) ) + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + else + { + if ( pEntry->nWID >= FN_UNO_RANGE_BEGIN && + pEntry->nWID <= FN_UNO_RANGE_END ) + { + (void)SwUnoCursorHelper::getCursorPropertyValue( + *pEntry, rPaM, nullptr, pStates[i] ); + } + else + { + if (!pSet) + { + switch ( eCaller ) + { + case SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT: + case SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION: + pSet.reset( + new SfxItemSet( rPaM.GetDoc()->GetAttrPool(), + svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END>{} )); + break; + case SW_PROPERTY_STATE_CALLER_SINGLE_VALUE_ONLY: + pSet.reset( + new SfxItemSet( rPaM.GetDoc()->GetAttrPool(), + {{pEntry->nWID, pEntry->nWID}} )); + break; + default: + pSet.reset( new SfxItemSet( + rPaM.GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_FRMATR_END - 1, + RES_UNKNOWNATR_CONTAINER, + RES_UNKNOWNATR_CONTAINER>{})); + } + // #i63870# + SwUnoCursorHelper::GetCursorAttr( rPaM, *pSet ); + } + + pStates[i] = ( pSet->Count() ) + ? rPropSet.getPropertyState( *pEntry, *pSet ) + : beans::PropertyState_DEFAULT_VALUE; + + //try again to find out if a value has been inherited + if( beans::PropertyState_DIRECT_VALUE == pStates[i] ) + { + if (!pSetParent) + { + pSetParent = pSet->Clone( false ); + // #i63870# + SwUnoCursorHelper::GetCursorAttr( + rPaM, *pSetParent, true, false ); + } + + pStates[i] = ( pSetParent->Count() ) + ? rPropSet.getPropertyState( *pEntry, *pSetParent ) + : beans::PropertyState_DEFAULT_VALUE; + } + } + } + } + return aRet; +} + +beans::PropertyState SwUnoCursorHelper::GetPropertyState( + SwPaM& rPaM, const SfxItemPropertySet& rPropSet, + const OUString& rPropertyName) +{ + uno::Sequence< OUString > aStrings { rPropertyName }; + uno::Sequence< beans::PropertyState > aSeq = + GetPropertyStates(rPaM, rPropSet, aStrings, + SW_PROPERTY_STATE_CALLER_SINGLE_VALUE_ONLY ); + return aSeq[0]; +} + +static void +lcl_SelectParaAndReset( SwPaM &rPaM, SwDoc & rDoc, + std::set<sal_uInt16> const &rWhichIds ) +{ + // if we are resetting paragraph attributes, we need to select the full paragraph first + SwPosition aStart = *rPaM.Start(); + SwPosition aEnd = *rPaM.End(); + auto pTemp ( rDoc.CreateUnoCursor(aStart) ); + if(!SwUnoCursorHelper::IsStartOfPara(*pTemp)) + { + pTemp->MovePara(GoCurrPara, fnParaStart); + } + pTemp->SetMark(); + *pTemp->GetPoint() = aEnd; + SwUnoCursorHelper::SelectPam(*pTemp, true); + if(!SwUnoCursorHelper::IsEndOfPara(*pTemp)) + { + pTemp->MovePara(GoCurrPara, fnParaEnd); + } + rDoc.ResetAttrs(*pTemp, true, rWhichIds); +} + +void SwUnoCursorHelper::SetPropertyToDefault( + SwPaM& rPaM, const SfxItemPropertySet& rPropSet, + const OUString& rPropertyName) +{ + SwDoc & rDoc = *rPaM.GetDoc(); + SfxItemPropertySimpleEntry const*const pEntry = + rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(nullptr)); + } + + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw uno::RuntimeException( + "setPropertyToDefault: property is read-only: " + + rPropertyName, nullptr); + } + + if (pEntry->nWID < RES_FRMATR_END) + { + std::set<sal_uInt16> aWhichIds; + aWhichIds.insert( pEntry->nWID ); + if (pEntry->nWID < RES_PARATR_BEGIN) + { + rDoc.ResetAttrs(rPaM, true, aWhichIds); + } + else + { + lcl_SelectParaAndReset ( rPaM, rDoc, aWhichIds ); + } + } + else + { + SwUnoCursorHelper::resetCursorPropertyValue(*pEntry, rPaM); + } +} + +uno::Any SwUnoCursorHelper::GetPropertyDefault( + SwPaM const & rPaM, const SfxItemPropertySet& rPropSet, + const OUString& rPropertyName) +{ + SfxItemPropertySimpleEntry const*const pEntry = + rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, static_cast<cppu::OWeakObject *>(nullptr)); + } + + uno::Any aRet; + if (pEntry->nWID < RES_FRMATR_END) + { + SwDoc & rDoc = *rPaM.GetDoc(); + const SfxPoolItem& rDefItem = + rDoc.GetAttrPool().GetDefaultItem(pEntry->nWID); + rDefItem.QueryValue(aRet, pEntry->nMemberId); + } + return aRet; +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXTextCursor::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xRef = [&]() + { + static SfxItemPropertyMapEntry const aCursorExtMap_Impl[] = + { + { OUString(UNO_NAME_IS_SKIP_HIDDEN_TEXT), FN_SKIP_HIDDEN_TEXT, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_SKIP_PROTECTED_TEXT), FN_SKIP_PROTECTED_TEXT, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + const uno::Reference< beans::XPropertySetInfo > xInfo = + m_pImpl->m_rPropSet.getPropertySetInfo(); + // extend PropertySetInfo! + const uno::Sequence<beans::Property> aPropSeq = xInfo->getProperties(); + return new SfxExtItemPropertySetInfo( + aCursorExtMap_Impl, + aPropSeq ); + }(); + return xRef; +} + +void SAL_CALL +SwXTextCursor::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (rPropertyName == UNO_NAME_IS_SKIP_HIDDEN_TEXT) + { + bool bSet(false); + if (!(rValue >>= bSet)) + { + throw lang::IllegalArgumentException(); + } + rUnoCursor.SetSkipOverHiddenSections(bSet); + } + else if (rPropertyName == UNO_NAME_IS_SKIP_PROTECTED_TEXT) + { + bool bSet(false); + if (!(rValue >>= bSet)) + { + throw lang::IllegalArgumentException(); + } + rUnoCursor.SetSkipOverProtectSections(bSet); + } + else + { + SwUnoCursorHelper::SetPropertyValue(rUnoCursor, + m_pImpl->m_rPropSet, rPropertyName, rValue); + } +} + +uno::Any SAL_CALL +SwXTextCursor::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + uno::Any aAny; + if (rPropertyName == UNO_NAME_IS_SKIP_HIDDEN_TEXT) + { + const bool bSet = rUnoCursor.IsSkipOverHiddenSections(); + aAny <<= bSet; + } + else if (rPropertyName == UNO_NAME_IS_SKIP_PROTECTED_TEXT) + { + const bool bSet = rUnoCursor.IsSkipOverProtectSections(); + aAny <<= bSet; + } + else + { + aAny = SwUnoCursorHelper::GetPropertyValue(rUnoCursor, + m_pImpl->m_rPropSet, rPropertyName); + } + return aAny; +} + +void SAL_CALL +SwXTextCursor::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextCursor::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextCursor::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextCursor::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextCursor::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextCursor::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextCursor::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextCursor::removeVetoableChangeListener(): not implemented"); +} + +beans::PropertyState SAL_CALL +SwXTextCursor::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const beans::PropertyState eRet = SwUnoCursorHelper::GetPropertyState( + rUnoCursor, m_pImpl->m_rPropSet, rPropertyName); + return eRet; +} + +uno::Sequence< beans::PropertyState > SAL_CALL +SwXTextCursor::getPropertyStates( + const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + return SwUnoCursorHelper::GetPropertyStates( + rUnoCursor, m_pImpl->m_rPropSet, rPropertyNames); +} + +void SAL_CALL +SwXTextCursor::setPropertyToDefault(const OUString& rPropertyName) +{ + // forward: need no solar mutex here + uno::Sequence < OUString > aSequence ( &rPropertyName, 1 ); + setPropertiesToDefault ( aSequence ); +} + +uno::Any SAL_CALL +SwXTextCursor::getPropertyDefault(const OUString& rPropertyName) +{ + // forward: need no solar mutex here + const uno::Sequence < OUString > aSequence ( &rPropertyName, 1 ); + return getPropertyDefaults ( aSequence ).getConstArray()[0]; +} + +void SAL_CALL SwXTextCursor::setPropertyValues( + const uno::Sequence< OUString >& aPropertyNames, + const uno::Sequence< uno::Any >& aValues ) +{ + if( aValues.getLength() != aPropertyNames.getLength() ) + { + OSL_FAIL( "mis-matched property value sequences" ); + throw lang::IllegalArgumentException(); + } + + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + // a little lame to have to copy into this. + uno::Sequence< beans::PropertyValue > aPropertyValues( aValues.getLength() ); + for ( sal_Int32 i = 0; i < aPropertyNames.getLength(); i++ ) + { + if ( aPropertyNames[ i ] == UNO_NAME_IS_SKIP_HIDDEN_TEXT || + aPropertyNames[ i ] == UNO_NAME_IS_SKIP_PROTECTED_TEXT ) + { + // the behaviour of these is hard to model in a group + OSL_FAIL("invalid property name for batch setting"); + throw lang::IllegalArgumentException(); + } + aPropertyValues[ i ].Name = aPropertyNames[ i ]; + aPropertyValues[ i ].Value = aValues[ i ]; + } + try + { + SwUnoCursorHelper::SetPropertyValues( rUnoCursor, m_pImpl->m_rPropSet, aPropertyValues ); + } + catch (const css::beans::UnknownPropertyException& e) + { + uno::Any a(cppu::getCaughtException()); + throw lang::WrappedTargetException( + "wrapped Exception " + e.Message, + uno::Reference<uno::XInterface>(), a); + } +} + +uno::Sequence< uno::Any > SAL_CALL +SwXTextCursor::getPropertyValues( const uno::Sequence< OUString >& aPropertyNames ) +{ + // a banal implementation for now + uno::Sequence< uno::Any > aValues( aPropertyNames.getLength() ); + std::transform(aPropertyNames.begin(), aPropertyNames.end(), aValues.begin(), + [this](const OUString& rName) -> uno::Any { return getPropertyValue( rName ); }); + return aValues; +} + +void SAL_CALL SwXTextCursor::addPropertiesChangeListener( + const uno::Sequence< OUString >& /* aPropertyNames */, + const uno::Reference< css::beans::XPropertiesChangeListener >& /* xListener */ ) +{ + OSL_FAIL("SwXTextCursor::addPropertiesChangeListener(): not implemented"); +} +void SAL_CALL SwXTextCursor::removePropertiesChangeListener( + const uno::Reference< css::beans::XPropertiesChangeListener >& /* xListener */ ) +{ + OSL_FAIL("SwXTextCursor::removePropertiesChangeListener(): not implemented"); +} + +void SAL_CALL SwXTextCursor::firePropertiesChangeEvent( + const uno::Sequence< OUString >& /* aPropertyNames */, + const uno::Reference< css::beans::XPropertiesChangeListener >& /* xListener */ ) +{ + OSL_FAIL("SwXTextCursor::firePropertiesChangeEvent(): not implemented"); +} + +// para specific attribute ranges +static sal_uInt16 g_ParaResetableSetRange[] = { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_PARATR_BEGIN, RES_PARATR_END-1, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + 0 +}; + +// selection specific attribute ranges +static sal_uInt16 g_ResetableSetRange[] = { + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_TXTATR_INETFMT, RES_TXTATR_INETFMT, + RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT, + RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY, + RES_TXTATR_UNKNOWN_CONTAINER, RES_TXTATR_UNKNOWN_CONTAINER, + 0 +}; + +static void +lcl_EnumerateIds(sal_uInt16 const* pIdRange, std::set<sal_uInt16> &rWhichIds) +{ + while (*pIdRange) + { + const sal_uInt16 nStart = *pIdRange++; + const sal_uInt16 nEnd = *pIdRange++; + for (sal_uInt16 nId = nStart + 1; nId <= nEnd; ++nId) + { + rWhichIds.insert( rWhichIds.end(), nId ); + } + } +} + +void SAL_CALL +SwXTextCursor::setAllPropertiesToDefault() +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + std::set<sal_uInt16> aParaWhichIds; + std::set<sal_uInt16> aWhichIds; + lcl_EnumerateIds(g_ParaResetableSetRange, aParaWhichIds); + lcl_EnumerateIds(g_ResetableSetRange, aWhichIds); + if (!aParaWhichIds.empty()) + { + lcl_SelectParaAndReset(rUnoCursor, *rUnoCursor.GetDoc(), + aParaWhichIds); + } + if (!aWhichIds.empty()) + { + rUnoCursor.GetDoc()->ResetAttrs(rUnoCursor, true, aWhichIds); + } +} + +void SAL_CALL +SwXTextCursor::setPropertiesToDefault( + const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if ( rPropertyNames.hasElements() ) + { + SwDoc & rDoc = *rUnoCursor.GetDoc(); + std::set<sal_uInt16> aWhichIds; + std::set<sal_uInt16> aParaWhichIds; + for (const OUString& rName : rPropertyNames) + { + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName( rName ); + if (!pEntry) + { + if (rName == UNO_NAME_IS_SKIP_HIDDEN_TEXT || + rName == UNO_NAME_IS_SKIP_PROTECTED_TEXT) + { + continue; + } + throw beans::UnknownPropertyException( + "Unknown property: " + rName, + static_cast<cppu::OWeakObject *>(this)); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw uno::RuntimeException( + "setPropertiesToDefault: property is read-only: " + rName, + static_cast<cppu::OWeakObject *>(this)); + } + + if (pEntry->nWID < RES_FRMATR_END) + { + if (pEntry->nWID < RES_PARATR_BEGIN) + { + aWhichIds.insert( pEntry->nWID ); + } + else + { + aParaWhichIds.insert( pEntry->nWID ); + } + } + else if (pEntry->nWID == FN_UNO_NUM_START_VALUE) + { + SwUnoCursorHelper::resetCursorPropertyValue(*pEntry, rUnoCursor); + } + } + + if (!aParaWhichIds.empty()) + { + lcl_SelectParaAndReset(rUnoCursor, rDoc, aParaWhichIds); + } + if (!aWhichIds.empty()) + { + rDoc.ResetAttrs(rUnoCursor, true, aWhichIds); + } + } +} + +uno::Sequence< uno::Any > SAL_CALL +SwXTextCursor::getPropertyDefaults( + const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + const sal_Int32 nCount = rPropertyNames.getLength(); + uno::Sequence< uno::Any > aRet(nCount); + if ( nCount ) + { + SwDoc & rDoc = *rUnoCursor.GetDoc(); + const OUString *pNames = rPropertyNames.getConstArray(); + uno::Any *pAny = aRet.getArray(); + for (sal_Int32 i = 0; i < nCount; i++) + { + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName( pNames[i] ); + if (!pEntry) + { + if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT || + pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT) + { + continue; + } + throw beans::UnknownPropertyException( + "Unknown property: " + pNames[i], + static_cast<cppu::OWeakObject *>(nullptr)); + } + if (pEntry->nWID < RES_FRMATR_END) + { + const SfxPoolItem& rDefItem = + rDoc.GetAttrPool().GetDefaultItem(pEntry->nWID); + rDefItem.QueryValue(pAny[i], pEntry->nMemberId); + } + } + } + return aRet; +} + +void SAL_CALL SwXTextCursor::invalidateMarkings(::sal_Int32 nType) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwNode& node = rUnoCursor.GetNode(); + + SwTextNode* txtNode = node.GetTextNode(); + + if (txtNode == nullptr) return; + + if ( text::TextMarkupType::SPELLCHECK == nType ) + { + txtNode->SetWrongDirty(SwTextNode::WrongState::TODO); + txtNode->SetWrong(nullptr); + } + else if( text::TextMarkupType::PROOFREADING == nType ) + { + txtNode->SetGrammarCheckDirty(true); + txtNode->SetGrammarCheck(nullptr); + } + else if ( text::TextMarkupType::SMARTTAG == nType ) + { + txtNode->SetSmartTagDirty(true); + txtNode->SetSmartTags(nullptr); + } + else return; + + SwFormatColl* fmtColl=txtNode->GetFormatColl(); + + if (fmtColl == nullptr) return; + + SwFormatChg aNew( fmtColl ); + txtNode->NotifyClients( nullptr, &aNew ); +} + +void SAL_CALL +SwXTextCursor::makeRedline( + const OUString& rRedlineType, + const uno::Sequence< beans::PropertyValue >& rRedlineProperties) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::makeRedline(rUnoCursor, rRedlineType, rRedlineProperties); +} + +void SAL_CALL SwXTextCursor::insertDocumentFromURL(const OUString& rURL, + const uno::Sequence< beans::PropertyValue >& rOptions) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwUnoCursorHelper::InsertFile(&rUnoCursor, rURL, rOptions); +} + +uno::Sequence< beans::PropertyValue > +SwUnoCursorHelper::CreateSortDescriptor(const bool bFromTable) +{ + uno::Sequence< beans::PropertyValue > aRet(5); + beans::PropertyValue* pArray = aRet.getArray(); + + uno::Any aVal; + aVal <<= bFromTable; + pArray[0] = beans::PropertyValue("IsSortInTable", -1, aVal, + beans::PropertyState_DIRECT_VALUE); + + aVal <<= u' '; + pArray[1] = beans::PropertyValue("Delimiter", -1, aVal, + beans::PropertyState_DIRECT_VALUE); + + aVal <<= false; + pArray[2] = beans::PropertyValue("IsSortColumns", -1, aVal, + beans::PropertyState_DIRECT_VALUE); + + aVal <<= sal_Int32(3); + pArray[3] = beans::PropertyValue("MaxSortFieldsCount", -1, aVal, + beans::PropertyState_DIRECT_VALUE); + + uno::Sequence< table::TableSortField > aFields(3); + table::TableSortField* pFields = aFields.getArray(); + + lang::Locale aLang( SvtSysLocale().GetLanguageTag().getLocale()); + // get collator algorithm to be used for the locale + uno::Sequence< OUString > aSeq( + GetAppCollator().listCollatorAlgorithms( aLang ) ); + const bool bHasElements = aSeq.hasElements(); + OSL_ENSURE( bHasElements, "list of collator algorithms is empty!"); + OUString aCollAlg; + if (bHasElements) + { + aCollAlg = aSeq.getConstArray()[0]; + } + + pFields[0].Field = 1; + pFields[0].IsAscending = true; + pFields[0].IsCaseSensitive = false; + pFields[0].FieldType = table::TableSortFieldType_ALPHANUMERIC; + pFields[0].CollatorLocale = aLang; + pFields[0].CollatorAlgorithm = aCollAlg; + + pFields[1].Field = 1; + pFields[1].IsAscending = true; + pFields[1].IsCaseSensitive = false; + pFields[1].FieldType = table::TableSortFieldType_ALPHANUMERIC; + pFields[1].CollatorLocale = aLang; + pFields[1].CollatorAlgorithm = aCollAlg; + + pFields[2].Field = 1; + pFields[2].IsAscending = true; + pFields[2].IsCaseSensitive = false; + pFields[2].FieldType = table::TableSortFieldType_ALPHANUMERIC; + pFields[2].CollatorLocale = aLang; + pFields[2].CollatorAlgorithm = aCollAlg; + + aVal <<= aFields; + pArray[4] = beans::PropertyValue("SortFields", -1, aVal, + beans::PropertyState_DIRECT_VALUE); + + return aRet; +} + +uno::Sequence< beans::PropertyValue > SAL_CALL +SwXTextCursor::createSortDescriptor() +{ + SolarMutexGuard aGuard; + + return SwUnoCursorHelper::CreateSortDescriptor(false); +} + +bool SwUnoCursorHelper::ConvertSortProperties( + const uno::Sequence< beans::PropertyValue >& rDescriptor, + SwSortOptions& rSortOpt) +{ + bool bRet = true; + + rSortOpt.bTable = false; + rSortOpt.cDeli = ' '; + rSortOpt.eDirection = SwSortDirection::Columns; //!! UI text may be contrary though !! + + std::unique_ptr<SwSortKey> pKey1(new SwSortKey); + pKey1->nColumnId = USHRT_MAX; + pKey1->bIsNumeric = true; + pKey1->eSortOrder = SwSortOrder::Ascending; + + std::unique_ptr<SwSortKey> pKey2(new SwSortKey); + pKey2->nColumnId = USHRT_MAX; + pKey2->bIsNumeric = true; + pKey2->eSortOrder = SwSortOrder::Ascending; + + std::unique_ptr<SwSortKey> pKey3(new SwSortKey); + pKey3->nColumnId = USHRT_MAX; + pKey3->bIsNumeric = true; + pKey3->eSortOrder = SwSortOrder::Ascending; + SwSortKey* aKeys[3] = {pKey1.get(), pKey2.get(), pKey3.get()}; + + bool bOldSortdescriptor(false); + bool bNewSortdescriptor(false); + + for (const beans::PropertyValue& rProperty : rDescriptor) + { + uno::Any aValue( rProperty.Value ); + const OUString& rPropName = rProperty.Name; + + // old and new sortdescriptor + if ( rPropName == "IsSortInTable" ) + { + if (auto b = o3tl::tryAccess<bool>(aValue)) + { + rSortOpt.bTable = *b; + } + else + { + bRet = false; + } + } + else if ( rPropName == "Delimiter" ) + { + sal_Unicode uChar; + sal_uInt16 nChar; + if (aValue >>= uChar) + { + rSortOpt.cDeli = uChar; + } + else if (aValue >>= nChar) + { + // For compatibility with BASIC, also accept an ANY containing + // an UNSIGNED SHORT: + rSortOpt.cDeli = nChar; + } + else + { + bRet = false; + } + } + // old sortdescriptor + else if ( rPropName == "SortColumns" ) + { + bOldSortdescriptor = true; + bool bTemp(false); + if (aValue >>= bTemp) + { + rSortOpt.eDirection = bTemp ? SwSortDirection::Columns : SwSortDirection::Rows; + } + else + { + bRet = false; + } + } + else if ( rPropName == "IsCaseSensitive" ) + { + bOldSortdescriptor = true; + bool bTemp(false); + if (aValue >>= bTemp) + { + rSortOpt.bIgnoreCase = !bTemp; + } + else + { + bRet = false; + } + } + else if ( rPropName == "CollatorLocale" ) + { + bOldSortdescriptor = true; + lang::Locale aLocale; + if (aValue >>= aLocale) + { + rSortOpt.nLanguage = LanguageTag::convertToLanguageType( aLocale); + } + else + { + bRet = false; + } + } + else if (rPropName.startsWith("CollatorAlgorithm") && + rPropName.getLength() == 18 && + (rPropName[17] >= '0' && rPropName[17] <= '9')) + { + bOldSortdescriptor = true; + sal_uInt16 nIndex = rPropName[17]; + nIndex -= '0'; + OUString aText; + if ((aValue >>= aText) && nIndex < 3) + { + aKeys[nIndex]->sSortType = aText; + } + else + { + bRet = false; + } + } + else if (rPropName.startsWith("SortRowOrColumnNo") && + rPropName.getLength() == 18 && + (rPropName[17] >= '0' && rPropName[17] <= '9')) + { + bOldSortdescriptor = true; + sal_uInt16 nIndex = rPropName[17]; + nIndex -= '0'; + sal_Int16 nCol = -1; + if (aValue.getValueType() == ::cppu::UnoType<sal_Int16>::get() + && nIndex < 3) + { + aValue >>= nCol; + } + if (nCol >= 0) + { + aKeys[nIndex]->nColumnId = nCol; + } + else + { + bRet = false; + } + } + else if (rPropName.startsWith("IsSortNumeric") && + rPropName.getLength() == 14 && + (rPropName[13] >= '0' && rPropName[13] <= '9')) + { + bOldSortdescriptor = true; + sal_uInt16 nIndex = rPropName[13]; + nIndex = nIndex - '0'; + auto bTemp = o3tl::tryAccess<bool>(aValue); + if (bTemp && nIndex < 3) + { + aKeys[nIndex]->bIsNumeric = *bTemp; + } + else + { + bRet = false; + } + } + else if (rPropName.startsWith("IsSortAscending") && + rPropName.getLength() == 16 && + (rPropName[15] >= '0' && rPropName[15] <= '9')) + { + bOldSortdescriptor = true; + sal_uInt16 nIndex = rPropName[15]; + nIndex -= '0'; + auto bTemp = o3tl::tryAccess<bool>(aValue); + if (bTemp && nIndex < 3) + { + aKeys[nIndex]->eSortOrder = (*bTemp) + ? SwSortOrder::Ascending : SwSortOrder::Descending; + } + else + { + bRet = false; + } + } + // new sortdescriptor + else if ( rPropName == "IsSortColumns" ) + { + bNewSortdescriptor = true; + if (auto bTemp = o3tl::tryAccess<bool>(aValue)) + { + rSortOpt.eDirection = *bTemp ? SwSortDirection::Columns : SwSortDirection::Rows; + } + else + { + bRet = false; + } + } + else if ( rPropName == "SortFields" ) + { + bNewSortdescriptor = true; + uno::Sequence < table::TableSortField > aFields; + if (aValue >>= aFields) + { + sal_Int32 nCount(aFields.getLength()); + if (nCount <= 3) + { + table::TableSortField* pFields = aFields.getArray(); + for (sal_Int32 i = 0; i < nCount; ++i) + { + rSortOpt.bIgnoreCase = !pFields[i].IsCaseSensitive; + rSortOpt.nLanguage = + LanguageTag::convertToLanguageType( pFields[i].CollatorLocale ); + aKeys[i]->sSortType = pFields[i].CollatorAlgorithm; + aKeys[i]->nColumnId = + static_cast<sal_uInt16>(pFields[i].Field); + aKeys[i]->bIsNumeric = (pFields[i].FieldType == + table::TableSortFieldType_NUMERIC); + aKeys[i]->eSortOrder = (pFields[i].IsAscending) + ? SwSortOrder::Ascending : SwSortOrder::Descending; + } + } + else + { + bRet = false; + } + } + else + { + bRet = false; + } + } + } + + if (bNewSortdescriptor && bOldSortdescriptor) + { + OSL_FAIL("someone tried to set the old deprecated and " + "the new sortdescriptor"); + bRet = false; + } + + if (pKey1->nColumnId != USHRT_MAX) + { + rSortOpt.aKeys.push_back(std::move(pKey1)); + } + if (pKey2->nColumnId != USHRT_MAX) + { + rSortOpt.aKeys.push_back(std::move(pKey2)); + } + if (pKey3->nColumnId != USHRT_MAX) + { + rSortOpt.aKeys.push_back(std::move(pKey3)); + } + + return bRet && !rSortOpt.aKeys.empty(); +} + +void SAL_CALL +SwXTextCursor::sort(const uno::Sequence< beans::PropertyValue >& rDescriptor) +{ + SolarMutexGuard aGuard; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + if (!rUnoCursor.HasMark()) + return; + + SwSortOptions aSortOpt; + if (!SwUnoCursorHelper::ConvertSortProperties(rDescriptor, aSortOpt)) + { + throw uno::RuntimeException("Bad sort properties"); + } + UnoActionContext aContext( rUnoCursor.GetDoc() ); + + SwPosition & rStart = *rUnoCursor.Start(); + SwPosition & rEnd = *rUnoCursor.End(); + + SwNodeIndex aPrevIdx( rStart.nNode, -1 ); + const sal_uLong nOffset = rEnd.nNode.GetIndex() - rStart.nNode.GetIndex(); + const sal_Int32 nCntStt = rStart.nContent.GetIndex(); + + rUnoCursor.GetDoc()->SortText(rUnoCursor, aSortOpt); + + // update selection + rUnoCursor.DeleteMark(); + rUnoCursor.GetPoint()->nNode.Assign( aPrevIdx.GetNode(), +1 ); + SwContentNode *const pCNd = rUnoCursor.GetContentNode(); + sal_Int32 nLen = pCNd->Len(); + if (nLen > nCntStt) + { + nLen = nCntStt; + } + rUnoCursor.GetPoint()->nContent.Assign(pCNd, nLen ); + rUnoCursor.SetMark(); + + rUnoCursor.GetPoint()->nNode += nOffset; + SwContentNode *const pCNd2 = rUnoCursor.GetContentNode(); + rUnoCursor.GetPoint()->nContent.Assign( pCNd2, pCNd2->Len() ); + +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXTextCursor::createContentEnumeration(const OUString& rServiceName) +{ + SolarMutexGuard g; + if (rServiceName != "com.sun.star.text.TextContent") + throw uno::RuntimeException(); + SwUnoCursor& rUnoCursor( m_pImpl->GetCursorOrThrow() ); + return SwXParaFrameEnumeration::Create(rUnoCursor, PARAFRAME_PORTION_TEXTRANGE); +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXTextCursor::createEnumeration() +{ + SolarMutexGuard g; + + SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() ); + + SwXText* pParentText = comphelper::getUnoTunnelImplementation<SwXText>(m_pImpl->m_xParentText); + OSL_ENSURE(pParentText, "parent is not a SwXText"); + if (!pParentText) + { + throw uno::RuntimeException(); + } + + auto pNewCursor(rUnoCursor.GetDoc()->CreateUnoCursor(*rUnoCursor.GetPoint()) ); + if (rUnoCursor.HasMark()) + { + pNewCursor->SetMark(); + *pNewCursor->GetMark() = *rUnoCursor.GetMark(); + } + const CursorType eSetType = (CursorType::TableText == m_pImpl->m_eType) + ? CursorType::SelectionInTable : CursorType::Selection; + SwTableNode const*const pStartNode( (CursorType::TableText == m_pImpl->m_eType) + ? rUnoCursor.GetPoint()->nNode.GetNode().FindTableNode() + : nullptr); + SwTable const*const pTable( + pStartNode ? & pStartNode->GetTable() : nullptr ); + return SwXParagraphEnumeration::Create(pParentText, pNewCursor, eSetType, pStartNode, pTable); +} + +uno::Type SAL_CALL +SwXTextCursor::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SwXTextCursor::hasElements() +{ + return true; +} + +uno::Sequence< OUString > SAL_CALL +SwXTextCursor::getAvailableServiceNames() +{ + uno::Sequence<OUString> aRet { "com.sun.star.text.TextContent" }; + return aRet; +} + +IMPLEMENT_FORWARD_REFCOUNT( SwXTextCursor,SwXTextCursor_Base ) + +uno::Any SAL_CALL +SwXTextCursor::queryInterface(const uno::Type& rType) +{ + return (rType == cppu::UnoType<lang::XUnoTunnel>::get()) + ? OTextCursorHelper::queryInterface(rType) + : SwXTextCursor_Base::queryInterface(rType); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoobj2.cxx b/sw/source/core/unocore/unoobj2.cxx new file mode 100644 index 000000000..9f0dc5d8e --- /dev/null +++ b/sw/source/core/unocore/unoobj2.cxx @@ -0,0 +1,1692 @@ +/* -*- 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 <sal/config.h> + +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/safeint.hxx> +#include <svl/listener.hxx> +#include <svx/svdobj.hxx> +#include <vcl/svapp.hxx> + +#include <anchoredobject.hxx> +#include <swtypes.hxx> +#include <hintids.hxx> +#include <IMark.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <textboxhelper.hxx> +#include <ndtxt.hxx> +#include <unocrsr.hxx> +#include <swundo.hxx> +#include <rootfrm.hxx> +#include <ftnidx.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <swtblfmt.hxx> +#include <docsh.hxx> +#include <pagedesc.hxx> +#include <cntfrm.hxx> +#include <unoparaframeenum.hxx> +#include <unofootnote.hxx> +#include <unotextbodyhf.hxx> +#include <unotextrange.hxx> +#include <unoparagraph.hxx> +#include <unomap.hxx> +#include <unoport.hxx> +#include <unocrsrhelper.hxx> +#include <unotbl.hxx> +#include <fmtanchr.hxx> +#include <flypos.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtcntnt.hxx> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unoframe.hxx> +#include <fmthdft.hxx> +#include <fmtflcnt.hxx> +#include <vector> +#include <sortedobjs.hxx> +#include <frameformats.hxx> +#include <algorithm> +#include <iterator> + +using namespace ::com::sun::star; + +namespace sw { + +void DeepCopyPaM(SwPaM const & rSource, SwPaM & rTarget) +{ + rTarget = rSource; + + if (rSource.GetNext() != &rSource) + { + SwPaM *pPam = const_cast<SwPaM*>(rSource.GetNext()); + do + { + // create new PaM + SwPaM *const pNew = new SwPaM(*pPam, nullptr); + // insert into ring + pNew->MoveTo(&rTarget); + pPam = pPam->GetNext(); + } + while (pPam != &rSource); + } +} + +} // namespace sw + +namespace { + +struct FrameClientSortListLess +{ + bool operator() (FrameClientSortListEntry const& r1, + FrameClientSortListEntry const& r2) const + { + return (r1.nIndex < r2.nIndex) + || ((r1.nIndex == r2.nIndex) && (r1.nOrder < r2.nOrder)); + } +}; + + void lcl_CollectFrameAtNodeWithLayout(const SwContentFrame* pCFrame, + FrameClientSortList_t& rFrames, + const RndStdIds nAnchorType) + { + auto pObjs = pCFrame->GetDrawObjs(); + if(!pObjs) + return; + for(const auto pAnchoredObj : *pObjs) + { + SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + // Filter out textboxes, which are not interesting at a UNO level. + if(SwTextBoxHelper::isTextBox(&rFormat, RES_FLYFRMFMT)) + continue; + if(rFormat.GetAnchor().GetAnchorId() == nAnchorType) + { + const auto nIdx = + rFormat.GetAnchor().GetContentAnchor()->nContent.GetIndex(); + const auto nOrder = rFormat.GetAnchor().GetOrder(); + FrameClientSortListEntry entry(nIdx, nOrder, std::make_shared<sw::FrameClient>(&rFormat)); + rFrames.push_back(entry); + } + } + } +} + + +void CollectFrameAtNode( const SwNodeIndex& rIdx, + FrameClientSortList_t& rFrames, + const bool bAtCharAnchoredObjs ) +{ + // _bAtCharAnchoredObjs: + // <true>: at-character anchored objects are collected + // <false>: at-paragraph anchored objects are collected + + // search all borders, images, and OLEs that are connected to the paragraph + SwDoc* pDoc = rIdx.GetNode().GetDoc(); + + const auto nChkType = bAtCharAnchoredObjs ? RndStdIds::FLY_AT_CHAR : RndStdIds::FLY_AT_PARA; + const SwContentFrame* pCFrame; + const SwContentNode* pCNd; + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && + nullptr != (pCNd = rIdx.GetNode().GetContentNode()) && + nullptr != (pCFrame = pCNd->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout())) ) + { + lcl_CollectFrameAtNodeWithLayout(pCFrame, rFrames, nChkType); + } + else + { + const SwFrameFormats& rFormats = *pDoc->GetSpzFrameFormats(); + const size_t nSize = rFormats.size(); + for ( size_t i = 0; i < nSize; i++) + { + const SwFrameFormat* pFormat = rFormats[ i ]; + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + const SwPosition* pAnchorPos; + if( rAnchor.GetAnchorId() == nChkType && + nullptr != (pAnchorPos = rAnchor.GetContentAnchor()) && + pAnchorPos->nNode == rIdx ) + { + + // OD 2004-05-07 #i28701# - determine insert position for + // sorted <rFrameArr> + const sal_Int32 nIndex = pAnchorPos->nContent.GetIndex(); + sal_uInt32 nOrder = rAnchor.GetOrder(); + + FrameClientSortListEntry entry(nIndex, nOrder, std::make_shared<sw::FrameClient>(const_cast<SwFrameFormat*>(pFormat))); + rFrames.push_back(entry); + } + } + std::sort(rFrames.begin(), rFrames.end(), FrameClientSortListLess()); + } +} + +UnoActionContext::UnoActionContext(SwDoc *const pDoc) + : m_pDoc(pDoc) +{ + SwRootFrame *const pRootFrame = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if (pRootFrame) + { + pRootFrame->StartAllAction(); + } +} + +UnoActionContext::~UnoActionContext() COVERITY_NOEXCEPT_FALSE +{ + // Doc may already have been removed here + if (m_pDoc) + { + SwRootFrame *const pRootFrame = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if (pRootFrame) + { + pRootFrame->EndAllAction(); + } + } +} + +static void lcl_RemoveImpl(SwDoc *const pDoc) +{ + assert(pDoc); + SwRootFrame *const pRootFrame = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if (pRootFrame) + { + pRootFrame->UnoRemoveAllActions(); + } +} + +UnoActionRemoveContext::UnoActionRemoveContext(SwDoc *const pDoc) + : m_pDoc(pDoc) +{ + lcl_RemoveImpl(m_pDoc); +} + +static SwDoc * lcl_IsNewStyleTable(SwUnoTableCursor const& rCursor) +{ + SwTableNode *const pTableNode = rCursor.GetNode().FindTableNode(); + return (pTableNode && !pTableNode->GetTable().IsNewModel()) + ? rCursor.GetDoc() + : nullptr; +} + +UnoActionRemoveContext::UnoActionRemoveContext(SwUnoTableCursor const& rCursor) + : m_pDoc(lcl_IsNewStyleTable(rCursor)) +{ + // this insanity is only necessary for old-style tables + // because SwRootFrame::MakeTableCursors() creates the table cursor for these + if (m_pDoc) + { + lcl_RemoveImpl(m_pDoc); + } +} + +UnoActionRemoveContext::~UnoActionRemoveContext() COVERITY_NOEXCEPT_FALSE +{ + if (m_pDoc) + { + SwRootFrame *const pRootFrame = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if (pRootFrame) + { + pRootFrame->UnoRestoreAllActions(); + } + } +} + +void SwUnoCursorHelper::SetCursorAttr(SwPaM & rPam, + const SfxItemSet& rSet, + const SetAttrMode nAttrMode, const bool bTableMode) +{ + const SetAttrMode nFlags = nAttrMode | SetAttrMode::APICALL; + SwDoc* pDoc = rPam.GetDoc(); + //StartEndAction + UnoActionContext aAction(pDoc); + if (rPam.GetNext() != &rPam) // Ring of Cursors + { + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr); + + for(SwPaM& rCurrent : rPam.GetRingContainer()) + { + if (rCurrent.HasMark() && + ( bTableMode || + (*rCurrent.GetPoint() != *rCurrent.GetMark()) )) + { + pDoc->getIDocumentContentOperations().InsertItemSet(rCurrent, rSet, nFlags); + } + } + + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr); + } + else + { + pDoc->getIDocumentContentOperations().InsertItemSet( rPam, rSet, nFlags ); + } + + if( rSet.GetItemState( RES_PARATR_OUTLINELEVEL, false ) >= SfxItemState::DEFAULT ) + { + SwTextNode * pTmpNode = rPam.GetNode().GetTextNode(); + if ( pTmpNode ) + { + rPam.GetDoc()->GetNodes().UpdateOutlineNode( *pTmpNode ); + } + } +} + +// #i63870# +// split third parameter <bCurrentAttrOnly> into new parameters <bOnlyTextAttr> +// and <bGetFromChrFormat> to get better control about resulting <SfxItemSet> +void SwUnoCursorHelper::GetCursorAttr(SwPaM & rPam, + SfxItemSet & rSet, const bool bOnlyTextAttr, const bool bGetFromChrFormat) +{ + static const sal_uLong nMaxLookup = 1000; + SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() ); + SfxItemSet *pSet = &rSet; + for(SwPaM& rCurrent : rPam.GetRingContainer()) + { + SwPosition const & rStart( *rCurrent.Start() ); + SwPosition const & rEnd( *rCurrent.End() ); + const sal_uLong nSttNd = rStart.nNode.GetIndex(); + const sal_uLong nEndNd = rEnd .nNode.GetIndex(); + + if (nEndNd - nSttNd >= nMaxLookup) + { + rSet.ClearItem(); + rSet.InvalidateAllItems(); + return;// uno::Any(); + } + + // the first node inserts the values into the get set + // all other nodes merge their values into the get set + for (sal_uLong n = nSttNd; n <= nEndNd; ++n) + { + SwNode *const pNd = rPam.GetDoc()->GetNodes()[ n ]; + switch (pNd->GetNodeType()) + { + case SwNodeType::Text: + { + const sal_Int32 nStart = (n == nSttNd) + ? rStart.nContent.GetIndex() : 0; + const sal_Int32 nEnd = (n == nEndNd) + ? rEnd.nContent.GetIndex() + : pNd->GetTextNode()->GetText().getLength(); + pNd->GetTextNode()->GetParaAttr(*pSet, nStart, nEnd, bOnlyTextAttr, bGetFromChrFormat); + } + break; + + case SwNodeType::Grf: + case SwNodeType::Ole: + static_cast<SwContentNode*>(pNd)->GetAttr( *pSet ); + break; + + default: + continue; // skip this node + } + + if (pSet != &rSet) + { + rSet.MergeValues( aSet ); + } + else + { + pSet = &aSet; + } + + if (aSet.Count()) + { + aSet.ClearItem(); + } + } + } +} + +namespace { + +struct SwXParagraphEnumerationImpl final : public SwXParagraphEnumeration +{ + uno::Reference< text::XText > const m_xParentText; + const CursorType m_eCursorType; + /// Start node of the cell _or_ table the enumeration belongs to. + /// Used to restrict the movement of the UNO cursor to the cell and its + /// embedded tables. + SwStartNode const*const m_pOwnStartNode; + SwTable const*const m_pOwnTable; + const sal_uLong m_nEndIndex; + sal_Int32 m_nFirstParaStart; + sal_Int32 m_nLastParaEnd; + bool m_bFirstParagraph; + uno::Reference< text::XTextContent > m_xNextPara; + sw::UnoCursorPointer m_pCursor; + + SwXParagraphEnumerationImpl( + uno::Reference< text::XText > const& xParent, + const std::shared_ptr<SwUnoCursor>& pCursor, + const CursorType eType, + SwStartNode const*const pStartNode, SwTable const*const pTable) + : m_xParentText( xParent ) + , m_eCursorType( eType ) + // remember table and start node for later travelling + // (used in export of tables in tables) + , m_pOwnStartNode( pStartNode ) + // for import of tables in tables we have to remember the actual + // table and start node of the current position in the enumeration. + , m_pOwnTable( pTable ) + , m_nEndIndex( pCursor->End()->nNode.GetIndex() ) + , m_nFirstParaStart( -1 ) + , m_nLastParaEnd( -1 ) + , m_bFirstParagraph( true ) + , m_pCursor(pCursor) + { + OSL_ENSURE(m_xParentText.is(), "SwXParagraphEnumeration: no parent?"); + OSL_ENSURE( !((CursorType::SelectionInTable == eType) || + (CursorType::TableText == eType)) + || (m_pOwnTable && m_pOwnStartNode), + "SwXParagraphEnumeration: table type but no start node or table?"); + + if ((CursorType::Selection == m_eCursorType) || + (CursorType::SelectionInTable == m_eCursorType)) + { + SwUnoCursor & rCursor = GetCursor(); + rCursor.Normalize(); + m_nFirstParaStart = rCursor.GetPoint()->nContent.GetIndex(); + m_nLastParaEnd = rCursor.GetMark()->nContent.GetIndex(); + rCursor.DeleteMark(); + } + } + + virtual ~SwXParagraphEnumerationImpl() override + { m_pCursor.reset(nullptr); } + virtual void SAL_CALL release() throw () override + { + SolarMutexGuard g; + OWeakObject::release(); + } + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { return "SwXParagraphEnumeration"; } + virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName) override + { return cppu::supportsService(this, rServiceName); }; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override + { return {"com.sun.star.text.ParagraphEnumeration"}; }; + + // XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual css::uno::Any SAL_CALL nextElement() override; + + SwUnoCursor& GetCursor() + { return *m_pCursor; } + /// @throws container::NoSuchElementException + /// @throws lang::WrappedTargetException + /// @throws uno::RuntimeException + uno::Reference< text::XTextContent > NextElement_Impl(); + + /** + * Determines if the last element in the enumeration should be ignored or + * not. + */ + bool IgnoreLastElement(SwUnoCursor& rCursor, bool bMovedFromTable); +}; + +} + +SwXParagraphEnumeration* SwXParagraphEnumeration::Create( + uno::Reference< text::XText > const& xParent, + const std::shared_ptr<SwUnoCursor>& pCursor, + const CursorType eType, + SwStartNode const*const pStartNode, + SwTable const*const pTable) +{ + return new SwXParagraphEnumerationImpl(xParent, pCursor, eType, pStartNode, pTable); +} + +sal_Bool SAL_CALL +SwXParagraphEnumerationImpl::hasMoreElements() +{ + SolarMutexGuard aGuard; + return m_bFirstParagraph || m_xNextPara.is(); +} + +//!! compare to SwShellTableCursor::FillRects() in viscrs.cxx +static SwTableNode * +lcl_FindTopLevelTable( + SwTableNode *const pTableNode, SwTable const*const pOwnTable) +{ + // find top-most table in current context (section) level + + SwTableNode * pLast = pTableNode; + for (SwTableNode* pTmp = pLast; + pTmp != nullptr && &pTmp->GetTable() != pOwnTable; /* we must not go up higher than the own table! */ + pTmp = pTmp->StartOfSectionNode()->FindTableNode() ) + { + pLast = pTmp; + } + return pLast; +} + +static bool +lcl_CursorIsInSection( + SwUnoCursor const*const pUnoCursor, SwStartNode const*const pOwnStartNode) +{ + // returns true if the cursor is in the section (or in a sub section!) + // represented by pOwnStartNode + + bool bRes = true; + if (pUnoCursor && pOwnStartNode) + { + const SwEndNode * pOwnEndNode = pOwnStartNode->EndOfSectionNode(); + bRes = pOwnStartNode->GetIndex() <= pUnoCursor->Start()->nNode.GetIndex() && + pUnoCursor->End()->nNode.GetIndex() <= pOwnEndNode->GetIndex(); + } + return bRes; +} + +bool SwXParagraphEnumerationImpl::IgnoreLastElement(SwUnoCursor& rCursor, bool bMovedFromTable) +{ + // Ignore the last element of a selection enumeration if this is a stub + // paragraph (directly after table, selection ends at paragaph start). + + if (rCursor.Start()->nNode.GetIndex() != m_nEndIndex) + return false; + + if (m_eCursorType != CursorType::Selection) + return false; + + if (!bMovedFromTable) + return false; + + return m_nLastParaEnd == 0; +} + +uno::Reference< text::XTextContent > +SwXParagraphEnumerationImpl::NextElement_Impl() +{ + SwUnoCursor& rUnoCursor = GetCursor(); + + // check for exceeding selections + if (!m_bFirstParagraph && + ((CursorType::Selection == m_eCursorType) || + (CursorType::SelectionInTable == m_eCursorType))) + { + SwPosition* pStart = rUnoCursor.Start(); + auto aNewCursor(rUnoCursor.GetDoc()->CreateUnoCursor(*pStart)); + // one may also go into tables here + if (CursorType::SelectionInTable != m_eCursorType) + { + aNewCursor->SetRemainInSection( false ); + } + + // os 2005-01-14: This part is only necessary to detect movements out + // of a selection; if there is no selection we don't have to care + SwTableNode *const pTableNode = aNewCursor->GetNode().FindTableNode(); + bool bMovedFromTable = false; + if (CursorType::SelectionInTable != m_eCursorType && pTableNode) + { + aNewCursor->GetPoint()->nNode = pTableNode->EndOfSectionIndex(); + aNewCursor->Move(fnMoveForward, GoInNode); + bMovedFromTable = true; + } + else + { + aNewCursor->MovePara(GoNextPara, fnParaStart); + } + if (m_nEndIndex < aNewCursor->Start()->nNode.GetIndex()) + { + return nullptr; + } + + if (IgnoreLastElement(*aNewCursor, bMovedFromTable)) + { + return nullptr; + } + } + + bool bInTable = false; + if (!m_bFirstParagraph) + { + rUnoCursor.SetRemainInSection( false ); + // what to do if already in a table? + SwTableNode * pTableNode = rUnoCursor.GetNode().FindTableNode(); + pTableNode = lcl_FindTopLevelTable( pTableNode, m_pOwnTable ); + if (pTableNode && (&pTableNode->GetTable() != m_pOwnTable)) + { + // this is a foreign table: go to end + rUnoCursor.GetPoint()->nNode = pTableNode->EndOfSectionIndex(); + if (!rUnoCursor.Move(fnMoveForward, GoInNode)) + { + return nullptr; + } + bInTable = true; + } + } + + uno::Reference< text::XTextContent > xRef; + // the cursor must remain in the current section or a subsection + // before AND after the movement... + if (lcl_CursorIsInSection( &rUnoCursor, m_pOwnStartNode ) && + (m_bFirstParagraph || bInTable || + (rUnoCursor.MovePara(GoNextPara, fnParaStart) && + lcl_CursorIsInSection( &rUnoCursor, m_pOwnStartNode )))) + { + if (m_eCursorType == CursorType::Selection || m_eCursorType == CursorType::SelectionInTable) + { + // This is a selection, check if the cursor would go past the end + // of the selection. + if (rUnoCursor.Start()->nNode.GetIndex() > m_nEndIndex) + return nullptr; + } + + SwPosition* pStart = rUnoCursor.Start(); + const sal_Int32 nFirstContent = + m_bFirstParagraph ? m_nFirstParaStart : -1; + const sal_Int32 nLastContent = + (m_nEndIndex == pStart->nNode.GetIndex()) ? m_nLastParaEnd : -1; + + // position in a table, or in a simple paragraph? + SwTableNode * pTableNode = rUnoCursor.GetNode().FindTableNode(); + pTableNode = lcl_FindTopLevelTable( pTableNode, m_pOwnTable ); + if (/*CursorType::TableText != eCursorType && CursorType::SelectionInTable != eCursorType && */ + pTableNode && (&pTableNode->GetTable() != m_pOwnTable)) + { + // this is a foreign table + SwFrameFormat* pTableFormat = pTableNode->GetTable().GetFrameFormat(); + xRef = SwXTextTable::CreateXTextTable(pTableFormat); + } + else + { + text::XText *const pText = m_xParentText.get(); + xRef = SwXParagraph::CreateXParagraph(*rUnoCursor.GetDoc(), + pStart->nNode.GetNode().GetTextNode(), + static_cast<SwXText*>(pText), nFirstContent, nLastContent); + } + } + + return xRef; +} + +uno::Any SAL_CALL SwXParagraphEnumerationImpl::nextElement() +{ + SolarMutexGuard aGuard; + if (m_bFirstParagraph) + { + m_xNextPara = NextElement_Impl(); + m_bFirstParagraph = false; + } + const uno::Reference< text::XTextContent > xRef = m_xNextPara; + if (!xRef.is()) + { + throw container::NoSuchElementException(); + } + m_xNextPara = NextElement_Impl(); + + uno::Any aRet; + aRet <<= xRef; + return aRet; +} + +class SwXTextRange::Impl + : public SvtListener +{ +public: + const SfxItemPropertySet& m_rPropSet; + const enum RangePosition m_eRangePosition; + SwDoc& m_rDoc; + uno::Reference<text::XText> m_xParentText; + const SwFrameFormat* m_pTableFormat; + const ::sw::mark::IMark* m_pMark; + + Impl(SwDoc& rDoc, const enum RangePosition eRange, + SwFrameFormat* const pTableFormat, + const uno::Reference<text::XText>& xParent = nullptr) + : m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR)) + , m_eRangePosition(eRange) + , m_rDoc(rDoc) + , m_xParentText(xParent) + , m_pTableFormat(pTableFormat) + , m_pMark(nullptr) + { + m_pTableFormat && StartListening(pTableFormat->GetNotifier()); + } + + virtual ~Impl() override + { + // Impl owns the bookmark; delete it here: SolarMutex is locked + Invalidate(); + } + + void Invalidate() + { + if (m_pMark) + { + m_rDoc.getIDocumentMarkAccess()->deleteMark(m_pMark); + m_pMark = nullptr; + } + m_pTableFormat = nullptr; + EndListeningAll(); + } + + const ::sw::mark::IMark* GetBookmark() const { return m_pMark; } + void SetMark(::sw::mark::IMark& rMark) + { + EndListeningAll(); + m_pTableFormat = nullptr; + m_pMark = &rMark; + StartListening(rMark.GetNotifier()); + } + +protected: + virtual void Notify(const SfxHint&) override; +}; + +void SwXTextRange::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + EndListeningAll(); + m_pTableFormat = nullptr; + m_pMark = nullptr; + } +} + +SwXTextRange::SwXTextRange(SwPaM const & rPam, + const uno::Reference< text::XText > & xParent, + const enum RangePosition eRange) + : m_pImpl( new SwXTextRange::Impl(*rPam.GetDoc(), eRange, nullptr, xParent) ) +{ + SetPositions(rPam); +} + +SwXTextRange::SwXTextRange(SwFrameFormat& rTableFormat) + : m_pImpl( + new SwXTextRange::Impl(*rTableFormat.GetDoc(), RANGE_IS_TABLE, &rTableFormat) ) +{ + SwTable *const pTable = SwTable::FindTable( &rTableFormat ); + SwTableNode *const pTableNode = pTable->GetTableNode(); + SwPosition aPosition( *pTableNode ); + SwPaM aPam( aPosition ); + + SetPositions( aPam ); +} + +SwXTextRange::~SwXTextRange() +{ +} + +const SwDoc& SwXTextRange::GetDoc() const +{ + return m_pImpl->m_rDoc; +} + +SwDoc& SwXTextRange::GetDoc() +{ + return m_pImpl->m_rDoc; +} + +void SwXTextRange::Invalidate() +{ + m_pImpl->Invalidate(); +} + +void SwXTextRange::SetPositions(const SwPaM& rPam) +{ + m_pImpl->Invalidate(); + IDocumentMarkAccess* const pMA = m_pImpl->m_rDoc.getIDocumentMarkAccess(); + auto pMark = pMA->makeMark(rPam, OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, sw::mark::InsertMode::New); + m_pImpl->SetMark(*pMark); +} + +void SwXTextRange::DeleteAndInsert( + const OUString& rText, const bool bForceExpandHints) +{ + if (RANGE_IS_TABLE == m_pImpl->m_eRangePosition) + { + // setString on table not allowed + throw uno::RuntimeException(); + } + + const SwPosition aPos(GetDoc().GetNodes().GetEndOfContent()); + SwCursor aCursor(aPos, nullptr); + if (GetPositions(aCursor)) + { + UnoActionContext aAction(& m_pImpl->m_rDoc); + m_pImpl->m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + if (aCursor.HasMark()) + { + m_pImpl->m_rDoc.getIDocumentContentOperations().DeleteAndJoin(aCursor); + } + + if (!rText.isEmpty()) + { + SwUnoCursorHelper::DocInsertStringSplitCR( + m_pImpl->m_rDoc, aCursor, rText, bForceExpandHints); + + SwUnoCursorHelper::SelectPam(aCursor, true); + aCursor.Left(rText.getLength()); + } + SetPositions(aCursor); + m_pImpl->m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); + } +} + +namespace +{ + class theSwXTextRangeUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextRangeUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextRange::getUnoTunnelId() +{ + return theSwXTextRangeUnoTunnelId::get().getSeq(); +} + +// XUnoTunnel +sal_Int64 SAL_CALL +SwXTextRange::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXTextRange>(rId, this); +} + +OUString SAL_CALL +SwXTextRange::getImplementationName() +{ + return "SwXTextRange"; +} + +sal_Bool SAL_CALL SwXTextRange::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXTextRange::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextRange", + "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesAsian", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.ParagraphProperties", + "com.sun.star.style.ParagraphPropertiesAsian", + "com.sun.star.style.ParagraphPropertiesComplex" + }; +} + +uno::Reference< text::XText > SAL_CALL +SwXTextRange::getText() +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_xParentText.is()) + { + if (m_pImpl->m_eRangePosition == RANGE_IS_TABLE && + m_pImpl->m_pTableFormat) + { + SwTable const*const pTable = SwTable::FindTable( m_pImpl->m_pTableFormat ); + SwTableNode const*const pTableNode = pTable->GetTableNode(); + const SwPosition aPosition( *pTableNode ); + m_pImpl->m_xParentText = + ::sw::CreateParentXText(m_pImpl->m_rDoc, aPosition); + } + } + OSL_ENSURE(m_pImpl->m_xParentText.is(), "SwXTextRange::getText: no text"); + return m_pImpl->m_xParentText; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXTextRange::getStart() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + ::sw::mark::IMark const * const pBkmk = m_pImpl->GetBookmark(); + if (!m_pImpl->m_xParentText.is()) + { + getText(); + } + if(pBkmk) + { + SwPaM aPam(pBkmk->GetMarkStart()); + xRet = new SwXTextRange(aPam, m_pImpl->m_xParentText); + } + else if (RANGE_IS_TABLE == m_pImpl->m_eRangePosition) + { + // start and end are this, if it's a table + xRet = this; + } + else + { + throw uno::RuntimeException(); + } + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXTextRange::getEnd() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + ::sw::mark::IMark const * const pBkmk = m_pImpl->GetBookmark(); + if (!m_pImpl->m_xParentText.is()) + { + getText(); + } + if(pBkmk) + { + SwPaM aPam(pBkmk->GetMarkEnd()); + xRet = new SwXTextRange(aPam, m_pImpl->m_xParentText); + } + else if (RANGE_IS_TABLE == m_pImpl->m_eRangePosition) + { + // start and end are this, if it's a table + xRet = this; + } + else + { + throw uno::RuntimeException(); + } + return xRet; +} + +OUString SAL_CALL SwXTextRange::getString() +{ + SolarMutexGuard aGuard; + + OUString sRet; + // for tables there is no bookmark, thus also no text + // one could export the table as ASCII here maybe? + SwPaM aPaM(GetDoc().GetNodes()); + if (GetPositions(aPaM) && aPaM.HasMark()) + { + SwUnoCursorHelper::GetTextFromPam(aPaM, sRet); + } + return sRet; +} + +void SAL_CALL SwXTextRange::setString(const OUString& rString) +{ + SolarMutexGuard aGuard; + + DeleteAndInsert(rString, false); +} + +bool SwXTextRange::GetPositions(SwPaM& rToFill) const +{ + ::sw::mark::IMark const * const pBkmk = m_pImpl->GetBookmark(); + if(pBkmk) + { + *rToFill.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + { + rToFill.SetMark(); + *rToFill.GetMark() = pBkmk->GetOtherMarkPos(); + } + else + { + rToFill.DeleteMark(); + } + return true; + } + return false; +} + +namespace sw { + +bool XTextRangeToSwPaM( SwUnoInternalPaM & rToFill, + const uno::Reference< text::XTextRange > & xTextRange) +{ + bool bRet = false; + + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + SwXTextPortion* pPortion = nullptr; + SwXText* pText = nullptr; + SwXParagraph* pPara = nullptr; + if(xRangeTunnel.is()) + { + pRange = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + pPortion= + ::sw::UnoTunnelGetImplementation<SwXTextPortion>(xRangeTunnel); + pText = ::sw::UnoTunnelGetImplementation<SwXText>(xRangeTunnel); + pPara = ::sw::UnoTunnelGetImplementation<SwXParagraph>(xRangeTunnel); + } + + // if it's a text then create a temporary cursor there and re-use + // the pCursor variable + // #i108489#: Reference in outside scope to keep cursor alive + uno::Reference< text::XTextCursor > xTextCursor; + if (pText) + { + xTextCursor.set( pText->CreateCursor() ); + xTextCursor->gotoEnd(true); + pCursor = + comphelper::getUnoTunnelImplementation<OTextCursorHelper>(xTextCursor); + } + if(pRange && &pRange->GetDoc() == rToFill.GetDoc()) + { + bRet = pRange->GetPositions(rToFill); + } + else + { + if (pPara) + { + bRet = pPara->SelectPaM(rToFill); + } + else + { + SwDoc* const pDoc = pCursor ? pCursor->GetDoc() + : (pPortion ? pPortion->GetCursor().GetDoc() : nullptr); + const SwPaM* const pUnoCursor = pCursor ? pCursor->GetPaM() + : (pPortion ? &pPortion->GetCursor() : nullptr); + if (pUnoCursor && pDoc == rToFill.GetDoc()) + { + OSL_ENSURE(!pUnoCursor->IsMultiSelection(), + "what to do about rings?"); + bRet = true; + *rToFill.GetPoint() = *pUnoCursor->GetPoint(); + if (pUnoCursor->HasMark()) + { + rToFill.SetMark(); + *rToFill.GetMark() = *pUnoCursor->GetMark(); + } + else + rToFill.DeleteMark(); + } + } + } + return bRet; +} + +static bool +lcl_IsStartNodeInFormat(const bool bHeader, SwStartNode const *const pSttNode, + SwFrameFormat const*const pFrameFormat, SwFrameFormat*& rpFormat) +{ + bool bRet = false; + const SfxItemSet& rSet = pFrameFormat->GetAttrSet(); + const SfxPoolItem* pItem; + if (SfxItemState::SET == rSet.GetItemState( + bHeader ? sal_uInt16(RES_HEADER) : sal_uInt16(RES_FOOTER), + true, &pItem)) + { + SfxPoolItem *const pItemNonConst(const_cast<SfxPoolItem *>(pItem)); + SwFrameFormat *const pHeadFootFormat = bHeader ? + static_cast<SwFormatHeader*>(pItemNonConst)->GetHeaderFormat() : + static_cast<SwFormatFooter*>(pItemNonConst)->GetFooterFormat(); + if (pHeadFootFormat) + { + const SwFormatContent& rFlyContent = pHeadFootFormat->GetContent(); + const SwNode& rNode = rFlyContent.GetContentIdx()->GetNode(); + SwStartNode const*const pCurSttNode = rNode.FindSttNodeByType( + bHeader ? SwHeaderStartNode : SwFooterStartNode); + if (pCurSttNode && (pCurSttNode == pSttNode)) + { + rpFormat = pHeadFootFormat; + bRet = true; + } + } + } + return bRet; +} + +} // namespace sw + +uno::Reference< text::XTextRange > +SwXTextRange::CreateXTextRange( + SwDoc & rDoc, const SwPosition& rPos, const SwPosition *const pMark) +{ + const uno::Reference<text::XText> xParentText( + ::sw::CreateParentXText(rDoc, rPos)); + const auto pNewCursor(rDoc.CreateUnoCursor(rPos)); + if(pMark) + { + pNewCursor->SetMark(); + *pNewCursor->GetMark() = *pMark; + } + const bool isCell( dynamic_cast<SwXCell*>(xParentText.get()) ); + const uno::Reference< text::XTextRange > xRet( + new SwXTextRange(*pNewCursor, xParentText, + isCell ? RANGE_IN_CELL : RANGE_IN_TEXT) ); + return xRet; +} + +namespace sw { + +uno::Reference< text::XText > +CreateParentXText(SwDoc & rDoc, const SwPosition& rPos) +{ + uno::Reference< text::XText > xParentText; + SwStartNode* pSttNode = rPos.nNode.GetNode().StartOfSectionNode(); + while(pSttNode && pSttNode->IsSectionNode()) + { + pSttNode = pSttNode->StartOfSectionNode(); + } + SwStartNodeType eType = pSttNode ? pSttNode->GetStartNodeType() : SwNormalStartNode; + switch(eType) + { + case SwTableBoxStartNode: + { + SwTableNode const*const pTableNode = pSttNode->FindTableNode(); + SwFrameFormat *const pTableFormat = + pTableNode->GetTable().GetFrameFormat(); + SwTableBox *const pBox = pSttNode->GetTableBox(); + + xParentText = pBox + ? SwXCell::CreateXCell( pTableFormat, pBox ) + : new SwXCell( pTableFormat, *pSttNode ); + } + break; + case SwFlyStartNode: + { + SwFrameFormat *const pFormat = pSttNode->GetFlyFormat(); + if (nullptr != pFormat) + { + xParentText.set(SwXTextFrame::CreateXTextFrame(rDoc, pFormat), + uno::UNO_QUERY); + } + } + break; + case SwHeaderStartNode: + case SwFooterStartNode: + { + const bool bHeader = (SwHeaderStartNode == eType); + const size_t nPDescCount = rDoc.GetPageDescCnt(); + for(size_t i = 0; i < nPDescCount; i++) + { + const SwPageDesc& rDesc = rDoc.GetPageDesc( i ); + + const SwFrameFormat* pFrameFormatMaster = &rDesc.GetMaster(); + const SwFrameFormat* pFrameFormatLeft = &rDesc.GetLeft(); + const SwFrameFormat* pFrameFormatFirstMaster = &rDesc.GetFirstMaster(); + const SwFrameFormat* pFrameFormatFirstLeft = &rDesc.GetFirstLeft(); + + SwFrameFormat* pHeadFootFormat = nullptr; + if (!lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatMaster, + pHeadFootFormat)) + { + if (!lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatLeft, + pHeadFootFormat)) + { + if (!lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatFirstMaster, + pHeadFootFormat)) + { + lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatFirstLeft, pHeadFootFormat); + } + } + } + + if (pHeadFootFormat) + { + xParentText = SwXHeadFootText::CreateXHeadFootText( + *pHeadFootFormat, bHeader); + } + } + } + break; + case SwFootnoteStartNode: + { + const size_t nFootnoteCnt = rDoc.GetFootnoteIdxs().size(); + for (size_t n = 0; n < nFootnoteCnt; ++n ) + { + const SwTextFootnote* pTextFootnote = rDoc.GetFootnoteIdxs()[ n ]; + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + pTextFootnote = rFootnote.GetTextFootnote(); + + if (pSttNode == pTextFootnote->GetStartNode()->GetNode(). + FindSttNodeByType(SwFootnoteStartNode)) + { + xParentText.set(SwXFootnote::CreateXFootnote(rDoc, + &const_cast<SwFormatFootnote&>(rFootnote)), uno::UNO_QUERY); + break; + } + } + } + break; + default: + { + // then it is the body text + const uno::Reference<frame::XModel> xModel = + rDoc.GetDocShell()->GetBaseModel(); + const uno::Reference< text::XTextDocument > xDoc( + xModel, uno::UNO_QUERY); + xParentText = xDoc->getText(); + } + } + OSL_ENSURE(xParentText.is(), "no parent text?"); + return xParentText; +} + +} // namespace sw + +uno::Reference< container::XEnumeration > SAL_CALL +SwXTextRange::createContentEnumeration(const OUString& rServiceName) +{ + SolarMutexGuard g; + + if ( rServiceName != "com.sun.star.text.TextContent" ) + { + throw uno::RuntimeException(); + } + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + const SwPosition aPos(GetDoc().GetNodes().GetEndOfContent()); + const auto pNewCursor(m_pImpl->m_rDoc.CreateUnoCursor(aPos)); + if (!GetPositions(*pNewCursor)) + { + throw uno::RuntimeException(); + } + + return SwXParaFrameEnumeration::Create(*pNewCursor, PARAFRAME_PORTION_TEXTRANGE); +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXTextRange::createEnumeration() +{ + SolarMutexGuard g; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + const SwPosition aPos(GetDoc().GetNodes().GetEndOfContent()); + auto pNewCursor(m_pImpl->m_rDoc.CreateUnoCursor(aPos)); + if (!GetPositions(*pNewCursor)) + { + throw uno::RuntimeException(); + } + if (!m_pImpl->m_xParentText.is()) + { + getText(); + } + + const CursorType eSetType = (RANGE_IN_CELL == m_pImpl->m_eRangePosition) + ? CursorType::SelectionInTable : CursorType::Selection; + return SwXParagraphEnumeration::Create(m_pImpl->m_xParentText, pNewCursor, eSetType); +} + +uno::Type SAL_CALL SwXTextRange::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SwXTextRange::hasElements() +{ + return true; +} + +uno::Sequence< OUString > SAL_CALL +SwXTextRange::getAvailableServiceNames() +{ + uno::Sequence<OUString> aRet { "com.sun.star.text.TextContent" }; + return aRet; +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXTextRange::getPropertySetInfo() +{ + SolarMutexGuard aGuard; + + static uno::Reference< beans::XPropertySetInfo > xRef = + m_pImpl->m_rPropSet.getPropertySetInfo(); + return xRef; +} + +void SAL_CALL +SwXTextRange::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + GetPositions(aPaM); + SwUnoCursorHelper::SetPropertyValue(aPaM, m_pImpl->m_rPropSet, + rPropertyName, rValue); +} + +uno::Any SAL_CALL +SwXTextRange::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + GetPositions(aPaM); + return SwUnoCursorHelper::GetPropertyValue(aPaM, m_pImpl->m_rPropSet, + rPropertyName); +} + +void SAL_CALL +SwXTextRange::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextRange::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextRange::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextRange::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextRange::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextRange::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextRange::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextRange::removeVetoableChangeListener(): not implemented"); +} + +beans::PropertyState SAL_CALL +SwXTextRange::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + GetPositions(aPaM); + return SwUnoCursorHelper::GetPropertyState(aPaM, m_pImpl->m_rPropSet, + rPropertyName); +} + +uno::Sequence< beans::PropertyState > SAL_CALL +SwXTextRange::getPropertyStates(const uno::Sequence< OUString >& rPropertyName) +{ + SolarMutexGuard g; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + GetPositions(aPaM); + return SwUnoCursorHelper::GetPropertyStates(aPaM, m_pImpl->m_rPropSet, + rPropertyName); +} + +void SAL_CALL SwXTextRange::setPropertyToDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + GetPositions(aPaM); + SwUnoCursorHelper::SetPropertyToDefault(aPaM, m_pImpl->m_rPropSet, + rPropertyName); +} + +uno::Any SAL_CALL +SwXTextRange::getPropertyDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + GetPositions(aPaM); + return SwUnoCursorHelper::GetPropertyDefault(aPaM, m_pImpl->m_rPropSet, + rPropertyName); +} + +void SAL_CALL +SwXTextRange::makeRedline( + const OUString& rRedlineType, + const uno::Sequence< beans::PropertyValue >& rRedlineProperties ) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->GetBookmark()) + { + throw uno::RuntimeException(); + } + SwPaM aPaM(GetDoc().GetNodes()); + SwXTextRange::GetPositions(aPaM); + SwUnoCursorHelper::makeRedline( aPaM, rRedlineType, rRedlineProperties ); +} + +namespace { + +struct SwXTextRangesImpl final : public SwXTextRanges +{ + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rIdentifier) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { return "SwXTextRanges"; }; + virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName) override + { return cppu::supportsService(this, rServiceName); }; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override + { return { "com.sun.star.text.TextRanges" }; }; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override + { return cppu::UnoType<text::XTextRange>::get(); }; + virtual sal_Bool SAL_CALL hasElements() override + { return getCount() > 0; }; + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override; + virtual css::uno::Any SAL_CALL getByIndex(sal_Int32 nIndex) override; + + explicit SwXTextRangesImpl(SwPaM *const pPaM) + { + if (pPaM) + { + m_pUnoCursor.reset(pPaM->GetDoc()->CreateUnoCursor(*pPaM->GetPoint())); + ::sw::DeepCopyPaM(*pPaM, *GetCursor()); + } + MakeRanges(); + } + virtual void SAL_CALL release() throw () override + { + SolarMutexGuard g; + OWeakObject::release(); + } + virtual SwUnoCursor* GetCursor() override + { return &(*m_pUnoCursor); }; + void MakeRanges(); + std::vector< uno::Reference< text::XTextRange > > m_Ranges; + sw::UnoCursorPointer m_pUnoCursor; +}; + +} + +void SwXTextRangesImpl::MakeRanges() +{ + if (GetCursor()) + { + for(SwPaM& rTmpCursor : GetCursor()->GetRingContainer()) + { + const uno::Reference< text::XTextRange > xRange( + SwXTextRange::CreateXTextRange( + *rTmpCursor.GetDoc(), + *rTmpCursor.GetPoint(), rTmpCursor.GetMark())); + if (xRange.is()) + { + m_Ranges.push_back(xRange); + } + } + } +} + +SwXTextRanges* SwXTextRanges::Create(SwPaM *const pPaM) + { return new SwXTextRangesImpl(pPaM); } + +namespace +{ + class theSwXTextRangesUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextRangesUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextRanges::getUnoTunnelId() + { return theSwXTextRangesUnoTunnelId::get().getSeq(); } + +sal_Int64 SAL_CALL +SwXTextRangesImpl::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXTextRanges>(rId, this); +} + +/* + * Text positions + * Up to the first access to a text position, only a SwCursor is stored. + * Afterwards, an array with uno::Reference<XTextPosition> will be created. + */ + +sal_Int32 SAL_CALL SwXTextRangesImpl::getCount() +{ + SolarMutexGuard aGuard; + return static_cast<sal_Int32>(m_Ranges.size()); +} + +uno::Any SAL_CALL SwXTextRangesImpl::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if ((nIndex < 0) || (o3tl::make_unsigned(nIndex) >= m_Ranges.size())) + throw lang::IndexOutOfBoundsException(); + uno::Any ret; + ret <<= m_Ranges.at(nIndex); + return ret; +} + +void SwUnoCursorHelper::SetString(SwCursor & rCursor, const OUString& rString) +{ + // Start/EndAction + SwDoc *const pDoc = rCursor.GetDoc(); + UnoActionContext aAction(pDoc); + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + if (rCursor.HasMark()) + { + pDoc->getIDocumentContentOperations().DeleteAndJoin(rCursor); + } + if (!rString.isEmpty()) + { + const bool bSuccess( SwUnoCursorHelper::DocInsertStringSplitCR( + *pDoc, rCursor, rString, false ) ); + OSL_ENSURE( bSuccess, "DocInsertStringSplitCR" ); + SwUnoCursorHelper::SelectPam(rCursor, true); + rCursor.Left(rString.getLength()); + } + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); +} + +namespace { + +struct SwXParaFrameEnumerationImpl final : public SwXParaFrameEnumeration +{ + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { return "SwXParaFrameEnumeration"; }; + virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override + { return cppu::supportsService(this, rServiceName); }; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override + { return {"com.sun.star.util.ContentEnumeration"}; }; + + // XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual css::uno::Any SAL_CALL nextElement() override; + + SwXParaFrameEnumerationImpl(const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, SwFrameFormat* const pFormat); + virtual void SAL_CALL release() throw () override + { + SolarMutexGuard g; + OWeakObject::release(); + } + SwUnoCursor& GetCursor() + { return *m_pUnoCursor; } + void PurgeFrameClients() + { + if(!m_pUnoCursor) + { + m_vFrames.clear(); + m_xNextObject = nullptr; + } + else + { + // removing orphaned Clients + const auto iter = std::remove_if(m_vFrames.begin(), m_vFrames.end(), + [] (std::shared_ptr<sw::FrameClient>& rEntry) -> bool { return !rEntry->GetRegisteredIn(); }); + m_vFrames.erase(iter, m_vFrames.end()); + } + } + void FillFrame(); + bool CreateNextObject(); + uno::Reference< text::XTextContent > m_xNextObject; + FrameClientList_t m_vFrames; + ::sw::UnoCursorPointer m_pUnoCursor; +}; + +} + +SwXParaFrameEnumeration* SwXParaFrameEnumeration::Create(const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, SwFrameFormat* const pFormat) + { return new SwXParaFrameEnumerationImpl(rPaM, eParaFrameMode, pFormat); } + +SwXParaFrameEnumerationImpl::SwXParaFrameEnumerationImpl( + const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, + SwFrameFormat* const pFormat) + : m_pUnoCursor(rPaM.GetDoc()->CreateUnoCursor(*rPaM.GetPoint())) +{ + if (rPaM.HasMark()) + { + GetCursor().SetMark(); + *GetCursor().GetMark() = *rPaM.GetMark(); + } + if (PARAFRAME_PORTION_PARAGRAPH == eParaFrameMode) + { + FrameClientSortList_t vFrames; + ::CollectFrameAtNode(rPaM.GetPoint()->nNode, vFrames, false); + std::transform(vFrames.begin(), vFrames.end(), + std::back_inserter(m_vFrames), + [] (const FrameClientSortListEntry& rEntry) { return rEntry.pFrameClient; }); + } + else if (pFormat) + { + m_vFrames.push_back(std::make_shared<sw::FrameClient>(pFormat)); + } + else if ((PARAFRAME_PORTION_CHAR == eParaFrameMode) || + (PARAFRAME_PORTION_TEXTRANGE == eParaFrameMode)) + { + if (PARAFRAME_PORTION_TEXTRANGE == eParaFrameMode) + { + //get all frames that are bound at paragraph or at character + for(const auto& pFlyFrame : rPaM.GetDoc()->GetAllFlyFormats(&GetCursor(), false, true)) + { + const auto pFrameFormat = const_cast<SwFrameFormat*>(&pFlyFrame->GetFormat()); + m_vFrames.push_back(std::make_shared<sw::FrameClient>(pFrameFormat)); + } + } + FillFrame(); + } +} + +// Search for a FLYCNT text attribute at the cursor point and fill the frame +// into the array +void SwXParaFrameEnumerationImpl::FillFrame() +{ + if(!m_pUnoCursor->GetNode().IsTextNode()) + return; + // search for objects at the cursor - anchored at/as char + const auto pTextAttr = m_pUnoCursor->GetNode().GetTextNode()->GetTextAttrForCharAt( + m_pUnoCursor->GetPoint()->nContent.GetIndex(), RES_TXTATR_FLYCNT); + if(!pTextAttr) + return; + const SwFormatFlyCnt& rFlyCnt = pTextAttr->GetFlyCnt(); + SwFrameFormat* const pFrameFormat = rFlyCnt.GetFrameFormat(); + m_vFrames.push_back(std::make_shared<sw::FrameClient>(pFrameFormat)); +} + +bool SwXParaFrameEnumerationImpl::CreateNextObject() +{ + if (m_vFrames.empty()) + return false; + + SwFrameFormat* const pFormat = static_cast<SwFrameFormat*>( + m_vFrames.front()->GetRegisteredIn()); + m_vFrames.pop_front(); + // the format should be valid here, otherwise the client + // would have been removed by PurgeFrameClients + // check for a shape first + if(pFormat->Which() == RES_DRAWFRMFMT) + { + SdrObject* pObject(nullptr); + pFormat->CallSwClientNotify(sw::FindSdrObjectHint(pObject)); + if(pObject) + m_xNextObject.set(pObject->getUnoShape(), uno::UNO_QUERY); + } + else + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + OSL_ENSURE(pIdx, "where is the index?"); + SwNode const*const pNd = + m_pUnoCursor->GetDoc()->GetNodes()[ pIdx->GetIndex() + 1 ]; + + if (!pNd->IsNoTextNode()) + { + m_xNextObject.set(SwXTextFrame::CreateXTextFrame( + *pFormat->GetDoc(), pFormat)); + } + else if (pNd->IsGrfNode()) + { + m_xNextObject.set(SwXTextGraphicObject::CreateXTextGraphicObject( + *pFormat->GetDoc(), pFormat)); + } + else + { + assert(pNd->IsOLENode()); + m_xNextObject.set(SwXTextEmbeddedObject::CreateXTextEmbeddedObject( + *pFormat->GetDoc(), pFormat)); + } + } + return m_xNextObject.is(); +} + +sal_Bool SAL_CALL +SwXParaFrameEnumerationImpl::hasMoreElements() +{ + SolarMutexGuard aGuard; + PurgeFrameClients(); + return m_xNextObject.is() || CreateNextObject(); +} + +uno::Any SAL_CALL SwXParaFrameEnumerationImpl::nextElement() +{ + SolarMutexGuard aGuard; + PurgeFrameClients(); + if (!m_xNextObject.is() && !m_vFrames.empty()) + CreateNextObject(); + if (!m_xNextObject.is()) + throw container::NoSuchElementException(); + uno::Any aRet; + aRet <<= m_xNextObject; + m_xNextObject = nullptr; + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoparagraph.cxx b/sw/source/core/unocore/unoparagraph.cxx new file mode 100644 index 000000000..3fef41218 --- /dev/null +++ b/sw/source/core/unocore/unoparagraph.cxx @@ -0,0 +1,1417 @@ +/* -*- 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 <unoparagraph.hxx> + +#include <comphelper/interfacecontainer2.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> + +#include <cmdid.h> +#include <unomid.h> +#include <unoparaframeenum.hxx> +#include <unotext.hxx> +#include <unotextrange.hxx> +#include <unoport.hxx> +#include <unomap.hxx> +#include <unocrsr.hxx> +#include <unoprnms.hxx> +#include <unocrsrhelper.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include <docsh.hxx> +#include <swunohelper.hxx> + +#include <com/sun/star/beans/SetPropertyTolerantFailed.hpp> +#include <com/sun/star/beans/GetPropertyTolerantResult.hpp> +#include <com/sun/star/beans/TolerantPropertySetResultType.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> + +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <comphelper/servicehelper.hxx> +#include <editeng/unoipset.hxx> +#include <svl/listener.hxx> +#include <svx/unobrushitemhelper.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/xflbstit.hxx> + +using namespace ::com::sun::star; + +namespace { + +class SwParaSelection +{ + SwCursor & m_rCursor; +public: + explicit SwParaSelection(SwCursor & rCursor); + ~SwParaSelection(); +}; + +} + +SwParaSelection::SwParaSelection(SwCursor & rCursor) + : m_rCursor(rCursor) +{ + if (m_rCursor.HasMark()) + { + m_rCursor.DeleteMark(); + } + // is it at the start? + if (m_rCursor.GetPoint()->nContent != 0) + { + m_rCursor.MovePara(GoCurrPara, fnParaStart); + } + // or at the end already? + if (m_rCursor.GetPoint()->nContent != m_rCursor.GetContentNode()->Len()) + { + m_rCursor.SetMark(); + m_rCursor.MovePara(GoCurrPara, fnParaEnd); + } +} + +SwParaSelection::~SwParaSelection() +{ + if (m_rCursor.GetPoint()->nContent != 0) + { + m_rCursor.DeleteMark(); + m_rCursor.MovePara(GoCurrPara, fnParaStart); + } +} + +/// @throws beans::UnknownPropertyException +/// @throws uno::RuntimeException +static beans::PropertyState lcl_SwXParagraph_getPropertyState( + const SwTextNode& rTextNode, + const SwAttrSet** ppSet, + const SfxItemPropertySimpleEntry& rEntry, + bool &rAttrSetFetched ); + +class SwXParagraph::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + SwXParagraph& m_rThis; + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + SfxItemPropertySet const& m_rPropSet; + bool m_bIsDescriptor; + sal_Int32 m_nSelectionStartPos; + sal_Int32 m_nSelectionEndPos; + OUString m_sText; + uno::Reference<text::XText> m_xParentText; + SwTextNode* m_pTextNode; + + Impl(SwXParagraph& rThis, + SwTextNode* const pTextNode = nullptr, uno::Reference<text::XText> const& xParent = nullptr, + const sal_Int32 nSelStart = -1, const sal_Int32 nSelEnd = -1) + : m_rThis(rThis) + , m_EventListeners(m_Mutex) + , m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_PARAGRAPH)) + , m_bIsDescriptor(nullptr == pTextNode) + , m_nSelectionStartPos(nSelStart) + , m_nSelectionEndPos(nSelEnd) + , m_xParentText(xParent) + , m_pTextNode(pTextNode) + { + m_pTextNode && StartListening(m_pTextNode->GetNotifier()); + } + + SwTextNode* GetTextNode() { + return m_pTextNode; + } + + SwTextNode& GetTextNodeOrThrow() { + if (!m_pTextNode) { + throw uno::RuntimeException("SwXParagraph: disposed or invalid", nullptr); + } + return *m_pTextNode; + } + + bool IsDescriptor() const { return m_bIsDescriptor; } + + /// @throws beans::UnknownPropertyException + /// @throws beans::PropertyVetoException + /// @throws lang::IllegalArgumentException + /// @throws lang::WrappedTargetException + /// @throws uno::RuntimeException + void SetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues); + + /// @throws beans::UnknownPropertyException + /// @throws lang::WrappedTargetException + /// @throws uno::RuntimeException + uno::Sequence< uno::Any > + GetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames); + + /// @throws uno::RuntimeException + void GetSinglePropertyValue_Impl( + const SfxItemPropertySimpleEntry& rEntry, + const SfxItemSet& rSet, + uno::Any& rAny ) const; + + /// @throws uno::RuntimeException + uno::Sequence< beans::GetDirectPropertyTolerantResult > + GetPropertyValuesTolerant_Impl( + const uno::Sequence< OUString >& rPropertyNames, + bool bDirectValuesOnly); +protected: + virtual void Notify(const SfxHint& rHint) override; + +}; + +void SwXParagraph::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pTextNode = nullptr; + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); + } +} + +SwXParagraph::SwXParagraph() + : m_pImpl( new SwXParagraph::Impl(*this) ) +{ +} + +SwXParagraph::SwXParagraph( + uno::Reference< text::XText > const & xParent, + SwTextNode & rTextNode, + const sal_Int32 nSelStart, const sal_Int32 nSelEnd) + : m_pImpl( + new SwXParagraph::Impl(*this, &rTextNode, xParent, nSelStart, nSelEnd)) +{ +} + +SwXParagraph::~SwXParagraph() +{ +} + +const SwTextNode * SwXParagraph::GetTextNode() const +{ + return m_pImpl->GetTextNode(); +} + +bool SwXParagraph::IsDescriptor() const +{ + return m_pImpl->IsDescriptor(); +} + +uno::Reference<text::XTextContent> +SwXParagraph::CreateXParagraph(SwDoc & rDoc, SwTextNode *const pTextNode, + uno::Reference< text::XText> const& i_xParent, + const sal_Int32 nSelStart, const sal_Int32 nSelEnd) +{ + // re-use existing SwXParagraph + // #i105557#: do not iterate over the registered clients: race condition + uno::Reference<text::XTextContent> xParagraph; + if (pTextNode && (-1 == nSelStart) && (-1 == nSelEnd)) + { // only use cache if no selection! + xParagraph.set(pTextNode->GetXParagraph()); + } + if (xParagraph.is()) + { + return xParagraph; + } + + // create new SwXParagraph + uno::Reference<text::XText> xParentText(i_xParent); + if (!xParentText.is() && pTextNode) + { + SwPosition Pos(*pTextNode); + xParentText.set(::sw::CreateParentXText( rDoc, Pos )); + } + SwXParagraph *const pXPara( pTextNode + ? new SwXParagraph(xParentText, *pTextNode, nSelStart, nSelEnd) + : new SwXParagraph); + // this is why the constructor is private: need to acquire pXPara here + xParagraph.set(pXPara); + // in order to initialize the weak pointer cache in the core object + if (pTextNode && (-1 == nSelStart) && (-1 == nSelEnd)) + { + pTextNode->SetXParagraph(xParagraph); + } + // need a permanent Reference to initialize m_wThis + pXPara->m_pImpl->m_wThis = xParagraph; + return xParagraph; +} + +bool SwXParagraph::SelectPaM(SwPaM & rPaM) +{ + SwTextNode const*const pTextNode( GetTextNode() ); + + if (!pTextNode) + { + return false; + } + + *rPaM.GetPoint() = SwPosition( *pTextNode ); + // set selection to the whole paragraph + rPaM.SetMark(); + rPaM.GetMark()->nContent = pTextNode->GetText().getLength(); + return true; +} + +namespace +{ + class theSwXParagraphUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXParagraphUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXParagraph::getUnoTunnelId() +{ + return theSwXParagraphUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXParagraph::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXParagraph>(rId, this); +} + +OUString SAL_CALL +SwXParagraph::getImplementationName() +{ + return "SwXParagraph"; +} + +sal_Bool SAL_CALL +SwXParagraph::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXParagraph::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextContent", + "com.sun.star.text.Paragraph", + "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesAsian", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.ParagraphProperties", + "com.sun.star.style.ParagraphPropertiesAsian", + "com.sun.star.style.ParagraphPropertiesComplex" + }; +} + +void +SwXParagraph::attachToText(SwXText & rParent, SwTextNode & rTextNode) +{ + OSL_ENSURE(m_pImpl->m_bIsDescriptor, "Paragraph is not a descriptor"); + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_bIsDescriptor = false; + m_pImpl->EndListeningAll(); + m_pImpl->StartListening(rTextNode.GetNotifier()); + rTextNode.SetXParagraph(uno::Reference<text::XTextContent>(this)); + m_pImpl->m_xParentText = &rParent; + if (!m_pImpl->m_sText.isEmpty()) + { + try { setString(m_pImpl->m_sText); } + catch(...){} + m_pImpl->m_sText.clear(); + } + } +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXParagraph::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xRef = + m_pImpl->m_rPropSet.getPropertySetInfo(); + return xRef; +} + +void SAL_CALL +SwXParagraph::setPropertyValue(const OUString& rPropertyName, + const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + uno::Sequence<OUString> aPropertyNames { rPropertyName }; + uno::Sequence<uno::Any> aValues(1); + aValues.getArray()[0] = rValue; + m_pImpl->SetPropertyValues_Impl( aPropertyNames, aValues ); +} + +uno::Any +SwXParagraph::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Sequence<OUString> aPropertyNames { rPropertyName }; + const uno::Sequence< uno::Any > aRet = + m_pImpl->GetPropertyValues_Impl(aPropertyNames); + return aRet.getConstArray()[0]; +} + +void SwXParagraph::Impl::SetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues ) +{ + SwTextNode & rTextNode(GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwCursor aCursor( aPos, nullptr ); + const OUString* pPropertyNames = rPropertyNames.getConstArray(); + const uno::Any* pValues = rValues.getConstArray(); + const SfxItemPropertyMap &rMap = m_rPropSet.getPropertyMap(); + SwParaSelection aParaSel( aCursor ); + + uno::Sequence< beans::PropertyValue > aValues( rPropertyNames.getLength() ); + for (sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); nProp++) + { + SfxItemPropertySimpleEntry const*const pEntry = + rMap.getByName( pPropertyNames[nProp] ); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + pPropertyNames[nProp], + static_cast< cppu::OWeakObject * >(&m_rThis)); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw beans::PropertyVetoException( + "Property is read-only: " + pPropertyNames[nProp], + static_cast< cppu::OWeakObject * >(&m_rThis)); + } + aValues[nProp].Name = pPropertyNames[nProp]; + aValues[nProp].Value = pValues[nProp]; + } + SwUnoCursorHelper::SetPropertyValues(aCursor, m_rPropSet, aValues); +} + +void SAL_CALL SwXParagraph::setPropertyValues( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues ) +{ + SolarMutexGuard aGuard; + + // workaround for bad designed API + try + { + m_pImpl->SetPropertyValues_Impl( rPropertyNames, rValues ); + } + catch (const beans::UnknownPropertyException &rException) + { + // wrap the original (here not allowed) exception in + // a lang::WrappedTargetException that gets thrown instead. + lang::WrappedTargetException aWExc; + aWExc.TargetException <<= rException; + throw aWExc; + } +} + +// Support for DrawingLayer FillStyles for GetPropertyValue() usages +void SwXParagraph::Impl::GetSinglePropertyValue_Impl( + const SfxItemPropertySimpleEntry& rEntry, + const SfxItemSet& rSet, + uno::Any& rAny ) const +{ + bool bDone(false); + + switch(rEntry.nWID) + { + case RES_BACKGROUND: + { + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND)); + + if(!aOriginalBrushItem->QueryValue(rAny, rEntry.nMemberId)) + { + OSL_ENSURE(false, "Error getting attribute from RES_BACKGROUND (!)"); + } + + bDone = true; + break; + } + case OWN_ATTR_FILLBMP_MODE: + { + if (rSet.Get(XATTR_FILLBMP_TILE).GetValue()) + { + rAny <<= drawing::BitmapMode_REPEAT; + } + else if (rSet.Get(XATTR_FILLBMP_STRETCH).GetValue()) + { + rAny <<= drawing::BitmapMode_STRETCH; + } + else + { + rAny <<= drawing::BitmapMode_NO_REPEAT; + } + + bDone = true; + break; + } + default: break; + } + + if(!bDone) + { + // fallback to standard get value implementation used before this helper was created + m_rPropSet.getPropertyValue(rEntry, rSet, rAny); + + if(rEntry.aType == cppu::UnoType<sal_Int16>::get() && rEntry.aType != rAny.getValueType()) + { + // since the sfx uInt16 item now exports a sal_Int32, we may have to fix this here + sal_Int32 nValue(0); + + if (rAny >>= nValue) + { + rAny <<= static_cast<sal_Int16>(nValue); + } + } + + // check for needed metric translation + if(rEntry.nMoreFlags & PropertyMoreFlags::METRIC_ITEM) + { + bool bDoIt(true); + + if(XATTR_FILLBMP_SIZEX == rEntry.nWID || XATTR_FILLBMP_SIZEY == rEntry.nWID) + { + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + sal_Int32 nValue = 0; + + if(rAny >>= nValue) + { + bDoIt = nValue > 0; + } + } + + if(bDoIt) + { + const MapUnit eMapUnit(rSet.GetPool()->GetMetric(rEntry.nWID)); + + if(eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertToMM(eMapUnit, rAny); + } + } + } + } +} + +uno::Sequence< uno::Any > SwXParagraph::Impl::GetPropertyValues_Impl( + const uno::Sequence< OUString > & rPropertyNames ) +{ + SwTextNode & rTextNode(GetTextNodeOrThrow()); + + uno::Sequence< uno::Any > aValues(rPropertyNames.getLength()); + SwPosition aPos( rTextNode ); + SwPaM aPam( aPos ); + uno::Any* pValues = aValues.getArray(); + const OUString* pPropertyNames = rPropertyNames.getConstArray(); + const SfxItemPropertyMap &rMap = m_rPropSet.getPropertyMap(); + const SwAttrSet& rAttrSet( rTextNode.GetSwAttrSet() ); + for (sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); nProp++) + { + SfxItemPropertySimpleEntry const*const pEntry = + rMap.getByName( pPropertyNames[nProp] ); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + pPropertyNames[nProp], + static_cast< cppu::OWeakObject * >(&m_rThis)); + } + if (! ::sw::GetDefaultTextContentValue( + pValues[nProp], pPropertyNames[nProp], pEntry->nWID)) + { + beans::PropertyState eTemp; + const bool bDone = SwUnoCursorHelper::getCursorPropertyValue( + *pEntry, aPam, &(pValues[nProp]), eTemp, &rTextNode ); + if (!bDone) + { + GetSinglePropertyValue_Impl(*pEntry, rAttrSet, pValues[nProp]); + } + } + } + return aValues; +} + +uno::Sequence< uno::Any > SAL_CALL +SwXParagraph::getPropertyValues(const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + uno::Sequence< uno::Any > aValues; + + // workaround for bad designed API + try + { + aValues = m_pImpl->GetPropertyValues_Impl( rPropertyNames ); + } + catch (beans::UnknownPropertyException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("Unknown property exception caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + catch (lang::WrappedTargetException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("WrappedTargetException caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + + return aValues; +} + +void SAL_CALL SwXParagraph::addPropertiesChangeListener( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ + OSL_FAIL("SwXParagraph::addPropertiesChangeListener(): not implemented"); +} + +void SAL_CALL SwXParagraph::removePropertiesChangeListener( + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ + OSL_FAIL("SwXParagraph::removePropertiesChangeListener(): not implemented"); +} + +void SAL_CALL SwXParagraph::firePropertiesChangeEvent( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ + OSL_FAIL("SwXParagraph::firePropertiesChangeEvent(): not implemented"); +} + +/* disabled for #i46921# */ + +uno::Sequence< beans::SetPropertyTolerantFailed > SAL_CALL +SwXParagraph::setPropertyValuesTolerant( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues ) +{ + SolarMutexGuard aGuard; + + if (rPropertyNames.getLength() != rValues.getLength()) + { + throw lang::IllegalArgumentException(); + } + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + //SwNode& rTextNode = pUnoCursor->GetPoint()->nNode.GetNode(); + //const SwAttrSet& rAttrSet = static_cast<SwTextNode&>(rTextNode).GetSwAttrSet(); + //sal_uInt16 nAttrCount = rAttrSet.Count(); + + const sal_Int32 nProps = rPropertyNames.getLength(); + const OUString *pProp = rPropertyNames.getConstArray(); + + //sal_Int32 nVals = rValues.getLength(); + const uno::Any *pValue = rValues.getConstArray(); + + sal_Int32 nFailed = 0; + uno::Sequence< beans::SetPropertyTolerantFailed > aFailed( nProps ); + beans::SetPropertyTolerantFailed *pFailed = aFailed.getArray(); + + // get entry to start with + const SfxItemPropertyMap &rPropMap = + m_pImpl->m_rPropSet.getPropertyMap(); + + SwPosition aPos( rTextNode ); + SwCursor aCursor( aPos, nullptr ); + SwParaSelection aParaSel( aCursor ); + for (sal_Int32 i = 0; i < nProps; ++i) + { + try + { + pFailed[ nFailed ].Name = pProp[i]; + + SfxItemPropertySimpleEntry const*const pEntry = + rPropMap.getByName( pProp[i] ); + if (!pEntry) + { + pFailed[ nFailed++ ].Result = + beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + else + { + // set property value + // (compare to SwXParagraph::setPropertyValues) + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + pFailed[ nFailed++ ].Result = + beans::TolerantPropertySetResultType::PROPERTY_VETO; + } + else + { + SwUnoCursorHelper::SetPropertyValue( + aCursor, m_pImpl->m_rPropSet, pProp[i], pValue[i]); + } + } + } + catch (beans::UnknownPropertyException &) + { + // should not occur because property was searched for before + OSL_FAIL( "unexpected exception caught" ); + pFailed[ nFailed++ ].Result = + beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + catch (lang::IllegalArgumentException &) + { + pFailed[ nFailed++ ].Result = + beans::TolerantPropertySetResultType::ILLEGAL_ARGUMENT; + } + catch (beans::PropertyVetoException &) + { + pFailed[ nFailed++ ].Result = + beans::TolerantPropertySetResultType::PROPERTY_VETO; + } + catch (lang::WrappedTargetException &) + { + pFailed[ nFailed++ ].Result = + beans::TolerantPropertySetResultType::WRAPPED_TARGET; + } + } + + aFailed.realloc( nFailed ); + return aFailed; +} + +uno::Sequence< beans::GetPropertyTolerantResult > SAL_CALL +SwXParagraph::getPropertyValuesTolerant( + const uno::Sequence< OUString >& rPropertyNames ) +{ + SolarMutexGuard aGuard; + + uno::Sequence< beans::GetDirectPropertyTolerantResult > aTmpRes( + m_pImpl->GetPropertyValuesTolerant_Impl( rPropertyNames, false ) ); + + // copy temporary result to final result type + const sal_Int32 nLen = aTmpRes.getLength(); + uno::Sequence< beans::GetPropertyTolerantResult > aRes( nLen ); + std::copy(aTmpRes.begin(), aTmpRes.end(), aRes.begin()); + return aRes; +} + +uno::Sequence< beans::GetDirectPropertyTolerantResult > SAL_CALL +SwXParagraph::getDirectPropertyValuesTolerant( + const uno::Sequence< OUString >& rPropertyNames ) +{ + SolarMutexGuard aGuard; + + return m_pImpl->GetPropertyValuesTolerant_Impl( rPropertyNames, true ); +} + +uno::Sequence< beans::GetDirectPropertyTolerantResult > +SwXParagraph::Impl::GetPropertyValuesTolerant_Impl( + const uno::Sequence< OUString >& rPropertyNames, + bool bDirectValuesOnly ) +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(GetTextNodeOrThrow()); + + // #i46786# Use SwAttrSet pointer for determining the state. + // Use the value SwAttrSet (from the paragraph OR the style) + // for determining the actual value(s). + const SwAttrSet* pAttrSet = rTextNode.GetpSwAttrSet(); + const SwAttrSet& rValueAttrSet = rTextNode.GetSwAttrSet(); + + sal_Int32 nProps = rPropertyNames.getLength(); + + uno::Sequence< beans::GetDirectPropertyTolerantResult > aResult( nProps ); + beans::GetDirectPropertyTolerantResult *pResult = aResult.getArray(); + sal_Int32 nIdx = 0; + + // get entry to start with + const SfxItemPropertyMap &rPropMap = m_rPropSet.getPropertyMap(); + + for (const OUString& rProp : rPropertyNames) + { + OSL_ENSURE( nIdx < nProps, "index out of bounds" ); + beans::GetDirectPropertyTolerantResult &rResult = pResult[nIdx]; + + try + { + rResult.Name = rProp; + + SfxItemPropertySimpleEntry const*const pEntry = + rPropMap.getByName( rProp ); + if (!pEntry) // property available? + { + rResult.Result = + beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + else + { + // get property state + // (compare to SwXParagraph::getPropertyState) + bool bAttrSetFetched = true; + beans::PropertyState eState = lcl_SwXParagraph_getPropertyState( + rTextNode, &pAttrSet, *pEntry, bAttrSetFetched ); + rResult.State = eState; + + rResult.Result = beans::TolerantPropertySetResultType::UNKNOWN_FAILURE; + if (!bDirectValuesOnly || + (beans::PropertyState_DIRECT_VALUE == eState)) + { + // get property value + // (compare to SwXParagraph::getPropertyValue(s)) + uno::Any aValue; + if (! ::sw::GetDefaultTextContentValue( + aValue, rProp, pEntry->nWID ) ) + { + SwPosition aPos( rTextNode ); + SwPaM aPam( aPos ); + // handle properties that are not part of the attribute + // and thus only pretended to be paragraph attributes + beans::PropertyState eTemp; + const bool bDone = + SwUnoCursorHelper::getCursorPropertyValue( + *pEntry, aPam, &aValue, eTemp, &rTextNode ); + + // if not found try the real paragraph attributes... + if (!bDone) + { + GetSinglePropertyValue_Impl(*pEntry, rValueAttrSet, aValue); + } + } + + rResult.Value = aValue; + rResult.Result = beans::TolerantPropertySetResultType::SUCCESS; + + nIdx++; + } + // this assertion should never occur! + OSL_ENSURE( nIdx < 1 || pResult[nIdx - 1].Result != beans::TolerantPropertySetResultType::UNKNOWN_FAILURE, + "unknown failure while retrieving property" ); + + } + } + catch (beans::UnknownPropertyException &) + { + // should not occur because property was searched for before + OSL_FAIL( "unexpected exception caught" ); + rResult.Result = beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + catch (lang::IllegalArgumentException &) + { + rResult.Result = beans::TolerantPropertySetResultType::ILLEGAL_ARGUMENT; + } + catch (beans::PropertyVetoException &) + { + rResult.Result = beans::TolerantPropertySetResultType::PROPERTY_VETO; + } + catch (lang::WrappedTargetException &) + { + rResult.Result = beans::TolerantPropertySetResultType::WRAPPED_TARGET; + } + } + + // resize to actually used size + aResult.realloc( nIdx ); + + return aResult; +} + +bool ::sw::GetDefaultTextContentValue( + uno::Any& rAny, const OUString& rPropertyName, sal_uInt16 nWID) +{ + if(!nWID) + { + if(rPropertyName == UNO_NAME_ANCHOR_TYPE) + nWID = FN_UNO_ANCHOR_TYPE; + else if(rPropertyName == UNO_NAME_ANCHOR_TYPES) + nWID = FN_UNO_ANCHOR_TYPES; + else if(rPropertyName == UNO_NAME_TEXT_WRAP) + nWID = FN_UNO_TEXT_WRAP; + else + return false; + } + + switch(nWID) + { + case FN_UNO_TEXT_WRAP: rAny <<= text::WrapTextMode_NONE; break; + case FN_UNO_ANCHOR_TYPE: rAny <<= text::TextContentAnchorType_AT_PARAGRAPH; break; + case FN_UNO_ANCHOR_TYPES: + { uno::Sequence<text::TextContentAnchorType> aTypes(1); + text::TextContentAnchorType* pArray = aTypes.getArray(); + pArray[0] = text::TextContentAnchorType_AT_PARAGRAPH; + rAny <<= aTypes; + } + break; + default: + return false; + } + return true; +} + +void SAL_CALL +SwXParagraph::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXParagraph::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXParagraph::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXParagraph::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXParagraph::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXParagraph::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXParagraph::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXParagraph::removeVetoableChangeListener(): not implemented"); +} + +static beans::PropertyState lcl_SwXParagraph_getPropertyState( + const SwTextNode& rTextNode, + const SwAttrSet** ppSet, + const SfxItemPropertySimpleEntry& rEntry, + bool &rAttrSetFetched) +{ + beans::PropertyState eRet(beans::PropertyState_DEFAULT_VALUE); + + if(!(*ppSet) && !rAttrSetFetched) + { + (*ppSet) = rTextNode.GetpSwAttrSet(); + rAttrSetFetched = true; + } + + SwPosition aPos(rTextNode); + SwPaM aPam(aPos); + bool bDone(false); + + switch(rEntry.nWID) + { + case FN_UNO_NUM_RULES: + { + // if numbering is set, return it; else do nothing + SwUnoCursorHelper::getNumberingProperty(aPam,eRet,nullptr); + bDone = true; + break; + } + case FN_UNO_ANCHOR_TYPES: + { + bDone = true; + break; + } + case RES_ANCHOR: + { + bDone = (MID_SURROUND_SURROUNDTYPE == rEntry.nMemberId); + break; + } + case RES_SURROUND: + { + bDone = (MID_ANCHOR_ANCHORTYPE == rEntry.nMemberId); + break; + } + case FN_UNO_PARA_STYLE: + case FN_UNO_PARA_CONDITIONAL_STYLE_NAME: + { + SwFormatColl* pFormat = SwUnoCursorHelper::GetCurTextFormatColl(aPam,rEntry.nWID == FN_UNO_PARA_CONDITIONAL_STYLE_NAME); + eRet = pFormat ? beans::PropertyState_DIRECT_VALUE : beans::PropertyState_AMBIGUOUS_VALUE; + bDone = true; + break; + } + case FN_UNO_PAGE_STYLE: + { + OUString sVal; + SwUnoCursorHelper::GetCurPageStyle( aPam, sVal ); + eRet = !sVal.isEmpty() ? beans::PropertyState_DIRECT_VALUE + : beans::PropertyState_AMBIGUOUS_VALUE; + bDone = true; + break; + } + + // DrawingLayer PropertyStyle support + case OWN_ATTR_FILLBMP_MODE: + { + if(*ppSet) + { + if(SfxItemState::SET == (*ppSet)->GetItemState(XATTR_FILLBMP_STRETCH, false) + || SfxItemState::SET == (*ppSet)->GetItemState(XATTR_FILLBMP_TILE, false)) + { + eRet = beans::PropertyState_DIRECT_VALUE; + } + else + { + eRet = beans::PropertyState_AMBIGUOUS_VALUE; + } + + bDone = true; + } + break; + } + case RES_BACKGROUND: + { + if(*ppSet) + { + if (SWUnoHelper::needToMapFillItemsToSvxBrushItemTypes(**ppSet, + rEntry.nMemberId)) + { + eRet = beans::PropertyState_DIRECT_VALUE; + } + bDone = true; + } + break; + } + } + + if(!bDone) + { + if((*ppSet) && SfxItemState::SET == (*ppSet)->GetItemState(rEntry.nWID, false)) + { + eRet = beans::PropertyState_DIRECT_VALUE; + } + } + + return eRet; +} + +beans::PropertyState SAL_CALL +SwXParagraph::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + const SwAttrSet* pSet = nullptr; + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + bool bDummy = false; + const beans::PropertyState eRet = + lcl_SwXParagraph_getPropertyState(rTextNode, &pSet, *pEntry, bDummy); + return eRet; +} + +uno::Sequence< beans::PropertyState > SAL_CALL +SwXParagraph::getPropertyStates( + const uno::Sequence< OUString >& PropertyNames) +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + const OUString* pNames = PropertyNames.getConstArray(); + uno::Sequence< beans::PropertyState > aRet(PropertyNames.getLength()); + beans::PropertyState* pStates = aRet.getArray(); + const SfxItemPropertyMap &rMap = m_pImpl->m_rPropSet.getPropertyMap(); + const SwAttrSet* pSet = nullptr; + bool bAttrSetFetched = false; + + for (sal_Int32 i = 0, nEnd = PropertyNames.getLength(); i < nEnd; + ++i, ++pStates, ++pNames) + { + SfxItemPropertySimpleEntry const*const pEntry = + rMap.getByName( *pNames ); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + *pNames, + static_cast<cppu::OWeakObject *>(this)); + } + + if (bAttrSetFetched && !pSet && isATR(pEntry->nWID)) + { + *pStates = beans::PropertyState_DEFAULT_VALUE; + } + else + { + *pStates = lcl_SwXParagraph_getPropertyState( + rTextNode, &pSet, *pEntry, bAttrSetFetched ); + } + } + + return aRet; +} + +void SAL_CALL +SwXParagraph::setPropertyToDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwCursor aCursor( aPos, nullptr ); + if (rPropertyName == UNO_NAME_ANCHOR_TYPE || + rPropertyName == UNO_NAME_ANCHOR_TYPES || + rPropertyName == UNO_NAME_TEXT_WRAP) + { + return; + } + + // select paragraph + SwParaSelection aParaSel( aCursor ); + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName( rPropertyName ); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw uno::RuntimeException( + "Property is read-only: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + const bool bBelowFrameAtrEnd(pEntry->nWID < RES_FRMATR_END); + const bool bDrawingLayerRange(XATTR_FILL_FIRST <= pEntry->nWID && XATTR_FILL_LAST >= pEntry->nWID); + + if(bBelowFrameAtrEnd || bDrawingLayerRange) + { + std::set<sal_uInt16> aWhichIds; + + // For FillBitmapMode two IDs have to be reset (!) + if(OWN_ATTR_FILLBMP_MODE == pEntry->nWID) + { + aWhichIds.insert(XATTR_FILLBMP_STRETCH); + aWhichIds.insert(XATTR_FILLBMP_TILE); + } + else + { + aWhichIds.insert(pEntry->nWID); + } + + if (pEntry->nWID < RES_PARATR_BEGIN) + { + aCursor.GetDoc()->ResetAttrs(aCursor, true, aWhichIds); + } + else + { + // for paragraph attributes the selection must be extended + // to paragraph boundaries + SwPosition aStart( *aCursor.Start() ); + SwPosition aEnd ( *aCursor.End() ); + auto pTemp( aCursor.GetDoc()->CreateUnoCursor(aStart) ); + if(!SwUnoCursorHelper::IsStartOfPara(*pTemp)) + { + pTemp->MovePara(GoCurrPara, fnParaStart); + } + + pTemp->SetMark(); + *pTemp->GetPoint() = aEnd; + + SwUnoCursorHelper::SelectPam(*pTemp, true); + + if (!SwUnoCursorHelper::IsEndOfPara(*pTemp)) + { + pTemp->MovePara(GoCurrPara, fnParaEnd); + } + + + pTemp->GetDoc()->ResetAttrs(*pTemp, true, aWhichIds); + } + } + else + { + SwUnoCursorHelper::resetCursorPropertyValue(*pEntry, aCursor); + } +} + +uno::Any SAL_CALL +SwXParagraph::getPropertyDefault(const OUString& rPropertyName) +{ + SolarMutexGuard g; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + uno::Any aRet; + if (::sw::GetDefaultTextContentValue(aRet, rPropertyName)) + { + return aRet; + } + + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + const bool bBelowFrameAtrEnd(pEntry->nWID < RES_FRMATR_END); + const bool bDrawingLayerRange(XATTR_FILL_FIRST <= pEntry->nWID && XATTR_FILL_LAST >= pEntry->nWID); + + if(bBelowFrameAtrEnd || bDrawingLayerRange) + { + const SfxPoolItem& rDefItem = rTextNode.GetDoc()->GetAttrPool().GetDefaultItem(pEntry->nWID); + + rDefItem.QueryValue(aRet, pEntry->nMemberId); + } + + return aRet; +} + +void SAL_CALL +SwXParagraph::attach(const uno::Reference< text::XTextRange > & /*xTextRange*/) +{ + SolarMutexGuard aGuard; + // SwXParagraph will only created in order to be inserted by + // 'insertTextContentBefore' or 'insertTextContentAfter' therefore + // they cannot be attached + throw uno::RuntimeException(); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXParagraph::getAnchor() +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwCursor aCursor( aPos, nullptr ); + // select paragraph + SwParaSelection aParaSel( aCursor ); + const uno::Reference< text::XTextRange > xRet = + new SwXTextRange(aCursor, m_pImpl->m_xParentText); + return xRet; +} + +void SAL_CALL SwXParagraph::dispose() +{ + SolarMutexGuard aGuard; + + SwTextNode *const pTextNode( m_pImpl->GetTextNode() ); + if (pTextNode) + { + SwCursor aCursor( SwPosition( *pTextNode ), nullptr ); + pTextNode->GetDoc()->getIDocumentContentOperations().DelFullPara(aCursor); + lang::EventObject const ev(static_cast< ::cppu::OWeakObject&>(*this)); + m_pImpl->m_EventListeners.disposeAndClear(ev); + } +} + +void SAL_CALL SwXParagraph::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXParagraph::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXParagraph::createEnumeration() +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwPaM aPam ( aPos ); + const uno::Reference< container::XEnumeration > xRef = + new SwXTextPortionEnumeration(aPam, m_pImpl->m_xParentText, + m_pImpl->m_nSelectionStartPos, m_pImpl->m_nSelectionEndPos); + return xRef; +} + +uno::Type SAL_CALL SwXParagraph::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SwXParagraph::hasElements() +{ + SolarMutexGuard aGuard; + return GetTextNode() != nullptr; +} + +uno::Reference< text::XText > SAL_CALL +SwXParagraph::getText() +{ + SolarMutexGuard g; + + return m_pImpl->m_xParentText; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXParagraph::getStart() +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwCursor aCursor( aPos, nullptr ); + SwParaSelection aParaSel( aCursor ); + SwPaM aPam( *aCursor.Start() ); + uno::Reference< text::XText > xParent = getText(); + const uno::Reference< text::XTextRange > xRet = + new SwXTextRange(aPam, xParent); + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXParagraph::getEnd() +{ + SolarMutexGuard aGuard; + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwCursor aCursor( aPos, nullptr ); + SwParaSelection aParaSel( aCursor ); + SwPaM aPam( *aCursor.End() ); + uno::Reference< text::XText > xParent = getText(); + const uno::Reference< text::XTextRange > xRet = + new SwXTextRange(aPam, xParent); + return xRet; +} + +OUString SAL_CALL SwXParagraph::getString() +{ + SolarMutexGuard aGuard; + OUString aRet; + SwTextNode const*const pTextNode( GetTextNode() ); + if (pTextNode) + { + SwPosition aPos( *pTextNode ); + SwCursor aCursor( aPos, nullptr ); + SwParaSelection aParaSel( aCursor ); + SwUnoCursorHelper::GetTextFromPam(aCursor, aRet); + } + else if (m_pImpl->IsDescriptor()) + { + aRet = m_pImpl->m_sText; + } + else + { + // Seems object is being disposed or some other problem occurs. + // Anyway from user point of view object still exist, so on that level this is not an error + SAL_WARN("sw.uno", "getString() for invalid paragraph called. Returning empty string."); + } + return aRet; +} + +void SAL_CALL SwXParagraph::setString(const OUString& aString) +{ + SolarMutexGuard aGuard; + + SwTextNode const*const pTextNode( GetTextNode() ); + if (pTextNode) + { + SwPosition aPos( *pTextNode ); + SwCursor aCursor( aPos, nullptr ); + if (!SwUnoCursorHelper::IsStartOfPara(aCursor)) { + aCursor.MovePara(GoCurrPara, fnParaStart); + } + SwUnoCursorHelper::SelectPam(aCursor, true); + if (pTextNode->GetText().getLength()) { + aCursor.MovePara(GoCurrPara, fnParaEnd); + } + SwUnoCursorHelper::SetString(aCursor, aString); + SwUnoCursorHelper::SelectPam(aCursor, false); + } + else if (m_pImpl->IsDescriptor()) + { + m_pImpl->m_sText = aString; + } + else + { + throw uno::RuntimeException(); + } +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXParagraph::createContentEnumeration(const OUString& rServiceName) +{ + SolarMutexGuard g; + + if ( rServiceName != "com.sun.star.text.TextContent" ) + { + throw uno::RuntimeException(); + } + + SwTextNode & rTextNode(m_pImpl->GetTextNodeOrThrow()); + + SwPosition aPos( rTextNode ); + SwPaM aPam( aPos ); + uno::Reference< container::XEnumeration > xRet = + SwXParaFrameEnumeration::Create(aPam, PARAFRAME_PORTION_PARAGRAPH); + return xRet; +} + +uno::Sequence< OUString > SAL_CALL +SwXParagraph::getAvailableServiceNames() +{ + uno::Sequence<OUString> aRet { "com.sun.star.text.TextContent" }; + return aRet; +} + +// MetadatableMixin +::sfx2::Metadatable* SwXParagraph::GetCoreObject() +{ + SwTextNode *const pTextNode( m_pImpl->GetTextNode() ); + return pTextNode; +} + +uno::Reference<frame::XModel> SwXParagraph::GetModel() +{ + SwTextNode *const pTextNode( m_pImpl->GetTextNode() ); + if (pTextNode) + { + SwDocShell const*const pShell( pTextNode->GetDoc()->GetDocShell() ); + return pShell ? pShell->GetModel() : nullptr; + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoport.cxx b/sw/source/core/unocore/unoport.cxx new file mode 100644 index 000000000..07af7a238 --- /dev/null +++ b/sw/source/core/unocore/unoport.cxx @@ -0,0 +1,825 @@ +/* -*- 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 <unoport.hxx> + +#include <cmdid.h> +#include <cppuhelper/exc_hlp.hxx> +#include <vcl/svapp.hxx> +#include <svl/itemprop.hxx> + +#include <unocrsrhelper.hxx> +#include <unoparaframeenum.hxx> +#include <unotextrange.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unomid.h> +#include <txtatr.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <frmfmt.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/SetPropertyTolerantFailed.hpp> +#include <com/sun/star/beans/GetPropertyTolerantResult.hpp> +#include <com/sun/star/beans/TolerantPropertySetResultType.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/text/XFootnote.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> + +using namespace ::com::sun::star; + +void SwXTextPortion::init(const SwUnoCursor* pPortionCursor) +{ + m_pUnoCursor = pPortionCursor->GetDoc()->CreateUnoCursor(*pPortionCursor->GetPoint()); + if (pPortionCursor->HasMark()) + { + m_pUnoCursor->SetMark(); + *m_pUnoCursor->GetMark() = *pPortionCursor->GetMark(); + } +} + +SwXTextPortion::SwXTextPortion( + const SwUnoCursor* pPortionCursor, + uno::Reference< text::XText > const& rParent, + SwTextPortionType eType) + : m_pPropSet(aSwMapProvider.GetPropertySet( + (PORTION_REDLINE_START == eType || + PORTION_REDLINE_END == eType) + ? PROPERTY_MAP_REDLINE_PORTION + : PROPERTY_MAP_TEXTPORTION_EXTENSIONS)) + , m_xParentText(rParent) + , m_pFrameFormat(nullptr) + , m_ePortionType(eType) + , m_bIsCollapsed(false) +{ + init( pPortionCursor); +} + +SwXTextPortion::SwXTextPortion( + const SwUnoCursor* pPortionCursor, + uno::Reference< text::XText > const& rParent, + SwFrameFormat& rFormat ) + : m_pPropSet(aSwMapProvider.GetPropertySet( + PROPERTY_MAP_TEXTPORTION_EXTENSIONS)) + , m_xParentText(rParent) + , m_pFrameFormat(&rFormat) + , m_ePortionType(PORTION_FRAME) + , m_bIsCollapsed(false) +{ + StartListening(rFormat.GetNotifier()); + init( pPortionCursor); +} + +SwXTextPortion::SwXTextPortion( + const SwUnoCursor* pPortionCursor, + SwTextRuby const& rAttr, + uno::Reference< text::XText > const& xParent, + bool bIsEnd ) + : m_pPropSet(aSwMapProvider.GetPropertySet( + PROPERTY_MAP_TEXTPORTION_EXTENSIONS)) + , m_xParentText(xParent) + , m_pRubyText ( bIsEnd ? nullptr : new uno::Any ) + , m_pRubyStyle ( bIsEnd ? nullptr : new uno::Any ) + , m_pRubyAdjust ( bIsEnd ? nullptr : new uno::Any ) + , m_pRubyIsAbove( bIsEnd ? nullptr : new uno::Any ) + , m_pRubyPosition( bIsEnd ? nullptr : new uno::Any ) + , m_pFrameFormat(nullptr) + , m_ePortionType( bIsEnd ? PORTION_RUBY_END : PORTION_RUBY_START ) + , m_bIsCollapsed(false) +{ + init( pPortionCursor); + + if (!bIsEnd) + { + const SfxPoolItem& rItem = rAttr.GetAttr(); + rItem.QueryValue(*m_pRubyText); + rItem.QueryValue(*m_pRubyStyle, MID_RUBY_CHARSTYLE); + rItem.QueryValue(*m_pRubyAdjust, MID_RUBY_ADJUST); + rItem.QueryValue(*m_pRubyIsAbove, MID_RUBY_ABOVE); + rItem.QueryValue(*m_pRubyPosition, MID_RUBY_POSITION); + } +} + +SwXTextPortion::~SwXTextPortion() +{ + SolarMutexGuard aGuard; + m_pUnoCursor.reset(nullptr); + EndListeningAll(); +} + +uno::Reference< text::XText > SwXTextPortion::getText() +{ + return m_xParentText; +} + +uno::Reference< text::XTextRange > SwXTextPortion::getStart() +{ + SolarMutexGuard aGuard; + uno::Reference< text::XTextRange > xRet; + SwUnoCursor& rUnoCursor = GetCursor(); + + SwPaM aPam(*rUnoCursor.Start()); + uno::Reference< text::XText > xParent = getText(); + xRet = new SwXTextRange(aPam, xParent); + return xRet; +} + +uno::Reference< text::XTextRange > SwXTextPortion::getEnd() +{ + SolarMutexGuard aGuard; + uno::Reference< text::XTextRange > xRet; + SwUnoCursor& rUnoCursor = GetCursor(); + + SwPaM aPam(*rUnoCursor.End()); + uno::Reference< text::XText > xParent = getText(); + xRet = new SwXTextRange(aPam, xParent); + return xRet; +} + +OUString SwXTextPortion::getString() +{ + SolarMutexGuard aGuard; + OUString aText; + SwUnoCursor& rUnoCursor = GetCursor(); + + // TextPortions are always within a paragraph + SwTextNode* pTextNd = rUnoCursor.GetNode().GetTextNode(); + if ( pTextNd ) + { + const sal_Int32 nStt = rUnoCursor.Start()->nContent.GetIndex(); + aText = pTextNd->GetExpandText(nullptr, nStt, + rUnoCursor.End()->nContent.GetIndex() - nStt ); + } + return aText; +} + +void SwXTextPortion::setString(const OUString& aString) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + + SwUnoCursorHelper::SetString(rUnoCursor, aString); +} + +uno::Reference< beans::XPropertySetInfo > SwXTextPortion::getPropertySetInfo() +{ + SolarMutexGuard aGuard; + //! PropertySetInfo for text portion extensions + static uno::Reference< beans::XPropertySetInfo > + xTextPorExtRef = aSwMapProvider.GetPropertySet( + PROPERTY_MAP_TEXTPORTION_EXTENSIONS)->getPropertySetInfo(); + //! PropertySetInfo for redline portions + static uno::Reference< beans::XPropertySetInfo > + xRedlPorRef = aSwMapProvider.GetPropertySet( + PROPERTY_MAP_REDLINE_PORTION)->getPropertySetInfo(); + + return (PORTION_REDLINE_START == m_ePortionType || + PORTION_REDLINE_END == m_ePortionType) ? xRedlPorRef : xTextPorExtRef; +} + +void SwXTextPortion::setPropertyValue(const OUString& rPropertyName, + const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + + SwUnoCursorHelper::SetPropertyValue(rUnoCursor, *m_pPropSet, + rPropertyName, aValue); +} + +void SwXTextPortion::GetPropertyValue( + uno::Any &rVal, + const SfxItemPropertySimpleEntry& rEntry, + SwUnoCursor *pUnoCursor, + std::unique_ptr<SfxItemSet> &pSet ) +{ + OSL_ENSURE( pUnoCursor, "UNO cursor missing" ); + if (!pUnoCursor) + return; + switch(rEntry.nWID) + { + case FN_UNO_TEXT_PORTION_TYPE: + { + const char* pRet; + switch (m_ePortionType) + { + case PORTION_TEXT: pRet = "Text";break; + case PORTION_FIELD: pRet = "TextField";break; + case PORTION_FRAME: pRet = "Frame";break; + case PORTION_FOOTNOTE: pRet = "Footnote";break; + case PORTION_REFMARK_START: + case PORTION_REFMARK_END: pRet = UNO_NAME_REFERENCE_MARK;break; + case PORTION_TOXMARK_START: + case PORTION_TOXMARK_END: pRet = UNO_NAME_DOCUMENT_INDEX_MARK;break; + case PORTION_BOOKMARK_START : + case PORTION_BOOKMARK_END : pRet = UNO_NAME_BOOKMARK;break; + case PORTION_REDLINE_START: + case PORTION_REDLINE_END: pRet = "Redline";break; + case PORTION_RUBY_START: + case PORTION_RUBY_END: pRet = "Ruby";break; + case PORTION_SOFT_PAGEBREAK:pRet = "SoftPageBreak";break; + case PORTION_META: pRet = UNO_NAME_META; break; + case PORTION_FIELD_START:pRet = "TextFieldStart";break; + case PORTION_FIELD_SEP: pRet = "TextFieldSeparator";break; + case PORTION_FIELD_END:pRet = "TextFieldEnd";break; + case PORTION_FIELD_START_END:pRet = "TextFieldStartEnd";break; + case PORTION_ANNOTATION: + pRet = "Annotation"; + break; + case PORTION_ANNOTATION_END: + pRet = "AnnotationEnd"; + break; + default: + pRet = nullptr; + } + + OUString sRet; + if( pRet ) + sRet = OUString::createFromAscii( pRet ); + rVal <<= sRet; + } + break; + case FN_UNO_CONTROL_CHARACTER: // obsolete! + break; + case FN_UNO_DOCUMENT_INDEX_MARK: + rVal <<= m_xTOXMark; + break; + case FN_UNO_REFERENCE_MARK: + rVal <<= m_xRefMark; + break; + case FN_UNO_BOOKMARK: + rVal <<= m_xBookmark; + break; + case FN_UNO_FOOTNOTE: + rVal <<= m_xFootnote; + break; + case FN_UNO_TEXT_FIELD: + rVal <<= m_xTextField; + break; + case FN_UNO_META: + rVal <<= m_xMeta; + break; + case FN_UNO_IS_COLLAPSED: + { + switch (m_ePortionType) + { + case PORTION_REFMARK_START: + case PORTION_BOOKMARK_START : + case PORTION_TOXMARK_START: + case PORTION_REFMARK_END: + case PORTION_TOXMARK_END: + case PORTION_BOOKMARK_END : + case PORTION_REDLINE_START : + case PORTION_REDLINE_END : + case PORTION_RUBY_START: + case PORTION_RUBY_END: + case PORTION_FIELD_START: + case PORTION_FIELD_SEP: + case PORTION_FIELD_END: + rVal <<= m_bIsCollapsed; + break; + default: + break; + } + } + break; + case FN_UNO_IS_START: + { + bool bStart = true, bPut = true; + switch (m_ePortionType) + { + case PORTION_REFMARK_START: + case PORTION_BOOKMARK_START: + case PORTION_TOXMARK_START: + case PORTION_REDLINE_START: + case PORTION_RUBY_START: + case PORTION_FIELD_START: + break; + + case PORTION_REFMARK_END: + case PORTION_TOXMARK_END: + case PORTION_BOOKMARK_END: + case PORTION_REDLINE_END: + case PORTION_RUBY_END: + case PORTION_FIELD_SEP: + case PORTION_FIELD_END: + bStart = false; + break; + default: + bPut = false; + } + if(bPut) + rVal <<= bStart; + } + break; + case RES_TXTATR_CJK_RUBY: + { + const uno::Any* pToSet = nullptr; + switch(rEntry.nMemberId) + { + case MID_RUBY_TEXT : pToSet = m_pRubyText.get(); break; + case MID_RUBY_ADJUST : pToSet = m_pRubyAdjust.get(); break; + case MID_RUBY_CHARSTYLE:pToSet = m_pRubyStyle.get(); break; + case MID_RUBY_ABOVE : pToSet = m_pRubyIsAbove.get();break; + case MID_RUBY_POSITION: pToSet = m_pRubyPosition.get();break; + } + if(pToSet) + rVal = *pToSet; + } + break; + default: + beans::PropertyState eTemp; + bool bDone = SwUnoCursorHelper::getCursorPropertyValue( + rEntry, *pUnoCursor, &rVal, eTemp ); + if(!bDone) + { + if(!pSet) + { + pSet = std::make_unique<SfxItemSet>( + pUnoCursor->GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_FRMATR_END - 1, + RES_UNKNOWNATR_CONTAINER, + RES_UNKNOWNATR_CONTAINER>{}); + SwUnoCursorHelper::GetCursorAttr(*pUnoCursor, *pSet); + } + m_pPropSet->getPropertyValue(rEntry, *pSet, rVal); + } + } +} + +uno::Sequence< uno::Any > SwXTextPortion::GetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames ) +{ + sal_Int32 nLength = rPropertyNames.getLength(); + const OUString *pPropertyNames = rPropertyNames.getConstArray(); + uno::Sequence< uno::Any > aValues(nLength); + uno::Any *pValues = aValues.getArray(); + SwUnoCursor& rUnoCursor = GetCursor(); + + { + std::unique_ptr<SfxItemSet> pSet; + // get starting point for the look-up, either the provided one or else + // from the beginning of the map + const SfxItemPropertyMap& rMap = m_pPropSet->getPropertyMap(); + for(sal_Int32 nProp = 0; nProp < nLength; nProp++) + { + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(pPropertyNames[nProp]); + if(!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + pPropertyNames[nProp], static_cast < cppu::OWeakObject * > ( this ) ); + GetPropertyValue( pValues[nProp], *pEntry, &rUnoCursor, pSet ); + } + } + return aValues; +} + +uno::Any SwXTextPortion::getPropertyValue( + const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Sequence< OUString > aPropertyNames { rPropertyName }; + return GetPropertyValues_Impl(aPropertyNames).getConstArray()[0]; +} + +void SwXTextPortion::SetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues ) +{ + SwUnoCursor& rUnoCursor = GetCursor(); + + { + const OUString* pPropertyNames = rPropertyNames.getConstArray(); + const uno::Any* pValues = rValues.getConstArray(); + const SfxItemPropertyMap& rMap = m_pPropSet->getPropertyMap(); + uno::Sequence< beans::PropertyValue > aValues( rPropertyNames.getLength() ); + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); nProp++) + { + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(pPropertyNames[nProp]); + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + pPropertyNames[nProp], static_cast < cppu::OWeakObject * > ( this ) ); + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException ("Property is read-only: " + pPropertyNames[nProp], static_cast < cppu::OWeakObject * > ( this ) ); + + aValues[nProp].Name = pPropertyNames[nProp]; + aValues[nProp].Value = pValues[nProp]; + } + SwUnoCursorHelper::SetPropertyValues( rUnoCursor, *m_pPropSet, aValues ); + } +} + +void SwXTextPortion::setPropertyValues( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues ) +{ + SolarMutexGuard aGuard; + + // workaround for bad designed API + try + { + SetPropertyValues_Impl( rPropertyNames, rValues ); + } + catch (const beans::UnknownPropertyException &rException) + { + // wrap the original (here not allowed) exception in + // a lang::WrappedTargetException that gets thrown instead. + lang::WrappedTargetException aWExc; + aWExc.TargetException <<= rException; + throw aWExc; + } +} + +uno::Sequence< uno::Any > SwXTextPortion::getPropertyValues( + const uno::Sequence< OUString >& rPropertyNames ) +{ + SolarMutexGuard aGuard; + uno::Sequence< uno::Any > aValues; + + // workaround for bad designed API + try + { + aValues = GetPropertyValues_Impl( rPropertyNames ); + } + catch (beans::UnknownPropertyException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("Unknown property exception caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + catch (lang::WrappedTargetException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("WrappedTargetException caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + + return aValues; +} + +/* disabled for #i46921# */ +uno::Sequence< beans::SetPropertyTolerantFailed > SAL_CALL SwXTextPortion::setPropertyValuesTolerant( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues ) +{ + SolarMutexGuard aGuard; + + if (rPropertyNames.getLength() != rValues.getLength()) + throw lang::IllegalArgumentException(); + SwUnoCursor& rUnoCursor = GetCursor(); + + sal_Int32 nProps = rPropertyNames.getLength(); + const OUString *pProp = rPropertyNames.getConstArray(); + + //sal_Int32 nVals = rValues.getLength(); + const uno::Any *pValue = rValues.getConstArray(); + + sal_Int32 nFailed = 0; + uno::Sequence< beans::SetPropertyTolerantFailed > aFailed( nProps ); + beans::SetPropertyTolerantFailed *pFailed = aFailed.getArray(); + + const SfxItemPropertyMap& rPropMap = m_pPropSet->getPropertyMap(); + + for (sal_Int32 i = 0; i < nProps; ++i) + { + try + { + pFailed[ nFailed ].Name = pProp[i]; + + const SfxItemPropertySimpleEntry* pEntry = rPropMap.getByName( pProp[i] ); + if (!pEntry) + pFailed[ nFailed++ ].Result = beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + else + { + // set property value + // (compare to SwXTextPortion::setPropertyValues) + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + pFailed[ nFailed++ ].Result = beans::TolerantPropertySetResultType::PROPERTY_VETO; + else + { + SwUnoCursorHelper::SetPropertyValue( + rUnoCursor, *m_pPropSet, pProp[i], pValue[i] ); + } + } + } + catch (beans::UnknownPropertyException &) + { + // should not occur because property was searched for before + OSL_FAIL( "unexpected exception caught" ); + pFailed[ nFailed++ ].Result = beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + catch (lang::IllegalArgumentException &) + { + pFailed[ nFailed++ ].Result = beans::TolerantPropertySetResultType::ILLEGAL_ARGUMENT; + } + catch (beans::PropertyVetoException &) + { + pFailed[ nFailed++ ].Result = beans::TolerantPropertySetResultType::PROPERTY_VETO; + } + catch (lang::WrappedTargetException &) + { + pFailed[ nFailed++ ].Result = beans::TolerantPropertySetResultType::WRAPPED_TARGET; + } + } + + aFailed.realloc( nFailed ); + return aFailed; +} + +uno::Sequence< beans::GetPropertyTolerantResult > SAL_CALL SwXTextPortion::getPropertyValuesTolerant( + const uno::Sequence< OUString >& rPropertyNames ) +{ + SolarMutexGuard aGuard; + + uno::Sequence< beans::GetDirectPropertyTolerantResult > aTmpRes( + GetPropertyValuesTolerant_Impl( rPropertyNames, false ) ); + + // copy temporary result to final result type + sal_Int32 nLen = aTmpRes.getLength(); + uno::Sequence< beans::GetPropertyTolerantResult > aRes( nLen ); + std::copy(aTmpRes.begin(), aTmpRes.end(), aRes.begin()); + return aRes; +} + +uno::Sequence< beans::GetDirectPropertyTolerantResult > SAL_CALL SwXTextPortion::getDirectPropertyValuesTolerant( + const uno::Sequence< OUString >& rPropertyNames ) +{ + SolarMutexGuard aGuard; + return GetPropertyValuesTolerant_Impl( rPropertyNames, true ); +} + +uno::Sequence< beans::GetDirectPropertyTolerantResult > SwXTextPortion::GetPropertyValuesTolerant_Impl( + const uno::Sequence< OUString >& rPropertyNames, + bool bDirectValuesOnly ) +{ + SolarMutexGuard aGuard; + + SwUnoCursor& rUnoCursor = GetCursor(); + + std::vector< beans::GetDirectPropertyTolerantResult > aResultVector; + + try + { + sal_Int32 nProps = rPropertyNames.getLength(); + const OUString *pProp = rPropertyNames.getConstArray(); + + std::unique_ptr<SfxItemSet> pSet; + + const SfxItemPropertyMap& rPropMap = m_pPropSet->getPropertyMap(); + + + uno::Sequence< beans::PropertyState > aPropertyStates = + SwUnoCursorHelper::GetPropertyStates( + rUnoCursor, *m_pPropSet, + rPropertyNames, + SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT ); + const beans::PropertyState* pPropertyStates = aPropertyStates.getConstArray(); + + for (sal_Int32 i = 0; i < nProps; ++i) + { + beans::GetDirectPropertyTolerantResult aResult; + try + { + aResult.Name = pProp[i]; + if(pPropertyStates[i] == beans::PropertyState::PropertyState_MAKE_FIXED_SIZE) // property unknown? + { + if( bDirectValuesOnly ) + continue; + else + aResult.Result = beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + else + { + const SfxItemPropertySimpleEntry* pEntry = rPropMap.getByName( pProp[i] ); + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + pProp[i], static_cast < cppu::OWeakObject * > ( this ) ); + aResult.State = pPropertyStates[i]; + + aResult.Result = beans::TolerantPropertySetResultType::UNKNOWN_FAILURE; + //#i104499# ruby portion attributes need special handling: + if( pEntry->nWID == RES_TXTATR_CJK_RUBY && + m_ePortionType == PORTION_RUBY_START ) + { + aResult.State = beans::PropertyState_DIRECT_VALUE; + } + if (!bDirectValuesOnly || beans::PropertyState_DIRECT_VALUE == aResult.State) + { + // get property value + // (compare to SwXTextPortion::getPropertyValue(s)) + GetPropertyValue( aResult.Value, *pEntry, &rUnoCursor, pSet ); + aResult.Result = beans::TolerantPropertySetResultType::SUCCESS; + aResultVector.push_back( aResult ); + } + } + } + catch (const beans::UnknownPropertyException &) + { + // should not occur because property was searched for before + OSL_FAIL( "unexpected exception caught" ); + aResult.Result = beans::TolerantPropertySetResultType::UNKNOWN_PROPERTY; + } + catch (const lang::IllegalArgumentException &) + { + aResult.Result = beans::TolerantPropertySetResultType::ILLEGAL_ARGUMENT; + } + catch (const beans::PropertyVetoException &) + { + aResult.Result = beans::TolerantPropertySetResultType::PROPERTY_VETO; + } + catch (const lang::WrappedTargetException &) + { + aResult.Result = beans::TolerantPropertySetResultType::WRAPPED_TARGET; + } + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception& e) + { + css::uno::Any a(cppu::getCaughtException()); + throw css::lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + css::uno::Reference<css::uno::XInterface>(), a); + } + + return comphelper::containerToSequence(aResultVector); +} + +void SwXTextPortion::addPropertiesChangeListener( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{} + +void SwXTextPortion::removePropertiesChangeListener( + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{} + +void SwXTextPortion::firePropertiesChangeEvent( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{} + +void SwXTextPortion::addPropertyChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextPortion::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextPortion::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextPortion::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + OSL_FAIL("not implemented"); +} + +beans::PropertyState SwXTextPortion::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + beans::PropertyState eRet = beans::PropertyState_DEFAULT_VALUE; + SwUnoCursor& rUnoCursor = GetCursor(); + + if (GetTextPortionType() == PORTION_RUBY_START && + rPropertyName.startsWith("Ruby")) + { + eRet = beans::PropertyState_DIRECT_VALUE; + } + else + { + eRet = SwUnoCursorHelper::GetPropertyState(rUnoCursor, *m_pPropSet, + rPropertyName); + } + return eRet; +} + +uno::Sequence< beans::PropertyState > SwXTextPortion::getPropertyStates( + const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + + uno::Sequence< beans::PropertyState > aRet = + SwUnoCursorHelper::GetPropertyStates(rUnoCursor, *m_pPropSet, + rPropertyNames, SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION); + + if(GetTextPortionType() == PORTION_RUBY_START) + { + const OUString* pNames = rPropertyNames.getConstArray(); + beans::PropertyState* pStates = aRet.getArray(); + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength();nProp++) + { + if (pNames[nProp].startsWith("Ruby")) + pStates[nProp] = beans::PropertyState_DIRECT_VALUE; + } + } + return aRet; +} + +void SwXTextPortion::setPropertyToDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + + SwUnoCursorHelper::SetPropertyToDefault( + rUnoCursor, *m_pPropSet, rPropertyName); +} + +uno::Any SwXTextPortion::getPropertyDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwUnoCursor& rUnoCursor = GetCursor(); + + aRet = SwUnoCursorHelper::GetPropertyDefault(rUnoCursor, *m_pPropSet, + rPropertyName); + return aRet; +} + +uno::Reference< container::XEnumeration > SwXTextPortion::createContentEnumeration(const OUString& /*aServiceName*/) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + + return SwXParaFrameEnumeration::Create(rUnoCursor, PARAFRAME_PORTION_CHAR, m_pFrameFormat); +} + +namespace +{ + class theSwXTextPortionUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextPortionUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextPortion::getUnoTunnelId() +{ + return theSwXTextPortionUnoTunnelId::get().getSeq(); +} + +sal_Int64 SwXTextPortion::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXTextPortion>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + +uno::Sequence< OUString > SwXTextPortion::getAvailableServiceNames() +{ + return { "com.sun.star.text.TextContent" }; +} + +OUString SwXTextPortion::getImplementationName() +{ + return { "SwXTextPortion" }; +} + +sal_Bool SwXTextPortion::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextPortion::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextPortion", + "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesAsian", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.ParagraphProperties", + "com.sun.star.style.ParagraphPropertiesAsian", + "com.sun.star.style.ParagraphPropertiesComplex" }; +} + +void SwXTextPortion::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFrameFormat = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoportenum.cxx b/sw/source/core/unocore/unoportenum.cxx new file mode 100644 index 000000000..8b968bb82 --- /dev/null +++ b/sw/source/core/unocore/unoportenum.cxx @@ -0,0 +1,1489 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <utility> + +#include <unoport.hxx> +#include <IMark.hxx> +#include <crossrefbookmark.hxx> +#include <annotationmark.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <txatbase.hxx> +#include <txtatr.hxx> +#include <ndhints.hxx> +#include <ndtxt.hxx> +#include <unocrsr.hxx> +#include <docary.hxx> +#include <textboxhelper.hxx> +#include <tox.hxx> +#include <unoparaframeenum.hxx> +#include <unocrsrhelper.hxx> +#include <unorefmark.hxx> +#include <unobookmark.hxx> +#include <unofield.hxx> +#include <unometa.hxx> +#include <fmtfld.hxx> +#include <fldbas.hxx> +#include <fmtmeta.hxx> +#include <fmtanchr.hxx> +#include <fmtrfmrk.hxx> +#include <frmfmt.hxx> +#include <fmtflcnt.hxx> +#include <unoidx.hxx> +#include <unocoll.hxx> +#include <redline.hxx> +#include <txtannotationfld.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/string.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/container/XEnumeration.hpp> +#include <algorithm> +#include <memory> +#include <set> +#include <stack> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; +using namespace ::std; + +typedef std::pair< TextRangeList_t * const, SwTextAttr const * const > PortionList_t; +typedef std::stack< PortionList_t > PortionStack_t; + +static void lcl_CreatePortions( + TextRangeList_t & i_rPortions, + uno::Reference< text::XText > const& i_xParentText, + SwUnoCursor* pUnoCursor, + FrameClientSortList_t & i_rFrames, + const sal_Int32 i_nStartPos, const sal_Int32 i_nEndPos ); + +namespace +{ + enum class BkmType { + Start, End, StartEnd + }; + + struct SwXBookmarkPortion_Impl + { + Reference<XTextContent> xBookmark; + BkmType nBkmType; + const SwPosition aPosition; + + SwXBookmarkPortion_Impl(uno::Reference<text::XTextContent> const& xMark, + const BkmType nType, SwPosition const& rPosition) + : xBookmark ( xMark ) + , nBkmType ( nType ) + , aPosition ( rPosition ) + { + } + sal_Int32 getIndex () const + { + return aPosition.nContent.GetIndex(); + } + }; + typedef std::shared_ptr < SwXBookmarkPortion_Impl > SwXBookmarkPortion_ImplSharedPtr; + struct BookmarkCompareStruct + { + bool operator () ( const SwXBookmarkPortion_ImplSharedPtr &r1, + const SwXBookmarkPortion_ImplSharedPtr &r2 ) const + { + // #i16896# for bookmark portions at the same position, the start should + // always precede the end. Hence compare positions, and use bookmark type + // as tie-breaker for same position. + // return ( r1->nIndex == r2->nIndex ) + // ? ( r1->nBkmType < r2->nBkmType ) + // : ( r1->nIndex < r2->nIndex ); + + // Note that the above code does not correctly handle + // the case when one bookmark ends, and another begins in the same + // position. When this occurs, the above code will return the + // start of the 2nd bookmark BEFORE the end of the first bookmark + // See bug #i58438# for more details. The below code is correct and + // fixes both #i58438 and #i16896# + return r1->aPosition < r2->aPosition; + } + }; + typedef std::multiset < SwXBookmarkPortion_ImplSharedPtr, BookmarkCompareStruct > SwXBookmarkPortion_ImplList; + + /// Inserts pBkmk to rBkmArr in case it starts or ends at nOwnNode + void lcl_FillBookmark(sw::mark::IMark* const pBkmk, const SwNodeIndex& nOwnNode, SwDoc& rDoc, SwXBookmarkPortion_ImplList& rBkmArr) + { + bool const hasOther = pBkmk->IsExpanded(); + + const SwPosition& rStartPos = pBkmk->GetMarkStart(); + if(rStartPos.nNode == nOwnNode) + { + // #i109272#: cross reference marks: need special handling! + ::sw::mark::CrossRefBookmark *const pCrossRefMark(dynamic_cast< ::sw::mark::CrossRefBookmark*>(pBkmk)); + BkmType const nType = (hasOther || pCrossRefMark) + ? BkmType::Start : BkmType::StartEnd; + rBkmArr.insert(std::make_shared<SwXBookmarkPortion_Impl>( + SwXBookmark::CreateXBookmark(rDoc, pBkmk), + nType, rStartPos)); + } + + const SwPosition& rEndPos = pBkmk->GetMarkEnd(); + if(rEndPos.nNode == nOwnNode) + { + unique_ptr<SwPosition> pCrossRefEndPos; + const SwPosition* pEndPos = nullptr; + ::sw::mark::CrossRefBookmark *const pCrossRefMark(dynamic_cast< ::sw::mark::CrossRefBookmark*>(pBkmk)); + if(hasOther) + { + pEndPos = &rEndPos; + } + else if (pCrossRefMark) + { + // Crossrefbookmarks only remember the start position but have to span the whole paragraph + pCrossRefEndPos = std::make_unique<SwPosition>(rEndPos); + pCrossRefEndPos->nContent = pCrossRefEndPos->nNode.GetNode().GetTextNode()->Len(); + pEndPos = pCrossRefEndPos.get(); + } + if(pEndPos) + { + rBkmArr.insert(std::make_shared<SwXBookmarkPortion_Impl>( + SwXBookmark::CreateXBookmark(rDoc, pBkmk), + BkmType::End, *pEndPos)); + } + } + } + + void lcl_FillBookmarkArray(SwDoc& rDoc, SwUnoCursor& rUnoCursor, SwXBookmarkPortion_ImplList& rBkmArr) + { + IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess(); + if(!pMarkAccess->getBookmarksCount()) + return; + + const SwNodeIndex nOwnNode = rUnoCursor.GetPoint()->nNode; + SwTextNode* pTextNode = nOwnNode.GetNode().GetTextNode(); + if (!pTextNode) + { + // no need to consider marks starting after aEndOfPara + SwPosition aEndOfPara(*rUnoCursor.GetPoint()); + aEndOfPara.nContent = aEndOfPara.nNode.GetNode().GetTextNode()->Len(); + const IDocumentMarkAccess::const_iterator_t pCandidatesEnd = + pMarkAccess->findFirstBookmarkStartsAfter(aEndOfPara); + + // search for all bookmarks that start or end in this paragraph + for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin(); + ppMark != pCandidatesEnd; + ++ppMark) + { + ::sw::mark::IMark* const pBkmk = *ppMark; + lcl_FillBookmark(pBkmk, nOwnNode, rDoc, rBkmArr); + } + } + else + { + // A text node already knows its marks via its SwIndexes. + o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks; + for (const SwIndex* pIndex = pTextNode->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + { + // Need a non-cost mark here, as we'll create a UNO wrapper around it. + sw::mark::IMark* pBkmk = const_cast<sw::mark::IMark*>(pIndex->GetMark()); + if (!pBkmk) + continue; + IDocumentMarkAccess::MarkType eType = IDocumentMarkAccess::GetType(*pBkmk); + // These are the types stored in the container otherwise accessible via getBookmarks*() + if (eType != IDocumentMarkAccess::MarkType::BOOKMARK && eType != IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK && + eType != IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK) + continue; + // Only handle bookmarks once, if they start and end at this node as well. + if (!aSeenMarks.insert(pBkmk).second) + continue; + lcl_FillBookmark(pBkmk, nOwnNode, rDoc, rBkmArr); + } + } + } + + class theSwXTextPortionEnumerationUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextPortionEnumerationUnoTunnelId > {}; + struct SwAnnotationStartPortion_Impl + { + + uno::Reference< text::XTextField > mxAnnotationField; + const SwPosition maPosition; + + SwAnnotationStartPortion_Impl( + uno::Reference< text::XTextField > const& xAnnotationField, + SwPosition const& rPosition) + : mxAnnotationField ( xAnnotationField ) + , maPosition ( rPosition ) + { + } + + sal_Int32 getIndex () const + { + return maPosition.nContent.GetIndex(); + } + }; + typedef std::shared_ptr < SwAnnotationStartPortion_Impl > SwAnnotationStartPortion_ImplSharedPtr; + struct AnnotationStartCompareStruct + { + bool operator () ( const SwAnnotationStartPortion_ImplSharedPtr &r1, + const SwAnnotationStartPortion_ImplSharedPtr &r2 ) + const + { + return r1->maPosition < r2->maPosition; + } + }; + typedef std::multiset < SwAnnotationStartPortion_ImplSharedPtr, AnnotationStartCompareStruct > SwAnnotationStartPortion_ImplList; + + void lcl_FillAnnotationStartArray( + SwDoc& rDoc, + SwUnoCursor& rUnoCursor, + SwAnnotationStartPortion_ImplList& rAnnotationStartArr ) + { + IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess(); + if ( pMarkAccess->getAnnotationMarksCount() == 0 ) + { + return; + } + + // no need to consider annotation marks starting after aEndOfPara + SwPosition aEndOfPara(*rUnoCursor.GetPoint()); + aEndOfPara.nContent = aEndOfPara.nNode.GetNode().GetTextNode()->Len(); + const IDocumentMarkAccess::const_iterator_t pCandidatesEnd = + pMarkAccess->findFirstAnnotationStartsAfter(aEndOfPara); + + // search for all annotation marks that have its start position in this paragraph + const SwNodeIndex nOwnNode = rUnoCursor.GetPoint()->nNode; + for( IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getAnnotationMarksBegin(); + ppMark != pCandidatesEnd; + ++ppMark ) + { + ::sw::mark::AnnotationMark* const pAnnotationMark = + dynamic_cast< ::sw::mark::AnnotationMark* >(*ppMark); + + if (!pAnnotationMark) + continue; + + const SwPosition& rStartPos = pAnnotationMark->GetMarkStart(); + if (rStartPos.nNode != nOwnNode) + continue; + + const SwFormatField* pAnnotationFormatField = pAnnotationMark->GetAnnotationFormatField(); + if (!pAnnotationFormatField) + { + SAL_WARN("sw.core", "missing annotation format field"); + continue; + } + + rAnnotationStartArr.insert( + std::make_shared<SwAnnotationStartPortion_Impl>( + SwXTextField::CreateXTextField(&rDoc, + pAnnotationFormatField), + rStartPos)); + } + } +} + +const uno::Sequence< sal_Int8 > & SwXTextPortionEnumeration::getUnoTunnelId() +{ + return theSwXTextPortionEnumerationUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXTextPortionEnumeration::getSomething( + const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXTextPortionEnumeration>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >( this ) ); + } + return 0; +} + +OUString SwXTextPortionEnumeration::getImplementationName() +{ + return "SwXTextPortionEnumeration"; +} + +sal_Bool +SwXTextPortionEnumeration::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextPortionEnumeration::getSupportedServiceNames() +{ + return { "com.sun.star.text.TextPortionEnumeration" }; +} + +SwXTextPortionEnumeration::SwXTextPortionEnumeration( + SwPaM& rParaCursor, + uno::Reference< XText > const & xParentText, + const sal_Int32 nStart, + const sal_Int32 nEnd ) + : m_Portions() +{ + m_pUnoCursor = rParaCursor.GetDoc()->CreateUnoCursor(*rParaCursor.GetPoint()); + + OSL_ENSURE(nEnd == -1 || (nStart <= nEnd && + nEnd <= m_pUnoCursor->Start()->nNode.GetNode().GetTextNode()->GetText().getLength()), + "start or end value invalid!"); + + // find all frames, graphics and OLEs that are bound AT character in para + FrameClientSortList_t frames; + ::CollectFrameAtNode(m_pUnoCursor->GetPoint()->nNode, frames, true); + lcl_CreatePortions(m_Portions, xParentText, &*m_pUnoCursor, frames, nStart, nEnd); +} + +SwXTextPortionEnumeration::SwXTextPortionEnumeration( + SwPaM& rParaCursor, + TextRangeList_t const & rPortions ) + : m_Portions( rPortions ) +{ + m_pUnoCursor = rParaCursor.GetDoc()->CreateUnoCursor(*rParaCursor.GetPoint()); +} + +SwXTextPortionEnumeration::~SwXTextPortionEnumeration() +{ + SolarMutexGuard aGuard; + m_pUnoCursor.reset(nullptr); +} + +sal_Bool SwXTextPortionEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + + return !m_Portions.empty(); +} + +uno::Any SwXTextPortionEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if (m_Portions.empty()) + throw container::NoSuchElementException(); + + Any any; + any <<= m_Portions.front(); + m_Portions.pop_front(); + return any; +} + +static void +lcl_FillFieldMarkArray(std::deque<sal_Int32> & rFieldMarks, SwUnoCursor const & rUnoCursor, + const sal_Int32 i_nStartPos) +{ + const SwTextNode * const pTextNode = + rUnoCursor.GetPoint()->nNode.GetNode().GetTextNode(); + if (!pTextNode) return; + + const sal_Unicode fld[] = { + CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDSEP, CH_TXT_ATR_FIELDEND, CH_TXT_ATR_FORMELEMENT, 0 }; + sal_Int32 pos = std::max(static_cast<sal_Int32>(0), i_nStartPos); + while ((pos = ::comphelper::string::indexOfAny(pTextNode->GetText(), fld, pos)) != -1) + { + rFieldMarks.push_back(pos); + ++pos; + } +} + +static uno::Reference<text::XTextRange> +lcl_ExportFieldMark( + uno::Reference< text::XText > const & i_xParentText, + SwUnoCursor * const pUnoCursor, + const SwTextNode * const pTextNode ) +{ + uno::Reference<text::XTextRange> xRef; + SwDoc* pDoc = pUnoCursor->GetDoc(); + // maybe it's a good idea to add a special hint to the hints array and rely on the hint segmentation... + const sal_Int32 start = pUnoCursor->Start()->nContent.GetIndex(); + OSL_ENSURE(pUnoCursor->End()->nContent.GetIndex() == start, + "hmm --- why is this different"); + + pUnoCursor->Right(1); + if ( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + { + OSL_FAIL("cannot move cursor?"); + return nullptr; + } + + const sal_Unicode Char = pTextNode->GetText()[start]; + if (CH_TXT_ATR_FIELDSTART == Char) + { + ::sw::mark::IFieldmark* pFieldmark = nullptr; + if (pDoc) + { + pFieldmark = pDoc->getIDocumentMarkAccess()-> + getFieldmarkAt(*pUnoCursor->GetMark()); + } + SwXTextPortion* pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_START); + xRef = pPortion; + if (pFieldmark && pDoc) + { + pPortion->SetBookmark( + SwXFieldmark::CreateXFieldmark(*pDoc, pFieldmark)); + } + } + else if (CH_TXT_ATR_FIELDSEP == Char) + { + // TODO how to get the field? + SwXTextPortion* pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_SEP); + xRef = pPortion; + } + else if (CH_TXT_ATR_FIELDEND == Char) + { + ::sw::mark::IFieldmark* pFieldmark = nullptr; + if (pDoc) + { + pFieldmark = pDoc->getIDocumentMarkAccess()-> + getFieldmarkAt(*pUnoCursor->GetMark()); + } + SwXTextPortion* pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_END); + xRef = pPortion; + if (pFieldmark && pDoc) + { + pPortion->SetBookmark( + SwXFieldmark::CreateXFieldmark(*pDoc, pFieldmark)); + } + } + else if (CH_TXT_ATR_FORMELEMENT == Char) + { + ::sw::mark::IFieldmark* pFieldmark = nullptr; + if (pDoc) + { + pFieldmark = pDoc->getIDocumentMarkAccess()->getFieldmarkAt(*pUnoCursor->GetMark()); + } + SwXTextPortion* pPortion = new SwXTextPortion( + pUnoCursor, i_xParentText, PORTION_FIELD_START_END); + xRef = pPortion; + if (pFieldmark && pDoc) + { + pPortion->SetBookmark( + SwXFieldmark::CreateXFieldmark(*pDoc, pFieldmark)); + } + } + else + { + OSL_FAIL("no fieldmark found?"); + } + return xRef; +} + +static Reference<XTextRange> +lcl_CreateRefMarkPortion( + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + const SwTextAttr & rAttr, const bool bEnd) +{ + SwDoc* pDoc = pUnoCursor->GetDoc(); + SwFormatRefMark& rRefMark = const_cast<SwFormatRefMark&>( + static_cast<const SwFormatRefMark&>(rAttr.GetAttr())); + Reference<XTextContent> xContent; + if (!xContent.is()) + { + xContent = SwXReferenceMark::CreateXReferenceMark(*pDoc, &rRefMark); + } + + SwXTextPortion* pPortion = nullptr; + if (!bEnd) + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_REFMARK_START); + pPortion->SetRefMark(xContent); + pPortion->SetCollapsed(rAttr.End() == nullptr); + } + else + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_REFMARK_END); + pPortion->SetRefMark(xContent); + } + return pPortion; +} + +static void +lcl_InsertRubyPortion( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + const SwTextAttr & rAttr, const bool bEnd) +{ + SwXTextPortion* pPortion = new SwXTextPortion(pUnoCursor, + static_txtattr_cast<const SwTextRuby&>(rAttr), xParent, bEnd); + rPortions.emplace_back(pPortion); + pPortion->SetCollapsed(rAttr.End() == nullptr); +} + +static Reference<XTextRange> +lcl_CreateTOXMarkPortion( + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwTextAttr & rAttr, const bool bEnd) +{ + SwDoc* pDoc = pUnoCursor->GetDoc(); + SwTOXMark & rTOXMark = static_cast<SwTOXMark&>(rAttr.GetAttr()); + + const Reference<XTextContent> xContent = + SwXDocumentIndexMark::CreateXDocumentIndexMark(*pDoc, & rTOXMark); + + SwXTextPortion* pPortion = nullptr; + if (!bEnd) + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_TOXMARK_START); + pPortion->SetTOXMark(xContent); + pPortion->SetCollapsed(rAttr.GetEnd() == nullptr); + } + else + { + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_TOXMARK_END); + pPortion->SetTOXMark(xContent); + } + return pPortion; +} + +static uno::Reference<text::XTextRange> +lcl_CreateMetaPortion( + uno::Reference<text::XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwTextAttr & rAttr, std::unique_ptr<TextRangeList_t const> && pPortions) +{ + const uno::Reference<rdf::XMetadatable> xMeta( SwXMeta::CreateXMeta( + *static_cast<SwFormatMeta &>(rAttr.GetAttr()).GetMeta(), + xParent, std::move(pPortions))); + SwXTextPortion * pPortion(nullptr); + if (RES_TXTATR_META == rAttr.Which()) + { + const uno::Reference<text::XTextContent> xContent(xMeta, + uno::UNO_QUERY); + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_META); + pPortion->SetMeta(xContent); + } + else + { + const uno::Reference<text::XTextField> xField(xMeta, uno::UNO_QUERY); + pPortion = new SwXTextPortion(pUnoCursor, xParent, PORTION_FIELD); + pPortion->SetTextField(xField); + } + return pPortion; +} + +/** + * Exports all bookmarks from rBkmArr into rPortions that have the same start + * or end position as nIndex. + * + * @param rBkmArr the array of bookmarks. If bOnlyFrameStarts is true, then + * this is only read, otherwise consumed entries are removed. + * + * @param rFramePositions the list of positions where there is an at-char / + * anchored frame. + * + * @param bOnlyFrameStarts If true: export only the start of the bookmarks + * which cover an at-char anchored frame. If false: export the end of the same + * bookmarks and everything else. + */ +static void lcl_ExportBookmark( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwXBookmarkPortion_ImplList& rBkmArr, + const sal_Int32 nIndex, + const std::set<sal_Int32>& rFramePositions, + bool bOnlyFrameStarts) +{ + for ( SwXBookmarkPortion_ImplList::iterator aIter = rBkmArr.begin(), aEnd = rBkmArr.end(); aIter != aEnd; ) + { + const SwXBookmarkPortion_ImplSharedPtr& pPtr = *aIter; + if ( nIndex > pPtr->getIndex() ) + { + if (bOnlyFrameStarts) + ++aIter; + else + aIter = rBkmArr.erase(aIter); + continue; + } + if ( nIndex < pPtr->getIndex() ) + break; + + if ((BkmType::Start == pPtr->nBkmType && bOnlyFrameStarts) || + (BkmType::StartEnd == pPtr->nBkmType)) + { + bool bFrameStart = rFramePositions.find(nIndex) != rFramePositions.end(); + bool bEnd = pPtr->nBkmType == BkmType::StartEnd && bFrameStart && !bOnlyFrameStarts; + if (pPtr->nBkmType == BkmType::Start || bFrameStart || !bOnlyFrameStarts) + { + // At this we create a text portion, due to one of these + // reasons: + // - this is the real start of a non-collapsed bookmark + // - this is the real position of a collapsed bookmark + // - this is the start or end (depending on bOnlyFrameStarts) + // of a collapsed bookmark at the same position as an at-char + // anchored frame + SwXTextPortion* pPortion = + new SwXTextPortion(pUnoCursor, xParent, bEnd ? PORTION_BOOKMARK_END : PORTION_BOOKMARK_START); + rPortions.emplace_back(pPortion); + pPortion->SetBookmark(pPtr->xBookmark); + pPortion->SetCollapsed( BkmType::StartEnd == pPtr->nBkmType && !bFrameStart ); + } + } + else if (BkmType::End == pPtr->nBkmType && !bOnlyFrameStarts) + { + SwXTextPortion* pPortion = + new SwXTextPortion(pUnoCursor, xParent, PORTION_BOOKMARK_END); + rPortions.emplace_back(pPortion); + pPortion->SetBookmark(pPtr->xBookmark); + } + + // next bookmark + if (bOnlyFrameStarts) + ++aIter; + else + aIter = rBkmArr.erase(aIter); + } +} + +static void lcl_ExportSoftPageBreak( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwSoftPageBreakList& rBreakArr, + const sal_Int32 nIndex) +{ + for ( SwSoftPageBreakList::iterator aIter = rBreakArr.begin(), + aEnd = rBreakArr.end(); + aIter != aEnd; ) + { + if ( nIndex > *aIter ) + { + aIter = rBreakArr.erase(aIter); + continue; + } + if ( nIndex < *aIter ) + break; + + rPortions.push_back( + new SwXTextPortion(pUnoCursor, xParent, PORTION_SOFT_PAGEBREAK) ); + aIter = rBreakArr.erase(aIter); + } +} + +namespace { + +struct SwXRedlinePortion_Impl +{ + const SwRangeRedline* m_pRedline; + const bool m_bStart; + + SwXRedlinePortion_Impl ( const SwRangeRedline* pRed, const bool bIsStart ) + : m_pRedline(pRed) + , m_bStart(bIsStart) + { + } + + sal_Int32 getRealIndex () const + { + return m_bStart ? m_pRedline->Start()->nContent.GetIndex() + : m_pRedline->End() ->nContent.GetIndex(); + } +}; + +} + +typedef std::shared_ptr < SwXRedlinePortion_Impl > + SwXRedlinePortion_ImplSharedPtr; + +namespace { + +struct RedlineCompareStruct +{ + static const SwPosition& getPosition ( const SwXRedlinePortion_ImplSharedPtr &r ) + { + return *(r->m_bStart ? r->m_pRedline->Start() : r->m_pRedline->End()); + } + + bool operator () ( const SwXRedlinePortion_ImplSharedPtr &r1, + const SwXRedlinePortion_ImplSharedPtr &r2 ) const + { + return getPosition ( r1 ) < getPosition ( r2 ); + } +}; + +} + +typedef std::multiset < SwXRedlinePortion_ImplSharedPtr, RedlineCompareStruct > +SwXRedlinePortion_ImplList; + +static Reference<XTextRange> +lcl_ExportHints( + PortionStack_t & rPortionStack, + const Reference<XText> & xParent, + SwUnoCursor * const pUnoCursor, + SwpHints const * const pHints, + const sal_Int32 i_nStartPos, + const sal_Int32 i_nEndPos, + const sal_Int32 nCurrentIndex, + const bool bRightMoveForbidden, + bool & o_rbCursorMoved, + sal_Int32 & o_rNextAttrPosition) +{ + // if the attribute has a dummy character, then xRef is set (except META) + // otherwise, the portion for the attribute is inserted into rPortions! + Reference<XTextRange> xRef; + SwDoc* pDoc = pUnoCursor->GetDoc(); + //search for special text attributes - first some ends + size_t nEndIndex = 0; + sal_Int32 nNextEnd = 0; + while(nEndIndex < pHints->Count() && + (!pHints->GetSortedByEnd(nEndIndex)->GetEnd() || + nCurrentIndex >= (nNextEnd = (*pHints->GetSortedByEnd(nEndIndex)->GetEnd())))) + { + if(pHints->GetSortedByEnd(nEndIndex)->GetEnd()) + { + SwTextAttr * const pAttr = pHints->GetSortedByEnd(nEndIndex); + if (nNextEnd == nCurrentIndex) + { + const sal_uInt16 nWhich( pAttr->Which() ); + switch (nWhich) + { + case RES_TXTATR_TOXMARK: + { + Reference<XTextRange> xTmp = lcl_CreateTOXMarkPortion( + xParent, pUnoCursor, *pAttr, true); + rPortionStack.top().first->push_back(xTmp); + } + break; + case RES_TXTATR_REFMARK: + { + Reference<XTextRange> xTmp = lcl_CreateRefMarkPortion( + xParent, pUnoCursor, *pAttr, true); + rPortionStack.top().first->push_back(xTmp); + } + break; + case RES_TXTATR_CJK_RUBY: + //#i91534# GetEnd() == 0 mixes the order of ruby start/end + if( *pAttr->GetEnd() == pAttr->GetStart()) + { + lcl_InsertRubyPortion( *rPortionStack.top().first, + xParent, pUnoCursor, *pAttr, false); + } + lcl_InsertRubyPortion( *rPortionStack.top().first, + xParent, pUnoCursor, *pAttr, true); + break; + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + OSL_ENSURE(pAttr->GetStart() != *pAttr->GetEnd(), + "empty meta?"); + if ((i_nStartPos > 0) && + (pAttr->GetStart() < i_nStartPos)) + { + // force skip pAttr and rest of attribute ends + // at nCurrentIndex + // because they are not contained in the meta pAttr + // and the meta pAttr itself is outside selection! + // (necessary for SwXMeta::createEnumeration) + if (pAttr->GetStart() + 1 == i_nStartPos) + { + nEndIndex = pHints->Count() - 1; + } + break; + } + PortionList_t Top = rPortionStack.top(); + if (Top.second != pAttr) + { + OSL_FAIL("ExportHints: stack error" ); + } + else + { + std::unique_ptr<const TextRangeList_t> + pCurrentPortions(Top.first); + rPortionStack.pop(); + const uno::Reference<text::XTextRange> xPortion( + lcl_CreateMetaPortion(xParent, pUnoCursor, + *pAttr, std::move(pCurrentPortions))); + rPortionStack.top().first->push_back(xPortion); + } + } + break; + } + } + } + nEndIndex++; + } + + // then some starts + size_t nStartIndex = 0; + sal_Int32 nNextStart = 0; + while(nStartIndex < pHints->Count() && + nCurrentIndex >= (nNextStart = pHints->Get(nStartIndex)->GetStart())) + { + SwTextAttr * const pAttr = pHints->Get(nStartIndex); + sal_uInt16 nAttrWhich = pAttr->Which(); + if (nNextStart == nCurrentIndex) + { + switch( nAttrWhich ) + { + case RES_TXTATR_FIELD: + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + SwXTextPortion* pPortion; + xRef = pPortion = + new SwXTextPortion( + pUnoCursor, xParent, PORTION_FIELD); + Reference<XTextField> const xField = + SwXTextField::CreateXTextField(pDoc, + &pAttr->GetFormatField()); + pPortion->SetTextField(xField); + } + break; + + case RES_TXTATR_ANNOTATION: + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + + const SwTextAnnotationField* pTextAnnotationField = dynamic_cast<const SwTextAnnotationField*>( pAttr ); + ::sw::mark::IMark* pAnnotationMark = pTextAnnotationField ? pTextAnnotationField->GetAnnotationMark() : nullptr; + if ( pAnnotationMark != nullptr ) + { + SwXTextPortion* pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION_END ); + pPortion->SetBookmark(SwXBookmark::CreateXBookmark( + *pDoc, pAnnotationMark)); + xRef = pPortion; + } + else + { + SwXTextPortion* pPortion = new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION ); + Reference<XTextField> xField = + SwXTextField::CreateXTextField(pDoc, + &pAttr->GetFormatField()); + pPortion->SetTextField(xField); + xRef = pPortion; + } + } + break; + + case RES_TXTATR_INPUTFIELD: + if(!bRightMoveForbidden) + { + + pUnoCursor->Right( + pAttr->GetFormatField().GetField()->ExpandField(true, nullptr).getLength() + 2 ); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + SwXTextPortion* pPortion = + new SwXTextPortion( pUnoCursor, xParent, PORTION_FIELD); + xRef = pPortion; + Reference<XTextField> xField = + SwXTextField::CreateXTextField(pDoc, + &pAttr->GetFormatField()); + pPortion->SetTextField(xField); + } + break; + + case RES_TXTATR_FLYCNT: + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; // Robust #i81708# content in covered cells + + // Do not expose inline anchored textboxes. + if (SwTextBoxHelper::isTextBox(pAttr->GetFlyCnt().GetFrameFormat(), RES_FLYFRMFMT)) + break; + + pUnoCursor->Exchange(); + xRef = new SwXTextPortion( pUnoCursor, xParent, PORTION_FRAME); + } + break; + + case RES_TXTATR_FTN: + { + if(!bRightMoveForbidden) + { + pUnoCursor->Right(1); + if( *pUnoCursor->GetMark() == *pUnoCursor->GetPoint() ) + break; + SwXTextPortion* pPortion; + xRef = pPortion = new SwXTextPortion( + pUnoCursor, xParent, PORTION_FOOTNOTE); + Reference<XFootnote> xContent = + SwXFootnotes::GetObject(*pDoc, pAttr->GetFootnote()); + pPortion->SetFootnote(xContent); + } + } + break; + + case RES_TXTATR_TOXMARK: + case RES_TXTATR_REFMARK: + { + bool bIsPoint = !(pAttr->GetEnd()); + if (!bRightMoveForbidden || !bIsPoint) + { + if (bIsPoint) + { + pUnoCursor->Right(1); + } + Reference<XTextRange> xTmp = + (RES_TXTATR_REFMARK == nAttrWhich) + ? lcl_CreateRefMarkPortion( + xParent, pUnoCursor, *pAttr, false) + : lcl_CreateTOXMarkPortion( + xParent, pUnoCursor, *pAttr, false); + if (bIsPoint) // consume CH_TXTATR! + { + pUnoCursor->Normalize(false); + pUnoCursor->DeleteMark(); + xRef = xTmp; + } + else // just insert it + { + rPortionStack.top().first->push_back(xTmp); + } + } + } + break; + case RES_TXTATR_CJK_RUBY: + //#i91534# GetEnd() == 0 mixes the order of ruby start/end + if(pAttr->GetEnd() && (*pAttr->GetEnd() != pAttr->GetStart())) + { + lcl_InsertRubyPortion( *rPortionStack.top().first, + xParent, pUnoCursor, *pAttr, false); + } + break; + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + if (pAttr->GetStart() != *pAttr->GetEnd()) + { + if (!bRightMoveForbidden) + { + pUnoCursor->Right(1); + o_rbCursorMoved = true; + // only if the end is included in selection! + if ((i_nEndPos < 0) || + (*pAttr->GetEnd() <= i_nEndPos)) + { + rPortionStack.push( std::make_pair( + new TextRangeList_t, pAttr )); + } + } + } + break; + case RES_TXTATR_AUTOFMT: + case RES_TXTATR_INETFMT: + case RES_TXTATR_CHARFMT: + break; // these are handled as properties of a "Text" portion + default: + OSL_FAIL("unknown attribute"); + break; + } + } + nStartIndex++; + } + + if (xRef.is()) // implies that we have moved the cursor + { + o_rbCursorMoved = true; + } + if (!o_rbCursorMoved) + { + // search for attribute changes behind the current cursor position + // break up at frames, bookmarks, redlines + + nStartIndex = 0; + nNextStart = 0; + while(nStartIndex < pHints->Count() && + nCurrentIndex >= (nNextStart = pHints->Get(nStartIndex)->GetStart())) + nStartIndex++; + + nEndIndex = 0; + nNextEnd = 0; + while(nEndIndex < pHints->Count() && + nCurrentIndex >= (nNextEnd = pHints->GetSortedByEnd(nEndIndex)->GetAnyEnd())) + nEndIndex++; + + sal_Int32 nNextPos = + ((nNextStart > nCurrentIndex) && (nNextStart < nNextEnd)) + ? nNextStart : nNextEnd; + if (nNextPos > nCurrentIndex) + { + o_rNextAttrPosition = nNextPos; + } + } + return xRef; +} + +static void lcl_MoveCursor( SwUnoCursor * const pUnoCursor, + const sal_Int32 nCurrentIndex, + const sal_Int32 nNextFrameIndex, + const sal_Int32 nNextPortionIndex, + const sal_Int32 nNextAttrIndex, + const sal_Int32 nNextMarkIndex, + const sal_Int32 nEndPos ) +{ + sal_Int32 nMovePos = pUnoCursor->GetContentNode()->Len(); + + if ((nEndPos >= 0) && (nEndPos < nMovePos)) + { + nMovePos = nEndPos; + } + + if ((nNextFrameIndex >= 0) && (nNextFrameIndex < nMovePos)) + { + nMovePos = nNextFrameIndex; + } + + if ((nNextPortionIndex >= 0) && (nNextPortionIndex < nMovePos)) + { + nMovePos = nNextPortionIndex; + } + + if ((nNextAttrIndex >= 0) && (nNextAttrIndex < nMovePos)) + { + nMovePos = nNextAttrIndex; + } + + if ((nNextMarkIndex >= 0) && (nNextMarkIndex < nMovePos)) + { + nMovePos = nNextMarkIndex; + } + + if (nMovePos > nCurrentIndex) + { + pUnoCursor->GetPoint()->nContent = nMovePos; + } +} + +static void lcl_FillRedlineArray( + SwDoc const & rDoc, + SwUnoCursor const & rUnoCursor, + SwXRedlinePortion_ImplList& rRedArr ) +{ + const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + const size_t nRedTableCount = rRedTable.size(); + + if ( nRedTableCount > 0 ) + { + const SwPosition* pStart = rUnoCursor.GetPoint(); + const SwNodeIndex nOwnNode = pStart->nNode; + + for(size_t nRed = 0; nRed < nRedTableCount; ++nRed) + { + const SwRangeRedline* pRedline = rRedTable[nRed]; + const SwPosition* pRedStart = pRedline->Start(); + const SwNodeIndex nRedNode = pRedStart->nNode; + if ( nOwnNode == nRedNode ) + rRedArr.insert( std::make_shared<SwXRedlinePortion_Impl>( + pRedline, true ) ); + if( pRedline->HasMark() && pRedline->End()->nNode == nOwnNode ) + rRedArr.insert( std::make_shared<SwXRedlinePortion_Impl>( + pRedline, false ) ); + } + } +} + +static void lcl_FillSoftPageBreakArray( + SwUnoCursor const & rUnoCursor, + SwSoftPageBreakList& rBreakArr ) +{ + const SwTextNode *pTextNode = + rUnoCursor.GetPoint()->nNode.GetNode().GetTextNode(); + if( pTextNode ) + pTextNode->fillSoftPageBreakList( rBreakArr ); +} + +static void lcl_ExportRedline( + TextRangeList_t & rPortions, + Reference<XText> const& xParent, + const SwUnoCursor * const pUnoCursor, + SwXRedlinePortion_ImplList& rRedlineArr, + const sal_Int32 nIndex) +{ + + // We want this loop to iterate over all red lines in this + // array. We will only insert the ones with index matches + for ( SwXRedlinePortion_ImplList::iterator aIter = rRedlineArr.begin(), aEnd = rRedlineArr.end(); + aIter != aEnd; ) + { + SwXRedlinePortion_ImplSharedPtr pPtr = *aIter; + sal_Int32 nRealIndex = pPtr->getRealIndex(); + // If there are elements before nIndex, remove them + if ( nIndex > nRealIndex ) + aIter = rRedlineArr.erase(aIter); + // If the elements match, and them to the list + else if ( nIndex == nRealIndex ) + { + rPortions.push_back( new SwXRedlinePortion( + *pPtr->m_pRedline, pUnoCursor, xParent, pPtr->m_bStart)); + aIter = rRedlineArr.erase(aIter); + } + // If we've iterated past nIndex, exit the loop + else + break; + } +} + +static void lcl_ExportBkmAndRedline( + TextRangeList_t & rPortions, + Reference<XText> const & xParent, + const SwUnoCursor * const pUnoCursor, + SwXBookmarkPortion_ImplList& rBkmArr, + SwXRedlinePortion_ImplList& rRedlineArr, + SwSoftPageBreakList& rBreakArr, + const sal_Int32 nIndex, + const std::set<sal_Int32>& rFramePositions, + bool bOnlyFrameBookmarkStarts) +{ + if (!rBkmArr.empty()) + lcl_ExportBookmark(rPortions, xParent, pUnoCursor, rBkmArr, nIndex, rFramePositions, + bOnlyFrameBookmarkStarts); + + if (bOnlyFrameBookmarkStarts) + // Only exporting the start of some collapsed bookmarks: no export of + // other arrays. + return; + + if (!rRedlineArr.empty()) + lcl_ExportRedline(rPortions, xParent, pUnoCursor, rRedlineArr, nIndex); + + if (!rBreakArr.empty()) + lcl_ExportSoftPageBreak(rPortions, xParent, pUnoCursor, rBreakArr, nIndex); +} + +/** + * Exports all start annotation marks from rAnnotationStartArr into rPortions that have the same + * start position as nIndex. + * + * @param rAnnotationStartArr the array of annotation marks. Consumed entries are removed. + * + * @param rFramePositions the list of positions where there is an at-char anchored frame. + * + * @param bOnlyFrame If true: export only the start of annotation marks which cover an at-char + * anchored frame. If false: export everything else. + */ +static void lcl_ExportAnnotationStarts( + TextRangeList_t & rPortions, + Reference<XText> const & xParent, + const SwUnoCursor * const pUnoCursor, + SwAnnotationStartPortion_ImplList& rAnnotationStartArr, + const sal_Int32 nIndex, + const std::set<sal_Int32>& rFramePositions, + bool bOnlyFrame) +{ + for ( SwAnnotationStartPortion_ImplList::iterator aIter = rAnnotationStartArr.begin(), aEnd = rAnnotationStartArr.end(); + aIter != aEnd; ) + { + SwAnnotationStartPortion_ImplSharedPtr pPtr = *aIter; + if ( nIndex > pPtr->getIndex() ) + { + aIter = rAnnotationStartArr.erase(aIter); + continue; + } + if ( pPtr->getIndex() > nIndex ) + { + break; + } + + bool bFrameStart = rFramePositions.find(nIndex) != rFramePositions.end(); + if (bFrameStart || !bOnlyFrame) + { + SwXTextPortion* pPortion = + new SwXTextPortion( pUnoCursor, xParent, PORTION_ANNOTATION ); + pPortion->SetTextField( pPtr->mxAnnotationField ); + rPortions.emplace_back(pPortion); + + aIter = rAnnotationStartArr.erase(aIter); + } + else + ++aIter; + } +} + +/// Fills character positions from rFrames into rFramePositions. +static void lcl_ExtractFramePositions(FrameClientSortList_t& rFrames, sal_Int32 nCurrentIndex, + std::set<sal_Int32>& rFramePositions) +{ + for (const auto& rFrame : rFrames) + { + if (rFrame.nIndex < nCurrentIndex) + continue; + + if (rFrame.nIndex > nCurrentIndex) + break; + + const SwModify* pFrame = rFrame.pFrameClient->GetRegisteredIn(); + if (!pFrame) + continue; + + auto& rFormat = *static_cast<SwFrameFormat*>(const_cast<SwModify*>(pFrame)); + const SwFormatAnchor& rAnchor = rFormat.GetAnchor(); + const SwPosition* pPosition = rAnchor.GetContentAnchor(); + if (!pPosition) + continue; + + rFramePositions.insert(pPosition->nContent.GetIndex()); + } +} + +/** + * Exports at-char anchored frames. + * + * @param i_rFrames the frames for this paragraph, frames at <= i_nCurrentIndex + * are removed from the container. + */ +static sal_Int32 lcl_ExportFrames( + TextRangeList_t & rPortions, + Reference<XText> const & i_xParent, + SwUnoCursor const * const i_pUnoCursor, + FrameClientSortList_t & i_rFrames, + sal_Int32 const i_nCurrentIndex) +{ + // Ignore frames which are not exported, as we are exporting a selection + // and they are anchored before the start of the selection. + while (!i_rFrames.empty() && i_rFrames.front().nIndex < i_nCurrentIndex) + i_rFrames.pop_front(); + + // find first Frame in (sorted) i_rFrames at current position + while (!i_rFrames.empty() && (i_rFrames.front().nIndex == i_nCurrentIndex)) + // do not check for i_nEnd here; this is done implicitly by lcl_MoveCursor + { + const SwModify * const pFrame = + i_rFrames.front().pFrameClient->GetRegisteredIn(); + if (pFrame) // Frame could be disposed + { + SwXTextPortion* pPortion = new SwXTextPortion(i_pUnoCursor, i_xParent, + *static_cast<SwFrameFormat*>( const_cast<SwModify*>( pFrame ) ) ); + rPortions.emplace_back(pPortion); + } + i_rFrames.pop_front(); + } + + return !i_rFrames.empty() ? i_rFrames.front().nIndex : -1; +} + +static sal_Int32 lcl_GetNextIndex( + SwXBookmarkPortion_ImplList const & rBkmArr, + SwXRedlinePortion_ImplList const & rRedlineArr, + SwSoftPageBreakList const & rBreakArr ) +{ + sal_Int32 nRet = -1; + if(!rBkmArr.empty()) + { + SwXBookmarkPortion_ImplSharedPtr pPtr = *rBkmArr.begin(); + nRet = pPtr->getIndex(); + } + if(!rRedlineArr.empty()) + { + SwXRedlinePortion_ImplSharedPtr pPtr = *rRedlineArr.begin(); + sal_Int32 nTmp = pPtr->getRealIndex(); + if(nRet < 0 || nTmp < nRet) + nRet = nTmp; + } + if(!rBreakArr.empty()) + { + if(nRet < 0 || *rBreakArr.begin() < nRet) + nRet = *rBreakArr.begin(); + } + return nRet; +}; + +static void lcl_CreatePortions( + TextRangeList_t & i_rPortions, + uno::Reference< text::XText > const & i_xParentText, + SwUnoCursor * const pUnoCursor, + FrameClientSortList_t & i_rFrames, + const sal_Int32 i_nStartPos, + const sal_Int32 i_nEndPos ) +{ + if (!pUnoCursor) + return; + + // set the start if a selection should be exported + if ((i_nStartPos > 0) && + (pUnoCursor->Start()->nContent.GetIndex() != i_nStartPos)) + { + pUnoCursor->DeleteMark(); + OSL_ENSURE(pUnoCursor->Start()->nNode.GetNode().GetTextNode() && + (i_nStartPos <= pUnoCursor->Start()->nNode.GetNode().GetTextNode()-> + GetText().getLength()), "Incorrect start position" ); + // ??? should this be i_nStartPos - current position ? + pUnoCursor->Right(i_nStartPos); + } + + SwDoc * const pDoc = pUnoCursor->GetDoc(); + + std::deque<sal_Int32> FieldMarks; + lcl_FillFieldMarkArray(FieldMarks, *pUnoCursor, i_nStartPos); + + SwXBookmarkPortion_ImplList Bookmarks; + lcl_FillBookmarkArray(*pDoc, *pUnoCursor, Bookmarks); + + SwXRedlinePortion_ImplList Redlines; + lcl_FillRedlineArray(*pDoc, *pUnoCursor, Redlines); + + SwSoftPageBreakList SoftPageBreaks; + lcl_FillSoftPageBreakArray(*pUnoCursor, SoftPageBreaks); + + SwAnnotationStartPortion_ImplList AnnotationStarts; + lcl_FillAnnotationStartArray( *pDoc, *pUnoCursor, AnnotationStarts ); + + PortionStack_t PortionStack; + PortionStack.push( PortionList_t(&i_rPortions, nullptr) ); + + bool bAtEnd( false ); + while (!bAtEnd) // every iteration consumes at least current character! + { + if (pUnoCursor->HasMark()) + { + pUnoCursor->Normalize(false); + pUnoCursor->DeleteMark(); + } + + SwTextNode * const pTextNode = pUnoCursor->GetNode().GetTextNode(); + if (!pTextNode) + { + OSL_FAIL("lcl_CreatePortions: no TextNode - what now ?"); + return; + } + + SwpHints * const pHints = pTextNode->GetpSwpHints(); + const sal_Int32 nCurrentIndex = + pUnoCursor->GetPoint()->nContent.GetIndex(); + // this contains the portion which consumes the character in the + // text at nCurrentIndex; i.e. it must be set _once_ per iteration + uno::Reference< XTextRange > xRef; + + SwUnoCursorHelper::SelectPam(*pUnoCursor, true); // set mark + + // First remember the frame positions. + std::set<sal_Int32> aFramePositions; + lcl_ExtractFramePositions(i_rFrames, nCurrentIndex, aFramePositions); + + // Then export start of collapsed bookmarks which "cover" at-char + // anchored frames. + lcl_ExportBkmAndRedline( *PortionStack.top().first, i_xParentText, + pUnoCursor, Bookmarks, Redlines, SoftPageBreaks, nCurrentIndex, aFramePositions, /*bOnlyFrameBookmarkStarts=*/true ); + + lcl_ExportAnnotationStarts( + *PortionStack.top().first, + i_xParentText, + pUnoCursor, + AnnotationStarts, + nCurrentIndex, + aFramePositions, + /*bOnlyFrame=*/true ); + + const sal_Int32 nFirstFrameIndex = + lcl_ExportFrames( *PortionStack.top().first, + i_xParentText, pUnoCursor, i_rFrames, nCurrentIndex); + + // Export ends of the previously started collapsed bookmarks + all + // other bookmarks, redlines, etc. + lcl_ExportBkmAndRedline( *PortionStack.top().first, i_xParentText, + pUnoCursor, Bookmarks, Redlines, SoftPageBreaks, nCurrentIndex, aFramePositions, /*bOnlyFrameBookmarkStarts=*/false ); + + lcl_ExportAnnotationStarts( + *PortionStack.top().first, + i_xParentText, + pUnoCursor, + AnnotationStarts, + nCurrentIndex, + aFramePositions, + /*bOnlyFrame=*/false ); + + bool bCursorMoved( false ); + sal_Int32 nNextAttrIndex = -1; + // #111716# the cursor must not move right at the + // end position of a selection! + bAtEnd = ((i_nEndPos >= 0) && (nCurrentIndex >= i_nEndPos)) + || (nCurrentIndex >= pTextNode->Len()); + if (pHints) + { + // N.B.: side-effects nNextAttrIndex, bCursorMoved; may move cursor + xRef = lcl_ExportHints(PortionStack, i_xParentText, pUnoCursor, + pHints, i_nStartPos, i_nEndPos, nCurrentIndex, bAtEnd, + bCursorMoved, nNextAttrIndex); + if (PortionStack.empty()) + { + OSL_FAIL("CreatePortions: stack underflow"); + return; + } + } + + if (!xRef.is() && !bCursorMoved) + { + if (!bAtEnd && + !FieldMarks.empty() && (FieldMarks.front() == nCurrentIndex)) + { + // moves cursor + xRef = lcl_ExportFieldMark(i_xParentText, pUnoCursor, pTextNode); + FieldMarks.pop_front(); + } + } + else + { + OSL_ENSURE(FieldMarks.empty() || + (FieldMarks.front() != nCurrentIndex), + "fieldmark and hint with CH_TXTATR at same pos?"); + } + + if (!bAtEnd && !xRef.is() && !bCursorMoved) + { + const sal_Int32 nNextPortionIndex = + lcl_GetNextIndex(Bookmarks, Redlines, SoftPageBreaks); + + sal_Int32 nNextMarkIndex = ( !FieldMarks.empty() ? FieldMarks.front() : -1 ); + if ( !AnnotationStarts.empty() + && ( nNextMarkIndex == -1 + || (*AnnotationStarts.begin())->getIndex() < nNextMarkIndex ) ) + { + nNextMarkIndex = (*AnnotationStarts.begin())->getIndex(); + } + + lcl_MoveCursor( + pUnoCursor, + nCurrentIndex, + nFirstFrameIndex, + nNextPortionIndex, + nNextAttrIndex, + nNextMarkIndex, + i_nEndPos ); + + xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT); + } + else if (bAtEnd && !xRef.is() && !pTextNode->Len()) + { + // special case: for an empty paragraph, we better put out a + // text portion because there may be a hyperlink attribute + xRef = new SwXTextPortion(pUnoCursor, i_xParentText, PORTION_TEXT); + } + + if (xRef.is()) + { + PortionStack.top().first->push_back(xRef); + } + } + + OSL_ENSURE((PortionStack.size() == 1) && !PortionStack.top().second, + "CreatePortions: stack error" ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoredline.cxx b/sw/source/core/unocore/unoredline.cxx new file mode 100644 index 000000000..615b1b04c --- /dev/null +++ b/sw/source/core/unocore/unoredline.cxx @@ -0,0 +1,587 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> +#include <com/sun/star/text/XTextSection.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <vcl/svapp.hxx> + +#include <pagedesc.hxx> +#include <poolfmt.hxx> +#include <redline.hxx> +#include <section.hxx> +#include <unoprnms.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unoparagraph.hxx> +#include <unocoll.hxx> +#include <unomap.hxx> +#include <unocrsr.hxx> +#include <unoport.hxx> +#include <unoredline.hxx> +#include <doc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docary.hxx> + +using namespace ::com::sun::star; + +SwXRedlineText::SwXRedlineText(SwDoc* _pDoc, const SwNodeIndex& aIndex) : + SwXText(_pDoc, CursorType::Redline), + aNodeIndex(aIndex) +{ +} + +const SwStartNode* SwXRedlineText::GetStartNode() const +{ + return aNodeIndex.GetNode().GetStartNode(); +} + +uno::Any SwXRedlineText::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet; + + if (cppu::UnoType<container::XEnumerationAccess>::get()== rType) + { + uno::Reference<container::XEnumerationAccess> aAccess = this; + aRet <<= aAccess; + } + else + { + // delegate to SwXText and OWeakObject + aRet = SwXText::queryInterface(rType); + if(!aRet.hasValue()) + { + aRet = OWeakObject::queryInterface(rType); + } + } + + return aRet; +} + +uno::Sequence<uno::Type> SwXRedlineText::getTypes() +{ + return cppu::OTypeCollection( + cppu::UnoType<container::XEnumerationAccess>::get(), + SwXText::getTypes() + ).getTypes(); +} + +uno::Sequence<sal_Int8> SwXRedlineText::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Reference<text::XTextCursor> SwXRedlineText::createTextCursor() +{ + SolarMutexGuard aGuard; + + SwPosition aPos(aNodeIndex); + SwXTextCursor *const pXCursor = + new SwXTextCursor(*GetDoc(), this, CursorType::Redline, aPos); + auto& rUnoCursor(pXCursor->GetCursor()); + rUnoCursor.Move(fnMoveForward, GoInNode); + + // #101929# prevent a newly created text cursor from running inside a table + // because table cells have their own XText. + // Patterned after SwXTextFrame::createTextCursor(). + + // skip all tables at the beginning + SwTableNode* pTableNode = rUnoCursor.GetNode().FindTableNode(); + SwContentNode* pContentNode = nullptr; + bool bTable = pTableNode != nullptr; + while( pTableNode != nullptr ) + { + rUnoCursor.GetPoint()->nNode = *(pTableNode->EndOfSectionNode()); + pContentNode = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode); + pTableNode = pContentNode->FindTableNode(); + } + if( pContentNode != nullptr ) + rUnoCursor.GetPoint()->nContent.Assign( pContentNode, 0 ); + if( bTable && rUnoCursor.GetNode().FindSttNodeByType( SwNormalStartNode ) + != GetStartNode() ) + { + // We have gone too far and have left our own redline. This means that + // no content node outside of a table could be found, and therefore we + // except. + uno::RuntimeException aExcept; + aExcept.Message = + "No content node found that is inside this change section " + "but outside of a table"; + throw aExcept; + } + + return static_cast<text::XWordCursor*>(pXCursor); +} + +uno::Reference<text::XTextCursor> SwXRedlineText::createTextCursorByRange( + const uno::Reference<text::XTextRange> & aTextRange) +{ + uno::Reference<text::XTextCursor> xCursor = createTextCursor(); + xCursor->gotoRange(aTextRange->getStart(), false); + xCursor->gotoRange(aTextRange->getEnd(), true); + return xCursor; +} + +uno::Reference<container::XEnumeration> SwXRedlineText::createEnumeration() +{ + SolarMutexGuard aGuard; + SwPaM aPam(aNodeIndex); + aPam.Move(fnMoveForward, GoInNode); + auto pUnoCursor(GetDoc()->CreateUnoCursor(*aPam.Start())); + return SwXParagraphEnumeration::Create(this, pUnoCursor, CursorType::Redline); +} + +uno::Type SwXRedlineText::getElementType( ) +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SwXRedlineText::hasElements( ) +{ + return true; // we always have a content index +} + +SwXRedlinePortion::SwXRedlinePortion(SwRangeRedline const& rRedline, + SwUnoCursor const*const pPortionCursor, + uno::Reference< text::XText > const& xParent, bool const bStart) + : SwXTextPortion(pPortionCursor, xParent, + bStart ? PORTION_REDLINE_START : PORTION_REDLINE_END) + , m_rRedline(rRedline) +{ + SetCollapsed(!m_rRedline.HasMark()); +} + +SwXRedlinePortion::~SwXRedlinePortion() +{ +} + +static uno::Sequence<beans::PropertyValue> lcl_GetSuccessorProperties(const SwRangeRedline& rRedline) +{ + uno::Sequence<beans::PropertyValue> aValues(4); + + const SwRedlineData* pNext = rRedline.GetRedlineData().Next(); + if(pNext) + { + beans::PropertyValue* pValues = aValues.getArray(); + pValues[0].Name = UNO_NAME_REDLINE_AUTHOR; + // GetAuthorString(n) walks the SwRedlineData* chain; + // here we always need element 1 + pValues[0].Value <<= rRedline.GetAuthorString(1); + pValues[1].Name = UNO_NAME_REDLINE_DATE_TIME; + pValues[1].Value <<= pNext->GetTimeStamp().GetUNODateTime(); + pValues[2].Name = UNO_NAME_REDLINE_COMMENT; + pValues[2].Value <<= pNext->GetComment(); + pValues[3].Name = UNO_NAME_REDLINE_TYPE; + pValues[3].Value <<= SwRedlineTypeToOUString(pNext->GetType()); + } + return aValues; +} + +uno::Any SwXRedlinePortion::getPropertyValue( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + Validate(); + uno::Any aRet; + if(rPropertyName == UNO_NAME_REDLINE_TEXT) + { + SwNodeIndex* pNodeIdx = m_rRedline.GetContentIdx(); + if(pNodeIdx ) + { + if ( 1 < ( pNodeIdx->GetNode().EndOfSectionIndex() - pNodeIdx->GetNode().GetIndex() ) ) + { + SwUnoCursor& rUnoCursor = GetCursor(); + uno::Reference<text::XText> xRet = new SwXRedlineText(rUnoCursor.GetDoc(), *pNodeIdx); + aRet <<= xRet; + } + else { + OSL_FAIL("Empty section in redline portion! (end node immediately follows start node)"); + } + } + } + else + { + aRet = GetPropertyValue(rPropertyName, m_rRedline); + if(!aRet.hasValue() && + rPropertyName != UNO_NAME_REDLINE_SUCCESSOR_DATA) + aRet = SwXTextPortion::getPropertyValue(rPropertyName); + } + return aRet; +} + +void SwXRedlinePortion::Validate() +{ + SwUnoCursor& rUnoCursor = GetCursor(); + //search for the redline + SwDoc* pDoc = rUnoCursor.GetDoc(); + const SwRedlineTable& rRedTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + bool bFound = false; + for(size_t nRed = 0; nRed < rRedTable.size() && !bFound; nRed++) + bFound = &m_rRedline == rRedTable[nRed]; + if(!bFound) + throw uno::RuntimeException(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwXRedlinePortion::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Any SwXRedlinePortion::GetPropertyValue( const OUString& rPropertyName, const SwRangeRedline& rRedline ) +{ + uno::Any aRet; + if(rPropertyName == UNO_NAME_REDLINE_AUTHOR) + aRet <<= rRedline.GetAuthorString(); + else if(rPropertyName == UNO_NAME_REDLINE_DATE_TIME) + { + aRet <<= rRedline.GetTimeStamp().GetUNODateTime(); + } + else if(rPropertyName == UNO_NAME_REDLINE_COMMENT) + aRet <<= rRedline.GetComment(); + else if(rPropertyName == UNO_NAME_REDLINE_DESCRIPTION) + aRet <<= const_cast<SwRangeRedline&>(rRedline).GetDescr(); + else if(rPropertyName == UNO_NAME_REDLINE_TYPE) + { + aRet <<= SwRedlineTypeToOUString(rRedline.GetType()); + } + else if(rPropertyName == UNO_NAME_REDLINE_SUCCESSOR_DATA) + { + if(rRedline.GetRedlineData().Next()) + aRet <<= lcl_GetSuccessorProperties(rRedline); + } + else if (rPropertyName == UNO_NAME_REDLINE_IDENTIFIER) + { + aRet <<= OUString::number( + sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(&rRedline) ) ); + } + else if (rPropertyName == UNO_NAME_IS_IN_HEADER_FOOTER) + { + aRet <<= rRedline.GetDoc()->IsInHeaderFooter( rRedline.GetPoint()->nNode ); + } + else if (rPropertyName == UNO_NAME_MERGE_LAST_PARA) + { + aRet <<= !rRedline.IsDelLastPara(); + } + return aRet; +} + +uno::Sequence< beans::PropertyValue > SwXRedlinePortion::CreateRedlineProperties( + const SwRangeRedline& rRedline, bool bIsStart ) +{ + uno::Sequence< beans::PropertyValue > aRet(12); + const SwRedlineData* pNext = rRedline.GetRedlineData().Next(); + beans::PropertyValue* pRet = aRet.getArray(); + + sal_Int32 nPropIdx = 0; + pRet[nPropIdx].Name = UNO_NAME_REDLINE_AUTHOR; + pRet[nPropIdx++].Value <<= rRedline.GetAuthorString(); + pRet[nPropIdx].Name = UNO_NAME_REDLINE_DATE_TIME; + pRet[nPropIdx++].Value <<= rRedline.GetTimeStamp().GetUNODateTime(); + pRet[nPropIdx].Name = UNO_NAME_REDLINE_COMMENT; + pRet[nPropIdx++].Value <<= rRedline.GetComment(); + pRet[nPropIdx].Name = UNO_NAME_REDLINE_DESCRIPTION; + pRet[nPropIdx++].Value <<= const_cast<SwRangeRedline&>(rRedline).GetDescr(); + pRet[nPropIdx].Name = UNO_NAME_REDLINE_TYPE; + pRet[nPropIdx++].Value <<= SwRedlineTypeToOUString(rRedline.GetType()); + pRet[nPropIdx].Name = UNO_NAME_REDLINE_IDENTIFIER; + pRet[nPropIdx++].Value <<= OUString::number( + sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(&rRedline) ) ); + pRet[nPropIdx].Name = UNO_NAME_IS_COLLAPSED; + pRet[nPropIdx++].Value <<= !rRedline.HasMark(); + + pRet[nPropIdx].Name = UNO_NAME_IS_START; + pRet[nPropIdx++].Value <<= bIsStart; + + pRet[nPropIdx].Name = UNO_NAME_MERGE_LAST_PARA; + pRet[nPropIdx++].Value <<= !rRedline.IsDelLastPara(); + + SwNodeIndex* pNodeIdx = rRedline.GetContentIdx(); + if(pNodeIdx ) + { + if ( 1 < ( pNodeIdx->GetNode().EndOfSectionIndex() - pNodeIdx->GetNode().GetIndex() ) ) + { + uno::Reference<text::XText> xRet = new SwXRedlineText(rRedline.GetDoc(), *pNodeIdx); + pRet[nPropIdx].Name = UNO_NAME_REDLINE_TEXT; + pRet[nPropIdx++].Value <<= xRet; + } + else { + OSL_FAIL("Empty section in redline portion! (end node immediately follows start node)"); + } + } + if(pNext) + { + pRet[nPropIdx].Name = UNO_NAME_REDLINE_SUCCESSOR_DATA; + pRet[nPropIdx++].Value <<= lcl_GetSuccessorProperties(rRedline); + } + aRet.realloc(nPropIdx); + return aRet; +} + +SwXRedline::SwXRedline(SwRangeRedline& rRedline, SwDoc& rDoc) : + SwXText(&rDoc, CursorType::Redline), + pDoc(&rDoc), + pRedline(&rRedline) +{ + StartListening(pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +} + +SwXRedline::~SwXRedline() +{ +} + +uno::Reference< beans::XPropertySetInfo > SwXRedline::getPropertySetInfo( ) +{ + static uno::Reference< beans::XPropertySetInfo > xRef = + aSwMapProvider.GetPropertySet(PROPERTY_MAP_REDLINE)->getPropertySetInfo(); + return xRef; +} + +void SwXRedline::setPropertyValue( const OUString& rPropertyName, const uno::Any& aValue ) +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + if(rPropertyName == UNO_NAME_REDLINE_AUTHOR) + { + OSL_FAIL("currently not available"); + } + else if(rPropertyName == UNO_NAME_REDLINE_DATE_TIME) + { + OSL_FAIL("currently not available"); + } + else if(rPropertyName == UNO_NAME_REDLINE_COMMENT) + { + OUString sTmp; aValue >>= sTmp; + pRedline->SetComment(sTmp); + } + else if(rPropertyName == UNO_NAME_REDLINE_DESCRIPTION) + { + SAL_WARN("sw.uno", "SwXRedline::setPropertyValue: can't set Description"); + } + else if(rPropertyName == UNO_NAME_REDLINE_TYPE) + { + OSL_FAIL("currently not available"); + OUString sTmp; aValue >>= sTmp; + if(sTmp.isEmpty()) + throw lang::IllegalArgumentException(); + } + else if(rPropertyName == UNO_NAME_REDLINE_SUCCESSOR_DATA) + { + OSL_FAIL("currently not available"); + } + else + { + throw lang::IllegalArgumentException(); + } +} + +uno::Any SwXRedline::getPropertyValue( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + uno::Any aRet; + bool bStart = rPropertyName == UNO_NAME_REDLINE_START; + if(bStart || + rPropertyName == UNO_NAME_REDLINE_END) + { + uno::Reference<XInterface> xRet; + SwNode* pNode = &pRedline->GetNode(); + if(!bStart && pRedline->HasMark()) + pNode = &pRedline->GetNode(false); + switch(pNode->GetNodeType()) + { + case SwNodeType::Section: + { + SwSectionNode* pSectNode = pNode->GetSectionNode(); + OSL_ENSURE(pSectNode, "No section node!"); + xRet = SwXTextSections::GetObject( *pSectNode->GetSection().GetFormat() ); + } + break; + case SwNodeType::Table : + { + SwTableNode* pTableNode = pNode->GetTableNode(); + OSL_ENSURE(pTableNode, "No table node!"); + SwTable& rTable = pTableNode->GetTable(); + SwFrameFormat* pTableFormat = rTable.GetFrameFormat(); + xRet = SwXTextTables::GetObject( *pTableFormat ); + } + break; + case SwNodeType::Text : + { + SwPosition* pPoint = nullptr; + if(bStart || !pRedline->HasMark()) + pPoint = pRedline->GetPoint(); + else + pPoint = pRedline->GetMark(); + const uno::Reference<text::XTextRange> xRange = + SwXTextRange::CreateXTextRange(*pDoc, *pPoint, nullptr); + xRet = xRange.get(); + } + break; + default: + OSL_FAIL("illegal node type"); + } + aRet <<= xRet; + } + else if(rPropertyName == UNO_NAME_REDLINE_TEXT) + { + SwNodeIndex* pNodeIdx = pRedline->GetContentIdx(); + if( pNodeIdx ) + { + if ( 1 < ( pNodeIdx->GetNode().EndOfSectionIndex() - pNodeIdx->GetNode().GetIndex() ) ) + { + uno::Reference<text::XText> xRet = new SwXRedlineText(pDoc, *pNodeIdx); + aRet <<= xRet; + } + else { + OSL_FAIL("Empty section in redline portion! (end node immediately follows start node)"); + } + } + } + else + aRet = SwXRedlinePortion::GetPropertyValue(rPropertyName, *pRedline); + return aRet; +} + +void SwXRedline::addPropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ +} + +void SwXRedline::removePropertyChangeListener( + const OUString& /*aPropertyName*/, const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ +} + +void SwXRedline::addVetoableChangeListener( + const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + +void SwXRedline::removeVetoableChangeListener( + const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + +void SwXRedline::Notify( const SfxHint& rHint ) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + pDoc = nullptr; + pRedline = nullptr; + } else if(auto pHint = dynamic_cast<const sw::FindRedlineHint*>(&rHint)) { + if(!*pHint->m_ppXRedline && &pHint->m_rRedline == GetRedline()) + *pHint->m_ppXRedline = this; + } +} + +uno::Reference< container::XEnumeration > SwXRedline::createEnumeration() +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + + SwNodeIndex* pNodeIndex = pRedline->GetContentIdx(); + if(!pNodeIndex) + return nullptr; + SwPaM aPam(*pNodeIndex); + aPam.Move(fnMoveForward, GoInNode); + auto pUnoCursor(GetDoc()->CreateUnoCursor(*aPam.Start())); + return SwXParagraphEnumeration::Create(this, pUnoCursor, CursorType::Redline); +} + +uno::Type SwXRedline::getElementType( ) +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SwXRedline::hasElements( ) +{ + if(!pDoc) + throw uno::RuntimeException(); + return nullptr != pRedline->GetContentIdx(); +} + +uno::Reference< text::XTextCursor > SwXRedline::createTextCursor() +{ + SolarMutexGuard aGuard; + if(!pDoc) + throw uno::RuntimeException(); + + uno::Reference< text::XTextCursor > xRet; + SwNodeIndex* pNodeIndex = pRedline->GetContentIdx(); + if(!pNodeIndex) + { + throw uno::RuntimeException(); + } + + SwPosition aPos(*pNodeIndex); + SwXTextCursor *const pXCursor = + new SwXTextCursor(*pDoc, this, CursorType::Redline, aPos); + auto& rUnoCursor(pXCursor->GetCursor()); + rUnoCursor.Move(fnMoveForward, GoInNode); + + // is here a table? + SwTableNode* pTableNode = rUnoCursor.GetNode().FindTableNode(); + SwContentNode* pCont = nullptr; + while( pTableNode ) + { + rUnoCursor.GetPoint()->nNode = *pTableNode->EndOfSectionNode(); + pCont = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode); + pTableNode = pCont->FindTableNode(); + } + if(pCont) + rUnoCursor.GetPoint()->nContent.Assign(pCont, 0); + xRet = static_cast<text::XWordCursor*>(pXCursor); + + return xRet; +} + +uno::Reference< text::XTextCursor > SwXRedline::createTextCursorByRange( + const uno::Reference< text::XTextRange > & /*aTextPosition*/) +{ + throw uno::RuntimeException(); +} + +uno::Any SwXRedline::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet = SwXText::queryInterface(rType); + if(!aRet.hasValue()) + { + aRet = SwXRedlineBaseClass::queryInterface(rType); + } + return aRet; +} + +uno::Sequence<uno::Type> SwXRedline::getTypes() +{ + return comphelper::concatSequences( + SwXText::getTypes(), + SwXRedlineBaseClass::getTypes() + ); +} + +uno::Sequence<sal_Int8> SwXRedline::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unoredlines.cxx b/sw/source/core/unocore/unoredlines.cxx new file mode 100644 index 000000000..bbab06ed6 --- /dev/null +++ b/sw/source/core/unocore/unoredlines.cxx @@ -0,0 +1,168 @@ +/* -*- 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 <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/supportsservice.hxx> + +#include <vcl/svapp.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <unoredlines.hxx> +#include <unoredline.hxx> +#include <pagedesc.hxx> +#include <poolfmt.hxx> +#include <doc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docary.hxx> +#include <redline.hxx> + +using namespace ::com::sun::star; + +SwXRedlines::SwXRedlines(SwDoc* _pDoc) : + SwUnoCollection(_pDoc) +{ +} + +SwXRedlines::~SwXRedlines() +{ +} + +sal_Int32 SwXRedlines::getCount( ) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const SwRedlineTable& rRedTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + return rRedTable.size(); +} + +uno::Any SwXRedlines::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const SwRedlineTable& rRedTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + if ((nIndex < 0) || (rRedTable.size() <= o3tl::make_unsigned(nIndex))) + throw lang::IndexOutOfBoundsException(); + + uno::Reference <beans::XPropertySet> xRet = SwXRedlines::GetObject( *rRedTable[nIndex], *GetDoc() ); + return uno::Any(xRet); +} + +uno::Reference< container::XEnumeration > SwXRedlines::createEnumeration() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + return uno::Reference< container::XEnumeration >(new SwXRedlineEnumeration(*GetDoc())); +} + +uno::Type SwXRedlines::getElementType( ) +{ + return cppu::UnoType<beans::XPropertySet>::get(); +} + +sal_Bool SwXRedlines::hasElements( ) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + const SwRedlineTable& rRedTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + return !rRedTable.empty(); +} + +OUString SwXRedlines::getImplementationName() +{ + return "SwXRedlines"; +} + +sal_Bool SwXRedlines::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SwXRedlines::getSupportedServiceNames() +{ + OSL_FAIL("not implemented"); + return uno::Sequence< OUString >(); +} + +beans::XPropertySet* SwXRedlines::GetObject( SwRangeRedline& rRedline, SwDoc& rDoc ) +{ + SwXRedline* pXRedline(nullptr); + sw::FindRedlineHint aHint(rRedline, &pXRedline); + rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier().Broadcast(aHint); + return pXRedline ? pXRedline : new SwXRedline(rRedline, rDoc); +} + +SwXRedlineEnumeration::SwXRedlineEnumeration(SwDoc& rDoc) : + pDoc(&rDoc), + nCurrentIndex(0) +{ + StartListening(pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +} + +SwXRedlineEnumeration::~SwXRedlineEnumeration() +{ +} + +sal_Bool SwXRedlineEnumeration::hasMoreElements() +{ + if(!pDoc) + throw uno::RuntimeException(); + return pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() > nCurrentIndex; +} + +uno::Any SwXRedlineEnumeration::nextElement() +{ + if(!pDoc) + throw uno::RuntimeException(); + const SwRedlineTable& rRedTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + if( rRedTable.size() <= nCurrentIndex ) + throw container::NoSuchElementException(); + uno::Reference <beans::XPropertySet> xRet = SwXRedlines::GetObject( *rRedTable[nCurrentIndex++], *pDoc ); + uno::Any aRet; + aRet <<= xRet; + return aRet; +} + +OUString SwXRedlineEnumeration::getImplementationName() +{ + return "SwXRedlineEnumeration"; +} + +sal_Bool SwXRedlineEnumeration::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SwXRedlineEnumeration::getSupportedServiceNames() +{ + return uno::Sequence< OUString >(); +} + +void SwXRedlineEnumeration::Notify( const SfxHint& rHint ) +{ + if(rHint.GetId() == SfxHintId::Dying) + pDoc = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unorefmk.cxx b/sw/source/core/unocore/unorefmk.cxx new file mode 100644 index 000000000..a69fd2cab --- /dev/null +++ b/sw/source/core/unocore/unorefmk.cxx @@ -0,0 +1,1522 @@ +/* -*- 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 <memory> +#include <utility> + +#include <comphelper/interfacecontainer2.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <sal/config.h> +#include <svl/listener.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +#include <unotextrange.hxx> +#include <unorefmark.hxx> +#include <unotextcursor.hxx> +#include <unomap.hxx> +#include <unocrsrhelper.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <fmtrfmrk.hxx> +#include <txtrfmrk.hxx> +#include <unometa.hxx> +#include <unotext.hxx> +#include <unoport.hxx> +#include <txtatr.hxx> +#include <fmtmeta.hxx> +#include <docsh.hxx> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/rdf/Statement.hpp> +#include <com/sun/star/rdf/URI.hpp> +#include <com/sun/star/rdf/URIs.hpp> +#include <com/sun/star/rdf/XLiteral.hpp> +#include <com/sun/star/rdf/XRepositorySupplier.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + +using namespace ::com::sun::star; + +class SwXReferenceMark::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + bool m_bIsDescriptor; + SwDoc* m_pDoc; + const SwFormatRefMark* m_pMarkFormat; + OUString m_sMarkName; + + Impl(SwDoc* const pDoc, SwFormatRefMark* const pRefMark) + : m_EventListeners(m_Mutex) + , m_bIsDescriptor(nullptr == pRefMark) + , m_pDoc(pDoc) + , m_pMarkFormat(pRefMark) + { + if (pRefMark) + { + StartListening(pRefMark->GetNotifier()); + m_sMarkName = pRefMark->GetRefName(); + } + } + + bool IsValid() const { return m_pMarkFormat; } + void InsertRefMark( SwPaM & rPam, SwXTextCursor const*const pCursor ); + void Invalidate(); +protected: + virtual void Notify(const SfxHint&) override; + +}; + +void SwXReferenceMark::Impl::Invalidate() +{ + EndListeningAll(); + m_pDoc = nullptr; + m_pMarkFormat = nullptr; + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); +} + +void SwXReferenceMark::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + Invalidate(); +} + +SwXReferenceMark::SwXReferenceMark( + SwDoc *const pDoc, SwFormatRefMark *const pRefMark) + : m_pImpl( new SwXReferenceMark::Impl(pDoc, pRefMark) ) +{ +} + +SwXReferenceMark::~SwXReferenceMark() +{ +} + +uno::Reference<text::XTextContent> +SwXReferenceMark::CreateXReferenceMark( + SwDoc & rDoc, SwFormatRefMark *const pMarkFormat) +{ + // i#105557: do not iterate over the registered clients: race condition + uno::Reference<text::XTextContent> xMark; + if (pMarkFormat) + { + xMark = pMarkFormat->GetXRefMark(); + } + if (!xMark.is()) + { + SwXReferenceMark *const pMark(new SwXReferenceMark(&rDoc, pMarkFormat)); + xMark.set(pMark); + if (pMarkFormat) + { + pMarkFormat->SetXRefMark(xMark); + } + // need a permanent Reference to initialize m_wThis + pMark->m_pImpl->m_wThis = xMark; + } + return xMark; +} + +namespace +{ + class theSwXReferenceMarkUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXReferenceMarkUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXReferenceMark::getUnoTunnelId() +{ + return theSwXReferenceMarkUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXReferenceMark::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXReferenceMark>(rId, this); +} + +OUString SAL_CALL SwXReferenceMark::getImplementationName() +{ + return "SwXReferenceMark"; +} + +sal_Bool SAL_CALL +SwXReferenceMark::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXReferenceMark::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextContent", + "com.sun.star.text.ReferenceMark" + }; +} + +namespace { + +template<typename T> struct NotContainedIn +{ + std::vector<T> const& m_rVector; + explicit NotContainedIn(std::vector<T> const& rVector) + : m_rVector(rVector) { } + bool operator() (T const& rT) { + return std::find(m_rVector.begin(), m_rVector.end(), rT) + == m_rVector.end(); + } +}; + +} + +void SwXReferenceMark::Impl::InsertRefMark(SwPaM& rPam, + SwXTextCursor const*const pCursor) +{ + //! in some cases when this function is called the pDoc pointer member may have become + //! invalid/deleted thus we obtain the document pointer from rPaM where it should always + //! be valid. + SwDoc *pDoc2 = rPam.GetDoc(); + + UnoActionContext aCont(pDoc2); + SwFormatRefMark aRefMark(m_sMarkName); + bool bMark = *rPam.GetPoint() != *rPam.GetMark(); + + const bool bForceExpandHints( !bMark && pCursor && pCursor->IsAtEndOfMeta() ); + const SetAttrMode nInsertFlags = bForceExpandHints + ? ( SetAttrMode::FORCEHINTEXPAND + | SetAttrMode::DONTEXPAND) + : SetAttrMode::DONTEXPAND; + + std::vector<SwTextAttr *> oldMarks; + if (bMark) + { + oldMarks = rPam.GetNode().GetTextNode()->GetTextAttrsAt( + rPam.GetPoint()->nContent.GetIndex(), RES_TXTATR_REFMARK); + } + + pDoc2->getIDocumentContentOperations().InsertPoolItem( rPam, aRefMark, nInsertFlags ); + + if( bMark && *rPam.GetPoint() > *rPam.GetMark()) + { + rPam.Exchange(); + } + + // aRefMark was copied into the document pool; now retrieve real format... + SwTextAttr * pTextAttr(nullptr); + if (bMark) + { + // #i107672# + // ensure that we do not retrieve a different mark at the same position + std::vector<SwTextAttr *> const newMarks( + rPam.GetNode().GetTextNode()->GetTextAttrsAt( + rPam.GetPoint()->nContent.GetIndex(), RES_TXTATR_REFMARK)); + std::vector<SwTextAttr *>::const_iterator const iter( + std::find_if(newMarks.begin(), newMarks.end(), + NotContainedIn<SwTextAttr *>(oldMarks))); + assert(newMarks.end() != iter); + if (newMarks.end() != iter) + { + pTextAttr = *iter; + } + } + else + { + SwTextNode *pTextNd = rPam.GetNode().GetTextNode(); + assert(pTextNd); + pTextAttr = pTextNd ? rPam.GetNode().GetTextNode()->GetTextAttrForCharAt( + rPam.GetPoint()->nContent.GetIndex() - 1, RES_TXTATR_REFMARK) : nullptr; + } + + if (!pTextAttr) + { + throw uno::RuntimeException( + "SwXReferenceMark::InsertRefMark(): cannot insert attribute", nullptr); + } + + m_pMarkFormat = &pTextAttr->GetRefMark(); + EndListeningAll(); + StartListening(const_cast<SwFormatRefMark*>(m_pMarkFormat)->GetNotifier()); +} + +void SAL_CALL +SwXReferenceMark::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard aGuard; + + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + if(xRangeTunnel.is()) + { + pRange = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + } + SwDoc *const pDocument = + pRange ? &pRange->GetDoc() : (pCursor ? pCursor->GetDoc() : nullptr); + if (!pDocument) + { + throw lang::IllegalArgumentException(); + } + + SwUnoInternalPaM aPam(*pDocument); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + m_pImpl->InsertRefMark(aPam, dynamic_cast<SwXTextCursor*>(pCursor)); + m_pImpl->m_bIsDescriptor = false; + m_pImpl->m_pDoc = pDocument; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXReferenceMark::getAnchor() +{ + SolarMutexGuard aGuard; + + if (m_pImpl->IsValid()) + { + SwFormatRefMark const*const pNewMark = + m_pImpl->m_pDoc->GetRefMark(m_pImpl->m_sMarkName); + if (pNewMark && (pNewMark == m_pImpl->m_pMarkFormat)) + { + SwTextRefMark const*const pTextMark = + m_pImpl->m_pMarkFormat->GetTextRefMark(); + if (pTextMark && + (&pTextMark->GetTextNode().GetNodes() == + &m_pImpl->m_pDoc->GetNodes())) + { + SwTextNode const& rTextNode = pTextMark->GetTextNode(); + const std::unique_ptr<SwPaM> pPam( (pTextMark->End()) + ? new SwPaM( rTextNode, *pTextMark->End(), + rTextNode, pTextMark->GetStart()) + : new SwPaM( rTextNode, pTextMark->GetStart()) ); + + return SwXTextRange::CreateXTextRange( + *m_pImpl->m_pDoc, *pPam->Start(), pPam->End()); + } + } + } + return nullptr; +} + +void SAL_CALL SwXReferenceMark::dispose() +{ + SolarMutexGuard aGuard; + if (m_pImpl->IsValid()) + { + SwFormatRefMark const*const pNewMark = + m_pImpl->m_pDoc->GetRefMark(m_pImpl->m_sMarkName); + if (pNewMark && (pNewMark == m_pImpl->m_pMarkFormat)) + { + SwTextRefMark const*const pTextMark = + m_pImpl->m_pMarkFormat->GetTextRefMark(); + if (pTextMark && + (&pTextMark->GetTextNode().GetNodes() == + &m_pImpl->m_pDoc->GetNodes())) + { + SwTextNode const& rTextNode = pTextMark->GetTextNode(); + const sal_Int32 nStt = pTextMark->GetStart(); + const sal_Int32 nEnd = pTextMark->End() + ? *pTextMark->End() + : nStt + 1; + + SwPaM aPam( rTextNode, nStt, rTextNode, nEnd ); + m_pImpl->m_pDoc->getIDocumentContentOperations().DeleteAndJoin( aPam ); + } + } + } + else if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->Invalidate(); + } +} + +void SAL_CALL SwXReferenceMark::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXReferenceMark::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +OUString SAL_CALL SwXReferenceMark::getName() +{ + SolarMutexGuard aGuard; + if (!m_pImpl->IsValid() || + !m_pImpl->m_pDoc->GetRefMark(m_pImpl->m_sMarkName)) + { + throw uno::RuntimeException(); + } + return m_pImpl->m_sMarkName; +} + +void SAL_CALL SwXReferenceMark::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_sMarkName = rName; + } + else + { + if (!m_pImpl->IsValid() + || !m_pImpl->m_pDoc->GetRefMark(m_pImpl->m_sMarkName) + || m_pImpl->m_pDoc->GetRefMark(rName)) + { + throw uno::RuntimeException(); + } + SwFormatRefMark const*const pCurMark = + m_pImpl->m_pDoc->GetRefMark(m_pImpl->m_sMarkName); + if ((rName != m_pImpl->m_sMarkName) + && pCurMark && (pCurMark == m_pImpl->m_pMarkFormat)) + { + const UnoActionContext aCont(m_pImpl->m_pDoc); + SwTextRefMark const*const pTextMark = + m_pImpl->m_pMarkFormat->GetTextRefMark(); + if (pTextMark && + (&pTextMark->GetTextNode().GetNodes() == + &m_pImpl->m_pDoc->GetNodes())) + { + SwTextNode const& rTextNode = pTextMark->GetTextNode(); + const sal_Int32 nStt = pTextMark->GetStart(); + const sal_Int32 nEnd = pTextMark->End() + ? *pTextMark->End() + : nStt + 1; + + SwPaM aPam( rTextNode, nStt, rTextNode, nEnd ); + // deletes the m_pImpl->m_pDoc member in the SwXReferenceMark! + m_pImpl->m_pDoc->getIDocumentContentOperations().DeleteAndJoin( aPam ); + // The aPam will keep the correct and functional doc though + + m_pImpl->m_sMarkName = rName; + //create a new one + m_pImpl->InsertRefMark( aPam, nullptr ); + m_pImpl->m_pDoc = aPam.GetDoc(); + } + } + } +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXReferenceMark::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xRef = + aSwMapProvider.GetPropertySet(PROPERTY_MAP_PARAGRAPH_EXTENSIONS) + ->getPropertySetInfo(); + return xRef; +} + +void SAL_CALL SwXReferenceMark::setPropertyValue( + const OUString& /*rPropertyName*/, const uno::Any& /*rValue*/ ) +{ + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL +SwXReferenceMark::getPropertyValue(const OUString& rPropertyName) +{ + // does not seem to need SolarMutex + uno::Any aRet; + if (! ::sw::GetDefaultTextContentValue(aRet, rPropertyName)) + { + throw beans::UnknownPropertyException(rPropertyName); + } + return aRet; +} + +void SAL_CALL SwXReferenceMark::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXReferenceMark::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL SwXReferenceMark::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXReferenceMark::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL SwXReferenceMark::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXReferenceMark::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL SwXReferenceMark::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXReferenceMark::removeVetoableChangeListener(): not implemented"); +} + +namespace { + +class SwXMetaText : public cppu::OWeakObject, public SwXText +{ +private: + SwXMeta & m_rMeta; + + virtual void PrepareForAttach(uno::Reference< text::XTextRange > & xRange, + const SwPaM & rPam) override; + + virtual bool CheckForOwnMemberMeta(const SwPaM & rPam, const bool bAbsorb) override; + +protected: + virtual const SwStartNode *GetStartNode() const override; + virtual uno::Reference< text::XTextCursor > + CreateCursor() override; + +public: + SwXMetaText(SwDoc & rDoc, SwXMeta & rMeta); + + /// make available for SwXMeta + using SwXText::Invalidate; + + // XInterface + virtual void SAL_CALL acquire() throw() override { cppu::OWeakObject::acquire(); } + virtual void SAL_CALL release() throw() override { cppu::OWeakObject::release(); } + + // XTypeProvider + virtual uno::Sequence< sal_Int8 > SAL_CALL + getImplementationId() override; + + // XText + virtual uno::Reference< text::XTextCursor > SAL_CALL + createTextCursor() override; + virtual uno::Reference< text::XTextCursor > SAL_CALL + createTextCursorByRange( + const uno::Reference< text::XTextRange > & xTextPosition) override; + +}; + +} + +SwXMetaText::SwXMetaText(SwDoc & rDoc, SwXMeta & rMeta) + : SwXText(&rDoc, CursorType::Meta) + , m_rMeta(rMeta) +{ +} + +const SwStartNode *SwXMetaText::GetStartNode() const +{ + SwXText const * const pParent( + dynamic_cast<SwXText*>(m_rMeta.GetParentText().get())); + return pParent ? pParent->GetStartNode() : nullptr; +} + +void SwXMetaText::PrepareForAttach( uno::Reference<text::XTextRange> & xRange, + const SwPaM & rPam) +{ + // create a new cursor to prevent modifying SwXTextRange + xRange = static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), &m_rMeta, CursorType::Meta, *rPam.GetPoint(), + (rPam.HasMark()) ? rPam.GetMark() : nullptr)); +} + +bool SwXMetaText::CheckForOwnMemberMeta(const SwPaM & rPam, const bool bAbsorb) +{ + return m_rMeta.CheckForOwnMemberMeta(rPam, bAbsorb); +} + +uno::Reference< text::XTextCursor > SwXMetaText::CreateCursor() +{ + uno::Reference< text::XTextCursor > xRet; + if (IsValid()) + { + SwTextNode * pTextNode; + sal_Int32 nMetaStart; + sal_Int32 nMetaEnd; + const bool bSuccess( + m_rMeta.SetContentRange(pTextNode, nMetaStart, nMetaEnd) ); + if (bSuccess) + { + SwPosition aPos(*pTextNode, nMetaStart); + xRet = static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), &m_rMeta, CursorType::Meta, aPos)); + } + } + return xRet; +} + +uno::Sequence<sal_Int8> SAL_CALL +SwXMetaText::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XText +uno::Reference< text::XTextCursor > SAL_CALL +SwXMetaText::createTextCursor() +{ + return CreateCursor(); +} + +uno::Reference< text::XTextCursor > SAL_CALL +SwXMetaText::createTextCursorByRange( + const uno::Reference<text::XTextRange> & xTextPosition) +{ + const uno::Reference<text::XTextCursor> xCursor( CreateCursor() ); + xCursor->gotoRange(xTextPosition, false); + return xCursor; +} + +// the Meta has a cached list of text portions for its contents +// this list is created by SwXTextPortionEnumeration +// the Meta listens at the SwTextNode and throws away the cache when it changes +class SwXMeta::Impl : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + std::unique_ptr<const TextRangeList_t> m_pTextPortions; + // 3 possible states: not attached, attached, disposed + bool m_bIsDisposed; + bool m_bIsDescriptor; + uno::Reference<text::XText> m_xParentText; + rtl::Reference<SwXMetaText> m_xText; + sw::Meta* m_pMeta; + + Impl(SwXMeta& rThis, SwDoc& rDoc, + ::sw::Meta* const pMeta, + uno::Reference<text::XText> const& xParentText, + std::unique_ptr<TextRangeList_t const> pPortions) + : m_EventListeners(m_Mutex) + , m_pTextPortions(std::move(pPortions)) + , m_bIsDisposed(false) + , m_bIsDescriptor(nullptr == pMeta) + , m_xParentText(xParentText) + , m_xText(new SwXMetaText(rDoc, rThis)) + , m_pMeta(pMeta) + { + !m_bIsDescriptor && StartListening(m_pMeta->GetNotifier()); + } + + inline const ::sw::Meta* GetMeta() const; + // only for SwXMetaField! + inline const ::sw::MetaField* GetMetaField() const; +protected: + virtual void Notify(const SfxHint& rHint) override; + +}; + +inline const ::sw::Meta* SwXMeta::Impl::GetMeta() const +{ + return m_pMeta; +} + +// SwModify +void SwXMeta::Impl::Notify(const SfxHint& rHint) +{ + m_pTextPortions.reset(); // throw away cache (SwTextNode changed) + if(rHint.GetId() == SfxHintId::Dying || rHint.GetId() == SfxHintId::Deinitializing) + { + m_bIsDisposed = true; + m_pMeta = nullptr; + m_xText->Invalidate(); + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); + } +} + +uno::Reference<text::XText> const & SwXMeta::GetParentText() const +{ + return m_pImpl->m_xParentText; +} + +SwXMeta::SwXMeta(SwDoc *const pDoc, ::sw::Meta *const pMeta, + uno::Reference<text::XText> const& xParentText, + std::unique_ptr<TextRangeList_t const> pPortions) + : m_pImpl( new SwXMeta::Impl(*this, *pDoc, pMeta, xParentText, std::move(pPortions)) ) +{ +} + +SwXMeta::SwXMeta(SwDoc *const pDoc) + : m_pImpl( new SwXMeta::Impl(*this, *pDoc, nullptr, nullptr, nullptr) ) +{ +} + +SwXMeta::~SwXMeta() +{ +} + +uno::Reference<rdf::XMetadatable> +SwXMeta::CreateXMeta(SwDoc & rDoc, bool const isField) +{ + SwXMeta *const pXMeta(isField + ? new SwXMetaField(& rDoc) : new SwXMeta(& rDoc)); + // this is why the constructor is private: need to acquire pXMeta here + uno::Reference<rdf::XMetadatable> const xMeta(pXMeta); + // need a permanent Reference to initialize m_wThis + pXMeta->m_pImpl->m_wThis = xMeta; + return xMeta; +} + +uno::Reference<rdf::XMetadatable> +SwXMeta::CreateXMeta(::sw::Meta & rMeta, + uno::Reference<text::XText> const& i_xParent, + std::unique_ptr<TextRangeList_t const> && pPortions) +{ + // re-use existing SwXMeta + // #i105557#: do not iterate over the registered clients: race condition + uno::Reference<rdf::XMetadatable> xMeta(rMeta.GetXMeta()); + if (xMeta.is()) + { + if (pPortions) // set cache in the XMeta to the given portions + { + SwXMeta *const pXMeta( + comphelper::getUnoTunnelImplementation<SwXMeta>(xMeta)); + assert(pXMeta); + // NB: the meta must always be created with the complete content + // if SwXTextPortionEnumeration is created for a selection, + // it must be checked that the Meta is contained in the selection! + pXMeta->m_pImpl->m_pTextPortions = std::move(pPortions); + // ??? is this necessary? + if (pXMeta->m_pImpl->m_xParentText.get() != i_xParent.get()) + { + SAL_WARN("sw.uno", "SwXMeta with different parent?"); + pXMeta->m_pImpl->m_xParentText.set(i_xParent); + } + } + return xMeta; + } + + // create new SwXMeta + SwTextNode * const pTextNode( rMeta.GetTextNode() ); + SAL_WARN_IF(!pTextNode, "sw.uno", "CreateXMeta: no text node?"); + if (!pTextNode) { return nullptr; } + uno::Reference<text::XText> xParentText(i_xParent); + if (!xParentText.is()) + { + SwTextMeta * const pTextAttr( rMeta.GetTextAttr() ); + SAL_WARN_IF(!pTextAttr, "sw.uno", "CreateXMeta: no text attr?"); + if (!pTextAttr) { return nullptr; } + const SwPosition aPos(*pTextNode, pTextAttr->GetStart()); + xParentText.set( ::sw::CreateParentXText(*pTextNode->GetDoc(), aPos) ); + } + if (!xParentText.is()) { return nullptr; } + SwXMeta *const pXMeta( (RES_TXTATR_META == rMeta.GetFormatMeta()->Which()) + ? new SwXMeta (pTextNode->GetDoc(), &rMeta, xParentText, + std::move(pPortions)) + : new SwXMetaField(pTextNode->GetDoc(), &rMeta, xParentText, + std::move(pPortions))); + // this is why the constructor is private: need to acquire pXMeta here + xMeta.set(pXMeta); + // in order to initialize the weak pointer cache in the core object + rMeta.SetXMeta(xMeta); + // need a permanent Reference to initialize m_wThis + pXMeta->m_pImpl->m_wThis = xMeta; + return xMeta; +} + +bool SwXMeta::SetContentRange( + SwTextNode *& rpNode, sal_Int32 & rStart, sal_Int32 & rEnd ) const +{ + ::sw::Meta const * const pMeta( m_pImpl->GetMeta() ); + if (pMeta) + { + SwTextMeta const * const pTextAttr( pMeta->GetTextAttr() ); + if (pTextAttr) + { + rpNode = pMeta->GetTextNode(); + if (rpNode) + { + // rStart points at the first position _within_ the meta! + rStart = pTextAttr->GetStart() + 1; + rEnd = *pTextAttr->End(); + return true; + } + } + } + return false; +} + +bool SwXMeta::CheckForOwnMemberMeta(const SwPaM & rPam, const bool bAbsorb) +{ + SwTextNode * pTextNode; + sal_Int32 nMetaStart; + sal_Int32 nMetaEnd; + const bool bSuccess( SetContentRange(pTextNode, nMetaStart, nMetaEnd) ); + OSL_ENSURE(bSuccess, "no pam?"); + if (!bSuccess) + throw lang::DisposedException(); + + SwPosition const * const pStartPos( rPam.Start() ); + if (&pStartPos->nNode.GetNode() != pTextNode) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but start " + "of text range not in same paragraph as text content", + nullptr, 0); + } + bool bForceExpandHints(false); + const sal_Int32 nStartPos(pStartPos->nContent.GetIndex()); + // not <= but < because nMetaStart is behind dummy char! + // not >= but > because == means insert at end! + if ((nStartPos < nMetaStart) || (nStartPos > nMetaEnd)) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but start " + "of text range not inside text content", + nullptr, 0); + } + else if (nStartPos == nMetaEnd) + { + bForceExpandHints = true; + } + if (rPam.HasMark() && bAbsorb) + { + SwPosition const * const pEndPos( rPam.End() ); + if (&pEndPos->nNode.GetNode() != pTextNode) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but end " + "of text range not in same paragraph as text content", + nullptr, 0); + } + const sal_Int32 nEndPos(pEndPos->nContent.GetIndex()); + // not <= but < because nMetaStart is behind dummy char! + // not >= but > because == means insert at end! + if ((nEndPos < nMetaStart) || (nEndPos > nMetaEnd)) + { + throw lang::IllegalArgumentException( + "trying to insert into a nesting text content, but end " + "of text range not inside text content", + nullptr, 0); + } + else if (nEndPos == nMetaEnd) + { + bForceExpandHints = true; + } + } + return bForceExpandHints; +} + +namespace +{ + class theSwXMetaUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXMetaUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXMeta::getUnoTunnelId() +{ + return theSwXMetaUnoTunnelId::get().getSeq(); +} + +// XUnoTunnel +sal_Int64 SAL_CALL +SwXMeta::getSomething( const uno::Sequence< sal_Int8 > & i_rId ) +{ + return ::sw::UnoTunnelImpl<SwXMeta>(i_rId, this); +} + +// XServiceInfo +OUString SAL_CALL +SwXMeta::getImplementationName() +{ + return "SwXMeta"; +} + +sal_Bool SAL_CALL +SwXMeta::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXMeta::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextContent", + "com.sun.star.text.InContentMetadata" + }; +} + +// XComponent +void SAL_CALL +SwXMeta::addEventListener( + uno::Reference< lang::XEventListener> const & xListener ) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL +SwXMeta::removeEventListener( + uno::Reference< lang::XEventListener> const & xListener ) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +void SAL_CALL +SwXMeta::dispose() +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pTextPortions.reset(); + lang::EventObject const ev(static_cast< ::cppu::OWeakObject&>(*this)); + m_pImpl->m_EventListeners.disposeAndClear(ev); + m_pImpl->m_bIsDisposed = true; + m_pImpl->m_xText->Invalidate(); + } + else if (!m_pImpl->m_bIsDisposed) + { + SwTextNode * pTextNode; + sal_Int32 nMetaStart; + sal_Int32 nMetaEnd; + const bool bSuccess(SetContentRange(pTextNode, nMetaStart, nMetaEnd)); + OSL_ENSURE(bSuccess, "no pam?"); + if (bSuccess) + { + // -1 because of CH_TXTATR + SwPaM aPam( *pTextNode, nMetaStart - 1, *pTextNode, nMetaEnd ); + SwDoc * const pDoc( pTextNode->GetDoc() ); + pDoc->getIDocumentContentOperations().DeleteAndJoin( aPam ); + + // removal should call Modify and do the dispose + assert(m_pImpl->m_bIsDisposed); + } + } +} + +void +SwXMeta::AttachImpl(const uno::Reference< text::XTextRange > & i_xTextRange, + const sal_uInt16 i_nWhich) +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDisposed) + { + throw lang::DisposedException(); + } + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException( + "SwXMeta::attach(): already attached", + static_cast< ::cppu::OWeakObject* >(this)); + } + + uno::Reference<lang::XUnoTunnel> xRangeTunnel(i_xTextRange, uno::UNO_QUERY); + if (!xRangeTunnel.is()) + { + throw lang::IllegalArgumentException( + "SwXMeta::attach(): argument is no XUnoTunnel", + static_cast< ::cppu::OWeakObject* >(this), 0); + } + SwXTextRange *const pRange( + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel)); + OTextCursorHelper *const pCursor( pRange ? nullptr : + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel)); + if (!pRange && !pCursor) + { + throw lang::IllegalArgumentException( + "SwXMeta::attach(): argument not supported type", + static_cast< ::cppu::OWeakObject* >(this), 0); + } + + SwDoc * const pDoc( + pRange ? &pRange->GetDoc() : pCursor->GetDoc()); + if (!pDoc) + { + throw lang::IllegalArgumentException( + "SwXMeta::attach(): argument has no SwDoc", + static_cast< ::cppu::OWeakObject* >(this), 0); + } + + SwUnoInternalPaM aPam(*pDoc); + ::sw::XTextRangeToSwPaM(aPam, i_xTextRange); + + UnoActionContext aContext(pDoc); + + SwXTextCursor const*const pTextCursor( + dynamic_cast<SwXTextCursor*>(pCursor)); + const bool bForceExpandHints(pTextCursor && pTextCursor->IsAtEndOfMeta()); + const SetAttrMode nInsertFlags( bForceExpandHints + ? ( SetAttrMode::FORCEHINTEXPAND + | SetAttrMode::DONTEXPAND) + : SetAttrMode::DONTEXPAND ); + + const std::shared_ptr< ::sw::Meta> pMeta( (RES_TXTATR_META == i_nWhich) + ? std::make_shared< ::sw::Meta>( nullptr ) + : std::shared_ptr< ::sw::Meta>( + pDoc->GetMetaFieldManager().makeMetaField()) ); + SwFormatMeta meta(pMeta, i_nWhich); // this is cloned by Insert! + const bool bSuccess( pDoc->getIDocumentContentOperations().InsertPoolItem( aPam, meta, nInsertFlags ) ); + SwTextAttr * const pTextAttr( pMeta->GetTextAttr() ); + if (!bSuccess) + { + throw lang::IllegalArgumentException( + "SwXMeta::attach(): cannot create meta: range invalid?", + static_cast< ::cppu::OWeakObject* >(this), 1); + } + if (!pTextAttr) + { + OSL_FAIL("meta inserted, but has no text attribute?"); + throw uno::RuntimeException( + "SwXMeta::attach(): cannot create meta", + static_cast< ::cppu::OWeakObject* >(this)); + } + + m_pImpl->EndListeningAll(); + m_pImpl->m_pMeta = pMeta.get(); + m_pImpl->StartListening(pMeta->GetNotifier()); + pMeta->SetXMeta(uno::Reference<rdf::XMetadatable>(this)); + + m_pImpl->m_xParentText = ::sw::CreateParentXText(*pDoc, *aPam.GetPoint()); + + m_pImpl->m_bIsDescriptor = false; +} + +// XTextContent +void SAL_CALL +SwXMeta::attach(const uno::Reference< text::XTextRange > & i_xTextRange) +{ + return SwXMeta::AttachImpl(i_xTextRange, RES_TXTATR_META); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXMeta::getAnchor() +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDisposed) + { + throw lang::DisposedException(); + } + if (m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException( + "SwXMeta::getAnchor(): not inserted", + static_cast< ::cppu::OWeakObject* >(this)); + } + + SwTextNode * pTextNode; + sal_Int32 nMetaStart; + sal_Int32 nMetaEnd; + const bool bSuccess(SetContentRange(pTextNode, nMetaStart, nMetaEnd)); + OSL_ENSURE(bSuccess, "no pam?"); + if (!bSuccess) + { + throw lang::DisposedException( + "SwXMeta::getAnchor(): not attached", + static_cast< ::cppu::OWeakObject* >(this)); + } + + const SwPosition start(*pTextNode, nMetaStart - 1); // -1 due to CH_TXTATR + const SwPosition end(*pTextNode, nMetaEnd); + return SwXTextRange::CreateXTextRange(*pTextNode->GetDoc(), start, &end); +} + +// XTextRange +uno::Reference< text::XText > SAL_CALL +SwXMeta::getText() +{ + return this; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXMeta::getStart() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->getStart(); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXMeta::getEnd() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->getEnd(); +} + +OUString SAL_CALL +SwXMeta::getString() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->getString(); +} + +void SAL_CALL +SwXMeta::setString(const OUString& rString) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->setString(rString); +} + +// XSimpleText +uno::Reference< text::XTextCursor > SAL_CALL +SwXMeta::createTextCursor() +{ + SolarMutexGuard g; + return m_pImpl->m_xText->createTextCursor(); +} + +uno::Reference< text::XTextCursor > SAL_CALL +SwXMeta::createTextCursorByRange( + const uno::Reference<text::XTextRange> & xTextPosition) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->createTextCursorByRange(xTextPosition); +} + +void SAL_CALL +SwXMeta::insertString(const uno::Reference<text::XTextRange> & xRange, + const OUString& rString, sal_Bool bAbsorb) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->insertString(xRange, rString, bAbsorb); +} + +void SAL_CALL +SwXMeta::insertControlCharacter(const uno::Reference<text::XTextRange> & xRange, + sal_Int16 nControlCharacter, sal_Bool bAbsorb) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->insertControlCharacter(xRange, nControlCharacter, + bAbsorb); +} + +// XText +void SAL_CALL +SwXMeta::insertTextContent( const uno::Reference<text::XTextRange> & xRange, + const uno::Reference<text::XTextContent> & xContent, sal_Bool bAbsorb) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->insertTextContent(xRange, xContent, bAbsorb); +} + +void SAL_CALL +SwXMeta::removeTextContent( + const uno::Reference< text::XTextContent > & xContent) +{ + SolarMutexGuard g; + return m_pImpl->m_xText->removeTextContent(xContent); +} + +// XChild +uno::Reference< uno::XInterface > SAL_CALL +SwXMeta::getParent() +{ + SolarMutexGuard g; + SwTextNode * pTextNode; + sal_Int32 nMetaStart; + sal_Int32 nMetaEnd; + bool const bSuccess( SetContentRange(pTextNode, nMetaStart, nMetaEnd) ); + OSL_ENSURE(bSuccess, "no pam?"); + if (!bSuccess) { throw lang::DisposedException(); } + // in order to prevent getting this meta, subtract 1 from nMetaStart; + // so we get the index of the dummy character, and we exclude it + // by calling GetTextAttrAt(_, _, PARENT) in GetNestedTextContent + uno::Reference<text::XTextContent> const xRet( + SwUnoCursorHelper::GetNestedTextContent(*pTextNode, nMetaStart - 1, + true) ); + return xRet; +} + +void SAL_CALL +SwXMeta::setParent(uno::Reference< uno::XInterface > const& /*xParent*/) +{ + throw lang::NoSupportException("setting parent not supported", *this); +} + +// XElementAccess +uno::Type SAL_CALL +SwXMeta::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SwXMeta::hasElements() +{ + SolarMutexGuard g; + return m_pImpl->m_pMeta != nullptr; +} + +// XEnumerationAccess +uno::Reference< container::XEnumeration > SAL_CALL +SwXMeta::createEnumeration() +{ + SolarMutexGuard g; + + if (m_pImpl->m_bIsDisposed) + { + throw lang::DisposedException(); + } + if (m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException( + "createEnumeration(): not inserted", + static_cast< ::cppu::OWeakObject* >(this)); + } + + SwTextNode * pTextNode; + sal_Int32 nMetaStart; + sal_Int32 nMetaEnd; + const bool bSuccess(SetContentRange(pTextNode, nMetaStart, nMetaEnd)); + OSL_ENSURE(bSuccess, "no pam?"); + if (!bSuccess) + throw lang::DisposedException(); + + SwPaM aPam(*pTextNode, nMetaStart); + + if (!m_pImpl->m_pTextPortions) + { + return new SwXTextPortionEnumeration( + aPam, GetParentText(), nMetaStart, nMetaEnd); + } + else // cached! + { + return new SwXTextPortionEnumeration(aPam, *m_pImpl->m_pTextPortions); + } +} + +// MetadatableMixin +::sfx2::Metadatable* SwXMeta::GetCoreObject() +{ + return const_cast< ::sw::Meta * >(m_pImpl->GetMeta()); +} + +uno::Reference<frame::XModel> SwXMeta::GetModel() +{ + ::sw::Meta const * const pMeta( m_pImpl->GetMeta() ); + if (pMeta) + { + SwTextNode const * const pTextNode( pMeta->GetTextNode() ); + if (pTextNode) + { + SwDocShell const * const pShell(pTextNode->GetDoc()->GetDocShell()); + return pShell ? pShell->GetModel() : nullptr; + } + } + return nullptr; +} + +inline const ::sw::MetaField* SwXMeta::Impl::GetMetaField() const +{ + return dynamic_cast<sw::MetaField*>(m_pMeta); +} + +SwXMetaField::SwXMetaField(SwDoc *const pDoc, ::sw::Meta *const pMeta, + uno::Reference<text::XText> const& xParentText, + std::unique_ptr<TextRangeList_t const> pPortions) + : SwXMetaField_Base(pDoc, pMeta, xParentText, std::move(pPortions)) +{ + OSL_ENSURE(dynamic_cast< ::sw::MetaField* >(pMeta), + "SwXMetaField created for wrong hint!"); +} + +SwXMetaField::SwXMetaField(SwDoc *const pDoc) + : SwXMetaField_Base(pDoc) +{ +} + +SwXMetaField::~SwXMetaField() +{ +} + +// XServiceInfo +OUString SAL_CALL +SwXMetaField::getImplementationName() +{ + return "SwXMetaField"; +} + +sal_Bool SAL_CALL +SwXMetaField::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXMetaField::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextContent", + "com.sun.star.text.TextField", + "com.sun.star.text.textfield.MetadataField" + }; +} + +// XComponent +void SAL_CALL +SwXMetaField::addEventListener( + uno::Reference< lang::XEventListener> const & xListener ) +{ + return SwXMeta::addEventListener(xListener); +} + +void SAL_CALL +SwXMetaField::removeEventListener( + uno::Reference< lang::XEventListener> const & xListener ) +{ + return SwXMeta::removeEventListener(xListener); +} + +void SAL_CALL +SwXMetaField::dispose() +{ + return SwXMeta::dispose(); +} + +// XTextContent +void SAL_CALL +SwXMetaField::attach(const uno::Reference< text::XTextRange > & i_xTextRange) +{ + return SwXMeta::AttachImpl(i_xTextRange, RES_TXTATR_METAFIELD); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXMetaField::getAnchor() +{ + return SwXMeta::getAnchor(); +} + +// XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXMetaField::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xRef( + aSwMapProvider.GetPropertySet(PROPERTY_MAP_METAFIELD) + ->getPropertySetInfo() ); + return xRef; +} + +void SAL_CALL +SwXMetaField::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard g; + + ::sw::MetaField * const pMeta( + const_cast< ::sw::MetaField * >(m_pImpl->GetMetaField()) ); + if (!pMeta) + throw lang::DisposedException(); + + if ( rPropertyName == "NumberFormat" ) + { + sal_Int32 nNumberFormat(0); + if (rValue >>= nNumberFormat) + { + pMeta->SetNumberFormat(static_cast<sal_uInt32>(nNumberFormat)); + } + } + else if ( rPropertyName == "IsFixedLanguage" ) + { + bool b(false); + if (rValue >>= b) + { + pMeta->SetIsFixedLanguage(b); + } + } + else + { + throw beans::UnknownPropertyException(rPropertyName); + } +} + +uno::Any SAL_CALL +SwXMetaField::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard g; + + ::sw::MetaField const * const pMeta( m_pImpl->GetMetaField() ); + if (!pMeta) + throw lang::DisposedException(); + + uno::Any any; + + if ( rPropertyName == "NumberFormat" ) + { + const OUString text( getPresentation(false) ); + any <<= static_cast<sal_Int32>(pMeta->GetNumberFormat(text)); + } + else if ( rPropertyName == "IsFixedLanguage" ) + { + any <<= pMeta->IsFixedLanguage(); + } + else + { + throw beans::UnknownPropertyException(rPropertyName); + } + + return any; +} + +void SAL_CALL +SwXMetaField::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXMetaField::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXMetaField::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXMetaField::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXMetaField::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXMetaField::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXMetaField::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXMetaField::removeVetoableChangeListener(): not implemented"); +} + +static uno::Reference<rdf::XURI> const& +lcl_getURI(const bool bPrefix) +{ + static uno::Reference< uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext()); + static uno::Reference< rdf::XURI > xOdfPrefix( + rdf::URI::createKnown(xContext, rdf::URIs::ODF_PREFIX), + uno::UNO_SET_THROW); + static uno::Reference< rdf::XURI > xOdfSuffix( + rdf::URI::createKnown(xContext, rdf::URIs::ODF_SUFFIX), + uno::UNO_SET_THROW); + return bPrefix ? xOdfPrefix : xOdfSuffix; +} + +static OUString +lcl_getPrefixOrSuffix( + uno::Reference<rdf::XRepository> const & xRepository, + uno::Reference<rdf::XResource> const & xMetaField, + uno::Reference<rdf::XURI> const & xPredicate) +{ + const uno::Reference<container::XEnumeration> xEnum( + xRepository->getStatements(xMetaField, xPredicate, nullptr), + uno::UNO_SET_THROW); + while (xEnum->hasMoreElements()) { + rdf::Statement stmt; + if (!(xEnum->nextElement() >>= stmt)) { + throw uno::RuntimeException(); + } + const uno::Reference<rdf::XLiteral> xObject(stmt.Object, + uno::UNO_QUERY); + if (!xObject.is()) continue; + if (xEnum->hasMoreElements()) { + SAL_INFO("sw.uno", "ignoring other odf:Prefix/odf:Suffix statements"); + } + return xObject->getValue(); + } + return OUString(); +} + +void +getPrefixAndSuffix( + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<rdf::XMetadatable>& xMetaField, + OUString *const o_pPrefix, OUString *const o_pSuffix) +{ + try { + const uno::Reference<rdf::XRepositorySupplier> xRS( + xModel, uno::UNO_QUERY_THROW); + const uno::Reference<rdf::XRepository> xRepo( + xRS->getRDFRepository(), uno::UNO_SET_THROW); + const uno::Reference<rdf::XResource> xMeta( + xMetaField, uno::UNO_QUERY_THROW); + if (o_pPrefix) + { + *o_pPrefix = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(true)); + } + if (o_pSuffix) + { + *o_pSuffix = lcl_getPrefixOrSuffix(xRepo, xMeta, lcl_getURI(false)); + } + } catch (uno::RuntimeException &) { + throw; + } catch (const uno::Exception &) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("getPrefixAndSuffix: exception", nullptr, anyEx); + } +} + +// XTextField +OUString SAL_CALL +SwXMetaField::getPresentation(sal_Bool bShowCommand) +{ + SolarMutexGuard g; + + if (bShowCommand) + { +//FIXME ? + return OUString(); + } + else + { + // getString should check if this is invalid + const OUString content( getString() ); + OUString prefix; + OUString suffix; + getPrefixAndSuffix(GetModel(), this, &prefix, &suffix); + return prefix + content + suffix; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unosect.cxx b/sw/source/core/unocore/unosect.cxx new file mode 100644 index 000000000..831f0bda3 --- /dev/null +++ b/sw/source/core/unocore/unosect.cxx @@ -0,0 +1,1735 @@ +/* -*- 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 <memory> +#include <unosection.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/text/SectionFileLink.hpp> + +#include <comphelper/interfacecontainer2.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <cmdid.h> +#include <hintids.hxx> +#include <svl/urihelper.hxx> +#include <svl/listener.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/xmlcnitm.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/lnkbase.hxx> +#include <osl/mutex.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <fmtclds.hxx> +#include <unotextrange.hxx> +#include <TextCursorHelper.hxx> +#include <unoport.hxx> +#include <redline.hxx> +#include <unomap.hxx> +#include <section.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <docsh.hxx> +#include <sfx2/docfile.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <tox.hxx> +#include <unoidx.hxx> +#include <doctxm.hxx> +#include <fmtftntx.hxx> +#include <fmtclbl.hxx> +#include <editeng/frmdiritem.hxx> +#include <fmtcntnt.hxx> +#include <editeng/lrspitem.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> + +using namespace ::com::sun::star; + +namespace { + +struct SwTextSectionProperties_Impl +{ + uno::Sequence<sal_Int8> m_Password; + OUString m_sCondition; + OUString m_sLinkFileName; + OUString m_sSectionFilter; + OUString m_sSectionRegion; + + std::unique_ptr<SwFormatCol> m_pColItem; + std::unique_ptr<SvxBrushItem> m_pBrushItem; + std::unique_ptr<SwFormatFootnoteAtTextEnd> m_pFootnoteItem; + std::unique_ptr<SwFormatEndAtTextEnd> m_pEndItem; + std::unique_ptr<SvXMLAttrContainerItem> m_pXMLAttr; + std::unique_ptr<SwFormatNoBalancedColumns> m_pNoBalanceItem; + std::unique_ptr<SvxFrameDirectionItem> m_pFrameDirItem; + std::unique_ptr<SvxLRSpaceItem> m_pLRSpaceItem; + + bool m_bDDE; + bool m_bHidden; + bool m_bCondHidden; + bool m_bProtect; + bool m_bEditInReadonly; + bool m_bUpdateType; + + SwTextSectionProperties_Impl() + : m_bDDE(false) + , m_bHidden(false) + , m_bCondHidden(false) + , m_bProtect(false) + , m_bEditInReadonly(false) + , m_bUpdateType(true) + { + } + +}; + +} + +class SwXTextSection::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + SwXTextSection & m_rThis; + uno::WeakReference<uno::XInterface> m_wThis; + const SfxItemPropertySet & m_rPropSet; + ::comphelper::OInterfaceContainerHelper2 m_EventListeners; + const bool m_bIndexHeader; + bool m_bIsDescriptor; + OUString m_sName; + std::unique_ptr<SwTextSectionProperties_Impl> m_pProps; + SwSectionFormat* m_pFormat; + + Impl( SwXTextSection& rThis, + SwSectionFormat* const pFormat, const bool bIndexHeader) + : SvtListener() + , m_rThis(rThis) + , m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_SECTION)) + , m_EventListeners(m_Mutex) + , m_bIndexHeader(bIndexHeader) + , m_bIsDescriptor(nullptr == pFormat) + , m_pProps(pFormat ? nullptr : new SwTextSectionProperties_Impl()) + , m_pFormat(pFormat) + { + if(m_pFormat) + StartListening(m_pFormat->GetNotifier()); + } + + void Attach(SwSectionFormat* pFormat) + { + EndListeningAll(); + StartListening(pFormat->GetNotifier()); + m_pFormat = pFormat; + } + + SwSectionFormat* GetSectionFormat() const + { return m_pFormat; } + + SwSectionFormat & GetSectionFormatOrThrow() const { + SwSectionFormat *const pFormat( GetSectionFormat() ); + if (!pFormat) { + throw uno::RuntimeException("SwXTextSection: disposed or invalid", nullptr); + } + return *pFormat; + } + + /// @throws beans::UnknownPropertyException + /// @throws beans::PropertyVetoException, + /// @throws lang::IllegalArgumentException + /// @throws lang::WrappedTargetException, + /// @throws uno::RuntimeException + void SetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& aValues); + /// @throws beans::UnknownPropertyException + /// @throws lang::WrappedTargetException, + /// @throws uno::RuntimeException + uno::Sequence< uno::Any > + GetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames); + virtual void Notify(const SfxHint& rHint) override; +}; + +void SwXTextSection::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pFormat = nullptr; + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (!xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + return; + } + lang::EventObject const ev(xThis); + m_EventListeners.disposeAndClear(ev); + } +} + +SwSectionFormat * SwXTextSection::GetFormat() const +{ + return m_pImpl->GetSectionFormat(); +} + +uno::Reference< text::XTextSection > +SwXTextSection::CreateXTextSection( + SwSectionFormat *const pFormat, const bool bIndexHeader) +{ + // re-use existing SwXTextSection + // #i105557#: do not iterate over the registered clients: race condition + uno::Reference< text::XTextSection > xSection; + if (pFormat) + { + xSection.set(pFormat->GetXTextSection()); + } + if ( !xSection.is() ) + { + SwXTextSection *const pNew = new SwXTextSection(pFormat, bIndexHeader); + xSection.set(pNew); + if (pFormat) + { + pFormat->SetXTextSection(xSection); + } + // need a permanent Reference to initialize m_wThis + pNew->m_pImpl->m_wThis = xSection; + } + return xSection; +} + +SwXTextSection::SwXTextSection( + SwSectionFormat *const pFormat, const bool bIndexHeader) + : m_pImpl( new SwXTextSection::Impl(*this, pFormat, bIndexHeader) ) +{ +} + +SwXTextSection::~SwXTextSection() +{ +} + +namespace +{ + class theSwXTextSectionUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextSectionUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextSection::getUnoTunnelId() +{ + return theSwXTextSectionUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXTextSection::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXTextSection>(rId, this); +} + +uno::Reference< text::XTextSection > SAL_CALL +SwXTextSection::getParentSection() +{ + SolarMutexGuard aGuard; + + SwSectionFormat & rSectionFormat( m_pImpl->GetSectionFormatOrThrow() ); + + SwSectionFormat *const pParentFormat = rSectionFormat.GetParent(); + const uno::Reference< text::XTextSection > xRet = + pParentFormat ? CreateXTextSection(pParentFormat) : nullptr; + return xRet; +} + +uno::Sequence< uno::Reference< text::XTextSection > > SAL_CALL +SwXTextSection::getChildSections() +{ + SolarMutexGuard aGuard; + + SwSectionFormat & rSectionFormat( m_pImpl->GetSectionFormatOrThrow() ); + + SwSections aChildren; + rSectionFormat.GetChildSections(aChildren, SectionSort::Not, false); + uno::Sequence<uno::Reference<text::XTextSection> > aSeq(aChildren.size()); + uno::Reference< text::XTextSection > * pArray = aSeq.getArray(); + for (size_t i = 0; i < aChildren.size(); ++i) + { + SwSectionFormat *const pChild = aChildren[i]->GetFormat(); + pArray[i] = CreateXTextSection(pChild); + } + return aSeq; +} + +void SAL_CALL +SwXTextSection::attach(const uno::Reference< text::XTextRange > & xTextRange) +{ + SolarMutexGuard g; + + if (!m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + if(xRangeTunnel.is()) + { + pRange = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + } + + SwDoc *const pDoc = + pRange ? &pRange->GetDoc() : (pCursor ? pCursor->GetDoc() : nullptr); + if (!pDoc) + { + throw lang::IllegalArgumentException(); + } + + SwUnoInternalPaM aPam(*pDoc); + // this has to return true now + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + UnoActionContext aCont(pDoc); + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSSECTION, nullptr ); + + if (m_pImpl->m_sName.isEmpty()) + { + m_pImpl->m_sName = "TextSection"; + } + SectionType eType(SectionType::FileLink); + if( m_pImpl->m_pProps->m_bDDE ) + eType = SectionType::DdeLink; + else if( m_pImpl->m_pProps->m_sLinkFileName.isEmpty() && m_pImpl->m_pProps->m_sSectionRegion.isEmpty() ) + eType = SectionType::Content; + // index header section? + if (m_pImpl->m_bIndexHeader) + { + // caller wants an index header section, but will only + // give him one if a) we are inside an index, and b) said + // index doesn't yet have a header section. + const SwTOXBase* pBase = SwDoc::GetCurTOX(*aPam.Start()); + + // are we inside an index? + if (pBase) + { + // get all child sections + SwSections aSectionsArr; + static_cast<const SwTOXBaseSection*>(pBase)->GetFormat()-> + GetChildSections(aSectionsArr); + + // and search for current header section + const size_t nCount = aSectionsArr.size(); + bool bHeaderPresent = false; + for(size_t i = 0; i < nCount; ++i) + { + if (aSectionsArr[i]->GetType() == SectionType::ToxHeader) + bHeaderPresent = true; + } + if (! bHeaderPresent) + { + eType = SectionType::ToxHeader; + } + } + } + + SwSectionData aSect(eType, pDoc->GetUniqueSectionName(&m_pImpl->m_sName)); + aSect.SetCondition(m_pImpl->m_pProps->m_sCondition); + aSect.SetLinkFileName(m_pImpl->m_pProps->m_sLinkFileName + + OUStringChar(sfx2::cTokenSeparator) + + m_pImpl->m_pProps->m_sSectionFilter + + OUStringChar(sfx2::cTokenSeparator) + + m_pImpl->m_pProps->m_sSectionRegion); + + aSect.SetHidden(m_pImpl->m_pProps->m_bHidden); + aSect.SetProtectFlag(m_pImpl->m_pProps->m_bProtect); + aSect.SetEditInReadonlyFlag(m_pImpl->m_pProps->m_bEditInReadonly); + + SfxItemSet aSet( + pDoc->GetAttrPool(), + svl::Items< + RES_LR_SPACE, RES_LR_SPACE, + RES_BACKGROUND, RES_BACKGROUND, + RES_COL, RES_COL, + RES_FTN_AT_TXTEND, RES_FRAMEDIR, + RES_UNKNOWNATR_CONTAINER,RES_UNKNOWNATR_CONTAINER>{}); + if (m_pImpl->m_pProps->m_pBrushItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pBrushItem); + } + if (m_pImpl->m_pProps->m_pColItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pColItem); + } + if (m_pImpl->m_pProps->m_pFootnoteItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pFootnoteItem); + } + if (m_pImpl->m_pProps->m_pEndItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pEndItem); + } + if (m_pImpl->m_pProps->m_pXMLAttr) + { + aSet.Put(*m_pImpl->m_pProps->m_pXMLAttr); + } + if (m_pImpl->m_pProps->m_pNoBalanceItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pNoBalanceItem); + } + if (m_pImpl->m_pProps->m_pFrameDirItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pFrameDirItem); + } + if (m_pImpl->m_pProps->m_pLRSpaceItem) + { + aSet.Put(*m_pImpl->m_pProps->m_pLRSpaceItem); + } + // section password + if (m_pImpl->m_pProps->m_Password.hasElements()) + { + aSect.SetPassword(m_pImpl->m_pProps->m_Password); + } + + SwSection *const pRet = + pDoc->InsertSwSection( aPam, aSect, nullptr, aSet.Count() ? &aSet : nullptr ); + if (!pRet) // fdo#42450 text range could partially overlap existing section + { + // shouldn't have created an undo object yet + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSSECTION, nullptr ); + throw lang::IllegalArgumentException( + "SwXTextSection::attach(): invalid TextRange", + static_cast< ::cppu::OWeakObject*>(this), 0); + } + m_pImpl->Attach(pRet->GetFormat()); + pRet->GetFormat()->SetXObject(static_cast< ::cppu::OWeakObject*>(this)); + + // XML import must hide sections depending on their old + // condition status + if (!m_pImpl->m_pProps->m_sCondition.isEmpty()) + { + pRet->SetCondHidden(m_pImpl->m_pProps->m_bCondHidden); + } + + // set update type if DDE link (and connect, if necessary) + if (m_pImpl->m_pProps->m_bDDE) + { + if (! pRet->IsConnected()) + { + pRet->CreateLink(LinkCreateType::Connect); + } + pRet->SetUpdateType( m_pImpl->m_pProps->m_bUpdateType ? + SfxLinkUpdateMode::ALWAYS : SfxLinkUpdateMode::ONCALL ); + } + + // end the Undo bracketing here + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSSECTION, nullptr ); + m_pImpl->m_pProps.reset(); + m_pImpl->m_bIsDescriptor = false; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXTextSection::getAnchor() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + SwSectionFormat *const pSectFormat = m_pImpl->GetSectionFormat(); + if(pSectFormat) + { + const SwNodeIndex* pIdx; + if( nullptr != ( pSectFormat->GetSection() ) && + nullptr != ( pIdx = pSectFormat->GetContent().GetContentIdx() ) && + pIdx->GetNode().GetNodes().IsDocNodes() ) + { + SwPaM aPaM(*pIdx); + aPaM.Move( fnMoveForward, GoInContent ); + + const SwEndNode* pEndNode = pIdx->GetNode().EndOfSectionNode(); + SwPaM aEnd(*pEndNode); + aEnd.Move( fnMoveBackward, GoInContent ); + xRet = SwXTextRange::CreateXTextRange(*pSectFormat->GetDoc(), + *aPaM.Start(), aEnd.Start()); + } + } + return xRet; +} + +void SAL_CALL SwXTextSection::dispose() +{ + SolarMutexGuard aGuard; + + SwSectionFormat *const pSectFormat = m_pImpl->GetSectionFormat(); + if (pSectFormat) + { + pSectFormat->GetDoc()->DelSectionFormat( pSectFormat ); + } +} + +void SAL_CALL SwXTextSection::addEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.addInterface(xListener); +} + +void SAL_CALL SwXTextSection::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_EventListeners.removeInterface(xListener); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXTextSection::getPropertySetInfo() +{ + SolarMutexGuard g; + return m_pImpl->m_rPropSet.getPropertySetInfo(); +} + +static void +lcl_UpdateLinkType(SwSection & rSection, bool const bLinkUpdateAlways) +{ + if (rSection.GetType() == SectionType::DdeLink) + { + // set update type; needs an established link + if (!rSection.IsConnected()) + { + rSection.CreateLink(LinkCreateType::Connect); + } + rSection.SetUpdateType( bLinkUpdateAlways + ? SfxLinkUpdateMode::ALWAYS : SfxLinkUpdateMode::ONCALL ); + } +} + +static void +lcl_UpdateSection(SwSectionFormat *const pFormat, + std::unique_ptr<SwSectionData> const& pSectionData, + std::unique_ptr<SfxItemSet> const& pItemSet, + bool const bLinkModeChanged, bool const bLinkUpdateAlways = true) +{ + if (pFormat) + { + SwSection & rSection = *pFormat->GetSection(); + SwDoc *const pDoc = pFormat->GetDoc(); + SwSectionFormats const& rFormats = pDoc->GetSections(); + UnoActionContext aContext(pDoc); + for (size_t i = 0; i < rFormats.size(); ++i) + { + if (rFormats[i]->GetSection()->GetSectionName() + == rSection.GetSectionName()) + { + pDoc->UpdateSection(i, *pSectionData, pItemSet.get(), + pDoc->IsInReading()); + { + // temporarily remove actions to allow cursor update + // TODO: why? no table cursor here! + UnoActionRemoveContext aRemoveContext( pDoc ); + } + + if (bLinkModeChanged) + { + lcl_UpdateLinkType(rSection, bLinkUpdateAlways); + } + // section found and processed: break from loop + break; + } + } + } +} + +void SwXTextSection::Impl::SetPropertyValues_Impl( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues) +{ + if(rPropertyNames.getLength() != rValues.getLength()) + { + throw lang::IllegalArgumentException(); + } + SwSectionFormat *const pFormat = GetSectionFormat(); + if (!pFormat && !m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + + std::unique_ptr<SwSectionData> const pSectionData( + pFormat ? new SwSectionData(*pFormat->GetSection()) : nullptr); + + OUString const*const pPropertyNames = rPropertyNames.getConstArray(); + uno::Any const*const pValues = rValues.getConstArray(); + std::unique_ptr<SfxItemSet> pItemSet; + bool bLinkModeChanged = false; + bool bLinkMode = false; + + for (sal_Int32 nProperty = 0; nProperty < rPropertyNames.getLength(); + nProperty++) + { + SfxItemPropertySimpleEntry const*const pEntry = + m_rPropSet.getPropertyMap().getByName(pPropertyNames[nProperty]); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + pPropertyNames[nProperty], + static_cast<cppu::OWeakObject *>(& m_rThis)); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw beans::PropertyVetoException( + "Property is read-only: " + pPropertyNames[nProperty], + static_cast<cppu::OWeakObject *>(& m_rThis)); + } + switch (pEntry->nWID) + { + case WID_SECT_CONDITION: + { + OUString uTmp; + pValues[nProperty] >>= uTmp; + if (m_bIsDescriptor) + { + m_pProps->m_sCondition = uTmp; + } + else + { + pSectionData->SetCondition(uTmp); + } + } + break; + case WID_SECT_DDE_TYPE: + case WID_SECT_DDE_FILE: + case WID_SECT_DDE_ELEMENT: + { + OUString sTmp; + pValues[nProperty] >>= sTmp; + if (m_bIsDescriptor) + { + if (!m_pProps->m_bDDE) + { + m_pProps->m_sLinkFileName = + OUStringChar(sfx2::cTokenSeparator) + OUStringChar(sfx2::cTokenSeparator); + m_pProps->m_bDDE = true; + } + m_pProps->m_sLinkFileName = comphelper::string::setToken( + m_pProps->m_sLinkFileName, + pEntry->nWID - WID_SECT_DDE_TYPE, sfx2::cTokenSeparator, sTmp); + } + else + { + OUString sLinkFileName(pSectionData->GetLinkFileName()); + if (pSectionData->GetType() != SectionType::DdeLink) + { + sLinkFileName = OUStringChar(sfx2::cTokenSeparator) + OUStringChar(sfx2::cTokenSeparator); + pSectionData->SetType(SectionType::DdeLink); + } + sLinkFileName = comphelper::string::setToken(sLinkFileName, + pEntry->nWID - WID_SECT_DDE_TYPE, + sfx2::cTokenSeparator, sTmp); + pSectionData->SetLinkFileName(sLinkFileName); + } + } + break; + case WID_SECT_DDE_AUTOUPDATE: + { + bool bVal(false); + if (!(pValues[nProperty] >>= bVal)) + { + throw lang::IllegalArgumentException(); + } + if (m_bIsDescriptor) + { + m_pProps->m_bUpdateType = bVal; + } + else + { + bLinkModeChanged = true; + bLinkMode = bVal; + } + } + break; + case WID_SECT_LINK: + { + text::SectionFileLink aLink; + if (!(pValues[nProperty] >>= aLink)) + { + throw lang::IllegalArgumentException(); + } + if (m_bIsDescriptor) + { + m_pProps->m_bDDE = false; + m_pProps->m_sLinkFileName = aLink.FileURL; + m_pProps->m_sSectionFilter = aLink.FilterName; + } + else + { + if (pSectionData->GetType() != SectionType::FileLink && + !aLink.FileURL.isEmpty()) + { + pSectionData->SetType(SectionType::FileLink); + } + const OUString sTmp(!aLink.FileURL.isEmpty() + ? URIHelper::SmartRel2Abs( + pFormat->GetDoc()->GetDocShell()->GetMedium()->GetURLObject(), + aLink.FileURL, URIHelper::GetMaybeFileHdl()) + : OUString()); + const OUString sFileName( + sTmp + OUStringChar(sfx2::cTokenSeparator) + + aLink.FilterName + OUStringChar(sfx2::cTokenSeparator) + + pSectionData->GetLinkFileName().getToken(2, sfx2::cTokenSeparator)); + pSectionData->SetLinkFileName(sFileName); + if (sFileName.getLength() < 3) + { + pSectionData->SetType(SectionType::Content); + } + } + } + break; + case WID_SECT_REGION: + { + OUString sLink; + pValues[nProperty] >>= sLink; + if (m_bIsDescriptor) + { + m_pProps->m_bDDE = false; + m_pProps->m_sSectionRegion = sLink; + } + else + { + if (pSectionData->GetType() != SectionType::FileLink && + !sLink.isEmpty()) + { + pSectionData->SetType(SectionType::FileLink); + } + OUString sSectLink(pSectionData->GetLinkFileName()); + for (sal_Int32 i = comphelper::string::getTokenCount(sSectLink, sfx2::cTokenSeparator); + i < 3; ++i) + { + sSectLink += OUStringChar(sfx2::cTokenSeparator); + } + sSectLink = comphelper::string::setToken(sSectLink, 2, sfx2::cTokenSeparator, sLink); + pSectionData->SetLinkFileName(sSectLink); + if (sSectLink.getLength() < 3) + { + pSectionData->SetType(SectionType::Content); + } + } + } + break; + case WID_SECT_VISIBLE: + { + bool bVal(false); + if (!(pValues[nProperty] >>= bVal)) + { + throw lang::IllegalArgumentException(); + } + if (m_bIsDescriptor) + { + m_pProps->m_bHidden = !bVal; + } + else + { + pSectionData->SetHidden(!bVal); + } + } + break; + case WID_SECT_CURRENTLY_VISIBLE: + { + bool bVal(false); + if (!(pValues[nProperty] >>= bVal)) + { + throw lang::IllegalArgumentException(); + } + if (m_bIsDescriptor) + { + m_pProps->m_bCondHidden = !bVal; + } + else + { + if (!pSectionData->GetCondition().isEmpty()) + { + pSectionData->SetCondHidden(!bVal); + } + } + } + break; + case WID_SECT_PROTECTED: + { + bool bVal(false); + if (!(pValues[nProperty] >>= bVal)) + { + throw lang::IllegalArgumentException(); + } + if (m_bIsDescriptor) + { + m_pProps->m_bProtect = bVal; + } + else + { + pSectionData->SetProtectFlag(bVal); + } + } + break; + case WID_SECT_EDIT_IN_READONLY: + { + bool bVal(false); + if (!(pValues[nProperty] >>= bVal)) + { + throw lang::IllegalArgumentException(); + } + if (m_bIsDescriptor) + { + m_pProps->m_bEditInReadonly = bVal; + } + else + { + pSectionData->SetEditInReadonlyFlag(bVal); + } + } + break; + case WID_SECT_PASSWORD: + { + uno::Sequence<sal_Int8> aSeq; + pValues[nProperty] >>= aSeq; + if (m_bIsDescriptor) + { + m_pProps->m_Password = aSeq; + } + else + { + pSectionData->SetPassword(aSeq); + } + } + break; + default: + { + if (pFormat) + { + const SfxItemSet& rOldAttrSet = pFormat->GetAttrSet(); + pItemSet.reset( new SfxItemSet(*rOldAttrSet.GetPool(), {{pEntry->nWID, pEntry->nWID}})); + pItemSet->Put(rOldAttrSet); + m_rPropSet.setPropertyValue(*pEntry, + pValues[nProperty], *pItemSet); + } + else + { + SfxPoolItem* pPutItem = nullptr; + if (RES_COL == pEntry->nWID) + { + if (!m_pProps->m_pColItem) + { + m_pProps->m_pColItem.reset(new SwFormatCol); + } + pPutItem = m_pProps->m_pColItem.get(); + } + else if (RES_BACKGROUND == pEntry->nWID) + { + if (!m_pProps->m_pBrushItem) + { + m_pProps->m_pBrushItem.reset( + new SvxBrushItem(RES_BACKGROUND)); + } + pPutItem = m_pProps->m_pBrushItem.get(); + } + else if (RES_FTN_AT_TXTEND == pEntry->nWID) + { + if (!m_pProps->m_pFootnoteItem) + { + m_pProps->m_pFootnoteItem.reset(new SwFormatFootnoteAtTextEnd); + } + pPutItem = m_pProps->m_pFootnoteItem.get(); + } + else if (RES_END_AT_TXTEND == pEntry->nWID) + { + if (!m_pProps->m_pEndItem) + { + m_pProps->m_pEndItem.reset(new SwFormatEndAtTextEnd); + } + pPutItem = m_pProps->m_pEndItem.get(); + } + else if (RES_UNKNOWNATR_CONTAINER== pEntry->nWID) + { + if (!m_pProps->m_pXMLAttr) + { + m_pProps->m_pXMLAttr.reset( + new SvXMLAttrContainerItem( + RES_UNKNOWNATR_CONTAINER)); + } + pPutItem = m_pProps->m_pXMLAttr.get(); + } + else if (RES_COLUMNBALANCE== pEntry->nWID) + { + if (!m_pProps->m_pNoBalanceItem) + { + m_pProps->m_pNoBalanceItem.reset( + new SwFormatNoBalancedColumns(true)); + } + pPutItem = m_pProps->m_pNoBalanceItem.get(); + } + else if (RES_FRAMEDIR == pEntry->nWID) + { + if (!m_pProps->m_pFrameDirItem) + { + m_pProps->m_pFrameDirItem.reset( + new SvxFrameDirectionItem( + SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR)); + } + pPutItem = m_pProps->m_pFrameDirItem.get(); + } + else if (RES_LR_SPACE == pEntry->nWID) + { + if (!m_pProps->m_pLRSpaceItem) + { + m_pProps->m_pLRSpaceItem.reset( + new SvxLRSpaceItem( RES_LR_SPACE )); + } + pPutItem = m_pProps->m_pLRSpaceItem.get(); + } + if (pPutItem) + { + pPutItem->PutValue(pValues[nProperty], + pEntry->nMemberId); + } + } + } + } + } + + lcl_UpdateSection(pFormat, pSectionData, pItemSet, bLinkModeChanged, + bLinkMode); +} + +void SAL_CALL +SwXTextSection::setPropertyValues( + const uno::Sequence< OUString >& rPropertyNames, + const uno::Sequence< uno::Any >& rValues) +{ + SolarMutexGuard aGuard; + + // workaround for bad designed API + try + { + m_pImpl->SetPropertyValues_Impl( rPropertyNames, rValues ); + } + catch (const beans::UnknownPropertyException &rException) + { + // wrap the original (here not allowed) exception in + // a WrappedTargetException that gets thrown instead. + lang::WrappedTargetException aWExc; + aWExc.TargetException <<= rException; + throw aWExc; + } +} + +void SwXTextSection::setPropertyValue( + const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + uno::Sequence< OUString > aPropertyNames { rPropertyName }; + uno::Sequence< uno::Any > aValues(1); + aValues.getArray()[0] = rValue; + m_pImpl->SetPropertyValues_Impl( aPropertyNames, aValues ); +} + +uno::Sequence< uno::Any > +SwXTextSection::Impl::GetPropertyValues_Impl( + const uno::Sequence< OUString > & rPropertyNames ) +{ + SwSectionFormat *const pFormat = GetSectionFormat(); + if (!pFormat && !m_bIsDescriptor) + { + throw uno::RuntimeException( "non-descriptor section without format"); + } + + uno::Sequence< uno::Any > aRet(rPropertyNames.getLength()); + uno::Any* pRet = aRet.getArray(); + SwSection *const pSect = pFormat ? pFormat->GetSection() : nullptr; + const OUString* pPropertyNames = rPropertyNames.getConstArray(); + + for (sal_Int32 nProperty = 0; nProperty < rPropertyNames.getLength(); + nProperty++) + { + SfxItemPropertySimpleEntry const*const pEntry = + m_rPropSet.getPropertyMap().getByName(pPropertyNames[nProperty]); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + pPropertyNames[nProperty], + static_cast<cppu::OWeakObject *>(& m_rThis)); + } + switch(pEntry->nWID) + { + case WID_SECT_CONDITION: + { + const OUString uTmp( m_bIsDescriptor + ? m_pProps->m_sCondition + : pSect->GetCondition()); + pRet[nProperty] <<= uTmp; + } + break; + case WID_SECT_DDE_TYPE: + case WID_SECT_DDE_FILE: + case WID_SECT_DDE_ELEMENT: + { + OUString sRet; + if (m_bIsDescriptor) + { + if (m_pProps->m_bDDE) + { + sRet = m_pProps->m_sLinkFileName; + } + } + else if (SectionType::DdeLink == pSect->GetType()) + { + sRet = pSect->GetLinkFileName(); + } + pRet[nProperty] <<= sRet.getToken(pEntry->nWID - WID_SECT_DDE_TYPE, + sfx2::cTokenSeparator); + } + break; + case WID_SECT_DDE_AUTOUPDATE: + { + // GetUpdateType() returns .._ALWAYS or .._ONCALL + if (pSect && pSect->IsLinkType() && pSect->IsConnected()) // #i73247# + { + const bool bTemp = + (pSect->GetUpdateType() == SfxLinkUpdateMode::ALWAYS); + pRet[nProperty] <<= bTemp; + } + } + break; + case WID_SECT_LINK : + { + text::SectionFileLink aLink; + if (m_bIsDescriptor) + { + if (!m_pProps->m_bDDE) + { + aLink.FileURL = m_pProps->m_sLinkFileName; + aLink.FilterName = m_pProps->m_sSectionFilter; + } + } + else if (SectionType::FileLink == pSect->GetType()) + { + const OUString& sRet( pSect->GetLinkFileName() ); + sal_Int32 nIndex(0); + aLink.FileURL = + sRet.getToken(0, sfx2::cTokenSeparator, nIndex); + aLink.FilterName = + sRet.getToken(0, sfx2::cTokenSeparator, nIndex); + } + pRet[nProperty] <<= aLink; + } + break; + case WID_SECT_REGION : + { + OUString sRet; + if (m_bIsDescriptor) + { + sRet = m_pProps->m_sSectionRegion; + } + else if (SectionType::FileLink == pSect->GetType()) + { + sRet = pSect->GetLinkFileName().getToken(2, + sfx2::cTokenSeparator); + } + pRet[nProperty] <<= sRet; + } + break; + case WID_SECT_VISIBLE : + { + const bool bTemp = m_bIsDescriptor + ? !m_pProps->m_bHidden : !pSect->IsHidden(); + pRet[nProperty] <<= bTemp; + } + break; + case WID_SECT_CURRENTLY_VISIBLE: + { + const bool bTemp = m_bIsDescriptor + ? !m_pProps->m_bCondHidden : !pSect->IsCondHidden(); + pRet[nProperty] <<= bTemp; + } + break; + case WID_SECT_PROTECTED: + { + const bool bTemp = m_bIsDescriptor + ? m_pProps->m_bProtect : pSect->IsProtect(); + pRet[nProperty] <<= bTemp; + } + break; + case WID_SECT_EDIT_IN_READONLY: + { + const bool bTemp = m_bIsDescriptor + ? m_pProps->m_bEditInReadonly : pSect->IsEditInReadonly(); + pRet[nProperty] <<= bTemp; + } + break; + case FN_PARAM_LINK_DISPLAY_NAME: + { + if (pFormat) + { + pRet[nProperty] <<= pFormat->GetSection()->GetSectionName(); + } + } + break; + case WID_SECT_DOCUMENT_INDEX: + { + // search enclosing index + SwSection* pEnclosingSection = pSect; + while ((pEnclosingSection != nullptr) && + (SectionType::ToxContent != pEnclosingSection->GetType())) + { + pEnclosingSection = pEnclosingSection->GetParent(); + } + SwTOXBaseSection* const pTOXBaseSect = pEnclosingSection ? + dynamic_cast<SwTOXBaseSection*>( pEnclosingSection ) : nullptr; + if (pTOXBaseSect) + { + // convert section to TOXBase and get SwXDocumentIndex + const uno::Reference<text::XDocumentIndex> xIndex = + SwXDocumentIndex::CreateXDocumentIndex( + *pTOXBaseSect->GetFormat()->GetDoc(), pTOXBaseSect); + pRet[nProperty] <<= xIndex; + } + // else: no enclosing index found -> empty return value + } + break; + case WID_SECT_IS_GLOBAL_DOC_SECTION: + { + const bool bRet = pFormat && (nullptr != pFormat->GetGlobalDocSection()); + pRet[nProperty] <<= bRet; + } + break; + case FN_UNO_ANCHOR_TYPES: + case FN_UNO_TEXT_WRAP: + case FN_UNO_ANCHOR_TYPE: + ::sw::GetDefaultTextContentValue( + pRet[nProperty], OUString(), pEntry->nWID); + break; + case FN_UNO_REDLINE_NODE_START: + case FN_UNO_REDLINE_NODE_END: + { + if (!pFormat) + break; // #i73247# + SwNode* pSectNode = pFormat->GetSectionNode(); + if (FN_UNO_REDLINE_NODE_END == pEntry->nWID) + { + pSectNode = pSectNode->EndOfSectionNode(); + } + const SwRedlineTable& rRedTable = + pFormat->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRangeRedline* pRedline : rRedTable) + { + const SwNode& rRedPointNode = pRedline->GetNode(); + const SwNode& rRedMarkNode = pRedline->GetNode(false); + if ((&rRedPointNode == pSectNode) || + (&rRedMarkNode == pSectNode)) + { + const SwNode& rStartOfRedline = + (SwNodeIndex(rRedPointNode) <= + SwNodeIndex(rRedMarkNode)) + ? rRedPointNode : rRedMarkNode; + const bool bIsStart = (&rStartOfRedline == pSectNode); + pRet[nProperty] <<= + SwXRedlinePortion::CreateRedlineProperties( + *pRedline, bIsStart); + break; + } + } + } + break; + case WID_SECT_PASSWORD: + { + pRet[nProperty] <<= m_bIsDescriptor + ? m_pProps->m_Password : pSect->GetPassword(); + } + break; + default: + { + if (pFormat) + { + m_rPropSet.getPropertyValue(*pEntry, + pFormat->GetAttrSet(), pRet[nProperty]); + } + else + { + const SfxPoolItem* pQueryItem = nullptr; + if (RES_COL == pEntry->nWID) + { + if (!m_pProps->m_pColItem) + { + m_pProps->m_pColItem.reset(new SwFormatCol); + } + pQueryItem = m_pProps->m_pColItem.get(); + } + else if (RES_BACKGROUND == pEntry->nWID) + { + if (!m_pProps->m_pBrushItem) + { + m_pProps->m_pBrushItem.reset( + new SvxBrushItem(RES_BACKGROUND)); + } + pQueryItem = m_pProps->m_pBrushItem.get(); + } + else if (RES_FTN_AT_TXTEND == pEntry->nWID) + { + if (!m_pProps->m_pFootnoteItem) + { + m_pProps->m_pFootnoteItem.reset(new SwFormatFootnoteAtTextEnd); + } + pQueryItem = m_pProps->m_pFootnoteItem.get(); + } + else if (RES_END_AT_TXTEND == pEntry->nWID) + { + if (!m_pProps->m_pEndItem) + { + m_pProps->m_pEndItem.reset(new SwFormatEndAtTextEnd); + } + pQueryItem = m_pProps->m_pEndItem.get(); + } + else if (RES_UNKNOWNATR_CONTAINER== pEntry->nWID) + { + if (!m_pProps->m_pXMLAttr) + { + m_pProps->m_pXMLAttr.reset( + new SvXMLAttrContainerItem); + } + pQueryItem = m_pProps->m_pXMLAttr.get(); + } + else if (RES_COLUMNBALANCE== pEntry->nWID) + { + if (!m_pProps->m_pNoBalanceItem) + { + m_pProps->m_pNoBalanceItem.reset( + new SwFormatNoBalancedColumns); + } + pQueryItem = m_pProps->m_pNoBalanceItem.get(); + } + else if (RES_FRAMEDIR == pEntry->nWID) + { + if (!m_pProps->m_pFrameDirItem) + { + m_pProps->m_pFrameDirItem.reset( + new SvxFrameDirectionItem( + SvxFrameDirection::Environment, RES_FRAMEDIR)); + } + pQueryItem = m_pProps->m_pFrameDirItem.get(); + } + else if (RES_LR_SPACE == pEntry->nWID) + { + if (!m_pProps->m_pLRSpaceItem) + { + m_pProps->m_pLRSpaceItem.reset( + new SvxLRSpaceItem( RES_LR_SPACE )); + } + pQueryItem = m_pProps->m_pLRSpaceItem.get(); + } + if (pQueryItem) + { + pQueryItem->QueryValue(pRet[nProperty], + pEntry->nMemberId); + } + } + } + } + } + return aRet; +} + +uno::Sequence< uno::Any > SAL_CALL +SwXTextSection::getPropertyValues( + const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + uno::Sequence< uno::Any > aValues; + + // workaround for bad designed API + try + { + aValues = m_pImpl->GetPropertyValues_Impl( rPropertyNames ); + } + catch (beans::UnknownPropertyException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("Unknown property exception caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + catch (lang::WrappedTargetException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("WrappedTargetException caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + + return aValues; +} + +uno::Any SAL_CALL +SwXTextSection::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Sequence< OUString > aPropertyNames { rPropertyName }; + return m_pImpl->GetPropertyValues_Impl(aPropertyNames).getConstArray()[0]; +} + +void SAL_CALL SwXTextSection::addPropertiesChangeListener( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ + OSL_FAIL("SwXTextSection::addPropertiesChangeListener(): not implemented"); +} + +void SAL_CALL SwXTextSection::removePropertiesChangeListener( + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ + OSL_FAIL("SwXTextSection::removePropertiesChangeListener(): not implemented"); +} + +void SAL_CALL SwXTextSection::firePropertiesChangeEvent( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ + OSL_FAIL("SwXTextSection::firePropertiesChangeEvent(): not implemented"); +} + +void SAL_CALL +SwXTextSection::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextSection::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextSection::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextSection::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextSection::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextSection::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXTextSection::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXTextSection::removeVetoableChangeListener(): not implemented"); +} + +beans::PropertyState SAL_CALL +SwXTextSection::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Sequence< OUString > aNames { rPropertyName }; + return getPropertyStates(aNames).getConstArray()[0]; +} + +uno::Sequence< beans::PropertyState > SAL_CALL +SwXTextSection::getPropertyStates( + const uno::Sequence< OUString >& rPropertyNames) +{ + SolarMutexGuard aGuard; + + SwSectionFormat *const pFormat = m_pImpl->GetSectionFormat(); + if (!pFormat && !m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + + uno::Sequence< beans::PropertyState > aStates(rPropertyNames.getLength()); + beans::PropertyState *const pStates = aStates.getArray(); + const OUString* pNames = rPropertyNames.getConstArray(); + for (sal_Int32 i = 0; i < rPropertyNames.getLength(); i++) + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName( pNames[i]); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + pNames[i], + static_cast< cppu::OWeakObject* >(this)); + } + switch (pEntry->nWID) + { + case WID_SECT_CONDITION: + case WID_SECT_DDE_TYPE: + case WID_SECT_DDE_FILE: + case WID_SECT_DDE_ELEMENT: + case WID_SECT_DDE_AUTOUPDATE: + case WID_SECT_LINK: + case WID_SECT_REGION : + case WID_SECT_VISIBLE: + case WID_SECT_PROTECTED: + case WID_SECT_EDIT_IN_READONLY: + case FN_PARAM_LINK_DISPLAY_NAME: + case FN_UNO_ANCHOR_TYPES: + case FN_UNO_TEXT_WRAP: + case FN_UNO_ANCHOR_TYPE: + pStates[i] = beans::PropertyState_DIRECT_VALUE; + break; + default: + { + if (pFormat) + { + pStates[i] = m_pImpl->m_rPropSet.getPropertyState( + pNames[i], pFormat->GetAttrSet()); + } + else + { + if (RES_COL == pEntry->nWID) + { + if (!m_pImpl->m_pProps->m_pColItem) + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + } + else + { + if (!m_pImpl->m_pProps->m_pBrushItem) + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + } + } + } + } + } + return aStates; +} + +void SAL_CALL +SwXTextSection::setPropertyToDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + SwSectionFormat *const pFormat = m_pImpl->GetSectionFormat(); + if (!pFormat && !m_pImpl->m_bIsDescriptor) + { + throw uno::RuntimeException(); + } + + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast< cppu::OWeakObject* >(this)); + } + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + { + throw uno::RuntimeException( + "Property is read-only: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + std::unique_ptr<SwSectionData> const pSectionData( + pFormat ? new SwSectionData(*pFormat->GetSection()) : nullptr); + + std::unique_ptr<SfxItemSet> pNewAttrSet; + bool bLinkModeChanged = false; + + switch (pEntry->nWID) + { + case WID_SECT_CONDITION: + { + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->m_sCondition.clear(); + } + else + { + pSectionData->SetCondition(OUString()); + } + } + break; + case WID_SECT_DDE_TYPE : + case WID_SECT_DDE_FILE : + case WID_SECT_DDE_ELEMENT : + case WID_SECT_LINK : + case WID_SECT_REGION : + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->m_bDDE = false; + m_pImpl->m_pProps->m_sLinkFileName.clear(); + m_pImpl->m_pProps->m_sSectionRegion.clear(); + m_pImpl->m_pProps->m_sSectionFilter.clear(); + } + else + { + pSectionData->SetType(SectionType::Content); + } + break; + case WID_SECT_DDE_AUTOUPDATE: + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->m_bUpdateType = true; + } + else + { + bLinkModeChanged = true; + } + break; + case WID_SECT_VISIBLE : + { + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->m_bHidden = false; + } + else + { + pSectionData->SetHidden(false); + } + } + break; + case WID_SECT_PROTECTED: + { + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->m_bProtect = false; + } + else + { + pSectionData->SetProtectFlag(false); + } + } + break; + case WID_SECT_EDIT_IN_READONLY: + { + if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_pProps->m_bEditInReadonly = false; + } + else + { + pSectionData->SetEditInReadonlyFlag(false); + } + } + break; + + case FN_UNO_ANCHOR_TYPES: + case FN_UNO_TEXT_WRAP: + case FN_UNO_ANCHOR_TYPE: + break; + default: + { + if (SfxItemPool::IsWhich(pEntry->nWID)) + { + if (pFormat) + { + const SfxItemSet& rOldAttrSet = pFormat->GetAttrSet(); + pNewAttrSet.reset( new SfxItemSet(*rOldAttrSet.GetPool(), {{pEntry->nWID, pEntry->nWID}})); + pNewAttrSet->ClearItem(pEntry->nWID); + } + else + { + if (RES_COL == pEntry->nWID) + { + m_pImpl->m_pProps->m_pColItem.reset(); + } + else if (RES_BACKGROUND == pEntry->nWID) + { + m_pImpl->m_pProps->m_pBrushItem.reset(); + } + } + } + } + } + + lcl_UpdateSection(pFormat, pSectionData, pNewAttrSet, bLinkModeChanged); +} + +uno::Any SAL_CALL +SwXTextSection::getPropertyDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Any aRet; + SwSectionFormat *const pFormat = m_pImpl->GetSectionFormat(); + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + throw beans::UnknownPropertyException( + "Unknown property: " + rPropertyName, + static_cast<cppu::OWeakObject *>(this)); + } + + switch(pEntry->nWID) + { + case WID_SECT_CONDITION: + case WID_SECT_DDE_TYPE : + case WID_SECT_DDE_FILE : + case WID_SECT_DDE_ELEMENT : + case WID_SECT_REGION : + case FN_PARAM_LINK_DISPLAY_NAME: + aRet <<= OUString(); + break; + case WID_SECT_LINK : + aRet <<= text::SectionFileLink(); + break; + case WID_SECT_DDE_AUTOUPDATE: + case WID_SECT_VISIBLE : + aRet <<= true; + break; + case WID_SECT_PROTECTED: + case WID_SECT_EDIT_IN_READONLY: + aRet <<= false; + break; + case FN_UNO_ANCHOR_TYPES: + case FN_UNO_TEXT_WRAP: + case FN_UNO_ANCHOR_TYPE: + ::sw::GetDefaultTextContentValue(aRet, OUString(), pEntry->nWID); + break; + default: + if(pFormat && SfxItemPool::IsWhich(pEntry->nWID)) + { + SwDoc *const pDoc = pFormat->GetDoc(); + const SfxPoolItem& rDefItem = + pDoc->GetAttrPool().GetDefaultItem(pEntry->nWID); + rDefItem.QueryValue(aRet, pEntry->nMemberId); + } + } + return aRet; +} + +OUString SAL_CALL SwXTextSection::getName() +{ + SolarMutexGuard aGuard; + + OUString sRet; + SwSectionFormat const*const pFormat = m_pImpl->GetSectionFormat(); + if(pFormat) + { + sRet = pFormat->GetSection()->GetSectionName(); + } + else if (m_pImpl->m_bIsDescriptor) + { + sRet = m_pImpl->m_sName; + } + else + { + throw uno::RuntimeException(); + } + return sRet; +} + +void SAL_CALL SwXTextSection::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + + SwSectionFormat *const pFormat = m_pImpl->GetSectionFormat(); + if(pFormat) + { + SwSection *const pSect = pFormat->GetSection(); + SwSectionData aSection(*pSect); + aSection.SetSectionName(rName); + + const SwSectionFormats& rFormats = pFormat->GetDoc()->GetSections(); + size_t nApplyPos = SIZE_MAX; + for( size_t i = 0; i < rFormats.size(); ++i ) + { + if(rFormats[i]->GetSection() == pSect) + { + nApplyPos = i; + } + else if (rName == rFormats[i]->GetSection()->GetSectionName()) + { + throw uno::RuntimeException(); + } + } + if (nApplyPos != SIZE_MAX) + { + { + UnoActionContext aContext(pFormat->GetDoc()); + pFormat->GetDoc()->UpdateSection(nApplyPos, aSection); + } + { + // temporarily remove actions to allow cursor update + // TODO: why? no table cursor here! + UnoActionRemoveContext aRemoveContext( pFormat->GetDoc() ); + } + } + } + else if (m_pImpl->m_bIsDescriptor) + { + m_pImpl->m_sName = rName; + } + else + { + throw uno::RuntimeException(); + } +} + +OUString SAL_CALL +SwXTextSection::getImplementationName() +{ + return "SwXTextSection"; +} + +sal_Bool SAL_CALL SwXTextSection::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXTextSection::getSupportedServiceNames() +{ + return { + "com.sun.star.text.TextContent", + "com.sun.star.text.TextSection", + "com.sun.star.document.LinkTarget" + }; +} + +// MetadatableMixin +::sfx2::Metadatable* SwXTextSection::GetCoreObject() +{ + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + return pSectionFormat; +} + +uno::Reference<frame::XModel> SwXTextSection::GetModel() +{ + SwSectionFormat *const pSectionFormat( m_pImpl->GetSectionFormat() ); + if (pSectionFormat) + { + SwDocShell const*const pShell( pSectionFormat->GetDoc()->GetDocShell() ); + return pShell ? pShell->GetModel() : nullptr; + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unosett.cxx b/sw/source/core/unocore/unosett.cxx new file mode 100644 index 000000000..81f1a6a2e --- /dev/null +++ b/sw/source/core/unocore/unosett.cxx @@ -0,0 +1,2446 @@ +/* -*- 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 <editeng/editids.hrc> +#include <swtypes.hxx> +#include <unomid.h> +#include <hintids.hxx> +#include <strings.hrc> +#include <poolfmt.hxx> +#include <fmtcol.hxx> +#include <unomap.hxx> +//#include <unostyle.hxx> +#include <unosett.hxx> +#include <unoprnms.hxx> +#include <ftninfo.hxx> +#include <doc.hxx> +#include <pagedesc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <charfmt.hxx> +#include <lineinfo.hxx> +#include <docsh.hxx> +#include <docary.hxx> +#include <docstyle.hxx> +#include <fmtclds.hxx> +#include <editeng/brushitem.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/FootnoteNumbering.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/style/LineNumberPosition.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <o3tl/any.hxx> +#include <o3tl/enumarray.hxx> +#include <vcl/font.hxx> +#include <editeng/flstitem.hxx> +#include <vcl/metric.hxx> +#include <vcl/graph.hxx> +#include <vcl/GraphicLoader.hxx> +#include <sfx2/docfile.hxx> +#include <svtools/ctrltool.hxx> +#include <vcl/svapp.hxx> +#include <editeng/unofdesc.hxx> +#include <fmtornt.hxx> +#include <SwStyleNameMapper.hxx> +#include <com/sun/star/text/PositionAndSpaceMode.hpp> +#include <com/sun/star/text/LabelFollow.hpp> +#include <numrule.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/propertyvalue.hxx> +#include <svl/itemprop.hxx> +#include <svl/listener.hxx> +#include <paratr.hxx> +#include <sal/log.hxx> +#include <numeric> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::style; + + +namespace +{ + SvtBroadcaster& GetPageDescNotifier(SwDoc* pDoc) + { + return pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier(); + } +} +// Constants for the css::text::ColumnSeparatorStyle +#define API_COL_LINE_NONE 0 +#define API_COL_LINE_SOLID 1 +#define API_COL_LINE_DOTTED 2 +#define API_COL_LINE_DASHED 3 + +#define WID_PREFIX 0 +#define WID_SUFFIX 1 +#define WID_NUMBERING_TYPE 2 +#define WID_START_AT 3 +#define WID_FOOTNOTE_COUNTING 4 +#define WID_PARAGRAPH_STYLE 5 +#define WID_PAGE_STYLE 6 +#define WID_CHARACTER_STYLE 7 +#define WID_POSITION_END_OF_DOC 8 +#define WID_END_NOTICE 9 +#define WID_BEGIN_NOTICE 10 +#define WID_ANCHOR_CHARACTER_STYLE 11 +#define WID_NUM_ON 12 +#define WID_SEPARATOR_INTERVAL 13 +#define WID_NUMBER_POSITION 14 +#define WID_DISTANCE 15 +#define WID_INTERVAL 16 +#define WID_SEPARATOR_TEXT 17 +#define WID_COUNT_EMPTY_LINES 18 +#define WID_COUNT_LINES_IN_FRAMES 19 +#define WID_RESTART_AT_EACH_PAGE 20 + + +static const SfxItemPropertySet* GetFootnoteSet() +{ + static const SfxItemPropertyMapEntry aFootnoteMap_Impl[] = + { + { OUString(UNO_NAME_ANCHOR_CHAR_STYLE_NAME),WID_ANCHOR_CHARACTER_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_BEGIN_NOTICE), WID_BEGIN_NOTICE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_STYLE_NAME), WID_CHARACTER_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_END_NOTICE), WID_END_NOTICE , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_FOOTNOTE_COUNTING), WID_FOOTNOTE_COUNTING, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_NUMBERING_TYPE), WID_NUMBERING_TYPE, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PAGE_STYLE_NAME), WID_PAGE_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PARA_STYLE_NAME), WID_PARAGRAPH_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_POSITION_END_OF_DOC), WID_POSITION_END_OF_DOC,cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PREFIX), WID_PREFIX, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_START_AT), WID_START_AT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SUFFIX), WID_SUFFIX, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aFootnoteSet_Impl(aFootnoteMap_Impl); + return &aFootnoteSet_Impl; +} + +static const SfxItemPropertySet* GetEndnoteSet() +{ + static const SfxItemPropertyMapEntry aEndnoteMap_Impl[] = + { + { OUString(UNO_NAME_ANCHOR_CHAR_STYLE_NAME),WID_ANCHOR_CHARACTER_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_CHAR_STYLE_NAME), WID_CHARACTER_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_NUMBERING_TYPE), WID_NUMBERING_TYPE, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PAGE_STYLE_NAME), WID_PAGE_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PARA_STYLE_NAME), WID_PARAGRAPH_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_PREFIX), WID_PREFIX, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_START_AT), WID_START_AT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SUFFIX), WID_SUFFIX, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aEndnoteSet_Impl(aEndnoteMap_Impl); + return &aEndnoteSet_Impl; +} + +static const SfxItemPropertySet* GetNumberingRulesSet() +{ + static const SfxItemPropertyMapEntry aNumberingRulesMap_Impl[] = + { + { OUString(UNO_NAME_IS_ABSOLUTE_MARGINS), WID_IS_ABS_MARGINS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_AUTOMATIC), WID_IS_AUTOMATIC, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_CONTINUOUS_NUMBERING), WID_CONTINUOUS, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_NAME), WID_RULE_NAME , ::cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(UNO_NAME_NUMBERING_IS_OUTLINE), WID_IS_OUTLINE, cppu::UnoType<bool>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DEFAULT_LIST_ID), WID_DEFAULT_LIST_ID, ::cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aNumberingRulesSet_Impl( aNumberingRulesMap_Impl ); + return &aNumberingRulesSet_Impl; +} + +static const SfxItemPropertySet* GetLineNumberingSet() +{ + static const SfxItemPropertyMapEntry aLineNumberingMap_Impl[] = + { + { OUString(UNO_NAME_CHAR_STYLE_NAME), WID_CHARACTER_STYLE, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_COUNT_EMPTY_LINES), WID_COUNT_EMPTY_LINES , cppu::UnoType<bool>::get(),PROPERTY_NONE, 0}, + { OUString(UNO_NAME_COUNT_LINES_IN_FRAMES), WID_COUNT_LINES_IN_FRAMES, cppu::UnoType<bool>::get(),PROPERTY_NONE, 0}, + { OUString(UNO_NAME_DISTANCE), WID_DISTANCE , ::cppu::UnoType<sal_Int32>::get(),PROPERTY_NONE, 0}, + { OUString(UNO_NAME_IS_ON), WID_NUM_ON, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_INTERVAL), WID_INTERVAL , ::cppu::UnoType<sal_Int16>::get(),PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEPARATOR_TEXT), WID_SEPARATOR_TEXT, ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + { OUString(UNO_NAME_NUMBER_POSITION), WID_NUMBER_POSITION, ::cppu::UnoType<sal_Int16>::get(),PROPERTY_NONE, 0}, + { OUString(UNO_NAME_NUMBERING_TYPE), WID_NUMBERING_TYPE , ::cppu::UnoType<sal_Int16>::get(),PROPERTY_NONE, 0}, + { OUString(UNO_NAME_RESTART_AT_EACH_PAGE), WID_RESTART_AT_EACH_PAGE, cppu::UnoType<bool>::get() , PROPERTY_NONE, 0}, + { OUString(UNO_NAME_SEPARATOR_INTERVAL), WID_SEPARATOR_INTERVAL, ::cppu::UnoType<sal_Int16>::get(),PROPERTY_NONE, 0}, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aLineNumberingSet_Impl(aLineNumberingMap_Impl); + return &aLineNumberingSet_Impl; +} + +static SwCharFormat* lcl_getCharFormat(SwDoc* pDoc, const uno::Any& aValue) +{ + SwCharFormat* pRet = nullptr; + OUString uTmp; + aValue >>= uTmp; + OUString sCharFormat; + SwStyleNameMapper::FillUIName(uTmp, sCharFormat, SwGetPoolIdFromName::ChrFmt); + if (sCharFormat != SwResId(STR_POOLCHR_STANDARD)) + { + pRet = pDoc->FindCharFormatByName( sCharFormat ); + } + if(!pRet) + { + const sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName(sCharFormat, SwGetPoolIdFromName::ChrFmt); + if(USHRT_MAX != nId) + pRet = pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( nId ); + } + return pRet; +} + +static SwTextFormatColl* lcl_GetParaStyle(SwDoc* pDoc, const uno::Any& aValue) +{ + OUString uTmp; + aValue >>= uTmp; + OUString sParaStyle; + SwStyleNameMapper::FillUIName(uTmp, sParaStyle, SwGetPoolIdFromName::TxtColl ); + SwTextFormatColl* pRet = pDoc->FindTextFormatCollByName( sParaStyle ); + if( !pRet ) + { + const sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName( sParaStyle, SwGetPoolIdFromName::TxtColl ); + if( USHRT_MAX != nId ) + pRet = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nId ); + } + return pRet; +} + +static SwPageDesc* lcl_GetPageDesc(SwDoc* pDoc, const uno::Any& aValue) +{ + OUString uTmp; + aValue >>= uTmp; + OUString sPageDesc; + SwStyleNameMapper::FillUIName(uTmp, sPageDesc, SwGetPoolIdFromName::PageDesc ); + SwPageDesc* pRet = pDoc->FindPageDesc( sPageDesc ); + if(!pRet) + { + const sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName(sPageDesc, SwGetPoolIdFromName::PageDesc); + if(USHRT_MAX != nId) + pRet = pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( nId ); + } + return pRet; +} + +// Numbering +const o3tl::enumarray<SvxAdjust, unsigned short> aSvxToUnoAdjust +{ + text::HoriOrientation::LEFT, //3 + text::HoriOrientation::RIGHT, //1 + USHRT_MAX, + text::HoriOrientation::CENTER, //2 + USHRT_MAX, + USHRT_MAX +}; + +const unsigned short aUnoToSvxAdjust[] = +{ + USHRT_MAX, + static_cast<unsigned short>(SvxAdjust::Right), // 1 + static_cast<unsigned short>(SvxAdjust::Center), // 3 + static_cast<unsigned short>(SvxAdjust::Left), // 0 + USHRT_MAX, + USHRT_MAX +}; + +OUString SwXFootnoteProperties::getImplementationName() +{ + return "SwXFootnoteProperties"; +} + +sal_Bool SwXFootnoteProperties::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXFootnoteProperties::getSupportedServiceNames() +{ + Sequence<OUString> aRet { "com.sun.star.text.FootnoteSettings" }; + return aRet; +} + +SwXFootnoteProperties::SwXFootnoteProperties(SwDoc* pDc) : + m_pDoc(pDc), + m_pPropertySet(GetFootnoteSet()) +{ +} + +SwXFootnoteProperties::~SwXFootnoteProperties() +{ + +} + +uno::Reference< beans::XPropertySetInfo > SwXFootnoteProperties::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropertySet->getPropertySetInfo(); + return aRef; +} + +void SwXFootnoteProperties::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + if(!m_pDoc) + throw uno::RuntimeException(); + + const SfxItemPropertySimpleEntry* pEntry = m_pPropertySet->getPropertyMap().getByName( rPropertyName ); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if ( pEntry->nFlags & PropertyAttribute::READONLY) + throw PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + SwFootnoteInfo aFootnoteInfo(m_pDoc->GetFootnoteInfo()); + switch(pEntry->nWID) + { + case WID_PREFIX: + { + OUString uTmp; + aValue >>= uTmp; + aFootnoteInfo.SetPrefix(uTmp); + } + break; + case WID_SUFFIX: + { + OUString uTmp; + aValue >>= uTmp; + aFootnoteInfo.SetSuffix(uTmp); + } + break; + case WID_NUMBERING_TYPE: + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + if(!(nTmp >= 0 && + (nTmp <= SVX_NUM_ARABIC || + nTmp > SVX_NUM_BITMAP))) + throw lang::IllegalArgumentException(); + + aFootnoteInfo.m_aFormat.SetNumberingType(static_cast<SvxNumType>(nTmp)); + + } + break; + case WID_START_AT: + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + aFootnoteInfo.m_nFootnoteOffset = nTmp; + } + break; + case WID_FOOTNOTE_COUNTING: + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + switch(nTmp) + { + case FootnoteNumbering::PER_PAGE: + aFootnoteInfo.m_eNum = FTNNUM_PAGE; + break; + case FootnoteNumbering::PER_CHAPTER: + aFootnoteInfo.m_eNum = FTNNUM_CHAPTER; + break; + case FootnoteNumbering::PER_DOCUMENT: + aFootnoteInfo.m_eNum = FTNNUM_DOC; + break; + } + } + break; + case WID_PARAGRAPH_STYLE: + { + SwTextFormatColl* pColl = lcl_GetParaStyle(m_pDoc, aValue); + if(pColl) + aFootnoteInfo.SetFootnoteTextColl(*pColl); + } + break; + case WID_PAGE_STYLE: + { + SwPageDesc* pDesc = lcl_GetPageDesc(m_pDoc, aValue); + if(pDesc) + aFootnoteInfo.ChgPageDesc( pDesc ); + } + break; + case WID_ANCHOR_CHARACTER_STYLE: + case WID_CHARACTER_STYLE: + { + SwCharFormat* pFormat = lcl_getCharFormat(m_pDoc, aValue); + if(pFormat) + { + if(pEntry->nWID == WID_ANCHOR_CHARACTER_STYLE) + aFootnoteInfo.SetAnchorCharFormat(pFormat); + else + aFootnoteInfo.SetCharFormat(pFormat); + } + } + break; + case WID_POSITION_END_OF_DOC: + { + bool bVal = *o3tl::doAccess<bool>(aValue); + aFootnoteInfo.m_ePos = bVal ? FTNPOS_CHAPTER : FTNPOS_PAGE; + } + break; + case WID_END_NOTICE: + { + OUString uTmp; + aValue >>= uTmp; + aFootnoteInfo.m_aQuoVadis = uTmp; + } + break; + case WID_BEGIN_NOTICE: + { + OUString uTmp; + aValue >>= uTmp; + aFootnoteInfo.m_aErgoSum = uTmp; + } + break; + } + m_pDoc->SetFootnoteInfo(aFootnoteInfo); + + +} + +uno::Any SwXFootnoteProperties::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(!m_pDoc) + throw uno::RuntimeException(); + + const SfxItemPropertySimpleEntry* pEntry = m_pPropertySet->getPropertyMap().getByName( rPropertyName ); + if(!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const SwFootnoteInfo& rFootnoteInfo = m_pDoc->GetFootnoteInfo(); + switch(pEntry->nWID) + { + case WID_PREFIX: + { + aRet <<= rFootnoteInfo.GetPrefix(); + } + break; + case WID_SUFFIX: + { + aRet <<= rFootnoteInfo.GetSuffix(); + } + break; + case WID_NUMBERING_TYPE : + { + aRet <<= static_cast<sal_Int16>(rFootnoteInfo.m_aFormat.GetNumberingType()); + } + break; + case WID_START_AT: + aRet <<= static_cast<sal_Int16>(rFootnoteInfo.m_nFootnoteOffset); + break; + case WID_FOOTNOTE_COUNTING : + { + sal_Int16 nRet = 0; + switch(rFootnoteInfo.m_eNum) + { + case FTNNUM_PAGE: + nRet = FootnoteNumbering::PER_PAGE; + break; + case FTNNUM_CHAPTER: + nRet = FootnoteNumbering::PER_CHAPTER; + break; + case FTNNUM_DOC: + nRet = FootnoteNumbering::PER_DOCUMENT; + break; + } + aRet <<= nRet; + } + break; + case WID_PARAGRAPH_STYLE : + { + SwTextFormatColl* pColl = rFootnoteInfo.GetFootnoteTextColl(); + OUString aString; + if(pColl) + aString = pColl->GetName(); + SwStyleNameMapper::FillProgName(aString, aString, SwGetPoolIdFromName::TxtColl); + aRet <<= aString; + } + break; + case WID_PAGE_STYLE : + { + OUString aString; + if( rFootnoteInfo.KnowsPageDesc() ) + { + SwStyleNameMapper::FillProgName( + rFootnoteInfo.GetPageDesc( *m_pDoc )->GetName(), + aString, + SwGetPoolIdFromName::PageDesc); + } + aRet <<= aString; + } + break; + case WID_ANCHOR_CHARACTER_STYLE: + case WID_CHARACTER_STYLE: + { + OUString aString; + const SwCharFormat* pCharFormat = rFootnoteInfo.GetCurrentCharFormat(pEntry->nWID == WID_ANCHOR_CHARACTER_STYLE); + if( pCharFormat ) + { + SwStyleNameMapper::FillProgName( + pCharFormat->GetName(), + aString, + SwGetPoolIdFromName::ChrFmt); + } + aRet <<= aString; + } + break; + case WID_POSITION_END_OF_DOC: + aRet <<= FTNPOS_CHAPTER == rFootnoteInfo.m_ePos; + break; + case WID_END_NOTICE : + aRet <<= rFootnoteInfo.m_aQuoVadis; + break; + case WID_BEGIN_NOTICE : + aRet <<= rFootnoteInfo.m_aErgoSum; + break; + } + + + return aRet; +} + +void SwXFootnoteProperties::addPropertyChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFootnoteProperties::removePropertyChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFootnoteProperties::addVetoableChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXFootnoteProperties::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +OUString SwXEndnoteProperties::getImplementationName() +{ + return "SwXEndnoteProperties"; +} + +sal_Bool SwXEndnoteProperties::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXEndnoteProperties::getSupportedServiceNames() +{ + Sequence<OUString> aRet { "com.sun.star.text.FootnoteSettings" }; + return aRet; +} + +SwXEndnoteProperties::SwXEndnoteProperties(SwDoc* pDc) : + m_pDoc(pDc), + m_pPropertySet(GetEndnoteSet()) +{ +} + +SwXEndnoteProperties::~SwXEndnoteProperties() +{ +} + +uno::Reference< beans::XPropertySetInfo > SwXEndnoteProperties::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropertySet->getPropertySetInfo(); + return aRef; +} + +void SwXEndnoteProperties::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + if(m_pDoc) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropertySet->getPropertyMap().getByName( rPropertyName ); + if(!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if ( pEntry->nFlags & PropertyAttribute::READONLY) + throw PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + SwEndNoteInfo aEndInfo(m_pDoc->GetEndNoteInfo()); + switch(pEntry->nWID) + { + case WID_PREFIX: + { + OUString uTmp; + aValue >>= uTmp; + aEndInfo.SetPrefix(uTmp); + } + break; + case WID_SUFFIX: + { + OUString uTmp; + aValue >>= uTmp; + aEndInfo.SetSuffix(uTmp); + } + break; + case WID_NUMBERING_TYPE : + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + aEndInfo.m_aFormat.SetNumberingType(static_cast<SvxNumType>(nTmp)); + } + break; + case WID_START_AT: + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + aEndInfo.m_nFootnoteOffset = nTmp; + } + break; + case WID_PARAGRAPH_STYLE : + { + SwTextFormatColl* pColl = lcl_GetParaStyle(m_pDoc, aValue); + if(pColl) + aEndInfo.SetFootnoteTextColl(*pColl); + } + break; + case WID_PAGE_STYLE : + { + SwPageDesc* pDesc = lcl_GetPageDesc(m_pDoc, aValue); + if(pDesc) + aEndInfo.ChgPageDesc( pDesc ); + } + break; + case WID_ANCHOR_CHARACTER_STYLE: + case WID_CHARACTER_STYLE : + { + SwCharFormat* pFormat = lcl_getCharFormat(m_pDoc, aValue); + if(pFormat) + { + if(pEntry->nWID == WID_ANCHOR_CHARACTER_STYLE) + aEndInfo.SetAnchorCharFormat(pFormat); + else + aEndInfo.SetCharFormat(pFormat); + } + } + break; + } + m_pDoc->SetEndNoteInfo(aEndInfo); + + } +} + +uno::Any SwXEndnoteProperties::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(m_pDoc) + { + const SfxItemPropertySimpleEntry* pEntry = m_pPropertySet->getPropertyMap().getByName( rPropertyName ); + if(!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const SwEndNoteInfo& rEndInfo = m_pDoc->GetEndNoteInfo(); + switch(pEntry->nWID) + { + case WID_PREFIX: + aRet <<= rEndInfo.GetPrefix(); + break; + case WID_SUFFIX: + aRet <<= rEndInfo.GetSuffix(); + break; + case WID_NUMBERING_TYPE : + aRet <<= static_cast<sal_Int16>(rEndInfo.m_aFormat.GetNumberingType()); + break; + case WID_START_AT: + aRet <<= static_cast<sal_Int16>(rEndInfo.m_nFootnoteOffset); + break; + case WID_PARAGRAPH_STYLE : + { + SwTextFormatColl* pColl = rEndInfo.GetFootnoteTextColl(); + OUString aString; + if(pColl) + aString = pColl->GetName(); + SwStyleNameMapper::FillProgName( + aString, + aString, + SwGetPoolIdFromName::TxtColl); + aRet <<= aString; + + } + break; + case WID_PAGE_STYLE : + { + OUString aString; + if( rEndInfo.KnowsPageDesc() ) + { + SwStyleNameMapper::FillProgName( + rEndInfo.GetPageDesc( *m_pDoc )->GetName(), + aString, + SwGetPoolIdFromName::PageDesc); + } + aRet <<= aString; + } + break; + case WID_ANCHOR_CHARACTER_STYLE: + case WID_CHARACTER_STYLE: + { + OUString aString; + const SwCharFormat* pCharFormat = rEndInfo.GetCurrentCharFormat( pEntry->nWID == WID_ANCHOR_CHARACTER_STYLE ); + if( pCharFormat ) + { + SwStyleNameMapper::FillProgName( + pCharFormat->GetName(), + aString, + SwGetPoolIdFromName::ChrFmt); + } + aRet <<= aString; + } + break; + } + + } + return aRet; +} + +void SwXEndnoteProperties::addPropertyChangeListener( + const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXEndnoteProperties::removePropertyChangeListener(const OUString& /*PropertyName*/, + const uno:: Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXEndnoteProperties::addVetoableChangeListener(const OUString& /*PropertyName*/, + const uno:: Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXEndnoteProperties::removeVetoableChangeListener(const OUString& /*PropertyName*/, const uno:: Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +OUString SwXLineNumberingProperties::getImplementationName() +{ + return "SwXLineNumberingProperties"; +} + +sal_Bool SwXLineNumberingProperties::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXLineNumberingProperties::getSupportedServiceNames() +{ + Sequence<OUString> aRet { "com.sun.star.text.LineNumberingProperties" }; + return aRet; +} + +SwXLineNumberingProperties::SwXLineNumberingProperties(SwDoc* pDc) : + m_pDoc(pDc), + m_pPropertySet(GetLineNumberingSet()) +{ +} + +SwXLineNumberingProperties::~SwXLineNumberingProperties() +{ +} + +uno::Reference< beans::XPropertySetInfo > SwXLineNumberingProperties::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropertySet->getPropertySetInfo(); + return aRef; +} + +void SwXLineNumberingProperties::setPropertyValue( + const OUString& rPropertyName, const Any& aValue) +{ + SolarMutexGuard aGuard; + if(!m_pDoc) + throw uno::RuntimeException(); + + const SfxItemPropertySimpleEntry* pEntry = m_pPropertySet->getPropertyMap().getByName( rPropertyName ); + if(!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if ( pEntry->nFlags & PropertyAttribute::READONLY) + throw PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + SwLineNumberInfo aFontMetric(m_pDoc->GetLineNumberInfo()); + switch(pEntry->nWID) + { + case WID_NUM_ON: + { + bool bVal = *o3tl::doAccess<bool>(aValue); + aFontMetric.SetPaintLineNumbers(bVal); + } + break; + case WID_CHARACTER_STYLE : + { + SwCharFormat* pFormat = lcl_getCharFormat(m_pDoc, aValue); + if(pFormat) + aFontMetric.SetCharFormat(pFormat); + } + break; + case WID_NUMBERING_TYPE : + { + SvxNumberType aNumType(aFontMetric.GetNumType()); + sal_Int16 nTmp = 0; + aValue >>= nTmp; + aNumType.SetNumberingType(static_cast<SvxNumType>(nTmp)); + aFontMetric.SetNumType(aNumType); + } + break; + case WID_NUMBER_POSITION : + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + switch(nTmp) + { + case style::LineNumberPosition::LEFT: + aFontMetric.SetPos(LINENUMBER_POS_LEFT); + break; + case style::LineNumberPosition::RIGHT : + aFontMetric.SetPos(LINENUMBER_POS_RIGHT); + break; + case style::LineNumberPosition::INSIDE: + aFontMetric.SetPos(LINENUMBER_POS_INSIDE); + break; + case style::LineNumberPosition::OUTSIDE: + aFontMetric.SetPos(LINENUMBER_POS_OUTSIDE); + break; + } + } + break; + case WID_DISTANCE : + { + sal_Int32 nVal = 0; + aValue >>= nVal; + sal_Int32 nTmp = convertMm100ToTwip(nVal); + if (nTmp > SAL_MAX_UINT16) + nTmp = SAL_MAX_UINT16; + aFontMetric.SetPosFromLeft( static_cast< sal_uInt16 >(nTmp) ); + } + break; + case WID_INTERVAL : + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + if( nTmp > 0) + aFontMetric.SetCountBy(nTmp); + } + break; + case WID_SEPARATOR_TEXT : + { + OUString uTmp; + aValue >>= uTmp; + aFontMetric.SetDivider(uTmp); + } + break; + case WID_SEPARATOR_INTERVAL: + { + sal_Int16 nTmp = 0; + aValue >>= nTmp; + if( nTmp >= 0) + aFontMetric.SetDividerCountBy(nTmp); + } + break; + case WID_COUNT_EMPTY_LINES : + { + bool bVal = *o3tl::doAccess<bool>(aValue); + aFontMetric.SetCountBlankLines(bVal); + } + break; + case WID_COUNT_LINES_IN_FRAMES : + { + bool bVal = *o3tl::doAccess<bool>(aValue); + aFontMetric.SetCountInFlys(bVal); + } + break; + case WID_RESTART_AT_EACH_PAGE : + { + bool bVal = *o3tl::doAccess<bool>(aValue); + aFontMetric.SetRestartEachPage(bVal); + } + break; + } + m_pDoc->SetLineNumberInfo(aFontMetric); +} + +Any SwXLineNumberingProperties::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + Any aRet; + if(!m_pDoc) + throw uno::RuntimeException(); + + const SfxItemPropertySimpleEntry* pEntry = m_pPropertySet->getPropertyMap().getByName( rPropertyName ); + if(!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + const SwLineNumberInfo& rInfo = m_pDoc->GetLineNumberInfo(); + switch(pEntry->nWID) + { + case WID_NUM_ON: + aRet <<= rInfo.IsPaintLineNumbers(); + break; + case WID_CHARACTER_STYLE : + { + OUString aString; + // return empty string if no char format is set + // otherwise it would be created here + if(rInfo.HasCharFormat()) + { + SwStyleNameMapper::FillProgName( + rInfo.GetCharFormat(m_pDoc->getIDocumentStylePoolAccess())->GetName(), + aString, + SwGetPoolIdFromName::ChrFmt); + } + aRet <<= aString; + } + break; + case WID_NUMBERING_TYPE : + aRet <<= static_cast<sal_Int16>(rInfo.GetNumType().GetNumberingType()); + break; + case WID_NUMBER_POSITION : + { + sal_Int16 nRet = 0; + switch(rInfo.GetPos()) + { + case LINENUMBER_POS_LEFT: + nRet = style::LineNumberPosition::LEFT; + break; + case LINENUMBER_POS_RIGHT : + nRet = style::LineNumberPosition::RIGHT ; + break; + case LINENUMBER_POS_INSIDE: + nRet = style::LineNumberPosition::INSIDE ; + break; + case LINENUMBER_POS_OUTSIDE : + nRet = style::LineNumberPosition::OUTSIDE ; + break; + } + aRet <<= nRet; + } + break; + case WID_DISTANCE : + { + sal_uInt32 nPos = rInfo.GetPosFromLeft(); + if(USHRT_MAX == nPos) + nPos = 0; + aRet <<= static_cast < sal_Int32 >(convertTwipToMm100(nPos)); + } + break; + case WID_INTERVAL : + aRet <<= static_cast<sal_Int16>(rInfo.GetCountBy()); + break; + case WID_SEPARATOR_TEXT : + aRet <<= rInfo.GetDivider(); + break; + case WID_SEPARATOR_INTERVAL: + aRet <<= static_cast<sal_Int16>(rInfo.GetDividerCountBy()); + break; + case WID_COUNT_EMPTY_LINES : + aRet <<= rInfo.IsCountBlankLines(); + break; + case WID_COUNT_LINES_IN_FRAMES : + aRet <<= rInfo.IsCountInFlys(); + break; + case WID_RESTART_AT_EACH_PAGE : + aRet <<= rInfo.IsRestartEachPage(); + break; + } + return aRet; +} + +void SwXLineNumberingProperties::addPropertyChangeListener(const OUString& /*rPropertyName*/, const uno:: Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ +OSL_FAIL("not implemented"); +} + +void SwXLineNumberingProperties::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno:: Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ +OSL_FAIL("not implemented"); +} + +void SwXLineNumberingProperties::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno:: Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ +OSL_FAIL("not implemented"); +} + +void SwXLineNumberingProperties::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno:: Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ +OSL_FAIL("not implemented"); +} + +static const char aInvalidStyle[] = "__XXX___invalid"; + +class SwXNumberingRules::Impl + : public SvtListener +{ + SwXNumberingRules& m_rParent; + virtual void Notify(const SfxHint&) override; + public: + explicit Impl(SwXNumberingRules& rParent) : m_rParent(rParent) {} +}; + +bool SwXNumberingRules::isInvalidStyle(const OUString &rName) +{ + return rName == aInvalidStyle; +} + +namespace +{ + class theSwXNumberingRulesUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXNumberingRulesUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXNumberingRules::getUnoTunnelId() +{ + return theSwXNumberingRulesUnoTunnelId::get().getSeq(); +} + +// return implementation specific data +sal_Int64 SwXNumberingRules::getSomething( const uno::Sequence< sal_Int8 > & rId ) +{ + if( isUnoTunnelId<SwXNumberingRules>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + +OUString SwXNumberingRules::getImplementationName() +{ + return "SwXNumberingRules"; +} + +sal_Bool SwXNumberingRules::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXNumberingRules::getSupportedServiceNames() +{ + Sequence<OUString> aRet { "com.sun.star.text.NumberingRules" }; + return aRet; +} + +SwXNumberingRules::SwXNumberingRules(const SwNumRule& rRule, SwDoc* doc) : + m_pImpl(new SwXNumberingRules::Impl(*this)), + m_pDoc(doc), + m_pDocShell(nullptr), + m_pNumRule(new SwNumRule(rRule)), + m_pPropertySet(GetNumberingRulesSet()), + m_bOwnNumRuleCreated(true) +{ + // first organize the document - it is dependent on the set character formats + // if no format is set, it should work as well + for( sal_uInt16 i = 0; i < MAXLEVEL; ++i) + { + SwNumFormat rFormat(m_pNumRule->Get(i)); + SwCharFormat* pCharFormat = rFormat.GetCharFormat(); + if(pCharFormat) + { + m_pDoc = pCharFormat->GetDoc(); + break; + } + } + if(m_pDoc) + m_pImpl->StartListening(GetPageDescNotifier(m_pDoc)); + for(sal_uInt16 i = 0; i < MAXLEVEL; ++i) + { + m_sNewCharStyleNames[i] = aInvalidStyle; + m_sNewBulletFontNames[i] = aInvalidStyle; + } +} + +SwXNumberingRules::SwXNumberingRules(SwDocShell& rDocSh) : + m_pImpl(new SwXNumberingRules::Impl(*this)), + m_pDoc(nullptr), + m_pDocShell(&rDocSh), + m_pNumRule(nullptr), + m_pPropertySet(GetNumberingRulesSet()), + m_bOwnNumRuleCreated(false) +{ + m_pImpl->StartListening(GetPageDescNotifier(m_pDocShell->GetDoc())); +} + +SwXNumberingRules::SwXNumberingRules(SwDoc& rDoc) : + m_pImpl(new SwXNumberingRules::Impl(*this)), + m_pDoc(&rDoc), + m_pDocShell(nullptr), + m_pNumRule(nullptr), + m_pPropertySet(GetNumberingRulesSet()), + m_bOwnNumRuleCreated(false) +{ + m_pImpl->StartListening(GetPageDescNotifier(&rDoc)); + m_sCreatedNumRuleName = rDoc.GetUniqueNumRuleName(); + rDoc.MakeNumRule( m_sCreatedNumRuleName, nullptr, false, + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode() ); +} + +SwXNumberingRules::~SwXNumberingRules() +{ + SolarMutexGuard aGuard; + if(m_pDoc && !m_sCreatedNumRuleName.isEmpty()) + m_pDoc->DelNumRule( m_sCreatedNumRuleName ); + if( m_bOwnNumRuleCreated ) + delete m_pNumRule; +} + +void SwXNumberingRules::replaceByIndex(sal_Int32 nIndex, const uno::Any& rElement) +{ + SolarMutexGuard aGuard; + if(nIndex < 0 || MAXLEVEL <= nIndex) + throw lang::IndexOutOfBoundsException(); + + auto rProperties = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>( + rElement); + if(!rProperties) + throw lang::IllegalArgumentException(); + SwNumRule* pRule = nullptr; + if(m_pNumRule) + SwXNumberingRules::SetNumberingRuleByIndex( *m_pNumRule, + *rProperties, nIndex); + else if(m_pDocShell) + { + // #i87650# - correction of cws warnings: + SwNumRule aNumRule( *(m_pDocShell->GetDoc()->GetOutlineNumRule()) ); + SwXNumberingRules::SetNumberingRuleByIndex( aNumRule, + *rProperties, nIndex); + // set character format if needed + const SwCharFormats* pFormats = m_pDocShell->GetDoc()->GetCharFormats(); + const size_t nChCount = pFormats->size(); + for(sal_uInt16 i = 0; i < MAXLEVEL;i++) + { + SwNumFormat aFormat(aNumRule.Get( i )); + if (!m_sNewCharStyleNames[i].isEmpty() && + m_sNewCharStyleNames[i] != UNO_NAME_CHARACTER_FORMAT_NONE && + (!aFormat.GetCharFormat() || aFormat.GetCharFormat()->GetName()!= m_sNewCharStyleNames[i])) + { + SwCharFormat* pCharFormat = nullptr; + for(size_t j = 0; j< nChCount; ++j) + { + SwCharFormat* pTmp = (*pFormats)[j]; + if(pTmp->GetName() == m_sNewCharStyleNames[i]) + { + pCharFormat = pTmp; + break; + } + } + if(!pCharFormat) + { + SfxStyleSheetBase* pBase; + pBase = m_pDocShell->GetStyleSheetPool()->Find(m_sNewCharStyleNames[i], + SfxStyleFamily::Char); + if(!pBase) + pBase = &m_pDocShell->GetStyleSheetPool()->Make(m_sNewCharStyleNames[i], SfxStyleFamily::Char); + pCharFormat = static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat(); + + } + aFormat.SetCharFormat( pCharFormat ); + aNumRule.Set( i, aFormat ); + } + } + m_pDocShell->GetDoc()->SetOutlineNumRule( aNumRule ); + } + else if(m_pDoc && !m_sCreatedNumRuleName.isEmpty() && + nullptr != (pRule = m_pDoc->FindNumRulePtr( m_sCreatedNumRuleName ))) + { + SwXNumberingRules::SetNumberingRuleByIndex( *pRule, + *rProperties, nIndex); + + pRule->Validate(); + } + else + throw uno::RuntimeException(); +} + +sal_Int32 SwXNumberingRules::getCount() +{ + return MAXLEVEL; +} + +uno::Any SwXNumberingRules::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(nIndex < 0 || MAXLEVEL <= nIndex) + throw lang::IndexOutOfBoundsException(); + + uno::Any aVal; + const SwNumRule* pRule = m_pNumRule; + if(!pRule && m_pDoc && !m_sCreatedNumRuleName.isEmpty()) + pRule = m_pDoc->FindNumRulePtr( m_sCreatedNumRuleName ); + if(pRule) + { + uno::Sequence<beans::PropertyValue> aRet = GetNumberingRuleByIndex( + *pRule, nIndex); + aVal <<= aRet; + + } + else if(m_pDocShell) + { + uno::Sequence<beans::PropertyValue> aRet = GetNumberingRuleByIndex( + *m_pDocShell->GetDoc()->GetOutlineNumRule(), nIndex); + aVal <<= aRet; + } + else + throw uno::RuntimeException(); + return aVal; +} + +uno::Type SwXNumberingRules::getElementType() +{ + return cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get(); +} + +sal_Bool SwXNumberingRules::hasElements() +{ + return true; +} + +static const char* STR_POOLCOLL_HEADLINE_ARY[] +{ + STR_POOLCOLL_HEADLINE1, + STR_POOLCOLL_HEADLINE2, + STR_POOLCOLL_HEADLINE3, + STR_POOLCOLL_HEADLINE4, + STR_POOLCOLL_HEADLINE5, + STR_POOLCOLL_HEADLINE6, + STR_POOLCOLL_HEADLINE7, + STR_POOLCOLL_HEADLINE8, + STR_POOLCOLL_HEADLINE9, + STR_POOLCOLL_HEADLINE10 +}; + +uno::Sequence<beans::PropertyValue> SwXNumberingRules::GetNumberingRuleByIndex( + const SwNumRule& rNumRule, sal_Int32 nIndex) const +{ + SolarMutexGuard aGuard; + OSL_ENSURE( 0 <= nIndex && nIndex < MAXLEVEL, "index out of range" ); + + const SwNumFormat& rFormat = rNumRule.Get( static_cast<sal_uInt16>(nIndex) ); + + SwCharFormat* pCharFormat = rFormat.GetCharFormat(); + OUString CharStyleName; + if (pCharFormat) + CharStyleName = pCharFormat->GetName(); + + // Whether or not a style is present: the array entry overwrites this string + if (!m_sNewCharStyleNames[nIndex].isEmpty() && + !SwXNumberingRules::isInvalidStyle(m_sNewCharStyleNames[nIndex])) + { + CharStyleName = m_sNewCharStyleNames[nIndex]; + } + + OUString aUString; + if (m_pDocShell) // -> Chapter Numbering + { + // template name + OUString sValue(SwResId(STR_POOLCOLL_HEADLINE_ARY[nIndex])); + const SwTextFormatColls* pColls = m_pDocShell->GetDoc()->GetTextFormatColls(); + const size_t nCount = pColls->size(); + for(size_t i = 0; i < nCount; ++i) + { + SwTextFormatColl &rTextColl = *pColls->operator[](i); + if(rTextColl.IsDefault()) + continue; + + const sal_Int16 nOutLevel = rTextColl.IsAssignedToListLevelOfOutlineStyle() + ? static_cast<sal_Int16>(rTextColl.GetAssignedOutlineStyleLevel()) + : MAXLEVEL; + if ( nOutLevel == nIndex ) + { + sValue = rTextColl.GetName(); + break; // the style for the level in question has been found + } + else if( sValue==rTextColl.GetName() ) + { + // if the default for the level is existing, but its + // level is different, then it cannot be the default. + sValue.clear(); + } + } + SwStyleNameMapper::FillProgName(sValue, aUString, SwGetPoolIdFromName::TxtColl); + } + + OUString referer; + if (m_pDoc != nullptr) { + auto const sh = m_pDoc->GetPersist(); + if (sh != nullptr && sh->HasName()) { + referer = sh->GetMedium()->GetName(); + } + } + return GetPropertiesForNumFormat( + rFormat, CharStyleName, m_pDocShell ? & aUString : nullptr, referer); + +} + +uno::Sequence<beans::PropertyValue> SwXNumberingRules::GetPropertiesForNumFormat( + const SwNumFormat& rFormat, OUString const& rCharFormatName, + OUString const*const pHeadingStyleName, OUString const & referer) +{ + bool bChapterNum = pHeadingStyleName != nullptr; + + std::vector<PropertyValue> aPropertyValues; + aPropertyValues.reserve(32); + //fill all properties into the array + + //adjust + SvxAdjust eAdj = rFormat.GetNumAdjust(); + sal_Int16 nINT16 = aSvxToUnoAdjust[eAdj]; + aPropertyValues.push_back(comphelper::makePropertyValue("Adjust", nINT16)); + + //parentnumbering + nINT16 = rFormat.GetIncludeUpperLevels(); + aPropertyValues.push_back(comphelper::makePropertyValue("ParentNumbering", nINT16)); + + //prefix + OUString aUString = rFormat.GetPrefix(); + aPropertyValues.push_back(comphelper::makePropertyValue("Prefix", aUString)); + + //suffix + aUString = rFormat.GetSuffix(); + aPropertyValues.push_back(comphelper::makePropertyValue("Suffix", aUString)); + + //listformat + if (rFormat.HasListFormat()) + { + aPropertyValues.push_back(comphelper::makePropertyValue("ListFormat", rFormat.GetListFormat())); + } + + //char style name + aUString.clear(); + SwStyleNameMapper::FillProgName( rCharFormatName, aUString, SwGetPoolIdFromName::ChrFmt); + aPropertyValues.push_back(comphelper::makePropertyValue("CharStyleName", aUString)); + + //startvalue + nINT16 = rFormat.GetStart(); + aPropertyValues.push_back(comphelper::makePropertyValue("StartWith", nINT16)); + + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + //leftmargin + sal_Int32 nINT32 = convertTwipToMm100(rFormat.GetAbsLSpace()); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_LEFT_MARGIN, nINT32)); + + //chartextoffset + nINT32 = convertTwipToMm100(rFormat.GetCharTextDistance()); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_SYMBOL_TEXT_DISTANCE, nINT32)); + + //firstlineoffset + nINT32 = convertTwipToMm100(rFormat.GetFirstLineOffset()); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_FIRST_LINE_OFFSET, nINT32)); + } + + // PositionAndSpaceMode + nINT16 = PositionAndSpaceMode::LABEL_WIDTH_AND_POSITION; + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + nINT16 = PositionAndSpaceMode::LABEL_ALIGNMENT; + } + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_POSITION_AND_SPACE_MODE, nINT16)); + + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + // LabelFollowedBy + nINT16 = LabelFollow::LISTTAB; + if ( rFormat.GetLabelFollowedBy() == SvxNumberFormat::SPACE ) + { + nINT16 = LabelFollow::SPACE; + } + else if ( rFormat.GetLabelFollowedBy() == SvxNumberFormat::NOTHING ) + { + nINT16 = LabelFollow::NOTHING; + } + else if ( rFormat.GetLabelFollowedBy() == SvxNumberFormat::NEWLINE ) + { + nINT16 = LabelFollow::NEWLINE; + } + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_LABEL_FOLLOWED_BY, nINT16)); + + // ListtabStopPosition + sal_Int32 nINT32 = convertTwipToMm100(rFormat.GetListtabPos()); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_LISTTAB_STOP_POSITION, nINT32)); + + // FirstLineIndent + nINT32 = convertTwipToMm100(rFormat.GetFirstLineIndent()); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_FIRST_LINE_INDENT, nINT32)); + + // IndentAt + nINT32 = convertTwipToMm100(rFormat.GetIndentAt()); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_INDENT_AT, nINT32)); + } + + //numberingtype + nINT16 = rFormat.GetNumberingType(); + aPropertyValues.push_back(comphelper::makePropertyValue("NumberingType", nINT16)); + + if(!bChapterNum) + { + if(SVX_NUM_CHAR_SPECIAL == rFormat.GetNumberingType()) + { + //BulletId + nINT16 = rFormat.GetBulletChar(); + aPropertyValues.push_back(comphelper::makePropertyValue("BulletId", nINT16)); + + const vcl::Font* pFont = rFormat.GetBulletFont(); + + //BulletChar + aUString = OUString(rFormat.GetBulletChar()); + aPropertyValues.push_back(comphelper::makePropertyValue("BulletChar", aUString)); + + //BulletFontName + aUString = pFont ? pFont->GetStyleName() : OUString(); + aPropertyValues.push_back(comphelper::makePropertyValue("BulletFontName", aUString)); + + //BulletFont + if(pFont) + { + awt::FontDescriptor aDesc; + SvxUnoFontDescriptor::ConvertFromFont( *pFont, aDesc ); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_BULLET_FONT, aDesc)); + } + } + if (SVX_NUM_BITMAP == rFormat.GetNumberingType()) + { + const SvxBrushItem* pBrush = rFormat.GetBrush(); + const Graphic* pGraphic = pBrush ? pBrush->GetGraphic(referer) : nullptr; + if (pGraphic) + { + //GraphicBitmap + uno::Reference<awt::XBitmap> xBitmap(pGraphic->GetXGraphic(), uno::UNO_QUERY); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_GRAPHIC_BITMAP, xBitmap)); + } + + Size aSize = rFormat.GetGraphicSize(); + // #i101131# + // adjust conversion due to type mismatch between <Size> and <awt::Size> + awt::Size aAwtSize(convertTwipToMm100(aSize.Width()), convertTwipToMm100(aSize.Height())); + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_GRAPHIC_SIZE, aAwtSize)); + + const SwFormatVertOrient* pOrient = rFormat.GetGraphicOrientation(); + if(pOrient) + { + uno::Any any; + pOrient->QueryValue(any); + aPropertyValues.emplace_back( + UNO_NAME_VERT_ORIENT, -1, any, PropertyState_DIRECT_VALUE); + } + } + } + else + { + aUString = *pHeadingStyleName; + aPropertyValues.push_back(comphelper::makePropertyValue(UNO_NAME_HEADING_STYLE_NAME, aUString)); + } + + return ::comphelper::containerToSequence(aPropertyValues); +} + +void SwXNumberingRules::SetNumberingRuleByIndex( + SwNumRule& rNumRule, + const uno::Sequence<beans::PropertyValue>& rProperties, sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + OSL_ENSURE( 0 <= nIndex && nIndex < MAXLEVEL, "index out of range" ); + + SwNumFormat aFormat(rNumRule.Get( static_cast<sal_uInt16>(nIndex) )); + + OUString sHeadingStyleName; + OUString sParagraphStyleName; + + SetPropertiesToNumFormat(aFormat, m_sNewCharStyleNames[nIndex], + &m_sNewBulletFontNames[nIndex], + &sHeadingStyleName, &sParagraphStyleName, + m_pDoc, rProperties); + + + if (m_pDoc && !sParagraphStyleName.isEmpty()) + { + const SwTextFormatColls* pColls = m_pDoc->GetTextFormatColls(); + const size_t nCount = pColls->size(); + for (size_t k = 0; k < nCount; ++k) + { + SwTextFormatColl &rTextColl = *((*pColls)[k]); + if (rTextColl.GetName() == sParagraphStyleName) + rTextColl.SetFormatAttr( SwNumRuleItem( rNumRule.GetName())); + } + } + + if (!sHeadingStyleName.isEmpty()) + { + assert(m_pDocShell); + const SwTextFormatColls* pColls = m_pDocShell->GetDoc()->GetTextFormatColls(); + const size_t nCount = pColls->size(); + for (size_t k = 0; k < nCount; ++k) + { + SwTextFormatColl &rTextColl = *((*pColls)[k]); + if (rTextColl.IsDefault()) + continue; + if (rTextColl.IsAssignedToListLevelOfOutlineStyle() && + rTextColl.GetAssignedOutlineStyleLevel() == nIndex && + rTextColl.GetName() != sHeadingStyleName) + { + rTextColl.DeleteAssignmentToListLevelOfOutlineStyle(); + } + else if (rTextColl.GetName() == sHeadingStyleName) + { + rTextColl.AssignToListLevelOfOutlineStyle( nIndex ); + } + } + } + + rNumRule.Set(static_cast<sal_uInt16>(nIndex), aFormat); +} + +void SwXNumberingRules::SetPropertiesToNumFormat( + SwNumFormat & aFormat, + OUString & rCharStyleName, OUString *const pBulletFontName, + OUString *const pHeadingStyleName, + OUString *const pParagraphStyleName, + SwDoc *const pDoc, + const uno::Sequence<beans::PropertyValue>& rProperties) +{ + bool bWrongArg = false; + std::unique_ptr<SvxBrushItem> pSetBrush; + std::unique_ptr<Size> pSetSize; + std::unique_ptr<SwFormatVertOrient> pSetVOrient; + bool bCharStyleNameSet = false; + + for (const beans::PropertyValue& rProp : rProperties) + { + if (rProp.Name == UNO_NAME_ADJUST) + { + sal_Int16 nValue = text::HoriOrientation::NONE; + rProp.Value >>= nValue; + if (nValue > text::HoriOrientation::NONE && + nValue <= text::HoriOrientation::LEFT && + USHRT_MAX != aUnoToSvxAdjust[nValue]) + { + aFormat.SetNumAdjust(static_cast<SvxAdjust>(aUnoToSvxAdjust[nValue])); + } + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_PARENT_NUMBERING) + { + sal_Int16 nSet = 0; + rProp.Value >>= nSet; + if(nSet >= 0 && MAXLEVEL >= nSet) + aFormat.SetIncludeUpperLevels( static_cast< sal_uInt8 >(nSet) ); + } + else if (rProp.Name == UNO_NAME_PREFIX) + { + OUString uTmp; + rProp.Value >>= uTmp; + aFormat.SetPrefix(uTmp); + } + else if (rProp.Name == UNO_NAME_SUFFIX) + { + OUString uTmp; + rProp.Value >>= uTmp; + aFormat.SetSuffix(uTmp); + } + else if (rProp.Name == UNO_NAME_CHAR_STYLE_NAME) + { + bCharStyleNameSet = true; + OUString uTmp; + rProp.Value >>= uTmp; + OUString sCharFormatName; + SwStyleNameMapper::FillUIName( uTmp, sCharFormatName, SwGetPoolIdFromName::ChrFmt ); + if (sCharFormatName == UNO_NAME_CHARACTER_FORMAT_NONE) + { + rCharStyleName = aInvalidStyle; + aFormat.SetCharFormat(nullptr); + } + else if(pDoc) + { + const SwCharFormats* pFormats = pDoc->GetCharFormats(); + const size_t nChCount = pFormats->size(); + + SwCharFormat* pCharFormat = nullptr; + if (!sCharFormatName.isEmpty()) + { + for(size_t j = 0; j< nChCount; ++j) + { + SwCharFormat* pTmp = (*pFormats)[j]; + if(pTmp->GetName() == sCharFormatName) + { + pCharFormat = pTmp; + break; + } + } + if(!pCharFormat) + { + + SfxStyleSheetBase* pBase; + SfxStyleSheetBasePool* pPool = pDoc->GetDocShell()->GetStyleSheetPool(); + pBase = pPool->Find(sCharFormatName, SfxStyleFamily::Char); + if(!pBase) + pBase = &pPool->Make(sCharFormatName, SfxStyleFamily::Char); + pCharFormat = static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat(); + } + } + aFormat.SetCharFormat( pCharFormat ); + // #i51842# + // If the character format has been found its name should not be in the + // char style names array + rCharStyleName.clear(); + } + else + rCharStyleName = sCharFormatName; + } + else if (rProp.Name == UNO_NAME_START_WITH) + { + sal_Int16 nVal = 0; + rProp.Value >>= nVal; + aFormat.SetStart(nVal); + } + else if (rProp.Name == UNO_NAME_LEFT_MARGIN) + { + sal_Int32 nValue = 0; + rProp.Value >>= nValue; + // #i23727# nValue can be negative + aFormat.SetAbsLSpace(convertMm100ToTwip(nValue)); + } + else if (rProp.Name == UNO_NAME_SYMBOL_TEXT_DISTANCE) + { + sal_Int32 nValue = 0; + rProp.Value >>= nValue; + if (nValue >= 0) + aFormat.SetCharTextDistance(static_cast<short>(convertMm100ToTwip(nValue))); + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_FIRST_LINE_OFFSET) + { + sal_Int32 nValue = 0; + rProp.Value >>= nValue; + // #i23727# nValue can be positive + nValue = convertMm100ToTwip(nValue); + aFormat.SetFirstLineOffset(nValue); + } + else if (rProp.Name == UNO_NAME_POSITION_AND_SPACE_MODE) + { + sal_Int16 nValue = 0; + rProp.Value >>= nValue; + if ( nValue == 0 ) + { + aFormat.SetPositionAndSpaceMode( SvxNumberFormat::LABEL_WIDTH_AND_POSITION ); + } + else if ( nValue == 1 ) + { + aFormat.SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + } + else + { + bWrongArg = true; + } + } + else if (rProp.Name == UNO_NAME_LABEL_FOLLOWED_BY) + { + sal_Int16 nValue = 0; + rProp.Value >>= nValue; + if ( nValue == LabelFollow::LISTTAB ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + } + else if ( nValue == LabelFollow::SPACE ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::SPACE ); + } + else if ( nValue == LabelFollow::NOTHING ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::NOTHING ); + } + else if ( nValue == LabelFollow::NEWLINE ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::NEWLINE ); + } + else + { + bWrongArg = true; + } + } + else if (rProp.Name == UNO_NAME_LISTTAB_STOP_POSITION) + { + sal_Int32 nValue = 0; + rProp.Value >>= nValue; + nValue = convertMm100ToTwip(nValue); + if ( nValue >= 0 ) + { + aFormat.SetListtabPos( nValue ); + } + else + { + bWrongArg = true; + } + } + else if (rProp.Name == UNO_NAME_FIRST_LINE_INDENT) + { + sal_Int32 nValue = 0; + rProp.Value >>= nValue; + nValue = convertMm100ToTwip(nValue); + aFormat.SetFirstLineIndent( nValue ); + } + else if (rProp.Name == UNO_NAME_INDENT_AT) + { + sal_Int32 nValue = 0; + rProp.Value >>= nValue; + nValue = convertMm100ToTwip(nValue); + aFormat.SetIndentAt( nValue ); + } + else if (rProp.Name == UNO_NAME_NUMBERING_TYPE) + { + sal_Int16 nSet = 0; + rProp.Value >>= nSet; + if(nSet >= 0) + aFormat.SetNumberingType(static_cast<SvxNumType>(nSet)); + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_PARAGRAPH_STYLE_NAME) + { + if (pParagraphStyleName) + { + OUString uTmp; + rProp.Value >>= uTmp; + OUString sStyleName; + SwStyleNameMapper::FillUIName(uTmp, sStyleName, SwGetPoolIdFromName::TxtColl ); + *pParagraphStyleName = sStyleName; + } + } + else if (rProp.Name == UNO_NAME_BULLET_ID) + { + sal_Int16 nSet = 0; + if( rProp.Value >>= nSet ) + aFormat.SetBulletChar(nSet); + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_BULLET_FONT) + { + awt::FontDescriptor desc; + if (rProp.Value >>= desc) + { + // #i93725# + // do not accept "empty" font + if (!desc.Name.isEmpty()) + { + vcl::Font aFont; + SvxUnoFontDescriptor::ConvertToFont(desc, aFont); + aFormat.SetBulletFont(&aFont); + } + } + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_BULLET_FONT_NAME) + { + OUString sBulletFontName; + rProp.Value >>= sBulletFontName; + SwDocShell* pLclDocShell = nullptr; + if( !sBulletFontName.isEmpty() && pDoc && (pLclDocShell = pDoc->GetDocShell()) ) + { + const SvxFontListItem* pFontListItem = + static_cast<const SvxFontListItem* >(pLclDocShell + ->GetItem( SID_ATTR_CHAR_FONTLIST )); + const FontList* pList = pFontListItem->GetFontList(); + FontMetric aFontMetric = pList->Get( + sBulletFontName, WEIGHT_NORMAL, ITALIC_NONE); + vcl::Font aFont(aFontMetric); + aFormat.SetBulletFont(&aFont); + } + else if (pBulletFontName) + *pBulletFontName = sBulletFontName; + } + else if (rProp.Name == UNO_NAME_BULLET_CHAR) + { + OUString aChar; + rProp.Value >>= aChar; + if(aChar.getLength() == 1) + { + aFormat.SetBulletChar(aChar.toChar()); + } + else if(aChar.isEmpty()) + { + // If w:lvlText's value is null - set bullet char to zero + aFormat.SetBulletChar(u'\0'); + } + else + { + bWrongArg = true; + } + } + else if (rProp.Name == UNO_NAME_GRAPHIC) + { + uno::Reference<graphic::XGraphic> xGraphic; + if (rProp.Value >>= xGraphic) + { + if (!pSetBrush) + { + const SvxBrushItem* pOrigBrush = aFormat.GetBrush(); + if(pOrigBrush) + pSetBrush.reset(new SvxBrushItem(*pOrigBrush)); + else + pSetBrush.reset(new SvxBrushItem(OUString(), OUString(), GPOS_AREA, RES_BACKGROUND)); + } + Graphic aGraphic(xGraphic); + pSetBrush->SetGraphic(aGraphic); + } + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_GRAPHIC_BITMAP) + { + uno::Reference<awt::XBitmap> xBitmap; + if (rProp.Value >>= xBitmap) + { + if(!pSetBrush) + { + const SvxBrushItem* pOrigBrush = aFormat.GetBrush(); + if(pOrigBrush) + pSetBrush.reset(new SvxBrushItem(*pOrigBrush)); + else + pSetBrush.reset(new SvxBrushItem(OUString(), OUString(), GPOS_AREA, RES_BACKGROUND)); + } + + uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY); + Graphic aGraphic(xGraphic); + pSetBrush->SetGraphic(aGraphic); + } + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_GRAPHIC_SIZE) + { + if(!pSetSize) + pSetSize.reset(new Size); + awt::Size size; + if (rProp.Value >>= size) + { + size.Width = convertMm100ToTwip(size.Width); + size.Height = convertMm100ToTwip(size.Height); + pSetSize->setWidth( size.Width ); + pSetSize->setHeight( size.Height ); + } + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_VERT_ORIENT) + { + if(!pSetVOrient) + { + if(aFormat.GetGraphicOrientation()) + pSetVOrient.reset(aFormat.GetGraphicOrientation()->Clone()); + else + pSetVOrient.reset(new SwFormatVertOrient); + } + pSetVOrient->PutValue(rProp.Value, MID_VERTORIENT_ORIENT); + } + else if (rProp.Name == UNO_NAME_HEADING_STYLE_NAME) + { + if (pHeadingStyleName) + { + OUString uTmp; + rProp.Value >>= uTmp; + OUString sStyleName; + SwStyleNameMapper::FillUIName(uTmp, sStyleName, SwGetPoolIdFromName::TxtColl ); + *pHeadingStyleName = sStyleName; + } + } + else if (rProp.Name == UNO_NAME_BULLET_REL_SIZE) + { + // BulletRelSize - unsupported - only available in Impress + } + else if (rProp.Name == UNO_NAME_BULLET_COLOR) + { + // BulletColor - ignored too + } + else if (rProp.Name == UNO_NAME_GRAPHIC_URL) + { + OUString aURL; + if (rProp.Value >>= aURL) + { + if(!pSetBrush) + { + const SvxBrushItem* pOrigBrush = aFormat.GetBrush(); + if(pOrigBrush) + pSetBrush.reset(new SvxBrushItem(*pOrigBrush)); + else + pSetBrush.reset(new SvxBrushItem(OUString(), OUString(), GPOS_AREA, RES_BACKGROUND)); + } + + Graphic aGraphic = vcl::graphic::loadFromURL(aURL); + if (!aGraphic.IsNone()) + pSetBrush->SetGraphic(aGraphic); + } + else + bWrongArg = true; + } + else if (rProp.Name == UNO_NAME_LIST_FORMAT) + { + OUString uTmp; + rProp.Value >>= uTmp; + aFormat.SetListFormat(uTmp); + } + else + { + // Invalid property name + SAL_WARN("sw.uno", "Unknown/incorrect property " << rProp.Name << ", failing"); + throw uno::RuntimeException("Unknown/incorrect property " + rProp.Name); + } + } + if(!bWrongArg && (pSetBrush || pSetSize || pSetVOrient)) + { + if(!pSetBrush && aFormat.GetBrush()) + pSetBrush.reset(new SvxBrushItem(*aFormat.GetBrush())); + + if(pSetBrush) + { + if(!pSetVOrient && aFormat.GetGraphicOrientation()) + pSetVOrient.reset( new SwFormatVertOrient(*aFormat.GetGraphicOrientation()) ); + + if(!pSetSize) + { + pSetSize.reset(new Size(aFormat.GetGraphicSize())); + if(!pSetSize->Width() || !pSetSize->Height()) + { + const Graphic* pGraphic = pSetBrush->GetGraphic(); + if(pGraphic) + *pSetSize = ::GetGraphicSizeTwip(*pGraphic, nullptr); + } + } + sal_Int16 eOrient = pSetVOrient ? + pSetVOrient->GetVertOrient() : text::VertOrientation::NONE; + aFormat.SetGraphicBrush( pSetBrush.get(), pSetSize.get(), text::VertOrientation::NONE == eOrient ? nullptr : &eOrient ); + } + } + if ((!bCharStyleNameSet || rCharStyleName.isEmpty()) + && aFormat.GetNumberingType() == NumberingType::BITMAP + && !aFormat.GetCharFormat() + && !SwXNumberingRules::isInvalidStyle(rCharStyleName)) + { + OUString tmp; + SwStyleNameMapper::FillProgName(RES_POOLCHR_BULLET_LEVEL, tmp); + rCharStyleName = tmp; + } + + if(bWrongArg) + throw lang::IllegalArgumentException(); +} + +uno::Reference< XPropertySetInfo > SwXNumberingRules::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropertySet->getPropertySetInfo(); + return aRef; +} + +void SwXNumberingRules::setPropertyValue( const OUString& rPropertyName, const Any& rValue ) +{ + SolarMutexGuard aGuard; + std::unique_ptr<SwNumRule> pDocRule; + SwNumRule* pCreatedRule = nullptr; + if(!m_pNumRule) + { + if(m_pDocShell) + { + pDocRule.reset(new SwNumRule(*m_pDocShell->GetDoc()->GetOutlineNumRule())); + } + else if(m_pDoc && !m_sCreatedNumRuleName.isEmpty()) + { + pCreatedRule = m_pDoc->FindNumRulePtr(m_sCreatedNumRuleName); + } + + } + if(!m_pNumRule && !pDocRule && !pCreatedRule) + throw RuntimeException(); + + if(rPropertyName == UNO_NAME_IS_AUTOMATIC) + { + bool bVal = *o3tl::doAccess<bool>(rValue); + if(!pCreatedRule) + pDocRule ? pDocRule->SetAutoRule(bVal) : m_pNumRule->SetAutoRule(bVal); + } + else if(rPropertyName == UNO_NAME_IS_CONTINUOUS_NUMBERING) + { + bool bVal = *o3tl::doAccess<bool>(rValue); + pDocRule ? pDocRule->SetContinusNum(bVal) : + pCreatedRule ? pCreatedRule->SetContinusNum(bVal) : m_pNumRule->SetContinusNum(bVal); + } + else if(rPropertyName == UNO_NAME_NAME) + { + throw IllegalArgumentException(); + } + else if(rPropertyName == UNO_NAME_IS_ABSOLUTE_MARGINS) + { + bool bVal = *o3tl::doAccess<bool>(rValue); + pDocRule ? pDocRule->SetAbsSpaces(bVal) : + pCreatedRule ? pCreatedRule->SetAbsSpaces(bVal) : m_pNumRule->SetAbsSpaces(bVal); + } + else if(rPropertyName == UNO_NAME_NUMBERING_IS_OUTLINE) + { + bool bVal = *o3tl::doAccess<bool>(rValue); + SwNumRuleType eNumRuleType = bVal ? OUTLINE_RULE : NUM_RULE; + pDocRule ? pDocRule->SetRuleType(eNumRuleType) : + pCreatedRule ? pCreatedRule->SetRuleType(eNumRuleType) : m_pNumRule->SetRuleType(eNumRuleType); + } + else if(rPropertyName == UNO_NAME_DEFAULT_LIST_ID) + { + throw IllegalArgumentException(); + } + else + throw UnknownPropertyException(rPropertyName); + + if(pDocRule) + { + assert(m_pDocShell); + m_pDocShell->GetDoc()->SetOutlineNumRule(*pDocRule); + pDocRule.reset(); + } + else if(pCreatedRule) + { + pCreatedRule->Validate(); + } +} + +Any SwXNumberingRules::getPropertyValue( const OUString& rPropertyName ) +{ + Any aRet; + const SwNumRule* pRule = m_pNumRule; + if(!pRule && m_pDocShell) + pRule = m_pDocShell->GetDoc()->GetOutlineNumRule(); + else if(m_pDoc && !m_sCreatedNumRuleName.isEmpty()) + pRule = m_pDoc->FindNumRulePtr( m_sCreatedNumRuleName ); + if(!pRule) + throw RuntimeException(); + + if(rPropertyName == UNO_NAME_IS_AUTOMATIC) + { + aRet <<= pRule->IsAutoRule(); + } + else if(rPropertyName == UNO_NAME_IS_CONTINUOUS_NUMBERING) + { + aRet <<= pRule->IsContinusNum(); + } + else if(rPropertyName == UNO_NAME_NAME) + aRet <<= pRule->GetName(); + else if(rPropertyName == UNO_NAME_IS_ABSOLUTE_MARGINS) + { + aRet <<= pRule->IsAbsSpaces(); + } + else if(rPropertyName == UNO_NAME_NUMBERING_IS_OUTLINE) + { + aRet <<= pRule->IsOutlineRule(); + } + else if(rPropertyName == UNO_NAME_DEFAULT_LIST_ID) + { + OSL_ENSURE( !pRule->GetDefaultListId().isEmpty(), + "<SwXNumberingRules::getPropertyValue(..)> - no default list id found. Serious defect." ); + aRet <<= pRule->GetDefaultListId(); + } + else + throw UnknownPropertyException(rPropertyName); + return aRet; +} + +void SwXNumberingRules::addPropertyChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + +void SwXNumberingRules::removePropertyChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + +void SwXNumberingRules::addVetoableChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +{ +} + +void SwXNumberingRules::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +{ +} + +OUString SwXNumberingRules::getName() +{ + if(m_pNumRule) + { + OUString aString; + SwStyleNameMapper::FillProgName(m_pNumRule->GetName(), aString, SwGetPoolIdFromName::NumRule ); + return aString; + } + // consider chapter numbering <SwXNumberingRules> + if ( m_pDocShell ) + { + OUString aString; + SwStyleNameMapper::FillProgName( m_pDocShell->GetDoc()->GetOutlineNumRule()->GetName(), + aString, SwGetPoolIdFromName::NumRule ); + return aString; + } + return m_sCreatedNumRuleName; +} + +void SwXNumberingRules::setName(const OUString& /*rName*/) +{ + RuntimeException aExcept; + aExcept.Message = "readonly"; + throw aExcept; +} + +void SwXNumberingRules::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + if(m_rParent.m_bOwnNumRuleCreated) + delete m_rParent.m_pNumRule; + m_rParent.m_pNumRule = nullptr; + m_rParent.m_pDoc = nullptr; + } +} + +OUString SwXChapterNumbering::getImplementationName() +{ + return "SwXChapterNumbering"; +} + +sal_Bool SwXChapterNumbering::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXChapterNumbering::getSupportedServiceNames() +{ + return { "com.sun.star.text.ChapterNumbering", "com.sun.star.text.NumberingRules" }; +} + +SwXChapterNumbering::SwXChapterNumbering(SwDocShell& rDocSh) : + SwXNumberingRules(rDocSh) +{ +} + +SwXChapterNumbering::~SwXChapterNumbering() +{ +} + +OUString SwXTextColumns::getImplementationName() +{ + return "SwXTextColumns"; +} + +sal_Bool SwXTextColumns::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SwXTextColumns::getSupportedServiceNames() +{ + Sequence<OUString> aRet { "com.sun.star.text.TextColumns" }; + return aRet; +} + +SwXTextColumns::SwXTextColumns() : + m_nReference(0), + m_bIsAutomaticWidth(true), + m_nAutoDistance(0), + m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_COLUMS)), + m_nSepLineWidth(0), + m_nSepLineColor(0), //black + m_nSepLineHeightRelative(100),//full height + m_nSepLineVertAlign(style::VerticalAlignment_MIDDLE), + m_bSepLineIsOn(false), + m_nSepLineStyle(API_COL_LINE_NONE) // None +{ +} + +SwXTextColumns::SwXTextColumns(const SwFormatCol& rFormatCol) : + m_nReference(0), + m_aTextColumns(rFormatCol.GetNumCols()), + m_bIsAutomaticWidth(rFormatCol.IsOrtho()), + m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_COLUMS)) +{ + const sal_uInt16 nItemGutterWidth = rFormatCol.GetGutterWidth(); + m_nAutoDistance = m_bIsAutomaticWidth ? + USHRT_MAX == nItemGutterWidth ? DEF_GUTTER_WIDTH : static_cast<sal_Int32>(nItemGutterWidth) + : 0; + m_nAutoDistance = convertTwipToMm100(m_nAutoDistance); + + TextColumn* pColumns = m_aTextColumns.getArray(); + const SwColumns& rCols = rFormatCol.GetColumns(); + for(sal_Int32 i = 0; i < m_aTextColumns.getLength(); ++i) + { + const SwColumn* pCol = &rCols[i]; + + pColumns[i].Width = pCol->GetWishWidth(); + m_nReference += pColumns[i].Width; + pColumns[i].LeftMargin = convertTwipToMm100(pCol->GetLeft ()); + pColumns[i].RightMargin = convertTwipToMm100(pCol->GetRight()); + } + if(!m_aTextColumns.hasElements()) + m_nReference = USHRT_MAX; + + m_nSepLineWidth = rFormatCol.GetLineWidth(); + m_nSepLineColor = rFormatCol.GetLineColor(); + m_nSepLineHeightRelative = rFormatCol.GetLineHeight(); + m_bSepLineIsOn = rFormatCol.GetLineAdj() != COLADJ_NONE; + sal_Int8 nStyle = API_COL_LINE_NONE; + switch (rFormatCol.GetLineStyle()) + { + case SvxBorderLineStyle::SOLID: nStyle = API_COL_LINE_SOLID; break; + case SvxBorderLineStyle::DOTTED: nStyle= API_COL_LINE_DOTTED; break; + case SvxBorderLineStyle::DASHED: nStyle= API_COL_LINE_DASHED; break; + default: break; + } + m_nSepLineStyle = nStyle; + switch(rFormatCol.GetLineAdj()) + { + case COLADJ_TOP: m_nSepLineVertAlign = style::VerticalAlignment_TOP; break; + case COLADJ_BOTTOM: m_nSepLineVertAlign = style::VerticalAlignment_BOTTOM; break; + case COLADJ_CENTER: + case COLADJ_NONE: m_nSepLineVertAlign = style::VerticalAlignment_MIDDLE; + } +} + +SwXTextColumns::~SwXTextColumns() +{ +} + +sal_Int32 SwXTextColumns::getReferenceValue() +{ + SolarMutexGuard aGuard; + return m_nReference; +} + +sal_Int16 SwXTextColumns::getColumnCount() +{ + SolarMutexGuard aGuard; + return static_cast< sal_Int16>( m_aTextColumns.getLength() ); +} + +void SwXTextColumns::setColumnCount(sal_Int16 nColumns) +{ + SolarMutexGuard aGuard; + if(nColumns <= 0) + throw uno::RuntimeException(); + m_bIsAutomaticWidth = true; + m_aTextColumns.realloc(nColumns); + TextColumn* pCols = m_aTextColumns.getArray(); + m_nReference = USHRT_MAX; + sal_Int32 nWidth = m_nReference / nColumns; + sal_Int32 nDiff = m_nReference - nWidth * nColumns; + sal_Int32 nDist = m_nAutoDistance / 2; + for(sal_Int16 i = 0; i < nColumns; i++) + { + pCols[i].Width = nWidth; + pCols[i].LeftMargin = i == 0 ? 0 : nDist; + pCols[i].RightMargin = i == nColumns - 1 ? 0 : nDist; + } + pCols[nColumns - 1].Width += nDiff; +} + +uno::Sequence< TextColumn > SwXTextColumns::getColumns() +{ + SolarMutexGuard aGuard; + return m_aTextColumns; +} + +void SwXTextColumns::setColumns(const uno::Sequence< TextColumn >& rColumns) +{ + SolarMutexGuard aGuard; + sal_Int32 nReferenceTemp = std::accumulate(rColumns.begin(), rColumns.end(), sal_Int32(0), + [](const sal_Int32 nSum, const TextColumn& rCol) { return nSum + rCol.Width; }); + m_bIsAutomaticWidth = false; + m_nReference = !nReferenceTemp ? USHRT_MAX : nReferenceTemp; + m_aTextColumns = rColumns; +} + +uno::Reference< XPropertySetInfo > SwXTextColumns::getPropertySetInfo( ) +{ + static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropSet->getPropertySetInfo(); + return aRef; +} + +void SwXTextColumns::setPropertyValue( const OUString& rPropertyName, const Any& aValue ) +{ + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if ( pEntry->nFlags & PropertyAttribute::READONLY) + throw PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + switch(pEntry->nWID) + { + case WID_TXTCOL_LINE_WIDTH: + { + sal_Int32 nTmp = 0; + aValue >>= nTmp; + if(nTmp < 0) + throw IllegalArgumentException(); + m_nSepLineWidth = convertMm100ToTwip(nTmp); + } + break; + case WID_TXTCOL_LINE_COLOR: + aValue >>= m_nSepLineColor; + break; + case WID_TXTCOL_LINE_STYLE: + { + aValue >>= m_nSepLineStyle; + } + break; + case WID_TXTCOL_LINE_REL_HGT: + { + sal_Int8 nTmp = 0; + aValue >>= nTmp; + if(nTmp < 0) + throw IllegalArgumentException(); + m_nSepLineHeightRelative = nTmp; + } + break; + case WID_TXTCOL_LINE_ALIGN: + { + style::VerticalAlignment eAlign; + if(!(aValue >>= eAlign) ) + { + sal_Int8 nTmp = 0; + if (! ( aValue >>= nTmp ) ) + throw IllegalArgumentException(); + m_nSepLineVertAlign = static_cast<style::VerticalAlignment>(nTmp); + } + else + m_nSepLineVertAlign = eAlign; + } + break; + case WID_TXTCOL_LINE_IS_ON: + m_bSepLineIsOn = *o3tl::doAccess<bool>(aValue); + break; + case WID_TXTCOL_AUTO_DISTANCE: + { + sal_Int32 nTmp = 0; + aValue >>= nTmp; + if(nTmp < 0 || nTmp >= m_nReference) + throw IllegalArgumentException(); + m_nAutoDistance = nTmp; + sal_Int32 nColumns = m_aTextColumns.getLength(); + TextColumn* pCols = m_aTextColumns.getArray(); + sal_Int32 nDist = m_nAutoDistance / 2; + for(sal_Int32 i = 0; i < nColumns; i++) + { + pCols[i].LeftMargin = i == 0 ? 0 : nDist; + pCols[i].RightMargin = i == nColumns - 1 ? 0 : nDist; + } + } + break; + } +} + +Any SwXTextColumns::getPropertyValue( const OUString& rPropertyName ) +{ + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); + if (!pEntry) + throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + Any aRet; + switch(pEntry->nWID) + { + case WID_TXTCOL_LINE_WIDTH: + aRet <<= static_cast < sal_Int32 >(convertTwipToMm100(m_nSepLineWidth)); + break; + case WID_TXTCOL_LINE_COLOR: + aRet <<= m_nSepLineColor; + break; + case WID_TXTCOL_LINE_STYLE: + aRet <<= m_nSepLineStyle; + break; + case WID_TXTCOL_LINE_REL_HGT: + aRet <<= m_nSepLineHeightRelative; + break; + case WID_TXTCOL_LINE_ALIGN: + aRet <<= m_nSepLineVertAlign; + break; + case WID_TXTCOL_LINE_IS_ON: + aRet <<= m_bSepLineIsOn; + break; + case WID_TXTCOL_IS_AUTOMATIC : + aRet <<= m_bIsAutomaticWidth; + break; + case WID_TXTCOL_AUTO_DISTANCE: + aRet <<= m_nAutoDistance; + break; + } + return aRet; +} + +void SwXTextColumns::addPropertyChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + +void SwXTextColumns::removePropertyChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + +void SwXTextColumns::addVetoableChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +{ +} + +void SwXTextColumns::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +{ +} + +namespace +{ + class theSwXTextColumnsUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextColumnsUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextColumns::getUnoTunnelId() +{ + return theSwXTextColumnsUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXTextColumns::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXTextColumns>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unosrch.cxx b/sw/source/core/unocore/unosrch.cxx new file mode 100644 index 000000000..a4656104a --- /dev/null +++ b/sw/source/core/unocore/unosrch.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/. + * + * 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 <hintids.hxx> +#include <unosrch.hxx> +#include <unomap.hxx> +#include <swtypes.hxx> + +#include <osl/diagnose.h> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/searchopt.hxx> +#include <o3tl/any.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svl/itemprop.hxx> +#include <svl/itempool.hxx> +#include <memory> + +using namespace ::com::sun::star; + +class SwSearchProperties_Impl +{ + std::unique_ptr<std::unique_ptr<beans::PropertyValue>[]> pValueArr; + const PropertyEntryVector_t aPropertyEntries; + + SwSearchProperties_Impl(const SwSearchProperties_Impl&) = delete; + SwSearchProperties_Impl& operator=(const SwSearchProperties_Impl&) = delete; + +public: + SwSearchProperties_Impl(); + + /// @throws beans::UnknownPropertyException + /// @throws lang::IllegalArgumentException + /// @throws uno::RuntimeException + void SetProperties(const uno::Sequence< beans::PropertyValue >& aSearchAttribs); + uno::Sequence< beans::PropertyValue > GetProperties() const; + + void FillItemSet(SfxItemSet& rSet, bool bIsValueSearch) const; + bool HasAttributes() const; +}; + +SwSearchProperties_Impl::SwSearchProperties_Impl() : + aPropertyEntries( aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR)->getPropertyMap().getPropertyEntries() ) +{ + size_t nArrLen = aPropertyEntries.size(); + pValueArr.reset( new std::unique_ptr<beans::PropertyValue>[nArrLen] ); +} + +void SwSearchProperties_Impl::SetProperties(const uno::Sequence< beans::PropertyValue >& aSearchAttribs) +{ + //delete all existing values + for(size_t i = 0; i < aPropertyEntries.size(); ++i) + { + pValueArr[i].reset(); + } + + for(const beans::PropertyValue& rSearchAttrib : aSearchAttribs) + { + const OUString& sName = rSearchAttrib.Name; + auto aIt = std::find_if(aPropertyEntries.begin(), aPropertyEntries.end(), + [&sName](const SfxItemPropertyNamedEntry& rProp) { return rProp.sName == sName; }); + if( aIt == aPropertyEntries.end() ) + throw beans::UnknownPropertyException(sName); + auto nIndex = static_cast<sal_uInt32>(std::distance(aPropertyEntries.begin(), aIt)); + pValueArr[nIndex].reset( new beans::PropertyValue(rSearchAttrib) ); + } +} + +uno::Sequence< beans::PropertyValue > SwSearchProperties_Impl::GetProperties() const +{ + sal_uInt32 nPropCount = 0; + for( size_t i = 0; i < aPropertyEntries.size(); i++) + if(pValueArr[i]) + nPropCount++; + + uno::Sequence< beans::PropertyValue > aRet(nPropCount); + beans::PropertyValue* pProps = aRet.getArray(); + nPropCount = 0; + for(size_t i = 0; i < aPropertyEntries.size(); i++) + { + if(pValueArr[i]) + { + pProps[nPropCount++] = *(pValueArr[i]); + } + } + return aRet; +} + +void SwSearchProperties_Impl::FillItemSet(SfxItemSet& rSet, bool bIsValueSearch) const +{ + + std::unique_ptr<SfxPoolItem> pBoxItem, + pCharBoxItem, + pBreakItem, + pAutoKernItem , + pWLineItem , + pTabItem , + pSplitItem , + pRegItem , + pLineSpaceItem , + pLineNumItem , + pKeepItem , + pLRItem , + pULItem , + pBackItem , + pAdjItem , + pDescItem , + pInetItem , + pDropItem , + pWeightItem , + pULineItem , + pOLineItem , + pCharFormatItem , + pShadItem , + pPostItem , + pNHyphItem , + pLangItem , + pKernItem , + pFontSizeItem , + pFontItem , + pBlinkItem , + pEscItem , + pCrossedOutItem , + pContourItem , + pCharColorItem , + pCasemapItem , + pBrushItem , + pFontCJKItem, + pFontSizeCJKItem, + pCJKLangItem, + pCJKPostureItem, + pCJKWeightItem, + pFontCTLItem, + pFontSizeCTLItem, + pCTLLangItem, + pCTLPostureItem, + pCTLWeightItem, + pShadowItem ; + + PropertyEntryVector_t::const_iterator aIt = aPropertyEntries.begin(); + for(size_t i = 0; i < aPropertyEntries.size(); i++, ++aIt) + { + if(pValueArr[i]) + { + SfxPoolItem* pTempItem = nullptr; + switch(aIt->nWID) + { + case RES_BOX: + if(!pBoxItem) + pBoxItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pBoxItem.get(); + break; + case RES_CHRATR_BOX: + if(!pCharBoxItem) + pCharBoxItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCharBoxItem.get(); + break; + case RES_BREAK: + if(!pBreakItem) + pBreakItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pBreakItem.get(); + break; + case RES_CHRATR_AUTOKERN: + if(!pAutoKernItem) + pAutoKernItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pAutoKernItem.get(); + break; + case RES_CHRATR_BACKGROUND: + if(!pBrushItem) + pBrushItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pBrushItem.get(); + break; + case RES_CHRATR_CASEMAP: + if(!pCasemapItem) + pCasemapItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCasemapItem.get(); + break; + case RES_CHRATR_COLOR: + if(!pCharColorItem) + pCharColorItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCharColorItem.get(); + break; + case RES_CHRATR_CONTOUR: + if(!pContourItem) + pContourItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pContourItem.get(); + break; + case RES_CHRATR_CROSSEDOUT: + if(!pCrossedOutItem) + pCrossedOutItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCrossedOutItem.get(); + break; + case RES_CHRATR_ESCAPEMENT: + if(!pEscItem) + pEscItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pEscItem.get(); + break; + case RES_CHRATR_BLINK: + if(!pBlinkItem) + pBlinkItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pBlinkItem.get(); + break; + case RES_CHRATR_FONT: + if(!pFontItem) + pFontItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pFontItem.get(); + break; + case RES_CHRATR_FONTSIZE: + if(!pFontSizeItem) + pFontSizeItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pFontSizeItem.get(); + break; + case RES_CHRATR_KERNING: + if(!pKernItem) + pKernItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pKernItem.get(); + break; + case RES_CHRATR_LANGUAGE: + if(!pLangItem) + pLangItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pLangItem.get(); + break; + case RES_CHRATR_NOHYPHEN: + if(!pNHyphItem) + pNHyphItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pNHyphItem.get(); + break; + case RES_CHRATR_POSTURE: + if(!pPostItem) + pPostItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pPostItem.get(); + break; + case RES_CHRATR_SHADOWED: + if(!pShadItem) + pShadItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pShadItem.get(); + break; + case RES_TXTATR_CHARFMT: + if(!pCharFormatItem) + pCharFormatItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCharFormatItem.get(); + break; + case RES_CHRATR_UNDERLINE: + if(!pULineItem) + pULineItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pULineItem.get(); + break; + case RES_CHRATR_OVERLINE: + if(!pOLineItem) + pOLineItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pOLineItem.get(); + break; + case RES_CHRATR_WEIGHT: + if(!pWeightItem) + pWeightItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pWeightItem.get(); + break; + case RES_PARATR_DROP: + if(!pDropItem) + pDropItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pDropItem.get(); + break; + case RES_TXTATR_INETFMT: + if(!pInetItem) + pInetItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pInetItem.get(); + break; + case RES_PAGEDESC: + if(!pDescItem) + pDescItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pDescItem.get(); + break; + case RES_PARATR_ADJUST: + if(!pAdjItem) + pAdjItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pAdjItem.get(); + break; + case RES_BACKGROUND: + if(!pBackItem) + pBackItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pBackItem.get(); + break; + case RES_UL_SPACE: + if(!pULItem) + pULItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pULItem.get(); + break; + case RES_LR_SPACE: + if(!pLRItem) + pLRItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pLRItem.get(); + break; + case RES_KEEP: + if(!pKeepItem) + pKeepItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pKeepItem.get(); + break; + case RES_LINENUMBER: + if(!pLineNumItem) + pLineNumItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pLineNumItem.get(); + break; + case RES_PARATR_LINESPACING: + if(!pLineSpaceItem) + pLineSpaceItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pLineSpaceItem.get(); + break; + case RES_PARATR_REGISTER: + if(!pRegItem) + pRegItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pRegItem.get(); + break; + case RES_PARATR_SPLIT: + if(!pSplitItem) + pSplitItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pSplitItem.get(); + break; + case RES_PARATR_TABSTOP: + if(!pTabItem) + pTabItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pTabItem.get(); + break; + case RES_CHRATR_WORDLINEMODE: + if(!pWLineItem) + pWLineItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pWLineItem.get(); + break; + case RES_CHRATR_CJK_FONT: + if(!pFontCJKItem ) + pFontCJKItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pFontCJKItem.get(); + break; + case RES_CHRATR_CJK_FONTSIZE: + if(!pFontSizeCJKItem ) + pFontSizeCJKItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pFontSizeCJKItem.get(); + break; + case RES_CHRATR_CJK_LANGUAGE: + if(!pCJKLangItem ) + pCJKLangItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCJKLangItem.get(); + break; + case RES_CHRATR_CJK_POSTURE: + if(!pCJKPostureItem ) + pCJKPostureItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCJKPostureItem.get(); + break; + case RES_CHRATR_CJK_WEIGHT: + if(!pCJKWeightItem ) + pCJKWeightItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCJKWeightItem.get(); + break; + case RES_CHRATR_CTL_FONT: + if(!pFontCTLItem ) + pFontCTLItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pFontCTLItem.get(); + break; + case RES_CHRATR_CTL_FONTSIZE: + if(!pFontSizeCTLItem ) + pFontSizeCTLItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pFontSizeCTLItem.get(); + break; + case RES_CHRATR_CTL_LANGUAGE: + if(!pCTLLangItem ) + pCTLLangItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCTLLangItem.get(); + break; + case RES_CHRATR_CTL_POSTURE: + if(!pCTLPostureItem ) + pCTLPostureItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCTLPostureItem.get(); + break; + case RES_CHRATR_CTL_WEIGHT: + if(!pCTLWeightItem ) + pCTLWeightItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pCTLWeightItem.get(); + break; + case RES_CHRATR_SHADOW: + if(!pShadowItem ) + pShadowItem.reset(rSet.GetPool()->GetDefaultItem(aIt->nWID).Clone()); + pTempItem = pShadowItem.get(); + break; + } + if(pTempItem) + { + if(bIsValueSearch) + { + pTempItem->PutValue(pValueArr[i]->Value, aIt->nMemberId); + rSet.Put(*pTempItem); + } + else + rSet.InvalidateItem( pTempItem->Which() ); + } + } + } +} + +bool SwSearchProperties_Impl::HasAttributes() const +{ + for(size_t i = 0; i < aPropertyEntries.size(); i++) + if(pValueArr[i]) + return true; + return false; +} + +SwXTextSearch::SwXTextSearch() : + m_pSearchProperties( new SwSearchProperties_Impl), + m_pReplaceProperties( new SwSearchProperties_Impl), + m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_SEARCH)), + m_bAll(false), + m_bWord(false), + m_bBack(false), + m_bExpr(false), + m_bCase(false), + m_bStyles(false), + m_bSimilarity(false), + m_bLevRelax(false), + m_nLevExchange(2), + m_nLevAdd(2), + m_nLevRemove(2), + m_bIsValueSearch(true) +{ +} + +SwXTextSearch::~SwXTextSearch() +{ + m_pSearchProperties.reset(); + m_pReplaceProperties.reset(); +} + +namespace +{ + class theSwXTextSearchUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextSearchUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextSearch::getUnoTunnelId() +{ + return theSwXTextSearchUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXTextSearch::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXTextSearch>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + +OUString SwXTextSearch::getSearchString() +{ + SolarMutexGuard aGuard; + return m_sSearchText; +} + +void SwXTextSearch::setSearchString(const OUString& rString) +{ + SolarMutexGuard aGuard; + m_sSearchText = rString; +} + +OUString SwXTextSearch::getReplaceString() +{ + SolarMutexGuard aGuard; + return m_sReplaceText; +} + +void SwXTextSearch::setReplaceString(const OUString& rReplaceString) +{ + SolarMutexGuard aGuard; + m_sReplaceText = rReplaceString; +} + +uno::Reference< beans::XPropertySetInfo > SwXTextSearch::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropSet->getPropertySetInfo(); + return aRef; +} + +void SwXTextSearch::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException ("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + bool bVal = false; + if(auto b = o3tl::tryAccess<bool>(aValue)) + bVal = *b; + switch(pEntry->nWID) + { + case WID_SEARCH_ALL : m_bAll = bVal; break; + case WID_WORDS: m_bWord = bVal; break; + case WID_BACKWARDS : m_bBack = bVal; break; + case WID_REGULAR_EXPRESSION : m_bExpr = bVal; break; + case WID_CASE_SENSITIVE : m_bCase = bVal; break; + //case WID_IN_SELECTION : bInSel = bVal; break; + case WID_STYLES : m_bStyles = bVal; break; + case WID_SIMILARITY : m_bSimilarity = bVal; break; + case WID_SIMILARITY_RELAX: m_bLevRelax = bVal; break; + case WID_SIMILARITY_EXCHANGE: aValue >>= m_nLevExchange; break; + case WID_SIMILARITY_ADD: aValue >>= m_nLevAdd; break; + case WID_SIMILARITY_REMOVE : aValue >>= m_nLevRemove;break; + }; + +} + +uno::Any SwXTextSearch::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + + const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName(rPropertyName); + bool bSet = false; + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + sal_Int16 nSet = 0; + switch(pEntry->nWID) + { + case WID_SEARCH_ALL : bSet = m_bAll; goto SET_BOOL; + case WID_WORDS: bSet = m_bWord; goto SET_BOOL; + case WID_BACKWARDS : bSet = m_bBack; goto SET_BOOL; + case WID_REGULAR_EXPRESSION : bSet = m_bExpr; goto SET_BOOL; + case WID_CASE_SENSITIVE : bSet = m_bCase; goto SET_BOOL; + //case WID_IN_SELECTION : bSet = bInSel; goto SET_BOOL; + case WID_STYLES : bSet = m_bStyles; goto SET_BOOL; + case WID_SIMILARITY : bSet = m_bSimilarity; goto SET_BOOL; + case WID_SIMILARITY_RELAX: bSet = m_bLevRelax; +SET_BOOL: + aRet <<= bSet; + break; + case WID_SIMILARITY_EXCHANGE: nSet = m_nLevExchange; goto SET_UINT16; + case WID_SIMILARITY_ADD: nSet = m_nLevAdd; goto SET_UINT16; + case WID_SIMILARITY_REMOVE : nSet = m_nLevRemove; +SET_UINT16: + aRet <<= nSet; + break; + } + + return aRet; +} + +void SwXTextSearch::addPropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextSearch::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextSearch::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +void SwXTextSearch::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) +{ + OSL_FAIL("not implemented"); +} + +sal_Bool SwXTextSearch::getValueSearch() +{ + SolarMutexGuard aGuard; + return m_bIsValueSearch; +} + +void SwXTextSearch::setValueSearch(sal_Bool ValueSearch_) +{ + SolarMutexGuard aGuard; + m_bIsValueSearch = ValueSearch_; +} + +uno::Sequence< beans::PropertyValue > SwXTextSearch::getSearchAttributes() +{ + return m_pSearchProperties->GetProperties(); +} + +void SwXTextSearch::setSearchAttributes(const uno::Sequence< beans::PropertyValue >& rSearchAttribs) +{ + m_pSearchProperties->SetProperties(rSearchAttribs); +} + +uno::Sequence< beans::PropertyValue > SwXTextSearch::getReplaceAttributes() +{ + return m_pReplaceProperties->GetProperties(); +} + +void SwXTextSearch::setReplaceAttributes(const uno::Sequence< beans::PropertyValue >& rReplaceAttribs) +{ + m_pReplaceProperties->SetProperties(rReplaceAttribs); +} + +void SwXTextSearch::FillSearchItemSet(SfxItemSet& rSet) const +{ + m_pSearchProperties->FillItemSet(rSet, m_bIsValueSearch); +} + +void SwXTextSearch::FillReplaceItemSet(SfxItemSet& rSet) const +{ + m_pReplaceProperties->FillItemSet(rSet, m_bIsValueSearch); +} + +bool SwXTextSearch::HasSearchAttributes() const +{ + return m_pSearchProperties->HasAttributes(); +} + +bool SwXTextSearch::HasReplaceAttributes() const +{ + return m_pReplaceProperties->HasAttributes(); +} + +OUString SwXTextSearch::getImplementationName() +{ + return "SwXTextSearch"; +} + +sal_Bool SwXTextSearch::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXTextSearch::getSupportedServiceNames() +{ + return { "com.sun.star.util.SearchDescriptor", "com.sun.star.util.ReplaceDescriptor" }; +} + +void SwXTextSearch::FillSearchOptions( i18nutil::SearchOptions2& rSearchOpt ) const +{ + if( m_bSimilarity ) + { + rSearchOpt.algorithmType = util::SearchAlgorithms_APPROXIMATE; + rSearchOpt.AlgorithmType2 = util::SearchAlgorithms2::APPROXIMATE; + rSearchOpt.changedChars = m_nLevExchange; + rSearchOpt.deletedChars = m_nLevRemove; + rSearchOpt.insertedChars = m_nLevAdd; + if( m_bLevRelax ) + rSearchOpt.searchFlag |= util::SearchFlags::LEV_RELAXED; + } + else if( m_bExpr ) + { + rSearchOpt.algorithmType = util::SearchAlgorithms_REGEXP; + rSearchOpt.AlgorithmType2 = util::SearchAlgorithms2::REGEXP; + } + else + { + rSearchOpt.algorithmType = util::SearchAlgorithms_ABSOLUTE; + rSearchOpt.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE; + } + + rSearchOpt.Locale = GetAppLanguageTag().getLocale(); + rSearchOpt.searchString = m_sSearchText; + rSearchOpt.replaceString = m_sReplaceText; + + if( !m_bCase ) + rSearchOpt.transliterateFlags |= TransliterationFlags::IGNORE_CASE; + if( m_bWord ) + rSearchOpt.searchFlag |= util::SearchFlags::NORM_WORD_ONLY; + +// bInSel: 1; // How is that possible? +// TODO: pSearch->bStyles! +// inSelection?? +// aSrchParam.SetSrchInSelection(TypeConversion::toBOOL(aVal)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unostyle.cxx b/sw/source/core/unocore/unostyle.cxx new file mode 100644 index 000000000..fedbf6ece --- /dev/null +++ b/sw/source/core/unocore/unostyle.cxx @@ -0,0 +1,5596 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <svx/svxids.hrc> +#include <hintids.hxx> +#include <vcl/svapp.hxx> +#include <svl/hint.hxx> +#include <svtools/ctrltool.hxx> +#include <svl/style.hxx> +#include <svl/itemiter.hxx> +#include <svl/listener.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <svx/pageitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/flstitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/paperinf.hxx> +#include <editeng/wghtitem.hxx> +#include <pagedesc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docary.hxx> +#include <charfmt.hxx> +#include <cmdid.h> +#include <unomid.h> +#include <unomap.hxx> +#include <unostyle.hxx> +#include <unosett.hxx> +#include <docsh.hxx> +#include <paratr.hxx> +#include <unoprnms.hxx> +#include <shellio.hxx> +#include <docstyle.hxx> +#include <unotextbodyhf.hxx> +#include <fmthdft.hxx> +#include <fmtpdsc.hxx> +#include <strings.hrc> +#include <poolfmt.hxx> +#include <unoevent.hxx> +#include <fmtruby.hxx> +#include <SwStyleNameMapper.hxx> +#include <sfx2/printer.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/style/ParagraphStyleCategory.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/document/XEventsSupplier.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <istyleaccess.hxx> +#include <GetMetricVal.hxx> +#include <fmtfsize.hxx> +#include <numrule.hxx> +#include <tblafmt.hxx> +#include <frameformats.hxx> + +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> + +#include <svl/stylepool.hxx> +#include <svx/unobrushitemhelper.hxx> +#include <editeng/unoipset.hxx> +#include <editeng/memberids.h> +#include <svx/unomid.hxx> +#include <svx/unoshape.hxx> +#include <svx/xflbstit.hxx> +#include <svx/xflbmtit.hxx> +#include <swunohelper.hxx> +#include <svx/xbtmpit.hxx> + +#include <ccoll.hxx> +#include <hints.hxx> + +#include <cassert> +#include <memory> +#include <set> +#include <limits> + +using namespace css; +using namespace css::io; +using namespace css::lang; +using namespace css::uno; + +namespace { + +class SwXStyle; +class SwStyleProperties_Impl; + + struct StyleFamilyEntry + { + using GetCountOrName_t = std::function<sal_Int32 (const SwDoc&, OUString*, sal_Int32)>; + using CreateStyle_t = std::function<uno::Reference<css::style::XStyle>(SfxStyleSheetBasePool*, SwDocShell*, const OUString&)>; + using TranslateIndex_t = std::function<sal_uInt16(const sal_uInt16)>; + SfxStyleFamily m_eFamily; + sal_uInt16 m_nPropMapType; + uno::Reference<beans::XPropertySetInfo> m_xPSInfo; + SwGetPoolIdFromName m_aPoolId; + OUString m_sName; + const char* m_pResId; + GetCountOrName_t m_fGetCountOrName; + CreateStyle_t m_fCreateStyle; + TranslateIndex_t m_fTranslateIndex; + StyleFamilyEntry(SfxStyleFamily eFamily, sal_uInt16 nPropMapType, SwGetPoolIdFromName aPoolId, OUString const& sName, const char* pResId, GetCountOrName_t const & fGetCountOrName, CreateStyle_t const & fCreateStyle, TranslateIndex_t const & fTranslateIndex) + : m_eFamily(eFamily) + , m_nPropMapType(nPropMapType) + , m_xPSInfo(aSwMapProvider.GetPropertySet(nPropMapType)->getPropertySetInfo()) + , m_aPoolId(aPoolId) + , m_sName(sName) + , m_pResId(pResId) + , m_fGetCountOrName(fGetCountOrName) + , m_fCreateStyle(fCreateStyle) + , m_fTranslateIndex(fTranslateIndex) + { } + }; + static const std::vector<StyleFamilyEntry>* our_pStyleFamilyEntries; + // these should really be constexprs, but MSVC still is apparently too stupid for them + #define nPoolChrNormalRange (RES_POOLCHR_NORMAL_END - RES_POOLCHR_NORMAL_BEGIN) + #define nPoolChrHtmlRange (RES_POOLCHR_HTML_END - RES_POOLCHR_HTML_BEGIN) + #define nPoolCollTextRange ( RES_POOLCOLL_TEXT_END - RES_POOLCOLL_TEXT_BEGIN) + #define nPoolCollListsRange ( RES_POOLCOLL_LISTS_END - RES_POOLCOLL_LISTS_BEGIN) + #define nPoolCollExtraRange ( RES_POOLCOLL_EXTRA_END - RES_POOLCOLL_EXTRA_BEGIN) + #define nPoolCollRegisterRange ( RES_POOLCOLL_REGISTER_END - RES_POOLCOLL_REGISTER_BEGIN) + #define nPoolCollDocRange ( RES_POOLCOLL_DOC_END - RES_POOLCOLL_DOC_BEGIN) + #define nPoolCollHtmlRange ( RES_POOLCOLL_HTML_END - RES_POOLCOLL_HTML_BEGIN) + #define nPoolFrameRange ( RES_POOLFRM_END - RES_POOLFRM_BEGIN) + #define nPoolPageRange ( RES_POOLPAGE_END - RES_POOLPAGE_BEGIN) + #define nPoolNumRange ( RES_POOLNUMRULE_END - RES_POOLNUMRULE_BEGIN) + #define nPoolCollListsStackedStart ( nPoolCollTextRange) + #define nPoolCollExtraStackedStart ( nPoolCollListsStackedStart + nPoolCollListsRange) + #define nPoolCollRegisterStackedStart ( nPoolCollExtraStackedStart + nPoolCollExtraRange) + #define nPoolCollDocStackedStart ( nPoolCollRegisterStackedStart + nPoolCollRegisterRange) + #define nPoolCollHtmlStackedStart ( nPoolCollDocStackedStart + nPoolCollDocRange) + using paragraphstyle_t = std::remove_const<decltype(style::ParagraphStyleCategory::TEXT)>::type; + using collectionbits_t = sal_uInt16; + struct ParagraphStyleCategoryEntry + { + paragraphstyle_t m_eCategory; + SfxStyleSearchBits m_nSwStyleBits; + collectionbits_t m_nCollectionBits; + ParagraphStyleCategoryEntry(paragraphstyle_t eCategory, SfxStyleSearchBits nSwStyleBits, collectionbits_t nCollectionBits) + : m_eCategory(eCategory) + , m_nSwStyleBits(nSwStyleBits) + , m_nCollectionBits(nCollectionBits) + { } + }; + static const std::vector<ParagraphStyleCategoryEntry>* our_pParagraphStyleCategoryEntries; +} +static const std::vector<StyleFamilyEntry>* lcl_GetStyleFamilyEntries(); + +using namespace ::com::sun::star; + +namespace sw +{ + namespace { + + class XStyleFamily : public cppu::WeakImplHelper + < + container::XNameContainer, + lang::XServiceInfo, + container::XIndexAccess, + beans::XPropertySet + > + , public SfxListener + { + const StyleFamilyEntry& m_rEntry; + SfxStyleSheetBasePool* m_pBasePool; + SwDocShell* m_pDocShell; + + SwXStyle* FindStyle(const OUString& rStyleName) const; + sal_Int32 GetCountOrName(OUString* pString, sal_Int32 nIndex = SAL_MAX_INT32) + { return m_rEntry.m_fGetCountOrName(*m_pDocShell->GetDoc(), pString, nIndex); }; + static const StyleFamilyEntry& InitEntry(SfxStyleFamily eFamily) + { + auto pEntries = lcl_GetStyleFamilyEntries(); + const auto pEntry = std::find_if(pEntries->begin(), pEntries->end(), + [eFamily] (const StyleFamilyEntry& e) { return e.m_eFamily == eFamily; }); + assert(pEntry != pEntries->end()); + return *pEntry; + } + public: + XStyleFamily(SwDocShell* pDocShell, const SfxStyleFamily eFamily) + : m_rEntry(InitEntry(eFamily)) + , m_pBasePool(pDocShell->GetStyleSheetPool()) + , m_pDocShell(pDocShell) + { + if (m_pBasePool) //tdf#124142 html docs can have no styles + StartListening(*m_pBasePool); + } + + //XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override + { + SolarMutexGuard aGuard; + return GetCountOrName(nullptr); + }; + virtual uno::Any SAL_CALL getByIndex(sal_Int32 nIndex) override; + + //XElementAccess + virtual uno::Type SAL_CALL getElementType( ) override + { return cppu::UnoType<style::XStyle>::get(); }; + virtual sal_Bool SAL_CALL hasElements( ) override + { + if(!m_pBasePool) + throw uno::RuntimeException(); + return true; + } + + //XNameAccess + virtual uno::Any SAL_CALL getByName(const OUString& Name) override; + virtual uno::Sequence< OUString > SAL_CALL getElementNames() override; + virtual sal_Bool SAL_CALL hasByName(const OUString& Name) override; + + //XNameContainer + virtual void SAL_CALL insertByName(const OUString& Name, const uno::Any& Element) override; + virtual void SAL_CALL replaceByName(const OUString& Name, const uno::Any& Element) override; + virtual void SAL_CALL removeByName(const OUString& Name) override; + + //XPropertySet + virtual uno::Reference< beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override + { return {}; }; + virtual void SAL_CALL setPropertyValue( const OUString&, const uno::Any&) override + { SAL_WARN("sw.uno", "###unexpected!"); }; + virtual uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString&, const uno::Reference<beans::XPropertyChangeListener>&) override + { SAL_WARN("sw.uno", "###unexpected!"); }; + virtual void SAL_CALL removePropertyChangeListener( const OUString&, const uno::Reference<beans::XPropertyChangeListener>&) override + { SAL_WARN("sw.uno", "###unexpected!"); }; + virtual void SAL_CALL addVetoableChangeListener(const OUString&, const uno::Reference<beans::XVetoableChangeListener>&) override + { SAL_WARN("sw.uno", "###unexpected!"); }; + virtual void SAL_CALL removeVetoableChangeListener(const OUString&, const uno::Reference<beans::XVetoableChangeListener>&) override + { SAL_WARN("sw.uno", "###unexpected!"); }; + + //SfxListener + virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override + { + if(rHint.GetId() == SfxHintId::Dying) + { + m_pBasePool = nullptr; + m_pDocShell = nullptr; + EndListening(rBC); + } + } + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { return {"XStyleFamily"}; }; + virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override + { return cppu::supportsService(this, rServiceName); }; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override + { return { "com.sun.star.style.StyleFamily" }; } + }; + + } +} + +namespace { + +class SwStyleBase_Impl; +class SwXStyle : public cppu::WeakImplHelper + < + css::style::XStyle, + css::beans::XPropertySet, + css::beans::XMultiPropertySet, + css::lang::XServiceInfo, + css::lang::XUnoTunnel, + css::beans::XPropertyState, + css::beans::XMultiPropertyStates + > + , public SfxListener + , public SvtListener +{ + SwDoc* m_pDoc; + OUString m_sStyleName; + const StyleFamilyEntry& m_rEntry; + bool m_bIsDescriptor; + bool m_bIsConditional; + OUString m_sParentStyleName; + +protected: + SfxStyleSheetBasePool* m_pBasePool; + std::unique_ptr<SwStyleProperties_Impl> m_pPropertiesImpl; + css::uno::Reference<css::container::XNameAccess> m_xStyleFamily; + css::uno::Reference<css::beans::XPropertySet> m_xStyleData; + + template<sal_uInt16> + void SetPropertyValue(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any&, SwStyleBase_Impl&); + void SetPropertyValues_Impl( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ); + SfxStyleSheetBase* GetStyleSheetBase(); + void PrepareStyleBase(SwStyleBase_Impl& rBase); + template<sal_uInt16> + uno::Any GetStyleProperty(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase); + uno::Any GetStyleProperty_Impl(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase); + uno::Any GetPropertyValue_Impl(const SfxItemPropertySet* pPropSet, SwStyleBase_Impl& rBase, const OUString& rPropertyName); + +public: + SwXStyle(SwDoc* pDoc, SfxStyleFamily eFam, bool bConditional = false); + SwXStyle(SfxStyleSheetBasePool* pPool, SfxStyleFamily eFamily, SwDoc* pDoc, const OUString& rStyleName); + virtual ~SwXStyle() override; + + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + + //XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; + + //XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName(const OUString& Name_) override; + + //XStyle + virtual sal_Bool SAL_CALL isUserDefined() override; + virtual sal_Bool SAL_CALL isInUse() override; + virtual OUString SAL_CALL getParentStyle() override; + virtual void SAL_CALL setParentStyle(const OUString& aParentStyle) override; + + //XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString&, const css::uno::Reference< css::beans::XPropertyChangeListener >& ) override + { OSL_FAIL("not implemented"); }; + virtual void SAL_CALL removePropertyChangeListener( const OUString&, const css::uno::Reference< css::beans::XPropertyChangeListener >& ) override + { OSL_FAIL("not implemented"); }; + virtual void SAL_CALL addVetoableChangeListener( const OUString&, const css::uno::Reference< css::beans::XVetoableChangeListener >& ) override + { OSL_FAIL("not implemented"); }; + virtual void SAL_CALL removeVetoableChangeListener( const OUString&, const css::uno::Reference< css::beans::XVetoableChangeListener >& ) override + { OSL_FAIL("not implemented"); }; + + //XMultiPropertySet + virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ) override; + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames ) override; + virtual void SAL_CALL addPropertiesChangeListener( const css::uno::Sequence< OUString >&, const css::uno::Reference< css::beans::XPropertiesChangeListener >& ) override + {}; + virtual void SAL_CALL removePropertiesChangeListener( const css::uno::Reference< css::beans::XPropertiesChangeListener >& ) override + {}; + virtual void SAL_CALL firePropertiesChangeEvent( const css::uno::Sequence< OUString >&, const css::uno::Reference< css::beans::XPropertiesChangeListener >& ) override + {}; + + //XPropertyState + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& PropertyName ) override; + virtual css::uno::Sequence< css::beans::PropertyState > SAL_CALL getPropertyStates( const css::uno::Sequence< OUString >& aPropertyName ) override; + virtual void SAL_CALL setPropertyToDefault( const OUString& PropertyName ) override; + virtual css::uno::Any SAL_CALL getPropertyDefault( const OUString& aPropertyName ) override; + + //XMultiPropertyStates + virtual void SAL_CALL setAllPropertiesToDefault( ) override; + virtual void SAL_CALL setPropertiesToDefault( const css::uno::Sequence< OUString >& aPropertyNames ) override; + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPropertyDefaults( const css::uno::Sequence< OUString >& aPropertyNames ) override; + + //XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { return {"SwXStyle"}; }; + virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override + { return cppu::supportsService(this, rServiceName); }; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + //SfxListener + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + //SvtListener + virtual void Notify(const SfxHint&) override; + const OUString& GetStyleName() const { return m_sStyleName;} + SfxStyleFamily GetFamily() const {return m_rEntry.m_eFamily;} + + bool IsDescriptor() const {return m_bIsDescriptor;} + bool IsConditional() const { return m_bIsConditional;} + const OUString& GetParentStyleName() const { return m_sParentStyleName;} + void SetDoc(SwDoc* pDc, SfxStyleSheetBasePool* pPool) + { + m_bIsDescriptor = false; m_pDoc = pDc; + m_pBasePool = pPool; + SfxListener::StartListening(*m_pBasePool); + } + SwDoc* GetDoc() const { return m_pDoc; } + void Invalidate(); + void ApplyDescriptorProperties(); + void SetStyleName(const OUString& rSet){ m_sStyleName = rSet;} + /// @throws beans::PropertyVetoException + /// @throws lang::IllegalArgumentException + /// @throws lang::WrappedTargetException + /// @throws uno::RuntimeException + void SetStyleProperty(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& rBase); + void PutItemToSet(const SvxSetItem* pSetItem, const SfxItemPropertySet& rPropSet, const SfxItemPropertySimpleEntry& rEntry, const uno::Any& rVal, SwStyleBase_Impl& rBaseImpl); +}; + +class SwXFrameStyle + : public SwXStyle + , public css::document::XEventsSupplier + , public sw::ICoreFrameStyle +{ +public: + SwXFrameStyle(SfxStyleSheetBasePool& rPool, + SwDoc* pDoc, + const OUString& rStyleName) : + SwXStyle(&rPool, SfxStyleFamily::Frame, pDoc, rStyleName){} + explicit SwXFrameStyle(SwDoc *pDoc); + + virtual void SAL_CALL acquire( ) throw() override {SwXStyle::acquire();} + virtual void SAL_CALL release( ) throw() override {SwXStyle::release();} + + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + virtual css::uno::Reference< css::container::XNameReplace > SAL_CALL getEvents( ) override; + + //ICoreStyle + virtual void SetItem(sal_uInt16 eAtr, const SfxPoolItem& rItem) override; + virtual const SfxPoolItem* GetItem(sal_uInt16 eAtr) override; + virtual css::document::XEventsSupplier& GetEventsSupplier() override + { return *this; }; +}; + +class SwXPageStyle + : public SwXStyle +{ +protected: + void SetPropertyValues_Impl( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ); + css::uno::Sequence< css::uno::Any > GetPropertyValues_Impl( const css::uno::Sequence< OUString >& aPropertyNames ); + +public: + SwXPageStyle(SfxStyleSheetBasePool& rPool, SwDocShell* pDocSh, const OUString& rStyleName); + explicit SwXPageStyle(SwDocShell* pDocSh); + + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + + virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ) override; + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames ) override; +}; + +} + +using sw::XStyleFamily; + +OUString SwXStyleFamilies::getImplementationName() + { return {"SwXStyleFamilies"}; } + +sal_Bool SwXStyleFamilies::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SwXStyleFamilies::getSupportedServiceNames() + { return { "com.sun.star.style.StyleFamilies" }; } + +SwXStyleFamilies::SwXStyleFamilies(SwDocShell& rDocShell) : + SwUnoCollection(rDocShell.GetDoc()), + m_pDocShell(&rDocShell) + { } + +SwXStyleFamilies::~SwXStyleFamilies() + { } + +uno::Any SAL_CALL SwXStyleFamilies::getByName(const OUString& Name) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + throw uno::RuntimeException(); + auto pEntries(lcl_GetStyleFamilyEntries()); + const auto pEntry = std::find_if(pEntries->begin(), pEntries->end(), + [&Name] (const StyleFamilyEntry& e) { return e.m_sName == Name; }); + if(pEntry == pEntries->end()) + throw container::NoSuchElementException(); + return getByIndex(pEntry-pEntries->begin()); +} + +uno::Sequence< OUString > SwXStyleFamilies::getElementNames() +{ + auto pEntries(lcl_GetStyleFamilyEntries()); + uno::Sequence<OUString> aNames(pEntries->size()); + std::transform(pEntries->begin(), pEntries->end(), + aNames.begin(), [] (const StyleFamilyEntry& e) { return e.m_sName; }); + return aNames; +} + +sal_Bool SwXStyleFamilies::hasByName(const OUString& Name) +{ + auto pEntries(lcl_GetStyleFamilyEntries()); + return std::any_of(pEntries->begin(), pEntries->end(), + [&Name] (const StyleFamilyEntry& e) { return e.m_sName == Name; }); +} + +sal_Int32 SwXStyleFamilies::getCount() +{ + return lcl_GetStyleFamilyEntries()->size(); +} + +uno::Any SwXStyleFamilies::getByIndex(sal_Int32 nIndex) +{ + auto pEntries(lcl_GetStyleFamilyEntries()); + SolarMutexGuard aGuard; + if(nIndex < 0 || nIndex >= static_cast<sal_Int32>(pEntries->size())) + throw lang::IndexOutOfBoundsException(); + if(!IsValid()) + throw uno::RuntimeException(); + auto eFamily = (*pEntries)[nIndex].m_eFamily; + assert(eFamily != SfxStyleFamily::All); + auto& rxFamily = m_vFamilies[eFamily]; + if(!rxFamily.is()) + rxFamily = new XStyleFamily(m_pDocShell, eFamily); + return uno::makeAny(rxFamily); +} + +uno::Type SwXStyleFamilies::getElementType() +{ + return cppu::UnoType<container::XNameContainer>::get(); +} + +sal_Bool SwXStyleFamilies::hasElements() + { return true; } + +void SwXStyleFamilies::loadStylesFromURL(const OUString& rURL, + const uno::Sequence< beans::PropertyValue >& aOptions) +{ + SolarMutexGuard aGuard; + if(!IsValid() || rURL.isEmpty()) + throw uno::RuntimeException(); + SwgReaderOption aOpt; + aOpt.SetFrameFormats(true); + aOpt.SetTextFormats(true); + aOpt.SetPageDescs(true); + aOpt.SetNumRules(true); + aOpt.SetMerge(false); + for(const auto& rProperty: aOptions) + { + bool bValue = false; + if(rProperty.Value.getValueType() == cppu::UnoType<bool>::get()) + bValue = rProperty.Value.get<bool>(); + + if(rProperty.Name == UNO_NAME_OVERWRITE_STYLES) + aOpt.SetMerge(!bValue); + else if(rProperty.Name == UNO_NAME_LOAD_NUMBERING_STYLES) + aOpt.SetNumRules(bValue); + else if(rProperty.Name == UNO_NAME_LOAD_PAGE_STYLES) + aOpt.SetPageDescs(bValue); + else if(rProperty.Name == UNO_NAME_LOAD_FRAME_STYLES) + aOpt.SetFrameFormats(bValue); + else if(rProperty.Name == UNO_NAME_LOAD_TEXT_STYLES) + aOpt.SetTextFormats(bValue); + else if(rProperty.Name == "InputStream") + { + Reference<XInputStream> xInputStream; + if (rProperty.Value >>= xInputStream) + aOpt.SetInputStream(xInputStream); + else + throw IllegalArgumentException("Parameter 'InputStream' could not be converted to " + "type 'com::sun::star::io::XInputStream'", + nullptr, 0); + } + } + const ErrCode nErr = m_pDocShell->LoadStylesFromFile( rURL, aOpt, true ); + if(nErr) + throw io::IOException(); +} + +uno::Sequence< beans::PropertyValue > SwXStyleFamilies::getStyleLoaderOptions() +{ + SolarMutexGuard aGuard; + uno::Sequence< beans::PropertyValue > aSeq(5); + beans::PropertyValue* pArray = aSeq.getArray(); + const uno::Any aVal(true); + pArray[0] = beans::PropertyValue(UNO_NAME_LOAD_TEXT_STYLES, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[1] = beans::PropertyValue(UNO_NAME_LOAD_FRAME_STYLES, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[2] = beans::PropertyValue(UNO_NAME_LOAD_PAGE_STYLES, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[3] = beans::PropertyValue(UNO_NAME_LOAD_NUMBERING_STYLES, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[4] = beans::PropertyValue(UNO_NAME_OVERWRITE_STYLES, -1, aVal, beans::PropertyState_DIRECT_VALUE); + return aSeq; +} + +static bool lcl_GetHeaderFooterItem( + SfxItemSet const& rSet, OUString const& rPropName, bool const bFooter, + SvxSetItem const*& o_rpItem) +{ + SfxItemState eState = rSet.GetItemState( + bFooter ? SID_ATTR_PAGE_FOOTERSET : SID_ATTR_PAGE_HEADERSET, + false, reinterpret_cast<const SfxPoolItem**>(&o_rpItem)); + if (SfxItemState::SET != eState && + rPropName == UNO_NAME_FIRST_IS_SHARED) + { // fdo#79269 header may not exist, check footer then + eState = rSet.GetItemState( + (!bFooter) ? SID_ATTR_PAGE_FOOTERSET : SID_ATTR_PAGE_HEADERSET, + false, reinterpret_cast<const SfxPoolItem**>(&o_rpItem)); + } + return SfxItemState::SET == eState; +} + +template<enum SfxStyleFamily> +static sal_Int32 lcl_GetCountOrName(const SwDoc&, OUString*, sal_Int32); + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Char>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + const sal_uInt16 nBaseCount = nPoolChrHtmlRange + nPoolChrNormalRange; + nIndex -= nBaseCount; + sal_Int32 nCount = 0; + for(auto pFormat : *rDoc.GetCharFormats()) + { + if(pFormat->IsDefault() && pFormat != rDoc.GetDfltCharFormat()) + continue; + if(!IsPoolUserFormat(pFormat->GetPoolFormatId())) + continue; + if(nIndex == nCount) + { + // the default character format needs to be set to "Default!" + if(rDoc.GetDfltCharFormat() == pFormat) + *pString = SwResId(STR_POOLCHR_STANDARD); + else + *pString = pFormat->GetName(); + break; + } + ++nCount; + } + return nCount + nBaseCount; +} + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Para>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + const sal_uInt16 nBaseCount = nPoolCollHtmlStackedStart + nPoolCollHtmlRange; + nIndex -= nBaseCount; + sal_Int32 nCount = 0; + for(auto pColl : *rDoc.GetTextFormatColls()) + { + if(pColl->IsDefault()) + continue; + if(!IsPoolUserFormat(pColl->GetPoolFormatId())) + continue; + if(nIndex == nCount) + { + *pString = pColl->GetName(); + break; + } + ++nCount; + } + return nCount + nBaseCount; +} + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Frame>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + nIndex -= nPoolFrameRange; + sal_Int32 nCount = 0; + for(const auto pFormat : *rDoc.GetFrameFormats()) + { + if(pFormat->IsDefault() || pFormat->IsAuto()) + continue; + if(!IsPoolUserFormat(pFormat->GetPoolFormatId())) + continue; + if(nIndex == nCount) + { + *pString = pFormat->GetName(); + break; + } + ++nCount; + } + return nCount + nPoolFrameRange; +} + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Page>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + nIndex -= nPoolPageRange; + sal_Int32 nCount = 0; + const size_t nArrLen = rDoc.GetPageDescCnt(); + for(size_t i = 0; i < nArrLen; ++i) + { + const SwPageDesc& rDesc = rDoc.GetPageDesc(i); + if(!IsPoolUserFormat(rDesc.GetPoolFormatId())) + continue; + if(nIndex == nCount) + { + *pString = rDesc.GetName(); + break; + } + ++nCount; + } + nCount += nPoolPageRange; + return nCount; +} + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Pseudo>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + nIndex -= nPoolNumRange; + sal_Int32 nCount = 0; + for(const auto pRule : rDoc.GetNumRuleTable()) + { + if(pRule->IsAutoRule()) + continue; + if(!IsPoolUserFormat(pRule->GetPoolFormatId())) + continue; + if(nIndex == nCount) + { + *pString = pRule->GetName(); + break; + } + ++nCount; + } + return nCount + nPoolNumRange; +} + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Table>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + if (!rDoc.HasTableStyles()) + return 0; + + const auto pAutoFormats = &rDoc.GetTableStyles(); + const sal_Int32 nCount = pAutoFormats->size(); + if (0 <= nIndex && nIndex < nCount) + *pString = pAutoFormats->operator[](nIndex).GetName(); + + return nCount; +} + +template<> +sal_Int32 lcl_GetCountOrName<SfxStyleFamily::Cell>(const SwDoc& rDoc, OUString* pString, sal_Int32 nIndex) +{ + const auto& rAutoFormats = rDoc.GetTableStyles(); + const auto& rTableTemplateMap = SwTableAutoFormat::GetTableTemplateMap(); + const sal_Int32 nUsedCellStylesCount = rAutoFormats.size() * rTableTemplateMap.size(); + const sal_Int32 nCount = nUsedCellStylesCount + rDoc.GetCellStyles().size(); + if (0 <= nIndex && nIndex < nCount) + { + if (nUsedCellStylesCount > nIndex) + { + const sal_Int32 nAutoFormat = nIndex / rTableTemplateMap.size(); + const sal_Int32 nBoxFormat = rTableTemplateMap[nIndex % rTableTemplateMap.size()]; + const SwTableAutoFormat& rTableFormat = rAutoFormats[nAutoFormat]; + SwStyleNameMapper::FillProgName(rTableFormat.GetName(), *pString, SwGetPoolIdFromName::TabStyle); + *pString += rTableFormat.GetTableTemplateCellSubName(rTableFormat.GetBoxFormat(nBoxFormat)); + } + else + *pString = rDoc.GetCellStyles()[nIndex-nUsedCellStylesCount].GetName(); + } + return nCount; +} + +template<SfxStyleFamily eFamily> +static uno::Reference< css::style::XStyle> lcl_CreateStyle(SfxStyleSheetBasePool* pBasePool, SwDocShell* pDocShell, const OUString& sStyleName) + { return pBasePool ? new SwXStyle(pBasePool, eFamily, pDocShell->GetDoc(), sStyleName) : new SwXStyle(pDocShell->GetDoc(), eFamily, false); }; + +template<> +uno::Reference< css::style::XStyle> lcl_CreateStyle<SfxStyleFamily::Para>(SfxStyleSheetBasePool* pBasePool, SwDocShell* pDocShell, const OUString& sStyleName) + { return pBasePool ? new SwXStyle(pBasePool, SfxStyleFamily::Para, pDocShell->GetDoc(), sStyleName) : new SwXStyle(pDocShell->GetDoc(), SfxStyleFamily::Para, false); }; +template<> +uno::Reference< css::style::XStyle> lcl_CreateStyle<SfxStyleFamily::Frame>(SfxStyleSheetBasePool* pBasePool, SwDocShell* pDocShell, const OUString& sStyleName) + { return pBasePool ? new SwXFrameStyle(*pBasePool, pDocShell->GetDoc(), sStyleName) : new SwXFrameStyle(pDocShell->GetDoc()); }; + +template<> +uno::Reference< css::style::XStyle> lcl_CreateStyle<SfxStyleFamily::Page>(SfxStyleSheetBasePool* pBasePool, SwDocShell* pDocShell, const OUString& sStyleName) + { return pBasePool ? new SwXPageStyle(*pBasePool, pDocShell, sStyleName) : new SwXPageStyle(pDocShell); }; + +template<> +uno::Reference< css::style::XStyle> lcl_CreateStyle<SfxStyleFamily::Table>(SfxStyleSheetBasePool* /*pBasePool*/, SwDocShell* pDocShell, const OUString& sStyleName) + { return SwXTextTableStyle::CreateXTextTableStyle(pDocShell, sStyleName); }; + +template<> +uno::Reference< css::style::XStyle> lcl_CreateStyle<SfxStyleFamily::Cell>(SfxStyleSheetBasePool* /*pBasePool*/, SwDocShell* pDocShell, const OUString& sStyleName) + { return SwXTextCellStyle::CreateXTextCellStyle(pDocShell, sStyleName); }; + +uno::Reference<css::style::XStyle> SwXStyleFamilies::CreateStyle(SfxStyleFamily eFamily, SwDoc& rDoc) +{ + auto pEntries(lcl_GetStyleFamilyEntries()); + const auto pEntry = std::find_if(pEntries->begin(), pEntries->end(), + [eFamily] (const StyleFamilyEntry& e) { return e.m_eFamily == eFamily; }); + return pEntry == pEntries->end() ? nullptr : pEntry->m_fCreateStyle(nullptr, rDoc.GetDocShell(), ""); +} + +// FIXME: Ugly special casing that should die. +uno::Reference<css::style::XStyle> SwXStyleFamilies::CreateStyleCondParagraph(SwDoc& rDoc) + { return new SwXStyle(&rDoc, SfxStyleFamily::Para, true); }; + +template<enum SfxStyleFamily> +static sal_uInt16 lcl_TranslateIndex(const sal_uInt16 nIndex); + +template<> +sal_uInt16 lcl_TranslateIndex<SfxStyleFamily::Char>(const sal_uInt16 nIndex) +{ + static_assert(nPoolChrNormalRange > 0 && nPoolChrHtmlRange > 0, "invalid pool range"); + if(nIndex < nPoolChrNormalRange) + return nIndex + RES_POOLCHR_NORMAL_BEGIN; + else if(nIndex < (nPoolChrHtmlRange+nPoolChrNormalRange)) + return nIndex + RES_POOLCHR_HTML_BEGIN - nPoolChrNormalRange; + throw lang::IndexOutOfBoundsException(); +} + +template<> +sal_uInt16 lcl_TranslateIndex<SfxStyleFamily::Para>(const sal_uInt16 nIndex) +{ + static_assert(nPoolCollTextRange > 0 && nPoolCollListsRange > 0 && nPoolCollExtraRange > 0 && nPoolCollRegisterRange > 0 && nPoolCollDocRange > 0 && nPoolCollHtmlRange > 0, "weird pool range"); + if(nIndex < nPoolCollListsStackedStart) + return nIndex + RES_POOLCOLL_TEXT_BEGIN; + else if(nIndex < nPoolCollExtraStackedStart) + return nIndex + RES_POOLCOLL_LISTS_BEGIN - nPoolCollListsStackedStart; + else if(nIndex < nPoolCollRegisterStackedStart) + return nIndex + RES_POOLCOLL_EXTRA_BEGIN - nPoolCollExtraStackedStart; + else if(nIndex < nPoolCollDocStackedStart) + return nIndex + RES_POOLCOLL_REGISTER_BEGIN - nPoolCollRegisterStackedStart; + else if(nIndex < nPoolCollHtmlStackedStart) + return nIndex + RES_POOLCOLL_DOC_BEGIN - nPoolCollDocStackedStart; + else if(nIndex < nPoolCollHtmlStackedStart + nPoolCollTextRange) + return nIndex + RES_POOLCOLL_HTML_BEGIN - nPoolCollHtmlStackedStart; + throw lang::IndexOutOfBoundsException(); +} + +template<> +sal_uInt16 lcl_TranslateIndex<SfxStyleFamily::Table>(const sal_uInt16 nIndex) +{ + return nIndex; +} + +template<> +sal_uInt16 lcl_TranslateIndex<SfxStyleFamily::Cell>(const sal_uInt16 nIndex) +{ + return nIndex; +} + +template<sal_uInt16 nRangeBegin, sal_uInt16 nRangeSize> +static sal_uInt16 lcl_TranslateIndexRange(const sal_uInt16 nIndex) +{ + if(nIndex < nRangeSize) + return nIndex + nRangeBegin; + throw lang::IndexOutOfBoundsException(); +} + +uno::Any XStyleFamily::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(nIndex < 0) + throw lang::IndexOutOfBoundsException(); + if(!m_pBasePool) + throw uno::RuntimeException(); + OUString sStyleName; + try + { + SwStyleNameMapper::FillUIName(m_rEntry.m_fTranslateIndex(nIndex), sStyleName); + } catch(...) {} + if (sStyleName.isEmpty()) + GetCountOrName(&sStyleName, nIndex); + if(sStyleName.isEmpty()) + throw lang::IndexOutOfBoundsException(); + return getByName(sStyleName); +} + +uno::Any XStyleFamily::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + OUString sStyleName; + SwStyleNameMapper::FillUIName(rName, sStyleName, m_rEntry.m_aPoolId); + if(!m_pBasePool) + throw uno::RuntimeException(); + SfxStyleSheetBase* pBase = m_pBasePool->Find(sStyleName, m_rEntry.m_eFamily); + if(!pBase) + throw container::NoSuchElementException(); + uno::Reference<style::XStyle> xStyle = FindStyle(sStyleName); + if(!xStyle.is()) + xStyle = m_rEntry.m_fCreateStyle(m_pBasePool, m_pDocShell, m_rEntry.m_eFamily == SfxStyleFamily::Frame ? pBase->GetName() : sStyleName); + return uno::makeAny(xStyle); +} + +uno::Sequence<OUString> XStyleFamily::getElementNames() +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + std::vector<OUString> vRet; + std::unique_ptr<SfxStyleSheetIterator> pIt = m_pBasePool->CreateIterator(m_rEntry.m_eFamily); + for (SfxStyleSheetBase* pStyle = pIt->First(); pStyle; pStyle = pIt->Next()) + { + OUString sName; + SwStyleNameMapper::FillProgName(pStyle->GetName(), sName, m_rEntry.m_aPoolId); + vRet.push_back(sName); + } + return comphelper::containerToSequence(vRet); +} + +sal_Bool XStyleFamily::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + OUString sStyleName; + SwStyleNameMapper::FillUIName(rName, sStyleName, m_rEntry.m_aPoolId); + SfxStyleSheetBase* pBase = m_pBasePool->Find(sStyleName, m_rEntry.m_eFamily); + return nullptr != pBase; +} + +void XStyleFamily::insertByName(const OUString& rName, const uno::Any& rElement) +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + OUString sStyleName; + SwStyleNameMapper::FillUIName(rName, sStyleName, m_rEntry.m_aPoolId); + SfxStyleSheetBase* pBase = m_pBasePool->Find(sStyleName, m_rEntry.m_eFamily); + SfxStyleSheetBase* pUINameBase = m_pBasePool->Find(sStyleName, m_rEntry.m_eFamily); + if(pBase || pUINameBase) + throw container::ElementExistException(); + if(rElement.getValueType().getTypeClass() != uno::TypeClass_INTERFACE) + throw lang::IllegalArgumentException(); + if (SwGetPoolIdFromName::CellStyle == m_rEntry.m_aPoolId) + { + // handle cell style + uno::Reference<style::XStyle> xStyle = rElement.get<uno::Reference<style::XStyle>>(); + SwXTextCellStyle* pNewStyle = dynamic_cast<SwXTextCellStyle*>(xStyle.get()); + if (!pNewStyle) + throw lang::IllegalArgumentException(); + + pNewStyle->setName(sStyleName); // insertByName sets the element name + m_pDocShell->GetDoc()->GetCellStyles().AddBoxFormat(*pNewStyle->GetBoxFormat(), sStyleName); + pNewStyle->SetPhysical(); + } + else if (SwGetPoolIdFromName::TabStyle == m_rEntry.m_aPoolId) + { + // handle table style + uno::Reference<style::XStyle> xStyle = rElement.get<uno::Reference<style::XStyle>>(); + SwXTextTableStyle* pNewStyle = dynamic_cast<SwXTextTableStyle*>(xStyle.get()); + if (!pNewStyle) + throw lang::IllegalArgumentException(); + + pNewStyle->setName(sStyleName); // insertByName sets the element name + m_pDocShell->GetDoc()->GetTableStyles().AddAutoFormat(*pNewStyle->GetTableFormat()); + pNewStyle->SetPhysical(); + } + else + { + uno::Reference<lang::XUnoTunnel> xStyleTunnel = rElement.get<uno::Reference<lang::XUnoTunnel>>(); + SwXStyle* pNewStyle = nullptr; + if(xStyleTunnel.is()) + { + pNewStyle = reinterpret_cast< SwXStyle * >( + sal::static_int_cast< sal_IntPtr >( xStyleTunnel->getSomething( SwXStyle::getUnoTunnelId()) )); + } + + if (!pNewStyle || !pNewStyle->IsDescriptor() || pNewStyle->GetFamily() != m_rEntry.m_eFamily) + throw lang::IllegalArgumentException(); + + SfxStyleSearchBits nMask = SfxStyleSearchBits::All; + if(m_rEntry.m_eFamily == SfxStyleFamily::Para && !pNewStyle->IsConditional()) + nMask &= ~SfxStyleSearchBits::SwCondColl; + m_pBasePool->Make(sStyleName, m_rEntry.m_eFamily, nMask); + pNewStyle->SetDoc(m_pDocShell->GetDoc(), m_pBasePool); + pNewStyle->SetStyleName(sStyleName); + const OUString sParentStyleName(pNewStyle->GetParentStyleName()); + if (!sParentStyleName.isEmpty()) + { + SfxStyleSheetBase* pParentBase = m_pBasePool->Find(sParentStyleName, m_rEntry.m_eFamily); + if(pParentBase && pParentBase->GetFamily() == m_rEntry.m_eFamily && + pParentBase->GetPool() == m_pBasePool) + m_pBasePool->SetParent(m_rEntry.m_eFamily, sStyleName, sParentStyleName); + } + // after all, we still need to apply the properties of the descriptor + pNewStyle->ApplyDescriptorProperties(); + } +} + +void XStyleFamily::replaceByName(const OUString& rName, const uno::Any& rElement) +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + OUString sStyleName; + SwStyleNameMapper::FillUIName(rName, sStyleName, m_rEntry.m_aPoolId); + SfxStyleSheetBase* pBase = m_pBasePool->Find(sStyleName, m_rEntry.m_eFamily); + // replacements only for userdefined styles + if(!pBase) + throw container::NoSuchElementException(); + if (SwGetPoolIdFromName::CellStyle == m_rEntry.m_aPoolId) + { + // handle cell styles, don't call on assigned cell styles (TableStyle child) + OUString sParent; + SwBoxAutoFormat* pBoxAutoFormat = SwXTextCellStyle::GetBoxAutoFormat(m_pDocShell, sStyleName, &sParent); + if (pBoxAutoFormat && sParent.isEmpty())// if parent exists then this style is assigned to a table style. Don't replace. + { + uno::Reference<style::XStyle> xStyle = rElement.get<uno::Reference<style::XStyle>>(); + SwXTextCellStyle* pStyleToReplaceWith = dynamic_cast<SwXTextCellStyle*>(xStyle.get()); + if (!pStyleToReplaceWith) + throw lang::IllegalArgumentException(); + + pStyleToReplaceWith->setName(sStyleName); + *pBoxAutoFormat = *pStyleToReplaceWith->GetBoxFormat(); + pStyleToReplaceWith->SetPhysical(); + } + } + else if (SwGetPoolIdFromName::TabStyle == m_rEntry.m_aPoolId) + { + // handle table styles + SwTableAutoFormat* pTableAutoFormat = SwXTextTableStyle::GetTableAutoFormat(m_pDocShell, sStyleName); + if (pTableAutoFormat) + { + uno::Reference<style::XStyle> xStyle = rElement.get<uno::Reference<style::XStyle>>(); + SwXTextTableStyle* pStyleToReplaceWith = dynamic_cast<SwXTextTableStyle*>(xStyle.get()); + if (!pStyleToReplaceWith) + throw lang::IllegalArgumentException(); + + pStyleToReplaceWith->setName(sStyleName); + *pTableAutoFormat = *pStyleToReplaceWith->GetTableFormat(); + pStyleToReplaceWith->SetPhysical(); + } + } + else + { + if(!pBase->IsUserDefined()) + throw lang::IllegalArgumentException(); + //if there's an object available to this style then it must be invalidated + uno::Reference<style::XStyle> xStyle = FindStyle(pBase->GetName()); + if(xStyle.is()) + { + SwXStyle* pStyle = comphelper::getUnoTunnelImplementation<SwXStyle>(xStyle); + if(pStyle) + pStyle->Invalidate(); + } + m_pBasePool->Remove(pBase); + insertByName(rName, rElement); + } +} + +void XStyleFamily::removeByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + OUString sName; + SwStyleNameMapper::FillUIName(rName, sName, m_rEntry.m_aPoolId); + SfxStyleSheetBase* pBase = m_pBasePool->Find(sName, m_rEntry.m_eFamily); + if(!pBase) + throw container::NoSuchElementException(); + if (SwGetPoolIdFromName::CellStyle == m_rEntry.m_aPoolId) + { + // handle cell style + m_pDocShell->GetDoc()->GetCellStyles().RemoveBoxFormat(rName); + } + else if (SwGetPoolIdFromName::TabStyle == m_rEntry.m_aPoolId) + { + // handle table style + m_pDocShell->GetDoc()->GetTableStyles().EraseAutoFormat(rName); + } + else + m_pBasePool->Remove(pBase); +} + +uno::Any SAL_CALL XStyleFamily::getPropertyValue( const OUString& sPropertyName ) +{ + if(sPropertyName != "DisplayName") + throw beans::UnknownPropertyException( "unknown property: " + sPropertyName, static_cast<OWeakObject *>(this) ); + SolarMutexGuard aGuard; + return uno::makeAny(SwResId(m_rEntry.m_pResId)); +} + + +SwXStyle* XStyleFamily::FindStyle(const OUString& rStyleName) const +{ + const size_t nLCount = m_pBasePool->GetSizeOfVector(); + for(size_t i = 0; i < nLCount; ++i) + { + SfxListener* pListener = m_pBasePool->GetListener(i); + SwXStyle* pTempStyle = dynamic_cast<SwXStyle*>(pListener); + if(pTempStyle && pTempStyle->GetFamily() == m_rEntry.m_eFamily && pTempStyle->GetStyleName() == rStyleName) + return pTempStyle; + } + return nullptr; +} + +static const std::vector<StyleFamilyEntry>* lcl_GetStyleFamilyEntries() +{ + if(!our_pStyleFamilyEntries) + { + our_pStyleFamilyEntries = new std::vector<StyleFamilyEntry>{ + { SfxStyleFamily::Char, PROPERTY_MAP_CHAR_STYLE, SwGetPoolIdFromName::ChrFmt, "CharacterStyles", STR_STYLE_FAMILY_CHARACTER, &lcl_GetCountOrName<SfxStyleFamily::Char>, &lcl_CreateStyle<SfxStyleFamily::Char>, &lcl_TranslateIndex<SfxStyleFamily::Char> }, + { SfxStyleFamily::Para, PROPERTY_MAP_PARA_STYLE, SwGetPoolIdFromName::TxtColl, "ParagraphStyles", STR_STYLE_FAMILY_PARAGRAPH, &lcl_GetCountOrName<SfxStyleFamily::Para>, &lcl_CreateStyle<SfxStyleFamily::Para>, &lcl_TranslateIndex<SfxStyleFamily::Para> }, + { SfxStyleFamily::Page, PROPERTY_MAP_PAGE_STYLE, SwGetPoolIdFromName::PageDesc, "PageStyles", STR_STYLE_FAMILY_PAGE, &lcl_GetCountOrName<SfxStyleFamily::Page>, &lcl_CreateStyle<SfxStyleFamily::Page>, &lcl_TranslateIndexRange<RES_POOLPAGE_BEGIN, nPoolPageRange> }, + { SfxStyleFamily::Frame, PROPERTY_MAP_FRAME_STYLE, SwGetPoolIdFromName::FrmFmt, "FrameStyles", STR_STYLE_FAMILY_FRAME, &lcl_GetCountOrName<SfxStyleFamily::Frame>, &lcl_CreateStyle<SfxStyleFamily::Frame>, &lcl_TranslateIndexRange<RES_POOLFRM_BEGIN, nPoolFrameRange> }, + { SfxStyleFamily::Pseudo, PROPERTY_MAP_NUM_STYLE, SwGetPoolIdFromName::NumRule, "NumberingStyles", STR_STYLE_FAMILY_NUMBERING, &lcl_GetCountOrName<SfxStyleFamily::Pseudo>, &lcl_CreateStyle<SfxStyleFamily::Pseudo>, &lcl_TranslateIndexRange<RES_POOLNUMRULE_BEGIN, nPoolNumRange> }, + { SfxStyleFamily::Table, PROPERTY_MAP_TABLE_STYLE, SwGetPoolIdFromName::TabStyle, "TableStyles", STR_STYLE_FAMILY_TABLE, &lcl_GetCountOrName<SfxStyleFamily::Table>, &lcl_CreateStyle<SfxStyleFamily::Table>, &lcl_TranslateIndex<SfxStyleFamily::Table> }, + { SfxStyleFamily::Cell, PROPERTY_MAP_CELL_STYLE, SwGetPoolIdFromName::CellStyle,"CellStyles", STR_STYLE_FAMILY_CELL, &lcl_GetCountOrName<SfxStyleFamily::Cell>, &lcl_CreateStyle<SfxStyleFamily::Cell>, &lcl_TranslateIndex<SfxStyleFamily::Cell> } + }; + } + return our_pStyleFamilyEntries; +} + +static const std::vector<ParagraphStyleCategoryEntry>* lcl_GetParagraphStyleCategoryEntries() +{ + if(!our_pParagraphStyleCategoryEntries) + { + our_pParagraphStyleCategoryEntries = new std::vector<ParagraphStyleCategoryEntry>{ + { style::ParagraphStyleCategory::TEXT, SfxStyleSearchBits::SwText, COLL_TEXT_BITS }, + { style::ParagraphStyleCategory::CHAPTER, SfxStyleSearchBits::SwChapter, COLL_DOC_BITS }, + { style::ParagraphStyleCategory::LIST, SfxStyleSearchBits::SwList, COLL_LISTS_BITS }, + { style::ParagraphStyleCategory::INDEX, SfxStyleSearchBits::SwIndex, COLL_REGISTER_BITS }, + { style::ParagraphStyleCategory::EXTRA, SfxStyleSearchBits::SwExtra, COLL_EXTRA_BITS }, + { style::ParagraphStyleCategory::HTML, SfxStyleSearchBits::SwHtml, COLL_HTML_BITS } + }; + } + return our_pParagraphStyleCategoryEntries; +} + +namespace { + +class SwStyleProperties_Impl +{ + const PropertyEntryVector_t aPropertyEntries; + std::map<OUString, uno::Any> m_vPropertyValues; +public: + explicit SwStyleProperties_Impl(const SfxItemPropertyMap& rMap) + : aPropertyEntries(rMap.getPropertyEntries()) + { } + + bool AllowsKey(const OUString& rName) + { + return std::any_of(aPropertyEntries.begin(), aPropertyEntries.end(), + [rName] (const SfxItemPropertyNamedEntry& rEntry) {return rName == rEntry.sName;} ); + } + bool SetProperty(const OUString& rName, const uno::Any& rValue) + { + if(!AllowsKey(rName)) + return false; + m_vPropertyValues[rName] = rValue; + return true; + } + void GetProperty(const OUString& rName, const uno::Any*& pAny) + { + if(!AllowsKey(rName)) + { + pAny = nullptr; + return; + } + pAny = &m_vPropertyValues[rName]; + return; + } + bool ClearProperty( const OUString& rName ) + { + if(!AllowsKey(rName)) + return false; + m_vPropertyValues[rName] = uno::Any(); + return true; + } + void ClearAllProperties( ) + { m_vPropertyValues.clear(); } + void Apply(SwXStyle& rStyle) + { + for(const auto& rPropertyPair : m_vPropertyValues) + { + if(rPropertyPair.second.hasValue()) + rStyle.setPropertyValue(rPropertyPair.first, rPropertyPair.second); + } + } + static void GetProperty(const OUString &rPropertyName, const uno::Reference < beans::XPropertySet > &rxPropertySet, uno::Any& rAny ) + { + rAny = rxPropertySet->getPropertyValue( rPropertyName ); + } +}; + +} + +static SwGetPoolIdFromName lcl_GetSwEnumFromSfxEnum(SfxStyleFamily eFamily) +{ + auto pEntries(lcl_GetStyleFamilyEntries()); + const auto pEntry = std::find_if(pEntries->begin(), pEntries->end(), + [eFamily] (const StyleFamilyEntry& e) { return e.m_eFamily == eFamily; }); + if(pEntry != pEntries->end()) + return pEntry->m_aPoolId; + SAL_WARN("sw.uno", "someone asking for all styles in unostyle.cxx!" ); + return SwGetPoolIdFromName::ChrFmt; +} + +namespace +{ + class theSwXStyleUnoTunnelId : public rtl::Static<UnoTunnelIdInit, theSwXStyleUnoTunnelId> {}; +} + +const uno::Sequence<sal_Int8>& SwXStyle::getUnoTunnelId() +{ + return theSwXStyleUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXStyle::getSomething(const uno::Sequence<sal_Int8>& rId) +{ + if(isUnoTunnelId<SwXStyle>(rId)) + { + return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this)); + } + return 0; +} + + +uno::Sequence< OUString > SwXStyle::getSupportedServiceNames() +{ + long nCount = 1; + if(SfxStyleFamily::Para == m_rEntry.m_eFamily) + { + nCount = 5; + if(m_bIsConditional) + nCount++; + } + else if(SfxStyleFamily::Char == m_rEntry.m_eFamily) + nCount = 5; + else if(SfxStyleFamily::Page == m_rEntry.m_eFamily) + nCount = 3; + uno::Sequence< OUString > aRet(nCount); + OUString* pArray = aRet.getArray(); + pArray[0] = "com.sun.star.style.Style"; + switch(m_rEntry.m_eFamily) + { + case SfxStyleFamily::Char: + pArray[1] = "com.sun.star.style.CharacterStyle"; + pArray[2] = "com.sun.star.style.CharacterProperties"; + pArray[3] = "com.sun.star.style.CharacterPropertiesAsian"; + pArray[4] = "com.sun.star.style.CharacterPropertiesComplex"; + break; + case SfxStyleFamily::Page: + pArray[1] = "com.sun.star.style.PageStyle"; + pArray[2] = "com.sun.star.style.PageProperties"; + break; + case SfxStyleFamily::Para: + pArray[1] = "com.sun.star.style.ParagraphStyle"; + pArray[2] = "com.sun.star.style.ParagraphProperties"; + pArray[3] = "com.sun.star.style.ParagraphPropertiesAsian"; + pArray[4] = "com.sun.star.style.ParagraphPropertiesComplex"; + if(m_bIsConditional) + pArray[5] = "com.sun.star.style.ConditionalParagraphStyle"; + break; + + default: + ; + } + return aRet; +} + +static uno::Reference<beans::XPropertySet> lcl_InitStandardStyle(const SfxStyleFamily eFamily, uno::Reference<container::XNameAccess> const & rxStyleFamily) +{ + using return_t = decltype(lcl_InitStandardStyle(eFamily, rxStyleFamily)); + if(eFamily != SfxStyleFamily::Para && eFamily != SfxStyleFamily::Page) + return {}; + auto aResult(rxStyleFamily->getByName("Standard")); + if(!aResult.has<return_t>()) + return {}; + return aResult.get<return_t>(); +} + +static uno::Reference<container::XNameAccess> lcl_InitStyleFamily(SwDoc* pDoc, const StyleFamilyEntry& rEntry) +{ + using return_t = decltype(lcl_InitStyleFamily(pDoc, rEntry)); + if(rEntry.m_eFamily != SfxStyleFamily::Char + && rEntry.m_eFamily != SfxStyleFamily::Para + && rEntry.m_eFamily != SfxStyleFamily::Page) + return {}; + auto xModel(pDoc->GetDocShell()->GetBaseModel()); + uno::Reference<style::XStyleFamiliesSupplier> xFamilySupplier(xModel, uno::UNO_QUERY); + auto xFamilies = xFamilySupplier->getStyleFamilies(); + auto aResult(xFamilies->getByName(rEntry.m_sName)); + if(!aResult.has<return_t>()) + return {}; + return aResult.get<return_t>(); +} + +static bool lcl_InitConditional(SfxStyleSheetBasePool* pBasePool, const SfxStyleFamily eFamily, const OUString& rStyleName) +{ + if(!pBasePool || eFamily != SfxStyleFamily::Para) + return false; + SfxStyleSheetBase* pBase = pBasePool->Find(rStyleName, eFamily); + SAL_WARN_IF(!pBase, "sw.uno", "where is the style?" ); + if(!pBase) + return false; + const sal_uInt16 nId(SwStyleNameMapper::GetPoolIdFromUIName(rStyleName, SwGetPoolIdFromName::TxtColl)); + if(nId != USHRT_MAX) + return ::IsConditionalByPoolId(nId); + return RES_CONDTXTFMTCOLL == static_cast<SwDocStyleSheet*>(pBase)->GetCollection()->Which(); +} + +static const StyleFamilyEntry& lcl_GetStyleEntry(const SfxStyleFamily eFamily) +{ + auto pEntries = lcl_GetStyleFamilyEntries(); + const auto pEntry = std::find_if(pEntries->begin(), pEntries->end(), + [eFamily] (const StyleFamilyEntry& e) { return e.m_eFamily == eFamily; }); + assert(pEntry != pEntries->end()); + return *pEntry; +} + +SwXStyle::SwXStyle(SwDoc* pDoc, SfxStyleFamily eFamily, bool bConditional) + : m_pDoc(pDoc) + , m_rEntry(lcl_GetStyleEntry(eFamily)) + , m_bIsDescriptor(true) + , m_bIsConditional(bConditional) + , m_pBasePool(nullptr) + , m_xStyleFamily(lcl_InitStyleFamily(pDoc, m_rEntry)) + , m_xStyleData(lcl_InitStandardStyle(eFamily, m_xStyleFamily)) +{ + assert(!m_bIsConditional || m_rEntry.m_eFamily == SfxStyleFamily::Para); // only paragraph styles are conditional + // Register ourselves as a listener to the document (via the page descriptor) + SvtListener::StartListening(pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); + m_pPropertiesImpl = std::make_unique<SwStyleProperties_Impl>( + aSwMapProvider.GetPropertySet(m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType)->getPropertyMap()); +} + +SwXStyle::SwXStyle(SfxStyleSheetBasePool* pPool, SfxStyleFamily eFamily, SwDoc* pDoc, const OUString& rStyleName) + : m_pDoc(pDoc) + , m_sStyleName(rStyleName) + , m_rEntry(lcl_GetStyleEntry(eFamily)) + , m_bIsDescriptor(false) + , m_bIsConditional(lcl_InitConditional(pPool, eFamily, rStyleName)) + , m_pBasePool(pPool) +{ } + +SwXStyle::~SwXStyle() +{ + SolarMutexGuard aGuard; + if(m_pBasePool) + SfxListener::EndListening(*m_pBasePool); + m_pPropertiesImpl.reset(); + SvtListener::EndListeningAll(); +} + +void SwXStyle::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pDoc = nullptr; + m_xStyleData.clear(); + m_xStyleFamily.clear(); + } +} + +OUString SwXStyle::getName() +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + return m_sStyleName; + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + SAL_WARN_IF(!pBase, "sw.uno", "where is the style?"); + if(!pBase) + throw uno::RuntimeException(); + OUString aString; + SwStyleNameMapper::FillProgName(pBase->GetName(), aString, lcl_GetSwEnumFromSfxEnum ( m_rEntry.m_eFamily )); + return aString; +} + +void SwXStyle::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + { + m_sStyleName = rName; + return; + } + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + SAL_WARN_IF(!pBase, "sw.uno", "where is the style?"); + if(!pBase || !pBase->IsUserDefined()) + throw uno::RuntimeException(); + rtl::Reference<SwDocStyleSheet> xTmp(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + if(!xTmp->SetName(rName)) + throw uno::RuntimeException(); + m_sStyleName = rName; +} + +sal_Bool SwXStyle::isUserDefined() +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + //if it is not found it must be non user defined + return pBase && pBase->IsUserDefined(); +} + +sal_Bool SwXStyle::isInUse() +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + throw uno::RuntimeException(); + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily, SfxStyleSearchBits::Used); + return pBase && pBase->IsUsed(); +} + +OUString SwXStyle::getParentStyle() +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + { + if(!m_bIsDescriptor) + throw uno::RuntimeException(); + return m_sParentStyleName; + } + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + OUString aString; + if(pBase) + aString = pBase->GetParent(); + SwStyleNameMapper::FillProgName(aString, aString, lcl_GetSwEnumFromSfxEnum(m_rEntry.m_eFamily)); + return aString; +} + +void SwXStyle::setParentStyle(const OUString& rParentStyle) +{ + SolarMutexGuard aGuard; + OUString sParentStyle; + SwStyleNameMapper::FillUIName(rParentStyle, sParentStyle, lcl_GetSwEnumFromSfxEnum ( m_rEntry.m_eFamily ) ); + if(!m_pBasePool) + { + if(!m_bIsDescriptor) + throw uno::RuntimeException(); + m_sParentStyleName = sParentStyle; + try + { + const auto aAny = m_xStyleFamily->getByName(sParentStyle); + m_xStyleData = aAny.get<decltype(m_xStyleData)>(); + } + catch(...) + { } + return; + } + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + if(!pBase) + throw uno::RuntimeException(); + rtl::Reference<SwDocStyleSheet> xBase(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + //make it a 'real' style - necessary for pooled styles + xBase->GetItemSet(); + if(xBase->GetParent() != sParentStyle) + { + if(!xBase->SetParent(sParentStyle)) + throw uno::RuntimeException(); + } +} + +uno::Reference<beans::XPropertySetInfo> SwXStyle::getPropertySetInfo() +{ + if(m_bIsConditional) + { + assert(m_rEntry.m_eFamily == SfxStyleFamily::Para); + static uno::Reference<beans::XPropertySetInfo> xCondParaRef; + xCondParaRef = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CONDITIONAL_PARA_STYLE)->getPropertySetInfo(); + return xCondParaRef; + } + return m_rEntry.m_xPSInfo; +} + +void SwXStyle::ApplyDescriptorProperties() +{ + m_bIsDescriptor = false; + m_xStyleData.clear(); + m_xStyleFamily.clear(); + m_pPropertiesImpl->Apply(*this); +} + +namespace { + +class SwStyleBase_Impl +{ +private: + SwDoc& m_rDoc; + const SwPageDesc* m_pOldPageDesc; + rtl::Reference<SwDocStyleSheet> m_xNewBase; + SfxItemSet* m_pItemSet; + std::unique_ptr<SfxItemSet> m_pMyItemSet; + OUString m_rStyleName; + const SwAttrSet* m_pParentStyle; +public: + SwStyleBase_Impl(SwDoc& rSwDoc, const OUString& rName, const SwAttrSet* pParentStyle) + : m_rDoc(rSwDoc) + , m_pOldPageDesc(nullptr) + , m_pItemSet(nullptr) + , m_rStyleName(rName) + , m_pParentStyle(pParentStyle) + { } + + rtl::Reference<SwDocStyleSheet>& getNewBase() + { + return m_xNewBase; + } + + void setNewBase(SwDocStyleSheet* pNew) + { + m_xNewBase = pNew; + } + + bool HasItemSet() const + { + return m_xNewBase.is(); + } + + SfxItemSet& GetItemSet() + { + assert(m_xNewBase.is()); + if(!m_pItemSet) + { + m_pMyItemSet.reset(new SfxItemSet(m_xNewBase->GetItemSet())); + m_pItemSet = m_pMyItemSet.get(); + + // set parent style to have the correct XFillStyle setting as XFILL_NONE + if(!m_pItemSet->GetParent() && m_pParentStyle) + m_pItemSet->SetParent(m_pParentStyle); + } + return *m_pItemSet; + } + + const SwPageDesc* GetOldPageDesc(); + + // still a hack, but a bit more explicit and with a proper scope + struct ItemSetOverrider + { + SwStyleBase_Impl& m_rStyleBase; + SfxItemSet* m_pOldSet; + ItemSetOverrider(SwStyleBase_Impl& rStyleBase, SfxItemSet* pTemp) + : m_rStyleBase(rStyleBase) + , m_pOldSet(m_rStyleBase.m_pItemSet) + { m_rStyleBase.m_pItemSet = pTemp; } + ~ItemSetOverrider() + { m_rStyleBase.m_pItemSet = m_pOldSet; }; + }; +}; + + const char* STR_POOLPAGE_ARY[] = + { + // Page styles + STR_POOLPAGE_STANDARD, + STR_POOLPAGE_FIRST, + STR_POOLPAGE_LEFT, + STR_POOLPAGE_RIGHT, + STR_POOLPAGE_JAKET, + STR_POOLPAGE_REGISTER, + STR_POOLPAGE_HTML, + STR_POOLPAGE_FOOTNOTE, + STR_POOLPAGE_ENDNOTE, + STR_POOLPAGE_LANDSCAPE + }; +} + +const SwPageDesc* SwStyleBase_Impl::GetOldPageDesc() +{ + if(!m_pOldPageDesc) + { + SwPageDesc *pd = m_rDoc.FindPageDesc(m_rStyleName); + if(pd) + m_pOldPageDesc = pd; + + if(!m_pOldPageDesc) + { + for (size_t i = 0; i < SAL_N_ELEMENTS(STR_POOLPAGE_ARY); ++i) + { + if (SwResId(STR_POOLPAGE_ARY[i]) == m_rStyleName) + { + m_pOldPageDesc = m_rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_BEGIN + i); + break; + } + } + } + } + return m_pOldPageDesc; +} + + + +static sal_uInt8 lcl_TranslateMetric(const SfxItemPropertySimpleEntry& rEntry, SwDoc* pDoc, uno::Any& o_aValue) +{ + // check for needed metric translation + if(!(rEntry.nMoreFlags & PropertyMoreFlags::METRIC_ITEM)) + return rEntry.nMemberId; + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + if((XATTR_FILLBMP_SIZEX == rEntry.nWID || XATTR_FILLBMP_SIZEY == rEntry.nWID) + && o_aValue.has<sal_Int32>() + && o_aValue.get<sal_Int32>() < 0) + return rEntry.nMemberId; + if(!pDoc) + return rEntry.nMemberId; + + const SfxItemPool& rPool = pDoc->GetAttrPool(); + const MapUnit eMapUnit(rPool.GetMetric(rEntry.nWID)); + if(eMapUnit != MapUnit::Map100thMM) + SvxUnoConvertFromMM(eMapUnit, o_aValue); + return rEntry.nMemberId; +} +template<> +void SwXStyle::SetPropertyValue<HINT_BEGIN>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + // default ItemSet handling + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + SfxItemSet aSet(*rStyleSet.GetPool(), {{rEntry.nWID, rEntry.nWID}}); + aSet.SetParent(&rStyleSet); + rPropSet.setPropertyValue(rEntry, rValue, aSet); + rStyleSet.Put(aSet); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_HIDDEN>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + bool bHidden = false; + if(rValue >>= bHidden) + { + //make it a 'real' style - necessary for pooled styles + o_rStyleBase.getNewBase()->GetItemSet(); + o_rStyleBase.getNewBase()->SetHidden(bHidden); + } + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, rValue, o_rStyleBase); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_STYLE_INTEROP_GRAB_BAG>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + o_rStyleBase.getNewBase()->GetItemSet(); + o_rStyleBase.getNewBase()->SetGrabBagItem(rValue); + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, rValue, o_rStyleBase); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(XATTR_FILLGRADIENT)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + uno::Any aValue(rValue); + const auto nMemberId(lcl_TranslateMetric(rEntry, m_pDoc, aValue)); + if(MID_NAME == nMemberId) + { + // add set commands for FillName items + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + if(!aValue.has<OUString>()) + throw lang::IllegalArgumentException(); + SvxShape::SetFillAttribute(rEntry.nWID, aValue.get<OUString>(), rStyleSet); + } + else if(MID_BITMAP == nMemberId) + { + if(sal_uInt16(XATTR_FILLBITMAP) == rEntry.nWID) + { + const Graphic aNullGraphic; + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + XFillBitmapItem aXFillBitmapItem(aNullGraphic); + aXFillBitmapItem.PutValue(aValue, nMemberId); + rStyleSet.Put(aXFillBitmapItem); + } + } + else + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, aValue, o_rStyleBase); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_BACKGROUND)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(rStyleSet, RES_BACKGROUND, true, m_pDoc->IsInXMLImport())); + std::unique_ptr<SvxBrushItem> aChangedBrushItem(aOriginalBrushItem->Clone()); + + uno::Any aValue(rValue); + const auto nMemberId(lcl_TranslateMetric(rEntry, m_pDoc, aValue)); + aChangedBrushItem->PutValue(aValue, nMemberId); + + // 0xff is already the default - but if BackTransparent is set + // to true, it must be applied in the item set on ODF import + // to potentially override parent style, which is unknown yet + if(*aChangedBrushItem == *aOriginalBrushItem && (MID_GRAPHIC_TRANSPARENT != nMemberId || !aValue.has<bool>() || !aValue.get<bool>())) + return; + + setSvxBrushItemAsFillAttributesToTargetSet(*aChangedBrushItem, rStyleSet); +} +template<> +void SwXStyle::SetPropertyValue<OWN_ATTR_FILLBMP_MODE>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + drawing::BitmapMode eMode; + if(!(rValue >>= eMode)) + { + if(!rValue.has<sal_Int32>()) + throw lang::IllegalArgumentException(); + eMode = static_cast<drawing::BitmapMode>(rValue.get<sal_Int32>()); + } + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + rStyleSet.Put(XFillBmpStretchItem(drawing::BitmapMode_STRETCH == eMode)); + rStyleSet.Put(XFillBmpTileItem(drawing::BitmapMode_REPEAT == eMode)); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_PAPER_BIN)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(!rValue.has<OUString>()) + throw lang::IllegalArgumentException(); + SfxPrinter* pPrinter = m_pDoc->getIDocumentDeviceAccess().getPrinter(true); + OUString sValue(rValue.get<OUString>()); + using printeridx_t = decltype(pPrinter->GetPaperBinCount()); + printeridx_t nBin = std::numeric_limits<printeridx_t>::max(); + if(sValue == "[From printer settings]") + nBin = std::numeric_limits<printeridx_t>::max()-1; + else if(pPrinter) + { + for(sal_uInt16 i=0, nEnd = pPrinter->GetPaperBinCount(); i < nEnd; ++i) + { + if (sValue == pPrinter->GetPaperBinName(i)) + { + nBin = i; + break; + } + } + } + if(nBin == std::numeric_limits<printeridx_t>::max()) + throw lang::IllegalArgumentException(); + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + SfxItemSet aSet(*rStyleSet.GetPool(), {{rEntry.nWID, rEntry.nWID}}); + aSet.SetParent(&rStyleSet); + rPropSet.setPropertyValue(rEntry, uno::makeAny(static_cast<sal_Int8>(nBin == std::numeric_limits<printeridx_t>::max()-1 ? -1 : nBin)), aSet); + rStyleSet.Put(aSet); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_NUM_RULES>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(!rValue.has<uno::Reference<container::XIndexReplace>>() || !rValue.has<uno::Reference<lang::XUnoTunnel>>()) + throw lang::IllegalArgumentException(); + auto xNumberTunnel(rValue.get<uno::Reference<lang::XUnoTunnel>>()); + SwXNumberingRules* pSwXRules = reinterpret_cast<SwXNumberingRules*>(sal::static_int_cast<sal_IntPtr>(xNumberTunnel->getSomething(SwXNumberingRules::getUnoTunnelId()))); + if(!pSwXRules) + return; + SwNumRule aSetRule(*pSwXRules->GetNumRule()); + for(sal_uInt16 i = 0; i < MAXLEVEL; ++i) + { + const SwNumFormat* pFormat = aSetRule.GetNumFormat(i); + if(!pFormat) + continue; + SwNumFormat aFormat(*pFormat); + const auto& rCharName(pSwXRules->GetNewCharStyleNames()[i]); + if(!rCharName.isEmpty() + && !SwXNumberingRules::isInvalidStyle(rCharName) + && (!pFormat->GetCharFormat() || pFormat->GetCharFormat()->GetName() != rCharName)) + { + auto pCharFormatIt(std::find_if(m_pDoc->GetCharFormats()->begin(), m_pDoc->GetCharFormats()->end(), + [&rCharName] (SwCharFormat* pF) { return pF->GetName() == rCharName; })); + if(pCharFormatIt != m_pDoc->GetCharFormats()->end()) + aFormat.SetCharFormat(*pCharFormatIt); + else if(m_pBasePool) + { + auto pBase(m_pBasePool->Find(rCharName, SfxStyleFamily::Char)); + if(!pBase) + pBase = &m_pBasePool->Make(rCharName, SfxStyleFamily::Char); + aFormat.SetCharFormat(static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat()); + } + else + aFormat.SetCharFormat(nullptr); + } + // same for fonts: + const auto& rBulletName(pSwXRules->GetBulletFontNames()[i]); + if(!rBulletName.isEmpty() + && !SwXNumberingRules::isInvalidStyle(rBulletName) + && (!pFormat->GetBulletFont() || pFormat->GetBulletFont()->GetFamilyName() != rBulletName)) + { + const auto pFontListItem(static_cast<const SvxFontListItem*>(m_pDoc->GetDocShell()->GetItem(SID_ATTR_CHAR_FONTLIST))); + const auto pList(pFontListItem->GetFontList()); + FontMetric aFontInfo(pList->Get(rBulletName, WEIGHT_NORMAL, ITALIC_NONE)); + vcl::Font aFont(aFontInfo); + aFormat.SetBulletFont(&aFont); + } + aSetRule.Set(i, &aFormat); + } + o_rStyleBase.getNewBase()->SetNumRule(aSetRule); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_PARATR_OUTLINELEVEL)>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(!rValue.has<sal_Int16>()) + return; + const auto nLevel(rValue.get<sal_Int16>()); + if(0 <= nLevel && nLevel <= MAXLEVEL) + o_rStyleBase.getNewBase()->GetCollection()->SetAttrOutlineLevel(nLevel); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_FOLLOW_STYLE>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(!rValue.has<OUString>()) + return; + const auto sValue(rValue.get<OUString>()); + OUString aString; + SwStyleNameMapper::FillUIName(sValue, aString, m_rEntry.m_aPoolId); + o_rStyleBase.getNewBase()->SetFollow(aString); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_PAGEDESC)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(MID_PAGEDESC_PAGEDESCNAME != rEntry.nMemberId) + { + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, rValue, o_rStyleBase); + return; + } + if(!rValue.has<OUString>()) + throw lang::IllegalArgumentException(); + // special handling for RES_PAGEDESC + SfxItemSet& rStyleSet = o_rStyleBase.GetItemSet(); + std::unique_ptr<SwFormatPageDesc> pNewDesc; + const SfxPoolItem* pItem; + if(SfxItemState::SET == rStyleSet.GetItemState(RES_PAGEDESC, true, &pItem)) + pNewDesc.reset(new SwFormatPageDesc(*static_cast<const SwFormatPageDesc*>(pItem))); + else + pNewDesc.reset(new SwFormatPageDesc); + const auto sValue(rValue.get<OUString>()); + OUString sDescName; + SwStyleNameMapper::FillUIName(sValue, sDescName, SwGetPoolIdFromName::PageDesc); + if(pNewDesc->GetPageDesc() && pNewDesc->GetPageDesc()->GetName() == sDescName) + return; + if(sDescName.isEmpty()) + { + rStyleSet.ClearItem(RES_BREAK); + rStyleSet.Put(SwFormatPageDesc()); + } + else + { + SwPageDesc* pPageDesc(SwPageDesc::GetByName(*m_pDoc, sDescName)); + if(!pPageDesc) + throw lang::IllegalArgumentException(); + pNewDesc->RegisterToPageDesc(*pPageDesc); + rStyleSet.Put(*pNewDesc); + } +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_TEXT_VERT_ADJUST)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(m_rEntry.m_eFamily != SfxStyleFamily::Page) + { + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, rValue, o_rStyleBase); + return; + } + if(!m_pDoc || !rValue.has<drawing::TextVerticalAdjust>() || !o_rStyleBase.GetOldPageDesc()) + return; + SwPageDesc* pPageDesc = m_pDoc->FindPageDesc(o_rStyleBase.GetOldPageDesc()->GetName()); + if(pPageDesc) + pPageDesc->SetVerticalAdjustment(rValue.get<drawing::TextVerticalAdjust>()); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_IS_AUTO_UPDATE>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(!rValue.has<bool>()) + throw lang::IllegalArgumentException(); + const bool bAuto(rValue.get<bool>()); + if(SfxStyleFamily::Para == m_rEntry.m_eFamily) + o_rStyleBase.getNewBase()->GetCollection()->SetAutoUpdateFormat(bAuto); + else if(SfxStyleFamily::Frame == m_rEntry.m_eFamily) + o_rStyleBase.getNewBase()->GetFrameFormat()->SetAutoUpdateFormat(bAuto); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_PARA_STYLE_CONDITIONS>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + static_assert(COND_COMMAND_COUNT == 28, "invalid size of command count?"); + using expectedarg_t = uno::Sequence<beans::NamedValue>; + if(!rValue.has<expectedarg_t>() || !m_pBasePool) + throw lang::IllegalArgumentException(); + SwCondCollItem aCondItem; + const auto aNamedValues = rValue.get<expectedarg_t>(); + for(const auto& rNamedValue : aNamedValues) + { + if(!rNamedValue.Value.has<OUString>()) + throw lang::IllegalArgumentException(); + + const OUString sValue(rNamedValue.Value.get<OUString>()); + // get UI style name from programmatic style name + OUString aStyleName; + SwStyleNameMapper::FillUIName(sValue, aStyleName, lcl_GetSwEnumFromSfxEnum(m_rEntry.m_eFamily)); + + // check for correct context and style name + const auto nIdx(GetCommandContextIndex(rNamedValue.Name)); + if (nIdx == -1) + throw lang::IllegalArgumentException(); + bool bStyleFound = false; + for(auto pBase = m_pBasePool->First(SfxStyleFamily::Para); pBase; pBase = m_pBasePool->Next()) + { + bStyleFound = pBase->GetName() == aStyleName; + if (bStyleFound) + break; + } + if (!bStyleFound) + throw lang::IllegalArgumentException(); + aCondItem.SetStyle(&aStyleName, nIdx); + } + o_rStyleBase.GetItemSet().Put(aCondItem); +} +template<> +void SwXStyle::SetPropertyValue<FN_UNO_CATEGORY>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(!o_rStyleBase.getNewBase()->IsUserDefined() || !rValue.has<paragraphstyle_t>()) + throw lang::IllegalArgumentException(); + static std::unique_ptr<std::map<paragraphstyle_t, SfxStyleSearchBits>> pUnoToCore; + if(!pUnoToCore) + { + pUnoToCore.reset(new std::map<paragraphstyle_t, SfxStyleSearchBits>); + auto pEntries = lcl_GetParagraphStyleCategoryEntries(); + std::transform(pEntries->begin(), pEntries->end(), std::inserter(*pUnoToCore, pUnoToCore->end()), + [] (const ParagraphStyleCategoryEntry& rEntry) { return std::pair<paragraphstyle_t, SfxStyleSearchBits>(rEntry.m_eCategory, rEntry.m_nSwStyleBits); }); + } + const auto pUnoToCoreIt(pUnoToCore->find(rValue.get<paragraphstyle_t>())); + if(pUnoToCoreIt == pUnoToCore->end()) + throw lang::IllegalArgumentException(); + o_rStyleBase.getNewBase()->SetMask( pUnoToCoreIt->second|SfxStyleSearchBits::UserDefined ); +} +template<> +void SwXStyle::SetPropertyValue<SID_SWREGISTER_COLLECTION>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + OUString sName; + rValue >>= sName; + SwRegisterItem aReg(!sName.isEmpty()); + aReg.SetWhich(SID_SWREGISTER_MODE); + o_rStyleBase.GetItemSet().Put(aReg); + OUString aString; + SwStyleNameMapper::FillUIName(sName, aString, SwGetPoolIdFromName::TxtColl); + o_rStyleBase.GetItemSet().Put(SfxStringItem(SID_SWREGISTER_COLLECTION, aString ) ); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_TXTATR_CJK_RUBY)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(MID_RUBY_CHARSTYLE != rEntry.nMemberId) + return; + if(!rValue.has<OUString>()) + throw lang::IllegalArgumentException(); + const auto sValue(rValue.get<OUString>()); + SfxItemSet& rStyleSet(o_rStyleBase.GetItemSet()); + std::unique_ptr<SwFormatRuby> pRuby; + const SfxPoolItem* pItem; + if(SfxItemState::SET == rStyleSet.GetItemState(RES_TXTATR_CJK_RUBY, true, &pItem)) + pRuby.reset(new SwFormatRuby(*static_cast<const SwFormatRuby*>(pItem))); + else + pRuby.reset(new SwFormatRuby(OUString())); + OUString sStyle; + SwStyleNameMapper::FillUIName(sValue, sStyle, SwGetPoolIdFromName::ChrFmt); + pRuby->SetCharFormatName(sValue); + pRuby->SetCharFormatId(0); + if(!sValue.isEmpty()) + { + const sal_uInt16 nId(SwStyleNameMapper::GetPoolIdFromUIName(sValue, SwGetPoolIdFromName::ChrFmt)); + pRuby->SetCharFormatId(nId); + } + rStyleSet.Put(*pRuby); + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, rValue, o_rStyleBase); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_PARATR_DROP)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + if(MID_DROPCAP_CHAR_STYLE_NAME != rEntry.nMemberId) + { + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, rValue, o_rStyleBase); + return; + } + if(!rValue.has<OUString>()) + throw lang::IllegalArgumentException(); + SfxItemSet& rStyleSet(o_rStyleBase.GetItemSet()); + std::unique_ptr<SwFormatDrop> pDrop; + const SfxPoolItem* pItem; + if(SfxItemState::SET == rStyleSet.GetItemState(RES_PARATR_DROP, true, &pItem)) + pDrop.reset(new SwFormatDrop(*static_cast<const SwFormatDrop*>(pItem))); + else + pDrop.reset(new SwFormatDrop); + const auto sValue(rValue.get<OUString>()); + OUString sStyle; + SwStyleNameMapper::FillUIName(sValue, sStyle, SwGetPoolIdFromName::ChrFmt); + auto pStyle(static_cast<SwDocStyleSheet*>(m_pDoc->GetDocShell()->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Char))); + //default character style must not be set as default format + if(!pStyle || pStyle->GetCharFormat() == m_pDoc->GetDfltCharFormat() ) + { + throw lang::IllegalArgumentException(); + } + pDrop->SetCharFormat(pStyle->GetCharFormat()); + rStyleSet.Put(*pDrop); +} +template<> +void SwXStyle::SetPropertyValue<sal_uInt16(RES_PARATR_NUMRULE)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& o_rStyleBase) +{ + uno::Any aValue(rValue); + lcl_TranslateMetric(rEntry, m_pDoc, aValue); + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, aValue, o_rStyleBase); + // --> OD 2006-10-18 #i70223# + if(SfxStyleFamily::Para == m_rEntry.m_eFamily && + o_rStyleBase.getNewBase().is() && o_rStyleBase.getNewBase()->GetCollection() && + //rBase.getNewBase()->GetCollection()->GetOutlineLevel() < MAXLEVEL /* assigned to list level of outline style */) //#outline level,removed by zhaojianwei + o_rStyleBase.getNewBase()->GetCollection()->IsAssignedToListLevelOfOutlineStyle()) ////<-end,add by zhaojianwei + { + OUString sNewNumberingRuleName; + aValue >>= sNewNumberingRuleName; + if(sNewNumberingRuleName.isEmpty() || sNewNumberingRuleName != m_pDoc->GetOutlineNumRule()->GetName()) + o_rStyleBase.getNewBase()->GetCollection()->DeleteAssignmentToListLevelOfOutlineStyle(); + } +} + +void SwXStyle::SetStyleProperty(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, const uno::Any& rValue, SwStyleBase_Impl& rBase) +{ + using propertytype_t = decltype(rEntry.nWID); + using coresetter_t = std::function<void(SwXStyle&, const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, const uno::Any&, SwStyleBase_Impl&)>; + static std::unique_ptr<std::map<propertytype_t, coresetter_t>> pUnoToCore; + if(!pUnoToCore) + { + pUnoToCore.reset(new std::map<propertytype_t, coresetter_t> { + // these explicit std::mem_fn() calls shouldn't be needed, but apparently MSVC is currently too stupid for C++11 again + { FN_UNO_HIDDEN, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_HIDDEN>) }, + { FN_UNO_STYLE_INTEROP_GRAB_BAG, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_STYLE_INTEROP_GRAB_BAG>) }, + { XATTR_FILLGRADIENT, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(XATTR_FILLGRADIENT)>) }, + { XATTR_FILLHATCH, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(XATTR_FILLGRADIENT)>) }, + { XATTR_FILLBITMAP, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(XATTR_FILLGRADIENT)>) }, + { XATTR_FILLFLOATTRANSPARENCE, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(XATTR_FILLGRADIENT)>) }, + { RES_BACKGROUND, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_BACKGROUND)>) }, + { OWN_ATTR_FILLBMP_MODE, std::mem_fn(&SwXStyle::SetPropertyValue<OWN_ATTR_FILLBMP_MODE>) }, + { RES_PAPER_BIN, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_PAPER_BIN)>) }, + { FN_UNO_NUM_RULES, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_NUM_RULES>) }, + { RES_PARATR_OUTLINELEVEL, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_PARATR_OUTLINELEVEL)>) }, + { FN_UNO_FOLLOW_STYLE, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_FOLLOW_STYLE>) }, + { RES_PAGEDESC, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_PAGEDESC)>) }, + { RES_TEXT_VERT_ADJUST, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_TEXT_VERT_ADJUST)>) }, + { FN_UNO_IS_AUTO_UPDATE, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_IS_AUTO_UPDATE>) }, + { FN_UNO_PARA_STYLE_CONDITIONS, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_PARA_STYLE_CONDITIONS>) }, + { FN_UNO_CATEGORY, std::mem_fn(&SwXStyle::SetPropertyValue<FN_UNO_CATEGORY>) }, + { SID_SWREGISTER_COLLECTION, std::mem_fn(&SwXStyle::SetPropertyValue<SID_SWREGISTER_COLLECTION>) }, + { RES_TXTATR_CJK_RUBY, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_TXTATR_CJK_RUBY)>) }, + { RES_PARATR_DROP, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_PARATR_DROP)>) }, + { RES_PARATR_NUMRULE, std::mem_fn(&SwXStyle::SetPropertyValue<sal_uInt16(RES_PARATR_NUMRULE)>) } + }); + } + const auto pUnoToCoreIt(pUnoToCore->find(rEntry.nWID)); + if(pUnoToCoreIt != pUnoToCore->end()) + pUnoToCoreIt->second(*this, rEntry, rPropSet, rValue, rBase); + else + { + // adapted switch logic to a more readable state; removed goto's and made + // execution of standard setting of property in ItemSet dependent of this variable + uno::Any aValue(rValue); + lcl_TranslateMetric(rEntry, m_pDoc, aValue); + SetPropertyValue<HINT_BEGIN>(rEntry, rPropSet, aValue, rBase); + } +} + +void SwXStyle::SetPropertyValues_Impl(const uno::Sequence<OUString>& rPropertyNames, const uno::Sequence<uno::Any>& rValues) +{ + if(!m_pDoc) + throw uno::RuntimeException(); + sal_Int8 nPropSetId = m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType; + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap &rMap = pPropSet->getPropertyMap(); + if(rPropertyNames.getLength() != rValues.getLength()) + throw lang::IllegalArgumentException(); + + SwStyleBase_Impl aBaseImpl(*m_pDoc, m_sStyleName, &GetDoc()->GetDfltTextFormatColl()->GetAttrSet()); // add pDfltTextFormatColl as parent + if(m_pBasePool) + { + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + SAL_WARN_IF(!pBase, "sw.uno", "where is the style?"); + if(!pBase) + throw uno::RuntimeException(); + aBaseImpl.setNewBase(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + } + if(!aBaseImpl.getNewBase().is() && !m_bIsDescriptor) + throw uno::RuntimeException(); + + const OUString* pNames = rPropertyNames.getConstArray(); + const uno::Any* pValues = rValues.getConstArray(); + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); ++nProp) + { + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(pNames[nProp]); + if(!pEntry || (!m_bIsConditional && pNames[nProp] == UNO_NAME_PARA_STYLE_CONDITIONS)) + throw beans::UnknownPropertyException("Unknown property: " + pNames[nProp], static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException ("Property is read-only: " + pNames[nProp], static_cast<cppu::OWeakObject*>(this)); + if(aBaseImpl.getNewBase().is()) + SetStyleProperty(*pEntry, *pPropSet, pValues[nProp], aBaseImpl); + else if(!m_pPropertiesImpl->SetProperty(pNames[nProp], pValues[nProp])) + throw lang::IllegalArgumentException(); + } + + if(aBaseImpl.HasItemSet()) + aBaseImpl.getNewBase()->SetItemSet(aBaseImpl.GetItemSet()); +} + +void SwXStyle::setPropertyValues(const uno::Sequence<OUString>& rPropertyNames, const uno::Sequence<uno::Any>& rValues) +{ + SolarMutexGuard aGuard; + // workaround for bad designed API + try + { + SetPropertyValues_Impl( rPropertyNames, rValues ); + } + catch (const beans::UnknownPropertyException &rException) + { + // wrap the original (here not allowed) exception in + // a lang::WrappedTargetException that gets thrown instead. + lang::WrappedTargetException aWExc; + aWExc.TargetException <<= rException; + throw aWExc; + } +} + +SfxStyleSheetBase* SwXStyle::GetStyleSheetBase() +{ + if(!m_pBasePool) + return nullptr; + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + return pBase; +} +void SwXStyle::PrepareStyleBase(SwStyleBase_Impl& rBase) +{ + SfxStyleSheetBase* pBase(GetStyleSheetBase()); + if(!pBase) + throw uno::RuntimeException(); + if(!rBase.getNewBase().is()) + rBase.setNewBase(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); +} + +template<> +uno::Any SwXStyle::GetStyleProperty<HINT_BEGIN>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase); +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_IS_PHYSICAL>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl&) +{ + SfxStyleSheetBase* pBase(GetStyleSheetBase()); + if(!pBase) + return uno::makeAny(false); + bool bPhys = static_cast<SwDocStyleSheet*>(pBase)->IsPhysical(); + // The standard character format is not existing physically + if( bPhys && SfxStyleFamily::Char == GetFamily() && + static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat() && + static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat()->IsDefault() ) + bPhys = false; + return uno::makeAny<bool>(bPhys); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_HIDDEN>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl&) +{ + SfxStyleSheetBase* pBase(GetStyleSheetBase()); + if(!pBase) + return uno::makeAny(false); + rtl::Reference<SwDocStyleSheet> xBase(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + return uno::makeAny(xBase->IsHidden()); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_STYLE_INTEROP_GRAB_BAG>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl&) +{ + SfxStyleSheetBase* pBase(GetStyleSheetBase()); + if(!pBase) + return uno::Any(); + uno::Any aRet; + rtl::Reference<SwDocStyleSheet> xBase(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + xBase->GetGrabBagItem(aRet); + return aRet; +} +template<> +uno::Any SwXStyle::GetStyleProperty<sal_uInt16(RES_PAPER_BIN)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + SfxItemSet& rSet = rBase.GetItemSet(); + uno::Any aValue; + rPropSet.getPropertyValue(rEntry, rSet, aValue); + sal_Int8 nBin(aValue.get<sal_Int8>()); + if(nBin == -1) + return uno::makeAny<OUString>("[From printer settings]"); + SfxPrinter* pPrinter = GetDoc()->getIDocumentDeviceAccess().getPrinter(false); + if(!pPrinter) + return uno::Any(); + return uno::makeAny(pPrinter->GetPaperBinName(nBin)); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_NUM_RULES>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + const SwNumRule* pRule = rBase.getNewBase()->GetNumRule(); + assert(pRule && "Where is the NumRule?"); + uno::Reference<container::XIndexReplace> xRules(new SwXNumberingRules(*pRule, GetDoc())); + return uno::makeAny<uno::Reference<container::XIndexReplace>>(xRules); +} +template<> +uno::Any SwXStyle::GetStyleProperty<sal_uInt16(RES_PARATR_OUTLINELEVEL)>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + SAL_WARN_IF(SfxStyleFamily::Para != GetFamily(), "sw.uno", "only paras"); + return uno::makeAny<sal_Int16>(rBase.getNewBase()->GetCollection()->GetAttrOutlineLevel()); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_FOLLOW_STYLE>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + OUString aString; + SwStyleNameMapper::FillProgName(rBase.getNewBase()->GetFollow(), aString, lcl_GetSwEnumFromSfxEnum(GetFamily())); + return uno::makeAny(aString); +} +template<> +uno::Any SwXStyle::GetStyleProperty<sal_uInt16(RES_PAGEDESC)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + if(MID_PAGEDESC_PAGEDESCNAME != rEntry.nMemberId) + return GetStyleProperty<HINT_BEGIN>(rEntry, rPropSet, rBase); + // special handling for RES_PAGEDESC + const SfxPoolItem* pItem; + if(SfxItemState::SET != rBase.GetItemSet().GetItemState(RES_PAGEDESC, true, &pItem)) + return uno::Any(); + const SwPageDesc* pDesc = static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc(); + if(!pDesc) + return uno::Any(); + OUString aString; + SwStyleNameMapper::FillProgName(pDesc->GetName(), aString, SwGetPoolIdFromName::PageDesc); + return uno::makeAny(aString); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_IS_AUTO_UPDATE>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + switch(GetFamily()) + { + case SfxStyleFamily::Para : return uno::makeAny<bool>(rBase.getNewBase()->GetCollection()->IsAutoUpdateFormat()); + case SfxStyleFamily::Frame: return uno::makeAny<bool>(rBase.getNewBase()->GetFrameFormat()->IsAutoUpdateFormat()); + default: return uno::Any(); + } +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_DISPLAY_NAME>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + return uno::makeAny(rBase.getNewBase()->GetName()); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_PARA_STYLE_CONDITIONS>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + static_assert(COND_COMMAND_COUNT == 28, "invalid size of command count?"); + uno::Sequence<beans::NamedValue> aSeq(COND_COMMAND_COUNT); + sal_uInt16 nIndex = 0; + for(auto& rNV : aSeq) + { + rNV.Name = GetCommandContextByIndex(nIndex++); + rNV.Value <<= OUString(); + } + SwFormat* pFormat = static_cast<SwDocStyleSheet*>(GetStyleSheetBase())->GetCollection(); + if(pFormat && RES_CONDTXTFMTCOLL == pFormat->Which()) + { + const CommandStruct* pCmds = SwCondCollItem::GetCmds(); + beans::NamedValue* pSeq = aSeq.getArray(); + for(sal_uInt16 n = 0; n < COND_COMMAND_COUNT; ++n) + { + const SwCollCondition* pCond = static_cast<SwConditionTextFormatColl*>(pFormat)->HasCondition(SwCollCondition(nullptr, pCmds[n].nCnd, pCmds[n].nSubCond)); + if(!pCond || !pCond->GetTextFormatColl()) + continue; + // get programmatic style name from UI style name + OUString aStyleName = pCond->GetTextFormatColl()->GetName(); + SwStyleNameMapper::FillProgName(aStyleName, aStyleName, lcl_GetSwEnumFromSfxEnum(GetFamily())); + pSeq[n].Value <<= aStyleName; + } + } + return uno::makeAny(aSeq); +} +template<> +uno::Any SwXStyle::GetStyleProperty<FN_UNO_CATEGORY>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + static std::unique_ptr<std::map<collectionbits_t, paragraphstyle_t>> pUnoToCore; + if(!pUnoToCore) + { + pUnoToCore.reset(new std::map<collectionbits_t, paragraphstyle_t>); + auto pEntries = lcl_GetParagraphStyleCategoryEntries(); + std::transform(pEntries->begin(), pEntries->end(), std::inserter(*pUnoToCore, pUnoToCore->end()), + [] (const ParagraphStyleCategoryEntry& rEntry) { return std::pair<collectionbits_t, paragraphstyle_t>(rEntry.m_nCollectionBits, rEntry.m_eCategory); }); + } + const sal_uInt16 nPoolId = rBase.getNewBase()->GetCollection()->GetPoolFormatId(); + const auto pUnoToCoreIt(pUnoToCore->find(COLL_GET_RANGE_BITS & nPoolId)); + if(pUnoToCoreIt == pUnoToCore->end()) + return uno::makeAny<sal_Int16>(-1); + return uno::makeAny(pUnoToCoreIt->second); +} +template<> +uno::Any SwXStyle::GetStyleProperty<SID_SWREGISTER_COLLECTION>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + const SwPageDesc *pPageDesc = rBase.getNewBase()->GetPageDesc(); + if(!pPageDesc) + return uno::makeAny(OUString()); + const SwTextFormatColl* pCol = pPageDesc->GetRegisterFormatColl(); + if(!pCol) + return uno::makeAny(OUString()); + OUString aName; + SwStyleNameMapper::FillProgName(pCol->GetName(), aName, SwGetPoolIdFromName::TxtColl); + return uno::makeAny(aName); +} +template<> +uno::Any SwXStyle::GetStyleProperty<sal_uInt16(RES_BACKGROUND)>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + const SfxItemSet& rSet = rBase.GetItemSet(); + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND)); + uno::Any aResult; + if(!aOriginalBrushItem->QueryValue(aResult, rEntry.nMemberId)) + SAL_WARN("sw.uno", "error getting attribute from RES_BACKGROUND."); + return aResult; +} +template<> +uno::Any SwXStyle::GetStyleProperty<OWN_ATTR_FILLBMP_MODE>(const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + const SfxItemSet& rSet = rBase.GetItemSet(); + if (rSet.Get(XATTR_FILLBMP_TILE).GetValue()) + return uno::makeAny(drawing::BitmapMode_REPEAT); + if (rSet.Get(XATTR_FILLBMP_STRETCH).GetValue()) + return uno::makeAny(drawing::BitmapMode_STRETCH); + return uno::makeAny(drawing::BitmapMode_NO_REPEAT); +} +template<> +uno::Any SwXStyle::GetStyleProperty<HINT_BEGIN>(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase) +{ + PrepareStyleBase(rBase); + SfxItemSet& rSet = rBase.GetItemSet(); + uno::Any aResult; + rPropSet.getPropertyValue(rEntry, rSet, aResult); + // + // since the sfx uint16 item now exports a sal_Int32, we may have to fix this here + if(rEntry.aType == cppu::UnoType<sal_Int16>::get() && aResult.getValueType() == cppu::UnoType<sal_Int32>::get()) + aResult <<= static_cast<sal_Int16>(aResult.get<sal_Int32>()); + // check for needed metric translation + if(rEntry.nMoreFlags & PropertyMoreFlags::METRIC_ITEM && GetDoc()) + { + const SfxItemPool& rPool = GetDoc()->GetAttrPool(); + const MapUnit eMapUnit(rPool.GetMetric(rEntry.nWID)); + bool bAllowedConvert(true); + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + if(XATTR_FILLBMP_SIZEX == rEntry.nWID || XATTR_FILLBMP_SIZEY == rEntry.nWID) + bAllowedConvert = !aResult.has<sal_Int32>() || aResult.get<sal_Int32>() > 0; + if(eMapUnit != MapUnit::Map100thMM && bAllowedConvert) + SvxUnoConvertToMM(eMapUnit, aResult); + } + return aResult; +} + +uno::Any SwXStyle::GetStyleProperty_Impl(const SfxItemPropertySimpleEntry& rEntry, const SfxItemPropertySet& rPropSet, SwStyleBase_Impl& rBase) +{ + using propertytype_t = decltype(rEntry.nWID); + using coresetter_t = std::function<uno::Any(SwXStyle&, const SfxItemPropertySimpleEntry&, const SfxItemPropertySet&, SwStyleBase_Impl&)>; + static std::unique_ptr<std::map<propertytype_t, coresetter_t>> pUnoToCore; + if(!pUnoToCore) + { + pUnoToCore.reset(new std::map<propertytype_t, coresetter_t> { + // these explicit std::mem_fn() calls shouldn't be needed, but apparently MSVC is currently too stupid for C++11 again + { FN_UNO_IS_PHYSICAL, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_IS_PHYSICAL>) }, + { FN_UNO_HIDDEN, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_HIDDEN>) }, + { FN_UNO_STYLE_INTEROP_GRAB_BAG, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_STYLE_INTEROP_GRAB_BAG>) }, + { RES_PAPER_BIN, std::mem_fn(&SwXStyle::GetStyleProperty<sal_uInt16(RES_PAPER_BIN)>) }, + { FN_UNO_NUM_RULES, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_NUM_RULES>) }, + { RES_PARATR_OUTLINELEVEL, std::mem_fn(&SwXStyle::GetStyleProperty<sal_uInt16(RES_PARATR_OUTLINELEVEL)>) }, + { FN_UNO_FOLLOW_STYLE, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_FOLLOW_STYLE>) }, + { RES_PAGEDESC, std::mem_fn(&SwXStyle::GetStyleProperty<sal_uInt16(RES_PAGEDESC)>) }, + { FN_UNO_IS_AUTO_UPDATE, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_IS_AUTO_UPDATE>) }, + { FN_UNO_DISPLAY_NAME, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_DISPLAY_NAME>) }, + { FN_UNO_PARA_STYLE_CONDITIONS, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_PARA_STYLE_CONDITIONS>) }, + { FN_UNO_CATEGORY, std::mem_fn(&SwXStyle::GetStyleProperty<FN_UNO_CATEGORY>) }, + { SID_SWREGISTER_COLLECTION, std::mem_fn(&SwXStyle::GetStyleProperty<SID_SWREGISTER_COLLECTION>) }, + { RES_BACKGROUND, std::mem_fn(&SwXStyle::GetStyleProperty<sal_uInt16(RES_BACKGROUND)>) }, + { OWN_ATTR_FILLBMP_MODE, std::mem_fn(&SwXStyle::GetStyleProperty<OWN_ATTR_FILLBMP_MODE>) } + }); + } + const auto pUnoToCoreIt(pUnoToCore->find(rEntry.nWID)); + if(pUnoToCoreIt != pUnoToCore->end()) + return pUnoToCoreIt->second(*this, rEntry, rPropSet, rBase); + return GetStyleProperty<HINT_BEGIN>(rEntry, rPropSet, rBase); +} + +uno::Any SwXStyle::GetPropertyValue_Impl(const SfxItemPropertySet* pPropSet, SwStyleBase_Impl& rBase, const OUString& rPropertyName) +{ + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(rPropertyName); + if(!pEntry || (!m_bIsConditional && rPropertyName == UNO_NAME_PARA_STYLE_CONDITIONS)) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast<cppu::OWeakObject*>(this)); + if(m_pBasePool) + return GetStyleProperty_Impl(*pEntry, *pPropSet, rBase); + const uno::Any* pAny = nullptr; + m_pPropertiesImpl->GetProperty(rPropertyName, pAny); + if(pAny->hasValue()) + return *pAny; + uno::Any aValue; + switch(m_rEntry.m_eFamily) + { + case SfxStyleFamily::Pseudo: + throw uno::RuntimeException("No default value for: " + rPropertyName); + break; + case SfxStyleFamily::Para: + case SfxStyleFamily::Page: + SwStyleProperties_Impl::GetProperty(rPropertyName, m_xStyleData, aValue); + break; + case SfxStyleFamily::Char: + case SfxStyleFamily::Frame: + { + if(pEntry->nWID < POOLATTR_BEGIN || pEntry->nWID >= RES_UNKNOWNATR_END) + throw uno::RuntimeException("No default value for: " + rPropertyName); + SwFormat* pFormat; + if(m_rEntry.m_eFamily == SfxStyleFamily::Char) + pFormat = m_pDoc->GetDfltCharFormat(); + else + pFormat = m_pDoc->GetDfltFrameFormat(); + const SwAttrPool* pPool = pFormat->GetAttrSet().GetPool(); + const SfxPoolItem& rItem = pPool->GetDefaultItem(pEntry->nWID); + rItem.QueryValue(aValue, pEntry->nMemberId); + } + break; + default: + ; + } + return aValue; +} + +uno::Any SwXStyle::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + if(!m_pDoc) + throw uno::RuntimeException(); + if(!m_pBasePool && !m_bIsDescriptor) + throw uno::RuntimeException(); + sal_Int8 nPropSetId = m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType; + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + SwStyleBase_Impl aBase(*m_pDoc, m_sStyleName, &m_pDoc->GetDfltTextFormatColl()->GetAttrSet()); // add pDfltTextFormatColl as parent + return GetPropertyValue_Impl(pPropSet, aBase, rPropertyName); +} + +uno::Sequence<uno::Any> SwXStyle::getPropertyValues(const uno::Sequence<OUString>& rPropertyNames) +{ + SolarMutexGuard aGuard; + if(!m_pDoc) + throw uno::RuntimeException(); + if(!m_pBasePool && !m_bIsDescriptor) + throw uno::RuntimeException(); + sal_Int8 nPropSetId = m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType; + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + SwStyleBase_Impl aBase(*m_pDoc, m_sStyleName, &m_pDoc->GetDfltTextFormatColl()->GetAttrSet()); // add pDfltTextFormatColl as parent + uno::Sequence<uno::Any> aValues(rPropertyNames.getLength()); + // workaround for bad designed API + try + { + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); ++nProp) + aValues[nProp] = GetPropertyValue_Impl(pPropSet, aBase, rPropertyNames[nProp]); + } + catch(beans::UnknownPropertyException&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("Unknown property exception caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + catch(lang::WrappedTargetException&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("WrappedTargetException caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + return aValues; +} + +void SwXStyle::setPropertyValue(const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + const uno::Sequence<OUString> aProperties(&rPropertyName, 1); + const uno::Sequence<uno::Any> aValues(&rValue, 1); + SetPropertyValues_Impl(aProperties, aValues); +} + +beans::PropertyState SwXStyle::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Sequence<OUString> aNames{rPropertyName}; + uno::Sequence<beans::PropertyState> aStates = getPropertyStates(aNames); + return aStates.getConstArray()[0]; +} + +// allow to retarget the SfxItemSet working on, default correctly. Only +// use pSourceSet below this point (except in header/footer processing) +static const SfxItemSet* lcl_GetItemsetForProperty(const SfxItemSet& rSet, SfxStyleFamily eFamily, const OUString& rPropertyName) +{ + if(eFamily != SfxStyleFamily::Page) + return &rSet; + const bool isFooter = rPropertyName.startsWith("Footer"); + if(!isFooter && !rPropertyName.startsWith("Header") && rPropertyName != UNO_NAME_FIRST_IS_SHARED) + return &rSet; + const SvxSetItem* pSetItem; + if(!lcl_GetHeaderFooterItem(rSet, rPropertyName, isFooter, pSetItem)) + return nullptr; + return &pSetItem->GetItemSet(); +} +uno::Sequence<beans::PropertyState> SwXStyle::getPropertyStates(const uno::Sequence<OUString>& rPropertyNames) +{ + SolarMutexGuard aGuard; + uno::Sequence<beans::PropertyState> aRet(rPropertyNames.getLength()); + beans::PropertyState* pStates = aRet.getArray(); + + if(!m_pBasePool) + throw uno::RuntimeException(); + SfxStyleSheetBase* pBase = m_pBasePool->Find(m_sStyleName, m_rEntry.m_eFamily); + + SAL_WARN_IF(!pBase, "sw.uno", "where is the style?"); + if(!pBase) + throw uno::RuntimeException(); + + const OUString* pNames = rPropertyNames.getConstArray(); + rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + sal_Int8 nPropSetId = m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType; + + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + const SfxItemSet& rSet = xStyle->GetItemSet(); + + for(sal_Int32 i = 0; i < rPropertyNames.getLength(); ++i) + { + const OUString sPropName = pNames[i]; + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(sPropName); + + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + sPropName, static_cast<cppu::OWeakObject*>(this)); + + if(FN_UNO_NUM_RULES == pEntry->nWID || FN_UNO_FOLLOW_STYLE == pEntry->nWID) + { + // handle NumRules first, done + pStates[i] = beans::PropertyState_DIRECT_VALUE; + continue; + } + const SfxItemSet* pSourceSet = lcl_GetItemsetForProperty(rSet, m_rEntry.m_eFamily, sPropName); + if(!pSourceSet) + { + // if no SetItem, value is ambiguous and we are done + pStates[i] = beans::PropertyState_AMBIGUOUS_VALUE; + continue; + } + switch(pEntry->nWID) + { + case OWN_ATTR_FILLBMP_MODE: + { + if(SfxItemState::SET == pSourceSet->GetItemState(XATTR_FILLBMP_STRETCH, false) + || SfxItemState::SET == pSourceSet->GetItemState(XATTR_FILLBMP_TILE, false)) + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_AMBIGUOUS_VALUE; + } + } + break; + case RES_BACKGROUND: + { + // for FlyFrames we need to mark the used properties from type RES_BACKGROUND + // as beans::PropertyState_DIRECT_VALUE to let users of this property call + // getPropertyValue where the member properties will be mapped from the + // fill attributes to the according SvxBrushItem entries + if (SWUnoHelper::needToMapFillItemsToSvxBrushItemTypes(*pSourceSet, pEntry->nMemberId)) + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + } + break; + default: + { + pStates[i] = pPropSet->getPropertyState(*pEntry, *pSourceSet); + + if(SfxStyleFamily::Page == m_rEntry.m_eFamily && SID_ATTR_PAGE_SIZE == pEntry->nWID && beans::PropertyState_DIRECT_VALUE == pStates[i]) + { + const SvxSizeItem& rSize = rSet.Get(SID_ATTR_PAGE_SIZE); + sal_uInt8 nMemberId = pEntry->nMemberId & 0x7f; + + if((LONG_MAX == rSize.GetSize().Width() && (MID_SIZE_WIDTH == nMemberId || MID_SIZE_SIZE == nMemberId)) || + (LONG_MAX == rSize.GetSize().Height() && MID_SIZE_HEIGHT == nMemberId)) + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + } + } + } + } + return aRet; +} + +void SwXStyle::setPropertyToDefault(const OUString& rPropertyName) +{ + const uno::Sequence<OUString> aSequence(&rPropertyName, 1); + setPropertiesToDefault(aSequence); +} + +static SwFormat* lcl_GetFormatForStyle(SwDoc const * pDoc, const rtl::Reference<SwDocStyleSheet>& xStyle, const SfxStyleFamily eFamily) +{ + if(!xStyle.is()) + return nullptr; + switch(eFamily) + { + case SfxStyleFamily::Char: return xStyle->GetCharFormat(); + case SfxStyleFamily::Para: return xStyle->GetCollection(); + case SfxStyleFamily::Frame: return xStyle->GetFrameFormat(); + case SfxStyleFamily::Page: + { + SwPageDesc* pDesc(pDoc->FindPageDesc(xStyle->GetPageDesc()->GetName())); + if(pDesc) + return &pDesc->GetMaster(); + } + break; + default: ; + } + return nullptr; +} + +void SAL_CALL SwXStyle::setPropertiesToDefault(const uno::Sequence<OUString>& aPropertyNames) +{ + SolarMutexGuard aGuard; + const rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(GetStyleSheetBase()))); + SwFormat* pTargetFormat = lcl_GetFormatForStyle(m_pDoc, xStyle, m_rEntry.m_eFamily); + if(!pTargetFormat) + { + if(!m_bIsDescriptor) + return; + for(const auto& rName : aPropertyNames) + m_pPropertiesImpl->ClearProperty(rName); + return; + } + const sal_Int8 nPropSetId = m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType; + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap &rMap = pPropSet->getPropertyMap(); + for(const auto& rName : aPropertyNames) + { + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(rName); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rName, static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nWID == FN_UNO_FOLLOW_STYLE || pEntry->nWID == FN_UNO_NUM_RULES) + throw uno::RuntimeException("Cannot reset: " + rName, static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw uno::RuntimeException("setPropertiesToDefault: property is read-only: " + rName, static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nWID == RES_PARATR_OUTLINELEVEL) + { + static_cast<SwTextFormatColl*>(pTargetFormat)->DeleteAssignmentToListLevelOfOutlineStyle(); + continue; + } + pTargetFormat->ResetFormatAttr(pEntry->nWID); + if(OWN_ATTR_FILLBMP_MODE == pEntry->nWID) + { + // + SwDoc* pDoc = pTargetFormat->GetDoc(); + SfxItemSet aSet(pDoc->GetAttrPool(), svl::Items<XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + aSet.SetParent(&pTargetFormat->GetAttrSet()); + + aSet.ClearItem(XATTR_FILLBMP_STRETCH); + aSet.ClearItem(XATTR_FILLBMP_TILE); + + pTargetFormat->SetFormatAttr(aSet); + } + } +} + +void SAL_CALL SwXStyle::setAllPropertiesToDefault() +{ + SolarMutexGuard aGuard; + if(!m_pBasePool) + { + if(!m_bIsDescriptor) + throw uno::RuntimeException(); + m_pPropertiesImpl->ClearAllProperties(); + return; + } + const rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(GetStyleSheetBase()))); + if(!xStyle.is()) + throw uno::RuntimeException(); + if(SfxStyleFamily::Page == m_rEntry.m_eFamily) + { + size_t nPgDscPos(0); + SwPageDesc* pDesc = m_pDoc->FindPageDesc(xStyle->GetPageDesc()->GetName(), &nPgDscPos); + SwFormat* pPageFormat(nullptr); + if(pDesc) + { + pPageFormat = &pDesc->GetMaster(); + pDesc->SetUseOn(UseOnPage::All); + } + else + pPageFormat = lcl_GetFormatForStyle(m_pDoc, xStyle, m_rEntry.m_eFamily); + SwPageDesc& rPageDesc = m_pDoc->GetPageDesc(nPgDscPos); + rPageDesc.ResetAllMasterAttr(); + + SvxLRSpaceItem aLR(RES_LR_SPACE); + sal_Int32 nSize = GetMetricVal(CM_1) * 2; + aLR.SetLeft(nSize); + aLR.SetLeft(nSize); + SvxULSpaceItem aUL(RES_UL_SPACE); + aUL.SetUpper(static_cast<sal_uInt16>(nSize)); + aUL.SetLower(static_cast<sal_uInt16>(nSize)); + pPageFormat->SetFormatAttr(aLR); + pPageFormat->SetFormatAttr(aUL); + SwPageDesc* pStdPgDsc = m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD); + std::shared_ptr<SwFormatFrameSize> aFrameSz(std::make_shared<SwFormatFrameSize>(SwFrameSize::Fixed)); + + if(RES_POOLPAGE_STANDARD == rPageDesc.GetPoolFormatId()) + { + if(m_pDoc->getIDocumentDeviceAccess().getPrinter(false)) + { + const Size aPhysSize( SvxPaperInfo::GetPaperSize( + static_cast<Printer*>(m_pDoc->getIDocumentDeviceAccess().getPrinter(false)))); + aFrameSz->SetSize(aPhysSize); + } + else + { + aFrameSz->SetSize(SvxPaperInfo::GetDefaultPaperSize()); + } + + } + else + { + aFrameSz.reset(pStdPgDsc->GetMaster().GetFrameSize().Clone()); + } + + if(pStdPgDsc->GetLandscape()) + { + SwTwips nTmp = aFrameSz->GetHeight(); + aFrameSz->SetHeight(aFrameSz->GetWidth()); + aFrameSz->SetWidth(nTmp); + } + + pPageFormat->SetFormatAttr(*aFrameSz); + m_pDoc->ChgPageDesc(nPgDscPos, m_pDoc->GetPageDesc(nPgDscPos)); + return; + } + if(SfxStyleFamily::Para == m_rEntry.m_eFamily) + { + if(xStyle->GetCollection()) + xStyle->GetCollection()->DeleteAssignmentToListLevelOfOutlineStyle(); + } + SwFormat* const pTargetFormat = lcl_GetFormatForStyle(m_pDoc, xStyle, m_rEntry.m_eFamily); + if(!pTargetFormat) + return; + pTargetFormat->ResetAllFormatAttr(); +} + +uno::Sequence<uno::Any> SAL_CALL SwXStyle::getPropertyDefaults(const uno::Sequence<OUString>& aPropertyNames) +{ + SolarMutexGuard aGuard; + sal_Int32 nCount = aPropertyNames.getLength(); + uno::Sequence<uno::Any> aRet(nCount); + if(!nCount) + return aRet; + SfxStyleSheetBase* pBase = GetStyleSheetBase(); + if(!pBase) + throw uno::RuntimeException(); + rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + const sal_Int8 nPropSetId = m_bIsConditional ? PROPERTY_MAP_CONDITIONAL_PARA_STYLE : m_rEntry.m_nPropMapType; + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + + const SfxItemSet &rSet = xStyle->GetItemSet(), *pParentSet = rSet.GetParent(); + for(sal_Int32 i = 0; i < nCount; ++i) + { + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(aPropertyNames[i]); + + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + aPropertyNames[i], static_cast < cppu::OWeakObject * >(this)); + // these cannot be in an item set, especially not the + // parent set, so the default value is void + if (pEntry->nWID >= RES_UNKNOWNATR_END) + continue; + + if(pParentSet) + { + aSwMapProvider.GetPropertySet(nPropSetId)->getPropertyValue(aPropertyNames[i], *pParentSet, aRet[i]); + } + else if(pEntry->nWID != rSet.GetPool()->GetSlotId(pEntry->nWID)) + { + const SfxPoolItem& rItem = rSet.GetPool()->GetDefaultItem(pEntry->nWID); + rItem.QueryValue(aRet[i], pEntry->nMemberId); + } + } + return aRet; +} + +uno::Any SwXStyle::getPropertyDefault(const OUString& rPropertyName) +{ + const uno::Sequence<OUString> aSequence(&rPropertyName, 1); + return getPropertyDefaults(aSequence)[0]; +} + +void SwXStyle::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + if((rHint.GetId() == SfxHintId::Dying) || (rHint.GetId() == SfxHintId::StyleSheetErased)) + { + m_pBasePool = nullptr; + SfxListener::EndListening(rBC); + } + else if(rHint.GetId() == SfxHintId::StyleSheetChanged) + { + SfxStyleSheetBasePool& rBP = static_cast<SfxStyleSheetBasePool&>(rBC); + SfxStyleSheetBase* pOwnBase = rBP.Find(m_sStyleName, m_rEntry.m_eFamily); + if(!pOwnBase) + { + SfxListener::EndListening(rBC); + Invalidate(); + } + } +} + +void SwXStyle::Invalidate() +{ + m_sStyleName.clear(); + m_pBasePool = nullptr; + m_pDoc = nullptr; + m_xStyleData.clear(); + m_xStyleFamily.clear(); +} + +SwXPageStyle::SwXPageStyle(SfxStyleSheetBasePool& rPool, SwDocShell* pDocSh, const OUString& rStyleName) + : SwXStyle(&rPool, SfxStyleFamily::Page, pDocSh->GetDoc(), rStyleName) +{ } + +SwXPageStyle::SwXPageStyle(SwDocShell* pDocSh) + : SwXStyle(pDocSh->GetDoc(), SfxStyleFamily::Page) +{ } + +void SwXStyle::PutItemToSet(const SvxSetItem* pSetItem, const SfxItemPropertySet& rPropSet, const SfxItemPropertySimpleEntry& rEntry, const uno::Any& rVal, SwStyleBase_Impl& rBaseImpl) +{ + // create a new SvxSetItem and get it's ItemSet as new target + const std::unique_ptr<SvxSetItem> pNewSetItem(pSetItem->Clone()); + SfxItemSet& rSetSet = pNewSetItem->GetItemSet(); + + // set parent to ItemSet to ensure XFILL_NONE as XFillStyleItem + rSetSet.SetParent(&m_pDoc->GetDfltFrameFormat()->GetAttrSet()); + + // replace the used SfxItemSet at the SwStyleBase_Impl temporarily and use the + // default method to set the property + { + SwStyleBase_Impl::ItemSetOverrider o(rBaseImpl, &rSetSet); + SetStyleProperty(rEntry, rPropSet, rVal, rBaseImpl); + } + + // reset parent at ItemSet from SetItem + rSetSet.SetParent(nullptr); + + // set the new SvxSetItem at the real target and delete it + rBaseImpl.GetItemSet().Put(*pNewSetItem); +} + +void SwXPageStyle::SetPropertyValues_Impl(const uno::Sequence<OUString>& rPropertyNames, const uno::Sequence<uno::Any>& rValues) +{ + if(!GetDoc()) + throw uno::RuntimeException(); + + if(rPropertyNames.getLength() != rValues.getLength()) + throw lang::IllegalArgumentException(); + + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_PAGE_STYLE); + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + SwStyleBase_Impl aBaseImpl(*GetDoc(), GetStyleName(), &GetDoc()->GetDfltFrameFormat()->GetAttrSet()); // add pDfltFrameFormat as parent + if(!m_pBasePool) + { + if(!IsDescriptor()) + throw uno::RuntimeException(); + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); ++nProp) + if(!m_pPropertiesImpl->SetProperty(rPropertyNames[nProp], rValues[nProp])) + throw lang::IllegalArgumentException(); + return; + } + SfxStyleSheetBase* pBase = GetStyleSheetBase(); + if(!pBase) + throw uno::RuntimeException(); + aBaseImpl.setNewBase(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); ++nProp) + { + const OUString& rPropName = rPropertyNames[nProp]; + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(rPropName); + + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropName, static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException("Property is read-only: " + rPropName, static_cast<cppu::OWeakObject*>(this)); + + const bool bHeader(rPropName.startsWith("Header")); + const bool bFooter(rPropName.startsWith("Footer")); + const bool bFirstIsShared(rPropName == UNO_NAME_FIRST_IS_SHARED); + if(bHeader || bFooter || bFirstIsShared) + { + switch(pEntry->nWID) + { + case SID_ATTR_PAGE_ON: + case RES_BACKGROUND: + case RES_BOX: + case RES_LR_SPACE: + case RES_SHADOW: + case RES_UL_SPACE: + case SID_ATTR_PAGE_DYNAMIC: + case SID_ATTR_PAGE_SHARED: + case SID_ATTR_PAGE_SHARED_FIRST: + case SID_ATTR_PAGE_SIZE: + case RES_HEADER_FOOTER_EAT_SPACING: + { + // it is a Header/Footer entry, access the SvxSetItem containing it's information + const SvxSetItem* pSetItem = nullptr; + if (lcl_GetHeaderFooterItem(aBaseImpl.GetItemSet(), rPropName, bFooter, pSetItem)) + { + PutItemToSet(pSetItem, *pPropSet, *pEntry, rValues[nProp], aBaseImpl); + + if (pEntry->nWID == SID_ATTR_PAGE_SHARED_FIRST) + { + // Need to add this to the other as well + if (SfxItemState::SET == aBaseImpl.GetItemSet().GetItemState( + bFooter ? SID_ATTR_PAGE_HEADERSET : SID_ATTR_PAGE_FOOTERSET, + false, reinterpret_cast<const SfxPoolItem**>(&pSetItem))) + { + PutItemToSet(pSetItem, *pPropSet, *pEntry, rValues[nProp], aBaseImpl); + } + } + } + else if(pEntry->nWID == SID_ATTR_PAGE_ON && rValues[nProp].get<bool>()) + { + // Header/footer gets switched on, create defaults and the needed SfxSetItem + SfxItemSet aTempSet(*aBaseImpl.GetItemSet().GetPool(), + svl::Items<RES_FRMATR_BEGIN,RES_FRMATR_END - 1, // [82 + + // FillAttribute support + XATTR_FILL_FIRST, XATTR_FILL_LAST, // [1014 + + SID_ATTR_BORDER_INNER,SID_ATTR_BORDER_INNER, // [10023 + SID_ATTR_PAGE_SIZE,SID_ATTR_PAGE_SIZE, // [10051 + SID_ATTR_PAGE_ON,SID_ATTR_PAGE_SHARED, // [10060 + SID_ATTR_PAGE_SHARED_FIRST,SID_ATTR_PAGE_SHARED_FIRST>{}); + + // set correct parent to get the XFILL_NONE FillStyle as needed + aTempSet.SetParent(&GetDoc()->GetDfltFrameFormat()->GetAttrSet()); + + aTempSet.Put(SfxBoolItem(SID_ATTR_PAGE_ON, true)); + aTempSet.Put(SvxSizeItem(SID_ATTR_PAGE_SIZE, Size(MM50, MM50))); + aTempSet.Put(SvxLRSpaceItem(RES_LR_SPACE)); + aTempSet.Put(SvxULSpaceItem(RES_UL_SPACE)); + aTempSet.Put(SfxBoolItem(SID_ATTR_PAGE_SHARED, true)); + aTempSet.Put(SfxBoolItem(SID_ATTR_PAGE_SHARED_FIRST, true)); + aTempSet.Put(SfxBoolItem(SID_ATTR_PAGE_DYNAMIC, true)); + + SvxSetItem aNewSetItem(bFooter ? SID_ATTR_PAGE_FOOTERSET : SID_ATTR_PAGE_HEADERSET, aTempSet); + aBaseImpl.GetItemSet().Put(aNewSetItem); + } + } + continue; + case XATTR_FILLBMP_SIZELOG: + case XATTR_FILLBMP_TILEOFFSETX: + case XATTR_FILLBMP_TILEOFFSETY: + case XATTR_FILLBMP_POSOFFSETX: + case XATTR_FILLBMP_POSOFFSETY: + case XATTR_FILLBMP_POS: + case XATTR_FILLBMP_SIZEX: + case XATTR_FILLBMP_SIZEY: + case XATTR_FILLBMP_STRETCH: + case XATTR_FILLBMP_TILE: + case OWN_ATTR_FILLBMP_MODE: + case XATTR_FILLCOLOR: + case XATTR_FILLBACKGROUND: + case XATTR_FILLBITMAP: + case XATTR_GRADIENTSTEPCOUNT: + case XATTR_FILLGRADIENT: + case XATTR_FILLHATCH: + case XATTR_FILLSTYLE: + case XATTR_FILLTRANSPARENCE: + case XATTR_FILLFLOATTRANSPARENCE: + case XATTR_SECONDARYFILLCOLOR: + if(bFirstIsShared) // only special handling for headers/footers here + break; + { + const SvxSetItem* pSetItem = nullptr; + + if(SfxItemState::SET == aBaseImpl.GetItemSet().GetItemState(bFooter ? SID_ATTR_PAGE_FOOTERSET : SID_ATTR_PAGE_HEADERSET, false, reinterpret_cast<const SfxPoolItem**>(&pSetItem))) + { + // create a new SvxSetItem and get it's ItemSet as new target + std::unique_ptr<SvxSetItem> pNewSetItem(pSetItem->Clone()); + SfxItemSet& rSetSet = pNewSetItem->GetItemSet(); + + // set parent to ItemSet to ensure XFILL_NONE as XFillStyleItem + rSetSet.SetParent(&GetDoc()->GetDfltFrameFormat()->GetAttrSet()); + + // replace the used SfxItemSet at the SwStyleBase_Impl temporarily and use the + // default method to set the property + { + SwStyleBase_Impl::ItemSetOverrider o(aBaseImpl, &rSetSet); + SetStyleProperty(*pEntry, *pPropSet, rValues[nProp], aBaseImpl); + } + + // reset paret at ItemSet from SetItem + rSetSet.SetParent(nullptr); + + // set the new SvxSetItem at the real target and delete it + aBaseImpl.GetItemSet().Put(*pNewSetItem); + } + } + continue; + default: ; + } + } + switch(pEntry->nWID) + { + case SID_ATTR_PAGE_DYNAMIC: + case SID_ATTR_PAGE_SHARED: + case SID_ATTR_PAGE_SHARED_FIRST: + case SID_ATTR_PAGE_ON: + case RES_HEADER_FOOTER_EAT_SPACING: + // these slots are exclusive to Header/Footer, thus this is an error + throw beans::UnknownPropertyException("Unknown property: " + rPropName, static_cast<cppu::OWeakObject*>(this)); + case FN_UNO_HEADER: + case FN_UNO_HEADER_LEFT: + case FN_UNO_HEADER_RIGHT: + case FN_UNO_HEADER_FIRST: + case FN_UNO_FOOTER: + case FN_UNO_FOOTER_LEFT: + case FN_UNO_FOOTER_RIGHT: + case FN_UNO_FOOTER_FIRST: + throw lang::IllegalArgumentException(); + case FN_PARAM_FTN_INFO: + { + const SfxPoolItem& rItem = aBaseImpl.GetItemSet().Get(FN_PARAM_FTN_INFO); + std::unique_ptr<SfxPoolItem> pNewFootnoteItem(rItem.Clone()); + if(!pNewFootnoteItem->PutValue(rValues[nProp], pEntry->nMemberId)) + throw lang::IllegalArgumentException(); + aBaseImpl.GetItemSet().Put(std::move(pNewFootnoteItem)); + break; + } + default: + { + SetStyleProperty(*pEntry, *pPropSet, rValues[nProp], aBaseImpl); + break; + } + } + } + + if(aBaseImpl.HasItemSet()) + { + ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + + if (undoGuard.UndoWasEnabled()) + { + // Fix i64460: as long as Undo of page styles with header/footer causes trouble... + GetDoc()->GetIDocumentUndoRedo().DelAllUndoObj(); + } + + aBaseImpl.getNewBase()->SetItemSet(aBaseImpl.GetItemSet()); + } +} + +void SwXPageStyle::setPropertyValues(const uno::Sequence<OUString>& rPropertyNames, const uno::Sequence<uno::Any>& rValues) +{ + SolarMutexGuard aGuard; + + // workaround for bad designed API + try + { + SetPropertyValues_Impl(rPropertyNames, rValues); + } + catch (const beans::UnknownPropertyException &rException) + { + // wrap the original (here not allowed) exception in + // a lang::WrappedTargetException that gets thrown instead. + lang::WrappedTargetException aWExc; + aWExc.TargetException <<= rException; + throw aWExc; + } +} + +static uno::Reference<text::XText> lcl_makeHeaderFooter(const sal_uInt16 nRes, const bool bHeader, SwFrameFormat const*const pFrameFormat) +{ + if (!pFrameFormat) + return nullptr; + const SfxItemSet& rSet = pFrameFormat->GetAttrSet(); + const SfxPoolItem* pItem; + if(SfxItemState::SET != rSet.GetItemState(nRes, true, &pItem)) + return nullptr; + SwFrameFormat* const pHeadFootFormat = bHeader + ? static_cast<SwFormatHeader*>(const_cast<SfxPoolItem*>(pItem))->GetHeaderFormat() + : static_cast<SwFormatFooter*>(const_cast<SfxPoolItem*>(pItem))->GetFooterFormat(); + if(!pHeadFootFormat) + return nullptr; + return SwXHeadFootText::CreateXHeadFootText(*pHeadFootFormat, bHeader); +} + +uno::Sequence<uno::Any> SwXPageStyle::GetPropertyValues_Impl(const uno::Sequence<OUString>& rPropertyNames) +{ + if(!GetDoc()) + throw uno::RuntimeException(); + + sal_Int32 nLength = rPropertyNames.getLength(); + uno::Sequence<uno::Any> aRet (nLength); + if(!m_pBasePool) + { + if(!IsDescriptor()) + throw uno::RuntimeException(); + for(sal_Int32 nProp = 0; nProp < rPropertyNames.getLength(); ++nProp) + { + const uno::Any* pAny = nullptr; + m_pPropertiesImpl->GetProperty(rPropertyNames[nProp], pAny); + if (!pAny->hasValue()) + SwStyleProperties_Impl::GetProperty(rPropertyNames[nProp], m_xStyleData, aRet[nProp]); + else + aRet[nProp] = *pAny; + } + return aRet; + } + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_PAGE_STYLE); + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + SwStyleBase_Impl aBase(*GetDoc(), GetStyleName(), &GetDoc()->GetDfltFrameFormat()->GetAttrSet()); // add pDfltFrameFormat as parent + SfxStyleSheetBase* pBase = GetStyleSheetBase(); + if(!pBase) + throw uno::RuntimeException(); + for(sal_Int32 nProp = 0; nProp < nLength; ++nProp) + { + const OUString& rPropName = rPropertyNames[nProp]; + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(rPropName); + + if (!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropName, static_cast < cppu::OWeakObject * > ( this ) ); + const bool bHeader(rPropName.startsWith("Header")); + const bool bFooter(rPropName.startsWith("Footer")); + const bool bFirstIsShared(rPropName == UNO_NAME_FIRST_IS_SHARED); + if(bHeader || bFooter || bFirstIsShared) + { + switch(pEntry->nWID) + { + case SID_ATTR_PAGE_ON: + case RES_BACKGROUND: + case RES_BOX: + case RES_LR_SPACE: + case RES_SHADOW: + case RES_UL_SPACE: + case SID_ATTR_PAGE_DYNAMIC: + case SID_ATTR_PAGE_SHARED: + case SID_ATTR_PAGE_SHARED_FIRST: + case SID_ATTR_PAGE_SIZE: + case RES_HEADER_FOOTER_EAT_SPACING: + { + // slot is a Header/Footer slot + rtl::Reference< SwDocStyleSheet > xStyle( new SwDocStyleSheet( *static_cast<SwDocStyleSheet*>(pBase) ) ); + const SfxItemSet& rSet = xStyle->GetItemSet(); + const SvxSetItem* pSetItem; + + if (lcl_GetHeaderFooterItem(rSet, rPropName, bFooter, pSetItem)) + { + // get from SfxItemSet of the corresponding SfxSetItem + const SfxItemSet& rSetSet = pSetItem->GetItemSet(); + { + SwStyleBase_Impl::ItemSetOverrider o(aBase, &const_cast< SfxItemSet& >(rSetSet)); + aRet[nProp] = GetStyleProperty_Impl(*pEntry, *pPropSet, aBase); + } + } + else if(pEntry->nWID == SID_ATTR_PAGE_ON) + { + // header/footer is not available, thus off. Default is <false>, though + aRet[nProp] <<= false; + } + } + continue; + case XATTR_FILLBMP_SIZELOG: + case XATTR_FILLBMP_TILEOFFSETX: + case XATTR_FILLBMP_TILEOFFSETY: + case XATTR_FILLBMP_POSOFFSETX: + case XATTR_FILLBMP_POSOFFSETY: + case XATTR_FILLBMP_POS: + case XATTR_FILLBMP_SIZEX: + case XATTR_FILLBMP_SIZEY: + case XATTR_FILLBMP_STRETCH: + case XATTR_FILLBMP_TILE: + case OWN_ATTR_FILLBMP_MODE: + case XATTR_FILLCOLOR: + case XATTR_FILLBACKGROUND: + case XATTR_FILLBITMAP: + case XATTR_GRADIENTSTEPCOUNT: + case XATTR_FILLGRADIENT: + case XATTR_FILLHATCH: + case XATTR_FILLSTYLE: + case XATTR_FILLTRANSPARENCE: + case XATTR_FILLFLOATTRANSPARENCE: + case XATTR_SECONDARYFILLCOLOR: + if(bFirstIsShared) // only special handling for headers/footers here + break; + { + rtl::Reference< SwDocStyleSheet > xStyle( new SwDocStyleSheet( *static_cast<SwDocStyleSheet*>(pBase) ) ); + const SfxItemSet& rSet = xStyle->GetItemSet(); + const SvxSetItem* pSetItem; + if(SfxItemState::SET == rSet.GetItemState(bFooter ? SID_ATTR_PAGE_FOOTERSET : SID_ATTR_PAGE_HEADERSET, false, reinterpret_cast<const SfxPoolItem**>(&pSetItem))) + { + // set at SfxItemSet of the corresponding SfxSetItem + const SfxItemSet& rSetSet = pSetItem->GetItemSet(); + { + SwStyleBase_Impl::ItemSetOverrider o(aBase, &const_cast<SfxItemSet&>(rSetSet)); + aRet[nProp] = GetStyleProperty_Impl(*pEntry, *pPropSet, aBase); + } + } + } + continue; + default: ; + } + } + switch(pEntry->nWID) + { + // these slots are exclusive to Header/Footer, thus this is an error + case SID_ATTR_PAGE_DYNAMIC: + case SID_ATTR_PAGE_SHARED: + case SID_ATTR_PAGE_SHARED_FIRST: + case SID_ATTR_PAGE_ON: + case RES_HEADER_FOOTER_EAT_SPACING: + throw beans::UnknownPropertyException( "Unknown property: " + rPropName, static_cast < cppu::OWeakObject * > ( this ) ); + case FN_UNO_HEADER: + case FN_UNO_HEADER_LEFT: + case FN_UNO_HEADER_FIRST: + case FN_UNO_HEADER_RIGHT: + case FN_UNO_FOOTER: + case FN_UNO_FOOTER_LEFT: + case FN_UNO_FOOTER_FIRST: + case FN_UNO_FOOTER_RIGHT: + { + bool bLeft(false); + bool bFirst(false); + sal_uInt16 nRes = 0; + switch(pEntry->nWID) + { + case FN_UNO_HEADER: nRes = RES_HEADER; break; + case FN_UNO_HEADER_LEFT: nRes = RES_HEADER; bLeft = true; break; + case FN_UNO_HEADER_FIRST: nRes = RES_HEADER; bFirst = true; break; + case FN_UNO_HEADER_RIGHT: nRes = RES_HEADER; break; + case FN_UNO_FOOTER: nRes = RES_FOOTER; break; + case FN_UNO_FOOTER_LEFT: nRes = RES_FOOTER; bLeft = true; break; + case FN_UNO_FOOTER_FIRST: nRes = RES_FOOTER; bFirst = true; break; + case FN_UNO_FOOTER_RIGHT: nRes = RES_FOOTER; break; + default: ; + } + + const SwPageDesc* pDesc = aBase.GetOldPageDesc(); + assert(pDesc); + const SwFrameFormat* pFrameFormat = nullptr; + bool bShare = (nRes == RES_HEADER && pDesc->IsHeaderShared()) || (nRes == RES_FOOTER && pDesc->IsFooterShared()); + bool bShareFirst = pDesc->IsFirstShared(); + // TextLeft returns the left content if there is one, + // Text and TextRight return the master content. + // TextRight does the same as Text and is for + // compatibility only. + if(bLeft && !bShare) + pFrameFormat = &pDesc->GetLeft(); + else if(bFirst && !bShareFirst) + { + pFrameFormat = &pDesc->GetFirstMaster(); + // no need to make GetFirstLeft() accessible + // since it is always shared + } + else + pFrameFormat = &pDesc->GetMaster(); + const uno::Reference<text::XText> xRet = lcl_makeHeaderFooter(nRes, nRes == RES_HEADER, pFrameFormat); + if (xRet.is()) + aRet[nProp] <<= xRet; + } + break; + case FN_PARAM_FTN_INFO: + { + rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + const SfxItemSet& rSet = xStyle->GetItemSet(); + const SfxPoolItem& rItem = rSet.Get(FN_PARAM_FTN_INFO); + rItem.QueryValue(aRet[nProp], pEntry->nMemberId); + } + break; + default: + aRet[nProp] = GetStyleProperty_Impl(*pEntry, *pPropSet, aBase); + } + } + return aRet; +} + +uno::Sequence<uno::Any> SwXPageStyle::getPropertyValues(const uno::Sequence<OUString>& rPropertyNames) +{ + SolarMutexGuard aGuard; + uno::Sequence<uno::Any> aValues; + + // workaround for bad designed API + try + { + aValues = GetPropertyValues_Impl(rPropertyNames); + } + catch(beans::UnknownPropertyException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("Unknown property exception caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + catch(lang::WrappedTargetException &) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("WrappedTargetException caught", + static_cast < cppu::OWeakObject * > ( this ), anyEx ); + } + + return aValues; +} + +uno::Any SwXPageStyle::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + const uno::Sequence<OUString> aProperties(&rPropertyName, 1); + return GetPropertyValues_Impl(aProperties)[0]; +} + +void SwXPageStyle::setPropertyValue(const OUString& rPropertyName, const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + const uno::Sequence<OUString> aProperties(&rPropertyName, 1); + const uno::Sequence<uno::Any> aValues(&rValue, 1); + SetPropertyValues_Impl(aProperties, aValues); +} + +SwXFrameStyle::SwXFrameStyle(SwDoc *pDoc) + : SwXStyle(pDoc, SfxStyleFamily::Frame, false) +{ } + +void SwXFrameStyle::SetItem(sal_uInt16 eAtr, const SfxPoolItem& rItem) +{ + assert(eAtr >= RES_FRMATR_BEGIN && eAtr < RES_FRMATR_END); + SfxStyleSheetBase* pBase = GetStyleSheetBase(); + if(!pBase) + return; + rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + SfxItemSet& rStyleSet = xStyle->GetItemSet(); + SfxItemSet aSet(*rStyleSet.GetPool(), {{sal_uInt16(eAtr), sal_uInt16(eAtr)}}); + aSet.Put(rItem); + xStyle->SetItemSet(aSet); +} + +const SfxPoolItem* SwXFrameStyle::GetItem(sal_uInt16 eAtr) +{ + assert(eAtr >= RES_FRMATR_BEGIN && eAtr < RES_FRMATR_END); + SfxStyleSheetBase* pBase = GetStyleSheetBase(); + if(!pBase) + return nullptr; + rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*static_cast<SwDocStyleSheet*>(pBase))); + return &xStyle->GetItemSet().Get(eAtr); +} + +uno::Sequence<uno::Type> SwXFrameStyle::getTypes() +{ + return cppu::OTypeCollection( + cppu::UnoType<XEventsSupplier>::get(), + SwXStyle::getTypes() + ).getTypes(); +} + +uno::Any SwXFrameStyle::queryInterface(const uno::Type& rType) +{ + if(rType == cppu::UnoType<XEventsSupplier>::get()) + return uno::makeAny(uno::Reference<XEventsSupplier>(this)); + return SwXStyle::queryInterface(rType); +} + +uno::Reference<container::XNameReplace> SwXFrameStyle::getEvents() +{ + return new SwFrameStyleEventDescriptor(*this); +} + +// Already implemented autostyle families: 3 +#define AUTOSTYLE_FAMILY_COUNT 3 +const IStyleAccess::SwAutoStyleFamily aAutoStyleByIndex[] = +{ + IStyleAccess::AUTO_STYLE_CHAR, + IStyleAccess::AUTO_STYLE_RUBY, + IStyleAccess::AUTO_STYLE_PARA +}; + +class SwAutoStylesEnumImpl +{ + std::vector<std::shared_ptr<SfxItemSet>> mAutoStyles; + std::vector<std::shared_ptr<SfxItemSet>>::iterator aIter; + SwDoc* pDoc; + IStyleAccess::SwAutoStyleFamily eFamily; +public: + SwAutoStylesEnumImpl( SwDoc* pInitDoc, IStyleAccess::SwAutoStyleFamily eFam ); + bool hasMoreElements() { return aIter != mAutoStyles.end(); } + std::shared_ptr<SfxItemSet> const & nextElement() { return *(aIter++); } + IStyleAccess::SwAutoStyleFamily getFamily() const { return eFamily; } + SwDoc* getDoc() const { return pDoc; } +}; + +SwXAutoStyles::SwXAutoStyles(SwDocShell& rDocShell) : + SwUnoCollection(rDocShell.GetDoc()), m_pDocShell( &rDocShell ) +{ +} + +SwXAutoStyles::~SwXAutoStyles() +{ +} + +sal_Int32 SwXAutoStyles::getCount() +{ + return AUTOSTYLE_FAMILY_COUNT; +} + +uno::Any SwXAutoStyles::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + if(nIndex < 0 || nIndex >= AUTOSTYLE_FAMILY_COUNT) + throw lang::IndexOutOfBoundsException(); + if(!IsValid()) + throw uno::RuntimeException(); + + uno::Reference< style::XAutoStyleFamily > aRef; + IStyleAccess::SwAutoStyleFamily nType = aAutoStyleByIndex[nIndex]; + switch( nType ) + { + case IStyleAccess::AUTO_STYLE_CHAR: + { + if(!m_xAutoCharStyles.is()) + m_xAutoCharStyles = new SwXAutoStyleFamily(m_pDocShell, nType); + aRef = m_xAutoCharStyles; + } + break; + case IStyleAccess::AUTO_STYLE_RUBY: + { + if(!m_xAutoRubyStyles.is()) + m_xAutoRubyStyles = new SwXAutoStyleFamily(m_pDocShell, nType ); + aRef = m_xAutoRubyStyles; + } + break; + case IStyleAccess::AUTO_STYLE_PARA: + { + if(!m_xAutoParaStyles.is()) + m_xAutoParaStyles = new SwXAutoStyleFamily(m_pDocShell, nType ); + aRef = m_xAutoParaStyles; + } + break; + + default: + ; + } + aRet <<= aRef; + + return aRet; +} + +uno::Type SwXAutoStyles::getElementType( ) +{ + return cppu::UnoType<style::XAutoStyleFamily>::get(); +} + +sal_Bool SwXAutoStyles::hasElements( ) +{ + return true; +} + +uno::Any SwXAutoStyles::getByName(const OUString& Name) +{ + uno::Any aRet; + if(Name == "CharacterStyles") + aRet = getByIndex(0); + else if(Name == "RubyStyles") + aRet = getByIndex(1); + else if(Name == "ParagraphStyles") + aRet = getByIndex(2); + else + throw container::NoSuchElementException(); + return aRet; +} + +uno::Sequence< OUString > SwXAutoStyles::getElementNames() +{ + uno::Sequence< OUString > aNames(AUTOSTYLE_FAMILY_COUNT); + OUString* pNames = aNames.getArray(); + pNames[0] = "CharacterStyles"; + pNames[1] = "RubyStyles"; + pNames[2] = "ParagraphStyles"; + return aNames; +} + +sal_Bool SwXAutoStyles::hasByName(const OUString& Name) +{ + if( Name == "CharacterStyles" || + Name == "RubyStyles" || + Name == "ParagraphStyles" ) + return true; + else + return false; +} + +SwXAutoStyleFamily::SwXAutoStyleFamily(SwDocShell* pDocSh, IStyleAccess::SwAutoStyleFamily nFamily) : + m_pDocShell( pDocSh ), m_eFamily(nFamily) +{ + // Register ourselves as a listener to the document (via the page descriptor) + StartListening(pDocSh->GetDoc()->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +} + +SwXAutoStyleFamily::~SwXAutoStyleFamily() +{ +} + +void SwXAutoStyleFamily::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pDocShell = nullptr; +} + +uno::Reference< style::XAutoStyle > SwXAutoStyleFamily::insertStyle( + const uno::Sequence< beans::PropertyValue >& Values ) +{ + if (!m_pDocShell) + { + throw uno::RuntimeException(); + } + + const sal_uInt16* pRange = nullptr; + const SfxItemPropertySet* pPropSet = nullptr; + switch( m_eFamily ) + { + case IStyleAccess::AUTO_STYLE_CHAR: + { + pRange = aCharAutoFormatSetRange; + pPropSet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE); + break; + } + case IStyleAccess::AUTO_STYLE_RUBY: + { + pRange = nullptr;//aTextNodeSetRange; + pPropSet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_RUBY_AUTO_STYLE); + break; + } + case IStyleAccess::AUTO_STYLE_PARA: + { + pRange = aTextNodeSetRange; // checked, already added support for [XATTR_FILL_FIRST, XATTR_FILL_LAST] + pPropSet = aSwMapProvider.GetPropertySet(PROPERTY_MAP_PARA_AUTO_STYLE); + break; + } + default: ; + } + + if( !pPropSet) + throw uno::RuntimeException(); + + SwAttrSet aSet( m_pDocShell->GetDoc()->GetAttrPool(), pRange ); + const bool bTakeCareOfDrawingLayerFillStyle(IStyleAccess::AUTO_STYLE_PARA == m_eFamily); + + if(!bTakeCareOfDrawingLayerFillStyle) + { + for( const beans::PropertyValue& rValue : Values ) + { + try + { + pPropSet->setPropertyValue( rValue.Name, rValue.Value, aSet ); + } + catch (const beans::UnknownPropertyException &) + { + OSL_FAIL( "Unknown property" ); + } + catch (const lang::IllegalArgumentException &) + { + OSL_FAIL( "Illegal argument" ); + } + } + } + else + { + // set parent to ItemSet to ensure XFILL_NONE as XFillStyleItem + // to make cases in RES_BACKGROUND work correct; target *is* a style + // where this is the case + aSet.SetParent(&m_pDocShell->GetDoc()->GetDfltTextFormatColl()->GetAttrSet()); + + // here the used DrawingLayer FillStyles are imported when family is + // equal to IStyleAccess::AUTO_STYLE_PARA, thus we will need to serve the + // used slots functionality here to do this correctly + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + + for( const beans::PropertyValue& rValue : Values ) + { + const OUString& rPropName = rValue.Name; + uno::Any aValue(rValue.Value); + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(rPropName); + + if (!pEntry) + { + SAL_WARN("sw.core", "SwXAutoStyleFamily::insertStyle: Unknown property: " << rPropName); + continue; + } + + const sal_uInt8 nMemberId(pEntry->nMemberId); + bool bDone(false); + + // check for needed metric translation + if(pEntry->nMoreFlags & PropertyMoreFlags::METRIC_ITEM) + { + bool bDoIt(true); + + if(XATTR_FILLBMP_SIZEX == pEntry->nWID || XATTR_FILLBMP_SIZEY == pEntry->nWID) + { + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + sal_Int32 nValue = 0; + + if(aValue >>= nValue) + { + bDoIt = nValue > 0; + } + } + + if(bDoIt) + { + const SfxItemPool& rPool = m_pDocShell->GetDoc()->GetAttrPool(); + const MapUnit eMapUnit(rPool.GetMetric(pEntry->nWID)); + + if(eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertFromMM(eMapUnit, aValue); + } + } + } + + switch(pEntry->nWID) + { + case XATTR_FILLGRADIENT: + case XATTR_FILLHATCH: + case XATTR_FILLBITMAP: + case XATTR_FILLFLOATTRANSPARENCE: + // not yet needed; activate when LineStyle support may be added + // case XATTR_LINESTART: + // case XATTR_LINEEND: + // case XATTR_LINEDASH: + { + if(MID_NAME == nMemberId) + { + // add set commands for FillName items + OUString aTempName; + + if(!(aValue >>= aTempName)) + { + throw lang::IllegalArgumentException(); + } + + SvxShape::SetFillAttribute(pEntry->nWID, aTempName, aSet); + bDone = true; + } + else if (MID_BITMAP == nMemberId) + { + if(XATTR_FILLBITMAP == pEntry->nWID) + { + const Graphic aNullGraphic; + XFillBitmapItem aXFillBitmapItem(aNullGraphic); + + aXFillBitmapItem.PutValue(aValue, nMemberId); + aSet.Put(aXFillBitmapItem); + bDone = true; + } + } + + break; + } + case RES_BACKGROUND: + { + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(aSet, RES_BACKGROUND, true, m_pDocShell->GetDoc()->IsInXMLImport())); + std::unique_ptr<SvxBrushItem> aChangedBrushItem(aOriginalBrushItem->Clone()); + + aChangedBrushItem->PutValue(aValue, nMemberId); + + if(*aChangedBrushItem != *aOriginalBrushItem) + { + setSvxBrushItemAsFillAttributesToTargetSet(*aChangedBrushItem, aSet); + } + + bDone = true; + break; + } + case OWN_ATTR_FILLBMP_MODE: + { + drawing::BitmapMode eMode; + + if(!(aValue >>= eMode)) + { + sal_Int32 nMode = 0; + + if(!(aValue >>= nMode)) + { + throw lang::IllegalArgumentException(); + } + + eMode = static_cast<drawing::BitmapMode>(nMode); + } + + aSet.Put(XFillBmpStretchItem(drawing::BitmapMode_STRETCH == eMode)); + aSet.Put(XFillBmpTileItem(drawing::BitmapMode_REPEAT == eMode)); + + bDone = true; + break; + } + default: break; + } + + if(!bDone) + { + try + { + pPropSet->setPropertyValue( rPropName, aValue, aSet ); + } + catch (const beans::UnknownPropertyException &) + { + OSL_FAIL( "Unknown property" ); + } + catch (const lang::IllegalArgumentException &) + { + OSL_FAIL( "Illegal argument" ); + } + } + } + + // clear parent again + aSet.SetParent(nullptr); + } + + // need to ensure uniqueness of evtl. added NameOrIndex items + // currently in principle only needed when bTakeCareOfDrawingLayerFillStyle, + // but does not hurt and is easily forgotten later eventually, so keep it + // as common case + m_pDocShell->GetDoc()->CheckForUniqueItemForLineFillNameOrIndex(aSet); + + // AutomaticStyle creation + std::shared_ptr<SfxItemSet> pSet = m_pDocShell->GetDoc()->GetIStyleAccess().cacheAutomaticStyle( aSet, m_eFamily ); + uno::Reference<style::XAutoStyle> xRet = new SwXAutoStyle(m_pDocShell->GetDoc(), pSet, m_eFamily); + + return xRet; +} + +uno::Reference< container::XEnumeration > SwXAutoStyleFamily::createEnumeration( ) +{ + if( !m_pDocShell ) + throw uno::RuntimeException(); + return uno::Reference< container::XEnumeration > + (new SwXAutoStylesEnumerator( m_pDocShell->GetDoc(), m_eFamily )); +} + +uno::Type SwXAutoStyleFamily::getElementType( ) +{ + return cppu::UnoType<style::XAutoStyle>::get(); +} + +sal_Bool SwXAutoStyleFamily::hasElements( ) +{ + return false; +} + +SwAutoStylesEnumImpl::SwAutoStylesEnumImpl( SwDoc* pInitDoc, IStyleAccess::SwAutoStyleFamily eFam ) +: pDoc( pInitDoc ), eFamily( eFam ) +{ + // special case for ruby auto styles: + if ( IStyleAccess::AUTO_STYLE_RUBY == eFam ) + { + std::set< std::pair< sal_uInt16, text::RubyAdjust > > aRubyMap; + SwAttrPool& rAttrPool = pDoc->GetAttrPool(); + + // do this in two phases otherwise we invalidate the iterators when we insert into the pool + std::vector<const SwFormatRuby*> vRubyItems; + for (const SfxPoolItem* pItem : rAttrPool.GetItemSurrogates(RES_TXTATR_CJK_RUBY)) + { + auto pRubyItem = dynamic_cast<const SwFormatRuby*>(pItem); + if ( pRubyItem && pRubyItem->GetTextRuby() ) + vRubyItems.push_back(pRubyItem); + } + for (const SwFormatRuby* pRubyItem : vRubyItems) + { + std::pair< sal_uInt16, text::RubyAdjust > aPair( pRubyItem->GetPosition(), pRubyItem->GetAdjustment() ); + if ( aRubyMap.insert( aPair ).second ) + { + auto pItemSet = std::make_shared<SfxItemSet>( rAttrPool, svl::Items<RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY>{} ); + pItemSet->Put( *pRubyItem ); + mAutoStyles.push_back( pItemSet ); + } + } + } + else + { + pDoc->GetIStyleAccess().getAllStyles( mAutoStyles, eFamily ); + } + + aIter = mAutoStyles.begin(); +} + +SwXAutoStylesEnumerator::SwXAutoStylesEnumerator( SwDoc* pDoc, IStyleAccess::SwAutoStyleFamily eFam ) +: m_pImpl( new SwAutoStylesEnumImpl( pDoc, eFam ) ) +{ + // Register ourselves as a listener to the document (via the page descriptor) + StartListening(pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +} + +SwXAutoStylesEnumerator::~SwXAutoStylesEnumerator() +{ +} + +void SwXAutoStylesEnumerator::Notify( const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pImpl.reset(); +} + +sal_Bool SwXAutoStylesEnumerator::hasMoreElements( ) +{ + if( !m_pImpl ) + throw uno::RuntimeException(); + return m_pImpl->hasMoreElements(); +} + +uno::Any SwXAutoStylesEnumerator::nextElement( ) +{ + if( !m_pImpl ) + throw uno::RuntimeException(); + uno::Any aRet; + if( m_pImpl->hasMoreElements() ) + { + std::shared_ptr<SfxItemSet> pNextSet = m_pImpl->nextElement(); + uno::Reference< style::XAutoStyle > xAutoStyle = new SwXAutoStyle(m_pImpl->getDoc(), + pNextSet, m_pImpl->getFamily()); + aRet <<= xAutoStyle; + } + return aRet; +} + +// SwXAutoStyle with the family IStyleAccess::AUTO_STYLE_PARA (or +// PROPERTY_MAP_PARA_AUTO_STYLE) now uses DrawingLayer FillStyles to allow +// unified paragraph background fill, thus the UNO API implementation has to +// support the needed slots for these. This seems to be used only for reading +// (no setPropertyValue implementation here), so maybe specialized for saving +// the Writer Doc to ODF + +SwXAutoStyle::SwXAutoStyle( + SwDoc* pDoc, + std::shared_ptr<SfxItemSet> const & pInitSet, + IStyleAccess::SwAutoStyleFamily eFam) +: mpSet(pInitSet), + meFamily(eFam), + mrDoc(*pDoc) +{ + // Register ourselves as a listener to the document (via the page descriptor) + //StartListening(mrDoc.getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +} + +SwXAutoStyle::~SwXAutoStyle() +{ +} + +void SwXAutoStyle::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + mpSet.reset(); +} + +uno::Reference< beans::XPropertySetInfo > SwXAutoStyle::getPropertySetInfo( ) +{ + uno::Reference< beans::XPropertySetInfo > xRet; + switch( meFamily ) + { + case IStyleAccess::AUTO_STYLE_CHAR: + { + static uno::Reference< beans::XPropertySetInfo > xCharRef; + if(!xCharRef.is()) + { + xCharRef = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE)->getPropertySetInfo(); + } + xRet = xCharRef; + } + break; + case IStyleAccess::AUTO_STYLE_RUBY: + { + static uno::Reference< beans::XPropertySetInfo > xRubyRef; + if(!xRubyRef.is()) + { + const sal_uInt16 nMapId = PROPERTY_MAP_RUBY_AUTO_STYLE; + xRubyRef = aSwMapProvider.GetPropertySet(nMapId)->getPropertySetInfo(); + } + xRet = xRubyRef; + } + break; + case IStyleAccess::AUTO_STYLE_PARA: + { + static uno::Reference< beans::XPropertySetInfo > xParaRef; + if(!xParaRef.is()) + { + const sal_uInt16 nMapId = PROPERTY_MAP_PARA_AUTO_STYLE; + xParaRef = aSwMapProvider.GetPropertySet(nMapId)->getPropertySetInfo(); + } + xRet = xParaRef; + } + break; + + default: + ; + } + + return xRet; +} + +void SwXAutoStyle::setPropertyValue( const OUString& /*rPropertyName*/, const uno::Any& /*rValue*/ ) +{ +} + +uno::Any SwXAutoStyle::getPropertyValue( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + const uno::Sequence<OUString> aProperties(&rPropertyName, 1); + return GetPropertyValues_Impl(aProperties).getConstArray()[0]; +} + +void SwXAutoStyle::addPropertyChangeListener( const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ +} + +void SwXAutoStyle::removePropertyChangeListener( const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ +} + +void SwXAutoStyle::addVetoableChangeListener( const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + +void SwXAutoStyle::removeVetoableChangeListener( const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + +void SwXAutoStyle::setPropertyValues( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Sequence< uno::Any >& /*aValues*/ ) +{ +} + +uno::Sequence< uno::Any > SwXAutoStyle::GetPropertyValues_Impl( + const uno::Sequence< OUString > & rPropertyNames ) +{ + if( !mpSet ) + { + throw uno::RuntimeException(); + } + + // query_item + sal_Int8 nPropSetId = PROPERTY_MAP_CHAR_AUTO_STYLE; + switch(meFamily) + { + case IStyleAccess::AUTO_STYLE_CHAR : nPropSetId = PROPERTY_MAP_CHAR_AUTO_STYLE; break; + case IStyleAccess::AUTO_STYLE_RUBY : nPropSetId = PROPERTY_MAP_RUBY_AUTO_STYLE; break; + case IStyleAccess::AUTO_STYLE_PARA : nPropSetId = PROPERTY_MAP_PARA_AUTO_STYLE; break; + default: ; + } + + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + const OUString* pNames = rPropertyNames.getConstArray(); + + const sal_Int32 nLen(rPropertyNames.getLength()); + uno::Sequence< uno::Any > aRet( nLen ); + uno::Any* pValues = aRet.getArray(); + const bool bTakeCareOfDrawingLayerFillStyle(IStyleAccess::AUTO_STYLE_PARA == meFamily); + + for( sal_Int32 i = 0; i < nLen; ++i ) + { + const OUString sPropName = pNames[i]; + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(sPropName); + if(!pEntry) + { + throw beans::UnknownPropertyException("Unknown property: " + sPropName, static_cast < cppu::OWeakObject * > ( this ) ); + } + + uno::Any aTarget; + bool bDone(false); + + if ( RES_TXTATR_AUTOFMT == pEntry->nWID || RES_AUTO_STYLE == pEntry->nWID ) + { + OUString sName(StylePool::nameOf( mpSet )); + aTarget <<= sName; + bDone = true; + } + else if(bTakeCareOfDrawingLayerFillStyle) + { + // add support for DrawingLayer FillStyle slots + switch(pEntry->nWID) + { + case RES_BACKGROUND: + { + const std::unique_ptr<SvxBrushItem> aOriginalBrushItem(getSvxBrushItemFromSourceSet(*mpSet, RES_BACKGROUND)); + + if(!aOriginalBrushItem->QueryValue(aTarget, pEntry->nMemberId)) + { + OSL_ENSURE(false, "Error getting attribute from RES_BACKGROUND (!)"); + } + + bDone = true; + break; + } + case OWN_ATTR_FILLBMP_MODE: + { + if (mpSet->Get(XATTR_FILLBMP_TILE).GetValue()) + { + aTarget <<= drawing::BitmapMode_REPEAT; + } + else if (mpSet->Get(XATTR_FILLBMP_STRETCH).GetValue()) + { + aTarget <<= drawing::BitmapMode_STRETCH; + } + else + { + aTarget <<= drawing::BitmapMode_NO_REPEAT; + } + + bDone = true; + break; + } + } + } + + if(!bDone) + { + pPropSet->getPropertyValue( *pEntry, *mpSet, aTarget ); + } + + if(bTakeCareOfDrawingLayerFillStyle) + { + if(pEntry->aType == cppu::UnoType<sal_Int16>::get() && pEntry->aType != aTarget.getValueType()) + { + // since the sfx uint16 item now exports a sal_Int32, we may have to fix this here + sal_Int32 nValue = 0; + if (aTarget >>= nValue) + { + aTarget <<= static_cast<sal_Int16>(nValue); + } + } + + // check for needed metric translation + if(pEntry->nMoreFlags & PropertyMoreFlags::METRIC_ITEM) + { + bool bDoIt(true); + + if(XATTR_FILLBMP_SIZEX == pEntry->nWID || XATTR_FILLBMP_SIZEY == pEntry->nWID) + { + // exception: If these ItemTypes are used, do not convert when these are negative + // since this means they are intended as percent values + sal_Int32 nValue = 0; + + if(aTarget >>= nValue) + { + bDoIt = nValue > 0; + } + } + + if(bDoIt) + { + const SfxItemPool& rPool = mrDoc.GetAttrPool(); + const MapUnit eMapUnit(rPool.GetMetric(pEntry->nWID)); + + if(eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertToMM(eMapUnit, aTarget); + } + } + } + } + + // add value + pValues[i] = aTarget; + } + + return aRet; +} + +uno::Sequence< uno::Any > SwXAutoStyle::getPropertyValues ( + const uno::Sequence< OUString >& rPropertyNames ) +{ + SolarMutexGuard aGuard; + uno::Sequence< uno::Any > aValues; + + // workaround for bad designed API + try + { + aValues = GetPropertyValues_Impl( rPropertyNames ); + } + catch (beans::UnknownPropertyException &) + { + css::uno::Any exc = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("Unknown property exception caught", static_cast < cppu::OWeakObject * > ( this ), exc ); + } + catch (lang::WrappedTargetException &) + { + css::uno::Any exc = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException("WrappedTargetException caught", static_cast < cppu::OWeakObject * > ( this ), exc ); + } + + return aValues; +} + +void SwXAutoStyle::addPropertiesChangeListener( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ +} + +void SwXAutoStyle::removePropertiesChangeListener( + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ +} + +void SwXAutoStyle::firePropertiesChangeEvent( + const uno::Sequence< OUString >& /*aPropertyNames*/, + const uno::Reference< beans::XPropertiesChangeListener >& /*xListener*/ ) +{ +} + +beans::PropertyState SwXAutoStyle::getPropertyState( const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + + uno::Sequence< OUString > aNames { rPropertyName }; + uno::Sequence< beans::PropertyState > aStates = getPropertyStates(aNames); + return aStates.getConstArray()[0]; +} + +void SwXAutoStyle::setPropertyToDefault( const OUString& /*PropertyName*/ ) +{ +} + +uno::Any SwXAutoStyle::getPropertyDefault( const OUString& rPropertyName ) +{ + const uno::Sequence < OUString > aSequence ( &rPropertyName, 1 ); + return getPropertyDefaults ( aSequence ).getConstArray()[0]; +} + +uno::Sequence< beans::PropertyState > SwXAutoStyle::getPropertyStates( + const uno::Sequence< OUString >& rPropertyNames ) +{ + if (!mpSet) + { + throw uno::RuntimeException(); + } + + SolarMutexGuard aGuard; + uno::Sequence< beans::PropertyState > aRet(rPropertyNames.getLength()); + beans::PropertyState* pStates = aRet.getArray(); + const OUString* pNames = rPropertyNames.getConstArray(); + + sal_Int8 nPropSetId = PROPERTY_MAP_CHAR_AUTO_STYLE; + switch(meFamily) + { + case IStyleAccess::AUTO_STYLE_CHAR : nPropSetId = PROPERTY_MAP_CHAR_AUTO_STYLE; break; + case IStyleAccess::AUTO_STYLE_RUBY : nPropSetId = PROPERTY_MAP_RUBY_AUTO_STYLE; break; + case IStyleAccess::AUTO_STYLE_PARA : nPropSetId = PROPERTY_MAP_PARA_AUTO_STYLE; break; + default: ; + } + + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap& rMap = pPropSet->getPropertyMap(); + const bool bTakeCareOfDrawingLayerFillStyle(IStyleAccess::AUTO_STYLE_PARA == meFamily); + + for(sal_Int32 i = 0; i < rPropertyNames.getLength(); i++) + { + const OUString sPropName = pNames[i]; + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(sPropName); + if(!pEntry) + { + throw beans::UnknownPropertyException("Unknown property: " + sPropName, static_cast < cppu::OWeakObject * > ( this ) ); + } + + bool bDone(false); + + if(bTakeCareOfDrawingLayerFillStyle) + { + // DrawingLayer PropertyStyle support + switch(pEntry->nWID) + { + case OWN_ATTR_FILLBMP_MODE: + { + if(SfxItemState::SET == mpSet->GetItemState(XATTR_FILLBMP_STRETCH, false) + || SfxItemState::SET == mpSet->GetItemState(XATTR_FILLBMP_TILE, false)) + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_AMBIGUOUS_VALUE; + } + + bDone = true; + break; + } + case RES_BACKGROUND: + { + if (SWUnoHelper::needToMapFillItemsToSvxBrushItemTypes(*mpSet, + pEntry->nMemberId)) + { + pStates[i] = beans::PropertyState_DIRECT_VALUE; + } + else + { + pStates[i] = beans::PropertyState_DEFAULT_VALUE; + } + bDone = true; + + break; + } + } + } + + if(!bDone) + { + pStates[i] = pPropSet->getPropertyState(*pEntry, *mpSet ); + } + } + + return aRet; +} + +void SwXAutoStyle::setAllPropertiesToDefault( ) +{ +} + +void SwXAutoStyle::setPropertiesToDefault( + const uno::Sequence< OUString >& /*rPropertyNames*/ ) +{ +} + +uno::Sequence< uno::Any > SwXAutoStyle::getPropertyDefaults( + const uno::Sequence< OUString >& /*aPropertyNames*/ ) +{ + uno::Sequence< uno::Any > aRet(0); + return aRet; +} + +uno::Sequence< beans::PropertyValue > SwXAutoStyle::getProperties() +{ + if( !mpSet ) + throw uno::RuntimeException(); + SolarMutexGuard aGuard; + std::vector< beans::PropertyValue > aPropertyVector; + + sal_Int8 nPropSetId = 0; + switch(meFamily) + { + case IStyleAccess::AUTO_STYLE_CHAR : nPropSetId = PROPERTY_MAP_CHAR_AUTO_STYLE; break; + case IStyleAccess::AUTO_STYLE_RUBY : nPropSetId = PROPERTY_MAP_RUBY_AUTO_STYLE; break; + case IStyleAccess::AUTO_STYLE_PARA : nPropSetId = PROPERTY_MAP_PARA_AUTO_STYLE; break; + default: ; + } + + const SfxItemPropertySet* pPropSet = aSwMapProvider.GetPropertySet(nPropSetId); + const SfxItemPropertyMap &rMap = pPropSet->getPropertyMap(); + PropertyEntryVector_t aPropVector = rMap.getPropertyEntries(); + + SfxItemSet& rSet = *mpSet; + SfxItemIter aIter(rSet); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + const sal_uInt16 nWID = pItem->Which(); + + // TODO: Optimize - and fix! the old iteration filled each WhichId + // only once but there are more properties than WhichIds + for( const auto& rProp : aPropVector ) + { + if ( rProp.nWID == nWID ) + { + beans::PropertyValue aPropertyValue; + aPropertyValue.Name = rProp.sName; + pItem->QueryValue( aPropertyValue.Value, rProp.nMemberId ); + aPropertyVector.push_back( aPropertyValue ); + } + } + } + + const sal_Int32 nCount = aPropertyVector.size(); + uno::Sequence< beans::PropertyValue > aRet( nCount ); + beans::PropertyValue* pProps = aRet.getArray(); + + for ( int i = 0; i < nCount; ++i, pProps++ ) + { + *pProps = aPropertyVector[i]; + } + + return aRet; +} + +SwXTextTableStyle::SwXTextTableStyle(SwDocShell* pDocShell, SwTableAutoFormat* pTableAutoFormat) : + m_pDocShell(pDocShell), m_pTableAutoFormat(pTableAutoFormat), m_bPhysical(true) +{ + UpdateCellStylesMapping(); +} + +SwXTextTableStyle::SwXTextTableStyle(SwDocShell* pDocShell, const OUString& rTableAutoFormatName) : + m_pDocShell(pDocShell), m_pTableAutoFormat_Impl(new SwTableAutoFormat(rTableAutoFormatName)), m_bPhysical(false) +{ + m_pTableAutoFormat = m_pTableAutoFormat_Impl.get(); + UpdateCellStylesMapping(); +} + +uno::Reference<style::XStyle> SwXTextTableStyle::CreateXTextTableStyle(SwDocShell* pDocShell, const OUString& rTableAutoFormatName) +{ + SolarMutexGuard aGuard; + uno::Reference<style::XStyle> xTextTableStyle; + SwTableAutoFormat* pAutoFormat = GetTableAutoFormat(pDocShell, rTableAutoFormatName); + if (pAutoFormat && pAutoFormat->GetName() == rTableAutoFormatName) + { + xTextTableStyle.set(pAutoFormat->GetXObject(), uno::UNO_QUERY); + if (!xTextTableStyle.is()) + { + xTextTableStyle.set(new SwXTextTableStyle(pDocShell, pAutoFormat)); + pAutoFormat->SetXObject(xTextTableStyle); + } + } + + // If corresponding AutoFormat doesn't exist create a non physical style. + if (!xTextTableStyle.is()) + { + xTextTableStyle.set(new SwXTextTableStyle(pDocShell, rTableAutoFormatName)); + SAL_INFO("sw.uno", "creating SwXTextTableStyle for non existing SwTableAutoFormat"); + } + + return xTextTableStyle; +} + +void SwXTextTableStyle::UpdateCellStylesMapping() +{ + const std::vector<sal_Int32> aTableTemplateMap = SwTableAutoFormat::GetTableTemplateMap(); + assert(aTableTemplateMap.size() == STYLE_COUNT && "can not map SwTableAutoFormat to a SwXTextTableStyle"); + for (sal_Int32 i=0; i<STYLE_COUNT; ++i) + { + SwBoxAutoFormat* pBoxFormat = &m_pTableAutoFormat->GetBoxFormat(aTableTemplateMap[i]); + uno::Reference<style::XStyle> xCellStyle(pBoxFormat->GetXObject(), uno::UNO_QUERY); + if (!xCellStyle.is()) + { + xCellStyle.set(new SwXTextCellStyle(m_pDocShell, pBoxFormat, m_pTableAutoFormat->GetName())); + pBoxFormat->SetXObject(xCellStyle); + } + m_aCellStyles[i] = xCellStyle; + } +} + +const CellStyleNameMap& SwXTextTableStyle::GetCellStyleNameMap() +{ + static CellStyleNameMap const aMap + { + { "first-row" , FIRST_ROW_STYLE }, + { "last-row" , LAST_ROW_STYLE }, + { "first-column" , FIRST_COLUMN_STYLE }, + { "last-column" , LAST_COLUMN_STYLE }, + { "body" , BODY_STYLE }, + { "even-rows" , EVEN_ROWS_STYLE }, + { "odd-rows" , ODD_ROWS_STYLE }, + { "even-columns" , EVEN_COLUMNS_STYLE }, + { "odd-columns" , ODD_COLUMNS_STYLE }, + { "background" , BACKGROUND_STYLE }, + // loext namespace + { "first-row-start-column" , FIRST_ROW_START_COLUMN_STYLE }, + { "first-row-end-column" , FIRST_ROW_END_COLUMN_STYLE }, + { "last-row-start-column" , LAST_ROW_START_COLUMN_STYLE }, + { "last-row-end-column" , LAST_ROW_END_COLUMN_STYLE }, + { "first-row-even-column" , FIRST_ROW_EVEN_COLUMN_STYLE }, + { "last-row-even-column" , LAST_ROW_EVEN_COLUMN_STYLE }, + }; + return aMap; +} + +SwTableAutoFormat* SwXTextTableStyle::GetTableFormat() +{ + return m_pTableAutoFormat; +} + +SwTableAutoFormat* SwXTextTableStyle::GetTableAutoFormat(SwDocShell* pDocShell, const OUString& sName) +{ + const size_t nStyles = pDocShell->GetDoc()->GetTableStyles().size(); + for(size_t i=0; i < nStyles; ++i) + { + SwTableAutoFormat* pAutoFormat = &pDocShell->GetDoc()->GetTableStyles()[i]; + if (pAutoFormat->GetName() == sName) + { + return pAutoFormat; + } + } + // not found + return nullptr; +} + +void SwXTextTableStyle::SetPhysical() +{ + if (!m_bPhysical) + { + // find table format in doc + SwTableAutoFormat* pTableAutoFormat = GetTableAutoFormat(m_pDocShell, m_pTableAutoFormat->GetName()); + if (pTableAutoFormat) + { + m_bPhysical = true; + /// take care of children, make SwXTextCellStyles use new core SwBoxAutoFormats + const std::vector<sal_Int32> aTableTemplateMap = SwTableAutoFormat::GetTableTemplateMap(); + for (size_t i=0; i<aTableTemplateMap.size(); ++i) + { + SwBoxAutoFormat* pOldBoxFormat = &m_pTableAutoFormat->GetBoxFormat(aTableTemplateMap[i]); + uno::Reference<style::XStyle> xCellStyle(pOldBoxFormat->GetXObject(), uno::UNO_QUERY); + if (!xCellStyle.is()) + continue; + SwXTextCellStyle& rStyle = dynamic_cast<SwXTextCellStyle&>(*xCellStyle); + SwBoxAutoFormat& rNewBoxFormat = pTableAutoFormat->GetBoxFormat(aTableTemplateMap[i]); + rStyle.SetBoxFormat(&rNewBoxFormat); + rNewBoxFormat.SetXObject(xCellStyle); + } + m_pTableAutoFormat_Impl = nullptr; + m_pTableAutoFormat = pTableAutoFormat; + m_pTableAutoFormat->SetXObject(uno::Reference<style::XStyle>(this)); + } + else + SAL_WARN("sw.uno", "setting style physical, but SwTableAutoFormat in document not found"); + } + else + SAL_WARN("sw.uno", "calling SetPhysical on a physical SwXTextTableStyle"); +} + +// XStyle +sal_Bool SAL_CALL SwXTextTableStyle::isUserDefined() +{ + SolarMutexGuard aGuard; + // only first style is not user defined + if (m_pDocShell->GetDoc()->GetTableStyles()[0].GetName() == m_pTableAutoFormat->GetName()) + return false; + + return true; +} + +sal_Bool SAL_CALL SwXTextTableStyle::isInUse() +{ + SolarMutexGuard aGuard; + if (!m_bPhysical) + return false; + + SwAutoFormatGetDocNode aGetHt( &m_pDocShell->GetDoc()->GetNodes() ); + + for (SwFrameFormat* const & pFormat : *m_pDocShell->GetDoc()->GetTableFrameFormats()) + { + if (!pFormat->GetInfo(aGetHt)) + { + SwTable* pTable = SwTable::FindTable(pFormat); + if (pTable->GetTableStyleName() == m_pTableAutoFormat->GetName()) + return true; + } + } + + return false; +} + +OUString SAL_CALL SwXTextTableStyle::getParentStyle() +{ + return OUString(); +} + +void SAL_CALL SwXTextTableStyle::setParentStyle(const OUString& /*aParentStyle*/) +{ } + +//XNamed +OUString SAL_CALL SwXTextTableStyle::getName() +{ + SolarMutexGuard aGuard; + OUString sProgName; + SwStyleNameMapper::FillProgName(m_pTableAutoFormat->GetName(), sProgName, SwGetPoolIdFromName::TabStyle); + return sProgName; +} + +void SAL_CALL SwXTextTableStyle::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + m_pTableAutoFormat->SetName(rName); +} + +//XPropertySet +css::uno::Reference<css::beans::XPropertySetInfo> SAL_CALL SwXTextTableStyle::getPropertySetInfo() +{ + static uno::Reference<beans::XPropertySetInfo> xRef(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TABLE_STYLE)->getPropertySetInfo()); + return xRef; +} + +void SAL_CALL SwXTextTableStyle::setPropertyValue(const OUString& /*rPropertyName*/, const css::uno::Any& /*aValue*/) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +css::uno::Any SAL_CALL SwXTextTableStyle::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + bool bIsRow = false; + + if (rPropertyName == UNO_NAME_TABLE_FIRST_ROW_END_COLUMN) + bIsRow = m_pTableAutoFormat->FirstRowEndColumnIsRow(); + else if (rPropertyName == UNO_NAME_TABLE_FIRST_ROW_START_COLUMN) + bIsRow = m_pTableAutoFormat->FirstRowStartColumnIsRow(); + else if (rPropertyName == UNO_NAME_TABLE_LAST_ROW_END_COLUMN) + bIsRow = m_pTableAutoFormat->LastRowEndColumnIsRow(); + else if (rPropertyName == UNO_NAME_TABLE_LAST_ROW_START_COLUMN) + bIsRow = m_pTableAutoFormat->LastRowStartColumnIsRow(); + else if (rPropertyName == UNO_NAME_DISPLAY_NAME) + return uno::makeAny(m_pTableAutoFormat->GetName()); + else + throw css::beans::UnknownPropertyException(rPropertyName); + + return uno::makeAny(bIsRow ? OUString("row") : OUString("column")); +} + +void SAL_CALL SwXTextTableStyle::addPropertyChangeListener( const OUString& /*aPropertyName*/, const css::uno::Reference< css::beans::XPropertyChangeListener >& /*xListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextTableStyle::removePropertyChangeListener( const OUString& /*aPropertyName*/, const css::uno::Reference< css::beans::XPropertyChangeListener >& /*aListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextTableStyle::addVetoableChangeListener( const OUString& /*PropertyName*/, const css::uno::Reference< css::beans::XVetoableChangeListener >& /*aListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextTableStyle::removeVetoableChangeListener( const OUString& /*PropertyName*/, const css::uno::Reference< css::beans::XVetoableChangeListener >& /*aListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +//XNameAccess +uno::Any SAL_CALL SwXTextTableStyle::getByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + const CellStyleNameMap& rMap = GetCellStyleNameMap(); + CellStyleNameMap::const_iterator iter = rMap.find(rName); + if(iter == rMap.end()) + throw css::container::NoSuchElementException(); + + return css::uno::Any(m_aCellStyles[(*iter).second]); +} + +css::uno::Sequence<OUString> SAL_CALL SwXTextTableStyle::getElementNames() +{ + SolarMutexGuard aGuard; + return comphelper::mapKeysToSequence(GetCellStyleNameMap()); +} + +sal_Bool SAL_CALL SwXTextTableStyle::hasByName(const OUString& rName) +{ + SolarMutexGuard aGuard; + const CellStyleNameMap& rMap = GetCellStyleNameMap(); + CellStyleNameMap::const_iterator iter = rMap.find(rName); + return iter != rMap.end(); +} + +//XNameContainer +void SAL_CALL SwXTextTableStyle::insertByName(const OUString& /*Name*/, const uno::Any& /*Element*/) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextTableStyle::replaceByName(const OUString& rName, const uno::Any& rElement) +{ + SolarMutexGuard aGuard; + const CellStyleNameMap& rMap = GetCellStyleNameMap(); + CellStyleNameMap::const_iterator iter = rMap.find(rName); + if(iter == rMap.end()) + throw container::NoSuchElementException(); + const sal_Int32 nCellStyle = iter->second; + + uno::Reference<style::XStyle> xStyle = rElement.get<uno::Reference<style::XStyle>>(); + if (!xStyle.is()) + throw lang::IllegalArgumentException(); + + SwXTextCellStyle* pStyleToReplaceWith = dynamic_cast<SwXTextCellStyle*>(xStyle.get()); + if (!pStyleToReplaceWith) + throw lang::IllegalArgumentException(); + + // replace only with physical ... + if (!pStyleToReplaceWith->IsPhysical()) + throw lang::IllegalArgumentException(); + + const auto& rTableTemplateMap = SwTableAutoFormat::GetTableTemplateMap(); + const sal_Int32 nBoxFormat = rTableTemplateMap[nCellStyle]; + + // move SwBoxAutoFormat to dest. SwTableAutoFormat + m_pTableAutoFormat->SetBoxFormat(*pStyleToReplaceWith->GetBoxFormat(), nBoxFormat); + // remove unassigned SwBoxAutoFormat, which is not anymore in use anyways + m_pDocShell->GetDoc()->GetCellStyles().RemoveBoxFormat(xStyle->getName()); + // make SwXTextCellStyle use new, moved SwBoxAutoFormat + pStyleToReplaceWith->SetBoxFormat(&m_pTableAutoFormat->GetBoxFormat(nBoxFormat)); + m_pTableAutoFormat->GetBoxFormat(nBoxFormat).SetXObject(xStyle); + // make this SwXTextTableStyle use new SwXTextCellStyle + m_aCellStyles[nCellStyle] = xStyle; +} + +void SAL_CALL SwXTextTableStyle::removeByName(const OUString& /*Name*/) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +//XElementAccess +uno::Type SAL_CALL SAL_CALL SwXTextTableStyle::getElementType() +{ + return cppu::UnoType<style::XStyle>::get(); +} + +sal_Bool SAL_CALL SAL_CALL SwXTextTableStyle::hasElements() +{ + return true; +} + +//XServiceInfo +OUString SAL_CALL SwXTextTableStyle::getImplementationName() +{ + return {"SwXTextTableStyle"}; +} + +sal_Bool SAL_CALL SwXTextTableStyle::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL SwXTextTableStyle::getSupportedServiceNames() +{ + return {"com.sun.star.style.Style"}; +} + +// SwXTextCellStyle +SwXTextCellStyle::SwXTextCellStyle(SwDocShell* pDocShell, SwBoxAutoFormat* pBoxAutoFormat, const OUString& sParentStyle) : + m_pDocShell(pDocShell), + m_pBoxAutoFormat(pBoxAutoFormat), + m_sParentStyle(sParentStyle), + m_bPhysical(true) +{ } + +SwXTextCellStyle::SwXTextCellStyle(SwDocShell* pDocShell, const OUString& sName) : + m_pDocShell(pDocShell), + m_pBoxAutoFormat_Impl(std::make_shared<SwBoxAutoFormat>()), + m_sName(sName), + m_bPhysical(false) +{ + m_pBoxAutoFormat = m_pBoxAutoFormat_Impl.get(); +} + +SwBoxAutoFormat* SwXTextCellStyle::GetBoxFormat() +{ + return m_pBoxAutoFormat; +} + +void SwXTextCellStyle::SetBoxFormat(SwBoxAutoFormat* pBoxFormat) +{ + if (m_bPhysical) + m_pBoxAutoFormat = pBoxFormat; + else + SAL_INFO("sw.uno", "trying to call SwXTextCellStyle::SetBoxFormat on non physical style"); +} + +void SwXTextCellStyle::SetPhysical() +{ + if (!m_bPhysical) + { + SwBoxAutoFormat* pBoxAutoFormat = GetBoxAutoFormat(m_pDocShell, m_sName, &m_sParentStyle); + if (pBoxAutoFormat) + { + m_bPhysical = true; + m_pBoxAutoFormat_Impl = nullptr; + m_pBoxAutoFormat = pBoxAutoFormat; + m_pBoxAutoFormat->SetXObject(uno::Reference<style::XStyle>(this)); + } + else + SAL_WARN("sw.uno", "setting style physical, but SwBoxAutoFormat in document not found"); + } + else + SAL_WARN("sw.uno", "calling SetPhysical on a physical SwXTextCellStyle"); +} + +bool SwXTextCellStyle::IsPhysical() const +{ + return m_bPhysical; +} + +SwBoxAutoFormat* SwXTextCellStyle::GetBoxAutoFormat(SwDocShell* pDocShell, const OUString& sName, OUString* pParentName) +{ + if (sName.isEmpty()) + return nullptr; + + SwBoxAutoFormat* pBoxAutoFormat = pDocShell->GetDoc()->GetCellStyles().GetBoxFormat(sName); + if (!pBoxAutoFormat) + { + sal_Int32 nSeparatorIndex, nTemplateIndex; + OUString sParentName, sCellSubName; + + nSeparatorIndex = sName.lastIndexOf('.'); + if (0 >= nSeparatorIndex) + return nullptr; + + sParentName = sName.copy(0, nSeparatorIndex); + sCellSubName = sName.copy(nSeparatorIndex+1); + nTemplateIndex = sCellSubName.toInt32()-1; // -1 because cell styles names start from 1, but internally are indexed from 0 + if (0 > nTemplateIndex) + return nullptr; + + const auto& rTableTemplateMap = SwTableAutoFormat::GetTableTemplateMap(); + if (rTableTemplateMap.size() <= o3tl::make_unsigned(nTemplateIndex)) + return nullptr; + + SwStyleNameMapper::FillUIName(sParentName, sParentName, SwGetPoolIdFromName::TabStyle); + SwTableAutoFormat* pTableAutoFormat = pDocShell->GetDoc()->GetTableStyles().FindAutoFormat(sParentName); + if (!pTableAutoFormat) + return nullptr; + + if (pParentName) + *pParentName = sParentName; + sal_uInt32 nBoxIndex = rTableTemplateMap[nTemplateIndex]; + pBoxAutoFormat = &pTableAutoFormat->GetBoxFormat(nBoxIndex); + } + + return pBoxAutoFormat; +} + +css::uno::Reference<css::style::XStyle> SwXTextCellStyle::CreateXTextCellStyle(SwDocShell* pDocShell, const OUString& sName) +{ + uno::Reference<style::XStyle> xTextCellStyle; + + if (!sName.isEmpty()) // create a cell style for a physical box + { + OUString sParentName; + SwBoxAutoFormat* pBoxFormat = GetBoxAutoFormat(pDocShell, sName, &sParentName); + + // something went wrong but we don't want a crash + if (!pBoxFormat) + { + // return a default-dummy style to prevent crash + static SwBoxAutoFormat aDefaultBoxFormat; + pBoxFormat = &aDefaultBoxFormat; + } + + xTextCellStyle.set(pBoxFormat->GetXObject(), uno::UNO_QUERY); + if (!xTextCellStyle.is()) + { + xTextCellStyle.set(new SwXTextCellStyle(pDocShell, pBoxFormat, sParentName)); + pBoxFormat->SetXObject(xTextCellStyle); + } + } + else // create a non physical style + xTextCellStyle.set(new SwXTextCellStyle(pDocShell, sName)); + + return xTextCellStyle; +} + +// XStyle +sal_Bool SAL_CALL SwXTextCellStyle::isUserDefined() +{ + SolarMutexGuard aGuard; + // if this cell belong to first table style then its default style + if (&m_pDocShell->GetDoc()->GetTableStyles()[0] == m_pDocShell->GetDoc()->GetTableStyles().FindAutoFormat(m_sParentStyle)) + return false; + + return true; +} + +sal_Bool SAL_CALL SwXTextCellStyle::isInUse() +{ + SolarMutexGuard aGuard; + uno::Reference<style::XStyleFamiliesSupplier> xFamiliesSupplier(m_pDocShell->GetModel(), uno::UNO_QUERY); + if (!xFamiliesSupplier.is()) + return false; + + uno::Reference<container::XNameAccess> xFamilies = xFamiliesSupplier->getStyleFamilies(); + if (!xFamilies.is()) + return false; + + uno::Reference<container::XNameAccess> xTableStyles; + xFamilies->getByName("TableStyles") >>= xTableStyles; + if (!xTableStyles.is()) + return false; + + uno::Reference<style::XStyle> xStyle; + xTableStyles->getByName(m_sParentStyle) >>= xStyle; + if (!xStyle.is()) + return false; + + return xStyle->isInUse(); +} + +OUString SAL_CALL SwXTextCellStyle::getParentStyle() +{ + // Do not return name of the parent (which is a table style) because the parent should be a cell style. + return OUString(); +} + +void SAL_CALL SwXTextCellStyle::setParentStyle(const OUString& /*sParentStyle*/) +{ + SolarMutexGuard aGuard; + // Changing parent to one which is unaware of it will lead to a something unexpected. getName() rely on a parent. + SAL_INFO("sw.uno", "Changing SwXTextCellStyle parent"); +} + +//XNamed +OUString SAL_CALL SwXTextCellStyle::getName() +{ + SolarMutexGuard aGuard; + OUString sName; + + // if style is physical then we request a name from doc + if (m_bPhysical) + { + SwTableAutoFormat* pTableFormat = m_pDocShell->GetDoc()->GetTableStyles().FindAutoFormat(m_sParentStyle); + if (!pTableFormat) + { + // if auto format is not found as a child of table formats, look in SwDoc cellstyles + sName = m_pDocShell->GetDoc()->GetCellStyles().GetBoxFormatName(*m_pBoxAutoFormat); + } + else + { + OUString sParentStyle; + SwStyleNameMapper::FillProgName(m_sParentStyle, sParentStyle, SwGetPoolIdFromName::TabStyle); + sName = sParentStyle + pTableFormat->GetTableTemplateCellSubName(*m_pBoxAutoFormat); + } + } + else + sName = m_sName; + + return sName; +} + +void SAL_CALL SwXTextCellStyle::setName(const OUString& sName) +{ + SolarMutexGuard aGuard; + // if style is physical then we can not rename it. + if (!m_bPhysical) + m_sName = sName; + // change name if style is unassigned (name is not generated automatically) + m_pDocShell->GetDoc()->GetCellStyles().ChangeBoxFormatName(getName(), sName); +} + +//XPropertySet +css::uno::Reference<css::beans::XPropertySetInfo> SAL_CALL SwXTextCellStyle::getPropertySetInfo() +{ + static uno::Reference<beans::XPropertySetInfo> xRef(aSwMapProvider.GetPropertySet(PROPERTY_MAP_CELL_STYLE)->getPropertySetInfo()); + return xRef; +} + +void SAL_CALL SwXTextCellStyle::setPropertyValue(const OUString& rPropertyName, const css::uno::Any& aValue) +{ + SolarMutexGuard aGuard; + const SfxItemPropertySimpleEntry *const pEntry = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CELL_STYLE)->getPropertyMap().getByName(rPropertyName); + if(pEntry) + { + switch(pEntry->nWID) + { + case RES_BACKGROUND: + { + SvxBrushItem rBrush = m_pBoxAutoFormat->GetBackground(); + rBrush.PutValue(aValue, 0); + m_pBoxAutoFormat->SetBackground(rBrush); + return; + } + case RES_BOX: + { + SvxBoxItem rBox = m_pBoxAutoFormat->GetBox(); + rBox.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetBox(rBox); + return; + } + case RES_VERT_ORIENT: + { + SwFormatVertOrient rVertOrient = m_pBoxAutoFormat->GetVerticalAlignment(); + rVertOrient.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetVerticalAlignment(rVertOrient); + return; + } + case RES_FRAMEDIR: + { + SvxFrameDirectionItem rDirItem = m_pBoxAutoFormat->GetTextOrientation(); + rDirItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetTextOrientation(rDirItem); + return; + } + case RES_BOXATR_FORMAT: + { + sal_uInt32 nKey; + if (aValue >>= nKey) + { + // FIXME: It's not working for old "automatic" currency formats, which are still in use by autotbl.fmt. + // Scenario: + // 1) Mark all styles present by default in autotbl.fmt as default. + // 2) convert all currencies present in autotbl.fmt before calling this code + const SvNumberformat* pNumFormat = m_pDocShell->GetDoc()->GetNumberFormatter()->GetEntry(nKey); + if (pNumFormat) + m_pBoxAutoFormat->SetValueFormat(pNumFormat->GetFormatstring(), pNumFormat->GetLanguage(), GetAppLanguage()); + } + return; + } + // Paragraph attributes + case RES_PARATR_ADJUST: + { + SvxAdjustItem rAdjustItem = m_pBoxAutoFormat->GetAdjust(); + rAdjustItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetAdjust(rAdjustItem); + return; + } + case RES_CHRATR_COLOR: + { + SvxColorItem rColorItem = m_pBoxAutoFormat->GetColor(); + rColorItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetColor(rColorItem); + return; + } + case RES_CHRATR_SHADOWED: + { + SvxShadowedItem rShadowedItem = m_pBoxAutoFormat->GetShadowed(); + bool bValue = false; aValue >>= bValue; + rShadowedItem.SetValue(bValue); + m_pBoxAutoFormat->SetShadowed(rShadowedItem); + return; + } + case RES_CHRATR_CONTOUR: + { + SvxContourItem rContourItem = m_pBoxAutoFormat->GetContour(); + bool bValue = false; aValue >>= bValue; + rContourItem.SetValue(bValue); + m_pBoxAutoFormat->SetContour(rContourItem); + return; + } + case RES_CHRATR_CROSSEDOUT: + { + SvxCrossedOutItem rCrossedOutItem = m_pBoxAutoFormat->GetCrossedOut(); + rCrossedOutItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCrossedOut(rCrossedOutItem); + return; + } + case RES_CHRATR_UNDERLINE: + { + SvxUnderlineItem rUnderlineItem = m_pBoxAutoFormat->GetUnderline(); + rUnderlineItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetUnderline(rUnderlineItem); + return; + } + case RES_CHRATR_FONTSIZE: + { + SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetHeight(); + rFontHeightItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetHeight(rFontHeightItem); + return; + } + case RES_CHRATR_WEIGHT: + { + SvxWeightItem rWeightItem = m_pBoxAutoFormat->GetWeight(); + rWeightItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetWeight(rWeightItem); + return; + } + case RES_CHRATR_POSTURE: + { + SvxPostureItem rPostureItem = m_pBoxAutoFormat->GetPosture(); + rPostureItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetPosture(rPostureItem); + return; + } + case RES_CHRATR_FONT: + { + SvxFontItem rFontItem = m_pBoxAutoFormat->GetFont(); + rFontItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetFont(rFontItem); + return; + } + case RES_CHRATR_CJK_FONTSIZE: + { + SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetCJKHeight(); + rFontHeightItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKHeight(rFontHeightItem); + return; + } + case RES_CHRATR_CJK_WEIGHT: + { + SvxWeightItem rWeightItem = m_pBoxAutoFormat->GetCJKWeight(); + rWeightItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKWeight(rWeightItem); + return; + } + case RES_CHRATR_CJK_POSTURE: + { + SvxPostureItem rPostureItem = m_pBoxAutoFormat->GetCJKPosture(); + rPostureItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKPosture(rPostureItem); + return; + } + case RES_CHRATR_CJK_FONT: + { + SvxFontItem rFontItem = m_pBoxAutoFormat->GetCJKFont(); + rFontItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKFont(rFontItem); + return; + } + case RES_CHRATR_CTL_FONTSIZE: + { + SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetCTLHeight(); + rFontHeightItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLHeight(rFontHeightItem); + return; + } + case RES_CHRATR_CTL_WEIGHT: + { + SvxWeightItem rWeightItem = m_pBoxAutoFormat->GetCTLWeight(); + rWeightItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLWeight(rWeightItem); + return; + } + case RES_CHRATR_CTL_POSTURE: + { + SvxPostureItem rPostureItem = m_pBoxAutoFormat->GetCTLPosture(); + rPostureItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLPosture(rPostureItem); + return; + } + case RES_CHRATR_CTL_FONT: + { + SvxFontItem rFontItem = m_pBoxAutoFormat->GetCTLFont(); + rFontItem.PutValue(aValue, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLFont(rFontItem); + return; + } + default: + SAL_WARN("sw.uno", "SwXTextCellStyle unknown nWID"); + throw css::uno::RuntimeException(); + } + } + + throw css::beans::UnknownPropertyException(rPropertyName); +} + +css::uno::Any SAL_CALL SwXTextCellStyle::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + const SfxItemPropertySimpleEntry *const pEntry = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CELL_STYLE)->getPropertyMap().getByName(rPropertyName); + if(pEntry) + { + switch(pEntry->nWID) + { + case RES_BACKGROUND: + { + const SvxBrushItem& rBrush = m_pBoxAutoFormat->GetBackground(); + rBrush.QueryValue(aRet); + return aRet; + } + case RES_BOX: + { + const SvxBoxItem& rBox = m_pBoxAutoFormat->GetBox(); + rBox.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_VERT_ORIENT: + { + const SwFormatVertOrient& rVertOrient = m_pBoxAutoFormat->GetVerticalAlignment(); + rVertOrient.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_FRAMEDIR: + { + const SvxFrameDirectionItem& rDirItem = m_pBoxAutoFormat->GetTextOrientation(); + rDirItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_BOXATR_FORMAT: + { + OUString sFormat; + LanguageType eLng, eSys; + m_pBoxAutoFormat->GetValueFormat(sFormat, eLng, eSys); + if(!sFormat.isEmpty()) + { + SvNumFormatType nType; bool bNew; sal_Int32 nCheckPos; + sal_uInt32 nKey = m_pDocShell->GetDoc()->GetNumberFormatter()->GetIndexPuttingAndConverting(sFormat, eLng, eSys, nType, bNew, nCheckPos); + aRet <<= nKey; + } + return aRet; + } + // Paragraph attributes + case RES_PARATR_ADJUST: + { + const SvxAdjustItem& rAdjustItem = m_pBoxAutoFormat->GetAdjust(); + rAdjustItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_COLOR: + { + const SvxColorItem& rColorItem = m_pBoxAutoFormat->GetColor(); + rColorItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_SHADOWED: + { + const SvxShadowedItem& rShadowedItem = m_pBoxAutoFormat->GetShadowed(); + aRet <<= rShadowedItem.GetValue(); + return aRet; + } + case RES_CHRATR_CONTOUR: + { + const SvxContourItem& rContourItem = m_pBoxAutoFormat->GetContour(); + aRet <<= rContourItem.GetValue(); + return aRet; + } + case RES_CHRATR_CROSSEDOUT: + { + const SvxCrossedOutItem& rCrossedOutItem = m_pBoxAutoFormat->GetCrossedOut(); + rCrossedOutItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_UNDERLINE: + { + const SvxUnderlineItem& rUnderlineItem = m_pBoxAutoFormat->GetUnderline(); + rUnderlineItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_FONTSIZE: + { + const SvxFontHeightItem& rFontHeightItem = m_pBoxAutoFormat->GetHeight(); + rFontHeightItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_WEIGHT: + { + const SvxWeightItem& rWeightItem = m_pBoxAutoFormat->GetWeight(); + rWeightItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_POSTURE: + { + const SvxPostureItem& rPostureItem = m_pBoxAutoFormat->GetPosture(); + rPostureItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_FONT: + { + const SvxFontItem rFontItem = m_pBoxAutoFormat->GetFont(); + rFontItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CJK_FONTSIZE: + { + const SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetCJKHeight(); + rFontHeightItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CJK_WEIGHT: + { + const SvxWeightItem& rWeightItem = m_pBoxAutoFormat->GetCJKWeight(); + rWeightItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CJK_POSTURE: + { + const SvxPostureItem& rPostureItem = m_pBoxAutoFormat->GetCJKPosture(); + rPostureItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CJK_FONT: + { + const SvxFontItem rFontItem = m_pBoxAutoFormat->GetCJKFont(); + rFontItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CTL_FONTSIZE: + { + const SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetCTLHeight(); + rFontHeightItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CTL_WEIGHT: + { + const SvxWeightItem& rWeightItem = m_pBoxAutoFormat->GetCTLWeight(); + rWeightItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CTL_POSTURE: + { + const SvxPostureItem& rPostureItem = m_pBoxAutoFormat->GetCTLPosture(); + rPostureItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + case RES_CHRATR_CTL_FONT: + { + const SvxFontItem rFontItem = m_pBoxAutoFormat->GetCTLFont(); + rFontItem.QueryValue(aRet, pEntry->nMemberId); + return aRet; + } + default: + SAL_WARN("sw.uno", "SwXTextCellStyle unknown nWID"); + throw css::uno::RuntimeException(); + } + } + + throw css::beans::UnknownPropertyException(rPropertyName); +} + +void SAL_CALL SwXTextCellStyle::addPropertyChangeListener( const OUString& /*aPropertyName*/, const css::uno::Reference< css::beans::XPropertyChangeListener >& /*xListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextCellStyle::removePropertyChangeListener( const OUString& /*aPropertyName*/, const css::uno::Reference< css::beans::XPropertyChangeListener >& /*aListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextCellStyle::addVetoableChangeListener( const OUString& /*PropertyName*/, const css::uno::Reference< css::beans::XVetoableChangeListener >& /*aListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +void SAL_CALL SwXTextCellStyle::removeVetoableChangeListener( const OUString& /*PropertyName*/, const css::uno::Reference< css::beans::XVetoableChangeListener >& /*aListener*/ ) +{ + SAL_WARN("sw.uno", "not implemented"); +} + +//XPropertyState +css::beans::PropertyState SAL_CALL SwXTextCellStyle::getPropertyState(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Sequence<OUString> aNames { rPropertyName }; + uno::Sequence<beans::PropertyState> aStates = getPropertyStates(aNames); + return aStates.getConstArray()[0]; +} + +css::uno::Sequence<css::beans::PropertyState> SAL_CALL SwXTextCellStyle::getPropertyStates(const css::uno::Sequence<OUString>& aPropertyNames) +{ + SolarMutexGuard aGuard; + uno::Sequence<beans::PropertyState> aRet(aPropertyNames.getLength()); + beans::PropertyState* pStates = aRet.getArray(); + const SwBoxAutoFormat& rDefaultBoxFormat = SwTableAutoFormat::GetDefaultBoxFormat(); + const SfxItemPropertyMap& rMap = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CELL_STYLE)->getPropertyMap(); + const OUString* pNames = aPropertyNames.getConstArray(); + for(sal_Int32 i=0; i < aPropertyNames.getLength(); ++i) + { + const OUString sPropName = pNames[i]; + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(sPropName); + if(pEntry) + { + uno::Any aAny1, aAny2; + switch(pEntry->nWID) + { + case RES_BACKGROUND: + m_pBoxAutoFormat->GetBackground().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetBackground().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_BOX: + m_pBoxAutoFormat->GetBox().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetBox().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_VERT_ORIENT: + m_pBoxAutoFormat->GetVerticalAlignment().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetVerticalAlignment().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_FRAMEDIR: + m_pBoxAutoFormat->GetTextOrientation().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetTextOrientation().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_BOXATR_FORMAT: + { + OUString sFormat; + LanguageType eLng, eSys; + m_pBoxAutoFormat->GetValueFormat(sFormat, eLng, eSys); + pStates[i] = sFormat.isEmpty() ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + } + case RES_PARATR_ADJUST: + m_pBoxAutoFormat->GetAdjust().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetAdjust().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_COLOR: + m_pBoxAutoFormat->GetColor().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetColor().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_SHADOWED: + m_pBoxAutoFormat->GetShadowed().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetShadowed().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CONTOUR: + m_pBoxAutoFormat->GetContour().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetContour().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CROSSEDOUT: + m_pBoxAutoFormat->GetCrossedOut().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCrossedOut().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_UNDERLINE: + m_pBoxAutoFormat->GetUnderline().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetUnderline().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_FONTSIZE: + m_pBoxAutoFormat->GetHeight().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetHeight().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_WEIGHT: + m_pBoxAutoFormat->GetWeight().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetWeight().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_POSTURE: + m_pBoxAutoFormat->GetPosture().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetPosture().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_FONT: + m_pBoxAutoFormat->GetFont().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetFont().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CJK_FONTSIZE: + m_pBoxAutoFormat->GetCJKHeight().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCJKHeight().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CJK_WEIGHT: + m_pBoxAutoFormat->GetCJKWeight().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCJKWeight().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CJK_POSTURE: + m_pBoxAutoFormat->GetCJKPosture().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCJKPosture().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CJK_FONT: + m_pBoxAutoFormat->GetCJKFont().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCJKFont().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CTL_FONTSIZE: + m_pBoxAutoFormat->GetCTLHeight().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCTLHeight().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CTL_WEIGHT: + m_pBoxAutoFormat->GetCTLWeight().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCTLWeight().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CTL_POSTURE: + m_pBoxAutoFormat->GetCTLPosture().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCTLPosture().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + case RES_CHRATR_CTL_FONT: + m_pBoxAutoFormat->GetCTLFont().QueryValue(aAny1, pEntry->nMemberId); + rDefaultBoxFormat.GetCTLFont().QueryValue(aAny2, pEntry->nMemberId); + pStates[i] = aAny1 == aAny2 ? beans::PropertyState_DEFAULT_VALUE : beans::PropertyState_DIRECT_VALUE; + break; + default: + // fallthrough to DIRECT_VALUE, to export properties for which getPropertyStates is not implemented + pStates[i] = beans::PropertyState_DIRECT_VALUE; + SAL_WARN("sw.uno", "SwXTextCellStyle getPropertyStates unknown nWID"); + } + } + else + { + SAL_WARN("sw.uno", "SwXTextCellStyle unknown property:" + sPropName); + throw css::beans::UnknownPropertyException(sPropName); + } + } + return aRet; +} + +void SAL_CALL SwXTextCellStyle::setPropertyToDefault(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + const SwBoxAutoFormat& rDefaultBoxFormat = SwTableAutoFormat::GetDefaultBoxFormat(); + const SfxItemPropertyMap& rMap = aSwMapProvider.GetPropertySet(PROPERTY_MAP_CELL_STYLE)->getPropertyMap(); + const SfxItemPropertySimpleEntry* pEntry = rMap.getByName(rPropertyName); + if(pEntry) + { + uno::Any aAny; + switch(pEntry->nWID) + { + case RES_BACKGROUND: + { + SvxBrushItem rBrush = m_pBoxAutoFormat->GetBackground(); + rDefaultBoxFormat.GetBackground().QueryValue(aAny, pEntry->nMemberId); + rBrush.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetBackground(rBrush); + break; + } + case RES_BOX: + { + SvxBoxItem rBox = m_pBoxAutoFormat->GetBox(); + rDefaultBoxFormat.GetBox().QueryValue(aAny, pEntry->nMemberId); + rBox.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetBox(rBox); + break; + } + case RES_VERT_ORIENT: + { + SwFormatVertOrient rVertOrient = m_pBoxAutoFormat->GetVerticalAlignment(); + rDefaultBoxFormat.GetVerticalAlignment().QueryValue(aAny, pEntry->nMemberId); + rVertOrient.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetVerticalAlignment(rVertOrient); + break; + } + case RES_FRAMEDIR: + { + SvxFrameDirectionItem rFrameDirectionItem = m_pBoxAutoFormat->GetTextOrientation(); + rDefaultBoxFormat.GetTextOrientation().QueryValue(aAny, pEntry->nMemberId); + rFrameDirectionItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetTextOrientation(rFrameDirectionItem); + break; + } + case RES_BOXATR_FORMAT: + { + OUString sFormat; + LanguageType eLng, eSys; + rDefaultBoxFormat.GetValueFormat(sFormat, eLng, eSys); + m_pBoxAutoFormat->SetValueFormat(sFormat, eLng, eSys); + break; + } + case RES_PARATR_ADJUST: + { + SvxAdjustItem rAdjustItem = m_pBoxAutoFormat->GetAdjust(); + rDefaultBoxFormat.GetAdjust().QueryValue(aAny, pEntry->nMemberId); + rAdjustItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetAdjust(rAdjustItem); + break; + } + case RES_CHRATR_COLOR: + { + SvxColorItem rColorItem = m_pBoxAutoFormat->GetColor(); + rDefaultBoxFormat.GetColor().QueryValue(aAny, pEntry->nMemberId); + rColorItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetColor(rColorItem); + break; + } + case RES_CHRATR_SHADOWED: + { + SvxShadowedItem rShadowedItem = m_pBoxAutoFormat->GetShadowed(); + rDefaultBoxFormat.GetShadowed().QueryValue(aAny, pEntry->nMemberId); + rShadowedItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetShadowed(rShadowedItem); + break; + } + case RES_CHRATR_CONTOUR: + { + SvxContourItem rContourItem = m_pBoxAutoFormat->GetContour(); + rDefaultBoxFormat.GetContour().QueryValue(aAny, pEntry->nMemberId); + rContourItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetContour(rContourItem); + break; + } + case RES_CHRATR_CROSSEDOUT: + { + SvxCrossedOutItem rCrossedOutItem = m_pBoxAutoFormat->GetCrossedOut(); + rDefaultBoxFormat.GetCrossedOut().QueryValue(aAny, pEntry->nMemberId); + rCrossedOutItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCrossedOut(rCrossedOutItem); + break; + } + case RES_CHRATR_UNDERLINE: + { + SvxUnderlineItem rUnderlineItem = m_pBoxAutoFormat->GetUnderline(); + rDefaultBoxFormat.GetUnderline().QueryValue(aAny, pEntry->nMemberId); + rUnderlineItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetUnderline(rUnderlineItem); + break; + } + case RES_CHRATR_FONTSIZE: + { + SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetHeight(); + rDefaultBoxFormat.GetHeight().QueryValue(aAny, pEntry->nMemberId); + rFontHeightItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetHeight(rFontHeightItem); + break; + } + case RES_CHRATR_WEIGHT: + { + SvxWeightItem rWeightItem = m_pBoxAutoFormat->GetWeight(); + rDefaultBoxFormat.GetWeight().QueryValue(aAny, pEntry->nMemberId); + rWeightItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetWeight(rWeightItem); + break; + } + case RES_CHRATR_POSTURE: + { + SvxPostureItem rPostureItem = m_pBoxAutoFormat->GetPosture(); + rDefaultBoxFormat.GetPosture().QueryValue(aAny, pEntry->nMemberId); + rPostureItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetPosture(rPostureItem); + break; + } + case RES_CHRATR_FONT: + { + SvxFontItem rFontItem = m_pBoxAutoFormat->GetFont(); + rDefaultBoxFormat.GetFont().QueryValue(aAny, pEntry->nMemberId); + rFontItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetFont(rFontItem); + break; + } + case RES_CHRATR_CJK_FONTSIZE: + { + SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetCJKHeight(); + rDefaultBoxFormat.GetCJKHeight().QueryValue(aAny, pEntry->nMemberId); + rFontHeightItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKHeight(rFontHeightItem); + break; + } + case RES_CHRATR_CJK_WEIGHT: + { + SvxWeightItem rWeightItem = m_pBoxAutoFormat->GetCJKWeight(); + rDefaultBoxFormat.GetCJKWeight().QueryValue(aAny, pEntry->nMemberId); + rWeightItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKWeight(rWeightItem); + break; + } + case RES_CHRATR_CJK_POSTURE: + { + SvxPostureItem rPostureItem = m_pBoxAutoFormat->GetCJKPosture(); + rDefaultBoxFormat.GetCJKPosture().QueryValue(aAny, pEntry->nMemberId); + rPostureItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKPosture(rPostureItem); + break; + } + case RES_CHRATR_CJK_FONT: + { + SvxFontItem rFontItem = m_pBoxAutoFormat->GetCJKFont(); + rDefaultBoxFormat.GetCJKFont().QueryValue(aAny, pEntry->nMemberId); + rFontItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCJKFont(rFontItem); + break; + } + case RES_CHRATR_CTL_FONTSIZE: + { + SvxFontHeightItem rFontHeightItem = m_pBoxAutoFormat->GetCTLHeight(); + rDefaultBoxFormat.GetCTLHeight().QueryValue(aAny, pEntry->nMemberId); + rFontHeightItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLHeight(rFontHeightItem); + break; + } + case RES_CHRATR_CTL_WEIGHT: + { + SvxWeightItem rWeightItem = m_pBoxAutoFormat->GetCTLWeight(); + rDefaultBoxFormat.GetCTLWeight().QueryValue(aAny, pEntry->nMemberId); + rWeightItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLWeight(rWeightItem); + break; + } + case RES_CHRATR_CTL_POSTURE: + { + SvxPostureItem rPostureItem = m_pBoxAutoFormat->GetCTLPosture(); + rDefaultBoxFormat.GetCTLPosture().QueryValue(aAny, pEntry->nMemberId); + rPostureItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLPosture(rPostureItem); + break; + } + case RES_CHRATR_CTL_FONT: + { + SvxFontItem rFontItem = m_pBoxAutoFormat->GetCTLFont(); + rDefaultBoxFormat.GetCTLFont().QueryValue(aAny, pEntry->nMemberId); + rFontItem.PutValue(aAny, pEntry->nMemberId); + m_pBoxAutoFormat->SetCTLFont(rFontItem); + break; + } + default: + SAL_WARN("sw.uno", "SwXTextCellStyle setPropertyToDefault unknown nWID"); + } + } +} + +css::uno::Any SAL_CALL SwXTextCellStyle::getPropertyDefault(const OUString& /*aPropertyName*/) +{ + SAL_WARN("sw.uno", "not implemented"); + uno::Any aRet; + return aRet; +} + +//XServiceInfo +OUString SAL_CALL SwXTextCellStyle::getImplementationName() +{ + return {"SwXTextCellStyle"}; +} + +sal_Bool SAL_CALL SwXTextCellStyle::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL SwXTextCellStyle::getSupportedServiceNames() +{ + return {"com.sun.star.style.Style"}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unotbl.cxx b/sw/source/core/unocore/unotbl.cxx new file mode 100644 index 000000000..762dcf73d --- /dev/null +++ b/sw/source/core/unocore/unotbl.cxx @@ -0,0 +1,4194 @@ +/* -*- 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 <tuple> +#include <utility> +#include <memory> +#include <vector> +#include <algorithm> + +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <svx/svxids.hrc> +#include <editeng/memberids.h> +#include <float.h> +#include <swtypes.hxx> +#include <cmdid.h> +#include <unocoll.hxx> +#include <unomid.h> +#include <unomap.hxx> +#include <unotbl.hxx> +#include <section.hxx> +#include <unocrsr.hxx> +#include <hints.hxx> +#include <swtblfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <shellres.hxx> +#include <docary.hxx> +#include <ndole.hxx> +#include <ndtxt.hxx> +#include <frame.hxx> +#include <vcl/svapp.hxx> +#include <fmtfsize.hxx> +#include <tblafmt.hxx> +#include <tabcol.hxx> +#include <cellatr.hxx> +#include <fmtpdsc.hxx> +#include <pagedesc.hxx> +#include <viewsh.hxx> +#include <rootfrm.hxx> +#include <tabfrm.hxx> +#include <redline.hxx> +#include <unoport.hxx> +#include <unocrsrhelper.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/TableColumnSeparator.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/XTextSection.hpp> +#include <com/sun/star/table/TableBorder.hpp> +#include <com/sun/star/table/TableBorder2.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/table/TableBorderDistances.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/chart/XChartDataChangeEventListener.hpp> +#include <com/sun/star/chart/ChartDataChangeEvent.hpp> +#include <com/sun/star/table/CellContentType.hpp> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unoparagraph.hxx> +#include <svl/zforlist.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <fmtornt.hxx> +#include <editeng/keepitem.hxx> +#include <fmtlsplt.hxx> +#include <swundo.hxx> +#include <osl/mutex.hxx> +#include <SwStyleNameMapper.hxx> +#include <frmatr.hxx> +#include <sortopt.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> +#include <editeng/frmdiritem.hxx> +#include <comphelper/interfacecontainer2.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <swtable.hxx> +#include <docsh.hxx> +#include <fesh.hxx> +#include <itabenum.hxx> +#include <poolfmt.hxx> +#include <frameformats.hxx> + +using namespace ::com::sun::star; +using ::editeng::SvxBorderLine; + +namespace +{ + template<typename Tcoretype, typename Tunotype> + struct FindUnoInstanceHint final : SfxHint + { + FindUnoInstanceHint(Tcoretype* pCore) : m_pCore(pCore), m_pResult(nullptr) {}; + const Tcoretype* const m_pCore; + mutable Tunotype* m_pResult; + }; + SwFrameFormat* lcl_EnsureCoreConnected(SwFrameFormat* pFormat, cppu::OWeakObject* pObject) + { + if(!pFormat) + throw uno::RuntimeException("Lost connection to core objects", pObject); + return pFormat; + } + SwTable* lcl_EnsureTableNotComplex(SwTable* pTable, cppu::OWeakObject* pObject) + { + if(pTable->IsTableComplex()) + throw uno::RuntimeException("Table too complex", pObject); + return pTable; + } + + chart::ChartDataChangeEvent createChartEvent(uno::Reference<uno::XInterface> const& xSource) + { + //TODO: find appropriate settings of the Event + chart::ChartDataChangeEvent event; + event.Source = xSource; + event.Type = chart::ChartDataChangeType_ALL; + event.StartColumn = 0; + event.EndColumn = 1; + event.StartRow = 0; + event.EndRow = 1; + return event; + } + + void lcl_SendChartEvent( + uno::Reference<uno::XInterface> const& xSource, + ::comphelper::OInterfaceContainerHelper2& rListeners) + { + rListeners.notifyEach( + &chart::XChartDataChangeEventListener::chartDataChanged, + createChartEvent(xSource)); + } + + void lcl_SendChartEvent( + uno::Reference<uno::XInterface> const& xSource, + ::cppu::OMultiTypeInterfaceContainerHelper const& rListeners) + { + auto pContainer(rListeners.getContainer(cppu::UnoType<chart::XChartDataChangeEventListener>::get())); + if (pContainer) + pContainer->notifyEach( + &chart::XChartDataChangeEventListener::chartDataChanged, + createChartEvent(xSource)); + } +} + +#define UNO_TABLE_COLUMN_SUM 10000 + + +static bool lcl_LineToSvxLine(const table::BorderLine& rLine, SvxBorderLine& rSvxLine) +{ + rSvxLine.SetColor(Color(rLine.Color)); + + rSvxLine.GuessLinesWidths( SvxBorderLineStyle::NONE, + convertMm100ToTwip( rLine.OuterLineWidth ), + convertMm100ToTwip( rLine.InnerLineWidth ), + convertMm100ToTwip( rLine.LineDistance ) ); + + return rLine.InnerLineWidth > 0 || rLine.OuterLineWidth > 0; +} + +/// @throws lang::IllegalArgumentException +/// @throws uno::RuntimeException +static void lcl_SetSpecialProperty(SwFrameFormat* pFormat, + const SfxItemPropertySimpleEntry* pEntry, + const uno::Any& aValue) +{ + // special treatment for "non-items" + switch(pEntry->nWID) + { + case FN_TABLE_HEADLINE_REPEAT: + case FN_TABLE_HEADLINE_COUNT: + { + SwTable* pTable = SwTable::FindTable( pFormat ); + UnoActionContext aAction(pFormat->GetDoc()); + if( pEntry->nWID == FN_TABLE_HEADLINE_REPEAT) + { + pFormat->GetDoc()->SetRowsToRepeat( *pTable, aValue.get<bool>() ? 1 : 0 ); + } + else + { + sal_Int32 nRepeat = 0; + aValue >>= nRepeat; + if( nRepeat >= 0 && nRepeat < SAL_MAX_UINT16 ) + pFormat->GetDoc()->SetRowsToRepeat( *pTable, static_cast<sal_uInt16>(nRepeat) ); + } + } + break; + + case FN_TABLE_IS_RELATIVE_WIDTH: + case FN_TABLE_WIDTH: + case FN_TABLE_RELATIVE_WIDTH: + { + SwFormatFrameSize aSz( pFormat->GetFrameSize() ); + if(FN_TABLE_WIDTH == pEntry->nWID) + { + sal_Int32 nWidth = 0; + aValue >>= nWidth; + aSz.SetWidthPercent(0); + aSz.SetWidth ( convertMm100ToTwip ( nWidth ) ); + } + else if(FN_TABLE_RELATIVE_WIDTH == pEntry->nWID) + { + sal_Int16 nSet = 0; + aValue >>= nSet; + if(nSet && nSet <=100) + aSz.SetWidthPercent( static_cast<sal_uInt8>(nSet) ); + } + else if(FN_TABLE_IS_RELATIVE_WIDTH == pEntry->nWID) + { + if(!aValue.get<bool>()) + aSz.SetWidthPercent(0); + else + { + lang::IllegalArgumentException aExcept; + aExcept.Message = "relative width cannot be switched on with this property"; + throw aExcept; + } + } + pFormat->GetDoc()->SetAttr(aSz, *pFormat); + } + break; + + case RES_PAGEDESC: + { + OUString sPageStyle; + aValue >>= sPageStyle; + const SwPageDesc* pDesc = nullptr; + if (!sPageStyle.isEmpty()) + { + SwStyleNameMapper::FillUIName(sPageStyle, sPageStyle, SwGetPoolIdFromName::PageDesc); + pDesc = SwPageDesc::GetByName(*pFormat->GetDoc(), sPageStyle); + } + SwFormatPageDesc aDesc( pDesc ); + pFormat->GetDoc()->SetAttr(aDesc, *pFormat); + } + break; + + default: + throw lang::IllegalArgumentException(); + } +} + +static uno::Any lcl_GetSpecialProperty(SwFrameFormat* pFormat, const SfxItemPropertySimpleEntry* pEntry ) +{ + switch(pEntry->nWID) + { + case FN_TABLE_HEADLINE_REPEAT: + case FN_TABLE_HEADLINE_COUNT: + { + SwTable* pTable = SwTable::FindTable( pFormat ); + const sal_uInt16 nRepeat = pTable->GetRowsToRepeat(); + if(pEntry->nWID == FN_TABLE_HEADLINE_REPEAT) + return uno::makeAny<bool>(nRepeat > 0); + return uno::makeAny<sal_Int32>(nRepeat); + } + + case FN_TABLE_WIDTH: + case FN_TABLE_IS_RELATIVE_WIDTH: + case FN_TABLE_RELATIVE_WIDTH: + { + uno::Any aRet; + const SwFormatFrameSize& rSz = pFormat->GetFrameSize(); + if(FN_TABLE_WIDTH == pEntry->nWID) + rSz.QueryValue(aRet, MID_FRMSIZE_WIDTH|CONVERT_TWIPS); + else if(FN_TABLE_RELATIVE_WIDTH == pEntry->nWID) + rSz.QueryValue(aRet, MID_FRMSIZE_REL_WIDTH); + else + aRet <<= (0 != rSz.GetWidthPercent()); + return aRet; + } + + case RES_PAGEDESC: + { + const SfxItemSet& rSet = pFormat->GetAttrSet(); + const SfxPoolItem* pItem; + if(SfxItemState::SET == rSet.GetItemState(RES_PAGEDESC, false, &pItem)) + { + const SwPageDesc* pDsc = static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc(); + if(pDsc) + return uno::makeAny<OUString>(SwStyleNameMapper::GetProgName(pDsc->GetName(), SwGetPoolIdFromName::PageDesc )); + } + return uno::makeAny(OUString()); + } + + case RES_ANCHOR: + return uno::makeAny(text::TextContentAnchorType_AT_PARAGRAPH); + + case FN_UNO_ANCHOR_TYPES: + { + uno::Sequence<text::TextContentAnchorType> aTypes{text::TextContentAnchorType_AT_PARAGRAPH}; + return uno::makeAny(aTypes); + } + + case FN_UNO_WRAP : + return uno::makeAny(text::WrapTextMode_NONE); + + case FN_PARAM_LINK_DISPLAY_NAME : + return uno::makeAny(pFormat->GetName()); + + case FN_UNO_REDLINE_NODE_START: + case FN_UNO_REDLINE_NODE_END: + { + SwTable* pTable = SwTable::FindTable( pFormat ); + SwNode* pTableNode = pTable->GetTableNode(); + if(FN_UNO_REDLINE_NODE_END == pEntry->nWID) + pTableNode = pTableNode->EndOfSectionNode(); + for(const SwRangeRedline* pRedline : pFormat->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable()) + { + const SwNode& rRedPointNode = pRedline->GetNode(); + const SwNode& rRedMarkNode = pRedline->GetNode(false); + if(&rRedPointNode == pTableNode || &rRedMarkNode == pTableNode) + { + const SwNode& rStartOfRedline = SwNodeIndex(rRedPointNode) <= SwNodeIndex(rRedMarkNode) ? + rRedPointNode : rRedMarkNode; + bool bIsStart = &rStartOfRedline == pTableNode; + return uno::makeAny(SwXRedlinePortion::CreateRedlineProperties(*pRedline, bIsStart)); + } + } + } + } + return uno::Any(); +} + +/** get position of a cell with a given name + * + * If everything was OK, the indices for column and row are changed (both >= 0). + * In case of errors, at least one of them is < 0. + * + * Also since the implementations of tables does not really have columns using + * this function is appropriate only for tables that are not complex (i.e. + * where IsTableComplex() returns false). + * + * @param rCellName e.g. A1..Z1, a1..z1, AA1..AZ1, Aa1..Az1, BA1..BZ1, Ba1..Bz1, ... + * @param [IN,OUT] o_rColumn (0-based) + * @param [IN,OUT] o_rRow (0-based) + */ +//TODO: potential for throwing proper exceptions instead of having every caller to check for errors +void SwXTextTable::GetCellPosition(const OUString& rCellName, sal_Int32& o_rColumn, sal_Int32& o_rRow) +{ + o_rColumn = o_rRow = -1; // default return values indicating failure + const sal_Int32 nLen = rCellName.getLength(); + if(!nLen) + { + SAL_WARN("sw.uno", "failed to get column or row index"); + return; + } + sal_Int32 nRowPos = 0; + while (nRowPos<nLen) + { + if (rCellName[nRowPos]>='0' && rCellName[nRowPos]<='9') + { + break; + } + ++nRowPos; + } + if (nRowPos>0 && nRowPos<nLen) + { + sal_Int32 nColIdx = 0; + for (sal_Int32 i = 0; i < nRowPos; ++i) + { + nColIdx *= 52; + if (i < nRowPos - 1) + ++nColIdx; + const sal_Unicode cChar = rCellName[i]; + if ('A' <= cChar && cChar <= 'Z') + nColIdx += cChar - 'A'; + else if ('a' <= cChar && cChar <= 'z') + nColIdx += 26 + cChar - 'a'; + else + { + nColIdx = -1; // sth failed + break; + } + } + + o_rColumn = nColIdx; + o_rRow = rCellName.copy(nRowPos).toInt32() - 1; // - 1 because indices ought to be 0 based + } +} + +/** compare position of two cells (check rows first) + * + * @note this function probably also make sense only + * for cell names of non-complex tables + * + * @param rCellName1 e.g. "A1" (non-empty string with valid cell name) + * @param rCellName2 e.g. "A1" (non-empty string with valid cell name) + * @return -1 if cell_1 < cell_2; 0 if both cells are equal; +1 if cell_1 > cell_2 + */ +int sw_CompareCellsByRowFirst( const OUString &rCellName1, const OUString &rCellName2 ) +{ + sal_Int32 nCol1 = -1, nRow1 = -1, nCol2 = -1, nRow2 = -1; + SwXTextTable::GetCellPosition( rCellName1, nCol1, nRow1 ); + SwXTextTable::GetCellPosition( rCellName2, nCol2, nRow2 ); + + if (nRow1 < nRow2 || (nRow1 == nRow2 && nCol1 < nCol2)) + return -1; + else if (nCol1 == nCol2 && nRow1 == nRow2) + return 0; + else + return +1; +} + +/** compare position of two cells (check columns first) + * + * @note this function probably also make sense only + * for cell names of non-complex tables + * + * @param rCellName1 e.g. "A1" (non-empty string with valid cell name) + * @param rCellName2 e.g. "A1" (non-empty string with valid cell name) + * @return -1 if cell_1 < cell_2; 0 if both cells are equal; +1 if cell_1 > cell_2 + */ +int sw_CompareCellsByColFirst( const OUString &rCellName1, const OUString &rCellName2 ) +{ + sal_Int32 nCol1 = -1, nRow1 = -1, nCol2 = -1, nRow2 = -1; + SwXTextTable::GetCellPosition( rCellName1, nCol1, nRow1 ); + SwXTextTable::GetCellPosition( rCellName2, nCol2, nRow2 ); + + if (nCol1 < nCol2 || (nCol1 == nCol2 && nRow1 < nRow2)) + return -1; + else if (nRow1 == nRow2 && nCol1 == nCol2) + return 0; + else + return +1; +} + +/** compare position of two cell ranges + * + * @note this function probably also make sense only + * for cell names of non-complex tables + * + * @param rRange1StartCell e.g. "A1" (non-empty string with valid cell name) + * @param rRange1EndCell e.g. "A1" (non-empty string with valid cell name) + * @param rRange2StartCell e.g. "A1" (non-empty string with valid cell name) + * @param rRange2EndCell e.g. "A1" (non-empty string with valid cell name) + * @param bCmpColsFirst if <true> position in columns will be compared first before rows + * + * @return -1 if cell_range_1 < cell_range_2; 0 if both cell ranges are equal; +1 if cell_range_1 > cell_range_2 + */ +int sw_CompareCellRanges( + const OUString &rRange1StartCell, const OUString &rRange1EndCell, + const OUString &rRange2StartCell, const OUString &rRange2EndCell, + bool bCmpColsFirst ) +{ + int (*pCompareCells)( const OUString &, const OUString & ) = + bCmpColsFirst ? &sw_CompareCellsByColFirst : &sw_CompareCellsByRowFirst; + + int nCmpResStartCells = pCompareCells( rRange1StartCell, rRange2StartCell ); + if ((-1 == nCmpResStartCells ) || + ( 0 == nCmpResStartCells && + -1 == pCompareCells( rRange1EndCell, rRange2EndCell ) )) + return -1; + else if (0 == nCmpResStartCells && + 0 == pCompareCells( rRange1EndCell, rRange2EndCell )) + return 0; + else + return +1; +} + +/** get cell name at a specified coordinate + * + * @param nColumn column index (0-based) + * @param nRow row index (0-based) + * @return the cell name + */ +OUString sw_GetCellName( sal_Int32 nColumn, sal_Int32 nRow ) +{ + if (nColumn < 0 || nRow < 0) + return OUString(); + OUString sCellName; + sw_GetTableBoxColStr( static_cast< sal_uInt16 >(nColumn), sCellName ); + return sCellName + OUString::number( nRow + 1 ); +} + +/** Find the top left or bottom right corner box in given table. + Consider nested lines when finding the box. + + @param rTableLines the table + @param i_bTopLeft if true, find top left box, otherwise find bottom + right box + */ +static const SwTableBox* lcl_FindCornerTableBox(const SwTableLines& rTableLines, const bool i_bTopLeft) +{ + const SwTableLines* pLines(&rTableLines); + while(true) + { + assert(!pLines->empty()); + if(pLines->empty()) + return nullptr; + const SwTableLine* pLine(i_bTopLeft ? pLines->front() : pLines->back()); + assert(pLine); + const SwTableBoxes& rBoxes(pLine->GetTabBoxes()); + assert(rBoxes.size() != 0); + const SwTableBox* pBox = i_bTopLeft ? rBoxes.front() : rBoxes.back(); + assert(pBox); + if (pBox->GetSttNd()) + return pBox; + pLines = &pBox->GetTabLines(); + } +} + +/** cleanup order in a range + * + * Sorts the input to a uniform format. I.e. for the four possible representation + * A1:C5, C5:A1, A5:C1, C1:A5 + * the result will be always A1:C5. + * + * @param [IN,OUT] rCell1 cell name (will be modified to upper-left corner), e.g. "A1" (non-empty string with valid cell name) + * @param [IN,OUT] rCell2 cell name (will be modified to lower-right corner), e.g. "A1" (non-empty string with valid cell name) + */ +void sw_NormalizeRange(OUString &rCell1, OUString &rCell2) +{ + sal_Int32 nCol1 = -1, nRow1 = -1, nCol2 = -1, nRow2 = -1; + SwXTextTable::GetCellPosition( rCell1, nCol1, nRow1 ); + SwXTextTable::GetCellPosition( rCell2, nCol2, nRow2 ); + if (nCol2 < nCol1 || nRow2 < nRow1) + { + rCell1 = sw_GetCellName( std::min(nCol1, nCol2), std::min(nRow1, nRow2) ); + rCell2 = sw_GetCellName( std::max(nCol1, nCol2), std::max(nRow1, nRow2) ); + } +} + +void SwRangeDescriptor::Normalize() +{ + if (nTop > nBottom) + std::swap(nBottom, nTop); + if (nLeft > nRight) + std::swap(nLeft, nRight); +} + +static SwXCell* lcl_CreateXCell(SwFrameFormat* pFormat, sal_Int32 nColumn, sal_Int32 nRow) +{ + const OUString sCellName = sw_GetCellName(nColumn, nRow); + SwTable* pTable = SwTable::FindTable(pFormat); + SwTableBox* pBox = const_cast<SwTableBox*>(pTable->GetTableBox(sCellName)); + if(!pBox) + return nullptr; + return SwXCell::CreateXCell(pFormat, pBox, pTable); +} + +static void lcl_InspectLines(SwTableLines& rLines, std::vector<OUString>& rAllNames) +{ + for(auto pLine : rLines) + { + for(auto pBox : pLine->GetTabBoxes()) + { + if(!pBox->GetName().isEmpty() && pBox->getRowSpan() > 0) + rAllNames.push_back(pBox->GetName()); + SwTableLines& rBoxLines = pBox->GetTabLines(); + if(!rBoxLines.empty()) + lcl_InspectLines(rBoxLines, rAllNames); + } + } +} + +static bool lcl_FormatTable(SwFrameFormat const * pTableFormat) +{ + bool bHasFrames = false; + SwIterator<SwFrame,SwFormat> aIter( *pTableFormat ); + for(SwFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + vcl::RenderContext* pRenderContext = pFrame->getRootFrame()->GetCurrShell()->GetOut(); + // mba: no TYPEINFO for SwTabFrame + if(!pFrame->IsTabFrame()) + continue; + DisableCallbackAction a(*pFrame->getRootFrame()); + SwTabFrame* pTabFrame = static_cast<SwTabFrame*>(pFrame); + if(pTabFrame->isFrameAreaDefinitionValid()) + pTabFrame->InvalidatePos(); + pTabFrame->SetONECalcLowers(); + pTabFrame->Calc(pRenderContext); + bHasFrames = true; + } + return bHasFrames; +} + +static void lcl_CursorSelect(SwPaM& rCursor, bool bExpand) +{ + if(bExpand) + { + if(!rCursor.HasMark()) + rCursor.SetMark(); + } + else if(rCursor.HasMark()) + rCursor.DeleteMark(); +} + +static void lcl_GetTableSeparators(uno::Any& rRet, SwTable const * pTable, SwTableBox const * pBox, bool bRow) +{ + SwTabCols aCols; + aCols.SetLeftMin ( 0 ); + aCols.SetLeft ( 0 ); + aCols.SetRight ( UNO_TABLE_COLUMN_SUM ); + aCols.SetRightMax( UNO_TABLE_COLUMN_SUM ); + + pTable->GetTabCols( aCols, pBox, false, bRow ); + + const size_t nSepCount = aCols.Count(); + uno::Sequence< text::TableColumnSeparator> aColSeq(nSepCount); + text::TableColumnSeparator* pArray = aColSeq.getArray(); + bool bError = false; + for(size_t i = 0; i < nSepCount; ++i) + { + pArray[i].Position = static_cast< sal_Int16 >(aCols[i]); + pArray[i].IsVisible = !aCols.IsHidden(i); + if(!bRow && !pArray[i].IsVisible) + { + bError = true; + break; + } + } + if(!bError) + rRet <<= aColSeq; + +} + +static void lcl_SetTableSeparators(const uno::Any& rVal, SwTable* pTable, SwTableBox const * pBox, bool bRow, SwDoc* pDoc) +{ + SwTabCols aOldCols; + + aOldCols.SetLeftMin ( 0 ); + aOldCols.SetLeft ( 0 ); + aOldCols.SetRight ( UNO_TABLE_COLUMN_SUM ); + aOldCols.SetRightMax( UNO_TABLE_COLUMN_SUM ); + + pTable->GetTabCols( aOldCols, pBox, false, bRow ); + const size_t nOldCount = aOldCols.Count(); + // there is no use in setting tab cols if there is only one column + if( !nOldCount ) + return; + + auto pSepSeq = + o3tl::tryAccess<uno::Sequence<text::TableColumnSeparator>>(rVal); + if(!pSepSeq || static_cast<size_t>(pSepSeq->getLength()) != nOldCount) + return; + SwTabCols aCols(aOldCols); + const text::TableColumnSeparator* pArray = pSepSeq->getConstArray(); + long nLastValue = 0; + //sal_Int32 nTableWidth = aCols.GetRight() - aCols.GetLeft(); + for(size_t i = 0; i < nOldCount; ++i) + { + aCols[i] = pArray[i].Position; + if(bool(pArray[i].IsVisible) == aCols.IsHidden(i) || + (!bRow && aCols.IsHidden(i)) || + aCols[i] < nLastValue || + UNO_TABLE_COLUMN_SUM < aCols[i] ) + return; // probably this should assert() + nLastValue = aCols[i]; + } + pDoc->SetTabCols(*pTable, aCols, aOldCols, pBox, bRow ); +} + +/* non UNO function call to set string in SwXCell */ +void sw_setString( SwXCell &rCell, const OUString &rText, + bool bKeepNumberFormat = false ) +{ + if(rCell.IsValid()) + { + SwFrameFormat* pBoxFormat = rCell.m_pBox->ClaimFrameFormat(); + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + if (!bKeepNumberFormat) + pBoxFormat->SetFormatAttr( SwTableBoxNumFormat(/*default Text*/) ); + pBoxFormat->UnlockModify(); + } + rCell.SwXText::setString(rText); +} + + +/* non UNO function call to set value in SwXCell */ +void sw_setValue( SwXCell &rCell, double nVal ) +{ + if(!rCell.IsValid()) + return; + // first this text (maybe) needs to be deleted + sal_uLong nNdPos = rCell.m_pBox->IsValidNumTextNd(); + if(ULONG_MAX != nNdPos) + sw_setString( rCell, OUString(), true ); // true == keep number format + SwDoc* pDoc = rCell.GetDoc(); + UnoActionContext aAction(pDoc); + SwFrameFormat* pBoxFormat = rCell.m_pBox->ClaimFrameFormat(); + SfxItemSet aSet(pDoc->GetAttrPool(), svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + const SfxPoolItem* pItem; + + //!! do we need to set a new number format? Yes, if + // - there is no current number format + // - the current number format is not a number format according to the number formatter, but rather a text format + if(SfxItemState::SET != pBoxFormat->GetAttrSet().GetItemState(RES_BOXATR_FORMAT, true, &pItem) + || pDoc->GetNumberFormatter()->IsTextFormat(static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue())) + { + aSet.Put(SwTableBoxNumFormat(0)); + } + + SwTableBoxValue aVal(nVal); + aSet.Put(aVal); + pDoc->SetTableBoxFormulaAttrs( *rCell.m_pBox, aSet ); + // update table + SwTableFormulaUpdate aTableUpdate( SwTable::FindTable( rCell.GetFrameFormat() )); + pDoc->getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); +} + + +SwXCell::SwXCell(SwFrameFormat* pTableFormat, SwTableBox* pBx, size_t const nPos) : + SwXText(pTableFormat->GetDoc(), CursorType::TableText), + m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TABLE_CELL)), + m_pBox(pBx), + m_pStartNode(nullptr), + m_pTableFormat(pTableFormat), + m_nFndPos(nPos) +{ + StartListening(pTableFormat->GetNotifier()); +} + +SwXCell::SwXCell(SwFrameFormat* pTableFormat, const SwStartNode& rStartNode) : + SwXText(pTableFormat->GetDoc(), CursorType::TableText), + m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TABLE_CELL)), + m_pBox(nullptr), + m_pStartNode(&rStartNode), + m_pTableFormat(pTableFormat), + m_nFndPos(NOTFOUND) +{ + StartListening(pTableFormat->GetNotifier()); +} + +SwXCell::~SwXCell() +{ + SolarMutexGuard aGuard; + EndListeningAll(); +} + +namespace +{ + class theSwXCellUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXCellUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXCell::getUnoTunnelId() +{ + return theSwXCellUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXCell::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXCell>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + else + return SwXText::getSomething(rId); +} + +uno::Sequence< uno::Type > SAL_CALL SwXCell::getTypes( ) +{ + return comphelper::concatSequences( + SwXCellBaseClass::getTypes(), + SwXText::getTypes() + ); +} + +uno::Sequence< sal_Int8 > SAL_CALL SwXCell::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +void SAL_CALL SwXCell::acquire( ) throw() +{ + SwXCellBaseClass::acquire(); +} + +void SAL_CALL SwXCell::release( ) throw() +{ + SolarMutexGuard aGuard; + + SwXCellBaseClass::release(); +} + +uno::Any SAL_CALL SwXCell::queryInterface( const uno::Type& aType ) +{ + uno::Any aRet = SwXCellBaseClass::queryInterface(aType); + if(aRet.getValueType() == cppu::UnoType<void>::get()) + aRet = SwXText::queryInterface(aType); + return aRet; +} + +const SwStartNode *SwXCell::GetStartNode() const +{ + const SwStartNode* pSttNd = nullptr; + + if( m_pStartNode || IsValid() ) + pSttNd = m_pStartNode ? m_pStartNode : m_pBox->GetSttNd(); + + return pSttNd; +} + +uno::Reference< text::XTextCursor > +SwXCell::CreateCursor() +{ + return createTextCursor(); +} + +bool SwXCell::IsValid() const +{ + // FIXME: this is now a const method, to make SwXText::IsValid invisible + // but the const_cast here are still ridiculous. TODO: find a better way. + SwFrameFormat* pTableFormat = m_pBox ? GetFrameFormat() : nullptr; + if(!pTableFormat) + { + const_cast<SwXCell*>(this)->m_pBox = nullptr; + } + else + { + SwTable* pTable = SwTable::FindTable( pTableFormat ); + SwTableBox const*const pFoundBox = + const_cast<SwXCell*>(this)->FindBox(pTable, m_pBox); + if (!pFoundBox) + { + const_cast<SwXCell*>(this)->m_pBox = nullptr; + } + } + return nullptr != m_pBox; +} + +OUString SwXCell::getFormula() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + return OUString(); + SwTableBoxFormula aFormula( m_pBox->GetFrameFormat()->GetTableBoxFormula() ); + SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + aFormula.PtrToBoxNm( pTable ); + return aFormula.GetFormula(); +} + +///@see sw_setValue (TODO: seems to be copy and paste programming here) +void SwXCell::setFormula(const OUString& rFormula) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + return; + // first this text (maybe) needs to be deleted + sal_uInt32 nNdPos = m_pBox->IsValidNumTextNd(); + if(USHRT_MAX == nNdPos) + sw_setString( *this, OUString(), true ); + OUString sFormula(comphelper::string::stripStart(rFormula, ' ')); + if( !sFormula.isEmpty() && '=' == sFormula[0] ) + sFormula = sFormula.copy( 1 ); + SwTableBoxFormula aFormula( sFormula ); + SwDoc* pMyDoc = GetDoc(); + UnoActionContext aAction(pMyDoc); + SfxItemSet aSet(pMyDoc->GetAttrPool(), svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_FORMULA>{}); + const SfxPoolItem* pItem; + SwFrameFormat* pBoxFormat = m_pBox->GetFrameFormat(); + if(SfxItemState::SET != pBoxFormat->GetAttrSet().GetItemState(RES_BOXATR_FORMAT, true, &pItem) + || pMyDoc->GetNumberFormatter()->IsTextFormat(static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue())) + { + aSet.Put(SwTableBoxNumFormat(0)); + } + aSet.Put(aFormula); + GetDoc()->SetTableBoxFormulaAttrs( *m_pBox, aSet ); + // update table + SwTableFormulaUpdate aTableUpdate( SwTable::FindTable( GetFrameFormat() )); + pMyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate ); +} + +double SwXCell::getValue() +{ + SolarMutexGuard aGuard; + // #i112652# a table cell may contain NaN as a value, do not filter that + double fRet; + if(IsValid() && !getString().isEmpty()) + fRet = m_pBox->GetFrameFormat()->GetTableBoxValue().GetValue(); + else + ::rtl::math::setNan( &fRet ); + return fRet; +} + +void SwXCell::setValue(double rValue) +{ + SolarMutexGuard aGuard; + sw_setValue( *this, rValue ); +} + +table::CellContentType SwXCell::getType() +{ + SolarMutexGuard aGuard; + + table::CellContentType nRes = table::CellContentType_EMPTY; + sal_uInt32 nNdPos = m_pBox->IsFormulaOrValueBox(); + switch (nNdPos) + { + case 0 : nRes = table::CellContentType_TEXT; break; + case USHRT_MAX : nRes = table::CellContentType_EMPTY; break; + case RES_BOXATR_VALUE : nRes = table::CellContentType_VALUE; break; + case RES_BOXATR_FORMULA : nRes = table::CellContentType_FORMULA; break; + default : + OSL_FAIL( "unexpected case" ); + } + return nRes; +} + +void SwXCell::setString(const OUString& aString) +{ + SolarMutexGuard aGuard; + sw_setString( *this, aString ); +} + +sal_Int32 SwXCell::getError() +{ + SolarMutexGuard aGuard; + OUString sContent = getString(); + return sal_Int32(sContent == SwViewShell::GetShellRes()->aCalc_Error); +} + +uno::Reference<text::XTextCursor> SwXCell::createTextCursor() +{ + SolarMutexGuard aGuard; + if(!m_pStartNode && !IsValid()) + throw uno::RuntimeException(); + const SwStartNode* pSttNd = m_pStartNode ? m_pStartNode : m_pBox->GetSttNd(); + SwPosition aPos(*pSttNd); + SwXTextCursor* const pXCursor = + new SwXTextCursor(*GetDoc(), this, CursorType::TableText, aPos); + auto& rUnoCursor(pXCursor->GetCursor()); + rUnoCursor.Move(fnMoveForward, GoInNode); + return static_cast<text::XWordCursor*>(pXCursor); +} + +uno::Reference<text::XTextCursor> SwXCell::createTextCursorByRange(const uno::Reference< text::XTextRange > & xTextPosition) +{ + SolarMutexGuard aGuard; + SwUnoInternalPaM aPam(*GetDoc()); + if((!m_pStartNode && !IsValid()) || !::sw::XTextRangeToSwPaM(aPam, xTextPosition)) + throw uno::RuntimeException(); + const SwStartNode* pSttNd = m_pStartNode ? m_pStartNode : m_pBox->GetSttNd(); + // skip sections + SwStartNode* p1 = aPam.GetNode().StartOfSectionNode(); + while(p1->IsSectionNode()) + p1 = p1->StartOfSectionNode(); + if( p1 != pSttNd ) + return nullptr; + return static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), this, CursorType::TableText, + *aPam.GetPoint(), aPam.GetMark())); +} + +uno::Reference< beans::XPropertySetInfo > SwXCell::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > xRef = m_pPropSet->getPropertySetInfo(); + return xRef; +} + +void SwXCell::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + return; + // Hack to support hidden property to transfer textDirection + if(rPropertyName == "FRMDirection") + { + SvxFrameDirectionItem aItem(SvxFrameDirection::Environment, RES_FRAMEDIR); + aItem.PutValue(aValue, 0); + m_pBox->GetFrameFormat()->SetFormatAttr(aItem); + } + else if(rPropertyName == "TableRedlineParams") + { + // Get the table row properties + uno::Sequence<beans::PropertyValue> tableCellProperties = aValue.get< uno::Sequence< beans::PropertyValue > >(); + comphelper::SequenceAsHashMap aPropMap(tableCellProperties); + OUString sRedlineType; + if(!(aPropMap.getValue("RedlineType") >>= sRedlineType)) + throw beans::UnknownPropertyException("No redline type property: ", static_cast<cppu::OWeakObject*>(this)); + + // Create a 'Table Cell Redline' object + SwUnoCursorHelper::makeTableCellRedline(*m_pBox, sRedlineType, tableCellProperties); + + + } + else + { + auto pEntry(m_pPropSet->getPropertyMap().getByName(rPropertyName)); + if ( !pEntry ) + { + // not a table property: ignore it, if it is a paragraph/character property + const SfxItemPropertySet& rParaPropSet = *aSwMapProvider.GetPropertySet(PROPERTY_MAP_PARAGRAPH); + pEntry = rParaPropSet.getPropertyMap().getByName(rPropertyName); + + if ( pEntry ) + return; + } + + if(!pEntry) + throw beans::UnknownPropertyException(rPropertyName, static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nWID != FN_UNO_CELL_ROW_SPAN) + { + SwFrameFormat* pBoxFormat = m_pBox->ClaimFrameFormat(); + SwAttrSet aSet(pBoxFormat->GetAttrSet()); + m_pPropSet->setPropertyValue(rPropertyName, aValue, aSet); + pBoxFormat->GetDoc()->SetAttr(aSet, *pBoxFormat); + } + else if(aValue.isExtractableTo(cppu::UnoType<sal_Int32>::get())) + m_pBox->setRowSpan(aValue.get<sal_Int32>()); + } +} + +uno::Any SwXCell::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + if(!IsValid()) + return uno::Any(); + auto pEntry(m_pPropSet->getPropertyMap().getByName(rPropertyName)); + if(!pEntry) + throw beans::UnknownPropertyException(rPropertyName, static_cast<cppu::OWeakObject*>(this)); + switch(pEntry->nWID) + { + case FN_UNO_CELL_ROW_SPAN: + return uno::makeAny(m_pBox->getRowSpan()); + break; + case FN_UNO_TEXT_SECTION: + { + SwFrameFormat* pTableFormat = GetFrameFormat(); + SwTable* pTable = SwTable::FindTable(pTableFormat); + SwTableNode* pTableNode = pTable->GetTableNode(); + SwSectionNode* pSectionNode = pTableNode->FindSectionNode(); + if(!pSectionNode) + return uno::Any(); + SwSection& rSect = pSectionNode->GetSection(); + return uno::makeAny(SwXTextSections::GetObject(*rSect.GetFormat())); + } + break; + case FN_UNO_CELL_NAME: + return uno::makeAny(m_pBox->GetName()); + break; + case FN_UNO_REDLINE_NODE_START: + case FN_UNO_REDLINE_NODE_END: + { + //redline can only be returned if it's a living object + return SwXText::getPropertyValue(rPropertyName); + } + break; + case FN_UNO_PARENT_TEXT: + { + if (!m_xParentText.is()) + { + const SwStartNode* pSttNd = m_pBox->GetSttNd(); + if (!pSttNd) + return uno::Any(); + + const SwTableNode* pTableNode = pSttNd->FindTableNode(); + if (!pTableNode) + return uno::Any(); + + SwPosition aPos(*pTableNode); + SwDoc* pDoc = aPos.GetDoc(); + if (!pDoc) + return uno::Any(); + + m_xParentText = sw::CreateParentXText(*pDoc, aPos); + } + + return uno::makeAny(m_xParentText); + } + break; + default: + { + const SwAttrSet& rSet = m_pBox->GetFrameFormat()->GetAttrSet(); + uno::Any aResult; + m_pPropSet->getPropertyValue(rPropertyName, rSet, aResult); + return aResult; + } + } +} + +void SwXCell::addPropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXCell::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXCell::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXCell::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +uno::Reference<container::XEnumeration> SwXCell::createEnumeration() +{ + SolarMutexGuard aGuard; + if(!IsValid()) + return uno::Reference<container::XEnumeration>(); + const SwStartNode* pSttNd = m_pBox->GetSttNd(); + SwPosition aPos(*pSttNd); + auto pUnoCursor(GetDoc()->CreateUnoCursor(aPos)); + pUnoCursor->Move(fnMoveForward, GoInNode); + // remember table and start node for later travelling + // (used in export of tables in tables) + SwTable const*const pTable(&pSttNd->FindTableNode()->GetTable()); + return SwXParagraphEnumeration::Create(this, pUnoCursor, CursorType::TableText, pSttNd, pTable); +} + +uno::Type SAL_CALL SwXCell::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SwXCell::hasElements() +{ + return true; +} + +void SwXCell::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pTableFormat = nullptr; + } + else if(auto pFindHint = dynamic_cast<const FindUnoInstanceHint<SwTableBox, SwXCell>*>(&rHint)) + { + if(!pFindHint->m_pResult && pFindHint->m_pCore == GetTableBox()) + pFindHint->m_pResult = this; + } +} + +SwXCell* SwXCell::CreateXCell(SwFrameFormat* pTableFormat, SwTableBox* pBox, SwTable *pTable ) +{ + if(!pTableFormat || !pBox) + return nullptr; + if(!pTable) + pTable = SwTable::FindTable(pTableFormat); + SwTableSortBoxes::const_iterator it = pTable->GetTabSortBoxes().find(pBox); + if(it == pTable->GetTabSortBoxes().end()) + return nullptr; + size_t const nPos = it - pTable->GetTabSortBoxes().begin(); + FindUnoInstanceHint<SwTableBox, SwXCell> aHint{pBox}; + pTableFormat->GetNotifier().Broadcast(aHint); + return aHint.m_pResult ? aHint.m_pResult : new SwXCell(pTableFormat, pBox, nPos); +} + +/** search if a box exists in a table + * + * @param pTable the table to search in + * @param pBox2 box model to find + * @return the box if existent in pTable, 0 (!!!) if not found + */ +SwTableBox* SwXCell::FindBox(SwTable* pTable, SwTableBox* pBox2) +{ + // check if nFndPos happens to point to the right table box + if( m_nFndPos < pTable->GetTabSortBoxes().size() && + pBox2 == pTable->GetTabSortBoxes()[ m_nFndPos ] ) + return pBox2; + + // if not, seek the entry (and return, if successful) + SwTableSortBoxes::const_iterator it = pTable->GetTabSortBoxes().find( pBox2 ); + if( it != pTable->GetTabSortBoxes().end() ) + { + m_nFndPos = it - pTable->GetTabSortBoxes().begin(); + return pBox2; + } + + // box not found: reset nFndPos pointer + m_nFndPos = NOTFOUND; + return nullptr; +} + +double SwXCell::GetForcedNumericalValue() const +{ + if(table::CellContentType_TEXT != const_cast<SwXCell*>(this)->getType()) + return getValue(); + // now we'll try to get a useful numerical value + // from the text in the cell... + sal_uInt32 nFIndex; + SvNumberFormatter* pNumFormatter(const_cast<SvNumberFormatter*>(GetDoc()->GetNumberFormatter())); + // look for SwTableBoxNumFormat value in parents as well + const SfxPoolItem* pItem; + auto pBoxFormat(GetTableBox()->GetFrameFormat()); + SfxItemState eState = pBoxFormat->GetAttrSet().GetItemState(RES_BOXATR_FORMAT, true, &pItem); + + if (eState == SfxItemState::SET) + { + // please note that the language of the numberformat + // is implicitly coded into the below value as well + nFIndex = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + + // since the current value indicates a text format but the call + // to 'IsNumberFormat' below won't work for text formats + // we need to get rid of the part that indicates the text format. + // According to ER this can be done like this: + nFIndex -= (nFIndex % SV_COUNTRY_LANGUAGE_OFFSET); + } + else + { + // system language is probably not the best possible choice + // but since we have to guess anyway (because the language of at + // the text is NOT the one used for the number format!) + // it is at least conform to what is used in + // SwTableShell::Execute when + // SID_ATTR_NUMBERFORMAT_VALUE is set... + LanguageType eLang = LANGUAGE_SYSTEM; + nFIndex = pNumFormatter->GetStandardIndex( eLang ); + } + double fTmp; + if (!const_cast<SwDoc*>(GetDoc())->IsNumberFormat(const_cast<SwXCell*>(this)->getString(), nFIndex, fTmp)) + ::rtl::math::setNan(&fTmp); + return fTmp; +} + +uno::Any SwXCell::GetAny() const +{ + if(!m_pBox) + throw uno::RuntimeException(); + // check if table box value item is set + auto pBoxFormat(m_pBox->GetFrameFormat()); + const bool bIsNum = pBoxFormat->GetItemState(RES_BOXATR_VALUE, false) == SfxItemState::SET; + return bIsNum ? uno::makeAny(getValue()) : uno::makeAny(const_cast<SwXCell*>(this)->getString()); +} + +OUString SwXCell::getImplementationName() + { return "SwXCell"; } + +sal_Bool SwXCell::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +uno::Sequence< OUString > SwXCell::getSupportedServiceNames() + { return {"com.sun.star.text.CellProperties"}; } + +OUString SwXTextTableRow::getImplementationName() + { return "SwXTextTableRow"; } + +sal_Bool SwXTextTableRow::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +uno::Sequence< OUString > SwXTextTableRow::getSupportedServiceNames() + { return {"com.sun.star.text.TextTableRow"}; } + + +SwXTextTableRow::SwXTextTableRow(SwFrameFormat* pFormat, SwTableLine* pLn) : + m_pFormat(pFormat), + pLine(pLn), + m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_TABLE_ROW)) +{ + StartListening(m_pFormat->GetNotifier()); +} + +SwXTextTableRow::~SwXTextTableRow() +{ + SolarMutexGuard aGuard; + EndListeningAll(); +} + +uno::Reference< beans::XPropertySetInfo > SwXTextTableRow::getPropertySetInfo() +{ + static uno::Reference<beans::XPropertySetInfo> xRef = m_pPropSet->getPropertySetInfo(); + return xRef; +} + +void SwXTextTableRow::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = SwTable::FindTable( pFormat ); + SwTableLine* pLn = SwXTextTableRow::FindLine(pTable, pLine); + if(pLn) + { + // Check for a specific property + if ( rPropertyName == "TableRedlineParams" ) + { + // Get the table row properties + uno::Sequence< beans::PropertyValue > tableRowProperties = aValue.get< uno::Sequence< beans::PropertyValue > >(); + comphelper::SequenceAsHashMap aPropMap( tableRowProperties ); + OUString sRedlineType; + if( !(aPropMap.getValue("RedlineType") >>= sRedlineType) ) + { + throw beans::UnknownPropertyException("No redline type property: ", static_cast < cppu::OWeakObject * > ( this ) ); + } + + // Create a 'Table Row Redline' object + SwUnoCursorHelper::makeTableRowRedline( *pLn, sRedlineType, tableRowProperties); + + } + else + { + const SfxItemPropertySimpleEntry* pEntry = + m_pPropSet->getPropertyMap().getByName(rPropertyName); + SwDoc* pDoc = pFormat->GetDoc(); + if (!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + switch(pEntry->nWID) + { + case FN_UNO_ROW_HEIGHT: + case FN_UNO_ROW_AUTO_HEIGHT: + { + SwFormatFrameSize aFrameSize(pLn->GetFrameFormat()->GetFrameSize()); + if(FN_UNO_ROW_AUTO_HEIGHT== pEntry->nWID) + { + bool bSet = *o3tl::doAccess<bool>(aValue); + aFrameSize.SetHeightSizeType(bSet ? SwFrameSize::Variable : SwFrameSize::Fixed); + } + else + { + sal_Int32 nHeight = 0; + aValue >>= nHeight; + Size aSz(aFrameSize.GetSize()); + aSz.setHeight( convertMm100ToTwip(nHeight) ); + aFrameSize.SetSize(aSz); + } + pDoc->SetAttr(aFrameSize, *pLn->ClaimFrameFormat()); + } + break; + + case FN_UNO_TABLE_COLUMN_SEPARATORS: + { + UnoActionContext aContext(pDoc); + SwTable* pTable2 = SwTable::FindTable( pFormat ); + lcl_SetTableSeparators(aValue, pTable2, pLine->GetTabBoxes()[0], true, pDoc); + } + break; + + default: + { + SwFrameFormat* pLnFormat = pLn->ClaimFrameFormat(); + SwAttrSet aSet(pLnFormat->GetAttrSet()); + m_pPropSet->setPropertyValue(*pEntry, aValue, aSet); + pDoc->SetAttr(aSet, *pLnFormat); + } + } + } + } +} + +uno::Any SwXTextTableRow::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = SwTable::FindTable( pFormat ); + SwTableLine* pLn = SwXTextTableRow::FindLine(pTable, pLine); + if(pLn) + { + const SfxItemPropertySimpleEntry* pEntry = + m_pPropSet->getPropertyMap().getByName(rPropertyName); + if (!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + switch(pEntry->nWID) + { + case FN_UNO_ROW_HEIGHT: + case FN_UNO_ROW_AUTO_HEIGHT: + { + const SwFormatFrameSize& rSize = pLn->GetFrameFormat()->GetFrameSize(); + if(FN_UNO_ROW_AUTO_HEIGHT== pEntry->nWID) + { + aRet <<= SwFrameSize::Variable == rSize.GetHeightSizeType(); + } + else + aRet <<= static_cast<sal_Int32>(convertTwipToMm100(rSize.GetSize().Height())); + } + break; + + case FN_UNO_TABLE_COLUMN_SEPARATORS: + { + lcl_GetTableSeparators(aRet, pTable, pLine->GetTabBoxes()[0], true); + } + break; + + default: + { + const SwAttrSet& rSet = pLn->GetFrameFormat()->GetAttrSet(); + m_pPropSet->getPropertyValue(*pEntry, rSet, aRet); + } + } + } + return aRet; +} + +void SwXTextTableRow::addPropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableRow::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableRow::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableRow::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableRow::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pFormat = nullptr; + } else if(auto pFindHint = dynamic_cast<const FindUnoInstanceHint<SwTableLine, SwXTextTableRow>*>(&rHint)) + { + if(!pFindHint->m_pCore && pFindHint->m_pCore == pLine) + pFindHint->m_pResult = this; + } +} + +SwTableLine* SwXTextTableRow::FindLine(SwTable* pTable, SwTableLine const * pLine) +{ + for(const auto& pCurrentLine : pTable->GetTabLines()) + if(pCurrentLine == pLine) + return pCurrentLine; + return nullptr; +} + +// SwXTextTableCursor + +OUString SwXTextTableCursor::getImplementationName() + { return "SwXTextTableCursor"; } + +sal_Bool SwXTextTableCursor::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +void SwXTextTableCursor::acquire() throw() +{ + SwXTextTableCursor_Base::acquire(); +} + +void SwXTextTableCursor::release() throw() +{ + SolarMutexGuard aGuard; + SwXTextTableCursor_Base::release(); +} + +css::uno::Any SAL_CALL +SwXTextTableCursor::queryInterface( const css::uno::Type& _rType ) +{ + css::uno::Any aReturn = SwXTextTableCursor_Base::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = OTextCursorHelper::queryInterface( _rType ); + return aReturn; +} + +const SwPaM* SwXTextTableCursor::GetPaM() const { return &GetCursor(); } +SwPaM* SwXTextTableCursor::GetPaM() { return &GetCursor(); } +const SwDoc* SwXTextTableCursor::GetDoc() const { return GetFrameFormat()->GetDoc(); } +SwDoc* SwXTextTableCursor::GetDoc() { return GetFrameFormat()->GetDoc(); } +const SwUnoCursor& SwXTextTableCursor::GetCursor() const { return *m_pUnoCursor; } +SwUnoCursor& SwXTextTableCursor::GetCursor() { return *m_pUnoCursor; } + +uno::Sequence<OUString> SwXTextTableCursor::getSupportedServiceNames() + { return {"com.sun.star.text.TextTableCursor"}; } + +SwXTextTableCursor::SwXTextTableCursor(SwFrameFormat* pFrameFormat, SwTableBox const* pBox) + : m_pFrameFormat(pFrameFormat) + , m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_TABLE_CURSOR)) +{ + StartListening(m_pFrameFormat->GetNotifier()); + SwDoc* pDoc = m_pFrameFormat->GetDoc(); + const SwStartNode* pSttNd = pBox->GetSttNd(); + SwPosition aPos(*pSttNd); + m_pUnoCursor = pDoc->CreateUnoCursor(aPos, true); + m_pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(*m_pUnoCursor); + rTableCursor.MakeBoxSels(); +} + +SwXTextTableCursor::SwXTextTableCursor(SwFrameFormat& rTableFormat, const SwTableCursor* pTableSelection) + : m_pFrameFormat(&rTableFormat) + , m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_TABLE_CURSOR)) +{ + StartListening(m_pFrameFormat->GetNotifier()); + m_pUnoCursor = pTableSelection->GetDoc()->CreateUnoCursor(*pTableSelection->GetPoint(), true); + if(pTableSelection->HasMark()) + { + m_pUnoCursor->SetMark(); + *m_pUnoCursor->GetMark() = *pTableSelection->GetMark(); + } + const SwSelBoxes& rBoxes = pTableSelection->GetSelectedBoxes(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(*m_pUnoCursor); + for(auto pBox : rBoxes) + rTableCursor.InsertBox(*pBox); + rTableCursor.MakeBoxSels(); +} + +OUString SwXTextTableCursor::getRangeName() +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor* pTableCursor = dynamic_cast<SwUnoTableCursor*>(&rUnoCursor); + //!! see also SwChartDataSequence::getSourceRangeRepresentation + if(!pTableCursor) + return OUString(); + pTableCursor->MakeBoxSels(); + const SwStartNode* pNode = pTableCursor->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + const SwTable* pTable = SwTable::FindTable(GetFrameFormat()); + const SwTableBox* pEndBox = pTable->GetTableBox(pNode->GetIndex()); + if(pTableCursor->HasMark()) + { + pNode = pTableCursor->GetMark()->nNode.GetNode().FindTableBoxStartNode(); + const SwTableBox* pStartBox = pTable->GetTableBox(pNode->GetIndex()); + if(pEndBox != pStartBox) + { + // need to switch start and end? + if(*pTableCursor->GetPoint() < *pTableCursor->GetMark()) + std::swap(pStartBox, pEndBox); + return pStartBox->GetName() + ":" + pEndBox->GetName(); + } + } + return pEndBox->GetName(); +} + +sal_Bool SwXTextTableCursor::gotoCellByName(const OUString& sCellName, sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + auto& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + return rTableCursor.GotoTableBox(sCellName); +} + +sal_Bool SwXTextTableCursor::goLeft(sal_Int16 Count, sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + return rTableCursor.Left(Count); +} + +sal_Bool SwXTextTableCursor::goRight(sal_Int16 Count, sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + return rTableCursor.Right(Count); +} + +sal_Bool SwXTextTableCursor::goUp(sal_Int16 Count, sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + return rTableCursor.UpDown(true, Count, nullptr, 0, + *rUnoCursor.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()); +} + +sal_Bool SwXTextTableCursor::goDown(sal_Int16 Count, sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + return rTableCursor.UpDown(false, Count, nullptr, 0, + *rUnoCursor.GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()); +} + +void SwXTextTableCursor::gotoStart(sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + rTableCursor.MoveTable(GotoCurrTable, fnTableStart); +} + +void SwXTextTableCursor::gotoEnd(sal_Bool bExpand) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + lcl_CursorSelect(rTableCursor, bExpand); + rTableCursor.MoveTable(GotoCurrTable, fnTableEnd); +} + +sal_Bool SwXTextTableCursor::mergeRange() +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + { + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rTableCursor); + } + rTableCursor.MakeBoxSels(); + bool bResult; + { + UnoActionContext aContext(rUnoCursor.GetDoc()); + bResult = TableMergeErr::Ok == rTableCursor.GetDoc()->MergeTable(rTableCursor); + } + if(bResult) + { + size_t nCount = rTableCursor.GetSelectedBoxesCount(); + while (nCount--) + rTableCursor.DeleteBox(nCount); + } + rTableCursor.MakeBoxSels(); + return bResult; +} + +sal_Bool SwXTextTableCursor::splitRange(sal_Int16 Count, sal_Bool Horizontal) +{ + SolarMutexGuard aGuard; + if (Count <= 0) + throw uno::RuntimeException("Illegal first argument: needs to be > 0", static_cast<cppu::OWeakObject*>(this)); + SwUnoCursor& rUnoCursor = GetCursor(); + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + { + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rTableCursor); + } + rTableCursor.MakeBoxSels(); + bool bResult; + { + UnoActionContext aContext(rUnoCursor.GetDoc()); + bResult = rTableCursor.GetDoc()->SplitTable(rTableCursor.GetSelectedBoxes(), !Horizontal, Count); + } + rTableCursor.MakeBoxSels(); + return bResult; +} + +uno::Reference< beans::XPropertySetInfo > SwXTextTableCursor::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > xRef = m_pPropSet->getPropertySetInfo(); + return xRef; +} + +void SwXTextTableCursor::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + auto pEntry(m_pPropSet->getPropertyMap().getByName(rPropertyName)); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast<cppu::OWeakObject*>(this)); + if(pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException("Property is read-only: " + rPropertyName, static_cast<cppu::OWeakObject*>(this)); + { + auto pSttNode = rUnoCursor.GetNode().StartOfSectionNode(); + const SwTableNode* pTableNode = pSttNode->FindTableNode(); + lcl_FormatTable(pTableNode->GetTable().GetFrameFormat()); + } + auto& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + rTableCursor.MakeBoxSels(); + SwDoc* pDoc = rUnoCursor.GetDoc(); + switch(pEntry->nWID) + { + case FN_UNO_TABLE_CELL_BACKGROUND: + { + std::unique_ptr<SfxPoolItem> aBrush(std::make_unique<SvxBrushItem>(RES_BACKGROUND)); + SwDoc::GetBoxAttr(rUnoCursor, aBrush); + aBrush->PutValue(aValue, pEntry->nMemberId); + pDoc->SetBoxAttr(rUnoCursor, *aBrush); + + } + break; + case RES_BOXATR_FORMAT: + { + SfxUInt32Item aNumberFormat(RES_BOXATR_FORMAT); + aNumberFormat.PutValue(aValue, 0); + pDoc->SetBoxAttr(rUnoCursor, aNumberFormat); + } + break; + case FN_UNO_PARA_STYLE: + SwUnoCursorHelper::SetTextFormatColl(aValue, rUnoCursor); + break; + default: + { + SfxItemSet aItemSet(pDoc->GetAttrPool(), {{pEntry->nWID, pEntry->nWID}}); + SwUnoCursorHelper::GetCursorAttr(rTableCursor.GetSelRing(), + aItemSet); + + if (!SwUnoCursorHelper::SetCursorPropertyValue( + *pEntry, aValue, rTableCursor.GetSelRing(), aItemSet)) + { + m_pPropSet->setPropertyValue(*pEntry, aValue, aItemSet); + } + SwUnoCursorHelper::SetCursorAttr(rTableCursor.GetSelRing(), + aItemSet, SetAttrMode::DEFAULT, true); + } + } +} + +uno::Any SwXTextTableCursor::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + SwUnoCursor& rUnoCursor = GetCursor(); + { + auto pSttNode = rUnoCursor.GetNode().StartOfSectionNode(); + const SwTableNode* pTableNode = pSttNode->FindTableNode(); + lcl_FormatTable(pTableNode->GetTable().GetFrameFormat()); + } + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(rUnoCursor); + auto pEntry(m_pPropSet->getPropertyMap().getByName(rPropertyName)); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast<cppu::OWeakObject*>(this)); + rTableCursor.MakeBoxSels(); + uno::Any aResult; + switch(pEntry->nWID) + { + case FN_UNO_TABLE_CELL_BACKGROUND: + { + std::unique_ptr<SfxPoolItem> aBrush(std::make_unique<SvxBrushItem>(RES_BACKGROUND)); + if (SwDoc::GetBoxAttr(rUnoCursor, aBrush)) + aBrush->QueryValue(aResult, pEntry->nMemberId); + } + break; + case RES_BOXATR_FORMAT: + // TODO: GetAttr for table selections in a Doc is missing + throw uno::RuntimeException("Unknown property: " + rPropertyName, static_cast<cppu::OWeakObject*>(this)); + break; + case FN_UNO_PARA_STYLE: + { + auto pFormat(SwUnoCursorHelper::GetCurTextFormatColl(rUnoCursor, false)); + if(pFormat) + aResult <<= pFormat->GetName(); + } + break; + default: + { + SfxItemSet aSet(rTableCursor.GetDoc()->GetAttrPool(), + svl::Items<RES_CHRATR_BEGIN, RES_FRMATR_END-1, + RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER>{}); + SwUnoCursorHelper::GetCursorAttr(rTableCursor.GetSelRing(), aSet); + m_pPropSet->getPropertyValue(*pEntry, aSet, aResult); + } + } + return aResult; +} + +void SwXTextTableCursor::addPropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableCursor::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableCursor::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableCursor::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("not implemented", static_cast<cppu::OWeakObject*>(this)); }; + +void SwXTextTableCursor::Notify( const SfxHint& rHint ) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFrameFormat = nullptr; +} + + +// SwXTextTable =========================================================== + +namespace { + +class SwTableProperties_Impl +{ + SwUnoCursorHelper::SwAnyMapHelper aAnyMap; +public: + SwTableProperties_Impl(); + + void SetProperty(sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any& aVal); + bool GetProperty(sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any*& rpAny); + void AddItemToSet(SfxItemSet& rSet, std::function<std::unique_ptr<SfxPoolItem>()> aItemFactory, + sal_uInt16 nWhich, std::initializer_list<sal_uInt16> vMember, bool bAddTwips = false); + void ApplyTableAttr(const SwTable& rTable, SwDoc& rDoc); +}; + +} + +SwTableProperties_Impl::SwTableProperties_Impl() + { } + +void SwTableProperties_Impl::SetProperty(sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any& rVal) + { aAnyMap.SetValue( nWhichId, nMemberId, rVal ); } + +bool SwTableProperties_Impl::GetProperty(sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any*& rpAny ) + { return aAnyMap.FillValue( nWhichId, nMemberId, rpAny ); } + +void SwTableProperties_Impl::AddItemToSet(SfxItemSet& rSet, + std::function<std::unique_ptr<SfxPoolItem>()> aItemFactory, + sal_uInt16 nWhich, std::initializer_list<sal_uInt16> vMember, bool bAddTwips) +{ + std::vector< std::pair<sal_uInt16, const uno::Any* > > vMemberAndAny; + for(sal_uInt16 nMember : vMember) + { + const uno::Any* pAny = nullptr; + GetProperty(nWhich, nMember, pAny); + if(pAny) + vMemberAndAny.emplace_back(nMember, pAny); + } + if(!vMemberAndAny.empty()) + { + std::unique_ptr<SfxPoolItem> aItem(aItemFactory()); + for(const auto& aMemberAndAny : vMemberAndAny) + aItem->PutValue(*aMemberAndAny.second, aMemberAndAny.first | (bAddTwips ? CONVERT_TWIPS : 0) ); + rSet.Put(*aItem); + } +} +void SwTableProperties_Impl::ApplyTableAttr(const SwTable& rTable, SwDoc& rDoc) +{ + SfxItemSet aSet( + rDoc.GetAttrPool(), + svl::Items< + RES_FRM_SIZE, RES_BREAK, + RES_HORI_ORIENT, RES_HORI_ORIENT, + RES_BACKGROUND, RES_BACKGROUND, + RES_SHADOW, RES_SHADOW, + RES_KEEP, RES_KEEP, + RES_LAYOUT_SPLIT, RES_LAYOUT_SPLIT>{}); + const uno::Any* pRepHead; + const SwFrameFormat &rFrameFormat = *rTable.GetFrameFormat(); + if(GetProperty(FN_TABLE_HEADLINE_REPEAT, 0xff, pRepHead )) + { + bool bVal(pRepHead->get<bool>()); + const_cast<SwTable&>(rTable).SetRowsToRepeat( bVal ? 1 : 0 ); // TODO: MULTIHEADER + } + + AddItemToSet(aSet, [&rFrameFormat]() { return rFrameFormat.makeBackgroundBrushItem(); }, RES_BACKGROUND, { + MID_BACK_COLOR, + MID_GRAPHIC_TRANSPARENT, + MID_GRAPHIC_POSITION, + MID_GRAPHIC, + MID_GRAPHIC_FILTER }); + + bool bPutBreak = true; + const uno::Any* pPage; + if(GetProperty(FN_UNO_PAGE_STYLE, 0, pPage) || GetProperty(RES_PAGEDESC, 0xff, pPage)) + { + OUString sPageStyle = pPage->get<OUString>(); + if(!sPageStyle.isEmpty()) + { + SwStyleNameMapper::FillUIName(sPageStyle, sPageStyle, SwGetPoolIdFromName::PageDesc); + const SwPageDesc* pDesc = SwPageDesc::GetByName(rDoc, sPageStyle); + if(pDesc) + { + SwFormatPageDesc aDesc(pDesc); + const uno::Any* pPgNo; + if(GetProperty(RES_PAGEDESC, MID_PAGEDESC_PAGENUMOFFSET, pPgNo)) + { + aDesc.SetNumOffset(pPgNo->get<sal_Int16>()); + } + aSet.Put(aDesc); + bPutBreak = false; + } + + } + } + + if(bPutBreak) + AddItemToSet(aSet, [&rFrameFormat]() { return std::unique_ptr<SfxPoolItem>(rFrameFormat.GetBreak().Clone()); }, RES_BREAK, {0}); + AddItemToSet(aSet, [&rFrameFormat]() { return std::unique_ptr<SfxPoolItem>(rFrameFormat.GetShadow().Clone()); }, RES_SHADOW, {0}, true); + AddItemToSet(aSet, [&rFrameFormat]() { return std::unique_ptr<SfxPoolItem>(rFrameFormat.GetKeep().Clone()); }, RES_KEEP, {0}); + AddItemToSet(aSet, [&rFrameFormat]() { return std::unique_ptr<SfxPoolItem>(rFrameFormat.GetHoriOrient().Clone()); }, RES_HORI_ORIENT, {MID_HORIORIENT_ORIENT}, true); + + const uno::Any* pSzRel(nullptr); + GetProperty(FN_TABLE_IS_RELATIVE_WIDTH, 0xff, pSzRel); + const uno::Any* pRelWidth(nullptr); + GetProperty(FN_TABLE_RELATIVE_WIDTH, 0xff, pRelWidth); + const uno::Any* pWidth(nullptr); + GetProperty(FN_TABLE_WIDTH, 0xff, pWidth); + + bool bPutSize = pWidth != nullptr; + SwFormatFrameSize aSz(SwFrameSize::Variable); + if(pWidth) + { + aSz.PutValue(*pWidth, MID_FRMSIZE_WIDTH); + bPutSize = true; + } + if(pSzRel && pSzRel->get<bool>() && pRelWidth) + { + aSz.PutValue(*pRelWidth, MID_FRMSIZE_REL_WIDTH|CONVERT_TWIPS); + bPutSize = true; + } + if(bPutSize) + { + if(!aSz.GetWidth()) + aSz.SetWidth(MINLAY); + aSet.Put(aSz); + } + AddItemToSet(aSet, [&rFrameFormat]() { return std::unique_ptr<SfxPoolItem>(rFrameFormat.GetLRSpace().Clone()); }, RES_LR_SPACE, { + MID_L_MARGIN|CONVERT_TWIPS, + MID_R_MARGIN|CONVERT_TWIPS }); + AddItemToSet(aSet, [&rFrameFormat]() { return std::unique_ptr<SfxPoolItem>(rFrameFormat.GetULSpace().Clone()); }, RES_UL_SPACE, { + MID_UP_MARGIN|CONVERT_TWIPS, + MID_LO_MARGIN|CONVERT_TWIPS }); + const::uno::Any* pSplit(nullptr); + if(GetProperty(RES_LAYOUT_SPLIT, 0, pSplit)) + { + SwFormatLayoutSplit aSp(pSplit->get<bool>()); + aSet.Put(aSp); + } + if(aSet.Count()) + { + rDoc.SetAttr(aSet, *rTable.GetFrameFormat()); + } +} + +class SwXTextTable::Impl + : public SvtListener +{ +private: + SwFrameFormat* m_pFrameFormat; + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::cppu::OMultiTypeInterfaceContainerHelper m_Listeners; + + const SfxItemPropertySet * m_pPropSet; + + css::uno::WeakReference<css::table::XTableRows> m_xRows; + css::uno::WeakReference<css::table::XTableColumns> m_xColumns; + + bool m_bFirstRowAsLabel; + bool m_bFirstColumnAsLabel; + + // Descriptor-interface + std::unique_ptr<SwTableProperties_Impl> m_pTableProps; + OUString m_sTableName; + unsigned short m_nRows; + unsigned short m_nColumns; + + explicit Impl(SwFrameFormat* const pFrameFormat) + : m_pFrameFormat(pFrameFormat) + , m_Listeners(m_Mutex) + , m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_TABLE)) + , m_bFirstRowAsLabel(false) + , m_bFirstColumnAsLabel(false) + , m_pTableProps(pFrameFormat ? nullptr : new SwTableProperties_Impl) + , m_nRows(pFrameFormat ? 0 : 2) + , m_nColumns(pFrameFormat ? 0 : 2) + { + if(m_pFrameFormat) + StartListening(m_pFrameFormat->GetNotifier()); + } + + SwFrameFormat* GetFrameFormat() { return m_pFrameFormat; } + void SetFrameFormat(SwFrameFormat& rFrameFormat) + { + EndListeningAll(); + m_pFrameFormat = &rFrameFormat; + StartListening(m_pFrameFormat->GetNotifier()); + } + + bool IsDescriptor() const { return m_pTableProps != nullptr; } + + // note: lock mutex before calling this to avoid concurrent update + static std::pair<sal_uInt16, sal_uInt16> ThrowIfComplex(SwXTextTable &rThis) + { + sal_uInt16 const nRowCount(rThis.m_pImpl->GetRowCount()); + sal_uInt16 const nColCount(rThis.m_pImpl->GetColumnCount()); + if (!nRowCount || !nColCount) + { + throw uno::RuntimeException("Table too complex", + static_cast<cppu::OWeakObject*>(&rThis)); + } + return std::make_pair(nRowCount, nColCount); + } + + sal_uInt16 GetRowCount(); + sal_uInt16 GetColumnCount(); + + virtual void Notify(const SfxHint&) override; + +}; + +namespace +{ + class theSwXTextTableUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextTableUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXTextTable::getUnoTunnelId() + { return theSwXTextTableUnoTunnelId::get().getSeq(); } + +sal_Int64 SAL_CALL SwXTextTable::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if(isUnoTunnelId<SwXTextTable>(rId)) + { + return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this)); + } + return 0; +} + + +SwXTextTable::SwXTextTable() + : m_pImpl(new Impl(nullptr)) +{ +} + +SwXTextTable::SwXTextTable(SwFrameFormat& rFrameFormat) + : m_pImpl(new Impl(&rFrameFormat)) +{ +} + +SwXTextTable::~SwXTextTable() +{ +} + +uno::Reference<text::XTextTable> SwXTextTable::CreateXTextTable(SwFrameFormat* const pFrameFormat) +{ + uno::Reference<text::XTextTable> xTable; + if(pFrameFormat) + xTable.set(pFrameFormat->GetXObject(), uno::UNO_QUERY); // cached? + if(xTable.is()) + return xTable; + SwXTextTable* const pNew( pFrameFormat ? new SwXTextTable(*pFrameFormat) : new SwXTextTable()); + xTable.set(pNew); + if(pFrameFormat) + pFrameFormat->SetXObject(xTable); + // need a permanent Reference to initialize m_wThis + pNew->m_pImpl->m_wThis = xTable; + return xTable; +} + +SwFrameFormat* SwXTextTable::GetFrameFormat() +{ + return m_pImpl->GetFrameFormat(); +} + +void SwXTextTable::initialize(sal_Int32 nR, sal_Int32 nC) +{ + if (!m_pImpl->IsDescriptor() || nR <= 0 || nC <= 0 || nR >= SAL_MAX_UINT16 || nC >= SAL_MAX_UINT16) + throw uno::RuntimeException(); + m_pImpl->m_nRows = static_cast<sal_uInt16>(nR); + m_pImpl->m_nColumns = static_cast<sal_uInt16>(nC); +} + +uno::Reference<table::XTableRows> SAL_CALL SwXTextTable::getRows() +{ + SolarMutexGuard aGuard; + uno::Reference<table::XTableRows> xResult(m_pImpl->m_xRows); + if(xResult.is()) + return xResult; + if(SwFrameFormat* pFormat = GetFrameFormat()) + m_pImpl->m_xRows = xResult = new SwXTableRows(*pFormat); + if(!xResult.is()) + throw uno::RuntimeException(); + return xResult; +} + +uno::Reference<table::XTableColumns> SAL_CALL SwXTextTable::getColumns() +{ + SolarMutexGuard aGuard; + uno::Reference<table::XTableColumns> xResult(m_pImpl->m_xColumns); + if(xResult.is()) + return xResult; + if(SwFrameFormat* pFormat = GetFrameFormat()) + m_pImpl->m_xColumns = xResult = new SwXTableColumns(*pFormat); + if(!xResult.is()) + throw uno::RuntimeException(); + return xResult; +} + +uno::Reference<table::XCell> SwXTextTable::getCellByName(const OUString& sCellName) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = SwTable::FindTable(pFormat); + SwTableBox* pBox = const_cast<SwTableBox*>(pTable->GetTableBox(sCellName)); + if(!pBox) + return nullptr; + return SwXCell::CreateXCell(pFormat, pBox); +} + +uno::Sequence<OUString> SwXTextTable::getCellNames() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat(GetFrameFormat()); + if(!pFormat) + return {}; + SwTable* pTable = SwTable::FindTable(pFormat); + // exists at the table and at all boxes + SwTableLines& rTableLines = pTable->GetTabLines(); + std::vector<OUString> aAllNames; + lcl_InspectLines(rTableLines, aAllNames); + return comphelper::containerToSequence(aAllNames); +} + +uno::Reference<text::XTextTableCursor> SwXTextTable::createCursorByCellName(const OUString& sCellName) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = SwTable::FindTable(pFormat); + SwTableBox* pBox = const_cast<SwTableBox*>(pTable->GetTableBox(sCellName)); + if(!pBox || pBox->getRowSpan() == 0) + throw uno::RuntimeException(); + return new SwXTextTableCursor(pFormat, pBox); +} + +void SAL_CALL +SwXTextTable::attach(const uno::Reference<text::XTextRange> & xTextRange) +{ + SolarMutexGuard aGuard; + + // attach() must only be called once + if (!m_pImpl->IsDescriptor()) /* already attached ? */ + throw uno::RuntimeException("SwXTextTable: already attached to range.", static_cast<cppu::OWeakObject*>(this)); + + uno::Reference<XUnoTunnel> xRangeTunnel(xTextRange, uno::UNO_QUERY); + SwXTextRange* pRange(nullptr); + OTextCursorHelper* pCursor(nullptr); + if(xRangeTunnel.is()) + { + pRange = reinterpret_cast<SwXTextRange*>( + sal::static_int_cast<sal_IntPtr>(xRangeTunnel->getSomething(SwXTextRange::getUnoTunnelId()))); + pCursor = reinterpret_cast<OTextCursorHelper*>( + sal::static_int_cast<sal_IntPtr>(xRangeTunnel->getSomething(OTextCursorHelper::getUnoTunnelId()))); + } + SwDoc* pDoc = pRange ? &pRange->GetDoc() : pCursor ? pCursor->GetDoc() : nullptr; + if (!pDoc || !m_pImpl->m_nRows || !m_pImpl->m_nColumns) + throw lang::IllegalArgumentException(); + SwUnoInternalPaM aPam(*pDoc); + // this now needs to return TRUE + ::sw::XTextRangeToSwPaM(aPam, xTextRange); + { + UnoActionContext aCont(pDoc); + + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + const SwTable* pTable(nullptr); + if( 0 != aPam.Start()->nContent.GetIndex() ) + { + pDoc->getIDocumentContentOperations().SplitNode(*aPam.Start(), false); + } + //TODO: if it is the last paragraph than add another one! + if(aPam.HasMark()) + { + pDoc->getIDocumentContentOperations().DeleteAndJoin(aPam); + aPam.DeleteMark(); + } + pTable = pDoc->InsertTable(SwInsertTableOptions( SwInsertTableFlags::Headline | SwInsertTableFlags::DefaultBorder | SwInsertTableFlags::SplitLayout, 0 ), + *aPam.GetPoint(), + m_pImpl->m_nRows, + m_pImpl->m_nColumns, + text::HoriOrientation::FULL); + if(pTable) + { + // here, the properties of the descriptor need to be analyzed + m_pImpl->m_pTableProps->ApplyTableAttr(*pTable, *pDoc); + SwFrameFormat* pTableFormat(pTable->GetFrameFormat()); + lcl_FormatTable(pTableFormat); + + m_pImpl->SetFrameFormat(*pTableFormat); + + if (!m_pImpl->m_sTableName.isEmpty()) + { + sal_uInt16 nIndex = 1; + OUString sTmpNameIndex(m_pImpl->m_sTableName); + while(pDoc->FindTableFormatByName(sTmpNameIndex, true) && nIndex < USHRT_MAX) + { + sTmpNameIndex = m_pImpl->m_sTableName + OUString::number(nIndex++); + } + pDoc->SetTableName( *pTableFormat, sTmpNameIndex); + } + + const::uno::Any* pName; + if (m_pImpl->m_pTableProps->GetProperty(FN_UNO_TABLE_NAME, 0, pName)) + setName(pName->get<OUString>()); + m_pImpl->m_pTableProps.reset(); + } + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } +} + +uno::Reference<text::XTextRange> SwXTextTable::getAnchor() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + return new SwXTextRange(*pFormat); +} + +void SwXTextTable::dispose() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = SwTable::FindTable(pFormat); + SwSelBoxes aSelBoxes; + for(auto& rBox : pTable->GetTabSortBoxes() ) + aSelBoxes.insert(rBox); + pFormat->GetDoc()->DeleteRowCol(aSelBoxes); +} + +void SAL_CALL SwXTextTable::addEventListener( + const uno::Reference<lang::XEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.addInterface( + cppu::UnoType<lang::XEventListener>::get(), xListener); +} + +void SAL_CALL SwXTextTable::removeEventListener( + const uno::Reference< lang::XEventListener > & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.removeInterface( + cppu::UnoType<lang::XEventListener>::get(), xListener); +} + +uno::Reference<table::XCell> SwXTextTable::getCellByPosition(sal_Int32 nColumn, sal_Int32 nRow) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat(GetFrameFormat()); + // sheet is unimportant + if(nColumn >= 0 && nRow >= 0 && pFormat) + { + auto pXCell = lcl_CreateXCell(pFormat, nColumn, nRow); + if(pXCell) + return pXCell; + } + throw lang::IndexOutOfBoundsException(); +} + +namespace { + +uno::Reference<table::XCellRange> GetRangeByName( + SwFrameFormat* pFormat, SwTable const * pTable, + const OUString& rTLName, const OUString& rBRName, + SwRangeDescriptor const & rDesc) +{ + const SwTableBox* pTLBox = pTable->GetTableBox(rTLName); + if(!pTLBox) + return nullptr; + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor to the upper-left cell of the range + auto pUnoCursor(pFormat->GetDoc()->CreateUnoCursor(aPos, true)); + pUnoCursor->Move(fnMoveForward, GoInNode); + pUnoCursor->SetRemainInSection(false); + const SwTableBox* pBRBox(pTable->GetTableBox(rBRName)); + if(!pBRBox) + return nullptr; + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->nNode = *pBRBox->GetSttNd(); + pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rCursor = dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + rCursor.MakeBoxSels(); + // pUnoCursor will be provided and will not be deleted + return SwXCellRange::CreateXCellRange(pUnoCursor, *pFormat, rDesc).get(); +} + +} // namespace + +uno::Reference<table::XCellRange> SwXTextTable::getCellRangeByPosition(sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat(GetFrameFormat()); + if(pFormat && + nLeft <= nRight && nTop <= nBottom && + nLeft >= 0 && nRight >= 0 && nTop >= 0 && nBottom >= 0 ) + { + SwTable* pTable = SwTable::FindTable(pFormat); + if(!pTable->IsTableComplex()) + { + SwRangeDescriptor aDesc; + aDesc.nTop = nTop; + aDesc.nBottom = nBottom; + aDesc.nLeft = nLeft; + aDesc.nRight = nRight; + const OUString sTLName = sw_GetCellName(aDesc.nLeft, aDesc.nTop); + const OUString sBRName = sw_GetCellName(aDesc.nRight, aDesc.nBottom); + // please note that according to the 'if' statement at the begin + // sTLName:sBRName already denotes the normalized range string + return GetRangeByName(pFormat, pTable, sTLName, sBRName, aDesc); + } + } + throw lang::IndexOutOfBoundsException(); +} + +uno::Reference<table::XCellRange> SwXTextTable::getCellRangeByName(const OUString& sRange) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = lcl_EnsureTableNotComplex(SwTable::FindTable(pFormat), static_cast<cppu::OWeakObject*>(this)); + sal_Int32 nPos = 0; + const OUString sTLName(sRange.getToken(0, ':', nPos)); + const OUString sBRName(sRange.getToken(0, ':', nPos)); + if(sTLName.isEmpty() || sBRName.isEmpty()) + throw uno::RuntimeException(); + SwRangeDescriptor aDesc; + aDesc.nTop = aDesc.nLeft = aDesc.nBottom = aDesc.nRight = -1; + SwXTextTable::GetCellPosition(sTLName, aDesc.nLeft, aDesc.nTop ); + SwXTextTable::GetCellPosition(sBRName, aDesc.nRight, aDesc.nBottom ); + + // we should normalize the range now (e.g. A5:C1 will become A1:C5) + // since (depending on what is done later) it will be troublesome + // elsewhere when the cursor in the implementation does not + // point to the top-left and bottom-right cells + aDesc.Normalize(); + return GetRangeByName(pFormat, pTable, sTLName, sBRName, aDesc); +} + +uno::Sequence< uno::Sequence< uno::Any > > SAL_CALL SwXTextTable::getDataArray() +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<sheet::XCellRangeData> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + return xAllRange->getDataArray(); +} + +void SAL_CALL SwXTextTable::setDataArray(const uno::Sequence< uno::Sequence< uno::Any > >& rArray) +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<sheet::XCellRangeData> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + return xAllRange->setDataArray(rArray); +} + +uno::Sequence< uno::Sequence< double > > SwXTextTable::getData() +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<chart::XChartDataArray> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + static_cast<SwXCellRange*>(xAllRange.get())->SetLabels( + m_pImpl->m_bFirstRowAsLabel, m_pImpl->m_bFirstColumnAsLabel); + return xAllRange->getData(); +} + +void SwXTextTable::setData(const uno::Sequence< uno::Sequence< double > >& rData) +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<chart::XChartDataArray> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + static_cast<SwXCellRange*>(xAllRange.get())->SetLabels( + m_pImpl->m_bFirstRowAsLabel, m_pImpl->m_bFirstColumnAsLabel); + xAllRange->setData(rData); + // this is rather inconsistent: setData on XTextTable sends events, but e.g. CellRanges do not + lcl_SendChartEvent(*this, m_pImpl->m_Listeners); +} + +uno::Sequence<OUString> SwXTextTable::getRowDescriptions() +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<chart::XChartDataArray> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + static_cast<SwXCellRange*>(xAllRange.get())->SetLabels( + m_pImpl->m_bFirstRowAsLabel, m_pImpl->m_bFirstColumnAsLabel); + return xAllRange->getRowDescriptions(); +} + +void SwXTextTable::setRowDescriptions(const uno::Sequence<OUString>& rRowDesc) +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<chart::XChartDataArray> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + static_cast<SwXCellRange*>(xAllRange.get())->SetLabels( + m_pImpl->m_bFirstRowAsLabel, m_pImpl->m_bFirstColumnAsLabel); + xAllRange->setRowDescriptions(rRowDesc); +} + +uno::Sequence<OUString> SwXTextTable::getColumnDescriptions() +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<chart::XChartDataArray> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + static_cast<SwXCellRange*>(xAllRange.get())->SetLabels( + m_pImpl->m_bFirstRowAsLabel, m_pImpl->m_bFirstColumnAsLabel); + return xAllRange->getColumnDescriptions(); +} + +void SwXTextTable::setColumnDescriptions(const uno::Sequence<OUString>& rColumnDesc) +{ + SolarMutexGuard aGuard; + std::pair<sal_uInt16, sal_uInt16> const RowsAndColumns(SwXTextTable::Impl::ThrowIfComplex(*this)); + uno::Reference<chart::XChartDataArray> const xAllRange( + getCellRangeByPosition(0, 0, RowsAndColumns.second-1, RowsAndColumns.first-1), + uno::UNO_QUERY_THROW); + static_cast<SwXCellRange*>(xAllRange.get())->SetLabels( + m_pImpl->m_bFirstRowAsLabel, m_pImpl->m_bFirstColumnAsLabel); + return xAllRange->setColumnDescriptions(rColumnDesc); +} + +void SAL_CALL SwXTextTable::addChartDataChangeEventListener( + const uno::Reference<chart::XChartDataChangeEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.addInterface( + cppu::UnoType<chart::XChartDataChangeEventListener>::get(), xListener); +} + +void SAL_CALL SwXTextTable::removeChartDataChangeEventListener( + const uno::Reference<chart::XChartDataChangeEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_Listeners.removeInterface( + cppu::UnoType<chart::XChartDataChangeEventListener>::get(), xListener); +} + +sal_Bool SwXTextTable::isNotANumber(double nNumber) +{ + // We use DBL_MIN because starcalc does (which uses it because chart + // wants it that way!) + return ( nNumber == DBL_MIN ); +} + +double SwXTextTable::getNotANumber() +{ + // We use DBL_MIN because starcalc does (which uses it because chart + // wants it that way!) + return DBL_MIN; +} + +uno::Sequence< beans::PropertyValue > SwXTextTable::createSortDescriptor() +{ + SolarMutexGuard aGuard; + + return SwUnoCursorHelper::CreateSortDescriptor(true); +} + +void SwXTextTable::sort(const uno::Sequence< beans::PropertyValue >& rDescriptor) +{ + SolarMutexGuard aGuard; + SwSortOptions aSortOpt; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat && + SwUnoCursorHelper::ConvertSortProperties(rDescriptor, aSortOpt)) + { + SwTable* pTable = SwTable::FindTable( pFormat ); + SwSelBoxes aBoxes; + const SwTableSortBoxes& rTBoxes = pTable->GetTabSortBoxes(); + for (size_t n = 0; n < rTBoxes.size(); ++n) + { + SwTableBox* pBox = rTBoxes[ n ]; + aBoxes.insert( pBox ); + } + UnoActionContext aContext( pFormat->GetDoc() ); + pFormat->GetDoc()->SortTable(aBoxes, aSortOpt); + } +} + +void SwXTextTable::autoFormat(const OUString& sAutoFormatName) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + SwTable* pTable = lcl_EnsureTableNotComplex(SwTable::FindTable(pFormat), static_cast<cppu::OWeakObject*>(this)); + SwTableAutoFormatTable aAutoFormatTable; + aAutoFormatTable.Load(); + for (size_t i = aAutoFormatTable.size(); i;) + if( sAutoFormatName == aAutoFormatTable[ --i ].GetName() ) + { + SwSelBoxes aBoxes; + const SwTableSortBoxes& rTBoxes = pTable->GetTabSortBoxes(); + for (size_t n = 0; n < rTBoxes.size(); ++n) + { + SwTableBox* pBox = rTBoxes[ n ]; + aBoxes.insert( pBox ); + } + UnoActionContext aContext( pFormat->GetDoc() ); + pFormat->GetDoc()->SetTableAutoFormat( aBoxes, aAutoFormatTable[i] ); + break; + } +} + +uno::Reference< beans::XPropertySetInfo > SwXTextTable::getPropertySetInfo() +{ + static uno::Reference<beans::XPropertySetInfo> xRef = m_pImpl->m_pPropSet->getPropertySetInfo(); + return xRef; +} + +void SwXTextTable::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if(!aValue.hasValue()) + throw lang::IllegalArgumentException(); + const SfxItemPropertySimpleEntry* pEntry = + m_pImpl->m_pPropSet->getPropertyMap().getByName(rPropertyName); + if( !pEntry ) + throw lang::IllegalArgumentException(); + if(pFormat) + { + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if(0xBF == pEntry->nMemberId) + { + lcl_SetSpecialProperty(pFormat, pEntry, aValue); + } + else + { + switch(pEntry->nWID) + { + case FN_UNO_TABLE_NAME : + { + OUString sName; + aValue >>= sName; + setName( sName ); + } + break; + + case FN_UNO_RANGE_ROW_LABEL: + { + bool bTmp = *o3tl::doAccess<bool>(aValue); + if (m_pImpl->m_bFirstRowAsLabel != bTmp) + { + lcl_SendChartEvent(*this, m_pImpl->m_Listeners); + m_pImpl->m_bFirstRowAsLabel = bTmp; + } + } + break; + + case FN_UNO_RANGE_COL_LABEL: + { + bool bTmp = *o3tl::doAccess<bool>(aValue); + if (m_pImpl->m_bFirstColumnAsLabel != bTmp) + { + lcl_SendChartEvent(*this, m_pImpl->m_Listeners); + m_pImpl->m_bFirstColumnAsLabel = bTmp; + } + } + break; + + case FN_UNO_TABLE_BORDER: + case FN_UNO_TABLE_BORDER2: + { + table::TableBorder oldBorder; + table::TableBorder2 aBorder; + SvxBorderLine aTopLine; + SvxBorderLine aBottomLine; + SvxBorderLine aLeftLine; + SvxBorderLine aRightLine; + SvxBorderLine aHoriLine; + SvxBorderLine aVertLine; + if (aValue >>= oldBorder) + { + aBorder.IsTopLineValid = oldBorder.IsTopLineValid; + aBorder.IsBottomLineValid = oldBorder.IsBottomLineValid; + aBorder.IsLeftLineValid = oldBorder.IsLeftLineValid; + aBorder.IsRightLineValid = oldBorder.IsRightLineValid; + aBorder.IsHorizontalLineValid = oldBorder.IsHorizontalLineValid; + aBorder.IsVerticalLineValid = oldBorder.IsVerticalLineValid; + aBorder.Distance = oldBorder.Distance; + aBorder.IsDistanceValid = oldBorder.IsDistanceValid; + lcl_LineToSvxLine( + oldBorder.TopLine, aTopLine); + lcl_LineToSvxLine( + oldBorder.BottomLine, aBottomLine); + lcl_LineToSvxLine( + oldBorder.LeftLine, aLeftLine); + lcl_LineToSvxLine( + oldBorder.RightLine, aRightLine); + lcl_LineToSvxLine( + oldBorder.HorizontalLine, aHoriLine); + lcl_LineToSvxLine( + oldBorder.VerticalLine, aVertLine); + } + else if (aValue >>= aBorder) + { + SvxBoxItem::LineToSvxLine( + aBorder.TopLine, aTopLine, true); + SvxBoxItem::LineToSvxLine( + aBorder.BottomLine, aBottomLine, true); + SvxBoxItem::LineToSvxLine( + aBorder.LeftLine, aLeftLine, true); + SvxBoxItem::LineToSvxLine( + aBorder.RightLine, aRightLine, true); + SvxBoxItem::LineToSvxLine( + aBorder.HorizontalLine, aHoriLine, true); + SvxBoxItem::LineToSvxLine( + aBorder.VerticalLine, aVertLine, true); + } + else + { + break; // something else + } + SwDoc* pDoc = pFormat->GetDoc(); + if(!lcl_FormatTable(pFormat)) + break; + SwTable* pTable = SwTable::FindTable( pFormat ); + SwTableLines &rLines = pTable->GetTabLines(); + + const SwTableBox* pTLBox = lcl_FindCornerTableBox(rLines, true); + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor to top left cell + auto pUnoCursor(pDoc->CreateUnoCursor(aPos, true)); + pUnoCursor->Move( fnMoveForward, GoInNode ); + pUnoCursor->SetRemainInSection( false ); + + const SwTableBox* pBRBox = lcl_FindCornerTableBox(rLines, false); + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->nNode = *pBRBox->GetSttNd(); + pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rCursor = dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + rCursor.MakeBoxSels(); + + SfxItemSet aSet(pDoc->GetAttrPool(), + svl::Items<RES_BOX, RES_BOX, + SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER>{}); + + SvxBoxItem aBox( RES_BOX ); + SvxBoxInfoItem aBoxInfo( SID_ATTR_BORDER_INNER ); + + aBox.SetLine(aTopLine.isEmpty() ? nullptr : &aTopLine, SvxBoxItemLine::TOP); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::TOP, aBorder.IsTopLineValid); + + aBox.SetLine(aBottomLine.isEmpty() ? nullptr : &aBottomLine, SvxBoxItemLine::BOTTOM); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::BOTTOM, aBorder.IsBottomLineValid); + + aBox.SetLine(aLeftLine.isEmpty() ? nullptr : &aLeftLine, SvxBoxItemLine::LEFT); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::LEFT, aBorder.IsLeftLineValid); + + aBox.SetLine(aRightLine.isEmpty() ? nullptr : &aRightLine, SvxBoxItemLine::RIGHT); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::RIGHT, aBorder.IsRightLineValid); + + aBoxInfo.SetLine(aHoriLine.isEmpty() ? nullptr : &aHoriLine, SvxBoxInfoItemLine::HORI); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI, aBorder.IsHorizontalLineValid); + + aBoxInfo.SetLine(aVertLine.isEmpty() ? nullptr : &aVertLine, SvxBoxInfoItemLine::VERT); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT, aBorder.IsVerticalLineValid); + + aBox.SetAllDistances(static_cast<sal_uInt16>(convertMm100ToTwip(aBorder.Distance))); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE, aBorder.IsDistanceValid); + + aSet.Put(aBox); + aSet.Put(aBoxInfo); + + pDoc->SetTabBorders(rCursor, aSet); + } + break; + + case FN_UNO_TABLE_BORDER_DISTANCES: + { + table::TableBorderDistances aTableBorderDistances; + if( !(aValue >>= aTableBorderDistances) || + (!aTableBorderDistances.IsLeftDistanceValid && + !aTableBorderDistances.IsRightDistanceValid && + !aTableBorderDistances.IsTopDistanceValid && + !aTableBorderDistances.IsBottomDistanceValid )) + break; + + const sal_uInt16 nLeftDistance = convertMm100ToTwip(aTableBorderDistances.LeftDistance); + const sal_uInt16 nRightDistance = convertMm100ToTwip(aTableBorderDistances.RightDistance); + const sal_uInt16 nTopDistance = convertMm100ToTwip(aTableBorderDistances.TopDistance); + const sal_uInt16 nBottomDistance = convertMm100ToTwip(aTableBorderDistances.BottomDistance); + SwDoc* pDoc = pFormat->GetDoc(); + SwTable* pTable = SwTable::FindTable( pFormat ); + SwTableLines &rLines = pTable->GetTabLines(); + pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::START, nullptr); + for(size_t i = 0; i < rLines.size(); ++i) + { + SwTableLine* pLine = rLines[i]; + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for(size_t k = 0; k < rBoxes.size(); ++k) + { + SwTableBox* pBox = rBoxes[k]; + const SwFrameFormat* pBoxFormat = pBox->GetFrameFormat(); + const SvxBoxItem& rBox = pBoxFormat->GetBox(); + if( + (aTableBorderDistances.IsLeftDistanceValid && nLeftDistance != rBox.GetDistance( SvxBoxItemLine::LEFT )) || + (aTableBorderDistances.IsRightDistanceValid && nRightDistance != rBox.GetDistance( SvxBoxItemLine::RIGHT )) || + (aTableBorderDistances.IsTopDistanceValid && nTopDistance != rBox.GetDistance( SvxBoxItemLine::TOP )) || + (aTableBorderDistances.IsBottomDistanceValid && nBottomDistance != rBox.GetDistance( SvxBoxItemLine::BOTTOM ))) + { + SvxBoxItem aSetBox( rBox ); + SwFrameFormat* pSetBoxFormat = pBox->ClaimFrameFormat(); + if( aTableBorderDistances.IsLeftDistanceValid ) + aSetBox.SetDistance( nLeftDistance, SvxBoxItemLine::LEFT ); + if( aTableBorderDistances.IsRightDistanceValid ) + aSetBox.SetDistance( nRightDistance, SvxBoxItemLine::RIGHT ); + if( aTableBorderDistances.IsTopDistanceValid ) + aSetBox.SetDistance( nTopDistance, SvxBoxItemLine::TOP ); + if( aTableBorderDistances.IsBottomDistanceValid ) + aSetBox.SetDistance( nBottomDistance, SvxBoxItemLine::BOTTOM ); + pDoc->SetAttr( aSetBox, *pSetBoxFormat ); + } + } + } + pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + break; + + case FN_UNO_TABLE_COLUMN_SEPARATORS: + { + UnoActionContext aContext(pFormat->GetDoc()); + SwTable* pTable = SwTable::FindTable( pFormat ); + lcl_SetTableSeparators(aValue, pTable, pTable->GetTabLines()[0]->GetTabBoxes()[0], false, pFormat->GetDoc()); + } + break; + + case FN_UNO_TABLE_COLUMN_RELATIVE_SUM:/*_readonly_*/ break; + + case FN_UNO_TABLE_TEMPLATE_NAME: + { + SwTable* pTable = SwTable::FindTable(pFormat); + OUString sName; + if (!(aValue >>= sName)) + break; + SwStyleNameMapper::FillUIName(sName, sName, SwGetPoolIdFromName::TabStyle); + pTable->SetTableStyleName(sName); + SwDoc* pDoc = pFormat->GetDoc(); + pDoc->GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(pTable->GetTableNode()); + } + break; + + default: + { + SwAttrSet aSet(pFormat->GetAttrSet()); + m_pImpl->m_pPropSet->setPropertyValue(*pEntry, aValue, aSet); + pFormat->GetDoc()->SetAttr(aSet, *pFormat); + } + } + } + } + else if (m_pImpl->IsDescriptor()) + { + m_pImpl->m_pTableProps->SetProperty(pEntry->nWID, pEntry->nMemberId, aValue); + } + else + throw uno::RuntimeException(); +} + +uno::Any SwXTextTable::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwFrameFormat* pFormat = GetFrameFormat(); + const SfxItemPropertySimpleEntry* pEntry = + m_pImpl->m_pPropSet->getPropertyMap().getByName(rPropertyName); + + if (!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if(pFormat) + { + if(0xBF == pEntry->nMemberId) + { + aRet = lcl_GetSpecialProperty(pFormat, pEntry ); + } + else + { + switch(pEntry->nWID) + { + case FN_UNO_TABLE_NAME: + { + aRet <<= getName(); + } + break; + + case FN_UNO_ANCHOR_TYPES: + case FN_UNO_TEXT_WRAP: + case FN_UNO_ANCHOR_TYPE: + ::sw::GetDefaultTextContentValue( + aRet, OUString(), pEntry->nWID); + break; + + case FN_UNO_RANGE_ROW_LABEL: + { + aRet <<= m_pImpl->m_bFirstRowAsLabel; + } + break; + + case FN_UNO_RANGE_COL_LABEL: + aRet <<= m_pImpl->m_bFirstColumnAsLabel; + break; + + case FN_UNO_TABLE_BORDER: + case FN_UNO_TABLE_BORDER2: + { + SwDoc* pDoc = pFormat->GetDoc(); + // tables without layout (invisible header/footer?) + if(!lcl_FormatTable(pFormat)) + break; + SwTable* pTable = SwTable::FindTable( pFormat ); + SwTableLines &rLines = pTable->GetTabLines(); + + const SwTableBox* pTLBox = lcl_FindCornerTableBox(rLines, true); + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor to top left cell + auto pUnoCursor(pDoc->CreateUnoCursor(aPos, true)); + pUnoCursor->Move( fnMoveForward, GoInNode ); + pUnoCursor->SetRemainInSection( false ); + + const SwTableBox* pBRBox = lcl_FindCornerTableBox(rLines, false); + pUnoCursor->SetMark(); + const SwStartNode* pLastNd = pBRBox->GetSttNd(); + pUnoCursor->GetPoint()->nNode = *pLastNd; + + pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rCursor = dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + rCursor.MakeBoxSels(); + + SfxItemSet aSet(pDoc->GetAttrPool(), + svl::Items<RES_BOX, RES_BOX, + SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER>{}); + aSet.Put(SvxBoxInfoItem( SID_ATTR_BORDER_INNER )); + SwDoc::GetTabBorders(rCursor, aSet); + const SvxBoxInfoItem& rBoxInfoItem = aSet.Get(SID_ATTR_BORDER_INNER); + const SvxBoxItem& rBox = aSet.Get(RES_BOX); + + if (FN_UNO_TABLE_BORDER == pEntry->nWID) + { + table::TableBorder aTableBorder; + aTableBorder.TopLine = SvxBoxItem::SvxLineToLine(rBox.GetTop(), true); + aTableBorder.IsTopLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::TOP); + aTableBorder.BottomLine = SvxBoxItem::SvxLineToLine(rBox.GetBottom(), true); + aTableBorder.IsBottomLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::BOTTOM); + aTableBorder.LeftLine = SvxBoxItem::SvxLineToLine(rBox.GetLeft(), true); + aTableBorder.IsLeftLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::LEFT); + aTableBorder.RightLine = SvxBoxItem::SvxLineToLine(rBox.GetRight(), true); + aTableBorder.IsRightLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::RIGHT ); + aTableBorder.HorizontalLine = SvxBoxItem::SvxLineToLine(rBoxInfoItem.GetHori(), true); + aTableBorder.IsHorizontalLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::HORI); + aTableBorder.VerticalLine = SvxBoxItem::SvxLineToLine(rBoxInfoItem.GetVert(), true); + aTableBorder.IsVerticalLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::VERT); + aTableBorder.Distance = convertTwipToMm100(rBox.GetSmallestDistance()); + aTableBorder.IsDistanceValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::DISTANCE); + aRet <<= aTableBorder; + } + else + { + table::TableBorder2 aTableBorder; + aTableBorder.TopLine = SvxBoxItem::SvxLineToLine(rBox.GetTop(), true); + aTableBorder.IsTopLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::TOP); + aTableBorder.BottomLine = SvxBoxItem::SvxLineToLine(rBox.GetBottom(), true); + aTableBorder.IsBottomLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::BOTTOM); + aTableBorder.LeftLine = SvxBoxItem::SvxLineToLine(rBox.GetLeft(), true); + aTableBorder.IsLeftLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::LEFT); + aTableBorder.RightLine = SvxBoxItem::SvxLineToLine(rBox.GetRight(), true); + aTableBorder.IsRightLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::RIGHT ); + aTableBorder.HorizontalLine = SvxBoxItem::SvxLineToLine(rBoxInfoItem.GetHori(), true); + aTableBorder.IsHorizontalLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::HORI); + aTableBorder.VerticalLine = SvxBoxItem::SvxLineToLine(rBoxInfoItem.GetVert(), true); + aTableBorder.IsVerticalLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::VERT); + aTableBorder.Distance = convertTwipToMm100(rBox.GetSmallestDistance()); + aTableBorder.IsDistanceValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::DISTANCE); + aRet <<= aTableBorder; + } + } + break; + + case FN_UNO_TABLE_BORDER_DISTANCES : + { + table::TableBorderDistances aTableBorderDistances( 0, true, 0, true, 0, true, 0, true ) ; + SwTable* pTable = SwTable::FindTable( pFormat ); + const SwTableLines &rLines = pTable->GetTabLines(); + bool bFirst = true; + sal_uInt16 nLeftDistance = 0; + sal_uInt16 nRightDistance = 0; + sal_uInt16 nTopDistance = 0; + sal_uInt16 nBottomDistance = 0; + + for(size_t i = 0; i < rLines.size(); ++i) + { + const SwTableLine* pLine = rLines[i]; + const SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for(size_t k = 0; k < rBoxes.size(); ++k) + { + const SwTableBox* pBox = rBoxes[k]; + SwFrameFormat* pBoxFormat = pBox->GetFrameFormat(); + const SvxBoxItem& rBox = pBoxFormat->GetBox(); + if( bFirst ) + { + nLeftDistance = convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::LEFT )); + nRightDistance = convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::RIGHT )); + nTopDistance = convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::TOP )); + nBottomDistance = convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::BOTTOM )); + bFirst = false; + } + else + { + if( aTableBorderDistances.IsLeftDistanceValid && + nLeftDistance != convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::LEFT ))) + aTableBorderDistances.IsLeftDistanceValid = false; + if( aTableBorderDistances.IsRightDistanceValid && + nRightDistance != convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::RIGHT ))) + aTableBorderDistances.IsRightDistanceValid = false; + if( aTableBorderDistances.IsTopDistanceValid && + nTopDistance != convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::TOP ))) + aTableBorderDistances.IsTopDistanceValid = false; + if( aTableBorderDistances.IsBottomDistanceValid && + nBottomDistance != convertTwipToMm100( rBox.GetDistance( SvxBoxItemLine::BOTTOM ))) + aTableBorderDistances.IsBottomDistanceValid = false; + } + + } + if( !aTableBorderDistances.IsLeftDistanceValid && + !aTableBorderDistances.IsRightDistanceValid && + !aTableBorderDistances.IsTopDistanceValid && + !aTableBorderDistances.IsBottomDistanceValid ) + break; + } + if( aTableBorderDistances.IsLeftDistanceValid) + aTableBorderDistances.LeftDistance = nLeftDistance; + if( aTableBorderDistances.IsRightDistanceValid) + aTableBorderDistances.RightDistance = nRightDistance; + if( aTableBorderDistances.IsTopDistanceValid) + aTableBorderDistances.TopDistance = nTopDistance; + if( aTableBorderDistances.IsBottomDistanceValid) + aTableBorderDistances.BottomDistance = nBottomDistance; + + aRet <<= aTableBorderDistances; + } + break; + + case FN_UNO_TABLE_COLUMN_SEPARATORS: + { + SwTable* pTable = SwTable::FindTable( pFormat ); + lcl_GetTableSeparators(aRet, pTable, pTable->GetTabLines()[0]->GetTabBoxes()[0], false); + } + break; + + case FN_UNO_TABLE_COLUMN_RELATIVE_SUM: + aRet <<= sal_Int16(UNO_TABLE_COLUMN_SUM); + break; + + case RES_ANCHOR: + // AnchorType is readonly and might be void (no return value) + break; + + case FN_UNO_TEXT_SECTION: + { + SwTable* pTable = SwTable::FindTable( pFormat ); + SwTableNode* pTableNode = pTable->GetTableNode(); + SwSectionNode* pSectionNode = pTableNode->FindSectionNode(); + if(pSectionNode) + { + SwSection& rSect = pSectionNode->GetSection(); + uno::Reference< text::XTextSection > xSect = + SwXTextSections::GetObject( *rSect.GetFormat() ); + aRet <<= xSect; + } + } + break; + + case FN_UNO_TABLE_TEMPLATE_NAME: + { + SwTable* pTable = SwTable::FindTable(pFormat); + OUString sName; + SwStyleNameMapper::FillProgName(pTable->GetTableStyleName(), sName, SwGetPoolIdFromName::TabStyle); + aRet <<= sName; + } + break; + + default: + { + const SwAttrSet& rSet = pFormat->GetAttrSet(); + m_pImpl->m_pPropSet->getPropertyValue(*pEntry, rSet, aRet); + } + } + } + } + else if (m_pImpl->IsDescriptor()) + { + const uno::Any* pAny = nullptr; + if (!m_pImpl->m_pTableProps->GetProperty(pEntry->nWID, pEntry->nMemberId, pAny)) + throw lang::IllegalArgumentException(); + else if(pAny) + aRet = *pAny; + } + else + throw uno::RuntimeException(); + return aRet; +} + +void SwXTextTable::addPropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +void SwXTextTable::removePropertyChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*xListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +void SwXTextTable::addVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +void SwXTextTable::removeVetoableChangeListener(const OUString& /*rPropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*xListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +OUString SwXTextTable::getName() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if (!pFormat && !m_pImpl->IsDescriptor()) + throw uno::RuntimeException(); + if(pFormat) + { + return pFormat->GetName(); + } + return m_pImpl->m_sTableName; +} + +void SwXTextTable::setName(const OUString& rName) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFormat = GetFrameFormat(); + if ((!pFormat && !m_pImpl->IsDescriptor()) || + rName.isEmpty() || + rName.indexOf('.')>=0 || + rName.indexOf(' ')>=0 ) + throw uno::RuntimeException(); + + if(pFormat) + { + const OUString aOldName( pFormat->GetName() ); + const SwFrameFormats* pFrameFormats = pFormat->GetDoc()->GetTableFrameFormats(); + for (size_t i = pFrameFormats->size(); i;) + { + const SwFrameFormat* pTmpFormat = (*pFrameFormats)[--i]; + if( !pTmpFormat->IsDefault() && + pTmpFormat->GetName() == rName && + pFormat->GetDoc()->IsUsed( *pTmpFormat )) + { + throw uno::RuntimeException(); + } + } + + pFormat->SetName( rName ); + + SwStartNode *pStNd; + SwNodeIndex aIdx( *pFormat->GetDoc()->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwNode *const pNd = & aIdx.GetNode(); + if ( pNd->IsOLENode() && + aOldName == static_cast<const SwOLENode*>(pNd)->GetChartTableName() ) + { + static_cast<SwOLENode*>(pNd)->SetChartTableName( rName ); + + SwTable* pTable = SwTable::FindTable( pFormat ); + //TL_CHART2: chart needs to be notfied about name changes + pFormat->GetDoc()->UpdateCharts( pTable->GetFrameFormat()->GetName() ); + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + pFormat->GetDoc()->getIDocumentState().SetModified(); + } + else + m_pImpl->m_sTableName = rName; +} + +sal_uInt16 SwXTextTable::Impl::GetRowCount() +{ + sal_uInt16 nRet = 0; + SwFrameFormat* pFormat = GetFrameFormat(); + if(pFormat) + { + SwTable* pTable = SwTable::FindTable( pFormat ); + if(!pTable->IsTableComplex()) + { + nRet = pTable->GetTabLines().size(); + } + } + return nRet; +} + +sal_uInt16 SwXTextTable::Impl::GetColumnCount() +{ + SwFrameFormat* pFormat = GetFrameFormat(); + sal_uInt16 nRet = 0; + if(pFormat) + { + SwTable* pTable = SwTable::FindTable( pFormat ); + if(!pTable->IsTableComplex()) + { + SwTableLines& rLines = pTable->GetTabLines(); + SwTableLine* pLine = rLines.front(); + nRet = pLine->GetTabBoxes().size(); + } + } + return nRet; +} + +void SwXTextTable::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + m_pFrameFormat = nullptr; + EndListeningAll(); + } + uno::Reference<uno::XInterface> const xThis(m_wThis); + if (xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + if(!m_pFrameFormat) + { + lang::EventObject const ev(xThis); + m_Listeners.disposeAndClear(ev); + } + else + { + lcl_SendChartEvent(xThis.get(), m_Listeners); + } + } +} + +OUString SAL_CALL SwXTextTable::getImplementationName() + { return "SwXTextTable"; } + +sal_Bool SwXTextTable::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +uno::Sequence<OUString> SwXTextTable::getSupportedServiceNames() +{ + return { + "com.sun.star.document.LinkTarget", + "com.sun.star.text.TextTable", + "com.sun.star.text.TextContent", + "com.sun.star.text.TextSortable" }; +} + + +class SwXCellRange::Impl + : public SvtListener +{ +private: + ::osl::Mutex m_Mutex; // just for OInterfaceContainerHelper2 + SwFrameFormat* m_pFrameFormat; + +public: + uno::WeakReference<uno::XInterface> m_wThis; + ::comphelper::OInterfaceContainerHelper2 m_ChartListeners; + + sw::UnoCursorPointer m_pTableCursor; + + SwRangeDescriptor m_RangeDescriptor; + const SfxItemPropertySet* m_pPropSet; + + bool m_bFirstRowAsLabel; + bool m_bFirstColumnAsLabel; + + Impl(sw::UnoCursorPointer const& pCursor, SwFrameFormat& rFrameFormat, SwRangeDescriptor const& rDesc) + : m_pFrameFormat(&rFrameFormat) + , m_ChartListeners(m_Mutex) + , m_pTableCursor(pCursor) + , m_RangeDescriptor(rDesc) + , m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TABLE_RANGE)) + , m_bFirstRowAsLabel(false) + , m_bFirstColumnAsLabel(false) + { + StartListening(rFrameFormat.GetNotifier()); + m_RangeDescriptor.Normalize(); + } + + SwFrameFormat* GetFrameFormat() + { + return m_pFrameFormat; + } + + std::tuple<sal_uInt32, sal_uInt32, sal_uInt32, sal_uInt32> GetLabelCoordinates(bool bRow); + + uno::Sequence<OUString> GetLabelDescriptions(SwXCellRange & rThis, bool bRow); + + void SetLabelDescriptions(SwXCellRange & rThis, + const css::uno::Sequence<OUString>& rDesc, bool bRow); + + sal_Int32 GetRowCount() const; + sal_Int32 GetColumnCount() const; + + virtual void Notify(const SfxHint& ) override; + +}; + +namespace +{ + class theSwXCellRangeUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXCellRangeUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXCellRange::getUnoTunnelId() +{ + return theSwXCellRangeUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SwXCellRange::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SwXCellRange>(rId) ) + { + return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); + } + return 0; +} + + +OUString SwXCellRange::getImplementationName() + { return "SwXCellRange"; } + +sal_Bool SwXCellRange::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +uno::Sequence<OUString> SwXCellRange::getSupportedServiceNames() +{ + return { + "com.sun.star.text.CellRange", + "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesAsian", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.ParagraphProperties", + "com.sun.star.style.ParagraphPropertiesAsian", + "com.sun.star.style.ParagraphPropertiesComplex" }; +} + +SwXCellRange::SwXCellRange(sw::UnoCursorPointer const& pCursor, + SwFrameFormat& rFrameFormat, SwRangeDescriptor const & rDesc) + : m_pImpl(new Impl(pCursor, rFrameFormat, rDesc)) +{ +} + +SwXCellRange::~SwXCellRange() +{ +} + +rtl::Reference<SwXCellRange> SwXCellRange::CreateXCellRange( + sw::UnoCursorPointer const& pCursor, SwFrameFormat& rFrameFormat, + SwRangeDescriptor const & rDesc) +{ + SwXCellRange *const pCellRange(new SwXCellRange(pCursor, rFrameFormat, rDesc)); + uno::Reference<table::XCellRange> xCellRange(pCellRange); + // need a permanent Reference to initialize m_wThis + pCellRange->m_pImpl->m_wThis = xCellRange; + return pCellRange; +} + +void SwXCellRange::SetLabels(bool bFirstRowAsLabel, bool bFirstColumnAsLabel) +{ + m_pImpl->m_bFirstRowAsLabel = bFirstRowAsLabel; + m_pImpl->m_bFirstColumnAsLabel = bFirstColumnAsLabel; +} + +std::vector< uno::Reference< table::XCell > > SwXCellRange::GetCells() +{ + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + const sal_Int32 nRowCount(m_pImpl->GetRowCount()); + const sal_Int32 nColCount(m_pImpl->GetColumnCount()); + std::vector< uno::Reference< table::XCell > > vResult; + vResult.reserve(static_cast<size_t>(nRowCount)*static_cast<size_t>(nColCount)); + for(sal_Int32 nRow = 0; nRow < nRowCount; ++nRow) + for(sal_Int32 nCol = 0; nCol < nColCount; ++nCol) + vResult.emplace_back(lcl_CreateXCell(pFormat, m_pImpl->m_RangeDescriptor.nLeft + nCol, m_pImpl->m_RangeDescriptor.nTop + nRow)); + return vResult; +} + +uno::Reference<table::XCell> SAL_CALL +SwXCellRange::getCellByPosition(sal_Int32 nColumn, sal_Int32 nRow) +{ + SolarMutexGuard aGuard; + uno::Reference< table::XCell > aRet; + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + if(pFormat) + { + if(nColumn >= 0 && nRow >= 0 && + m_pImpl->GetColumnCount() > nColumn && m_pImpl->GetRowCount() > nRow ) + { + SwXCell* pXCell = lcl_CreateXCell(pFormat, + m_pImpl->m_RangeDescriptor.nLeft + nColumn, + m_pImpl->m_RangeDescriptor.nTop + nRow); + if(pXCell) + aRet = pXCell; + } + } + if(!aRet.is()) + throw lang::IndexOutOfBoundsException(); + return aRet; +} + +uno::Reference<table::XCellRange> SAL_CALL +SwXCellRange::getCellRangeByPosition( + sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom) +{ + SolarMutexGuard aGuard; + uno::Reference< table::XCellRange > aRet; + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + if (pFormat && m_pImpl->GetColumnCount() > nRight + && m_pImpl->GetRowCount() > nBottom && + nLeft <= nRight && nTop <= nBottom + && nLeft >= 0 && nRight >= 0 && nTop >= 0 && nBottom >= 0 ) + { + SwTable* pTable = SwTable::FindTable( pFormat ); + if(!pTable->IsTableComplex()) + { + SwRangeDescriptor aNewDesc; + aNewDesc.nTop = nTop + m_pImpl->m_RangeDescriptor.nTop; + aNewDesc.nBottom = nBottom + m_pImpl->m_RangeDescriptor.nTop; + aNewDesc.nLeft = nLeft + m_pImpl->m_RangeDescriptor.nLeft; + aNewDesc.nRight = nRight + m_pImpl->m_RangeDescriptor.nLeft; + aNewDesc.Normalize(); + const OUString sTLName = sw_GetCellName(aNewDesc.nLeft, aNewDesc.nTop); + const OUString sBRName = sw_GetCellName(aNewDesc.nRight, aNewDesc.nBottom); + const SwTableBox* pTLBox = pTable->GetTableBox( sTLName ); + if(pTLBox) + { + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor in the upper-left cell of the range + auto pUnoCursor(pFormat->GetDoc()->CreateUnoCursor(aPos, true)); + pUnoCursor->Move( fnMoveForward, GoInNode ); + pUnoCursor->SetRemainInSection( false ); + const SwTableBox* pBRBox = pTable->GetTableBox( sBRName ); + if(pBRBox) + { + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->nNode = *pBRBox->GetSttNd(); + pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rCursor = dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + rCursor.MakeBoxSels(); + // pUnoCursor will be provided and will not be deleted + aRet = SwXCellRange::CreateXCellRange(pUnoCursor, *pFormat, aNewDesc).get(); + } + } + } + } + if(!aRet.is()) + throw lang::IndexOutOfBoundsException(); + return aRet; +} + +uno::Reference<table::XCellRange> SAL_CALL +SwXCellRange::getCellRangeByName(const OUString& rRange) +{ + SolarMutexGuard aGuard; + sal_Int32 nPos = 0; + const OUString sTLName(rRange.getToken(0, ':', nPos)); + const OUString sBRName(rRange.getToken(0, ':', nPos)); + if(sTLName.isEmpty() || sBRName.isEmpty()) + throw uno::RuntimeException(); + SwRangeDescriptor aDesc; + aDesc.nTop = aDesc.nLeft = aDesc.nBottom = aDesc.nRight = -1; + SwXTextTable::GetCellPosition( sTLName, aDesc.nLeft, aDesc.nTop ); + SwXTextTable::GetCellPosition( sBRName, aDesc.nRight, aDesc.nBottom ); + aDesc.Normalize(); + return getCellRangeByPosition( + aDesc.nLeft - m_pImpl->m_RangeDescriptor.nLeft, + aDesc.nTop - m_pImpl->m_RangeDescriptor.nTop, + aDesc.nRight - m_pImpl->m_RangeDescriptor.nLeft, + aDesc.nBottom - m_pImpl->m_RangeDescriptor.nTop); +} + +uno::Reference< beans::XPropertySetInfo > SwXCellRange::getPropertySetInfo() +{ + static uno::Reference<beans::XPropertySetInfo> xRef = m_pImpl->m_pPropSet->getPropertySetInfo(); + return xRef; +} + +void SAL_CALL +SwXCellRange::setPropertyValue(const OUString& rPropertyName, const uno::Any& aValue) +{ + SolarMutexGuard aGuard; + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + if(pFormat) + { + const SfxItemPropertySimpleEntry *const pEntry = + m_pImpl->m_pPropSet->getPropertyMap().getByName(rPropertyName); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + if ( pEntry->nFlags & beans::PropertyAttribute::READONLY) + throw beans::PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + SwDoc *const pDoc = m_pImpl->m_pTableCursor->GetDoc(); + SwUnoTableCursor& rCursor(dynamic_cast<SwUnoTableCursor&>(*m_pImpl->m_pTableCursor)); + { + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + } + rCursor.MakeBoxSels(); + switch(pEntry->nWID ) + { + case FN_UNO_TABLE_CELL_BACKGROUND: + { + std::unique_ptr<SfxPoolItem> aBrush(std::make_unique<SvxBrushItem>(RES_BACKGROUND)); + SwDoc::GetBoxAttr(*m_pImpl->m_pTableCursor, aBrush); + aBrush->PutValue(aValue, pEntry->nMemberId); + pDoc->SetBoxAttr(*m_pImpl->m_pTableCursor, *aBrush); + + } + break; + case RES_BOX : + { + SfxItemSet aSet(pDoc->GetAttrPool(), + svl::Items<RES_BOX, RES_BOX, + SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER>{}); + SvxBoxInfoItem aBoxInfo( SID_ATTR_BORDER_INNER ); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::ALL, false); + SvxBoxInfoItemValidFlags nValid = SvxBoxInfoItemValidFlags::NONE; + switch(pEntry->nMemberId & ~CONVERT_TWIPS) + { + case LEFT_BORDER : nValid = SvxBoxInfoItemValidFlags::LEFT; break; + case RIGHT_BORDER: nValid = SvxBoxInfoItemValidFlags::RIGHT; break; + case TOP_BORDER : nValid = SvxBoxInfoItemValidFlags::TOP; break; + case BOTTOM_BORDER: nValid = SvxBoxInfoItemValidFlags::BOTTOM; break; + case LEFT_BORDER_DISTANCE : + case RIGHT_BORDER_DISTANCE: + case TOP_BORDER_DISTANCE : + case BOTTOM_BORDER_DISTANCE: + nValid = SvxBoxInfoItemValidFlags::DISTANCE; + break; + } + aBoxInfo.SetValid(nValid); + + aSet.Put(aBoxInfo); + SwDoc::GetTabBorders(rCursor, aSet); + + aSet.Put(aBoxInfo); + SvxBoxItem aBoxItem(aSet.Get(RES_BOX)); + static_cast<SfxPoolItem&>(aBoxItem).PutValue(aValue, pEntry->nMemberId); + aSet.Put(aBoxItem); + pDoc->SetTabBorders(*m_pImpl->m_pTableCursor, aSet); + } + break; + case RES_BOXATR_FORMAT: + { + SfxUInt32Item aNumberFormat(RES_BOXATR_FORMAT); + static_cast<SfxPoolItem&>(aNumberFormat).PutValue(aValue, 0); + pDoc->SetBoxAttr(rCursor, aNumberFormat); + } + break; + case FN_UNO_RANGE_ROW_LABEL: + { + bool bTmp = *o3tl::doAccess<bool>(aValue); + if (m_pImpl->m_bFirstRowAsLabel != bTmp) + { + lcl_SendChartEvent(*this, m_pImpl->m_ChartListeners); + m_pImpl->m_bFirstRowAsLabel = bTmp; + } + } + break; + case FN_UNO_RANGE_COL_LABEL: + { + bool bTmp = *o3tl::doAccess<bool>(aValue); + if (m_pImpl->m_bFirstColumnAsLabel != bTmp) + { + lcl_SendChartEvent(*this, m_pImpl->m_ChartListeners); + m_pImpl->m_bFirstColumnAsLabel = bTmp; + } + } + break; + case RES_VERT_ORIENT: + { + sal_Int16 nAlign = -1; + aValue >>= nAlign; + if( nAlign >= text::VertOrientation::NONE && nAlign <= text::VertOrientation::BOTTOM) + pDoc->SetBoxAlign( rCursor, nAlign ); + } + break; + default: + { + SfxItemSet aItemSet( pDoc->GetAttrPool(), {{pEntry->nWID, pEntry->nWID}} ); + SwUnoCursorHelper::GetCursorAttr(rCursor.GetSelRing(), + aItemSet); + + if (!SwUnoCursorHelper::SetCursorPropertyValue( + *pEntry, aValue, rCursor.GetSelRing(), aItemSet)) + { + m_pImpl->m_pPropSet->setPropertyValue(*pEntry, aValue, aItemSet); + } + SwUnoCursorHelper::SetCursorAttr(rCursor.GetSelRing(), + aItemSet, SetAttrMode::DEFAULT, true); + } + } + + } +} + +uno::Any SAL_CALL SwXCellRange::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + uno::Any aRet; + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + if(pFormat) + { + const SfxItemPropertySimpleEntry *const pEntry = + m_pImpl->m_pPropSet->getPropertyMap().getByName(rPropertyName); + if(!pEntry) + throw beans::UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); + + switch(pEntry->nWID ) + { + case FN_UNO_TABLE_CELL_BACKGROUND: + { + std::unique_ptr<SfxPoolItem> aBrush(std::make_unique<SvxBrushItem>(RES_BACKGROUND)); + if (SwDoc::GetBoxAttr(*m_pImpl->m_pTableCursor, aBrush)) + aBrush->QueryValue(aRet, pEntry->nMemberId); + + } + break; + case RES_BOX : + { + SwDoc *const pDoc = m_pImpl->m_pTableCursor->GetDoc(); + SfxItemSet aSet(pDoc->GetAttrPool(), + svl::Items<RES_BOX, RES_BOX, + SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER>{}); + aSet.Put(SvxBoxInfoItem( SID_ATTR_BORDER_INNER )); + SwDoc::GetTabBorders(*m_pImpl->m_pTableCursor, aSet); + const SvxBoxItem& rBoxItem = aSet.Get(RES_BOX); + rBoxItem.QueryValue(aRet, pEntry->nMemberId); + } + break; + case RES_BOXATR_FORMAT: + OSL_FAIL("not implemented"); + break; + case FN_UNO_PARA_STYLE: + { + SwFormatColl *const pTmpFormat = + SwUnoCursorHelper::GetCurTextFormatColl(*m_pImpl->m_pTableCursor, false); + OUString sRet; + if (pTmpFormat) + sRet = pTmpFormat->GetName(); + aRet <<= sRet; + } + break; + case FN_UNO_RANGE_ROW_LABEL: + aRet <<= m_pImpl->m_bFirstRowAsLabel; + break; + case FN_UNO_RANGE_COL_LABEL: + aRet <<= m_pImpl->m_bFirstColumnAsLabel; + break; + case RES_VERT_ORIENT: + { + std::unique_ptr<SfxPoolItem> aVertOrient( + std::make_unique<SwFormatVertOrient>(RES_VERT_ORIENT)); + if (SwDoc::GetBoxAttr(*m_pImpl->m_pTableCursor, aVertOrient)) + { + aVertOrient->QueryValue( aRet, pEntry->nMemberId ); + } + } + break; + default: + { + SfxItemSet aSet( + m_pImpl->m_pTableCursor->GetDoc()->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_FRMATR_END - 1, + RES_UNKNOWNATR_CONTAINER, + RES_UNKNOWNATR_CONTAINER>{}); + // first look at the attributes of the cursor + SwUnoTableCursor *const pCursor = + dynamic_cast<SwUnoTableCursor*>(&(*m_pImpl->m_pTableCursor)); + SwUnoCursorHelper::GetCursorAttr(pCursor->GetSelRing(), aSet); + m_pImpl->m_pPropSet->getPropertyValue(*pEntry, aSet, aRet); + } + } + + } + return aRet; +} + +void SwXCellRange::addPropertyChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +void SwXCellRange::removePropertyChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +void SwXCellRange::addVetoableChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +void SwXCellRange::removeVetoableChangeListener(const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +///@see SwXCellRange::getData +uno::Sequence<uno::Sequence<uno::Any>> SAL_CALL SwXCellRange::getDataArray() +{ + SolarMutexGuard aGuard; + const sal_Int32 nRowCount = m_pImpl->GetRowCount(); + const sal_Int32 nColCount = m_pImpl->GetColumnCount(); + if(!nRowCount || !nColCount) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + lcl_EnsureCoreConnected(m_pImpl->GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + uno::Sequence< uno::Sequence< uno::Any > > aRowSeq(nRowCount); + auto vCells(GetCells()); + auto pCurrentCell(vCells.begin()); + for(auto& rRow : aRowSeq) + { + rRow = uno::Sequence< uno::Any >(nColCount); + for(auto& rCellAny : rRow) + { + auto pCell(static_cast<SwXCell*>(pCurrentCell->get())); + if(!pCell) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + rCellAny = pCell->GetAny(); + ++pCurrentCell; + } + } + return aRowSeq; +} + +///@see SwXCellRange::setData +void SAL_CALL SwXCellRange::setDataArray(const uno::Sequence< uno::Sequence< uno::Any > >& rArray) +{ + SolarMutexGuard aGuard; + const sal_Int32 nRowCount = m_pImpl->GetRowCount(); + const sal_Int32 nColCount = m_pImpl->GetColumnCount(); + if(!nRowCount || !nColCount) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + if(!pFormat) + return; + if(rArray.getLength() != nRowCount) + throw uno::RuntimeException("Row count mismatch. expected: " + OUString::number(nRowCount) + " got: " + OUString::number(rArray.getLength()), static_cast<cppu::OWeakObject*>(this)); + auto vCells(GetCells()); + auto pCurrentCell(vCells.begin()); + for(const auto& rColSeq : rArray) + { + if(rColSeq.getLength() != nColCount) + throw uno::RuntimeException("Column count mismatch. expected: " + OUString::number(nColCount) + " got: " + OUString::number(rColSeq.getLength()), static_cast<cppu::OWeakObject*>(this)); + for(const auto& aValue : rColSeq) + { + auto pCell(static_cast<SwXCell*>(pCurrentCell->get())); + if(!pCell || !pCell->GetTableBox()) + throw uno::RuntimeException("Box for cell missing", static_cast<cppu::OWeakObject*>(this)); + if(aValue.isExtractableTo(cppu::UnoType<OUString>::get())) + sw_setString(*pCell, aValue.get<OUString>()); + else if(aValue.isExtractableTo(cppu::UnoType<double>::get())) + sw_setValue(*pCell, aValue.get<double>()); + else + sw_setString(*pCell, OUString(), true); + ++pCurrentCell; + } + } +} + +uno::Sequence<uno::Sequence<double>> SAL_CALL +SwXCellRange::getData() +{ + SolarMutexGuard aGuard; + const sal_Int32 nRowCount = m_pImpl->GetRowCount(); + const sal_Int32 nColCount = m_pImpl->GetColumnCount(); + if(!nRowCount || !nColCount) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + if (m_pImpl->m_bFirstColumnAsLabel || m_pImpl->m_bFirstRowAsLabel) + { + uno::Reference<chart::XChartDataArray> const xDataRange( + getCellRangeByPosition((m_pImpl->m_bFirstColumnAsLabel) ? 1 : 0, + (m_pImpl->m_bFirstRowAsLabel) ? 1 : 0, + nColCount-1, nRowCount-1), uno::UNO_QUERY_THROW); + return xDataRange->getData(); + } + uno::Sequence< uno::Sequence< double > > vRows(nRowCount); + auto vCells(GetCells()); + auto pCurrentCell(vCells.begin()); + for(auto& rRow : vRows) + { + rRow = uno::Sequence<double>(nColCount); + for(auto& rValue : rRow) + { + if(!(*pCurrentCell)) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + rValue = (*pCurrentCell)->getValue(); + ++pCurrentCell; + } + } + return vRows; +} + +void SAL_CALL +SwXCellRange::setData(const uno::Sequence< uno::Sequence<double> >& rData) +{ + SolarMutexGuard aGuard; + const sal_Int32 nRowCount = m_pImpl->GetRowCount(); + const sal_Int32 nColCount = m_pImpl->GetColumnCount(); + if(!nRowCount || !nColCount) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + if (m_pImpl->m_bFirstColumnAsLabel || m_pImpl->m_bFirstRowAsLabel) + { + uno::Reference<chart::XChartDataArray> const xDataRange( + getCellRangeByPosition((m_pImpl->m_bFirstColumnAsLabel) ? 1 : 0, + (m_pImpl->m_bFirstRowAsLabel) ? 1 : 0, + nColCount-1, nRowCount-1), uno::UNO_QUERY_THROW); + return xDataRange->setData(rData); + } + lcl_EnsureCoreConnected(m_pImpl->GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + if(rData.getLength() != nRowCount) + throw uno::RuntimeException("Row count mismatch. expected: " + OUString::number(nRowCount) + " got: " + OUString::number(rData.getLength()), static_cast<cppu::OWeakObject*>(this)); + auto vCells(GetCells()); + auto pCurrentCell(vCells.begin()); + for(const auto& rRow : rData) + { + if(rRow.getLength() != nColCount) + throw uno::RuntimeException("Column count mismatch. expected: " + OUString::number(nColCount) + " got: " + OUString::number(rRow.getLength()), static_cast<cppu::OWeakObject*>(this)); + for(const auto& rValue : rRow) + { + uno::Reference<table::XCell>(*pCurrentCell, uno::UNO_SET_THROW)->setValue(rValue); + ++pCurrentCell; + } + } +} + +std::tuple<sal_uInt32, sal_uInt32, sal_uInt32, sal_uInt32> +SwXCellRange::Impl::GetLabelCoordinates(bool bRow) +{ + sal_uInt32 nLeft, nTop, nRight, nBottom; + nLeft = nTop = nRight = nBottom = 0; + if(bRow) + { + nTop = m_bFirstRowAsLabel ? 1 : 0; + nBottom = GetRowCount() - 1; + } + else + { + nLeft = m_bFirstColumnAsLabel ? 1 : 0; + nRight = GetColumnCount() - 1; + } + return std::make_tuple(nLeft, nTop, nRight, nBottom); +} + +uno::Sequence<OUString> +SwXCellRange::Impl::GetLabelDescriptions(SwXCellRange & rThis, bool bRow) +{ + SolarMutexGuard aGuard; + sal_uInt32 nLeft, nTop, nRight, nBottom; + std::tie(nLeft, nTop, nRight, nBottom) = GetLabelCoordinates(bRow); + if(!nRight && !nBottom) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(&rThis)); + lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(&rThis)); + if (!(bRow ? m_bFirstColumnAsLabel : m_bFirstRowAsLabel)) + return {}; // without labels we have no descriptions + auto xLabelRange(rThis.getCellRangeByPosition(nLeft, nTop, nRight, nBottom)); + auto vCells(static_cast<SwXCellRange*>(xLabelRange.get())->GetCells()); + uno::Sequence<OUString> vResult(vCells.size()); + std::transform(vCells.begin(), vCells.end(), vResult.begin(), + [](uno::Reference<table::XCell> xCell) -> OUString { return uno::Reference<text::XText>(xCell, uno::UNO_QUERY_THROW)->getString(); }); + return vResult; +} + +uno::Sequence<OUString> SAL_CALL SwXCellRange::getRowDescriptions() +{ + return m_pImpl->GetLabelDescriptions(*this, true); +} + +uno::Sequence<OUString> SAL_CALL SwXCellRange::getColumnDescriptions() +{ + return m_pImpl->GetLabelDescriptions(*this, false); +} + +void SwXCellRange::Impl::SetLabelDescriptions(SwXCellRange & rThis, + const uno::Sequence<OUString>& rDesc, bool bRow) +{ + SolarMutexGuard aGuard; + lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(&rThis)); + if (!(bRow ? m_bFirstColumnAsLabel : m_bFirstRowAsLabel)) + return; // if there are no labels we cannot set descriptions + sal_uInt32 nLeft, nTop, nRight, nBottom; + std::tie(nLeft, nTop, nRight, nBottom) = GetLabelCoordinates(bRow); + if(!nRight && !nBottom) + throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(&rThis)); + auto xLabelRange(rThis.getCellRangeByPosition(nLeft, nTop, nRight, nBottom)); + if (!xLabelRange.is()) + throw uno::RuntimeException("Missing Cell Range", static_cast<cppu::OWeakObject*>(&rThis)); + auto vCells(static_cast<SwXCellRange*>(xLabelRange.get())->GetCells()); + if (sal::static_int_cast<sal_uInt32>(rDesc.getLength()) != vCells.size()) + throw uno::RuntimeException("Too few or too many descriptions", static_cast<cppu::OWeakObject*>(&rThis)); + auto pDescIterator(rDesc.begin()); + for(auto& xCell : vCells) + uno::Reference<text::XText>(xCell, uno::UNO_QUERY_THROW)->setString(*pDescIterator++); +} + +void SAL_CALL SwXCellRange::setRowDescriptions( + const uno::Sequence<OUString>& rRowDesc) +{ + m_pImpl->SetLabelDescriptions(*this, rRowDesc, true); +} + +void SAL_CALL SwXCellRange::setColumnDescriptions( + const uno::Sequence<OUString>& rColumnDesc) +{ + m_pImpl->SetLabelDescriptions(*this, rColumnDesc, false); +} + +void SAL_CALL SwXCellRange::addChartDataChangeEventListener( + const uno::Reference<chart::XChartDataChangeEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_ChartListeners.addInterface(xListener); +} + +void SAL_CALL SwXCellRange::removeChartDataChangeEventListener( + const uno::Reference<chart::XChartDataChangeEventListener> & xListener) +{ + // no need to lock here as m_pImpl is const and container threadsafe + m_pImpl->m_ChartListeners.removeInterface(xListener); +} + +sal_Bool SwXCellRange::isNotANumber(double /*fNumber*/) + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +double SwXCellRange::getNotANumber() + { throw uno::RuntimeException("Not implemented", static_cast<cppu::OWeakObject*>(this)); } + +uno::Sequence< beans::PropertyValue > SwXCellRange::createSortDescriptor() +{ + SolarMutexGuard aGuard; + return SwUnoCursorHelper::CreateSortDescriptor(true); +} + +void SAL_CALL SwXCellRange::sort(const uno::Sequence< beans::PropertyValue >& rDescriptor) +{ + SolarMutexGuard aGuard; + SwSortOptions aSortOpt; + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + if(pFormat && SwUnoCursorHelper::ConvertSortProperties(rDescriptor, aSortOpt)) + { + SwUnoTableCursor& rTableCursor = dynamic_cast<SwUnoTableCursor&>(*m_pImpl->m_pTableCursor); + rTableCursor.MakeBoxSels(); + UnoActionContext aContext(pFormat->GetDoc()); + pFormat->GetDoc()->SortTable(rTableCursor.GetSelectedBoxes(), aSortOpt); + } +} + +sal_Int32 SwXCellRange::Impl::GetColumnCount() const +{ + return m_RangeDescriptor.nRight - m_RangeDescriptor.nLeft + 1; +} + +sal_Int32 SwXCellRange::Impl::GetRowCount() const +{ + return m_RangeDescriptor.nBottom - m_RangeDescriptor.nTop + 1; +} + +const SwUnoCursor* SwXCellRange::GetTableCursor() const +{ + SwFrameFormat *const pFormat = m_pImpl->GetFrameFormat(); + return pFormat ? &(*m_pImpl->m_pTableCursor) : nullptr; +} + +void SwXCellRange::Impl::Notify( const SfxHint& rHint ) +{ + uno::Reference<uno::XInterface> const xThis(m_wThis); + if(rHint.GetId() == SfxHintId::Dying) + { + m_pFrameFormat = nullptr; + m_pTableCursor.reset(nullptr); + } + if (xThis.is()) + { // fdo#72695: if UNO object is already dead, don't revive it with event + if(m_pFrameFormat) + lcl_SendChartEvent(xThis.get(), m_ChartListeners); + else + m_ChartListeners.disposeAndClear(lang::EventObject(xThis)); + } +} + +class SwXTableRows::Impl : public SvtListener +{ +private: + SwFrameFormat* m_pFrameFormat; + +public: + explicit Impl(SwFrameFormat& rFrameFormat) : m_pFrameFormat(&rFrameFormat) + { + StartListening(rFrameFormat.GetNotifier()); + } + SwFrameFormat* GetFrameFormat() { return m_pFrameFormat; } + virtual void Notify(const SfxHint&) override; +}; + +// SwXTableRows + +OUString SwXTableRows::getImplementationName() + { return "SwXTableRows"; } + +sal_Bool SwXTableRows::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +uno::Sequence< OUString > SwXTableRows::getSupportedServiceNames() + { return { "com.sun.star.text.TableRows" }; } + + +SwXTableRows::SwXTableRows(SwFrameFormat& rFrameFormat) : + m_pImpl(new SwXTableRows::Impl(rFrameFormat)) +{ } + +SwXTableRows::~SwXTableRows() +{ } + +SwFrameFormat* SwXTableRows::GetFrameFormat() +{ + return m_pImpl->GetFrameFormat(); +} + +sal_Int32 SwXTableRows::getCount() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFrameFormat = GetFrameFormat(); + if(!pFrameFormat) + throw uno::RuntimeException(); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + return pTable->GetTabLines().size(); +} + +///@see SwXCell::CreateXCell (TODO: seems to be copy and paste programming here) +uno::Any SwXTableRows::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFrameFormat(lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this))); + if(nIndex < 0) + throw lang::IndexOutOfBoundsException(); + SwTable* pTable = SwTable::FindTable( pFrameFormat ); + if(o3tl::make_unsigned(nIndex) >= pTable->GetTabLines().size()) + throw lang::IndexOutOfBoundsException(); + SwTableLine* pLine = pTable->GetTabLines()[nIndex]; + FindUnoInstanceHint<SwTableLine,SwXTextTableRow> aHint{pLine}; + pFrameFormat->GetNotifier().Broadcast(aHint); + if(!aHint.m_pResult) + aHint.m_pResult = new SwXTextTableRow(pFrameFormat, pLine); + uno::Reference<beans::XPropertySet> xRet = static_cast<beans::XPropertySet*>(aHint.m_pResult); + return uno::makeAny(xRet); +} + +uno::Type SAL_CALL SwXTableRows::getElementType() +{ + return cppu::UnoType<beans::XPropertySet>::get(); +} + +sal_Bool SwXTableRows::hasElements() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFrameFormat = GetFrameFormat(); + if(!pFrameFormat) + throw uno::RuntimeException(); + // a table always has rows + return true; +} + +void SwXTableRows::insertByIndex(sal_Int32 nIndex, sal_Int32 nCount) +{ + SolarMutexGuard aGuard; + if (nCount == 0) + return; + SwFrameFormat* pFrameFormat(lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this))); + SwTable* pTable = lcl_EnsureTableNotComplex(SwTable::FindTable(pFrameFormat), static_cast<cppu::OWeakObject*>(this)); + const size_t nRowCount = pTable->GetTabLines().size(); + if (nCount <= 0 || !(0 <= nIndex && o3tl::make_unsigned(nIndex) <= nRowCount)) + throw uno::RuntimeException("Illegal arguments", static_cast<cppu::OWeakObject*>(this)); + const OUString sTLName = sw_GetCellName(0, nIndex); + const SwTableBox* pTLBox = pTable->GetTableBox(sTLName); + bool bAppend = false; + if(!pTLBox) + { + bAppend = true; + // to append at the end the cursor must be in the last line + SwTableLines& rLines = pTable->GetTabLines(); + SwTableLine* pLine = rLines.back(); + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + pTLBox = rBoxes.front(); + } + if(!pTLBox) + throw uno::RuntimeException("Illegal arguments", static_cast<cppu::OWeakObject*>(this)); + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor to the upper-left cell of the range + UnoActionContext aAction(pFrameFormat->GetDoc()); + std::shared_ptr<SwUnoTableCursor> const pUnoCursor( + std::dynamic_pointer_cast<SwUnoTableCursor>( + pFrameFormat->GetDoc()->CreateUnoCursor(aPos, true))); + pUnoCursor->Move( fnMoveForward, GoInNode ); + { + // remove actions - TODO: why? + UnoActionRemoveContext aRemoveContext(pUnoCursor->GetDoc()); + } + pFrameFormat->GetDoc()->InsertRow(*pUnoCursor, static_cast<sal_uInt16>(nCount), bAppend); +} + +void SwXTableRows::removeByIndex(sal_Int32 nIndex, sal_Int32 nCount) +{ + SolarMutexGuard aGuard; + if (nCount == 0) + return; + SwFrameFormat* pFrameFormat(lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this))); + if(nIndex < 0 || nCount <=0 ) + throw uno::RuntimeException(); + SwTable* pTable = lcl_EnsureTableNotComplex(SwTable::FindTable(pFrameFormat), static_cast<cppu::OWeakObject*>(this)); + OUString sTLName = sw_GetCellName(0, nIndex); + const SwTableBox* pTLBox = pTable->GetTableBox(sTLName); + if(!pTLBox) + throw uno::RuntimeException("Illegal arguments", static_cast<cppu::OWeakObject*>(this)); + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor to the upper-left cell of the range + auto pUnoCursor(pFrameFormat->GetDoc()->CreateUnoCursor(aPos, true)); + pUnoCursor->Move(fnMoveForward, GoInNode); + pUnoCursor->SetRemainInSection( false ); + const OUString sBLName = sw_GetCellName(0, nIndex + nCount - 1); + const SwTableBox* pBLBox = pTable->GetTableBox( sBLName ); + if(!pBLBox) + throw uno::RuntimeException("Illegal arguments", static_cast<cppu::OWeakObject*>(this)); + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->nNode = *pBLBox->GetSttNd(); + pUnoCursor->Move(fnMoveForward, GoInNode); + SwUnoTableCursor& rCursor = dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + { + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + } + rCursor.MakeBoxSels(); + { // these braces are important + UnoActionContext aAction(pFrameFormat->GetDoc()); + pFrameFormat->GetDoc()->DeleteRow(*pUnoCursor); + pUnoCursor.reset(); + } + { + // invalidate all actions - TODO: why? + UnoActionRemoveContext aRemoveContext(pFrameFormat->GetDoc()); + } +} + +void SwXTableRows::Impl::Notify( const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFrameFormat = nullptr; +} + +// SwXTableColumns + +class SwXTableColumns::Impl : public SvtListener +{ + SwFrameFormat* m_pFrameFormat; + public: + explicit Impl(SwFrameFormat& rFrameFormat) : m_pFrameFormat(&rFrameFormat) + { + StartListening(rFrameFormat.GetNotifier()); + } + SwFrameFormat* GetFrameFormat() { return m_pFrameFormat; } + virtual void Notify(const SfxHint&) override; +}; + +OUString SwXTableColumns::getImplementationName() + { return "SwXTableColumns"; } + +sal_Bool SwXTableColumns::supportsService(const OUString& rServiceName) + { return cppu::supportsService(this, rServiceName); } + +uno::Sequence< OUString > SwXTableColumns::getSupportedServiceNames() + { return { "com.sun.star.text.TableColumns"}; } + + +SwXTableColumns::SwXTableColumns(SwFrameFormat& rFrameFormat) : + m_pImpl(new SwXTableColumns::Impl(rFrameFormat)) +{ } + +SwXTableColumns::~SwXTableColumns() +{ } + +SwFrameFormat* SwXTableColumns::GetFrameFormat() const +{ + return m_pImpl->GetFrameFormat(); +} + +sal_Int32 SwXTableColumns::getCount() +{ + SolarMutexGuard aGuard; + SwFrameFormat* pFrameFormat(lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this))); + SwTable* pTable = SwTable::FindTable( pFrameFormat ); +// if(!pTable->IsTableComplex()) +// throw uno::RuntimeException("Table too complex", static_cast<cppu::OWeakObject*>(this)); + SwTableLines& rLines = pTable->GetTabLines(); + SwTableLine* pLine = rLines.front(); + return pLine->GetTabBoxes().size(); +} + +uno::Any SwXTableColumns::getByIndex(sal_Int32 nIndex) +{ + SolarMutexGuard aGuard; + if(nIndex < 0 || getCount() <= nIndex) + throw lang::IndexOutOfBoundsException(); + return uno::makeAny(uno::Reference<uno::XInterface>()); // i#21699 not supported +} + +uno::Type SAL_CALL SwXTableColumns::getElementType() +{ + return cppu::UnoType<uno::XInterface>::get(); +} + +sal_Bool SwXTableColumns::hasElements() +{ + SolarMutexGuard aGuard; + lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this)); + return true; +} + +///@see SwXTableRows::insertByIndex (TODO: seems to be copy and paste programming here) +void SwXTableColumns::insertByIndex(sal_Int32 nIndex, sal_Int32 nCount) +{ + SolarMutexGuard aGuard; + if (nCount == 0) + return; + SwFrameFormat* pFrameFormat(lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this))); + SwTable* pTable = lcl_EnsureTableNotComplex(SwTable::FindTable(pFrameFormat), static_cast<cppu::OWeakObject*>(this)); + SwTableLines& rLines = pTable->GetTabLines(); + SwTableLine* pLine = rLines.front(); + const size_t nColCount = pLine->GetTabBoxes().size(); + if (nCount <= 0 || !(0 <= nIndex && o3tl::make_unsigned(nIndex) <= nColCount)) + throw uno::RuntimeException("Illegal arguments", static_cast<cppu::OWeakObject*>(this)); + const OUString sTLName = sw_GetCellName(nIndex, 0); + const SwTableBox* pTLBox = pTable->GetTableBox( sTLName ); + bool bAppend = false; + if(!pTLBox) + { + bAppend = true; + // to append at the end the cursor must be in the last line + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + pTLBox = rBoxes.back(); + } + if(!pTLBox) + throw uno::RuntimeException("Illegal arguments", static_cast<cppu::OWeakObject*>(this)); + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + UnoActionContext aAction(pFrameFormat->GetDoc()); + auto pUnoCursor(pFrameFormat->GetDoc()->CreateUnoCursor(aPos, true)); + pUnoCursor->Move(fnMoveForward, GoInNode); + + { + // remove actions - TODO: why? + UnoActionRemoveContext aRemoveContext(pUnoCursor->GetDoc()); + } + + pFrameFormat->GetDoc()->InsertCol(*pUnoCursor, static_cast<sal_uInt16>(nCount), bAppend); +} + +///@see SwXTableRows::removeByIndex (TODO: seems to be copy and paste programming here) +void SwXTableColumns::removeByIndex(sal_Int32 nIndex, sal_Int32 nCount) +{ + SolarMutexGuard aGuard; + if (nCount == 0) + return; + SwFrameFormat* pFrameFormat(lcl_EnsureCoreConnected(GetFrameFormat(), static_cast<cppu::OWeakObject*>(this))); + if(nIndex < 0 || nCount <=0 ) + throw uno::RuntimeException(); + SwTable* pTable = lcl_EnsureTableNotComplex(SwTable::FindTable(pFrameFormat), static_cast<cppu::OWeakObject*>(this)); + const OUString sTLName = sw_GetCellName(nIndex, 0); + const SwTableBox* pTLBox = pTable->GetTableBox( sTLName ); + if(!pTLBox) + throw uno::RuntimeException("Cell not found", static_cast<cppu::OWeakObject*>(this)); + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + // set cursor to the upper-left cell of the range + auto pUnoCursor(pFrameFormat->GetDoc()->CreateUnoCursor(aPos, true)); + pUnoCursor->Move(fnMoveForward, GoInNode); + pUnoCursor->SetRemainInSection(false); + const OUString sTRName = sw_GetCellName(nIndex + nCount - 1, 0); + const SwTableBox* pTRBox = pTable->GetTableBox(sTRName); + if(!pTRBox) + throw uno::RuntimeException("Cell not found", static_cast<cppu::OWeakObject*>(this)); + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->nNode = *pTRBox->GetSttNd(); + pUnoCursor->Move(fnMoveForward, GoInNode); + SwUnoTableCursor& rCursor = dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + { + // HACK: remove pending actions for selecting old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + } + rCursor.MakeBoxSels(); + { // these braces are important + UnoActionContext aAction(pFrameFormat->GetDoc()); + pFrameFormat->GetDoc()->DeleteCol(*pUnoCursor); + pUnoCursor.reset(); + } + { + // invalidate all actions - TODO: why? + UnoActionRemoveContext aRemoveContext(pFrameFormat->GetDoc()); + } +} + +void SwXTableColumns::Impl::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFrameFormat = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx new file mode 100644 index 000000000..394e8ae7d --- /dev/null +++ b/sw/source/core/unocore/unotext.cxx @@ -0,0 +1,2717 @@ +/* -*- 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 <stdlib.h> + +#include <memory> +#include <set> + +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/text/ControlCharacter.hpp> +#include <com/sun/star/text/TableColumnSeparator.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> + +#include <svl/listener.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/profilezone.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> + +#include <cmdid.h> +#include <unotextbodyhf.hxx> +#include <unotext.hxx> +#include <unotextrange.hxx> +#include <unotextcursor.hxx> +#include <unosection.hxx> +#include <unobookmark.hxx> +#include <unorefmark.hxx> +#include <unoport.hxx> +#include <unotbl.hxx> +#include <unoidx.hxx> +#include <unocoll.hxx> +#include <unoframe.hxx> +#include <unofield.hxx> +#include <unometa.hxx> +#include <unomap.hxx> +#include <unoprnms.hxx> +#include <unoparagraph.hxx> +#include <unocrsrhelper.hxx> +#include <docary.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <redline.hxx> +#include <swundo.hxx> +#include <section.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <ndtxt.hxx> +#include <SwRewriter.hxx> +#include <strings.hrc> +#include <frameformats.hxx> + +using namespace ::com::sun::star; + +const char cInvalidObject[] = "this object is invalid"; + +class SwXText::Impl +{ + +public: + SwXText & m_rThis; + SfxItemPropertySet const& m_rPropSet; + const CursorType m_eType; + SwDoc * m_pDoc; + bool m_bIsValid; + + Impl( SwXText & rThis, + SwDoc *const pDoc, const CursorType eType) + : m_rThis(rThis) + , m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT)) + , m_eType(eType) + , m_pDoc(pDoc) + , m_bIsValid(nullptr != pDoc) + { + } + + /// @throws lang::IllegalArgumentException + /// @throws uno::RuntimeException + uno::Reference< text::XTextRange > + finishOrAppendParagraph( + const uno::Sequence< beans::PropertyValue >& + rCharacterAndParagraphProperties, + const uno::Reference< text::XTextRange >& xInsertPosition); + + /// @throws lang::IllegalArgumentException + /// @throws uno::RuntimeException + sal_Int16 ComparePositions( + const uno::Reference<text::XTextRange>& xPos1, + const uno::Reference<text::XTextRange>& xPos2); + + /// @throws lang::IllegalArgumentException + /// @throws uno::RuntimeException + bool CheckForOwnMember(const SwPaM & rPaM); + + void ConvertCell( + const uno::Sequence< uno::Reference< text::XTextRange > > & rCell, + std::vector<SwNodeRange> & rRowNodes, + SwNodeRange *const pLastCell); + +}; + +SwXText::SwXText(SwDoc *const pDoc, const CursorType eType) + : m_pImpl( new SwXText::Impl(*this, pDoc, eType) ) +{ +} + +SwXText::~SwXText() +{ +} + +const SwDoc * SwXText::GetDoc() const +{ + return m_pImpl->m_pDoc; +} + SwDoc * SwXText::GetDoc() +{ + return m_pImpl->m_pDoc; +} + +bool SwXText::IsValid() const +{ + return m_pImpl->m_bIsValid; +} + +void SwXText::Invalidate() +{ + m_pImpl->m_bIsValid = false; +} + +void SwXText::SetDoc(SwDoc *const pDoc) +{ + OSL_ENSURE(!m_pImpl->m_pDoc || !pDoc, + "SwXText::SetDoc: already have a doc?"); + m_pImpl->m_pDoc = pDoc; + m_pImpl->m_bIsValid = (nullptr != pDoc); +} + +void +SwXText::PrepareForAttach(uno::Reference< text::XTextRange > &, const SwPaM &) +{ +} + +bool SwXText::CheckForOwnMemberMeta(const SwPaM &, const bool) +{ + OSL_ENSURE(CursorType::Meta != m_pImpl->m_eType, "should not be called!"); + return false; +} + +const SwStartNode *SwXText::GetStartNode() const +{ + return GetDoc()->GetNodes().GetEndOfContent().StartOfSectionNode(); +} + +uno::Reference< text::XTextCursor > +SwXText::CreateCursor() +{ + uno::Reference< text::XTextCursor > xRet; + if(IsValid()) + { + SwNode& rNode = GetDoc()->GetNodes().GetEndOfContent(); + SwPosition aPos(rNode); + xRet = static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), this, m_pImpl->m_eType, aPos)); + xRet->gotoStart(false); + } + return xRet; +} + +uno::Any SAL_CALL +SwXText::queryInterface(const uno::Type& rType) +{ + uno::Any aRet; + if (rType == cppu::UnoType<text::XText>::get()) + { + aRet <<= uno::Reference< text::XText >(this); + } + else if (rType == cppu::UnoType<text::XSimpleText>::get()) + { + aRet <<= uno::Reference< text::XSimpleText >(this); + } + else if (rType == cppu::UnoType<text::XTextRange>::get()) + { + aRet <<= uno::Reference< text::XTextRange>(this); + } + else if (rType == cppu::UnoType<text::XTextRangeCompare>::get()) + { + aRet <<= uno::Reference< text::XTextRangeCompare >(this); + } + else if (rType == cppu::UnoType<lang::XTypeProvider>::get()) + { + aRet <<= uno::Reference< lang::XTypeProvider >(this); + } + else if (rType == cppu::UnoType<text::XRelativeTextContentInsert>::get()) + { + aRet <<= uno::Reference< text::XRelativeTextContentInsert >(this); + } + else if (rType == cppu::UnoType<text::XRelativeTextContentRemove>::get()) + { + aRet <<= uno::Reference< text::XRelativeTextContentRemove >(this); + } + else if (rType == cppu::UnoType<beans::XPropertySet>::get()) + { + aRet <<= uno::Reference< beans::XPropertySet >(this); + } + else if (rType == cppu::UnoType<lang::XUnoTunnel>::get()) + { + aRet <<= uno::Reference< lang::XUnoTunnel >(this); + } + else if (rType == cppu::UnoType<text::XTextAppendAndConvert>::get()) + { + aRet <<= uno::Reference< text::XTextAppendAndConvert >(this); + } + else if (rType == cppu::UnoType<text::XTextAppend>::get()) + { + aRet <<= uno::Reference< text::XTextAppend >(this); + } + else if (rType == cppu::UnoType<text::XTextPortionAppend>::get()) + { + aRet <<= uno::Reference< text::XTextPortionAppend >(this); + } + else if (rType == cppu::UnoType<text::XParagraphAppend>::get()) + { + aRet <<= uno::Reference< text::XParagraphAppend >(this); + } + else if (rType == cppu::UnoType<text::XTextConvert>::get() ) + { + aRet <<= uno::Reference< text::XTextConvert >(this); + } + else if (rType == cppu::UnoType<text::XTextContentAppend>::get()) + { + aRet <<= uno::Reference< text::XTextContentAppend >(this); + } + else if(rType == cppu::UnoType<text::XTextCopy>::get()) + { + aRet <<= uno::Reference< text::XTextCopy >( this ); + } + return aRet; +} + +uno::Sequence< uno::Type > SAL_CALL +SwXText::getTypes() +{ + static const uno::Sequence< uno::Type > aTypes { + cppu::UnoType<text::XText>::get(), + cppu::UnoType<text::XTextRangeCompare>::get(), + cppu::UnoType<text::XRelativeTextContentInsert>::get(), + cppu::UnoType<text::XRelativeTextContentRemove>::get(), + cppu::UnoType<lang::XUnoTunnel>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<text::XTextPortionAppend>::get(), + cppu::UnoType<text::XParagraphAppend>::get(), + cppu::UnoType<text::XTextContentAppend>::get(), + cppu::UnoType<text::XTextConvert>::get(), + cppu::UnoType<text::XTextAppend>::get(), + cppu::UnoType<text::XTextAppendAndConvert>::get() + }; + return aTypes; +} + +// belongs the range in the text ? insert it then. +void SAL_CALL +SwXText::insertString(const uno::Reference< text::XTextRange >& xTextRange, + const OUString& rString, sal_Bool bAbsorb) +{ + SolarMutexGuard aGuard; + comphelper::ProfileZone aZone("SwXText::insertString"); + + if (!xTextRange.is()) + { + throw uno::RuntimeException(); + } + if (!GetDoc()) + { + throw uno::RuntimeException(); + } + const uno::Reference<lang::XUnoTunnel> xRangeTunnel(xTextRange, + uno::UNO_QUERY); + SwXTextRange *const pRange = + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + if ((!pRange || &pRange ->GetDoc() != GetDoc()) && + (!pCursor || pCursor->GetDoc() != GetDoc())) + { + throw uno::RuntimeException(); + } + + const SwStartNode *const pOwnStartNode = GetStartNode(); + SwPaM aPam(GetDoc()->GetNodes()); + const SwPaM * pPam(nullptr); + if (pCursor) + { + pPam = pCursor->GetPaM(); + } + else // pRange + { + if (pRange->GetPositions(aPam)) + { + pPam = &aPam; + } + } + if (!pPam) + { + throw uno::RuntimeException(); + } + + const SwStartNode* pTmp(pPam->GetNode().StartOfSectionNode()); + while (pTmp && pTmp->IsSectionNode()) + { + pTmp = pTmp->StartOfSectionNode(); + } + if (!pOwnStartNode || (pOwnStartNode != pTmp)) + { + throw uno::RuntimeException(); + } + + bool bForceExpandHints( false ); + if (CursorType::Meta == m_pImpl->m_eType) + { + try + { + bForceExpandHints = CheckForOwnMemberMeta(*pPam, bAbsorb); + } + catch (const lang::IllegalArgumentException& iae) + { + // stupid method not allowed to throw iae + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException( iae.Message, + uno::Reference< uno::XInterface >(), anyEx ); + } + } + if (bAbsorb) + { + //!! scan for CR characters and inserting the paragraph breaks + //!! has to be done in the called function. + //!! Implemented in SwXTextRange::DeleteAndInsert + if (pCursor) + { + SwXTextCursor * const pTextCursor( + dynamic_cast<SwXTextCursor*>(pCursor) ); + if (pTextCursor) + { + pTextCursor->DeleteAndInsert(rString, bForceExpandHints); + } + else + { + xTextRange->setString(rString); + } + } + else + { + pRange->DeleteAndInsert(rString, bForceExpandHints); + } + } + else + { + // create a PaM positioned before the parameter PaM, + // so the text is inserted before + UnoActionContext aContext(GetDoc()); + SwPaM aInsertPam(*pPam->Start()); + ::sw::GroupUndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + SwUnoCursorHelper::DocInsertStringSplitCR( + *GetDoc(), aInsertPam, rString, bForceExpandHints ); + } +} + +void SAL_CALL +SwXText::insertControlCharacter( + const uno::Reference< text::XTextRange > & xTextRange, + sal_Int16 nControlCharacter, sal_Bool bAbsorb) +{ + SolarMutexGuard aGuard; + + if (!xTextRange.is()) + { + throw lang::IllegalArgumentException(); + } + if (!GetDoc()) + { + throw uno::RuntimeException(); + } + + SwUnoInternalPaM aPam(*GetDoc()); + if (!::sw::XTextRangeToSwPaM(aPam, xTextRange)) + { + throw uno::RuntimeException(); + } + const bool bForceExpandHints(CheckForOwnMemberMeta(aPam, bAbsorb)); + + const SwInsertFlags nInsertFlags = + bForceExpandHints + ? ( SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND) + : SwInsertFlags::EMPTYEXPAND; + + if (bAbsorb && aPam.HasMark()) + { + m_pImpl->m_pDoc->getIDocumentContentOperations().DeleteAndJoin(aPam); + aPam.DeleteMark(); + } + + sal_Unicode cIns = 0; + switch (nControlCharacter) + { + case text::ControlCharacter::PARAGRAPH_BREAK : + // a table cell now becomes an ordinary text cell! + m_pImpl->m_pDoc->ClearBoxNumAttrs(aPam.GetPoint()->nNode); + m_pImpl->m_pDoc->getIDocumentContentOperations().SplitNode(*aPam.GetPoint(), false); + break; + case text::ControlCharacter::APPEND_PARAGRAPH: + { + m_pImpl->m_pDoc->ClearBoxNumAttrs(aPam.GetPoint()->nNode); + m_pImpl->m_pDoc->getIDocumentContentOperations().AppendTextNode(*aPam.GetPoint()); + + const uno::Reference<lang::XUnoTunnel> xRangeTunnel( + xTextRange, uno::UNO_QUERY); + SwXTextRange *const pRange = + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>( + xRangeTunnel); + if (pRange) + { + pRange->SetPositions(aPam); + } + else if (pCursor) + { + SwPaM *const pCursorPam = pCursor->GetPaM(); + *pCursorPam->GetPoint() = *aPam.GetPoint(); + pCursorPam->DeleteMark(); + } + } + break; + case text::ControlCharacter::LINE_BREAK: cIns = 10; break; + case text::ControlCharacter::SOFT_HYPHEN: cIns = CHAR_SOFTHYPHEN; break; + case text::ControlCharacter::HARD_HYPHEN: cIns = CHAR_HARDHYPHEN; break; + case text::ControlCharacter::HARD_SPACE: cIns = CHAR_HARDBLANK; break; + } + if (cIns) + { + m_pImpl->m_pDoc->getIDocumentContentOperations().InsertString( + aPam, OUString(cIns), nInsertFlags); + } + + if (bAbsorb) + { + const uno::Reference<lang::XUnoTunnel> xRangeTunnel( + xTextRange, uno::UNO_QUERY); + SwXTextRange *const pRange = + ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel); + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel); + + SwCursor aCursor(*aPam.GetPoint(), nullptr); + SwUnoCursorHelper::SelectPam(aCursor, true); + aCursor.Left(1); + // here, the PaM needs to be moved: + if (pRange) + { + pRange->SetPositions(aCursor); + } + else + { + SwPaM *const pUnoCursor = pCursor->GetPaM(); + *pUnoCursor->GetPoint() = *aCursor.GetPoint(); + if (aCursor.HasMark()) + { + pUnoCursor->SetMark(); + *pUnoCursor->GetMark() = *aCursor.GetMark(); + } + else + { + pUnoCursor->DeleteMark(); + } + } + } +} + +void SAL_CALL +SwXText::insertTextContent( + const uno::Reference< text::XTextRange > & xRange, + const uno::Reference< text::XTextContent > & xContent, + sal_Bool bAbsorb) +{ + SolarMutexGuard aGuard; + comphelper::ProfileZone aZone("SwXText::insertTextContent"); + + if (!xRange.is()) + { + lang::IllegalArgumentException aIllegal; + aIllegal.Message = "first parameter invalid;"; + throw aIllegal; + } + if (!xContent.is()) + { + lang::IllegalArgumentException aIllegal; + aIllegal.Message = "second parameter invalid"; + throw aIllegal; + } + if(!GetDoc()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + SwUnoInternalPaM aPam(*GetDoc()); + if (!::sw::XTextRangeToSwPaM(aPam, xRange)) + { + lang::IllegalArgumentException aIllegal; + aIllegal.Message = "first parameter invalid"; + throw aIllegal; + } + + // first test if the range is at the right position, then call + // xContent->attach + const SwStartNode* pOwnStartNode = GetStartNode(); + SwStartNodeType eSearchNodeType = SwNormalStartNode; + switch (m_pImpl->m_eType) + { + case CursorType::Frame: eSearchNodeType = SwFlyStartNode; break; + case CursorType::TableText: eSearchNodeType = SwTableBoxStartNode; break; + case CursorType::Footnote: eSearchNodeType = SwFootnoteStartNode; break; + case CursorType::Header: eSearchNodeType = SwHeaderStartNode; break; + case CursorType::Footer: eSearchNodeType = SwFooterStartNode; break; + //case CURSOR_INVALID: + //case CursorType::Body: + default: + break; + } + + const SwStartNode* pTmp = + aPam.GetNode().FindSttNodeByType(eSearchNodeType); + + // ignore SectionNodes + while (pTmp && pTmp->IsSectionNode()) + { + pTmp = pTmp->StartOfSectionNode(); + } + // if the document starts with a section + while (pOwnStartNode && pOwnStartNode->IsSectionNode()) + { + pOwnStartNode = pOwnStartNode->StartOfSectionNode(); + } + // this checks if (this) and xRange are in the same text::XText interface + if (pOwnStartNode != pTmp) + { + uno::RuntimeException aRunException; + aRunException.Message = "text interface and cursor not related"; + throw aRunException; + } + + const bool bForceExpandHints(CheckForOwnMemberMeta(aPam, bAbsorb)); + + // special treatment for Contents that do not replace the range, but + // instead are "overlaid" + const uno::Reference<lang::XUnoTunnel> xContentTunnel(xContent, + uno::UNO_QUERY); + if (!xContentTunnel.is()) + { + lang::IllegalArgumentException aArgException; + aArgException.Message = "text content does not support lang::XUnoTunnel"; + throw aArgException; + } + SwXDocumentIndexMark *const pDocumentIndexMark = + ::sw::UnoTunnelGetImplementation<SwXDocumentIndexMark>(xContentTunnel); + SwXTextSection *const pSection = + ::sw::UnoTunnelGetImplementation<SwXTextSection>(xContentTunnel); + SwXBookmark *const pBookmark = + ::sw::UnoTunnelGetImplementation<SwXBookmark>(xContentTunnel); + SwXReferenceMark *const pReferenceMark = + ::sw::UnoTunnelGetImplementation<SwXReferenceMark>(xContentTunnel); + SwXMeta *const pMeta = + ::sw::UnoTunnelGetImplementation<SwXMeta>(xContentTunnel); + SwXTextField* pTextField = + ::sw::UnoTunnelGetImplementation<SwXTextField>(xContentTunnel); + if (pTextField && pTextField->GetServiceId() != SwServiceType::FieldTypeAnnotation) + pTextField = nullptr; + + const bool bAttribute = pBookmark || pDocumentIndexMark + || pSection || pReferenceMark || pMeta || pTextField; + + if (bAbsorb && !bAttribute) + { + xRange->setString(OUString()); + } + uno::Reference< text::XTextRange > xTempRange = + (bAttribute && bAbsorb) ? xRange : xRange->getStart(); + if (bForceExpandHints) + { + // if necessary, replace xTempRange with a new SwXTextCursor + PrepareForAttach(xTempRange, aPam); + } + xContent->attach(xTempRange); +} + +void SAL_CALL +SwXText::insertTextContentBefore( + const uno::Reference< text::XTextContent>& xNewContent, + const uno::Reference< text::XTextContent>& xSuccessor) +{ + SolarMutexGuard aGuard; + + if(!GetDoc()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + SwXParagraph *const pPara = + comphelper::getUnoTunnelImplementation<SwXParagraph>(xNewContent); + if (!pPara || !pPara->IsDescriptor() || !xSuccessor.is()) + { + throw lang::IllegalArgumentException(); + } + + bool bRet = false; + const uno::Reference<lang::XUnoTunnel> xSuccTunnel(xSuccessor, + uno::UNO_QUERY); + SwXTextSection *const pXSection = + ::sw::UnoTunnelGetImplementation<SwXTextSection>(xSuccTunnel); + SwXTextTable *const pXTable = + ::sw::UnoTunnelGetImplementation<SwXTextTable>(xSuccTunnel); + SwFrameFormat *const pTableFormat = pXTable ? pXTable->GetFrameFormat() : nullptr; + SwTextNode * pTextNode = nullptr; + if(pTableFormat && pTableFormat->GetDoc() == GetDoc()) + { + SwTable *const pTable = SwTable::FindTable( pTableFormat ); + SwTableNode *const pTableNode = pTable->GetTableNode(); + + const SwNodeIndex aTableIdx( *pTableNode, -1 ); + SwPosition aBefore(aTableIdx); + bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( aBefore ); + pTextNode = aBefore.nNode.GetNode().GetTextNode(); + } + else if (pXSection && pXSection->GetFormat() && + pXSection->GetFormat()->GetDoc() == GetDoc()) + { + SwSectionFormat *const pSectFormat = pXSection->GetFormat(); + SwSectionNode *const pSectNode = pSectFormat->GetSectionNode(); + + const SwNodeIndex aSectIdx( *pSectNode, -1 ); + SwPosition aBefore(aSectIdx); + bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( aBefore ); + pTextNode = aBefore.nNode.GetNode().GetTextNode(); + } + if (!bRet || !pTextNode) + { + throw lang::IllegalArgumentException(); + } + pPara->attachToText(*this, *pTextNode); +} + +void SAL_CALL +SwXText::insertTextContentAfter( + const uno::Reference< text::XTextContent>& xNewContent, + const uno::Reference< text::XTextContent>& xPredecessor) +{ + SolarMutexGuard aGuard; + + if(!GetDoc()) + { + throw uno::RuntimeException(); + } + + SwXParagraph *const pPara = + comphelper::getUnoTunnelImplementation<SwXParagraph>(xNewContent); + if(!pPara || !pPara->IsDescriptor() || !xPredecessor.is()) + { + throw lang::IllegalArgumentException(); + } + + const uno::Reference<lang::XUnoTunnel> xPredTunnel(xPredecessor, + uno::UNO_QUERY); + SwXTextSection *const pXSection = + ::sw::UnoTunnelGetImplementation<SwXTextSection>(xPredTunnel); + SwXTextTable *const pXTable = + ::sw::UnoTunnelGetImplementation<SwXTextTable>(xPredTunnel); + SwFrameFormat *const pTableFormat = pXTable ? pXTable->GetFrameFormat() : nullptr; + bool bRet = false; + SwTextNode * pTextNode = nullptr; + if(pTableFormat && pTableFormat->GetDoc() == GetDoc()) + { + SwTable *const pTable = SwTable::FindTable( pTableFormat ); + SwTableNode *const pTableNode = pTable->GetTableNode(); + + SwEndNode *const pTableEnd = pTableNode->EndOfSectionNode(); + SwPosition aTableEnd(*pTableEnd); + bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( aTableEnd ); + pTextNode = aTableEnd.nNode.GetNode().GetTextNode(); + } + else if (pXSection && pXSection->GetFormat() && + pXSection->GetFormat()->GetDoc() == GetDoc()) + { + SwSectionFormat *const pSectFormat = pXSection->GetFormat(); + SwSectionNode *const pSectNode = pSectFormat->GetSectionNode(); + SwEndNode *const pEnd = pSectNode->EndOfSectionNode(); + SwPosition aEnd(*pEnd); + bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( aEnd ); + pTextNode = aEnd.nNode.GetNode().GetTextNode(); + } + if (!bRet || !pTextNode) + { + throw lang::IllegalArgumentException(); + } + pPara->attachToText(*this, *pTextNode); +} + +void SAL_CALL +SwXText::removeTextContentBefore( + const uno::Reference< text::XTextContent>& xSuccessor) +{ + SolarMutexGuard aGuard; + + if(!GetDoc()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + bool bRet = false; + const uno::Reference<lang::XUnoTunnel> xSuccTunnel(xSuccessor, + uno::UNO_QUERY); + SwXTextSection *const pXSection = + ::sw::UnoTunnelGetImplementation<SwXTextSection>(xSuccTunnel); + SwXTextTable *const pXTable = + ::sw::UnoTunnelGetImplementation<SwXTextTable>(xSuccTunnel); + SwFrameFormat *const pTableFormat = pXTable ? pXTable->GetFrameFormat() : nullptr; + if(pTableFormat && pTableFormat->GetDoc() == GetDoc()) + { + SwTable *const pTable = SwTable::FindTable( pTableFormat ); + SwTableNode *const pTableNode = pTable->GetTableNode(); + + const SwNodeIndex aTableIdx( *pTableNode, -1 ); + if(aTableIdx.GetNode().IsTextNode()) + { + SwPaM aBefore(aTableIdx); + bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( aBefore ); + } + } + else if (pXSection && pXSection->GetFormat() && + pXSection->GetFormat()->GetDoc() == GetDoc()) + { + SwSectionFormat *const pSectFormat = pXSection->GetFormat(); + SwSectionNode *const pSectNode = pSectFormat->GetSectionNode(); + + const SwNodeIndex aSectIdx( *pSectNode, -1 ); + if(aSectIdx.GetNode().IsTextNode()) + { + SwPaM aBefore(aSectIdx); + bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( aBefore ); + } + } + if(!bRet) + { + throw lang::IllegalArgumentException(); + } +} + +void SAL_CALL +SwXText::removeTextContentAfter( + const uno::Reference< text::XTextContent>& xPredecessor) +{ + SolarMutexGuard aGuard; + + if(!GetDoc()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + bool bRet = false; + const uno::Reference<lang::XUnoTunnel> xPredTunnel(xPredecessor, + uno::UNO_QUERY); + SwXTextSection *const pXSection = + ::sw::UnoTunnelGetImplementation<SwXTextSection>(xPredTunnel); + SwXTextTable *const pXTable = + ::sw::UnoTunnelGetImplementation<SwXTextTable>(xPredTunnel); + SwFrameFormat *const pTableFormat = pXTable ? pXTable->GetFrameFormat() : nullptr; + if(pTableFormat && pTableFormat->GetDoc() == GetDoc()) + { + SwTable *const pTable = SwTable::FindTable( pTableFormat ); + SwTableNode *const pTableNode = pTable->GetTableNode(); + SwEndNode *const pTableEnd = pTableNode->EndOfSectionNode(); + + const SwNodeIndex aTableIdx( *pTableEnd, 1 ); + if(aTableIdx.GetNode().IsTextNode()) + { + SwPaM aPaM(aTableIdx); + bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( aPaM ); + } + } + else if (pXSection && pXSection->GetFormat() && + pXSection->GetFormat()->GetDoc() == GetDoc()) + { + SwSectionFormat *const pSectFormat = pXSection->GetFormat(); + SwSectionNode *const pSectNode = pSectFormat->GetSectionNode(); + SwEndNode *const pEnd = pSectNode->EndOfSectionNode(); + const SwNodeIndex aSectIdx( *pEnd, 1 ); + if(aSectIdx.GetNode().IsTextNode()) + { + SwPaM aAfter(aSectIdx); + bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( aAfter ); + } + } + if(!bRet) + { + throw lang::IllegalArgumentException(); + } +} + +void SAL_CALL +SwXText::removeTextContent( + const uno::Reference< text::XTextContent > & xContent) +{ + // forward: need no solar mutex here + if(!xContent.is()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = "first parameter invalid"; + throw aRuntime; + } + xContent->dispose(); +} + +uno::Reference< text::XText > SAL_CALL +SwXText::getText() +{ + SolarMutexGuard aGuard; + comphelper::ProfileZone aZone("SwXText::getText"); + + const uno::Reference< text::XText > xRet(this); + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXText::getStart() +{ + SolarMutexGuard aGuard; + + const uno::Reference< text::XTextCursor > xRef = CreateCursor(); + if(!xRef.is()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + xRef->gotoStart(false); + return xRef; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXText::getEnd() +{ + SolarMutexGuard aGuard; + + const uno::Reference< text::XTextCursor > xRef = CreateCursor(); + if(!xRef.is()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + xRef->gotoEnd(false); + return xRef; +} + +OUString SAL_CALL SwXText::getString() +{ + SolarMutexGuard aGuard; + + const uno::Reference< text::XTextCursor > xRet = CreateCursor(); + if(!xRet.is()) + { + SAL_WARN("sw.uno", "cursor was not created in getString() call. Returning empty string."); + return OUString(); + } + xRet->gotoEnd(true); + return xRet->getString(); +} + +void SAL_CALL +SwXText::setString(const OUString& rString) +{ + SolarMutexGuard aGuard; + + if (!GetDoc()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + const SwStartNode* pStartNode = GetStartNode(); + if (!pStartNode) + { + throw uno::RuntimeException(); + } + + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::START, nullptr); + //insert an empty paragraph at the start and at the end to ensure that + //all tables and sections can be removed by the selecting text::XTextCursor + if (CursorType::Meta != m_pImpl->m_eType) + { + SwPosition aStartPos(*pStartNode); + const SwEndNode* pEnd = pStartNode->EndOfSectionNode(); + SwNodeIndex aEndIdx(*pEnd); + --aEndIdx; + //the inserting of nodes should only be done if really necessary + //to prevent #97924# (removes paragraph attributes when setting the text + //e.g. of a table cell + bool bInsertNodes = false; + SwNodeIndex aStartIdx(*pStartNode); + do + { + ++aStartIdx; + SwNode& rCurrentNode = aStartIdx.GetNode(); + if(rCurrentNode.GetNodeType() == SwNodeType::Section + ||rCurrentNode.GetNodeType() == SwNodeType::Table) + { + bInsertNodes = true; + break; + } + } + while(aStartIdx < aEndIdx); + if(bInsertNodes) + { + GetDoc()->getIDocumentContentOperations().AppendTextNode( aStartPos ); + SwPosition aEndPos(aEndIdx.GetNode()); + SwPaM aPam(aEndPos); + GetDoc()->getIDocumentContentOperations().AppendTextNode( *aPam.Start() ); + } + } + + const uno::Reference< text::XTextCursor > xRet = CreateCursor(); + if(!xRet.is()) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + xRet->gotoEnd(true); + xRet->setString(rString); + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); +} + +//FIXME why is CheckForOwnMember duplicated in some insert methods? +// Description: Checks if pRange/pCursor are member of the same text interface. +// Only one of the pointers has to be set! +bool SwXText::Impl::CheckForOwnMember( + const SwPaM & rPaM) +{ + const uno::Reference<text::XTextCursor> xOwnCursor(m_rThis.CreateCursor()); + + OTextCursorHelper *const pOwnCursor = + comphelper::getUnoTunnelImplementation<OTextCursorHelper>(xOwnCursor); + OSL_ENSURE(pOwnCursor, "OTextCursorHelper::getUnoTunnelId() ??? "); + const SwStartNode* pOwnStartNode = + pOwnCursor->GetPaM()->GetNode().StartOfSectionNode(); + SwStartNodeType eSearchNodeType = SwNormalStartNode; + switch (m_eType) + { + case CursorType::Frame: eSearchNodeType = SwFlyStartNode; break; + case CursorType::TableText: eSearchNodeType = SwTableBoxStartNode; break; + case CursorType::Footnote: eSearchNodeType = SwFootnoteStartNode; break; + case CursorType::Header: eSearchNodeType = SwHeaderStartNode; break; + case CursorType::Footer: eSearchNodeType = SwFooterStartNode; break; + //case CURSOR_INVALID: + //case CursorType::Body: + default: + ; + } + + const SwNode& rSrcNode = rPaM.GetNode(); + const SwStartNode* pTmp = rSrcNode.FindSttNodeByType(eSearchNodeType); + + // skip SectionNodes / TableNodes to be able to compare across table/section boundaries + while (pTmp + && (pTmp->IsSectionNode() || pTmp->IsTableNode() + || (m_eType != CursorType::TableText + && pTmp->GetStartNodeType() == SwTableBoxStartNode))) + { + pTmp = pTmp->StartOfSectionNode(); + } + + while (pOwnStartNode->IsSectionNode() || pOwnStartNode->IsTableNode() + || (m_eType != CursorType::TableText + && pOwnStartNode->GetStartNodeType() == SwTableBoxStartNode)) + { + pOwnStartNode = pOwnStartNode->StartOfSectionNode(); + } + + //this checks if (this) and xRange are in the same text::XText interface + return (pOwnStartNode == pTmp); +} + +sal_Int16 +SwXText::Impl::ComparePositions( + const uno::Reference<text::XTextRange>& xPos1, + const uno::Reference<text::XTextRange>& xPos2) +{ + SwUnoInternalPaM aPam1(*m_pDoc); + SwUnoInternalPaM aPam2(*m_pDoc); + + if (!::sw::XTextRangeToSwPaM(aPam1, xPos1) || + !::sw::XTextRangeToSwPaM(aPam2, xPos2)) + { + throw lang::IllegalArgumentException(); + } + if (!CheckForOwnMember(aPam1) || !CheckForOwnMember(aPam2)) + { + throw lang::IllegalArgumentException(); + } + + sal_Int16 nCompare = 0; + SwPosition const*const pStart1 = aPam1.Start(); + SwPosition const*const pStart2 = aPam2.Start(); + if (*pStart1 < *pStart2) + { + nCompare = 1; + } + else if (*pStart1 > *pStart2) + { + nCompare = -1; + } + else + { + OSL_ENSURE(*pStart1 == *pStart2, + "SwPositions should be equal here"); + nCompare = 0; + } + + return nCompare; +} + +sal_Int16 SAL_CALL +SwXText::compareRegionStarts( + const uno::Reference<text::XTextRange>& xRange1, + const uno::Reference<text::XTextRange>& xRange2) +{ + SolarMutexGuard aGuard; + + if (!xRange1.is() || !xRange2.is()) + { + throw lang::IllegalArgumentException(); + } + const uno::Reference<text::XTextRange> xStart1 = xRange1->getStart(); + const uno::Reference<text::XTextRange> xStart2 = xRange2->getStart(); + + return m_pImpl->ComparePositions(xStart1, xStart2); +} + +sal_Int16 SAL_CALL +SwXText::compareRegionEnds( + const uno::Reference<text::XTextRange>& xRange1, + const uno::Reference<text::XTextRange>& xRange2) +{ + SolarMutexGuard aGuard; + + if (!xRange1.is() || !xRange2.is()) + { + throw lang::IllegalArgumentException(); + } + uno::Reference<text::XTextRange> xEnd1 = xRange1->getEnd(); + uno::Reference<text::XTextRange> xEnd2 = xRange2->getEnd(); + + return m_pImpl->ComparePositions(xEnd1, xEnd2); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL +SwXText::getPropertySetInfo() +{ + SolarMutexGuard g; + + static uno::Reference< beans::XPropertySetInfo > xInfo = + m_pImpl->m_rPropSet.getPropertySetInfo(); + return xInfo; +} + +void SAL_CALL +SwXText::setPropertyValue(const OUString& /*aPropertyName*/, + const uno::Any& /*aValue*/) +{ + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL +SwXText::getPropertyValue( + const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + { + throw uno::RuntimeException(); + } + + SfxItemPropertySimpleEntry const*const pEntry = + m_pImpl->m_rPropSet.getPropertyMap().getByName(rPropertyName); + if (!pEntry) + { + beans::UnknownPropertyException aExcept; + aExcept.Message = "Unknown property: " + rPropertyName; + throw aExcept; + } + + uno::Any aRet; + switch (pEntry->nWID) + { +// no code necessary - the redline is always located at the end node +// case FN_UNO_REDLINE_NODE_START: +// break; + case FN_UNO_REDLINE_NODE_END: + { + const SwRedlineTable& rRedTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + const size_t nRedTableCount = rRedTable.size(); + if (nRedTableCount > 0) + { + SwStartNode const*const pStartNode = GetStartNode(); + const sal_uLong nOwnIndex = pStartNode->EndOfSectionIndex(); + for (size_t nRed = 0; nRed < nRedTableCount; ++nRed) + { + SwRangeRedline const*const pRedline = rRedTable[nRed]; + SwPosition const*const pRedStart = pRedline->Start(); + const SwNodeIndex nRedNode = pRedStart->nNode; + if (nOwnIndex == nRedNode.GetIndex()) + { + aRet <<= SwXRedlinePortion::CreateRedlineProperties( + *pRedline, true); + break; + } + } + } + } + break; + } + return aRet; +} + +void SAL_CALL +SwXText::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXText::addPropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXText::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXText::removePropertyChangeListener(): not implemented"); +} + +void SAL_CALL +SwXText::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXText::addVetoableChangeListener(): not implemented"); +} + +void SAL_CALL +SwXText::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/) +{ + OSL_FAIL("SwXText::removeVetoableChangeListener(): not implemented"); +} + +namespace +{ + class theSwXTextUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 > & SwXText::getUnoTunnelId() +{ + return theSwXTextUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL +SwXText::getSomething(const uno::Sequence< sal_Int8 >& rId) +{ + return ::sw::UnoTunnelImpl<SwXText>(rId, this); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXText::finishParagraph( + const uno::Sequence< beans::PropertyValue > & rProperties) +{ + SolarMutexGuard g; + + return m_pImpl->finishOrAppendParagraph(rProperties, uno::Reference< text::XTextRange >()); +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXText::finishParagraphInsert( + const uno::Sequence< beans::PropertyValue > & rProperties, + const uno::Reference< text::XTextRange >& xInsertPosition) +{ + SolarMutexGuard g; + + return m_pImpl->finishOrAppendParagraph(rProperties, xInsertPosition); +} + +uno::Reference< text::XTextRange > +SwXText::Impl::finishOrAppendParagraph( + const uno::Sequence< beans::PropertyValue > & rProperties, + const uno::Reference< text::XTextRange >& xInsertPosition) +{ + if (!m_bIsValid) + { + throw uno::RuntimeException(); + } + + const SwStartNode* pStartNode = m_rThis.GetStartNode(); + if(!pStartNode) + { + throw uno::RuntimeException(); + } + + uno::Reference< text::XTextRange > xRet; + bool bIllegalException = false; + bool bRuntimeException = false; + OUString sMessage; + m_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::START , nullptr); + // find end node, go backward - don't skip tables because the new + // paragraph has to be the last node + //aPam.Move( fnMoveBackward, GoInNode ); + SwPosition aInsertPosition( + SwNodeIndex( *pStartNode->EndOfSectionNode(), -1 ) ); + SwPaM aPam(aInsertPosition); + // If we got a position reference, then the insert point is not the end of + // the document. + if (xInsertPosition.is()) + { + SwUnoInternalPaM aStartPam(*m_rThis.GetDoc()); + ::sw::XTextRangeToSwPaM(aStartPam, xInsertPosition); + aPam = aStartPam; + aPam.SetMark(); + } + m_pDoc->getIDocumentContentOperations().AppendTextNode( *aPam.GetPoint() ); + // remove attributes from the previous paragraph + m_pDoc->ResetAttrs(aPam); + // in case of finishParagraph the PaM needs to be moved to the + // previous paragraph + aPam.Move( fnMoveBackward, GoInNode ); + + try + { + SfxItemPropertySet const*const pParaPropSet = + aSwMapProvider.GetPropertySet(PROPERTY_MAP_PARAGRAPH); + + SwUnoCursorHelper::SetPropertyValues(aPam, *pParaPropSet, rProperties); + + // tdf#127616 keep direct character formatting of empty paragraphs, + // if character style of the paragraph sets also the same attributes + if (aPam.Start()->nNode.GetNode().GetTextNode()->Len() == 0) + { + auto itCharStyle = std::find_if(rProperties.begin(), rProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "CharStyleName"; + }); + if ( itCharStyle != rProperties.end() ) + { + for (const auto& rValue : rProperties) + { + if ( rValue != *itCharStyle && rValue.Name.startsWith("Char") ) + { + SwUnoCursorHelper::SetPropertyValue(aPam, *pParaPropSet, rValue.Name, rValue.Value); + } + } + } + } + } + catch (const lang::IllegalArgumentException& rIllegal) + { + sMessage = rIllegal.Message; + bIllegalException = true; + } + catch (const uno::RuntimeException& rRuntime) + { + sMessage = rRuntime.Message; + bRuntimeException = true; + } + catch (const uno::Exception& rEx) + { + sMessage = rEx.Message; + bRuntimeException = true; + } + + m_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + if (bIllegalException || bRuntimeException) + { + m_pDoc->GetIDocumentUndoRedo().Undo(); + if (bIllegalException) + { + lang::IllegalArgumentException aEx; + aEx.Message = sMessage; + throw aEx; + } + else + { + uno::RuntimeException aEx; + aEx.Message = sMessage; + throw aEx; + } + } + SwTextNode *const pTextNode( aPam.Start()->nNode.GetNode().GetTextNode() ); + OSL_ENSURE(pTextNode, "no SwTextNode?"); + if (pTextNode) + { + xRet.set(SwXParagraph::CreateXParagraph(*m_pDoc, pTextNode, &m_rThis), + uno::UNO_QUERY); + } + + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXText::insertTextPortion( + const OUString& rText, + const uno::Sequence< beans::PropertyValue > & + rCharacterAndParagraphProperties, + const uno::Reference<text::XTextRange>& xInsertPosition) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + { + throw uno::RuntimeException(); + } + uno::Reference< text::XTextRange > xRet; + const uno::Reference<text::XTextCursor> xTextCursor = createTextCursorByRange(xInsertPosition); + + const uno::Reference< lang::XUnoTunnel > xRangeTunnel( + xTextCursor, uno::UNO_QUERY_THROW ); + SwXTextCursor *const pTextCursor = + ::sw::UnoTunnelGetImplementation<SwXTextCursor>(xRangeTunnel); + + bool bIllegalException = false; + bool bRuntimeException = false; + OUString sMessage; + m_pImpl->m_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); + + auto& rCursor(pTextCursor->GetCursor()); + m_pImpl->m_pDoc->DontExpandFormat( *rCursor.Start() ); + + if (!rText.isEmpty()) + { + SwNodeIndex const nodeIndex(rCursor.GetPoint()->nNode, -1); + const sal_Int32 nContentPos = rCursor.GetPoint()->nContent.GetIndex(); + SwUnoCursorHelper::DocInsertStringSplitCR( + *m_pImpl->m_pDoc, rCursor, rText, false); + SwUnoCursorHelper::SelectPam(rCursor, true); + rCursor.GetPoint()->nNode.Assign(nodeIndex.GetNode(), +1); + rCursor.GetPoint()->nContent = nContentPos; + } + + try + { + SfxItemPropertySet const*const pCursorPropSet = + aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR); + SwUnoCursorHelper::SetPropertyValues(rCursor, *pCursorPropSet, + rCharacterAndParagraphProperties, + SetAttrMode::NOFORMATATTR); + } + catch (const lang::IllegalArgumentException& rIllegal) + { + sMessage = rIllegal.Message; + bIllegalException = true; + } + catch (const uno::RuntimeException& rRuntime) + { + sMessage = rRuntime.Message; + bRuntimeException = true; + } + m_pImpl->m_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr); + if (bIllegalException || bRuntimeException) + { + m_pImpl->m_pDoc->GetIDocumentUndoRedo().Undo(); + if (bIllegalException) + { + lang::IllegalArgumentException aEx; + aEx.Message = sMessage; + throw aEx; + } + else + { + uno::RuntimeException aEx; + aEx.Message = sMessage; + throw aEx; + } + } + xRet = new SwXTextRange(rCursor, this); + return xRet; +} + +// Append text portions at the end of the last paragraph of the text interface. +// Support of import filters. +uno::Reference< text::XTextRange > SAL_CALL +SwXText::appendTextPortion( + const OUString& rText, + const uno::Sequence< beans::PropertyValue > & + rCharacterAndParagraphProperties) +{ + // Right now this doesn't need a guard, as it's just calling the insert + // version, that has it already. + uno::Reference<text::XTextRange> xInsertPosition = getEnd(); + return insertTextPortion(rText, rCharacterAndParagraphProperties, xInsertPosition); +} + +// enable inserting/appending text contents like graphic objects, shapes and so on to +// support import filters +uno::Reference< text::XTextRange > SAL_CALL +SwXText::insertTextContentWithProperties( + const uno::Reference< text::XTextContent >& xTextContent, + const uno::Sequence< beans::PropertyValue >& + rCharacterAndParagraphProperties, + const uno::Reference< text::XTextRange >& xInsertPosition) +{ + SolarMutexGuard aGuard; + + if (!IsValid()) + { + throw uno::RuntimeException(); + } + + SwUnoInternalPaM aPam(*GetDoc()); + if (!::sw::XTextRangeToSwPaM(aPam, xInsertPosition)) + { + throw lang::IllegalArgumentException("invalid position", nullptr, 2); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_UNDO_INSERT_TEXTBOX)); + + m_pImpl->m_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, &aRewriter); + + // Any direct formatting ending at the insert position (xRange) should not + // be expanded to cover the inserted content (xContent) + // (insertTextContent() shouldn't do this, only ...WithProperties()!) + GetDoc()->DontExpandFormat( *aPam.Start() ); + + // now attach the text content here + insertTextContent( xInsertPosition, xTextContent, false ); + // now apply the properties to the anchor + if (rCharacterAndParagraphProperties.hasElements()) + { + try + { + const uno::Reference< beans::XPropertySet > xAnchor( + xTextContent->getAnchor(), uno::UNO_QUERY); + if (xAnchor.is()) + { + for (const auto& rProperty : rCharacterAndParagraphProperties) + { + xAnchor->setPropertyValue(rProperty.Name, rProperty.Value); + } + } + } + catch (const uno::Exception& e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + m_pImpl->m_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, &aRewriter); + throw lang::WrappedTargetRuntimeException( e.Message, + uno::Reference< uno::XInterface >(), anyEx ); + } + } + m_pImpl->m_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, &aRewriter); + return xInsertPosition; +} + +uno::Reference< text::XTextRange > SAL_CALL +SwXText::appendTextContent( + const uno::Reference< text::XTextContent >& xTextContent, + const uno::Sequence< beans::PropertyValue >& rCharacterAndParagraphProperties + ) +{ + // Right now this doesn't need a guard, as it's just calling the insert + // version, that has it already. + uno::Reference<text::XTextRange> xInsertPosition = getEnd(); + return insertTextContentWithProperties(xTextContent, rCharacterAndParagraphProperties, xInsertPosition); +} + +// determine whether SwFrameFormat is a graphic node +static bool isGraphicNode(const SwFrameFormat* pFrameFormat) +{ + // safety + if( !pFrameFormat->GetContent().GetContentIdx() ) + { + return false; + } + auto index = *pFrameFormat->GetContent().GetContentIdx(); + // consider the next node -> there is the graphic stored + index++; + return index.GetNode().IsGrfNode(); +} + +// move previously appended paragraphs into a text frames +// to support import filters +uno::Reference< text::XTextContent > SAL_CALL +SwXText::convertToTextFrame( + const uno::Reference< text::XTextRange >& xStart, + const uno::Reference< text::XTextRange >& xEnd, + const uno::Sequence< beans::PropertyValue >& rFrameProperties) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + { + throw uno::RuntimeException(); + } + uno::Reference< text::XTextContent > xRet; + std::unique_ptr<SwUnoInternalPaM> pTempStartPam(new SwUnoInternalPaM(*GetDoc())); + std::unique_ptr< SwUnoInternalPaM > pEndPam(new SwUnoInternalPaM(*GetDoc())); + if (!::sw::XTextRangeToSwPaM(*pTempStartPam, xStart) || + !::sw::XTextRangeToSwPaM(*pEndPam, xEnd)) + { + throw lang::IllegalArgumentException(); + } + auto pStartPam(GetDoc()->CreateUnoCursor(*pTempStartPam->GetPoint())); + if (pTempStartPam->HasMark()) + { + pStartPam->SetMark(); + *pStartPam->GetMark() = *pTempStartPam->GetMark(); + } + pTempStartPam.reset(); + + SwXTextRange *const pStartRange = + comphelper::getUnoTunnelImplementation<SwXTextRange>(xStart); + SwXTextRange *const pEndRange = + comphelper::getUnoTunnelImplementation<SwXTextRange>(xEnd); + // bookmarks have to be removed before the referenced text node + // is deleted in DelFullPara + if (pStartRange) + { + pStartRange->Invalidate(); + } + if (pEndRange) + { + pEndRange->Invalidate(); + } + + m_pImpl->m_pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + bool bIllegalException = false; + bool bRuntimeException = false; + OUString sMessage; + SwStartNode* pStartStartNode = pStartPam->GetNode().StartOfSectionNode(); + while (pStartStartNode && pStartStartNode->IsSectionNode()) + { + pStartStartNode = pStartStartNode->StartOfSectionNode(); + } + SwStartNode* pEndStartNode = pEndPam->GetNode().StartOfSectionNode(); + while (pEndStartNode && pEndStartNode->IsSectionNode()) + { + pEndStartNode = pEndStartNode->StartOfSectionNode(); + } + bool bParaAfterInserted = false; + bool bParaBeforeInserted = false; + if ( + pStartStartNode && pEndStartNode && + (pStartStartNode != pEndStartNode || pStartStartNode != GetStartNode()) + ) + { + // todo: if the start/end is in a table then insert a paragraph + // before/after, move the start/end nodes, then convert and + // remove the additional paragraphs in the end + SwTableNode * pStartTableNode(nullptr); + if (pStartStartNode->GetStartNodeType() == SwTableBoxStartNode) + { + pStartTableNode = pStartStartNode->FindTableNode(); + // Is it the same table start node than the end? + SwTableNode *const pEndStartTableNode(pEndStartNode->FindTableNode()); + while (pEndStartTableNode && pStartTableNode && + pEndStartTableNode->GetIndex() < pStartTableNode->GetIndex()) + { + SwStartNode* pStartStartTableNode = pStartTableNode->StartOfSectionNode(); + pStartTableNode = pStartStartTableNode->FindTableNode(); + } + } + if (pStartTableNode) + { + const SwNodeIndex aTableIdx( *pStartTableNode, -1 ); + SwPosition aBefore(aTableIdx); + bParaBeforeInserted = GetDoc()->getIDocumentContentOperations().AppendTextNode( aBefore ); + pStartPam->DeleteMark(); + *pStartPam->GetPoint() = aBefore; + pStartStartNode = pStartPam->GetNode().StartOfSectionNode(); + } + if (pEndStartNode->GetStartNodeType() == SwTableBoxStartNode) + { + SwTableNode *const pEndTableNode = pEndStartNode->FindTableNode(); + SwEndNode *const pTableEnd = pEndTableNode->EndOfSectionNode(); + SwPosition aTableEnd(*pTableEnd); + bParaAfterInserted = GetDoc()->getIDocumentContentOperations().AppendTextNode( aTableEnd ); + pEndPam->DeleteMark(); + *pEndPam->GetPoint() = aTableEnd; + pEndStartNode = pEndPam->GetNode().StartOfSectionNode(); + } + // now we should have the positions in the same hierarchy + if ((pStartStartNode != pEndStartNode) || + (pStartStartNode != GetStartNode())) + { + // if not - remove the additional paragraphs and throw + if (bParaBeforeInserted) + { + SwCursor aDelete(*pStartPam->GetPoint(), nullptr); + *pStartPam->GetPoint() = // park it because node is deleted + SwPosition(GetDoc()->GetNodes().GetEndOfContent()); + aDelete.MovePara(GoCurrPara, fnParaStart); + aDelete.SetMark(); + aDelete.MovePara(GoCurrPara, fnParaEnd); + GetDoc()->getIDocumentContentOperations().DelFullPara(aDelete); + } + if (bParaAfterInserted) + { + SwCursor aDelete(*pEndPam->GetPoint(), nullptr); + *pEndPam->GetPoint() = // park it because node is deleted + SwPosition(GetDoc()->GetNodes().GetEndOfContent()); + aDelete.MovePara(GoCurrPara, fnParaStart); + aDelete.SetMark(); + aDelete.MovePara(GoCurrPara, fnParaEnd); + GetDoc()->getIDocumentContentOperations().DelFullPara(aDelete); + } + throw lang::IllegalArgumentException(); + } + } + + // make a selection from pStartPam to pEndPam + // If there is no content in the frame the shape is in + // it gets deleted in the DelFullPara call below, + // In this case insert a tmp text node ( we delete it later ) + if (pStartPam->Start()->nNode == pEndPam->Start()->nNode + && pStartPam->End()->nNode == pEndPam->End()->nNode) + { + SwPosition aEnd(*pStartPam->End()); + bParaAfterInserted = GetDoc()->getIDocumentContentOperations().AppendTextNode( aEnd ); + pEndPam->DeleteMark(); + *pEndPam->GetPoint() = aEnd; + } + pStartPam->SetMark(); + *pStartPam->End() = *pEndPam->End(); + pEndPam.reset(); + + // see if there are frames already anchored to this node + // we have to work with the SdrObjects, as unique name is not guaranteed in their frame format + // tdf#115094: do nothing if we have a graphic node + o3tl::sorted_vector<const SdrObject*> aAnchoredObjectsByPtr; + std::set<OUString> aAnchoredObjectsByName; + for (size_t i = 0; i < m_pImpl->m_pDoc->GetSpzFrameFormats()->size(); ++i) + { + const SwFrameFormat* pFrameFormat = (*m_pImpl->m_pDoc->GetSpzFrameFormats())[i]; + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + if ( !isGraphicNode(pFrameFormat) && + (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId() || RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) && + pStartPam->Start()->nNode.GetIndex() <= rAnchor.GetContentAnchor()->nNode.GetIndex() && + pStartPam->End()->nNode.GetIndex() >= rAnchor.GetContentAnchor()->nNode.GetIndex()) + { + if (pFrameFormat->GetName().isEmpty()) + { + aAnchoredObjectsByPtr.insert(pFrameFormat->FindSdrObject()); + } + else + { + aAnchoredObjectsByName.insert(pFrameFormat->GetName()); + } + } + } + + const uno::Reference<text::XTextFrame> xNewFrame( + SwXTextFrame::CreateXTextFrame(*m_pImpl->m_pDoc, nullptr)); + SwXTextFrame& rNewFrame = dynamic_cast<SwXTextFrame&>(*xNewFrame); + try + { + for (const beans::PropertyValue& rValue : rFrameProperties) + { + rNewFrame.SwXFrame::setPropertyValue(rValue.Name, rValue.Value); + } + + { // has to be in a block to remove the SwIndexes before + // DelFullPara is called + const uno::Reference< text::XTextRange> xInsertTextRange = + new SwXTextRange(*pStartPam, this); + assert(rNewFrame.IsDescriptor()); + rNewFrame.attachToRange(xInsertTextRange, pStartPam.get()); + rNewFrame.setName(m_pImpl->m_pDoc->GetUniqueFrameName()); + } + + SwTextNode *const pTextNode(pStartPam->GetNode().GetTextNode()); + assert(pTextNode); + if (!pTextNode || !pTextNode->Len()) // don't remove if it contains text! + { + { // has to be in a block to remove the SwIndexes before + // DelFullPara is called + SwPaM aMovePam( pStartPam->GetNode() ); + if (aMovePam.Move( fnMoveForward, GoInContent )) + { + // move the anchor to the next paragraph + SwFormatAnchor aNewAnchor(rNewFrame.GetFrameFormat()->GetAnchor()); + aNewAnchor.SetAnchor( aMovePam.Start() ); + m_pImpl->m_pDoc->SetAttr( + aNewAnchor, *rNewFrame.GetFrameFormat() ); + + // also move frames anchored to us + for (size_t i = 0; i < m_pImpl->m_pDoc->GetSpzFrameFormats()->size(); ++i) + { + SwFrameFormat* pFrameFormat = (*m_pImpl->m_pDoc->GetSpzFrameFormats())[i]; + if ((!pFrameFormat->GetName().isEmpty() && aAnchoredObjectsByName.find(pFrameFormat->GetName()) != aAnchoredObjectsByName.end() ) || + ( pFrameFormat->GetName().isEmpty() && aAnchoredObjectsByPtr.find(pFrameFormat->FindSdrObject()) != aAnchoredObjectsByPtr.end()) ) + { + // copy the anchor to the next paragraph + SwFormatAnchor aAnchor(pFrameFormat->GetAnchor()); + aAnchor.SetAnchor(aMovePam.Start()); + m_pImpl->m_pDoc->SetAttr(aAnchor, *pFrameFormat); + } + } + } + } + m_pImpl->m_pDoc->getIDocumentContentOperations().DelFullPara(*pStartPam); + } + } + catch (const lang::IllegalArgumentException& rIllegal) + { + sMessage = rIllegal.Message; + bIllegalException = true; + } + catch (const uno::RuntimeException& rRuntime) + { + sMessage = rRuntime.Message; + bRuntimeException = true; + } + xRet = xNewFrame; + if (bParaBeforeInserted || bParaAfterInserted) + { + const uno::Reference<text::XTextCursor> xFrameTextCursor = + rNewFrame.createTextCursor(); + SwXTextCursor *const pFrameCursor = + comphelper::getUnoTunnelImplementation<SwXTextCursor>(xFrameTextCursor); + if (bParaBeforeInserted) + { + // todo: remove paragraph before frame + m_pImpl->m_pDoc->getIDocumentContentOperations().DelFullPara(*pFrameCursor->GetPaM()); + } + if (bParaAfterInserted) + { + xFrameTextCursor->gotoEnd(false); + if (!bParaBeforeInserted) + m_pImpl->m_pDoc->getIDocumentContentOperations().DelFullPara(*pFrameCursor->GetPaM()); + else + { + // In case the frame has a table only, the cursor points to the end of the first cell of the table. + SwPaM aPaM(*pFrameCursor->GetPaM()->GetNode().FindSttNodeByType(SwFlyStartNode)->EndOfSectionNode()); + // Now we have the end of the frame -- the node before that will be the paragraph we want to remove. + --aPaM.GetPoint()->nNode; + m_pImpl->m_pDoc->getIDocumentContentOperations().DelFullPara(aPaM); + } + } + } + + m_pImpl->m_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + if (bIllegalException || bRuntimeException) + { + m_pImpl->m_pDoc->GetIDocumentUndoRedo().Undo(); + if (bIllegalException) + { + lang::IllegalArgumentException aEx; + aEx.Message = sMessage; + throw aEx; + } + else + { + uno::RuntimeException aEx; + aEx.Message = sMessage; + throw aEx; + } + } + return xRet; +} + +namespace { + +// Move previously imported paragraphs into a new text table. +struct VerticallyMergedCell +{ + std::vector<uno::Reference< beans::XPropertySet > > aCells; + sal_Int32 nLeftPosition; + bool bOpen; + + VerticallyMergedCell(uno::Reference< beans::XPropertySet > const& rxCell, + const sal_Int32 nLeft) + : nLeftPosition( nLeft ) + , bOpen( true ) + { + aCells.push_back( rxCell ); + } +}; + +} + +#define COL_POS_FUZZY 2 + +static bool lcl_SimilarPosition( const sal_Int32 nPos1, const sal_Int32 nPos2 ) +{ + return abs( nPos1 - nPos2 ) < COL_POS_FUZZY; +} + +void SwXText::Impl::ConvertCell( + const uno::Sequence< uno::Reference< text::XTextRange > > & rCell, + std::vector<SwNodeRange> & rRowNodes, + SwNodeRange *const pLastCell) +{ + if (rCell.getLength() != 2) + { + throw lang::IllegalArgumentException( + "rCell needs to contain 2 elements", + uno::Reference< text::XTextCopy >( &m_rThis ), sal_Int16( 2 ) ); + } + const uno::Reference<text::XTextRange> xStartRange = rCell[0]; + const uno::Reference<text::XTextRange> xEndRange = rCell[1]; + SwUnoInternalPaM aStartCellPam(*m_pDoc); + SwUnoInternalPaM aEndCellPam(*m_pDoc); + + // !!! TODO - PaMs in tables and sections do not work here - + // the same applies to PaMs in frames !!! + + if (!::sw::XTextRangeToSwPaM(aStartCellPam, xStartRange) || + !::sw::XTextRangeToSwPaM(aEndCellPam, xEndRange)) + { + throw lang::IllegalArgumentException( + "Start or End range cannot be resolved to a SwPaM", + uno::Reference< text::XTextCopy >( &m_rThis ), sal_Int16( 2 ) ); + } + + SwNodeRange aTmpRange(aStartCellPam.Start()->nNode, + aEndCellPam.End()->nNode); + std::unique_ptr<SwNodeRange> pCorrectedRange = + m_pDoc->GetNodes().ExpandRangeForTableBox(aTmpRange); + + if (pCorrectedRange) + { + SwPaM aNewStartPaM(pCorrectedRange->aStart, 0); + aStartCellPam = aNewStartPaM; + + sal_Int32 nEndLen = 0; + SwTextNode * pTextNode = pCorrectedRange->aEnd.GetNode().GetTextNode(); + if (pTextNode != nullptr) + nEndLen = pTextNode->Len(); + + SwPaM aNewEndPaM(pCorrectedRange->aEnd, nEndLen); + aEndCellPam = aNewEndPaM; + + pCorrectedRange.reset(); + } + + /** check the nodes between start and end + it is allowed to have pairs of StartNode/EndNodes + */ + if (aStartCellPam.Start()->nNode < aEndCellPam.End()->nNode) + { + // increment on each StartNode and decrement on each EndNode + // we must reach zero at the end and must not go below zero + long nOpenNodeBlock = 0; + SwNodeIndex aCellIndex = aStartCellPam.Start()->nNode; + while (aCellIndex < aEndCellPam.End()->nNode.GetIndex()) + { + if (aCellIndex.GetNode().IsStartNode()) + { + ++nOpenNodeBlock; + } + else if (aCellIndex.GetNode().IsEndNode()) + { + --nOpenNodeBlock; + } + if (nOpenNodeBlock < 0) + { + throw lang::IllegalArgumentException(); + } + ++aCellIndex; + } + if (nOpenNodeBlock != 0) + { + throw lang::IllegalArgumentException(); + } + } + + /** The vector<vector> NodeRanges has to contain consecutive nodes. + In rTableRanges the ranges don't need to be full paragraphs but + they have to follow each other. To process the ranges they + have to be aligned on paragraph borders by inserting paragraph + breaks. Non-consecutive ranges must initiate an exception. + */ + if (!pLastCell) // first cell? + { + // align the beginning - if necessary + if (aStartCellPam.Start()->nContent.GetIndex()) + { + m_pDoc->getIDocumentContentOperations().SplitNode(*aStartCellPam.Start(), false); + } + } + else + { + // check the predecessor + const sal_uLong nStartCellNodeIndex = + aStartCellPam.Start()->nNode.GetIndex(); + const sal_uLong nLastNodeEndIndex = pLastCell->aEnd.GetIndex(); + if (nLastNodeEndIndex == nStartCellNodeIndex) + { + // same node as predecessor then equal nContent? + if (0 != aStartCellPam.Start()->nContent.GetIndex()) + { + throw lang::IllegalArgumentException(); + } + + m_pDoc->getIDocumentContentOperations().SplitNode(*aStartCellPam.Start(), false); + sal_uLong const nNewIndex(aStartCellPam.Start()->nNode.GetIndex()); + if (nNewIndex != nStartCellNodeIndex) + { + // aStartCellPam now points to the 2nd node + // the last cell may *also* point to 2nd node now - fix it! + assert(nNewIndex == nStartCellNodeIndex + 1); + if (pLastCell->aEnd.GetIndex() == nNewIndex) + { + --pLastCell->aEnd; + if (pLastCell->aStart.GetIndex() == nNewIndex) + { + --pLastCell->aStart; + } + } + } + } + else if (nStartCellNodeIndex == (nLastNodeEndIndex + 1)) + { + // next paragraph - now the content index of the new should be 0 + // and of the old one should be equal to the text length + // but if it isn't we don't care - the cell is being inserted on + // the node border anyway + } + else + { + throw lang::IllegalArgumentException(); + } + } + // now check if there's a need to insert another paragraph break + if (aEndCellPam.End()->nContent.GetIndex() < + aEndCellPam.End()->nNode.GetNode().GetTextNode()->Len()) + { + m_pDoc->getIDocumentContentOperations().SplitNode(*aEndCellPam.End(), false); + // take care that the new start/endcell is moved to the right position + // aStartCellPam has to point to the start of the new (previous) node + // aEndCellPam has to point to the end of the new (previous) node + aStartCellPam.DeleteMark(); + aStartCellPam.Move(fnMoveBackward, GoInNode); + aStartCellPam.GetPoint()->nContent = 0; + aEndCellPam.DeleteMark(); + aEndCellPam.Move(fnMoveBackward, GoInNode); + aEndCellPam.GetPoint()->nContent = + aEndCellPam.GetNode().GetTextNode()->Len(); + } + + assert(aStartCellPam.Start()->nContent.GetIndex() == 0); + assert(aEndCellPam.End()->nContent.GetIndex() == aEndCellPam.End()->nNode.GetNode().GetTextNode()->Len()); + SwNodeRange aCellRange(aStartCellPam.Start()->nNode, + aEndCellPam.End()->nNode); + rRowNodes.push_back(aCellRange); // note: invalidates pLastCell! +} + +typedef uno::Sequence< text::TableColumnSeparator > TableColumnSeparators; + +static void +lcl_ApplyRowProperties( + uno::Sequence<beans::PropertyValue> const& rRowProperties, + uno::Any const& rRow, + TableColumnSeparators & rRowSeparators) +{ + uno::Reference< beans::XPropertySet > xRow; + rRow >>= xRow; + for (const beans::PropertyValue& rProperty : rRowProperties) + { + if ( rProperty.Name == "TableColumnSeparators" ) + { + // add the separators to access the cell's positions + // for vertical merging later + TableColumnSeparators aSeparators; + rProperty.Value >>= aSeparators; + rRowSeparators = aSeparators; + } + xRow->setPropertyValue(rProperty.Name, rProperty.Value); + } +} + +static sal_Int32 lcl_GetLeftPos(sal_Int32 nCell, TableColumnSeparators const& rRowSeparators) +{ + if(!nCell) + return 0; + if (rRowSeparators.getLength() < nCell) + return -1; + return rRowSeparators[nCell - 1].Position; +} + +static void +lcl_ApplyCellProperties( + const sal_Int32 nLeftPos, + const uno::Sequence< beans::PropertyValue >& rCellProperties, + const uno::Reference< uno::XInterface >& xCell, + std::vector<VerticallyMergedCell> & rMergedCells) +{ + const uno::Reference< beans::XPropertySet > xCellPS(xCell, uno::UNO_QUERY); + for (const auto& rCellProperty : rCellProperties) + { + const OUString & rName = rCellProperty.Name; + const uno::Any & rValue = rCellProperty.Value; + if ( rName == "VerticalMerge" ) + { + // determine left border position + // add the cell to a queue of merged cells + bool bMerge = false; + rValue >>= bMerge; + if (bMerge) + { + // 'close' all the cell with the same left position + // if separate vertical merges in the same column exist + for(auto& aMergedCell : rMergedCells) + { + if(lcl_SimilarPosition(aMergedCell.nLeftPosition, nLeftPos)) + { + aMergedCell.bOpen = false; + } + } + // add the new group of merged cells + rMergedCells.emplace_back(xCellPS, nLeftPos); + } + else + { + bool bFound = false; + SAL_WARN_IF(rMergedCells.empty(), "sw.uno", "the first merged cell is missing"); + for(auto& aMergedCell : rMergedCells) + { + if (aMergedCell.bOpen && lcl_SimilarPosition(aMergedCell.nLeftPosition, nLeftPos)) + { + aMergedCell.aCells.push_back( xCellPS ); + bFound = true; + } + } + SAL_WARN_IF(!bFound, "sw.uno", "couldn't find first vertically merged cell" ); + } + } + else + { + try + { + xCellPS->setPropertyValue(rName, rValue); + } + catch (const uno::Exception& e) + { + SAL_WARN( "sw.uno", "Exception when getting PropertyState: " + + rName + ". Message: " + e.Message ); + } + } + } +} + +static void +lcl_MergeCells(std::vector<VerticallyMergedCell> & rMergedCells) +{ + for(auto& aMergedCell : rMergedCells) + { + // the first of the cells gets the number of cells set as RowSpan + // the others get the inverted number of remaining merged cells + // (3,-2,-1) + sal_Int32 nCellCount = static_cast<sal_Int32>(aMergedCell.aCells.size()); + if(nCellCount<2) + { + SAL_WARN("sw.uno", "incomplete vertical cell merge"); + continue; + } + aMergedCell.aCells.front()->setPropertyValue(UNO_NAME_ROW_SPAN, uno::makeAny(nCellCount--)); + nCellCount*=-1; + for(auto pxPSet = aMergedCell.aCells.begin()+1; nCellCount<0; ++pxPSet, ++nCellCount) + (*pxPSet)->setPropertyValue(UNO_NAME_ROW_SPAN, uno::makeAny(nCellCount)); + } +} + +uno::Reference< text::XTextTable > SAL_CALL +SwXText::convertToTable( + const uno::Sequence< uno::Sequence< uno::Sequence< + uno::Reference< text::XTextRange > > > >& rTableRanges, + const uno::Sequence< uno::Sequence< uno::Sequence< + beans::PropertyValue > > >& rCellProperties, + const uno::Sequence< uno::Sequence< beans::PropertyValue > >& + rRowProperties, + const uno::Sequence< beans::PropertyValue >& rTableProperties) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + { + throw uno::RuntimeException(); + } + + IDocumentRedlineAccess & rIDRA(m_pImpl->m_pDoc->getIDocumentRedlineAccess()); + if (!IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())) + { + throw uno::RuntimeException( + "cannot convertToTable if tracked changes are hidden!"); + } + + //at first collect the text ranges as SwPaMs + const uno::Sequence< uno::Sequence< uno::Reference< text::XTextRange > > >* + pTableRanges = rTableRanges.getConstArray(); + std::vector< std::vector<SwNodeRange> > aTableNodes; + for (sal_Int32 nRow = 0; nRow < rTableRanges.getLength(); ++nRow) + { + std::vector<SwNodeRange> aRowNodes; + const uno::Sequence< uno::Reference< text::XTextRange > >* pRow = + pTableRanges[nRow].getConstArray(); + const sal_Int32 nCells(pTableRanges[nRow].getLength()); + + if (0 == nCells) // this would lead to no pLastCell below + { // and make it impossible to detect node gaps + throw lang::IllegalArgumentException(); + } + + for (sal_Int32 nCell = 0; nCell < nCells; ++nCell) + { + SwNodeRange *const pLastCell( + (nCell == 0) + ? ((nRow == 0) + ? nullptr + : &*aTableNodes.rbegin()->rbegin()) + : &*aRowNodes.rbegin()); + m_pImpl->ConvertCell(pRow[nCell], aRowNodes, pLastCell); + } + assert(!aRowNodes.empty()); + aTableNodes.push_back(aRowNodes); + } + + std::vector< TableColumnSeparators > + aRowSeparators(rRowProperties.getLength()); + std::vector<VerticallyMergedCell> aMergedCells; + + SwTable const*const pTable = m_pImpl->m_pDoc->TextToTable( aTableNodes ); + + if (!pTable) + return uno::Reference< text::XTextTable >(); + + uno::Reference<text::XTextTable> const xRet = + SwXTextTable::CreateXTextTable(pTable->GetFrameFormat()); + uno::Reference<beans::XPropertySet> const xPrSet(xRet, uno::UNO_QUERY); + // set properties to the table + // catch lang::WrappedTargetException and lang::IndexOutOfBoundsException + try + { + //apply table properties + for(const auto& rTableProperty : rTableProperties) + { + try + { + xPrSet->setPropertyValue(rTableProperty.Name, rTableProperty.Value); + } + catch (const uno::Exception& e) + { + SAL_WARN( "sw.uno", "Exception when setting property: " + + rTableProperty.Name + ". Message: " + e.Message ); + } + } + + //apply row properties + const auto xRows = xRet->getRows(); + const sal_Int32 nLast = std::min(xRows->getCount(), rRowProperties.getLength()); + SAL_WARN_IF(nLast != rRowProperties.getLength(), "sw.uno", "not enough rows for properties"); + for(sal_Int32 nCnt = 0; nCnt < nLast; ++nCnt) + lcl_ApplyRowProperties(rRowProperties[nCnt], xRows->getByIndex(nCnt), aRowSeparators[nCnt]); + + uno::Reference<table::XCellRange> const xCR(xRet, uno::UNO_QUERY_THROW); + //apply cell properties + sal_Int32 nRow = 0; + for(const auto& rCellPropertiesForRow : rCellProperties) + { + sal_Int32 nCell = 0; + for(const auto& rCellProps : rCellPropertiesForRow) + { + lcl_ApplyCellProperties(lcl_GetLeftPos(nCell, aRowSeparators[nRow]), + rCellProps, + xCR->getCellByPosition(nCell, nRow), + aMergedCells); + ++nCell; + } + ++nRow; + } + + // now that the cell properties are set the vertical merge values + // have to be applied + lcl_MergeCells(aMergedCells); + } + catch (const lang::WrappedTargetException&) + { + } + catch (const lang::IndexOutOfBoundsException&) + { + } + + assert(SwTable::FindTable(pTable->GetFrameFormat()) == pTable); + assert(pTable->GetFrameFormat() == + dynamic_cast<SwXTextTable&>(*xRet).GetFrameFormat()); + return xRet; +} + +void SAL_CALL +SwXText::copyText( + const uno::Reference< text::XTextCopy >& xSource ) +{ + SolarMutexGuard aGuard; + + uno::Reference<lang::XUnoTunnel> const xSourceTunnel(xSource, + uno::UNO_QUERY); + SwXText const*const pSource( xSourceTunnel.is() + ? ::sw::UnoTunnelGetImplementation<SwXText>(xSourceTunnel) + : nullptr); + + uno::Reference< text::XText > const xText(xSource, uno::UNO_QUERY_THROW); + uno::Reference< text::XTextCursor > const xCursor = + xText->createTextCursor(); + xCursor->gotoEnd( true ); + + uno::Reference< lang::XUnoTunnel > const xCursorTunnel(xCursor, + uno::UNO_QUERY_THROW); + + OTextCursorHelper *const pCursor = + ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xCursorTunnel); + if (!pCursor) + { + throw uno::RuntimeException(); + } + + SwNodeIndex rNdIndex( *GetStartNode( ), 1 ); + SwPosition rPos( rNdIndex ); + // tdf#112202 need SwXText because cursor cannot select table at the start + if (pSource) + { + SwTextNode * pFirstNode; + { + SwPaM temp(*pSource->GetStartNode(), *pSource->GetStartNode()->EndOfSectionNode(), +1, -1); + pFirstNode = temp.GetMark()->nNode.GetNode().GetTextNode(); + if (pFirstNode) + { + pFirstNode->MakeStartIndex(&temp.GetMark()->nContent); + } + if (SwTextNode *const pNode = temp.GetPoint()->nNode.GetNode().GetTextNode()) + { + pNode->MakeEndIndex(&temp.GetPoint()->nContent); + } + // Explicitly request copy text mode, so + // sw::DocumentContentOperationsManager::CopyFlyInFlyImpl() will copy shapes anchored to + // us, even if we have only a single paragraph. + m_pImpl->m_pDoc->getIDocumentContentOperations().CopyRange(temp, rPos, SwCopyFlags::CheckPosInFly); + } + if (!pFirstNode) + { // the node at rPos was split; get rid of the first empty one so + // that the pasted table is first + auto pDelCursor(m_pImpl->m_pDoc->CreateUnoCursor(SwPosition(SwNodeIndex(*GetStartNode(), 1)))); + m_pImpl->m_pDoc->getIDocumentContentOperations().DelFullPara(*pDelCursor); + } + } + else + { + m_pImpl->m_pDoc->getIDocumentContentOperations().CopyRange(*pCursor->GetPaM(), rPos, SwCopyFlags::CheckPosInFly); + } + +} + +SwXBodyText::SwXBodyText(SwDoc *const pDoc) + : SwXText(pDoc, CursorType::Body) +{ +} + +SwXBodyText::~SwXBodyText() +{ +} + +OUString SAL_CALL +SwXBodyText::getImplementationName() +{ + return "SwXBodyText"; +} + +sal_Bool SAL_CALL SwXBodyText::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL +SwXBodyText::getSupportedServiceNames() +{ + return { "com.sun.star.text.Text" }; +} + +uno::Any SAL_CALL +SwXBodyText::queryAggregation(const uno::Type& rType) +{ + uno::Any aRet; + if (rType == cppu::UnoType<container::XEnumerationAccess>::get()) + { + aRet <<= uno::Reference< container::XEnumerationAccess >(this); + } + else if (rType == cppu::UnoType<container::XElementAccess>::get()) + { + aRet <<= uno::Reference< container::XElementAccess >(this); + } + else if (rType == cppu::UnoType<lang::XServiceInfo>::get()) + { + aRet <<= uno::Reference< lang::XServiceInfo >(this); + } + else + { + aRet = SwXText::queryInterface( rType ); + } + if(aRet.getValueType() == cppu::UnoType<void>::get()) + { + aRet = OWeakAggObject::queryAggregation( rType ); + } + return aRet; +} + +uno::Sequence< uno::Type > SAL_CALL +SwXBodyText::getTypes() +{ + const uno::Sequence< uno::Type > aTypes = SwXBodyText_Base::getTypes(); + const uno::Sequence< uno::Type > aTextTypes = SwXText::getTypes(); + return ::comphelper::concatSequences(aTypes, aTextTypes); +} + +uno::Sequence< sal_Int8 > SAL_CALL +SwXBodyText::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Any SAL_CALL +SwXBodyText::queryInterface(const uno::Type& rType) +{ + const uno::Any ret = SwXText::queryInterface(rType); + return (ret.getValueType() == cppu::UnoType<void>::get()) + ? SwXBodyText_Base::queryInterface(rType) + : ret; +} + +SwXTextCursor * SwXBodyText::CreateTextCursor(const bool bIgnoreTables) +{ + if(!IsValid()) + { + return nullptr; + } + + // the cursor has to skip tables contained in this text + SwPaM aPam(GetDoc()->GetNodes().GetEndOfContent()); + aPam.Move( fnMoveBackward, GoInDoc ); + if (!bIgnoreTables) + { + SwTableNode * pTableNode = aPam.GetNode().FindTableNode(); + SwContentNode * pCont = nullptr; + while (pTableNode) + { + aPam.GetPoint()->nNode = *pTableNode->EndOfSectionNode(); + pCont = GetDoc()->GetNodes().GoNext(&aPam.GetPoint()->nNode); + pTableNode = pCont->FindTableNode(); + } + if (pCont) + { + aPam.GetPoint()->nContent.Assign(pCont, 0); + } + } + return new SwXTextCursor(*GetDoc(), this, CursorType::Body, *aPam.GetPoint()); +} + +uno::Reference< text::XTextCursor > SAL_CALL +SwXBodyText::createTextCursor() +{ + SolarMutexGuard aGuard; + + const uno::Reference< text::XTextCursor > xRef( + static_cast<text::XWordCursor*>(CreateTextCursor()) ); + if (!xRef.is()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + return xRef; +} + +uno::Reference< text::XTextCursor > SAL_CALL +SwXBodyText::createTextCursorByRange( + const uno::Reference< text::XTextRange > & xTextPosition) +{ + SolarMutexGuard aGuard; + + if(!IsValid()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + uno::Reference< text::XTextCursor > aRef; + SwUnoInternalPaM aPam(*GetDoc()); + if (::sw::XTextRangeToSwPaM(aPam, xTextPosition)) + { + if ( !aPam.GetNode().GetTextNode() ) + throw uno::RuntimeException("Invalid text range" ); + + SwNode& rNode = GetDoc()->GetNodes().GetEndOfContent(); + + SwStartNode* p1 = aPam.GetNode().StartOfSectionNode(); + //document starts with a section? + while(p1->IsSectionNode()) + { + p1 = p1->StartOfSectionNode(); + } + SwStartNode *const p2 = rNode.StartOfSectionNode(); + + if(p1 == p2) + { + aRef = static_cast<text::XWordCursor*>( + new SwXTextCursor(*GetDoc(), this, CursorType::Body, + *aPam.GetPoint(), aPam.GetMark())); + } + } + if(!aRef.is()) + { + throw uno::RuntimeException( "End of content node doesn't have the proper start node", + uno::Reference< uno::XInterface >( *this ) ); + } + return aRef; +} + +uno::Reference< container::XEnumeration > SAL_CALL +SwXBodyText::createEnumeration() +{ + SolarMutexGuard aGuard; + + if (!IsValid()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + SwNode& rNode = GetDoc()->GetNodes().GetEndOfContent(); + SwPosition aPos(rNode); + auto pUnoCursor(GetDoc()->CreateUnoCursor(aPos)); + pUnoCursor->Move(fnMoveBackward, GoInDoc); + return SwXParagraphEnumeration::Create(this, pUnoCursor, CursorType::Body); +} + +uno::Type SAL_CALL +SwXBodyText::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL +SwXBodyText::hasElements() +{ + SolarMutexGuard aGuard; + + if (!IsValid()) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + return true; +} + +class SwXHeadFootText::Impl + : public SvtListener +{ + public: + SwFrameFormat* m_pHeadFootFormat; + bool m_bIsHeader; + + Impl(SwFrameFormat& rHeadFootFormat, const bool bIsHeader) + : m_pHeadFootFormat(&rHeadFootFormat) + , m_bIsHeader(bIsHeader) + { + StartListening(m_pHeadFootFormat->GetNotifier()); + } + + SwFrameFormat* GetHeadFootFormat() const { + return m_pHeadFootFormat; + } + + SwFrameFormat& GetHeadFootFormatOrThrow() { + if (!m_pHeadFootFormat) { + throw uno::RuntimeException("SwXHeadFootText: disposed or invalid", nullptr); + } + return *m_pHeadFootFormat; + } + protected: + virtual void Notify(const SfxHint& rHint) override + { + if(rHint.GetId() == SfxHintId::Dying) + m_pHeadFootFormat = nullptr; + } +}; + +uno::Reference<text::XText> SwXHeadFootText::CreateXHeadFootText( + SwFrameFormat& rHeadFootFormat, + const bool bIsHeader) +{ + // re-use existing SwXHeadFootText + // #i105557#: do not iterate over the registered clients: race condition + uno::Reference<text::XText> xText(rHeadFootFormat.GetXObject(), uno::UNO_QUERY); + if(!xText.is()) + { + const auto pXHFT(new SwXHeadFootText(rHeadFootFormat, bIsHeader)); + xText.set(pXHFT); + rHeadFootFormat.SetXObject(xText); + } + return xText; +} + +SwXHeadFootText::SwXHeadFootText(SwFrameFormat& rHeadFootFormat, const bool bIsHeader) + : SwXText( + rHeadFootFormat.GetDoc(), + bIsHeader ? CursorType::Header : CursorType::Footer) + , m_pImpl(new SwXHeadFootText::Impl(rHeadFootFormat, bIsHeader)) +{ +} + +SwXHeadFootText::~SwXHeadFootText() +{ } + +OUString SAL_CALL +SwXHeadFootText::getImplementationName() +{ + return {"SwXHeadFootText"}; +} + +sal_Bool SAL_CALL SwXHeadFootText::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> SAL_CALL +SwXHeadFootText::getSupportedServiceNames() +{ + return {"com.sun.star.text.Text"}; +} + +const SwStartNode* SwXHeadFootText::GetStartNode() const +{ + const SwStartNode* pSttNd = nullptr; + SwFrameFormat* const pHeadFootFormat = m_pImpl->GetHeadFootFormat(); + if(pHeadFootFormat) + { + const SwFormatContent& rFlyContent = pHeadFootFormat->GetContent(); + if(rFlyContent.GetContentIdx()) + { + pSttNd = rFlyContent.GetContentIdx()->GetNode().GetStartNode(); + } + } + return pSttNd; +} + +uno::Reference<text::XTextCursor> SwXHeadFootText::CreateCursor() +{ + return createTextCursor(); +} + +uno::Sequence<uno::Type> SAL_CALL SwXHeadFootText::getTypes() +{ + return ::comphelper::concatSequences( + SwXHeadFootText_Base::getTypes(), + SwXText::getTypes()); +} + +uno::Sequence<sal_Int8> SAL_CALL SwXHeadFootText::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Any SAL_CALL SwXHeadFootText::queryInterface(const uno::Type& rType) +{ + const uno::Any ret = SwXHeadFootText_Base::queryInterface(rType); + return (ret.getValueType() == cppu::UnoType<void>::get()) + ? SwXText::queryInterface(rType) + : ret; +} + +uno::Reference<text::XTextCursor> SAL_CALL +SwXHeadFootText::createTextCursor() +{ + SolarMutexGuard aGuard; + + SwFrameFormat & rHeadFootFormat( m_pImpl->GetHeadFootFormatOrThrow() ); + + const SwFormatContent& rFlyContent = rHeadFootFormat.GetContent(); + const SwNode& rNode = rFlyContent.GetContentIdx()->GetNode(); + SwPosition aPos(rNode); + SwXTextCursor *const pXCursor = new SwXTextCursor(*GetDoc(), this, + (m_pImpl->m_bIsHeader) ? CursorType::Header : CursorType::Footer, aPos); + auto& rUnoCursor(pXCursor->GetCursor()); + rUnoCursor.Move(fnMoveForward, GoInNode); + + // save current start node to be able to check if there is content + // after the table - otherwise the cursor would be in the body text! + SwStartNode const*const pOwnStartNode = rNode.FindSttNodeByType( + (m_pImpl->m_bIsHeader) ? SwHeaderStartNode : SwFooterStartNode); + // is there a table here? + SwTableNode* pTableNode = rUnoCursor.GetNode().FindTableNode(); + SwContentNode* pCont = nullptr; + while (pTableNode) + { + rUnoCursor.GetPoint()->nNode = *pTableNode->EndOfSectionNode(); + pCont = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode); + pTableNode = pCont->FindTableNode(); + } + if (pCont) + { + rUnoCursor.GetPoint()->nContent.Assign(pCont, 0); + } + SwStartNode const*const pNewStartNode = rUnoCursor.GetNode().FindSttNodeByType( + (m_pImpl->m_bIsHeader) ? SwHeaderStartNode : SwFooterStartNode); + if (!pNewStartNode || (pNewStartNode != pOwnStartNode)) + { + uno::RuntimeException aExcept; + aExcept.Message = "no text available"; + throw aExcept; + } + return static_cast<text::XWordCursor*>(pXCursor); +} + +uno::Reference<text::XTextCursor> SAL_CALL SwXHeadFootText::createTextCursorByRange( + const uno::Reference<text::XTextRange>& xTextPosition) +{ + SolarMutexGuard aGuard; + SwFrameFormat& rHeadFootFormat( m_pImpl->GetHeadFootFormatOrThrow() ); + + SwUnoInternalPaM aPam(*GetDoc()); + if (!sw::XTextRangeToSwPaM(aPam, xTextPosition)) + { + uno::RuntimeException aRuntime; + aRuntime.Message = cInvalidObject; + throw aRuntime; + } + + SwNode& rNode = rHeadFootFormat.GetContent().GetContentIdx()->GetNode(); + SwPosition aPos(rNode); + SwPaM aHFPam(aPos); + aHFPam.Move(fnMoveForward, GoInNode); + SwStartNode* const pOwnStartNode = aHFPam.GetNode().FindSttNodeByType( + (m_pImpl->m_bIsHeader) ? SwHeaderStartNode : SwFooterStartNode); + SwStartNode* const p1 = aPam.GetNode().FindSttNodeByType( + (m_pImpl->m_bIsHeader) ? SwHeaderStartNode : SwFooterStartNode); + if (p1 == pOwnStartNode) + { + return static_cast<text::XWordCursor*>( + new SwXTextCursor( + *GetDoc(), + this, + (m_pImpl->m_bIsHeader) ? CursorType::Header : CursorType::Footer, + *aPam.GetPoint(), aPam.GetMark())); + } + return nullptr; +} + +uno::Reference<container::XEnumeration> SAL_CALL SwXHeadFootText::createEnumeration() +{ + SolarMutexGuard aGuard; + SwFrameFormat& rHeadFootFormat(m_pImpl->GetHeadFootFormatOrThrow()); + + const SwFormatContent& rFlyContent = rHeadFootFormat.GetContent(); + const SwNode& rNode = rFlyContent.GetContentIdx()->GetNode(); + SwPosition aPos(rNode); + auto pUnoCursor(GetDoc()->CreateUnoCursor(aPos)); + pUnoCursor->Move(fnMoveForward, GoInNode); + return SwXParagraphEnumeration::Create( + this, + pUnoCursor, + (m_pImpl->m_bIsHeader) + ? CursorType::Header + : CursorType::Footer); +} + +uno::Type SAL_CALL SwXHeadFootText::getElementType() + { return cppu::UnoType<text::XTextRange>::get(); } + +sal_Bool SAL_CALL SwXHeadFootText::hasElements() + { return true; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unotextmarkup.cxx b/sw/source/core/unocore/unotextmarkup.cxx new file mode 100644 index 000000000..f20b47e15 --- /dev/null +++ b/sw/source/core/unocore/unotextmarkup.cxx @@ -0,0 +1,531 @@ +/* -*- 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 <unotextmarkup.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <svl/listener.hxx> +#include <vcl/svapp.hxx> +#include <SwSmartTagMgr.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/TextMarkupType.hpp> +#include <com/sun/star/text/TextMarkupDescriptor.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/container/XStringKeyMap.hpp> +#include <ndtxt.hxx> +#include <SwGrammarMarkUp.hxx> +#include <TextCursorHelper.hxx> + +#include <IGrammarContact.hxx> + +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/text/XTextRange.hpp> + +#include <pam.hxx> + +#include <unotextrange.hxx> +#include <modeltoviewhelper.hxx> + +using namespace ::com::sun::star; + +struct SwXTextMarkup::Impl + : public SvtListener +{ + SwTextNode* m_pTextNode; + ModelToViewHelper const m_ConversionMap; + + Impl(SwTextNode* const pTextNode, const ModelToViewHelper& rMap) + : m_pTextNode(pTextNode) + , m_ConversionMap(rMap) + { + if(m_pTextNode) + StartListening(pTextNode->GetNotifier()); + } + + virtual void Notify(const SfxHint& rHint) override; +}; + +SwXTextMarkup::SwXTextMarkup( + SwTextNode *const pTextNode, const ModelToViewHelper& rMap) + : m_pImpl(new Impl(pTextNode, rMap)) +{ +} + +SwXTextMarkup::~SwXTextMarkup() +{ +} + +SwTextNode* SwXTextMarkup::GetTextNode() +{ + return m_pImpl->m_pTextNode; +} + +void SwXTextMarkup::ClearTextNode() +{ + m_pImpl->m_pTextNode = nullptr; + m_pImpl->EndListeningAll(); +} + +const ModelToViewHelper& SwXTextMarkup::GetConversionMap() const +{ + return m_pImpl->m_ConversionMap; +} + +uno::Reference< container::XStringKeyMap > SAL_CALL SwXTextMarkup::getMarkupInfoContainer() +{ + SolarMutexGuard aGuard; + + uno::Reference< container::XStringKeyMap > xProp = new SwXStringKeyMap; + return xProp; +} + +void SAL_CALL SwXTextMarkup::commitTextRangeMarkup(::sal_Int32 nType, const OUString & aIdentifier, const uno::Reference< text::XTextRange> & xRange, + const uno::Reference< container::XStringKeyMap > & xMarkupInfoContainer) +{ + SolarMutexGuard aGuard; + + uno::Reference<lang::XUnoTunnel> xRangeTunnel( xRange, uno::UNO_QUERY); + + if(!xRangeTunnel.is()) return; + + SwXTextRange* pRange = nullptr; + OTextCursorHelper* pCursor = nullptr; + + if(xRangeTunnel.is()) + { + pRange = reinterpret_cast<SwXTextRange*>( sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething(SwXTextRange::getUnoTunnelId()))); + pCursor = reinterpret_cast<OTextCursorHelper*>( sal::static_int_cast< sal_IntPtr >( xRangeTunnel->getSomething(OTextCursorHelper::getUnoTunnelId()))); + } + + if (pRange) + { + SwDoc& rDoc = pRange->GetDoc(); + + SwUnoInternalPaM aPam(rDoc); + + ::sw::XTextRangeToSwPaM(aPam, xRange); + + SwPosition* startPos = aPam.Start(); + SwPosition* endPos = aPam.End(); + + commitStringMarkup (nType, aIdentifier, startPos->nContent.GetIndex(), endPos->nContent.GetIndex() - startPos->nContent.GetIndex(), xMarkupInfoContainer); + } + else if (pCursor) + { + SwPaM & rPam(*pCursor->GetPaM()); + + SwPosition* startPos = rPam.Start(); + SwPosition* endPos = rPam.End(); + + commitStringMarkup (nType, aIdentifier, startPos->nContent.GetIndex(), endPos->nContent.GetIndex() - startPos->nContent.GetIndex(), xMarkupInfoContainer); + } +} + +void SAL_CALL SwXTextMarkup::commitStringMarkup( + ::sal_Int32 nType, + const OUString & rIdentifier, + ::sal_Int32 nStart, + ::sal_Int32 nLength, + const uno::Reference< container::XStringKeyMap > & xMarkupInfoContainer) +{ + SolarMutexGuard aGuard; + + // paragraph already dead or modified? + if (!m_pImpl->m_pTextNode || nLength <= 0) + return; + + if ( nType == text::TextMarkupType::SMARTTAG && + !SwSmartTagMgr::Get().IsSmartTagTypeEnabled( rIdentifier ) ) + return; + + // get appropriate list to use... + SwWrongList* pWList = nullptr; + bool bRepaint = false; + if ( nType == text::TextMarkupType::SPELLCHECK ) + { + pWList = m_pImpl->m_pTextNode->GetWrong(); + if ( !pWList ) + { + pWList = new SwWrongList( WRONGLIST_SPELL ); + m_pImpl->m_pTextNode->SetWrong( pWList ); + } + } + else if ( nType == text::TextMarkupType::PROOFREADING || nType == text::TextMarkupType::SENTENCE ) + { + IGrammarContact *pGrammarContact = getGrammarContact(*m_pImpl->m_pTextNode); + if( pGrammarContact ) + { + pWList = pGrammarContact->getGrammarCheck(*m_pImpl->m_pTextNode, true); + OSL_ENSURE( pWList, "GrammarContact _has_ to deliver a wrong list" ); + } + else + { + pWList = m_pImpl->m_pTextNode->GetGrammarCheck(); + if ( !pWList ) + { + m_pImpl->m_pTextNode->SetGrammarCheck( new SwGrammarMarkUp() ); + pWList = m_pImpl->m_pTextNode->GetGrammarCheck(); + } + } + bRepaint = pWList == m_pImpl->m_pTextNode->GetGrammarCheck(); + if( pWList->GetBeginInv() < COMPLETE_STRING ) + static_cast<SwGrammarMarkUp*>(pWList)->ClearGrammarList(); + } + else if ( nType == text::TextMarkupType::SMARTTAG ) + { + pWList = m_pImpl->m_pTextNode->GetSmartTags(); + if ( !pWList ) + { + pWList = new SwWrongList( WRONGLIST_SMARTTAG ); + m_pImpl->m_pTextNode->SetSmartTags( pWList ); + } + } + else + { + OSL_FAIL( "Unknown mark-up type" ); + return; + } + + const ModelToViewHelper::ModelPosition aStartPos = + m_pImpl->m_ConversionMap.ConvertToModelPosition( nStart ); + const ModelToViewHelper::ModelPosition aEndPos = + m_pImpl->m_ConversionMap.ConvertToModelPosition( nStart + nLength - 1); + + const bool bStartInField = aStartPos.mbIsField; + const bool bEndInField = aEndPos.mbIsField; + bool bCommit = false; + + if ( bStartInField && bEndInField && aStartPos.mnPos == aEndPos.mnPos ) + { + nStart = aStartPos.mnSubPos; + const sal_Int32 nFieldPosModel = aStartPos.mnPos; + const sal_uInt16 nInsertPos = pWList->GetWrongPos( nFieldPosModel ); + + SwWrongList* pSubList = pWList->SubList( nInsertPos ); + if ( !pSubList ) + { + if( nType == text::TextMarkupType::PROOFREADING || nType == text::TextMarkupType::SENTENCE ) + pSubList = new SwGrammarMarkUp(); + else + pSubList = new SwWrongList( pWList->GetWrongListType() ); + pWList->InsertSubList( nFieldPosModel, 1, nInsertPos, pSubList ); + } + + pWList = pSubList; + bCommit = true; + } + else if ( !bStartInField && !bEndInField ) + { + nStart = aStartPos.mnPos; + bCommit = true; + nLength = aEndPos.mnPos + 1 - aStartPos.mnPos; + } + else if( nType == text::TextMarkupType::PROOFREADING || nType == text::TextMarkupType::SENTENCE ) + { + bCommit = true; + nStart = aStartPos.mnPos; + sal_Int32 nEnd = aEndPos.mnPos; + if( bStartInField && nType != text::TextMarkupType::SENTENCE ) + { + const sal_Int32 nFieldPosModel = aStartPos.mnPos; + const sal_uInt16 nInsertPos = pWList->GetWrongPos( nFieldPosModel ); + SwWrongList* pSubList = pWList->SubList( nInsertPos ); + if ( !pSubList ) + { + pSubList = new SwGrammarMarkUp(); + pWList->InsertSubList( nFieldPosModel, 1, nInsertPos, pSubList ); + } + const sal_Int32 nTmpStart = + m_pImpl->m_ConversionMap.ConvertToViewPosition(aStartPos.mnPos); + const sal_Int32 nTmpLen = + m_pImpl->m_ConversionMap.ConvertToViewPosition(aStartPos.mnPos + 1) + - nTmpStart - aStartPos.mnSubPos; + if( nTmpLen > 0 ) + { + pSubList->Insert( rIdentifier, xMarkupInfoContainer, aStartPos.mnSubPos, nTmpLen ); + } + ++nStart; + } + if( bEndInField && nType != text::TextMarkupType::SENTENCE ) + { + const sal_Int32 nFieldPosModel = aEndPos.mnPos; + const sal_uInt16 nInsertPos = pWList->GetWrongPos( nFieldPosModel ); + SwWrongList* pSubList = pWList->SubList( nInsertPos ); + if ( !pSubList ) + { + pSubList = new SwGrammarMarkUp(); + pWList->InsertSubList( nFieldPosModel, 1, nInsertPos, pSubList ); + } + const sal_Int32 nTmpLen = aEndPos.mnSubPos + 1; + pSubList->Insert( rIdentifier, xMarkupInfoContainer, 0, nTmpLen ); + } + else + ++nEnd; + if( nEnd > nStart ) + nLength = nEnd - nStart; + else + bCommit = false; + } + + if ( bCommit ) + { + if( nType == text::TextMarkupType::SENTENCE ) + static_cast<SwGrammarMarkUp*>(pWList)->setSentence( nStart ); + else + pWList->Insert( rIdentifier, xMarkupInfoContainer, nStart, nLength ); + } + + if( bRepaint ) + finishGrammarCheck(*m_pImpl->m_pTextNode); +} + +static void lcl_commitGrammarMarkUp( + const ModelToViewHelper& rConversionMap, + SwGrammarMarkUp* pWList, + ::sal_Int32 nType, + const OUString & rIdentifier, + ::sal_Int32 nStart, + ::sal_Int32 nLength, + const uno::Reference< container::XStringKeyMap > & xMarkupInfoContainer) +{ + OSL_ENSURE( nType == text::TextMarkupType::PROOFREADING || nType == text::TextMarkupType::SENTENCE, "Wrong mark-up type" ); + const ModelToViewHelper::ModelPosition aStartPos = + rConversionMap.ConvertToModelPosition( nStart ); + const ModelToViewHelper::ModelPosition aEndPos = + rConversionMap.ConvertToModelPosition( nStart + nLength - 1); + + const bool bStartInField = aStartPos.mbIsField; + const bool bEndInField = aEndPos.mbIsField; + bool bCommit = false; + + if ( bStartInField && bEndInField && aStartPos.mnPos == aEndPos.mnPos ) + { + nStart = aStartPos.mnSubPos; + const sal_Int32 nFieldPosModel = aStartPos.mnPos; + const sal_uInt16 nInsertPos = pWList->GetWrongPos( nFieldPosModel ); + + SwGrammarMarkUp* pSubList = static_cast<SwGrammarMarkUp*>(pWList->SubList( nInsertPos )); + if ( !pSubList ) + { + pSubList = new SwGrammarMarkUp(); + pWList->InsertSubList( nFieldPosModel, 1, nInsertPos, pSubList ); + } + + pWList = pSubList; + bCommit = true; + } + else if ( !bStartInField && !bEndInField ) + { + nStart = aStartPos.mnPos; + bCommit = true; + nLength = aEndPos.mnPos + 1 - aStartPos.mnPos; + } + else + { + bCommit = true; + nStart = aStartPos.mnPos; + sal_Int32 nEnd = aEndPos.mnPos; + if( bStartInField && nType != text::TextMarkupType::SENTENCE ) + { + const sal_Int32 nFieldPosModel = aStartPos.mnPos; + const sal_uInt16 nInsertPos = pWList->GetWrongPos( nFieldPosModel ); + SwGrammarMarkUp* pSubList = static_cast<SwGrammarMarkUp*>(pWList->SubList( nInsertPos )); + if ( !pSubList ) + { + pSubList = new SwGrammarMarkUp(); + pWList->InsertSubList( nFieldPosModel, 1, nInsertPos, pSubList ); + } + const sal_Int32 nTmpStart = rConversionMap.ConvertToViewPosition( aStartPos.mnPos ); + const sal_Int32 nTmpLen = rConversionMap.ConvertToViewPosition( aStartPos.mnPos + 1 ) + - nTmpStart - aStartPos.mnSubPos; + if( nTmpLen > 0 ) + pSubList->Insert( rIdentifier, xMarkupInfoContainer, aStartPos.mnSubPos, nTmpLen ); + ++nStart; + } + if( bEndInField && nType != text::TextMarkupType::SENTENCE ) + { + const sal_Int32 nFieldPosModel = aEndPos.mnPos; + const sal_uInt16 nInsertPos = pWList->GetWrongPos( nFieldPosModel ); + SwGrammarMarkUp* pSubList = static_cast<SwGrammarMarkUp*>(pWList->SubList( nInsertPos )); + if ( !pSubList ) + { + pSubList = new SwGrammarMarkUp(); + pWList->InsertSubList( nFieldPosModel, 1, nInsertPos, pSubList ); + } + const sal_Int32 nTmpLen = aEndPos.mnSubPos + 1; + pSubList->Insert( rIdentifier, xMarkupInfoContainer, 0, nTmpLen ); + } + else + ++nEnd; + if( nEnd > nStart ) + nLength = nEnd - nStart; + else + bCommit = false; + } + + if ( bCommit ) + { + if( nType == text::TextMarkupType::SENTENCE ) + pWList->setSentence( nStart+nLength ); + else + pWList->Insert( rIdentifier, xMarkupInfoContainer, nStart, nLength ); + } +} + +void SAL_CALL SwXTextMarkup::commitMultiTextMarkup( + const uno::Sequence< text::TextMarkupDescriptor > &rMarkups ) +{ + SolarMutexGuard aGuard; + + // paragraph already dead or modified? + if (!m_pImpl->m_pTextNode) + return; + + // for grammar checking there should be exactly one sentence markup + // and 0..n grammar markups. + // Different markups are not expected but may be applied anyway since + // that should be no problem... + // but it has to be implemented, at the moment only this function is for + // grammar markups and sentence markup only! + const text::TextMarkupDescriptor *pSentenceMarkUp = nullptr; + for( const text::TextMarkupDescriptor &rDesc : rMarkups ) + { + if (rDesc.nType == text::TextMarkupType::SENTENCE) + { + if (pSentenceMarkUp != nullptr) + throw lang::IllegalArgumentException(); // there is already one sentence markup + pSentenceMarkUp = &rDesc; + } + else if( rDesc.nType != text::TextMarkupType::PROOFREADING ) + return; + } + + if( pSentenceMarkUp == nullptr ) + return; + + // get appropriate list to use... + SwGrammarMarkUp* pWList = nullptr; + bool bRepaint = false; + IGrammarContact *pGrammarContact = getGrammarContact(*m_pImpl->m_pTextNode); + if( pGrammarContact ) + { + pWList = pGrammarContact->getGrammarCheck(*m_pImpl->m_pTextNode, true); + OSL_ENSURE( pWList, "GrammarContact _has_ to deliver a wrong list" ); + } + else + { + pWList = m_pImpl->m_pTextNode->GetGrammarCheck(); + if ( !pWList ) + { + m_pImpl->m_pTextNode->SetGrammarCheck( new SwGrammarMarkUp() ); + pWList = m_pImpl->m_pTextNode->GetGrammarCheck(); + pWList->SetInvalid( 0, COMPLETE_STRING ); + } + } + bRepaint = pWList == m_pImpl->m_pTextNode->GetGrammarCheck(); + + bool bAcceptGrammarError = false; + if( pWList->GetBeginInv() < COMPLETE_STRING ) + { + const ModelToViewHelper::ModelPosition aSentenceEnd = + m_pImpl->m_ConversionMap.ConvertToModelPosition( + pSentenceMarkUp->nOffset + pSentenceMarkUp->nLength ); + bAcceptGrammarError = aSentenceEnd.mnPos > pWList->GetBeginInv(); + pWList->ClearGrammarList( aSentenceEnd.mnPos ); + } + + if( bAcceptGrammarError ) + { + for( const text::TextMarkupDescriptor &rDesc : rMarkups ) + { + lcl_commitGrammarMarkUp(m_pImpl->m_ConversionMap, pWList, rDesc.nType, + rDesc.aIdentifier, rDesc.nOffset, rDesc.nLength, rDesc.xMarkupInfoContainer ); + } + } + else + { + bRepaint = false; + const text::TextMarkupDescriptor &rDesc = *pSentenceMarkUp; + lcl_commitGrammarMarkUp(m_pImpl->m_ConversionMap, pWList, rDesc.nType, + rDesc.aIdentifier, rDesc.nOffset, rDesc.nLength, rDesc.xMarkupInfoContainer ); + } + + if( bRepaint ) + finishGrammarCheck(*m_pImpl->m_pTextNode); +} + +void SwXTextMarkup::Impl::Notify(const SfxHint& rHint) +{ + DBG_TESTSOLARMUTEX(); + if(rHint.GetId() == SfxHintId::Dying) + { + m_pTextNode = nullptr; + } +} + +SwXStringKeyMap::SwXStringKeyMap() +{ +} + +uno::Any SAL_CALL SwXStringKeyMap::getValue(const OUString & aKey) +{ + std::map< OUString, uno::Any >::const_iterator aIter = maMap.find( aKey ); + if ( aIter == maMap.end() ) + throw container::NoSuchElementException(); + + return (*aIter).second; +} + +sal_Bool SAL_CALL SwXStringKeyMap::hasValue(const OUString & aKey) +{ + return maMap.find( aKey ) != maMap.end(); +} + +void SAL_CALL SwXStringKeyMap::insertValue(const OUString & aKey, const uno::Any & aValue) +{ + std::map< OUString, uno::Any >::const_iterator aIter = maMap.find( aKey ); + if ( aIter != maMap.end() ) + throw container::ElementExistException(); + + maMap[ aKey ] = aValue; +} + +::sal_Int32 SAL_CALL SwXStringKeyMap::getCount() +{ + return maMap.size(); +} + +OUString SAL_CALL SwXStringKeyMap::getKeyByIndex(::sal_Int32 nIndex) +{ + if ( o3tl::make_unsigned(nIndex) >= maMap.size() ) + throw lang::IndexOutOfBoundsException(); + + return OUString(); +} + +uno::Any SAL_CALL SwXStringKeyMap::getValueByIndex(::sal_Int32 nIndex) +{ + if ( o3tl::make_unsigned(nIndex) >= maMap.size() ) + throw lang::IndexOutOfBoundsException(); + + return uno::Any(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/dialoghelp.cxx b/sw/source/core/view/dialoghelp.cxx new file mode 100644 index 000000000..cdb476944 --- /dev/null +++ b/sw/source/core/view/dialoghelp.cxx @@ -0,0 +1,48 @@ +/* -*- 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/. + */ + +#include <sfx2/docfile.hxx> +#include <sfx2/frame.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> + +#include <dialoghelp.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <view.hxx> + +weld::Window* GetFrameWeld(const SfxFrame* pFrame) +{ + return pFrame ? pFrame->GetWindow().GetFrameWeld() : nullptr; +} + +weld::Window* GetFrameWeld(const SfxMedium* pMedium) +{ + return GetFrameWeld(pMedium ? pMedium->GetLoadTargetFrame() : nullptr); +} + +weld::Window* GetFrameWeld(SwDocShell* pDocShell) +{ + if (!pDocShell) + return nullptr; + weld::Window* pRet = GetFrameWeld(pDocShell->GetMedium()); + if (!pRet) + { + if (SwView* pView = pDocShell->GetView()) + pRet = pView->GetFrameWeld(); + } + return pRet; +} + +weld::Window* GetFrameWeld(SwDoc* pDoc) +{ + return GetFrameWeld(pDoc ? pDoc->GetDocShell() : nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/core/view/pagepreviewlayout.cxx b/sw/source/core/view/pagepreviewlayout.cxx new file mode 100644 index 000000000..b3b08c25a --- /dev/null +++ b/sw/source/core/view/pagepreviewlayout.cxx @@ -0,0 +1,1461 @@ +/* -*- 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 <pagepreviewlayout.hxx> +#include <prevwpage.hxx> + +#include <algorithm> +#include <tools/fract.hxx> +#include <vcl/window.hxx> +#include <vcl/settings.hxx> + +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewsh.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <swregion.hxx> +#include <strings.hrc> +#include <frmtool.hxx> +#include <sfx2/zoomitem.hxx> +#include <printdata.hxx> +#include <paintfrm.hxx> + +#include <IDocumentDeviceAccess.hxx> + +// methods to initialize page preview layout + +SwPagePreviewLayout::SwPagePreviewLayout( SwViewShell& _rParentViewShell, + const SwRootFrame& _rLayoutRootFrame ) + : mrParentViewShell( _rParentViewShell ), + mrLayoutRootFrame ( _rLayoutRootFrame ) +{ + Clear_(); + + mbBookPreview = false; + mbBookPreviewModeToggled = false; + + mbPrintEmptyPages = mrParentViewShell.getIDocumentDeviceAccess().getPrintData().IsPrintEmptyPages(); +} + +void SwPagePreviewLayout::Clear_() +{ + mbLayoutInfoValid = mbLayoutSizesValid = mbPaintInfoValid = false; + + maWinSize.setWidth( 0 ); + maWinSize.setHeight( 0 ); + mnCols = mnRows = 0; + + ClearPreviewLayoutSizes(); + + mbDoesLayoutRowsFitIntoWindow = false; + mbDoesLayoutColsFitIntoWindow = false; + + mnPaintPhyStartPageNum = 0; + mnPaintStartCol = mnPaintStartRow = 0; + mbNoPageVisible = false; + maPaintStartPageOffset.setX( 0 ); + maPaintStartPageOffset.setY( 0 ); + maPaintPreviewDocOffset.setX( 0 ); + maPaintPreviewDocOffset.setY( 0 ); + maAdditionalPaintOffset.setX( 0 ); + maAdditionalPaintOffset.setY( 0 ); + maPaintedPreviewDocRect.SetLeft( 0 ); + maPaintedPreviewDocRect.SetTop( 0 ); + maPaintedPreviewDocRect.SetRight( 0 ); + maPaintedPreviewDocRect.SetBottom( 0 ); + mnSelectedPageNum = 0; + ClearPreviewPageData(); + + mbInPaint = false; + mbNewLayoutDuringPaint = false; +} + +void SwPagePreviewLayout::ClearPreviewLayoutSizes() +{ + mnPages = 0; + + maMaxPageSize.setWidth( 0 ); + maMaxPageSize.setHeight( 0 ); + maPreviewDocRect.SetLeft( 0 ); + maPreviewDocRect.SetTop( 0 ); + maPreviewDocRect.SetRight( 0 ); + maPreviewDocRect.SetBottom( 0 ); + mnColWidth = mnRowHeight = 0; + mnPreviewLayoutWidth = mnPreviewLayoutHeight = 0; +} + +void SwPagePreviewLayout::ClearPreviewPageData() +{ + maPreviewPages.clear(); +} + +/** calculate page preview layout sizes + +*/ +void SwPagePreviewLayout::CalcPreviewLayoutSizes() +{ + vcl::RenderContext* pRenderContext = mrParentViewShell.GetOut(); + // calculate maximal page size; calculate also number of pages + + const SwPageFrame* pPage = static_cast<const SwPageFrame*>(mrLayoutRootFrame.Lower()); + while ( pPage ) + { + if ( !mbBookPreview && !mbPrintEmptyPages && pPage->IsEmptyPage() ) + { + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + continue; + } + + ++mnPages; + pPage->Calc(pRenderContext); + const Size& rPageSize = pPage->getFrameArea().SSize(); + if ( rPageSize.Width() > maMaxPageSize.Width() ) + maMaxPageSize.setWidth( rPageSize.Width() ); + if ( rPageSize.Height() > maMaxPageSize.Height() ) + maMaxPageSize.setHeight( rPageSize.Height() ); + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + // calculate and set column width and row height + mnColWidth = maMaxPageSize.Width() + gnXFree; + mnRowHeight = maMaxPageSize.Height() + gnYFree; + + // calculate and set preview layout width and height + mnPreviewLayoutWidth = mnCols * mnColWidth + gnXFree; + mnPreviewLayoutHeight = mnRows * mnRowHeight + gnYFree; + + // calculate document rectangle in preview layout + { + Size aDocSize; + // document width + aDocSize.setWidth( mnPreviewLayoutWidth ); + + // document height + // determine number of rows needed for <nPages> in preview layout + // use method <GetRowOfPage(..)>. + const sal_uInt16 nDocRows = GetRowOfPage( mnPages ); + aDocSize.setHeight( nDocRows * maMaxPageSize.Height() + + (nDocRows+1) * gnYFree ); + maPreviewDocRect.SetPos( Point( 0, 0 ) ); + maPreviewDocRect.SetSize( aDocSize ); + } +} + +/** init page preview layout + + initialize the page preview settings for a given layout. + + side effects: + (1) If parameter <_bCalcScale> is true, mapping mode with calculated + scaling is set at the output device and the zoom at the view options of + the given view shell is set with the calculated scaling. +*/ +void SwPagePreviewLayout::Init( const sal_uInt16 _nCols, + const sal_uInt16 _nRows, + const Size& _rPxWinSize + ) +{ + // check environment and parameters + { + bool bColsRowsValid = (_nCols != 0) && (_nRows != 0); + OSL_ENSURE( bColsRowsValid, "preview layout parameters not correct - preview layout can *not* be initialized" ); + if ( !bColsRowsValid ) + return; + + bool bPxWinSizeValid = (_rPxWinSize.Width() >= 0) && + (_rPxWinSize.Height() >= 0); + OSL_ENSURE( bPxWinSizeValid, "no window size - preview layout can *not* be initialized" ); + if ( !bPxWinSizeValid ) + return; + } + + // environment and parameters ok + + // clear existing preview settings + Clear_(); + + // set layout information columns and rows + mnCols = _nCols; + mnRows = _nRows; + + CalcPreviewLayoutSizes(); + + // validate layout information + mbLayoutInfoValid = true; + + // calculate scaling + MapMode aMapMode( MapUnit::MapTwip ); + Size aWinSize = mrParentViewShell.GetOut()->PixelToLogic( _rPxWinSize, aMapMode ); + Fraction aXScale( aWinSize.Width(), mnPreviewLayoutWidth ); + Fraction aYScale( aWinSize.Height(), mnPreviewLayoutHeight ); + if( aXScale < aYScale ) + aYScale = aXScale; + { + // adjust scaling for Drawing layer. + aYScale *= Fraction( 1000, 1 ); + long nNewNuminator = aYScale.operator long(); + if( nNewNuminator < 1 ) + nNewNuminator = 1; + aYScale = Fraction( nNewNuminator, 1000 ); + // propagate scaling as zoom percentage to view options for font cache + ApplyNewZoomAtViewShell( static_cast<sal_uInt8>(nNewNuminator/10) ); + + aMapMode.SetScaleY( aYScale ); + aMapMode.SetScaleX( aYScale ); + // set created mapping mode with calculated scaling at output device. + mrParentViewShell.GetOut()->SetMapMode( aMapMode ); + // update statics for paint. + ::SwCalcPixStatics( mrParentViewShell.GetOut() ); + } + + // set window size in twips + maWinSize = mrParentViewShell.GetOut()->PixelToLogic( _rPxWinSize ); + // validate layout sizes + mbLayoutSizesValid = true; +} + +/** apply new zoom at given view shell */ +void SwPagePreviewLayout::ApplyNewZoomAtViewShell( sal_uInt8 _aNewZoom ) +{ + SwViewOption aNewViewOptions = *(mrParentViewShell.GetViewOptions()); + if ( aNewViewOptions.GetZoom() != _aNewZoom ) + { + aNewViewOptions.SetZoom( _aNewZoom ); + //#i19975# - consider zoom type. + aNewViewOptions.SetZoomType( SvxZoomType::PERCENT ); + mrParentViewShell.ApplyViewOptions( aNewViewOptions ); + } +} + +/** method to adjust page preview layout to document changes + +*/ +void SwPagePreviewLayout::ReInit() +{ + // check environment and parameters + { + bool bLayoutSettingsValid = mbLayoutInfoValid && mbLayoutSizesValid; + OSL_ENSURE( bLayoutSettingsValid, + "no valid preview layout info/sizes - no re-init of page preview layout"); + if ( !bLayoutSettingsValid ) + return; + } + + ClearPreviewLayoutSizes(); + CalcPreviewLayoutSizes(); +} + +// methods to prepare paint of page preview + +/** prepare paint of page preview + + delete parameter _onStartPageVirtNum + + @note _nProposedStartPageNum, _onStartPageNum are absolute +*/ +bool SwPagePreviewLayout::Prepare( const sal_uInt16 _nProposedStartPageNum, + const Point& rProposedStartPos, + const Size& _rPxWinSize, + sal_uInt16& _onStartPageNum, + tools::Rectangle& _orDocPreviewPaintRect, + const bool _bStartWithPageAtFirstCol + ) +{ + sal_uInt16 nProposedStartPageNum = ConvertAbsoluteToRelativePageNum( _nProposedStartPageNum ); + // check environment and parameters + { + bool bLayoutSettingsValid = mbLayoutInfoValid && mbLayoutSizesValid; + OSL_ENSURE( bLayoutSettingsValid, + "no valid preview layout info/sizes - no prepare of preview paint"); + if ( !bLayoutSettingsValid ) + return false; + + bool bStartPageRangeValid = nProposedStartPageNum <= mnPages; + OSL_ENSURE( bStartPageRangeValid, + "proposed start page not existing - no prepare of preview paint"); + if ( !bStartPageRangeValid ) + return false; + + bool bStartPosRangeValid = + rProposedStartPos.X() >= 0 && rProposedStartPos.Y() >= 0 && + rProposedStartPos.X() <= maPreviewDocRect.Right() && + rProposedStartPos.Y() <= maPreviewDocRect.Bottom(); + OSL_ENSURE( bStartPosRangeValid, + "proposed start position out of range - no prepare of preview paint"); + if ( !bStartPosRangeValid ) + return false; + + bool bWinSizeValid = !_rPxWinSize.IsEmpty(); + OSL_ENSURE( bWinSizeValid, "no window size - no prepare of preview paint"); + if ( !bWinSizeValid ) + return false; + + bool bStartInfoValid = _nProposedStartPageNum > 0 || + rProposedStartPos != Point(0,0); + if ( !bStartInfoValid ) + nProposedStartPageNum = 1; + } + + // environment and parameter ok + + // update window size at preview setting data + maWinSize = mrParentViewShell.GetOut()->PixelToLogic( _rPxWinSize ); + + mbNoPageVisible = false; + if ( nProposedStartPageNum > 0 ) + { + // determine column and row of proposed start page in virtual preview layout + const sal_uInt16 nColOfProposed = GetColOfPage( nProposedStartPageNum ); + const sal_uInt16 nRowOfProposed = GetRowOfPage( nProposedStartPageNum ); + // determine start page + if ( _bStartWithPageAtFirstCol ) + { + // leaving left-top-corner blank is + // controlled by <mbBookPreview>. + if ( mbBookPreview && + ( nProposedStartPageNum == 1 || nRowOfProposed == 1 ) + ) + mnPaintPhyStartPageNum = 1; + else + mnPaintPhyStartPageNum = nProposedStartPageNum - (nColOfProposed-1); + } + else + mnPaintPhyStartPageNum = nProposedStartPageNum; + + mnPaintPhyStartPageNum = ConvertRelativeToAbsolutePageNum( mnPaintPhyStartPageNum ); + + // set starting column + if ( _bStartWithPageAtFirstCol ) + mnPaintStartCol = 1; + else + mnPaintStartCol = nColOfProposed; + // set starting row + mnPaintStartRow = nRowOfProposed; + // page offset == (-1,-1), indicating no offset and paint of free space. + maPaintStartPageOffset.setX( -1 ); + maPaintStartPageOffset.setY( -1 ); + // virtual preview document offset. + if ( _bStartWithPageAtFirstCol ) + maPaintPreviewDocOffset.setX( 0 ); + else + maPaintPreviewDocOffset.setX( (nColOfProposed-1) * mnColWidth ); + maPaintPreviewDocOffset.setY( (nRowOfProposed-1) * mnRowHeight ); + } + else + { + // determine column and row of proposed start position. + // Note: paint starts at point (0,0) + const sal_uInt16 nColOfProposed = + static_cast<sal_uInt16>(rProposedStartPos.X() / mnColWidth) + 1; + const sal_uInt16 nRowOfProposed = + static_cast<sal_uInt16>(rProposedStartPos.Y() / mnRowHeight) + 1; + // determine start page == page at proposed start position + // leaving left-top-corner blank is + // controlled by <mbBookPreview>. + if ( mbBookPreview && + ( nRowOfProposed == 1 && nColOfProposed == 1 ) + ) + mnPaintPhyStartPageNum = 1; + else + { + // leaving left-top-corner blank is + // controlled by <mbBookPreview>. + mnPaintPhyStartPageNum = (nRowOfProposed-1) * mnCols + nColOfProposed; + if ( mbBookPreview ) + --mnPaintPhyStartPageNum; + if ( mnPaintPhyStartPageNum > mnPages ) + { + // no page will be visible, because shown part of document + // preview is the last row to the right of the last page + mnPaintPhyStartPageNum = mnPages; + mbNoPageVisible = true; + } + } + // set starting column and starting row + mnPaintStartCol = nColOfProposed; + mnPaintStartRow = nRowOfProposed; + // page offset + maPaintStartPageOffset.setX( + (rProposedStartPos.X() % mnColWidth) - gnXFree ); + maPaintStartPageOffset.setY( + (rProposedStartPos.Y() % mnRowHeight) - gnYFree ); + // virtual preview document offset. + maPaintPreviewDocOffset = rProposedStartPos; + } + + // determine additional paint offset, if preview layout fits into window. + CalcAdditionalPaintOffset(); + + // determine rectangle to be painted from document preview + CalcDocPreviewPaintRect(); + _orDocPreviewPaintRect = maPaintedPreviewDocRect; + + // shift visible preview document area to the left, + // if on the right is an area left blank. + if ( !mbDoesLayoutColsFitIntoWindow && + maPaintedPreviewDocRect.GetWidth() < maWinSize.Width() ) + { + maPaintedPreviewDocRect.Move( + -(maWinSize.Width() - maPaintedPreviewDocRect.GetWidth()), 0 ); + Prepare( 0, maPaintedPreviewDocRect.TopLeft(), + _rPxWinSize, _onStartPageNum, + _orDocPreviewPaintRect, _bStartWithPageAtFirstCol ); + } + + // shift visible preview document area to the top, + // if on the bottom is an area left blank. + if ( mbBookPreviewModeToggled && + maPaintedPreviewDocRect.Bottom() == maPreviewDocRect.Bottom() && + maPaintedPreviewDocRect.GetHeight() < maWinSize.Height() ) + { + if ( mbDoesLayoutRowsFitIntoWindow ) + { + if ( maPaintedPreviewDocRect.GetHeight() < mnPreviewLayoutHeight) + { + maPaintedPreviewDocRect.Move( + 0, -(mnPreviewLayoutHeight - maPaintedPreviewDocRect.GetHeight()) ); + Prepare( 0, maPaintedPreviewDocRect.TopLeft(), + _rPxWinSize, _onStartPageNum, + _orDocPreviewPaintRect, _bStartWithPageAtFirstCol ); + } + } + else + { + maPaintedPreviewDocRect.Move( + 0, -(maWinSize.Height() - maPaintedPreviewDocRect.GetHeight()) ); + Prepare( 0, maPaintedPreviewDocRect.TopLeft(), + _rPxWinSize, _onStartPageNum, + _orDocPreviewPaintRect, _bStartWithPageAtFirstCol ); + } + } + + // determine preview pages - visible pages with needed data for paint and + // accessible pages with needed data. + CalcPreviewPages(); + + // OD 07.11.2003 #i22014# - indicate new layout, if print preview is in paint + if ( mbInPaint ) + { + mbNewLayoutDuringPaint = true; + } + + // validate paint data + mbPaintInfoValid = true; + + // return start page + _onStartPageNum = mnPaintPhyStartPageNum; + + return true; +} + +/** calculate additional paint offset + +*/ +void SwPagePreviewLayout::CalcAdditionalPaintOffset() +{ + if ( mnPreviewLayoutWidth <= maWinSize.Width() && + maPaintStartPageOffset.X() <= 0 ) + { + mbDoesLayoutColsFitIntoWindow = true; + maAdditionalPaintOffset.setX( (maWinSize.Width() - mnPreviewLayoutWidth) / 2 ); + } + else + { + mbDoesLayoutColsFitIntoWindow = false; + maAdditionalPaintOffset.setX( 0 ); + } + + if ( mnPreviewLayoutHeight <= maWinSize.Height() && + maPaintStartPageOffset.Y() <= 0 ) + { + mbDoesLayoutRowsFitIntoWindow = true; + maAdditionalPaintOffset.setY( (maWinSize.Height() - mnPreviewLayoutHeight) / 2 ); + } + else + { + mbDoesLayoutRowsFitIntoWindow = false; + maAdditionalPaintOffset.setY( 0 ); + } +} + +/** calculate painted preview document rectangle + +*/ +void SwPagePreviewLayout::CalcDocPreviewPaintRect() +{ + Point aTopLeftPos = maPaintPreviewDocOffset; + maPaintedPreviewDocRect.SetPos( aTopLeftPos ); + + Size aSize; + if ( mbDoesLayoutColsFitIntoWindow ) + aSize.setWidth( std::min( mnPreviewLayoutWidth, + maPreviewDocRect.GetWidth() - aTopLeftPos.X() ) ); + else + aSize.setWidth( std::min( maPreviewDocRect.GetWidth() - aTopLeftPos.X(), + maWinSize.Width() - maAdditionalPaintOffset.X() ) ); + if ( mbDoesLayoutRowsFitIntoWindow ) + aSize.setHeight( std::min( mnPreviewLayoutHeight, + maPreviewDocRect.GetHeight() - aTopLeftPos.Y() ) ); + else + aSize.setHeight( std::min( maPreviewDocRect.GetHeight() - aTopLeftPos.Y(), + maWinSize.Height() - maAdditionalPaintOffset.Y() ) ); + maPaintedPreviewDocRect.SetSize( aSize ); +} + +/** calculate preview pages + +*/ +void SwPagePreviewLayout::CalcPreviewPages() +{ + vcl::RenderContext* pRenderContext = mrParentViewShell.GetOut(); + ClearPreviewPageData(); + + if ( mbNoPageVisible ) + return; + + // determine start page frame + const SwPageFrame* pStartPage = mrLayoutRootFrame.GetPageByPageNum( mnPaintPhyStartPageNum ); + + // calculate initial paint offset + Point aInitialPaintOffset; + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()){ + if ( maPaintStartPageOffset != Point( -1, -1 ) ) + aInitialPaintOffset = Point(0,0) - maPaintStartPageOffset; + else + aInitialPaintOffset = Point( gnXFree, gnYFree ); + } + else { + if ( maPaintStartPageOffset != Point( -1, -1 ) ) + aInitialPaintOffset = Point(0 + ((SwPagePreviewLayout::mnCols-1)*mnColWidth),0) - maPaintStartPageOffset; + else + aInitialPaintOffset = Point( gnXFree + ((SwPagePreviewLayout::mnCols-1)*mnColWidth), gnYFree ); + } + aInitialPaintOffset += maAdditionalPaintOffset; + + // prepare loop data + const SwPageFrame* pPage = pStartPage; + sal_uInt16 nCurrCol = mnPaintStartCol; + sal_uInt16 nConsideredRows = 0; + Point aCurrPaintOffset = aInitialPaintOffset; + // loop on pages to determine preview background rectangles + while ( pPage && + (!mbDoesLayoutRowsFitIntoWindow || nConsideredRows < mnRows) && + aCurrPaintOffset.Y() < maWinSize.Height() + ) + { + if ( !mbBookPreview && !mbPrintEmptyPages && pPage->IsEmptyPage() ) + { + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + continue; + } + + pPage->Calc(pRenderContext); + + // consider only pages, which have to be painted. + if ( nCurrCol < mnPaintStartCol ) + { + // calculate data of unvisible page needed for accessibility + std::unique_ptr<PreviewPage> pPreviewPage(new PreviewPage); + Point aCurrAccOffset = aCurrPaintOffset - + Point( (mnPaintStartCol-nCurrCol) * mnColWidth, 0 ); + CalcPreviewDataForPage( *pPage, aCurrAccOffset, pPreviewPage.get() ); + pPreviewPage->bVisible = false; + maPreviewPages.push_back( std::move(pPreviewPage) ); + // continue with next page and next column + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + ++nCurrCol; + continue; + } + if ( aCurrPaintOffset.X() < maWinSize.Width() ) + { + // leaving left-top-corner blank is + // controlled by <mbBookPreview>. + if ( mbBookPreview && pPage->GetPhyPageNum() == 1 && mnCols != 1 && nCurrCol == 1 + ) + { + // first page in 2nd column + // --> continue with increased paint offset and next column + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()) + aCurrPaintOffset.AdjustX(mnColWidth ); + else aCurrPaintOffset.AdjustX( -mnColWidth ); + ++nCurrCol; + continue; + } + + // calculate data of visible page + std::unique_ptr<PreviewPage> pPreviewPage(new PreviewPage); + CalcPreviewDataForPage( *pPage, aCurrPaintOffset, pPreviewPage.get() ); + pPreviewPage->bVisible = true; + maPreviewPages.push_back( std::move(pPreviewPage) ); + } + else + { + // calculate data of unvisible page needed for accessibility + std::unique_ptr<PreviewPage> pPreviewPage(new PreviewPage); + CalcPreviewDataForPage( *pPage, aCurrPaintOffset, pPreviewPage.get() ); + pPreviewPage->bVisible = false; + maPreviewPages.push_back( std::move(pPreviewPage) ); + } + + // prepare data for next loop + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()) + aCurrPaintOffset.AdjustX(mnColWidth ); + else aCurrPaintOffset.AdjustX( -mnColWidth ); + ++nCurrCol; + if ( nCurrCol > mnCols ) + { + ++nConsideredRows; + aCurrPaintOffset.setX( aInitialPaintOffset.X() ); + nCurrCol = 1; + aCurrPaintOffset.AdjustY(mnRowHeight ); + } + } +} + +/** determines preview data for a given page and a given preview offset + + OD 13.12.2002 #103492# +*/ +void SwPagePreviewLayout::CalcPreviewDataForPage( const SwPageFrame& _rPage, + const Point& _rPreviewOffset, + PreviewPage* _opPreviewPage ) +{ + // page frame + _opPreviewPage->pPage = &_rPage; + // size of page frame + if ( _rPage.IsEmptyPage() ) + { + if ( _rPage.GetPhyPageNum() % 2 == 0 ) + _opPreviewPage->aPageSize = _rPage.GetPrev()->getFrameArea().SSize(); + else + _opPreviewPage->aPageSize = _rPage.GetNext()->getFrameArea().SSize(); + } + else + _opPreviewPage->aPageSize = _rPage.getFrameArea().SSize(); + // position of page in preview window + Point aPreviewWinOffset( _rPreviewOffset ); + if ( _opPreviewPage->aPageSize.Width() < maMaxPageSize.Width() ) + aPreviewWinOffset.AdjustX(( maMaxPageSize.Width() - _opPreviewPage->aPageSize.Width() ) / 2 ); + if ( _opPreviewPage->aPageSize.Height() < maMaxPageSize.Height() ) + aPreviewWinOffset.AdjustY(( maMaxPageSize.Height() - _opPreviewPage->aPageSize.Height() ) / 2 ); + _opPreviewPage->aPreviewWinPos = aPreviewWinOffset; + // logic position of page and mapping offset for paint + if ( _rPage.IsEmptyPage() ) + { + _opPreviewPage->aLogicPos = _opPreviewPage->aPreviewWinPos; + _opPreviewPage->aMapOffset = Point( 0, 0 ); + } + else + { + _opPreviewPage->aLogicPos = _rPage.getFrameArea().Pos(); + _opPreviewPage->aMapOffset = _opPreviewPage->aPreviewWinPos - _opPreviewPage->aLogicPos; + } +} + +/** enable/disable book preview + + OD 2004-03-04 #i18143# +*/ +bool SwPagePreviewLayout::SetBookPreviewMode( const bool _bEnableBookPreview, + sal_uInt16& _onStartPageNum, + tools::Rectangle& _orDocPreviewPaintRect ) +{ + if ( mbBookPreview != _bEnableBookPreview) + { + mbBookPreview = _bEnableBookPreview; + // re-initialize page preview layout + ReInit(); + // re-prepare page preview layout + { + mbBookPreviewModeToggled = true; + Point aProposedStartPos( maPaintPreviewDocOffset ); + // if proposed start position is below virtual preview document + // bottom, adjust it to the virtual preview document bottom + if ( aProposedStartPos.Y() > maPreviewDocRect.Bottom() ) + { + aProposedStartPos.setY( maPreviewDocRect.Bottom() ); + } + Prepare( 0, aProposedStartPos, + mrParentViewShell.GetOut()->LogicToPixel( maWinSize ), + _onStartPageNum, _orDocPreviewPaintRect ); + mbBookPreviewModeToggled = false; + } + + return true; + } + + return false; +} + +// methods to determine new data for changing the current shown part of the +// document preview. + +/** calculate start position for new scale + +*/ +Point SwPagePreviewLayout::GetPreviewStartPosForNewScale( + const Fraction& _aNewScale, + const Fraction& _aOldScale, + const Size& _aNewWinSize ) const +{ + Point aNewPaintStartPos = maPaintedPreviewDocRect.TopLeft(); + if ( _aNewScale < _aOldScale ) + { + // increase paint width by moving start point to left. + if ( mnPreviewLayoutWidth < _aNewWinSize.Width() ) + aNewPaintStartPos.setX( 0 ); + else if ( maPaintedPreviewDocRect.GetWidth() < _aNewWinSize.Width() ) + { + aNewPaintStartPos.AdjustX( -( + (_aNewWinSize.Width() - maPaintedPreviewDocRect.GetWidth()) / 2) ); + if ( aNewPaintStartPos.X() < 0) + aNewPaintStartPos.setX( 0 ); + } + + if ( !mbDoesLayoutRowsFitIntoWindow ) + { + // increase paint height by moving start point to top. + if ( mnPreviewLayoutHeight < _aNewWinSize.Height() ) + { + aNewPaintStartPos.setY( + (mnPaintStartRow - 1) * mnRowHeight ); + } + else if ( maPaintedPreviewDocRect.GetHeight() < _aNewWinSize.Height() ) + { + aNewPaintStartPos.AdjustY( -( + (_aNewWinSize.Height() - maPaintedPreviewDocRect.GetHeight()) / 2) ); + if ( aNewPaintStartPos.Y() < 0) + aNewPaintStartPos.setY( 0 ); + } + } + } + else + { + // decrease paint width by moving start point to right + if ( maPaintedPreviewDocRect.GetWidth() > _aNewWinSize.Width() ) + aNewPaintStartPos.AdjustX( + (maPaintedPreviewDocRect.GetWidth() - _aNewWinSize.Width()) / 2 ); + // decrease paint height by moving start point to bottom + if ( maPaintedPreviewDocRect.GetHeight() > _aNewWinSize.Height() ) + { + aNewPaintStartPos.AdjustY( + (maPaintedPreviewDocRect.GetHeight() - _aNewWinSize.Height()) / 2 ); + // check, if new y-position is outside document preview + if ( aNewPaintStartPos.Y() > maPreviewDocRect.Bottom() ) + aNewPaintStartPos.setY( + std::max( 0L, maPreviewDocRect.Bottom() - mnPreviewLayoutHeight ) ); + } + } + + return aNewPaintStartPos; +} + +/** determines, if page with given page number is visible in preview + + @note _nPageNum is absolute +*/ +bool SwPagePreviewLayout::IsPageVisible( const sal_uInt16 _nPageNum ) const +{ + const PreviewPage* pPreviewPage = GetPreviewPageByPageNum( _nPageNum ); + return pPreviewPage && pPreviewPage->bVisible; +} + +/** calculate data to bring new selected page into view. + + @note IN/OUT parameters are absolute page numbers!!! +*/ +void SwPagePreviewLayout::CalcStartValuesForSelectedPageMove( + const sal_Int16 _nHoriMove, + const sal_Int16 _nVertMove, + sal_uInt16& _orNewSelectedPage, + sal_uInt16& _orNewStartPage, + Point& _orNewStartPos ) const +{ + // determine position of current selected page + sal_uInt16 nTmpRelSelPageNum = ConvertAbsoluteToRelativePageNum( mnSelectedPageNum ); + sal_uInt16 nNewRelSelectedPageNum = nTmpRelSelPageNum; + + const sal_uInt16 nCurrRow = GetRowOfPage(nTmpRelSelPageNum); + + // determine new selected page number + { + if ( _nHoriMove != 0 ) + { + if ( (nNewRelSelectedPageNum + _nHoriMove) < 1 ) + nNewRelSelectedPageNum = 1; + else if ( (nNewRelSelectedPageNum + _nHoriMove) > mnPages ) + nNewRelSelectedPageNum = mnPages; + else + nNewRelSelectedPageNum = nNewRelSelectedPageNum + _nHoriMove; + } + if ( _nVertMove != 0 ) + { + if ( (nNewRelSelectedPageNum + (_nVertMove * mnCols)) < 1 ) + nNewRelSelectedPageNum = 1; + else if ( (nNewRelSelectedPageNum + (_nVertMove * mnCols)) > mnPages ) + nNewRelSelectedPageNum = mnPages; + else + nNewRelSelectedPageNum += ( _nVertMove * mnCols ); + } + } + + sal_uInt16 nNewStartPage = mnPaintPhyStartPageNum; + Point aNewStartPos(0,0); + + const sal_uInt16 nNewAbsSelectedPageNum = ConvertRelativeToAbsolutePageNum( nNewRelSelectedPageNum ); + if ( !IsPageVisible( nNewAbsSelectedPageNum ) ) + { + if ( _nHoriMove != 0 && _nVertMove != 0 ) + { + OSL_FAIL( "missing implementation for moving preview selected page horizontal AND vertical"); + return; + } + + // new selected page has to be brought into view considering current + // visible preview. + const sal_uInt16 nTotalRows = GetRowOfPage( mnPages ); + if ( (_nHoriMove > 0 || _nVertMove > 0) && + mbDoesLayoutRowsFitIntoWindow && + mbDoesLayoutColsFitIntoWindow && + nCurrRow > nTotalRows - mnRows ) + { + // new proposed start page = left-top-corner of last possible + // preview page. + nNewStartPage = (nTotalRows - mnRows) * mnCols + 1; + // leaving left-top-corner blank is controlled + // by <mbBookPreview>. + if ( mbBookPreview ) + { + // Note: decrease new proposed start page number by one, + // because of blank left-top-corner + --nNewStartPage; + } + nNewStartPage = ConvertRelativeToAbsolutePageNum( nNewStartPage ); + } + else + { + // new proposed start page = new selected page. + nNewStartPage = ConvertRelativeToAbsolutePageNum( nNewRelSelectedPageNum ); + } + } + + _orNewSelectedPage = nNewAbsSelectedPageNum; + _orNewStartPage = nNewStartPage; + _orNewStartPos = aNewStartPos; +} + +namespace { + +/** checks, if given position is inside a shown document page */ +struct PreviewPosInsidePagePred +{ + const Point mnPreviewPos; + explicit PreviewPosInsidePagePred(const Point& rPreviewPos) + : mnPreviewPos( rPreviewPos ) + {} + bool operator() ( const std::unique_ptr<PreviewPage> & _pPreviewPage ) + { + if ( _pPreviewPage->bVisible ) + { + tools::Rectangle aPreviewPageRect( _pPreviewPage->aPreviewWinPos, _pPreviewPage->aPageSize ); + return aPreviewPageRect.IsInside( mnPreviewPos ); + } + return false; + } +}; + +} + +bool SwPagePreviewLayout::IsPreviewPosInDocPreviewPage( const Point& rPreviewPos, + Point& _orDocPos, + bool& _obPosInEmptyPage, + sal_uInt16& _onPageNum ) const +{ + // initialize variable parameter values. + _orDocPos.setX( 0 ); + _orDocPos.setY( 0 ); + _obPosInEmptyPage = false; + _onPageNum = 0; + + auto aFoundPreviewPageIter = + std::find_if( maPreviewPages.begin(), maPreviewPages.end(), + PreviewPosInsidePagePred( rPreviewPos ) ); + + if ( aFoundPreviewPageIter != maPreviewPages.end() ) + { + // given preview position is inside a document page. + _onPageNum = (*aFoundPreviewPageIter)->pPage->GetPhyPageNum(); + _obPosInEmptyPage = (*aFoundPreviewPageIter)->pPage->IsEmptyPage(); + if ( !_obPosInEmptyPage ) + { + // given preview position inside a normal page + _orDocPos = rPreviewPos - + (*aFoundPreviewPageIter)->aPreviewWinPos + + (*aFoundPreviewPageIter)->aLogicPos; + return true; + } + } + + return false; +} + +/** determine window page scroll amount */ +SwTwips SwPagePreviewLayout::GetWinPagesScrollAmount( + const sal_Int16 _nWinPagesToScroll ) const +{ + SwTwips nScrollAmount; + if ( mbDoesLayoutRowsFitIntoWindow ) + { + nScrollAmount = (mnPreviewLayoutHeight - gnYFree) * _nWinPagesToScroll; + } + else + nScrollAmount = _nWinPagesToScroll * maPaintedPreviewDocRect.GetHeight(); + + // check, if preview layout size values are valid. + // If not, the checks for an adjustment of the scroll amount aren't useful. + if ( mbLayoutSizesValid ) + { + if ( (maPaintedPreviewDocRect.Top() + nScrollAmount) <= 0 ) + nScrollAmount = -maPaintedPreviewDocRect.Top(); + + // correct scroll amount + if ( nScrollAmount > 0 && + maPaintedPreviewDocRect.Bottom() == maPreviewDocRect.Bottom() + ) + { + nScrollAmount = 0; + } + else + { + while ( (maPaintedPreviewDocRect.Top() + nScrollAmount + gnYFree) >= maPreviewDocRect.GetHeight() ) + { + nScrollAmount -= mnRowHeight; + } + } + } + + return nScrollAmount; +} + +// methods to paint page preview layout + +namespace +{ +/// Similar to RenderContextGuard, but does not touch the draw view. +class PreviewRenderContextGuard +{ + VclPtr<vcl::RenderContext> m_pOriginalValue; + SwViewShell& m_rShell; + +public: + PreviewRenderContextGuard(SwViewShell& rShell, vcl::RenderContext* pValue) + : m_pOriginalValue(rShell.GetOut()), + m_rShell(rShell) + { + m_rShell.SetOut(pValue); + } + + ~PreviewRenderContextGuard() + { + m_rShell.SetOut(m_pOriginalValue); + } +}; +} + +/** paint prepared preview + +*/ +bool SwPagePreviewLayout::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rOutRect) const +{ + PreviewRenderContextGuard aGuard(mrParentViewShell, &rRenderContext); + // check environment and parameters + { + if (!mrParentViewShell.GetWin() && !mrParentViewShell.GetOut()->GetConnectMetaFile()) + { + return false; + } + + OSL_ENSURE(mbPaintInfoValid, "invalid preview settings - no paint of preview"); + if (!mbPaintInfoValid) + return false; + } + + // OD 17.11.2003 #i22014# - no paint, if <superfluous> flag is set at layout + if (mrLayoutRootFrame.IsSuperfluous()) + { + return true; + } + + // environment and parameter ok + + if (mbInPaint) + { + return false; + } + mbInPaint = true; + + OutputDevice* pOutputDev = &rRenderContext; + + // prepare paint + if ( !maPreviewPages.empty() ) + { + mrParentViewShell.Imp()->m_bFirstPageInvalid = false; + mrParentViewShell.Imp()->m_pFirstVisiblePage = + const_cast<SwPageFrame*>(maPreviewPages[0]->pPage); + } + + // paint preview background + { + SwRegionRects aPreviewBackgrdRegion(rOutRect); + // calculate preview background rectangles + for ( auto & rpPreviewPage : maPreviewPages ) + { + if ( rpPreviewPage->bVisible ) + { + aPreviewBackgrdRegion -= + SwRect( rpPreviewPage->aPreviewWinPos, rpPreviewPage->aPageSize ); + } + } + // paint preview background rectangles + mrParentViewShell.PaintDesktop_(aPreviewBackgrdRegion); + } + + // prepare data for paint of pages + const tools::Rectangle aPxOutRect( pOutputDev->LogicToPixel(rOutRect) ); + + MapMode aMapMode( pOutputDev->GetMapMode() ); + MapMode aSavedMapMode = aMapMode; + + const vcl::Font& rEmptyPgFont = SwPageFrame::GetEmptyPageFont(); + + for ( auto & rpPreviewPage : maPreviewPages ) + { + if ( !rpPreviewPage->bVisible ) + continue; + + tools::Rectangle aPageRect( rpPreviewPage->aLogicPos, rpPreviewPage->aPageSize ); + aMapMode.SetOrigin( rpPreviewPage->aMapOffset ); + pOutputDev->SetMapMode( aMapMode ); + tools::Rectangle aPxPaintRect = pOutputDev->LogicToPixel( aPageRect ); + if ( aPxOutRect.IsOver( aPxPaintRect) ) + { + const SwPageFrame* pPage = rpPreviewPage->pPage; + + if (pPage->IsEmptyPage()) + { + const Color aRetouche( mrParentViewShell.Imp()->GetRetoucheColor() ); + if( pOutputDev->GetFillColor() != aRetouche ) + pOutputDev->SetFillColor( aRetouche ); + pOutputDev->SetLineColor(); // no line color + // use aligned page rectangle + { + SwRect aTmpPageRect( aPageRect ); + ::SwAlignRect( aTmpPageRect, &mrParentViewShell, &rRenderContext ); + aPageRect = aTmpPageRect.SVRect(); + } + pOutputDev->DrawRect( aPageRect ); + + // paint empty page text + vcl::Font aOldFont( pOutputDev->GetFont() ); + pOutputDev->SetFont( rEmptyPgFont ); + pOutputDev->DrawText( aPageRect, SwResId( STR_EMPTYPAGE ), + DrawTextFlags::VCenter | + DrawTextFlags::Center | + DrawTextFlags::Clip ); + pOutputDev->SetFont( aOldFont ); + // paint shadow and border for empty page + // use new method to paint page border and shadow + SwPageFrame::PaintBorderAndShadow( aPageRect, &mrParentViewShell, true, false, true ); + } + else + { + const bool bIsLeftShadowed = pPage->IsLeftShadowNeeded(); + const bool bIsRightShadowed = pPage->IsRightShadowNeeded(); + + mrParentViewShell.maVisArea = aPageRect; + aPxPaintRect.Intersection( aPxOutRect ); + tools::Rectangle aPaintRect = pOutputDev->PixelToLogic( aPxPaintRect ); + mrParentViewShell.Paint(rRenderContext, aPaintRect); + + // --> OD 2007-08-15 #i80691# + // paint page border and shadow + { + SwRect aPageBorderRect; + SwPageFrame::GetBorderAndShadowBoundRect( SwRect( aPageRect ), &mrParentViewShell, &rRenderContext, aPageBorderRect, + bIsLeftShadowed, bIsRightShadowed, true ); + const vcl::Region aDLRegion(aPageBorderRect.SVRect()); + mrParentViewShell.DLPrePaint2(aDLRegion); + SwPageFrame::PaintBorderAndShadow( aPageRect, &mrParentViewShell, true, false, true ); + mrParentViewShell.DLPostPaint2(true); + } + // <-- + } + // OD 07.11.2003 #i22014# - stop painting, because new print + // preview layout is created during paint. + if ( mbNewLayoutDuringPaint ) + { + break; + } + + if (pPage->GetPhyPageNum() == mnSelectedPageNum) + { + PaintSelectMarkAtPage(rRenderContext, rpPreviewPage.get()); + } + } + } + + // OD 17.11.2003 #i22014# - no update of accessible preview, if a new + // print preview layout is created during paint. + if ( !mbNewLayoutDuringPaint ) + { + // update at accessibility interface + mrParentViewShell.Imp()->UpdateAccessiblePreview( + maPreviewPages, + aMapMode.GetScaleX(), + mrLayoutRootFrame.GetPageByPageNum( mnSelectedPageNum ), + maWinSize ); + } + + pOutputDev->SetMapMode( aSavedMapMode ); + mrParentViewShell.maVisArea.Clear(); + + // OD 07.11.2003 #i22014# + mbInPaint = false; + mbNewLayoutDuringPaint = false; + + return true; +} + +/** repaint pages on page preview + + OD 18.12.2002 #103492# +*/ +void SwPagePreviewLayout::Repaint( const tools::Rectangle& rInvalidCoreRect ) const +{ + // check environment and parameters + { + if ( !mrParentViewShell.GetWin() && + !mrParentViewShell.GetOut()->GetConnectMetaFile() ) + return; + + OSL_ENSURE( mbPaintInfoValid, + "invalid preview settings - no paint of preview" ); + if ( !mbPaintInfoValid ) + return; + } + + // environment and parameter ok + + // prepare paint + if ( !maPreviewPages.empty() ) + { + mrParentViewShell.Imp()->m_bFirstPageInvalid = false; + mrParentViewShell.Imp()->m_pFirstVisiblePage = + const_cast<SwPageFrame*>(maPreviewPages[0]->pPage); + } + + // invalidate visible pages, which overlap the invalid core rectangle + for ( auto & rpPreviewPage : maPreviewPages ) + { + if ( !rpPreviewPage->bVisible ) + continue; + + tools::Rectangle aPageRect( rpPreviewPage->aLogicPos, rpPreviewPage->aPageSize ); + if ( rInvalidCoreRect.IsOver( aPageRect ) ) + { + aPageRect.Intersection(rInvalidCoreRect); + tools::Rectangle aInvalidPreviewRect = aPageRect; + aInvalidPreviewRect.SetPos( aInvalidPreviewRect.TopLeft() - + rpPreviewPage->aLogicPos + + rpPreviewPage->aPreviewWinPos ); + mrParentViewShell.GetWin()->Invalidate( aInvalidPreviewRect ); + } + } +} + +/** paint selection mark at page + + OD 17.12.2002 #103492# +*/ +void SwPagePreviewLayout::PaintSelectMarkAtPage(vcl::RenderContext& rRenderContext, + const PreviewPage* _aSelectedPreviewPage ) const +{ + OutputDevice* pOutputDev = &rRenderContext; + MapMode aMapMode( pOutputDev->GetMapMode() ); + // save mapping mode of output device + MapMode aSavedMapMode = aMapMode; + // save fill and line color of output device + Color aFill( pOutputDev->GetFillColor() ); + Color aLine( pOutputDev->GetLineColor() ); + + // determine selection mark color + Color aSelPgLineColor(117, 114, 106); + const StyleSettings& rSettings = + mrParentViewShell.GetWin()->GetSettings().GetStyleSettings(); + if ( rSettings.GetHighContrastMode() ) + aSelPgLineColor = rSettings.GetHighlightTextColor(); + + // set needed mapping mode at output device + aMapMode.SetOrigin( _aSelectedPreviewPage->aMapOffset ); + pOutputDev->SetMapMode( aMapMode ); + + // calculate page rectangle in pixel coordinates + SwRect aPageRect( _aSelectedPreviewPage->aLogicPos, + _aSelectedPreviewPage->aPageSize ); + // OD 19.02.2003 #107369# - use aligned page rectangle, as it is used for + // page border and shadow paint - see <SwPageFrame::PaintBorderAndShadow(..)> + ::SwAlignRect( aPageRect, &mrParentViewShell, pOutputDev ); + tools::Rectangle aPxPageRect = pOutputDev->LogicToPixel( aPageRect.SVRect() ); + + // draw two rectangle + // OD 19.02.2003 #107369# - adjust position of select mark rectangle + tools::Rectangle aRect( aPxPageRect.Left(), aPxPageRect.Top(), + aPxPageRect.Right(), aPxPageRect.Bottom() ); + aRect = pOutputDev->PixelToLogic( aRect ); + pOutputDev->SetFillColor(); // OD 20.02.2003 #107369# - no fill color + pOutputDev->SetLineColor( aSelPgLineColor ); + pOutputDev->DrawRect( aRect ); + // OD 19.02.2003 #107369# - adjust position of select mark rectangle + aRect = tools::Rectangle( aPxPageRect.Left()+1, aPxPageRect.Top()+1, + aPxPageRect.Right()-1, aPxPageRect.Bottom()-1 ); + aRect = pOutputDev->PixelToLogic( aRect ); + pOutputDev->DrawRect( aRect ); + + // reset fill and line color of output device + pOutputDev->SetFillColor( aFill ); + pOutputDev->SetLineColor( aLine ); + + // reset mapping mode of output device + pOutputDev->SetMapMode( aSavedMapMode ); +} + +/** paint to mark new selected page + + OD 17.12.2002 #103492# + Perform paint for current selected page in order to unmark it. + Set new selected page and perform paint to mark this page. + + @note _nSelectedPage, mnSelectedPage are absolute +*/ +void SwPagePreviewLayout::MarkNewSelectedPage( const sal_uInt16 _nSelectedPage ) +{ + const sal_uInt16 nOldSelectedPageNum = mnSelectedPageNum; + mnSelectedPageNum = _nSelectedPage; + + // re-paint for current selected page in order to unmark it. + const PreviewPage* pOldSelectedPreviewPage = GetPreviewPageByPageNum( nOldSelectedPageNum ); + OutputDevice* pOutputDev = mrParentViewShell.GetOut(); + if ( pOldSelectedPreviewPage && pOldSelectedPreviewPage->bVisible ) + { + // OD 20.02.2003 #107369# - invalidate only areas of selection mark. + SwRect aPageRect( pOldSelectedPreviewPage->aPreviewWinPos, + pOldSelectedPreviewPage->aPageSize ); + ::SwAlignRect( aPageRect, &mrParentViewShell, pOutputDev ); + tools::Rectangle aPxPageRect = pOutputDev->LogicToPixel( aPageRect.SVRect() ); + // invalidate top mark line + tools::Rectangle aInvalPxRect( aPxPageRect.Left(), aPxPageRect.Top(), + aPxPageRect.Right(), aPxPageRect.Top()+1 ); + mrParentViewShell.GetWin()->Invalidate( pOutputDev->PixelToLogic( aInvalPxRect ) ); + // invalidate right mark line + aInvalPxRect = tools::Rectangle( aPxPageRect.Right()-1, aPxPageRect.Top(), + aPxPageRect.Right(), aPxPageRect.Bottom() ); + mrParentViewShell.GetWin()->Invalidate( pOutputDev->PixelToLogic( aInvalPxRect ) ); + // invalidate bottom mark line + aInvalPxRect = tools::Rectangle( aPxPageRect.Left(), aPxPageRect.Bottom()-1, + aPxPageRect.Right(), aPxPageRect.Bottom() ); + mrParentViewShell.GetWin()->Invalidate( pOutputDev->PixelToLogic( aInvalPxRect ) ); + // invalidate left mark line + aInvalPxRect = tools::Rectangle( aPxPageRect.Left(), aPxPageRect.Top(), + aPxPageRect.Left()+1, aPxPageRect.Bottom() ); + mrParentViewShell.GetWin()->Invalidate( pOutputDev->PixelToLogic( aInvalPxRect ) ); + } + + // re-paint for new selected page in order to mark it. + const PreviewPage* pNewSelectedPreviewPage = GetPreviewPageByPageNum( _nSelectedPage ); + if ( pNewSelectedPreviewPage && pNewSelectedPreviewPage->bVisible ) + { + const PreviewPage* pSelectedPreviewPage = GetPreviewPageByPageNum(mnSelectedPageNum); + SwRect aPageRect(pSelectedPreviewPage->aPreviewWinPos, pSelectedPreviewPage->aPageSize); + ::SwAlignRect(aPageRect, &mrParentViewShell, pOutputDev); + mrParentViewShell.GetWin()->Invalidate(aPageRect.SVRect()); + } +} + +// helper methods + +namespace { + +/** get preview page by physical page number + + OD 17.12.2002 #103492# +*/ +struct EqualsPageNumPred +{ + const sal_uInt16 mnPageNum; + explicit EqualsPageNumPred(const sal_uInt16 _nPageNum) + : mnPageNum( _nPageNum ) + {} + bool operator() ( const std::unique_ptr<PreviewPage> & _pPreviewPage ) + { + return _pPreviewPage->pPage->GetPhyPageNum() == mnPageNum; + } +}; + +} + +const PreviewPage* SwPagePreviewLayout::GetPreviewPageByPageNum( const sal_uInt16 _nPageNum ) const +{ + auto aFoundPreviewPageIter = + std::find_if( maPreviewPages.begin(), maPreviewPages.end(), + EqualsPageNumPred( _nPageNum ) ); + + if ( aFoundPreviewPageIter == maPreviewPages.end() ) + return nullptr; + + return aFoundPreviewPageIter->get(); +} + +/** determine row the page with the given number is in + + OD 17.01.2003 #103492# + + @note _nPageNum is relative +*/ +sal_uInt16 SwPagePreviewLayout::GetRowOfPage( sal_uInt16 _nPageNum ) const +{ + // OD 19.02.2003 #107369# - leaving left-top-corner blank is controlled + // by <mbBookPreview>. + if ( mbBookPreview ) + { + // Note: increase given physical page number by one, because left-top-corner + // in the preview layout is left blank. + ++_nPageNum; + } + + return _nPageNum / mnCols + ((_nPageNum % mnCols)>0 ? 1 : 0); +} + +/** determine column the page with the given number is in + + OD 17.01.2003 #103492# + + @note _nPageNum is relative +*/ +sal_uInt16 SwPagePreviewLayout::GetColOfPage( sal_uInt16 _nPageNum ) const +{ + // OD 19.02.2003 #107369# - leaving left-top-corner blank is controlled + // by <mbBookPreview>. + if ( mbBookPreview ) + { + // Note: increase given physical page number by one, because left-top-corner + // in the preview layout is left blank. + ++_nPageNum; + } + + const sal_uInt16 nCol = _nPageNum % mnCols; + return nCol ? nCol : mnCols; +} + +Size SwPagePreviewLayout::GetPreviewDocSize() const +{ + OSL_ENSURE( PreviewLayoutValid(), "PagePreviewLayout not valid" ); + return maPreviewDocRect.GetSize(); +} + +/** get size of a preview page by its physical page number + + OD 15.01.2003 #103492# +*/ +Size SwPagePreviewLayout::GetPreviewPageSizeByPageNum( sal_uInt16 _nPageNum ) const +{ + const PreviewPage* pPreviewPage = GetPreviewPageByPageNum( _nPageNum ); + if ( pPreviewPage ) + { + return pPreviewPage->aPageSize; + } + return Size( 0, 0 ); +} + +/** get virtual page number by its physical page number + + OD 21.03.2003 #108282# +*/ +sal_uInt16 SwPagePreviewLayout::GetVirtPageNumByPageNum( sal_uInt16 _nPageNum ) const +{ + const PreviewPage* pPreviewPage = GetPreviewPageByPageNum( _nPageNum ); + if ( pPreviewPage ) + { + return pPreviewPage->pPage->GetVirtPageNum(); + } + return 0; +} + +/** Convert absolute to relative page numbers (see PrintEmptyPages) */ +sal_uInt16 SwPagePreviewLayout::ConvertAbsoluteToRelativePageNum( sal_uInt16 _nAbsPageNum ) const +{ + if ( mbBookPreview || mbPrintEmptyPages || !_nAbsPageNum ) + { + return _nAbsPageNum; + } + + const SwPageFrame* pTmpPage = static_cast<const SwPageFrame*>(mrLayoutRootFrame.Lower()); + + sal_uInt16 nRet = 1; + + while ( pTmpPage && pTmpPage->GetPhyPageNum() != _nAbsPageNum ) + { + if ( !pTmpPage->IsEmptyPage() ) + ++nRet; + + pTmpPage = static_cast<const SwPageFrame*>( pTmpPage->GetNext() ); + } + + return nRet; +} + +/** Convert relative to absolute page numbers (see PrintEmptyPages) */ +sal_uInt16 SwPagePreviewLayout::ConvertRelativeToAbsolutePageNum( sal_uInt16 _nRelPageNum ) const +{ + if ( mbBookPreview || mbPrintEmptyPages || !_nRelPageNum ) + { + return _nRelPageNum; + } + + const SwPageFrame* pTmpPage = static_cast<const SwPageFrame*>(mrLayoutRootFrame.Lower()); + const SwPageFrame* pRet = nullptr; + + sal_uInt16 i = 0; + while( pTmpPage && i != _nRelPageNum ) + { + if ( !pTmpPage->IsEmptyPage() ) + ++i; + + pRet = pTmpPage; + pTmpPage = static_cast<const SwPageFrame*>( pTmpPage->GetNext() ); + } + + return pRet->GetPhyPageNum(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/printdata.cxx b/sw/source/core/view/printdata.cxx new file mode 100644 index 000000000..4001314a9 --- /dev/null +++ b/sw/source/core/view/printdata.cxx @@ -0,0 +1,459 @@ +/* -*- 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 <printdata.hxx> + +#include <strings.hrc> +#include <doc.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <unotxdoc.hxx> +#include <wdocsh.hxx> +#include <viewsh.hxx> +#include <docfld.hxx> + +#include <svl/languageoptions.hxx> +#include <toolkit/awt/vclxdevice.hxx> +#include <unotools/moduleoptions.hxx> +#include <vcl/outdev.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +SwRenderData::SwRenderData() +{ +} + +SwRenderData::~SwRenderData() +{ + OSL_ENSURE( !m_pPostItShell, "m_pPostItShell should already have been deleted" ); + OSL_ENSURE( !m_pPostItFields, " should already have been deleted" ); +} + +void SwRenderData::CreatePostItData( SwDoc *pDoc, const SwViewOption *pViewOpt, OutputDevice *pOutDev ) +{ + DeletePostItData(); + m_pPostItFields.reset(new SetGetExpFields); + sw_GetPostIts( &pDoc->getIDocumentFieldsAccess(), m_pPostItFields.get() ); + + //!! Disable spell and grammar checking in the temporary document. + //!! Otherwise the grammar checker might process it and crash if we later on + //!! simply delete this document while he is still at it. + SwViewOption aViewOpt( *pViewOpt ); + aViewOpt.SetOnlineSpell( false ); + + m_pPostItShell.reset(new SwViewShell(*new SwDoc, nullptr, &aViewOpt, pOutDev)); +} + +void SwRenderData::DeletePostItData() +{ + if (HasPostItData()) + { + // printer needs to remain at the real document + m_pPostItShell->GetDoc()->getIDocumentDeviceAccess().setPrinter( nullptr, false, false ); + { // avoid destroying layout from SwDoc dtor + rtl::Reference<SwDoc> const xKeepAlive(m_pPostItShell->GetDoc()); + m_pPostItShell.reset(); + } + m_pPostItFields.reset(); + } +} + + +void SwRenderData::SetTempDocShell(SfxObjectShellLock const& xShell) +{ + m_xTempDocShell = xShell; +} + +bool SwRenderData::NeedNewViewOptionAdjust( const SwViewShell& rCompare ) const +{ + return !(m_pViewOptionAdjust && m_pViewOptionAdjust->checkShell( rCompare )); +} + +void SwRenderData::ViewOptionAdjustStart( + SwViewShell &rSh, const SwViewOption &rViewOptions) +{ + if (m_pViewOptionAdjust) + { + OSL_FAIL("error: there should be no ViewOptionAdjust active when calling this function" ); + } + m_pViewOptionAdjust.reset( + new SwViewOptionAdjust_Impl( rSh, rViewOptions )); +} + +void SwRenderData::ViewOptionAdjust(SwPrintData const*const pPrtOptions, bool setShowPlaceHoldersInPDF) +{ + m_pViewOptionAdjust->AdjustViewOptions( pPrtOptions, setShowPlaceHoldersInPDF ); +} + +void SwRenderData::ViewOptionAdjustStop() +{ + m_pViewOptionAdjust.reset(); +} + +void SwRenderData::ViewOptionAdjustCrashPreventionKludge() +{ + m_pViewOptionAdjust->DontTouchThatViewShellItSmellsFunny(); +} + +void SwRenderData::MakeSwPrtOptions( + SwDocShell const*const pDocShell, + SwPrintUIOptions const*const pOpt, + bool const bIsPDFExport) +{ + if (!pDocShell || !pOpt) + return; + + m_pPrtOptions.reset(new SwPrintData); + SwPrintData & rOptions(*m_pPrtOptions); + + // get default print options + bool bWeb = dynamic_cast<const SwWebDocShell*>( pDocShell) != nullptr; + ::sw::InitPrintOptionsFromApplication(rOptions, bWeb); + + // get print options to use from provided properties + rOptions.m_bPrintGraphic = pOpt->IsPrintGraphics(); + rOptions.m_bPrintTable = true; // for now it was decided that tables should always be printed + rOptions.m_bPrintDraw = pOpt->IsPrintDrawings(); + rOptions.m_bPrintControl = pOpt->IsPrintFormControls(); + rOptions.m_bPrintLeftPages = pOpt->IsPrintLeftPages(); + rOptions.m_bPrintRightPages = pOpt->IsPrintRightPages(); + rOptions.m_bPrintPageBackground = pOpt->IsPrintPageBackground(); + rOptions.m_bPrintEmptyPages = pOpt->IsPrintEmptyPages( bIsPDFExport ); + // bUpdateFieldsInPrinting <-- not set here; mail merge only + rOptions.m_bPaperFromSetup = pOpt->IsPaperFromSetup(); + rOptions.m_bPrintReverse = false; /*handled by print dialog now*/ + rOptions.m_bPrintProspect = pOpt->IsPrintProspect(); + rOptions.m_bPrintProspectRTL = pOpt->IsPrintProspectRTL(); + // bPrintSingleJobs <-- not set here; mail merge and or configuration + // bModified <-- not set here; mail merge only + rOptions.m_bPrintBlackFont = pOpt->IsPrintWithBlackTextColor(); + rOptions.m_bPrintHiddenText = pOpt->IsPrintHiddenText(); + rOptions.m_bPrintTextPlaceholder = pOpt->IsPrintTextPlaceholders(); + rOptions.m_nPrintPostIts = pOpt->GetPrintPostItsType(); + + //! needs to be set after MakeOptions since the assignment operation in that + //! function will destroy the pointers + rOptions.SetRenderData( this ); +} + +SwPrintUIOptions::SwPrintUIOptions( + sal_uInt16 nCurrentPage, + bool bWeb, + bool bSwSrcView, + bool bHasSelection, + bool bHasPostIts, + const SwPrintData &rDefaultPrintData ) : + m_rDefaultPrintData( rDefaultPrintData ) +{ + // printing HTML sources does not have any valid UI options. + // It's just the source code that gets printed... + if (bSwSrcView) + { + m_aUIProperties.clear(); + return; + } + + // check if either CJK or CTL is enabled + SvtLanguageOptions aLangOpt; + bool bRTL = aLangOpt.IsCJKFontEnabled() || aLangOpt.IsCTLFontEnabled(); + + // create sequence of print UI options + // (5 options are not available for Writer-Web) + const int nRTLOpts = bRTL ? 1 : 0; + const int nNumProps = nRTLOpts + (bWeb ? 15 : 19); + m_aUIProperties.resize( nNumProps); + int nIdx = 0; + + // load the writer PrinterOptions into the custom tab + m_aUIProperties[nIdx].Name = "OptionsUIFile"; + m_aUIProperties[nIdx++].Value <<= OUString("modules/swriter/ui/printeroptions.ui"); + + // create "writer" section (new tab page in dialog) + SvtModuleOptions aModOpt; + OUString aAppGroupname( SwResId( STR_PRINTOPTUI_PRODUCTNAME) ); + aAppGroupname = aAppGroupname.replaceFirst( "%s", aModOpt.GetModuleName( SvtModuleOptions::EModule::WRITER ) ); + m_aUIProperties[ nIdx++ ].Value = setGroupControlOpt("tabcontrol-page2", aAppGroupname, ".HelpID:vcl:PrintDialog:TabPage:AppPage"); + + // create sub section for Contents + m_aUIProperties[ nIdx++ ].Value = setSubgroupControlOpt("contents", SwResId( STR_PRINTOPTUI_CONTENTS), OUString()); + + // create a bool option for background + bool bDefaultVal = rDefaultPrintData.IsPrintPageBackground(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("pagebackground", SwResId( STR_PRINTOPTUI_PAGE_BACKGROUND), + ".HelpID:vcl:PrintDialog:PrintPageBackground:CheckBox", + "PrintPageBackground", + bDefaultVal); + + // create a bool option for pictures/graphics AND OLE and drawing objects as well + bDefaultVal = rDefaultPrintData.IsPrintGraphic() || rDefaultPrintData.IsPrintDraw(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("pictures", SwResId( STR_PRINTOPTUI_PICTURES), + ".HelpID:vcl:PrintDialog:PrintPicturesAndObjects:CheckBox", + "PrintPicturesAndObjects", + bDefaultVal); + if (!bWeb) + { + // create a bool option for hidden text + bDefaultVal = rDefaultPrintData.IsPrintHiddenText(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("hiddentext", SwResId( STR_PRINTOPTUI_HIDDEN), + ".HelpID:vcl:PrintDialog:PrintHiddenText:CheckBox", + "PrintHiddenText", + bDefaultVal); + + // create a bool option for place holder + bDefaultVal = rDefaultPrintData.IsPrintTextPlaceholder(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("placeholders", SwResId( STR_PRINTOPTUI_TEXT_PLACEHOLDERS), + ".HelpID:vcl:PrintDialog:PrintTextPlaceholder:CheckBox", + "PrintTextPlaceholder", + bDefaultVal); + } + + // create a bool option for controls + bDefaultVal = rDefaultPrintData.IsPrintControl(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("formcontrols", SwResId( STR_PRINTOPTUI_FORM_CONTROLS), + ".HelpID:vcl:PrintDialog:PrintControls:CheckBox", + "PrintControls", + bDefaultVal); + + // create sub section for Color + m_aUIProperties[ nIdx++ ].Value = setSubgroupControlOpt("color", SwResId( STR_PRINTOPTUI_COLOR), OUString()); + + // create a bool option for printing text with black font color + bDefaultVal = rDefaultPrintData.IsPrintBlackFont(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("textinblack", SwResId( STR_PRINTOPTUI_PRINT_BLACK), + ".HelpID:vcl:PrintDialog:PrintBlackFonts:CheckBox", + "PrintBlackFonts", + bDefaultVal); + + if (!bWeb) + { + // create subgroup for misc options + m_aUIProperties[ nIdx++ ].Value = setSubgroupControlOpt("pages", SwResId( STR_PRINTOPTUI_PAGES_TEXT), OUString()); + + // create a bool option for printing automatically inserted blank pages + bDefaultVal = rDefaultPrintData.IsPrintEmptyPages(); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("autoblankpages", SwResId( STR_PRINTOPTUI_PRINT_BLANK), + ".HelpID:vcl:PrintDialog:PrintEmptyPages:CheckBox", + "PrintEmptyPages", + bDefaultVal); + } + + // create a bool option for paper tray + bDefaultVal = rDefaultPrintData.IsPaperFromSetup(); + vcl::PrinterOptionsHelper::UIControlOptions aPaperTrayOpt; + aPaperTrayOpt.maGroupHint = "OptionsPageOptGroup"; + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("printpaperfromsetup", SwResId( STR_PRINTOPTUI_ONLY_PAPER), + ".HelpID:vcl:PrintDialog:PrintPaperFromSetup:CheckBox", + "PrintPaperFromSetup", + bDefaultVal, + aPaperTrayOpt); + + // print range selection + vcl::PrinterOptionsHelper::UIControlOptions aPrintRangeOpt; + aPrintRangeOpt.maGroupHint = "PrintRange"; + aPrintRangeOpt.mbInternalOnly = true; + m_aUIProperties[nIdx++].Value = setSubgroupControlOpt( "printrange", + SwResId( STR_PRINTOPTUI_PAGES_TEXT ), + OUString(), + aPrintRangeOpt ); + + // create a choice for the content to create + const OUString aPrintRangeName( "PrintContent" ); + uno::Sequence< OUString > aChoices( 3 ); + uno::Sequence< sal_Bool > aChoicesDisabled( 3 ); + uno::Sequence< OUString > aHelpIds( 3 ); + uno::Sequence< OUString > aWidgetIds( 3 ); + aChoices[0] = SwResId( STR_PRINTOPTUI_PRINTALLPAGES ); + aChoicesDisabled[0] = false; + aHelpIds[0] = ".HelpID:vcl:PrintDialog:PrintContent:RadioButton:0"; + aWidgetIds[0] = "rbAllPages"; + aChoices[1] = SwResId( STR_PRINTOPTUI_PRINTPAGES ); + aChoicesDisabled[1] = false; + aHelpIds[1] = ".HelpID:vcl:PrintDialog:PrintContent:RadioButton:1"; + aWidgetIds[1] = "rbRangePages"; + aChoices[2] = SwResId( STR_PRINTOPTUI_PRINTSELECTION ); + aChoicesDisabled[2] = !bHasSelection; + aHelpIds[2] = ".HelpID:vcl:PrintDialog:PrintContent:RadioButton:2"; + aWidgetIds[2] = "rbRangeSelection"; + m_aUIProperties[nIdx++].Value = setChoiceRadiosControlOpt(aWidgetIds, OUString(), + aHelpIds, aPrintRangeName, + aChoices, + bHasSelection ? 2 : 0, + aChoicesDisabled); + + // show an Edit dependent on "Pages" selected + vcl::PrinterOptionsHelper::UIControlOptions aPageRangeOpt( aPrintRangeName, 1, true ); + m_aUIProperties[nIdx++].Value = setEditControlOpt("pagerange", OUString(), + ".HelpID:vcl:PrintDialog:PageRange:Edit", + "PageRange", + OUString::number( nCurrentPage ) /* set text box to current page number */, + aPageRangeOpt); + + vcl::PrinterOptionsHelper::UIControlOptions aEvenOddOpt(aPrintRangeName, -1, true); + m_aUIProperties[ nIdx++ ].Value = setChoiceListControlOpt("evenoddbox", + OUString(), + uno::Sequence<OUString>(), + "EvenOdd", + uno::Sequence<OUString>(), + 0, + uno::Sequence< sal_Bool >(), + aEvenOddOpt); + + // create a list box for notes content + const SwPostItMode nPrintPostIts = rDefaultPrintData.GetPrintPostIts(); + aChoices.realloc( 5 ); + aChoices[0] = SwResId( STR_PRINTOPTUI_NONE); + aChoices[1] = SwResId( STR_PRINTOPTUI_COMMENTS_ONLY); + aChoices[2] = SwResId( STR_PRINTOPTUI_PLACE_END); + aChoices[3] = SwResId( STR_PRINTOPTUI_PLACE_PAGE); + aChoices[4] = SwResId( STR_PRINTOPTUI_PLACE_MARGINS); + aHelpIds.realloc( 2 ); + aHelpIds[0] = ".HelpID:vcl:PrintDialog:PrintAnnotationMode:FixedText"; + aHelpIds[1] = ".HelpID:vcl:PrintDialog:PrintAnnotationMode:ListBox"; + vcl::PrinterOptionsHelper::UIControlOptions aAnnotOpt( "PrintProspect", 0, false ); + aAnnotOpt.mbEnabled = bHasPostIts; + m_aUIProperties[ nIdx++ ].Value = setChoiceListControlOpt("writercomments", + SwResId( STR_PRINTOPTUI_COMMENTS), + aHelpIds, + "PrintAnnotationMode", + aChoices, + bHasPostIts ? static_cast<sal_uInt16>(nPrintPostIts) : 0, + uno::Sequence< sal_Bool >(), + aAnnotOpt); + + // create subsection for Page settings + vcl::PrinterOptionsHelper::UIControlOptions aPageSetOpt; + aPageSetOpt.maGroupHint = "LayoutPage"; + + // create a bool option for brochure + bDefaultVal = rDefaultPrintData.IsPrintProspect(); + const OUString aBrochurePropertyName( "PrintProspect" ); + m_aUIProperties[ nIdx++ ].Value = setBoolControlOpt("brochure", SwResId( STR_PRINTOPTUI_BROCHURE), + ".HelpID:vcl:PrintDialog:PrintProspect:CheckBox", + aBrochurePropertyName, + bDefaultVal, + aPageSetOpt); + + if (bRTL) + { + // create a bool option for brochure RTL dependent on brochure + uno::Sequence< OUString > aBRTLChoices( 2 ); + aBRTLChoices[0] = SwResId( STR_PRINTOPTUI_LEFT_SCRIPT); + aBRTLChoices[1] = SwResId( STR_PRINTOPTUI_RIGHT_SCRIPT); + vcl::PrinterOptionsHelper::UIControlOptions aBrochureRTLOpt( aBrochurePropertyName, -1, true ); + uno::Sequence<OUString> aBRTLHelpIds { ".HelpID:vcl:PrintDialog:PrintProspectRTL:ListBox" }; + aBrochureRTLOpt.maGroupHint = "LayoutPage"; + // RTL brochure choices + // 0 : left-to-right + // 1 : right-to-left + const sal_Int16 nBRTLChoice = rDefaultPrintData.IsPrintProspectRTL() ? 1 : 0; + m_aUIProperties[ nIdx++ ].Value = setChoiceListControlOpt("scriptdirection", + OUString(), + aBRTLHelpIds, + "PrintProspectRTL", + aBRTLChoices, + nBRTLChoice, + uno::Sequence< sal_Bool >(), + aBrochureRTLOpt); + } + + assert(nIdx == nNumProps); +} + +SwPrintUIOptions::~SwPrintUIOptions() +{ +} + +bool SwPrintUIOptions::IsPrintLeftPages() const +{ + // take care of different property names for the option. + // for compatibility the old name should win (may still be used for PDF export or via Uno API) + + // 0: left and right pages + // 1: left pages only + // 2: right pages only + sal_Int64 nEOPages = getIntValue( "EvenOdd", 0 /* default: all */ ); + bool bRes = nEOPages != 1; + bRes = getBoolValue( "EvenOdd", bRes /* <- default value if property is not found */ ); + return bRes; +} + +bool SwPrintUIOptions::IsPrintRightPages() const +{ + // take care of different property names for the option. + // for compatibility the old name should win (may still be used for PDF export or via Uno API) + + sal_Int64 nEOPages = getIntValue( "EvenOdd", 0 /* default: all */ ); + bool bRes = nEOPages != 2; + bRes = getBoolValue( "EvenOdd", bRes /* <- default value if property is not found */ ); + return bRes; +} + +bool SwPrintUIOptions::IsPrintEmptyPages( bool bIsPDFExport ) const +{ + // take care of different property names for the option. + + bool bRes = bIsPDFExport ? + !getBoolValue( "IsSkipEmptyPages", true ) : + getBoolValue( "PrintEmptyPages", true ); + return bRes; +} + +bool SwPrintUIOptions::IsPrintGraphics() const +{ + // take care of different property names for the option. + // for compatibility the old name should win (may still be used for PDF export or via Uno API) + + bool bRes = getBoolValue( "PrintPicturesAndObjects", true ); + bRes = getBoolValue( "PrintGraphics", bRes ); + return bRes; +} + +bool SwPrintUIOptions::IsPrintDrawings() const +{ + // take care of different property names for the option. + // for compatibility the old name should win (may still be used for PDF export or via Uno API) + + bool bRes = getBoolValue( "PrintPicturesAndObjects", true ); + bRes = getBoolValue( "PrintDrawings", bRes ); + return bRes; +} + +bool SwPrintUIOptions::processPropertiesAndCheckFormat( const uno::Sequence< beans::PropertyValue >& i_rNewProp ) +{ + bool bChanged = processProperties( i_rNewProp ); + + uno::Reference< awt::XDevice > xRenderDevice; + uno::Any aVal( getValue( "RenderDevice" ) ); + aVal >>= xRenderDevice; + + VclPtr< OutputDevice > pOut; + if (xRenderDevice.is()) + { + VCLXDevice* pDevice = comphelper::getUnoTunnelImplementation<VCLXDevice>( xRenderDevice ); + if (pDevice) + pOut = pDevice->GetOutputDevice(); + } + bChanged = bChanged || (pOut.get() != m_pLast.get()); + if( pOut ) + m_pLast = pOut; + + return bChanged; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/vdraw.cxx b/sw/source/core/view/vdraw.cxx new file mode 100644 index 000000000..b59e43275 --- /dev/null +++ b/sw/source/core/view/vdraw.cxx @@ -0,0 +1,279 @@ +/* -*- 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 <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <swmodule.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <svx/svdpagv.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> + +#include <svx/svdoutl.hxx> + +#include <drawdoc.hxx> +#include <fesh.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <viewimp.hxx> +#include <dflyobj.hxx> +#include <printdata.hxx> +#include <dcontact.hxx> +#include <dview.hxx> +#include <flyfrm.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/canvastools.hxx> +#include <sal/log.hxx> + +#include <basegfx/range/b2irectangle.hxx> + +#include <IDocumentDrawModelAccess.hxx> + +void SwViewShellImp::StartAction() +{ + if ( HasDrawView() ) + { + SET_CURR_SHELL( GetShell() ); + if ( dynamic_cast<const SwFEShell*>( m_pShell) != nullptr ) + static_cast<SwFEShell*>(m_pShell)->HideChainMarker(); // might have changed + } +} + +void SwViewShellImp::EndAction() +{ + if ( HasDrawView() ) + { + SET_CURR_SHELL( GetShell() ); + if ( dynamic_cast<const SwFEShell*>(m_pShell) != nullptr ) + static_cast<SwFEShell*>(m_pShell)->SetChainMarker(); // might have changed + } +} + +void SwViewShellImp::LockPaint() +{ + if ( HasDrawView() ) + { + m_bResetHdlHiddenPaint = !GetDrawView()->areMarkHandlesHidden(); + GetDrawView()->hideMarkHandles(); + } + else + { + m_bResetHdlHiddenPaint = false; + } +} + +void SwViewShellImp::UnlockPaint() +{ + if ( m_bResetHdlHiddenPaint ) + GetDrawView()->showMarkHandles(); +} + +void SwViewShellImp::PaintLayer( const SdrLayerID _nLayerID, + SwPrintData const*const pPrintData, + SwPageFrame const& rPageFrame, + const SwRect& aPaintRect, + const Color* _pPageBackgrdColor, + const bool _bIsPageRightToLeft, + sdr::contact::ViewObjectContactRedirector* pRedirector ) +{ + if ( !HasDrawView() ) + return; + + //change the draw mode in high contrast mode + OutputDevice* pOutDev = GetShell()->GetOut(); + DrawModeFlags nOldDrawMode = pOutDev->GetDrawMode(); + if( GetShell()->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() && + (!GetShell()->IsPreview()||SW_MOD()->GetAccessibilityOptions().GetIsForPagePreviews())) + { + pOutDev->SetDrawMode( nOldDrawMode | DrawModeFlags::SettingsLine | DrawModeFlags::SettingsFill | + DrawModeFlags::SettingsText | DrawModeFlags::SettingsGradient ); + } + + // For correct handling of accessibility, high contrast, the + // page background color is set as the background color at the + // outliner of the draw view. Only necessary for the layers + // hell and heaven + Color aOldOutlinerBackgrdColor; + // set default horizontal text direction on painting <hell> or + // <heaven>. + EEHorizontalTextDirection aOldEEHoriTextDir = EEHorizontalTextDirection::L2R; + const IDocumentDrawModelAccess& rIDDMA = GetShell()->getIDocumentDrawModelAccess(); + if ( (_nLayerID == rIDDMA.GetHellId()) || + (_nLayerID == rIDDMA.GetHeavenId()) ) + { + OSL_ENSURE( _pPageBackgrdColor, + "incorrect usage of SwViewShellImp::PaintLayer: pPageBackgrdColor have to be set for painting layer <hell> or <heaven>"); + if ( _pPageBackgrdColor ) + { + aOldOutlinerBackgrdColor = + GetDrawView()->GetModel()->GetDrawOutliner().GetBackgroundColor(); + GetDrawView()->GetModel()->GetDrawOutliner().SetBackgroundColor( *_pPageBackgrdColor ); + } + + aOldEEHoriTextDir = + GetDrawView()->GetModel()->GetDrawOutliner().GetDefaultHorizontalTextDirection(); + EEHorizontalTextDirection aEEHoriTextDirOfPage = + _bIsPageRightToLeft ? EEHorizontalTextDirection::R2L : EEHorizontalTextDirection::L2R; + GetDrawView()->GetModel()->GetDrawOutliner().SetDefaultHorizontalTextDirection( aEEHoriTextDirOfPage ); + } + + pOutDev->Push( PushFlags::LINECOLOR ); + if (pPrintData) + { + // hide drawings but not form controls (form controls are handled elsewhere) + SdrView &rSdrView = GetPageView()->GetView(); + rSdrView.setHideDraw( !pPrintData->IsPrintDraw() ); + } + basegfx::B2IRectangle const pageFrame = vcl::unotools::b2IRectangleFromRectangle(rPageFrame.getFrameArea().SVRect()); + GetPageView()->DrawLayer(_nLayerID, pOutDev, pRedirector, aPaintRect.SVRect(), &pageFrame); + pOutDev->Pop(); + + // reset background color of the outliner & default horiz. text dir. + if ( (_nLayerID == rIDDMA.GetHellId()) || + (_nLayerID == rIDDMA.GetHeavenId()) ) + { + GetDrawView()->GetModel()->GetDrawOutliner().SetBackgroundColor( aOldOutlinerBackgrdColor ); + GetDrawView()->GetModel()->GetDrawOutliner().SetDefaultHorizontalTextDirection( aOldEEHoriTextDir ); + } + + pOutDev->SetDrawMode( nOldDrawMode ); + +} + +#define FUZZY_EDGE 400 + +bool SwViewShellImp::IsDragPossible( const Point &rPoint ) +{ + if ( !HasDrawView() ) + return false; + + const SdrMarkList &rMrkList = GetDrawView()->GetMarkedObjectList(); + + if( !rMrkList.GetMarkCount() ) + return false; + + SdrObject *pO = rMrkList.GetMark(rMrkList.GetMarkCount()-1)->GetMarkedSdrObj(); + + SwRect aRect; + if( pO && ::CalcClipRect( pO, aRect, false ) ) + { + SwRect aTmp; + ::CalcClipRect( pO, aTmp ); + aRect.Union( aTmp ); + } + else + aRect = GetShell()->GetLayout()->getFrameArea(); + + aRect.AddTop (- FUZZY_EDGE ); + aRect.AddBottom( FUZZY_EDGE ); + aRect.AddLeft (- FUZZY_EDGE ); + aRect.AddRight ( FUZZY_EDGE ); + return aRect.IsInside( rPoint ); +} + +void SwViewShellImp::NotifySizeChg( const Size &rNewSz ) +{ + if ( !HasDrawView() ) + return; + + if ( GetPageView() ) + GetPageView()->GetPage()->SetSize( rNewSz ); + + // Limitation of the work area + const tools::Rectangle aDocRect( Point( DOCUMENTBORDER, DOCUMENTBORDER ), rNewSz ); + const tools::Rectangle &rOldWork = GetDrawView()->GetWorkArea(); + bool bCheckDrawObjs = false; + if ( aDocRect != rOldWork ) + { + if ( rOldWork.Bottom() > aDocRect.Bottom() || rOldWork.Right() > aDocRect.Right()) + bCheckDrawObjs = true; + GetDrawView()->SetWorkArea( aDocRect ); + } + if ( !bCheckDrawObjs ) + return; + + OSL_ENSURE( m_pShell->getIDocumentDrawModelAccess().GetDrawModel(), "NotifySizeChg without DrawModel" ); + SdrPage* pPage = m_pShell->getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 ); + const size_t nObjs = pPage->GetObjCount(); + for( size_t nObj = 0; nObj < nObjs; ++nObj ) + { + SdrObject *pObj = pPage->GetObj( nObj ); + if( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + { + // Objects not anchored to the frame, do not need to be adjusted + const SwContact *pCont = GetUserCall(pObj); + // this function might be called by the InsertDocument, when + // a PageDesc-Attribute is set on a node. Then the SdrObject + // must not have an UserCall. + if( !pCont || dynamic_cast<const SwDrawContact*>( pCont) == nullptr ) + continue; + + const SwFrame *pAnchor = static_cast<const SwDrawContact*>(pCont)->GetAnchorFrame(); + if ( !pAnchor || pAnchor->IsInFly() || !pAnchor->isFrameAreaDefinitionValid() || + !pAnchor->GetUpper() || !pAnchor->FindPageFrame() || + (RndStdIds::FLY_AS_CHAR == pCont->GetFormat()->GetAnchor().GetAnchorId()) ) + { + continue; + } + else + { + // Actually this should never happen but currently layouting + // is broken. So don't move anchors, if the page is invalid. + // This should be turned into a DBG_ASSERT, once layouting is fixed! + const SwPageFrame *pPageFrame = pAnchor->FindPageFrame(); + if (!pPageFrame || pPageFrame->IsInvalid() ) { + SAL_WARN( "sw.core", "Trying to move anchor from invalid page - fix layouting!" ); + continue; + } + } + + // no move for drawing objects in header/footer + if ( pAnchor->FindFooterOrHeader() ) + { + continue; + } + + const tools::Rectangle aObjBound( pObj->GetCurrentBoundRect() ); + if ( !aDocRect.IsInside( aObjBound ) ) + { + Size aSz; + if ( aObjBound.Left() > aDocRect.Right() ) + aSz.setWidth( (aDocRect.Right() - aObjBound.Left()) - MINFLY ); + if ( aObjBound.Top() > aDocRect.Bottom() ) + aSz.setHeight( (aDocRect.Bottom() - aObjBound.Top()) - MINFLY ); + if ( aSz.Width() || aSz.Height() ) + pObj->Move( aSz ); + + // Don't let large objects disappear to the top + aSz.setWidth(0); + aSz.setHeight(0); + if ( aObjBound.Right() < aDocRect.Left() ) + aSz.setWidth( (aDocRect.Left() - aObjBound.Right()) + MINFLY ); + if ( aObjBound.Bottom() < aDocRect.Top() ) + aSz.setHeight( (aDocRect.Top() - aObjBound.Bottom()) + MINFLY ); + if ( aSz.Width() || aSz.Height() ) + pObj->Move( aSz ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/viewimp.cxx b/sw/source/core/view/viewimp.cxx new file mode 100644 index 000000000..317480500 --- /dev/null +++ b/sw/source/core/view/viewimp.cxx @@ -0,0 +1,457 @@ +/* -*- 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 <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <flyfrm.hxx> +#include <layact.hxx> +#include <swregion.hxx> +#include <dview.hxx> +#include <swmodule.hxx> +#include <svx/svdpage.hxx> +#include <accmap.hxx> + +#include <pagepreviewlayout.hxx> +#include <comphelper/lok.hxx> +#include <tools/diagnose_ex.h> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <drawdoc.hxx> +#include <prevwpage.hxx> + +void SwViewShellImp::Init( const SwViewOption *pNewOpt ) +{ + OSL_ENSURE( m_pDrawView, "SwViewShellImp::Init without DrawView" ); + //Create PageView if it doesn't exist + SwRootFrame *pRoot = m_pShell->GetLayout(); + if ( !m_pSdrPageView ) + { + IDocumentDrawModelAccess& rIDDMA = m_pShell->getIDocumentDrawModelAccess(); + if ( !pRoot->GetDrawPage() ) + pRoot->SetDrawPage( rIDDMA.GetDrawModel()->GetPage( 0 ) ); + + if ( pRoot->GetDrawPage()->GetSize() != pRoot->getFrameArea().SSize() ) + pRoot->GetDrawPage()->SetSize( pRoot->getFrameArea().SSize() ); + + m_pSdrPageView = m_pDrawView->ShowSdrPage( pRoot->GetDrawPage()); + // Notify drawing page view about invisible layers + rIDDMA.NotifyInvisibleLayers( *m_pSdrPageView ); + } + m_pDrawView->SetDragStripes( pNewOpt->IsCrossHair() ); + m_pDrawView->SetGridSnap( pNewOpt->IsSnap() ); + m_pDrawView->SetGridVisible( pNewOpt->IsGridVisible() ); + const Size &rSz = pNewOpt->GetSnapSize(); + m_pDrawView->SetGridCoarse( rSz ); + const Size aFSize + ( rSz.Width() ? rSz.Width() /std::max(short(1),pNewOpt->GetDivisionX()):0, + rSz.Height()? rSz.Height()/std::max(short(1),pNewOpt->GetDivisionY()):0); + m_pDrawView->SetGridFine( aFSize ); + Fraction aSnGrWdtX(rSz.Width(), pNewOpt->GetDivisionX() + 1); + Fraction aSnGrWdtY(rSz.Height(), pNewOpt->GetDivisionY() + 1); + m_pDrawView->SetSnapGridWidth( aSnGrWdtX, aSnGrWdtY ); + + if ( pRoot->getFrameArea().HasArea() ) + m_pDrawView->SetWorkArea( pRoot->getFrameArea().SVRect() ); + + if ( GetShell()->IsPreview() ) + m_pDrawView->SetAnimationEnabled( false ); + + m_pDrawView->SetUseIncompatiblePathCreateInterface( false ); + + // set handle size to 9 pixels, always + m_pDrawView->SetMarkHdlSizePixel(9); +} + +/// CTor for the core internals +SwViewShellImp::SwViewShellImp( SwViewShell *pParent ) : + m_pShell( pParent ), + m_pSdrPageView( nullptr ), + m_pFirstVisiblePage( nullptr ), + m_pLayAction( nullptr ), + m_pIdleAct( nullptr ), + m_bFirstPageInvalid( true ), + m_bResetHdlHiddenPaint( false ), + m_bSmoothUpdate( false ), + m_bStopSmooth( false ), + m_nRestoreActions( 0 ) +{ +} + +SwViewShellImp::~SwViewShellImp() +{ + m_pAccessibleMap.reset(); + + m_pPagePreviewLayout.reset(); + + // Make sure HideSdrPage is also executed after ShowSdrPage. + if( m_pDrawView ) + m_pDrawView->HideSdrPage(); + + m_pDrawView.reset(); + + DelRegion(); + + OSL_ENSURE( !m_pLayAction, "Have action for the rest of your life." ); + OSL_ENSURE( !m_pIdleAct,"Be idle for the rest of your life." ); +} + +void SwViewShellImp::DelRegion() +{ + m_pRegion.reset(); +} + +bool SwViewShellImp::AddPaintRect( const SwRect &rRect ) +{ + // In case of tiled rendering the visual area is the last painted tile -> not interesting. + if ( rRect.IsOver( m_pShell->VisArea() ) || comphelper::LibreOfficeKit::isActive() ) + { + if ( !m_pRegion ) + { + // In case of normal rendering, this makes sure only visible rectangles are painted. + // Otherwise get the rectangle of the full document, so all paint rectangles are invalidated. + const SwRect& rArea = comphelper::LibreOfficeKit::isActive() ? m_pShell->GetLayout()->getFrameArea() : m_pShell->VisArea(); + m_pRegion.reset(new SwRegionRects(rArea)); + } + (*m_pRegion) -= rRect; + return true; + } + return false; +} + +void SwViewShellImp::CheckWaitCursor() +{ + if ( m_pLayAction ) + m_pLayAction->CheckWaitCursor(); +} + +bool SwViewShellImp::IsCalcLayoutProgress() const +{ + return m_pLayAction && m_pLayAction->IsCalcLayout(); +} + +bool SwViewShellImp::IsUpdateExpFields() +{ + if ( m_pLayAction && m_pLayAction->IsCalcLayout() ) + { + m_pLayAction->SetUpdateExpFields(); + return true; + } + return false; +} + +void SwViewShellImp::SetFirstVisPage(OutputDevice const * pRenderContext) +{ + if ( m_pShell->mbDocSizeChgd && m_pShell->VisArea().Top() > m_pShell->GetLayout()->getFrameArea().Height() ) + { + //We are in an action and because of erase actions the VisArea is + //after the first visible page. + //To avoid excessive formatting, hand back the last page. + m_pFirstVisiblePage = static_cast<SwPageFrame*>(m_pShell->GetLayout()->Lower()); + while ( m_pFirstVisiblePage && m_pFirstVisiblePage->GetNext() ) + m_pFirstVisiblePage = static_cast<SwPageFrame*>(m_pFirstVisiblePage->GetNext()); + } + else + { + const SwViewOption* pSwViewOption = GetShell()->GetViewOptions(); + const bool bBookMode = pSwViewOption->IsViewLayoutBookMode(); + + SwPageFrame *pPage = static_cast<SwPageFrame*>(m_pShell->GetLayout()->Lower()); + SwRect aPageRect = pPage->GetBoundRect(pRenderContext); + while ( pPage && !aPageRect.IsOver( m_pShell->VisArea() ) ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + if ( pPage ) + { + aPageRect = pPage->GetBoundRect(pRenderContext); + if ( bBookMode && pPage->IsEmptyPage() ) + { + const SwPageFrame& rFormatPage = pPage->GetFormatPage(); + aPageRect.SSize( rFormatPage.GetBoundRect(pRenderContext).SSize() ); + } + } + } + m_pFirstVisiblePage = pPage ? pPage : static_cast<SwPageFrame*>(m_pShell->GetLayout()->Lower()); + } + m_bFirstPageInvalid = false; +} + +void SwViewShellImp::MakeDrawView() +{ + IDocumentDrawModelAccess& rIDDMA = GetShell()->getIDocumentDrawModelAccess(); + + // the else here is not an error, MakeDrawModel_() calls this method again + // after the DrawModel is created to create DrawViews for all shells... + if( !rIDDMA.GetDrawModel() ) + { + rIDDMA.MakeDrawModel_(); + } + else + { + if ( !m_pDrawView ) + { + // #i72809# + // Discussed with FME, he also thinks that the getPrinter is old and not correct. When i got + // him right, it anyways returns GetOut() when it's a printer, but NULL when not. He suggested + // to use GetOut() and check the existing cases. + // Check worked well. Took a look at viewing, printing, PDF export and print preview with a test + // document which has an empty 2nd page (right page, see bug) + OutputDevice* pOutDevForDrawView = GetShell()->GetWin(); + + if(!pOutDevForDrawView) + { + pOutDevForDrawView = GetShell()->GetOut(); + } + + m_pDrawView.reset( new SwDrawView( + *this, + *rIDDMA.GetOrCreateDrawModel(), + pOutDevForDrawView) ); + } + + GetDrawView()->SetActiveLayer("Heaven"); + const SwViewOption* pSwViewOption = GetShell()->GetViewOptions(); + Init(pSwViewOption); + + // #i68597# If document is read-only, we will not profit from overlay, + // so switch it off. + if (m_pDrawView->IsBufferedOverlayAllowed()) + { + if(pSwViewOption->IsReadonly()) + { + m_pDrawView->SetBufferedOverlayAllowed(false); + } + } + } +} + +Color SwViewShellImp::GetRetoucheColor() const +{ + Color aRet( COL_TRANSPARENT ); + const SwViewShell &rSh = *GetShell(); + if ( rSh.GetWin() ) + { + if ( rSh.GetViewOptions()->getBrowseMode() && + COL_TRANSPARENT != rSh.GetViewOptions()->GetRetoucheColor() ) + aRet = rSh.GetViewOptions()->GetRetoucheColor(); + else if(rSh.GetViewOptions()->IsPagePreview() && + !SW_MOD()->GetAccessibilityOptions().GetIsForPagePreviews()) + aRet = COL_WHITE; + else + aRet = SwViewOption::GetDocColor(); + } + return aRet; +} + +SwPageFrame *SwViewShellImp::GetFirstVisPage(OutputDevice const * pRenderContext) +{ + if ( m_bFirstPageInvalid ) + SetFirstVisPage(pRenderContext); + return m_pFirstVisiblePage; +} + +const SwPageFrame *SwViewShellImp::GetFirstVisPage(OutputDevice const * pRenderContext) const +{ + if ( m_bFirstPageInvalid ) + const_cast<SwViewShellImp*>(this)->SetFirstVisPage(pRenderContext); + return m_pFirstVisiblePage; +} + +// create page preview layout +void SwViewShellImp::InitPagePreviewLayout() +{ + OSL_ENSURE( m_pShell->GetLayout(), "no layout - page preview layout can not be created."); + if ( m_pShell->GetLayout() ) + m_pPagePreviewLayout.reset( new SwPagePreviewLayout( *m_pShell, *(m_pShell->GetLayout()) ) ); +} + +void SwViewShellImp::UpdateAccessible() +{ + // We require a layout and an XModel to be accessible. + IDocumentLayoutAccess& rIDLA = GetShell()->getIDocumentLayoutAccess(); + vcl::Window *pWin = GetShell()->GetWin(); + OSL_ENSURE( GetShell()->GetLayout(), "no layout, no access" ); + OSL_ENSURE( pWin, "no window, no access" ); + + if( IsAccessible() && rIDLA.GetCurrentViewShell() && pWin ) + { + try + { + GetAccessibleMap().GetDocumentView(); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("sw.a11y", ""); + assert(!"SwViewShellImp::UpdateAccessible: unhandled exception"); + } + } +} + +void SwViewShellImp::DisposeAccessible(const SwFrame *pFrame, + const SdrObject *pObj, + bool bRecursive, + bool bCanSkipInvisible) +{ + OSL_ENSURE( !pFrame || pFrame->IsAccessibleFrame(), "frame is not accessible" ); + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if( rTmp.Imp()->IsAccessible() ) + rTmp.Imp()->GetAccessibleMap().A11yDispose( pFrame, pObj, nullptr, bRecursive, bCanSkipInvisible ); + } +} + +void SwViewShellImp::MoveAccessible( const SwFrame *pFrame, const SdrObject *pObj, + const SwRect& rOldFrame ) +{ + OSL_ENSURE( !pFrame || pFrame->IsAccessibleFrame(), "frame is not accessible" ); + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if( rTmp.Imp()->IsAccessible() ) + rTmp.Imp()->GetAccessibleMap().InvalidatePosOrSize( pFrame, pObj, nullptr, + rOldFrame ); + } +} + +void SwViewShellImp::InvalidateAccessibleFrameContent( const SwFrame *pFrame ) +{ + OSL_ENSURE( pFrame->IsAccessibleFrame(), "frame is not accessible" ); + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if( rTmp.Imp()->IsAccessible() ) + rTmp.Imp()->GetAccessibleMap().InvalidateContent( pFrame ); + } +} + +void SwViewShellImp::InvalidateAccessibleCursorPosition( const SwFrame *pFrame ) +{ + if( IsAccessible() ) + GetAccessibleMap().InvalidateCursorPosition( pFrame ); +} + +void SwViewShellImp::InvalidateAccessibleEditableState( bool bAllShells, + const SwFrame *pFrame ) +{ + if( bAllShells ) + { + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if( rTmp.Imp()->IsAccessible() ) + rTmp.Imp()->GetAccessibleMap().InvalidateEditableStates( pFrame ); + } + } + else if( IsAccessible() ) + { + GetAccessibleMap().InvalidateEditableStates( pFrame ); + } +} + +void SwViewShellImp::InvalidateAccessibleRelationSet( const SwFlyFrame *pMaster, + const SwFlyFrame *pFollow ) +{ + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if( rTmp.Imp()->IsAccessible() ) + rTmp.Imp()->GetAccessibleMap().InvalidateRelationSet( pMaster, + pFollow ); + } +} + +/// invalidate CONTENT_FLOWS_FROM/_TO relation for paragraphs +void SwViewShellImp::InvalidateAccessibleParaFlowRelation_( const SwTextFrame* _pFromTextFrame, + const SwTextFrame* _pToTextFrame ) +{ + if ( !_pFromTextFrame && !_pToTextFrame ) + { + // No text frame provided. Thus, nothing to do. + return; + } + + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if ( rTmp.Imp()->IsAccessible() ) + { + if ( _pFromTextFrame ) + { + rTmp.Imp()->GetAccessibleMap(). + InvalidateParaFlowRelation( *_pFromTextFrame, true ); + } + if ( _pToTextFrame ) + { + rTmp.Imp()->GetAccessibleMap(). + InvalidateParaFlowRelation( *_pToTextFrame, false ); + } + } + } +} + +/// invalidate text selection for paragraphs +void SwViewShellImp::InvalidateAccessibleParaTextSelection_() +{ + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if ( rTmp.Imp()->IsAccessible() ) + { + rTmp.Imp()->GetAccessibleMap().InvalidateTextSelectionOfAllParas(); + } + } +} + +/// invalidate attributes for paragraphs +void SwViewShellImp::InvalidateAccessibleParaAttrs_( const SwTextFrame& rTextFrame ) +{ + for(SwViewShell& rTmp : GetShell()->GetRingContainer()) + { + if ( rTmp.Imp()->IsAccessible() ) + { + rTmp.Imp()->GetAccessibleMap().InvalidateAttr( rTextFrame ); + } + } +} + +void SwViewShellImp::UpdateAccessiblePreview( const std::vector<std::unique_ptr<PreviewPage>>& _rPreviewPages, + const Fraction& _rScale, + const SwPageFrame* _pSelectedPageFrame, + const Size& _rPreviewWinSize ) +{ + if( IsAccessible() ) + GetAccessibleMap().UpdatePreview( _rPreviewPages, _rScale, + _pSelectedPageFrame, _rPreviewWinSize ); +} + +void SwViewShellImp::InvalidateAccessiblePreviewSelection( sal_uInt16 nSelPage ) +{ + if( IsAccessible() ) + GetAccessibleMap().InvalidatePreviewSelection( nSelPage ); +} + +SwAccessibleMap *SwViewShellImp::CreateAccessibleMap() +{ + assert(!m_pAccessibleMap); + m_pAccessibleMap = std::make_shared<SwAccessibleMap>(GetShell()); + return m_pAccessibleMap.get(); +} + +void SwViewShellImp::FireAccessibleEvents() +{ + if( IsAccessible() ) + GetAccessibleMap().FireEvents(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/viewpg.cxx b/sw/source/core/view/viewpg.cxx new file mode 100644 index 000000000..07df8ba6b --- /dev/null +++ b/sw/source/core/view/viewpg.cxx @@ -0,0 +1,209 @@ +/* -*- 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 <tools/fract.hxx> +#include <viewsh.hxx> +#include <pagefrm.hxx> +#include <viewimp.hxx> +#include <printdata.hxx> +#include <ptqueue.hxx> +#include <fntcache.hxx> + +#include "vprint.hxx" + +using namespace ::com::sun::star; + +SwPagePreviewLayout* SwViewShell::PagePreviewLayout() +{ + return Imp()->PagePreviewLayout(); +} + +void SwViewShell::ShowPreviewSelection( sal_uInt16 nSelPage ) +{ + Imp()->InvalidateAccessiblePreviewSelection( nSelPage ); +} + +// adjust view options for page preview +void SwViewShell::AdjustOptionsForPagePreview(SwPrintData const& rPrintOptions) +{ + if ( !IsPreview() ) + { + OSL_FAIL( "view shell doesn't belongs to a page preview - no adjustment of its view options"); + return; + } + + PrepareForPrint( rPrintOptions ); +} + +/// print brochure +// consider empty pages on calculation of the scaling +// for a page to be printed. +void SwViewShell::PrintProspect( + OutputDevice *pOutDev, + const SwPrintData &rPrintData, + sal_Int32 nRenderer // the index in the vector of prospect pages to be printed + ) +{ + const sal_Int32 nMaxRenderer = rPrintData.GetRenderData().GetPagePairsForProspectPrinting().size() - 1; + OSL_ENSURE( 0 <= nRenderer && nRenderer <= nMaxRenderer, "nRenderer out of bounds"); + Printer *pPrinter = dynamic_cast< Printer * >(pOutDev); + if (!pPrinter || nMaxRenderer < 0 || nRenderer < 0 || nRenderer > nMaxRenderer) + return; + + // save settings of OutputDevice (should be done always since the + // output device is now provided by a call from outside the Writer) + pPrinter->Push(); + + std::pair< sal_Int32, sal_Int32 > rPagesToPrint = + rPrintData.GetRenderData().GetPagePairsForProspectPrinting()[ nRenderer ]; + OSL_ENSURE( rPagesToPrint.first == -1 || rPrintData.GetRenderData().GetValidPagesSet().count( rPagesToPrint.first ) == 1, "first Page not valid" ); + OSL_ENSURE( rPagesToPrint.second == -1 || rPrintData.GetRenderData().GetValidPagesSet().count( rPagesToPrint.second ) == 1, "second Page not valid" ); + + // create a new shell for the printer + SwViewShell aShell( *this, nullptr, pPrinter ); + + SET_CURR_SHELL( &aShell ); + + aShell.PrepareForPrint( rPrintData ); + + //!! applying view options and formatting the document should now only be done in getRendererCount! + + MapMode aMapMode( MapUnit::MapTwip ); + Size aPrtSize( pPrinter->PixelToLogic( pPrinter->GetPaperSizePixel(), aMapMode ) ); + + SwTwips nMaxRowSz, nMaxColSz; + + const SwPageFrame *pStPage = nullptr; + const SwPageFrame *pNxtPage = nullptr; + if (rPagesToPrint.first > 0) + { + pStPage = sw_getPage(*aShell.GetLayout(), rPagesToPrint.first); + } + if (rPagesToPrint.second > 0) + { + pNxtPage = sw_getPage(*aShell.GetLayout(), rPagesToPrint.second); + } + + // i#14016 consider empty pages on calculation + // of page size, used for calculation of scaling. + Size aSttPageSize; + if ( pStPage ) + { + if ( pStPage->IsEmptyPage() ) + { + if ( pStPage->GetPhyPageNum() % 2 == 0 ) + aSttPageSize = pStPage->GetPrev()->getFrameArea().SSize(); + else + aSttPageSize = pStPage->GetNext()->getFrameArea().SSize(); + } + else + { + aSttPageSize = pStPage->getFrameArea().SSize(); + } + } + Size aNxtPageSize; + if ( pNxtPage ) + { + if ( pNxtPage->IsEmptyPage() ) + { + if ( pNxtPage->GetPhyPageNum() % 2 == 0 ) + aNxtPageSize = pNxtPage->GetPrev()->getFrameArea().SSize(); + else + aNxtPageSize = pNxtPage->GetNext()->getFrameArea().SSize(); + } + else + { + aNxtPageSize = pNxtPage->getFrameArea().SSize(); + } + } + + if( !pStPage ) + { + nMaxColSz = 2 * aNxtPageSize.Width(); + nMaxRowSz = aNxtPageSize.Height(); + } + else if( !pNxtPage ) + { + nMaxColSz = 2 * aSttPageSize.Width(); + nMaxRowSz = aSttPageSize.Height(); + } + else + { + nMaxColSz = aNxtPageSize.Width() + aSttPageSize.Width(); + nMaxRowSz = std::max( aNxtPageSize.Height(), aSttPageSize.Height() ); + } + + // set the MapMode + aMapMode.SetOrigin( Point() ); + { + Fraction aScX( aPrtSize.Width(), nMaxColSz ); + Fraction aScY( aPrtSize.Height(), nMaxRowSz ); + if( aScX < aScY ) + aScY = aScX; + + { + // Round percentages for Drawings so that these can paint their objects properly + aScY *= Fraction( 1000, 1 ); + long nTmp = static_cast<long>(aScY); + if( 1 < nTmp ) + --nTmp; + else + nTmp = 1; + aScY = Fraction( nTmp, 1000 ); + } + + aMapMode.SetScaleY( aScY ); + aMapMode.SetScaleX( aScY ); + } + + Size aTmpPrtSize( pPrinter->PixelToLogic( pPrinter->GetPaperSizePixel(), aMapMode ) ); + + // calculate start point for equal border on all sides + Point aSttPt( (aTmpPrtSize.Width() - nMaxColSz) / 2, + (aTmpPrtSize.Height() - nMaxRowSz) / 2 ); + for( int nC = 0; nC < 2; ++nC ) + { + if( pStPage ) + { + aShell.Imp()->SetFirstVisPageInvalid(); + aShell.maVisArea = pStPage->getFrameArea(); + + Point aPos( aSttPt ); + aPos -= aShell.maVisArea.Pos(); + aMapMode.SetOrigin( aPos ); + pPrinter->SetMapMode( aMapMode ); + pStPage->GetUpper()->PaintSwFrame( *pOutDev, pStPage->getFrameArea() ); + } + + pStPage = pNxtPage; + aSttPt.AdjustX(aTmpPrtSize.Width() / 2 ); + } + + SwPaintQueue::Repaint(); + + //!! applying/modifying view options and formatting the document should now only be done in getRendererCount! + + pFntCache->Flush(); + + // restore settings of OutputDevice (should be done always now since the + // output device is now provided by a call from outside the Writer) + pPrinter->Pop(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx new file mode 100644 index 000000000..ef26275ab --- /dev/null +++ b/sw/source/core/view/viewsh.cxx @@ -0,0 +1,2619 @@ +/* -*- 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 <com/sun/star/accessibility/XAccessible.hpp> +#include <sfx2/viewfrm.hxx> +#include <sfx2/progress.hxx> +#include <svx/srchdlg.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/ipclient.hxx> +#include <sal/log.hxx> +#include <drawdoc.hxx> +#include <swwait.hxx> +#include <crsrsh.hxx> +#include <doc.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentOutlineNodes.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentState.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <viewimp.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <dview.hxx> +#include <swregion.hxx> +#include <hints.hxx> +#include <docufld.hxx> +#include <txtfrm.hxx> +#include <layact.hxx> +#include <mdiexp.hxx> +#include <fntcache.hxx> +#include <ptqueue.hxx> +#include <docsh.hxx> +#include <pagedesc.hxx> +#include <ndole.hxx> +#include <ndindex.hxx> +#include <accmap.hxx> +#include <vcl/bitmapex.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <accessibilityoptions.hxx> +#include <strings.hrc> +#include <bitmaps.hlst> +#include <pagepreviewlayout.hxx> +#include <sortedobjs.hxx> +#include <anchoredobject.hxx> +#include <DocumentSettingManager.hxx> + +#include <unotxdoc.hxx> +#include <view.hxx> +#include <PostItMgr.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/svdpagv.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> + +#if !HAVE_FEATURE_DESKTOP +#include <vcl/sysdata.hxx> +#endif + +bool SwViewShell::mbLstAct = false; +ShellResource *SwViewShell::mpShellRes = nullptr; +vcl::DeleteOnDeinit<std::shared_ptr<weld::Window>> SwViewShell::mpCareDialog(new std::shared_ptr<weld::Window>); + +static bool bInSizeNotify = false; + + +using namespace ::com::sun::star; + +void SwViewShell::SetShowHeaderFooterSeparator( FrameControlType eControl, bool bShow ) { + + //tdf#118621 - Optionally disable floating header/footer menu + if ( bShow ) + bShow = GetViewOptions()->IsUseHeaderFooterMenu(); + + if ( eControl == FrameControlType::Header ) + mbShowHeaderSeparator = bShow; + else + mbShowFooterSeparator = bShow; +} + +void SwViewShell::ToggleHeaderFooterEdit() +{ + mbHeaderFooterEdit = !mbHeaderFooterEdit; + if ( !mbHeaderFooterEdit ) + { + SetShowHeaderFooterSeparator( FrameControlType::Header, false ); + SetShowHeaderFooterSeparator( FrameControlType::Footer, false ); + } + + // Avoid corner case + if ( ( GetViewOptions()->IsUseHeaderFooterMenu() ) && + ( !IsShowHeaderFooterSeparator( FrameControlType::Header ) && + !IsShowHeaderFooterSeparator( FrameControlType::Footer ) ) ) + { + mbHeaderFooterEdit = false; + } + + // Repaint everything + GetWin()->Invalidate(); +} + +void SwViewShell::setOutputToWindow(bool bOutputToWindow) +{ + mbOutputToWindow = bOutputToWindow; +} + +bool SwViewShell::isOutputToWindow() const +{ + return mbOutputToWindow; +} + +void SwViewShell::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwViewShell")); + xmlTextWriterEndElement(pWriter); +} + +static void +lcl_PaintTransparentFormControls(SwViewShell const & rShell, SwRect const& rRect) +{ + // Direct paint has been performed: the background of transparent child + // windows has been painted, so need to paint the child windows now. + if (rShell.GetWin()) + { + vcl::Window& rWindow = *(rShell.GetWin()); + const tools::Rectangle aRectanglePixel(rShell.GetOut()->LogicToPixel(rRect.SVRect())); + PaintTransparentChildren(rWindow, aRectanglePixel); + } +} + +// #i72754# 2nd set of Pre/PostPaints +// This time it uses the lock counter (mPrePostPaintRegions empty/non-empty) to allow only one activation +// and deactivation and mpPrePostOutDev to remember the OutDev from the BeginDrawLayers +// call. That way, all places where paint take place can be handled the same way, even +// when calling other paint methods. This is the case at the places where SW paints +// buffered into VDevs to avoid flicker. It is in general problematic and should be +// solved once using the BufferedOutput functionality of the DrawView. + +void SwViewShell::PrePaint() +{ + // forward PrePaint event from VCL Window to DrawingLayer + if(HasDrawView()) + { + Imp()->GetDrawView()->PrePaint(); + } +} + +void SwViewShell::DLPrePaint2(const vcl::Region& rRegion) +{ + if(mPrePostPaintRegions.empty()) + { + mPrePostPaintRegions.push( rRegion ); + // #i75172# ensure DrawView to use DrawingLayer bufferings + if ( !HasDrawView() ) + MakeDrawView(); + + // Prefer window; if not available, get mpOut (e.g. printer) + const bool bWindow = GetWin() && !comphelper::LibreOfficeKit::isActive() && !isOutputToWindow(); + mpPrePostOutDev = bWindow ? GetWin(): GetOut(); + + // #i74769# use SdrPaintWindow now direct + mpTargetPaintWindow = Imp()->GetDrawView()->BeginDrawLayers(mpPrePostOutDev, rRegion); + OSL_ENSURE(mpTargetPaintWindow, "BeginDrawLayers: Got no SdrPaintWindow (!)"); + + // #i74769# if prerender, save OutDev and redirect to PreRenderDevice + if(mpTargetPaintWindow->GetPreRenderDevice()) + { + mpBufferedOut = mpOut; + mpOut = &(mpTargetPaintWindow->GetTargetOutputDevice()); + } + else if (isOutputToWindow()) + // In case mpOut is used without buffering and we're not printing, need to set clipping. + mpOut->SetClipRegion(rRegion); + + // remember original paint MapMode for wrapped FlyFrame paints + maPrePostMapMode = mpOut->GetMapMode(); + } + else + { + // region needs to be updated to the given one + if( mPrePostPaintRegions.top() != rRegion ) + Imp()->GetDrawView()->UpdateDrawLayersRegion(mpPrePostOutDev, rRegion); + mPrePostPaintRegions.push( rRegion ); + } +} + +void SwViewShell::DLPostPaint2(bool bPaintFormLayer) +{ + OSL_ENSURE(!mPrePostPaintRegions.empty(), "SwViewShell::DLPostPaint2: Pre/PostPaint encapsulation broken (!)"); + + if( mPrePostPaintRegions.size() > 1 ) + { + vcl::Region current = std::move(mPrePostPaintRegions.top()); + mPrePostPaintRegions.pop(); + if( current != mPrePostPaintRegions.top()) + Imp()->GetDrawView()->UpdateDrawLayersRegion(mpPrePostOutDev, mPrePostPaintRegions.top()); + return; + } + mPrePostPaintRegions.pop(); // clear + if(nullptr != mpTargetPaintWindow) + { + // #i74769# restore buffered OutDev + if(mpTargetPaintWindow->GetPreRenderDevice()) + { + mpOut = mpBufferedOut; + } + + // #i74769# use SdrPaintWindow now direct + Imp()->GetDrawView()->EndDrawLayers(*mpTargetPaintWindow, bPaintFormLayer); + mpTargetPaintWindow = nullptr; + } +} +// end of Pre/PostPaints + +void SwViewShell::ImplEndAction( const bool bIdleEnd ) +{ + // Nothing to do for the printer? + if ( !GetWin() || IsPreview() ) + { + mbPaintWorks = true; + UISizeNotify(); + // tdf#101464 print preview may generate events if another view shell + // performs layout... + if (IsPreview() && Imp()->IsAccessible()) + { + Imp()->FireAccessibleEvents(); + } + return; + } + + mbInEndAction = true; + //will this put the EndAction of the last shell in the sequence? + + SwViewShell::mbLstAct = true; + for(SwViewShell& rShell : GetRingContainer()) + { + if(&rShell != this && rShell.ActionPend()) + { + SwViewShell::mbLstAct = false; + break; + } + } + + const bool bIsShellForCheckViewLayout = ( this == GetLayout()->GetCurrShell() ); + + SET_CURR_SHELL( this ); + if ( Imp()->HasDrawView() && !Imp()->GetDrawView()->areMarkHandlesHidden() ) + Imp()->StartAction(); + + if ( Imp()->GetRegion() && Imp()->GetRegion()->GetOrigin() != VisArea() ) + Imp()->DelRegion(); + + const bool bExtraData = ::IsExtraData( GetDoc() ); + + if ( !bIdleEnd ) + { + SwLayAction aAction( GetLayout(), Imp() ); + aAction.SetComplete( false ); + if ( mnLockPaint ) + aAction.SetPaint( false ); + aAction.Action(GetWin()); + } + + if ( bIsShellForCheckViewLayout ) + GetLayout()->CheckViewLayout( GetViewOptions(), &maVisArea ); + + //If we don't call Paints, we wait for the Paint of the system. + //Then the clipping is set correctly; e.g. shifting of a Draw object + if ( Imp()->GetRegion() || + maInvalidRect.HasArea() || + bExtraData ) + { + if ( !mnLockPaint ) + { + SolarMutexGuard aGuard; + + bool bPaintsFromSystem = maInvalidRect.HasArea(); + GetWin()->PaintImmediately(); + if ( maInvalidRect.HasArea() ) + { + if ( bPaintsFromSystem ) + Imp()->AddPaintRect( maInvalidRect ); + + ResetInvalidRect(); + bPaintsFromSystem = true; + } + mbPaintWorks = true; + + std::unique_ptr<SwRegionRects> pRegion = std::move(Imp()->m_pRegion); + + //JP 27.11.97: what hid the selection, must also Show it, + // else we get Paint errors! + // e.g. additional mode, page half visible vertically, in the + // middle a selection and with another cursor jump to left + // right border. Without ShowCursor the selection disappears. + bool bShowCursor = pRegion && dynamic_cast<const SwCursorShell*>(this) != nullptr; + if( bShowCursor ) + static_cast<SwCursorShell*>(this)->HideCursors(); + + if ( pRegion ) + { + SwRootFrame* pCurrentLayout = GetLayout(); + + //First Invert then Compress, never the other way round! + pRegion->Invert(); + + pRegion->Compress(); + + ScopedVclPtr<VirtualDevice> pVout; + while ( !pRegion->empty() ) + { + SwRect aRect( pRegion->back() ); + pRegion->pop_back(); + + bool bPaint = true; + if ( IsEndActionByVirDev() ) + { + //create virtual device and set. + if ( !pVout ) + pVout = VclPtr<VirtualDevice>::Create( *GetOut() ); + MapMode aMapMode( GetOut()->GetMapMode() ); + pVout->SetMapMode( aMapMode ); + + bool bSizeOK = true; + + tools::Rectangle aTmp1( aRect.SVRect() ); + aTmp1 = GetOut()->LogicToPixel( aTmp1 ); + tools::Rectangle aTmp2( GetOut()->PixelToLogic( aTmp1 ) ); + if ( aTmp2.Left() > aRect.Left() ) + aTmp1.SetLeft( std::max( 0L, aTmp1.Left() - 1 ) ); + if ( aTmp2.Top() > aRect.Top() ) + aTmp1.SetTop( std::max( 0L, aTmp1.Top() - 1 ) ); + aTmp1.AdjustRight(1 ); + aTmp1.AdjustBottom(1 ); + aTmp1 = GetOut()->PixelToLogic( aTmp1 ); + aRect = SwRect( aTmp1 ); + + const Size aTmp( pVout->GetOutputSize() ); + if ( aTmp.Height() < aRect.Height() || + aTmp.Width() < aRect.Width() ) + { + bSizeOK = pVout->SetOutputSize( aRect.SSize() ); + } + if ( bSizeOK ) + { + bPaint = false; + + // --> OD 2007-07-26 #i79947# + // #i72754# start Pre/PostPaint encapsulation before mpOut is changed to the buffering VDev + const vcl::Region aRepaintRegion(aRect.SVRect()); + DLPrePaint2(aRepaintRegion); + // <-- + + OutputDevice *pOld = GetOut(); + pVout->SetLineColor( pOld->GetLineColor() ); + pVout->SetFillColor( pOld->GetFillColor() ); + Point aOrigin( aRect.Pos() ); + aOrigin.setX( -aOrigin.X() ); aOrigin.setY( -aOrigin.Y() ); + aMapMode.SetOrigin( aOrigin ); + pVout->SetMapMode( aMapMode ); + + mpOut = pVout.get(); + if ( bPaintsFromSystem ) + PaintDesktop(*mpOut, aRect); + pCurrentLayout->PaintSwFrame( *mpOut, aRect ); + pOld->DrawOutDev( aRect.Pos(), aRect.SSize(), + aRect.Pos(), aRect.SSize(), *pVout ); + mpOut = pOld; + + // #i72754# end Pre/PostPaint encapsulation when mpOut is back and content is painted + DLPostPaint2(true); + } + } + if ( bPaint ) + { + if (GetWin()->SupportsDoubleBuffering()) + InvalidateWindows(aRect.SVRect()); + else + { + // #i75172# begin DrawingLayer paint + // need to do begin/end DrawingLayer preparation for each single rectangle of the + // repaint region. I already tried to prepare only once for the whole Region. This + // seems to work (and does technically) but fails with transparent objects. Since the + // region given to BeginDarwLayers() defines the clip region for DrawingLayer paint, + // transparent objects in the single rectangles will indeed be painted multiple times. + DLPrePaint2(vcl::Region(aRect.SVRect())); + + if ( bPaintsFromSystem ) + PaintDesktop(*GetOut(), aRect); + if (!comphelper::LibreOfficeKit::isActive()) + pCurrentLayout->PaintSwFrame( *mpOut, aRect ); + else + pCurrentLayout->GetCurrShell()->InvalidateWindows(aRect.SVRect()); + + // #i75172# end DrawingLayer paint + DLPostPaint2(true); + } + } + + lcl_PaintTransparentFormControls(*this, aRect); // i#107365 + } + } + if( bShowCursor ) + static_cast<SwCursorShell*>(this)->ShowCursors( true ); + } + else + { + Imp()->DelRegion(); + mbPaintWorks = true; + } + } + else + mbPaintWorks = true; + + mbInEndAction = false; + SwViewShell::mbLstAct = false; + Imp()->EndAction(); + + //We artificially end the action here to enable the automatic scrollbars + //to adjust themselves correctly + //EndAction sends a Notify, and that must call Start-/EndAction to + //adjust the scrollbars correctly + --mnStartAction; + UISizeNotify(); + ++mnStartAction; + + if( Imp()->IsAccessible() ) + Imp()->FireAccessibleEvents(); +} + +void SwViewShell::ImplStartAction() +{ + mbPaintWorks = false; + Imp()->StartAction(); +} + +void SwViewShell::ImplLockPaint() +{ + if ( GetWin() && GetWin()->IsVisible() ) + GetWin()->EnablePaint( false ); //Also cut off the controls. + Imp()->LockPaint(); +} + +void SwViewShell::ImplUnlockPaint( bool bVirDev ) +{ + SET_CURR_SHELL( this ); + if ( GetWin() && GetWin()->IsVisible() ) + { + if ( (bInSizeNotify || bVirDev ) && VisArea().HasArea() ) + { + //Refresh with virtual device to avoid flickering. + VclPtrInstance<VirtualDevice> pVout( *mpOut ); + pVout->SetMapMode( mpOut->GetMapMode() ); + Size aSize( VisArea().SSize() ); + aSize.AdjustWidth(20 ); + aSize.AdjustHeight(20 ); + if( pVout->SetOutputSize( aSize ) ) + { + GetWin()->EnablePaint( true ); + GetWin()->Validate(); + + Imp()->UnlockPaint(); + pVout->SetLineColor( mpOut->GetLineColor() ); + pVout->SetFillColor( mpOut->GetFillColor() ); + + // #i72754# start Pre/PostPaint encapsulation before mpOut is changed to the buffering VDev + const vcl::Region aRepaintRegion(VisArea().SVRect()); + DLPrePaint2(aRepaintRegion); + + OutputDevice *pOld = mpOut; + mpOut = pVout.get(); + Paint(*mpOut, VisArea().SVRect()); + mpOut = pOld; + mpOut->DrawOutDev( VisArea().Pos(), aSize, + VisArea().Pos(), aSize, *pVout ); + + // #i72754# end Pre/PostPaint encapsulation when mpOut is back and content is painted + DLPostPaint2(true); + + lcl_PaintTransparentFormControls(*this, VisArea()); // fdo#63949 + } + else + { + Imp()->UnlockPaint(); + GetWin()->EnablePaint( true ); + GetWin()->Invalidate( InvalidateFlags::Children ); + } + pVout.disposeAndClear(); + } + else + { + Imp()->UnlockPaint(); + GetWin()->EnablePaint( true ); + GetWin()->Invalidate( InvalidateFlags::Children ); + } + } + else + Imp()->UnlockPaint(); +} + +bool SwViewShell::AddPaintRect( const SwRect & rRect ) +{ + bool bRet = false; + for(SwViewShell& rSh : GetRingContainer()) + { + if( rSh.Imp() ) + { + if ( rSh.IsPreview() && rSh.GetWin() ) + ::RepaintPagePreview( &rSh, rRect ); + else + bRet |= rSh.Imp()->AddPaintRect( rRect ); + } + } + return bRet; +} + +void SwViewShell::InvalidateWindows( const SwRect &rRect ) +{ + if ( !Imp()->IsCalcLayoutProgress() ) + { + for(SwViewShell& rSh : GetRingContainer()) + { + if ( rSh.GetWin() ) + { + if ( rSh.IsPreview() ) + ::RepaintPagePreview( &rSh, rRect ); + // In case of tiled rendering, invalidation is wanted even if + // the rectangle is outside the visual area. + else if ( rSh.VisArea().IsOver( rRect ) || comphelper::LibreOfficeKit::isActive() ) + rSh.GetWin()->Invalidate( rRect.SVRect() ); + } + } + } +} + +const SwRect& SwViewShell::VisArea() const +{ + // when using the tiled rendering, consider the entire document as our + // visible area + return comphelper::LibreOfficeKit::isActive()? GetLayout()->getFrameArea(): maVisArea; +} + +void SwViewShell::MakeVisible( const SwRect &rRect ) +{ + if ( !VisArea().IsInside( rRect ) || IsScrollMDI( this, rRect ) || GetCareDialog(*this) ) + { + if ( !IsViewLocked() ) + { + if( mpWin ) + { + const SwFrame* pRoot = GetLayout(); + int nLoopCnt = 3; + long nOldH; + do{ + nOldH = pRoot->getFrameArea().Height(); + StartAction(); + ScrollMDI( this, rRect, USHRT_MAX, USHRT_MAX ); + EndAction(); + } while( nOldH != pRoot->getFrameArea().Height() && nLoopCnt-- ); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + //MA: 04. Nov. 94, no one needs this, does one? + OSL_ENSURE( false, "Is MakeVisible still needed for printers?" ); + } + +#endif + } + } +} + +weld::Window* SwViewShell::CareChildWin(SwViewShell const & rVSh) +{ + if (!rVSh.mpSfxViewShell) + return nullptr; +#if HAVE_FEATURE_DESKTOP + const sal_uInt16 nId = SvxSearchDialogWrapper::GetChildWindowId(); + SfxViewFrame* pVFrame = rVSh.mpSfxViewShell->GetViewFrame(); + SfxChildWindow* pChWin = pVFrame->GetChildWindow( nId ); + if (!pChWin) + return nullptr; + weld::DialogController* pController = pChWin->GetController().get(); + if (!pController) + return nullptr; + weld::Window* pWin = pController->getDialog(); + if (pWin && pWin->get_visible()) + return pWin; +#endif + return nullptr; +} + +Point SwViewShell::GetPagePos( sal_uInt16 nPageNum ) const +{ + return GetLayout()->GetPagePos( nPageNum ); +} + +sal_uInt16 SwViewShell::GetNumPages() const +{ + //It is possible that no layout exists when the method from + //root-Ctor is called. + return GetLayout() ? GetLayout()->GetPageNum() : 0; +} + +bool SwViewShell::IsDummyPage( sal_uInt16 nPageNum ) const +{ + return GetLayout() && GetLayout()->IsDummyPage( nPageNum ); +} + +/** + * Forces update of each field. + * It notifies all fields with pNewHt. If that is 0 (default), the field + * type is sent (???). + * @param[in] bCloseDB Passed in to GetDoc()->UpdateFields. [TODO] Purpose??? + */ +void SwViewShell::UpdateFields(bool bCloseDB) +{ + SET_CURR_SHELL( this ); + + bool bCursor = dynamic_cast<const SwCursorShell*>( this ) != nullptr; + if ( bCursor ) + static_cast<SwCursorShell*>(this)->StartAction(); + else + StartAction(); + + GetDoc()->getIDocumentFieldsAccess().UpdateFields(bCloseDB); + + if ( bCursor ) + static_cast<SwCursorShell*>(this)->EndAction(); + else + EndAction(); +} + +/** update all charts for which any table exists */ +void SwViewShell::UpdateAllCharts() +{ + SET_CURR_SHELL( this ); + // Start-/EndAction handled in the SwDoc-Method! + GetDoc()->UpdateAllCharts(); +} + +bool SwViewShell::HasCharts() const +{ + bool bRet = false; + SwNodeIndex aIdx( *GetDoc()->GetNodes().GetEndOfAutotext(). + StartOfSectionNode(), 1 ); + while (aIdx.GetNode().GetStartNode()) + { + ++aIdx; + const SwOLENode *pNd = aIdx.GetNode().GetOLENode(); + if( pNd && !pNd->GetChartTableName().isEmpty() ) + { + bRet = true; + break; + } + } + return bRet; +} + +void SwViewShell::LayoutIdle() +{ + if( !mpOpt->IsIdle() || !GetWin() || HasDrawViewDrag() ) + return; + + //No idle when printing is going on. + for(const SwViewShell& rSh : GetRingContainer()) + { + if ( !rSh.GetWin() ) + return; + } + + SET_CURR_SHELL( this ); + +#ifdef DBG_UTIL + // If Test5 has been set, the IdleFormatter is disabled. + if( mpOpt->IsTest5() ) + return; +#endif + + { + // Preserve top of the text frame cache. + SwSaveSetLRUOfst aSaveLRU; + // #125243# there are lots of stacktraces indicating that Imp() returns NULL + // this SwViewShell seems to be invalid - but it's not clear why + // this return is only a workaround! + OSL_ENSURE(Imp(), "SwViewShell already deleted?"); + if(!Imp()) + return; + SwLayIdle aIdle( GetLayout(), Imp() ); + } +} + +static void lcl_InvalidateAllContent( SwViewShell& rSh, SwInvalidateFlags nInv ) +{ + bool bCursor = dynamic_cast<const SwCursorShell*>( &rSh) != nullptr; + if ( bCursor ) + static_cast<SwCursorShell&>(rSh).StartAction(); + else + rSh.StartAction(); + rSh.GetLayout()->InvalidateAllContent( nInv ); + if ( bCursor ) + static_cast<SwCursorShell&>(rSh).EndAction(); + else + rSh.EndAction(); + + rSh.GetDoc()->getIDocumentState().SetModified(); +} + +/** local method to invalidate/re-calculate positions of floating screen + * objects (Writer fly frame and drawing objects), which are anchored + * to paragraph or to character. #i11860# + */ +static void lcl_InvalidateAllObjPos( SwViewShell &_rSh ) +{ + const bool bIsCursorShell = dynamic_cast<const SwCursorShell*>( &_rSh) != nullptr; + if ( bIsCursorShell ) + static_cast<SwCursorShell&>(_rSh).StartAction(); + else + _rSh.StartAction(); + + _rSh.GetLayout()->InvalidateAllObjPos(); + + if ( bIsCursorShell ) + static_cast<SwCursorShell&>(_rSh).EndAction(); + else + _rSh.EndAction(); + + _rSh.GetDoc()->getIDocumentState().SetModified(); +} + +void SwViewShell::SetParaSpaceMax( bool bNew ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) != bNew ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::PARA_SPACE_MAX, bNew ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +void SwViewShell::SetParaSpaceMaxAtPages( bool bNew ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES) != bNew ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES, bNew ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +void SwViewShell::SetTabCompat( bool bNew ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if( rIDSA.get(DocumentSettingId::TAB_COMPAT) != bNew ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::TAB_COMPAT, bNew ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Size | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +void SwViewShell::SetAddExtLeading( bool bNew ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::ADD_EXT_LEADING) != bNew ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::ADD_EXT_LEADING, bNew ); + SwDrawModel* pTmpDrawModel = getIDocumentDrawModelAccess().GetDrawModel(); + if ( pTmpDrawModel ) + pTmpDrawModel->SetAddExtLeading( bNew ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Size | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +void SwViewShell::SetUseVirDev( bool bNewVirtual ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::USE_VIRTUAL_DEVICE) != bNewVirtual ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + // this sets the flag at the document and calls PrtDataChanged + IDocumentDeviceAccess& rIDDA = getIDocumentDeviceAccess(); + rIDDA.setReferenceDeviceType( bNewVirtual, true ); + } +} + +/** Sets if paragraph and table spacing is added at bottom of table cells. + * #106629# + * @param[in] (bool) setting of the new value + */ +void SwViewShell::SetAddParaSpacingToTableCells( bool _bAddParaSpacingToTableCells ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if (rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) != _bAddParaSpacingToTableCells + || rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS) != _bAddParaSpacingToTableCells) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS, _bAddParaSpacingToTableCells ); + // note: the dialog can't change the value to indeterminate, so only false/false and true/true + rIDSA.set(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS, _bAddParaSpacingToTableCells ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +/** + * Sets if former formatting of text lines with proportional line spacing should used. + * #i11859# + * @param[in] (bool) setting of the new value + */ +void SwViewShell::SetUseFormerLineSpacing( bool _bUseFormerLineSpacing ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::OLD_LINE_SPACING) != _bUseFormerLineSpacing ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::OLD_LINE_SPACING, _bUseFormerLineSpacing ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +/** + * Sets IDocumentSettingAccess if former object positioning should be used. + * #i11860# + * @param[in] (bool) setting the new value + */ +void SwViewShell::SetUseFormerObjectPositioning( bool _bUseFormerObjPos ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::USE_FORMER_OBJECT_POS) != _bUseFormerObjPos ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::USE_FORMER_OBJECT_POS, _bUseFormerObjPos ); + lcl_InvalidateAllObjPos( *this ); + } +} + +// #i28701# +void SwViewShell::SetConsiderWrapOnObjPos( bool _bConsiderWrapOnObjPos ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) != _bConsiderWrapOnObjPos ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION, _bConsiderWrapOnObjPos ); + lcl_InvalidateAllObjPos( *this ); + } +} + +void SwViewShell::SetUseFormerTextWrapping( bool _bUseFormerTextWrapping ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) != _bUseFormerTextWrapping ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::USE_FORMER_TEXT_WRAPPING, _bUseFormerTextWrapping ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Size | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +// #i45491# +void SwViewShell::SetDoNotJustifyLinesWithManualBreak( bool _bDoNotJustifyLinesWithManualBreak ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK) != _bDoNotJustifyLinesWithManualBreak ) + { + SwWait aWait( *GetDoc()->GetDocShell(), true ); + rIDSA.set(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK, _bDoNotJustifyLinesWithManualBreak ); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Size | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent( *this, nInv ); + } +} + +void SwViewShell::SetProtectForm( bool _bProtectForm ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + rIDSA.set(DocumentSettingId::PROTECT_FORM, _bProtectForm ); +} + +void SwViewShell::SetMsWordCompTrailingBlanks( bool _bMsWordCompTrailingBlanks ) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if (rIDSA.get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS) != _bMsWordCompTrailingBlanks) + { + SwWait aWait(*GetDoc()->GetDocShell(), true); + rIDSA.set(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS, _bMsWordCompTrailingBlanks); + const SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Size | SwInvalidateFlags::Table | SwInvalidateFlags::Section; + lcl_InvalidateAllContent(*this, nInv); + } +} + +void SwViewShell::SetSubtractFlysAnchoredAtFlys(bool bSubtractFlysAnchoredAtFlys) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + rIDSA.set(DocumentSettingId::SUBTRACT_FLYS, bSubtractFlysAnchoredAtFlys); +} + +void SwViewShell::SetEmptyDbFieldHidesPara(bool bEmptyDbFieldHidesPara) +{ + IDocumentSettingAccess& rIDSA = getIDocumentSettingAccess(); + if (rIDSA.get(DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA) != bEmptyDbFieldHidesPara) + { + SwWait aWait(*GetDoc()->GetDocShell(), true); + rIDSA.set(DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA, bEmptyDbFieldHidesPara); + StartAction(); + GetDoc()->getIDocumentState().SetModified(); + for (auto const & pFieldType : *GetDoc()->getIDocumentFieldsAccess().GetFieldTypes()) + { + if (pFieldType->Which() == SwFieldIds::Database) + { + pFieldType->ModifyNotification(nullptr, nullptr); + } + } + EndAction(); + } +} + +void SwViewShell::Reformat() +{ + SwWait aWait( *GetDoc()->GetDocShell(), true ); + + // we go for safe: get rid of the old font information, + // when the printer resolution or zoom factor changes. + // Init() and Reformat() are the safest locations. + pFntCache->Flush( ); + + if( GetLayout()->IsCallbackActionEnabled() ) + { + StartAction(); + GetLayout()->InvalidateAllContent( SwInvalidateFlags::Size | SwInvalidateFlags::Pos | SwInvalidateFlags::PrtArea ); + EndAction(); + } +} + +void SwViewShell::ChgNumberDigits() +{ + SdrModel* pTmpDrawModel = getIDocumentDrawModelAccess().GetDrawModel(); + if ( pTmpDrawModel ) + pTmpDrawModel->ReformatAllTextObjects(); + Reformat(); +} + +void SwViewShell::CalcLayout() +{ + // extremely likely to be a Bad Idea to call this without StartAction + // (except the Page Preview apparently only has a non-subclassed ViewShell) + assert((typeid(*this) == typeid(SwViewShell)) || mnStartAction); + + SET_CURR_SHELL( this ); + SwWait aWait( *GetDoc()->GetDocShell(), true ); + + // Preserve top of the text frame cache. + SwSaveSetLRUOfst aSaveLRU; + + //switch on Progress when none is running yet. + const bool bEndProgress = SfxProgress::GetActiveProgress( GetDoc()->GetDocShell() ) == nullptr; + if ( bEndProgress ) + { + long nEndPage = GetLayout()->GetPageNum(); + nEndPage += nEndPage * 10 / 100; + ::StartProgress( STR_STATSTR_REFORMAT, 0, nEndPage, GetDoc()->GetDocShell() ); + } + + SwLayAction aAction( GetLayout(), Imp() ); + aAction.SetPaint( false ); + aAction.SetStatBar( true ); + aAction.SetCalcLayout( true ); + aAction.SetReschedule( true ); + GetDoc()->getIDocumentFieldsAccess().LockExpFields(); + aAction.Action(GetOut()); + GetDoc()->getIDocumentFieldsAccess().UnlockExpFields(); + + //the SetNewFieldLst() on the Doc was cut off and must be fetched again + //(see flowfrm.cxx, txtfld.cxx) + if ( aAction.IsExpFields() ) + { + aAction.Reset(); + aAction.SetPaint( false ); + aAction.SetStatBar( true ); + aAction.SetReschedule( true ); + + SwDocPosUpdate aMsgHint( 0 ); + GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + GetDoc()->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + aAction.Action(GetOut()); + } + + if ( VisArea().HasArea() ) + InvalidateWindows( VisArea() ); + if ( bEndProgress ) + ::EndProgress( GetDoc()->GetDocShell() ); +} + +void SwViewShell::SetFirstVisPageInvalid() +{ + for(SwViewShell& rSh : GetRingContainer()) + { + if ( rSh.Imp() ) + rSh.Imp()->SetFirstVisPageInvalid(); + } +} + +void SwViewShell::SizeChgNotify() +{ + if ( !mpWin ) + mbDocSizeChgd = true; + else if( ActionPend() || Imp()->IsCalcLayoutProgress() || mbPaintInProgress ) + { + mbDocSizeChgd = true; + + if ( !Imp()->IsCalcLayoutProgress() && dynamic_cast<const SwCursorShell*>( this ) != nullptr ) + { + const SwFrame *pCnt = static_cast<SwCursorShell*>(this)->GetCurrFrame( false ); + const SwPageFrame *pPage; + if ( pCnt && nullptr != (pPage = pCnt->FindPageFrame()) ) + { + const sal_uInt16 nVirtNum = pPage->GetVirtPageNum(); + const SvxNumberType& rNum = pPage->GetPageDesc()->GetNumType(); + OUString sDisplay = rNum.GetNumStr( nVirtNum ); + PageNumNotify( this, pCnt->GetPhyPageNum(), nVirtNum, sDisplay ); + + if (comphelper::LibreOfficeKit::isActive()) + { + Size aDocSize = GetDocSize(); + std::stringstream ss; + ss << aDocSize.Width() + 2 * DOCUMENTBORDER << ", " << aDocSize.Height() + 2 * DOCUMENTBORDER; + OString sSize = ss.str().c_str(); + + SwXTextDocument* pModel = comphelper::getUnoTunnelImplementation<SwXTextDocument>(GetSfxViewShell()->GetCurrentDocument()); + SfxLokHelper::notifyDocumentSizeChanged(GetSfxViewShell(), sSize, pModel); + } + } + } + } + else + { + mbDocSizeChgd = false; + ::SizeNotify( this, GetDocSize() ); + } +} + +void SwViewShell::VisPortChgd( const SwRect &rRect) +{ + OSL_ENSURE( GetWin(), "VisPortChgd without Window." ); + + if ( rRect == VisArea() ) + return; + + // Is someone spuriously rescheduling again? + SAL_WARN_IF(mbInEndAction, "sw.core", "Scrolling during EndAction"); + + //First get the old visible page, so we don't have to look + //for it afterwards. + const SwFrame *pOldPage = Imp()->GetFirstVisPage(GetWin()); + + const SwRect aPrevArea( VisArea() ); + const bool bFull = aPrevArea.IsEmpty(); + maVisArea = rRect; + SetFirstVisPageInvalid(); + + //When there a PaintRegion still exists and the VisArea has changed, + //the PaintRegion is at least by now obsolete. The PaintRegion can + //have been created by RootFrame::PaintSwFrame. + if ( !mbInEndAction && + Imp()->GetRegion() && Imp()->GetRegion()->GetOrigin() != VisArea() ) + Imp()->DelRegion(); + + SET_CURR_SHELL( this ); + + bool bScrolled = false; + + SwPostItMgr* pPostItMgr = GetPostItMgr(); + + if ( bFull ) + GetWin()->Invalidate(); + else + { + //Calculate amount to be scrolled. + const long nXDiff = aPrevArea.Left() - VisArea().Left(); + const long nYDiff = aPrevArea.Top() - VisArea().Top(); + + if( !nXDiff && !GetViewOptions()->getBrowseMode() && + (!Imp()->HasDrawView() || !Imp()->GetDrawView()->IsGridVisible() ) ) + { + // If possible, don't scroll the application background + // (PaintDesktop). Also limit the left and right side of + // the scroll range to the pages. + const SwPageFrame *pPage = static_cast<SwPageFrame*>(GetLayout()->Lower()); + if ( pPage->getFrameArea().Top() > pOldPage->getFrameArea().Top() ) + pPage = static_cast<const SwPageFrame*>(pOldPage); + SwRect aBoth( VisArea() ); + aBoth.Union( aPrevArea ); + const SwTwips nBottom = aBoth.Bottom(); + SwTwips nMinLeft = SAL_MAX_INT32; + SwTwips nMaxRight= 0; + + const bool bBookMode = GetViewOptions()->IsViewLayoutBookMode(); + + while ( pPage && pPage->getFrameArea().Top() <= nBottom ) + { + SwRect aPageRect( pPage->GetBoundRect(GetWin()) ); + if ( bBookMode ) + { + const SwPageFrame& rFormatPage = pPage->GetFormatPage(); + aPageRect.SSize( rFormatPage.GetBoundRect(GetWin()).SSize() ); + } + + // #i9719# - consider new border and shadow width + if ( aPageRect.IsOver( aBoth ) ) + { + SwTwips nPageLeft = 0; + SwTwips nPageRight = 0; + const sw::sidebarwindows::SidebarPosition aSidebarPos = pPage->SidebarPosition(); + + if( aSidebarPos != sw::sidebarwindows::SidebarPosition::NONE ) + { + nPageLeft = aPageRect.Left(); + nPageRight = aPageRect.Right(); + } + + if( nPageLeft < nMinLeft ) + nMinLeft = nPageLeft; + if( nPageRight > nMaxRight ) + nMaxRight = nPageRight; + //match with the draw objects + //take nOfst into account as the objects have been + //selected and have handles attached. + if ( pPage->GetSortedObjs() ) + { + const long nOfst = GetOut()->PixelToLogic( + Size(Imp()->GetDrawView()->GetMarkHdlSizePixel()/2,0)).Width(); + for (SwAnchoredObject* pObj : *pPage->GetSortedObjs()) + { + // ignore objects that are not actually placed on the page + if (pObj->IsFormatPossible()) + { + const tools::Rectangle &rBound = pObj->GetObjRect().SVRect(); + if (rBound.Left() != FAR_AWAY) { + // OD 03.03.2003 #107927# - use correct datatype + const SwTwips nL = std::max( 0L, rBound.Left() - nOfst ); + if ( nL < nMinLeft ) + nMinLeft = nL; + if( rBound.Right() + nOfst > nMaxRight ) + nMaxRight = rBound.Right() + nOfst; + } + } + } + } + } + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + tools::Rectangle aRect( aPrevArea.SVRect() ); + aRect.SetLeft( nMinLeft ); + aRect.SetRight( nMaxRight ); + if( VisArea().IsOver( aPrevArea ) && !mnLockPaint ) + { + bScrolled = true; + maVisArea.Pos() = aPrevArea.Pos(); + if ( SmoothScroll( nXDiff, nYDiff, &aRect ) ) + return; + maVisArea.Pos() = rRect.Pos(); + } + else + GetWin()->Invalidate( aRect ); + } + else if ( !mnLockPaint ) //will be released in Unlock + { + if( VisArea().IsOver( aPrevArea ) ) + { + bScrolled = true; + maVisArea.Pos() = aPrevArea.Pos(); + if ( SmoothScroll( nXDiff, nYDiff, nullptr ) ) + return; + maVisArea.Pos() = rRect.Pos(); + } + else + GetWin()->Invalidate(); + } + } + + // When tiled rendering, the map mode of the window is disabled, avoid + // enabling it here. + if (!comphelper::LibreOfficeKit::isActive()) + { + Point aPt( VisArea().Pos() ); + aPt.setX( -aPt.X() ); aPt.setY( -aPt.Y() ); + MapMode aMapMode( GetWin()->GetMapMode() ); + aMapMode.SetOrigin( aPt ); + GetWin()->SetMapMode( aMapMode ); + } + + if ( HasDrawView() ) + { + Imp()->GetDrawView()->VisAreaChanged( GetWin() ); + Imp()->GetDrawView()->SetActualWin( GetWin() ); + } + GetWin()->PaintImmediately(); + + if ( pPostItMgr ) // #i88070# + { + pPostItMgr->Rescale(); + pPostItMgr->CalcRects(); + pPostItMgr->LayoutPostIts(); + } + + if ( !bScrolled && pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + pPostItMgr->CorrectPositions(); + + if( Imp()->IsAccessible() ) + Imp()->UpdateAccessible(); +} + +bool SwViewShell::SmoothScroll( long lXDiff, long lYDiff, const tools::Rectangle *pRect ) +{ +#if !defined(MACOSX) && !defined(ANDROID) && !defined(IOS) + // #i98766# - disable smooth scrolling for Mac + + const sal_uLong nBitCnt = mpOut->GetBitCount(); + long lMult = 1, lMax = LONG_MAX; + if ( nBitCnt == 16 ) + { + lMax = 7000; + lMult = 2; + } + if ( nBitCnt == 24 ) + { + lMax = 5000; + lMult = 6; + } + else if ( nBitCnt == 1 ) + { + lMax = 3000; + lMult = 12; + } + + // #i75172# isolated static conditions + const bool bOnlyYScroll(!lXDiff && std::abs(lYDiff) != 0 && std::abs(lYDiff) < lMax); + const bool bAllowedWithChildWindows(GetWin()->GetWindowClipRegionPixel().IsNull()); + const bool bSmoothScrollAllowed(bOnlyYScroll && mbEnableSmooth && GetViewOptions()->IsSmoothScroll() && bAllowedWithChildWindows); + + if(bSmoothScrollAllowed) + { + Imp()->m_bStopSmooth = false; + + const SwRect aOldVis( VisArea() ); + + //create virtual device and set. + const Size aPixSz = GetWin()->PixelToLogic(Size(1,1)); + VclPtrInstance<VirtualDevice> pVout( *GetWin() ); + pVout->SetLineColor( GetWin()->GetLineColor() ); + pVout->SetFillColor( GetWin()->GetFillColor() ); + MapMode aMapMode( GetWin()->GetMapMode() ); + pVout->SetMapMode( aMapMode ); + Size aSize( maVisArea.Width()+2*aPixSz.Width(), std::abs(lYDiff)+(2*aPixSz.Height()) ); + if ( pRect ) + aSize.setWidth( std::min(aSize.Width(), pRect->GetWidth()+2*aPixSz.Width()) ); + if ( pVout->SetOutputSize( aSize ) ) + { + mnLockPaint++; + + //First Paint everything in the virtual device. + SwRect aRect( VisArea() ); + aRect.Height( aSize.Height() ); + if ( pRect ) + { + aRect.Pos().setX( std::max(aRect.Left(),pRect->Left()-aPixSz.Width()) ); + aRect.Right( std::min(aRect.Right()+2*aPixSz.Width(), pRect->Right()+aPixSz.Width())); + } + else + aRect.AddWidth(2*aPixSz.Width() ); + aRect.Pos().setY( lYDiff < 0 ? aOldVis.Bottom() - aPixSz.Height() + : aRect.Top() - aSize.Height() + aPixSz.Height() ); + aRect.Pos().setX( std::max( 0L, aRect.Left()-aPixSz.Width() ) ); + aRect.Pos() = GetWin()->PixelToLogic( GetWin()->LogicToPixel( aRect.Pos())); + aRect.SSize( GetWin()->PixelToLogic( GetWin()->LogicToPixel( aRect.SSize())) ); + maVisArea = aRect; + const Point aPt( -aRect.Left(), -aRect.Top() ); + aMapMode.SetOrigin( aPt ); + pVout->SetMapMode( aMapMode ); + OutputDevice *pOld = mpOut; + mpOut = pVout.get(); + + { + // #i75172# To get a clean repaint, a new ObjectContact is needed here. Without, the + // repaint would not be correct since it would use the wrong DrawPage visible region. + // This repaint IS about painting something currently outside the visible part (!). + // For that purpose, AddWindowToPaintView is used which creates a new SdrPageViewWindow + // and all the necessary stuff. It's not cheap, but necessary here. Alone because repaint + // target really is NOT the current window. + // Also will automatically NOT use PreRendering and overlay (since target is VirtualDevice) + if(!HasDrawView()) + MakeDrawView(); + SdrView* pDrawView = GetDrawView(); + pDrawView->AddWindowToPaintView(pVout, nullptr); + + // clear mpWin during DLPrePaint2 to get paint preparation for mpOut, but set it again + // immediately afterwards. There are many decisions in SW which imply that Printing + // is used when mpWin == 0 (wrong but widely used). + vcl::Window* pOldWin = mpWin; + mpWin = nullptr; + DLPrePaint2(vcl::Region(aRect.SVRect())); + mpWin = pOldWin; + + // SW paint stuff + PaintDesktop(*GetOut(), aRect); + SwViewShell::mbLstAct = true; + GetLayout()->PaintSwFrame( *GetOut(), aRect ); + SwViewShell::mbLstAct = false; + + // end paint and destroy ObjectContact again + DLPostPaint2(true); + pDrawView->DeleteWindowFromPaintView(pVout); + } + + mpOut = pOld; + maVisArea = aOldVis; + + //Now shift in parts and copy the new Pixel from the virtual device. + + // ?????????????????????? + // or is it better to get the scrollfactor from the User + // as option? + // ?????????????????????? + long lMaDelta = aPixSz.Height(); + if ( std::abs(lYDiff) > ( maVisArea.Height() / 3 ) ) + lMaDelta *= 6; + else + lMaDelta *= 2; + + lMaDelta *= lMult; + + if ( lYDiff < 0 ) + lMaDelta = -lMaDelta; + + long lDiff = lYDiff; + while ( lDiff ) + { + long lScroll; + if ( Imp()->m_bStopSmooth || std::abs(lDiff) <= std::abs(lMaDelta) ) + { + lScroll = lDiff; + lDiff = 0; + } + else + { + lScroll = lMaDelta; + lDiff -= lMaDelta; + } + + const SwRect aTmpOldVis = VisArea(); + maVisArea.Pos().AdjustY( -lScroll ); + maVisArea.Pos() = GetWin()->PixelToLogic( GetWin()->LogicToPixel( VisArea().Pos())); + lScroll = aTmpOldVis.Top() - VisArea().Top(); + if ( pRect ) + { + tools::Rectangle aTmp( aTmpOldVis.SVRect() ); + aTmp.SetLeft( pRect->Left() ); + aTmp.SetRight( pRect->Right() ); + GetWin()->Scroll( 0, lScroll, aTmp, ScrollFlags::Children); + } + else + GetWin()->Scroll( 0, lScroll, ScrollFlags::Children ); + + const Point aTmpPt( -VisArea().Left(), -VisArea().Top() ); + MapMode aTmpMapMode( GetWin()->GetMapMode() ); + aTmpMapMode.SetOrigin( aTmpPt ); + GetWin()->SetMapMode( aTmpMapMode ); + + if ( Imp()->HasDrawView() ) + Imp()->GetDrawView()->VisAreaChanged( GetWin() ); + + SetFirstVisPageInvalid(); + if ( !Imp()->m_bStopSmooth ) + { + const bool bScrollDirectionIsUp(lScroll > 0); + Imp()->m_aSmoothRect = VisArea(); + + if(bScrollDirectionIsUp) + { + Imp()->m_aSmoothRect.Bottom( VisArea().Top() + lScroll + aPixSz.Height()); + } + else + { + Imp()->m_aSmoothRect.Top( VisArea().Bottom() + lScroll - aPixSz.Height()); + } + + Imp()->m_bSmoothUpdate = true; + GetWin()->PaintImmediately(); + Imp()->m_bSmoothUpdate = false; + + if(!Imp()->m_bStopSmooth) + { + // start paint on logic base + const tools::Rectangle aTargetLogic(Imp()->m_aSmoothRect.SVRect()); + DLPrePaint2(vcl::Region(aTargetLogic)); + + // get target rectangle in discrete pixels + OutputDevice& rTargetDevice = mpTargetPaintWindow->GetTargetOutputDevice(); + const tools::Rectangle aTargetPixel(rTargetDevice.LogicToPixel(aTargetLogic)); + + // get source top-left in discrete pixels + const Point aSourceTopLeft(pVout->LogicToPixel(aTargetLogic.TopLeft())); + + // switch off MapModes + const bool bMapModeWasEnabledDest(rTargetDevice.IsMapModeEnabled()); + const bool bMapModeWasEnabledSource(pVout->IsMapModeEnabled()); + rTargetDevice.EnableMapMode(false); + pVout->EnableMapMode(false); + + rTargetDevice.DrawOutDev( + aTargetPixel.TopLeft(), aTargetPixel.GetSize(), // dest + aSourceTopLeft, aTargetPixel.GetSize(), // source + *pVout); + + // restore MapModes + rTargetDevice.EnableMapMode(bMapModeWasEnabledDest); + pVout->EnableMapMode(bMapModeWasEnabledSource); + + // end paint on logoc base + DLPostPaint2(true); + } + else + --mnLockPaint; + } + } + pVout.disposeAndClear(); + GetWin()->PaintImmediately(); + if ( !Imp()->m_bStopSmooth ) + --mnLockPaint; + SetFirstVisPageInvalid(); + return true; + } + pVout.disposeAndClear(); + } +#endif + + maVisArea.Pos().AdjustX( -lXDiff ); + maVisArea.Pos().AdjustY( -lYDiff ); + if ( pRect ) + GetWin()->Scroll( lXDiff, lYDiff, *pRect, ScrollFlags::Children); + else + GetWin()->Scroll( lXDiff, lYDiff, ScrollFlags::Children); + return false; +} + +void SwViewShell::PaintDesktop(vcl::RenderContext& rRenderContext, const SwRect &rRect) +{ + if ( !GetWin() && !GetOut()->GetConnectMetaFile() ) + return; //for the printer we don't do anything here. + + //Catch exceptions, so that it doesn't look so surprising. + //Can e.g. happen during Idle. + //Unfortunately we must at any rate Paint the rectangles next to the pages, + //as these are not painted at VisPortChgd. + bool bBorderOnly = false; + const SwRootFrame *pRoot = GetLayout(); + if ( rRect.Top() > pRoot->getFrameArea().Bottom() ) + { + const SwFrame *pPg = pRoot->Lower(); + while ( pPg && pPg->GetNext() ) + pPg = pPg->GetNext(); + if ( !pPg || !pPg->getFrameArea().IsOver( VisArea() ) ) + bBorderOnly = true; + } + + const bool bBookMode = GetViewOptions()->IsViewLayoutBookMode(); + + SwRegionRects aRegion( rRect ); + + //mod #i6193: remove sidebar area to avoid flickering + const SwPostItMgr* pPostItMgr = GetPostItMgr(); + const SwTwips nSidebarWidth = pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ? + pPostItMgr->GetSidebarWidth() + pPostItMgr->GetSidebarBorderWidth() : + 0; + + if ( bBorderOnly ) + { + const SwFrame *pPage =pRoot->Lower(); + SwRect aLeft( rRect ), aRight( rRect ); + while ( pPage ) + { + long nTmp = pPage->getFrameArea().Left(); + if ( nTmp < aLeft.Right() ) + aLeft.Right( nTmp ); + nTmp = pPage->getFrameArea().Right(); + if ( nTmp > aRight.Left() ) + { + aRight.Left( nTmp + nSidebarWidth ); + } + pPage = pPage->GetNext(); + } + aRegion.clear(); + if ( aLeft.HasArea() ) + aRegion.push_back( aLeft ); + if ( aRight.HasArea() ) + aRegion.push_back( aRight ); + } + else + { + const SwFrame *pPage = Imp()->GetFirstVisPage(&rRenderContext); + const SwTwips nBottom = rRect.Bottom(); + while ( pPage && !aRegion.empty() && + (pPage->getFrameArea().Top() <= nBottom) ) + { + SwRect aPageRect( pPage->getFrameArea() ); + if ( bBookMode ) + { + const SwPageFrame& rFormatPage = static_cast<const SwPageFrame*>(pPage)->GetFormatPage(); + aPageRect.SSize( rFormatPage.getFrameArea().SSize() ); + } + + const bool bSidebarRight = + static_cast<const SwPageFrame*>(pPage)->SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT; + aPageRect.Pos().AdjustX( -(bSidebarRight ? 0 : nSidebarWidth) ); + aPageRect.AddWidth(nSidebarWidth ); + + if ( aPageRect.IsOver( rRect ) ) + aRegion -= aPageRect; + + pPage = pPage->GetNext(); + } + } + if ( !aRegion.empty() ) + PaintDesktop_(aRegion); +} + +// PaintDesktop is split in two, this part is also used by PreviewPage +void SwViewShell::PaintDesktop_(const SwRegionRects &rRegion) +{ + // OD 2004-04-23 #116347# + GetOut()->Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + GetOut()->SetLineColor(); + + for ( auto &rRgn : rRegion ) + { + const tools::Rectangle aRectangle(rRgn.SVRect()); + + // #i93170# + // Here we have a real Problem. On the one hand we have the buffering for paint + // and overlay which needs an embracing pair of DLPrePaint2/DLPostPaint2 calls, + // on the other hand the MapMode is not set correctly when this code is executed. + // This is done in the users of this method, for each SWpage before painting it. + // Since the MapMode is not correct here, the call to DLPostPaint2 will paint + // existing FormControls due to the current MapMode. + + // There are basically three solutions for this: + + // (1) Set the MapMode correct, move the background painting to the users of + // this code + + // (2) Do no DLPrePaint2/DLPostPaint2 here; no SdrObjects are allowed to lie in + // the desktop region. Disadvantage: the desktop will not be part of the + // buffers, e.g. overlay. Thus, as soon as overlay will be used over the + // desktop, it will not work. + + // (3) expand DLPostPaint2 with a flag to signal if FormControl paints shall + // be done or not + + // Currently, (3) will be the best possible solution. It will keep overlay and + // buffering intact and work without MapMode for single pages. In the medium + // to long run, (1) will need to be used and the bool bPaintFormLayer needs + // to be removed again + + // #i68597# inform Drawinglayer about display change + DLPrePaint2(vcl::Region(aRectangle)); + + // #i75172# needed to move line/Fill color setters into loop since DLPrePaint2 + // may exchange GetOut(), that's it's purpose. This happens e.g. at print preview. + GetOut()->SetFillColor( SwViewOption::GetAppBackgroundColor()); + GetOut()->SetLineColor(); + GetOut()->DrawRect(aRectangle); + + DLPostPaint2(false); + } + + GetOut()->Pop(); +} + +bool SwViewShell::CheckInvalidForPaint( const SwRect &rRect ) +{ + if ( !GetWin() ) + return false; + + const SwPageFrame *pPage = Imp()->GetFirstVisPage(GetOut()); + const SwTwips nBottom = VisArea().Bottom(); + const SwTwips nRight = VisArea().Right(); + bool bRet = false; + while ( !bRet && pPage && !((pPage->getFrameArea().Top() > nBottom) || + (pPage->getFrameArea().Left() > nRight))) + { + if ( pPage->IsInvalid() || pPage->IsInvalidFly() ) + bRet = true; + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + + if ( bRet ) + { + //Unfortunately Start/EndAction won't help here, as the Paint originated + //from GUI and so Clipping has been set against getting through. + //Ergo: do it all yourself (see ImplEndAction()) + if ( Imp()->GetRegion() && Imp()->GetRegion()->GetOrigin() != VisArea()) + Imp()->DelRegion(); + + SwLayAction aAction( GetLayout(), Imp() ); + aAction.SetComplete( false ); + // We increment the action counter to avoid a recursive call of actions + // e.g. from a SwFEShell::RequestObjectResize(..) in bug 95829. + // A recursive call of actions is no good idea because the inner action + // can't format frames which are locked by the outer action. This may + // cause and endless loop. + ++mnStartAction; + aAction.Action(GetWin()); + --mnStartAction; + + SwRegionRects *pRegion = Imp()->GetRegion(); + if ( pRegion && aAction.IsBrowseActionStop() ) + { + //only of interest when something has changed in the visible range + bool bStop = true; + for ( size_t i = 0; i < pRegion->size(); ++i ) + { + const SwRect &rTmp = (*pRegion)[i]; + bStop = rTmp.IsOver( VisArea() ); + if ( !bStop ) + break; + } + if ( bStop ) + { + Imp()->DelRegion(); + pRegion = nullptr; + } + } + + if ( pRegion ) + { + //First Invert then Compress, never the other way round! + pRegion->Invert(); + pRegion->Compress(); + bRet = false; + if ( !pRegion->empty() ) + { + SwRegionRects aRegion( rRect ); + for ( size_t i = 0; i < pRegion->size(); ++i ) + { const SwRect &rTmp = (*pRegion)[i]; + if ( !rRect.IsInside( rTmp ) ) + { + InvalidateWindows( rTmp ); + if ( rTmp.IsOver( VisArea() ) ) + { aRegion -= rTmp; + bRet = true; + } + } + } + if ( bRet ) + { + for ( size_t i = 0; i < aRegion.size(); ++i ) + GetWin()->Invalidate( aRegion[i].SVRect() ); + + if ( rRect != VisArea() ) + { + //rRect == VisArea is the special case for new or + //Shift-Ctrl-R, when it shouldn't be necessary to + //hold the rRect again in Document coordinates. + if ( maInvalidRect.IsEmpty() ) + maInvalidRect = rRect; + else + maInvalidRect.Union( rRect ); + } + } + } + else + bRet = false; + Imp()->DelRegion(); + } + else + bRet = false; + } + return bRet; +} + +namespace +{ +/// Similar to comphelper::FlagRestorationGuard, but for vcl::RenderContext. +class RenderContextGuard +{ + std::unique_ptr<SdrPaintWindow> m_TemporaryPaintWindow; + SdrPageWindow* m_pPatchedPageWindow; + SdrPaintWindow* m_pPreviousPaintWindow = nullptr; + +public: + RenderContextGuard(VclPtr<vcl::RenderContext>& pRef, vcl::RenderContext* pValue, SwViewShell* pShell) + : m_pPatchedPageWindow(nullptr) + { + pRef = pValue; + + if (pValue != pShell->GetWin()) + { + SdrView* pDrawView(pShell->Imp()->GetDrawView()); + + if (nullptr != pDrawView) + { + SdrPageView* pSdrPageView(pDrawView->GetSdrPageView()); + + if (nullptr != pSdrPageView) + { + m_pPatchedPageWindow = pSdrPageView->FindPageWindow(*pShell->GetWin()); + + if (nullptr != m_pPatchedPageWindow) + { + m_TemporaryPaintWindow.reset(new SdrPaintWindow(*pDrawView, *pValue)); + m_pPreviousPaintWindow = m_pPatchedPageWindow->patchPaintWindow(*m_TemporaryPaintWindow); + } + } + } + } + } + + ~RenderContextGuard() + { + if(nullptr != m_pPatchedPageWindow) + { + m_pPatchedPageWindow->unpatchPaintWindow(m_pPreviousPaintWindow); + } + } +}; +} + +void SwViewShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect) +{ + RenderContextGuard aGuard(mpOut, &rRenderContext, this); + if ( mnLockPaint ) + { + if ( Imp()->m_bSmoothUpdate ) + { + SwRect aTmp( rRect ); + if ( !Imp()->m_aSmoothRect.IsInside( aTmp ) ) + Imp()->m_bStopSmooth = true; + else + { + Imp()->m_aSmoothRect = aTmp; + return; + } + } + else + return; + } + + if ( SwRootFrame::IsInPaint() ) + { + //During the publication of a page at printing the Paint is buffered. + SwPaintQueue::Add( this, SwRect( rRect ) ); + return; + } + + //With !nStartAction I try to protect me against erroneous code at other places. + //Hopefully it will not lead to problems!? + if ( mbPaintWorks && !mnStartAction ) + { + if( GetWin() && GetWin()->IsVisible() ) + { + SwRect aRect( rRect ); + if ( mbPaintInProgress ) //Guard against double Paints! + { + GetWin()->Invalidate( rRect ); + return; + } + + mbPaintInProgress = true; + SET_CURR_SHELL( this ); + SwRootFrame::SetNoVirDev( true ); + + //We don't want to Clip to and from, we trust that all are limited + //to the rectangle and only need to calculate the clipping once. + //The ClipRect is removed here once and not recovered, as externally + //no one needs it anymore anyway. + //Not when we paint a Metafile. + if( !GetOut()->GetConnectMetaFile() && GetOut()->IsClipRegion()) + GetOut()->SetClipRegion(); + + if ( IsPreview() ) + { + //When useful, process or destroy the old InvalidRect. + if ( aRect.IsInside( maInvalidRect ) ) + ResetInvalidRect(); + SwViewShell::mbLstAct = true; + GetLayout()->PaintSwFrame( rRenderContext, aRect ); + SwViewShell::mbLstAct = false; + } + else + { + //When one of the visible pages still has anything entered for + //Repaint, Repaint must be triggered. + if ( !CheckInvalidForPaint( aRect ) ) + { + // --> OD 2009-08-12 #i101192# + // start Pre/PostPaint encapsulation to avoid screen blinking + const vcl::Region aRepaintRegion(aRect.SVRect()); + DLPrePaint2(aRepaintRegion); + + // <-- + PaintDesktop(rRenderContext, aRect); + + //When useful, process or destroy the old InvalidRect. + if ( aRect.IsInside( maInvalidRect ) ) + ResetInvalidRect(); + SwViewShell::mbLstAct = true; + GetLayout()->PaintSwFrame( rRenderContext, aRect ); + SwViewShell::mbLstAct = false; + // --> OD 2009-08-12 #i101192# + // end Pre/PostPaint encapsulation + DLPostPaint2(true); + // <-- + } + } + SwRootFrame::SetNoVirDev( false ); + mbPaintInProgress = false; + UISizeNotify(); + } + } + else + { + if ( maInvalidRect.IsEmpty() ) + maInvalidRect = SwRect( rRect ); + else + maInvalidRect.Union( SwRect( rRect ) ); + + if ( mbInEndAction && GetWin() ) + { + const vcl::Region aRegion(GetWin()->GetPaintRegion()); + RectangleVector aRectangles; + aRegion.GetRegionRectangles(aRectangles); + + for(const auto& rRectangle : aRectangles) + { + Imp()->AddPaintRect(rRectangle); + } + + //RegionHandle hHdl( aRegion.BeginEnumRects() ); + //Rectangle aRect; + //while ( aRegion.GetEnumRects( hHdl, aRect ) ) + // Imp()->AddPaintRect( aRect ); + //aRegion.EndEnumRects( hHdl ); + } + else if ( SfxProgress::GetActiveProgress( GetDoc()->GetDocShell() ) && + GetOut() == GetWin() ) + { + // #i68597# + const vcl::Region aDLRegion(rRect); + DLPrePaint2(aDLRegion); + + rRenderContext.Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + rRenderContext.SetFillColor( Imp()->GetRetoucheColor() ); + rRenderContext.SetLineColor(); + rRenderContext.DrawRect( rRect ); + rRenderContext.Pop(); + // #i68597# + DLPostPaint2(true); + } + } +} + +void SwViewShell::PaintTile(VirtualDevice &rDevice, int contextWidth, int contextHeight, int tilePosX, int tilePosY, long tileWidth, long tileHeight) +{ + // SwViewShell's output device setup + // TODO clean up SwViewShell's approach to output devices (the many of + // them - mpBufferedOut, mpOut, mpWin, ...) + OutputDevice *pSaveOut = mpOut; + comphelper::LibreOfficeKit::setTiledPainting(true); + mpOut = &rDevice; + + // resizes the virtual device so to contain the entries context + rDevice.SetOutputSizePixel(Size(contextWidth, contextHeight)); + + // setup the output device to draw the tile + MapMode aMapMode(rDevice.GetMapMode()); + aMapMode.SetMapUnit(MapUnit::MapTwip); + aMapMode.SetOrigin(Point(-tilePosX, -tilePosY)); + + // Scaling. Must convert from pixels to twips. We know + // that VirtualDevices use a DPI of 96. + Fraction scaleX = Fraction(contextWidth, 96) * Fraction(1440) / Fraction(tileWidth); + Fraction scaleY = Fraction(contextHeight, 96) * Fraction(1440) / Fraction(tileHeight); + aMapMode.SetScaleX(scaleX); + aMapMode.SetScaleY(scaleY); + rDevice.SetMapMode(aMapMode); + + // Update scaling of SwEditWin and its sub-widgets, needed for comments. + sal_uInt16 nOldZoomValue = 0; + if (GetWin() && GetWin()->GetMapMode().GetScaleX() != scaleX) + { + double fScale = double(scaleX); + SwViewOption aOption(*GetViewOptions()); + nOldZoomValue = aOption.GetZoom(); + aOption.SetZoom(fScale * 100); + ApplyViewOptions(aOption); + // Make sure the map mode (disabled in SwXTextDocument::initializeForTiledRendering()) is still disabled. + GetWin()->EnableMapMode(false); + } + + tools::Rectangle aOutRect(Point(tilePosX, tilePosY), + rDevice.PixelToLogic(Size(contextWidth, contextHeight))); + + // Make the requested area visible -- we can't use MakeVisible as that will + // only scroll the contents, but won't zoom/resize if needed. + // Without this, items/text that are outside the visible area (in the SwView) + // won't be painted when rendering tiles (at least when using either the + // tiledrendering app, or the gtktiledviewer) -- although ultimately we + // probably want to fix things so that the SwView's area doesn't affect + // tiled rendering? + VisPortChgd(SwRect(aOutRect)); + + // Invoke SwLayAction if layout is not yet ready. + CheckInvalidForPaint(aOutRect); + + // draw - works in logic coordinates + Paint(rDevice, aOutRect); + + SwPostItMgr* pPostItMgr = GetPostItMgr(); + if (GetViewOptions()->IsPostIts() && pPostItMgr) + pPostItMgr->PaintTile(rDevice); + + // SwViewShell's output device tear down + + // A view shell can get a PaintTile call for a tile at a zoom level + // different from the one, the related client really is. + // In such a case it is better to reset the current scale value to + // the original one, since such a value should be in synchronous with + // the zoom level in the client (see setClientZoom). + // At present the zoom value returned by GetViewOptions()->GetZoom() is + // used in SwXTextDocument methods (postMouseEvent and setGraphicSelection) + // for passing the correct mouse position to an edited chart (if any). + if (nOldZoomValue !=0) + { + SwViewOption aOption(*GetViewOptions()); + aOption.SetZoom(nOldZoomValue); + ApplyViewOptions(aOption); + + // Changing the zoom value doesn't always trigger the updating of + // the client ole object area, so we call it directly. + SfxInPlaceClient* pIPClient = GetSfxViewShell()->GetIPClient(); + if (pIPClient) + { + pIPClient->VisAreaChanged(); + } + // Make sure the map mode (disabled in SwXTextDocument::initializeForTiledRendering()) is still disabled. + GetWin()->EnableMapMode(false); + } + + mpOut = pSaveOut; + comphelper::LibreOfficeKit::setTiledPainting(false); +} + +void SwViewShell::SetBrowseBorder( const Size& rNew ) +{ + if( rNew != maBrowseBorder ) + { + maBrowseBorder = rNew; + if ( maVisArea.HasArea() ) + InvalidateLayout( false ); + } +} + +const Size& SwViewShell::GetBrowseBorder() const +{ + return maBrowseBorder; +} + +sal_Int32 SwViewShell::GetBrowseWidth() const +{ + const SwPostItMgr* pPostItMgr = GetPostItMgr(); + if ( pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ) + { + Size aBorder( maBrowseBorder ); + aBorder.AdjustWidth(maBrowseBorder.Width() ); + aBorder.AdjustWidth(pPostItMgr->GetSidebarWidth(true) + pPostItMgr->GetSidebarBorderWidth(true) ); + return maVisArea.Width() - GetOut()->PixelToLogic(aBorder).Width(); + } + else + return maVisArea.Width() - 2 * GetOut()->PixelToLogic(maBrowseBorder).Width(); +} + +void SwViewShell::InvalidateLayout( bool bSizeChanged ) +{ + if ( !bSizeChanged && !GetViewOptions()->getBrowseMode() && + !GetViewOptions()->IsWhitespaceHidden() ) + return; + + SET_CURR_SHELL( this ); + + OSL_ENSURE( GetLayout(), "Layout not ready" ); + + // When the Layout doesn't have a height yet, nothing is formatted. + // That leads to problems with Invalidate, e.g. when setting up a new View + // the content is inserted and formatted (regardless of empty VisArea). + // Therefore the pages must be roused for formatting. + if( !GetLayout()->getFrameArea().Height() ) + { + SwFrame* pPage = GetLayout()->Lower(); + while( pPage ) + { + pPage->InvalidateSize_(); + pPage = pPage->GetNext(); + } + return; + } + + LockPaint(); + StartAction(); + + SwPageFrame *pPg = static_cast<SwPageFrame*>(GetLayout()->Lower()); + do + { pPg->InvalidateSize(); + pPg->InvalidatePrt_(); + pPg->InvaPercentLowers(); + if ( bSizeChanged ) + { + pPg->PrepareHeader(); + pPg->PrepareFooter(); + } + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } while ( pPg ); + + // When the size ratios in browse mode change, + // the Position and PrtArea of the Content and Tab frames must be Invalidated. + SwInvalidateFlags nInv = SwInvalidateFlags::PrtArea | SwInvalidateFlags::Table | SwInvalidateFlags::Pos; + // In case of layout or mode change, the ContentFrames need a size-Invalidate + // because of printer/screen formatting. + if ( bSizeChanged ) + nInv |= SwInvalidateFlags::Size | SwInvalidateFlags::Direction; + + GetLayout()->InvalidateAllContent( nInv ); + + SwFrame::CheckPageDescs( static_cast<SwPageFrame*>(GetLayout()->Lower()) ); + + EndAction(); + UnlockPaint(); +} + +SwRootFrame *SwViewShell::GetLayout() const +{ + return mpLayout.get(); +} + +vcl::RenderContext& SwViewShell::GetRefDev() const +{ + OutputDevice* pTmpOut = nullptr; + if ( GetWin() && + GetViewOptions()->getBrowseMode() && + !GetViewOptions()->IsPrtFormat() ) + pTmpOut = GetWin(); + else + pTmpOut = GetDoc()->getIDocumentDeviceAccess().getReferenceDevice( true ); + + return *pTmpOut; +} + +const SwNodes& SwViewShell::GetNodes() const +{ + return mxDoc->GetNodes(); +} + +void SwViewShell::DrawSelChanged() +{ +} + +Size SwViewShell::GetDocSize() const +{ + Size aSz; + const SwRootFrame* pRoot = GetLayout(); + if( pRoot ) + aSz = pRoot->getFrameArea().SSize(); + + return aSz; +} + +SfxItemPool& SwViewShell::GetAttrPool() +{ + return GetDoc()->GetAttrPool(); +} + +void SwViewShell::ApplyViewOptions( const SwViewOption &rOpt ) +{ + for(SwViewShell& rSh : GetRingContainer()) + rSh.StartAction(); + + ImplApplyViewOptions( rOpt ); + + // With one layout per view it is no longer necessary + // to sync these "layout related" view options + // But as long as we have to disable "multiple layout" + + for(SwViewShell& rSh : GetRingContainer()) + { + if(&rSh == this) + continue; + SwViewOption aOpt( *rSh.GetViewOptions() ); + aOpt.SetFieldName( rOpt.IsFieldName() ); + aOpt.SetShowHiddenField( rOpt.IsShowHiddenField() ); + aOpt.SetShowHiddenPara( rOpt.IsShowHiddenPara() ); + aOpt.SetShowHiddenChar( rOpt.IsShowHiddenChar() ); + aOpt.SetViewLayoutBookMode( rOpt.IsViewLayoutBookMode() ); + aOpt.SetHideWhitespaceMode(rOpt.IsHideWhitespaceMode()); + aOpt.SetViewLayoutColumns(rOpt.GetViewLayoutColumns()); + aOpt.SetPostIts(rOpt.IsPostIts()); + if ( !(aOpt == *rSh.GetViewOptions()) ) + rSh.ImplApplyViewOptions( aOpt ); + } + // End of disabled multiple window + + for(SwViewShell& rSh : GetRingContainer()) + rSh.EndAction(); +} + +void SwViewShell::ImplApplyViewOptions( const SwViewOption &rOpt ) +{ + if (*mpOpt == rOpt) + return; + + vcl::Window *pMyWin = GetWin(); + if( !pMyWin ) + { + OSL_ENSURE( pMyWin, "SwViewShell::ApplyViewOptions: no window" ); + return; + } + + SET_CURR_SHELL( this ); + + bool bReformat = false; + + if( mpOpt->IsShowHiddenField() != rOpt.IsShowHiddenField() ) + { + static_cast<SwHiddenTextFieldType*>(mxDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::HiddenText ))-> + SetHiddenFlag( !rOpt.IsShowHiddenField() ); + bReformat = true; + } + if ( mpOpt->IsShowHiddenPara() != rOpt.IsShowHiddenPara() ) + { + SwHiddenParaFieldType* pFieldType = static_cast<SwHiddenParaFieldType*>(GetDoc()-> + getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::HiddenPara)); + if( pFieldType && pFieldType->HasWriterListeners() ) + { + SwMsgPoolItem aHint( RES_HIDDENPARA_PRINT ); + pFieldType->ModifyNotification( &aHint, nullptr); + } + bReformat = true; + } + if ( !bReformat && mpOpt->IsShowHiddenChar() != rOpt.IsShowHiddenChar() ) + { + bReformat = GetDoc()->ContainsHiddenChars(); + } + + // bReformat becomes true, if ... + // - fieldnames apply or not ... + // ( - SwEndPortion must _no_ longer be generated. ) + // - Of course, the screen is something completely different than the printer ... + bReformat = bReformat || mpOpt->IsFieldName() != rOpt.IsFieldName(); + + // The map mode is changed, minima/maxima will be attended by UI + if( mpOpt->GetZoom() != rOpt.GetZoom() && !IsPreview() ) + { + MapMode aMode( pMyWin->GetMapMode() ); + Fraction aNewFactor( rOpt.GetZoom(), 100 ); + aMode.SetScaleX( aNewFactor ); + aMode.SetScaleY( aNewFactor ); + pMyWin->SetMapMode( aMode ); + // if not a reference device (printer) is used for formatting, + // but the screen, new formatting is needed for zoomfactor changes. + if (mpOpt->getBrowseMode() || mpOpt->IsWhitespaceHidden()) + bReformat = true; + } + + bool bBrowseModeChanged = false; + if( mpOpt->getBrowseMode() != rOpt.getBrowseMode() ) + { + bBrowseModeChanged = true; + bReformat = true; + } + else if( mpOpt->getBrowseMode() && mpOpt->IsPrtFormat() != rOpt.IsPrtFormat() ) + bReformat = true; + + bool bHideWhitespaceModeChanged = false; + if (mpOpt->IsWhitespaceHidden() != rOpt.IsWhitespaceHidden()) + { + // When whitespace is hidden, view change needs reformatting. + bHideWhitespaceModeChanged = true; + bReformat = true; + } + + if ( HasDrawView() || rOpt.IsGridVisible() ) + { + if ( !HasDrawView() ) + MakeDrawView(); + + SwDrawView *pDView = Imp()->GetDrawView(); + if ( pDView->IsDragStripes() != rOpt.IsCrossHair() ) + pDView->SetDragStripes( rOpt.IsCrossHair() ); + + if ( pDView->IsGridSnap() != rOpt.IsSnap() ) + pDView->SetGridSnap( rOpt.IsSnap() ); + + if ( pDView->IsGridVisible() != rOpt.IsGridVisible() ) + pDView->SetGridVisible( rOpt.IsGridVisible() ); + + const Size &rSz = rOpt.GetSnapSize(); + pDView->SetGridCoarse( rSz ); + + const Size aFSize + ( rSz.Width() ? rSz.Width() / (rOpt.GetDivisionX()+1) : 0, + rSz.Height()? rSz.Height()/ (rOpt.GetDivisionY()+1) : 0); + pDView->SetGridFine( aFSize ); + Fraction aSnGrWdtX(rSz.Width(), rOpt.GetDivisionX() + 1); + Fraction aSnGrWdtY(rSz.Height(), rOpt.GetDivisionY() + 1); + pDView->SetSnapGridWidth( aSnGrWdtX, aSnGrWdtY ); + + // set handle size to 9 pixels, always + pDView->SetMarkHdlSizePixel(9); + } + + bool bOnlineSpellChgd = mpOpt->IsOnlineSpell() != rOpt.IsOnlineSpell(); + + *mpOpt = rOpt; // First the options are taken. + mpOpt->SetUIOptions(rOpt); + + mxDoc->GetDocumentSettingManager().set(DocumentSettingId::HTML_MODE, 0 != ::GetHtmlMode(mxDoc->GetDocShell())); + + if( bBrowseModeChanged || bHideWhitespaceModeChanged ) + { + // #i44963# Good occasion to check if page sizes in + // page descriptions are still set to (LONG_MAX, LONG_MAX) (html import) + mxDoc->CheckDefaultPageFormat(); + InvalidateLayout( true ); + } + + pMyWin->Invalidate(); + if ( bReformat ) + { + // Nothing helps, we need to send all ContentFrames a + // Prepare, we format anew: + StartAction(); + Reformat(); + EndAction(); + } + + if( bOnlineSpellChgd ) + { + bool bOnlineSpl = rOpt.IsOnlineSpell(); + for(SwViewShell& rSh : GetRingContainer()) + { + if(&rSh == this) + continue; + rSh.mpOpt->SetOnlineSpell( bOnlineSpl ); + vcl::Window *pTmpWin = rSh.GetWin(); + if( pTmpWin ) + pTmpWin->Invalidate(); + } + } + +} + +void SwViewShell::SetUIOptions( const SwViewOption &rOpt ) +{ + mpOpt->SetUIOptions(rOpt); + //the API-Flag of the view options is set but never reset + //it is required to set scroll bars in readonly documents + if(rOpt.IsStarOneSetting()) + mpOpt->SetStarOneSetting(true); + + mpOpt->SetSymbolFont(rOpt.GetSymbolFont()); +} + +void SwViewShell::SetReadonlyOption(bool bSet) +{ + //JP 01.02.99: at readonly flag query properly + // and if need be format; Bug 61335 + + // Are we switching from readonly to edit? + if( bSet != mpOpt->IsReadonly() ) + { + // so that the flags can be queried properly. + mpOpt->SetReadonly( false ); + + bool bReformat = mpOpt->IsFieldName(); + + mpOpt->SetReadonly( bSet ); + + if( bReformat ) + { + StartAction(); + Reformat(); + if ( GetWin() ) + GetWin()->Invalidate(); + EndAction(); + } + else if ( GetWin() ) + GetWin()->Invalidate(); + if( Imp()->IsAccessible() ) + Imp()->InvalidateAccessibleEditableState( false ); + } +} + +void SwViewShell::SetPDFExportOption(bool bSet) +{ + if( bSet != mpOpt->IsPDFExport() ) + { + if( bSet && mpOpt->getBrowseMode() ) + mpOpt->SetPrtFormat( true ); + mpOpt->SetPDFExport(bSet); + } +} + +void SwViewShell::SetReadonlySelectionOption(bool bSet) +{ + if( bSet != mpOpt->IsSelectionInReadonly() ) + { + mpOpt->SetSelectionInReadonly(bSet); + } +} + +void SwViewShell::SetPrtFormatOption( bool bSet ) +{ + mpOpt->SetPrtFormat( bSet ); +} + +void SwViewShell::UISizeNotify() +{ + if ( mbDocSizeChgd ) + { + mbDocSizeChgd = false; + bool bOld = bInSizeNotify; + bInSizeNotify = true; + ::SizeNotify( this, GetDocSize() ); + bInSizeNotify = bOld; + } +} + +void SwViewShell::SetRestoreActions(sal_uInt16 nSet) +{ + OSL_ENSURE(!GetRestoreActions()||!nSet, "multiple restore of the Actions ?"); + Imp()->SetRestoreActions(nSet); +} +sal_uInt16 SwViewShell::GetRestoreActions() const +{ + return Imp()->GetRestoreActions(); +} + +bool SwViewShell::IsNewLayout() const +{ + return GetLayout()->IsNewLayout(); +} + +uno::Reference< css::accessibility::XAccessible > SwViewShell::CreateAccessible() +{ + uno::Reference< css::accessibility::XAccessible > xAcc; + + // We require a layout and an XModel to be accessible. + OSL_ENSURE( mpLayout, "no layout, no access" ); + OSL_ENSURE( GetWin(), "no window, no access" ); + + if( mxDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && GetWin() ) + xAcc = Imp()->GetAccessibleMap().GetDocumentView(); + + return xAcc; +} + +uno::Reference< css::accessibility::XAccessible > SwViewShell::CreateAccessiblePreview() +{ + OSL_ENSURE( IsPreview(), + "Can't create accessible preview for non-preview SwViewShell" ); + + // We require a layout and an XModel to be accessible. + OSL_ENSURE( mpLayout, "no layout, no access" ); + OSL_ENSURE( GetWin(), "no window, no access" ); + + if ( IsPreview() && GetLayout()&& GetWin() ) + { + return Imp()->GetAccessibleMap().GetDocumentPreview( + PagePreviewLayout()->maPreviewPages, + GetWin()->GetMapMode().GetScaleX(), + GetLayout()->GetPageByPageNum( PagePreviewLayout()->mnSelectedPageNum ), + PagePreviewLayout()->maWinSize ); + } + return nullptr; +} + +void SwViewShell::InvalidateAccessibleFocus() +{ + if( Imp() && Imp()->IsAccessible() ) + Imp()->GetAccessibleMap().InvalidateFocus(); +} + +/** + * invalidate CONTENT_FLOWS_FROM/_TO relation for paragraphs #i27138# + */ +void SwViewShell::InvalidateAccessibleParaFlowRelation( const SwTextFrame* _pFromTextFrame, + const SwTextFrame* _pToTextFrame ) +{ + if ( GetLayout() && GetLayout()->IsAnyShellAccessible() ) + { + Imp()->InvalidateAccessibleParaFlowRelation_( _pFromTextFrame, _pToTextFrame ); + } +} + +/** + * invalidate text selection for paragraphs #i27301# + */ +void SwViewShell::InvalidateAccessibleParaTextSelection() +{ + if ( GetLayout() && GetLayout()->IsAnyShellAccessible() ) + { + Imp()->InvalidateAccessibleParaTextSelection_(); + } +} + +/** + * invalidate attributes for paragraphs #i88069# + */ +void SwViewShell::InvalidateAccessibleParaAttrs( const SwTextFrame& rTextFrame ) +{ + if ( GetLayout() && GetLayout()->IsAnyShellAccessible() ) + { + Imp()->InvalidateAccessibleParaAttrs_( rTextFrame ); + } +} + +SwAccessibleMap* SwViewShell::GetAccessibleMap() +{ + if ( Imp()->IsAccessible() ) + { + return &(Imp()->GetAccessibleMap()); + } + + return nullptr; +} + +void SwViewShell::ApplyAccessibilityOptions(SvtAccessibilityOptions const & rAccessibilityOptions) +{ + if (utl::ConfigManager::IsFuzzing()) + return; + if (mpOpt->IsPagePreview() && !rAccessibilityOptions.GetIsForPagePreviews()) + { + mpAccOptions->SetAlwaysAutoColor(false); + mpAccOptions->SetStopAnimatedGraphics(false); + } + else + { + mpAccOptions->SetAlwaysAutoColor(rAccessibilityOptions.GetIsAutomaticFontColor()); + mpAccOptions->SetStopAnimatedGraphics(! rAccessibilityOptions.GetIsAllowAnimatedGraphics()); + + // Form view + // Always set this option, not only if document is read-only: + mpOpt->SetSelectionInReadonly(rAccessibilityOptions.IsSelectionInReadonly()); + } +} + +ShellResource* SwViewShell::GetShellRes() +{ + return mpShellRes; +} + +void SwViewShell::SetCareDialog(const std::shared_ptr<weld::Window>& rNew) +{ + (*mpCareDialog.get()) = rNew; +} + +sal_uInt16 SwViewShell::GetPageCount() const +{ + return GetLayout() ? GetLayout()->GetPageNum() : 1; +} + +Size SwViewShell::GetPageSize( sal_uInt16 nPageNum, bool bSkipEmptyPages ) const +{ + Size aSize; + const SwRootFrame* pTmpRoot = GetLayout(); + if( pTmpRoot && nPageNum ) + { + const SwPageFrame* pPage = static_cast<const SwPageFrame*> + (pTmpRoot->Lower()); + + while( --nPageNum && pPage->GetNext() ) + pPage = static_cast<const SwPageFrame*>( pPage->GetNext() ); + + if( !bSkipEmptyPages && pPage->IsEmptyPage() && pPage->GetNext() ) + pPage = static_cast<const SwPageFrame*>( pPage->GetNext() ); + + aSize = pPage->getFrameArea().SSize(); + } + return aSize; +} + +// #i12836# enhanced pdf export +sal_Int32 SwViewShell::GetPageNumAndSetOffsetForPDF( OutputDevice& rOut, const SwRect& rRect ) const +{ + OSL_ENSURE( GetLayout(), "GetPageNumAndSetOffsetForPDF assumes presence of layout" ); + + sal_Int32 nRet = -1; + + // #i40059# Position out of bounds: + SwRect aRect( rRect ); + aRect.Pos().setX( std::max( aRect.Left(), GetLayout()->getFrameArea().Left() ) ); + + const SwPageFrame* pPage = GetLayout()->GetPageAtPos( aRect.Center() ); + if ( pPage ) + { + OSL_ENSURE( pPage, "GetPageNumAndSetOffsetForPDF: No page found" ); + + Point aOffset( pPage->getFrameArea().Pos() ); + aOffset.setX( -aOffset.X() ); + aOffset.setY( -aOffset.Y() ); + + MapMode aMapMode( rOut.GetMapMode() ); + aMapMode.SetOrigin( aOffset ); + rOut.SetMapMode( aMapMode ); + + nRet = pPage->GetPhyPageNum() - 1; + } + + return nRet; +} + +// --> PB 2007-05-30 #146850# +const BitmapEx& SwViewShell::GetReplacementBitmap( bool bIsErrorState ) +{ + if (bIsErrorState) + { + if (!m_xErrorBmp) + m_xErrorBmp.reset(new BitmapEx(RID_GRAPHIC_ERRORBMP)); + return *m_xErrorBmp; + } + + if (!m_xReplaceBmp) + m_xReplaceBmp.reset(new BitmapEx(RID_GRAPHIC_REPLACEBMP)); + return *m_xReplaceBmp; +} + +void SwViewShell::DeleteReplacementBitmaps() +{ + m_xErrorBmp.reset(); + m_xReplaceBmp.reset(); +} + +SwPostItMgr* SwViewShell::GetPostItMgr() +{ + SwView* pView = GetDoc()->GetDocShell() ? GetDoc()->GetDocShell()->GetView() : nullptr; + if ( pView ) + return pView->GetPostItMgr(); + + return nullptr; +} + +/* + * Document Interface Access + */ +const IDocumentSettingAccess& SwViewShell::getIDocumentSettingAccess() const { return mxDoc->GetDocumentSettingManager(); } +IDocumentSettingAccess& SwViewShell::getIDocumentSettingAccess() { return mxDoc->GetDocumentSettingManager(); } +const IDocumentDeviceAccess& SwViewShell::getIDocumentDeviceAccess() const { return mxDoc->getIDocumentDeviceAccess(); } +IDocumentDeviceAccess& SwViewShell::getIDocumentDeviceAccess() { return mxDoc->getIDocumentDeviceAccess(); } +const IDocumentMarkAccess* SwViewShell::getIDocumentMarkAccess() const { return mxDoc->getIDocumentMarkAccess(); } +IDocumentMarkAccess* SwViewShell::getIDocumentMarkAccess() { return mxDoc->getIDocumentMarkAccess(); } +const IDocumentDrawModelAccess& SwViewShell::getIDocumentDrawModelAccess() const { return mxDoc->getIDocumentDrawModelAccess(); } +IDocumentDrawModelAccess& SwViewShell::getIDocumentDrawModelAccess() { return mxDoc->getIDocumentDrawModelAccess(); } +const IDocumentRedlineAccess& SwViewShell::getIDocumentRedlineAccess() const { return mxDoc->getIDocumentRedlineAccess(); } +IDocumentRedlineAccess& SwViewShell::getIDocumentRedlineAccess() { return mxDoc->getIDocumentRedlineAccess(); } +const IDocumentLayoutAccess& SwViewShell::getIDocumentLayoutAccess() const { return mxDoc->getIDocumentLayoutAccess(); } +IDocumentLayoutAccess& SwViewShell::getIDocumentLayoutAccess() { return mxDoc->getIDocumentLayoutAccess(); } +IDocumentContentOperations& SwViewShell::getIDocumentContentOperations() { return mxDoc->getIDocumentContentOperations(); } +IDocumentStylePoolAccess& SwViewShell::getIDocumentStylePoolAccess() { return mxDoc->getIDocumentStylePoolAccess(); } +const IDocumentStatistics& SwViewShell::getIDocumentStatistics() const { return mxDoc->getIDocumentStatistics(); } + +IDocumentUndoRedo & SwViewShell::GetIDocumentUndoRedo() +{ return mxDoc->GetIDocumentUndoRedo(); } +IDocumentUndoRedo const& SwViewShell::GetIDocumentUndoRedo() const +{ return mxDoc->GetIDocumentUndoRedo(); } + +// --> OD 2007-11-14 #i83479# +const IDocumentListItems* SwViewShell::getIDocumentListItemsAccess() const +{ + return &mxDoc->getIDocumentListItems(); +} + +const IDocumentOutlineNodes* SwViewShell::getIDocumentOutlineNodesAccess() const +{ + return &mxDoc->getIDocumentOutlineNodes(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/vnew.cxx b/sw/source/core/view/vnew.cxx new file mode 100644 index 000000000..9c55d1265 --- /dev/null +++ b/sw/source/core/view/vnew.cxx @@ -0,0 +1,387 @@ +/* -*- 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 <sfx2/printer.hxx> +#include <sal/log.hxx> +#include <doc.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <docsh.hxx> +#include <viewsh.hxx> +#include <rootfrm.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <fntcache.hxx> +#include <docufld.hxx> +#include <ptqueue.hxx> +#include <dview.hxx> +#include <ndgrf.hxx> +#include <ndindex.hxx> +#include <accessibilityoptions.hxx> + +void SwViewShell::Init( const SwViewOption *pNewOpt ) +{ + mbDocSizeChgd = false; + + // We play it safe: Remove old font information whenever the printer + // resolution or the zoom factor changes. For that, Init() and Reformat() + // are the most secure places. + pFntCache->Flush( ); + + // ViewOptions are created dynamically + + if( !mpOpt ) + { + mpOpt.reset(new SwViewOption); + + // ApplyViewOptions() does not need to be called + if( pNewOpt ) + { + *mpOpt = *pNewOpt; + // Zoom factor needs to be set because there is no call to ApplyViewOptions() during + // CTOR for performance reasons. + if( GetWin() && 100 != mpOpt->GetZoom() ) + { + MapMode aMode( mpWin->GetMapMode() ); + const Fraction aNewFactor( mpOpt->GetZoom(), 100 ); + aMode.SetScaleX( aNewFactor ); + aMode.SetScaleY( aNewFactor ); + mpWin->SetMapMode( aMode ); + } + } + } + + SwDocShell* pDShell = mxDoc->GetDocShell(); + mxDoc->GetDocumentSettingManager().set(DocumentSettingId::HTML_MODE, 0 != ::GetHtmlMode( pDShell ) ); + // set readonly flag at ViewOptions before creating layout. Otherwise, + // one would have to reformat again. + + if( pDShell && pDShell->IsReadOnly() ) + mpOpt->SetReadonly( true ); + + SAL_INFO( "sw.core", "View::Init - before InitPrt" ); + OutputDevice* pPDFOut = nullptr; + + if (mpOut && (OUTDEV_PDF == mpOut->GetOutDevType())) + pPDFOut = mpOut; + + // Only setup the printer if we need one: + const bool bBrowseMode = mpOpt->getBrowseMode(); + if( pPDFOut ) + InitPrt( pPDFOut ); + + // i#44963 Good occasion to check if page sizes in + // page descriptions are still set to (LONG_MAX, LONG_MAX) (html import) + if ( !bBrowseMode ) + { + mxDoc->CheckDefaultPageFormat(); + } + + SAL_INFO( "sw.core", "View::Init - after InitPrt" ); + if( GetWin() ) + { + SwViewOption::Init( GetWin() ); + GetWin()->SetFillColor(); + GetWin()->SetBackground(); + GetWin()->SetLineColor(); + } + + // Create a new layout, if there is no one available + if( !mpLayout ) + { + // Here's the code which disables the usage of "multiple" layouts at the moment + // If the problems with controls and groups objects are solved, + // this code can be removed... + SwViewShell *pCurrShell = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pCurrShell ) + mpLayout = pCurrShell->mpLayout; + // end of "disable multiple layouts" + if( !mpLayout ) + { + // switched to two step construction because creating the layout in SwRootFrame needs a valid pLayout set + mpLayout = SwRootFramePtr(new SwRootFrame(mxDoc->GetDfltFrameFormat(), this), + &SwFrame::DestroyFrame); + mpLayout->Init( mxDoc->GetDfltFrameFormat() ); + } + } + SizeChgNotify(); + + // XForms mode: initialize XForms mode, based on design mode (draw view) + // MakeDrawView() requires layout + if( GetDoc()->isXForms() ) + { + if( ! HasDrawView() ) + MakeDrawView(); + mpOpt->SetFormView( ! GetDrawView()->IsDesignMode() ); + } +} + +/// CTor for the first Shell. +SwViewShell::SwViewShell( SwDoc& rDocument, vcl::Window *pWindow, + const SwViewOption *pNewOpt, OutputDevice *pOutput, + long nFlags ) + : + maBrowseBorder(), + mpSfxViewShell( nullptr ), + mpImp( new SwViewShellImp( this ) ), + mpWin( pWindow ), + mpOut( pOutput ? pOutput + : pWindow ? static_cast<OutputDevice*>(pWindow) + : static_cast<OutputDevice*>(rDocument.getIDocumentDeviceAccess().getPrinter( true ))), + mpAccOptions( new SwAccessibilityOptions ), + mbShowHeaderSeparator( false ), + mbShowFooterSeparator( false ), + mbHeaderFooterEdit( false ), + mpTargetPaintWindow(nullptr), + mpBufferedOut(nullptr), + mxDoc( &rDocument ), + mnStartAction( 0 ), + mnLockPaint( 0 ), + mbSelectAll(false), + mbOutputToWindow(false), + mpPrePostOutDev(nullptr), + maPrePostMapMode() +{ + // in order to suppress event handling in + // <SwDrawContact::Changed> during construction of <SwViewShell> instance + mbInConstructor = true; + + mbPaintInProgress = mbViewLocked = mbInEndAction = mbFrameView = + mbEndActionByVirDev = false; + mbPaintWorks = mbEnableSmooth = true; + mbPreview = 0 !=( VSHELLFLAG_ISPREVIEW & nFlags ); + + // i#38810 Do not reset modified state of document, + // if it's already been modified. + const bool bIsDocModified( mxDoc->getIDocumentState().IsModified() ); + OutputDevice* pOrigOut = mpOut; + Init( pNewOpt ); // may change the Outdev (InitPrt()) + mpOut = pOrigOut; + + // initialize print preview layout after layout + // is created in <SwViewShell::Init(..)> - called above. + if ( mbPreview ) + { + // init page preview layout + mpImp->InitPagePreviewLayout(); + } + + SET_CURR_SHELL( this ); + + static_cast<SwHiddenTextFieldType*>(mxDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::HiddenText ))-> + SetHiddenFlag( !mpOpt->IsShowHiddenField() ); + + // In Init a standard FrameFormat is created. + if ( !mxDoc->GetIDocumentUndoRedo().IsUndoNoResetModified() + && !bIsDocModified ) + { + mxDoc->getIDocumentState().ResetModified(); + } + + // extend format cache. + if ( SwTextFrame::GetTextCache()->GetCurMax() < 2550 ) + SwTextFrame::GetTextCache()->IncreaseMax( 100 ); + if( mpOpt->IsGridVisible() || getIDocumentDrawModelAccess().GetDrawModel() ) + Imp()->MakeDrawView(); + + mbInConstructor = false; +} + +/// CTor for further Shells on a document. +SwViewShell::SwViewShell( SwViewShell& rShell, vcl::Window *pWindow, + OutputDevice * pOutput, long const nFlags) + : Ring( &rShell ) , + maBrowseBorder( rShell.maBrowseBorder ), + mpSfxViewShell( nullptr ), + mpImp( new SwViewShellImp( this ) ), + mpWin( pWindow ), + mpOut( pOutput ? pOutput + : pWindow ? static_cast<OutputDevice*>(pWindow) + : static_cast<OutputDevice*>(rShell.GetDoc()->getIDocumentDeviceAccess().getPrinter( true ))), + mpAccOptions( new SwAccessibilityOptions ), + mbShowHeaderSeparator( false ), + mbShowFooterSeparator( false ), + mbHeaderFooterEdit( false ), + mpTargetPaintWindow(nullptr), + mpBufferedOut(nullptr), + mxDoc( rShell.GetDoc() ), + mnStartAction( 0 ), + mnLockPaint( 0 ), + mbSelectAll(false), + mbOutputToWindow(false), + mpPrePostOutDev(nullptr), + maPrePostMapMode() +{ + // in order to suppress event handling in + // <SwDrawContact::Changed> during construction of <SwViewShell> instance + mbInConstructor = true; + + mbPaintWorks = mbEnableSmooth = true; + mbPaintInProgress = mbViewLocked = mbInEndAction = mbFrameView = + mbEndActionByVirDev = false; + mbPreview = 0 !=( VSHELLFLAG_ISPREVIEW & nFlags ); + + if( nFlags & VSHELLFLAG_SHARELAYOUT ) + mpLayout = rShell.mpLayout; + + SET_CURR_SHELL( this ); + + bool bModified = mxDoc->getIDocumentState().IsModified(); + + OutputDevice* pOrigOut = mpOut; + Init( rShell.GetViewOptions() ); // might change Outdev (InitPrt()) + mpOut = pOrigOut; + + if ( mbPreview ) + mpImp->InitPagePreviewLayout(); + + static_cast<SwHiddenTextFieldType*>(mxDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::HiddenText ))-> + SetHiddenFlag( !mpOpt->IsShowHiddenField() ); + + // In Init a standard FrameFormat is created. + if( !bModified && !mxDoc->GetIDocumentUndoRedo().IsUndoNoResetModified() ) + { + mxDoc->getIDocumentState().ResetModified(); + } + + // extend format cache. + if ( SwTextFrame::GetTextCache()->GetCurMax() < 2550 ) + SwTextFrame::GetTextCache()->IncreaseMax( 100 ); + if( mpOpt->IsGridVisible() || getIDocumentDrawModelAccess().GetDrawModel() ) + Imp()->MakeDrawView(); + + mbInConstructor = false; + +} + +SwViewShell::~SwViewShell() +{ + IDocumentLayoutAccess* const pLayoutAccess + = mxDoc ? &mxDoc->getIDocumentLayoutAccess() : nullptr; + + { + SET_CURR_SHELL( this ); + mbPaintWorks = false; + + // i#9684 Stopping the animated graphics is not + // necessary during printing or pdf export, because the animation + // has not been started in this case. + if( mxDoc && GetWin() ) + { + SwNodes& rNds = mxDoc->GetNodes(); + + SwStartNode *pStNd; + SwNodeIndex aIdx( *rNds.GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwGrfNode *pGNd = aIdx.GetNode().GetGrfNode(); + if ( nullptr != pGNd ) + { + if( pGNd->IsAnimated() ) + { + SwIterator<SwFrame,SwGrfNode> aIter( *pGNd ); + for( SwFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + OSL_ENSURE( pFrame->IsNoTextFrame(), "GraphicNode with Text?" ); + static_cast<SwNoTextFrame*>(pFrame)->StopAnimation( mpOut ); + } + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), +1 ); + } + + GetDoc()->StopNumRuleAnimations( mpOut ); + } + + mpImp.reset(); + + if (mxDoc) + { + if( mxDoc->getReferenceCount() > 1 ) + GetLayout()->ResetNewLayout(); + } + + mpOpt.reset(); + + // resize format cache. + if ( SwTextFrame::GetTextCache()->GetCurMax() > 250 ) + SwTextFrame::GetTextCache()->DecreaseMax( 100 ); + + // Remove from PaintQueue if necessary + SwPaintQueue::Remove( this ); + + OSL_ENSURE( !mnStartAction, "EndAction() pending." ); + } + + if ( pLayoutAccess ) + { + GetLayout()->DeRegisterShell( this ); + if(pLayoutAccess->GetCurrentViewShell()==this) + { + pLayoutAccess->SetCurrentViewShell(nullptr); + for(SwViewShell& rShell : GetRingContainer()) + { + if(&rShell != this) + { + pLayoutAccess->SetCurrentViewShell(&rShell); + break; + } + } + } + } + + mpAccOptions.reset(); +} + +bool SwViewShell::HasDrawView() const +{ + return Imp() && Imp()->HasDrawView(); +} + +void SwViewShell::MakeDrawView() +{ + Imp()->MakeDrawView( ); +} + +bool SwViewShell::HasDrawViewDrag() const +{ + return Imp()->HasDrawView() && Imp()->GetDrawView()->IsDragObj(); +} + +SdrView* SwViewShell::GetDrawView() +{ + return Imp()->GetDrawView(); +} + +SdrView* SwViewShell::GetDrawViewWithValidMarkList() +{ + SwDrawView* pDView = Imp()->GetDrawView(); + pDView->ValidateMarkList(); + return pDView; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/vprint.cxx b/sw/source/core/view/vprint.cxx new file mode 100644 index 000000000..5dbf21b71 --- /dev/null +++ b/sw/source/core/view/vprint.cxx @@ -0,0 +1,688 @@ +/* -*- 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 <hintids.hxx> +#include <sfx2/printer.hxx> +#include <svx/svdview.hxx> + +#include <txtfld.hxx> +#include <fmtfld.hxx> +#include <fmtfsize.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <fesh.hxx> +#include <pam.hxx> +#include <viewimp.hxx> +#include <layact.hxx> +#include <ndtxt.hxx> +#include <viewopt.hxx> +#include <printdata.hxx> +#include <pagedesc.hxx> +#include <ptqueue.hxx> +#include <viscrs.hxx> +#include <fmtpdsc.hxx> +#include <PostItMgr.hxx> +#include "vprint.hxx" + +using namespace ::com::sun::star; + +/// Painting buffer +class SwQueuedPaint +{ +public: + SwQueuedPaint *pNext; + SwViewShell *pSh; + SwRect aRect; + + SwQueuedPaint( SwViewShell *pNew, const SwRect &rRect ) : + pNext( nullptr ), + pSh( pNew ), + aRect( rRect ) + {} +}; + +SwQueuedPaint *SwPaintQueue::s_pPaintQueue = nullptr; + +namespace { + +// saves some settings from the draw view +class SwDrawViewSave +{ + SdrView* pDV; + bool bPrintControls; +public: + explicit SwDrawViewSave( SdrView* pSdrView ); + ~SwDrawViewSave(); +}; + +} + +void SwPaintQueue::Add( SwViewShell *pNew, const SwRect &rNew ) +{ + SwQueuedPaint *pPt; + if (nullptr != (pPt = s_pPaintQueue)) + { + while ( pPt->pSh != pNew && pPt->pNext ) + pPt = pPt->pNext; + if ( pPt->pSh == pNew ) + { + pPt->aRect.Union( rNew ); + return; + } + } + SwQueuedPaint *pNQ = new SwQueuedPaint( pNew, rNew ); + if ( pPt ) + pPt->pNext = pNQ; + else + s_pPaintQueue = pNQ; +} + +void SwPaintQueue::Repaint() +{ + if (!SwRootFrame::IsInPaint() && s_pPaintQueue) + { + SwQueuedPaint *pPt = s_pPaintQueue; + do + { SwViewShell *pSh = pPt->pSh; + SET_CURR_SHELL( pSh ); + if ( pSh->IsPreview() ) + { + if ( pSh->GetWin() ) + { + // for previewing, since rows/columns are known in PaintHdl (UI) + pSh->GetWin()->Invalidate(); + } + } + else + pSh->Paint(*pSh->GetOut(), pPt->aRect.SVRect()); + pPt = pPt->pNext; + } while ( pPt ); + + do + { + pPt = s_pPaintQueue; + s_pPaintQueue = s_pPaintQueue->pNext; + delete pPt; + } while (s_pPaintQueue); + } +} + +void SwPaintQueue::Remove( SwViewShell const *pSh ) +{ + SwQueuedPaint *pPt; + if (nullptr != (pPt = s_pPaintQueue)) + { + SwQueuedPaint *pPrev = nullptr; + while ( pPt && pPt->pSh != pSh ) + { + pPrev = pPt; + pPt = pPt->pNext; + } + if ( pPt ) + { + if ( pPrev ) + pPrev->pNext = pPt->pNext; + else if (pPt == s_pPaintQueue) + s_pPaintQueue = nullptr; + delete pPt; + } + } +} + +void SetSwVisArea( SwViewShell *pSh, const SwRect &rRect ) +{ + OSL_ENSURE( !pSh->GetWin(), "Print with window?" ); + pSh->maVisArea = rRect; + pSh->Imp()->SetFirstVisPageInvalid(); + Point aPt( rRect.Pos() ); + + // calculate an offset for the rectangle of the n-th page to + // move the start point of the output operation to a position + // such that in the output device all pages will be painted + // at the same position + aPt.setX( -aPt.X() ); aPt.setY( -aPt.Y() ); + + vcl::RenderContext *pOut = pSh->GetOut(); + + MapMode aMapMode( pOut->GetMapMode() ); + aMapMode.SetOrigin( aPt ); + pOut->SetMapMode( aMapMode ); +} + +void SwViewShell::InitPrt( OutputDevice *pOutDev ) +{ + // For printing we use a negative offset (exactly the offset of OutputSize). + // This is necessary because the origin is in the upper left corner of the + // physical page while the output uses OutputOffset as origin. + if ( pOutDev ) + { + maPrtOffset = Point(); + + maPrtOffset += pOutDev->GetMapMode().GetOrigin(); + MapMode aMapMode( pOutDev->GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + pOutDev->SetMapMode( aMapMode ); + pOutDev->SetLineColor(); + pOutDev->SetFillColor(); + } + else + { + maPrtOffset.setX(0); + maPrtOffset.setY(0); + } + + if ( !mpWin ) + mpOut = pOutDev; +} + +void SwViewShell::ChgAllPageOrientation( Orientation eOri ) +{ + OSL_ENSURE( mnStartAction, "missing an Action" ); + SET_CURR_SHELL( this ); + + const size_t nAll = GetDoc()->GetPageDescCnt(); + bool bNewOri = eOri != Orientation::Portrait; + + for( size_t i = 0; i < nAll; ++ i ) + { + const SwPageDesc& rOld = GetDoc()->GetPageDesc( i ); + + if( rOld.GetLandscape() != bNewOri ) + { + SwPageDesc aNew( rOld ); + { + ::sw::UndoGuard const ug(GetDoc()->GetIDocumentUndoRedo()); + GetDoc()->CopyPageDesc(rOld, aNew); + } + aNew.SetLandscape( bNewOri ); + SwFrameFormat& rFormat = aNew.GetMaster(); + SwFormatFrameSize aSz( rFormat.GetFrameSize() ); + // adjust size + // PORTRAIT -> higher than wide + // LANDSCAPE -> wider than high + // Height is the VarSize, width the FixSize (per Def.) + if( bNewOri ? aSz.GetHeight() > aSz.GetWidth() + : aSz.GetHeight() < aSz.GetWidth() ) + { + SwTwips aTmp = aSz.GetHeight(); + aSz.SetHeight( aSz.GetWidth() ); + aSz.SetWidth( aTmp ); + rFormat.SetFormatAttr( aSz ); + } + GetDoc()->ChgPageDesc( i, aNew ); + } + } +} + +void SwViewShell::ChgAllPageSize( Size const &rSz ) +{ + OSL_ENSURE( mnStartAction, "missing an Action" ); + SET_CURR_SHELL( this ); + + SwDoc* pMyDoc = GetDoc(); + const size_t nAll = pMyDoc->GetPageDescCnt(); + + for( size_t i = 0; i < nAll; ++i ) + { + const SwPageDesc &rOld = pMyDoc->GetPageDesc( i ); + SwPageDesc aNew( rOld ); + { + ::sw::UndoGuard const ug(GetDoc()->GetIDocumentUndoRedo()); + GetDoc()->CopyPageDesc( rOld, aNew ); + } + SwFrameFormat& rPgFormat = aNew.GetMaster(); + Size aSz( rSz ); + const bool bOri = aNew.GetLandscape(); + if( bOri ? aSz.Height() > aSz.Width() + : aSz.Height() < aSz.Width() ) + { + SwTwips aTmp = aSz.Height(); + aSz.setHeight( aSz.Width() ); + aSz.setWidth( aTmp ); + } + + SwFormatFrameSize aFrameSz( rPgFormat.GetFrameSize() ); + aFrameSz.SetSize( aSz ); + rPgFormat.SetFormatAttr( aFrameSz ); + pMyDoc->ChgPageDesc( i, aNew ); + } +} + +void SwViewShell::CalcPagesForPrint( sal_uInt16 nMax ) +{ + SET_CURR_SHELL( this ); + + SwRootFrame* pMyLayout = GetLayout(); + + const SwFrame *pPage = pMyLayout->Lower(); + SwLayAction aAction( pMyLayout, Imp() ); + + pMyLayout->StartAllAction(); + for ( sal_uInt16 i = 1; pPage && i <= nMax; pPage = pPage->GetNext(), ++i ) + { + pPage->Calc(GetOut()); + SwRect aOldVis( VisArea() ); + maVisArea = pPage->getFrameArea(); + Imp()->SetFirstVisPageInvalid(); + aAction.Reset(); + aAction.SetPaint( false ); + aAction.SetWaitAllowed( false ); + aAction.SetReschedule( true ); + + aAction.Action(GetOut()); + + maVisArea = aOldVis; //reset due to the paints + Imp()->SetFirstVisPageInvalid(); + } + + pMyLayout->EndAllAction(); +} + +void SwViewShell::FillPrtDoc( SwDoc *pPrtDoc, const SfxPrinter* pPrt) +{ + OSL_ENSURE( dynamic_cast<const SwFEShell*>( this) != nullptr,"SwViewShell::Prt for FEShell only"); + SwFEShell* pFESh = static_cast<SwFEShell*>(this); + pPrtDoc->getIDocumentFieldsAccess().LockExpFields(); + + // use given printer + //! Make a copy of it since it gets destroyed with the temporary document + //! used for PDF export + if (pPrt) + pPrtDoc->getIDocumentDeviceAccess().setPrinter( VclPtr<SfxPrinter>::Create(*pPrt), true, true ); + + const SfxItemPool& rPool = GetAttrPool(); + for( sal_uInt16 nWh = POOLATTR_BEGIN; nWh < POOLATTR_END; ++nWh ) + { + const SfxPoolItem* pCpyItem = rPool.GetPoolDefaultItem( nWh ); + if( nullptr != pCpyItem ) + pPrtDoc->GetAttrPool().SetPoolDefaultItem( *pCpyItem ); + } + + // JP 29.07.99 - Bug 67951 - set all Styles from the SourceDoc into + // the PrintDoc - will be replaced! + pPrtDoc->ReplaceStyles( *GetDoc() ); + + SwShellCursor *pActCursor = pFESh->GetCursor_(); + SwShellCursor *pFirstCursor = pActCursor->GetNext(); + if( !pActCursor->HasMark() ) // with a multi-selection the current cursor might be empty + { + pActCursor = pActCursor->GetPrev(); + } + + // Y-position of the first selection + Point aSelPoint; + if( pFESh->IsTableMode() ) + { + SwShellTableCursor* pShellTableCursor = pFESh->GetTableCursor(); + + const SwContentNode *const pContentNode = + pShellTableCursor->Start()->nNode.GetNode().GetContentNode(); + const SwContentFrame *const pContentFrame = pContentNode ? pContentNode->getLayoutFrame(GetLayout(), pShellTableCursor->Start()) : nullptr; + if( pContentFrame ) + { + SwRect aCharRect; + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + pContentFrame->GetCharRect( aCharRect, *pShellTableCursor->Start(), &aTmpState ); + aSelPoint = Point( aCharRect.Left(), aCharRect.Top() ); + } + } + else if (pFirstCursor) + { + aSelPoint = pFirstCursor->GetSttPos(); + } + + const SwPageFrame* pPage = GetLayout()->GetPageAtPos( aSelPoint ); + OSL_ENSURE( pPage, "no page found!" ); + + // get page descriptor - fall back to the first one if pPage could not be found + const SwPageDesc* pPageDesc = pPage ? pPrtDoc->FindPageDesc( + pPage->GetPageDesc()->GetName() ) : &pPrtDoc->GetPageDesc( 0 ); + + if( !pFESh->IsTableMode() && pActCursor && pActCursor->HasMark() ) + { // Tweak paragraph attributes of last paragraph + SwNodeIndex aNodeIdx( *pPrtDoc->GetNodes().GetEndOfContent().StartOfSectionNode() ); + SwTextNode* pTextNd = pPrtDoc->GetNodes().GoNext( &aNodeIdx )->GetTextNode(); + SwContentNode *pLastNd = + pActCursor->GetContentNode( (*pActCursor->GetMark()) <= (*pActCursor->GetPoint()) ); + // copy the paragraph attributes of the first paragraph + if( pLastNd && pLastNd->IsTextNode() ) + static_cast<SwTextNode*>(pLastNd)->CopyCollFormat( *pTextNd ); + } + + // fill it with the selected content + pFESh->Copy( pPrtDoc ); + + // set the page style at the first paragraph + { + SwNodeIndex aNodeIdx( *pPrtDoc->GetNodes().GetEndOfContent().StartOfSectionNode() ); + SwContentNode* pCNd = pPrtDoc->GetNodes().GoNext( &aNodeIdx ); // go to 1st ContentNode + if( pFESh->IsTableMode() ) + { + SwTableNode* pTNd = pCNd->FindTableNode(); + if( pTNd ) + pTNd->GetTable().GetFrameFormat()->SetFormatAttr( SwFormatPageDesc( pPageDesc ) ); + } + else + { + pCNd->SetAttr( SwFormatPageDesc( pPageDesc ) ); + if( pFirstCursor && pFirstCursor->HasMark() ) + { + SwTextNode *pTextNd = pCNd->GetTextNode(); + if( pTextNd ) + { + SwContentNode *pFirstNd = + pFirstCursor->GetContentNode( (*pFirstCursor->GetMark()) > (*pFirstCursor->GetPoint()) ); + // copy paragraph attributes of the first paragraph + if( pFirstNd && pFirstNd->IsTextNode() ) + static_cast<SwTextNode*>(pFirstNd)->CopyCollFormat( *pTextNd ); + } + } + } + } +} + +// TODO: there is already a GetPageByPageNum, but it checks some physical page +// number; unsure if we want that here, should find out what that is... +SwPageFrame const* +sw_getPage(SwRootFrame const& rLayout, sal_Int32 const nPage) +{ + // yes this is O(n^2) but at least it does not crash... + SwPageFrame const* pPage = dynamic_cast<const SwPageFrame*>(rLayout.Lower()); + for (sal_Int32 i = nPage; pPage && (i > 0); --i) + { + if (1 == i) { // note: nPage is 1-based, i.e. 0 is invalid! + return pPage; + } + pPage = dynamic_cast<SwPageFrame const*>(pPage->GetNext()); + } + OSL_ENSURE(pPage, "ERROR: SwPageFrame expected"); + OSL_FAIL("non-existent page requested"); + return nullptr; +} + +bool SwViewShell::PrintOrPDFExport( + OutputDevice *pOutDev, + SwPrintData const& rPrintData, + sal_Int32 nRenderer, /* the index in the vector of pages to be printed */ + bool bIsPDFExport ) +{ + // CAUTION: Do also always update the printing routines in viewpg.cxx (PrintProspect)! + + const sal_Int32 nMaxRenderer = rPrintData.GetRenderData().GetPagesToPrint().size() - 1; + OSL_ENSURE( 0 <= nRenderer && nRenderer <= nMaxRenderer, "nRenderer out of bounds"); + if (!pOutDev || nMaxRenderer < 0 || nRenderer < 0 || nRenderer > nMaxRenderer) + return false; + + // save settings of OutputDevice (should be done always since the + // output device is now provided by a call from outside the Writer) + pOutDev->Push(); + + // fdo#36815 for comments in margins print to a metafile + // and then scale that metafile down so that the comments + // will fit on the real page, and replay that scaled + // output to the real outputdevice + GDIMetaFile *pOrigRecorder(nullptr); + std::unique_ptr<GDIMetaFile> pMetaFile; + SwPostItMode nPostItMode = rPrintData.GetPrintPostIts(); + + // tdf#91680 Reserve space in margin for comments only if there are comments + const bool bHasPostItsToPrintInMargins = ( nPostItMode == SwPostItMode::InMargins ) && + sw_GetPostIts( &GetDoc()->getIDocumentFieldsAccess(), nullptr ); + + if ( bHasPostItsToPrintInMargins ) + { + //get and disable the existing recorder + pOrigRecorder = pOutDev->GetConnectMetaFile(); + pOutDev->SetConnectMetaFile(nullptr); + // turn off output to the device + pOutDev->EnableOutput(false); + // just record the rendering commands to the metafile + // instead + pMetaFile.reset(new GDIMetaFile); + pMetaFile->SetPrefSize(pOutDev->GetOutputSize()); + pMetaFile->SetPrefMapMode(pOutDev->GetMapMode()); + pMetaFile->Record(pOutDev); + } + + // Print/PDF export for (multi-)selection has already generated a + // temporary document with the selected text. + // (see XRenderable implementation in unotxdoc.cxx) + // It is implemented this way because PDF export calls this Prt function + // once per page and we do not like to always have the temporary document + // to be created that often here. + std::unique_ptr<SwViewShell> pShell(new SwViewShell(*this, nullptr, pOutDev)); + + SdrView *pDrawView = pShell->GetDrawView(); + if (pDrawView) + { + pDrawView->SetBufferedOutputAllowed( false ); + pDrawView->SetBufferedOverlayAllowed( false ); + } + + { // additional scope so that the CurrShell is reset before destroying the shell + + SET_CURR_SHELL( pShell.get() ); + + //JP 01.02.99: Bug 61335 - the ReadOnly flag is never copied + if( mpOpt->IsReadonly() ) + pShell->mpOpt->SetReadonly( true ); + + // save options at draw view: + SwDrawViewSave aDrawViewSave( pShell->GetDrawView() ); + pShell->PrepareForPrint( rPrintData, bIsPDFExport ); + + const sal_Int32 nPage = rPrintData.GetRenderData().GetPagesToPrint()[ nRenderer ]; + OSL_ENSURE( nPage < 0 || + rPrintData.GetRenderData().GetValidPagesSet().count( nPage ) == 1, + "SwViewShell::PrintOrPDFExport: nPage not valid" ); + SwViewShell *const pViewSh2 = (nPage < 0) + ? rPrintData.GetRenderData().m_pPostItShell.get()// post-it page + : pShell.get(); // a 'regular' page, not one from the post-it doc + + SwPageFrame const*const pStPage = + sw_getPage(*pViewSh2->GetLayout(), abs(nPage)); + OSL_ENSURE( pStPage, "failed to get start page" ); + if (!pStPage) + { + return false; + } + + //!! applying view options and formatting the document should now only be done in getRendererCount! + + ::SetSwVisArea( pViewSh2, pStPage->getFrameArea() ); + + pShell->InitPrt(pOutDev); + + ::SetSwVisArea( pViewSh2, pStPage->getFrameArea() ); + + pStPage->GetUpper()->PaintSwFrame( *pOutDev, pStPage->getFrameArea(), &rPrintData ); + + SwPaintQueue::Repaint(); + + SwPostItMgr *pPostItManager = bHasPostItsToPrintInMargins ? pShell->GetPostItMgr() : nullptr; + + if (pPostItManager) + { + pPostItManager->CalcRects(); + pPostItManager->LayoutPostIts(); + pPostItManager->DrawNotesForPage(pOutDev, nPage-1); + + //Stop recording now + pMetaFile->Stop(); + pMetaFile->WindStart(); + //Enable output to the device again + pOutDev->EnableOutput(); + //Restore the original recorder + pOutDev->SetConnectMetaFile(pOrigRecorder); + + //Now scale the recorded page down so the notes + //will fit in the final page + double fScale = 0.75; + long nOrigHeight = pStPage->getFrameArea().Height(); + long nNewHeight = nOrigHeight*fScale; + long nShiftY = (nOrigHeight-nNewHeight)/2; + pMetaFile->Scale( fScale, fScale ); + pMetaFile->WindStart(); + //Move the scaled page down to center it + //the other variant of Move does not map pixels + //back to the logical units correctly + pMetaFile->Move(0, convertTwipToMm100(nShiftY), pOutDev->GetDPIX(), pOutDev->GetDPIY()); + pMetaFile->WindStart(); + + //play back the scaled page + pMetaFile->Play(pOutDev); + pMetaFile.reset(); + } + } + + pShell.reset(); + + // restore settings of OutputDevice (should be done always now since the + // output device is now provided by a call from outside the Writer) + pOutDev->Pop(); + + return true; +} + +void SwViewShell::PrtOle2( SwDoc *pDoc, const SwViewOption *pOpt, const SwPrintData& rOptions, + vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) +{ + // For printing a shell is needed. Either the Doc already has one, then we + // create a new view, or it has none, then we create the first view. + std::unique_ptr<SwViewShell> pSh; + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + pSh.reset(new SwViewShell( *pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(), nullptr, &rRenderContext,VSHELLFLAG_SHARELAYOUT )); + else + pSh.reset(new SwViewShell( *pDoc, nullptr, pOpt, &rRenderContext)); + + { + SET_CURR_SHELL( pSh.get() ); + pSh->PrepareForPrint( rOptions ); + pSh->SetPrtFormatOption( true ); + + SwRect aSwRect( rRect ); + pSh->maVisArea = aSwRect; + + if ( pSh->GetViewOptions()->getBrowseMode() && + pSh->GetRingContainer().size() == 1 ) + { + pSh->InvalidateLayout( false ); + pSh->GetLayout()->Lower()->InvalidateSize(); + } + + // CalcPagesForPrint() should not be necessary here. The pages in the + // visible area will be formatted in SwRootFrame::PaintSwFrame(). + // Removing this gives us a performance gain during saving the + // document because the thumbnail creation will not trigger a complete + // formatting of the document. + + rRenderContext.Push( PushFlags::CLIPREGION ); + rRenderContext.IntersectClipRegion( aSwRect.SVRect() ); + pSh->GetLayout()->PaintSwFrame( rRenderContext, aSwRect ); + + rRenderContext.Pop(); + // first the CurrShell object needs to be destroyed! + } +} + +/// Check if the DocNodesArray contains fields. +bool SwViewShell::IsAnyFieldInDoc() const +{ + for (const SfxPoolItem* pItem : mxDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_FIELD)) + { + auto pFormatField = dynamic_cast<const SwFormatField*>(pItem); + if(pFormatField) + { + const SwTextField* pTextField = pFormatField->GetTextField(); + if( pTextField && pTextField->GetTextNode().GetNodes().IsDocNodes() ) + { + return true; + } + } + } + + for (const SfxPoolItem* pItem : mxDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INPUTFIELD)) + { + const SwFormatField* pFormatField = dynamic_cast<const SwFormatField*>(pItem); + if(pFormatField) + { + const SwTextField* pTextField = pFormatField->GetTextField(); + if( pTextField && pTextField->GetTextNode().GetNodes().IsDocNodes() ) + { + return true; + } + } + } + + return false; +} + +/// Saves some settings at the draw view +SwDrawViewSave::SwDrawViewSave( SdrView* pSdrView ) + : pDV( pSdrView ) + , bPrintControls(true) +{ + if ( pDV ) + { + bPrintControls = pDV->IsLayerPrintable( "Controls" ); + } +} + +SwDrawViewSave::~SwDrawViewSave() +{ + if ( pDV ) + { + pDV->SetLayerPrintable( "Controls", bPrintControls ); + } +} + +// OD 09.01.2003 #i6467# - method also called for page preview +void SwViewShell::PrepareForPrint( const SwPrintData &rOptions, bool bIsPDFExport ) + { + mpOpt->SetGraphic ( rOptions.m_bPrintGraphic ); + mpOpt->SetTable ( rOptions.m_bPrintTable ); + mpOpt->SetDraw ( rOptions.m_bPrintDraw ); + mpOpt->SetControl ( rOptions.m_bPrintControl ); + mpOpt->SetPageBack ( rOptions.m_bPrintPageBackground ); + // Font should not be black if it's a PDF Export + mpOpt->SetBlackFont( rOptions.m_bPrintBlackFont && !bIsPDFExport ); + + if ( HasDrawView() ) + { + SdrView *pDrawView = GetDrawView(); + // OD 09.01.2003 #i6467# - consider, if view shell belongs to page preview + if ( !IsPreview() ) + { + pDrawView->SetLayerPrintable( "Controls", rOptions.m_bPrintControl ); + } + else + { + pDrawView->SetLayerVisible( "Controls", rOptions.m_bPrintControl ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/view/vprint.hxx b/sw/source/core/view/vprint.hxx new file mode 100644 index 000000000..fc8e35c99 --- /dev/null +++ b/sw/source/core/view/vprint.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_VIEW_VPRINT_HXX +#define INCLUDED_SW_SOURCE_CORE_VIEW_VPRINT_HXX + +#include <sal/types.h> + +class SwRootFrame; +class SwPageFrame; + +SwPageFrame const* sw_getPage(SwRootFrame const& rLayout, sal_Int32 const nPage); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + -- cgit v1.2.3